From f2053eea39a4b310ee502b388a8762c77f02469e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 31 Mar 2025 10:06:00 +0300 Subject: [PATCH 01/48] hwmon: (qnap-mcu) Remove (explicitly) unused header The fwnode.h is not supposed to be used by the drivers as it has the definitions for the core parts for different device property provider implementations. Drop it. Note, that fwnode API for drivers is provided in property.h which is included here. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250331070600.3985850-1-andriy.shevchenko@linux.intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/qnap-mcu-hwmon.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hwmon/qnap-mcu-hwmon.c b/drivers/hwmon/qnap-mcu-hwmon.c index 29057514739c..e86e64c4d391 100644 --- a/drivers/hwmon/qnap-mcu-hwmon.c +++ b/drivers/hwmon/qnap-mcu-hwmon.c @@ -6,7 +6,6 @@ * Copyright (C) 2024 Heiko Stuebner */ -#include #include #include #include From ce6642211888805cb4389157c3fd2b513b3cfccd Mon Sep 17 00:00:00 2001 From: Francesco Dolcini Date: Wed, 2 Apr 2025 12:21:45 +0200 Subject: [PATCH 02/48] dt-bindings: hwmon: amc6821: add fan and PWM output Add properties to describe the fan and the PWM controller output. Link: https://www.ti.com/lit/gpn/amc6821 Signed-off-by: Francesco Dolcini Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250402102146.65406-2-francesco@dolcini.it Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ti,amc6821.yaml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/ti,amc6821.yaml b/Documentation/devicetree/bindings/hwmon/ti,amc6821.yaml index 5d33f1a23d03..9ca7356760a7 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,amc6821.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,amc6821.yaml @@ -28,6 +28,17 @@ properties: i2c-mux: type: object + fan: + $ref: fan-common.yaml# + unevaluatedProperties: false + + "#pwm-cells": + const: 2 + description: | + Number of cells in a PWM specifier. + - cell 0: PWM period in nanoseconds + - cell 1: PWM polarity: 0 or PWM_POLARITY_INVERTED + required: - compatible - reg @@ -50,9 +61,14 @@ examples: #address-cells = <1>; #size-cells = <0>; - fan@18 { + fan_controller: fan@18 { compatible = "ti,amc6821"; reg = <0x18>; + #pwm-cells = <2>; + + fan { + pwms = <&fan_controller 40000 0>; + }; }; }; From cd17587272e28411b5ed1de37f84d106470824a9 Mon Sep 17 00:00:00 2001 From: Francesco Dolcini Date: Wed, 2 Apr 2025 12:21:46 +0200 Subject: [PATCH 03/48] hwmon: (amc6821) Add PWM polarity configuration with OF Add support to configure the PWM-Out pin polarity based on the device tree. The binding would allow also to configure the PWM period, this is currently not implemented by the driver. The driver has a module option to set the PWM polarity (normal=0, inverted=1), when specified it always takes the precedence over the DT. Signed-off-by: Francesco Dolcini Link: https://lore.kernel.org/r/20250402102146.65406-3-francesco@dolcini.it Signed-off-by: Guenter Roeck --- drivers/hwmon/amc6821.c | 50 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index 1e3c6acd8974..13a789cc85d2 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -23,9 +23,12 @@ #include #include #include +#include #include #include +#include + /* * Addresses to scan. */ @@ -37,7 +40,7 @@ static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, * Insmod parameters */ -static int pwminv; /*Inverted PWM output. */ +static int pwminv = -1; /*Inverted PWM output. */ module_param(pwminv, int, 0444); static int init = 1; /*Power-on initialization.*/ @@ -845,9 +848,43 @@ static int amc6821_detect(struct i2c_client *client, struct i2c_board_info *info return 0; } -static int amc6821_init_client(struct amc6821_data *data) +static enum pwm_polarity amc6821_pwm_polarity(struct i2c_client *client) +{ + enum pwm_polarity polarity = PWM_POLARITY_NORMAL; + struct of_phandle_args args; + struct device_node *fan_np; + + /* + * For backward compatibility, the pwminv module parameter takes + * always the precedence over any other device description + */ + if (pwminv == 0) + return PWM_POLARITY_NORMAL; + if (pwminv > 0) + return PWM_POLARITY_INVERSED; + + fan_np = of_get_child_by_name(client->dev.of_node, "fan"); + if (!fan_np) + return PWM_POLARITY_NORMAL; + + if (of_parse_phandle_with_args(fan_np, "pwms", "#pwm-cells", 0, &args)) + goto out; + of_node_put(args.np); + + if (args.args_count != 2) + goto out; + + if (args.args[1] & PWM_POLARITY_INVERTED) + polarity = PWM_POLARITY_INVERSED; +out: + of_node_put(fan_np); + return polarity; +} + +static int amc6821_init_client(struct i2c_client *client, struct amc6821_data *data) { struct regmap *regmap = data->regmap; + u32 regval; int err; if (init) { @@ -864,11 +901,14 @@ static int amc6821_init_client(struct amc6821_data *data) if (err) return err; + regval = AMC6821_CONF1_START; + if (amc6821_pwm_polarity(client) == PWM_POLARITY_INVERSED) + regval |= AMC6821_CONF1_PWMINV; + err = regmap_update_bits(regmap, AMC6821_REG_CONF1, AMC6821_CONF1_THERMOVIE | AMC6821_CONF1_FANIE | AMC6821_CONF1_START | AMC6821_CONF1_PWMINV, - AMC6821_CONF1_START | - (pwminv ? AMC6821_CONF1_PWMINV : 0)); + regval); if (err) return err; } @@ -916,7 +956,7 @@ static int amc6821_probe(struct i2c_client *client) "Failed to initialize regmap\n"); data->regmap = regmap; - err = amc6821_init_client(data); + err = amc6821_init_client(client, data); if (err) return err; From 2c183963fb5fdd849cda66ddf2d93d88d2296a75 Mon Sep 17 00:00:00 2001 From: David Hows Date: Thu, 3 Apr 2025 09:08:09 +1100 Subject: [PATCH 04/48] hwmon: (k10temp) Add support for Zen5 Ryzen Desktop Add support for retrieving CCD temperatures on Zen5 (Granite Ridge) Desktop CPUs. Signed-off-by: David Hows Link: https://lore.kernel.org/r/Z-21SQkZpuWiWK06@archibald.hows.id.au Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 3685906cc57c..b73a959e01cc 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -503,6 +503,13 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) k10temp_get_ccd_support(data, 12); break; } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { + case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ + data->ccd_offset = 0x308; + k10temp_get_ccd_support(data, 8); + break; + } } for (i = 0; i < ARRAY_SIZE(tctl_offset_table); i++) { From 73e5b6b51f00f3c8a8e7531d4a8e211f5f8cfc02 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Mon, 7 Apr 2025 13:10:02 -0700 Subject: [PATCH 05/48] hwmon: (pmbus) Introduce page_change_delay We have some buggy pmbus devices that require a delay after performing a page change operation before trying to issue more commands to the device. This allows for a configurable delay after page changes, but not affecting other read or write operations. This makes a slight behavioral tweak to the existing delay logic, where it considers the longest of delays between operations, instead of always chosing the write delay over the access delay. Signed-off-by: William A. Kennington III Link: https://lore.kernel.org/r/20250407201002.1198092-1-william@wkennington.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 1 + drivers/hwmon/pmbus/pmbus_core.c | 69 ++++++++++++++++---------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index ddb19c9726d6..742dafc44390 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -482,6 +482,7 @@ struct pmbus_driver_info { */ int access_delay; /* in microseconds */ int write_delay; /* in microseconds */ + int page_change_delay; /* in microseconds */ }; /* Regulator ops */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index cfeba2e4c5c3..be6d05def115 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -32,6 +32,13 @@ #define PMBUS_ATTR_ALLOC_SIZE 32 #define PMBUS_NAME_SIZE 24 +/* + * The type of operation used for picking the delay between + * successive pmbus operations. + */ +#define PMBUS_OP_WRITE BIT(0) +#define PMBUS_OP_PAGE_CHANGE BIT(1) + static int wp = -1; module_param(wp, int, 0444); @@ -113,8 +120,8 @@ struct pmbus_data { int vout_low[PMBUS_PAGES]; /* voltage low margin */ int vout_high[PMBUS_PAGES]; /* voltage high margin */ - ktime_t write_time; /* Last SMBUS write timestamp */ - ktime_t access_time; /* Last SMBUS access timestamp */ + + ktime_t next_access_backoff; /* Wait until at least this time */ }; struct pmbus_debugfs_entry { @@ -169,32 +176,26 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); static void pmbus_wait(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); - const struct pmbus_driver_info *info = data->info; - s64 delta; + s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); - if (info->access_delay) { - delta = ktime_us_delta(ktime_get(), data->access_time); - - if (delta < info->access_delay) - fsleep(info->access_delay - delta); - } else if (info->write_delay) { - delta = ktime_us_delta(ktime_get(), data->write_time); - - if (delta < info->write_delay) - fsleep(info->write_delay - delta); - } + if (delay > 0) + fsleep(delay); } -/* Sets the last accessed timestamp for pmbus_wait */ -static void pmbus_update_ts(struct i2c_client *client, bool write_op) +/* Sets the last operation timestamp for pmbus_wait */ +static 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; - if (info->access_delay) - data->access_time = ktime_get(); - else if (info->write_delay && write_op) - data->write_time = ktime_get(); + if (op & PMBUS_OP_WRITE) + delay = max(delay, info->write_delay); + if (op & PMBUS_OP_PAGE_CHANGE) + delay = max(delay, info->page_change_delay); + + if (delay > 0) + data->next_access_backoff = ktime_add_us(ktime_get(), delay); } int pmbus_set_page(struct i2c_client *client, int page, int phase) @@ -209,13 +210,13 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase) data->info->pages > 1 && page != data->currpage) { pmbus_wait(client); rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); - pmbus_update_ts(client, true); + pmbus_update_ts(client, PMBUS_OP_WRITE | PMBUS_OP_PAGE_CHANGE); if (rv < 0) return rv; pmbus_wait(client); rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); - pmbus_update_ts(client, false); + pmbus_update_ts(client, 0); if (rv < 0) return rv; @@ -229,7 +230,7 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase) pmbus_wait(client); rv = i2c_smbus_write_byte_data(client, PMBUS_PHASE, phase); - pmbus_update_ts(client, true); + pmbus_update_ts(client, PMBUS_OP_WRITE); if (rv) return rv; } @@ -249,7 +250,7 @@ int pmbus_write_byte(struct i2c_client *client, int page, u8 value) pmbus_wait(client); rv = i2c_smbus_write_byte(client, value); - pmbus_update_ts(client, true); + pmbus_update_ts(client, PMBUS_OP_WRITE); return rv; } @@ -284,7 +285,7 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, pmbus_wait(client); rv = i2c_smbus_write_word_data(client, reg, word); - pmbus_update_ts(client, true); + pmbus_update_ts(client, PMBUS_OP_WRITE); return rv; } @@ -405,7 +406,7 @@ int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg) pmbus_wait(client); rv = i2c_smbus_read_word_data(client, reg); - pmbus_update_ts(client, false); + pmbus_update_ts(client, 0); return rv; } @@ -468,7 +469,7 @@ int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg) pmbus_wait(client); rv = i2c_smbus_read_byte_data(client, reg); - pmbus_update_ts(client, false); + pmbus_update_ts(client, 0); return rv; } @@ -484,7 +485,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value) pmbus_wait(client); rv = i2c_smbus_write_byte_data(client, reg, value); - pmbus_update_ts(client, true); + pmbus_update_ts(client, PMBUS_OP_WRITE); return rv; } @@ -520,7 +521,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, pmbus_wait(client); rv = i2c_smbus_read_block_data(client, reg, data_buf); - pmbus_update_ts(client, false); + pmbus_update_ts(client, 0); return rv; } @@ -2524,7 +2525,7 @@ static int pmbus_read_coefficients(struct i2c_client *client, rv = i2c_smbus_xfer(client->adapter, client->addr, client->flags, I2C_SMBUS_WRITE, PMBUS_COEFFICIENTS, I2C_SMBUS_BLOCK_PROC_CALL, &data); - pmbus_update_ts(client, true); + pmbus_update_ts(client, PMBUS_OP_WRITE); if (rv < 0) return rv; @@ -2728,7 +2729,7 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, if (!(data->flags & PMBUS_NO_CAPABILITY)) { pmbus_wait(client); ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); - pmbus_update_ts(client, false); + pmbus_update_ts(client, 0); if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) @@ -2744,13 +2745,13 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, data->read_status = pmbus_read_status_word; pmbus_wait(client); ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD); - pmbus_update_ts(client, false); + pmbus_update_ts(client, 0); if (ret < 0 || ret == 0xffff) { data->read_status = pmbus_read_status_byte; pmbus_wait(client); ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); - pmbus_update_ts(client, false); + pmbus_update_ts(client, 0); if (ret < 0 || ret == 0xff) { dev_err(dev, "PMBus status register not found\n"); From e894b6442a9692dd55f01c4edccee459163665a4 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:16:16 +0200 Subject: [PATCH 06/48] hwmon: (ltc2992) Use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-hwmon-v1-1-1fa38f34dc07@linaro.org Reviewed-by: Linus Walleij [groeck: Fixed multi-line alignment issue] Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc2992.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index 541fa09dc6e7..a07e2eb93c71 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -256,33 +256,38 @@ static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask return 0; } -static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +static int ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct ltc2992_state *st = gpiochip_get_data(chip); unsigned long gpio_ctrl; - int reg; + int reg, ret; mutex_lock(&st->gpio_mutex); reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1); if (reg < 0) { mutex_unlock(&st->gpio_mutex); - return; + return reg; } gpio_ctrl = reg; assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value); - ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl); + ret = ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, + gpio_ctrl); mutex_unlock(&st->gpio_mutex); + + return ret; } -static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, - unsigned long *bits) +static int ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) { struct ltc2992_state *st = gpiochip_get_data(chip); unsigned long gpio_ctrl_io = 0; unsigned long gpio_ctrl = 0; unsigned int gpio_nr; + int ret; for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) { if (gpio_nr < 3) @@ -293,9 +298,14 @@ static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mas } mutex_lock(&st->gpio_mutex); - ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io); - ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl); + ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io); + if (ret) + goto out; + + ret = ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl); +out: mutex_unlock(&st->gpio_mutex); + return ret; } static int ltc2992_config_gpio(struct ltc2992_state *st) @@ -329,8 +339,8 @@ static int ltc2992_config_gpio(struct ltc2992_state *st) st->gc.ngpio = ARRAY_SIZE(st->gpio_names); st->gc.get = ltc2992_gpio_get; st->gc.get_multiple = ltc2992_gpio_get_multiple; - st->gc.set = ltc2992_gpio_set; - st->gc.set_multiple = ltc2992_gpio_set_multiple; + st->gc.set_rv = ltc2992_gpio_set; + st->gc.set_multiple_rv = ltc2992_gpio_set_multiple; ret = devm_gpiochip_add_data(&st->client->dev, &st->gc, st); if (ret) From 9c47e45de1f7633bd3bbf54bf36cd9a385db2232 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:16:17 +0200 Subject: [PATCH 07/48] hwmon: (pmbus/ucd9000) Use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-hwmon-v1-2-1fa38f34dc07@linaro.org Reviewed-by: Linus Walleij Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ucd9000.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index 9b0eadc81a2e..2bc8cccb01fd 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -212,8 +212,8 @@ static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) return !!(ret & UCD9000_GPIO_CONFIG_STATUS); } -static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, - int value) +static int ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) { struct i2c_client *client = gpiochip_get_data(gc); int ret; @@ -222,17 +222,17 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, if (ret < 0) { dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", offset, ret); - return; + return ret; } if (value) { if (ret & UCD9000_GPIO_CONFIG_STATUS) - return; + return 0; ret |= UCD9000_GPIO_CONFIG_STATUS; } else { if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) - return; + return 0; ret &= ~UCD9000_GPIO_CONFIG_STATUS; } @@ -244,7 +244,7 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, if (ret < 0) { dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", offset, ret); - return; + return ret; } ret &= ~UCD9000_GPIO_CONFIG_ENABLE; @@ -253,6 +253,8 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, if (ret < 0) dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", offset, ret); + + return ret; } static int ucd9000_gpio_get_direction(struct gpio_chip *gc, @@ -362,7 +364,7 @@ static void ucd9000_probe_gpio(struct i2c_client *client, data->gpio.direction_input = ucd9000_gpio_direction_input; data->gpio.direction_output = ucd9000_gpio_direction_output; data->gpio.get = ucd9000_gpio_get; - data->gpio.set = ucd9000_gpio_set; + data->gpio.set_rv = ucd9000_gpio_set; data->gpio.can_sleep = true; data->gpio.base = -1; data->gpio.parent = &client->dev; From 19932f844f3f51646f762f3eac4744ec3a405064 Mon Sep 17 00:00:00 2001 From: Alexis Czezar Torreno Date: Mon, 7 Apr 2025 11:47:24 +0800 Subject: [PATCH 08/48] hwmon: (pmbus/max34440) Fix support for max34451 The max344** family has an issue with some PMBUS address being switched. This includes max34451 however version MAX34451-NA6 and later has this issue fixed and this commit supports that update. Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20250407-dev_adpm12160-v3-1-9cd3095445c8@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max34440.c | 48 +++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index c9dda33831ff..d6d556b01385 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -34,16 +34,21 @@ enum chips { max34440, max34441, max34446, max34451, max34460, max34461 }; /* * The whole max344* family have IOUT_OC_WARN_LIMIT and IOUT_OC_FAULT_LIMIT * swapped from the standard pmbus spec addresses. + * For max34451, version MAX34451ETNA6+ and later has this issue fixed. */ #define MAX34440_IOUT_OC_WARN_LIMIT 0x46 #define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A +#define MAX34451ETNA6_MFR_REV 0x0012 + #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f struct max34440_data { int id; struct pmbus_driver_info info; + u8 iout_oc_warn_limit; + u8 iout_oc_fault_limit; }; #define to_max34440_data(x) container_of(x, struct max34440_data, info) @@ -60,11 +65,11 @@ static int max34440_read_word_data(struct i2c_client *client, int page, switch (reg) { case PMBUS_IOUT_OC_FAULT_LIMIT: ret = pmbus_read_word_data(client, page, phase, - MAX34440_IOUT_OC_FAULT_LIMIT); + data->iout_oc_fault_limit); break; case PMBUS_IOUT_OC_WARN_LIMIT: ret = pmbus_read_word_data(client, page, phase, - MAX34440_IOUT_OC_WARN_LIMIT); + data->iout_oc_warn_limit); break; case PMBUS_VIRT_READ_VOUT_MIN: ret = pmbus_read_word_data(client, page, phase, @@ -133,11 +138,11 @@ static int max34440_write_word_data(struct i2c_client *client, int page, switch (reg) { case PMBUS_IOUT_OC_FAULT_LIMIT: - ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_FAULT_LIMIT, + ret = pmbus_write_word_data(client, page, data->iout_oc_fault_limit, word); break; case PMBUS_IOUT_OC_WARN_LIMIT: - ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_WARN_LIMIT, + ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit, word); break; case PMBUS_VIRT_RESET_POUT_HISTORY: @@ -235,6 +240,25 @@ static int max34451_set_supported_funcs(struct i2c_client *client, */ int page, rv; + bool max34451_na6 = false; + + rv = i2c_smbus_read_word_data(client, PMBUS_MFR_REVISION); + if (rv < 0) + return rv; + + if (rv >= MAX34451ETNA6_MFR_REV) { + 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; + data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; + } for (page = 0; page < 16; page++) { rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); @@ -251,16 +275,30 @@ static int max34451_set_supported_funcs(struct i2c_client *client, case 0x20: data->info.func[page] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + if (max34451_na6) + data->info.func[page] |= PMBUS_HAVE_VIN | + PMBUS_HAVE_STATUS_INPUT; break; case 0x21: data->info.func[page] = PMBUS_HAVE_VOUT; + + if (max34451_na6) + data->info.func[page] |= PMBUS_HAVE_VIN; break; case 0x22: data->info.func[page] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + + if (max34451_na6) + data->info.func[page] |= PMBUS_HAVE_IIN | + PMBUS_HAVE_STATUS_INPUT; break; case 0x23: data->info.func[page] = PMBUS_HAVE_IOUT; + + if (max34451_na6) + data->info.func[page] |= PMBUS_HAVE_IIN; break; default: break; @@ -494,6 +532,8 @@ static int max34440_probe(struct i2c_client *client) return -ENOMEM; data->id = i2c_match_id(max34440_id, client)->driver_data; data->info = max34440_info[data->id]; + data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; if (data->id == max34451) { rv = max34451_set_supported_funcs(client, data); From 629cf8f6c23a987201558ffcca5590a60ae3959d Mon Sep 17 00:00:00 2001 From: Alexis Czezar Torreno Date: Mon, 7 Apr 2025 11:47:25 +0800 Subject: [PATCH 09/48] hwmon: (pmbus/max34440) Add support for ADPM12160 ADPM12160 is a quarter brick DC/DC Power Module. It is a high power non-isolated converter capable of delivering a fully regulated 12V, with continuous power level of 1600W with peak power at 2400W for a limited time. Uses PMBus Configuration. Signed-off-by: Alexis Czezar Torreno Link: https://lore.kernel.org/r/20250407-dev_adpm12160-v3-2-9cd3095445c8@analog.com [groeck: The chip is "ADPM12160"] Signed-off-by: Guenter Roeck --- Documentation/hwmon/max34440.rst | 30 ++++++++++++----- drivers/hwmon/pmbus/max34440.c | 55 ++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/Documentation/hwmon/max34440.rst b/Documentation/hwmon/max34440.rst index 162d289f0814..8591a7152ce5 100644 --- a/Documentation/hwmon/max34440.rst +++ b/Documentation/hwmon/max34440.rst @@ -3,6 +3,14 @@ Kernel driver max34440 Supported chips: + * ADI ADPM12160 + + Prefixes: 'adpm12160' + + Addresses scanned: - + + Datasheet: - + * Maxim MAX34440 Prefixes: 'max34440' @@ -67,13 +75,14 @@ Author: Guenter Roeck Description ----------- -This driver supports 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. -It also supports the MAX34451, MAX34460, and MAX34461 PMBus Voltage Monitor & -Sequencers. 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. +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. 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 ADPM1260 +also monitors both input and output of voltage and current. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers. @@ -128,7 +137,10 @@ in[1-6]_highest Historical maximum voltage. in[1-6]_reset_history Write any value to reset history. ======================= ======================================================= -.. note:: MAX34446 only supports in[1-4]. +.. note:: + + - MAX34446 only supports in[1-4]. + - ADPM12160 only supports in[1-2]. Label is "vin1" and "vout1" respectively. Curr ~~~~ @@ -150,6 +162,7 @@ curr[1-6]_reset_history Write any value to reset history. - in6 and curr6 attributes only exist for MAX34440. - MAX34446 only supports curr[1-4]. + - For ADPM12160, curr[1] is "iin1" and curr[2-6] are "iout[1-5]. Power ~~~~~ @@ -185,6 +198,7 @@ temp[1-8]_reset_history Write any value to reset history. .. note:: - temp7 and temp8 attributes only exist for MAX34440. - MAX34446 only supports temp[1-3]. + - ADPM12160 only supports temp[1]. .. note:: diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index d6d556b01385..3d70c454efb5 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -14,7 +14,15 @@ #include #include "pmbus.h" -enum chips { max34440, max34441, max34446, max34451, max34460, max34461 }; +enum chips { + adpm12160, + max34440, + max34441, + max34446, + max34451, + max34460, + max34461, +}; #define MAX34440_MFR_VOUT_PEAK 0xd4 #define MAX34440_MFR_IOUT_PEAK 0xd5 @@ -80,7 +88,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, MAX34440_MFR_VOUT_PEAK); break; case PMBUS_VIRT_READ_IOUT_AVG: - if (data->id != max34446 && data->id != max34451) + if (data->id != max34446 && data->id != max34451 && + data->id != adpm12160) return -ENXIO; ret = pmbus_read_word_data(client, page, phase, MAX34446_MFR_IOUT_AVG); @@ -164,7 +173,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, case PMBUS_VIRT_RESET_IOUT_HISTORY: ret = pmbus_write_word_data(client, page, MAX34440_MFR_IOUT_PEAK, 0); - if (!ret && (data->id == max34446 || data->id == max34451)) + if (!ret && (data->id == max34446 || data->id == max34451 || + data->id == adpm12160)) ret = pmbus_write_word_data(client, page, MAX34446_MFR_IOUT_AVG, 0); @@ -309,6 +319,41 @@ static int max34451_set_supported_funcs(struct i2c_client *client, } static struct pmbus_driver_info max34440_info[] = { + [adpm12160] = { + .pages = 19, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .m[PSC_CURRENT_IN] = 1, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 2, + .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, + /* absent func below [18] are not for monitoring */ + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[6] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[7] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[8] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[9] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT, + .func[10] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, [max34440] = { .pages = 14, .format[PSC_VOLTAGE_IN] = direct, @@ -539,12 +584,16 @@ static int max34440_probe(struct i2c_client *client) rv = max34451_set_supported_funcs(client, data); if (rv) return rv; + } else if (data->id == adpm12160) { + data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; } return pmbus_do_probe(client, &data->info); } static const struct i2c_device_id max34440_id[] = { + {"adpm12160", adpm12160}, {"max34440", max34440}, {"max34441", max34441}, {"max34446", max34446}, From 0b3c04c81804197bf0025f3281e4463152f04bf1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 22 Mar 2025 07:26:02 -0700 Subject: [PATCH 10/48] hwmon: (pmbus) Do not set regulators_node for single-channel chips Single-channel regulators do not need and should not have a "regulators" node. We can not entirely remove it due to existing bindings. To solve the problem for new drivers, provide additional macros PMBUS_REGULATOR_ONE_NODE and PMBUS_REGULATOR_STEP_ONE_NODE and convert existing drivers to use those macros. The exception is the ir38064 driver because its devicetree files and its description do not require or use the nested regulators node. Modify PMBUS_REGULATOR_STEP_ONE and PMBUS_REGULATOR_ONE to set the regulators_node pointer to NULL. Cc: Cedricjustine.Encarnacion@analog.com Signed-off-by: Guenter Roeck Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250322142602.560042-1-linux@roeck-us.net Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/lm25066.c | 2 +- drivers/hwmon/pmbus/mpq7932.c | 4 ++-- drivers/hwmon/pmbus/pmbus.h | 18 +++++++++++++++--- drivers/hwmon/pmbus/tda38640.c | 2 +- drivers/hwmon/pmbus/tps25990.c | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 40b0dda32ea6..dd7275a67a0a 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -437,7 +437,7 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, #if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) static const struct regulator_desc lm25066_reg_desc[] = { - PMBUS_REGULATOR_ONE("vout"), + PMBUS_REGULATOR_ONE_NODE("vout"), }; #endif diff --git a/drivers/hwmon/pmbus/mpq7932.c b/drivers/hwmon/pmbus/mpq7932.c index c1e2d0cb2fd0..8f10e37a7a76 100644 --- a/drivers/hwmon/pmbus/mpq7932.c +++ b/drivers/hwmon/pmbus/mpq7932.c @@ -51,8 +51,8 @@ static const struct regulator_desc mpq7932_regulators_desc[] = { }; static const struct regulator_desc mpq7932_regulators_desc_one[] = { - PMBUS_REGULATOR_STEP_ONE("buck", MPQ7932_N_VOLTAGES, - MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN), + PMBUS_REGULATOR_STEP_ONE_NODE("buck", MPQ7932_N_VOLTAGES, + MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN), }; #endif diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 742dafc44390..d2e9bfb5320f 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -509,11 +509,11 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev, #define PMBUS_REGULATOR(_name, _id) PMBUS_REGULATOR_STEP(_name, _id, 0, 0, 0) -#define PMBUS_REGULATOR_STEP_ONE(_name, _voltages, _step, _min_uV) \ +#define __PMBUS_REGULATOR_STEP_ONE(_name, _node, _voltages, _step, _min_uV) \ { \ .name = (_name), \ .of_match = of_match_ptr(_name), \ - .regulators_node = of_match_ptr("regulators"), \ + .regulators_node = of_match_ptr(_node), \ .ops = &pmbus_regulator_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -523,7 +523,19 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev, .init_cb = pmbus_regulator_init_cb, \ } -#define PMBUS_REGULATOR_ONE(_name) PMBUS_REGULATOR_STEP_ONE(_name, 0, 0, 0) +/* + * _NODE macros are defined for historic reasons and MUST NOT be used in new + * drivers. + */ +#define PMBUS_REGULATOR_STEP_ONE_NODE(_name, _voltages, _step, _min_uV) \ + __PMBUS_REGULATOR_STEP_ONE(_name, "regulators", _voltages, _step, _min_uV) + +#define PMBUS_REGULATOR_ONE_NODE(_name) PMBUS_REGULATOR_STEP_ONE_NODE(_name, 0, 0, 0) + +#define PMBUS_REGULATOR_STEP_ONE(_name, _voltages, _step, _min_uV) \ + __PMBUS_REGULATOR_STEP_ONE(_name, NULL, _voltages, _step, _min_uV) + +#define PMBUS_REGULATOR_ONE(_name) PMBUS_REGULATOR_STEP_ONE(_name, 0, 0, 0) /* Function declarations */ diff --git a/drivers/hwmon/pmbus/tda38640.c b/drivers/hwmon/pmbus/tda38640.c index 07fe58c24485..d902d39f49f4 100644 --- a/drivers/hwmon/pmbus/tda38640.c +++ b/drivers/hwmon/pmbus/tda38640.c @@ -15,7 +15,7 @@ #include "pmbus.h" static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = { - PMBUS_REGULATOR_ONE("vout"), + PMBUS_REGULATOR_ONE_NODE("vout"), }; struct tda38640_data { diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c index 0d2655e69549..c13edd7e1abf 100644 --- a/drivers/hwmon/pmbus/tps25990.c +++ b/drivers/hwmon/pmbus/tps25990.c @@ -333,7 +333,7 @@ static int tps25990_write_byte_data(struct i2c_client *client, #if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR) static const struct regulator_desc tps25990_reg_desc[] = { - PMBUS_REGULATOR_ONE("vout"), + PMBUS_REGULATOR_ONE_NODE("vout"), }; #endif From 6de6868df18728790eb4ffe764b49f356fea7397 Mon Sep 17 00:00:00 2001 From: Naresh Solanki Date: Fri, 4 Apr 2025 17:26:45 +0530 Subject: [PATCH 11/48] hwmon: (max6639) Allow setting target RPM Currently, during startup, the fan is set to its maximum RPM by default, which may not be suitable for all use cases. This patch introduces support for specifying a target RPM via the Device Tree property "target-rpm". Changes: - Added `target_rpm` field to `max6639_data` structure to store the target RPM for each fan channel. - Modified `max6639_probe_child_from_dt()` to read the `"target-rpm"` property from the Device Tree and set `target_rpm` accordingly. - Updated `max6639_init_client()` to use `target_rpm` to compute the initial PWM duty cycle instead of defaulting to full speed (120/120). Behavior: - If `"target-rpm"` is specified, the fan speed is set accordingly. - If `"target-rpm"` is not specified, the previous behavior (full speed at startup) is retained. This allows better control over fan speed during system initialization. Signed-off-by: Naresh Solanki Link: https://lore.kernel.org/r/20250404115646.2000563-1-you@example.com Signed-off-by: Guenter Roeck --- drivers/hwmon/max6639.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c index 32b4d54b2076..a06346496e1d 100644 --- a/drivers/hwmon/max6639.c +++ b/drivers/hwmon/max6639.c @@ -80,6 +80,7 @@ struct max6639_data { /* Register values initialized only once */ u8 ppr[MAX6639_NUM_CHANNELS]; /* Pulses per rotation 0..3 for 1..4 ppr */ u8 rpm_range[MAX6639_NUM_CHANNELS]; /* Index in above rpm_ranges table */ + u32 target_rpm[MAX6639_NUM_CHANNELS]; /* Optional regulator for FAN supply */ struct regulator *reg; @@ -563,6 +564,10 @@ static int max6639_probe_child_from_dt(struct i2c_client *client, if (!err) data->rpm_range[i] = rpm_range_to_reg(val); + err = of_property_read_u32(child, "target-rpm", &val); + if (!err) + data->target_rpm[i] = val; + return 0; } @@ -573,6 +578,7 @@ static int max6639_init_client(struct i2c_client *client, const struct device_node *np = dev->of_node; struct device_node *child; int i, err; + u8 target_duty; /* Reset chip to default values, see below for GCONFIG setup */ err = regmap_write(data->regmap, MAX6639_REG_GCONFIG, MAX6639_GCONFIG_POR); @@ -586,6 +592,8 @@ static int max6639_init_client(struct i2c_client *client, /* default: 4000 RPM */ data->rpm_range[0] = 1; data->rpm_range[1] = 1; + data->target_rpm[0] = 4000; + data->target_rpm[1] = 4000; for_each_child_of_node(np, child) { if (strcmp(child->name, "fan")) @@ -639,8 +647,12 @@ static int max6639_init_client(struct i2c_client *client, if (err) return err; - /* PWM 120/120 (i.e. 100%) */ - err = regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(i), 120); + /* Set PWM based on target RPM if specified */ + if (data->target_rpm[i] > rpm_ranges[data->rpm_range[i]]) + data->target_rpm[i] = rpm_ranges[data->rpm_range[i]]; + + target_duty = 120 * data->target_rpm[i] / rpm_ranges[data->rpm_range[i]]; + err = regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(i), target_duty); if (err) return err; } From ab2f6bffe7311327d5f6432e413c704f395b93c3 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Mon, 7 Apr 2025 18:10:06 -0700 Subject: [PATCH 12/48] hwmon: (max34451) Work around lost page When requesting new pages from the max34451 we sometimes see that the firmware responds with stale or bad data to reads that happen immediately after a page change. This is due to a lack of clock stretching after page changing on the device side when it needs more time to complete the operation. To remedy this, the manufacturer recommends we wait 50us until the firmware should be ready with the new page. Signed-off-by: William A. Kennington III Link: https://lore.kernel.org/r/20250408011006.1314622-1-william@wkennington.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max34440.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index 3d70c454efb5..56834d26f8ef 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "pmbus.h" enum chips { @@ -24,6 +25,14 @@ enum chips { max34461, }; +/* + * Firmware is sometimes not ready if we try and read the + * data from the page immediately after setting. Maxim + * recommends 50us delay due to the chip failing to clock + * stretch long enough here. + */ +#define MAX34440_PAGE_CHANGE_DELAY 50 + #define MAX34440_MFR_VOUT_PEAK 0xd4 #define MAX34440_MFR_IOUT_PEAK 0xd5 #define MAX34440_MFR_TEMPERATURE_PEAK 0xd6 @@ -272,6 +281,7 @@ static int max34451_set_supported_funcs(struct i2c_client *client, for (page = 0; page < 16; page++) { rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + fsleep(MAX34440_PAGE_CHANGE_DELAY); if (rv < 0) return rv; @@ -395,6 +405,7 @@ static struct pmbus_driver_info max34440_info[] = { .read_byte_data = max34440_read_byte_data, .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, [max34441] = { .pages = 12, @@ -438,6 +449,7 @@ static struct pmbus_driver_info max34440_info[] = { .read_byte_data = max34440_read_byte_data, .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, [max34446] = { .pages = 7, @@ -475,6 +487,7 @@ static struct pmbus_driver_info max34440_info[] = { .read_byte_data = max34440_read_byte_data, .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, [max34451] = { .pages = 21, @@ -498,6 +511,7 @@ static struct pmbus_driver_info max34440_info[] = { .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, [max34460] = { .pages = 18, @@ -528,6 +542,7 @@ static struct pmbus_driver_info max34440_info[] = { .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, [max34461] = { .pages = 23, @@ -563,6 +578,7 @@ static struct pmbus_driver_info max34440_info[] = { .func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, .read_word_data = max34440_read_word_data, .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, }; From 0bf08f9e358d33a8972cdb3d698079f5d768d7ed Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Tue, 8 Apr 2025 22:44:57 +0200 Subject: [PATCH 13/48] hwmon: (asus-ec-sensors) sort sensor definition arrays The arrays have to be sorted by the sensor register bank and index because this is what the sensor reading function expects. So sort them and leave a comment for future contributors. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250408204505.11412-1-eugene.shalygin@gmail.com [groeck: Fixed alignment of new multi-line comment] Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 006ced5ab6e6..7f2389db8923 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -169,7 +169,11 @@ enum board_family { family_intel_600_series }; -/* All the known sensors for ASUS EC controllers */ +/* + * All the known sensors for ASUS EC controllers. These arrays have to be sorted + * by the full ((bank << 8) + index) register index (see asus_ec_block_read() as + * to why). + */ static const struct ec_sensor_info sensors_family_amd_400[] = { [ec_sensor_temp_chipset] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), @@ -183,10 +187,10 @@ static const struct ec_sensor_info sensors_family_amd_400[] = { EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), [ec_sensor_in_cpu_core] = EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2), - [ec_sensor_fan_cpu_opt] = - EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc), [ec_sensor_fan_vrm_hs] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc), [ec_sensor_fan_chipset] = /* no chipset fans in this generation */ EC_SENSOR("Chipset", hwmon_fan, 0, 0x00, 0x00), @@ -194,10 +198,10 @@ static const struct ec_sensor_info sensors_family_amd_400[] = { EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xb4), [ec_sensor_curr_cpu] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), - [ec_sensor_temp_water_in] = - EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d), [ec_sensor_temp_water_out] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x0b), + [ec_sensor_temp_water_in] = + EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d), }; static const struct ec_sensor_info sensors_family_amd_500[] = { @@ -239,19 +243,20 @@ static const struct ec_sensor_info sensors_family_amd_500[] = { static const struct ec_sensor_info sensors_family_amd_600[] = { [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x30), - [ec_sensor_temp_cpu_package] = EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31), + [ec_sensor_temp_cpu_package] = + EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31), [ec_sensor_temp_mb] = EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x32), [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33), [ec_sensor_temp_t_sensor] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x36), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), [ec_sensor_temp_water_in] = EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), [ec_sensor_temp_water_out] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), - [ec_sensor_fan_cpu_opt] = - EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), }; static const struct ec_sensor_info sensors_family_intel_300[] = { From 9b116ba6c9eb9d3b33de0363fafdc5941116f029 Mon Sep 17 00:00:00 2001 From: Ciprian Marian Costea Date: Wed, 9 Apr 2025 10:45:29 +0300 Subject: [PATCH 14/48] hwmon: (ina2xx) make regulator 'vs' support optional According to the 'ti,ina2xx' binding, the 'vs-supply' property is optional. Use devm_regulator_get_enable_optional() to avoid a kernel warning message if the property is not provided. Co-developed-by: Florin Buica Tested-by: Enric Balletbo i Serra Signed-off-by: Florin Buica Signed-off-by: Ciprian Marian Costea Link: https://lore.kernel.org/r/20250409074529.2233733-1-ciprianmarian.costea@oss.nxp.com [groeck: Use standard multi-line comment] Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 345fe7db9de9..bc3c1f7314b3 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -959,8 +959,12 @@ static int ina2xx_probe(struct i2c_client *client) return PTR_ERR(data->regmap); } - ret = devm_regulator_get_enable(dev, "vs"); - if (ret) + /* + * Regulator core returns -ENODEV if the 'vs' is not available. + * Hence the check for -ENODEV return code is necessary. + */ + ret = devm_regulator_get_enable_optional(dev, "vs"); + if (ret < 0 && ret != -ENODEV) return dev_err_probe(dev, ret, "failed to enable vs regulator\n"); ret = ina2xx_init(dev, data); From 0d01110e6356e95320091f36e3d7ce92fa597d1f Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Wed, 9 Apr 2025 08:54:26 +0200 Subject: [PATCH 15/48] hwmon: (gpio-fan) Add regulator support FANs might be supplied by a regulator which needs to be enabled as well. This is implemented using runtime PM. Every time speed_index changes from 0 to non-zero and vise versa RPM is resumed or suspended. Intitial RPM state is determined by initial value of speed_index. Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20250409065430.1413439-1-alexander.stein@ew.tq-group.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 103 +++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 16 deletions(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index b779240328d5..516c34bb61c9 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include #include struct gpio_fan_speed { @@ -42,6 +45,7 @@ struct gpio_fan_data { bool pwm_enable; struct gpio_desc *alarm_gpio; struct work_struct alarm_work; + struct regulator *supply; }; /* @@ -125,13 +129,32 @@ static int __get_fan_ctrl(struct gpio_fan_data *fan_data) } /* Must be called with fan_data->lock held, except during initialization. */ -static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) +static int set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) { if (fan_data->speed_index == speed_index) - return; + return 0; + + if (fan_data->speed_index == 0 && speed_index > 0) { + int ret; + + ret = pm_runtime_resume_and_get(fan_data->dev); + if (ret < 0) + return ret; + } __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); + + if (fan_data->speed_index > 0 && speed_index == 0) { + int ret; + + ret = pm_runtime_put_sync(fan_data->dev); + if (ret < 0) + return ret; + } + fan_data->speed_index = speed_index; + + return 0; } static int get_fan_speed_index(struct gpio_fan_data *fan_data) @@ -176,7 +199,7 @@ static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr, struct gpio_fan_data *fan_data = dev_get_drvdata(dev); unsigned long pwm; int speed_index; - int ret = count; + int ret; if (kstrtoul(buf, 10, &pwm) || pwm > 255) return -EINVAL; @@ -189,12 +212,12 @@ static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr, } speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255); - set_fan_speed(fan_data, speed_index); + ret = set_fan_speed(fan_data, speed_index); exit_unlock: mutex_unlock(&fan_data->lock); - return ret; + return ret ? ret : count; } static ssize_t pwm1_enable_show(struct device *dev, @@ -211,6 +234,7 @@ static ssize_t pwm1_enable_store(struct device *dev, { struct gpio_fan_data *fan_data = dev_get_drvdata(dev); unsigned long val; + int ret = 0; if (kstrtoul(buf, 10, &val) || val > 1) return -EINVAL; @@ -224,11 +248,11 @@ static ssize_t pwm1_enable_store(struct device *dev, /* Disable manual control mode: set fan at full speed. */ if (val == 0) - set_fan_speed(fan_data, fan_data->num_speed - 1); + ret = set_fan_speed(fan_data, fan_data->num_speed - 1); mutex_unlock(&fan_data->lock); - return count; + return ret ? ret : count; } static ssize_t pwm1_mode_show(struct device *dev, @@ -279,7 +303,7 @@ static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, goto exit_unlock; } - set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm)); + ret = set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm)); exit_unlock: mutex_unlock(&fan_data->lock); @@ -386,6 +410,7 @@ static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { struct gpio_fan_data *fan_data = cdev->devdata; + int ret; if (!fan_data) return -EINVAL; @@ -395,11 +420,11 @@ static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev, mutex_lock(&fan_data->lock); - set_fan_speed(fan_data, state); + ret = set_fan_speed(fan_data, state); mutex_unlock(&fan_data->lock); - return 0; + return ret; } static const struct thermal_cooling_device_ops gpio_fan_cool_ops = { @@ -499,6 +524,8 @@ static void gpio_fan_stop(void *data) mutex_lock(&fan_data->lock); set_fan_speed(data, 0); mutex_unlock(&fan_data->lock); + + pm_runtime_disable(fan_data->dev); } static int gpio_fan_probe(struct platform_device *pdev) @@ -521,6 +548,11 @@ static int gpio_fan_probe(struct platform_device *pdev) platform_set_drvdata(pdev, fan_data); mutex_init(&fan_data->lock); + fan_data->supply = devm_regulator_get(dev, "fan"); + if (IS_ERR(fan_data->supply)) + return dev_err_probe(dev, PTR_ERR(fan_data->supply), + "Failed to get fan-supply"); + /* Configure control GPIOs if available. */ if (fan_data->gpios && fan_data->num_gpios > 0) { if (!fan_data->speed || fan_data->num_speed <= 1) @@ -548,6 +580,17 @@ static int gpio_fan_probe(struct platform_device *pdev) return err; } + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_enable(&pdev->dev); + /* If current GPIO state is active, mark RPM as active as well */ + if (fan_data->speed_index > 0) { + int ret; + + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret) + return ret; + } + /* Optional cooling device register for Device tree platforms */ fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np, "gpio-fan", fan_data, &gpio_fan_cool_ops); @@ -565,41 +608,69 @@ static void gpio_fan_shutdown(struct platform_device *pdev) set_fan_speed(fan_data, 0); } +static int gpio_fan_runtime_suspend(struct device *dev) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + int ret = 0; + + if (fan_data->supply) + ret = regulator_disable(fan_data->supply); + + return ret; +} + +static int gpio_fan_runtime_resume(struct device *dev) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + int ret = 0; + + if (fan_data->supply) + ret = regulator_enable(fan_data->supply); + + return ret; +} + static int gpio_fan_suspend(struct device *dev) { struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + int ret = 0; if (fan_data->gpios) { fan_data->resume_speed = fan_data->speed_index; mutex_lock(&fan_data->lock); - set_fan_speed(fan_data, 0); + ret = set_fan_speed(fan_data, 0); mutex_unlock(&fan_data->lock); } - return 0; + return ret; } static int gpio_fan_resume(struct device *dev) { struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + int ret = 0; if (fan_data->gpios) { mutex_lock(&fan_data->lock); - set_fan_speed(fan_data, fan_data->resume_speed); + ret = set_fan_speed(fan_data, fan_data->resume_speed); mutex_unlock(&fan_data->lock); } - return 0; + return ret; } -static DEFINE_SIMPLE_DEV_PM_OPS(gpio_fan_pm, gpio_fan_suspend, gpio_fan_resume); +static const struct dev_pm_ops gpio_fan_pm = { + RUNTIME_PM_OPS(gpio_fan_runtime_suspend, + gpio_fan_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(gpio_fan_suspend, gpio_fan_resume) +}; static struct platform_driver gpio_fan_driver = { .probe = gpio_fan_probe, .shutdown = gpio_fan_shutdown, .driver = { .name = "gpio-fan", - .pm = pm_sleep_ptr(&gpio_fan_pm), + .pm = pm_ptr(&gpio_fan_pm), .of_match_table = of_gpio_fan_match, }, }; From 7e581c193bde7d5ac49587d9a182e5d13e05547c Mon Sep 17 00:00:00 2001 From: Gerhard Engleder Date: Wed, 9 Apr 2025 21:08:30 +0200 Subject: [PATCH 16/48] hwmon: Add KEBA battery monitoring controller support The KEBA battery monitoring controller is found in the system FPGA of KEBA PLC devices. It puts a load on the coin cell battery to check the state of the battery. If the coin cell battery is nearly empty, then the user space is signaled with a hwmon alarm. The auxiliary device for this driver is instantiated by the cp500 misc driver. Signed-off-by: Gerhard Engleder Link: https://lore.kernel.org/r/20250409190830.60489-1-gerhard@engleder-embedded.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/kbatt.rst | 60 ++++++++++++++ drivers/hwmon/Kconfig | 10 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/kbatt.c | 147 ++++++++++++++++++++++++++++++++++ 5 files changed, 219 insertions(+) create mode 100644 Documentation/hwmon/kbatt.rst create mode 100644 drivers/hwmon/kbatt.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index f0ddf6222c44..da5895115724 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -106,6 +106,7 @@ Hardware Monitoring Kernel Drivers jc42 k10temp k8temp + kbatt lan966x lineage-pem lm25066 diff --git a/Documentation/hwmon/kbatt.rst b/Documentation/hwmon/kbatt.rst new file mode 100644 index 000000000000..b72718c5ede3 --- /dev/null +++ b/Documentation/hwmon/kbatt.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver kbatt +=================== + +Supported chips: + + * KEBA battery monitoring controller (IP core in FPGA) + + Prefix: 'kbatt' + +Authors: + + Gerhard Engleder + Petar Bojanic + +Description +----------- + +The KEBA battery monitoring controller is an IP core for FPGAs, which +monitors the health of a coin cell battery. The coin cell battery is +typically used to supply the RTC during power off to keep the current +time. E.g., the CP500 FPGA includes this IP core to monitor the coin cell +battery of PLCs and the corresponding cp500 driver creates an auxiliary +device for the kbatt driver. + +This driver provides information about the coin cell battery health to +user space. Actually the user space shall be informed that the coin cell +battery is nearly empty and needs to be replaced. + +The coin cell battery must be tested actively to get to know if its nearly +empty or not. Therefore, a load is put on the coin cell battery and the +resulting voltage is evaluated. This evaluation is done by some hard wired +analog logic, which compares the voltage to a defined limit. If the +voltage is above the limit, then the coin cell battery is assumed to be +ok. If the voltage is below the limit, then the coin cell battery is +nearly empty (or broken, removed, ...) and shall be replaced by a new one. +The KEBA battery monitoring controller allows to start the test of the +coin cell battery and to get the result if the voltage is above or below +the limit. The actual voltage is not available. Only the information if +the voltage is below a limit is available. + +The test load, which is put on the coin cell battery for the health check, +is similar to the load during power off. Therefore, the lifetime of the +coin cell battery is reduced directly by the duration of each test. To +limit the negative impact to the lifetime the test is limited to at most +once every 10 seconds. The test load is put on the coin cell battery for +100ms. Thus, in worst case the coin cell battery lifetime is reduced by +1% of the uptime or 3.65 days per year. As the coin cell battery lasts +multiple years, this lifetime reduction negligible. + +This driver only provides a single alarm attribute, which is raised when +the coin cell battery is nearly empty. + +====================== ==== =================================================== +Attribute R/W Contents +====================== ==== =================================================== +in0_min_alarm R voltage of coin cell battery under load is below + limit +====================== ==== =================================================== diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f91f713b0105..832d7e5f9f7b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -335,6 +335,16 @@ config SENSORS_K10TEMP This driver can also be built as a module. If so, the module will be called k10temp. +config SENSORS_KBATT + tristate "KEBA battery controller support" + depends on KEBA_CP500 + help + This driver supports the battery monitoring controller found in + KEBA system FPGA devices. + + This driver can also be built as a module. If so, the module + will be called kbatt. + config SENSORS_FAM15H_POWER tristate "AMD Family 15h processor power" depends on X86 && PCI && CPU_SUP_AMD diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 766c652ef22b..af18deb0422e 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -110,6 +110,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_KBATT) += kbatt.o obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o diff --git a/drivers/hwmon/kbatt.c b/drivers/hwmon/kbatt.c new file mode 100644 index 000000000000..501b8f4ded33 --- /dev/null +++ b/drivers/hwmon/kbatt.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 KEBA Industrial Automation GmbH + * + * Driver for KEBA battery monitoring controller FPGA IP core + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define KBATT "kbatt" + +#define KBATT_CONTROL_REG 0x4 +#define KBATT_CONTROL_BAT_TEST 0x01 + +#define KBATT_STATUS_REG 0x8 +#define KBATT_STATUS_BAT_OK 0x01 + +#define KBATT_MAX_UPD_INTERVAL (10 * HZ) +#define KBATT_SETTLE_TIME_US (100 * USEC_PER_MSEC) + +struct kbatt { + /* update lock */ + struct mutex lock; + void __iomem *base; + + unsigned long next_update; /* in jiffies */ + bool alarm; +}; + +static bool kbatt_alarm(struct kbatt *kbatt) +{ + mutex_lock(&kbatt->lock); + + if (!kbatt->next_update || time_after(jiffies, kbatt->next_update)) { + /* switch load on */ + iowrite8(KBATT_CONTROL_BAT_TEST, + kbatt->base + KBATT_CONTROL_REG); + + /* wait some time to let things settle */ + fsleep(KBATT_SETTLE_TIME_US); + + /* check battery state */ + if (ioread8(kbatt->base + KBATT_STATUS_REG) & + KBATT_STATUS_BAT_OK) + kbatt->alarm = false; + else + kbatt->alarm = true; + + /* switch load off */ + iowrite8(0, kbatt->base + KBATT_CONTROL_REG); + + kbatt->next_update = jiffies + KBATT_MAX_UPD_INTERVAL; + } + + mutex_unlock(&kbatt->lock); + + return kbatt->alarm; +} + +static int kbatt_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct kbatt *kbatt = dev_get_drvdata(dev); + + *val = kbatt_alarm(kbatt) ? 1 : 0; + + return 0; +} + +static umode_t kbatt_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (channel == 0 && attr == hwmon_in_min_alarm) + return 0444; + + return 0; +} + +static const struct hwmon_channel_info *kbatt_info[] = { + HWMON_CHANNEL_INFO(in, + /* 0: input minimum alarm channel */ + HWMON_I_MIN_ALARM), + NULL +}; + +static const struct hwmon_ops kbatt_hwmon_ops = { + .is_visible = kbatt_is_visible, + .read = kbatt_read, +}; + +static const struct hwmon_chip_info kbatt_chip_info = { + .ops = &kbatt_hwmon_ops, + .info = kbatt_info, +}; + +static int kbatt_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct keba_batt_auxdev *kbatt_auxdev = + container_of(auxdev, struct keba_batt_auxdev, auxdev); + struct device *dev = &auxdev->dev; + struct device *hwmon_dev; + struct kbatt *kbatt; + int retval; + + kbatt = devm_kzalloc(dev, sizeof(*kbatt), GFP_KERNEL); + if (!kbatt) + return -ENOMEM; + + retval = devm_mutex_init(dev, &kbatt->lock); + if (retval) + return retval; + + kbatt->base = devm_ioremap_resource(dev, &kbatt_auxdev->io); + if (IS_ERR(kbatt->base)) + return PTR_ERR(kbatt->base); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, KBATT, kbatt, + &kbatt_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct auxiliary_device_id kbatt_devtype_aux[] = { + { .name = "keba.batt" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, kbatt_devtype_aux); + +static struct auxiliary_driver kbatt_driver_aux = { + .name = KBATT, + .id_table = kbatt_devtype_aux, + .probe = kbatt_probe, +}; +module_auxiliary_driver(kbatt_driver_aux); + +MODULE_AUTHOR("Petar Bojanic "); +MODULE_AUTHOR("Gerhard Engleder "); +MODULE_DESCRIPTION("KEBA battery monitoring controller driver"); +MODULE_LICENSE("GPL"); From 56591083846b8f4203234faf52de7a89f038ceeb Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 10 Apr 2025 19:03:57 +0100 Subject: [PATCH 17/48] hwmon: (pwm-fan) disable threaded interrupts The interrupt handler here just increments an atomic counter, jumping to a threaded handler risks missing tachometer pulses and is likely to be more expensive than the simple atomic increment. Signed-off-by: John Keeping Link: https://lore.kernel.org/r/20250410180357.2258822-1-jkeeping@inmusicbrands.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index d506a5e7e033..2df294793f6e 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -620,8 +620,8 @@ static int pwm_fan_probe(struct platform_device *pdev) if (tach->irq == -EPROBE_DEFER) return tach->irq; if (tach->irq > 0) { - ret = devm_request_irq(dev, tach->irq, pulse_handler, 0, - pdev->name, tach); + 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", From 80fcd1e7f5c7009fa1c64737df100cc304c19c1f Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 11 Apr 2025 12:20:53 +0100 Subject: [PATCH 18/48] hwmon: (xgene-hwmon) Simplify PCC shared memory region handling The PCC driver now handles mapping and unmapping of shared memory areas as part of pcc_mbox_{request,free}_channel(). Without these before, this xgene hwmon driver did handling of those mappings like several other PCC mailbox client drivers. There were redundant operations, leading to unnecessary code. Maintaining the consistency across these driver was harder due to scattered handling of shmem. Just use the mapped shmem and remove all redundant operations from this driver. Cc: Jean Delvare Cc: Guenter Roeck Cc: linux-hwmon@vger.kernel.org Signed-off-by: Sudeep Holla Link: https://lore.kernel.org/r/20250411112053.1148624-1-sudeep.holla@arm.com Signed-off-by: Guenter Roeck --- drivers/hwmon/xgene-hwmon.c | 39 ++++--------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index 2cdbd5f107a2..11c5d80428cd 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -103,8 +103,6 @@ struct xgene_hwmon_dev { struct device *hwmon_dev; bool temp_critical_alarm; - phys_addr_t comm_base_addr; - void *pcc_comm_addr; unsigned int usecs_lat; }; @@ -125,7 +123,8 @@ static u16 xgene_word_tst_and_clr(u16 *addr, u16 mask) static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) { - struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; + struct acpi_pcct_shared_memory __iomem *generic_comm_base = + ctx->pcc_chan->shmem; u32 *ptr = (void *)(generic_comm_base + 1); int rc, i; u16 val; @@ -523,7 +522,8 @@ static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg) static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg) { struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); - struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; + struct acpi_pcct_shared_memory __iomem *generic_comm_base = + ctx->pcc_chan->shmem; struct slimpro_resp_msg amsg; /* @@ -649,7 +649,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev) } else { struct pcc_mbox_chan *pcc_chan; const struct acpi_device_id *acpi_id; - int version; acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); @@ -658,8 +657,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev) goto out_mbox_free; } - version = (int)acpi_id->driver_data; - if (device_property_read_u32(&pdev->dev, "pcc-channel", &ctx->mbox_idx)) { dev_err(&pdev->dev, "no pcc-channel property\n"); @@ -685,34 +682,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev) goto out; } - /* - * This is the shared communication region - * for the OS and Platform to communicate over. - */ - ctx->comm_base_addr = pcc_chan->shmem_base_addr; - if (ctx->comm_base_addr) { - if (version == XGENE_HWMON_V2) - ctx->pcc_comm_addr = (void __force *)devm_ioremap(&pdev->dev, - ctx->comm_base_addr, - pcc_chan->shmem_size); - else - ctx->pcc_comm_addr = devm_memremap(&pdev->dev, - ctx->comm_base_addr, - pcc_chan->shmem_size, - MEMREMAP_WB); - } else { - dev_err(&pdev->dev, "Failed to get PCC comm region\n"); - rc = -ENODEV; - goto out; - } - - if (IS_ERR_OR_NULL(ctx->pcc_comm_addr)) { - dev_err(&pdev->dev, - "Failed to ioremap PCC comm region\n"); - rc = -ENOMEM; - goto out; - } - /* * pcc_chan->latency is just a Nominal value. In reality * the remote processor could be much slower to reply. From 38b5a5acabb639a2e3d40bffb92bd42f613dcd71 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 14 Apr 2025 15:47:39 +0800 Subject: [PATCH 19/48] hwmon: (lm90) Use to_delayed_work() Use to_delayed_work() instead of open-coding it. Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20250414074739.3954203-1-nichen@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/lm90.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 75f09553fd67..c1f528e292f3 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -1235,7 +1235,7 @@ static int lm90_update_alarms(struct lm90_data *data, bool force) static void lm90_alert_work(struct work_struct *__work) { - struct delayed_work *delayed_work = container_of(__work, struct delayed_work, work); + struct delayed_work *delayed_work = to_delayed_work(__work); struct lm90_data *data = container_of(delayed_work, struct lm90_data, alert_work); /* Nothing to do if alerts are enabled */ From 41e743881e85b4cda80c53e11075e7b19809b3ce Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 16 Apr 2025 15:59:19 -0700 Subject: [PATCH 20/48] hwmon: (aht10) Drop doctype annotations from static functions doctype annotations of static functions have little if any value. Drop them to silence 0-day complaints. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202504161919.duDL1s2X-lkp@intel.com/ Cc: Johannes Cornelis Draaijer Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/aht10.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c index 312ef3e98754..d1c55e2eb479 100644 --- a/drivers/hwmon/aht10.c +++ b/drivers/hwmon/aht10.c @@ -94,7 +94,7 @@ struct aht10_data { unsigned int meas_size; }; -/** +/* * aht10_init() - Initialize an AHT10/AHT20 chip * @data: the data associated with this AHT10/AHT20 chip * Return: 0 if successful, 1 if not @@ -124,7 +124,7 @@ static int aht10_init(struct aht10_data *data) return 0; } -/** +/* * aht10_polltime_expired() - check if the minimum poll interval has * expired * @data: the data containing the time to compare @@ -140,7 +140,7 @@ static int aht10_polltime_expired(struct aht10_data *data) DECLARE_CRC8_TABLE(crc8_table); -/** +/* * crc8_check() - check crc of the sensor's measurements * @raw_data: data frame received from sensor(including crc as the last byte) * @count: size of the data frame @@ -155,7 +155,7 @@ static int crc8_check(u8 *raw_data, int count) return crc8(crc8_table, raw_data, count, CRC8_INIT_VALUE); } -/** +/* * aht10_read_values() - read and parse the raw data from the AHT10/AHT20 * @data: the struct aht10_data to use for the lock * Return: 0 if successful, 1 if not @@ -214,7 +214,7 @@ static int aht10_read_values(struct aht10_data *data) return 0; } -/** +/* * aht10_interval_write() - store the given minimum poll interval. * Return: 0 on success, -EINVAL if a value lower than the * AHT10_MIN_POLL_INTERVAL is given @@ -226,7 +226,7 @@ static ssize_t aht10_interval_write(struct aht10_data *data, return 0; } -/** +/* * aht10_interval_read() - read the minimum poll interval * in milliseconds */ @@ -237,7 +237,7 @@ static ssize_t aht10_interval_read(struct aht10_data *data, return 0; } -/** +/* * aht10_temperature1_read() - read the temperature in millidegrees */ static int aht10_temperature1_read(struct aht10_data *data, long *val) @@ -252,7 +252,7 @@ static int aht10_temperature1_read(struct aht10_data *data, long *val) return 0; } -/** +/* * aht10_humidity1_read() - read the relative humidity in millipercent */ static int aht10_humidity1_read(struct aht10_data *data, long *val) From e799657a8aac1d974c90a295dd88b1d95697d18d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 21 Apr 2025 00:33:34 +0200 Subject: [PATCH 21/48] hwmon: (dell-smm) Add the Dell OptiPlex 7050 to the DMI whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user reported that the driver works on the OptiPlex 7050. Add this machine to the DMI whitelist. Closes: https://github.com/Wer-Wolf/i8kutils/issues/12 Signed-off-by: Armin Wolf Acked-by: Pali Rohár Link: https://lore.kernel.org/r/20250420223334.12920-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 79e5606e6d2f..1e2c8e284001 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1273,6 +1273,13 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7060"), }, }, + { + .ident = "Dell OptiPlex 7050", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7050"), + }, + }, { .ident = "Dell Precision", .matches = { From 4cf1aab45cc56e9276924c21b79da42e2eac01df Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 19 Jun 2024 08:53:56 -0700 Subject: [PATCH 22/48] hwmon: (spd5118) Split into common and I2C specific code Split spd5118 driver into common and I2C specific code to enable adding support for I3C. Signed-off-by: Guenter Roeck --- drivers/hwmon/spd5118.c | 305 +++++++++++++++++++++------------------- 1 file changed, 157 insertions(+), 148 deletions(-) diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c index 358152868d96..02eb21684c3a 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -305,51 +305,6 @@ static bool spd5118_vendor_valid(u8 bank, u8 id) return id && id != 0x7f; } -/* Return 0 if detection is successful, -ENODEV otherwise */ -static int spd5118_detect(struct i2c_client *client, struct i2c_board_info *info) -{ - struct i2c_adapter *adapter = client->adapter; - int regval; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | - I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; - - regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE); - if (regval != 0x5118) - return -ENODEV; - - regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR); - if (regval < 0 || !spd5118_vendor_valid(regval & 0xff, regval >> 8)) - return -ENODEV; - - regval = i2c_smbus_read_byte_data(client, SPD5118_REG_CAPABILITY); - if (regval < 0) - return -ENODEV; - if (!(regval & SPD5118_CAP_TS_SUPPORT) || (regval & 0xfc)) - return -ENODEV; - - regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CLR); - if (regval) - return -ENODEV; - regval = i2c_smbus_read_byte_data(client, SPD5118_REG_ERROR_CLR); - if (regval) - return -ENODEV; - - regval = i2c_smbus_read_byte_data(client, SPD5118_REG_REVISION); - if (regval < 0 || (regval & 0xc1)) - return -ENODEV; - - regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CONFIG); - if (regval < 0) - return -ENODEV; - if (regval & ~SPD5118_TS_DISABLE) - return -ENODEV; - - strscpy(info->type, "spd5118", I2C_NAME_SIZE); - return 0; -} - static const struct hwmon_channel_info *spd5118_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), @@ -483,7 +438,7 @@ static bool spd5118_volatile_reg(struct device *dev, unsigned int reg) } } -static const struct regmap_range_cfg spd5118_regmap_range_cfg[] = { +static const struct regmap_range_cfg spd5118_i2c_regmap_range_cfg[] = { { .selector_reg = SPD5118_REG_I2C_LEGACY_MODE, .selector_mask = SPD5118_LEGACY_PAGE_MASK, @@ -495,7 +450,7 @@ static const struct regmap_range_cfg spd5118_regmap_range_cfg[] = { }, }; -static const struct regmap_config spd5118_regmap_config = { +static const struct regmap_config spd5118_i2c_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = 0x7ff, @@ -503,11 +458,153 @@ static const struct regmap_config spd5118_regmap_config = { .volatile_reg = spd5118_volatile_reg, .cache_type = REGCACHE_MAPLE, - .ranges = spd5118_regmap_range_cfg, - .num_ranges = ARRAY_SIZE(spd5118_regmap_range_cfg), + .ranges = spd5118_i2c_regmap_range_cfg, + .num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg), }; -static int spd5118_init(struct i2c_client *client) +static int spd5118_suspend(struct device *dev) +{ + struct spd5118_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + u32 regval; + int err; + + /* + * Make sure the configuration register in the regmap cache is current + * before bypassing it. + */ + err = regmap_read(regmap, SPD5118_REG_TEMP_CONFIG, ®val); + if (err < 0) + return err; + + regcache_cache_bypass(regmap, true); + regmap_update_bits(regmap, SPD5118_REG_TEMP_CONFIG, SPD5118_TS_DISABLE, + SPD5118_TS_DISABLE); + regcache_cache_bypass(regmap, false); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + + return 0; +} + +static int spd5118_resume(struct device *dev) +{ + struct spd5118_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + + regcache_cache_only(regmap, false); + return regcache_sync(regmap); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume); + +static int spd5118_common_probe(struct device *dev, struct regmap *regmap) +{ + unsigned int capability, revision, vendor, bank; + struct spd5118_data *data; + struct device *hwmon_dev; + int err; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = regmap_read(regmap, SPD5118_REG_CAPABILITY, &capability); + if (err) + return err; + if (!(capability & SPD5118_CAP_TS_SUPPORT)) + return -ENODEV; + + err = regmap_read(regmap, SPD5118_REG_REVISION, &revision); + if (err) + return err; + + err = regmap_read(regmap, SPD5118_REG_VENDOR, &bank); + if (err) + return err; + err = regmap_read(regmap, SPD5118_REG_VENDOR + 1, &vendor); + if (err) + return err; + if (!spd5118_vendor_valid(bank, vendor)) + return -ENODEV; + + data->regmap = regmap; + mutex_init(&data->nvmem_lock); + dev_set_drvdata(dev, data); + + err = spd5118_nvmem_init(dev, data); + /* Ignore if NVMEM support is disabled */ + if (err && err != -EOPNOTSUPP) { + dev_err_probe(dev, err, "failed to register nvmem\n"); + return err; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "spd5118", + regmap, &spd5118_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + /* + * From JESD300-5B + * MR2 bits [5:4]: Major revision, 1..4 + * MR2 bits [3:1]: Minor revision, 0..8? Probably a typo, assume 1..8 + */ + dev_info(dev, "DDR5 temperature sensor: vendor 0x%02x:0x%02x revision %d.%d\n", + bank & 0x7f, vendor, ((revision >> 4) & 0x03) + 1, ((revision >> 1) & 0x07) + 1); + + return 0; +} + +/* I2C */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int spd5118_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int regval; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE); + if (regval != 0x5118) + return -ENODEV; + + regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR); + if (regval < 0 || !spd5118_vendor_valid(regval & 0xff, regval >> 8)) + return -ENODEV; + + regval = i2c_smbus_read_byte_data(client, SPD5118_REG_CAPABILITY); + if (regval < 0) + return -ENODEV; + if (!(regval & SPD5118_CAP_TS_SUPPORT) || (regval & 0xfc)) + return -ENODEV; + + regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CLR); + if (regval) + return -ENODEV; + regval = i2c_smbus_read_byte_data(client, SPD5118_REG_ERROR_CLR); + if (regval) + return -ENODEV; + + regval = i2c_smbus_read_byte_data(client, SPD5118_REG_REVISION); + if (regval < 0 || (regval & 0xc1)) + return -ENODEV; + + regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CONFIG); + if (regval < 0) + return -ENODEV; + if (regval & ~SPD5118_TS_DISABLE) + return -ENODEV; + + strscpy(info->type, "spd5118", I2C_NAME_SIZE); + return 0; +} + +static int spd5118_i2c_init(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; int err, regval, mode; @@ -559,116 +656,28 @@ static int spd5118_init(struct i2c_client *client) return 0; } -static int spd5118_probe(struct i2c_client *client) +static int spd5118_i2c_probe(struct i2c_client *client) { struct device *dev = &client->dev; - unsigned int regval, revision, vendor, bank; - struct spd5118_data *data; - struct device *hwmon_dev; struct regmap *regmap; int err; - err = spd5118_init(client); + err = spd5118_i2c_init(client); if (err) return err; - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - regmap = devm_regmap_init_i2c(client, &spd5118_regmap_config); + regmap = devm_regmap_init_i2c(client, &spd5118_i2c_regmap_config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); - err = regmap_read(regmap, SPD5118_REG_CAPABILITY, ®val); - if (err) - return err; - if (!(regval & SPD5118_CAP_TS_SUPPORT)) - return -ENODEV; - - err = regmap_read(regmap, SPD5118_REG_REVISION, &revision); - if (err) - return err; - - err = regmap_read(regmap, SPD5118_REG_VENDOR, &bank); - if (err) - return err; - err = regmap_read(regmap, SPD5118_REG_VENDOR + 1, &vendor); - if (err) - return err; - if (!spd5118_vendor_valid(bank, vendor)) - return -ENODEV; - - data->regmap = regmap; - mutex_init(&data->nvmem_lock); - dev_set_drvdata(dev, data); - - err = spd5118_nvmem_init(dev, data); - /* Ignore if NVMEM support is disabled */ - if (err && err != -EOPNOTSUPP) { - dev_err_probe(dev, err, "failed to register nvmem\n"); - return err; - } - - hwmon_dev = devm_hwmon_device_register_with_info(dev, "spd5118", - regmap, &spd5118_chip_info, - NULL); - if (IS_ERR(hwmon_dev)) - return PTR_ERR(hwmon_dev); - - /* - * From JESD300-5B - * MR2 bits [5:4]: Major revision, 1..4 - * MR2 bits [3:1]: Minor revision, 0..8? Probably a typo, assume 1..8 - */ - dev_info(dev, "DDR5 temperature sensor: vendor 0x%02x:0x%02x revision %d.%d\n", - bank & 0x7f, vendor, ((revision >> 4) & 0x03) + 1, ((revision >> 1) & 0x07) + 1); - - return 0; + return spd5118_common_probe(dev, regmap); } -static int spd5118_suspend(struct device *dev) -{ - struct spd5118_data *data = dev_get_drvdata(dev); - struct regmap *regmap = data->regmap; - u32 regval; - int err; - - /* - * Make sure the configuration register in the regmap cache is current - * before bypassing it. - */ - err = regmap_read(regmap, SPD5118_REG_TEMP_CONFIG, ®val); - if (err < 0) - return err; - - regcache_cache_bypass(regmap, true); - regmap_update_bits(regmap, SPD5118_REG_TEMP_CONFIG, SPD5118_TS_DISABLE, - SPD5118_TS_DISABLE); - regcache_cache_bypass(regmap, false); - - regcache_cache_only(regmap, true); - regcache_mark_dirty(regmap); - - return 0; -} - -static int spd5118_resume(struct device *dev) -{ - struct spd5118_data *data = dev_get_drvdata(dev); - struct regmap *regmap = data->regmap; - - regcache_cache_only(regmap, false); - return regcache_sync(regmap); -} - -static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume); - -static const struct i2c_device_id spd5118_id[] = { +static const struct i2c_device_id spd5118_i2c_id[] = { { "spd5118" }, { } }; -MODULE_DEVICE_TABLE(i2c, spd5118_id); +MODULE_DEVICE_TABLE(i2c, spd5118_i2c_id); static const struct of_device_id spd5118_of_ids[] = { { .compatible = "jedec,spd5118", }, @@ -676,20 +685,20 @@ static const struct of_device_id spd5118_of_ids[] = { }; MODULE_DEVICE_TABLE(of, spd5118_of_ids); -static struct i2c_driver spd5118_driver = { +static struct i2c_driver spd5118_i2c_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "spd5118", .of_match_table = spd5118_of_ids, .pm = pm_sleep_ptr(&spd5118_pm_ops), }, - .probe = spd5118_probe, - .id_table = spd5118_id, + .probe = spd5118_i2c_probe, + .id_table = spd5118_i2c_id, .detect = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? spd5118_detect : NULL, .address_list = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? normal_i2c : NULL, }; -module_i2c_driver(spd5118_driver); +module_i2c_driver(spd5118_i2c_driver); MODULE_AUTHOR("René Rebe "); MODULE_AUTHOR("Guenter Roeck "); From ae28532aff1f7912c1d118b4257c095ce62f1cb0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 17 Nov 2024 07:55:32 -0800 Subject: [PATCH 23/48] hwmon: (spd5118) Name chips taking the specification literally The Renesas/IDT SPD5118 Hub Controller is known to take the specification literally and does not permit access to volatile registers except for the page register if the selected page is non-zero. Explicitly name the chip to ensure that the information does not get lost. Signed-off-by: Guenter Roeck --- drivers/hwmon/spd5118.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c index 02eb21684c3a..c5ad06b90cbf 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -621,7 +621,8 @@ static int spd5118_i2c_init(struct i2c_client *client) * If the device type registers return 0, it is possible that the chip * has a non-zero page selected and takes the specification literally, * i.e. disables access to volatile registers besides the page register - * if the page is not 0. Try to identify such chips. + * if the page is not 0. The Renesas/ITD SPD5118 Hub Controller is known + * to show this behavior. Try to identify such chips. */ if (!regval) { /* Vendor ID registers must also be 0 */ From be82d39c537e884e490a79bcc38a3be8d9e8b0a9 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 14 Jun 2024 15:13:46 -0700 Subject: [PATCH 24/48] hwmon: (spd5118) Support 16-bit addressing for NVMEM accesses I3C uses 16-bit register addresses. Setting the page through the legacy mode access pointer in the legacy mode device configuration register (MR11) is not needed. This is similar to 16-bit addressing in legacy mode. Signed-off-by: Guenter Roeck --- drivers/hwmon/spd5118.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c index c5ad06b90cbf..92a3cb0bb515 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -66,6 +66,9 @@ static const unsigned short normal_i2c[] = { #define SPD5118_EEPROM_BASE 0x80 #define SPD5118_EEPROM_SIZE (SPD5118_PAGE_SIZE * SPD5118_NUM_PAGES) +#define PAGE_ADDR0(page) (((page) & BIT(0)) << 6) +#define PAGE_ADDR1_4(page) (((page) & GENMASK(4, 1)) >> 1) + /* Temperature unit in millicelsius */ #define SPD5118_TEMP_UNIT (MILLIDEGREE_PER_DEGREE / 4) /* Representable temperature range in millicelsius */ @@ -75,6 +78,7 @@ static const unsigned short normal_i2c[] = { struct spd5118_data { struct regmap *regmap; struct mutex nvmem_lock; + bool is_16bit; }; /* hwmon */ @@ -331,11 +335,12 @@ static const struct hwmon_chip_info spd5118_chip_info = { /* nvmem */ -static ssize_t spd5118_nvmem_read_page(struct regmap *regmap, char *buf, +static ssize_t spd5118_nvmem_read_page(struct spd5118_data *data, char *buf, unsigned int offset, size_t count) { - int addr = (offset >> SPD5118_PAGE_SHIFT) * 0x100 + SPD5118_EEPROM_BASE; - int err; + int page = offset >> SPD5118_PAGE_SHIFT; + struct regmap *regmap = data->regmap; + int err, addr; offset &= SPD5118_PAGE_MASK; @@ -343,6 +348,12 @@ static ssize_t spd5118_nvmem_read_page(struct regmap *regmap, char *buf, if (offset + count > SPD5118_PAGE_SIZE) count = SPD5118_PAGE_SIZE - offset; + if (data->is_16bit) { + addr = SPD5118_EEPROM_BASE | PAGE_ADDR0(page) | + (PAGE_ADDR1_4(page) << 8); + } else { + addr = page * 0x100 + SPD5118_EEPROM_BASE; + } err = regmap_bulk_read(regmap, addr + offset, buf, count); if (err) return err; @@ -365,7 +376,7 @@ static int spd5118_nvmem_read(void *priv, unsigned int off, void *val, size_t co mutex_lock(&data->nvmem_lock); while (count) { - ret = spd5118_nvmem_read_page(data->regmap, buf, off, count); + ret = spd5118_nvmem_read_page(data, buf, off, count); if (ret < 0) { mutex_unlock(&data->nvmem_lock); return ret; @@ -516,6 +527,13 @@ static int spd5118_common_probe(struct device *dev, struct regmap *regmap) if (!(capability & SPD5118_CAP_TS_SUPPORT)) return -ENODEV; + /* + * 16-bit register addresses are not (yet) supported with I2C. + * Therefore, if this is an I2C device, register addresses must be + * 8 bit wide. + */ + data->is_16bit = !!i2c_verify_adapter(dev); + err = regmap_read(regmap, SPD5118_REG_REVISION, &revision); if (err) return err; From a852162efbff611ed49ae61a141e80c81689d54c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 19 Jun 2024 09:40:36 -0700 Subject: [PATCH 25/48] hwmon: (spd5118) Detect and support 16-bit register addressing Add support for SPD5118 compatible chips with 16-bit addressing enabled which are connected to I2C controllers. Signed-off-by: Guenter Roeck --- drivers/hwmon/spd5118.c | 75 +++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c index 92a3cb0bb515..5da44571b6a0 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -461,7 +461,7 @@ static const struct regmap_range_cfg spd5118_i2c_regmap_range_cfg[] = { }, }; -static const struct regmap_config spd5118_i2c_regmap_config = { +static const struct regmap_config spd5118_regmap8_config = { .reg_bits = 8, .val_bits = 8, .max_register = 0x7ff, @@ -473,6 +473,15 @@ static const struct regmap_config spd5118_i2c_regmap_config = { .num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg), }; +static const struct regmap_config spd5118_regmap16_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x7ff, + .writeable_reg = spd5118_writeable_reg, + .volatile_reg = spd5118_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + static int spd5118_suspend(struct device *dev) { struct spd5118_data *data = dev_get_drvdata(dev); @@ -510,7 +519,8 @@ static int spd5118_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume); -static int spd5118_common_probe(struct device *dev, struct regmap *regmap) +static int spd5118_common_probe(struct device *dev, struct regmap *regmap, + bool is_16bit) { unsigned int capability, revision, vendor, bank; struct spd5118_data *data; @@ -527,12 +537,7 @@ static int spd5118_common_probe(struct device *dev, struct regmap *regmap) if (!(capability & SPD5118_CAP_TS_SUPPORT)) return -ENODEV; - /* - * 16-bit register addresses are not (yet) supported with I2C. - * Therefore, if this is an I2C device, register addresses must be - * 8 bit wide. - */ - data->is_16bit = !!i2c_verify_adapter(dev); + data->is_16bit = is_16bit; err = regmap_read(regmap, SPD5118_REG_REVISION, &revision); if (err) @@ -675,21 +680,69 @@ static int spd5118_i2c_init(struct i2c_client *client) return 0; } +/* + * 16-bit addressing note: + * + * If I2C_FUNC_I2C is not supported by an I2C adapter driver, regmap uses + * SMBus operations as alternative. To simulate a read operation with a 16-bit + * address, it writes the address using i2c_smbus_write_byte_data(), followed + * by one or more calls to i2c_smbus_read_byte() to read the data. + * Per spd5118 standard, a read operation after writing the address must start + * with (Repeat Start). However, a SMBus read byte operation starts with + * (Start). This resets the register address in the spd5118 chip. As result, + * i2c_smbus_read_byte() always returns data from register address 0x00. + * + * A working alternative to access chips with 16-bit register addresses in the + * absence of I2C_FUNC_I2C support is not known. + * + * For this reason, 16-bit addressing can only be supported with I2C if the + * adapter supports I2C_FUNC_I2C. + * + * For I2C, the addressing mode selected by the BIOS must not be changed. + * Experiments show that at least some PC BIOS versions will not change the + * addressing mode on a soft reboot and end up in setup, claiming that some + * configuration change happened. This will happen again after a power cycle, + * which does reset the addressing mode. To prevent this from happening, + * detect if 16-bit addressing is enabled and always use the currently + * configured addressing mode. + */ + static int spd5118_i2c_probe(struct i2c_client *client) { + const struct regmap_config *config; struct device *dev = &client->dev; struct regmap *regmap; - int err; + int err, mode; + bool is_16bit; err = spd5118_i2c_init(client); if (err) return err; - regmap = devm_regmap_init_i2c(client, &spd5118_i2c_regmap_config); + mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE); + if (mode < 0) + return mode; + + is_16bit = mode & SPD5118_LEGACY_MODE_ADDR; + if (is_16bit) { + /* + * See 16-bit addressing note above explaining why it is + * necessary to check for I2C_FUNC_I2C support here. + */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "Adapter does not support 16-bit register addresses\n"); + return -ENODEV; + } + config = &spd5118_regmap16_config; + } else { + config = &spd5118_regmap8_config; + } + + regmap = devm_regmap_init_i2c(client, config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); - return spd5118_common_probe(dev, regmap); + return spd5118_common_probe(dev, regmap, is_16bit); } static const struct i2c_device_id spd5118_i2c_id[] = { From 48834a4e794302c162a73e1680c76cd73847bbdb Mon Sep 17 00:00:00 2001 From: Cedric Encarnacion Date: Mon, 21 Apr 2025 20:18:18 +0800 Subject: [PATCH 26/48] dt-bindings: hwmon: pmbus: add lt3074 Add Analog Devices LT3074 Ultralow Noise, High PSRR Dropout Linear Regulator. Signed-off-by: Cedric Encarnacion Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250421-upstream-lt3074-v3-1-71636322f9fe@analog.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/adi,lt3074.yaml | 50 +++++++++++++++++++ MAINTAINERS | 7 +++ 2 files changed, 57 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/adi,lt3074.yaml diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,lt3074.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,lt3074.yaml new file mode 100644 index 000000000000..bf028a8718f1 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,lt3074.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/pmbus/adi,lt3074.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices LT3074 voltage regulator + +maintainers: + - Cedric Encarnacion + +description: | + The LT3074 is a low voltage, ultra-low noise and ultra-fast transient + response linear regulator. It allows telemetry for input/output voltage, + output current and temperature through the PMBus serial interface. + + Datasheet: + https://www.analog.com/en/products/lt3074.html + +allOf: + - $ref: /schemas/regulator/regulator.yaml# + +properties: + compatible: + enum: + - adi,lt3074 + + reg: + maxItems: 1 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@6d { + compatible = "adi,lt3074"; + reg = <0x6d>; + regulator-name = "vout"; + regulator-max-microvolt = <1250000>; + regulator-min-microvolt = <1150000>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index fa1e04e87d1d..eaa786e04abd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13989,6 +13989,13 @@ L: linux-scsi@vger.kernel.org S: Maintained F: drivers/scsi/sym53c8xx_2/ +LT3074 HARDWARE MONITOR DRIVER +M: Cedric Encarnacion +L: linux-hwmon@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/hwmon/pmbus/adi,lt3074.yaml + LTC1660 DAC DRIVER M: Marcus Folkesson L: linux-iio@vger.kernel.org From c66c5bda7f24a7dae2b5b789025e4b8418eb0fae Mon Sep 17 00:00:00 2001 From: Cedric Encarnacion Date: Mon, 21 Apr 2025 20:18:19 +0800 Subject: [PATCH 27/48] hwmon: (pmbus/lt3074) add support for lt3074 Add hardware monitoring and regulator support for LT3074. The LT3074 is an ultrafast, ultralow noise 3A, 5.5V dropout linear regulator. The PMBus serial interface allows telemetry for input/output voltage, bias voltage, output current, and die temperature. Signed-off-by: Cedric Encarnacion Link: https://lore.kernel.org/r/20250421-upstream-lt3074-v3-2-71636322f9fe@analog.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/lt3074.rst | 72 +++++++++++++++++++ MAINTAINERS | 2 + drivers/hwmon/pmbus/Kconfig | 18 +++++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/lt3074.c | 122 +++++++++++++++++++++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 Documentation/hwmon/lt3074.rst create mode 100644 drivers/hwmon/pmbus/lt3074.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index da5895115724..d17f942d8535 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -126,6 +126,7 @@ Hardware Monitoring Kernel Drivers lm95234 lm95245 lochnagar + lt3074 lt7182s ltc2992 ltc2945 diff --git a/Documentation/hwmon/lt3074.rst b/Documentation/hwmon/lt3074.rst new file mode 100644 index 000000000000..234f369153cf --- /dev/null +++ b/Documentation/hwmon/lt3074.rst @@ -0,0 +1,72 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver lt3074 +==================== + +Supported chips: + + * Analog Devices LT3074 + + Prefix: 'lt3074' + + Addresses scanned: - + + Datasheet: https://www.analog.com/en/products/lt3074.html + +Authors: Cedric Encarnacion + + +Description +----------- + +This driver supports hardware monitoring for Analog Devices LT3074 Linear +Regulator with PMBus interface. + +The LT3074 is a low voltage, ultra-low noise and ultra-fast transient +response linear regulator with PMBus serial interface. PMBus telemetry +feature provides information regarding the output voltage and current, +input voltage, bias voltage and die temperature. + +The driver is a client driver to the core PMBus driver. Please see +Documentation/hwmon/pmbus.rst for details on PMBus client drivers. + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate +the devices explicitly. Please see Documentation/i2c/instantiating-devices.rst +for details. + +Platform data support +--------------------- + +The driver supports standard PMBus driver platform data. + +Sysfs entries +------------- + +======================= ======================================================= +in1_label "vin" +in1_input Measured input voltage +in1_max Input overvoltage warning limit +in1_max_alarm Input overvoltage warning status +in1_min Input undervoltage warning limit +in1_min_alarm Input undervoltage warning status +in2_label "vmon" +in2_input Measured bias voltage +in2_max Bias overvoltage warning limit +in2_min Bias undervoltage warning limit +in3_label "vout1" +in3_input Measured output voltage +in3_max Output overvoltage warning limit +in3_max_alarm Output overvoltage warning status +in3_min Output undervoltage warning limit +in3_min_alarm Output undervoltage warning status +curr1_label "iout1" +curr1_input Measured output current. +curr1_crit Output overcurrent fault limit +curr1_crit_alarm Output overcurrent fault status +temp1_input Measured temperature +temp1_max Maximum temperature limit +temp1_max_alarm Overtemperature warning status +======================= ======================================================= diff --git a/MAINTAINERS b/MAINTAINERS index eaa786e04abd..6034c18b2c08 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13995,6 +13995,8 @@ L: linux-hwmon@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/hwmon/pmbus/adi,lt3074.yaml +F: Documentation/hwmon/lt3074.rst +F: drivers/hwmon/pmbus/lt3074.c LTC1660 DAC DRIVER M: Marcus Folkesson diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index c9b3c3149982..441f984a859d 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -218,6 +218,24 @@ config SENSORS_LM25066_REGULATOR If you say yes here you get regulator support for National Semiconductor LM25066, LM5064, and LM5066. +config SENSORS_LT3074 + tristate "Analog Devices LT3074" + help + If you say yes here you get hardware monitoring support for Analog + Devices LT3074. + + This driver can also be built as a module. If so, the module will + be called lt3074. + +config SENSORS_LT3074_REGULATOR + tristate "Regulator support for LT3074" + depends on SENSORS_LT3074 && REGULATOR + help + If you say yes here you get regulator support for Analog Devices + LT3074. The LT3074 is a low voltage, ultralow noise, high PSRR, + dropout linear regulator. The device supplies up to 3A with a + typical dropout voltage of 45mV. + config SENSORS_LT7182S tristate "Analog Devices LT7182S" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 56f128c4653e..29cd8a3317d2 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_SENSORS_IR38064) += ir38064.o obj-$(CONFIG_SENSORS_IRPS5401) += irps5401.o obj-$(CONFIG_SENSORS_ISL68137) += isl68137.o obj-$(CONFIG_SENSORS_LM25066) += lm25066.o +obj-$(CONFIG_SENSORS_LT3074) += lt3074.o obj-$(CONFIG_SENSORS_LT7182S) += lt7182s.o obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o diff --git a/drivers/hwmon/pmbus/lt3074.c b/drivers/hwmon/pmbus/lt3074.c new file mode 100644 index 000000000000..3704dbe7b54a --- /dev/null +++ b/drivers/hwmon/pmbus/lt3074.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hardware monitoring driver for Analog Devices LT3074 + * + * Copyright (C) 2025 Analog Devices, Inc. + */ +#include +#include +#include +#include + +#include "pmbus.h" + +#define LT3074_MFR_READ_VBIAS 0xc6 +#define LT3074_MFR_BIAS_OV_WARN_LIMIT 0xc7 +#define LT3074_MFR_BIAS_UV_WARN_LIMIT 0xc8 +#define LT3074_MFR_SPECIAL_ID 0xe7 + +#define LT3074_SPECIAL_ID_VALUE 0x1c1d + +static const struct regulator_desc __maybe_unused lt3074_reg_desc[] = { + PMBUS_REGULATOR_ONE("regulator"), +}; + +static int lt3074_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + switch (reg) { + case PMBUS_VIRT_READ_VMON: + return pmbus_read_word_data(client, page, phase, + LT3074_MFR_READ_VBIAS); + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + return pmbus_read_word_data(client, page, phase, + LT3074_MFR_BIAS_UV_WARN_LIMIT); + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + return pmbus_read_word_data(client, page, phase, + LT3074_MFR_BIAS_OV_WARN_LIMIT); + default: + return -ENODATA; + } +} + +static int lt3074_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + switch (reg) { + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + return pmbus_write_word_data(client, 0, + LT3074_MFR_BIAS_UV_WARN_LIMIT, + word); + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + return pmbus_write_word_data(client, 0, + LT3074_MFR_BIAS_OV_WARN_LIMIT, + word); + default: + return -ENODATA; + } +} + +static struct pmbus_driver_info lt3074_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_VMON | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = lt3074_read_word_data, + .write_word_data = lt3074_write_word_data, +#if IS_ENABLED(CONFIG_SENSORS_LT3074_REGULATOR) + .num_regulators = 1, + .reg_desc = lt3074_reg_desc, +#endif +}; + +static int lt3074_probe(struct i2c_client *client) +{ + int ret; + struct device *dev = &client->dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_word_data(client, LT3074_MFR_SPECIAL_ID); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read ID\n"); + + if (ret != LT3074_SPECIAL_ID_VALUE) + return dev_err_probe(dev, -ENODEV, "ID mismatch\n"); + + return pmbus_do_probe(client, <3074_info); +} + +static const struct i2c_device_id lt3074_id[] = { + { "lt3074", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, lt3074_id); + +static const struct of_device_id __maybe_unused lt3074_of_match[] = { + { .compatible = "adi,lt3074" }, + {} +}; +MODULE_DEVICE_TABLE(of, lt3074_of_match); + +static struct i2c_driver lt3074_driver = { + .driver = { + .name = "lt3074", + .of_match_table = of_match_ptr(lt3074_of_match), + }, + .probe = lt3074_probe, + .id_table = lt3074_id, +}; +module_i2c_driver(lt3074_driver); + +MODULE_AUTHOR("Cedric Encarnacion "); +MODULE_DESCRIPTION("PMBus driver for Analog Devices LT3074"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); From 03abdce464efc3dcdfe323f7fc315d0fa7abd457 Mon Sep 17 00:00:00 2001 From: Peter Korsgaard Date: Thu, 17 Apr 2025 20:04:25 +0200 Subject: [PATCH 28/48] dt-bindings: hwmon: ti,tmp102: document optional V+ supply property TMP102 is powered by its V+ supply, document it. The property is called "vcc-supply" since the plus sign (+) is not a valid property character. Signed-off-by: Peter Korsgaard Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250417180426.3872314-1-peter@korsgaard.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml b/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml index 7e5b62a0215d..4c89448eba0d 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml @@ -23,6 +23,9 @@ properties: "#thermal-sensor-cells": const: 1 + vcc-supply: + description: Power supply for tmp102 + required: - compatible - reg @@ -42,6 +45,7 @@ examples: reg = <0x48>; interrupt-parent = <&gpio7>; interrupts = <16 IRQ_TYPE_LEVEL_LOW>; + vcc-supply = <&supply>; #thermal-sensor-cells = <1>; }; }; From 3e749ce132676683f3cdeec9a887c3f8f5ed96eb Mon Sep 17 00:00:00 2001 From: Peter Korsgaard Date: Thu, 17 Apr 2025 20:04:26 +0200 Subject: [PATCH 29/48] hwmon: (tmp102) add vcc regulator support Make it optional for backwards compatibility. Signed-off-by: Peter Korsgaard Link: https://lore.kernel.org/r/20250417180426.3872314-2-peter@korsgaard.com Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp102.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index 8af44a33055f..a02daa496c9c 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #define DRIVER_NAME "tmp102" @@ -204,6 +205,10 @@ static int tmp102_probe(struct i2c_client *client) return -ENODEV; } + err = devm_regulator_get_enable_optional(dev, "vcc"); + if (err < 0 && err != -ENODEV) + return dev_err_probe(dev, err, "Failed to enable regulator\n"); + tmp102 = devm_kzalloc(dev, sizeof(*tmp102), GFP_KERNEL); if (!tmp102) return -ENOMEM; From 8debd8511dd9376fab33bff62500a5a71937420a Mon Sep 17 00:00:00 2001 From: Dzmitry Sankouski Date: Wed, 23 Apr 2025 07:54:36 +0300 Subject: [PATCH 30/48] hwmon: (max77705) Add initial support Maxim MAX77705 is a Companion Power Management and Type-C interface IC. It includes charger and fuel gauge blocks, and is capable of measuring charger input current, system bus volatage and current, and bypass voltage. Add support for mentioned measurements. Signed-off-by: Dzmitry Sankouski Link: https://lore.kernel.org/r/20250423-initial-support-for-max77705-sensors-v6-1-ff379e1b06c5@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/max77705.rst | 39 ++++++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 9 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/max77705-hwmon.c | 221 +++++++++++++++++++++++++++++++ 6 files changed, 278 insertions(+) create mode 100644 Documentation/hwmon/max77705.rst create mode 100644 drivers/hwmon/max77705-hwmon.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index d17f942d8535..946b8a266d89 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -163,6 +163,7 @@ Hardware Monitoring Kernel Drivers max6639 max6650 max6697 + max77705 max8688 mc13783-adc mc34vr500 diff --git a/Documentation/hwmon/max77705.rst b/Documentation/hwmon/max77705.rst new file mode 100644 index 000000000000..4a7680a340e1 --- /dev/null +++ b/Documentation/hwmon/max77705.rst @@ -0,0 +1,39 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver max77705 +====================== + +Supported chips: + + * Maxim Integrated MAX77705 + + Prefix: 'max77705' + + Addresses scanned: none + + Datasheet: Not available + +Authors: + - Dzmitry Sankouski + +Description +----------- + +The MAX77705 PMIC provides current and voltage measurements besides fuelgauge: +- chip input current +- system bus current and voltage +- VBYP voltage + +Sysfs Attributes +---------------- + +================= ======================================== +in1_label "vbyp" +in1_input Measured chip vbyp voltage +in2_label "vsys" +in2_input Measured chip system bus voltage +curr1_label "iin" +curr1_input Measured chip input current. +curr2_label "isys" +curr2_input Measured chip system bus current. +================= ======================================== diff --git a/MAINTAINERS b/MAINTAINERS index 6034c18b2c08..063c97f40310 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18433,6 +18433,13 @@ S: Maintained F: Documentation/hwmon/pc87427.rst F: drivers/hwmon/pc87427.c +MAX77705 HARDWARE MONITORING DRIVER +M: Dzmitry Sankouski +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/max77705.rst +F: drivers/hwmon/max77705-hwmon.c + PCA9532 LED DRIVER M: Riku Voipio S: Maintained diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 832d7e5f9f7b..80e277448c71 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1318,6 +1318,15 @@ config SENSORS_MAX31790 This driver can also be built as a module. If so, the module will be called max31790. +config SENSORS_MAX77705 + tristate "MAX77705 current and voltage sensor" + depends on MFD_MAX77705 + help + If you say yes here you get support for MAX77705 sensors connected with I2C. + + This driver can also be built as a module. If so, the module + will be called max77705-hwmon. + config SENSORS_MC34VR500 tristate "NXP MC34VR500 hardware monitoring driver" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index af18deb0422e..50df221f67c2 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -162,6 +162,7 @@ obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MAX6697) += max6697.o obj-$(CONFIG_SENSORS_MAX31790) += max31790.o obj-$(CONFIG_MAX31827) += max31827.o +obj-$(CONFIG_SENSORS_MAX77705) += max77705-hwmon.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o diff --git a/drivers/hwmon/max77705-hwmon.c b/drivers/hwmon/max77705-hwmon.c new file mode 100644 index 000000000000..990023e6474e --- /dev/null +++ b/drivers/hwmon/max77705-hwmon.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MAX77705 voltage and current hwmon driver. + * + * Copyright (C) 2025 Dzmitry Sankouski + */ + +#include +#include +#include +#include +#include +#include +#include + +struct channel_desc { + u8 reg; + u8 avg_reg; + const char *const label; + // register resolution. nano Volts for voltage, nano Amperes for current + u32 resolution; +}; + +static const struct channel_desc current_channel_desc[] = { + { + .reg = IIN_REG, + .label = "IIN_REG", + .resolution = 125000 + }, + { + .reg = ISYS_REG, + .avg_reg = AVGISYS_REG, + .label = "ISYS_REG", + .resolution = 312500 + } +}; + +static const struct channel_desc voltage_channel_desc[] = { + { + .reg = VBYP_REG, + .label = "VBYP_REG", + .resolution = 427246 + }, + { + .reg = VSYS_REG, + .label = "VSYS_REG", + .resolution = 156250 + } +}; + +static int max77705_read_and_convert(struct regmap *regmap, u8 reg, u32 res, + bool is_signed, long *val) +{ + int ret; + u32 regval; + + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + return ret; + + if (is_signed) + *val = mult_frac((long)sign_extend32(regval, 15), res, 1000000); + else + *val = mult_frac((long)regval, res, 1000000); + + return 0; +} + +static umode_t max77705_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + return 0444; + default: + break; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_in_label: + return 0444; + case hwmon_curr_average: + if (current_channel_desc[channel].avg_reg) + return 0444; + break; + default: + break; + } + break; + default: + break; + } + return 0; +} + +static int max77705_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **buf) +{ + switch (type) { + case hwmon_curr: + switch (attr) { + case hwmon_in_label: + *buf = current_channel_desc[channel].label; + return 0; + default: + return -EOPNOTSUPP; + } + + case hwmon_in: + switch (attr) { + case hwmon_in_label: + *buf = voltage_channel_desc[channel].label; + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int max77705_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + u8 reg; + u32 res; + + switch (type) { + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + reg = current_channel_desc[channel].reg; + res = current_channel_desc[channel].resolution; + + return max77705_read_and_convert(regmap, reg, res, true, val); + case hwmon_curr_average: + reg = current_channel_desc[channel].avg_reg; + res = current_channel_desc[channel].resolution; + + return max77705_read_and_convert(regmap, reg, res, true, val); + default: + return -EOPNOTSUPP; + } + + case hwmon_in: + switch (attr) { + case hwmon_in_input: + reg = voltage_channel_desc[channel].reg; + res = voltage_channel_desc[channel].resolution; + + return max77705_read_and_convert(regmap, reg, res, false, val); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_ops max77705_hwmon_ops = { + .is_visible = max77705_is_visible, + .read = max77705_read, + .read_string = max77705_read_string, +}; + +static const struct hwmon_channel_info *max77705_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL + ), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_AVERAGE | HWMON_C_LABEL + ), + NULL +}; + +static const struct hwmon_chip_info max77705_chip_info = { + .ops = &max77705_hwmon_ops, + .info = max77705_info, +}; + +static int max77705_hwmon_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev; + struct regmap *regmap; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) + return -ENODEV; + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "max77705", regmap, + &max77705_chip_info, NULL); + if (IS_ERR(hwmon_dev)) + return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev), + "Unable to register hwmon device\n"); + + return 0; +}; + +static struct platform_driver max77705_hwmon_driver = { + .driver = { + .name = "max77705-hwmon", + }, + .probe = max77705_hwmon_probe, +}; + +module_platform_driver(max77705_hwmon_driver); + +MODULE_AUTHOR("Dzmitry Sankouski "); +MODULE_DESCRIPTION("MAX77705 monitor driver"); +MODULE_LICENSE("GPL"); From 928e1c67c13ec2a88982bc60ea9fbcb9c3d307bc Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Mon, 14 Apr 2025 06:35:00 +0800 Subject: [PATCH 31/48] dt-bindings: hwmon: Add Sophgo SG2044 external hardware monitor support The MCU device on SG2044 exposes the same interface as SG2042, which is already supported by the kernel. Add compatible string for monitor device of SG2044. Signed-off-by: Inochi Amaoto Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250413223507.46480-7-inochiama@gmail.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml b/Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml index f0667ac41d75..b76805d39427 100644 --- a/Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml +++ b/Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml @@ -11,7 +11,11 @@ maintainers: properties: compatible: - const: sophgo,sg2042-hwmon-mcu + oneOf: + - items: + - const: sophgo,sg2044-hwmon-mcu + - const: sophgo,sg2042-hwmon-mcu + - const: sophgo,sg2042-hwmon-mcu reg: maxItems: 1 From a60d965931a8957f175cab2a5f594053fbdd9a58 Mon Sep 17 00:00:00 2001 From: Daniel Grainger Date: Thu, 1 May 2025 15:19:53 +0200 Subject: [PATCH 32/48] hwmon: (asus-ec-sensors) add ROG MAXIMUS Z90 Formula. Board and chipset information is from LibreHardwareMonitor [1]. [1] https://github.com/LibreHardwareMonitor/LibreHardwareMonitor Signed-off-by: Daniel Grainger Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250501132009.726742-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index d2be9db29614..3f5a2d1ffe65 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -20,6 +20,7 @@ Supported boards: * ROG CROSSHAIR X670E GENE * ROG MAXIMUS XI HERO * ROG MAXIMUS XI HERO (WI-FI) + * ROG MAXIMUS Z690 FORMULA * ROG STRIX B550-E GAMING * ROG STRIX B550-I GAMING * ROG STRIX X570-E GAMING diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 7f2389db8923..d1f913762318 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -283,6 +283,14 @@ 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_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe), + [ec_sensor_temp_water_in] = + EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), + [ec_sensor_temp_water_out] = + EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), + [ec_sensor_temp_water_block_in] = + EC_SENSOR("Water_Block_In", hwmon_temp, 1, 0x01, 0x02), }; /* Shortcuts for common combinations */ @@ -407,6 +415,13 @@ static const struct ec_board_info board_info_maximus_xi_hero = { .family = family_intel_300_series, }; +static const struct ec_board_info board_info_maximus_z690_formula = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW, + .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, + .family = family_intel_600_series, +}; + static const struct ec_board_info board_info_crosshair_viii_impact = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | @@ -542,6 +557,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_maximus_xi_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", &board_info_maximus_xi_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z690 FORMULA", + &board_info_maximus_z690_formula), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT", &board_info_crosshair_viii_impact), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", From 25be318324563c63cbd9cb53186203a08d2f83a1 Mon Sep 17 00:00:00 2001 From: Alexei Safin Date: Thu, 24 Apr 2025 23:26:54 +0300 Subject: [PATCH 33/48] hwmon: (asus-ec-sensors) check sensor index in read_string() Prevent a potential invalid memory access when the requested sensor is not found. find_ec_sensor_index() may return a negative value (e.g. -ENOENT), but its result was used without checking, which could lead to undefined behavior when passed to get_sensor_info(). Add a proper check to return -EINVAL if sensor_index is negative. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: d0ddfd241e57 ("hwmon: (asus-ec-sensors) add driver for ASUS EC") Signed-off-by: Alexei Safin Link: https://lore.kernel.org/r/20250424202654.5902-1-a.safin@rosa.ru [groeck: Return error code returned from find_ec_sensor_index] Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index d1f913762318..964673c67597 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -955,6 +955,10 @@ static int asus_ec_hwmon_read_string(struct device *dev, { struct ec_sensors_data *state = dev_get_drvdata(dev); int sensor_index = find_ec_sensor_index(state, type, channel); + + if (sensor_index < 0) + return sensor_index; + *str = get_sensor_info(state, sensor_index)->label; return 0; From a92d87d2f90e28516d1934f22c559ee85330bb6d Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Wed, 23 Apr 2025 21:08:29 -0400 Subject: [PATCH 34/48] hwmon: (isl28022, nct7363) Convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20250424010829.2610-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/isl28022.c | 2 +- drivers/hwmon/nct7363.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/isl28022.c b/drivers/hwmon/isl28022.c index 1fb9864635db..0f27ac556f51 100644 --- a/drivers/hwmon/isl28022.c +++ b/drivers/hwmon/isl28022.c @@ -301,7 +301,7 @@ static const struct regmap_config isl28022_regmap_config = { .writeable_reg = isl28022_is_writeable_reg, .volatile_reg = isl28022_is_volatile_reg, .val_format_endian = REGMAP_ENDIAN_BIG, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; diff --git a/drivers/hwmon/nct7363.c b/drivers/hwmon/nct7363.c index be7bf32f6e68..e13ab918b1ab 100644 --- a/drivers/hwmon/nct7363.c +++ b/drivers/hwmon/nct7363.c @@ -391,7 +391,7 @@ static const struct regmap_config nct7363_regmap_config = { .val_bits = 8, .use_single_read = true, .use_single_write = true, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = nct7363_regmap_is_volatile, }; From 0c0c84e4869895091c3029bc2625caa0feec99b9 Mon Sep 17 00:00:00 2001 From: pkarc Date: Sun, 4 May 2025 01:00:06 +0200 Subject: [PATCH 35/48] hwmon: (ausus-ec-sensors) add MAXIMUS VI HERO. Add support for MAXIMUS VI HERO. Signed-off-by: pkarc Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250503230020.1005801-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 3f5a2d1ffe65..816d1f9947ea 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -4,6 +4,7 @@ Kernel driver asus_ec_sensors ================================= Supported boards: + * MAXIMUS VI HERO * PRIME X470-PRO * PRIME X570-PRO * PRIME X670E-PRO WIFI diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 964673c67597..e0a95197c71b 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -313,6 +313,15 @@ struct ec_board_info { enum board_family family; }; +static const struct ec_board_info board_info_maximus_vi_hero = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_intel_300_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 | @@ -527,6 +536,8 @@ static const struct ec_board_info board_info_tuf_gaming_x670e_plus = { } static const struct dmi_system_id dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("MAXIMUS VI HERO", + &board_info_maximus_vi_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO", &board_info_prime_x470_pro), DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", From 024e5cf24327184bb363b6e330550394982b93f8 Mon Sep 17 00:00:00 2001 From: Wenliang Yan Date: Tue, 6 May 2025 01:37:38 -0400 Subject: [PATCH 36/48] hwmon: (ina238) Add ina238_config to save configurations for different chips Add structure ina238_config to store proprietary properties for different chips to meet different chip adaptations Signed-off-by: Wenliang Yan Link: https://lore.kernel.org/r/20250506053741.4837-2-wenliang202407@163.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 57 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 2d9f12f68d50..c8d6bd3041b3 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -102,7 +102,20 @@ static const struct regmap_config ina238_regmap_config = { .val_bits = 16, }; +enum ina238_ids { ina238, ina237 }; + +struct ina238_config { + bool has_power_highest; /* chip detection power peak */ + bool has_energy; /* chip detection energy */ + u8 temp_shift; /* fixed parameters for temp calculate */ + u32 power_calculate_factor; /* fixed parameters for power calculate */ + u16 config_default; /* Power-on default state */ + int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */ + int temp_lsb; /* use for temperature calculate */ +}; + struct ina238_data { + const struct ina238_config *config; struct i2c_client *client; struct mutex config_lock; struct regmap *regmap; @@ -110,6 +123,27 @@ struct ina238_data { int gain; }; +static const struct ina238_config ina238_config[] = { + [ina238] = { + .has_energy = false, + .has_power_highest = false, + .temp_shift = 4, + .power_calculate_factor = 20, + .config_default = INA238_CONFIG_DEFAULT, + .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, + .temp_lsb = INA238_DIE_TEMP_LSB, + }, + [ina237] = { + .has_energy = false, + .has_power_highest = false, + .temp_shift = 4, + .power_calculate_factor = 20, + .config_default = INA238_CONFIG_DEFAULT, + .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, + .temp_lsb = INA238_DIE_TEMP_LSB, + }, +}; + static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val) { u8 data[3]; @@ -536,14 +570,20 @@ static int ina238_probe(struct i2c_client *client) struct device *dev = &client->dev; struct device *hwmon_dev; struct ina238_data *data; + enum ina238_ids chip; int config; int ret; + chip = (uintptr_t)i2c_get_match_data(client); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->client = client; + /* set the device type */ + data->config = &ina238_config[chip]; + mutex_init(&data->config_lock); data->regmap = devm_regmap_init_i2c(client, &ina238_regmap_config); @@ -570,7 +610,7 @@ static int ina238_probe(struct i2c_client *client) } /* Setup CONFIG register */ - config = INA238_CONFIG_DEFAULT; + config = data->config->config_default; if (data->gain == 1) config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */ ret = regmap_write(data->regmap, INA238_CONFIG, config); @@ -616,15 +656,22 @@ static int ina238_probe(struct i2c_client *client) } static const struct i2c_device_id ina238_id[] = { - { "ina238" }, + { "ina237", ina237 }, + { "ina238", ina238 }, { } }; MODULE_DEVICE_TABLE(i2c, ina238_id); static const struct of_device_id __maybe_unused ina238_of_match[] = { - { .compatible = "ti,ina237" }, - { .compatible = "ti,ina238" }, - { }, + { + .compatible = "ti,ina237", + .data = (void *)ina237 + }, + { + .compatible = "ti,ina238", + .data = (void *)ina238 + }, + { } }; MODULE_DEVICE_TABLE(of, ina238_of_match); From 00ca54bee4b25fb23534f9d8ba3a75ea0f1abc66 Mon Sep 17 00:00:00 2001 From: Wenliang Yan Date: Tue, 6 May 2025 01:37:41 -0400 Subject: [PATCH 37/48] dt-bindings: Add SQ52206 to ina2xx devicetree bindings Add the sq52206 compatible to the ina2xx.yaml Signed-off-by: Wenliang Yan Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250506053741.4837-5-wenliang202407@163.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index bc03781342c0..d1fb7b9abda0 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -19,6 +19,7 @@ description: | properties: compatible: enum: + - silergy,sq52206 - silergy,sy24655 - ti,ina209 - ti,ina219 @@ -58,6 +59,9 @@ properties: shunt voltage, and a value of 4 maps to ADCRANGE=0 such that a wider voltage range is used. + For SQ52206,the shunt-gain value 1 mapps to ADCRANGE=10/11, the value 2 + mapps to ADCRANGE=01, and the value 4 mapps to ADCRANGE=00. + The default value is device dependent, and is defined by the reset value of PGA/ADCRANGE in the respective configuration registers. $ref: /schemas/types.yaml#/definitions/uint32 @@ -97,6 +101,7 @@ allOf: compatible: contains: enum: + - silergy,sq52206 - silergy,sy24655 - ti,ina209 - ti,ina219 From 6daaf15a11731c32f1c34920b1d22903704375f9 Mon Sep 17 00:00:00 2001 From: Wenliang Yan Date: Tue, 6 May 2025 01:37:39 -0400 Subject: [PATCH 38/48] hwmon: (ina238) Add support for SQ52206 Add support for SQ52206 to the Ina238 driver. Add registers, increase compatibility, add compatibility programs for multiple chips. Signed-off-by: Wenliang Yan Link: https://lore.kernel.org/r/20250506053741.4837-3-wenliang202407@163.com [groeck: Fixed checkpatch issues (alignment, {} placing)] Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina238.rst | 15 +++++ drivers/hwmon/ina238.c | 116 ++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 9 deletions(-) diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst index d9f479984420..d1b93cf8627f 100644 --- a/Documentation/hwmon/ina238.rst +++ b/Documentation/hwmon/ina238.rst @@ -14,6 +14,12 @@ Supported chips: Datasheet: https://www.ti.com/lit/gpn/ina238 + * Silergy SQ52206 + + Prefix: 'SQ52206' + + Addresses: I2C 0x40 - 0x4f + Author: Nathan Rossi Description @@ -54,3 +60,12 @@ temp1_input Die temperature measurement (mC) temp1_max Maximum die temperature threshold (mC) temp1_max_alarm Maximum die temperature alarm ======================= ======================================================= + +Additional sysfs entries for sq52206 +------------------------------------ + +======================= ======================================================= +energy1_input Energy measurement (mJ) + +power1_input_highest Peak Power (uW) +======================= ======================================================= diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index c8d6bd3041b3..48b61328eb35 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -21,11 +21,14 @@ #define INA238_CONFIG 0x0 #define INA238_ADC_CONFIG 0x1 #define INA238_SHUNT_CALIBRATION 0x2 +#define SQ52206_SHUNT_TEMPCO 0x3 #define INA238_SHUNT_VOLTAGE 0x4 #define INA238_BUS_VOLTAGE 0x5 #define INA238_DIE_TEMP 0x6 #define INA238_CURRENT 0x7 #define INA238_POWER 0x8 +#define SQ52206_ENERGY 0x9 +#define SQ52206_CHARGE 0xa #define INA238_DIAG_ALERT 0xb #define INA238_SHUNT_OVER_VOLTAGE 0xc #define INA238_SHUNT_UNDER_VOLTAGE 0xd @@ -33,9 +36,12 @@ #define INA238_BUS_UNDER_VOLTAGE 0xf #define INA238_TEMP_LIMIT 0x10 #define INA238_POWER_LIMIT 0x11 +#define SQ52206_POWER_PEAK 0x20 #define INA238_DEVICE_ID 0x3f /* not available on INA237 */ #define INA238_CONFIG_ADCRANGE BIT(4) +#define SQ52206_CONFIG_ADCRANGE_HIGH BIT(4) +#define SQ52206_CONFIG_ADCRANGE_LOW BIT(3) #define INA238_DIAG_ALERT_TMPOL BIT(7) #define INA238_DIAG_ALERT_SHNTOL BIT(6) @@ -44,12 +50,13 @@ #define INA238_DIAG_ALERT_BUSUL BIT(3) #define INA238_DIAG_ALERT_POL BIT(2) -#define INA238_REGISTERS 0x11 +#define INA238_REGISTERS 0x20 #define INA238_RSHUNT_DEFAULT 10000 /* uOhm */ /* Default configuration of device on reset. */ #define INA238_CONFIG_DEFAULT 0 +#define SQ52206_CONFIG_DEFAULT 0x0005 /* 16 sample averaging, 1052us conversion time, continuous mode */ #define INA238_ADC_CONFIG_DEFAULT 0xfb6a /* Configure alerts to be based on averaged value (SLOWALERT) */ @@ -87,14 +94,19 @@ * shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb) * * Current (mA) = register value * 20000 / rshunt / 4 * gain - * Power (W) = 0.2 * register value * 20000 / rshunt / 4 * gain + * Power (mW) = 0.2 * register value * 20000 / rshunt / 4 * gain + * (Specific for SQ52206) + * Power (mW) = 0.24 * register value * 20000 / rshunt / 4 * gain + * Energy (mJ) = 16 * 0.24 * register value * 20000 / rshunt / 4 * gain */ #define INA238_CALIBRATION_VALUE 16384 #define INA238_FIXED_SHUNT 20000 #define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */ #define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */ -#define INA238_DIE_TEMP_LSB 125 /* 125 mC/lsb */ +#define INA238_DIE_TEMP_LSB 1250000 /* 125.0000 mC/lsb */ +#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */ +#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */ static const struct regmap_config ina238_regmap_config = { .max_register = INA238_REGISTERS, @@ -102,7 +114,7 @@ static const struct regmap_config ina238_regmap_config = { .val_bits = 16, }; -enum ina238_ids { ina238, ina237 }; +enum ina238_ids { ina238, ina237, sq52206 }; struct ina238_config { bool has_power_highest; /* chip detection power peak */ @@ -142,6 +154,15 @@ static const struct ina238_config ina238_config[] = { .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, .temp_lsb = INA238_DIE_TEMP_LSB, }, + [sq52206] = { + .has_energy = true, + .has_power_highest = true, + .temp_shift = 0, + .power_calculate_factor = 24, + .config_default = SQ52206_CONFIG_DEFAULT, + .bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB, + .temp_lsb = SQ52206_DIE_TEMP_LSB, + }, }; static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val) @@ -160,6 +181,24 @@ static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val) return 0; } +static int ina238_read_reg40(const struct i2c_client *client, u8 reg, u64 *val) +{ + u8 data[5]; + u32 low; + int err; + + /* 40-bit register read */ + err = i2c_smbus_read_i2c_block_data(client, reg, 5, data); + if (err < 0) + return err; + if (err != 5) + return -EIO; + low = (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]; + *val = ((long long)data[0] << 32) | low; + + return 0; +} + static int ina238_read_in(struct device *dev, u32 attr, int channel, long *val) { @@ -330,6 +369,17 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) if (err) return err; + /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ + power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * + data->gain, 20 * data->rshunt); + /* Clamp value to maximum value of long */ + *val = clamp_val(power, 0, LONG_MAX); + break; + case hwmon_power_input_highest: + err = ina238_read_reg24(data->client, SQ52206_POWER_PEAK, ®val); + if (err) + return err; + /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain, 20 * data->rshunt); @@ -437,6 +487,25 @@ static int ina238_write_temp(struct device *dev, u32 attr, long val) return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval); } +static ssize_t energy1_input_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int ret; + u64 regval; + u64 energy; + + ret = ina238_read_reg40(data->client, SQ52206_ENERGY, ®val); + if (ret) + return ret; + + /* result in mJ */ + energy = div_u64(regval * INA238_FIXED_SHUNT * data->gain * 16 * + data->config->power_calculate_factor, 4 * 100 * data->rshunt); + + return sysfs_emit(buf, "%llu\n", energy); +} + static int ina238_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -456,7 +525,7 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type, } static int ina238_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long val) + u32 attr, int channel, long val) { struct ina238_data *data = dev_get_drvdata(dev); int err; @@ -486,6 +555,9 @@ static umode_t ina238_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct ina238_data *data = drvdata; + bool has_power_highest = data->config->has_power_highest; + switch (type) { case hwmon_in: switch (attr) { @@ -513,6 +585,10 @@ static umode_t ina238_is_visible(const void *drvdata, return 0444; case hwmon_power_max: return 0644; + case hwmon_power_input_highest: + if (has_power_highest) + return 0444; + return 0; default: return 0; } @@ -546,7 +622,8 @@ static const struct hwmon_channel_info * const ina238_info[] = { HWMON_C_INPUT), HWMON_CHANNEL_INFO(power, /* 0: power */ - HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM), + HWMON_P_INPUT | HWMON_P_MAX | + HWMON_P_MAX_ALARM | HWMON_P_INPUT_HIGHEST), HWMON_CHANNEL_INFO(temp, /* 0: die temperature */ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM), @@ -564,6 +641,15 @@ static const struct hwmon_chip_info ina238_chip_info = { .info = ina238_info, }; +/* energy attributes are 5 bytes wide so we need u64 */ +static DEVICE_ATTR_RO(energy1_input); + +static struct attribute *ina238_attrs[] = { + &dev_attr_energy1_input.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ina238); + static int ina238_probe(struct i2c_client *client) { struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); @@ -604,15 +690,21 @@ static int ina238_probe(struct i2c_client *client) /* load shunt gain value */ if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0) data->gain = 4; /* Default of ADCRANGE = 0 */ - if (data->gain != 1 && data->gain != 4) { + if (data->gain != 1 && data->gain != 2 && data->gain != 4) { dev_err(dev, "invalid shunt gain value %u\n", data->gain); return -EINVAL; } /* Setup CONFIG register */ config = data->config->config_default; - if (data->gain == 1) + if (chip == sq52206) { + if (data->gain == 1) + config |= SQ52206_CONFIG_ADCRANGE_HIGH; /* ADCRANGE = 10/11 is /1 */ + else if (data->gain == 2) + config |= SQ52206_CONFIG_ADCRANGE_LOW; /* ADCRANGE = 01 is /2 */ + } else if (data->gain == 1) { config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */ + } ret = regmap_write(data->regmap, INA238_CONFIG, config); if (ret < 0) { dev_err(dev, "error configuring the device: %d\n", ret); @@ -645,7 +737,8 @@ static int ina238_probe(struct i2c_client *client) hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &ina238_chip_info, - NULL); + data->config->has_energy ? + ina238_groups : NULL); if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); @@ -658,6 +751,7 @@ static int ina238_probe(struct i2c_client *client) static const struct i2c_device_id ina238_id[] = { { "ina237", ina237 }, { "ina238", ina238 }, + { "sq52206", sq52206 }, { } }; MODULE_DEVICE_TABLE(i2c, ina238_id); @@ -671,6 +765,10 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = { .compatible = "ti,ina238", .data = (void *)ina238 }, + { + .compatible = "silergy,sq52206", + .data = (void *)sq52206 + }, { } }; MODULE_DEVICE_TABLE(of, ina238_of_match); From 0d9f596b1fe3445040c05d0fa3842224fc77810b Mon Sep 17 00:00:00 2001 From: Wenliang Yan Date: Tue, 6 May 2025 01:37:40 -0400 Subject: [PATCH 39/48] hwmon: (ina238) Modify the calculation formula to adapt to different chips Modify the calculation formula to adapt to different chips. Signed-off-by: Wenliang Yan Link: https://lore.kernel.org/r/20250506053741.4837-4-wenliang202407@163.com [groeck: Fixed checkpatch issue (space before and after arithmetic operators)] Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 47 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 48b61328eb35..a4a41742786b 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -270,10 +270,10 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel, regval = (s16)regval; if (channel == 0) /* gain of 1 -> LSB / 4 */ - *val = (regval * INA238_SHUNT_VOLTAGE_LSB) / - (1000 * (4 - data->gain + 1)); + *val = (regval * INA238_SHUNT_VOLTAGE_LSB) * + data->gain / (1000 * 4); else - *val = (regval * INA238_BUS_VOLTAGE_LSB) / 1000; + *val = (regval * data->config->bus_voltage_lsb) / 1000; break; case hwmon_in_max_alarm: case hwmon_in_min_alarm: @@ -298,8 +298,8 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel, case 0: /* signed value, clamp to max range +/-163 mV */ regval = clamp_val(val, -163, 163); - regval = (regval * 1000 * (4 - data->gain + 1)) / - INA238_SHUNT_VOLTAGE_LSB; + regval = (regval * 1000 * 4) / + (INA238_SHUNT_VOLTAGE_LSB * data->gain); regval = clamp_val(regval, S16_MIN, S16_MAX); switch (attr) { @@ -315,7 +315,7 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel, case 1: /* signed value, positive values only. Clamp to max 102.396 V */ regval = clamp_val(val, 0, 102396); - regval = (regval * 1000) / INA238_BUS_VOLTAGE_LSB; + regval = (regval * 1000) / data->config->bus_voltage_lsb; regval = clamp_val(regval, 0, S16_MAX); switch (attr) { @@ -370,8 +370,8 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) return err; /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ - power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * - data->gain, 20 * data->rshunt); + power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain * + data->config->power_calculate_factor, 4 * 100 * data->rshunt); /* Clamp value to maximum value of long */ *val = clamp_val(power, 0, LONG_MAX); break; @@ -381,8 +381,8 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) return err; /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ - power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * - data->gain, 20 * data->rshunt); + power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain * + data->config->power_calculate_factor, 4 * 100 * data->rshunt); /* Clamp value to maximum value of long */ *val = clamp_val(power, 0, LONG_MAX); break; @@ -395,8 +395,8 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) * Truncated 24-bit compare register, lower 8-bits are * truncated. Same conversion to/from uW as POWER register. */ - power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * - data->gain, 20 * data->rshunt); + power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * data->gain * + data->config->power_calculate_factor, 4 * 100 * data->rshunt); /* Clamp value to maximum value of long */ *val = clamp_val(power, 0, LONG_MAX); break; @@ -428,8 +428,8 @@ static int ina238_write_power(struct device *dev, u32 attr, long val) * register. */ regval = clamp_val(val, 0, LONG_MAX); - regval = div_u64(val * 20ULL * data->rshunt, - 1000ULL * INA238_FIXED_SHUNT * data->gain); + regval = div_u64(val * 4 * 100 * data->rshunt, data->config->power_calculate_factor * + 1000ULL * INA238_FIXED_SHUNT * data->gain); regval = clamp_val(regval >> 8, 0, U16_MAX); return regmap_write(data->regmap, INA238_POWER_LIMIT, regval); @@ -446,17 +446,17 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val) err = regmap_read(data->regmap, INA238_DIE_TEMP, ®val); if (err) return err; - - /* Signed, bits 15-4 of register, result in mC */ - *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB; + /* Signed, result in mC */ + *val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) * + (s64)data->config->temp_lsb, 10000); break; case hwmon_temp_max: err = regmap_read(data->regmap, INA238_TEMP_LIMIT, ®val); if (err) return err; - - /* Signed, bits 15-4 of register, result in mC */ - *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB; + /* Signed, result in mC */ + *val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) * + (s64)data->config->temp_lsb, 10000); break; case hwmon_temp_max_alarm: err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); @@ -480,9 +480,10 @@ static int ina238_write_temp(struct device *dev, u32 attr, long val) if (attr != hwmon_temp_max) return -EOPNOTSUPP; - /* Signed, bits 15-4 of register */ - regval = (val / INA238_DIE_TEMP_LSB) << 4; - regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xfff0; + /* Signed */ + regval = clamp_val(val, -40000, 125000); + regval = div_s64(val * 10000, data->config->temp_lsb) << data->config->temp_shift; + regval = clamp_val(regval, S16_MIN, S16_MAX) & (0xffff << data->config->temp_shift); return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval); } From e00fe40065b874f5622949d5a6734a632b3fd76a Mon Sep 17 00:00:00 2001 From: Pawel Dembicki Date: Sun, 11 May 2025 05:55:44 +0200 Subject: [PATCH 40/48] dt-bindings: hwmon: Add bindings for mpq8785 driver Add device tree bindings for Monolithic Power Systems MPQ8785, MPM82504 and MPM3695 PMBus-compliant voltage regulators. These bindings also documents the optional "mps,vout-fb-divider-ratio-permille" property. Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250511035701.2607947-2-paweldembicki@gmail.com Signed-off-by: Pawel Dembicki Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/mps,mpq8785.yaml | 74 +++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 2 - 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml new file mode 100644 index 000000000000..90970a0433e9 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.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/mps,mpq8785.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Monolithic Power Systems Multiphase Voltage Regulators with PMBus + +maintainers: + - Charles Hsu + +description: + Monolithic Power Systems digital multiphase voltage regulators with PMBus. + +properties: + compatible: + enum: + - mps,mpm3695 + - mps,mpm3695-25 + - mps,mpm82504 + - mps,mpq8785 + + reg: + maxItems: 1 + + mps,vout-fb-divider-ratio-permille: + description: + The feedback resistor divider ratio, expressed in permille + (Vfb / Vout * 1000). This value is written to the PMBUS_VOUT_SCALE_LOOP + register and is required for correct output voltage presentation. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 4095 + default: 706 + +required: + - compatible + - reg + +allOf: + - if: + properties: + compatible: + enum: + - mps,mpm3695 + - mps,mpm82504 + then: + properties: + mps,vout-fb-divider-ratio-permille: + maximum: 1023 + + - if: + properties: + compatible: + const: mps,mpq8785 + then: + properties: + mps,vout-fb-divider-ratio-permille: + maximum: 2047 + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@30 { + compatible = "mps,mpm82504"; + reg = <0x30>; + mps,vout-fb-divider-ratio-permille = <600>; + }; + }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 8da408107e55..7c1c0cc29655 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -293,8 +293,6 @@ properties: - mps,mp5990 # Monolithic Power Systems Inc. digital step-down converter mp9941 - mps,mp9941 - # Monolithic Power Systems Inc. synchronous step-down converter mpq8785 - - mps,mpq8785 # Temperature sensor with integrated fan control - national,lm63 # Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor From 1bc6020dc400ea8290a7b26aa4365d4568e23e27 Mon Sep 17 00:00:00 2001 From: Pawel Dembicki Date: Sun, 11 May 2025 05:55:45 +0200 Subject: [PATCH 41/48] hwmon: pmbus: mpq8785: Prepare driver for multiple device support Refactor the driver to support multiple Monolithic Power Systems devices. Introduce chip ID handling based on device tree matching. No functional changes intended. Signed-off-by: Pawel Dembicki Link: https://lore.kernel.org/r/20250511035701.2607947-3-paweldembicki@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/mpq8785.c | 38 +++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c index 331c274ca892..d0ac294b4bbc 100644 --- a/drivers/hwmon/pmbus/mpq8785.c +++ b/drivers/hwmon/pmbus/mpq8785.c @@ -8,6 +8,8 @@ #include #include "pmbus.h" +enum chips { mpq8785 }; + static int mpq8785_identify(struct i2c_client *client, struct pmbus_driver_info *info) { @@ -53,26 +55,46 @@ static struct pmbus_driver_info mpq8785_info = { PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, - .identify = mpq8785_identify, -}; - -static int mpq8785_probe(struct i2c_client *client) -{ - return pmbus_do_probe(client, &mpq8785_info); }; static const struct i2c_device_id mpq8785_id[] = { - { "mpq8785" }, + { "mpq8785", mpq8785 }, { }, }; MODULE_DEVICE_TABLE(i2c, mpq8785_id); static const struct of_device_id __maybe_unused mpq8785_of_match[] = { - { .compatible = "mps,mpq8785" }, + { .compatible = "mps,mpq8785", .data = (void *)mpq8785 }, {} }; MODULE_DEVICE_TABLE(of, mpq8785_of_match); +static int mpq8785_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + enum chips chip_id; + + info = devm_kmemdup(dev, &mpq8785_info, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (dev->of_node) + chip_id = (kernel_ulong_t)of_device_get_match_data(dev); + else + chip_id = (kernel_ulong_t)i2c_get_match_data(client); + + switch (chip_id) { + case mpq8785: + info->identify = mpq8785_identify; + break; + default: + return -ENODEV; + } + + return pmbus_do_probe(client, info); +}; + static struct i2c_driver mpq8785_driver = { .driver = { .name = "mpq8785", From dc1a4bab48d513e426118e42b9c371d942ddb04b Mon Sep 17 00:00:00 2001 From: Pawel Dembicki Date: Sun, 11 May 2025 05:55:46 +0200 Subject: [PATCH 42/48] hwmon: pmbus: mpq8785: Implement VOUT feedback resistor divider ratio configuration Implement support for setting the VOUT_SCALE_LOOP PMBus register based on an optional device tree property "mps,vout-fb-divider-ratio-permille". This allows the driver to provide the correct VOUT value depending on the feedback voltage divider configuration for chips where the bootloader does not configure the VOUT_SCALE_LOOP register. Signed-off-by: Pawel Dembicki Link: https://lore.kernel.org/r/20250511035701.2607947-4-paweldembicki@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/mpq8785.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c index d0ac294b4bbc..2e7c0d0c3f81 100644 --- a/drivers/hwmon/pmbus/mpq8785.c +++ b/drivers/hwmon/pmbus/mpq8785.c @@ -5,11 +5,16 @@ #include #include +#include #include #include "pmbus.h" enum chips { mpq8785 }; +static u16 voltage_scale_loop_max_val[] = { + [mpq8785] = GENMASK(10, 0), +}; + static int mpq8785_identify(struct i2c_client *client, struct pmbus_driver_info *info) { @@ -74,6 +79,8 @@ static int mpq8785_probe(struct i2c_client *client) struct device *dev = &client->dev; struct pmbus_driver_info *info; enum chips chip_id; + u32 voltage_scale; + int ret; info = devm_kmemdup(dev, &mpq8785_info, sizeof(*info), GFP_KERNEL); if (!info) @@ -92,6 +99,17 @@ static int mpq8785_probe(struct i2c_client *client) return -ENODEV; } + if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", + &voltage_scale)) { + if (voltage_scale > voltage_scale_loop_max_val[chip_id]) + return -EINVAL; + + ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, + voltage_scale); + if (ret) + return ret; + } + return pmbus_do_probe(client, info); }; From c27291468eb957b11dc81cd35fad36faf0861c07 Mon Sep 17 00:00:00 2001 From: Pawel Dembicki Date: Sun, 11 May 2025 05:55:47 +0200 Subject: [PATCH 43/48] hwmon: pmbus: mpq8785: Add support for MPM82504 Add support for the Monolithic Power Systems MPM82504 digital voltage regulator. MPM82504 uses PMBus direct format for voltage output. Tested with device tree based matching. Signed-off-by: Pawel Dembicki Link: https://lore.kernel.org/r/20250511035701.2607947-5-paweldembicki@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/mpq8785.rst | 20 +++++++++++++++----- drivers/hwmon/pmbus/mpq8785.c | 29 ++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/Documentation/hwmon/mpq8785.rst b/Documentation/hwmon/mpq8785.rst index bf8176b87086..b91fefb1a84c 100644 --- a/Documentation/hwmon/mpq8785.rst +++ b/Documentation/hwmon/mpq8785.rst @@ -5,6 +5,7 @@ Kernel driver mpq8785 Supported chips: + * MPS MPM82504 * MPS MPQ8785 Prefix: 'mpq8785' @@ -14,6 +15,14 @@ Author: Charles Hsu Description ----------- +The MPM82504 is a quad 25A, scalable, fully integrated power module with a PMBus +interface. The device offers a complete power solution that achieves up to 25A +per output channel. The MPM82504 has four output channels that can be paralleled +to provide 50A, 75A, or 100A of output current for flexible configurations. +The device can also operate in parallel with the MPM3695-100 and additional +MPM82504 devices to provide a higher output current. The MPM82504 operates +at high efficiency across a wide load range. + The MPQ8785 is a fully integrated, PMBus-compatible, high-frequency, synchronous buck converter. The MPQ8785 offers a very compact solution that achieves up to 40A output current per phase, with excellent load and line regulation over a @@ -23,18 +32,19 @@ output current load range. The PMBus interface provides converter configurations and key parameters monitoring. -The MPQ8785 adopts MPS's proprietary multi-phase digital constant-on-time (MCOT) +The devices 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 MPQ8785 devices to be connected in parallel -with excellent current sharing and phase interleaving for high-current +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). -The MPQ8785 requires a minimal number of readily available, standard external -components, and is available in a TLGA (5mmx6mm) package. +All supported modules require a minimal number of readily available, standard +external components. The MPM82504 is available in a BGA (15mmx30mmx5.18mm) +package and the MPQ8785 is available in a TLGA (5mmx6mm) package. Device compliant with: diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c index 2e7c0d0c3f81..8827d5a57310 100644 --- a/drivers/hwmon/pmbus/mpq8785.c +++ b/drivers/hwmon/pmbus/mpq8785.c @@ -4,14 +4,18 @@ */ #include +#include #include #include #include #include "pmbus.h" -enum chips { mpq8785 }; +#define MPM82504_READ_TEMPERATURE_1_SIGN_POS 9 + +enum chips { mpm82504, mpq8785 }; static u16 voltage_scale_loop_max_val[] = { + [mpm82504] = GENMASK(9, 0), [mpq8785] = GENMASK(10, 0), }; @@ -41,6 +45,20 @@ static int mpq8785_identify(struct i2c_client *client, return 0; }; +static int mpm82504_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + ret = pmbus_read_word_data(client, page, phase, reg); + + if (ret < 0 || reg != PMBUS_READ_TEMPERATURE_1) + return ret; + + /* Fix PMBUS_READ_TEMPERATURE_1 signedness */ + return sign_extend32(ret, MPM82504_READ_TEMPERATURE_1_SIGN_POS) & 0xffff; +} + static struct pmbus_driver_info mpq8785_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = direct, @@ -63,12 +81,14 @@ static struct pmbus_driver_info mpq8785_info = { }; static const struct i2c_device_id mpq8785_id[] = { + { "mpm82504", mpm82504 }, { "mpq8785", mpq8785 }, { }, }; MODULE_DEVICE_TABLE(i2c, mpq8785_id); static const struct of_device_id __maybe_unused mpq8785_of_match[] = { + { .compatible = "mps,mpm82504", .data = (void *)mpm82504 }, { .compatible = "mps,mpq8785", .data = (void *)mpq8785 }, {} }; @@ -92,6 +112,13 @@ static int mpq8785_probe(struct i2c_client *client) chip_id = (kernel_ulong_t)i2c_get_match_data(client); switch (chip_id) { + case mpm82504: + info->format[PSC_VOLTAGE_OUT] = direct; + info->m[PSC_VOLTAGE_OUT] = 8; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = 2; + info->read_word_data = mpm82504_read_word_data; + break; case mpq8785: info->identify = mpq8785_identify; break; From 8fcefe7812f246fff86d6f7ad8e166a564916202 Mon Sep 17 00:00:00 2001 From: Pawel Dembicki Date: Sun, 11 May 2025 05:55:48 +0200 Subject: [PATCH 44/48] hwmon: pmbus: mpq8785: Add support for MPM3695 family Add support for the Monolithic Power Systems MPM3695 family. It contains four devices with suffixes: -10, -20, -25 and -100. The device is PMBus compliant and shares characteristics with the MPM82504. MPM3695-25 has different VOLTAGE_SCALE_LOOP register size [11:0] and therefore needs to have a separate compatible entry. Tested with device tree based matching (MPM3695-10). Signed-off-by: Pawel Dembicki Link: https://lore.kernel.org/r/20250511035701.2607947-6-paweldembicki@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/mpq8785.rst | 13 +++++++++---- drivers/hwmon/pmbus/mpq8785.c | 10 +++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Documentation/hwmon/mpq8785.rst b/Documentation/hwmon/mpq8785.rst index b91fefb1a84c..198d5dfd7c30 100644 --- a/Documentation/hwmon/mpq8785.rst +++ b/Documentation/hwmon/mpq8785.rst @@ -5,6 +5,7 @@ Kernel driver mpq8785 Supported chips: + * MPS MPM3695 family * MPS MPM82504 * MPS MPQ8785 @@ -15,6 +16,14 @@ Author: Charles Hsu Description ----------- +The MPM3695 family is a scalable, ultra-thin, fully integrated power module with +a PMBus interface. It offers a complete power solution that achieves up to +10A (-10 variant), 20A (-25 variant), 25A (-20 variant), 100A (-100 variant) +of output current with excellent load and line regulation across a wide input +voltage range. It operates at high efficiency over a wide load range, and can +be parallled to deliver higher current. Variants -10,-20 and -100 have different +voltage scale configuration register range (10 bits) than -25 version (11 bits). + The MPM82504 is a quad 25A, scalable, fully integrated power module with a PMBus interface. The device offers a complete power solution that achieves up to 25A per output channel. The MPM82504 has four output channels that can be paralleled @@ -42,10 +51,6 @@ Fully integrated protection features include over-current protection (OCP), over-voltage protection (OVP), under-voltage protection (UVP), and over-temperature protection (OTP). -All supported modules require a minimal number of readily available, standard -external components. The MPM82504 is available in a BGA (15mmx30mmx5.18mm) -package and the MPQ8785 is available in a TLGA (5mmx6mm) package. - Device compliant with: - PMBus rev 1.3 interface. diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c index 8827d5a57310..1f56aaf4dde8 100644 --- a/drivers/hwmon/pmbus/mpq8785.c +++ b/drivers/hwmon/pmbus/mpq8785.c @@ -12,9 +12,11 @@ #define MPM82504_READ_TEMPERATURE_1_SIGN_POS 9 -enum chips { mpm82504, mpq8785 }; +enum chips { mpm3695, mpm3695_25, mpm82504, mpq8785 }; static u16 voltage_scale_loop_max_val[] = { + [mpm3695] = GENMASK(9, 0), + [mpm3695_25] = GENMASK(11, 0), [mpm82504] = GENMASK(9, 0), [mpq8785] = GENMASK(10, 0), }; @@ -81,6 +83,8 @@ static struct pmbus_driver_info mpq8785_info = { }; static const struct i2c_device_id mpq8785_id[] = { + { "mpm3695", mpm3695 }, + { "mpm3695-25", mpm3695_25 }, { "mpm82504", mpm82504 }, { "mpq8785", mpq8785 }, { }, @@ -88,6 +92,8 @@ static const struct i2c_device_id mpq8785_id[] = { MODULE_DEVICE_TABLE(i2c, mpq8785_id); static const struct of_device_id __maybe_unused mpq8785_of_match[] = { + { .compatible = "mps,mpm3695", .data = (void *)mpm3695 }, + { .compatible = "mps,mpm3695-25", .data = (void *)mpm3695_25 }, { .compatible = "mps,mpm82504", .data = (void *)mpm82504 }, { .compatible = "mps,mpq8785", .data = (void *)mpq8785 }, {} @@ -112,6 +118,8 @@ static int mpq8785_probe(struct i2c_client *client) chip_id = (kernel_ulong_t)i2c_get_match_data(client); switch (chip_id) { + case mpm3695: + case mpm3695_25: case mpm82504: info->format[PSC_VOLTAGE_OUT] = direct; info->m[PSC_VOLTAGE_OUT] = 8; From 9b96f82c782c9d3f614c7d34e13382d91bb4854c Mon Sep 17 00:00:00 2001 From: Gerhard Engleder Date: Fri, 25 Apr 2025 21:48:23 +0200 Subject: [PATCH 45/48] hwmon: Add KEBA fan controller support The KEBA fan controller is found in the system FPGA of KEBA PLC devices. It detects if the fan is removed or blocked. For fans with tacho signal the monitoring of the speed of the fan is supported. It also supports to regulate the speed of fans with PWM input. The auxiliary device for this driver is instantiated by the cp500 misc driver. Signed-off-by: Gerhard Engleder Link: https://lore.kernel.org/r/20250425194823.54664-1-gerhard@engleder-embedded.com [groeck: Added various missing "break;" statements] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/kfan.rst | 39 ++++++ drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/kfan.c | 246 ++++++++++++++++++++++++++++++++++ 5 files changed, 297 insertions(+) create mode 100644 Documentation/hwmon/kfan.rst create mode 100644 drivers/hwmon/kfan.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 946b8a266d89..983aa0233e4a 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -107,6 +107,7 @@ Hardware Monitoring Kernel Drivers k10temp k8temp kbatt + kfan lan966x lineage-pem lm25066 diff --git a/Documentation/hwmon/kfan.rst b/Documentation/hwmon/kfan.rst new file mode 100644 index 000000000000..ce02dddfb4b8 --- /dev/null +++ b/Documentation/hwmon/kfan.rst @@ -0,0 +1,39 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver kfan +================== + +Supported chips: + + * KEBA fan controller (IP core in FPGA) + + Prefix: 'kfan' + +Authors: + + Gerhard Engleder + Petar Bojanic + +Description +----------- + +The KEBA fan controller is an IP core for FPGAs, which monitors the health +and controls the speed of a fan. The fan is typically used to cool the CPU +and the whole device. E.g., the CP500 FPGA includes this IP core to monitor +and control the fan of PLCs and the corresponding cp500 driver creates an +auxiliary device for the kfan driver. + +This driver provides information about the fan health to user space. +The user space shall be informed if the fan is removed or blocked. +Additionally, the speed in RPM is reported for fans with tacho signal. + +For fan control PWM is supported. For PWM 255 equals 100%. None-regulable +fans can be turned on with PWM 255 and turned off with PWM 0. + +====================== ==== =================================================== +Attribute R/W Contents +====================== ==== =================================================== +fan1_fault R Fan fault +fan1_input R Fan tachometer input (in RPM) +pwm1 RW Fan target duty cycle (0..255) +====================== ==== =================================================== diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 80e277448c71..01b21fe55ccd 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -345,6 +345,16 @@ config SENSORS_KBATT This driver can also be built as a module. If so, the module will be called kbatt. +config SENSORS_KFAN + tristate "KEBA fan controller support" + depends on KEBA_CP500 + help + This driver supports the fan controller found in KEBA system + FPGA devices. + + This driver can also be built as a module. If so, the module + will be called kfan. + config SENSORS_FAM15H_POWER tristate "AMD Family 15h processor power" depends on X86 && PCI && CPU_SUP_AMD diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 50df221f67c2..c34a0ae658e0 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -111,6 +111,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_KBATT) += kbatt.o +obj-$(CONFIG_SENSORS_KFAN) += kfan.o obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o diff --git a/drivers/hwmon/kfan.c b/drivers/hwmon/kfan.c new file mode 100644 index 000000000000..f353acb66749 --- /dev/null +++ b/drivers/hwmon/kfan.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 KEBA Industrial Automation GmbH + * + * Driver for KEBA fan controller FPGA IP core + * + */ + +#include +#include +#include +#include +#include +#include + +#define KFAN "kfan" + +#define KFAN_CONTROL_REG 0x04 + +#define KFAN_STATUS_REG 0x08 +#define KFAN_STATUS_PRESENT 0x01 +#define KFAN_STATUS_REGULABLE 0x02 +#define KFAN_STATUS_TACHO 0x04 +#define KFAN_STATUS_BLOCKED 0x08 + +#define KFAN_TACHO_REG 0x0c + +#define KFAN_DEFAULT_DIV 2 + +struct kfan { + void __iomem *base; + bool tacho; + bool regulable; + + /* hwmon API configuration */ + u32 fan_channel_config[2]; + struct hwmon_channel_info fan_info; + u32 pwm_channel_config[2]; + struct hwmon_channel_info pwm_info; + const struct hwmon_channel_info *info[3]; + struct hwmon_chip_info chip; +}; + +static bool kfan_get_fault(struct kfan *kfan) +{ + u8 status = ioread8(kfan->base + KFAN_STATUS_REG); + + if (!(status & KFAN_STATUS_PRESENT)) + return true; + + if (!kfan->tacho && (status & KFAN_STATUS_BLOCKED)) + return true; + + return false; +} + +static unsigned int kfan_count_to_rpm(u16 count) +{ + if (count == 0 || count == 0xffff) + return 0; + + return 5000000UL / (KFAN_DEFAULT_DIV * count); +} + +static unsigned int kfan_get_rpm(struct kfan *kfan) +{ + unsigned int rpm; + u16 count; + + count = ioread16(kfan->base + KFAN_TACHO_REG); + rpm = kfan_count_to_rpm(count); + + return rpm; +} + +static unsigned int kfan_get_pwm(struct kfan *kfan) +{ + return ioread8(kfan->base + KFAN_CONTROL_REG); +} + +static int kfan_set_pwm(struct kfan *kfan, long val) +{ + if (val < 0 || val > 0xff) + return -EINVAL; + + /* if none-regulable, then only 0 or 0xff can be written */ + if (!kfan->regulable && val > 0) + val = 0xff; + + iowrite8(val, kfan->base + KFAN_CONTROL_REG); + + return 0; +} + +static int kfan_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct kfan *kfan = dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return kfan_set_pwm(kfan, val); + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static int kfan_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct kfan *kfan = dev_get_drvdata(dev); + + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_fault: + *val = kfan_get_fault(kfan); + return 0; + case hwmon_fan_input: + *val = kfan_get_rpm(kfan); + return 0; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + *val = kfan_get_pwm(kfan); + return 0; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static umode_t kfan_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + return 0444; + case hwmon_fan_fault: + return 0444; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return 0644; + default: + break; + } + break; + default: + break; + } + + return 0; +} + +static const struct hwmon_ops kfan_hwmon_ops = { + .is_visible = kfan_is_visible, + .read = kfan_read, + .write = kfan_write, +}; + +static int kfan_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct keba_fan_auxdev *kfan_auxdev = + container_of(auxdev, struct keba_fan_auxdev, auxdev); + struct device *dev = &auxdev->dev; + struct device *hwmon_dev; + struct kfan *kfan; + u8 status; + + kfan = devm_kzalloc(dev, sizeof(*kfan), GFP_KERNEL); + if (!kfan) + return -ENOMEM; + + kfan->base = devm_ioremap_resource(dev, &kfan_auxdev->io); + if (IS_ERR(kfan->base)) + return PTR_ERR(kfan->base); + + status = ioread8(kfan->base + KFAN_STATUS_REG); + if (status & KFAN_STATUS_REGULABLE) + kfan->regulable = true; + if (status & KFAN_STATUS_TACHO) + kfan->tacho = true; + + /* fan */ + kfan->fan_channel_config[0] = HWMON_F_FAULT; + if (kfan->tacho) + kfan->fan_channel_config[0] |= HWMON_F_INPUT; + kfan->fan_info.type = hwmon_fan; + kfan->fan_info.config = kfan->fan_channel_config; + kfan->info[0] = &kfan->fan_info; + + /* PWM */ + kfan->pwm_channel_config[0] = HWMON_PWM_INPUT; + kfan->pwm_info.type = hwmon_pwm; + kfan->pwm_info.config = kfan->pwm_channel_config; + kfan->info[1] = &kfan->pwm_info; + + kfan->chip.ops = &kfan_hwmon_ops; + kfan->chip.info = kfan->info; + hwmon_dev = devm_hwmon_device_register_with_info(dev, KFAN, kfan, + &kfan->chip, NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct auxiliary_device_id kfan_devtype_aux[] = { + { .name = "keba.fan" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, kfan_devtype_aux); + +static struct auxiliary_driver kfan_driver_aux = { + .name = KFAN, + .id_table = kfan_devtype_aux, + .probe = kfan_probe, +}; +module_auxiliary_driver(kfan_driver_aux); + +MODULE_AUTHOR("Petar Bojanic "); +MODULE_AUTHOR("Gerhard Engleder "); +MODULE_DESCRIPTION("KEBA fan controller driver"); +MODULE_LICENSE("GPL"); From 0ddce5549012a3f3b9ddcf59822aa0d1be61e8e8 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Tue, 13 May 2025 14:17:39 +0300 Subject: [PATCH 46/48] hwmon: (lm75) Fix I3C transfer buffer pointer for incoming data Use the I3C private transfer input buffer pointer for incoming data instead of output buffer. For now this is harmless since both of those pointers are union members but may confuse when reading the code. Signed-off-by: Jarkko Nikula Link: https://lore.kernel.org/r/20250513111739.508886-1-jarkko.nikula@linux.intel.com Reviewed-by: Wolfram Sang Signed-off-by: Guenter Roeck --- drivers/hwmon/lm75.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index d95a3c6c245c..9b4875e2fd8d 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -622,7 +622,7 @@ static int lm75_i3c_reg_read(void *context, unsigned int reg, unsigned int *val) { .rnw = true, .len = 2, - .data.out = data->val_buf, + .data.in = data->val_buf, }, }; int ret; From b2446a16dbf2347a07af0cf994ca36576d94df77 Mon Sep 17 00:00:00 2001 From: Yikai Tsai Date: Mon, 19 May 2025 16:40:51 +0800 Subject: [PATCH 47/48] hwmon: (isl28022) Fix current reading calculation According to the ISL28022 datasheet, bit15 of the current register is representing -32768. Fix the calculation to properly handle this bit, ensuring correct measurements for negative values. Signed-off-by: Yikai Tsai Link: https://lore.kernel.org/r/20250519084055.3787-2-yikai.tsai.wiwynn@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/isl28022.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/isl28022.c b/drivers/hwmon/isl28022.c index 0f27ac556f51..c2e559dde63f 100644 --- a/drivers/hwmon/isl28022.c +++ b/drivers/hwmon/isl28022.c @@ -154,6 +154,7 @@ static int isl28022_read_current(struct device *dev, u32 attr, long *val) struct isl28022_data *data = dev_get_drvdata(dev); unsigned int regval; int err; + u16 sign_bit; switch (attr) { case hwmon_curr_input: @@ -161,8 +162,9 @@ static int isl28022_read_current(struct device *dev, u32 attr, long *val) ISL28022_REG_CURRENT, ®val); if (err < 0) return err; - *val = ((long)regval * 1250L * (long)data->gain) / - (long)data->shunt; + sign_bit = (regval >> 15) & 0x01; + *val = (((long)(((u16)regval) & 0x7FFF) - (sign_bit * 32768)) * + 1250L * (long)data->gain) / (long)data->shunt; break; default: return -EOPNOTSUPP; From 46d40b2479ab6417db4d7174f7a938c994435b3f Mon Sep 17 00:00:00 2001 From: Shinji Nomoto Date: Tue, 20 May 2025 15:27:04 +0900 Subject: [PATCH 48/48] doc: hwmon: acpi_power_meter: Add information about enabling the power capping feature. To enable the power capping feature of the acpi_power_meter driver on systems other than IBM products, you must explicitly specify the force_cap_on module parameter. Add information to the documentation about enabling the power capping feature with this driver, including the above, to improve user convenience. Signed-off-by: Shinji Nomoto Link: https://lore.kernel.org/r/20250520062707.1657667-1-fj5851bi@fujitsu.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/acpi_power_meter.rst | 29 +++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Documentation/hwmon/acpi_power_meter.rst b/Documentation/hwmon/acpi_power_meter.rst index 8628c1161015..a91403a2a26f 100644 --- a/Documentation/hwmon/acpi_power_meter.rst +++ b/Documentation/hwmon/acpi_power_meter.rst @@ -37,9 +37,16 @@ arbitrary strings that ACPI provides with the meter. The measures/ directory contains symlinks to the devices that this meter measures. Some computers have the ability to enforce a power cap in hardware. If this is -the case, the `power[1-*]_cap` and related sysfs files will appear. When the -average power consumption exceeds the cap, an ACPI event will be broadcast on -the netlink event socket and a poll notification will be sent to the +the case, the `power[1-*]_cap` and related sysfs files will appear. +For information on enabling the power cap feature, refer to the description +of the "force_on_cap" option in the "Module Parameters" chapter. +To use the power cap feature properly, you need to set appropriate value +(in microWatts) to the `power[1-*]_cap` sysfs files. +The value must be within the range between the minimum value at `power[1-]_cap_min` +and the maximum value at `power[1-]_cap_max (both in microWatts)`. + +When the average power consumption exceeds the cap, an ACPI event will be +broadcast on the netlink event socket and a poll notification will be sent to the appropriate `power[1-*]_alarm` file to indicate that capping has begun, and the hardware has taken action to reduce power consumption. Most likely this will result in reduced performance. @@ -52,3 +59,19 @@ follows: `power[1-*]_cap` will be notified if the firmware changes the power cap. `power[1-*]_interval` will be notified if the firmware changes the averaging interval. + +Module Parameters +----------------- + +* force_cap_on: bool + Forcefully enable the power capping feature to specify + the upper limit of the system's power consumption. + + By default, the driver's power capping feature is only + enabled on IBM products. + Therefore, on other systems that support power capping, + you will need to use the option to enable it. + + Note: power capping is potentially unsafe feature. + Please check the platform specifications to make sure + that capping is supported before using this option.