[PATCH v2 3/7] power: regulator: add MPS MPQ8785 PMBus regulator driver
Vincent Jardin
vjardin at free.fr
Thu Jul 2 22:14:38 CEST 2026
Per-chip UCLASS_REGULATOR driver for the MPS MPQ8785 / MPM3695 /
MPM3695-25 / MPM82504 family, layered on the shared pmbus_helper
adapter. Runs Linux's mpq8785_identify() VOUT_MODE switch
(LINEAR16 vs DIRECT m=64 R=1) at probe time, applies the optional
mps,vout-fb-divider-ratio-permille DT property, and walks the
documented MPS ADDR_VBOOT windows on MFR_ID failure so a
die-revision address shift just works.
Publishes the pmbus mps last|clear last|clear force vendor
extension subcommands (NVM PROTECTION_LAST read and clear) when
CMD_PMBUS is enabled.
Adaapted from Linux code.
Signed-off-by: Vincent Jardin <vjardin at free.fr>
---
(no changes since v1)
MAINTAINERS | 1 +
drivers/power/regulator/Kconfig | 9 +
drivers/power/regulator/Makefile | 1 +
drivers/power/regulator/mpq8785.c | 494 ++++++++++++++++++++++++++++++
4 files changed, 505 insertions(+)
create mode 100644 drivers/power/regulator/mpq8785.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 123ea9ed3ed..5ba9b195c0b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1585,6 +1585,7 @@ M: Vincent Jardin <vjardin at free.fr>
S: Maintained
F: cmd/pmbus.c
F: doc/develop/pmbus.rst
+F: drivers/power/regulator/mpq8785.c
F: drivers/power/regulator/pmbus_generic.c
F: drivers/power/regulator/pmbus_helper.c
F: drivers/power/regulator/pmbus_helper.h
diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig
index 5b1a9c3f991..67c9e1fb377 100644
--- a/drivers/power/regulator/Kconfig
+++ b/drivers/power/regulator/Kconfig
@@ -581,3 +581,12 @@ config DM_REGULATOR_PMBUS_GENERIC
per chip driver only when chip specific quirks (vendor
registers, VID coercion, ADDR pin auto promotion, non standard
m / b / R coefficients) need handling.
+
+config DM_REGULATOR_MPQ8785
+ bool "MPS MPQ8785 / MPM3695 / MPM82504 PMBus voltage regulator"
+ depends on DM_REGULATOR_PMBUS_HELPER
+ help
+ Driver for the Monolithic Power Systems MPQ8785 family of
+ digital multiphase voltage regulators with PMBus. Supports
+ MPM3695, MPM3695-25, MPM82504, and MPQ8785. Adapted from the
+ Linux drivers/hwmon/pmbus/mpq8785.c reference.
diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile
index b084e9ac4bc..aa3faf2f34f 100644
--- a/drivers/power/regulator/Makefile
+++ b/drivers/power/regulator/Makefile
@@ -51,3 +51,4 @@ 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
+obj-$(CONFIG_DM_REGULATOR_MPQ8785) += mpq8785.o
diff --git a/drivers/power/regulator/mpq8785.c b/drivers/power/regulator/mpq8785.c
new file mode 100644
index 00000000000..dc321f0e410
--- /dev/null
+++ b/drivers/power/regulator/mpq8785.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Free Mobile, Vincent Jardin
+ *
+ * MPS MPQ8785 / MPM3695 / MPM3695-25 / MPM82504 PMBus voltage
+ * regulator driver. Adapted from
+ * linux/drivers/hwmon/pmbus/mpq8785.c
+ * (Charles Hsu, GPL-2.0-or-later) with the kernel hwmon publication
+ * and caching layers stripped.
+ *
+ * Hooks the shared pmbus_helper UCLASS_REGULATOR ops + adds the MPS
+ * specific identify (VOUT_MODE switch between LINEAR16 and DIRECT
+ * m=64 R=1 for the chip's "VID" mode), the MPS vendor extension
+ * (pmbus mps last|clear last|clear force), and ADDR_VBOOT auto
+ * promotion when the DT declared address fails the MFR_ID probe.
+ */
+
+#include <command.h>
+#include <dm.h>
+#include <i2c.h>
+#include <log.h>
+#include <pmbus.h>
+#include <vsprintf.h>
+#include <linux/bitops.h>
+#include <power/regulator.h>
+
+#include "pmbus_helper.h"
+
+/* Chip family identifiers (driver_data). */
+enum mpq_chip_id {
+ MPQ_MPM3695 = 0,
+ MPQ_MPM3695_25 = 1,
+ MPQ_MPM82504 = 2,
+ MPQ_MPQ8785 = 3,
+};
+
+/*
+ * MPS vendor extended command codes (NOT in PMBus 1.3 Part II).
+ *
+ * CLEAR_LAST_FAULT (08h) clears the NVM backed PROTECTION_LAST
+ * register. Gated by MFR_CFG_EXT (F5h)
+ * bit[6] = 1; chip silently no ops if the
+ * gate is cleared.
+ * PROTECTION_LAST (FBh) single event, NVM backed log of the last
+ * protection event. Survives the chip's own
+ * power cycle. The boot time post mortem the
+ * SoC has no other way to obtain.
+ *
+ * NEVER issue CLEAR_LAST_FAULT (08h) implicitly; it would erase the
+ * post mortem trail. Only the explicit pmbus mps clear last and
+ * pmbus mps clear force subcommands write it.
+ */
+#define PMBUS_CLEAR_LAST_FAULT 0x08
+#define MPS_PROTECTION_LAST 0xfb
+
+/*
+ * MFR_CFG_EXT (F5h) is an MPS extended config WORD (16 bits, not a
+ * byte). Bit[6] (MFR_CLR_FAULT_CFG) gates CLEAR_LAST_FAULT (08h)
+ * clearing PROTECTION_LAST. Several other bits are fixed and MUST be
+ * preserved on writeback; always read modify write the full 16 bits,
+ * only flip bit[6], restore on the way out.
+ */
+#define MPS_MFR_CFG_EXT 0xf5
+#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6)
+
+/*
+ * MPQ8785 driver_info (transcribed from Linux's
+ * drivers/hwmon/pmbus/mpq8785.c::mpq8785_info). DIRECT format with
+ * chip specific m / b / R coefficients on VIN, IOUT, TEMPERATURE.
+ * VOUT format is selected at probe time from VOUT_MODE: bits[7:5] == 0
+ * selects LINEAR16, bits[7:5] == 1 or 2 selects DIRECT m=64 R=1.
+ */
+
+/*
+ * MPS-extended STATUS_* bit names. The MPQ8785 family reuses several
+ * bit positions documented as RESERVED / UNKNOWN / NONE_ABOVE /
+ * MFR_SPECIFIC by PMBus 1.3 for chip specific signals. The override
+ * table below substitutes the chip name for the standard one when
+ * the bit is set, leaving every other PMBus 1.3 standard bit
+ * (VOUT_OV, IOUT_OC, TEMP, CML, ...) unchanged.
+ *
+ * STATUS_WORD bit[12] spec MFR_SPECIFIC chip NVM_SUMMARY (NVM
+ * backed PROTECTION_LAST
+ * register is non zero)
+ * STATUS_WORD bit[8] spec UNKNOWN chip WATCH_DOG (internal
+ * calculation FSM watchdog
+ * overflow)
+ * STATUS_WORD bit[0] spec NONE_ABOVE chip DRMOS_FAULT (DrMOS
+ * stage fault)
+ * STATUS_CML bit[4] spec MEMORY chip MTP_CRC_FAULT (NVM
+ * CRC mismatch on restore)
+ * STATUS_CML bit[0] spec OTHER_MEM_LOGIC chip MTP_FAULT (NVM
+ * signature fault)
+ * STATUS_TEMPERATURE bit[0] (PMBus leaves bits[3:0] reserved on
+ * this register) chip
+ * OT_SELF (controller die
+ * OT condition)
+ */
+static const struct pmbus_status_override mpq8785_status_overrides[] = {
+ { PMBUS_STATUS_WORD, BIT(12), "NVM_SUMMARY" },
+ { PMBUS_STATUS_WORD, BIT(8), "WATCH_DOG" },
+ { PMBUS_STATUS_WORD, BIT(0), "DRMOS_FAULT" },
+ { PMBUS_STATUS_CML, BIT(4), "MTP_CRC_FAULT" },
+ { PMBUS_STATUS_CML, BIT(0), "MTP_FAULT" },
+ { PMBUS_STATUS_TEMPERATURE, BIT(0), "OT_SELF" },
+ { /* sentinel */ }
+};
+
+static struct pmbus_driver_info mpq8785_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = pmbus_fmt_direct,
+ .format[PSC_VOLTAGE_OUT] = pmbus_fmt_linear, /* refined per VOUT_MODE */
+ .format[PSC_CURRENT_OUT] = pmbus_fmt_direct,
+ .format[PSC_TEMPERATURE] = pmbus_fmt_direct,
+ .m[PSC_VOLTAGE_IN] = 4, .b[PSC_VOLTAGE_IN] = 0, .R[PSC_VOLTAGE_IN] = 1,
+ .m[PSC_CURRENT_OUT] = 16, .b[PSC_CURRENT_OUT] = 0, .R[PSC_CURRENT_OUT] = 0,
+ .m[PSC_TEMPERATURE] = 1, .b[PSC_TEMPERATURE] = 0, .R[PSC_TEMPERATURE] = 0,
+ /*
+ * Sensor set this family actually implements with calibrated
+ * coefficients: VIN, VOUT, IOUT, TEMP. READ_IIN / READ_POUT are
+ * ACKed by the silicon but uncalibrated here (the kernel's mpq8646
+ * / mpq8785 drivers expose neither), so declaring the set keeps
+ * pmbus_print_telemetry from printing a bogus POUT / IIN -- matching
+ * the kernel's per-chip sensor list.
+ */
+ .classes_present = BIT(PSC_VOLTAGE_IN) | BIT(PSC_VOLTAGE_OUT) |
+ BIT(PSC_CURRENT_OUT) | BIT(PSC_TEMPERATURE),
+ .status_overrides = mpq8785_status_overrides,
+};
+
+/*
+ * MPM3695 / MPM3695-25 / MPM82504 driver_info: VOUT in DIRECT format
+ * with chip family default m=8 R=2. Other sensor classes default to
+ * LINEAR (the chip family does not document non standard formats for
+ * VIN / IOUT / TEMPERATURE; the helper falls back to LINEAR11 when
+ * the active info is non NULL but format[c] is linear).
+ */
+static struct pmbus_driver_info mpm82504_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_OUT] = pmbus_fmt_direct,
+ .m[PSC_VOLTAGE_OUT] = 8, .b[PSC_VOLTAGE_OUT] = 0, .R[PSC_VOLTAGE_OUT] = 2,
+ .format[PSC_VOLTAGE_IN] = pmbus_fmt_linear,
+ .format[PSC_CURRENT_OUT] = pmbus_fmt_linear,
+ .format[PSC_TEMPERATURE] = pmbus_fmt_linear,
+};
+
+/*
+ * Chip match for the framework's pmbus dev <bus>:<addr> raw I2C
+ * path. Used when the operator selects the chip directly by address
+ * instead of by regulator-name; the framework probes MFR_ID, sees
+ * "MPS" (after the byte reverse helper), and caches mpq8785_info.
+ */
+static const struct pmbus_chip_match mpq8785_match = {
+ .mfr_id = "MPS",
+ .mfr_id_reverse = true,
+ .vendor = "mps",
+ .info = &mpq8785_info,
+};
+
+static const struct pmbus_bit mpq_protection_last_bits[] = {
+ { 1u << 15, "INIT_FAULT" },
+ { 1u << 14, "NVM_CRC_ERROR" },
+ { 1u << 13, "NVM_FAULT" },
+ { 1u << 12, "OC_PHASE_FAULT" },
+ { 1u << 11, "OTP_SELF_FAULT" },
+ { 1u << 9, "SWITCH_PRD_FAULT" },
+ { 1u << 8, "VIN_OV_FAULT" },
+ { 1u << 7, "VOUT_OV_FAULT" },
+ { 1u << 6, "VOUT_UV_FAULT" },
+ { 1u << 5, "OC_TOT_FAULT" },
+ { 1u << 4, "VIN_UVLO_FAULT" },
+ { 1u << 3, "DRMOS_OTP" },
+ { /* sentinel */ }
+};
+
+static int mps_require_active(struct udevice **chip)
+{
+ const struct pmbus_active_dev *act = pmbus_active();
+
+ if (!act) {
+ printf("pmbus mps: no active device. Use 'pmbus dev <bus>:<addr>' first.\n");
+ return CMD_RET_FAILURE;
+ }
+ if (strcmp(act->vendor, "mps") != 0) {
+ printf("pmbus mps: active device is not from vendor 'mps' (got '%s')\n",
+ act->vendor[0] ? act->vendor : "(generic)");
+ return CMD_RET_FAILURE;
+ }
+ if (pmbus_active_get_i2c(chip)) {
+ printf("pmbus mps: cannot reach i2c%d:0x%02x\n",
+ act->bus_seq, act->addr);
+ return CMD_RET_FAILURE;
+ }
+ return CMD_RET_SUCCESS;
+}
+
+static int mps_do_last(struct udevice *chip)
+{
+ u16 prot_last = 0;
+
+ if (pmbus_read_word(chip, MPS_PROTECTION_LAST, &prot_last)) {
+ printf("pmbus mps: PROTECTION_LAST (FBh) read failed\n");
+ return CMD_RET_FAILURE;
+ }
+ printf("PROTECTION_LAST (FBh) = 0x%04x [", prot_last);
+ pmbus_print_bits(prot_last, mpq_protection_last_bits);
+ printf("] (NVM, survives MPQ power cycle)\n");
+ return CMD_RET_SUCCESS;
+}
+
+static int mps_do_clear_last(struct udevice *chip)
+{
+ int ret;
+
+ printf("pmbus mps: WARNING, erasing NVM PROTECTION_LAST (FBh) post mortem\n");
+ ret = dm_i2c_write(chip, PMBUS_CLEAR_LAST_FAULT, NULL, 0);
+ if (ret) {
+ printf("pmbus mps: CLEAR_LAST_FAULT (08h) write failed (%d)\n", ret);
+ return CMD_RET_FAILURE;
+ }
+ printf("pmbus mps: CLEAR_LAST_FAULT (08h) issued; gated by MFR_CFG_EXT bit[6]\n");
+ printf(" (chip silently no ops if F5h bit[6] = 0; verify by re reading FBh)\n");
+ return CMD_RET_SUCCESS;
+}
+
+static int mps_do_clear_force(struct udevice *chip)
+{
+ u8 wp_orig = 0;
+ u16 cfg_orig = 0, cfg_unlocked;
+ int ret, last_rc = 0, rc;
+
+ printf("pmbus mps: FORCE; temporarily lowering WRITE_PROTECT and MFR_CFG_EXT.CLEAR_LAST_EN\n");
+ printf("pmbus mps: WARNING, erasing NVM PROTECTION_LAST (FBh) post mortem\n");
+
+ ret = pmbus_read_byte(chip, PMBUS_WRITE_PROTECT, &wp_orig);
+ if (ret) {
+ printf("pmbus mps: WRITE_PROTECT (10h) read failed (%d), aborting force\n", ret);
+ return CMD_RET_FAILURE;
+ }
+ ret = pmbus_read_word(chip, MPS_MFR_CFG_EXT, &cfg_orig);
+ if (ret) {
+ printf("pmbus mps: MFR_CFG_EXT (F5h) read failed (%d), aborting force\n", ret);
+ return CMD_RET_FAILURE;
+ }
+ printf("pmbus mps: saved WRITE_PROTECT=0x%02x MFR_CFG_EXT=0x%04x\n",
+ wp_orig, cfg_orig);
+
+ if (wp_orig != 0) {
+ u8 wp_open = 0x00;
+
+ ret = dm_i2c_write(chip, PMBUS_WRITE_PROTECT, &wp_open, 1);
+ if (ret) {
+ printf("pmbus mps: WRITE_PROTECT clear failed (%d), chip refuses unlock\n",
+ ret);
+ return CMD_RET_FAILURE;
+ }
+ }
+
+ cfg_unlocked = cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN;
+ ret = pmbus_write_word(chip, MPS_MFR_CFG_EXT, cfg_unlocked);
+ if (ret) {
+ printf("pmbus mps: MFR_CFG_EXT <- 0x%04x failed (%d)\n",
+ cfg_unlocked, ret);
+ goto restore_wp;
+ }
+
+ last_rc = dm_i2c_write(chip, PMBUS_CLEAR_LAST_FAULT, NULL, 0);
+ if (last_rc)
+ printf("pmbus mps: CLEAR_LAST_FAULT (08h) write failed (%d) even with gate open\n",
+ last_rc);
+ else
+ printf("pmbus mps: CLEAR_LAST_FAULT (08h) issued with MFR_CFG_EXT bit[6]=1, PROTECTION_LAST should now read 0x0000\n");
+
+ rc = pmbus_write_word(chip, MPS_MFR_CFG_EXT, cfg_orig);
+ if (rc)
+ printf("pmbus mps: MFR_CFG_EXT restore failed (%d), gate may stay open until POR\n",
+ rc);
+
+restore_wp:
+ if (wp_orig != 0) {
+ rc = dm_i2c_write(chip, PMBUS_WRITE_PROTECT, &wp_orig, 1);
+ if (rc)
+ printf("pmbus mps: WRITE_PROTECT restore failed (%d), chip stays unlocked until POR\n",
+ rc);
+ }
+
+ return (ret || last_rc) ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
+}
+
+static int mps_vendor_handler(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct udevice *chip;
+ int rc;
+
+ if (argc < 2)
+ return CMD_RET_USAGE;
+
+ rc = mps_require_active(&chip);
+ if (rc)
+ return rc;
+
+ if (!strcmp(argv[1], "last") && argc == 2)
+ return mps_do_last(chip);
+ if (!strcmp(argv[1], "clear") && argc >= 3) {
+ if (!strcmp(argv[2], "last"))
+ return mps_do_clear_last(chip);
+ if (!strcmp(argv[2], "force"))
+ return mps_do_clear_force(chip);
+ }
+ return CMD_RET_USAGE;
+}
+
+static const struct pmbus_vendor_op mps_vendor_op = {
+ .vendor = "mps",
+ .handler = mps_vendor_handler,
+ .help = "pmbus mps last : read MPS PROTECTION_LAST (FBh)\n"
+ "pmbus mps clear last : issue MPS CLEAR_LAST_FAULT (08h) (DESTRUCTIVE)\n"
+ "pmbus mps clear force : force clear via MFR_CFG_EXT bit[6] (DESTRUCTIVE)\n",
+};
+
+static void mpq8785_identify_vout(struct udevice *i2c_dev)
+{
+ enum pmbus_data_format fmt;
+
+ /*
+ * Let the shared helper read VOUT_MODE and pick the base format
+ * (the single source of truth for the bit layout). The MPS quirk:
+ * this family encodes VOUT in DIRECT with m=64 R=1 whenever
+ * VOUT_MODE reports VID *or* DIRECT -- override the helper's
+ * generic DIRECT m=1 / VID-unwired result in those two modes.
+ * LINEAR and IEEE754 keep the helper's selection unchanged.
+ */
+ fmt = pmbus_regulator_identify_vout(i2c_dev, &mpq8785_info);
+ if (fmt == pmbus_fmt_vid || fmt == pmbus_fmt_direct) {
+ mpq8785_info.format[PSC_VOLTAGE_OUT] = pmbus_fmt_direct;
+ mpq8785_info.m[PSC_VOLTAGE_OUT] = 64;
+ mpq8785_info.b[PSC_VOLTAGE_OUT] = 0;
+ mpq8785_info.R[PSC_VOLTAGE_OUT] = 1;
+ }
+}
+
+/*
+ * The MPQ8785 datasheet revision letter changes which window the
+ * analog ADDR_VBOOT level resolves to. Boards have been observed at
+ * 0x10 (later die rev) versus the 0x20 the original driver assumed.
+ * If the DT declared address fails the MFR_ID probe at probe time,
+ * walk the three documented windows looking for an MPS responder.
+ *
+ * Each window covers 16 consecutive 7 bit I2C addresses; the low
+ * nibble selects the chip's MFR_ADDR_PMBUS slot within the window.
+ */
+#define MPS_ADDR_VBOOT_WINDOW_SIZE 16
+static const u8 mps_addr_window_starts[] = { 0x10, 0x20, 0x60 };
+
+static int mpq8785_probe_addr(struct udevice *bus, u8 addr,
+ struct udevice **chip_out)
+{
+ char id[PMBUS_MFR_STRING_MAX] = "";
+ struct udevice *chip;
+ int ret;
+
+ ret = i2c_get_chip(bus, addr, 1, &chip);
+ if (ret)
+ return ret;
+ ret = pmbus_read_string(chip, PMBUS_MFR_ID, id, sizeof(id), true);
+ if (ret < 0)
+ return ret;
+ if (strncmp(id, "MPS", 3) != 0)
+ return -ENODEV;
+ *chip_out = chip;
+ return 0;
+}
+
+static int mpq8785_scan_windows(struct udevice *bus, u8 *found_addr,
+ struct udevice **chip_out)
+{
+ unsigned int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(mps_addr_window_starts); i++) {
+ for (j = 0; j < MPS_ADDR_VBOOT_WINDOW_SIZE; j++) {
+ u8 addr = mps_addr_window_starts[i] + j;
+
+ if (mpq8785_probe_addr(bus, addr, chip_out) == 0) {
+ *found_addr = addr;
+ return 0;
+ }
+ }
+ }
+ return -ENODEV;
+}
+
+static struct pmbus_driver_info *mpq8785_pick_info(enum mpq_chip_id chip_id)
+{
+ switch (chip_id) {
+ case MPQ_MPM3695:
+ case MPQ_MPM3695_25:
+ case MPQ_MPM82504:
+ return &mpm82504_info;
+ case MPQ_MPQ8785:
+ default:
+ return &mpq8785_info;
+ }
+}
+
+static int mpq8785_probe(struct udevice *dev)
+{
+ enum mpq_chip_id chip_id = (enum mpq_chip_id)dev_get_driver_data(dev);
+ struct pmbus_regulator_priv *priv = dev_get_priv(dev);
+ struct pmbus_driver_info *info = mpq8785_pick_info(chip_id);
+ static bool match_registered;
+ static bool vendor_registered;
+ u32 fb_div;
+ int ret;
+
+ ret = pmbus_regulator_probe_common(dev, info, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Verify the chip answers MFR_ID="MPS" at the DT declared
+ * address. If it doesn't, walk the documented ADDR_VBOOT windows
+ * looking for it (a die rev address shift). On a hit, replace
+ * priv->i2c_dev with the discovered chip handle and continue.
+ */
+ {
+ char id[PMBUS_MFR_STRING_MAX] = "";
+
+ ret = pmbus_read_string(priv->i2c_dev, PMBUS_MFR_ID, id,
+ sizeof(id), true);
+ if (ret < 0 || strncmp(id, "MPS", 3) != 0) {
+ struct udevice *bus = dev_get_parent(dev);
+ struct udevice *promoted;
+ u8 found = 0;
+
+ if (mpq8785_scan_windows(bus, &found, &promoted) == 0) {
+ printf("MPQ8785: DT addr 0x%02x silent, auto promoted to 0x%02x\n",
+ (unsigned int)dev_read_addr(dev), found);
+ priv->i2c_dev = promoted;
+ } else {
+ printf("MPQ8785: no MPS responder found in 0x10..0x1f / 0x20..0x2f / 0x60..0x6f\n");
+ return -ENODEV;
+ }
+ }
+ }
+
+ /* MPQ8785 specific: refine VOUT format from VOUT_MODE. */
+ if (chip_id == MPQ_MPQ8785)
+ mpq8785_identify_vout(priv->i2c_dev);
+
+ /* Apply mps,vout-fb-divider-ratio-permille if present in DT. */
+ fb_div = dev_read_u32_default(dev, "mps,vout-fb-divider-ratio-permille", 0);
+ if (fb_div) {
+ ret = pmbus_regulator_apply_voltage_scale(dev, fb_div);
+ if (ret) {
+ printf("MPQ8785: VOUT_SCALE_LOOP write failed (%d)\n", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Register the chip match and the MPS vendor handler exactly
+ * once across all bound MPS regulators (a board could legally
+ * carry several). Both registries are global and idempotent
+ * matches return -ENOSPC, so the static guards keep things
+ * tidy.
+ */
+ if (!match_registered) {
+ if (pmbus_register_chip(&mpq8785_match) == 0)
+ match_registered = true;
+ }
+ if (!vendor_registered) {
+ if (pmbus_register_vendor_handler(&mps_vendor_op) == 0)
+ vendor_registered = true;
+ }
+ return 0;
+}
+
+static const struct udevice_id mpq8785_ids[] = {
+ { .compatible = "mps,mpm3695", .data = MPQ_MPM3695 },
+ { .compatible = "mps,mpm3695-25", .data = MPQ_MPM3695_25 },
+ { .compatible = "mps,mpm82504", .data = MPQ_MPM82504 },
+ { .compatible = "mps,mpq8785", .data = MPQ_MPQ8785 },
+ { }
+};
+
+U_BOOT_DRIVER(mpq8785_regulator) = {
+ .name = "mpq8785_regulator",
+ .id = UCLASS_REGULATOR,
+ .of_match = mpq8785_ids,
+ .probe = mpq8785_probe,
+ .ops = &pmbus_regulator_ops,
+ .priv_auto = sizeof(struct pmbus_regulator_priv),
+};
--
2.43.0
More information about the U-Boot
mailing list