[PATCH v1 6/7] test: dm: pmbus: add a sandbox PMBus chip emulator

Vincent Jardin vjardin at free.fr
Thu Jul 2 19:15:50 CEST 2026


Add a UCLASS_I2C_EMUL driver that emulates a PMBus 1.x compliant chip
behind the sandbox I2C bus, plus the test.dts wiring and sandbox
defconfig that bind it to the generic PMBus regulator
(compatible = "pmbus"). This design is a stub only: it lets the
follow-up dm unit test drive lib/pmbus.c, the generic regulator and
the pmbus CLI command with no real hardware.

The emulator models a flat per-command 16-bit register file and the
three identification block strings (MFR_ID / MFR_MODEL /
MFR_REVISION). READ_IIN and READ_POUT are deliberately left
unimplemented (the chip NAKs them) so the telemetry printer's
"(not supported)" path is exercised, mirroring a real buck that only
calibrates a subset of the sensor classes.

Signed-off-by: Vincent Jardin <vjardin at free.fr>
---

 MAINTAINERS                             |   1 +
 arch/sandbox/dts/test.dts               |  10 ++
 configs/sandbox_defconfig               |   6 +
 drivers/power/regulator/Kconfig         |  10 ++
 drivers/power/regulator/Makefile        |   1 +
 drivers/power/regulator/sandbox_pmbus.c | 160 ++++++++++++++++++++++++
 6 files changed, 188 insertions(+)
 create mode 100644 drivers/power/regulator/sandbox_pmbus.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 258c0d9bc18..bdfb660a743 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1593,6 +1593,7 @@ 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
+F:	drivers/power/regulator/sandbox_pmbus.c
 F:	drivers/thermal/pmbus_thermal.c
 F:	include/pmbus.h
 F:	lib/pmbus.c
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 0887de4333b..5032eecceea 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1027,6 +1027,9 @@
 			emul1: emull {
 				compatible = "sandbox,i2c-rtc-emul";
 			};
+			emul_pmbus: emul-pmbus {
+				compatible = "sandbox,i2c-pmbus";
+			};
 		};
 
 		sandbox_pmic: sandbox_pmic at 40 {
@@ -1038,6 +1041,13 @@
 			reg = <0x41>;
 			sandbox,emul = <&emul_pmic1>;
 		};
