PMBus regulators differ in numeric formats and quirks, not in how they
are driven. Share that common behaviour as a regulator-uclass adapter
so chip drivers and the pmbus CLI do not each reimplement the decode
and transport, and add a catch-all driver on compatible = "pmbus" for
compliant chips that have no dedicated driver yet.

Gated by CONFIG_DM_REGULATOR_PMBUS_HELPER and
CONFIG_DM_REGULATOR_PMBUS_GENERIC.

Signed-off-by: Vincent Jardin <[email protected]>
---

(no changes since v1)

 MAINTAINERS                             |   3 +
 drivers/power/regulator/Kconfig         |  24 ++
 drivers/power/regulator/Makefile        |   2 +
 drivers/power/regulator/pmbus_generic.c |  90 +++++++
 drivers/power/regulator/pmbus_helper.c  | 315 ++++++++++++++++++++++++
 drivers/power/regulator/pmbus_helper.h  |  90 +++++++
 include/pmbus.h                         |  37 +++
 lib/pmbus.c                             |  17 ++
 8 files changed, 578 insertions(+)
 create mode 100644 drivers/power/regulator/pmbus_generic.c
 create mode 100644 drivers/power/regulator/pmbus_helper.c
 create mode 100644 drivers/power/regulator/pmbus_helper.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 39e333f8f70..123ea9ed3ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1585,6 +1585,9 @@ M:        Vincent Jardin <[email protected]>
 S:     Maintained
 F:     cmd/pmbus.c
 F:     doc/develop/pmbus.rst
+F:     drivers/power/regulator/pmbus_generic.c
+F:     drivers/power/regulator/pmbus_helper.c
+F:     drivers/power/regulator/pmbus_helper.h
 F:     include/pmbus.h
 F:     lib/pmbus.c
 
diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig
index ca5de5b8726..5b1a9c3f991 100644
--- a/drivers/power/regulator/Kconfig
+++ b/drivers/power/regulator/Kconfig
@@ -557,3 +557,27 @@ config DM_REGULATOR_MT6359
          MediaTek MT6359 PMIC.
          This driver supports the control of different power rails of device
          through regulator interface.
+
+config DM_REGULATOR_PMBUS_HELPER
+       bool "Shared regulator helpers for PMBus chip drivers"
+       depends on DM_REGULATOR && PMBUS && DM_I2C
+       help
+         Provide shared get_value / set_value / get_enable / set_enable
+         operations for UCLASS_REGULATOR drivers that bind PMBus 1.x
+         compliant voltage regulators. Per chip drivers
+         (mps,mpq8785, lltc,ltc3882, ...) consume this helper to avoid
+         duplicating the LINEAR16 / DIRECT decoder dispatch and the
+         VOUT_MODE / VOUT_COMMAND / OPERATION transport sequences.
+
+config DM_REGULATOR_PMBUS_GENERIC
+       bool "Generic PMBus 1.x regulator driver (compatible=\"pmbus\")"
+       depends on DM_REGULATOR_PMBUS_HELPER
+       help
+         Catch all UCLASS_REGULATOR driver bound to compatible = "pmbus".
+         Auto detects the VOUT numeric format from the chip's VOUT_MODE
+         register and exposes telemetry plus voltage set / get against
+         the standard PMBus 1.x command codes. Use this for PMBus
+         compliant chips that have no per chip driver yet; promote to a
+         per chip driver only when chip specific quirks (vendor
+         registers, VID coercion, ADDR pin auto promotion, non standard
+         m / b / R coefficients) need handling.
diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile
index 36a84e7cd71..b084e9ac4bc 100644
--- a/drivers/power/regulator/Makefile
+++ b/drivers/power/regulator/Makefile
@@ -49,3 +49,5 @@ obj-$(CONFIG_REGULATOR_RZG2L_USBPHY) += 
rzg2l-usbphy-regulator.o
 obj-$(CONFIG_$(PHASE_)DM_REGULATOR_CPCAP) += cpcap_regulator.o
 obj-$(CONFIG_DM_REGULATOR_MT6357) += mt6357_regulator.o
 obj-$(CONFIG_DM_REGULATOR_MT6359) += mt6359_regulator.o
