From f87ae7f176dcea3cf159ff82b27e5592c3c38a3e Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:50:53 +0000 Subject: [PATCH 1/3] novatek: stay in the loader's U-Boot region, below 64 MiB U-Boot relocated to the top of the DDR. On the NT98566 that is past 64 MiB, so the MAC's descriptors and buffers went there too, and on an XM N8S board the Tx DMA never took the first descriptor: the link came up, then "eqos: Tx timed out" on every frame (OpenIPC/u-boot#95). The vendor U-Boot never leaves /nvt_memory_cfg/uboot (0x3900000, 6.75 MiB): its ram_size is that region. Relocate to its top, as the vendor does; the DDR size, the osmem split and the lmb reservations are unchanged. On the NT98562 this also keeps U-Boot off core2entry2, just above. Verified under qemu-hisilicon, nt98562 and nt98566: relocaddr 0x3f6b000, a 3 MB tftpboot with matching crc32, ping, boot to login from an 8 MiB image with the nt9856x_lite kernel and rootfs, Linux's driver gets its lease. Not yet on silicon: whether this fixes #95 is for the reporter to confirm. --- arch/arm/mach-novatek/include/mach/na51089.h | 3 +++ board/novatek/na51089/na51089.c | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/arch/arm/mach-novatek/include/mach/na51089.h b/arch/arm/mach-novatek/include/mach/na51089.h index e3b4d9723912..da529981161d 100644 --- a/arch/arm/mach-novatek/include/mach/na51089.h +++ b/arch/arm/mach-novatek/include/mach/na51089.h @@ -57,5 +57,8 @@ #define NVT_SHMINFO (NVT_DDR_BASE + 0x00200000) #define NVT_SHM_LD_NVT 0x00 /* "LD_NVT" */ #define NVT_SHM_FDT_ADDR 0x2C +/* /nvt_memory_cfg/uboot: where U-Boot is loaded, and where it stays */ +#define NVT_UBOOT_BASE (NVT_DDR_BASE + 0x03900000) +#define NVT_UBOOT_SIZE 0x006C0000 #endif diff --git a/board/novatek/na51089/na51089.c b/board/novatek/na51089/na51089.c index 209c37698429..41a78ad84702 100644 --- a/board/novatek/na51089/na51089.c +++ b/board/novatek/na51089/na51089.c @@ -63,6 +63,18 @@ int misc_init_r(void) return ret; } +/* + * U-Boot relocates to the top of the loader's region for it, not of the + * DDR, as the vendor U-Boot does (its ram_size is that region). On the + * NT98566 the top of the DDR is past 64 MiB, and there the MAC's DMA + * never took a Tx descriptor (OpenIPC/u-boot#95). On the NT98562 it + * also keeps clear of /nvt_memory_cfg/core2entry2, just above. + */ +ulong board_get_usable_ram_top(ulong total_size) +{ + return NVT_UBOOT_BASE + NVT_UBOOT_SIZE; +} + /* What Linux gets of the DDR (mem=); the vendor media modules get the rest */ static ulong nvt_osmem(void) { From e5ea81b44fd38596a3ea06e986dca126b44e15d5 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:58:04 +0000 Subject: [PATCH 2/3] novatek: the rest of the vendor U-Boot's board set-up What the vendor U-Boot's nvt_ivot_hw_init_early() and nvt_ivot_hw_init() (nvt-na51089/na51089_hw_init.c) do, in board_init(), in their order: - the OTP controller's timing for the APB clock, and CG 0x70 bit 16 cleared; - the CPU clock the loader's device tree asks for (/cpus/cpu@0 clock-frequency, 960 MHz on the Xiongmai trees: PLL8 at ratio 0xA00000), and the APB clock if it names one, as nvt_cpu_freq and nvt_apb_freq do; their polls get a timeout; - the AXI bridge timeouts (0xF0012018, 0xF0013018); - the USB PHY set-up by chip revision, then its power-down. Its PHY reset on D_GPIO(1) stays out: the pin is set in the EVB's header, nvt-na51089-evb.h, and may be anything on another board. Verified under qemu-hisilicon, nt98562 and nt98566: boot to login from an 8 MiB image with the nt9856x_lite kernel and rootfs, a 3 MB tftpboot with matching crc32, ping, Linux's driver gets its lease. qemu does not model the clock switch or the bridges; not yet on silicon. --- board/novatek/na51089/na51089.c | 129 +++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/board/novatek/na51089/na51089.c b/board/novatek/na51089/na51089.c index 41a78ad84702..b967eecedf87 100644 --- a/board/novatek/na51089/na51089.c +++ b/board/novatek/na51089/na51089.c @@ -21,8 +21,136 @@ DECLARE_GLOBAL_DATA_PTR; +/* + * The rest of the vendor U-Boot's board set-up (nvt-na51089/ + * na51089_hw_init.c: nvt_ivot_hw_init_early(), nvt_ivot_hw_init()), in + * its order. Its PHY reset on D_GPIO(1) is left out: that pin is its + * EVB's (nvt-na51089-evb.h), not every board's. + */ +#define CG_SYSCLK (NVT_CG_BASE + 0x10) +#define SYSCLK_CPU (3 << 0) /* 80 MHz, PLL8, 480 MHz */ +#define SYSCLK_CPU_80 0 +#define SYSCLK_CPU_PLL8 1 +#define SYSCLK_CPU_480 2 +#define SYSCLK_APB (3 << 8) /* 48, 60, 80, 120 MHz */ +#define CG_PLL_EN (NVT_CG_BASE + 0x00) +#define CG_PLL_STAT (NVT_CG_BASE + 0x04) +#define CG_PLL8 BIT(8) /* the CPU's PLL */ +#define CG_CLK_SWITCH (NVT_CG_BASE + 0xA0) +#define CG_CLKEN_70 (NVT_CG_BASE + 0x70) +#define CG_PLL8_RATIO(n) (NVT_CG_BASE + 0x4420 + 4 * (n)) +#define OTP_TIMING 0xF0660010 + +/* Wait for (reg & mask) != 0; the vendor waits for ever */ +static void nvt_wait_set(ulong reg, u32 mask) +{ + int i; + + for (i = 0; i < 100000 && !(readl(reg) & mask); i++) + udelay(1); +} + +/* A new CPU clock source takes effect through this handshake */ +static void nvt_cpu_clk_sel(u32 sel) +{ + clrsetbits_le32(CG_SYSCLK, SYSCLK_CPU, sel); + writel(0x34, CG_CLK_SWITCH); + nvt_wait_set(CG_CLK_SWITCH, BIT(3)); + writel(0x28, CG_CLK_SWITCH); +} + +/* The vendor's nvt_cpu_freq: 80, 480, or anything else as 960 MHz */ +static void nvt_set_cpu_mhz(u32 mhz) +{ + u32 sel = readl(CG_SYSCLK) & SYSCLK_CPU; + + if (mhz == 80 || mhz == 480) { + u32 want = mhz == 80 ? SYSCLK_CPU_80 : SYSCLK_CPU_480; + + if (sel == want) + return; + nvt_cpu_clk_sel(want); + clrbits_le32(CG_PLL_EN, CG_PLL8); + return; + } + if (sel != SYSCLK_CPU_PLL8) { + if (!(readl(CG_PLL_EN) & CG_PLL8)) { + setbits_le32(CG_PLL_EN, CG_PLL8); + nvt_wait_set(CG_PLL_STAT, CG_PLL8); + } + nvt_cpu_clk_sel(SYSCLK_CPU_PLL8); + } + /* 960 MHz: 12 MHz * 0xA00000 / 2^17 */ + writel(0x00, CG_PLL8_RATIO(0)); + writel(0x00, CG_PLL8_RATIO(1)); + writel(0xA0, CG_PLL8_RATIO(2)); +} + +/* The vendor's nvt_apb_freq: 48, 60, 120, or anything else as 80 MHz */ +static void nvt_set_apb_mhz(u32 mhz) +{ + u32 sel = mhz == 48 ? 0 : mhz == 60 ? 1 : mhz == 120 ? 3 : 2; + + clrsetbits_le32(CG_SYSCLK, SYSCLK_APB, sel << 8); +} + +/* The clocks the loader's device tree asks for: /cpus/cpu@0 */ +static void nvt_set_clocks(void) +{ + const void *shm = (const void *)NVT_SHMINFO; + const void *fdt; + const fdt32_t *val; + int node; + + if (memcmp(shm + NVT_SHM_LD_NVT, "LD_NVT", 6)) + return; + fdt = (const void *)readl(shm + NVT_SHM_FDT_ADDR); + if (fdt_check_header(fdt)) + return; + node = fdt_first_subnode(fdt, fdt_path_offset(fdt, "/cpus")); + if (node < 0) + return; + val = fdt_getprop(fdt, node, "clock-frequency", NULL); + if (val) + nvt_set_cpu_mhz(fdt32_to_cpu(*val) / 1000000); + /* Sic, as the vendor spells it */ + val = fdt_getprop(fdt, node, "apb-frequenccy", NULL); + if (val) + nvt_set_apb_mhz(fdt32_to_cpu(*val) / 1000000); +} + int board_init(void) { + /* The eFuse controller's timing for the APB clock it runs at */ + static const u32 otp_timing[4] = { + 0x692618, 0x8B3026, 0xAF4032, 0xFF6050, + }; + + /* nvt_ivot_hw_init_early() */ + writel(otp_timing[(readl(CG_SYSCLK) >> 8) & 3], OTP_TIMING); + clrbits_le32(CG_CLKEN_70, BIT(16)); + + /* nvt_ivot_hw_init() */ + nvt_set_clocks(); + /* AXI bridge timeouts */ + writel(0x7c, 0xF0012018); + writel(0x7c, 0xF0013018); + /* The USB PHY, by the chip's revision, then powered down */ + if (readl(NVT_TOP_CHIP_ID) == 0x50210000) { + setbits_le32(0xFF600400, BIT(21)); + setbits_le32(0xFF6001C8, BIT(31)); + } else { + setbits_le32(0xF0600310, BIT(1)); + setbits_le32(0xF06001C8, BIT(5)); + } + writel(0xFF, 0xF06010D0); + writel(0x34, 0xF06010D4); + writel(0x14, 0xF0601008); + writel(0x88, 0xF0601000); + writel(0x35, 0xF0601030); + writel(0xFF, 0xF060102C); + writel(0x20, 0xF0601028); + writel(0x93, 0xF0601024); return 0; } @@ -165,7 +293,6 @@ int ft_board_setup(void *blob, struct bd_info *bd) #define SRAM_SD_ETH BIT(18) #define PAD_DRV_P (NVT_PAD_BASE + 0x50) #define PAD_DRV_DSI (NVT_PAD_BASE + 0x98) -#define CG_PLL_EN (NVT_CG_BASE + 0x00) #define CG_PLL6 BIT(6) #define CG_CLKEN2 (NVT_CG_BASE + 0x78) #define CG_ETH_PHY_CLK BIT(24) From 2f2d456923e0e79cbd3e942c5f941669cab6cb21 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:08:11 +0000 Subject: [PATCH 3/3] novatek: check the CPU PLL locks; eFuse timing follows the APB clock From the review of the board set-up: - the waits return a status: the CPU is not switched to a PLL8 that did not lock (the clock stays as the loader left it, with a message), and an unacknowledged switch says so; the vendor waits for ever; - the eFuse controller's timing is set again when the device tree changes the APB clock; the vendor leaves it at the old clock's. No Xiongmai tree names an APB clock, so this only matters for others. Verified under qemu-hisilicon, nt98562 and nt98566: boot to login, a 3 MB tftpboot with matching crc32, ping, Linux's lease. --- board/novatek/na51089/na51089.c | 44 ++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/board/novatek/na51089/na51089.c b/board/novatek/na51089/na51089.c index b967eecedf87..b507611259c6 100644 --- a/board/novatek/na51089/na51089.c +++ b/board/novatek/na51089/na51089.c @@ -41,13 +41,17 @@ DECLARE_GLOBAL_DATA_PTR; #define CG_PLL8_RATIO(n) (NVT_CG_BASE + 0x4420 + 4 * (n)) #define OTP_TIMING 0xF0660010 -/* Wait for (reg & mask) != 0; the vendor waits for ever */ -static void nvt_wait_set(ulong reg, u32 mask) +/* Wait 100 ms for (reg & mask) != 0; the vendor waits for ever */ +static int nvt_wait_set(ulong reg, u32 mask) { int i; - for (i = 0; i < 100000 && !(readl(reg) & mask); i++) + for (i = 0; i < 100000; i++) { + if (readl(reg) & mask) + return 0; udelay(1); + } + return -ETIMEDOUT; } /* A new CPU clock source takes effect through this handshake */ @@ -55,7 +59,8 @@ static void nvt_cpu_clk_sel(u32 sel) { clrsetbits_le32(CG_SYSCLK, SYSCLK_CPU, sel); writel(0x34, CG_CLK_SWITCH); - nvt_wait_set(CG_CLK_SWITCH, BIT(3)); + if (nvt_wait_set(CG_CLK_SWITCH, BIT(3))) + printf("CPU clock switch not acknowledged\n"); writel(0x28, CG_CLK_SWITCH); } @@ -74,9 +79,11 @@ static void nvt_set_cpu_mhz(u32 mhz) return; } if (sel != SYSCLK_CPU_PLL8) { - if (!(readl(CG_PLL_EN) & CG_PLL8)) { - setbits_le32(CG_PLL_EN, CG_PLL8); - nvt_wait_set(CG_PLL_STAT, CG_PLL8); + setbits_le32(CG_PLL_EN, CG_PLL8); + /* Not onto a PLL that has not locked: stay where it is */ + if (nvt_wait_set(CG_PLL_STAT, CG_PLL8)) { + printf("CPU PLL8 not locked, clock left as is\n"); + return; } nvt_cpu_clk_sel(SYSCLK_CPU_PLL8); } @@ -86,12 +93,26 @@ static void nvt_set_cpu_mhz(u32 mhz) writel(0xA0, CG_PLL8_RATIO(2)); } -/* The vendor's nvt_apb_freq: 48, 60, 120, or anything else as 80 MHz */ +/* The eFuse controller's timing for the APB clock it runs at */ +static void nvt_otp_timing(void) +{ + static const u32 timing[4] = { /* 48, 60, 80, 120 MHz */ + 0x692618, 0x8B3026, 0xAF4032, 0xFF6050, + }; + + writel(timing[(readl(CG_SYSCLK) & SYSCLK_APB) >> 8], OTP_TIMING); +} + +/* + * The vendor's nvt_apb_freq: 48, 60, 120, or anything else as 80 MHz. + * The eFuse timing follows, which the vendor leaves at the old clock's. + */ static void nvt_set_apb_mhz(u32 mhz) { u32 sel = mhz == 48 ? 0 : mhz == 60 ? 1 : mhz == 120 ? 3 : 2; clrsetbits_le32(CG_SYSCLK, SYSCLK_APB, sel << 8); + nvt_otp_timing(); } /* The clocks the loader's device tree asks for: /cpus/cpu@0 */ @@ -121,13 +142,8 @@ static void nvt_set_clocks(void) int board_init(void) { - /* The eFuse controller's timing for the APB clock it runs at */ - static const u32 otp_timing[4] = { - 0x692618, 0x8B3026, 0xAF4032, 0xFF6050, - }; - /* nvt_ivot_hw_init_early() */ - writel(otp_timing[(readl(CG_SYSCLK) >> 8) & 3], OTP_TIMING); + nvt_otp_timing(); clrbits_le32(CG_CLKEN_70, BIT(16)); /* nvt_ivot_hw_init() */