From 7c3a3b29dea6f246aaf1d8cb369e7cbf48a49f3c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Feb 2026 15:35:00 +0100 Subject: hwmon: (bt1-pvt) Remove not-going-to-be-supported code for Baikal SoC As noticed in the discussion [1] the Baikal SoC and platforms are not going to be finalized, hence remove stale code. Link: https://lore.kernel.org/lkml/22b92ddf-6321-41b5-8073-f9c7064d3432@infradead.org/ [1] Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20260220143500.2401057-1-andriy.shevchenko@linux.intel.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/baikal,bt1-pvt.yaml | 105 -- Documentation/hwmon/bt1-pvt.rst | 117 -- Documentation/hwmon/index.rst | 1 - drivers/hwmon/Kconfig | 26 - drivers/hwmon/Makefile | 1 - drivers/hwmon/bt1-pvt.c | 1171 -------------------- drivers/hwmon/bt1-pvt.h | 247 ----- 7 files changed, 1668 deletions(-) delete mode 100644 Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml delete mode 100644 Documentation/hwmon/bt1-pvt.rst delete mode 100644 drivers/hwmon/bt1-pvt.c delete mode 100644 drivers/hwmon/bt1-pvt.h diff --git a/Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml b/Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml deleted file mode 100644 index 5d3ce641fcde..000000000000 --- a/Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml +++ /dev/null @@ -1,105 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/hwmon/baikal,bt1-pvt.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Baikal-T1 PVT Sensor - -maintainers: - - Serge Semin - -description: | - Baikal-T1 SoC provides an embedded process, voltage and temperature - sensor to monitor an internal SoC environment (chip temperature, supply - voltage and process monitor) and on time detect critical situations, - which may cause the system instability and even damages. The IP-block - is based on the Analog Bits PVT sensor, but is equipped with a dedicated - control wrapper, which provides a MMIO registers-based access to the - sensor core functionality (APB3-bus based) and exposes an additional - functions like thresholds/data ready interrupts, its status and masks, - measurements timeout. Its internal structure is depicted on the next - diagram: - - Analog Bits core Bakal-T1 PVT control block - +--------------------+ +------------------------+ - | Temperature sensor |-+ +------| Sensors control | - |--------------------| |<---En---| |------------------------| - | Voltage sensor |-|<--Mode--| +--->| Sampled data | - |--------------------| |<--Trim--+ | |------------------------| - | Low-Vt sensor |-| | +--| Thresholds comparator | - |--------------------| |---Data----| | |------------------------| - | High-Vt sensor |-| | +->| Interrupts status | - |--------------------| |--Valid--+-+ | |------------------------| - | Standard-Vt sensor |-+ +---+--| Interrupts mask | - +--------------------+ |------------------------| - ^ | Interrupts timeout | - | +------------------------+ - | ^ ^ - Rclk-----+----------------------------------------+ | - APB3-------------------------------------------------+ - - This bindings describes the external Baikal-T1 PVT control interfaces - like MMIO registers space, interrupt request number and clocks source. - These are then used by the corresponding hwmon device driver to - implement the sysfs files-based access to the sensors functionality. - -properties: - compatible: - const: baikal,bt1-pvt - - reg: - maxItems: 1 - - interrupts: - maxItems: 1 - - clocks: - items: - - description: PVT reference clock - - description: APB3 interface clock - - clock-names: - items: - - const: ref - - const: pclk - - "#thermal-sensor-cells": - description: Baikal-T1 can be referenced as the CPU thermal-sensor - const: 0 - - baikal,pvt-temp-offset-millicelsius: - description: | - Temperature sensor trimming factor. It can be used to manually adjust the - temperature measurements within 7.130 degrees Celsius. - default: 0 - minimum: 0 - maximum: 7130 - -additionalProperties: false - -required: - - compatible - - reg - - interrupts - - clocks - - clock-names - -examples: - - | - #include - - pvt@1f200000 { - compatible = "baikal,bt1-pvt"; - reg = <0x1f200000 0x1000>; - #thermal-sensor-cells = <0>; - - interrupts = ; - - baikal,pvt-temp-offset-millicelsius = <1000>; - - clocks = <&ccu_sys>, <&ccu_sys>; - clock-names = "ref", "pclk"; - }; -... diff --git a/Documentation/hwmon/bt1-pvt.rst b/Documentation/hwmon/bt1-pvt.rst deleted file mode 100644 index cbb0c0613132..000000000000 --- a/Documentation/hwmon/bt1-pvt.rst +++ /dev/null @@ -1,117 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0-only - -Kernel driver bt1-pvt -===================== - -Supported chips: - - * Baikal-T1 PVT sensor (in SoC) - - Prefix: 'bt1-pvt' - - Addresses scanned: - - - Datasheet: Provided by BAIKAL ELECTRONICS upon request and under NDA - -Authors: - Maxim Kaurkin - Serge Semin - -Description ------------ - -This driver implements support for the hardware monitoring capabilities of the -embedded into Baikal-T1 process, voltage and temperature sensors. PVT IP-core -consists of one temperature and four voltage sensors, which can be used to -monitor the chip internal environment like heating, supply voltage and -transistors performance. The driver can optionally provide the hwmon alarms -for each sensor the PVT controller supports. The alarms functionality is made -compile-time configurable due to the hardware interface implementation -peculiarity, which is connected with an ability to convert data from only one -sensor at a time. Additional limitation is that the controller performs the -thresholds checking synchronously with the data conversion procedure. Due to -these in order to have the hwmon alarms automatically detected the driver code -must switch from one sensor to another, read converted data and manually check -the threshold status bits. Depending on the measurements timeout settings -(update_interval sysfs node value) this design may cause additional burden on -the system performance. So in case if alarms are unnecessary in your system -design it's recommended to have them disabled to prevent the PVT IRQs being -periodically raised to get the data cache/alarms status up to date. By default -in alarm-less configuration the data conversion is performed by the driver -on demand when read operation is requested via corresponding _input-file. - -Temperature Monitoring ----------------------- - -Temperature is measured with 10-bit resolution and reported in millidegree -Celsius. The driver performs all the scaling by itself therefore reports true -temperatures that don't need any user-space adjustments. While the data -translation formulae isn't linear, which gives us non-linear discreteness, -it's close to one, but giving a bit better accuracy for higher temperatures. -The temperature input is mapped as follows (the last column indicates the input -ranges):: - - temp1: CPU embedded diode -48.38C - +147.438C - -In case if the alarms kernel config is enabled in the driver the temperature input -has associated min and max limits which trigger an alarm when crossed. - -Voltage Monitoring ------------------- - -The voltage inputs are also sampled with 10-bit resolution and reported in -millivolts. But in this case the data translation formulae is linear, which -provides a constant measurements discreteness. The data scaling is also -performed by the driver, so returning true millivolts. The voltage inputs are -mapped as follows (the last column indicates the input ranges):: - - in0: VDD (processor core) 0.62V - 1.168V - in1: Low-Vt (low voltage threshold) 0.62V - 1.168V - in2: High-Vt (high voltage threshold) 0.62V - 1.168V - in3: Standard-Vt (standard voltage threshold) 0.62V - 1.168V - -In case if the alarms config is enabled in the driver the voltage inputs -have associated min and max limits which trigger an alarm when crossed. - -Sysfs Attributes ----------------- - -Following is a list of all sysfs attributes that the driver provides, their -permissions and a short description: - -=============================== ======= ======================================= -Name Perm Description -=============================== ======= ======================================= -update_interval RW Measurements update interval per - sensor. -temp1_type RO Sensor type (always 1 as CPU embedded - diode). -temp1_label RO CPU Core Temperature sensor. -temp1_input RO Measured temperature in millidegree - Celsius. -temp1_min RW Low limit for temp input. -temp1_max RW High limit for temp input. -temp1_min_alarm RO Temperature input alarm. Returns 1 if - temperature input went below min limit, - 0 otherwise. -temp1_max_alarm RO Temperature input alarm. Returns 1 if - temperature input went above max limit, - 0 otherwise. -temp1_offset RW Temperature offset in millidegree - Celsius which is added to the - temperature reading by the chip. It can - be used to manually adjust the - temperature measurements within 7.130 - degrees Celsius. -in[0-3]_label RO CPU Voltage sensor (either core or - low/high/standard thresholds). -in[0-3]_input RO Measured voltage in millivolts. -in[0-3]_min RW Low limit for voltage input. -in[0-3]_max RW High limit for voltage input. -in[0-3]_min_alarm RO Voltage input alarm. Returns 1 if - voltage input went below min limit, - 0 otherwise. -in[0-3]_max_alarm RO Voltage input alarm. Returns 1 if - voltage input went above max limit, - 0 otherwise. -=============================== ======= ======================================= diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b2ca8513cfcd..c7ba1cc86882 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -52,7 +52,6 @@ Hardware Monitoring Kernel Drivers bcm54140 bel-pfe bpa-rs600 - bt1-pvt cgbc-hwmon chipcap2 coretemp diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 328867242cb3..27088850a063 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -457,32 +457,6 @@ config SENSORS_ATXP1 This driver can also be built as a module. If so, the module will be called atxp1. -config SENSORS_BT1_PVT - tristate "Baikal-T1 Process, Voltage, Temperature sensor driver" - depends on MIPS_BAIKAL_T1 || COMPILE_TEST - select POLYNOMIAL - help - If you say yes here you get support for Baikal-T1 PVT sensor - embedded into the SoC. - - This driver can also be built as a module. If so, the module will be - called bt1-pvt. - -config SENSORS_BT1_PVT_ALARMS - bool "Enable Baikal-T1 PVT sensor alarms" - depends on SENSORS_BT1_PVT - help - Baikal-T1 PVT IP-block provides threshold registers for each - supported sensor. But the corresponding interrupts might be - generated by the thresholds comparator only in synchronization with - a data conversion. Additionally there is only one sensor data can - be converted at a time. All of these makes the interface impossible - to be used for the hwmon alarms implementation without periodic - switch between the PVT sensors. By default the data conversion is - performed on demand from the user-space. If this config is enabled - the data conversion will be periodically performed and the data will be - saved in the internal driver cache. - config SENSORS_CGBC tristate "Congatec Board Controller Sensors" depends on MFD_CGBC diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5833c807c688..c1020dbbc1ac 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -58,7 +58,6 @@ obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o -obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o obj-$(CONFIG_SENSORS_CGBC) += cgbc-hwmon.o obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c deleted file mode 100644 index b77ebac2e0ce..000000000000 --- a/drivers/hwmon/bt1-pvt.c +++ /dev/null @@ -1,1171 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Maxim Kaurkin - * Serge Semin - * - * Baikal-T1 Process, Voltage, Temperature sensor driver - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "bt1-pvt.h" - -/* - * For the sake of the code simplification we created the sensors info table - * with the sensor names, activation modes, threshold registers base address - * and the thresholds bit fields. - */ -static const struct pvt_sensor_info pvt_info[] = { - PVT_SENSOR_INFO(0, "CPU Core Temperature", hwmon_temp, TEMP, TTHRES), - PVT_SENSOR_INFO(0, "CPU Core Voltage", hwmon_in, VOLT, VTHRES), - PVT_SENSOR_INFO(1, "CPU Core Low-Vt", hwmon_in, LVT, LTHRES), - PVT_SENSOR_INFO(2, "CPU Core High-Vt", hwmon_in, HVT, HTHRES), - PVT_SENSOR_INFO(3, "CPU Core Standard-Vt", hwmon_in, SVT, STHRES), -}; - -/* - * The original translation formulae of the temperature (in degrees of Celsius) - * to PVT data and vice-versa are following: - * N = 1.8322e-8*(T^4) + 2.343e-5*(T^3) + 8.7018e-3*(T^2) + 3.9269*(T^1) + - * 1.7204e2, - * T = -1.6743e-11*(N^4) + 8.1542e-8*(N^3) + -1.8201e-4*(N^2) + - * 3.1020e-1*(N^1) - 4.838e1, - * where T = [-48.380, 147.438]C and N = [0, 1023]. - * They must be accordingly altered to be suitable for the integer arithmetics. - * The technique is called 'factor redistribution', which just makes sure the - * multiplications and divisions are made so to have a result of the operations - * within the integer numbers limit. In addition we need to translate the - * formulae to accept millidegrees of Celsius. Here what they look like after - * the alterations: - * N = (18322e-20*(T^4) + 2343e-13*(T^3) + 87018e-9*(T^2) + 39269e-3*T + - * 17204e2) / 1e4, - * T = -16743e-12*(D^4) + 81542e-9*(D^3) - 182010e-6*(D^2) + 310200e-3*D - - * 48380, - * where T = [-48380, 147438] mC and N = [0, 1023]. - */ -static const struct polynomial __maybe_unused poly_temp_to_N = { - .total_divider = 10000, - .terms = { - {4, 18322, 10000, 10000}, - {3, 2343, 10000, 10}, - {2, 87018, 10000, 10}, - {1, 39269, 1000, 1}, - {0, 1720400, 1, 1} - } -}; - -static const struct polynomial poly_N_to_temp = { - .total_divider = 1, - .terms = { - {4, -16743, 1000, 1}, - {3, 81542, 1000, 1}, - {2, -182010, 1000, 1}, - {1, 310200, 1000, 1}, - {0, -48380, 1, 1} - } -}; - -/* - * Similar alterations are performed for the voltage conversion equations. - * The original formulae are: - * N = 1.8658e3*V - 1.1572e3, - * V = (N + 1.1572e3) / 1.8658e3, - * where V = [0.620, 1.168] V and N = [0, 1023]. - * After the optimization they looks as follows: - * N = (18658e-3*V - 11572) / 10, - * V = N * 10^5 / 18658 + 11572 * 10^4 / 18658. - */ -static const struct polynomial __maybe_unused poly_volt_to_N = { - .total_divider = 10, - .terms = { - {1, 18658, 1000, 1}, - {0, -11572, 1, 1} - } -}; - -static const struct polynomial poly_N_to_volt = { - .total_divider = 10, - .terms = { - {1, 100000, 18658, 1}, - {0, 115720000, 1, 18658} - } -}; - -static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data) -{ - u32 old; - - old = readl_relaxed(reg); - writel((old & ~mask) | (data & mask), reg); - - return old & mask; -} - -/* - * Baikal-T1 PVT mode can be updated only when the controller is disabled. - * So first we disable it, then set the new mode together with the controller - * getting back enabled. The same concerns the temperature trim and - * measurements timeout. If it is necessary the interface mutex is supposed - * to be locked at the time the operations are performed. - */ -static inline void pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) -{ - u32 old; - - mode = FIELD_PREP(PVT_CTRL_MODE_MASK, mode); - - old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN, - mode | old); -} - -static inline u32 pvt_calc_trim(long temp) -{ - temp = clamp_val(temp, 0, PVT_TRIM_TEMP); - - return DIV_ROUND_UP(temp, PVT_TRIM_STEP); -} - -static inline void pvt_set_trim(struct pvt_hwmon *pvt, u32 trim) -{ - u32 old; - - trim = FIELD_PREP(PVT_CTRL_TRIM_MASK, trim); - - old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN, - trim | old); -} - -static inline void pvt_set_tout(struct pvt_hwmon *pvt, u32 tout) -{ - u32 old; - - old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - writel(tout, pvt->regs + PVT_TTIMEOUT); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, old); -} - -/* - * This driver can optionally provide the hwmon alarms for each sensor the PVT - * controller supports. The alarms functionality is made compile-time - * configurable due to the hardware interface implementation peculiarity - * described further in this comment. So in case if alarms are unnecessary in - * your system design it's recommended to have them disabled to prevent the PVT - * IRQs being periodically raised to get the data cache/alarms status up to - * date. - * - * Baikal-T1 PVT embedded controller is based on the Analog Bits PVT sensor, - * but is equipped with a dedicated control wrapper. It exposes the PVT - * sub-block registers space via the APB3 bus. In addition the wrapper provides - * a common interrupt vector of the sensors conversion completion events and - * threshold value alarms. Alas the wrapper interface hasn't been fully thought - * through. There is only one sensor can be activated at a time, for which the - * thresholds comparator is enabled right after the data conversion is - * completed. Due to this if alarms need to be implemented for all available - * sensors we can't just set the thresholds and enable the interrupts. We need - * to enable the sensors one after another and let the controller to detect - * the alarms by itself at each conversion. This also makes pointless to handle - * the alarms interrupts, since in occasion they happen synchronously with - * data conversion completion. The best driver design would be to have the - * completion interrupts enabled only and keep the converted value in the - * driver data cache. This solution is implemented if hwmon alarms are enabled - * in this driver. In case if the alarms are disabled, the conversion is - * performed on demand at the time a sensors input file is read. - */ - -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - -#define pvt_hard_isr NULL - -static irqreturn_t pvt_soft_isr(int irq, void *data) -{ - const struct pvt_sensor_info *info; - struct pvt_hwmon *pvt = data; - struct pvt_cache *cache; - u32 val, thres_sts, old; - - /* - * DVALID bit will be cleared by reading the data. We need to save the - * status before the next conversion happens. Threshold events will be - * handled a bit later. - */ - thres_sts = readl(pvt->regs + PVT_RAW_INTR_STAT); - - /* - * Then lets recharge the PVT interface with the next sampling mode. - * Lock the interface mutex to serialize trim, timeouts and alarm - * thresholds settings. - */ - cache = &pvt->cache[pvt->sensor]; - info = &pvt_info[pvt->sensor]; - pvt->sensor = (pvt->sensor == PVT_SENSOR_LAST) ? - PVT_SENSOR_FIRST : (pvt->sensor + 1); - - /* - * For some reason we have to mask the interrupt before changing the - * mode, otherwise sometimes the temperature mode doesn't get - * activated even though the actual mode in the ctrl register - * corresponds to one. Then we read the data. By doing so we also - * recharge the data conversion. After this the mode corresponding - * to the next sensor in the row is set. Finally we enable the - * interrupts back. - */ - mutex_lock(&pvt->iface_mtx); - - old = pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - - val = readl(pvt->regs + PVT_DATA); - - pvt_set_mode(pvt, pvt_info[pvt->sensor].mode); - - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, old); - - mutex_unlock(&pvt->iface_mtx); - - /* - * We can now update the data cache with data just retrieved from the - * sensor. Lock write-seqlock to make sure the reader has a coherent - * data. - */ - write_seqlock(&cache->data_seqlock); - - cache->data = FIELD_GET(PVT_DATA_DATA_MASK, val); - - write_sequnlock(&cache->data_seqlock); - - /* - * While PVT core is doing the next mode data conversion, we'll check - * whether the alarms were triggered for the current sensor. Note that - * according to the documentation only one threshold IRQ status can be - * set at a time, that's why if-else statement is utilized. - */ - if ((thres_sts & info->thres_sts_lo) ^ cache->thres_sts_lo) { - WRITE_ONCE(cache->thres_sts_lo, thres_sts & info->thres_sts_lo); - hwmon_notify_event(pvt->hwmon, info->type, info->attr_min_alarm, - info->channel); - } else if ((thres_sts & info->thres_sts_hi) ^ cache->thres_sts_hi) { - WRITE_ONCE(cache->thres_sts_hi, thres_sts & info->thres_sts_hi); - hwmon_notify_event(pvt->hwmon, info->type, info->attr_max_alarm, - info->channel); - } - - return IRQ_HANDLED; -} - -static inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type) -{ - return 0644; -} - -static inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type) -{ - return 0444; -} - -static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - long *val) -{ - struct pvt_cache *cache = &pvt->cache[type]; - unsigned int seq; - u32 data; - - do { - seq = read_seqbegin(&cache->data_seqlock); - data = cache->data; - } while (read_seqretry(&cache->data_seqlock, seq)); - - if (type == PVT_TEMP) - *val = polynomial_calc(&poly_N_to_temp, data); - else - *val = polynomial_calc(&poly_N_to_volt, data); - - return 0; -} - -static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - u32 data; - - /* No need in serialization, since it is just read from MMIO. */ - data = readl(pvt->regs + pvt_info[type].thres_base); - - if (is_low) - data = FIELD_GET(PVT_THRES_LO_MASK, data); - else - data = FIELD_GET(PVT_THRES_HI_MASK, data); - - if (type == PVT_TEMP) - *val = polynomial_calc(&poly_N_to_temp, data); - else - *val = polynomial_calc(&poly_N_to_volt, data); - - return 0; -} - -static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long val) -{ - u32 data, limit, mask; - int ret; - - if (type == PVT_TEMP) { - val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX); - data = polynomial_calc(&poly_temp_to_N, val); - } else { - val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX); - data = polynomial_calc(&poly_volt_to_N, val); - } - - /* Serialize limit update, since a part of the register is changed. */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - /* Make sure the upper and lower ranges don't intersect. */ - limit = readl(pvt->regs + pvt_info[type].thres_base); - if (is_low) { - limit = FIELD_GET(PVT_THRES_HI_MASK, limit); - data = clamp_val(data, PVT_DATA_MIN, limit); - data = FIELD_PREP(PVT_THRES_LO_MASK, data); - mask = PVT_THRES_LO_MASK; - } else { - limit = FIELD_GET(PVT_THRES_LO_MASK, limit); - data = clamp_val(data, limit, PVT_DATA_MAX); - data = FIELD_PREP(PVT_THRES_HI_MASK, data); - mask = PVT_THRES_HI_MASK; - } - - pvt_update(pvt->regs + pvt_info[type].thres_base, mask, data); - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - if (is_low) - *val = !!READ_ONCE(pvt->cache[type].thres_sts_lo); - else - *val = !!READ_ONCE(pvt->cache[type].thres_sts_hi); - - return 0; -} - -static const struct hwmon_channel_info * const pvt_channel_info[] = { - HWMON_CHANNEL_INFO(chip, - HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), - HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL | - HWMON_T_MIN | HWMON_T_MIN_ALARM | - HWMON_T_MAX | HWMON_T_MAX_ALARM | - HWMON_T_OFFSET), - HWMON_CHANNEL_INFO(in, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM), - NULL -}; - -#else /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static irqreturn_t pvt_hard_isr(int irq, void *data) -{ - struct pvt_hwmon *pvt = data; - struct pvt_cache *cache; - u32 val; - - /* - * Mask the DVALID interrupt so after exiting from the handler a - * repeated conversion wouldn't happen. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - - /* - * Nothing special for alarm-less driver. Just read the data, update - * the cache and notify a waiter of this event. - */ - val = readl(pvt->regs + PVT_DATA); - if (!(val & PVT_DATA_VALID)) { - dev_err(pvt->dev, "Got IRQ when data isn't valid\n"); - return IRQ_HANDLED; - } - - cache = &pvt->cache[pvt->sensor]; - - WRITE_ONCE(cache->data, FIELD_GET(PVT_DATA_DATA_MASK, val)); - - complete(&cache->conversion); - - return IRQ_HANDLED; -} - -#define pvt_soft_isr NULL - -static inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type) -{ - return 0; -} - -static inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type) -{ - return 0; -} - -static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - long *val) -{ - struct pvt_cache *cache = &pvt->cache[type]; - unsigned long timeout; - u32 data; - int ret; - - /* - * Lock PVT conversion interface until data cache is updated. The - * data read procedure is following: set the requested PVT sensor - * mode, enable IRQ and conversion, wait until conversion is finished, - * then disable conversion and IRQ, and read the cached data. - */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - pvt->sensor = type; - pvt_set_mode(pvt, pvt_info[type].mode); - - /* - * Unmask the DVALID interrupt and enable the sensors conversions. - * Do the reverse procedure when conversion is done. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN); - - /* - * Wait with timeout since in case if the sensor is suddenly powered - * down the request won't be completed and the caller will hang up on - * this procedure until the power is back up again. Multiply the - * timeout by the factor of two to prevent a false timeout. - */ - timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); - ret = wait_for_completion_timeout(&cache->conversion, timeout); - - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - - data = READ_ONCE(cache->data); - - mutex_unlock(&pvt->iface_mtx); - - if (!ret) - return -ETIMEDOUT; - - if (type == PVT_TEMP) - *val = polynomial_calc(&poly_N_to_temp, data); - else - *val = polynomial_calc(&poly_N_to_volt, data); - - return 0; -} - -static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - return -EOPNOTSUPP; -} - -static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long val) -{ - return -EOPNOTSUPP; -} - -static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - return -EOPNOTSUPP; -} - -static const struct hwmon_channel_info * const pvt_channel_info[] = { - HWMON_CHANNEL_INFO(chip, - HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), - HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL | - HWMON_T_OFFSET), - HWMON_CHANNEL_INFO(in, - HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL), - NULL -}; - -#endif /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static inline bool pvt_hwmon_channel_is_valid(enum hwmon_sensor_types type, - int ch) -{ - switch (type) { - case hwmon_temp: - if (ch < 0 || ch >= PVT_TEMP_CHS) - return false; - break; - case hwmon_in: - if (ch < 0 || ch >= PVT_VOLT_CHS) - return false; - break; - default: - break; - } - - /* The rest of the types are independent from the channel number. */ - return true; -} - -static umode_t pvt_hwmon_is_visible(const void *data, - enum hwmon_sensor_types type, - u32 attr, int ch) -{ - if (!pvt_hwmon_channel_is_valid(type, ch)) - return 0; - - switch (type) { - case hwmon_chip: - switch (attr) { - case hwmon_chip_update_interval: - return 0644; - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_input: - case hwmon_temp_type: - case hwmon_temp_label: - return 0444; - case hwmon_temp_min: - case hwmon_temp_max: - return pvt_limit_is_visible(ch); - case hwmon_temp_min_alarm: - case hwmon_temp_max_alarm: - return pvt_alarm_is_visible(ch); - case hwmon_temp_offset: - return 0644; - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_input: - case hwmon_in_label: - return 0444; - case hwmon_in_min: - case hwmon_in_max: - return pvt_limit_is_visible(PVT_VOLT + ch); - case hwmon_in_min_alarm: - case hwmon_in_max_alarm: - return pvt_alarm_is_visible(PVT_VOLT + ch); - } - break; - default: - break; - } - - return 0; -} - -static int pvt_read_trim(struct pvt_hwmon *pvt, long *val) -{ - u32 data; - - data = readl(pvt->regs + PVT_CTRL); - *val = FIELD_GET(PVT_CTRL_TRIM_MASK, data) * PVT_TRIM_STEP; - - return 0; -} - -static int pvt_write_trim(struct pvt_hwmon *pvt, long val) -{ - u32 trim; - int ret; - - /* - * Serialize trim update, since a part of the register is changed and - * the controller is supposed to be disabled during this operation. - */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - trim = pvt_calc_trim(val); - pvt_set_trim(pvt, trim); - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_read_timeout(struct pvt_hwmon *pvt, long *val) -{ - int ret; - - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - /* Return the result in msec as hwmon sysfs interface requires. */ - *val = ktime_to_ms(pvt->timeout); - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_write_timeout(struct pvt_hwmon *pvt, long val) -{ - unsigned long rate; - ktime_t kt, cache; - u32 data; - int ret; - - rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk); - if (!rate) - return -ENODEV; - - /* - * If alarms are enabled, the requested timeout must be divided - * between all available sensors to have the requested delay - * applicable to each individual sensor. - */ - cache = kt = ms_to_ktime(val); -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - kt = ktime_divns(kt, PVT_SENSORS_NUM); -#endif - - /* - * Subtract a constant lag, which always persists due to the limited - * PVT sampling rate. Make sure the timeout is not negative. - */ - kt = ktime_sub_ns(kt, PVT_TOUT_MIN); - if (ktime_to_ns(kt) < 0) - kt = ktime_set(0, 0); - - /* - * Finally recalculate the timeout in terms of the reference clock - * period. - */ - data = ktime_divns(kt * rate, NSEC_PER_SEC); - - /* - * Update the measurements delay, but lock the interface first, since - * we have to disable PVT in order to have the new delay actually - * updated. - */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - pvt_set_tout(pvt, data); - pvt->timeout = cache; - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_hwmon_read(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int ch, long *val) -{ - struct pvt_hwmon *pvt = dev_get_drvdata(dev); - - if (!pvt_hwmon_channel_is_valid(type, ch)) - return -EINVAL; - - switch (type) { - case hwmon_chip: - switch (attr) { - case hwmon_chip_update_interval: - return pvt_read_timeout(pvt, val); - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_input: - return pvt_read_data(pvt, ch, val); - case hwmon_temp_type: - *val = 1; - return 0; - case hwmon_temp_min: - return pvt_read_limit(pvt, ch, true, val); - case hwmon_temp_max: - return pvt_read_limit(pvt, ch, false, val); - case hwmon_temp_min_alarm: - return pvt_read_alarm(pvt, ch, true, val); - case hwmon_temp_max_alarm: - return pvt_read_alarm(pvt, ch, false, val); - case hwmon_temp_offset: - return pvt_read_trim(pvt, val); - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_input: - return pvt_read_data(pvt, PVT_VOLT + ch, val); - case hwmon_in_min: - return pvt_read_limit(pvt, PVT_VOLT + ch, true, val); - case hwmon_in_max: - return pvt_read_limit(pvt, PVT_VOLT + ch, false, val); - case hwmon_in_min_alarm: - return pvt_read_alarm(pvt, PVT_VOLT + ch, true, val); - case hwmon_in_max_alarm: - return pvt_read_alarm(pvt, PVT_VOLT + ch, false, val); - } - break; - default: - break; - } - - return -EOPNOTSUPP; -} - -static int pvt_hwmon_read_string(struct device *dev, - enum hwmon_sensor_types type, - u32 attr, int ch, const char **str) -{ - if (!pvt_hwmon_channel_is_valid(type, ch)) - return -EINVAL; - - switch (type) { - case hwmon_temp: - switch (attr) { - case hwmon_temp_label: - *str = pvt_info[ch].label; - return 0; - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_label: - *str = pvt_info[PVT_VOLT + ch].label; - return 0; - } - break; - default: - break; - } - - return -EOPNOTSUPP; -} - -static int pvt_hwmon_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int ch, long val) -{ - struct pvt_hwmon *pvt = dev_get_drvdata(dev); - - if (!pvt_hwmon_channel_is_valid(type, ch)) - return -EINVAL; - - switch (type) { - case hwmon_chip: - switch (attr) { - case hwmon_chip_update_interval: - return pvt_write_timeout(pvt, val); - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_min: - return pvt_write_limit(pvt, ch, true, val); - case hwmon_temp_max: - return pvt_write_limit(pvt, ch, false, val); - case hwmon_temp_offset: - return pvt_write_trim(pvt, val); - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_min: - return pvt_write_limit(pvt, PVT_VOLT + ch, true, val); - case hwmon_in_max: - return pvt_write_limit(pvt, PVT_VOLT + ch, false, val); - } - break; - default: - break; - } - - return -EOPNOTSUPP; -} - -static const struct hwmon_ops pvt_hwmon_ops = { - .is_visible = pvt_hwmon_is_visible, - .read = pvt_hwmon_read, - .read_string = pvt_hwmon_read_string, - .write = pvt_hwmon_write -}; - -static const struct hwmon_chip_info pvt_hwmon_info = { - .ops = &pvt_hwmon_ops, - .info = pvt_channel_info -}; - -static void pvt_clear_data(void *data) -{ - struct pvt_hwmon *pvt = data; -#if !defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - int idx; - - for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) - complete_all(&pvt->cache[idx].conversion); -#endif - - mutex_destroy(&pvt->iface_mtx); -} - -static struct pvt_hwmon *pvt_create_data(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct pvt_hwmon *pvt; - int ret, idx; - - pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); - if (!pvt) - return ERR_PTR(-ENOMEM); - - ret = devm_add_action(dev, pvt_clear_data, pvt); - if (ret) { - dev_err(dev, "Can't add PVT data clear action\n"); - return ERR_PTR(ret); - } - - pvt->dev = dev; - pvt->sensor = PVT_SENSOR_FIRST; - mutex_init(&pvt->iface_mtx); - -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) - seqlock_init(&pvt->cache[idx].data_seqlock); -#else - for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) - init_completion(&pvt->cache[idx].conversion); -#endif - - return pvt; -} - -static int pvt_request_regs(struct pvt_hwmon *pvt) -{ - struct platform_device *pdev = to_platform_device(pvt->dev); - - pvt->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(pvt->regs)) - return PTR_ERR(pvt->regs); - - return 0; -} - -static void pvt_disable_clks(void *data) -{ - struct pvt_hwmon *pvt = data; - - clk_bulk_disable_unprepare(PVT_CLOCK_NUM, pvt->clks); -} - -static int pvt_request_clks(struct pvt_hwmon *pvt) -{ - int ret; - - pvt->clks[PVT_CLOCK_APB].id = "pclk"; - pvt->clks[PVT_CLOCK_REF].id = "ref"; - - ret = devm_clk_bulk_get(pvt->dev, PVT_CLOCK_NUM, pvt->clks); - if (ret) { - dev_err(pvt->dev, "Couldn't get PVT clocks descriptors\n"); - return ret; - } - - ret = clk_bulk_prepare_enable(PVT_CLOCK_NUM, pvt->clks); - if (ret) { - dev_err(pvt->dev, "Couldn't enable the PVT clocks\n"); - return ret; - } - - ret = devm_add_action_or_reset(pvt->dev, pvt_disable_clks, pvt); - if (ret) { - dev_err(pvt->dev, "Can't add PVT clocks disable action\n"); - return ret; - } - - return 0; -} - -static int pvt_check_pwr(struct pvt_hwmon *pvt) -{ - unsigned long tout; - int ret = 0; - u32 data; - - /* - * Test out the sensor conversion functionality. If it is not done on - * time then the domain must have been unpowered and we won't be able - * to use the device later in this driver. - * Note If the power source is lost during the normal driver work the - * data read procedure will either return -ETIMEDOUT (for the - * alarm-less driver configuration) or just stop the repeated - * conversion. In the later case alas we won't be able to detect the - * problem. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN); - pvt_set_tout(pvt, 0); - readl(pvt->regs + PVT_DATA); - - tout = PVT_TOUT_MIN / NSEC_PER_USEC; - usleep_range(tout, 2 * tout); - - data = readl(pvt->regs + PVT_DATA); - if (!(data & PVT_DATA_VALID)) { - ret = -ENODEV; - dev_err(pvt->dev, "Sensor is powered down\n"); - } - - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - - return ret; -} - -static int pvt_init_iface(struct pvt_hwmon *pvt) -{ - unsigned long rate; - u32 trim, temp; - - rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk); - if (!rate) { - dev_err(pvt->dev, "Invalid reference clock rate\n"); - return -ENODEV; - } - - /* - * Make sure all interrupts and controller are disabled so not to - * accidentally have ISR executed before the driver data is fully - * initialized. Clear the IRQ status as well. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - readl(pvt->regs + PVT_CLR_INTR); - readl(pvt->regs + PVT_DATA); - - /* Setup default sensor mode, timeout and temperature trim. */ - pvt_set_mode(pvt, pvt_info[pvt->sensor].mode); - pvt_set_tout(pvt, PVT_TOUT_DEF); - - /* - * Preserve the current ref-clock based delay (Ttotal) between the - * sensors data samples in the driver data so not to recalculate it - * each time on the data requests and timeout reads. It consists of the - * delay introduced by the internal ref-clock timer (N / Fclk) and the - * constant timeout caused by each conversion latency (Tmin): - * Ttotal = N / Fclk + Tmin - * If alarms are enabled the sensors are polled one after another and - * in order to get the next measurement of a particular sensor the - * caller will have to wait for at most until all the others are - * polled. In that case the formulae will look a bit different: - * Ttotal = 5 * (N / Fclk + Tmin) - */ -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - pvt->timeout = ktime_set(PVT_SENSORS_NUM * PVT_TOUT_DEF, 0); - pvt->timeout = ktime_divns(pvt->timeout, rate); - pvt->timeout = ktime_add_ns(pvt->timeout, PVT_SENSORS_NUM * PVT_TOUT_MIN); -#else - pvt->timeout = ktime_set(PVT_TOUT_DEF, 0); - pvt->timeout = ktime_divns(pvt->timeout, rate); - pvt->timeout = ktime_add_ns(pvt->timeout, PVT_TOUT_MIN); -#endif - - trim = PVT_TRIM_DEF; - if (!of_property_read_u32(pvt->dev->of_node, - "baikal,pvt-temp-offset-millicelsius", &temp)) - trim = pvt_calc_trim(temp); - - pvt_set_trim(pvt, trim); - - return 0; -} - -static int pvt_request_irq(struct pvt_hwmon *pvt) -{ - struct platform_device *pdev = to_platform_device(pvt->dev); - int ret; - - pvt->irq = platform_get_irq(pdev, 0); - if (pvt->irq < 0) - return pvt->irq; - - ret = devm_request_threaded_irq(pvt->dev, pvt->irq, - pvt_hard_isr, pvt_soft_isr, -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - IRQF_SHARED | IRQF_TRIGGER_HIGH | - IRQF_ONESHOT, -#else - IRQF_SHARED | IRQF_TRIGGER_HIGH, -#endif - "pvt", pvt); - if (ret) { - dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); - return ret; - } - - return 0; -} - -static int pvt_create_hwmon(struct pvt_hwmon *pvt) -{ - pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", pvt, - &pvt_hwmon_info, NULL); - if (IS_ERR(pvt->hwmon)) { - dev_err(pvt->dev, "Couldn't create hwmon device\n"); - return PTR_ERR(pvt->hwmon); - } - - return 0; -} - -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - -static void pvt_disable_iface(void *data) -{ - struct pvt_hwmon *pvt = data; - - mutex_lock(&pvt->iface_mtx); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - mutex_unlock(&pvt->iface_mtx); -} - -static int pvt_enable_iface(struct pvt_hwmon *pvt) -{ - int ret; - - ret = devm_add_action(pvt->dev, pvt_disable_iface, pvt); - if (ret) { - dev_err(pvt->dev, "Can't add PVT disable interface action\n"); - return ret; - } - - /* - * Enable sensors data conversion and IRQ. We need to lock the - * interface mutex since hwmon has just been created and the - * corresponding sysfs files are accessible from user-space, - * which theoretically may cause races. - */ - mutex_lock(&pvt->iface_mtx); - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN); - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -#else /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static int pvt_enable_iface(struct pvt_hwmon *pvt) -{ - return 0; -} - -#endif /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static int pvt_probe(struct platform_device *pdev) -{ - struct pvt_hwmon *pvt; - int ret; - - pvt = pvt_create_data(pdev); - if (IS_ERR(pvt)) - return PTR_ERR(pvt); - - ret = pvt_request_regs(pvt); - if (ret) - return ret; - - ret = pvt_request_clks(pvt); - if (ret) - return ret; - - ret = pvt_check_pwr(pvt); - if (ret) - return ret; - - ret = pvt_init_iface(pvt); - if (ret) - return ret; - - ret = pvt_request_irq(pvt); - if (ret) - return ret; - - ret = pvt_create_hwmon(pvt); - if (ret) - return ret; - - ret = pvt_enable_iface(pvt); - if (ret) - return ret; - - return 0; -} - -static const struct of_device_id pvt_of_match[] = { - { .compatible = "baikal,bt1-pvt" }, - { } -}; -MODULE_DEVICE_TABLE(of, pvt_of_match); - -static struct platform_driver pvt_driver = { - .probe = pvt_probe, - .driver = { - .name = "bt1-pvt", - .of_match_table = pvt_of_match - } -}; -module_platform_driver(pvt_driver); - -MODULE_AUTHOR("Maxim Kaurkin "); -MODULE_DESCRIPTION("Baikal-T1 PVT driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/bt1-pvt.h b/drivers/hwmon/bt1-pvt.h deleted file mode 100644 index 93b8dd5e7c94..000000000000 --- a/drivers/hwmon/bt1-pvt.h +++ /dev/null @@ -1,247 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Baikal-T1 Process, Voltage, Temperature sensor driver - */ -#ifndef __HWMON_BT1_PVT_H__ -#define __HWMON_BT1_PVT_H__ - -#include -#include -#include -#include -#include -#include - -/* Baikal-T1 PVT registers and their bitfields */ -#define PVT_CTRL 0x00 -#define PVT_CTRL_EN BIT(0) -#define PVT_CTRL_MODE_FLD 1 -#define PVT_CTRL_MODE_MASK GENMASK(3, PVT_CTRL_MODE_FLD) -#define PVT_CTRL_MODE_TEMP 0x0 -#define PVT_CTRL_MODE_VOLT 0x1 -#define PVT_CTRL_MODE_LVT 0x2 -#define PVT_CTRL_MODE_HVT 0x4 -#define PVT_CTRL_MODE_SVT 0x6 -#define PVT_CTRL_TRIM_FLD 4 -#define PVT_CTRL_TRIM_MASK GENMASK(8, PVT_CTRL_TRIM_FLD) -#define PVT_DATA 0x04 -#define PVT_DATA_VALID BIT(10) -#define PVT_DATA_DATA_FLD 0 -#define PVT_DATA_DATA_MASK GENMASK(9, PVT_DATA_DATA_FLD) -#define PVT_TTHRES 0x08 -#define PVT_VTHRES 0x0C -#define PVT_LTHRES 0x10 -#define PVT_HTHRES 0x14 -#define PVT_STHRES 0x18 -#define PVT_THRES_LO_FLD 0 -#define PVT_THRES_LO_MASK GENMASK(9, PVT_THRES_LO_FLD) -#define PVT_THRES_HI_FLD 10 -#define PVT_THRES_HI_MASK GENMASK(19, PVT_THRES_HI_FLD) -#define PVT_TTIMEOUT 0x1C -#define PVT_INTR_STAT 0x20 -#define PVT_INTR_MASK 0x24 -#define PVT_RAW_INTR_STAT 0x28 -#define PVT_INTR_DVALID BIT(0) -#define PVT_INTR_TTHRES_LO BIT(1) -#define PVT_INTR_TTHRES_HI BIT(2) -#define PVT_INTR_VTHRES_LO BIT(3) -#define PVT_INTR_VTHRES_HI BIT(4) -#define PVT_INTR_LTHRES_LO BIT(5) -#define PVT_INTR_LTHRES_HI BIT(6) -#define PVT_INTR_HTHRES_LO BIT(7) -#define PVT_INTR_HTHRES_HI BIT(8) -#define PVT_INTR_STHRES_LO BIT(9) -#define PVT_INTR_STHRES_HI BIT(10) -#define PVT_INTR_ALL GENMASK(10, 0) -#define PVT_CLR_INTR 0x2C - -/* - * PVT sensors-related limits and default values - * @PVT_TEMP_MIN: Minimal temperature in millidegrees of Celsius. - * @PVT_TEMP_MAX: Maximal temperature in millidegrees of Celsius. - * @PVT_TEMP_CHS: Number of temperature hwmon channels. - * @PVT_VOLT_MIN: Minimal voltage in mV. - * @PVT_VOLT_MAX: Maximal voltage in mV. - * @PVT_VOLT_CHS: Number of voltage hwmon channels. - * @PVT_DATA_MIN: Minimal PVT raw data value. - * @PVT_DATA_MAX: Maximal PVT raw data value. - * @PVT_TRIM_MIN: Minimal temperature sensor trim value. - * @PVT_TRIM_MAX: Maximal temperature sensor trim value. - * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value - * when one is determined for Baikal-T1 SoC). - * @PVT_TRIM_TEMP: Maximum temperature encoded by the trim factor. - * @PVT_TRIM_STEP: Temperature stride corresponding to the trim value. - * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. - * @PVT_TOUT_DEF: Default data measurements timeout. In case if alarms are - * activated the PVT IRQ is enabled to be raised after each - * conversion in order to have the thresholds checked and the - * converted value cached. Too frequent conversions may cause - * the system CPU overload. Lets set the 50ms delay between - * them by default to prevent this. - */ -#define PVT_TEMP_MIN -48380L -#define PVT_TEMP_MAX 147438L -#define PVT_TEMP_CHS 1 -#define PVT_VOLT_MIN 620L -#define PVT_VOLT_MAX 1168L -#define PVT_VOLT_CHS 4 -#define PVT_DATA_MIN 0 -#define PVT_DATA_MAX (PVT_DATA_DATA_MASK >> PVT_DATA_DATA_FLD) -#define PVT_TRIM_MIN 0 -#define PVT_TRIM_MAX (PVT_CTRL_TRIM_MASK >> PVT_CTRL_TRIM_FLD) -#define PVT_TRIM_TEMP 7130 -#define PVT_TRIM_STEP (PVT_TRIM_TEMP / PVT_TRIM_MAX) -#define PVT_TRIM_DEF 0 -#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) -# define PVT_TOUT_DEF 60000 -#else -# define PVT_TOUT_DEF 0 -#endif - -/* - * enum pvt_sensor_type - Baikal-T1 PVT sensor types (correspond to each PVT - * sampling mode) - * @PVT_SENSOR*: helpers to traverse the sensors in loops. - * @PVT_TEMP: PVT Temperature sensor. - * @PVT_VOLT: PVT Voltage sensor. - * @PVT_LVT: PVT Low-Voltage threshold sensor. - * @PVT_HVT: PVT High-Voltage threshold sensor. - * @PVT_SVT: PVT Standard-Voltage threshold sensor. - */ -enum pvt_sensor_type { - PVT_SENSOR_FIRST, - PVT_TEMP = PVT_SENSOR_FIRST, - PVT_VOLT, - PVT_LVT, - PVT_HVT, - PVT_SVT, - PVT_SENSOR_LAST = PVT_SVT, - PVT_SENSORS_NUM -}; - -/* - * enum pvt_clock_type - Baikal-T1 PVT clocks. - * @PVT_CLOCK_APB: APB clock. - * @PVT_CLOCK_REF: PVT reference clock. - */ -enum pvt_clock_type { - PVT_CLOCK_APB, - PVT_CLOCK_REF, - PVT_CLOCK_NUM -}; - -/* - * struct pvt_sensor_info - Baikal-T1 PVT sensor informational structure - * @channel: Sensor channel ID. - * @label: hwmon sensor label. - * @mode: PVT mode corresponding to the channel. - * @thres_base: upper and lower threshold values of the sensor. - * @thres_sts_lo: low threshold status bitfield. - * @thres_sts_hi: high threshold status bitfield. - * @type: Sensor type. - * @attr_min_alarm: Min alarm attribute ID. - * @attr_min_alarm: Max alarm attribute ID. - */ -struct pvt_sensor_info { - int channel; - const char *label; - u32 mode; - unsigned long thres_base; - u32 thres_sts_lo; - u32 thres_sts_hi; - enum hwmon_sensor_types type; - u32 attr_min_alarm; - u32 attr_max_alarm; -}; - -#define PVT_SENSOR_INFO(_ch, _label, _type, _mode, _thres) \ - { \ - .channel = _ch, \ - .label = _label, \ - .mode = PVT_CTRL_MODE_ ##_mode, \ - .thres_base = PVT_ ##_thres, \ - .thres_sts_lo = PVT_INTR_ ##_thres## _LO, \ - .thres_sts_hi = PVT_INTR_ ##_thres## _HI, \ - .type = _type, \ - .attr_min_alarm = _type## _min, \ - .attr_max_alarm = _type## _max, \ - } - -/* - * struct pvt_cache - PVT sensors data cache - * @data: data cache in raw format. - * @thres_sts_lo: low threshold status saved on the previous data conversion. - * @thres_sts_hi: high threshold status saved on the previous data conversion. - * @data_seqlock: cached data seq-lock. - * @conversion: data conversion completion. - */ -struct pvt_cache { - u32 data; -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - seqlock_t data_seqlock; - u32 thres_sts_lo; - u32 thres_sts_hi; -#else - struct completion conversion; -#endif -}; - -/* - * struct pvt_hwmon - Baikal-T1 PVT private data - * @dev: device structure of the PVT platform device. - * @hwmon: hwmon device structure. - * @regs: pointer to the Baikal-T1 PVT registers region. - * @irq: PVT events IRQ number. - * @clks: Array of the PVT clocks descriptor (APB/ref clocks). - * @ref_clk: Pointer to the reference clocks descriptor. - * @iface_mtx: Generic interface mutex (used to lock the alarm registers - * when the alarms enabled, or the data conversion interface - * if alarms are disabled). - * @sensor: current PVT sensor the data conversion is being performed for. - * @cache: data cache descriptor. - * @timeout: conversion timeout cache. - */ -struct pvt_hwmon { - struct device *dev; - struct device *hwmon; - - void __iomem *regs; - int irq; - - struct clk_bulk_data clks[PVT_CLOCK_NUM]; - - struct mutex iface_mtx; - enum pvt_sensor_type sensor; - struct pvt_cache cache[PVT_SENSORS_NUM]; - ktime_t timeout; -}; - -/* - * struct pvt_poly_term - a term descriptor of the PVT data translation - * polynomial - * @deg: degree of the term. - * @coef: multiplication factor of the term. - * @divider: distributed divider per each degree. - * @divider_leftover: divider leftover, which couldn't be redistributed. - */ -struct pvt_poly_term { - unsigned int deg; - long coef; - long divider; - long divider_leftover; -}; - -/* - * struct pvt_poly - PVT data translation polynomial descriptor - * @total_divider: total data divider. - * @terms: polynomial terms up to a free one. - */ -struct pvt_poly { - long total_divider; - struct pvt_poly_term terms[]; -}; - -#endif /* __HWMON_BT1_PVT_H__ */ -- cgit v1.2.3 From ab4b7071ae0a831e4c2fd45c626c3b1d66cc1201 Mon Sep 17 00:00:00 2001 From: "Timothy C. Sweeney-Fanelli" Date: Sun, 15 Feb 2026 16:16:18 +0100 Subject: hwmon: (asus-ec-sensors )add ROG CROSSHAIR X670E EXTREME Add support for ROG CROSSHAIR X670E EXTREME Signed-off-by: Timothy C. Sweeney-Fanelli Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260215151743.20138-3-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 58986546c723..8a080a786abd 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -22,6 +22,7 @@ Supported boards: * ROG CROSSHAIR VIII FORMULA * ROG CROSSHAIR VIII HERO * ROG CROSSHAIR VIII IMPACT + * ROG CROSSHAIR X670E EXTREME * ROG CROSSHAIR X670E HERO * ROG CROSSHAIR X670E GENE * ROG MAXIMUS X HERO diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 86f444498650..976dc04a6aaa 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -451,6 +451,15 @@ static const struct ec_board_info board_info_crosshair_viii_impact = { .family = family_amd_500_series, }; +static const struct ec_board_info board_info_crosshair_x670e_extreme = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_WATER_IN | + SENSOR_TEMP_WATER_OUT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_600_series, +}; + static const struct ec_board_info board_info_crosshair_x670e_gene = { .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_T_SENSOR | @@ -820,6 +829,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_crosshair_viii_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT", &board_info_crosshair_viii_impact), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E EXTREME", + &board_info_crosshair_x670e_extreme), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E GENE", &board_info_crosshair_x670e_gene), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", -- cgit v1.2.3 From 940c92c40ecaec1b2d417d924e8273cc480bf358 Mon Sep 17 00:00:00 2001 From: Varasina Farmadani Date: Sun, 15 Feb 2026 16:16:19 +0100 Subject: hwmon: (asus-ec-sensors) add ROG STRIX X470-F GAMING Add support for ROG STRIX X470-F GAMING Signed-off-by: Varasina Farmadani Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260215151743.20138-4-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 8a080a786abd..84b92093771e 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -33,6 +33,7 @@ Supported boards: * ROG STRIX B550-I GAMING * ROG STRIX B650E-I GAMING WIFI * ROG STRIX B850-I GAMING WIFI + * ROG STRIX X470-F GAMING * ROG STRIX X470-I GAMING * ROG STRIX X570-E GAMING * ROG STRIX X570-E GAMING WIFI II diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 976dc04a6aaa..ba1db62ad646 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -630,6 +630,14 @@ static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = { .family = family_amd_800_series, }; +static const struct ec_board_info board_info_strix_x470_f_gaming = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_400_series, +}; + static const struct ec_board_info board_info_strix_x470_i_gaming = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | @@ -851,6 +859,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_b650e_i_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI", &board_info_strix_b850_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING", + &board_info_strix_x470_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING", &board_info_strix_x470_i_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", -- cgit v1.2.3 From 4853b53264869e51378cad7bf1556d4e8049d445 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Fri, 20 Feb 2026 17:16:01 +0100 Subject: hwmon: (gpd-fan) Add GPD Win 5 The GPD Win 5 is a new device by GPD with an AMD AI MAX 385/395 chip. It uses the same fan control registers as the GPD Win Duo. This information was provided by GPD. Signed-off-by: Antheas Kapenekakis Link: https://lore.kernel.org/r/20260220161601.2344291-1-lkml@antheas.dev Signed-off-by: Guenter Roeck --- drivers/hwmon/gpd-fan.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c index 1729729b135f..80de5f20781e 100644 --- a/drivers/hwmon/gpd-fan.c +++ b/drivers/hwmon/gpd-fan.c @@ -209,6 +209,14 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = &gpd_duo_drvdata, }, + { + // GPD Win 5 with AMD AI MAX 395 + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-05"), + }, + .driver_data = &gpd_duo_drvdata, + }, { // GPD Pocket 4 .matches = { -- cgit v1.2.3 From ddc3dea2e4248c6f4692c0e70fe96f9ff76aad0e Mon Sep 17 00:00:00 2001 From: Ian Ray Date: Fri, 20 Feb 2026 13:20:20 +0200 Subject: dt-bindings: hwmon: ti,ina2xx: Add INA234 device Add a compatible string for the INA234 device, which is like INA226 but has different scaling. Note that the device tree compatible must be different since the driver uses the compatible to configure the scaling. Signed-off-by: Ian Ray Reviewed-by: Krzysztof Kozlowski # v1 Tested-by: Jens Almer Link: https://lore.kernel.org/r/20260220112024.97446-2-ian.ray@gehealthcare.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index d3cde8936686..009d78b30859 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -29,6 +29,7 @@ properties: - ti,ina230 - ti,ina231 - ti,ina233 + - ti,ina234 - ti,ina237 - ti,ina238 - ti,ina260 @@ -113,6 +114,7 @@ allOf: - ti,ina228 - ti,ina230 - ti,ina231 + - ti,ina234 - ti,ina237 - ti,ina238 - ti,ina260 @@ -134,6 +136,7 @@ allOf: - ti,ina226 - ti,ina230 - ti,ina231 + - ti,ina234 - ti,ina260 - ti,ina700 - ti,ina780 -- cgit v1.2.3 From f6e14b5bcabf4ee97a2d535c3c2d7e72c8da4c15 Mon Sep 17 00:00:00 2001 From: Ian Ray Date: Fri, 20 Feb 2026 13:20:21 +0200 Subject: hwmon: (ina2xx) Make it easier to add more devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make sysfs entries documentation easier to maintain. * Use multi-line enum. * Correct "has_power_average" comment. Create a new "has_update_interval" member for chips which support averaging. Signed-off-by: Ian Ray Reviewed-by: Bence Csókás # v2 Tested-by: Jens Almer Link: https://lore.kernel.org/r/20260220112024.97446-3-ian.ray@gehealthcare.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina2xx.rst | 12 ++++++++++-- drivers/hwmon/ina2xx.c | 18 ++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Documentation/hwmon/ina2xx.rst b/Documentation/hwmon/ina2xx.rst index a3860aae444c..a4ddf4bd2b08 100644 --- a/Documentation/hwmon/ina2xx.rst +++ b/Documentation/hwmon/ina2xx.rst @@ -124,8 +124,16 @@ power1_input Power(uW) measurement channel shunt_resistor Shunt resistance(uOhm) channel (not for ina260) ======================= =============================================== -Additional sysfs entries for ina226, ina230, ina231, ina260, and sy24655 ------------------------------------------------------------------------- +Additional sysfs entries +------------------------ + +Additional entries are available for the following chips: + + * ina226 + * ina230 + * ina231 + * ina260 + * sy24655 ======================= ==================================================== curr1_lcrit Critical low current diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 69ac0468dee4..cd0d39ee7616 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -135,13 +135,19 @@ static const struct regmap_config ina2xx_regmap_config = { .writeable_reg = ina2xx_writeable_reg, }; -enum ina2xx_ids { ina219, ina226, ina260, sy24655 }; +enum ina2xx_ids { + ina219, + ina226, + ina260, + sy24655 +}; struct ina2xx_config { u16 config_default; bool has_alerts; /* chip supports alerts and limits */ bool has_ishunt; /* chip has internal shunt resistor */ - bool has_power_average; /* chip has internal shunt resistor */ + bool has_power_average; /* chip supports average power */ + bool has_update_interval; int calibration_value; int shunt_div; int bus_voltage_shift; @@ -171,6 +177,7 @@ static const struct ina2xx_config ina2xx_config[] = { .has_alerts = false, .has_ishunt = false, .has_power_average = false, + .has_update_interval = false, }, [ina226] = { .config_default = INA226_CONFIG_DEFAULT, @@ -182,6 +189,7 @@ static const struct ina2xx_config ina2xx_config[] = { .has_alerts = true, .has_ishunt = false, .has_power_average = false, + .has_update_interval = true, }, [ina260] = { .config_default = INA260_CONFIG_DEFAULT, @@ -192,6 +200,7 @@ static const struct ina2xx_config ina2xx_config[] = { .has_alerts = true, .has_ishunt = true, .has_power_average = false, + .has_update_interval = true, }, [sy24655] = { .config_default = SY24655_CONFIG_DEFAULT, @@ -203,6 +212,7 @@ static const struct ina2xx_config ina2xx_config[] = { .has_alerts = true, .has_ishunt = false, .has_power_average = true, + .has_update_interval = false, }, }; @@ -706,7 +716,7 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type const struct ina2xx_data *data = _data; bool has_alerts = data->config->has_alerts; bool has_power_average = data->config->has_power_average; - enum ina2xx_ids chip = data->chip; + bool has_update_interval = data->config->has_update_interval; switch (type) { case hwmon_in: @@ -768,7 +778,7 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type case hwmon_chip: switch (attr) { case hwmon_chip_update_interval: - if (chip == ina226 || chip == ina260) + if (has_update_interval) return 0644; break; default: -- cgit v1.2.3 From 88a928eebccdc5445d874ddcbf1683b76c9f1431 Mon Sep 17 00:00:00 2001 From: Ian Ray Date: Fri, 20 Feb 2026 13:20:22 +0200 Subject: hwmon: (ina2xx) Add support for INA234 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit INA234 is register compatible to INA226 (excepting manufacturer and die or device id registers) but has different scaling. Signed-off-by: Ian Ray Reviewed-by: Bence Csókás # v2 Tested-by: Jens Almer Tested-by: Jonas Rebmann Link: https://lore.kernel.org/r/20260220112024.97446-4-ian.ray@gehealthcare.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina2xx.rst | 13 ++++++++++++- drivers/hwmon/Kconfig | 2 +- drivers/hwmon/ina2xx.c | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/ina2xx.rst b/Documentation/hwmon/ina2xx.rst index a4ddf4bd2b08..d64e7af46a12 100644 --- a/Documentation/hwmon/ina2xx.rst +++ b/Documentation/hwmon/ina2xx.rst @@ -74,6 +74,16 @@ Supported chips: https://us1.silergy.com/ + * Texas Instruments INA234 + + Prefix: 'ina234' + + Addresses: I2C 0x40 - 0x43 + + Datasheet: Publicly available at the Texas Instruments website + + https://www.ti.com/ + Author: Lothar Felten Description @@ -89,7 +99,7 @@ interface. The INA220 monitors both shunt drop and supply voltage. The INA226 is a current shunt and power monitor with an I2C interface. The INA226 monitors both a shunt voltage drop and bus supply voltage. -INA230 and INA231 are high or low side current shunt and power monitors +INA230, INA231, and INA234 are high or low side current shunt and power monitors with an I2C interface. The chips monitor both a shunt voltage drop and bus supply voltage. @@ -132,6 +142,7 @@ Additional entries are available for the following chips: * ina226 * ina230 * ina231 + * ina234 * ina260 * sy24655 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 27088850a063..817a0bfad8c7 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2247,7 +2247,7 @@ config SENSORS_INA2XX select REGMAP_I2C help If you say yes here you get support for INA219, INA220, INA226, - INA230, INA231, INA260, and SY24655 power monitor chips. + INA230, INA231, INA234, INA260, and SY24655 power monitor chips. The INA2xx driver is configured for the default configuration of the part as described in the datasheet. diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index cd0d39ee7616..836e15a5a780 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -138,6 +138,7 @@ static const struct regmap_config ina2xx_regmap_config = { enum ina2xx_ids { ina219, ina226, + ina234, ina260, sy24655 }; @@ -191,6 +192,18 @@ static const struct ina2xx_config ina2xx_config[] = { .has_power_average = false, .has_update_interval = true, }, + [ina234] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .shunt_div = 400, /* 2.5 µV/LSB raw ADC reading from INA2XX_SHUNT_VOLTAGE */ + .bus_voltage_shift = 4, + .bus_voltage_lsb = 25600, + .power_lsb_factor = 32, + .has_alerts = true, + .has_ishunt = false, + .has_power_average = false, + .has_update_interval = true, + }, [ina260] = { .config_default = INA260_CONFIG_DEFAULT, .shunt_div = 400, @@ -992,6 +1005,7 @@ static const struct i2c_device_id ina2xx_id[] = { { "ina226", ina226 }, { "ina230", ina226 }, { "ina231", ina226 }, + { "ina234", ina234 }, { "ina260", ina260 }, { "sy24655", sy24655 }, { } @@ -1023,6 +1037,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { .compatible = "ti,ina231", .data = (void *)ina226 }, + { + .compatible = "ti,ina234", + .data = (void *)ina234 + }, { .compatible = "ti,ina260", .data = (void *)ina260 -- cgit v1.2.3 From 9c9f86da4a03e2072be8c28cb3346563dc117a3f Mon Sep 17 00:00:00 2001 From: Hao Yu Date: Tue, 24 Feb 2026 01:38:52 +0800 Subject: dt-bindings: hwmon: add Aosong AHT10/AHT20/DHT20 to trivial devices Add Aosong AHT10, AHT20 and DHT20 temperature and humidity sensors to the trivial-devices documentation. These sensors use a standard I2C interface and do not require complex binding definitions. Signed-off-by: Hao Yu Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260223173853.30617-2-haoyufine@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index a482aeadcd44..aa924a410fc3 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -59,6 +59,10 @@ properties: - adi,lt7182s # AMS iAQ-Core VOC Sensor - ams,iaq-core + # Aosong temperature & humidity sensors with I2C interface + - aosong,aht10 + - aosong,aht20 + - aosong,dht20 # Arduino microcontroller interface over SPI on UnoQ board - arduino,unoq-mcu # Temperature monitoring of Astera Labs PT5161L PCIe retimer -- cgit v1.2.3 From f88aac547b628e28e75d0c8bed51c6773a34cc72 Mon Sep 17 00:00:00 2001 From: Hao Yu Date: Tue, 24 Feb 2026 01:38:53 +0800 Subject: hwmon: (aht10) add device tree ID matching Add of_device_id table to allow the driver to be matched via Device Tree. This is required for supporting the AHT10/20/DHT20 sensors on platforms using DT. Signed-off-by: Hao Yu Link: https://lore.kernel.org/r/20260223173853.30617-3-haoyufine@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/aht10.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c index 4ce019d2cc80..66955395d058 100644 --- a/drivers/hwmon/aht10.c +++ b/drivers/hwmon/aht10.c @@ -62,6 +62,15 @@ static const struct i2c_device_id aht10_id[] = { }; MODULE_DEVICE_TABLE(i2c, aht10_id); +static const struct of_device_id aht10_of_match[] = { + { .compatible = "aosong,aht10", .data = (void *)aht10 }, + { .compatible = "aosong,aht20", .data = (void *)aht20 }, + { .compatible = "aosong,dht20", .data = (void *)dht20 }, + {} +}; + +MODULE_DEVICE_TABLE(of, aht10_of_match); + /** * struct aht10_data - All the data required to operate an AHT10/AHT20 chip * @client: the i2c client associated with the AHT10/AHT20 @@ -377,6 +386,7 @@ static int aht10_probe(struct i2c_client *client) static struct i2c_driver aht10_driver = { .driver = { .name = "aht10", + .of_match_table = aht10_of_match, }, .probe = aht10_probe, .id_table = aht10_id, -- cgit v1.2.3 From 4c9d861b423b488312327728532b7ea7a2b8fb5c Mon Sep 17 00:00:00 2001 From: Ashish Yadav Date: Mon, 23 Feb 2026 10:38:02 +0530 Subject: dt-bindings: trivial-devices: Add support for XDPE1A2G5B/7B Add Infineon Digital Multi-phase XDPE1A2G5B and XDPE1A2G7B Controllers to trivial devices. Signed-off-by: Ashish Yadav Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20260223050804.4287-2-Ashish.Yadav@infineon.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index aa924a410fc3..ccac768f5380 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -161,6 +161,9 @@ properties: - infineon,xdpe15284 # Infineon Multi-phase Digital VR Controller xdpe152c4 - infineon,xdpe152c4 + # Infineon Multi-phase Digital VR Controller xdpe1a2g7b + - infineon,xdpe1a2g5b + - infineon,xdpe1a2g7b # Injoinic IP5108 2.0A Power Bank IC with I2C - injoinic,ip5108 # Injoinic IP5109 2.1A Power Bank IC with I2C -- cgit v1.2.3 From 969a4ec86ca5fbd9952d5262b5a63ff434ccd63b Mon Sep 17 00:00:00 2001 From: Ashish Yadav Date: Mon, 23 Feb 2026 10:38:03 +0530 Subject: hwmon: (pmbus/core) Add support for NVIDIA nvidia195mv mode Extend the PMBus core vrm_version handling to support NVIDIA nvidia195mv VID mode. This adds a new VRM/VID encoding type and the corresponding voltage conversion logic so devices reporting nvidia195mv can have their VOUT/VID values interpreted correctly by the hwmon PMBus core. Signed-off-by: Ashish Yadav Link: https://lore.kernel.org/r/20260223050804.4287-3-Ashish.Yadav@infineon.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 2 +- drivers/hwmon/pmbus/pmbus_core.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index d2e9bfb5320f..3ddcb742d289 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -416,7 +416,7 @@ enum pmbus_sensor_classes { #define PMBUS_PAGE_VIRTUAL BIT(31) /* Page is virtual */ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; -enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv }; +enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; /* PMBus revision identifiers */ #define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 572be3ebc03d..7d73abecc050 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -891,6 +891,10 @@ static s64 pmbus_reg2data_vid(struct pmbus_data *data, if (val >= 0x0 && val <= 0xd8) rv = DIV_ROUND_CLOSEST(155000 - val * 625, 100); break; + case nvidia195mv: + if (val >= 0x01) + rv = 195 + (val - 1) * 5; /* VID step is 5mv */ + break; } return rv; } -- cgit v1.2.3 From 34b66c7ed11c639a1efc02201e8c46382d5463ee Mon Sep 17 00:00:00 2001 From: Ashish Yadav Date: Mon, 23 Feb 2026 10:38:04 +0530 Subject: hwmon:(pmbus/xdpe1a2g7b) Add support for xdpe1a2g5b/7b controllers Add the pmbus driver for Infineon Digital Multi-phase XDPE1A2G5B and XDPE1A2G7B controllers. Signed-off-by: Ashish Yadav Link: https://lore.kernel.org/r/20260223050804.4287-4-Ashish.Yadav@infineon.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 9 +++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/xdpe1a2g7b.c | 119 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 drivers/hwmon/pmbus/xdpe1a2g7b.c diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index fc1273abe357..a4513fc6bc26 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -711,6 +711,15 @@ config SENSORS_XDPE152 This driver can also be built as a module. If so, the module will be called xdpe152c4. +config SENSORS_XDPE1A2G7B + tristate "Infineon XDPE1A2G7B" + help + If you say yes here you get hardware monitoring support for Infineon + XDPE1A2G5B and XDPE1A2G7B. + + This driver can also be built as a module. If so, the module will + be called xdpe1a2g7b. + config SENSORS_XDPE122 tristate "Infineon XDPE122 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index d6c86924f887..d592d8c77bec 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o obj-$(CONFIG_SENSORS_XDP710) += xdp710.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o +obj-$(CONFIG_SENSORS_XDPE1A2G7B) += xdpe1a2g7b.o obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o obj-$(CONFIG_SENSORS_CRPS) += crps.o diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c new file mode 100644 index 000000000000..1755e3522ede --- /dev/null +++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Infineon Multi-phase Digital XDPE1A2G5B + * and XDPE1A2G7B Controllers + * + * Copyright (c) 2026 Infineon Technologies. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define XDPE1A2G7B_PAGE_NUM 2 +#define XDPE1A2G7B_NVIDIA_195MV 0x1E /* NVIDIA mode 1.95mV, VID step is 5mV */ + +static int xdpe1a2g7b_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + u8 vout_params; + int vout_mode; + + /* + * XDPE1A2G5B and XDPE1A2G7B support both Linear and NVIDIA PWM VID data + * formats via VOUT_MODE. Note that the device pages/loops are not fully + * independent: configuration is shared, so programming each page/loop + * separately is not supported. + */ + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode < 0) + return vout_mode; + + switch (vout_mode >> 5) { + case 0: + info->format[PSC_VOLTAGE_OUT] = linear; + return 0; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + vout_params = vout_mode & GENMASK(4, 0); + /* Check for VID Code Type */ + switch (vout_params) { + case XDPE1A2G7B_NVIDIA_195MV: + /* VID vrm_version for PAGE0 and PAGE1 */ + info->vrm_version[0] = nvidia195mv; + info->vrm_version[1] = nvidia195mv; + break; + default: + return -EINVAL; + } + break; + default: + return -ENODEV; + } + + return 0; +} + +static struct pmbus_driver_info xdpe1a2g7b_info = { + .pages = XDPE1A2G7B_PAGE_NUM, + .identify = xdpe1a2g7b_identify, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, +}; + +static int xdpe1a2g7b_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + + info = devm_kmemdup(&client->dev, &xdpe1a2g7b_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id xdpe1a2g7b_id[] = { + { "xdpe1a2g5b" }, + { "xdpe1a2g7b" }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, xdpe1a2g7b_id); + +static const struct of_device_id __maybe_unused xdpe1a2g7b_of_match[] = { + { .compatible = "infineon,xdpe1a2g5b" }, + { .compatible = "infineon,xdpe1a2g7b" }, + {} +}; + +MODULE_DEVICE_TABLE(of, xdpe1a2g7b_of_match); + +static struct i2c_driver xdpe1a2g7b_driver = { + .driver = { + .name = "xdpe1a2g7b", + .of_match_table = of_match_ptr(xdpe1a2g7b_of_match), + }, + .probe = xdpe1a2g7b_probe, + .id_table = xdpe1a2g7b_id, +}; + +module_i2c_driver(xdpe1a2g7b_driver); + +MODULE_AUTHOR("Ashish Yadav "); +MODULE_DESCRIPTION("PMBus driver for Infineon XDPE1A2G5B/7B"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); -- cgit v1.2.3 From 7b7ee707d61ff5a29af5270dd0505438c86e71e8 Mon Sep 17 00:00:00 2001 From: Tomer Maimon Date: Sun, 15 Feb 2026 18:35:53 +0200 Subject: dt-bindings: hwmon: convert npcm750-pwm-fan to DT schema Convert the Nuvoton HWMON PWM and FAN controllers binding to schema format. Signed-off-by: Tomer Maimon Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20260215163553.1334475-1-tmaimon77@gmail.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/npcm750-pwm-fan.txt | 88 ------------- .../bindings/hwmon/nuvoton,npcm750-pwm-fan.yaml | 139 +++++++++++++++++++++ 2 files changed, 139 insertions(+), 88 deletions(-) delete mode 100644 Documentation/devicetree/bindings/hwmon/npcm750-pwm-fan.txt create mode 100644 Documentation/devicetree/bindings/hwmon/nuvoton,npcm750-pwm-fan.yaml diff --git a/Documentation/devicetree/bindings/hwmon/npcm750-pwm-fan.txt b/Documentation/devicetree/bindings/hwmon/npcm750-pwm-fan.txt deleted file mode 100644 index 18095ba87a5a..000000000000 --- a/Documentation/devicetree/bindings/hwmon/npcm750-pwm-fan.txt +++ /dev/null @@ -1,88 +0,0 @@ -Nuvoton NPCM PWM and Fan Tacho controller device - -The Nuvoton BMC NPCM7XX supports 8 Pulse-width modulation (PWM) -controller outputs and 16 Fan tachometer controller inputs. - -The Nuvoton BMC NPCM8XX supports 12 Pulse-width modulation (PWM) -controller outputs and 16 Fan tachometer controller inputs. - -Required properties for pwm-fan node -- #address-cells : should be 1. -- #size-cells : should be 0. -- compatible : "nuvoton,npcm750-pwm-fan" for Poleg NPCM7XX. - : "nuvoton,npcm845-pwm-fan" for Arbel NPCM8XX. -- reg : specifies physical base address and size of the registers. -- reg-names : must contain: - * "pwm" for the PWM registers. - * "fan" for the Fan registers. -- clocks : phandle of reference clocks. -- clock-names : must contain - * "pwm" for PWM controller operating clock. - * "fan" for Fan controller operating clock. -- interrupts : contain the Fan interrupts with flags for falling edge. -- pinctrl-names : a pinctrl state named "default" must be defined. -- pinctrl-0 : phandle referencing pin configuration of the PWM and Fan - controller ports. - -fan subnode format: -=================== -Under fan subnode can be upto 8 child nodes, each child node representing a fan. -Each fan subnode must have one PWM channel and at least one Fan tach channel. - -For PWM channel can be configured cooling-levels to create cooling device. -Cooling device could be bound to a thermal zone for the thermal control. - -Required properties for each child node: -- reg : specify the PWM output channel. - integer value in the range 0 through 7, that represent - the PWM channel number that used. - -- fan-tach-ch : specify the Fan tach input channel. - integer value in the range 0 through 15, that represent - the fan tach channel number that used. - - At least one Fan tach input channel is required - -Optional property for each child node: -- cooling-levels: PWM duty cycle values in a range from 0 to 255 - which correspond to thermal cooling states. - -Examples: - -pwm_fan:pwm-fan-controller@103000 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "nuvoton,npcm750-pwm-fan"; - reg = <0x103000 0x2000>, - <0x180000 0x8000>; - reg-names = "pwm", "fan"; - clocks = <&clk NPCM7XX_CLK_APB3>, - <&clk NPCM7XX_CLK_APB4>; - clock-names = "pwm","fan"; - interrupts = , - , - , - , - , - , - , - ; - pinctrl-names = "default"; - pinctrl-0 = <&pwm0_pins &pwm1_pins &pwm2_pins - &fanin0_pins &fanin1_pins &fanin2_pins - &fanin3_pins &fanin4_pins>; - fan@0 { - reg = <0x00>; - fan-tach-ch = /bits/ 8 <0x00 0x01>; - cooling-levels = <127 255>; - }; - fan@1 { - reg = <0x01>; - fan-tach-ch = /bits/ 8 <0x02 0x03>; - }; - fan@2 { - reg = <0x02>; - fan-tach-ch = /bits/ 8 <0x04>; - }; - -}; diff --git a/Documentation/devicetree/bindings/hwmon/nuvoton,npcm750-pwm-fan.yaml b/Documentation/devicetree/bindings/hwmon/nuvoton,npcm750-pwm-fan.yaml new file mode 100644 index 000000000000..73464af3078e --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/nuvoton,npcm750-pwm-fan.yaml @@ -0,0 +1,139 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/nuvoton,npcm750-pwm-fan.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton NPCM7xx/NPCM8xx PWM and Fan Tach Controller + +maintainers: + - Tomer Maimon + +description: + The NPCM7xx/NPCM8xx family includes a PWM and Fan Tachometer controller. + The controller provides up to 8 (NPCM7xx) or 12 (NPCM8xx) PWM channels and up + to 16 tachometer inputs. It is used for fan speed control and monitoring. + +properties: + compatible: + enum: + - nuvoton,npcm750-pwm-fan + - nuvoton,npcm845-pwm-fan + + reg: + maxItems: 2 + description: Register addresses for PWM and Fan Tach units. + + reg-names: + items: + - const: pwm + - const: fan + + clocks: + maxItems: 2 + description: Clocks for the PWM and Fan Tach modules. + + clock-names: + items: + - const: pwm + - const: fan + + interrupts: + description: + Contains the Fan interrupts with flags for falling edge. + For NPCM7XX, 8 interrupt lines are expected (one per PWM channel). + For NPCM8XX, 12 interrupt lines are expected (one per PWM channel). + + minItems: 8 + maxItems: 12 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^fan@[0-9a-f]+$": + type: object + $ref: fan-common.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + Specify the PWM output channel. Integer value in the range 0-7 for + NPCM7XX or 0-11 for NPCM8XX, representing the PWM channel number. + + maximum: 11 + + fan-tach-ch: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: + The tach channel(s) used for the fan. + Integer values in the range 0-15. + + items: + maximum: 15 + + cooling-levels: + description: + PWM duty cycle values in a range from 0 to 255 which + correspond to thermal cooling states. This property enables + thermal zone integration for automatic fan speed control + based on temperature. + + items: + maximum: 255 + + required: + - reg + - fan-tach-ch + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + pwm_fan: pwm-fan@103000 { + compatible = "nuvoton,npcm750-pwm-fan"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x103000 0x2000>, <0x180000 0x8000>; + reg-names = "pwm", "fan"; + + clocks = <&clk NPCM7XX_CLK_APB3>, <&clk NPCM7XX_CLK_APB4>; + clock-names = "pwm", "fan"; + + interrupts = , + , + , + , + , + , + , + ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm0_pins &fanin0_pins>; + + fan@0 { + reg = <0>; + fan-tach-ch = <0 1>; + cooling-levels = <64 128 192 255>; + }; + + fan@1 { + reg = <1>; + fan-tach-ch = <2>; + }; + }; -- cgit v1.2.3 From 03f40ff2a0ab1a2df461e47771ad86e5da24bda8 Mon Sep 17 00:00:00 2001 From: Volodimir Buchakchiyskiy Date: Sat, 28 Feb 2026 12:44:02 +0100 Subject: hwmon: (asus-ec-sensors) add ROG STRIX Z790-H GAMING WIFI Add limited support for ROG STRIX Z790-H GAMING WIFI (VRM temp and T_Sensor only). Signed-off-by: Volodimir Buchakchiyskiy Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260228114412.358148-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 84b92093771e..9ad3f0a57f55 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -50,6 +50,7 @@ Supported boards: * ROG STRIX Z690-A GAMING WIFI D4 * ROG STRIX Z690-E GAMING WIFI * ROG STRIX Z790-E GAMING WIFI II + * ROG STRIX Z790-H GAMING WIFI * ROG STRIX Z790-I GAMING WIFI * ROG ZENITH II EXTREME * ROG ZENITH II EXTREME ALPHA diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index ba1db62ad646..070bb368f2b7 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -762,6 +762,12 @@ static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = { .family = family_intel_700_series, }; +static const struct ec_board_info board_info_strix_z790_h_gaming_wifi = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, + .family = family_intel_700_series, +}; + static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = { .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_VRM, @@ -893,6 +899,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_z690_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II", &board_info_strix_z790_e_gaming_wifi_ii), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-H GAMING WIFI", + &board_info_strix_z790_h_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI", &board_info_strix_z790_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME", -- cgit v1.2.3 From 0600919f8c25b31042281d1053ac9d88a2caf3fd Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Wed, 25 Feb 2026 11:51:32 +0200 Subject: hwmon: tmp102: Add support for TMP110 and TMP113 devices TMP110 and TMP113 temperature sensors are software compatible with TMP102 sensor but have different accuracy (maximum error). Signed-off-by: Flaviu Nistor Link: https://lore.kernel.org/r/20260225095132.29954-1-flaviu.nistor@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/tmp102.rst | 21 +++++++++++++++++++++ drivers/hwmon/Kconfig | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/tmp102.rst b/Documentation/hwmon/tmp102.rst index b1f585531a88..3c2cb5bab1e9 100644 --- a/Documentation/hwmon/tmp102.rst +++ b/Documentation/hwmon/tmp102.rst @@ -11,6 +11,22 @@ Supported chips: Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp102.html + * Texas Instruments TMP110 + + Prefix: 'tmp110' + + Addresses scanned: none + + Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp110.html + + * Texas Instruments TMP113 + + Prefix: 'tmp113' + + Addresses scanned: none + + Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp113.html + Author: Steven King @@ -27,5 +43,10 @@ operating temperature has a minimum of -55 C and a maximum of +150 C. The TMP102 has a programmable update rate that can select between 8, 4, 1, and 0.5 Hz. (Currently the driver only supports the default of 4 Hz). +The TMP110 and TMP113 are software compatible with TMP102, but have different +accuracy (maximum error) specifications. The TMP110 has an accuracy (maximum error) +of 1.0 degree, TMP113 has an accuracy (maximum error) of 0.3 degree, while TMP102 +has an accuracy (maximum error) of 2.0 degree. + The driver provides the common sysfs-interface for temperatures (see Documentation/hwmon/sysfs-interface.rst under Temperatures). diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 817a0bfad8c7..f034d204be4f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2336,8 +2336,8 @@ config SENSORS_TMP102 depends on I2C select REGMAP_I2C help - If you say yes here you get support for Texas Instruments TMP102 - sensor chips. + If you say yes here you get support for Texas Instruments TMP102, + TMP110 and TMP113 sensor chips. This driver can also be built as a module. If so, the module will be called tmp102. -- cgit v1.2.3 From de19682f9ecbe02a5287060b59e83c3ded8f0a39 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 1 Mar 2026 14:17:19 +0100 Subject: hwmon: (acpi_power_meter) Drop redundant checks from three functions Since acpi_power_meter_notify() and acpi_power_meter_remove() are .notify() and .remove() callback functions of an ACPI driver, respectively, the first argument of the former and the only argument of the latter cannot be NULL. Likewise, the acpi_power_meter_resume() argument cannot be NULL because it is a system resume callback function. Moreover, since all of these functions can only run after acpi_power_meter_add() has returned 0, the driver_data field in the struct acpi_device object used by them cannot be NULL either. Accordingly, drop the redundant "device" checks against NULL from acpi_power_meter_notify() and acpi_power_meter_remove(), drop the redundant "dev" check against NULL from acpi_power_meter_resume(), and drop the redundant acpi_driver_data() checks against NULL from all of these functions. Additionally, combine the initialization of the "resource" local variable in acpi_power_meter_notify() and acpi_power_meter_remove() with its declaration. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/5085645.31r3eYUQgx@rafael.j.wysocki Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 1e3fab5f7946..49e57c20ef70 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -816,14 +816,9 @@ end: /* Handle ACPI event notifications */ static void acpi_power_meter_notify(struct acpi_device *device, u32 event) { - struct acpi_power_meter_resource *resource; + struct acpi_power_meter_resource *resource = acpi_driver_data(device); int res; - if (!device || !acpi_driver_data(device)) - return; - - resource = acpi_driver_data(device); - guard(mutex)(&acpi_notify_lock); switch (event) { @@ -956,12 +951,8 @@ exit: static void acpi_power_meter_remove(struct acpi_device *device) { - struct acpi_power_meter_resource *resource; + struct acpi_power_meter_resource *resource = acpi_driver_data(device); - if (!device || !acpi_driver_data(device)) - return; - - resource = acpi_driver_data(device); if (!IS_ERR(resource->hwmon_dev)) hwmon_device_unregister(resource->hwmon_dev); @@ -975,12 +966,7 @@ static int acpi_power_meter_resume(struct device *dev) { struct acpi_power_meter_resource *resource; - if (!dev) - return -EINVAL; - resource = acpi_driver_data(to_acpi_device(dev)); - if (!resource) - return -EINVAL; free_capabilities(resource); read_capabilities(resource); -- cgit v1.2.3 From a5445f75443dba37698f336c2b28daba606e86cf Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 1 Mar 2026 14:18:05 +0100 Subject: hwmon: (acpi_power_meter) Register ACPI notify handler directly To facilitate subsequent conversion of the driver to a platform one, make it install an ACPI notify handler directly instead of using a .notify() callback in struct acpi_driver. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/2405555.ElGaqSPkdT@rafael.j.wysocki Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 49e57c20ef70..c010f55f7c7b 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -814,8 +814,9 @@ end: } /* Handle ACPI event notifications */ -static void acpi_power_meter_notify(struct acpi_device *device, u32 event) +static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) { + struct acpi_device *device = data; struct acpi_power_meter_resource *resource = acpi_driver_data(device); int res; @@ -936,9 +937,16 @@ static int acpi_power_meter_add(struct acpi_device *device) goto exit_remove; } + res = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_power_meter_notify, device); + if (res) + goto exit_hwmon; + res = 0; goto exit; +exit_hwmon: + hwmon_device_unregister(resource->hwmon_dev); exit_remove: remove_domain_devices(resource); exit_free_capability: @@ -953,6 +961,9 @@ static void acpi_power_meter_remove(struct acpi_device *device) { struct acpi_power_meter_resource *resource = acpi_driver_data(device); + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_power_meter_notify); + if (!IS_ERR(resource->hwmon_dev)) hwmon_device_unregister(resource->hwmon_dev); @@ -984,7 +995,6 @@ static struct acpi_driver acpi_power_meter_driver = { .ops = { .add = acpi_power_meter_add, .remove = acpi_power_meter_remove, - .notify = acpi_power_meter_notify, }, .drv.pm = pm_sleep_ptr(&acpi_power_meter_pm), }; -- cgit v1.2.3 From afc6c4aedea5717b7cb4a14b4bcc094892ea8d89 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 1 Mar 2026 14:18:49 +0100 Subject: hwmon: (acpi_power_meter) Convert ACPI driver to a platform one In all cases in which a struct acpi_driver is used for binding a driver to an ACPI device object, a corresponding platform device is created by the ACPI core and that device is regarded as a proper representation of underlying hardware. Accordingly, a struct platform_driver should be used by driver code to bind to that device. There are multiple reasons why drivers should not bind directly to ACPI device objects [1]. Overall, it is better to bind drivers to platform devices than to their ACPI companions, so convert the hwmon ACPI power meter driver to a platform one. After this change, the subordinate hwmon device will be registered under the platform device representing the ACPI power meter, sysfs notifications will trigger on that device, and diagnostic messages will be printed relative to it instead of its ACPI companion. Link: https://lore.kernel.org/all/2396510.ElGaqSPkdT@rafael.j.wysocki/ [1] Signed-off-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/1952740.tdWV9SEqCh@rafael.j.wysocki Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 76 +++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index c010f55f7c7b..be7f702dcde9 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -18,6 +18,7 @@ #include #include #include +#include #define ACPI_POWER_METER_NAME "power_meter" #define ACPI_POWER_METER_DEVICE_NAME "Power Meter" @@ -816,8 +817,8 @@ end: /* Handle ACPI event notifications */ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) { - struct acpi_device *device = data; - struct acpi_power_meter_resource *resource = acpi_driver_data(device); + struct device *dev = data; + struct acpi_power_meter_resource *resource = dev_get_drvdata(dev); int res; guard(mutex)(&acpi_notify_lock); @@ -833,43 +834,43 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) remove_domain_devices(resource); res = read_capabilities(resource); if (res) - dev_err_once(&device->dev, "read capabilities failed.\n"); + dev_err_once(dev, "read capabilities failed.\n"); res = read_domain_devices(resource); if (res && res != -ENODEV) - dev_err_once(&device->dev, "read domain devices failed.\n"); + dev_err_once(dev, "read domain devices failed.\n"); mutex_unlock(&resource->lock); resource->hwmon_dev = - hwmon_device_register_with_info(&device->dev, + hwmon_device_register_with_info(dev, ACPI_POWER_METER_NAME, resource, &power_meter_chip_info, power_extra_groups); if (IS_ERR(resource->hwmon_dev)) - dev_err_once(&device->dev, "register hwmon device failed.\n"); + dev_err_once(dev, "register hwmon device failed.\n"); break; case METER_NOTIFY_TRIP: - sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); + sysfs_notify(&dev->kobj, NULL, POWER_AVERAGE_NAME); break; case METER_NOTIFY_CAP: mutex_lock(&resource->lock); res = update_cap(resource); if (res) - dev_err_once(&device->dev, "update cap failed when capping value is changed.\n"); + dev_err_once(dev, "update cap failed when capping value is changed.\n"); mutex_unlock(&resource->lock); - sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); + sysfs_notify(&dev->kobj, NULL, POWER_CAP_NAME); break; case METER_NOTIFY_INTERVAL: - sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); + sysfs_notify(&dev->kobj, NULL, POWER_AVG_INTERVAL_NAME); break; case METER_NOTIFY_CAPPING: mutex_lock(&resource->lock); resource->power_alarm = true; mutex_unlock(&resource->lock); - sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); - dev_info(&device->dev, "Capping in progress.\n"); + sysfs_notify(&dev->kobj, NULL, POWER_ALARM_NAME); + dev_info(dev, "Capping in progress.\n"); break; default: WARN(1, "Unexpected event %d\n", event); @@ -877,16 +878,15 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) } acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, - dev_name(&device->dev), event, 0); + dev_name(&resource->acpi_dev->dev), + event, 0); } -static int acpi_power_meter_add(struct acpi_device *device) +static int acpi_power_meter_probe(struct platform_device *pdev) { - int res; + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct acpi_power_meter_resource *resource; - - if (!device) - return -EINVAL; + int res; resource = kzalloc_obj(*resource); if (!resource) @@ -897,7 +897,8 @@ static int acpi_power_meter_add(struct acpi_device *device) mutex_init(&resource->lock); strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); - device->driver_data = resource; + + platform_set_drvdata(pdev, resource); #if IS_REACHABLE(CONFIG_ACPI_IPMI) /* @@ -910,7 +911,7 @@ static int acpi_power_meter_add(struct acpi_device *device) struct acpi_device *ipi_device = acpi_dev_get_first_match_dev("IPI0001", NULL, -1); if (ipi_device && acpi_wait_for_acpi_ipmi()) - dev_warn(&device->dev, "Waiting for ACPI IPMI timeout"); + dev_warn(&pdev->dev, "Waiting for ACPI IPMI timeout"); acpi_dev_put(ipi_device); } #endif @@ -928,7 +929,7 @@ static int acpi_power_meter_add(struct acpi_device *device) goto exit_free_capability; resource->hwmon_dev = - hwmon_device_register_with_info(&device->dev, + hwmon_device_register_with_info(&pdev->dev, ACPI_POWER_METER_NAME, resource, &power_meter_chip_info, power_extra_groups); @@ -938,7 +939,7 @@ static int acpi_power_meter_add(struct acpi_device *device) } res = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, - acpi_power_meter_notify, device); + acpi_power_meter_notify, &pdev->dev); if (res) goto exit_hwmon; @@ -957,11 +958,11 @@ exit: return res; } -static void acpi_power_meter_remove(struct acpi_device *device) +static void acpi_power_meter_remove(struct platform_device *pdev) { - struct acpi_power_meter_resource *resource = acpi_driver_data(device); + struct acpi_power_meter_resource *resource = platform_get_drvdata(pdev); - acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_dev_remove_notify_handler(resource->acpi_dev, ACPI_DEVICE_NOTIFY, acpi_power_meter_notify); if (!IS_ERR(resource->hwmon_dev)) @@ -975,9 +976,7 @@ static void acpi_power_meter_remove(struct acpi_device *device) static int acpi_power_meter_resume(struct device *dev) { - struct acpi_power_meter_resource *resource; - - resource = acpi_driver_data(to_acpi_device(dev)); + struct acpi_power_meter_resource *resource = dev_get_drvdata(dev); free_capabilities(resource); read_capabilities(resource); @@ -988,15 +987,14 @@ static int acpi_power_meter_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL, acpi_power_meter_resume); -static struct acpi_driver acpi_power_meter_driver = { - .name = "power_meter", - .class = ACPI_POWER_METER_CLASS, - .ids = power_meter_ids, - .ops = { - .add = acpi_power_meter_add, - .remove = acpi_power_meter_remove, - }, - .drv.pm = pm_sleep_ptr(&acpi_power_meter_pm), +static struct platform_driver acpi_power_meter_driver = { + .probe = acpi_power_meter_probe, + .remove = acpi_power_meter_remove, + .driver = { + .name = "acpi-power-meter", + .acpi_match_table = power_meter_ids, + .pm = &acpi_power_meter_pm, + }, }; /* Module init/exit routines */ @@ -1025,7 +1023,7 @@ static int __init acpi_power_meter_init(void) dmi_check_system(pm_dmi_table); - result = acpi_bus_register_driver(&acpi_power_meter_driver); + result = platform_driver_register(&acpi_power_meter_driver); if (result < 0) return result; @@ -1034,7 +1032,7 @@ static int __init acpi_power_meter_init(void) static void __exit acpi_power_meter_exit(void) { - acpi_bus_unregister_driver(&acpi_power_meter_driver); + platform_driver_unregister(&acpi_power_meter_driver); } MODULE_AUTHOR("Darrick J. Wong "); -- cgit v1.2.3 From a28b088ede9df9fd9f5be6e54b4a7d9fc70d9f35 Mon Sep 17 00:00:00 2001 From: Mariano Abad Date: Mon, 2 Mar 2026 21:46:04 -0300 Subject: hwmon: Add LattePanda Sigma EC driver Add hardware monitoring support for the LattePanda Sigma SBC (DFRobot, ITE IT8613E EC). The driver reads fan speed and temperatures via direct port I/O, as the BIOS disables the ACPI EC interface. Signed-off-by: Mariano Abad Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/lattepanda-sigma-ec.rst | 61 +++++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 17 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/lattepanda-sigma-ec.c | 359 ++++++++++++++++++++++++++++ 6 files changed, 446 insertions(+) create mode 100644 Documentation/hwmon/lattepanda-sigma-ec.rst create mode 100644 drivers/hwmon/lattepanda-sigma-ec.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index c7ba1cc86882..559c32344cd3 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -110,6 +110,7 @@ Hardware Monitoring Kernel Drivers kbatt kfan lan966x + lattepanda-sigma-ec lineage-pem lm25066 lm63 diff --git a/Documentation/hwmon/lattepanda-sigma-ec.rst b/Documentation/hwmon/lattepanda-sigma-ec.rst new file mode 100644 index 000000000000..8a521ee1fef1 --- /dev/null +++ b/Documentation/hwmon/lattepanda-sigma-ec.rst @@ -0,0 +1,61 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver lattepanda-sigma-ec +================================= + +Supported systems: + + * LattePanda Sigma (Intel 13th Gen i5-1340P) + + DMI vendor: LattePanda + + DMI product: LattePanda Sigma + + BIOS version: 5.27 (verified) + + Datasheet: Not available (EC registers discovered empirically) + +Author: Mariano Abad + +Description +----------- + +This driver provides hardware monitoring for the LattePanda Sigma +single-board computer made by DFRobot. The board uses an ITE IT8613E +Embedded Controller to manage a CPU cooling fan and thermal sensors. + +The BIOS declares the ACPI Embedded Controller (``PNP0C09``) with +``_STA`` returning 0, preventing the kernel's ACPI EC subsystem from +initializing. This driver reads the EC directly via the standard ACPI +EC I/O ports (``0x62`` data, ``0x66`` command/status). + +Sysfs attributes +---------------- + +======================= =============================================== +``fan1_input`` Fan speed in RPM (EC registers 0x2E:0x2F, + 16-bit big-endian) +``fan1_label`` "CPU Fan" +``temp1_input`` Board/ambient temperature in millidegrees + Celsius (EC register 0x60, unsigned) +``temp1_label`` "Board Temp" +``temp2_input`` CPU proximity temperature in millidegrees + Celsius (EC register 0x70, unsigned) +``temp2_label`` "CPU Temp" +======================= =============================================== + +Module parameters +----------------- + +``force`` (bool, default false) + Force loading on BIOS versions other than 5.27. The driver still + requires DMI vendor and product name matching. + +Known limitations +----------------- + +* Fan speed control is not supported. The fan is always under EC + automatic control. +* The EC register map was verified only on BIOS version 5.27. + Other versions may use different register offsets; use the ``force`` + parameter at your own risk. diff --git a/MAINTAINERS b/MAINTAINERS index c3fe46d7c4bc..1c49613b142e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14421,6 +14421,13 @@ F: drivers/net/wan/framer/ F: drivers/pinctrl/pinctrl-pef2256.c F: include/linux/framer/ +LATTEPANDA SIGMA EC HARDWARE MONITOR DRIVER +M: Mariano Abad +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/lattepanda-sigma-ec.rst +F: drivers/hwmon/lattepanda-sigma-ec.c + LASI 53c700 driver for PARISC M: "James E.J. Bottomley" L: linux-scsi@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f034d204be4f..e0cd83b4b715 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -964,6 +964,23 @@ config SENSORS_LAN966X This driver can also be built as a module. If so, the module will be called lan966x-hwmon. +config SENSORS_LATTEPANDA_SIGMA_EC + tristate "LattePanda Sigma EC hardware monitoring" + depends on X86 + depends on DMI + depends on HAS_IOPORT + help + If you say yes here you get support for the hardware monitoring + features of the Embedded Controller on LattePanda Sigma + single-board computers, including CPU fan speed (RPM) and + board and CPU temperatures. + + The driver reads the EC directly via ACPI EC I/O ports and + uses DMI matching to ensure it only loads on supported hardware. + + This driver can also be built as a module. If so, the module + will be called lattepanda-sigma-ec. + config SENSORS_LENOVO_EC tristate "Sensor reader for Lenovo ThinkStations" depends on X86 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c1020dbbc1ac..556e86d277b1 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -113,6 +113,7 @@ obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_KBATT) += kbatt.o obj-$(CONFIG_SENSORS_KFAN) += kfan.o obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o +obj-$(CONFIG_SENSORS_LATTEPANDA_SIGMA_EC) += lattepanda-sigma-ec.o obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o diff --git a/drivers/hwmon/lattepanda-sigma-ec.c b/drivers/hwmon/lattepanda-sigma-ec.c new file mode 100644 index 000000000000..f06097422201 --- /dev/null +++ b/drivers/hwmon/lattepanda-sigma-ec.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for LattePanda Sigma EC. + * + * The LattePanda Sigma is an x86 SBC made by DFRobot with an ITE IT8613E + * Embedded Controller that manages a CPU fan and thermal sensors. + * + * The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA + * returning 0 and provides only stub ECRD/ECWT methods that return Zero + * for all registers. Since the kernel's ACPI EC subsystem never initializes, + * ec_read() is not available and direct port I/O to the standard ACPI EC + * ports (0x62/0x66) is used instead. + * + * Because ACPI never initializes the EC, there is no concurrent firmware + * access to these ports, and no ACPI Global Lock or namespace mutex is + * required. The hwmon with_info API serializes all sysfs callbacks, + * so no additional driver-level locking is needed. + * + * The EC register map was discovered by dumping all 256 registers, + * identifying those that change in real-time, and validating by physically + * stopping the fan and observing the RPM register drop to zero. The map + * has been verified on BIOS version 5.27; other versions may differ. + * + * Copyright (c) 2026 Mariano Abad + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "lattepanda_sigma_ec" + +/* EC I/O ports (standard ACPI EC interface) */ +#define EC_DATA_PORT 0x62 +#define EC_CMD_PORT 0x66 /* also status port */ + +/* EC commands */ +#define EC_CMD_READ 0x80 + +/* EC status register bits */ +#define EC_STATUS_OBF 0x01 /* Output Buffer Full */ +#define EC_STATUS_IBF 0x02 /* Input Buffer Full */ + +/* EC register offsets for LattePanda Sigma (BIOS 5.27) */ +#define EC_REG_FAN_RPM_HI 0x2E +#define EC_REG_FAN_RPM_LO 0x2F +#define EC_REG_TEMP_BOARD 0x60 +#define EC_REG_TEMP_CPU 0x70 +#define EC_REG_FAN_DUTY 0x93 + +/* + * EC polling uses udelay() because the EC typically responds within a + * few microseconds. The kernel's own ACPI EC driver (drivers/acpi/ec.c) + * likewise uses udelay() for busy-polling with a per-poll delay of 550us. + * + * usleep_range() was tested but caused EC protocol failures: the EC + * clears its status flags within microseconds, and sleeping for 50-100us + * between polls allowed the flags to transition past the expected state. + * + * The worst-case total busy-wait of 25ms covers EC recovery after errors. + * In practice the EC responds within 10us so the loop exits immediately. + */ +#define EC_TIMEOUT_US 25000 +#define EC_POLL_US 1 + +static bool force; +module_param(force, bool, 0444); +MODULE_PARM_DESC(force, + "Force loading on untested BIOS versions (default: false)"); + +static struct platform_device *lps_ec_pdev; + +static int ec_wait_ibf_clear(void) +{ + int i; + + for (i = 0; i < EC_TIMEOUT_US; i++) { + if (!(inb(EC_CMD_PORT) & EC_STATUS_IBF)) + return 0; + udelay(EC_POLL_US); + } + return -ETIMEDOUT; +} + +static int ec_wait_obf_set(void) +{ + int i; + + for (i = 0; i < EC_TIMEOUT_US; i++) { + if (inb(EC_CMD_PORT) & EC_STATUS_OBF) + return 0; + udelay(EC_POLL_US); + } + return -ETIMEDOUT; +} + +static int ec_read_reg(u8 reg, u8 *val) +{ + int ret; + + ret = ec_wait_ibf_clear(); + if (ret) + return ret; + + outb(EC_CMD_READ, EC_CMD_PORT); + + ret = ec_wait_ibf_clear(); + if (ret) + return ret; + + outb(reg, EC_DATA_PORT); + + ret = ec_wait_obf_set(); + if (ret) + return ret; + + *val = inb(EC_DATA_PORT); + return 0; +} + +/* + * Read a 16-bit big-endian value from two consecutive EC registers. + * + * The EC may update the register pair between reading the high and low + * bytes, which could produce a corrupted value if the high byte rolls + * over (e.g., 0x0100 -> 0x00FF read as 0x01FF). Guard against this by + * re-reading the high byte after reading the low byte. If the high byte + * changed, re-read the low byte to get a consistent pair. + * See also lm90_read16() which uses the same approach. + */ +static int ec_read_reg16(u8 reg_hi, u8 reg_lo, u16 *val) +{ + int ret; + u8 oldh, newh, lo; + + ret = ec_read_reg(reg_hi, &oldh); + if (ret) + return ret; + + ret = ec_read_reg(reg_lo, &lo); + if (ret) + return ret; + + ret = ec_read_reg(reg_hi, &newh); + if (ret) + return ret; + + if (oldh != newh) { + ret = ec_read_reg(reg_lo, &lo); + if (ret) + return ret; + } + + *val = ((u16)newh << 8) | lo; + return 0; +} + +static int +lps_ec_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, + const char **str) +{ + switch (type) { + case hwmon_fan: + *str = "CPU Fan"; + return 0; + case hwmon_temp: + *str = channel == 0 ? "Board Temp" : "CPU Temp"; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static umode_t +lps_ec_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_input || attr == hwmon_fan_label) + return 0444; + break; + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + default: + break; + } + return 0; +} + +static int +lps_ec_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + u16 rpm; + u8 v; + int ret; + + switch (type) { + case hwmon_fan: + if (attr != hwmon_fan_input) + return -EOPNOTSUPP; + ret = ec_read_reg16(EC_REG_FAN_RPM_HI, + EC_REG_FAN_RPM_LO, &rpm); + if (ret) + return ret; + *val = rpm; + return 0; + + case hwmon_temp: + if (attr != hwmon_temp_input) + return -EOPNOTSUPP; + ret = ec_read_reg(channel == 0 ? EC_REG_TEMP_BOARD + : EC_REG_TEMP_CPU, + &v); + if (ret) + return ret; + /* EC reports unsigned 8-bit temperature in degrees Celsius */ + *val = (unsigned long)v * 1000; + return 0; + + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info * const lps_ec_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_ops lps_ec_ops = { + .is_visible = lps_ec_is_visible, + .read = lps_ec_read, + .read_string = lps_ec_read_string, +}; + +static const struct hwmon_chip_info lps_ec_chip_info = { + .ops = &lps_ec_ops, + .info = lps_ec_info, +}; + +static int lps_ec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *hwmon; + u8 test; + int ret; + + if (!devm_request_region(dev, EC_DATA_PORT, 1, DRIVER_NAME)) + return dev_err_probe(dev, -EBUSY, + "Failed to request EC data port 0x%x\n", + EC_DATA_PORT); + + if (!devm_request_region(dev, EC_CMD_PORT, 1, DRIVER_NAME)) + return dev_err_probe(dev, -EBUSY, + "Failed to request EC cmd port 0x%x\n", + EC_CMD_PORT); + + /* Sanity check: verify EC is responsive */ + ret = ec_read_reg(EC_REG_FAN_DUTY, &test); + if (ret) + return dev_err_probe(dev, ret, + "EC not responding on ports 0x%x/0x%x\n", + EC_DATA_PORT, EC_CMD_PORT); + + hwmon = devm_hwmon_device_register_with_info(dev, DRIVER_NAME, NULL, + &lps_ec_chip_info, NULL); + if (IS_ERR(hwmon)) + return dev_err_probe(dev, PTR_ERR(hwmon), + "Failed to register hwmon device\n"); + + dev_info(dev, "EC hwmon registered (fan duty: %u%%)\n", test); + return 0; +} + +/* DMI table with strict BIOS version match (override with force=1) */ +static const struct dmi_system_id lps_ec_dmi_table[] = { + { + .ident = "LattePanda Sigma", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"), + DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"), + DMI_MATCH(DMI_BIOS_VERSION, "5.27"), + }, + }, + { } /* terminator */ +}; +MODULE_DEVICE_TABLE(dmi, lps_ec_dmi_table); + +/* Loose table (vendor + product only) for use with force=1 */ +static const struct dmi_system_id lps_ec_dmi_table_force[] = { + { + .ident = "LattePanda Sigma", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"), + DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"), + }, + }, + { } /* terminator */ +}; + +static struct platform_driver lps_ec_driver = { + .probe = lps_ec_probe, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init lps_ec_init(void) +{ + int ret; + + if (!dmi_check_system(lps_ec_dmi_table)) { + if (!force || !dmi_check_system(lps_ec_dmi_table_force)) + return -ENODEV; + pr_warn("%s: BIOS version not verified, loading due to force=1\n", + DRIVER_NAME); + } + + ret = platform_driver_register(&lps_ec_driver); + if (ret) + return ret; + + lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1, + NULL, 0); + if (IS_ERR(lps_ec_pdev)) { + platform_driver_unregister(&lps_ec_driver); + return PTR_ERR(lps_ec_pdev); + } + + return 0; +} + +static void __exit lps_ec_exit(void) +{ + platform_device_unregister(lps_ec_pdev); + platform_driver_unregister(&lps_ec_driver); +} + +module_init(lps_ec_init); +module_exit(lps_ec_exit); + +MODULE_AUTHOR("Mariano Abad "); +MODULE_DESCRIPTION("Hardware monitoring driver for LattePanda Sigma EC"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 932ca40b10e94923dc6447f8e9026838793fd188 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Tue, 3 Mar 2026 12:07:01 +0100 Subject: hwmon: (ina2xx) clean up unused define and outdated comment The list of supported chips in the header is incomplete and contains no other information not readily available. Remove the list and instead hint that the chips supported by this driver have 219/226 compatible register layout [unlike the ones supported by e.g. ina238]. Remove the unused INA226_DIE_ID define. Signed-off-by: Jonas Rebmann Link: https://lore.kernel.org/r/20260303-ina234-shift-v1-1-318c33ac4480@pengutronix.de [groeck: macro -> define in commit message] Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 836e15a5a780..6a2cebbb9f15 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -1,22 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Driver for Texas Instruments INA219, INA226 power monitor chips - * - * INA219: - * Zero Drift Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina219 - * - * INA220: - * Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina220 - * - * INA226: - * Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina226 - * - * INA230: - * Bi-directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina230 + * Driver for Texas Instruments INA219, INA226 and register-layout compatible + * current/power monitor chips with I2C Interface * * Copyright (C) 2012 Lothar Felten * Thanks to Jan Volkering @@ -49,7 +34,6 @@ /* INA226 register definitions */ #define INA226_MASK_ENABLE 0x06 #define INA226_ALERT_LIMIT 0x07 -#define INA226_DIE_ID 0xFF /* SY24655 register definitions */ #define SY24655_EIN 0x0A -- cgit v1.2.3 From eeca1114d1e2cc0eacaebb80f3f2afbaebfc60be Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Tue, 3 Mar 2026 12:07:02 +0100 Subject: hwmon: (ina2xx) Shift INA234 shunt and current registers The INA219 has the lowest three bits of the bus voltage register zero-reserved, the bus_voltage_shift ina2xx_config field was introduced to accommodate for that. The INA234 has four bits of the bus voltage, of the shunt voltage, and of the current registers zero-reserved but the latter two were implemented by choosing a 16x higher shunt_div instead of a separate field specifying a bit shift. This is possible because shunt voltage and current are divided by shunt_div, hence a 16x higher shunt_div results in a 16x smaller LSB for both the shunt voltage and the current register, perfectly accounting for the missing bit shift. For consistency and correctness, account for the reserved bits via shunt_voltage_shift and current_shift configuration fields as already done for voltage registers and use the conversion constants given in the INA234 datasheet. Signed-off-by: Jonas Rebmann Link: https://lore.kernel.org/r/20260303-ina234-shift-v1-2-318c33ac4480@pengutronix.de Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 6a2cebbb9f15..613ffb622b7c 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -135,9 +135,11 @@ struct ina2xx_config { bool has_update_interval; int calibration_value; int shunt_div; + int shunt_voltage_shift; int bus_voltage_shift; int bus_voltage_lsb; /* uV */ int power_lsb_factor; + int current_shift; }; struct ina2xx_data { @@ -156,59 +158,69 @@ static const struct ina2xx_config ina2xx_config[] = { .config_default = INA219_CONFIG_DEFAULT, .calibration_value = 4096, .shunt_div = 100, + .shunt_voltage_shift = 0, .bus_voltage_shift = 3, .bus_voltage_lsb = 4000, .power_lsb_factor = 20, .has_alerts = false, .has_ishunt = false, .has_power_average = false, + .current_shift = 0, .has_update_interval = false, }, [ina226] = { .config_default = INA226_CONFIG_DEFAULT, .calibration_value = 2048, .shunt_div = 400, + .shunt_voltage_shift = 0, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, .power_lsb_factor = 25, .has_alerts = true, .has_ishunt = false, .has_power_average = false, + .current_shift = 0, .has_update_interval = true, }, [ina234] = { .config_default = INA226_CONFIG_DEFAULT, .calibration_value = 2048, - .shunt_div = 400, /* 2.5 µV/LSB raw ADC reading from INA2XX_SHUNT_VOLTAGE */ + .shunt_div = 25, /* 2.5 µV/LSB raw ADC reading from INA2XX_SHUNT_VOLTAGE */ + .shunt_voltage_shift = 4, .bus_voltage_shift = 4, .bus_voltage_lsb = 25600, .power_lsb_factor = 32, .has_alerts = true, .has_ishunt = false, .has_power_average = false, + .current_shift = 4, .has_update_interval = true, }, [ina260] = { .config_default = INA260_CONFIG_DEFAULT, .shunt_div = 400, + .shunt_voltage_shift = 0, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, .power_lsb_factor = 8, .has_alerts = true, .has_ishunt = true, .has_power_average = false, + .current_shift = 0, .has_update_interval = true, }, [sy24655] = { .config_default = SY24655_CONFIG_DEFAULT, .calibration_value = 4096, .shunt_div = 400, + .shunt_voltage_shift = 0, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, .power_lsb_factor = 25, .has_alerts = true, .has_ishunt = false, .has_power_average = true, + .current_shift = 0, .has_update_interval = false, }, }; @@ -262,7 +274,8 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, switch (reg) { case INA2XX_SHUNT_VOLTAGE: /* signed register */ - val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div); + val = (s16)regval >> data->config->shunt_voltage_shift; + val = DIV_ROUND_CLOSEST(val, data->config->shunt_div); break; case INA2XX_BUS_VOLTAGE: val = (regval >> data->config->bus_voltage_shift) * @@ -274,7 +287,8 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, break; case INA2XX_CURRENT: /* signed register, result in mA */ - val = (s16)regval * data->current_lsb_uA; + val = ((s16)regval >> data->config->current_shift) * + data->current_lsb_uA; val = DIV_ROUND_CLOSEST(val, 1000); break; case INA2XX_CALIBRATION: @@ -368,6 +382,7 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) case INA2XX_SHUNT_VOLTAGE: val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div); val *= data->config->shunt_div; + val <<= data->config->shunt_voltage_shift; return clamp_val(val, 0, SHRT_MAX); case INA2XX_BUS_VOLTAGE: val = clamp_val(val, 0, 200000); @@ -382,6 +397,7 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) val = clamp_val(val, INT_MIN / 1000, INT_MAX / 1000); /* signed register, result in mA */ val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_uA); + val <<= data->config->current_shift; return clamp_val(val, SHRT_MIN, SHRT_MAX); default: /* programmer goofed */ -- cgit v1.2.3 From 8baa5fc554f342d35e679fdf6b375d0cbdc8c0b4 Mon Sep 17 00:00:00 2001 From: Amay Agarwal Date: Tue, 3 Mar 2026 20:54:52 +0530 Subject: hwmon: (tc74) Replace sprintf() with sysfs_emit() Replace sprintf() with sysfs_emit() when writing to sysfs buffers. sysfs_emit() performs proper bounds checking and is the preferred helper for sysfs output. No functional change intended Signed-off-by: Amay Agarwal Link: https://lore.kernel.org/r/20260303152456.35763-2-tt@turingtested.xyz Signed-off-by: Guenter Roeck --- drivers/hwmon/tc74.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/tc74.c b/drivers/hwmon/tc74.c index 9984373a25fb..7fb7b50ad1ad 100644 --- a/drivers/hwmon/tc74.c +++ b/drivers/hwmon/tc74.c @@ -92,7 +92,7 @@ static ssize_t temp_input_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%d\n", data->temp_input * 1000); + return sysfs_emit(buf, "%d\n", data->temp_input * 1000); } static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); -- cgit v1.2.3 From aeb651cc95e83ccf129ba9151dd22101619dea1e Mon Sep 17 00:00:00 2001 From: Amay Agarwal Date: Tue, 3 Mar 2026 20:54:53 +0530 Subject: hwmon: (max31722) Replace sprintf() with sysfs_emit() Replace sprintf() with sysfs_emit() when writing to sysfs buffers. sysfs_emit() performs proper bounds checking and is the preferred helper for sysfs output. No functional change intended. Signed-off-by: Amay Agarwal Link: https://lore.kernel.org/r/20260303152456.35763-3-tt@turingtested.xyz Signed-off-by: Guenter Roeck --- drivers/hwmon/max31722.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/max31722.c b/drivers/hwmon/max31722.c index 9a31ef388396..6c5c86c75c36 100644 --- a/drivers/hwmon/max31722.c +++ b/drivers/hwmon/max31722.c @@ -11,6 +11,7 @@ #include #include #include +#include #define MAX31722_REG_CFG 0x00 #define MAX31722_REG_TEMP_LSB 0x01 @@ -56,7 +57,7 @@ static ssize_t max31722_temp_show(struct device *dev, if (ret < 0) return ret; /* Keep 12 bits and multiply by the scale of 62.5 millidegrees/bit. */ - return sprintf(buf, "%d\n", (s16)le16_to_cpu(ret) * 125 / 32); + return sysfs_emit(buf, "%d\n", (s16)le16_to_cpu(ret) * 125 / 32); } static SENSOR_DEVICE_ATTR_RO(temp1_input, max31722_temp, 0); -- cgit v1.2.3 From 5de81d9b9ff6a26f3f7e75f0aa93b26cc81acd86 Mon Sep 17 00:00:00 2001 From: Amay Agarwal Date: Tue, 3 Mar 2026 20:54:54 +0530 Subject: hwmon: (ads7828) Replace sprintf() with sysfs_emit() Replace sprintf() with sysfs_emit() when writing to sysfs buffers. sysfs_emit() performs proper bounds checking and is the preferred helper for sysfs output. No functional change intended. Signed-off-by: Amay Agarwal Link: https://lore.kernel.org/r/20260303152456.35763-4-tt@turingtested.xyz [groeck: Fixed continuation line alignment] Signed-off-by: Guenter Roeck --- drivers/hwmon/ads7828.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ads7828.c b/drivers/hwmon/ads7828.c index 436637264056..7f43565ca284 100644 --- a/drivers/hwmon/ads7828.c +++ b/drivers/hwmon/ads7828.c @@ -62,8 +62,8 @@ static ssize_t ads7828_in_show(struct device *dev, if (err < 0) return err; - return sprintf(buf, "%d\n", - DIV_ROUND_CLOSEST(regval * data->lsb_resol, 1000)); + return sysfs_emit(buf, "%d\n", + DIV_ROUND_CLOSEST(regval * data->lsb_resol, 1000)); } static SENSOR_DEVICE_ATTR_RO(in0_input, ads7828_in, 0); -- cgit v1.2.3 From c9d468ba1bf222e61f886b46748696cd940e43a4 Mon Sep 17 00:00:00 2001 From: Amay Agarwal Date: Tue, 3 Mar 2026 20:54:55 +0530 Subject: hwmon: (max6650) Replace sprintf() with sysfs_emit() Replace sprintf() with sysfs_emit() when writing to sysfs buffers. sysfs_emit() performs proper bounds checking and is the preferred helper for sysfs output. No functional change intended. Signed-off-by: Amay Agarwal Link: https://lore.kernel.org/r/20260303152456.35763-5-tt@turingtested.xyz Signed-off-by: Guenter Roeck --- drivers/hwmon/max6650.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index 9649c6611d5f..56b8157885bb 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -27,6 +27,7 @@ #include #include #include +#include #include /* @@ -312,7 +313,7 @@ static ssize_t alarm_show(struct device *dev, mutex_unlock(&data->update_lock); } - return sprintf(buf, "%d\n", alarm); + return sysfs_emit(buf, "%d\n", alarm); } static SENSOR_DEVICE_ATTR_RO(gpio1_alarm, alarm, MAX6650_ALRM_GPIO1); -- cgit v1.2.3 From 987bf9bd9d6ceb2f6e6f73414e35cd573999fc3d Mon Sep 17 00:00:00 2001 From: Amay Agarwal Date: Tue, 3 Mar 2026 20:54:56 +0530 Subject: hwmon: (emc1403) Replace sprintf() with sysfs_emit() Replace sprintf() with sysfs_emit() when writing to sysfs buffers. sysfs_emit() performs proper bounds checking and is the preferred helper for sysfs output. No functional change intended. Signed-off-by: Amay Agarwal Link: https://lore.kernel.org/r/20260303152456.35763-6-tt@turingtested.xyz Signed-off-by: Guenter Roeck --- drivers/hwmon/emc1403.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index ccce948a4306..964a8cb278f1 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -40,7 +40,7 @@ static ssize_t power_state_show(struct device *dev, struct device_attribute *att retval = regmap_read(data->regmap, 0x03, &val); if (retval < 0) return retval; - return sprintf(buf, "%d\n", !!(val & BIT(6))); + return sysfs_emit(buf, "%d\n", !!(val & BIT(6))); } static ssize_t power_state_store(struct device *dev, struct device_attribute *attr, -- cgit v1.2.3 From a105ba85246f124e2a0b00c75c420bc4ece66620 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 4 Mar 2026 10:02:30 +0100 Subject: hwmon: (gpio-fan) Drop unneeded dependency on OF_GPIO OF_GPIO is selected automatically on all OF systems. Any symbols it controls also provide stubs so there's really no reason to select it explicitly. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260304-gpio-of-kconfig-v1-9-d597916e79e7@oss.qualcomm.com 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 e0cd83b4b715..8af80e17d25e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -775,7 +775,6 @@ config SENSORS_G762 config SENSORS_GPIO_FAN tristate "GPIO fan" - depends on OF_GPIO depends on GPIOLIB || COMPILE_TEST depends on THERMAL || THERMAL=n help -- cgit v1.2.3 From d782715ae7103bb0627093444dcbc01db6878e37 Mon Sep 17 00:00:00 2001 From: Nuno Sá Date: Wed, 4 Mar 2026 10:17:47 +0000 Subject: docs: hwmon: ltc4282: Fix scanned addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LTC4282 driver does not implement an I2C .detect() callback, meaning no I2C address scanning is performed. Update the documentation to reflect this by replacing the listed I2C address ranges with "-". Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20260304-hwmon-ltc4282-minor-improvs-v1-1-344622924d3a@analog.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/ltc4282.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/hwmon/ltc4282.rst b/Documentation/hwmon/ltc4282.rst index a87ec3564998..dd730207b141 100644 --- a/Documentation/hwmon/ltc4282.rst +++ b/Documentation/hwmon/ltc4282.rst @@ -9,8 +9,7 @@ Supported chips: Prefix: 'ltc4282' - Addresses scanned: - I2C 0x40 - 0x5A (7-bit) - Addresses scanned: - I2C 0x80 - 0xB4 with a step of 2 (8-bit) + Addresses scanned: - Datasheet: -- cgit v1.2.3 From d0cf6a161f5c777e74f3d27020ec24a852dafcab Mon Sep 17 00:00:00 2001 From: Nuno Sá Date: Wed, 4 Mar 2026 10:17:48 +0000 Subject: hwmon: (ltc4282) Add default rsense value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of failing probe when the "adi,rsense-nano-ohms" firmware property is not provided, default rsense to (NANO/MILLI), or 1 milli-Ohm. This allows the device to probe without requiring firmware properties, which might be useful for some high level testing. Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20260304-hwmon-ltc4282-minor-improvs-v1-2-344622924d3a@analog.com [groeck: Clarify that the default is 1 milli-Ohm. No functional change.] Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index db6534e67991..b9084424160d 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -1328,15 +1328,16 @@ static int ltc4282_setup(struct ltc4282_state *st, struct device *dev) if (ret) return ret; + /* default to 1 milli-ohm so we can probe without FW properties */ + st->rsense = 1 * (NANO / MILLI); ret = device_property_read_u32(dev, "adi,rsense-nano-ohms", &st->rsense); - if (ret) - return dev_err_probe(dev, ret, - "Failed to read adi,rsense-nano-ohms\n"); - if (st->rsense < CENTI) - return dev_err_probe(dev, -EINVAL, - "adi,rsense-nano-ohms too small (< %lu)\n", - CENTI); + if (!ret) { + if (st->rsense < CENTI) + return dev_err_probe(dev, -EINVAL, + "adi,rsense-nano-ohms too small (< %lu)\n", + CENTI); + } /* * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which -- cgit v1.2.3 From ee175259073320139aa8b94ab1225d98e8c6bcfc Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:26:52 +0100 Subject: hwmon: (asus_atk0110) Convert ACPI driver to a platform one In all cases in which a struct acpi_driver is used for binding a driver to an ACPI device object, a corresponding platform device is created by the ACPI core and that device is regarded as a proper representation of underlying hardware. Accordingly, a struct platform_driver should be used by driver code to bind to that device. There are multiple reasons why drivers should not bind directly to ACPI device objects [1]. Overall, it is better to bind drivers to platform devices than to their ACPI companions, so convert the asus_atk0110 ACPI driver to a platform one. After this change, the subordinate hwmon device will be registered under the platform device used for driver binding and messages will be printed relative to that device instead of its ACPI companion. While this is not expected to alter functionality, it changes sysfs layout and so it will be visible to user space. Link: https://lore.kernel.org/all/2396510.ElGaqSPkdT@rafael.j.wysocki/ [1] Signed-off-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/3691136.iIbC2pHGDl@rafael.j.wysocki Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_atk0110.c | 92 ++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/drivers/hwmon/asus_atk0110.c b/drivers/hwmon/asus_atk0110.c index c80350e499e9..5688ff5f7c28 100644 --- a/drivers/hwmon/asus_atk0110.c +++ b/drivers/hwmon/asus_atk0110.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #define ATK_HID "ATK0110" @@ -107,7 +108,7 @@ enum atk_pack_member { struct atk_data { struct device *hwmon_dev; acpi_handle atk_handle; - struct acpi_device *acpi_dev; + struct device *dev; bool old_interface; @@ -187,18 +188,17 @@ struct atk_acpi_input_buf { u32 param2; }; -static int atk_add(struct acpi_device *device); -static void atk_remove(struct acpi_device *device); +static int atk_probe(struct platform_device *pdev); +static void atk_remove(struct platform_device *pdev); static void atk_print_sensor(struct atk_data *data, union acpi_object *obj); static int atk_read_value(struct atk_sensor_data *sensor, u64 *value); -static struct acpi_driver atk_driver = { - .name = ATK_HID, - .class = "hwmon", - .ids = atk_ids, - .ops = { - .add = atk_add, - .remove = atk_remove, +static struct platform_driver atk_driver = { + .probe = atk_probe, + .remove = atk_remove, + .driver = { + .name = ATK_HID, + .acpi_match_table = atk_ids, }, }; @@ -327,7 +327,7 @@ static union acpi_object *atk_get_pack_member(struct atk_data *data, */ static int validate_hwmon_pack(struct atk_data *data, union acpi_object *obj) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *tmp; bool old_if = data->old_interface; int const expected_size = old_if ? _HWMON_OLD_PACK_SIZE : @@ -422,7 +422,7 @@ static char const *atk_sensor_type(union acpi_object *flags) static void atk_print_sensor(struct atk_data *data, union acpi_object *obj) { #ifdef DEBUG - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *flags; union acpi_object *name; union acpi_object *limit1; @@ -449,7 +449,7 @@ static void atk_print_sensor(struct atk_data *data, union acpi_object *obj) static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value) { struct atk_data *data = sensor->data; - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_object_list params; union acpi_object id; acpi_status status; @@ -487,7 +487,7 @@ static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value) static union acpi_object *atk_ggrp(struct atk_data *data, u16 mux) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_buffer buf; acpi_status ret; struct acpi_object_list params; @@ -523,7 +523,7 @@ static union acpi_object *atk_ggrp(struct atk_data *data, u16 mux) static union acpi_object *atk_gitm(struct atk_data *data, u64 id) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct atk_acpi_input_buf buf; union acpi_object tmp; struct acpi_object_list params; @@ -565,7 +565,7 @@ static union acpi_object *atk_gitm(struct atk_data *data, u64 id) static union acpi_object *atk_sitm(struct atk_data *data, struct atk_acpi_input_buf *buf) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_object_list params; union acpi_object tmp; struct acpi_buffer ret; @@ -602,7 +602,7 @@ static union acpi_object *atk_sitm(struct atk_data *data, static int atk_read_value_new(struct atk_sensor_data *sensor, u64 *value) { struct atk_data *data = sensor->data; - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *obj; struct atk_acpi_ret_buffer *buf; int err = 0; @@ -819,7 +819,7 @@ static void atk_debugfs_cleanup(struct atk_data *data) static int atk_add_sensor(struct atk_data *data, union acpi_object *obj) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *flags; union acpi_object *name; union acpi_object *limit1; @@ -937,7 +937,7 @@ static int atk_add_sensor(struct atk_data *data, union acpi_object *obj) static int atk_enumerate_old_hwmon(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_buffer buf; union acpi_object *pack; acpi_status status; @@ -1012,7 +1012,7 @@ static int atk_enumerate_old_hwmon(struct atk_data *data) static int atk_ec_present(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *pack; union acpi_object *ec; int ret; @@ -1058,7 +1058,7 @@ static int atk_ec_present(struct atk_data *data) static int atk_ec_enabled(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *obj; struct atk_acpi_ret_buffer *buf; int err; @@ -1084,7 +1084,7 @@ static int atk_ec_enabled(struct atk_data *data) static int atk_ec_ctl(struct atk_data *data, int enable) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *obj; struct atk_acpi_input_buf sitm; struct atk_acpi_ret_buffer *ec_ret; @@ -1113,7 +1113,7 @@ static int atk_ec_ctl(struct atk_data *data, int enable) static int atk_enumerate_new_hwmon(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *pack; int err; int i; @@ -1155,7 +1155,7 @@ static int atk_enumerate_new_hwmon(struct atk_data *data) static int atk_init_attribute_groups(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct atk_sensor_data *s; struct attribute **attrs; int i = 0; @@ -1181,7 +1181,7 @@ static int atk_init_attribute_groups(struct atk_data *data) static int atk_register_hwmon(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; dev_dbg(dev, "registering hwmon device\n"); data->hwmon_dev = hwmon_device_register_with_groups(dev, "atk0110", @@ -1193,7 +1193,7 @@ static int atk_register_hwmon(struct atk_data *data) static int atk_probe_if(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; acpi_handle ret; acpi_status status; int err = 0; @@ -1266,7 +1266,7 @@ static int atk_probe_if(struct atk_data *data) return err; } -static int atk_add(struct acpi_device *device) +static int atk_probe(struct platform_device *pdev) { acpi_status ret; int err; @@ -1274,14 +1274,14 @@ static int atk_add(struct acpi_device *device) union acpi_object *obj; struct atk_data *data; - dev_dbg(&device->dev, "adding...\n"); + dev_dbg(&pdev->dev, "adding...\n"); - data = devm_kzalloc(&device->dev, sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->acpi_dev = device; - data->atk_handle = device->handle; + data->dev = &pdev->dev; + data->atk_handle = ACPI_HANDLE(&pdev->dev); INIT_LIST_HEAD(&data->sensor_list); data->disable_ec = false; @@ -1289,13 +1289,13 @@ static int atk_add(struct acpi_device *device) ret = acpi_evaluate_object_typed(data->atk_handle, BOARD_ID, NULL, &buf, ACPI_TYPE_PACKAGE); if (ret != AE_OK) { - dev_dbg(&device->dev, "atk: method MBIF not found\n"); + dev_dbg(&pdev->dev, "atk: method MBIF not found\n"); } else { obj = buf.pointer; if (obj->package.count >= 2) { union acpi_object *id = &obj->package.elements[1]; if (id->type == ACPI_TYPE_STRING) - dev_dbg(&device->dev, "board ID = %s\n", + dev_dbg(&pdev->dev, "board ID = %s\n", id->string.pointer); } ACPI_FREE(buf.pointer); @@ -1303,21 +1303,21 @@ static int atk_add(struct acpi_device *device) err = atk_probe_if(data); if (err) { - dev_err(&device->dev, "No usable hwmon interface detected\n"); + dev_err(&pdev->dev, "No usable hwmon interface detected\n"); goto out; } if (data->old_interface) { - dev_dbg(&device->dev, "Using old hwmon interface\n"); + dev_dbg(&pdev->dev, "Using old hwmon interface\n"); err = atk_enumerate_old_hwmon(data); } else { - dev_dbg(&device->dev, "Using new hwmon interface\n"); + dev_dbg(&pdev->dev, "Using new hwmon interface\n"); err = atk_enumerate_new_hwmon(data); } if (err < 0) goto out; if (err == 0) { - dev_info(&device->dev, + dev_info(&pdev->dev, "No usable sensor detected, bailing out\n"); err = -ENODEV; goto out; @@ -1332,7 +1332,8 @@ static int atk_add(struct acpi_device *device) atk_debugfs_init(data); - device->driver_data = data; + platform_set_drvdata(pdev, data); + return 0; out: if (data->disable_ec) @@ -1340,12 +1341,11 @@ out: return err; } -static void atk_remove(struct acpi_device *device) +static void atk_remove(struct platform_device *pdev) { - struct atk_data *data = device->driver_data; - dev_dbg(&device->dev, "removing...\n"); + struct atk_data *data = platform_get_drvdata(pdev); - device->driver_data = NULL; + dev_dbg(&pdev->dev, "removing...\n"); atk_debugfs_cleanup(data); @@ -1353,7 +1353,7 @@ static void atk_remove(struct acpi_device *device) if (data->disable_ec) { if (atk_ec_ctl(data, 0)) - dev_err(&device->dev, "Failed to disable EC\n"); + dev_err(&pdev->dev, "Failed to disable EC\n"); } } @@ -1370,16 +1370,16 @@ static int __init atk0110_init(void) if (dmi_check_system(atk_force_new_if)) new_if = true; - ret = acpi_bus_register_driver(&atk_driver); + ret = platform_driver_register(&atk_driver); if (ret) - pr_info("acpi_bus_register_driver failed: %d\n", ret); + pr_info("platform_driver_register failed: %d\n", ret); return ret; } static void __exit atk0110_exit(void) { - acpi_bus_unregister_driver(&atk_driver); + platform_driver_unregister(&atk_driver); } module_init(atk0110_init); -- cgit v1.2.3 From 3ea44f8d76411c1f240062a16298668dd8907400 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:42 -0600 Subject: hwmon: (pmbus/bel-pfe) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has another benefit: * It doesn't need the i2c_device_id passed in so we do not need to have that forward declared, allowing us to remove that. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-2-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/bel-pfe.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/bel-pfe.c b/drivers/hwmon/pmbus/bel-pfe.c index ddf9d9a2958c..6499556f735b 100644 --- a/drivers/hwmon/pmbus/bel-pfe.c +++ b/drivers/hwmon/pmbus/bel-pfe.c @@ -88,13 +88,10 @@ static struct pmbus_driver_info pfe_driver_info[] = { }, }; -static const struct i2c_device_id pfe_device_id[]; - static int pfe_pmbus_probe(struct i2c_client *client) { - int model; + int model = (uintptr_t)i2c_get_match_data(client); - model = (int)i2c_match_id(pfe_device_id, client)->driver_data; client->dev.platform_data = &pfe_plat_data; /* -- cgit v1.2.3 From ea0c1194ffe89c9957bc9ca098a4d6bba14c7233 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:43 -0600 Subject: hwmon: (pmbus/ibm-cffps) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has another benefit: * It also checks for device match data, which allows for OF based probing. That means we do not have to manually check those first and can remove that check. As i2c_get_match_data() return NULL/0 on failure which also matches the enum for "cffps1", switch around the enum order so cffps_unknown is index 0 and existing behavior is preserved. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-3-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ibm-cffps.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index d05ef7a968a9..6c7256d997f4 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -58,7 +58,7 @@ enum { CFFPS_DEBUGFS_NUM_ENTRIES }; -enum versions { cffps1, cffps2, cffps_unknown }; +enum versions { cffps_unknown, cffps1, cffps2 }; struct ibm_cffps { enum versions version; @@ -482,19 +482,9 @@ MODULE_DEVICE_TABLE(i2c, ibm_cffps_id); static int ibm_cffps_probe(struct i2c_client *client) { int i, rc; - enum versions vs = cffps_unknown; + enum versions vs = (uintptr_t)i2c_get_match_data(client); struct dentry *debugfs; struct ibm_cffps *psu; - const void *md = of_device_get_match_data(&client->dev); - const struct i2c_device_id *id; - - if (md) { - vs = (uintptr_t)md; - } else { - id = i2c_match_id(ibm_cffps_id, client); - if (id) - vs = (enum versions)id->driver_data; - } if (vs == cffps_unknown) { u16 ccin_revision = 0; @@ -534,7 +524,7 @@ static int ibm_cffps_probe(struct i2c_client *client) } /* Set the client name to include the version number. */ - snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1); + snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs); } client->dev.platform_data = &ibm_cffps_pdata; -- cgit v1.2.3 From cfea13489052b164355f8da76fbd363992fb9752 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:44 -0600 Subject: hwmon: (pmbus/isl68137) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has a couple other benefits: * It doesn't need the i2c_device_id passed in so we do not need to have that forward declared, allowing us to remove that. * It also checks for device match data, which allows for OF and ACPI based probing. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-4-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/isl68137.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index 3e3a887aad05..48059ac4a08b 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -90,8 +90,6 @@ struct isl68137_data { #define to_isl68137_data(x) container_of(x, struct isl68137_data, info) -static const struct i2c_device_id raa_dmpvr_id[]; - static ssize_t isl68137_avs_enable_show_page(struct i2c_client *client, int page, char *buf) @@ -393,7 +391,7 @@ static int isl68137_probe(struct i2c_client *client) memcpy(&data->info, &raa_dmpvr_info, sizeof(data->info)); info = &data->info; - switch (i2c_match_id(raa_dmpvr_id, client)->driver_data) { + switch ((uintptr_t)i2c_get_match_data(client)) { case raa_dmpvr1_2rail: info->pages = 2; info->R[PSC_VOLTAGE_IN] = 3; -- cgit v1.2.3 From 13bb2cbceccb3c0b1a254e894c50d96efcfdd906 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:45 -0600 Subject: hwmon: (pmbus/max20730) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has another benefit: * It also checks for device match data. That means we do not have to manually check that first. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-5-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max20730.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/max20730.c b/drivers/hwmon/pmbus/max20730.c index 95869d198ecf..fe03164788df 100644 --- a/drivers/hwmon/pmbus/max20730.c +++ b/drivers/hwmon/pmbus/max20730.c @@ -715,10 +715,7 @@ static int max20730_probe(struct i2c_client *client) return -ENODEV; } - if (client->dev.of_node) - chip_id = (uintptr_t)of_device_get_match_data(dev); - else - chip_id = i2c_match_id(max20730_id, client)->driver_data; + chip_id = (uintptr_t)i2c_get_match_data(client); data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) -- cgit v1.2.3 From 6235e025343e3768c83e1a4a551589190b44916e Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:46 -0600 Subject: hwmon: (pmbus/max34440) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has another benefit: * It doesn't need the i2c_device_id passed in so we do not need to have that forward declared, allowing us to remove that. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-6-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max34440.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index 8ea4e68d4e9d..cc96bb22f8f5 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -71,8 +71,6 @@ struct max34440_data { #define to_max34440_data(x) container_of(x, struct max34440_data, info) -static const struct i2c_device_id max34440_id[]; - static int max34440_read_word_data(struct i2c_client *client, int page, int phase, int reg) { @@ -628,7 +626,7 @@ static int max34440_probe(struct i2c_client *client) GFP_KERNEL); if (!data) return -ENOMEM; - data->id = i2c_match_id(max34440_id, client)->driver_data; + data->id = (uintptr_t)i2c_get_match_data(client); data->info = max34440_info[data->id]; data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; -- cgit v1.2.3 From 241662082bc554fe9500d1258d70064d4a008905 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:47 -0600 Subject: hwmon: (pmbus) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has another benefit: * It doesn't need the i2c_device_id passed in so we do not need to have that forward declared, allowing us to remove that. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-7-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c index 920cd5408141..d1844c7a51ee 100644 --- a/drivers/hwmon/pmbus/pmbus.c +++ b/drivers/hwmon/pmbus/pmbus.c @@ -20,8 +20,6 @@ struct pmbus_device_info { u32 flags; }; -static const struct i2c_device_id pmbus_id[]; - /* * Find sensor groups and status registers on each page. */ @@ -174,7 +172,7 @@ static int pmbus_probe(struct i2c_client *client) if (!info) return -ENOMEM; - device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data; + device_info = (struct pmbus_device_info *)i2c_get_match_data(client); if (device_info->flags) { pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data), GFP_KERNEL); -- cgit v1.2.3 From 1ca93dd91b5072301d9f9dada7b2057178d8c738 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:48 -0600 Subject: hwmon: (pmbus/q54sj108a2) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has another benefit: * It also checks for device match data, which means we do not have to manually check that first. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-8-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/q54sj108a2.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c index d5d60a9af8c5..7e799f36f923 100644 --- a/drivers/hwmon/pmbus/q54sj108a2.c +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -292,10 +292,7 @@ static int q54sj108a2_probe(struct i2c_client *client) I2C_FUNC_SMBUS_BLOCK_DATA)) return -ENODEV; - if (client->dev.of_node) - chip_id = (enum chips)(unsigned long)of_device_get_match_data(dev); - else - chip_id = i2c_match_id(q54sj108a2_id, client)->driver_data; + chip_id = (enum chips)(uintptr_t)i2c_get_match_data(client); ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); if (ret < 0) { -- cgit v1.2.3 From 8ec910b1709ced46188c12afc9fd2ef6634d6245 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:49 -0600 Subject: hwmon: (pmbus/tps53679) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This is often used to then retrieve the matching driver_data. This can be done in one step with the helper i2c_get_match_data(). This helper has another benefit: * It also checks for device match data, which means we do not have to manually check that first. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-9-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps53679.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index ca2bfa25eb04..df2726659a4e 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -253,10 +253,7 @@ static int tps53679_probe(struct i2c_client *client) struct pmbus_driver_info *info; enum chips chip_id; - if (dev->of_node) - chip_id = (uintptr_t)of_device_get_match_data(dev); - else - chip_id = i2c_match_id(tps53679_id, client)->driver_data; + chip_id = (uintptr_t)i2c_get_match_data(client); info = devm_kmemdup(dev, &tps53679_info, sizeof(*info), GFP_KERNEL); if (!info) -- cgit v1.2.3 From 6f0a5e6d52234043ffd01324cae1d5da87607b5b Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:50 -0600 Subject: hwmon: (pmbus/fsp-3y) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This can be done instead with i2c_client_get_device_id() which doesn't need the i2c_device_id passed in so we do not need to have that forward declared, allowing us to move the i2c_device_id table down to its more natural spot with the other module info. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-10-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/fsp-3y.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/pmbus/fsp-3y.c b/drivers/hwmon/pmbus/fsp-3y.c index a4dc09e2ef75..cad4d2330003 100644 --- a/drivers/hwmon/pmbus/fsp-3y.c +++ b/drivers/hwmon/pmbus/fsp-3y.c @@ -222,12 +222,6 @@ static int fsp3y_detect(struct i2c_client *client) return -ENODEV; } -static const struct i2c_device_id fsp3y_id[] = { - {"ym2151e", ym2151e}, - {"yh5151e", yh5151e}, - { } -}; - static int fsp3y_probe(struct i2c_client *client) { struct fsp3y_data *data; @@ -242,7 +236,7 @@ static int fsp3y_probe(struct i2c_client *client) if (data->chip < 0) return data->chip; - id = i2c_match_id(fsp3y_id, client); + id = i2c_client_get_device_id(client); if (data->chip != id->driver_data) dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", id->name, (int)id->driver_data, data->chip); @@ -276,6 +270,11 @@ static int fsp3y_probe(struct i2c_client *client) return pmbus_do_probe(client, &data->info); } +static const struct i2c_device_id fsp3y_id[] = { + {"ym2151e", ym2151e}, + {"yh5151e", yh5151e}, + { } +}; MODULE_DEVICE_TABLE(i2c, fsp3y_id); static struct i2c_driver fsp3y_driver = { -- cgit v1.2.3 From 7e9d6299a2a952636af23cd8e9c8118f2e5efc22 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:51 -0600 Subject: hwmon: (pmbus/ltc2978) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This can instead be done with i2c_client_get_device_id(). For this driver functionality should not change. Switch over to remove the last couple users of the i2c_match_id() function from kernel. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-11-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ltc2978.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 8f5be520a15d..d69a5e675e80 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -733,7 +733,7 @@ static int ltc2978_probe(struct i2c_client *client) return chip_id; data->id = chip_id; - id = i2c_match_id(ltc2978_id, client); + id = i2c_client_get_device_id(client); if (data->id != id->driver_data) dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", -- cgit v1.2.3 From 9828c651c62525e20afb73d35cd89869e561145a Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 6 Mar 2026 11:16:52 -0600 Subject: hwmon: (pmbus/max16601) Remove use of i2c_match_id() The function i2c_match_id() is used to fetch the matching ID from the i2c_device_id table. This can instead be done with i2c_client_get_device_id(). For this driver functionality should not change. Switch over to remove the last couple users of the i2c_match_id() function from kernel. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20260306171652.951274-12-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max16601.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index d696e506aafb..36dc13424d92 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -318,7 +318,7 @@ static int max16601_probe(struct i2c_client *client) if (chip_id < 0) return chip_id; - id = i2c_match_id(max16601_id, client); + id = i2c_client_get_device_id(client); if (chip_id != id->driver_data) dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", -- cgit v1.2.3 From 4cd4489493531fce9046a135c6b99ce1abdb9053 Mon Sep 17 00:00:00 2001 From: Tabrez Ahmed Date: Sat, 7 Mar 2026 14:08:15 +0530 Subject: hwmon: (ads7871) Replace sprintf() with sysfs_emit() Use sysfs_emit() instead of sprintf() in the sysfs show function voltage_show() to comply with the preferred kernel interface for writing to sysfs buffers, which ensures PAGE_SIZE buffer limits are respected. No functional change intended. Note: Not runtime tested due to lack of hardware. Signed-off-by: Tabrez Ahmed Link: https://lore.kernel.org/r/20260307083815.12095-1-tabreztalks@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ads7871.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ads7871.c b/drivers/hwmon/ads7871.c index 5434c37969d7..b84426c940c5 100644 --- a/drivers/hwmon/ads7871.c +++ b/drivers/hwmon/ads7871.c @@ -124,7 +124,7 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da, val = ads7871_read_reg16(spi, REG_LS_BYTE); /*result in volts*10000 = (val/8192)*2.5*10000*/ val = ((val >> 2) * 25000) / 8192; - return sprintf(buf, "%d\n", val); + return sysfs_emit(buf, "%d\n", val); } else { return -1; } -- cgit v1.2.3 From 69694e9622118e546a735affc311ac8e652111d6 Mon Sep 17 00:00:00 2001 From: Tabrez Ahmed Date: Sat, 7 Mar 2026 17:22:26 +0530 Subject: hwmon: (ads7871) Fix incorrect error code in voltage_show The voltage_show() function returns -1 when the A/D conversion fails to complete within the polling loop. -1 maps to -EPERM (operation not permitted), which does not describe the actual failure. Replace this -1 error code with -ETIMEDOUT to better indicate the timeout condition to userspace. Drop the else block after return. Note: not runtime tested due to lack of hardware. Signed-off-by: Tabrez Ahmed Link: https://lore.kernel.org/r/20260307115226.25757-1-tabreztalks@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ads7871.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ads7871.c b/drivers/hwmon/ads7871.c index b84426c940c5..753bf77ce19b 100644 --- a/drivers/hwmon/ads7871.c +++ b/drivers/hwmon/ads7871.c @@ -125,9 +125,9 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da, /*result in volts*10000 = (val/8192)*2.5*10000*/ val = ((val >> 2) * 25000) / 8192; return sysfs_emit(buf, "%d\n", val); - } else { - return -1; } + + return -ETIMEDOUT; } static SENSOR_DEVICE_ATTR_RO(in0_input, voltage, 0); -- cgit v1.2.3 From 0a42986b65776759490ac960910651c1e4634b17 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Sat, 7 Mar 2026 14:45:19 -0800 Subject: hwmon: (pmbus/max31785) fix argument type for i2c_smbus_write_byte_data wrapper The local wrapper max31785_i2c_write_byte_data() declares its data parameter as u16 but passes it directly to i2c_smbus_write_byte_data() which takes u8. Fix the type to match the underlying API. No functional change; all current callers pass values that fit in u8. Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260307224517.38316-2-sanman.p211993@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max31785.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 1f94d38a1637..50073fe0c5e8 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -55,7 +55,7 @@ static inline void max31785_wait(const struct max31785_data *data) static int max31785_i2c_write_byte_data(struct i2c_client *client, struct max31785_data *driver_data, - int command, u16 data) + int command, u8 data) { int rc; -- cgit v1.2.3 From 487a9ab28fdd4df773b68c953e69a6f6ecc2fe68 Mon Sep 17 00:00:00 2001 From: Tabrez Ahmed Date: Sun, 8 Mar 2026 18:17:14 +0530 Subject: hwmon: (ads7871) Propagate SPI errors in voltage_show The voltage_show() function previously ignored negative error codes returned by the underlying SPI read/write functions. Because negative numbers have their most significant bits set in two's complement, a failed SPI read returning -EIO (-5) would incorrectly evaluate to true when masked with MUX_CNV_BM (0x80). This would cause the driver to enter the polling loop even when the SPI bus failed, eventually returning a misleading -ETIMEDOUT error to userspace instead of the actual hardware error. Furthermore, the return values of the initial SPI write and the final 16-bit SPI read were completely ignored. Add proper error checking after every SPI operation to ensure hardware failures are immediately propagated back to userspace. Suggested-by: Guenter Roeck Signed-off-by: Tabrez Ahmed Link: https://lore.kernel.org/r/20260308124714.84715-1-tabreztalks@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ads7871.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ads7871.c b/drivers/hwmon/ads7871.c index 753bf77ce19b..9bfdf9e6bcd7 100644 --- a/drivers/hwmon/ads7871.c +++ b/drivers/hwmon/ads7871.c @@ -104,10 +104,14 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da, */ /*MUX_M3_BM forces single ended*/ /*This is also where the gain of the PGA would be set*/ - ads7871_write_reg8(spi, REG_GAIN_MUX, - (MUX_CNV_BM | MUX_M3_BM | channel)); + ret = ads7871_write_reg8(spi, REG_GAIN_MUX, + (MUX_CNV_BM | MUX_M3_BM | channel)); + if (ret < 0) + return ret; ret = ads7871_read_reg8(spi, REG_GAIN_MUX); + if (ret < 0) + return ret; mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV); /* * on 400MHz arm9 platform the conversion @@ -116,12 +120,16 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da, while ((i < 2) && mux_cnv) { i++; ret = ads7871_read_reg8(spi, REG_GAIN_MUX); + if (ret < 0) + return ret; mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV); msleep_interruptible(1); } if (mux_cnv == 0) { val = ads7871_read_reg16(spi, REG_LS_BYTE); + if (val < 0) + return val; /*result in volts*10000 = (val/8192)*2.5*10000*/ val = ((val >> 2) * 25000) / 8192; return sysfs_emit(buf, "%d\n", val); -- cgit v1.2.3 From 32a7bdbd201be809c690c64a3a233c7bb640c48c Mon Sep 17 00:00:00 2001 From: Colin Huang Date: Mon, 16 Mar 2026 16:39:33 +0800 Subject: dt-bindings: trivial-devices: Add Delta Q54SN120A1 and Q54SW120A7 Add two additional Delta 1/4-brick DC/DC power modules, Q54SN120A1 and Q54SW120A7, to the trivial-devices list. Acked-by: Conor Dooley Signed-off-by: Colin Huang Link: https://lore.kernel.org/r/20260316-add-q54sn120a1-q54q54sw120a7-v2-1-60e6182cc4a7@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index ccac768f5380..03a274943223 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -101,6 +101,10 @@ properties: - delta,dps920ab # 1/4 Brick DC/DC Regulated Power Module - delta,q54sj108a2 + # 1300W 1/4 Brick DC/DC Regulated Power Module + - delta,q54sn120a1 + # 2000W 1/4 Brick DC/DC Regulated Power Module + - delta,q54sw120a7 # Devantech SRF02 ultrasonic ranger in I2C mode - devantech,srf02 # Devantech SRF08 ultrasonic ranger -- cgit v1.2.3 From ffa2ad0aa64697c29d501f76cb7b7187cbbb147a Mon Sep 17 00:00:00 2001 From: Colin Huang Date: Mon, 16 Mar 2026 16:39:34 +0800 Subject: hwmon: (pmbus) Add Delta Q54SN120A1 Q54SW120A7 chip Add the DELTA chips Q54SN120A1, Q54SW120A7 in q54sj108a2, 1/4 Brick DC/DC Regulated Power Module with PMBus support Signed-off-by: Colin Huang Link: https://lore.kernel.org/r/20260316-add-q54sn120a1-q54q54sw120a7-v2-2-60e6182cc4a7@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/q54sj108a2.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c index 7e799f36f923..a368cfa9d45a 100644 --- a/drivers/hwmon/pmbus/q54sj108a2.c +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -271,6 +271,8 @@ static const struct file_operations q54sj108a2_fops = { static const struct i2c_device_id q54sj108a2_id[] = { { "q54sj108a2", q54sj108a2 }, + { "q54sn120a1", q54sj108a2 }, + { "q54sw120a7", q54sj108a2 }, { }, }; @@ -280,6 +282,7 @@ static int q54sj108a2_probe(struct i2c_client *client) { struct device *dev = &client->dev; u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; enum chips chip_id; int ret, i; struct dentry *debugfs; @@ -313,8 +316,12 @@ static int q54sj108a2_probe(struct i2c_client *client) dev_err(dev, "Failed to read Manufacturer Model\n"); return ret; } - if (ret != 14 || strncmp(buf, "Q54SJ108A2", 10)) { - buf[ret] = '\0'; + buf[ret] = '\0'; + for (mid = q54sj108a2_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, buf, strlen(mid->name))) + break; + } + if (!mid->name[0]) { dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); return -ENODEV; } @@ -324,7 +331,10 @@ static int q54sj108a2_probe(struct i2c_client *client) dev_err(dev, "Failed to read Manufacturer Revision\n"); return ret; } - if (ret != 4 || buf[0] != 'S') { + /* + * accept manufacturer revision with optional NUL byte + */ + if (!(ret == 4 || ret == 5) || buf[0] != 'S') { buf[ret] = '\0'; dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf); return -ENODEV; @@ -401,6 +411,8 @@ static int q54sj108a2_probe(struct i2c_client *client) static const struct of_device_id q54sj108a2_of_match[] = { { .compatible = "delta,q54sj108a2", .data = (void *)q54sj108a2 }, + { .compatible = "delta,q54sn120a1", .data = (void *)q54sj108a2 }, + { .compatible = "delta,q54sw120a7", .data = (void *)q54sj108a2 }, { }, }; -- cgit v1.2.3 From 967ee29c103a44c6e584a5e37401968a69e54a0c Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Tue, 10 Mar 2026 00:24:56 +0800 Subject: dt-bindings: hwmon: moortec,mr75203: adapt multipleOf for T-Head TH1520 The G and J coefficients provided by T-Head TH1520 manual (which calls them A and C coefficients and calls H coefficient in the binding as B) have 1/100 degree Celsius precision (the values are 42.74 and -0.16 respectively), however the binding currently only allows coefficients as precise as 100 milli-Celsius (1/10 degree Celsius). Change the multipleOf value of these two coefficients to 10 (in the unit of milli-Celsius) to satisfy the need of TH1520. Signed-off-by: Icenowy Zheng Reviewed-by: Drew Fustini Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20260309162457.4128205-2-zhengxingda@iscas.ac.cn Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/moortec,mr75203.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/moortec,mr75203.yaml b/Documentation/devicetree/bindings/hwmon/moortec,mr75203.yaml index 56db2292f062..7d57c2934a8a 100644 --- a/Documentation/devicetree/bindings/hwmon/moortec,mr75203.yaml +++ b/Documentation/devicetree/bindings/hwmon/moortec,mr75203.yaml @@ -105,7 +105,7 @@ properties: G coefficient for temperature equation. Default for series 5 = 60000 Default for series 6 = 57400 - multipleOf: 100 + multipleOf: 10 minimum: 1000 $ref: /schemas/types.yaml#/definitions/uint32 @@ -131,7 +131,7 @@ properties: J coefficient for temperature equation. Default for series 5 = -100 Default for series 6 = 0 - multipleOf: 100 + multipleOf: 10 maximum: 0 $ref: /schemas/types.yaml#/definitions/int32 -- cgit v1.2.3 From 46fef8583daa1bf78fda7eaa523c64d4440322ac Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Mon, 9 Mar 2026 10:33:24 +0800 Subject: hwmon: (aspeed-g6-pwm-tach): remove redundant driver remove callback Drops the remove callback as it only asserts reset and the probe already registers a devres action (devm_add_action_or_reset()) to call aspeed_pwm_tach_reset_assert(). Fixes: 7e1449cd15d1 ("hwmon: (aspeed-g6-pwm-tacho): Support for ASPEED g6 PWM/Fan tach") Signed-off-by: Billy Tsai Link: https://lore.kernel.org/r/20260309-pwm_fixes-v2-1-ca9768e70470@aspeedtech.com Signed-off-by: Guenter Roeck --- drivers/hwmon/aspeed-g6-pwm-tach.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/hwmon/aspeed-g6-pwm-tach.c b/drivers/hwmon/aspeed-g6-pwm-tach.c index 44e1ecba205d..4f6e6d440dd4 100644 --- a/drivers/hwmon/aspeed-g6-pwm-tach.c +++ b/drivers/hwmon/aspeed-g6-pwm-tach.c @@ -517,13 +517,6 @@ static int aspeed_pwm_tach_probe(struct platform_device *pdev) return 0; } -static void aspeed_pwm_tach_remove(struct platform_device *pdev) -{ - struct aspeed_pwm_tach_data *priv = platform_get_drvdata(pdev); - - reset_control_assert(priv->reset); -} - static const struct of_device_id aspeed_pwm_tach_match[] = { { .compatible = "aspeed,ast2600-pwm-tach", @@ -537,7 +530,6 @@ MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match); static struct platform_driver aspeed_pwm_tach_driver = { .probe = aspeed_pwm_tach_probe, - .remove = aspeed_pwm_tach_remove, .driver = { .name = "aspeed-g6-pwm-tach", .of_match_table = aspeed_pwm_tach_match, -- cgit v1.2.3 From 709cc8ff1b6276c01cdd811578062d4b1deb3452 Mon Sep 17 00:00:00 2001 From: Dawei Liu Date: Wed, 18 Mar 2026 10:19:19 +0800 Subject: hwmon: (pmbus/isl68137) Remove unused enum chips The enum chips is not used anywhere in the driver. Device matching relies on the variants enum instead. Remove it to clean up the code. Signed-off-by: Dawei Liu Link: https://lore.kernel.org/r/20260318021921.75-2-dawei.liu.jy@renesas.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/isl68137.c | 46 ------------------------------------------ 1 file changed, 46 deletions(-) diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index 48059ac4a08b..62d476064bd2 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -23,52 +23,6 @@ #define RAA_DMPVR2_READ_VMON 0xc8 #define MAX_CHANNELS 4 -enum chips { - isl68137, - isl68220, - isl68221, - isl68222, - isl68223, - isl68224, - isl68225, - isl68226, - isl68227, - isl68229, - isl68233, - isl68239, - isl69222, - isl69223, - isl69224, - isl69225, - isl69227, - isl69228, - isl69234, - isl69236, - isl69239, - isl69242, - isl69243, - isl69247, - isl69248, - isl69254, - isl69255, - isl69256, - isl69259, - isl69260, - isl69268, - isl69269, - isl69298, - raa228000, - raa228004, - raa228006, - raa228228, - raa228244, - raa228246, - raa229001, - raa229004, - raa229141, - raa229621, -}; - enum variants { raa_dmpvr1_2rail, raa_dmpvr2_1rail, -- cgit v1.2.3 From dc4acb718ed9b292aec93b91ae95d71e85705c72 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Sat, 21 Mar 2026 18:11:30 +0000 Subject: hwmon: (pmbus) export pmbus_wait and pmbus_update_ts Export pmbus_wait() and pmbus_update_ts() so that PMBus device drivers which perform raw I2C transfers outside the core helpers can keep the PMBus core delay bookkeeping in sync. Move PMBUS_OP_WRITE and PMBUS_OP_PAGE_CHANGE from pmbus_core.c to pmbus.h so device drivers can pass the correct operation type flags to pmbus_update_ts(). This is needed by the max31785 driver, which performs raw i2c_transfer() calls for its 4-byte extended fan speed reads that cannot use the standard PMBus word read path. Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260321181052.27129-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 6 ++++++ drivers/hwmon/pmbus/pmbus_core.c | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 3ddcb742d289..deb556971a72 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -424,6 +424,10 @@ enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; #define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ #define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ +/* Operation type flags for pmbus_update_ts */ +#define PMBUS_OP_WRITE BIT(0) +#define PMBUS_OP_PAGE_CHANGE BIT(1) + struct pmbus_driver_info { int pages; /* Total number of pages */ u8 phases[PMBUS_PAGES]; /* Number of phases per page */ @@ -541,6 +545,8 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev, void pmbus_clear_cache(struct i2c_client *client); void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); +void pmbus_wait(struct i2c_client *client); +void pmbus_update_ts(struct i2c_client *client, int op); int pmbus_set_page(struct i2c_client *client, int page, int phase); int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 7d73abecc050..d82162b3c3b4 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -37,8 +37,7 @@ * The type of operation used for picking the delay between * successive pmbus operations. */ -#define PMBUS_OP_WRITE BIT(0) -#define PMBUS_OP_PAGE_CHANGE BIT(1) +/* PMBUS_OP_WRITE and PMBUS_OP_PAGE_CHANGE are defined in pmbus.h */ static int wp = -1; module_param(wp, int, 0444); @@ -179,7 +178,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update) EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); /* Some chips need a delay between accesses. */ -static void pmbus_wait(struct i2c_client *client) +void pmbus_wait(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); @@ -187,9 +186,10 @@ static void pmbus_wait(struct i2c_client *client) if (delay > 0) fsleep(delay); } +EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); /* Sets the last operation timestamp for pmbus_wait */ -static void pmbus_update_ts(struct i2c_client *client, int op) +void pmbus_update_ts(struct i2c_client *client, int op) { struct pmbus_data *data = i2c_get_clientdata(client); const struct pmbus_driver_info *info = data->info; @@ -203,6 +203,7 @@ static void pmbus_update_ts(struct i2c_client *client, int op) if (delay > 0) data->next_access_backoff = ktime_add_us(ktime_get(), delay); } +EXPORT_SYMBOL_NS_GPL(pmbus_update_ts, "PMBUS"); int pmbus_set_page(struct i2c_client *client, int page, int phase) { -- cgit v1.2.3 From 6be9b04ef3fff87bf329fecae4647196ffefc539 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Sat, 21 Mar 2026 18:11:47 +0000 Subject: hwmon: (pmbus/max31785) use access_delay for PMBus-mediated accesses The MAX31785 driver currently uses driver-local wrappers around PMBus core accesses to enforce a 250us inter-access delay needed to work around occasional NACKs from the device. This duplicates the PMBus core delay mechanism already provided by pmbus_driver_info.access_delay and adds unnecessary complexity. Replace the PMBus wrapper approach with access_delay for normal PMBus-mediated accesses, while keeping the minimal local delay handling needed for raw pre-probe SMBus operations. For the raw i2c_transfer() long-read path, use pmbus_wait() and pmbus_update_ts() to keep the PMBus core timing state consistent with the raw transfer. Also: - allow PMBUS_FAN_CONFIG_12 physical-page accesses to fall back to the PMBus core, while remapping only virtual pages - use pmbus_update_fan() directly for fan configuration updates - use the delayed raw read helper for MFR_REVISION during probe - add a final max31785_wait() before pmbus_do_probe() to bridge the timing gap between pre-probe accesses and PMBus core registration - rename 'virtual' to 'vpage', 'driver_data' to 'data', and drop the unused to_max31785_data() macro Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260321181052.27129-3-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max31785.c | 191 +++++++++++++---------------------------- 1 file changed, 60 insertions(+), 131 deletions(-) diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 50073fe0c5e8..260aa8fb50f1 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -31,8 +31,6 @@ struct max31785_data { struct pmbus_driver_info info; }; -#define to_max31785_data(x) container_of(x, struct max31785_data, info) - /* * MAX31785 Driver Workaround * @@ -40,9 +38,8 @@ struct max31785_data { * These issues are not indicated by the device itself, except for occasional * NACK responses during master transactions. No error bits are set in STATUS_BYTE. * - * To address this, we introduce a delay of 250us between consecutive accesses - * to the fan controller. This delay helps mitigate communication problems by - * allowing sufficient time between accesses. + * Keep minimal local delay handling for raw pre-probe SMBus accesses. + * Normal PMBus-mediated accesses use pmbus_driver_info.access_delay instead. */ static inline void max31785_wait(const struct max31785_data *data) { @@ -54,89 +51,40 @@ static inline void max31785_wait(const struct max31785_data *data) } static int max31785_i2c_write_byte_data(struct i2c_client *client, - struct max31785_data *driver_data, - int command, u8 data) + struct max31785_data *data, + int command, u8 value) { int rc; - max31785_wait(driver_data); - rc = i2c_smbus_write_byte_data(client, command, data); - driver_data->access = ktime_get(); + max31785_wait(data); + rc = i2c_smbus_write_byte_data(client, command, value); + data->access = ktime_get(); return rc; } static int max31785_i2c_read_word_data(struct i2c_client *client, - struct max31785_data *driver_data, + struct max31785_data *data, int command) { int rc; - max31785_wait(driver_data); + max31785_wait(data); rc = i2c_smbus_read_word_data(client, command); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_read_byte_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int command) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_read_byte_data(client, page, command); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_write_byte_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int command, u16 data) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_write_byte_data(client, page, command, data); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_read_word_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int phase, int command) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_read_word_data(client, page, phase, command); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_write_word_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int command, u16 data) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_write_word_data(client, page, command, data); - driver_data->access = ktime_get(); + data->access = ktime_get(); return rc; } static int max31785_read_byte_data(struct i2c_client *client, int page, int reg) { - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max31785_data *driver_data = to_max31785_data(info); - switch (reg) { case PMBUS_VOUT_MODE: return -ENOTSUPP; case PMBUS_FAN_CONFIG_12: - return _max31785_read_byte_data(client, driver_data, - page - MAX31785_NR_PAGES, - reg); + if (page < MAX31785_NR_PAGES) + return -ENODATA; + return pmbus_read_byte_data(client, + page - MAX31785_NR_PAGES, + reg); } return -ENODATA; @@ -178,7 +126,21 @@ static int max31785_read_long_data(struct i2c_client *client, int page, if (rc < 0) return rc; + /* + * Ensure the raw transfer is properly spaced from the + * preceding PMBus transaction. + */ + pmbus_wait(client); + rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + + /* + * Update PMBus core timing state for the raw transfer, even on error. + * Pass 0 as the operation mask since this is a raw read, intentionally + * neither PMBUS_OP_WRITE nor PMBUS_OP_PAGE_CHANGE. + */ + pmbus_update_ts(client, 0); + if (rc < 0) return rc; @@ -203,19 +165,16 @@ static int max31785_get_pwm(struct i2c_client *client, int page) return rv; } -static int max31785_get_pwm_mode(struct i2c_client *client, - struct max31785_data *driver_data, int page) +static int max31785_get_pwm_mode(struct i2c_client *client, int page) { int config; int command; - config = _max31785_read_byte_data(client, driver_data, page, - PMBUS_FAN_CONFIG_12); + config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); if (config < 0) return config; - command = _max31785_read_word_data(client, driver_data, page, 0xff, - PMBUS_FAN_COMMAND_1); + command = pmbus_read_word_data(client, page, 0xff, PMBUS_FAN_COMMAND_1); if (command < 0) return command; @@ -233,8 +192,6 @@ static int max31785_get_pwm_mode(struct i2c_client *client, static int max31785_read_word_data(struct i2c_client *client, int page, int phase, int reg) { - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max31785_data *driver_data = to_max31785_data(info); u32 val; int rv; @@ -263,7 +220,7 @@ static int max31785_read_word_data(struct i2c_client *client, int page, rv = max31785_get_pwm(client, page); break; case PMBUS_VIRT_PWM_ENABLE_1: - rv = max31785_get_pwm_mode(client, driver_data, page); + rv = max31785_get_pwm_mode(client, page); break; default: rv = -ENODATA; @@ -294,35 +251,7 @@ static inline u32 max31785_scale_pwm(u32 sensor_val) return (sensor_val * 100) / 255; } -static int max31785_update_fan(struct i2c_client *client, - struct max31785_data *driver_data, int page, - u8 config, u8 mask, u16 command) -{ - int from, rv; - u8 to; - - from = _max31785_read_byte_data(client, driver_data, page, - PMBUS_FAN_CONFIG_12); - if (from < 0) - return from; - - to = (from & ~mask) | (config & mask); - - if (to != from) { - rv = _max31785_write_byte_data(client, driver_data, page, - PMBUS_FAN_CONFIG_12, to); - if (rv < 0) - return rv; - } - - rv = _max31785_write_word_data(client, driver_data, page, - PMBUS_FAN_COMMAND_1, command); - - return rv; -} - -static int max31785_pwm_enable(struct i2c_client *client, - struct max31785_data *driver_data, int page, +static int max31785_pwm_enable(struct i2c_client *client, int page, u16 word) { int config = 0; @@ -351,23 +280,20 @@ static int max31785_pwm_enable(struct i2c_client *client, return -EINVAL; } - return max31785_update_fan(client, driver_data, page, config, - PB_FAN_1_RPM, rate); + return pmbus_update_fan(client, page, 0, config, + PB_FAN_1_RPM, rate); } static int max31785_write_word_data(struct i2c_client *client, int page, int reg, u16 word) { - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max31785_data *driver_data = to_max31785_data(info); - switch (reg) { case PMBUS_VIRT_PWM_1: - return max31785_update_fan(client, driver_data, page, 0, - PB_FAN_1_RPM, - max31785_scale_pwm(word)); + return pmbus_update_fan(client, page, 0, 0, + PB_FAN_1_RPM, + max31785_scale_pwm(word)); case PMBUS_VIRT_PWM_ENABLE_1: - return max31785_pwm_enable(client, driver_data, page, word); + return max31785_pwm_enable(client, page, word); default: break; } @@ -391,6 +317,7 @@ static const struct pmbus_driver_info max31785_info = { .read_byte_data = max31785_read_byte_data, .read_word_data = max31785_read_word_data, .write_byte = max31785_write_byte, + .access_delay = MAX31785_WAIT_DELAY_US, /* RPM */ .format[PSC_FAN] = direct, @@ -438,29 +365,29 @@ static const struct pmbus_driver_info max31785_info = { }; static int max31785_configure_dual_tach(struct i2c_client *client, - struct pmbus_driver_info *info) + struct max31785_data *data) { + struct pmbus_driver_info *info = &data->info; int ret; int i; - struct max31785_data *driver_data = to_max31785_data(info); for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) { - ret = max31785_i2c_write_byte_data(client, driver_data, + ret = max31785_i2c_write_byte_data(client, data, PMBUS_PAGE, i); if (ret < 0) return ret; - ret = max31785_i2c_read_word_data(client, driver_data, + ret = max31785_i2c_read_word_data(client, data, MFR_FAN_CONFIG); if (ret < 0) return ret; if (ret & MFR_FAN_CONFIG_DUAL_TACH) { - int virtual = MAX31785_NR_PAGES + i; + int vpage = MAX31785_NR_PAGES + i; - info->pages = virtual + 1; - info->func[virtual] |= PMBUS_HAVE_FAN12; - info->func[virtual] |= PMBUS_PAGE_VIRTUAL; + info->pages = vpage + 1; + info->func[vpage] |= PMBUS_HAVE_FAN12; + info->func[vpage] |= PMBUS_PAGE_VIRTUAL; } } @@ -471,7 +398,7 @@ static int max31785_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct pmbus_driver_info *info; - struct max31785_data *driver_data; + struct max31785_data *data; bool dual_tach = false; int ret; @@ -480,20 +407,20 @@ static int max31785_probe(struct i2c_client *client) I2C_FUNC_SMBUS_WORD_DATA)) return -ENODEV; - driver_data = devm_kzalloc(dev, sizeof(struct max31785_data), GFP_KERNEL); - if (!driver_data) + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) return -ENOMEM; - info = &driver_data->info; - driver_data->access = ktime_get(); + data->access = ktime_get(); + info = &data->info; *info = max31785_info; - ret = max31785_i2c_write_byte_data(client, driver_data, - PMBUS_PAGE, 255); + ret = max31785_i2c_write_byte_data(client, data, + PMBUS_PAGE, 0xff); if (ret < 0) return ret; - ret = i2c_smbus_read_word_data(client, MFR_REVISION); + ret = max31785_i2c_read_word_data(client, data, MFR_REVISION); if (ret < 0) return ret; @@ -509,11 +436,13 @@ static int max31785_probe(struct i2c_client *client) } if (dual_tach) { - ret = max31785_configure_dual_tach(client, info); + ret = max31785_configure_dual_tach(client, data); if (ret < 0) return ret; } + max31785_wait(data); + return pmbus_do_probe(client, info); } -- cgit v1.2.3 From 80306ba88ba6891f8cd16d4042beec69e9044de7 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Sat, 21 Mar 2026 18:12:05 +0000 Subject: hwmon: (pmbus/max31785) check for partial i2c_transfer in read_long_data i2c_transfer() returns the number of messages successfully transferred, not only a negative errno on failure. When called with two messages (write command byte followed by a read of the 4-byte response), a return value of 1 means the command write succeeded but the read did not complete. In that case, rspbuf remains uninitialized and must not be interpreted as valid data. Treat any return value other than ARRAY_SIZE(msg) as an error, and return -EIO for partial completion. Also return 0 on success instead of the message count, since the caller only needs to distinguish success from failure. Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260321181052.27129-4-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max31785.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 260aa8fb50f1..3caa76bcbeb5 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -141,13 +141,13 @@ static int max31785_read_long_data(struct i2c_client *client, int page, */ pmbus_update_ts(client, 0); - if (rc < 0) - return rc; + if (rc != ARRAY_SIZE(msg)) + return rc < 0 ? rc : -EIO; *data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) | (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8)); - return rc; + return 0; } static int max31785_get_pwm(struct i2c_client *client, int page) -- cgit v1.2.3 From 21518579cbdeb4e86a6fffbc3d52f52bd74ab87e Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sun, 22 Mar 2026 15:18:45 +0200 Subject: hwmon: (nct6775) Add ASUS X870/W480 to WMI monitoring list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boards such as * G15CE, * PRIME X870-P WIFI, * PRIME X870-P, * Pro WS W480-ACE, * ProArt X870E-CREATOR WIFI, * ROG CROSSHAIR X870E APEX, * ROG CROSSHAIR X870E DARK HERO, * ROG CROSSHAIR X870E EXTREME, * ROG CROSSHAIR X870E GLACIAL, * ROG CROSSHAIR X870E HERO BTF, * ROG CROSSHAIR X870E HERO, * ROG STRIX X870-A GAMING WIFI, * ROG STRIX X870-F GAMING WIFI, * ROG STRIX X870-I GAMING WIFI, * ROG STRIX X870E-E GAMING WIFI, * ROG STRIX X870E-E GAMING WIFI7 R2, * TUF GAMING X870-PLUS WIFI, * TUF GAMING X870-PRO WIFI7 W NEO, * TUF GAMING X870E-PLUS WIFI7, * W480/SYS, * X870 AYW GAMING WIFI W, * X870 MAX GAMING WIFI7 W, * X870 MAX GAMING WIFI7 have got a nct6775 chip, but by default there's no use of it because of resource conflict with WMI method. Add the boards to the WMI monitoring list. Link: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Tested-by: Tomáš Bžatek Tested-by: Theunis Scheepers Link: https://lore.kernel.org/r/20260322131848.6261-1-pauk.denis@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775-platform.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index 555029dfe713..1975399ac440 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -1159,6 +1159,7 @@ static const char * const asus_wmi_boards[] = { "Pro A520M-C", "Pro A520M-C II", "Pro B550M-C", + "Pro WS W480-ACE", "Pro WS X570-ACE", "ProArt B550-CREATOR", "ProArt X570-CREATOR WIFI", @@ -1258,6 +1259,7 @@ static const char * const asus_wmi_boards[] = { "TUF Z390-PRO GAMING", "TUF Z390M-PRO GAMING", "TUF Z390M-PRO GAMING (WI-FI)", + "W480/SYS", "WS Z390 PRO", "Z490-GUNDAM (WI-FI)", }; @@ -1270,6 +1272,7 @@ static const char * const asus_msi_boards[] = { "EX-B760M-V5 D4", "EX-H510M-V3", "EX-H610M-V3 D4", + "G15CE", "G15CF", "PRIME A620M-A", "PRIME B560-PLUS", @@ -1320,6 +1323,8 @@ static const char * const asus_msi_boards[] = { "PRIME X670-P", "PRIME X670-P WIFI", "PRIME X670E-PRO WIFI", + "PRIME X870-P", + "PRIME X870-P WIFI", "PRIME Z590-A", "PRIME Z590-P", "PRIME Z590-P WIFI", @@ -1362,11 +1367,18 @@ static const char * const asus_msi_boards[] = { "ProArt B660-CREATOR D4", "ProArt B760-CREATOR D4", "ProArt X670E-CREATOR WIFI", + "ProArt X870E-CREATOR WIFI", "ProArt Z690-CREATOR WIFI", "ProArt Z790-CREATOR WIFI", "ROG CROSSHAIR X670E EXTREME", "ROG CROSSHAIR X670E GENE", "ROG CROSSHAIR X670E HERO", + "ROG CROSSHAIR X870E APEX", + "ROG CROSSHAIR X870E DARK HERO", + "ROG CROSSHAIR X870E EXTREME", + "ROG CROSSHAIR X870E GLACIAL", + "ROG CROSSHAIR X870E HERO", + "ROG CROSSHAIR X870E HERO BTF", "ROG MAXIMUS XIII APEX", "ROG MAXIMUS XIII EXTREME", "ROG MAXIMUS XIII EXTREME GLACIAL", @@ -1404,6 +1416,11 @@ static const char * const asus_msi_boards[] = { "ROG STRIX X670E-E GAMING WIFI", "ROG STRIX X670E-F GAMING WIFI", "ROG STRIX X670E-I GAMING WIFI", + "ROG STRIX X870-A GAMING WIFI", + "ROG STRIX X870-F GAMING WIFI", + "ROG STRIX X870-I GAMING WIFI", + "ROG STRIX X870E-E GAMING WIFI", + "ROG STRIX X870E-E GAMING WIFI7 R2", "ROG STRIX X870E-H GAMING WIFI7", "ROG STRIX Z590-A GAMING WIFI", "ROG STRIX Z590-A GAMING WIFI II", @@ -1451,6 +1468,9 @@ static const char * const asus_msi_boards[] = { "TUF GAMING H770-PRO WIFI", "TUF GAMING X670E-PLUS", "TUF GAMING X670E-PLUS WIFI", + "TUF GAMING X870-PLUS WIFI", + "TUF GAMING X870-PRO WIFI7 W NEO", + "TUF GAMING X870E-PLUS WIFI7", "TUF GAMING Z590-PLUS", "TUF GAMING Z590-PLUS WIFI", "TUF GAMING Z690-PLUS", @@ -1460,6 +1480,9 @@ static const char * const asus_msi_boards[] = { "TUF GAMING Z790-PLUS D4", "TUF GAMING Z790-PLUS WIFI", "TUF GAMING Z790-PLUS WIFI D4", + "X870 AYW GAMING WIFI W", + "X870 MAX GAMING WIFI7", + "X870 MAX GAMING WIFI7 W", "Z590 WIFI GUNDAM EDITION", }; -- cgit v1.2.3 From 66b8eaf8def2d51dab49c4921b93f1bf1c7638dc Mon Sep 17 00:00:00 2001 From: Markus Hoffmann Date: Sun, 22 Mar 2026 10:33:01 +0000 Subject: hwmon: (it87) Add support for IT8689E Add support for the ITE IT8689E Super I/O chip. The IT8689E supports newer autopwm, 12mV ADC, 16-bit fans, six fans, six PWM channels, PWM frequency 2, six temperature inputs, AVCC3, temperature offset, and fan on/off control. Give it8689 its own GPIO configuration block in it87_find() rather than sharing the it8620/it8628 block. The shared block reads IT87_SIO_PINX2_REG and either marks IN3 as internal AVCC or skips IN9. Because it8689 declares FEAT_AVCC3, IN9 is already marked as always-internal before the GPIO block is reached; applying the PINX2 check would either create duplicate AVCC labels on IN3 and IN9 or incorrectly skip IN9. Also update Documentation/hwmon/it87.rst and drivers/hwmon/Kconfig to document the newly supported chip. Signed-off-by: Markus Hoffmann Link: https://lore.kernel.org/r/20260322103301.18112-1-markus@thehoffs.at Signed-off-by: Guenter Roeck --- Documentation/hwmon/it87.rst | 26 ++++++++++++++----- drivers/hwmon/Kconfig | 4 +-- drivers/hwmon/it87.c | 61 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/Documentation/hwmon/it87.rst b/Documentation/hwmon/it87.rst index 5cef4f265000..fc1c90b023ae 100644 --- a/Documentation/hwmon/it87.rst +++ b/Documentation/hwmon/it87.rst @@ -25,6 +25,14 @@ Supported chips: Datasheet: Not publicly available + * IT8689E + + Prefix: 'it8689' + + Addresses scanned: from Super I/O config space (8 I/O ports) + + Datasheet: Not publicly available + * IT8705F Prefix: 'it87' @@ -228,9 +236,9 @@ Description ----------- This driver implements support for the IT8603E, IT8620E, IT8623E, IT8628E, -IT8705F, IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8732F, -IT8758E, IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E, -IT8792E/IT8795E, IT87952E and SiS950 chips. +IT8689E, IT8705F, IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, +IT8728F, IT8732F, IT8758E, IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, +IT8786E, IT8790E, IT8792E/IT8795E, IT87952E and SiS950 chips. These chips are 'Super I/O chips', supporting floppy disks, infrared ports, joysticks and other miscellaneous stuff. For hardware monitoring, they @@ -274,6 +282,9 @@ of the fan is not supported (value 0 of pwmX_enable). The IT8620E and IT8628E are custom designs, hardware monitoring part is similar to IT8728F. It only supports 16-bit fan mode. Both chips support up to 6 fans. +The IT8689E supports newer autopwm, 12mV ADC, 16-bit fans, six fans, six PWM +channels, PWM frequency 2, six temperature inputs, and AVCC3 (in9). + The IT8790E, IT8792E/IT8795E and IT87952E support up to 3 fans. 16-bit fan mode is always enabled. @@ -301,12 +312,15 @@ of 0.016 volt. IT8603E, IT8721F/IT8758E and IT8728F can measure between 0 and 2.8 volts with a resolution of 0.0109 volt. The battery voltage in8 does not have limit registers. -On the IT8603E, IT8620E, IT8628E, IT8721F/IT8758E, IT8732F, IT8781F, IT8782F, -and IT8783E/F, some voltage inputs are internal and scaled inside the chip: +On the IT8603E, IT8620E, IT8628E, IT8689E, IT8721F/IT8758E, IT8732F, IT8781F, +IT8782F, and IT8783E/F, some voltage inputs are internal and scaled inside the +chip: + * in3 (optional) * in7 (optional for IT8781F, IT8782F, and IT8783E/F) * in8 (always) -* in9 (relevant for IT8603E only) +* in9 (IT8603E, IT8622E, and IT8689E: always AVCC3; others: optional) + The driver handles this transparently so user-space doesn't have to care. The VID lines (IT8712F/IT8716F/IT8718F/IT8720F) encode the core voltage value: diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8af80e17d25e..9d49cfd4ef3d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -909,8 +909,8 @@ config SENSORS_IT87 If you say yes here you get support for ITE IT8705F, IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8732F, IT8758E, IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E, - IT8603E, IT8620E, IT8623E, and IT8628E sensor chips, and the SiS950 - clone. + IT8603E, IT8620E, IT8623E, IT8628E, and IT8689E sensor chips, and + the SiS950 clone. This driver can also be built as a module. If so, the module will be called it87. diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 5cfb98a0512f..5fd310662ee4 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -16,6 +16,7 @@ * IT8622E Super I/O chip w/LPC interface * IT8623E Super I/O chip w/LPC interface * IT8628E Super I/O chip w/LPC interface + * IT8689E Super I/O chip w/LPC interface * IT8705F Super I/O chip w/LPC interface * IT8712F Super I/O chip w/LPC interface * IT8716F Super I/O chip w/LPC interface @@ -64,7 +65,7 @@ enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732, it8771, it8772, it8781, it8782, it8783, it8786, it8790, - it8792, it8603, it8620, it8622, it8628, it87952 }; + it8792, it8603, it8620, it8622, it8628, it8689, it87952 }; static struct platform_device *it87_pdev[2]; @@ -162,6 +163,7 @@ static inline void superio_exit(int ioreg, bool noexit) #define IT8622E_DEVID 0x8622 #define IT8623E_DEVID 0x8623 #define IT8628E_DEVID 0x8628 +#define IT8689E_DEVID 0x8689 #define IT87952E_DEVID 0x8695 /* Logical device 4 (Environmental Monitor) registers */ @@ -502,6 +504,15 @@ static const struct it87_devices it87_devices[] = { | FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF, .peci_mask = 0x07, }, + [it8689] = { + .name = "it8689", + .model = "IT8689E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL + | FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3 + | FEAT_FANCTL_ONOFF, + .smbus_bitmap = BIT(1) | BIT(2), + }, [it87952] = { .name = "it87952", .model = "IT87952E", @@ -2785,6 +2796,9 @@ static int __init it87_find(int sioaddr, unsigned short *address, case IT8628E_DEVID: sio_data->type = it8628; break; + case IT8689E_DEVID: + sio_data->type = it8689; + break; case IT87952E_DEVID: sio_data->type = it87952; break; @@ -3000,6 +3014,51 @@ static int __init it87_find(int sioaddr, unsigned short *address, else sio_data->skip_in |= BIT(9); + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8689) { + int reg; + + superio_select(sioaddr, GPIO); + + /* Check for pwm5 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO1_REG); + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(4); + + /* Check for fan4, fan5 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO2_REG); + if (!(reg & BIT(5))) + sio_data->skip_fan |= BIT(3); + if (!(reg & BIT(4))) + sio_data->skip_fan |= BIT(4); + + /* Check for pwm3, fan3 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(2); + if (reg & BIT(7)) + sio_data->skip_fan |= BIT(2); + + /* Check for pwm4 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO4_REG); + if (reg & BIT(2)) + sio_data->skip_pwm |= BIT(3); + + /* Check for pwm2, fan2 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO5_REG); + if (reg & BIT(1)) + sio_data->skip_pwm |= BIT(1); + if (reg & BIT(2)) + sio_data->skip_fan |= BIT(1); + /* Check for pwm6, fan6 */ + if (!(reg & BIT(7))) { + sio_data->skip_pwm |= BIT(5); + sio_data->skip_fan |= BIT(5); + } + + /* in9 (AVCC3) is always internal, no PINX2 check needed */ + sio_data->beep_pin = superio_inb(sioaddr, IT87_SIO_BEEP_PIN_REG) & 0x3f; } else if (sio_data->type == it8622) { -- cgit v1.2.3 From 9ca750883a13244b9d6f3607d6aa56002ae2e928 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Sun, 22 Mar 2026 18:26:16 +0200 Subject: hwmon: lm75: Add support for label Add support for label sysfs attribute similar to other hwmon devices. This is particularly useful for systems with multiple sensors on the same board, where identifying individual sensors is much easier since labels can be defined via device tree. Signed-off-by: Flaviu Nistor Link: https://lore.kernel.org/r/20260322162616.102229-1-flaviu.nistor@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lm75.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index eda93a8c23c9..f1a1e5b888f6 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -108,6 +108,7 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c, #define PCT2075_REG_IDLE 0x04 struct lm75_data { + const char *label; struct regmap *regmap; u16 orig_conf; u8 resolution; /* In bits, 9 to 16 */ @@ -363,6 +364,16 @@ static irqreturn_t lm75_alarm_handler(int irq, void *private) return IRQ_HANDLED; } +static int lm75_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct lm75_data *data = dev_get_drvdata(dev); + + *str = data->label; + + return 0; +} + static int lm75_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -534,6 +545,9 @@ static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type, switch (attr) { case hwmon_temp_input: return 0444; + case hwmon_temp_label: + /* Hide label node if label is not provided */ + return config_data->label ? 0444 : 0; case hwmon_temp_max: case hwmon_temp_max_hyst: return 0644; @@ -553,13 +567,14 @@ static const struct hwmon_channel_info * const lm75_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_ALARM), NULL }; static const struct hwmon_ops lm75_hwmon_ops = { .is_visible = lm75_is_visible, + .read_string = lm75_read_string, .read = lm75_read, .write = lm75_write, }; @@ -721,6 +736,9 @@ static int lm75_generic_probe(struct device *dev, const char *name, /* needed by custom regmap callbacks */ dev_set_drvdata(dev, data); + /* Save the connected input label if available */ + device_property_read_string(dev, "label", &data->label); + data->kind = kind; data->regmap = regmap; -- cgit v1.2.3 From b8960e3e2cd56aebce382bde5676cb8adfd4ad5e Mon Sep 17 00:00:00 2001 From: Dawei Liu Date: Wed, 25 Mar 2026 17:02:07 +0800 Subject: dt-bindings: hwmon: isl68137: Add compatible strings for RAA228942 and RAA228943 RAA228942 and RAA228943 are Renesas digital dual-output 16-phase (X+Y <= 16) PWM controllers with 2-rail non-TC driver configuration. At the PMBus hwmon interface level, they are compatible with existing 2-rail non-TC controllers and use renesas,raa228244 as fallback compatible Signed-off-by: Dawei Liu Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260325090208.857-2-dawei.liu.jy@renesas.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/isil,isl68137.yaml | 93 ++++++++++++---------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml index ae23a05375cb..8216cdf758d8 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml @@ -16,49 +16,56 @@ description: | properties: compatible: - enum: - - isil,isl68137 - - renesas,isl68220 - - renesas,isl68221 - - renesas,isl68222 - - renesas,isl68223 - - renesas,isl68224 - - renesas,isl68225 - - renesas,isl68226 - - renesas,isl68227 - - renesas,isl68229 - - renesas,isl68233 - - renesas,isl68239 - - renesas,isl69222 - - renesas,isl69223 - - renesas,isl69224 - - renesas,isl69225 - - renesas,isl69227 - - renesas,isl69228 - - renesas,isl69234 - - renesas,isl69236 - - renesas,isl69239 - - renesas,isl69242 - - renesas,isl69243 - - renesas,isl69247 - - renesas,isl69248 - - renesas,isl69254 - - renesas,isl69255 - - renesas,isl69256 - - renesas,isl69259 - - isil,isl69260 - - renesas,isl69268 - - isil,isl69269 - - renesas,isl69298 - - renesas,raa228000 - - renesas,raa228004 - - renesas,raa228006 - - renesas,raa228228 - - renesas,raa228244 - - renesas,raa228246 - - renesas,raa229001 - - renesas,raa229004 - - renesas,raa229621 + oneOf: + - enum: + - isil,isl68137 + - renesas,isl68220 + - renesas,isl68221 + - renesas,isl68222 + - renesas,isl68223 + - renesas,isl68224 + - renesas,isl68225 + - renesas,isl68226 + - renesas,isl68227 + - renesas,isl68229 + - renesas,isl68233 + - renesas,isl68239 + - renesas,isl69222 + - renesas,isl69223 + - renesas,isl69224 + - renesas,isl69225 + - renesas,isl69227 + - renesas,isl69228 + - renesas,isl69234 + - renesas,isl69236 + - renesas,isl69239 + - renesas,isl69242 + - renesas,isl69243 + - renesas,isl69247 + - renesas,isl69248 + - renesas,isl69254 + - renesas,isl69255 + - renesas,isl69256 + - renesas,isl69259 + - isil,isl69260 + - renesas,isl69268 + - isil,isl69269 + - renesas,isl69298 + - renesas,raa228000 + - renesas,raa228004 + - renesas,raa228006 + - renesas,raa228228 + - renesas,raa228244 + - renesas,raa228246 + - renesas,raa229001 + - renesas,raa229004 + - renesas,raa229621 + + - items: + - enum: + - renesas,raa228942 + - renesas,raa228943 + - const: renesas,raa228244 reg: maxItems: 1 -- cgit v1.2.3 From 7c760db74c9f30da7281c7f450d0676ec78ec3e6 Mon Sep 17 00:00:00 2001 From: Dawei Liu Date: Wed, 25 Mar 2026 17:02:08 +0800 Subject: hwmon: (pmbus/isl68137) Add support for Renesas RAA228942 and RAA228943 Add I2C device IDs for Renesas RAA228942 and RAA228943. At the Linux PMBus hwmon interface level currently supported by this driver, these devices are compatible with the existing 2-rail non-TC controllers, so devicetree will use fallback compatibles and no dedicated OF match entries are needed. Signed-off-by: Dawei Liu Link: https://lore.kernel.org/r/20260325090208.857-3-dawei.liu.jy@renesas.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/isl68137.rst | 20 ++++++++++++++++++++ drivers/hwmon/pmbus/isl68137.c | 2 ++ 2 files changed, 22 insertions(+) diff --git a/Documentation/hwmon/isl68137.rst b/Documentation/hwmon/isl68137.rst index e77f582c2850..0ce20d09164f 100644 --- a/Documentation/hwmon/isl68137.rst +++ b/Documentation/hwmon/isl68137.rst @@ -394,6 +394,26 @@ Supported chips: Provided by Renesas upon request and NDA + * Renesas RAA228942 + + Prefix: 'raa228942' + + Addresses scanned: - + + Datasheet: + + Provided by Renesas upon request and NDA + + * Renesas RAA228943 + + Prefix: 'raa228943' + + Addresses scanned: - + + Datasheet: + + Provided by Renesas upon request and NDA + * Renesas RAA229001 Prefix: 'raa229001' diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index 62d476064bd2..21d047b577a4 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -450,6 +450,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = { {"raa228228", raa_dmpvr2_2rail_nontc}, {"raa228244", raa_dmpvr2_2rail_nontc}, {"raa228246", raa_dmpvr2_2rail_nontc}, + {"raa228942", raa_dmpvr2_2rail_nontc}, + {"raa228943", raa_dmpvr2_2rail_nontc}, {"raa229001", raa_dmpvr2_2rail}, {"raa229004", raa_dmpvr2_2rail}, {"raa229141", raa_dmpvr2_2rail_pmbus}, -- cgit v1.2.3 From 1814f4d3ff358277a5b6957e7f133c2812dc80ec Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 20 Mar 2026 07:18:37 -0700 Subject: hwmon: (pmbus) Add support for guarded PMBus lock Add support for guard(pmbus_lock)() and scoped_guard(pmbus_lock)() to be able to simplify the PMBus code. Also introduce pmbus_lock() as pre-requisite for supporting guard(). Reviewed-by: Sanman Pradhan Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 5 +++++ drivers/hwmon/pmbus/pmbus_core.c | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index deb556971a72..23e3eda58870 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -10,6 +10,7 @@ #define PMBUS_H #include +#include #include /* @@ -569,7 +570,11 @@ int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, enum pmbus_fan_mode mode); int pmbus_lock_interruptible(struct i2c_client *client); +void pmbus_lock(struct i2c_client *client); void pmbus_unlock(struct i2c_client *client); + +DEFINE_GUARD(pmbus_lock, struct i2c_client *, pmbus_lock(_T), pmbus_unlock(_T)) + int pmbus_update_fan(struct i2c_client *client, int page, int id, u8 config, u8 mask, u16 command); struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index d82162b3c3b4..0500e92d7732 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -3876,6 +3876,14 @@ struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) } EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, "PMBUS"); +void pmbus_lock(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); +} +EXPORT_SYMBOL_NS_GPL(pmbus_lock, "PMBUS"); + int pmbus_lock_interruptible(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); -- cgit v1.2.3 From bd1c178affd7d1ca86eaf97cf797e0d15e57eb0a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 20 Mar 2026 07:45:55 -0700 Subject: hwmon: (pmbus_core) Use guard() for mutex protection Simplify the code by using guard() and scoped_guard() instead of mutex_lock()/mutex_unlock() sequences. This patch changes semantics for debugfs accesses. Previously, those used mutex_lock_interruptible() and not mutex_lock(). This change is intentional and should have little if any impact since locks should not be held for a significant amount of time and debugfs accesses are less critical than sysfs accesses (which never used interruptable locks). Reviewed-by: Sanman Pradhan Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 277 +++++++++++++++------------------------ 1 file changed, 107 insertions(+), 170 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 0500e92d7732..e8fdd799c71c 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1161,12 +1161,11 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, int ret, status; u16 regval; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + status = pmbus_get_status(client, page, reg); - if (status < 0) { - ret = status; - goto unlock; - } + if (status < 0) + return status; if (s1) pmbus_update_sensor_data(client, s1); @@ -1178,7 +1177,7 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, if (data->revision >= PMBUS_REV_12) { ret = _pmbus_write_byte_data(client, page, reg, regval); if (ret) - goto unlock; + return ret; } else { pmbus_clear_fault_page(client, page); } @@ -1186,14 +1185,10 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, if (s1 && s2) { s64 v1, v2; - if (s1->data < 0) { - ret = s1->data; - goto unlock; - } - if (s2->data < 0) { - ret = s2->data; - goto unlock; - } + if (s1->data < 0) + return s1->data; + if (s2->data < 0) + return s2->data; v1 = pmbus_reg2data(data, s1); v2 = pmbus_reg2data(data, s2); @@ -1201,8 +1196,6 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, } else { ret = !!regval; } -unlock: - mutex_unlock(&data->update_lock); return ret; } @@ -1232,16 +1225,16 @@ static ssize_t pmbus_show_sensor(struct device *dev, struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); struct pmbus_data *data = i2c_get_clientdata(client); - ssize_t ret; + s64 val; - mutex_lock(&data->update_lock); - pmbus_update_sensor_data(client, sensor); - if (sensor->data < 0) - ret = sensor->data; - else - ret = sysfs_emit(buf, "%lld\n", pmbus_reg2data(data, sensor)); - mutex_unlock(&data->update_lock); - return ret; + scoped_guard(pmbus_lock, client) { + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + return sensor->data; + val = pmbus_reg2data(data, sensor); + } + + return sysfs_emit(buf, "%lld\n", val); } static ssize_t pmbus_set_sensor(struct device *dev, @@ -1251,7 +1244,6 @@ static ssize_t pmbus_set_sensor(struct device *dev, struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_data *data = i2c_get_clientdata(client); struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); - ssize_t rv = count; s64 val; int ret; u16 regval; @@ -1259,15 +1251,15 @@ static ssize_t pmbus_set_sensor(struct device *dev, if (kstrtos64(buf, 10, &val) < 0) return -EINVAL; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + regval = pmbus_data2reg(data, sensor, val); ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval); if (ret < 0) - rv = ret; - else - sensor->data = -ENODATA; - mutex_unlock(&data->update_lock); - return rv; + return ret; + + sensor->data = -ENODATA; + return count; } static ssize_t pmbus_show_label(struct device *dev, @@ -1369,7 +1361,7 @@ static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp) struct pmbus_data *pmbus_data = tdata->pmbus_data; struct i2c_client *client = to_i2c_client(pmbus_data->dev); struct device *dev = pmbus_data->hwmon_dev; - int ret = 0; + int _temp; if (!dev) { /* May not even get to hwmon yet */ @@ -1377,15 +1369,15 @@ static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp) return 0; } - mutex_lock(&pmbus_data->update_lock); - pmbus_update_sensor_data(client, sensor); - if (sensor->data < 0) - ret = sensor->data; - else - *temp = (int)pmbus_reg2data(pmbus_data, sensor); - mutex_unlock(&pmbus_data->update_lock); + scoped_guard(pmbus_lock, client) { + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + return sensor->data; + _temp = (int)pmbus_reg2data(pmbus_data, sensor); + } - return ret; + *temp = _temp; + return 0; } static const struct thermal_zone_device_ops pmbus_thermal_ops = { @@ -2417,13 +2409,12 @@ static ssize_t pmbus_show_samples(struct device *dev, int val; struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_samples_reg *reg = to_samples_reg(devattr); - struct pmbus_data *data = i2c_get_clientdata(client); - mutex_lock(&data->update_lock); - val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg); - mutex_unlock(&data->update_lock); - if (val < 0) - return val; + scoped_guard(pmbus_lock, client) { + val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg); + if (val < 0) + return val; + } return sysfs_emit(buf, "%d\n", val); } @@ -2436,14 +2427,13 @@ static ssize_t pmbus_set_samples(struct device *dev, long val; struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_samples_reg *reg = to_samples_reg(devattr); - struct pmbus_data *data = i2c_get_clientdata(client); if (kstrtol(buf, 0, &val) < 0) return -EINVAL; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + ret = _pmbus_write_word_data(client, reg->page, reg->attr->reg, val); - mutex_unlock(&data->update_lock); return ret ? : count; } @@ -2955,14 +2945,9 @@ static int _pmbus_is_enabled(struct i2c_client *client, u8 page) static int __maybe_unused pmbus_is_enabled(struct i2c_client *client, u8 page) { - struct pmbus_data *data = i2c_get_clientdata(client); - int ret; + guard(pmbus_lock)(client); - mutex_lock(&data->update_lock); - ret = _pmbus_is_enabled(client, page); - mutex_unlock(&data->update_lock); - - return ret; + return _pmbus_is_enabled(client, page); } #define to_dev_attr(_dev_attr) \ @@ -2992,14 +2977,13 @@ static void pmbus_notify(struct pmbus_data *data, int page, int reg, int flags) } } -static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flags, +static int _pmbus_get_flags(struct i2c_client *client, u8 page, unsigned int *flags, unsigned int *event, bool notify) { + struct pmbus_data *data = i2c_get_clientdata(client); int i, status; const struct pmbus_status_category *cat; const struct pmbus_status_assoc *bit; - struct device *dev = data->dev; - struct i2c_client *client = to_i2c_client(dev); int func = data->info->func[page]; *flags = 0; @@ -3075,16 +3059,12 @@ static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flag return 0; } -static int __maybe_unused pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flags, +static int __maybe_unused pmbus_get_flags(struct i2c_client *client, u8 page, unsigned int *flags, unsigned int *event, bool notify) { - int ret; - - mutex_lock(&data->update_lock); - ret = _pmbus_get_flags(data, page, flags, event, notify); - mutex_unlock(&data->update_lock); + guard(pmbus_lock)(client); - return ret; + return _pmbus_get_flags(client, page, flags, event, notify); } #if IS_ENABLED(CONFIG_REGULATOR) @@ -3100,17 +3080,13 @@ static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); - int ret; - mutex_lock(&data->update_lock); - ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION, - PB_OPERATION_CONTROL_ON, - enable ? PB_OPERATION_CONTROL_ON : 0); - mutex_unlock(&data->update_lock); + guard(pmbus_lock)(client); - return ret; + return pmbus_update_byte_data(client, page, PMBUS_OPERATION, + PB_OPERATION_CONTROL_ON, + enable ? PB_OPERATION_CONTROL_ON : 0); } static int pmbus_regulator_enable(struct regulator_dev *rdev) @@ -3127,54 +3103,41 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_data *data = i2c_get_clientdata(client); int event; - return pmbus_get_flags(data, rdev_get_id(rdev), flags, &event, false); + return pmbus_get_flags(client, rdev_get_id(rdev), flags, &event, false); } static int pmbus_regulator_get_status(struct regulator_dev *rdev) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); int status, ret; int event; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + status = pmbus_get_status(client, page, PMBUS_STATUS_WORD); - if (status < 0) { - ret = status; - goto unlock; - } + if (status < 0) + return status; - if (status & PB_STATUS_OFF) { - ret = REGULATOR_STATUS_OFF; - goto unlock; - } + if (status & PB_STATUS_OFF) + return REGULATOR_STATUS_OFF; /* If regulator is ON & reports power good then return ON */ - if (!(status & PB_STATUS_POWER_GOOD_N)) { - ret = REGULATOR_STATUS_ON; - goto unlock; - } + if (!(status & PB_STATUS_POWER_GOOD_N)) + return REGULATOR_STATUS_ON; - ret = _pmbus_get_flags(data, rdev_get_id(rdev), &status, &event, false); + ret = _pmbus_get_flags(client, rdev_get_id(rdev), &status, &event, false); if (ret) - goto unlock; + return ret; if (status & (REGULATOR_ERROR_UNDER_VOLTAGE | REGULATOR_ERROR_OVER_CURRENT | - REGULATOR_ERROR_REGULATION_OUT | REGULATOR_ERROR_FAIL | REGULATOR_ERROR_OVER_TEMP)) { - ret = REGULATOR_STATUS_ERROR; - goto unlock; - } - - ret = REGULATOR_STATUS_UNDEFINED; + REGULATOR_ERROR_REGULATION_OUT | REGULATOR_ERROR_FAIL | REGULATOR_ERROR_OVER_TEMP)) + return REGULATOR_STATUS_ERROR; -unlock: - mutex_unlock(&data->update_lock); - return ret; + return REGULATOR_STATUS_UNDEFINED; } static int pmbus_regulator_get_low_margin(struct i2c_client *client, int page) @@ -3239,19 +3202,16 @@ static int pmbus_regulator_get_voltage(struct regulator_dev *rdev) .class = PSC_VOLTAGE_OUT, .convert = true, }; - int ret; + int voltage; - mutex_lock(&data->update_lock); - s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); - if (s.data < 0) { - ret = s.data; - goto unlock; + scoped_guard(pmbus_lock, client) { + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); + if (s.data < 0) + return s.data; + voltage = (int)pmbus_reg2data(data, &s); } - ret = (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */ -unlock: - mutex_unlock(&data->update_lock); - return ret; + return voltage * 1000; /* unit is uV */ } static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, @@ -3268,22 +3228,18 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, }; int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */ int low, high; - int ret; *selector = 0; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + low = pmbus_regulator_get_low_margin(client, s.page); - if (low < 0) { - ret = low; - goto unlock; - } + if (low < 0) + return low; high = pmbus_regulator_get_high_margin(client, s.page); - if (high < 0) { - ret = high; - goto unlock; - } + if (high < 0) + return high; /* Make sure we are within margins */ if (low > val) @@ -3293,10 +3249,7 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, val = pmbus_data2reg(data, &s, val); - ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); -unlock: - mutex_unlock(&data->update_lock); - return ret; + return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); } static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, @@ -3306,7 +3259,6 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_data *data = i2c_get_clientdata(client); int val, low, high; - int ret; if (data->flags & PMBUS_VOUT_PROTECTED) return 0; @@ -3319,29 +3271,20 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, val = DIV_ROUND_CLOSEST(rdev->desc->min_uV + (rdev->desc->uV_step * selector), 1000); /* convert to mV */ - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev)); - if (low < 0) { - ret = low; - goto unlock; - } + if (low < 0) + return low; high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev)); - if (high < 0) { - ret = high; - goto unlock; - } + if (high < 0) + return high; - if (val >= low && val <= high) { - ret = val * 1000; /* unit is uV */ - goto unlock; - } + if (val >= low && val <= high) + return val * 1000; /* unit is uV */ - ret = 0; -unlock: - mutex_unlock(&data->update_lock); - return ret; + return 0; } const struct regulator_ops pmbus_regulator_ops = { @@ -3477,16 +3420,16 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) struct i2c_client *client = to_i2c_client(data->dev); int i, status, event; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + for (i = 0; i < data->info->pages; i++) { - _pmbus_get_flags(data, i, &status, &event, true); + _pmbus_get_flags(client, i, &status, &event, true); if (event) pmbus_regulator_notify(data, i, event); } pmbus_clear_faults(client); - mutex_unlock(&data->update_lock); return IRQ_HANDLED; } @@ -3542,15 +3485,13 @@ static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */ static int pmbus_debugfs_get(void *data, u64 *val) { - int rc; struct pmbus_debugfs_entry *entry = data; - struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + struct i2c_client *client = entry->client; + int rc; - rc = mutex_lock_interruptible(&pdata->update_lock); - if (rc) - return rc; - rc = _pmbus_read_byte_data(entry->client, entry->page, entry->reg); - mutex_unlock(&pdata->update_lock); + guard(pmbus_lock)(client); + + rc = _pmbus_read_byte_data(client, entry->page, entry->reg); if (rc < 0) return rc; @@ -3563,15 +3504,14 @@ DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, static int pmbus_debugfs_get_status(void *data, u64 *val) { - int rc; struct pmbus_debugfs_entry *entry = data; - struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + struct i2c_client *client = entry->client; + struct pmbus_data *pdata = i2c_get_clientdata(client); + int rc; - rc = mutex_lock_interruptible(&pdata->update_lock); - if (rc) - return rc; - rc = pdata->read_status(entry->client, entry->page); - mutex_unlock(&pdata->update_lock); + guard(pmbus_lock)(client); + + rc = pdata->read_status(client, entry->page); if (rc < 0) return rc; @@ -3587,17 +3527,14 @@ static ssize_t pmbus_debugfs_block_read(struct file *file, char __user *buf, { int rc; struct pmbus_debugfs_entry *entry = file->private_data; - struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + struct i2c_client *client = entry->client; char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; - rc = mutex_lock_interruptible(&pdata->update_lock); - if (rc) - return rc; - rc = pmbus_read_block_data(entry->client, entry->page, entry->reg, - data); - mutex_unlock(&pdata->update_lock); - if (rc < 0) - return rc; + scoped_guard(pmbus_lock, client) { + rc = pmbus_read_block_data(client, entry->page, entry->reg, data); + if (rc < 0) + return rc; + } /* Add newline at the end of a read data */ data[rc] = '\n'; -- cgit v1.2.3 From b95ba51883fdfb60ab2633e0a5320a2f26f5298e Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 27 Mar 2026 03:19:50 +0800 Subject: hwmon: Add label support for 64-bit energy attributes Since commit 0bcd01f757bc ("hwmon: Introduce 64-bit energy attribute support"), devices can report 64-bit energy values by selecting the sensor type "energy64". However, such sensors can't report their labels since is_string_attr() was not updated to match it. Add label support for 64-bit energy attributes by updating is_string_attr() to match hwmon_energy64 in addition to hwmon_energy. Signed-off-by: Rong Zhang Link: https://lore.kernel.org/r/20260327-b4-hwmon-witrn-v1-1-8d2f1896c045@rong.moe Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 9695dca62a7e..6812d1fd7c28 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -505,6 +505,7 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) (type == hwmon_curr && attr == hwmon_curr_label) || (type == hwmon_power && attr == hwmon_power_label) || (type == hwmon_energy && attr == hwmon_energy_label) || + (type == hwmon_energy64 && attr == hwmon_energy_label) || (type == hwmon_humidity && attr == hwmon_humidity_label) || (type == hwmon_fan && attr == hwmon_fan_label); } -- cgit v1.2.3 From 331e5fd5bfd7aae3ab4eb947367b9d609ebb3fb3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 26 Mar 2026 10:30:00 +0100 Subject: hwmon: (ina2xx) drop unused platform data Nobody defines struct ina2xx_platform_data. Remove platform data support from the drivers which still have it (it's effectively dead code) and remove the header. Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Acked-by: Jonathan Cameron Link: https://lore.kernel.org/r/20260326-drop-ina2xx-pdata-v1-1-c159437bb2df@oss.qualcomm.com [groeck: Fixed continuation line alignment] Signed-off-by: Guenter Roeck --- drivers/hwmon/ina209.c | 11 ++--------- drivers/iio/adc/ina2xx-adc.c | 14 ++------------ include/linux/platform_data/ina2xx.h | 16 ---------------- 3 files changed, 4 insertions(+), 37 deletions(-) delete mode 100644 include/linux/platform_data/ina2xx.h diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c index bd7b3380d847..a116f1600e81 100644 --- a/drivers/hwmon/ina209.c +++ b/drivers/hwmon/ina209.c @@ -27,8 +27,6 @@ #include #include -#include - /* register definitions */ #define INA209_CONFIGURATION 0x00 #define INA209_STATUS 0x01 @@ -487,7 +485,6 @@ static void ina209_restore_conf(struct i2c_client *client, static int ina209_init_client(struct i2c_client *client, struct ina209_data *data) { - struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); u32 shunt; int reg; @@ -501,12 +498,8 @@ static int ina209_init_client(struct i2c_client *client, return reg; data->config_orig = reg; - if (pdata) { - if (pdata->shunt_uohms <= 0) - return -EINVAL; - shunt = pdata->shunt_uohms; - } else if (!of_property_read_u32(client->dev.of_node, "shunt-resistor", - &shunt)) { + if (!of_property_read_u32(client->dev.of_node, "shunt-resistor", + &shunt)) { if (shunt == 0) return -EINVAL; } else { diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index 857e1b69d6cd..c6cded508738 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -33,8 +33,6 @@ #include #include -#include - /* INA2XX registers definition */ #define INA2XX_CONFIG 0x00 #define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ @@ -980,16 +978,8 @@ static int ina2xx_probe(struct i2c_client *client) mutex_init(&chip->state_lock); - if (of_property_read_u32(client->dev.of_node, - "shunt-resistor", &val) < 0) { - struct ina2xx_platform_data *pdata = - dev_get_platdata(&client->dev); - - if (pdata) - val = pdata->shunt_uohms; - else - val = INA2XX_RSHUNT_DEFAULT; - } + if (of_property_read_u32(client->dev.of_node, "shunt-resistor", &val) < 0) + val = INA2XX_RSHUNT_DEFAULT; ret = set_shunt_resistor(chip, val); if (ret) diff --git a/include/linux/platform_data/ina2xx.h b/include/linux/platform_data/ina2xx.h deleted file mode 100644 index 2aa5ee9a9050..000000000000 --- a/include/linux/platform_data/ina2xx.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Driver for Texas Instruments INA219, INA226 power monitor chips - * - * Copyright (C) 2012 Lothar Felten - * - * For further information, see the Documentation/hwmon/ina2xx.rst file. - */ - -/** - * struct ina2xx_platform_data - ina2xx info - * @shunt_uohms shunt resistance in microohms - */ -struct ina2xx_platform_data { - long shunt_uohms; -}; -- cgit v1.2.3 From c67c248ca406a86cf8b20bf1b3af5e7f3e36581f Mon Sep 17 00:00:00 2001 From: Sergio Melas Date: Fri, 27 Mar 2026 23:16:02 +0100 Subject: hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring This driver provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad laptops by interfacing with the Embedded Controller (EC) via ACPI. To address low-resolution sampling in Lenovo EC firmware, a Rate-Limited Lag (RLLag) filter is implemented. The filter ensures a consistent physical curve regardless of userspace polling frequency. Hardware identification is performed via DMI-based quirk tables, which map specific ACPI object paths and register widths (8-bit vs 16-bit) deterministically. Signed-off-by: Sergio Melas Link: https://lore.kernel.org/r/20260327221602.18832-1-sergiomelas@gmail.com [groeck: Dropped double empty line in Kconfig] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/yogafan.rst | 130 +++++++++++++++++++ MAINTAINERS | 8 ++ drivers/hwmon/Kconfig | 12 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/yogafan.c | 275 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 427 insertions(+) create mode 100644 Documentation/hwmon/yogafan.rst create mode 100644 drivers/hwmon/yogafan.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 559c32344cd3..199f35a75282 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -282,4 +282,5 @@ Hardware Monitoring Kernel Drivers xdp710 xdpe12284 xdpe152c4 + yogafan zl6100 diff --git a/Documentation/hwmon/yogafan.rst b/Documentation/hwmon/yogafan.rst new file mode 100644 index 000000000000..c0a449aa8a36 --- /dev/null +++ b/Documentation/hwmon/yogafan.rst @@ -0,0 +1,130 @@ +.. SPDX-License-Identifier: GPL-2.0-only +=============================================================================================== +Kernel driver yogafan +=============================================================================================== + +Supported chips: + + * Lenovo Yoga, Legion, IdeaPad, Slim, Flex, and LOQ Embedded Controllers + Prefix: 'yogafan' + Addresses: ACPI handle (See Database Below) + +Author: Sergio Melas + +Description +----------- + +This driver provides fan speed monitoring for modern Lenovo consumer laptops. +Most Lenovo laptops do not provide fan tachometer data through standard +ISA/LPC hardware monitoring chips. Instead, the data is stored in the +Embedded Controller (EC) and exposed via ACPI. + +The driver implements a **Rate-Limited Lag (RLLag)** filter to handle +the low-resolution and jittery sampling found in Lenovo EC firmware. + +Hardware Identification and Multiplier Logic +-------------------------------------------- + +The driver supports two distinct EC architectures. Differentiation is handled +deterministically via a DMI Product Family quirk table during the probe phase, +eliminating the need for runtime heuristics. + +1. 8-bit EC Architecture (Multiplier: 100) + - **Families:** Yoga, IdeaPad, Slim, Flex. + - **Technical Detail:** These models allocate a single 8-bit register for + tachometer data. Since 8-bit fields are limited to a value of 255, the + BIOS stores fan speed in units of 100 RPM (e.g., 42 = 4200 RPM). + +2. 16-bit EC Architecture (Multiplier: 1) + - **Families:** Legion, LOQ. + - **Technical Detail:** High-performance gaming models require greater + precision for fans exceeding 6000 RPM. These use a 16-bit word (2 bytes) + storing the raw RPM value directly. + +Filter Details: +--------------- + +The RLLag filter is a passive discrete-time first-order lag model that ensures: + - **Smoothing:** Low-resolution step increments are smoothed into 1-RPM increments. + - **Slew-Rate Limiting:** Prevents unrealistic readings by capping the change + to 1500 RPM/s, matching physical fan inertia. + - **Polling Independence:** The filter math scales based on the time delta + between userspace reads, ensuring a consistent physical curve regardless + of polling frequency. + +Suspend and Resume +------------------ + +The driver utilizes the boottime clock (ktime_get_boottime()) to calculate the +sampling delta. This ensures that time spent in system suspend is accounted +for. If the delta exceeds 5 seconds (e.g., after waking the laptop), the +filter automatically resets to the current hardware value to prevent +reporting "ghost" RPM data from before the sleep state. + +Usage +----- + +The driver exposes standard hwmon sysfs attributes: +Attribute Description +fanX_input Filtered fan speed in RPM. + + +Note: If the hardware reports 0 RPM, the filter is bypassed and 0 is reported +immediately to ensure the user knows the fan has stopped. + + +==================================================================================================== + LENOVO FAN CONTROLLER: MASTER REFERENCE DATABASE (2026) +==================================================================================================== + +MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier +---------------------------------------------------------------------------------------------------- +82N7 | Yoga 14cACN | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 +80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 +83E2 / 83DN | Yoga Pro 7/9 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 +82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 +81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 +82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 +82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 +82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 +82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 +82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1 +83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 +81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 +*Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100 +---------------------------------------------------------------------------------------------------- + +METHODOLOGY & IDENTIFICATION: + +1. DSDT ANALYSIS (THE PATH): + BIOS ACPI tables were analyzed using 'iasl' and cross-referenced with + public dumps. Internal labels (FANS, FAN0, FA2S) are mapped to + EmbeddedControl OperationRegion offsets. + +2. EC MEMORY MAPPING (THE OFFSET): + Validated by matching NBFC (NoteBook FanControl) XML logic with DSDT Field + definitions found in BIOS firmware. + +3. DATA-WIDTH ANALYSIS (THE MULTIPLIER): + - 8-bit (Multiplier 100): Standard for Yoga/IdeaPad. Raw values (0-255). + - 16-bit (Multiplier 1): Standard for Legion/LOQ. Two registers (0xFE/0xFF). + + +References +---------- + +1. **ACPI Specification (Field Objects):** Documentation on how 8-bit vs 16-bit + fields are accessed in OperationRegions. + https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#field-objects + +2. **NBFC Projects:** Community-driven reverse engineering + of Lenovo Legion/LOQ EC memory maps (16-bit raw registers). + https://github.com/hirschmann/nbfc/tree/master/Configs + +3. **Linux Kernel Timekeeping API:** Documentation for ktime_get_boottime() and + handling deltas across suspend states. + https://www.kernel.org/doc/html/latest/core-api/timekeeping.html + +4. **Lenovo IdeaPad Laptop Driver:** Reference for DMI-based hardware + feature gating in Lenovo laptops. + https://github.com/torvalds/linux/blob/master/drivers/platform/x86/ideapad-laptop.c diff --git a/MAINTAINERS b/MAINTAINERS index 1c49613b142e..64acaa1e6f69 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14878,6 +14878,14 @@ W: https://linuxtv.org Q: http://patchwork.linuxtv.org/project/linux-media/list/ F: drivers/media/usb/dvb-usb-v2/lmedm04* +LENOVO YOGA FAN DRIVER +M: Sergio Melas +L: linux-hwmon@vger.kernel.org +S: Maintained +W: https://github.com/sergiomelas +F: Documentation/hwmon/yogafan.rst +F: drivers/hwmon/yogafan.c + LOADPIN SECURITY MODULE M: Kees Cook S: Supported diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9d49cfd4ef3d..152ab31298d9 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2651,6 +2651,18 @@ config SENSORS_XGENE If you say yes here you get support for the temperature and power sensors for APM X-Gene SoC. +config SENSORS_YOGAFAN + tristate "Lenovo Yoga Fan Hardware Monitoring" + depends on ACPI && HWMON && DMI + help + If you say yes here you get support for fan speed monitoring + on Lenovo Yoga, Legion, IdeaPad, Slim and LOQ laptops. + The driver interfaces with the Embedded Controller via ACPI + and uses a Rate-Limited Lag filter to smooth RPM readings. + + This driver can also be built as a module. If so, the module + will be called yogafan. + config SENSORS_INTEL_M10_BMC_HWMON tristate "Intel MAX10 BMC Hardware Monitoring" depends on MFD_INTEL_M10_BMC_CORE diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 556e86d277b1..0fce31b43eb1 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -245,6 +245,7 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o +obj-$(CONFIG_SENSORS_YOGAFAN) += yogafan.o obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_SENSORS_PECI) += peci/ diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c new file mode 100644 index 000000000000..605cc928f21f --- /dev/null +++ b/drivers/hwmon/yogafan.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * yoga_fan.c - Lenovo Yoga/Legion Fan Hardware Monitoring Driver + * + * Provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad + * laptops by interfacing with the Embedded Controller (EC) via ACPI. + * + * The driver implements a passive discrete-time first-order lag filter + * with slew-rate limiting (RLLag). This addresses low-resolution + * tachometer sampling in the EC by smoothing RPM readings based on + * the time delta (dt) between userspace requests, ensuring physical + * consistency without background task overhead or race conditions. + * The filter implements multirate filtering with autoreset in case + * of large sampling time. + * + * Copyright (C) 2021-2026 Sergio Melas + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Driver Configuration Constants */ +#define DRVNAME "yogafan" +#define MAX_FANS 8 + +/* Filter Configuration Constants */ +#define TAU_MS 1000 /* Time constant for the first-order lag (ms) */ +#define MAX_SLEW_RPM_S 1500 /* Maximum allowed change in RPM per second */ +#define MAX_SAMPLING 5000 /* Maximum allowed Ts for reset (ms) */ +#define MIN_SAMPLING 100 /* Minimum interval between filter updates (ms) */ + +/* RPM Sanitation Constants */ +#define RPM_FLOOR_LIMIT 50 /* Snap filtered value to 0 if raw is 0 */ + +struct yogafan_config { + int multiplier; + int fan_count; + const char *paths[2]; +}; + +struct yoga_fan_data { + acpi_handle active_handles[MAX_FANS]; + long filtered_val[MAX_FANS]; + ktime_t last_sample[MAX_FANS]; + int multiplier; + int fan_count; +}; + +/* Specific configurations mapped via DMI */ +static const struct yogafan_config yoga_8bit_fans_cfg = { + .multiplier = 100, + .fan_count = 1, + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", NULL } +}; + +static const struct yogafan_config ideapad_8bit_fan0_cfg = { + .multiplier = 100, + .fan_count = 1, + .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL } +}; + +static const struct yogafan_config legion_16bit_dual_cfg = { + .multiplier = 1, + .fan_count = 2, + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } +}; + +static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) +{ + ktime_t now = ktime_get_boottime(); + s64 dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx])); + long delta, step, limit, alpha; + s64 temp_num; + + if (raw_rpm < RPM_FLOOR_LIMIT) { + data->filtered_val[idx] = 0; + data->last_sample[idx] = now; + return; + } + + if (data->last_sample[idx] == 0 || dt_ms > MAX_SAMPLING) { + data->filtered_val[idx] = raw_rpm; + data->last_sample[idx] = now; + return; + } + + if (dt_ms < MIN_SAMPLING) + return; + + delta = raw_rpm - data->filtered_val[idx]; + if (delta == 0) { + data->last_sample[idx] = now; + return; + } + + temp_num = dt_ms << 12; + alpha = (long)div64_s64(temp_num, (s64)(TAU_MS + dt_ms)); + step = (delta * alpha) >> 12; + + if (step == 0 && delta != 0) + step = (delta > 0) ? 1 : -1; + + limit = (MAX_SLEW_RPM_S * (long)dt_ms) / 1000; + if (limit < 1) + limit = 1; + + if (step > limit) + step = limit; + else if (step < -limit) + step = -limit; + + data->filtered_val[idx] += step; + data->last_sample[idx] = now; +} + +static int yoga_fan_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct yoga_fan_data *data = dev_get_drvdata(dev); + unsigned long long raw_acpi; + acpi_status status; + + if (type != hwmon_fan || attr != hwmon_fan_input) + return -EOPNOTSUPP; + + status = acpi_evaluate_integer(data->active_handles[channel], NULL, NULL, &raw_acpi); + if (ACPI_FAILURE(status)) + return -EIO; + + apply_rllag_filter(data, channel, (long)raw_acpi * data->multiplier); + *val = data->filtered_val[channel]; + + return 0; +} + +static umode_t yoga_fan_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct yoga_fan_data *fan_data = data; + + if (type == hwmon_fan && channel < fan_data->fan_count) + return 0444; + + return 0; +} + +static const struct hwmon_ops yoga_fan_hwmon_ops = { + .is_visible = yoga_fan_is_visible, + .read = yoga_fan_read, +}; + +static const struct hwmon_channel_info *yoga_fan_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, HWMON_F_INPUT, + HWMON_F_INPUT, HWMON_F_INPUT, + HWMON_F_INPUT, HWMON_F_INPUT, + HWMON_F_INPUT, HWMON_F_INPUT), + NULL +}; + +static const struct hwmon_chip_info yoga_fan_chip_info = { + .ops = &yoga_fan_hwmon_ops, + .info = yoga_fan_info, +}; + +static const struct dmi_system_id yogafan_quirks[] = { + { + .ident = "Lenovo Yoga", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga"), + }, + .driver_data = (void *)&yoga_8bit_fans_cfg, + }, + { + .ident = "Lenovo Legion", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion"), + }, + .driver_data = (void *)&legion_16bit_dual_cfg, + }, + { + .ident = "Lenovo IdeaPad", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad"), + }, + .driver_data = (void *)&ideapad_8bit_fan0_cfg, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, yogafan_quirks); + +static int yoga_fan_probe(struct platform_device *pdev) +{ + const struct dmi_system_id *dmi_id; + const struct yogafan_config *cfg; + struct yoga_fan_data *data; + struct device *hwmon_dev; + int i; + + dmi_id = dmi_first_match(yogafan_quirks); + if (!dmi_id) + return -ENODEV; + + cfg = dmi_id->driver_data; + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->multiplier = cfg->multiplier; + + for (i = 0; i < cfg->fan_count; i++) { + acpi_status status; + + status = acpi_get_handle(NULL, (char *)cfg->paths[i], + &data->active_handles[data->fan_count]); + if (ACPI_SUCCESS(status)) + data->fan_count++; + } + + if (data->fan_count == 0) + return -ENODEV; + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, DRVNAME, + data, &yoga_fan_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver yoga_fan_driver = { + .driver = { .name = DRVNAME }, + .probe = yoga_fan_probe, +}; + +static struct platform_device *yoga_fan_device; + +static int __init yoga_fan_init(void) +{ + int ret; + + if (!dmi_check_system(yogafan_quirks)) + return -ENODEV; + + ret = platform_driver_register(&yoga_fan_driver); + if (ret) + return ret; + + yoga_fan_device = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(yoga_fan_device)) { + platform_driver_unregister(&yoga_fan_driver); + return PTR_ERR(yoga_fan_device); + } + return 0; +} + +static void __exit yoga_fan_exit(void) +{ + platform_device_unregister(yoga_fan_device); + platform_driver_unregister(&yoga_fan_driver); +} + +module_init(yoga_fan_init); +module_exit(yoga_fan_exit); + +MODULE_AUTHOR("Sergio Melas "); +MODULE_DESCRIPTION("Lenovo Yoga/Legion Fan Monitor Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 6d50ae25666d5433108b1cd11965c0f53c355a83 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 30 Mar 2026 14:46:24 -0700 Subject: hwmon: (yogafan) fix markup warning Add a blank line between the License and heading lines to prevent a documentation build warning: Documentation/hwmon/yogafan.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent. [docutils] Fixes: c67c248ca406 ("hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring") Signed-off-by: Randy Dunlap Link: https://lore.kernel.org/r/20260330214624.3781789-1-rdunlap@infradead.org Signed-off-by: Guenter Roeck --- Documentation/hwmon/yogafan.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/hwmon/yogafan.rst b/Documentation/hwmon/yogafan.rst index c0a449aa8a36..b0adf67be0c9 100644 --- a/Documentation/hwmon/yogafan.rst +++ b/Documentation/hwmon/yogafan.rst @@ -1,4 +1,5 @@ .. SPDX-License-Identifier: GPL-2.0-only + =============================================================================================== Kernel driver yogafan =============================================================================================== -- cgit v1.2.3 From 92842776cc45f134a6a965cfe875e14633cb9a47 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Fri, 3 Apr 2026 17:06:54 +0300 Subject: hwmon: (tmp102) add support for update interval Since the sensor supports different sampling intervals via bits CR0 and CR1 from the CONFIG register, add support in order for the conversion rate to be changed from user space. Default is 4 conv/sec. Signed-off-by: Flaviu Nistor Link: https://lore.kernel.org/r/20260403140654.10368-1-flaviu.nistor@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/tmp102.rst | 19 +++++- drivers/hwmon/tmp102.c | 128 ++++++++++++++++++++++++++++++++++------- 2 files changed, 123 insertions(+), 24 deletions(-) diff --git a/Documentation/hwmon/tmp102.rst b/Documentation/hwmon/tmp102.rst index 3c2cb5bab1e9..425a09a3c9b3 100644 --- a/Documentation/hwmon/tmp102.rst +++ b/Documentation/hwmon/tmp102.rst @@ -41,12 +41,25 @@ degree from -40 to +125 C. Resolution of the sensor is 0.0625 degree. The operating temperature has a minimum of -55 C and a maximum of +150 C. The TMP102 has a programmable update rate that can select between 8, 4, 1, and -0.5 Hz. (Currently the driver only supports the default of 4 Hz). +0.25 Hz. The TMP110 and TMP113 are software compatible with TMP102, but have different accuracy (maximum error) specifications. The TMP110 has an accuracy (maximum error) of 1.0 degree, TMP113 has an accuracy (maximum error) of 0.3 degree, while TMP102 has an accuracy (maximum error) of 2.0 degree. -The driver provides the common sysfs-interface for temperatures (see -Documentation/hwmon/sysfs-interface.rst under Temperatures). +sysfs-Interface +--------------- + +The following list includes the sysfs attributes that the driver provides, their +permissions and a short description: + +=============================== ======= =========================================== +Name Perm Description +=============================== ======= =========================================== +temp1_input: RO Temperature input +temp1_label: RO Descriptive name for the sensor +temp1_max: RW Maximum temperature +temp1_max_hyst: RW Maximum hysteresis temperature +update_interval RW Update conversions interval in milliseconds +=============================== ======= =========================================== diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index 5b10c395a84d..3aa1a3fbeaa9 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -50,11 +50,16 @@ #define CONVERSION_TIME_MS 35 /* in milli-seconds */ +#define NUM_SAMPLE_TIMES 4 +#define DEFAULT_SAMPLE_TIME_MS 250 +static const unsigned int *sample_times = (const unsigned int []){ 125, 250, 1000, 4000 }; + struct tmp102 { const char *label; struct regmap *regmap; u16 config_orig; unsigned long ready_time; + u16 sample_time; }; /* convert left adjusted 13-bit TMP102 register value to milliCelsius */ @@ -79,8 +84,20 @@ static int tmp102_read_string(struct device *dev, enum hwmon_sensor_types type, return 0; } -static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long *temp) +static int tmp102_read_chip(struct device *dev, u32 attr, long *val) +{ + struct tmp102 *tmp102 = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + *val = tmp102->sample_time; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int tmp102_read_temp(struct device *dev, u32 attr, long *val) { struct tmp102 *tmp102 = dev_get_drvdata(dev); unsigned int regval; @@ -108,13 +125,54 @@ static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, err = regmap_read(tmp102->regmap, reg, ®val); if (err < 0) return err; - *temp = tmp102_reg_to_mC(regval); + + *val = tmp102_reg_to_mC(regval); return 0; } -static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long temp) +static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return tmp102_read_chip(dev, attr, val); + case hwmon_temp: + return tmp102_read_temp(dev, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static int tmp102_update_interval(struct device *dev, long val) +{ + struct tmp102 *tmp102 = dev_get_drvdata(dev); + u8 index; + s32 err; + + index = find_closest(val, sample_times, NUM_SAMPLE_TIMES); + + err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG, + (TMP102_CONF_CR1 | TMP102_CONF_CR0), (3 - index) << 6); + if (err < 0) + return err; + tmp102->sample_time = sample_times[index]; + + return 0; +} + +static int tmp102_write_chip(struct device *dev, u32 attr, long val) +{ + switch (attr) { + case hwmon_chip_update_interval: + return tmp102_update_interval(dev, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int tmp102_write_temp(struct device *dev, u32 attr, long val) { struct tmp102 *tmp102 = dev_get_drvdata(dev); int reg; @@ -130,8 +188,22 @@ static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, return -EOPNOTSUPP; } - temp = clamp_val(temp, -256000, 255000); - return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(temp)); + val = clamp_val(val, -256000, 255000); + return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(val)); +} + +static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return tmp102_write_chip(dev, attr, val); + case hwmon_temp: + return tmp102_write_temp(dev, attr, val); + default: + return -EOPNOTSUPP; + } + return 0; } static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type, @@ -139,27 +211,39 @@ static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type, { const struct tmp102 *tmp102 = data; - if (type != hwmon_temp) - return 0; - - switch (attr) { - case hwmon_temp_input: - return 0444; - case hwmon_temp_label: - if (tmp102->label) + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: return 0444; - return 0; - case hwmon_temp_max_hyst: - case hwmon_temp_max: - return 0644; + case hwmon_temp_label: + if (tmp102->label) + return 0444; + return 0; + case hwmon_temp_max_hyst: + case hwmon_temp_max: + return 0644; + default: + break; + } + break; default: - return 0; + break; } + return 0; } static const struct hwmon_channel_info * const tmp102_info[] = { HWMON_CHANNEL_INFO(chip, - HWMON_C_REGISTER_TZ), + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST), NULL @@ -237,6 +321,8 @@ static int tmp102_probe(struct i2c_client *client) if (IS_ERR(tmp102->regmap)) return PTR_ERR(tmp102->regmap); + tmp102->sample_time = DEFAULT_SAMPLE_TIME_MS; + err = regmap_read(tmp102->regmap, TMP102_CONF_REG, ®val); if (err < 0) { dev_err(dev, "error reading config register\n"); -- cgit v1.2.3 From 7db0b82754140c76774ce01f6110ff0d1c0f2b67 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Thu, 2 Apr 2026 14:34:24 +0200 Subject: hwmon: (sparx5) Make it selectable for ARCH_LAN969X LAN969x uses the same sensor and driver, so make it selectable for ARCH_LAN969X. Signed-off-by: Robert Marko Link: https://lore.kernel.org/r/20260402123436.47856-1-robert.marko@sartura.hr Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 152ab31298d9..fb847ab40ab4 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -606,7 +606,7 @@ config SENSORS_I5K_AMB config SENSORS_SPARX5 tristate "Sparx5 SoC temperature sensor" - depends on ARCH_SPARX5 || COMPILE_TEST + depends on ARCH_SPARX5 || ARCH_LAN969X || COMPILE_TEST help If you say yes here you get support for temperature monitoring with the Microchip Sparx5 SoC. -- cgit v1.2.3 From 0b30c1037a6a48a4c293d45c6cbe8e312633782f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 6 Apr 2026 22:23:17 -0700 Subject: hwmon: (yogafan) various markup improvements There are several places in yogafan.rst where it appears that lines are meant to be presented on their own but instead they are strung together due to the lack of markups. Fix these issues by: - using bullets where needed - indenting continuation lines of bulleted items - using a table where appropriate - using a literal block where appropriate Fixes: c67c248ca406 ("hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring") Signed-off-by: Randy Dunlap Link: https://lore.kernel.org/r/20260407052317.2097791-1-rdunlap@infradead.org Signed-off-by: Guenter Roeck --- Documentation/hwmon/yogafan.rst | 55 +++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/Documentation/hwmon/yogafan.rst b/Documentation/hwmon/yogafan.rst index b0adf67be0c9..c553a381f772 100644 --- a/Documentation/hwmon/yogafan.rst +++ b/Documentation/hwmon/yogafan.rst @@ -7,8 +7,8 @@ Kernel driver yogafan Supported chips: * Lenovo Yoga, Legion, IdeaPad, Slim, Flex, and LOQ Embedded Controllers - Prefix: 'yogafan' - Addresses: ACPI handle (See Database Below) + * Prefix: 'yogafan' + * Addresses: ACPI handle (See Database Below) Author: Sergio Melas @@ -31,19 +31,21 @@ deterministically via a DMI Product Family quirk table during the probe phase, eliminating the need for runtime heuristics. 1. 8-bit EC Architecture (Multiplier: 100) + - **Families:** Yoga, IdeaPad, Slim, Flex. - **Technical Detail:** These models allocate a single 8-bit register for - tachometer data. Since 8-bit fields are limited to a value of 255, the - BIOS stores fan speed in units of 100 RPM (e.g., 42 = 4200 RPM). + tachometer data. Since 8-bit fields are limited to a value of 255, the + BIOS stores fan speed in units of 100 RPM (e.g., 42 = 4200 RPM). 2. 16-bit EC Architecture (Multiplier: 1) + - **Families:** Legion, LOQ. - **Technical Detail:** High-performance gaming models require greater - precision for fans exceeding 6000 RPM. These use a 16-bit word (2 bytes) - storing the raw RPM value directly. + precision for fans exceeding 6000 RPM. These use a 16-bit word (2 bytes) + storing the raw RPM value directly. -Filter Details: ---------------- +Filter Details +-------------- The RLLag filter is a passive discrete-time first-order lag model that ensures: - **Smoothing:** Low-resolution step increments are smoothed into 1-RPM increments. @@ -66,8 +68,11 @@ Usage ----- The driver exposes standard hwmon sysfs attributes: + +=============== ============================ Attribute Description fanX_input Filtered fan speed in RPM. +=============== ============================ Note: If the hardware reports 0 RPM, the filter is bypassed and 0 is reported @@ -78,22 +83,24 @@ immediately to ensure the user knows the fan has stopped. LENOVO FAN CONTROLLER: MASTER REFERENCE DATABASE (2026) ==================================================================================================== -MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier ----------------------------------------------------------------------------------------------------- -82N7 | Yoga 14cACN | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 -80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 -83E2 / 83DN | Yoga Pro 7/9 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 -82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 -81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 -82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 -82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 -82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 -82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 -82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1 -83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 -81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 -*Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100 ----------------------------------------------------------------------------------------------------- +:: + + MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier + ---------------------------------------------------------------------------------------------------- + 82N7 | Yoga 14cACN | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 + 80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 83E2 / 83DN | Yoga Pro 7/9 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 + 82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 + 81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 + 82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 + 82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 + 82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 + 82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1 + 83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + *Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100 + ---------------------------------------------------------------------------------------------------- METHODOLOGY & IDENTIFICATION: -- cgit v1.2.3 From 502a498c1d03b941efae90b192d51109a66d463f Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Fri, 10 Apr 2026 13:24:11 +1200 Subject: dt-bindings: trivial-devices: Add sony,aps-379 Add the compatible string for the sony,aps-379. This is a simple PMBus (I2C) device that requires no additional attributes. Signed-off-by: Chris Packham Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260410012414.2818829-2-chris.packham@alliedtelesis.co.nz 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 03a274943223..23fd4513933a 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -441,6 +441,8 @@ properties: - smsc,emc6d103s # Socionext Uniphier SMP control registers - socionext,uniphier-smpctrl + # Sony APS-379 Power Supply + - sony,aps-379 # SparkFun Qwiic Joystick (COM-15168) with i2c interface - sparkfun,qwiic-joystick # STMicroelectronics Hot-swap controller stef48h28 -- cgit v1.2.3 From a2981c20ad673bcd5f0e5caa6ef103b8fcdbd6a2 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Fri, 10 Apr 2026 13:24:12 +1200 Subject: hwmon: pmbus: Add support for Sony APS-379 Add pmbus support for Sony APS-379 power supplies. There are a few PMBUS commands that return data that is undocumented/invalid so these need to be rejected with -ENXIO. The READ_VOUT command returns data in linear11 format instead of linear16 so we need to workaround this. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20260410012414.2818829-3-chris.packham@alliedtelesis.co.nz [groeck: Dropped empty line from documentation; added module name to Kconfig] Signed-off-by: Guenter Roeck --- Documentation/hwmon/aps-379.rst | 57 +++++++++++++++ Documentation/hwmon/index.rst | 1 + drivers/hwmon/pmbus/Kconfig | 9 +++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/aps-379.c | 155 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 Documentation/hwmon/aps-379.rst create mode 100644 drivers/hwmon/pmbus/aps-379.c diff --git a/Documentation/hwmon/aps-379.rst b/Documentation/hwmon/aps-379.rst new file mode 100644 index 000000000000..6d4e63283e34 --- /dev/null +++ b/Documentation/hwmon/aps-379.rst @@ -0,0 +1,57 @@ +Kernel driver aps-379 +===================== + +Supported chips: + + * Sony APS-379 + + Prefix: 'aps-379' + + Addresses scanned: - + + Authors: + - Chris Packham + +Description +----------- + +This driver implements support for the PMBus monitor on the Sony APS-379 +modular power supply. The APS-379 deviates from the PMBus standard for the +READ_VOUT command by using the linear11 format instead of linear16. + +The known supported PMBus commands are: + +=== ============================= ========= ======= ===== +Cmd Function Protocol Scaling Bytes +=== ============================= ========= ======= ===== +01 On / Off Command (OPERATION) Byte R/W -- 1 +10 WRITE_PROTECT Byte R/W -- 1 +3B FAN_COMMAND_1 Word R/W -- 2 +46 Current Limit (in percent) Word R/W 2^0 2 +47 Current Limit Fault Response Byte R/W -- 1 +79 Alarm Data Bits (STATUS_WORD) Word Rd -- 2 +8B Output Voltage (READ_VOUT) Word Rd 2^-4 2 +8C Output Current (READ_IOUT) Word Rd 2^-2 2 +8D Power Supply Ambient Temp Word Rd 2^0 2 +90 READ_FAN_SPEED_1 Word Rd 2^6 2 +91 READ_FAN_SPEED_2 Word Rd 2^6 2 +96 Output Wattage (READ_POUT) Word Rd 2^1 2 +97 Input Wattage (READ_PIN) Word Rd 2^1 2 +9A Unit Model Number (MFR_MODEL) Block R/W -- 10 +9B Unit Revision Number Block R/W -- 10 +9E Unit Serial Number Block R/W -- 8 +99 Unit Manufacturer ID (MFR_ID) Block R/W -- 8 +D0 Unit Run Time Information Block Rd -- 4 +D5 Firmware Version Rd cust -- 8 +B0 User Data 1 (USER_DATA_00) Block R/W -- 4 +B1 User Data 2 (USER_DATA_01) Block R/W -- 4 +B2 User Data 3 (USER_DATA_02) Block R/W -- 4 +B3 User Data 4 (USER_DATA_03) Block R/W -- 4 +B4 User Data 5 (USER_DATA_04) Block R/W -- 4 +B5 User Data 6 (USER_DATA_05) Block R/W -- 4 +B6 User Data 7 (USER_DATA_06) Block R/W -- 4 +B7 User Data 8 (USER_DATA_07) Block R/W -- 4 +F0 Calibration command Byte R/W -- 1 +F1 Calibration data Word Wr 2^9 2 +F2 Unlock Calibration Byte Wr -- 1 +=== ============================= ========= ======= ===== diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 199f35a75282..80d5be0dda2d 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -41,6 +41,7 @@ Hardware Monitoring Kernel Drivers adt7475 aht10 amc6821 + aps-379 aquacomputer_d5next asb100 asc7621 diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index a4513fc6bc26..db63acbe08c8 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -77,6 +77,15 @@ config SENSORS_ADP1050_REGULATOR µModule regulators that can provide microprocessor power from 54V power distribution architecture. +config SENSORS_APS_379 + tristate "Sony APS-379 Power Supplies" + help + If you say yes here you get hardware monitoring support for Sony + APS-379 Power Supplies. + + This driver can also be built as a module. If so, the module will + be called aps-379. + config SENSORS_BEL_PFE tristate "Bel PFE Compatible Power Supplies" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index d592d8c77bec..63a9870c7b53 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_ACBEL_FSG032) += acbel-fsg032.o obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o obj-$(CONFIG_SENSORS_ADP1050) += adp1050.o +obj-$(CONFIG_SENSORS_APS_379) += aps-379.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 diff --git a/drivers/hwmon/pmbus/aps-379.c b/drivers/hwmon/pmbus/aps-379.c new file mode 100644 index 000000000000..7d46cd647e20 --- /dev/null +++ b/drivers/hwmon/pmbus/aps-379.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Sony APS-379 Power Supplies + * + * Copyright 2026 Allied Telesis Labs + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * The VOUT format used by the chip is linear11, not linear16. Provide a hard + * coded VOUT_MODE that says VOUT is in linear mode with a fixed exponent of + * 2^-4. + */ +#define APS_379_VOUT_MODE ((u8)(-4 & 0x1f)) + +static int aps_379_read_byte_data(struct i2c_client *client, int page, int reg) +{ + switch (reg) { + case PMBUS_VOUT_MODE: + return APS_379_VOUT_MODE; + default: + return -ENODATA; + } +} + +/* + * The APS-379 uses linear11 format instead of linear16. We've reported the exponent + * via the PMBUS_VOUT_MODE so we just return the mantissa here. + */ +static int aps_379_read_vout(struct i2c_client *client) +{ + int ret; + + ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_READ_VOUT); + if (ret < 0) + return ret; + + return clamp_val(sign_extend32(ret & 0x7ff, 10), 0, 0x3ff); +} + +static int aps_379_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + switch (reg) { + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_FAULT_LIMIT: + case PMBUS_VOUT_OV_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_UT_WARN_LIMIT: + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + case PMBUS_POUT_OP_WARN_LIMIT: + case PMBUS_MFR_IIN_MAX: + case PMBUS_MFR_PIN_MAX: + case PMBUS_MFR_VOUT_MIN: + case PMBUS_MFR_VOUT_MAX: + case PMBUS_MFR_IOUT_MAX: + case PMBUS_MFR_POUT_MAX: + case PMBUS_MFR_MAX_TEMP_1: + /* These commands return data but it is invalid/un-documented */ + return -ENXIO; + case PMBUS_IOUT_OC_FAULT_LIMIT: + /* + * The standard requires this to be a value in Amps but it's + * actually a percentage of the rated output (123A for + * 110-240Vac, 110A for 90-100Vac) which we don't know. Ignore + * it rather than guessing. + */ + return -ENXIO; + case PMBUS_READ_VOUT: + return aps_379_read_vout(client); + default: + return -ENODATA; + } +} + +static struct pmbus_driver_info aps_379_info = { + .pages = 1, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_FAN] = linear, + .func[0] = PMBUS_HAVE_VOUT | + PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | + PMBUS_HAVE_FAN12, + .read_byte_data = aps_379_read_byte_data, + .read_word_data = aps_379_read_word_data, +}; + +static const struct i2c_device_id aps_379_id[] = { + { "aps-379", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, aps_379_id); + +static int aps_379_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = { 0 }; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(dev, "Failed to read Manufacturer Model\n"); + return ret; + } + + if (strncasecmp(buf, aps_379_id[0].name, strlen(aps_379_id[0].name)) != 0) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } + + return pmbus_do_probe(client, &aps_379_info); +} + +static const struct of_device_id __maybe_unused aps_379_of_match[] = { + { .compatible = "sony,aps-379" }, + {}, +}; +MODULE_DEVICE_TABLE(of, aps_379_of_match); + +static struct i2c_driver aps_379_driver = { + .driver = { + .name = "aps-379", + .of_match_table = of_match_ptr(aps_379_of_match), + }, + .probe = aps_379_probe, + .id_table = aps_379_id, +}; + +module_i2c_driver(aps_379_driver); + +MODULE_AUTHOR("Chris Packham"); +MODULE_DESCRIPTION("PMBus driver for Sony APS-379"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); -- cgit v1.2.3 From 08e57f5e1a9067d5fbf33993aa7f51d60b3d13a4 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Fri, 10 Apr 2026 00:25:35 +0000 Subject: hwmon: (powerz) Fix use-after-free on USB disconnect After powerz_disconnect() frees the URB and releases the mutex, a subsequent powerz_read() call can acquire the mutex and call powerz_read_data(), which dereferences the freed URB pointer. Fix by: - Setting priv->urb to NULL in powerz_disconnect() so that powerz_read_data() can detect the disconnected state. - Adding a !priv->urb check at the start of powerz_read_data() to return -ENODEV on a disconnected device. - Moving usb_set_intfdata() before hwmon registration so the disconnect handler can always find the priv pointer. Fixes: 4381a36abdf1c ("hwmon: add POWER-Z driver") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260410002521.422645-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/powerz.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index 4e663d5b4e33..a75b941bd6e2 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -108,6 +108,9 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { int ret; + if (!priv->urb) + return -ENODEV; + priv->status = -ETIMEDOUT; reinit_completion(&priv->completion); @@ -224,6 +227,8 @@ static int powerz_probe(struct usb_interface *intf, mutex_init(&priv->mutex); init_completion(&priv->completion); + usb_set_intfdata(intf, priv); + hwmon_dev = devm_hwmon_device_register_with_info(parent, DRIVER_NAME, priv, &powerz_chip_info, NULL); @@ -232,8 +237,6 @@ static int powerz_probe(struct usb_interface *intf, return PTR_ERR(hwmon_dev); } - usb_set_intfdata(intf, priv); - return 0; } @@ -244,6 +247,7 @@ static void powerz_disconnect(struct usb_interface *intf) mutex_lock(&priv->mutex); usb_kill_urb(priv->urb); usb_free_urb(priv->urb); + priv->urb = NULL; mutex_unlock(&priv->mutex); } -- cgit v1.2.3 From b66437cb20a2d9ef201f40b675569f8ea7787c9f Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Fri, 10 Apr 2026 00:25:41 +0000 Subject: hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt wait_for_completion_interruptible_timeout() returns -ERESTARTSYS when interrupted. This needs to abort the URB and return an error. No data has been received from the device so any reads from the transfer buffer are invalid. The original code tests !ret, which only catches the timeout case (0). On signal delivery (-ERESTARTSYS), !ret is false so the function skips usb_kill_urb() and falls through to read from the unfilled transfer buffer. Fix by capturing the return value into a long (matching the function return type) and handling signal (negative) and timeout (zero) cases with separate checks that both call usb_kill_urb() before returning. Fixes: 4381a36abdf1c ("hwmon: add POWER-Z driver") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260410002521.422645-3-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/powerz.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2..96438f5f05d4 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit v1.2.3 From 24c73e93d6a756e1b8626bb259d2e07c5b89b370 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Fri, 10 Apr 2026 00:25:55 +0000 Subject: hwmon: (pt5161l) Fix bugs in pt5161l_read_block_data() Fix two bugs in pt5161l_read_block_data(): 1. Buffer overrun: The local buffer rbuf is declared as u8 rbuf[24], but i2c_smbus_read_block_data() can return up to I2C_SMBUS_BLOCK_MAX (32) bytes. The i2c-core copies the data into the caller's buffer before the return value can be checked, so the post-read length validation does not prevent a stack overrun if a device returns more than 24 bytes. Resize the buffer to I2C_SMBUS_BLOCK_MAX. 2. Unexpected positive return on length mismatch: When all three retries are exhausted because the device returns data with an unexpected length, i2c_smbus_read_block_data() returns a positive byte count. The function returns this directly, and callers treat any non-negative return as success, processing stale or incomplete buffer contents. Return -EIO when retries are exhausted with a positive return value, preserving the negative error code on I2C failure. Fixes: 1b2ca93cd0592 ("hwmon: Add driver for Astera Labs PT5161L retimer") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260410002549.424162-1-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pt5161l.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/pt5161l.c b/drivers/hwmon/pt5161l.c index 20e3cfa625f1..89d4da8aa4c0 100644 --- a/drivers/hwmon/pt5161l.c +++ b/drivers/hwmon/pt5161l.c @@ -121,7 +121,7 @@ static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address, int ret, tries; u8 remain_len = len; u8 curr_len; - u8 wbuf[16], rbuf[24]; + u8 wbuf[16], rbuf[I2C_SMBUS_BLOCK_MAX]; u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ @@ -151,7 +151,7 @@ static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address, break; } if (tries >= 3) - return ret; + return ret < 0 ? ret : -EIO; memcpy(val, rbuf, curr_len); val += curr_len; -- cgit v1.2.3 From a7c0aaa50e40ffd8fd703d006d5a04b540b9ca92 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Fri, 10 Apr 2026 00:26:19 +0000 Subject: hwmon: (isl28022) Fix integer overflow in power calculation on 32-bit isl28022_read_power() computes: *val = ((51200000L * ((long)data->gain)) / (long)data->shunt) * (long)regval; On 32-bit platforms, 'long' is 32 bits. With gain=8 and shunt=10000 (the default configuration): (51200000 * 8) / 10000 = 40960 40960 * 65535 = 2,684,313,600 This exceeds LONG_MAX (2,147,483,647), resulting in signed integer overflow. Additionally, dividing before multiplying by regval loses precision unnecessarily. Use u64 arithmetic with div_u64() and multiply before dividing to retain precision. The intermediate product cannot overflow u64 (worst case: 51200000 * 8 * 65535 = 26843136000000). Power is inherently non-negative, so unsigned types are the natural fit. Cap the result to LONG_MAX before returning it through the hwmon callback. Fixes: 39671a14df4f2 ("hwmon: (isl28022) new driver for ISL28022 power monitor") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260410002613.424557-1-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/isl28022.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/isl28022.c b/drivers/hwmon/isl28022.c index c2e559dde63f..c5a34ceedcdb 100644 --- a/drivers/hwmon/isl28022.c +++ b/drivers/hwmon/isl28022.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -185,8 +186,8 @@ static int isl28022_read_power(struct device *dev, u32 attr, long *val) ISL28022_REG_POWER, ®val); if (err < 0) return err; - *val = ((51200000L * ((long)data->gain)) / - (long)data->shunt) * (long)regval; + *val = min(div_u64(51200000ULL * data->gain * regval, + data->shunt), LONG_MAX); break; default: return -EOPNOTSUPP; -- cgit v1.2.3 From 3023c050af3600bf451153335dea5e073c9a3088 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 8 Apr 2026 20:45:50 +0200 Subject: hwmon: (powerz) Avoid cacheline sharing for DMA buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depending on the architecture the transfer buffer may share a cacheline with the following mutex. As the buffer may be used for DMA, that is problematic. Use the high-level DMA helpers to make sure that cacheline sharing can not happen. Also drop the comment, as the helpers are documentation enough. https://sashiko.dev/#/message/20260408175814.934BFC19421%40smtp.kernel.org Fixes: 4381a36abdf1c ("hwmon: add POWER-Z driver") Cc: stable@vger.kernel.org # ca085faabb42: dma-mapping: add __dma_from_device_group_begin()/end() Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260408-powerz-cacheline-alias-v1-1-1254891be0dd@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/powerz.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index 96438f5f05d4..6e1359144cab 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -33,7 +34,9 @@ struct powerz_sensor_data { } __packed; struct powerz_priv { - char transfer_buffer[64]; /* first member to satisfy DMA alignment */ + __dma_from_device_group_begin(); + char transfer_buffer[64]; + __dma_from_device_group_end(); struct mutex mutex; struct completion completion; struct urb *urb; -- cgit v1.2.3 From a345c1e3cd1b49ddf03331ee9c19ddebe149793e Mon Sep 17 00:00:00 2001 From: Victor Duicu Date: Fri, 3 Apr 2026 16:32:16 +0300 Subject: dt-bindings: hwmon: add support for MCP998X Add devicetree schema for Microchip MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Family. Signed-off-by: Victor Duicu Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260403-add-mcp9982-hwmon-v12-1-b3bfb26ff136@microchip.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/microchip,mcp9982.yaml | 237 +++++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 243 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml diff --git a/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml new file mode 100644 index 000000000000..83dd2bf37e27 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml @@ -0,0 +1,237 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/microchip,mcp9982.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip MCP998X/33 and MCP998XD/33D Temperature Monitor + +maintainers: + - Victor Duicu + +description: | + The MCP998X/33 and MCP998XD/33D family is a high-accuracy 2-wire + multichannel automotive temperature monitor. + The datasheet can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf + +properties: + compatible: + enum: + - microchip,mcp9933 + - microchip,mcp9933d + - microchip,mcp9982 + - microchip,mcp9982d + - microchip,mcp9983 + - microchip,mcp9983d + - microchip,mcp9984 + - microchip,mcp9984d + - microchip,mcp9985 + - microchip,mcp9985d + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 2 + + interrupt-names: + description: + The chip family has three different interrupt pins divided among them. + The chips without "D" have alert-therm and therm-addr. + The chips with "D" have alert-therm and sys-shtdwn. + minItems: 1 + items: + - enum: [alert-therm, therm-addr, sys-shtdwn] + - enum: [therm-addr, sys-shtdwn] + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + microchip,enable-anti-parallel: + description: + Enable anti-parallel diode mode operation. + MCP9984/84D/85/85D and MCP9933/33D support reading two external diodes + in anti-parallel connection on the same set of pins. + type: boolean + + microchip,parasitic-res-on-channel1-2: + description: + Indicates that the chip and the diodes/transistors are sufficiently far + apart that a parasitic resistance is added to the wires, which can affect + the measurements. Due to the anti-parallel diode connections, channels + 1 and 2 are affected together. + type: boolean + + microchip,parasitic-res-on-channel3-4: + description: + Indicates that the chip and the diodes/transistors are sufficiently far + apart that a parasitic resistance is added to the wires, which can affect + the measurements. Due to the anti-parallel diode connections, channels + 3 and 4 are affected together. + type: boolean + + microchip,power-state: + description: + The chip can be set in Run state or Standby state. In Run state the ADC + is converting on all channels at the programmed conversion rate. + In Standby state the host must initiate a conversion cycle by writing + to the One-Shot register. + True value sets Run state. + Chips with "D" in the name can only be set in Run mode. + type: boolean + + vdd-supply: true + +patternProperties: + "^channel@[1-4]$": + description: + Represents the external temperature channels to which + a remote diode is connected. + type: object + + properties: + reg: + items: + maxItems: 1 + + label: + description: Unique name to identify which channel this is. + + required: + - reg + + additionalProperties: false + +required: + - compatible + - reg + - vdd-supply + +allOf: + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp9982d + - microchip,mcp9983d + - microchip,mcp9984d + - microchip,mcp9985d + - microchip,mcp9933d + then: + properties: + interrupt-names: + items: + enum: + - alert-therm + - sys-shtdwn + required: + - microchip,power-state + - microchip,parasitic-res-on-channel1-2 + else: + properties: + microchip,power-state: true + interrupt-names: + items: + enum: + - alert-therm + - therm-addr + + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp9983d + - microchip,mcp9984d + - microchip,mcp9985d + then: + required: + - microchip,parasitic-res-on-channel3-4 + + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp9982 + - microchip,mcp9982d + then: + properties: + microchip,enable-anti-parallel: false + patternProperties: + "^channel@[2-4]$": false + + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp9983 + - microchip,mcp9983d + then: + properties: + microchip,enable-anti-parallel: false + patternProperties: + "^channel@[3-4]$": false + + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp9933 + - microchip,mcp9933d + then: + patternProperties: + "^channel@[3-4]$": false + + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp9984 + - microchip,mcp9984d + then: + properties: + channel@4: false + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@4c { + compatible = "microchip,mcp9985"; + reg = <0x4c>; + + #address-cells = <1>; + #size-cells = <0>; + + microchip,enable-anti-parallel; + microchip,parasitic-res-on-channel1-2; + microchip,parasitic-res-on-channel3-4; + vdd-supply = <&vdd>; + + channel@1 { + reg = <1>; + label = "Room Temperature"; + }; + + channel@2 { + reg = <2>; + label = "GPU Temperature"; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 64acaa1e6f69..788b92a0b8b1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17358,6 +17358,12 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/microchip,mcp3911.yaml F: drivers/iio/adc/mcp3911.c +MICROCHIP MCP9982 TEMPERATURE DRIVER +M: Victor Duicu +L: linux-hwmon@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml + MICROCHIP MMC/SD/SDIO MCI DRIVER M: Aubin Constans S: Maintained -- cgit v1.2.3 From e2fe950f34e54d6bd91d2c56501faa903e25fb5e Mon Sep 17 00:00:00 2001 From: Victor Duicu Date: Fri, 3 Apr 2026 16:32:17 +0300 Subject: hwmon: add support for MCP998X Add driver for Microchip MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Family. Signed-off-by: Victor Duicu Link: https://lore.kernel.org/r/20260403-add-mcp9982-hwmon-v12-2-b3bfb26ff136@microchip.com [groeck: Add missing break; to avoid build warning] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mcp9982.rst | 111 +++++ MAINTAINERS | 2 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/mcp9982.c | 998 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1124 insertions(+) create mode 100644 Documentation/hwmon/mcp9982.rst create mode 100644 drivers/hwmon/mcp9982.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 80d5be0dda2d..8b655e5d6b68 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -175,6 +175,7 @@ Hardware Monitoring Kernel Drivers mc33xs2410_hwmon mc34vr500 mcp3021 + mcp9982 menf21bmc mlxreg-fan mp2856 diff --git a/Documentation/hwmon/mcp9982.rst b/Documentation/hwmon/mcp9982.rst new file mode 100644 index 000000000000..790ee1697b45 --- /dev/null +++ b/Documentation/hwmon/mcp9982.rst @@ -0,0 +1,111 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +Kernel driver MCP998X +===================== + +Supported chips: + + * Microchip Technology MCP998X/MCP9933 and MCP998XD/MCP9933D + + Prefix: 'mcp9982' + + Datasheet: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf + +Authors: + + - Victor Duicu + +Description +----------- + +This driver implements support for the MCP998X family containing: MCP9982, +MCP9982D, MCP9983, MCP9983D, MCP9984, MCP9984D, MCP9985, MCP9985D, +MCP9933 and MCP9933D. + +The MCP998X Family is a high accuracy 2-wire multichannel automotive +temperature monitor. + +The chips in the family have different numbers of external channels, +ranging from 1 (MCP9982) to 4 channels (MCP9985). Reading diodes in +anti-parallel connection is supported by MCP9984/85/33 and +MCP9984D/85D/33D. Dedicated hardware shutdown circuitry is present +only in MCP998XD and MCP9933D. + +Temperatures are read in millidegrees Celsius, ranging from -64 to +191.875 with 0.125 precision. + +Each channel has a minimum, maximum, and critical limit alongside associated alarms. +The chips also implement a hysteresis mechanism which applies only to the maximum +and critical limits. The relative difference between a limit and its hysteresis +is the same for both and the value is kept in a single register. + +The chips measure temperatures with a variable conversion rate. +Update_interval = Conversion/Second, so the available options are: +- 16000 (ms) = 1 conv/16 sec +- 8000 (ms) = 1 conv/8 sec +- 4000 (ms) = 1 conv/4 sec +- 2000 (ms) = 1 conv/2 sec +- 1000 (ms) = 1 conv/sec +- 500 (ms) = 2 conv/sec +- 250 (ms) = 4 conv/sec +- 125 (ms) = 8 conv/sec +- 64 (ms) = 16 conv/sec +- 32 (ms) = 32 conv/sec +- 16 (ms) = 64 conv/sec + +Usage Notes +----------- + +Parameters that can be configured in devicetree: +- anti-parallel diode mode operation +- resistance error correction on channels 1 and 2 +- resistance error correction on channels 3 and 4 +- power state + +Chips 82/83 and 82D/83D do not support anti-parallel diode mode. +For chips with "D" in the name resistance error correction must be on. +Please see Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml +for details. + +There are two power states: +- Active state: in which the chip is converting on all channels at the +programmed rate. + +- Standby state: in which the host must initiate a conversion cycle. + +Chips with "D" in the name work in Active state only and those without +can work in either state. + +Chips with "D" in the name can't set update interval slower than 1 second. + +Among the hysteresis attributes, only the tempX_crit_hyst ones are writeable +while the others are read only. Setting tempX_crit_hyst writes the difference +between tempX_crit and tempX_crit_hyst in the hysteresis register. The new value +applies automatically to the other limits. At power up the device starts with +a 10 degree hysteresis. + +Sysfs entries +------------- + +The following attributes are supported. The temperature limits and +update_interval are read-write. The attribute tempX_crit_hyst is read-write, +while tempX_max_hyst is read only. All other attributes are read only. + +======================= ================================================== +temp[1-5]_label User name for channel. +temp[1-5]_input Measured temperature for channel. + +temp[1-5]_crit Critical temperature limit. +temp[1-5]_crit_alarm Critical temperature limit alarm. +temp[1-5]_crit_hyst Critical temperature limit hysteresis. + +temp[1-5]_max High temperature limit. +temp[1-5]_max_alarm High temperature limit alarm. +temp[1-5]_max_hyst High temperature limit hysteresis. + +temp[1-5]_min Low temperature limit. +temp[1-5]_min_alarm Low temperature limit alarm. + +update_interval The interval at which the chip will update readings. +======================= ================================================== diff --git a/MAINTAINERS b/MAINTAINERS index 788b92a0b8b1..0a3991c10ade 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17363,6 +17363,8 @@ M: Victor Duicu L: linux-hwmon@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml +F: Documentation/hwmon/mcp9982.rst +F: drivers/hwmon/mcp9982.c MICROCHIP MMC/SD/SDIO MCI DRIVER M: Aubin Constans diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index fb847ab40ab4..14e4cea48acc 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1378,6 +1378,17 @@ config SENSORS_MCP3021 This driver can also be built as a module. If so, the module will be called mcp3021. +config SENSORS_MCP9982 + tristate "Microchip Technology MCP9982 driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to include support for Microchip Technology's MCP998X/33 + and MCP998XD/33D Multichannel Automotive Temperature Monitor Family. + + This driver can also be built as a module. If so, the module + will be called mcp9982. + config SENSORS_MLXREG_FAN tristate "Mellanox FAN driver" depends on MELLANOX_PLATFORM diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 0fce31b43eb1..4788996aa137 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -170,6 +170,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o +obj-$(CONFIG_SENSORS_MCP9982) += mcp9982.o obj-$(CONFIG_SENSORS_TC654) += tc654.o obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c new file mode 100644 index 000000000000..26c69e3388ab --- /dev/null +++ b/drivers/hwmon/mcp9982.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HWMON driver for MCP998X/33 and MCP998XD/33D Multichannel Automotive + * Temperature Monitor Family + * + * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries + * + * Author: Victor Duicu + * + * Datasheet can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MCP9982 Registers */ +#define MCP9982_HIGH_BYTE_ADDR(index) (2 * (index)) +#define MCP9982_ONE_SHOT_ADDR 0x0A +#define MCP9982_INTERNAL_HIGH_LIMIT_ADDR 0x0B +#define MCP9982_INTERNAL_LOW_LIMIT_ADDR 0x0C +#define MCP9982_EXT_HIGH_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0D) +#define MCP9982_EXT_LOW_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0F) +#define MCP9982_THERM_LIMIT_ADDR(index) ((index) + 0x1D) +#define MCP9982_CFG_ADDR 0x22 +#define MCP9982_CONV_ADDR 0x24 +#define MCP9982_HYS_ADDR 0x25 +#define MCP9982_CONSEC_ALRT_ADDR 0x26 +#define MCP9982_ALRT_CFG_ADDR 0x27 +#define MCP9982_RUNNING_AVG_ADDR 0x28 +#define MCP9982_HOTTEST_CFG_ADDR 0x29 +#define MCP9982_STATUS_ADDR 0x2A +#define MCP9982_EXT_FAULT_STATUS_ADDR 0x2B +#define MCP9982_HIGH_LIMIT_STATUS_ADDR 0x2C +#define MCP9982_LOW_LIMIT_STATUS_ADDR 0x2D +#define MCP9982_THERM_LIMIT_STATUS_ADDR 0x2E +#define MCP9982_HOTTEST_HIGH_BYTE_ADDR 0x2F +#define MCP9982_HOTTEST_LOW_BYTE_ADDR 0x30 +#define MCP9982_HOTTEST_STATUS_ADDR 0x31 +#define MCP9982_THERM_SHTDWN_CFG_ADDR 0x32 +#define MCP9982_HRDW_THERM_SHTDWN_LIMIT_ADDR 0x33 +#define MCP9982_EXT_BETA_CFG_ADDR(index) ((index) + 0x33) +#define MCP9982_EXT_IDEAL_ADDR(index) ((index) + 0x35) + +/* MCP9982 Bits */ +#define MCP9982_CFG_MSKAL BIT(7) +#define MCP9982_CFG_RS BIT(6) +#define MCP9982_CFG_ATTHM BIT(5) +#define MCP9982_CFG_RECD12 BIT(4) +#define MCP9982_CFG_RECD34 BIT(3) +#define MCP9982_CFG_RANGE BIT(2) +#define MCP9982_CFG_DA_ENA BIT(1) +#define MCP9982_CFG_APDD BIT(0) + +#define MCP9982_STATUS_BUSY BIT(5) + +/* Constants and default values */ +#define MCP9982_MAX_NUM_CHANNELS 5 +#define MCP9982_BETA_AUTODETECT 16 +#define MCP9982_IDEALITY_DEFAULT 18 +#define MCP9982_OFFSET 64 +#define MCP9982_DEFAULT_CONSEC_ALRT_VAL 112 +#define MCP9982_DEFAULT_HYS_VAL 10 +#define MCP9982_DEFAULT_CONV_VAL 6 +#define MCP9982_WAKE_UP_TIME_US 125000 +#define MCP9982_WAKE_UP_TIME_MAX_US 130000 +#define MCP9982_HIGH_LIMIT_DEFAULT 85000 +#define MCP9982_LOW_LIMIT_DEFAULT 0 + +static const struct hwmon_channel_info * const mcp9985_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST), + HWMON_CHANNEL_INFO(chip, + HWMON_C_UPDATE_INTERVAL), + NULL +}; + +/** + * struct mcp9982_features - features of a mcp9982 instance + * @name: chip's name + * @phys_channels: number of physical channels supported by the chip + * @hw_thermal_shutdown: presence of hardware thermal shutdown circuitry + * @allow_apdd: whether the chip supports enabling APDD + * @has_recd34: whether the chip has the channels that are affected by recd34 + */ +struct mcp9982_features { + const char *name; + u8 phys_channels; + bool hw_thermal_shutdown; + bool allow_apdd; + bool has_recd34; +}; + +static const struct mcp9982_features mcp9933_chip_config = { + .name = "mcp9933", + .phys_channels = 3, + .hw_thermal_shutdown = false, + .allow_apdd = true, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9933d_chip_config = { + .name = "mcp9933d", + .phys_channels = 3, + .hw_thermal_shutdown = true, + .allow_apdd = true, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9982_chip_config = { + .name = "mcp9982", + .phys_channels = 2, + .hw_thermal_shutdown = false, + .allow_apdd = false, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9982d_chip_config = { + .name = "mcp9982d", + .phys_channels = 2, + .hw_thermal_shutdown = true, + .allow_apdd = false, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9983_chip_config = { + .name = "mcp9983", + .phys_channels = 3, + .hw_thermal_shutdown = false, + .allow_apdd = false, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9983d_chip_config = { + .name = "mcp9983d", + .phys_channels = 3, + .hw_thermal_shutdown = true, + .allow_apdd = false, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9984_chip_config = { + .name = "mcp9984", + .phys_channels = 4, + .hw_thermal_shutdown = false, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9984d_chip_config = { + .name = "mcp9984d", + .phys_channels = 4, + .hw_thermal_shutdown = true, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9985_chip_config = { + .name = "mcp9985", + .phys_channels = 5, + .hw_thermal_shutdown = false, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9985d_chip_config = { + .name = "mcp9985d", + .phys_channels = 5, + .hw_thermal_shutdown = true, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const unsigned int mcp9982_update_interval[11] = { + 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 64, 32, 16 +}; + +/* MCP9982 regmap configuration */ +static const struct regmap_range mcp9982_regmap_wr_ranges[] = { + regmap_reg_range(MCP9982_ONE_SHOT_ADDR, MCP9982_CFG_ADDR), + regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_HOTTEST_CFG_ADDR), + regmap_reg_range(MCP9982_THERM_SHTDWN_CFG_ADDR, MCP9982_THERM_SHTDWN_CFG_ADDR), + regmap_reg_range(MCP9982_EXT_BETA_CFG_ADDR(1), MCP9982_EXT_IDEAL_ADDR(4)), +}; + +static const struct regmap_access_table mcp9982_regmap_wr_table = { + .yes_ranges = mcp9982_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_wr_ranges), +}; + +static const struct regmap_range mcp9982_regmap_rd_ranges[] = { + regmap_reg_range(MCP9982_HIGH_BYTE_ADDR(0), MCP9982_CFG_ADDR), + regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_EXT_IDEAL_ADDR(4)), +}; + +static const struct regmap_access_table mcp9982_regmap_rd_table = { + .yes_ranges = mcp9982_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_rd_ranges), +}; + +static bool mcp9982_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MCP9982_ONE_SHOT_ADDR: + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR: + case MCP9982_INTERNAL_LOW_LIMIT_ADDR: + case MCP9982_EXT_LOW_LIMIT_ADDR(1): + case MCP9982_EXT_LOW_LIMIT_ADDR(1) + 1: + case MCP9982_EXT_LOW_LIMIT_ADDR(2): + case MCP9982_EXT_LOW_LIMIT_ADDR(2) + 1: + case MCP9982_EXT_LOW_LIMIT_ADDR(3): + case MCP9982_EXT_LOW_LIMIT_ADDR(3) + 1: + case MCP9982_EXT_LOW_LIMIT_ADDR(4): + case MCP9982_EXT_LOW_LIMIT_ADDR(4) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(1): + case MCP9982_EXT_HIGH_LIMIT_ADDR(1) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(2): + case MCP9982_EXT_HIGH_LIMIT_ADDR(2) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(3): + case MCP9982_EXT_HIGH_LIMIT_ADDR(3) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(4): + case MCP9982_EXT_HIGH_LIMIT_ADDR(4) + 1: + case MCP9982_THERM_LIMIT_ADDR(0): + case MCP9982_THERM_LIMIT_ADDR(1): + case MCP9982_THERM_LIMIT_ADDR(2): + case MCP9982_THERM_LIMIT_ADDR(3): + case MCP9982_THERM_LIMIT_ADDR(4): + case MCP9982_CFG_ADDR: + case MCP9982_CONV_ADDR: + case MCP9982_HYS_ADDR: + case MCP9982_CONSEC_ALRT_ADDR: + case MCP9982_ALRT_CFG_ADDR: + case MCP9982_RUNNING_AVG_ADDR: + case MCP9982_HOTTEST_CFG_ADDR: + case MCP9982_THERM_SHTDWN_CFG_ADDR: + return false; + default: + return true; + } +} + +static const struct regmap_config mcp9982_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &mcp9982_regmap_rd_table, + .wr_table = &mcp9982_regmap_wr_table, + .volatile_reg = mcp9982_is_volatile_reg, + .max_register = MCP9982_EXT_IDEAL_ADDR(4), + .cache_type = REGCACHE_MAPLE, +}; + +/** + * struct mcp9982_priv - information about chip parameters + * @regmap: device register map + * @chip: pointer to structure holding chip features + * @labels: labels of the channels + * @interval_idx: index representing the current update interval + * @enabled_channel_mask: mask containing which channels should be enabled + * @num_channels: number of active physical channels + * @recd34_enable: state of Resistance Error Correction(REC) on channels 3 and 4 + * @recd12_enable: state of Resistance Error Correction(REC) on channels 1 and 2 + * @apdd_enable: state of anti-parallel diode mode + * @run_state: chip is in Run state, otherwise is in Standby state + */ +struct mcp9982_priv { + struct regmap *regmap; + const struct mcp9982_features *chip; + const char *labels[MCP9982_MAX_NUM_CHANNELS]; + unsigned int interval_idx; + unsigned long enabled_channel_mask; + u8 num_channels; + bool recd34_enable; + bool recd12_enable; + bool apdd_enable; + bool run_state; +}; + +static int mcp9982_read_limit(struct mcp9982_priv *priv, u8 address, long *val) +{ + unsigned int limit, reg_high, reg_low; + int ret; + + switch (address) { + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR: + case MCP9982_INTERNAL_LOW_LIMIT_ADDR: + case MCP9982_THERM_LIMIT_ADDR(0): + case MCP9982_THERM_LIMIT_ADDR(1): + case MCP9982_THERM_LIMIT_ADDR(2): + case MCP9982_THERM_LIMIT_ADDR(3): + case MCP9982_THERM_LIMIT_ADDR(4): + ret = regmap_read(priv->regmap, address, &limit); + if (ret) + return ret; + + *val = ((int)limit - MCP9982_OFFSET) * 1000; + + return 0; + case MCP9982_EXT_HIGH_LIMIT_ADDR(1): + case MCP9982_EXT_HIGH_LIMIT_ADDR(2): + case MCP9982_EXT_HIGH_LIMIT_ADDR(3): + case MCP9982_EXT_HIGH_LIMIT_ADDR(4): + case MCP9982_EXT_LOW_LIMIT_ADDR(1): + case MCP9982_EXT_LOW_LIMIT_ADDR(2): + case MCP9982_EXT_LOW_LIMIT_ADDR(3): + case MCP9982_EXT_LOW_LIMIT_ADDR(4): + /* + * In order to keep consistency with reading temperature memory region we will use + * single byte I2C read. + */ + ret = regmap_read(priv->regmap, address, ®_high); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, address + 1, ®_low); + if (ret) + return ret; + + *val = ((reg_high << 8) + reg_low) >> 5; + *val = (*val - (MCP9982_OFFSET << 3)) * 125; + + return 0; + default: + return -EINVAL; + } +} + +static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + unsigned int reg_high, reg_low, hyst, reg_status; + int ret; + u8 addr; + + /* + * In Standby State the conversion cycle must be initated manually in + * order to read fresh temperature values and the status of the alarms. + */ + if (!priv->run_state) { + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + case hwmon_temp_min_alarm: + case hwmon_temp_crit_alarm: + ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1); + if (ret) + return ret; + /* + * When the device is in Standby mode, 125 ms need + * to pass from writing in One Shot register before + * the conversion cycle begins. + */ + usleep_range(MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_MAX_US); + ret = regmap_read_poll_timeout + (priv->regmap, MCP9982_STATUS_ADDR, + reg_status, !(reg_status & MCP9982_STATUS_BUSY), + MCP9982_WAKE_UP_TIME_US, + MCP9982_WAKE_UP_TIME_US * 10); + break; + } + break; + default: + break; + } + } + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + /* + * The only areas of memory that support SMBus block read are 80h->89h + * (temperature memory block) and 90h->97h(status memory block). + * In this context the read operation uses SMBus protocol and the first + * value returned will be the number of addresses that can be read. + * Temperature memory block is 10 bytes long and status memory block is 8 + * bytes long. + * + * Depending on the read instruction used, the chip behaves differently: + * - regmap_bulk_read() when applied to the temperature memory block + * (80h->89h), the chip replies with SMBus block read, including count, + * additionally to the high and the low bytes. This function cannot be + * applied on the memory region 00h->09h(memory area which does not support + * block reads, returns wrong data) unless use_single_read is set in + * regmap_config. + * + * - regmap_multi_reg_read() when applied to the 00h->09h area uses I2C + * and returns only the high and low temperature bytes. When applied to + * the temperature memory block (80h->89h) returns the count till the end of + * the temperature memory block(aka SMBus count). + * + * - i2c_smbus_read_block_data() is not supported by all drivers. + * + * In order to keep consistency with reading limit memory region we will + * use single byte I2C read. + * + * Low register is latched when high temperature register is read. + */ + ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel), ®_high); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1, + ®_low); + if (ret) + return ret; + + *val = ((reg_high << 8) + reg_low) >> 5; + *val = (*val - (MCP9982_OFFSET << 3)) * 125; + + return 0; + case hwmon_temp_max: + if (channel) + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR; + + return mcp9982_read_limit(priv, addr, val); + case hwmon_temp_max_alarm: + *val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR, + BIT(channel)); + if (*val < 0) + return *val; + + return 0; + case hwmon_temp_max_hyst: + if (channel) + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR; + ret = mcp9982_read_limit(priv, addr, val); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst); + if (ret) + return ret; + + *val -= hyst * 1000; + + return 0; + case hwmon_temp_min: + if (channel) + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR; + + return mcp9982_read_limit(priv, addr, val); + case hwmon_temp_min_alarm: + *val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR, + BIT(channel)); + if (*val < 0) + return *val; + + return 0; + case hwmon_temp_crit: + return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val); + case hwmon_temp_crit_alarm: + *val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR, + BIT(channel)); + if (*val < 0) + return *val; + + return 0; + case hwmon_temp_crit_hyst: + ret = mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst); + if (ret) + return ret; + + *val -= hyst * 1000; + + return 0; + default: + return -EINVAL; + } + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + *val = mcp9982_update_interval[priv->interval_idx]; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str = priv->labels[channel]; + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val) +{ + int ret; + unsigned int regh, regl; + + switch (address) { + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR: + case MCP9982_INTERNAL_LOW_LIMIT_ADDR: + case MCP9982_THERM_LIMIT_ADDR(0): + case MCP9982_THERM_LIMIT_ADDR(1): + case MCP9982_THERM_LIMIT_ADDR(2): + case MCP9982_THERM_LIMIT_ADDR(3): + case MCP9982_THERM_LIMIT_ADDR(4): + regh = DIV_ROUND_CLOSEST(val, 1000); + regh = clamp_val(regh, 0, 255); + + return regmap_write(priv->regmap, address, regh); + case MCP9982_EXT_HIGH_LIMIT_ADDR(1): + case MCP9982_EXT_HIGH_LIMIT_ADDR(2): + case MCP9982_EXT_HIGH_LIMIT_ADDR(3): + case MCP9982_EXT_HIGH_LIMIT_ADDR(4): + case MCP9982_EXT_LOW_LIMIT_ADDR(1): + case MCP9982_EXT_LOW_LIMIT_ADDR(2): + case MCP9982_EXT_LOW_LIMIT_ADDR(3): + case MCP9982_EXT_LOW_LIMIT_ADDR(4): + val = DIV_ROUND_CLOSEST(val, 125); + regh = (val >> 3) & 0xff; + regl = (val & 0x07) << 5; + /* Block writing is not supported by the chip. */ + ret = regmap_write(priv->regmap, address, regh); + if (ret) + return ret; + + return regmap_write(priv->regmap, address + 1, regl); + default: + return -EINVAL; + } +} + +static int mcp9982_write_hyst(struct mcp9982_priv *priv, int channel, long val) +{ + int hyst, ret; + int limit; + + val = DIV_ROUND_CLOSEST(val, 1000); + val = clamp_val(val, 0, 255); + + /* Therm register is 8 bits and so it keeps only the integer part of the temperature. */ + ret = regmap_read(priv->regmap, MCP9982_THERM_LIMIT_ADDR(channel), &limit); + if (ret) + return ret; + + hyst = clamp_val(limit - val, 0, 255); + + return regmap_write(priv->regmap, MCP9982_HYS_ADDR, hyst); +} + +static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + unsigned int idx; + u8 addr; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + + /* + * For MCP998XD and MCP9933D update interval + * can't be longer than 1 second. + */ + if (priv->chip->hw_thermal_shutdown) + val = clamp_val(val, 0, 1000); + + idx = find_closest_descending(val, mcp9982_update_interval, + ARRAY_SIZE(mcp9982_update_interval)); + priv->interval_idx = idx; + + return regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx); + default: + return -EINVAL; + } + case hwmon_temp: + val = clamp_val(val, -64000, 191875); + val = val + (MCP9982_OFFSET * 1000); + switch (attr) { + case hwmon_temp_max: + if (channel) + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR; + + return mcp9982_write_limit(priv, addr, val); + case hwmon_temp_min: + if (channel) + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR; + + return mcp9982_write_limit(priv, addr, val); + case hwmon_temp_crit: + return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val); + case hwmon_temp_crit_hyst: + return mcp9982_write_hyst(priv, channel, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct mcp9982_priv *priv = _data; + + if (!test_bit(channel, &priv->enabled_channel_mask)) + return 0; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + if (priv->labels[channel]) + return 0444; + else + return 0; + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_max_hyst: + case hwmon_temp_crit_alarm: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + return 0644; + default: + return 0; + } + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_ops mcp9982_hwmon_ops = { + .is_visible = mcp9982_is_visible, + .read = mcp9982_read, + .read_string = mcp9982_read_label, + .write = mcp9982_write, +}; + +static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv) +{ + long high_limit, low_limit; + unsigned int i; + int ret; + u8 val; + + /* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */ + if (!priv->chip->allow_apdd && priv->apdd_enable == 1) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n"); + + /* Chips with "D" work only in Run state. */ + if (priv->chip->hw_thermal_shutdown && !priv->run_state) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n"); + + /* All chips with "D" in the name must have RECD12 enabled. */ + if (priv->chip->hw_thermal_shutdown && !priv->recd12_enable) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD12.\n"); + /* Chips 83D/84D/85D must have RECD34 enabled. */ + if (priv->chip->hw_thermal_shutdown) + if ((priv->chip->has_recd34 && !priv->recd34_enable)) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD34.\n"); + + /* + * Set default values in registers. + * APDD, RECD12 and RECD34 are active on 0. + */ + val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) | + FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) | + FIELD_PREP(MCP9982_CFG_ATTHM, 1) | + FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) | + FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) | + FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) | + FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable); + + ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val); + if (ret) + return ret; + + /* + * Read initial value from register. + * The convert register utilises only 4 out of 8 bits. + * Numerical values 0->10 set their respective update intervals, + * while numerical values 11->15 default to 1 second. + */ + ret = regmap_read(priv->regmap, MCP9982_CONV_ADDR, &priv->interval_idx); + if (ret) + return ret; + if (priv->interval_idx >= 11) + priv->interval_idx = 4; + + ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_ALRT_CFG_ADDR, 0); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0); + if (ret) + return ret; + + /* + * Only external channels 1 and 2 support beta compensation. + * Set beta auto-detection. + */ + for (i = 1; i < 3; i++) + if (test_bit(i, &priv->enabled_channel_mask)) { + ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i), + MCP9982_BETA_AUTODETECT); + if (ret) + return ret; + } + + high_limit = MCP9982_HIGH_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000); + low_limit = MCP9982_LOW_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000); + + /* Set default values for internal channel limits. */ + if (test_bit(0, &priv->enabled_channel_mask)) { + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR, high_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR, low_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0), high_limit); + if (ret) + return ret; + } + + /* Set ideality factor and limits to default for external channels. */ + for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++) + if (test_bit(i, &priv->enabled_channel_mask)) { + ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i), + MCP9982_IDEALITY_DEFAULT); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i), high_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i), low_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i), high_limit); + if (ret) + return ret; + } + + return 0; +} + +static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + unsigned int reg_nr; + int ret; + + /* Initialise internal channel( which is always present ). */ + priv->labels[0] = "internal diode"; + priv->enabled_channel_mask = 1; + + /* Default values to work on systems without devicetree or firmware nodes. */ + if (!dev_fwnode(dev)) { + priv->num_channels = device_nr_channels; + priv->enabled_channel_mask = BIT(priv->num_channels) - 1; + priv->apdd_enable = false; + priv->recd12_enable = true; + priv->recd34_enable = true; + priv->run_state = true; + return 0; + } + + priv->apdd_enable = + device_property_read_bool(dev, "microchip,enable-anti-parallel"); + + priv->recd12_enable = + device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2"); + + priv->recd34_enable = + device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4"); + + priv->run_state = + device_property_read_bool(dev, "microchip,power-state"); + + priv->num_channels = device_get_child_node_count(dev) + 1; + + if (priv->num_channels > device_nr_channels) + return dev_err_probe(dev, -EINVAL, + "More channels than the chip supports.\n"); + + /* Read information about the external channels. */ + device_for_each_named_child_node_scoped(dev, child, "channel") { + reg_nr = 0; + ret = fwnode_property_read_u32(child, "reg", ®_nr); + if (ret || !reg_nr || reg_nr >= device_nr_channels) + return dev_err_probe(dev, -EINVAL, + "Channel reg is incorrectly set.\n"); + + fwnode_property_read_string(child, "label", &priv->labels[reg_nr]); + set_bit(reg_nr, &priv->enabled_channel_mask); + } + + return 0; +} + +static const struct hwmon_chip_info mcp998x_chip_info = { + .ops = &mcp9982_hwmon_ops, + .info = mcp9985_info, +}; + +static int mcp9982_probe(struct i2c_client *client) +{ + const struct mcp9982_features *chip; + struct device *dev = &client->dev; + struct mcp9982_priv *priv; + struct device *hwmon_dev; + int ret; + + priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config); + + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Cannot initialize register map.\n"); + + dev_set_drvdata(dev, priv); + + chip = i2c_get_match_data(client); + if (!chip) + return -EINVAL; + priv->chip = chip; + + ret = mcp9982_parse_fw_config(dev, chip->phys_channels); + if (ret) + return ret; + + ret = mcp9982_init(dev, priv); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv, + &mcp998x_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id mcp9982_id[] = { + { .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config }, + { .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config }, + { .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config }, + { .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config }, + { .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config }, + { .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config }, + { .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config }, + { .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config }, + { .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config }, + { .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp9982_id); + +static const struct of_device_id mcp9982_of_match[] = { + { + .compatible = "microchip,mcp9933", + .data = &mcp9933_chip_config, + }, { + .compatible = "microchip,mcp9933d", + .data = &mcp9933d_chip_config, + }, { + .compatible = "microchip,mcp9982", + .data = &mcp9982_chip_config, + }, { + .compatible = "microchip,mcp9982d", + .data = &mcp9982d_chip_config, + }, { + .compatible = "microchip,mcp9983", + .data = &mcp9983_chip_config, + }, { + .compatible = "microchip,mcp9983d", + .data = &mcp9983d_chip_config, + }, { + .compatible = "microchip,mcp9984", + .data = &mcp9984_chip_config, + }, { + .compatible = "microchip,mcp9984d", + .data = &mcp9984d_chip_config, + }, { + .compatible = "microchip,mcp9985", + .data = &mcp9985_chip_config, + }, { + .compatible = "microchip,mcp9985d", + .data = &mcp9985d_chip_config, + }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp9982_of_match); + +static struct i2c_driver mcp9982_driver = { + .driver = { + .name = "mcp9982", + .of_match_table = mcp9982_of_match, + }, + .probe = mcp9982_probe, + .id_table = mcp9982_id, +}; +module_i2c_driver(mcp9982_driver); + +MODULE_AUTHOR("Victor Duicu "); +MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 59f0b7befab1859d0e1dde8ad9774ab1858f0b22 Mon Sep 17 00:00:00 2001 From: Ashish Yadav Date: Fri, 10 Apr 2026 12:31:53 +0530 Subject: dt-bindings: hwmon/pmbus: Add Infineon XDP720 Add documentation for the device tree binding of the XDP720 eFuse. Signed-off-by: Ashish Yadav Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20260410070154.3313-2-Ashish.Yadav@infineon.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/infineon,xdp720.yaml | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/infineon,xdp720.yaml diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/infineon,xdp720.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/infineon,xdp720.yaml new file mode 100644 index 000000000000..72bc3a5e7139 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/infineon,xdp720.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/pmbus/infineon,xdp720.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Infineon XDP720 Digital eFuse Controller + +maintainers: + - Ashish Yadav + +description: | + The XDP720 is an eFuse with integrated current sensor and digital + controller. It provides accurate system telemetry (V, I, P, T) and + reports analog current at the IMON pin for post-processing. + + Datasheet: + https://www.infineon.com/assets/row/public/documents/24/49/infineon-xdp720-001-datasheet-en.pdf + +properties: + compatible: + enum: + - infineon,xdp720 + + reg: + maxItems: 1 + + infineon,rimon-micro-ohms: + description: + The value of the RIMON resistor, in micro ohms, required to enable + the system overcurrent protection. + + vdd-vin-supply: + description: + Supply for the VDD_VIN pin (pin 9), the IC controller power supply. + Typically connected to the input bus (VIN) through a 100 ohm / 100 nF + RC filter. + +required: + - compatible + - reg + - vdd-vin-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hwmon@11 { + compatible = "infineon,xdp720"; + reg = <0x11>; + vdd-vin-supply = <&vdd_vin>; + infineon,rimon-micro-ohms = <1098000000>; /* 1.098k ohm */ + }; + }; -- cgit v1.2.3 From a0c370a6fd9634bd55ee10c83643940a88bdd159 Mon Sep 17 00:00:00 2001 From: Ashish Yadav Date: Fri, 10 Apr 2026 12:31:54 +0530 Subject: hwmon:(pmbus/xdp720) Add support for efuse xdp720 Add the pmbus driver for Infineon XDP720 Digital eFuse Controller. Signed-off-by: Ashish Yadav Link: https://lore.kernel.org/r/20260410070154.3313-3-Ashish.Yadav@infineon.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 9 +++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/xdp720.c | 128 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 drivers/hwmon/pmbus/xdp720.c diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index db63acbe08c8..8f4bff375ecb 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -711,6 +711,15 @@ config SENSORS_XDP710 This driver can also be built as a module. If so, the module will be called xdp710. +config SENSORS_XDP720 + tristate "Infineon XDP720 family" + help + If you say yes here you get hardware monitoring support for Infineon + XDP720. + + This driver can also be built as a module. If so, the module will + be called xdp720. + config SENSORS_XDPE152 tristate "Infineon XDPE152 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 63a9870c7b53..7129b62bc00f 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o obj-$(CONFIG_SENSORS_XDP710) += xdp710.o +obj-$(CONFIG_SENSORS_XDP720) += xdp720.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o obj-$(CONFIG_SENSORS_XDPE1A2G7B) += xdpe1a2g7b.o diff --git a/drivers/hwmon/pmbus/xdp720.c b/drivers/hwmon/pmbus/xdp720.c new file mode 100644 index 000000000000..8729a771f216 --- /dev/null +++ b/drivers/hwmon/pmbus/xdp720.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Infineon XDP720 Digital eFuse Controller + * + * Copyright (c) 2026 Infineon Technologies. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * The IMON resistor required to generate the system overcurrent protection. + * Arbitrary default Rimon value: 2k Ohm + */ +#define XDP720_DEFAULT_RIMON 2000000000 /* 2k ohm */ +#define XDP720_TELEMETRY_AVG 0xE9 + +static struct pmbus_driver_info xdp720_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] = 4653, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + .m[PSC_VOLTAGE_OUT] = 4653, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -2, + /* + * Current and Power measurement depends on the RIMON (kOhm) and + * GIMON(microA/A) values. + */ + .m[PSC_CURRENT_OUT] = 24668, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -4, + .m[PSC_POWER] = 4486, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = -1, + .m[PSC_TEMPERATURE] = 54, + .b[PSC_TEMPERATURE] = 22521, + .R[PSC_TEMPERATURE] = -1, + + .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 xdp720_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + int ret; + u32 rimon; + int gimon; + + info = devm_kmemdup(&client->dev, &xdp720_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = devm_regulator_get_enable(&client->dev, "vdd-vin"); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable vdd-vin supply\n"); + + ret = i2c_smbus_read_word_data(client, XDP720_TELEMETRY_AVG); + if (ret < 0) { + dev_err(&client->dev, "Can't get TELEMETRY_AVG\n"); + return ret; + } + + ret >>= 10; /* 10th bit of TELEMETRY_AVG REG for GIMON Value */ + ret &= GENMASK(0, 0); + if (ret == 1) + gimon = 18200; /* output gain 18.2 microA/A */ + else + gimon = 9100; /* output gain 9.1 microA/A */ + + if (of_property_read_u32(client->dev.of_node, + "infineon,rimon-micro-ohms", &rimon)) + rimon = XDP720_DEFAULT_RIMON; /* Default if not set via DT */ + if (rimon == 0) + return -EINVAL; + + /* Adapt the current and power scale for each instance */ + info->m[PSC_CURRENT_OUT] = DIV64_U64_ROUND_CLOSEST((u64) + info->m[PSC_CURRENT_OUT] * rimon * gimon, 1000000000000ULL); + info->m[PSC_POWER] = DIV64_U64_ROUND_CLOSEST((u64) + info->m[PSC_POWER] * rimon * gimon, 1000000000000000ULL); + + return pmbus_do_probe(client, info); +} + +static const struct of_device_id xdp720_of_match[] = { + { .compatible = "infineon,xdp720" }, + {} +}; +MODULE_DEVICE_TABLE(of, xdp720_of_match); + +static const struct i2c_device_id xdp720_id[] = { + { "xdp720" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, xdp720_id); + +static struct i2c_driver xdp720_driver = { + .driver = { + .name = "xdp720", + .of_match_table = xdp720_of_match, + }, + .probe = xdp720_probe, + .id_table = xdp720_id, +}; + +module_i2c_driver(xdp720_driver); + +MODULE_AUTHOR("Ashish Yadav "); +MODULE_DESCRIPTION("PMBus driver for Infineon XDP720 Digital eFuse Controller"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); -- cgit v1.2.3 From ff708b549c4dbecb308fa97e360a8fe0b2f89309 Mon Sep 17 00:00:00 2001 From: Petr Klotz Date: Sun, 12 Apr 2026 00:17:27 +0000 Subject: hwmon: (nct6683) Add customer ID for ASRock B650I Lightning WiFi The ASRock B650I Lightning WiFi motherboard uses an NCT6686D chip with a customer ID of 0x1633. Without this ID, the nct6683 driver fails to recognize the hardware on this board, preventing hardware monitoring from working. Add NCT6683_CUSTOMER_ID_ASROCK6 (0x1633) to the list of supported customer IDs and update the probe function to handle it Signed-off-by: Petr Klotz Link: https://lore.kernel.org/r/20260412000911.9063-2-pklotz0@protonmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6683.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c index 4a8380414038..0581770380cc 100644 --- a/drivers/hwmon/nct6683.c +++ b/drivers/hwmon/nct6683.c @@ -182,6 +182,7 @@ superio_exit(int ioreg) #define NCT6683_CUSTOMER_ID_ASROCK3 0x1631 #define NCT6683_CUSTOMER_ID_ASROCK4 0x163e #define NCT6683_CUSTOMER_ID_ASROCK5 0x1621 +#define NCT6683_CUSTOMER_ID_ASROCK6 0x1633 #define NCT6683_REG_BUILD_YEAR 0x604 #define NCT6683_REG_BUILD_MONTH 0x605 @@ -1245,6 +1246,8 @@ static int nct6683_probe(struct platform_device *pdev) break; case NCT6683_CUSTOMER_ID_ASROCK5: break; + case NCT6683_CUSTOMER_ID_ASROCK6: + break; default: if (!force) return -ENODEV; -- cgit v1.2.3 From a69ae329d425df7e0638903ca74abea615cafc7d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 19 Feb 2026 15:19:36 +0100 Subject: hwmon: (pmbus/tps25990) Don't check for specific errors when parsing properties Instead of checking for the specific error codes (that can be considered a layering violation to some extent) check for the property existence first and then either parse it, or apply a default value. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20260219141936.2259945-1-andriy.shevchenko@linux.intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps25990.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c index c13edd7e1abf..05c6288ecafc 100644 --- a/drivers/hwmon/pmbus/tps25990.c +++ b/drivers/hwmon/pmbus/tps25990.c @@ -402,12 +402,18 @@ static int tps25990_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct pmbus_driver_info *info; - u32 rimon = TPS25990_DEFAULT_RIMON; + const char *propname; + u32 rimon; int ret; - ret = device_property_read_u32(dev, "ti,rimon-micro-ohms", &rimon); - if (ret < 0 && ret != -EINVAL) - return dev_err_probe(dev, ret, "failed to get rimon\n"); + propname = "ti,rimon-micro-ohms"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &rimon); + if (ret) + return dev_err_probe(dev, ret, "failed to get %s\n", propname); + } else { + rimon = TPS25990_DEFAULT_RIMON; + } info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL); if (!info) -- cgit v1.2.3 From 77353904e1847ae51b7e1df14dd73c661ff799a4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 19 Feb 2026 15:05:32 +0100 Subject: hwmon: (isl28022) Don't check for specific errors when parsing properties Instead of checking for the specific error codes (that can be considered a layering violation to some extent) check for the property existence first and then either parse it, or apply a default value. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20260219140532.2259235-1-andriy.shevchenko@linux.intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/isl28022.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/drivers/hwmon/isl28022.c b/drivers/hwmon/isl28022.c index c5a34ceedcdb..96fcfbfff48f 100644 --- a/drivers/hwmon/isl28022.c +++ b/drivers/hwmon/isl28022.c @@ -338,21 +338,28 @@ DEFINE_SHOW_ATTRIBUTE(shunt_voltage); */ static int isl28022_read_properties(struct device *dev, struct isl28022_data *data) { + const char *propname; u32 val; int err; - err = device_property_read_u32(dev, "shunt-resistor-micro-ohms", &val); - if (err == -EINVAL) + propname = "shunt-resistor-micro-ohms"; + if (device_property_present(dev, propname)) { + err = device_property_read_u32(dev, propname, &val); + if (err) + return err; + } else { val = 10000; - else if (err < 0) - return err; + } data->shunt = val; - err = device_property_read_u32(dev, "renesas,shunt-range-microvolt", &val); - if (err == -EINVAL) + propname = "renesas,shunt-range-microvolt"; + if (device_property_present(dev, propname)) { + err = device_property_read_u32(dev, propname, &val); + if (err) + return err; + } else { val = 320000; - else if (err < 0) - return err; + } switch (val) { case 40000: @@ -376,20 +383,19 @@ static int isl28022_read_properties(struct device *dev, struct isl28022_data *da goto shunt_invalid; break; default: - return dev_err_probe(dev, -EINVAL, - "renesas,shunt-range-microvolt invalid value %d\n", - val); + return dev_err_probe(dev, -EINVAL, "%s invalid value %u\n", propname, val); } - err = device_property_read_u32(dev, "renesas,average-samples", &val); - if (err == -EINVAL) + propname = "renesas,average-samples"; + if (device_property_present(dev, propname)) { + err = device_property_read_u32(dev, propname, &val); + if (err) + return err; + } else { val = 1; - else if (err < 0) - return err; + } if (val > 128 || hweight32(val) != 1) - return dev_err_probe(dev, -EINVAL, - "renesas,average-samples invalid value %d\n", - val); + return dev_err_probe(dev, -EINVAL, "%s invalid value %u\n", propname, val); data->average = val; -- cgit v1.2.3 From fb447217c59a13b2fff22d94de2498c185cd9032 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 19 Feb 2026 15:15:32 +0100 Subject: hwmon: (ina233) Don't check for specific errors when parsing properties Instead of checking for the specific error codes (that can be considered a layering violation to some extent) check for the property existence first and then either parse it, or apply a default value. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20260219141532.2259642-1-andriy.shevchenko@linux.intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ina233.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/hwmon/pmbus/ina233.c b/drivers/hwmon/pmbus/ina233.c index 7aebd854763a..652087589c55 100644 --- a/drivers/hwmon/pmbus/ina233.c +++ b/drivers/hwmon/pmbus/ina233.c @@ -85,6 +85,7 @@ static int ina233_read_word_data(struct i2c_client *client, int page, static int ina233_probe(struct i2c_client *client) { struct device *dev = &client->dev; + const char *propname; int ret, m, R; u32 rshunt; u32 max_current; @@ -114,27 +115,28 @@ static int ina233_probe(struct i2c_client *client) /* If INA233 skips current/power, shunt-resistor and current-lsb aren't needed. */ /* read rshunt value (uOhm) */ - ret = device_property_read_u32(dev, "shunt-resistor", &rshunt); - if (ret) { - if (ret != -EINVAL) - return dev_err_probe(dev, ret, "Shunt resistor property read fail.\n"); + propname = "shunt-resistor"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &rshunt); + if (ret) + return dev_err_probe(dev, ret, "%s property read fail.\n", propname); + } else { rshunt = INA233_RSHUNT_DEFAULT; } if (!rshunt) - return dev_err_probe(dev, -EINVAL, - "Shunt resistor cannot be zero.\n"); + return dev_err_probe(dev, -EINVAL, "%s cannot be zero.\n", propname); /* read Maximum expected current value (uA) */ - ret = device_property_read_u32(dev, "ti,maximum-expected-current-microamp", &max_current); - if (ret) { - if (ret != -EINVAL) - return dev_err_probe(dev, ret, - "Maximum expected current property read fail.\n"); + propname = "ti,maximum-expected-current-microamp"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &max_current); + if (ret) + return dev_err_probe(dev, ret, "%s property read fail.\n", propname); + } else { max_current = INA233_MAX_CURRENT_DEFAULT; } if (max_current < 32768) - return dev_err_probe(dev, -EINVAL, - "Maximum expected current cannot less then 32768.\n"); + return dev_err_probe(dev, -EINVAL, "%s cannot be less than 32768.\n", propname); /* Calculate Current_LSB according to the spec formula */ current_lsb = max_current / 32768; -- cgit v1.2.3