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

Carl Lee posted 1 patch 3 weeks, 1 day ago
drivers/hwmon/pmbus/ltc4286.c | 86 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 82 insertions(+), 4 deletions(-)
[PATCH] hwmon: (pmbus/ltc4286) Add runtime shunt_resistor control
Posted by Carl Lee 3 weeks, 1 day ago
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.

Signed-off-by: Carl Lee <carl.lee@amd.com>
---
 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,
-- 
Carl Lee <carl.lee@amd.com>
Re: [PATCH] hwmon: (pmbus/ltc4286) Add runtime shunt_resistor control
Posted by Guenter Roeck 3 weeks, 1 day ago
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@amd.com>
> ---
>   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,