CI: https://git.u-boot-project.org/u-boot/custodians/u-boot-mmc/-/pipelines/614

- Add PMBUS regulator, thermal and test
- Add regulator helper to set voltage within an acceptable range
- Update dw_mmc to use in-spec voltage range for vqmmc
- Fix regulator_enable/disable() macros
- Clear LPUART OR STAT in tstc to avoid hang
- Add MAINTAINERS entry for SDHCI
This commit is contained in:
Tom Rini
2026-07-14 07:59:50 -06:00
38 changed files with 4759 additions and 75 deletions
+20
View File
@@ -1532,6 +1532,21 @@ S: Maintained
F: cmd/pci_mps.c
F: test/cmd/pci_mps.c
PMBUS
M: Vincent Jardin <vjardin@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
F: drivers/power/regulator/sandbox_pmbus.c
F: drivers/thermal/pmbus_thermal.c
F: include/pmbus.h
F: lib/pmbus.c
F: test/dm/pmbus.c
POWER
M: Jaehoon Chung <jh80.chung@samsung.com>
M: Peng Fan <peng.fan@nxp.com>
@@ -1658,6 +1673,11 @@ F: drivers/firmware/scmi/
F: include/scmi*
N: scmi
SDHCI CADENCE
M: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
S: Maintained
F: drivers/mmc/sdhci-cadence*
SEAMA
M: Linus Walleij <linusw@kernel.org>
S: Maintained
+4
View File
@@ -242,24 +242,28 @@ config VOL_MONITOR_IR36021_SET
config VOL_MONITOR_LTC3882_READ
bool "Enable the LTC3882 voltage monitor read"
select PMBUS
help
This option enables LTC3882 voltage monitor read
functionality. It is used by the common VID driver.
config VOL_MONITOR_LTC3882_SET
bool "Enable the LTC3882 voltage monitor set"
select PMBUS
help
This option enables LTC3882 voltage monitor set
functionality. It is used by the common VID driver.
config VOL_MONITOR_ISL68233_READ
bool "Enable the ISL68233 voltage monitor read"
select PMBUS
help
This option enables ISL68233 voltage monitor read
functionality. It is used by the common VID driver.
config VOL_MONITOR_ISL68233_SET
bool "Enable the ISL68233 voltage monitor set"
select PMBUS
help
This option enables ISL68233 voltage monitor set
functionality. It is used by the common VID driver.
+6
View File
@@ -47,6 +47,12 @@
regulator-min-microvolt = <1500000>;
regulator-max-microvolt = <1500000>;
};
ldo3 {
regulator-name = "SUPPLY_1.8_3.3V";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
};
};
&mc34708 {
+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 {
+61 -49
View File
@@ -11,6 +11,7 @@
#include <i2c.h>
#include <irq_func.h>
#include <log.h>
#include <pmbus.h>
#include <vsprintf.h>
#include <asm/io.h>
#ifdef CONFIG_FSL_LSCH2
@@ -260,45 +261,38 @@ static int read_voltage_from_IR(int i2caddress)
*/
#define VOUT_WARNING "VID: VOUT_MODE exponent has resolution worse than 1 V!\n"
/* Checks the PMBus voltage monitor for the format used for voltage values */
static int get_pmbus_multiplier(DEVICE_HANDLE_T dev)
/*
* Read VOUT_MODE for downstream LINEAR16 decode/encode through the
* tree level <pmbus.h> helpers (pmbus_reg2data_linear16,
* pmbus_data2reg_linear16). Stores the raw VOUT_MODE byte in *mode
* and returns 0 on success, or the negative bus error (a failed read
* must not be decoded: raw 0 aliases Linear mode with exponent 0).
* Emits VOUT_WARNING on Linear mode chips with a non negative
* exponent (resolution >= 1 V is unusable for sub volt SoC rails)
* and an informational note on the unsupported VID format.
*/
static int vid_read_vout_mode(DEVICE_HANDLE_T dev, u8 *mode)
{
u8 mode;
int exponent, multiplier, ret;
int ret;
ret = I2C_READ(dev, PMBUS_CMD_VOUT_MODE, &mode, sizeof(mode));
ret = pmbus_read_byte(dev, PMBUS_VOUT_MODE, mode);
if (ret) {
printf("VID: unable to determine voltage multiplier\n");
return 1;
return ret;
}
/* Upper 3 bits is mode, lower 5 bits is exponent */
exponent = (int)mode & 0x1F;
mode >>= 5;
switch (mode) {
case 0:
/* Linear, 5 bit twos component exponent */
if (exponent & 0x10) {
multiplier = 1 << (16 - (exponent & 0xF));
} else {
/* If exponent is >= 0, then resolution is 1 V! */
switch (*mode & PB_VOUT_MODE_MODE_MASK) {
case PB_VOUT_MODE_LINEAR:
if (!(*mode & 0x10))
printf(VOUT_WARNING);
multiplier = 1;
}
break;
case 1:
/* VID code identifier */
case PB_VOUT_MODE_VID:
printf("VID: custom VID codes are not supported\n");
multiplier = MV_PER_V;
break;
default:
/* Direct, in mV */
multiplier = MV_PER_V;
break;
}
debug("VID: calculated multiplier is %d\n", multiplier);
return multiplier;
return 0;
}
#endif
@@ -306,8 +300,8 @@ static int get_pmbus_multiplier(DEVICE_HANDLE_T dev)
defined(CONFIG_VOL_MONITOR_LTC3882_READ)
static int read_voltage_from_pmbus(int i2caddress)
{
int ret, multiplier, vout;
u8 channel = PWM_CHANNEL0;
int ret, vout;
u8 channel = PWM_CHANNEL0, vout_mode;
u16 vcode;
DEVICE_HANDLE_T dev;
@@ -317,25 +311,33 @@ static int read_voltage_from_pmbus(int i2caddress)
return ret;
/* Select the right page */
ret = I2C_WRITE(dev, PMBUS_CMD_PAGE, &channel, sizeof(channel));
ret = pmbus_write_byte(dev, PMBUS_PAGE, channel);
if (ret) {
printf("VID: failed to select VDD page %d\n", channel);
return ret;
}
/* VOUT is little endian */
ret = I2C_READ(dev, PMBUS_CMD_READ_VOUT, (void *)&vcode, sizeof(vcode));
ret = pmbus_read_word(dev, PMBUS_READ_VOUT, &vcode);
if (ret) {
printf("VID: failed to read core voltage\n");
return ret;
}
/* Scale down to the real mV */
multiplier = get_pmbus_multiplier(dev);
vout = (int)vcode;
/* Multiplier 1000 (direct mode) requires no change to convert */
if (multiplier != MV_PER_V)
vout = DIV_ROUND_UP(vout * MV_PER_V, multiplier);
/*
* Decode LINEAR16 via the tree level helper from <pmbus.h>. For
* non Linear VOUT_MODE settings the helper returns 0; fall back
* to the historic mV pass through so existing LSCH boards keep.
*/
ret = vid_read_vout_mode(dev, &vout_mode);
if (ret)
return ret;
if ((vout_mode & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_LINEAR) {
s64 uv = pmbus_reg2data_linear16(vcode, vout_mode);
vout = (int)((uv + 500) / 1000); /* round to mV */
} else {
vout = (int)vcode;
}
return vout - board_vdd_drop_compensation();
}
#endif
@@ -463,11 +465,13 @@ static int set_voltage_to_IR(int i2caddress, int vdd)
static int set_voltage_to_pmbus(int i2caddress, int vdd)
{
int ret, vdd_last, vdd_target = vdd;
int count = MAX_LOOP_WAIT_NEW_VOL, temp = 0, multiplier;
int count = MAX_LOOP_WAIT_NEW_VOL, temp = 0;
u8 vout_mode;
u16 raw;
unsigned char value;
/* The data to be sent with the PMBus command PAGE_PLUS_WRITE */
u8 buffer[5] = { 0x04, PWM_CHANNEL0, PMBUS_CMD_VOUT_COMMAND, 0, 0 };
u8 buffer[5] = { 0x04, PWM_CHANNEL0, PMBUS_VOUT_COMMAND, 0, 0 };
DEVICE_HANDLE_T dev;
/* Open device handle */
@@ -475,24 +479,32 @@ static int set_voltage_to_pmbus(int i2caddress, int vdd)
if (ret)
return ret;
/* Scale up to the proper value for the VOUT command, little endian */
multiplier = get_pmbus_multiplier(dev);
/*
* Encode target mV as LINEAR16 raw via the tree level helper
* from <pmbus.h>. For non Linear VOUT_MODE settings the helper
* returns 0; fall back to the historic mV pass through. A failed
* VOUT_MODE read aborts: never write a voltage code whose
* encoding could not be determined.
*/
vdd += board_vdd_drop_compensation();
if (multiplier != MV_PER_V)
vdd = DIV_ROUND_UP(vdd * multiplier, MV_PER_V);
buffer[3] = vdd & 0xFF;
buffer[4] = (vdd & 0xFF00) >> 8;
ret = vid_read_vout_mode(dev, &vout_mode);
if (ret)
return ret;
if ((vout_mode & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_LINEAR)
raw = pmbus_data2reg_linear16((s64)vdd * 1000LL, vout_mode);
else
raw = (u16)vdd;
buffer[3] = raw & 0xFF;
buffer[4] = (raw & 0xFF00) >> 8;
/* Check write protect state */
ret = I2C_READ(dev, PMBUS_CMD_WRITE_PROTECT, (void *)&value,
sizeof(value));
ret = pmbus_read_byte(dev, PMBUS_WRITE_PROTECT, &value);
if (ret)
goto exit;
if (value != EN_WRITE_ALL_CMD) {
value = EN_WRITE_ALL_CMD;
ret = I2C_WRITE(dev, PMBUS_CMD_WRITE_PROTECT,
(void *)&value, sizeof(value));
ret = pmbus_write_byte(dev, PMBUS_WRITE_PROTECT, value);
if (ret)
goto exit;
}
+9 -6
View File
@@ -22,8 +22,9 @@
#define IR_VDD_STEP_UP 5
/* LTC3882 */
#define PMBUS_CMD_WRITE_PROTECT 0x10
/*
* PMBUS_WRITE_PROTECT (10h) provided by <pmbus.h>
*
* WRITE_PROTECT command supported values
* 0x80: Disable all writes except WRITE_PROTECT, PAGE,
* STORE_USER_ALL and MFR_EE_UNLOCK commands.
@@ -51,12 +52,14 @@
#define VDD_MV_MAX 925
#endif
/* PM Bus commands code for LTC3882*/
/*
* PM Bus commands code for LTC3882. PMBUS_PAGE / PMBUS_READ_VOUT /
* PMBUS_VOUT_MODE / PMBUS_VOUT_COMMAND are provided by <pmbus.h>.
* PMBUS_CMD_PAGE_PLUS_WRITE (05h) is the LTC3882 SMBus block write
* transaction not in <pmbus.h>'s standard subset, so keep its
* definition here.
*/
#define PWM_CHANNEL0 0x0
#define PMBUS_CMD_PAGE 0x0
#define PMBUS_CMD_READ_VOUT 0x8B
#define PMBUS_CMD_VOUT_MODE 0x20
#define PMBUS_CMD_VOUT_COMMAND 0x21
#define PMBUS_CMD_PAGE_PLUS_WRITE 0x05
#if defined(CONFIG_TARGET_LX2160AQDS) || defined(CONFIG_TARGET_LX2162AQDS) || \
+6 -5
View File
@@ -7,6 +7,7 @@
#include <display_options.h>
#include <env.h>
#include <i2c.h>
#include <pmbus.h>
#include <init.h>
#include <log.h>
#include <malloc.h>
@@ -667,13 +668,13 @@ int get_serdes_volt(void)
/* Select the PAGE 0 using PMBus commands PAGE for VDD */
#if !CONFIG_IS_ENABLED(DM_I2C)
ret = i2c_write(I2C_SVDD_MONITOR_ADDR,
PMBUS_CMD_PAGE, 1, &chan, 1);
PMBUS_PAGE, 1, &chan, 1);
#else
struct udevice *dev;
ret = i2c_get_chip_for_busnum(0, I2C_SVDD_MONITOR_ADDR, 1, &dev);
if (!ret)
ret = dm_i2c_write(dev, PMBUS_CMD_PAGE,
ret = dm_i2c_write(dev, PMBUS_PAGE,
&chan, 1);
#endif
@@ -685,9 +686,9 @@ int get_serdes_volt(void)
/* Read the output voltage using PMBus command READ_VOUT */
#if !CONFIG_IS_ENABLED(DM_I2C)
ret = i2c_read(I2C_SVDD_MONITOR_ADDR,
PMBUS_CMD_READ_VOUT, 1, (void *)&vcode, 2);
PMBUS_READ_VOUT, 1, (void *)&vcode, 2);
#else
dm_i2c_read(dev, PMBUS_CMD_READ_VOUT, (void *)&vcode, 2);
dm_i2c_read(dev, PMBUS_READ_VOUT, (void *)&vcode, 2);
#endif
if (ret) {
printf("VID: failed to read the voltage\n");
@@ -700,7 +701,7 @@ int get_serdes_volt(void)
int set_serdes_volt(int svdd)
{
int ret, vdd_last;
u8 buff[5] = {0x04, PWM_CHANNEL0, PMBUS_CMD_VOUT_COMMAND,
u8 buff[5] = {0x04, PWM_CHANNEL0, PMBUS_VOUT_COMMAND,
svdd & 0xFF, (svdd & 0xFF00) >> 8};
/* Write the desired voltage code to the SVDD regulator */
+14
View File
@@ -2723,6 +2723,20 @@ config CMD_PMIC
- pmic write address - write byte to register at address
The only one change for this command is 'dev' subcommand.
config CMD_PMBUS
bool "pmbus device interrogation and control command"
depends on PMBUS
help
Enable the pmbus U-Boot CLI command. Provides identification,
decoded telemetry, STATUS_* decoding, raw register read/write,
CLEAR_FAULTS, and VOUT_COMMAND set/get against any PMBus 1.x
compliant device selectable via 'pmbus dev <bus>:<addr>'.
Per chip drivers and board files publish vendor extensions in
the 'pmbus <vendor> ...' namespace.
See doc/develop/pmbus.rst for the full usage reference.
config CMD_REGULATOR
bool "Enable Driver Model REGULATOR command"
depends on DM_REGULATOR
+1
View File
@@ -228,6 +228,7 @@ obj-$(CONFIG_CMD_AXI) += axi.o
obj-$(CONFIG_CMD_PVBLOCK) += pvblock.o
# Power
obj-$(CONFIG_CMD_PMBUS) += pmbus.o
obj-$(CONFIG_CMD_PMIC) += pmic.o
obj-$(CONFIG_CMD_REGULATOR) += regulator.o
+814
View File
@@ -0,0 +1,814 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2026 Free Mobile, Vincent Jardin
*
* pmbus U-Boot CLI command.
*
* Generic command surface over the PMBus 1.x framework defined in
* <pmbus.h> + lib/pmbus.c.
*
* See doc/develop/pmbus.rst for the full usage reference.
*/
#include <command.h>
#include <dm.h>
#include <i2c.h>
#include <log.h>
#include <pmbus.h>
#include <vsprintf.h>
#include <linux/ctype.h>
#include <power/regulator.h>
static int parse_bus_addr(const char *s, int *bus_seq, u8 *addr)
{
char busbuf[8];
const char *colon;
unsigned long b, a;
size_t buslen;
colon = strchr(s, ':');
if (!colon)
return -EINVAL;
buslen = colon - s;
if (buslen == 0 || buslen >= sizeof(busbuf))
return -EINVAL;
memcpy(busbuf, s, buslen);
busbuf[buslen] = '\0';
if (strict_strtoul(busbuf, 10, &b))
return -EINVAL;
if (strict_strtoul(colon + 1, 16, &a) || a > 0x7f)
return -EINVAL;
*bus_seq = (int)b;
*addr = (u8)a;
return 0;
}
static const struct {
const char *name;
u8 reg;
} pmbus_reg_syms[] = {
{ "PAGE", PMBUS_PAGE },
{ "OPERATION", PMBUS_OPERATION },
{ "ON_OFF_CONFIG", PMBUS_ON_OFF_CONFIG },
{ "CLEAR_FAULTS", PMBUS_CLEAR_FAULTS },
{ "WRITE_PROTECT", PMBUS_WRITE_PROTECT },
{ "CAPABILITY", PMBUS_CAPABILITY },
{ "VOUT_MODE", PMBUS_VOUT_MODE },
{ "VOUT_COMMAND", PMBUS_VOUT_COMMAND },
{ "VOUT_TRIM", PMBUS_VOUT_TRIM },
{ "VOUT_MAX", PMBUS_VOUT_MAX },
{ "VOUT_SCALE_LOOP", PMBUS_VOUT_SCALE_LOOP },
{ "STATUS_BYTE", PMBUS_STATUS_BYTE },
{ "STATUS_WORD", PMBUS_STATUS_WORD },
{ "STATUS_VOUT", PMBUS_STATUS_VOUT },
{ "STATUS_IOUT", PMBUS_STATUS_IOUT },
{ "STATUS_INPUT", PMBUS_STATUS_INPUT },
{ "STATUS_TEMP", PMBUS_STATUS_TEMPERATURE },
{ "STATUS_CML", PMBUS_STATUS_CML },
{ "READ_VIN", PMBUS_READ_VIN },
{ "READ_IIN", PMBUS_READ_IIN },
{ "READ_VOUT", PMBUS_READ_VOUT },
{ "READ_IOUT", PMBUS_READ_IOUT },
{ "READ_TEMP1", PMBUS_READ_TEMPERATURE_1 },
{ "READ_TEMP2", PMBUS_READ_TEMPERATURE_2 },
{ "READ_TEMP3", PMBUS_READ_TEMPERATURE_3 },
{ "READ_DUTY", PMBUS_READ_DUTY_CYCLE },
{ "READ_FREQ", PMBUS_READ_FREQUENCY },
{ "READ_POUT", PMBUS_READ_POUT },
{ "READ_PIN", PMBUS_READ_PIN },
{ "REVISION", PMBUS_REVISION },
{ "MFR_ID", PMBUS_MFR_ID },
{ "MFR_MODEL", PMBUS_MFR_MODEL },
{ "MFR_REVISION", PMBUS_MFR_REVISION },
};
static int parse_reg(const char *s, u8 *reg)
{
unsigned long v;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(pmbus_reg_syms); i++) {
if (!strcasecmp(s, pmbus_reg_syms[i].name)) {
*reg = pmbus_reg_syms[i].reg;
return 0;
}
}
if (strict_strtoul(s, 16, &v) || v > 0xff)
return -EINVAL;
*reg = (u8)v;
return 0;
}
static const char *pmbus_reg_name(u8 reg)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(pmbus_reg_syms); i++)
if (pmbus_reg_syms[i].reg == reg)
return pmbus_reg_syms[i].name;
return "?";
}
static int require_active(struct udevice **chip,
const struct pmbus_active_dev **act)
{
*act = pmbus_active();
if (!*act) {
printf("pmbus: no active device. Use 'pmbus dev <bus>:<addr>' first.\n");
return CMD_RET_FAILURE;
}
if (pmbus_active_get_i2c(chip)) {
printf("pmbus: cannot reach i2c%d:0x%02x\n",
(*act)->bus_seq, (*act)->addr);
return CMD_RET_FAILURE;
}
return CMD_RET_SUCCESS;
}
static void print_micro(s64 micro, const char *unit)
{
s64 abs_milli;
abs_milli = (micro < 0 ? -micro : micro) / 1000LL;
printf("%lld.%03lld%s",
(long long)(micro / 1000000LL),
(long long)(abs_milli % 1000LL), unit);
}
static void print_active(const struct pmbus_active_dev *act)
{
printf("pmbus: active i2c%d:0x%02x", act->bus_seq, act->addr);
if (act->name[0])
printf(" rail=\"%s\"", act->name);
printf(" MFR_ID=\"%s\" MODEL=\"%s\" vendor=%s%s\n",
act->mfr_id[0] ? act->mfr_id : "?",
act->mfr_model[0] ? act->mfr_model : "?",
act->vendor[0] ? act->vendor : "(generic)",
act->info ? "" : " [no driver_info]");
}
static int do_dev(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
int bus_seq, ret;
u8 addr;
if (argc < 2) {
act = pmbus_active();
if (!act) {
printf("pmbus: no active device\n");
return CMD_RET_SUCCESS;
}
print_active(act);
return CMD_RET_SUCCESS;
}
if (parse_bus_addr(argv[1], &bus_seq, &addr) < 0) {
ret = pmbus_resolve_by_name(argv[1], &bus_seq, &addr);
if (ret) {
printf("pmbus: '%s' is neither <bus>:<addr> nor a known regulator-name (%d)\n",
argv[1], ret);
return CMD_RET_FAILURE;
}
}
ret = pmbus_set_active(bus_seq, addr);
if (ret) {
printf("pmbus: cannot select i2c%d:0x%02x (%d)\n",
bus_seq, addr, ret);
return CMD_RET_FAILURE;
}
act = pmbus_active();
if (act)
print_active(act);
return CMD_RET_SUCCESS;
}
static int do_list(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct udevice *dev;
struct uclass *uc;
int ret, n = 0;
if (!IS_ENABLED(CONFIG_DM_REGULATOR)) {
printf("pmbus: CONFIG_DM_REGULATOR not enabled in this build. Use 'pmbus dev <bus>:<addr>'.\n");
return CMD_RET_SUCCESS;
}
ret = uclass_get(UCLASS_REGULATOR, &uc);
if (ret) {
printf("pmbus: UCLASS_REGULATOR not available\n");
return CMD_RET_SUCCESS;
}
uclass_foreach_dev(dev, uc) {
struct dm_regulator_uclass_plat *up = dev_get_uclass_plat(dev);
struct udevice *parent = dev_get_parent(dev);
const char *rname = (up && up->name) ? up->name : "";
const char *drv = (dev->driver && dev->driver->name)
? dev->driver->name : "?";
int bus_seq = -1;
int addr = -1;
if (parent && device_get_uclass_id(parent) == UCLASS_I2C) {
bus_seq = dev_seq(parent);
addr = dev_read_addr(dev);
}
if (n == 0)
printf("UCLASS_REGULATOR devices (no PMBus filter):\n");
if (bus_seq >= 0 && addr >= 0) {
printf(" i2c%d:0x%02x rail=\"%s\" node=%s driver=%s\n",
bus_seq, addr, rname, dev->name, drv);
} else {
printf(" (non-I2C) rail=\"%s\" node=%s driver=%s\n",
rname, dev->name, drv);
}
n++;
}
if (!n)
printf("pmbus: no UCLASS_REGULATOR devices bound. Use 'pmbus dev <bus>:<addr>' to select a chip directly.\n");
return CMD_RET_SUCCESS;
}
static int do_info(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
static const char * const cls_names[PSC_NUM_CLASSES] = {
"VOLTAGE_IN", "VOLTAGE_OUT", "CURRENT_IN", "CURRENT_OUT",
"POWER", "TEMPERATURE",
};
static const char * const fmt_names[] = {
"LINEAR", "IEEE754", "DIRECT", "VID",
};
const struct pmbus_active_dev *act;
struct udevice *chip;
int rc, c, rrev;
u8 rev = 0;
rc = require_active(&chip, &act);
if (rc)
return rc;
rrev = pmbus_read_byte(chip, PMBUS_REVISION, &rev);
printf("pmbus device i2c%d:0x%02x\n", act->bus_seq, act->addr);
if (act->name[0])
printf(" regulator-name: \"%s\"\n", act->name);
printf(" MFR_ID : \"%s\"\n", act->mfr_id[0] ? act->mfr_id : "?");
printf(" MFR_MODEL : \"%s\"\n", act->mfr_model[0] ? act->mfr_model : "?");
/*
* MFR_REVISION may encodes the revision as a non printable byte
* (BCD nibbles, packed major / minor, etc.). Show both the
* printable form and the raw bytes the chip returned.
*/
{
u8 raw[PMBUS_MFR_STRING_MAX];
int len, i;
if (dm_i2c_read(chip, PMBUS_MFR_REVISION, raw, 1) ||
raw[0] < 1 || raw[0] > sizeof(raw) - 1 ||
dm_i2c_read(chip, PMBUS_MFR_REVISION, raw, raw[0] + 1)) {
printf(" MFR_REVISION : \"%s\"\n",
act->mfr_revision[0] ? act->mfr_revision : "?");
} else {
len = raw[0];
printf(" MFR_REVISION : \"%s\" raw=0x",
act->mfr_revision[0] ? act->mfr_revision : "?");
for (i = 1; i <= len; i++)
printf("%02x", raw[i]);
printf("\n");
}
}
if (rrev)
printf(" PMBUS_REVISION: <read failed (%d)>\n", rrev);
else
printf(" PMBUS_REVISION: 0x%02x (%s)\n", rev,
rev == PMBUS_REV_13 ? "PMBus 1.3" :
rev == PMBUS_REV_12 ? "PMBus 1.2" :
rev == PMBUS_REV_11 ? "PMBus 1.1" :
rev == PMBUS_REV_10 ? "PMBus 1.0" : "unknown");
printf(" vendor : %s\n", act->vendor[0] ? act->vendor : "(none)");
if (!act->info) {
printf(" driver_info : not registered (decoders fall back to LINEAR16 / LINEAR11)\n");
return CMD_RET_SUCCESS;
}
printf(" driver_info : pages=%d\n", act->info->pages);
for (c = 0; c < PSC_NUM_CLASSES; c++) {
printf(" [%-12s] format=%s",
cls_names[c], fmt_names[act->info->format[c]]);
if (act->info->format[c] == pmbus_fmt_direct)
printf(", m=%d, b=%d, R=%d",
act->info->m[c], act->info->b[c], act->info->R[c]);
printf("\n");
}
return CMD_RET_SUCCESS;
}
static int do_telemetry(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
struct udevice *chip;
int rc;
rc = require_active(&chip, &act);
if (rc)
return rc;
printf("pmbus telemetry @ i2c%d:0x%02x\n", act->bus_seq, act->addr);
pmbus_print_telemetry(chip);
return CMD_RET_SUCCESS;
}
static int do_status(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
struct udevice *chip;
u16 word = 0;
u8 b;
int rc;
rc = require_active(&chip, &act);
if (rc)
return rc;
if (pmbus_read_word(chip, PMBUS_STATUS_WORD, &word)) {
printf("pmbus: STATUS_WORD read failed\n");
return CMD_RET_FAILURE;
}
const struct pmbus_status_override *ovr =
act->info ? act->info->status_overrides : NULL;
printf("pmbus status @ i2c%d:0x%02x\n", act->bus_seq, act->addr);
printf(" STATUS_WORD (79h) = 0x%04x [", word);
pmbus_print_status_bits(PMBUS_STATUS_WORD, word,
pmbus_status_word_bits, ovr);
printf("]\n");
if (pmbus_read_byte(chip, PMBUS_STATUS_VOUT, &b) == 0) {
printf(" STATUS_VOUT (7Ah) = 0x%02x [", b);
pmbus_print_status_bits(PMBUS_STATUS_VOUT, b,
pmbus_status_vout_bits, ovr);
printf("]\n");
}
if (pmbus_read_byte(chip, PMBUS_STATUS_IOUT, &b) == 0) {
printf(" STATUS_IOUT (7Bh) = 0x%02x [", b);
pmbus_print_status_bits(PMBUS_STATUS_IOUT, b,
pmbus_status_iout_bits, ovr);
printf("]\n");
}
if (pmbus_read_byte(chip, PMBUS_STATUS_INPUT, &b) == 0) {
printf(" STATUS_INPUT (7Ch) = 0x%02x [", b);
pmbus_print_status_bits(PMBUS_STATUS_INPUT, b,
pmbus_status_input_bits, ovr);
printf("]\n");
}
if (pmbus_read_byte(chip, PMBUS_STATUS_TEMPERATURE, &b) == 0) {
printf(" STATUS_TEMP (7Dh) = 0x%02x [", b);
pmbus_print_status_bits(PMBUS_STATUS_TEMPERATURE, b,
pmbus_status_temp_bits, ovr);
printf("]\n");
}
if (pmbus_read_byte(chip, PMBUS_STATUS_CML, &b) == 0) {
printf(" STATUS_CML (7Eh) = 0x%02x [", b);
pmbus_print_status_bits(PMBUS_STATUS_CML, b,
pmbus_status_cml_bits, ovr);
printf("]\n");
}
return CMD_RET_SUCCESS;
}
static int do_dump(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
struct udevice *chip;
unsigned int i;
int rc;
rc = require_active(&chip, &act);
if (rc)
return rc;
printf("pmbus dump @ i2c%d:0x%02x (registers known to <pmbus.h>)\n",
act->bus_seq, act->addr);
for (i = 0; i < ARRAY_SIZE(pmbus_reg_syms); i++) {
u8 reg = pmbus_reg_syms[i].reg;
u8 b = 0;
u16 w = 0;
switch (reg) {
case PMBUS_PAGE:
case PMBUS_OPERATION:
case PMBUS_ON_OFF_CONFIG:
case PMBUS_WRITE_PROTECT:
case PMBUS_CAPABILITY:
case PMBUS_VOUT_MODE:
case PMBUS_STATUS_BYTE:
case PMBUS_STATUS_VOUT:
case PMBUS_STATUS_IOUT:
case PMBUS_STATUS_INPUT:
case PMBUS_STATUS_TEMPERATURE:
case PMBUS_STATUS_CML:
case PMBUS_REVISION:
if (pmbus_read_byte(chip, reg, &b) == 0)
printf(" %02xh %-15s b=0x%02x\n",
reg, pmbus_reg_syms[i].name, b);
break;
case PMBUS_MFR_ID:
case PMBUS_MFR_MODEL:
case PMBUS_MFR_REVISION: {
char s[PMBUS_MFR_STRING_MAX];
if (pmbus_read_string(chip, reg, s, sizeof(s),
act->mfr_reverse) >= 0)
printf(" %02xh %-15s s=\"%s\"\n",
reg, pmbus_reg_syms[i].name, s);
break;
}
default:
if (pmbus_read_word(chip, reg, &w) == 0)
printf(" %02xh %-15s w=0x%04x\n",
reg, pmbus_reg_syms[i].name, w);
break;
}
}
return CMD_RET_SUCCESS;
}
static int do_read(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
struct udevice *chip;
const char *fmt = "b";
u8 reg, b;
u16 w;
char s[PMBUS_MFR_STRING_MAX];
int rc, ret;
if (argc < 2)
return CMD_RET_USAGE;
rc = require_active(&chip, &act);
if (rc)
return rc;
if (parse_reg(argv[1], &reg) < 0) {
printf("pmbus: invalid register '%s'\n", argv[1]);
return CMD_RET_USAGE;
}
if (argc >= 3)
fmt = argv[2];
if (!strcmp(fmt, "b")) {
ret = pmbus_read_byte(chip, reg, &b);
if (ret) {
printf("pmbus: read byte 0x%02x failed (%d)\n", reg, ret);
return CMD_RET_FAILURE;
}
printf(" %02xh %-15s b=0x%02x\n", reg, pmbus_reg_name(reg), b);
} else if (!strcmp(fmt, "w")) {
ret = pmbus_read_word(chip, reg, &w);
if (ret) {
printf("pmbus: read word 0x%02x failed (%d)\n", reg, ret);
return CMD_RET_FAILURE;
}
printf(" %02xh %-15s w=0x%04x\n", reg, pmbus_reg_name(reg), w);
} else if (!strcmp(fmt, "s")) {
ret = pmbus_read_string(chip, reg, s, sizeof(s), false);
if (ret < 0) {
printf("pmbus: read string 0x%02x failed (%d)\n", reg, ret);
return CMD_RET_FAILURE;
}
printf(" %02xh %-15s s=\"%s\"\n", reg, pmbus_reg_name(reg), s);
} else {
printf("pmbus: unknown format '%s' (expected b, w, or s)\n", fmt);
return CMD_RET_USAGE;
}
return CMD_RET_SUCCESS;
}
static int do_write(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
struct udevice *chip;
const char *fmt = "b";
unsigned long val;
u8 reg, b;
u8 buf[2];
int rc, ret;
if (argc < 3)
return CMD_RET_USAGE;
rc = require_active(&chip, &act);
if (rc)
return rc;
if (parse_reg(argv[1], &reg) < 0) {
printf("pmbus: invalid register '%s'\n", argv[1]);
return CMD_RET_USAGE;
}
if (strict_strtoul(argv[2], 16, &val)) {
printf("pmbus: invalid value '%s'\n", argv[2]);
return CMD_RET_USAGE;
}
if (argc >= 4)
fmt = argv[3];
if (!strcmp(fmt, "b")) {
if (val > 0xff) {
printf("pmbus: byte value out of range\n");
return CMD_RET_USAGE;
}
b = (u8)val;
ret = dm_i2c_write(chip, reg, &b, 1);
} else if (!strcmp(fmt, "w")) {
if (val > 0xffff) {
printf("pmbus: word value out of range\n");
return CMD_RET_USAGE;
}
buf[0] = (u8)(val & 0xff);
buf[1] = (u8)((val >> 8) & 0xff);
ret = dm_i2c_write(chip, reg, buf, 2);
} else {
printf("pmbus: unknown format '%s' (expected b or w)\n", fmt);
return CMD_RET_USAGE;
}
if (ret) {
printf("pmbus: write 0x%02x failed (%d)\n", reg, ret);
return CMD_RET_FAILURE;
}
printf("pmbus: wrote 0x%lx to %02xh (%s)\n", val, reg, pmbus_reg_name(reg));
return CMD_RET_SUCCESS;
}
static int do_clear(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
struct udevice *chip;
int rc, ret;
if (argc >= 2 && strcmp(argv[1], "faults") != 0) {
printf("pmbus: unknown clear subcommand '%s' (expected 'faults')\n",
argv[1]);
return CMD_RET_USAGE;
}
rc = require_active(&chip, &act);
if (rc)
return rc;
ret = pmbus_clear_faults(chip);
if (ret) {
printf("pmbus: CLEAR_FAULTS (03h) failed (%d)\n", ret);
return CMD_RET_FAILURE;
}
printf("pmbus: CLEAR_FAULTS (03h) issued (RAM sticky STATUS_* cleared)\n");
return CMD_RET_SUCCESS;
}
static int do_vout(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_active_dev *act;
struct udevice *chip;
u8 vout_mode = 0;
u16 raw;
s64 uv;
int rc, ret;
rc = require_active(&chip, &act);
if (rc)
return rc;
if (pmbus_read_byte(chip, PMBUS_VOUT_MODE, &vout_mode)) {
printf("pmbus: VOUT_MODE read failed\n");
return CMD_RET_FAILURE;
}
if (argc < 2) {
if (pmbus_read_word(chip, PMBUS_READ_VOUT, &raw)) {
printf("pmbus: READ_VOUT failed\n");
return CMD_RET_FAILURE;
}
if (act->info)
uv = pmbus_reg2data(act->info, PSC_VOLTAGE_OUT, raw, vout_mode);
else
uv = pmbus_reg2data_linear16(raw, vout_mode);
printf("pmbus VOUT @ i2c%d:0x%02x raw=0x%04x ",
act->bus_seq, act->addr, raw);
print_micro(uv, "V");
printf("\n");
return CMD_RET_SUCCESS;
}
{
unsigned long target_uv;
const char *fmt_name;
u8 buf[2];
if (strict_strtoul(argv[1], 10, &target_uv)) {
printf("pmbus: invalid microvolt value '%s'\n", argv[1]);
return CMD_RET_USAGE;
}
switch (vout_mode & PB_VOUT_MODE_MODE_MASK) {
case PB_VOUT_MODE_LINEAR:
raw = pmbus_data2reg_linear16((s64)target_uv, vout_mode);
fmt_name = "LINEAR16";
break;
case PB_VOUT_MODE_DIRECT:
if (!act->info ||
act->info->format[PSC_VOLTAGE_OUT] != pmbus_fmt_direct) {
printf("pmbus: VOUT_MODE selects DIRECT but the active driver_info has no DIRECT coefficients for VOLTAGE_OUT\n");
return CMD_RET_FAILURE;
}
raw = pmbus_data2reg_direct((s64)target_uv,
act->info->m[PSC_VOLTAGE_OUT],
act->info->b[PSC_VOLTAGE_OUT],
act->info->R[PSC_VOLTAGE_OUT]);
fmt_name = "DIRECT";
break;
default:
printf("pmbus: VOUT_MODE 0x%02x selects an encoder not yet implemented (VID / IEEE754)\n",
vout_mode);
return CMD_RET_FAILURE;
}
buf[0] = raw & 0xff;
buf[1] = (raw >> 8) & 0xff;
ret = dm_i2c_write(chip, PMBUS_VOUT_COMMAND, buf, 2);
if (ret) {
printf("pmbus: VOUT_COMMAND write failed (%d)\n", ret);
return CMD_RET_FAILURE;
}
printf("pmbus: VOUT_COMMAND <- 0x%04x (%s, target %lu uV)\n",
raw, fmt_name, target_uv);
}
return CMD_RET_SUCCESS;
}
static int pmbus_scan_one_bus(struct udevice *bus, int bus_seq)
{
int hits = 0;
int addr;
for (addr = 0x08; addr <= 0x77; addr++) {
struct udevice *chip;
u8 b;
if (i2c_get_chip(bus, addr, 1, &chip))
continue;
/* MFR_ID block read: 1st byte is the length of the string. */
if (dm_i2c_read(chip, PMBUS_MFR_ID, &b, 1))
continue;
if (b >= 1 && b <= PMBUS_MFR_STRING_MAX - 1) {
char s[PMBUS_MFR_STRING_MAX] = "";
pmbus_read_string(chip, PMBUS_MFR_ID, s, sizeof(s), false);
if (!s[0])
pmbus_read_string(chip, PMBUS_MFR_ID, s, sizeof(s), true);
printf(" i2c%d:0x%02x MFR_ID=\"%s\"\n",
bus_seq, addr, s[0] ? s : "(unprintable)");
hits++;
}
}
return hits;
}
static int do_scan(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
int total = 0;
if (argc >= 2) {
struct udevice *bus;
unsigned long val;
int bus_seq;
if (strict_strtoul(argv[1], 10, &val)) {
printf("pmbus: invalid bus seq '%s'\n", argv[1]);
return CMD_RET_USAGE;
}
bus_seq = (int)val;
if (uclass_get_device_by_seq(UCLASS_I2C, bus_seq, &bus)) {
printf("pmbus: i2c%d not available\n", bus_seq);
return CMD_RET_FAILURE;
}
printf("pmbus scan i2c%d:\n", bus_seq);
total = pmbus_scan_one_bus(bus, bus_seq);
} else {
struct uclass *uc;
struct udevice *bus;
if (uclass_get(UCLASS_I2C, &uc))
return CMD_RET_FAILURE;
uclass_foreach_dev(bus, uc) {
int seq = dev_seq(bus);
if (seq < 0)
continue;
printf("pmbus scan i2c%d:\n", seq);
total += pmbus_scan_one_bus(bus, seq);
}
}
if (!total)
printf("pmbus: no PMBus responders found\n");
return CMD_RET_SUCCESS;
}
static int do_help(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
unsigned int i, n = pmbus_vendor_count();
if (n == 0) {
printf("pmbus: no vendor extensions registered.\n");
printf(" Vendor handlers are registered by per chip drivers at\n");
printf(" probe time; trigger a probe via 'pmbus dev <name>' or a\n");
printf(" board hook (boot snapshot) and re run 'pmbus help'.\n");
return CMD_RET_SUCCESS;
}
printf("Registered pmbus vendor extensions (%u):\n\n", n);
for (i = 0; i < n; i++) {
const struct pmbus_vendor_op *op = pmbus_vendor_at(i);
if (!op)
continue;
printf("[vendor: %s]\n", op->vendor);
if (op->help)
printf("%s", op->help);
printf("\n");
}
return CMD_RET_SUCCESS;
}
static struct cmd_tbl pmbus_subcmd[] = {
U_BOOT_CMD_MKENT(dev, 2, 1, do_dev, "", ""),
U_BOOT_CMD_MKENT(list, 1, 1, do_list, "", ""),
U_BOOT_CMD_MKENT(info, 1, 1, do_info, "", ""),
U_BOOT_CMD_MKENT(telemetry, 1, 1, do_telemetry, "", ""),
U_BOOT_CMD_MKENT(status, 1, 1, do_status, "", ""),
U_BOOT_CMD_MKENT(dump, 1, 1, do_dump, "", ""),
U_BOOT_CMD_MKENT(read, 3, 1, do_read, "", ""),
U_BOOT_CMD_MKENT(write, 4, 1, do_write, "", ""),
U_BOOT_CMD_MKENT(clear, 2, 1, do_clear, "", ""),
U_BOOT_CMD_MKENT(vout, 2, 1, do_vout, "", ""),
U_BOOT_CMD_MKENT(scan, 2, 1, do_scan, "", ""),
U_BOOT_CMD_MKENT(help, 1, 1, do_help, "", ""),
};
static int do_pmbus(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const struct pmbus_vendor_op *vop;
struct cmd_tbl *cmd;
if (argc < 2)
return CMD_RET_USAGE;
argc--;
argv++;
cmd = find_cmd_tbl(argv[0], pmbus_subcmd, ARRAY_SIZE(pmbus_subcmd));
if (cmd) {
if (argc > cmd->maxargs)
return CMD_RET_USAGE;
return cmd->cmd(cmdtp, flag, argc, argv);
}
/* Vendor namespace dispatch */
vop = pmbus_lookup_vendor(argv[0]);
if (vop)
return vop->handler(cmdtp, flag, argc, argv);
printf("pmbus: unknown subcommand '%s'\n", argv[0]);
return CMD_RET_USAGE;
}
U_BOOT_CMD(pmbus, CONFIG_SYS_MAXARGS, 1, do_pmbus,
"PMBus 1.x device interrogation and control",
"list - list UCLASS_REGULATOR devices (DM bound)\n"
"pmbus dev [<bus>:<addr>|<name>] - show / select active PMBus device\n"
" (<bus> decimal, <addr>/<reg>/<val> hex)\n"
"pmbus info - identification banner + driver_info\n"
"pmbus telemetry - decoded VIN, VOUT, IIN, IOUT, TEMP\n"
"pmbus status - decode every STATUS_* register\n"
"pmbus dump - hex dump of every standard register\n"
"pmbus read <reg> [b|w|s] - raw read (b=byte, w=word, s=string)\n"
"pmbus write <reg> <val> [b|w] - raw write\n"
"pmbus clear [faults] - issue CLEAR_FAULTS (03h)\n"
"pmbus vout [<uV>] - read / set VOUT_COMMAND (microvolts)\n"
"pmbus scan [<bus>] - PMBus aware probe of one or all I2C buses\n"
"pmbus help - list registered vendor extensions\n"
"\n"
"Vendor extensions (pmbus <vendor> ...) are registered by per chip\n"
"drivers at probe time. Run 'pmbus help' after a chip is probed to\n"
"see the available subcommands.\n"
);
+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
+1
View File
@@ -49,6 +49,7 @@ Implementation
logging
makefiles
menus
pmbus
printf
smbios
spl
+637
View File
@@ -0,0 +1,637 @@
.. SPDX-License-Identifier: GPL-2.0+
PMBus support in U-Boot
=======================
This document describes U-Boot's PMBus 1.x support: what it is for,
how it is structured, and how to add support for a new PMBus chipn
either from scratch from a chip datasheet or by porting an existing
Linux ``drivers/hwmon/pmbus/`` driver.
.. contents::
:local:
:depth: 2
Intent and scope
----------------
U-Boot's PMBus layer is not a hardware monitoring (hwmon) clone of
the Linux kernel's ``drivers/hwmon/pmbus/`` subsystem. Linux owns the
runtime side: continuous polling, sysfs publication, alert IRQ
handling, fan control loops. U-Boot owns the boot time side. Concretely
the U-Boot PMBus support exists to:
* Identify the PMBus regulator(s) a board carries at boot:
``MFR_ID``, ``MFR_MODEL``, ``MFR_REVISION`` reads, plus a quick
``STATUS_WORD`` sanity check.
* Print telemetry so an operator can confirm rail voltages, input
current, and temperature before handing off to the kernel. One shot
reads, on demand, via the ``pmbus`` and ``regulator`` U-Boot
commands (``pmbus dev <name>; pmbus telemetry``,
``regulator dev <name>; regulator value``).
* Decode chip alerts when a rail trips an over/under voltage,
over current, or thermal threshold; so a boot log shows why the
previous boot failed, before the kernel even comes up.
* Optionally trim a critical rail (typically the SoC core) before
the kernel takes over; "set the voltage prior to a kernel boot
to better protect the board". This is the existing
``board/nxp/common/vid.c`` AVS path and any future per board
speed binning trim.
Out of scope, by design:
* No periodic polling. No worker thread. No background updates.
* No sysfs / procfs / userspace surface. U-Boot has none.
* No fan speed control loop. The kernel runs that.
* No long tail of virtual sensor registers (``PMBUS_VIRT_*``).
* No sensor caching / update timestamps.
If you find yourself wanting any of those, the answer is "wait until
Linux comes up". Keep U-Boot's PMBus surface minimal.
Architecture overview
---------------------
The framework is split into four layers (layer 3 comes in two
flavours, 3a and 3b),
::
+----------------------------------------+
| Layer 1: include/pmbus.h |
| Standard PMBus 1.x command codes, |
| numeric format enum, sensor class |
| enum, struct pmbus_driver_info, |
| decoder + transport prototypes, |
| STATUS_WORD bit names. |
+----------------------------------------+
| Layer 2: lib/pmbus.c |
| Format decoders (LINEAR11/LINEAR16/|
| DIRECT) and encoder (LINEAR16), |
| two stage SMBus block read helper, |
| STATUS_*-bit print tables, generic |
| dispatcher pmbus_reg2data(). |
+----------------------------------------+
| Layer 3a: drivers/power/regulator/ |
| <chip>.c |
| UCLASS_REGULATOR per chip drivers |
| ; one struct pmbus_driver_info |
| plus regulator set_value/get_value |
| ops. Optional: per chip identify() |
| hook to refine format from the |
| chip's own VOUT_MODE. |
+----------------------------------------+
| Layer 3b: drivers/power/regulator/ |
| pmbus_generic.c |
| Catch all driver matching |
| compatible = "pmbus". |
| Auto detects format via VOUT_MODE |
| and PMBUS_QUERY where supported. |
| Use for compliant chips with no |
| per chip driver yet; ship |
| telemetry today, write a per chip |
| driver later only if quirks demand |
| it. |
+----------------------------------------+
| Layer 4: board/<vendor>/<board>/ |
| <chip>_diag.c |
| Diagnostic commands only: |
| <chip>_info / <chip>_raw |
| Reads via regulator_get_value() |
| and lib/pmbus.c helpers. LINEAR / |
| DIRECT math NOT here. |
+----------------------------------------+
Generic vs. board specific separation rule. Layer 1, 2, and 3
files are tree level and platform agnostic. Their comments may
reference only:
* the PMBus 1.x specification, and
* chip manufacturer datasheets.
Never a specific board, SoC, or product. Board-specific quirks
(a particular bus number, a particular slave address, a particular
PCB feedback divider, board local design notes) live exclusively in
``board/<vendor>/<board>/`` files.
CLI commands
------------
The framework publishes one top level command, ``pmbus``, plus a
vendor namespace dispatcher so per chip code can register chip
specific extensions without touching the framework.
Active device model
~~~~~~~~~~~~~~~~~~~
``pmbus`` mirrors the ``regulator`` command: select an active device
once, then operate on it across subcommands. The active device is
selected by I2C bus (decimal sequence number) and 7 bit address (hex,
``0x`` optional, à la ``i2c`` convention)::
=> pmbus dev 0:10
pmbus: active i2c0:0x10 MFR_ID="MPS" MFR_MODEL="MPQ8785" vendor=mps
The framework probes ``MFR_ID`` (in both natural and reverse byte
orders) at selection time, looks the result up in the chip match
registry populated by per chip code via ``pmbus_register_chip()``,
and caches the matched ``pmbus_driver_info``. All subsequent
subcommands consume that cached metadata.
Standard subcommands
~~~~~~~~~~~~~~~~~~~~
::
pmbus list list UCLASS_REGULATOR devices (DM bound)
pmbus dev [<bus>:<addr>] show / select active PMBus device
pmbus info identification banner + driver_info
pmbus telemetry decoded VIN, VOUT, IIN, IOUT, TEMP
pmbus status decode every STATUS_* register
pmbus dump hex dump of every standard register
pmbus read <reg> [b|w|s] raw read (b=byte, w=word, s=string)
pmbus write <reg> <val> [b|w] raw write
pmbus clear [faults] issue CLEAR_FAULTS (03h)
pmbus vout [<uV>] read or set VOUT_COMMAND (microvolts)
pmbus scan [<bus>] PMBus aware probe of one or all I2C buses
The ``<reg>`` argument accepts either a hexadecimal address
(``88``, ``0x`` optional) or a symbolic name (``READ_VIN``,
``VOUT_MODE``, ``MFR_ID``); symbolic names win when both parsed.
``<val>`` is hexadecimal too. Only ``pmbus vout``'s microvolt
argument and bus numbers are decimal.
Format selectors after the register select the SMBus transaction width:
``b`` for byte, ``w`` for 16 bit little endian word,
``s`` for the SMBus block read used by string registers.
Decoded telemetry honours the active device's ``pmbus_driver_info``;
when no chip match has been registered, VOUT falls back to LINEAR16
driven by ``VOUT_MODE`` and the other sensors fall back to LINEAR11.
Vendor namespace
~~~~~~~~~~~~~~~~
Per chip drivers and board files publish chip specific subcommands
in the ``pmbus <vendor> ...`` namespace by calling
``pmbus_register_vendor_handler()`` at init time. The framework
dispatches ``pmbus mps last``, ``pmbus mps clear last``, and
``pmbus mps clear force`` to the MPS handler when the active
device matches the ``mps`` vendor. Additional vendor handlers for
``lltc``, ``renesas``, etc. land alongside the per chip drivers
that need them.
Relationship to ``vdd_override`` / ``vdd_read``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The NXP Layerscape ``vdd_override <mV>`` and ``vdd_read`` commands
remain available in their original form for compatibility with
existing AVS production scripts. The new ``pmbus vout`` and
``pmbus vout <uV>`` subcommands cover the read and single shot
write paths against the same chips, but do not implement
``vdd_override``'s full sequence (board drop compensation, fuse
target derivation, multi step convergence loop, atomic
``PAGE_PLUS_WRITE`` block transaction, ``WRITE_PROTECT`` dance).
For interactive bring up ``pmbus vout`` is sufficient; for
production AVS, ``vdd_override`` stays canonical.
Lifecycle: from board boot to Linux handoff
-------------------------------------------
The PMBus framework spans the entire U-Boot lifecycle. This section
walks the boot timeline phase by phase, showing when each piece
comes online and how the regulator uclass and the ``pmbus`` CLI
converge on the same chip.
Timeline overview
~~~~~~~~~~~~~~~~~
::
Phase 0 chip power on chip ramps to NVM default VOUT
Phase 1 boot ROM / SPL / TF-A PMBus typically untouched
Phase 2 U-Boot relocation, DM init regulators bound, not probed
Phase 3 first regulator probe chip driver runs, framework lights up
Phase 4 board hooks / boot scripts snapshot, AVS trim, gating
Phase 5 Linux handoff DT passed, chip state preserved
Phase 6 Linux runtime kernel pmbus driver takes over
Phase 0: chip power on
~~~~~~~~~~~~~~~~~~~~~~
When the regulator chip receives its input voltage, it ramps its
output to the VOUT default programmed into its NVM at factory
provisioning. PMBus is silent: no software runs anywhere on the
SoC yet.
Phase 1: pre U-Boot stages
~~~~~~~~~~~~~~~~~~~~~~~~~~
Boot ROMs, secondary boot loaders (SPL, ARM TF-A BL2 / BL31)
typically do not touch PMBus. They focus on PLLs, DDR PHY init,
and bringing up enough hardware to load the next stage. Some
platforms have a pre U-Boot AVS path in board specific TF-A
code that writes ``VOUT_COMMAND`` from a fuse derived target;
that path is independent of the U-Boot framework described here.
Phase 2: U-Boot relocation and DM init
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After relocation, U-Boot binds device tree nodes to drivers but
does not probe them. UCLASS_REGULATOR devices for PMBus chips
are bound (driver and DT match resolved) but the ``.probe``
callback has not run yet.
Framework state at this point:
* chip match registry: empty
* vendor handler registry: empty
* active device: none
* regulator uclass: devices bound, none probed
Phase 3: lazy regulator probe
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The first caller into the regulator uclass for a given chip
triggers the chip driver's ``.probe``. Typical first callers:
* a board ``EVENT_SPY`` at ``EVT_LAST_STAGE_INIT`` (boot snapshot)
* a U-Boot script: ``regulator dev <name>; regulator value``
* the ``pmbus dev <name>`` CLI command (resolves to the regulator)
* another DT consumer with a ``regulator-supplies`` reference
The probe chain looks like this::
<chip>_probe(dev)
pmbus_regulator_probe_common(dev, &<chip>_info, page)
dev_read_addr(dev) -> reg = <addr>
i2c_get_chip(dev->parent, addr) -> I2C chip handle
priv->i2c_dev = handle
priv->info = &<chip>_info
priv->page = page
(page > 0) write PMBUS_PAGE
<chip>_identify_vout(priv->i2c_dev) [optional]
read VOUT_MODE; refine info->format[PSC_VOLTAGE_OUT]
pmbus_regulator_apply_voltage_scale(dev, fb_div) [optional]
write PMBUS_VOUT_SCALE_LOOP if DT property set
pmbus_register_chip(&<chip>_match) [idempotent]
pmbus_register_vendor_handler(&<chip>_op) [idempotent]
Once probed, three independent surfaces are functional against
the same chip:
* the regulator uclass API (``regulator_get_value``,
``regulator_set_value``, ``regulator_get_enable``,
``regulator_set_enable``)
* the ``pmbus`` CLI (chip is reachable by name through
``pmbus_resolve_by_name()``, by raw ``<bus>:<addr>`` through
``pmbus_set_active()``)
* the chip's vendor extension subcommands (``pmbus <vendor> ...``)
Phase 4: board hooks and boot scripts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Boards hook the boot flow at well known points to drive board
specific PMBus behaviour. The framework prescribes none of these;
they are conventions:
* boot time rail snapshot. An ``EVENT_SPY`` at
``EVT_LAST_STAGE_INIT`` reads telemetry through the regulator
uclass and prints a one shot summary to the console. Useful
for operator visibility on serial during bring up.
* pre kernel rail trim (AVS). A board hook in
``board_late_init`` or a custom event spy reads a fuse derived
target voltage and calls ``regulator_set_value_force()`` to
trim the SoC core rail before kernel handoff.
* Linux handoff gate. A bootcmd reads the rail voltage
through the regulator command and refuses to boot Linux if
the rail is outside the expected range.
Phase 5: Linux handoff
~~~~~~~~~~~~~~~~~~~~~~
When U-Boot transfers control to Linux, it passes the device
tree (potentially patched). The DT compatible strings for PMBus
regulators must match those in the upstream kernel binding so
the kernel's ``drivers/hwmon/pmbus/<chip>.c`` picks them up.
Property names are shared with the kernel binding
(``regulator-name``, ``regulator-min-microvolt``,
``mps,vout-fb-divider-ratio-permille``, etc.); see "DT alignment
with Linux" below.
The chip itself is left in the state U-Boot wrote it to. If
U-Boot trimmed VOUT, the chip stays at the trimmed voltage
through handoff. ``CLEAR_FAULTS`` state is preserved unless an
operator explicitly issued one.
Phase 6: Linux runtime
~~~~~~~~~~~~~~~~~~~~~~
Linux's ``drivers/hwmon/pmbus/pmbus_core.c`` probes the chip,
exposes telemetry under ``/sys/class/hwmon``, and takes over
runtime voltage management through its regulator subsystem.
The hwmon framework polls periodically; U-Boot does not.
Operation paths through the regulator uclass
--------------------------------------------
After the first probe completes, calls into the regulator uclass
for a PMBus chip flow through the shared helper.
Read VOUT::
regulator_get_value(dev)
-> dm_regulator_ops->get_value
-> pmbus_regulator_get_value(dev)
pmbus_regulator_select_page(priv)
pmbus_read_byte(priv->i2c_dev, VOUT_MODE, &mode)
pmbus_read_word(priv->i2c_dev, READ_VOUT, &raw)
pmbus_reg2data(priv->info, PSC_VOLTAGE_OUT, raw, mode)
-> reg2data_linear16 (mode = 0)
-> reg2data_direct (chip configured for DIRECT)
return engineering value (microvolts)
Write VOUT::
regulator_set_value(dev, uV)
-> dm_regulator_ops->set_value
-> pmbus_regulator_set_value(dev, uV)
pmbus_regulator_select_page(priv)
pmbus_read_byte(VOUT_MODE)
check (mode == LINEAR) [LINEAR16 only today]
raw = pmbus_data2reg_linear16(uV, mode)
dm_i2c_write(VOUT_COMMAND, raw)
Read / write enable bit::
regulator_get_enable(dev)
-> pmbus_regulator_get_enable(dev)
pmbus_read_byte(OPERATION) & PB_OPERATION_ON
regulator_set_enable(dev, on)
-> pmbus_regulator_set_enable(dev, on)
read OPERATION, set or clear PB_OPERATION_ON, write back
Bus traffic per call:
* ``get_value`` : 1 byte read (VOUT_MODE) + 1 word read (READ_VOUT)
+ 1 byte write (PAGE) when ``page > 0``
* ``set_value`` : 1 byte read (VOUT_MODE) + 1 word write (VOUT_COMMAND)
+ 1 byte write (PAGE) when ``page > 0``
* ``get_enable`` : 1 byte read (OPERATION)
* ``set_enable`` : 1 byte read (OPERATION) + 1 byte write (OPERATION)
Common board hook patterns
~~~~~~~~~~~~~~~~~~~~~~~~~~
Boot time rail snapshot::
static int my_board_pmbus_snapshot(void)
{
struct udevice *reg;
if (regulator_get_by_platname("MY_RAIL", &reg))
return 0;
printf("MY_RAIL: VOUT = %d uV, enabled = %d\n",
regulator_get_value(reg),
regulator_get_enable(reg));
return 0;
}
EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, my_board_pmbus_snapshot);
The first call to ``regulator_get_value()`` triggers the chip
driver's ``.probe``, which seeds the chip match and vendor
extension registries. Subsequent ``pmbus`` CLI commands work
without further setup.
Pre kernel rail trim (AVS)::
int board_late_init(void)
{
struct udevice *reg;
int target_uV = compute_avs_target();
if (regulator_get_by_platname("VDD_CORE", &reg))
return 0;
return regulator_set_value_force(reg, target_uV);
}
Use ``regulator_set_value_force()`` when the target may sit
outside the DT declared ``regulator-min-microvolt`` /
``regulator-max-microvolt`` range; force bypasses the bounds
check.
Adding a new PMBus chip from scratch
------------------------------------
Use this path when the chip has no Linux driver yet, or when you want
to validate the U-Boot port against the datasheet alone.
1. Confirm PMBus 1.x compliance level. Locate in the chip
datasheet:
which PMBus standard command codes the chip implements
(``READ_VIN``, ``READ_VOUT``, ``STATUS_WORD``, ``MFR_ID`` ...),
which numeric format(s) it uses for VOUT (LINEAR16 with the
exponent in ``VOUT_MODE``, DIRECT with chip specific m/b/R, or
VID with one of the documented VRM tables),
which numeric format it uses for VIN, IIN, IOUT, TEMPERATURE
(most commonly LINEAR11; some MPS / MPS derivative chips use
DIRECT instead),
how many output rails it exposes (single page parts vs.
multi rail PMBus pages).
2. Declare a ``struct pmbus_driver_info``. Wire each sensor
class to one ``enum pmbus_data_format``, plus the m/b/R triple if
the format is DIRECT::
static struct pmbus_driver_info chipname_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = pmbus_fmt_direct,
.format[PSC_VOLTAGE_OUT] = pmbus_fmt_linear,
.format[PSC_CURRENT_OUT] = pmbus_fmt_direct,
.format[PSC_TEMPERATURE] = pmbus_fmt_direct,
.m[PSC_VOLTAGE_IN] = 4, .R[PSC_VOLTAGE_IN] = 1,
.m[PSC_CURRENT_OUT] = 16, .R[PSC_CURRENT_OUT] = 0,
.m[PSC_TEMPERATURE] = 1, .R[PSC_TEMPERATURE] = 0,
};
3. Bind to a DT compatible. Use the lowercase ``vendor,chip``
tuple Linux uses (see "DT alignment with Linux" below). Add the
driver under ``drivers/power/regulator/`` matching the existing
skeleton (``fan53555.c``, ``pca9450.c``).
4. Rely on the DT binding from the Linux kernel which is imported into
U-Boot under ``dts/upstream/Bindings/`` (for PMBus chips,
``dts/upstream/Bindings/hwmon/pmbus/``).
5. Smoke test. With the chip wired up in DT::
=> regulator dev <name>
=> regulator value
=> regulator info
Numbers should match the bench measurement to within the chip's
advertised LSB.
Porting an existing Linux PMBus driver to U-Boot
------------------------------------------------
When the chip already has a ``linux/drivers/hwmon/pmbus/<chip>.c``,
that driver is the authoritative reference for format, coefficients,
and quirks. Take what carries; leave what does not.
What carries verbatim
~~~~~~~~~~~~~~~~~~~~~
* Numeric formats (``format[PSC_*]``).
* DIRECT coefficients (``m[]``, ``b[]``, ``R[]``).
* Per page count and per page functionality bits (``pages``,
``func[]``).
* VOUT_MODE driven per chip identify hook (e.g. MPQ8785's
switch between LINEAR16 and VID coerced DIRECT m=64 R=1).
* Vendor register addresses for chip specific quirks (fault
history, scale-loop, page mapping).
What does not carry
~~~~~~~~~~~~~~~~~~~~~~~
* ``hwmon_device_register()`` and the attribute groups it consumes.
* ``struct pmbus_data`` / ``update_lock`` / ``last_updated``
U-Boot has no caching layer.
* ALERT# IRQ wiring; U-Boot is single threaded boot code.
* Fan control hooks (``read_fan_*``, ``set_pwm_*``).
* Virtual register handling (``PMBUS_VIRT_READ_VIN_*`` etc.); those
are entirely a hwmon publication aid.
* ``module_i2c_driver(...)`` and ``MODULE_*`` macros; U-Boot uses
``U_BOOT_DRIVER(...)``.
Worked example: porting MPQ8785
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Linux's ``drivers/hwmon/pmbus/mpq8785.c`` is 193 LOC; the U-Boot
equivalent is ~150 LOC.
The ``mpq8785_info`` struct transcribes verbatim::
.pages = 1,
.format[PSC_VOLTAGE_IN] = direct, .m[PSC_VOLTAGE_IN] = 4, .R[PSC_VOLTAGE_IN] = 1,
.format[PSC_CURRENT_OUT] = direct, .m[PSC_CURRENT_OUT] = 16, .R[PSC_CURRENT_OUT] = 0,
.format[PSC_TEMPERATURE] = direct, .m[PSC_TEMPERATURE] = 1, .R[PSC_TEMPERATURE] = 0,
The VOUT format is decided at probe time from VOUT_MODE bits[7:5] :
mode 0 means LINEAR16, mode 1 or 2 means DIRECT m=64 R=1 (the chip's
"VID" mode is coerced to DIRECT by the driver). Translate Linux's
``mpq8785_identify()`` 1:1.
The per chip quirks that carry over:
* MPS NVM string byte order: chip stores ``S P M`` for the human
string ``MPS``. ``pmbus_read_string()`` accepts a ``reverse_bytes``
flag for this case.
* ``mps,vout-fb-divider-ratio-permille`` DT property maps to
``VOUT_SCALE_LOOP`` write at probe time.
The quirks that do not carry over:
* The ``PMBUS_VIRT_*`` virtual sensor wiring. Drop entirely.
* The ``hwmon_chip_info`` attribute group registration.
* The ``MODULE_AUTHOR`` / ``MODULE_LICENSE`` declarations.
Using the generic ``compatible = "pmbus"`` driver
-------------------------------------------------
When a board carries a PMBus chip without a per chip U-Boot driver,
the catch all ``drivers/power/regulator/pmbus_generic.c`` (Layer 3b)
binds against ``compatible = "pmbus"``. It auto detects format via
``VOUT_MODE`` and ``PMBUS_QUERY`` (where the chip supports it) and
provides telemetry + voltage set/get against the standard PMBus 1.x
subset.
Decision tree:
1. Try the generic driver first. Add the regulator node to the
board DT with ``compatible = "pmbus"`` plus the standard
regulator properties. Boot, run ``regulator value``, compare
against bench measurement.
2. Switch to a per chip driver only when the generic one is
wrong: telemetry shows wrong values (chip uses DIRECT with
non default coefficients), an alert can't be decoded (chip has
vendor specific status bits), AVS is needed (the boot path has
to actively trim VOUT before kernel handoff), or the chip has an
ADDR-pin auto promotion / VID coercion / vendor register quirk.
DT alignment with Linux
-----------------------
The same ``.dts`` file should work under both U-Boot (BL33) and Linux
post handoff. To make that possible:
* Reuse the upstream Linux compatible for every PMBus chip. Look
in ``linux/Documentation/devicetree/bindings/hwmon/pmbus/`` and
``linux/Documentation/devicetree/bindings/regulator/``. The
``<vendor>,<chip>`` tuple from the kernel binding goes into U-Boot's
``of_match_table`` unchanged.
* Reuse Linux property names verbatim: ``regulator-name``,
``regulator-min-microvolt``, ``regulator-max-microvolt``,
``regulator-boot-on``, ``regulator-always-on``,
``mps,vout-fb-divider-ratio-permille``, etc.
* The DT binding is the kernel's, imported under
``dts/upstream/Bindings/`` (PMBus chips live in
``dts/upstream/Bindings/hwmon/pmbus/``).
Multi rail/multi page chips (e.g. ISL68137 with seven outputs)
declare each rail as a child regulator node with ``reg = <page>``;
each child binds as a UCLASS_REGULATOR with that PMBus PAGE setting
applied at every read/write.
Common pitfalls
---------------
These have all bitten contributors during nbxv3 bring up; record them
here so the next port doesn't repeat them.
* VOUT_MODE/DIRECT format confusion. Most generic PMBus call
sites assume LINEAR16. Several MPS chips report VOUT in DIRECT
format with chip specific m/b/R after a single VOUT_MODE read,
the same chip read at the same address produces different
numbers depending on the format the driver applies. Always read
``VOUT_MODE`` at probe time and switch the decoder accordingly.
Linux's per chip ``identify()`` callbacks document the exact
rules; copy them rather than guessing.
* SMBus block read protocol. Some I2C controllers strict check
block read transactions: the master must read the length byte
first, then reissue the read for the payload. Over reading a
fixed length and ignoring the length byte works on lenient
controllers but errors on strict ones. ``pmbus_read_string()``
does the two stage read; use it.
* I2C bus number stability. ``uclass_get_device_by_seq()``
uses the DT alias index (``i2c0`` -> ``UCLASS_I2C`` seq 0) when
aliases are declared, otherwise falls back to probe order which
varies with which controllers are enabled in the defconfig.
Always declare DT aliases for I2C buses you reference by index.
* ADDR-pin auto addressessing. Some chips (notably MPS parts) decode
their PMBus 7-bit address from an external resistor divider on
ADDR_VBOOT. The "default" address in the datasheet is the
factory fused slot; a board with a different divider or a die
with a different revision can land in another window. If the
driver hardcodes the default and the board side scan finds the
chip in another window, auto promote the working address rather
than failing the probe.
* MFR string byte order. Most PMBus chips return ``MFR_ID``
characters in human order. Some MPS personalities reverse them.
Pass ``reverse_bytes=true`` to ``pmbus_read_string()`` for those;
spec compliant chips pass false.
References
----------
* Linux PMBus core: ``linux/drivers/hwmon/pmbus/pmbus_core.c``,
decoder reference; ignore the hwmon publication and caching layers.
* Linux PMBus header: ``linux/drivers/hwmon/pmbus/pmbus.h``; API
surface reference; many constants and the ``struct
pmbus_driver_info`` shape are mirrored verbatim into U-Boot's
``include/pmbus.h``.
* Linux DT bindings:
``linux/Documentation/devicetree/bindings/hwmon/pmbus/``.
+2 -1
View File
@@ -409,7 +409,8 @@ This example shows the abridged sandbox output::
regulator 1 [ ] sandbox_buck | | |-- buck2
regulator 2 [ ] sandbox_ldo | | |-- ldo1
regulator 3 [ ] sandbox_ldo | | |-- ldo2
regulator 4 [ ] sandbox_buck | | `-- no_match_by_nodename
regulator 4 [ ] sandbox_buck | | |-- no_match_by_nodename
regulator 5 [ ] sandbox_ldo | | `-- ldo3
pmic 1 [ ] mc34708_pmic | `-- pmic@41
bootcount 0 [ + ] bootcount-rtc |-- bootcount@0
bootcount 1 [ ] bootcount-i2c-eeprom |-- bootcount
+4 -2
View File
@@ -645,9 +645,11 @@ static int dwmci_set_ios(struct mmc *mmc)
int ret;
if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180)
ret = regulator_set_value(mmc->vqmmc_supply, 1800000);
ret = regulator_set_value_clamp(mmc->vqmmc_supply,
1700000, 1800000, 1950000);
else
ret = regulator_set_value(mmc->vqmmc_supply, 3300000);
ret = regulator_set_value_clamp(mmc->vqmmc_supply,
2700000, 3300000, 3600000);
if (ret && ret != -ENOSYS)
return ret;
}
+1 -1
View File
@@ -551,7 +551,7 @@ void sdhci_set_uhs_timing(struct sdhci_host *host)
void sdhci_set_voltage(struct sdhci_host *host)
{
if (IS_ENABLED(CONFIG_MMC_IO_VOLTAGE)) {
if (CONFIG_IS_ENABLED(MMC_IO_VOLTAGE)) {
struct mmc *mmc = (struct mmc *)host->mmc;
u32 ctrl;
+43
View File
@@ -557,3 +557,46 @@ 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.
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.
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.
+4
View File
@@ -49,3 +49,7 @@ 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
obj-$(CONFIG_DM_REGULATOR_MPQ8785) += mpq8785.o
obj-$(CONFIG_SANDBOX_PMBUS) += sandbox_pmbus.o
+494
View File
@@ -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),
};
+90
View File
@@ -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),
};
+315
View File
@@ -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);
}
+90
View File
@@ -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_ */
@@ -111,6 +111,33 @@ int regulator_get_suspend_value(struct udevice *dev)
return ops->get_suspend_value(dev);
}
int regulator_set_value_clamp(struct udevice *dev,
int min_uV, int target_uV, int max_uV)
{
const struct dm_regulator_ops *ops = dev_get_driver_ops(dev);
struct dm_regulator_uclass_plat *uc_pdata;
int uV;
if (!ops || !ops->set_value)
return -ENOSYS;
uc_pdata = dev_get_uclass_plat(dev);
if (uc_pdata->min_uV != -ENODATA && max_uV < uc_pdata->min_uV)
return -EINVAL;
if (uc_pdata->max_uV != -ENODATA && min_uV > uc_pdata->max_uV)
return -EINVAL;
if (min_uV > max_uV)
return -EINVAL;
if (uc_pdata->min_uV != -ENODATA)
min_uV = max(min_uV, uc_pdata->min_uV);
if (uc_pdata->max_uV != -ENODATA)
max_uV = min(max_uV, uc_pdata->max_uV);
uV = clamp(target_uV, min_uV, max_uV);
return regulator_set_value(dev, uV);
}
/*
* To be called with at most caution as there is no check
* before setting the actual voltage value.
+6 -5
View File
@@ -56,10 +56,11 @@ static struct dm_regulator_mode sandbox_buck_modes[] = {
MODE(BUCK_OM_PWM, OM2REG(BUCK_OM_PWM), "PWM"),
};
/* LDO: 1,2 - voltage range */
/* LDO: 1,2,3 - voltage range */
static struct output_range ldo_voltage_range[] = {
RANGE(OUT_LDO1_UV_MIN, OUT_LDO1_UV_MAX, OUT_LDO1_UV_STEP),
RANGE(OUT_LDO2_UV_MIN, OUT_LDO2_UV_MAX, OUT_LDO2_UV_STEP),
RANGE(OUT_LDO3_UV_MIN, OUT_LDO3_UV_MAX, OUT_LDO3_UV_STEP),
};
/* LDO: 1 - current range */
@@ -288,8 +289,8 @@ static int ldo_set_voltage(struct udevice *dev, int uV)
static int ldo_get_current(struct udevice *dev)
{
/* LDO2 - unsupported */
if (dev->driver_data == 2)
/* LDO: 2,3 - unsupported */
if (dev->driver_data >= 2)
return -ENOSYS;
return out_get_value(dev, SANDBOX_LDO_COUNT, OUT_REG_UA,
@@ -298,8 +299,8 @@ static int ldo_get_current(struct udevice *dev)
static int ldo_set_current(struct udevice *dev, int uA)
{
/* LDO2 - unsupported */
if (dev->driver_data == 2)
/* LDO: 2,3 - unsupported */
if (dev->driver_data >= 2)
return -ENOSYS;
return out_set_value(dev, SANDBOX_LDO_COUNT, OUT_REG_UA,
+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,
};
+5 -1
View File
@@ -362,10 +362,14 @@ static int _lpuart32_serial_putc(struct lpuart_serial_plat *plat,
static int _lpuart32_serial_tstc(struct lpuart_serial_plat *plat)
{
struct lpuart_fsl_reg32 *base = plat->reg;
u32 water;
u32 stat, water;
lpuart_read32(plat->flags, &base->water, &water);
lpuart_read32(plat->flags, &base->stat, &stat);
if (stat & STAT_OR)
lpuart_write32(plat->flags, &base->stat, STAT_OR);
if ((water >> 24) == 0)
return 0;
+8
View File
@@ -63,4 +63,12 @@ config DM_THERMAL_JC42
Enable support for the JEDEC JC-42.4 temperature sensor found
on the SPD bus of DDR3 and DDR4 DIMMs (TSE2004av and compatible).
config PMBUS_THERMAL
bool "Generic PMBus temperature"
depends on DM_REGULATOR_PMBUS_HELPER
help
Expose the die-temperature reading of any PMBus voltage
regulator bound by a pmbus_helper based chip driver
as a UCLASS_THERMAL device.
endif # if DM_THERMAL
+1
View File
@@ -12,3 +12,4 @@ obj-$(CONFIG_SANDBOX) += thermal_sandbox.o
obj-$(CONFIG_TI_DRA7_THERMAL) += ti-bandgap.o
obj-$(CONFIG_TI_LM74_THERMAL) += ti-lm74.o
obj-$(CONFIG_DM_THERMAL_JC42) += jc42.o
obj-$(CONFIG_PMBUS_THERMAL) += pmbus_thermal.o
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2026 Free Mobile - Vincent Jardin
*
* Generic UCLASS_THERMAL companion for PMBus voltage regulators.
*
* Works with any chip bound by a pmbus_helper based regulator driver
* (drivers/power/regulator/<chip>.c calling
* pmbus_regulator_probe_common()). It auto-spawns one
* of these thermal devices per regulator.
*
* The reading is the chip's READ_TEMPERATURE_1 (PMBus 0x8D), decoded
* through the parent's pmbus_driver_info.
*/
#include <dm.h>
#include <pmbus.h>
#include <thermal.h>
static int pmbus_thermal_get_temp(struct udevice *dev, int *temp)
{
return pmbus_regulator_read_temp(dev_get_parent(dev), temp);
}
static const struct dm_thermal_ops pmbus_thermal_ops = {
.get_temp = pmbus_thermal_get_temp,
};
U_BOOT_DRIVER(pmbus_thermal) = {
.name = "pmbus_thermal",
.id = UCLASS_THERMAL,
.ops = &pmbus_thermal_ops,
};
+667
View File
@@ -0,0 +1,667 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2026 Free Mobile, Vincent Jardin
*
* PMBus 1.x command codes, numeric format decoders, and driver_info
* scaffolding for U-Boot.
*
* Intents
*
* U-Boot's PMBus support is not a hwmon clone. It shall be used to:
* 1. identify PMBus regulators a board carries at boot,
* 2. print telemetry so an operator can confirm rail voltages and
* fault status before handing off to the kernel,
* 3. decode chip alerts when a rail trips an over/under voltage,
* over current, or thermal threshold,
* 4. optionally trim a critical rail before kernel boot, to better
* protect the board.
*
* No periodic polling, no /sys, no userspace surface, no fan control
* loops. Linux owns those. See doc/develop/pmbus.rst for the full
* porting guide and policy notes.
*
* Linux relationship
*
* Constants (command codes, status bit names, sensor class enum,
* format enum) are mirrored verbatim from
* linux/drivers/hwmon/pmbus/pmbus.h
* with attribution; this is standardised data and copying it avoids
* accidental drift. Decoders (LINEAR11/16, DIRECT, VID, IEEE754) are
* reimplemented from the PMBus 1.x spec rather than copied. The
* surrounding kernel context (struct pmbus_data, hwmon caching,
* sysfs publication) does not apply to U-Boot.
*
* Tree level files (this header, lib/pmbus.c, future per chip drivers
* under drivers/power/regulator/) must stay platform agnostic. They
* may reference only the PMBus 1.x specification and chip datasheets,
* never a specific board, SoC, or product. Board specific quirks
* live under board/<vendor>/<board>/.
*/
#ifndef _PMBUS_H_
#define _PMBUS_H_
#include <linux/types.h>
struct udevice;
struct pmbus_driver_info;
struct pmbus_status_override;
/*
* PMBus 1.3 standard command codes (Part II).
*
* Subset relevant to U-Boot's needs:
* - configuration (PAGE, OPERATION, VOUT_*),
* - telemetry (READ_VIN, READ_IOUT, READ_TEMPERATURE_1, ...),
* - status (STATUS_WORD, STATUS_VOUT, ...),
* - identification (MFR_ID, MFR_MODEL, MFR_REVISION).
* Chip specific extensions (for example MPS PROTECTION_LAST 0xFB) shall be in
* the per chip driver file.
*/
#define PMBUS_PAGE 0x00
#define PMBUS_OPERATION 0x01
#define PMBUS_ON_OFF_CONFIG 0x02
#define PMBUS_CLEAR_FAULTS 0x03
#define PMBUS_PHASE 0x04
#define PMBUS_WRITE_PROTECT 0x10
#define PMBUS_CAPABILITY 0x19
#define PMBUS_QUERY 0x1a
#define PMBUS_SMBALERT_MASK 0x1b
#define PMBUS_VOUT_MODE 0x20
#define PMBUS_VOUT_COMMAND 0x21
#define PMBUS_VOUT_TRIM 0x22
#define PMBUS_VOUT_CAL_OFFSET 0x23
#define PMBUS_VOUT_MAX 0x24
#define PMBUS_VOUT_MARGIN_HIGH 0x25
#define PMBUS_VOUT_MARGIN_LOW 0x26
#define PMBUS_VOUT_TRANSITION_RATE 0x27
#define PMBUS_VOUT_DROOP 0x28
#define PMBUS_VOUT_SCALE_LOOP 0x29
#define PMBUS_VOUT_SCALE_MONITOR 0x2a
#define PMBUS_COEFFICIENTS 0x30
#define PMBUS_POUT_MAX 0x31
#define PMBUS_STATUS_BYTE 0x78
#define PMBUS_STATUS_WORD 0x79
#define PMBUS_STATUS_VOUT 0x7a
#define PMBUS_STATUS_IOUT 0x7b
#define PMBUS_STATUS_INPUT 0x7c
#define PMBUS_STATUS_TEMPERATURE 0x7d
#define PMBUS_STATUS_CML 0x7e
#define PMBUS_STATUS_OTHER 0x7f
#define PMBUS_STATUS_MFR_SPECIFIC 0x80
#define PMBUS_READ_VIN 0x88
#define PMBUS_READ_IIN 0x89
#define PMBUS_READ_VCAP 0x8a
#define PMBUS_READ_VOUT 0x8b
#define PMBUS_READ_IOUT 0x8c
#define PMBUS_READ_TEMPERATURE_1 0x8d
#define PMBUS_READ_TEMPERATURE_2 0x8e
#define PMBUS_READ_TEMPERATURE_3 0x8f
#define PMBUS_READ_DUTY_CYCLE 0x94
#define PMBUS_READ_FREQUENCY 0x95
#define PMBUS_READ_POUT 0x96
#define PMBUS_READ_PIN 0x97
#define PMBUS_REVISION 0x98
#define PMBUS_MFR_ID 0x99
#define PMBUS_MFR_MODEL 0x9a
#define PMBUS_MFR_REVISION 0x9b
#define PMBUS_MFR_LOCATION 0x9c
#define PMBUS_MFR_DATE 0x9d
#define PMBUS_MFR_SERIAL 0x9e
#define PMBUS_IC_DEVICE_ID 0xad
#define PMBUS_IC_DEVICE_REV 0xae
/* VOUT_MODE upper bits: numeric format selector (Part II sec 8.3). */
#define PB_VOUT_MODE_MODE_MASK 0xe0
#define PB_VOUT_MODE_PARAM_MASK 0x1f
#define PB_VOUT_MODE_LINEAR 0x00
#define PB_VOUT_MODE_VID 0x20
#define PB_VOUT_MODE_DIRECT 0x40
#define PB_VOUT_MODE_IEEE754 0x60
/* STATUS_WORD lower byte (= STATUS_BYTE), Part II sec 10.1.1. */
#define PB_STATUS_NONE_ABOVE BIT(0)
#define PB_STATUS_CML BIT(1)
#define PB_STATUS_TEMPERATURE BIT(2)
#define PB_STATUS_VIN_UV BIT(3)
#define PB_STATUS_IOUT_OC BIT(4)
#define PB_STATUS_VOUT_OV BIT(5)
#define PB_STATUS_OFF BIT(6)
#define PB_STATUS_BUSY BIT(7)
/* STATUS_WORD upper byte. */
#define PB_STATUS_UNKNOWN BIT(8)
#define PB_STATUS_OTHER BIT(9)
#define PB_STATUS_FANS BIT(10)
#define PB_STATUS_POWER_GOOD_N BIT(11)
#define PB_STATUS_WORD_MFR BIT(12)
#define PB_STATUS_INPUT BIT(13)
#define PB_STATUS_IOUT_POUT BIT(14)
#define PB_STATUS_VOUT BIT(15)
/* STATUS_VOUT (PMBus 1.3.1 Part II sec 17.3, Table 17). */
#define PB_VOLTAGE_VOUT_MAX_MIN_WARN BIT(3)
#define PB_VOLTAGE_UV_FAULT BIT(4)
#define PB_VOLTAGE_UV_WARNING BIT(5)
#define PB_VOLTAGE_OV_WARNING BIT(6)
#define PB_VOLTAGE_OV_FAULT BIT(7)
/* STATUS_IOUT (Part II sec 10.6). */
#define PB_POUT_OP_WARNING BIT(0)
#define PB_POUT_OP_FAULT BIT(1)
#define PB_POWER_LIMITING BIT(2)
#define PB_CURRENT_SHARE_FAULT BIT(3)
#define PB_IOUT_UC_FAULT BIT(4)
#define PB_IOUT_OC_WARNING BIT(5)
#define PB_IOUT_OC_LV_FAULT BIT(6)
#define PB_IOUT_OC_FAULT BIT(7)
/* STATUS_INPUT (Part II sec 10.7). */
#define PB_PIN_OP_WARNING BIT(0)
#define PB_IIN_OC_WARNING BIT(1)
#define PB_IIN_OC_FAULT BIT(2)
/* STATUS_TEMPERATURE (Part II sec 10.8). */
#define PB_TEMP_UT_FAULT BIT(4)
#define PB_TEMP_UT_WARNING BIT(5)
#define PB_TEMP_OT_WARNING BIT(6)
#define PB_TEMP_OT_FAULT BIT(7)
/* STATUS_CML (Part II sec 10.9). */
#define PB_CML_FAULT_OTHER_MEM_LOGIC BIT(0)
#define PB_CML_FAULT_OTHER_COMM BIT(1)
#define PB_CML_FAULT_PROCESSOR BIT(3)
#define PB_CML_FAULT_MEMORY BIT(4)
#define PB_CML_FAULT_PACKET_ERROR BIT(5)
#define PB_CML_FAULT_INVALID_DATA BIT(6)
#define PB_CML_FAULT_INVALID_COMMAND BIT(7)
/*
* OPERATION (01h) command bits per PMBus 1.3 Part II sec 9.1. Bit[7]
* is the master rail enable; the lower bits select margin high/low
* and turn off behaviour (subset surfaced for the regulator helper).
*/
#define PB_OPERATION_ON BIT(7)
/*
* LINEAR11 numeric format (PMBus 1.3 Part II sec 7): 16 bit register
* with a signed 11 bit mantissa in bits[10:0] and a signed 5 bit
* exponent in bits[15:11]. Engineering value = mantissa * 2^exponent.
*/
#define PB_LINEAR11_MANT_MASK 0x07ff
#define PB_LINEAR11_MANT_BITS 11
#define PB_LINEAR11_EXP_SHIFT 11
#define PB_LINEAR11_EXP_MASK 0x1f
#define PB_LINEAR11_EXP_BITS 5
/*
* Cache buffer sizes for the active device singleton + MFR_* block
* reads. PMBus block reads return up to 32 bytes per the SMBus spec;
* 16 covers every MFR string seen in practice on regulator class
* chips and keeps the singleton compact.
*/
#define PMBUS_MFR_STRING_MAX 16
#define PMBUS_VENDOR_NAME_MAX 8
#define PMBUS_REGULATOR_NAME_MAX 24
/* PMBus revision identifiers reported by PMBUS_REVISION (98h). */
#define PMBUS_REV_10 0x00 /* PMBus 1.0 */
#define PMBUS_REV_11 0x11 /* PMBus 1.1 */
#define PMBUS_REV_12 0x22 /* PMBus 1.2 */
#define PMBUS_REV_13 0x33 /* PMBus 1.3 */
/*
* Numeric formats and sensor classes.
*
* Mirrors linux/drivers/hwmon/pmbus/pmbus.h enum pmbus_data_format
* and enum pmbus_sensor_classes. A chip's pmbus_driver_info wires
* each sensor class to one format and (for DIRECT) to its m/b/R
* coefficients.
*/
enum pmbus_data_format {
pmbus_fmt_linear = 0,
pmbus_fmt_ieee754,
pmbus_fmt_direct,
pmbus_fmt_vid,
};
enum pmbus_sensor_classes {
PSC_VOLTAGE_IN = 0,
PSC_VOLTAGE_OUT,
PSC_CURRENT_IN,
PSC_CURRENT_OUT,
PSC_POWER,
PSC_TEMPERATURE,
PSC_NUM_CLASSES
};
/*
* Per chip identification record. Each per chip driver declares one
* of these and points the framework at it. Subset of the kernel
* struct pmbus_driver_info: U-Boot has no per page caches, no fan
* accessors, no virtual registers, no async sysfs publication.
*
* pages number of PAGE distinct rails the chip exposes
* (1 for single rail parts).
* format[] numeric format per sensor class.
* m/b/R[] DIRECT format coefficients per sensor class. See
* pmbus_reg2data_direct() below for the formula.
* Unused for non DIRECT classes.
* read_byte_data, read_word_data
* optional per chip register translators. Return the
* standard register value on success, ENODATA to fall
* through to the generic transport, any other negative
* errno on bus error.
* identify optional probe time hook to discover format and
* page count from the chip itself (for example, the
* MPQ8785 VOUT_MODE switch between LINEAR and DIRECT).
*/
struct pmbus_driver_info {
int pages;
enum pmbus_data_format format[PSC_NUM_CLASSES];
int m[PSC_NUM_CLASSES];
int b[PSC_NUM_CLASSES];
int R[PSC_NUM_CLASSES];
int (*read_byte_data)(struct udevice *dev, int page, int reg);
int (*read_word_data)(struct udevice *dev, int page, int reg);
int (*identify)(struct udevice *dev, struct pmbus_driver_info *info);
/*
* Optional sparse table of chip specific STATUS_* bit name
* substitutions. Terminated by an entry with .name = NULL
* (matching the convention used by struct udevice_id and
* other U-Boot driver tables). NULL pointer means the chip
* uses only PMBus 1.x standard names. See struct
* pmbus_status_override and pmbus_print_status_bits().
*/
const struct pmbus_status_override *status_overrides;
/*
* Bitmask (BIT(enum pmbus_sensor_classes)) of the sensor classes
* this chip actually implements. When non-zero, pmbus_print_telemetry
* prints exactly these classes -- mirroring the kernel's per chip
* sensor set -- and skips the rest. This is how an MPS buck that
* ACKs READ_POUT / READ_IIN with an uncalibrated value still hides
* POWER / CURRENT_IN (the kernel's mpq8646 driver exposes neither).
* Zero means "not declared": the telemetry printer falls back to a
* live pmbus_word_command_supported() probe per class, which is what
* the generic driver (compatible = "pmbus") relies on.
*/
u8 classes_present;
};
/*
* Decoder helpers (raw register, returns engineering value in micro
* units).
*
* All return signed micro units (uV, uA, udegC), 64 bit to avoid
* overflow on large mantissa times exponent products. The caller
* divides by 1000 for milli units, or by 1_000_000 for the integer
* engineering value.
*/
/*
* LINEAR11. Bits[15:11] = signed 5 bit exponent Y, bits[10:0] =
* signed 11 bit mantissa N. Engineering value = N * 2^Y.
*
* Used by most PMBus chips for VIN, IIN, IOUT, TEMP. Some MPS
* parts deviate (they report DIRECT format with chip specific m/b/R
* coefficients); check the chip datasheet against PMBUS_VOUT_MODE
* and the Linux per chip driver if porting.
*/
s64 pmbus_reg2data_linear11(u16 raw);
/*
* LINEAR16. 16 bit unsigned mantissa multiplied by 2^Y, where Y is
* the signed 5 bit exponent supplied via VOUT_MODE bits[4:0]. The
* caller must read VOUT_MODE (cmd 0x20) and pass it in vout_mode.
* Only the mode_mask bits[7:5] = 0 selector is the LINEAR16 path.
*
* Used for READ_VOUT (8Bh) on chips whose VOUT_MODE selects Linear.
* Returns 0 if VOUT_MODE indicates a non Linear mode; the caller
* is then expected to dispatch to pmbus_reg2data_direct() with the
* appropriate per chip m/b/R, or to pmbus_reg2data_vid() / _ieee754()
* if the chip uses those formats.
*/
s64 pmbus_reg2data_linear16(u16 raw, u8 vout_mode);
/*
* DIRECT. PMBus 1.3 Part II sec 8.4. The chip stores a signed 16 bit
* value X; the engineering value Y is one over m, multiplied by the
* quantity (X scaled by ten to the power minus R, then offset by
* minus b), with chip specific (m, b, R) coefficients.
*
* In symbolic form: Y = (1/m) * (X * 10**(-R) - b). The negative
* exponent and trailing subtraction are math operators in the
* formula, not punctuation in the prose.
*
* Returns micro units. Implementation order matches the Linux
* reference (multiply before subtract, scale R then divide by m) to
* minimise quantisation drift. m == 0 returns 0.
*/
s64 pmbus_reg2data_direct(s16 raw, int m, int b, int R);
/*
* Encoder: engineering value (micro units) to LINEAR16 raw register
* value. Used by pre kernel rail trim code (see board/nxp/common/vid.c)
* to write VOUT_COMMAND from a target voltage. The exponent is
* recovered from VOUT_MODE.
*
* Returns 0 if VOUT_MODE indicates a non Linear format; the caller
* must then dispatch to pmbus_data2reg_direct() (DIRECT format) or
* the VID / IEEE754 encoders (when those land) per the chip's actual
* VOUT_MODE selector.
*/
u16 pmbus_data2reg_linear16(s64 micro, u8 vout_mode);
/*
* Encoder: engineering value (micro units) to DIRECT raw register
* value. Inverse of pmbus_reg2data_direct():
*
* X = (m * Y + b) * 10^R
*
* with chip specific (m, b, R) coefficients (typically taken from
* the chip's pmbus_driver_info[PSC_VOLTAGE_OUT]). m == 0 returns 0.
*
* The result is saturated to the s16 range mandated by the PMBus
* 1.3 Part II sec 8.4 DIRECT encoding; out of range targets return
* 0x7fff or 0x8000 rather than wrapping.
*/
u16 pmbus_data2reg_direct(s64 micro, int m, int b, int R);
/*
* Dispatcher: pick the right reg2data_* helper based on the chip's
* pmbus_driver_info[class]. vout_mode is consulted only for
* VOLTAGE_OUT in linear format. For DIRECT, m/b/R are taken from
* info. For VID and IEEE754 the dispatcher returns 0 (formats
* not yet wired up; add when a consumer lands).
*/
s64 pmbus_reg2data(const struct pmbus_driver_info *info,
enum pmbus_sensor_classes class,
u16 raw, u8 vout_mode);
/*
* Transport helpers.
*
* Thin wrappers over the U-Boot DM I2C primitives that handle PMBus
* framing details (little endian word layout, two stage block read,
* CLEAR_FAULTS pseudo command without payload).
*/
/* Read a byte register. */
int pmbus_read_byte(struct udevice *dev, u8 cmd, u8 *val);
/* Read a 16 bit register, little endian on the wire. */
int pmbus_read_word(struct udevice *dev, u8 cmd, u16 *val);
/* Write a byte register. */
int pmbus_write_byte(struct udevice *dev, u8 cmd, u8 val);
/* Write a 16 bit register, little endian on the wire. */
int pmbus_write_word(struct udevice *dev, u8 cmd, u16 val);
/*
* Block read of a vendor string register (MFR_ID, MFR_MODEL,
* MFR_REVISION). The first wire byte is the payload length; the
* helper does the second read for the payload itself, so even strict
* I2C controllers (which forbid over read on block transactions)
* work. Output is null terminated and printable only (non printable
* bytes are substituted with '.').
*
* reverse_bytes: some MPS NVM personalities store ASCII strings
* LSB first (chip returns "SPM" for the human string "MPS"); pass
* true to reverse on copy. Spec compliant chips pass false.
*
* Returns string length on success or a negative errno on bus error
* or invalid length byte. outsz must be at least 2.
*/
int pmbus_read_string(struct udevice *dev, u8 cmd, char *out, int outsz,
bool reverse_bytes);
/* Issue a CLEAR_FAULTS (03h) write. Clears RAM sticky STATUS_*. */
int pmbus_clear_faults(struct udevice *dev);
/*
* Capability probe: is a word sized command implemented by the chip?
*
* Primary signal is the bus NAK -- compliant parts do not ACK a
* command they do not implement, so the word read fails. Secondary
* signal is a clean -> dirty transition of STATUS_CML[INVALID_COMMAND]
* across the read (a chip that ACKs but does not implement the
* register raises it). Non destructive: never issues CLEAR_FAULTS, so
* sticky fault history survives for a subsequent pmbus status; a
* pre existing CML fault disables the secondary signal so it cannot
* produce a false "unsupported".
*
* Returns true if the command appears supported, false otherwise.
*/
bool pmbus_word_command_supported(struct udevice *dev, u8 reg);
/*
* High level snapshot printers shared by the pmbus CLI and board
* boot time diagnostics. Both operate on the current pmbus_active()
* device (select it first via pmbus_set_active()); chip is the I2C
* handle from pmbus_active_get_i2c() / the CLI's require_active().
*
* pmbus_print_telemetry: decodes VIN / VOUT / IIN / IOUT / POUT / TEMP
* through the active device's pmbus_driver_info (LINEAR / DIRECT / VID
* per VOUT_MODE and per class format), skipping commands the chip does
* not implement (pmbus_word_command_supported). Falls back to
* LINEAR16 / LINEAR11 when no driver_info is cached. Caller prints the
* header line.
*
* pmbus_print_status_word: reads + decodes STATUS_WORD with the active
* device's chip specific status_overrides, if any.
*/
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.
*
* Sparse mask to name table. pmbus_print_bits() emits only the bits
* that are SET in v, joined by |; if no bit is set, prints
* clean. Bits not in the table are silently ignored (RESERVED bits
* or chip specific bits handled by a separate per chip table).
*/
struct pmbus_bit {
u16 mask;
const char *name;
};
void pmbus_print_bits(u16 v, const struct pmbus_bit *tab);
/*
* Per chip override entry. When a chip reuses a PMBus standard
* STATUS bit for a documented chip specific signal (for example MPS
* uses STATUS_WORD bit[12] = MFR_SPECIFIC as NVM_SUMMARY, bit[8] =
* UNKNOWN as WATCH_DOG, bit[0] = NONE_ABOVE as DRMOS_FAULT), the
* per chip driver supplies a sparse table of (reg, mask, name)
* triples and pmbus_print_status_bits() substitutes the chip name
* for the standard one when the bit is set.
*
* reg is one of PMBUS_STATUS_WORD / VOUT / IOUT / INPUT /
* TEMPERATURE / CML, so the same table can carry overrides for
* every status register on the chip in one place. Tables that omit
* a (reg, mask) leave the standard name in place.
*
* Override entries whose mask is NOT in the standard table are
* still printed (the chip can extend coverage beyond PMBus 1.x for
* vendor specific bits in standard registers).
*
* Tables are NULL terminated: the last entry has .name = NULL.
* Following the same convention U-Boot uses for struct udevice_id
* and other driver tables avoids the explicit-count foot-gun.
*/
struct pmbus_status_override {
u8 reg;
u16 mask;
const char *name;
};
/*
* Print the bit names of a STATUS_* register value. For each bit
* set in v, prefer a chip override matching (reg, mask) over the
* standard std table entry; if neither matches, the bit is
* silently skipped (RESERVED). If no bit is set at all, prints
* clean. Pass ovr = NULL to disable the override path.
*/
void pmbus_print_status_bits(u8 reg, u16 v,
const struct pmbus_bit *std,
const struct pmbus_status_override *ovr);
/*
* Built in PMBus 1.3 standard bit tables (use these from per chip
* drivers and board diagnostics; vendor extensions go in chip local
* tables that the per chip driver passes alongside these).
*/
/*
* All tables below are NULL terminated (last entry has .name = NULL),
* so callers walk with for (t = tab; t && t->name; t++) and the
* helpers above need no count argument.
*/
extern const struct pmbus_bit pmbus_status_word_bits[];
extern const struct pmbus_bit pmbus_status_vout_bits[];
extern const struct pmbus_bit pmbus_status_iout_bits[];
extern const struct pmbus_bit pmbus_status_input_bits[];
extern const struct pmbus_bit pmbus_status_temp_bits[];
extern const struct pmbus_bit pmbus_status_cml_bits[];
/*
* Active device tracking for the pmbus U-Boot CLI.
*
* The framework keeps one active PMBus device. It is selected by
* pmbus dev <bus>:<addr> (raw I2C tuple) and remembered across
* subcommands so subsequent invocations of pmbus telemetry,
* pmbus status, pmbus dump, etc. operate on the same chip
* without re-typing the address.
*
* pmbus_set_active() probes the chip's MFR_ID at the given address,
* looks the result up in the chip-match registry (populated by
* per chip drivers via pmbus_register_chip()), and caches the
* resulting struct pmbus_driver_info. Subcommands consult
* pmbus_active() to find the cached metadata.
*/
struct pmbus_active_dev {
bool valid;
int bus_seq;
u8 addr;
char vendor[PMBUS_VENDOR_NAME_MAX];
char name[PMBUS_REGULATOR_NAME_MAX]; /* DT regulator-name when bound; "" otherwise */
char mfr_id[PMBUS_MFR_STRING_MAX];
char mfr_model[PMBUS_MFR_STRING_MAX];
char mfr_revision[PMBUS_MFR_STRING_MAX];
bool mfr_reverse; /* chip stores MFR strings LSB first */
const struct pmbus_driver_info *info;
};
const struct pmbus_active_dev *pmbus_active(void);
int pmbus_active_get_i2c(struct udevice **i2c_dev);
int pmbus_set_active(int bus_seq, u8 addr);
void pmbus_clear_active(void);
/*
* Per chip driver / board file registers a chip match so the
* framework can associate an MFR_ID prefix (read at probe time)
* with a vendor namespace ("mps", "lltc", "renesas", ...) and a
* pmbus_driver_info pointer. The first matching entry wins.
*
* mfr_id_reverse flags MPS style chips that store the MFR_ID
* string LSB first (chip returns "SPM" for the human string
* "MPS"); the framework reads the string in both orderings and
* matches against the prefix in the natural reading.
*/
struct pmbus_chip_match {
const char *mfr_id;
bool mfr_id_reverse;
const char *vendor;
const struct pmbus_driver_info *info;
};
int pmbus_register_chip(const struct pmbus_chip_match *match);
/*
* Resolve a regulator-name (DT regulator-name property) to its
* (bus, addr) tuple by walking UCLASS_REGULATOR. Used by the
* pmbus dev <name> CLI alias so a chip bound through DT can be
* selected by its human readable rail name (e.g. "+0V8_VDD")
* instead of the i2c bus / address pair. Returns 0 on success
* (out parameters populated and the regulator probed), or a
* negative errno if no match is found or the bus / address cannot
* be derived. Available only when CONFIG_DM_REGULATOR is set.
*/
int pmbus_resolve_by_name(const char *name, int *bus_seq, u8 *addr);
/*
* Vendor extension dispatcher.
*
* When the user types pmbus <vendor> <args...>, the framework
* looks up the registered handler for <vendor> and calls it with
* the argv tail (argv[0] = "<vendor>"). The handler operates on
* pmbus_active(), or returns CMD_RET_USAGE if the active device is
* not from this vendor.
*
* Per chip drivers register their vendor handler at init time. The
* MPS extension publishes pmbus mps last, pmbus mps clear last,
* and pmbus mps clear force.
*/
typedef int (*pmbus_vendor_handler_t)(struct cmd_tbl *cmdtp, int flag,
int argc, char *const argv[]);
struct pmbus_vendor_op {
const char *vendor;
pmbus_vendor_handler_t handler;
const char *help;
};
int pmbus_register_vendor_handler(const struct pmbus_vendor_op *op);
const struct pmbus_vendor_op *pmbus_lookup_vendor(const char *vendor);
unsigned int pmbus_vendor_count(void);
const struct pmbus_vendor_op *pmbus_vendor_at(unsigned int i);
#endif /* _PMBUS_H_ */
+21 -3
View File
@@ -315,6 +315,18 @@ int regulator_set_suspend_value(struct udevice *dev, int uV);
*/
int regulator_get_suspend_value(struct udevice *dev);
/**
* regulator_set_value_clamp: set clamped microvoltage value of a given regulator
*
* @dev - pointer to the regulator device
* @min_uV - the minimum output value to set [micro Volts]
* @target_uV - the target output value to set [micro Volts]
* @max_uV - the maximum output value to set [micro Volts]
* Return: - 0 on success or -errno val if fails
*/
int regulator_set_value_clamp(struct udevice *dev,
int min_uV, int target_uV, int max_uV);
/**
* regulator_set_value_force: set the microvoltage value of a given regulator
* without any min-,max condition check
@@ -359,9 +371,6 @@ int regulator_get_enable(struct udevice *dev);
*/
int regulator_set_enable(struct udevice *dev, bool enable);
#define regulator_enable(dev) regulator_set_enable(dev, true)
#define regulator_disable(dev) regulator_set_enable(dev, false)
/**
* regulator_set_enable_if_allowed: set regulator enable state if allowed by
* regulator
@@ -550,6 +559,12 @@ static inline int regulator_get_suspend_value(struct udevice *dev)
return -ENOSYS;
}
static inline int regulator_set_value_clamp(struct udevice *dev,
int min_uV, int target_uV, int max_uV)
{
return -ENOSYS;
}
static inline int regulator_set_value_force(struct udevice *dev, int uV)
{
return -ENOSYS;
@@ -633,4 +648,7 @@ static inline int device_get_supply_regulator(struct udevice *dev, const char *s
}
#endif
#define regulator_enable(dev) regulator_set_enable_if_allowed(dev, true)
#define regulator_disable(dev) regulator_set_enable_if_allowed(dev, false)
#endif /* _INCLUDE_REGULATOR_H_ */
+13 -2
View File
@@ -13,10 +13,10 @@
#define SANDBOX_OF_BUCK_PREFIX "buck"
#define SANDBOX_BUCK_COUNT 3
#define SANDBOX_LDO_COUNT 2
#define SANDBOX_LDO_COUNT 3
/*
* Sandbox PMIC registers:
* We have only 12 significant registers, but we alloc 16 for padding.
* We have only 15 significant registers, but we alloc 16 for padding.
*/
enum {
SANDBOX_PMIC_REG_BUCK1_UV = 0,
@@ -36,6 +36,10 @@ enum {
SANDBOX_PMIC_REG_LDO2_UA,
SANDBOX_PMIC_REG_LDO2_OM,
SANDBOX_PMIC_REG_LDO3_UV,
SANDBOX_PMIC_REG_LDO3_UA,
SANDBOX_PMIC_REG_LDO3_OM,
SANDBOX_PMIC_REG_COUNT = 16,
};
@@ -94,6 +98,11 @@ enum {
#define OUT_LDO2_UV_MAX 3950000
#define OUT_LDO2_UV_STEP 50000
/* LDO3 Voltage: min: 0.75V, step: 50mV, max 3.95V */
#define OUT_LDO3_UV_MIN 750000
#define OUT_LDO3_UV_MAX 3950000
#define OUT_LDO3_UV_STEP 50000
/* register <-> value conversion */
#define REG2VAL(min, step, reg) ((min) + ((step) * (reg)))
#define VAL2REG(min, step, val) (((val) - (min)) / (step))
@@ -116,6 +125,8 @@ enum {
#define SANDBOX_LDO1_PLATNAME "VDD_EMMC_1.8V"
#define SANDBOX_LDO2_DEVNAME "ldo2"
#define SANDBOX_LDO2_PLATNAME "VDD_LCD_3.3V"
#define SANDBOX_LDO3_DEVNAME "ldo3"
#define SANDBOX_LDO3_PLATNAME "SUPPLY_1.8_3.3V"
/*
* Expected regulators setup after call of:
+16
View File
@@ -283,6 +283,22 @@ config PANIC_HANG
development since you can try to debug the conditions that lead to
the situation.
config PMBUS
bool "PMBus 1.x decoder and transport helpers"
depends on DM_I2C
help
Enable include/pmbus.h and lib/pmbus.c: standard PMBus 1.x
command codes, LINEAR11, LINEAR16, and DIRECT numeric format
decoders, the two stage SMBus block read helper used for
MFR_ID, MFR_MODEL, and MFR_REVISION reads, and the table
driven STATUS_* bit print helper.
This is the substrate for any pre kernel PMBus consumer in
U-Boot (board/nxp/common/vid.c rail trim, board local telemetry
diagnostics, future per chip regulator drivers under
drivers/power/regulator/). It is not a hwmon clone. See
doc/develop/pmbus.rst for the policy notes.
config REGEX
bool "Enable regular expression support"
default y if NET_LEGACY
+1
View File
@@ -53,6 +53,7 @@ obj-y += rc4.o
obj-$(CONFIG_RBTREE) += rbtree.o
obj-$(CONFIG_BITREVERSE) += bitrev.o
obj-y += list_sort.o
obj-$(CONFIG_PMBUS) += pmbus.o
endif
obj-$(CONFIG_$(PHASE_)TPM) += tpm-common.o
+877
View File
@@ -0,0 +1,877 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2026 Free Mobile, Vincent Jardin
*
* PMBus 1.x decoders, transport helpers, and standard status bit
* tables for U-Boot. See include/pmbus.h for the API surface and
* doc/develop/pmbus.rst for the porting guide.
*
* Decoder math is implemented from the PMBus 1.3 specification:
* 1. Part I (transport): see
* doc/PMBus/PMBus_Specification_Rev_1_3_1_Part_I_20150313.{pdf,txt}
* 2. Part II (commands): see
* doc/PMBus/PMBus_Specification_Rev_1_3_1_Part_II_20150313.{pdf,txt}
*
* Reference Linux implementation: linux/drivers/hwmon/pmbus/pmbus_core.c
* (the kernel side `struct pmbus_data` caching and hwmon publication
* layers do not apply; only the arithmetic carries over).
*
* No code in this file may reference a specific board, SoC, or
* product. Per chip quirks (MPS DIRECT format LSBs, vendor registers,
* VID coercion, ADDR_VBOOT auto promotion, and the like) belong in
* per chip drivers under drivers/power/regulator/ or in board local
* files under board/<vendor>/<board>/.
*/
#include <ctype.h>
#include <dm.h>
#include <i2c.h>
#include <log.h>
#include <pmbus.h>
#include <limits.h>
#include <linux/bitops.h>
#include <power/regulator.h>
static int pmbus_sign_extend(unsigned int v, unsigned int width)
{
unsigned int mask = (1U << width) - 1U;
unsigned int sign = 1U << (width - 1U);
v &= mask;
if (v & sign)
v |= ~mask;
return (int)v;
}
s64 pmbus_reg2data_linear11(u16 raw)
{
int mantissa = pmbus_sign_extend(raw & PB_LINEAR11_MANT_MASK,
PB_LINEAR11_MANT_BITS);
int exponent = pmbus_sign_extend((raw >> PB_LINEAR11_EXP_SHIFT) &
PB_LINEAR11_EXP_MASK,
PB_LINEAR11_EXP_BITS);
s64 micro;
/* Engineering value = mantissa * 2^exponent, scaled to micro units. */
micro = (s64)mantissa * 1000000LL;
if (exponent >= 0)
micro <<= exponent;
else
micro >>= -exponent;
return micro;
}
s64 pmbus_reg2data_linear16(u16 raw, u8 vout_mode)
{
int exponent;
s64 micro;
/*
* VOUT_MODE bits[7:5] = mode; bits[4:0] = parameter. Linear mode
* (000) treats bits[4:0] as the signed 5-bit exponent. For other
* modes the caller must dispatch elsewhere.
*/
if ((vout_mode & PB_VOUT_MODE_MODE_MASK) != PB_VOUT_MODE_LINEAR)
return 0;
exponent = pmbus_sign_extend(vout_mode & PB_VOUT_MODE_PARAM_MASK, 5);
/* Mantissa is unsigned 16-bit; scale to micro units. */
micro = (s64)raw * 1000000LL;
if (exponent >= 0)
micro <<= exponent;
else
micro >>= -exponent;
return micro;
}
s64 pmbus_reg2data_direct(s16 raw, int m, int b, int R)
{
s64 acc;
if (m == 0)
return 0;
/*
* PMBus Part II sec 8.4: Y = (1/m) * (X * 10^-R - b)
*
* Pre scale acc to micro units so the final integer division by
* m absorbs the rounding loss into the least significant micro
* digit rather than into a coarser place.
*/
acc = (s64)raw * 1000000LL;
/* Apply 10^-R: positive R means divide; negative R means multiply. */
while (R > 0) {
acc /= 10;
R--;
}
while (R < 0) {
acc *= 10;
R++;
}
/* Subtract the offset b, also in micro units. */
acc -= (s64)b * 1000000LL;
/* Final: divide by m. */
acc /= m;
return acc;
}
u16 pmbus_data2reg_linear16(s64 micro, u8 vout_mode)
{
int exponent;
s64 raw;
if ((vout_mode & PB_VOUT_MODE_MODE_MASK) != PB_VOUT_MODE_LINEAR)
return 0;
exponent = pmbus_sign_extend(vout_mode & PB_VOUT_MODE_PARAM_MASK, 5);
/* raw = micro / (2^exponent * 10^6). */
raw = micro;
if (exponent >= 0)
raw >>= exponent;
else
raw <<= -exponent;
raw /= 1000000LL;
if (raw < 0)
raw = 0;
if (raw > U16_MAX)
raw = U16_MAX;
return (u16)raw;
}
u16 pmbus_data2reg_direct(s64 micro, int m, int b, int R)
{
s64 acc;
if (m == 0)
return 0;
/*
* Inverse of pmbus_reg2data_direct(): X = (m * Y + b) * 10^R.
* Work in micro units throughout: acc = m * Y_micro + b * 10^6,
* then scale by 10^R, finally divide by 10^6 to get the raw
* chip count. Order chosen to match the decoder's quantisation
* pattern so a round trip (data2reg then reg2data) returns the
* input within +/- one LSB.
*/
acc = (s64)m * micro + (s64)b * 1000000LL;
while (R > 0) {
acc *= 10;
R--;
}
while (R < 0) {
acc /= 10;
R++;
}
acc /= 1000000LL;
/* PMBus 1.3 Part II sec 8.4 mandates a signed 16 bit raw value. */
if (acc > S16_MAX)
acc = S16_MAX;
if (acc < S16_MIN)
acc = S16_MIN;
return (u16)(s16)acc;
}
s64 pmbus_reg2data(const struct pmbus_driver_info *info,
enum pmbus_sensor_classes class,
u16 raw, u8 vout_mode)
{
if (!info || class >= PSC_NUM_CLASSES)
return 0;
switch (info->format[class]) {
case pmbus_fmt_linear:
if (class == PSC_VOLTAGE_OUT)
return pmbus_reg2data_linear16(raw, vout_mode);
return pmbus_reg2data_linear11(raw);
case pmbus_fmt_direct:
return pmbus_reg2data_direct((s16)raw,
info->m[class],
info->b[class],
info->R[class]);
case pmbus_fmt_vid:
case pmbus_fmt_ieee754:
/*
* Not yet wired up. Add when a consumer lands. VID needs
* the per page vrm_version table from the kernel's
* pmbus_reg2data_vid(); IEEE754 needs the half precision
* decoder from pmbus_reg2data_ieee754().
*/
return 0;
}
return 0;
}
int pmbus_read_byte(struct udevice *dev, u8 cmd, u8 *val)
{
return dm_i2c_read(dev, cmd, val, 1);
}
int pmbus_read_word(struct udevice *dev, u8 cmd, u16 *val)
{
u8 raw[2];
int ret;
ret = dm_i2c_read(dev, cmd, raw, 2);
if (ret)
return ret;
*val = (u16)raw[0] | ((u16)raw[1] << 8);
return 0;
}
int pmbus_write_byte(struct udevice *dev, u8 cmd, u8 val)
{
return dm_i2c_write(dev, cmd, &val, 1);
}
int pmbus_write_word(struct udevice *dev, u8 cmd, u16 val)
{
u8 raw[2];
raw[0] = (u8)(val & 0xff); /* PMBus words are little-endian */
raw[1] = (u8)(val >> 8);
return dm_i2c_write(dev, cmd, raw, 2);
}
int pmbus_read_string(struct udevice *dev, u8 cmd, char *out, int outsz,
bool reverse_bytes)
{
u8 raw[PMBUS_MFR_STRING_MAX + 1]; /* length byte + payload */
int ret, len, i;
if (outsz < 2)
return -EINVAL;
/* Stage 1: read the length byte. */
ret = dm_i2c_read(dev, cmd, raw, 1);
if (ret)
return ret;
len = raw[0];
if (len <= 0 || len > (int)sizeof(raw) - 1)
return -EBADMSG;
if (len > outsz - 1)
len = outsz - 1;
/* Stage 2: reread length + payload (some controllers mandate this). */
ret = dm_i2c_read(dev, cmd, raw, len + 1);
if (ret)
return ret;
if (reverse_bytes) {
for (i = 0; i < len; i++) {
u8 b = raw[len - i];
out[i] = isprint(b) ? (char)b : '.';
}
} else {
for (i = 0; i < len; i++) {
u8 b = raw[i + 1];
out[i] = isprint(b) ? (char)b : '.';
}
}
out[len] = '\0';
return len;
}
int pmbus_clear_faults(struct udevice *dev)
{
return dm_i2c_write(dev, PMBUS_CLEAR_FAULTS, NULL, 0);
}
void pmbus_print_bits(u16 v, const struct pmbus_bit *tab)
{
const struct pmbus_bit *t;
int first = 1;
if (v == 0) {
printf("clean");
return;
}
for (t = tab; t && t->name; t++) {
if (v & t->mask) {
printf("%s%s", first ? "" : "|", t->name);
first = 0;
}
}
}
void pmbus_print_status_bits(u8 reg, u16 v,
const struct pmbus_bit *std,
const struct pmbus_status_override *ovr)
{
const struct pmbus_bit *s;
const struct pmbus_status_override *o;
int first = 1;
if (v == 0) {
printf("clean");
return;
}
/*
* Pass 1: walk the standard table in declared order so the
* printout retains the conventional bit-15-first ordering. For
* each set bit, prefer a chip override matching (reg, mask).
*/
for (s = std; s && s->name; s++) {
const char *name;
if (!(v & s->mask))
continue;
name = s->name;
for (o = ovr; o && o->name; o++) {
if (o->reg == reg && o->mask == s->mask) {
name = o->name;
break;
}
}
printf("%s%s", first ? "" : "|", name);
first = 0;
}
/*
* Pass 2: print overrides whose mask is not in the standard
* table at all (chip-specific bit at a position the spec
* leaves RESERVED). These would otherwise be swallowed.
*/
for (o = ovr; o && o->name; o++) {
bool in_std = false;
if (o->reg != reg)
continue;
if (!(v & o->mask))
continue;
for (s = std; s && s->name; s++) {
if (s->mask == o->mask) {
in_std = true;
break;
}
}
if (in_std)
continue;
printf("%s%s", first ? "" : "|", o->name);
first = 0;
}
}
/*
* Standard PMBus 1.3 status bit tables. Per-chip drivers may publish
* their own tables for vendor extended bits (e.g. NVM summary bits,
* DR MOS faults) but the standard layout below is the safe baseline.
*
* All tables are NULL terminated (`name = NULL` sentinel), matching
* the convention used elsewhere in U-Boot for driver tables.
*/
const struct pmbus_bit pmbus_status_word_bits[] = {
{ PB_STATUS_VOUT, "VOUT" },
{ PB_STATUS_IOUT_POUT, "IOUT_POUT" },
{ PB_STATUS_INPUT, "INPUT" },
{ PB_STATUS_WORD_MFR, "MFR" },
{ PB_STATUS_POWER_GOOD_N, "PG#" },
{ PB_STATUS_FANS, "FANS" },
{ PB_STATUS_OTHER, "OTHER" },
{ PB_STATUS_UNKNOWN, "UNKNOWN" },
{ PB_STATUS_BUSY, "BUSY" },
{ PB_STATUS_OFF, "OFF" },
{ PB_STATUS_VOUT_OV, "VOUT_OV" },
{ PB_STATUS_IOUT_OC, "IOUT_OC" },
{ PB_STATUS_VIN_UV, "VIN_UV" },
{ PB_STATUS_TEMPERATURE, "TEMP" },
{ PB_STATUS_CML, "CML" },
{ PB_STATUS_NONE_ABOVE, "NONE_ABOVE" },
{ /* sentinel */ }
};
const struct pmbus_bit pmbus_status_vout_bits[] = {
{ PB_VOLTAGE_OV_FAULT, "VOUT_OV_FAULT" },
{ PB_VOLTAGE_OV_WARNING, "VOUT_OV_WARN" },
{ PB_VOLTAGE_UV_WARNING, "VOUT_UV_WARN" },
{ PB_VOLTAGE_UV_FAULT, "VOUT_UV_FAULT" },
{ PB_VOLTAGE_VOUT_MAX_MIN_WARN, "VOUT_MAX_MIN_WARN" },
{ /* sentinel */ }
};
const struct pmbus_bit pmbus_status_iout_bits[] = {
{ PB_IOUT_OC_FAULT, "IOUT_OC_FAULT" },
{ PB_IOUT_OC_LV_FAULT, "IOUT_OC_LV_FAULT" },
{ PB_IOUT_OC_WARNING, "IOUT_OC_WARN" },
{ PB_IOUT_UC_FAULT, "IOUT_UC_FAULT" },
{ PB_CURRENT_SHARE_FAULT, "ISHARE_FAULT" },
{ PB_POWER_LIMITING, "POWER_LIMITING" },
{ PB_POUT_OP_FAULT, "POUT_OP_FAULT" },
{ PB_POUT_OP_WARNING, "POUT_OP_WARN" },
{ /* sentinel */ }
};
const struct pmbus_bit pmbus_status_input_bits[] = {
{ PB_IIN_OC_FAULT, "IIN_OC_FAULT" },
{ PB_IIN_OC_WARNING, "IIN_OC_WARN" },
{ PB_PIN_OP_WARNING, "PIN_OP_WARN" },
{ /* sentinel */ }
};
const struct pmbus_bit pmbus_status_temp_bits[] = {
{ PB_TEMP_OT_FAULT, "OT_FAULT" },
{ PB_TEMP_OT_WARNING, "OT_WARN" },
{ PB_TEMP_UT_WARNING, "UT_WARN" },
{ PB_TEMP_UT_FAULT, "UT_FAULT" },
{ /* sentinel */ }
};
const struct pmbus_bit pmbus_status_cml_bits[] = {
{ PB_CML_FAULT_INVALID_COMMAND, "INVALID_CMD" },
{ PB_CML_FAULT_INVALID_DATA, "INVALID_DATA" },
{ PB_CML_FAULT_PACKET_ERROR, "PEC" },
{ PB_CML_FAULT_MEMORY, "MEM" },
{ PB_CML_FAULT_PROCESSOR, "PROC" },
{ PB_CML_FAULT_OTHER_COMM, "OTHER_COMM" },
{ PB_CML_FAULT_OTHER_MEM_LOGIC, "OTHER_MEM_LOGIC" },
{ /* sentinel */ }
};
/*
* Active device tracking + chip / vendor registries (consumed by the
* `pmbus` U-Boot CLI command in cmd/pmbus.c).
*/
#define PMBUS_MAX_CHIP_MATCHES 8
#define PMBUS_MAX_VENDOR_HANDLERS 4
static struct pmbus_active_dev pmbus_active_state;
static const struct pmbus_chip_match *pmbus_chip_table[PMBUS_MAX_CHIP_MATCHES];
static unsigned int pmbus_chip_table_n;
static const struct pmbus_vendor_op *pmbus_vendor_table[PMBUS_MAX_VENDOR_HANDLERS];
static unsigned int pmbus_vendor_table_n;
const struct pmbus_active_dev *pmbus_active(void)
{
return pmbus_active_state.valid ? &pmbus_active_state : NULL;
}
void pmbus_clear_active(void)
{
memset(&pmbus_active_state, 0, sizeof(pmbus_active_state));
}
int pmbus_active_get_i2c(struct udevice **i2c_dev)
{
struct udevice *bus;
int ret;
if (!pmbus_active_state.valid)
return -ENODEV;
ret = uclass_get_device_by_seq(UCLASS_I2C, pmbus_active_state.bus_seq, &bus);
if (ret)
return ret;
return i2c_get_chip(bus, pmbus_active_state.addr, 1, i2c_dev);
}
/* engineering value in micro units -> "I.FFF<unit>" (3 fractional digits) */
static void pmbus_emit_micro(s64 micro, const char *unit)
{
s64 abs_milli = (micro < 0 ? -micro : micro) / 1000LL;
printf("%lld.%03lld%s", (long long)(micro / 1000000LL),
(long long)(abs_milli % 1000LL), unit);
}
struct pmbus_telem_entry {
u8 reg;
const char *label;
enum pmbus_sensor_classes class;
const char *unit;
};
/*
* Telemetry register set, in print order. POUT is included so PSU
* class parts report input/output power; chips that do not implement
* a given command are skipped via pmbus_word_command_supported().
*/
static const struct pmbus_telem_entry pmbus_telem_table[] = {
{ PMBUS_READ_VIN, "VIN ", PSC_VOLTAGE_IN, "V" },
{ PMBUS_READ_VOUT, "VOUT", PSC_VOLTAGE_OUT, "V" },
{ PMBUS_READ_IIN, "IIN ", PSC_CURRENT_IN, "A" },
{ PMBUS_READ_IOUT, "IOUT", PSC_CURRENT_OUT, "A" },
{ PMBUS_READ_POUT, "POUT", PSC_POWER, "W" },
{ PMBUS_READ_TEMPERATURE_1, "TEMP", PSC_TEMPERATURE, "C" },
};
bool pmbus_word_command_supported(struct udevice *dev, u8 reg)
{
u8 cml_before = 0, cml_after = 0;
bool have_cml;
u16 w;
have_cml = !pmbus_read_byte(dev, PMBUS_STATUS_CML, &cml_before);
if (pmbus_read_word(dev, reg, &w))
return false; /* NAK: unsupported command not ACKed */
if (have_cml && !(cml_before & PB_CML_FAULT_INVALID_COMMAND) &&
!pmbus_read_byte(dev, PMBUS_STATUS_CML, &cml_after) &&
(cml_after & PB_CML_FAULT_INVALID_COMMAND))
return false; /* ACKed but chip raised INVALID_COMMAND */
return true;
}
/* Telemetry of the currently selected page. */
static void pmbus_print_telemetry_page(struct udevice *chip,
const struct pmbus_active_dev *act)
{
u8 vout_mode = 0;
unsigned int i;
/*
* On a read failure vout_mode stays 0 (LINEAR, exponent 0). That is
* a silent mis-scale of every VOLTAGE_OUT reading, so make the
* fallback visible rather than printing a wrong voltage as if good.
*/
if (pmbus_read_byte(chip, PMBUS_VOUT_MODE, &vout_mode))
printf(" (VOUT_MODE read failed; VOUT decode assumes LINEAR exp 0)\n");
for (i = 0; i < ARRAY_SIZE(pmbus_telem_table); i++) {
const struct pmbus_telem_entry *e = &pmbus_telem_table[i];
u16 raw = 0;
/*
* Class gating. A chip driver that declares classes_present
* lists exactly the sensors it implements (kernel-style
* per-chip sensor set), so unlisted classes are skipped
* silently -- this is what hides the MPS buck's uncalibrated
* POUT / IIN. A generic / undeclared device instead gets a
* live capability probe per class.
*/
if (act->info && act->info->classes_present) {
if (!(act->info->classes_present & BIT(e->class)))
continue;
} else if (!pmbus_word_command_supported(chip, e->reg)) {
printf(" %s : (not supported)\n", e->label);
continue;
}
if (pmbus_read_word(chip, e->reg, &raw)) {
printf(" %s : (read failed)\n", e->label);
continue;
}
printf(" %s : raw=0x%04x ", e->label, raw);
if (act->info) {
u16 dec = raw;
/*
* Some DIRECT format parts (e.g. MPS) report
* temperature as 1 degC/LSB in the low byte only;
* mask there. LINEAR temperatures use all 16 bits
* and must NOT be masked.
*/
if (e->class == PSC_TEMPERATURE &&
act->info->format[PSC_TEMPERATURE] == pmbus_fmt_direct)
dec = raw & 0x00ff;
pmbus_emit_micro(pmbus_reg2data(act->info, e->class,
dec, vout_mode),
e->unit);
} else if (e->class == PSC_VOLTAGE_OUT) {
pmbus_emit_micro(pmbus_reg2data_linear16(raw, vout_mode),
e->unit);
} else {
pmbus_emit_micro(pmbus_reg2data_linear11(raw), e->unit);
printf(" (LINEAR11 fallback)");
}
printf("\n");
}
}
void pmbus_print_telemetry(struct udevice *chip)
{
const struct pmbus_active_dev *act = pmbus_active();
int npages, p;
u8 zero = 0;
if (!act)
return;
/*
* Multi-rail parts (PSU bricks) expose one rail per PMBUS_PAGE.
* Chip drivers set pmbus_driver_info.pages; the generic driver
* takes it from the DT `pmbus,num-pages` (default 1). We always
* write PMBUS_PAGE before reading a page -- including page 0 --
* because a device may power up selected on a different page, which
* is what made the 48V PSU read all-zeros before. Only valid pages
* (0..npages-1) are ever written, so we never induce the
* out-of-range-PAGE STATUS_CML fault and the device's sticky fault
* log is left untouched (no CLEAR_FAULTS, no scrubbing).
*/
npages = (act->info && act->info->pages > 0) ? act->info->pages : 1;
for (p = 0; p < npages; p++) {
u8 pg = (u8)p;
if (dm_i2c_write(chip, PMBUS_PAGE, &pg, 1)) {
printf(" [page %d] PAGE select failed\n", p);
continue;
}
if (npages > 1)
printf(" [page %d]\n", p);
pmbus_print_telemetry_page(chip, act);
}
if (npages > 1)
dm_i2c_write(chip, PMBUS_PAGE, &zero, 1); /* leave on page 0 */
}
void pmbus_print_status_word(struct udevice *chip)
{
const struct pmbus_active_dev *act = pmbus_active();
const struct pmbus_status_override *ovr =
(act && act->info) ? act->info->status_overrides : NULL;
u16 word = 0;
if (pmbus_read_word(chip, PMBUS_STATUS_WORD, &word)) {
printf(" STATUS_WORD (79h) = (read failed)\n");
return;
}
printf(" STATUS_WORD (79h) = 0x%04x [", word);
pmbus_print_status_bits(PMBUS_STATUS_WORD, word,
pmbus_status_word_bits, ovr);
printf("]\n");
}
static const struct pmbus_chip_match *pmbus_match_mfr(const char *id)
{
unsigned int i;
if (!id || !id[0])
return NULL;
for (i = 0; i < pmbus_chip_table_n; i++) {
const struct pmbus_chip_match *m = pmbus_chip_table[i];
size_t plen = strlen(m->mfr_id);
if (strlen(id) >= plen && !strncmp(id, m->mfr_id, plen))
return m;
}
return NULL;
}
/*
* Walk UCLASS_REGULATOR looking for a regulator whose I2C parent
* bus seq + DT reg address match the requested (bus_seq, addr).
* Returns the regulator-name (uclass plat .name) on hit, or NULL if
* no UCLASS_REGULATOR device matches (chip not bound through DT, or
* CONFIG_DM_REGULATOR disabled).
*/
static const char *pmbus_lookup_regname(int bus_seq, u8 addr)
{
struct uclass *uc;
struct udevice *r;
if (!IS_ENABLED(CONFIG_DM_REGULATOR))
return NULL;
if (uclass_get(UCLASS_REGULATOR, &uc))
return NULL;
uclass_foreach_dev(r, uc) {
struct dm_regulator_uclass_plat *up;
struct udevice *parent = dev_get_parent(r);
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;
up = dev_get_uclass_plat(r);
if (up && up->name)
return up->name;
return r->name;
}
return NULL;
}
int pmbus_set_active(int bus_seq, u8 addr)
{
const struct pmbus_chip_match *match = NULL;
struct udevice *bus, *chip;
char id_fwd[PMBUS_MFR_STRING_MAX] = "";
char id_rev[PMBUS_MFR_STRING_MAX] = "";
const char *rname;
int ret;
pmbus_clear_active();
ret = uclass_get_device_by_seq(UCLASS_I2C, bus_seq, &bus);
if (ret)
return ret;
ret = i2c_get_chip(bus, addr, 1, &chip);
if (ret)
return ret;
pmbus_active_state.bus_seq = bus_seq;
pmbus_active_state.addr = addr;
/*
* Probe MFR_ID in both byte orders. Spec compliant chips return
* "MPS" / "TI" / etc. in the natural reading (forward); MPS NVM
* personalities store the string LSB first and need the reverse
* read. Chip table entries declare which one is canonical for
* the chip family they describe.
*/
if (pmbus_read_string(chip, PMBUS_MFR_ID, id_fwd, sizeof(id_fwd), false) < 0)
id_fwd[0] = '\0';
if (pmbus_read_string(chip, PMBUS_MFR_ID, id_rev, sizeof(id_rev), true) < 0)
id_rev[0] = '\0';
match = pmbus_match_mfr(id_fwd);
if (match && !match->mfr_id_reverse) {
strlcpy(pmbus_active_state.mfr_id, id_fwd,
sizeof(pmbus_active_state.mfr_id));
} else {
match = pmbus_match_mfr(id_rev);
if (match && match->mfr_id_reverse) {
strlcpy(pmbus_active_state.mfr_id, id_rev,
sizeof(pmbus_active_state.mfr_id));
} else {
/* No registered match; cache the forward read as best effort. */
strlcpy(pmbus_active_state.mfr_id,
id_fwd[0] ? id_fwd : id_rev,
sizeof(pmbus_active_state.mfr_id));
}
}
if (match) {
pmbus_active_state.info = match->info;
if (match->vendor)
strlcpy(pmbus_active_state.vendor, match->vendor,
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
* forward order.
*/
{
bool reverse = match && match->mfr_id_reverse;
pmbus_active_state.mfr_reverse = reverse;
pmbus_read_string(chip, PMBUS_MFR_MODEL,
pmbus_active_state.mfr_model,
sizeof(pmbus_active_state.mfr_model), reverse);
pmbus_read_string(chip, PMBUS_MFR_REVISION,
pmbus_active_state.mfr_revision,
sizeof(pmbus_active_state.mfr_revision), reverse);
}
rname = pmbus_lookup_regname(bus_seq, addr);
if (rname)
strlcpy(pmbus_active_state.name, rname,
sizeof(pmbus_active_state.name));
pmbus_active_state.valid = true;
return 0;
}
int pmbus_register_chip(const struct pmbus_chip_match *match)
{
if (!match || !match->mfr_id)
return -EINVAL;
if (pmbus_chip_table_n >= PMBUS_MAX_CHIP_MATCHES)
return -ENOSPC;
pmbus_chip_table[pmbus_chip_table_n++] = match;
return 0;
}
int pmbus_register_vendor_handler(const struct pmbus_vendor_op *op)
{
if (!op || !op->vendor || !op->handler)
return -EINVAL;
if (pmbus_vendor_table_n >= PMBUS_MAX_VENDOR_HANDLERS)
return -ENOSPC;
pmbus_vendor_table[pmbus_vendor_table_n++] = op;
return 0;
}
const struct pmbus_vendor_op *pmbus_lookup_vendor(const char *vendor)
{
unsigned int i;
if (!vendor)
return NULL;
for (i = 0; i < pmbus_vendor_table_n; i++)
if (!strcmp(pmbus_vendor_table[i]->vendor, vendor))
return pmbus_vendor_table[i];
return NULL;
}
unsigned int pmbus_vendor_count(void)
{
return pmbus_vendor_table_n;
}
const struct pmbus_vendor_op *pmbus_vendor_at(unsigned int i)
{
return i < pmbus_vendor_table_n ? pmbus_vendor_table[i] : NULL;
}
int pmbus_resolve_by_name(const char *name, int *bus_seq, u8 *addr)
{
struct udevice *reg;
struct udevice *parent;
int ret;
int a;
if (!IS_ENABLED(CONFIG_DM_REGULATOR))
return -ENOSYS;
if (!name || !bus_seq || !addr)
return -EINVAL;
ret = regulator_get_by_platname(name, &reg);
if (ret)
return ret;
parent = dev_get_parent(reg);
if (!parent || device_get_uclass_id(parent) != UCLASS_I2C)
return -ENODEV;
a = dev_read_addr(reg);
if (a < 0 || a > 0x7f)
return -EINVAL;
*bus_seq = dev_seq(parent);
*addr = (u8)a;
return 0;
}
+1
View File
@@ -95,6 +95,7 @@ obj-$(CONFIG_PINCONF) += pinmux.o
endif
obj-$(CONFIG_POWER_DOMAIN) += power-domain.o
obj-$(CONFIG_ACPI_PMC) += pmc.o
obj-$(CONFIG_CMD_PMBUS) += pmbus.o
obj-$(CONFIG_DM_PMIC) += pmic.o
obj-$(CONFIG_DM_PWM) += pwm.o
obj-$(CONFIG_ARM_FFA_TRANSPORT) += ffa.o
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2026 Free Mobile - Vincent Jardin
*
* Unit tests for the PMBus 1.x framework, the generic
* PMBus regulator and the pmbus CLI command.
*/
#include <dm.h>
#include <i2c.h>
#include <pmbus.h>
#include <dm/test.h>
#include <test/test.h>
#include <test/ut.h>
/* The line pmbus dev prints when a chip is selected */
#define PMBUS_ACTIVE_LINE \
"pmbus: active i2c0:0x70 rail=\"sandbox-pmbus-vout\" " \
"MFR_ID=\"SANDBOX\" MODEL=\"PMBUS-EMUL\" vendor=(generic)"
/* The line pmbus list prints for the bound chip */
#define PMBUS_LIST_LINE \
" i2c0:0x70 rail=\"sandbox-pmbus-vout\" node=pmbus@70 " \
"driver=pmbus_generic_regulator"
/* Select the emulated chip and check the resulting banner line */
static int pmbus_select(struct unit_test_state *uts)
{
ut_assertok(run_command("pmbus dev 0:70", 0));
ut_assert_nextline(PMBUS_ACTIVE_LINE);
return 0;
}
/* The chip is reachable via UCLASS_REGULATOR (compatible = "pmbus") */
static int dm_test_pmbus_bind(struct unit_test_state *uts)
{
struct udevice *dev;
ut_assertok(uclass_get_device_by_name(UCLASS_REGULATOR, "pmbus@70",
&dev));
ut_asserteq_str("pmbus_generic_regulator", dev->driver->name);
ut_asserteq(UCLASS_I2C, device_get_uclass_id(dev_get_parent(dev)));
return 0;
}
DM_TEST(dm_test_pmbus_bind, UTF_SCAN_FDT);
/* pmbus dev by <bus>:<addr> and by regulator-name select the chip */
static int dm_test_pmbus_dev(struct unit_test_state *uts)
{
ut_assertok(run_command("pmbus dev 0:70", 0));
ut_assert_nextline(PMBUS_ACTIVE_LINE);
ut_assert_console_end();
/* Selecting by DT regulator-name resolves to the same chip */
ut_assertok(run_command("pmbus dev sandbox-pmbus-vout", 0));
ut_assert_nextline(PMBUS_ACTIVE_LINE);
ut_assert_console_end();
/* pmbus dev with no argument reprints the active chip */
ut_assertok(run_command("pmbus dev", 0));
ut_assert_nextline(PMBUS_ACTIVE_LINE);
ut_assert_console_end();
return 0;
}
DM_TEST(dm_test_pmbus_dev, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus list enumerates the bound chip among the UCLASS_REGULATOR devices */
static int dm_test_pmbus_list(struct unit_test_state *uts)
{
ut_assertok(run_command("pmbus list", 0));
ut_assert_skip_to_line(PMBUS_LIST_LINE);
return 0;
}
DM_TEST(dm_test_pmbus_list, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus info decodes identification + the detected driver_info */
static int dm_test_pmbus_info(struct unit_test_state *uts)
{
ut_assertok(pmbus_select(uts));
ut_assertok(run_command("pmbus info", 0));
ut_assert_nextline("pmbus device i2c0:0x70");
ut_assert_nextline(" regulator-name: \"sandbox-pmbus-vout\"");
ut_assert_nextline(" MFR_ID : \"SANDBOX\"");
ut_assert_nextline(" MFR_MODEL : \"PMBUS-EMUL\"");
ut_assert_nextline(" MFR_REVISION : \"1.0\" raw=0x312e30");
ut_assert_nextline(" PMBUS_REVISION: 0x33 (PMBus 1.3)");
ut_assert_nextline(" vendor : (none)");
ut_assert_nextline(" driver_info : pages=1");
ut_assert_nextline(" [VOLTAGE_IN ] format=LINEAR");
ut_assert_nextline(" [VOLTAGE_OUT ] format=LINEAR");
ut_assert_nextline(" [CURRENT_IN ] format=LINEAR");
ut_assert_nextline(" [CURRENT_OUT ] format=LINEAR");
ut_assert_nextline(" [POWER ] format=LINEAR");
ut_assert_nextline(" [TEMPERATURE ] format=LINEAR");
ut_assert_console_end();
return 0;
}
DM_TEST(dm_test_pmbus_info, UTF_SCAN_FDT | UTF_CONSOLE);
/*
* pmbus telemetry decodes the implemented sensors and prints
* "(not supported)" for the commands the emulator NAKs (READ_IIN,
* READ_POUT). LINEAR11 is used for VIN/IOUT/TEMP, LINEAR16 (with the
* VOUT_MODE 2^-8 exponent) for VOUT.
*/
static int dm_test_pmbus_telemetry(struct unit_test_state *uts)
{
ut_assertok(pmbus_select(uts));
ut_assertok(run_command("pmbus telemetry", 0));
ut_assert_nextline("pmbus telemetry @ i2c0:0x70");
ut_assert_nextline(" VIN : raw=0x0abc 1400.000V");
ut_assert_nextline(" VOUT : raw=0x0200 2.000V");
ut_assert_nextline(" IIN : (not supported)");
ut_assert_nextline(" IOUT : raw=0x0123 291.000A");
ut_assert_nextline(" POUT : (not supported)");
ut_assert_nextline(" TEMP : raw=0x0019 25.000C");
ut_assert_console_end();
return 0;
}
DM_TEST(dm_test_pmbus_telemetry, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus status decodes every STATUS_* register; the emulator is clean */
static int dm_test_pmbus_status(struct unit_test_state *uts)
{
ut_assertok(pmbus_select(uts));
ut_assertok(run_command("pmbus status", 0));
ut_assert_nextline("pmbus status @ i2c0:0x70");
ut_assert_nextline(" STATUS_WORD (79h) = 0x0000 [clean]");
ut_assert_nextline(" STATUS_VOUT (7Ah) = 0x00 [clean]");
ut_assert_nextline(" STATUS_IOUT (7Bh) = 0x00 [clean]");
ut_assert_nextline(" STATUS_INPUT (7Ch) = 0x00 [clean]");
ut_assert_nextline(" STATUS_TEMP (7Dh) = 0x00 [clean]");
ut_assert_nextline(" STATUS_CML (7Eh) = 0x00 [clean]");
ut_assert_console_end();
return 0;
}
DM_TEST(dm_test_pmbus_status, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus read/pmbus write raw register access (byte, word, string) */
static int dm_test_pmbus_read_write(struct unit_test_state *uts)
{
ut_assertok(pmbus_select(uts));
/* Symbolic and numeric register names both resolve */
ut_assertok(run_command("pmbus read VOUT_MODE", 0));
ut_assert_nextline(" 20h VOUT_MODE b=0x18");
ut_assertok(run_command("pmbus read 8b w", 0));
ut_assert_nextline(" 8bh READ_VOUT w=0x0200");
ut_assertok(run_command("pmbus read MFR_ID s", 0));
ut_assert_nextline(" 99h MFR_ID s=\"SANDBOX\"");
/* A word write is observable on the next read-back */
ut_assertok(run_command("pmbus write VOUT_COMMAND 123 w", 0));
ut_assert_nextline("pmbus: wrote 0x123 to 21h (VOUT_COMMAND)");
ut_assertok(run_command("pmbus read VOUT_COMMAND w", 0));
ut_assert_nextline(" 21h VOUT_COMMAND w=0x0123");
ut_assert_console_end();
return 0;
}
DM_TEST(dm_test_pmbus_read_write, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus vout reads back VOUT via the active driver_info decoder */
static int dm_test_pmbus_vout(struct unit_test_state *uts)
{
ut_assertok(pmbus_select(uts));
ut_assertok(run_command("pmbus vout", 0));
ut_assert_nextline("pmbus VOUT @ i2c0:0x70 raw=0x0200 2.000V");
ut_assert_console_end();
return 0;
}
DM_TEST(dm_test_pmbus_vout, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus dump walks every standard register; spot-check one line */
static int dm_test_pmbus_dump(struct unit_test_state *uts)
{
ut_assertok(pmbus_select(uts));
ut_assertok(run_command("pmbus dump", 0));
ut_assert_nextline("pmbus dump @ i2c0:0x70 (registers known to <pmbus.h>)");
ut_assert_skip_to_line(" 20h VOUT_MODE b=0x18");
return 0;
}
DM_TEST(dm_test_pmbus_dump, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus clear [faults] issues CLEAR_FAULTS (03h) */
static int dm_test_pmbus_clear(struct unit_test_state *uts)
{
ut_assertok(pmbus_select(uts));
ut_assertok(run_command("pmbus clear faults", 0));
ut_assert_nextline("pmbus: CLEAR_FAULTS (03h) issued (RAM sticky STATUS_* cleared)");
ut_assert_console_end();
return 0;
}
DM_TEST(dm_test_pmbus_clear, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus scan finds the emulated chip by its MFR_ID block read */
static int dm_test_pmbus_scan(struct unit_test_state *uts)
{
ut_assertok(run_command("pmbus scan 0", 0));
ut_assert_skip_to_line(" i2c0:0x70 MFR_ID=\"SANDBOX\"");
return 0;
}
DM_TEST(dm_test_pmbus_scan, UTF_SCAN_FDT | UTF_CONSOLE);
/* pmbus help lists vendor extensions; none are registered here */
static int dm_test_pmbus_help(struct unit_test_state *uts)
{
ut_assertok(run_command("pmbus help", 0));
ut_assert_nextline("pmbus: no vendor extensions registered.");
ut_assert_skip_to_line(" board hook (boot snapshot) and re run 'pmbus help'.");
return 0;
}
DM_TEST(dm_test_pmbus_help, UTF_SCAN_FDT | UTF_CONSOLE);
+38
View File
@@ -28,6 +28,7 @@ enum {
BUCK3,
LDO1,
LDO2,
LDO3,
OUTPUT_COUNT,
};
@@ -44,6 +45,7 @@ static const char *regulator_names[OUTPUT_COUNT][OUTPUT_NAME_COUNT] = {
{ SANDBOX_BUCK3_DEVNAME, SANDBOX_BUCK3_PLATNAME },
{ SANDBOX_LDO1_DEVNAME, SANDBOX_LDO1_PLATNAME},
{ SANDBOX_LDO2_DEVNAME, SANDBOX_LDO2_PLATNAME},
{ SANDBOX_LDO3_DEVNAME, SANDBOX_LDO3_PLATNAME},
};
/* Test regulator get method */
@@ -118,6 +120,42 @@ static int dm_test_power_regulator_set_get_voltage(struct unit_test_state *uts)
}
DM_TEST(dm_test_power_regulator_set_get_voltage, UTF_SCAN_FDT);
/* Test regulator set Voltage clamp method */
static int dm_test_power_regulator_set_value_clamp(struct unit_test_state *uts)
{
struct udevice *dev;
const char *platname;
/* LDO3 have 'min' 1.8V and 'max' 3.3V */
platname = regulator_names[LDO3][PLATNAME];
ut_assertok(regulator_get_by_platname(platname, &dev));
/* 'target' in 'min'/'max' range - should not clamp voltage */
ut_assertok(regulator_set_value_clamp(dev, 1700000, 1800000, 1950000));
ut_asserteq(1800000, regulator_get_value(dev));
ut_assertok(regulator_set_value_clamp(dev, 2700000, 3300000, 3600000));
ut_asserteq(3300000, regulator_get_value(dev));
/* 'target' out of 'min'/'max' range - should clamp voltage */
ut_assertok(regulator_set_value_clamp(dev, 1700000, 1700000, 1950000));
ut_asserteq(1800000, regulator_get_value(dev));
ut_assertok(regulator_set_value_clamp(dev, 2700000, 3400000, 3600000));
ut_asserteq(3300000, regulator_get_value(dev));
/* 'min'/'max' out of range - should return -EINVAL */
ut_asserteq(-EINVAL,
regulator_set_value_clamp(dev, 1200000, 1500000, 1700000));
ut_asserteq(-EINVAL,
regulator_set_value_clamp(dev, 3500000, 4000000, 5000000));
/* 'min' higher than 'max' - should return -EINVAL */
ut_asserteq(-EINVAL,
regulator_set_value_clamp(dev, 3100000, 3000000, 2900000));
return 0;
}
DM_TEST(dm_test_power_regulator_set_value_clamp, UTF_SCAN_FDT);
/* Test regulator set and get Current method */
static int dm_test_power_regulator_set_get_current(struct unit_test_state *uts)
{