From 4fb0abfee424b05f0ec6d2d09e38f04ee2b82a8a Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Mon, 8 Nov 2021 15:51:21 -0600 Subject: x86/amd_nb: Add AMD Family 19h Models (10h-1Fh) and (A0h-AFh) PCI IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the new PCI Device IDs to support new generation of AMD 19h family of processors. Signed-off-by: Yazen Ghannam Signed-off-by: Babu Moger Acked-by: Krzysztof Wilczyński Acked-by: Borislav Petkov Acked-by: Bjorn Helgaas # pci_ids.h Link: https://lore.kernel.org/r/163640828133.955062.18349019796157170473.stgit@bmoger-ubuntu Signed-off-by: Guenter Roeck --- arch/x86/kernel/amd_nb.c | 5 +++++ include/linux/pci_ids.h | 1 + 2 files changed, 6 insertions(+) diff --git a/arch/x86/kernel/amd_nb.c b/arch/x86/kernel/amd_nb.c index c92c9c774c0e..f3e885f3dd0f 100644 --- a/arch/x86/kernel/amd_nb.c +++ b/arch/x86/kernel/amd_nb.c @@ -19,12 +19,14 @@ #define PCI_DEVICE_ID_AMD_17H_M10H_ROOT 0x15d0 #define PCI_DEVICE_ID_AMD_17H_M30H_ROOT 0x1480 #define PCI_DEVICE_ID_AMD_17H_M60H_ROOT 0x1630 +#define PCI_DEVICE_ID_AMD_19H_M10H_ROOT 0x14a4 #define PCI_DEVICE_ID_AMD_17H_DF_F4 0x1464 #define PCI_DEVICE_ID_AMD_17H_M10H_DF_F4 0x15ec #define PCI_DEVICE_ID_AMD_17H_M30H_DF_F4 0x1494 #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F4 0x144c #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F4 0x1444 #define PCI_DEVICE_ID_AMD_19H_DF_F4 0x1654 +#define PCI_DEVICE_ID_AMD_19H_M10H_DF_F4 0x14b1 #define PCI_DEVICE_ID_AMD_19H_M40H_ROOT 0x14b5 #define PCI_DEVICE_ID_AMD_19H_M40H_DF_F4 0x167d #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F4 0x166e @@ -39,6 +41,7 @@ static const struct pci_device_id amd_root_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M10H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M30H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_ROOT) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M10H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_ROOT) }, {} }; @@ -61,6 +64,7 @@ static const struct pci_device_id amd_nb_misc_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, {} @@ -78,6 +82,7 @@ static const struct pci_device_id amd_nb_link_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F4) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F4) }, diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 011f2f1ea5bb..b5248f27910e 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -555,6 +555,7 @@ #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443 #define PCI_DEVICE_ID_AMD_19H_DF_F3 0x1653 +#define PCI_DEVICE_ID_AMD_19H_M10H_DF_F3 0x14b0 #define PCI_DEVICE_ID_AMD_19H_M40H_DF_F3 0x167c #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F3 0x166d #define PCI_DEVICE_ID_AMD_CNB17H_F3 0x1703 -- cgit v1.2.3-59-g8ed1b From f707bcb5d1cb4c47d27c688c859dcdb70e3c7065 Mon Sep 17 00:00:00 2001 From: Babu Moger Date: Mon, 8 Nov 2021 15:51:27 -0600 Subject: hwmon: (k10temp) Remove unused definitions Usage of these definitions were removed after the commit 0a4e668b5d52 ("hwmon: (k10temp) Remove support for displaying voltage and current on Zen CPUs"). So, cleanup them up. Signed-off-by: Babu Moger Link: https://lore.kernel.org/r/163640828776.955062.15863375803675701204.stgit@bmoger-ubuntu Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 3618a924e78e..662bad7ed0a2 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -76,26 +76,6 @@ static DEFINE_MUTEX(nb_smu_ind_mutex); #define ZEN_CUR_TEMP_SHIFT 21 #define ZEN_CUR_TEMP_RANGE_SEL_MASK BIT(19) -#define ZEN_SVI_BASE 0x0005A000 - -/* F17h thermal registers through SMN */ -#define F17H_M01H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0xc) -#define F17H_M01H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) -#define F17H_M31H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14) -#define F17H_M31H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) - -#define F17H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F17H_M01H_CFACTOR_ISOC 250000 /* 0.25A / LSB */ -#define F17H_M31H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F17H_M31H_CFACTOR_ISOC 310000 /* 0.31A / LSB */ - -/* F19h thermal registers through SMN */ -#define F19H_M01_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14) -#define F19H_M01_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) - -#define F19H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F19H_M01H_CFACTOR_ISOC 310000 /* 0.31A / LSB */ - struct k10temp_data { struct pci_dev *pdev; void (*read_htcreg)(struct pci_dev *pdev, u32 *regval); -- cgit v1.2.3-59-g8ed1b From 3cf90efa13678d2de2f9f7e44e26353996db842a Mon Sep 17 00:00:00 2001 From: Babu Moger Date: Mon, 8 Nov 2021 15:51:34 -0600 Subject: hwmon: (k10temp) Add support for AMD Family 19h Models 10h-1Fh and A0h-AFh Add thermal info support for AMD Family 19h Models 10h-1Fh and A0h-AFh. Thermal info is supported via a new PCI device ID at offset 0x300h. Signed-off-by: Babu Moger Link: https://lore.kernel.org/r/163640829419.955062.12539219969781039915.stgit@bmoger-ubuntu Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 662bad7ed0a2..880990fa4795 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -433,7 +433,9 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->ccd_offset = 0x154; k10temp_get_ccd_support(pdev, data, 8); break; + case 0x10 ... 0x1f: case 0x40 ... 0x4f: /* Yellow Carp */ + case 0xa0 ... 0xaf: data->ccd_offset = 0x300; k10temp_get_ccd_support(pdev, data, 8); break; @@ -477,6 +479,7 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, -- cgit v1.2.3-59-g8ed1b From ed68a0effe51be3c89f7fa03dfc630b2ef1f1ee8 Mon Sep 17 00:00:00 2001 From: Nathan Rossi Date: Tue, 2 Nov 2021 05:27:54 +0000 Subject: dt-bindings: hwmon: ti,ina2xx: Document ti,ina238 compatible string Document the compatible string for the Texas Instruments INA238, this device is a variant of the existing INA2xx devices and has the same device tree bindings (shunt resistor). Signed-off-by: Nathan Rossi Acked-by: Rob Herring Link: https://lore.kernel.org/r/20211102052754.817220-1-nathan@nathanrossi.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index 6f0443322a36..180573f26c88 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -26,6 +26,7 @@ properties: - ti,ina226 - ti,ina230 - ti,ina231 + - ti,ina238 reg: maxItems: 1 -- cgit v1.2.3-59-g8ed1b From 8be23b9b3114952c92c13fac2d37854aba208b9e Mon Sep 17 00:00:00 2001 From: Nathan Rossi Date: Tue, 2 Nov 2021 05:27:54 +0000 Subject: dt-bindings: hwmon: ti,ina2xx: Add ti,shunt-gain property Add a property to the binding to define the selected shunt voltage gain. This specifies the range and accuracy that applies to the shunt circuit. This property only applies to devices that have a selectable shunt voltage range via PGA or ADCRANGE register configuration. Signed-off-by: Nathan Rossi Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20211102052754.817220-2-nathan@nathanrossi.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ti,ina2xx.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index 180573f26c88..47af97bb4ced 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -36,6 +36,27 @@ properties: Shunt resistor value in micro-Ohm. $ref: /schemas/types.yaml#/definitions/uint32 + ti,shunt-gain: + description: | + Programmable gain divisor for the shunt voltage accuracy and range. This + property only applies to devices that have configurable PGA/ADCRANGE. The + gain value is used configure the gain and to convert the shunt voltage, + current and power register values when reading measurements from the + device. + + For devices that have a configurable PGA (e.g. INA209, INA219, INA220), + the gain value maps directly with the PG bits of the config register. + + For devices that have ADCRANGE configuration (e.g. INA238) a shunt-gain + value of 1 maps to ADCRANGE=1 where no gain divisor is applied to the + shunt voltage, and a value of 4 maps to ADCRANGE=0 such that a wider + voltage range is used. + + The default value is device dependent, and is defined by the reset value + of PGA/ADCRANGE in the respective configuration registers. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 8] + required: - compatible - reg -- cgit v1.2.3-59-g8ed1b From eacb52f010a80752e77a86aee9cb01f4864b0ca4 Mon Sep 17 00:00:00 2001 From: Nathan Rossi Date: Tue, 2 Nov 2021 05:27:54 +0000 Subject: hwmon: Driver for Texas Instruments INA238 The INA238 is a I2C power monitor similar to other INA2xx devices, providing shunt voltage, bus voltage, current, power and temperature measurements. Signed-off-by: Nathan Rossi Link: https://lore.kernel.org/r/20211102052754.817220-3-nathan@nathanrossi.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina238.rst | 56 ++++ Documentation/hwmon/index.rst | 1 + drivers/hwmon/Kconfig | 12 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ina238.c | 644 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 714 insertions(+) create mode 100644 Documentation/hwmon/ina238.rst create mode 100644 drivers/hwmon/ina238.c diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst new file mode 100644 index 000000000000..d9f479984420 --- /dev/null +++ b/Documentation/hwmon/ina238.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver ina238 +==================== + +Supported chips: + + * Texas Instruments INA238 + + Prefix: 'ina238' + + Addresses: I2C 0x40 - 0x4f + + Datasheet: + https://www.ti.com/lit/gpn/ina238 + +Author: Nathan Rossi + +Description +----------- + +The INA238 is a current shunt, power and temperature monitor with an I2C +interface. It includes a number of programmable functions including alerts, +conversion rate, sample averaging and selectable shunt voltage accuracy. + +The shunt value in micro-ohms can be set via platform data or device tree at +compile-time or via the shunt_resistor attribute in sysfs at run-time. Please +refer to the Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings +if the device tree is used. + +Sysfs entries +------------- + +======================= ======================================================= +in0_input Shunt voltage (mV) +in0_min Minimum shunt voltage threshold (mV) +in0_min_alarm Minimum shunt voltage alarm +in0_max Maximum shunt voltage threshold (mV) +in0_max_alarm Maximum shunt voltage alarm + +in1_input Bus voltage (mV) +in1_min Minimum bus voltage threshold (mV) +in1_min_alarm Minimum shunt voltage alarm +in1_max Maximum bus voltage threshold (mV) +in1_max_alarm Maximum shunt voltage alarm + +power1_input Power measurement (uW) +power1_max Maximum power threshold (uW) +power1_max_alarm Maximum power alarm + +curr1_input Current measurement (mA) + +temp1_input Die temperature measurement (mC) +temp1_max Maximum die temperature threshold (mC) +temp1_max_alarm Maximum die temperature alarm +======================= ======================================================= diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 7046bf1870d9..6f30c8c9c76a 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -76,6 +76,7 @@ Hardware Monitoring Kernel Drivers ibmpowernv ina209 ina2xx + ina238 ina3221 intel-m10-bmc-hwmon ir35221 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 64bd3dfba2c4..8815e96911d1 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1872,6 +1872,18 @@ config SENSORS_INA2XX This driver can also be built as a module. If so, the module will be called ina2xx. +config SENSORS_INA238 + tristate "Texas Instruments INA238" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the INA238 power monitor + chip. This driver supports voltage, current, power and temperature + measurements as well as alarm configuration. + + This driver can also be built as a module. If so, the module + will be called ina238. + config SENSORS_INA3221 tristate "Texas Instruments INA3221 Triple Power Monitor" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index baee6a8d4dd1..1ddb26f57a6f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o obj-$(CONFIG_SENSORS_INA209) += ina209.o obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o +obj-$(CONFIG_SENSORS_INA238) += ina238.o obj-$(CONFIG_SENSORS_INA3221) += ina3221.o obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) += intel-m10-bmc-hwmon.o obj-$(CONFIG_SENSORS_IT87) += it87.o diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c new file mode 100644 index 000000000000..50eb9c5e132e --- /dev/null +++ b/drivers/hwmon/ina238.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments INA238 power monitor chip + * Datasheet: https://www.ti.com/product/ina238 + * + * Copyright (C) 2021 Nathan Rossi + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* INA238 register definitions */ +#define INA238_CONFIG 0x0 +#define INA238_ADC_CONFIG 0x1 +#define INA238_SHUNT_CALIBRATION 0x2 +#define INA238_SHUNT_VOLTAGE 0x4 +#define INA238_BUS_VOLTAGE 0x5 +#define INA238_DIE_TEMP 0x6 +#define INA238_CURRENT 0x7 +#define INA238_POWER 0x8 +#define INA238_DIAG_ALERT 0xb +#define INA238_SHUNT_OVER_VOLTAGE 0xc +#define INA238_SHUNT_UNDER_VOLTAGE 0xd +#define INA238_BUS_OVER_VOLTAGE 0xe +#define INA238_BUS_UNDER_VOLTAGE 0xf +#define INA238_TEMP_LIMIT 0x10 +#define INA238_POWER_LIMIT 0x11 +#define INA238_DEVICE_ID 0x3f + +#define INA238_CONFIG_ADCRANGE BIT(4) + +#define INA238_DIAG_ALERT_TMPOL BIT(7) +#define INA238_DIAG_ALERT_SHNTOL BIT(6) +#define INA238_DIAG_ALERT_SHNTUL BIT(5) +#define INA238_DIAG_ALERT_BUSOL BIT(4) +#define INA238_DIAG_ALERT_BUSUL BIT(3) +#define INA238_DIAG_ALERT_POL BIT(2) + +#define INA238_REGISTERS 0x11 + +#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */ + +/* Default configuration of device on reset. */ +#define INA238_CONFIG_DEFAULT 0 +/* 16 sample averaging, 1052us conversion time, continuous mode */ +#define INA238_ADC_CONFIG_DEFAULT 0xfb6a +/* Configure alerts to be based on averaged value (SLOWALERT) */ +#define INA238_DIAG_ALERT_DEFAULT 0x2000 +/* + * This driver uses a fixed calibration value in order to scale current/power + * based on a fixed shunt resistor value. This allows for conversion within the + * device to avoid integer limits whilst current/power accuracy is scaled + * relative to the shunt resistor value within the driver. This is similar to + * how the ina2xx driver handles current/power scaling. + * + * The end result of this is that increasing shunt values (from a fixed 20 mOhm + * shunt) increase the effective current/power accuracy whilst limiting the + * range and decreasing shunt values decrease the effective accuracy but + * increase the range. + * + * The value of the Current register is calculated given the following: + * Current (A) = (shunt voltage register * 5) * calibration / 81920 + * + * The maximum shunt voltage is 163.835 mV (0x7fff, ADC_RANGE = 0, gain = 4). + * With the maximum current value of 0x7fff and a fixed shunt value results in + * a calibration value of 16384 (0x4000). + * + * 0x7fff = (0x7fff * 5) * calibration / 81920 + * calibration = 0x4000 + * + * Equivalent calibration is applied for the Power register (maximum value for + * bus voltage is 102396.875 mV, 0x7fff), where the maximum power that can + * occur is ~16776192 uW (register value 0x147a8): + * + * This scaling means the resulting values for Current and Power registers need + * to be scaled by the difference between the fixed shunt resistor and the + * actual shunt resistor: + * + * shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb) + * + * Current (mA) = register value * 20000 / rshunt / 4 * gain + * Power (W) = 0.2 * register value * 20000 / rshunt / 4 * gain + */ +#define INA238_CALIBRATION_VALUE 16384 +#define INA238_FIXED_SHUNT 20000 + +#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */ +#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */ +#define INA238_DIE_TEMP_LSB 125 /* 125 mC/lsb */ + +static struct regmap_config ina238_regmap_config = { + .max_register = INA238_REGISTERS, + .reg_bits = 8, + .val_bits = 16, +}; + +struct ina238_data { + struct i2c_client *client; + struct mutex config_lock; + struct regmap *regmap; + u32 rshunt; + int gain; +}; + +static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val) +{ + u8 data[3]; + int err; + + /* 24-bit register read */ + err = i2c_smbus_read_i2c_block_data(client, reg, 3, data); + if (err < 0) + return err; + if (err != 3) + return -EIO; + *val = (data[0] << 16) | (data[1] << 8) | data[2]; + + return 0; +} + +static int ina238_read_in(struct device *dev, u32 attr, int channel, + long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int reg, mask; + int regval; + int err; + + switch (channel) { + case 0: + switch (attr) { + case hwmon_in_input: + reg = INA238_SHUNT_VOLTAGE; + break; + case hwmon_in_max: + reg = INA238_SHUNT_OVER_VOLTAGE; + break; + case hwmon_in_min: + reg = INA238_SHUNT_UNDER_VOLTAGE; + break; + case hwmon_in_max_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTOL; + break; + case hwmon_in_min_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTUL; + break; + default: + return -EOPNOTSUPP; + } + break; + case 1: + switch (attr) { + case hwmon_in_input: + reg = INA238_BUS_VOLTAGE; + break; + case hwmon_in_max: + reg = INA238_BUS_OVER_VOLTAGE; + break; + case hwmon_in_min: + reg = INA238_BUS_UNDER_VOLTAGE; + break; + case hwmon_in_max_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_BUSOL; + break; + case hwmon_in_min_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_BUSUL; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + err = regmap_read(data->regmap, reg, ®val); + if (err < 0) + return err; + + switch (attr) { + case hwmon_in_input: + case hwmon_in_max: + case hwmon_in_min: + /* signed register, value in mV */ + regval = (s16)regval; + if (channel == 0) + /* gain of 1 -> LSB / 4 */ + *val = (regval * INA238_SHUNT_VOLTAGE_LSB) / + (1000 * (4 - data->gain + 1)); + else + *val = (regval * INA238_BUS_VOLTAGE_LSB) / 1000; + break; + case hwmon_in_max_alarm: + case hwmon_in_min_alarm: + *val = !!(regval & mask); + break; + } + + return 0; +} + +static int ina238_write_in(struct device *dev, u32 attr, int channel, + long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + + if (attr != hwmon_in_max && attr != hwmon_in_min) + return -EOPNOTSUPP; + + /* convert decimal to register value */ + switch (channel) { + case 0: + /* signed value, clamp to max range +/-163 mV */ + regval = clamp_val(val, -163, 163); + regval = (regval * 1000 * (4 - data->gain + 1)) / + INA238_SHUNT_VOLTAGE_LSB; + regval = clamp_val(regval, S16_MIN, S16_MAX); + + switch (attr) { + case hwmon_in_max: + return regmap_write(data->regmap, + INA238_SHUNT_OVER_VOLTAGE, regval); + case hwmon_in_min: + return regmap_write(data->regmap, + INA238_SHUNT_UNDER_VOLTAGE, regval); + default: + return -EOPNOTSUPP; + } + case 1: + /* signed value, positive values only. Clamp to max 102.396 V */ + regval = clamp_val(val, 0, 102396); + regval = (regval * 1000) / INA238_BUS_VOLTAGE_LSB; + regval = clamp_val(regval, 0, S16_MAX); + + switch (attr) { + case hwmon_in_max: + return regmap_write(data->regmap, + INA238_BUS_OVER_VOLTAGE, regval); + case hwmon_in_min: + return regmap_write(data->regmap, + INA238_BUS_UNDER_VOLTAGE, regval); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int ina238_read_current(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + int err; + + switch (attr) { + case hwmon_curr_input: + err = regmap_read(data->regmap, INA238_CURRENT, ®val); + if (err < 0) + return err; + + /* Signed register, fixed 1mA current lsb. result in mA */ + *val = div_s64((s16)regval * INA238_FIXED_SHUNT * data->gain, + data->rshunt * 4); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_read_power(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + long long power; + int regval; + int err; + + switch (attr) { + case hwmon_power_input: + err = ina238_read_reg24(data->client, INA238_POWER, ®val); + if (err) + return err; + + /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ + power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * + data->gain, 20 * data->rshunt); + /* Clamp value to maximum value of long */ + *val = clamp_val(power, 0, LONG_MAX); + break; + case hwmon_power_max: + err = regmap_read(data->regmap, INA238_POWER_LIMIT, ®val); + if (err) + return err; + + /* + * Truncated 24-bit compare register, lower 8-bits are + * truncated. Same conversion to/from uW as POWER register. + */ + power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * + data->gain, 20 * data->rshunt); + /* Clamp value to maximum value of long */ + *val = clamp_val(power, 0, LONG_MAX); + break; + case hwmon_power_max_alarm: + err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); + if (err) + return err; + + *val = !!(regval & INA238_DIAG_ALERT_POL); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_write_power(struct device *dev, u32 attr, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + long regval; + + if (attr != hwmon_power_max) + return -EOPNOTSUPP; + + /* + * Unsigned postive values. Compared against the 24-bit power register, + * lower 8-bits are truncated. Same conversion to/from uW as POWER + * register. + */ + regval = clamp_val(val, 0, LONG_MAX); + regval = div_u64(val * 20ULL * data->rshunt, + 1000ULL * INA238_FIXED_SHUNT * data->gain); + regval = clamp_val(regval >> 8, 0, U16_MAX); + + return regmap_write(data->regmap, INA238_POWER_LIMIT, regval); +} + +static int ina238_read_temp(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + int err; + + switch (attr) { + case hwmon_temp_input: + err = regmap_read(data->regmap, INA238_DIE_TEMP, ®val); + if (err) + return err; + + /* Signed, bits 15-4 of register, result in mC */ + *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB; + break; + case hwmon_temp_max: + err = regmap_read(data->regmap, INA238_TEMP_LIMIT, ®val); + if (err) + return err; + + /* Signed, bits 15-4 of register, result in mC */ + *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB; + break; + case hwmon_temp_max_alarm: + err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); + if (err) + return err; + + *val = !!(regval & INA238_DIAG_ALERT_TMPOL); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_write_temp(struct device *dev, u32 attr, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + + if (attr != hwmon_temp_max) + return -EOPNOTSUPP; + + /* Signed, bits 15-4 of register */ + regval = (val / INA238_DIE_TEMP_LSB) << 4; + regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xfff0; + + return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval); +} + +static int ina238_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return ina238_read_in(dev, attr, channel, val); + case hwmon_curr: + return ina238_read_current(dev, attr, val); + case hwmon_power: + return ina238_read_power(dev, attr, val); + case hwmon_temp: + return ina238_read_temp(dev, attr, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina238_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int err; + + mutex_lock(&data->config_lock); + + switch (type) { + case hwmon_in: + err = ina238_write_in(dev, attr, channel, val); + break; + case hwmon_power: + err = ina238_write_power(dev, attr, val); + break; + case hwmon_temp: + err = ina238_write_temp(dev, attr, val); + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->config_lock); + return err; +} + +static umode_t ina238_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_max_alarm: + case hwmon_in_min_alarm: + return 0444; + case hwmon_in_max: + case hwmon_in_min: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_max_alarm: + return 0444; + case hwmon_power_max: + return 0644; + default: + return 0; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_max: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +#define INA238_HWMON_IN_CONFIG (HWMON_I_INPUT | \ + HWMON_I_MAX | HWMON_I_MAX_ALARM | \ + HWMON_I_MIN | HWMON_I_MIN_ALARM) + +static const struct hwmon_channel_info *ina238_info[] = { + HWMON_CHANNEL_INFO(in, + /* 0: shunt voltage */ + INA238_HWMON_IN_CONFIG, + /* 1: bus voltage */ + INA238_HWMON_IN_CONFIG), + HWMON_CHANNEL_INFO(curr, + /* 0: current through shunt */ + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* 0: power */ + HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM), + HWMON_CHANNEL_INFO(temp, + /* 0: die temperature */ + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM), + NULL +}; + +static const struct hwmon_ops ina238_hwmon_ops = { + .is_visible = ina238_is_visible, + .read = ina238_read, + .write = ina238_write, +}; + +static const struct hwmon_chip_info ina238_chip_info = { + .ops = &ina238_hwmon_ops, + .info = ina238_info, +}; + +static int ina238_probe(struct i2c_client *client) +{ + struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct ina238_data *data; + int config; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->config_lock); + + data->regmap = devm_regmap_init_i2c(client, &ina238_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + /* load shunt value */ + data->rshunt = INA238_RSHUNT_DEFAULT; + if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata) + data->rshunt = pdata->shunt_uohms; + if (data->rshunt == 0) { + dev_err(dev, "invalid shunt resister value %u\n", data->rshunt); + return -EINVAL; + } + + /* load shunt gain value */ + if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0) + data->gain = 4; /* Default of ADCRANGE = 0 */ + if (data->gain != 1 && data->gain != 4) { + dev_err(dev, "invalid shunt gain value %u\n", data->gain); + return -EINVAL; + } + + /* Setup CONFIG register */ + config = INA238_CONFIG_DEFAULT; + if (data->gain == 1) + config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */ + ret = regmap_write(data->regmap, INA238_CONFIG, config); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + /* Setup ADC_CONFIG register */ + ret = regmap_write(data->regmap, INA238_ADC_CONFIG, + INA238_ADC_CONFIG_DEFAULT); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + /* Setup SHUNT_CALIBRATION register with fixed value */ + ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION, + INA238_CALIBRATION_VALUE); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + /* Setup alert/alarm configuration */ + ret = regmap_write(data->regmap, INA238_DIAG_ALERT, + INA238_DIAG_ALERT_DEFAULT); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &ina238_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n", + client->name, data->rshunt, data->gain); + + return 0; +} + +static const struct i2c_device_id ina238_id[] = { + { "ina238", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina238_id); + +static const struct of_device_id __maybe_unused ina238_of_match[] = { + { .compatible = "ti,ina238" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ina238_of_match); + +static struct i2c_driver ina238_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ina238", + .of_match_table = of_match_ptr(ina238_of_match), + }, + .probe_new = ina238_probe, + .id_table = ina238_id, +}; + +module_i2c_driver(ina238_driver); + +MODULE_AUTHOR("Nathan Rossi "); +MODULE_DESCRIPTION("ina238 driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From bcb31e680837b71648158f2fedfc078cf6699207 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 17 Oct 2021 07:47:43 -0700 Subject: hwmon: (tmp401) Simplify temperature register arrays The difference between TMP431 and other chips of this series is that the TMP431 has an additional sensor. The register addresses for other sensors are the same for all chips. It is therefore unnecessary to maintain two different arrays for TMP431 and the other chips. Just use the same array for all chips and add the TMP431 register addresses as third column. Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index b31f4964f852..29c857a76c9e 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -49,36 +49,22 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; #define TMP401_MANUFACTURER_ID_REG 0xFE #define TMP401_DEVICE_ID_REG 0xFF -static const u8 TMP401_TEMP_MSB_READ[7][2] = { - { 0x00, 0x01 }, /* temp */ - { 0x06, 0x08 }, /* low limit */ - { 0x05, 0x07 }, /* high limit */ - { 0x20, 0x19 }, /* therm (crit) limit */ - { 0x30, 0x34 }, /* lowest */ - { 0x32, 0x36 }, /* highest */ -}; - -static const u8 TMP401_TEMP_MSB_WRITE[7][2] = { - { 0, 0 }, /* temp (unused) */ - { 0x0C, 0x0E }, /* low limit */ - { 0x0B, 0x0D }, /* high limit */ - { 0x20, 0x19 }, /* therm (crit) limit */ - { 0x30, 0x34 }, /* lowest */ - { 0x32, 0x36 }, /* highest */ -}; - -static const u8 TMP432_TEMP_MSB_READ[4][3] = { +static const u8 TMP401_TEMP_MSB_READ[7][3] = { { 0x00, 0x01, 0x23 }, /* temp */ { 0x06, 0x08, 0x16 }, /* low limit */ { 0x05, 0x07, 0x15 }, /* high limit */ - { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ + { 0x20, 0x19, 0x1a }, /* therm (crit) limit */ + { 0x30, 0x34, 0x00 }, /* lowest */ + { 0x32, 0x36, 0x00 }, /* highest */ }; -static const u8 TMP432_TEMP_MSB_WRITE[4][3] = { - { 0, 0, 0 }, /* temp - unused */ +static const u8 TMP401_TEMP_MSB_WRITE[7][3] = { + { 0x00, 0x00, 0x00 }, /* temp (unused) */ { 0x0C, 0x0E, 0x16 }, /* low limit */ { 0x0B, 0x0D, 0x15 }, /* high limit */ - { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ + { 0x20, 0x19, 0x1a }, /* therm (crit) limit */ + { 0x30, 0x34, 0x00 }, /* lowest */ + { 0x32, 0x36, 0x00 }, /* highest */ }; /* [0] = fault, [1] = low, [2] = high, [3] = therm/crit */ @@ -182,9 +168,7 @@ static int tmp401_update_device_reg16(struct i2c_client *client, for (j = 0; j < num_regs; j++) { /* temp / low / ... */ u8 regaddr; - regaddr = data->kind == tmp432 ? - TMP432_TEMP_MSB_READ[j][i] : - TMP401_TEMP_MSB_READ[j][i]; + regaddr = TMP401_TEMP_MSB_READ[j][i]; if (j == 3) { /* crit is msb only */ val = i2c_smbus_read_byte_data(client, regaddr); } else { @@ -336,8 +320,7 @@ static ssize_t temp_store(struct device *dev, mutex_lock(&data->update_lock); - regaddr = data->kind == tmp432 ? TMP432_TEMP_MSB_WRITE[nr][index] - : TMP401_TEMP_MSB_WRITE[nr][index]; + regaddr = TMP401_TEMP_MSB_WRITE[nr][index]; if (nr == 3) { /* crit is msb only */ i2c_smbus_write_byte_data(client, regaddr, reg >> 8); } else { -- cgit v1.2.3-59-g8ed1b From ca53e7640de7579f7e3ee467c82618e1ad98857a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 16 Oct 2021 10:51:27 -0700 Subject: hwmon: (tmp401) Convert to _info API The new API is cleaner and reduces code size significantly. All chip accesses are 'hidden' in chip access to prepare for using regmap. Local caching code is removed, to be replaced by regmap based caching in a follow-up patch. Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 734 +++++++++++++++++++++++-------------------------- 1 file changed, 348 insertions(+), 386 deletions(-) diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 29c857a76c9e..bcd8edfbd5e0 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -18,17 +18,14 @@ * and thus has 16 bits registers for its value and limit instead of 8 bits. */ -#include -#include #include -#include -#include +#include #include #include -#include -#include +#include +#include #include -#include +#include /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d, @@ -55,7 +52,7 @@ static const u8 TMP401_TEMP_MSB_READ[7][3] = { { 0x05, 0x07, 0x15 }, /* high limit */ { 0x20, 0x19, 0x1a }, /* therm (crit) limit */ { 0x30, 0x34, 0x00 }, /* lowest */ - { 0x32, 0x36, 0x00 }, /* highest */ + { 0x32, 0xf6, 0x00 }, /* highest */ }; static const u8 TMP401_TEMP_MSB_WRITE[7][3] = { @@ -64,7 +61,7 @@ static const u8 TMP401_TEMP_MSB_WRITE[7][3] = { { 0x0B, 0x0D, 0x15 }, /* high limit */ { 0x20, 0x19, 0x1a }, /* therm (crit) limit */ { 0x30, 0x34, 0x00 }, /* lowest */ - { 0x32, 0x36, 0x00 }, /* highest */ + { 0x32, 0xf6, 0x00 }, /* highest */ }; /* [0] = fault, [1] = low, [2] = high, [3] = therm/crit */ @@ -117,309 +114,264 @@ MODULE_DEVICE_TABLE(i2c, tmp401_id); struct tmp401_data { struct i2c_client *client; - const struct attribute_group *groups[3]; struct mutex update_lock; - bool valid; /* false until following fields are valid */ - unsigned long last_updated; /* in jiffies */ enum chips kind; - unsigned int update_interval; /* in milliseconds */ + bool extended_range; - /* register values */ - u8 status[4]; - u8 config; - u16 temp[7][3]; - u8 temp_crit_hyst; + /* hwmon API configuration data */ + u32 chip_channel_config[4]; + struct hwmon_channel_info chip_info; + u32 temp_channel_config[4]; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[3]; + struct hwmon_chip_info chip; }; -/* - * Sysfs attr show / store functions - */ - -static int tmp401_register_to_temp(u16 reg, u8 config) +static int tmp401_register_to_temp(u16 reg, bool extended) { int temp = reg; - if (config & TMP401_CONFIG_RANGE) + if (extended) temp -= 64 * 256; return DIV_ROUND_CLOSEST(temp * 125, 32); } -static u16 tmp401_temp_to_register(long temp, u8 config, int zbits) +static u16 tmp401_temp_to_register(long temp, bool extended, int zbits) { - if (config & TMP401_CONFIG_RANGE) { + if (extended) { temp = clamp_val(temp, -64000, 191000); temp += 64000; - } else + } else { temp = clamp_val(temp, 0, 127000); + } return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; } -static int tmp401_update_device_reg16(struct i2c_client *client, - struct tmp401_data *data) -{ - int i, j, val; - int num_regs = data->kind == tmp411 ? 6 : 4; - int num_sensors = data->kind == tmp432 ? 3 : 2; - - for (i = 0; i < num_sensors; i++) { /* local / r1 / r2 */ - for (j = 0; j < num_regs; j++) { /* temp / low / ... */ - u8 regaddr; - - regaddr = TMP401_TEMP_MSB_READ[j][i]; - if (j == 3) { /* crit is msb only */ - val = i2c_smbus_read_byte_data(client, regaddr); - } else { - val = i2c_smbus_read_word_swapped(client, - regaddr); - } - if (val < 0) - return val; - - data->temp[j][i] = j == 3 ? val << 8 : val; - } - } - return 0; -} - -static struct tmp401_data *tmp401_update_device(struct device *dev) +static int tmp401_reg_read(struct tmp401_data *data, unsigned int reg) { - struct tmp401_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; - struct tmp401_data *ret = data; - int i, val; - unsigned long next_update; + int val, regval; - mutex_lock(&data->update_lock); - - next_update = data->last_updated + - msecs_to_jiffies(data->update_interval); - if (time_after(jiffies, next_update) || !data->valid) { - 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; - } - } - - val = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - data->config = val; - val = tmp401_update_device_reg16(client, data); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - val = i2c_smbus_read_byte_data(client, TMP401_TEMP_CRIT_HYST); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; + switch (reg) { + case 0: /* local temp msb */ + case 1: /* remote temp msb */ + case 5: /* local temp high limit msb */ + case 6: /* local temp low limit msb */ + case 7: /* remote temp ligh limit msb */ + case 8: /* remote temp low limit msb */ + case 0x15: /* remote temp 2 high limit msb */ + case 0x16: /* remote temp 2 low limit msb */ + case 0x23: /* remote temp 2 msb */ + case 0x30: /* local temp minimum, tmp411 */ + case 0x32: /* local temp maximum, tmp411 */ + case 0x34: /* remote temp minimum, tmp411 */ + case 0xf6: /* remote temp maximum, tmp411 (really 0x36) */ + /* work around register overlap between TMP411 and TMP432 */ + if (reg == 0xf6) + reg = 0x36; + return i2c_smbus_read_word_swapped(client, reg); + case 0x19: /* critical limits, 8-bit registers */ + case 0x1a: + case 0x20: + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + return regval << 8; + case 0x1b: + case 0x35 ... 0x37: + if (data->kind == tmp432) + return i2c_smbus_read_byte_data(client, reg); + /* simulate TMP432 status registers */ + regval = i2c_smbus_read_byte_data(client, TMP401_STATUS); + if (regval < 0) + return regval; + val = 0; + switch (reg) { + case 0x1b: /* open / fault */ + if (regval & TMP401_STATUS_REMOTE_OPEN) + val |= BIT(1); + break; + case 0x35: /* high limit */ + if (regval & TMP401_STATUS_LOCAL_HIGH) + val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_HIGH) + val |= BIT(1); + break; + case 0x36: /* low limit */ + if (regval & TMP401_STATUS_LOCAL_LOW) + val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_LOW) + val |= BIT(1); + break; + case 0x37: /* therm / crit limit */ + if (regval & TMP401_STATUS_LOCAL_CRIT) + val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_CRIT) + val |= BIT(1); + break; } - data->temp_crit_hyst = val; - - data->last_updated = jiffies; - data->valid = true; + return val; + default: + return i2c_smbus_read_byte_data(client, reg); } - -abort: - mutex_unlock(&data->update_lock); - return ret; -} - -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - int nr = to_sensor_dev_attr_2(devattr)->nr; - int index = to_sensor_dev_attr_2(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", - tmp401_register_to_temp(data->temp[nr][index], data->config)); } -static ssize_t temp_crit_hyst_show(struct device *dev, - struct device_attribute *devattr, - char *buf) +static int tmp401_reg_write(struct tmp401_data *data, unsigned int reg, unsigned int val) { - int temp, index = to_sensor_dev_attr(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); - - mutex_lock(&data->update_lock); - temp = tmp401_register_to_temp(data->temp[3][index], data->config); - temp -= data->temp_crit_hyst * 1000; - mutex_unlock(&data->update_lock); + struct i2c_client *client = data->client; - return sprintf(buf, "%d\n", temp); + switch (reg) { + case 0x0b: /* local temp high limit msb */ + case 0x0c: /* local temp low limit msb */ + case 0x0d: /* remote temp ligh limit msb */ + case 0x0e: /* remote temp low limit msb */ + case 0x15: /* remote temp 2 high limit msb */ + case 0x16: /* remote temp 2 low limit msb */ + return i2c_smbus_write_word_swapped(client, reg, val); + case 0x19: /* critical limits, 8-bit registers */ + case 0x1a: + case 0x20: + return i2c_smbus_write_byte_data(client, reg, val >> 8); + default: + return i2c_smbus_write_byte_data(client, reg, val); + } } -static ssize_t status_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - 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); +static const u8 tmp401_temp_reg_index[] = { + [hwmon_temp_input] = 0, + [hwmon_temp_min] = 1, + [hwmon_temp_max] = 2, + [hwmon_temp_crit] = 3, + [hwmon_temp_lowest] = 4, + [hwmon_temp_highest] = 5, +}; - return sprintf(buf, "%d\n", !!(data->status[nr] & mask)); -} +static const u8 tmp401_status_reg_index[] = { + [hwmon_temp_fault] = 0, + [hwmon_temp_min_alarm] = 1, + [hwmon_temp_max_alarm] = 2, + [hwmon_temp_crit_alarm] = 3, +}; -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) +static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val) { - int nr = to_sensor_dev_attr_2(devattr)->nr; - int index = to_sensor_dev_attr_2(devattr)->index; struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - long val; - u16 reg; - u8 regaddr; - - if (kstrtol(buf, 10, &val)) - return -EINVAL; - - reg = tmp401_temp_to_register(val, data->config, nr == 3 ? 8 : 4); - - mutex_lock(&data->update_lock); - - regaddr = TMP401_TEMP_MSB_WRITE[nr][index]; - if (nr == 3) { /* crit is msb only */ - i2c_smbus_write_byte_data(client, regaddr, reg >> 8); - } else { - /* Hardware expects big endian data --> use _swapped */ - i2c_smbus_write_word_swapped(client, regaddr, reg); + int reg, regval; + + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_lowest: + case hwmon_temp_highest: + reg = TMP401_TEMP_MSB_READ[tmp401_temp_reg_index[attr]][channel]; + regval = tmp401_reg_read(data, reg); + if (regval < 0) + return regval; + *val = tmp401_register_to_temp(regval, data->extended_range); + break; + case hwmon_temp_crit_hyst: + mutex_lock(&data->update_lock); + reg = TMP401_TEMP_MSB_READ[3][channel]; + regval = tmp401_reg_read(data, reg); + if (regval < 0) + goto unlock; + *val = tmp401_register_to_temp(regval, data->extended_range); + regval = tmp401_reg_read(data, TMP401_TEMP_CRIT_HYST); + if (regval < 0) + goto unlock; + *val -= regval * 1000; + regval = 0; +unlock: + mutex_unlock(&data->update_lock); + if (regval < 0) + return regval; + break; + case hwmon_temp_fault: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + reg = TMP432_STATUS_REG[tmp401_status_reg_index[attr]]; + regval = tmp401_reg_read(data, reg); + if (regval < 0) + return regval; + *val = !!(regval & BIT(channel)); + break; + default: + return -EOPNOTSUPP; } - data->temp[nr][index] = reg; - - mutex_unlock(&data->update_lock); - - return count; -} - -static ssize_t temp_crit_hyst_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - int temp, index = to_sensor_dev_attr(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - long val; - u8 reg; - - if (IS_ERR(data)) - return PTR_ERR(data); - - if (kstrtol(buf, 10, &val)) - return -EINVAL; - - if (data->config & TMP401_CONFIG_RANGE) - val = clamp_val(val, -64000, 191000); - else - val = clamp_val(val, 0, 127000); - - mutex_lock(&data->update_lock); - temp = tmp401_register_to_temp(data->temp[3][index], data->config); - val = clamp_val(val, temp - 255000, temp); - reg = ((temp - val) + 500) / 1000; - - i2c_smbus_write_byte_data(data->client, TMP401_TEMP_CRIT_HYST, - reg); - - data->temp_crit_hyst = reg; - - mutex_unlock(&data->update_lock); - - return count; + return 0; } -/* - * Resets the historical measurements of minimum and maximum temperatures. - * This is done by writing any value to any of the minimum/maximum registers - * (0x30-0x37). - */ -static ssize_t reset_temp_history_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) +static int tmp401_temp_write(struct device *dev, u32 attr, int channel, + long val) { struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - long val; - - if (kstrtol(buf, 10, &val)) - return -EINVAL; + int reg, regval, ret, temp; - if (val != 1) { - dev_err(dev, - "temp_reset_history value %ld not supported. Use 1 to reset the history!\n", - val); - return -EINVAL; - } mutex_lock(&data->update_lock); - i2c_smbus_write_byte_data(client, TMP401_TEMP_MSB_WRITE[5][0], val); - data->valid = false; + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + reg = TMP401_TEMP_MSB_WRITE[tmp401_temp_reg_index[attr]][channel]; + regval = tmp401_temp_to_register(val, data->extended_range, + attr == hwmon_temp_crit ? 8 : 4); + ret = tmp401_reg_write(data, reg, regval); + break; + case hwmon_temp_crit_hyst: + if (data->extended_range) + val = clamp_val(val, -64000, 191000); + else + val = clamp_val(val, 0, 127000); + + reg = TMP401_TEMP_MSB_READ[3][channel]; + ret = tmp401_reg_read(data, reg); + if (ret < 0) + break; + temp = tmp401_register_to_temp(ret, data->extended_range); + val = clamp_val(val, temp - 255000, temp); + regval = ((temp - val) + 500) / 1000; + ret = tmp401_reg_write(data, TMP401_TEMP_CRIT_HYST, regval); + break; + default: + ret = -EOPNOTSUPP; + break; + } mutex_unlock(&data->update_lock); - - return count; + return ret; } -static ssize_t update_interval_show(struct device *dev, - struct device_attribute *attr, char *buf) +static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val) { struct tmp401_data *data = dev_get_drvdata(dev); + int regval; + + switch (attr) { + case hwmon_chip_update_interval: + regval = tmp401_reg_read(data, TMP401_CONVERSION_RATE_READ); + if (regval < 0) + return regval; + *val = (1 << (7 - regval)) * 125; + break; + case hwmon_chip_temp_reset_history: + *val = 0; + break; + default: + return -EOPNOTSUPP; + } - return sprintf(buf, "%u\n", data->update_interval); + return 0; } -static ssize_t update_interval_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int tmp401_set_convrate(struct i2c_client *client, struct tmp401_data *data, long val) { - struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - unsigned long val; int err, rate; - err = kstrtoul(buf, 10, &val); - if (err) - return err; - /* * For valid rates, interval can be calculated as * interval = (1 << (7 - rate)) * 125; @@ -430,130 +382,113 @@ static ssize_t update_interval_store(struct device *dev, */ val = clamp_val(val, 125, 16000); rate = 7 - __fls(val * 4 / (125 * 3)); + err = i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, rate); + if (err) + return err; + return 0; +} + +static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int err; + mutex_lock(&data->update_lock); - i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, rate); - data->update_interval = (1 << (7 - rate)) * 125; + switch (attr) { + case hwmon_chip_update_interval: + err = tmp401_set_convrate(client, data, val); + break; + case hwmon_chip_temp_reset_history: + if (val != 1) { + err = -EINVAL; + break; + } + /* + * Reset history by writing any value to any of the + * minimum/maximum registers (0x30-0x37). + */ + err = i2c_smbus_write_byte_data(client, 0x30, 0); + break; + default: + err = -EOPNOTSUPP; + break; + } mutex_unlock(&data->update_lock); - return count; + return err; } -static SENSOR_DEVICE_ATTR_2_RO(temp1_input, temp, 0, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_min, temp, 1, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_max, temp, 2, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_crit, temp, 3, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_crit_hyst, temp_crit_hyst, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp1_min_alarm, status, 1, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp1_max_alarm, status, 2, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp1_crit_alarm, status, 3, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp2_input, temp, 0, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_min, temp, 1, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_max, temp, 2, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_crit, temp, 3, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_hyst, temp_crit_hyst, 1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_fault, status, 0, TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_min_alarm, status, 1, - TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_max_alarm, status, 2, - TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_crit_alarm, status, 3, - TMP432_STATUS_REMOTE1); - -static DEVICE_ATTR_RW(update_interval); - -static struct attribute *tmp401_attributes[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp2_crit.dev_attr.attr, - &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - - &dev_attr_update_interval.attr, - - NULL -}; - -static const struct attribute_group tmp401_group = { - .attrs = tmp401_attributes, -}; - -/* - * Additional features of the TMP411 chip. - * The TMP411 stores the minimum and maximum - * temperature measured since power-on, chip-reset, or - * minimum and maximum register reset for both the local - * and remote channels. - */ -static SENSOR_DEVICE_ATTR_2_RO(temp1_lowest, temp, 4, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp1_highest, temp, 5, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp2_lowest, temp, 4, 1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_highest, temp, 5, 1); -static SENSOR_DEVICE_ATTR_WO(temp_reset_history, reset_temp_history, 0); - -static struct attribute *tmp411_attributes[] = { - &sensor_dev_attr_temp1_highest.dev_attr.attr, - &sensor_dev_attr_temp1_lowest.dev_attr.attr, - &sensor_dev_attr_temp2_highest.dev_attr.attr, - &sensor_dev_attr_temp2_lowest.dev_attr.attr, - &sensor_dev_attr_temp_reset_history.dev_attr.attr, - NULL -}; +static int tmp401_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return tmp401_chip_read(dev, attr, channel, val); + case hwmon_temp: + return tmp401_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static const struct attribute_group tmp411_group = { - .attrs = tmp411_attributes, -}; +static int tmp401_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return tmp401_chip_write(dev, attr, channel, val); + case hwmon_temp: + return tmp401_temp_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static SENSOR_DEVICE_ATTR_2_RO(temp3_input, temp, 0, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_min, temp, 1, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_max, temp, 2, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_crit, temp, 3, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_hyst, temp_crit_hyst, 2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_fault, status, 0, TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_min_alarm, status, 1, - TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_max_alarm, status, 2, - TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_crit_alarm, status, 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 umode_t tmp401_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + case hwmon_chip_temp_reset_history: + return 0644; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_fault: + case hwmon_temp_lowest: + case hwmon_temp_highest: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + return 0644; + default: + break; + } + break; + default: + break; + } + return 0; +} -static const struct attribute_group tmp432_group = { - .attrs = tmp432_attributes, +static const struct hwmon_ops tmp401_ops = { + .is_visible = tmp401_is_visible, + .read = tmp401_read, + .write = tmp401_write, }; -/* - * Begin non sysfs callback code (aka Real code) - */ - static int tmp401_init_client(struct tmp401_data *data, struct i2c_client *client) { @@ -561,7 +496,6 @@ static int tmp401_init_client(struct tmp401_data *data, /* Set the conversion rate to 2 Hz */ i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, 5); - data->update_interval = 500; /* Start conversions (disable shutdown if necessary) */ config = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); @@ -571,6 +505,8 @@ static int tmp401_init_client(struct tmp401_data *data, config_orig = config; config &= ~TMP401_CONFIG_SHUTDOWN; + data->extended_range = !!(config & TMP401_CONFIG_RANGE); + if (config != config_orig) status = i2c_smbus_write_byte_data(client, TMP401_CONFIG_WRITE, @@ -654,9 +590,10 @@ static int tmp401_probe(struct i2c_client *client) "TMP401", "TMP411", "TMP431", "TMP432", "TMP435" }; struct device *dev = &client->dev; + struct hwmon_channel_info *info; struct device *hwmon_dev; struct tmp401_data *data; - int groups = 0, status; + int status; data = devm_kzalloc(dev, sizeof(struct tmp401_data), GFP_KERNEL); if (!data) @@ -666,24 +603,49 @@ static int tmp401_probe(struct i2c_client *client) mutex_init(&data->update_lock); data->kind = i2c_match_id(tmp401_id, client)->driver_data; + /* initialize configuration data */ + data->chip.ops = &tmp401_ops; + data->chip.info = data->info; + + data->info[0] = &data->chip_info; + data->info[1] = &data->temp_info; + + info = &data->chip_info; + info->type = hwmon_chip; + info->config = data->chip_channel_config; + + data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; + + info = &data->temp_info; + info->type = hwmon_temp; + info->config = data->temp_channel_config; + + data->temp_channel_config[0] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM; + data->temp_channel_config[1] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT; + + if (data->kind == tmp411) { + data->temp_channel_config[0] |= HWMON_T_HIGHEST | HWMON_T_LOWEST; + data->temp_channel_config[1] |= HWMON_T_HIGHEST | HWMON_T_LOWEST; + data->chip_channel_config[0] |= HWMON_C_TEMP_RESET_HISTORY; + } + + if (data->kind == tmp432) { + data->temp_channel_config[2] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT; + } + /* Initialize the TMP401 chip */ status = tmp401_init_client(data, client); if (status < 0) return status; - /* Register sysfs hooks */ - data->groups[groups++] = &tmp401_group; - - /* Register additional tmp411 sysfs hooks */ - if (data->kind == tmp411) - data->groups[groups++] = &tmp411_group; - - /* Register additional tmp432 sysfs hooks */ - if (data->kind == tmp432) - data->groups[groups++] = &tmp432_group; - - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, data->groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &data->chip, NULL); if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); -- cgit v1.2.3-59-g8ed1b From 50152fb6c1a197271b8916b8225525a6ca71e9ee Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 16 Oct 2021 14:56:29 -0700 Subject: hwmon: (tmp401) Use regmap Use regmap for register accesses to be able to utilize its caching functionality. This also lets us hide register access differences in regmap code. Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/tmp401.c | 225 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 150 insertions(+), 76 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8815e96911d1..36e777de3565 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1951,6 +1951,7 @@ config SENSORS_TMP108 config SENSORS_TMP401 tristate "Texas Instruments TMP401 and compatibles" depends on I2C + select REGMAP help If you say yes here you get support for Texas Instruments TMP401, TMP411, TMP431, TMP432, and TMP435 temperature sensor chips. diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index bcd8edfbd5e0..00b6e4275a45 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -25,6 +25,7 @@ #include #include #include +#include #include /* Addresses to scan */ @@ -114,6 +115,7 @@ MODULE_DEVICE_TABLE(i2c, tmp401_id); struct tmp401_data { struct i2c_client *client; + struct regmap *regmap; struct mutex update_lock; enum chips kind; @@ -128,32 +130,30 @@ struct tmp401_data { struct hwmon_chip_info chip; }; -static int tmp401_register_to_temp(u16 reg, bool extended) -{ - int temp = reg; - - if (extended) - temp -= 64 * 256; +/* regmap */ - return DIV_ROUND_CLOSEST(temp * 125, 32); -} - -static u16 tmp401_temp_to_register(long temp, bool extended, int zbits) +static bool tmp401_regmap_is_volatile(struct device *dev, unsigned int reg) { - if (extended) { - temp = clamp_val(temp, -64000, 191000); - temp += 64000; - } else { - temp = clamp_val(temp, 0, 127000); + switch (reg) { + case 0: /* local temp msb */ + case 1: /* remote temp msb */ + case 2: /* status */ + case 0x10: /* remote temp lsb */ + case 0x15: /* local temp lsb */ + case 0x1b: /* status (tmp432) */ + case 0x23 ... 0x24: /* remote temp 2 msb / lsb */ + case 0x30 ... 0x37: /* lowest/highest temp; status (tmp432) */ + return true; + default: + return false; } - - return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; } -static int tmp401_reg_read(struct tmp401_data *data, unsigned int reg) +static int tmp401_reg_read(void *context, unsigned int reg, unsigned int *val) { + struct tmp401_data *data = context; struct i2c_client *client = data->client; - int val, regval; + int regval; switch (reg) { case 0: /* local temp msb */ @@ -172,55 +172,71 @@ static int tmp401_reg_read(struct tmp401_data *data, unsigned int reg) /* work around register overlap between TMP411 and TMP432 */ if (reg == 0xf6) reg = 0x36; - return i2c_smbus_read_word_swapped(client, reg); + regval = i2c_smbus_read_word_swapped(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; case 0x19: /* critical limits, 8-bit registers */ case 0x1a: case 0x20: regval = i2c_smbus_read_byte_data(client, reg); if (regval < 0) return regval; - return regval << 8; + *val = regval << 8; + break; case 0x1b: case 0x35 ... 0x37: - if (data->kind == tmp432) - return i2c_smbus_read_byte_data(client, reg); + if (data->kind == tmp432) { + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; + } /* simulate TMP432 status registers */ regval = i2c_smbus_read_byte_data(client, TMP401_STATUS); if (regval < 0) return regval; - val = 0; + *val = 0; switch (reg) { case 0x1b: /* open / fault */ if (regval & TMP401_STATUS_REMOTE_OPEN) - val |= BIT(1); + *val |= BIT(1); break; case 0x35: /* high limit */ if (regval & TMP401_STATUS_LOCAL_HIGH) - val |= BIT(0); + *val |= BIT(0); if (regval & TMP401_STATUS_REMOTE_HIGH) - val |= BIT(1); + *val |= BIT(1); break; case 0x36: /* low limit */ if (regval & TMP401_STATUS_LOCAL_LOW) - val |= BIT(0); + *val |= BIT(0); if (regval & TMP401_STATUS_REMOTE_LOW) - val |= BIT(1); + *val |= BIT(1); break; case 0x37: /* therm / crit limit */ if (regval & TMP401_STATUS_LOCAL_CRIT) - val |= BIT(0); + *val |= BIT(0); if (regval & TMP401_STATUS_REMOTE_CRIT) - val |= BIT(1); + *val |= BIT(1); break; } - return val; + break; default: - return i2c_smbus_read_byte_data(client, reg); + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; } + return 0; } -static int tmp401_reg_write(struct tmp401_data *data, unsigned int reg, unsigned int val) +static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val) { + struct tmp401_data *data = context; struct i2c_client *client = data->client; switch (reg) { @@ -240,6 +256,41 @@ static int tmp401_reg_write(struct tmp401_data *data, unsigned int reg, unsigned } } +static const struct regmap_config tmp401_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tmp401_regmap_is_volatile, + .reg_read = tmp401_reg_read, + .reg_write = tmp401_reg_write, +}; + +/* temperature conversion */ + +static int tmp401_register_to_temp(u16 reg, bool extended) +{ + int temp = reg; + + if (extended) + temp -= 64 * 256; + + return DIV_ROUND_CLOSEST(temp * 125, 32); +} + +static u16 tmp401_temp_to_register(long temp, bool extended, int zbits) +{ + if (extended) { + temp = clamp_val(temp, -64000, 191000); + temp += 64000; + } else { + temp = clamp_val(temp, 0, 127000); + } + + return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; +} + +/* hwmon API functions */ + static const u8 tmp401_temp_reg_index[] = { [hwmon_temp_input] = 0, [hwmon_temp_min] = 1, @@ -259,7 +310,9 @@ static const u8 tmp401_status_reg_index[] = { static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val) { struct tmp401_data *data = dev_get_drvdata(dev); - int reg, regval; + struct regmap *regmap = data->regmap; + unsigned int regval; + int reg, ret; switch (attr) { case hwmon_temp_input: @@ -269,36 +322,35 @@ static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val case hwmon_temp_lowest: case hwmon_temp_highest: reg = TMP401_TEMP_MSB_READ[tmp401_temp_reg_index[attr]][channel]; - regval = tmp401_reg_read(data, reg); - if (regval < 0) - return regval; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + return ret; *val = tmp401_register_to_temp(regval, data->extended_range); break; case hwmon_temp_crit_hyst: mutex_lock(&data->update_lock); reg = TMP401_TEMP_MSB_READ[3][channel]; - regval = tmp401_reg_read(data, reg); - if (regval < 0) + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) goto unlock; *val = tmp401_register_to_temp(regval, data->extended_range); - regval = tmp401_reg_read(data, TMP401_TEMP_CRIT_HYST); - if (regval < 0) + ret = regmap_read(regmap, TMP401_TEMP_CRIT_HYST, ®val); + if (ret < 0) goto unlock; *val -= regval * 1000; - regval = 0; unlock: mutex_unlock(&data->update_lock); - if (regval < 0) - return regval; + if (ret < 0) + return ret; break; case hwmon_temp_fault: case hwmon_temp_min_alarm: case hwmon_temp_max_alarm: case hwmon_temp_crit_alarm: reg = TMP432_STATUS_REG[tmp401_status_reg_index[attr]]; - regval = tmp401_reg_read(data, reg); - if (regval < 0) - return regval; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + return ret; *val = !!(regval & BIT(channel)); break; default: @@ -311,7 +363,9 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel, long val) { struct tmp401_data *data = dev_get_drvdata(dev); - int reg, regval, ret, temp; + struct regmap *regmap = data->regmap; + unsigned int regval; + int reg, ret, temp; mutex_lock(&data->update_lock); switch (attr) { @@ -321,7 +375,14 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel, reg = TMP401_TEMP_MSB_WRITE[tmp401_temp_reg_index[attr]][channel]; regval = tmp401_temp_to_register(val, data->extended_range, attr == hwmon_temp_crit ? 8 : 4); - ret = tmp401_reg_write(data, reg, regval); + ret = regmap_write(regmap, reg, regval); + if (ret) + break; + /* + * Read and write limit registers are different, so we need to + * reinitialize the cache. + */ + ret = regmap_reinit_cache(regmap, &tmp401_regmap_config); break; case hwmon_temp_crit_hyst: if (data->extended_range) @@ -330,13 +391,13 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel, val = clamp_val(val, 0, 127000); reg = TMP401_TEMP_MSB_READ[3][channel]; - ret = tmp401_reg_read(data, reg); + ret = regmap_read(regmap, reg, ®val); if (ret < 0) break; - temp = tmp401_register_to_temp(ret, data->extended_range); + temp = tmp401_register_to_temp(regval, data->extended_range); val = clamp_val(val, temp - 255000, temp); regval = ((temp - val) + 500) / 1000; - ret = tmp401_reg_write(data, TMP401_TEMP_CRIT_HYST, regval); + ret = regmap_write(regmap, TMP401_TEMP_CRIT_HYST, regval); break; default: ret = -EOPNOTSUPP; @@ -349,13 +410,14 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel, static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val) { struct tmp401_data *data = dev_get_drvdata(dev); - int regval; + u32 regval; + int ret; switch (attr) { case hwmon_chip_update_interval: - regval = tmp401_reg_read(data, TMP401_CONVERSION_RATE_READ); - if (regval < 0) - return regval; + ret = regmap_read(data->regmap, TMP401_CONVERSION_RATE_READ, ®val); + if (ret < 0) + return ret; *val = (1 << (7 - regval)) * 125; break; case hwmon_chip_temp_reset_history: @@ -368,7 +430,7 @@ static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val return 0; } -static int tmp401_set_convrate(struct i2c_client *client, struct tmp401_data *data, long val) +static int tmp401_set_convrate(struct regmap *regmap, long val) { int err, rate; @@ -382,22 +444,26 @@ static int tmp401_set_convrate(struct i2c_client *client, struct tmp401_data *da */ val = clamp_val(val, 125, 16000); rate = 7 - __fls(val * 4 / (125 * 3)); - err = i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, rate); + err = regmap_write(regmap, TMP401_CONVERSION_RATE_WRITE, rate); if (err) return err; - return 0; + /* + * Read and write conversion rate registers are different, so we need to + * reinitialize the cache. + */ + return regmap_reinit_cache(regmap, &tmp401_regmap_config); } static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val) { struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; + struct regmap *regmap = data->regmap; int err; mutex_lock(&data->update_lock); switch (attr) { case hwmon_chip_update_interval: - err = tmp401_set_convrate(client, data, val); + err = tmp401_set_convrate(regmap, val); break; case hwmon_chip_temp_reset_history: if (val != 1) { @@ -408,7 +474,7 @@ static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val * Reset history by writing any value to any of the * minimum/maximum registers (0x30-0x37). */ - err = i2c_smbus_write_byte_data(client, 0x30, 0); + err = regmap_write(regmap, 0x30, 0); break; default: err = -EOPNOTSUPP; @@ -489,18 +555,23 @@ static const struct hwmon_ops tmp401_ops = { .write = tmp401_write, }; -static int tmp401_init_client(struct tmp401_data *data, - struct i2c_client *client) +/* chip initialization, detect, probe */ + +static int tmp401_init_client(struct tmp401_data *data) { - int config, config_orig, status = 0; + struct regmap *regmap = data->regmap; + u32 config, config_orig; + int ret; - /* Set the conversion rate to 2 Hz */ - i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, 5); + /* Set conversion rate to 2 Hz */ + ret = regmap_write(regmap, TMP401_CONVERSION_RATE_WRITE, 5); + if (ret < 0) + return ret; /* Start conversions (disable shutdown if necessary) */ - config = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); - if (config < 0) - return config; + ret = regmap_read(regmap, TMP401_CONFIG_READ, &config); + if (ret < 0) + return ret; config_orig = config; config &= ~TMP401_CONFIG_SHUTDOWN; @@ -508,11 +579,9 @@ static int tmp401_init_client(struct tmp401_data *data, data->extended_range = !!(config & TMP401_CONFIG_RANGE); if (config != config_orig) - status = i2c_smbus_write_byte_data(client, - TMP401_CONFIG_WRITE, - config); + ret = regmap_write(regmap, TMP401_CONFIG_WRITE, config); - return status; + return ret; } static int tmp401_detect(struct i2c_client *client, @@ -603,6 +672,10 @@ static int tmp401_probe(struct i2c_client *client) mutex_init(&data->update_lock); data->kind = i2c_match_id(tmp401_id, client)->driver_data; + data->regmap = devm_regmap_init(dev, NULL, data, &tmp401_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + /* initialize configuration data */ data->chip.ops = &tmp401_ops; data->chip.info = data->info; @@ -640,7 +713,7 @@ static int tmp401_probe(struct i2c_client *client) } /* Initialize the TMP401 chip */ - status = tmp401_init_client(data, client); + status = tmp401_init_client(data); if (status < 0) return status; -- cgit v1.2.3-59-g8ed1b From ff300b71ba3860d282f77774d2b02ec49ad0e9cf Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Nov 2021 13:18:26 -0800 Subject: hwmon: (tmp401) Hide register write address differences in regmap code Since we are using regmap access functions to write into chip registers, we can hide the difference in register write addresses within regmap code. By doing this we do not need to clear the regmap cache on register writes, and the high level code does not need to bother about different read/write register addresses. Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 69 ++++++++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 00b6e4275a45..b86d9df7105d 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -39,15 +39,13 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; * reading and writing */ #define TMP401_STATUS 0x02 -#define TMP401_CONFIG_READ 0x03 -#define TMP401_CONFIG_WRITE 0x09 -#define TMP401_CONVERSION_RATE_READ 0x04 -#define TMP401_CONVERSION_RATE_WRITE 0x0A +#define TMP401_CONFIG 0x03 +#define TMP401_CONVERSION_RATE 0x04 #define TMP401_TEMP_CRIT_HYST 0x21 #define TMP401_MANUFACTURER_ID_REG 0xFE #define TMP401_DEVICE_ID_REG 0xFF -static const u8 TMP401_TEMP_MSB_READ[7][3] = { +static const u8 TMP401_TEMP_MSB[7][3] = { { 0x00, 0x01, 0x23 }, /* temp */ { 0x06, 0x08, 0x16 }, /* low limit */ { 0x05, 0x07, 0x15 }, /* high limit */ @@ -56,15 +54,6 @@ static const u8 TMP401_TEMP_MSB_READ[7][3] = { { 0x32, 0xf6, 0x00 }, /* highest */ }; -static const u8 TMP401_TEMP_MSB_WRITE[7][3] = { - { 0x00, 0x00, 0x00 }, /* temp (unused) */ - { 0x0C, 0x0E, 0x16 }, /* low limit */ - { 0x0B, 0x0D, 0x15 }, /* high limit */ - { 0x20, 0x19, 0x1a }, /* therm (crit) limit */ - { 0x30, 0x34, 0x00 }, /* lowest */ - { 0x32, 0xf6, 0x00 }, /* highest */ -}; - /* [0] = fault, [1] = low, [2] = high, [3] = therm/crit */ static const u8 TMP432_STATUS_REG[] = { 0x1b, 0x36, 0x35, 0x37 }; @@ -240,10 +229,12 @@ static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val) struct i2c_client *client = data->client; switch (reg) { - case 0x0b: /* local temp high limit msb */ - case 0x0c: /* local temp low limit msb */ - case 0x0d: /* remote temp ligh limit msb */ - case 0x0e: /* remote temp low limit msb */ + case 0x05: /* local temp high limit msb */ + case 0x06: /* local temp low limit msb */ + case 0x07: /* remote temp ligh limit msb */ + case 0x08: /* remote temp low limit msb */ + reg += 6; /* adjust for register write address */ + fallthrough; case 0x15: /* remote temp 2 high limit msb */ case 0x16: /* remote temp 2 low limit msb */ return i2c_smbus_write_word_swapped(client, reg, val); @@ -251,6 +242,10 @@ static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val) case 0x1a: case 0x20: return i2c_smbus_write_byte_data(client, reg, val >> 8); + case TMP401_CONVERSION_RATE: + case TMP401_CONFIG: + reg += 6; /* adjust for register write address */ + fallthrough; default: return i2c_smbus_write_byte_data(client, reg, val); } @@ -321,7 +316,7 @@ static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val case hwmon_temp_crit: case hwmon_temp_lowest: case hwmon_temp_highest: - reg = TMP401_TEMP_MSB_READ[tmp401_temp_reg_index[attr]][channel]; + reg = TMP401_TEMP_MSB[tmp401_temp_reg_index[attr]][channel]; ret = regmap_read(regmap, reg, ®val); if (ret < 0) return ret; @@ -329,7 +324,7 @@ static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val break; case hwmon_temp_crit_hyst: mutex_lock(&data->update_lock); - reg = TMP401_TEMP_MSB_READ[3][channel]; + reg = TMP401_TEMP_MSB[3][channel]; ret = regmap_read(regmap, reg, ®val); if (ret < 0) goto unlock; @@ -372,17 +367,10 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel, case hwmon_temp_min: case hwmon_temp_max: case hwmon_temp_crit: - reg = TMP401_TEMP_MSB_WRITE[tmp401_temp_reg_index[attr]][channel]; + reg = TMP401_TEMP_MSB[tmp401_temp_reg_index[attr]][channel]; regval = tmp401_temp_to_register(val, data->extended_range, attr == hwmon_temp_crit ? 8 : 4); ret = regmap_write(regmap, reg, regval); - if (ret) - break; - /* - * Read and write limit registers are different, so we need to - * reinitialize the cache. - */ - ret = regmap_reinit_cache(regmap, &tmp401_regmap_config); break; case hwmon_temp_crit_hyst: if (data->extended_range) @@ -390,7 +378,7 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel, else val = clamp_val(val, 0, 127000); - reg = TMP401_TEMP_MSB_READ[3][channel]; + reg = TMP401_TEMP_MSB[3][channel]; ret = regmap_read(regmap, reg, ®val); if (ret < 0) break; @@ -415,7 +403,7 @@ static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val switch (attr) { case hwmon_chip_update_interval: - ret = regmap_read(data->regmap, TMP401_CONVERSION_RATE_READ, ®val); + ret = regmap_read(data->regmap, TMP401_CONVERSION_RATE, ®val); if (ret < 0) return ret; *val = (1 << (7 - regval)) * 125; @@ -432,7 +420,7 @@ static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val static int tmp401_set_convrate(struct regmap *regmap, long val) { - int err, rate; + int rate; /* * For valid rates, interval can be calculated as @@ -444,14 +432,7 @@ static int tmp401_set_convrate(struct regmap *regmap, long val) */ val = clamp_val(val, 125, 16000); rate = 7 - __fls(val * 4 / (125 * 3)); - err = regmap_write(regmap, TMP401_CONVERSION_RATE_WRITE, rate); - if (err) - return err; - /* - * Read and write conversion rate registers are different, so we need to - * reinitialize the cache. - */ - return regmap_reinit_cache(regmap, &tmp401_regmap_config); + return regmap_write(regmap, TMP401_CONVERSION_RATE, rate); } static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val) @@ -564,12 +545,12 @@ static int tmp401_init_client(struct tmp401_data *data) int ret; /* Set conversion rate to 2 Hz */ - ret = regmap_write(regmap, TMP401_CONVERSION_RATE_WRITE, 5); + ret = regmap_write(regmap, TMP401_CONVERSION_RATE, 5); if (ret < 0) return ret; /* Start conversions (disable shutdown if necessary) */ - ret = regmap_read(regmap, TMP401_CONFIG_READ, &config); + ret = regmap_read(regmap, TMP401_CONFIG, &config); if (ret < 0) return ret; @@ -579,7 +560,7 @@ static int tmp401_init_client(struct tmp401_data *data) data->extended_range = !!(config & TMP401_CONFIG_RANGE); if (config != config_orig) - ret = regmap_write(regmap, TMP401_CONFIG_WRITE, config); + ret = regmap_write(regmap, TMP401_CONFIG, config); return ret; } @@ -639,11 +620,11 @@ static int tmp401_detect(struct i2c_client *client, return -ENODEV; } - reg = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); + reg = i2c_smbus_read_byte_data(client, TMP401_CONFIG); if (reg & 0x1b) return -ENODEV; - reg = i2c_smbus_read_byte_data(client, TMP401_CONVERSION_RATE_READ); + reg = i2c_smbus_read_byte_data(client, TMP401_CONVERSION_RATE); /* Datasheet says: 0x1-0x6 */ if (reg > 15) return -ENODEV; -- cgit v1.2.3-59-g8ed1b From ff9b8778797940628934205010d12b7084c85447 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 14 Nov 2021 22:06:35 -0800 Subject: hwmon: (adm1021) Improve detection of LM84, MAX1617, and MAX1617A The adm1021 driver is quite generous with its automatic chip detection and easily misdetects several chips. Strengthen detection of MAX1617, MAX1617A, and LM84 to make the driver less vulnerable to false matches. Signed-off-by: Guenter Roeck --- drivers/hwmon/adm1021.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/adm1021.c b/drivers/hwmon/adm1021.c index 38b447c6e8cd..91ecfee243bf 100644 --- a/drivers/hwmon/adm1021.c +++ b/drivers/hwmon/adm1021.c @@ -324,7 +324,7 @@ static int adm1021_detect(struct i2c_client *client, { struct i2c_adapter *adapter = client->adapter; const char *type_name; - int conv_rate, status, config, man_id, dev_id; + int reg, conv_rate, status, config, man_id, dev_id; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { pr_debug("detect failed, smbus byte data not supported!\n"); @@ -349,9 +349,19 @@ static int adm1021_detect(struct i2c_client *client, if (man_id < 0 || dev_id < 0) return -ENODEV; - if (man_id == 0x4d && dev_id == 0x01) + if (man_id == 0x4d && dev_id == 0x01) { + /* + * dev_id 0x01 matches MAX6680, MAX6695, MAX6696, and possibly + * others. Read register which is unsupported on MAX1617 but + * exists on all those chips and compare with the dev_id + * register. If it matches, it may be a MAX1617A. + */ + reg = i2c_smbus_read_byte_data(client, + ADM1023_REG_REM_TEMP_PREC); + if (reg != dev_id) + return -ENODEV; type_name = "max1617a"; - else if (man_id == 0x41) { + } else if (man_id == 0x41) { if ((dev_id & 0xF0) == 0x30) type_name = "adm1023"; else if ((dev_id & 0xF0) == 0x00) @@ -395,13 +405,18 @@ static int adm1021_detect(struct i2c_client *client, /* * LM84 Mfr ID is in a different place, - * and it has more unused bits. + * and it has more unused bits. Registers at 0xfe and 0xff + * are undefined and return the most recently read value, + * here the value of the configuration register. */ if (conv_rate == 0x00 + && man_id == config && dev_id == config && (config & 0x7F) == 0x00 && (status & 0xAB) == 0x00) { type_name = "lm84"; } else { + if ((config & 0x3f) || (status & 0x03)) + return -ENODEV; /* fail if low limits are larger than high limits */ if ((s8)llo > lhi || (s8)rlo > rhi) return -ENODEV; -- cgit v1.2.3-59-g8ed1b From df293076a903530d7d12966ca19aa9570a6c70b3 Mon Sep 17 00:00:00 2001 From: Menghui Wu Date: Wed, 17 Nov 2021 10:43:20 +0800 Subject: hwmon: (f71882fg) Add F81966 support This adds hardware monitor support the Fintek F81966 Super I/O chip. Testing was done on the Aaeon SSE-IPTI Signed-off-by: Menghui Wu Signed-off-by: Chia-Lin Kao (AceLan) Link: https://lore.kernel.org/r/20211117024320.2428144-1-acelan.kao@canonical.com Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 4673d403759a..938a8b9ec70d 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -49,6 +49,7 @@ #define SIO_F81768D_ID 0x1210 /* Chipset ID */ #define SIO_F81865_ID 0x0704 /* Chipset ID */ #define SIO_F81866_ID 0x1010 /* Chipset ID */ +#define SIO_F81966_ID 0x1502 /* Chipset ID */ #define REGION_LENGTH 8 #define ADDR_REG_OFFSET 5 @@ -2672,6 +2673,7 @@ static int __init f71882fg_find(int sioaddr, struct f71882fg_sio_data *sio_data) sio_data->type = f81865f; break; case SIO_F81866_ID: + case SIO_F81966_ID: sio_data->type = f81866a; break; default: -- cgit v1.2.3-59-g8ed1b From b87611d43757c131e5f272b42f0561faed52029e Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Tue, 16 Nov 2021 22:57:43 +0200 Subject: hwmon: (asus_wmi_ec_sensors) Support B550 Asus WMI. Linux HWMON sensors driver for ASUS motherboards to read sensors from the embedded controller. Many ASUS motherboards do not publish all the available sensors via the Super I/O chip but the missing ones are available through the embedded controller (EC) registers. This driver implements reading those sensor data via the WMI method BREC, which is known to be present in all ASUS motherboards based on the AMD 500 series chipsets (and probably is available in other models too). The driver needs to know exact register addresses for the sensors and thus support for each motherboard has to be added explicitly. The EC registers do not provide critical values for the sensors and as such they are not published to the HWMON. Supported motherboards: * PRIME X570-PRO * Pro WS X570-ACE * ROG CROSSHAIR VIII HERO * ROG CROSSHAIR VIII DARK HERO * ROG CROSSHAIR VIII FORMULA * ROG STRIX X570-E GAMING * ROG STRIX B550-I GAMING * ROG STRIX B550-E GAMING Co-developed-by: Eugene Shalygin Signed-off-by: Eugene Shalygin Co-developed-by: Andy Shevchenko Signed-off-by: Andy Shevchenko Signed-off-by: Denis Pauk Tested-by: Tor Vic Tested-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_wmi_ec_sensors.rst | 38 ++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 7 + drivers/hwmon/Kconfig | 12 + drivers/hwmon/Makefile | 1 + drivers/hwmon/asus_wmi_ec_sensors.c | 621 ++++++++++++++++++++++++++++ 6 files changed, 680 insertions(+) create mode 100644 Documentation/hwmon/asus_wmi_ec_sensors.rst create mode 100644 drivers/hwmon/asus_wmi_ec_sensors.c diff --git a/Documentation/hwmon/asus_wmi_ec_sensors.rst b/Documentation/hwmon/asus_wmi_ec_sensors.rst new file mode 100644 index 000000000000..1b287f229e86 --- /dev/null +++ b/Documentation/hwmon/asus_wmi_ec_sensors.rst @@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_wmi_ec_sensors +================================= + +Supported boards: + * PRIME X570-PRO, + * Pro WS X570-ACE, + * ROG CROSSHAIR VIII DARK HERO, + * ROG CROSSHAIR VIII FORMULA, + * ROG CROSSHAIR VIII HERO, + * ROG STRIX B550-E GAMING, + * ROG STRIX B550-I GAMING, + * ROG STRIX X570-E GAMING. + +Authors: + - Eugene Shalygin + +Description: +------------ +ASUS mainboards publish hardware monitoring information via Super I/O +chip and the ACPI embedded controller (EC) registers. Some of the sensors +are only available via the EC. + +ASUS WMI interface provides a method (BREC) to read data from EC registers, +which is utilized by this driver to publish those sensor readings to the +HWMON system. The driver is aware of and reads the following sensors: + +1. Chipset (PCH) temperature +2. CPU package temperature +3. Motherboard temperature +4. Readings from the T_Sensor header +5. VRM temperature +6. CPU_Opt fan RPM +7. Chipset fan RPM +8. Readings from the "Water flow meter" header (RPM) +9. Readings from the "Water In" and "Water Out" temperature headers +10. CPU current diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 6f30c8c9c76a..3e196eaff360 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -43,6 +43,7 @@ Hardware Monitoring Kernel Drivers asb100 asc7621 aspeed-pwm-tacho + asus_wmi_ec_sensors bcm54140 bel-pfe bpa-rs600 diff --git a/MAINTAINERS b/MAINTAINERS index fb18ce7168aa..6b386f91e697 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2998,6 +2998,13 @@ W: http://acpi4asus.sf.net F: drivers/platform/x86/asus*.c F: drivers/platform/x86/eeepc*.c +ASUS WMI EC HARDWARE MONITOR DRIVER +M: Eugene Shalygin +M: Denis Pauk +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/asus_wmi_ec_sensors.c + ASUS WIRELESS RADIO CONTROL DRIVER M: João Paulo Rechi Vita L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 36e777de3565..81f5e2ddbdf5 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2228,6 +2228,18 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. +config SENSORS_ASUS_WMI_EC + tristate "ASUS WMI B550/X570" + depends on ACPI_WMI + help + If you say yes here you get support for the ACPI embedded controller + hardware monitoring interface found in B550/X570 ASUS motherboards. + This driver will provide readings of fans, voltages and temperatures + through the system firmware. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors_ec. + endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 1ddb26f57a6f..821345e49107 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o # APCI drivers obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. diff --git a/drivers/hwmon/asus_wmi_ec_sensors.c b/drivers/hwmon/asus_wmi_ec_sensors.c new file mode 100644 index 000000000000..f612abc66c89 --- /dev/null +++ b/drivers/hwmon/asus_wmi_ec_sensors.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS B550/X570 motherboards that publish sensor + * values via the embedded controller registers. + * + * Copyright (C) 2021 Eugene Shalygin + * Copyright (C) 2018-2019 Ed Brindley + * + * EC provides: + * - Chipset temperature + * - CPU temperature + * - Motherboard temperature + * - T_Sensor temperature + * - VRM temperature + * - Water In temperature + * - Water Out temperature + * - CPU Optional Fan RPM + * - Chipset Fan RPM + * - Water Flow Fan RPM + * - CPU current + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_BLOCK_READ_EC 0x42524543 /* BREC */ +/* From the ASUS DSDT source */ +#define ASUSWMI_BREC_REGISTERS_MAX 16 +#define ASUSWMI_MAX_BUF_LEN 128 +#define SENSOR_LABEL_LEN 16 + +static u32 hwmon_attributes[] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +struct asus_wmi_ec_sensor_address { + u8 index; + u8 bank; + u8 size; +}; + +#define MAKE_SENSOR_ADDRESS(size_i, bank_i, index_i) { \ + .size = size_i, \ + .bank = bank_i, \ + .index = index_i, \ +} + +struct ec_sensor_info { + struct asus_wmi_ec_sensor_address addr; + char label[SENSOR_LABEL_LEN]; + enum hwmon_sensor_types type; +}; + +#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \ + .addr = MAKE_SENSOR_ADDRESS(size, bank, index), \ + .label = sensor_label, \ + .type = sensor_type, \ +} + +enum known_ec_sensor { + SENSOR_TEMP_CHIPSET, + SENSOR_TEMP_CPU, + SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, + SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_FAN_CHIPSET, + SENSOR_FAN_VRM_HS, + SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_TEMP_WATER_IN, + SENSOR_TEMP_WATER_OUT, + SENSOR_MAX +}; + +/* All known sensors for ASUS EC controllers */ +static const struct ec_sensor_info known_ec_sensors[] = { + [SENSOR_TEMP_CHIPSET] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [SENSOR_TEMP_CPU] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [SENSOR_TEMP_MB] = EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [SENSOR_TEMP_T_SENSOR] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [SENSOR_TEMP_VRM] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [SENSOR_FAN_CPU_OPT] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [SENSOR_FAN_VRM_HS] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [SENSOR_FAN_CHIPSET] = EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4), + [SENSOR_FAN_WATER_FLOW] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc), + [SENSOR_CURR_CPU] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [SENSOR_TEMP_WATER_IN] = EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), + [SENSOR_TEMP_WATER_OUT] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), +}; + +struct asus_wmi_data { + const enum known_ec_sensor known_board_sensors[SENSOR_MAX + 1]; +}; + +/* boards with EC support */ +static struct asus_wmi_data sensors_board_PW_X570_P = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_PW_X570_A = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_R_C8H = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without chipset fan */ +static struct asus_wmi_data sensors_board_R_C8DH = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without water */ +static struct asus_wmi_data sensors_board_R_C8F = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_I_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_VRM_HS, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_X570_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, sensors) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ + .driver_data = sensors, \ +} + +static const struct dmi_system_id asus_wmi_ec_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", &sensors_board_PW_X570_P), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &sensors_board_PW_X570_A), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", &sensors_board_R_C8DH), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII FORMULA", &sensors_board_R_C8F), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO", &sensors_board_R_C8H), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &sensors_board_RS_B550_E_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &sensors_board_RS_B550_I_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", &sensors_board_RS_X570_E_G), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_ec_dmi_table); + +struct ec_sensor { + enum known_ec_sensor info_index; + long cached_value; +}; + +/** + * struct asus_wmi_ec_info - sensor info. + * @sensors: list of sensors. + * @read_arg: UTF-16LE string to pass to BRxx() WMI function. + * @read_buffer: decoded output from WMI result. + * @nr_sensors: number of board EC sensors. + * @nr_registers: number of EC registers to read (sensor might span more than 1 register). + * @last_updated: in jiffies. + */ +struct asus_wmi_ec_info { + struct ec_sensor sensors[SENSOR_MAX]; + char read_arg[(ASUSWMI_BREC_REGISTERS_MAX * 4 + 1) * 2]; + u8 read_buffer[ASUSWMI_BREC_REGISTERS_MAX]; + unsigned int nr_sensors; + unsigned int nr_registers; + unsigned long last_updated; +}; + +struct asus_wmi_sensors { + struct asus_wmi_ec_info ec; + /* lock access to internal cache */ + struct mutex lock; +}; + +static int asus_wmi_ec_fill_board_sensors(struct asus_wmi_ec_info *ec, + const enum known_ec_sensor *bsi) +{ + struct ec_sensor *s = ec->sensors; + int i; + + ec->nr_sensors = 0; + ec->nr_registers = 0; + + for (i = 0; bsi[i] != SENSOR_MAX; i++) { + s[i].info_index = bsi[i]; + ec->nr_sensors++; + ec->nr_registers += known_ec_sensors[bsi[i]].addr.size; + } + + return 0; +} + +/* + * The next four functions convert to or from BRxx string argument format. + * The format of the string is as follows: + * - The string consists of two-byte UTF-16LE characters. + * - The value of the very first byte in the string is equal to the total + * length of the next string in bytes, thus excluding the first two-byte + * character. + * - The rest of the string encodes the pairs of (bank, index) pairs, where + * both values are byte-long (0x00 to 0xFF). + * - Numbers are encoded as UTF-16LE hex values. + */ +static int asus_wmi_ec_decode_reply_buffer(const u8 *in, u32 length, u8 *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + u32 len = min_t(u32, get_unaligned_le16(in), length - 2); + + utf16s_to_utf8s((wchar_t *)(in + 2), len / 2, UTF16_LITTLE_ENDIAN, buffer, sizeof(buffer)); + + return hex2bin(out, buffer, len / 4); +} + +static void asus_wmi_ec_encode_registers(const u8 *in, u32 len, char *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + + bin2hex(buffer, in, len); + + utf8s_to_utf16s(buffer, len * 2, UTF16_LITTLE_ENDIAN, (wchar_t *)(out + 2), len * 2); + + put_unaligned_le16(len * 4, out); +} + +static void asus_wmi_ec_make_block_read_query(struct asus_wmi_ec_info *ec) +{ + u8 registers[ASUSWMI_BREC_REGISTERS_MAX * 2]; + const struct ec_sensor_info *si; + int i, j, offset; + + offset = 0; + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + for (j = 0; j < si->addr.size; j++) { + registers[offset++] = si->addr.bank; + registers[offset++] = si->addr.index + j; + } + } + + asus_wmi_ec_encode_registers(registers, offset, ec->read_arg); +} + +static int asus_wmi_ec_block_read(u32 method_id, char *query, u8 *out) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret; + + /* The first byte of the BRxx() argument string has to be the string size. */ + input.length = query[0] + 2; + input.pointer = query; + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) { + ret = -EIO; + goto out_free_obj; + } + + ret = asus_wmi_ec_decode_reply_buffer(obj->buffer.pointer, obj->buffer.length, out); + +out_free_obj: + ACPI_FREE(obj); + return ret; +} + +static inline long get_sensor_value(const struct ec_sensor_info *si, u8 *data) +{ + switch (si->addr.size) { + case 1: + return *data; + case 2: + return get_unaligned_be16(data); + case 4: + return get_unaligned_be32(data); + default: + return 0; + } +} + +static void asus_wmi_ec_update_ec_sensors(struct asus_wmi_ec_info *ec) +{ + const struct ec_sensor_info *si; + struct ec_sensor *s; + u8 i_sensor; + u8 *data; + + data = ec->read_buffer; + for (i_sensor = 0; i_sensor < ec->nr_sensors; i_sensor++) { + s = &ec->sensors[i_sensor]; + si = &known_ec_sensors[s->info_index]; + s->cached_value = get_sensor_value(si, data); + data += si->addr.size; + } +} + +static long asus_wmi_ec_scale_sensor_value(long value, int data_type) +{ + switch (data_type) { + case hwmon_curr: + case hwmon_temp: + case hwmon_in: + return value * MILLI; + default: + return value; + } +} + +static int asus_wmi_ec_find_sensor_index(const struct asus_wmi_ec_info *ec, + enum hwmon_sensor_types type, int channel) +{ + int i; + + for (i = 0; i < ec->nr_sensors; i++) { + if (known_ec_sensors[ec->sensors[i].info_index].type == type) { + if (channel == 0) + return i; + + channel--; + } + } + return -EINVAL; +} + +static int asus_wmi_ec_get_cached_value_or_update(struct asus_wmi_sensors *sensor_data, + int sensor_index, + long *value) +{ + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, ec->last_updated + HZ)) { + ret = asus_wmi_ec_block_read(ASUSWMI_METHODID_BLOCK_READ_EC, + ec->read_arg, ec->read_buffer); + if (ret) + goto unlock; + + asus_wmi_ec_update_ec_sensors(ec); + ec->last_updated = jiffies; + } + + *value = ec->sensors[sensor_index].cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ + +static int asus_wmi_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret, sidx, info_index; + long value = 0; + + sidx = asus_wmi_ec_find_sensor_index(ec, type, channel); + if (sidx < 0) + return sidx; + + ret = asus_wmi_ec_get_cached_value_or_update(sensor_data, sidx, &value); + if (ret) + return ret; + + info_index = ec->sensors[sidx].info_index; + *val = asus_wmi_ec_scale_sensor_value(value, known_ec_sensors[info_index].type); + + return ret; +} + +static int asus_wmi_ec_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int sensor_index; + + sensor_index = asus_wmi_ec_find_sensor_index(ec, type, channel); + *str = known_ec_sensors[ec->sensors[sensor_index].info_index].label; + + return 0; +} + +static umode_t asus_wmi_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_ec_info *ec = &sensor_data->ec; + int index; + + index = asus_wmi_ec_find_sensor_index(ec, type, channel); + + return index < 0 ? 0 : 0444; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +static const struct hwmon_ops asus_wmi_ec_hwmon_ops = { + .is_visible = asus_wmi_ec_hwmon_is_visible, + .read = asus_wmi_ec_hwmon_read, + .read_string = asus_wmi_ec_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_ec_chip_info = { + .ops = &asus_wmi_ec_hwmon_ops, +}; + +static int asus_wmi_ec_configure_sensor_setup(struct device *dev, + const enum known_ec_sensor *bsi) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + const struct hwmon_channel_info **asus_wmi_ci; + int nr_count[hwmon_max] = {}, nr_types = 0; + const struct hwmon_chip_info *chip_info; + const struct ec_sensor_info *si; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, ret; + + ret = asus_wmi_ec_fill_board_sensors(ec, bsi); + if (ret) + return ret; + + if (!sensor_data->ec.nr_sensors) + return -ENODEV; + + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + if (!nr_count[si->type]) + nr_types++; + nr_count[si->type]++; + } + + if (nr_count[hwmon_temp]) { + nr_count[hwmon_chip]++; + nr_types++; + } + + /* + * If we can get values for all the registers in a single query, + * the query will not change from call to call. + */ + asus_wmi_ec_make_block_read_query(ec); + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*asus_wmi_ci), GFP_KERNEL); + if (!asus_wmi_ci) + return -ENOMEM; + + asus_wmi_ec_chip_info.info = asus_wmi_ci; + chip_info = &asus_wmi_ec_chip_info; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + ret = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (ret) + return ret; + + *asus_wmi_ci++ = asus_wmi_hwmon_chan++; + } + + dev_dbg(dev, "board has %d EC sensors that span %d registers", + ec->nr_sensors, ec->nr_registers); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_ec_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct asus_wmi_data *board_sensors; + const struct dmi_system_id *dmi_id; + const enum known_ec_sensor *bsi; + struct device *dev = &wdev->dev; + + dmi_id = dmi_first_match(asus_wmi_ec_dmi_table); + if (!dmi_id) + return -ENODEV; + + board_sensors = dmi_id->driver_data; + bsi = board_sensors->known_board_sensors; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_ec_configure_sensor_setup(dev, bsi); +} + +static const struct wmi_device_id asus_ec_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_ec_sensors", + }, + .id_table = asus_ec_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley "); +MODULE_AUTHOR("Eugene Shalygin "); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 548820e21ce10582f4b4bd257cb290d4257bcd93 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Tue, 16 Nov 2021 22:57:44 +0200 Subject: hwmon: (asus_wmi_sensors) Support X370 Asus WMI. Provides a Linux kernel module "asus_wmi_sensors" that provides sensor readouts via ASUS' WMI interface present in the UEFI of X370/X470/B450/X399 Ryzen motherboards. Supported motherboards: * ROG CROSSHAIR VI HERO, * PRIME X399-A, * PRIME X470-PRO, * ROG CROSSHAIR VI EXTREME, * ROG CROSSHAIR VI HERO (WI-FI AC), * ROG CROSSHAIR VII HERO, * ROG CROSSHAIR VII HERO (WI-FI), * ROG STRIX B450-E GAMING, * ROG STRIX B450-F GAMING, * ROG STRIX B450-I GAMING, * ROG STRIX X399-E GAMING, * ROG STRIX X470-F GAMING, * ROG STRIX X470-I GAMING, * ROG ZENITH EXTREME, * ROG ZENITH EXTREME ALPHA. Co-developed-by: Ed Brindley Signed-off-by: Ed Brindley Signed-off-by: Denis Pauk [groeck: Squashed: "hwmon: Fix warnings in asus_wmi_sensors.rst documetation."] Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_wmi_sensors.rst | 78 ++++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 7 + drivers/hwmon/Kconfig | 12 + drivers/hwmon/Makefile | 1 + drivers/hwmon/asus_wmi_sensors.c | 664 +++++++++++++++++++++++++++++++ 6 files changed, 763 insertions(+) create mode 100644 Documentation/hwmon/asus_wmi_sensors.rst create mode 100644 drivers/hwmon/asus_wmi_sensors.c diff --git a/Documentation/hwmon/asus_wmi_sensors.rst b/Documentation/hwmon/asus_wmi_sensors.rst new file mode 100644 index 000000000000..8f2096cf5183 --- /dev/null +++ b/Documentation/hwmon/asus_wmi_sensors.rst @@ -0,0 +1,78 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_wmi_sensors +================================= + +Supported boards: + * PRIME X399-A, + * PRIME X470-PRO, + * ROG CROSSHAIR VI EXTREME, + * ROG CROSSHAIR VI HERO, + * ROG CROSSHAIR VI HERO (WI-FI AC), + * ROG CROSSHAIR VII HERO, + * ROG CROSSHAIR VII HERO (WI-FI), + * ROG STRIX B450-E GAMING, + * ROG STRIX B450-F GAMING, + * ROG STRIX B450-I GAMING, + * ROG STRIX X399-E GAMING, + * ROG STRIX X470-F GAMING, + * ROG STRIX X470-I GAMING, + * ROG ZENITH EXTREME, + * ROG ZENITH EXTREME ALPHA. + +Authors: + - Ed Brindley + +Description: +------------ +ASUS mainboards publish hardware monitoring information via WMI interface. + +ASUS WMI interface provides a methods to get list of sensors and values of +such, which is utilized by this driver to publish those sensor readings to the +HWMON system. + +The driver is aware of and reads the following sensors: + * CPU Core Voltage, + * CPU SOC Voltage, + * DRAM Voltage, + * VDDP Voltage, + * 1.8V PLL Voltage, + * +12V Voltage, + * +5V Voltage, + * 3VSB Voltage, + * VBAT Voltage, + * AVCC3 Voltage, + * SB 1.05V Voltage, + * CPU Core Voltage, + * CPU SOC Voltage, + * DRAM Voltage, + * CPU Fan RPM, + * Chassis Fan 1 RPM, + * Chassis Fan 2 RPM, + * Chassis Fan 3 RPM, + * HAMP Fan RPM, + * Water Pump RPM, + * CPU OPT RPM, + * Water Flow RPM, + * AIO Pump RPM, + * CPU Temperature, + * CPU Socket Temperature, + * Motherboard Temperature, + * Chipset Temperature, + * Tsensor 1 Temperature, + * CPU VRM Temperature, + * Water In, + * Water Out, + * CPU VRM Output Current. + +Known Issues: + * The WMI implementation in some of Asus' BIOSes is buggy. This can result in + fans stopping, fans getting stuck at max speed, or temperature readouts + getting stuck. This is not an issue with the driver, but the BIOS. The Prime + X470 Pro seems particularly bad for this. The more frequently the WMI + interface is polled the greater the potential for this to happen. Until you + have subjected your computer to an extended soak test while polling the + sensors frequently, don't leave you computer unattended. Upgrading to new + BIOS version with method version greater than or equal to two should + rectify the issue. + * A few boards report 12v voltages to be ~10v. diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 3e196eaff360..9c9361d76f3d 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -44,6 +44,7 @@ Hardware Monitoring Kernel Drivers asc7621 aspeed-pwm-tacho asus_wmi_ec_sensors + asus_wmi_sensors bcm54140 bel-pfe bpa-rs600 diff --git a/MAINTAINERS b/MAINTAINERS index 6b386f91e697..979195fbd054 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2998,6 +2998,13 @@ W: http://acpi4asus.sf.net F: drivers/platform/x86/asus*.c F: drivers/platform/x86/eeepc*.c +ASUS WMI HARDWARE MONITOR DRIVER +M: Ed Brindley +M: Denis Pauk +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/asus_wmi_sensors.c + ASUS WMI EC HARDWARE MONITOR DRIVER M: Eugene Shalygin M: Denis Pauk diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 81f5e2ddbdf5..44886bf23aed 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2228,6 +2228,18 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. +config SENSORS_ASUS_WMI + tristate "ASUS WMI X370/X470/B450/X399" + depends on ACPI_WMI + help + If you say yes here you get support for the ACPI hardware monitoring + interface found in X370/X470/B450/X399 ASUS motherboards. This driver + will provide readings of fans, voltages and temperatures through the system + firmware. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors. + config SENSORS_ASUS_WMI_EC tristate "ASUS WMI B550/X570" depends on ACPI_WMI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 821345e49107..3a1551b3d570 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o # APCI drivers obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o # Native drivers diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c new file mode 100644 index 000000000000..67af15d99396 --- /dev/null +++ b/drivers/hwmon/asus_wmi_sensors.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS motherboards that provides sensor readouts via WMI + * interface present in the UEFI of the X370/X470/B450/X399 Ryzen motherboards. + * + * Copyright (C) 2018-2019 Ed Brindley + * + * WMI interface provides: + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - VDDP Voltage, + * - 1.8V PLL Voltage, + * - +12V Voltage, + * - +5V Voltage, + * - 3VSB Voltage, + * - VBAT Voltage, + * - AVCC3 Voltage, + * - SB 1.05V Voltage, + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - CPU Fan RPM, + * - Chassis Fan 1 RPM, + * - Chassis Fan 2 RPM, + * - Chassis Fan 3 RPM, + * - HAMP Fan RPM, + * - Water Pump RPM, + * - CPU OPT RPM, + * - Water Flow RPM, + * - AIO Pump RPM, + * - CPU Temperature, + * - CPU Socket Temperature, + * - Motherboard Temperature, + * - Chipset Temperature, + * - Tsensor 1 Temperature, + * - CPU VRM Temperature, + * - Water In, + * - Water Out, + * - CPU VRM Output Current. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_GET_VALUE 0x52574543 /* RWEC */ +#define ASUSWMI_METHODID_UPDATE_BUFFER 0x51574543 /* QWEC */ +#define ASUSWMI_METHODID_GET_INFO 0x50574543 /* PWEC */ +#define ASUSWMI_METHODID_GET_NUMBER 0x50574572 /* PWEr */ +#define ASUSWMI_METHODID_GET_VERSION 0x50574574 /* PWEt */ + +#define ASUS_WMI_MAX_STR_SIZE 32 + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ +} + +static const struct dmi_system_id asus_wmi_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X399-A"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO (WI-FI AC)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME ALPHA"), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_dmi_table); + +enum asus_wmi_sensor_class { + VOLTAGE = 0x0, + TEMPERATURE_C = 0x1, + FAN_RPM = 0x2, + CURRENT = 0x3, + WATER_FLOW = 0x4, +}; + +enum asus_wmi_location { + CPU = 0x0, + CPU_SOC = 0x1, + DRAM = 0x2, + MOTHERBOARD = 0x3, + CHIPSET = 0x4, + AUX = 0x5, + VRM = 0x6, + COOLER = 0x7 +}; + +enum asus_wmi_type { + SIGNED_INT = 0x0, + UNSIGNED_INT = 0x1, + SCALED = 0x3, +}; + +enum asus_wmi_source { + SIO = 0x1, + EC = 0x2 +}; + +static enum hwmon_sensor_types asus_data_types[] = { + [VOLTAGE] = hwmon_in, + [TEMPERATURE_C] = hwmon_temp, + [FAN_RPM] = hwmon_fan, + [CURRENT] = hwmon_curr, + [WATER_FLOW] = hwmon_fan, +}; + +static u32 hwmon_attributes[] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +/** + * struct asus_wmi_sensor_info - sensor info. + * @id: sensor id. + * @data_type: sensor class e.g. voltage, temp etc. + * @location: sensor location. + * @name: sensor name. + * @source: sensor source. + * @type: sensor type signed, unsigned etc. + * @cached_value: cached sensor value. + */ +struct asus_wmi_sensor_info { + u32 id; + int data_type; + int location; + char name[ASUS_WMI_MAX_STR_SIZE]; + int source; + int type; + long cached_value; +}; + +struct asus_wmi_wmi_info { + unsigned long source_last_updated[3]; /* in jiffies */ + int sensor_count; + + const struct asus_wmi_sensor_info **info[hwmon_max]; + struct asus_wmi_sensor_info **info_by_id; +}; + +struct asus_wmi_sensors { + struct asus_wmi_wmi_info wmi; + /* lock access to internal cache */ + struct mutex lock; +}; + +/* + * Universal method for calling WMI method + */ +static int asus_wmi_call_method(u32 method_id, u32 *args, struct acpi_buffer *output) +{ + struct acpi_buffer input = {(acpi_size) sizeof(*args), args }; + acpi_status status; + + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, + method_id, &input, output); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +/* + * Gets the version of the ASUS sensors interface implemented + */ +static int asus_wmi_get_version(u32 *version) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VERSION, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *version = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +/* + * Gets the number of sensor items + */ +static int asus_wmi_get_item_count(u32 *count) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_NUMBER, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *count = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +/* + * For a given sensor item returns details e.g. type (voltage/temperature/fan speed etc), bank etc + */ +static int asus_wmi_sensor_info(int index, struct asus_wmi_sensor_info *s) +{ + union acpi_object name_obj, data_type_obj, location_obj, source_obj, type_obj; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_INFO, args, &output); + if (err) + return err; + + s->id = index; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_PACKAGE) { + err = -EIO; + goto out_free_obj; + } + + if (obj->package.count != 5) { + err = -EIO; + goto out_free_obj; + } + + name_obj = obj->package.elements[0]; + if (name_obj.type != ACPI_TYPE_STRING) { + err = -EIO; + goto out_free_obj; + } + + strncpy(s->name, name_obj.string.pointer, sizeof(s->name) - 1); + + data_type_obj = obj->package.elements[1]; + if (data_type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->data_type = data_type_obj.integer.value; + + location_obj = obj->package.elements[2]; + if (location_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->location = location_obj.integer.value; + + source_obj = obj->package.elements[3]; + if (source_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->source = source_obj.integer.value; + + type_obj = obj->package.elements[4]; + if (type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + s->type = type_obj.integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_buffer(int source) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {source, 0}; + + return asus_wmi_call_method(ASUSWMI_METHODID_UPDATE_BUFFER, args, &output); +} + +static int asus_wmi_get_sensor_value(u8 index, long *value) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VALUE, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *value = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_values_for_source(u8 source, struct asus_wmi_sensors *sensor_data) +{ + struct asus_wmi_sensor_info *sensor; + long value = 0; + int ret; + int i; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + sensor = sensor_data->wmi.info_by_id[i]; + if (sensor && sensor->source == source) { + ret = asus_wmi_get_sensor_value(sensor->id, &value); + if (ret) + return ret; + + sensor->cached_value = value; + } + } + + return 0; +} + +static int asus_wmi_scale_sensor_value(u32 value, int data_type) +{ + /* FAN_RPM and WATER_FLOW don't need scaling */ + switch (data_type) { + case VOLTAGE: + /* value in microVolts */ + return DIV_ROUND_CLOSEST(value, KILO); + case TEMPERATURE_C: + /* value in Celsius */ + return value * MILLIDEGREE_PER_DEGREE; + case CURRENT: + /* value in Amperes */ + return value * MILLI; + } + return value; +} + +static int asus_wmi_get_cached_value_or_update(const struct asus_wmi_sensor_info *sensor, + struct asus_wmi_sensors *sensor_data, + u32 *value) +{ + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, sensor_data->wmi.source_last_updated[sensor->source] + HZ)) { + ret = asus_wmi_update_buffer(sensor->source); + if (ret) + goto unlock; + + ret = asus_wmi_update_values_for_source(sensor->source, sensor_data); + if (ret) + goto unlock; + + sensor_data->wmi.source_last_updated[sensor->source] = jiffies; + } + + *value = sensor->cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ +static int asus_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + const struct asus_wmi_sensor_info *sensor; + u32 value = 0; + int ret; + + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + + sensor = *(sensor_data->wmi.info[type] + channel); + + ret = asus_wmi_get_cached_value_or_update(sensor, sensor_data, &value); + if (ret) + return ret; + + *val = asus_wmi_scale_sensor_value(value, sensor->data_type); + + return ret; +} + +static int asus_wmi_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + *str = sensor->name; + + return 0; +} + +static umode_t asus_wmi_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + if (sensor) + return 0444; + + return 0; +} + +static const struct hwmon_ops asus_wmi_hwmon_ops = { + .is_visible = asus_wmi_hwmon_is_visible, + .read = asus_wmi_hwmon_read, + .read_string = asus_wmi_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_chip_info = { + .ops = &asus_wmi_hwmon_ops, + .info = NULL, +}; + +static int asus_wmi_configure_sensor_setup(struct device *dev, + struct asus_wmi_sensors *sensor_data) +{ + const struct hwmon_channel_info **ptr_asus_wmi_ci; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + int nr_count[hwmon_max] = {}, nr_types = 0; + struct asus_wmi_sensor_info *temp_sensor; + const struct hwmon_chip_info *chip_info; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, idx; + int err; + + temp_sensor = devm_kcalloc(dev, 1, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + return err; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + if (!nr_count[type]) + nr_types++; + nr_count[type]++; + break; + } + } + + if (nr_count[hwmon_temp]) + nr_count[hwmon_chip]++, nr_types++; + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, + sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + ptr_asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, + sizeof(*ptr_asus_wmi_ci), GFP_KERNEL); + if (!ptr_asus_wmi_ci) + return -ENOMEM; + + asus_wmi_chip_info.info = ptr_asus_wmi_ci; + chip_info = &asus_wmi_chip_info; + + sensor_data->wmi.info_by_id = devm_kcalloc(dev, sensor_data->wmi.sensor_count, + sizeof(*sensor_data->wmi.info_by_id), + GFP_KERNEL); + + if (!sensor_data->wmi.info_by_id) + return -ENOMEM; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + err = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (err) + return err; + + *ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++; + + sensor_data->wmi.info[type] = devm_kcalloc(dev, + nr_count[type], + sizeof(*sensor_data->wmi.info), + GFP_KERNEL); + if (!sensor_data->wmi.info[type]) + return -ENOMEM; + } + + for (i = sensor_data->wmi.sensor_count - 1; i >= 0; i--) { + temp_sensor = devm_kzalloc(dev, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + continue; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + idx = --nr_count[type]; + *(sensor_data->wmi.info[type] + idx) = temp_sensor; + sensor_data->wmi.info_by_id[i] = temp_sensor; + break; + } + } + + dev_dbg(dev, "board has %d sensors", + sensor_data->wmi.sensor_count); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct device *dev = &wdev->dev; + u32 version = 0; + + if (!dmi_check_system(asus_wmi_dmi_table)) + return -ENODEV; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + if (asus_wmi_get_version(&version)) + return -ENODEV; + + if (asus_wmi_get_item_count(&sensor_data->wmi.sensor_count)) + return -ENODEV; + + if (sensor_data->wmi.sensor_count <= 0 || version < 2) { + dev_info(dev, "version: %u with %d sensors is unsupported\n", + version, sensor_data->wmi.sensor_count); + + return -ENODEV; + } + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_configure_sensor_setup(dev, sensor_data); +} + +static const struct wmi_device_id asus_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_sensors", + }, + .id_table = asus_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley "); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 8bb050cd5cf494f3d0cb45a6b54a476af09edb8d Mon Sep 17 00:00:00 2001 From: Babu Moger Date: Wed, 24 Nov 2021 10:03:13 -0600 Subject: hwmon: (k10temp) Support up to 12 CCDs on AMD Family of processors The current driver can read the temperatures from upto 8 CCDs (Core-Complex Die). The newer AMD Family 19h Models 10h-1Fh and A0h-AFh can support up to 12 CCDs. Update the driver to read up to 12 CCDs. Signed-off-by: Babu Moger Link: https://lore.kernel.org/r/163776976762.904164.5618896687524494215.stgit@bmoger-ubuntu Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 880990fa4795..4e239bd75b1d 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -171,6 +171,10 @@ static const char *k10temp_temp_label[] = { "Tccd6", "Tccd7", "Tccd8", + "Tccd9", + "Tccd10", + "Tccd11", + "Tccd12", }; static int k10temp_read_labels(struct device *dev, @@ -206,7 +210,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, if (*val < 0) *val = 0; break; - case 2 ... 9: /* Tccd{1-8} */ + case 2 ... 13: /* Tccd{1-12} */ amd_smn_read(amd_pci_dev_to_node_id(data->pdev), ZEN_CCD_TEMP(data->ccd_offset, channel - 2), ®val); @@ -341,6 +345,10 @@ static const struct hwmon_channel_info *k10temp_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), NULL }; @@ -433,12 +441,15 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->ccd_offset = 0x154; k10temp_get_ccd_support(pdev, data, 8); break; - case 0x10 ... 0x1f: case 0x40 ... 0x4f: /* Yellow Carp */ - case 0xa0 ... 0xaf: data->ccd_offset = 0x300; k10temp_get_ccd_support(pdev, data, 8); break; + case 0x10 ... 0x1f: + case 0xa0 ... 0xaf: + data->ccd_offset = 0x300; + k10temp_get_ccd_support(pdev, data, 12); + break; } } else { data->read_htcreg = read_htcreg_pci; -- cgit v1.2.3-59-g8ed1b From bf4d843050af4fde7e8514b4b5ffe79874ee3936 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 8 Nov 2021 07:17:52 -0800 Subject: hwmon: (jc42) Add support for ONSEMI N34TS04 N34TS04 is a JC42.4 compatible temperature sensor from ONSEMI. Signed-off-by: Guenter Roeck --- drivers/hwmon/jc42.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index 4a03d010ec5a..cb347a6bd8d9 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -137,6 +137,9 @@ static const unsigned short normal_i2c[] = { #define CAT34TS04_DEVID 0x2200 #define CAT34TS04_DEVID_MASK 0xfff0 +#define N34TS04_DEVID 0x2230 +#define N34TS04_DEVID_MASK 0xfff0 + /* ST Microelectronics */ #define STTS424_DEVID 0x0101 #define STTS424_DEVID_MASK 0xffff @@ -181,6 +184,7 @@ static struct jc42_chips jc42_chips[] = { { ONS_MANID, CAT6095_DEVID, CAT6095_DEVID_MASK }, { ONS_MANID, CAT34TS02C_DEVID, CAT34TS02C_DEVID_MASK }, { ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK }, + { ONS_MANID, N34TS04_DEVID, N34TS04_DEVID_MASK }, { NXP_MANID, SE98_DEVID, SE98_DEVID_MASK }, { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, -- cgit v1.2.3-59-g8ed1b From 11a24ca7e34d968991a7d437b950d1924396bd81 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 25 Nov 2021 03:08:38 +0100 Subject: hwmon: (ntc_thermistor) Merge platform data into driver Platform data is supposed to be used with "board files", device descriptions in C. Since the introduction of the NTC driver in 2011, no such platforms have been submitted to the Linux kernel, and their use is strongly discouraged in favor of Device Tree, ACPI or as last resort software firmware nodes. Drop the external header and copy the platform data into the driver file. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211125020841.3616359-2-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 41 ++++++++++++++++++++--- include/linux/platform_data/ntc_thermistor.h | 50 ---------------------------- 2 files changed, 36 insertions(+), 55 deletions(-) delete mode 100644 include/linux/platform_data/ntc_thermistor.h diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index cf26c44f2b88..034ef55d0706 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -14,12 +14,45 @@ #include #include #include +#include +#include -#include +enum ntc_thermistor_type { + TYPE_B57330V2103, + TYPE_B57891S0103, + TYPE_NCPXXWB473, + TYPE_NCPXXWF104, + TYPE_NCPXXWL333, + TYPE_NCPXXXH103, +}; -#include +struct ntc_thermistor_platform_data { + /* + * One (not both) of read_uV and read_ohm should be provided and only + * one of the two should be provided. + * Both functions should return negative value for an error case. + * + * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required to use + * read_uV() + * + * How to setup pullup_ohm, pulldown_ohm, and connect is + * described at Documentation/hwmon/ntc_thermistor.rst + * + * 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)(struct ntc_thermistor_platform_data *); + unsigned int pullup_uv; -#include + unsigned int pullup_ohm; + unsigned int pulldown_ohm; + enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; + struct iio_channel *chan; + + int (*read_ohm)(void); +}; struct ntc_compensation { int temp_c; @@ -651,8 +684,6 @@ static int ntc_thermistor_probe(struct platform_device *pdev) pdata = ntc_thermistor_parse_dt(dev); if (IS_ERR(pdata)) return PTR_ERR(pdata); - else if (pdata == NULL) - pdata = dev_get_platdata(dev); if (!pdata) { dev_err(dev, "No platform init data supplied.\n"); diff --git a/include/linux/platform_data/ntc_thermistor.h b/include/linux/platform_data/ntc_thermistor.h deleted file mode 100644 index b324d03e580c..000000000000 --- a/include/linux/platform_data/ntc_thermistor.h +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * ntc_thermistor.h - NTC Thermistors - * - * Copyright (C) 2010 Samsung Electronics - * MyungJoo Ham - */ -#ifndef _LINUX_NTC_H -#define _LINUX_NTC_H - -struct iio_channel; - -enum ntc_thermistor_type { - TYPE_B57330V2103, - TYPE_B57891S0103, - TYPE_NCPXXWB473, - TYPE_NCPXXWF104, - TYPE_NCPXXWL333, - TYPE_NCPXXXH103, -}; - -struct ntc_thermistor_platform_data { - /* - * One (not both) of read_uV and read_ohm should be provided and only - * one of the two should be provided. - * Both functions should return negative value for an error case. - * - * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required to use - * read_uV() - * - * How to setup pullup_ohm, pulldown_ohm, and connect is - * described at Documentation/hwmon/ntc_thermistor.rst - * - * 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)(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); -}; - -#endif /* _LINUX_NTC_H */ -- cgit v1.2.3-59-g8ed1b From 76f240ff9523673106385120bc9af15ada9ca2f8 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 25 Nov 2021 03:08:39 +0100 Subject: hwmon: (ntc_thermistor) Drop get_ohm() Nothing in the kernel (this driver) is using the callback to read ohms directly. We always read a voltage and convert it to a resistance. Drop this callback. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211125020841.3616359-3-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index 034ef55d0706..8a78e899fa12 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -28,10 +28,6 @@ enum ntc_thermistor_type { struct ntc_thermistor_platform_data { /* - * One (not both) of read_uV and read_ohm should be provided and only - * one of the two should be provided. - * Both functions should return negative value for an error case. - * * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required to use * read_uV() * @@ -50,8 +46,6 @@ struct ntc_thermistor_platform_data { unsigned int pulldown_ohm; enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; struct iio_channel *chan; - - int (*read_ohm)(void); }; struct ntc_compensation { @@ -600,9 +594,6 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data) { int read_uv; - if (data->pdata->read_ohm) - return data->pdata->read_ohm(); - if (data->pdata->read_uv) { read_uv = data->pdata->read_uv(data->pdata); if (read_uv < 0) @@ -690,19 +681,11 @@ static int ntc_thermistor_probe(struct platform_device *pdev) return -ENODEV; } - /* Either one of the two is required. */ - if (!pdata->read_uv && !pdata->read_ohm) { - dev_err(dev, - "Both read_uv and read_ohm missing. Need either one of the two.\n"); + if (!pdata->read_uv) { + dev_err(dev, "read_uv missing\n"); return -EINVAL; } - if (pdata->read_uv && pdata->read_ohm) { - dev_warn(dev, - "Only one of read_uv and read_ohm is needed; ignoring read_uv.\n"); - pdata->read_uv = NULL; - } - if (pdata->read_uv && (pdata->pullup_uv == 0 || (pdata->pullup_ohm == 0 && pdata->connect == NTC_CONNECTED_GROUND) || -- cgit v1.2.3-59-g8ed1b From 209218efd6ac8bebfe85fd2bc3ad64e5c3bad0a8 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 25 Nov 2021 03:08:40 +0100 Subject: hwmon: (ntc_thermistor) Drop read_uv() depend on OF and IIO The only possible assignment of a function to get a voltage to convert to a resistance is to use the internal function ntc_adc_iio_read() which is only available when using IIO and OF. Bite the bullet and mandate OF and IIO, drop the read_uv() callback abstraction and some ifdefs. As no board is using the platform data, all users are using OF and IIO anyway. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211125020841.3616359-4-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 5 +++-- drivers/hwmon/ntc_thermistor.c | 49 +++++++++++------------------------------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 44886bf23aed..43e5245874ad 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1414,8 +1414,9 @@ config SENSORS_PC87427 will be called pc87427. config SENSORS_NTC_THERMISTOR - tristate "NTC thermistor support from Murata" - depends on !OF || IIO=n || IIO + tristate "NTC thermistor support" + depends on OF + depends on IIO depends on THERMAL || !THERMAL_OF help This driver supports NTC thermistors sensor reading and its diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index 8a78e899fa12..cedb3ee0f762 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -28,8 +28,7 @@ enum ntc_thermistor_type { struct ntc_thermistor_platform_data { /* - * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required to use - * read_uV() + * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required. * * How to setup pullup_ohm, pulldown_ohm, and connect is * described at Documentation/hwmon/ntc_thermistor.rst @@ -39,9 +38,7 @@ struct ntc_thermistor_platform_data { * chan: iio_channel pointer to communicate with the ADC which the * thermistor is using for conversion of the analog values. */ - 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; @@ -346,7 +343,6 @@ struct ntc_data { int n_comp; }; -#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO) static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) { struct iio_channel *channel = pdata->chan; @@ -451,20 +447,9 @@ ntc_thermistor_parse_dt(struct device *dev) pdata->connect = NTC_CONNECTED_GROUND; pdata->chan = chan; - pdata->read_uv = ntc_adc_iio_read; return pdata; } -#else -static struct ntc_thermistor_platform_data * -ntc_thermistor_parse_dt(struct device *dev) -{ - return NULL; -} - -#define ntc_match NULL - -#endif static inline u64 div64_u64_safe(u64 dividend, u64 divisor) { @@ -594,13 +579,10 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data) { int read_uv; - if (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); - } - return -EINVAL; + read_uv = ntc_adc_iio_read(data->pdata); + if (read_uv < 0) + return read_uv; + return get_ohm_of_thermistor(data, read_uv); } static int ntc_read(struct device *dev, enum hwmon_sensor_types type, @@ -681,19 +663,14 @@ static int ntc_thermistor_probe(struct platform_device *pdev) return -ENODEV; } - if (!pdata->read_uv) { - dev_err(dev, "read_uv missing\n"); - return -EINVAL; - } - - if (pdata->read_uv && (pdata->pullup_uv == 0 || - (pdata->pullup_ohm == 0 && pdata->connect == - NTC_CONNECTED_GROUND) || - (pdata->pulldown_ohm == 0 && pdata->connect == - NTC_CONNECTED_POSITIVE) || - (pdata->connect != NTC_CONNECTED_POSITIVE && - pdata->connect != NTC_CONNECTED_GROUND))) { - dev_err(dev, "Required data to use read_uv not supplied.\n"); + if (pdata->pullup_uv == 0 || + (pdata->pullup_ohm == 0 && pdata->connect == + NTC_CONNECTED_GROUND) || + (pdata->pulldown_ohm == 0 && pdata->connect == + NTC_CONNECTED_POSITIVE) || + (pdata->connect != NTC_CONNECTED_POSITIVE && + pdata->connect != NTC_CONNECTED_GROUND)) { + dev_err(dev, "Required data to use NTC driver not supplied.\n"); return -EINVAL; } -- cgit v1.2.3-59-g8ed1b From e380095b8018acd9f962a9020251c5d8f1191e49 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 25 Nov 2021 03:08:41 +0100 Subject: hwmon: (ntc_thermistor) Merge platform data Allocate one state container for the device: struct ntc_data. Move all items from struct ntc_thermistor_platform_data into this struct and simplify. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211125020841.3616359-5-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 109 ++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 61 deletions(-) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index cedb3ee0f762..ed638ebd0923 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -26,25 +26,6 @@ enum ntc_thermistor_type { TYPE_NCPXXXH103, }; -struct ntc_thermistor_platform_data { - /* - * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required. - * - * How to setup pullup_ohm, pulldown_ohm, and connect is - * described at Documentation/hwmon/ntc_thermistor.rst - * - * 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. - */ - unsigned int pullup_uv; - unsigned int pullup_ohm; - unsigned int pulldown_ohm; - enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; - struct iio_channel *chan; -}; - struct ntc_compensation { int temp_c; unsigned int ohm; @@ -337,15 +318,30 @@ static const struct ntc_type ntc_type[] = { NTC_TYPE(TYPE_NCPXXXH103, ncpXXxh103), }; +/* + * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required. + * + * How to setup pullup_ohm, pulldown_ohm, and connect is + * described at Documentation/hwmon/ntc_thermistor.rst + * + * 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. + */ struct ntc_data { - struct ntc_thermistor_platform_data *pdata; const struct ntc_compensation *comp; int n_comp; + unsigned int pullup_uv; + unsigned int pullup_ohm; + unsigned int pulldown_ohm; + enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; + struct iio_channel *chan; }; -static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) +static int ntc_adc_iio_read(struct ntc_data *data) { - struct iio_channel *channel = pdata->chan; + struct iio_channel *channel = data->chan; int uv, ret; ret = iio_read_channel_processed_scale(channel, &uv, 1000); @@ -365,7 +361,7 @@ static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) ret = iio_convert_raw_to_processed(channel, raw, &uv, 1000); if (ret < 0) { /* Assume 12 bit ADC with vref at pullup_uv */ - uv = (pdata->pullup_uv * (s64)raw) >> 12; + uv = (data->pullup_uv * (s64)raw) >> 12; } } @@ -407,20 +403,19 @@ static const struct of_device_id ntc_match[] = { }; MODULE_DEVICE_TABLE(of, ntc_match); -static struct ntc_thermistor_platform_data * -ntc_thermistor_parse_dt(struct device *dev) +static struct ntc_data *ntc_thermistor_parse_dt(struct device *dev) { + struct ntc_data *data; struct iio_channel *chan; enum iio_chan_type type; struct device_node *np = dev->of_node; - struct ntc_thermistor_platform_data *pdata; int ret; if (!np) return NULL; - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) return ERR_PTR(-ENOMEM); chan = devm_iio_channel_get(dev, NULL); @@ -434,21 +429,21 @@ ntc_thermistor_parse_dt(struct device *dev) if (type != IIO_VOLTAGE) return ERR_PTR(-EINVAL); - if (of_property_read_u32(np, "pullup-uv", &pdata->pullup_uv)) + if (of_property_read_u32(np, "pullup-uv", &data->pullup_uv)) return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pullup-ohm", &pdata->pullup_ohm)) + if (of_property_read_u32(np, "pullup-ohm", &data->pullup_ohm)) return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pulldown-ohm", &pdata->pulldown_ohm)) + if (of_property_read_u32(np, "pulldown-ohm", &data->pulldown_ohm)) return ERR_PTR(-ENODEV); if (of_find_property(np, "connected-positive", NULL)) - pdata->connect = NTC_CONNECTED_POSITIVE; + data->connect = NTC_CONNECTED_POSITIVE; else /* status change should be possible if not always on. */ - pdata->connect = NTC_CONNECTED_GROUND; + data->connect = NTC_CONNECTED_GROUND; - pdata->chan = chan; + data->chan = chan; - return pdata; + return data; } static inline u64 div64_u64_safe(u64 dividend, u64 divisor) @@ -462,24 +457,23 @@ static inline u64 div64_u64_safe(u64 dividend, u64 divisor) static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uv) { - struct ntc_thermistor_platform_data *pdata = data->pdata; - u32 puv = pdata->pullup_uv; + u32 puv = data->pullup_uv; u64 n, puo, pdo; - puo = pdata->pullup_ohm; - pdo = pdata->pulldown_ohm; + puo = data->pullup_ohm; + pdo = data->pulldown_ohm; if (uv == 0) - return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + return (data->connect == NTC_CONNECTED_POSITIVE) ? INT_MAX : 0; if (uv >= puv) - return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + return (data->connect == NTC_CONNECTED_POSITIVE) ? 0 : INT_MAX; - if (pdata->connect == NTC_CONNECTED_POSITIVE && puo == 0) + if (data->connect == NTC_CONNECTED_POSITIVE && puo == 0) n = div_u64(pdo * (puv - uv), uv); - else if (pdata->connect == NTC_CONNECTED_GROUND && pdo == 0) + else if (data->connect == NTC_CONNECTED_GROUND && pdo == 0) n = div_u64(puo * uv, puv - uv); - else if (pdata->connect == NTC_CONNECTED_POSITIVE) + else if (data->connect == NTC_CONNECTED_POSITIVE) n = div64_u64_safe(pdo * puo * (puv - uv), puo * uv - pdo * (puv - uv)); else @@ -579,7 +573,7 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data) { int read_uv; - read_uv = ntc_adc_iio_read(data->pdata); + read_uv = ntc_adc_iio_read(data); if (read_uv < 0) return read_uv; return get_ohm_of_thermistor(data, read_uv); @@ -650,38 +644,31 @@ static int ntc_thermistor_probe(struct platform_device *pdev) const struct of_device_id *of_id = of_match_device(of_match_ptr(ntc_match), dev); const struct platform_device_id *pdev_id; - struct ntc_thermistor_platform_data *pdata; struct device *hwmon_dev; struct ntc_data *data; - pdata = ntc_thermistor_parse_dt(dev); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); + data = ntc_thermistor_parse_dt(dev); + if (IS_ERR(data)) + return PTR_ERR(data); - if (!pdata) { + if (!data) { dev_err(dev, "No platform init data supplied.\n"); return -ENODEV; } - if (pdata->pullup_uv == 0 || - (pdata->pullup_ohm == 0 && pdata->connect == + if (data->pullup_uv == 0 || + (data->pullup_ohm == 0 && data->connect == NTC_CONNECTED_GROUND) || - (pdata->pulldown_ohm == 0 && pdata->connect == + (data->pulldown_ohm == 0 && data->connect == NTC_CONNECTED_POSITIVE) || - (pdata->connect != NTC_CONNECTED_POSITIVE && - pdata->connect != NTC_CONNECTED_GROUND)) { + (data->connect != NTC_CONNECTED_POSITIVE && + data->connect != NTC_CONNECTED_GROUND)) { dev_err(dev, "Required data to use NTC driver not supplied.\n"); return -EINVAL; } - data = devm_kzalloc(dev, sizeof(struct ntc_data), GFP_KERNEL); - if (!data) - return -ENOMEM; - pdev_id = of_id ? of_id->data : platform_get_device_id(pdev); - data->pdata = pdata; - if (pdev_id->driver_data >= ARRAY_SIZE(ntc_type)) { dev_err(dev, "Unknown device type: %lu(%s)\n", pdev_id->driver_data, pdev_id->name); -- cgit v1.2.3-59-g8ed1b From 62cfc0576393e57a4c5622a08b6c4ba20fe5f880 Mon Sep 17 00:00:00 2001 From: David Mosberger-Tang Date: Sun, 21 Nov 2021 16:07:02 +0000 Subject: hwmon: (sht4x) Add device tree match table This patch enables automatic loading of the sht4x module via a device tree table entry. Signed-off-by: David Mosberger-Tang Link: https://lore.kernel.org/r/20211121160637.2312106-1-davidm@egauge.net Signed-off-by: Guenter Roeck --- drivers/hwmon/sht4x.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 3415d7a0e0fc..c19df3ade48e 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -281,9 +281,16 @@ static const struct i2c_device_id sht4x_id[] = { }; MODULE_DEVICE_TABLE(i2c, sht4x_id); +static const struct of_device_id sht4x_of_match[] = { + { .compatible = "sensirion,sht4x" }, + { } +}; +MODULE_DEVICE_TABLE(of, sht4x_of_match); + static struct i2c_driver sht4x_driver = { .driver = { .name = "sht4x", + .of_match_table = sht4x_of_match, }, .probe = sht4x_probe, .id_table = sht4x_id, -- cgit v1.2.3-59-g8ed1b From 34e2bd10ab6005d2967c2f203fea664fd44d0b0f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 30 Nov 2021 13:50:34 +0300 Subject: hwmon: (asus_wmi_ec_sensors) fix array overflow Smatch detected an array out of bounds error: drivers/hwmon/asus_wmi_ec_sensors.c:562 asus_wmi_ec_configure_sensor_setup() error: buffer overflow 'hwmon_attributes' 8 <= 9 The hwmon_attributes[] array needs to be declared with "hwmon_max" elements. Fixes: c04c7f7bfcbe ("hwmon: (asus_wmi_ec_sensors) Support B550 Asus WMI.") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20211130105034.GG5827@kili Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_wmi_ec_sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/asus_wmi_ec_sensors.c b/drivers/hwmon/asus_wmi_ec_sensors.c index f612abc66c89..22a1459305a7 100644 --- a/drivers/hwmon/asus_wmi_ec_sensors.c +++ b/drivers/hwmon/asus_wmi_ec_sensors.c @@ -41,7 +41,7 @@ #define ASUSWMI_MAX_BUF_LEN 128 #define SENSOR_LABEL_LEN 16 -static u32 hwmon_attributes[] = { +static u32 hwmon_attributes[hwmon_max] = { [hwmon_chip] = HWMON_C_REGISTER_TZ, [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, -- cgit v1.2.3-59-g8ed1b From 3315e716999d98d628abebbffaa82bef52962c95 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 30 Nov 2021 13:51:17 +0300 Subject: hwmon: (asus_wmi_sensors) fix an array overflow Smatch detects this array overflow: drivers/hwmon/asus_wmi_sensors.c:569 asus_wmi_configure_sensor_setup() error: buffer overflow 'hwmon_attributes' 8 <= 9 The hwmon_attributes[] array should have "hwmon_max" so that it gets larger when more attributes are added. Fixes: 9d07e54a25b8 ("hwmon: (asus_wmi_sensors) Support X370 Asus WMI.") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20211130105117.GH5827@kili Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_wmi_sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c index 67af15d99396..c80eee874b6c 100644 --- a/drivers/hwmon/asus_wmi_sensors.c +++ b/drivers/hwmon/asus_wmi_sensors.c @@ -125,7 +125,7 @@ static enum hwmon_sensor_types asus_data_types[] = { [WATER_FLOW] = hwmon_fan, }; -static u32 hwmon_attributes[] = { +static u32 hwmon_attributes[hwmon_max] = { [hwmon_chip] = HWMON_C_REGISTER_TZ, [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, -- cgit v1.2.3-59-g8ed1b From d75553790b9f44f9a6023a51ca5283ef4688f339 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 4 Dec 2021 23:31:55 +0000 Subject: hwmon: (adm1031) Remove redundant assignment to variable range Variable range is being initialized with a value that is never read, it is being re-assigned in the next statement. The assignment is redundant, remove it and initialize range using the second assigned value. Clean up the formatting too by adding missing spaces. Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20211204233155.55454-1-colin.i.king@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adm1031.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hwmon/adm1031.c b/drivers/hwmon/adm1031.c index 257ec53ae723..ac841fa3a369 100644 --- a/drivers/hwmon/adm1031.c +++ b/drivers/hwmon/adm1031.c @@ -242,9 +242,8 @@ static int FAN_TO_REG(int reg, int div) static int AUTO_TEMP_MAX_TO_REG(int val, int reg, int pwm) { int ret; - int range = val - AUTO_TEMP_MIN_FROM_REG(reg); + int range = ((val - AUTO_TEMP_MIN_FROM_REG(reg)) * 10) / (16 - pwm); - range = ((val - AUTO_TEMP_MIN_FROM_REG(reg))*10)/(16 - pwm); ret = ((reg & 0xf8) | (range < 10000 ? 0 : range < 20000 ? 1 : -- cgit v1.2.3-59-g8ed1b From e0149eebe47b9fe50cf85f23bcaeed81d7356c99 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 6 Dec 2021 03:04:21 +0100 Subject: hwmon: (ntc_thermistor) Move and refactor DT parsing Move the parsing of the DT config right above probe(). Allocate the state container for the driver in probe() instead of inside the DT config parsing function: as a result return an int instead of a pointer. Drop the check for !np: we can only probe from DT right now so no risk of ending up here. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211206020423.62402-1-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 93 +++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index ed638ebd0923..12435ef66530 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -403,49 +403,6 @@ static const struct of_device_id ntc_match[] = { }; MODULE_DEVICE_TABLE(of, ntc_match); -static struct ntc_data *ntc_thermistor_parse_dt(struct device *dev) -{ - struct ntc_data *data; - struct iio_channel *chan; - enum iio_chan_type type; - struct device_node *np = dev->of_node; - int ret; - - if (!np) - return NULL; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return ERR_PTR(-ENOMEM); - - chan = devm_iio_channel_get(dev, NULL); - if (IS_ERR(chan)) - return ERR_CAST(chan); - - ret = iio_get_channel_type(chan, &type); - if (ret < 0) - return ERR_PTR(ret); - - if (type != IIO_VOLTAGE) - return ERR_PTR(-EINVAL); - - if (of_property_read_u32(np, "pullup-uv", &data->pullup_uv)) - return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pullup-ohm", &data->pullup_ohm)) - return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pulldown-ohm", &data->pulldown_ohm)) - return ERR_PTR(-ENODEV); - - if (of_find_property(np, "connected-positive", NULL)) - data->connect = NTC_CONNECTED_POSITIVE; - else /* status change should be possible if not always on. */ - data->connect = NTC_CONNECTED_GROUND; - - data->chan = chan; - - return data; -} - static inline u64 div64_u64_safe(u64 dividend, u64 divisor) { if (divisor == 0 && dividend == 0) @@ -638,6 +595,42 @@ static const struct hwmon_chip_info ntc_chip_info = { .info = ntc_info, }; +static int ntc_thermistor_parse_dt(struct device *dev, + struct ntc_data *data) +{ + struct iio_channel *chan; + enum iio_chan_type type; + struct device_node *np = dev->of_node; + int ret; + + chan = devm_iio_channel_get(dev, NULL); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + ret = iio_get_channel_type(chan, &type); + if (ret < 0) + return ret; + + if (type != IIO_VOLTAGE) + return -EINVAL; + + if (of_property_read_u32(np, "pullup-uv", &data->pullup_uv)) + return -ENODEV; + if (of_property_read_u32(np, "pullup-ohm", &data->pullup_ohm)) + return -ENODEV; + if (of_property_read_u32(np, "pulldown-ohm", &data->pulldown_ohm)) + return -ENODEV; + + if (of_find_property(np, "connected-positive", NULL)) + data->connect = NTC_CONNECTED_POSITIVE; + else /* status change should be possible if not always on. */ + data->connect = NTC_CONNECTED_GROUND; + + data->chan = chan; + + return 0; +} + static int ntc_thermistor_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -646,15 +639,15 @@ static int ntc_thermistor_probe(struct platform_device *pdev) const struct platform_device_id *pdev_id; struct device *hwmon_dev; struct ntc_data *data; + int ret; - data = ntc_thermistor_parse_dt(dev); - if (IS_ERR(data)) - return PTR_ERR(data); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; - if (!data) { - dev_err(dev, "No platform init data supplied.\n"); - return -ENODEV; - } + ret = ntc_thermistor_parse_dt(dev, data); + if (ret) + return ret; if (data->pullup_uv == 0 || (data->pullup_ohm == 0 && data->connect == -- cgit v1.2.3-59-g8ed1b From 70760e80db06247b0c7b1933a9be81b5c22fc25e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 6 Dec 2021 03:04:22 +0100 Subject: hwmon: (ntc_thermistor) Switch to generic firmware props This switches to retrieveing the configuration of the NTC from generic firmware properties so that we get neutral from device tree: now ACPI or, more importantly, software nodes can be used to spawn NTC devices provided they have the required properties. This was inspired by the similar changes made to the IIO drivers. This was tested on the Ux500 HREF with the NTC devices probing from device tree just as fine after this as before. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211206020423.62402-2-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index 12435ef66530..0c8b3dbcb38b 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -9,10 +9,10 @@ #include #include #include +#include #include +#include #include -#include -#include #include #include #include @@ -595,12 +595,11 @@ static const struct hwmon_chip_info ntc_chip_info = { .info = ntc_info, }; -static int ntc_thermistor_parse_dt(struct device *dev, - struct ntc_data *data) +static int ntc_thermistor_parse_props(struct device *dev, + struct ntc_data *data) { struct iio_channel *chan; enum iio_chan_type type; - struct device_node *np = dev->of_node; int ret; chan = devm_iio_channel_get(dev, NULL); @@ -614,14 +613,19 @@ static int ntc_thermistor_parse_dt(struct device *dev, if (type != IIO_VOLTAGE) return -EINVAL; - if (of_property_read_u32(np, "pullup-uv", &data->pullup_uv)) - return -ENODEV; - if (of_property_read_u32(np, "pullup-ohm", &data->pullup_ohm)) - return -ENODEV; - if (of_property_read_u32(np, "pulldown-ohm", &data->pulldown_ohm)) - return -ENODEV; + ret = device_property_read_u32(dev, "pullup-uv", &data->pullup_uv); + if (ret) + return dev_err_probe(dev, ret, "pullup-uv not specified\n"); + + ret = device_property_read_u32(dev, "pullup-ohm", &data->pullup_ohm); + if (ret) + return dev_err_probe(dev, ret, "pullup-ohm not specified\n"); + + ret = device_property_read_u32(dev, "pulldown-ohm", &data->pulldown_ohm); + if (ret) + return dev_err_probe(dev, ret, "pulldown-ohm not specified\n"); - if (of_find_property(np, "connected-positive", NULL)) + if (device_property_read_bool(dev, "connected-positive")) data->connect = NTC_CONNECTED_POSITIVE; else /* status change should be possible if not always on. */ data->connect = NTC_CONNECTED_GROUND; @@ -634,8 +638,6 @@ static int ntc_thermistor_parse_dt(struct device *dev, static int ntc_thermistor_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - const struct of_device_id *of_id = - of_match_device(of_match_ptr(ntc_match), dev); const struct platform_device_id *pdev_id; struct device *hwmon_dev; struct ntc_data *data; @@ -645,7 +647,7 @@ static int ntc_thermistor_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - ret = ntc_thermistor_parse_dt(dev, data); + ret = ntc_thermistor_parse_props(dev, data); if (ret) return ret; @@ -660,7 +662,7 @@ static int ntc_thermistor_probe(struct platform_device *pdev) return -EINVAL; } - pdev_id = of_id ? of_id->data : platform_get_device_id(pdev); + pdev_id = device_get_match_data(dev); if (pdev_id->driver_data >= ARRAY_SIZE(ntc_type)) { dev_err(dev, "Unknown device type: %lu(%s)\n", @@ -688,7 +690,7 @@ static int ntc_thermistor_probe(struct platform_device *pdev) static struct platform_driver ntc_thermistor_driver = { .driver = { .name = "ntc-thermistor", - .of_match_table = of_match_ptr(ntc_match), + .of_match_table = ntc_match, }, .probe = ntc_thermistor_probe, .id_table = ntc_thermistor_id, -- cgit v1.2.3-59-g8ed1b From 9f448e796cf9d525fb9e1aa0d4fee073b80f1cab Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 6 Dec 2021 00:59:48 +0100 Subject: hwmon: (ntc_thermistor) Move DT matches to the driver block This moves the device tree match data toward the end of the driver which is the convention, here we can also add ACPI and similar match data in a conforming manner. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211205235948.4167075-3-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 70 +++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index 0c8b3dbcb38b..00356c28e8c8 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -368,41 +368,6 @@ static int ntc_adc_iio_read(struct ntc_data *data) return uv; } -static const struct of_device_id ntc_match[] = { - { .compatible = "epcos,b57330v2103", - .data = &ntc_thermistor_id[NTC_B57330V2103]}, - { .compatible = "epcos,b57891s0103", - .data = &ntc_thermistor_id[NTC_B57891S0103] }, - { .compatible = "murata,ncp03wb473", - .data = &ntc_thermistor_id[NTC_NCP03WB473] }, - { .compatible = "murata,ncp03wf104", - .data = &ntc_thermistor_id[NTC_NCP03WF104] }, - { .compatible = "murata,ncp15wb473", - .data = &ntc_thermistor_id[NTC_NCP15WB473] }, - { .compatible = "murata,ncp15wl333", - .data = &ntc_thermistor_id[NTC_NCP15WL333] }, - { .compatible = "murata,ncp15xh103", - .data = &ntc_thermistor_id[NTC_NCP15XH103] }, - { .compatible = "murata,ncp18wb473", - .data = &ntc_thermistor_id[NTC_NCP18WB473] }, - { .compatible = "murata,ncp21wb473", - .data = &ntc_thermistor_id[NTC_NCP21WB473] }, - - /* Usage of vendor name "ntc" is deprecated */ - { .compatible = "ntc,ncp03wb473", - .data = &ntc_thermistor_id[NTC_NCP03WB473] }, - { .compatible = "ntc,ncp15wb473", - .data = &ntc_thermistor_id[NTC_NCP15WB473] }, - { .compatible = "ntc,ncp15wl333", - .data = &ntc_thermistor_id[NTC_NCP15WL333] }, - { .compatible = "ntc,ncp18wb473", - .data = &ntc_thermistor_id[NTC_NCP18WB473] }, - { .compatible = "ntc,ncp21wb473", - .data = &ntc_thermistor_id[NTC_NCP21WB473] }, - { }, -}; -MODULE_DEVICE_TABLE(of, ntc_match); - static inline u64 div64_u64_safe(u64 dividend, u64 divisor) { if (divisor == 0 && dividend == 0) @@ -687,6 +652,41 @@ static int ntc_thermistor_probe(struct platform_device *pdev) return 0; } +static const struct of_device_id ntc_match[] = { + { .compatible = "epcos,b57330v2103", + .data = &ntc_thermistor_id[NTC_B57330V2103]}, + { .compatible = "epcos,b57891s0103", + .data = &ntc_thermistor_id[NTC_B57891S0103] }, + { .compatible = "murata,ncp03wb473", + .data = &ntc_thermistor_id[NTC_NCP03WB473] }, + { .compatible = "murata,ncp03wf104", + .data = &ntc_thermistor_id[NTC_NCP03WF104] }, + { .compatible = "murata,ncp15wb473", + .data = &ntc_thermistor_id[NTC_NCP15WB473] }, + { .compatible = "murata,ncp15wl333", + .data = &ntc_thermistor_id[NTC_NCP15WL333] }, + { .compatible = "murata,ncp15xh103", + .data = &ntc_thermistor_id[NTC_NCP15XH103] }, + { .compatible = "murata,ncp18wb473", + .data = &ntc_thermistor_id[NTC_NCP18WB473] }, + { .compatible = "murata,ncp21wb473", + .data = &ntc_thermistor_id[NTC_NCP21WB473] }, + + /* Usage of vendor name "ntc" is deprecated */ + { .compatible = "ntc,ncp03wb473", + .data = &ntc_thermistor_id[NTC_NCP03WB473] }, + { .compatible = "ntc,ncp15wb473", + .data = &ntc_thermistor_id[NTC_NCP15WB473] }, + { .compatible = "ntc,ncp15wl333", + .data = &ntc_thermistor_id[NTC_NCP15WL333] }, + { .compatible = "ntc,ncp18wb473", + .data = &ntc_thermistor_id[NTC_NCP18WB473] }, + { .compatible = "ntc,ncp21wb473", + .data = &ntc_thermistor_id[NTC_NCP21WB473] }, + { }, +}; +MODULE_DEVICE_TABLE(of, ntc_match); + static struct platform_driver ntc_thermistor_driver = { .driver = { .name = "ntc-thermistor", -- cgit v1.2.3-59-g8ed1b From c2fe0f63cafe3fc3adbd0aff6f1758b504ee3cdb Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 15 Dec 2021 14:40:50 +0300 Subject: hwmon: (nct6775) delete some extension lines This code can fit on one line. No need to break it up. Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20211215114050.GB14967@kili Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 57ce8633a725..2c5057fa3b71 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -3154,10 +3154,8 @@ store_speed_tolerance(struct device *dev, struct device_attribute *attr, 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; + 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) -- cgit v1.2.3-59-g8ed1b From 9c6d555187f504b880ca506b0b2d0edbdb5d2d5f Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 11 Dec 2021 19:44:49 +0100 Subject: hwmon: (raspberrypi) Exit immediately in case of error in init Exit immediately if devm_hwmon_device_register_with_info() fails since registering a delayed work whould be useless in such a case anyway. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20211211184449.18211-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/raspberrypi-hwmon.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c index 573f53d52912..1650d3b4c26e 100644 --- a/drivers/hwmon/raspberrypi-hwmon.c +++ b/drivers/hwmon/raspberrypi-hwmon.c @@ -120,6 +120,8 @@ static int rpi_hwmon_probe(struct platform_device *pdev) data, &rpi_chip_info, NULL); + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); ret = devm_delayed_work_autocancel(dev, &data->get_values_poll_work, get_values_poll); @@ -127,10 +129,9 @@ static int rpi_hwmon_probe(struct platform_device *pdev) return ret; platform_set_drvdata(pdev, data); - if (!PTR_ERR_OR_ZERO(data->hwmon_dev)) - schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); + schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); - return PTR_ERR_OR_ZERO(data->hwmon_dev); + return 0; } static struct platform_driver rpi_hwmon_driver = { -- cgit v1.2.3-59-g8ed1b From 02405387746915b93b283c18780b6cef90394ea1 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 11 Dec 2021 16:54:21 +0100 Subject: hwmon: (dell-smm) Simplify ioctl handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The second switch-case has no real purpose: - for I8K_BIOS_VERSION, val does not represent a return value, making the check for error values unnecessary. - for I8K_MACHINE_ID, val remains zero, so the error check is unnecessary too. Remove the switch-case and move the calls to copy_to_user() into the first switch-case for I8K_BIOS_VERSION/_MACHINE_ID. Omit buff[] since data->bios_machineid already contains the string with the necessary zero padding through devm_kzalloc(). Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20211211155422.16830-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 5596c211f38d..186d40938036 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -454,7 +454,6 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd { int val = 0; int speed, err; - unsigned char buff[16]; int __user *argp = (int __user *)arg; if (!argp) @@ -468,15 +467,19 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd val = (data->bios_version[0] << 16) | (data->bios_version[1] << 8) | data->bios_version[2]; - break; + if (copy_to_user(argp, &val, sizeof(val))) + return -EFAULT; + + return 0; case I8K_MACHINE_ID: if (restricted && !capable(CAP_SYS_ADMIN)) return -EPERM; - strscpy_pad(buff, data->bios_machineid, sizeof(buff)); - break; + if (copy_to_user(argp, data->bios_machineid, sizeof(data->bios_machineid))) + return -EFAULT; + return 0; case I8K_FN_STATUS: val = i8k_get_fn_status(); break; @@ -527,23 +530,8 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (val < 0) return val; - switch (cmd) { - case I8K_BIOS_VERSION: - if (copy_to_user(argp, &val, 4)) - return -EFAULT; - - break; - case I8K_MACHINE_ID: - if (copy_to_user(argp, buff, 16)) - return -EFAULT; - - break; - default: - if (copy_to_user(argp, &val, sizeof(int))) - return -EFAULT; - - break; - } + if (copy_to_user(argp, &val, sizeof(int))) + return -EFAULT; return 0; } -- cgit v1.2.3-59-g8ed1b From 87b93329fdd64bbb87db72d06ca084a06d183d6d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 11 Dec 2021 16:54:22 +0100 Subject: hwmon: (dell-smm) Unify i8k_ioctl() and i8k_ioctl_unlocked() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only purpose of i8k_ioctl() is to call i8k_ioctl_unlocked() with i8k_mutex held. Judging from the hwmon code, this mutex only needs to be held when setting the fan speed/mode, so the operation of I8K_SET_FAN is guaranteed to be atomic. Unify both functions and reduce the locking of i8k_mutex to I8K_SET_FAN. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20211211155422.16830-3-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 186d40938036..d8c6e75bb374 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -449,12 +449,12 @@ static int i8k_get_power_status(void) * Procfs interface */ -static int -i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg) +static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { - int val = 0; - int speed, err; + struct dell_smm_data *data = PDE_DATA(file_inode(fp)); int __user *argp = (int __user *)arg; + int speed, err; + int val = 0; if (!argp) return -EINVAL; @@ -516,11 +516,13 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (copy_from_user(&speed, argp + 1, sizeof(int))) return -EFAULT; + mutex_lock(&data->i8k_mutex); err = i8k_set_fan(data, val, speed); if (err < 0) - return err; - - val = i8k_get_fan_status(data, val); + val = err; + else + val = i8k_get_fan_status(data, val); + mutex_unlock(&data->i8k_mutex); break; default: @@ -536,18 +538,6 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd return 0; } -static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) -{ - struct dell_smm_data *data = PDE_DATA(file_inode(fp)); - long ret; - - mutex_lock(&data->i8k_mutex); - ret = i8k_ioctl_unlocked(fp, data, cmd, arg); - mutex_unlock(&data->i8k_mutex); - - return ret; -} - /* * Print the information for /proc/i8k. */ -- cgit v1.2.3-59-g8ed1b From 8569e5558d9fa5be2d57ce7195566861a45984c1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 15 Dec 2021 15:29:33 +0100 Subject: hwmon: (ntc_thermistor) Drop OF dependency The driver has been augmented to just use device properties so the OF dependency can be dropped. Cc: Peter Rosin Cc: Chris Lesiak Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20211215142933.1409324-1-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 43e5245874ad..3e6064203d65 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1415,7 +1415,6 @@ config SENSORS_PC87427 config SENSORS_NTC_THERMISTOR tristate "NTC thermistor support" - depends on OF depends on IIO depends on THERMAL || !THERMAL_OF help -- cgit v1.2.3-59-g8ed1b From e13e979b2b3dafc9b5e7a4ec0ff17c875fedcf67 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 15 Dec 2021 18:42:41 +0100 Subject: hwmon: (ntc_thermistor) Add Samsung 1404-001221 NTC This adds the Samsung 1404-001221 NTC thermistor to the NTC thermistor driver. As far as I can tell it is electrically compatible with the Murata 47K NTC thermistor. This thermistor is mounted in a variety of Samsung products. Cc: Peter Rosin Cc: Chris Lesiak Cc: devicetree@vger.kernel.org Signed-off-by: Linus Walleij Acked-by: Rob Herring Link: https://lore.kernel.org/r/20211215174241.1496169-1-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml | 1 + drivers/hwmon/ntc_thermistor.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml b/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml index 9e77cee07dbc..3d3b139a91a2 100644 --- a/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml +++ b/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml @@ -76,6 +76,7 @@ properties: - const: murata,ncp15wl333 - const: murata,ncp03wf104 - const: murata,ncp15xh103 + - const: samsung,1404-001221 # Deprecated "ntp," compatible strings - const: ntc,ncp15wb473 deprecated: true diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index 00356c28e8c8..414204f5704c 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -45,6 +45,7 @@ enum { NTC_NCP15XH103, NTC_NCP18WB473, NTC_NCP21WB473, + NTC_SSG1404001221, NTC_LAST, }; @@ -58,6 +59,7 @@ static const struct platform_device_id ntc_thermistor_id[] = { [NTC_NCP15XH103] = { "ncp15xh103", TYPE_NCPXXXH103 }, [NTC_NCP18WB473] = { "ncp18wb473", TYPE_NCPXXWB473 }, [NTC_NCP21WB473] = { "ncp21wb473", TYPE_NCPXXWB473 }, + [NTC_SSG1404001221] = { "ssg1404-001221", TYPE_NCPXXWB473 }, [NTC_LAST] = { }, }; @@ -671,6 +673,8 @@ static const struct of_device_id ntc_match[] = { .data = &ntc_thermistor_id[NTC_NCP18WB473] }, { .compatible = "murata,ncp21wb473", .data = &ntc_thermistor_id[NTC_NCP21WB473] }, + { .compatible = "samsung,1404-001221", + .data = &ntc_thermistor_id[NTC_SSG1404001221] }, /* Usage of vendor name "ntc" is deprecated */ { .compatible = "ntc,ncp03wb473", -- cgit v1.2.3-59-g8ed1b From 130d168866a11829b844ffdb19b9aefe384f754c Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Thu, 16 Dec 2021 16:42:57 +0100 Subject: hwmon: prefix kernel-doc comments for structs with struct The command ./scripts/kernel-doc -none include/linux/hwmon.h warns: include/linux/hwmon.h:406: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Channel information include/linux/hwmon.h:425: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Chip configuration Address those kernel-doc warnings by prefixing kernel-doc descriptions for structs with the keyword 'struct'. Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20211216154257.26758-1-lukas.bulwahn@gmail.com Signed-off-by: Guenter Roeck --- include/linux/hwmon.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 1e8d6ea8992e..fad1f1df26df 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -403,7 +403,7 @@ struct hwmon_ops { }; /** - * Channel information + * struct hwmon_channel_info - Channel information * @type: Channel type. * @config: Pointer to NULL-terminated list of channel parameters. * Use for per-channel attributes. @@ -422,7 +422,7 @@ struct hwmon_channel_info { }) /** - * Chip configuration + * struct hwmon_chip_info - Chip configuration * @ops: Pointer to hwmon operations. * @info: Null-terminated list of channel information. */ -- cgit v1.2.3-59-g8ed1b From d387d88ed045a0a2db0698d079b06822f75d940b Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Wed, 8 Dec 2021 13:37:02 -0800 Subject: hwmon: (pmbus) Add Delta AHE-50DC fan control module driver This device is an integrated module of the Delta AHE-50DC Open19 power shelf. I haven't been able to procure any proper documentation for it, but it seems to be a (somewhat minimally) PMBus-compliant device. It provides four fan speeds, four temperatures (three standard and one manufacturer-specific via a virtual second page), and a vin reading. Signed-off-by: Zev Weiss Link: https://lore.kernel.org/r/20211208213703.2577-2-zev@bewilderbeest.net Signed-off-by: Guenter Roeck --- MAINTAINERS | 6 ++ drivers/hwmon/pmbus/Kconfig | 10 +++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/delta-ahe50dc-fan.c | 114 ++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 drivers/hwmon/pmbus/delta-ahe50dc-fan.c diff --git a/MAINTAINERS b/MAINTAINERS index 979195fbd054..5a00307d67e3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5436,6 +5436,12 @@ W: https://linuxtv.org T: git git://linuxtv.org/media_tree.git F: drivers/media/platform/sti/delta +DELTA AHE-50DC FAN CONTROL MODULE DRIVER +M: Zev Weiss +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/pmbus/delta-ahe50dc-fan.c + DELTA DPS920AB PSU DRIVER M: Robert Marko L: linux-hwmon@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index ffb609cee3a4..0b1157b883aa 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -66,6 +66,16 @@ config SENSORS_BPA_RS600 This driver can also be built as a module. If so, the module will be called bpa-rs600. +config SENSORS_DELTA_AHE50DC_FAN + tristate "Delta AHE-50DC fan control module" + help + If you say yes here you get hardware monitoring support for + the integrated fan control module of the Delta AHE-50DC + Open19 power shelf. + + This driver can also be built as a module. If so, the module + will be called delta-ahe50dc-fan. + config SENSORS_FSP_3Y tristate "FSP/3Y-Power power supplies" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 0ed4d596a948..a56b2897288d 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o +obj-$(CONFIG_SENSORS_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o obj-$(CONFIG_SENSORS_DPS920AB) += dps920ab.o diff --git a/drivers/hwmon/pmbus/delta-ahe50dc-fan.c b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c new file mode 100644 index 000000000000..40dffd9c4cbf --- /dev/null +++ b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Delta AHE-50DC power shelf fan control module driver + * + * Copyright 2021 Zev Weiss + */ + +#include +#include +#include +#include + +#include "pmbus.h" + +#define AHE50DC_PMBUS_READ_TEMP4 0xd0 + +static int ahe50dc_fan_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + /* temp1 in (virtual) page 1 is remapped to mfr-specific temp4 */ + if (page == 1) { + if (reg == PMBUS_READ_TEMPERATURE_1) + return i2c_smbus_read_word_data(client, AHE50DC_PMBUS_READ_TEMP4); + return -EOPNOTSUPP; + } + + /* + * There's a fairly limited set of commands this device actually + * supports, so here we block attempts to read anything else (which + * return 0xffff and would cause confusion elsewhere). + */ + switch (reg) { + case PMBUS_STATUS_WORD: + case PMBUS_FAN_COMMAND_1: + case PMBUS_FAN_COMMAND_2: + case PMBUS_FAN_COMMAND_3: + case PMBUS_FAN_COMMAND_4: + case PMBUS_STATUS_FAN_12: + case PMBUS_STATUS_FAN_34: + case PMBUS_READ_VIN: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_READ_TEMPERATURE_2: + case PMBUS_READ_TEMPERATURE_3: + case PMBUS_READ_FAN_SPEED_1: + case PMBUS_READ_FAN_SPEED_2: + case PMBUS_READ_FAN_SPEED_3: + case PMBUS_READ_FAN_SPEED_4: + return -ENODATA; + default: + return -EOPNOTSUPP; + } +} + +static struct pmbus_driver_info ahe50dc_fan_info = { + .pages = 2, + .format[PSC_FAN] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 1, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .func[0] = PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_VIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_FAN34 | + PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_STATUS_FAN34 | PMBUS_PAGE_VIRTUAL, + .func[1] = PMBUS_HAVE_TEMP | PMBUS_PAGE_VIRTUAL, + .read_word_data = ahe50dc_fan_read_word_data, +}; + +/* + * CAPABILITY returns 0xff, which appears to be this device's way indicating + * it doesn't support something (and if we enable I2C_CLIENT_PEC on seeing bit + * 7 being set it generates bad PECs, so let's not go there). + */ +static struct pmbus_platform_data ahe50dc_fan_data = { + .flags = PMBUS_NO_CAPABILITY, +}; + +static int ahe50dc_fan_probe(struct i2c_client *client) +{ + client->dev.platform_data = &ahe50dc_fan_data; + return pmbus_do_probe(client, &ahe50dc_fan_info); +} + +static const struct i2c_device_id ahe50dc_fan_id[] = { + { "ahe50dc_fan" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ahe50dc_fan_id); + +static const struct of_device_id __maybe_unused ahe50dc_fan_of_match[] = { + { .compatible = "delta,ahe50dc-fan" }, + { } +}; +MODULE_DEVICE_TABLE(of, ahe50dc_fan_of_match); + +static struct i2c_driver ahe50dc_fan_driver = { + .driver = { + .name = "ahe50dc_fan", + .of_match_table = of_match_ptr(ahe50dc_fan_of_match), + }, + .probe_new = ahe50dc_fan_probe, + .id_table = ahe50dc_fan_id, +}; +module_i2c_driver(ahe50dc_fan_driver); + +MODULE_AUTHOR("Zev Weiss "); +MODULE_DESCRIPTION("Driver for Delta AHE-50DC power shelf fan control module"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); -- cgit v1.2.3-59-g8ed1b From 0710e2b9f9b7d48e666a5f4b5de742050be3d66b Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Wed, 8 Dec 2021 13:37:03 -0800 Subject: dt-bindings: add Delta AHE-50DC fan control module This is the integrated fan control module of the Delta AHE-50DC Open19 power shelf. Signed-off-by: Zev Weiss Acked-by: Rob Herring Link: https://lore.kernel.org/r/20211208213703.2577-3-zev@bewilderbeest.net Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 791079021f1b..1c43cc91f804 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -73,6 +73,8 @@ properties: - dallas,ds4510 # Digital Thermometer and Thermostat - dallas,ds75 + # Delta AHE-50DC Open19 power shelf fan control module + - delta,ahe50dc-fan # Delta Electronics DPS-650-AB power supply - delta,dps650ab # Delta Electronics DPS920AB 920W 54V Power Supply -- cgit v1.2.3-59-g8ed1b From e1c5cd7e8af0f50a1deb15369b1cdcef4e6a7f85 Mon Sep 17 00:00:00 2001 From: "Howard.Chiu@quantatw.com" Date: Thu, 9 Dec 2021 04:48:54 +0000 Subject: hwmon: (pmbus) Add support for MPS Multi-phase mp5023 Add support for mp5023 device from Monolithic Power Systems, Inc. (MPS) vendor. This is a Hot-Swap Controller. Signed-off-by: Howard Chiu Link: https://lore.kernel.org/r/HKAPR04MB400349AA406694FB976D78D096709@HKAPR04MB4003.apcprd04.prod.outlook.com [groeck: Added MODULE_IMPORT_NS, entry in index.rst] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mp5023.rst | 84 ++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/pmbus/Kconfig | 9 +++++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/mp5023.c | 67 +++++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+) create mode 100644 Documentation/hwmon/mp5023.rst create mode 100644 drivers/hwmon/pmbus/mp5023.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 9c9361d76f3d..284dfe20f8dc 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -145,6 +145,7 @@ Hardware Monitoring Kernel Drivers mlxreg-fan mp2888 mp2975 + mp5023 nct6683 nct6775 nct7802 diff --git a/Documentation/hwmon/mp5023.rst b/Documentation/hwmon/mp5023.rst new file mode 100644 index 000000000000..af5ab1345a91 --- /dev/null +++ b/Documentation/hwmon/mp5023.rst @@ -0,0 +1,84 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver mp5023 +==================== + +Supported chips: + + * MPS MP5023 + + Prefix: 'mp5023' + + * Datasheet + + Publicly available at the MPS website : https://www.monolithicpower.com/en/mp5023.html + +Author: + + Howard Chiu + +Description +----------- + +This driver implements support for Monolithic Power Systems, Inc. (MPS) +MP5023 Hot-Swap Controller. + +Device complaint with: + +- PMBus rev 1.3 interface. + +Device supports direct format for reading input voltage, output voltage, +output current, input power and temperature. + +The driver exports the following attributes via the 'sysfs' files +for input voltage: + +**in1_input** + +**in1_label** + +**in1_max** + +**in1_max_alarm** + +**in1_min** + +**in1_min_alarm** + +The driver provides the following attributes for output voltage: + +**in2_input** + +**in2_label** + +**in2_alarm** + +The driver provides the following attributes for output current: + +**curr1_input** + +**curr1_label** + +**curr1_alarm** + +**curr1_max** + +The driver provides the following attributes for input power: + +**power1_input** + +**power1_label** + +**power1_alarm** + +The driver provides the following attributes for temperature: + +**temp1_input** + +**temp1_max** + +**temp1_max_alarm** + +**temp1_crit** + +**temp1_crit_alarm** diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 0b1157b883aa..44587575c8e0 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -286,6 +286,15 @@ config SENSORS_MP2975 This driver can also be built as a module. If so, the module will be called mp2975. +config SENSORS_MP5023 + tristate "MPS MP5023" + help + If you say yes here you get hardware monitoring support for MPS + MP5023. + + This driver can also be built as a module. If so, the module will + be called mp5023. + config SENSORS_PIM4328 tristate "Flex PIM4328 and compatibles" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index a56b2897288d..e5935f70c9e0 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_MP2888) += mp2888.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o +obj-$(CONFIG_SENSORS_MP5023) += mp5023.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o diff --git a/drivers/hwmon/pmbus/mp5023.c b/drivers/hwmon/pmbus/mp5023.c new file mode 100644 index 000000000000..791a06c3c54a --- /dev/null +++ b/drivers/hwmon/pmbus/mp5023.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MP5023 Hot-Swap Controller + */ + +#include +#include +#include +#include "pmbus.h" + +static struct pmbus_driver_info mp5023_info = { + .pages = 1, + + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + + .m[PSC_VOLTAGE_IN] = 32, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 32, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .m[PSC_TEMPERATURE] = 2, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + + .func[0] = + PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, +}; + +static int mp5023_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &mp5023_info); +} + +static const struct of_device_id __maybe_unused mp5023_of_match[] = { + { .compatible = "mps,mp5023", }, + {} +}; + +MODULE_DEVICE_TABLE(of, mp5023_of_match); + +static struct i2c_driver mp5023_driver = { + .driver = { + .name = "mp5023", + .of_match_table = of_match_ptr(mp5023_of_match), + }, + .probe_new = mp5023_probe, +}; + +module_i2c_driver(mp5023_driver); + +MODULE_AUTHOR("Howard Chiu "); +MODULE_DESCRIPTION("PMBus driver for MPS MP5023 HSC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); -- cgit v1.2.3-59-g8ed1b From 1e7c94b251d15e01e8dd13940d544c865467e339 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 11 Dec 2021 20:00:36 +0200 Subject: hwmon: (nct6775) add ROG STRIX B550-A/X570-I GAMING ASUS ROG STRIX B550-A/X570-I GAMING boards have got an nct6775 chip, but by default there's no use of it because of resource conflict with WMI method. This commit adds "ROG STRIX B550-A GAMING" and "ROG STRIX X570-I GAMING" to the list of boards that can be monitored using ASUS WMI. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Tested-by: Daniel Gibson Tested-by: Michael Altizer Tested-by: Mikhail Gavrilov Link: https://lore.kernel.org/r/20211211180037.367062-1-pauk.denis@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 2c5057fa3b71..c58538246cc8 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -4993,11 +4993,13 @@ static const char * const asus_wmi_boards[] = { "ROG CROSSHAIR VIII FORMULA", "ROG CROSSHAIR VIII HERO", "ROG CROSSHAIR VIII IMPACT", + "ROG STRIX B550-A GAMING", "ROG STRIX B550-E GAMING", "ROG STRIX B550-F GAMING", "ROG STRIX B550-F GAMING (WI-FI)", "ROG STRIX B550-I GAMING", "ROG STRIX X570-F GAMING", + "ROG STRIX X570-I GAMING", "ROG STRIX Z390-E GAMING", "ROG STRIX Z490-I GAMING", "TUF GAMING B550M-PLUS", -- cgit v1.2.3-59-g8ed1b From 53e68c20aeb1e23419bed811aa3a309ceda200f9 Mon Sep 17 00:00:00 2001 From: Aleksandr Mezin Date: Sun, 31 Oct 2021 09:30:58 +0600 Subject: hwmon: add driver for NZXT RGB&Fan Controller/Smart Device v2. This driver implements monitoring and control of fans plugged into the device. Besides typical speed monitoring and PWM duty cycle control, voltage and current are reported for every fan. The device also has 2 connectors for RGB LEDs, support for them isn't implemented (mainly because there is no standardized sysfs interface). Also, the device has a noise sensor, but the sensor seems to be completely useless (and very imprecise), so support for it isn't implemented too. The driver coexists with userspace tools that access the device through hidraw interface with no known issues. The driver has been tested on x86_64, built in and as a module. Some changes/improvements were suggested by Jonas Malaco. Signed-off-by: Aleksandr Mezin Link: https://lore.kernel.org/r/20211031033058.151014-1-mezin.alexander@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/nzxt-smart2.rst | 62 +++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/nzxt-smart2.c | 829 ++++++++++++++++++++++++++++++++++++ 6 files changed, 910 insertions(+) create mode 100644 Documentation/hwmon/nzxt-smart2.rst create mode 100644 drivers/hwmon/nzxt-smart2.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 284dfe20f8dc..df20022c741f 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -154,6 +154,7 @@ Hardware Monitoring Kernel Drivers nsa320 ntc_thermistor nzxt-kraken2 + nzxt-smart2 occ pc87360 pc87427 diff --git a/Documentation/hwmon/nzxt-smart2.rst b/Documentation/hwmon/nzxt-smart2.rst new file mode 100644 index 000000000000..d9d1b2742665 --- /dev/null +++ b/Documentation/hwmon/nzxt-smart2.rst @@ -0,0 +1,62 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver nzxt-smart2 +========================= + +Supported devices: + +- NZXT RGB & Fan controller +- NZXT Smart Device v2 + +Description +----------- + +This driver implements monitoring and control of fans plugged into the device. +Besides typical speed monitoring and PWM duty cycle control, voltage and current +is reported for every fan. + +The device also has two connectors for RGB LEDs; support for them isn't +implemented (mainly because there is no standardized sysfs interface). + +Also, the device has a noise sensor, but the sensor seems to be completely +useless (and very imprecise), so support for it isn't implemented too. + +Usage Notes +----------- + +The device should be autodetected, and the driver should load automatically. + +If fans are plugged in/unplugged while the system is powered on, the driver +must be reloaded to detect configuration changes; otherwise, new fans can't +be controlled (`pwm*` changes will be ignored). It is necessary because the +device has a dedicated "detect fans" command, and currently, it is executed only +during initialization. Speed, voltage, current monitoring will work even without +reload. As an alternative to reloading the module, a userspace tool (like +`liquidctl`_) can be used to run "detect fans" command through hidraw interface. + +The driver coexists with userspace tools that access the device through hidraw +interface with no known issues. + +.. _liquidctl: https://github.com/liquidctl/liquidctl + +Sysfs entries +------------- + +======================= ======================================================== +fan[1-3]_input Fan speed monitoring (in rpm). +curr[1-3]_input Current supplied to the fan (in milliamperes). +in[0-2]_input Voltage supplied to the fan (in millivolts). +pwm[1-3] Controls fan speed: PWM duty cycle for PWM-controlled + fans, voltage for other fans. Voltage can be changed in + 9-12 V range, but the value of the sysfs attribute is + always in 0-255 range (1 = 9V, 255 = 12V). Setting the + attribute to 0 turns off the fan completely. +pwm[1-3]_enable 1 if the fan can be controlled by writing to the + corresponding pwm* attribute, 0 otherwise. The device + can control only the fans it detected itself, so the + attribute is read-only. +pwm[1-3]_mode Read-only, 1 for PWM-controlled fans, 0 for other fans + (or if no fan connected). +update_interval The interval at which all inputs are updated (in + milliseconds). The default is 1000ms. Minimum is 250ms. +======================= ======================================================== diff --git a/MAINTAINERS b/MAINTAINERS index 5a00307d67e3..e7e40563498f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13805,6 +13805,13 @@ S: Maintained F: Documentation/hwmon/nzxt-kraken2.rst F: drivers/hwmon/nzxt-kraken2.c +NZXT-SMART2 HARDWARE MONITORING DRIVER +M: Aleksandr Mezin +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/nzxt-smart2.rst +F: drivers/hwmon/nzxt-smart2.c + OBJAGG M: Jiri Pirko L: netdev@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3e6064203d65..8df25f1079ba 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1513,6 +1513,16 @@ config SENSORS_NZXT_KRAKEN2 This driver can also be built as a module. If so, the module will be called nzxt-kraken2. +config SENSORS_NZXT_SMART2 + tristate "NZXT RGB & Fan Controller/Smart Device v2" + depends on USB_HID + help + If you say yes here you get support for hardware monitoring for the + NZXT RGB & Fan Controller/Smart Device v2. + + This driver can also be built as a module. If so, the module + will be called nzxt-smart2. + source "drivers/hwmon/occ/Kconfig" config SENSORS_PCF8591 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3a1551b3d570..185f946d698b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -160,6 +160,7 @@ obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o +obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c new file mode 100644 index 000000000000..534d39b8908e --- /dev/null +++ b/drivers/hwmon/nzxt-smart2.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Reverse-engineered NZXT RGB & Fan Controller/Smart Device v2 driver. + * + * Copyright (c) 2021 Aleksandr Mezin + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * The device has only 3 fan channels/connectors. But all HID reports have + * space reserved for up to 8 channels. + */ +#define FAN_CHANNELS 3 +#define FAN_CHANNELS_MAX 8 + +#define UPDATE_INTERVAL_DEFAULT_MS 1000 + +/* These strings match labels on the device exactly */ +static const char *const fan_label[] = { + "FAN 1", + "FAN 2", + "FAN 3", +}; + +static const char *const curr_label[] = { + "FAN 1 Current", + "FAN 2 Current", + "FAN 3 Current", +}; + +static const char *const in_label[] = { + "FAN 1 Voltage", + "FAN 2 Voltage", + "FAN 3 Voltage", +}; + +enum { + INPUT_REPORT_ID_FAN_CONFIG = 0x61, + INPUT_REPORT_ID_FAN_STATUS = 0x67, +}; + +enum { + FAN_STATUS_REPORT_SPEED = 0x02, + FAN_STATUS_REPORT_VOLTAGE = 0x04, +}; + +enum { + FAN_TYPE_NONE = 0, + FAN_TYPE_DC = 1, + FAN_TYPE_PWM = 2, +}; + +struct unknown_static_data { + /* + * Some configuration data? Stays the same after fan speed changes, + * changes in fan configuration, reboots and driver reloads. + * + * The same data in multiple report types. + * + * Byte 12 seems to be the number of fan channels, but I am not sure. + */ + u8 unknown1[14]; +} __packed; + +/* + * The device sends this input report in response to "detect fans" command: + * a 2-byte output report { 0x60, 0x03 }. + */ +struct fan_config_report { + /* report_id should be INPUT_REPORT_ID_FAN_CONFIG = 0x61 */ + u8 report_id; + /* Always 0x03 */ + u8 magic; + struct unknown_static_data unknown_data; + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ + u8 fan_type[FAN_CHANNELS_MAX]; +} __packed; + +/* + * The device sends these reports at a fixed interval (update interval) - + * one report with type = FAN_STATUS_REPORT_SPEED, and one report with type = + * FAN_STATUS_REPORT_VOLTAGE per update interval. + */ +struct fan_status_report { + /* report_id should be INPUT_REPORT_ID_STATUS = 0x67 */ + u8 report_id; + /* FAN_STATUS_REPORT_SPEED = 0x02 or FAN_STATUS_REPORT_VOLTAGE = 0x04 */ + u8 type; + struct unknown_static_data unknown_data; + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ + u8 fan_type[FAN_CHANNELS_MAX]; + + union { + /* When type == FAN_STATUS_REPORT_SPEED */ + struct { + /* + * Fan speed, in RPM. Zero for channels without fans + * connected. + */ + __le16 fan_rpm[FAN_CHANNELS_MAX]; + /* + * Fan duty cycle, in percent. Non-zero even for + * channels without fans connected. + */ + u8 duty_percent[FAN_CHANNELS_MAX]; + /* + * Exactly the same values as duty_percent[], non-zero + * for disconnected fans too. + */ + u8 duty_percent_dup[FAN_CHANNELS_MAX]; + /* "Case Noise" in db */ + u8 noise_db; + } __packed fan_speed; + /* When type == FAN_STATUS_REPORT_VOLTAGE */ + struct { + /* + * Voltage, in millivolts. Non-zero even when fan is + * not connected. + */ + __le16 fan_in[FAN_CHANNELS_MAX]; + /* + * Current, in milliamperes. Near-zero when + * disconnected. + */ + __le16 fan_current[FAN_CHANNELS_MAX]; + } __packed fan_voltage; + } __packed; +} __packed; + +#define OUTPUT_REPORT_SIZE 64 + +enum { + OUTPUT_REPORT_ID_INIT_COMMAND = 0x60, + OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62, +}; + +enum { + INIT_COMMAND_SET_UPDATE_INTERVAL = 0x02, + INIT_COMMAND_DETECT_FANS = 0x03, +}; + +/* + * This output report sets pwm duty cycle/target fan speed for one or more + * channels. + */ +struct set_fan_speed_report { + /* report_id should be OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62 */ + u8 report_id; + /* Should be 0x01 */ + u8 magic; + /* To change fan speed on i-th channel, set i-th bit here */ + u8 channel_bit_mask; + /* + * Fan duty cycle/target speed in percent. For voltage-controlled fans, + * the minimal voltage (duty_percent = 1) is about 9V. + * Setting duty_percent to 0 (if the channel is selected in + * channel_bit_mask) turns off the fan completely (regardless of the + * control mode). + */ + u8 duty_percent[FAN_CHANNELS_MAX]; +} __packed; + +struct drvdata { + struct hid_device *hid; + struct device *hwmon; + + u8 fan_duty_percent[FAN_CHANNELS]; + u16 fan_rpm[FAN_CHANNELS]; + bool pwm_status_received; + + u16 fan_in[FAN_CHANNELS]; + u16 fan_curr[FAN_CHANNELS]; + bool voltage_status_received; + + u8 fan_type[FAN_CHANNELS]; + bool fan_config_received; + + /* + * wq is used to wait for *_received flags to become true. + * All accesses to *_received flags and fan_* arrays are performed with + * wq.lock held. + */ + wait_queue_head_t wq; + /* + * mutex is used to: + * 1) Prevent concurrent conflicting changes to update interval and pwm + * values (after sending an output hid report, the corresponding field + * in drvdata must be updated, and only then new output reports can be + * sent). + * 2) Synchronize access to output_buffer (well, the buffer is here, + * because synchronization is necessary anyway - so why not get rid of + * a kmalloc?). + */ + struct mutex mutex; + long update_interval; + u8 output_buffer[OUTPUT_REPORT_SIZE]; +}; + +static long scale_pwm_value(long val, long orig_max, long new_max) +{ + if (val <= 0) + return 0; + + /* + * Positive values should not become zero: 0 completely turns off the + * fan. + */ + return max(1L, DIV_ROUND_CLOSEST(min(val, orig_max) * new_max, orig_max)); +} + +static void handle_fan_config_report(struct drvdata *drvdata, void *data, int size) +{ + struct fan_config_report *report = data; + int i; + + if (size < sizeof(struct fan_config_report)) + return; + + if (report->magic != 0x03) + return; + + spin_lock(&drvdata->wq.lock); + + for (i = 0; i < FAN_CHANNELS; i++) + drvdata->fan_type[i] = report->fan_type[i]; + + drvdata->fan_config_received = true; + wake_up_all_locked(&drvdata->wq); + spin_unlock(&drvdata->wq.lock); +} + +static void handle_fan_status_report(struct drvdata *drvdata, void *data, int size) +{ + struct fan_status_report *report = data; + int i; + + if (size < sizeof(struct fan_status_report)) + return; + + spin_lock(&drvdata->wq.lock); + + /* + * The device sends INPUT_REPORT_ID_FAN_CONFIG = 0x61 report in response + * to "detect fans" command. Only accept other data after getting 0x61, + * to make sure that fan detection is complete. In particular, fan + * detection resets pwm values. + */ + if (!drvdata->fan_config_received) { + spin_unlock(&drvdata->wq.lock); + return; + } + + for (i = 0; i < FAN_CHANNELS; i++) { + if (drvdata->fan_type[i] == report->fan_type[i]) + continue; + + /* + * This should not happen (if my expectations about the device + * are correct). + * + * Even if the userspace sends fan detect command through + * hidraw, fan config report should arrive first. + */ + hid_warn_once(drvdata->hid, + "Fan %d type changed unexpectedly from %d to %d", + i, drvdata->fan_type[i], report->fan_type[i]); + drvdata->fan_type[i] = report->fan_type[i]; + } + + switch (report->type) { + case FAN_STATUS_REPORT_SPEED: + for (i = 0; i < FAN_CHANNELS; i++) { + drvdata->fan_rpm[i] = + get_unaligned_le16(&report->fan_speed.fan_rpm[i]); + drvdata->fan_duty_percent[i] = + report->fan_speed.duty_percent[i]; + } + + drvdata->pwm_status_received = true; + wake_up_all_locked(&drvdata->wq); + break; + + case FAN_STATUS_REPORT_VOLTAGE: + for (i = 0; i < FAN_CHANNELS; i++) { + drvdata->fan_in[i] = + get_unaligned_le16(&report->fan_voltage.fan_in[i]); + drvdata->fan_curr[i] = + get_unaligned_le16(&report->fan_voltage.fan_current[i]); + } + + drvdata->voltage_status_received = true; + wake_up_all_locked(&drvdata->wq); + break; + } + + spin_unlock(&drvdata->wq.lock); +} + +static umode_t nzxt_smart2_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: + return 0644; + + default: + return 0444; + } + + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + + default: + return 0444; + } + + default: + return 0444; + } +} + +static int nzxt_smart2_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct drvdata *drvdata = dev_get_drvdata(dev); + int res = -EINVAL; + + if (type == hwmon_chip) { + switch (attr) { + case hwmon_chip_update_interval: + *val = drvdata->update_interval; + return 0; + + default: + return -EINVAL; + } + } + + spin_lock_irq(&drvdata->wq.lock); + + switch (type) { + case hwmon_pwm: + /* + * fancontrol: + * 1) remembers pwm* values when it starts + * 2) needs pwm*_enable to be 1 on controlled fans + * So make sure we have correct data before allowing pwm* reads. + * Returning errors for pwm of fan speed read can even cause + * fancontrol to shut down. So the wait is unavoidable. + */ + switch (attr) { + case hwmon_pwm_enable: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) + goto unlock; + + *val = drvdata->fan_type[channel] != FAN_TYPE_NONE; + break; + + case hwmon_pwm_mode: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) + goto unlock; + + *val = drvdata->fan_type[channel] == FAN_TYPE_PWM; + break; + + case hwmon_pwm_input: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->pwm_status_received); + if (res) + goto unlock; + + *val = scale_pwm_value(drvdata->fan_duty_percent[channel], + 100, 255); + break; + } + break; + + case hwmon_fan: + /* + * It's not strictly necessary to wait for *_received in the + * remaining cases (fancontrol doesn't care about them). But I'm + * doing it to have consistent behavior. + */ + if (attr == hwmon_fan_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->pwm_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_rpm[channel]; + } + break; + + case hwmon_in: + if (attr == hwmon_in_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->voltage_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_in[channel]; + } + break; + + case hwmon_curr: + if (attr == hwmon_curr_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->voltage_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_curr[channel]; + } + break; + + default: + break; + } + +unlock: + spin_unlock_irq(&drvdata->wq.lock); + return res; +} + +static int send_output_report(struct drvdata *drvdata, const void *data, + size_t data_size) +{ + int ret; + + if (data_size > sizeof(drvdata->output_buffer)) + return -EINVAL; + + memcpy(drvdata->output_buffer, data, data_size); + + if (data_size < sizeof(drvdata->output_buffer)) + memset(drvdata->output_buffer + data_size, 0, + sizeof(drvdata->output_buffer) - data_size); + + ret = hid_hw_output_report(drvdata->hid, drvdata->output_buffer, + sizeof(drvdata->output_buffer)); + return ret < 0 ? ret : 0; +} + +static int set_pwm(struct drvdata *drvdata, int channel, long val) +{ + int ret; + u8 duty_percent = scale_pwm_value(val, 255, 100); + + struct set_fan_speed_report report = { + .report_id = OUTPUT_REPORT_ID_SET_FAN_SPEED, + .magic = 1, + .channel_bit_mask = 1 << channel + }; + + ret = mutex_lock_interruptible(&drvdata->mutex); + if (ret) + return ret; + + report.duty_percent[channel] = duty_percent; + ret = send_output_report(drvdata, &report, sizeof(report)); + if (ret) + goto unlock; + + /* + * pwmconfig and fancontrol scripts expect pwm writes to take effect + * immediately (i. e. read from pwm* sysfs should return the value + * written into it). The device seems to always accept pwm values - even + * when there is no fan connected - so update pwm status without waiting + * for a report, to make pwmconfig and fancontrol happy. Worst case - + * if the device didn't accept new pwm value for some reason (never seen + * this in practice) - it will be reported incorrectly only until next + * update. This avoids "fan stuck" messages from pwmconfig, and + * fancontrol setting fan speed to 100% during shutdown. + */ + spin_lock_bh(&drvdata->wq.lock); + drvdata->fan_duty_percent[channel] = duty_percent; + spin_unlock_bh(&drvdata->wq.lock); + +unlock: + mutex_unlock(&drvdata->mutex); + return ret; +} + +/* + * Workaround for fancontrol/pwmconfig trying to write to pwm*_enable even if it + * already is 1 and read-only. Otherwise, fancontrol won't restore pwm on + * shutdown properly. + */ +static int set_pwm_enable(struct drvdata *drvdata, int channel, long val) +{ + long expected_val; + int res; + + spin_lock_irq(&drvdata->wq.lock); + + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) { + spin_unlock_irq(&drvdata->wq.lock); + return res; + } + + expected_val = drvdata->fan_type[channel] != FAN_TYPE_NONE; + + spin_unlock_irq(&drvdata->wq.lock); + + return (val == expected_val) ? 0 : -EOPNOTSUPP; +} + +/* + * Control byte | Actual update interval in seconds + * 0xff | 65.5 + * 0xf7 | 63.46 + * 0x7f | 32.74 + * 0x3f | 16.36 + * 0x1f | 8.17 + * 0x0f | 4.07 + * 0x07 | 2.02 + * 0x03 | 1.00 + * 0x02 | 0.744 + * 0x01 | 0.488 + * 0x00 | 0.25 + */ +static u8 update_interval_to_control_byte(long interval) +{ + if (interval <= 250) + return 0; + + return clamp_val(1 + DIV_ROUND_CLOSEST(interval - 488, 256), 0, 255); +} + +static long control_byte_to_update_interval(u8 control_byte) +{ + if (control_byte == 0) + return 250; + + return 488 + (control_byte - 1) * 256; +} + +static int set_update_interval(struct drvdata *drvdata, long val) +{ + u8 control = update_interval_to_control_byte(val); + u8 report[] = { + OUTPUT_REPORT_ID_INIT_COMMAND, + INIT_COMMAND_SET_UPDATE_INTERVAL, + 0x01, + 0xe8, + control, + 0x01, + 0xe8, + control, + }; + int ret; + + ret = send_output_report(drvdata, report, sizeof(report)); + if (ret) + return ret; + + drvdata->update_interval = control_byte_to_update_interval(control); + return 0; +} + +static int init_device(struct drvdata *drvdata, long update_interval) +{ + int ret; + u8 detect_fans_report[] = { + OUTPUT_REPORT_ID_INIT_COMMAND, + INIT_COMMAND_DETECT_FANS, + }; + + ret = send_output_report(drvdata, detect_fans_report, + sizeof(detect_fans_report)); + if (ret) + return ret; + + return set_update_interval(drvdata, update_interval); +} + +static int nzxt_smart2_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct drvdata *drvdata = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + return set_pwm_enable(drvdata, channel, val); + + case hwmon_pwm_input: + return set_pwm(drvdata, channel, val); + + default: + return -EINVAL; + } + + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + ret = mutex_lock_interruptible(&drvdata->mutex); + if (ret) + return ret; + + ret = set_update_interval(drvdata, val); + + mutex_unlock(&drvdata->mutex); + return ret; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int nzxt_smart2_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_fan: + *str = fan_label[channel]; + return 0; + case hwmon_curr: + *str = curr_label[channel]; + return 0; + case hwmon_in: + *str = in_label[channel]; + return 0; + default: + return -EINVAL; + } +} + +static const struct hwmon_ops nzxt_smart2_hwmon_ops = { + .is_visible = nzxt_smart2_hwmon_is_visible, + .read = nzxt_smart2_hwmon_read, + .read_string = nzxt_smart2_hwmon_read_string, + .write = nzxt_smart2_hwmon_write, +}; + +static const struct hwmon_channel_info *nzxt_smart2_channel_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL), + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), + NULL +}; + +static const struct hwmon_chip_info nzxt_smart2_chip_info = { + .ops = &nzxt_smart2_hwmon_ops, + .info = nzxt_smart2_channel_info, +}; + +static int nzxt_smart2_hid_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + u8 report_id = *data; + + switch (report_id) { + case INPUT_REPORT_ID_FAN_CONFIG: + handle_fan_config_report(drvdata, data, size); + break; + + case INPUT_REPORT_ID_FAN_STATUS: + handle_fan_status_report(drvdata, data, size); + break; + } + + return 0; +} + +static int nzxt_smart2_hid_reset_resume(struct hid_device *hdev) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + + /* + * Userspace is still frozen (so no concurrent sysfs attribute access + * is possible), but raw_event can already be called concurrently. + */ + spin_lock_bh(&drvdata->wq.lock); + drvdata->fan_config_received = false; + drvdata->pwm_status_received = false; + drvdata->voltage_status_received = false; + spin_unlock_bh(&drvdata->wq.lock); + + return init_device(drvdata, drvdata->update_interval); +} + +static int nzxt_smart2_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&hdev->dev, sizeof(struct drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->hid = hdev; + hid_set_drvdata(hdev, drvdata); + + init_waitqueue_head(&drvdata->wq); + + mutex_init(&drvdata->mutex); + devm_add_action(&hdev->dev, (void (*)(void *))mutex_destroy, + &drvdata->mutex); + + ret = hid_parse(hdev); + if (ret) + return ret; + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) + return ret; + + ret = hid_hw_open(hdev); + if (ret) + goto out_hw_stop; + + hid_device_io_start(hdev); + + init_device(drvdata, UPDATE_INTERVAL_DEFAULT_MS); + + drvdata->hwmon = + hwmon_device_register_with_info(&hdev->dev, "nzxtsmart2", drvdata, + &nzxt_smart2_chip_info, NULL); + if (IS_ERR(drvdata->hwmon)) { + ret = PTR_ERR(drvdata->hwmon); + goto out_hw_close; + } + + return 0; + +out_hw_close: + hid_hw_close(hdev); + +out_hw_stop: + hid_hw_stop(hdev); + return ret; +} + +static void nzxt_smart2_hid_remove(struct hid_device *hdev) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + + hwmon_device_unregister(drvdata->hwmon); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id nzxt_smart2_hid_id_table[] = { + { HID_USB_DEVICE(0x1e71, 0x2006) }, /* NZXT Smart Device V2 */ + { HID_USB_DEVICE(0x1e71, 0x200d) }, /* NZXT Smart Device V2 */ + { HID_USB_DEVICE(0x1e71, 0x2009) }, /* NZXT RGB & Fan Controller */ + { HID_USB_DEVICE(0x1e71, 0x200e) }, /* NZXT RGB & Fan Controller */ + { HID_USB_DEVICE(0x1e71, 0x2010) }, /* NZXT RGB & Fan Controller */ + {}, +}; + +static struct hid_driver nzxt_smart2_hid_driver = { + .name = "nzxt-smart2", + .id_table = nzxt_smart2_hid_id_table, + .probe = nzxt_smart2_hid_probe, + .remove = nzxt_smart2_hid_remove, + .raw_event = nzxt_smart2_hid_raw_event, +#ifdef CONFIG_PM + .reset_resume = nzxt_smart2_hid_reset_resume, +#endif +}; + +static int __init nzxt_smart2_init(void) +{ + return hid_register_driver(&nzxt_smart2_hid_driver); +} + +static void __exit nzxt_smart2_exit(void) +{ + hid_unregister_driver(&nzxt_smart2_hid_driver); +} + +MODULE_DEVICE_TABLE(hid, nzxt_smart2_hid_id_table); +MODULE_AUTHOR("Aleksandr Mezin "); +MODULE_DESCRIPTION("Driver for NZXT RGB & Fan Controller/Smart Device V2"); +MODULE_LICENSE("GPL"); + +/* + * With module_init()/module_hid_driver() and the driver built into the kernel: + * + * Driver 'nzxt_smart2' was unable to register with bus_type 'hid' because the + * bus was not initialized. + */ +late_initcall(nzxt_smart2_init); +module_exit(nzxt_smart2_exit); -- cgit v1.2.3-59-g8ed1b From ca003af3aa1574646b784abee861626a52d345ea Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 13 Dec 2021 15:28:12 +0100 Subject: hwmon/pmbus: (ir38064) Add support for IR38060, IR38164 IR38263 The IR38060, IR38164 and IR38263 can be supported using this driver. Signed-off-by: Patrick Rudolph Signed-off-by: Arthur Heymans Link: https://lore.kernel.org/r/20211213142814.264802-2-arthur.heymans@9elements.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/trivial-devices.yaml | 6 +++++ Documentation/hwmon/ir38064.rst | 28 ++++++++++++++++++++-- drivers/hwmon/pmbus/Kconfig | 4 ++-- drivers/hwmon/pmbus/ir38064.c | 5 +++- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 1c43cc91f804..c451ae82d8d7 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -123,8 +123,14 @@ properties: - ibm,cffps2 # Infineon IR36021 digital POL buck controller - infineon,ir36021 + # Infineon IR38060 Voltage Regulator + - infineon,ir38060 # Infineon IR38064 Voltage Regulator - infineon,ir38064 + # Infineon IR38164 Voltage Regulator + - infineon,ir38164 + # Infineon IR38263 Voltage Regulator + - infineon,ir38263 # Infineon SLB9635 (Soft-) I2C TPM (old protocol, max 100khz) - infineon,slb9635tt # Infineon SLB9645 I2C TPM (new protocol, max 400khz) diff --git a/Documentation/hwmon/ir38064.rst b/Documentation/hwmon/ir38064.rst index c455d755a267..e1148f21ea2a 100644 --- a/Documentation/hwmon/ir38064.rst +++ b/Documentation/hwmon/ir38064.rst @@ -3,14 +3,38 @@ Kernel driver ir38064 Supported chips: + * Infineon IR38060 + + Prefix: 'IR38060' + Addresses scanned: - + + Datasheet: Publicly available at the Infineon website + https://www.infineon.com/dgdl/Infineon-IR38060M-DS-v03_16-EN.pdf?fileId=5546d4625c167129015c3291ea9a4cee + * Infineon IR38064 Prefix: 'ir38064' Addresses scanned: - - Datasheet: Publicly available at the Infineon webiste + Datasheet: Publicly available at the Infineon website https://www.infineon.com/dgdl/Infineon-IR38064MTRPBF-DS-v03_07-EN.pdf?fileId=5546d462584d1d4a0158db0d9efb67ca + * Infineon IR38164 + + Prefix: 'ir38164' + Addresses scanned: - + + Datasheet: Publicly available at the Infineon website + https://www.infineon.com/dgdl/Infineon-IR38164M-DS-v02_02-EN.pdf?fileId=5546d462636cc8fb01640046efea1248 + + * Infineon ir38263 + + Prefix: 'ir38263' + Addresses scanned: - + + Datasheet: Publicly available at the Infineon website + https://www.infineon.com/dgdl/Infineon-IR38263M-DataSheet-v03_05-EN.pdf?fileId=5546d4625b62cd8a015bcf81f90a6e52 + Authors: - Maxim Sloyko - Patrick Venture @@ -18,7 +42,7 @@ Authors: Description ----------- -IR38064 is a Single-input Voltage, Synchronous Buck Regulator, DC-DC Converter. +IR38x6x are a Single-input Voltage, Synchronous Buck Regulator, DC-DC Converter. Usage Notes ----------- diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 44587575c8e0..793edf072325 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -133,10 +133,10 @@ config SENSORS_IR36021 be called ir36021. config SENSORS_IR38064 - tristate "Infineon IR38064" + tristate "Infineon IR38064 and compatibles" help If you say yes here you get hardware monitoring support for Infineon - IR38064. + IR38060, IR38064, IR38164 and IR38263. This driver can also be built as a module. If so, the module will be called ir38064. diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 1fb7f1248639..4e91d3e54a4a 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -41,7 +41,10 @@ static int ir38064_probe(struct i2c_client *client) } static const struct i2c_device_id ir38064_id[] = { + {"ir38060", 0}, {"ir38064", 0}, + {"ir38164", 0}, + {"ir38263", 0}, {} }; @@ -59,6 +62,6 @@ static struct i2c_driver ir38064_driver = { module_i2c_driver(ir38064_driver); MODULE_AUTHOR("Maxim Sloyko "); -MODULE_DESCRIPTION("PMBus driver for Infineon IR38064"); +MODULE_DESCRIPTION("PMBus driver for Infineon IR38064 and comaptible chips"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(PMBUS); -- cgit v1.2.3-59-g8ed1b From e65de225ef2f7eade2888b00970eec37aeca0044 Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Mon, 13 Dec 2021 15:28:13 +0100 Subject: hwmon/pmbus: (ir38064) Add of_match_table Add the missing of_match_table to allow device tree probing. Signed-off-by: Arthur Heymans Link: https://lore.kernel.org/r/20211213142814.264802-3-arthur.heymans@9elements.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ir38064.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 4e91d3e54a4a..4211de048069 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "pmbus.h" static struct pmbus_driver_info ir38064_info = { @@ -50,10 +51,21 @@ static const struct i2c_device_id ir38064_id[] = { MODULE_DEVICE_TABLE(i2c, ir38064_id); +static const struct of_device_id ir38064_of_match[] = { + { .compatible = "infineon,ir38060" }, + { .compatible = "infineon,ir38064" }, + { .compatible = "infineon,ir38164" }, + { .compatible = "infineon,ir38263" }, + {} +}; + +MODULE_DEVICE_TABLE(of, ir38064_of_match); + /* This is the driver that will be inserted */ static struct i2c_driver ir38064_driver = { .driver = { .name = "ir38064", + .of_match_table = of_match_ptr(ir38064_of_match), }, .probe_new = ir38064_probe, .id_table = ir38064_id, -- cgit v1.2.3-59-g8ed1b From 0ee7f624263e6492b5e541c86b0fc716349da7bf Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 13 Dec 2021 15:28:14 +0100 Subject: hwmon/pmbus: (ir38064) Expose a regulator The chip series supported by this driver are voltage regulators, so expose them to the regulator subsystem. Signed-off-by: Patrick Rudolph Link: https://lore.kernel.org/r/20211213142814.264802-4-arthur.heymans@9elements.com [groeck: Added brief patch description] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 6 ++++++ drivers/hwmon/pmbus/ir38064.c | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 793edf072325..41f6cbf96d3b 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -141,6 +141,12 @@ config SENSORS_IR38064 This driver can also be built as a module. If so, the module will be called ir38064. +config SENSORS_IR38064_REGULATOR + bool "Regulator support for IR38064 and compatibles" + depends on SENSORS_IR38064 && REGULATOR + help + Uses the IR38064 or compatible as regulator. + config SENSORS_IRPS5401 tristate "Infineon IRPS5401" help diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 4211de048069..07bdbb16f216 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -17,8 +17,15 @@ #include #include #include +#include #include "pmbus.h" +#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) +static const struct regulator_desc ir38064_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif /* CONFIG_SENSORS_IR38064_REGULATOR */ + static struct pmbus_driver_info ir38064_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = linear, @@ -34,6 +41,10 @@ static struct pmbus_driver_info ir38064_info = { | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, +#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) + .num_regulators = 1, + .reg_desc = ir38064_reg_desc, +#endif }; static int ir38064_probe(struct i2c_client *client) -- cgit v1.2.3-59-g8ed1b From 23c7df14f696dd64c691be34368c835cfac5017e Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 20 Dec 2021 15:55:27 +0000 Subject: hwmon/pmbus: (ir38064) Fix spelling mistake "comaptible" -> "compatible" There is a spelling mistake in the module description, fix it. Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20211220155527.179125-1-colin.i.king@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ir38064.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 07bdbb16f216..0ea7e1c18bdc 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -85,6 +85,6 @@ static struct i2c_driver ir38064_driver = { module_i2c_driver(ir38064_driver); MODULE_AUTHOR("Maxim Sloyko "); -MODULE_DESCRIPTION("PMBus driver for Infineon IR38064 and comaptible chips"); +MODULE_DESCRIPTION("PMBus driver for Infineon IR38064 and compatible chips"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(PMBUS); -- cgit v1.2.3-59-g8ed1b From a8d6d4992ad9d92356619ac372906bd29687bb46 Mon Sep 17 00:00:00 2001 From: Arseny Demidov Date: Sun, 19 Dec 2021 13:22:39 +0300 Subject: hwmon: (mr75203) fix wrong power-up delay value In the file mr75203.c we have a macro named POWER_DELAY_CYCLE_256, the correct value should be 0x100. The register ip_tmr is expressed in units of IP clk cycles, in accordance with the datasheet. Typical power-up delays for Temperature Sensor are 256 cycles i.e. 0x100. Fixes: 9d823351a337 ("hwmon: Add hardware monitoring driver for Moortec MR75203 PVT controller") Signed-off-by: Arseny Demidov Link: https://lore.kernel.org/r/20211219102239.1112-1-a.demidov@yadro.com Signed-off-by: Guenter Roeck --- drivers/hwmon/mr75203.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/mr75203.c b/drivers/hwmon/mr75203.c index 868243dba1ee..1ba1e3145969 100644 --- a/drivers/hwmon/mr75203.c +++ b/drivers/hwmon/mr75203.c @@ -93,7 +93,7 @@ #define VM_CH_REQ BIT(21) #define IP_TMR 0x05 -#define POWER_DELAY_CYCLE_256 0x80 +#define POWER_DELAY_CYCLE_256 0x100 #define POWER_DELAY_CYCLE_64 0x40 #define PVT_POLL_DELAY_US 20 -- cgit v1.2.3-59-g8ed1b From 20f2e67cbc7599217d5a764c76e9c2bbe85e3761 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 18 Dec 2021 22:52:06 +0200 Subject: hwmon: (nct6775) Additional check for ChipID before ASUS WMI usage WMI monitoring methods can be changed or removed in new ASUS boards BIOS versions. Such versions return zero instead of a real one as Chip ID. Commit adds additional validation for the result of Chip ID call before enabling access by ASUS WMI methods. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Link: https://lore.kernel.org/r/20211218205206.615865-1-pauk.denis@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index c58538246cc8..fd3f91cb01c6 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -5038,7 +5038,7 @@ static int __init sensors_nct6775_init(void) board_name); if (err >= 0) { /* if reading chip id via WMI succeeds, use WMI */ - if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp)) { + if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) { pr_info("Using Asus WMI to access %#x chip.\n", tmp); access = access_asuswmi; } else { -- cgit v1.2.3-59-g8ed1b From 565210c7812013aac7969320ac5b86fff7a74cc6 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 21 Dec 2021 17:28:05 +0100 Subject: hwmon: (dell-smm) Pack the whole smm_regs struct When desiring the whole struct to be packed, __packed should be applied to the whole struct, not just every struct member except the first one. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20211221162805.104202-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index d8c6e75bb374..d401f9acf450 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -113,12 +113,12 @@ MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)" struct smm_regs { unsigned int eax; - unsigned int ebx __packed; - unsigned int ecx __packed; - unsigned int edx __packed; - unsigned int esi __packed; - unsigned int edi __packed; -}; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int esi; + unsigned int edi; +} __packed; static const char * const temp_labels[] = { "CPU", -- cgit v1.2.3-59-g8ed1b From f103b2e5a6197586effb0b9a1b72c30b5d65b0cd Mon Sep 17 00:00:00 2001 From: Aleksandr Mezin Date: Tue, 28 Dec 2021 07:48:13 +0600 Subject: hwmon: (nzxt-smart2) Fix "unused function" warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix warning when building with CONFIG_PM=n (and CONFIG_WERROR=y): drivers/hwmon/nzxt-smart2.c:707:12: error: ‘nzxt_smart2_hid_reset_resume’ defined but not used [-Werror=unused-function] 707 | static int nzxt_smart2_hid_reset_resume(struct hid_device *hdev) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ Signed-off-by: Aleksandr Mezin Link: https://lore.kernel.org/r/20211228014813.832491-1-mezin.alexander@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nzxt-smart2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c index 534d39b8908e..6e67da766969 100644 --- a/drivers/hwmon/nzxt-smart2.c +++ b/drivers/hwmon/nzxt-smart2.c @@ -704,7 +704,7 @@ static int nzxt_smart2_hid_raw_event(struct hid_device *hdev, return 0; } -static int nzxt_smart2_hid_reset_resume(struct hid_device *hdev) +static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev) { struct drvdata *drvdata = hid_get_drvdata(hdev); -- cgit v1.2.3-59-g8ed1b From 660d187887cf28bcd71e56008a71657811953189 Mon Sep 17 00:00:00 2001 From: Peiwei Hu Date: Tue, 28 Dec 2021 16:59:10 +0800 Subject: hwmon: (xgene-hwmon) Add free before exiting xgene_hwmon_probe Call kfifo_free(&ctx->async_msg_fifo) before error exiting instead of returning directly. Signed-off-by: Peiwei Hu Link: https://lore.kernel.org/r/tencent_C851C0324431466CBC22D60C5C6AC4A8E808@qq.com Signed-off-by: Guenter Roeck --- drivers/hwmon/xgene-hwmon.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index 30aae8642069..5cde837bfd09 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -659,8 +659,10 @@ static int xgene_hwmon_probe(struct platform_device *pdev) acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); - if (!acpi_id) - return -EINVAL; + if (!acpi_id) { + rc = -EINVAL; + goto out_mbox_free; + } version = (int)acpi_id->driver_data; -- cgit v1.2.3-59-g8ed1b From 00f5117c5f785b95b13663e52dcdcf684a47d4e3 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sun, 9 Jan 2022 19:45:58 +0000 Subject: hwmon: (nzxt-smart2) make array detect_fans_report static const Don't populate the read-only array detect_fans_report on the stack but instead it static const. Also makes the object code a little smaller. Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20220109194558.45811-1-colin.i.king@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nzxt-smart2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c index 6e67da766969..dd892ff5a3e8 100644 --- a/drivers/hwmon/nzxt-smart2.c +++ b/drivers/hwmon/nzxt-smart2.c @@ -583,7 +583,7 @@ static int set_update_interval(struct drvdata *drvdata, long val) static int init_device(struct drvdata *drvdata, long update_interval) { int ret; - u8 detect_fans_report[] = { + static const u8 detect_fans_report[] = { OUTPUT_REPORT_ID_INIT_COMMAND, INIT_COMMAND_DETECT_FANS, }; -- cgit v1.2.3-59-g8ed1b