Merge tag 'mmc-for-2026.10-rc1' of https://git.u-boot-project.org/u-boot/custodians/u-boot-mmc
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:
+20
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) || \
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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], ®) < 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], ®) < 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"
|
||||
);
|
||||
@@ -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
|
||||
|
||||
@@ -49,6 +49,7 @@ Implementation
|
||||
logging
|
||||
makefiles
|
||||
menus
|
||||
pmbus
|
||||
printf
|
||||
smbios
|
||||
spl
|
||||
|
||||
@@ -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", ®))
|
||||
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", ®))
|
||||
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/``.
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
@@ -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),
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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_ */
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
@@ -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
@@ -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, ®);
|
||||
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;
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user