From 1859d60bb4b963af15adfebf7e0cea2fb5c788ef Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 08:28:33 +0000 Subject: [PATCH] sunxi: V831/V833: boot on real silicon, SPI NOR at 12 MHz The V831 image was silent on a real camera: the SPL hung in clock_init(), before the console. QEMU models neither the PLL output gates nor the reset values, so it never showed this. - clock_init_safe() follows boot0's sunxi_board_pll_init() in order: the CPU dividers while still on OSC24M, PLL_PERI0 read-modify-written instead of overwritten and with its output enable, the bus factors before their source, and PLL_CPUX with its output enable (bit 27). - SPL stack in SRAM C (0x58000), where boot0 keeps its own; 0x118000 in SRAM A2 was taken from the H6. - NOR images: the eGON image is 32 KiB long, as boot0's is. SPI NOR: the controller node had no spi-max-frequency, so spi-sunxi clamped every transfer to its 1 MHz default and the firmware scan took over 30 s on an empty flash. Now 24 MHz at the controller, 12 MHz on the bus (SF_DEFAULT_SPEED, ENV_SPI_MAX_HZ). --- arch/arm/dts/sun8i-v831-openipc.dts | 2 + .../include/asm/arch-sunxi/clock_sun50i_h6.h | 3 - arch/arm/mach-sunxi/clock_sun50i_h6.c | 107 +++++++++++++++--- build.sh | 16 +++ openipc/v831_config | 8 +- openipc/v833_config | 8 +- 6 files changed, 123 insertions(+), 21 deletions(-) diff --git a/arch/arm/dts/sun8i-v831-openipc.dts b/arch/arm/dts/sun8i-v831-openipc.dts index 2bac4eb2f1de..46ae5d77ee57 100644 --- a/arch/arm/dts/sun8i-v831-openipc.dts +++ b/arch/arm/dts/sun8i-v831-openipc.dts @@ -58,6 +58,8 @@ &spi0 { status = "okay"; + /* The controller's own limit: without it the driver clamps to 1 MHz */ + spi-max-frequency = <24000000>; flash@0 { #address-cells = <1>; diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h index 3ede57028870..260d5e9e240f 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h @@ -278,9 +278,6 @@ struct sunxi_ccm_reg { /* apb1 bit field */ #define CCM_APB1_DEFAULT 0x03000102 #elif CONFIG_MACH_SUN8I_V831 -/* PLL_PERI0(2X) 1200 MHz, as boot0 sets it */ -#define CCM_PLL6_DEFAULT 0xa0003100 - /* psi_ahb1_ahb2 bit field: PLL_PERI0(1X) / 3 = 200 MHz */ #define CCM_PSI_AHB1_AHB2_DEFAULT 0x03000002 diff --git a/arch/arm/mach-sunxi/clock_sun50i_h6.c b/arch/arm/mach-sunxi/clock_sun50i_h6.c index aec5c6dc69fe..ccbde51bda0b 100644 --- a/arch/arm/mach-sunxi/clock_sun50i_h6.c +++ b/arch/arm/mach-sunxi/clock_sun50i_h6.c @@ -3,8 +3,80 @@ #include #include #include +#include #ifdef CONFIG_SPL_BUILD +#ifdef CONFIG_MACH_SUN8I_V831 +/* boot0's wait_pll_stable(): retry the enable until the PLL locks */ +static void v831_pll_lock(u32 *reg) +{ + unsigned int i; + + while (1) { + setbits_le32(reg, BIT(31) | BIT(29)); + for (i = 0; i < 0x7ff; i++) + if (readl(reg) & BIT(28)) + return; + clrbits_le32(reg, BIT(31) | BIT(29)); + } +} + +/* + * The order of boot0's sunxi_board_pll_init(). PLL_PERI0 is + * read-modify-written: its reset value holds bits a plain write would + * clear, and the buses stop when they move onto it without them. The + * timer is not running yet, hence sdelay(). + */ +void clock_init_safe(void) +{ + struct sunxi_ccm_reg *const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + struct sunxi_prcm_reg *const prcm = + (struct sunxi_prcm_reg *)SUNXI_PRCM_BASE; + + /* the analog (ADDA) supply and the calibration circuit */ + setbits_le32(&prcm->sys_pwroff_gating, 0x10); + setbits_le32(&prcm->res_cal_ctrl, 1); + /* PLL_CPUX tuning step */ + clrsetbits_le32(SUNXI_CCM_BASE + 0x400, 0x7 << 28, 0x1 << 28); + + /* the CPU's dividers first, while still on OSC24M */ + writel(0x302, &ccm->cpu_axi_cfg); + sdelay(0x100); + clock_set_pll1(408000000); + + /* PLL_PERI0: lock at N=0x4f, then settle at N=0x31, 1200 MHz (2X) */ + clrbits_le32(&ccm->psi_ahb1_ahb2_cfg, 0x3 << 24); + clrbits_le32(&ccm->pll6_cfg, BIT(31)); + clrsetbits_le32(&ccm->pll6_cfg, (0xff << 8) | 0x3, 0x4f << 8); + sdelay(0x400); + setbits_le32(&ccm->pll6_cfg, BIT(27)); /* output enable */ + v831_pll_lock(&ccm->pll6_cfg); + sdelay(0x400); + clrbits_le32(&ccm->pll6_cfg, BIT(29)); + clrsetbits_le32(&ccm->pll6_cfg, (0xff << 8) | 0x3, 0x31 << 8); + setbits_le32(&ccm->pll6_cfg, BIT(29)); + sdelay(0x400); + while (!(readl(&ccm->pll6_cfg) & CCM_PLL6_LOCK)) + ; + sdelay(0x800); + + /* the buses: the factors, then the source */ + writel(CCM_PSI_AHB1_AHB2_DEFAULT & ~(0x3 << 24), + &ccm->psi_ahb1_ahb2_cfg); + setbits_le32(&ccm->psi_ahb1_ahb2_cfg, 0x3 << 24); + sdelay(0x100); + writel(CCM_AHB3_DEFAULT & ~(0x3 << 24), &ccm->ahb3_cfg); + setbits_le32(&ccm->ahb3_cfg, 0x3 << 24); + writel(CCM_APB1_DEFAULT & ~(0x3 << 24), &ccm->apb1_cfg); + setbits_le32(&ccm->apb1_cfg, 0x3 << 24); + sdelay(0x100); + + /* the MBUS runs at a quarter of the DRAM clock: no mux */ + writel(MBUS_RESET, &ccm->mbus_cfg); + sdelay(0x100); +} +#else void clock_init_safe(void) { struct sunxi_ccm_reg *const ccm = @@ -18,13 +90,6 @@ void clock_init_safe(void) setbits_le32(&prcm->res_cal_ctrl, 2); } - if (IS_ENABLED(CONFIG_MACH_SUN8I_V831)) { - /* boot0: the analog (ADDA) supply and the calibration circuit */ - setbits_le32(&prcm->sys_pwroff_gating, 0x10); - /* boot0: PLL_CPUX tuning step */ - clrsetbits_le32(SUNXI_CCM_BASE + 0x400, 0x7 << 28, 0x1 << 28); - } - if (!IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) { clrbits_le32(&prcm->res_cal_ctrl, 1); setbits_le32(&prcm->res_cal_ctrl, 1); @@ -44,7 +109,6 @@ void clock_init_safe(void) ; clrsetbits_le32(&ccm->cpu_axi_cfg, CCM_CPU_AXI_APB_MASK | CCM_CPU_AXI_AXI_MASK, - IS_ENABLED(CONFIG_MACH_SUN8I_V831) ? 0x302 : CCM_CPU_AXI_DEFAULT_FACTORS); writel(CCM_PSI_AHB1_AHB2_DEFAULT, &ccm->psi_ahb1_ahb2_cfg); @@ -53,18 +117,13 @@ void clock_init_safe(void) #endif writel(CCM_APB1_DEFAULT, &ccm->apb1_cfg); - if (IS_ENABLED(CONFIG_MACH_SUN8I_V831)) { - /* the MBUS runs at a quarter of the DRAM clock: no mux */ - writel(MBUS_RESET, &ccm->mbus_cfg); - return; - } - /* * The mux and factor are set, but the clock will be enabled in * DRAM initialization code. */ writel(MBUS_CLK_SRC_PLL6X2 | MBUS_CLK_M(3), &ccm->mbus_cfg); } +#endif /* CONFIG_MACH_SUN8I_V831 */ #endif void clock_init_uart(void) @@ -103,6 +162,26 @@ void clock_set_pll1(unsigned int clk) val |= CCM_CPU_AXI_MUX_OSC24M; writel(val, &ccm->cpu_axi_cfg); + if (IS_ENABLED(CONFIG_MACH_SUN8I_V831)) { + /* boot0's order: factors, lock enable, PLL enable, wait */ + sdelay(0x100); + writel(CCM_PLL1_OUT_EN | CCM_PLL1_CLOCK_TIME_2 | + CCM_PLL1_CTRL_N(clk / 24000000), &ccm->pll1_cfg); + setbits_le32(&ccm->pll1_cfg, CCM_PLL1_LOCK_EN); + setbits_le32(&ccm->pll1_cfg, CCM_PLL1_CTRL_EN); + while (!(readl(&ccm->pll1_cfg) & CCM_PLL1_LOCK)) + ; + sdelay(0x100); + clrbits_le32(&ccm->pll1_cfg, CCM_PLL1_LOCK_EN); + + val = readl(&ccm->cpu_axi_cfg); + val &= ~CCM_CPU_AXI_MUX_MASK; + val |= CCM_CPU_AXI_MUX_PLL_CPUX; + writel(val, &ccm->cpu_axi_cfg); + sdelay(0x100); + return; + } + /* clk = 24*n/p, p is ignored if clock is >288MHz */ writel(CCM_PLL1_CTRL_EN | CCM_PLL1_LOCK_EN | CCM_PLL1_CLOCK_TIME_2 | #ifdef CONFIG_MACH_SUN50I_H616 diff --git a/build.sh b/build.sh index 4adae07c5c8b..63e35f5eff60 100755 --- a/build.sh +++ b/build.sh @@ -54,6 +54,22 @@ build_v83x() { return 1 fi + # V831/V833 on NOR: an eGON image as long as the vendor's boot0 + # (32 KiB, the length booted on silicon); mkimage pads only to 8 KiB + case "${soc}-${kind}" in + v831-nor|v833-nor) + splbin=${builddir}/spl/u-boot-spl.bin + { cat "${splbin}" + head -c $((UBOOT_OFFSET - 0x60 - $(stat -c %s "${splbin}"))) \ + /dev/zero | tr '\0' '\377'; } > "${splbin}.pad" || return 1 + "${builddir}/tools/mkimage" -A arm -T sunxi_egon \ + -a "$(sed -n 's/^CONFIG_SPL_TEXT_BASE=//p' "${builddir}/.config")" \ + -n "$(sed -n 's/^CONFIG_DEFAULT_DEVICE_TREE="\(.*\)"$/\1/p' \ + "${builddir}/.config")" \ + -d "${splbin}.pad" "${spl}" >/dev/null || return 1 + ;; + esac + text=$(sed -n 's/^CONFIG_SYS_TEXT_BASE=//p' "${builddir}/.config") xz --format=lzma -9 -c "${builddir}/u-boot.bin" \ > "${builddir}/u-boot.bin.lzma" || return 1 diff --git a/openipc/v831_config b/openipc/v831_config index ee1b8b71de3d..9f54400eca22 100644 --- a/openipc/v831_config +++ b/openipc/v831_config @@ -4,8 +4,12 @@ CONFIG_PRODUCT_SOC="v831" CONFIG_MACH_SUN8I_V831=y CONFIG_DEFAULT_DEVICE_TREE="sun8i-v831-openipc" CONFIG_IDENT_STRING=" Allwinner V83x" -# SPL stack: the end of SRAM A2, as on the H6 -CONFIG_SPL_STACK=0x118000 +# SPL stack: top of SRAM C, the region vendor boot0 stacks in (0x58ffc) +CONFIG_SPL_STACK=0x58000 +# SPI NOR at 12 MHz (24 MHz HOSC / 2), not the 1 MHz default: the +# firmware scan reads up to 3.7 MiB of flash on every boot +CONFIG_SF_DEFAULT_SPEED=12000000 +CONFIG_ENV_SPI_MAX_HZ=12000000 # Kernel hand-off: non-secure, PSCI 1.0 from U-Boot's own monitor, as # the vendor kernels expect from OP-TEE CONFIG_ARMV7_NONSEC=y diff --git a/openipc/v833_config b/openipc/v833_config index 1d2b4f613609..9cbd9a323c8b 100644 --- a/openipc/v833_config +++ b/openipc/v833_config @@ -5,8 +5,12 @@ CONFIG_PRODUCT_SOC="v833" CONFIG_MACH_SUN8I_V831=y CONFIG_DEFAULT_DEVICE_TREE="sun8i-v831-openipc" CONFIG_IDENT_STRING=" Allwinner V83x" -# SPL stack: the end of SRAM A2, as on the H6 -CONFIG_SPL_STACK=0x118000 +# SPL stack: top of SRAM C, the region vendor boot0 stacks in (0x58ffc) +CONFIG_SPL_STACK=0x58000 +# SPI NOR at 12 MHz (24 MHz HOSC / 2), not the 1 MHz default: the +# firmware scan reads up to 3.7 MiB of flash on every boot +CONFIG_SF_DEFAULT_SPEED=12000000 +CONFIG_ENV_SPI_MAX_HZ=12000000 # Kernel hand-off: non-secure, PSCI 1.0 from U-Boot's own monitor, as # the vendor kernels expect from OP-TEE CONFIG_ARMV7_NONSEC=y