[PATCH v1 08/23] board: nbxv3: add DPLL status (ZL30733, ZL30643)

Vincent Jardin <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Add a boot time probe for each Microsemi/Microchip DPLL on the
board:
 - ZL30733
 - ZL30643

Linux: full DPLL configuration (clock tree programming, output enables,
holdover policy, line side rates) is the job of the Linux DPLL driver
at runtime. U-Boot only verifies presence and identity, it does not
program any output.

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

 arch/arm/dts/fsl-lx2160a-nbxv3.dts     |   8 ++
 board/nxp/lx2160a/Kconfig              |   7 +
 board/nxp/lx2160a/nbxv3/Makefile       |   1 +
 board/nxp/lx2160a/nbxv3/dpll_zl30733.c | 183 +++++++++++++++++++++++++
 4 files changed, 199 insertions(+)
 create mode 100644 board/nxp/lx2160a/nbxv3/dpll_zl30733.c

diff --git a/arch/arm/dts/fsl-lx2160a-nbxv3.dts b/arch/arm/dts/fsl-lx2160a-nbxv3.dts
index 8e336a622f3..9dcc1bf1d5e 100644
--- a/arch/arm/dts/fsl-lx2160a-nbxv3.dts
+++ b/arch/arm/dts/fsl-lx2160a-nbxv3.dts
@@ -57,3 +57,11 @@
 		regulator-always-on;
 	};
 };
+
+&dspi0 { /* DSPI1: on module management SPI */
+	dpll@0 {
+		compatible = "microchip,zl30733";
+		reg = <0>;
+		spi-max-frequency = <12500000>;
+	};
+};
diff --git a/board/nxp/lx2160a/Kconfig b/board/nxp/lx2160a/Kconfig
index e46223d1b43..12d2e418aa5 100644
--- a/board/nxp/lx2160a/Kconfig
+++ b/board/nxp/lx2160a/Kconfig
@@ -76,4 +76,11 @@ config NBXV3_PMBUS
 	select DM_REGULATOR_PMBUS_GENERIC
 	select CMD_REGULATOR
 
+config NBXV3_DPLL
+	bool
+	default y
+	select SPI
+	select DM_SPI
+	select FSL_DSPI
+
 endif
diff --git a/board/nxp/lx2160a/nbxv3/Makefile b/board/nxp/lx2160a/nbxv3/Makefile
index 4ce2508249d..c8a676fb708 100644
--- a/board/nxp/lx2160a/nbxv3/Makefile
+++ b/board/nxp/lx2160a/nbxv3/Makefile
@@ -4,6 +4,7 @@
 
 obj-y += eth_nbxv3.o
 obj-y += carrier.o
+obj-y += dpll_zl30733.o
 obj-y += mps_pmbus.o
 obj-y += psu_pmbus.o
 obj-y += vid_handoff.o