+
+		pmbus at 70 {
+			reg = <0x70>;
+			compatible = "pmbus";
+			regulator-name = "sandbox-pmbus-vout";
+			sandbox,emul = <&emul_pmbus>;
+		};
 	};
 
 	i3c0 {
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index ba800f7d19d..de90a2b39e5 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -392,3 +392,9 @@ CONFIG_UTHREAD=y
 CONFIG_UNIT_TEST=y
 CONFIG_UT_TIME=y
 CONFIG_UT_DM=y
+CONFIG_PMBUS=y
+CONFIG_CMD_PMBUS=y
+CONFIG_DM_REGULATOR_PMBUS_HELPER=y
+CONFIG_DM_REGULATOR_PMBUS_GENERIC=y
+CONFIG_SANDBOX_PMBUS=y
+CONFIG_PMBUS_THERMAL=y
diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig
index 67c9e1fb377..143911eeb17 100644
--- a/drivers/power/regulator/Kconfig
+++ b/drivers/power/regulator/Kconfig
@@ -590,3 +590,13 @@ config DM_REGULATOR_MPQ8785
 	  digital multiphase voltage regulators with PMBus. Supports
 	  MPM3695, MPM3695-25, MPM82504, and MPQ8785. Adapted from the
 	  Linux drivers/hwmon/pmbus/mpq8785.c reference.
+
+config SANDBOX_PMBUS
+	bool "Sandbox PMBus 1.x chip emulator"
+	depends on SANDBOX && PMBUS && DM_I2C
+	help
+	  Emulate a PMBus 1.x compliant chip behind a sandbox I2C bus so
+	  the PMBus framework (lib/pmbus.c), the generic regulator
+	  (DM_REGULATOR_PMBUS_GENERIC) and the pmbus CLI command can be
+	  exercised by the dm unit tests with no real hardware. Only
+	  useful for testing; say N on real boards.
diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile
index aa3faf2f34f..b8599ea7fd9 100644
--- a/drivers/power/regulator/Makefile
+++ b/drivers/power/regulator/Makefile
@@ -52,3 +52,4 @@ 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
+obj-$(CONFIG_SANDBOX_PMBUS)              += sandbox_pmbus.o
diff --git a/drivers/power/regulator/sandbox_pmbus.c b/drivers/power/regulator/sandbox_pmbus.c
new file mode 100644
index 00000000000..2900d0a6d83
--- /dev/null
+++ b/drivers/power/regulator/sandbox_pmbus.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Free Mobile - Vincent Jardin
+ *
+ * Sandbox PMBus 1.x chip stub (UCLASS_I2C_EMUL).
+ *
+ * Stub a DT i2c node so the PMBus framework, the generic pmbus
+ * regulator and the pmbus CLI command can be tested.
+ * The model is a flat per-command 16 bit
+ * register file combined with some fixed identification strings.
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <pmbus.h>
+#include <linux/ctype.h>
+
+#define PMBUS_EMUL_NREG		256
+
+struct sandbox_pmbus_priv {
+	u16 reg[PMBUS_EMUL_NREG];
+	bool supported[PMBUS_EMUL_NREG];
+};
+
+/* Identification strings reported in the natural (forward) byte order. */
+static const char *pmbus_emul_string(u8 cmd)
+{
+	switch (cmd) {
+	case PMBUS_MFR_ID:
+		return "SANDBOX";
+	case PMBUS_MFR_MODEL:
+		return "PMBUS-EMUL";
+	case PMBUS_MFR_REVISION:
+		return "1.0";
+	default:
+		return NULL;
+	}
+}
+
+static int sandbox_pmbus_read(struct sandbox_pmbus_priv *priv, u8 cmd,
+			      u8 *buf, int len)
+{
+	const char *str = pmbus_emul_string(cmd);
+	int i;
+
+	if (str) {
+		int slen = strlen(str);
+
+		/* Block payload: [length][bytes...]. */
+		buf[0] = (u8)slen;
+		for (i = 1; i < len; i++)
+			buf[i] = (i - 1 < slen) ? (u8)str[i - 1] : 0;
+		return 0;
+	}
+
+	if (!priv->supported[cmd])
+		return -EREMOTEIO; /* chip NAKs an unimplemented command */
+
+	for (i = 0; i < len; i++)
+		buf[i] = (u8)(priv->reg[cmd] >> (8 * i));
+	return 0;
+}
+
+static int sandbox_pmbus_write(struct sandbox_pmbus_priv *priv, u8 cmd,
+			       const u8 *buf, int len)
+{
+	if (len == 0)
+		return 0; /* send-byte (eg CLEAR_FAULTS): just ACK */
+	if (!priv->supported[cmd])
+		return -EREMOTEIO;
+	if (len == 1)
+		priv->reg[cmd] = buf[0];
+	else
+		priv->reg[cmd] = (u16)buf[0] | ((u16)buf[1] << 8);
+	return 0;
+}
+
+static int sandbox_pmbus_xfer(struct udevice *emul, struct i2c_msg *msg,
+			      int nmsgs)
+{
+	struct sandbox_pmbus_priv *priv = dev_get_priv(emul);
+	u8 cmd;
+
+	if (nmsgs == 0)
+		return 0;
+	/* A PMBus transaction always opens with the command-code write. */
+	if (msg[0].flags & I2C_M_RD)
+		return -EIO;
+	if (msg[0].len == 0)
+		return 0; /* address-only probe */
+	cmd = msg[0].buf[0];
+
+	if (nmsgs >= 2 && (msg[1].flags & I2C_M_RD))
+		return sandbox_pmbus_read(priv, cmd, msg[1].buf, msg[1].len);
+
+	return sandbox_pmbus_write(priv, cmd, msg[0].buf + 1, msg[0].len - 1);
+}
+
+static void sandbox_pmbus_support(struct sandbox_pmbus_priv *priv, u8 cmd,
+				  u16 val)
+{
+	priv->supported[cmd] = true;
+	priv->reg[cmd] = val;
+}
+
+static int sandbox_pmbus_probe(struct udevice *emul)
+{
+	struct sandbox_pmbus_priv *priv = dev_get_priv(emul);
+
+	/* Configuration / identification. */
+	sandbox_pmbus_support(priv, PMBUS_PAGE, 0);
+	sandbox_pmbus_support(priv, PMBUS_OPERATION, PB_OPERATION_ON);
+	sandbox_pmbus_support(priv, PMBUS_ON_OFF_CONFIG, 0);
+	sandbox_pmbus_support(priv, PMBUS_WRITE_PROTECT, 0);
+	sandbox_pmbus_support(priv, PMBUS_CAPABILITY, 0xb0);
+	sandbox_pmbus_support(priv, PMBUS_VOUT_MODE, 0x18); /* LINEAR 2^-8 */
+	sandbox_pmbus_support(priv, PMBUS_VOUT_COMMAND, 0x0200);
+	sandbox_pmbus_support(priv, PMBUS_VOUT_TRIM, 0);
+	sandbox_pmbus_support(priv, PMBUS_VOUT_MAX, 0x0400);
+	sandbox_pmbus_support(priv, PMBUS_VOUT_SCALE_LOOP, 0);
+	sandbox_pmbus_support(priv, PMBUS_REVISION, PMBUS_REV_13);
+
+	/* Status registers, all clean. */
+	sandbox_pmbus_support(priv, PMBUS_STATUS_BYTE, 0);
+	sandbox_pmbus_support(priv, PMBUS_STATUS_WORD, 0);
+	sandbox_pmbus_support(priv, PMBUS_STATUS_VOUT, 0);
+	sandbox_pmbus_support(priv, PMBUS_STATUS_IOUT, 0);
+	sandbox_pmbus_support(priv, PMBUS_STATUS_INPUT, 0);
+	sandbox_pmbus_support(priv, PMBUS_STATUS_TEMPERATURE, 0);
+	sandbox_pmbus_support(priv, PMBUS_STATUS_CML, 0);
+
+	/*
+	 * Telemetry the emulated chip implements. READ_IIN and READ_POUT
+	 * are intentionally absent so callers see the unsupported path.
+	 */
+	sandbox_pmbus_support(priv, PMBUS_READ_VIN, 0x0abc);
+	sandbox_pmbus_support(priv, PMBUS_READ_VOUT, 0x0200);
+	sandbox_pmbus_support(priv, PMBUS_READ_IOUT, 0x0123);
+	sandbox_pmbus_support(priv, PMBUS_READ_TEMPERATURE_1, 0x0019);
+
+	return 0;
+}
+
+static struct dm_i2c_ops sandbox_pmbus_emul_ops = {
+	.xfer = sandbox_pmbus_xfer,
+};
+
+static const struct udevice_id sandbox_pmbus_ids[] = {
+	{ .compatible = "sandbox,i2c-pmbus" },
+	{ }
+};
+
+U_BOOT_DRIVER(sandbox_pmbus_emul) = {
+	.name		= "sandbox_pmbus_emul",
+	.id		= UCLASS_I2C_EMUL,
+	.of_match	= sandbox_pmbus_ids,
+	.probe		= sandbox_pmbus_probe,
+	.priv_auto	= sizeof(struct sandbox_pmbus_priv),
+	.ops		= &sandbox_pmbus_emul_ops,
+};
-- 
2.43.0



More information about the U-Boot mailing list