[PATCH v1 06/23] board: nbxv3: add VID-fuse driven core voltage

Vincent Jardin <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
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

Hooked at EVT_LAST_STAGE_INIT so the rail is at the VID-correct
voltage before the kernel regulator framework probes the chip.

Set nbxv3_vdd_mv to a millivolt integer in [600, 1100] to bypass
the VID fuse and pin the rail at the wanted voltage. Useful for
bench characterisation.

Signed-off-by: Vincent Jardin <[email protected]>
---

 board/nxp/lx2160a/nbxv3/Makefile      |   1 +
 board/nxp/lx2160a/nbxv3/vid_handoff.c | 146 ++++++++++++++++++++++++++
 2 files changed, 147 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..4131bd96023
--- /dev/null
+++ b/board/nxp/lx2160a/nbxv3/vid_handoff.c
@@ -0,0 +1,146 @@
+// 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 <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
+#define NBXV3_VID_TOLERANCE_uV		1953
+#define NBXV3_VID_STEP_uV		1953
+#define NBXV3_VID_MAX_ITER		50
+#define NBXV3_VID_PLATEAU_LIMIT		3
+
+static const u16 lx2160a_vid_table[32] = {
+	[0]  = 8250,
+	[1]  = 7875,
+	[2]  = 7750,
+	[16] = 8000,
+	[17] = 8125,
+	[18] = 8250,
+	[20] = 8500,
+};
+
+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;
+	char *override_str;
+	unsigned long override_mv;
+	int ret;
+
+	if (regulator_get_by_platname(NBXV3_VDD_REGULATOR_NAME, &reg) || !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 (actual_uV >= target_uV - NBXV3_VID_TOLERANCE_uV) {
+		printf("MPQ8785 (%s): VID handoff fuse=0x%02x target=%d mV (already within tolerance, rail at %d uV)\n",
+		       NBXV3_VDD_REGULATOR_NAME, vid, target_mv, actual_uV);
+		return 0;
+	}
+
+	printf("MPQ8785 (%s): VID handoff fuse=0x%02x %d uV -> target %d mV (closed-loop adjust)\n",
+	       NBXV3_VDD_REGULATOR_NAME, vid, actual_uV, target_mv);
+
+	/*
+	 * Plateau detection: if the rail stops moving while we are
+	 * still bumping VOUT_COMMAND, the chip is clamping at VOUT_MAX
+	 * (or refusing to slew higher for some other reason). Bail
+	 * with a WARNING and leave the rail at whatever it settled at.
+	 */
+	{
+		int cmd_uV = target_uV;
+		int last_actual_uV = -1;
+		int plateau = 0;
+		int iter;
+
+		for (iter = 0; iter < NBXV3_VID_MAX_ITER; iter++) {
+			ret = regulator_set_value(reg, cmd_uV);
+			if (ret) {
+				printf("WARNING: MPQ8785 (%s): VID handoff set_value failed at iter %d (cmd %d uV, ret %d), rail at %d uV\n",
+				       NBXV3_VDD_REGULATOR_NAME, iter, cmd_uV,
+				       ret, actual_uV);
+				return 0;
+			}
+
+			actual_uV = regulator_get_value(reg);
+			if (actual_uV >= target_uV - NBXV3_VID_TOLERANCE_uV) {
+				printf("MPQ8785 (%s): VID handoff settled at %d uV after %d iter (cmd %d uV, target %d uV)\n",
+				       NBXV3_VDD_REGULATOR_NAME, actual_uV,
+				       iter + 1, cmd_uV, target_uV);
+				return 0;
+			}
+
+			if (last_actual_uV >= 0 &&
+			    actual_uV - last_actual_uV < NBXV3_VID_LSB_uV) {
+				plateau++;
+				if (plateau >= NBXV3_VID_PLATEAU_LIMIT) {
+					printf("WARNING: MPQ8785 (%s): VID handoff stalled at %d uV after %d iter (target %d uV, cmd %d uV) -- VOUT_MAX clamp or chip refused\n",
+					       NBXV3_VDD_REGULATOR_NAME,
+					       actual_uV, iter + 1,
+					       target_uV, cmd_uV);
+					return 0;
+				}
+			} else {
+				plateau = 0;
+			}
+			last_actual_uV = actual_uV;
+			cmd_uV += NBXV3_VID_STEP_uV;
+		}
+
+		printf("WARNING: MPQ8785 (%s): VID handoff did not converge after %d iter (rail %d uV, target %d uV, cmd %d uV)\n",
+		       NBXV3_VDD_REGULATOR_NAME, iter, actual_uV, target_uV,
+		       cmd_uV);
+		return 0;
+	}
+}
+EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, nbxv3_vid_handoff);
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.