[PATCH v2 3/4] hwmon: (pmbus_core) Support relative encoding of vout related commands

From: Abdurrahman Hussain

Date: Tue Oct 20 2026 - 03:47:48 EST


From: Lars Petter Mostad <lars.petter.mostad@xxxxxxxxxx>

PMBus 1.3 allows for relative encoding of voltages in certain output
voltage related commands. These commands are encoded relative to
VOUT_COMMAND. Support this when encoding/decoding commands.

Signed-off-by: Lars Petter Mostad <lars.petter.mostad@xxxxxxxxxx>
[abdurrahman: rebased; check VOUT_MODE for 0xff before testing the
relative bit; use signed division and clamp the user value before
scaling; reject a zero VOUT_COMMAND at probe; update the cached
VOUT_COMMAND only after a successful write]
Signed-off-by: Abdurrahman Hussain <abdurrahman@xxxxxxxxxx>
---
drivers/hwmon/pmbus/pmbus.c | 2 +-
drivers/hwmon/pmbus/pmbus_core.c | 103 ++++++++++++++++++++++++++++++++++-----
2 files changed, 91 insertions(+), 14 deletions(-)

diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c
index 7a7eac90d62e..dff1ef9c27f9 100644
--- a/drivers/hwmon/pmbus/pmbus.c
+++ b/drivers/hwmon/pmbus/pmbus.c
@@ -121,7 +121,7 @@ static int pmbus_identify(struct i2c_client *client,

vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
if (vout_mode >= 0 && vout_mode != 0xff) {
- switch (vout_mode >> 5) {
+ switch ((vout_mode & 0x60) >> 5) {
case 0:
break;
case 1:
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 5f69c1420b4e..47dff264d28b 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -101,6 +101,9 @@ struct pmbus_data {

int exponent[PMBUS_PAGES];
/* linear mode: exponent for output voltages */
+ bool vout_mode_relative[PMBUS_PAGES];
+ /* VOUT limits relative to VOUT_COMMAND */
+ int vout_command[PMBUS_PAGES]; /* VOUT_COMMAND in mV, relative mode */

const struct pmbus_driver_info *info;

@@ -803,6 +806,26 @@ static void pmbus_update_sensor_data(struct i2c_client *client, struct pmbus_sen
sensor->phase, sensor->reg);
}

+static bool pmbus_sensor_is_vout_relative(struct pmbus_data *data,
+ struct pmbus_sensor *sensor)
+{
+ if (sensor->class != PSC_VOLTAGE_OUT ||
+ !data->vout_mode_relative[sensor->page])
+ return false;
+
+ switch (sensor->reg) {
+ case PMBUS_VOUT_MARGIN_HIGH:
+ case PMBUS_VOUT_MARGIN_LOW:
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_OV_WARN_LIMIT:
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
/*
* Convert ieee754 sensor values to milli- or micro-units
* depending on sensor type.
@@ -1005,6 +1028,12 @@ static s64 pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
val = pmbus_reg2data_linear(data, sensor);
break;
}
+
+ /* In relative mode, val is a permillage of VOUT_COMMAND */
+ if (pmbus_sensor_is_vout_relative(data, sensor))
+ val = DIV_S64_ROUND_CLOSEST(val * data->vout_command[sensor->page],
+ 1000);
+
return val;
}

@@ -1209,6 +1238,12 @@ static u16 pmbus_data2reg(struct pmbus_data *data,
if (!sensor->convert)
return val;

+ if (pmbus_sensor_is_vout_relative(data, sensor)) {
+ val = clamp_val(val, -INT_MAX, INT_MAX);
+ val = DIV_S64_ROUND_CLOSEST(val * 1000,
+ data->vout_command[sensor->page]);
+ }
+
switch (data->info->format[sensor->class]) {
case direct:
regval = pmbus_data2reg_direct(data, sensor, val);
@@ -2736,6 +2771,20 @@ static int pmbus_init_coefficients(struct i2c_client *client,
return 0;
}

+static void pmbus_init_vout_relative(struct pmbus_data *data, int page,
+ int vout_command_raw)
+{
+ struct pmbus_sensor s = {
+ .page = page,
+ .reg = PMBUS_VOUT_COMMAND,
+ .class = PSC_VOLTAGE_OUT,
+ .convert = true,
+ .data = vout_command_raw,
+ };
+
+ data->vout_command[page] = pmbus_reg2data(data, &s);
+}
+
/*
* Identify chip parameters.
* This function is called for all chips.
@@ -2753,6 +2802,10 @@ static int pmbus_identify_common(struct i2c_client *client,
* Not all chips support the VOUT_MODE command,
* so a failure to read it is not an error.
*/
+ if (vout_mode & 0x80) {
+ data->vout_mode_relative[page] = true;
+ vout_mode &= 0x7f;
+ }
switch (vout_mode >> 5) {
case 0: /* linear mode */
if (data->info->format[PSC_VOLTAGE_OUT] != linear)
@@ -2979,6 +3032,21 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
return ret;
}

+ for (page = 0; page < info->pages; page++) {
+ if (data->vout_mode_relative[page]) {
+ ret = _pmbus_read_word_data(client, page, 0xff,
+ PMBUS_VOUT_COMMAND);
+ if (ret < 0)
+ return ret;
+ pmbus_init_vout_relative(data, page, ret);
+ if (data->vout_command[page] <= 0) {
+ dev_err(dev, "Invalid VOUT_COMMAND 0x%04x for relative VOUT mode\n",
+ ret);
+ return -ENODEV;
+ }
+ }
+ }
+
if (client->flags & I2C_CLIENT_PEC) {
/*
* If I2C_CLIENT_PEC is set here, both the I2C adapter and the
@@ -3351,29 +3419,38 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
.data = -1,
};
int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */
- int low, high;
+ int low, high, ret;

*selector = 0;

guard(pmbus_lock)(client);

- low = pmbus_regulator_get_low_margin(client, s.page);
- if (low < 0)
- return low;
+ if (!data->vout_mode_relative[s.page]) {
+ low = pmbus_regulator_get_low_margin(client, s.page);
+ if (low < 0)
+ return low;

- high = pmbus_regulator_get_high_margin(client, s.page);
- if (high < 0)
- return high;
+ high = pmbus_regulator_get_high_margin(client, s.page);
+ if (high < 0)
+ return high;

- /* Make sure we are within margins */
- if (low > val)
- val = low;
- if (high < val)
- val = high;
+ /* Make sure we are within margins */
+ if (low > val)
+ val = low;
+ if (high < val)
+ val = high;
+ }

val = pmbus_data2reg(data, &s, val);

- return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
+ ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
+ if (ret < 0)
+ return ret;
+
+ if (data->vout_mode_relative[s.page])
+ pmbus_init_vout_relative(data, s.page, val);
+
+ return 0;
}

static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,

--
2.54.0