| From b3991e27b26930ef46206dad17715d8ac2f38a4e Mon Sep 17 00:00:00 2001 |
| From: Timothy Pearson <tpearson@raptorengineering.com> |
| Date: Tue, 11 Oct 2016 09:35:51 -0500 |
| Subject: [PATCH] hwmon: Add support for MAX31785 intelligent fan controller |
| |
| Add a basic driver for the MAX31785, focusing on the fan control |
| features but ignoring the temperature and voltage monitoring |
| features of the device. |
| |
| This driver supports all fan control modes and tachometer / PWM |
| readback where applicable. |
| |
| Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com> |
| Signed-off-by: Joel Stanley <joel@jms.id.au> |
| --- |
| Documentation/hwmon/max31785 | 36 +++ |
| drivers/hwmon/Kconfig | 10 + |
| drivers/hwmon/Makefile | 1 + |
| drivers/hwmon/max31785.c | 714 +++++++++++++++++++++++++++++++++++++++++++ |
| 4 files changed, 761 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 0000000..0911d20 |
| --- /dev/null |
| +++ b/Documentation/hwmon/max31785 |
| @@ -0,0 +1,36 @@ |
| +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@raptorengineering.com> |
| + |
| + |
| +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) |
| diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig |
| index 190d270..136605d 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 d2cb7e8..e8ba5c6 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 0000000..fb7b3f0 |
| --- /dev/null |
| +++ b/drivers/hwmon/max31785.c |
| @@ -0,0 +1,714 @@ |
| +/* |
| + * 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_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 }; |
| + |
| +/* |
| + * Client data (each client gets its own) |
| + */ |
| +struct max31785_data { |
| + struct i2c_client *client; |
| + struct mutex device_lock; |
| + bool valid; /* zero until following fields are valid */ |
| + unsigned long last_updated; /* in jiffies */ |
| + |
| + /* register values */ |
| + u8 fan_config[NR_CHANNEL]; |
| + u16 fan_command[NR_CHANNEL]; |
| + u8 mfr_fan_config[NR_CHANNEL]; |
| + u8 fault_status[NR_CHANNEL]; |
| + u16 tach_rpm[NR_CHANNEL]; |
| + u16 pwm[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 max31785_read_fan_data(struct i2c_client *client, |
| + u8 fan, u8 reg, u8 byte_mode) |
| +{ |
| + int rv; |
| + |
| + rv = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan)); |
| + if (rv < 0) |
| + return rv; |
| + |
| + if (byte_mode) |
| + rv = i2c_smbus_read_byte_data(client, reg); |
| + else |
| + rv = i2c_smbus_read_word_data(client, reg); |
| + |
| + return rv; |
| +} |
| + |
| +static int max31785_write_fan_data(struct i2c_client *client, |
| + u8 fan, u8 reg, u16 data, |
| + u8 byte_mode) |
| +{ |
| + int err; |
| + |
| + err = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan)); |
| + if (err < 0) |
| + return err; |
| + |
| + if (byte_mode) |
| + err = i2c_smbus_write_byte_data(client, reg, data); |
| + else |
| + err = i2c_smbus_write_word_data(client, reg, data); |
| + |
| + if (err < 0) |
| + return err; |
| + |
| + return 0; |
| +} |
| + |
| +static bool is_automatic_control_mode(struct max31785_data *data, |
| + int index) |
| +{ |
| + if (data->fan_command[index] > 0x7fff) |
| + return true; |
| + else |
| + return false; |
| +} |
| + |
| +static struct max31785_data *max31785_update_device(struct device *dev) |
| +{ |
| + struct max31785_data *data = dev_get_drvdata(dev); |
| + struct i2c_client *client = data->client; |
| + struct max31785_data *ret = data; |
| + int i; |
| + int rv; |
| + |
| + mutex_lock(&data->device_lock); |
| + |
| + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { |
| + for (i = 0; i < NR_CHANNEL; i++) { |
| + rv = max31785_read_fan_data(client, i, |
| + MAX31785_REG_STATUS_FANS_1_2, 1); |
| + if (rv < 0) |
| + goto abort; |
| + data->fault_status[i] = rv; |
| + |
| + rv = max31785_read_fan_data(client, i, |
| + MAX31785_REG_FAN_SPEED_1, 0); |
| + if (rv < 0) |
| + goto abort; |
| + data->tach_rpm[i] = rv; |
| + |
| + if ((data->fan_config[i] |
| + & MAX31785_FAN_CFG_CONTROL_MODE_RPM) |
| + || is_automatic_control_mode(data, i)) { |
| + rv = max31785_read_fan_data(client, i, |
| + MAX31785_REG_READ_FAN_PWM, 0); |
| + if (rv < 0) |
| + goto abort; |
| + data->pwm[i] = rv; |
| + } |
| + |
| + if (!is_automatic_control_mode(data, i)) { |
| + /* Poke watchdog for manual fan control */ |
| + rv = max31785_write_fan_data(client, |
| + i, MAX31785_REG_FAN_COMMAND_1, |
| + data->fan_command[i], 0); |
| + if (rv < 0) |
| + goto abort; |
| + } |
| + } |
| + |
| + data->last_updated = jiffies; |
| + data->valid = true; |
| + } |
| + goto done; |
| + |
| +abort: |
| + data->valid = false; |
| + ret = ERR_PTR(rv); |
| + |
| +done: |
| + mutex_unlock(&data->device_lock); |
| + |
| + return ret; |
| +} |
| + |
| +static ssize_t get_fan(struct device *dev, |
| + struct device_attribute *devattr, char *buf) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = max31785_update_device(dev); |
| + |
| + if (IS_ERR(data)) |
| + return PTR_ERR(data); |
| + |
| + return sprintf(buf, "%d\n", data->tach_rpm[attr->index]); |
| +} |
| + |
| +static ssize_t get_fan_target(struct device *dev, |
| + struct device_attribute *devattr, char *buf) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = max31785_update_device(dev); |
| + int rpm; |
| + |
| + if (IS_ERR(data)) |
| + return PTR_ERR(data); |
| + |
| + if (data->fan_config[attr->index] |
| + & MAX31785_FAN_CFG_CONTROL_MODE_RPM) |
| + rpm = data->fan_command[attr->index]; |
| + else |
| + rpm = data->fan_command[attr->index] |
| + / MAX31785_FAN_COMMAND_PWM_RATIO; |
| + |
| + return sprintf(buf, "%d\n", rpm); |
| +} |
| + |
| +static ssize_t set_fan_target(struct device *dev, |
| + struct device_attribute *devattr, |
| + const char *buf, size_t count) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = dev_get_drvdata(dev); |
| + struct i2c_client *client = data->client; |
| + unsigned long rpm; |
| + int err; |
| + |
| + err = kstrtoul(buf, 10, &rpm); |
| + if (err) |
| + return err; |
| + |
| + if (rpm > 0x7fff) |
| + return -EINVAL; |
| + |
| + mutex_lock(&data->device_lock); |
| + |
| + /* Write new RPM value */ |
| + data->fan_command[attr->index] = rpm; |
| + err = max31785_write_fan_data(client, attr->index, |
| + MAX31785_REG_FAN_COMMAND_1, |
| + data->fan_command[attr->index], 0); |
| + |
| + mutex_unlock(&data->device_lock); |
| + |
| + if (err < 0) |
| + return err; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t get_fan_pulses(struct device *dev, |
| + struct device_attribute *devattr, char *buf) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = max31785_update_device(dev); |
| + int pulses; |
| + |
| + if (IS_ERR(data)) |
| + return PTR_ERR(data); |
| + |
| + pulses = ((data->fan_config[attr->index] & MAX31785_FAN_CFG_PULSE_MASK) |
| + >> MAX31785_FAN_CFG_PULSE_SHIFT) |
| + + MAX31785_FAN_CFG_PULSE_OFFSET; |
| + |
| + return sprintf(buf, "%d\n", pulses); |
| +} |
| + |
| +static ssize_t set_fan_pulses(struct device *dev, |
| + struct device_attribute *devattr, |
| + const char *buf, size_t count) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = dev_get_drvdata(dev); |
| + struct i2c_client *client = data->client; |
| + unsigned long pulses; |
| + int err; |
| + |
| + err = kstrtoul(buf, 10, &pulses); |
| + if (err) |
| + return err; |
| + |
| + if (pulses > 4) |
| + return -EINVAL; |
| + |
| + data->fan_config[attr->index] &= MAX31785_FAN_CFG_PULSE_MASK; |
| + data->fan_config[attr->index] |= |
| + ((pulses - MAX31785_FAN_CFG_PULSE_OFFSET) |
| + << MAX31785_FAN_CFG_PULSE_SHIFT); |
| + |
| + mutex_lock(&data->device_lock); |
| + |
| + /* Write new pulse value */ |
| + data->fan_command[attr->index] = pulses; |
| + err = max31785_write_fan_data(client, attr->index, |
| + MAX31785_REG_FAN_CONFIG_1_2, |
| + data->fan_config[attr->index], 1); |
| + |
| + mutex_unlock(&data->device_lock); |
| + |
| + if (err < 0) |
| + return err; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t get_pwm(struct device *dev, |
| + struct device_attribute *devattr, char *buf) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = max31785_update_device(dev); |
| + int pwm; |
| + |
| + if (IS_ERR(data)) |
| + return PTR_ERR(data); |
| + |
| + if ((data->fan_config[attr->index] |
| + & MAX31785_FAN_CFG_CONTROL_MODE_RPM) |
| + || is_automatic_control_mode(data, attr->index)) |
| + pwm = data->pwm[attr->index] / 100; |
| + else |
| + pwm = data->fan_command[attr->index] |
| + / MAX31785_FAN_COMMAND_PWM_RATIO; |
| + |
| + return sprintf(buf, "%d\n", pwm); |
| +} |
| + |
| +static ssize_t set_pwm(struct device *dev, |
| + struct device_attribute *devattr, |
| + const char *buf, size_t count) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = dev_get_drvdata(dev); |
| + struct i2c_client *client = data->client; |
| + unsigned long pwm; |
| + int err; |
| + |
| + err = kstrtoul(buf, 10, &pwm); |
| + if (err) |
| + return err; |
| + |
| + if (pwm > 255) |
| + return -EINVAL; |
| + |
| + mutex_lock(&data->device_lock); |
| + |
| + /* Write new PWM value */ |
| + data->fan_command[attr->index] = pwm * MAX31785_FAN_COMMAND_PWM_RATIO; |
| + err = max31785_write_fan_data(client, attr->index, |
| + MAX31785_REG_FAN_COMMAND_1, |
| + data->fan_command[attr->index], 0); |
| + |
| + mutex_unlock(&data->device_lock); |
| + |
| + if (err < 0) |
| + return err; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t get_pwm_enable(struct device *dev, |
| + struct device_attribute *devattr, char *buf) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = max31785_update_device(dev); |
| + int mode; |
| + |
| + if (IS_ERR(data)) |
| + return PTR_ERR(data); |
| + |
| + if (!(data->fan_config[attr->index] & MAX31785_FAN_CFG_PWM_ENABLE)) |
| + mode = 0; |
| + else if (is_automatic_control_mode(data, attr->index)) |
| + mode = 3; |
| + else if (data->fan_config[attr->index] |
| + & MAX31785_FAN_CFG_CONTROL_MODE_RPM) |
| + mode = 2; |
| + else |
| + mode = 1; |
| + |
| + return sprintf(buf, "%d\n", mode); |
| +} |
| + |
| +static ssize_t set_pwm_enable(struct device *dev, |
| + struct device_attribute *devattr, |
| + const char *buf, size_t count) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = dev_get_drvdata(dev); |
| + struct i2c_client *client = data->client; |
| + unsigned long mode; |
| + int err; |
| + |
| + err = kstrtoul(buf, 10, &mode); |
| + if (err) |
| + return err; |
| + |
| + switch (mode) { |
| + case 0: |
| + data->fan_config[attr->index] = |
| + data->fan_config[attr->index] |
| + & ~MAX31785_FAN_CFG_PWM_ENABLE; |
| + break; |
| + case 1: |
| + case 2: |
| + case 3: |
| + data->fan_config[attr->index] = |
| + data->fan_config[attr->index] |
| + | MAX31785_FAN_CFG_PWM_ENABLE; |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + |
| + switch (mode) { |
| + case 0: |
| + break; |
| + case 1: |
| + data->fan_config[attr->index] = |
| + data->fan_config[attr->index] |
| + & ~MAX31785_FAN_CFG_CONTROL_MODE_RPM; |
| + break; |
| + case 2: |
| + data->fan_config[attr->index] = |
| + data->fan_config[attr->index] |
| + | MAX31785_FAN_CFG_CONTROL_MODE_RPM; |
| + break; |
| + case 3: |
| + data->fan_command[attr->index] = 0xffff; |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + |
| + mutex_lock(&data->device_lock); |
| + |
| + err = max31785_write_fan_data(client, attr->index, |
| + MAX31785_REG_FAN_CONFIG_1_2, |
| + data->fan_config[attr->index], 1); |
| + |
| + if (err < 0) |
| + goto abort; |
| + |
| + err = max31785_write_fan_data(client, attr->index, |
| + MAX31785_REG_FAN_COMMAND_1, |
| + data->fan_command[attr->index], 0); |
| + |
| +abort: |
| + mutex_unlock(&data->device_lock); |
| + |
| + if (err < 0) |
| + return err; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t get_fan_fault(struct device *dev, |
| + struct device_attribute *devattr, char *buf) |
| +{ |
| + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| + struct max31785_data *data = max31785_update_device(dev); |
| + int fault; |
| + |
| + if (IS_ERR(data)) |
| + return PTR_ERR(data); |
| + |
| + fault = !!(data->fault_status[attr->index] |
| + & MAX31785_FAN_STATUS_FAULT_MASK); |
| + |
| + return sprintf(buf, "%d\n", fault); |
| +} |
| + |
| +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0); |
| +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1); |
| +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2); |
| +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3); |
| +static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, get_fan, NULL, 4); |
| +static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, get_fan, NULL, 5); |
| + |
| +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan_fault, NULL, 0); |
| +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, get_fan_fault, NULL, 1); |
| +static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, get_fan_fault, NULL, 2); |
| +static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, get_fan_fault, NULL, 3); |
| +static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, get_fan_fault, NULL, 4); |
| +static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, get_fan_fault, NULL, 5); |
| + |
| +static SENSOR_DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO, |
| + get_fan_target, set_fan_target, 0); |
| +static SENSOR_DEVICE_ATTR(fan2_target, S_IWUSR | S_IRUGO, |
| + get_fan_target, set_fan_target, 1); |
| +static SENSOR_DEVICE_ATTR(fan3_target, S_IWUSR | S_IRUGO, |
| + get_fan_target, set_fan_target, 2); |
| +static SENSOR_DEVICE_ATTR(fan4_target, S_IWUSR | S_IRUGO, |
| + get_fan_target, set_fan_target, 3); |
| +static SENSOR_DEVICE_ATTR(fan5_target, S_IWUSR | S_IRUGO, |
| + get_fan_target, set_fan_target, 4); |
| +static SENSOR_DEVICE_ATTR(fan6_target, S_IWUSR | S_IRUGO, |
| + get_fan_target, set_fan_target, 5); |
| + |
| +static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, |
| + get_fan_pulses, set_fan_pulses, 0); |
| +static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO, |
| + get_fan_pulses, set_fan_pulses, 1); |
| +static SENSOR_DEVICE_ATTR(fan3_pulses, S_IWUSR | S_IRUGO, |
| + get_fan_pulses, set_fan_pulses, 2); |
| +static SENSOR_DEVICE_ATTR(fan4_pulses, S_IWUSR | S_IRUGO, |
| + get_fan_pulses, set_fan_pulses, 3); |
| +static SENSOR_DEVICE_ATTR(fan5_pulses, S_IWUSR | S_IRUGO, |
| + get_fan_pulses, set_fan_pulses, 4); |
| +static SENSOR_DEVICE_ATTR(fan6_pulses, S_IWUSR | S_IRUGO, |
| + get_fan_pulses, set_fan_pulses, 5); |
| + |
| +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 0); |
| +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 1); |
| +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 2); |
| +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 3); |
| +static SENSOR_DEVICE_ATTR(pwm5, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 4); |
| +static SENSOR_DEVICE_ATTR(pwm6, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 5); |
| + |
| +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, |
| + get_pwm_enable, set_pwm_enable, 0); |
| +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, |
| + get_pwm_enable, set_pwm_enable, 1); |
| +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, |
| + get_pwm_enable, set_pwm_enable, 2); |
| +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, |
| + get_pwm_enable, set_pwm_enable, 3); |
| +static SENSOR_DEVICE_ATTR(pwm5_enable, S_IWUSR | S_IRUGO, |
| + get_pwm_enable, set_pwm_enable, 4); |
| +static SENSOR_DEVICE_ATTR(pwm6_enable, S_IWUSR | S_IRUGO, |
| + get_pwm_enable, set_pwm_enable, 5); |
| + |
| +static struct attribute *max31785_attrs[] = { |
| + &sensor_dev_attr_fan1_input.dev_attr.attr, |
| + &sensor_dev_attr_fan2_input.dev_attr.attr, |
| + &sensor_dev_attr_fan3_input.dev_attr.attr, |
| + &sensor_dev_attr_fan4_input.dev_attr.attr, |
| + &sensor_dev_attr_fan5_input.dev_attr.attr, |
| + &sensor_dev_attr_fan6_input.dev_attr.attr, |
| + |
| + &sensor_dev_attr_fan1_fault.dev_attr.attr, |
| + &sensor_dev_attr_fan2_fault.dev_attr.attr, |
| + &sensor_dev_attr_fan3_fault.dev_attr.attr, |
| + &sensor_dev_attr_fan4_fault.dev_attr.attr, |
| + &sensor_dev_attr_fan5_fault.dev_attr.attr, |
| + &sensor_dev_attr_fan6_fault.dev_attr.attr, |
| + |
| + &sensor_dev_attr_fan1_target.dev_attr.attr, |
| + &sensor_dev_attr_fan2_target.dev_attr.attr, |
| + &sensor_dev_attr_fan3_target.dev_attr.attr, |
| + &sensor_dev_attr_fan4_target.dev_attr.attr, |
| + &sensor_dev_attr_fan5_target.dev_attr.attr, |
| + &sensor_dev_attr_fan6_target.dev_attr.attr, |
| + |
| + &sensor_dev_attr_fan1_pulses.dev_attr.attr, |
| + &sensor_dev_attr_fan2_pulses.dev_attr.attr, |
| + &sensor_dev_attr_fan3_pulses.dev_attr.attr, |
| + &sensor_dev_attr_fan4_pulses.dev_attr.attr, |
| + &sensor_dev_attr_fan5_pulses.dev_attr.attr, |
| + &sensor_dev_attr_fan6_pulses.dev_attr.attr, |
| + |
| + &sensor_dev_attr_pwm1.dev_attr.attr, |
| + &sensor_dev_attr_pwm2.dev_attr.attr, |
| + &sensor_dev_attr_pwm3.dev_attr.attr, |
| + &sensor_dev_attr_pwm4.dev_attr.attr, |
| + &sensor_dev_attr_pwm5.dev_attr.attr, |
| + &sensor_dev_attr_pwm6.dev_attr.attr, |
| + |
| + &sensor_dev_attr_pwm1_enable.dev_attr.attr, |
| + &sensor_dev_attr_pwm2_enable.dev_attr.attr, |
| + &sensor_dev_attr_pwm3_enable.dev_attr.attr, |
| + &sensor_dev_attr_pwm4_enable.dev_attr.attr, |
| + &sensor_dev_attr_pwm5_enable.dev_attr.attr, |
| + &sensor_dev_attr_pwm6_enable.dev_attr.attr, |
| + NULL |
| +}; |
| + |
| +static umode_t max31785_attrs_visible(struct kobject *kobj, |
| + struct attribute *a, int n) |
| +{ |
| + return a->mode; |
| +} |
| + |
| +static const struct attribute_group max31785_group = { |
| + .attrs = max31785_attrs, |
| + .is_visible = max31785_attrs_visible, |
| +}; |
| +__ATTRIBUTE_GROUPS(max31785); |
| + |
| +static int max31785_init_client(struct i2c_client *client, |
| + struct max31785_data *data) |
| +{ |
| + int i, rv; |
| + |
| + for (i = 0; i < NR_CHANNEL; i++) { |
| + rv = max31785_read_fan_data(client, i, |
| + MAX31785_REG_FAN_CONFIG_1_2, 1); |
| + if (rv < 0) |
| + return rv; |
| + data->fan_config[i] = rv; |
| + |
| + rv = max31785_read_fan_data(client, i, |
| + MAX31785_REG_FAN_COMMAND_1, 0); |
| + if (rv < 0) |
| + return rv; |
| + data->fan_command[i] = rv; |
| + |
| + rv = max31785_read_fan_data(client, i, |
| + MAX31785_REG_MFR_FAN_CONFIG, 1); |
| + 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 int max31785_probe(struct i2c_client *client, |
| + const struct i2c_device_id *id) |
| +{ |
| + struct i2c_adapter *adapter = client->adapter; |
| + struct device *dev = &client->dev; |
| + struct max31785_data *data; |
| + struct device *hwmon_dev; |
| + int err; |
| + |
| + if (!i2c_check_functionality(adapter, |
| + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) |
| + return -ENODEV; |
| + |
| + data = devm_kzalloc(dev, sizeof(struct max31785_data), GFP_KERNEL); |
| + if (!data) |
| + return -ENOMEM; |
| + |
| + data->client = client; |
| + mutex_init(&data->device_lock); |
| + |
| + /* |
| + * Initialize the max31785 chip |
| + */ |
| + err = max31785_init_client(client, data); |
| + if (err) |
| + return err; |
| + |
| + hwmon_dev = devm_hwmon_device_register_with_groups(dev, |
| + client->name, data, max31785_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@raptorengineering.com>"); |
| +MODULE_DESCRIPTION("MAX31785 sensor driver"); |
| +MODULE_LICENSE("GPL"); |
| -- |
| 1.8.3.1 |
| |