Re: [PATCH] hwmon: (pmbus/ltc4286) Add runtime shunt_resistor control

From: Guenter Roeck

Date: Thu Sep 03 2026 - 10:13:22 EST


On 9/3/26 02:23, Carl Lee wrote:
Expose a writable shunt_resistor sysfs attribute for ltc4286/ltc4287.

The driver still initializes rsense from the existing
shunt-resistor-micro-ohms firmware property (or 300 uOhm by default).
When userspace updates shunt_resistor, validate the value and update the
direct-format scaling coefficients used for current and power readings.

This enables runtime calibration without requiring a DT change or reboot.


What is the practical use case ? "to be able to do it" is not a use case.

Thanks,
Guenter

Signed-off-by: Carl Lee <carl.lee@xxxxxxx>
---
drivers/hwmon/pmbus/ltc4286.c | 86 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 82 insertions(+), 4 deletions(-)

diff --git a/drivers/hwmon/pmbus/ltc4286.c b/drivers/hwmon/pmbus/ltc4286.c
index 8715d380784a..e80c9ecea099 100644
--- a/drivers/hwmon/pmbus/ltc4286.c
+++ b/drivers/hwmon/pmbus/ltc4286.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/err.h>
+#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
@@ -16,6 +17,75 @@
#define LTC4286_MFR_ID_SIZE 3
+struct ltc4286_data {
+ struct pmbus_driver_info info;
+ u32 rsense;
+ bool vrange_low_enable;
+};
+
+#define to_ltc4286_data(x) container_of((x), struct ltc4286_data, info)
+
+static ssize_t ltc4286_rsense_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct ltc4286_data *data = to_ltc4286_data(info);
+
+ return sysfs_emit(buf, "%u\n", data->rsense);
+}
+
+static ssize_t ltc4286_rsense_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ const struct pmbus_driver_info *info_ro = pmbus_get_driver_info(client);
+ struct ltc4286_data *data = to_ltc4286_data(info_ro);
+ struct pmbus_driver_info *info = &data->info;
+ u32 rsense;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &rsense);
+ if (ret)
+ return ret;
+
+ if (rsense == 0)
+ return -EINVAL;
+
+ if (rsense > (INT_MAX / 1024))
+ return -EINVAL;
+
+ ret = pmbus_lock_interruptible(client);
+ if (ret)
+ return ret;
+
+ data->rsense = rsense;
+ info->m[PSC_CURRENT_OUT] = 1024 * rsense;
+ info->m[PSC_POWER] = data->vrange_low_enable ? 4 * rsense : rsense;
+
+ pmbus_unlock(client);
+
+ return count;
+}
+
+static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ltc4286_rsense, 0);
+
+static struct attribute *ltc4286_attrs[] = {
+ &sensor_dev_attr_shunt_resistor.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ltc4286_group = {
+ .attrs = ltc4286_attrs,
+};
+
+static const struct attribute_group *ltc4286_attribute_groups[] = {
+ &ltc4286_group,
+ NULL,
+};
+
/*
* Initialize the MBR as default settings which is referred to LTC4286 datasheet
* (March 22, 2022 version) table 3 page 16
@@ -55,6 +125,7 @@ static struct pmbus_driver_info ltc4286_info = {
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP,
+ .groups = ltc4286_attribute_groups,
};
static const struct i2c_device_id ltc4286_id[] = {
@@ -69,6 +140,7 @@ static int ltc4286_probe(struct i2c_client *client)
int ret;
const struct i2c_device_id *mid;
u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
+ struct ltc4286_data *data;
struct pmbus_driver_info *info;
u32 rsense;
int vrange_nval, vrange_oval;
@@ -114,11 +186,14 @@ static int ltc4286_probe(struct i2c_client *client)
if (rsense > (INT_MAX / 1024))
return -EINVAL;
- info = devm_kmemdup(&client->dev, &ltc4286_info, sizeof(*info),
- GFP_KERNEL);
- if (!info)
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
return -ENOMEM;
+ data->info = ltc4286_info;
+ data->rsense = rsense;
+ info = &data->info;
+
/* Check MFR1 CONFIG register bit 1 VRANGE_SELECT before driver loading */
vrange_oval = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1);
if (vrange_oval < 0)
@@ -126,7 +201,10 @@ static int ltc4286_probe(struct i2c_client *client)
"Failed to read manufacturer configuration one\n");
vrange_nval = vrange_oval;
- if (device_property_read_bool(&client->dev, "adi,vrange-low-enable")) {
+ data->vrange_low_enable =
+ device_property_read_bool(&client->dev, "adi,vrange-low-enable");
+
+ if (data->vrange_low_enable) {
vrange_nval &=
~VRANGE_SELECT_BIT; /* VRANGE_SELECT = 0, 25.6 volts */

---
base-commit: 32b6ef9a5d0eca44f9cd91f52f4faa89f145a0de
change-id: 20260903-hwmon-pmbus-ltc4286-add-runtime-shunt-resistor-control-44b1fcc18d38

Best regards,