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

Reply via email to