[PATCH v2] hwmon: (pmbus/ltc4286) Add writable shunt_resistor sysfs attribute

Carl Lee posted 1 patch 2 weeks, 4 days ago
drivers/hwmon/pmbus/ltc4286.c | 96 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 92 insertions(+), 4 deletions(-)
[PATCH v2] hwmon: (pmbus/ltc4286) Add writable shunt_resistor sysfs attribute
Posted by Carl Lee 2 weeks, 4 days ago
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 <carl.lee@amd.com>
---
This series adds runtime shunt resistor calibration support for
ltc4286/ltc4287 via a writable sysfs attribute.

Practical use case: a single software image may be deployed across
qualified platform/BOM variants with different shunt resistor values.
After platform identification in userspace, rsense can be updated once
to match installed hardware, preventing mis-scaled current/power
telemetry.
---
Changes in v2:
- Clarify the practical deployment use case and refine commit message wording.
- Protect shunt_resistor show path with PMBus lock to avoid read/write race with store.
- Link to v1: https://lore.kernel.org/r/20260903-hwmon-pmbus-ltc4286-add-runtime-shunt-resistor-control-v1-1-aca2d49a1418@amd.com
---
 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 <linux/err.h>
+#include <linux/hwmon-sysfs.h>
 #include <linux/i2c.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
@@ -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[] = {
+	&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 +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, &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 +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 */
 

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

Best regards,
-- 
Carl Lee <carl.lee@amd.com>
Re: [PATCH v2] hwmon: (pmbus/ltc4286) Add writable shunt_resistor sysfs attribute
Posted by Guenter Roeck 2 weeks, 3 days ago
On Mon, Sep 07, 2026 at 03:58:32PM +0800, Carl Lee wrote:
> 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 <carl.lee@amd.com>

Applied.

Thanks,
Guenter