From 9908ad4cd4bc3e6620e1819e7f9b43f109650e1b Mon Sep 17 00:00:00 2001 From: Jean Delvare <khali@linux-fr.org> Date: Tue, 31 Jan 2012 09:27:11 -0500 Subject: hwmon: (lm80) Add detection of NatSemi/TI LM96080 Add detection of the National Semiconductor (now Texas Instruments) LM96080. It is functionally compatible with the LM80 but detection is completely different. Signed-off-by: Jean Delvare <khali@linux-fr.org> Cc: Guenter Roeck <guenter.roeck@ericsson.com> Cc: Frans Meulenbroeks <fransmeulenbroeks@gmail.com> Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com> --- Documentation/hwmon/lm80 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/lm80 b/Documentation/hwmon/lm80 index cb5b407ba3e6..a60b43efc32b 100644 --- a/Documentation/hwmon/lm80 +++ b/Documentation/hwmon/lm80 @@ -7,6 +7,11 @@ Supported chips: Addresses scanned: I2C 0x28 - 0x2f Datasheet: Publicly available at the National Semiconductor website http://www.national.com/ + * National Semiconductor LM96080 + Prefix: 'lm96080' + Addresses scanned: I2C 0x28 - 0x2f + Datasheet: Publicly available at the National Semiconductor website + http://www.national.com/ Authors: Frodo Looijaard <frodol@dds.nl>, @@ -17,7 +22,9 @@ Description This driver implements support for the National Semiconductor LM80. It is described as a 'Serial Interface ACPI-Compatible Microprocessor -System Hardware Monitor'. +System Hardware Monitor'. The LM96080 is a more recent incarnation, +it is pin and register compatible, with a few additional features not +yet supported by the driver. The LM80 implements one temperature sensor, two fan rotation speed sensors, seven voltage sensors, alarms, and some miscellaneous stuff. -- cgit v1.2.3 From 56aad5d143cbce620c079e1acf761b71f59758a0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck <linux@roeck-us.net> Date: Fri, 24 Feb 2012 08:13:31 -0800 Subject: hwmon: (max34440) Add support for 'lowest' output voltage attribute MAX34440 and compatibles support reporting the lowest measured output voltage. Add support for it. Signed-off-by: Guenter Roeck <linux@roeck-us.net> Acked-by: Jean Delvare <khali@linux-fr.org> --- Documentation/hwmon/max34440 | 1 + drivers/hwmon/pmbus/max34440.c | 9 +++++++++ 2 files changed, 10 insertions(+) (limited to 'Documentation') diff --git a/Documentation/hwmon/max34440 b/Documentation/hwmon/max34440 index 8ab51536a1eb..36b335c461dc 100644 --- a/Documentation/hwmon/max34440 +++ b/Documentation/hwmon/max34440 @@ -56,6 +56,7 @@ in[1-6]_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status. in[1-6]_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status. in[1-6]_lcrit_alarm Voltage critical low alarm. From VOLTAGE_UV_FAULT status. in[1-6]_crit_alarm Voltage critical high alarm. From VOLTAGE_OV_FAULT status. +in[1-6]_lowest Historical minimum voltage. in[1-6]_highest Historical maximum voltage. in[1-6]_reset_history Write any value to reset history. diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index 7d830c1e7032..95ee9e195cb6 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -30,6 +30,7 @@ enum chips { max34440, max34441 }; #define MAX34440_MFR_VOUT_PEAK 0xd4 #define MAX34440_MFR_IOUT_PEAK 0xd5 #define MAX34440_MFR_TEMPERATURE_PEAK 0xd6 +#define MAX34440_MFR_VOUT_MIN 0xd7 #define MAX34440_STATUS_OC_WARN (1 << 0) #define MAX34440_STATUS_OC_FAULT (1 << 1) @@ -41,6 +42,10 @@ static int max34440_read_word_data(struct i2c_client *client, int page, int reg) int ret; switch (reg) { + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_VOUT_MIN); + break; case PMBUS_VIRT_READ_VOUT_MAX: ret = pmbus_read_word_data(client, page, MAX34440_MFR_VOUT_PEAK); @@ -72,6 +77,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page, switch (reg) { case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_VOUT_MIN, 0x7fff); + if (ret) + break; ret = pmbus_write_word_data(client, page, MAX34440_MFR_VOUT_PEAK, 0); break; -- cgit v1.2.3 From c5f35c9d8285f4a065cb0fc2c57f7526e646b30f Mon Sep 17 00:00:00 2001 From: Guenter Roeck <linux@roeck-us.net> Date: Tue, 28 Feb 2012 10:24:54 -0800 Subject: hwmon: (pmbus) Add support for TI TPS40400 and TPS40422 TPS40400 and TPS40422 are supported by the generic PMBus driver. Add device IDs and data sheet references. Signed-off-by: Guenter Roeck <linux@roeck-us.net> --- Documentation/hwmon/pmbus | 6 ++++++ drivers/hwmon/pmbus/Kconfig | 2 +- drivers/hwmon/pmbus/pmbus.c | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/pmbus b/Documentation/hwmon/pmbus index d28b591753d1..c2b157ba9006 100644 --- a/Documentation/hwmon/pmbus +++ b/Documentation/hwmon/pmbus @@ -22,6 +22,12 @@ Supported chips: http://www.lineagepower.com/oem/pdf/PDT006A0X.pdf http://www.lineagepower.com/oem/pdf/PDT012A0X.pdf http://www.lineagepower.com/oem/pdf/UDT020A0X.pdf + * Texas Instruments TPS40400, TPS40422 + Prefixes: 'tps40400', 'tps40422' + Addresses scanned: - + Datasheets: + http://www.ti.com/lit/gpn/tps40400 + http://www.ti.com/lit/gpn/tps40422 * Generic PMBus devices Prefix: 'pmbus' Addresses scanned: - diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index cfec923f42b7..7968c815f443 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -20,7 +20,7 @@ config SENSORS_PMBUS help If you say yes here you get hardware monitoring support for generic PMBus devices, including but not limited to ADP4000, BMR453, BMR454, - NCP4200, and NCP4208. + NCP4200, NCP4208, TPS40400, and TPS40422. This driver can also be built as a module. If so, the module will be called pmbus. diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c index 34887408505b..7d5161dd04f0 100644 --- a/drivers/hwmon/pmbus/pmbus.c +++ b/drivers/hwmon/pmbus/pmbus.c @@ -191,6 +191,8 @@ static const struct i2c_device_id pmbus_id[] = { {"pdt006", 1}, {"pdt012", 1}, {"pmbus", 0}, + {"tps40400", 1}, + {"tps40422", 2}, {"udt020", 1}, {} }; -- cgit v1.2.3 From 216334094a875ed350079fb0b1b057ca17eb8b8e Mon Sep 17 00:00:00 2001 From: Guenter Roeck <linux@roeck-us.net> Date: Tue, 28 Feb 2012 11:00:54 -0800 Subject: hwmon: (pmbus) Add support for Lineage Power MDT040 MDT040 is supported by the generic PMBus driver. Add device ID and reference to datasheet. Also mention Lineage Power device support in Kconfig. Signed-off-by: Guenter Roeck <linux@roeck-us.net> --- Documentation/hwmon/pmbus | 3 ++- drivers/hwmon/pmbus/Kconfig | 3 ++- drivers/hwmon/pmbus/pmbus.c | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/pmbus b/Documentation/hwmon/pmbus index c2b157ba9006..f90f99920cc5 100644 --- a/Documentation/hwmon/pmbus +++ b/Documentation/hwmon/pmbus @@ -15,13 +15,14 @@ Supported chips: http://www.onsemi.com/pub_link/Collateral/NCP4200-D.PDF http://www.onsemi.com/pub_link/Collateral/JUNE%202009-%20REV.%200.PDF * Lineage Power - Prefixes: 'pdt003', 'pdt006', 'pdt012', 'udt020' + Prefixes: 'mdt040', 'pdt003', 'pdt006', 'pdt012', 'udt020' Addresses scanned: - Datasheets: http://www.lineagepower.com/oem/pdf/PDT003A0X.pdf http://www.lineagepower.com/oem/pdf/PDT006A0X.pdf http://www.lineagepower.com/oem/pdf/PDT012A0X.pdf http://www.lineagepower.com/oem/pdf/UDT020A0X.pdf + http://www.lineagepower.com/oem/pdf/MDT040A0X.pdf * Texas Instruments TPS40400, TPS40422 Prefixes: 'tps40400', 'tps40422' Addresses scanned: - diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 7968c815f443..d1aa2dbd5b75 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -20,7 +20,8 @@ config SENSORS_PMBUS help If you say yes here you get hardware monitoring support for generic PMBus devices, including but not limited to ADP4000, BMR453, BMR454, - NCP4200, NCP4208, TPS40400, and TPS40422. + MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, UDT020, TPS40400, + and TPS40422. This driver can also be built as a module. If so, the module will be called pmbus. diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c index 7d5161dd04f0..7e91700131a7 100644 --- a/drivers/hwmon/pmbus/pmbus.c +++ b/drivers/hwmon/pmbus/pmbus.c @@ -185,6 +185,7 @@ static const struct i2c_device_id pmbus_id[] = { {"adp4000", 1}, {"bmr453", 1}, {"bmr454", 1}, + {"mdt040", 1}, {"ncp4200", 1}, {"ncp4208", 1}, {"pdt003", 1}, -- cgit v1.2.3 From 590defe59ef9596dcd892d2d8395d730c510323d Mon Sep 17 00:00:00 2001 From: Guenter Roeck <linux@roeck-us.net> Date: Fri, 24 Feb 2012 03:40:22 -0800 Subject: hwmon: (max34440) Add support for MAX34446 Signed-off-by: Guenter Roeck <linux@roeck-us.net> --- Documentation/hwmon/max34440 | 29 ++++++++++- drivers/hwmon/pmbus/Kconfig | 4 +- drivers/hwmon/pmbus/max34440.c | 112 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 138 insertions(+), 7 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/max34440 b/Documentation/hwmon/max34440 index 36b335c461dc..bbdf9bf5d8f5 100644 --- a/Documentation/hwmon/max34440 +++ b/Documentation/hwmon/max34440 @@ -11,6 +11,11 @@ Supported chips: Prefixes: 'max34441' Addresses scanned: - Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX34441.pdf + * Maxim MAX34446 + PMBus Power-Supply Data Logger + Prefixes: 'max34446' + Addresses scanned: - + Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX34446.pdf Author: Guenter Roeck <guenter.roeck@ericsson.com> @@ -19,8 +24,8 @@ Description ----------- This driver supports hardware montoring for Maxim MAX34440 PMBus 6-Channel -Power-Supply Manager and MAX34441 PMBus 5-Channel Power-Supply Manager -and Intelligent Fan Controller. +Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply Manager +and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data Logger. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus for details on PMBus client drivers. @@ -33,6 +38,13 @@ This driver does not auto-detect devices. You will have to instantiate the devices explicitly. Please see Documentation/i2c/instantiating-devices for details. +For MAX34446, the value of the currX_crit attribute determines if current or +voltage measurement is enabled for a given channel. Voltage measurement is +enabled if currX_crit is set to 0; current measurement is enabled if the +attribute is set to a positive value. Power measurement is only enabled if +channel 1 (3) is configured for voltage measurement, and channel 2 (4) is +configured for current measurement. + Platform data support --------------------- @@ -60,16 +72,27 @@ in[1-6]_lowest Historical minimum voltage. in[1-6]_highest Historical maximum voltage. in[1-6]_reset_history Write any value to reset history. + MAX34446 only supports in[1-4]. + curr[1-6]_label "iout[1-6]". curr[1-6]_input Measured current. From READ_IOUT register. curr[1-6]_max Maximum current. From IOUT_OC_WARN_LIMIT register. curr[1-6]_crit Critical maximum current. From IOUT_OC_FAULT_LIMIT register. curr[1-6]_max_alarm Current high alarm. From IOUT_OC_WARNING status. curr[1-6]_crit_alarm Current critical high alarm. From IOUT_OC_FAULT status. +curr[1-4]_average Historical average current (MAX34446 only). curr[1-6]_highest Historical maximum current. curr[1-6]_reset_history Write any value to reset history. in6 and curr6 attributes only exist for MAX34440. + MAX34446 only supports curr[1-4]. + +power[1,3]_label "pout[1,3]" +power[1,3]_input Measured power. +power[1,3]_average Historical average power. +power[1,3]_highest Historical maximum power. + + Power attributes only exist for MAX34446. temp[1-8]_input Measured temperatures. From READ_TEMPERATURE_1 register. temp1 is the chip's internal temperature. temp2..temp5 @@ -80,7 +103,9 @@ temp[1-8]_max Maximum temperature. From OT_WARN_LIMIT register. temp[1-8]_crit Critical high temperature. From OT_FAULT_LIMIT register. temp[1-8]_max_alarm Temperature high alarm. temp[1-8]_crit_alarm Temperature critical high alarm. +temp[1-8]_average Historical average temperature (MAX34446 only). temp[1-8]_highest Historical maximum temperature. 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]. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index d1aa2dbd5b75..1f0d0110b6b8 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -68,11 +68,11 @@ config SENSORS_MAX16064 be called max16064. config SENSORS_MAX34440 - tristate "Maxim MAX34440/MAX34441" + tristate "Maxim MAX34440 and compatibles" default n help If you say yes here you get hardware monitoring support for Maxim - MAX34440 and MAX34441. + MAX34440, MAX34441, and MAX34446. 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 95ee9e195cb6..2ada7b021fbe 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -25,21 +25,35 @@ #include <linux/i2c.h> #include "pmbus.h" -enum chips { max34440, max34441 }; +enum chips { max34440, max34441, max34446 }; #define MAX34440_MFR_VOUT_PEAK 0xd4 #define MAX34440_MFR_IOUT_PEAK 0xd5 #define MAX34440_MFR_TEMPERATURE_PEAK 0xd6 #define MAX34440_MFR_VOUT_MIN 0xd7 +#define MAX34446_MFR_POUT_PEAK 0xe0 +#define MAX34446_MFR_POUT_AVG 0xe1 +#define MAX34446_MFR_IOUT_AVG 0xe2 +#define MAX34446_MFR_TEMPERATURE_AVG 0xe3 + #define MAX34440_STATUS_OC_WARN (1 << 0) #define MAX34440_STATUS_OC_FAULT (1 << 1) #define MAX34440_STATUS_OT_FAULT (1 << 5) #define MAX34440_STATUS_OT_WARN (1 << 6) +struct max34440_data { + int id; + struct pmbus_driver_info info; +}; + +#define to_max34440_data(x) container_of(x, struct max34440_data, info) + static int max34440_read_word_data(struct i2c_client *client, int page, int reg) { int ret; + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); switch (reg) { case PMBUS_VIRT_READ_VOUT_MIN: @@ -50,14 +64,43 @@ static int max34440_read_word_data(struct i2c_client *client, int page, int reg) ret = pmbus_read_word_data(client, page, MAX34440_MFR_VOUT_PEAK); break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_IOUT_AVG); + break; case PMBUS_VIRT_READ_IOUT_MAX: ret = pmbus_read_word_data(client, page, MAX34440_MFR_IOUT_PEAK); break; + case PMBUS_VIRT_READ_POUT_AVG: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_POUT_AVG); + break; + case PMBUS_VIRT_READ_POUT_MAX: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_POUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_AVG: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_TEMPERATURE_AVG); + break; case PMBUS_VIRT_READ_TEMP_MAX: ret = pmbus_read_word_data(client, page, MAX34440_MFR_TEMPERATURE_PEAK); break; + case PMBUS_VIRT_RESET_POUT_HISTORY: + if (data->id != max34446) + return -ENXIO; + ret = 0; + break; case PMBUS_VIRT_RESET_VOUT_HISTORY: case PMBUS_VIRT_RESET_IOUT_HISTORY: case PMBUS_VIRT_RESET_TEMP_HISTORY: @@ -73,9 +116,19 @@ static int max34440_read_word_data(struct i2c_client *client, int page, int reg) static int max34440_write_word_data(struct i2c_client *client, int page, int reg, u16 word) { + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); int ret; switch (reg) { + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); + if (ret) + break; + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_AVG, 0); + break; case PMBUS_VIRT_RESET_VOUT_HISTORY: ret = pmbus_write_word_data(client, page, MAX34440_MFR_VOUT_MIN, 0x7fff); @@ -87,11 +140,18 @@ static int max34440_write_word_data(struct i2c_client *client, int page, case PMBUS_VIRT_RESET_IOUT_HISTORY: ret = pmbus_write_word_data(client, page, MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && data->id == max34446) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + break; case PMBUS_VIRT_RESET_TEMP_HISTORY: ret = pmbus_write_word_data(client, page, MAX34440_MFR_TEMPERATURE_PEAK, 0x8000); + if (!ret && data->id == max34446) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_TEMPERATURE_AVG, 0); break; default: ret = -ENODATA; @@ -225,20 +285,66 @@ static struct pmbus_driver_info max34440_info[] = { .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, }, + [max34446] = { + .pages = 7, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[5] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[6] = 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, const struct i2c_device_id *id) { - return pmbus_do_probe(client, id, &max34440_info[id->driver_data]); + struct max34440_data *data; + + data = devm_kzalloc(&client->dev, sizeof(struct max34440_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + data->id = id->driver_data; + data->info = max34440_info[id->driver_data]; + + return pmbus_do_probe(client, id, &data->info); } static const struct i2c_device_id max34440_id[] = { {"max34440", max34440}, {"max34441", max34441}, + {"max34446", max34446}, {} }; - MODULE_DEVICE_TABLE(i2c, max34440_id); /* This is the driver that will be inserted */ -- cgit v1.2.3 From 927112696654f4c96a85aa8a494866cbc6ccfbe6 Mon Sep 17 00:00:00 2001 From: Guenter Roeck <linux@roeck-us.net> Date: Fri, 24 Feb 2012 03:40:53 -0800 Subject: hwmon: (adm1275) Add support for ADM1075 Signed-off-by: Guenter Roeck <linux@roeck-us.net> --- Documentation/hwmon/adm1275 | 30 +++++++++++---- drivers/hwmon/pmbus/Kconfig | 4 +- drivers/hwmon/pmbus/adm1275.c | 88 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 13 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/adm1275 b/Documentation/hwmon/adm1275 index ab70d96d2dfd..535ad23ea704 100644 --- a/Documentation/hwmon/adm1275 +++ b/Documentation/hwmon/adm1275 @@ -2,6 +2,10 @@ Kernel driver adm1275 ===================== Supported chips: + * Analog Devices ADM1075 + Prefix: 'adm1075' + Addresses scanned: - + Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1075.pdf * Analog Devices ADM1275 Prefix: 'adm1275' Addresses scanned: - @@ -17,13 +21,13 @@ Author: Guenter Roeck <guenter.roeck@ericsson.com> Description ----------- -This driver supports hardware montoring for Analog Devices ADM1275 and ADM1276 -Hot-Swap Controller and Digital Power Monitor. +This driver supports hardware montoring for Analog Devices ADM1075, ADM1275, +and ADM1276 Hot-Swap Controller and Digital Power Monitor. -ADM1275 and ADM1276 are hot-swap controllers that allow a circuit board to be -removed from or inserted into a live backplane. They also feature current and -voltage readback via an integrated 12-bit analog-to-digital converter (ADC), -accessed using a PMBus interface. +ADM1075, ADM1275, and ADM1276 are hot-swap controllers that allow a circuit +board to be removed from or inserted into a live backplane. They also feature +current and voltage readback via an integrated 12-bit analog-to-digital +converter (ADC), accessed using a PMBus interface. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus for details on PMBus client drivers. @@ -36,6 +40,10 @@ This driver does not auto-detect devices. You will have to instantiate the devices explicitly. Please see Documentation/i2c/instantiating-devices for details. +The ADM1075, unlike many other PMBus devices, does not support internal voltage +or current scaling. Reported voltages, currents, and power are raw measurements, +and will typically have to be scaled. + Platform data support --------------------- @@ -51,7 +59,8 @@ The following attributes are supported. Limits are read-write, history reset attributes are write-only, all other attributes are read-only. in1_label "vin1" or "vout1" depending on chip variant and - configuration. + configuration. On ADM1075, vout1 reports the voltage on + the VAUX pin. in1_input Measured voltage. in1_min Minumum Voltage. in1_max Maximum voltage. @@ -74,3 +83,10 @@ curr1_crit Critical maximum current. Depending on the chip curr1_crit_alarm Critical current high alarm. curr1_highest Historical maximum current. curr1_reset_history Write any value to reset history. + +power1_label "pin1" +power1_input Input power. +power1_reset_history Write any value to reset history. + + Power attributes are supported on ADM1075 and ADM1276 + only. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 1f0d0110b6b8..9246d0154f74 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -31,8 +31,8 @@ config SENSORS_ADM1275 default n help If you say yes here you get hardware monitoring support for Analog - Devices ADM1275 and ADM1276 Hot-Swap Controller and Digital Power - Monitor. + Devices ADM1075, ADM1275, and ADM1276 Hot-Swap Controller and Digital + Power Monitors. This driver can also be built as a module. If so, the module will be called adm1275. diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 5e4421e57eb0..60aad9570f01 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -23,7 +23,7 @@ #include <linux/i2c.h> #include "pmbus.h" -enum chips { adm1275, adm1276 }; +enum chips { adm1075, adm1275, adm1276 }; #define ADM1275_PEAK_IOUT 0xd0 #define ADM1275_PEAK_VIN 0xd1 @@ -32,6 +32,9 @@ enum chips { adm1275, adm1276 }; #define ADM1275_VIN_VOUT_SELECT (1 << 6) #define ADM1275_VRANGE (1 << 5) +#define ADM1075_IRANGE_50 (1 << 4) +#define ADM1075_IRANGE_25 (1 << 3) +#define ADM1075_IRANGE_MASK ((1 << 3) | (1 << 4)) #define ADM1275_IOUT_WARN2_LIMIT 0xd7 #define ADM1275_DEVICE_CONFIG 0xd8 @@ -42,6 +45,14 @@ enum chips { adm1275, adm1276 }; #define ADM1275_MFR_STATUS_IOUT_WARN2 (1 << 0) +#define ADM1075_READ_VAUX 0xdd +#define ADM1075_VAUX_OV_WARN_LIMIT 0xde +#define ADM1075_VAUX_UV_WARN_LIMIT 0xdf +#define ADM1075_VAUX_STATUS 0xf6 + +#define ADM1075_VAUX_OV_WARN (1<<7) +#define ADM1075_VAUX_UV_WARN (1<<6) + struct adm1275_data { int id; bool have_oc_fault; @@ -74,6 +85,29 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) } ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = pmbus_read_word_data(client, 0, + ADM1075_VAUX_OV_WARN_LIMIT); + break; + case PMBUS_VOUT_UV_WARN_LIMIT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = pmbus_read_word_data(client, 0, + ADM1075_VAUX_UV_WARN_LIMIT); + break; + case PMBUS_READ_VOUT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = pmbus_read_word_data(client, 0, ADM1075_READ_VAUX); + break; case PMBUS_VIRT_READ_IOUT_MAX: ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT); break; @@ -84,7 +118,7 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN); break; case PMBUS_VIRT_READ_PIN_MAX: - if (data->id != adm1276) { + if (data->id == adm1275) { ret = -ENXIO; break; } @@ -95,7 +129,7 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) case PMBUS_VIRT_RESET_VIN_HISTORY: break; case PMBUS_VIRT_RESET_PIN_HISTORY: - if (data->id != adm1276) + if (data->id == adm1275) ret = -ENXIO; break; default: @@ -163,6 +197,19 @@ static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg) PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT; } break; + case PMBUS_STATUS_VOUT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = 0; + mfr_status = pmbus_read_byte_data(client, 0, + ADM1075_VAUX_STATUS); + if (mfr_status & ADM1075_VAUX_OV_WARN) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfr_status & ADM1075_VAUX_UV_WARN) + ret |= PB_VOLTAGE_UV_WARNING; + break; default: ret = -ENODATA; break; @@ -171,6 +218,7 @@ static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg) } static const struct i2c_device_id adm1275_id[] = { + { "adm1075", adm1075 }, { "adm1275", adm1275 }, { "adm1276", adm1276 }, { } @@ -251,7 +299,14 @@ static int adm1275_probe(struct i2c_client *client, info->read_byte_data = adm1275_read_byte_data; info->write_word_data = adm1275_write_word_data; - if (config & ADM1275_VRANGE) { + if (data->id == adm1075) { + info->m[PSC_VOLTAGE_IN] = 27169; + info->b[PSC_VOLTAGE_IN] = 0; + info->R[PSC_VOLTAGE_IN] = -1; + info->m[PSC_VOLTAGE_OUT] = 27169; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = -1; + } else if (config & ADM1275_VRANGE) { info->m[PSC_VOLTAGE_IN] = 19199; info->b[PSC_VOLTAGE_IN] = 0; info->R[PSC_VOLTAGE_IN] = -2; @@ -271,6 +326,31 @@ static int adm1275_probe(struct i2c_client *client, data->have_oc_fault = true; switch (data->id) { + case adm1075: + info->format[PSC_POWER] = direct; + info->b[PSC_POWER] = 0; + info->R[PSC_POWER] = -1; + switch (config & ADM1075_IRANGE_MASK) { + case ADM1075_IRANGE_25: + info->m[PSC_POWER] = 8549; + info->m[PSC_CURRENT_OUT] = 806; + break; + case ADM1075_IRANGE_50: + info->m[PSC_POWER] = 4279; + info->m[PSC_CURRENT_OUT] = 404; + break; + default: + dev_err(&client->dev, "Invalid input current range"); + info->m[PSC_POWER] = 0; + info->m[PSC_CURRENT_OUT] = 0; + break; + } + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; case adm1275: if (config & ADM1275_VIN_VOUT_SELECT) info->func[0] |= -- cgit v1.2.3 From 3360a106f8b4f87d3f3b0f1fd06c0c66fe45a87b Mon Sep 17 00:00:00 2001 From: Guenter Roeck <linux@roeck-us.net> Date: Tue, 28 Feb 2012 13:18:47 -0800 Subject: hwmon: (zl6100) Add support for ZL9101M and ZL9117M ZL9101M and ZL9117M are compatible to ZL6100. Add support to the zl6100 driver. Signed-off-by: Guenter Roeck <linux@roeck-us.net> Acked-by: Jean Delvare <khali@linux-fr.org> --- Documentation/hwmon/zl6100 | 8 ++++++++ drivers/hwmon/pmbus/Kconfig | 6 +++--- drivers/hwmon/pmbus/zl6100.c | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/zl6100 b/Documentation/hwmon/zl6100 index a4e8d90f59f6..2028c0a10c59 100644 --- a/Documentation/hwmon/zl6100 +++ b/Documentation/hwmon/zl6100 @@ -34,6 +34,14 @@ Supported chips: Prefix: 'zl6105' Addresses scanned: - Datasheet: http://www.intersil.com/data/fn/fn6906.pdf + * Intersil / Zilker Labs ZL9101M + Prefix: 'zl9101' + Addresses scanned: - + Datasheet: http://www.intersil.com/data/fn/fn7669.pdf + * Intersil / Zilker Labs ZL9117M + Prefix: 'zl9117' + Addresses scanned: - + Datasheet: http://www.intersil.com/data/fn/fn7914.pdf * Ericsson BMR450, BMR451 Prefix: 'bmr450', 'bmr451' Addresses scanned: - diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 9246d0154f74..2ca6a5a4f5a7 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -114,9 +114,9 @@ config SENSORS_ZL6100 default n help If you say yes here you get hardware monitoring support for Intersil - ZL2004, ZL2005, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, and ZL6105 - Digital DC/DC Controllers, as well as for Ericsson BMR450, BMR451, - BMR462, BMR463, and BMR464. + ZL2004, ZL2005, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, ZL6105, + ZL9101M, and ZL9117M Digital DC/DC Controllers, as well as for + Ericsson BMR450, BMR451, BMR462, BMR463, and BMR464. This driver can also be built as a module. If so, the module will be called zl6100. diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c index e5bb7355d480..fc5eed8e85bb 100644 --- a/drivers/hwmon/pmbus/zl6100.c +++ b/drivers/hwmon/pmbus/zl6100.c @@ -28,7 +28,8 @@ #include <linux/delay.h> #include "pmbus.h" -enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105 }; +enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105, + zl9101, zl9117 }; struct zl6100_data { int id; @@ -152,6 +153,8 @@ static const struct i2c_device_id zl6100_id[] = { {"zl2106", zl2106}, {"zl6100", zl6100}, {"zl6105", zl6105}, + {"zl9101", zl9101}, + {"zl9117", zl9117}, { } }; MODULE_DEVICE_TABLE(i2c, zl6100_id); -- cgit v1.2.3 From 86dda176344314526c7b65f1adce937b09d04f6d Mon Sep 17 00:00:00 2001 From: Guenter Roeck <linux@roeck-us.net> Date: Thu, 8 Mar 2012 08:20:24 -0800 Subject: hwmon: (jc42) Remove unnecessary device IDs We don't really use or need separate device IDs for the various JC42.4 compliant chips, so remove them and just stick with jc42. Also update a datasheet references for SE98A, STTS424, and STTS424E02. Signed-off-by: Guenter Roeck <linux@roeck-us.net> Acked-by: Jean Delvare <khali@linux-fr.org> --- Documentation/hwmon/jc42 | 39 +++++++++------------------------------ drivers/hwmon/jc42.c | 18 ------------------ 2 files changed, 9 insertions(+), 48 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/jc42 b/Documentation/hwmon/jc42 index 52729a756c1b..66ecb9fc8246 100644 --- a/Documentation/hwmon/jc42 +++ b/Documentation/hwmon/jc42 @@ -3,71 +3,50 @@ Kernel driver jc42 Supported chips: * Analog Devices ADT7408 - Prefix: 'adt7408' - Addresses scanned: I2C 0x18 - 0x1f Datasheets: http://www.analog.com/static/imported-files/data_sheets/ADT7408.pdf * Atmel AT30TS00 - Prefix: 'at30ts00' - Addresses scanned: I2C 0x18 - 0x1f Datasheets: http://www.atmel.com/Images/doc8585.pdf * IDT TSE2002B3, TSE2002GB2, TS3000B3, TS3000GB2 - Prefix: 'tse2002', 'ts3000' - Addresses scanned: I2C 0x18 - 0x1f Datasheets: http://www.idt.com/sites/default/files/documents/IDT_TSE2002B3C_DST_20100512_120303152056.pdf http://www.idt.com/sites/default/files/documents/IDT_TSE2002GB2A1_DST_20111107_120303145914.pdf http://www.idt.com/sites/default/files/documents/IDT_TS3000B3A_DST_20101129_120303152013.pdf http://www.idt.com/sites/default/files/documents/IDT_TS3000GB2A1_DST_20111104_120303151012.pdf * Maxim MAX6604 - Prefix: 'max6604' - Addresses scanned: I2C 0x18 - 0x1f Datasheets: http://datasheets.maxim-ic.com/en/ds/MAX6604.pdf * Microchip MCP9804, MCP9805, MCP98242, MCP98243, MCP9843 - Prefixes: 'mcp9804', 'mcp9805', 'mcp98242', 'mcp98243', 'mcp9843' - Addresses scanned: I2C 0x18 - 0x1f 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 - * NXP Semiconductors SE97, SE97B - Prefix: 'se97' - Addresses scanned: I2C 0x18 - 0x1f + * NXP Semiconductors SE97, SE97B, SE98, SE98A Datasheets: http://www.nxp.com/documents/data_sheet/SE97.pdf http://www.nxp.com/documents/data_sheet/SE97B.pdf - * NXP Semiconductors SE98 - Prefix: 'se98' - Addresses scanned: I2C 0x18 - 0x1f - Datasheets: http://www.nxp.com/documents/data_sheet/SE98.pdf + http://www.nxp.com/documents/data_sheet/SE98A.pdf * ON Semiconductor CAT34TS02, CAT6095 - Prefix: 'cat34ts02', 'cat6095' - Addresses scanned: I2C 0x18 - 0x1f Datasheet: http://www.onsemi.com/pub_link/Collateral/CAT34TS02-D.PDF http://www.onsemi.com/pub/Collateral/CAT6095-D.PDF - * ST Microelectronics STTS424, STTS424E02 - Prefix: 'stts424' - Addresses scanned: I2C 0x18 - 0x1f - Datasheets: - http://www.st.com/stonline/products/literature/ds/13447/stts424.pdf - http://www.st.com/stonline/products/literature/ds/13448/stts424e02.pdf - * ST Microelectronics STTS2002, STTS3000 - Prefix: 'stts2002', 'stts3000' - Addresses scanned: I2C 0x18 - 0x1f + * ST Microelectronics STTS424, STTS424E02, STTS2002, STTS3000 Datasheets: + http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00157556.pdf + http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00157558.pdf http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00225278.pdf http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATA_BRIEF/CD00270920.pdf * JEDEC JC 42.4 compliant temperature sensor chips - Prefix: 'jc42' - Addresses scanned: I2C 0x18 - 0x1f Datasheet: http://www.jedec.org/sites/default/files/docs/4_01_04R19.pdf + Common for all chips: + Prefix: 'jc42' + Addresses scanned: I2C 0x18 - 0x1f + Author: Guenter Roeck <guenter.roeck@ericsson.com> diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index a002bdac470a..a9bfd6736d9a 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -180,25 +180,7 @@ static int jc42_remove(struct i2c_client *client); static struct jc42_data *jc42_update_device(struct device *dev); static const struct i2c_device_id jc42_id[] = { - { "adt7408", 0 }, - { "at30ts00", 0 }, - { "cat94ts02", 0 }, - { "cat6095", 0 }, { "jc42", 0 }, - { "max6604", 0 }, - { "mcp9804", 0 }, - { "mcp9805", 0 }, - { "mcp98242", 0 }, - { "mcp98243", 0 }, - { "mcp9843", 0 }, - { "se97", 0 }, - { "se97b", 0 }, - { "se98", 0 }, - { "stts424", 0 }, - { "stts2002", 0 }, - { "stts3000", 0 }, - { "tse2002", 0 }, - { "ts3000", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, jc42_id); -- cgit v1.2.3 From 312869ec935ab3bb67b7ba641a7d11230555aff5 Mon Sep 17 00:00:00 2001 From: Hans de Goede <hdegoede@redhat.com> Date: Sun, 18 Mar 2012 13:05:08 +0100 Subject: hwmon: (sch56xx) Add support for the integrated watchdog (v2) Add support for the watchdog integrated into the SMSC SCH5627 and SCH5636 superio-s. Since the watchdog is part of the hwmon logical device and thus shares ioports with it, the watchdog driver is integrated into the existing hwmon drivers for these. Note that this version of the watchdog support for sch56xx superio-s implements the watchdog chardev interface itself, rather then relying on the recently added watchdog core / watchdog_dev. This is done because currently some needed functionality is missing from watchdog_dev, as soon as this functionality is added (which is being discussed on the linux-watchdog mailinglist), I'll convert this driver over to using watchdog_dev. Signed-off-by: Hans de Goede <hdegoede@redhat.com> [guenter.roeck@ericsson.com: Added missing linux/slab.h include] Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com> --- Documentation/hwmon/sch5627 | 5 + Documentation/hwmon/sch5636 | 3 + drivers/hwmon/Kconfig | 6 +- drivers/hwmon/sch5627.c | 11 +- drivers/hwmon/sch5636.c | 11 +- drivers/hwmon/sch56xx-common.c | 519 ++++++++++++++++++++++++++++++++++++++++- drivers/hwmon/sch56xx-common.h | 10 +- 7 files changed, 558 insertions(+), 7 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/sch5627 b/Documentation/hwmon/sch5627 index 446a054e4912..0551d266c51c 100644 --- a/Documentation/hwmon/sch5627 +++ b/Documentation/hwmon/sch5627 @@ -16,6 +16,11 @@ Description SMSC SCH5627 Super I/O chips include complete hardware monitoring capabilities. They can monitor up to 5 voltages, 4 fans and 8 temperatures. +The SMSC SCH5627 hardware monitoring part also contains an integrated +watchdog. In order for this watchdog to function some motherboard specific +initialization most be done by the BIOS, so if the watchdog is not enabled +by the BIOS the sch5627 driver will not register a watchdog device. + The hardware monitoring part of the SMSC SCH5627 is accessed by talking through an embedded microcontroller. An application note describing the protocol for communicating with the microcontroller is available upon diff --git a/Documentation/hwmon/sch5636 b/Documentation/hwmon/sch5636 index f83bd1c260f0..7b0a01da0717 100644 --- a/Documentation/hwmon/sch5636 +++ b/Documentation/hwmon/sch5636 @@ -26,6 +26,9 @@ temperatures. Note that the driver detects how many fan headers / temperature sensors are actually implemented on the motherboard, so you will likely see fewer temperature and fan inputs. +The Fujitsu Theseus hwmon solution also contains an integrated watchdog. +This watchdog is fully supported by the sch5636 driver. + An application note describing the Theseus' registers, as well as an application note describing the protocol for communicating with the microcontroller is available upon request. Please mail me if you want a copy. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1cd0e201819c..811e6c47e7e6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1028,7 +1028,8 @@ config SENSORS_SCH5627 select SENSORS_SCH56XX_COMMON help If you say yes here you get support for the hardware monitoring - features of the SMSC SCH5627 Super-I/O chip. + features of the SMSC SCH5627 Super-I/O chip including support for + the integrated watchdog. This driver can also be built as a module. If so, the module will be called sch5627. @@ -1044,7 +1045,8 @@ config SENSORS_SCH5636 Currently this driver only supports the Fujitsu Theseus SCH5636 based hwmon solution. Say yes here if you want support for the Fujitsu - Theseus' hardware monitoring features. + Theseus' hardware monitoring features including support for the + integrated watchdog. This driver can also be built as a module. If so, the module will be called sch5636. diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c index 79b6dabe3161..8ec6dfbccb64 100644 --- a/drivers/hwmon/sch5627.c +++ b/drivers/hwmon/sch5627.c @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> * + * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> * * * * 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 * @@ -79,6 +79,7 @@ static const char * const SCH5627_IN_LABELS[SCH5627_NO_IN] = { struct sch5627_data { unsigned short addr; struct device *hwmon_dev; + struct sch56xx_watchdog_data *watchdog; u8 control; u8 temp_max[SCH5627_NO_TEMPS]; u8 temp_crit[SCH5627_NO_TEMPS]; @@ -453,6 +454,9 @@ static int sch5627_remove(struct platform_device *pdev) { struct sch5627_data *data = platform_get_drvdata(pdev); + if (data->watchdog) + sch56xx_watchdog_unregister(data->watchdog); + if (data->hwmon_dev) hwmon_device_unregister(data->hwmon_dev); @@ -574,6 +578,11 @@ static int __devinit sch5627_probe(struct platform_device *pdev) goto error; } + /* Note failing to register the watchdog is not a fatal error */ + data->watchdog = sch56xx_watchdog_register(data->addr, + (build_code << 24) | (build_id << 8) | hwmon_rev, + &data->update_lock, 1); + return 0; error: diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c index 9d5236fb09b4..906d4ed32d81 100644 --- a/drivers/hwmon/sch5636.c +++ b/drivers/hwmon/sch5636.c @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com> * + * Copyright (C) 2011-2012 Hans de Goede <hdegoede@redhat.com> * * * * 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 * @@ -67,6 +67,7 @@ static const u16 SCH5636_REG_FAN_VAL[SCH5636_NO_FANS] = { struct sch5636_data { unsigned short addr; struct device *hwmon_dev; + struct sch56xx_watchdog_data *watchdog; struct mutex update_lock; char valid; /* !=0 if following fields are valid */ @@ -384,6 +385,9 @@ static int sch5636_remove(struct platform_device *pdev) struct sch5636_data *data = platform_get_drvdata(pdev); int i; + if (data->watchdog) + sch56xx_watchdog_unregister(data->watchdog); + if (data->hwmon_dev) hwmon_device_unregister(data->hwmon_dev); @@ -505,6 +509,11 @@ static int __devinit sch5636_probe(struct platform_device *pdev) goto error; } + /* Note failing to register the watchdog is not a fatal error */ + data->watchdog = sch56xx_watchdog_register(data->addr, + (revision[0] << 8) | revision[1], + &data->update_lock, 0); + return 0; error: diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index fac32ee0b10e..ce52fc57d41d 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> * + * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> * * * * 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 * @@ -26,8 +26,20 @@ #include <linux/io.h> #include <linux/acpi.h> #include <linux/delay.h> +#include <linux/fs.h> +#include <linux/watchdog.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/kref.h> +#include <linux/slab.h> #include "sch56xx-common.h" +/* Insmod parameters */ +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + #define SIO_SCH56XX_LD_EM 0x0C /* Embedded uController Logical Dev */ #define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */ #define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ @@ -40,13 +52,45 @@ #define SIO_SCH5627_ID 0xC6 /* Chipset ID */ #define SIO_SCH5636_ID 0xC7 /* Chipset ID */ -#define REGION_LENGTH 9 +#define REGION_LENGTH 10 #define SCH56XX_CMD_READ 0x02 #define SCH56XX_CMD_WRITE 0x03 +/* Watchdog registers */ +#define SCH56XX_REG_WDOG_PRESET 0x58B +#define SCH56XX_REG_WDOG_CONTROL 0x58C +#define SCH56XX_WDOG_TIME_BASE_SEC 0x01 +#define SCH56XX_REG_WDOG_OUTPUT_ENABLE 0x58E +#define SCH56XX_WDOG_OUTPUT_ENABLE 0x02 + +struct sch56xx_watchdog_data { + u16 addr; + u32 revision; + struct mutex *io_lock; + struct mutex watchdog_lock; + struct list_head list; /* member of the watchdog_data_list */ + struct kref kref; + struct miscdevice watchdog_miscdev; + unsigned long watchdog_is_open; + char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ + char watchdog_expect_close; + u8 watchdog_preset; + u8 watchdog_control; + u8 watchdog_output_enable; +}; + static struct platform_device *sch56xx_pdev; +/* + * Somewhat ugly :( global data pointer list with all sch56xx devices, so that + * we can find our device data as when using misc_register there is no other + * method to get to ones device data from the open fop. + */ +static LIST_HEAD(watchdog_data_list); +/* Note this lock not only protect list access, but also data.kref access */ +static DEFINE_MUTEX(watchdog_data_mutex); + /* Super I/O functions */ static inline int superio_inb(int base, int reg) { @@ -224,6 +268,477 @@ int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, } EXPORT_SYMBOL(sch56xx_read_virtual_reg12); +/* + * Watchdog routines + */ + +/* + * Release our data struct when the platform device has been released *and* + * all references to our watchdog device are released. + */ +static void sch56xx_watchdog_release_resources(struct kref *r) +{ + struct sch56xx_watchdog_data *data = + container_of(r, struct sch56xx_watchdog_data, kref); + kfree(data); +} + +static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, + int timeout) +{ + int ret, resolution; + u8 control; + + /* 1 second or 60 second resolution? */ + if (timeout <= 255) + resolution = 1; + else + resolution = 60; + + if (timeout < resolution || timeout > (resolution * 255)) + return -EINVAL; + + mutex_lock(&data->watchdog_lock); + if (!data->addr) { + ret = -ENODEV; + goto leave; + } + + if (resolution == 1) + control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC; + else + control = data->watchdog_control & ~SCH56XX_WDOG_TIME_BASE_SEC; + + if (data->watchdog_control != control) { + mutex_lock(data->io_lock); + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_CONTROL, + control); + mutex_unlock(data->io_lock); + if (ret) + goto leave; + + data->watchdog_control = control; + } + + /* + * Remember new timeout value, but do not write as that (re)starts + * the watchdog countdown. + */ + data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); + + ret = data->watchdog_preset * resolution; +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_get_timeout(struct sch56xx_watchdog_data *data) +{ + int timeout; + + mutex_lock(&data->watchdog_lock); + if (data->watchdog_control & SCH56XX_WDOG_TIME_BASE_SEC) + timeout = data->watchdog_preset; + else + timeout = data->watchdog_preset * 60; + mutex_unlock(&data->watchdog_lock); + + return timeout; +} + +static int watchdog_start(struct sch56xx_watchdog_data *data) +{ + int ret; + u8 val; + + mutex_lock(&data->watchdog_lock); + if (!data->addr) { + ret = -ENODEV; + goto leave_unlock_watchdog; + } + + /* + * The sch56xx's watchdog cannot really be started / stopped + * it is always running, but we can avoid the timer expiring + * from causing a system reset by clearing the output enable bit. + * + * The sch56xx's watchdog will set the watchdog event bit, bit 0 + * of the second interrupt source register (at base-address + 9), + * when the timer expires. + * + * This will only cause a system reset if the 0-1 flank happens when + * output enable is true. Setting output enable after the flank will + * not cause a reset, nor will the timer expiring a second time. + * This means we must clear the watchdog event bit in case it is set. + * + * The timer may still be running (after a recent watchdog_stop) and + * mere milliseconds away from expiring, so the timer must be reset + * first! + */ + + mutex_lock(data->io_lock); + + /* 1. Reset the watchdog countdown counter */ + ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, + data->watchdog_preset); + if (ret) + goto leave; + + /* 2. Enable output (if not already enabled) */ + if (!(data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) { + val = data->watchdog_output_enable | + SCH56XX_WDOG_OUTPUT_ENABLE; + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_OUTPUT_ENABLE, + val); + if (ret) + goto leave; + + data->watchdog_output_enable = val; + } + + /* 3. Clear the watchdog event bit if set */ + val = inb(data->addr + 9); + if (val & 0x01) + outb(0x01, data->addr + 9); + +leave: + mutex_unlock(data->io_lock); +leave_unlock_watchdog: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_trigger(struct sch56xx_watchdog_data *data) +{ + int ret; + + mutex_lock(&data->watchdog_lock); + if (!data->addr) { + ret = -ENODEV; + goto leave; + } + + /* Reset the watchdog countdown counter */ + mutex_lock(data->io_lock); + ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, + data->watchdog_preset); + mutex_unlock(data->io_lock); +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_stop_unlocked(struct sch56xx_watchdog_data *data) +{ + int ret = 0; + u8 val; + + if (!data->addr) + return -ENODEV; + + if (data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) { + val = data->watchdog_output_enable & + ~SCH56XX_WDOG_OUTPUT_ENABLE; + mutex_lock(data->io_lock); + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_OUTPUT_ENABLE, + val); + mutex_unlock(data->io_lock); + if (ret) + return ret; + + data->watchdog_output_enable = val; + } + + return ret; +} + +static int watchdog_stop(struct sch56xx_watchdog_data *data) +{ + int ret; + + mutex_lock(&data->watchdog_lock); + ret = watchdog_stop_unlocked(data); + mutex_unlock(&data->watchdog_lock); + + return ret; +} + +static int watchdog_release(struct inode *inode, struct file *filp) +{ + struct sch56xx_watchdog_data *data = filp->private_data; + + if (data->watchdog_expect_close) { + watchdog_stop(data); + data->watchdog_expect_close = 0; + } else { + watchdog_trigger(data); + pr_crit("unexpected close, not stopping watchdog!\n"); + } + + clear_bit(0, &data->watchdog_is_open); + + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, sch56xx_watchdog_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static int watchdog_open(struct inode *inode, struct file *filp) +{ + struct sch56xx_watchdog_data *pos, *data = NULL; + int ret, watchdog_is_open; + + /* + * We get called from drivers/char/misc.c with misc_mtx hold, and we + * call misc_register() from sch56xx_watchdog_probe() with + * watchdog_data_mutex hold, as misc_register() takes the misc_mtx + * lock, this is a possible deadlock, so we use mutex_trylock here. + */ + if (!mutex_trylock(&watchdog_data_mutex)) + return -ERESTARTSYS; + list_for_each_entry(pos, &watchdog_data_list, list) { + if (pos->watchdog_miscdev.minor == iminor(inode)) { + data = pos; + break; + } + } + /* Note we can never not have found data, so we don't check for this */ + watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); + if (!watchdog_is_open) + kref_get(&data->kref); + mutex_unlock(&watchdog_data_mutex); + + if (watchdog_is_open) + return -EBUSY; + + filp->private_data = data; + + /* Start the watchdog */ + ret = watchdog_start(data); + if (ret) { + watchdog_release(inode, filp); + return ret; + } + + return nonseekable_open(inode, filp); +} + +static ssize_t watchdog_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + int ret; + struct sch56xx_watchdog_data *data = filp->private_data; + + if (count) { + if (!nowayout) { + size_t i; + + /* Clear it in case it was set with a previous write */ + data->watchdog_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + data->watchdog_expect_close = 1; + } + } + ret = watchdog_trigger(data); + if (ret) + return ret; + } + return count; +} + +static long watchdog_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, + .identity = "sch56xx watchdog" + }; + int i, ret = 0; + struct sch56xx_watchdog_data *data = filp->private_data; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ident.firmware_version = data->revision; + if (!nowayout) + ident.options |= WDIOF_MAGICCLOSE; + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_KEEPALIVE: + ret = watchdog_trigger(data); + break; + + case WDIOC_GETTIMEOUT: + i = watchdog_get_timeout(data); + ret = put_user(i, (int __user *)arg); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + ret = watchdog_set_timeout(data, i); + if (ret >= 0) + ret = put_user(ret, (int __user *)arg); + break; + + case WDIOC_SETOPTIONS: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + + if (i & WDIOS_DISABLECARD) + ret = watchdog_stop(data); + else if (i & WDIOS_ENABLECARD) + ret = watchdog_trigger(data); + else + ret = -EINVAL; + break; + + default: + ret = -ENOTTY; + } + return ret; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_release, + .write = watchdog_write, + .unlocked_ioctl = watchdog_ioctl, +}; + +struct sch56xx_watchdog_data *sch56xx_watchdog_register( + u16 addr, u32 revision, struct mutex *io_lock, int check_enabled) +{ + struct sch56xx_watchdog_data *data; + int i, err, control, output_enable; + const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; + + /* Cache the watchdog registers */ + mutex_lock(io_lock); + control = + sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_CONTROL); + output_enable = + sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_OUTPUT_ENABLE); + mutex_unlock(io_lock); + + if (control < 0) + return NULL; + if (output_enable < 0) + return NULL; + if (check_enabled && !(output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) { + pr_warn("Watchdog not enabled by BIOS, not registering\n"); + return NULL; + } + + data = kzalloc(sizeof(struct sch56xx_watchdog_data), GFP_KERNEL); + if (!data) + return NULL; + + data->addr = addr; + data->revision = revision; + data->io_lock = io_lock; + data->watchdog_control = control; + data->watchdog_output_enable = output_enable; + mutex_init(&data->watchdog_lock); + INIT_LIST_HEAD(&data->list); + kref_init(&data->kref); + + err = watchdog_set_timeout(data, 60); + if (err < 0) + goto error; + + /* + * We take the data_mutex lock early so that watchdog_open() cannot + * run when misc_register() has completed, but we've not yet added + * our data to the watchdog_data_list. + */ + mutex_lock(&watchdog_data_mutex); + for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { + /* Register our watchdog part */ + snprintf(data->watchdog_name, sizeof(data->watchdog_name), + "watchdog%c", (i == 0) ? '\0' : ('0' + i)); + data->watchdog_miscdev.name = data->watchdog_name; + data->watchdog_miscdev.fops = &watchdog_fops; + data->watchdog_miscdev.minor = watchdog_minors[i]; + err = misc_register(&data->watchdog_miscdev); + if (err == -EBUSY) + continue; + if (err) + break; + + list_add(&data->list, &watchdog_data_list); + pr_info("Registered /dev/%s chardev major 10, minor: %d\n", + data->watchdog_name, watchdog_minors[i]); + break; + } + mutex_unlock(&watchdog_data_mutex); + + if (err) { + pr_err("Registering watchdog chardev: %d\n", err); + goto error; + } + if (i == ARRAY_SIZE(watchdog_minors)) { + pr_warn("Couldn't register watchdog (no free minor)\n"); + goto error; + } + + return data; + +error: + kfree(data); + return NULL; +} +EXPORT_SYMBOL(sch56xx_watchdog_register); + +void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data) +{ + mutex_lock(&watchdog_data_mutex); + misc_deregister(&data->watchdog_miscdev); + list_del(&data->list); + mutex_unlock(&watchdog_data_mutex); + + mutex_lock(&data->watchdog_lock); + if (data->watchdog_is_open) { + pr_warn("platform device unregistered with watchdog " + "open! Stopping watchdog.\n"); + watchdog_stop_unlocked(data); + } + /* Tell the wdog start/stop/trigger functions our dev is gone */ + data->addr = 0; + data->io_lock = NULL; + mutex_unlock(&data->watchdog_lock); + + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, sch56xx_watchdog_release_resources); + mutex_unlock(&watchdog_data_mutex); +} +EXPORT_SYMBOL(sch56xx_watchdog_unregister); + +/* + * platform dev find, add and remove functions + */ + static int __init sch56xx_find(int sioaddr, unsigned short *address, const char **name) { diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h index d5eaf3b9ebf5..7475086eb978 100644 --- a/drivers/hwmon/sch56xx-common.h +++ b/drivers/hwmon/sch56xx-common.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> * + * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> * * * * 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 * @@ -17,8 +17,16 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +#include <linux/mutex.h> + +struct sch56xx_watchdog_data; + int sch56xx_read_virtual_reg(u16 addr, u16 reg); int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val); int sch56xx_read_virtual_reg16(u16 addr, u16 reg); int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, int high_nibble); + +struct sch56xx_watchdog_data *sch56xx_watchdog_register( + u16 addr, u32 revision, struct mutex *io_lock, int check_enabled); +void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data); -- cgit v1.2.3