From 50bf46509f24c914562b4d818a155d8dc8f45e10 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 20 Apr 2014 08:07:42 -0700 Subject: hwmon: (lm77) Do not preserve hysteresis when updating critical temp limit Updating the hysteresis value when updating the critical temperature limit was following the rule of 'least surprise'. However, it had the undesirable side effect of changing the hysteresis for all other attributes, which defeats the purpose of least surprise. In addition, it could result in invalid hysteresis values if the resulting hysteresis was too large. In such cases the resulting hysteresis ended up changed anyway, which again defeats the purpose. So drop that code and document the new behavior. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm77 | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/lm77 b/Documentation/hwmon/lm77 index 57c3a46d6370..bfc915fe3639 100644 --- a/Documentation/hwmon/lm77 +++ b/Documentation/hwmon/lm77 @@ -18,5 +18,21 @@ sensor incorporates a band-gap type temperature sensor, 10-bit ADC, and a digital comparator with user-programmable upper and lower limit values. -Limits can be set through the Overtemperature Shutdown register and -Hysteresis register. +The LM77 implements 3 limits: low (temp1_min), high (temp1_max) and +critical (temp1_crit.) It also implements an hysteresis mechanism which +applies to all 3 limits. The relative difference is stored in a single +register on the chip, which means that the relative difference between +the limit and its hysteresis is always the same for all 3 limits. + +This implementation detail implies the following: +* When setting a limit, its hysteresis will automatically follow, the + difference staying unchanged. For example, if the old critical limit + was 80 degrees C, and the hysteresis was 75 degrees C, and you change + the critical limit to 90 degrees C, then the hysteresis will + automatically change to 85 degrees C. +* All 3 hysteresis can't be set independently. We decided to make + temp1_crit_hyst writable, while temp1_min_hyst and temp1_max_hyst are + read-only. Setting temp1_crit_hyst writes the difference between + temp1_crit_hyst and temp1_crit into the chip, and the same relative + hysteresis applies automatically to the low and high limits. +* The limits should be set before the hysteresis. -- cgit v1.2.3-58-ga151 From a9622663e0406932612ffd44fde586e59df9de43 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 10 May 2014 09:56:15 -0700 Subject: hwmon: Document hwmon kernel API The hwmon subsystem has been around for a while. Time to document its kernel API. Acked-by: Randy Dunlap Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.txt | 107 +++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 Documentation/hwmon/hwmon-kernel-api.txt (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/hwmon-kernel-api.txt b/Documentation/hwmon/hwmon-kernel-api.txt new file mode 100644 index 000000000000..2ecdbfc85ecf --- /dev/null +++ b/Documentation/hwmon/hwmon-kernel-api.txt @@ -0,0 +1,107 @@ +The Linux Hardware Monitoring kernel API. +========================================= + +Guenter Roeck + +Introduction +------------ + +This document describes the API that can be used by hardware monitoring +drivers that want to use the hardware monitoring framework. + +This document does not describe what a hardware monitoring (hwmon) Driver or +Device is. It also does not describe the API which can be used by user space +to communicate with a hardware monitoring device. If you want to know this +then please read the following file: Documentation/hwmon/sysfs-interface. + +For additional guidelines on how to write and improve hwmon drivers, please +also read Documentation/hwmon/submitting-patches. + +The API +------- +Each hardware monitoring driver must #include and, in most +cases, . linux/hwmon.h declares the following +register/unregister functions: + +struct device *hwmon_device_register(struct device *dev); +struct device * +hwmon_device_register_with_groups(struct device *dev, const char *name, + void *drvdata, + const struct attribute_group **groups); + +struct device * +devm_hwmon_device_register_with_groups(struct device *dev, + const char *name, void *drvdata, + const struct attribute_group **groups); + +void hwmon_device_unregister(struct device *dev); +void devm_hwmon_device_unregister(struct device *dev); + +hwmon_device_register registers a hardware monitoring device. The parameter +of this function is a pointer to the parent device. +This function returns a pointer to the newly created hardware monitoring device +or PTR_ERR for failure. If this registration function is used, hardware +monitoring sysfs attributes are expected to have been created and attached to +the parent device prior to calling hwmon_device_register. A name attribute must +have been created by the caller. + +hwmon_device_register_with_groups is similar to hwmon_device_register. However, +it has additional parameters. The name parameter is a pointer to the hwmon +device name. The registration function wil create a name sysfs attribute +pointing to this name. The drvdata parameter is the pointer to the local +driver data. hwmon_device_register_with_groups will attach this pointer +to the newly allocated hwmon device. The pointer can be retrieved by the driver +using dev_get_drvdata() on the hwmon device pointer. The groups parameter is +a pointer to a list of sysfs attribute groups. The list must be NULL terminated. +hwmon_device_register_with_groups creates the hwmon device with name attribute +as well as all sysfs attributes attached to the hwmon device. + +devm_hwmon_device_register_with_groups is similar to +hwmon_device_register_with_groups. However, it is device managed, meaning the +hwmon device does not have to be removed explicitly by the removal function. + +hwmon_device_unregister deregisters a registered hardware monitoring device. +The parameter of this function is the pointer to the registered hardware +monitoring device structure. This function must be called from the driver +remove function if the hardware monitoring device was registered with +hwmon_device_register or with hwmon_device_register_with_groups. + +devm_hwmon_device_unregister does not normally have to be called. It is only +needed for error handling, and only needed if the driver probe fails after +the call to devm_hwmon_device_register_with_groups. + +The header file linux/hwmon-sysfs.h provides a number of useful macros to +declare and use hardware monitoring sysfs attributes. + +In many cases, you can use the exsting define DEVICE_ATTR to declare such +attributes. This is feasible if an attribute has no additional context. However, +in many cases there will be additional information such as a sensor index which +will need to be passed to the sysfs attribute handling function. + +SENSOR_DEVICE_ATTR and SENSOR_DEVICE_ATTR_2 can be used to define attributes +which need such additional context information. SENSOR_DEVICE_ATTR requires +one additional argument, SENSOR_DEVICE_ATTR_2 requires two. + +SENSOR_DEVICE_ATTR defines a struct sensor_device_attribute variable. +This structure has the following fields. + +struct sensor_device_attribute { + struct device_attribute dev_attr; + int index; +}; + +You can use to_sensor_dev_attr to get the pointer to this structure from the +attribute read or write function. Its parameter is the device to which the +attribute is attached. + +SENSOR_DEVICE_ATTR_2 defines a struct sensor_device_attribute_2 variable, +which is defined as follows. + +struct sensor_device_attribute_2 { + struct device_attribute dev_attr; + u8 index; + u8 nr; +}; + +Use to_sensor_dev_attr_2 to get the pointer to this structure. Its parameter +is the device to which the attribute is attached. -- cgit v1.2.3-58-ga151 From 57d60b1b7e0d0d32602587fd032d56d7ed9e1556 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 12 May 2014 10:11:32 -0700 Subject: hwmon: (emc1403) Add driver documentation With the driver now supporting several chip variants, it is about time to document what is supported. Signed-off-by: Guenter Roeck --- Documentation/hwmon/emc1403 | 56 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Documentation/hwmon/emc1403 (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/emc1403 b/Documentation/hwmon/emc1403 new file mode 100644 index 000000000000..b109e35e2385 --- /dev/null +++ b/Documentation/hwmon/emc1403 @@ -0,0 +1,56 @@ +Kernel driver emc1403 +===================== + +Supported chips: + * SMSC / Microchip EMC1402, EMC1412 + Addresses scanned: I2C 0x18, 0x1c, 0x29, 0x4c, 0x4d, 0x5c + Prefix: 'emc1402' + Datasheets: + http://ww1.microchip.com/downloads/en/DeviceDoc/1412.pdf + http://ww1.microchip.com/downloads/en/DeviceDoc/1402.pdf + * SMSC / Microchip EMC1403, EMC1404, EMC1413, EMC1414 + Addresses scanned: I2C 0x18, 0x29, 0x4c, 0x4d + Prefix: 'emc1403', 'emc1404' + Datasheets: + http://ww1.microchip.com/downloads/en/DeviceDoc/1403_1404.pdf + http://ww1.microchip.com/downloads/en/DeviceDoc/1413_1414.pdf + * SMSC / Microchip EMC1422 + Addresses scanned: I2C 0x4c + Prefix: 'emc1422' + Datasheet: + http://ww1.microchip.com/downloads/en/DeviceDoc/1422.pdf + * SMSC / Microchip EMC1423, EMC1424 + Addresses scanned: I2C 0x4c + Prefix: 'emc1423', 'emc1424' + Datasheet: + http://ww1.microchip.com/downloads/en/DeviceDoc/1423_1424.pdf + +Author: + Kalhan Trisal Date: Mon, 12 May 2014 11:10:56 -0700 Subject: hwmon: (emc1403) Make all hyst attributes except for temp1_crit_hyst read-only All chips in this chip series only support a single hysteresis value. Having multiple writable hysteresis attributes is therefore confusing, since a single write affects all hysteresis temperatures. Make all but one (temp1_crit_hyst) read-only. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- Documentation/hwmon/emc1403 | 7 +++++-- drivers/hwmon/emc1403.c | 9 +++------ 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/emc1403 b/Documentation/hwmon/emc1403 index b109e35e2385..a869b0ef6a9d 100644 --- a/Documentation/hwmon/emc1403 +++ b/Documentation/hwmon/emc1403 @@ -51,6 +51,9 @@ This implementation detail implies the following: was 80 degrees C, and the hysteresis was 75 degrees C, and you change the critical limit to 90 degrees C, then the hysteresis will automatically change to 85 degrees C. -* While hysteresis limits can be set for all critical limits, setting a single - hysteresis value affects the hysteresis values for all limits on all sensors. +* The hysteresis values can't be set independently. We decided to make + only temp1_crit_hyst writable, while all other hysteresis attributes + are read-only. Setting temp1_crit_hyst writes the difference between + temp1_crit_hyst and temp1_crit into the chip, and the same relative + hysteresis applies automatically to all other limits. * The limits should be set before the hysteresis. diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index f76e5503b89a..46220b131153 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -195,8 +195,7 @@ static SENSOR_DEVICE_ATTR_2(temp2_max_alarm, S_IRUGO, show_bit, NULL, 0x35, 0x02); static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, show_bit, NULL, 0x37, 0x02); -static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO | S_IWUSR, - show_hyst, store_hyst, 0x19); +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_hyst, NULL, 0x19); static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO | S_IWUSR, show_temp, store_temp, 0x16); @@ -212,8 +211,7 @@ static SENSOR_DEVICE_ATTR_2(temp3_max_alarm, S_IRUGO, show_bit, NULL, 0x35, 0x04); static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, show_bit, NULL, 0x37, 0x04); -static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO | S_IWUSR, - show_hyst, store_hyst, 0x1A); +static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO, show_hyst, NULL, 0x1A); static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO | S_IWUSR, show_temp, store_temp, 0x2D); @@ -229,8 +227,7 @@ static SENSOR_DEVICE_ATTR_2(temp4_max_alarm, S_IRUGO, show_bit, NULL, 0x35, 0x08); static SENSOR_DEVICE_ATTR_2(temp4_crit_alarm, S_IRUGO, show_bit, NULL, 0x37, 0x08); -static SENSOR_DEVICE_ATTR(temp4_crit_hyst, S_IRUGO | S_IWUSR, - show_hyst, store_hyst, 0x30); +static SENSOR_DEVICE_ATTR(temp4_crit_hyst, S_IRUGO, show_hyst, NULL, 0x30); static SENSOR_DEVICE_ATTR_2(power_state, S_IRUGO | S_IWUSR, show_bit, store_bit, 0x03, 0x40); -- cgit v1.2.3-58-ga151 From 175c490c9e7f75dbe6addd937c41939c137c6847 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 15 Apr 2014 22:07:30 -0700 Subject: hwmon: (jc42) Add support for STTS2004 and AT30TSE004 Also fix links to datasheets for other supported sensors from ST Microelectronics, and add links to several Atmel datasheets. Signed-off-by: Guenter Roeck --- Documentation/hwmon/jc42 | 16 ++++++++++------ drivers/hwmon/jc42.c | 9 +++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/jc42 b/Documentation/hwmon/jc42 index 868d74d6b773..f3893f7440de 100644 --- a/Documentation/hwmon/jc42 +++ b/Documentation/hwmon/jc42 @@ -5,9 +5,12 @@ Supported chips: * Analog Devices ADT7408 Datasheets: http://www.analog.com/static/imported-files/data_sheets/ADT7408.pdf - * Atmel AT30TS00 + * Atmel AT30TS00, AT30TS002A/B, AT30TSE004A Datasheets: http://www.atmel.com/Images/doc8585.pdf + http://www.atmel.com/Images/doc8711.pdf + http://www.atmel.com/Images/Atmel-8852-SEEPROM-AT30TSE002A-Datasheet.pdf + http://www.atmel.com/Images/Atmel-8868-DTS-AT30TSE004A-Datasheet.pdf * IDT TSE2002B3, TSE2002GB2, TS3000B3, TS3000GB2 Datasheets: http://www.idt.com/sites/default/files/documents/IDT_TSE2002B3C_DST_20100512_120303152056.pdf @@ -34,12 +37,13 @@ Supported chips: Datasheet: http://www.onsemi.com/pub_link/Collateral/CAT34TS02-D.PDF http://www.onsemi.com/pub/Collateral/CAT6095-D.PDF - * ST Microelectronics STTS424, STTS424E02, STTS2002, STTS3000 + * ST Microelectronics STTS424, STTS424E02, STTS2002, STTS2004, 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 + http://www.st.com/web/en/resource/technical/document/datasheet/CD00157556.pdf + http://www.st.com/web/en/resource/technical/document/datasheet/CD00157558.pdf + http://www.st.com/web/en/resource/technical/document/datasheet/CD00266638.pdf + http://www.st.com/web/en/resource/technical/document/datasheet/CD00225278.pdf + http://www.st.com/web/en/resource/technical/document/datasheet/DM00076709.pdf * JEDEC JC 42.4 compliant temperature sensor chips Datasheet: http://www.jedec.org/sites/default/files/docs/4_01_04R19.pdf diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index baa912a3ba75..388f8bcd898e 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -65,6 +65,7 @@ static const unsigned short normal_i2c[] = { /* Manufacturer IDs */ #define ADT_MANID 0x11d4 /* Analog Devices */ #define ATMEL_MANID 0x001f /* Atmel */ +#define ATMEL_MANID2 0x1114 /* Atmel */ #define MAX_MANID 0x004d /* Maxim */ #define IDT_MANID 0x00b3 /* IDT */ #define MCP_MANID 0x0054 /* Microchip */ @@ -82,6 +83,9 @@ static const unsigned short normal_i2c[] = { #define AT30TS00_DEVID 0x8201 #define AT30TS00_DEVID_MASK 0xffff +#define AT30TSE004_DEVID 0x2200 +#define AT30TSE004_DEVID_MASK 0xffff + /* IDT */ #define TS3000B3_DEVID 0x2903 /* Also matches TSE2002B3 */ #define TS3000B3_DEVID_MASK 0xffff @@ -130,6 +134,9 @@ static const unsigned short normal_i2c[] = { #define STTS2002_DEVID 0x0300 #define STTS2002_DEVID_MASK 0xffff +#define STTS2004_DEVID 0x2201 +#define STTS2004_DEVID_MASK 0xffff + #define STTS3000_DEVID 0x0200 #define STTS3000_DEVID_MASK 0xffff @@ -144,6 +151,7 @@ struct jc42_chips { static struct jc42_chips jc42_chips[] = { { ADT_MANID, ADT7408_DEVID, ADT7408_DEVID_MASK }, { ATMEL_MANID, AT30TS00_DEVID, AT30TS00_DEVID_MASK }, + { ATMEL_MANID2, AT30TSE004_DEVID, AT30TSE004_DEVID_MASK }, { IDT_MANID, TS3000B3_DEVID, TS3000B3_DEVID_MASK }, { IDT_MANID, TS3000GB2_DEVID, TS3000GB2_DEVID_MASK }, { MAX_MANID, MAX6604_DEVID, MAX6604_DEVID_MASK }, @@ -158,6 +166,7 @@ static struct jc42_chips jc42_chips[] = { { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, { STM_MANID, STTS2002_DEVID, STTS2002_DEVID_MASK }, + { STM_MANID, STTS2004_DEVID, STTS2004_DEVID_MASK }, { STM_MANID, STTS3000_DEVID, STTS3000_DEVID_MASK }, }; -- cgit v1.2.3-58-ga151 From 41082d66bfd6fafe001c0902bb4622d7aee6f128 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 6 Apr 2014 08:57:20 -0700 Subject: hwmon: Driver for NCT6683D Nuvoton NCT6683D is an eSIO with hardware monitoring capabilities. Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6683 | 57 ++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/nct6683.c | 1455 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1523 insertions(+) create mode 100644 Documentation/hwmon/nct6683 create mode 100644 drivers/hwmon/nct6683.c (limited to 'Documentation/hwmon') diff --git a/Documentation/hwmon/nct6683 b/Documentation/hwmon/nct6683 new file mode 100644 index 000000000000..c1301d4300cd --- /dev/null +++ b/Documentation/hwmon/nct6683 @@ -0,0 +1,57 @@ +Kernel driver nct6683 +===================== + +Supported chips: + * Nuvoton NCT6683D + Prefix: 'nct6683' + Addresses scanned: ISA address retrieved from Super I/O registers + Datasheet: Available from Nuvoton upon request + +Authors: + Guenter Roeck + +Description +----------- + +This driver implements support for the Nuvoton NCT6683D eSIO chip. + +The chips implement up to shared 32 temperature and voltage sensors. +It supports up to 16 fan rotation sensors and up to 8 fan control engines. + +Temperatures are measured in degrees Celsius. Measurement resolution is +0.5 degrees C. + +Voltage sensors (also known as IN sensors) report their values in millivolts. + +Fan rotation speeds are reported in RPM (rotations per minute). + +Usage Note +---------- + +Limit register locations on Intel boards with EC firmware version 1.0 +build date 04/03/13 do not match the register locations in the Nuvoton +datasheet. Nuvoton confirms that Intel uses a special firmware version +with different register addresses. The specification describing the Intel +firmware is held under NDA by Nuvoton and Intel and not available +to the public. + +Some of the register locations can be reverse engineered; others are too +well hidden. Given this, writing any values from the operating system is +considered too risky with this firmware and has been disabled. All limits +must all be written from the BIOS. + +The driver has only been tested with the Intel firmware, and by default +only instantiates on Intel boards. To enable it on non-Intel boards, +set the 'force' module parameter to 1. + +Tested Boards and Firmware Versions +----------------------------------- + +The driver has been reported to work with the following boards and +firmware versions. + +Board Firmware version +--------------------------------------------------------------- +Intel DH87RL NCT6683D EC firmware version 1.0 build 04/03/13 +Intel DH87MC NCT6683D EC firmware version 1.0 build 04/03/13 +Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index bc196f49ec53..b7c0b830d9e4 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1065,6 +1065,16 @@ config SENSORS_NTC_THERMISTOR This driver can also be built as a module. If so, the module will be called ntc-thermistor. +config SENSORS_NCT6683 + tristate "Nuvoton NCT6683D" + depends on !PPC + help + If you say yes here you get support for the hardware monitoring + functionality of the Nuvoton NCT6683D eSIO chip. + + This driver can also be built as a module. If so, the module + will be called nct6683. + config SENSORS_NCT6775 tristate "Nuvoton NCT6775F and compatibles" depends on !PPC diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c48f9873ac73..11798ad7e801 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -114,6 +114,7 @@ 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_NCT6683) += nct6683.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c new file mode 100644 index 000000000000..540c81c52344 --- /dev/null +++ b/drivers/hwmon/nct6683.c @@ -0,0 +1,1455 @@ +/* + * nct6683 - Driver for the hardware monitoring functionality of + * Nuvoton NCT6683D eSIO + * + * Copyright (C) 2013 Guenter Roeck + * + * Derived from nct6775 driver + * Copyright (C) 2012, 2013 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 + * 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. + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp chip ID + * nct6683d 21(1) 16 8 32(1) 0xc730 + * + * Notes: + * (1) Total number of vin and temp inputs is 32. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum kinds { nct6683 }; + +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Set to one to enable detection on non-Intel boards"); + +static const char * const nct6683_device_names[] = { + "nct6683", +}; + +static const char * const nct6683_chip_names[] = { + "NCT6683D", +}; + +#define DRVNAME "nct6683" + +/* + * Super-I/O constants and functions + */ + +#define NCT6683_LD_ACPI 0x0a +#define NCT6683_LD_HWM 0x0b +#define NCT6683_LD_VID 0x0d + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_NCT6681_ID 0xb270 /* for later */ +#define SIO_NCT6683_ID 0xc730 +#define SIO_ID_MASK 0xFFF0 + +static inline void +superio_outb(int ioreg, int reg, int val) +{ + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static inline int +superio_inb(int ioreg, int reg) +{ + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static inline void +superio_select(int ioreg, int ld) +{ + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); +} + +static inline int +superio_enter(int ioreg) +{ + /* + * Try to reserve and for exclusive access. + */ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x87, ioreg); + + return 0; +} + +static inline void +superio_exit(int ioreg) +{ + outb(0xaa, ioreg); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + release_region(ioreg, 2); +} + +/* + * ISA constants + */ + +#define IOREGION_ALIGNMENT (~7) +#define IOREGION_OFFSET 4 /* Use EC port 1 */ +#define IOREGION_LENGTH 4 + +#define EC_PAGE_REG 0 +#define EC_INDEX_REG 1 +#define EC_DATA_REG 2 +#define EC_EVENT_REG 3 + +/* Common and NCT6683 specific data */ + +#define NCT6683_NUM_REG_MON 32 +#define NCT6683_NUM_REG_FAN 16 +#define NCT6683_NUM_REG_PWM 8 + +#define NCT6683_REG_MON(x) (0x100 + (x) * 2) +#define NCT6683_REG_FAN_RPM(x) (0x140 + (x) * 2) +#define NCT6683_REG_PWM(x) (0x160 + (x)) + +#define NCT6683_REG_MON_STS(x) (0x174 + (x)) +#define NCT6683_REG_IDLE(x) (0x178 + (x)) + +#define NCT6683_REG_FAN_STS(x) (0x17c + (x)) +#define NCT6683_REG_FAN_ERRSTS 0x17e +#define NCT6683_REG_FAN_INITSTS 0x17f + +#define NCT6683_HWM_CFG 0x180 + +#define NCT6683_REG_MON_CFG(x) (0x1a0 + (x)) +#define NCT6683_REG_FANIN_CFG(x) (0x1c0 + (x)) +#define NCT6683_REG_FANOUT_CFG(x) (0x1d0 + (x)) + +#define NCT6683_REG_INTEL_TEMP_MAX(x) (0x901 + (x) * 16) +#define NCT6683_REG_INTEL_TEMP_CRIT(x) (0x90d + (x) * 16) + +#define NCT6683_REG_TEMP_HYST(x) (0x330 + (x)) /* 8 bit */ +#define NCT6683_REG_TEMP_MAX(x) (0x350 + (x)) /* 8 bit */ +#define NCT6683_REG_MON_HIGH(x) (0x370 + (x) * 2) /* 8 bit */ +#define NCT6683_REG_MON_LOW(x) (0x371 + (x) * 2) /* 8 bit */ + +#define NCT6683_REG_FAN_MIN(x) (0x3b8 + (x) * 2) /* 16 bit */ + +#define NCT6683_REG_CUSTOMER_ID 0x602 +#define NCT6683_CUSTOMER_ID_INTEL 0x805 + +#define NCT6683_REG_BUILD_YEAR 0x604 +#define NCT6683_REG_BUILD_MONTH 0x605 +#define NCT6683_REG_BUILD_DAY 0x606 +#define NCT6683_REG_SERIAL 0x607 +#define NCT6683_REG_VERSION_HI 0x608 +#define NCT6683_REG_VERSION_LO 0x609 + +#define NCT6683_REG_CR_CASEOPEN 0xe8 +#define NCT6683_CR_CASEOPEN_MASK (1 << 7) + +#define NCT6683_REG_CR_BEEP 0xe0 +#define NCT6683_CR_BEEP_MASK (1 << 6) + +static const char *const nct6683_mon_label[] = { + NULL, /* disabled */ + "Local", + "Diode 0 (curr)", + "Diode 1 (curr)", + "Diode 2 (curr)", + "Diode 0 (volt)", + "Diode 1 (volt)", + "Diode 2 (volt)", + "Thermistor 14", + "Thermistor 15", + "Thermistor 16", + "Thermistor 0", + "Thermistor 1", + "Thermistor 2", + "Thermistor 3", + "Thermistor 4", + "Thermistor 5", /* 0x10 */ + "Thermistor 6", + "Thermistor 7", + "Thermistor 8", + "Thermistor 9", + "Thermistor 10", + "Thermistor 11", + "Thermistor 12", + "Thermistor 13", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + "PECI 0.0", /* 0x20 */ + "PECI 1.0", + "PECI 2.0", + "PECI 3.0", + "PECI 0.1", + "PECI 1.1", + "PECI 2.1", + "PECI 3.1", + "PECI DIMM 0", + "PECI DIMM 1", + "PECI DIMM 2", + "PECI DIMM 3", + NULL, NULL, NULL, NULL, + "PCH CPU", /* 0x30 */ + "PCH CHIP", + "PCH CHIP CPU MAX", + "PCH MCH", + "PCH DIMM 0", + "PCH DIMM 1", + "PCH DIMM 2", + "PCH DIMM 3", + "SMBus 0", + "SMBus 1", + "SMBus 2", + "SMBus 3", + "SMBus 4", + "SMBus 5", + "DIMM 0", + "DIMM 1", + "DIMM 2", /* 0x40 */ + "DIMM 3", + "AMD TSI Addr 90h", + "AMD TSI Addr 92h", + "AMD TSI Addr 94h", + "AMD TSI Addr 96h", + "AMD TSI Addr 98h", + "AMD TSI Addr 9ah", + "AMD TSI Addr 9ch", + "AMD TSI Addr 9dh", + NULL, NULL, NULL, NULL, NULL, NULL, + "Virtual 0", /* 0x50 */ + "Virtual 1", + "Virtual 2", + "Virtual 3", + "Virtual 4", + "Virtual 5", + "Virtual 6", + "Virtual 7", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + "VCC", /* 0x60 voltage sensors */ + "VSB", + "AVSB", + "VTT", + "VBAT", + "VREF", + "VIN0", + "VIN1", + "VIN2", + "VIN3", + "VIN4", + "VIN5", + "VIN6", + "VIN7", + "VIN8", + "VIN9", + "VIN10", + "VIN11", + "VIN12", + "VIN13", + "VIN14", + "VIN15", + "VIN16", +}; + +#define NUM_MON_LABELS ARRAY_SIZE(nct6683_mon_label) +#define MON_VOLTAGE_START 0x60 + +/* ------------------------------------------------------- */ + +struct nct6683_data { + int addr; /* IO base of EC space */ + int sioreg; /* SIO register */ + enum kinds kind; + u16 customer_id; + + struct device *hwmon_dev; + const struct attribute_group *groups[6]; + + int temp_num; /* number of temperature attributes */ + u8 temp_index[NCT6683_NUM_REG_MON]; + u8 temp_src[NCT6683_NUM_REG_MON]; + + u8 in_num; /* number of voltage attributes */ + u8 in_index[NCT6683_NUM_REG_MON]; + u8 in_src[NCT6683_NUM_REG_MON]; + + struct mutex update_lock; /* used to protect sensor updates */ + bool valid; /* true if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Voltage attribute values */ + u8 in[3][NCT6683_NUM_REG_MON]; /* [0]=in, [1]=in_max, [2]=in_min */ + + /* Temperature attribute values */ + s16 temp_in[NCT6683_NUM_REG_MON]; + s8 temp[4][NCT6683_NUM_REG_MON];/* [0]=min, [1]=max, [2]=hyst, + * [3]=crit + */ + + /* Fan attribute values */ + unsigned int rpm[NCT6683_NUM_REG_FAN]; + u16 fan_min[NCT6683_NUM_REG_FAN]; + u8 fanin_cfg[NCT6683_NUM_REG_FAN]; + u8 fanout_cfg[NCT6683_NUM_REG_FAN]; + u16 have_fan; /* some fan inputs can be disabled */ + + u8 have_pwm; + u8 pwm[NCT6683_NUM_REG_PWM]; + +#ifdef CONFIG_PM + /* Remember extra register values over suspend/resume */ + u8 hwm_cfg; +#endif +}; + +struct nct6683_sio_data { + int sioreg; + enum kinds kind; +}; + +struct sensor_device_template { + struct device_attribute dev_attr; + union { + struct { + u8 nr; + u8 index; + } s; + int index; + } u; + bool s2; /* true if both index and nr are used */ +}; + +struct sensor_device_attr_u { + union { + struct sensor_device_attribute a1; + struct sensor_device_attribute_2 a2; + } u; + char name[32]; +}; + +#define __TEMPLATE_ATTR(_template, _mode, _show, _store) { \ + .attr = {.name = _template, .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +#define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \ + { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.index = _index, \ + .s2 = false } + +#define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) \ + { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.s.index = _index, \ + .u.s.nr = _nr, \ + .s2 = true } + +#define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \ +static struct sensor_device_template sensor_dev_template_##_name \ + = SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, \ + _index) + +#define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \ + _nr, _index) \ +static struct sensor_device_template sensor_dev_template_##_name \ + = SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) + +struct sensor_template_group { + struct sensor_device_template **templates; + umode_t (*is_visible)(struct kobject *, struct attribute *, int); + int base; +}; + +static struct attribute_group * +nct6683_create_attr_group(struct device *dev, struct sensor_template_group *tg, + int repeat) +{ + struct sensor_device_attribute_2 *a2; + struct sensor_device_attribute *a; + struct sensor_device_template **t; + struct sensor_device_attr_u *su; + struct attribute_group *group; + struct attribute **attrs; + int i, j, count; + + if (repeat <= 0) + return ERR_PTR(-EINVAL); + + t = tg->templates; + for (count = 0; *t; t++, count++) + ; + + if (count == 0) + return ERR_PTR(-EINVAL); + + group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); + if (group == NULL) + return ERR_PTR(-ENOMEM); + + attrs = devm_kzalloc(dev, sizeof(*attrs) * (repeat * count + 1), + GFP_KERNEL); + if (attrs == NULL) + return ERR_PTR(-ENOMEM); + + su = devm_kzalloc(dev, sizeof(*su) * repeat * count, + GFP_KERNEL); + if (su == NULL) + return ERR_PTR(-ENOMEM); + + group->attrs = attrs; + group->is_visible = tg->is_visible; + + for (i = 0; i < repeat; i++) { + t = tg->templates; + for (j = 0; *t != NULL; j++) { + snprintf(su->name, sizeof(su->name), + (*t)->dev_attr.attr.name, tg->base + i); + if ((*t)->s2) { + a2 = &su->u.a2; + a2->dev_attr.attr.name = su->name; + a2->nr = (*t)->u.s.nr + i; + a2->index = (*t)->u.s.index; + a2->dev_attr.attr.mode = + (*t)->dev_attr.attr.mode; + a2->dev_attr.show = (*t)->dev_attr.show; + a2->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a2->dev_attr.attr; + } else { + a = &su->u.a1; + a->dev_attr.attr.name = su->name; + a->index = (*t)->u.index + i; + a->dev_attr.attr.mode = + (*t)->dev_attr.attr.mode; + a->dev_attr.show = (*t)->dev_attr.show; + a->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a->dev_attr.attr; + } + attrs++; + su++; + t++; + } + } + + return group; +} + +/* LSB is 16 mV, except for the following sources, where it is 32 mV */ +#define MON_SRC_VCC 0x60 +#define MON_SRC_VSB 0x61 +#define MON_SRC_AVSB 0x62 +#define MON_SRC_VBAT 0x64 + +static inline long in_from_reg(u16 reg, u8 src) +{ + int scale = 16; + + if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || + src == MON_SRC_VBAT) + scale <<= 1; + return reg * scale; +} + +static inline u16 in_to_reg(u32 val, u8 src) +{ + int scale = 16; + + if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || + src == MON_SRC_VBAT) + scale <<= 1; + + return clamp_val(DIV_ROUND_CLOSEST(val, scale), 0, 127); +} + +static u16 nct6683_read(struct nct6683_data *data, u16 reg) +{ + int res; + + outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ + outb_p(reg >> 8, data->addr + EC_PAGE_REG); + outb_p(reg & 0xff, data->addr + EC_INDEX_REG); + res = inb_p(data->addr + EC_DATA_REG); + return res; +} + +static u16 nct6683_read16(struct nct6683_data *data, u16 reg) +{ + return (nct6683_read(data, reg) << 8) | nct6683_read(data, reg + 1); +} + +static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value) +{ + outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ + outb_p(reg >> 8, data->addr + EC_PAGE_REG); + outb_p(reg & 0xff, data->addr + EC_INDEX_REG); + outb_p(value & 0xff, data->addr + EC_DATA_REG); +} + +static int get_in_reg(struct nct6683_data *data, int nr, int index) +{ + int ch = data->in_index[index]; + int reg = -EINVAL; + + switch (nr) { + case 0: + reg = NCT6683_REG_MON(ch); + break; + case 1: + if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) + reg = NCT6683_REG_MON_LOW(ch); + break; + case 2: + if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) + reg = NCT6683_REG_MON_HIGH(ch); + break; + default: + break; + } + return reg; +} + +static int get_temp_reg(struct nct6683_data *data, int nr, int index) +{ + int ch = data->temp_index[index]; + int reg = -EINVAL; + + switch (data->customer_id) { + case NCT6683_CUSTOMER_ID_INTEL: + switch (nr) { + default: + case 1: /* max */ + reg = NCT6683_REG_INTEL_TEMP_MAX(ch); + break; + case 3: /* crit */ + reg = NCT6683_REG_INTEL_TEMP_CRIT(ch); + break; + } + break; + default: + switch (nr) { + default: + case 0: /* min */ + reg = NCT6683_REG_MON_LOW(ch); + break; + case 1: /* max */ + reg = NCT6683_REG_TEMP_MAX(ch); + break; + case 2: /* hyst */ + reg = NCT6683_REG_TEMP_HYST(ch); + break; + case 3: /* crit */ + reg = NCT6683_REG_MON_HIGH(ch); + break; + } + break; + } + return reg; +} + +static void nct6683_update_pwm(struct device *dev) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int i; + + for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { + if (!(data->have_pwm & (1 << i))) + continue; + data->pwm[i] = nct6683_read(data, NCT6683_REG_PWM(i)); + } +} + +static struct nct6683_data *nct6683_update_device(struct device *dev) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int i, j; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + /* Measured voltages and limits */ + for (i = 0; i < data->in_num; i++) { + for (j = 0; j < 3; j++) { + int reg = get_in_reg(data, j, i); + + if (reg >= 0) + data->in[j][i] = + nct6683_read(data, reg); + } + } + + /* Measured temperatures and limits */ + for (i = 0; i < data->temp_num; i++) { + u8 ch = data->temp_index[i]; + + data->temp_in[i] = nct6683_read16(data, + NCT6683_REG_MON(ch)); + for (j = 0; j < 4; j++) { + int reg = get_temp_reg(data, j, i); + + if (reg >= 0) + data->temp[j][i] = + nct6683_read(data, reg); + } + } + + /* Measured fan speeds and limits */ + for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { + if (!(data->have_fan & (1 << i))) + continue; + + data->rpm[i] = nct6683_read16(data, + NCT6683_REG_FAN_RPM(i)); + data->fan_min[i] = nct6683_read16(data, + NCT6683_REG_FAN_MIN(i)); + } + + nct6683_update_pwm(dev); + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs callback functions + */ +static ssize_t +show_in_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int nr = sattr->index; + + return sprintf(buf, "%s\n", nct6683_mon_label[data->in_src[nr]]); +} + +static ssize_t +show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int index = sattr->index; + int nr = sattr->nr; + + return sprintf(buf, "%ld\n", + in_from_reg(data->in[index][nr], data->in_index[index])); +} + +static umode_t nct6683_in_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int nr = index % 4; /* attribute */ + + /* + * Voltage limits exist for Intel boards, + * but register location and encoding is unknown + */ + if ((nr == 2 || nr == 3) && + data->customer_id == NCT6683_CUSTOMER_ID_INTEL) + return 0; + + return attr->mode; +} + +SENSOR_TEMPLATE(in_label, "in%d_label", S_IRUGO, show_in_label, NULL, 0); +SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); +SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IRUGO, show_in_reg, NULL, 0, 1); +SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IRUGO, show_in_reg, NULL, 0, 2); + +static struct sensor_device_template *nct6683_attributes_in_template[] = { + &sensor_dev_template_in_label, + &sensor_dev_template_in_input, + &sensor_dev_template_in_min, + &sensor_dev_template_in_max, + NULL +}; + +static struct sensor_template_group nct6683_in_template_group = { + .templates = nct6683_attributes_in_template, + .is_visible = nct6683_in_is_visible, +}; + +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + + return sprintf(buf, "%d\n", data->rpm[sattr->index]); +} + +static ssize_t +show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = nct6683_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%d\n", data->fan_min[nr]); +} + +static ssize_t +show_fan_pulses(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + + return sprintf(buf, "%d\n", + ((data->fanin_cfg[sattr->index] >> 5) & 0x03) + 1); +} + +static umode_t nct6683_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int fan = index / 3; /* fan index */ + int nr = index % 3; /* attribute index */ + + if (!(data->have_fan & (1 << fan))) + return 0; + + /* + * Intel may have minimum fan speed limits, + * but register location and encoding are unknown. + */ + if (nr == 2 && data->customer_id == NCT6683_CUSTOMER_ID_INTEL) + return 0; + + return attr->mode; +} + +SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); +SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IRUGO, show_fan_pulses, NULL, 0); +SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IRUGO, show_fan_min, NULL, 0); + +/* + * nct6683_fan_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6683_attributes_fan_template[] = { + &sensor_dev_template_fan_input, + &sensor_dev_template_fan_pulses, + &sensor_dev_template_fan_min, + NULL +}; + +static struct sensor_template_group nct6683_fan_template_group = { + .templates = nct6683_attributes_fan_template, + .is_visible = nct6683_fan_is_visible, + .base = 1, +}; + +static ssize_t +show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int nr = sattr->index; + + return sprintf(buf, "%s\n", nct6683_mon_label[data->temp_src[nr]]); +} + +static ssize_t +show_temp8(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int index = sattr->index; + int nr = sattr->nr; + + return sprintf(buf, "%d\n", data->temp[index][nr] * 1000); +} + +static ssize_t +show_temp_hyst(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int nr = sattr->index; + int temp = data->temp[1][nr] - data->temp[2][nr]; + + return sprintf(buf, "%d\n", temp * 1000); +} + +static ssize_t +show_temp16(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int index = sattr->index; + + return sprintf(buf, "%d\n", (data->temp_in[index] / 128) * 500); +} + +/* + * Temperature sensor type is determined by temperature source + * and can not be modified. + * 0x02..0x07: Thermal diode + * 0x08..0x18: Thermistor + * 0x20..0x2b: Intel PECI + * 0x42..0x49: AMD TSI + * Others are unspecified (not visible) + */ + +static int get_temp_type(u8 src) +{ + if (src >= 0x02 && src <= 0x07) + return 3; /* thermal diode */ + else if (src >= 0x08 && src <= 0x18) + return 4; /* thermistor */ + else if (src >= 0x20 && src <= 0x2b) + return 6; /* PECI */ + else if (src >= 0x42 && src <= 0x49) + return 5; + + return 0; +} + +static ssize_t +show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = nct6683_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + return sprintf(buf, "%d\n", get_temp_type(data->temp_src[nr])); +} + +static umode_t nct6683_temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int temp = index / 7; /* temp index */ + int nr = index % 7; /* attribute index */ + + /* + * Intel does not have low temperature limits or temperature hysteresis + * registers, or at least register location and encoding is unknown. + */ + if ((nr == 2 || nr == 4) && + data->customer_id == NCT6683_CUSTOMER_ID_INTEL) + return 0; + + if (nr == 6 && get_temp_type(data->temp_src[temp]) == 0) + return 0; /* type */ + + return attr->mode; +} + +SENSOR_TEMPLATE(temp_input, "temp%d_input", S_IRUGO, show_temp16, NULL, 0); +SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); +SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temp8, NULL, 0, 0); +SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temp8, NULL, 0, 1); +SENSOR_TEMPLATE(temp_max_hyst, "temp%d_max_hyst", S_IRUGO, show_temp_hyst, NULL, + 0); +SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO, show_temp8, NULL, 0, 3); +SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO, show_temp_type, NULL, 0); + +/* + * nct6683_temp_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6683_attributes_temp_template[] = { + &sensor_dev_template_temp_input, + &sensor_dev_template_temp_label, + &sensor_dev_template_temp_min, /* 2 */ + &sensor_dev_template_temp_max, /* 3 */ + &sensor_dev_template_temp_max_hyst, /* 4 */ + &sensor_dev_template_temp_crit, /* 5 */ + &sensor_dev_template_temp_type, /* 6 */ + NULL +}; + +static struct sensor_template_group nct6683_temp_template_group = { + .templates = nct6683_attributes_temp_template, + .is_visible = nct6683_temp_is_visible, + .base = 1, +}; + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = nct6683_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int index = sattr->index; + + return sprintf(buf, "%d\n", data->pwm[index]); +} + +SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, NULL, 0); + +static umode_t nct6683_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int pwm = index; /* pwm index */ + + if (!(data->have_pwm & (1 << pwm))) + return 0; + + return attr->mode; +} + +static struct sensor_device_template *nct6683_attributes_pwm_template[] = { + &sensor_dev_template_pwm, + NULL +}; + +static struct sensor_template_group nct6683_pwm_template_group = { + .templates = nct6683_attributes_pwm_template, + .is_visible = nct6683_pwm_is_visible, + .base = 1, +}; + +static ssize_t +show_global_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int ret; + u8 reg; + + mutex_lock(&data->update_lock); + + ret = superio_enter(data->sioreg); + if (ret) + goto error; + superio_select(data->sioreg, NCT6683_LD_HWM); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); + superio_exit(data->sioreg); + + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", !!(reg & NCT6683_CR_BEEP_MASK)); + +error: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t +store_global_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + unsigned long val; + u8 reg; + int ret; + + if (kstrtoul(buf, 10, &val) || (val != 0 && val != 1)) + return -EINVAL; + + mutex_lock(&data->update_lock); + + ret = superio_enter(data->sioreg); + if (ret) { + count = ret; + goto error; + } + + superio_select(data->sioreg, NCT6683_LD_HWM); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); + if (val) + reg |= NCT6683_CR_BEEP_MASK; + else + reg &= ~NCT6683_CR_BEEP_MASK; + superio_outb(data->sioreg, NCT6683_REG_CR_BEEP, reg); + superio_exit(data->sioreg); +error: + mutex_unlock(&data->update_lock); + return count; +} + +/* Case open detection */ + +static ssize_t +show_caseopen(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int ret; + u8 reg; + + mutex_lock(&data->update_lock); + + ret = superio_enter(data->sioreg); + if (ret) + goto error; + superio_select(data->sioreg, NCT6683_LD_ACPI); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); + superio_exit(data->sioreg); + + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", !(reg & NCT6683_CR_CASEOPEN_MASK)); + +error: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t +clear_caseopen(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + unsigned long val; + u8 reg; + int ret; + + if (kstrtoul(buf, 10, &val) || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + + /* + * Use CR registers to clear caseopen status. + * Caseopen is activ low, clear by writing 1 into the register. + */ + + ret = superio_enter(data->sioreg); + if (ret) { + count = ret; + goto error; + } + + superio_select(data->sioreg, NCT6683_LD_ACPI); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); + reg |= NCT6683_CR_CASEOPEN_MASK; + superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); + reg &= ~NCT6683_CR_CASEOPEN_MASK; + superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); + superio_exit(data->sioreg); + + data->valid = false; /* Force cache refresh */ +error: + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen, + clear_caseopen); +static DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_global_beep, + store_global_beep); + +static struct attribute *nct6683_attributes_other[] = { + &dev_attr_intrusion0_alarm.attr, + &dev_attr_beep_enable.attr, + NULL +}; + +static const struct attribute_group nct6683_group_other = { + .attrs = nct6683_attributes_other, +}; + +/* Get the monitoring functions started */ +static inline void nct6683_init_device(struct nct6683_data *data) +{ + u8 tmp; + + /* Start hardware monitoring if needed */ + tmp = nct6683_read(data, NCT6683_HWM_CFG); + if (!(tmp & 0x80)) + nct6683_write(data, NCT6683_HWM_CFG, tmp | 0x80); +} + +/* + * There are a total of 24 fan inputs. Each can be configured as input + * or as output. A maximum of 16 inputs and 8 outputs is configurable. + */ +static void +nct6683_setup_fans(struct nct6683_data *data) +{ + int i; + u8 reg; + + for (i = 0; i < NCT6683_NUM_REG_FAN; i++) { + reg = nct6683_read(data, NCT6683_REG_FANIN_CFG(i)); + if (reg & 0x80) + data->have_fan |= 1 << i; + data->fanin_cfg[i] = reg; + } + for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { + reg = nct6683_read(data, NCT6683_REG_FANOUT_CFG(i)); + if (reg & 0x80) + data->have_pwm |= 1 << i; + data->fanout_cfg[i] = reg; + } +} + +/* + * Translation from monitoring register to temperature and voltage attributes + * ========================================================================== + * + * There are a total of 32 monitoring registers. Each can be assigned to either + * a temperature or voltage monitoring source. + * NCT6683_REG_MON_CFG(x) defines assignment for each monitoring source. + * + * Temperature and voltage attribute mapping is determined by walking through + * the NCT6683_REG_MON_CFG registers. If the assigned source is + * a temperature, temp_index[n] is set to the monitor register index, and + * temp_src[n] is set to the temperature source. If the assigned source is + * a voltage, the respective values are stored in in_index[] and in_src[], + * respectively. + */ + +static void nct6683_setup_sensors(struct nct6683_data *data) +{ + u8 reg; + int i; + + data->temp_num = 0; + data->in_num = 0; + for (i = 0; i < NCT6683_NUM_REG_MON; i++) { + reg = nct6683_read(data, NCT6683_REG_MON_CFG(i)) & 0x7f; + /* Ignore invalid assignments */ + if (reg >= NUM_MON_LABELS) + continue; + /* Skip if disabled or reserved */ + if (nct6683_mon_label[reg] == NULL) + continue; + if (reg < MON_VOLTAGE_START) { + data->temp_index[data->temp_num] = i; + data->temp_src[data->temp_num] = reg; + data->temp_num++; + } else { + data->in_index[data->in_num] = i; + data->in_src[data->in_num] = reg; + data->in_num++; + } + } +} + +static int nct6683_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6683_sio_data *sio_data = dev->platform_data; + struct attribute_group *group; + struct nct6683_data *data; + struct device *hwmon_dev; + struct resource *res; + int groups = 0; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME)) + return -EBUSY; + + data = devm_kzalloc(dev, sizeof(struct nct6683_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = sio_data->kind; + data->sioreg = sio_data->sioreg; + data->addr = res->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + data->customer_id = nct6683_read16(data, NCT6683_REG_CUSTOMER_ID); + + nct6683_init_device(data); + nct6683_setup_fans(data); + nct6683_setup_sensors(data); + + /* Register sysfs hooks */ + + if (data->have_pwm) { + group = nct6683_create_attr_group(dev, + &nct6683_pwm_template_group, + fls(data->have_pwm)); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + + if (data->in_num) { + group = nct6683_create_attr_group(dev, + &nct6683_in_template_group, + data->in_num); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + + if (data->have_fan) { + group = nct6683_create_attr_group(dev, + &nct6683_fan_template_group, + fls(data->have_fan)); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + + if (data->temp_num) { + group = nct6683_create_attr_group(dev, + &nct6683_temp_template_group, + data->temp_num); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + data->groups[groups++] = &nct6683_group_other; + + dev_info(dev, "%s EC firmware version %d.%d build %02x/%02x/%02x\n", + nct6683_chip_names[data->kind], + nct6683_read(data, NCT6683_REG_VERSION_HI), + nct6683_read(data, NCT6683_REG_VERSION_LO), + nct6683_read(data, NCT6683_REG_BUILD_MONTH), + nct6683_read(data, NCT6683_REG_BUILD_DAY), + nct6683_read(data, NCT6683_REG_BUILD_YEAR)); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + nct6683_device_names[data->kind], data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +#ifdef CONFIG_PM +static int nct6683_suspend(struct device *dev) +{ + struct nct6683_data *data = nct6683_update_device(dev); + + mutex_lock(&data->update_lock); + data->hwm_cfg = nct6683_read(data, NCT6683_HWM_CFG); + mutex_unlock(&data->update_lock); + + return 0; +} + +static int nct6683_resume(struct device *dev) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + + mutex_lock(&data->update_lock); + + nct6683_write(data, NCT6683_HWM_CFG, data->hwm_cfg); + + /* Force re-reading all values */ + data->valid = false; + mutex_unlock(&data->update_lock); + + return 0; +} + +static const struct dev_pm_ops nct6683_dev_pm_ops = { + .suspend = nct6683_suspend, + .resume = nct6683_resume, + .freeze = nct6683_suspend, + .restore = nct6683_resume, +}; + +#define NCT6683_DEV_PM_OPS (&nct6683_dev_pm_ops) +#else +#define NCT6683_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct platform_driver nct6683_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + .pm = NCT6683_DEV_PM_OPS, + }, + .probe = nct6683_probe, +}; + +static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data) +{ + const char *board_vendor; + int addr; + u16 val; + int err; + + /* + * Only run on Intel boards unless the 'force' module parameter is set + */ + if (!force) { + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + if (!board_vendor || strcmp(board_vendor, "Intel Corporation")) + return -ENODEV; + } + + err = superio_enter(sioaddr); + if (err) + return err; + + val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) + | superio_inb(sioaddr, SIO_REG_DEVID + 1); + + switch (val & SIO_ID_MASK) { + case SIO_NCT6683_ID: + sio_data->kind = nct6683; + break; + default: + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + goto fail; + } + + /* We have a known chip, find the HWM I/O address */ + superio_select(sioaddr, NCT6683_LD_HWM); + val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) + | superio_inb(sioaddr, SIO_REG_ADDR + 1); + addr = val & IOREGION_ALIGNMENT; + if (addr == 0) { + pr_err("EC base I/O port unconfigured\n"); + goto fail; + } + + /* Activate logical device if needed */ + val = superio_inb(sioaddr, SIO_REG_ENABLE); + if (!(val & 0x01)) { + pr_err("EC is disabled\n"); + goto fail; + } + + superio_exit(sioaddr); + pr_info("Found %s or compatible chip at %#x:%#x\n", + nct6683_chip_names[sio_data->kind], sioaddr, addr); + sio_data->sioreg = sioaddr; + + return addr; + +fail: + superio_exit(sioaddr); + return -ENODEV; +} + +/* + * when Super-I/O functions move to a separate file, the Super-I/O + * bus will manage the lifetime of the device and this module will only keep + * track of the nct6683 driver. But since we use platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev[2]; + +static int __init sensors_nct6683_init(void) +{ + struct nct6683_sio_data sio_data; + int sioaddr[2] = { 0x2e, 0x4e }; + struct resource res; + bool found = false; + int address; + int i, err; + + err = platform_driver_register(&nct6683_driver); + if (err) + return err; + + /* + * initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * nct6683 hardware monitor, and call probe() + */ + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + address = nct6683_find(sioaddr[i], &sio_data); + if (address <= 0) + continue; + + found = true; + + pdev[i] = platform_device_alloc(DRVNAME, address); + if (!pdev[i]) { + err = -ENOMEM; + goto exit_device_put; + } + + err = platform_device_add_data(pdev[i], &sio_data, + sizeof(struct nct6683_sio_data)); + if (err) + goto exit_device_put; + + memset(&res, 0, sizeof(res)); + res.name = DRVNAME; + res.start = address + IOREGION_OFFSET; + res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags = IORESOURCE_IO; + + err = acpi_check_resource_conflict(&res); + if (err) { + platform_device_put(pdev[i]); + pdev[i] = NULL; + continue; + } + + err = platform_device_add_resources(pdev[i], &res, 1); + if (err) + goto exit_device_put; + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev[i]); + if (err) + goto exit_device_put; + } + if (!found) { + err = -ENODEV; + goto exit_unregister; + } + + return 0; + +exit_device_put: + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + if (pdev[i]) + platform_device_put(pdev[i]); + } +exit_unregister: + platform_driver_unregister(&nct6683_driver); + return err; +} + +static void __exit sensors_nct6683_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } + platform_driver_unregister(&nct6683_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("NCT6683D driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_nct6683_init); +module_exit(sensors_nct6683_exit); -- cgit v1.2.3-58-ga151