From 6fe4043c161978e6fbcd8eee4f23b119279bf705 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 11 Jun 2026 16:05:24 +0200 Subject: [PATCH 001/110] dt-bindings: hwmon: ina2xx: add ina232 compatible The INA232 is a current/power monitor from Texas Instruments sharing the same register map as the other INA2xx. Signed-off-by: Martino Facchin Signed-off-by: Loic Poulain Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260611-monza-ina232-v2-1-e4375ce652d0@oss.qualcomm.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 009d78b30859..a79ade3142fa 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -28,6 +28,7 @@ properties: - ti,ina228 - ti,ina230 - ti,ina231 + - ti,ina232 - ti,ina233 - ti,ina234 - ti,ina237 @@ -114,6 +115,7 @@ allOf: - ti,ina228 - ti,ina230 - ti,ina231 + - ti,ina232 - ti,ina234 - ti,ina237 - ti,ina238 @@ -136,6 +138,7 @@ allOf: - ti,ina226 - ti,ina230 - ti,ina231 + - ti,ina232 - ti,ina234 - ti,ina260 - ti,ina700 From d1db9b08fbf399ace18cb99adfd609d7e5582b73 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 11 Jun 2026 16:05:25 +0200 Subject: [PATCH 002/110] hwmon: (ina2xx) Add support for INA232 The INA232 is a current/power monitor. It shares the same register layout as the other chips in the series and uses the INA226 default configuration, but differs in its electrical characteristics: Signed-off-by: Martino Facchin Signed-off-by: Loic Poulain Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260611-monza-ina232-v2-2-e4375ce652d0@oss.qualcomm.com [groeck: Rephrased commit message] Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 449a72c6b40b..5c6dc2c370d8 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -123,6 +123,7 @@ static const struct regmap_config ina2xx_regmap_config = { enum ina2xx_ids { ina219, ina226, + ina232, ina234, ina260, sy24655 @@ -197,6 +198,20 @@ static const struct ina2xx_config ina2xx_config[] = { .current_shift = 4, .has_update_interval = true, }, + [ina232] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .shunt_div = 400, + .shunt_voltage_shift = 0, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1600, + .power_lsb_factor = 32, + .has_alerts = true, + .has_ishunt = false, + .has_power_average = false, + .current_shift = 0, + .has_update_interval = true, + }, [ina260] = { .config_default = INA260_CONFIG_DEFAULT, .shunt_div = 400, @@ -1014,6 +1029,7 @@ static const struct i2c_device_id ina2xx_id[] = { { .name = "ina226", .driver_data = ina226 }, { .name = "ina230", .driver_data = ina226 }, { .name = "ina231", .driver_data = ina226 }, + { .name = "ina232", .driver_data = ina232 }, { .name = "ina234", .driver_data = ina234 }, { .name = "ina260", .driver_data = ina260 }, { .name = "sy24655", .driver_data = sy24655 }, @@ -1046,6 +1062,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { .compatible = "ti,ina231", .data = (void *)ina226 }, + { + .compatible = "ti,ina232", + .data = (void *)ina232 + }, { .compatible = "ti,ina234", .data = (void *)ina234 From 4cbf6f017a5c04d04a668b6c80764ec599ada0c2 Mon Sep 17 00:00:00 2001 From: Ziming Zhu Date: Fri, 12 Jun 2026 11:03:02 +0800 Subject: [PATCH 003/110] dt-bindings: hwmon: pmbus: Add bindings for Silergy SQ24860 Add devicetree binding documentation for the Silergy SQ24860 eFuse. The device is a PMBus hardware monitoring device which reports voltage, current, power, and temperature telemetry. The board-specific IMON resistor value is described with silergy,rimon-micro-ohms. Signed-off-by: Ziming Zhu Reviewed-by: Conor Dooley Link: https://lore.kernel.org/r/20260612030304.5165-2-zmzhu0630@163.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/silergy,sq24860.yaml | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml new file mode 100644 index 000000000000..03ef82c11e1a --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/pmbus/silergy,sq24860.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Silergy SQ24860 eFuse + +maintainers: + - Ziming Zhu + +description: + The Silergy SQ24860 is an integrated, high-current circuit protection and + power management device with PMBus interface. + +properties: + compatible: + const: silergy,sq24860 + + reg: + maxItems: 1 + + silergy,rimon-micro-ohms: + description: + Micro-ohms value of the resistance installed between the IMON pin and + the ground reference. + + interrupts: + description: PMBus SMBAlert interrupt. + maxItems: 1 + + regulators: + type: object + description: + List of regulators provided by this controller. + + properties: + vout: + $ref: /schemas/regulator/regulator.yaml# + type: object + unevaluatedProperties: false + + additionalProperties: false + +required: + - compatible + - reg + - silergy,rimon-micro-ohms + +additionalProperties: false + +examples: + - | + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hw-monitor@40 { + compatible = "silergy,sq24860"; + reg = <0x40>; + + interrupt-parent = <&gpio>; + interrupts = <42 8>; + silergy,rimon-micro-ohms = <1600000000>; + + regulators { + cpu0_vout: vout { + regulator-name = "main_cpu0"; + }; + }; + }; + }; From 93ab13d896418e6929e8b2a6618d9b789ae56f4d Mon Sep 17 00:00:00 2001 From: Ziming Zhu Date: Fri, 12 Jun 2026 11:03:03 +0800 Subject: [PATCH 004/110] hwmon: (pmbus) Add support for Silergy SQ24860 Add PMBus hwmon support for the Silergy SQ24860 eFuse. The driver reports input voltage, output voltage, auxiliary voltage, input current, input power, and temperature. It also exposes peak, average, and minimum history attributes, sample count configuration, and maps the manufacturer-specific VIREF register to the generic input over-current fault limit attribute. The IMON resistor value is read from the silergy,rimon-micro-ohms device property and used to configure the input current calibration gain. Signed-off-by: Ziming Zhu Link: https://lore.kernel.org/r/20260612030304.5165-3-zmzhu0630@163.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 19 ++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/sq24860.c | 430 ++++++++++++++++++++++++++++++++++ 3 files changed, 450 insertions(+) create mode 100644 drivers/hwmon/pmbus/sq24860.c diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index c8cda160b5f8..b308fcee7405 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -677,6 +677,25 @@ config SENSORS_STEF48H28 This driver can also be built as a module. If so, the module will be called stef48h28. +config SENSORS_SQ24860 + tristate "Silergy SQ24860" + help + If you say yes here you get hardware monitoring support for Silergy + SQ24860 eFuse. + + This driver can also be built as a module. If so, the module will + be called sq24860. + +config SENSORS_SQ24860_REGULATOR + bool "Regulator support for SQ24860" + depends on SENSORS_SQ24860 && REGULATOR + default SENSORS_SQ24860 + help + If you say yes here you get regulator support for Silergy SQ24860. + The regulator is registered through the PMBus regulator framework and + can be used to control the output exposed by the device. + This option is only useful if regulator framework support is needed. + config SENSORS_STPDDC60 tristate "ST STPDDC60" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index ffc05f493213..e01adacc8b76 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o +obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o diff --git a/drivers/hwmon/pmbus/sq24860.c b/drivers/hwmon/pmbus/sq24860.c new file mode 100644 index 000000000000..30202a4b34cf --- /dev/null +++ b/drivers/hwmon/pmbus/sq24860.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Ziming Zhu + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pmbus.h" + +#define SQ24860_IIN_CAL_GAIN 0x38 +#define SQ24860_READ_VAUX 0xd0 +#define SQ24860_READ_VIN_MIN 0xd1 +#define SQ24860_READ_VIN_PEAK 0xd2 +#define SQ24860_READ_IIN_PEAK 0xd4 +#define SQ24860_READ_PIN_PEAK 0xd5 +#define SQ24860_READ_TEMP_AVG 0xd6 +#define SQ24860_READ_TEMP_PEAK 0xd7 +#define SQ24860_READ_VOUT_MIN 0xda +#define SQ24860_READ_VIN_AVG 0xdc +#define SQ24860_READ_VOUT_AVG 0xdd +#define SQ24860_READ_IIN_AVG 0xde +#define SQ24860_READ_PIN_AVG 0xdf +#define SQ24860_VIREF 0xe0 +#define SQ24860_PK_MIN_AVG 0xea +#define PK_MIN_AVG_RST_PEAK BIT(7) +#define PK_MIN_AVG_RST_AVG BIT(6) +#define PK_MIN_AVG_RST_MIN BIT(5) +#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0) +#define SQ24860_MFR_WRITE_PROTECT 0xf8 +#define SQ24860_UNLOCKED BIT(7) + +#define SQ24860_8B_SHIFT 2 +#define SQ24860_IIN_OCF_NUM 1000000 +#define SQ24860_IIN_OCF_DIV 129278 +#define SQ24860_IIN_OCF_OFF 165 + +#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ + PK_MIN_AVG_RST_AVG | \ + PK_MIN_AVG_RST_MIN) +#define SQ24860_MAX_SAMPLES BIT(FIELD_MAX(PK_MIN_AVG_AVG_CNT)) +/* + * Arbitrary default Rimon value: 1.6kOhm + */ +#define SQ24860_DEFAULT_RIMON 1600000000 +#define SQ24860_GIMON 18180 + +#define SQ24860_VAUX_DIV 20 + +static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon) +{ + u64 temp = 6400ULL * 1000000000ULL * 1000ULL; + u64 denom; + u64 word; + + if (!rimon) + return -EINVAL; + + denom = (u64)rimon * SQ24860_GIMON; + word = div64_u64(temp, denom); + if (!word || word > U16_MAX) + return -EINVAL; + + return i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN, + (u16)word); +} + +static int sq24860_mfr_write_protect_set(struct i2c_client *client, + u8 protect) +{ + u8 val; + + switch (protect) { + case 0: + val = 0xa2; + break; + case PB_WP_ALL: + val = 0x0; + break; + default: + return -EINVAL; + } + + return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT, + val); +} + +static int sq24860_mfr_write_protect_get(struct i2c_client *client) +{ + int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT); + + if (ret < 0) + return ret; + + return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL; +} + +static int sq24860_read_word_data(struct i2c_client *client, + int page, int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VIN_PEAK); + break; + + case PMBUS_VIRT_READ_VIN_MIN: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VIN_MIN); + break; + + case PMBUS_VIRT_READ_VIN_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VIN_AVG); + break; + + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VOUT_MIN); + break; + + case PMBUS_VIRT_READ_VOUT_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VOUT_AVG); + break; + + case PMBUS_VIRT_READ_IIN_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_IIN_AVG); + break; + + case PMBUS_VIRT_READ_IIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_IIN_PEAK); + break; + + case PMBUS_VIRT_READ_TEMP_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_TEMP_AVG); + break; + + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_TEMP_PEAK); + break; + + case PMBUS_VIRT_READ_PIN_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_PIN_AVG); + break; + + case PMBUS_VIRT_READ_PIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_PIN_PEAK); + break; + + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VAUX); + if (ret < 0) + break; + ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV); + break; + + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_IIN_OC_WARN_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + /* + * These registers provide an 8 bits value instead of a + * 10bits one. Just shifting twice the register value is + * enough to make the sensor type conversion work, even + * if the datasheet provides different m, b and R for + * those. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + break; + ret <<= SQ24860_8B_SHIFT; + break; + + case PMBUS_IIN_OC_FAULT_LIMIT: + /* + * VIREF directly sets the over-current limit at which the eFuse + * will turn the FET off and trigger a fault. Expose it through + * this generic property instead of a manufacturer specific one. + */ + ret = pmbus_read_byte_data(client, page, SQ24860_VIREF); + if (ret < 0) + break; + ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM, + SQ24860_IIN_OCF_DIV); + ret += SQ24860_IIN_OCF_OFF; + break; + + case PMBUS_VIRT_SAMPLES: + ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG); + if (ret < 0) + break; + ret = BIT(FIELD_GET(PK_MIN_AVG_AVG_CNT, ret)); + break; + + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IIN_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = 0; + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int sq24860_write_word_data(struct i2c_client *client, + int page, int reg, u16 value) +{ + int ret; + + switch (reg) { + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_IIN_OC_WARN_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + value = max_t(s16, (s16)value, 0); + value >>= SQ24860_8B_SHIFT; + value = clamp_val(value, 0, 0xff); + ret = pmbus_write_word_data(client, page, reg, value); + break; + + case PMBUS_IIN_OC_FAULT_LIMIT: + value = max_t(s16, (s16)value, SQ24860_IIN_OCF_OFF); + value -= SQ24860_IIN_OCF_OFF; + value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV, + SQ24860_IIN_OCF_NUM); + value = clamp_val(value, 0, 0x3f); + ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value); + break; + + case PMBUS_VIRT_SAMPLES: + value = clamp_val(value, 1, SQ24860_MAX_SAMPLES); + value = ilog2(value); + ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG, + PK_MIN_AVG_AVG_CNT, + FIELD_PREP(PK_MIN_AVG_AVG_CNT, value)); + break; + + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IIN_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + /* + * SQ24860 has history resets based on MIN/AVG/PEAK instead of per + * sensor type. Exposing this quirk in hwmon is not desirable so + * reset MIN, AVG and PEAK together. Even is there effectively only + * one reset, which resets everything, expose the 5 entries so + * userspace is not required map a sensor type to another to trigger + * a reset + */ + ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG, + PK_MIN_AVG_RST_MASK, + PK_MIN_AVG_RST_MASK); + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int sq24860_read_byte_data(struct i2c_client *client, + int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_WRITE_PROTECT: + ret = sq24860_mfr_write_protect_get(client); + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int sq24860_write_byte_data(struct i2c_client *client, + int page, int reg, u8 byte) +{ + int ret; + + switch (reg) { + case PMBUS_WRITE_PROTECT: + ret = sq24860_mfr_write_protect_set(client, byte); + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) +static const struct regulator_desc sq24860_reg_desc[] = { + PMBUS_REGULATOR_ONE_NODE("vout"), +}; +#endif + +static const struct pmbus_driver_info sq24860_base_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 64, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .format[PSC_VOLTAGE_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 64, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + /* + * Current and power measurements depend on the calibration gain + * programmed from the board-specific IMON resistor value. + */ + .format[PSC_CURRENT_IN] = direct, + .m[PSC_CURRENT_IN] = 16, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 0, + .format[PSC_POWER] = direct, + .m[PSC_POWER] = 2, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | + PMBUS_HAVE_VMON | + PMBUS_HAVE_IIN | + PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_SAMPLES, + .read_word_data = sq24860_read_word_data, + .write_word_data = sq24860_write_word_data, + .read_byte_data = sq24860_read_byte_data, + .write_byte_data = sq24860_write_byte_data, + +#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) + .reg_desc = sq24860_reg_desc, + .num_regulators = ARRAY_SIZE(sq24860_reg_desc), +#endif +}; + +static const struct i2c_device_id sq24860_i2c_id[] = { + { "sq24860" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id); + +static const struct of_device_id sq24860_of_match[] = { + { .compatible = "silergy,sq24860" }, + {} +}; +MODULE_DEVICE_TABLE(of, sq24860_of_match); + +static int sq24860_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + u32 rimon; + int ret; + + if (device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon)) + rimon = SQ24860_DEFAULT_RIMON; + ret = sq24860_write_iin_cal_gain(client, rimon); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to set gain\n"); + info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static struct i2c_driver sq24860_driver = { + .driver = { + .name = "sq24860", + .of_match_table = sq24860_of_match, + }, + .probe = sq24860_probe, + .id_table = sq24860_i2c_id, +}; +module_i2c_driver(sq24860_driver); + +MODULE_AUTHOR("Ziming Zhu "); +MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); From efd053e3f6829f55fd1d3680d198c7acd15177a4 Mon Sep 17 00:00:00 2001 From: Ziming Zhu Date: Fri, 12 Jun 2026 11:03:04 +0800 Subject: [PATCH 005/110] hwmon: Add documentation for SQ24860 Document the supported sysfs attributes for the Silergy SQ24860 PMBus hwmon driver. Signed-off-by: Ziming Zhu Link: https://lore.kernel.org/r/20260612030304.5165-4-zmzhu0630@163.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/sq24860.rst | 96 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 Documentation/hwmon/sq24860.rst diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 29130df44d12..74d67fa21d19 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -253,6 +253,7 @@ Hardware Monitoring Kernel Drivers smsc47m1 sparx5-temp spd5118 + sq24860 stpddc60 surface_fan sy7636a-hwmon diff --git a/Documentation/hwmon/sq24860.rst b/Documentation/hwmon/sq24860.rst new file mode 100644 index 000000000000..f0182b955d8a --- /dev/null +++ b/Documentation/hwmon/sq24860.rst @@ -0,0 +1,96 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver sq24860 +===================== + +Supported chips: + + * Silergy SQ24860 + + Prefix: 'sq24860' + +Author: + + Ziming Zhu + +Description +------------ + +This driver implements support for the Silergy SQ24860 eFuse. The device is an +integrated circuit protection and power management device with a PMBus +interface. + +The device supports direct format for reading input voltage, output voltage, +auxiliary voltage, input current, input power, and temperature. + +The current and power measurement scale depends on the resistor connected +between the IMON pin and ground. The resistor value can be configured with the +``silergy,rimon-micro-ohms`` device tree property. See +``Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml`` for details. + +Due to the specificities of the chip, all history reset attributes are tied +together. Resetting the history of one sensor resets the history of all sensors. + +Sysfs entries +------------- + +The following attributes are supported. Limits are read-write; all other +attributes are read-only. + +======================= ====================================================== +in1_label "vin" +in1_input Measured input voltage. +in1_average Average measured input voltage. +in1_min Minimum input voltage limit. +in1_lcrit Critical low input voltage limit. +in1_max Maximum input voltage limit. +in1_crit Critical high input voltage limit. +in1_min_alarm Input voltage low warning alarm. +in1_lcrit_alarm Input voltage low fault alarm. +in1_max_alarm Input voltage high warning alarm. +in1_crit_alarm Input voltage high fault alarm. +in1_highest Historical maximum input voltage. +in1_lowest Historical minimum input voltage. +in1_reset_history Write any value to reset history. + +in2_label "vmon" +in2_input Measured auxiliary input voltage. + +in3_label "vout1" +in3_input Measured output voltage. +in3_average Average measured output voltage. +in3_min Minimum output voltage limit. +in3_min_alarm Output voltage low alarm. +in3_lowest Historical minimum output voltage. +in3_reset_history Write any value to reset history. + +curr1_label "iin" +curr1_input Measured input current. +curr1_average Average measured input current. +curr1_max Maximum input current warning limit. +curr1_crit Critical input over-current fault limit. +curr1_max_alarm Input current warning alarm. +curr1_crit_alarm Input over-current fault alarm. +curr1_highest Historical maximum input current. +curr1_reset_history Write any value to reset history. + +power1_label "pin" +power1_input Measured input power. +power1_average Average measured input power. +power1_max Maximum input power warning limit. +power1_alarm Input power warning alarm. +power1_input_highest Historical maximum input power. +power1_reset_history Write any value to reset history. + +temp1_input Measured temperature. +temp1_average Average measured temperature. +temp1_max Maximum temperature warning limit. +temp1_crit Critical temperature fault limit. +temp1_max_alarm Temperature warning alarm. +temp1_crit_alarm Temperature fault alarm. +temp1_highest Historical maximum temperature. +temp1_reset_history Write any value to reset history. + +samples Number of samples used for average values. +======================= ====================================================== + From 47afef9f4cf24571caf63c30ecccfcbc0ba5e79d Mon Sep 17 00:00:00 2001 From: Potin Lai Date: Mon, 15 Jun 2026 17:49:16 +0800 Subject: [PATCH 006/110] dt-bindings: hwmon: pmbus: ti,lm25066: add current limit properties Add a 'ti,current-range' string property to configure the device's Current Limit (CL) behavior to "high" or "low" via the register, overriding the physical CL pin setting. This configuration is supported on LM25066, LM5064, LM5066, and LM5066i. LM25056 is excluded because it does not support configuring the current limit via the DEVICE_SETUP register (bit 2 of DEVICE_SETUP is reserved). The values "low" and "high" map to the respective low/high threshold voltages of the chips: - LM25066: low = 25 mV, high = 46 mV - LM5064, LM5066, LM5066i: low = 26 mV, high = 50 mV Signed-off-by: Potin Lai Reviewed-by: Conor Dooley Link: https://lore.kernel.org/r/20260615-lm25066-cl-config-v3-1-decb4f5b0b77@gmail.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/ti,lm25066.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml index a20f140dc79a..fe42daabaaa8 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml @@ -46,12 +46,32 @@ properties: additionalProperties: false + ti,current-range: + description: | + Configure the current limit setting. When present, this property + overrides the hardware setting of the physical CL pin by configuring + the DEVICE_SETUP register. + - "low": maps to 25 mV (LM25066) or 26 mV (LM5064, LM5066, LM5066i) + - "high": maps to 46 mV (LM25066) or 50 mV (LM5064, LM5066, LM5066i) + $ref: /schemas/types.yaml#/definitions/string + enum: + - low + - high + required: - compatible - reg allOf: - $ref: /schemas/hwmon/hwmon-common.yaml# + - if: + properties: + compatible: + contains: + const: ti,lm25056 + then: + properties: + ti,current-range: false unevaluatedProperties: false From a86e9ca4eda2445b582aad4cff42504fa8be6f61 Mon Sep 17 00:00:00 2001 From: Potin Lai Date: Mon, 15 Jun 2026 17:49:17 +0800 Subject: [PATCH 007/110] hwmon: (pmbus/lm25066) add current limit configuration support Add support for the 'ti,current-range' devicetree property to configure the current limit via the DEVICE_SETUP (0xD9) register, overriding the physical CL pin setting. This configuration is supported on all chips in this driver (LM25066, LM5064, LM5066, LM5066i) except LM25056. The property values "low" and "high" map to: - LM25066: low = 25 mV, high = 46 mV - LM5064, LM5066, LM5066i: low = 26 mV, high = 50 mV The Bit 4 mapping to High/Low current limit is handled dynamically on probe because it is swapped for LM25066 compared to the other supported chips. Signed-off-by: Potin Lai Link: https://lore.kernel.org/r/20260615-lm25066-cl-config-v3-2-decb4f5b0b77@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/lm25066.c | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index c797e2e8258f..497802c646e9 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -35,6 +35,7 @@ enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; #define LM25066_READ_AVG_PIN 0xdf #define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ +#define LM25066_DEV_SETUP_CL_CFG BIT(2) /* Current limit configuration */ #define LM25066_SAMPLES_FOR_AVG_MAX 4096 @@ -485,6 +486,42 @@ static int lm25066_probe(struct i2c_client *client) data->id = (enum chips)(unsigned long)i2c_get_match_data(client); + if (data->id != lm25056) { + int config_new = config; + const char *cl_setting; + int ret; + + if (!of_property_read_string(client->dev.of_node, + "ti,current-range", &cl_setting)) { + config_new |= LM25066_DEV_SETUP_CL_CFG; + if (strcmp(cl_setting, "high") == 0) { + if (data->id == lm25066) + config_new |= LM25066_DEV_SETUP_CL; + else + config_new &= ~LM25066_DEV_SETUP_CL; + } else if (strcmp(cl_setting, "low") == 0) { + if (data->id == lm25066) + config_new &= ~LM25066_DEV_SETUP_CL; + else + config_new |= LM25066_DEV_SETUP_CL; + } else { + dev_err(&client->dev, + "invalid current-range setting: %s\n", + cl_setting); + return -EINVAL; + } + } + + if (config_new != config) { + ret = i2c_smbus_write_byte_data(client, + LM25066_DEVICE_SETUP, + config_new); + if (ret < 0) + return ret; + config = config_new; + } + } + info = &data->info; info->pages = 1; From bf74d14fba4d308d86ac42093e8d75ea7674d620 Mon Sep 17 00:00:00 2001 From: Ximing Zhang Date: Tue, 16 Jun 2026 16:15:49 +0400 Subject: [PATCH 008/110] hwmon: coretemp: Fix documentation wording Fix two minor wording issues in the coretemp documentation. Signed-off-by: Ximing Zhang Link: https://lore.kernel.org/r/20260616121549.29484-1-xzhangjr@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/coretemp.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/coretemp.rst b/Documentation/hwmon/coretemp.rst index f63b21f24d42..349301683381 100644 --- a/Documentation/hwmon/coretemp.rst +++ b/Documentation/hwmon/coretemp.rst @@ -44,9 +44,9 @@ Temperature known as TjMax is the maximum junction temperature of processor, which depends on the CPU model. See table below. At this temperature, protection mechanism will perform actions to forcibly cool down the processor. Alarm may be raised, if the temperature grows enough (more than TjMax) to trigger -the Out-Of-Spec bit. Following table summarizes the exported sysfs files: +the Out-Of-Spec bit. The following table summarizes the exported sysfs files: -All Sysfs entries are named with their core_id (represented here by 'X'). +All sysfs entries are named with their core_id (represented here by 'X'). ================= ======================================================== tempX_input Core temperature (in millidegrees Celsius). From 6a9ad0ac9bd6f4234f5e468850e9f1e26707e37a Mon Sep 17 00:00:00 2001 From: Ximing Zhang Date: Tue, 16 Jun 2026 16:56:31 +0400 Subject: [PATCH 009/110] hwmon: (coretemp) Clarify attr_size comment The attr_size field stores the number of sysfs attributes created for a temperature data group. Clarify the comment to describe the field directly. No functional change intended. Signed-off-by: Ximing Zhang Link: https://lore.kernel.org/r/20260616125631.35537-1-xzhangjr@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 6215ea49faaa..d47ba64788e1 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -68,7 +68,7 @@ enum coretemp_attr_index { * This value is passed as "id" field to rdmsr/wrmsr functions. * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, * from where the temperature values should be read. - * @attr_size: Total number of pre-core attrs displayed in the sysfs. + * @attr_size: Number of sysfs attributes in this temperature data group. */ struct temp_data { int temp; From bee42c631116d61edbec7853b8791598369571b3 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Fri, 19 Jun 2026 23:44:57 -0700 Subject: [PATCH 010/110] hwmon: (pmbus/xdpe1a2g7b) Add regulator support Register both dual-loop output channels with the regulator framework by populating regulator_desc[] and num_regulators in pmbus_driver_info. This exposes the standard PMBus OPERATION-based on/off control and status reporting to consumer drivers. Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260619-xdpe1a2g7b-v1-1-428c3847be0d@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 10 ++++++++++ drivers/hwmon/pmbus/xdpe1a2g7b.c | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index b308fcee7405..68fadd8fe589 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -822,6 +822,16 @@ config SENSORS_XDPE1A2G7B This driver can also be built as a module. If so, the module will be called xdpe1a2g7b. +config SENSORS_XDPE1A2G7B_REGULATOR + bool "Regulator support for XDPE1A2G7B and compatibles" + depends on SENSORS_XDPE1A2G7B && REGULATOR + help + If you say yes here you get regulator support for Infineon + XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers. + + This enables the controllers to be used as regulator devices, + providing voltage control through the regulator framework. + config SENSORS_XDPE122 tristate "Infineon XDPE122 family" help diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c index 971e7b73752e..9455ef443b2b 100644 --- a/drivers/hwmon/pmbus/xdpe1a2g7b.c +++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c @@ -57,6 +57,13 @@ static int xdpe1a2g7b_identify(struct i2c_client *client, return 0; } +#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) +static const struct regulator_desc xdpe1a2g7b_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), +}; +#endif + static struct pmbus_driver_info xdpe1a2g7b_info = { .pages = XDPE1A2G7B_PAGE_NUM, .identify = xdpe1a2g7b_identify, @@ -72,6 +79,10 @@ static struct pmbus_driver_info xdpe1a2g7b_info = { .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, +#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) + .num_regulators = XDPE1A2G7B_PAGE_NUM, + .reg_desc = xdpe1a2g7b_reg_desc, +#endif }; static int xdpe1a2g7b_probe(struct i2c_client *client) From aa28a6c3a2f5ebe5afc3c113854a85f87ae679d2 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Thu, 25 Jun 2026 19:04:22 +0300 Subject: [PATCH 011/110] dt-bindings: hwmon: chipcap2: Add label property Add support for an optional label property similar to other hwmon devices. This allows, in case of boards with multiple CHIPCAP2 sensors, to assign distinct names to each instance. Signed-off-by: Flaviu Nistor Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260625160423.17882-1-flaviu.nistor@gmail.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/amphenol,chipcap2.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml b/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml index 17351fdbefce..56b0cecfca5f 100644 --- a/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml +++ b/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml @@ -45,6 +45,8 @@ properties: - const: low - const: high + label: true + vdd-supply: description: Dedicated, controllable supply-regulator to reset the device and @@ -55,6 +57,9 @@ required: - reg - vdd-supply +allOf: + - $ref: hwmon-common.yaml# + additionalProperties: false examples: @@ -72,6 +77,7 @@ examples: <5 IRQ_TYPE_EDGE_RISING>, <6 IRQ_TYPE_EDGE_RISING>; interrupt-names = "ready", "low", "high"; + label = "Room"; vdd-supply = <®_vdd>; }; }; From 5a260163e15fe898e190ba14c25da81ce15f6018 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Thu, 25 Jun 2026 19:04:23 +0300 Subject: [PATCH 012/110] hwmon: (chipcap2) 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 Reviewed-by: Javier Carrasco Link: https://lore.kernel.org/r/20260625160423.17882-2-flaviu.nistor@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/chipcap2.rst | 2 ++ drivers/hwmon/chipcap2.c | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/chipcap2.rst b/Documentation/hwmon/chipcap2.rst index dc165becc64c..c38d87b91b69 100644 --- a/Documentation/hwmon/chipcap2.rst +++ b/Documentation/hwmon/chipcap2.rst @@ -70,4 +70,6 @@ humidity1_min_hyst: RW humidity low hystersis humidity1_max_hyst: RW humidity high hystersis humidity1_min_alarm: RO humidity low alarm indicator humidity1_max_alarm: RO humidity high alarm indicator +humidity1_label: RO descriptive name for the sensor +temp1_label: RO descriptive name for the sensor =============================== ======= ======================================== diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 4aecf463180f..086571d556b7 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #define CC2_START_CM 0xA0 #define CC2_START_NOM 0x80 @@ -83,6 +85,7 @@ struct cc2_data { struct i2c_client *client; struct regulator *regulator; const char *name; + const char *label; int irq_ready; int irq_low; int irq_high; @@ -449,6 +452,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, switch (attr) { case hwmon_humidity_input: return 0444; + case hwmon_humidity_label: + return cc2->label ? 0444 : 0; case hwmon_humidity_min_alarm: return cc2->rh_alarm.low_alarm_visible ? 0444 : 0; case hwmon_humidity_max_alarm: @@ -466,6 +471,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, switch (attr) { case hwmon_temp_input: return 0444; + case hwmon_temp_label: + return cc2->label ? 0444 : 0; default: return 0; } @@ -552,6 +559,16 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) return 0; } +static int cc2_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct cc2_data *data = dev_get_drvdata(dev); + + *str = data->label; + + return 0; +} + static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -670,8 +687,9 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) } static const struct hwmon_channel_info *cc2_info[] = { - HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), - HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX | + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_LABEL | + HWMON_H_MIN | HWMON_H_MAX | HWMON_H_MIN_HYST | HWMON_H_MAX_HYST | HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM), NULL @@ -680,6 +698,7 @@ static const struct hwmon_channel_info *cc2_info[] = { static const struct hwmon_ops cc2_hwmon_ops = { .is_visible = cc2_is_visible, .read = cc2_read, + .read_string = cc2_read_string, .write = cc2_write, }; @@ -710,6 +729,8 @@ static int cc2_probe(struct i2c_client *client) return dev_err_probe(dev, PTR_ERR(data->regulator), "Failed to get regulator\n"); + device_property_read_string(dev, "label", &data->label); + ret = cc2_request_ready_irq(data, dev); if (ret) return dev_err_probe(dev, ret, "Failed to request ready irq\n"); From b0374b52b4b56034d46897d00f72dd4351e9a433 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 26 Jun 2026 10:21:42 +0300 Subject: [PATCH 013/110] dt-bindings: adm1275: ROHM BD12780 hot-swap controller Support ROHM BD12780 and BD12780A hot-swap controllers, which are largely compatible with the Analog Devices adm1278. Main difference between the BD12780 and the BD12780A is, that the BD12780 has one I2C address configuration pin more (ADDR3) than the BD12780A. Introduce own compatibles for both variants but require the BD12780A to always have the BD12780 as a fall-back. Signed-off-by: Matti Vaittinen Acked-by: Conor Dooley Link: https://lore.kernel.org/r/2b7d5bb8cba773d0bba1d6779f0e6daa6a40eed4.1782458224.git.mazziesaccount@gmail.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/adi,adm1275.yaml | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml index d6a7517f2a50..503e93756584 100644 --- a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml @@ -25,19 +25,34 @@ description: | https://www.silergy.com/ download/downloadFile?id=5669&type=product&ftype=note + The BD12780 and BD12780A are hot-swap controllers from ROHM. They are + functionally compatible with the ADM1278. The main difference between + the BD12780A and the BD12780 is amount of configurable I2C addresses. + + Datasheets: + https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780muv-lb-e.pdf + https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780amuv-lb-e.pdf + properties: compatible: - enum: - - adi,adm1075 - - adi,adm1272 - - adi,adm1273 - - adi,adm1275 - - adi,adm1276 - - adi,adm1278 - - adi,adm1281 - - adi,adm1293 - - adi,adm1294 - - silergy,mc09c + oneOf: + - enum: + - adi,adm1075 + - adi,adm1272 + - adi,adm1273 + - adi,adm1275 + - adi,adm1276 + - adi,adm1278 + - adi,adm1281 + - adi,adm1293 + - adi,adm1294 + - rohm,bd12780 + - silergy,mc09c + + # Require BD12780 as a fall-back for BD12780A. + - items: + - const: rohm,bd12780a + - const: rohm,bd12780 reg: maxItems: 1 @@ -104,6 +119,7 @@ allOf: - adi,adm1281 - adi,adm1293 - adi,adm1294 + - rohm,bd12780 - silergy,mc09c then: properties: From d475fcefbb145b7cd0ebacf17a1def2ff65148a7 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 26 Jun 2026 10:24:21 +0300 Subject: [PATCH 014/110] hwmon: (pmbus/adm1275) Support module auto-loading Populating the i2c_device_id -table is not enough to make the driver module automatically load when device-tree node for the power-monitor is parsed at boot. Adding the of_device_id tables causes the driver module to be automatically load at boot. Testing has been done with rather old Debian system. When inspecting the generated module-aliases with the insmod, following entries seem to be the difference: alias: of:N*T*Cadi,adm1075C* alias: of:N*T*Cadi,adm1075 I suspect these are required for the module loading to work. Signed-off-by: Matti Vaittinen Link: https://lore.kernel.org/r/9eca6831f9fe2d781bb88337397c39b10e36f5c6.1782458224.git.mazziesaccount@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1275.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index d3c3ff85dba3..0a8b32218573 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -870,9 +870,25 @@ static int adm1275_probe(struct i2c_client *client) return pmbus_do_probe(client, info); } +static const struct of_device_id adm1275_of_match[] = { + { .compatible = "adi,adm1075", }, + { .compatible = "adi,adm1272", }, + { .compatible = "adi,adm1273", }, + { .compatible = "adi,adm1275", }, + { .compatible = "adi,adm1276", }, + { .compatible = "adi,adm1278", }, + { .compatible = "adi,adm1281", }, + { .compatible = "adi,adm1293", }, + { .compatible = "adi,adm1294", }, + { .compatible = "silergy,mc09c", }, + { } +}; +MODULE_DEVICE_TABLE(of, adm1275_of_match); + static struct i2c_driver adm1275_driver = { .driver = { .name = "adm1275", + .of_match_table = adm1275_of_match, }, .probe = adm1275_probe, .id_table = adm1275_id, From 3f48a6ffe159f11d555a2173c844bbb0590196ab Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 26 Jun 2026 10:24:38 +0300 Subject: [PATCH 015/110] doc: hwmon: Add ROHM BD12780 and BD12780A Add the ROHM BD12780 and the BD12780A to the list of the ICs supported by the adm1275 driver. Signed-off-by: Matti Vaittinen Link: https://lore.kernel.org/r/5ff4a4a7a939cd642522fde407b5c9fa123a7089.1782458224.git.mazziesaccount@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/adm1275.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/hwmon/adm1275.rst b/Documentation/hwmon/adm1275.rst index cf923f20fa52..8a793dd2b412 100644 --- a/Documentation/hwmon/adm1275.rst +++ b/Documentation/hwmon/adm1275.rst @@ -67,6 +67,22 @@ Supported chips: Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1293_1294.pdf + * ROHM Semiconductor BD12780 + + Prefix: 'bd12780' + + Addresses scanned: - + + Datasheet: https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780muv-lb-e.pdf + + * ROHM Semiconductor BD12780A + + Prefix: 'bd12780' + + Addresses scanned: - + + Datasheet: https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780amuv-lb-e.pdf + * Silergy SQ24905C Prefix: 'mc09c' From 5a419373bc1c77185e99a5c7306d81121269b2ef Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 26 Jun 2026 10:24:55 +0300 Subject: [PATCH 016/110] hwmon: (pmbus/adm1275) Support ROHM BD12780 ROHM BD12780 and BD12780A are hot-swap controllers. They are largely similar to Analog Devices ADM1278. Besides the ID registers and some added functionality, the BD12780 and BD12780A mark PMON_CONFIG bits [15:14] as reserved. Hence TSFILT setting must be omitted on these ICs. The BD12780 has 3 pins usable for configuring the I2C address. The BD12780A lists the ADDR3-pin as "not connect". Support ROHM BD12780 and BD12780A controllers. Signed-off-by: Matti Vaittinen Link: https://lore.kernel.org/r/e1e6e6218c08b562311356ef9c57378d32c26b08.1782458224.git.mazziesaccount@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 2 +- drivers/hwmon/pmbus/adm1275.c | 55 ++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 68fadd8fe589..5e3a84416083 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -52,7 +52,7 @@ config SENSORS_ADM1275 help If you say yes here you get hardware monitoring support for Analog Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, - ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and + ADM1293, ADM1294, ROHM BD12780, and SQ24905C Hot-Swap Controller and Digital Power Monitors. This driver can also be built as a module. If so, the module will diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 0a8b32218573..eecb00371afa 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -19,7 +19,7 @@ #include "pmbus.h" enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, - adm1293, adm1294, sq24905c }; + adm1293, adm1294, bd12780, sq24905c }; #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) @@ -47,6 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, #define ADM1278_VOUT_EN BIT(1) #define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) +/* The BD12780 data sheets mark TSFILT bit as reserved. */ +#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN) #define ADM1293_IRANGE_25 0 #define ADM1293_IRANGE_50 BIT(6) @@ -487,6 +489,7 @@ static const struct i2c_device_id adm1275_id[] = { { .name = "adm1281", .driver_data = adm1281 }, { .name = "adm1293", .driver_data = adm1293 }, { .name = "adm1294", .driver_data = adm1294 }, + { .name = "bd12780", .driver_data = bd12780 }, { .name = "mc09c", .driver_data = sq24905c }, { } }; @@ -494,12 +497,13 @@ MODULE_DEVICE_TABLE(i2c, adm1275_id); /* Enable VOUT & TEMP1 if not enabled (disabled by default) */ static int adm1275_enable_vout_temp(struct adm1275_data *data, - struct i2c_client *client, int config) + struct i2c_client *client, int config, + u16 defconfig) { int ret; - if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) { - config |= ADM1278_PMON_DEFCONFIG; + if ((config & defconfig) != defconfig) { + config |= defconfig; ret = adm1275_write_pmon_config(data, client, config); if (ret < 0) { dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n"); @@ -535,7 +539,8 @@ static int adm1275_probe(struct i2c_client *client) return ret; } if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) && - (ret != 2 || strncmp(block_buffer, "SY", 2))) { + (ret != 2 || strncmp(block_buffer, "SY", 2)) && + (ret != 4 || strncmp(block_buffer, "ROHM", 4))) { dev_err(&client->dev, "Unsupported Manufacturer ID\n"); return -ENODEV; } @@ -562,7 +567,7 @@ static int adm1275_probe(struct i2c_client *client) if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || mid->driver_data == adm1278 || mid->driver_data == adm1281 || mid->driver_data == adm1293 || mid->driver_data == adm1294 || - mid->driver_data == sq24905c) + mid->driver_data == bd12780 || mid->driver_data == sq24905c) config_read_fn = i2c_smbus_read_word_data; else config_read_fn = i2c_smbus_read_byte_data; @@ -666,7 +671,8 @@ static int adm1275_probe(struct i2c_client *client) PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - ret = adm1275_enable_vout_temp(data, client, config); + ret = adm1275_enable_vout_temp(data, client, config, + ADM1278_PMON_DEFCONFIG); if (ret) return ret; @@ -728,13 +734,45 @@ static int adm1275_probe(struct i2c_client *client) PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - ret = adm1275_enable_vout_temp(data, client, config); + ret = adm1275_enable_vout_temp(data, client, config, + ADM1278_PMON_DEFCONFIG); if (ret) return ret; if (config & ADM1278_VIN_EN) info->func[0] |= PMBUS_HAVE_VIN; break; + + /* + * The BD12780 is almost functionally identical with the adm1278 above. + * Only differences visible to the driver are lack of TSFILT bits and + * different identification register contents. + */ + case bd12780: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + data->have_power_sampling = true; + + coefficients = adm1278_coefficients; + vindex = 0; + cindex = 1; + pindex = 2; + tindex = 3; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + ret = adm1275_enable_vout_temp(data, client, config, + BD12780_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + + break; case adm1293: case adm1294: data->have_iout_min = true; @@ -880,6 +918,7 @@ static const struct of_device_id adm1275_of_match[] = { { .compatible = "adi,adm1281", }, { .compatible = "adi,adm1293", }, { .compatible = "adi,adm1294", }, + { .compatible = "rohm,bd12780", }, { .compatible = "silergy,mc09c", }, { } }; From 1170dfa0c3611d8a37e812e62f2be4747ca44e92 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 26 Jun 2026 10:25:33 +0300 Subject: [PATCH 017/110] doc: adm1275: Add ROHM BD12790 Add the ROHM BD12790 to the list of the ICs supported by the adm1275 driver. Signed-off-by: Matti Vaittinen Link: https://lore.kernel.org/r/9bafcfe37190b5a3925e8dcfa3b59f8d240234ba.1782458224.git.mazziesaccount@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/adm1275.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/hwmon/adm1275.rst b/Documentation/hwmon/adm1275.rst index 8a793dd2b412..d8495be313b8 100644 --- a/Documentation/hwmon/adm1275.rst +++ b/Documentation/hwmon/adm1275.rst @@ -83,6 +83,14 @@ Supported chips: Datasheet: https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780amuv-lb-e.pdf + * ROHM Semiconductor BD12790 + + Prefix: 'bd12790' + + Addresses scanned: - + + Datasheet: - + * Silergy SQ24905C Prefix: 'mc09c' From b66f500d6259c6a8fbdc97588236dcda1ee45215 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 26 Jun 2026 10:25:13 +0300 Subject: [PATCH 018/110] dt-bindings: adm1275: ROHM BD12790 hot-swap controller Support ROHM BD12790 hot-swap controller which is largely compatible with the Analog Devices adm1272. Signed-off-by: Matti Vaittinen Acked-by: Conor Dooley Link: https://lore.kernel.org/r/753a6ded3e1aff1aaba7d932f0a883f789d9dfaa.1782458224.git.mazziesaccount@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml index 503e93756584..283cd1662689 100644 --- a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml @@ -33,6 +33,9 @@ description: | https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780muv-lb-e.pdf https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780amuv-lb-e.pdf + The BD12790 is a ROHM hot-swap controller, functionally similar to the + ADM1272. + properties: compatible: oneOf: @@ -47,6 +50,7 @@ properties: - adi,adm1293 - adi,adm1294 - rohm,bd12780 + - rohm,bd12790 - silergy,mc09c # Require BD12780 as a fall-back for BD12780A. @@ -103,6 +107,7 @@ allOf: enum: - adi,adm1272 - adi,adm1273 + - rohm,bd12790 then: properties: adi,volt-curr-sample-average: From 73a76fa6e865aaf82bea6df26afd3efb06e6ea88 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 26 Jun 2026 10:26:02 +0300 Subject: [PATCH 019/110] hwmon: (pmbus/adm1275) Support ROHM BD12790 Add support for ROHM BD12790 hot-swap controller which is largely similar to Analog Devices adm1272. The BD12790 uses the same selectable 60V/100V voltage ranges and 15mV/30mV current-sense ranges as the ADM1272, and the same VRANGE (bit 5) and IRANGE (bit 0) layout in PMON_CONFIG. It therefore uses a dedicated coefficient table that mirrors adm1272_coefficients, with the following differences derived from BD12790 datasheet Table 1 (p.18): - power 60V/30mV: m=17560 (vs. 17561) - power 100V/30mV: m=10536 (vs. 10535) - temperature: b=31880 (vs. 31871, reflecting T[11:0] = 4.2*T + 3188) Signed-off-by: Matti Vaittinen Assisted-by: GitHub Copilot:claude-sonnet-4.6 Link: https://lore.kernel.org/r/b209c1b47712b69f17b52cfd7a7a38ed76024ca7.1782458224.git.mazziesaccount@gmail.com [groeck: Fixed comment describing BD12790 coefficients] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 4 +- drivers/hwmon/pmbus/adm1275.c | 79 +++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 5e3a84416083..ae003a6eac30 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -52,8 +52,8 @@ config SENSORS_ADM1275 help If you say yes here you get hardware monitoring support for Analog Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, - ADM1293, ADM1294, ROHM BD12780, and SQ24905C Hot-Swap Controller and - Digital Power Monitors. + ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C + Hot-Swap Controller and Digital Power Monitors. This driver can also be built as a module. If so, the module will be called adm1275. diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index eecb00371afa..174f9bcba7e2 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -19,7 +19,7 @@ #include "pmbus.h" enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, - adm1293, adm1294, bd12780, sq24905c }; + adm1293, adm1294, bd12780, bd12790, sq24905c }; #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) @@ -47,7 +47,7 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, #define ADM1278_VOUT_EN BIT(1) #define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) -/* The BD12780 data sheets mark TSFILT bit as reserved. */ +/* The BD127[89]0 data sheets mark TSFILT bit as reserved. */ #define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN) #define ADM1293_IRANGE_25 0 @@ -136,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = { }; +/* + * BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18) + * and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m. + * + * Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V) + * -> m = 6770, R=-2 / m = 4062, R=-2 + * Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV) + * -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R + * Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72 + * -> m = round(k * 10^(-3-R)), R=-2 for 60V/15mV, R=-3 for the other three + * Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1 + */ +static const struct coefficients bd12790_coefficients[] = { + [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ + [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ + [2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */ + [3] = { 664, 20480, -1 }, /* current, vsense range 30mV */ + [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ + [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ + [6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */ + [7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */ + [8] = { 42, 31880, -1 }, /* temperature */ +}; + static const struct coefficients adm1275_coefficients[] = { [0] = { 19199, 0, -2 }, /* voltage, vrange set */ [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ @@ -490,6 +514,7 @@ static const struct i2c_device_id adm1275_id[] = { { .name = "adm1293", .driver_data = adm1293 }, { .name = "adm1294", .driver_data = adm1294 }, { .name = "bd12780", .driver_data = bd12780 }, + { .name = "bd12790", .driver_data = bd12790 }, { .name = "mc09c", .driver_data = sq24905c }, { } }; @@ -567,7 +592,8 @@ static int adm1275_probe(struct i2c_client *client) if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || mid->driver_data == adm1278 || mid->driver_data == adm1281 || mid->driver_data == adm1293 || mid->driver_data == adm1294 || - mid->driver_data == bd12780 || mid->driver_data == sq24905c) + mid->driver_data == bd12780 || mid->driver_data == bd12790 || + mid->driver_data == sq24905c) config_read_fn = i2c_smbus_read_word_data; else config_read_fn = i2c_smbus_read_byte_data; @@ -647,6 +673,7 @@ static int adm1275_probe(struct i2c_client *client) data->have_power_sampling = true; coefficients = adm1272_coefficients; + vindex = (config & ADM1275_VRANGE) ? 1 : 0; cindex = (config & ADM1272_IRANGE) ? 3 : 2; /* pindex depends on the combination of the above */ @@ -679,6 +706,51 @@ static int adm1275_probe(struct i2c_client *client) if (config & ADM1278_VIN_EN) info->func[0] |= PMBUS_HAVE_VIN; break; + + /* + * The BD12790 is almost identical to the adm1272. Only the defconfig + * and coefficients have minor differences. + */ + case bd12790: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + data->have_power_sampling = true; + + coefficients = bd12790_coefficients; + + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ + switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { + case 0: + default: + pindex = 4; + break; + case ADM1275_VRANGE: + pindex = 5; + break; + case ADM1272_IRANGE: + pindex = 6; + break; + case ADM1275_VRANGE | ADM1272_IRANGE: + pindex = 7; + break; + } + tindex = 8; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + ret = adm1275_enable_vout_temp(data, client, config, + BD12780_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; case adm1275: if (device_config & ADM1275_IOUT_WARN2_SELECT) data->have_oc_fault = true; @@ -919,6 +991,7 @@ static const struct of_device_id adm1275_of_match[] = { { .compatible = "adi,adm1293", }, { .compatible = "adi,adm1294", }, { .compatible = "rohm,bd12780", }, + { .compatible = "rohm,bd12790", }, { .compatible = "silergy,mc09c", }, { } }; From 92ead3c1f17f7a94f72d672024ef2b1838f3f8de Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Sat, 27 Jun 2026 17:37:30 +0100 Subject: [PATCH 020/110] hwmon: (xgene) Stop writing PCC shared memory signature ACPI specification defines the generic PCC shared memory signature as the PCC base signature ORed with the subspace ID. ACPI 6.6 added clarification that the signature is populated by the platform and verified by OSPM. The PCC mailbox controller now validates the signature when the channel is requested. Stop rewriting the signature before each command and leave the platform-populated value intact. Cc: Guenter Roeck Cc: linux-hwmon@vger.kernel.org Signed-off-by: Sudeep Holla Link: https://lore.kernel.org/r/20260627-acpi_pcc_signature-v1-2-c1b7268d4fdc@kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/xgene-hwmon.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index 11c5d80428cd..38b140c23c88 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -133,10 +133,6 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) init_completion(&ctx->rd_complete); ctx->resp_pending = true; - /* Write signature for subspace */ - WRITE_ONCE(generic_comm_base->signature, - cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx)); - /* Write to the shared command region */ WRITE_ONCE(generic_comm_base->command, cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR)); From ecc0eb5e95695ebfd7e51eecc65e80fff421cdcf Mon Sep 17 00:00:00 2001 From: Lars Randers Date: Mon, 29 Jun 2026 10:30:59 +0100 Subject: [PATCH 021/110] hwmon: Driver for the temp/voltage sensor on PolarFire SoC Add a driver for the temperature and voltage sensors on PolarFire SoC. The temperature reports how hot the die is, and the voltages are the SoC's 1.05, 1.8 and 2.5 volt rails respectively. The hardware supports alarms in theory, but there is an erratum that prevents clearing them once triggered, so no support is added for them. The hardware measures voltage with 16 bits, of which 1 is a sign bit and the remainder holds the voltage as a fixed point integer value. It's improbable that the hardware will work if the voltages are negative, so the driver ignores the sign bits. There's no dt support etc here because this is the child of a simple-mfd syscon. Signed-off-by: Lars Randers Co-developed-by: Conor Dooley Signed-off-by: Conor Dooley Link: https://lore.kernel.org/r/20260629-wriggle-headscarf-c85a4070dddb@spud Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/tvs-mpfs.rst | 53 +++++ MAINTAINERS | 1 + drivers/hwmon/Kconfig | 13 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/tvs-mpfs.c | 390 +++++++++++++++++++++++++++++++ 6 files changed, 459 insertions(+) create mode 100644 Documentation/hwmon/tvs-mpfs.rst create mode 100644 drivers/hwmon/tvs-mpfs.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 74d67fa21d19..226789376217 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -273,6 +273,7 @@ Hardware Monitoring Kernel Drivers tps53679 tps546d24 tsc1641 + tvs-mpfs twl4030-madc-hwmon ucd9000 ucd9200 diff --git a/Documentation/hwmon/tvs-mpfs.rst b/Documentation/hwmon/tvs-mpfs.rst new file mode 100644 index 000000000000..1035812f363a --- /dev/null +++ b/Documentation/hwmon/tvs-mpfs.rst @@ -0,0 +1,53 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver tvs-mpfs +====================== + +Supported chips: + + * PolarFire SoC + +Authors: + + - Conor Dooley + - Lars Randers + +Description +----------- + +This driver implements support for the temperature and voltage sensors on +PolarFire SoC. The temperature reports how hot the die is, and the voltages are +the SoC's 1.05, 1.8 and 2.5 volt rails respectively. + + +Usage Notes +----------- + +update_interval has a permitted range of 0 to 8 milliseconds. + +Temperatures are read in millidegrees Celsius, but the hardware measures in +degrees Kelvin, storing the result as 11.4 fixed point data, for a maximum +value of 2047.9375 degrees Kelvin. + +Voltages are read in millivolts. The hardware measures in millivolts, storing +the value as 12.3 fixed point data, for a maximum of 4095.875 millivolts. +The minimum value reportable by the driver is 0 volts, although the hardware +is capable of measuring negative values. + +Sysfs entries +------------- + +The following attributes are supported. update_interval is read-write, as are +the enables. All other attributes are read only. + +======================= ==================================================== +temp1_label Fixed name for channel. +temp1_input Measured temperature for channel. +temp1_enable Enable/disable for channel. + +in[0-2]_label Fixed name for channel. +in[0-2]_input Measured voltage for channel. +in[0-2]_enable Enable/disable for channel. + +update_interval The interval at which the chip will update readings. +======================= ==================================================== diff --git a/MAINTAINERS b/MAINTAINERS index 8014b9f8253e..0c612a1e5920 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23259,6 +23259,7 @@ F: drivers/char/hw_random/mpfs-rng.c F: drivers/clk/microchip/clk-mpfs*.c F: drivers/firmware/microchip/mpfs-auto-update.c F: drivers/gpio/gpio-mpfs.c +F: drivers/hwmon/tvs-mpfs.c F: drivers/i2c/busses/i2c-microchip-corei2c.c F: drivers/mailbox/mailbox-mpfs.c F: drivers/pci/controller/plda/pcie-microchip-host.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2bfbcc033d59..b2e445b5536e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -942,6 +942,19 @@ config SENSORS_JC42 This driver can also be built as a module. If so, the module will be called jc42. +config SENSORS_POLARFIRE_SOC_TVS + tristate "PolarFire SoC (MPFS) temperature and voltage sensor" + depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST + depends on MFD_SYSCON + help + This driver adds support for the PolarFire SoC (MPFS) Temperature and + Voltage Sensor. + + To compile this driver as a module, choose M here. the + module will be called tvs-mpfs. + + If unsure, say N. + config SENSORS_POWERZ tristate "ChargerLAB POWER-Z USB-C tester" depends on USB diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 63effc0ab8d1..caea1f55222d 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -197,6 +197,7 @@ obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o +obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c new file mode 100644 index 000000000000..aad00676518b --- /dev/null +++ b/drivers/hwmon/tvs-mpfs.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Author: Lars Randers + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MPFS_TVS_CTRL 0x08 +#define MPFS_TVS_OUTPUT0 0x24 +#define MPFS_TVS_OUTPUT1 0x28 + +#define MPFS_TVS_CTRL_TEMP_VALID BIT(19) +#define MPFS_TVS_CTRL_V2P5_VALID BIT(18) +#define MPFS_TVS_CTRL_V1P8_VALID BIT(17) +#define MPFS_TVS_CTRL_V1P05_VALID BIT(16) + +#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3) +#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2) +#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1) +#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0) +#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0) + +/* + * For all of these the value in millivolts is stored in 16 bits, with an upper + * sign bit and a lower 3 bits of decimal. These masks discard the sign bit and + * decimal places, because if Linux is running these voltages cannot be negative + * and so avoid having to convert to two's complement. + */ +#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19) +#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3) +#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3) + +/* + * The register map claims that the temperature is stored in bits 31:16, but + * application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor" + * says that 31 is reserved. Temperature is in kelvin, so what's probably a + * sign bit has no value anyway. + */ +#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16) + +#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8) +#define MPFS_TVS_INTERVAL_OFFSET 8 +/* The interval register is in increments of 32 us */ +#define MPFS_TVS_INTERVAL_SCALE 32 +/* with 254 usable increments of 32 us available, 8 ms is the integer limit */ +#define MPFS_TVS_INTERVAL_MAX_MS 8 + +/* 273.1875 in 11.4 fixed-point notation */ +#define MPFS_TVS_K_TO_C 0x1113 + +enum mpfs_tvs_sensors { + SENSOR_V1P05 = 0, + SENSOR_V1P8, + SENSOR_V2P5, +}; + +static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" }; + +struct mpfs_tvs { + struct regmap *regmap; +}; + +static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr, + int channel, long *val) +{ + u32 tmp, control; + + if (attr != hwmon_in_input && attr != hwmon_in_enable) + return -EOPNOTSUPP; + + regmap_read(data->regmap, MPFS_TVS_CTRL, &control); + + switch (channel) { + case SENSOR_V2P5: + if (attr == hwmon_in_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control); + break; + } + + if (!(control & MPFS_TVS_CTRL_V2P5_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); + *val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp); + break; + case SENSOR_V1P8: + if (attr == hwmon_in_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control); + break; + } + + if (!(control & MPFS_TVS_CTRL_V1P8_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); + *val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp); + break; + case SENSOR_V1P05: + if (attr == hwmon_in_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control); + break; + } + + if (!(control & MPFS_TVS_CTRL_V1P05_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); + *val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr, + int channel, long val) +{ + u32 tmp; + + if (attr != hwmon_in_enable) + return -EOPNOTSUPP; + + if (val > 1 || val < 0) + return -EINVAL; + + switch (channel) { + case SENSOR_V2P5: + tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_V2P5_ENABLE, tmp); + break; + case SENSOR_V1P8: + tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_V1P8_ENABLE, tmp); + break; + case SENSOR_V1P05: + tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_V1P05_ENABLE, tmp); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val) +{ + u32 tmp, control; + + if (attr != hwmon_temp_input && attr != hwmon_temp_enable) + return -EOPNOTSUPP; + + regmap_read(data->regmap, MPFS_TVS_CTRL, &control); + + if (attr == hwmon_temp_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control); + return 0; + } + + if (!(control & MPFS_TVS_CTRL_TEMP_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); + *val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp); + *val -= MPFS_TVS_K_TO_C; + *val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */ + + return 0; +} + +static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val) +{ + u32 tmp; + + if (attr != hwmon_temp_enable) + return -EOPNOTSUPP; + + if (val > 1 || val < 0) + return -EINVAL; + + tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_TEMP_ENABLE, tmp); + + return 0; +} + +static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val) +{ + u32 tmp; + + if (attr != hwmon_chip_update_interval) + return -EOPNOTSUPP; + + regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp); + *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp); + *val *= MPFS_TVS_INTERVAL_SCALE; + *val = roundup(*val, 1000); + *val /= 1000; + + return 0; +} + +static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val) +{ + long temp = val; + + if (attr != hwmon_chip_update_interval) + return -EOPNOTSUPP; + + temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS); + + temp *= 1000; + temp /= MPFS_TVS_INTERVAL_SCALE; + + temp <<= MPFS_TVS_INTERVAL_OFFSET; + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_INTERVAL_MASK, temp); + + return 0; +} + +static umode_t mpfs_tvs_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_chip && attr == hwmon_chip_update_interval) + return 0644; + + if (type == hwmon_temp) { + switch (attr) { + case hwmon_temp_enable: + return 0644; + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + default: + return 0; + } + } + + if (type == hwmon_in) { + switch (attr) { + case hwmon_in_enable: + return 0644; + case hwmon_in_input: + case hwmon_in_label: + return 0444; + default: + return 0; + } + } + + return 0; +} + +static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct mpfs_tvs *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + return mpfs_tvs_temp_read(data, attr, val); + case hwmon_in: + return mpfs_tvs_voltage_read(data, attr, channel, val); + case hwmon_chip: + return mpfs_tvs_interval_read(data, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct mpfs_tvs *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + return mpfs_tvs_temp_write(data, attr, val); + case hwmon_in: + return mpfs_tvs_voltage_write(data, attr, channel, val); + case hwmon_chip: + return mpfs_tvs_interval_write(data, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static int mpfs_tvs_read_labels(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, + const char **str) +{ + switch (type) { + case hwmon_temp: + *str = "Die Temp"; + return 0; + case hwmon_in: + *str = mpfs_tvs_voltage_labels[channel]; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops mpfs_tvs_ops = { + .is_visible = mpfs_tvs_is_visible, + .read_string = mpfs_tvs_read_labels, + .read = mpfs_tvs_read, + .write = mpfs_tvs_write, +}; + +static const struct hwmon_channel_info *mpfs_tvs_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_ENABLE), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE), + NULL +}; + +static const struct hwmon_chip_info mpfs_tvs_chip_info = { + .ops = &mpfs_tvs_ops, + .info = mpfs_tvs_info, +}; + +static int mpfs_tvs_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev; + struct mpfs_tvs *data; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = device_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(data->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap), + "Failed to find syscon regmap\n"); + + /* + * It's an MMIO regmap with no resources, there's nothing that can fail + * and return an error + */ + regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL); + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs", + data, + &mpfs_tvs_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev), + "hwmon device registration failed.\n"); + + return 0; +} + +static struct platform_driver mpfs_tvs_driver = { + .probe = mpfs_tvs_probe, + .driver = { + .name = "mpfs-tvs", + }, +}; +module_platform_driver(mpfs_tvs_driver); + +MODULE_AUTHOR("Lars Randers "); +MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver"); +MODULE_LICENSE("GPL"); From de0181c95b961a9d65ed5923ec3f4011944a6ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 30 Jun 2026 22:57:51 +0200 Subject: [PATCH 022/110] hwmon: (cros_ec) Implement custom kelvin to celsius conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ChromeOS EC APIs use integers representing degrees kelvin for temperatures. The default conversions from linux/units.h will then always convert these integer degrees celsius with a 150 millidegree offset. This is a bit confusing, as it also differs from other CrOS EC tooling. Internally the EC uses a kelvin to celsius offset of a round 273, so the current conversion is also not entirely accurate. Implement a custom conversion which preserves round values. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260630-cros_ec-hwmon-overflow-v1-1-3d2ecd3eb0f2@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/cros_ec_hwmon.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index 03bfcc40bb7c..e91e2103a6c7 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -146,9 +146,17 @@ static bool cros_ec_hwmon_is_error_temp(u8 temp) temp == EC_TEMP_SENSOR_NOT_CALIBRATED; } +/* This differs slightly from the variant in units.h to avoid rounding inconsistencies. */ +#define CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS (-273000) + +static long cros_ec_hwmon_kelvin_to_millicelsius(long t) +{ + return t * MILLIDEGREE_PER_DEGREE + CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS; +} + static long cros_ec_hwmon_temp_to_millicelsius(u8 temp) { - return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); + return cros_ec_hwmon_kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); } static bool cros_ec_hwmon_attr_is_temp_threshold(u32 attr) @@ -227,7 +235,7 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, cros_ec_hwmon_attr_to_thres(attr), &threshold); if (ret == 0) - *val = kelvin_to_millicelsius(threshold); + *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); } } From 9a916636384dafe75c258b2799ccbef45e632137 Mon Sep 17 00:00:00 2001 From: Huan He Date: Tue, 30 Jun 2026 17:11:22 +0800 Subject: [PATCH 023/110] dt-bindings: hwmon: Add Eswin EIC7700 PVT sensor Add device tree binding documentation for ESWIN EIC7700 Voltage and Temperature sensor. The EIC7700 SoC integrates two PVT instances for monitoring SoC and DDR power domains respectively. Signed-off-by: Yulin Lu Signed-off-by: Huan He Reviewed-by: Conor Dooley Signed-off-by: Xuyang Dong Link: https://lore.kernel.org/r/20260630091122.1462-1-dongxuyang@eswincomputing.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/eswin,eic7700-pvt.yaml | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/eswin,eic7700-pvt.yaml diff --git a/Documentation/devicetree/bindings/hwmon/eswin,eic7700-pvt.yaml b/Documentation/devicetree/bindings/hwmon/eswin,eic7700-pvt.yaml new file mode 100644 index 000000000000..58ec8635dce3 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/eswin,eic7700-pvt.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/eswin,eic7700-pvt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ESWIN EIC7700 PVT Sensor + +maintainers: + - Yulin Lu + - Huan He + +description: + ESWIN EIC7700 SoC integrates embedded voltage and temperature sensors to + monitor the internal SoC environment. The system includes two PVT sensor + instances. The PVT0 monitors the main SoC power domain. The PVT1 sensor + monitors the DDR core power domain. + +allOf: + - $ref: /schemas/hwmon/hwmon-common.yaml# + +properties: + compatible: + const: eswin,eic7700-pvt + + reg: + maxItems: 1 + + clocks: + items: + - description: PVT enable clock + - description: APB bus clock + + clock-names: + items: + - const: enable + - const: apb + + interrupts: + maxItems: 1 + + resets: + maxItems: 1 + + '#thermal-sensor-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - resets + - '#thermal-sensor-cells' + +unevaluatedProperties: false + +examples: + - | + sensor@50b00000 { + compatible = "eswin,eic7700-pvt"; + reg = <0x50b00000 0x10000>; + clocks = <&clocks 244>, <&clocks 234>; + clock-names = "enable", "apb"; + interrupts = <349>; + interrupt-parent = <&plic>; + label = "pvt0"; + resets = <&reset 111>; + #thermal-sensor-cells = <0>; + }; +... From a24f4ea5f3c26ee235d5a8491324ccec53ff120d Mon Sep 17 00:00:00 2001 From: Huan He Date: Tue, 30 Jun 2026 17:12:20 +0800 Subject: [PATCH 024/110] hwmon: Add Eswin EIC7700 PVT sensor driver Add support for ESWIN EIC7700 Voltage and Temperature sensor. The driver supports temperature and voltage monitoring with polynomial conversion, and provides sysfs interface for sensor data access. The PVT IP contains one temperature sensor and one voltage sensor. Signed-off-by: Yulin Lu Signed-off-by: Huan He Signed-off-by: Xuyang Dong Link: https://lore.kernel.org/r/20260630091220.1608-1-dongxuyang@eswincomputing.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/eic7700-pvt.c | 509 ++++++++++++++++++++++++++++++++++++ drivers/hwmon/eic7700-pvt.h | 99 +++++++ 4 files changed, 620 insertions(+) create mode 100644 drivers/hwmon/eic7700-pvt.c create mode 100644 drivers/hwmon/eic7700-pvt.h diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b2e445b5536e..08c29685126a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2083,6 +2083,17 @@ config SENSORS_DME1737 This driver can also be built as a module. If so, the module will be called dme1737. +config SENSORS_EIC7700_PVT + tristate "Eswin EIC7700 Voltage, Temperature sensor driver" + depends on ARCH_ESWIN || COMPILE_TEST + select POLYNOMIAL + help + If you say yes here you get support for Eswin EIC7700 PVT sensor + embedded into the SoC. + + This driver can also be built as a module. If so, the module will be + called eic7700-pvt. + config SENSORS_EMC1403 tristate "SMSC EMC1403/23 thermal sensor" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index caea1f55222d..63809eeec2f4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737) += dme1737.o obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o obj-$(CONFIG_SENSORS_DS620) += ds620.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o +obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o diff --git a/drivers/hwmon/eic7700-pvt.c b/drivers/hwmon/eic7700-pvt.c new file mode 100644 index 000000000000..d7403ab33f4e --- /dev/null +++ b/drivers/hwmon/eic7700-pvt.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN EIC7700 Voltage, Temperature sensor driver + * + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. + * + * Authors: + * Yulin Lu + * Huan He + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "eic7700-pvt.h" + +static const struct pvt_sensor_info pvt_info[] = { + PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP), + PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT), +}; + +static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"}; + +/* + * The original translation formulae of the temperature (in degrees of Celsius) + * to PVT data and vice-versa are following: + * N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) + + * 1.6198e2, + * T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) + + * 3.2843e-1*(N^1) - 4.8690e1, + * where T = [-40, 125]C and N = [27, 771]. + * 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 = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T + + * 16198e2) / 1e4, + * T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N - + * 48690, + * where T = [-40000, 125000] mC and N = [27, 771]. + */ +static const struct polynomial poly_N_to_temp = { + .total_divider = 1, + .terms = { + {4, -18439, 1000, 1}, + {3, 80705, 1000, 1}, + {2, -185010, 1000, 1}, + {1, 328430, 1000, 1}, + {0, -48690, 1, 1} + } +}; + +/* + * Similar alterations are performed for the voltage conversion equations. + * The original formulae are: + * N = 1.3905e3*V - 5.7685e2, + * V = (N + 5.7685e2) / 1.3905e3, + * where V = [0.72, 0.88] V and N = [424, 646]. + * After the optimization they looks as follows: + * N = (13905e-3*V - 5768.5) / 10, + * V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10. + * where V = [720, 880] mV and N = [424, 646]. + */ +static const struct polynomial poly_N_to_volt = { + .total_divider = 10, + .terms = { + {1, 100000, 13905, 1}, + {0, 57685000, 1, 13905} + } +}; + +static inline u32 eic7700_pvt_update(void __iomem *reg, u32 mask, u32 data) +{ + u32 old; + + old = readl_relaxed(reg); + writel((old & ~mask) | (data & mask), reg); + + return old & mask; +} + +static inline void eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) +{ + u32 old; + + mode = FIELD_PREP(PVT_MODE_MASK, mode); + + old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) +{ + u32 old; + + old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + writel(val, pvt->regs + PVT_TRIM); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data) +{ + struct pvt_hwmon *pvt = data; + u32 stat, val; + int active; + + if (IS_ENABLED(CONFIG_PM)) { + active = pm_runtime_get_if_active(pvt->dev); + if (active <= 0) + return IRQ_NONE; + } + + stat = readl(pvt->regs + PVT_INT); + if (!(stat & PVT_INT_STAT)) { + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_put(pvt->dev); + return IRQ_NONE; + } + + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + /* + * Read the data, update the cache and notify a waiter of this event. + */ + val = readl(pvt->regs + PVT_DATA); + WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val)); + complete(&pvt->conversion); + + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_put(pvt->dev); + + return IRQ_HANDLED; +} + +static int eic7700_pvt_read_data(struct pvt_hwmon *pvt, + enum pvt_sensor_type type, long *val) +{ + unsigned long timeout; + u32 data; + int ret; + + /* + * Wait for PVT conversion to complete and update the data cache. The + * data read procedure is following: set the requested PVT sensor mode, + * enable conversion, wait until conversion is finished, then disable + * conversion and IRQ, and read the cached data. + */ + reinit_completion(&pvt->conversion); + + eic7700_pvt_set_mode(pvt, pvt_info[type].mode); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_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(&pvt->conversion, timeout); + + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + + if (!ret) + synchronize_irq(pvt->irq); + + data = READ_ONCE(pvt->data_cache); + + 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 const struct hwmon_channel_info *pvt_channel_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static umode_t eic7700_pvt_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int ch) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + return 0444; + } + break; + default: + break; + } + + return 0; +} + +static int eic7700_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); + int ret; + + ret = pm_runtime_get_sync(pvt->dev); + if (ret < 0) { + dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret); + pm_runtime_put_noidle(pvt->dev); + return ret; + } + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + ret = eic7700_pvt_read_data(pvt, ch, val); + break; + default: + ret = -EOPNOTSUPP; + } + break; + case hwmon_in: + if (attr == hwmon_in_input) + ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val); + else + ret = -EOPNOTSUPP; + break; + default: + ret = -EOPNOTSUPP; + } + + pm_runtime_mark_last_busy(pvt->dev); + pm_runtime_put_autosuspend(pvt->dev); + return ret; +} + +static int eic7700_pvt_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int ch, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = pvt_info[ch].label; + return 0; + } + break; + case hwmon_in: + if (attr == hwmon_in_label) { + *str = pvt_info[PVT_VOLT + ch].label; + return 0; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops pvt_hwmon_ops = { + .is_visible = eic7700_pvt_hwmon_is_visible, + .read = eic7700_pvt_hwmon_read, + .read_string = eic7700_pvt_hwmon_read_string +}; + +static const struct hwmon_chip_info pvt_hwmon_info = { + .ops = &pvt_hwmon_ops, + .info = pvt_channel_info +}; + +static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pvt_hwmon *pvt; + + pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); + if (!pvt) + return ERR_PTR(-ENOMEM); + + pvt->dev = dev; + init_completion(&pvt->conversion); + + return pvt; +} + +static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt) +{ + /* + * Make sure controller are disabled so not to accidentally have ISR + * executed before the driver data is fully initialized. Clear the IRQ + * status as well. + */ + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + readl(pvt->regs + PVT_INT); + readl(pvt->regs + PVT_DATA); + + /* Setup default sensor mode and temperature trim. */ + eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode); + + /* + * Max conversion latency (~333 µs) derived from PVT spec: + * maximum sampling rate = 3000 samples/sec. + */ + pvt->timeout = ns_to_ktime(PVT_TOUT_MIN); + + eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); + + return 0; +} + +static int eic7700_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, + eic7700_pvt_hard_isr, NULL, + IRQF_TRIGGER_HIGH, "pvt", pvt); + if (ret) { + dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); + return ret; + } + + return 0; +} + +static int eic7700_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; +} + +static void eic7700_pvt_disable_pm_runtime(void *data) +{ + struct pvt_hwmon *pvt = data; + + pm_runtime_dont_use_autosuspend(pvt->dev); + pm_runtime_disable(pvt->dev); + + if (!pm_runtime_status_suspended(pvt->dev)) { + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + pm_runtime_set_suspended(pvt->dev); + } +} + +static int eic7700_pvt_probe(struct platform_device *pdev) +{ + struct reset_control *rst; + struct pvt_hwmon *pvt; + int i, ret; + + pvt = eic7700_pvt_create_data(pdev); + if (IS_ERR(pvt)) + return PTR_ERR(pvt); + + platform_set_drvdata(pdev, pvt); + + pvt->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pvt->regs)) + return PTR_ERR(pvt->regs); + + for (i = 0; i < PVT_CLK_NUM; i++) + pvt->clks[i].id = pvt_clk_names[i]; + + ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Couldn't get clock descriptors\n"); + + rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(pvt->dev, PTR_ERR(rst), + "Couldn't get reset control\n"); + + ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); + if (ret) + return dev_err_probe(pvt->dev, ret, + "Failed to enable clocks\n"); + + ret = eic7700_pvt_init_iface(pvt); + if (ret) { + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + return ret; + } + + if (IS_ENABLED(CONFIG_PM)) + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 3000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime, + pvt); + if (ret) { + pm_runtime_put_noidle(&pdev->dev); + return dev_err_probe(&pdev->dev, ret, + "Can't register PM cleanup\n"); + } + + ret = eic7700_pvt_request_irq(pvt); + if (ret) + goto err_put_pm_runtime; + + ret = eic7700_pvt_create_hwmon(pvt); + if (ret) + goto err_put_pm_runtime; + + pm_runtime_put_autosuspend(&pdev->dev); + + return 0; + +err_put_pm_runtime: + pm_runtime_put_noidle(&pdev->dev); + return ret; +} + +static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); + if (ret) { + dev_err(dev, "Failed to enable clocks: %d\n", ret); + return ret; + } + + eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); + + return 0; +} + +static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + + return 0; +} + +static const struct dev_pm_ops eic7700_pvt_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume, + NULL) +}; + +static const struct of_device_id pvt_of_match[] = { + { .compatible = "eswin,eic7700-pvt"}, + { } +}; +MODULE_DEVICE_TABLE(of, pvt_of_match); + +static struct platform_driver pvt_driver = { + .probe = eic7700_pvt_probe, + .driver = { + .name = "eic7700-pvt", + .of_match_table = pvt_of_match, + .pm = pm_ptr(&eic7700_pvt_pm_ops), + }, +}; +module_platform_driver(pvt_driver); + +MODULE_AUTHOR("Yulin Lu "); +MODULE_AUTHOR("Huan He "); +MODULE_DESCRIPTION("Eswin eic7700 PVT driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/eic7700-pvt.h b/drivers/hwmon/eic7700-pvt.h new file mode 100644 index 000000000000..fb10f9e4e93a --- /dev/null +++ b/drivers/hwmon/eic7700-pvt.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ESWIN EIC7700 Voltage, Temperature sensor driver + * + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. + */ +#ifndef __HWMON_EIC7700_PVT_H__ +#define __HWMON_EIC7700_PVT_H__ + +#include +#include +#include +#include + +/* ESWIN EIC7700 PVT registers and their bitfields */ +#define PVT_TRIM 0x04 +#define PVT_MODE 0x08 +#define PVT_MODE_MASK GENMASK(2, 0) +#define PVT_CTRL_MODE_TEMP 0x0 +#define PVT_CTRL_MODE_VOLT 0x4 +#define PVT_ENA 0x0c +#define PVT_ENA_EN BIT(0) +#define PVT_INT 0x10 +#define PVT_INT_STAT BIT(0) +#define PVT_INT_CLR BIT(1) +#define PVT_DATA 0x14 +#define PVT_DATA_OUT GENMASK(9, 0) + +/* + * PVT sensors-related limits and default values + * @PVT_TEMP_CHS: Number of temperature hwmon channels. + * @PVT_VOLT_CHS: Number of voltage hwmon channels. + * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value + * when one is determined for ESWIN EIC7700 SoC). + * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. + */ +#define PVT_TEMP_CHS 1 +#define PVT_VOLT_CHS 1 +#define PVT_TRIM_DEF 0 +#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) + +/* + * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT + * sampling mode) + * @PVT_TEMP: PVT Temperature sensor. + * @PVT_VOLT: PVT Voltage sensor. + */ +enum pvt_sensor_type { + PVT_TEMP = 0, + PVT_VOLT +}; + +#define PVT_CLK_NUM 2 + +/* + * struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure + * @channel: Sensor channel ID. + * @label: hwmon sensor label. + * @mode: PVT mode corresponding to the channel. + * @type: Sensor type. + */ +struct pvt_sensor_info { + int channel; + const char *label; + u32 mode; + enum hwmon_sensor_types type; +}; + +#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \ + { \ + .channel = _ch, \ + .label = _label, \ + .mode = PVT_CTRL_MODE_ ##_mode, \ + .type = _type, \ + } + +/* + * struct pvt_hwmon - Eswin EIC7700 PVT private data + * @dev: device structure of the PVT platform device. + * @hwmon: hwmon device structure. + * @regs: pointer to the Eswin EIC7700 PVT registers region. + * @irq: PVT events IRQ number. + * @clks: PVT clock descriptors. + * @data_cache: data cache in raw format. + * @conversion: data conversion completion. + * @timeout: conversion timeout. + */ +struct pvt_hwmon { + struct device *dev; + struct device *hwmon; + void __iomem *regs; + int irq; + struct clk_bulk_data clks[PVT_CLK_NUM]; + u32 data_cache; + struct completion conversion; + ktime_t timeout; +}; + +#endif /* __HWMON_EIC7700_PVT_H__ */ From 2d2f6ce9f84abbad27387c4629211135fea66836 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Jul 2026 14:35:51 +0200 Subject: [PATCH 025/110] hwmon: (acpi_power_meter) Stop setting acpi_device_name/class() The driver populates acpi_device_name() and acpi_device_class() that are never read afterward, so make it stop doing that and drop the symbols defined for this purpose. Since ACPI_POWER_METER_CLASS is also used for generating ACPI netlink events, use the "pwr_meter_resource" string literal directly instead of it for that. No intentional functional impact. This will facilitate the removal of device_name and device_class from struct acpi_device_pnp in the future. Signed-off-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/2297496.irdbgypaU6@rafael.j.wysocki Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 0c9b9f4180fb..8a539e8d1334 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -21,8 +21,6 @@ #include #define ACPI_POWER_METER_NAME "power_meter" -#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" -#define ACPI_POWER_METER_CLASS "pwr_meter_resource" #define NUM_SENSORS 17 @@ -877,7 +875,7 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) break; } - acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, + acpi_bus_generate_netlink_event("pwr_meter_resource", dev_name(&resource->acpi_dev->dev), event, 0); } @@ -899,8 +897,6 @@ static int acpi_power_meter_probe(struct platform_device *pdev) resource->sensors_valid = 0; resource->acpi_dev = device; mutex_init(&resource->lock); - strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); platform_set_drvdata(pdev, resource); From 8012fd4927083bcdcc2611a5d7b0db5804a544bb Mon Sep 17 00:00:00 2001 From: Florian Schut Date: Sun, 5 Jul 2026 13:31:15 +0200 Subject: [PATCH 026/110] hwmon: (asus-ec-sensors) add ROG Maximus Z790 Hero Add support for ROG Maximus Z790 Hero. Signed-off-by: Florian Schut Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260705113125.812045-1-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 77a709517437..ccf43fd1fa9f 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -30,6 +30,7 @@ Supported boards: * ROG MAXIMUS XI HERO (WI-FI) * ROG MAXIMUS Z690 FORMULA * ROG MAXIMUS Z790 EXTREME + * ROG MAXIMUS Z790 HERO * ROG STRIX B550-E GAMING * ROG STRIX B550-I GAMING * ROG STRIX B650E-E GAMING WIFI diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 01a022b3ee82..cfe127556f25 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -527,6 +527,14 @@ static const struct ec_board_info board_info_maximus_z790_extreme = { .family = family_intel_700_series, }; +static const struct ec_board_info board_info_maximus_z790_hero = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, + .family = family_intel_700_series, +}; + static const struct ec_board_info board_info_prime_x470_pro = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | @@ -893,6 +901,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_maximus_z690_formula), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME", &board_info_maximus_z790_extreme), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 HERO", + &board_info_maximus_z790_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &board_info_strix_b550_e_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", From 57e374e17468234d69585562583fe56a0f049b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miska=20H=C3=A4m=C3=A4l=C3=A4inen?= Date: Sun, 5 Jul 2026 13:32:36 +0200 Subject: [PATCH 027/110] hwmon: (asus-ec-sensors) add ROG CROSSHAIR X870E HERO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for ROG CROSSHAIR X870E HERO Signed-off-by: Miska Hämäläinen Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260705113246.812500-1-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 ccf43fd1fa9f..84c9160efdd2 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -25,6 +25,7 @@ Supported boards: * ROG CROSSHAIR X670E EXTREME * ROG CROSSHAIR X670E HERO * ROG CROSSHAIR X670E GENE + * ROG CROSSHAIR X870E HERO * ROG MAXIMUS X HERO * ROG MAXIMUS XI HERO * ROG MAXIMUS XI HERO (WI-FI) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index cfe127556f25..16da28e0d881 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -487,6 +487,14 @@ static const struct ec_board_info board_info_crosshair_x670e_hero = { .family = family_amd_600_series, }; +static const struct ec_board_info board_info_crosshair_x870e_hero = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +}; + static const struct ec_board_info board_info_maximus_vi_hero = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | @@ -891,6 +899,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_crosshair_x670e_gene), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", &board_info_crosshair_x670e_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X870E HERO", + &board_info_crosshair_x870e_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO", &board_info_maximus_xi_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", From 3b7434feaad9b39d8b1ea390c6b755ef544d17df Mon Sep 17 00:00:00 2001 From: Vishaal Saraiya Date: Thu, 9 Jul 2026 22:38:35 +0200 Subject: [PATCH 028/110] hwmon: (asus-ec-sensors) add ROG STRIX X870E-E GAMING WIFI7 R2 The board sensors is a copy of ROG STRIX X870E-E GAMING WIFI7, thus we simply reuse its board_info struct. Signed-off-by: Vishaal Saraiya Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260709204037.1998433-3-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 84c9160efdd2..b0d083eaeb43 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -49,6 +49,7 @@ Supported boards: * 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 Z390-F GAMING * ROG STRIX Z490-F GAMING diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 16da28e0d881..6e171e15333e 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -947,6 +947,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x870_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI", &board_info_strix_x870e_e_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI7 R2", + &board_info_strix_x870e_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7", &board_info_strix_x870e_h_gaming_wifi7), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", From 440da016ceb4350a445d99c632849da605d9ae4e Mon Sep 17 00:00:00 2001 From: Sarbajit Sarkar Date: Fri, 10 Jul 2026 00:05:00 +0530 Subject: [PATCH 029/110] hwmon: (yogafan) Add support for Lenovo LOQ 15IAX9 Add the Lenovo LOQ 15IAX9 product family to the yogafan dmi_system_id table. Unlike the Legion series, ACPI table extraction reveals this LOQ utilizes an 8-bit EC architecture with a 100x multiplier, but maintains a dual-fan setup via the \_SB.PC00.LPCB.EC0.FA1S and FA2S paths. A new loq_15iax9_8bit_dual_cfg structure was added to properly map these hardware registers and calculate RPMs correctly while avoiding path conflicts with AMD-based LOQ variants. Signed-off-by: Sarbajit Sarkar Link: https://lore.kernel.org/r/20260709183500.13761-1-sarbajitsarkar16108@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/yogafan.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c index 605cc928f21f..0a41b8672b56 100644 --- a/drivers/hwmon/yogafan.c +++ b/drivers/hwmon/yogafan.c @@ -71,6 +71,12 @@ static const struct yogafan_config legion_16bit_dual_cfg = { .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } }; +static const struct yogafan_config loq_15iax9_8bit_dual_cfg = { + .multiplier = 100, + .fan_count = 2, + .paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" } +}; + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) { ktime_t now = ktime_get_boottime(); @@ -194,6 +200,14 @@ static const struct dmi_system_id yogafan_quirks[] = { }, .driver_data = (void *)&ideapad_8bit_fan0_cfg, }, + { + .ident = "Lenovo LOQ 15IAX9", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), + }, + .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, + }, { } }; MODULE_DEVICE_TABLE(dmi, yogafan_quirks); From 6b80b95e71afdd991fac9de0b4009fff98077ef0 Mon Sep 17 00:00:00 2001 From: Maurice Mehlhaff Date: Wed, 8 Jul 2026 21:56:26 +0200 Subject: [PATCH 030/110] hwmon: (asus_ec_sensors) add ProArt Z690-CREATOR WIFI Add support for the ProArt Z690-CREATOR WIFI board Signed-off-by: Maurice Mehlhaff Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260708195638.1324168-1-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 b0d083eaeb43..a5db48d5a72e 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -17,6 +17,7 @@ Supported boards: * ProArt X670E-CREATOR WIFI * ProArt X870E-CREATOR WIFI * ProArt B550-CREATOR + * ProArt Z690-CREATOR WIFI * ROG CROSSHAIR VIII DARK HERO * ROG CROSSHAIR VIII HERO (WI-FI) * ROG CROSSHAIR VIII FORMULA diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 6e171e15333e..49b87f95a22b 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -386,6 +386,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { [ec_sensor_temp_t_sensor] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), [ec_sensor_fan_water_flow] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe), [ec_sensor_temp_water_in] = @@ -606,6 +608,13 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { .family = family_amd_800_series, }; +static const struct ec_board_info board_info_pro_art_z690_creator_wifi = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, + .family = family_intel_600_series, +}; + static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = { /* Board also has a nct6798 */ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME | @@ -875,6 +884,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_pro_art_x670E_creator_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI", &board_info_pro_art_x870E_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt Z690-CREATOR WIFI", + &board_info_pro_art_z690_creator_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI", &board_info_pro_ws_trx50_sage_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI A", From a3b61ba2558ea8fe936193c2c2c925ad8c4a1d36 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 13 Jul 2026 23:07:54 +0200 Subject: [PATCH 031/110] hwmon: (tmp401) register with thermal subsystem tmp401 is missing HWMON_C_REGISTER_TZ. So a devicetree thermal zone referencing a such sensor ('thermal-sensors = <&tmp411 N>') fails to register it. Then its cooling-maps is not set, which means that the fans are not managed. Same as lm90 or jc42, set HWMON_C_REGISTER_TZ. Boards without the property into the DT keep the same behavior. Signed-off-by: Vincent Jardin Link: https://lore.kernel.org/r/20260713-for-upstream-hwmon-tmp401-register-tz-v1-1-47315d8617bc@free.fr Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index ca0ff525ea29..83aafe4bd3e7 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -695,7 +695,7 @@ static int tmp401_probe(struct i2c_client *client) info->type = hwmon_chip; info->config = data->chip_channel_config; - data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; + data->chip_channel_config[0] = HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL; info = &data->temp_info; info->type = hwmon_temp; From 90639003c0826393f21c65fbbcafaa18510bbfdb Mon Sep 17 00:00:00 2001 From: Vishaal Saraiya Date: Sat, 11 Jul 2026 09:31:52 +0200 Subject: [PATCH 032/110] hwmon: (asus-ec-sensors) add T_Sensor for ROG STRIX X870E-E GAMING WIFI Enable T_Sensor for ROG STRIX X870E-E GAMING WIFI. Board specs list the sensor, reading at the standard for this family address without the thermistor connected to the motherboard, returns -62, which is one of the known blank values (the other one in -40) readings for temperature sensors in the ASUS EC when there is no thermistor connected to the motherboard socket. With that evidence we conclude that the T_Sensor can be enabled in the driver for this board. Signed-off-by: Vishaal Saraiya Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260711073319.553163-2-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 49b87f95a22b..4c69c7f3ca3f 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -768,7 +768,7 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = { .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | - SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, .family = family_amd_800_series, From 71a6c1950fa573e5b27de319e371fa94f6572006 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Sat, 11 Jul 2026 14:26:44 +0200 Subject: [PATCH 033/110] hwmon: (asus-ec-sensors) detect unconnected physical sensors When there is no physical sensor connected to the motherboard socket,reading EC register returns one of the special values (-62, -60, -40). Test for them and return -ENODATA to hwmon. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260711122937.594989-2-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 51 +++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 4c69c7f3ca3f..d386f056dc55 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -185,6 +185,20 @@ enum ec_sensors { #define SENSOR_TEMP_SENSOR_EXTRA_2 BIT(ec_sensor_temp_sensor_extra_2) #define SENSOR_TEMP_SENSOR_EXTRA_3 BIT(ec_sensor_temp_sensor_extra_3) +/* + * The values for temperature sensor readings without physical sensors connected. + * The value varies across generations and is seemingly defined by the EC chip + * used in the given board. + */ +static const s32 temperature_blank_values[] = {-62, -60, -40}; + +static const s32 environment_temp_sensors = + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_ALT1 | + SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | + SENSOR_TEMP_WATER_BLOCK_IN | SENSOR_TEMP_WATER_BLOCK_OUT | + SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_SENSOR_EXTRA_1 | + SENSOR_TEMP_SENSOR_EXTRA_2 | SENSOR_TEMP_SENSOR_EXTRA_3; + enum board_family { family_unknown, family_amd_400_series, @@ -988,6 +1002,7 @@ static const struct dmi_system_id dmi_table[] = { }; struct ec_sensor { + /* this is ec_sensors enum value */ unsigned int info_index; s32 cached_value; }; @@ -1080,6 +1095,12 @@ get_sensor_info(const struct ec_sensors_data *state, int index) return state->sensors_info + state->sensors[index].info_index; } +static enum ec_sensors +get_ec_sensor_type(const struct ec_sensors_data *state, int index) +{ + return state->sensors[index].info_index; +} + static int find_ec_sensor_index(const struct ec_sensors_data *ec, enum hwmon_sensor_types type, int channel) { @@ -1323,6 +1344,17 @@ static int get_cached_value_or_update(const struct device *dev, return 0; } +static bool is_blank_temperature_value(s32 value) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(temperature_blank_values); ++i) { + if (value == temperature_blank_values[i]) + return true; + } + return false; +} + /* * Now follow the functions that implement the hwmon interface */ @@ -1330,6 +1362,8 @@ static int get_cached_value_or_update(const struct device *dev, static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { + const struct ec_sensor_info *sensor_info; + enum ec_sensors ec_sensor; int ret; s32 value = 0; @@ -1341,12 +1375,19 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, } ret = get_cached_value_or_update(dev, sidx, state, &value); - if (!ret) { - *val = scale_sensor_value(value, - get_sensor_info(state, sidx)->type); - } + if (ret) + return ret; - return ret; + sensor_info = get_sensor_info(state, sidx); + if (sensor_info->type == hwmon_temp) { + ec_sensor = get_ec_sensor_type(state, sidx); + if ((environment_temp_sensors & BIT(ec_sensor)) && + is_blank_temperature_value(value)) + return -ENODATA; + } + *val = scale_sensor_value(value, sensor_info->type); + + return 0; } static int asus_ec_hwmon_read_string(struct device *dev, From 20b95cf6a664697bc1c229a9f7138971efead4f0 Mon Sep 17 00:00:00 2001 From: Zaixiang Xu Date: Mon, 13 Jul 2026 15:45:56 +0800 Subject: [PATCH 034/110] dt-bindings: vendor-prefixes: Add GXCAS Technology Add vendor prefix for Beijing Galaxy-CAS Technology Co., Ltd. (GXCAS). The prefix was confirmed from the manufacturer's website: https://www.gxcas.com/en/index.html Acked-by: Conor Dooley Acked-by: Krzysztof Kozlowski Signed-off-by: Zaixiang Xu Link: https://lore.kernel.org/r/20260713074559.12196-2-zaixiang.xu.dev@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 396044f368e7..6793125d6cc6 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -692,6 +692,8 @@ patternProperties: description: Gateworks Corporation use "gateworks" vendor prefix deprecated: true + "^gxcas,.*": + description: Beijing Galaxy-CAS Technology Co., Ltd. "^hannstar,.*": description: HannStar Display Corporation "^haochuangyi,.*": From 71b99f4242b521675790b36596b29dc969616b84 Mon Sep 17 00:00:00 2001 From: Zaixiang Xu Date: Mon, 13 Jul 2026 15:45:57 +0800 Subject: [PATCH 035/110] dt-bindings: hwmon: Add Sensirion SHT30 series Add a YAML devicetree binding for the Sensirion SHT3x/STS3x sensor family and the compatible GXCAS GXHT30, with the ALERT interrupt, nRESET GPIO and VDD supply as optional properties. The SHT30, SHT31, SHT35 and SHT85 measure humidity and temperature and differ only in accuracy and packaging, while the STS30, STS31, STS32 and STS35 are temperature-only parts. Within each group the parts share the same software interface, so model them with a fallback to the base part of their group: "sensirion,sht30" for the SHT parts and "sensirion,sts30" for the STS parts. The GXCAS GXHT30 is a drop-in replacement for the SHT30 and uses it as fallback as well. Signed-off-by: Zaixiang Xu Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260713074559.12196-3-zaixiang.xu.dev@gmail.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/sensirion,sht30.yaml | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/sensirion,sht30.yaml diff --git a/Documentation/devicetree/bindings/hwmon/sensirion,sht30.yaml b/Documentation/devicetree/bindings/hwmon/sensirion,sht30.yaml new file mode 100644 index 000000000000..dee0252941c2 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/sensirion,sht30.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/sensirion,sht30.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sensirion SHT3x and GXCAS GXHT30 humidity and temperature sensors + +maintainers: + - Zaixiang Xu + +properties: + compatible: + oneOf: + - enum: + - sensirion,sht30 + - sensirion,sts30 + - items: + - enum: + - gxcas,gxht30 + - sensirion,sht31 + - sensirion,sht35 + - sensirion,sht85 + - const: sensirion,sht30 + - items: + - enum: + - sensirion,sts31 + - sensirion,sts32 + - sensirion,sts35 + - const: sensirion,sts30 + + reg: + maxItems: 1 + + vdd-supply: + description: Regulator that provides power to the VDD pin. + + reset-gpios: + maxItems: 1 + description: + GPIO connected to the nRESET pin. Active low. + + interrupts: + maxItems: 1 + description: + Interrupt connected to the ALERT pin. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + humidity-sensor@44 { + compatible = "gxcas,gxht30", "sensirion,sht30"; + reg = <0x44>; + vdd-supply = <&vcc_3v3_reg>; + reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; + interrupt-parent = <&gpio1>; + interrupts = <16 IRQ_TYPE_LEVEL_HIGH>; + }; + }; From 75186ab25c4a31cec5990d0088160acfc6a178ff Mon Sep 17 00:00:00 2001 From: Zaixiang Xu Date: Mon, 13 Jul 2026 15:45:58 +0800 Subject: [PATCH 036/110] hwmon: (sht3x) Add devicetree support Add an of_device_id table to support devicetree based instantiation of the supported sensors. The devicetree binding models all higher accuracy parts with a fallback to the base part of their group, so only the two base compatibles need to be listed. The match data distinguishes the humidity and temperature parts (SHT3x) from the temperature-only parts (STS3x), which require different configuration. Start enum sht3x_chips at 1: with sht3x equal to 0, the OF match data for the SHT devices would be NULL, causing i2c_get_match_data() to fall back to i2c_device_id name matching, which fails for devicetree names. The devices would then only work because the resulting chip id 0 happens to equal sht3x. Non-zero match data avoids relying on that coincidence. Signed-off-by: Zaixiang Xu Link: https://lore.kernel.org/r/20260713074559.12196-4-zaixiang.xu.dev@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/sht3x.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c index 4d90f89a9929..b46de050e20a 100644 --- a/drivers/hwmon/sht3x.c +++ b/drivers/hwmon/sht3x.c @@ -62,7 +62,7 @@ static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 }; #define SHT3X_MAX_HUMIDITY 100000 enum sht3x_chips { - sht3x, + sht3x = 1, sts3x, }; @@ -940,8 +940,19 @@ static const struct i2c_device_id sht3x_ids[] = { MODULE_DEVICE_TABLE(i2c, sht3x_ids); +static const struct of_device_id sht3x_of_match[] = { + { .compatible = "sensirion,sht30", .data = (void *)(uintptr_t)sht3x }, + { .compatible = "sensirion,sts30", .data = (void *)(uintptr_t)sts3x }, + { } +}; + +MODULE_DEVICE_TABLE(of, sht3x_of_match); + static struct i2c_driver sht3x_i2c_driver = { - .driver.name = "sht3x", + .driver = { + .name = "sht3x", + .of_match_table = sht3x_of_match, + }, .probe = sht3x_probe, .id_table = sht3x_ids, }; From 622a2dbdc0192f946b05da175277c70f6a612ef2 Mon Sep 17 00:00:00 2001 From: Zaixiang Xu Date: Mon, 13 Jul 2026 15:45:59 +0800 Subject: [PATCH 037/110] hwmon: (sht3x) Document support for GXCAS GXHT30 The GXCAS GXHT30 is a humidity and temperature sensor that is a drop-in replacement for and fully software compatible with the Sensirion SHT30. It is handled by the "sensirion,sht30" fallback compatible without any driver changes; manual instantiation works with the existing sht3x device name. Document the chip as supported. Signed-off-by: Zaixiang Xu Link: https://lore.kernel.org/r/20260713074559.12196-5-zaixiang.xu.dev@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht3x.rst | 17 ++++++++++++++--- drivers/hwmon/Kconfig | 5 +++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Documentation/hwmon/sht3x.rst b/Documentation/hwmon/sht3x.rst index ea1642920295..4f67671eb0cd 100644 --- a/Documentation/hwmon/sht3x.rst +++ b/Documentation/hwmon/sht3x.rst @@ -31,6 +31,14 @@ Supported chips: Datasheet: https://sensirion.com/media/documents/4B40CEF3/640B2346/Sensirion_Humidity_Sensors_SHT85_Datasheet.pdf + * GXCAS GXHT30 + + Prefix: 'sht3x' + + Addresses scanned: none + + Datasheet: https://www.galaxy-cas.com/uploads/files/202509/GXHT30_datasheet_V3.9_20250919170350.pdf + Author: - David Frey @@ -40,9 +48,12 @@ Description ----------- This driver implements support for the Sensirion SHT3x-DIS, STS3x-DIS and SHT85 -series of humidity and temperature sensors. Temperature is measured in degrees -celsius, relative humidity is expressed as a percentage. In the sysfs interface, -all values are scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500. +series of humidity and temperature sensors, as well as the compatible GXCAS +GXHT30. The GXHT30 is a drop-in replacement for the SHT30: instantiate it from +devicetree with the "gxcas,gxht30", "sensirion,sht30" compatibles, or manually +using the sht3x device name. Temperature is measured in degrees celsius, +relative humidity is expressed as a percentage. In the sysfs interface, all +values are scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500. The device communicates with the I2C protocol. SHT3x sensors can have the I2C addresses 0x44 or 0x45 (0x4a or 0x4b for sts3x), depending on the wiring. SHT85 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 08c29685126a..64fc00ade5ca 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2023,8 +2023,9 @@ config SENSORS_SHT3x depends on I2C select CRC8 help - If you say yes here you get support for the Sensiron SHT30, SHT31 and - SHT85 humidity and temperature sensors. + If you say yes here you get support for the Sensiron SHT30, SHT31, + SHT85 and the compatible GXCAS GXHT30 humidity and temperature + sensors. This driver can also be built as a module. If so, the module will be called sht3x. From 3b87c46d0d5b8dee28dbb37f0d6cadb511216d48 Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Sun, 12 Jul 2026 08:12:58 +0000 Subject: [PATCH 038/110] hwmon: (peci) Fix kernel-doc parameter names in common.h scripts/kernel-doc -Wall -none reports: Warning: drivers/hwmon/peci/common.h:39 function parameter 'state' not described in 'peci_sensor_need_update' Warning: drivers/hwmon/peci/common.h:39 Excess function parameter 'sensor' description in 'peci_sensor_need_update' Warning: drivers/hwmon/peci/common.h:49 function parameter 'state' not described in 'peci_sensor_mark_updated' Warning: drivers/hwmon/peci/common.h:49 Excess function parameter 'sensor' description in 'peci_sensor_mark_updated' Both peci_sensor_need_update() and peci_sensor_mark_updated() take a pointer to struct peci_sensor_state named 'state', but their kernel-doc comments document a parameter named 'sensor' and describe it as a pointer to the sensor data struct. The comments have been out of sync with the code since the file was added by commit bf3608f338e9 ("hwmon: peci: Add cputemp driver"). Update the kernel-doc comments to document the 'state' parameter. No functional change. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Babanpreet Singh Link: https://lore.kernel.org/r/20260712081258.7-1-bbnpreetsingh@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/peci/common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h index 92a7ee1925bc..5eae9681e385 100644 --- a/drivers/hwmon/peci/common.h +++ b/drivers/hwmon/peci/common.h @@ -31,7 +31,7 @@ struct peci_sensor_data { /** * peci_sensor_need_update() - check whether sensor update is needed or not - * @sensor: pointer to sensor data struct + * @state: pointer to sensor state struct * * Return: true if update is needed, false if not. */ @@ -44,7 +44,7 @@ static inline bool peci_sensor_need_update(struct peci_sensor_state *state) /** * peci_sensor_mark_updated() - mark the sensor is updated - * @sensor: pointer to sensor data struct + * @state: pointer to sensor state struct */ static inline void peci_sensor_mark_updated(struct peci_sensor_state *state) { From 3997143f2fa00f53d5ad93606eb0b15c6e909b91 Mon Sep 17 00:00:00 2001 From: Wensheng Wang Date: Sat, 4 Jul 2026 16:19:51 +0800 Subject: [PATCH 039/110] dt-bindings: hwmon: Add MPS mpq82d00 Add support for MPS mpq82d00 controller. Signed-off-by: Wensheng Wang Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20260704081952.1701914-1-wenswang@yeah.net Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 435c4baab436..8116f21bf8be 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -362,6 +362,8 @@ properties: - mps,mp9941 # Monolithic Power Systems Inc. digital step-down converter mp9945 - mps,mp9945 + # Monolithic Power Systems Inc. digital step-down converter mpq82d00 + - mps,mpq82d00 # Murata D1U74T-W power supply unit - murata,d1u74t # Temperature sensor with integrated fan control From ff71049f36172fd3590bd170d1bc9cdd9c3e6839 Mon Sep 17 00:00:00 2001 From: Wensheng Wang Date: Sat, 4 Jul 2026 16:19:52 +0800 Subject: [PATCH 040/110] hwmon: (pmbus) Add MPQ82D00 driver Add support for MPS mpq82d00 controller. This driver exposes telemetry and limit value readings and writtings. Signed-off-by: Wensheng Wang Link: https://lore.kernel.org/r/20260704081952.1701914-2-wenswang@yeah.net [groeck: Fixed "WARNING: Title underline too short"] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mpq82d00.rst | 73 +++++++++ MAINTAINERS | 7 + drivers/hwmon/pmbus/Kconfig | 9 ++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/mpq82d00.c | 249 +++++++++++++++++++++++++++++++ 6 files changed, 340 insertions(+) create mode 100644 Documentation/hwmon/mpq82d00.rst create mode 100644 drivers/hwmon/pmbus/mpq82d00.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 226789376217..956f3da0b722 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -201,6 +201,7 @@ Hardware Monitoring Kernel Drivers mp5990 mp9941 mp9945 + mpq82d00 mpq8785 nct6683 nct6775 diff --git a/Documentation/hwmon/mpq82d00.rst b/Documentation/hwmon/mpq82d00.rst new file mode 100644 index 000000000000..0749c130bd11 --- /dev/null +++ b/Documentation/hwmon/mpq82d00.rst @@ -0,0 +1,73 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver mpq82d00 +====================== + +Supported chips: + + * MPS mpq82d00 + + Prefix: 'mpq82d00' + +Author: + + Wensheng Wang + +Description +----------- + +This driver implements support for Monolithic Power Systems, Inc. (MPS) +MPQ82D00 Step-Down Controller. + +Device compliant with: + +- PMBus rev 1.3 interface. + +The driver exports the following attributes via the 'sysfs' files +for input voltage: + +**in1_input** + +**in1_label** + +**in1_alarm** + +The driver provides the following attributes for output voltage: + +**in2_input** + +**in2_label** + +**in2_alarm** + +The driver provides the following attributes for output current: + +**curr1_input** + +**curr1_label** + +**curr1_crit** + +**curr1_crit_alarm** + +The driver provides the following attributes for input power: + +**power1_input** + +**power1_label** + +**power1_alarm** + +The driver provides the following attributes for output power: + +**power2_input** + +**power2_label** + +The driver provides the following attributes for temperature: + +**temp1_input** + +**temp1_crit** + +**temp1_crit_alarm** diff --git a/MAINTAINERS b/MAINTAINERS index 0c612a1e5920..c97521e6bd8e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18326,6 +18326,13 @@ S: Maintained F: Documentation/hwmon/mp9945.rst F: drivers/hwmon/pmbus/mp9945.c +MPS MPQ82D00 DRIVER +M: Wensheng Wang +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/mpq82d00.rst +F: drivers/hwmon/pmbus/mpq82d00.c + MR800 AVERMEDIA USB FM RADIO DRIVER M: Alexey Klimov L: linux-media@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index ae003a6eac30..ca312be58ed5 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -607,6 +607,15 @@ config SENSORS_MPQ7932 This driver can also be built as a module. If so, the module will be called mpq7932. +config SENSORS_MPQ82D00 + tristate "MPS MPQ82D00" + help + If you say yes here you get hardware monitoring functionality support + for power management IC MPS MPQ82D00. + + This driver can also be built as a module. If so, the module will + be called mpq82d00. + config SENSORS_MPQ8785 tristate "MPS MPQ8785" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index e01adacc8b76..080d366809f3 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_SENSORS_MP5990) += mp5990.o obj-$(CONFIG_SENSORS_MP9941) += mp9941.o obj-$(CONFIG_SENSORS_MP9945) += mp9945.o obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o +obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o diff --git a/drivers/hwmon/pmbus/mpq82d00.c b/drivers/hwmon/pmbus/mpq82d00.c new file mode 100644 index 000000000000..d51fcffcc6d9 --- /dev/null +++ b/drivers/hwmon/pmbus/mpq82d00.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00) + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define MPQ82D00_VOUT_DIV 64 + +#define MPQ82D00_PAGE_NUM 1 + +#define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ + PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \ + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \ + PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_STATUS_TEMP | \ + PMBUS_HAVE_STATUS_INPUT) + +struct mpq82d00_data { + struct pmbus_driver_info info; + int vout_scale; +}; + +#define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info) + +static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent) +{ + s16 exponent, mantissa, target_exponent; + + exponent = ((s16)word) >> 11; + mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; + target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11; + + /* + * The MPQ82D00 does not support negtive limit value, if a negtive + * limit value is written, the limit value will become to 0. And + * the maximum positive limit value is limitted to 0x3FF. + */ + if (mantissa < 0) { + mantissa = 0; + } else { + if (exponent > target_exponent) { + mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ? + mantissa << (exponent - target_exponent) : + 0x3FF; + } else { + mantissa = clamp_val(mantissa >> (target_exponent - exponent), + 0, 0x3FF); + } + } + + return mantissa | ((expect_exponent << 11) & 0xf800); +} + +static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VOUT_MODE: + /* + * The MPQ82D00 does not follow standard PMBus protocol completely, + * and the calculation of vout in this driver is based on direct + * format. As a result, the format of vout is enforced to direct. + */ + ret = PB_VOUT_MODE_DIRECT; + break; + default: + /* + * These registers are not explicitly handled by the driver, + * as a result, return -ENODATA directly. + */ + ret = -ENODATA; + break; + } + + return ret; +} + +static int mpq82d00_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 mpq82d00_data *data = to_mpq82d00_data(info); + int ret; + + switch (reg) { + case PMBUS_STATUS_WORD: + case PMBUS_READ_VIN: + case PMBUS_READ_IOUT: + case PMBUS_READ_POUT: + case PMBUS_READ_PIN: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + /* + * These registers are not explicitly handled by the driver, + * as a result, return -ENODATA directly. + */ + ret = -ENODATA; + break; + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale, + MPQ82D00_VOUT_DIV); + break; + default: + /* + * The MPQ82D00 do not support other telemetry and limit + * value reading, so, return -EINVAL directly. + */ + ret = -EINVAL; + break; + } + + return ret; +} + +static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_OT_FAULT_LIMIT: + /* + * The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format, + * and the exponent is a constant value(5'b00000), so the + * exponent of word parameter should be converted to 5'b00000. + */ + ret = pmbus_write_word_data(client, page, reg, + mpq82d00_linear_exp_transfer(word, 0x00)); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + /* + * The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format, + * and the exponent is a constant value(5'b11100), so the + * exponent of word parameter should be converted to 5'b11100. + */ + ret = pmbus_write_word_data(client, page, reg, + mpq82d00_linear_exp_transfer(word, 0x1C)); + break; + default: + /* + * The MPQ82D00 do not support other limit value configuration, + * so, return -EINVAL directly. + */ + ret = -EINVAL; + break; + } + + return ret; +} + +static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info) +{ + struct mpq82d00_data *data = to_mpq82d00_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + + /* + * The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE + * bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5 + * is 0, it is linear mode, the vout scale is 1.953125mv/LSB. + */ + if (FIELD_GET(GENMASK(5, 5), ret)) + data->vout_scale = 320; + else + data->vout_scale = 125; + + return 0; +} + +static struct pmbus_driver_info mpq82d00_info = { + .pages = MPQ82D00_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_POWER] = linear, + + .m[PSC_VOLTAGE_OUT] = 1, + .R[PSC_VOLTAGE_OUT] = 3, + .b[PSC_VOLTAGE_OUT] = 0, + + .func[0] = MPQ82D00_RAIL1_FUNC, + .read_word_data = mpq82d00_read_word_data, + .read_byte_data = mpq82d00_read_byte_data, + .write_word_data = mpq82d00_write_word_data, + .identify = mpq82d00_identify, +}; + +static int mpq82d00_probe(struct i2c_client *client) +{ + struct mpq82d00_data *data; + + data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info)); + + return pmbus_do_probe(client, &data->info); +} + +static const struct i2c_device_id mpq82d00_id[] = { + {.name = "mpq82d00"}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mpq82d00_id); + +static const struct of_device_id __maybe_unused mpq82d00_of_match[] = { + {.compatible = "mps,mpq82d00"}, + {} +}; +MODULE_DEVICE_TABLE(of, mpq82d00_of_match); + +static struct i2c_driver mpq82d00_driver = { + .driver = { + .name = "mpq82d00", + .of_match_table = mpq82d00_of_match, + }, + .probe = mpq82d00_probe, + .id_table = mpq82d00_id, +}; + +module_i2c_driver(mpq82d00_driver); + +MODULE_AUTHOR("Wensheng Wang Date: Sat, 4 Jul 2026 23:15:43 +0800 Subject: [PATCH 041/110] hwmon: (acbel-fsg032) Add missing MODULE_DEVICE_TABLE() The driver has a match table for the i2c bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://lore.kernel.org/r/20260704151543.35069-1-pengpeng@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/acbel-fsg032.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/pmbus/acbel-fsg032.c b/drivers/hwmon/pmbus/acbel-fsg032.c index d283005d92ae..5222e7aeca44 100644 --- a/drivers/hwmon/pmbus/acbel-fsg032.c +++ b/drivers/hwmon/pmbus/acbel-fsg032.c @@ -52,6 +52,7 @@ static const struct i2c_device_id acbel_fsg032_id[] = { { .name = "acbel_fsg032" }, { } }; +MODULE_DEVICE_TABLE(i2c, acbel_fsg032_id); static struct pmbus_driver_info acbel_fsg032_info = { .pages = 1, From 9f1012119f2a4cb5a7f589dc77d5e15dbab49d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 11 Jul 2026 11:59:05 +0200 Subject: [PATCH 042/110] hwmon: (cros_ec) Register the thermal devices after the hwmon ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To guarantee consistency for the read-modify-write access in cros_ec_hwmon_cooling_set_cur_state(), locking is necessary. The locking will use standard hwmon device locks, which requires the hwmon device to be ready before the fan devices are set up. Reorder the initialization so this works. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260711-cros_ec-hwmon-locking-v1-1-cb6d0fdbb2d3@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/cros_ec_hwmon.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index e91e2103a6c7..e7c0076972fc 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -578,13 +578,17 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev) priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec); priv->temp_threshold_supported = is_cros_ec_cmd_available(priv->cros_ec, EC_CMD_THERMAL_GET_THRESHOLD, 1); - cros_ec_hwmon_register_fan_cooling_devices(dev, priv); hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv, &cros_ec_hwmon_chip_info, NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + cros_ec_hwmon_register_fan_cooling_devices(dev, priv); + platform_set_drvdata(pdev, priv); - return PTR_ERR_OR_ZERO(hwmon_dev); + return 0; } static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state) From 434baf3aea987ae61d0d54b6cdf8fccf78383b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 11 Jul 2026 11:59:06 +0200 Subject: [PATCH 043/110] hwmon: (cros_ec) Store the hwmon device in cros_ec_hwmon_priv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming locking scheme for thermal devices will require access to the hwmon device. Add it do the priv struct. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260711-cros_ec-hwmon-locking-v1-2-cb6d0fdbb2d3@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/cros_ec_hwmon.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index e7c0076972fc..b62b9848d74f 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -24,6 +24,7 @@ struct cros_ec_hwmon_priv { struct cros_ec_device *cros_ec; + struct device *hwmon_dev; const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES]; u8 usable_fans; bool fan_control_supported; @@ -555,7 +556,6 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev) struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); struct cros_ec_device *cros_ec = ec_dev->ec_dev; struct cros_ec_hwmon_priv *priv; - struct device *hwmon_dev; u8 thermal_version; int ret; @@ -579,10 +579,10 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev) priv->temp_threshold_supported = is_cros_ec_cmd_available(priv->cros_ec, EC_CMD_THERMAL_GET_THRESHOLD, 1); - hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv, - &cros_ec_hwmon_chip_info, NULL); - if (IS_ERR(hwmon_dev)) - return PTR_ERR(hwmon_dev); + priv->hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv, + &cros_ec_hwmon_chip_info, NULL); + if (IS_ERR(priv->hwmon_dev)) + return PTR_ERR(priv->hwmon_dev); cros_ec_hwmon_register_fan_cooling_devices(dev, priv); From 26db273640887276a24ca2546b06e334508871f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 11 Jul 2026 11:59:07 +0200 Subject: [PATCH 044/110] hwmon: (cros_ec) Synchronize EC access from the thermal device callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To guarantee consistency for the read-modify-write access in cros_ec_hwmon_cooling_set_cur_state(), locking is necessary. Use the existing hwmon device lock to do so. Fixes: 5798b62867b4 ("hwmon: (cros_ec) register fans into thermal framework cooling devices") Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260711-cros_ec-hwmon-locking-v1-3-cb6d0fdbb2d3@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/cros_ec_hwmon.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index b62b9848d74f..1337b646e022 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -5,6 +5,7 @@ * Copyright (C) 2024 Thomas Weißschuh */ +#include #include #include #include @@ -410,6 +411,8 @@ static int cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cd u8 read_val; int ret; + guard(hwmon_lock)(priv->hwmon_priv->hwmon_dev); + ret = cros_ec_hwmon_read_pwm_value(priv->hwmon_priv->cros_ec, priv->index, &read_val); if (ret) return ret; @@ -423,6 +426,8 @@ static int cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cd { const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata; + guard(hwmon_lock)(priv->hwmon_priv->hwmon_dev); + return cros_ec_hwmon_write_pwm_input(priv->hwmon_priv->cros_ec, priv->index, val); } From d33baed3fea26756ef3b077347b8da0de3f492f1 Mon Sep 17 00:00:00 2001 From: Shih-Yuan Lee Date: Sat, 11 Jul 2026 17:33:21 +0800 Subject: [PATCH 045/110] hwmon: (applesmc) Cache fan positions during register initialization To support the read_string callback for fan labels in the modern HWMON API, load and cache the fan position names in smcreg.fan_positions during register initialization. Pre-pad fallback labels with four spaces to match the "+ 4" pointer arithmetic offset used by all fan labels in the read_string callback. Signed-off-by: Shih-Yuan Lee Link: https://lore.kernel.org/r/20260711093323.14529-2-fourdollars@debian.org Signed-off-by: Guenter Roeck --- drivers/hwmon/applesmc.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 90a14a7f2c4c..9b2d9ecb20c0 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -133,6 +133,7 @@ static struct applesmc_registers { bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ const char **index; /* temperature key index */ + char fan_positions[10][17]; /* cached fan position labels */ } smcreg = { .mutex = __MUTEX_INITIALIZER(smcreg.mutex), }; @@ -566,7 +567,7 @@ static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; bool left_light_sensor = false, right_light_sensor = false; - unsigned int count; + unsigned int count, i; u8 tmp[1]; int ret; @@ -597,6 +598,16 @@ static int applesmc_init_smcreg_try(void) if (s->fan_count > 10) s->fan_count = 10; + for (i = 0; i < s->fan_count; i++) { + char newkey[5]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); + ret = applesmc_read_key(newkey, s->fan_positions[i], 16); + s->fan_positions[i][16] = 0; + if (ret) + scnprintf(s->fan_positions[i], 17, " Fan %d", i); + } + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret; From 5a151274c1fd8696ddc7e9a84906a67ab0df66e8 Mon Sep 17 00:00:00 2001 From: Shih-Yuan Lee Date: Sat, 11 Jul 2026 17:33:22 +0800 Subject: [PATCH 046/110] hwmon: (applesmc) Fix lockless cache validation data race In applesmc_get_entry_by_index(), the cache->valid flag is checked locklessly, but setting it to true lacks memory barriers. This can lead to a data race (TOCTOU) where another thread sees cache->valid as true before the actual cache contents (cache->key, cache->len, cache->type, etc.) are fully committed and visible to that CPU, potentially causing it to read uninitialized data and send incorrect keys to the Apple SMC hardware. Introduce memory barriers (smp_load_acquire and smp_store_release) with explanatory comments to ensure cache synchronization is thread-safe and fully visible across all CPUs. Signed-off-by: Shih-Yuan Lee Link: https://lore.kernel.org/r/20260711093323.14529-3-fourdollars@debian.org Signed-off-by: Guenter Roeck --- drivers/hwmon/applesmc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 9b2d9ecb20c0..317135fc4b73 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -33,6 +33,7 @@ #include #include #include +#include /* data port used by Apple SMC */ #define APPLESMC_DATA_PORT 0x300 @@ -373,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) __be32 be; int ret = 0; - if (cache->valid) + /* Pairs with smp_store_release() to ensure cache contents are visible */ + if (smp_load_acquire(&cache->valid)) return cache; mutex_lock(&smcreg.mutex); @@ -392,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) cache->len = info[0]; memcpy(cache->type, &info[1], 4); cache->flags = info[5]; - cache->valid = true; + /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ + smp_store_release(&cache->valid, true); out: mutex_unlock(&smcreg.mutex); From 94f5081d359265985587b5c96797ca919253f480 Mon Sep 17 00:00:00 2001 From: Shih-Yuan Lee Date: Sat, 11 Jul 2026 17:33:23 +0800 Subject: [PATCH 047/110] hwmon: (applesmc) Convert to hwmon_device_register_with_info The legacy hwmon_device_register() function is deprecated and triggers warnings in dmesg. Convert the driver to the modern hwmon_device_register_with_info() API. This conversion does the following: - Dynamically allocates standard HWMON temp, fan, and pwm channels. - Configures HWMON ops callbacks (.is_visible, .read, .read_string, .write). - Standardizes attribute naming to match the HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically registers non-standard fanX_safe attributes under the HWMON class directory via extra_groups. - Cleans up legacy sysfs nodes, groups, and unused show/store static functions to avoid unused symbol compiler warnings. - Avoids recursive mutex deadlocks when writing to pwmX_enable by locklessly resolving the entry and invoking the underlying raw SMC read/write calls. - Avoids UAF race condition on module exit by using unmanaged registration and explicitly calling hwmon_device_unregister() as the first step of applesmc_exit(), guaranteeing that HWMON nodes are destroyed before static structures are freed. Signed-off-by: Shih-Yuan Lee Link: https://lore.kernel.org/r/20260711093323.14529-4-fourdollars@debian.org Signed-off-by: Guenter Roeck --- drivers/hwmon/applesmc.c | 470 ++++++++++++++++++++++++++------------- 1 file changed, 321 insertions(+), 149 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 317135fc4b73..ca56bd8b170e 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -145,8 +145,8 @@ static s16 rest_x; static s16 rest_y; static u8 backlight_state[2]; -static struct device *hwmon_dev; static struct input_dev *applesmc_idev; +static struct device *hwmon_dev; /* * Last index written to key_at_index sysfs file, and value to use for all other @@ -822,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); } -/* Displays sensor key as label */ -static ssize_t applesmc_show_sensor_label(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - - return sysfs_emit(sysfsbuf, "%s\n", key); -} - -/* Displays degree Celsius * 1000 */ -static ssize_t applesmc_show_temperature(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - int ret; - s16 value; - int temp; - - ret = applesmc_read_s16(key, &value); - if (ret) - return ret; - - temp = 250 * (value >> 6); - - return sysfs_emit(sysfsbuf, "%d\n", temp); -} - static ssize_t applesmc_show_fan_speed(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -868,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, return sysfs_emit(sysfsbuf, "%u\n", speed); } -static ssize_t applesmc_store_fan_speed(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - unsigned long speed; - char newkey[5]; - u8 buffer[2]; - - if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) - return -EINVAL; /* Bigger than a 14-bit value */ - - scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], - to_index(attr)); - - buffer[0] = (speed >> 6) & 0xff; - buffer[1] = (speed << 2) & 0xff; - ret = applesmc_write_key(newkey, buffer, 2); - - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_manual(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - u16 manual = 0; - u8 buffer[2]; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - return ret; - - manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; - return sysfs_emit(sysfsbuf, "%d\n", manual); -} - -static ssize_t applesmc_store_fan_manual(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - u8 buffer[2]; - unsigned long input; - u16 val; - - if (kstrtoul(sysfsbuf, 10, &input) < 0) - return -EINVAL; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - goto out; - - val = (buffer[0] << 8 | buffer[1]); - - if (input) - val = val | (0x01 << to_index(attr)); - else - val = val & ~(0x01 << to_index(attr)); - - buffer[0] = (val >> 8) & 0xFF; - buffer[1] = val & 0xFF; - - ret = applesmc_write_key(FANS_MANUAL, buffer, 2); - -out: - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_position(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - char newkey[5]; - u8 buffer[17]; - - scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); - - ret = applesmc_read_key(newkey, buffer, 16); - buffer[16] = 0; - - if (ret) - return ret; - - return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); -} - static ssize_t applesmc_calibrate_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -1108,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { { } }; -static struct applesmc_node_group fan_group[] = { - { "fan%d_label", applesmc_show_fan_position }, - { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, - { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, - { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, - { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, - { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, - { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, - { } -}; -static struct applesmc_node_group temp_group[] = { - { "temp%d_label", applesmc_show_sensor_label }, - { "temp%d_input", applesmc_show_temperature }, - { } -}; /* Module stuff */ @@ -1321,8 +1186,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static struct applesmc_dev_attr *fan_safe_attrs; +static struct attribute **fan_safe_attr_list; +static struct attribute_group fan_safe_group; +static const struct attribute_group *applesmc_extra_groups[2]; + +static u32 *applesmc_temp_config; +static u32 *applesmc_fan_config; +static u32 *applesmc_pwm_config; +static struct hwmon_channel_info *applesmc_info_temp; +static struct hwmon_channel_info *applesmc_info_fan; +static struct hwmon_channel_info *applesmc_info_pwm; +static const struct hwmon_channel_info **applesmc_info_arr; +static struct hwmon_chip_info *applesmc_chip; + +static void applesmc_free_hwmon(void) +{ + kfree(applesmc_temp_config); + kfree(applesmc_fan_config); + kfree(applesmc_pwm_config); + kfree(applesmc_info_temp); + kfree(applesmc_info_fan); + kfree(applesmc_info_pwm); + kfree(applesmc_info_arr); + kfree(applesmc_chip); + kfree(fan_safe_attrs); + kfree(fan_safe_attr_list); +} + +static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + case hwmon_fan_max: + return 0444; + case hwmon_fan_min: + case hwmon_fan_target: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) + return 0644; + break; + default: + break; + } + return 0; +} + +static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + const char *key = smcreg.index[channel]; + s16 value; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + *val = 250 * (value >> 6); + return 0; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dAc", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_min: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMn", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_max: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMx", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_target: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dTg", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + *val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_min || attr == hwmon_fan_target) { + char key[5]; + u8 buffer[2]; + const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; + + if (val < 0 || val >= 0x4000) + return -EINVAL; + scnprintf(key, sizeof(key), fmt, channel); + buffer[0] = (val >> 6) & 0xff; + buffer[1] = (val << 2) & 0xff; + return applesmc_write_key(key, buffer, 2); + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + u16 manual_val; + const struct applesmc_entry *entry; + + if (val != 1 && val != 2) + return -EINVAL; + + entry = applesmc_get_entry_by_key(FANS_MANUAL); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + mutex_lock(&smcreg.mutex); + ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); + if (ret) + goto out_unlock; + manual_val = (buffer[0] << 8 | buffer[1]); + if (val == 1) + manual_val |= (0x01 << channel); + else + manual_val &= ~(0x01 << channel); + buffer[0] = (manual_val >> 8) & 0xff; + buffer[1] = manual_val & 0xff; + ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); +out_unlock: + mutex_unlock(&smcreg.mutex); + return ret; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = smcreg.index[channel]; + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_label) { + *str = smcreg.fan_positions[channel] + 4; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_hwmon_is_visible, + .read = applesmc_hwmon_read, + .write = applesmc_hwmon_write, + .read_string = applesmc_hwmon_read_string, +}; + static int __init applesmc_init(void) { + int i; int ret; if (!dmi_check_system(applesmc_whitelist)) { @@ -1357,17 +1452,97 @@ static int __init applesmc_init(void) if (ret) goto out_smcreg; - ret = applesmc_create_nodes(fan_group, smcreg.fan_count); - if (ret) + /* allocate hwmon channel configs */ + applesmc_temp_config = kcalloc(smcreg.index_count + 1, + sizeof(*applesmc_temp_config), GFP_KERNEL); + applesmc_fan_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_fan_config), GFP_KERNEL); + applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_pwm_config), GFP_KERNEL); + if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { + ret = -ENOMEM; goto out_info; + } - ret = applesmc_create_nodes(temp_group, smcreg.index_count); - if (ret) - goto out_fans; + for (i = 0; i < smcreg.index_count; i++) + applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; + applesmc_temp_config[smcreg.index_count] = 0; + + for (i = 0; i < smcreg.fan_count; i++) { + applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | + HWMON_F_MAX | HWMON_F_TARGET; + applesmc_pwm_config[i] = HWMON_PWM_ENABLE; + } + applesmc_fan_config[smcreg.fan_count] = 0; + applesmc_pwm_config[smcreg.fan_count] = 0; + + applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); + applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); + applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); + if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_temp->type = hwmon_temp; + applesmc_info_temp->config = applesmc_temp_config; + + applesmc_info_fan->type = hwmon_fan; + applesmc_info_fan->config = applesmc_fan_config; + + applesmc_info_pwm->type = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + + applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_arr[0] = applesmc_info_temp; + applesmc_info_arr[1] = applesmc_info_fan; + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + + applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_chip->ops = &applesmc_hwmon_ops; + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ + fan_safe_attrs = kcalloc(smcreg.fan_count, + sizeof(*fan_safe_attrs), GFP_KERNEL); + fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, + sizeof(*fan_safe_attr_list), GFP_KERNEL); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; + } + + for (i = 0; i < smcreg.fan_count; i++) { + struct applesmc_dev_attr *node = &fan_safe_attrs[i]; + + scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); + node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ + node->sda.dev_attr.show = applesmc_show_fan_speed; + node->sda.dev_attr.store = NULL; + sysfs_attr_init(&node->sda.dev_attr.attr); + node->sda.dev_attr.attr.name = node->name; + node->sda.dev_attr.attr.mode = 0444; + fan_safe_attr_list[i] = &node->sda.dev_attr.attr; + } + fan_safe_attr_list[smcreg.fan_count] = NULL; + fan_safe_group.attrs = fan_safe_attr_list; + applesmc_extra_groups[0] = &fan_safe_group; + applesmc_extra_groups[1] = NULL; ret = applesmc_create_accelerometer(); if (ret) - goto out_temperature; + goto out_info; ret = applesmc_create_light_sensor(); if (ret) @@ -1377,7 +1552,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + applesmc_chip, applesmc_extra_groups); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; @@ -1391,11 +1567,8 @@ static int __init applesmc_init(void) applesmc_release_light_sensor(); out_accelerometer: applesmc_release_accelerometer(); -out_temperature: - applesmc_destroy_nodes(temp_group); -out_fans: - applesmc_destroy_nodes(fan_group); out_info: + applesmc_free_hwmon(); applesmc_destroy_nodes(info_group); out_smcreg: applesmc_destroy_smcreg(); @@ -1416,13 +1589,12 @@ static void __exit applesmc_exit(void) applesmc_release_key_backlight(); applesmc_release_light_sensor(); applesmc_release_accelerometer(); - applesmc_destroy_nodes(temp_group); - applesmc_destroy_nodes(fan_group); applesmc_destroy_nodes(info_group); applesmc_destroy_smcreg(); platform_device_unregister(pdev); platform_driver_unregister(&applesmc_driver); release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); + applesmc_free_hwmon(); } module_init(applesmc_init); From 93238564c0be57a59d3fb4d902e36effc14ca2b9 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 20 Jul 2026 14:37:35 +0800 Subject: [PATCH 048/110] hwmon: (axi-fan-control) Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://lore.kernel.org/r/20260720063745.309331-2-panchuang@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index 1cb481a1ad26..04a767d762b3 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -508,8 +508,7 @@ static int axi_fan_control_probe(struct platform_device *pdev) IRQF_ONESHOT | IRQF_TRIGGER_HIGH, NULL, ctl); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed to request an irq\n"); + return ret; return 0; } From ff72fc130f9677463b7d29f009079621cffdb4b1 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 20 Jul 2026 14:37:36 +0800 Subject: [PATCH 049/110] hwmon: (da9055) Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://lore.kernel.org/r/20260720063745.309331-3-panchuang@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/da9055-hwmon.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c index 7652d553b8ea..7d330baa0f05 100644 --- a/drivers/hwmon/da9055-hwmon.c +++ b/drivers/hwmon/da9055-hwmon.c @@ -256,11 +256,8 @@ static int da9055_hwmon_probe(struct platform_device *pdev) NULL, da9055_auxadc_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "adc-irq", hwmon); - if (ret != 0) { - dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", - ret); + if (ret != 0) return ret; - } hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", hwmon, From 6dda9ad3149fe1e3704542767409cb8d92c18e8c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 20 Jul 2026 14:37:37 +0800 Subject: [PATCH 050/110] hwmon: (lm90) Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://lore.kernel.org/r/20260720063745.309331-4-panchuang@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lm90.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index c6186505661f..ae3b0a33d676 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -2918,10 +2918,8 @@ static int lm90_probe(struct i2c_client *client) err = devm_request_threaded_irq(dev, client->irq, NULL, lm90_irq_thread, IRQF_ONESHOT, "lm90", client); - if (err < 0) { - dev_err(dev, "cannot request IRQ %d\n", client->irq); + if (err < 0) return err; - } } return 0; From 87dc289532b5092d0b81e55c0a13a17e03102468 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 20 Jul 2026 14:37:38 +0800 Subject: [PATCH 051/110] hwmon: (npcm750-pwm-fan) Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://lore.kernel.org/r/20260720063745.309331-5-panchuang@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/npcm750-pwm-fan.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c index 6ff2a6bc06c1..d24242eea95e 100644 --- a/drivers/hwmon/npcm750-pwm-fan.c +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -996,10 +996,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) sprintf(name, "NPCM7XX-FAN-MD%d", i); ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr, 0, name, (void *)data); - if (ret) { - dev_err(dev, "register IRQ fan%d failed\n", i); + if (ret) return ret; - } } for_each_child_of_node_scoped(np, child) { From 45c6696215e47eeed7061d0c503f72e459003972 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 20 Jul 2026 14:37:39 +0800 Subject: [PATCH 052/110] hwmon: (pwm-fan) Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://lore.kernel.org/r/20260720063745.309331-6-panchuang@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 37f37813ea51..3b87f65bae05 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -628,12 +628,8 @@ static int pwm_fan_probe(struct platform_device *pdev) if (tach->irq > 0) { ret = devm_request_irq(dev, tach->irq, pulse_handler, IRQF_NO_THREAD, pdev->name, tach); - if (ret) { - dev_err(dev, - "Failed to request interrupt: %d\n", - ret); + if (ret) return ret; - } } if (!ctx->pulses_per_revolution[i]) { From b8ffbb950dffbcf810ba41e776516cf3438bbe25 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 20 Jul 2026 14:37:40 +0800 Subject: [PATCH 053/110] hwmon: (sht15) Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://lore.kernel.org/r/20260720063745.309331-7-panchuang@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/sht15.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 101cebbe68e4..7bb2105d1ae2 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -981,10 +981,8 @@ static int sht15_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING, "sht15 data", data); - if (ret) { - dev_err(&pdev->dev, "failed to get irq for data line\n"); + if (ret) goto err_release_reg; - } disable_irq_nosync(gpiod_to_irq(data->data)); ret = sht15_connection_reset(data); if (ret) From ff496cdace8896f3d6fabc22be64799fd9b9726e Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 20 Jul 2026 14:37:41 +0800 Subject: [PATCH 054/110] hwmon: (pmbus) Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://lore.kernel.org/r/20260720063745.309331-8-panchuang@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 3b219ed5386f..20d04dd713cc 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -3512,10 +3512,8 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data) /* Register notifiers */ err = devm_request_threaded_irq(dev, client->irq, NULL, pmbus_fault_handler, IRQF_ONESHOT, "pmbus-irq", data); - if (err) { - dev_err(dev, "failed to request an irq %d\n", err); + if (err) return err; - } return 0; } From 152de279c15443db0ca9891f4ef40fcdf76ca54f Mon Sep 17 00:00:00 2001 From: Nick Hawkins Date: Wed, 5 Jul 2023 14:45:42 -0500 Subject: [PATCH 055/110] dt-bindings: hwmon: hpe,gxp-fan-ctrl: remove fn2 and pl registers Reduce the hpe,gxp-fan-ctrl register references from 3 to 1. The function2 (fn2) and programmable logic (pl) references are removed. The purpose of removal being their functionality will be consumed by a new GPIO driver. Signed-off-by: Nick Hawkins Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230705194544.100370-4-nick.hawkins@hpe.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/hpe,gxp-fan-ctrl.yaml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/hpe,gxp-fan-ctrl.yaml b/Documentation/devicetree/bindings/hwmon/hpe,gxp-fan-ctrl.yaml index 4a52aac6be72..963aa640dc05 100644 --- a/Documentation/devicetree/bindings/hwmon/hpe,gxp-fan-ctrl.yaml +++ b/Documentation/devicetree/bindings/hwmon/hpe,gxp-fan-ctrl.yaml @@ -18,21 +18,12 @@ properties: const: hpe,gxp-fan-ctrl reg: - items: - - description: Fan controller PWM - - description: Programmable logic - - description: Function 2 - - reg-names: - items: - - const: base - - const: pl - - const: fn2 + description: Fan controller PWM + maxItems: 1 required: - compatible - reg - - reg-names additionalProperties: false @@ -40,6 +31,5 @@ examples: - | fan-controller@1000c00 { compatible = "hpe,gxp-fan-ctrl"; - reg = <0x1000c00 0x200>, <0xd1000000 0xff>, <0x80200000 0x100000>; - reg-names = "base", "pl", "fn2"; + reg = <0x1000c00 0x200>; }; From 6db3ba30de0d6faad7ebf23390e5707b58dbed1f Mon Sep 17 00:00:00 2001 From: Nick Hawkins Date: Wed, 5 Jul 2023 14:45:43 -0500 Subject: [PATCH 056/110] hwmon: (gxp_fan_ctrl) Provide fan info via gpio The fan driver now is independent of the fan plreg GPIO information. Therefore there will no longer be presence or fail information available from the driver. Part of the changes includes removing a system power check as the GPIO driver needs it to report power state to host. Signed-off-by: Nick Hawkins Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20230705194544.100370-5-nick.hawkins@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 2 +- drivers/hwmon/gxp-fan-ctrl.c | 106 +---------------------------------- 2 files changed, 3 insertions(+), 105 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 64fc00ade5ca..741704203e52 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -802,7 +802,7 @@ config SENSORS_GXP_FAN_CTRL If you say yes here you get support for GXP fan control functionality. The GXP controls fan function via the CPLD through the use of PWM - registers. This driver reports status and pwm setting of the fans. + registers. This driver enables pwm setting of the fans. config SENSORS_HIH6130 tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor" diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c index 86e7bafd3a38..64f85d7e748e 100644 --- a/drivers/hwmon/gxp-fan-ctrl.c +++ b/drivers/hwmon/gxp-fan-ctrl.c @@ -1,59 +1,16 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ +/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */ -#include #include #include #include #include #include -#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ -#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */ -#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */ -#define POWER_BIT 24 - struct gxp_fan_ctrl_drvdata { void __iomem *base; - void __iomem *plreg; - void __iomem *fn2; }; -static bool fan_installed(struct device *dev, int fan) -{ - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u8 val; - - val = readb(drvdata->plreg + OFS_FAN_INST); - - return !!(val & BIT(fan)); -} - -static long fan_failed(struct device *dev, int fan) -{ - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u8 val; - - val = readb(drvdata->plreg + OFS_FAN_FAIL); - - return !!(val & BIT(fan)); -} - -static long fan_enabled(struct device *dev, int fan) -{ - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u32 val; - - /* - * Check the power status as if the platform is off the value - * reported for the PWM will be incorrect. Report fan as - * disabled. - */ - val = readl(drvdata->fn2 + OFS_SEVSTAT); - - return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); -} - static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) { struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); @@ -80,37 +37,11 @@ static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, } } -static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) -{ - switch (attr) { - case hwmon_fan_enable: - *val = fan_enabled(dev, channel); - return 0; - case hwmon_fan_fault: - *val = fan_failed(dev, channel); - return 0; - default: - return -EOPNOTSUPP; - } -} - static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) { struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u32 reg; - /* - * Check the power status of the platform. If the platform is off - * the value reported for the PWM will be incorrect. In this case - * report a PWM of zero. - */ - - reg = readl(drvdata->fn2 + OFS_SEVSTAT); - - if (reg & BIT(POWER_BIT)) - *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; - else - *val = 0; + *val = readb(drvdata->base + channel); return 0; } @@ -119,8 +50,6 @@ static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { switch (type) { - case hwmon_fan: - return gxp_fan_read(dev, attr, channel, val); case hwmon_pwm: return gxp_pwm_read(dev, attr, channel, val); default: @@ -135,16 +64,6 @@ static umode_t gxp_fan_ctrl_is_visible(const void *_data, umode_t mode = 0; switch (type) { - case hwmon_fan: - switch (attr) { - case hwmon_fan_enable: - case hwmon_fan_fault: - mode = 0444; - break; - default: - break; - } - break; case hwmon_pwm: switch (attr) { case hwmon_pwm_input: @@ -168,15 +87,6 @@ static const struct hwmon_ops gxp_fan_ctrl_ops = { }; static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { - HWMON_CHANNEL_INFO(fan, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE), HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT, @@ -211,18 +121,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(drvdata->base), "failed to map base\n"); - drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, - "pl"); - if (IS_ERR(drvdata->plreg)) - return dev_err_probe(dev, PTR_ERR(drvdata->plreg), - "failed to map plreg\n"); - - drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, - "fn2"); - if (IS_ERR(drvdata->fn2)) - return dev_err_probe(dev, PTR_ERR(drvdata->fn2), - "failed to map fn2\n"); - hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "hpe_gxp_fan_ctrl", drvdata, From 596f410c05b188c14c7dd295536182afe28056ed Mon Sep 17 00:00:00 2001 From: Alexis Czezar Torreno Date: Wed, 22 Jul 2026 09:29:30 +0800 Subject: [PATCH 057/110] hwmon: (pmbus/max34440): add support for newer version of max34451 The MAX34451 released a newer version called max34451etna8+. This simply changes the direct format coefficients Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20260722-max34451_fixes-v2-2-bbddb18ec1d6@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max34440.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index e56057e9273c..81514727565f 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -59,6 +59,7 @@ enum chips { #define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A #define MAX34451ETNA6_MFR_REV 0x0012 +#define MAX34451ETNA8_MFR_REV 0x0014 #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f @@ -343,12 +344,24 @@ static int max34451_set_supported_funcs(struct i2c_client *client, max34451_na6 = true; data->info.format[PSC_VOLTAGE_IN] = direct; data->info.format[PSC_CURRENT_IN] = direct; - data->info.m[PSC_VOLTAGE_IN] = 1; + data->info.b[PSC_VOLTAGE_IN] = 0; - data->info.R[PSC_VOLTAGE_IN] = 3; - data->info.m[PSC_CURRENT_IN] = 1; data->info.b[PSC_CURRENT_IN] = 0; - data->info.R[PSC_CURRENT_IN] = 2; + if (rv >= MAX34451ETNA8_MFR_REV) { + data->info.m[PSC_VOLTAGE_IN] = 125; + data->info.R[PSC_VOLTAGE_IN] = 0; + data->info.m[PSC_VOLTAGE_OUT] = 125; + data->info.R[PSC_VOLTAGE_OUT] = 0; + data->info.m[PSC_CURRENT_IN] = 250; + data->info.R[PSC_CURRENT_IN] = -1; + data->info.m[PSC_CURRENT_OUT] = 250; + data->info.R[PSC_CURRENT_OUT] = -1; + } else { + data->info.m[PSC_VOLTAGE_IN] = 1; + data->info.R[PSC_VOLTAGE_IN] = 3; + data->info.m[PSC_CURRENT_IN] = 1; + data->info.R[PSC_CURRENT_IN] = 2; + } data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; } From 3d078657cf8c8b270136423eee2732842a688693 Mon Sep 17 00:00:00 2001 From: Carlos Jones Jr Date: Wed, 22 Jul 2026 09:29:31 +0800 Subject: [PATCH 058/110] hwmon: (pmbus/max34440): Add support for MAX34452 Add support for Maxim MAX34452 PMBus 16-Channel V/I Monitor and 12-Channel Sequencer/Marginer. The device is similar to MAX34451 and shares the same configuration function. The MAX34452 supports: - 16 configurable voltage/current monitoring channels - 5 temperature sensors (pages 16-20) - Dynamic channel configuration via MFR_CHANNEL_CONFIG - IOUT average monitoring Signed-off-by: Carlos Jones Jr Co-Developed by: Alexis Czezar Torreno Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20260722-max34451_fixes-v2-3-bbddb18ec1d6@analog.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/max34440.rst | 26 +++++++---- drivers/hwmon/pmbus/Kconfig | 5 ++- drivers/hwmon/pmbus/max34440.c | 75 ++++++++++++++++++++++++-------- 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/Documentation/hwmon/max34440.rst b/Documentation/hwmon/max34440.rst index e7421f4dbf38..866f22b88e9b 100644 --- a/Documentation/hwmon/max34440.rst +++ b/Documentation/hwmon/max34440.rst @@ -65,6 +65,16 @@ Supported chips: Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max34451.pdf + * Maxim MAX34452 + + PMBus 16-Channel V/I Monitor and 12-Channel Sequencer/Marginer + + Prefixes: 'max34452' + + Addresses scanned: - + + Datasheet: - + * Maxim MAX34460 PMBus 12-Channel Voltage Monitor & Sequencer @@ -94,11 +104,11 @@ Description This driver supports multiple devices: hardware monitoring for Maxim MAX34440 PMBus 6-Channel Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply Manager and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data -Logger; PMBus Voltage Monitor and Sequencers for MAX34451, MAX34460, and -MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, and ADPM12250. The -MAX34451 supports monitoring voltage or current of 12 channels based on GIN -pins. The MAX34460 supports 12 voltage channels, and the MAX34461 supports 16 -voltage channels. The ADPM12160, ADPM12200, and ADPM12250 also monitors both +Logger; PMBus Voltage Monitor and Sequencers for MAX34451, MAX34452, MAX34460, +and MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, and ADPM12250. The +MAX34451 and MAX34452 support monitoring voltage or current of 16 channels based +on GIN pins. The MAX34460 supports 12 voltage channels, and the MAX34461 supports +16 voltage channels. The ADPM12160, ADPM12200, and ADPM12250 also monitor both input and output of voltage and current. The driver is a client driver to the core PMBus driver. Please see @@ -171,7 +181,7 @@ curr[1-6]_crit Critical maximum current. From IOUT_OC_FAULT_LIMIT register. curr[1-6]_max_alarm Current high alarm. From IOUT_OC_WARNING status. curr[1-6]_crit_alarm Current critical high alarm. From IOUT_OC_FAULT status. -curr[1-4]_average Historical average current (MAX34446/34451 only). +curr[1-4]_average Historical average current (MAX34446/34451/34452 only). curr[1-6]_highest Historical maximum current. curr[1-6]_reset_history Write any value to reset history. ======================= ======================================================== @@ -223,7 +233,7 @@ temp[1-8]_reset_history Write any value to reset history. .. note:: - - MAX34451 supports attribute groups in[1-16] (or curr[1-16] based on - input pins) and temp[1-5]. + - MAX34451 and MAX34452 support attribute groups in[1-16] (or curr[1-16] + based on input pins) and temp[1-5]. - MAX34460 supports attribute groups in[1-12] and temp[1-5]. - MAX34461 supports attribute groups in[1-16] and temp[1-5]. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index ca312be58ed5..9b4bfdd00310 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -434,8 +434,9 @@ config SENSORS_MAX34440 tristate "Maxim MAX34440 and compatibles" help If you say yes here you get hardware monitoring support for Maxim - MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. - Other compatible are ADPM12160, and ADPM12200. + MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and + MAX34461. Other compatible devices are ADPM12160, ADPM12200, and + ADPM12250. This driver can also be built as a module. If so, the module will be called max34440. diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index 81514727565f..866d25ae8971 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -23,6 +23,7 @@ enum chips { max34441, max34446, max34451, + max34452, max34460, max34461, }; @@ -106,16 +107,21 @@ static int max34440_read_word_data(struct i2c_client *client, int page, case PMBUS_UT_FAULT_LIMIT: case PMBUS_MFR_MAX_TEMP_1: /* - * MAX34451/ADPM family do not support VIN/IIN limit registers, - * manufacturer-specific min/max registers, or undercurrent/ - * undertemperature fault limits. Accessing these triggers CML - * error and asserts ALERT. + * MAX34451/MAX34452/ADPM family do not support VIN/IIN limit + * registers, manufacturer-specific min/max registers, or + * undercurrent/undertemperature fault limits. Accessing these + * triggers CML error and asserts ALERT. */ - if (data->id == max34451 || data->id == adpm12160 || - data->id == adpm12200 || data->id == adpm12250) + if (data->id == max34451 || data->id == max34452 || + data->id == adpm12160 || data->id == adpm12200 || + data->id == adpm12250) return -ENXIO; ret = -ENODATA; break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->id == max34452) + return -ENXIO; + return -ENODATA; case PMBUS_VIRT_READ_VOUT_MIN: ret = pmbus_read_word_data(client, page, phase, MAX34440_MFR_VOUT_MIN); @@ -126,8 +132,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, break; case PMBUS_VIRT_READ_IOUT_AVG: if (data->id != max34446 && data->id != max34451 && - data->id != adpm12160 && data->id != adpm12200 && - data->id != adpm12250) + data->id != max34452 && data->id != adpm12160 && + data->id != adpm12200 && data->id != adpm12250) return -ENXIO; ret = pmbus_read_word_data(client, page, phase, MAX34446_MFR_IOUT_AVG); @@ -192,6 +198,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page, ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit, word); break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->id == max34452) + return -ENXIO; + return -ENODATA; case PMBUS_VIRT_RESET_POUT_HISTORY: ret = pmbus_write_word_data(client, page, MAX34446_MFR_POUT_PEAK, 0); @@ -212,8 +222,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, ret = pmbus_write_word_data(client, page, MAX34440_MFR_IOUT_PEAK, 0); if (!ret && (data->id == max34446 || data->id == max34451 || - data->id == adpm12160 || data->id == adpm12200 || - data->id == adpm12250)) + data->id == max34452 || data->id == adpm12160 || + data->id == adpm12200 || data->id == adpm12250)) ret = pmbus_write_word_data(client, page, MAX34446_MFR_IOUT_AVG, 0); @@ -281,12 +291,13 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg) case PMBUS_STATUS_BYTE: case PMBUS_STATUS_OTHER: /* - * MAX34451/ADPM family do not support STATUS_BYTE or + * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or * STATUS_OTHER registers. Accessing them triggers CML * error and asserts ALERT. */ - if (data->id == max34451 || data->id == adpm12160 || - data->id == adpm12200 || data->id == adpm12250) + if (data->id == max34451 || data->id == max34452 || + data->id == adpm12160 || data->id == adpm12200 || + data->id == adpm12250) return -ENXIO; return -ENODATA; default: @@ -304,12 +315,13 @@ static int max34451_write_byte_data(struct i2c_client *client, int page, case PMBUS_STATUS_BYTE: case PMBUS_STATUS_OTHER: /* - * MAX34451/ADPM family do not support STATUS_BYTE or + * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or * STATUS_OTHER registers. Writing to them triggers CML * error and asserts ALERT. */ - if (data->id == max34451 || data->id == adpm12160 || - data->id == adpm12200 || data->id == adpm12250) + if (data->id == max34451 || data->id == max34452 || + data->id == adpm12160 || data->id == adpm12200 || + data->id == adpm12250) return -ENXIO; return -ENODATA; default: @@ -340,7 +352,7 @@ static int max34451_set_supported_funcs(struct i2c_client *client, if (rv < 0) return rv; - if (rv >= MAX34451ETNA6_MFR_REV) { + if (data->id == max34451 && rv >= MAX34451ETNA6_MFR_REV) { max34451_na6 = true; data->info.format[PSC_VOLTAGE_IN] = direct; data->info.format[PSC_CURRENT_IN] = direct; @@ -678,6 +690,32 @@ static struct pmbus_driver_info max34440_info[] = { .write_word_data = max34440_write_word_data, .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, + [max34452] = { + .pages = 21, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + /* func 0-15 is set dynamically before probing */ + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34451_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_byte_data = max34451_write_byte_data, + .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, + }, [max34460] = { .pages = 18, .format[PSC_VOLTAGE_OUT] = direct, @@ -761,7 +799,7 @@ static int max34440_probe(struct i2c_client *client) data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; - if (data->id == max34451) { + if (data->id == max34451 || data->id == max34452) { rv = max34451_set_supported_funcs(client, data); if (rv) return rv; @@ -782,6 +820,7 @@ static const struct i2c_device_id max34440_id[] = { { .name = "max34441", .driver_data = max34441 }, { .name = "max34446", .driver_data = max34446 }, { .name = "max34451", .driver_data = max34451 }, + { .name = "max34452", .driver_data = max34452 }, { .name = "max34460", .driver_data = max34460 }, { .name = "max34461", .driver_data = max34461 }, { } From bd300460cee457c50a16a42654d6de4c85562785 Mon Sep 17 00:00:00 2001 From: surendra Date: Tue, 21 Jul 2026 11:20:22 +0530 Subject: [PATCH 059/110] hwmon: (pmbus/ltc4283) Add missing MODULE_DEVICE_TABLE for OF match table The driver registers ltc4283_of_match as the platform driver's .of_match_table so the kernel probes the device when a DT node with compatible "adi,ltc4283" is present, but the corresponding MODULE_DEVICE_TABLE(of, ...) call is absent. Without it the module alias table (modules.alias) does not contain an "of:N*T*Cadi,ltc4283" entry, so udev / kmod cannot auto-load the module when the DT node appears, breaking modules-based deployments while built-in configurations happen to work by accident. Add the missing MODULE_DEVICE_TABLE() to fix module auto-loading. Fixes: dd63353a0b5e ("hwmon: ltc4283: Add support for the LTC4283 Swap Controller") Signed-off-by: surendra Link: https://lore.kernel.org/r/20260721055022.34629-1-kr494167@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4283.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/ltc4283.c b/drivers/hwmon/ltc4283.c index 9b85293ea664..9ec409bc2648 100644 --- a/drivers/hwmon/ltc4283.c +++ b/drivers/hwmon/ltc4283.c @@ -1772,6 +1772,7 @@ static const struct of_device_id ltc4283_of_match[] = { { .compatible = "adi,ltc4283" }, { } }; +MODULE_DEVICE_TABLE(of, ltc4283_of_match); static const struct i2c_device_id ltc4283_i2c_id[] = { { "ltc4283" }, From 9fcd738d131d5e5fbd04bf3720751a2d73a7cea8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 5 Feb 2025 12:34:13 -0800 Subject: [PATCH 060/110] hwmon: (pmbus/mp2975) Eliminate dead code Coverity reports dead code in mp2975_data2reg_linear11(). Code analysis shows that the parameter passed to the function is always non-negative. This means that negative values do not need to be handled, and the associated variable and conditional code can be removed. No functional impact or change. Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/mp2975.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c index 5393f7aeea0f..8e5193933ceb 100644 --- a/drivers/hwmon/pmbus/mp2975.c +++ b/drivers/hwmon/pmbus/mp2975.c @@ -176,7 +176,6 @@ mp2975_vid2direct(int vrf, int val) static u16 mp2975_data2reg_linear11(s64 val) { s16 exponent = 0, mantissa; - bool negative = false; /* simple case */ if (val == 0) @@ -196,10 +195,6 @@ static u16 mp2975_data2reg_linear11(s64 val) /* Convert mantissa from milli-units to units */ mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); - /* restore sign */ - if (negative) - mantissa = -mantissa; - /* Convert to 5 bit exponent, 11 bit mantissa */ return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); } From 828ac5f5a392b68ff85ded56562bda20cf4fa8d8 Mon Sep 17 00:00:00 2001 From: "shaikh.kamal" Date: Sun, 26 Jul 2026 12:28:35 +0530 Subject: [PATCH 061/110] docs: hwmon: sg2042-mcu: fix spelling error Fix spelling error reported by codespell: supprts -> supports No functional change. Signed-off-by: shaikh.kamal Link: https://lore.kernel.org/r/20260726065835.21012-1-shaikhkamal2012@gmail.com [groeck: Move driver name to subject line] Signed-off-by: Guenter Roeck --- Documentation/hwmon/sg2042-mcu.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/sg2042-mcu.rst b/Documentation/hwmon/sg2042-mcu.rst index 077e79841d2e..431ed832d083 100644 --- a/Documentation/hwmon/sg2042-mcu.rst +++ b/Documentation/hwmon/sg2042-mcu.rst @@ -18,7 +18,7 @@ Authors: Description ----------- -This driver supprts hardware monitoring for onboard MCU with +This driver supports hardware monitoring for onboard MCU with i2c interface. Usage Notes From 1d687d863c43ca60564136b34cbf4632f89b8aeb Mon Sep 17 00:00:00 2001 From: Andy Chung Date: Fri, 24 Jul 2026 12:47:37 +0800 Subject: [PATCH 062/110] dt-bindings: Add vendor prefix for Kandou Kandou AI is the vendor of the KB9002 PCIe retimer. Signed-off-by: Andy Chung Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260724-kb9002-upstream-v5-1-9523c3ffe83a@amd.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 6793125d6cc6..29afa8d30aa0 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -881,6 +881,8 @@ patternProperties: description: JuTouch Technology Co., Ltd. "^kam,.*": description: Kamstrup A/S + "^kandou,.*": + description: Kandou AI "^karo,.*": description: Ka-Ro electronics GmbH "^keithkoep,.*": From 495e5dead2cce443c43f55c09d5f5e42392d3dbc Mon Sep 17 00:00:00 2001 From: Andy Chung Date: Fri, 24 Jul 2026 12:47:38 +0800 Subject: [PATCH 063/110] dt-bindings: trivial-devices: Add Kandou KB9002 The Kandou KB9002 is an 8-lane PCIe 5.0 retimer exposing an SMBus target that reports an aggregated die temperature. It needs no device-specific device tree properties beyond its address, so add it to trivial-devices. Signed-off-by: Andy Chung Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260724-kb9002-upstream-v5-2-9523c3ffe83a@amd.com 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 8116f21bf8be..2de8eb09cb7d 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -196,6 +196,8 @@ properties: - isil,isl76682 # JEDEC JESD300 (SPD5118) Hub and Serial Presence Detect - jedec,spd5118 + # Kandou KB9002 PCIe 5.0 retimer + - kandou,kb9002 # Linear Technology LTC2488 - lineartechnology,ltc2488 # Regulated 12V, 860W, Digital DC/DC Power Module From 694329073a3a964278a80a3ce1202b770f311b4d Mon Sep 17 00:00:00 2001 From: Andy Chung Date: Fri, 24 Jul 2026 12:47:39 +0800 Subject: [PATCH 064/110] hwmon: (kb9002) Add driver for Kandou KB9002 retimer The Kandou KB9002 is an 8-lane PCIe 5.0 retimer that exposes an SMBus target with mandatory PEC. Add a hwmon driver reporting the firmware aggregated maximum die temperature as temp1_input, with the firmware version and boot status under debugfs. Signed-off-by: Andy Chung Link: https://lore.kernel.org/r/20260724-kb9002-upstream-v5-3-9523c3ffe83a@amd.com Signed-off-by: Guenter Roeck --- MAINTAINERS | 7 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/kb9002.c | 460 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 479 insertions(+) create mode 100644 drivers/hwmon/kb9002.c diff --git a/MAINTAINERS b/MAINTAINERS index c97521e6bd8e..119bc58dbc43 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13930,6 +13930,13 @@ S: Maintained F: Documentation/hwmon/k8temp.rst F: drivers/hwmon/k8temp.c +KANDOU KB9002 PCIE RETIMER HWMON DRIVER +M: Andy Chung +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/kb9002.rst +F: drivers/hwmon/kb9002.c + KASAN M: Andrey Ryabinin R: Alexander Potapenko diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 741704203e52..6b71e3304b4f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -335,6 +335,17 @@ config SENSORS_K10TEMP This driver can also be built as a module. If so, the module will be called k10temp. +config SENSORS_KB9002 + tristate "Kandou KB9002 PCIe retimer" + depends on I2C + help + If you say yes here you get support for the integrated + temperature sensor and firmware version readout on Kandou + KB9002 PCIe 5.0 retimers, accessed over SMBus. + + This driver can also be built as a module. If so, the module + will be called kb9002. + config SENSORS_KBATT tristate "KEBA battery controller support" depends on KEBA_CP500 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 63809eeec2f4..0cad7e21634c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -113,6 +113,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o +obj-$(CONFIG_SENSORS_KB9002) += kb9002.o obj-$(CONFIG_SENSORS_KBATT) += kbatt.o obj-$(CONFIG_SENSORS_KFAN) += kfan.o obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o diff --git a/drivers/hwmon/kb9002.c b/drivers/hwmon/kb9002.c new file mode 100644 index 000000000000..9f4521630f3a --- /dev/null +++ b/drivers/hwmon/kb9002.c @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Kandou KB9002 PCIe 5.0 retimer hwmon driver. + * + * The retimer exposes a system management bus (SMBus 3.0 with PEC) + * target for firmware-managed status registers. This driver assumes + * the chip is strapped to SMBus mode and exports the aggregated + * maximum die temperature as hwmon temp1_input (millidegrees Celsius) + * plus the firmware version and boot status under debugfs. + * + * The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch + * the host interface) carries the 32-bit register address and data + * big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read) + * carries the register address and returned data little-endian. + * + * Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KB9002_DEV_NAME "kb9002" + +/* + * SMBus read command codes. Each read is a two-phase PEC-protected + * transaction: prime writes the target address, data reads it back with + * the register contents. FW reads use a 16-bit address, HW reads 32-bit. + */ +#define KB9002_CC_FW_READ_PRIME 0x82 +#define KB9002_CC_FW_READ_DATA 0x81 +#define KB9002_CC_HW_READ_PRIME 0x8a +#define KB9002_CC_HW_READ_DATA 0x89 + +/* Firmware register offsets (16-bit). */ +#define KB9002_FW_REG_VID 0x0004 +#define KB9002_FW_REG_FW_VERSION 0x0500 +#define KB9002_FW_REG_TEMP_MAXIMUM 0x0550 + +#define KB9002_VID_MASK GENMASK(31, 16) +#define KB9002_VID_KANDOU 0x1e6f + +/* Firmware boot status: 0xe8 in the top byte means init completed OK. */ +#define KB9002_HW_REG_FW_BOOT_STATUS 0xe0090008 +#define KB9002_FW_BOOT_STATUS_OK_MSB 0xe8 + +/* + * Hardware registers reached over raw I2C (32-bit addressing). The + * host-interface bit selects SMBus (set) vs raw-I2C target; parts + * strapped to raw I2C need it set before SMBus access works. + */ +#define KB9002_HW_REG_REVID 0x00480004 +#define KB9002_HW_REG_HOST_IF 0x00480008 +#define KB9002_HOST_IF_SMBUS BIT(1) + +#define KB9002_REVID_MASK GENMASK(7, 0) +#define KB9002_REVID_B0 0x10 +#define KB9002_REVID_B1 0x11 + +/* Retries to drain a stray leading 0xff from the raw-I2C FIFO. */ +#define KB9002_REVID_READ_RETRIES 16 + +/* Temperature: 32-bit Q16.16 absolute Kelvin. */ +#define KB9002_TEMP_FRAC_BITS 16 +#define KB9002_ABS_ZERO_MILLI_C (-273150) + +/* Firmware takes up to ~2s to respond after a host-interface change. */ +#define KB9002_FW_READY_POLL_US (25 * USEC_PER_MSEC) +#define KB9002_FW_READY_TIMEOUT_US (2 * USEC_PER_SEC) + +struct kb9002_data { + struct i2c_client *client; + struct device *hwmon_dev; +}; + +/* Raw-I2C read: write the 32-bit BE address, then read 4 BE data bytes. */ +static int kb9002_i2c_read(struct i2c_client *client, u32 reg, u32 *val) +{ + u8 addr[4]; + u8 rbuf[4]; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(addr), + .buf = addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(rbuf), + .buf = rbuf, + }, + }; + int ret; + + put_unaligned_be32(reg, addr); + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(rbuf); + return 0; +} + +/* Raw-I2C write: 4 BE address bytes followed by 4 BE data bytes. */ +static int kb9002_i2c_write(struct i2c_client *client, u32 reg, u32 val) +{ + u8 buf[8]; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + }; + int ret; + + put_unaligned_be32(reg, &buf[0]); + put_unaligned_be32(val, &buf[4]); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + if (ret != 1) + return -EIO; + + return 0; +} + +/* + * Read the silicon revision ID. A fresh FIFO may start with a stray + * 0xff that shifts the result, so drain one byte between retries until + * the top byte is no longer 0xff. + */ +static int kb9002_read_revid(struct i2c_client *client, u32 *revid) +{ + u8 dummy; + int ret; + int i; + + for (i = 0; i < KB9002_REVID_READ_RETRIES; i++) { + ret = kb9002_i2c_read(client, KB9002_HW_REG_REVID, revid); + if (ret) + return ret; + if ((*revid >> 24) != 0xff) + return 0; + /* Drain one byte from the chip to re-align the I2C FIFO. */ + i2c_master_recv(client, &dummy, 1); + } + + return -EIO; +} + +/* + * Read a 32-bit firmware register over SMBus: block-write the 16-bit LE + * address, then block-read the echoed address plus 4 LE data bytes. + */ +static int kb9002_fw_read(struct kb9002_data *data, u16 reg, u32 *val) +{ + struct i2c_client *client = data->client; + u8 addr[2]; + u8 rbuf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + put_unaligned_le16(reg, addr); + + ret = i2c_smbus_write_block_data(client, KB9002_CC_FW_READ_PRIME, + sizeof(addr), addr); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_block_data(client, KB9002_CC_FW_READ_DATA, rbuf); + if (ret < 0) + return ret; + if (ret < (int)(sizeof(addr) + sizeof(*val))) + return -EIO; + + *val = get_unaligned_le32(&rbuf[sizeof(addr)]); + return 0; +} + +/* Like kb9002_fw_read but for a hardware register (32-bit LE address). */ +static int kb9002_smbus_hw_read(struct kb9002_data *data, u32 reg, u32 *val) +{ + struct i2c_client *client = data->client; + u8 addr[4]; + u8 rbuf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + put_unaligned_le32(reg, addr); + + ret = i2c_smbus_write_block_data(client, KB9002_CC_HW_READ_PRIME, + sizeof(addr), addr); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_block_data(client, KB9002_CC_HW_READ_DATA, rbuf); + if (ret < 0) + return ret; + if (ret < (int)(sizeof(addr) + sizeof(*val))) + return -EIO; + + *val = get_unaligned_le32(&rbuf[sizeof(addr)]); + return 0; +} + +/* + * Switch the host interface from raw-I2C to SMBus and wait for firmware + * to come back up. Called only when SMBus access failed in probe, i.e. + * the chip is strapped to raw-I2C mode. Confirms the revision, sets the + * SMBus-mode bit, then polls until firmware responds again. + */ +static int kb9002_enable_smbus_target(struct kb9002_data *data) +{ + struct i2c_client *client = data->client; + u32 revid; + u32 val; + int op_ret; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(&client->dev, -ENODEV, + "raw I2C required to switch to SMBus mode\n"); + + ret = kb9002_read_revid(client, &revid); + if (ret) + return dev_err_probe(&client->dev, ret, + "revision ID read failed\n"); + + switch (FIELD_GET(KB9002_REVID_MASK, revid)) { + case KB9002_REVID_B0: + case KB9002_REVID_B1: + break; + default: + return dev_err_probe(&client->dev, -ENODEV, + "unsupported revision ID 0x%08x\n", revid); + } + + ret = kb9002_i2c_read(client, KB9002_HW_REG_HOST_IF, &val); + if (ret) + return dev_err_probe(&client->dev, ret, + "host interface read failed\n"); + + val |= KB9002_HOST_IF_SMBUS; + + ret = kb9002_i2c_write(client, KB9002_HW_REG_HOST_IF, val); + if (ret) + return dev_err_probe(&client->dev, ret, + "host interface write failed\n"); + + /* Wait until firmware re-initialisation completes. */ + ret = read_poll_timeout(kb9002_fw_read, op_ret, op_ret == 0, + KB9002_FW_READY_POLL_US, + KB9002_FW_READY_TIMEOUT_US, true, + data, KB9002_FW_REG_VID, &val); + if (ret) + return dev_err_probe(&client->dev, ret, + "firmware not responding over SMBus\n"); + + return 0; +} + +/* Convert Q16.16 absolute Kelvin to millidegrees Celsius. */ +static long kb9002_temp_to_milli_c(u32 raw) +{ + s64 milli_k = ((s64)raw * 1000) >> KB9002_TEMP_FRAC_BITS; + + return (long)milli_k + KB9002_ABS_ZERO_MILLI_C; +} + +static int kb9002_read_temp(struct kb9002_data *data, long *val) +{ + u32 raw; + int ret; + + ret = kb9002_fw_read(data, KB9002_FW_REG_TEMP_MAXIMUM, &raw); + if (ret) + return ret; + + *val = kb9002_temp_to_milli_c(raw); + return 0; +} + +static umode_t kb9002_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static int kb9002_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct kb9002_data *data = dev_get_drvdata(dev); + + if (type == hwmon_temp && attr == hwmon_temp_input) + return kb9002_read_temp(data, val); + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops kb9002_hwmon_ops = { + .is_visible = kb9002_is_visible, + .read = kb9002_read, +}; + +static const struct hwmon_channel_info * const kb9002_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL, +}; + +static const struct hwmon_chip_info kb9002_chip_info = { + .ops = &kb9002_hwmon_ops, + .info = kb9002_hwmon_info, +}; + +static int kb9002_fw_version_show(struct seq_file *s, void *unused) +{ + struct kb9002_data *data = s->private; + u32 ver; + int ret; + + guard(hwmon_lock)(data->hwmon_dev); + + ret = kb9002_fw_read(data, KB9002_FW_REG_FW_VERSION, &ver); + if (ret) + return ret; + + seq_printf(s, "%u.%02u.%02u.%u\n", + (ver >> 24) & 0xff, (ver >> 16) & 0xff, + (ver >> 8) & 0xff, (ver >> 0) & 0xff); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(kb9002_fw_version); + +static int kb9002_fw_load_status_show(struct seq_file *s, void *unused) +{ + struct kb9002_data *data = s->private; + u32 status; + int ret; + + guard(hwmon_lock)(data->hwmon_dev); + + ret = kb9002_smbus_hw_read(data, KB9002_HW_REG_FW_BOOT_STATUS, &status); + if (ret) + return ret; + + seq_printf(s, "%s\n", + (status >> 24) == KB9002_FW_BOOT_STATUS_OK_MSB ? + "normal" : "abnormal"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(kb9002_fw_load_status); + +static void kb9002_debugfs_init(struct kb9002_data *data) +{ + struct dentry *dir = data->client->debugfs; + + debugfs_create_file("fw_ver", 0444, dir, data, + &kb9002_fw_version_fops); + debugfs_create_file("fw_load_status", 0444, dir, data, + &kb9002_fw_load_status_fops); +} + +static int kb9002_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct kb9002_data *data; + struct device *hwmon_dev; + u32 vid; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_SMBUS_PEC)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + + /* All firmware register accesses are PEC-protected. */ + client->flags |= I2C_CLIENT_PEC; + + i2c_set_clientdata(client, data); + + /* + * Try SMBus first. If the chip is strapped to raw-I2C mode it + * will not respond to SMBus framing, so fall back to switching + * the host interface over raw I2C and retry. + */ + ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); + if (ret) { + dev_dbg(dev, "SMBus probe failed (%d), trying raw-I2C host-interface switch\n", + ret); + ret = kb9002_enable_smbus_target(data); + if (ret) + return ret; + ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); + if (ret) + return dev_err_probe(dev, ret, + "VID read failed after host-interface switch\n"); + } + if (FIELD_GET(KB9002_VID_MASK, vid) != KB9002_VID_KANDOU) + return dev_err_probe(dev, -ENODEV, + "unexpected VID 0x%08x\n", vid); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, KB9002_DEV_NAME, + data, + &kb9002_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + data->hwmon_dev = hwmon_dev; + kb9002_debugfs_init(data); + return 0; +} + +static const struct i2c_device_id kb9002_id[] = { + { .name = KB9002_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, kb9002_id); + +static const struct of_device_id kb9002_of_match[] = { + { .compatible = "kandou,kb9002" }, + { } +}; +MODULE_DEVICE_TABLE(of, kb9002_of_match); + +static struct i2c_driver kb9002_driver = { + .driver = { + .name = KB9002_DEV_NAME, + .of_match_table = kb9002_of_match, + }, + .probe = kb9002_probe, + .id_table = kb9002_id, +}; +module_i2c_driver(kb9002_driver); + +MODULE_AUTHOR("Andy Chung "); +MODULE_DESCRIPTION("Kandou KB9002 PCIe retimer hwmon driver"); +MODULE_LICENSE("GPL"); From 20e95671a612b18382d930ddbe1ee45a5eb855ba Mon Sep 17 00:00:00 2001 From: Andy Chung Date: Fri, 24 Jul 2026 12:47:40 +0800 Subject: [PATCH 065/110] hwmon: (kb9002) Add documentation Document the sysfs and debugfs interfaces of the Kandou KB9002 hwmon driver. Signed-off-by: Andy Chung Link: https://lore.kernel.org/r/20260724-kb9002-upstream-v5-4-9523c3ffe83a@amd.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/kb9002.rst | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 Documentation/hwmon/kb9002.rst diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 956f3da0b722..a8b3e870d13e 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -112,6 +112,7 @@ Hardware Monitoring Kernel Drivers jc42 k10temp k8temp + kb9002 kbatt kfan lan966x diff --git a/Documentation/hwmon/kb9002.rst b/Documentation/hwmon/kb9002.rst new file mode 100644 index 000000000000..6f8116dde918 --- /dev/null +++ b/Documentation/hwmon/kb9002.rst @@ -0,0 +1,64 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver kb9002 +==================== + +Supported chips: + + * Kandou KB9002 + + Prefix: 'kb9002' + + Addresses scanned: - + + Datasheet: KA-015171-PD (available from Kandou under NDA) + +Author: Andy Chung + +Description +----------- + +The Kandou KB9002 is an 8-lane PCIe 5.0 retimer with an integrated +microcontroller. It exposes an SMBus 3.0 target (with mandatory PEC) +on its sideband interface. The internal firmware aggregates per-lane +die temperatures and publishes the maximum value through a 16-bit +addressed register window. + +This driver reports that aggregated maximum as the only hwmon +temperature channel. The running firmware version and the firmware +boot status are exposed under debugfs. + +sysfs interface +--------------- + +================== =============================================== +temp1_input Aggregated maximum die temperature across all + active lanes (millidegrees Celsius). +================== =============================================== + +debugfs interface +----------------- + +Files live in the per-client debugfs directory created by the I2C +core: ``/sys/kernel/debug/i2c/i2c-/-/``. + +================== =============================================== +fw_ver Running firmware version in + "major.minor.patch.suffix" format. Read-only. +fw_load_status Firmware boot status: "normal" once firmware has + finished initialising, "abnormal" otherwise. + Read-only. +================== =============================================== + +Notes +----- + +The driver requires ``I2C_FUNC_SMBUS_BLOCK_DATA``, +``I2C_FUNC_SMBUS_PEC`` and ``I2C_FUNC_I2C`` from the host adapter. The +last is needed only during probe for the host-interface mode switch; +runtime accesses use SMBus block transactions exclusively. + +The retimer's SMBus address is configurable on three strap pins and +ranges from 0x20 to 0x27. The address is selected through device tree +(or i2c board info) the same way as any other I2C device; the driver +does not auto-detect. From 047e3f8667aeb7b4780c167b99d82fa18675e537 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Sat, 25 Jul 2026 21:20:06 +0000 Subject: [PATCH 066/110] dt-bindings: hwmon: pmbus: Add Analog Devices MAX16545/MAX16550 and Volterra VT7505 Add device tree binding documentation for the Analog Devices MAX16545 and MAX16550, and the Volterra VT7505 PMBus controllers. Signed-off-by: Sanman Pradhan Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260725211945.288415-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/adi,vt7505.yaml | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/adi,vt7505.yaml diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,vt7505.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,vt7505.yaml new file mode 100644 index 000000000000..ce5e9456c318 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,vt7505.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/pmbus/adi,vt7505.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX16545/MAX16550 and Volterra VT7505 PMBus controllers + +maintainers: + - Sanman Pradhan + +description: | + The Analog Devices MAX16545 and MAX16550, and the Volterra VT7505, are PMBus + controllers that provide input/output voltage, input/output current, + input power and temperature monitoring over the PMBus interface. + + The MAX16545 uses the same programming model as the VT7505. + + Datasheets: + https://www.analog.com/media/en/technical-documentation/data-sheets/max16545b-max16545c.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/max16550.pdf + +properties: + compatible: + oneOf: + - enum: + - adi,max16550 + - adi,vt7505 + - items: + - const: adi,max16545 + - const: adi,vt7505 + + reg: + maxItems: 1 + + adi,rload-ohms: + description: + Resistance in ohms of the external telemetry load resistor connected + between the ILOAD current-output pin and ground. This is not a + current-path shunt resistor; it sets the scaling of the reported input + current, output current and input power. + minimum: 1 + maximum: 283383959 + default: 4750 + + adi,ocp-severe-filter-us: + description: + Deglitch time, in microseconds, applied to the severe + overcurrent-protection comparator, programmed into the + MFR_CONFIG[15:14] field. The MAX16550 and VT7505 expose this field. + On the MAX16545 the severe overcurrent delay is fixed and these bits + are reserved. If omitted, the existing hardware setting is retained. + enum: [0, 1, 2, 10] + +required: + - compatible + - reg + +allOf: + # The severe OCP deglitch filter is fixed on the MAX16545, so the property + # is only valid for the MAX16550 and VT7505. + - if: + properties: + compatible: + contains: + const: adi,max16545 + then: + properties: + adi,ocp-severe-filter-us: false + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@40 { + compatible = "adi,vt7505"; + reg = <0x40>; + adi,rload-ohms = <4750>; + adi,ocp-severe-filter-us = <10>; + }; + }; From 97c1723a1d35ee3046eeb97b15eb69b87da0bea5 Mon Sep 17 00:00:00 2001 From: Georgi Vlaev Date: Sat, 25 Jul 2026 21:20:14 +0000 Subject: [PATCH 067/110] hwmon: (pmbus/vt7505) Add driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505 Add a PMBus driver for the Analog Devices MAX16545 and MAX16550, and the Volterra VT7505 controllers. The driver reports input/output voltage, input/output current, input power and temperature over PMBus. All values use the direct data format. Peak input voltage, output voltage, output current, input power and temperature readings are exposed and can be reset. The reported current and power depend on the external current-report resistor connected to the ILOAD pin, which can be provided through the "adi,rload-ohms" device tree property. On the MAX16550 and VT7505, the severe-OCP deglitch time can optionally be configured with the "adi,ocp-severe-filter-us" device tree property. When omitted, the existing hardware setting is left unchanged. Co-developed-by: Dhruva Devineni Co-developed-by: Debjit Ghosh Co-developed-by: JawaharBalaji Thirumalaisamy Co-developed-by: Avadhut Naik Co-developed-by: Santhanakrishnan Balraj Co-developed-by: Brian Mak Signed-off-by: Georgi Vlaev Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260725211945.288415-3-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/vt7505.rst | 89 ++++++++++ MAINTAINERS | 8 + drivers/hwmon/pmbus/Kconfig | 9 ++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/vt7505.c | 287 +++++++++++++++++++++++++++++++++ 6 files changed, 395 insertions(+) create mode 100644 Documentation/hwmon/vt7505.rst create mode 100644 drivers/hwmon/pmbus/vt7505.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index a8b3e870d13e..e924e511bc60 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -282,6 +282,7 @@ Hardware Monitoring Kernel Drivers vexpress via686a vt1211 + vt7505 w83627ehf w83627hf w83773g diff --git a/Documentation/hwmon/vt7505.rst b/Documentation/hwmon/vt7505.rst new file mode 100644 index 000000000000..e56dc0055e89 --- /dev/null +++ b/Documentation/hwmon/vt7505.rst @@ -0,0 +1,89 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver vt7505 +==================== + +Supported chips: + + * Analog Devices MAX16545 + + Prefix: 'max16545' + + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max16545b-max16545c.pdf + + * Analog Devices MAX16550 + + Prefix: 'max16550' + + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max16550.pdf + + * Volterra VT7505 + + Prefix: 'vt7505' + + Datasheet: Not publicly available; register-compatible with the MAX16545. + +Author: Georgi Vlaev + +Description +----------- + +This driver supports hardware monitoring for the Analog Devices MAX16545 and +MAX16550, and the Volterra VT7505 PMBus controllers. + +The devices report input and output voltage, input and output current, +input power, and temperature. All values use the PMBus direct data format. +Peak (maximum) values for input voltage, output voltage, output current, +input power and temperature are exposed and can be reset. Writing to any +``*_reset_history`` attribute clears all of the device's peak values. + +The reported current and power values depend on the resistance of the +external current-report resistor connected between the ILOAD pin and +ground. This resistance can be provided via the ``adi,rload-ohms`` +device tree property; a default of 4750 ohms is used when it is absent. + +Sysfs entries +------------- + +The limit and alarm attributes are created by the PMBus core for the limit +registers that the device implements. See +Documentation/hwmon/sysfs-interface.rst for the meaning of each attribute. + +======================= ======================================================== +in1_label "vin" +in1_input Measured input voltage. +in1_min Input undervoltage warning limit. +in1_min_alarm Input undervoltage warning alarm. +in1_lcrit Input undervoltage fault limit. +in1_lcrit_alarm Input undervoltage fault alarm. +in1_highest Historical maximum input voltage. +in1_reset_history Write to reset all peak values. +in2_label "vout1" +in2_input Measured output voltage. +in2_min Output undervoltage warning limit. +in2_min_alarm Output undervoltage warning alarm. +in2_highest Historical maximum output voltage. +in2_reset_history Write to reset all peak values. +curr1_label "iin" +curr1_input Measured input current. +curr1_alarm Input current alarm. +curr2_label "iout1" +curr2_input Measured output current. +curr2_max Output overcurrent warning limit. +curr2_max_alarm Output overcurrent warning alarm. +curr2_highest Historical maximum output current. +curr2_reset_history Write to reset all peak values. +power1_label "pin" +power1_input Measured input power. +power1_max Input overpower warning limit. +power1_alarm Input overpower warning alarm. +power1_input_highest Historical maximum input power. +power1_reset_history Write to reset all peak values. +temp1_input Measured temperature. +temp1_max Overtemperature warning limit. +temp1_max_alarm Overtemperature warning alarm. +temp1_crit Overtemperature fault limit. +temp1_crit_alarm Overtemperature fault alarm. +temp1_highest Historical maximum temperature. +temp1_reset_history Write to reset all peak values. +======================= ======================================================== diff --git a/MAINTAINERS b/MAINTAINERS index 119bc58dbc43..2142c15b9946 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28972,6 +28972,14 @@ S: Maintained F: Documentation/hwmon/vt1211.rst F: drivers/hwmon/vt1211.c +VT7505 HARDWARE MONITOR DRIVER +M: Sanman Pradhan +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/pmbus/adi,vt7505.yaml +F: Documentation/hwmon/vt7505.rst +F: drivers/hwmon/pmbus/vt7505.c + VT8231 HARDWARE MONITOR DRIVER M: Roger Lucas L: linux-hwmon@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 9b4bfdd00310..becc311dacc6 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -796,6 +796,15 @@ config SENSORS_UCD9200 This driver can also be built as a module. If so, the module will be called ucd9200. +config SENSORS_VT7505 + tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505" + help + If you say yes here you get hardware monitoring support for Analog + Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers. + + This driver can also be built as a module. If so, the module will + be called vt7505. + config SENSORS_XDP710 tristate "Infineon XDP710 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 080d366809f3..daa4b49bc90f 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o +obj-$(CONFIG_SENSORS_VT7505) += vt7505.o obj-$(CONFIG_SENSORS_XDP710) += xdp710.o obj-$(CONFIG_SENSORS_XDP720) += xdp720.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o diff --git a/drivers/hwmon/pmbus/vt7505.c b/drivers/hwmon/pmbus/vt7505.c new file mode 100644 index 000000000000..6a66f06eddb8 --- /dev/null +++ b/drivers/hwmon/pmbus/vt7505.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and + * Volterra VT7505 PMBus controllers. + * + * Copyright 2026 Hewlett Packard Enterprise Development LP + */ + +#include +#include +#include +#include +#include +#include + +#include "pmbus.h" + +#define VT7505_MFR_CONFIG 0xd0 +#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14) + +#define VT7505_MFR_PEAK_VIN 0xd1 +#define VT7505_MFR_PEAK_IOUT 0xd2 +#define VT7505_MFR_PEAK_PIN 0xd3 +#define VT7505_MFR_PEAK_TEMP 0xd4 +#define VT7505_MFR_CLEAR_PEAKS 0xd5 +#define VT7505_MFR_PEAK_VOUT 0xfd + +#define VT7505_RLOAD_DEFAULT 4750 +/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */ +#define VT7505_RLOAD_MAX 283383959U + +struct vt7505_chip_data { + int temp_m; + int temp_b; + bool has_ocp_filter; +}; + +static const struct vt7505_chip_data max16545_data = { + .temp_m = 205, + .temp_b = 6545, +}; + +static const struct vt7505_chip_data max16550_data = { + .temp_m = 199, + .temp_b = 7046, + .has_ocp_filter = true, +}; + +static const struct vt7505_chip_data vt7505_data = { + .temp_m = 205, + .temp_b = 6545, + .has_ocp_filter = true, +}; + +static int vt7505_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_VIN); + case PMBUS_VIRT_READ_IOUT_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_IOUT); + case PMBUS_VIRT_READ_PIN_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_PIN); + case PMBUS_VIRT_READ_TEMP_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_TEMP); + case PMBUS_VIRT_READ_VOUT_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_VOUT); + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + return 0; + default: + return -ENODATA; + } +} + +static int vt7505_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + switch (reg) { + /* + * A single reset command clears all peak values. CLEAR_PEAKS is a + * send-byte command; the device NAKs a word or byte-data write to it. + */ + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + return pmbus_write_byte(client, page, + VT7505_MFR_CLEAR_PEAKS); + default: + return -ENODATA; + } +} + +/* + * None of these controllers implement the standard PMBus WRITE_PROTECT + * (0x10) register, so tell the core not to access it. + */ +static struct pmbus_platform_data vt7505_pdata = { + .flags = PMBUS_NO_WRITE_PROTECT, +}; + +static int vt7505_set_ocp_filter(struct i2c_client *client) +{ + u32 ocp_us; + u8 field; + int ret; + u16 word; + + if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us", + &ocp_us)) + return 0; + + switch (ocp_us) { + case 0: + field = 0; + break; + case 1: + field = 1; + break; + case 2: + field = 2; + break; + case 10: + field = 3; + break; + default: + return dev_err_probe(&client->dev, -EINVAL, + "invalid adi,ocp-severe-filter-us value %u\n", + ocp_us); + } + + ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "failed to read MFR_CONFIG\n"); + + word = ret & ~VT7505_CFG_OCP_S_FILT_MASK; + word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field); + + ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "failed to write MFR_CONFIG\n"); + + return 0; +} + +static void vt7505_set_m(int *m, u32 rload) +{ + u64 val = (u64)*m * rload; + + /* rload is range-checked in probe, so the result fits in int. */ + *m = DIV_ROUND_CLOSEST_ULL(val, 1000); +} + +static int vt7505_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct vt7505_chip_data *chip; + struct pmbus_driver_info *info; + u32 rload; + int ret; + + chip = i2c_get_match_data(client); + if (!chip) + return -ENODEV; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + dev->platform_data = &vt7505_pdata; + + /* + * The m coefficient used in the direct-format current and power + * calculations depends on RLOAD, the external current-report resistor + * connected between the ILOAD pin and ground. Use the default value if + * none is specified. + */ + if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload)) + rload = VT7505_RLOAD_DEFAULT; + + if (!rload || rload > VT7505_RLOAD_MAX) + return dev_err_probe(dev, -EINVAL, + "adi,rload-ohms must be 1-%u\n", + VT7505_RLOAD_MAX); + + info->pages = 1; + info->read_word_data = vt7505_read_word_data; + info->write_word_data = vt7505_write_word_data; + + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_IN] = direct; + info->format[PSC_CURRENT_OUT] = direct; + info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; + + /* + * Direct data format coefficients from the device datasheet ("PMBus + * Equation Parameters"). The current and power m coefficients scale + * with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving + * m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The + * temperature coefficients are chip specific (see the chip data). + */ + info->m[PSC_VOLTAGE_IN] = 7578; + info->R[PSC_VOLTAGE_IN] = -2; + info->m[PSC_VOLTAGE_OUT] = 7578; + info->R[PSC_VOLTAGE_OUT] = -2; + info->m[PSC_CURRENT_IN] = 3824; + info->b[PSC_CURRENT_IN] = -4300; + info->R[PSC_CURRENT_IN] = -3; + info->m[PSC_CURRENT_OUT] = 3824; + info->b[PSC_CURRENT_OUT] = -4300; + info->R[PSC_CURRENT_OUT] = -3; + info->m[PSC_POWER] = 895; + info->b[PSC_POWER] = -9100; + info->R[PSC_POWER] = -2; + + vt7505_set_m(&info->m[PSC_CURRENT_IN], rload); + vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload); + vt7505_set_m(&info->m[PSC_POWER], rload); + + info->m[PSC_TEMPERATURE] = chip->temp_m; + info->b[PSC_TEMPERATURE] = chip->temp_b; + info->R[PSC_TEMPERATURE] = -2; + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN; + + /* + * The severe OCP deglitch filter is programmable on the MAX16550 and + * the VT7505, but fixed on the MAX16545. + */ + if (chip->has_ocp_filter) { + ret = vt7505_set_ocp_filter(client); + if (ret) + return ret; + } + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id vt7505_id[] = { + { .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data }, + { .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data }, + { .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vt7505_id); + +static const struct of_device_id vt7505_of_match[] = { + { .compatible = "adi,max16545", .data = &max16545_data }, + { .compatible = "adi,max16550", .data = &max16550_data }, + { .compatible = "adi,vt7505", .data = &vt7505_data }, + { } +}; +MODULE_DEVICE_TABLE(of, vt7505_of_match); + +static struct i2c_driver vt7505_driver = { + .driver = { + .name = "vt7505", + .of_match_table = vt7505_of_match, + }, + .probe = vt7505_probe, + .id_table = vt7505_id, +}; +module_i2c_driver(vt7505_driver); + +MODULE_AUTHOR("Georgi Vlaev "); +MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); From 1dcda2881714079b97563b23c98bd4f8755c51d0 Mon Sep 17 00:00:00 2001 From: Sean Lyon Date: Mon, 27 Jul 2026 23:11:24 -0400 Subject: [PATCH 068/110] hwmon: (nzxt-kraken3) Add support for NZXT Kraken 2024 Elite The Kraken 2024 Elite (USB ID 1e71:3012) is protocol-compatible with the existing Kraken 2023 devices and works with the current Z53 code paths. Add its USB product ID and map it to the KRAKEN2023 kind. Tested on hardware: liquid temperature and pump/fan speeds read correctly (cross-checked against liquidctl), and PWM control of both the pump and fan channels works as expected. Signed-off-by: Sean Lyon Link: https://lore.kernel.org/r/20260728031127.26553-1-sean@seanlyon.dev Signed-off-by: Guenter Roeck --- Documentation/hwmon/nzxt-kraken3.rst | 1 + drivers/hwmon/nzxt-kraken3.c | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/nzxt-kraken3.rst b/Documentation/hwmon/nzxt-kraken3.rst index 57fe99d23301..ee2d5d535fe7 100644 --- a/Documentation/hwmon/nzxt-kraken3.rst +++ b/Documentation/hwmon/nzxt-kraken3.rst @@ -13,6 +13,7 @@ Supported devices: * NZXT Kraken Z73 * NZXT Kraken 2023 * NZXT Kraken 2023 Elite +* NZXT Kraken 2024 Elite Author: Jonas Malaco, Aleksa Savic diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c index 05525406c5fb..a185e8b27532 100644 --- a/drivers/hwmon/nzxt-kraken3.c +++ b/drivers/hwmon/nzxt-kraken3.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. + * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73, 2023/2023 Elite, and + * 2024 Elite all in one coolers. * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). * 2023 models use the Z53 code paths. * @@ -25,6 +26,7 @@ #define USB_PRODUCT_ID_Z53 0x3008 #define USB_PRODUCT_ID_KRAKEN2023 0x300E #define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C +#define USB_PRODUCT_ID_KRAKEN2024_ELITE 0x3012 enum kinds { X53, Z53, KRAKEN2023 } __packed; enum pwm_enable { off, manual, curve } __packed; @@ -927,6 +929,10 @@ static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id priv->kind = KRAKEN2023; device_name = "kraken2023elite"; break; + case USB_PRODUCT_ID_KRAKEN2024_ELITE: + priv->kind = KRAKEN2023; + device_name = "kraken2024elite"; + break; default: ret = -ENODEV; goto fail_and_close; @@ -994,6 +1000,7 @@ static const struct hid_device_id kraken3_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) }, { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2024_ELITE) }, { } }; From 6d479f0f1b4bc34d7a747ea2b283fa229c1b7520 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 28 Jul 2026 15:44:27 -0700 Subject: [PATCH 069/110] hwmon: (pmbus) Let PMBus drivers report the supported PMBus revision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some PMBus chips do not support the PMBUS_REVISION command. Knowing the PMBUs revision supported by a chip is relevant for PMBUs core functionality, so add support for letting chip drivers report the PMBUs revision. Use the new capability to report the PMBus revision supported by MAX20830. While at it, add definitions for PMBUs revisons 1.3.1 and 1.4. Cc: Nuno Sá Cc: Alexis Czezar Torreno Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max20830.c | 2 ++ drivers/hwmon/pmbus/pmbus.h | 20 +++++++++++++---- drivers/hwmon/pmbus/pmbus_core.c | 38 ++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c index e3470118fd36..d43ee6438b26 100644 --- a/drivers/hwmon/pmbus/max20830.c +++ b/drivers/hwmon/pmbus/max20830.c @@ -23,6 +23,8 @@ static struct pmbus_driver_info max20830_info = { PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .have_pmbus_revision = true, + .pmbus_revision = PMBUS_REV_13, }; static int max20830_probe(struct i2c_client *client) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 23e3eda58870..1af247de9075 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -420,10 +420,12 @@ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; /* PMBus revision identifiers */ -#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ -#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ -#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ -#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ +#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ +#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ +#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ +#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ +#define PMBUS_REV_131 0x44 /* PMBus revision 1.3.1 */ +#define PMBUS_REV_14 0x55 /* PMBus revision 1.4 */ /* Operation type flags for pmbus_update_ts */ #define PMBUS_OP_WRITE BIT(0) @@ -488,6 +490,16 @@ struct pmbus_driver_info { int access_delay; /* in microseconds */ int write_delay; /* in microseconds */ int page_change_delay; /* in microseconds */ + + /* + * Some chips do not support the PMBUS_REVISION command. + * Drivers for such chips can report the supported PMBus revision here. + * + * Drivers must set have_pmbus_revision to true and provide the + * supported PMBus version in pmbus_revision. + */ + bool have_pmbus_revision; /* true if pmbus_revision is valid */ + u8 pmbus_revision; /* PMBus revision */ }; /* Regulator ops */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 20d04dd713cc..884d6e6148d5 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -96,7 +96,8 @@ struct pmbus_data { u32 flags; /* from platform data */ - u8 revision; /* The PMBus revision the device is compliant with */ + bool have_pmbus_revision; + u8 revision; /* The PMBus revision the device is compliant with */ int exponent[PMBUS_PAGES]; /* linear mode: exponent for output voltages */ @@ -2847,9 +2848,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) pmbus_init_wp(client, data); - ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); - if (ret >= 0) - data->revision = ret; + if (info->have_pmbus_revision) { + data->have_pmbus_revision = true; + data->revision = info->pmbus_revision; + } else { + ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); + if (ret >= 0) { + data->have_pmbus_revision = true; + data->revision = ret; + } + } if (data->info->pages) pmbus_clear_faults(client); @@ -3539,6 +3547,17 @@ static int pmbus_debugfs_get(void *data, u64 *val) DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, "0x%02llx\n"); +static int pmbus_debugfs_get_revision(void *data, u64 *val) +{ + struct pmbus_data *pdata = data; + + *val = pdata->revision; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_revision_ops, pmbus_debugfs_get_revision, NULL, + "0x%02llx\n"); + static int pmbus_debugfs_get_status(void *data, u64 *val) { struct pmbus_debugfs_entry *entry = data; @@ -3694,14 +3713,9 @@ static void pmbus_init_debugfs(struct i2c_client *client, &entries[idx++], &pmbus_debugfs_ops); } - if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) { - entries[idx].client = client; - entries[idx].page = 0; - entries[idx].reg = PMBUS_REVISION; - debugfs_create_file("pmbus_revision", 0444, debugfs, - &entries[idx++], - &pmbus_debugfs_ops); - } + if (data->have_pmbus_revision) + debugfs_create_file("pmbus_revision", 0444, debugfs, data, + &pmbus_debugfs_revision_ops); for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) { const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i]; From b8efcfca4370d1392e23c0ca84111bff26fe3926 Mon Sep 17 00:00:00 2001 From: Alexis Czezar Torreno Date: Thu, 30 Jul 2026 09:03:33 +0800 Subject: [PATCH 070/110] dt-bindings: hwmon: (pmbus/max20830): add VOUT feedback resistor properties and complete examples Add adi,vout-rfb1-ohms and adi,vout-rfb2-ohms properties to support external voltage divider configuration for VOUT sensing. When the desired output voltage is higher than VREF, a resistor divider (RFB1 and RFB2) is required to reach the intended value. The properties use a dependency constraint to ensure both resistors are specified together, or neither. This prevents misconfiguration where only one resistor value is provided. This patch also adds missing entries in the examples. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20260730-dev-max20830c-v5-1-a7553f84ee74@analog.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/adi,max20830.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml index 1625dd59417f..01b7275f5b34 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml @@ -44,15 +44,32 @@ properties: GPIO connected to the power-good status output pin. maxItems: 1 + adi,vout-rfb1-ohms: + description: + Top feedback resistor (RFB1) value in ohms for VOUT sensing divider. + When the desired output voltage is higher than VREF, a resistor divider + is required. VOUT = VREF × (1 + RFB1/RFB2) + + adi,vout-rfb2-ohms: + description: + Bottom feedback resistor (RFB2) value in ohms for VOUT sensing divider. + Datasheet recommends that RFB2 does not exceed 2.5kΩ. + required: - compatible - reg - vddh-supply +dependencies: + adi,vout-rfb1-ohms: ['adi,vout-rfb2-ohms'] + adi,vout-rfb2-ohms: ['adi,vout-rfb1-ohms'] + unevaluatedProperties: false examples: - | + #include + i2c { #address-cells = <1>; #size-cells = <0>; @@ -61,6 +78,11 @@ examples: compatible = "adi,max20830"; reg = <0x30>; vddh-supply = <&vddh>; + avdd-supply = <&avdd>; + ldoin-supply = <&ldoin>; + pwr-good-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>; + adi,vout-rfb1-ohms = <10000>; + adi,vout-rfb2-ohms = <2000>; }; }; ... From fdd78852b07bfa6f7032f476af4a3397b5f2212e Mon Sep 17 00:00:00 2001 From: Alexis Czezar Torreno Date: Thu, 30 Jul 2026 09:03:34 +0800 Subject: [PATCH 071/110] hwmon: (pmbus/max20830): add VOUT feedback resistor scaling support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement support for external voltage divider scaling using the adi,vout-rfb1-ohms and adi,vout-rfb2-ohms device tree properties. When the desired output voltage exceeds VREF, a resistor divider (RFB1 and RFB2) is used to scale down the feedback voltage. The driver reads these resistor values from device tree and applies the scaling formula: VOUT_actual = VOUT_measured × (1 + RFB1/RFB2) The properties are optional. If not specified, the driver assumes no voltage divider is present and reports the raw VOUT reading. Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20260730-dev-max20830c-v5-2-a7553f84ee74@analog.com [groeck: Fixed minor conflicts] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max20830.c | 65 +++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c index d43ee6438b26..5129fc2dbaea 100644 --- a/drivers/hwmon/pmbus/max20830.c +++ b/drivers/hwmon/pmbus/max20830.c @@ -7,12 +7,63 @@ #include #include +#include #include #include #include "pmbus.h" #define MAX20830_IC_DEVICE_ID_LENGTH 9 +struct max20830_data { + struct pmbus_driver_info info; + u32 vout_rfb1; + u32 vout_rfb2; +}; + +/* + * MAX20830 only supports READ_VOUT for VOUT monitoring. + * + * Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not + * supported by this driver and return -ENODATA. This means sysfs attributes + * like in1_max, in1_crit, etc. will not be available. Only in1_input (the + * scaled output voltage) is supported. + * + * MAX20830 uses an external resistor divider for voltage sensing: + * - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin + * - READ_VOUT reports the feedback voltage, which needs to be scaled for actual + * output voltage + * + * Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2) + * + * If regulator support is added in the future, some adjustments are needed to + * ensure correct feedback voltages are set. + */ +static int max20830_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max20830_data *data = container_of(info, struct max20830_data, info); + int ret; + u64 temp; + + switch (reg) { + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + /* Apply voltage divider scaling if resistors are non-zero */ + if (data->vout_rfb1 && data->vout_rfb2) { + temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2; + temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2); + ret = clamp_val(temp, 0, 0xFFFF); + } + return ret; + default: + return -ENODATA; + } +} + static struct pmbus_driver_info max20830_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = linear, @@ -23,6 +74,7 @@ static struct pmbus_driver_info max20830_info = { PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max20830_read_word_data, .have_pmbus_revision = true, .pmbus_revision = PMBUS_REV_13, }; @@ -30,8 +82,19 @@ static struct pmbus_driver_info max20830_info = { static int max20830_probe(struct i2c_client *client) { u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {}; + struct max20830_data *data; int ret; + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->info = max20830_info; + + /* Read optional voltage divider resistor values */ + device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1); + device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) && !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) return -ENODEV; @@ -79,7 +142,7 @@ static int max20830_probe(struct i2c_client *client) return dev_err_probe(&client->dev, -ENODEV, "Unsupported device: '%s'\n", buf); - return pmbus_do_probe(client, &max20830_info); + return pmbus_do_probe(client, &data->info); } static const struct i2c_device_id max20830_id[] = { From b60e4b3ae17a7e906a882da38bc2b91e9a9835b7 Mon Sep 17 00:00:00 2001 From: Alexis Czezar Torreno Date: Thu, 30 Jul 2026 09:03:35 +0800 Subject: [PATCH 072/110] dt-bindings: hwmon: (pmbus/max20830): add max20830c and max20840c support Add compatible strings for variants of MAX20830 which are MAX20830C and MAX20840C. These devices have the same register functionality with MAX20830 but with a longer IC_DEVICE_ID. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20260730-dev-max20830c-v5-3-a7553f84ee74@analog.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/pmbus/adi,max20830.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml index 01b7275f5b34..cc50a4ee44a2 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml @@ -22,7 +22,13 @@ allOf: properties: compatible: - const: adi,max20830 + oneOf: + - const: adi,max20830 + - items: + - enum: + - adi,max20830c + - adi,max20840c + - const: adi,max20830 reg: maxItems: 1 From 4cdcd4789fb6d43939680ff5ac0eb8af49ca1d84 Mon Sep 17 00:00:00 2001 From: Alexis Czezar Torreno Date: Thu, 30 Jul 2026 09:03:36 +0800 Subject: [PATCH 073/110] hwmon: (pmbus/max20830): add support for max20830c and max20840c Add support for MAX20830C and MAX20840 step-down DC-DC switching regulator with PMBus interface. MAX20830C is a different packaging for MAX20830, and MAX20840C supports 40A regulation compared to MAX20830 that is only 30A. Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20260730-dev-max20830c-v5-4-a7553f84ee74@analog.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/max20830.rst | 27 +++++++++++++++++++----- drivers/hwmon/pmbus/Kconfig | 4 ++-- drivers/hwmon/pmbus/max20830.c | 36 ++++++++++++++++++++------------ 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Documentation/hwmon/max20830.rst b/Documentation/hwmon/max20830.rst index 936e409dcc5c..b850f3b6e40d 100644 --- a/Documentation/hwmon/max20830.rst +++ b/Documentation/hwmon/max20830.rst @@ -13,6 +13,22 @@ Supported chips: Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max20830.pdf + * Analog Devices MAX20830C + + Prefix: 'max20830c' + + Addresses scanned: - + + Datasheet: + + * Analog Devices MAX20840C + + Prefix: 'max20840c' + + Addresses scanned: - + + Datasheet: + Author: - Alexis Czezar Torreno @@ -21,12 +37,13 @@ Author: Description ----------- -This driver supports hardware monitoring for Analog Devices MAX20830 -Step-Down Switching Regulator with PMBus Interface. +This driver supports hardware monitoring for Analog Devices MAX20830, MAX20830C +and MAX20840C. These are Step-Down Switching Regulator with PMBus Interface. -The MAX20830 is a 2.7V to 16V, 30A fully integrated step-down DC-DC switching -regulator. Through the PMBus interface, the device can monitor input/output -voltages, output current and temperature. +MAX20830, and MAX20830C are 2.7V to 16V, 30A fully integrated step-down DC-DC +switching regulators. MAX20840C is similar but can reach 40A. Through the PMBus +interface, these devices can monitor input/output voltages, output current and +temperature. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index becc311dacc6..2758f9695577 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -394,10 +394,10 @@ config SENSORS_MAX20751 be called max20751. config SENSORS_MAX20830 - tristate "Analog Devices MAX20830" + tristate "Analog Devices MAX20830 and compatibles" help If you say yes here you get hardware monitoring support for Analog - Devices MAX20830. + Devices MAX20830, MAX20830C, and MAX20840C. This driver can also be built as a module. If so, the module will be called max20830. diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c index 5129fc2dbaea..689fc6f0fe01 100644 --- a/drivers/hwmon/pmbus/max20830.c +++ b/drivers/hwmon/pmbus/max20830.c @@ -20,6 +20,12 @@ struct max20830_data { u32 vout_rfb2; }; +static const char * const supported_chip_ids[] = { + "MAX20830", + "MAX20830C", + "MAX20840C", +}; + /* * MAX20830 only supports READ_VOUT for VOUT monitoring. * @@ -83,7 +89,7 @@ static int max20830_probe(struct i2c_client *client) { u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {}; struct max20830_data *data; - int ret; + int i, ret; data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -105,13 +111,12 @@ static int max20830_probe(struct i2c_client *client) * which do not support SMBus block reads. */ if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { - /* Reads 9 Data bytes from MAX20830 */ ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); if (ret < 0) return dev_err_probe(&client->dev, ret, "Failed to read IC_DEVICE_ID\n"); } else { - /* Reads 1 length byte + 9 Data bytes from MAX20830 */ + /* Reads 1 length byte + data bytes */ ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, MAX20830_IC_DEVICE_ID_LENGTH + 1, buf); @@ -127,20 +132,25 @@ static int max20830_probe(struct i2c_client *client) ret = ret - 1; } - /* - * MAX20830 IC_DEVICE_ID sends string data "MAX20830\0". - * Return value should at least be 9 bytes of data. - */ + /* Verify we read the expected number of bytes */ if (ret < MAX20830_IC_DEVICE_ID_LENGTH) return dev_err_probe(&client->dev, -ENODEV, - "IC_DEVICE_ID too short: expected at least 9 bytes, got %d\n", - ret); + "IC_DEVICE_ID too short: expected %d bytes, got %d\n", + MAX20830_IC_DEVICE_ID_LENGTH, ret); - /* 9 bytes of data, buf[0]-buf[7] = "MAX20830", buf[8] = '\0' */ - buf[MAX20830_IC_DEVICE_ID_LENGTH - 1] = '\0'; - if (strncmp(buf, "MAX20830", MAX20830_IC_DEVICE_ID_LENGTH - 1)) + /* Null-terminate the string */ + buf[ret] = '\0'; + + /* Verify the device ID matches what we expect */ + for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) { + if (!strcmp(buf, supported_chip_ids[i])) + break; + } + + /* No match found - unsupported device */ + if (i == ARRAY_SIZE(supported_chip_ids)) return dev_err_probe(&client->dev, -ENODEV, - "Unsupported device: '%s'\n", buf); + "Unsupported device: '%*pE'\n", ret, buf); return pmbus_do_probe(client, &data->info); } From a39b3336943e7f4180812e10c035b939153d9079 Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Wed, 29 Jul 2026 23:54:27 +0200 Subject: [PATCH 074/110] hwmon: remove tautological dependency expressions from Kconfig Commit 554b3529fe01 ("thermal/drivers/core: Remove the module Kconfig's option") [1] obviated some of the "Optional dependencies" (as described in "Documentation/kbuild/kconfig-language.rst") in "drivers/hwmon/Kconfig". Namely, said commit restricted the valid/possible subset of the full Cartesian product "CONFIG_THERMAL x CONFIG_THERMAL_OF" to { (n, n), (y, n), (y, y) } through "drivers/thermal/Kconfig". Numerically (using tristate values), this set can be expressed as: { (0, 0), (2, 0), (2, 2) } Over this set, the "drivers/hwmon/Kconfig" tristate expression THERMAL || !THERMAL_OF or equivalently, max(THERMAL, 2-THERMAL_OF) invariably evaluates to "y" (2). Similarly, with THERMAL having been restricted to { n, y } or numerically, { 0, 2 } the "drivers/hwmon/Kconfig" tristate expression THERMAL || THERMAL=n or equivalently, max(THERMAL, THERMAL=0) invariably evaluates to "y" (2). (Note that "THERMAL=0" returns either "y" (2) or "n" (0).) Commit f37353320ee9 ("hwmon/drivers/core: Simplify complex dependency") [2], which is the final commit of the series that [1] is the beginning of, cleaned up the hwmon C source code in fact; however, it missed the now-collapsed dependencies in Kconfig. At the time of [2], six of today's such dependencies were present in Kconfig. Since then, two more have been added (such that they persist, anyway): one by commit 4f8d374b770a ("hwmon: (max6650) add thermal cooling device capability"), which was independent of the above-noted series, and another by commit 9855caf5d4eb ("hwmon: add driver for the hwmon parts of qnap-mcu devices"), which was a descendant of the series. Remove these always-"y" dependencies. Cc: Andrew Jeffery Cc: Cristian Marussi Cc: Guenter Roeck Cc: Heiko Stuebner Cc: Joel Stanley Cc: Sudeep Holla Cc: arm-scmi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-aspeed@lists.ozlabs.org Cc: linux-hwmon@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Laszlo Ersek Link: https://lore.kernel.org/r/20260729215436.2103380-1-laszlo.ersek@arm.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 6b71e3304b4f..e51269fd9de3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -414,7 +414,6 @@ config SENSORS_ARCTIC_FAN_CONTROLLER config SENSORS_ARM_SCMI tristate "ARM SCMI Sensors" depends on ARM_SCMI_PROTOCOL - depends on THERMAL || !THERMAL_OF help This driver provides support for temperature, voltage, current and power sensors available on SCMI based platforms. The actual @@ -426,7 +425,6 @@ config SENSORS_ARM_SCMI config SENSORS_ARM_SCPI tristate "ARM SCPI Sensors" depends on ARM_SCPI_PROTOCOL - depends on THERMAL || !THERMAL_OF help This driver provides support for temperature, voltage, current and power sensors available on ARM Ltd's SCP based platforms. The @@ -446,7 +444,6 @@ config SENSORS_ASB100 config SENSORS_ASPEED tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver" depends on ARCH_ASPEED || COMPILE_TEST - depends on THERMAL || THERMAL=n select REGMAP help This driver provides support for ASPEED AST2400/AST2500 PWM @@ -799,7 +796,6 @@ config SENSORS_G762 config SENSORS_GPIO_FAN tristate "GPIO fan" depends on GPIOLIB || COMPILE_TEST - depends on THERMAL || THERMAL=n help If you say yes here you get support for fans connected to GPIO lines. @@ -1381,7 +1377,6 @@ config SENSORS_MAX6639 config SENSORS_MAX6650 tristate "Maxim MAX6650 sensor chip" depends on I2C - depends on THERMAL || THERMAL=n help If you say yes here you get support for the MAX6650 / MAX6651 sensor chips. @@ -1756,7 +1751,6 @@ config SENSORS_PC87427 config SENSORS_NTC_THERMISTOR tristate "NTC thermistor support" depends on IIO - depends on THERMAL || !THERMAL_OF help This driver supports NTC thermistors sensor reading and its interpretation. The driver can also monitor the temperature and @@ -1957,7 +1951,6 @@ config SENSORS_PT5161L config SENSORS_PWM_FAN tristate "PWM fan" depends on PWM || COMPILE_TEST - depends on THERMAL || THERMAL=n help If you say yes here you get support for fans connected to PWM lines. The driver uses the generic PWM interface, thus it will work on a @@ -1969,7 +1962,6 @@ config SENSORS_PWM_FAN config SENSORS_QNAP_MCU_HWMON tristate "QNAP MCU hardware monitoring" depends on MFD_QNAP_MCU - depends on THERMAL || THERMAL=n help Say yes here to enable support for fan and temperature sensor connected to a QNAP MCU, as found in a number of QNAP network From fb564622ec5e0f9e4eb780aa2ff15f445bfbb417 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Thu, 30 Jul 2026 12:14:09 +0300 Subject: [PATCH 075/110] hwmon: (lm90) Switch channel parsing to fwnode APIs Replace OF property handling with fwnode in the probe function to read the channels properties, improving the driver compatibility since this method is not limited to Device Tree only. Add also the needed headers for explicit include and clean up related function naming. Signed-off-by: Flaviu Nistor Link: https://lore.kernel.org/r/20260730091409.10544-1-flaviu.nistor@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lm90.c | 54 +++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index ae3b0a33d676..1c603272538a 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -101,6 +101,7 @@ #include #include #include +#include #include #include #include @@ -108,7 +109,7 @@ #include #include #include -#include +#include #include #include #include @@ -295,7 +296,7 @@ static const struct i2c_device_id lm90_id[] = { }; MODULE_DEVICE_TABLE(i2c, lm90_id); -static const struct of_device_id __maybe_unused lm90_of_match[] = { +static const struct of_device_id lm90_of_match[] = { { .compatible = "adi,adm1032", .data = (void *)adm1032 @@ -2602,7 +2603,6 @@ static void lm90_stop_work(void *_data) static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) { - struct device_node *np = client->dev.of_node; int config, convrate; if (data->flags & LM90_HAVE_CONVRATE) { @@ -2626,7 +2626,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) /* Check Temperature Range Select */ if (data->flags & LM90_HAVE_EXTENDED_TEMP) { - if (of_property_read_bool(np, "ti,extended-range-enable")) + if (device_property_read_bool(&client->dev, "ti,extended-range-enable")) config |= 0x04; if (!(config & 0x04)) data->flags &= ~LM90_HAVE_EXTENDED_TEMP; @@ -2692,36 +2692,41 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id) return IRQ_NONE; } -static int lm90_probe_channel_from_dt(struct i2c_client *client, - struct device_node *child, - struct lm90_data *data) +static int lm90_probe_channel(struct i2c_client *client, + struct fwnode_handle *child, + struct lm90_data *data) { u32 id; s32 val; int err; struct device *dev = &client->dev; - err = of_property_read_u32(child, "reg", &id); + err = fwnode_property_read_u32(child, "reg", &id); if (err) { - dev_err(dev, "missing reg property of %pOFn\n", child); + dev_err(dev, "missing reg property of %pfw\n", child); return err; } if (id >= MAX_CHANNELS) { - dev_err(dev, "invalid reg property value %d in %pOFn\n", id, child); + dev_err(dev, "invalid reg property value %d in %pfw\n", id, child); return -EINVAL; } - err = of_property_read_string(child, "label", &data->channel_label[id]); + err = fwnode_property_read_string(child, "label", &data->channel_label[id]); if (err == -ENODATA || err == -EILSEQ) { - dev_err(dev, "invalid label property in %pOFn\n", child); + dev_err(dev, "invalid label property in %pfw\n", child); return err; } if (data->channel_label[id]) data->channel_config[id] |= HWMON_T_LABEL; - err = of_property_read_s32(child, "temperature-offset-millicelsius", &val); + /* + * fwnode_property_read_u32() has no signed equivalent. + * temperature-offset-millicelsius is signed, so read and reinterpret it as s32 to + * preserve negative offsets values (same behavior as the old of_property_read_s32()). + */ + err = fwnode_property_read_u32(child, "temperature-offset-millicelsius", (u32 *)&val); if (!err) { if (id == 0) { dev_err(dev, "temperature-offset-millicelsius can't be set for internal channel\n"); @@ -2739,18 +2744,17 @@ static int lm90_probe_channel_from_dt(struct i2c_client *client, return 0; } -static int lm90_parse_dt_channel_info(struct i2c_client *client, - struct lm90_data *data) +static int lm90_parse_channel_info(struct i2c_client *client, + struct lm90_data *data) { int err; struct device *dev = &client->dev; - const struct device_node *np = dev->of_node; - for_each_child_of_node_scoped(np, child) { - if (strcmp(child->name, "channel")) + device_for_each_child_node_scoped(dev, child) { + if (!fwnode_name_eq(child, "channel")) continue; - err = lm90_probe_channel_from_dt(client, child, data); + err = lm90_probe_channel(client, child, data); if (err) return err; } @@ -2887,12 +2891,10 @@ static int lm90_probe(struct i2c_client *client) /* Set maximum conversion rate */ data->max_convrate = lm90_params[data->kind].max_convrate; - /* Parse device-tree channel information */ - if (client->dev.of_node) { - err = lm90_parse_dt_channel_info(client, data); - if (err) - return err; - } + /* Parse channel information */ + err = lm90_parse_channel_info(client, data); + if (err) + return err; /* Initialize the LM90 chip */ err = lm90_init_client(client, data); @@ -2983,7 +2985,7 @@ static struct i2c_driver lm90_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "lm90", - .of_match_table = of_match_ptr(lm90_of_match), + .of_match_table = lm90_of_match, .pm = pm_sleep_ptr(&lm90_pm_ops), }, .probe = lm90_probe, From 53a945a89df8fea7a4946e4a20b7bc8c57d2b787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 30 Jul 2026 17:12:26 +0200 Subject: [PATCH 076/110] hwmon: (cros_ec) Avoid threshold temperature conversion overflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the EC returns non-sensical values the temperature conversions might overflow on 32-bit systems. As these values wouldn't make sense, clamp them to 255 degrees celsius. The machine would die before reaching that limit anyways. Suggested-by: Guenter Roeck Link: https://lore.kernel.org/lkml/0fbf7f69-bb90-4209-b9d5-258759711496@roeck-us.net/ Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260730-cros_ec-hwmon-overflow-v3-1-c7198ba034ad@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/cros_ec_hwmon.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index 1337b646e022..27af13f0941c 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -236,8 +236,13 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ret = cros_ec_hwmon_read_temp_threshold(priv->cros_ec, channel, cros_ec_hwmon_attr_to_thres(attr), &threshold); - if (ret == 0) - *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); + if (ret == 0) { + /* Limit to sensible, non-overflowing values. */ + if (threshold > 255 + 273) + *val = 255000; + else + *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); + } } } From bf69cb41979b1be20ddfaf37279fafc6f15e3e19 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 30 Jul 2026 17:43:59 +0200 Subject: [PATCH 077/110] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() Factor of pmbus_fault_handler() into an exported helper so drivers can notify sequence from a polling work item on boards for which the chip's SMBALERT# pin is not wired to the CPU. The interrupt handler becomes a thin wrapper. Like the SMBALERT# path, the helper notifies and then clears the latched faults unconditionally, so a polling caller inherits exactly the interrupt semantics. Signed-off-by: Vincent Jardin Link: https://lore.kernel.org/r/20260730-mpq8646_v0-v7-1-e7c7ad768d5d@free.fr Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 1 + drivers/hwmon/pmbus/pmbus_core.c | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 1af247de9075..5fe2c415eada 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -572,6 +572,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, u8 mask, u8 value); void pmbus_clear_faults(struct i2c_client *client); +void pmbus_check_and_notify_faults(struct i2c_client *client); bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 884d6e6148d5..6991c7f8d125 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -3461,10 +3461,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, return ret; } -static irqreturn_t pmbus_fault_handler(int irq, void *pdata) +void pmbus_check_and_notify_faults(struct i2c_client *client) { - struct pmbus_data *data = pdata; - struct i2c_client *client = to_i2c_client(data->dev); + struct pmbus_data *data = i2c_get_clientdata(client); int i, status, event; guard(pmbus_lock)(client); @@ -3477,6 +3476,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) } pmbus_clear_faults(client); +} +EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS"); + +static irqreturn_t pmbus_fault_handler(int irq, void *pdata) +{ + struct pmbus_data *data = pdata; + struct i2c_client *client = to_i2c_client(data->dev); + + pmbus_check_and_notify_faults(client); return IRQ_HANDLED; } From e7ba3115134b670992562ffaab86853cad452274 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 30 Jul 2026 17:44:00 +0200 Subject: [PATCH 078/110] dt-bindings: hwmon: pmbus: add MPS MPQ8646 The MPS MPQ8646 PMBus step-down converter uses the same mps,vout-fb-divider-ratio-permille property with the same constraints (maximum 2047, default 706) as the mps,mpq8785, so add its compatible to the existing schema. Signed-off-by: Vincent Jardin Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260730-mpq8646_v0-v7-2-e7c7ad768d5d@free.fr Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml index 90970a0433e9..d7a32fb3b7af 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml @@ -18,6 +18,7 @@ properties: - mps,mpm3695 - mps,mpm3695-25 - mps,mpm82504 + - mps,mpq8646 - mps,mpq8785 reg: @@ -52,7 +53,9 @@ allOf: - if: properties: compatible: - const: mps,mpq8785 + enum: + - mps,mpq8646 + - mps,mpq8785 then: properties: mps,vout-fb-divider-ratio-permille: From 24be24bebc1bc6d529068a2e725984a498b2aa98 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 30 Jul 2026 17:44:01 +0200 Subject: [PATCH 079/110] hwmon: (pmbus) Add MPQ8646 driver Add a new driver for the MPS MPQ8646 that is a PMBus device. Beyond basic PMBus telemetry, the driver adds: - alarm acknowledge via inX_reset_history. - STATUS_WORD MPS-extended bit decode and the NVM-backed PROTECTION_LAST post-mortem, exposed as a read-only debugfs decoder. - In-driver alarm-poll fallback work item (thanks lm90) for boards without SMBALERT Signed-off-by: Vincent Jardin Link: https://lore.kernel.org/r/20260730-mpq8646_v0-v7-3-e7c7ad768d5d@free.fr Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mpq8646.rst | 261 ++++++++++++ MAINTAINERS | 7 + drivers/hwmon/pmbus/Kconfig | 11 + drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/mpq8646.c | 688 ++++++++++++++++++++++++++++++++ 6 files changed, 969 insertions(+) create mode 100644 Documentation/hwmon/mpq8646.rst create mode 100644 drivers/hwmon/pmbus/mpq8646.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index e924e511bc60..d979e6d6e9f2 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -203,6 +203,7 @@ Hardware Monitoring Kernel Drivers mp9941 mp9945 mpq82d00 + mpq8646 mpq8785 nct6683 nct6775 diff --git a/Documentation/hwmon/mpq8646.rst b/Documentation/hwmon/mpq8646.rst new file mode 100644 index 000000000000..d3ede656ea15 --- /dev/null +++ b/Documentation/hwmon/mpq8646.rst @@ -0,0 +1,261 @@ +.. SPDX-License-Identifier: GPL-2.0-only +.. Copyright (c) 2026 Free Mobile - Vincent Jardin + +Kernel driver mpq8646 +===================== + +Supported chips: + + * MPS MPQ8646 + + Prefix: 'mpq8646' + +Author: + - Vincent Jardin + +Chip-identity +------------- + +Support the boards that are designed with the MPS MPQ8646 probed thanks +to``MFR_MODEL`` register. + +This driver targets the MPQ8646 silicon specifically. + +Description +----------- + +The MPQ8646 is a fully integrated, PMBus-compatible, high-frequency, +synchronous buck converter. It offers a compact solution that +achieves up high Amps output current per phase, with excellent load +and line regulation over a wide input supply range. The chip +operates at high efficiency over a wide output current load range. + +The PMBus interface provides converter configurations and key +parameters monitoring. + +The device adopts MPS's proprietary multi-phase digital +constant-on-time (MCOT) control, which provides fast transient +response and eases loop stabilization. The MCOT scheme also allows +multiple devices or channels to be connected in parallel with +excellent current sharing and phase interleaving for high-current +applications. + +Fully integrated protection features include over-current +protection (OCP), over-voltage protection (OVP), under-voltage +protection (UVP), and over-temperature protection (OTP). + +This device is compliant with: + +- PMBus rev 1.3 interface. + +The driver exports the following attributes via the 'sysfs' files +for input voltage: + +- in1_input +- in1_label +- in1_max +- in1_max_alarm +- in1_min +- in1_min_alarm +- in1_crit +- in1_crit_alarm + +The driver provides the following attributes for output voltage: + +- in2_input +- in2_label +- in2_alarm +- in2_max +- in2_max_alarm +- in2_min +- in2_min_alarm +- in2_crit +- in2_crit_alarm +- in2_lcrit +- in2_lcrit_alarm + +The driver provides the following attributes for output current: + +- curr1_input +- curr1_label +- curr1_max +- curr1_max_alarm +- curr1_crit +- curr1_crit_alarm + +The driver provides the following attributes for temperature: + +- temp1_input +- temp1_max +- temp1_max_alarm +- temp1_crit +- temp1_crit_alarm + +Alarm acknowledgment +--------------------- + +The hwmon-class ``inX_alarm``/``currX_alarm``/``tempX_alarm`` files are +read-only in pmbus_core. The driver exposes the standard +``PMBUS_VIRT_RESET_*_HISTORY`` virtual-register channel for fault +acknowledgment: writing ``1`` to ``inX_reset_history`` / +``currX_reset_history`` / ``tempX_reset_history`` sends the chip a +``CLEAR_FAULTS`` (0x03) Send-Byte, which clears the latched +``STATUS_WORD`` / ``STATUS_VOUT`` / ``STATUS_IOUT`` / +``STATUS_INPUT`` / ``STATUS_TEMPERATURE`` bits the chip is currently +exposing. + +The MPS-specific NVM post-mortem register +``PROTECTION_LAST`` (0xFB) is not cleared by this path; it can be read +(decoded) via the ``protection_last`` debugfs entry described below. + +Regulator framework integration +------------------------------- + +When ``CONFIG_REGULATOR=y`` is set, the chip is +exposed under ``/sys/class/regulator/`` and accepts the standard +regulator framework operations: + +- ``regulator_enable()`` / ``regulator_disable()`` -> ``OPERATION`` (0x01) +- ``regulator_set_voltage()`` -> ``VOUT_COMMAND`` (0x21) +- ``regulator_get_voltage()`` -> ``READ_VOUT`` (0x8B) +- ``regulator_is_enabled()`` -> ``OPERATION`` bit-decode + +The single regulator descriptor is named ``"vout"`` (page 0). For a +multi-page configuration the descriptor table can be extended in +the driver. + +In-driver alarm-poll fallback +----------------------------- + +On boards where the chip's ``SMBALERT#`` pin is unavailable to the +SoC, ``pmbus_core::pmbus_irq_setup`` cannot deliver SMBALERT-driven +``poll(POLLPRI)`` wakes or ``udev change@...`` events on the +``inX_alarm`` files. The driver provides a ``delayed_work``-based +polling fallback that +periodically invokes the pmbus core fault check, +``pmbus_check_and_notify_faults()``, which sends the notifications and +then clears the latched faults, exactly like the ``SMBALERT#`` +interrupt path. The frequency of polling is tunable: + +:: + + /sys/kernel/debug/i2c/i2c-/-/alarm_poll_interval_ms + +The default is 1000 ms. + +Set it to 0 to disable. The worker self-suppresses +when ``client->irq != 0`` (i.e. when DT supplies an +``interrupts = <...>`` property on the regulator node), so adding +``SMBALERT#`` wiring is a zero-driver-change uplift on a future +board rev. + +MPS post-mortem (PROTECTION_LAST) +--------------------------------- + +The MPQ8646 silicon keeps a single 16-bit NVM-backed record of the +last protection event in ``PROTECTION_LAST`` (0xFB). It survives +chip power/reset. The following debugfs entries expose it: + +:: + + /sys/kernel/debug/i2c/i2c-/-/protection_last (RO) + /sys/kernel/debug/i2c/i2c-/-/status_decoded (RO) + +``protection_last`` decodes the 16-bit value based on the datasheet +fault names (``INIT_FAULT``, ``NVM_CRC_ERROR``, ``NVM_FAULT``, +``OC_PHASE_FAULT``, ``OTP_SELF_FAULT``, ``SWITCH_PRD_FAULT``, +``VIN_OV_FAULT``, ``VOUT_OV_FAULT``, ``VOUT_UV_FAULT``, +``OC_TOT_FAULT``, ``VIN_UVLO_FAULT``, ``DRMOS_OTP``). + +``status_decoded`` reads ``STATUS_WORD`` (0x79) and renders the +16 bits with MPS-extension labels (bit12 = ``NVM_SUMMARY``, +bit8 = ``WATCH_DOG``, bit0 = ``DRMOS_FAULT``) instead of the +PMBus 1.3 spec generic names. + +NVMEM snapshot +-------------- + +When ``CONFIG_NVMEM=y`` is set, the chip's NVM-backed +observability registers are exposed as a single 16-byte read-only +``nvmem_device`` at ``/sys/bus/nvmem/devices//nvmem``. +Layout (little-endian for 16-bit fields, zero-fill on per-entry read +failure and for the reserved tail): + +:: + + offset 0..1 PROTECTION_LAST (0xFB) word + offset 2..3 MFR_RETRY_TIMES (0xF4) word + offset 4..5 MFR_CONFIG_ID (0xC0) word + offset 6..7 MFR_VBOOT_CFG (0xFC) word + offset 8 MFR_SILICON_REV (0xC3) byte + offset 9..15 reserved (zero) + +Suitable for single-``cat`` post-mortem capture by a fleet daemon. + +Diagnostics and introspection +----------------------------- + +When ``CONFIG_DEBUG_FS=y`` is set, the driver adds a read-only +decode surface to the client's pmbus debugfs directory, +``/sys/kernel/debug/i2c/i2c-/-/``. +The driver intentionally exposes no raw register poke/peek debugfs; +use i2c-dev (``i2cget``/``i2cset``/``i2ctransfer``) for that. + +Identity / observability (read-only): + +========================== ================== ================================= +File PMBus / MFR cmd Description +========================== ================== ================================= +``mfr_config_id`` 0xC0 (word) board / SKU NVM identifier +``mfr_config_code_rev`` 0xC1 (word) NVM image revision (PART_RECOG + + config code rev fields) +``mfr_silicon_rev`` 0xC3 (byte) die revision +``mfr_retry_times`` 0xF4 (word) per-fault-class recovery-mode + configuration (NOT a retry + count). Four 4-bit fields + [15:12]=OTP, [11:8]=VOUT_OV, + [7:4]=VOUT_UV, [3:0]=OCP. + Each field: 0x0=latch-off, + 0x1..0xE=retry N times then + latch off, 0xF=hiccup + (retry indefinitely). The + chip exposes no in-NVM + retry-event counter; track + transitions externally if + needed (poll + ``protection_last`` or watch + ``inX_alarm`` / ``temp1_alarm`` + ``poll(POLLPRI)`` wakes). +``mfr_vboot_cfg`` 0xFC (word) ADDR/VBOOT latched at POR +========================== ================== ================================= + +Timing / UVLO knobs (read-only): + +========================== ================== ================================= +File PMBus cmd Description +========================== ================== ================================= +``vin_on`` 0x35 (word) UVLO turn-on threshold +``vin_off`` 0x36 (word) UVLO turn-off threshold +``ton_delay`` 0x60 (word) soft-start delay +``ton_rise`` 0x61 (word) soft-start ramp time +``toff_delay`` 0x64 (word) soft-stop delay +``toff_fall`` 0x65 (word) soft-stop ramp time +========================== ================== ================================= + +The only writable entry is ``alarm_poll_interval_ms`` (the alarm-poll +worker cadence, see above), it is driver-local and never touches the +chip. + +Devicetree +---------- + +The driver uses ``compatible = "mps,mpq8646"``. There are some few optional +properties: + +- ``mps,vout-fb-divider-ratio-permille`` : it writes ``VOUT_SCALE_LOOP`` + (0x29) at probe to compensate for an external resistor divider in + the VOUT feedback path. The valid range is 11-bit. +- ``interrupts = <...>`` : if the board routes the chip's + ``SMBALERT#`` pin to a SoC GPIO, declaring it here lights up + ``pmbus_core::pmbus_irq_setup`` and disables the in-driver + alarm-poll fallback diff --git a/MAINTAINERS b/MAINTAINERS index 2142c15b9946..6156e48e1816 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18340,6 +18340,13 @@ S: Maintained F: Documentation/hwmon/mpq82d00.rst F: drivers/hwmon/pmbus/mpq82d00.c +MPS MPQ8646 PMBUS DRIVER +M: Vincent Jardin +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/mpq8646.rst +F: drivers/hwmon/pmbus/mpq8646.c + MR800 AVERMEDIA USB FM RADIO DRIVER M: Alexey Klimov L: linux-media@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 2758f9695577..a1187c876784 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -626,6 +626,17 @@ config SENSORS_MPQ8785 This driver can also be built as a module. If so, the module will be called mpq8785. +config SENSORS_MPQ8646 + tristate "MPS MPQ8646" + depends on REGULATOR || !REGULATOR + depends on NVMEM || !NVMEM + help + If you say yes here you get hardware monitoring support for the + Monolithic Power Systems MPQ8646. + + This driver can also be built as a module. If so, the module + will be called mpq8646. + config SENSORS_PIM4328 tristate "Flex PIM4328 and compatibles" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index daa4b49bc90f..e288fe72a437 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_SENSORS_MP9945) += mp9945.o obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o +obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.o obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o diff --git a/drivers/hwmon/pmbus/mpq8646.c b/drivers/hwmon/pmbus/mpq8646.c new file mode 100644 index 000000000000..5133a2471873 --- /dev/null +++ b/drivers/hwmon/pmbus/mpq8646.c @@ -0,0 +1,688 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MPQ8646 step-down converter. + * + * Copyright (c) 2026 Free Mobile - Vincent Jardin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* Default cadence for the in-driver alarm-poll fallback (ms) */ +#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 + +/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ +#define MPS_PROTECTION_LAST 0xFB + +/* PMBus 1.3 timing / UVLO command codes */ +#define PMBUS_VIN_ON 0x35 +#define PMBUS_VIN_OFF 0x36 +#define PMBUS_TON_DELAY 0x60 +#define PMBUS_TON_RISE 0x61 +#define PMBUS_TOFF_DELAY 0x64 +#define PMBUS_TOFF_FALL 0x65 + +/* MPS vendor-extended observability / identity registers */ +#define MPS_MFR_CONFIG_ID 0xC0 +#define MPS_MFR_CONFIG_CODE_REV 0xC1 +#define MPS_MFR_SILICON_REV 0xC3 +#define MPS_MFR_RETRY_TIMES 0xF4 +#define MPS_MFR_VBOOT_CFG 0xFC + +#define MPQ8646_DEBUG(client, fmt, ...) \ + dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) + +/* + * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon + * The chip uses an 11-bit VOUT feedback-divider scale register. + */ +#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0) + +/* Forward declaration */ +struct mpq8646_dbg_reg_ctx; + +/* Per-instance state */ +struct mpq8646_priv { + struct pmbus_driver_info info; /* must be first, container_of target */ + struct i2c_client *client; + + /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ + struct mutex mps_lock; + + /* Set alarm_poll_interval_ms = 0 to disable. */ + struct delayed_work alarm_poll_work; + u32 alarm_poll_interval_ms; + +#ifdef CONFIG_DEBUG_FS + /* the only debugfs file that can re-arm the poll worker */ + struct dentry *dbg_poll; + struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; +#endif +}; + +static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + + return container_of(info, struct mpq8646_priv, info); +} + +/* + * VID-mode m/b/R coefficients, same values as the mpq8785 driver for + * the MPS VID encoding. Per the PMBus Direct formula used by + * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields + * 1.5625 mV per LSB. + */ +#define MPQ8646_VID_M 64 +#define MPQ8646_VID_B 0 +#define MPQ8646_VID_R 1 + +static int mpq8646_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode < 0) + return vout_mode; + + switch (vout_mode & PB_VOUT_MODE_MODE_MASK) { + case PB_VOUT_MODE_LINEAR: + info->format[PSC_VOLTAGE_OUT] = linear; + break; + case PB_VOUT_MODE_VID: + case PB_VOUT_MODE_DIRECT: + info->format[PSC_VOLTAGE_OUT] = direct; + info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M; + info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B; + info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R; + break; + default: + return -ENODEV; + } + + return 0; +}; + +static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg); + + switch (reg) { + case PMBUS_VOUT_MODE: + ret = pmbus_read_byte_data(client, page, reg); + MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret); + if (ret < 0) + return ret; + + if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID) + return PB_VOUT_MODE_DIRECT; + + return ret; + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_CML: + case PMBUS_STATUS_OTHER: + case PMBUS_STATUS_MFR_SPECIFIC: + case PMBUS_STATUS_FAN_12: + case PMBUS_STATUS_FAN_34: + return -ENXIO; + case PMBUS_MFR_LOCATION: + case PMBUS_MFR_DATE: + case PMBUS_MFR_SERIAL: + case PMBUS_IC_DEVICE_ID: + case PMBUS_IC_DEVICE_REV: + MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); + return -ENXIO; + default: + return -ENODATA; + } +} + +/* + * Reference: ltc2978.c::ltc2978_write_word_data which uses the + * same virtual-register channel for its real chip-side peak reset. + */ +static int mpq8646_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + struct mpq8646_priv *priv = mpq8646_priv_from_client(client); + int rc; + + switch (reg) { + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n", + reg); + rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); + if (rc < 0) + MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc); + return rc < 0 ? rc : 0; + default: + return -ENODATA; /* let pmbus_core do the direct write */ + } +} + +static int mpq8646_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int rc; + + MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n", + page, phase, reg); + + switch (reg) { + case PMBUS_READ_VIN: + case PMBUS_READ_VOUT: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_STATUS_WORD: + case PMBUS_VOUT_OV_FAULT_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_UV_FAULT_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_MFR_VIN_MAX: + case PMBUS_MFR_VOUT_MAX: + case PMBUS_MFR_IOUT_MAX: + case PMBUS_MFR_MAX_TEMP_1: + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + return 0; + default: + return -ENODATA; + } + + rc = i2c_smbus_read_word_data(client, reg); + + MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc); + return rc; +} + +static struct pmbus_driver_info mpq8646_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 4, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, +}; + +#if IS_ENABLED(CONFIG_REGULATOR) +static const struct regulator_desc mpq8646_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif /* CONFIG_REGULATOR */ + +#if IS_ENABLED(CONFIG_NVMEM) +/* + * Expose using + * /sys/bus/nvmem/devices//nvmem + * + * Layout (16 bytes): + * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM + * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM + * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM + * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM + * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM + * offset 9..15 reserved (zero-fill, leaves room for additions) + */ +#define MPQ8646_NVMEM_SIZE 16 + +struct mpq8646_nvmem_entry { + unsigned int off; + u8 reg; + bool is_word; +}; + +static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = { + { 0, MPS_PROTECTION_LAST, true }, + { 2, MPS_MFR_RETRY_TIMES, true }, + { 4, MPS_MFR_CONFIG_ID, true }, + { 6, MPS_MFR_VBOOT_CFG, true }, + { 8, MPS_MFR_SILICON_REV, false }, +}; + +static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val, + size_t bytes) +{ + struct mpq8646_priv *priv = data; + u8 *out = val; + size_t i; + + if (offset >= MPQ8646_NVMEM_SIZE) + return -EINVAL; + if (offset + bytes > MPQ8646_NVMEM_SIZE) + bytes = MPQ8646_NVMEM_SIZE - offset; + + memset(out, 0, bytes); + + guard(mutex)(&priv->mps_lock); + for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { + const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i]; + unsigned int e_start = e->off; + unsigned int e_end = e_start + (e->is_word ? 2 : 1); + u8 raw[2]; + int rc; + unsigned int j; + + if (e_end <= offset || e_start >= offset + bytes) + continue; /* outside requested slice */ + + if (e->is_word) + rc = i2c_smbus_read_word_data(priv->client, e->reg); + else + rc = i2c_smbus_read_byte_data(priv->client, e->reg); + if (rc < 0) + continue; /* leave the zero-fill in place */ + + raw[0] = rc & 0xff; + raw[1] = (rc >> 8) & 0xff; + + for (j = 0; j < e_end - e_start; j++) { + unsigned int abs = e_start + j; + + if (abs >= offset && abs < offset + bytes) + out[abs - offset] = raw[j]; + } + } + return 0; +} +#endif /* CONFIG_NVMEM */ + +static const struct i2c_device_id mpq8646_id[] = { + { .name = "mpq8646" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, mpq8646_id); + +static const struct of_device_id __maybe_unused mpq8646_of_match[] = { + { .compatible = "mps,mpq8646" }, + {} +}; +MODULE_DEVICE_TABLE(of, mpq8646_of_match); + +static struct pmbus_platform_data mpq8646_no_pec_pdata = { + .flags = PMBUS_NO_CAPABILITY, +}; + +#ifdef CONFIG_DEBUG_FS +/* + * Read-only decode cases in the client's pmbus debugfs directory: + * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the + * identity/timing registers. + */ + +struct mpq_status_bit { + u16 mask; + const char *name; +}; + +static const struct mpq_status_bit mpq8646_status_word_bits[] = { + { PB_STATUS_VOUT, "VOUT" }, + { PB_STATUS_IOUT_POUT, "IOUT_POUT" }, + { PB_STATUS_INPUT, "INPUT" }, + { PB_STATUS_WORD_MFR, "NVM_SUMMARY" }, + { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" }, + { PB_STATUS_FANS, "FANS" }, + { PB_STATUS_OTHER, "OTHER" }, + { PB_STATUS_UNKNOWN, "WATCH_DOG" }, + { PB_STATUS_BUSY, "BUSY" }, + { PB_STATUS_OFF, "OFF" }, + { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" }, + { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" }, + { PB_STATUS_VIN_UV, "VIN_UV_FAULT" }, + { PB_STATUS_TEMPERATURE, "TEMP" }, + { PB_STATUS_CML, "CML" }, + { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" }, + { /* sentinel */ } +}; + +/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */ +static const struct mpq_status_bit mpq8646_protection_last_bits[] = { + { BIT(15), "INIT_FAULT" }, + { BIT(14), "NVM_CRC_ERROR" }, + { BIT(13), "NVM_FAULT" }, + { BIT(12), "OC_PHASE_FAULT" }, + { BIT(11), "OTP_SELF_FAULT" }, + { BIT(9), "SWITCH_PRD_FAULT" }, + { BIT(8), "VIN_OV_FAULT" }, + { BIT(7), "VOUT_OV_FAULT" }, + { BIT(6), "VOUT_UV_FAULT" }, + { BIT(5), "OC_TOT_FAULT" }, + { BIT(4), "VIN_UVLO_FAULT" }, + { BIT(3), "DRMOS_OTP" }, + { /* sentinel */ } +}; + +static void mpq8646_print_bits(struct seq_file *s, u16 v, + const struct mpq_status_bit *tab) +{ + const struct mpq_status_bit *t; + bool first = true; + + seq_printf(s, "0x%04x", v); + if (!v) { + seq_puts(s, " [clean]\n"); + return; + } + seq_puts(s, " ["); + for (t = tab; t->mask; t++) { + if (v & t->mask) { + if (!first) + seq_putc(s, ' '); + seq_puts(s, t->name); + first = false; + } + } + seq_puts(s, "]\n"); +} + +static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused) +{ + struct mpq8646_priv *priv = s->private; + int rc; + + scoped_guard(pmbus_lock, priv->client) + rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); + if (rc < 0) { + seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc); + return 0; + } + seq_puts(s, "STATUS_WORD: "); + mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits); + seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); + +static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused) +{ + struct mpq8646_priv *priv = s->private; + int rc; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); + + if (rc < 0) { + seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc); + return 0; + } + seq_puts(s, "PROTECTION_LAST: "); + mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits); + seq_puts(s, "(NVM-backed, survives chip POR)\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); + +struct mpq8646_dbg_reg { + u8 reg; + bool is_word; + const char *name; +}; + +static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = { + /* MPS vendor extensions (read-only identity / observability) */ + { MPS_MFR_CONFIG_ID, true, "mfr_config_id" }, + { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" }, + { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" }, + { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" }, + { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" }, + /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ + { PMBUS_VIN_ON, true, "vin_on" }, + { PMBUS_VIN_OFF, true, "vin_off" }, + { PMBUS_TON_DELAY, true, "ton_delay" }, + { PMBUS_TON_RISE, true, "ton_rise" }, + { PMBUS_TOFF_DELAY, true, "toff_delay" }, + { PMBUS_TOFF_FALL, true, "toff_fall" }, +}; + +struct mpq8646_dbg_reg_ctx { + struct mpq8646_priv *priv; + const struct mpq8646_dbg_reg *desc; +}; + +static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) +{ + struct mpq8646_dbg_reg_ctx *ctx = s->private; + int rc; + + guard(pmbus_lock)(ctx->priv->client); + if (ctx->desc->is_word) + rc = i2c_smbus_read_word_data(ctx->priv->client, + ctx->desc->reg); + else + rc = i2c_smbus_read_byte_data(ctx->priv->client, + ctx->desc->reg); + + if (rc < 0) { + seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n", + ctx->desc->reg, ctx->desc->name, rc); + return 0; + } + seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); + +static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) +{ + struct mpq8646_priv *priv = data; + + *val = priv->alarm_poll_interval_ms; + return 0; +} + +static int mpq8646_dbg_poll_interval_set(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + bool was_off = !priv->alarm_poll_interval_ms; + + priv->alarm_poll_interval_ms = (u32)val; + + if (val && was_off && !priv->client->irq) + schedule_delayed_work(&priv->alarm_poll_work, + msecs_to_jiffies((u32)val)); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops, + mpq8646_dbg_poll_interval_get, + mpq8646_dbg_poll_interval_set, "%llu\n"); + +static void mpq8646_debugfs_register(struct mpq8646_priv *priv) +{ + struct dentry *root; + size_t i; + + root = pmbus_get_debugfs_dir(priv->client); + if (!root) + return; + + /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */ + debugfs_create_file("status_decoded", 0400, root, priv, + &mpq8646_dbg_status_decoded_fops); + debugfs_create_file("protection_last", 0400, root, priv, + &mpq8646_dbg_protection_last_fops); + priv->dbg_poll = + debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600, + root, priv, + &mpq8646_dbg_poll_interval_fops); + + priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev, + ARRAY_SIZE(mpq8646_dbg_regs), + sizeof(*priv->dbg_reg_ctx), + GFP_KERNEL); + if (!priv->dbg_reg_ctx) + return; + for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { + priv->dbg_reg_ctx[i].priv = priv; + priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i]; + debugfs_create_file(mpq8646_dbg_regs[i].name, 0400, + root, &priv->dbg_reg_ctx[i], + &mpq8646_dbg_reg_fops); + } +} + +static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) +{ + debugfs_remove(priv->dbg_poll); +} +#else +static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {} +static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {} +#endif /* CONFIG_DEBUG_FS */ + +static void mpq8646_alarm_poll_work(struct work_struct *work) +{ + struct mpq8646_priv *priv = container_of(to_delayed_work(work), + struct mpq8646_priv, + alarm_poll_work); + + if (priv->client->irq) + return; /* SMBALERT# wired; polling not needed */ + + if (!priv->alarm_poll_interval_ms) + return; /* polling disabled; don't re-arm */ + + pmbus_check_and_notify_faults(priv->client); + + schedule_delayed_work(&priv->alarm_poll_work, + msecs_to_jiffies(priv->alarm_poll_interval_ms)); +} + +static int mpq8646_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + struct mpq8646_priv *priv; + u32 voltage_scale; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->client = client; + mutex_init(&priv->mps_lock); + memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); + info = &priv->info; + + info->identify = mpq8646_identify; + info->read_byte_data = mpq8646_read_byte_data; + info->read_word_data = mpq8646_read_word_data; + info->write_word_data = mpq8646_write_word_data; + dev->platform_data = &mpq8646_no_pec_pdata; + +#if IS_ENABLED(CONFIG_REGULATOR) + info->reg_desc = mpq8646_reg_desc; + info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc); +#endif + + INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); + priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT; + + if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", + &voltage_scale)) { + if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, + voltage_scale); + if (ret) + return ret; + } + + ret = pmbus_do_probe(client, info); + if (ret) + return ret; + + mpq8646_debugfs_register(priv); + + if (!client->irq) + schedule_delayed_work(&priv->alarm_poll_work, + msecs_to_jiffies(priv->alarm_poll_interval_ms)); + +#if IS_ENABLED(CONFIG_NVMEM) + { + struct nvmem_config cfg = { + .dev = &client->dev, + .name = dev_name(&client->dev), + .owner = THIS_MODULE, + .read_only = true, + .root_only = true, + .word_size = 1, + .stride = 1, + .size = MPQ8646_NVMEM_SIZE, + .reg_read = mpq8646_nvmem_read, + .priv = priv, + }; + struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg); + + if (IS_ERR(nv)) + dev_warn(&client->dev, + "nvmem snapshot register failed (%pe)\n", + nv); + } +#endif + return 0; +}; + +static void mpq8646_remove(struct i2c_client *client) +{ + struct mpq8646_priv *priv = mpq8646_priv_from_client(client); + + mpq8646_debugfs_unregister(priv); + cancel_delayed_work_sync(&priv->alarm_poll_work); +} + +static struct i2c_driver mpq8646_driver = { + .driver = { + .name = "mpq8646", + .of_match_table = of_match_ptr(mpq8646_of_match), + }, + .probe = mpq8646_probe, + .remove = mpq8646_remove, + .id_table = mpq8646_id, +}; + +module_i2c_driver(mpq8646_driver); + +MODULE_AUTHOR("Vincent Jardin "); +MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); From 25ad621391ffc292f5d14381ebcb8c6d30f16a5f Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 30 Jul 2026 17:44:02 +0200 Subject: [PATCH 080/110] hwmon: (pmbus/mpq8646) Gate the writes The write cases of debugfs entries are provisioning and bench helpers. By designs the MPQ8646 powers the CPU core rail, so a wrong write can brown out the board or persist a bad setpoint into finite-cycle NVM. Said differently: some wrong register writes can and likely will physically damage or destroy the chip and/or the board. To be safe, it is disabled by default behind a CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE and we print an explicit warning banner at probe time when they are built in. Signed-off-by: Vincent Jardin Link: https://lore.kernel.org/r/20260730-mpq8646_v0-v7-4-e7c7ad768d5d@free.fr Signed-off-by: Guenter Roeck --- Documentation/hwmon/mpq8646.rst | 39 +++++ drivers/hwmon/pmbus/Kconfig | 20 +++ drivers/hwmon/pmbus/mpq8646.c | 288 ++++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+) diff --git a/Documentation/hwmon/mpq8646.rst b/Documentation/hwmon/mpq8646.rst index d3ede656ea15..d606d957581b 100644 --- a/Documentation/hwmon/mpq8646.rst +++ b/Documentation/hwmon/mpq8646.rst @@ -246,6 +246,45 @@ The only writable entry is ``alarm_poll_interval_ms`` (the alarm-poll worker cadence, see above), it is driver-local and never touches the chip. +Unsafe provisioning +------------------- + +Available only when ``CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE=y`` +(``default n``, it is for boards bring up only). They WRITE to the regulator. +On many designs the MPQ8646 powers the main CPU core rail, so a wrong write can +brown out the board or persist a bad setpoint into finite-cycle NVM. +WARNING: Some wrong register writes can and likely will physically damage or +destroy the chip and/or the board. An explicit warning banner is printed at probe +time when this option is enabled. + +=============================== =============== ========================================================= +File PMBus / MFR cmd Description +=============================== =============== ========================================================= +``store_all`` 0x15 STORE_USER_ALL Send-Byte (commit RAM config to NVM) +``restore_all`` 0x16 RESTORE_USER_ALL Send-Byte (revert RAM to last-NVM image) +``clear_protection_last`` 0x08 CLEAR_LAST_FAULT Send-Byte +``clear_protection_last_force`` -- gated CLEAR_LAST_FAULT, see below +``on_off_config`` 0x02 (byte) PMBus vs CTRL-pin on/off source + active polarity +``vout_margin_high`` 0x25 (word) margin-high VOUT setpoint +``vout_margin_low`` 0x26 (word) margin-low VOUT setpoint +``mfr_pmbus_lock`` 0xEE (word) programmable PMBus write-lock +``mfr_product_rev_user`` 0xC2 (word) user-programmable product revision +=============================== =============== ========================================================= + +``clear_protection_last`` writes ``CLEAR_LAST_FAULT`` (0x08) Send-Byte. +The chip silently no-ops unless ``MFR_CFG_EXT`` (0xF5) bit[6] is set. + +``clear_protection_last_force`` performs the unlock with the following +six-step dancing: + +1. read ``WRITE_PROTECT`` (0x10) and ``MFR_CFG_EXT`` (0xF5) for restore +2. clear ``WRITE_PROTECT`` if set +3. set ``MFR_CFG_EXT`` bit[6] = 1, preserving other bits +4. send ``CLEAR_LAST_FAULT`` (0x08) +5. restore ``MFR_CFG_EXT`` (with retry to handle the chip's + undocumented post-NVM-write busy window) +6. restore ``WRITE_PROTECT`` + Devicetree ---------- diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index a1187c876784..eb71a0ac044b 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -637,6 +637,26 @@ config SENSORS_MPQ8646 This driver can also be built as a module. If so, the module will be called mpq8646. +config SENSORS_MPQ8646_DEBUG_UNSAFE + bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)" + depends on SENSORS_MPQ8646 && DEBUG_FS + default n + help + Expose additional WRITE-able debugfs files in the client's + pmbus debugfs directory, + /sys/kernel/debug/i2c/i2c-/-/ + + These are provisioning and bring-up aids. On many designs the + MPQ8646 powers the SoC core rail, so a wrong write can brown out + or permanently mis-provision the board. + + WARNING: Wrong register writes can and likely will physically + damage or destroy the chip and/or the board. + + It shall never be set in a production, shipping, or default configuration. + + If unsure, say N. + config SENSORS_PIM4328 tristate "Flex PIM4328 and compatibles" help diff --git a/drivers/hwmon/pmbus/mpq8646.c b/drivers/hwmon/pmbus/mpq8646.c index 5133a2471873..6ac82beb0981 100644 --- a/drivers/hwmon/pmbus/mpq8646.c +++ b/drivers/hwmon/pmbus/mpq8646.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -23,8 +24,18 @@ #define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 /* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ +#define MPS_CLEAR_LAST_FAULT 0x08 +#define MPS_MFR_CFG_EXT 0xF5 +#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6) #define MPS_PROTECTION_LAST 0xFB +/* + * PMBus 1.3 NVM commit / revert commands. MPS equivalent of + * STORE_ALL (15h) and RESTORE_ALL (16h). + */ +#define PMBUS_STORE_USER_ALL 0x15 +#define PMBUS_RESTORE_USER_ALL 0x16 + /* PMBus 1.3 timing / UVLO command codes */ #define PMBUS_VIN_ON 0x35 #define PMBUS_VIN_OFF 0x36 @@ -36,10 +47,32 @@ /* MPS vendor-extended observability / identity registers */ #define MPS_MFR_CONFIG_ID 0xC0 #define MPS_MFR_CONFIG_CODE_REV 0xC1 +#define MPS_MFR_PRODUCT_REV_USER 0xC2 #define MPS_MFR_SILICON_REV 0xC3 #define MPS_MFR_RETRY_TIMES 0xF4 #define MPS_MFR_VBOOT_CFG 0xFC +/* + * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate + * subsequent PMBus writes + * bits[1:0] = 00 -- unlocked (POR default) + * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21) + * so the operator can still DVFS the rail + * 11 -- lock all writes + * A negative-going PG edge resets these bits to 00, the lock + * is operationally reversible without a full chip POR. + */ +#define MPS_MFR_PMBUS_LOCK 0xEE + +/* + * Retry parameters for the MFR_CFG_EXT gate-close write after + * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this + * silicon is about 1 ms; the datasheet does not have information. + */ +#define MPQ8646_NVM_RETRY_MAX 5 +#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000 +#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000 + #define MPQ8646_DEBUG(client, fmt, ...) \ dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) @@ -501,6 +534,259 @@ static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); +#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE +/* + * Write/provisioning data, disabled by default: NVM commit and + * revert, the CLEAR_LAST_FAULT sequences and a small set of named + * writable registers. + */ + +static void mpq8646_unsafe_banner(struct mpq8646_priv *priv) +{ + dev_warn(&priv->client->dev, + "**********************************************************\n" + "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" + "** **\n" + "** The MPQ8646 provisioning debugfs writes are enabled. **\n" + "** Wrong register writes can and likely will physically **\n" + "** damage or destroy the chip and/or the board. **\n" + "** **\n" + "** If you see this message and you are not debugging **\n" + "** the kernel, report this immediately to your system **\n" + "** administrator! **\n" + "** **\n" + "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" + "**********************************************************\n"); +} + +static int mpq8646_dbg_clear_protection_last(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); + if (rc < 0) + dev_warn(&priv->client->dev, + "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n", + rc); + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops, + NULL, mpq8646_dbg_clear_protection_last, "%llu\n"); + +static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc, ret; + int wp_orig, cfg_orig; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + guard(mutex)(&priv->mps_lock); + + wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); + if (wp_orig < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n", + wp_orig); + return wp_orig; + } + cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); + if (cfg_orig < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n", + cfg_orig); + return cfg_orig; + } + + if (wp_orig != 0) { + rc = i2c_smbus_write_byte_data(priv->client, + PMBUS_WRITE_PROTECT, 0); + if (rc < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: WP clear failed (%d), aborting\n", + rc); + return rc; + } + } + + ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, + (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); + if (ret < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: gate open failed (%d)\n", + ret); + } else { + ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); + if (ret < 0) + dev_warn(&priv->client->dev, + "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n", + ret); + + for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { + rc = i2c_smbus_write_word_data(priv->client, + MPS_MFR_CFG_EXT, + (u16)cfg_orig); + if (rc >= 0) + break; + usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN, + MPQ8646_NVM_RETRY_DELAY_US_MAX); + } + if (rc < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n", + rc); + if (ret == 0) + ret = rc; + } + } + + if (wp_orig != 0) { + rc = i2c_smbus_write_byte_data(priv->client, + PMBUS_WRITE_PROTECT, + (u8)wp_orig); + if (rc < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: WP restore failed (%d)\n", + rc); + if (ret == 0) + ret = rc; + } + } + return ret; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops, + NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n"); + +static int mpq8646_dbg_store_all(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); + if (rc < 0) + dev_warn(&priv->client->dev, + "store_all: STORE_USER_ALL (0x15) write failed (%d)\n", + rc); + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops, + NULL, mpq8646_dbg_store_all, "%llu\n"); + +static int mpq8646_dbg_restore_all(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); + if (rc < 0) + dev_warn(&priv->client->dev, + "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n", + rc); + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, + NULL, mpq8646_dbg_restore_all, "%llu\n"); + +static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = { + /* PMBus 1.3 control / margin */ + { PMBUS_ON_OFF_CONFIG, false, "on_off_config" }, + { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" }, + { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" }, + /* MPS PMBus-level write-protect */ + { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" }, + /* MPS user-writable product revision */ + { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" }, +}; + +static int mpq8646_dbg_reg_get(void *data, u64 *val) +{ + struct mpq8646_dbg_reg_ctx *ctx = data; + int rc; + + guard(pmbus_lock)(ctx->priv->client); + if (ctx->desc->is_word) + rc = i2c_smbus_read_word_data(ctx->priv->client, + ctx->desc->reg); + else + rc = i2c_smbus_read_byte_data(ctx->priv->client, + ctx->desc->reg); + if (rc < 0) + return rc; + *val = rc; + return 0; +} + +static int mpq8646_dbg_reg_set(void *data, u64 val) +{ + struct mpq8646_dbg_reg_ctx *ctx = data; + int rc; + + guard(pmbus_lock)(ctx->priv->client); + if (ctx->desc->is_word) + rc = i2c_smbus_write_word_data(ctx->priv->client, + ctx->desc->reg, (u16)val); + else + rc = i2c_smbus_write_byte_data(ctx->priv->client, + ctx->desc->reg, (u8)val); + return rc < 0 ? rc : 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops, + mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n"); + +static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, + struct dentry *root) +{ + struct mpq8646_dbg_reg_ctx *ctx; + size_t i; + + mpq8646_unsafe_banner(priv); + + debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv, + &mpq8646_dbg_clear_protection_last_fops); + debugfs_create_file_unsafe("clear_protection_last_force", 0200, root, + priv, + &mpq8646_dbg_clear_protection_last_force_fops); + debugfs_create_file_unsafe("store_all", 0200, root, priv, + &mpq8646_dbg_store_all_fops); + debugfs_create_file_unsafe("restore_all", 0200, root, priv, + &mpq8646_dbg_restore_all_fops); + + ctx = devm_kcalloc(&priv->client->dev, + ARRAY_SIZE(mpq8646_dbg_regs_unsafe), + sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return; + for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) { + ctx[i].priv = priv; + ctx[i].desc = &mpq8646_dbg_regs_unsafe[i]; + debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name, + 0600, root, &ctx[i], + &mpq8646_dbg_reg_rw_fops); + } +} +#else +static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, + struct dentry *root) {} +#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */ + static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) { struct mpq8646_priv *priv = data; @@ -557,6 +843,8 @@ static void mpq8646_debugfs_register(struct mpq8646_priv *priv) root, &priv->dbg_reg_ctx[i], &mpq8646_dbg_reg_fops); } + + mpq8646_debugfs_register_unsafe(priv, root); } static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) From 1c5b549f80a7b8f6b9977321e6888c3f91c0822d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 31 Jul 2026 22:28:29 -0700 Subject: [PATCH 081/110] hwmon: (pmbus/core) Introduce pmbus_read_smbus_i2c_block_data() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PMBus drivers need support for reading SMBus block data. Unfortunately, not all i2C controllers support this command. Implement pmbus_read_smbus_i2c_block_data() which first tries to use i2c_smbus_read_block_data(). If not supported, try to emulate it by calling i2c_smbus_read_i2c_block_data(). Export the new function for use in PMBus drivers. Cc: Alexis Czezar Torreno Cc: Nuno Sá Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 1 + drivers/hwmon/pmbus/pmbus_core.c | 98 +++++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 5fe2c415eada..2cd3216b3cd9 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -561,6 +561,7 @@ 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_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf); int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg); int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 6991c7f8d125..806c9a4913bb 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -182,7 +182,12 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); 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()); + s64 delay; + + if (!data) + return; + + delay = ktime_us_delta(data->next_access_backoff, ktime_get()); if (delay > 0) fsleep(delay); @@ -193,8 +198,14 @@ EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); 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; - int delay = info->access_delay; + const struct pmbus_driver_info *info; + int delay; + + if (!data) + return; + + info = data->info; + delay = info->access_delay; if (op & PMBUS_OP_WRITE) delay = max(delay, info->write_delay); @@ -518,6 +529,81 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, } EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS"); +/** + * pmbus_read_smbus_i2c_block_data() - Read SMBus/I2C block data + * @client: Handle to slave device + * @reg: Byte interpreted by slave + * @data_buf: Byte array into which data will be read + * Return: Negative errno or number of bytes read + * + * PMBus internal function to read a SMBus block from a PMBus chip. + * + * PMBus chips report various properties using SMBus block read operations. + * However, not all I2C controllers support this operation. + * + * Execute SMBus block read if supported. If not supported, but SMBus I2C block + * read is supported, use it instead. Note that at most 31 data bytes can be + * read from the device if i2c_smbus_read_i2c_block_data() is used to read the + * data. This is a SMBUs protocol limit which can not be avoided. + * + * Return -EOPNOTSUPP if neither I2C_FUNC_SMBUS_READ_BLOCK_DATA nor + * I2C_FUNC_SMBUS_READ_I2C_BLOCK is supported. + * + * Callers must hold pmbus_lock or execute calls from the probe function. + */ +int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf) +{ + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int blen, len, ret; + + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { + pmbus_wait(client); + ret = i2c_smbus_read_block_data(client, reg, data_buf); + pmbus_update_ts(client, 0); + return ret; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_I2C_BLOCK\n"); + return -EOPNOTSUPP; + } + + /* + * If the returned data is valid SMBus block data, the first byte + * must be the data length. + * + * i2c_smbus_read_i2c_block_data() may return an error if the chip + * sends NACK before the number of requested bytes is received. + * Handle this by reading the data length first, then reading the + * entire message up to I2C_SMBUS_BLOCK_MAX bytes. This ensures + * that requested number of bytes never exceeds the number of + * bytes sent by the chip. + */ + pmbus_wait(client); + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + pmbus_update_ts(client, 0); + if (ret < 0) + return ret; + + len = buf[0]; + if (len == 0) + return 0; + blen = len; + if (len >= I2C_SMBUS_BLOCK_MAX) + len = I2C_SMBUS_BLOCK_MAX - 1; + pmbus_wait(client); + ret = i2c_smbus_read_i2c_block_data(client, reg, len + 1, buf); + pmbus_update_ts(client, 0); + if (ret < 0) + return ret; + if (buf[0] != blen) + return -EIO; + memcpy(data_buf, buf + 1, len); + return len; +} +EXPORT_SYMBOL_NS_GPL(pmbus_read_smbus_i2c_block_data, "PMBUS"); + static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, char *data_buf) { @@ -527,11 +613,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, if (rv < 0) return rv; - pmbus_wait(client); - rv = i2c_smbus_read_block_data(client, reg, data_buf); - pmbus_update_ts(client, 0); - - return rv; + return pmbus_read_smbus_i2c_block_data(client, reg, data_buf); } static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, From 5b3dfe24a1a87110302504e543daa3f9e165eac5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 1 Aug 2026 18:41:26 -0700 Subject: [PATCH 082/110] hwmon: (pmbus/adm1275) Use pmbus_read_smbus_i2c_block_data for block commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver uses i2c_smbus_read_block_data() for PMBus block commands. Use pmbus_read_smbus_i2c_block_data() instead to enable the driver to work with I2C controllers which do not support i2c_smbus_read_block_data(). Drop the functionality check to get an explicit error message if the required SMBus commands are not supported. Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1275.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 174f9bcba7e2..beb32a878d1c 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -553,12 +553,7 @@ static int adm1275_probe(struct i2c_client *client) u32 shunt; u32 avg; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_READ_BYTE_DATA - | I2C_FUNC_SMBUS_BLOCK_DATA)) - return -ENODEV; - - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, block_buffer); if (ret < 0) { dev_err(&client->dev, "Failed to read Manufacturer ID\n"); return ret; @@ -570,7 +565,7 @@ static int adm1275_probe(struct i2c_client *client) return -ENODEV; } - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, block_buffer); if (ret < 0) { dev_err(&client->dev, "Failed to read Manufacturer Model\n"); return ret; From 08007275d56e2bec64a34f137b882411df3f99d2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 1 Aug 2026 18:54:05 -0700 Subject: [PATCH 083/110] hwmon: (pmbus/ltc2978) Use pmbus_read_smbus_i2c_block_data for block commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver uses a mix of pmbus_read_smbus_i2c_block_data() and i2c_smbus_read_i2c_block_data() for PMBus block commands. Use pmbus_read_smbus_i2c_block_data() instead to enable the driver to work I2C controllers which do not support both block commands. Drop the functionality check to simplify the code and to trigger a return with -EOPNOTSUPP if SMBus block commands are not supported. As part of this patch, rework detection of LT7170 and LT7171. The return length of pmbus_read_smbus_i2c_block_data() may be less than the requested number of bytes, meaning the return length needs to be checked. Also, checking for "LT7170-1" after checking for "LT7170" and checking for "LT7171-1" after checking for "LT7171" is pointless since the first check will already produce a match, so drop the second part of the check. Cc: Alexis Czezar Torreno Cc: Nuno Sá Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ltc2978.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 10877b0867fd..aea7b6c93e74 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -611,17 +611,13 @@ static int ltc2978_get_id(struct i2c_client *client) u8 buf[I2C_SMBUS_BLOCK_MAX]; int ret; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_READ_BLOCK_DATA)) - return -ENODEV; - - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, buf); if (ret < 0) return ret; if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3))) return -ENODEV; - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); if (ret < 0) return ret; for (id = <c2978_id[0]; strlen(id->name); id++) { @@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client) u8 buf[I2C_SMBUS_BLOCK_MAX]; int ret; - ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, - sizeof(buf), buf); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, + buf); if (ret < 0) return ret; - if (!strncmp(buf + 1, "LT7170", 6) || - !strncmp(buf + 1, "LT7170-1", 8)) + if (ret < 6) + return -ENODEV; + + if (!strncmp(buf, "LT7170", 6)) return lt7170; - if (!strncmp(buf + 1, "LT7171", 6) || - !strncmp(buf + 1, "LT7171-1", 8)) + if (!strncmp(buf, "LT7171", 6)) return lt7171; return -ENODEV; From 9d91132f4c15acba1a5be069b52577cc784c7e18 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 1 Aug 2026 18:59:38 -0700 Subject: [PATCH 084/110] hwmon: (pmbus/max20830) Use pmbus_read_smbus_i2c_block_data for block commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver tries to use both pmbus_read_smbus_i2c_block_data() and i2c_smbus_read_i2c_block_data() for PMBus block commands. Use pmbus_read_smbus_i2c_block_data() instead to simplify the code. Drop the functionality check to simplify the code and to trigger a return with -EOPNOTSUPP if SMBus block commands are not supported. Cc: Alexis Czezar Torreno Cc: Nuno Sá Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max20830.c | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c index 689fc6f0fe01..4d25a084af09 100644 --- a/drivers/hwmon/pmbus/max20830.c +++ b/drivers/hwmon/pmbus/max20830.c @@ -101,36 +101,10 @@ static int max20830_probe(struct i2c_client *client) device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1); device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2); - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) && - !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) - return -ENODEV; - - /* - * Use i2c_smbus_read_block_data() if supported, otherwise fall back - * to i2c_smbus_read_i2c_block_data() to support I2C controllers - * which do not support SMBus block reads. - */ - if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { - ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to read IC_DEVICE_ID\n"); - } else { - /* Reads 1 length byte + data bytes */ - ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, - MAX20830_IC_DEVICE_ID_LENGTH + 1, - buf); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to read IC_DEVICE_ID\n"); - /* - * Moves data forward, removing the length byte, this is to - * match the format of i2c_smbus_read_block_data(). - * Also adjust return value to reflect length byte removal. - */ - memmove(buf, buf + 1, MAX20830_IC_DEVICE_ID_LENGTH); - ret = ret - 1; - } + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read IC_DEVICE_ID\n"); /* Verify we read the expected number of bytes */ if (ret < MAX20830_IC_DEVICE_ID_LENGTH) From 84f3d13ca48ecd971d8f94ec5a6e4a39ef0296e1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 1 Aug 2026 19:09:29 -0700 Subject: [PATCH 085/110] hwmon: (pmbus/ir36021) Use pmbus_read_smbus_i2c_block_data for block commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver uses i2c_smbus_read_i2c_block_data() for PMBus block commands. Use pmbus_read_smbus_i2c_block_data() instead to enable the driver to work with I2C controllers which do not support i2c_smbus_read_i2c_block_data(). Drop the functionality check to get an explicit error message if the required SMBus commands are not supported. Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ir36021.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c index 0dce4c3f666f..58d8c4aac0d1 100644 --- a/drivers/hwmon/pmbus/ir36021.c +++ b/drivers/hwmon/pmbus/ir36021.c @@ -31,21 +31,13 @@ static int ir36021_probe(struct i2c_client *client) u8 buf[I2C_SMBUS_BLOCK_MAX]; 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_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf); - if (ret < 0) { - dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); - return ret; - } - if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) { - dev_err(&client->dev, "MFR_MODEL unrecognised\n"); - return -ENODEV; - } + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read PMBUS_MFR_MODEL\n"); + if (ret != 1 || buf[0] != 0x2d) + return dev_err_probe(&client->dev, -ENODEV, + "MFR_MODEL unrecognised\n"); return pmbus_do_probe(client, &ir36021_info); } From 7d9d401d98bc93e134481139fe9dfa6f4fe25dd7 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Wed, 5 Aug 2026 10:15:17 -0300 Subject: [PATCH 086/110] dt-bindings: hwmon: add adi,adt7470 The Analog Devices ADT7470 is a multichannel temperature sensor and PWM fan controller. It supports monitoring up to 10 external temperature sensors and controlling up to 4 fans. Add the device tree binding documentation for it. This includes support for the thermal framework by defining the "#thermal-sensor-cells" property, and models the fan control lines as PWM channels by defining the "#pwm-cells" property. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260805-adt7470_thermalzone-v6-1-5605a5f95467@gmail.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/adi,adt7470.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/adi,adt7470.yaml diff --git a/Documentation/devicetree/bindings/hwmon/adi,adt7470.yaml b/Documentation/devicetree/bindings/hwmon/adi,adt7470.yaml new file mode 100644 index 000000000000..a701542898ee --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,adt7470.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,adt7470.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADT7470 hwmon sensor + +maintainers: + - Luiz Angelo Daros de Luca + +description: + Multi-channel temperature monitor and PWM fan controller. + It supports monitoring up to 10 external temperature sensors and + controlling up to 4 fans. The fan control lines are modeled + as standard PWM channels. + +properties: + compatible: + const: adi,adt7470 + + reg: + maxItems: 1 + + "#pwm-cells": + const: 3 + + "#thermal-sensor-cells": + const: 1 + description: + Valid index values are 0 to 9, corresponding to temp1 through temp10. + +required: + - compatible + - reg + +allOf: + - if: + required: + - "#thermal-sensor-cells" + then: + $ref: /schemas/thermal/thermal-sensor.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hwmon@2f { + compatible = "adi,adt7470"; + reg = <0x2f>; + #pwm-cells = <3>; + #thermal-sensor-cells = <1>; + }; + }; From 75eae6e1fa6ea8dd6319ca6d8bce9a466654f809 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Wed, 5 Aug 2026 10:15:18 -0300 Subject: [PATCH 087/110] hwmon: (adt7470) Add ADT7470_PWM_MAX macro Instead of a magic 255, use a macro to refer to it. Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260805-adt7470_thermalzone-v6-2-5605a5f95467@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index c45b984c02e6..87ddd7b8ddae 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -100,6 +100,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; #define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2)) #define ADT7470_PWM_COUNT 4 +#define ADT7470_PWM_MAX 255 #define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x)) #define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x)) #define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x)) @@ -850,7 +851,7 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val switch (attr) { case hwmon_pwm_input: - val = clamp_val(val, 0, 255); + val = clamp_val(val, 0, ADT7470_PWM_MAX); mutex_lock(&data->lock); err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), val); @@ -910,7 +911,7 @@ static ssize_t pwm_max_store(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; - temp = clamp_val(temp, 0, 255); + temp = clamp_val(temp, 0, ADT7470_PWM_MAX); mutex_lock(&data->lock); data->pwm_max[attr->index] = temp; @@ -945,7 +946,7 @@ static ssize_t pwm_min_store(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; - temp = clamp_val(temp, 0, 255); + temp = clamp_val(temp, 0, ADT7470_PWM_MAX); mutex_lock(&data->lock); data->pwm_min[attr->index] = temp; From 48e20476948dbb8fc1b963d8ae220619b4be5d70 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Wed, 5 Aug 2026 10:15:19 -0300 Subject: [PATCH 088/110] hwmon: (adt7470) Expose fan control via PWM framework The ADT7470 features four PWM outputs designed to control fans. Previously, these were only accessible through the legacy hwmon sysfs interface. Expose the ADT7470 fan control lines through the generic PWM framework. This allows generic PWM consumers described in Device Tree, such as "pwm-fan", to use the device through the "#pwm-cells" property. This is particularly necessary for boards where the ADT7470 external temperature sensors are not populated and the chip is used strictly as a fan controller (e.g., Edgecore ECS-2100-52T switches). In such setups, delegating control to the generic PWM subsystem allows the kernel's thermal framework to manage the fans based on unrelated temperature zones, such as internal SoC sensors. When a PWM consumer applies a new PWM state, the driver automatically switches the corresponding PWM channel to manual mode so that the requested duty cycle takes effect. The duty cycle specified by the PWM framework is internally converted to the 0-255 scale expected by the hardware registers. To prevent conflicting access, the hwmon PWM sysfs attributes are hidden when the device is registered with PWM framework support (i.e. when "#pwm-cells" is present in DT and CONFIG_PWM is reachable). Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260805-adt7470_thermalzone-v6-3-5605a5f95467@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/adt7470.c | 194 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 194 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e51269fd9de3..e60bfc31bbad 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -224,6 +224,7 @@ config SENSORS_ADT7462 config SENSORS_ADT7470 tristate "Analog Devices ADT7470" depends on I2C + depends on PWM || PWM=n select REGMAP_I2C help If you say yes here you get support for the Analog Devices diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 87ddd7b8ddae..eeec3db0da88 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; @@ -164,6 +167,7 @@ struct adt7470_data { char limits_valid; unsigned long sensors_last_updated; /* In jiffies */ unsigned long limits_last_updated; /* In jiffies */ + bool use_pwm_framework; int num_temp_sensors; /* -1 = probe */ int temperatures_probed; @@ -887,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val return err; } +struct adt7470_pwm_wfhw { + u8 val; +}; + +static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + struct adt7470_pwm_wfhw *wfhw = _wfhw; + u64 actual_period; + + if (wf->duty_length_ns == 0) { + wfhw->val = 0; + return 0; + } + + /* + * The PWM frequency (period) is a single chip-wide setting shared by + * all 4 channels, so it cannot be changed on a per-pwm_device basis + * through this API. The duty cycle is rounded against the currently + * configured hardware period rather than the period requested in + * @wf; round_waveform_fromhw() reports the actual resulting + * waveform back so the core/consumer can detect a mismatch. + */ + actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); + + if (actual_period > wf->period_length_ns) + /* period too short */ + return 1; + + if (wf->duty_length_ns >= actual_period) { + wfhw->val = ADT7470_PWM_MAX; + } else { + wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns, + ADT7470_PWM_MAX, + actual_period); + } + + return 0; +} + +static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + const struct adt7470_pwm_wfhw *wfhw = _wfhw; + + wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); + wf->duty_offset_ns = 0; + wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns, + ADT7470_PWM_MAX); + return 0; +} + +static int adt7470_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + struct device *dev = regmap_get_device(data->regmap); + struct adt7470_pwm_wfhw *wfhw = _wfhw; + + data = adt7470_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * No lock needed: like the other hwmon_ops read callbacks in this + * driver (e.g. adt7470_pwm_read()), this only does a single byte + * read from the cache populated by adt7470_update_device(). + */ + wfhw->val = data->pwm[pwm->hwpwm]; + + return 0; +} + +static int adt7470_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + const struct adt7470_pwm_wfhw *wfhw = _wfhw; + unsigned int pwm_auto_reg_mask; + int err; + + if (pwm->hwpwm % 2) + pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; + else + pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; + + guard(mutex)(&data->lock); + + if (data->pwm[pwm->hwpwm] == wfhw->val && + data->pwm_automatic[pwm->hwpwm] == 0) + return 0; + + /* Put the PWM channel in manual mode before updating it. */ + err = regmap_update_bits(data->regmap, + ADT7470_REG_PWM_CFG(pwm->hwpwm), + pwm_auto_reg_mask, 0); + if (err < 0) + return err; + + data->pwm_automatic[pwm->hwpwm] = 0; + + err = regmap_write(data->regmap, + ADT7470_REG_PWM(pwm->hwpwm), wfhw->val); + if (err < 0) + return err; + + data->pwm[pwm->hwpwm] = wfhw->val; + + return 0; +} + +static const struct pwm_ops adt7470_pwm_ops = { + .sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw), + .round_waveform_tohw = adt7470_pwm_round_waveform_tohw, + .round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw, + .read_waveform = adt7470_pwm_read_waveform, + .write_waveform = adt7470_pwm_write_waveform, +}; + static ssize_t pwm_max_show(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -1107,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = { &dev_attr_alarm_mask.attr, &dev_attr_num_temp_sensors.attr, &dev_attr_auto_update_interval.attr, + NULL +}; + +static struct attribute *adt7470_pwm_attrs[] = { &sensor_dev_attr_force_pwm_max.dev_attr.attr, &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, @@ -1131,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = { NULL }; -ATTRIBUTE_GROUPS(adt7470); +static const struct attribute_group adt7470_group = { + .attrs = adt7470_attrs, +}; + +static umode_t adt7470_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct adt7470_data *data = dev_get_drvdata(dev); + + if (data->use_pwm_framework) + return 0; + + return attr->mode; +} + +static const struct attribute_group adt7470_pwm_group = { + .attrs = adt7470_pwm_attrs, + .is_visible = adt7470_pwm_is_visible, +}; + +static const struct attribute_group *adt7470_groups[] = { + &adt7470_group, + &adt7470_pwm_group, + NULL, +}; static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) @@ -1166,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct adt7470_data *data = _data; umode_t mode = 0; switch (type) { @@ -1198,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ } break; case hwmon_pwm: + /* Hide all pwm attributes if this device is exposed to the PWM + * framework + */ + if (data->use_pwm_framework) { + mode = 0; + break; + } + switch (attr) { case hwmon_pwm_input: case hwmon_pwm_enable: @@ -1328,6 +1497,29 @@ static int adt7470_probe(struct i2c_client *client) data->pwm_freq = (u32)freq_val; + data->use_pwm_framework = false; + + if (device_property_present(dev, "#pwm-cells")) { + if (IS_REACHABLE(CONFIG_PWM)) { + struct pwm_chip *chip; + + chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->ops = &adt7470_pwm_ops; + pwmchip_set_drvdata(chip, data); + + err = devm_pwmchip_add(dev, chip); + if (err) + return dev_err_probe(dev, err, "failed to register PWM chip\n"); + + data->use_pwm_framework = true; + } else { + dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n"); + } + } + /* Register sysfs hooks */ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &adt7470_chip_info, From 62f8ed1d030f1918fa859b9df27e1aed8d240858 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Wed, 5 Aug 2026 10:15:20 -0300 Subject: [PATCH 089/110] hwmon: (adt7470) Add thermal zone sensor support Register the ADT7470 temperature channels as thermal zone sensors. Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260805-adt7470_thermalzone-v6-4-5605a5f95467@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index eeec3db0da88..f652532f2f32 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -1396,6 +1396,8 @@ static const struct hwmon_ops adt7470_hwmon_ops = { }; static const struct hwmon_channel_info * const adt7470_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, From 9c110a467686ee84e951a8a3cc518afc9de7eeef Mon Sep 17 00:00:00 2001 From: Sergio Melas Date: Thu, 6 Aug 2026 15:11:03 +0200 Subject: [PATCH 090/110] hwmon: (yogafan) Add support for new Lenovo models Add DMI quirk entries and map correct ACPI paths and configurations for additional Lenovo laptop models including the LOQ 15IAX9, XiaoXin Pro 13ARE 2020, IdeaPad 3 15ALC6, Legion Pro 7 16AFR10H, Yoga Pro 7 14IAH10, and Yoga 7 16ARP8. Reorder the DMI quirk table to evaluate specific model matches before generic family fallbacks, preventing DMI shadowing. Signed-off-by: Sergio Melas Link: https://lore.kernel.org/r/20260806131103.19104-1-sergiomelas@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/yogafan.rst | 27 ++++++++++++- drivers/hwmon/yogafan.c | 69 +++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/Documentation/hwmon/yogafan.rst b/Documentation/hwmon/yogafan.rst index 68761947a1a8..6395c94f4a6b 100644 --- a/Documentation/hwmon/yogafan.rst +++ b/Documentation/hwmon/yogafan.rst @@ -99,6 +99,11 @@ immediately to ensure the user knows the fan has stopped. 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 + 83KF | XiaoXinPro 13ARE | 0x06/0xFE | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 + 82KU | IdeaPad 3 15ALC6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 83RU | Legion Pro 7 16 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 + 83KF | Yoga Pro 7 14IAH | 0x06 | \_SB.PC00.LPCB.EC0.FANS | 8-bit | 100 + 83BS | Yoga 7 16ARP8 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 *Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100 ---------------------------------------------------------------------------------------------------- @@ -117,7 +122,6 @@ METHODOLOGY & IDENTIFICATION: - 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 ---------- @@ -136,3 +140,24 @@ References 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/lenovo/ideapad-laptop.c + +5. **Lenovo Product Specifications Reference (PSREF):** Official hardware layout index + and spec sheets for active and withdrawn Lenovo laptop models. + https://psref.lenovo.com/l/withdrawn/ + +6. **Yogafan Master Quirk Database:** Master spreadsheet mapping Lenovo Product + Specifications Reference (PSREF) to explicit EC offsets, register widths, paths, and multipliers. + https://github.com/sergiomelas/lenovo-linux-drivers/blob/main/Lenovo_Drivers/Prototype/PSREF/yogafan_v3_quirks_database.ods + +7. **Yogafan ACPI DSDT Repository:** Central repository containing user-contributed raw + and decompiled ACPI DSDT firmware dumps used for path verification and hardware expansions. + https://github.com/sergiomelas/lenovo-linux-drivers/tree/main/Lenovo_Drivers/Prototype/DSDT + +Contributors & DSDT Providers: +------------------------------ +- **Sarbajit Sarkar** (Lenovo LOQ 15IAX9) +- **HinataKato** (XiaoXin Pro 13ARE 2020 - 83KF) +- **PenPenIsGod** (IdeaPad 3 15ALC6 - 82KU & Legion Pro 7 16AFR10H - 83RU) +- **unlockxiaom** (Legion Pro 7 16AFR10H - 83RU, Yoga Pro 7 14IAH10 - 83KF, ThinkCentre M80q) +- **Phani Pavan K** (Yoga Pro 7 14IAH10 - 83KF) +- **Splarkszter** (Yoga 7 16ARP8 - 83BS) diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c index 0a41b8672b56..48fa5148d9e2 100644 --- a/drivers/hwmon/yogafan.c +++ b/drivers/hwmon/yogafan.c @@ -53,6 +53,7 @@ struct yoga_fan_data { }; /* Specific configurations mapped via DMI */ + static const struct yogafan_config yoga_8bit_fans_cfg = { .multiplier = 100, .fan_count = 1, @@ -77,6 +78,18 @@ static const struct yogafan_config loq_15iax9_8bit_dual_cfg = { .paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" } }; +static const struct yogafan_config xiaoxin_8bit_dual_cfg = { + .multiplier = 100, + .fan_count = 2, + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } +}; + +static const struct yogafan_config yoga_pro_7_14iah10_cfg = { + .multiplier = 100, + .fan_count = 1, + .paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL } +}; + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) { ktime_t now = ktime_get_boottime(); @@ -176,6 +189,54 @@ static const struct hwmon_chip_info yoga_fan_chip_info = { }; static const struct dmi_system_id yogafan_quirks[] = { + { + .ident = "Lenovo LOQ 15IAX9", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), + }, + .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, + }, + { + .ident = "Lenovo XiaoXin Pro 13ARE 2020", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "XiaoXinPro-13ARE 2020"), + }, + .driver_data = (void *)&xiaoxin_8bit_dual_cfg, + }, + { + .ident = "Lenovo IdeaPad 3 15ALC6", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6"), + }, + .driver_data = (void *)&ideapad_8bit_fan0_cfg, + }, + { + .ident = "Lenovo Legion Pro 7 16AFR10H", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion Pro 7 16AFR10H"), + }, + .driver_data = (void *)&xiaoxin_8bit_dual_cfg, + }, + { + .ident = "Lenovo Yoga Pro 7 14IAH10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga Pro 7 14IAH10"), + }, + .driver_data = (void *)&yoga_pro_7_14iah10_cfg, + }, + { + .ident = "Lenovo Yoga 7 16ARP8", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 7 16ARP8"), + }, + .driver_data = (void *)&xiaoxin_8bit_dual_cfg, + }, { .ident = "Lenovo Yoga", .matches = { @@ -200,14 +261,6 @@ static const struct dmi_system_id yogafan_quirks[] = { }, .driver_data = (void *)&ideapad_8bit_fan0_cfg, }, - { - .ident = "Lenovo LOQ 15IAX9", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), - }, - .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, - }, { } }; MODULE_DEVICE_TABLE(dmi, yogafan_quirks); From dafe7f1ec9db8f92a575a22f67d4f0f28bf10657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 6 Aug 2026 08:09:11 +0200 Subject: [PATCH 091/110] hwmon: (core) Constify device attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mark the attribute structures as const, as they are never modified. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260806-sysfs-const-attr-hwmon-v2-1-22fee8b85509@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 29dc90a2c3fe..f1051b89c564 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -71,27 +71,27 @@ struct hwmon_thermal_data { }; static ssize_t -name_show(struct device *dev, struct device_attribute *attr, char *buf) +name_show(struct device *dev, const struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->name); } -static DEVICE_ATTR_RO(name); +static const DEVICE_ATTR_RO(name); static ssize_t -label_show(struct device *dev, struct device_attribute *attr, char *buf) +label_show(struct device *dev, const struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); } -static DEVICE_ATTR_RO(label); +static const DEVICE_ATTR_RO(label); -static struct attribute *hwmon_dev_attrs[] = { +static const struct attribute *const hwmon_dev_attrs[] = { &dev_attr_name.attr, &dev_attr_label.attr, NULL }; static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, - struct attribute *attr, int n) + const struct attribute *attr, int n) { struct device *dev = kobj_to_dev(kobj); struct hwmon_device *hdev = to_hwmon_device(dev); @@ -106,8 +106,8 @@ static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, } static const struct attribute_group hwmon_dev_attr_group = { - .attrs = hwmon_dev_attrs, - .is_visible = hwmon_dev_attr_is_visible, + .attrs_const = hwmon_dev_attrs, + .is_visible_const = hwmon_dev_attr_is_visible, }; static const struct attribute_group *hwmon_dev_attr_groups[] = { From 7df38f8d4cbf41eace0834ebbb6aecb1df4682b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 6 Aug 2026 08:09:12 +0200 Subject: [PATCH 092/110] hwmon: (core) Use const APIs for the dynamically allocated sysfs attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The non-const sysfs attribute APIs are going to go away at some point. Switch to the const variants to prepare for that. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260806-sysfs-const-attr-hwmon-v2-2-22fee8b85509@weissschuh.net Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 54 +++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index f1051b89c564..fbceede1182a 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -57,8 +57,8 @@ struct hwmon_device_attribute { }; #define to_hwmon_attr(d) \ - container_of(d, struct hwmon_device_attribute, dev_attr) -#define to_dev_attr(a) container_of(a, struct device_attribute, attr) + container_of_const(d, struct hwmon_device_attribute, dev_attr) +#define to_dev_attr(a) container_of_const(a, struct device_attribute, attr) /* * Thermal zone information @@ -115,13 +115,13 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = { NULL }; -static void hwmon_free_attrs(struct attribute **attrs) +static void hwmon_free_attrs(const struct attribute *const *attrs) { int i; for (i = 0; attrs[i]; i++) { - struct device_attribute *dattr = to_dev_attr(attrs[i]); - struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); + const struct device_attribute *dattr = to_dev_attr(attrs[i]); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); kfree(hattr); } @@ -132,8 +132,8 @@ static void hwmon_dev_release(struct device *dev) { struct hwmon_device *hwdev = to_hwmon_device(dev); - if (hwdev->group.attrs) - hwmon_free_attrs(hwdev->group.attrs); + if (hwdev->group.attrs_const) + hwmon_free_attrs(hwdev->group.attrs_const); kfree(hwdev->groups); kfree(hwdev->label); kfree(hwdev); @@ -425,9 +425,9 @@ static int hwmon_pec_register(struct device *hdev) /* sysfs attribute management */ static ssize_t hwmon_attr_show(struct device *dev, - struct device_attribute *devattr, char *buf) + const struct device_attribute *devattr, char *buf) { - struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); struct hwmon_device *hwdev = to_hwmon_device(dev); s64 val64; long val; @@ -450,10 +450,10 @@ static ssize_t hwmon_attr_show(struct device *dev, } static ssize_t hwmon_attr_show_string(struct device *dev, - struct device_attribute *devattr, + const struct device_attribute *devattr, char *buf) { - struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); struct hwmon_device *hwdev = to_hwmon_device(dev); enum hwmon_sensor_types type = hattr->type; const char *s; @@ -473,10 +473,10 @@ static ssize_t hwmon_attr_show_string(struct device *dev, } static ssize_t hwmon_attr_store(struct device *dev, - struct device_attribute *devattr, + const struct device_attribute *devattr, const char *buf, size_t count) { - struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); struct hwmon_device *hwdev = to_hwmon_device(dev); long val; int ret; @@ -510,12 +510,12 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) (type == hwmon_fan && attr == hwmon_fan_label); } -static struct attribute *hwmon_genattr(const void *drvdata, - enum hwmon_sensor_types type, - u32 attr, - int index, - const char *template, - const struct hwmon_ops *ops) +static const struct attribute *hwmon_genattr(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, + int index, + const char *template, + const struct hwmon_ops *ops) { struct hwmon_device_attribute *hattr; struct device_attribute *dattr; @@ -552,8 +552,8 @@ static struct attribute *hwmon_genattr(const void *drvdata, hattr->ops = ops; dattr = &hattr->dev_attr; - dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; - dattr->store = hwmon_attr_store; + dattr->show_const = is_string ? hwmon_attr_show_string : hwmon_attr_show; + dattr->store_const = hwmon_attr_store; a = &dattr->attr; sysfs_attr_init(a); @@ -831,7 +831,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) } static int hwmon_genattrs(const void *drvdata, - struct attribute **attrs, + const struct attribute **attrs, const struct hwmon_ops *ops, const struct hwmon_channel_info *info) { @@ -850,7 +850,7 @@ static int hwmon_genattrs(const void *drvdata, u32 attr; while (attr_mask) { - struct attribute *a; + const struct attribute *a; attr = __ffs(attr_mask); attr_mask &= ~BIT(attr); @@ -869,11 +869,11 @@ static int hwmon_genattrs(const void *drvdata, return aindex; } -static struct attribute ** +static const struct attribute ** __hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip) { int ret, i, aindex = 0, nattrs = 0; - struct attribute **attrs; + const struct attribute **attrs; for (i = 0; chip->info[i]; i++) nattrs += hwmon_num_channel_attrs(chip->info[i]); @@ -928,7 +928,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, hdev = &hwdev->dev; if (chip) { - struct attribute **attrs; + const struct attribute **attrs; int ngroups = 2; /* terminating NULL plus &hwdev->groups */ if (groups) @@ -947,7 +947,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, goto free_hwmon; } - hwdev->group.attrs = attrs; + hwdev->group.attrs_const = attrs; ngroups = 0; hwdev->groups[ngroups++] = &hwdev->group; From 52fcdd5639dc0cc2701a7d035e6d05adfd393f73 Mon Sep 17 00:00:00 2001 From: Wilken Gottwalt Date: Fri, 7 Aug 2026 05:32:54 +0000 Subject: [PATCH 093/110] hwmon: (corsair-psu) Update documentation Add information about the HX1500i 2025 series and fix the missing HX1500i in Kbuild. Signed-off-by: Wilken Gottwalt Link: https://lore.kernel.org/r/anVuBd8Bb7T66OUO@monster.localdomain Signed-off-by: Guenter Roeck --- Documentation/hwmon/corsair-psu.rst | 6 +++--- drivers/hwmon/Kconfig | 4 ++-- drivers/hwmon/corsair-psu.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/hwmon/corsair-psu.rst b/Documentation/hwmon/corsair-psu.rst index 2e99cfd556a0..8d3636292076 100644 --- a/Documentation/hwmon/corsair-psu.rst +++ b/Documentation/hwmon/corsair-psu.rst @@ -19,7 +19,7 @@ Supported devices: Corsair HX1200i (Legacy, Series 2023 and Series 2025) - Corsair HX1500i (Legacy and Series 2023) + Corsair HX1500i (Legacy, Series 2023 and Series 2025) Corsair RM550i @@ -93,8 +93,8 @@ Debugfs entries --------------- ======================= ======================================================== -ocpmode Single or multi rail mode of the PCIe power connectors -product Product name of the psu +ocpmode Single or multi rail mode of the PCIe power connectors +product Product name of the psu uptime Session uptime of the psu uptime_total Total uptime of the psu vendor Vendor name of the psu diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e60bfc31bbad..43418633e2e8 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -515,8 +515,8 @@ config SENSORS_CORSAIR_PSU If you say yes here you get support for Corsair PSUs with a HID interface. Currently this driver supports the (RM/HX)550i, (RM/HX)650i, - (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies - by Corsair. + (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i, HX1200i and HX1500i power + supplies by Corsair. This driver can also be built as a module. If so, the module will be called corsair-psu. diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c index 0cf072f86acb..b545461d4ced 100644 --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -884,7 +884,7 @@ static const struct hid_device_id corsairpsu_idtable[] = { { HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */ { HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */ { HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */ - { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */ + { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy, Series 2023 and 2025 */ { HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */ { HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */ { }, From 87fdc8d7bb64d6f6669fa8daf4c59940387df07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 6 Aug 2026 08:09:13 +0200 Subject: [PATCH 094/110] hwmon: (sysfs) Allow drivers to register const attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch to the __DEVICE_ATTR() macro which can handle callbacks taking both const and non-const attribute structure arguments. Allow the step-wise migration of the drivers. Also use container_of_const() over container_of() to avoid casting away the constness accidentally. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20260806-sysfs-const-attr-hwmon-v2-3-22fee8b85509@weissschuh.net [groeck: Squashed 'hwmon: (core) Constify PEC device attribute'] Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 6 +++--- include/linux/hwmon-sysfs.h | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index fbceede1182a..41755910a25a 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -341,7 +341,7 @@ static int hwmon_match_device(struct device *dev, const void *data) return dev->class == &hwmon_class; } -static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, +static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, char *buf) { struct i2c_client *client = to_i2c_client(dev); @@ -349,7 +349,7 @@ static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); } -static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, +static ssize_t pec_store(struct device *dev, const struct device_attribute *devattr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); @@ -390,7 +390,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, return err; } -static DEVICE_ATTR_RW(pec); +static const DEVICE_ATTR_RW(pec); static void hwmon_remove_pec(void *dev) { diff --git a/include/linux/hwmon-sysfs.h b/include/linux/hwmon-sysfs.h index d896713359cd..ee5b33185b2d 100644 --- a/include/linux/hwmon-sysfs.h +++ b/include/linux/hwmon-sysfs.h @@ -15,10 +15,10 @@ struct sensor_device_attribute{ int index; }; #define to_sensor_dev_attr(_dev_attr) \ - container_of(_dev_attr, struct sensor_device_attribute, dev_attr) + container_of_const(_dev_attr, struct sensor_device_attribute, dev_attr) -#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ - { .dev_attr = __ATTR(_name, _mode, _show, _store), \ +#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ + { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ .index = _index } #define SENSOR_ATTR_RO(_name, _func, _index) \ @@ -49,11 +49,11 @@ struct sensor_device_attribute_2 { u8 nr; }; #define to_sensor_dev_attr_2(_dev_attr) \ - container_of(_dev_attr, struct sensor_device_attribute_2, dev_attr) + container_of_const(_dev_attr, struct sensor_device_attribute_2, dev_attr) -#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ - { .dev_attr = __ATTR(_name, _mode, _show, _store), \ - .index = _index, \ +#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ + { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ + .index = _index, \ .nr = _nr } #define SENSOR_ATTR_2_RO(_name, _func, _nr, _index) \ From 6ee0d3de9568b8372e6be0e5517fd443aaaf325c Mon Sep 17 00:00:00 2001 From: Rodrigo Araujo Date: Sun, 9 Aug 2026 23:26:53 +0200 Subject: [PATCH 095/110] hwmon: (asus-ec-sensors) add ROG STRIX Z390-E GAMING The ROG STRIX Z390-E GAMING and Z390-F GAMING are essentially the same board apart from the E's onboard WiFi, so they share the same EC sensor layout. Signed-off-by: Rodrigo Araujo Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20260809212705.1501382-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index a5db48d5a72e..2c68a343a2ed 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -52,6 +52,7 @@ Supported boards: * ROG STRIX X870E-E GAMING WIFI * ROG STRIX X870E-E GAMING WIFI7 R2 * ROG STRIX X870E-H GAMING WIFI7 + * ROG STRIX Z390-E GAMING * ROG STRIX Z390-F GAMING * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index d386f056dc55..e43645e884fd 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -976,6 +976,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x870e_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7", &board_info_strix_x870e_h_gaming_wifi7), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING", + &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", From 788c49cb94a915b8cda1ce0d4742106aff0e0689 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Fri, 31 Jul 2026 14:30:05 +0300 Subject: [PATCH 096/110] dt-bindings: hwmon: Document SMSC EMC1402/1403/1404/1428 Document the basic hardware layout of SMSC (now Microchip) EMC1402/1403/1404/1428 thermal sensors. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20260731113007.145322-2-clamor95@gmail.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/smsc,emc1403.yaml | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/smsc,emc1403.yaml diff --git a/Documentation/devicetree/bindings/hwmon/smsc,emc1403.yaml b/Documentation/devicetree/bindings/hwmon/smsc,emc1403.yaml new file mode 100644 index 000000000000..d7873a37c329 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/smsc,emc1403.yaml @@ -0,0 +1,203 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/smsc,emc1403.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SMSC EMC1402/1403/1404/1428 thermal sensors + +maintainers: + - Svyatoslav Ryhel + +properties: + compatible: + oneOf: + - enum: + - smsc,emc1402 + - smsc,emc1403 + - smsc,emc1404 + - smsc,emc1428 + + - items: + - enum: + - smsc,emc1412 + - smsc,emc1422 + - smsc,emc1442 + - const: smsc,emc1402 + + - items: + - enum: + - smsc,emc1413 + - smsc,emc1423 + - const: smsc,emc1403 + + - items: + - enum: + - smsc,emc1414 + - smsc,emc1424 + - const: smsc,emc1404 + + - items: + - enum: + - smsc,emc1438 + - const: smsc,emc1428 + + interrupts: + items: + - description: Sensors "ALERT" pin output. + + reg: + maxItems: 1 + + "#thermal-sensor-cells": + const: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + vdd-supply: true + +required: + - compatible + - reg + +patternProperties: + "^channel@([0-7])$": + type: object + description: Represents channels of the device and their specific configuration. + + properties: + reg: + description: The channel number. 0 is local channel, 1-7 are remote channels. + items: + minimum: 0 + maximum: 7 + + label: + description: A descriptive name for this channel, like "ambient" or "psu". + + required: + - reg + + additionalProperties: false + +allOf: + - if: + properties: + compatible: + contains: + enum: + - smsc,emc1402 + then: + patternProperties: + "^channel@([0-7])$": + properties: + reg: + items: + maximum: 1 + + - if: + properties: + compatible: + contains: + enum: + - smsc,emc1403 + then: + patternProperties: + "^channel@([0-7])$": + properties: + reg: + items: + maximum: 2 + + - if: + properties: + compatible: + contains: + enum: + - smsc,emc1404 + then: + patternProperties: + "^channel@([0-7])$": + properties: + reg: + items: + maximum: 3 + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@4c { + compatible = "smsc,emc1412", "smsc,emc1402"; + reg = <0x4c>; + + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_LEVEL_LOW>; + + vdd-supply = <&vdd_3v3_sys>; + #thermal-sensor-cells = <1>; + }; + }; + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@29 { + compatible = "smsc,emc1428"; + reg = <0x29>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0x0>; + label = "local"; + }; + + channel@1 { + reg = <0x1>; + label = "chan1"; + }; + + channel@2 { + reg = <0x2>; + label = "chan2"; + }; + + channel@3 { + reg = <0x3>; + label = "chan3"; + }; + + channel@4 { + reg = <0x4>; + label = "chan4"; + }; + + channel@5 { + reg = <0x5>; + label = "chan5"; + }; + + channel@6 { + reg = <0x6>; + label = "chan6"; + }; + + channel@7 { + reg = <0x7>; + label = "chan7"; + }; + }; + }; From 799bea4b95797770599df91491563f35be70a170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Schw=C3=B6bel?= Date: Fri, 31 Jul 2026 14:30:06 +0300 Subject: [PATCH 097/110] hwmon: (emc1403) Convert to use OF bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add OF match compatible table to be able to use this driver with Device Trees. Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Link: https://lore.kernel.org/r/20260731113007.145322-3-clamor95@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/emc1403.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index cd753b38709f..8b3173da2597 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -659,14 +659,14 @@ static int emc1403_probe(struct i2c_client *client) { struct thermal_data *data; struct device *hwmon_dev; - const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client); data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->chip = id->driver_data; + data->chip = (uintptr_t)i2c_get_match_data(client); + data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config); if (IS_ERR(data->regmap)) return PTR_ERR(data->regmap); @@ -682,10 +682,20 @@ static const unsigned short emc1403_address_list[] = { 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END }; +static const struct of_device_id emc1403_of_match[] = { + { .compatible = "smsc,emc1402", .data = (void *)emc1402 }, + { .compatible = "smsc,emc1403", .data = (void *)emc1403 }, + { .compatible = "smsc,emc1404", .data = (void *)emc1404 }, + { .compatible = "smsc,emc1428", .data = (void *)emc1428 }, + { } +}; +MODULE_DEVICE_TABLE(of, emc1403_of_match); + static struct i2c_driver sensor_emc1403 = { .class = I2C_CLASS_HWMON, .driver = { .name = "emc1403", + .of_match_table = emc1403_of_match, }, .detect = emc1403_detect, .probe = emc1403_probe, From aa85c604799790dcfbee6d15b7605321cbbdca04 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Fri, 31 Jul 2026 14:30:07 +0300 Subject: [PATCH 098/110] hwmon: (emc1403) Add regulator support Add support for VDD power supply for this sensor. It is required on some devices for the sensor to start up and work properly. Signed-off-by: Svyatoslav Ryhel Link: https://lore.kernel.org/r/20260731113007.145322-4-clamor95@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/emc1403.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index 8b3173da2597..a0f8d29eb6c7 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #define THERMAL_PID_REG 0xfd @@ -659,6 +660,12 @@ static int emc1403_probe(struct i2c_client *client) { struct thermal_data *data; struct device *hwmon_dev; + int ret; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to enable regulator\n"); data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), GFP_KERNEL); From 7a00741653f66c0b46dd6c2a41f6d7a209791105 Mon Sep 17 00:00:00 2001 From: Jiazhi Huang Date: Tue, 11 Aug 2026 17:37:43 +0800 Subject: [PATCH 099/110] Documentation: hwmon: (nct6775) Add missing NCT6797D and NCT6798D The NCT6797D and NCT6798D chips are already supported by the driver but were missing from the documentation. Signed-off-by: Jiazhi Huang Link: https://lore.kernel.org/r/20260811093744.12199-2-huangjia2hi@163.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/hwmon/nct6775.rst b/Documentation/hwmon/nct6775.rst index 9d7a10de61a7..6725f8ff98e0 100644 --- a/Documentation/hwmon/nct6775.rst +++ b/Documentation/hwmon/nct6775.rst @@ -80,6 +80,22 @@ Supported chips: Datasheet: Available from Nuvoton upon request + * Nuvoton NCT6797D + + Prefix: 'nct6797' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Available from Nuvoton upon request + + * Nuvoton NCT6798D + + Prefix: 'nct6798' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Available from Nuvoton upon request + * Nuvoton NCT6796D-S/NCT6799D-R Prefix: 'nct6799' From 54743b5ab981d686b885c3da639d4ed6cc995e8b Mon Sep 17 00:00:00 2001 From: Jiazhi Huang Date: Tue, 11 Aug 2026 17:37:44 +0800 Subject: [PATCH 100/110] hwmon: (nct6775) Add NCT5585D to list of supported chips NCT5585D uses the same chip ID (0xd428) as NCT6798D and is fully compatible with it. Tested on an ASUS PRIME H610M-D motherboard. Signed-off-by: Jiazhi Huang Link: https://lore.kernel.org/r/20260811093744.12199-3-huangjia2hi@163.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6775.rst | 8 ++++++++ drivers/hwmon/nct6775-core.c | 1 + drivers/hwmon/nct6775-platform.c | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/nct6775.rst b/Documentation/hwmon/nct6775.rst index 6725f8ff98e0..41b21bed7e27 100644 --- a/Documentation/hwmon/nct6775.rst +++ b/Documentation/hwmon/nct6775.rst @@ -96,6 +96,14 @@ Supported chips: Datasheet: Available from Nuvoton upon request + * Nuvoton NCT5585D (compatible to NCT6798D) + + Prefix: 'nct6798' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Not available + * Nuvoton NCT6796D-S/NCT6799D-R Prefix: 'nct6799' diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c index 94482c8092cd..759ea3d1286e 100644 --- a/drivers/hwmon/nct6775-core.c +++ b/drivers/hwmon/nct6775-core.c @@ -33,6 +33,7 @@ * (0xd451) * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 * (0xd429) + * nct5585d 14 7 7 2+6 0xd428 0xc1 0x5ca3 * nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3 * nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3 * diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index 1975399ac440..d394376e4306 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -34,7 +34,7 @@ static const char * const nct6775_sio_names[] __initconst = { [nct6795] = "NCT6795D", [nct6796] = "NCT6796D", [nct6797] = "NCT6797D", - [nct6798] = "NCT6798D", + [nct6798] = "NCT6798D/NCT5585D", [nct6799] = "NCT6796D-S/NCT6799D-R", }; From 7c933f9a521070eec2ee1d76abcef6088935a8c8 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Wed, 12 Aug 2026 21:09:45 +0200 Subject: [PATCH 101/110] hwmon: (tmp102) Add TMP110 device ID The TMP110 is register compatible with TMP102, add non-DT I2C device ID. Signed-off-by: Marek Vasut Link: https://lore.kernel.org/r/20260812191021.65304-2-marex@nabladev.com Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp102.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index 95fb912a1f7e..26c461e95175 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -396,6 +396,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume static const struct i2c_device_id tmp102_id[] = { { .name = "tmp102" }, + { .name = "tmp110" }, { } }; MODULE_DEVICE_TABLE(i2c, tmp102_id); From 24be3317697c2374802bd63f09cb5085b24c4cf9 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 13 Aug 2026 11:04:24 +0200 Subject: [PATCH 102/110] hwmon: (tmp102) Add TMP113 device ID The TMP113 temperature sensor part is register compatible with TMP102, add non-DT I2C device ID. Unlike TMP102 and TMP110, the TMP113 does have additional unique ID registers, those are currently unused. Signed-off-by: Marek Vasut Link: https://lore.kernel.org/r/20260813090456.70816-2-marex@nabladev.com Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp102.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index 26c461e95175..50a8b050d8da 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -397,6 +397,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume static const struct i2c_device_id tmp102_id[] = { { .name = "tmp102" }, { .name = "tmp110" }, + { .name = "tmp113" }, { } }; MODULE_DEVICE_TABLE(i2c, tmp102_id); From 8440d5aca227d623801b5f8a2b9e3d86b7daa8bf Mon Sep 17 00:00:00 2001 From: Gabriel Ford Date: Thu, 13 Aug 2026 17:02:08 +0000 Subject: [PATCH 103/110] hwmon: (k10temp) Add per-CCD temperature monitoring for Zen5 Turin Add support for per-CCD temperature monitoring on Zen 5 Turin (EPYC 9005) CPUs, as they fall into a separate model range with a different offset and a higher maximum CCD count than their desktop counterparts. As such, this patch also updates the driver to support CPUs with up to 16 CCDs. Tested and working on an EPYC 9555P. Signed-off-by: Gabriel Ford Link: https://lore.kernel.org/r/20260813170232.3841-1-gabriel@gford.dev Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index de0760dc597d..75a45010d687 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -207,6 +207,10 @@ static const char *k10temp_temp_label[] = { "Tccd10", "Tccd11", "Tccd12", + "Tccd13", + "Tccd14", + "Tccd15", + "Tccd16", }; static int k10temp_read_labels(struct device *dev, @@ -243,7 +247,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, if (*val < 0 && !data->disp_negative) *val = 0; break; - case 2 ... 13: /* Tccd{1-12} */ + case 2 ... 17: /* Tccd{1-16} */ ret = read_ccd_temp_reg(data, channel - 2, ®val); if (ret) @@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const k10temp_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), NULL }; @@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) } } else if (boot_cpu_data.x86 == 0x1a) { switch (boot_cpu_data.x86_model) { + case 0x00 ... 0x2f: /* Zen5 Turin */ + data->ccd_offset = 0x1F0; + k10temp_get_ccd_support(data, 16); + break; case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ data->ccd_offset = 0x308; k10temp_get_ccd_support(data, 8); From b20ee9aee8779427d2f9de99cbb578b048bf7c04 Mon Sep 17 00:00:00 2001 From: Arie Miller Date: Wed, 12 Aug 2026 06:35:30 -0400 Subject: [PATCH 104/110] hwmon: (asus_rog_ryujin) Add per-device configuration Move model-specific report offsets and capabilities into a device information structure. This prepares the driver for coolers which use a different report layout or do not include the external fan controller, while preserving the existing Ryujin II 360 behavior. Handles an issue reported by Sashiko where an id could be missing driver_data. Link: https://lore.kernel.org/r/5a817284-a9f4-48b2-9f0f-802c5dc6963c@roeck-us.net Assisted-by: Codex:gpt-5.6-sol sparse Signed-off-by: Arie Miller Reviewed-by: Aleksa Savic Link: https://lore.kernel.org/r/20260812103532.395049-2-renari@arimil.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_rog_ryujin.c | 75 +++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index 10a1f5aca988..80563ce38519 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -18,15 +18,25 @@ #define USB_VENDOR_ID_ASUS_ROG 0x0b05 #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ +struct rog_ryujin_device_info { + u8 temp_offset; + u8 pump_speed_offset; + u8 fan_speed_offset; + u8 duty_channel; + bool has_controller; +}; + +static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = { + .temp_offset = 3, + .pump_speed_offset = 5, + .fan_speed_offset = 7, + .duty_channel = 0, + .has_controller = true, +}; + #define STATUS_VALIDITY 1500 /* ms */ #define MAX_REPORT_LENGTH 65 -/* Cooler status report offsets */ -#define RYUJIN_TEMP_SENSOR_1 3 -#define RYUJIN_TEMP_SENSOR_2 4 -#define RYUJIN_PUMP_SPEED 5 -#define RYUJIN_INTERNAL_FAN_SPEED 7 - /* Cooler duty report offsets */ #define RYUJIN_PUMP_DUTY 4 #define RYUJIN_INTERNAL_FAN_DUTY 5 @@ -81,6 +91,7 @@ static const char *const rog_ryujin_speed_label[] = { struct rog_ryujin_data { struct hid_device *hdev; struct device *hwmon_dev; + const struct rog_ryujin_device_info *info; /* For reinitializing the completions below */ spinlock_t status_report_request_lock; struct completion cooler_status_received; @@ -112,6 +123,8 @@ static int rog_ryujin_pwm_to_percent(long val) static umode_t rog_ryujin_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct rog_ryujin_data *priv = data; + switch (type) { case hwmon_temp: switch (attr) { @@ -123,6 +136,8 @@ static umode_t rog_ryujin_is_visible(const void *data, } break; case hwmon_fan: + if (channel >= 2 && !priv->info->has_controller) + return 0; switch (attr) { case hwmon_fan_label: case hwmon_fan_input: @@ -132,6 +147,8 @@ static umode_t rog_ryujin_is_visible(const void *data, } break; case hwmon_pwm: + if (channel >= 2 && !priv->info->has_controller) + return 0; switch (attr) { case hwmon_pwm_input: return 0644; @@ -198,12 +215,14 @@ static int rog_ryujin_get_status(struct rog_ryujin_data *priv) if (ret < 0) return ret; - /* Retrieve controller status (speeds) */ - ret = - rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH, - &priv->controller_status_received); - if (ret < 0) - return ret; + if (priv->info->has_controller) { + /* Retrieve controller status (speeds) */ + ret = rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, + GET_CMD_LENGTH, + &priv->controller_status_received); + if (ret < 0) + return ret; + } /* Retrieve cooler duty */ ret = @@ -212,12 +231,14 @@ static int rog_ryujin_get_status(struct rog_ryujin_data *priv) if (ret < 0) return ret; - /* Retrieve controller duty */ - ret = - rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH, - &priv->controller_duty_received); - if (ret < 0) - return ret; + if (priv->info->has_controller) { + /* Retrieve controller duty */ + ret = rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, + GET_CMD_LENGTH, + &priv->controller_duty_received); + if (ret < 0) + return ret; + } priv->updated = jiffies; return 0; @@ -289,6 +310,7 @@ static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel return ret; memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH); + set_cmd[2] = priv->info->duty_channel; /* Cooler duties are set as 0-100% */ val = rog_ryujin_pwm_to_percent(val); @@ -394,10 +416,12 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { /* Received coolant temp and speeds of pump and internal fan */ - priv->temp_input[0] = - data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100; - priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED); - priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED); + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + + data[priv->info->temp_offset + 1] * 100; + priv->speed_input[0] = + get_unaligned_le16(data + priv->info->pump_speed_offset); + priv->speed_input[1] = + get_unaligned_le16(data + priv->info->fan_speed_offset); if (!completion_done(&priv->cooler_status_received)) complete_all(&priv->cooler_status_received); @@ -471,11 +495,15 @@ static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id struct rog_ryujin_data *priv; int ret; + if (!id->driver_data) + return -EINVAL; + priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->hdev = hdev; + priv->info = (const struct rog_ryujin_device_info *)id->driver_data; hid_set_drvdata(hdev, priv); /* @@ -546,7 +574,8 @@ static void rog_ryujin_remove(struct hid_device *hdev) } static const struct hid_device_id rog_ryujin_table[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO), + .driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info }, { } }; From cd258aca0dc489cfa9206f0a4dfa1715ac54e0aa Mon Sep 17 00:00:00 2001 From: Arie Miller Date: Wed, 12 Aug 2026 06:35:31 -0400 Subject: [PATCH 105/110] hwmon: (asus_rog_ryujin) Add ROG Ryujin III support The ROG Ryujin III uses different report offsets and a different cooler-duty channel from the Ryujin II. It also lacks the separate external fan controller supplied with the older model. Add model data and USB IDs for the Extreme and EVA Edition variants. Skip controller commands and hide the unavailable controller hwmon channels for these devices. Update the driver documentation, Kconfig text, and module description accordingly. Link: https://github.com/aleksamagicka/asus_rog_ryujin-hwmon/pull/9 Assisted-by: Codex:gpt-5.6-sol sparse Signed-off-by: Arie Miller Reviewed-by: Aleksa Savic Link: https://lore.kernel.org/r/20260812103532.395049-3-renari@arimil.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_rog_ryujin.rst | 24 +++++++++++++----------- drivers/hwmon/Kconfig | 4 ++-- drivers/hwmon/asus_rog_ryujin.c | 18 ++++++++++++++++-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Documentation/hwmon/asus_rog_ryujin.rst b/Documentation/hwmon/asus_rog_ryujin.rst index 9f77da070022..cfdfe3712f19 100644 --- a/Documentation/hwmon/asus_rog_ryujin.rst +++ b/Documentation/hwmon/asus_rog_ryujin.rst @@ -6,6 +6,8 @@ Kernel driver asus_rog_ryujin Supported devices: * ASUS ROG RYUJIN II 360 +* ASUS ROG RYUJIN III EXTREME +* ASUS ROG RYUJIN III EVA EDITION Author: Aleksa Savic @@ -16,10 +18,10 @@ This driver enables hardware monitoring support for the listed ASUS ROG RYUJIN all-in-one CPU liquid coolers. Available sensors are pump, internal and external (controller) fan speed in RPM, their duties in PWM, as well as coolant temperature. -Attaching external fans to the controller is optional and allows them to be -controlled from the device. If not connected, the fan-related sensors will -report zeroes. The controller is a separate hardware unit that comes bundled -with the AIO and connects to it to allow fan control. +The RYUJIN II includes a separate external fan controller. Attaching fans to +the controller is optional and allows them to be controlled from the device. +If not connected, the controller-related sensors will report zeroes. The +RYUJIN III does not expose these controller channels. The addressable LCD screen is not supported in this driver and should be controlled through userspace tools. @@ -33,15 +35,15 @@ supports hot swapping. Sysfs entries ------------- -=========== ============================================= +=========== ========================================================== fan1_input Pump speed (in rpm) fan2_input Internal fan speed (in rpm) -fan3_input External (controller) fan 1 speed (in rpm) -fan4_input External (controller) fan 2 speed (in rpm) -fan5_input External (controller) fan 3 speed (in rpm) -fan6_input External (controller) fan 4 speed (in rpm) +fan3_input External (controller) fan 1 speed (in rpm, RYUJIN II only) +fan4_input External (controller) fan 2 speed (in rpm, RYUJIN II only) +fan5_input External (controller) fan 3 speed (in rpm, RYUJIN II only) +fan6_input External (controller) fan 4 speed (in rpm, RYUJIN II only) temp1_input Coolant temperature (in millidegrees Celsius) pwm1 Pump duty pwm2 Internal fan duty -pwm3 External (controller) fan duty -=========== ============================================= +pwm3 External (controller) fan duty (RYUJIN II only) +=========== ========================================================== diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 43418633e2e8..81a9a1d40eec 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -293,11 +293,11 @@ config SENSORS_ASC7621 will be called asc7621. config SENSORS_ASUS_ROG_RYUJIN - tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver" + tristate "ASUS ROG RYUJIN hardware monitoring driver" depends on HID help If you say yes here you get support for the fans and sensors of - the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler. + supported ASUS ROG RYUJIN II and III AIO CPU liquid coolers. This driver can also be built as a module. If so, the module will be called asus_rog_ryujin. diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index 80563ce38519..79e5b6d6dae0 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. + * hwmon driver for Asus ROG Ryujin AIO coolers. * * Copyright 2024 Aleksa Savic */ @@ -17,6 +17,8 @@ #define USB_VENDOR_ID_ASUS_ROG 0x0b05 #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ +#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb +#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade struct rog_ryujin_device_info { u8 temp_offset; @@ -34,6 +36,14 @@ static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = { .has_controller = true, }; +static const struct rog_ryujin_device_info rog_ryujin_iii_info = { + .temp_offset = 5, + .pump_speed_offset = 7, + .fan_speed_offset = 10, + .duty_channel = 1, + .has_controller = false, +}; + #define STATUS_VALIDITY 1500 /* ms */ #define MAX_REPORT_LENGTH 65 @@ -576,6 +586,10 @@ static void rog_ryujin_remove(struct hid_device *hdev) static const struct hid_device_id rog_ryujin_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO), .driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EXTREME), + .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA), + .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, { } }; @@ -605,4 +619,4 @@ module_exit(rog_ryujin_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Aleksa Savic "); -MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); +MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin AIO coolers"); From 21b21a8c202ad1a0a1abb4e29ea45535da834b33 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Wed, 12 Aug 2026 06:35:32 -0400 Subject: [PATCH 106/110] hwmon: (asus_rog_ryujin) Add ROG Ryujin III White Edition The ROG Ryujin III White Edition uses the same report layout as the other supported Ryujin III variants. Add its USB device ID and list it in the driver documentation. The device was tested with the driver on the author's hardware. Link: https://github.com/aleksamagicka/asus_rog_ryujin-hwmon/pull/10 Signed-off-by: Will Smith Assisted-by: Codex:gpt-5.6-sol sparse Signed-off-by: Arie Miller Reviewed-by: Aleksa Savic Link: https://lore.kernel.org/r/20260812103532.395049-4-renari@arimil.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_rog_ryujin.rst | 1 + drivers/hwmon/asus_rog_ryujin.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Documentation/hwmon/asus_rog_ryujin.rst b/Documentation/hwmon/asus_rog_ryujin.rst index cfdfe3712f19..b0d7ce8dd921 100644 --- a/Documentation/hwmon/asus_rog_ryujin.rst +++ b/Documentation/hwmon/asus_rog_ryujin.rst @@ -8,6 +8,7 @@ Supported devices: * ASUS ROG RYUJIN II 360 * ASUS ROG RYUJIN III EXTREME * ASUS ROG RYUJIN III EVA EDITION +* ASUS ROG RYUJIN III WHITE EDITION Author: Aleksa Savic diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index 79e5b6d6dae0..702edb831394 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -19,6 +19,7 @@ #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ #define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb #define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade +#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada struct rog_ryujin_device_info { u8 temp_offset; @@ -590,6 +591,8 @@ static const struct hid_device_id rog_ryujin_table[] = { .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA), .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE), + .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, { } }; From 24fbeb83d9b750a36da42cb835a154d80fd3d495 Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 10 Aug 2026 11:27:54 +0700 Subject: [PATCH 107/110] hwmon: (max6621) fix temperature clamp range MAX6621_TEMP_INPUT_MIN and MAX6621_TEMP_INPUT_MAX are used to clamp the writable offset and critical thresholds. They are defined as -127000 and 128000. The driver decodes the temperature through an s8 and its own comment in max6621_read() documents an 8-bit two's complement value, whose range is -128 to +127 degrees C. The current limits therefore reject the valid -128 degrees C and accept +128 degrees C, which does not fit the 8-bit range. Correct the limits to -128000 and 127000. Fixes: 92b64580f14b ("hwmon: (max6621) Add support for Maxim MAX6621 temperature sensor") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://lore.kernel.org/r/9d3a4f1895a47794bb359a2a32fb1ccd6a15812c.1786334038.git.congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/max6621.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/max6621.c b/drivers/hwmon/max6621.c index e86ec6d237ca..16a3a61d2532 100644 --- a/drivers/hwmon/max6621.c +++ b/drivers/hwmon/max6621.c @@ -17,8 +17,8 @@ #define MAX6621_DRV_NAME "max6621" #define MAX6621_TEMP_INPUT_REG_NUM 9 -#define MAX6621_TEMP_INPUT_MIN -127000 -#define MAX6621_TEMP_INPUT_MAX 128000 +#define MAX6621_TEMP_INPUT_MIN -128000 +#define MAX6621_TEMP_INPUT_MAX 127000 #define MAX6621_TEMP_ALERT_CHAN_SHIFT 1 #define MAX6621_TEMP_S0D0_REG 0x00 From acc52bd431e2d8698fae8d82a74ac45d79b62e0a Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 10 Aug 2026 11:28:39 +0700 Subject: [PATCH 108/110] hwmon: (max6621) fix negative temperature offset and crit readings max6621_read() reads the CONFIG2 offset and the critical alert threshold registers into a u32 and scales them without sign extension: /* offset */ *val = (regval >> MAX6621_REG_TEMP_SHIFT) * 1000L; /* crit */ *val = regval * 1000L; Both attributes are writable and their write paths clamp to a negative minimum and encode negative values, so a value written as negative is read back as a large positive number. For example, writing a -10 degrees C offset stores max6621_temp_mc2reg(-10000) = (-10 << 6) = 0xfd80; the read then computes 0xfd80 >> 6 = 1014 -> 1014000 instead of -10000. Cast the register value to s16 before scaling so the read preserves the sign the write path encodes. The temperature input path already uses an s8 intermediate and is left unchanged. Fixes: 92b64580f14b ("hwmon: (max6621) Add support for Maxim MAX6621 temperature sensor") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://lore.kernel.org/r/ad0baddbd6163cf73545c8e9273258136718585c.1786334038.git.congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/max6621.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/max6621.c b/drivers/hwmon/max6621.c index 16a3a61d2532..1a644d787143 100644 --- a/drivers/hwmon/max6621.c +++ b/drivers/hwmon/max6621.c @@ -239,7 +239,7 @@ max6621_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, if (ret) return ret; - *val = (regval >> MAX6621_REG_TEMP_SHIFT) * + *val = ((s16)regval >> MAX6621_REG_TEMP_SHIFT) * 1000L; break; @@ -254,7 +254,7 @@ max6621_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, if (ret) return ret; - *val = regval * 1000L; + *val = (s16)regval * 1000L; break; case hwmon_temp_crit_alarm: From 0cd8450c257faa0cece0e0c43d3b55d1a389acc7 Mon Sep 17 00:00:00 2001 From: Szymon Wilczek Date: Mon, 10 Aug 2026 21:23:44 +0200 Subject: [PATCH 109/110] hwmon: (coretemp) Fix core_data leak on CPUs without PTS pdata->core_data is allocated in init_temp_data() when the first core temp_data of a package is created, but it is only released from destroy_temp_data(), and only in the branch that handles the package temp_data. Package temp_data is created solely when the CPU supports X86_FEATURE_PTS. On a CPU without it, coretemp_cpu_online() never calls coretemp_add_core() with pkg_flag set, so pdata->pkg_data stays NULL. coretemp_cpu_offline() then skips the removal of the package interface, destroy_temp_data() is never called for package data, and the array is still allocated when coretemp_device_remove() frees the platform data that pointed at it. Release the array in coretemp_device_remove(). destroy_temp_data() sets pdata->core_data to NULL when it frees it, so the added kfree() is a no-op on CPUs that do have PTS. Tested on an Intel Core i5-1135G7. The driver was instrumented to log every allocation and release of pdata->core_data, and the PTS check in coretemp_cpu_online() was patched out to emulate a CPU without package thermal support. Without this change the array was allocated and never released, and coretemp_device_remove() still saw a non-NULL pointer. With it the array is released and the pointer accounting balances. On an unmodified build the release still happens via the package temp_data and the added kfree() sees NULL, with no slab warnings over repeated module load and unload cycles. Fixes: 1a793caf6f69 ("hwmon: (coretemp) Use dynamic allocated memory for core temp_data") Signed-off-by: Szymon Wilczek Link: https://lore.kernel.org/r/20260810192344.3733721-1-swilczek.lx@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index d47ba64788e1..5095eb057680 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -650,6 +650,7 @@ static void coretemp_device_remove(int zoneid) struct platform_data *pdata = platform_get_drvdata(pdev); ida_destroy(&pdata->ida); + kfree(pdata->core_data); kfree(pdata); platform_device_unregister(pdev); } From 75f2c0b3690702c90863c2e138cb5520670845ea Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Thu, 13 Aug 2026 11:40:07 +0300 Subject: [PATCH 110/110] hwmon: (emc1403) Drop hysteresis for low limit temperature Remove the hysteresis for low temperature limit, in hardware the hysteresis is applied only to the maxim limit and the critical limit temperature. Fixes: 54392ce4446e3 ("hwmon: (emc1403) Add support for min_hyst attributes") Signed-off-by: Marius Cristea Link: https://lore.kernel.org/r/20260813-emc1403_remove_min_hyst-v1-1-43a0d05d9f49@microchip.com [groeck: Updated subject] Signed-off-by: Guenter Roeck --- Documentation/hwmon/emc1403.rst | 8 ++++---- drivers/hwmon/emc1403.c | 27 +++++++++++---------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Documentation/hwmon/emc1403.rst b/Documentation/hwmon/emc1403.rst index 77060d515323..ce258f0fccc6 100644 --- a/Documentation/hwmon/emc1403.rst +++ b/Documentation/hwmon/emc1403.rst @@ -71,10 +71,10 @@ and EMC14x8 support eight sensors (one internal, seven external). The chips implement three limits for each sensor: low (tempX_min), high (tempX_max) and critical (tempX_crit.) The chips also implement an -hysteresis mechanism which applies to all limits. The relative difference -is stored in a single register on the chip, which means that the relative -difference between the limit and its hysteresis is always the same for -all three limits. +hysteresis mechanism which applies to high and critical limits. The relative +difference is stored in a single register on the chip, which means that the +relative difference between the limit and its hysteresis is always the same +for high and critical limits. This implementation detail implies the following: diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index a0f8d29eb6c7..d737505acd9d 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -306,10 +306,9 @@ static int emc1403_get_hyst(struct thermal_data *data, int channel, ret = regmap_read(data->regmap, 0x21, &hyst); if (ret < 0) return ret; - if (map == temp_min) - *val = limit + hyst * 1000; - else - *val = limit - hyst * 1000; + + *val = limit - hyst * 1000; + return 0; } @@ -325,9 +324,6 @@ static int emc1403_temp_read(struct thermal_data *data, u32 attr, int channel, l case hwmon_temp_input: ret = emc1403_get_temp(data, channel, ema1403_temp_map[attr], val); break; - case hwmon_temp_min_hyst: - ret = emc1403_get_hyst(data, channel, temp_min, val); - break; case hwmon_temp_max_hyst: ret = emc1403_get_hyst(data, channel, temp_max, val); break; @@ -549,7 +545,6 @@ static umode_t emc1403_temp_is_visible(const void *_data, u32 attr, int channel) case hwmon_temp_max_alarm: case hwmon_temp_crit_alarm: case hwmon_temp_fault: - case hwmon_temp_min_hyst: case hwmon_temp_max_hyst: return 0444; case hwmon_temp_min: @@ -592,35 +587,35 @@ static const struct hwmon_channel_info * const emc1403_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT ),