From bc8c5bfc10896374182c1f20201446fc1abecdda Mon Sep 17 00:00:00 2001 From: Carl Lee Date: Mon, 7 Sep 2026 15:58:32 +0800 Subject: [PATCH] hwmon: (pmbus/ltc4286) Add writable shunt_resistor sysfs attribute The LTC4286/LTC4287 driver uses rsense to scale current and power telemetry. rsense is board-specific rather than a fixed device characteristic, and may differ across qualified BOM variants using the same software image. If rsense does not match the installed board configuration, current and power readings are mis-scaled. Add a writable shunt_resistor sysfs attribute so userspace can update the board calibration value after platform identification. Initialize rsense from shunt-resistor-micro-ohms as before (or the existing default when absent), and validate runtime updates before recalculating the current/power scaling coefficients. Signed-off-by: Carl Lee Link: https://patch.msgid.link/20260907-hwmon-pmbus-ltc4286-add-runtime-shunt-resistor-control-v2-1-5f47f7e5a1bb@amd.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ltc4286.c | 96 +++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/ltc4286.c b/drivers/hwmon/pmbus/ltc4286.c index 8715d380784a..8190706d1beb 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 +#include #include #include #include @@ -16,6 +17,85 @@ #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); + u32 rsense; + int ret; + + ret = pmbus_lock_interruptible(client); + if (ret) + return ret; + + rsense = data->rsense; + + pmbus_unlock(client); + + return sysfs_emit(buf, "%u\n", 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[] = { + <c4286_group, + NULL, +}; + /* * Initialize the MBR as default settings which is referred to LTC4286 datasheet * (March 22, 2022 version) table 3 page 16 @@ -55,6 +135,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 +150,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 +196,14 @@ static int ltc4286_probe(struct i2c_client *client) if (rsense > (INT_MAX / 1024)) return -EINVAL; - info = devm_kmemdup(&client->dev, <c4286_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 +211,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 */