From 51c2a4871c1b47255ff8d74f0a86b2a0defff319 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 12 Mar 2013 11:38:46 +0100 Subject: hwmon: (adt7410) Add support for the adt7310/adt7320 The adt7310/adt7320 is the SPI version of the adt7410/adt7420. The register map layout is a bit different, i.e. the register addresses differ between the two variants, but the bit layouts of the individual registers are identical. So both chip variants can easily be supported by the same driver. The issue of non matching register address layouts is solved by a simple look-up table which translates the I2C addresses to the SPI addresses. The patch moves the bulk of the adt7410 driver to a common module that will be shared by the adt7410 and adt7310 drivers. This common module implements the driver logic and uses a set of virtual functions to perform IO access. The adt7410 and adt7310 driver modules provide proper implementations of these IO accessor functions for I2C respective SPI. Signed-off-by: Lars-Peter Clausen Reviewed-by: Hartmut Knaack Signed-off-by: Guenter Roeck --- Documentation/hwmon/adt7410 | 47 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/adt7410 b/Documentation/hwmon/adt7410 index 58150c480e5..9817941e5f1 100644 --- a/Documentation/hwmon/adt7410 +++ b/Documentation/hwmon/adt7410 @@ -12,29 +12,42 @@ Supported chips: Addresses scanned: None Datasheet: Publicly available at the Analog Devices website http://www.analog.com/static/imported-files/data_sheets/ADT7420.pdf + * Analog Devices ADT7310 + Prefix: 'adt7310' + Addresses scanned: None + Datasheet: Publicly available at the Analog Devices website + http://www.analog.com/static/imported-files/data_sheets/ADT7310.pdf + * Analog Devices ADT7320 + Prefix: 'adt7320' + Addresses scanned: None + Datasheet: Publicly available at the Analog Devices website + http://www.analog.com/static/imported-files/data_sheets/ADT7320.pdf Author: Hartmut Knaack Description ----------- -The ADT7410 is a temperature sensor with rated temperature range of -55°C to -+150°C. It has a high accuracy of +/-0.5°C and can be operated at a resolution -of 13 bits (0.0625°C) or 16 bits (0.0078°C). The sensor provides an INT pin to -indicate that a minimum or maximum temperature set point has been exceeded, as -well as a critical temperature (CT) pin to indicate that the critical -temperature set point has been exceeded. Both pins can be set up with a common -hysteresis of 0°C - 15°C and a fault queue, ranging from 1 to 4 events. Both -pins can individually set to be active-low or active-high, while the whole -device can either run in comparator mode or interrupt mode. The ADT7410 -supports continous temperature sampling, as well as sampling one temperature -value per second or even justget one sample on demand for power saving. -Besides, it can completely power down its ADC, if power management is -required. - -The ADT7420 is register compatible, the only differences being the package, -a slightly narrower operating temperature range (-40°C to +150°C), and a -better accuracy (0.25°C instead of 0.50°C.) +The ADT7310/ADT7410 is a temperature sensor with rated temperature range of +-55°C to +150°C. It has a high accuracy of +/-0.5°C and can be operated at a +resolution of 13 bits (0.0625°C) or 16 bits (0.0078°C). The sensor provides an +INT pin to indicate that a minimum or maximum temperature set point has been +exceeded, as well as a critical temperature (CT) pin to indicate that the +critical temperature set point has been exceeded. Both pins can be set up with a +common hysteresis of 0°C - 15°C and a fault queue, ranging from 1 to 4 events. +Both pins can individually set to be active-low or active-high, while the whole +device can either run in comparator mode or interrupt mode. The ADT7410 supports +continuous temperature sampling, as well as sampling one temperature value per +second or even just get one sample on demand for power saving. Besides, it can +completely power down its ADC, if power management is required. + +The ADT7320/ADT7420 is register compatible, the only differences being the +package, a slightly narrower operating temperature range (-40°C to +150°C), and +a better accuracy (0.25°C instead of 0.50°C.) + +The difference between the ADT7310/ADT7320 and ADT7410/ADT7420 is the control +interface, the ADT7310 and ADT7320 use SPI while the ADT7410 and ADT7420 use +I2C. Configuration Notes ------------------- -- cgit v1.2.3-70-g09d2 From 9e8269de100dd0be1199778dc175ff22417aebd2 Mon Sep 17 00:00:00 2001 From: Naveen Krishna Chatradhi Date: Wed, 13 Mar 2013 09:38:20 +0530 Subject: hwmon: (ntc_thermistor) Add DT with IIO support to NTC thermistor driver This patch adds DT support to NTC driver to parse the platform data. Also adds the support to work as an iio device client. During the probe ntc driver gets the respective channels of ADC and uses iio_raw_read calls to get the ADC converted value. Signed-off-by: Naveen Krishna Chatradhi [Guenter Roeck: fixed Kconfig dependencies; use ERR_CAST] Tested-by: Doug Anderson Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ntc_thermistor.txt | 29 +++++ drivers/hwmon/Kconfig | 1 + drivers/hwmon/ntc_thermistor.c | 145 ++++++++++++++++++--- include/linux/platform_data/ntc_thermistor.h | 8 +- 4 files changed, 163 insertions(+), 20 deletions(-) create mode 100644 Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt b/Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt new file mode 100644 index 00000000000..c6f66674f19 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt @@ -0,0 +1,29 @@ +NTC Thermistor hwmon sensors +------------------------------- + +Requires node properties: +- "compatible" value : one of + "ntc,ncp15wb473" + "ntc,ncp18wb473" + "ntc,ncp21wb473" + "ntc,ncp03wb473" + "ntc,ncp15wl333" +- "pullup-uv" Pull up voltage in micro volts +- "pullup-ohm" Pull up resistor value in ohms +- "pulldown-ohm" Pull down resistor value in ohms +- "connected-positive" Always ON, If not specified. + Status change is possible. +- "io-channels" Channel node of ADC to be used for + conversion. + +Read more about iio bindings at + Documentation/devicetree/bindings/iio/iio-bindings.txt + +Example: + ncp15wb473@0 { + compatible = "ntc,ncp15wb473"; + pullup-uv = <1800000>; + pullup-ohm = <47000>; + pulldown-ohm = <0>; + io-channels = <&adc 3>; + }; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index aaa14f4a0f7..47d2176957a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -899,6 +899,7 @@ config SENSORS_MCP3021 config SENSORS_NTC_THERMISTOR tristate "NTC thermistor support" + depends on (!OF && !IIO) || (OF && IIO) help This driver supports NTC thermistors sensor reading and its interpretation. The driver can also monitor the temperature and diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index b5f63f9c0ce..d399197655e 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -26,9 +26,16 @@ #include #include #include +#include +#include #include +#include +#include +#include +#include + #include #include @@ -37,6 +44,15 @@ struct ntc_compensation { unsigned int ohm; }; +static const struct platform_device_id ntc_thermistor_id[] = { + { "ncp15wb473", TYPE_NCPXXWB473 }, + { "ncp18wb473", TYPE_NCPXXWB473 }, + { "ncp21wb473", TYPE_NCPXXWB473 }, + { "ncp03wb473", TYPE_NCPXXWB473 }, + { "ncp15wl333", TYPE_NCPXXWL333 }, + { }, +}; + /* * A compensation table should be sorted by the values of .ohm * in descending order. @@ -125,6 +141,92 @@ struct ntc_data { char name[PLATFORM_NAME_SIZE]; }; +#ifdef CONFIG_OF +static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) +{ + struct iio_channel *channel = pdata->chan; + unsigned int result; + int val, ret; + + ret = iio_read_channel_raw(channel, &val); + if (ret < 0) { + pr_err("read channel() error: %d\n", ret); + return ret; + } + + /* unit: mV */ + result = pdata->pullup_uV * val; + result >>= 12; + + return result; +} + +static const struct of_device_id ntc_match[] = { + { .compatible = "ntc,ncp15wb473", + .data = &ntc_thermistor_id[TYPE_NCPXXWB473] }, + { .compatible = "ntc,ncp18wb473", + .data = &ntc_thermistor_id[TYPE_NCPXXWB473] }, + { .compatible = "ntc,ncp21wb473", + .data = &ntc_thermistor_id[TYPE_NCPXXWB473] }, + { .compatible = "ntc,ncp03wb473", + .data = &ntc_thermistor_id[TYPE_NCPXXWB473] }, + { .compatible = "ntc,ncp15wl333", + .data = &ntc_thermistor_id[TYPE_NCPXXWL333] }, + { }, +}; +MODULE_DEVICE_TABLE(of, ntc_match); + +static struct ntc_thermistor_platform_data * +ntc_thermistor_parse_dt(struct platform_device *pdev) +{ + struct iio_channel *chan; + struct device_node *np = pdev->dev.of_node; + struct ntc_thermistor_platform_data *pdata; + + if (!np) + return NULL; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + chan = iio_channel_get(&pdev->dev, NULL); + if (IS_ERR(chan)) + return ERR_CAST(chan); + + if (of_property_read_u32(np, "pullup-uv", &pdata->pullup_uV)) + return ERR_PTR(-ENODEV); + if (of_property_read_u32(np, "pullup-ohm", &pdata->pullup_ohm)) + return ERR_PTR(-ENODEV); + if (of_property_read_u32(np, "pulldown-ohm", &pdata->pulldown_ohm)) + return ERR_PTR(-ENODEV); + + if (of_find_property(np, "connected-positive", NULL)) + pdata->connect = NTC_CONNECTED_POSITIVE; + else /* status change should be possible if not always on. */ + pdata->connect = NTC_CONNECTED_GROUND; + + pdata->chan = chan; + pdata->read_uV = ntc_adc_iio_read; + + return pdata; +} +static void ntc_iio_channel_release(struct ntc_thermistor_platform_data *pdata) +{ + if (pdata->chan) + iio_channel_release(pdata->chan); +} +#else +static struct ntc_thermistor_platform_data * +ntc_thermistor_parse_dt(struct platform_device *pdev) +{ + return NULL; +} + +static void ntc_iio_channel_release(struct ntc_thermistor_platform_data *pdata) +{ } +#endif + static inline u64 div64_u64_safe(u64 dividend, u64 divisor) { if (divisor == 0 && dividend == 0) @@ -259,7 +361,7 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data) return data->pdata->read_ohm(); if (data->pdata->read_uV) { - read_uV = data->pdata->read_uV(); + read_uV = data->pdata->read_uV(data->pdata); if (read_uV < 0) return read_uV; return get_ohm_of_thermistor(data, read_uV); @@ -311,9 +413,18 @@ static const struct attribute_group ntc_attr_group = { static int ntc_thermistor_probe(struct platform_device *pdev) { + const struct of_device_id *of_id = + of_match_device(of_match_ptr(ntc_match), &pdev->dev); + const struct platform_device_id *pdev_id; + struct ntc_thermistor_platform_data *pdata; struct ntc_data *data; - struct ntc_thermistor_platform_data *pdata = pdev->dev.platform_data; - int ret = 0; + int ret; + + pdata = ntc_thermistor_parse_dt(pdev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + else if (pdata == NULL) + pdata = pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "No platform init data supplied.\n"); @@ -349,11 +460,13 @@ static int ntc_thermistor_probe(struct platform_device *pdev) if (!data) return -ENOMEM; + pdev_id = of_id ? of_id->data : platform_get_device_id(pdev); + data->dev = &pdev->dev; data->pdata = pdata; - strlcpy(data->name, pdev->id_entry->name, sizeof(data->name)); + strlcpy(data->name, pdev_id->name, sizeof(data->name)); - switch (pdev->id_entry->driver_data) { + switch (pdev_id->driver_data) { case TYPE_NCPXXWB473: data->comp = ncpXXwb473; data->n_comp = ARRAY_SIZE(ncpXXwb473); @@ -364,8 +477,7 @@ static int ntc_thermistor_probe(struct platform_device *pdev) break; default: dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n", - pdev->id_entry->driver_data, - pdev->id_entry->name); + pdev_id->driver_data, pdev_id->name); return -EINVAL; } @@ -384,39 +496,34 @@ static int ntc_thermistor_probe(struct platform_device *pdev) goto err_after_sysfs; } - dev_info(&pdev->dev, "Thermistor %s:%d (type: %s/%lu) successfully probed.\n", - pdev->name, pdev->id, pdev->id_entry->name, - pdev->id_entry->driver_data); + dev_info(&pdev->dev, "Thermistor type: %s successfully probed.\n", + pdev->name); + return 0; err_after_sysfs: sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); + ntc_iio_channel_release(pdata); return ret; } static int ntc_thermistor_remove(struct platform_device *pdev) { struct ntc_data *data = platform_get_drvdata(pdev); + struct ntc_thermistor_platform_data *pdata = data->pdata; hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); + ntc_iio_channel_release(pdata); platform_set_drvdata(pdev, NULL); return 0; } -static const struct platform_device_id ntc_thermistor_id[] = { - { "ncp15wb473", TYPE_NCPXXWB473 }, - { "ncp18wb473", TYPE_NCPXXWB473 }, - { "ncp21wb473", TYPE_NCPXXWB473 }, - { "ncp03wb473", TYPE_NCPXXWB473 }, - { "ncp15wl333", TYPE_NCPXXWL333 }, - { }, -}; - static struct platform_driver ntc_thermistor_driver = { .driver = { .name = "ntc-thermistor", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ntc_match), }, .probe = ntc_thermistor_probe, .remove = ntc_thermistor_remove, diff --git a/include/linux/platform_data/ntc_thermistor.h b/include/linux/platform_data/ntc_thermistor.h index 88734e871e3..fa95f9cbe7e 100644 --- a/include/linux/platform_data/ntc_thermistor.h +++ b/include/linux/platform_data/ntc_thermistor.h @@ -21,6 +21,8 @@ #ifndef _LINUX_NTC_H #define _LINUX_NTC_H +struct iio_channel; + enum ntc_thermistor_type { TYPE_NCPXXWB473, TYPE_NCPXXWL333, @@ -39,13 +41,17 @@ struct ntc_thermistor_platform_data { * described at Documentation/hwmon/ntc_thermistor * * pullup/down_ohm: 0 for infinite / not-connected + * + * chan: iio_channel pointer to communicate with the ADC which the + * thermistor is using for conversion of the analog values. */ - int (*read_uV)(void); + int (*read_uV)(struct ntc_thermistor_platform_data *); unsigned int pullup_uV; unsigned int pullup_ohm; unsigned int pulldown_ohm; enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; + struct iio_channel *chan; int (*read_ohm)(void); }; -- cgit v1.2.3-70-g09d2 From 9de2e2e84e7d52e4c2a9e1a1e21ab6ac686233c0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 20 May 2012 19:29:48 -0700 Subject: hwmon: Driver for Nuvoton NCT6775F, NCT6776F, and NCT6779D This driver will replace the w83627ehf driver for NCT6775F and NCT6776F, and provides support for NCT6779D. This patch provides support for voltage monitor attributes. Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775 | 81 ++++ drivers/hwmon/Kconfig | 13 + drivers/hwmon/Makefile | 1 + drivers/hwmon/nct6775.c | 1021 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1116 insertions(+) create mode 100644 Documentation/hwmon/nct6775 create mode 100644 drivers/hwmon/nct6775.c (limited to 'Documentation') diff --git a/Documentation/hwmon/nct6775 b/Documentation/hwmon/nct6775 new file mode 100644 index 00000000000..ccfd5cc2100 --- /dev/null +++ b/Documentation/hwmon/nct6775 @@ -0,0 +1,81 @@ +Note +==== + +This driver supersedes the NCT6775F and NCT6776F support in the W83627EHF +driver. + +Kernel driver NCT6775 +===================== + +Supported chips: + * Nuvoton NCT6775F/W83667HG-I + Prefix: 'nct6775' + Addresses scanned: ISA address retrieved from Super I/O registers + Datasheet: Available from Nuvoton upon request + * Nuvoton NCT6776F + Prefix: 'nct6776' + Addresses scanned: ISA address retrieved from Super I/O registers + Datasheet: Available from Nuvoton upon request + * Nuvoton NCT6779D + Prefix: 'nct6779' + 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 NCT6775F, NCT6776F, and NCT6779D +super I/O chips. + +The chips support up to 25 temperature monitoring sources. Up to 6 of those are +direct temperature sensor inputs, the others are special sources such as PECI, +PCH, and SMBUS. Depending on the chip type, 2 to 6 of the temperature sources +can be monitored and compared against minimum, maximum, and critical +temperatures. The driver reports up to 10 of the temperatures to the user. +There are 4 to 5 fan rotation speed sensors, 8 to 15 analog voltage sensors, +one VID, alarms with beep warnings (control unimplemented), and some automatic +fan regulation strategies (plus manual fan control mode). + +The temperature sensor sources on all chips are configurable. The configured +source for each of the temperature sensors is provided in tempX_label. + +Temperatures are measured in degrees Celsius and measurement resolution is +either 1 degC or 0.5 degC, depending on the temperature source and +configuration. An alarm is triggered when the temperature gets higher than +the high limit; it stays on until the temperature falls below the hysteresis +value. Alarms are only supported for temp1 to temp6, depending on the chip type. + +Fan rotation speeds are reported in RPM (rotations per minute). An alarm is +triggered if the rotation speed has dropped below a programmable limit. On +NCT6775F, fan readings can be divided by a programmable divider (1, 2, 4, 8, +16, 32, 64 or 128) to give the readings more range or accuracy; the other chips +do not have a fan speed divider. The driver sets the most suitable fan divisor +itself; specifically, it doubles the divider value each time a fan speed reading +returns an invalid value. Some fans might not be present because they share pins +with other functions. + +Voltage sensors (also known as IN sensors) report their values in millivolts. +An alarm is triggered if the voltage has crossed a programmable minimum +or maximum limit. + +The driver supports automatic fan control mode known as Thermal Cruise. +In this mode, the chip attempts to keep the measured temperature in a +predefined temperature range. If the temperature goes out of range, fan +is driven slower/faster to reach the predefined range again. + +The mode works for fan1-fan5. + +Usage Notes +----------- + +On various ASUS boards with NCT6776F, it appears that CPUTIN is not really +connected to anything and floats, or that it is connected to some non-standard +temperature measurement device. As a result, the temperature reported on CPUTIN +will not reflect a usable value. It often reports unreasonably high +temperatures, and in some cases the reported temperature declines if the actual +temperature increases (similar to the raw PECI temperature value - see PECI +specification for details). CPUTIN should therefore be be ignored on ASUS +boards. The CPU temperature on ASUS boards is reported from PECI 0. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 47d2176957a..a0f1d6a406e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -897,6 +897,19 @@ config SENSORS_MCP3021 This driver can also be built as a module. If so, the module will be called mcp3021. +config SENSORS_NCT6775 + tristate "Nuvoton NCT6775F, NCT6776F, NCT6779D" + depends on !PPC + select HWMON_VID + help + If you say yes here you get support for the hardware monitoring + functionality of the Nuvoton NCT6775F, NCT6776F, and NCT6779D + Super-I/O chips. This driver replaces the w83627ehf driver for + NCT6775F and NCT6776F. + + This driver can also be built as a module. If so, the module + will be called nct6775. + config SENSORS_NTC_THERMISTOR tristate "NTC thermistor support" depends on (!OF && !IIO) || (OF && IIO) diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5d36a57c055..82975724d3a 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -105,6 +105,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_NCT6775) += nct6775.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c new file mode 100644 index 00000000000..f75cd823153 --- /dev/null +++ b/drivers/hwmon/nct6775.c @@ -0,0 +1,1021 @@ +/* + * nct6775 - Driver for the hardware monitoring functionality of + * Nuvoton NCT677x Super-I/O chips + * + * Copyright (C) 2012 Guenter Roeck + * + * Derived from w83627ehf driver + * Copyright (C) 2005-2012 Jean Delvare + * Copyright (C) 2006 Yuan Mu (Winbond), + * Rudolf Marek + * David Hubbard + * Daniel J Blueman + * Copyright (C) 2010 Sheng-Yuan Huang (Nuvoton) (PS00) + * + * Shamelessly ripped from the w83627hf driver + * Copyright (C) 2003 Mark Studebaker + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp chip IDs man ID + * nct6775f 9 4 3 6+3 0xb470 0xc1 0x5ca3 + * nct6776f 9 5 3 6+3 0xc330 0xc1 0x5ca3 + * nct6779d 15 5 5 2+6 0xc560 0xc1 0x5ca3 + * + * #temp lists the number of monitored temperature sources (first value) plus + * the number of directly connectable temperature sensors (second value). + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm75.h" + +enum kinds { nct6775, nct6776, nct6779 }; + +/* used to set data->name = nct6775_device_names[data->sio_kind] */ +static const char * const nct6775_device_names[] = { + "nct6775", + "nct6776", + "nct6779", +}; + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +#define DRVNAME "nct6775" + +/* + * Super-I/O constants and functions + */ + +#define NCT6775_LD_HWM 0x0b +#define NCT6775_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_NCT6775_ID 0xb470 +#define SIO_NCT6776_ID 0xc330 +#define SIO_NCT6779_ID 0xc560 +#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 5 +#define IOREGION_LENGTH 2 +#define ADDR_REG_OFFSET 0 +#define DATA_REG_OFFSET 1 + +#define NCT6775_REG_BANK 0x4E +#define NCT6775_REG_CONFIG 0x40 + +/* + * Not currently used: + * REG_MAN_ID has the value 0x5ca3 for all supported chips. + * REG_CHIP_ID == 0x88/0xa1/0xc1 depending on chip model. + * REG_MAN_ID is at port 0x4f + * REG_CHIP_ID is at port 0x58 + */ + +#define NUM_REG_ALARM 4 /* Max number of alarm registers */ + +/* Common and NCT6775 specific data */ + +/* Voltage min/max registers for nr=7..14 are in bank 5 */ + +static const u16 NCT6775_REG_IN_MAX[] = { + 0x2b, 0x2d, 0x2f, 0x31, 0x33, 0x35, 0x37, 0x554, 0x556, 0x558, 0x55a, + 0x55c, 0x55e, 0x560, 0x562 }; +static const u16 NCT6775_REG_IN_MIN[] = { + 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x555, 0x557, 0x559, 0x55b, + 0x55d, 0x55f, 0x561, 0x563 }; +static const u16 NCT6775_REG_IN[] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x550, 0x551, 0x552 +}; + +#define NCT6775_REG_VBAT 0x5D + +static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; + +/* 0..15 voltages, 16..23 fans, 24..31 temperatures */ + +static const s8 NCT6775_ALARM_BITS[] = { + 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ + 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + -1, /* unused */ + 6, 7, 11, 10, 23, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, -1 }; /* intrusion0, intrusion1 */ + +/* NCT6776 specific data */ + +static const s8 NCT6776_ALARM_BITS[] = { + 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ + 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + -1, /* unused */ + 6, 7, 11, 10, 23, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, 9 }; /* intrusion0, intrusion1 */ + +/* NCT6779 specific data */ + +static const u16 NCT6779_REG_IN[] = { + 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, + 0x488, 0x489, 0x48a, 0x48b, 0x48c, 0x48d, 0x48e }; + +static const u16 NCT6779_REG_ALARM[NUM_REG_ALARM] = { + 0x459, 0x45A, 0x45B, 0x568 }; + +static const s8 NCT6779_ALARM_BITS[] = { + 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ + 17, 24, 25, 26, 27, 28, 29, /* in8..in14 */ + -1, /* unused */ + 6, 7, 11, 10, 23, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, 9 }; /* intrusion0, intrusion1 */ + +/* + * Conversions + */ + +/* + * Some of the voltage inputs have internal scaling, the tables below + * contain 8 (the ADC LSB in mV) * scaling factor * 100 + */ +static const u16 scale_in[15] = { + 800, 800, 1600, 1600, 800, 800, 800, 1600, 1600, 800, 800, 800, 800, + 800, 800 +}; + +static inline long in_from_reg(u8 reg, u8 nr) +{ + return DIV_ROUND_CLOSEST(reg * scale_in[nr], 100); +} + +static inline u8 in_to_reg(u32 val, u8 nr) +{ + return clamp_val(DIV_ROUND_CLOSEST(val * 100, scale_in[nr]), 0, 255); +} + +/* + * Data structures and manipulation thereof + */ + +struct nct6775_data { + int addr; /* IO base of hw monitor block */ + enum kinds kind; + const char *name; + + struct device *hwmon_dev; + struct mutex lock; + + u16 REG_CONFIG; + u16 REG_VBAT; + + const s8 *ALARM_BITS; + + const u16 *REG_VIN; + const u16 *REG_IN_MINMAX[2]; + + const u16 *REG_ALARM; + + struct mutex update_lock; + bool valid; /* true if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values */ + u8 bank; /* current register bank */ + u8 in_num; /* number of in inputs we have */ + u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */ + + u64 alarms; + + u8 vid; + u8 vrm; + + u16 have_in; +}; + +struct nct6775_sio_data { + int sioreg; + enum kinds kind; +}; + +static bool is_word_sized(struct nct6775_data *data, u16 reg) +{ + switch (data->kind) { + case nct6775: + return (((reg & 0xff00) == 0x100 || + (reg & 0xff00) == 0x200) && + ((reg & 0x00ff) == 0x50 || + (reg & 0x00ff) == 0x53 || + (reg & 0x00ff) == 0x55)) || + (reg & 0xfff0) == 0x630 || + reg == 0x640 || reg == 0x642 || + reg == 0x662 || + ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || + reg == 0x73 || reg == 0x75 || reg == 0x77; + case nct6776: + return (((reg & 0xff00) == 0x100 || + (reg & 0xff00) == 0x200) && + ((reg & 0x00ff) == 0x50 || + (reg & 0x00ff) == 0x53 || + (reg & 0x00ff) == 0x55)) || + (reg & 0xfff0) == 0x630 || + reg == 0x402 || + reg == 0x640 || reg == 0x642 || + ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || + reg == 0x73 || reg == 0x75 || reg == 0x77; + case nct6779: + return reg == 0x150 || reg == 0x153 || reg == 0x155 || + ((reg & 0xfff0) == 0x4b0 && (reg & 0x000f) < 0x09) || + reg == 0x402 || + reg == 0x63a || reg == 0x63c || reg == 0x63e || + reg == 0x640 || reg == 0x642 || + reg == 0x73 || reg == 0x75 || reg == 0x77 || reg == 0x79 || + reg == 0x7b; + } + return false; +} + +/* + * On older chips, only registers 0x50-0x5f are banked. + * On more recent chips, all registers are banked. + * Assume that is the case and set the bank number for each access. + * Cache the bank number so it only needs to be set if it changes. + */ +static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg) +{ + u8 bank = reg >> 8; + if (data->bank != bank) { + outb_p(NCT6775_REG_BANK, data->addr + ADDR_REG_OFFSET); + outb_p(bank, data->addr + DATA_REG_OFFSET); + data->bank = bank; + } +} + +static u16 nct6775_read_value(struct nct6775_data *data, u16 reg) +{ + int res, word_sized = is_word_sized(data, reg); + + mutex_lock(&data->lock); + + nct6775_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + res = inb_p(data->addr + DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + res = (res << 8) + inb_p(data->addr + DATA_REG_OFFSET); + } + + mutex_unlock(&data->lock); + return res; +} + +static int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) +{ + int word_sized = is_word_sized(data, reg); + + mutex_lock(&data->lock); + + nct6775_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, data->addr + DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + } + outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); + + mutex_unlock(&data->lock); + return 0; +} + +static struct nct6775_data *nct6775_update_device(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ/2) + || !data->valid) { + /* Measured voltages and limits */ + for (i = 0; i < data->in_num; i++) { + if (!(data->have_in & (1 << i))) + continue; + + data->in[i][0] = nct6775_read_value(data, + data->REG_VIN[i]); + data->in[i][1] = nct6775_read_value(data, + data->REG_IN_MINMAX[0][i]); + data->in[i][2] = nct6775_read_value(data, + data->REG_IN_MINMAX[1][i]); + } + + data->alarms = 0; + for (i = 0; i < NUM_REG_ALARM; i++) { + u8 alarm; + if (!data->REG_ALARM[i]) + continue; + alarm = nct6775_read_value(data, data->REG_ALARM[i]); + data->alarms |= ((u64)alarm) << (i << 3); + } + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs callback functions + */ +static ssize_t +show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + return sprintf(buf, "%ld\n", in_from_reg(data->in[nr][index], nr)); +} + +static ssize_t +store_in_reg(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + mutex_lock(&data->update_lock); + data->in[nr][index] = in_to_reg(val, nr); + nct6775_write_value(data, data->REG_IN_MINMAX[index-1][nr], + data->in[nr][index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_alarm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = data->ALARM_BITS[sattr->index]; + return sprintf(buf, "%u\n", + (unsigned int)((data->alarms >> nr) & 0x01)); +} + +static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in_reg, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in_reg, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in_reg, NULL, 2, 0); +static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in_reg, NULL, 3, 0); +static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in_reg, NULL, 4, 0); +static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_in_reg, NULL, 5, 0); +static SENSOR_DEVICE_ATTR_2(in6_input, S_IRUGO, show_in_reg, NULL, 6, 0); +static SENSOR_DEVICE_ATTR_2(in7_input, S_IRUGO, show_in_reg, NULL, 7, 0); +static SENSOR_DEVICE_ATTR_2(in8_input, S_IRUGO, show_in_reg, NULL, 8, 0); +static SENSOR_DEVICE_ATTR_2(in9_input, S_IRUGO, show_in_reg, NULL, 9, 0); +static SENSOR_DEVICE_ATTR_2(in10_input, S_IRUGO, show_in_reg, NULL, 10, 0); +static SENSOR_DEVICE_ATTR_2(in11_input, S_IRUGO, show_in_reg, NULL, 11, 0); +static SENSOR_DEVICE_ATTR_2(in12_input, S_IRUGO, show_in_reg, NULL, 12, 0); +static SENSOR_DEVICE_ATTR_2(in13_input, S_IRUGO, show_in_reg, NULL, 13, 0); +static SENSOR_DEVICE_ATTR_2(in14_input, S_IRUGO, show_in_reg, NULL, 14, 0); + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(in11_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(in12_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(in13_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(in14_alarm, S_IRUGO, show_alarm, NULL, 14); + +static SENSOR_DEVICE_ATTR_2(in0_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 0, 1); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 1, 1); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 2, 1); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 3, 1); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 4, 1); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 5, 1); +static SENSOR_DEVICE_ATTR_2(in6_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 6, 1); +static SENSOR_DEVICE_ATTR_2(in7_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 7, 1); +static SENSOR_DEVICE_ATTR_2(in8_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 8, 1); +static SENSOR_DEVICE_ATTR_2(in9_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 9, 1); +static SENSOR_DEVICE_ATTR_2(in10_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 10, 1); +static SENSOR_DEVICE_ATTR_2(in11_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 11, 1); +static SENSOR_DEVICE_ATTR_2(in12_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 12, 1); +static SENSOR_DEVICE_ATTR_2(in13_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 13, 1); +static SENSOR_DEVICE_ATTR_2(in14_min, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 14, 1); + +static SENSOR_DEVICE_ATTR_2(in0_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 0, 2); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 1, 2); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 2, 2); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 3, 2); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 4, 2); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 5, 2); +static SENSOR_DEVICE_ATTR_2(in6_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 6, 2); +static SENSOR_DEVICE_ATTR_2(in7_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 7, 2); +static SENSOR_DEVICE_ATTR_2(in8_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 8, 2); +static SENSOR_DEVICE_ATTR_2(in9_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 9, 2); +static SENSOR_DEVICE_ATTR_2(in10_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 10, 2); +static SENSOR_DEVICE_ATTR_2(in11_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 11, 2); +static SENSOR_DEVICE_ATTR_2(in12_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 12, 2); +static SENSOR_DEVICE_ATTR_2(in13_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 13, 2); +static SENSOR_DEVICE_ATTR_2(in14_max, S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 14, 2); + +static struct attribute *nct6775_attributes_in[15][5] = { + { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in9_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in10_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in11_min.dev_attr.attr, + &sensor_dev_attr_in11_max.dev_attr.attr, + &sensor_dev_attr_in11_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_in12_min.dev_attr.attr, + &sensor_dev_attr_in12_max.dev_attr.attr, + &sensor_dev_attr_in12_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in13_input.dev_attr.attr, + &sensor_dev_attr_in13_min.dev_attr.attr, + &sensor_dev_attr_in13_max.dev_attr.attr, + &sensor_dev_attr_in13_alarm.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_in14_input.dev_attr.attr, + &sensor_dev_attr_in14_min.dev_attr.attr, + &sensor_dev_attr_in14_max.dev_attr.attr, + &sensor_dev_attr_in14_alarm.dev_attr.attr, + NULL + }, +}; + +static const struct attribute_group nct6775_group_in[15] = { + { .attrs = nct6775_attributes_in[0] }, + { .attrs = nct6775_attributes_in[1] }, + { .attrs = nct6775_attributes_in[2] }, + { .attrs = nct6775_attributes_in[3] }, + { .attrs = nct6775_attributes_in[4] }, + { .attrs = nct6775_attributes_in[5] }, + { .attrs = nct6775_attributes_in[6] }, + { .attrs = nct6775_attributes_in[7] }, + { .attrs = nct6775_attributes_in[8] }, + { .attrs = nct6775_attributes_in[9] }, + { .attrs = nct6775_attributes_in[10] }, + { .attrs = nct6775_attributes_in[11] }, + { .attrs = nct6775_attributes_in[12] }, + { .attrs = nct6775_attributes_in[13] }, + { .attrs = nct6775_attributes_in[14] }, +}; + +static ssize_t +show_name(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static ssize_t +show_vid(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL); + +/* + * Driver and device management + */ + +static void nct6775_device_remove_files(struct device *dev) +{ + /* + * some entries in the following arrays may not have been used in + * device_create_file(), but device_remove_file() will ignore them + */ + int i; + struct nct6775_data *data = dev_get_drvdata(dev); + + for (i = 0; i < data->in_num; i++) + sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]); + + device_remove_file(dev, &dev_attr_name); + device_remove_file(dev, &dev_attr_cpu0_vid); +} + +/* Get the monitoring functions started */ +static inline void nct6775_init_device(struct nct6775_data *data) +{ + u8 tmp; + + /* Start monitoring if needed */ + if (data->REG_CONFIG) { + tmp = nct6775_read_value(data, data->REG_CONFIG); + if (!(tmp & 0x01)) + nct6775_write_value(data, data->REG_CONFIG, tmp | 0x01); + } + + /* Enable VBAT monitoring if needed */ + tmp = nct6775_read_value(data, data->REG_VBAT); + if (!(tmp & 0x01)) + nct6775_write_value(data, data->REG_VBAT, tmp | 0x01); +} + +static int nct6775_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6775_sio_data *sio_data = dev->platform_data; + struct nct6775_data *data; + struct resource *res; + int i, err = 0; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, + DRVNAME)) + return -EBUSY; + + data = devm_kzalloc(&pdev->dev, sizeof(struct nct6775_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = sio_data->kind; + data->addr = res->start; + mutex_init(&data->lock); + mutex_init(&data->update_lock); + data->name = nct6775_device_names[data->kind]; + data->bank = 0xff; /* Force initial bank selection */ + platform_set_drvdata(pdev, data); + + switch (data->kind) { + case nct6775: + data->in_num = 9; + + data->ALARM_BITS = NCT6775_ALARM_BITS; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_VIN = NCT6775_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_ALARM = NCT6775_REG_ALARM; + break; + case nct6776: + data->in_num = 9; + + data->ALARM_BITS = NCT6776_ALARM_BITS; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_VIN = NCT6775_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_ALARM = NCT6775_REG_ALARM; + break; + case nct6779: + data->in_num = 15; + + data->ALARM_BITS = NCT6779_ALARM_BITS; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_VIN = NCT6779_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_ALARM = NCT6779_REG_ALARM; + break; + default: + return -ENODEV; + } + data->have_in = (1 << data->in_num) - 1; + + /* Initialize the chip */ + nct6775_init_device(data); + + data->vrm = vid_which_vrm(); + err = superio_enter(sio_data->sioreg); + if (err) + return err; + + /* + * Read VID value + * We can get the VID input values directly at logical device D 0xe3. + */ + superio_select(sio_data->sioreg, NCT6775_LD_VID); + data->vid = superio_inb(sio_data->sioreg, 0xe3); + superio_exit(sio_data->sioreg); + + err = device_create_file(dev, &dev_attr_cpu0_vid); + if (err) + return err; + + for (i = 0; i < data->in_num; i++) { + if (!(data->have_in & (1 << i))) + continue; + err = sysfs_create_group(&dev->kobj, &nct6775_group_in[i]); + if (err) + goto exit_remove; + } + + err = device_create_file(dev, &dev_attr_name); + if (err) + goto exit_remove; + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + nct6775_device_remove_files(dev); + return err; +} + +static int nct6775_remove(struct platform_device *pdev) +{ + struct nct6775_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + nct6775_device_remove_files(&pdev->dev); + + return 0; +} + +static struct platform_driver nct6775_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + }, + .probe = nct6775_probe, + .remove = nct6775_remove, +}; + +/* nct6775_find() looks for a '627 in the Super-I/O config space */ +static int __init nct6775_find(int sioaddr, unsigned short *addr, + struct nct6775_sio_data *sio_data) +{ + static const char sio_name_NCT6775[] __initconst = "NCT6775F"; + static const char sio_name_NCT6776[] __initconst = "NCT6776F"; + static const char sio_name_NCT6779[] __initconst = "NCT6779D"; + + u16 val; + const char *sio_name; + int err; + + err = superio_enter(sioaddr); + if (err) + return err; + + if (force_id) + val = force_id; + else + val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) + | superio_inb(sioaddr, SIO_REG_DEVID + 1); + switch (val & SIO_ID_MASK) { + case SIO_NCT6775_ID: + sio_data->kind = nct6775; + sio_name = sio_name_NCT6775; + break; + case SIO_NCT6776_ID: + sio_data->kind = nct6776; + sio_name = sio_name_NCT6776; + break; + case SIO_NCT6779_ID: + sio_data->kind = nct6779; + sio_name = sio_name_NCT6779; + break; + default: + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + superio_exit(sioaddr); + return -ENODEV; + } + + /* We have a known chip, find the HWM I/O address */ + superio_select(sioaddr, NCT6775_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("Refusing to enable a Super-I/O device with a base I/O port 0\n"); + superio_exit(sioaddr); + return -ENODEV; + } + + /* Activate logical device if needed */ + val = superio_inb(sioaddr, SIO_REG_ENABLE); + if (!(val & 0x01)) { + pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); + superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); + } + + superio_exit(sioaddr); + pr_info("Found %s chip at %#x\n", sio_name, *addr); + sio_data->sioreg = sioaddr; + + return 0; +} + +/* + * 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 nct6775 driver. But since we platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev; + +static int __init sensors_nct6775_init(void) +{ + int err; + unsigned short address; + struct resource res; + struct nct6775_sio_data sio_data; + + /* + * 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 + * nct6775 hardware monitor, and call probe() + */ + if (nct6775_find(0x2e, &address, &sio_data) && + nct6775_find(0x4e, &address, &sio_data)) + return -ENODEV; + + err = platform_driver_register(&nct6775_driver); + if (err) + goto exit; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit_unregister; + } + + err = platform_device_add_data(pdev, &sio_data, + sizeof(struct nct6775_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + 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) + goto exit_device_put; + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit_unregister: + platform_driver_unregister(&nct6775_driver); +exit: + return err; +} + +static void __exit sensors_nct6775_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&nct6775_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("NCT6775F/NCT6776F/NCT6779D driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_nct6775_init); +module_exit(sensors_nct6775_exit); -- cgit v1.2.3-70-g09d2 From 1c65dc365ed38d6839fcc231ea38a6163fb9d343 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Dec 2012 07:56:24 -0800 Subject: hwmon: (nct6775) Add support for fan speed attributes Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775 | 5 +- drivers/hwmon/nct6775.c | 516 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 518 insertions(+), 3 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/nct6775 b/Documentation/hwmon/nct6775 index ccfd5cc2100..dcd56a375ba 100644 --- a/Documentation/hwmon/nct6775 +++ b/Documentation/hwmon/nct6775 @@ -53,8 +53,9 @@ triggered if the rotation speed has dropped below a programmable limit. On NCT6775F, fan readings can be divided by a programmable divider (1, 2, 4, 8, 16, 32, 64 or 128) to give the readings more range or accuracy; the other chips do not have a fan speed divider. The driver sets the most suitable fan divisor -itself; specifically, it doubles the divider value each time a fan speed reading -returns an invalid value. Some fans might not be present because they share pins +itself; specifically, it increases the divider value each time a fan speed +reading returns an invalid value, and it reduces it if the fan speed reading +is lower than optimal. Some fans might not be present because they share pins with other functions. Voltage sensors (also known as IN sensors) report their values in millivolts. diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index fd0dd15ae4b..bafcae55e25 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -180,6 +180,9 @@ static const u16 NCT6775_REG_IN[] = { #define NCT6775_REG_VBAT 0x5D #define NCT6775_REG_DIODE 0x5E +#define NCT6775_REG_FANDIV1 0x506 +#define NCT6775_REG_FANDIV2 0x507 + static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; /* 0..15 voltages, 16..23 fans, 24..31 temperatures */ @@ -193,12 +196,16 @@ static const s8 NCT6775_ALARM_BITS[] = { 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ 12, -1 }; /* intrusion0, intrusion1 */ +#define FAN_ALARM_BASE 16 #define TEMP_ALARM_BASE 24 #define INTRUSION_ALARM_BASE 30 static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee }; static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; +static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; +static const u16 NCT6775_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d }; + static const u16 NCT6775_REG_TEMP[] = { 0x27, 0x150, 0x250, 0x62b, 0x62c, 0x62d }; @@ -256,6 +263,8 @@ static const s8 NCT6776_ALARM_BITS[] = { 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ 12, 9 }; /* intrusion0, intrusion1 */ +static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642 }; + static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { 0x18, 0x152, 0x252, 0x628, 0x629, 0x62A }; @@ -309,6 +318,8 @@ static const s8 NCT6779_ALARM_BITS[] = { 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ 12, 9 }; /* intrusion0, intrusion1 */ +static const u16 NCT6779_REG_FAN[] = { 0x4b0, 0x4b2, 0x4b4, 0x4b6, 0x4b8 }; + static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 }; static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = { 0x18, 0x152 }; @@ -363,6 +374,44 @@ static const u16 NCT6779_REG_TEMP_CRIT[ARRAY_SIZE(nct6779_temp_label) - 1] * Conversions */ +static unsigned int fan_from_reg8(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 255) + return 0; + return 1350000U / (reg << divreg); +} + +static unsigned int fan_from_reg13(u16 reg, unsigned int divreg) +{ + if ((reg & 0xff1f) == 0xff1f) + return 0; + + reg = (reg & 0x1f) | ((reg & 0xff00) >> 3); + + if (reg == 0) + return 0; + + return 1350000U / reg; +} + +static unsigned int fan_from_reg16(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 0xffff) + return 0; + + /* + * Even though the registers are 16 bit wide, the fan divisor + * still applies. + */ + return 1350000U / (reg << divreg); +} + +static inline unsigned int +div_from_reg(u8 reg) +{ + return 1 << reg; +} + /* * Some of the voltage inputs have internal scaling, the tables below * contain 8 (the ADC LSB in mV) * scaling factor * 100 @@ -411,12 +460,17 @@ struct nct6775_data { const u16 *REG_VIN; const u16 *REG_IN_MINMAX[2]; - const u16 *REG_TEMP_SOURCE; /* temp register sources */ + const u16 *REG_FAN; + const u16 *REG_FAN_MIN; + const u16 *REG_TEMP_SOURCE; /* temp register sources */ const u16 *REG_TEMP_OFFSET; const u16 *REG_ALARM; + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); + unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); + struct mutex update_lock; bool valid; /* true if following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -425,6 +479,12 @@ struct nct6775_data { u8 bank; /* current register bank */ u8 in_num; /* number of in inputs we have */ u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */ + unsigned int rpm[5]; + u16 fan_min[5]; + u8 fan_div[5]; + u8 has_fan; /* some fan inputs can be disabled */ + u8 has_fan_min; /* some fans don't have min register */ + bool has_fan_div; u8 temp_fixed_num; /* 3 or 6 */ u8 temp_type[NUM_TEMP_FIXED]; @@ -556,6 +616,153 @@ static int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value) return nct6775_write_value(data, reg, value); } +/* This function assumes that the caller holds data->update_lock */ +static void nct6775_write_fan_div(struct nct6775_data *data, int nr) +{ + u8 reg; + + switch (nr) { + case 0: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x70) + | (data->fan_div[0] & 0x7); + nct6775_write_value(data, NCT6775_REG_FANDIV1, reg); + break; + case 1: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x7) + | ((data->fan_div[1] << 4) & 0x70); + nct6775_write_value(data, NCT6775_REG_FANDIV1, reg); + break; + case 2: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x70) + | (data->fan_div[2] & 0x7); + nct6775_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + case 3: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x7) + | ((data->fan_div[3] << 4) & 0x70); + nct6775_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + } +} + +static void nct6775_write_fan_div_common(struct nct6775_data *data, int nr) +{ + if (data->kind == nct6775) + nct6775_write_fan_div(data, nr); +} + +static void nct6775_update_fan_div(struct nct6775_data *data) +{ + u8 i; + + i = nct6775_read_value(data, NCT6775_REG_FANDIV1); + data->fan_div[0] = i & 0x7; + data->fan_div[1] = (i & 0x70) >> 4; + i = nct6775_read_value(data, NCT6775_REG_FANDIV2); + data->fan_div[2] = i & 0x7; + if (data->has_fan & (1<<3)) + data->fan_div[3] = (i & 0x70) >> 4; +} + +static void nct6775_update_fan_div_common(struct nct6775_data *data) +{ + if (data->kind == nct6775) + nct6775_update_fan_div(data); +} + +static void nct6775_init_fan_div(struct nct6775_data *data) +{ + int i; + + nct6775_update_fan_div_common(data); + /* + * For all fans, start with highest divider value if the divider + * register is not initialized. This ensures that we get a + * reading from the fan count register, even if it is not optimal. + * We'll compute a better divider later on. + */ + for (i = 0; i < 3; i++) { + if (!(data->has_fan & (1 << i))) + continue; + if (data->fan_div[i] == 0) { + data->fan_div[i] = 7; + nct6775_write_fan_div_common(data, i); + } + } +} + +static void nct6775_init_fan_common(struct device *dev, + struct nct6775_data *data) +{ + int i; + u8 reg; + + if (data->has_fan_div) + nct6775_init_fan_div(data); + + /* + * If fan_min is not set (0), set it to 0xff to disable it. This + * prevents the unnecessary warning when fanX_min is reported as 0. + */ + for (i = 0; i < 5; i++) { + if (data->has_fan_min & (1 << i)) { + reg = nct6775_read_value(data, data->REG_FAN_MIN[i]); + if (!reg) + nct6775_write_value(data, data->REG_FAN_MIN[i], + data->has_fan_div ? 0xff + : 0xff1f); + } + } +} + +static void nct6775_select_fan_div(struct device *dev, + struct nct6775_data *data, int nr, u16 reg) +{ + u8 fan_div = data->fan_div[nr]; + u16 fan_min; + + if (!data->has_fan_div) + return; + + /* + * If we failed to measure the fan speed, or the reported value is not + * in the optimal range, and the clock divider can be modified, + * let's try that for next time. + */ + if (reg == 0x00 && fan_div < 0x07) + fan_div++; + else if (reg != 0x00 && reg < 0x30 && fan_div > 0) + fan_div--; + + if (fan_div != data->fan_div[nr]) { + dev_dbg(dev, "Modifying fan%d clock divider from %u to %u\n", + nr + 1, div_from_reg(data->fan_div[nr]), + div_from_reg(fan_div)); + + /* Preserve min limit if possible */ + if (data->has_fan_min & (1 << nr)) { + fan_min = data->fan_min[nr]; + if (fan_div > data->fan_div[nr]) { + if (fan_min != 255 && fan_min > 1) + fan_min >>= 1; + } else { + if (fan_min != 255) { + fan_min <<= 1; + if (fan_min > 254) + fan_min = 254; + } + } + if (fan_min != data->fan_min[nr]) { + data->fan_min[nr] = fan_min; + nct6775_write_value(data, data->REG_FAN_MIN[nr], + fan_min); + } + } + data->fan_div[nr] = fan_div; + nct6775_write_fan_div_common(data, nr); + } +} + static struct nct6775_data *nct6775_update_device(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); @@ -565,6 +772,9 @@ static struct nct6775_data *nct6775_update_device(struct device *dev) if (time_after(jiffies, data->last_updated + HZ + HZ/2) || !data->valid) { + /* Fan clock dividers */ + nct6775_update_fan_div_common(data); + /* Measured voltages and limits */ for (i = 0; i < data->in_num; i++) { if (!(data->have_in & (1 << i))) @@ -578,6 +788,24 @@ static struct nct6775_data *nct6775_update_device(struct device *dev) data->REG_IN_MINMAX[1][i]); } + /* Measured fan speeds and limits */ + for (i = 0; i < 5; i++) { + u16 reg; + + if (!(data->has_fan & (1 << i))) + continue; + + reg = nct6775_read_value(data, data->REG_FAN[i]); + data->rpm[i] = data->fan_from_reg(reg, + data->fan_div[i]); + + if (data->has_fan_min & (1 << i)) + data->fan_min[i] = nct6775_read_value(data, + data->REG_FAN_MIN[i]); + + nct6775_select_fan_div(dev, data, i, reg); + } + /* Measured temperatures and limits */ for (i = 0; i < NUM_TEMP; i++) { if (!(data->have_temp & (1 << i))) @@ -874,6 +1102,166 @@ static const struct attribute_group nct6775_group_in[15] = { { .attrs = nct6775_attributes_in[14] }, }; +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + return sprintf(buf, "%d\n", data->rpm[nr]); +} + +static ssize_t +show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + return sprintf(buf, "%d\n", + data->fan_from_reg_min(data->fan_min[nr], + data->fan_div[nr])); +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr])); +} + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + unsigned int reg; + u8 new_div; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + if (!data->has_fan_div) { + /* NCT6776F or NCT6779D; we know this is a 13 bit register */ + if (!val) { + val = 0xff1f; + } else { + if (val > 1350000U) + val = 135000U; + val = 1350000U / val; + val = (val & 0x1f) | ((val << 3) & 0xff00); + } + data->fan_min[nr] = val; + goto write_min; /* Leave fan divider alone */ + } + if (!val) { + /* No min limit, alarm disabled */ + data->fan_min[nr] = 255; + new_div = data->fan_div[nr]; /* No change */ + dev_info(dev, "fan%u low limit and alarm disabled\n", nr + 1); + goto write_div; + } + reg = 1350000U / val; + if (reg >= 128 * 255) { + /* + * Speed below this value cannot possibly be represented, + * even with the highest divider (128) + */ + data->fan_min[nr] = 254; + new_div = 7; /* 128 == (1 << 7) */ + dev_warn(dev, + "fan%u low limit %lu below minimum %u, set to minimum\n", + nr + 1, val, data->fan_from_reg_min(254, 7)); + } else if (!reg) { + /* + * Speed above this value cannot possibly be represented, + * even with the lowest divider (1) + */ + data->fan_min[nr] = 1; + new_div = 0; /* 1 == (1 << 0) */ + dev_warn(dev, + "fan%u low limit %lu above maximum %u, set to maximum\n", + nr + 1, val, data->fan_from_reg_min(1, 0)); + } else { + /* + * Automatically pick the best divider, i.e. the one such + * that the min limit will correspond to a register value + * in the 96..192 range + */ + new_div = 0; + while (reg > 192 && new_div < 7) { + reg >>= 1; + new_div++; + } + data->fan_min[nr] = reg; + } + +write_div: + /* + * Write both the fan clock divider (if it changed) and the new + * fan min (unconditionally) + */ + if (new_div != data->fan_div[nr]) { + dev_dbg(dev, "fan%u clock divider changed from %u to %u\n", + nr + 1, div_from_reg(data->fan_div[nr]), + div_from_reg(new_div)); + data->fan_div[nr] = new_div; + nct6775_write_fan_div_common(data, nr); + /* Give the chip time to sample a new speed value */ + data->last_updated = jiffies; + } + +write_min: + nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute sda_fan_input[] = { + SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0), + SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1), + SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2), + SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3), + SENSOR_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4), +}; + +static struct sensor_device_attribute sda_fan_alarm[] = { + SENSOR_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE), + SENSOR_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 1), + SENSOR_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 2), + SENSOR_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 3), + SENSOR_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 4), +}; + +static struct sensor_device_attribute sda_fan_min[] = { + SENSOR_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 0), + SENSOR_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 1), + SENSOR_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 2), + SENSOR_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 3), + SENSOR_ATTR(fan5_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 4), +}; + +static struct sensor_device_attribute sda_fan_div[] = { + SENSOR_ATTR(fan1_div, S_IRUGO, show_fan_div, NULL, 0), + SENSOR_ATTR(fan2_div, S_IRUGO, show_fan_div, NULL, 1), + SENSOR_ATTR(fan3_div, S_IRUGO, show_fan_div, NULL, 2), + SENSOR_ATTR(fan4_div, S_IRUGO, show_fan_div, NULL, 3), + SENSOR_ATTR(fan5_div, S_IRUGO, show_fan_div, NULL, 4), +}; + static ssize_t show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1228,6 +1616,12 @@ static void nct6775_device_remove_files(struct device *dev) for (i = 0; i < data->in_num; i++) sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]); + for (i = 0; i < 5; i++) { + device_remove_file(dev, &sda_fan_input[i].dev_attr); + device_remove_file(dev, &sda_fan_alarm[i].dev_attr); + device_remove_file(dev, &sda_fan_div[i].dev_attr); + device_remove_file(dev, &sda_fan_min[i].dev_attr); + } for (i = 0; i < NUM_TEMP; i++) { if (!(data->have_temp & (1 << i))) continue; @@ -1294,6 +1688,75 @@ static inline void nct6775_init_device(struct nct6775_data *data) } } +static int +nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data, + struct nct6775_data *data) +{ + int regval; + bool fan3pin, fan3min, fan4pin, fan4min, fan5pin; + int ret; + + ret = superio_enter(sio_data->sioreg); + if (ret) + return ret; + + /* fan4 and fan5 share some pins with the GPIO and serial flash */ + if (data->kind == nct6775) { + regval = superio_inb(sio_data->sioreg, 0x2c); + + fan3pin = regval & (1 << 6); + fan3min = fan3pin; + + /* On NCT6775, fan4 shares pins with the fdc interface */ + fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80); + fan4min = 0; + fan5pin = 0; + } else if (data->kind == nct6776) { + bool gpok = superio_inb(sio_data->sioreg, 0x27) & 0x80; + + superio_select(sio_data->sioreg, NCT6775_LD_HWM); + regval = superio_inb(sio_data->sioreg, SIO_REG_ENABLE); + + if (regval & 0x80) + fan3pin = gpok; + else + fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40); + + if (regval & 0x40) + fan4pin = gpok; + else + fan4pin = superio_inb(sio_data->sioreg, 0x1C) & 0x01; + + if (regval & 0x20) + fan5pin = gpok; + else + fan5pin = superio_inb(sio_data->sioreg, 0x1C) & 0x02; + + fan4min = fan4pin; + fan3min = fan3pin; + } else { /* NCT6779D */ + regval = superio_inb(sio_data->sioreg, 0x1c); + + fan3pin = !(regval & (1 << 5)); + fan4pin = !(regval & (1 << 6)); + fan5pin = !(regval & (1 << 7)); + + fan3min = fan3pin; + fan4min = fan4pin; + } + + superio_exit(sio_data->sioreg); + + data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */ + data->has_fan |= fan3pin << 2; + data->has_fan_min |= fan3min << 2; + + data->has_fan |= (fan4pin << 3) | (fan5pin << 4); + data->has_fan_min |= (fan4min << 3) | (fan5pin << 4); + + return 0; +} + static int nct6775_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1327,10 +1790,14 @@ static int nct6775_probe(struct platform_device *pdev) switch (data->kind) { case nct6775: data->in_num = 9; + data->has_fan_div = true; data->temp_fixed_num = 3; data->ALARM_BITS = NCT6775_ALARM_BITS; + data->fan_from_reg = fan_from_reg16; + data->fan_from_reg_min = fan_from_reg8; + data->temp_label = nct6775_temp_label; data->temp_label_num = ARRAY_SIZE(nct6775_temp_label); @@ -1340,6 +1807,8 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_VIN = NCT6775_REG_IN; data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MIN = NCT6775_REG_FAN_MIN; data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_ALARM = NCT6775_REG_ALARM; @@ -1355,10 +1824,14 @@ static int nct6775_probe(struct platform_device *pdev) break; case nct6776: data->in_num = 9; + data->has_fan_div = false; data->temp_fixed_num = 3; data->ALARM_BITS = NCT6776_ALARM_BITS; + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + data->temp_label = nct6776_temp_label; data->temp_label_num = ARRAY_SIZE(nct6776_temp_label); @@ -1368,6 +1841,8 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_VIN = NCT6775_REG_IN; data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_ALARM = NCT6775_REG_ALARM; @@ -1383,10 +1858,14 @@ static int nct6775_probe(struct platform_device *pdev) break; case nct6779: data->in_num = 15; + data->has_fan_div = false; data->temp_fixed_num = 6; data->ALARM_BITS = NCT6779_ALARM_BITS; + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + data->temp_label = nct6779_temp_label; data->temp_label_num = ARRAY_SIZE(nct6779_temp_label); @@ -1396,6 +1875,8 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_VIN = NCT6779_REG_IN; data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_FAN = NCT6779_REG_FAN; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_ALARM = NCT6779_REG_ALARM; @@ -1575,6 +2056,13 @@ static int nct6775_probe(struct platform_device *pdev) if (err) return err; + err = nct6775_check_fan_inputs(sio_data, data); + if (err) + goto exit_remove; + + /* Read fan clock dividers immediately */ + nct6775_init_fan_common(dev, data); + for (i = 0; i < data->in_num; i++) { if (!(data->have_in & (1 << i))) continue; @@ -1583,6 +2071,32 @@ static int nct6775_probe(struct platform_device *pdev) goto exit_remove; } + for (i = 0; i < 5; i++) { + if (data->has_fan & (1 << i)) { + err = device_create_file(dev, + &sda_fan_input[i].dev_attr); + if (err) + goto exit_remove; + err = device_create_file(dev, + &sda_fan_alarm[i].dev_attr); + if (err) + goto exit_remove; + if (data->kind != nct6776 && + data->kind != nct6779) { + err = device_create_file(dev, + &sda_fan_div[i].dev_attr); + if (err) + goto exit_remove; + } + if (data->has_fan_min & (1 << i)) { + err = device_create_file(dev, + &sda_fan_min[i].dev_attr); + if (err) + goto exit_remove; + } + } + } + for (i = 0; i < NUM_TEMP; i++) { if (!(data->have_temp & (1 << i))) continue; -- cgit v1.2.3-70-g09d2 From 77eb5b3703d995e6c72ef4a1e5411821f81df7e4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Dec 2012 08:30:54 -0800 Subject: hwmon: (nct6775) Add support for pwm, pwm_mode, and pwm_enable Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775 | 18 +++ drivers/hwmon/nct6775.c | 329 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 347 insertions(+) (limited to 'Documentation') diff --git a/Documentation/hwmon/nct6775 b/Documentation/hwmon/nct6775 index dcd56a375ba..7c9f1d30391 100644 --- a/Documentation/hwmon/nct6775 +++ b/Documentation/hwmon/nct6775 @@ -69,6 +69,24 @@ is driven slower/faster to reach the predefined range again. The mode works for fan1-fan5. +sysfs attributes +---------------- + +pwm[1-5] - this file stores PWM duty cycle or DC value (fan speed) in range: + 0 (lowest speed) to 255 (full) + +pwm[1-5]_enable - this file controls mode of fan/temperature control: + * 0 Fan control disabled (fans set to maximum speed) + * 1 Manual mode, write to pwm[0-5] any value 0-255 + * 2 "Thermal Cruise" mode + * 3 "Fan Speed Cruise" mode + * 4 "Smart Fan III" mode (NCT6775F only) + * 5 "Smart Fan IV" mode + +pwm[1-5]_mode - controls if output is PWM or DC level + * 0 DC output + * 1 PWM output + Usage Notes ----------- diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 56d7652d303..ad4ecc04e23 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -96,6 +96,8 @@ MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); #define SIO_NCT6779_ID 0xc560 #define SIO_ID_MASK 0xFFF0 +enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; + static inline void superio_outb(int ioreg, int reg, int val) { @@ -209,6 +211,15 @@ static const s8 NCT6775_ALARM_BITS[] = { static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee }; static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; +/* DC or PWM output fan configuration */ +static const u8 NCT6775_REG_PWM_MODE[] = { 0x04, 0x04, 0x12 }; +static const u8 NCT6775_PWM_MODE_MASK[] = { 0x01, 0x02, 0x01 }; + +static const u16 NCT6775_REG_FAN_MODE[] = { 0x102, 0x202, 0x302, 0x802, 0x902 }; + +static const u16 NCT6775_REG_PWM[] = { 0x109, 0x209, 0x309, 0x809, 0x909 }; +static const u16 NCT6775_REG_PWM_READ[] = { 0x01, 0x03, 0x11, 0x13, 0x15 }; + static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; static const u16 NCT6775_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d }; static const u16 NCT6775_REG_FAN_PULSES[] = { 0x641, 0x642, 0x643, 0x644, 0 }; @@ -270,6 +281,9 @@ static const s8 NCT6776_ALARM_BITS[] = { 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ 12, 9 }; /* intrusion0, intrusion1 */ +static const u8 NCT6776_REG_PWM_MODE[] = { 0x04, 0, 0 }; +static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0 }; + static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642 }; static const u16 NCT6776_REG_FAN_PULSES[] = { 0x644, 0x645, 0x646, 0, 0 }; @@ -380,6 +394,20 @@ static const u16 NCT6779_REG_TEMP_ALTERNATE[ARRAY_SIZE(nct6779_temp_label) - 1] static const u16 NCT6779_REG_TEMP_CRIT[ARRAY_SIZE(nct6779_temp_label) - 1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x709, 0x70a }; +static enum pwm_enable reg_to_pwm_enable(int pwm, int mode) +{ + if (mode == 0 && pwm == 255) + return off; + return mode + 1; +} + +static int pwm_enable_to_reg(enum pwm_enable mode) +{ + if (mode == off) + return 0; + return mode - 1; +} + /* * Conversions */ @@ -471,9 +499,16 @@ struct nct6775_data { const u16 *REG_IN_MINMAX[2]; const u16 *REG_FAN; + const u16 *REG_FAN_MODE; const u16 *REG_FAN_MIN; const u16 *REG_FAN_PULSES; + const u8 *REG_PWM_MODE; + const u8 *PWM_MODE_MASK; + + const u16 *REG_PWM[1]; /* [0]=pwm */ + const u16 *REG_PWM_READ; + const u16 *REG_TEMP_SOURCE; /* temp register sources */ const u16 *REG_TEMP_OFFSET; @@ -494,6 +529,7 @@ struct nct6775_data { u16 fan_min[5]; u8 fan_pulses[5]; u8 fan_div[5]; + u8 has_pwm; u8 has_fan; /* some fan inputs can be disabled */ u8 has_fan_min; /* some fans don't have min register */ bool has_fan_div; @@ -505,6 +541,18 @@ struct nct6775_data { * 3=temp_crit */ u64 alarms; + u8 pwm_num; /* number of pwm */ + u8 pwm_mode[5]; /* 1->DC variable voltage, 0->PWM variable duty cycle */ + enum pwm_enable pwm_enable[5]; + /* 0->off + * 1->manual + * 2->thermal cruise mode (also called SmartFan I) + * 3->fan speed cruise mode + * 4->SmartFan III + * 5->enhanced variable thermal cruise (SmartFan IV) + */ + u8 pwm[1][5]; /* [0]=pwm */ + u8 vid; u8 vrm; @@ -781,6 +829,36 @@ static void nct6775_select_fan_div(struct device *dev, } } +static void nct6775_update_pwm(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j; + int fanmodecfg; + bool duty_is_dc; + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_pwm & (1 << i))) + continue; + + duty_is_dc = data->REG_PWM_MODE[i] && + (nct6775_read_value(data, data->REG_PWM_MODE[i]) + & data->PWM_MODE_MASK[i]); + data->pwm_mode[i] = duty_is_dc; + + fanmodecfg = nct6775_read_value(data, data->REG_FAN_MODE[i]); + for (j = 0; j < ARRAY_SIZE(data->REG_PWM); j++) { + if (data->REG_PWM[j] && data->REG_PWM[j][i]) { + data->pwm[j][i] + = nct6775_read_value(data, + data->REG_PWM[j][i]); + } + } + + data->pwm_enable[i] = reg_to_pwm_enable(data->pwm[0][i], + (fanmodecfg >> 4) & 7); + } +} + static struct nct6775_data *nct6775_update_device(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); @@ -826,6 +904,8 @@ static struct nct6775_data *nct6775_update_device(struct device *dev) nct6775_select_fan_div(dev, data, i, reg); } + nct6775_update_pwm(dev); + /* Measured temperatures and limits */ for (i = 0; i < NUM_TEMP; i++) { if (!(data->have_temp & (1 << i))) @@ -1599,6 +1679,170 @@ static struct sensor_device_attribute sda_temp_alarm[] = { #define NUM_TEMP_ALARM ARRAY_SIZE(sda_temp_alarm) +static ssize_t +show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", !data->pwm_mode[sattr->index]); +} + +static ssize_t +store_pwm_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u8 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > 1) + return -EINVAL; + + /* Setting DC mode is not supported for all chips/channels */ + if (data->REG_PWM_MODE[nr] == 0) { + if (val) + return -EINVAL; + return count; + } + + mutex_lock(&data->update_lock); + data->pwm_mode[nr] = val; + reg = nct6775_read_value(data, data->REG_PWM_MODE[nr]); + reg &= ~data->PWM_MODE_MASK[nr]; + if (val) + reg |= data->PWM_MODE_MASK[nr]; + nct6775_write_value(data, data->REG_PWM_MODE[nr], reg); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + int pwm; + + /* + * For automatic fan control modes, show current pwm readings. + * Otherwise, show the configured value. + */ + if (index == 0 && data->pwm_enable[nr] > manual) + pwm = nct6775_read_value(data, data->REG_PWM_READ[nr]); + else + pwm = data->pwm[index][nr]; + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm[index][nr] = val; + nct6775_write_value(data, data->REG_PWM[index][nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", data->pwm_enable[sattr->index]); +} + +static ssize_t +store_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > sf4) + return -EINVAL; + + if (val == sf3 && data->kind != nct6775) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm_enable[nr] = val; + if (val == off) { + /* + * turn off pwm control: select manual mode, set pwm to maximum + */ + data->pwm[0][nr] = 255; + nct6775_write_value(data, data->REG_PWM[0][nr], 255); + } + reg = nct6775_read_value(data, data->REG_FAN_MODE[nr]); + reg &= 0x0f; + reg |= pwm_enable_to_reg(val) << 4; + nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR_2(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm3, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm4, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 3, 0); +static SENSOR_DEVICE_ATTR_2(pwm5, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 4, 0); + +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 0); +static SENSOR_DEVICE_ATTR(pwm2_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 1); +static SENSOR_DEVICE_ATTR(pwm3_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 2); +static SENSOR_DEVICE_ATTR(pwm4_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 3); +static SENSOR_DEVICE_ATTR(pwm5_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 4); + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 2); +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 3); +static SENSOR_DEVICE_ATTR(pwm5_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 4); + static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1609,6 +1853,47 @@ show_name(struct device *dev, struct device_attribute *attr, char *buf) static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); +static struct attribute *nct6775_attributes_pwm[5][4] = { + { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_mode.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_mode.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm4.dev_attr.attr, + &sensor_dev_attr_pwm4_mode.dev_attr.attr, + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm5.dev_attr.attr, + &sensor_dev_attr_pwm5_mode.dev_attr.attr, + &sensor_dev_attr_pwm5_enable.dev_attr.attr, + NULL + }, +}; + +static const struct attribute_group nct6775_group_pwm[5] = { + { .attrs = nct6775_attributes_pwm[0] }, + { .attrs = nct6775_attributes_pwm[1] }, + { .attrs = nct6775_attributes_pwm[2] }, + { .attrs = nct6775_attributes_pwm[3] }, + { .attrs = nct6775_attributes_pwm[4] }, +}; + static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1681,6 +1966,9 @@ static void nct6775_device_remove_files(struct device *dev) int i; struct nct6775_data *data = dev_get_drvdata(dev); + for (i = 0; i < data->pwm_num; i++) + sysfs_remove_group(&dev->kobj, &nct6775_group_pwm[i]); + for (i = 0; i < data->in_num; i++) sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]); @@ -1763,6 +2051,7 @@ nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data, { int regval; bool fan3pin, fan3min, fan4pin, fan4min, fan5pin; + bool pwm3pin, pwm4pin, pwm5pin; int ret; ret = superio_enter(sio_data->sioreg); @@ -1775,11 +2064,14 @@ nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data, fan3pin = regval & (1 << 6); fan3min = fan3pin; + pwm3pin = regval & (1 << 7); /* On NCT6775, fan4 shares pins with the fdc interface */ fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80); fan4min = 0; fan5pin = 0; + pwm4pin = 0; + pwm5pin = 0; } else if (data->kind == nct6776) { bool gpok = superio_inb(sio_data->sioreg, 0x27) & 0x80; @@ -1803,6 +2095,9 @@ nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data, fan4min = fan4pin; fan3min = fan3pin; + pwm3pin = fan3pin; + pwm4pin = 0; + pwm5pin = 0; } else { /* NCT6779D */ regval = superio_inb(sio_data->sioreg, 0x1c); @@ -1810,6 +2105,10 @@ nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data, fan4pin = !(regval & (1 << 6)); fan5pin = !(regval & (1 << 7)); + pwm3pin = !(regval & (1 << 0)); + pwm4pin = !(regval & (1 << 1)); + pwm5pin = !(regval & (1 << 2)); + fan3min = fan3pin; fan4min = fan4pin; } @@ -1823,6 +2122,8 @@ nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data, data->has_fan |= (fan4pin << 3) | (fan5pin << 4); data->has_fan_min |= (fan4min << 3) | (fan5pin << 4); + data->has_pwm = 0x03 | (pwm3pin << 2) | (pwm4pin << 3) | (pwm5pin << 4); + return 0; } @@ -1859,6 +2160,7 @@ static int nct6775_probe(struct platform_device *pdev) switch (data->kind) { case nct6775: data->in_num = 9; + data->pwm_num = 3; data->has_fan_div = true; data->temp_fixed_num = 3; @@ -1877,8 +2179,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; data->REG_FAN_MIN = NCT6775_REG_FAN_MIN; data->REG_FAN_PULSES = NCT6775_REG_FAN_PULSES; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6775_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6775_PWM_MODE_MASK; data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_ALARM = NCT6775_REG_ALARM; @@ -1894,6 +2201,7 @@ static int nct6775_probe(struct platform_device *pdev) break; case nct6776: data->in_num = 9; + data->pwm_num = 3; data->has_fan_div = false; data->temp_fixed_num = 3; @@ -1912,8 +2220,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; data->REG_FAN_PULSES = NCT6776_REG_FAN_PULSES; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_ALARM = NCT6775_REG_ALARM; @@ -1929,6 +2242,7 @@ static int nct6775_probe(struct platform_device *pdev) break; case nct6779: data->in_num = 15; + data->pwm_num = 5; data->has_fan_div = false; data->temp_fixed_num = 6; @@ -1947,8 +2261,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; data->REG_FAN = NCT6779_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_ALARM = NCT6779_REG_ALARM; @@ -2157,6 +2476,16 @@ static int nct6775_probe(struct platform_device *pdev) /* Read fan clock dividers immediately */ nct6775_init_fan_common(dev, data); + /* Register sysfs hooks */ + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_pwm & (1 << i))) + continue; + + err = sysfs_create_group(&dev->kobj, &nct6775_group_pwm[i]); + if (err) + goto exit_remove; + } + for (i = 0; i < data->in_num; i++) { if (!(data->have_in & (1 << i))) continue; -- cgit v1.2.3-70-g09d2 From cdcaeceb74ff3686eb25de6812870fbc765c3c39 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Dec 2012 09:04:52 -0800 Subject: hwmon: (nct6775) Add support for automatic fan control Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775 | 69 +++ drivers/hwmon/nct6775.c | 1099 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1161 insertions(+), 7 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/nct6775 b/Documentation/hwmon/nct6775 index 7c9f1d30391..b4fe6bc4d37 100644 --- a/Documentation/hwmon/nct6775 +++ b/Documentation/hwmon/nct6775 @@ -87,6 +87,75 @@ pwm[1-5]_mode - controls if output is PWM or DC level * 0 DC output * 1 PWM output +Common fan control attributes +----------------------------- + +pwm[1-5]_temp_sel Temperature source. Value is temperature sensor index. + For example, select '1' for temp1_input. + +Thermal Cruise mode (2) +----------------------- + +If the temperature is in the range defined by: + +pwm[1-5]_target_temp Target temperature, unit millidegree Celsius + (range 0 - 127000) +pwm[1-5]_temp_tolerance + Target temperature tolerance, unit millidegree Celsius + +there are no changes to fan speed. Once the temperature leaves the interval, fan +speed increases (if temperature is higher that desired) or decreases (if +temperature is lower than desired), using the following limits and time +intervals. + +pwm[1-5]_start fan pwm start value (range 1 - 255), to start fan + when the temperature is above defined range. +pwm[1-5]_floor lowest fan pwm (range 0 - 255) if temperature is below + the defined range. If set to 0, the fan is expected to + stop if the temperature is below the defined range. +pwm[1-5]_step_up_time milliseconds before fan speed is increased +pwm[1-5]_step_down_time milliseconds before fan speed is decreased +pwm[1-5]_stop_time how many milliseconds must elapse to switch + corresponding fan off (when the temperature was below + defined range). + +Speed Cruise mode (3) +--------------------- + +This modes tries to keep the fan speed constant. + +fan[1-5]_target Target fan speed +fan[1-5]_tolerance + Target speed tolerance + + +Untested; use at your own risk. + +Smart Fan IV mode (5) +--------------------- + +This mode offers multiple slopes to control the fan speed. The slopes can be +controlled by setting the pwm and temperature attributes. When the temperature +rises, the chip will calculate the DC/PWM output based on the current slope. +There are up to seven data points depending on the chip type. Subsequent data +points should be set to higher temperatures and higher pwm values to achieve +higher fan speeds with increasing temperature. The last data point reflects +critical temperature mode, in which the fans should run at full speed. + +pwm[1-5]_auto_point[1-7]_pwm + pwm value to be set if temperature reaches matching + temperature range. +pwm[1-5]_auto_point[1-7]_temp + Temperature over which the matching pwm is enabled. +pwm[1-5]_temp_tolerance + Temperature tolerance, unit millidegree Celsius +pwm[1-5]_crit_temp_tolerance + Temperature tolerance for critical temperature, + unit millidegree Celsius + +pwm[1-5]_step_up_time milliseconds before fan speed is increased +pwm[1-5]_step_down_time milliseconds before fan speed is decreased + Usage Notes ----------- diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index ad4ecc04e23..47b1d8947e4 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -215,8 +215,23 @@ static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; static const u8 NCT6775_REG_PWM_MODE[] = { 0x04, 0x04, 0x12 }; static const u8 NCT6775_PWM_MODE_MASK[] = { 0x01, 0x02, 0x01 }; -static const u16 NCT6775_REG_FAN_MODE[] = { 0x102, 0x202, 0x302, 0x802, 0x902 }; +/* Advanced Fan control, some values are common for all fans */ +static const u16 NCT6775_REG_TARGET[] = { 0x101, 0x201, 0x301, 0x801, 0x901 }; +static const u16 NCT6775_REG_FAN_MODE[] = { 0x102, 0x202, 0x302, 0x802, 0x902 }; +static const u16 NCT6775_REG_FAN_STEP_DOWN_TIME[] = { + 0x103, 0x203, 0x303, 0x803, 0x903 }; +static const u16 NCT6775_REG_FAN_STEP_UP_TIME[] = { + 0x104, 0x204, 0x304, 0x804, 0x904 }; +static const u16 NCT6775_REG_FAN_STOP_OUTPUT[] = { + 0x105, 0x205, 0x305, 0x805, 0x905 }; +static const u16 NCT6775_REG_FAN_START_OUTPUT[] + = { 0x106, 0x206, 0x306, 0x806, 0x906 }; +static const u16 NCT6775_REG_FAN_MAX_OUTPUT[] = { 0x10a, 0x20a, 0x30a }; +static const u16 NCT6775_REG_FAN_STEP_OUTPUT[] = { 0x10b, 0x20b, 0x30b }; + +static const u16 NCT6775_REG_FAN_STOP_TIME[] = { + 0x107, 0x207, 0x307, 0x807, 0x907 }; static const u16 NCT6775_REG_PWM[] = { 0x109, 0x209, 0x309, 0x809, 0x909 }; static const u16 NCT6775_REG_PWM_READ[] = { 0x01, 0x03, 0x11, 0x13, 0x15 }; @@ -237,8 +252,26 @@ static const u16 NCT6775_REG_TEMP_OVER[ARRAY_SIZE(NCT6775_REG_TEMP)] = { static const u16 NCT6775_REG_TEMP_SOURCE[ARRAY_SIZE(NCT6775_REG_TEMP)] = { 0x621, 0x622, 0x623, 0x624, 0x625, 0x626 }; +static const u16 NCT6775_REG_TEMP_SEL[] = { + 0x100, 0x200, 0x300, 0x800, 0x900 }; + static const u16 NCT6775_REG_TEMP_OFFSET[] = { 0x454, 0x455, 0x456 }; +static const u16 NCT6775_REG_AUTO_TEMP[] = { + 0x121, 0x221, 0x321, 0x821, 0x921 }; +static const u16 NCT6775_REG_AUTO_PWM[] = { + 0x127, 0x227, 0x327, 0x827, 0x927 }; + +#define NCT6775_AUTO_TEMP(data, nr, p) ((data)->REG_AUTO_TEMP[nr] + (p)) +#define NCT6775_AUTO_PWM(data, nr, p) ((data)->REG_AUTO_PWM[nr] + (p)) + +static const u16 NCT6775_REG_CRITICAL_ENAB[] = { 0x134, 0x234, 0x334 }; + +static const u16 NCT6775_REG_CRITICAL_TEMP[] = { + 0x135, 0x235, 0x335, 0x835, 0x935 }; +static const u16 NCT6775_REG_CRITICAL_TEMP_TOLERANCE[] = { + 0x138, 0x238, 0x338, 0x838, 0x938 }; + static const char *const nct6775_temp_label[] = { "", "SYSTIN", @@ -281,6 +314,9 @@ static const s8 NCT6776_ALARM_BITS[] = { 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ 12, 9 }; /* intrusion0, intrusion1 */ +static const u16 NCT6776_REG_TOLERANCE_H[] = { + 0x10c, 0x20c, 0x30c, 0x80c, 0x90c }; + static const u8 NCT6776_REG_PWM_MODE[] = { 0x04, 0, 0 }; static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0 }; @@ -344,6 +380,11 @@ static const u16 NCT6779_REG_FAN[] = { 0x4b0, 0x4b2, 0x4b4, 0x4b6, 0x4b8 }; static const u16 NCT6779_REG_FAN_PULSES[] = { 0x644, 0x645, 0x646, 0x647, 0x648 }; +static const u16 NCT6779_REG_CRITICAL_PWM_ENABLE[] = { + 0x136, 0x236, 0x336, 0x836, 0x936 }; +static const u16 NCT6779_REG_CRITICAL_PWM[] = { + 0x137, 0x237, 0x337, 0x837, 0x937 }; + static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 }; static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = { 0x18, 0x152 }; @@ -412,6 +453,18 @@ static int pwm_enable_to_reg(enum pwm_enable mode) * Conversions */ +/* 1 is DC mode, output in ms */ +static unsigned int step_time_from_reg(u8 reg, u8 mode) +{ + return mode ? 400 * reg : 100 * reg; +} + +static u8 step_time_to_reg(unsigned int msec, u8 mode) +{ + return clamp_val((mode ? (msec + 200) / 400 : + (msec + 50) / 100), 1, 255); +} + static unsigned int fan_from_reg8(u16 reg, unsigned int divreg) { if (reg == 0 || reg == 255) @@ -444,6 +497,14 @@ static unsigned int fan_from_reg16(u16 reg, unsigned int divreg) return 1350000U / (reg << divreg); } +static u16 fan_to_reg(u32 fan, unsigned int divreg) +{ + if (!fan) + return 0; + + return (1350000U / fan) >> divreg; +} + static inline unsigned int div_from_reg(u8 reg) { @@ -498,18 +559,31 @@ struct nct6775_data { const u16 *REG_VIN; const u16 *REG_IN_MINMAX[2]; + const u16 *REG_TARGET; const u16 *REG_FAN; const u16 *REG_FAN_MODE; const u16 *REG_FAN_MIN; const u16 *REG_FAN_PULSES; + const u16 *REG_FAN_TIME[3]; + + const u16 *REG_TOLERANCE_H; const u8 *REG_PWM_MODE; const u8 *PWM_MODE_MASK; - const u16 *REG_PWM[1]; /* [0]=pwm */ + const u16 *REG_PWM[5]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step + */ const u16 *REG_PWM_READ; + const u16 *REG_AUTO_TEMP; + const u16 *REG_AUTO_PWM; + + const u16 *REG_CRITICAL_TEMP; + const u16 *REG_CRITICAL_TEMP_TOLERANCE; + const u16 *REG_TEMP_SOURCE; /* temp register sources */ + const u16 *REG_TEMP_SEL; const u16 *REG_TEMP_OFFSET; const u16 *REG_ALARM; @@ -551,7 +625,26 @@ struct nct6775_data { * 4->SmartFan III * 5->enhanced variable thermal cruise (SmartFan IV) */ - u8 pwm[1][5]; /* [0]=pwm */ + u8 pwm[5][5]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step + */ + + u8 target_temp[5]; + u8 target_temp_mask; + u32 target_speed[5]; + u32 target_speed_tolerance[5]; + u8 speed_tolerance_limit; + + u8 temp_tolerance[2][5]; + u8 tolerance_mask; + + u8 fan_time[3][5]; /* 0 = stop_time, 1 = step_up, 2 = step_down */ + + /* Automatic fan speed control registers */ + int auto_pwm_num; + u8 auto_pwm[5][7]; + u8 auto_temp[5][7]; + u8 pwm_temp_sel[5]; u8 vid; u8 vrm; @@ -833,7 +926,7 @@ static void nct6775_update_pwm(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); int i, j; - int fanmodecfg; + int fanmodecfg, reg; bool duty_is_dc; for (i = 0; i < data->pwm_num; i++) { @@ -856,6 +949,96 @@ static void nct6775_update_pwm(struct device *dev) data->pwm_enable[i] = reg_to_pwm_enable(data->pwm[0][i], (fanmodecfg >> 4) & 7); + + if (!data->temp_tolerance[0][i] || + data->pwm_enable[i] != speed_cruise) + data->temp_tolerance[0][i] = fanmodecfg & 0x0f; + if (!data->target_speed_tolerance[i] || + data->pwm_enable[i] == speed_cruise) { + u8 t = fanmodecfg & 0x0f; + if (data->REG_TOLERANCE_H) { + t |= (nct6775_read_value(data, + data->REG_TOLERANCE_H[i]) & 0x70) >> 1; + } + data->target_speed_tolerance[i] = t; + } + + data->temp_tolerance[1][i] = + nct6775_read_value(data, + data->REG_CRITICAL_TEMP_TOLERANCE[i]); + + reg = nct6775_read_value(data, data->REG_TEMP_SEL[i]); + data->pwm_temp_sel[i] = reg & 0x1f; + /* If fan can stop, report floor as 0 */ + if (reg & 0x80) + data->pwm[2][i] = 0; + } +} + +static void nct6775_update_pwm_limits(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j; + u8 reg; + u16 reg_t; + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_pwm & (1 << i))) + continue; + + for (j = 0; j < 3; j++) { + data->fan_time[j][i] = + nct6775_read_value(data, data->REG_FAN_TIME[j][i]); + } + + reg_t = nct6775_read_value(data, data->REG_TARGET[i]); + /* Update only in matching mode or if never updated */ + if (!data->target_temp[i] || + data->pwm_enable[i] == thermal_cruise) + data->target_temp[i] = reg_t & data->target_temp_mask; + if (!data->target_speed[i] || + data->pwm_enable[i] == speed_cruise) { + if (data->REG_TOLERANCE_H) { + reg_t |= (nct6775_read_value(data, + data->REG_TOLERANCE_H[i]) & 0x0f) << 8; + } + data->target_speed[i] = reg_t; + } + + for (j = 0; j < data->auto_pwm_num; j++) { + data->auto_pwm[i][j] = + nct6775_read_value(data, + NCT6775_AUTO_PWM(data, i, j)); + data->auto_temp[i][j] = + nct6775_read_value(data, + NCT6775_AUTO_TEMP(data, i, j)); + } + + /* critical auto_pwm temperature data */ + data->auto_temp[i][data->auto_pwm_num] = + nct6775_read_value(data, data->REG_CRITICAL_TEMP[i]); + + switch (data->kind) { + case nct6775: + reg = nct6775_read_value(data, + NCT6775_REG_CRITICAL_ENAB[i]); + data->auto_pwm[i][data->auto_pwm_num] = + (reg & 0x02) ? 0xff : 0x00; + break; + case nct6776: + data->auto_pwm[i][data->auto_pwm_num] = 0xff; + break; + case nct6779: + reg = nct6775_read_value(data, + NCT6779_REG_CRITICAL_PWM_ENABLE[i]); + if (reg & 1) + data->auto_pwm[i][data->auto_pwm_num] = + nct6775_read_value(data, + NCT6779_REG_CRITICAL_PWM[i]); + else + data->auto_pwm[i][data->auto_pwm_num] = 0xff; + break; + } } } @@ -905,6 +1088,7 @@ static struct nct6775_data *nct6775_update_device(struct device *dev) } nct6775_update_pwm(dev); + nct6775_update_pwm_limits(dev); /* Measured temperatures and limits */ for (i = 0; i < NUM_TEMP; i++) { @@ -1754,20 +1938,91 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, int nr = sattr->nr; int index = sattr->index; unsigned long val; + int minval[5] = { 0, 1, 1, data->pwm[2][nr], 0 }; + int maxval[5] + = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255 }; int err; + u8 reg; err = kstrtoul(buf, 10, &val); if (err < 0) return err; - val = clamp_val(val, 0, 255); + val = clamp_val(val, minval[index], maxval[index]); mutex_lock(&data->update_lock); data->pwm[index][nr] = val; nct6775_write_value(data, data->REG_PWM[index][nr], val); + if (index == 2) { /* floor: disable if val == 0 */ + reg = nct6775_read_value(data, data->REG_TEMP_SEL[nr]); + reg &= 0x7f; + if (val) + reg |= 0x80; + nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); + } mutex_unlock(&data->update_lock); return count; } +/* Returns 0 if OK, -EINVAL otherwise */ +static int check_trip_points(struct nct6775_data *data, int nr) +{ + int i; + + for (i = 0; i < data->auto_pwm_num - 1; i++) { + if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) + return -EINVAL; + } + for (i = 0; i < data->auto_pwm_num - 1; i++) { + if (data->auto_pwm[nr][i] > data->auto_pwm[nr][i + 1]) + return -EINVAL; + } + /* validate critical temperature and pwm if enabled (pwm > 0) */ + if (data->auto_pwm[nr][data->auto_pwm_num]) { + if (data->auto_temp[nr][data->auto_pwm_num - 1] > + data->auto_temp[nr][data->auto_pwm_num] || + data->auto_pwm[nr][data->auto_pwm_num - 1] > + data->auto_pwm[nr][data->auto_pwm_num]) + return -EINVAL; + } + return 0; +} + +static void pwm_update_registers(struct nct6775_data *data, int nr) +{ + u8 reg; + + switch (data->pwm_enable[nr]) { + case off: + case manual: + break; + case speed_cruise: + reg = nct6775_read_value(data, data->REG_FAN_MODE[nr]); + reg = (reg & ~data->tolerance_mask) | + (data->target_speed_tolerance[nr] & data->tolerance_mask); + nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + nct6775_write_value(data, data->REG_TARGET[nr], + data->target_speed[nr] & 0xff); + if (data->REG_TOLERANCE_H) { + reg = (data->target_speed[nr] >> 8) & 0x0f; + reg |= (data->target_speed_tolerance[nr] & 0x38) << 1; + nct6775_write_value(data, + data->REG_TOLERANCE_H[nr], + reg); + } + break; + case thermal_cruise: + nct6775_write_value(data, data->REG_TARGET[nr], + data->target_temp[nr]); + /* intentional */ + default: + reg = nct6775_read_value(data, data->REG_FAN_MODE[nr]); + reg = (reg & ~data->tolerance_mask) | + data->temp_tolerance[0][nr]; + nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + break; + } +} + static ssize_t show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1798,6 +2053,12 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, if (val == sf3 && data->kind != nct6775) return -EINVAL; + if (val == sf4 && check_trip_points(data, nr)) { + dev_err(dev, "Inconsistent trip points, not switching to SmartFan IV mode\n"); + dev_err(dev, "Adjust trip points and try again\n"); + return -EINVAL; + } + mutex_lock(&data->update_lock); data->pwm_enable[nr] = val; if (val == off) { @@ -1807,6 +2068,7 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, data->pwm[0][nr] = 255; nct6775_write_value(data, data->REG_PWM[0][nr], 255); } + pwm_update_registers(data, nr); reg = nct6775_read_value(data, data->REG_FAN_MODE[nr]); reg &= 0x0f; reg |= pwm_enable_to_reg(val) << 4; @@ -1815,6 +2077,235 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, return count; } +static ssize_t +show_pwm_temp_sel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int i, src, sel = 0; + + src = data->pwm_temp_sel[sattr->index]; + + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + if (src == data->temp_src[i]) { + sel = i + 1; + break; + } + } + + return sprintf(buf, "%d\n", sel); +} + +static ssize_t +store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err, reg, src; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val == 0 || val > NUM_TEMP) + return -EINVAL; + if (!(data->have_temp & (1 << (val - 1))) || !data->temp_src[val - 1]) + return -EINVAL; + + mutex_lock(&data->update_lock); + src = data->temp_src[val - 1]; + data->pwm_temp_sel[nr] = src; + reg = nct6775_read_value(data, data->REG_TEMP_SEL[nr]); + reg &= 0xe0; + reg |= src; + nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_target_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", data->target_temp[sattr->index] * 1000); +} + +static ssize_t +store_target_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, + data->target_temp_mask); + + mutex_lock(&data->update_lock); + data->target_temp[nr] = val; + pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_target_speed(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%d\n", + fan_from_reg16(data->target_speed[nr], + data->fan_div[nr])); +} + +static ssize_t +store_target_speed(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 speed; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(val, 0, 1350000U); + speed = fan_to_reg(val, data->fan_div[nr]); + + mutex_lock(&data->update_lock); + data->target_speed[nr] = speed; + pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_temp_tolerance(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + return sprintf(buf, "%d\n", data->temp_tolerance[index][nr] * 1000); +} + +static ssize_t +store_temp_tolerance(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + /* Limit tolerance as needed */ + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, data->tolerance_mask); + + mutex_lock(&data->update_lock); + data->temp_tolerance[index][nr] = val; + if (index) + pwm_update_registers(data, nr); + else + nct6775_write_value(data, + data->REG_CRITICAL_TEMP_TOLERANCE[nr], + val); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Fan speed tolerance is a tricky beast, since the associated register is + * a tick counter, but the value is reported and configured as rpm. + * Compute resulting low and high rpm values and report the difference. + */ +static ssize_t +show_speed_tolerance(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + int low = data->target_speed[nr] - data->target_speed_tolerance[nr]; + int high = data->target_speed[nr] + data->target_speed_tolerance[nr]; + int tolerance; + + if (low <= 0) + low = 1; + if (high > 0xffff) + high = 0xffff; + if (high < low) + high = low; + + tolerance = (fan_from_reg16(low, data->fan_div[nr]) + - fan_from_reg16(high, data->fan_div[nr])) / 2; + + return sprintf(buf, "%d\n", tolerance); +} + +static ssize_t +store_speed_tolerance(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + int low, high; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + high = fan_from_reg16(data->target_speed[nr], + data->fan_div[nr]) + val; + low = fan_from_reg16(data->target_speed[nr], + data->fan_div[nr]) - val; + if (low <= 0) + low = 1; + if (high < low) + high = low; + + val = (fan_to_reg(low, data->fan_div[nr]) - + fan_to_reg(high, data->fan_div[nr])) / 2; + + /* Limit tolerance as needed */ + val = clamp_val(val, 0, data->speed_tolerance_limit); + + mutex_lock(&data->update_lock); + data->target_speed_tolerance[nr] = val; + pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return count; +} + static SENSOR_DEVICE_ATTR_2(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 0); static SENSOR_DEVICE_ATTR_2(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1, 0); static SENSOR_DEVICE_ATTR_2(pwm3, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 2, 0); @@ -1843,6 +2334,88 @@ static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, show_pwm_enable, static SENSOR_DEVICE_ATTR(pwm5_enable, S_IWUSR | S_IRUGO, show_pwm_enable, store_pwm_enable, 4); +static SENSOR_DEVICE_ATTR(pwm1_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_temp_sel, store_pwm_temp_sel, 0); +static SENSOR_DEVICE_ATTR(pwm2_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_temp_sel, store_pwm_temp_sel, 1); +static SENSOR_DEVICE_ATTR(pwm3_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_temp_sel, store_pwm_temp_sel, 2); +static SENSOR_DEVICE_ATTR(pwm4_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_temp_sel, store_pwm_temp_sel, 3); +static SENSOR_DEVICE_ATTR(pwm5_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_temp_sel, store_pwm_temp_sel, 4); + +static SENSOR_DEVICE_ATTR(pwm1_target_temp, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 0); +static SENSOR_DEVICE_ATTR(pwm2_target_temp, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 1); +static SENSOR_DEVICE_ATTR(pwm3_target_temp, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 2); +static SENSOR_DEVICE_ATTR(pwm4_target_temp, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 3); +static SENSOR_DEVICE_ATTR(pwm5_target_temp, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 4); + +static SENSOR_DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO, show_target_speed, + store_target_speed, 0); +static SENSOR_DEVICE_ATTR(fan2_target, S_IWUSR | S_IRUGO, show_target_speed, + store_target_speed, 1); +static SENSOR_DEVICE_ATTR(fan3_target, S_IWUSR | S_IRUGO, show_target_speed, + store_target_speed, 2); +static SENSOR_DEVICE_ATTR(fan4_target, S_IWUSR | S_IRUGO, show_target_speed, + store_target_speed, 3); +static SENSOR_DEVICE_ATTR(fan5_target, S_IWUSR | S_IRUGO, show_target_speed, + store_target_speed, 4); + +static SENSOR_DEVICE_ATTR(fan1_tolerance, S_IWUSR | S_IRUGO, + show_speed_tolerance, store_speed_tolerance, 0); +static SENSOR_DEVICE_ATTR(fan2_tolerance, S_IWUSR | S_IRUGO, + show_speed_tolerance, store_speed_tolerance, 1); +static SENSOR_DEVICE_ATTR(fan3_tolerance, S_IWUSR | S_IRUGO, + show_speed_tolerance, store_speed_tolerance, 2); +static SENSOR_DEVICE_ATTR(fan4_tolerance, S_IWUSR | S_IRUGO, + show_speed_tolerance, store_speed_tolerance, 3); +static SENSOR_DEVICE_ATTR(fan5_tolerance, S_IWUSR | S_IRUGO, + show_speed_tolerance, store_speed_tolerance, 4); + +/* Smart Fan registers */ + +static ssize_t +show_fan_time(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + return sprintf(buf, "%d\n", + step_time_from_reg(data->fan_time[index][nr], + data->pwm_mode[nr])); +} + +static ssize_t +store_fan_time(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = step_time_to_reg(val, data->pwm_mode[nr]); + mutex_lock(&data->update_lock); + data->fan_time[index][nr] = val; + nct6775_write_value(data, data->REG_FAN_TIME[index][nr], val); + mutex_unlock(&data->update_lock); + return count; +} + static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1853,35 +2426,190 @@ show_name(struct device *dev, struct device_attribute *attr, char *buf) static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); -static struct attribute *nct6775_attributes_pwm[5][4] = { +static SENSOR_DEVICE_ATTR_2(pwm1_stop_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_stop_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_stop_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm4_stop_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 3, 0); +static SENSOR_DEVICE_ATTR_2(pwm5_stop_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 4, 0); + +static SENSOR_DEVICE_ATTR_2(pwm1_step_up_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_step_up_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_step_up_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm4_step_up_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 3, 1); +static SENSOR_DEVICE_ATTR_2(pwm5_step_up_time, S_IWUSR | S_IRUGO, show_fan_time, + store_fan_time, 4, 1); + +static SENSOR_DEVICE_ATTR_2(pwm1_step_down_time, S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 0, 2); +static SENSOR_DEVICE_ATTR_2(pwm2_step_down_time, S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 1, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_step_down_time, S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm4_step_down_time, S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 3, 2); +static SENSOR_DEVICE_ATTR_2(pwm5_step_down_time, S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 4, 2); + +static SENSOR_DEVICE_ATTR_2(pwm1_start, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_start, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_start, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm4_start, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 3, 1); +static SENSOR_DEVICE_ATTR_2(pwm5_start, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 4, 1); + +static SENSOR_DEVICE_ATTR_2(pwm1_floor, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 0, 2); +static SENSOR_DEVICE_ATTR_2(pwm2_floor, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 1, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_floor, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm4_floor, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 3, 2); +static SENSOR_DEVICE_ATTR_2(pwm5_floor, S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 4, 2); + +static SENSOR_DEVICE_ATTR_2(pwm1_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm4_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 3, 0); +static SENSOR_DEVICE_ATTR_2(pwm5_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 4, 0); + +static SENSOR_DEVICE_ATTR_2(pwm1_crit_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_crit_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_crit_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm4_crit_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 3, 1); +static SENSOR_DEVICE_ATTR_2(pwm5_crit_temp_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 4, 1); + +/* pwm_max is not supported on all chips */ +static struct sensor_device_attribute_2 sda_pwm_max[] = { + SENSOR_ATTR_2(pwm1_max, S_IWUSR | S_IRUGO, show_pwm, store_pwm, + 0, 3), + SENSOR_ATTR_2(pwm2_max, S_IWUSR | S_IRUGO, show_pwm, store_pwm, + 1, 3), + SENSOR_ATTR_2(pwm3_max, S_IWUSR | S_IRUGO, show_pwm, store_pwm, + 2, 3), + SENSOR_ATTR_2(pwm4_max, S_IWUSR | S_IRUGO, show_pwm, store_pwm, + 3, 3), + SENSOR_ATTR_2(pwm5_max, S_IWUSR | S_IRUGO, show_pwm, store_pwm, + 4, 3), +}; + +/* pwm_step is not supported on all chips */ +static struct sensor_device_attribute_2 sda_pwm_step[] = { + SENSOR_ATTR_2(pwm1_step, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 4), + SENSOR_ATTR_2(pwm2_step, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1, 4), + SENSOR_ATTR_2(pwm3_step, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 2, 4), + SENSOR_ATTR_2(pwm4_step, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 3, 4), + SENSOR_ATTR_2(pwm5_step, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 4, 4), +}; + +static struct attribute *nct6775_attributes_pwm[5][15] = { { &sensor_dev_attr_pwm1.dev_attr.attr, &sensor_dev_attr_pwm1_mode.dev_attr.attr, &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm1_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm1_crit_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm1_target_temp.dev_attr.attr, + &sensor_dev_attr_fan1_target.dev_attr.attr, + &sensor_dev_attr_fan1_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm1_stop_time.dev_attr.attr, + &sensor_dev_attr_pwm1_step_up_time.dev_attr.attr, + &sensor_dev_attr_pwm1_step_down_time.dev_attr.attr, + &sensor_dev_attr_pwm1_start.dev_attr.attr, + &sensor_dev_attr_pwm1_floor.dev_attr.attr, NULL }, { &sensor_dev_attr_pwm2.dev_attr.attr, &sensor_dev_attr_pwm2_mode.dev_attr.attr, &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm2_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm2_crit_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm2_target_temp.dev_attr.attr, + &sensor_dev_attr_fan2_target.dev_attr.attr, + &sensor_dev_attr_fan2_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm2_stop_time.dev_attr.attr, + &sensor_dev_attr_pwm2_step_up_time.dev_attr.attr, + &sensor_dev_attr_pwm2_step_down_time.dev_attr.attr, + &sensor_dev_attr_pwm2_start.dev_attr.attr, + &sensor_dev_attr_pwm2_floor.dev_attr.attr, NULL }, { &sensor_dev_attr_pwm3.dev_attr.attr, &sensor_dev_attr_pwm3_mode.dev_attr.attr, &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm3_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm3_crit_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm3_target_temp.dev_attr.attr, + &sensor_dev_attr_fan3_target.dev_attr.attr, + &sensor_dev_attr_fan3_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm3_stop_time.dev_attr.attr, + &sensor_dev_attr_pwm3_step_up_time.dev_attr.attr, + &sensor_dev_attr_pwm3_step_down_time.dev_attr.attr, + &sensor_dev_attr_pwm3_start.dev_attr.attr, + &sensor_dev_attr_pwm3_floor.dev_attr.attr, NULL }, { &sensor_dev_attr_pwm4.dev_attr.attr, &sensor_dev_attr_pwm4_mode.dev_attr.attr, &sensor_dev_attr_pwm4_enable.dev_attr.attr, + &sensor_dev_attr_pwm4_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm4_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm4_crit_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm4_target_temp.dev_attr.attr, + &sensor_dev_attr_fan4_target.dev_attr.attr, + &sensor_dev_attr_fan4_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm4_stop_time.dev_attr.attr, + &sensor_dev_attr_pwm4_step_up_time.dev_attr.attr, + &sensor_dev_attr_pwm4_step_down_time.dev_attr.attr, + &sensor_dev_attr_pwm4_start.dev_attr.attr, + &sensor_dev_attr_pwm4_floor.dev_attr.attr, NULL }, { &sensor_dev_attr_pwm5.dev_attr.attr, &sensor_dev_attr_pwm5_mode.dev_attr.attr, &sensor_dev_attr_pwm5_enable.dev_attr.attr, + &sensor_dev_attr_pwm5_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm5_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm5_crit_temp_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm5_target_temp.dev_attr.attr, + &sensor_dev_attr_fan5_target.dev_attr.attr, + &sensor_dev_attr_fan5_tolerance.dev_attr.attr, + &sensor_dev_attr_pwm5_stop_time.dev_attr.attr, + &sensor_dev_attr_pwm5_step_up_time.dev_attr.attr, + &sensor_dev_attr_pwm5_step_down_time.dev_attr.attr, + &sensor_dev_attr_pwm5_start.dev_attr.attr, + &sensor_dev_attr_pwm5_floor.dev_attr.attr, NULL }, }; @@ -1894,6 +2622,277 @@ static const struct attribute_group nct6775_group_pwm[5] = { { .attrs = nct6775_attributes_pwm[4] }, }; +static ssize_t +show_auto_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + return sprintf(buf, "%d\n", data->auto_pwm[sattr->nr][sattr->index]); +} + +static ssize_t +store_auto_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int point = sattr->index; + unsigned long val; + int err; + u8 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 255) + return -EINVAL; + + if (point == data->auto_pwm_num) { + if (data->kind != nct6775 && !val) + return -EINVAL; + if (data->kind != nct6779 && val) + val = 0xff; + } + + mutex_lock(&data->update_lock); + data->auto_pwm[nr][point] = val; + if (point < data->auto_pwm_num) { + nct6775_write_value(data, + NCT6775_AUTO_PWM(data, nr, point), + data->auto_pwm[nr][point]); + } else { + switch (data->kind) { + case nct6775: + /* disable if needed (pwm == 0) */ + reg = nct6775_read_value(data, + NCT6775_REG_CRITICAL_ENAB[nr]); + if (val) + reg |= 0x02; + else + reg &= ~0x02; + nct6775_write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], + reg); + break; + case nct6776: + break; /* always enabled, nothing to do */ + case nct6779: + nct6775_write_value(data, NCT6779_REG_CRITICAL_PWM[nr], + val); + reg = nct6775_read_value(data, + NCT6779_REG_CRITICAL_PWM_ENABLE[nr]); + if (val == 255) + reg &= ~0x01; + else + reg |= 0x01; + nct6775_write_value(data, + NCT6779_REG_CRITICAL_PWM_ENABLE[nr], + reg); + break; + } + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_auto_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int point = sattr->index; + + /* + * We don't know for sure if the temperature is signed or unsigned. + * Assume it is unsigned. + */ + return sprintf(buf, "%d\n", data->auto_temp[nr][point] * 1000); +} + +static ssize_t +store_auto_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int point = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val > 255000) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_temp[nr][point] = DIV_ROUND_CLOSEST(val, 1000); + if (point < data->auto_pwm_num) { + nct6775_write_value(data, + NCT6775_AUTO_TEMP(data, nr, point), + data->auto_temp[nr][point]); + } else { + nct6775_write_value(data, data->REG_CRITICAL_TEMP[nr], + data->auto_temp[nr][point]); + } + mutex_unlock(&data->update_lock); + return count; +} + +/* + * The number of auto-point trip points is chip dependent. + * Need to check support while generating/removing attribute files. + */ +static struct sensor_device_attribute_2 sda_auto_pwm_arrays[] = { + SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 0, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 0, 1), + SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 0, 1), + SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 0, 2), + SENSOR_ATTR_2(pwm1_auto_point3_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 0, 2), + SENSOR_ATTR_2(pwm1_auto_point4_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 0, 3), + SENSOR_ATTR_2(pwm1_auto_point4_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 0, 3), + SENSOR_ATTR_2(pwm1_auto_point5_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 0, 4), + SENSOR_ATTR_2(pwm1_auto_point5_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 0, 4), + SENSOR_ATTR_2(pwm1_auto_point6_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 0, 5), + SENSOR_ATTR_2(pwm1_auto_point6_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 0, 5), + SENSOR_ATTR_2(pwm1_auto_point7_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 0, 6), + SENSOR_ATTR_2(pwm1_auto_point7_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 0, 6), + + SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 1, 0), + SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 1, 0), + SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 1, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 1, 2), + SENSOR_ATTR_2(pwm2_auto_point3_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 1, 2), + SENSOR_ATTR_2(pwm2_auto_point4_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 1, 3), + SENSOR_ATTR_2(pwm2_auto_point4_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 1, 3), + SENSOR_ATTR_2(pwm2_auto_point5_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 1, 4), + SENSOR_ATTR_2(pwm2_auto_point5_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 1, 4), + SENSOR_ATTR_2(pwm2_auto_point6_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 1, 5), + SENSOR_ATTR_2(pwm2_auto_point6_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 1, 5), + SENSOR_ATTR_2(pwm2_auto_point7_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 1, 6), + SENSOR_ATTR_2(pwm2_auto_point7_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 1, 6), + + SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 2, 0), + SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 2, 0), + SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 2, 1), + SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 2, 1), + SENSOR_ATTR_2(pwm3_auto_point3_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 2, 2), + SENSOR_ATTR_2(pwm3_auto_point3_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 2, 2), + SENSOR_ATTR_2(pwm3_auto_point4_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 2, 3), + SENSOR_ATTR_2(pwm3_auto_point4_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 2, 3), + SENSOR_ATTR_2(pwm3_auto_point5_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 2, 4), + SENSOR_ATTR_2(pwm3_auto_point5_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 2, 4), + SENSOR_ATTR_2(pwm3_auto_point6_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 2, 5), + SENSOR_ATTR_2(pwm3_auto_point6_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 2, 5), + SENSOR_ATTR_2(pwm3_auto_point7_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 2, 6), + SENSOR_ATTR_2(pwm3_auto_point7_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 2, 6), + + SENSOR_ATTR_2(pwm4_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 3, 0), + SENSOR_ATTR_2(pwm4_auto_point1_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 3, 0), + SENSOR_ATTR_2(pwm4_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 3, 1), + SENSOR_ATTR_2(pwm4_auto_point2_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 3, 1), + SENSOR_ATTR_2(pwm4_auto_point3_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 3, 2), + SENSOR_ATTR_2(pwm4_auto_point3_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 3, 2), + SENSOR_ATTR_2(pwm4_auto_point4_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 3, 3), + SENSOR_ATTR_2(pwm4_auto_point4_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 3, 3), + SENSOR_ATTR_2(pwm4_auto_point5_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 3, 4), + SENSOR_ATTR_2(pwm4_auto_point5_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 3, 4), + SENSOR_ATTR_2(pwm4_auto_point6_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 3, 5), + SENSOR_ATTR_2(pwm4_auto_point6_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 3, 5), + SENSOR_ATTR_2(pwm4_auto_point7_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 3, 6), + SENSOR_ATTR_2(pwm4_auto_point7_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 3, 6), + + SENSOR_ATTR_2(pwm5_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 4, 0), + SENSOR_ATTR_2(pwm5_auto_point1_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 4, 0), + SENSOR_ATTR_2(pwm5_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 4, 1), + SENSOR_ATTR_2(pwm5_auto_point2_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 4, 1), + SENSOR_ATTR_2(pwm5_auto_point3_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 4, 2), + SENSOR_ATTR_2(pwm5_auto_point3_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 4, 2), + SENSOR_ATTR_2(pwm5_auto_point4_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 4, 3), + SENSOR_ATTR_2(pwm5_auto_point4_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 4, 3), + SENSOR_ATTR_2(pwm5_auto_point5_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 4, 4), + SENSOR_ATTR_2(pwm5_auto_point5_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 4, 4), + SENSOR_ATTR_2(pwm5_auto_point6_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 4, 5), + SENSOR_ATTR_2(pwm5_auto_point6_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 4, 5), + SENSOR_ATTR_2(pwm5_auto_point7_pwm, S_IWUSR | S_IRUGO, + show_auto_pwm, store_auto_pwm, 4, 6), + SENSOR_ATTR_2(pwm5_auto_point7_temp, S_IWUSR | S_IRUGO, + show_auto_temp, store_auto_temp, 4, 6), +}; + static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1969,6 +2968,15 @@ static void nct6775_device_remove_files(struct device *dev) for (i = 0; i < data->pwm_num; i++) sysfs_remove_group(&dev->kobj, &nct6775_group_pwm[i]); + for (i = 0; i < ARRAY_SIZE(sda_pwm_max); i++) + device_remove_file(dev, &sda_pwm_max[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(sda_pwm_step); i++) + device_remove_file(dev, &sda_pwm_step[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(sda_auto_pwm_arrays); i++) + device_remove_file(dev, &sda_auto_pwm_arrays[i].dev_attr); + for (i = 0; i < data->in_num; i++) sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]); @@ -2161,6 +3169,7 @@ static int nct6775_probe(struct platform_device *pdev) case nct6775: data->in_num = 9; data->pwm_num = 3; + data->auto_pwm_num = 6; data->has_fan_div = true; data->temp_fixed_num = 3; @@ -2168,6 +3177,9 @@ static int nct6775_probe(struct platform_device *pdev) data->fan_from_reg = fan_from_reg16; data->fan_from_reg_min = fan_from_reg8; + data->target_temp_mask = 0x7f; + data->tolerance_mask = 0x0f; + data->speed_tolerance_limit = 15; data->temp_label = nct6775_temp_label; data->temp_label_num = ARRAY_SIZE(nct6775_temp_label); @@ -2178,16 +3190,30 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_VIN = NCT6775_REG_IN; data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; data->REG_FAN = NCT6775_REG_FAN; data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; data->REG_FAN_MIN = NCT6775_REG_FAN_MIN; data->REG_FAN_PULSES = NCT6775_REG_FAN_PULSES; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME; data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[3] = NCT6775_REG_FAN_MAX_OUTPUT; + data->REG_PWM[4] = NCT6775_REG_FAN_STEP_OUTPUT; data->REG_PWM_READ = NCT6775_REG_PWM_READ; data->REG_PWM_MODE = NCT6775_REG_PWM_MODE; data->PWM_MODE_MASK = NCT6775_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; data->REG_ALARM = NCT6775_REG_ALARM; reg_temp = NCT6775_REG_TEMP; @@ -2202,6 +3228,7 @@ static int nct6775_probe(struct platform_device *pdev) case nct6776: data->in_num = 9; data->pwm_num = 3; + data->auto_pwm_num = 4; data->has_fan_div = false; data->temp_fixed_num = 3; @@ -2209,6 +3236,9 @@ static int nct6775_probe(struct platform_device *pdev) data->fan_from_reg = fan_from_reg13; data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; data->temp_label = nct6776_temp_label; data->temp_label_num = ARRAY_SIZE(nct6776_temp_label); @@ -2219,16 +3249,29 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_VIN = NCT6775_REG_IN; data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; data->REG_FAN = NCT6775_REG_FAN; data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; data->REG_FAN_PULSES = NCT6776_REG_FAN_PULSES; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; data->REG_PWM_READ = NCT6775_REG_PWM_READ; data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; data->REG_ALARM = NCT6775_REG_ALARM; reg_temp = NCT6775_REG_TEMP; @@ -2243,6 +3286,7 @@ static int nct6775_probe(struct platform_device *pdev) case nct6779: data->in_num = 15; data->pwm_num = 5; + data->auto_pwm_num = 4; data->has_fan_div = false; data->temp_fixed_num = 6; @@ -2250,6 +3294,9 @@ static int nct6775_probe(struct platform_device *pdev) data->fan_from_reg = fan_from_reg13; data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; data->temp_label = nct6779_temp_label; data->temp_label_num = ARRAY_SIZE(nct6779_temp_label); @@ -2260,16 +3307,29 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_VIN = NCT6779_REG_IN; data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; data->REG_FAN = NCT6779_REG_FAN; data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; data->REG_PWM_READ = NCT6775_REG_PWM_READ; data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; data->REG_ALARM = NCT6779_REG_ALARM; reg_temp = NCT6779_REG_TEMP; @@ -2484,6 +3544,31 @@ static int nct6775_probe(struct platform_device *pdev) err = sysfs_create_group(&dev->kobj, &nct6775_group_pwm[i]); if (err) goto exit_remove; + + if (data->REG_PWM[3]) { + err = device_create_file(dev, + &sda_pwm_max[i].dev_attr); + if (err) + goto exit_remove; + } + if (data->REG_PWM[4]) { + err = device_create_file(dev, + &sda_pwm_step[i].dev_attr); + if (err) + goto exit_remove; + } + } + for (i = 0; i < ARRAY_SIZE(sda_auto_pwm_arrays); i++) { + struct sensor_device_attribute_2 *attr = + &sda_auto_pwm_arrays[i]; + + if (!(data->has_pwm & (1 << attr->nr))) + continue; + if (attr->index > data->auto_pwm_num) + continue; + err = device_create_file(dev, &attr->dev_attr); + if (err) + goto exit_remove; } for (i = 0; i < data->in_num; i++) { @@ -2518,7 +3603,7 @@ static int nct6775_probe(struct platform_device *pdev) goto exit_remove; } err = device_create_file(dev, - &sda_fan_pulses[i].dev_attr); + &sda_fan_pulses[i].dev_attr); if (err) goto exit_remove; } -- cgit v1.2.3-70-g09d2 From bbd8decd4123648ddeba2be485bc7e1a3117bfe4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Dec 2012 09:08:29 -0800 Subject: hwmon: (nct6775) Add support for weighted fan control The NCT677X series support weighted fan control. In this mode, a secondary temperature source is used in addition to the primary temperature source to control fan speed. Add support for this feature. Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775 | 19 +++ drivers/hwmon/nct6775.c | 280 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 285 insertions(+), 14 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/nct6775 b/Documentation/hwmon/nct6775 index b4fe6bc4d37..3f5587e3909 100644 --- a/Documentation/hwmon/nct6775 +++ b/Documentation/hwmon/nct6775 @@ -92,6 +92,25 @@ Common fan control attributes pwm[1-5]_temp_sel Temperature source. Value is temperature sensor index. For example, select '1' for temp1_input. +pwm[1-5]_weight_temp_sel + Secondary temperature source. Value is temperature + sensor index. For example, select '1' for temp1_input. + Set to 0 to disable secondary temperature control. + +If secondary temperature functionality is enabled, it is controlled with the +following attributes. + +pwm[1-5]_weight_duty_step + Duty step size. +pwm[1-5]_weight_temp_step + Temperature step size. With each step over + temp_step_base, the value of weight_duty_step is added + to the current pwm value. +pwm[1-5]_weight_temp_step_base + Temperature at which secondary temperature control kicks + in. +pwm[1-5]_weight_temp_step_tol + Temperature step tolerance. Thermal Cruise mode (2) ----------------------- diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 47b1d8947e4..f80ff823c28 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -255,6 +255,17 @@ static const u16 NCT6775_REG_TEMP_SOURCE[ARRAY_SIZE(NCT6775_REG_TEMP)] = { static const u16 NCT6775_REG_TEMP_SEL[] = { 0x100, 0x200, 0x300, 0x800, 0x900 }; +static const u16 NCT6775_REG_WEIGHT_TEMP_SEL[] = { + 0x139, 0x239, 0x339, 0x839, 0x939 }; +static const u16 NCT6775_REG_WEIGHT_TEMP_STEP[] = { + 0x13a, 0x23a, 0x33a, 0x83a, 0x93a }; +static const u16 NCT6775_REG_WEIGHT_TEMP_STEP_TOL[] = { + 0x13b, 0x23b, 0x33b, 0x83b, 0x93b }; +static const u16 NCT6775_REG_WEIGHT_DUTY_STEP[] = { + 0x13c, 0x23c, 0x33c, 0x83c, 0x93c }; +static const u16 NCT6775_REG_WEIGHT_TEMP_BASE[] = { + 0x13d, 0x23d, 0x33d, 0x83d, 0x93d }; + static const u16 NCT6775_REG_TEMP_OFFSET[] = { 0x454, 0x455, 0x456 }; static const u16 NCT6775_REG_AUTO_TEMP[] = { @@ -323,6 +334,9 @@ static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0 }; static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642 }; static const u16 NCT6776_REG_FAN_PULSES[] = { 0x644, 0x645, 0x646, 0, 0 }; +static const u16 NCT6776_REG_WEIGHT_DUTY_BASE[] = { + 0x13e, 0x23e, 0x33e, 0x83e, 0x93e }; + static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { 0x18, 0x152, 0x252, 0x628, 0x629, 0x62A }; @@ -571,8 +585,9 @@ struct nct6775_data { const u8 *REG_PWM_MODE; const u8 *PWM_MODE_MASK; - const u16 *REG_PWM[5]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, - * [3]=pwm_max, [4]=pwm_step + const u16 *REG_PWM[7]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base */ const u16 *REG_PWM_READ; @@ -584,6 +599,9 @@ struct nct6775_data { const u16 *REG_TEMP_SOURCE; /* temp register sources */ const u16 *REG_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP[3]; /* 0=base, 1=tolerance, 2=step */ + const u16 *REG_TEMP_OFFSET; const u16 *REG_ALARM; @@ -625,8 +643,9 @@ struct nct6775_data { * 4->SmartFan III * 5->enhanced variable thermal cruise (SmartFan IV) */ - u8 pwm[5][5]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, - * [3]=pwm_max, [4]=pwm_step + u8 pwm[7][5]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base */ u8 target_temp[5]; @@ -645,6 +664,10 @@ struct nct6775_data { u8 auto_pwm[5][7]; u8 auto_temp[5][7]; u8 pwm_temp_sel[5]; + u8 pwm_weight_temp_sel[5]; + u8 weight_temp[3][5]; /* 0->temp_step, 1->temp_step_tol, + * 2->temp_base + */ u8 vid; u8 vrm; @@ -972,6 +995,19 @@ static void nct6775_update_pwm(struct device *dev) /* If fan can stop, report floor as 0 */ if (reg & 0x80) data->pwm[2][i] = 0; + + reg = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[i]); + data->pwm_weight_temp_sel[i] = reg & 0x1f; + /* If weight is disabled, report weight source as 0 */ + if (j == 1 && !(reg & 0x80)) + data->pwm_weight_temp_sel[i] = 0; + + /* Weight temp data */ + for (j = 0; j < 3; j++) { + data->weight_temp[j][i] + = nct6775_read_value(data, + data->REG_WEIGHT_TEMP[j][i]); + } } } @@ -1938,9 +1974,9 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, int nr = sattr->nr; int index = sattr->index; unsigned long val; - int minval[5] = { 0, 1, 1, data->pwm[2][nr], 0 }; - int maxval[5] - = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255 }; + int minval[7] = { 0, 1, 1, data->pwm[2][nr], 0, 0, 0 }; + int maxval[7] + = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255, 255, 255 }; int err; u8 reg; @@ -2078,13 +2114,9 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, } static ssize_t -show_pwm_temp_sel(struct device *dev, struct device_attribute *attr, char *buf) +show_pwm_temp_sel_common(struct nct6775_data *data, char *buf, int src) { - struct nct6775_data *data = nct6775_update_device(dev); - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - int i, src, sel = 0; - - src = data->pwm_temp_sel[sattr->index]; + int i, sel = 0; for (i = 0; i < NUM_TEMP; i++) { if (!(data->have_temp & (1 << i))) @@ -2098,6 +2130,16 @@ show_pwm_temp_sel(struct device *dev, struct device_attribute *attr, char *buf) return sprintf(buf, "%d\n", sel); } +static ssize_t +show_pwm_temp_sel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int index = sattr->index; + + return show_pwm_temp_sel_common(data, buf, data->pwm_temp_sel[index]); +} + static ssize_t store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -2128,6 +2170,56 @@ store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, return count; } +static ssize_t +show_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int index = sattr->index; + + return show_pwm_temp_sel_common(data, buf, + data->pwm_weight_temp_sel[index]); +} + +static ssize_t +store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err, reg, src; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > NUM_TEMP) + return -EINVAL; + if (val && (!(data->have_temp & (1 << (val - 1))) || + !data->temp_src[val - 1])) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val) { + src = data->temp_src[val - 1]; + data->pwm_weight_temp_sel[nr] = src; + reg = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + reg &= 0xe0; + reg |= (src | 0x80); + nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + } else { + data->pwm_weight_temp_sel[nr] = 0; + reg = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + reg &= 0x7f; + nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + } + mutex_unlock(&data->update_lock); + + return count; +} + static ssize_t show_target_temp(struct device *dev, struct device_attribute *attr, char *buf) { @@ -2380,6 +2472,115 @@ static SENSOR_DEVICE_ATTR(fan5_tolerance, S_IWUSR | S_IRUGO, /* Smart Fan registers */ +static ssize_t +show_weight_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + return sprintf(buf, "%d\n", data->weight_temp[index][nr] * 1000); +} + +static ssize_t +store_weight_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255); + + mutex_lock(&data->update_lock); + data->weight_temp[index][nr] = val; + nct6775_write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_weight_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, + 0); +static SENSOR_DEVICE_ATTR(pwm2_weight_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, + 1); +static SENSOR_DEVICE_ATTR(pwm3_weight_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, + 2); +static SENSOR_DEVICE_ATTR(pwm4_weight_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, + 3); +static SENSOR_DEVICE_ATTR(pwm5_weight_temp_sel, S_IWUSR | S_IRUGO, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, + 4); + +static SENSOR_DEVICE_ATTR_2(pwm1_weight_temp_step, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_weight_temp_step, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_weight_temp_step, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm4_weight_temp_step, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 3, 0); +static SENSOR_DEVICE_ATTR_2(pwm5_weight_temp_step, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 4, 0); + +static SENSOR_DEVICE_ATTR_2(pwm1_weight_temp_step_tol, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_weight_temp_step_tol, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_weight_temp_step_tol, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm4_weight_temp_step_tol, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 3, 1); +static SENSOR_DEVICE_ATTR_2(pwm5_weight_temp_step_tol, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 4, 1); + +static SENSOR_DEVICE_ATTR_2(pwm1_weight_temp_step_base, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 0, 2); +static SENSOR_DEVICE_ATTR_2(pwm2_weight_temp_step_base, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 1, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_weight_temp_step_base, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm4_weight_temp_step_base, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 3, 2); +static SENSOR_DEVICE_ATTR_2(pwm5_weight_temp_step_base, S_IWUSR | S_IRUGO, + show_weight_temp, store_weight_temp, 4, 2); + +static SENSOR_DEVICE_ATTR_2(pwm1_weight_duty_step, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 0, 5); +static SENSOR_DEVICE_ATTR_2(pwm2_weight_duty_step, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 1, 5); +static SENSOR_DEVICE_ATTR_2(pwm3_weight_duty_step, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 2, 5); +static SENSOR_DEVICE_ATTR_2(pwm4_weight_duty_step, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 3, 5); +static SENSOR_DEVICE_ATTR_2(pwm5_weight_duty_step, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 4, 5); + +/* duty_base is not supported on all chips */ +static struct sensor_device_attribute_2 sda_weight_duty_base[] = { + SENSOR_ATTR_2(pwm1_weight_duty_base, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 0, 6), + SENSOR_ATTR_2(pwm2_weight_duty_base, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 1, 6), + SENSOR_ATTR_2(pwm3_weight_duty_base, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 2, 6), + SENSOR_ATTR_2(pwm4_weight_duty_base, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 3, 6), + SENSOR_ATTR_2(pwm5_weight_duty_base, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 4, 6), +}; + static ssize_t show_fan_time(struct device *dev, struct device_attribute *attr, char *buf) { @@ -2526,7 +2727,7 @@ static struct sensor_device_attribute_2 sda_pwm_step[] = { SENSOR_ATTR_2(pwm5_step, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 4, 4), }; -static struct attribute *nct6775_attributes_pwm[5][15] = { +static struct attribute *nct6775_attributes_pwm[5][20] = { { &sensor_dev_attr_pwm1.dev_attr.attr, &sensor_dev_attr_pwm1_mode.dev_attr.attr, @@ -2542,6 +2743,11 @@ static struct attribute *nct6775_attributes_pwm[5][15] = { &sensor_dev_attr_pwm1_step_down_time.dev_attr.attr, &sensor_dev_attr_pwm1_start.dev_attr.attr, &sensor_dev_attr_pwm1_floor.dev_attr.attr, + &sensor_dev_attr_pwm1_weight_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm1_weight_temp_step.dev_attr.attr, + &sensor_dev_attr_pwm1_weight_temp_step_tol.dev_attr.attr, + &sensor_dev_attr_pwm1_weight_temp_step_base.dev_attr.attr, + &sensor_dev_attr_pwm1_weight_duty_step.dev_attr.attr, NULL }, { @@ -2559,6 +2765,11 @@ static struct attribute *nct6775_attributes_pwm[5][15] = { &sensor_dev_attr_pwm2_step_down_time.dev_attr.attr, &sensor_dev_attr_pwm2_start.dev_attr.attr, &sensor_dev_attr_pwm2_floor.dev_attr.attr, + &sensor_dev_attr_pwm2_weight_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm2_weight_temp_step.dev_attr.attr, + &sensor_dev_attr_pwm2_weight_temp_step_tol.dev_attr.attr, + &sensor_dev_attr_pwm2_weight_temp_step_base.dev_attr.attr, + &sensor_dev_attr_pwm2_weight_duty_step.dev_attr.attr, NULL }, { @@ -2576,6 +2787,11 @@ static struct attribute *nct6775_attributes_pwm[5][15] = { &sensor_dev_attr_pwm3_step_down_time.dev_attr.attr, &sensor_dev_attr_pwm3_start.dev_attr.attr, &sensor_dev_attr_pwm3_floor.dev_attr.attr, + &sensor_dev_attr_pwm3_weight_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm3_weight_temp_step.dev_attr.attr, + &sensor_dev_attr_pwm3_weight_temp_step_tol.dev_attr.attr, + &sensor_dev_attr_pwm3_weight_temp_step_base.dev_attr.attr, + &sensor_dev_attr_pwm3_weight_duty_step.dev_attr.attr, NULL }, { @@ -2593,6 +2809,11 @@ static struct attribute *nct6775_attributes_pwm[5][15] = { &sensor_dev_attr_pwm4_step_down_time.dev_attr.attr, &sensor_dev_attr_pwm4_start.dev_attr.attr, &sensor_dev_attr_pwm4_floor.dev_attr.attr, + &sensor_dev_attr_pwm4_weight_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm4_weight_temp_step.dev_attr.attr, + &sensor_dev_attr_pwm4_weight_temp_step_tol.dev_attr.attr, + &sensor_dev_attr_pwm4_weight_temp_step_base.dev_attr.attr, + &sensor_dev_attr_pwm4_weight_duty_step.dev_attr.attr, NULL }, { @@ -2610,6 +2831,11 @@ static struct attribute *nct6775_attributes_pwm[5][15] = { &sensor_dev_attr_pwm5_step_down_time.dev_attr.attr, &sensor_dev_attr_pwm5_start.dev_attr.attr, &sensor_dev_attr_pwm5_floor.dev_attr.attr, + &sensor_dev_attr_pwm5_weight_temp_sel.dev_attr.attr, + &sensor_dev_attr_pwm5_weight_temp_step.dev_attr.attr, + &sensor_dev_attr_pwm5_weight_temp_step_tol.dev_attr.attr, + &sensor_dev_attr_pwm5_weight_temp_step_base.dev_attr.attr, + &sensor_dev_attr_pwm5_weight_duty_step.dev_attr.attr, NULL }, }; @@ -2974,6 +3200,9 @@ static void nct6775_device_remove_files(struct device *dev) for (i = 0; i < ARRAY_SIZE(sda_pwm_step); i++) device_remove_file(dev, &sda_pwm_step[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(sda_weight_duty_base); i++) + device_remove_file(dev, &sda_weight_duty_base[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(sda_auto_pwm_arrays); i++) device_remove_file(dev, &sda_auto_pwm_arrays[i].dev_attr); @@ -3203,6 +3432,7 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; data->REG_PWM[3] = NCT6775_REG_FAN_MAX_OUTPUT; data->REG_PWM[4] = NCT6775_REG_FAN_STEP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; data->REG_PWM_READ = NCT6775_REG_PWM_READ; data->REG_PWM_MODE = NCT6775_REG_PWM_MODE; data->PWM_MODE_MASK = NCT6775_PWM_MODE_MASK; @@ -3214,6 +3444,10 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6775_REG_ALARM; reg_temp = NCT6775_REG_TEMP; @@ -3261,6 +3495,8 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_PWM[0] = NCT6775_REG_PWM; data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; data->REG_PWM_READ = NCT6775_REG_PWM_READ; data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; @@ -3272,6 +3508,10 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6775_REG_ALARM; reg_temp = NCT6775_REG_TEMP; @@ -3319,6 +3559,8 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_PWM[0] = NCT6775_REG_PWM; data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; data->REG_PWM_READ = NCT6775_REG_PWM_READ; data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; @@ -3330,6 +3572,10 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6779_REG_ALARM; reg_temp = NCT6779_REG_TEMP; @@ -3557,6 +3803,12 @@ static int nct6775_probe(struct platform_device *pdev) if (err) goto exit_remove; } + if (data->REG_PWM[6]) { + err = device_create_file(dev, + &sda_weight_duty_base[i].dev_attr); + if (err) + goto exit_remove; + } } for (i = 0; i < ARRAY_SIZE(sda_auto_pwm_arrays); i++) { struct sensor_device_attribute_2 *attr = -- cgit v1.2.3-70-g09d2 From 8c770f3a472fa74ad86871f42f6e991951ddeed2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 22 Feb 2013 07:52:39 -0800 Subject: hwmon: (pmbus/ltc2978) Clean up documentation Some sysfs attributes are only supported on LTC2978 and not on LTC3880. Update documentation to reflect which attributes are supported for which chips. Output current attributes supported on LTC3880 were described as providing input current values. Fix text to reflect that the attributes provide output current values. "reset history" attribute descriptions were misleading and seemed to imply that all history was reset when writing a single attribute. Replace with more accurate text. Replace 'internal temperature" with "chip temperature". Temperature limits not only apply to the chip temperature, but also to external temperatures on LTC3880, so remove the word "chip" from the attribute description. Signed-off-by: Guenter Roeck --- Documentation/hwmon/ltc2978 | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/ltc2978 b/Documentation/hwmon/ltc2978 index e4d75c606c9..dcb10be36e7 100644 --- a/Documentation/hwmon/ltc2978 +++ b/Documentation/hwmon/ltc2978 @@ -41,17 +41,16 @@ Sysfs attributes in1_label "vin" in1_input Measured input voltage. in1_min Minimum input voltage. -in1_max Maximum input voltage. -in1_lcrit Critical minimum input voltage. +in1_max Maximum input voltage. LTC2978 only. +in1_lcrit Critical minimum input voltage. LTC2978 only. in1_crit Critical maximum input voltage. in1_min_alarm Input voltage low alarm. -in1_max_alarm Input voltage high alarm. -in1_lcrit_alarm Input voltage critical low alarm. +in1_max_alarm Input voltage high alarm. LTC2978 only. +in1_lcrit_alarm Input voltage critical low alarm. LTC2978 only. in1_crit_alarm Input voltage critical high alarm. in1_lowest Lowest input voltage. LTC2978 only. in1_highest Highest input voltage. -in1_reset_history Reset history. Writing into this attribute will reset - history for all attributes. +in1_reset_history Reset input voltage history. in[2-9]_label "vout[1-8]". Channels 3 to 9 on LTC2978 only. in[2-9]_input Measured output voltage. @@ -65,27 +64,25 @@ in[2-9]_lcrit_alarm Output voltage critical low alarm. in[2-9]_crit_alarm Output voltage critical high alarm. in[2-9]_lowest Lowest output voltage. LTC2978 only. in[2-9]_highest Lowest output voltage. -in[2-9]_reset_history Reset history. Writing into this attribute will reset - history for all attributes. +in[2-9]_reset_history Reset output voltage history. temp[1-3]_input Measured temperature. On LTC2978, only one temperature measurement is - supported and reflects the internal temperature. + supported and reports the chip temperature. On LTC3880, temp1 and temp2 report external - temperatures, and temp3 reports the internal + temperatures, and temp3 reports the chip temperature. -temp[1-3]_min Mimimum temperature. +temp[1-3]_min Mimimum temperature. LTC2978 only. temp[1-3]_max Maximum temperature. temp[1-3]_lcrit Critical low temperature. temp[1-3]_crit Critical high temperature. -temp[1-3]_min_alarm Chip temperature low alarm. -temp[1-3]_max_alarm Chip temperature high alarm. -temp[1-3]_lcrit_alarm Chip temperature critical low alarm. -temp[1-3]_crit_alarm Chip temperature critical high alarm. +temp[1-3]_min_alarm Temperature low alarm. LTC2978 only. +temp[1-3]_max_alarm Temperature high alarm. +temp[1-3]_lcrit_alarm Temperature critical low alarm. +temp[1-3]_crit_alarm Temperature critical high alarm. temp[1-3]_lowest Lowest measured temperature. LTC2978 only. temp[1-3]_highest Highest measured temperature. -temp[1-3]_reset_history Reset history. Writing into this attribute will reset - history for all attributes. +temp[1-3]_reset_history Reset temperature history. power[1-2]_label "pout[1-2]". LTC3880 only. power[1-2]_input Measured power. @@ -96,8 +93,8 @@ curr1_max Maximum input current. curr1_max_alarm Input current high alarm. curr[2-3]_label "iout[1-2]". LTC3880 only. -curr[2-3]_input Measured input current. -curr[2-3]_max Maximum input current. -curr[2-3]_crit Critical input current. -curr[2-3]_max_alarm Input current high alarm. -curr[2-3]_crit_alarm Input current critical high alarm. +curr[2-3]_input Measured output current. +curr[2-3]_max Maximum output current. +curr[2-3]_crit Critical output current. +curr[2-3]_max_alarm Output current high alarm. +curr[2-3]_crit_alarm Output current critical high alarm. -- cgit v1.2.3-70-g09d2 From fd9175d2f603509e7ddf14e7b60633f6e88fb0e7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 27 Jan 2013 09:24:28 -0800 Subject: hwmon: (pmbus/ltc2978) Add support for LTC2974 and LTC3883 Signed-off-by: Guenter Roeck --- Documentation/hwmon/ltc2978 | 134 +++++++++++++++++++++++-------------- drivers/hwmon/pmbus/Kconfig | 4 +- drivers/hwmon/pmbus/ltc2978.c | 149 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 218 insertions(+), 69 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/ltc2978 b/Documentation/hwmon/ltc2978 index dcb10be36e7..dc0d08c6130 100644 --- a/Documentation/hwmon/ltc2978 +++ b/Documentation/hwmon/ltc2978 @@ -2,6 +2,10 @@ Kernel driver ltc2978 ===================== Supported chips: + * Linear Technology LTC2974 + Prefix: 'ltc2974' + Addresses scanned: - + Datasheet: http://www.linear.com/product/ltc2974 * Linear Technology LTC2978 Prefix: 'ltc2978' Addresses scanned: - @@ -10,6 +14,10 @@ Supported chips: Prefix: 'ltc3880' Addresses scanned: - Datasheet: http://www.linear.com/product/ltc3880 + * Linear Technology LTC3883 + Prefix: 'ltc3883' + Addresses scanned: - + Datasheet: http://www.linear.com/product/ltc3883 Author: Guenter Roeck @@ -17,9 +25,9 @@ Author: Guenter Roeck Description ----------- -The LTC2978 is an octal power supply monitor, supervisor, sequencer and -margin controller. The LTC3880 is a dual, PolyPhase DC/DC synchronous -step-down switching regulator controller. +LTC2974 is a quad digital power supply manager. LTC2978 is an octal power supply +monitor. LTC3880 is a dual output poly-phase step-down DC/DC controller. LTC3883 +is a single phase step-down DC/DC controller. Usage Notes @@ -41,60 +49,90 @@ Sysfs attributes in1_label "vin" in1_input Measured input voltage. in1_min Minimum input voltage. -in1_max Maximum input voltage. LTC2978 only. -in1_lcrit Critical minimum input voltage. LTC2978 only. +in1_max Maximum input voltage. LTC2974 and LTC2978 only. +in1_lcrit Critical minimum input voltage. LTC2974 and LTC2978 + only. in1_crit Critical maximum input voltage. in1_min_alarm Input voltage low alarm. -in1_max_alarm Input voltage high alarm. LTC2978 only. -in1_lcrit_alarm Input voltage critical low alarm. LTC2978 only. +in1_max_alarm Input voltage high alarm. LTC2974 and LTC2978 only. +in1_lcrit_alarm Input voltage critical low alarm. LTC2974 and LTC2978 + only. in1_crit_alarm Input voltage critical high alarm. -in1_lowest Lowest input voltage. LTC2978 only. +in1_lowest Lowest input voltage. LTC2974 and LTC2978 only. in1_highest Highest input voltage. in1_reset_history Reset input voltage history. -in[2-9]_label "vout[1-8]". Channels 3 to 9 on LTC2978 only. -in[2-9]_input Measured output voltage. -in[2-9]_min Minimum output voltage. -in[2-9]_max Maximum output voltage. -in[2-9]_lcrit Critical minimum output voltage. -in[2-9]_crit Critical maximum output voltage. -in[2-9]_min_alarm Output voltage low alarm. -in[2-9]_max_alarm Output voltage high alarm. -in[2-9]_lcrit_alarm Output voltage critical low alarm. -in[2-9]_crit_alarm Output voltage critical high alarm. -in[2-9]_lowest Lowest output voltage. LTC2978 only. -in[2-9]_highest Lowest output voltage. -in[2-9]_reset_history Reset output voltage history. - -temp[1-3]_input Measured temperature. +in[N]_label "vout[1-8]". + LTC2974: N=2-5 + LTC2978: N=2-9 + LTC3880: N=2-3 + LTC3883: N=2 +in[N]_input Measured output voltage. +in[N]_min Minimum output voltage. +in[N]_max Maximum output voltage. +in[N]_lcrit Critical minimum output voltage. +in[N]_crit Critical maximum output voltage. +in[N]_min_alarm Output voltage low alarm. +in[N]_max_alarm Output voltage high alarm. +in[N]_lcrit_alarm Output voltage critical low alarm. +in[N]_crit_alarm Output voltage critical high alarm. +in[N]_lowest Lowest output voltage. LTC2974 and LTC2978 only. +in[N]_highest Highest output voltage. +in[N]_reset_history Reset output voltage history. + +temp[N]_input Measured temperature. + On LTC2974, temp[1-4] report external temperatures, + and temp5 reports the chip temperature. On LTC2978, only one temperature measurement is supported and reports the chip temperature. On LTC3880, temp1 and temp2 report external - temperatures, and temp3 reports the chip - temperature. -temp[1-3]_min Mimimum temperature. LTC2978 only. -temp[1-3]_max Maximum temperature. -temp[1-3]_lcrit Critical low temperature. -temp[1-3]_crit Critical high temperature. -temp[1-3]_min_alarm Temperature low alarm. LTC2978 only. -temp[1-3]_max_alarm Temperature high alarm. -temp[1-3]_lcrit_alarm Temperature critical low alarm. -temp[1-3]_crit_alarm Temperature critical high alarm. -temp[1-3]_lowest Lowest measured temperature. LTC2978 only. -temp[1-3]_highest Highest measured temperature. -temp[1-3]_reset_history Reset temperature history. - -power[1-2]_label "pout[1-2]". LTC3880 only. -power[1-2]_input Measured power. - -curr1_label "iin". LTC3880 only. + temperatures, and temp3 reports the chip temperature. + On LTC3883, temp1 reports an external temperature, + and temp2 reports the chip temperature. +temp[N]_min Mimimum temperature. LTC2974 and LTC2978 only. +temp[N]_max Maximum temperature. +temp[N]_lcrit Critical low temperature. +temp[N]_crit Critical high temperature. +temp[N]_min_alarm Temperature low alarm. LTC2974 and LTC2978 only. +temp[N]_max_alarm Temperature high alarm. +temp[N]_lcrit_alarm Temperature critical low alarm. +temp[N]_crit_alarm Temperature critical high alarm. +temp[N]_lowest Lowest measured temperature. LTC2974 and LTC2978 only. + Not supported for chip temperature sensor on LTC2974. +temp[N]_highest Highest measured temperature. Not supported for chip + temperature sensor on LTC2974. +temp[N]_reset_history Reset temperature history. Not supported for chip + temperature sensor on LTC2974. + +power1_label "pin". LTC3883 only. +power1_input Measured input power. + +power[N]_label "pout[1-4]". + LTC2974: N=1-4 + LTC2978: Not supported + LTC3880: N=1-2 + LTC3883: N=2 +power[N]_input Measured output power. + +curr1_label "iin". LTC3880 and LTC3883 only. curr1_input Measured input current. curr1_max Maximum input current. curr1_max_alarm Input current high alarm. - -curr[2-3]_label "iout[1-2]". LTC3880 only. -curr[2-3]_input Measured output current. -curr[2-3]_max Maximum output current. -curr[2-3]_crit Critical output current. -curr[2-3]_max_alarm Output current high alarm. -curr[2-3]_crit_alarm Output current critical high alarm. +curr1_highest Highest input current. LTC3883 only. +curr1_reset_history Reset input current history. LTC3883 only. + +curr[N]_label "iout[1-4]". + LTC2974: N=1-4 + LTC2978: not supported + LTC3880: N=2-3 + LTC3883: N=2 +curr[N]_input Measured output current. +curr[N]_max Maximum output current. +curr[N]_crit Critical high output current. +curr[N]_lcrit Critical low output current. LTC2974 only. +curr[N]_max_alarm Output current high alarm. +curr[N]_crit_alarm Output current critical high alarm. +curr[N]_lcrit_alarm Output current critical low alarm. LTC2974 only. +curr[N]_lowest Lowest output current. LTC2974 only. +curr[N]_highest Highest output current. +curr[N]_reset_history Reset output current history. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 4f9eb0af522..b1adad60d6a 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -48,11 +48,11 @@ config SENSORS_LM25066 be called lm25066. config SENSORS_LTC2978 - tristate "Linear Technologies LTC2978 and LTC3880" + tristate "Linear Technologies LTC2974, LTC2978, LTC3880, and LTC3883" default n help If you say yes here you get hardware monitoring support for Linear - Technology LTC2978 and LTC3880. + Technology LTC2974, LTC2978, LTC3880, and LTC3883. This driver can also be built as a module. If so, the module will be called ltc2978. diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 945f7eced40..586a89ef9e0 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -1,7 +1,8 @@ /* - * Hardware monitoring driver for LTC2978 and LTC3880 + * Hardware monitoring driver for LTC2974, LTC2978, LTC3880, and LTC3883 * * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 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 @@ -26,31 +27,43 @@ #include #include "pmbus.h" -enum chips { ltc2978, ltc3880 }; +enum chips { ltc2974, ltc2978, ltc3880, ltc3883 }; -/* LTC2978 and LTC3880 */ +/* Common for all chips */ #define LTC2978_MFR_VOUT_PEAK 0xdd #define LTC2978_MFR_VIN_PEAK 0xde #define LTC2978_MFR_TEMPERATURE_PEAK 0xdf #define LTC2978_MFR_SPECIAL_ID 0xe7 -/* LTC2978 only */ +/* LTC2974 and LTC2978 */ #define LTC2978_MFR_VOUT_MIN 0xfb #define LTC2978_MFR_VIN_MIN 0xfc #define LTC2978_MFR_TEMPERATURE_MIN 0xfd -/* LTC3880 only */ +/* LTC2974 only */ +#define LTC2974_MFR_IOUT_PEAK 0xd7 +#define LTC2974_MFR_IOUT_MIN 0xd8 + +/* LTC3880 and LTC3883 */ #define LTC3880_MFR_IOUT_PEAK 0xd7 #define LTC3880_MFR_CLEAR_PEAKS 0xe3 #define LTC3880_MFR_TEMPERATURE2_PEAK 0xf4 +/* LTC3883 only */ +#define LTC3883_MFR_IIN_PEAK 0xe1 + +#define LTC2974_ID 0x0212 #define LTC2978_ID_REV1 0x0121 #define LTC2978_ID_REV2 0x0122 #define LTC3880_ID 0x4000 #define LTC3880_ID_MASK 0xff00 +#define LTC3883_ID 0x4300 +#define LTC3883_ID_MASK 0xff00 +#define LTC2974_NUM_PAGES 4 #define LTC2978_NUM_PAGES 8 #define LTC3880_NUM_PAGES 2 +#define LTC3883_NUM_PAGES 1 /* * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which @@ -63,9 +76,10 @@ enum chips { ltc2978, ltc3880 }; struct ltc2978_data { enum chips id; u16 vin_min, vin_max; - u16 temp_min, temp_max[LTC3880_NUM_PAGES]; + u16 temp_min[LTC2974_NUM_PAGES], temp_max[LTC2974_NUM_PAGES]; u16 vout_min[LTC2978_NUM_PAGES], vout_max[LTC2978_NUM_PAGES]; - u16 iout_max[LTC3880_NUM_PAGES]; + u16 iout_min[LTC2974_NUM_PAGES], iout_max[LTC2974_NUM_PAGES]; + u16 iin_max; u16 temp2_max; struct pmbus_driver_info info; }; @@ -171,9 +185,9 @@ static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg) LTC2978_MFR_TEMPERATURE_MIN); if (ret >= 0) { if (lin11_to_val(ret) - < lin11_to_val(data->temp_min)) - data->temp_min = ret; - ret = data->temp_min; + < lin11_to_val(data->temp_min[page])) + data->temp_min[page] = ret; + ret = data->temp_min[page]; } break; case PMBUS_VIRT_READ_IOUT_MAX: @@ -189,6 +203,41 @@ static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg) return ret; } +static int ltc2974_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, LTC2974_MFR_IOUT_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) + > lin11_to_val(data->iout_max[page])) + data->iout_max[page] = ret; + ret = data->iout_max[page]; + } + break; + case PMBUS_VIRT_READ_IOUT_MIN: + ret = pmbus_read_word_data(client, page, LTC2974_MFR_IOUT_MIN); + if (ret >= 0) { + if (lin11_to_val(ret) + < lin11_to_val(data->iout_min[page])) + data->iout_min[page] = ret; + ret = data->iout_min[page]; + } + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data(client, page, reg); + break; + } + return ret; +} + static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg) { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); @@ -230,15 +279,41 @@ static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg) return ret; } +static int ltc3883_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IIN_MAX: + ret = pmbus_read_word_data(client, page, LTC3883_MFR_IIN_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) + > lin11_to_val(data->iin_max)) + data->iin_max = ret; + ret = data->iin_max; + } + break; + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = 0; + break; + default: + ret = ltc3880_read_word_data(client, page, reg); + break; + } + return ret; +} + static int ltc2978_clear_peaks(struct i2c_client *client, int page, enum chips id) { int ret; - if (id == ltc2978) - ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); - else + if (id == ltc3880 || id == ltc3883) ret = pmbus_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS); + else + ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); return ret; } @@ -251,8 +326,13 @@ static int ltc2978_write_word_data(struct i2c_client *client, int page, int ret; switch (reg) { + case PMBUS_VIRT_RESET_IIN_HISTORY: + data->iin_max = 0x7c00; + ret = ltc2978_clear_peaks(client, page, data->id); + break; case PMBUS_VIRT_RESET_IOUT_HISTORY: data->iout_max[page] = 0x7c00; + data->iout_min[page] = 0xfbff; ret = ltc2978_clear_peaks(client, page, data->id); break; case PMBUS_VIRT_RESET_TEMP2_HISTORY: @@ -270,7 +350,7 @@ static int ltc2978_write_word_data(struct i2c_client *client, int page, ret = ltc2978_clear_peaks(client, page, data->id); break; case PMBUS_VIRT_RESET_TEMP_HISTORY: - data->temp_min = 0x7bff; + data->temp_min[page] = 0x7bff; data->temp_max[page] = 0x7c00; ret = ltc2978_clear_peaks(client, page, data->id); break; @@ -282,8 +362,10 @@ static int ltc2978_write_word_data(struct i2c_client *client, int page, } static const struct i2c_device_id ltc2978_id[] = { + {"ltc2974", ltc2974}, {"ltc2978", ltc2978}, {"ltc3880", ltc3880}, + {"ltc3883", ltc3883}, {} }; MODULE_DEVICE_TABLE(i2c, ltc2978_id); @@ -308,10 +390,14 @@ static int ltc2978_probe(struct i2c_client *client, if (chip_id < 0) return chip_id; - if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) { + if (chip_id == LTC2974_ID) { + data->id = ltc2974; + } else if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) { data->id = ltc2978; } else if ((chip_id & LTC3880_ID_MASK) == LTC3880_ID) { data->id = ltc3880; + } else if ((chip_id & LTC3883_ID_MASK) == LTC3883_ID) { + data->id = ltc3883; } else { dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id); return -ENODEV; @@ -329,12 +415,29 @@ static int ltc2978_probe(struct i2c_client *client, data->vin_max = 0x7c00; for (i = 0; i < ARRAY_SIZE(data->vout_min); i++) data->vout_min[i] = 0xffff; - data->temp_min = 0x7bff; + for (i = 0; i < ARRAY_SIZE(data->iout_min); i++) + data->iout_min[i] = 0xfbff; + for (i = 0; i < ARRAY_SIZE(data->iout_max); i++) + data->iout_max[i] = 0x7c00; + for (i = 0; i < ARRAY_SIZE(data->temp_min); i++) + data->temp_min[i] = 0x7bff; for (i = 0; i < ARRAY_SIZE(data->temp_max); i++) data->temp_max[i] = 0x7c00; data->temp2_max = 0x7c00; switch (data->id) { + case ltc2974: + info->read_word_data = ltc2974_read_word_data; + info->pages = LTC2974_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP2; + for (i = 0; i < info->pages; i++) { + info->func[i] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + } + break; case ltc2978: info->read_word_data = ltc2978_read_word_data; info->pages = LTC2978_NUM_PAGES; @@ -359,8 +462,16 @@ static int ltc2978_probe(struct i2c_client *client, | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - data->iout_max[0] = 0x7c00; - data->iout_max[1] = 0x7c00; + break; + case ltc3883: + info->read_word_data = ltc3883_read_word_data; + info->pages = LTC3883_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; break; default: return -ENODEV; @@ -381,5 +492,5 @@ static struct i2c_driver ltc2978_driver = { module_i2c_driver(ltc2978_driver); MODULE_AUTHOR("Guenter Roeck"); -MODULE_DESCRIPTION("PMBus driver for LTC2978 and LTC3880"); +MODULE_DESCRIPTION("PMBus driver for LTC2974, LTC2978, LTC3880, and LTC3883"); MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From a7c69118bdc8647db0e15defa9e399df21a48890 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 6 Feb 2013 09:55:37 -0800 Subject: hwmon: (pmbus/lm25066) Report VAUX as vmon So far the driver reported the voltage on VAUX as "vout2". This was not entirely appropriate as it is not an output voltage, and complicates the code. Use the new virtual "VMON" register set and report the voltage as "vmon" instead. Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm25066 | 16 +++++----- drivers/hwmon/pmbus/lm25066.c | 73 +++++++++++++------------------------------ 2 files changed, 29 insertions(+), 60 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/lm25066 b/Documentation/hwmon/lm25066 index 26025e419d3..2bc5ba6aee6 100644 --- a/Documentation/hwmon/lm25066 +++ b/Documentation/hwmon/lm25066 @@ -60,14 +60,14 @@ in1_max Maximum input voltage. in1_min_alarm Input voltage low alarm. in1_max_alarm Input voltage high alarm. -in2_label "vout1" -in2_input Measured output voltage. -in2_average Average measured output voltage. -in2_min Minimum output voltage. -in2_min_alarm Output voltage low alarm. - -in3_label "vout2" -in3_input Measured voltage on vaux pin +in2_label "vmon" +in2_input Measured voltage on VAUX pin + +in3_label "vout1" +in3_input Measured output voltage. +in3_average Average measured output voltage. +in3_min Minimum output voltage. +in3_min_alarm Output voltage low alarm. curr1_label "iin" curr1_input Measured input current. diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index c299392716a..5489d70015c 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -2,6 +2,7 @@ * Hardware monitoring driver for LM25066 / LM5064 / LM5066 * * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 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 @@ -56,42 +57,27 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) const struct lm25066_data *data = to_lm25066_data(info); int ret; - if (page > 1) - return -ENXIO; - - /* Map READ_VAUX into READ_VOUT register on page 1 */ - if (page == 1) { - switch (reg) { - case PMBUS_READ_VOUT: - ret = pmbus_read_word_data(client, 0, - LM25066_READ_VAUX); - if (ret < 0) - break; - /* Adjust returned value to match VOUT coefficients */ - switch (data->id) { - case lm25066: - /* VOUT: 4.54 mV VAUX: 283.2 uV LSB */ - ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); - break; - case lm5064: - /* VOUT: 4.53 mV VAUX: 700 uV LSB */ - ret = DIV_ROUND_CLOSEST(ret * 70, 453); - break; - case lm5066: - /* VOUT: 2.18 mV VAUX: 725 uV LSB */ - ret = DIV_ROUND_CLOSEST(ret * 725, 2180); - break; - } + switch (reg) { + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, 0, LM25066_READ_VAUX); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + switch (data->id) { + case lm25066: + /* VIN: 4.54 mV VAUX: 283.2 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); + break; + case lm5064: + /* VIN: 4.53 mV VAUX: 700 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 70, 453); break; - default: - /* No other valid registers on page 1 */ - ret = -ENXIO; + case lm5066: + /* VIN: 2.18 mV VAUX: 725 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 725, 2180); break; } - goto done; - } - - switch (reg) { + break; case PMBUS_READ_IIN: ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_IIN); break; @@ -128,7 +114,6 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) ret = -ENODATA; break; } -done: return ret; } @@ -137,9 +122,6 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, { int ret; - if (page > 1) - return -ENXIO; - switch (reg) { case PMBUS_IIN_OC_WARN_LIMIT: ret = pmbus_write_word_data(client, 0, @@ -161,17 +143,6 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, return ret; } -static int lm25066_write_byte(struct i2c_client *client, int page, u8 value) -{ - if (page > 1) - return -ENXIO; - - if (page <= 0) - return pmbus_write_byte(client, page, value); - - return 0; -} - static int lm25066_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -195,7 +166,7 @@ static int lm25066_probe(struct i2c_client *client, data->id = id->driver_data; info = &data->info; - info->pages = 2; + info->pages = 1; info->format[PSC_VOLTAGE_IN] = direct; info->format[PSC_VOLTAGE_OUT] = direct; info->format[PSC_CURRENT_IN] = direct; @@ -206,14 +177,12 @@ static int lm25066_probe(struct i2c_client *client, info->b[PSC_TEMPERATURE] = 0; info->R[PSC_TEMPERATURE] = 0; - info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VMON | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - info->func[1] = PMBUS_HAVE_VOUT; info->read_word_data = lm25066_read_word_data; info->write_word_data = lm25066_write_word_data; - info->write_byte = lm25066_write_byte; switch (id->driver_data) { case lm25066: -- cgit v1.2.3-70-g09d2 From 58615a94f6a190f2fb9f9a99f1894d161c4b85b9 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 9 Feb 2013 15:15:52 -0800 Subject: hwmon: (pmbus/lm25066) Add support for LM25056 Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm25066 | 18 +++++- drivers/hwmon/pmbus/Kconfig | 2 +- drivers/hwmon/pmbus/lm25066.c | 132 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 139 insertions(+), 13 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/lm25066 b/Documentation/hwmon/lm25066 index 2bc5ba6aee6..c1b57d72efc 100644 --- a/Documentation/hwmon/lm25066 +++ b/Documentation/hwmon/lm25066 @@ -1,7 +1,13 @@ -Kernel driver max8688 +Kernel driver lm25066 ===================== Supported chips: + * TI LM25056 + Prefix: 'lm25056' + Addresses scanned: - + Datasheets: + http://www.ti.com/lit/gpn/lm25056 + http://www.ti.com/lit/gpn/lm25056a * National Semiconductor LM25066 Prefix: 'lm25066' Addresses scanned: - @@ -25,8 +31,9 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for National Semiconductor LM25066, -LM5064, and LM5064 Power Management, Monitoring, Control, and Protection ICs. +This driver supports hardware montoring for National Semiconductor / TI LM25056, +LM25066, LM5064, and LM5064 Power Management, Monitoring, Control, and +Protection ICs. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus for details on PMBus client drivers. @@ -62,8 +69,13 @@ in1_max_alarm Input voltage high alarm. in2_label "vmon" in2_input Measured voltage on VAUX pin +in2_min Minimum VAUX voltage (LM25056 only). +in2_max Maximum VAUX voltage (LM25056 only). +in2_min_alarm VAUX voltage low alarm (LM25056 only). +in2_max_alarm VAUX voltage high alarm (LM25056 only). in3_label "vout1" + Not supported on LM25056. in3_input Measured output voltage. in3_average Average measured output voltage. in3_min Minimum output voltage. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index b1adad60d6a..39cc63edfbb 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -42,7 +42,7 @@ config SENSORS_LM25066 default n help If you say yes here you get hardware monitoring support for National - Semiconductor LM25066, LM5064, and LM5066. + Semiconductor LM25056, LM25066, LM5064, and LM5066. This driver can also be built as a module. If so, the module will be called lm25066. diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 08267149dc6..6a9d6edaacb 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -1,5 +1,5 @@ /* - * Hardware monitoring driver for LM25066 / LM5064 / LM5066 + * Hardware monitoring driver for LM25056 / LM25066 / LM5064 / LM5066 * * Copyright (c) 2011 Ericsson AB. * Copyright (c) 2013 Guenter Roeck @@ -27,7 +27,7 @@ #include #include "pmbus.h" -enum chips { lm25066, lm5064, lm5066 }; +enum chips { lm25056, lm25066, lm5064, lm5066 }; #define LM25066_READ_VAUX 0xd0 #define LM25066_MFR_READ_IIN 0xd1 @@ -44,6 +44,14 @@ enum chips { lm25066, lm5064, lm5066 }; #define LM25066_DEV_SETUP_CL (1 << 4) /* Current limit */ +/* LM25056 only */ + +#define LM25056_VAUX_OV_WARN_LIMIT 0xe3 +#define LM25056_VAUX_UV_WARN_LIMIT 0xe4 + +#define LM25056_MFR_STS_VAUX_OV_WARN (1 << 1) +#define LM25056_MFR_STS_VAUX_UV_WARN (1 << 0) + struct __coeff { short m, b, R; }; @@ -51,7 +59,34 @@ struct __coeff { #define PSC_CURRENT_IN_L (PSC_NUM_CLASSES) #define PSC_POWER_L (PSC_NUM_CLASSES + 1) -static struct __coeff lm25066_coeff[3][PSC_NUM_CLASSES + 2] = { +static struct __coeff lm25066_coeff[4][PSC_NUM_CLASSES + 2] = { + [lm25056] = { + [PSC_VOLTAGE_IN] = { + .m = 16296, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 13797, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 6726, + .R = -2, + }, + [PSC_POWER] = { + .m = 5501, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 26882, + .R = -4, + }, + [PSC_TEMPERATURE] = { + .m = 1580, + .b = -14500, + .R = -2, + }, + }, [lm25066] = { [PSC_VOLTAGE_IN] = { .m = 22070, @@ -161,6 +196,10 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) break; /* Adjust returned value to match VIN coefficients */ switch (data->id) { + case lm25056: + /* VIN: 6.14 mV VAUX: 293 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; case lm25066: /* VIN: 4.54 mV VAUX: 283.2 uV LSB */ ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); @@ -214,6 +253,58 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) return ret; } +static int lm25056_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25056_VAUX_UV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25056_VAUX_OV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + default: + ret = lm25066_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int lm25056_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret, s; + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + s = 0; + if (ret & LM25056_MFR_STS_VAUX_UV_WARN) + s |= PB_VOLTAGE_UV_WARNING; + if (ret & LM25056_MFR_STS_VAUX_OV_WARN) + s |= PB_VOLTAGE_OV_WARNING; + ret = s; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, u16 word) { @@ -243,6 +334,22 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, word); pmbus_clear_cache(client); break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, 0x0fff); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_UV_WARN_LIMIT, word); + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, 0x0fff); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_OV_WARN_LIMIT, word); + pmbus_clear_cache(client); + break; case PMBUS_VIRT_RESET_PIN_HISTORY: ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK); break; @@ -284,12 +391,18 @@ static int lm25066_probe(struct i2c_client *client, info->format[PSC_TEMPERATURE] = direct; info->format[PSC_POWER] = direct; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VMON + | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VMON | PMBUS_HAVE_VOUT - | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN - | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - - info->read_word_data = lm25066_read_word_data; + if (data->id == lm25056) { + info->func[0] |= PMBUS_HAVE_STATUS_VMON; + info->read_word_data = lm25056_read_word_data; + info->read_byte_data = lm25056_read_byte_data; + } else { + info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + info->read_word_data = lm25066_read_word_data; + } info->write_word_data = lm25066_write_word_data; coeff = &lm25066_coeff[data->id][0]; @@ -318,6 +431,7 @@ static int lm25066_probe(struct i2c_client *client, } static const struct i2c_device_id lm25066_id[] = { + {"lm25056", lm25056}, {"lm25066", lm25066}, {"lm5064", lm5064}, {"lm5066", lm5066}, @@ -339,5 +453,5 @@ static struct i2c_driver lm25066_driver = { module_i2c_driver(lm25066_driver); MODULE_AUTHOR("Guenter Roeck"); -MODULE_DESCRIPTION("PMBus driver for LM25066/LM5064/LM5066"); +MODULE_DESCRIPTION("PMBus driver for LM25056/LM25066/LM5064/LM5066"); MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From a1fac92b8b2c439678424f7660f066341607a82a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 15 Mar 2013 12:55:08 -0700 Subject: hwmon: (tmp401) Add support for TMP431 TMP431 is compatible to TMP401. Also add support for additional I2C addresses supported by TMP411B and TMP411C. Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- Documentation/hwmon/tmp401 | 16 ++++++++++------ drivers/hwmon/Kconfig | 4 ++-- drivers/hwmon/tmp401.c | 15 ++++++++++++--- 3 files changed, 24 insertions(+), 11 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/tmp401 b/Documentation/hwmon/tmp401 index 9fc44724921..12e4781c0bc 100644 --- a/Documentation/hwmon/tmp401 +++ b/Documentation/hwmon/tmp401 @@ -8,8 +8,12 @@ Supported chips: Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp401.html * Texas Instruments TMP411 Prefix: 'tmp411' - Addresses scanned: I2C 0x4c + Addresses scanned: I2C 0x4c, 0x4d, 0x4e Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp411.html + * Texas Instruments TMP431 + Prefix: 'tmp431' + Addresses scanned: I2C 0x4c, 0x4d + Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp431.html Authors: Hans de Goede @@ -18,8 +22,8 @@ Authors: Description ----------- -This driver implements support for Texas Instruments TMP401 and -TMP411 chips. These chips implements one remote and one local +This driver implements support for Texas Instruments TMP401, TMP411, +and TMP431 chips. These chips implement one remote and one local temperature sensor. Temperature is measured in degrees Celsius. Resolution of the remote sensor is 0.0625 degree. Local sensor resolution can be set to 0.5, 0.25, 0.125 or 0.0625 degree (not @@ -27,10 +31,10 @@ supported by the driver so far, so using the default resolution of 0.5 degree). The driver provides the common sysfs-interface for temperatures (see -/Documentation/hwmon/sysfs-interface under Temperatures). +Documentation/hwmon/sysfs-interface under Temperatures). -The TMP411 chip is compatible with TMP401. It provides some additional -features. +The TMP411 and TMP431 chips are compatible with TMP401. TMP411 provides +some additional features. * Minimum and Maximum temperature measured since power-on, chip-reset diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a0f1d6a406e..43ed3aef21c 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1238,8 +1238,8 @@ config SENSORS_TMP401 tristate "Texas Instruments TMP401 and compatibles" depends on I2C help - If you say yes here you get support for Texas Instruments TMP401 and - TMP411 temperature sensor chips. + If you say yes here you get support for Texas Instruments TMP401, + TMP411, and TMP431 temperature sensor chips. This driver can also be built as a module. If so, the module will be called tmp401. diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 97bf34494d8..f4290ec7d9e 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -40,9 +40,9 @@ #include /* Addresses to scan */ -static const unsigned short normal_i2c[] = { 0x4c, I2C_CLIENT_END }; +static const unsigned short normal_i2c[] = { 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; -enum chips { tmp401, tmp411 }; +enum chips { tmp401, tmp411, tmp431 }; /* * The TMP401 registers, note some registers have different addresses for @@ -90,6 +90,7 @@ static const u8 TMP411_TEMP_HIGHEST_LSB[2] = { 0x33, 0x37 }; #define TMP401_MANUFACTURER_ID 0x55 #define TMP401_DEVICE_ID 0x11 #define TMP411_DEVICE_ID 0x12 +#define TMP431_DEVICE_ID 0x31 /* * Driver data (common to all clients) @@ -98,6 +99,7 @@ static const u8 TMP411_TEMP_HIGHEST_LSB[2] = { 0x33, 0x37 }; static const struct i2c_device_id tmp401_id[] = { { "tmp401", tmp401 }, { "tmp411", tmp411 }, + { "tmp431", tmp431 }, { } }; MODULE_DEVICE_TABLE(i2c, tmp401_id); @@ -555,11 +557,18 @@ static int tmp401_detect(struct i2c_client *client, switch (reg) { case TMP401_DEVICE_ID: + if (client->addr != 0x4c) + return -ENODEV; kind = tmp401; break; case TMP411_DEVICE_ID: kind = tmp411; break; + case TMP431_DEVICE_ID: + if (client->addr == 0x4e) + return -ENODEV; + kind = tmp431; + break; default: return -ENODEV; } @@ -603,7 +612,7 @@ static int tmp401_probe(struct i2c_client *client, { int i, err = 0; struct tmp401_data *data; - const char *names[] = { "TMP401", "TMP411" }; + const char *names[] = { "TMP401", "TMP411", "TMP431" }; data = devm_kzalloc(&client->dev, sizeof(struct tmp401_data), GFP_KERNEL); -- cgit v1.2.3-70-g09d2 From e1eb49063b301fd885fca63e2f24d1dac1d65d0e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 10 Mar 2013 16:54:19 -0700 Subject: hwmon: Add driver for LM95234 Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm95234 | 36 +++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/lm95234.c | 769 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 816 insertions(+) create mode 100644 Documentation/hwmon/lm95234 create mode 100644 drivers/hwmon/lm95234.c (limited to 'Documentation') diff --git a/Documentation/hwmon/lm95234 b/Documentation/hwmon/lm95234 new file mode 100644 index 00000000000..a0e95ddfd37 --- /dev/null +++ b/Documentation/hwmon/lm95234 @@ -0,0 +1,36 @@ +Kernel driver lm95234 +===================== + +Supported chips: + * National Semiconductor / Texas Instruments LM95234 + Addresses scanned: I2C 0x18, 0x4d, 0x4e + Datasheet: Publicly available at the Texas Instruments website + http://www.ti.com/product/lm95234 + + +Author: Guenter Roeck + +Description +----------- + +LM95234 is an 11-bit digital temperature sensor with a 2-wire System Management +Bus (SMBus) interface and TrueTherm technology that can very accurately monitor +the temperature of four remote diodes as well as its own temperature. +The four remote diodes can be external devices such as microprocessors, +graphics processors or diode-connected 2N3904s. The LM95234's TruTherm +beta compensation technology allows sensing of 90 nm or 65 nm process +thermal diodes accurately. + +All temperature values are given in millidegrees Celsius. Temperature +is provided within a range of -127 to +255 degrees (+127.875 degrees for +the internal sensor). Resolution depends on temperature input and range. + +Each sensor has its own maximum limit, but the hysteresis is common to all +channels. The hysteresis is configurable with the tem1_max_hyst attribute and +affects the hysteresis on all channels. The first two external sensors also +have a critical limit. + +The lm95234 driver can change its update interval to a fixed set of values. +It will round up to the next selectable interval. See the datasheet for exact +values. Reading sensor values more often will do no harm, but will return +'old' values. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 43ed3aef21c..4986961a98f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -771,6 +771,16 @@ config SENSORS_LTC4261 This driver can also be built as a module. If so, the module will be called ltc4261. +config SENSORS_LM95234 + tristate "National Semiconductor LM95234" + depends on I2C + help + If you say yes here you get support for the LM95234 temperature + sensor. + + This driver can also be built as a module. If so, the module + will be called lm95234. + config SENSORS_LM95241 tristate "National Semiconductor LM95241 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 82975724d3a..5c71fe6a9c7 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM93) += lm93.o +obj-$(CONFIG_SENSORS_LM95234) += lm95234.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LM95245) += lm95245.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c new file mode 100644 index 00000000000..307c9eaeeb9 --- /dev/null +++ b/drivers/hwmon/lm95234.c @@ -0,0 +1,769 @@ +/* + * Driver for Texas Instruments / National Semiconductor LM95234 + * + * Copyright (c) 2013 Guenter Roeck + * + * Derived from lm95241.c + * Copyright (C) 2008, 2010 Davide Rizzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "lm95234" + +static const unsigned short normal_i2c[] = { 0x18, 0x4d, 0x4e, I2C_CLIENT_END }; + +/* LM95234 registers */ +#define LM95234_REG_MAN_ID 0xFE +#define LM95234_REG_CHIP_ID 0xFF +#define LM95234_REG_STATUS 0x02 +#define LM95234_REG_CONFIG 0x03 +#define LM95234_REG_CONVRATE 0x04 +#define LM95234_REG_STS_FAULT 0x07 +#define LM95234_REG_STS_TCRIT1 0x08 +#define LM95234_REG_STS_TCRIT2 0x09 +#define LM95234_REG_TEMPH(x) ((x) + 0x10) +#define LM95234_REG_TEMPL(x) ((x) + 0x20) +#define LM95234_REG_UTEMPH(x) ((x) + 0x19) /* Remote only */ +#define LM95234_REG_UTEMPL(x) ((x) + 0x29) +#define LM95234_REG_REM_MODEL 0x30 +#define LM95234_REG_REM_MODEL_STS 0x38 +#define LM95234_REG_OFFSET(x) ((x) + 0x31) /* Remote only */ +#define LM95234_REG_TCRIT1(x) ((x) + 0x40) +#define LM95234_REG_TCRIT2(x) ((x) + 0x49) /* Remote channel 1,2 */ +#define LM95234_REG_TCRIT_HYST 0x5a + +#define NATSEMI_MAN_ID 0x01 +#define LM95234_CHIP_ID 0x79 + +/* Client data (each client gets its own) */ +struct lm95234_data { + struct device *hwmon_dev; + struct mutex update_lock; + unsigned long last_updated, interval; /* in jiffies */ + bool valid; /* false until following fields are valid */ + /* registers values */ + int temp[5]; /* temperature (signed) */ + u32 status; /* fault/alarm status */ + u8 tcrit1[5]; /* critical temperature limit */ + u8 tcrit2[2]; /* high temperature limit */ + s8 toffset[4]; /* remote temperature offset */ + u8 thyst; /* common hysteresis */ + + u8 sensor_type; /* temperature sensor type */ +}; + +static int lm95234_read_temp(struct i2c_client *client, int index, int *t) +{ + int val; + u16 temp = 0; + + if (index) { + val = i2c_smbus_read_byte_data(client, + LM95234_REG_UTEMPH(index - 1)); + if (val < 0) + return val; + temp = val << 8; + val = i2c_smbus_read_byte_data(client, + LM95234_REG_UTEMPL(index - 1)); + if (val < 0) + return val; + temp |= val; + *t = temp; + } + /* + * Read signed temperature if unsigned temperature is 0, + * or if this is the local sensor. + */ + if (!temp) { + val = i2c_smbus_read_byte_data(client, + LM95234_REG_TEMPH(index)); + if (val < 0) + return val; + temp = val << 8; + val = i2c_smbus_read_byte_data(client, + LM95234_REG_TEMPL(index)); + if (val < 0) + return val; + temp |= val; + *t = (s16)temp; + } + return 0; +} + +static u16 update_intervals[] = { 143, 364, 1000, 2500 }; + +/* Fill value cache. Must be called with update lock held. */ + +static int lm95234_fill_cache(struct i2c_client *client) +{ + struct lm95234_data *data = i2c_get_clientdata(client); + int i, ret; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_CONVRATE); + if (ret < 0) + return ret; + + data->interval = msecs_to_jiffies(update_intervals[ret & 0x03]); + + for (i = 0; i < ARRAY_SIZE(data->tcrit1); i++) { + ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT1(i)); + if (ret < 0) + return ret; + data->tcrit1[i] = ret; + } + for (i = 0; i < ARRAY_SIZE(data->tcrit2); i++) { + ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT2(i)); + if (ret < 0) + return ret; + data->tcrit2[i] = ret; + } + for (i = 0; i < ARRAY_SIZE(data->toffset); i++) { + ret = i2c_smbus_read_byte_data(client, LM95234_REG_OFFSET(i)); + if (ret < 0) + return ret; + data->toffset[i] = ret; + } + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT_HYST); + if (ret < 0) + return ret; + data->thyst = ret; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); + if (ret < 0) + return ret; + data->sensor_type = ret; + + return 0; +} + +static int lm95234_update_device(struct i2c_client *client, + struct lm95234_data *data) +{ + int ret; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + data->interval) || + !data->valid) { + int i; + + if (!data->valid) { + ret = lm95234_fill_cache(client); + if (ret < 0) + goto abort; + } + + data->valid = false; + for (i = 0; i < ARRAY_SIZE(data->temp); i++) { + ret = lm95234_read_temp(client, i, &data->temp[i]); + if (ret < 0) + goto abort; + } + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_FAULT); + if (ret < 0) + goto abort; + data->status = ret; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT1); + if (ret < 0) + goto abort; + data->status |= ret << 8; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT2); + if (ret < 0) + goto abort; + data->status |= ret << 16; + + data->last_updated = jiffies; + data->valid = true; + } + ret = 0; +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + return sprintf(buf, "%d\n", + DIV_ROUND_CLOSEST(data->temp[index] * 125, 32)); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + u32 mask = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + return sprintf(buf, "%u", !!(data->status & mask)); +} + +static ssize_t show_type(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + u8 mask = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + return sprintf(buf, data->sensor_type & mask ? "1\n" : "2\n"); +} + +static ssize_t set_type(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + unsigned long val; + u8 mask = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + if (val != 1 && val != 2) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val == 1) + data->sensor_type |= mask; + else + data->sensor_type &= ~mask; + data->valid = false; + i2c_smbus_write_byte_data(client, LM95234_REG_REM_MODEL, + data->sensor_type); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_tcrit2(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + return sprintf(buf, "%u", data->tcrit2[index] * 1000); +} + +static ssize_t set_tcrit2(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + long val; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, index ? 255 : 127); + + mutex_lock(&data->update_lock); + data->tcrit2[index] = val; + i2c_smbus_write_byte_data(client, LM95234_REG_TCRIT2(index), val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_tcrit2_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + /* Result can be negative, so be careful with unsigned operands */ + return sprintf(buf, "%d", + ((int)data->tcrit2[index] - (int)data->thyst) * 1000); +} + +static ssize_t show_tcrit1(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%u", data->tcrit1[index] * 1000); +} + +static ssize_t set_tcrit1(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + long val; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255); + + mutex_lock(&data->update_lock); + data->tcrit1[index] = val; + i2c_smbus_write_byte_data(client, LM95234_REG_TCRIT1(index), val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_tcrit1_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + /* Result can be negative, so be careful with unsigned operands */ + return sprintf(buf, "%d", + ((int)data->tcrit1[index] - (int)data->thyst) * 1000); +} + +static ssize_t set_tcrit1_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + long val; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + val = DIV_ROUND_CLOSEST(val, 1000); + val = clamp_val((int)data->tcrit1[index] - val, 0, 31); + + mutex_lock(&data->update_lock); + data->thyst = val; + i2c_smbus_write_byte_data(client, LM95234_REG_TCRIT_HYST, val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_offset(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + return sprintf(buf, "%d", data->toffset[index] * 500); +} + +static ssize_t set_offset(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int index = to_sensor_dev_attr(attr)->index; + long val; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + /* Accuracy is 1/2 degrees C */ + val = clamp_val(DIV_ROUND_CLOSEST(val, 500), -128, 127); + + mutex_lock(&data->update_lock); + data->toffset[index] = val; + i2c_smbus_write_byte_data(client, LM95234_REG_OFFSET(index), val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_interval(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + return sprintf(buf, "%lu\n", + DIV_ROUND_CLOSEST(data->interval * 1000, HZ)); +} + +static ssize_t set_interval(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95234_data *data = i2c_get_clientdata(client); + unsigned long val; + u8 regval; + int ret = lm95234_update_device(client, data); + + if (ret) + return ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + for (regval = 0; regval < 3; regval++) { + if (val <= update_intervals[regval]) + break; + } + + mutex_lock(&data->update_lock); + data->interval = msecs_to_jiffies(update_intervals[regval]); + i2c_smbus_write_byte_data(client, LM95234_REG_CONVRATE, regval); + mutex_unlock(&data->update_lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); + +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, + BIT(0) | BIT(1)); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, + BIT(2) | BIT(3)); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, + BIT(4) | BIT(5)); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_alarm, NULL, + BIT(6) | BIT(7)); + +static SENSOR_DEVICE_ATTR(temp2_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(1)); +static SENSOR_DEVICE_ATTR(temp3_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(2)); +static SENSOR_DEVICE_ATTR(temp4_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(3)); +static SENSOR_DEVICE_ATTR(temp5_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(4)); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_tcrit2, + set_tcrit2, 0); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_tcrit2, + set_tcrit2, 1); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 4); + +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_tcrit1_hyst, + set_tcrit1_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IRUGO, show_tcrit2_hyst, NULL, 0); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO, show_tcrit2_hyst, NULL, 1); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 4); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(0 + 8)); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(1 + 16)); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(2 + 16)); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(3 + 8)); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(4 + 8)); + +static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 1); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 2); + +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 2); + +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, + BIT(1 + 8)); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, + BIT(2 + 8)); + +static SENSOR_DEVICE_ATTR(temp2_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 0); +static SENSOR_DEVICE_ATTR(temp3_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 1); +static SENSOR_DEVICE_ATTR(temp4_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 2); +static SENSOR_DEVICE_ATTR(temp5_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 3); + +static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_interval, + set_interval); + +static struct attribute *lm95234_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, + &sensor_dev_attr_temp4_type.dev_attr.attr, + &sensor_dev_attr_temp5_type.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp5_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + &sensor_dev_attr_temp4_offset.dev_attr.attr, + &sensor_dev_attr_temp5_offset.dev_attr.attr, + &dev_attr_update_interval.attr, + NULL +}; + +static const struct attribute_group lm95234_group = { + .attrs = lm95234_attributes, +}; + +static int lm95234_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int mfg_id, chip_id, val; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + mfg_id = i2c_smbus_read_byte_data(client, LM95234_REG_MAN_ID); + if (mfg_id != NATSEMI_MAN_ID) + return -ENODEV; + + chip_id = i2c_smbus_read_byte_data(client, LM95234_REG_CHIP_ID); + if (chip_id != LM95234_CHIP_ID) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_STATUS); + if (val & 0x30) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_CONFIG); + if (val & 0xbc) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_CONVRATE); + if (val & 0xfc) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); + if (val & 0xe1) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL_STS); + if (val & 0xe1) + return -ENODEV; + + strlcpy(info->type, "lm95234", I2C_NAME_SIZE); + return 0; +} + +static int lm95234_init_client(struct i2c_client *client) +{ + int val, model; + + /* start conversion if necessary */ + val = i2c_smbus_read_byte_data(client, LM95234_REG_CONFIG); + if (val < 0) + return val; + if (val & 0x40) + i2c_smbus_write_byte_data(client, LM95234_REG_CONFIG, + val & ~0x40); + + /* If diode type status reports an error, try to fix it */ + val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL_STS); + if (val < 0) + return val; + model = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); + if (model < 0) + return model; + if (model & val) { + dev_notice(&client->dev, + "Fixing remote diode type misconfiguration (0x%x)\n", + val); + i2c_smbus_write_byte_data(client, LM95234_REG_REM_MODEL, + model & ~val); + } + return 0; +} + +static int lm95234_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct lm95234_data *data; + int err; + + data = devm_kzalloc(dev, sizeof(struct lm95234_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the LM95234 chip */ + err = lm95234_init_client(client); + if (err < 0) + return err; + + /* Register sysfs hooks */ + err = sysfs_create_group(&dev->kobj, &lm95234_group); + if (err) + return err; + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&dev->kobj, &lm95234_group); + return err; +} + +static int lm95234_remove(struct i2c_client *client) +{ + struct lm95234_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &lm95234_group); + + return 0; +} + +/* Driver data (common to all clients) */ +static const struct i2c_device_id lm95234_id[] = { + { "lm95234", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm95234_id); + +static struct i2c_driver lm95234_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = DRVNAME, + }, + .probe = lm95234_probe, + .remove = lm95234_remove, + .id_table = lm95234_id, + .detect = lm95234_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm95234_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("LM95234 sensor driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From 2c7fd30da21bf6bda12d7a0f678e4fd8ed362c96 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 2 Apr 2013 08:53:19 -0700 Subject: hwmon: (nct6775) Expand scope of supported chips NCT6775, NCT6776, and NCT6779 have a number of variants with the same chip ID but different chip labels. Add text "or compatible" to the message displayed when the driver is loaded and rephrase the Kconfig entry to reflect that it also supports compatible chips. Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775 | 8 ++++---- drivers/hwmon/Kconfig | 8 ++++---- drivers/hwmon/nct6775.c | 17 ++++++++--------- 3 files changed, 16 insertions(+), 17 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/nct6775 b/Documentation/hwmon/nct6775 index 3f5587e3909..4e9ef60e8c6 100644 --- a/Documentation/hwmon/nct6775 +++ b/Documentation/hwmon/nct6775 @@ -8,15 +8,15 @@ Kernel driver NCT6775 ===================== Supported chips: - * Nuvoton NCT6775F/W83667HG-I + * Nuvoton NCT5572D/NCT6771F/NCT6772F/NCT6775F/W83677HG-I Prefix: 'nct6775' Addresses scanned: ISA address retrieved from Super I/O registers Datasheet: Available from Nuvoton upon request - * Nuvoton NCT6776F + * Nuvoton NCT5577D/NCT6776D/NCT6776F Prefix: 'nct6776' Addresses scanned: ISA address retrieved from Super I/O registers Datasheet: Available from Nuvoton upon request - * Nuvoton NCT6779D + * Nuvoton NCT5532D/NCT6779D Prefix: 'nct6779' Addresses scanned: ISA address retrieved from Super I/O registers Datasheet: Available from Nuvoton upon request @@ -28,7 +28,7 @@ Description ----------- This driver implements support for the Nuvoton NCT6775F, NCT6776F, and NCT6779D -super I/O chips. +and compatible super I/O chips. The chips support up to 25 temperature monitoring sources. Up to 6 of those are direct temperature sensor inputs, the others are special sources such as PECI, diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 4986961a98f..26ebff0b45c 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -908,14 +908,14 @@ config SENSORS_MCP3021 will be called mcp3021. config SENSORS_NCT6775 - tristate "Nuvoton NCT6775F, NCT6776F, NCT6779D" + tristate "Nuvoton NCT6775F and compatibles" depends on !PPC select HWMON_VID help If you say yes here you get support for the hardware monitoring - functionality of the Nuvoton NCT6775F, NCT6776F, and NCT6779D - Super-I/O chips. This driver replaces the w83627ehf driver for - NCT6775F and NCT6776F. + functionality of the Nuvoton NCT6775F, NCT6776F, NCT6779D + and compatible Super-I/O chips. This driver replaces the + w83627ehf driver for NCT6775F and NCT6776F. This driver can also be built as a module. If so, the module will be called nct6775. diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 2269bb241b8..d05a700b7da 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -4072,16 +4072,17 @@ static struct platform_driver nct6775_driver = { .remove = nct6775_remove, }; +static const char *nct6775_sio_names[] __initconst = { + "NCT6775F", + "NCT6776D/F", + "NCT6779D", +}; + /* nct6775_find() looks for a '627 in the Super-I/O config space */ static int __init nct6775_find(int sioaddr, unsigned short *addr, struct nct6775_sio_data *sio_data) { - static const char sio_name_NCT6775[] __initconst = "NCT6775F"; - static const char sio_name_NCT6776[] __initconst = "NCT6776F"; - static const char sio_name_NCT6779[] __initconst = "NCT6779D"; - u16 val; - const char *sio_name; int err; err = superio_enter(sioaddr); @@ -4096,15 +4097,12 @@ static int __init nct6775_find(int sioaddr, unsigned short *addr, switch (val & SIO_ID_MASK) { case SIO_NCT6775_ID: sio_data->kind = nct6775; - sio_name = sio_name_NCT6775; break; case SIO_NCT6776_ID: sio_data->kind = nct6776; - sio_name = sio_name_NCT6776; break; case SIO_NCT6779_ID: sio_data->kind = nct6779; - sio_name = sio_name_NCT6779; break; default: if (val != 0xffff) @@ -4132,7 +4130,8 @@ static int __init nct6775_find(int sioaddr, unsigned short *addr, } superio_exit(sioaddr); - pr_info("Found %s chip at %#x\n", sio_name, *addr); + pr_info("Found %s or compatible chip at %#x\n", + nct6775_sio_names[sio_data->kind], *addr); sio_data->sioreg = sioaddr; return 0; -- cgit v1.2.3-70-g09d2 From 1754e4c5c7650da6581dc28f182af9010b84e9c2 Mon Sep 17 00:00:00 2001 From: Masanari Iida Date: Sat, 13 Apr 2013 01:22:11 +0900 Subject: documentation: hwmon: Fix typo in documentation/hwmon Correct spelling typo in Documentation/hwmon Signed-off-by: Masanari Iida Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht15 | 2 +- Documentation/hwmon/zl6100 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/sht15 b/Documentation/hwmon/sht15 index 02850bdfac1..778987d1856 100644 --- a/Documentation/hwmon/sht15 +++ b/Documentation/hwmon/sht15 @@ -40,7 +40,7 @@ bits for humidity, or 12 bits for temperature and 8 bits for humidity. The humidity calibration coefficients are programmed into an OTP memory on the chip. These coefficients are used to internally calibrate the signals from the sensors. Disabling the reload of those coefficients allows saving 10ms for each -measurement and decrease power consumption, while loosing on precision. +measurement and decrease power consumption, while losing on precision. Some options may be set directly in the sht15_platform_data structure or via sysfs attributes. diff --git a/Documentation/hwmon/zl6100 b/Documentation/hwmon/zl6100 index 756b57c6b73..33908a4d68f 100644 --- a/Documentation/hwmon/zl6100 +++ b/Documentation/hwmon/zl6100 @@ -125,7 +125,7 @@ in2_label "vmon" in2_input Measured voltage on VMON (ZL2004) or VDRV (ZL9101M, ZL9117M) pin. Reported voltage is 16x the voltage on the pin (adjusted internally by the chip). -in2_lcrit Critical minumum VMON/VDRV Voltage. +in2_lcrit Critical minimum VMON/VDRV Voltage. in2_crit Critical maximum VMON/VDRV voltage. in2_lcrit_alarm VMON/VDRV voltage critical low alarm. in2_crit_alarm VMON/VDRV voltage critical high alarm. -- cgit v1.2.3-70-g09d2 From 29dd3b64b9edba3dd3dc8bb4d589869a4597a710 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 28 Mar 2013 01:36:44 -0700 Subject: hwmon: (tmp401) Add support for TMP432 TMP432 is similar to TMP431 with a second external temperature sensor. Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- Documentation/hwmon/tmp401 | 11 ++- drivers/hwmon/Kconfig | 2 +- drivers/hwmon/tmp401.c | 192 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 165 insertions(+), 40 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/tmp401 b/Documentation/hwmon/tmp401 index 12e4781c0bc..f91e3fa7e5e 100644 --- a/Documentation/hwmon/tmp401 +++ b/Documentation/hwmon/tmp401 @@ -14,6 +14,10 @@ Supported chips: Prefix: 'tmp431' Addresses scanned: I2C 0x4c, 0x4d Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp431.html + * Texas Instruments TMP432 + Prefix: 'tmp432' + Addresses scanned: I2C 0x4c, 0x4d + Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp432.html Authors: Hans de Goede @@ -23,8 +27,8 @@ Description ----------- This driver implements support for Texas Instruments TMP401, TMP411, -and TMP431 chips. These chips implement one remote and one local -temperature sensor. Temperature is measured in degrees +TMP431, and TMP432 chips. These chips implement one or two remote and +one local temperature sensors. Temperature is measured in degrees Celsius. Resolution of the remote sensor is 0.0625 degree. Local sensor resolution can be set to 0.5, 0.25, 0.125 or 0.0625 degree (not supported by the driver so far, so using the default resolution of 0.5 @@ -44,3 +48,6 @@ some additional features. Exported via sysfs attribute temp_reset_history. Writing 1 to this file triggers a reset. + +TMP432 is compatible with TMP401 and TMP431. It supports two external +temperature sensors. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 26ebff0b45c..4f29117651b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1249,7 +1249,7 @@ config SENSORS_TMP401 depends on I2C help If you say yes here you get support for Texas Instruments TMP401, - TMP411, and TMP431 temperature sensor chips. + TMP411, TMP431, and TMP432 temperature sensor chips. This driver can also be built as a module. If so, the module will be called tmp401. diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index fa6af51b300..a478454f690 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -5,6 +5,9 @@ * Gabriel Konat, Sander Leget, Wouter Willems * Copyright (C) 2009 Andre Prendel * + * Cleanup and support for TMP431 and TMP432 by Guenter Roeck + * Copyright (c) 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 @@ -43,7 +46,7 @@ /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; -enum chips { tmp401, tmp411, tmp431 }; +enum chips { tmp401, tmp411, tmp431, tmp432 }; /* * The TMP401 registers, note some registers have different addresses for @@ -85,6 +88,30 @@ static const u8 TMP401_TEMP_LSB[6][2] = { { 0x33, 0x37 }, /* highest */ }; +static const u8 TMP432_TEMP_MSB_READ[4][3] = { + { 0x00, 0x01, 0x23 }, /* temp */ + { 0x06, 0x08, 0x16 }, /* low limit */ + { 0x05, 0x07, 0x15 }, /* high limit */ + { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ +}; + +static const u8 TMP432_TEMP_MSB_WRITE[4][3] = { + { 0, 0, 0 }, /* temp - unused */ + { 0x0C, 0x0E, 0x16 }, /* low limit */ + { 0x0B, 0x0D, 0x15 }, /* high limit */ + { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ +}; + +static const u8 TMP432_TEMP_LSB[3][3] = { + { 0x29, 0x10, 0x24 }, /* temp */ + { 0x3E, 0x14, 0x18 }, /* low limit */ + { 0x3D, 0x13, 0x17 }, /* high limit */ +}; + +/* [0] = fault, [1] = low, [2] = high, [3] = therm/crit */ +static const u8 TMP432_STATUS_REG[] = { + 0x1b, 0x36, 0x35, 0x37 }; + /* Flags */ #define TMP401_CONFIG_RANGE BIT(2) #define TMP401_CONFIG_SHUTDOWN BIT(6) @@ -96,6 +123,11 @@ static const u8 TMP401_TEMP_LSB[6][2] = { #define TMP401_STATUS_LOCAL_LOW BIT(5) #define TMP401_STATUS_LOCAL_HIGH BIT(6) +/* On TMP432, each status has its own register */ +#define TMP432_STATUS_LOCAL BIT(0) +#define TMP432_STATUS_REMOTE1 BIT(1) +#define TMP432_STATUS_REMOTE2 BIT(2) + /* Manufacturer / Device ID's */ #define TMP401_MANUFACTURER_ID 0x55 #define TMP401_DEVICE_ID 0x11 @@ -103,6 +135,7 @@ static const u8 TMP401_TEMP_LSB[6][2] = { #define TMP411B_DEVICE_ID 0x13 #define TMP411C_DEVICE_ID 0x10 #define TMP431_DEVICE_ID 0x31 +#define TMP432_DEVICE_ID 0x32 /* * Driver data (common to all clients) @@ -112,6 +145,7 @@ static const struct i2c_device_id tmp401_id[] = { { "tmp401", tmp401 }, { "tmp411", tmp411 }, { "tmp431", tmp431 }, + { "tmp432", tmp432 }, { } }; MODULE_DEVICE_TABLE(i2c, tmp401_id); @@ -130,9 +164,9 @@ struct tmp401_data { unsigned int update_interval; /* in milliseconds */ /* register values */ - u8 status; + u8 status[4]; u8 config; - u16 temp[6][2]; + u16 temp[6][3]; u8 temp_crit_hyst; }; @@ -166,22 +200,27 @@ static int tmp401_update_device_reg16(struct i2c_client *client, { int i, j, val; int num_regs = data->kind == tmp411 ? 6 : 4; + int num_sensors = data->kind == tmp432 ? 3 : 2; - for (i = 0; i < 2; i++) { /* local / rem1 */ + for (i = 0; i < num_sensors; i++) { /* local / r1 / r2 */ for (j = 0; j < num_regs; j++) { /* temp / low / ... */ + u8 regaddr; /* * High byte must be read first immediately followed * by the low byte */ - val = i2c_smbus_read_byte_data(client, - TMP401_TEMP_MSB_READ[j][i]); + regaddr = data->kind == tmp432 ? + TMP432_TEMP_MSB_READ[j][i] : + TMP401_TEMP_MSB_READ[j][i]; + val = i2c_smbus_read_byte_data(client, regaddr); if (val < 0) return val; data->temp[j][i] = val << 8; if (j == 3) /* crit is msb only */ continue; - val = i2c_smbus_read_byte_data(client, - TMP401_TEMP_LSB[j][i]); + regaddr = data->kind == tmp432 ? TMP432_TEMP_LSB[j][i] + : TMP401_TEMP_LSB[j][i]; + val = i2c_smbus_read_byte_data(client, regaddr); if (val < 0) return val; data->temp[j][i] |= val; @@ -195,7 +234,7 @@ static struct tmp401_data *tmp401_update_device(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct tmp401_data *data = i2c_get_clientdata(client); struct tmp401_data *ret = data; - int val; + int i, val; unsigned long next_update; mutex_lock(&data->update_lock); @@ -203,12 +242,38 @@ static struct tmp401_data *tmp401_update_device(struct device *dev) next_update = data->last_updated + msecs_to_jiffies(data->update_interval) + 1; if (time_after(jiffies, next_update) || !data->valid) { - val = i2c_smbus_read_byte_data(client, TMP401_STATUS); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; + if (data->kind != tmp432) { + /* + * The driver uses the TMP432 status format internally. + * Convert status to TMP432 format for other chips. + */ + val = i2c_smbus_read_byte_data(client, TMP401_STATUS); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->status[0] = + (val & TMP401_STATUS_REMOTE_OPEN) >> 1; + data->status[1] = + ((val & TMP401_STATUS_REMOTE_LOW) >> 2) | + ((val & TMP401_STATUS_LOCAL_LOW) >> 5); + data->status[2] = + ((val & TMP401_STATUS_REMOTE_HIGH) >> 3) | + ((val & TMP401_STATUS_LOCAL_HIGH) >> 6); + data->status[3] = val & (TMP401_STATUS_LOCAL_CRIT + | TMP401_STATUS_REMOTE_CRIT); + } else { + for (i = 0; i < ARRAY_SIZE(data->status); i++) { + val = i2c_smbus_read_byte_data(client, + TMP432_STATUS_REG[i]); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->status[i] = val; + } } - data->status = val; + val = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); if (val < 0) { ret = ERR_PTR(val); @@ -270,13 +335,14 @@ static ssize_t show_temp_crit_hyst(struct device *dev, static ssize_t show_status(struct device *dev, struct device_attribute *devattr, char *buf) { - int mask = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + int mask = to_sensor_dev_attr_2(devattr)->index; struct tmp401_data *data = tmp401_update_device(dev); if (IS_ERR(data)) return PTR_ERR(data); - return sprintf(buf, "%d\n", !!(data->status & mask)); + return sprintf(buf, "%d\n", !!(data->status[nr] & mask)); } static ssize_t store_temp(struct device *dev, struct device_attribute *devattr, @@ -288,6 +354,7 @@ static ssize_t store_temp(struct device *dev, struct device_attribute *devattr, struct tmp401_data *data = tmp401_update_device(dev); long val; u16 reg; + u8 regaddr; if (IS_ERR(data)) return PTR_ERR(data); @@ -299,13 +366,13 @@ static ssize_t store_temp(struct device *dev, struct device_attribute *devattr, mutex_lock(&data->update_lock); - i2c_smbus_write_byte_data(client, - TMP401_TEMP_MSB_WRITE[nr][index], - reg >> 8); + regaddr = data->kind == tmp432 ? TMP432_TEMP_MSB_WRITE[nr][index] + : TMP401_TEMP_MSB_WRITE[nr][index]; + i2c_smbus_write_byte_data(client, regaddr, reg >> 8); if (nr != 3) { - i2c_smbus_write_byte_data(client, - TMP401_TEMP_LSB[nr][index], - reg & 0xFF); + regaddr = data->kind == tmp432 ? TMP432_TEMP_LSB[nr][index] + : TMP401_TEMP_LSB[nr][index]; + i2c_smbus_write_byte_data(client, regaddr, reg & 0xFF); } data->temp[nr][index] = reg; @@ -426,12 +493,12 @@ static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IWUSR | S_IRUGO, show_temp, store_temp, 3, 0); static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temp_crit_hyst, store_temp_crit_hyst, 0); -static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_status, NULL, - TMP401_STATUS_LOCAL_LOW); -static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_status, NULL, - TMP401_STATUS_LOCAL_HIGH); -static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_status, NULL, - TMP401_STATUS_LOCAL_CRIT); +static SENSOR_DEVICE_ATTR_2(temp1_min_alarm, S_IRUGO, show_status, NULL, + 1, TMP432_STATUS_LOCAL); +static SENSOR_DEVICE_ATTR_2(temp1_max_alarm, S_IRUGO, show_status, NULL, + 2, TMP432_STATUS_LOCAL); +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, show_status, NULL, + 3, TMP432_STATUS_LOCAL); static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1); static SENSOR_DEVICE_ATTR_2(temp2_min, S_IWUSR | S_IRUGO, show_temp, store_temp, 1, 1); @@ -441,14 +508,14 @@ static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IWUSR | S_IRUGO, show_temp, store_temp, 3, 1); static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1); -static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_status, NULL, - TMP401_STATUS_REMOTE_OPEN); -static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_status, NULL, - TMP401_STATUS_REMOTE_LOW); -static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_status, NULL, - TMP401_STATUS_REMOTE_HIGH); -static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_status, NULL, - TMP401_STATUS_REMOTE_CRIT); +static SENSOR_DEVICE_ATTR_2(temp2_fault, S_IRUGO, show_status, NULL, + 0, TMP432_STATUS_REMOTE1); +static SENSOR_DEVICE_ATTR_2(temp2_min_alarm, S_IRUGO, show_status, NULL, + 1, TMP432_STATUS_REMOTE1); +static SENSOR_DEVICE_ATTR_2(temp2_max_alarm, S_IRUGO, show_status, NULL, + 2, TMP432_STATUS_REMOTE1); +static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, show_status, NULL, + 3, TMP432_STATUS_REMOTE1); static DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, show_update_interval, set_update_interval); @@ -509,6 +576,42 @@ static const struct attribute_group tmp411_group = { .attrs = tmp411_attributes, }; +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2); +static SENSOR_DEVICE_ATTR_2(temp3_min, S_IWUSR | S_IRUGO, show_temp, + store_temp, 1, 2); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IWUSR | S_IRUGO, show_temp, + store_temp, 2, 2); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IWUSR | S_IRUGO, show_temp, + store_temp, 3, 2); +static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, + NULL, 2); +static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_status, NULL, + 0, TMP432_STATUS_REMOTE2); +static SENSOR_DEVICE_ATTR_2(temp3_min_alarm, S_IRUGO, show_status, NULL, + 1, TMP432_STATUS_REMOTE2); +static SENSOR_DEVICE_ATTR_2(temp3_max_alarm, S_IRUGO, show_status, NULL, + 2, TMP432_STATUS_REMOTE2); +static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, show_status, NULL, + 3, TMP432_STATUS_REMOTE2); + +static struct attribute *tmp432_attributes[] = { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + + NULL +}; + +static const struct attribute_group tmp432_group = { + .attrs = tmp432_attributes, +}; + /* * Begin non sysfs callback code (aka Real code) */ @@ -579,6 +682,11 @@ static int tmp401_detect(struct i2c_client *client, return -ENODEV; kind = tmp431; break; + case TMP432_DEVICE_ID: + if (client->addr == 0x4e) + return -ENODEV; + kind = tmp432; + break; default: return -ENODEV; } @@ -610,6 +718,9 @@ static int tmp401_remove(struct i2c_client *client) if (data->kind == tmp411) sysfs_remove_group(&dev->kobj, &tmp411_group); + if (data->kind == tmp432) + sysfs_remove_group(&dev->kobj, &tmp432_group); + return 0; } @@ -619,7 +730,7 @@ static int tmp401_probe(struct i2c_client *client, struct device *dev = &client->dev; int err; struct tmp401_data *data; - const char *names[] = { "TMP401", "TMP411", "TMP431" }; + const char *names[] = { "TMP401", "TMP411", "TMP431", "TMP432" }; data = devm_kzalloc(dev, sizeof(struct tmp401_data), GFP_KERNEL); if (!data) @@ -644,6 +755,13 @@ static int tmp401_probe(struct i2c_client *client, goto exit_remove; } + /* Register additional tmp432 sysfs hooks */ + if (data->kind == tmp432) { + err = sysfs_create_group(&dev->kobj, &tmp432_group); + if (err) + goto exit_remove; + } + data->hwmon_dev = hwmon_device_register(dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); -- cgit v1.2.3-70-g09d2