+obj-$(CONFIG_DM_REGULATOR_PMBUS_HELPER)  += pmbus_helper.o
+obj-$(CONFIG_DM_REGULATOR_PMBUS_GENERIC) += pmbus_generic.o
diff --git a/drivers/power/regulator/pmbus_generic.c 
b/drivers/power/regulator/pmbus_generic.c
new file mode 100644
index 00000000000..6ba50ce08f8
--- /dev/null
+++ b/drivers/power/regulator/pmbus_generic.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Free Mobile, Vincent Jardin
+ *
+ * Generic PMBus 1.x compatible voltage regulator driver.
+ *
+ * Catch all driver bound to compatible = "pmbus" for chips that have
+ * no per chip driver under drivers/power/regulator/. The probe path
+ * detects the VOUT numeric format from VOUT_MODE bits[7:5]:
+ *
+ *   - 0  LINEAR16 with the exponent supplied via VOUT_MODE bits[4:0]
+ *   - 1  VID; mapped to pmbus_fmt_vid (decoder returns 0 today; per
+ *        chip driver still required to plug a VID table)
+ *   - 2  DIRECT; default coefficients m=1, b=0, R=0 (per chip
+ *        coefficients arrive via PMBUS_QUERY / PMBUS_COEFFICIENTS,
+ *        not yet consumed by U-Boot; values may need a per chip
+ *        driver if telemetry numbers are wrong)
+ *   - 3  IEEE754; mapped to pmbus_fmt_ieee754 (decoder returns 0
+ *        today; per chip driver required)
+ *
+ * Other sensor classes (VIN, IIN, IOUT, TEMPERATURE) default to
+ * LINEAR which is the spec baseline for compliant chips. If an
+ * operator sees wrong telemetry numbers on this driver, the answer
+ * is to write a per chip driver with the correct format[] / m / b / R.
+ *
+ * Adapted in spirit from linux/drivers/hwmon/pmbus/pmbus.c (the
+ * kernel's generic probe driver). The U-Boot version drops the
+ * page count auto detection (most generic compliant parts are
+ * single rail; multi rail chips are quirky enough to need a per
+ * chip driver) and the kernel hwmon publication layers.
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <log.h>
+#include <pmbus.h>
+#include <power/regulator.h>
+
+#include "pmbus_helper.h"
+
+struct pmbus_generic_priv {
+       struct pmbus_regulator_priv base;       /* must be first */
+       struct pmbus_driver_info info;
+};
+
+static int pmbus_generic_probe(struct udevice *dev)
+{
+       struct pmbus_generic_priv *gpriv = dev_get_priv(dev);
+       struct pmbus_driver_info *info = &gpriv->info;
+       enum pmbus_sensor_classes c;
+       int ret;
+
+       info->pages = 1;
+       for (c = 0; c < PSC_NUM_CLASSES; c++) {
+               info->format[c] = pmbus_fmt_linear;
+               info->m[c] = 0;
+               info->b[c] = 0;
+               info->R[c] = 0;
+       }
+
+       ret = pmbus_regulator_probe_common(dev, info, 0);
+       if (ret)
+               return ret;
+
+       /*
+        * Avoid reading non supported pages to avoid device's sticky
+        * status.
+        */
+       info->pages = dev_read_u32_default(dev, "pmbus,num-pages", 1);
+       if (info->pages < 1)
+               info->pages = 1;
+
+       pmbus_regulator_identify_vout(gpriv->base.i2c_dev, info);
+
+       return 0;
+}
+
+static const struct udevice_id pmbus_generic_ids[] = {
+       { .compatible = "pmbus" },
+       { }
+};
+
+U_BOOT_DRIVER(pmbus_generic_regulator) = {
+       .name      = "pmbus_generic_regulator",
+       .id        = UCLASS_REGULATOR,
+       .of_match  = pmbus_generic_ids,
+       .probe     = pmbus_generic_probe,
+       .ops       = &pmbus_regulator_ops,
+       .priv_auto = sizeof(struct pmbus_generic_priv),
+};
diff --git a/drivers/power/regulator/pmbus_helper.c 
b/drivers/power/regulator/pmbus_helper.c
new file mode 100644
index 00000000000..4763470442d
--- /dev/null
+++ b/drivers/power/regulator/pmbus_helper.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Free Mobile, Vincent Jardin
+ *
+ * Shared UCLASS_REGULATOR operations over the PMBus 1.x framework.
+ * See pmbus_helper.h for the API surface and doc/develop/pmbus.rst
+ * for the porting guide.
+ *
+ * No code in this file may reference a specific chip family or
+ * board. Chip specific quirks (vendor registers, VID coercion,
+ * ADDR pin auto promotion, byte reversed MFR strings, etc.) belong
+ * in the per chip driver under drivers/power/regulator/<chip>.c.
+ */
+
+#include <dm.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <i2c.h>
+#include <log.h>
+#include <pmbus.h>
+#include <vsprintf.h>
+#include <linux/types.h>
+#include <power/regulator.h>
+
+#include "pmbus_helper.h"
+
+static int pmbus_regulator_select_page(struct pmbus_regulator_priv *priv)
+{
+       u8 p;
+
+       if (priv->page <= 0)
+               return 0;
+       p = (u8)priv->page;
+       return dm_i2c_write(priv->i2c_dev, PMBUS_PAGE, &p, 1);
+}
+
+static int pmbus_regulator_get_value(struct udevice *dev)
+{
+       struct pmbus_regulator_priv *priv = dev_get_priv(dev);
+       u8 vout_mode = 0;
+       u16 raw = 0;
+       s64 uv;
+       int ret;
+
+       ret = pmbus_regulator_select_page(priv);
+       if (ret)
+               return ret;
+       pmbus_read_byte(priv->i2c_dev, PMBUS_VOUT_MODE, &vout_mode);
+       if (pmbus_read_word(priv->i2c_dev, PMBUS_READ_VOUT, &raw))
+               return -EIO;
+
+       if (priv->info)
+               uv = pmbus_reg2data(priv->info, PSC_VOLTAGE_OUT, raw, 
vout_mode);
+       else
+               uv = pmbus_reg2data_linear16(raw, vout_mode);
+
+       if (uv > INT_MAX)
+               uv = INT_MAX;
+       if (uv < INT_MIN)
+               uv = INT_MIN;
+       return (int)uv;
+}
+
+static int pmbus_regulator_set_value(struct udevice *dev, int uV)
+{
+       struct pmbus_regulator_priv *priv = dev_get_priv(dev);
+       u8 vout_mode = 0;
+       u8 buf[2];
+       u16 raw;
+       int ret;
+
+       ret = pmbus_regulator_select_page(priv);
+       if (ret)
+               return ret;
+       if (pmbus_read_byte(priv->i2c_dev, PMBUS_VOUT_MODE, &vout_mode))
+               return -EIO;
+
+       /*
+        * Dispatch on the chip's VOUT_MODE selector. LINEAR16 and DIRECT
+        * are wired today; VID and IEEE754 return -ENOSYS until their
+        * encoders land. For DIRECT, the m / b / R triple comes from the
+        * chip's pmbus_driver_info[PSC_VOLTAGE_OUT]; if the per chip
+        * driver did not populate them, the encoder cannot run.
+        */
+       switch (vout_mode & PB_VOUT_MODE_MODE_MASK) {
+       case PB_VOUT_MODE_LINEAR:
+               raw = pmbus_data2reg_linear16((s64)uV, vout_mode);
+               break;
+       case PB_VOUT_MODE_DIRECT:
+               if (!priv->info ||
+                   priv->info->format[PSC_VOLTAGE_OUT] != pmbus_fmt_direct)
+                       return -ENODATA;
+               raw = pmbus_data2reg_direct((s64)uV,
+                                           priv->info->m[PSC_VOLTAGE_OUT],
+                                           priv->info->b[PSC_VOLTAGE_OUT],
+                                           priv->info->R[PSC_VOLTAGE_OUT]);
+               break;
+       default:
+               return -ENOSYS;
+       }
+
+       buf[0] = (u8)(raw & 0xff);
+       buf[1] = (u8)((raw >> 8) & 0xff);
+       return dm_i2c_write(priv->i2c_dev, PMBUS_VOUT_COMMAND, buf, 2);
+}
+
+static int pmbus_regulator_get_enable(struct udevice *dev)
+{
+       struct pmbus_regulator_priv *priv = dev_get_priv(dev);
+       u8 op = 0;
+       int ret;
+
+       ret = pmbus_regulator_select_page(priv);
+       if (ret)
+               return ret;
+       if (pmbus_read_byte(priv->i2c_dev, PMBUS_OPERATION, &op))
+               return -EIO;
+       return (op & PB_OPERATION_ON) ? 1 : 0;
+}
+
+static int pmbus_regulator_set_enable(struct udevice *dev, bool enable)
+{
+       struct pmbus_regulator_priv *priv = dev_get_priv(dev);
+       u8 op = 0;
+       int ret;
+
+       ret = pmbus_regulator_select_page(priv);
+       if (ret)
+               return ret;
+       if (pmbus_read_byte(priv->i2c_dev, PMBUS_OPERATION, &op))
+               return -EIO;
+
+       if (enable)
+               op |= PB_OPERATION_ON;
+       else
+               op &= (u8)~PB_OPERATION_ON;
+       return dm_i2c_write(priv->i2c_dev, PMBUS_OPERATION, &op, 1);
+}
+
+const struct dm_regulator_ops pmbus_regulator_ops = {
+       .get_value  = pmbus_regulator_get_value,
+       .set_value  = pmbus_regulator_set_value,
+       .get_enable = pmbus_regulator_get_enable,
+       .set_enable = pmbus_regulator_set_enable,
+};
+
+int pmbus_regulator_read_temp(struct udevice *reg_dev, int *temp_mc)
+{
+       struct pmbus_regulator_priv *priv;
+       u16 raw = 0;
+       s64 udeg;
+       int ret;
+
+       if (!reg_dev || !temp_mc)
+               return -EINVAL;
+       priv = dev_get_priv(reg_dev);
+       if (!priv || !priv->i2c_dev)
+               return -ENODEV;
+
+       ret = pmbus_regulator_select_page(priv);
+       if (ret)
+               return ret;
+       if (pmbus_read_word(priv->i2c_dev, PMBUS_READ_TEMPERATURE_1, &raw))
+               return -EIO;
+
+       /*
+        * vout_mode is meaningless for the temperature class. With a
+        * chip info record the dispatcher honours its per-class format
+        * (DIRECT m/b/R for MPS, LINEAR11 for spec-compliant parts);
+        * without one, fall back to the PMBus 1.x standard LINEAR11.
+        */
+       if (priv->info)
+               udeg = pmbus_reg2data(priv->info, PSC_TEMPERATURE, raw, 0);
+       else
+               udeg = pmbus_reg2data_linear11(raw);
+
+       *temp_mc = (int)(udeg / 1000);
+       return 0;
+}
+
+enum pmbus_data_format
+pmbus_regulator_identify_vout(struct udevice *i2c_dev,
+                             struct pmbus_driver_info *info)
+{
+       u8 vout_mode = 0;
+
+       if (pmbus_read_byte(i2c_dev, PMBUS_VOUT_MODE, &vout_mode))
+               return info->format[PSC_VOLTAGE_OUT];
+
+       switch (vout_mode & PB_VOUT_MODE_MODE_MASK) {
+       case PB_VOUT_MODE_LINEAR:
+               info->format[PSC_VOLTAGE_OUT] = pmbus_fmt_linear;
+               break;
+       case PB_VOUT_MODE_VID:
+               info->format[PSC_VOLTAGE_OUT] = pmbus_fmt_vid;
+               break;
+       case PB_VOUT_MODE_DIRECT:
+               info->format[PSC_VOLTAGE_OUT] = pmbus_fmt_direct;
+               info->m[PSC_VOLTAGE_OUT] = 1;
+               info->b[PSC_VOLTAGE_OUT] = 0;
+               info->R[PSC_VOLTAGE_OUT] = 0;
+               break;
+       case PB_VOUT_MODE_IEEE754:
+               info->format[PSC_VOLTAGE_OUT] = pmbus_fmt_ieee754;
+               break;
+       default:
+               break;
+       }
+       return info->format[PSC_VOLTAGE_OUT];
+}
+
+const struct pmbus_driver_info *pmbus_regulator_info_by_addr(int bus_seq,
+                                                            u8 addr)
+{
+       struct uclass *uc;
+       struct udevice *r;
+
+       if (uclass_get(UCLASS_REGULATOR, &uc))
+               return NULL;
+
+       uclass_foreach_dev(r, uc) {
+               struct udevice *parent = dev_get_parent(r);
+               struct pmbus_regulator_priv *priv;
+               int ra;
+
+               if (!parent || device_get_uclass_id(parent) != UCLASS_I2C)
+                       continue;
+               if (dev_seq(parent) != bus_seq)
+                       continue;
+               ra = dev_read_addr(r);
+               if (ra < 0 || (u8)ra != addr)
+                       continue;
+
+               /*
+                * Address matches. Only chips driven through this helper
+                * carry a pmbus_regulator_priv at the head of their priv;
+                * identify them by their shared ops vector so we never
+                * misread a foreign regulator's private layout.
+                */
+               if (!r->driver || r->driver->ops != &pmbus_regulator_ops)
+                       return NULL;
+               if (device_probe(r))
+                       return NULL;
+               priv = dev_get_priv(r);
+               return priv ? priv->info : NULL;
+       }
+       return NULL;
+}
+
+/*
+ * Spawn the generic UCLASS_THERMAL companion (drivers/thermal/
+ * pmbus_thermal.c) as a child of this regulator so READ_TEMPERATURE_1
+ * is reachable through the standard `temperature list` / `temperature
+ * get` interface. Named "<regulator-name>-temp" so several PMBus rails
+ * on one board produce distinct, descriptive device names. Failure is
+ * non-fatal: the chip still works as a UCLASS_REGULATOR.
+ */
+static void pmbus_regulator_bind_thermal(struct udevice *dev)
+{
+       struct udevice *therm;
+       const char *rname;
+       char name[48];
+
+       if (!IS_ENABLED(CONFIG_PMBUS_THERMAL))
+               return;
+       if (device_bind_driver(dev, "pmbus_thermal", "pmbus-temp", &therm))
+               return;
+       rname = dev_read_string(dev, "regulator-name");
+       snprintf(name, sizeof(name), "%s-temp", rname ? rname : dev->name);
+       device_set_name(therm, name);
+}
+
+int pmbus_regulator_probe_common(struct udevice *dev,
+                                const struct pmbus_driver_info *info,
+                                int page)
+{
+       struct pmbus_regulator_priv *priv = dev_get_priv(dev);
+       int chip_addr;
+       int ret;
+
+       chip_addr = dev_read_addr(dev);
+       if (chip_addr < 0)
+               return -EINVAL;
+
+       ret = i2c_get_chip(dev_get_parent(dev), (u32)chip_addr, 1, 
&priv->i2c_dev);
+       if (ret)
+               return ret;
+
+       priv->info = info;
+       priv->page = page;
+
+       if (page > 0) {
+               u8 p = (u8)page;
+
+               ret = dm_i2c_write(priv->i2c_dev, PMBUS_PAGE, &p, 1);
+               if (ret)
+                       return ret;
+       }
+
+       pmbus_regulator_bind_thermal(dev);
+       return 0;
+}
+
+int pmbus_regulator_apply_voltage_scale(struct udevice *dev,
+                                       u32 fb_divider_permille)
+{
+       struct pmbus_regulator_priv *priv = dev_get_priv(dev);
+       u8 buf[2];
+
+       if (fb_divider_permille == 0)
+               return 0;
+       buf[0] = (u8)(fb_divider_permille & 0xff);
+       buf[1] = (u8)((fb_divider_permille >> 8) & 0xff);
+       return dm_i2c_write(priv->i2c_dev, PMBUS_VOUT_SCALE_LOOP, buf, 2);
+}
diff --git a/drivers/power/regulator/pmbus_helper.h 
b/drivers/power/regulator/pmbus_helper.h
new file mode 100644
index 00000000000..82f263cd513
--- /dev/null
+++ b/drivers/power/regulator/pmbus_helper.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2026 Free Mobile, Vincent Jardin
+ *
+ * Shared UCLASS_REGULATOR ops for PMBus 1.x voltage regulator chips.
+ *
+ * Per chip drivers under drivers/power/regulator/<chip>.c bind a
+ * vendor,chip compatible from DT and call pmbus_regulator_probe_common()
+ * in their .probe. They install pmbus_regulator_ops as the .ops vector;
+ * the helper handles VOUT_MODE / READ_VOUT / VOUT_COMMAND / OPERATION
+ * via the tree level <pmbus.h> framework.
+ *
+ * Per chip drivers retain control of identify hooks (VOUT_MODE based
+ * format selection), chip specific quirks (vendor registers, ADDR pin
+ * auto promotion), and DT property handling (e.g. MPS
+ * mps,vout-fb-divider-ratio-permille).
+ */
+
+#ifndef _DRIVERS_POWER_REGULATOR_PMBUS_HELPER_H_
+#define _DRIVERS_POWER_REGULATOR_PMBUS_HELPER_H_
+
+#include <linux/types.h>
+#include <pmbus.h>
+
+struct udevice;
+struct dm_regulator_ops;
+
+/*
+ * Per chip private state. The first field of every per chip driver's
+ * priv_auto must be (or contain at offset 0) a struct
+ * pmbus_regulator_priv so the shared ops vector can recover it via
+ * dev_get_priv(dev).
+ *
+ *   i2c_dev   chip handle obtained from dev->parent at probe time
+ *             (the parent must be a UCLASS_I2C bus).
+ *   page      PMBUS_PAGE selector for multi rail chips. Single rail
+ *             chips set page = 0; the helper writes PMBUS_PAGE only
+ *             when page > 0 to avoid wasted bus traffic on single
+ *             rail parts.
+ *   info      pointer to the chip's pmbus_driver_info; consumed by
+ *             pmbus_reg2data() / pmbus_data2reg_linear16() to pick
+ *             the right format[] / m / b / R coefficients.
+ */
+struct pmbus_regulator_priv {
+       struct udevice *i2c_dev;
+       int page;
+       const struct pmbus_driver_info *info;
+};
+
+extern const struct dm_regulator_ops pmbus_regulator_ops;
+
+/*
+ * Per chip probe glue. Reads `reg` from DT, gets the I2C chip handle
+ * from dev->parent, populates priv->i2c_dev / page / info, and writes
+ * PMBUS_PAGE if page > 0. Per chip drivers call this in their .probe
+ * before any chip specific identification.
+ */
+int pmbus_regulator_probe_common(struct udevice *dev,
+                                const struct pmbus_driver_info *info,
+                                int page);
+
+/*
+ * Optional helper for per chip drivers that honour an external
+ * feedback divider DT property (e.g. MPS mps,vout-fb-divider-ratio-
+ * permille). Writes the supplied ratio to PMBUS_VOUT_SCALE_LOOP at
+ * probe time. fb_divider_permille == 0 leaves the chip default.
+ */
+int pmbus_regulator_apply_voltage_scale(struct udevice *dev,
+                                       u32 fb_divider_permille);
+
+/*
+ * Read PMBUS_VOUT_MODE and set info->format[PSC_VOLTAGE_OUT] from its
+ * mode selector bits[7:5] per PMBus 1.3 Part II sec 8.3:
+ *   LINEAR  -> pmbus_fmt_linear
+ *   VID     -> pmbus_fmt_vid
+ *   DIRECT  -> pmbus_fmt_direct (default coefficients m=1, b=0, R=0)
+ *   IEEE754 -> pmbus_fmt_ieee754
+ *
+ * The single place that knows the VOUT_MODE bit layout; both the
+ * generic regulator and per chip drivers call it so they never
+ * re-implement the switch. Returns the selected format so a chip
+ * driver can post-adjust a quirk (e.g. MPS encodes VOUT in DIRECT
+ * with m=64 R=1 even when VOUT_MODE reports VID). On a VOUT_MODE read
+ * failure the format is left unchanged and the prior value is returned.
+ */
+enum pmbus_data_format
+pmbus_regulator_identify_vout(struct udevice *i2c_dev,
+                             struct pmbus_driver_info *info);
+
+#endif /* _DRIVERS_POWER_REGULATOR_PMBUS_HELPER_H_ */
diff --git a/include/pmbus.h b/include/pmbus.h
index f86e0673e41..0e121fa6637 100644
--- a/include/pmbus.h
+++ b/include/pmbus.h
@@ -464,6 +464,43 @@ bool pmbus_word_command_supported(struct udevice *dev, u8 
reg);
 void pmbus_print_telemetry(struct udevice *chip);
 void pmbus_print_status_word(struct udevice *chip);
 
+/*
+ * Regulator -> thermal bridge.
+ *
+ * Read READ_TEMPERATURE_1 (8Dh) from a UCLASS_REGULATOR device that
+ * was bound by a pmbus_helper based chip driver, decode it through
+ * the chip's pmbus_driver_info (so the MPS DIRECT 1 degC/LSB quirk
+ * and the standard LINEAR11 encoding are both handled), select the
+ * regulator's PAGE first on multi rail parts, and return the result
+ * in millidegrees Celsius.
+ *
+ * This is what the generic drivers/thermal/pmbus_thermal.c companion
+ * calls on its parent; keeping the decode here avoids exposing the
+ * regulator-private pmbus_regulator_priv layout to other subsystems.
+ *
+ * Returns 0 on success, or a negative errno (-ENODEV if reg is not a
+ * probed pmbus regulator, -EIO on bus error).
+ */
+int pmbus_regulator_read_temp(struct udevice *reg_dev, int *temp_mc);
+
+/*
+ * Look up the pmbus_driver_info of a probed UCLASS_REGULATOR device at
+ * (bus_seq, addr) that is driven by a pmbus_helper based chip driver
+ * (mpq8785, pmbus_generic, ...). Probes the device so its identify
+ * hook has run and format[] is populated, then returns its
+ * driver_info. Returns NULL if no such regulator is bound at that
+ * address, if the device is not a pmbus regulator, or if
+ * CONFIG_DM_REGULATOR_PMBUS_HELPER is disabled.
+ *
+ * Lets pmbus_set_active() -- and thus the pmbus CLI and the board
+ * boot snapshots -- reuse the rich, VOUT_MODE detected driver_info of
+ * a DT bound generic / chip regulator when the device is selected by
+ * raw <bus>:<addr> (which has no chip-match registry entry and would
+ * otherwise fall back to blanket LINEAR16 / LINEAR11 decoding).
+ */
+const struct pmbus_driver_info *pmbus_regulator_info_by_addr(int bus_seq,
+                                                            u8 addr);
+
 /*
  * Status bit name decoding.
  *
diff --git a/lib/pmbus.c b/lib/pmbus.c
index 4192abde91c..62a66593638 100644
--- a/lib/pmbus.c
+++ b/lib/pmbus.c
@@ -761,6 +761,23 @@ int pmbus_set_active(int bus_seq, u8 addr)
                                sizeof(pmbus_active_state.vendor));
        }
 
+       /*
+        * No MFR_ID chip-match (a spec compliant part with no per chip
+        * driver, e.g. a Flex / Delta PSU): if a generic / chip
+        * UCLASS_REGULATOR is bound at this address, reuse its
+        * VOUT_MODE detected driver_info so telemetry decodes through
+        * the right per class formats instead of the blanket
+        * LINEAR16 / LINEAR11 fallback.
+        */
+       if (CONFIG_IS_ENABLED(DM_REGULATOR_PMBUS_HELPER) &&
+           !pmbus_active_state.info) {
+               const struct pmbus_driver_info *di =
+                       pmbus_regulator_info_by_addr(bus_seq, addr);
+
+               if (di)
+                       pmbus_active_state.info = di;
+       }
+
        /*
         * MFR_MODEL / MFR_REVISION are best effort. Use the same byte
         * order the matched chip declared; if nothing matched, use the
-- 
2.43.0

Reply via email to