diff --git a/board/nxp/lx2160a/nbxv3/dpll_zl30733.c b/board/nxp/lx2160a/nbxv3/dpll_zl30733.c
new file mode 100644
index 00000000000..4dfa4579cce
--- /dev/null
+++ b/board/nxp/lx2160a/nbxv3/dpll_zl30733.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Free Mobile - Vincent Jardin
+ *
+ * Nodebox v3 - DPLL identification probe (both DPLLs).
+ *
+ * It reads a small set of read-only descriptor registers from each
+ * Microsemi/Microchip DPLL on the board and prints the result on the
+ * console at EVT_LAST_STAGE_INIT time, just before the prompt.
+ * It is purely a basic power on test diagnostic: no configuration
+ * register is written, the DPLL register profile loaded at power-on
+ * is left untouched.
+ */
+
+#include <command.h>
+#include <dm.h>
+#include <event.h>
+#include <log.h>
+#include <spi.h>
+#include <vsprintf.h>
+#include <linux/delay.h>
+
+#define ZL30733_SPI_HZ		12500000
+#define ZL30733_SPI_MODE	SPI_MODE_0
+
+/* The two DPLLs sharing the ZL3073x register protocol on this board */
+static const struct nbxv3_dpll {
+	const char *label;	/* expected part at this location */
+	const char *devname;	/* DM device name for the ad-hoc slave */
+	int bus;		/* U-Boot SPI bus number */
+	int cs;			/* chip select on that bus */
+	u32 hz;			/* SCK ceiling */
+	const char *hint;	/* what to check when reads are implausible */
+} nbxv3_dplls[] = {
+	{
+		.label = "ZL30733", .devname = "zl30733_dpll",
+		.bus = 0, .cs = 0, .hz = ZL30733_SPI_HZ,
+		.hint = "check PROG_DPLL_EN mux",
+	},
+	{
+		.label = "ZL30643", .devname = "zl30643_dpll",
+		.bus = 2, .cs = 1, .hz = 10000000,
+		.hint = "check CS1 rework / RST_B",
+	},
+};
+
+#define ZL30733_REG_INFO	0x00
+#define ZL30733_REG_ID_HI	0x01
+#define ZL30733_REG_ID_LO	0x02
+#define ZL30733_REG_REVISION	0x03
+#define ZL30733_REG_FW_VER	0x05	/* 6 bytes 0x05..0x0A   */
+#define ZL30733_REG_FW_VER_LEN	6
+#define ZL30733_REG_RST_STATUS	0x18
+#define ZL30733_REG_PAGE_SEL	0x7F
+
+#define ZL30733_CMD_READ	0x80	/* OR'd into the 7-bit address */
+
+#define ZL30733_INFO_DEFAULT		0x21
+#define ZL30733_INFO_ID_MASK		0x7f
+#define ZL30733_INFO_EEPROM_BOOTED	0x80
+#define ZL30733_REV_DEFAULT		0x03
+
+static int zl30733_read_block(struct spi_slave *slave, u8 addr, u8 *buf,
+			      size_t len)
+{
+	u8 cmd = ZL30733_CMD_READ | (addr & 0x7F);
+	int ret;
+
+	ret = spi_xfer(slave, 8, &cmd, NULL, SPI_XFER_BEGIN);
+	if (ret)
+		return ret;
+
+	return spi_xfer(slave, len * 8, NULL, buf, SPI_XFER_END);
+}
+
+static int zl30733_select_page0(struct spi_slave *slave)
+{
+	u8 cmd[2] = { ZL30733_REG_PAGE_SEL & 0x7F, 0x00 };
+
+	return spi_xfer(slave, sizeof(cmd) * 8, cmd, NULL,
+			SPI_XFER_BEGIN | SPI_XFER_END);
+}
+
+static int zl30733_dump(struct spi_slave *slave, const struct nbxv3_dpll *d)
+{
+	u8 info, rev, rst;
+	u8 fw[ZL30733_REG_FW_VER_LEN];
+	u8 idbuf[2];
+	u16 id;
+	int ret;
+	bool plausible;
+
+	ret = zl30733_select_page0(slave);
+	if (ret) {
+		printf("DPLL: %s page select failed (%d)\n", d->label, ret);
+		return ret;
+	}
+
+	ret = zl30733_read_block(slave, ZL30733_REG_INFO, &info, 1);
+	if (ret)
+		return ret;
+	ret = zl30733_read_block(slave, ZL30733_REG_ID_HI, idbuf, sizeof(idbuf));
+	if (ret)
+		return ret;
+	id = ((u16)idbuf[0] << 8) | idbuf[1];
+	ret = zl30733_read_block(slave, ZL30733_REG_REVISION, &rev, 1);
+	if (ret)
+		return ret;
+	ret = zl30733_read_block(slave, ZL30733_REG_FW_VER, fw, sizeof(fw));
+	if (ret)
+		return ret;
+	ret = zl30733_read_block(slave, ZL30733_REG_RST_STATUS, &rst, 1);
+	if (ret)
+		return ret;
+
+	plausible = (info & ZL30733_INFO_ID_MASK) == ZL30733_INFO_DEFAULT &&
+		    id != 0x0000 && id != 0xFFFF;
+
+	printf("DPLL: %s @ spi%d:%d  info=0x%02x (%s) id=0x%04x rev=0x%02x\n",
+	       d->label, d->bus, d->cs, info,
+	       (info & ZL30733_INFO_EEPROM_BOOTED) ? "EEPROM boot" : "MCU default boot",
+	       id, rev);
+	printf("      fw_ver=%02x:%02x:%02x:%02x:%02x:%02x  reset_status=0x%02x",
+	       fw[0], fw[1], fw[2], fw[3], fw[4], fw[5], rst);
+	if (!plausible)
+		printf("  [implausible; %s]", d->hint);
+	printf("\n");
+
+	return plausible ? 0 : 1;
+}
+
+static int zl30733_probe_print(void)
+{
+	struct udevice *bus;
+	struct spi_slave *slave;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(nbxv3_dplls); i++) {
+		const struct nbxv3_dpll *d = &nbxv3_dplls[i];
+
+		ret = _spi_get_bus_and_cs(d->bus, d->cs, d->hz,
+					  ZL30733_SPI_MODE,
+					  "spi_generic_drv", d->devname,
+					  &bus, &slave);
+		if (ret) {
+			printf("DPLL: %s spi%d:%d not reachable (%d)\n",
+			       d->label, d->bus, d->cs, ret);
+			continue;
+		}
+
+		ret = spi_claim_bus(slave);
+		if (ret) {
+			printf("DPLL: %s spi_claim_bus failed (%d)\n",
+			       d->label, ret);
+			continue;
+		}
+
+		zl30733_dump(slave, d);
+
+		spi_release_bus(slave);
+	}
+
+	return 0;
+}
+
+static int nbxv3_dpll_last_stage_init(void)
+{
+	zl30733_probe_print();
+	return 0;
+}
+EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, nbxv3_dpll_last_stage_init);
+
+static int do_dpll_info(struct cmd_tbl *cmdtp, int flag, int argc,
+			char *const argv[])
+{
+	return zl30733_probe_print() < 0 ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(
+	dpll_info, 1, 0, do_dpll_info,
+	"display ZL30733 + ZL30643 DPLL identification + reset status",
+	""
+);
-- 
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.