Read the LX2160A DCFG_FUSESR.ALTVID / VID fuses, look up the binned target voltage, and command the rail through the UCLASS_REGULATOR set_value op
The rail is set ONCE to exactly the fused voltage. Signed-off-by: Vincent Jardin <[email protected]> --- (no changes since v1) board/nxp/lx2160a/nbxv3/Makefile | 1 + board/nxp/lx2160a/nbxv3/vid_handoff.c | 144 ++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 board/nxp/lx2160a/nbxv3/vid_handoff.c diff --git a/board/nxp/lx2160a/nbxv3/Makefile b/board/nxp/lx2160a/nbxv3/Makefile index 24874138833..68fab9bf18f 100644 --- a/board/nxp/lx2160a/nbxv3/Makefile +++ b/board/nxp/lx2160a/nbxv3/Makefile @@ -5,3 +5,4 @@ obj-y += eth_nbxv3.o obj-y += mps_pmbus.o obj-y += psu_pmbus.o +obj-y += vid_handoff.o diff --git a/board/nxp/lx2160a/nbxv3/vid_handoff.c b/board/nxp/lx2160a/nbxv3/vid_handoff.c new file mode 100644 index 00000000000..d3edaa61080 --- /dev/null +++ b/board/nxp/lx2160a/nbxv3/vid_handoff.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2026 Free Mobile, Vincent Jardin + * + * Per die core voltage trim for the Nodebox v3 +0V8_VDD rail + */ + +#include <command.h> +#include <dm.h> +#include <env.h> +#include <event.h> +#include <linux/delay.h> +#include <log.h> +#include <vsprintf.h> +#include <asm/io.h> +#include <asm/arch/immap_lsch3.h> +#include <power/regulator.h> + +#define NBXV3_VDD_REGULATOR_NAME "+0V8_VDD" +#define NBXV3_VDD_OVERRIDE_ENV "nbxv3_vdd_mv" +#define NBXV3_VDD_MIN_MV 600 +#define NBXV3_VDD_MAX_MV 1100 + +#define NBXV3_VID_LSB_uV 1953 /* VOUT_MODE 0x17: 2^-9 V = one PMBus step */ + +#define NBXV3_VID_SLEW_SETTLE_US 5000 /* >> slew time for a 30 mV step */ + +/* + * From LX2160ARM + * "This is the minimum voltage required to reach the frequency + * specified." + * Everything not listed is Reserved, it maps to + * 0 = "leave the rail untouched" below. + */ +static const u16 lx2160a_vid_table[32] = { + [0x00] = 8500, /* default */ + [0x02] = 7750, + [0x10] = 8000, + [0x12] = 8250, + [0x14] = 8500, +}; + +/* + * Write VOUT_COMMAND, let the slew finish, read the rail back. + * Returns the rail in uV, or a negative errno from the write. + */ +static int nbxv3_vid_write_readback(struct udevice *reg, int cmd_uV) +{ + int ret = regulator_set_value(reg, cmd_uV); + + if (ret) + return ret; + udelay(NBXV3_VID_SLEW_SETTLE_US); + return regulator_get_value(reg); +} + +static int nbxv3_vid_handoff(void) +{ + struct ccsr_gur *gur = (void *)(CFG_SYS_FSL_GUTS_ADDR); + struct udevice *reg; + u32 fusesr; + u8 vid; + int target_mv, target_uV, actual_uV, cmd_uV; + char *override_str; + unsigned long override_mv; + int ret; + + if (regulator_get_by_platname(NBXV3_VDD_REGULATOR_NAME, ®) || !reg) + return 0; + + fusesr = in_le32(&gur->dcfg_fusesr); + vid = (fusesr >> FSL_CHASSIS3_DCFG_FUSESR_ALTVID_SHIFT) & + FSL_CHASSIS3_DCFG_FUSESR_ALTVID_MASK; + if (vid == 0 || vid == FSL_CHASSIS3_DCFG_FUSESR_ALTVID_MASK) + vid = (fusesr >> FSL_CHASSIS3_DCFG_FUSESR_VID_SHIFT) & + FSL_CHASSIS3_DCFG_FUSESR_VID_MASK; + + target_mv = (lx2160a_vid_table[vid] + 5) / 10; + if (target_mv == 0) { + printf("MPQ8785 (%s): VID handoff fuse=0x%02x reserved/unknown, leaving rail untouched\n", + NBXV3_VDD_REGULATOR_NAME, vid); + return 0; + } + + /* Env override */ + override_str = env_get(NBXV3_VDD_OVERRIDE_ENV); + if (override_str && !strict_strtoul(override_str, 10, &override_mv) && + override_mv >= NBXV3_VDD_MIN_MV && + override_mv <= NBXV3_VDD_MAX_MV) { + printf("MPQ8785 (%s): VID handoff override %s=%lu mV (fuse VID=0x%02x would have been %d mV)\n", + NBXV3_VDD_REGULATOR_NAME, NBXV3_VDD_OVERRIDE_ENV, + override_mv, vid, target_mv); + target_mv = (int)override_mv; + } else if (override_str) { + printf("MPQ8785 (%s): VID handoff %s=\"%s\" out of range [%d, %d] mV, ignored\n", + NBXV3_VDD_REGULATOR_NAME, NBXV3_VDD_OVERRIDE_ENV, + override_str, NBXV3_VDD_MIN_MV, NBXV3_VDD_MAX_MV); + } + + target_uV = target_mv * 1000; + actual_uV = regulator_get_value(reg); + + if (abs(actual_uV - target_uV) <= NBXV3_VID_LSB_uV) { + printf("MPQ8785 (%s): VID handoff fuse=0x%02x target=%d mV (rail already at %d uV, untouched)\n", + NBXV3_VDD_REGULATOR_NAME, vid, target_mv, actual_uV); + return 0; + } + + printf("MPQ8785 (%s): VID handoff fuse=0x%02x rail %d uV, target %d mV -> write %d uV\n", + NBXV3_VDD_REGULATOR_NAME, vid, actual_uV, target_mv, target_uV); + + ret = nbxv3_vid_write_readback(reg, target_uV); + if (ret < 0) { + printf("WARNING: MPQ8785 (%s): VID handoff set_value(%d uV) failed (%d), rail left at %d uV\n", + NBXV3_VDD_REGULATOR_NAME, target_uV, ret, actual_uV); + return 0; + } + actual_uV = ret; + if (abs(actual_uV - target_uV) <= NBXV3_VID_LSB_uV) { + printf("MPQ8785 (%s): VID handoff verified %d uV (target %d uV, cmd %d uV)\n", + NBXV3_VDD_REGULATOR_NAME, actual_uV, target_uV, target_uV); + return 0; + } + + cmd_uV = target_uV + (target_uV - actual_uV); + ret = nbxv3_vid_write_readback(reg, cmd_uV); + if (ret < 0) { + printf("WARNING: MPQ8785 (%s): VID handoff correction set_value(%d uV) failed (%d), rail left at %d uV\n", + NBXV3_VDD_REGULATOR_NAME, cmd_uV, ret, actual_uV); + return 0; + } + actual_uV = ret; + if (abs(actual_uV - target_uV) <= NBXV3_VID_LSB_uV) + printf("MPQ8785 (%s): VID handoff verified %d uV (target %d uV, cmd %d uV after 1 correction)\n", + NBXV3_VDD_REGULATOR_NAME, actual_uV, target_uV, cmd_uV); + else + printf("WARNING: MPQ8785 (%s): VID handoff rail %d uV %s target %d uV after correction (cmd %d uV) -- VOUT_MAX / non-linear load line, fix on the VR side\n", + NBXV3_VDD_REGULATOR_NAME, actual_uV, + actual_uV < target_uV ? "below" : "above", target_uV, + cmd_uV); + return 0; +} + +EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, nbxv3_vid_handoff); -- 2.43.0
