From c25fb816298138a4b12c606f0aaa018bdd3cc09c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 9 Jan 2013 08:12:46 -0800 Subject: hwmon: Retire SENSORS_LIMIT SENSORS_LIMIT and clamp_val have the same functionality, so retire SENSORS_LIMIT as it is no longer needed. Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- Documentation/hwmon/sysfs-interface | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface index 1f4dd855a299..79f8257dd790 100644 --- a/Documentation/hwmon/sysfs-interface +++ b/Documentation/hwmon/sysfs-interface @@ -722,14 +722,14 @@ add/subtract if it has been divided before the add/subtract. What to do if a value is found to be invalid, depends on the type of the sysfs attribute that is being set. If it is a continuous setting like a tempX_max or inX_max attribute, then the value should be clamped to its -limits using SENSORS_LIMIT(value, min_limit, max_limit). If it is not -continuous like for example a tempX_type, then when an invalid value is -written, -EINVAL should be returned. +limits using clamp_val(value, min_limit, max_limit). If it is not continuous +like for example a tempX_type, then when an invalid value is written, +-EINVAL should be returned. Example1, temp1_max, register is a signed 8 bit value (-128 - 127 degrees): long v = simple_strtol(buf, NULL, 10) / 1000; - v = SENSORS_LIMIT(v, -128, 127); + v = clamp_val(v, -128, 127); /* write v to register */ Example2, fan divider setting, valid values 2, 4 and 8: -- cgit v1.2.3-59-g8ed1b From 9e3970fba9ea43bc2c215f71f78bcf766c1775e7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 16 Nov 2012 21:55:24 -0800 Subject: hwmon: (coretemp) Document and add support for additional CPU models Cc: Fenghua Yu Signed-off-by: Guenter Roeck --- Documentation/hwmon/coretemp | 9 +++++++++ drivers/hwmon/coretemp.c | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/coretemp b/Documentation/hwmon/coretemp index 3374c085678d..fec5a9bf755f 100644 --- a/Documentation/hwmon/coretemp +++ b/Documentation/hwmon/coretemp @@ -66,6 +66,7 @@ Process Processor TjMax(C) i5 3470T 91 32nm Core i3/i5/i7 Processors + i7 2600 98 i7 660UM/640/620, 640LM/620, 620M, 610E 105 i5 540UM/520/430, 540M/520/450/430 105 i3 330E, 370M/350/330 90 rPGA, 105 BGA @@ -79,7 +80,10 @@ Process Processor TjMax(C) P4505/P4500 90 32nm Atom Processors + S1260/1220 95 + S1240 102 Z2460 90 + Z2760 90 D2700/2550/2500 100 N2850/2800/2650/2600 100 @@ -98,6 +102,7 @@ Process Processor TjMax(C) 45nm Atom Processors D525/510/425/410 100 + K525/510/425/410 100 Z670/650 90 Z560/550/540/530P/530/520PT/520/515/510PT/510P 90 Z510/500 90 @@ -107,7 +112,11 @@ Process Processor TjMax(C) 330/230 125 E680/660/640/620 90 E680T/660T/640T/620T 110 + E665C/645C 90 + E665CT/645CT 110 CE4170/4150/4110 110 + CE4200 series unknown + CE5300 series unknown 45nm Core2 Processors Solo ULV SU3500/3300 100 diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index d64923d63537..3f1e297663ad 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -198,7 +198,7 @@ struct tjmax { static const struct tjmax __cpuinitconst tjmax_table[] = { { "CPU 230", 100000 }, /* Model 0x1c, stepping 2 */ { "CPU 330", 125000 }, /* Model 0x1c, stepping 2 */ - { "CPU CE4110", 110000 }, /* Model 0x1c, stepping 10 */ + { "CPU CE4110", 110000 }, /* Model 0x1c, stepping 10 Sodaville */ { "CPU CE4150", 110000 }, /* Model 0x1c, stepping 10 */ { "CPU CE4170", 110000 }, /* Model 0x1c, stepping 10 */ }; @@ -212,7 +212,7 @@ struct tjmax_model { #define ANY 0xff static const struct tjmax_model __cpuinitconst tjmax_model_table[] = { - { 0x1c, 10, 100000 }, /* D4xx, N4xx, D5xx, N5xx */ + { 0x1c, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */ { 0x1c, ANY, 90000 }, /* Z5xx, N2xx, possibly others * Note: Also matches 230 and 330, * which are covered by tjmax_table @@ -222,6 +222,7 @@ static const struct tjmax_model __cpuinitconst tjmax_model_table[] = { * is undetectable by software */ { 0x27, ANY, 90000 }, /* Atom Medfield (Z2460) */ + { 0x35, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z2760) */ { 0x36, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx) */ }; -- cgit v1.2.3-59-g8ed1b From 5372d2d71c46e5649e5d2edd4514adcd6fe7a085 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 12 Oct 2012 23:01:58 -0700 Subject: hwmon: Driver for Maxim MAX6697 and compatibles Add support for MAX6581, MAX6602, MAX6622, MAX6636, MAX6689, MAX6693, MAX6694, MAX6697, MAX6698, and MAX6699 temperature sensors Signed-off-by: Guenter Roeck Reviewed-by: Jean Delvare --- Documentation/devicetree/bindings/i2c/max6697.txt | 64 ++ Documentation/hwmon/max6697 | 58 ++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max6697.c | 726 ++++++++++++++++++++++ include/linux/platform_data/max6697.h | 36 ++ 6 files changed, 896 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/max6697.txt create mode 100644 Documentation/hwmon/max6697 create mode 100644 drivers/hwmon/max6697.c create mode 100644 include/linux/platform_data/max6697.h (limited to 'Documentation/hwmon') diff --git a/Documentation/devicetree/bindings/i2c/max6697.txt b/Documentation/devicetree/bindings/i2c/max6697.txt new file mode 100644 index 000000000000..5f793998e4a4 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/max6697.txt @@ -0,0 +1,64 @@ +max6697 properties + +Required properties: +- compatible: + Should be one of + maxim,max6581 + maxim,max6602 + maxim,max6622 + maxim,max6636 + maxim,max6689 + maxim,max6693 + maxim,max6694 + maxim,max6697 + maxim,max6698 + maxim,max6699 +- reg: I2C address + +Optional properties: + +- smbus-timeout-disable + Set to disable SMBus timeout. If not specified, SMBus timeout will be + enabled. +- extended-range-enable + Only valid for MAX6581. Set to enable extended temperature range. + Extended temperature will be disabled if not specified. +- beta-compensation-enable + Only valid for MAX6693 and MX6694. Set to enable beta compensation on + remote temperature channel 1. + Beta compensation will be disabled if not specified. +- alert-mask + Alert bit mask. Alert disabled for bits set. + Select bit 0 for local temperature, bit 1..7 for remote temperatures. + If not specified, alert will be enabled for all channels. +- over-temperature-mask + Over-temperature bit mask. Over-temperature reporting disabled for + bits set. + Select bit 0 for local temperature, bit 1..7 for remote temperatures. + If not specified, over-temperature reporting will be enabled for all + channels. +- resistance-cancellation + Boolean for all chips other than MAX6581. Set to enable resistance + cancellation on remote temperature channel 1. + For MAX6581, resistance cancellation enabled for all channels if + specified as boolean, otherwise as per bit mask specified. + Only supported for remote temperatures (bit 1..7). + If not specified, resistance cancellation will be disabled for all + channels. +- transistor-ideality + For MAX6581 only. Two values; first is bit mask, second is ideality + select value as per MAX6581 data sheet. Select bit 1..7 for remote + channels. + Transistor ideality will be initialized to default (1.008) if not + specified. + +Example: + +temp-sensor@1a { + compatible = "maxim,max6697"; + reg = <0x1a>; + smbus-timeout-disable; + resistance-cancellation; + alert-mask = <0x72>; + over-temperature-mask = <0x7f>; +}; diff --git a/Documentation/hwmon/max6697 b/Documentation/hwmon/max6697 new file mode 100644 index 000000000000..6594177ededa --- /dev/null +++ b/Documentation/hwmon/max6697 @@ -0,0 +1,58 @@ +Kernel driver max6697 +===================== + +Supported chips: + * Maxim MAX6581 + Prefix: 'max6581' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6581.pdf + * Maxim MAX6602 + Prefix: 'max6602' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6602.pdf + * Maxim MAX6622 + Prefix: 'max6622' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6622.pdf + * Maxim MAX6636 + Prefix: 'max6636' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6636.pdf + * Maxim MAX6689 + Prefix: 'max6689' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6689.pdf + * Maxim MAX6693 + Prefix: 'max6693' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6693.pdf + * Maxim MAX6694 + Prefix: 'max6694' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6694.pdf + * Maxim MAX6697 + Prefix: 'max6697' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6697.pdf + * Maxim MAX6698 + Prefix: 'max6698' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6698.pdf + * Maxim MAX6699 + Prefix: 'max6699' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6699.pdf + +Author: + Guenter Roeck + +Description +----------- + +This driver implements support for several MAX6697 compatible temperature sensor +chips. The chips support one local temperature sensor plus four, six, or seven +remote temperature sensors. Remote temperature sensors are diode-connected +thermal transitors, except for MAX6698 which supports three diode-connected +thermal transistors plus three thermistors in addition to the local temperature +sensor. + +The driver provides the following sysfs attributes. temp1 is the local (chip) +temperature, temp[2..n] are remote temperatures. The actually supported +per-channel attributes are chip type and channel dependent. + +tempX_input RO temperature +tempX_max RW temperature maximum threshold +tempX_max_alarm RO temperature maximum threshold alarm +tempX_crit RW temperature critical threshold +tempX_crit_alarm RO temperature critical threshold alarm +tempX_fault RO temperature diode fault (remote sensors only) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 32f238f3caea..ef5757265f78 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -854,6 +854,17 @@ config SENSORS_MAX6650 This driver can also be built as a module. If so, the module will be called max6650. +config SENSORS_MAX6697 + tristate "Maxim MAX6697 and compatibles" + depends on I2C + help + If you say yes here you get support for MAX6581, MAX6602, MAX6622, + MAX6636, MAX6689, MAX6693, MAX6694, MAX6697, MAX6698, and MAX6699 + temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called max6697. + config SENSORS_MCP3021 tristate "Microchip MCP3021 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5da287443f6c..a37a82c64da2 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_SENSORS_MAX197) += max197.o 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_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c new file mode 100644 index 000000000000..bf4aa3777fc1 --- /dev/null +++ b/drivers/hwmon/max6697.c @@ -0,0 +1,726 @@ +/* + * Copyright (c) 2012 Guenter Roeck + * + * based on max1668.c + * Copyright (c) 2011 David George + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum chips { max6581, max6602, max6622, max6636, max6689, max6693, max6694, + max6697, max6698, max6699 }; + +/* Report local sensor as temp1 */ + +static const u8 MAX6697_REG_TEMP[] = { + 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08 }; +static const u8 MAX6697_REG_TEMP_EXT[] = { + 0x57, 0x09, 0x52, 0x53, 0x54, 0x55, 0x56, 0 }; +static const u8 MAX6697_REG_MAX[] = { + 0x17, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18 }; +static const u8 MAX6697_REG_CRIT[] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; + +/* + * Map device tree / platform data register bit map to chip bit map. + * Applies to alert register and over-temperature register. + */ +#define MAX6697_MAP_BITS(reg) ((((reg) & 0x7e) >> 1) | \ + (((reg) & 0x01) << 6) | ((reg) & 0x80)) + +#define MAX6697_REG_STAT(n) (0x44 + (n)) + +#define MAX6697_REG_CONFIG 0x41 +#define MAX6581_CONF_EXTENDED (1 << 1) +#define MAX6693_CONF_BETA (1 << 2) +#define MAX6697_CONF_RESISTANCE (1 << 3) +#define MAX6697_CONF_TIMEOUT (1 << 5) +#define MAX6697_REG_ALERT_MASK 0x42 +#define MAX6697_REG_OVERT_MASK 0x43 + +#define MAX6581_REG_RESISTANCE 0x4a +#define MAX6581_REG_IDEALITY 0x4b +#define MAX6581_REG_IDEALITY_SELECT 0x4c +#define MAX6581_REG_OFFSET 0x4d +#define MAX6581_REG_OFFSET_SELECT 0x4e + +#define MAX6697_CONV_TIME 156 /* ms per channel, worst case */ + +struct max6697_chip_data { + int channels; + u32 have_ext; + u32 have_crit; + u32 have_fault; + u8 valid_conf; + const u8 *alarm_map; +}; + +struct max6697_data { + struct device *hwmon_dev; + + enum chips type; + const struct max6697_chip_data *chip; + + int update_interval; /* in milli-seconds */ + int temp_offset; /* in degrees C */ + + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + bool valid; /* true if following fields are valid */ + + /* 1x local and up to 7x remote */ + u8 temp[8][4]; /* [nr][0]=temp [1]=ext [2]=max [3]=crit */ +#define MAX6697_TEMP_INPUT 0 +#define MAX6697_TEMP_EXT 1 +#define MAX6697_TEMP_MAX 2 +#define MAX6697_TEMP_CRIT 3 + u32 alarms; +}; + +/* Diode fault status bits on MAX6581 are right shifted by one bit */ +static const u8 max6581_alarm_map[] = { + 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23 }; + +static const struct max6697_chip_data max6697_chip_data[] = { + [max6581] = { + .channels = 8, + .have_crit = 0xff, + .have_ext = 0x7f, + .have_fault = 0xfe, + .valid_conf = MAX6581_CONF_EXTENDED | MAX6697_CONF_TIMEOUT, + .alarm_map = max6581_alarm_map, + }, + [max6602] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6622] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6636] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6689] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6693] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6693_CONF_BETA | + MAX6697_CONF_TIMEOUT, + }, + [max6694] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6693_CONF_BETA | + MAX6697_CONF_TIMEOUT, + }, + [max6697] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6698] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x0e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6699] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, +}; + +static struct max6697_data *max6697_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6697_data *data = i2c_get_clientdata(client); + struct max6697_data *ret = data; + int val; + int i; + u32 alarms; + + mutex_lock(&data->update_lock); + + if (data->valid && + !time_after(jiffies, data->last_updated + + msecs_to_jiffies(data->update_interval))) + goto abort; + + for (i = 0; i < data->chip->channels; i++) { + if (data->chip->have_ext & (1 << i)) { + val = i2c_smbus_read_byte_data(client, + MAX6697_REG_TEMP_EXT[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_EXT] = val; + } + + val = i2c_smbus_read_byte_data(client, MAX6697_REG_TEMP[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_INPUT] = val; + + val = i2c_smbus_read_byte_data(client, MAX6697_REG_MAX[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_MAX] = val; + + if (data->chip->have_crit & (1 << i)) { + val = i2c_smbus_read_byte_data(client, + MAX6697_REG_CRIT[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_CRIT] = val; + } + } + + alarms = 0; + for (i = 0; i < 3; i++) { + val = i2c_smbus_read_byte_data(client, MAX6697_REG_STAT(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + alarms = (alarms << 8) | val; + } + data->alarms = alarms; + data->last_updated = jiffies; + data->valid = true; +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max6697_data *data = max6697_update_device(dev); + int temp; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = (data->temp[index][MAX6697_TEMP_INPUT] - data->temp_offset) << 3; + temp |= data->temp[index][MAX6697_TEMP_EXT] >> 5; + + return sprintf(buf, "%d\n", temp * 125); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct max6697_data *data = max6697_update_device(dev); + int temp; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = data->temp[nr][index]; + temp -= data->temp_offset; + + return sprintf(buf, "%d\n", temp * 1000); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct max6697_data *data = max6697_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->chip->alarm_map) + index = data->chip->alarm_map[index]; + + return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1); +} + +static ssize_t set_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct max6697_data *data = i2c_get_clientdata(client); + long temp; + int ret; + + ret = kstrtol(buf, 10, &temp); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + temp = DIV_ROUND_CLOSEST(temp, 1000) + data->temp_offset; + temp = clamp_val(temp, 0, data->type == max6581 ? 255 : 127); + data->temp[nr][index] = temp; + ret = i2c_smbus_write_byte_data(client, + index == 2 ? MAX6697_REG_MAX[nr] + : MAX6697_REG_CRIT[nr], + temp); + mutex_unlock(&data->update_lock); + + return ret < 0 ? ret : count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_input, NULL, 3); +static SENSOR_DEVICE_ATTR_2(temp4_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp4_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_input, NULL, 4); +static SENSOR_DEVICE_ATTR_2(temp5_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp5_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_input, NULL, 5); +static SENSOR_DEVICE_ATTR_2(temp6_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp6_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp_input, NULL, 6); +static SENSOR_DEVICE_ATTR_2(temp7_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 6, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp7_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 6, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp_input, NULL, 7); +static SENSOR_DEVICE_ATTR_2(temp8_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 7, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp8_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 7, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 22); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 18); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, 19); +static SENSOR_DEVICE_ATTR(temp6_max_alarm, S_IRUGO, show_alarm, NULL, 20); +static SENSOR_DEVICE_ATTR(temp7_max_alarm, S_IRUGO, show_alarm, NULL, 21); +static SENSOR_DEVICE_ATTR(temp8_max_alarm, S_IRUGO, show_alarm, NULL, 23); + +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp6_crit_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(temp7_crit_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(temp8_crit_alarm, S_IRUGO, show_alarm, NULL, 15); + +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_alarm, NULL, 7); + +static struct attribute *max6697_attributes[8][7] = { + { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp6_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp7_max.dev_attr.attr, + &sensor_dev_attr_temp7_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_crit.dev_attr.attr, + &sensor_dev_attr_temp7_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp8_max.dev_attr.attr, + &sensor_dev_attr_temp8_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_crit.dev_attr.attr, + &sensor_dev_attr_temp8_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group max6697_group[8] = { + { .attrs = max6697_attributes[0] }, + { .attrs = max6697_attributes[1] }, + { .attrs = max6697_attributes[2] }, + { .attrs = max6697_attributes[3] }, + { .attrs = max6697_attributes[4] }, + { .attrs = max6697_attributes[5] }, + { .attrs = max6697_attributes[6] }, + { .attrs = max6697_attributes[7] }, +}; + +static void max6697_get_config_of(struct device_node *node, + struct max6697_platform_data *pdata) +{ + int len; + const __be32 *prop; + + prop = of_get_property(node, "smbus-timeout-disable", &len); + if (prop) + pdata->smbus_timeout_disable = true; + prop = of_get_property(node, "extended-range-enable", &len); + if (prop) + pdata->extended_range_enable = true; + prop = of_get_property(node, "beta-compensation-enable", &len); + if (prop) + pdata->beta_compensation = true; + prop = of_get_property(node, "alert-mask", &len); + if (prop && len == sizeof(u32)) + pdata->alert_mask = be32_to_cpu(prop[0]); + prop = of_get_property(node, "over-temperature-mask", &len); + if (prop && len == sizeof(u32)) + pdata->over_temperature_mask = be32_to_cpu(prop[0]); + prop = of_get_property(node, "resistance-cancellation", &len); + if (prop) { + if (len == sizeof(u32)) + pdata->resistance_cancellation = be32_to_cpu(prop[0]); + else + pdata->resistance_cancellation = 0xfe; + } + prop = of_get_property(node, "transistor-ideality", &len); + if (prop && len == 2 * sizeof(u32)) { + pdata->ideality_mask = be32_to_cpu(prop[0]); + pdata->ideality_value = be32_to_cpu(prop[1]); + } +} + +static int max6697_init_chip(struct i2c_client *client) +{ + struct max6697_data *data = i2c_get_clientdata(client); + struct max6697_platform_data *pdata = dev_get_platdata(&client->dev); + struct max6697_platform_data p; + const struct max6697_chip_data *chip = data->chip; + int factor = chip->channels; + int ret, reg; + + /* + * Don't touch configuration if neither platform data nor OF + * configuration was specified. If that is the case, use the + * current chip configuration. + */ + if (!pdata && !client->dev.of_node) { + reg = i2c_smbus_read_byte_data(client, MAX6697_REG_CONFIG); + if (reg < 0) + return reg; + if (data->type == max6581) { + if (reg & MAX6581_CONF_EXTENDED) + data->temp_offset = 64; + reg = i2c_smbus_read_byte_data(client, + MAX6581_REG_RESISTANCE); + if (reg < 0) + return reg; + factor += hweight8(reg); + } else { + if (reg & MAX6697_CONF_RESISTANCE) + factor++; + } + goto done; + } + + if (client->dev.of_node) { + memset(&p, 0, sizeof(p)); + max6697_get_config_of(client->dev.of_node, &p); + pdata = &p; + } + + reg = 0; + if (pdata->smbus_timeout_disable && + (chip->valid_conf & MAX6697_CONF_TIMEOUT)) { + reg |= MAX6697_CONF_TIMEOUT; + } + if (pdata->extended_range_enable && + (chip->valid_conf & MAX6581_CONF_EXTENDED)) { + reg |= MAX6581_CONF_EXTENDED; + data->temp_offset = 64; + } + if (pdata->resistance_cancellation && + (chip->valid_conf & MAX6697_CONF_RESISTANCE)) { + reg |= MAX6697_CONF_RESISTANCE; + factor++; + } + if (pdata->beta_compensation && + (chip->valid_conf & MAX6693_CONF_BETA)) { + reg |= MAX6693_CONF_BETA; + } + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_CONFIG, reg); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_ALERT_MASK, + MAX6697_MAP_BITS(pdata->alert_mask)); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_OVERT_MASK, + MAX6697_MAP_BITS(pdata->over_temperature_mask)); + if (ret < 0) + return ret; + + if (data->type == max6581) { + factor += hweight8(pdata->resistance_cancellation >> 1); + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_RESISTANCE, + pdata->resistance_cancellation >> 1); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_IDEALITY, + pdata->ideality_mask >> 1); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, + MAX6581_REG_IDEALITY_SELECT, + pdata->ideality_value); + if (ret < 0) + return ret; + } +done: + data->update_interval = factor * MAX6697_CONV_TIME; + return 0; +} + +static void max6697_remove_files(struct i2c_client *client) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(max6697_group); i++) + sysfs_remove_group(&client->dev.kobj, &max6697_group[i]); +} + +static int max6697_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct max6697_data *data; + int i, err; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct max6697_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->type = id->driver_data; + data->chip = &max6697_chip_data[data->type]; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + err = max6697_init_chip(client); + if (err) + return err; + + for (i = 0; i < data->chip->channels; i++) { + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][0]); + if (err) + goto error; + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][1]); + if (err) + goto error; + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][2]); + if (err) + goto error; + + if (data->chip->have_crit & (1 << i)) { + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][3]); + if (err) + goto error; + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][4]); + if (err) + goto error; + } + if (data->chip->have_fault & (1 << i)) { + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][5]); + if (err) + goto error; + } + } + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error; + } + + return 0; + +error: + max6697_remove_files(client); + return err; +} + +static int max6697_remove(struct i2c_client *client) +{ + struct max6697_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + max6697_remove_files(client); + + return 0; +} + +static const struct i2c_device_id max6697_id[] = { + { "max6581", max6581 }, + { "max6602", max6602 }, + { "max6622", max6622 }, + { "max6636", max6636 }, + { "max6689", max6689 }, + { "max6693", max6693 }, + { "max6694", max6694 }, + { "max6697", max6697 }, + { "max6698", max6698 }, + { "max6699", max6699 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6697_id); + +static struct i2c_driver max6697_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6697", + }, + .probe = max6697_probe, + .remove = max6697_remove, + .id_table = max6697_id, +}; + +module_i2c_driver(max6697_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("MAX6697 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/max6697.h b/include/linux/platform_data/max6697.h new file mode 100644 index 000000000000..ed9d3b3daf02 --- /dev/null +++ b/include/linux/platform_data/max6697.h @@ -0,0 +1,36 @@ +/* + * max6697.h + * Copyright (c) 2012 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef MAX6697_H +#define MAX6697_H + +#include + +/* + * For all bit masks: + * bit 0: local temperature + * bit 1..7: remote temperatures + */ +struct max6697_platform_data { + bool smbus_timeout_disable; /* set to disable SMBus timeouts */ + bool extended_range_enable; /* set to enable extended temp range */ + bool beta_compensation; /* set to enable beta compensation */ + u8 alert_mask; /* set bit to 1 to disable alert */ + u8 over_temperature_mask; /* set bit to 1 to disable */ + u8 resistance_cancellation; /* set bit to 0 to disable + * bit mask for MAX6581, + * boolean for other chips + */ + u8 ideality_mask; /* set bit to 0 to disable */ + u8 ideality_value; /* transistor ideality as per + * MAX6581 datasheet + */ +}; + +#endif /* MAX6697_H */ -- cgit v1.2.3-59-g8ed1b From b06367070de3d9f50e4f8f82b92e77ba9a8ebf8f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 7 Sep 2012 17:34:41 -0600 Subject: hwmon: (it87) Add support for IT8771E and IT8772E Assume that IT8771E and IT8772E are fully compatible with IT8728F. IT8771E support contributed by Kelly Anderson. Signed-off-by: Guenter Roeck Reviewed-by: Jean Delvare --- Documentation/hwmon/it87 | 16 ++++++++++++---- drivers/hwmon/Kconfig | 3 ++- drivers/hwmon/it87.c | 39 +++++++++++++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 9 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/it87 b/Documentation/hwmon/it87 index 8386aadc0a82..c263740f0cba 100644 --- a/Documentation/hwmon/it87 +++ b/Documentation/hwmon/it87 @@ -30,6 +30,14 @@ Supported chips: Prefix: 'it8728' Addresses scanned: from Super I/O config space (8 I/O ports) Datasheet: Not publicly available + * IT8771E + Prefix: 'it8771' + Addresses scanned: from Super I/O config space (8 I/O ports) + Datasheet: Not publicly available + * IT8772E + Prefix: 'it8772' + Addresses scanned: from Super I/O config space (8 I/O ports) + Datasheet: Not publicly available * IT8782F Prefix: 'it8782' Addresses scanned: from Super I/O config space (8 I/O ports) @@ -83,8 +91,8 @@ Description ----------- This driver implements support for the IT8705F, IT8712F, IT8716F, -IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8758E, IT8781F, IT8782F, -IT8783E/F, and SiS950 chips. +IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8758E, IT8771E, IT8772E, +IT8782F, IT8783E/F, and SiS950 chips. These chips are 'Super I/O chips', supporting floppy disks, infrared ports, joysticks and other miscellaneous stuff. For hardware monitoring, they @@ -118,8 +126,8 @@ The IT8726F is just bit enhanced IT8716F with additional hardware for AMD power sequencing. Therefore the chip will appear as IT8716F to userspace applications. -The IT8728F is considered compatible with the IT8721F, until a datasheet -becomes available (hopefully.) +The IT8728F, IT8771E, and IT8772E are considered compatible with the IT8721F, +until a datasheet becomes available (hopefully.) Temperatures are measured in degrees Celsius. An alarm is triggered once when the Overtemperature Shutdown limit is crossed. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index ef5757265f78..3588b2a0915c 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -506,7 +506,8 @@ config SENSORS_IT87 help If you say yes here you get support for ITE IT8705F, IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8758E, - IT8782F, and IT8783E/F sensor chips, and the SiS950 clone. + IT8771E, IT8772E, IT8782F, and IT8783E/F sensor chips, and the + SiS950 clone. This driver can also be built as a module. If so, the module will be called it87. diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 29632e849285..37fc980fde24 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -19,6 +19,8 @@ * IT8726F Super I/O chip w/LPC interface * IT8728F Super I/O chip w/LPC interface * IT8758E Super I/O chip w/LPC interface + * IT8771E Super I/O chip w/LPC interface + * IT8772E Super I/O chip w/LPC interface * IT8782F Super I/O chip w/LPC interface * IT8783E/F Super I/O chip w/LPC interface * Sis950 A clone of the IT8705F @@ -61,8 +63,8 @@ #define DRVNAME "it87" -enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8782, - it8783 }; +enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8771, + it8772, it8782, it8783 }; static unsigned short force_id; module_param(force_id, ushort, 0); @@ -140,6 +142,8 @@ static inline void superio_exit(void) #define IT8721F_DEVID 0x8721 #define IT8726F_DEVID 0x8726 #define IT8728F_DEVID 0x8728 +#define IT8771E_DEVID 0x8771 +#define IT8772E_DEVID 0x8772 #define IT8782F_DEVID 0x8782 #define IT8783E_DEVID 0x8783 #define IT87_ACT_REG 0x30 @@ -281,6 +285,24 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI, .peci_mask = 0x07, }, + [it8771] = { + .name = "it8771", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI, + /* PECI: guesswork */ + /* 12mV ADC (OHM) */ + /* 16 bit fans (OHM) */ + .peci_mask = 0x07, + }, + [it8772] = { + .name = "it8772", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI, + /* PECI (coreboot) */ + /* 12mV ADC (HWSensors4, OHM) */ + /* 16 bit fans (HWSensors4, OHM) */ + .peci_mask = 0x07, + }, [it8782] = { .name = "it8782", .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET @@ -1708,6 +1730,12 @@ static int __init it87_find(unsigned short *address, case IT8728F_DEVID: sio_data->type = it8728; break; + case IT8771E_DEVID: + sio_data->type = it8771; + break; + case IT8772E_DEVID: + sio_data->type = it8772; + break; case IT8782F_DEVID: sio_data->type = it8782; break; @@ -1825,10 +1853,11 @@ static int __init it87_find(unsigned short *address, reg = superio_inb(IT87_SIO_GPIO3_REG); if (sio_data->type == it8721 || sio_data->type == it8728 || + sio_data->type == it8771 || sio_data->type == it8772 || sio_data->type == it8782) { /* * IT8721F/IT8758E, and IT8782F don't have VID pins - * at all, not sure about the IT8728F. + * at all, not sure about the IT8728F and compatibles. */ sio_data->skip_vid = 1; } else { @@ -1882,7 +1911,9 @@ static int __init it87_find(unsigned short *address, if (reg & (1 << 0)) sio_data->internal |= (1 << 0); if ((reg & (1 << 1)) || sio_data->type == it8721 || - sio_data->type == it8728) + sio_data->type == it8728 || + sio_data->type == it8771 || + sio_data->type == it8772) sio_data->internal |= (1 << 1); /* -- cgit v1.2.3-59-g8ed1b From 8c14d126ae2efbcd094b24e5413b8cbe1d2c01e4 Mon Sep 17 00:00:00 2001 From: Chris Verges Date: Sat, 5 Jan 2013 01:41:19 -0800 Subject: hwmon: (lm73) Add 'update_interval' attribute The LM73 supports four A/D conversion resolutions. The default used by the existing lm73 driver is the chip's default, 11-bit (0.25 C/LSB). This patch enables changing of this resolution from userspace via the update_interval sysfs attribute. Full details on usage are included in Documentation/hwmon/lm73. Signed-off-by: Chris Verges [linux@roeck-us.net: cleanup] Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm73 | 79 +++++++++++++++++++++++++++++++++++++++ drivers/hwmon/lm73.c | 97 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 Documentation/hwmon/lm73 (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/lm73 b/Documentation/hwmon/lm73 new file mode 100644 index 000000000000..782b3e1055ca --- /dev/null +++ b/Documentation/hwmon/lm73 @@ -0,0 +1,79 @@ +Kernel driver lm73 +================== + +Supported chips: + * Texas Instruments LM73 + Prefix: 'lm73' + Addresses scanned: I2C 0x48, 0x49, 0x4a, 0x4c, 0x4d, and 0x4e + Datasheet: Publicly available at the Texas Instruments website + http://www.ti.com/product/lm73 + +Author: Guillaume Ligneul +Documentation: Chris Verges + + +Description +----------- + +The LM73 is a digital temperature sensor. All temperature values are +given in degrees Celsius. + +Measurement Resolution Support +------------------------------ + +The LM73 supports four resolutions, defined in terms of degrees C per +LSB: 0.25, 0.125, 0.0625, and 0.3125. Changing the resolution mode +affects the conversion time of the LM73's analog-to-digital converter. +From userspace, the desired resolution can be specified as a function of +conversion time via the 'update_interval' sysfs attribute for the +device. This attribute will normalize ranges of input values to the +maximum times defined for the resolution in the datasheet. + + Resolution Conv. Time Input Range + (C/LSB) (msec) (msec) + -------------------------------------- + 0.25 14 0..14 + 0.125 28 15..28 + 0.0625 56 29..56 + 0.03125 112 57..infinity + -------------------------------------- + +The following examples show how the 'update_interval' attribute can be +used to change the conversion time: + + $ echo 0 > update_interval + $ cat update_interval + 14 + $ cat temp1_input + 24250 + + $ echo 22 > update_interval + $ cat update_interval + 28 + $ cat temp1_input + 24125 + + $ echo 56 > update_interval + $ cat update_interval + 56 + $ cat temp1_input + 24062 + + $ echo 85 > update_interval + $ cat update_interval + 112 + $ cat temp1_input + 24031 + +As shown here, the lm73 driver automatically adjusts any user input for +'update_interval' via a step function. Reading back the +'update_interval' value after a write operation will confirm the +conversion time actively in use. + +Mathematically, the resolution can be derived from the conversion time +via the following function: + + g(x) = 0.250 * [log(x/14) / log(2)] + +where 'x' is the output from 'update_interval' and 'g(x)' is the +resolution in degrees C per LSB. diff --git a/drivers/hwmon/lm73.c b/drivers/hwmon/lm73.c index dad83fea807d..0b19f03950e0 100644 --- a/drivers/hwmon/lm73.c +++ b/drivers/hwmon/lm73.c @@ -39,8 +39,24 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, #define LM73_TEMP_MIN (-256000 / 250) #define LM73_TEMP_MAX (255750 / 250) -/*-----------------------------------------------------------------------*/ +#define LM73_CTRL_RES_SHIFT 5 +#define LM73_CTRL_RES_MASK (BIT(5) | BIT(6)) +#define LM73_CTRL_TO_MASK BIT(7) + +static const unsigned short lm73_convrates[] = { + 14, /* 11-bits (0.25000 C/LSB): RES1 Bit = 0, RES0 Bit = 0 */ + 28, /* 12-bits (0.12500 C/LSB): RES1 Bit = 0, RES0 Bit = 1 */ + 56, /* 13-bits (0.06250 C/LSB): RES1 Bit = 1, RES0 Bit = 0 */ + 112, /* 14-bits (0.03125 C/LSB): RES1 Bit = 1, RES0 Bit = 1 */ +}; + +struct lm73_data { + struct device *hwmon_dev; + struct mutex lock; + u8 ctrl; /* control register value */ +}; +/*-----------------------------------------------------------------------*/ static ssize_t set_temp(struct device *dev, struct device_attribute *da, const char *buf, size_t count) @@ -78,6 +94,51 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *da, return scnprintf(buf, PAGE_SIZE, "%d\n", temp); } +static ssize_t set_convrate(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm73_data *data = i2c_get_clientdata(client); + unsigned long convrate; + s32 err; + int res = 0; + + err = kstrtoul(buf, 10, &convrate); + if (err < 0) + return err; + + /* + * Convert the desired conversion rate into register bits. + * res is already initialized, and everything past the second-to-last + * value in the array is treated as belonging to the last value + * in the array. + */ + while (res < (ARRAY_SIZE(lm73_convrates) - 1) && + convrate > lm73_convrates[res]) + res++; + + mutex_lock(&data->lock); + data->ctrl &= LM73_CTRL_TO_MASK; + data->ctrl |= res << LM73_CTRL_RES_SHIFT; + err = i2c_smbus_write_byte_data(client, LM73_REG_CTRL, data->ctrl); + mutex_unlock(&data->lock); + + if (err < 0) + return err; + + return count; +} + +static ssize_t show_convrate(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm73_data *data = i2c_get_clientdata(client); + int res; + + res = (data->ctrl & LM73_CTRL_RES_MASK) >> LM73_CTRL_RES_SHIFT; + return scnprintf(buf, PAGE_SIZE, "%hu\n", lm73_convrates[res]); +} /*-----------------------------------------------------------------------*/ @@ -89,13 +150,14 @@ static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, LM73_REG_MIN); static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, LM73_REG_INPUT); - +static SENSOR_DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, + show_convrate, set_convrate, 0); static struct attribute *lm73_attributes[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, &sensor_dev_attr_temp1_min.dev_attr.attr, - + &sensor_dev_attr_update_interval.dev_attr.attr, NULL }; @@ -110,23 +172,36 @@ static const struct attribute_group lm73_group = { static int lm73_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct device *hwmon_dev; int status; + struct lm73_data *data; + int ctrl; + + data = devm_kzalloc(&client->dev, sizeof(struct lm73_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->lock); + + ctrl = i2c_smbus_read_byte_data(client, LM73_REG_CTRL); + if (ctrl < 0) + return ctrl; + data->ctrl = ctrl; /* Register sysfs hooks */ status = sysfs_create_group(&client->dev.kobj, &lm73_group); if (status) return status; - hwmon_dev = hwmon_device_register(&client->dev); - if (IS_ERR(hwmon_dev)) { - status = PTR_ERR(hwmon_dev); + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + status = PTR_ERR(data->hwmon_dev); goto exit_remove; } - i2c_set_clientdata(client, hwmon_dev); dev_info(&client->dev, "%s: sensor '%s'\n", - dev_name(hwmon_dev), client->name); + dev_name(data->hwmon_dev), client->name); return 0; @@ -137,9 +212,9 @@ exit_remove: static int lm73_remove(struct i2c_client *client) { - struct device *hwmon_dev = i2c_get_clientdata(client); + struct lm73_data *data = i2c_get_clientdata(client); - hwmon_device_unregister(hwmon_dev); + hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&client->dev.kobj, &lm73_group); return 0; } -- cgit v1.2.3-59-g8ed1b From 2bf9233a108cea32edbb5a65afa8e609fa146577 Mon Sep 17 00:00:00 2001 From: Chris Verges Date: Sat, 5 Jan 2013 01:41:20 -0800 Subject: hwmon: (lm73) Add support for max/min alarms Add support for temp1_min_alarm and temp1_max_alarm Signed-off-by: Chris Verges [linux@roeck-us.net: cleanup; dropped platform data and interrupt support] Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm73 | 11 +++++++++++ drivers/hwmon/lm73.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/lm73 b/Documentation/hwmon/lm73 index 782b3e1055ca..8af059dcb642 100644 --- a/Documentation/hwmon/lm73 +++ b/Documentation/hwmon/lm73 @@ -77,3 +77,14 @@ via the following function: where 'x' is the output from 'update_interval' and 'g(x)' is the resolution in degrees C per LSB. + +Alarm Support +------------- + +The LM73 features a simple over-temperature alarm mechanism. This +feature is exposed via the sysfs attributes. + +The attributes 'temp1_max_alarm' and 'temp1_min_alarm' are flags +provided by the LM73 that indicate whether the measured temperature has +passed the 'temp1_max' and 'temp1_min' thresholds, respectively. These +values _must_ be read to clear the registers on the LM73. diff --git a/drivers/hwmon/lm73.c b/drivers/hwmon/lm73.c index 0b19f03950e0..9bde9644b102 100644 --- a/drivers/hwmon/lm73.c +++ b/drivers/hwmon/lm73.c @@ -8,6 +8,7 @@ * Guillaume Ligneul * Adrien Demarez * Jeremy Laine + * Chris Verges * * This software program is licensed subject to the GNU General Public License * (GPL).Version 2,June 1991, available at @@ -43,6 +44,9 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, #define LM73_CTRL_RES_MASK (BIT(5) | BIT(6)) #define LM73_CTRL_TO_MASK BIT(7) +#define LM73_CTRL_HI_SHIFT 2 +#define LM73_CTRL_LO_SHIFT 1 + static const unsigned short lm73_convrates[] = { 14, /* 11-bits (0.25000 C/LSB): RES1 Bit = 0, RES0 Bit = 0 */ 28, /* 12-bits (0.12500 C/LSB): RES1 Bit = 0, RES0 Bit = 1 */ @@ -140,6 +144,28 @@ static ssize_t show_convrate(struct device *dev, struct device_attribute *da, return scnprintf(buf, PAGE_SIZE, "%hu\n", lm73_convrates[res]); } +static ssize_t show_maxmin_alarm(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm73_data *data = i2c_get_clientdata(client); + s32 ctrl; + + mutex_lock(&data->lock); + ctrl = i2c_smbus_read_byte_data(client, LM73_REG_CTRL); + if (ctrl < 0) + goto abort; + data->ctrl = ctrl; + mutex_unlock(&data->lock); + + return scnprintf(buf, PAGE_SIZE, "%d\n", (ctrl >> attr->index) & 1); + +abort: + mutex_unlock(&data->lock); + return ctrl; +} + /*-----------------------------------------------------------------------*/ /* sysfs attributes for hwmon */ @@ -152,12 +178,18 @@ static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, LM73_REG_INPUT); static SENSOR_DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_convrate, set_convrate, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, + show_maxmin_alarm, NULL, LM73_CTRL_HI_SHIFT); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, + show_maxmin_alarm, NULL, LM73_CTRL_LO_SHIFT); static struct attribute *lm73_attributes[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, &sensor_dev_attr_temp1_min.dev_attr.attr, &sensor_dev_attr_update_interval.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, NULL }; -- cgit v1.2.3-59-g8ed1b From ceeadc5c5187b78ffbea737c7a82ecc1e31e80df Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2009 14:49:25 -0700 Subject: hwmon: Driver for Texas Instruments INA209 Add support for the TI / Burr-Brown INA209 voltage / current / power monitor. Cc: Paul Hays Cc: Ira W. Snyder Tested-by: Ira W. Snyder Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/i2c/ina209.txt | 18 + Documentation/hwmon/ina209 | 93 ++++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ina209.c | 636 +++++++++++++++++++++++ 5 files changed, 758 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/ina209.txt create mode 100644 Documentation/hwmon/ina209 create mode 100644 drivers/hwmon/ina209.c (limited to 'Documentation/hwmon') diff --git a/Documentation/devicetree/bindings/i2c/ina209.txt b/Documentation/devicetree/bindings/i2c/ina209.txt new file mode 100644 index 000000000000..9dd2bee80840 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/ina209.txt @@ -0,0 +1,18 @@ +ina209 properties + +Required properties: +- compatible: Must be "ti,ina209" +- reg: I2C address + +Optional properties: + +- shunt-resistor + Shunt resistor value in micro-Ohm + +Example: + +temp-sensor@4c { + compatible = "ti,ina209"; + reg = <0x4c>; + shunt-resistor = <5000>; +}; diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209 new file mode 100644 index 000000000000..672501de4509 --- /dev/null +++ b/Documentation/hwmon/ina209 @@ -0,0 +1,93 @@ +Kernel driver ina209 +===================== + +Supported chips: + * Burr-Brown / Texas Instruments INA209 + Prefix: 'ina209' + Addresses scanned: - + Datasheet: + http://www.ti.com/lit/gpn/ina209 + +Author: Paul Hays +Author: Ira W. Snyder +Author: Guenter Roeck + + +Description +----------- + +The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side +of a D.C. power supply. It can perform measurements and calculations in the +background to supply readings at any time. It includes a programmable +calibration multiplier to scale the displayed current and power values. + + +Sysfs entries +------------- + +The INA209 chip is highly configurable both via hardwiring and via +the I2C bus. See the datasheet for details. + +This tries to expose most monitoring features of the hardware via +sysfs. It does not support every feature of this chip. + + +in0_input shunt voltage (mV) +in0_input_highest shunt voltage historical maximum reading (mV) +in0_input_lowest shunt voltage historical minimum reading (mV) +in0_reset_history reset shunt voltage history +in0_max shunt voltage max alarm limit (mV) +in0_min shunt voltage min alarm limit (mV) +in0_crit_max shunt voltage crit max alarm limit (mV) +in0_crit_min shunt voltage crit min alarm limit (mV) +in0_max_alarm shunt voltage max alarm limit exceeded +in0_min_alarm shunt voltage min alarm limit exceeded +in0_crit_max_alarm shunt voltage crit max alarm limit exceeded +in0_crit_min_alarm shunt voltage crit min alarm limit exceeded + +in1_input bus voltage (mV) +in1_input_highest bus voltage historical maximum reading (mV) +in1_input_lowest bus voltage historical minimum reading (mV) +in1_reset_history reset bus voltage history +in1_max bus voltage max alarm limit (mV) +in1_min bus voltage min alarm limit (mV) +in1_crit_max bus voltage crit max alarm limit (mV) +in1_crit_min bus voltage crit min alarm limit (mV) +in1_max_alarm bus voltage max alarm limit exceeded +in1_min_alarm bus voltage min alarm limit exceeded +in1_crit_max_alarm bus voltage crit max alarm limit exceeded +in1_crit_min_alarm bus voltage crit min alarm limit exceeded + +power1_input power measurement (uW) +power1_input_highest power historical maximum reading (uW) +power1_reset_history reset power history +power1_max power max alarm limit (uW) +power1_crit power crit alarm limit (uW) +power1_max_alarm power max alarm limit exceeded +power1_crit_alarm power crit alarm limit exceeded + +curr1_input current measurement (mA) + +update_interval data conversion time; affects number of samples used + to average results for shunt and bus voltages. + +General Remarks +--------------- + +The power and current registers in this chip require that the calibration +register is programmed correctly before they are used. Normally this is expected +to be done in the BIOS. In the absence of BIOS programming, the shunt resistor +voltage can be provided using platform data. The driver uses platform data from +the ina2xx driver for this purpose. If calibration register data is not provided +via platform data, the driver checks if the calibration register has been +programmed (ie has a value not equal to zero). If so, this value is retained. +Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is +programmed into the calibration register. + + +Output Pins +----------- + +Output pin programming is a board feature which depends on the BIOS. It is +outside the scope of a hardware monitoring driver to enable or disable output +pins. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3588b2a0915c..5e77cef09ca6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1157,6 +1157,16 @@ config SENSORS_AMC6821 This driver can also be build as a module. If so, the module will be called amc6821. +config SENSORS_INA209 + tristate "TI / Burr Brown INA209" + depends on I2C + help + If you say yes here you get support for the TI / Burr Brown INA209 + voltage / current / power monitor I2C interface. + + This driver can also be built as a module. If so, the module will + be called ina209. + config SENSORS_INA2XX tristate "Texas Instruments INA219 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index a37a82c64da2..8d6d97ea7c1e 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o +obj-$(CONFIG_SENSORS_INA209) += ina209.o obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c new file mode 100644 index 000000000000..c6fdd5bd395e --- /dev/null +++ b/drivers/hwmon/ina209.c @@ -0,0 +1,636 @@ +/* + * Driver for the Texas Instruments / Burr Brown INA209 + * Bidirectional Current/Power Monitor + * + * Copyright (C) 2012 Guenter Roeck + * + * Derived from Ira W. Snyder's original driver submission + * Copyright (C) 2008 Paul Hays + * Copyright (C) 2008-2009 Ira W. Snyder + * + * Aligned with ina2xx driver + * Copyright (C) 2012 Lothar Felten + * Thanks to Jan Volkering + * + * 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; version 2 of the License. + * + * Datasheet: + * http://www.ti.com/lit/gpn/ina209 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* register definitions */ +#define INA209_CONFIGURATION 0x00 +#define INA209_STATUS 0x01 +#define INA209_STATUS_MASK 0x02 +#define INA209_SHUNT_VOLTAGE 0x03 +#define INA209_BUS_VOLTAGE 0x04 +#define INA209_POWER 0x05 +#define INA209_CURRENT 0x06 +#define INA209_SHUNT_VOLTAGE_POS_PEAK 0x07 +#define INA209_SHUNT_VOLTAGE_NEG_PEAK 0x08 +#define INA209_BUS_VOLTAGE_MAX_PEAK 0x09 +#define INA209_BUS_VOLTAGE_MIN_PEAK 0x0a +#define INA209_POWER_PEAK 0x0b +#define INA209_SHUNT_VOLTAGE_POS_WARN 0x0c +#define INA209_SHUNT_VOLTAGE_NEG_WARN 0x0d +#define INA209_POWER_WARN 0x0e +#define INA209_BUS_VOLTAGE_OVER_WARN 0x0f +#define INA209_BUS_VOLTAGE_UNDER_WARN 0x10 +#define INA209_POWER_OVER_LIMIT 0x11 +#define INA209_BUS_VOLTAGE_OVER_LIMIT 0x12 +#define INA209_BUS_VOLTAGE_UNDER_LIMIT 0x13 +#define INA209_CRITICAL_DAC_POS 0x14 +#define INA209_CRITICAL_DAC_NEG 0x15 +#define INA209_CALIBRATION 0x16 + +#define INA209_REGISTERS 0x17 + +#define INA209_CONFIG_DEFAULT 0x3c47 /* PGA=8, full range */ +#define INA209_SHUNT_DEFAULT 10000 /* uOhm */ + +struct ina209_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + u16 regs[INA209_REGISTERS]; /* All chip registers */ + + u16 config_orig; /* Original configuration */ + u16 calibration_orig; /* Original calibration */ + u16 update_interval; +}; + +static struct ina209_data *ina209_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ina209_data *data = i2c_get_clientdata(client); + struct ina209_data *ret = data; + s32 val; + int i; + + mutex_lock(&data->update_lock); + + if (!data->valid || + time_after(jiffies, data->last_updated + data->update_interval)) { + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + val = i2c_smbus_read_word_swapped(client, i); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->regs[i] = val; + } + data->last_updated = jiffies; + data->valid = true; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* + * Read a value from a device register and convert it to the + * appropriate sysfs units + */ +static long ina209_from_reg(const u8 reg, const u16 val) +{ + switch (reg) { + case INA209_SHUNT_VOLTAGE: + case INA209_SHUNT_VOLTAGE_POS_PEAK: + case INA209_SHUNT_VOLTAGE_NEG_PEAK: + case INA209_SHUNT_VOLTAGE_POS_WARN: + case INA209_SHUNT_VOLTAGE_NEG_WARN: + /* LSB=10 uV. Convert to mV. */ + return DIV_ROUND_CLOSEST(val, 100); + + case INA209_BUS_VOLTAGE: + case INA209_BUS_VOLTAGE_MAX_PEAK: + case INA209_BUS_VOLTAGE_MIN_PEAK: + case INA209_BUS_VOLTAGE_OVER_WARN: + case INA209_BUS_VOLTAGE_UNDER_WARN: + case INA209_BUS_VOLTAGE_OVER_LIMIT: + case INA209_BUS_VOLTAGE_UNDER_LIMIT: + /* LSB=4 mV, last 3 bits unused */ + return (val >> 3) * 4; + + case INA209_CRITICAL_DAC_POS: + /* LSB=1 mV, in the upper 8 bits */ + return val >> 8; + + case INA209_CRITICAL_DAC_NEG: + /* LSB=1 mV, in the upper 8 bits */ + return -1 * (val >> 8); + + case INA209_POWER: + case INA209_POWER_PEAK: + case INA209_POWER_WARN: + case INA209_POWER_OVER_LIMIT: + /* LSB=20 mW. Convert to uW */ + return val * 20 * 1000L; + + case INA209_CURRENT: + /* LSB=1 mA (selected). Is in mA */ + return val; + } + + /* programmer goofed */ + WARN_ON_ONCE(1); + return 0; +} + +/* + * Take a value and convert it to register format, clamping the value + * to the appropriate range. + */ +static int ina209_to_reg(u8 reg, u16 old, long val) +{ + switch (reg) { + case INA209_SHUNT_VOLTAGE_POS_WARN: + case INA209_SHUNT_VOLTAGE_NEG_WARN: + /* Limit to +- 320 mV, 10 uV LSB */ + return clamp_val(val, -320, 320) * 100; + + case INA209_BUS_VOLTAGE_OVER_WARN: + case INA209_BUS_VOLTAGE_UNDER_WARN: + case INA209_BUS_VOLTAGE_OVER_LIMIT: + case INA209_BUS_VOLTAGE_UNDER_LIMIT: + /* + * Limit to 0-32000 mV, 4 mV LSB + * + * The last three bits aren't part of the value, but we'll + * preserve them in their original state. + */ + return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3) + | (old & 0x7); + + case INA209_CRITICAL_DAC_NEG: + /* + * Limit to -255-0 mV, 1 mV LSB + * Convert the value to a positive value for the register + * + * The value lives in the top 8 bits only, be careful + * and keep original value of other bits. + */ + return (clamp_val(-val, 0, 255) << 8) | (old & 0xff); + + case INA209_CRITICAL_DAC_POS: + /* + * Limit to 0-255 mV, 1 mV LSB + * + * The value lives in the top 8 bits only, be careful + * and keep original value of other bits. + */ + return (clamp_val(val, 0, 255) << 8) | (old & 0xff); + + case INA209_POWER_WARN: + case INA209_POWER_OVER_LIMIT: + /* 20 mW LSB */ + return DIV_ROUND_CLOSEST(val, 20 * 1000); + } + + /* Other registers are read-only, return access error */ + return -EACCES; +} + +static int ina209_interval_from_reg(u16 reg) +{ + return 68 >> (15 - ((reg >> 3) & 0x0f)); +} + +static u16 ina209_reg_from_interval(u16 config, long interval) +{ + int i, adc; + + if (interval <= 0) { + adc = 8; + } else { + adc = 15; + for (i = 34 + 34 / 2; i; i >>= 1) { + if (i < interval) + break; + adc--; + } + } + return (config & 0xf807) | (adc << 3) | (adc << 7); +} + +static ssize_t ina209_set_interval(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ina209_data *data = ina209_update_device(dev); + long val; + u16 regval; + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION], + val); + i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval); + data->regs[INA209_CONFIGURATION] = regval; + data->update_interval = ina209_interval_from_reg(regval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t ina209_show_interval(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ina209_data *data = i2c_get_clientdata(client); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval); +} + +/* + * History is reset by writing 1 into bit 0 of the respective peak register. + * Since more than one peak register may be affected by the scope of a + * reset_history attribute write, use a bit mask in attr->index to identify + * which registers are affected. + */ +static u16 ina209_reset_history_regs[] = { + INA209_SHUNT_VOLTAGE_POS_PEAK, + INA209_SHUNT_VOLTAGE_NEG_PEAK, + INA209_BUS_VOLTAGE_MAX_PEAK, + INA209_BUS_VOLTAGE_MIN_PEAK, + INA209_POWER_PEAK +}; + +static ssize_t ina209_reset_history(struct device *dev, + struct device_attribute *da, + const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ina209_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + u32 mask = attr->index; + long val; + int i, ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) { + if (mask & (1 << i)) + i2c_smbus_write_word_swapped(client, + ina209_reset_history_regs[i], 1); + } + data->valid = false; + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t ina209_set_value(struct device *dev, + struct device_attribute *da, + const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ina209_data *data = ina209_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int reg = attr->index; + long val; + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + ret = ina209_to_reg(reg, data->regs[reg], val); + if (ret < 0) { + count = ret; + goto abort; + } + i2c_smbus_write_word_swapped(client, reg, ret); + data->regs[reg] = ret; +abort: + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t ina209_show_value(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina209_data *data = ina209_update_device(dev); + long val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = ina209_from_reg(attr->index, data->regs[attr->index]); + return snprintf(buf, PAGE_SIZE, "%ld\n", val); +} + +static ssize_t ina209_show_alarm(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina209_data *data = ina209_update_device(dev); + const unsigned int mask = attr->index; + u16 status; + + if (IS_ERR(data)) + return PTR_ERR(data); + + status = data->regs[INA209_STATUS]; + + /* + * All alarms are in the INA209_STATUS register. To avoid a long + * switch statement, the mask is passed in attr->index + */ + return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask)); +} + +/* Shunt voltage, history, limits, alarms */ +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL, + INA209_SHUNT_VOLTAGE); +static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL, + INA209_SHUNT_VOLTAGE_POS_PEAK); +static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL, + INA209_SHUNT_VOLTAGE_NEG_PEAK); +static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL, + ina209_reset_history, (1 << 0) | (1 << 1)); +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN); +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN); +static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_CRITICAL_DAC_POS); +static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_CRITICAL_DAC_NEG); + +static SENSOR_DEVICE_ATTR(in0_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 11); +static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 12); +static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 6); +static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 7); + +/* Bus voltage, history, limits, alarms */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL, + INA209_BUS_VOLTAGE); +static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL, + INA209_BUS_VOLTAGE_MAX_PEAK); +static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL, + INA209_BUS_VOLTAGE_MIN_PEAK); +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL, + ina209_reset_history, (1 << 2) | (1 << 3)); +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN); +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN); +static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT); +static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT); + +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 14); +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 15); +static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 9); +static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 10); + +/* Power */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL, + INA209_POWER); +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value, + NULL, INA209_POWER_PEAK); +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL, + ina209_reset_history, 1 << 4); +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_POWER_WARN); +static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_POWER_OVER_LIMIT); + +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 13); +static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 8); + +/* Current */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL, + INA209_CURRENT); + +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, + ina209_show_interval, ina209_set_interval, 0); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *ina209_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_input_highest.dev_attr.attr, + &sensor_dev_attr_in0_input_lowest.dev_attr.attr, + &sensor_dev_attr_in0_reset_history.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_crit_max.dev_attr.attr, + &sensor_dev_attr_in0_crit_min.dev_attr.attr, + &sensor_dev_attr_in0_max_alarm.dev_attr.attr, + &sensor_dev_attr_in0_min_alarm.dev_attr.attr, + &sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr, + &sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_input_highest.dev_attr.attr, + &sensor_dev_attr_in1_input_lowest.dev_attr.attr, + &sensor_dev_attr_in1_reset_history.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_crit_max.dev_attr.attr, + &sensor_dev_attr_in1_crit_min.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_power1_input_highest.dev_attr.attr, + &sensor_dev_attr_power1_reset_history.dev_attr.attr, + &sensor_dev_attr_power1_max.dev_attr.attr, + &sensor_dev_attr_power1_crit.dev_attr.attr, + &sensor_dev_attr_power1_max_alarm.dev_attr.attr, + &sensor_dev_attr_power1_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + + &sensor_dev_attr_update_interval.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group ina209_group = { + .attrs = ina209_attributes, +}; + +static void ina209_restore_conf(struct i2c_client *client, + struct ina209_data *data) +{ + /* Restore initial configuration */ + i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, + data->config_orig); + i2c_smbus_write_word_swapped(client, INA209_CALIBRATION, + data->calibration_orig); +} + +static int ina209_init_client(struct i2c_client *client, + struct ina209_data *data) +{ + struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); + u32 shunt; + int reg; + + reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION); + if (reg < 0) + return reg; + data->calibration_orig = reg; + + reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION); + if (reg < 0) + return reg; + data->config_orig = reg; + + if (pdata) { + if (pdata->shunt_uohms <= 0) + return -EINVAL; + shunt = pdata->shunt_uohms; + } else if (!of_property_read_u32(client->dev.of_node, "shunt-resistor", + &shunt)) { + if (shunt == 0) + return -EINVAL; + } else { + shunt = data->calibration_orig ? + 40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT; + } + + i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, + INA209_CONFIG_DEFAULT); + data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT); + + /* + * Calibrate current LSB to 1mA. Shunt is in uOhms. + * See equation 13 in datasheet. + */ + i2c_smbus_write_word_swapped(client, INA209_CALIBRATION, + clamp_val(40960000 / shunt, 1, 65535)); + + /* Clear status register */ + i2c_smbus_read_word_swapped(client, INA209_STATUS); + + return 0; +} + +static int ina209_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct ina209_data *data; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + ret = ina209_init_client(client, data); + if (ret) + return ret; + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, &ina209_group); + if (ret) + goto out_restore_conf; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_hwmon_device_register; + } + + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, &ina209_group); +out_restore_conf: + ina209_restore_conf(client, data); + return ret; +} + +static int ina209_remove(struct i2c_client *client) +{ + struct ina209_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &ina209_group); + ina209_restore_conf(client, data); + + return 0; +} + +static const struct i2c_device_id ina209_id[] = { + { "ina209", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina209_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ina209_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ina209", + }, + .probe = ina209_probe, + .remove = ina209_remove, + .id_table = ina209_id, +}; + +module_i2c_driver(ina209_driver); + +MODULE_AUTHOR("Ira W. Snyder , Paul Hays , Guenter Roeck "); +MODULE_DESCRIPTION("INA209 driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 1640eaecc4c5f62c73a87445a55c0f0cbe3b4579 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 7 Mar 2012 03:54:50 -0800 Subject: hwmon: (pmbus/zl6100) Add support for VMON/VDRV Some of the ZL6100 compatible chips support monitoring a separate voltage pin, VMON (ZL2004) or VDRV (ZL91xx). Report it as in2 / vmon. The chips support implicit warning limits for VMON/VDRV, as percentage of the respective critical voltage. Support by reading/writing the critical voltages and calculating the associated warning voltages. Signed-off-by: Guenter Roeck --- Documentation/hwmon/zl6100 | 26 +++++-- drivers/hwmon/pmbus/zl6100.c | 176 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 188 insertions(+), 14 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/zl6100 b/Documentation/hwmon/zl6100 index a995b41724fd..3d924b6b59e9 100644 --- a/Documentation/hwmon/zl6100 +++ b/Documentation/hwmon/zl6100 @@ -121,12 +121,26 @@ in1_max_alarm Input voltage high alarm. in1_lcrit_alarm Input voltage critical low alarm. in1_crit_alarm Input voltage critical high alarm. -in2_label "vout1" -in2_input Measured output voltage. -in2_lcrit Critical minimum output Voltage. -in2_crit Critical maximum output voltage. -in2_lcrit_alarm Critical output voltage critical low alarm. -in2_crit_alarm Critical output voltage critical high alarm. +in2_label "vmon" +in2_input Measured voltage on VMON (ZL2004) or VDRV (ZL9101M, + ZL9117M) pin. Reported voltage is 16x the voltage on the + pin (adjusted internally by the chip). +in2_lcrit Critical minumum VMON/VDRV Voltage. +in2_crit Critical maximum VMON/VDRV voltage. +in2_lcrit_alarm VMON/VDRV voltage critical low alarm. +in2_crit_alarm VMON/VDRV voltage critical high alarm. + + vmon attributes are supported on ZL2004, ZL9101M, + and ZL9117M only. + +inX_label "vout1" +inX_input Measured output voltage. +inX_lcrit Critical minimum output Voltage. +inX_crit Critical maximum output voltage. +inX_lcrit_alarm Critical output voltage critical low alarm. +inX_crit_alarm Critical output voltage critical high alarm. + + X is 3 for ZL2004, ZL9101M, and ZL9117M, 2 otherwise. curr1_label "iout1" curr1_input Measured output current. diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c index fc5eed8e85bb..819644121259 100644 --- a/drivers/hwmon/pmbus/zl6100.c +++ b/drivers/hwmon/pmbus/zl6100.c @@ -2,6 +2,7 @@ * Hardware monitoring driver for ZL6100 and compatibles * * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck * * 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 @@ -45,12 +46,87 @@ struct zl6100_data { #define ZL6100_MFR_XTEMP_ENABLE (1 << 7) +#define MFR_VMON_OV_FAULT_LIMIT 0xf5 +#define MFR_VMON_UV_FAULT_LIMIT 0xf6 +#define MFR_READ_VMON 0xf7 + +#define VMON_UV_WARNING (1 << 5) +#define VMON_OV_WARNING (1 << 4) +#define VMON_UV_FAULT (1 << 1) +#define VMON_OV_FAULT (1 << 0) + #define ZL6100_WAIT_TIME 1000 /* uS */ static ushort delay = ZL6100_WAIT_TIME; module_param(delay, ushort, 0644); MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); +/* Convert linear sensor value to milli-units */ +static long zl6100_l2d(s16 l) +{ + s16 exponent; + s32 mantissa; + long val; + + exponent = l >> 11; + mantissa = ((s16)((l & 0x7ff) << 5)) >> 5; + + val = mantissa; + + /* scale result to milli-units */ + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 zl6100_d2l(long val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (val < 0) { + negative = true; + val = -val; + } + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + /* Some chips need a delay between accesses */ static inline void zl6100_wait(const struct zl6100_data *data) { @@ -65,9 +141,9 @@ static int zl6100_read_word_data(struct i2c_client *client, int page, int reg) { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); struct zl6100_data *data = to_zl6100_data(info); - int ret; + int ret, vreg; - if (page || reg >= PMBUS_VIRT_BASE) + if (page > 0) return -ENXIO; if (data->id == zl2005) { @@ -83,9 +159,39 @@ static int zl6100_read_word_data(struct i2c_client *client, int page, int reg) } } + switch (reg) { + case PMBUS_VIRT_READ_VMON: + vreg = MFR_READ_VMON; + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + break; + } + zl6100_wait(data); - ret = pmbus_read_word_data(client, page, reg); + ret = pmbus_read_word_data(client, page, vreg); data->access = ktime_get(); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 9, 10)); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 11, 10)); + break; + } return ret; } @@ -94,13 +200,35 @@ static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); struct zl6100_data *data = to_zl6100_data(info); - int ret; + int ret, status; if (page > 0) return -ENXIO; zl6100_wait(data); - ret = pmbus_read_byte_data(client, page, reg); + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + + status = 0; + if (ret & VMON_UV_WARNING) + status |= PB_VOLTAGE_UV_WARNING; + if (ret & VMON_OV_WARNING) + status |= PB_VOLTAGE_OV_WARNING; + if (ret & VMON_UV_FAULT) + status |= PB_VOLTAGE_UV_FAULT; + if (ret & VMON_OV_FAULT) + status |= PB_VOLTAGE_OV_FAULT; + ret = status; + break; + default: + ret = pmbus_read_byte_data(client, page, reg); + break; + } data->access = ktime_get(); return ret; @@ -111,13 +239,38 @@ static int zl6100_write_word_data(struct i2c_client *client, int page, int reg, { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); struct zl6100_data *data = to_zl6100_data(info); - int ret; + int ret, vreg; - if (page || reg >= PMBUS_VIRT_BASE) + if (page > 0) return -ENXIO; + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 9)); + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 11)); + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + } + zl6100_wait(data); - ret = pmbus_write_word_data(client, page, reg, word); + ret = pmbus_write_word_data(client, page, vreg, word); data->access = ktime_get(); return ret; @@ -225,6 +378,13 @@ static int zl6100_probe(struct i2c_client *client, | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + /* + * ZL2004, ZL9101M, and ZL9117M support monitoring an extra voltage + * (VMON for ZL2004, VDRV for ZL9101M and ZL9117M). Report it as vmon. + */ + if (data->id == zl2004 || data->id == zl9101 || data->id == zl9117) + info->func[0] |= PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON; + ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG); if (ret < 0) return ret; -- cgit v1.2.3-59-g8ed1b From 50115ac9b6f35ce6bbb61e5ae21fc3bbd01d490c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 27 Apr 2012 15:13:08 -0700 Subject: hwmon: (pmbus/max34440) Add support for MAX34460 and MAX34461 MAX34460 is a PMBus 12-Channel Voltage Monitor & Sequencer. MAX34461 is a PMBus 16-Channel Voltage Monitor & Sequencer. Signed-off-by: Guenter Roeck --- Documentation/hwmon/max34440 | 16 +++++++++ drivers/hwmon/pmbus/Kconfig | 2 +- drivers/hwmon/pmbus/max34440.c | 75 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 3 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/max34440 b/Documentation/hwmon/max34440 index 04482226db20..47651ff341ae 100644 --- a/Documentation/hwmon/max34440 +++ b/Documentation/hwmon/max34440 @@ -16,6 +16,16 @@ Supported chips: Prefixes: 'max34446' Addresses scanned: - Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX34446.pdf + * Maxim MAX34460 + PMBus 12-Channel Voltage Monitor & Sequencer + Prefix: 'max34460' + Addresses scanned: - + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX34460.pdf + * Maxim MAX34461 + PMBus 16-Channel Voltage Monitor & Sequencer + Prefix: 'max34461' + Addresses scanned: - + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX34461.pdf Author: Guenter Roeck @@ -26,6 +36,9 @@ Description This driver supports hardware montoring for Maxim MAX34440 PMBus 6-Channel Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply Manager and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data Logger. +It also supports the MAX34460 and MAX34461 PMBus Voltage Monitor & Sequencers. +The MAX34460 supports 12 voltage channels, and the MAX34461 supports 16 voltage +channels. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus for details on PMBus client drivers. @@ -109,3 +122,6 @@ temp[1-8]_reset_history Write any value to reset history. temp7 and temp8 attributes only exist for MAX34440. MAX34446 only supports temp[1-3]. + +MAX34460 supports attribute groups in[1-12] and temp[1-5]. +MAX34461 supports attribute groups in[1-16] and temp[1-5]. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 60745a535821..4f9eb0af5229 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -72,7 +72,7 @@ config SENSORS_MAX34440 default n help If you say yes here you get hardware monitoring support for Maxim - MAX34440, MAX34441, and MAX34446. + MAX34440, MAX34441, MAX34446, MAX34460, and MAX34461. This driver can also be built as a module. If so, the module will be called max34440. diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index 2ada7b021fbe..7e930c3ce1ab 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -2,6 +2,7 @@ * Hardware monitoring driver for Maxim MAX34440/MAX34441 * * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck * * 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 @@ -25,7 +26,7 @@ #include #include "pmbus.h" -enum chips { max34440, max34441, max34446 }; +enum chips { max34440, max34441, max34446, max34460, max34461 }; #define MAX34440_MFR_VOUT_PEAK 0xd4 #define MAX34440_MFR_IOUT_PEAK 0xd5 @@ -87,7 +88,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, int reg) MAX34446_MFR_POUT_PEAK); break; case PMBUS_VIRT_READ_TEMP_AVG: - if (data->id != max34446) + if (data->id != max34446 && data->id != max34460 && + data->id != max34461) return -ENXIO; ret = pmbus_read_word_data(client, page, MAX34446_MFR_TEMPERATURE_AVG); @@ -322,6 +324,73 @@ static struct pmbus_driver_info max34440_info[] = { .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[14] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[15] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34461] = { + .pages = 23, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[12] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[14] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[15] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + /* page 16 is reserved */ + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, }; static int max34440_probe(struct i2c_client *client, @@ -343,6 +412,8 @@ static const struct i2c_device_id max34440_id[] = { {"max34440", max34440}, {"max34441", max34441}, {"max34446", max34446}, + {"max34460", max34460}, + {"max34461", max34461}, {} }; MODULE_DEVICE_TABLE(i2c, max34440_id); -- cgit v1.2.3-59-g8ed1b From d47682803776f147ee9662a505c63bbbf27a1274 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 28 Jan 2013 20:35:19 -0800 Subject: hwmon: (jc42) Add support for MCP98244 Signed-off-by: Guenter Roeck Reviewed-by: Jean Delvare --- Documentation/hwmon/jc42 | 3 ++- drivers/hwmon/Kconfig | 4 ++-- drivers/hwmon/jc42.c | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/jc42 b/Documentation/hwmon/jc42 index 66ecb9fc8246..165077121238 100644 --- a/Documentation/hwmon/jc42 +++ b/Documentation/hwmon/jc42 @@ -17,12 +17,13 @@ Supported chips: * Maxim MAX6604 Datasheets: http://datasheets.maxim-ic.com/en/ds/MAX6604.pdf - * Microchip MCP9804, MCP9805, MCP98242, MCP98243, MCP9843 + * Microchip MCP9804, MCP9805, MCP98242, MCP98243, MCP98244, MCP9843 Datasheets: http://ww1.microchip.com/downloads/en/DeviceDoc/22203C.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/21977b.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/21996a.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/22153c.pdf + http://ww1.microchip.com/downloads/en/DeviceDoc/22327A.pdf * NXP Semiconductors SE97, SE97B, SE98, SE98A Datasheets: http://www.nxp.com/documents/data_sheet/SE97.pdf diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5e77cef09ca6..52d5174792ee 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -530,8 +530,8 @@ config SENSORS_JC42 temperature sensors, which are used on many DDR3 memory modules for mobile devices and servers. Support will include, but not be limited to, ADT7408, AT30TS00, CAT34TS02, CAT6095, MAX6604, MCP9804, MCP9805, - MCP98242, MCP98243, MCP9843, SE97, SE98, STTS424(E), STTS2002, - STTS3000, TSE2002B3, TSE2002GB2, TS3000B3, and TS3000GB2. + MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98, STTS424(E), + STTS2002, STTS3000, TSE2002B3, TSE2002GB2, TS3000B3, and TS3000GB2. This driver can also be built as a module. If so, the module will be called jc42. diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index 82d962488f55..4a58f130fd4e 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -103,6 +103,9 @@ static const unsigned short normal_i2c[] = { #define MCP98243_DEVID 0x2100 #define MCP98243_DEVID_MASK 0xfffc +#define MCP98244_DEVID 0x2200 +#define MCP98244_DEVID_MASK 0xfffc + #define MCP9843_DEVID 0x0000 /* Also matches mcp9805 */ #define MCP9843_DEVID_MASK 0xfffe @@ -147,6 +150,7 @@ static struct jc42_chips jc42_chips[] = { { MCP_MANID, MCP9804_DEVID, MCP9804_DEVID_MASK }, { MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK }, { MCP_MANID, MCP98243_DEVID, MCP98243_DEVID_MASK }, + { MCP_MANID, MCP98244_DEVID, MCP98244_DEVID_MASK }, { MCP_MANID, MCP9843_DEVID, MCP9843_DEVID_MASK }, { NXP_MANID, SE97_DEVID, SE97_DEVID_MASK }, { ONS_MANID, CAT6095_DEVID, CAT6095_DEVID_MASK }, -- cgit v1.2.3-59-g8ed1b