test: dm: pmbus: add a sandbox PMBus chip emulator

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@free.fr>
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
This commit is contained in:
Vincent Jardin
2026-07-13 11:24:40 +08:00
committed by Peng Fan
parent a5639a4d7b
commit 995cc3383b
6 changed files with 199 additions and 0 deletions
+1
View File
@@ -1541,6 +1541,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
+10
View File
@@ -1027,6 +1027,9 @@
emul1: emull {
compatible = "sandbox,i2c-rtc-emul";
};
emul_pmbus: emul-pmbus {
compatible = "sandbox,i2c-pmbus";
};
};
sandbox_pmic: sandbox_pmic@40 {
@@ -1038,6 +1041,13 @@
reg = <0x41>;
sandbox,emul = <&emul_pmic1>;
};
pmbus@70 {
reg = <0x70>;
compatible = "pmbus";
regulator-name = "sandbox-pmbus-vout";
sandbox,emul = <&emul_pmbus>;
};
};
i3c0 {
+6
View File
@@ -138,6 +138,7 @@ CONFIG_CMD_PSTORE=y
CONFIG_CMD_PSTORE_MEM_ADDR=0x3000000
CONFIG_CMD_BOOTSTAGE=y
CONFIG_CMD_PMIC=y
CONFIG_CMD_PMBUS=y
CONFIG_CMD_REGULATOR=y
CONFIG_CMD_AES=y
CONFIG_CMD_TPM=y
@@ -312,6 +313,9 @@ CONFIG_REGULATOR_S5M8767=y
CONFIG_DM_REGULATOR_SANDBOX=y
CONFIG_REGULATOR_TPS65090=y
CONFIG_DM_REGULATOR_SCMI=y
CONFIG_DM_REGULATOR_PMBUS_HELPER=y
CONFIG_DM_REGULATOR_PMBUS_GENERIC=y
CONFIG_SANDBOX_PMBUS=y
CONFIG_DM_PWM=y
CONFIG_PWM_CROS_EC=y
CONFIG_PWM_SANDBOX=y
@@ -341,6 +345,7 @@ CONFIG_SYSINFO=y
CONFIG_SYSINFO_SANDBOX=y
CONFIG_SYSINFO_GPIO=y
CONFIG_DM_THERMAL=y
CONFIG_PMBUS_THERMAL=y
CONFIG_TIMER_EARLY=y
CONFIG_SANDBOX_TIMER=y
CONFIG_USB=y
@@ -381,6 +386,7 @@ CONFIG_FS_EXFAT=y
CONFIG_FS_CRAMFS=y
CONFIG_ADDR_MAP=y
CONFIG_PANIC_HANG=y
CONFIG_PMBUS=y
CONFIG_CMD_DHRYSTONE=y
CONFIG_MBEDTLS_LIB=y
CONFIG_HKDF_MBEDTLS=y
+10
View File
@@ -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.
+1
View File
@@ -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
+171
View File
@@ -0,0 +1,171 @@
// 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 (len < 1)
return -EINVAL;
if (str) {
int slen = strlen(str);
/*
* Block payload: [length][bytes...]. A one-byte read is
* the SMBus length probe and reports the true length (the
* master just NAKs early). Any longer read must have room
* for length + payload, otherwise the length byte would
* lie about the data actually returned.
*/
if (len > 1 && len < slen + 1)
return -EREMOTEIO;
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, 0x30);
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,
};