On Wed, Jun 7, 2017 at 1:50 AM, Matthew Barth <msbarth@xxxxxxxxxxxxxxxxxx> wrote: > > On 06/06/17 8:33 AM, Guenter Roeck wrote: >> >> On 06/06/2017 12:02 AM, Andrew Jeffery wrote: >>> Over and above the features of the original patch is support for a >>> secondary >>> rotor measurement value that is provided by MAX31785 chips with a revised >>> firmware. The feature(s) of the firmware are determined at probe time and >>> extra >>> attributes exposed accordingly. Specifically, the MFR_REVISION 0x3040 of >>> the >>> firmware supports 'slow' and 'fast' rotor reads. The feature is >>> implemented by >>> command 0x90 (READ_FAN_SPEED_1) providing a 4-byte response (with the >>> 'fast' >>> measurement in the second word) rather than the 2-bytes response in the >>> original firmware (MFR_REVISION 0x3030). >>> >> >> Taking the pmbus driver question out, why would this warrant another >> non-standard >> attribute outside the ABI ? I could see the desire to replace the 'slow' >> access >> with the 'fast' one, but provide two attributes ? No, I don't see the >> point, sorry, >> even more so without detailed explanation why the second attribute in >> addition >> to the first one would add any value. > > In the case of counter-rotating(CR) fans which contain two rotors to provide > more airflow there are then two tach feedbacks. These CR fans take a single > target speed and provide individual feedbacks for each rotor contained > within the fan enclosure. Providing these individual feedbacks assists in > fan fault driven speed changes, improved thermal characterization among > other things. > > Maxim provided this as a 'slow' / 'fast' set of bytes to be user > compatible(so the 'slow' rotor speed, regardless of which rotor, is in the > first 2 bytes with the 'slow' version of firmware as well). In some cases, > mfg systems could have a mix of these revisions. Andrew, instead of creating the _fast sysfs nodes, I think you could expose the extra rotors are separate fan devices in sysfs. This would not introduce new ABI. Guenter, would this be acceptable to you? Cheers, Joel > >> >>> This feature is not documented in the public datasheet[1]. >>> >>> [1] https://datasheets.maximintegrated.com/en/ds/MAX31785.pdf >>> >>> The need to read a 4-byte value drives the addition of a helper that is a >>> cut-down version of i2c_smbus_xfer_emulated(), as 4-byte transactions >>> aren't a >>> defined transaction type in the PMBus spec. This seemed more tasteful >>> than >>> hacking the PMBus core to support the quirks of a single device. >>> >> >> That is why we have PMBus helper drivers. >> >> Guenter >> >>> Also changed from Timothy's original posting is I've massaged the locking >>> a bit >>> and removed what seemed to be a copy/paste bug around >>> max31785_fan_set_pulses() >>> setting the fan_command member. >>> >>> Tested on an IBM Witherspoon machine. >>> >>> Cheers, >>> >>> Andrew >>> >>> Documentation/hwmon/max31785 | 44 +++ >>> drivers/hwmon/Kconfig | 10 + >>> drivers/hwmon/Makefile | 1 + >>> drivers/hwmon/max31785.c | 824 >>> +++++++++++++++++++++++++++++++++++++++++++ >>> 4 files changed, 879 insertions(+) >>> create mode 100644 Documentation/hwmon/max31785 >>> create mode 100644 drivers/hwmon/max31785.c >>> >>> diff --git a/Documentation/hwmon/max31785 b/Documentation/hwmon/max31785 >>> new file mode 100644 >>> index 000000000000..dd891c06401e >>> --- /dev/null >>> +++ b/Documentation/hwmon/max31785 >>> @@ -0,0 +1,44 @@ >>> +Kernel driver max31785 >>> +====================== >>> + >>> +Supported chips: >>> + * Maxim MAX31785 >>> + Prefix: 'max31785' >>> + Addresses scanned: 0x52 0x53 0x54 0x55 >>> + Datasheet: http://pdfserv.maximintegrated.com/en/ds/MAX31785.pdf >>> + >>> +Author: Timothy Pearson <tpearson@xxxxxxxxxxxxxxxxxxxxx> >>> + >>> + >>> +Description >>> +----------- >>> + >>> +This driver implements support for the Maxim MAX31785 chip. >>> + >>> +The MAX31785 controls the speeds of up to six fans using six independent >>> +PWM outputs. The desired fan speeds (or PWM duty cycles) are written >>> +through the I2C interface. The outputs drive "4-wire" fans directly, >>> +or can be used to modulate the fan's power terminals using an external >>> +pass transistor. >>> + >>> +Tachometer inputs monitor fan tachometer logic outputs for precise >>> (+/-1%) >>> +monitoring and control of fan RPM as well as detection of fan failure. >>> + >>> + >>> +Sysfs entries >>> +------------- >>> + >>> +fan[1-6]_input RO fan tachometer speed in RPM >>> +fan[1-6]_fault RO fan experienced fault >>> +fan[1-6]_pulses RW tachometer pulses per fan revolution >>> +fan[1-6]_target RW desired fan speed in RPM >>> +pwm[1-6]_enable RW pwm mode, 0=disabled, 1=pwm, 2=rpm, >>> 3=automatic >>> +pwm[1-6] RW fan target duty cycle (0-255) >>> + >>> +Dynamic sysfs entries >>> +-------------------- >>> + >>> +Whether these entries are present depends on the firmware features >>> detected on >>> +the device during probe. >>> + >>> +fan[1-6]_input_fast RO fan tachometer speed in RPM (fast rotor >>> measurement) >>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig >>> index e80ca81577f4..c75d6072c823 100644 >>> --- a/drivers/hwmon/Kconfig >>> +++ b/drivers/hwmon/Kconfig >>> @@ -886,6 +886,16 @@ config SENSORS_MAX6697 >>> This driver can also be built as a module. If so, the module >>> will be called max6697. >>> +config SENSORS_MAX31785 >>> + tristate "Maxim MAX31785 sensor chip" >>> + depends on I2C >>> + help >>> + If you say yes here you get support for 6-Channel PWM-Output >>> + Fan RPM Controller. >>> + >>> + This driver can also be built as a module. If so, the module >>> + will be called max31785. >>> + >>> config SENSORS_MAX31790 >>> tristate "Maxim MAX31790 sensor chip" >>> depends on I2C >>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile >>> index f03dd0a15933..dc55722bee88 100644 >>> --- a/drivers/hwmon/Makefile >>> +++ b/drivers/hwmon/Makefile >>> @@ -119,6 +119,7 @@ obj-$(CONFIG_SENSORS_MAX6639) += max6639.o >>> obj-$(CONFIG_SENSORS_MAX6642) += max6642.o >>> obj-$(CONFIG_SENSORS_MAX6650) += max6650.o >>> obj-$(CONFIG_SENSORS_MAX6697) += max6697.o >>> +obj-$(CONFIG_SENSORS_MAX31785) += max31785.o >>> obj-$(CONFIG_SENSORS_MAX31790) += max31790.o >>> obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o >>> obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o >>> diff --git a/drivers/hwmon/max31785.c b/drivers/hwmon/max31785.c >>> new file mode 100644 >>> index 000000000000..fc03b7c8e723 >>> --- /dev/null >>> +++ b/drivers/hwmon/max31785.c >>> @@ -0,0 +1,824 @@ >>> +/* >>> + * max31785.c - Part of lm_sensors, Linux kernel modules for hardware >>> + * monitoring. >>> + * >>> + * (C) 2016 Raptor Engineering, LLC >>> + * >>> + * This program is free software; you can redistribute it and/or modify >>> + * it under the terms of the GNU General Public License as published by >>> + * the Free Software Foundation; either version 2 of the License, or >>> + * (at your option) any later version. >>> + * >>> + * This program is distributed in the hope that it will be useful, >>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>> + * GNU General Public License for more details. >>> + */ >>> + >>> +#include <linux/err.h> >>> +#include <linux/hwmon.h> >>> +#include <linux/hwmon-sysfs.h> >>> +#include <linux/i2c.h> >>> +#include <linux/init.h> >>> +#include <linux/jiffies.h> >>> +#include <linux/module.h> >>> +#include <linux/slab.h> >>> + >>> +/* MAX31785 device IDs */ >>> +#define MAX31785_MFR_ID 0x4d >>> +#define MAX31785_MFR_MODEL 0x53 >>> + >>> +/* MAX31785 registers */ >>> +#define MAX31785_REG_PAGE 0x00 >>> +#define MAX31785_PAGE_FAN_CONFIG(ch) (0x00 + (ch)) >>> +#define MAX31785_REG_FAN_CONFIG_1_2 0x3a >>> +#define MAX31785_REG_FAN_COMMAND_1 0x3b >>> +#define MAX31785_REG_STATUS_FANS_1_2 0x81 >>> +#define MAX31785_REG_FAN_SPEED_1 0x90 >>> +#define MAX31785_REG_MFR_ID 0x99 >>> +#define MAX31785_REG_MFR_MODEL 0x9a >>> +#define MAX31785_REG_MFR_REVISION 0x9b >>> +#define MAX31785_REG_MFR_FAN_CONFIG 0xf1 >>> +#define MAX31785_REG_READ_FAN_PWM 0xf3 >>> + >>> +/* Fan Config register bits */ >>> +#define MAX31785_FAN_CFG_PWM_ENABLE 0x80 >>> +#define MAX31785_FAN_CFG_CONTROL_MODE_RPM 0x40 >>> +#define MAX31785_FAN_CFG_PULSE_MASK 0x30 >>> +#define MAX31785_FAN_CFG_PULSE_SHIFT 4 >>> +#define MAX31785_FAN_CFG_PULSE_OFFSET 1 >>> + >>> +/* Fan Status register bits */ >>> +#define MAX31785_FAN_STATUS_FAULT_MASK 0x80 >>> + >>> +/* Fan Command constants */ >>> +#define MAX31785_FAN_COMMAND_PWM_RATIO 40 >>> + >>> +#define NR_CHANNEL 6 >>> + >>> +/* Addresses to scan */ >>> +static const unsigned short normal_i2c[] = { >>> + 0x52, 0x53, 0x54, 0x55, >>> + I2C_CLIENT_END >>> +}; >>> + >>> +#define MAX31785_CAP_FAST_ROTOR BIT(0) >>> + >>> +/* >>> + * Client data (each client gets its own) >>> + * >>> + * @lock: Protects device access and access to cached values >>> + * @valid: False until fields below it are valid >>> + * @last_updated: Last update time in jiffies >>> + */ >>> +struct max31785 { >>> + struct i2c_client *client; >>> + struct mutex lock; >>> + bool valid; >>> + unsigned long last_updated; >>> + u32 capabilities; >>> + >>> + /* Registers */ >>> + u8 fan_config[NR_CHANNEL]; >>> + u16 fan_command[NR_CHANNEL]; >>> + u8 mfr_fan_config[NR_CHANNEL]; >>> + u8 fault_status[NR_CHANNEL]; >>> + u16 pwm[NR_CHANNEL]; >>> + u16 tach_rpm[NR_CHANNEL]; >>> + u16 tach_rpm_fast[NR_CHANNEL]; >>> +}; >>> + >>> +static int max31785_set_page(struct i2c_client *client, >>> + u8 page) >>> +{ >>> + return i2c_smbus_write_byte_data(client, MAX31785_REG_PAGE, page); >>> +} >>> + >>> +static int read_fan_data(struct i2c_client *client, u8 fan, u8 reg, >>> + s32 (*read)(const struct i2c_client *, u8)) >>> +{ >>> + int rv; >>> + >>> + rv = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan)); >>> + if (rv < 0) >>> + return rv; >>> + >>> + return read(client, reg); >>> +} >>> + >>> +static inline int max31785_read_fan_byte(struct i2c_client *client, u8 >>> fan, >>> + u8 reg) >>> +{ >>> + return read_fan_data(client, fan, reg, i2c_smbus_read_byte_data); >>> +} >>> + >>> +static inline int max31785_read_fan_word(struct i2c_client *client, u8 >>> fan, >>> + u8 reg) >>> +{ >>> + return read_fan_data(client, fan, reg, i2c_smbus_read_word_data); >>> +} >>> + >>> +static int max31785_write_fan_byte(struct i2c_client *client, u8 fan, >>> + u8 reg, u8 data) >>> +{ >>> + int err; >>> + >>> + err = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan)); >>> + if (err < 0) >>> + return err; >>> + >>> + return i2c_smbus_write_byte_data(client, reg, data); >>> +} >>> + >>> +static int max31785_write_fan_word(struct i2c_client *client, u8 fan, >>> + u8 reg, u16 data) >>> +{ >>> + int err; >>> + >>> + err = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan)); >>> + if (err < 0) >>> + return err; >>> + >>> + return i2c_smbus_write_word_data(client, reg, data); >>> +} >>> + >>> +/* Cut down version of i2c_smbus_xfer_emulated(), reading 4 bytes */ >>> +static s64 max31785_smbus_read_long_data(struct i2c_client *client, u8 >>> command) >>> +{ >>> + unsigned char cmdbuf[1]; >>> + unsigned char rspbuf[4]; >>> + s64 rc; >>> + >>> + struct i2c_msg msg[2] = { >>> + { >>> + .addr = client->addr, >>> + .flags = 0, >>> + .len = sizeof(cmdbuf), >>> + .buf = cmdbuf, >>> + }, >>> + { >>> + .addr = client->addr, >>> + .flags = I2C_M_RD, >>> + .len = sizeof(rspbuf), >>> + .buf = rspbuf, >>> + }, >>> + }; >>> + >>> + cmdbuf[0] = command; >>> + >>> + rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); >>> + if (rc < 0) >>> + return rc; >>> + >>> + rc = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) | >>> + (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8)); >>> + >>> + return rc; >>> +} >>> + >>> +static int max31785_update_fan_speed(struct max31785 *data, u8 fan) >>> +{ >>> + s64 rc; >>> + >>> + rc = max31785_set_page(data->client, MAX31785_PAGE_FAN_CONFIG(fan)); >>> + if (rc) >>> + return rc; >>> + >>> + if (data->capabilities & MAX31785_CAP_FAST_ROTOR) { >>> + rc = max31785_smbus_read_long_data(data->client, >>> + MAX31785_REG_FAN_SPEED_1); >>> + if (rc < 0) >>> + return rc; >>> + >>> + data->tach_rpm[fan] = rc & 0xffff; >>> + data->tach_rpm_fast[fan] = (rc >> 16) & 0xffff; >>> + >>> + return rc; >>> + } >>> + >>> + rc = i2c_smbus_read_word_data(data->client, >>> MAX31785_REG_FAN_SPEED_1); >>> + if (rc < 0) >>> + return rc; >>> + >>> + data->tach_rpm[fan] = rc; >>> + >>> + return rc; >>> +} >>> + >>> +static inline bool is_automatic_control_mode(struct max31785 *data, >>> + int index) >>> +{ >>> + return data->fan_command[index] > 0x7fff; >>> +} >>> + >>> +static struct max31785 *max31785_update_device(struct device *dev) >>> +{ >>> + struct max31785 *data = dev_get_drvdata(dev); >>> + struct i2c_client *client = data->client; >>> + struct max31785 *ret = data; >>> + int rv; >>> + int i; >>> + >>> + mutex_lock(&data->lock); >>> + >>> + if (!time_after(jiffies, data->last_updated + HZ) && data->valid) { >>> + mutex_unlock(&data->lock); >>> + >>> + return ret; >>> + } >>> + >>> + for (i = 0; i < NR_CHANNEL; i++) { >>> + rv = max31785_read_fan_byte(client, i, >>> + MAX31785_REG_STATUS_FANS_1_2); >>> + if (rv < 0) >>> + goto abort; >>> + data->fault_status[i] = rv; >>> + >>> + rv = max31785_update_fan_speed(data, i); >>> + if (rv < 0) >>> + goto abort; >>> + >>> + if ((data->fan_config[i] >>> + & MAX31785_FAN_CFG_CONTROL_MODE_RPM) >>> + || is_automatic_control_mode(data, i)) { >>> + rv = max31785_read_fan_word(client, i, >>> + MAX31785_REG_READ_FAN_PWM); >>> + if (rv < 0) >>> + goto abort; >>> + data->pwm[i] = rv; >>> + } >>> + >>> + if (!is_automatic_control_mode(data, i)) { >>> + /* >>> + * Poke watchdog for manual fan control >>> + * >>> + * XXX (AJ): This isn't documented in the MAX31785 >>> + * datasheet, or anywhere else it seems. >>> + */ >>> + rv = max31785_write_fan_word(client, >>> + i, MAX31785_REG_FAN_COMMAND_1, >>> + data->fan_command[i]); >>> + if (rv < 0) >>> + goto abort; >>> + } >>> + } >>> + >>> + data->last_updated = jiffies; >>> + data->valid = true; >>> + >>> + mutex_unlock(&data->lock); >>> + >>> + return ret; >>> + >>> +abort: >>> + data->valid = false; >>> + >>> + mutex_unlock(&data->lock); >>> + >>> + return ERR_PTR(rv); >>> + >>> +} >>> + >>> +static ssize_t max31785_fan_set_target(struct max31785 *data, int >>> channel, >>> + long rpm) >>> +{ >>> + int rc; >>> + >>> + if (rpm > 0x7fff) >>> + return -EINVAL; >>> + >>> + mutex_lock(&data->lock); >>> + >>> + /* Write new RPM value */ >>> + data->fan_command[channel] = rpm; >>> + rc = max31785_write_fan_word(data->client, channel, >>> + MAX31785_REG_FAN_COMMAND_1, >>> + data->fan_command[channel]); >>> + >>> + mutex_unlock(&data->lock); >>> + >>> + return rc; >>> +} >>> + >>> +static ssize_t max31785_fan_set_pulses(struct max31785 *data, int >>> channel, >>> + long pulses) >>> +{ >>> + int rc; >>> + >>> + if (pulses > 4) >>> + return -EINVAL; >>> + >>> + mutex_lock(&data->lock); >>> + >>> + /* XXX (AJ): This sequence disables the fan and sets in PWM mode */ >>> + data->fan_config[channel] &= MAX31785_FAN_CFG_PULSE_MASK; >>> + data->fan_config[channel] |= ((pulses - >>> MAX31785_FAN_CFG_PULSE_OFFSET) >>> + << MAX31785_FAN_CFG_PULSE_SHIFT); >>> + >>> + /* Write new pulse value */ >>> + rc = max31785_write_fan_byte(data->client, channel, >>> + MAX31785_REG_FAN_CONFIG_1_2, >>> + data->fan_config[channel]); >>> + >>> + mutex_unlock(&data->lock); >>> + >>> + return rc; >>> +} >>> + >>> +static ssize_t max31785_pwm_set(struct max31785 *data, int channel, long >>> pwm) >>> +{ >>> + int rc; >>> + >>> + if (pwm > 255) >>> + return -EINVAL; >>> + >>> + mutex_lock(&data->lock); >>> + >>> + /* Write new PWM value */ >>> + data->fan_command[channel] = pwm * MAX31785_FAN_COMMAND_PWM_RATIO; >>> + rc = max31785_write_fan_word(data->client, channel, >>> + MAX31785_REG_FAN_COMMAND_1, >>> + data->fan_command[channel]); >>> + >>> + mutex_unlock(&data->lock); >>> + >>> + return rc; >>> +} >>> + >>> +static ssize_t max31785_pwm_enable(struct max31785 *data, int channel, >>> + long mode) >>> +{ >>> + struct i2c_client *client = data->client; >>> + int rc; >>> + >>> + mutex_lock(&data->lock); >>> + >>> + switch (mode) { >>> + case 0: >>> + data->fan_config[channel] = >>> + data->fan_config[channel] >>> + & ~MAX31785_FAN_CFG_PWM_ENABLE; >>> + break; >>> + case 1: /* fallthrough */ >>> + case 2: /* fallthrough */ >>> + case 3: >>> + data->fan_config[channel] = >>> + data->fan_config[channel] >>> + | MAX31785_FAN_CFG_PWM_ENABLE; >>> + break; >>> + default: >>> + rc = -EINVAL; >>> + goto done; >>> + >>> + } >>> + >>> + switch (mode) { >>> + case 0: >>> + break; >>> + case 1: >>> + data->fan_config[channel] = >>> + data->fan_config[channel] >>> + & ~MAX31785_FAN_CFG_CONTROL_MODE_RPM; >>> + break; >>> + case 2: >>> + data->fan_config[channel] = >>> + data->fan_config[channel] >>> + | MAX31785_FAN_CFG_CONTROL_MODE_RPM; >>> + break; >>> + case 3: >>> + data->fan_command[channel] = 0xffff; >>> + break; >>> + default: >>> + rc = -EINVAL; >>> + goto done; >>> + } >>> + >>> + rc = max31785_write_fan_byte(client, channel, >>> + MAX31785_REG_FAN_CONFIG_1_2, >>> + data->fan_config[channel]); >>> + >>> + if (!rc) >>> + rc = max31785_write_fan_word(client, channel, >>> + MAX31785_REG_FAN_COMMAND_1, >>> + data->fan_command[channel]); >>> + >>> +done: >>> + mutex_unlock(&data->lock); >>> + >>> + return rc; >>> +} >>> + >>> +static int max31785_init_fans(struct max31785 *data) >>> +{ >>> + struct i2c_client *client = data->client; >>> + int i, rv; >>> + >>> + for (i = 0; i < NR_CHANNEL; i++) { >>> + rv = max31785_read_fan_byte(client, i, >>> + MAX31785_REG_FAN_CONFIG_1_2); >>> + if (rv < 0) >>> + return rv; >>> + data->fan_config[i] = rv; >>> + >>> + rv = max31785_read_fan_word(client, i, >>> + MAX31785_REG_FAN_COMMAND_1); >>> + if (rv < 0) >>> + return rv; >>> + data->fan_command[i] = rv; >>> + >>> + rv = max31785_read_fan_byte(client, i, >>> + MAX31785_REG_MFR_FAN_CONFIG); >>> + if (rv < 0) >>> + return rv; >>> + data->mfr_fan_config[i] = rv; >>> + >>> + if (!((data->fan_config[i] >>> + & MAX31785_FAN_CFG_CONTROL_MODE_RPM) >>> + || is_automatic_control_mode(data, i))) { >>> + data->pwm[i] = 0; >>> + } >>> + } >>> + >>> + return rv; >>> +} >>> + >>> +/* Return 0 if detection is successful, -ENODEV otherwise */ >>> +static int max31785_detect(struct i2c_client *client, >>> + struct i2c_board_info *info) >>> +{ >>> + struct i2c_adapter *adapter = client->adapter; >>> + int rv; >>> + >>> + if (!i2c_check_functionality(adapter, >>> + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) >>> + return -ENODEV; >>> + >>> + /* Probe manufacturer / model registers */ >>> + rv = i2c_smbus_read_byte_data(client, MAX31785_REG_MFR_ID); >>> + if (rv < 0) >>> + return -ENODEV; >>> + if (rv != MAX31785_MFR_ID) >>> + return -ENODEV; >>> + >>> + rv = i2c_smbus_read_byte_data(client, MAX31785_REG_MFR_MODEL); >>> + if (rv < 0) >>> + return -ENODEV; >>> + if (rv != MAX31785_MFR_MODEL) >>> + return -ENODEV; >>> + >>> + strlcpy(info->type, "max31785", I2C_NAME_SIZE); >>> + >>> + return 0; >>> +} >>> + >>> +static const u32 max31785_fan_config[] = { >>> + HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT, >>> + HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT, >>> + HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT, >>> + HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT, >>> + HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT, >>> + HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT, >>> + 0 >>> +}; >>> + >>> +static const struct hwmon_channel_info max31785_fan = { >>> + .type = hwmon_fan, >>> + .config = max31785_fan_config, >>> +}; >>> + >>> +static const u32 max31785_pwm_config[] = { >>> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, >>> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, >>> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, >>> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, >>> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, >>> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, >>> + 0, >>> +}; >>> + >>> +static const struct hwmon_channel_info max31785_pwm = { >>> + .type = hwmon_pwm, >>> + .config = max31785_pwm_config >>> +}; >>> + >>> +static const struct hwmon_channel_info *max31785_info[] = { >>> + &max31785_fan, >>> + &max31785_pwm, >>> + NULL, >>> +}; >>> + >>> +static int max31785_read_fan(struct max31785 *data, u32 attr, int >>> channel, >>> + long *val) >>> +{ >>> + int rc = 0; >>> + >>> + switch (attr) { >>> + case hwmon_fan_pulses: >>> + { >>> + long pulses; >>> + >>> + pulses = data->fan_config[channel]; >>> + pulses &= MAX31785_FAN_CFG_PULSE_MASK; >>> + pulses >>= MAX31785_FAN_CFG_PULSE_SHIFT; >>> + pulses += MAX31785_FAN_CFG_PULSE_OFFSET; >>> + >>> + *val = pulses; >>> + break; >>> + } >>> + case hwmon_fan_target: >>> + { >>> + long target; >>> + >>> + mutex_lock(&data->lock); >>> + >>> + target = data->fan_command[channel]; >>> + >>> + if (!(data->fan_config[channel] & >>> + MAX31785_FAN_CFG_CONTROL_MODE_RPM)) >>> + target /= MAX31785_FAN_COMMAND_PWM_RATIO; >>> + >>> + *val = target; >>> + >>> + mutex_unlock(&data->lock); >>> + >>> + break; >>> + } >>> + case hwmon_fan_input: >>> + *val = data->tach_rpm[channel]; >>> + break; >>> + case hwmon_fan_fault: >>> + *val = !!(data->fault_status[channel] & >>> + MAX31785_FAN_STATUS_FAULT_MASK); >>> + break; >>> + default: >>> + rc = -EOPNOTSUPP; >>> + break; >>> + }; >>> + >>> + return rc; >>> +} >>> + >>> +static int max31785_fan_get_fast(struct device *dev, >>> + struct device_attribute *attr, char *buf) >>> +{ >>> + struct sensor_device_attribute_2 *attr2 = >>> to_sensor_dev_attr_2(attr); >>> + struct max31785 *data = max31785_update_device(dev); >>> + >>> + return sprintf(buf, "%d\n", data->tach_rpm_fast[attr2->index]); >>> +} >>> + >>> +static int max31785_read_pwm(struct max31785 *data, u32 attr, int >>> channel, >>> + long *val) >>> +{ >>> + bool is_auto; >>> + bool is_rpm; >>> + int rc; >>> + >>> + mutex_lock(&data->lock); >>> + >>> + is_rpm = !!(data->fan_config[channel] & >>> + MAX31785_FAN_CFG_CONTROL_MODE_RPM); >>> + is_auto = is_automatic_control_mode(data, channel); >>> + >>> + switch (attr) { >>> + case hwmon_pwm_enable: >>> + { >>> + bool pwm_enabled; >>> + >>> + pwm_enabled = (data->fan_config[channel] & >>> + MAX31785_FAN_CFG_PWM_ENABLE); >>> + >>> + if (!pwm_enabled) >>> + *val = 0; >>> + else if (is_auto) >>> + *val = 3; >>> + else if (is_rpm) >>> + *val = 2; >>> + else >>> + *val = 1; >>> + break; >>> + } >>> + case hwmon_pwm_input: >>> + if (is_rpm || is_auto) >>> + *val = data->pwm[channel] / 100; >>> + else >>> + *val = data->fan_command[channel] >>> + / MAX31785_FAN_COMMAND_PWM_RATIO; >>> + break; >>> + default: >>> + rc = -EOPNOTSUPP; >>> + }; >>> + >>> + mutex_unlock(&data->lock); >>> + >>> + return rc; >>> +} >>> + >>> +static int max31785_read(struct device *dev, enum hwmon_sensor_types >>> type, >>> + u32 attr, int channel, long *val) >>> +{ >>> + struct max31785 *data; >>> + int rc; >>> + >>> + data = max31785_update_device(dev); >>> + >>> + if (IS_ERR(data)) >>> + return PTR_ERR(data); >>> + >>> + switch (type) { >>> + case hwmon_fan: >>> + return max31785_read_fan(data, attr, channel, val); >>> + case hwmon_pwm: >>> + return max31785_read_pwm(data, attr, channel, val); >>> + default: >>> + rc = -EOPNOTSUPP; >>> + } >>> + >>> + return rc; >>> +} >>> + >>> +static int max31785_write_fan(struct max31785 *data, u32 attr, int >>> channel, >>> + long val) >>> +{ >>> + int rc; >>> + >>> + switch (attr) { >>> + break; >>> + case hwmon_fan_pulses: >>> + return max31785_fan_set_pulses(data, channel, val); >>> + case hwmon_fan_target: >>> + return max31785_fan_set_target(data, channel, val); >>> + default: >>> + rc = -EOPNOTSUPP; >>> + }; >>> + >>> + return rc; >>> +} >>> + >>> +static int max31785_write_pwm(struct max31785 *data, u32 attr, int >>> channel, >>> + long val) >>> +{ >>> + int rc; >>> + >>> + switch (attr) { >>> + case hwmon_pwm_enable: >>> + return max31785_pwm_enable(data, channel, val); >>> + case hwmon_pwm_input: >>> + return max31785_pwm_set(data, channel, val); >>> + default: >>> + rc = -EOPNOTSUPP; >>> + }; >>> + >>> + return rc; >>> +} >>> + >>> +static int max31785_write(struct device *dev, enum hwmon_sensor_types >>> type, >>> + u32 attr, int channel, long val) >>> +{ >>> + struct max31785 *data; >>> + int rc; >>> + >>> + data = dev_get_drvdata(dev); >>> + >>> + switch (type) { >>> + case hwmon_fan: >>> + return max31785_write_fan(data, attr, channel, val); >>> + case hwmon_pwm: >>> + return max31785_write_pwm(data, attr, channel, val); >>> + default: >>> + rc = -EOPNOTSUPP; >>> + } >>> + >>> + return rc; >>> + >>> +} >>> + >>> +static umode_t max31785_is_visible(const void *_data, >>> + enum hwmon_sensor_types type, u32 attr, int channel) >>> +{ >>> + switch (type) { >>> + case hwmon_fan: >>> + switch (attr) { >>> + case hwmon_fan_input: >>> + case hwmon_fan_fault: >>> + return 0444; >>> + case hwmon_fan_pulses: >>> + case hwmon_fan_target: >>> + return 0644; >>> + }; >>> + case hwmon_pwm: >>> + return 0644; >>> + default: >>> + return 0; >>> + }; >>> +} >>> + >>> +static const struct hwmon_ops max31785_hwmon_ops = { >>> + .is_visible = max31785_is_visible, >>> + .read = max31785_read, >>> + .write = max31785_write, >>> +}; >>> + >>> +static const struct hwmon_chip_info max31785_chip_info = { >>> + .ops = &max31785_hwmon_ops, >>> + .info = max31785_info, >>> +}; >>> + >>> +static SENSOR_DEVICE_ATTR(fan1_input_fast, 0444, max31785_fan_get_fast, >>> + NULL, 0); >>> +static SENSOR_DEVICE_ATTR(fan2_input_fast, 0444, max31785_fan_get_fast, >>> + NULL, 1); >>> +static SENSOR_DEVICE_ATTR(fan3_input_fast, 0444, max31785_fan_get_fast, >>> + NULL, 2); >>> +static SENSOR_DEVICE_ATTR(fan4_input_fast, 0444, max31785_fan_get_fast, >>> + NULL, 3); >>> +static SENSOR_DEVICE_ATTR(fan5_input_fast, 0444, max31785_fan_get_fast, >>> + NULL, 4); >>> +static SENSOR_DEVICE_ATTR(fan6_input_fast, 0444, max31785_fan_get_fast, >>> + NULL, 5); >>> + >>> +static struct attribute *max31785_attrs[] = { >>> + &sensor_dev_attr_fan1_input_fast.dev_attr.attr, >>> + &sensor_dev_attr_fan2_input_fast.dev_attr.attr, >>> + &sensor_dev_attr_fan3_input_fast.dev_attr.attr, >>> + &sensor_dev_attr_fan4_input_fast.dev_attr.attr, >>> + &sensor_dev_attr_fan5_input_fast.dev_attr.attr, >>> + &sensor_dev_attr_fan6_input_fast.dev_attr.attr, >>> + NULL, >>> +}; >>> +ATTRIBUTE_GROUPS(max31785); >>> + >>> +static int max31785_get_capabilities(struct max31785 *data) >>> +{ >>> + s32 rc; >>> + >>> + rc = i2c_smbus_read_word_data(data->client, >>> MAX31785_REG_MFR_REVISION); >>> + if (rc < 0) >>> + return rc; >>> + >>> + if (rc == 0x3040) >>> + data->capabilities |= MAX31785_CAP_FAST_ROTOR; >>> + >>> + return 0; >>> +} >>> + >>> +static int max31785_probe(struct i2c_client *client, >>> + const struct i2c_device_id *id) >>> +{ >>> + struct i2c_adapter *adapter = client->adapter; >>> + const struct attribute_group **extra_groups; >>> + struct device *dev = &client->dev; >>> + struct device *hwmon_dev; >>> + struct max31785 *data; >>> + int rc; >>> + >>> + if (!i2c_check_functionality(adapter, >>> + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) >>> + return -ENODEV; >>> + >>> + data = devm_kzalloc(dev, sizeof(struct max31785), GFP_KERNEL); >>> + if (!data) >>> + return -ENOMEM; >>> + >>> + data->client = client; >>> + mutex_init(&data->lock); >>> + >>> + rc = max31785_init_fans(data); >>> + if (rc) >>> + return rc; >>> + >>> + rc = max31785_get_capabilities(data); >>> + if (rc < 0) >>> + return rc; >>> + >>> + if (data->capabilities & MAX31785_CAP_FAST_ROTOR) >>> + extra_groups = max31785_groups; >>> + >>> + hwmon_dev = devm_hwmon_device_register_with_info(dev, >>> + client->name, data, &max31785_chip_info, extra_groups); >>> + >>> + return PTR_ERR_OR_ZERO(hwmon_dev); >>> +} >>> + >>> +static const struct i2c_device_id max31785_id[] = { >>> + { "max31785", 0 }, >>> + { } >>> +}; >>> +MODULE_DEVICE_TABLE(i2c, max31785_id); >>> + >>> +static struct i2c_driver max31785_driver = { >>> + .class = I2C_CLASS_HWMON, >>> + .probe = max31785_probe, >>> + .driver = { >>> + .name = "max31785", >>> + }, >>> + .id_table = max31785_id, >>> + .detect = max31785_detect, >>> + .address_list = normal_i2c, >>> +}; >>> + >>> +module_i2c_driver(max31785_driver); >>> + >>> +MODULE_AUTHOR("Timothy Pearson <tpearson@xxxxxxxxxxxxxxxxxxxxx>"); >>> +MODULE_DESCRIPTION("MAX31785 sensor driver"); >>> +MODULE_LICENSE("GPL"); >>> >> > -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html