* [PATCH] hwmon: (pmbus/mp2975) Get vout fault limits dynamically
@ 2026-11-04 9:32 Felix Radensky
2026-11-04 9:39 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Felix Radensky @ 2026-11-04 9:32 UTC (permalink / raw)
To: Guenter Roeck; +Cc: Vadim Pasternak, Felix Radensky, linux-hwmon
The over- and under-voltage fault limits of the tracking mode are
relative to the voltage reference, which follows the VID/AVS requests
issued to the device and therefore changes at runtime.
The driver reads the voltage reference once during probe and derives the
limits from that snapshot, which the PMBus core caches in addition. As a
result in2_crit and in2_lcrit reflect the operating point at probe, rather
than the thresholds the device enforces. On systems where the device
supplies the CPU core voltage, in2_crit was reported anywhere between
1.15V and 1.70V across identical systems, causing false over-voltage
reports once the CPU voltage exceeded the value latched at probe.
Read the voltage reference and the over-voltage offset selector whenever
a limit is requested, and mark both limits with pmbus_set_update(), so
that the core does not cache them. The voltage reference is available on
page 3 only. Let the core change the page, so that the page it caches
matches the device, except for a single rail configuration, for which the
core does not issue PAGE commands. Retry a failed page change once in that
case, since the core would not set the page again later on. Likewise, set
the rail page again if the change to page 3 fails, since the device can
then be on page 3 while the core still caches the rail page.
With the selector read at runtime, a device configured with an
unsupported selector no longer fails to probe. The OVP1 threshold, which
does not depend on the selector, is reported for it instead, with a
warning.
Clamp the under-voltage limit to zero, since the offset can exceed a low
voltage reference, while a negative value would be reported as a read
error.
Fixes: 2c6fcbb21149 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2975 controller")
Signed-off-by: Felix Radensky <fradensky@nvidia.com>
Reviewed-by: Vadim Pasternak <vadimp@nvidia.com>
---
Tested on a switch platform carrying twelve MP2975 devices, twenty one
rails in total.
On the rail supplying the CPU core voltage in2_crit followed the CPU,
reading 1150mV at idle and up to 1720mV under load, where the unpatched
driver reported a constant 1195mV, derived from the reference latched at
probe. in2_crit and in2_lcrit, which are obtained through separate page 3
excursions, agreed on the same voltage reference within the P-state jitter
between the two reads.
The remaining rails have a static reference, and reported the same limits
as before the change.
A single rail device, for which the PMBus core never writes PAGE, was
exercised with 400 page 3 excursions, with no page 3 register value
observed in place of the sensor values. A dual rail device, for which the
core changes the page, was exercised with 800 page 3 excursions, each
followed by a read of the other rail, with no page 3 or other rail
register value observed in place of the sensor values.
A single failed page change is recovered from: the rail page is set again
if the change to page 3 fails, a failed change back from page 3 leaves the
core caching page 3, so that it writes PAGE on the next access, and a
direct PAGE write, used for a single rail configuration, is retried once.
Repeated failures can still leave the device on a page the core does not
expect, as is the case for page changes made by the core itself.
Recovering from these within the driver would take state and write
callbacks of its own, and still would not cover block reads, so it is not
attempted here.
I would like this patch to be considered as is, since it fixes the
reported limits independently of the above. If you think it necessary,
I am happy to follow up with a separate patch to pmbus_set_page(), which
invalidates the cached page when a page change fails, so that the next
access writes PAGE again.
drivers/hwmon/pmbus/mp2975.c | 228 +++++++++++++++++++++--------------
1 file changed, 136 insertions(+), 92 deletions(-)
diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c
index 8e5193933ceb..9e685a9ed47f 100644
--- a/drivers/hwmon/pmbus/mp2975.c
+++ b/drivers/hwmon/pmbus/mp2975.c
@@ -109,10 +109,7 @@ struct mp2975_data {
int vout_scale;
int max_phases[MP2975_PAGE_NUM];
int vid_step[MP2975_PAGE_NUM];
- int vref[MP2975_PAGE_NUM];
- int vref_off[MP2975_PAGE_NUM];
int vout_max[MP2975_PAGE_NUM];
- int vout_ov_fixed[MP2975_PAGE_NUM];
int curr_sense_gain[MP2975_PAGE_NUM];
};
@@ -460,12 +457,98 @@ static int mp2973_write_word_data(struct i2c_client *client, int page,
return pmbus_write_word_data(client, 0, PMBUS_SMBALERT_MASK, ret);
}
+/*
+ * Write PAGE directly, retrying a failed write once, since the core would
+ * not set the page again later on.
+ */
+static int mp2975_page_write(struct i2c_client *client, int page)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+
+ return ret;
+}
+
+/*
+ * Let the PMBus core change the page, so that the page it caches matches the
+ * device. The core does not issue PAGE commands for a single rail
+ * configuration, thus change the page directly then.
+ */
+static int
+mp2975_page_set(struct i2c_client *client, struct mp2975_data *data, int page)
+{
+ if (data->info.pages > 1)
+ return pmbus_set_page(client, page, 0xff);
+
+ return mp2975_page_write(client, page);
+}
+
+/*
+ * Voltage reference is available on page 3 only. The callers read a register
+ * of the rail through the core first, thus the core caches the rail page.
+ */
+static int
+mp2975_vref_get(struct i2c_client *client, struct mp2975_data *data, int page)
+{
+ int ret, vref;
+
+ ret = mp2975_page_set(client, data, 3);
+ if (ret < 0) {
+ /*
+ * The change can fail after the device has been set to page 3,
+ * thus set the rail page again, as the core expects it.
+ */
+ mp2975_page_write(client, page);
+ return ret;
+ }
+
+ vref = i2c_smbus_read_word_data(client, page ? MP2975_MFR_READ_VREF_R2 :
+ MP2975_MFR_READ_VREF_R1);
+
+ ret = mp2975_page_set(client, data, page);
+ if (ret < 0)
+ return ret;
+
+ return (vref < 0) ? vref : vref * data->vid_step[page];
+}
+
+static int
+mp2975_ovp2_offset_get(int ovp_th_set)
+{
+ switch ((ovp_th_set & GENMASK(5, 3)) >> 3) {
+ case 1:
+ return 140;
+ case 2:
+ return 220;
+ case 4:
+ return 400;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int
+mp2975_vout_ovp2_get(struct i2c_client *client, struct mp2975_data *data,
+ int page, int vref_off)
+{
+ int vref;
+
+ vref = mp2975_vref_get(client, data, page);
+ if (vref < 0)
+ return vref;
+
+ return vref + DIV_ROUND_CLOSEST(vref_off * data->vout_scale, 10);
+}
+
static int mp2975_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct mp2975_data *data = to_mp2975_data(info);
- int ret;
+ int ret, ovp1, vref_off, uv_off;
switch (reg) {
case PMBUS_OT_FAULT_LIMIT:
@@ -482,19 +565,35 @@ static int mp2975_read_word_data(struct i2c_client *client, int page,
break;
case PMBUS_VOUT_OV_FAULT_LIMIT:
/*
- * Register provides two values for over-voltage protection
- * threshold for fixed (ovp2) and tracking (ovp1) modes. The
- * minimum of these two values is provided as over-voltage
- * fault alarm.
+ * Device provides dual over-voltage protection. OVP1 is
+ * referred to the maximum reference voltage, while OVP2 is
+ * referred to the current voltage reference, which is changed
+ * at runtime. Protection is tripped by any of them, thus the
+ * minimum of the two is exposed as over-voltage fault alarm,
+ * and OVP2 is obtained on each read.
*/
ret = mp2975_read_word_helper(client, page, phase,
MP2975_MFR_OVP_TH_SET,
- GENMASK(2, 0));
+ GENMASK(5, 0));
if (ret < 0)
return ret;
- ret = min_t(int, data->vout_max[page] + 50 * (ret + 1),
- data->vout_ov_fixed[page]);
+ ovp1 = data->vout_max[page] + 50 * ((ret & GENMASK(2, 0)) + 1);
+
+ /* OVP1 still applies if the OVP2 threshold is unknown. */
+ vref_off = mp2975_ovp2_offset_get(ret);
+ if (vref_off < 0) {
+ dev_warn_once(&client->dev,
+ "unsupported OVP2 selector, reporting OVP1 threshold\n");
+ ret = ovp1;
+ break;
+ }
+
+ ret = mp2975_vout_ovp2_get(client, data, page, vref_off);
+ if (ret < 0)
+ return ret;
+
+ ret = min_t(int, ovp1, ret);
break;
case PMBUS_VOUT_UV_FAULT_LIMIT:
ret = mp2975_read_word_helper(client, page, phase,
@@ -503,8 +602,17 @@ static int mp2975_read_word_data(struct i2c_client *client, int page,
if (ret < 0)
return ret;
- ret = DIV_ROUND_CLOSEST(data->vref[page] * 10 - 50 *
- (ret + 1) * data->vout_scale, 10);
+ uv_off = 50 * (ret + 1) * data->vout_scale;
+
+ ret = mp2975_vref_get(client, data, page);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Offset can exceed the voltage reference, while a negative
+ * value would be reported as a read error.
+ */
+ ret = max_t(int, DIV_ROUND_CLOSEST(ret * 10 - uv_off, 10), 0);
break;
case PMBUS_VIRT_READ_POUT_MAX:
ret = mp2975_read_word_helper(client, page, phase,
@@ -753,60 +861,6 @@ mp2975_current_sense_gain_get(struct i2c_client *client,
return 0;
}
-static int
-mp2975_vref_get(struct i2c_client *client, struct mp2975_data *data,
- struct pmbus_driver_info *info)
-{
- int ret;
-
- ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 3);
- if (ret < 0)
- return ret;
-
- /* Get voltage reference value for rail 1. */
- ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R1);
- if (ret < 0)
- return ret;
-
- data->vref[0] = ret * data->vid_step[0];
-
- /* Get voltage reference value for rail 2, if connected. */
- if (data->info.pages == MP2975_PAGE_NUM) {
- ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R2);
- if (ret < 0)
- return ret;
-
- data->vref[1] = ret * data->vid_step[1];
- }
- return 0;
-}
-
-static int
-mp2975_vref_offset_get(struct i2c_client *client, struct mp2975_data *data,
- int page)
-{
- int ret;
-
- ret = i2c_smbus_read_word_data(client, MP2975_MFR_OVP_TH_SET);
- if (ret < 0)
- return ret;
-
- switch ((ret & GENMASK(5, 3)) >> 3) {
- case 1:
- data->vref_off[page] = 140;
- break;
- case 2:
- data->vref_off[page] = 220;
- break;
- case 4:
- data->vref_off[page] = 400;
- break;
- default:
- return -EINVAL;
- }
- return 0;
-}
-
static int
mp2975_vout_max_get(struct i2c_client *client, struct mp2975_data *data,
struct pmbus_driver_info *info, int page)
@@ -911,25 +965,6 @@ mp2975_vout_per_rail_config_get(struct i2c_client *client,
ret = mp2975_vout_max_get(client, data, info, i);
if (ret < 0)
return ret;
-
- /* Skip if reading Vref is unsupported */
- if (data->chip_id != mp2975)
- continue;
-
- /* Obtain voltage reference offsets. */
- ret = mp2975_vref_offset_get(client, data, i);
- if (ret < 0)
- return ret;
-
- /*
- * Set over-voltage fixed value. Thresholds are provided as
- * fixed value, and tracking value. The minimum of them are
- * exposed as over-voltage critical threshold.
- */
- data->vout_ov_fixed[i] = data->vref[i] +
- DIV_ROUND_CLOSEST(data->vref_off[i] *
- data->vout_scale,
- 10);
}
return 0;
@@ -1043,11 +1078,6 @@ static int mp2975_probe(struct i2c_client *client)
if (ret)
return ret;
- /* Obtain voltage reference values. */
- ret = mp2975_vref_get(client, data, info);
- if (ret)
- return ret;
-
/* Obtain vout over-voltage scales. */
ret = mp2975_vout_ov_scale_get(client, data, info);
if (ret < 0)
@@ -1064,7 +1094,21 @@ static int mp2975_probe(struct i2c_client *client)
if (ret)
return ret;
- return pmbus_do_probe(client, info);
+ ret = pmbus_do_probe(client, info);
+ if (ret)
+ return ret;
+
+ /*
+ * Vout over- and under-voltage fault limits are relative to the
+ * voltage reference, which is changed at runtime. Do not allow the
+ * PMBus core to cache them.
+ */
+ if (data->chip_id == mp2975) {
+ pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true);
+ pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true);
+ }
+
+ return 0;
}
static const struct of_device_id mp2975_of_match[] = {
base-commit: 6b4b543fa18757297ac756a6fe6e64f506b7844a
--
2.34.1
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH] hwmon: (pmbus/mp2975) Get vout fault limits dynamically
2026-11-04 9:32 [PATCH] hwmon: (pmbus/mp2975) Get vout fault limits dynamically Felix Radensky
@ 2026-11-04 9:39 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-11-04 9:39 UTC (permalink / raw)
To: Felix Radensky; +Cc: linux-hwmon
> The over- and under-voltage fault limits of the tracking mode are
> relative to the voltage reference, which follows the VID/AVS requests
> issued to the device and therefore changes at runtime.
>
> The driver reads the voltage reference once during probe and derives the
> limits from that snapshot, which the PMBus core caches in addition. As a
> result in2_crit and in2_lcrit reflect the operating point at probe, rather
> than the thresholds the device enforces. On systems where the device
> supplies the CPU core voltage, in2_crit was reported anywhere between
> 1.15V and 1.70V across identical systems, causing false over-voltage
> reports once the CPU voltage exceeded the value latched at probe.
>
> Read the voltage reference and the over-voltage offset selector whenever
> a limit is requested, and mark both limits with pmbus_set_update(), so
> that the core does not cache them. The voltage reference is available on
> [ ... ]
> voltage reference, while a negative value would be reported as a read
> error.
>
> Fixes: 2c6fcbb21149 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2975 controller")
> Signed-off-by: Felix Radensky <fradensky@nvidia.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104093213.2422350-1-fradensky@nvidia.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-04 9:39 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 9:32 [PATCH] hwmon: (pmbus/mp2975) Get vout fault limits dynamically Felix Radensky
2026-11-04 9:39 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.