From 26ee242100f21a2856f58802bbe238dbc18455a8 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:07:14 +0000 Subject: [PATCH 1/4] sunxi: OpenIPC: the SD boot source and the env fallback on every SoC The -mmc images of the V3s and V851s hung before the console after an SD boot: the SPI flash fallback in env_get_location() was the V831's only, so env_init() found no location. It now applies with OPENIPC, whose images all keep the environment in the SPI NOR; other sunxi boards are unaffected, as before. v83x_boot_source() (source=fatload mmc 0 after an SD boot, not saved) moves to board.c as openipc_boot_source(), for every SoC. --- board/sunxi/board.c | 28 +++++++++++++++++++++------- board/sunxi/v83x.c | 17 ----------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/board/sunxi/board.c b/board/sunxi/board.c index 6b0ed4e97816..27566224c793 100644 --- a/board/sunxi/board.c +++ b/board/sunxi/board.c @@ -54,7 +54,6 @@ void v83x_board_init(void); void v83x_kernel_fdt(void); void v83x_ft_board_setup(void *blob); -void v83x_boot_source(void); DECLARE_GLOBAL_DATA_PTR; @@ -186,10 +185,10 @@ enum env_location env_get_location(enum env_operation op, int prio) if (IS_ENABLED(CONFIG_ENV_IS_IN_UBI)) return ENVL_UBI; /* - * The V831/V833 images keep it in the SPI NOR alone, also + * The OpenIPC images keep it in the SPI NOR alone, also * when the SPL and U-Boot came from an SD card */ - if (IS_ENABLED(CONFIG_MACH_SUN8I_V831) && + if (IS_ENABLED(CONFIG_OPENIPC) && IS_ENABLED(CONFIG_ENV_IS_IN_SPI_FLASH)) return ENVL_SPI_FLASH; } @@ -201,6 +200,23 @@ enum env_location env_get_location(enum env_operation op, int prio) static void mmc_pinmux_setup(int sdc); #endif +#ifdef CONFIG_OPENIPC +/* + * Booted from an SD card (the -mmc images): that card is where the + * unbricking files are, so ub/uk/ur read them from it, as after srcmmc. + * Set for this boot only, not saved: the next boot from the SPI NOR keeps + * the environment's source. + */ +static void openipc_boot_source(void) +{ + if (sunxi_get_boot_device() != BOOT_DEVICE_MMC1) + return; + + printf("OpenIPC: booted from the SD card, update source: fatload mmc 0\n"); + env_set("source", "fatload mmc 0"); +} +#endif + /* add board specific code here */ int board_init(void) { @@ -881,11 +897,9 @@ int misc_init_r(void) #ifdef CONFIG_OPENIPC openipc_helper(); -#endif - /* after the helper, which may save the default environment */ - if (IS_ENABLED(CONFIG_MACH_SUN8I_V831)) - v83x_boot_source(); + openipc_boot_source(); +#endif return 0; } diff --git a/board/sunxi/v83x.c b/board/sunxi/v83x.c index 6e4020e6bff3..1434b836572a 100644 --- a/board/sunxi/v83x.c +++ b/board/sunxi/v83x.c @@ -9,8 +9,6 @@ #include #include #include -#include -#include #include #include #include @@ -55,21 +53,6 @@ void v83x_kernel_fdt(void) env_set_hex("fdtaddr", (ulong)fdt); } -/* - * Booted from an SD card (the -mmc images): that card is where the - * unbricking files are, so ub/uk/ur read them from it, as after srcmmc. - * Set for this boot only, not saved: the next boot from the SPI NOR keeps - * the environment's source. - */ -void v83x_boot_source(void) -{ - if (sunxi_get_boot_device() != BOOT_DEVICE_MMC1) - return; - - printf("V83x: booted from the SD card, update source: fatload mmc 0\n"); - env_set("source", "fatload mmc 0"); -} - /* What the vendor U-Boot sets at boot besides /memory and /chosen */ void v83x_ft_board_setup(void *blob) { From 5a55ceac18ca321aafc0a550cc0c21c8e484947f Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:07:27 +0000 Subject: [PATCH 2/4] arm: sunxi: add the V3s and V851s OpenIPC images Two more Allwinner camera SoCs, built as the V831/V833 are: an SPL and an LZMA U-Boot in the 256 KiB boot partition, the OpenIPC environment in the SPI NOR, -nor and -mmc images (build.sh v3s v851s). V3s: mainline's MACH_SUN8I_V3S and its DRAM driver as they are. Added: the SPI NOR SPL (the H3's SPI0), the EMAC and its internal PHY (sun8i_emac's V3s compatible, the EMAC/EPHY gates and resets in clk_v3s.c), sun8i-v3s-openipc.dts. V851s: the D1/T113 generation backported from mainline (2023-2024) as far as U-Boot uses it, alongside the SUN50I_GEN_H6 conditions as mainline has them: - SUNXI_GEN_NCAT2 and its memory map (cpu_sunxi_ncat2.h); clocks (PLL_CPU LDO, PLL_PERI0 P0, no AHB3, no resistor calibration); MMC calibration and new mode; the watchdog's 0x16AA key; - SUNXI_NEW_PINCTRL: PIO banks 0x30 apart, 4-bit drive fields; - the SPL's SPI0 at 0x04025000 on PC0-PC5, without the controller divider; spi-sunxi's R329 variant (no divider, normal sampling); - DRAM: dram_sun20i_d1.c as OpenIPC's hardware-tested V851s U-Boot (mainline 2024.10) carries it, non-DM, with its parameters as the Kconfig defaults; - sun8i-v851s.dtsi with what U-Boot uses (the CCU's gates are the H6's); the kernel is entered in HYP with PSCI from psci_v831.c. Shared: include/configs/v83x.h becomes openipc-sunxi.h (the DDR size per SoC); the SoC, device tree, PSCI and SPL stack move to the per-SoC fragments; ATAGs are built in for the kernels with an appended device tree; BINMAN is off with OPENIPC. Under qemu-bench every SoC's -nor and -mmc image passes the two-boot smoke test and reset; the SD unbricking path (run ub, then the NOR alone) works on the V3s and V851s. The V851s's DRAM, CCU and PRCM registers equal those left by the published OpenIPC u-boot-v851s-nor.bin (SMHC0's clock aside: this U-Boot probes the card), and the published OpenIPC V851s kernel and rootfs boot to a shell from the NOR and reboot. --- arch/arm/Kconfig | 2 +- arch/arm/cpu/armv7/sunxi/Makefile | 2 +- arch/arm/dts/Makefile | 5 +- arch/arm/dts/sun8i-v3s-openipc.dts | 53 + arch/arm/dts/sun8i-v851s-openipc.dts | 47 + arch/arm/dts/sun8i-v851s.dtsi | 150 ++ arch/arm/include/asm/arch-sunxi/clock.h | 2 +- .../include/asm/arch-sunxi/clock_sun50i_h6.h | 8 + arch/arm/include/asm/arch-sunxi/cpu.h | 2 + .../include/asm/arch-sunxi/cpu_sunxi_ncat2.h | 56 + arch/arm/include/asm/arch-sunxi/dram.h | 2 + arch/arm/include/asm/arch-sunxi/gpio.h | 21 + arch/arm/include/asm/arch-sunxi/mmc.h | 3 +- arch/arm/include/asm/arch-sunxi/prcm.h | 2 +- arch/arm/include/asm/arch-sunxi/timer.h | 3 +- arch/arm/include/asm/arch-sunxi/watchdog.h | 10 +- arch/arm/mach-sunxi/Kconfig | 83 +- arch/arm/mach-sunxi/Makefile | 2 + arch/arm/mach-sunxi/board.c | 7 +- arch/arm/mach-sunxi/clock_sun50i_h6.c | 18 +- arch/arm/mach-sunxi/cpu_info.c | 2 + arch/arm/mach-sunxi/dram_sun20i_d1.c | 1496 +++++++++++++++++ arch/arm/mach-sunxi/dram_sun20i_d1.h | 209 +++ arch/arm/mach-sunxi/spl_spi_sunxi.c | 44 +- build.sh | 4 +- common/spl/Kconfig | 4 +- drivers/clk/sunxi/Kconfig | 2 +- drivers/clk/sunxi/clk_sunxi.c | 2 + drivers/clk/sunxi/clk_v3s.c | 4 + drivers/mmc/sunxi_mmc.c | 12 +- drivers/net/sun8i_emac.c | 1 + drivers/pinctrl/sunxi/Kconfig | 5 + drivers/pinctrl/sunxi/pinctrl-sunxi.c | 22 + drivers/spi/spi-sunxi.c | 33 +- include/configs/openipc-common.h | 7 +- include/configs/{v83x.h => openipc-sunxi.h} | 14 +- openipc/openipc_config | 18 +- openipc/v3s_config | 9 + openipc/v831_config | 11 + openipc/v833_config | 11 + openipc/v851s_config | 15 + 41 files changed, 2351 insertions(+), 52 deletions(-) create mode 100644 arch/arm/dts/sun8i-v3s-openipc.dts create mode 100644 arch/arm/dts/sun8i-v851s-openipc.dts create mode 100644 arch/arm/dts/sun8i-v851s.dtsi create mode 100644 arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h create mode 100644 arch/arm/mach-sunxi/dram_sun20i_d1.c create mode 100644 arch/arm/mach-sunxi/dram_sun20i_d1.h rename include/configs/{v83x.h => openipc-sunxi.h} (66%) create mode 100644 openipc/v3s_config create mode 100644 openipc/v851s_config diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index c119552ad539..a89612890369 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1144,7 +1144,7 @@ config ARCH_SOCFPGA config ARCH_SUNXI bool "Support sunxi (Allwinner) SoCs" - select BINMAN if !MACH_SUN8I_V831 + select BINMAN if !OPENIPC select CMD_GPIO select CMD_MMC if MMC select CMD_USB if DISTRO_DEFAULTS && USB_HOST diff --git a/arch/arm/cpu/armv7/sunxi/Makefile b/arch/arm/cpu/armv7/sunxi/Makefile index a2be669cad63..5ca080176ccb 100644 --- a/arch/arm/cpu/armv7/sunxi/Makefile +++ b/arch/arm/cpu/armv7/sunxi/Makefile @@ -13,7 +13,7 @@ obj-$(CONFIG_MACH_SUN6I) += sram.o obj-$(CONFIG_MACH_SUN8I) += sram.o ifndef CONFIG_SPL_BUILD -ifdef CONFIG_MACH_SUN8I_V831 +ifneq ($(CONFIG_MACH_SUN8I_V831)$(CONFIG_MACH_SUN8I_V851S),) obj-$(CONFIG_ARMV7_PSCI) += psci_v831.o else obj-$(CONFIG_ARMV7_PSCI) += psci.o diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index c83f4fe32e0e..3bff0f61f3eb 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -685,9 +685,12 @@ dtb-$(CONFIG_MACH_SUN8I_V3S) += \ sun8i-s3-elimo-initium.dtb \ sun8i-s3-pinecube.dtb \ sun8i-v3-sl631-imx179.dtb \ - sun8i-v3s-licheepi-zero.dtb + sun8i-v3s-licheepi-zero.dtb \ + sun8i-v3s-openipc.dtb dtb-$(CONFIG_MACH_SUN8I_V831) += \ sun8i-v831-openipc.dtb +dtb-$(CONFIG_MACH_SUN8I_V851S) += \ + sun8i-v851s-openipc.dtb dtb-$(CONFIG_MACH_SUN50I_H5) += \ sun50i-h5-bananapi-m2-plus.dtb \ sun50i-h5-emlid-neutis-n5-devboard.dtb \ diff --git a/arch/arm/dts/sun8i-v3s-openipc.dts b/arch/arm/dts/sun8i-v3s-openipc.dts new file mode 100644 index 000000000000..e9390241b1a4 --- /dev/null +++ b/arch/arm/dts/sun8i-v3s-openipc.dts @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * A V3s IP camera: console on UART0 (PB8/PB9), SPI NOR on SPI0 (PC0-PC3), + * the SD card on MMC0 (PF0-PF5), the EMAC on the SoC's own PHY. + */ + +/dts-v1/; + +#include "sun8i-v3s.dtsi" + +/ { + model = "OpenIPC V3s"; + compatible = "openipc,v3s", "allwinner,sun8i-v3s"; + + aliases { + ethernet0 = &emac; + serial0 = &uart0; + spi0 = &spi0; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; +}; + +&emac { + allwinner,leds-active-low; + status = "okay"; +}; + +&mmc0 { + broken-cd; + bus-width = <4>; + status = "okay"; +}; + +&spi0 { + status = "okay"; + + flash@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <50000000>; + }; +}; + +&uart0 { + pinctrl-0 = <&uart0_pb_pins>; + pinctrl-names = "default"; + status = "okay"; +}; diff --git a/arch/arm/dts/sun8i-v851s-openipc.dts b/arch/arm/dts/sun8i-v851s-openipc.dts new file mode 100644 index 000000000000..b3ff31a96855 --- /dev/null +++ b/arch/arm/dts/sun8i-v851s-openipc.dts @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * A V851s IP camera as OpenIPC boots it: console on UART0 (PH9/PH10), + * SPI NOR on SPI0 (PC0-PC5), the SD card on SMHC0 (PF0-PF5). + */ + +/dts-v1/; + +#include "sun8i-v851s.dtsi" + +/ { + model = "OpenIPC V851s"; + compatible = "openipc,v851s", "allwinner,sun8i-v851s"; + + aliases { + serial0 = &uart0; + spi0 = &spi0; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; +}; + +&mmc0 { + bus-width = <4>; + broken-cd; + status = "okay"; +}; + +&spi0 { + status = "okay"; + + flash@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <50000000>; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_ph_pins>; + status = "okay"; +}; diff --git a/arch/arm/dts/sun8i-v851s.dtsi b/arch/arm/dts/sun8i-v851s.dtsi new file mode 100644 index 000000000000..d8e86a89d99a --- /dev/null +++ b/arch/arm/dts/sun8i-v851s.dtsi @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Allwinner V851s (sun8iw21p1): one Cortex-A7 with the D1/T113's + * peripherals, clocks and memory map. Only what U-Boot uses: the D1's + * CCU has the H6's bus gates and module clocks at the H6's offsets, so + * the clock and reset indices are the H6's, as on the V831. + */ + +#include +#include +#include + +/ { + interrupt-parent = <&gic>; + #address-cells = <1>; + #size-cells = <1>; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + compatible = "arm,cortex-a7"; + device_type = "cpu"; + reg = <0>; + }; + }; + + osc24M: osc24M_clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <24000000>; + clock-output-names = "osc24M"; + }; + + osc32k: osc32k_clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <32768>; + clock-output-names = "osc32k"; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = , + , + , + ; + clock-frequency = <24000000>; + arm,cpu-registers-not-fw-configured; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + pio: pinctrl@2000000 { + compatible = "allwinner,sun8i-v851s-pinctrl"; + reg = <0x02000000 0x800>; + clocks = <&ccu CLK_APB1>, <&osc24M>, <&osc32k>; + clock-names = "apb", "hosc", "losc"; + gpio-controller; + #gpio-cells = <3>; + interrupt-controller; + #interrupt-cells = <3>; + + mmc0_pins: mmc0-pins { + pins = "PF0", "PF1", "PF2", "PF3", "PF4", "PF5"; + function = "mmc0"; + drive-strength = <30>; + bias-pull-up; + }; + + spi0_pins: spi0-pins { + pins = "PC0", "PC1", "PC2", "PC3", "PC4", "PC5"; + function = "spi0"; + }; + + uart0_ph_pins: uart0-ph-pins { + pins = "PH9", "PH10"; + function = "uart0"; + }; + }; + + ccu: clock-controller@2001000 { + compatible = "allwinner,sun8i-v851s-ccu"; + reg = <0x02001000 0x1000>; + clocks = <&osc24M>, <&osc32k>; + clock-names = "hosc", "losc"; + #clock-cells = <1>; + #reset-cells = <1>; + }; + + uart0: serial@2500000 { + compatible = "snps,dw-apb-uart"; + reg = <0x02500000 0x400>; + interrupts = ; + reg-shift = <2>; + reg-io-width = <4>; + clocks = <&ccu CLK_BUS_UART0>; + resets = <&ccu RST_BUS_UART0>; + status = "disabled"; + }; + + gic: interrupt-controller@3021000 { + compatible = "arm,gic-400"; + reg = <0x03021000 0x1000>, + <0x03022000 0x2000>, + <0x03024000 0x2000>, + <0x03026000 0x2000>; + interrupts = ; + interrupt-controller; + #interrupt-cells = <3>; + }; + + mmc0: mmc@4020000 { + compatible = "allwinner,sun8i-v851s-mmc", + "allwinner,sun50i-a100-mmc"; + reg = <0x04020000 0x1000>; + clocks = <&ccu CLK_BUS_MMC0>, <&ccu CLK_MMC0>; + clock-names = "ahb", "mmc"; + resets = <&ccu RST_BUS_MMC0>; + reset-names = "ahb"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&mmc0_pins>; + max-frequency = <150000000>; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + }; + + spi0: spi@4025000 { + compatible = "allwinner,sun8i-v851s-spi", + "allwinner,sun50i-r329-spi"; + reg = <0x04025000 0x1000>; + interrupts = ; + clocks = <&ccu CLK_BUS_SPI0>, <&ccu CLK_SPI0>; + clock-names = "ahb", "mod"; + resets = <&ccu RST_BUS_SPI0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins>; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + }; + }; +}; diff --git a/arch/arm/include/asm/arch-sunxi/clock.h b/arch/arm/include/asm/arch-sunxi/clock.h index 2cfd5407423d..3d34261b0e59 100644 --- a/arch/arm/include/asm/arch-sunxi/clock.h +++ b/arch/arm/include/asm/arch-sunxi/clock.h @@ -16,7 +16,7 @@ /* clock control module regs definition */ #if defined(CONFIG_MACH_SUN8I_A83T) #include -#elif defined(CONFIG_SUN50I_GEN_H6) +#elif defined(CONFIG_SUN50I_GEN_H6) || defined(CONFIG_SUNXI_GEN_NCAT2) #include #elif defined(CONFIG_MACH_SUN6I) || defined(CONFIG_MACH_SUN8I) || \ defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUNIV) 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 b21c0bccaa44..3ede57028870 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h @@ -231,6 +231,7 @@ struct sunxi_ccm_reg { #define CCM_PLL1_LOCK_EN BIT(29) #define CCM_PLL1_LOCK BIT(28) #define CCM_PLL1_OUT_EN BIT(27) +#define CCM_PLL1_LDO_EN BIT(30) /* NCAT2 */ #define CCM_PLL1_CLOCK_TIME_2 (2 << 24) #define CCM_PLL1_CTRL_P(p) ((p) << 16) #define CCM_PLL1_CTRL_N(n) (((n) - 1) << 8) @@ -254,6 +255,8 @@ struct sunxi_ccm_reg { #define CCM_PLL6_CTRL_DIV1_MASK (0x1 << CCM_PLL6_CTRL_DIV1_SHIFT) #define CCM_PLL6_CTRL_DIV2_SHIFT 1 #define CCM_PLL6_CTRL_DIV2_MASK (0x1 << CCM_PLL6_CTRL_DIV2_SHIFT) +#define CCM_PLL6_CTRL_P0_SHIFT 16 /* NCAT2 */ +#define CCM_PLL6_CTRL_P0_MASK (0x7 << CCM_PLL6_CTRL_P0_SHIFT) /* cpu_axi bit field*/ #define CCM_CPU_AXI_MUX_MASK (0x3 << 24) @@ -286,6 +289,11 @@ struct sunxi_ccm_reg { /* apb1 bit field: 100 MHz */ #define CCM_APB1_DEFAULT 0x03000102 +#elif CONFIG_MACH_SUN8I_V851S +/* OpenIPC's hardware-tested V851s U-Boot (mainline 2024.10)'s values */ +#define CCM_PLL6_DEFAULT 0xe8216310 +#define CCM_PSI_AHB1_AHB2_DEFAULT 0x03000002 +#define CCM_APB1_DEFAULT 0x03000102 #elif CONFIG_MACH_SUN50I_H616 #define CCM_PLL6_DEFAULT 0xa8003100 diff --git a/arch/arm/include/asm/arch-sunxi/cpu.h b/arch/arm/include/asm/arch-sunxi/cpu.h index b08f2023748c..768c6572d6b7 100644 --- a/arch/arm/include/asm/arch-sunxi/cpu.h +++ b/arch/arm/include/asm/arch-sunxi/cpu.h @@ -10,6 +10,8 @@ #include #elif defined(CONFIG_SUN50I_GEN_H6) #include +#elif defined(CONFIG_SUNXI_GEN_NCAT2) +#include #else #include #endif diff --git a/arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h b/arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h new file mode 100644 index 000000000000..e0de8a56a6d9 --- /dev/null +++ b/arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * The D1/T113 ("NCAT2") memory map: V851s here. From mainline's + * cpu_sunxi_ncat2.h (2023), with the names this tree's sunxi code uses. + */ + +#ifndef _SUNXI_CPU_SUNXI_NCAT2_H +#define _SUNXI_CPU_SUNXI_NCAT2_H + +#define SUNXI_SRAM_A1_BASE CONFIG_SUNXI_SRAM_ADDRESS + +#define SUNXI_PIO_BASE 0x02000000 +#define SUNXI_CCM_BASE 0x02001000 +#define SUNXI_TIMER_BASE 0x02050000 + +#define SUNXI_UART0_BASE 0x02500000 +#define SUNXI_UART1_BASE 0x02500400 +#define SUNXI_UART2_BASE 0x02500800 +#define SUNXI_UART3_BASE 0x02500C00 + +#define SUNXI_TWI0_BASE 0x02502000 +#define SUNXI_TWI1_BASE 0x02502400 +#define SUNXI_TWI2_BASE 0x02502800 +#define SUNXI_TWI3_BASE 0x02502C00 + +#define SUNXI_SRAMC_BASE 0x03000000 +/* SID address space starts at 0x03006000, but e-fuse is at offset 0x200 */ +#define SUNXI_SIDC_BASE 0x03006000 +#define SUNXI_SID_BASE 0x03006200 +#define SUNXI_GIC400_BASE 0x03020000 + +#define SUNXI_DRAM_COM_BASE 0x03102000 +#define SUNXI_DRAM_PHY_BASE 0x03103000 + +#define SUNXI_MMC0_BASE 0x04020000 +#define SUNXI_MMC1_BASE 0x04021000 +#define SUNXI_MMC2_BASE 0x04022000 + +#define SUNXI_SPI0_BASE 0x04025000 +#define SUNXI_GMAC_BASE 0x04500000 + +#define SUNXI_R_CPUCFG_BASE 0x07000400 +#define SUNXI_PRCM_BASE 0x07010000 +#define SUNXI_R_PIO_BASE 0x07022000 +#define SUNXI_R_UART_BASE 0x07080000 +#define SUNXI_RTC_BASE 0x07090000 + +#define SUNXI_CPUCFG_BASE 0x09010000 + +#ifndef __ASSEMBLY__ +void sunxi_board_init(void); +void sunxi_reset(void); +int sunxi_get_sid(unsigned int *sid); +#endif + +#endif /* _SUNXI_CPU_SUNXI_NCAT2_H */ diff --git a/arch/arm/include/asm/arch-sunxi/dram.h b/arch/arm/include/asm/arch-sunxi/dram.h index 8eb3646edb18..38fb09aeea88 100644 --- a/arch/arm/include/asm/arch-sunxi/dram.h +++ b/arch/arm/include/asm/arch-sunxi/dram.h @@ -35,6 +35,8 @@ #include #elif defined(CONFIG_MACH_SUN8I_V831) #include +#elif defined(CONFIG_MACH_SUN8I_V851S) +/* dram_sun20i_d1.c keeps its structures in its own header */ #else #include #endif diff --git a/arch/arm/include/asm/arch-sunxi/gpio.h b/arch/arm/include/asm/arch-sunxi/gpio.h index a287307701a5..c025bc7994eb 100644 --- a/arch/arm/include/asm/arch-sunxi/gpio.h +++ b/arch/arm/include/asm/arch-sunxi/gpio.h @@ -52,12 +52,23 @@ #define SUNXI_GPIO_M 12 #define SUNXI_GPIO_N 13 +#ifdef CONFIG_SUNXI_NEW_PINCTRL +/* D1/T113 (NCAT2): four drive registers, 4 bits a pin; banks 0x30 apart */ +struct sunxi_gpio { + u32 cfg[4]; + u32 dat; + u32 drv[4]; + u32 pull[2]; + u32 res; +}; +#else struct sunxi_gpio { u32 cfg[4]; u32 dat; u32 drv[2]; u32 pull[2]; }; +#endif /* gpio interrupt control */ struct sunxi_gpio_int { @@ -69,7 +80,11 @@ struct sunxi_gpio_int { struct sunxi_gpio_reg { struct sunxi_gpio gpio_bank[SUNXI_GPIO_BANKS]; +#ifdef CONFIG_SUNXI_NEW_PINCTRL + u8 res[0x50]; +#else u8 res[0xbc]; +#endif struct sunxi_gpio_int gpio_int; }; @@ -86,8 +101,13 @@ struct sunxi_gpio_reg { #define GPIO_CFG_INDEX(pin) (((pin) & 0x1f) >> 3) #define GPIO_CFG_OFFSET(pin) ((((pin) & 0x1f) & 0x7) << 2) +#ifdef CONFIG_SUNXI_NEW_PINCTRL +#define GPIO_DRV_INDEX(pin) (((pin) & 0x1f) >> 3) +#define GPIO_DRV_OFFSET(pin) ((((pin) & 0x1f) & 0x7) << 2) +#else #define GPIO_DRV_INDEX(pin) (((pin) & 0x1f) >> 4) #define GPIO_DRV_OFFSET(pin) ((((pin) & 0x1f) & 0xf) << 1) +#endif #define GPIO_PULL_INDEX(pin) (((pin) & 0x1f) >> 4) #define GPIO_PULL_OFFSET(pin) ((((pin) & 0x1f) & 0xf) << 1) @@ -188,6 +208,7 @@ enum sunxi_gpio_number { #define SUN50I_H6_GPH_UART0 2 #define SUN50I_H616_GPH_UART0 2 #define SUN8I_V831_GPH_UART0 5 +#define SUN8I_V851S_GPH_UART0 5 #define SUNXI_GPI_SDC3 2 diff --git a/arch/arm/include/asm/arch-sunxi/mmc.h b/arch/arm/include/asm/arch-sunxi/mmc.h index 5daacf10eb10..4663e3a7ede2 100644 --- a/arch/arm/include/asm/arch-sunxi/mmc.h +++ b/arch/arm/include/asm/arch-sunxi/mmc.h @@ -45,7 +45,8 @@ struct sunxi_mmc { u32 chda; /* 0x90 */ u32 cbda; /* 0x94 */ u32 res2[26]; -#if defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) +#if defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) || \ + defined(CONFIG_SUNXI_GEN_NCAT2) u32 res3[17]; u32 samp_dl; u32 res4[46]; diff --git a/arch/arm/include/asm/arch-sunxi/prcm.h b/arch/arm/include/asm/arch-sunxi/prcm.h index 5106076f5e91..c5418cfd28d4 100644 --- a/arch/arm/include/asm/arch-sunxi/prcm.h +++ b/arch/arm/include/asm/arch-sunxi/prcm.h @@ -9,7 +9,7 @@ #define _SUNXI_PRCM_H /* prcm regs definition */ -#if defined(CONFIG_SUN50I_GEN_H6) +#if defined(CONFIG_SUN50I_GEN_H6) || defined(CONFIG_SUNXI_GEN_NCAT2) #include #else #include diff --git a/arch/arm/include/asm/arch-sunxi/timer.h b/arch/arm/include/asm/arch-sunxi/timer.h index bb5626d893bb..71a00f71a0c4 100644 --- a/arch/arm/include/asm/arch-sunxi/timer.h +++ b/arch/arm/include/asm/arch-sunxi/timer.h @@ -76,7 +76,8 @@ struct sunxi_timer_reg { struct sunxi_tgp tgp[4]; u8 res5[8]; u32 cpu_cfg; -#elif defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) +#elif defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) || \ + defined(CONFIG_SUNXI_GEN_NCAT2) u8 res3[16]; struct sunxi_wdog wdog[5]; /* We have 5 watchdogs */ #endif diff --git a/arch/arm/include/asm/arch-sunxi/watchdog.h b/arch/arm/include/asm/arch-sunxi/watchdog.h index 38e2ef2aca3d..0553190c0000 100644 --- a/arch/arm/include/asm/arch-sunxi/watchdog.h +++ b/arch/arm/include/asm/arch-sunxi/watchdog.h @@ -28,8 +28,14 @@ struct sunxi_wdog { #else -#define WDT_CFG_RESET (0x1) -#define WDT_MODE_EN (0x1) +/* NCAT2 (D1, V851s): CFG and MODE take writes with this key only */ +#ifdef CONFIG_SUNXI_GEN_NCAT2 +#define WDT_KEY_FIELD (0x16aa << 16) +#else +#define WDT_KEY_FIELD 0 +#endif +#define WDT_CFG_RESET (WDT_KEY_FIELD | 0x1) +#define WDT_MODE_EN (WDT_KEY_FIELD | 0x1) struct sunxi_wdog { u32 irq_en; /* 0x00 */ diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index 38f19fc8c7a6..71fe9cf71e6f 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -56,6 +56,12 @@ config DRAM_SUN8I_V831 help Select this dram controller driver for the V831/V833 (sun8iw19). +config DRAM_SUN20I_D1 + bool + help + Select this dram controller driver for the D1/R528/T113s/V851s + (from mainline U-Boot's drivers/ram/sunxi/dram_sun20i_d1.c). + if DRAM_SUN50I_H616 config DRAM_SUN50I_H616_WRITE_LEVELING bool "H616 DRAM write leveling" @@ -107,7 +113,7 @@ config AXP_PMIC_BUS config SUNXI_SRAM_ADDRESS hex default 0x10000 if MACH_SUN9I || MACH_SUN50I || MACH_SUN50I_H5 - default 0x20000 if SUN50I_GEN_H6 + default 0x20000 if SUN50I_GEN_H6 || SUNXI_GEN_NCAT2 default 0x0 ---help--- Older Allwinner SoCs have their mask boot ROM mapped just below 4GB, @@ -143,6 +149,20 @@ config SUN50I_GEN_H6 Select this for sunxi SoCs which have H6 like peripherals, clocks and memory map. +config SUNXI_GEN_NCAT2 + bool + select MMC_SUNXI_HAS_NEW_MODE + select SUPPORT_SPL + ---help--- + Select this for sunxi SoCs which have D1 like peripherals, clocks + and memory map. + +config SUNXI_NEW_PINCTRL + bool + ---help--- + The D1 generation's PIO: banks 0x30 apart, four bits a pin for the + drive strength. + config SUNXI_DRAM_DW bool ---help--- @@ -331,6 +351,20 @@ config MACH_SUN8I_V831 peripherals, clocks and memory map, with DDR2 (V831, 64 MiB) or DDR3 (V833, 128 MiB) in the package. +config MACH_SUN8I_V851S + bool "sun8i (Allwinner V851s/V853)" + select CPU_V7A + select CPU_V7_HAS_NONSEC + select CPU_V7_HAS_VIRT + select ARCH_SUPPORT_PSCI + select SPL_ARMV7_SET_CORTEX_SMPEN + select SUNXI_GEN_NCAT2 + select SUNXI_NEW_PINCTRL + select DRAM_SUN20I_D1 + help + The V851s (sun8iw21): one Cortex-A7 with the D1's peripherals, + clocks and memory map, and 64 MiB of DDR2 in the package. + config MACH_SUN9I bool "sun9i (Allwinner A80)" select CPU_V7A @@ -480,7 +514,7 @@ config DRAM_TYPE config DRAM_CLK int "sunxi dram clock speed" default 792 if MACH_SUN9I - default 528 if MACH_SUN8I_V831 + default 528 if MACH_SUN8I_V831 || MACH_SUN8I_V851S default 648 if MACH_SUN8I_R40 default 312 if MACH_SUN6I || MACH_SUN8I default 360 if MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || \ @@ -510,6 +544,7 @@ config DRAM_ZQ default 127 if MACH_SUN7I default 14779 if MACH_SUN8I_V3S default 3065 if MACH_SUN8I_V831 + default 8092665 if MACH_SUN8I_V851S default 3881979 if MACH_SUNXI_H3_H5 || MACH_SUN8I_R40 || MACH_SUN50I_H6 default 4145117 if MACH_SUN9I default 3881915 if MACH_SUN50I @@ -527,6 +562,42 @@ config DRAM_ODT_EN ---help--- Select this to enable dram odt (on die termination). +if DRAM_SUN20I_D1 + +# The defaults are OpenIPC's hardware-tested V851s U-Boot's (64 MiB DDR2) +config DRAM_SUNXI_ODT_EN + hex "DRAM ODT EN parameter" + default 0x0 + +config DRAM_SUNXI_PARA1 + hex "DRAM PARA1 parameter" + default 0xd2 + +config DRAM_SUNXI_MR1 + hex "DRAM MR1 parameter" + default 0x2 + +config DRAM_SUNXI_TPR11 + hex "DRAM TPR11 parameter" + default 0x22 + +config DRAM_SUNXI_TPR12 + hex "DRAM TPR12 parameter" + default 0x77 + +config DRAM_SUNXI_TPR13 + hex "DRAM TPR13 parameter" + default 0x34000100 + help + TPR13 value from vendor DRAM settings. It tells which features + should be configured. + +config SUNXI_DRAM_TYPE + int "DRAM type: 2 DDR2, 3 DDR3, 6 LPDDR2, 7 LPDDR3" + default 2 + +endif + if DRAM_SUN8I_V831 # The vendor's dram_para words (boot0 header, sys_config.fex [dram_para]). # The defaults are the V831's (DDR2 in the package); a V833 board config @@ -690,19 +761,20 @@ config SYS_CLK_FREQ default 912000000 if MACH_SUN7I default 816000000 if MACH_SUN50I || MACH_SUN50I_H5 default 816000000 if MACH_SUN8I_V831 + default 912000000 if MACH_SUN8I_V851S default 1008000000 if MACH_SUN8I default 1008000000 if MACH_SUN9I default 888000000 if MACH_SUN50I_H6 default 1008000000 if MACH_SUN50I_H616 config SYS_CONFIG_NAME + default "openipc-sunxi" if OPENIPC default "suniv" if MACH_SUNIV default "sun4i" if MACH_SUN4I default "sun5i" if MACH_SUN5I default "sun6i" if MACH_SUN6I default "sun7i" if MACH_SUN7I default "sun8i" if MACH_SUN8I - default "v83x" if MACH_SUN8I_V831 default "sun9i" if MACH_SUN9I default "sun50i" if MACH_SUN50I default "sun50i" if MACH_SUN50I_H6 @@ -873,6 +945,7 @@ config VIDEO_SUNXI depends on !MACH_SUN9I depends on !MACH_SUN50I depends on !SUN50I_GEN_H6 + depends on !SUNXI_GEN_NCAT2 select DM_VIDEO select DISPLAY imply VIDEO_DT_SIMPLEFB @@ -1091,14 +1164,14 @@ config SPL_STACK_R_ADDR default 0x4fe00000 if MACH_SUN6I default 0x4fe00000 if MACH_SUN7I default 0x4fe00000 if MACH_SUN8I - default 0x43e00000 if MACH_SUN8I_V831 + default 0x43e00000 if MACH_SUN8I_V831 || MACH_SUN8I_V851S default 0x2fe00000 if MACH_SUN9I default 0x4fe00000 if MACH_SUN50I default 0x4fe00000 if SUN50I_GEN_H6 config SPL_SPI_SUNXI bool "Support for SPI Flash on Allwinner SoCs in SPL" - depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUNXI_H3_H5 || MACH_SUN50I || MACH_SUN8I_R40 || MACH_SUN50I_H6 || MACH_SUNIV || MACH_SUN8I_V831 + depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUNXI_H3_H5 || MACH_SUN50I || MACH_SUN8I_R40 || MACH_SUN50I_H6 || MACH_SUNIV || MACH_SUN8I_V831 || MACH_SUN8I_V3S || MACH_SUN8I_V851S help Enable support for SPI Flash. This option allows SPL to read from sunxi SPI Flash. It uses the same method as the boot ROM, so does diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile index a5610de8ccc5..aa8ed0049ba7 100644 --- a/arch/arm/mach-sunxi/Makefile +++ b/arch/arm/mach-sunxi/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_MACH_SUN8I) += clock_sun6i.o endif obj-$(CONFIG_MACH_SUN9I) += clock_sun9i.o gtbus_sun9i.o obj-$(CONFIG_SUN50I_GEN_H6) += clock_sun50i_h6.o +obj-$(CONFIG_SUNXI_GEN_NCAT2) += clock_sun50i_h6.o ifndef CONFIG_ARM64 obj-y += timer.o endif @@ -46,4 +47,5 @@ obj-$(CONFIG_DRAM_SUN50I_H6) += dram_timings/ obj-$(CONFIG_DRAM_SUN50I_H616) += dram_sun50i_h616.o obj-$(CONFIG_DRAM_SUN50I_H616) += dram_timings/ obj-$(CONFIG_DRAM_SUN8I_V831) += dram_sun8i_v831.o +obj-$(CONFIG_DRAM_SUN20I_D1) += dram_sun20i_d1.o endif diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c index c71deb1b1107..c4725f53e99f 100644 --- a/arch/arm/mach-sunxi/board.c +++ b/arch/arm/mach-sunxi/board.c @@ -135,6 +135,10 @@ static int gpio_init(void) sunxi_gpio_set_cfgpin(SUNXI_GPH(0), SUN50I_H616_GPH_UART0); sunxi_gpio_set_cfgpin(SUNXI_GPH(1), SUN50I_H616_GPH_UART0); sunxi_gpio_set_pull(SUNXI_GPH(1), SUNXI_GPIO_PULL_UP); +#elif CONFIG_CONS_INDEX == 1 && defined(CONFIG_MACH_SUN8I_V851S) + sunxi_gpio_set_cfgpin(SUNXI_GPH(9), SUN8I_V851S_GPH_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPH(10), SUN8I_V851S_GPH_UART0); + sunxi_gpio_set_pull(SUNXI_GPH(10), SUNXI_GPIO_PULL_UP); #elif CONFIG_CONS_INDEX == 1 && defined(CONFIG_MACH_SUN8I_V831) sunxi_gpio_set_cfgpin(SUNXI_GPH(9), SUN8I_V831_GPH_UART0); sunxi_gpio_set_cfgpin(SUNXI_GPH(10), SUN8I_V831_GPH_UART0); @@ -483,7 +487,8 @@ void reset_cpu(void) /* sun5i sometimes gets stuck without this */ writel(WDT_MODE_RESET_EN | WDT_MODE_EN, &wdog->mode); } -#elif defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) +#elif defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) || \ + defined(CONFIG_SUNXI_GEN_NCAT2) #if defined(CONFIG_MACH_SUN50I_H6) /* WDOG is broken for some H6 rev. use the R_WDOG instead */ static const struct sunxi_wdog *wdog = diff --git a/arch/arm/mach-sunxi/clock_sun50i_h6.c b/arch/arm/mach-sunxi/clock_sun50i_h6.c index b97bc9515467..aec5c6dc69fe 100644 --- a/arch/arm/mach-sunxi/clock_sun50i_h6.c +++ b/arch/arm/mach-sunxi/clock_sun50i_h6.c @@ -25,8 +25,10 @@ void clock_init_safe(void) clrsetbits_le32(SUNXI_CCM_BASE + 0x400, 0x7 << 28, 0x1 << 28); } - clrbits_le32(&prcm->res_cal_ctrl, 1); - setbits_le32(&prcm->res_cal_ctrl, 1); + if (!IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) { + clrbits_le32(&prcm->res_cal_ctrl, 1); + setbits_le32(&prcm->res_cal_ctrl, 1); + } if (IS_ENABLED(CONFIG_MACH_SUN50I_H6)) { /* set key field for ldo enable */ @@ -46,7 +48,9 @@ void clock_init_safe(void) CCM_CPU_AXI_DEFAULT_FACTORS); writel(CCM_PSI_AHB1_AHB2_DEFAULT, &ccm->psi_ahb1_ahb2_cfg); +#ifdef CCM_AHB3_DEFAULT writel(CCM_AHB3_DEFAULT, &ccm->ahb3_cfg); +#endif writel(CCM_APB1_DEFAULT, &ccm->apb1_cfg); if (IS_ENABLED(CONFIG_MACH_SUN8I_V831)) { @@ -103,6 +107,9 @@ void clock_set_pll1(unsigned int clk) writel(CCM_PLL1_CTRL_EN | CCM_PLL1_LOCK_EN | CCM_PLL1_CLOCK_TIME_2 | #ifdef CONFIG_MACH_SUN50I_H616 CCM_PLL1_OUT_EN | +#endif +#ifdef CONFIG_SUNXI_GEN_NCAT2 + CCM_PLL1_OUT_EN | CCM_PLL1_LDO_EN | #endif CCM_PLL1_CTRL_N(clk / 24000000), &ccm->pll1_cfg); while (!(readl(&ccm->pll1_cfg) & CCM_PLL1_LOCK)) {} @@ -127,6 +134,13 @@ unsigned int clock_get_pll6(void) CCM_PLL6_CTRL_DIV1_SHIFT) + 1; int div2 = ((rval & CCM_PLL6_CTRL_DIV2_MASK) >> CCM_PLL6_CTRL_DIV2_SHIFT) + 1; + + /* NCAT2: P0 divides PLL_PERI0 (1X), as mainline's R528 code does */ + if (IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) { + div1 = ((rval & CCM_PLL6_CTRL_P0_MASK) >> + CCM_PLL6_CTRL_P0_SHIFT) + 1; + m = 1; + } /* The register defines PLL6-2X or PLL6-4X, not plain PLL6 */ return 24000000 / m * n / div1 / div2; } diff --git a/arch/arm/mach-sunxi/cpu_info.c b/arch/arm/mach-sunxi/cpu_info.c index afc238de82a1..efb2548b186e 100644 --- a/arch/arm/mach-sunxi/cpu_info.c +++ b/arch/arm/mach-sunxi/cpu_info.c @@ -105,6 +105,8 @@ int print_cpuinfo(void) puts("CPU: Allwinner H616 (SUN50I)\n"); #elif defined CONFIG_MACH_SUN8I_V831 puts("CPU: Allwinner V831/V833 (SUN8IW19)\n"); +#elif defined CONFIG_MACH_SUN8I_V851S + puts("CPU: Allwinner V851s (SUN8IW21)\n"); #else #warning Please update cpu_info.c with correct CPU information puts("CPU: SUNXI Family\n"); diff --git a/arch/arm/mach-sunxi/dram_sun20i_d1.c b/arch/arm/mach-sunxi/dram_sun20i_d1.c new file mode 100644 index 000000000000..20e0ec91bfa5 --- /dev/null +++ b/arch/arm/mach-sunxi/dram_sun20i_d1.c @@ -0,0 +1,1496 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Allwinner D1/D1s/R528/T113-sx/V851S DRAM initialisation + * + * As usual there is no documentation for the memory controller or PHY IP + * used here. The baseline of this code was lifted from awboot[1], which + * seems to be based on some form of de-compilation of some original Allwinner + * code bits (with a GPL2 license tag from the very beginning). + * This version here is a reworked version, to match the U-Boot coding style + * and style of the other Allwinner DRAM drivers. + * + * [1] https://github.com/szemzoa/awboot.git + */ + +#include +#include +#include +#include +#ifdef CONFIG_RAM + #include + #include +#endif +#include + +#include "dram_sun20i_d1.h" + +#define SUNXI_R_CPUCFG_SUP_STAN_FLAG 0x1d4 + +#define VDD_SYS_PWROFF_GATING_REG 0x250 +#define ANALOG_PWROFF_GATING_REG 0x254 + +#define SP_STDBY_FLAG_REG 0x1f8 + +static inline int ns_to_t(struct dram_para *para, int nanoseconds) +{ + const unsigned int ctrl_freq = para->clk / 2; + + return DIV_ROUND_UP(ctrl_freq * nanoseconds, 1000); +} + +static void sid_read_ldoB_cal(struct dram_para *para) +{ + struct sunxi_sidc_reg * const sidc = + (struct sunxi_sidc_reg *)SUNXI_SIDC_BASE; + struct sunxi_sramc_reg * const sramc = + (struct sunxi_sramc_reg *)SUNXI_SRAMC_BASE; + + uint32_t reg; + + reg = (readl(&sidc->ldob_sid) & 0xff00) >> 8; + + if (reg == 0) + return; + + switch (para->type) { + case SUNXI_DRAM_TYPE_DDR2: + break; + case SUNXI_DRAM_TYPE_DDR3: + if (reg > 0x20) + reg -= 0x16; + break; + default: + reg = 0; + break; + } + + clrsetbits_le32(&sramc->ldo_ctal, 0xff00, reg << 8); +} + +static void dram_voltage_set(struct dram_para *para) +{ + struct sunxi_sramc_reg * const sramc = + (struct sunxi_sramc_reg *)SUNXI_SRAMC_BASE; + + int vol; + + switch (para->type) { + case SUNXI_DRAM_TYPE_DDR2: + vol = 47; + break; + case SUNXI_DRAM_TYPE_DDR3: + vol = 25; + break; + default: + vol = 0; + break; + } + + clrsetbits_le32(&sramc->ldo_ctal, 0x20ff00, vol << 8); + + udelay(1); + + sid_read_ldoB_cal(para); +} + +static void dram_enable_all_master(void) +{ + struct sunxi_mctl_com_reg * const mctl_com = + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + + writel(~0, &mctl_com->maer0); + writel(0xff, &mctl_com->maer1); + writel(0xffff, &mctl_com->maer2); + udelay(10); +} + +static void dram_disable_all_master(void) +{ + struct sunxi_mctl_com_reg * const mctl_com = + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + + writel(1, &mctl_com->maer0); + writel(0, &mctl_com->maer1); + writel(0, &mctl_com->maer2); + udelay(10); +} + +static void eye_delay_compensation(struct dram_para *para) +{ + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + uint32_t delay, i = 0; + + // DATn0IOCR, n = 0...7 + delay = (para->tpr11 & 0xf) << 9; + delay |= (para->tpr12 & 0xf) << 1; + for (i = 0; i < 9; i++) + setbits_le32(DATX0IOCR(i), delay); + + // DATn1IOCR, n = 0...7 + delay = (para->tpr11 & 0xf0) << 5; + delay |= (para->tpr12 & 0xf0) >> 3; + for (i = 0; i < 9; i++) + setbits_le32(DATX1IOCR(i), delay); + + // PGCR0: assert AC loopback FIFO reset + clrbits_le32(&mctl_phy->pgcr0, 0x04000000); + + // DQS0 read and write delay + delay = (para->tpr11 & 0xf0000) >> 7; + delay |= (para->tpr12 & 0xf0000) >> 15; + setbits_le32(DATX0IOCR(9), delay); // DQS0 P + setbits_le32(DATX0IOCR(10), delay); // DQS0 N + + // DQS1 read and write delay + delay = (para->tpr11 & 0xf00000) >> 11; + delay |= (para->tpr12 & 0xf00000) >> 19; + setbits_le32(DATX1IOCR(9), delay); // DQS1 P + setbits_le32(DATX1IOCR(10), delay); // DQS1 N + + // DQS0 enable bit delay + setbits_le32(DXnSDLR6(0), (para->tpr11 & 0xf0000) << 9); + + // DQS1 enable bit delay + setbits_le32(DXnSDLR6(1), (para->tpr11 & 0xf00000) << 5); + + // PGCR0: release AC loopback FIFO reset + setbits_le32(&mctl_phy->pgcr0, BIT(26)); + + udelay(1); + + // Set RAS CAS and CA delay + delay = (para->tpr10 & 0xf0) << 4; + for (i = 6; i < 27; i++) + setbits_le32(ACIOCR1(i), delay); + + // Set CK CS delay + setbits_le32(ACIOCR1(2), (para->tpr10 & 0x0f) << 8); + setbits_le32(ACIOCR1(3), (para->tpr10 & 0x0f) << 8); + setbits_le32(ACIOCR1(28), (para->tpr10 & 0xf00) >> 4); +} + +/* + * Main purpose of the auto_set_timing routine seems to be to calculate all + * timing settings for the specific type of sdram used. Read together with + * an sdram datasheet for context on the various variables. + */ +static void mctl_set_timing_params(struct dram_para *para) +{ + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + /* DRAM_TPR0 */ + u8 tccd = 2; + u8 tfaw; + u8 trrd; + u8 trcd; + u8 trc; + + /* DRAM_TPR1 */ + u8 txp; + u8 twtr; + u8 trtp = 4; + u8 twr; + u8 trp; + u8 tras; + + /* DRAM_TPR2 */ + u16 trefi; + u16 trfc; + + u8 tcksrx; + u8 tckesr; + u8 trd2wr; + u8 twr2rd; + u8 trasmax; + u8 twtp; + u8 tcke; + u8 tmod; + u8 tmrd; + u8 tmrw; + + u8 tcl; + u8 tcwl; + u8 t_rdata_en; + u8 wr_latency; + + u32 mr0; + u32 mr1; + u32 mr2; + u32 mr3; + + u32 tdinit0; + u32 tdinit1; + u32 tdinit2; + u32 tdinit3; + + switch (para->type) { + case SUNXI_DRAM_TYPE_DDR2: + /* DRAM_TPR0 */ + tfaw = ns_to_t(para, 50); + trrd = ns_to_t(para, 10); + trcd = ns_to_t(para, 20); + trc = ns_to_t(para, 65); + + /* DRAM_TPR1 */ + txp = 2; + twtr = ns_to_t(para, 8); + twr = ns_to_t(para, 15); + trp = ns_to_t(para, 15); + tras = ns_to_t(para, 45); + + /* DRAM_TRP2 */ + trfc = ns_to_t(para, 328); + trefi = ns_to_t(para, 7800) / 32; + + trasmax = para->clk / 30; + if (para->clk < 409) { + t_rdata_en = 1; + tcl = 3; + mr0 = 0x06a3; + } else { + t_rdata_en = 2; + tcl = 4; + mr0 = 0x0e73; + } + tmrd = 2; + twtp = twr + 5; + tcksrx = 5; + tckesr = 4; + trd2wr = 4; + tcke = 3; + tmod = 12; + wr_latency = 1; + tmrw = 0; + twr2rd = twtr + 5; + tcwl = 0; + + mr1 = para->mr1; + mr2 = 0; + mr3 = 0; + + tdinit0 = 200 * para->clk + 1; + tdinit1 = 100 * para->clk / 1000 + 1; + tdinit2 = 200 * para->clk + 1; + tdinit3 = 1 * para->clk + 1; + + break; + case SUNXI_DRAM_TYPE_DDR3: + trfc = ns_to_t(para, 350); + trefi = ns_to_t(para, 7800) / 32 + 1; // XXX + + twtr = ns_to_t(para, 8) + 2; // + 2 ? XXX + /* Only used by trd2wr calculation, which gets discard below */ +// twr = max(ns_to_t(para, 15), 2); + trrd = max(ns_to_t(para, 10), 2); + txp = max(ns_to_t(para, 10), 2); + + if (para->clk <= 800) { + tfaw = ns_to_t(para, 50); + trcd = ns_to_t(para, 15); + trp = ns_to_t(para, 15); + trc = ns_to_t(para, 53); + tras = ns_to_t(para, 38); + + mr0 = 0x1c70; + mr2 = 0x18; + tcl = 6; + wr_latency = 2; + tcwl = 4; + t_rdata_en = 4; + } else { + tfaw = ns_to_t(para, 35); + trcd = ns_to_t(para, 14); + trp = ns_to_t(para, 14); + trc = ns_to_t(para, 48); + tras = ns_to_t(para, 34); + + mr0 = 0x1e14; + mr2 = 0x20; + tcl = 7; + wr_latency = 3; + tcwl = 5; + t_rdata_en = 5; + } + + trasmax = para->clk / 30; + twtp = tcwl + 2 + twtr; // WL+BL/2+tWTR + /* Gets overwritten below */ + // trd2wr = tcwl + 2 + twr; // WL+BL/2+tWR + twr2rd = tcwl + twtr; // WL+tWTR + + tdinit0 = 500 * para->clk + 1; // 500 us + tdinit1 = 360 * para->clk / 1000 + 1; // 360 ns + tdinit2 = 200 * para->clk + 1; // 200 us + tdinit3 = 1 * para->clk + 1; // 1 us + + mr1 = para->mr1; + mr3 = 0; + tcke = 3; + tcksrx = 5; + tckesr = 4; + if (((para->tpr13 & 0xc) == 0x04) || para->clk < 912) + trd2wr = 5; + else + trd2wr = 6; + + tmod = 12; + tmrd = 4; + tmrw = 0; + + break; + case SUNXI_DRAM_TYPE_LPDDR2: + tfaw = max(ns_to_t(para, 50), 4); + trrd = max(ns_to_t(para, 10), 1); + trcd = max(ns_to_t(para, 24), 2); + trc = ns_to_t(para, 70); + txp = ns_to_t(para, 8); + if (txp < 2) { + txp++; + twtr = 2; + } else { + twtr = txp; + } + twr = max(ns_to_t(para, 15), 2); + trp = ns_to_t(para, 17); + tras = ns_to_t(para, 42); + trefi = ns_to_t(para, 3900) / 32; + trfc = ns_to_t(para, 210); + + trasmax = para->clk / 60; + mr3 = para->mr3; + twtp = twr + 5; + mr2 = 6; + mr1 = 5; + tcksrx = 5; + tckesr = 5; + trd2wr = 10; + tcke = 2; + tmod = 5; + tmrd = 5; + tmrw = 3; + tcl = 4; + wr_latency = 1; + t_rdata_en = 1; + + tdinit0 = 200 * para->clk + 1; + tdinit1 = 100 * para->clk / 1000 + 1; + tdinit2 = 11 * para->clk + 1; + tdinit3 = 1 * para->clk + 1; + twr2rd = twtr + 5; + tcwl = 2; + mr1 = 195; + mr0 = 0; + + break; + case SUNXI_DRAM_TYPE_LPDDR3: + tfaw = max(ns_to_t(para, 50), 4); + trrd = max(ns_to_t(para, 10), 1); + trcd = max(ns_to_t(para, 24), 2); + trc = ns_to_t(para, 70); + twtr = max(ns_to_t(para, 8), 2); + twr = max(ns_to_t(para, 15), 2); + trp = ns_to_t(para, 17); + tras = ns_to_t(para, 42); + trefi = ns_to_t(para, 3900) / 32; + trfc = ns_to_t(para, 210); + txp = twtr; + + trasmax = para->clk / 60; + if (para->clk < 800) { + tcwl = 4; + wr_latency = 3; + t_rdata_en = 6; + mr2 = 12; + } else { + tcwl = 3; + tcke = 6; + wr_latency = 2; + t_rdata_en = 5; + mr2 = 10; + } + twtp = tcwl + 5; + tcl = 7; + mr3 = para->mr3; + tcksrx = 5; + tckesr = 5; + trd2wr = 13; + tcke = 3; + tmod = 12; + tdinit0 = 400 * para->clk + 1; + tdinit1 = 500 * para->clk / 1000 + 1; + tdinit2 = 11 * para->clk + 1; + tdinit3 = 1 * para->clk + 1; + tmrd = 5; + tmrw = 5; + twr2rd = tcwl + twtr + 5; + mr1 = 195; + mr0 = 0; + + break; + default: + trfc = 128; + trp = 6; + trefi = 98; + txp = 10; + twr = 8; + twtr = 3; + tras = 14; + tfaw = 16; + trc = 20; + trcd = 6; + trrd = 3; + + twr2rd = 8; + tcksrx = 4; + tckesr = 3; + trd2wr = 4; + trasmax = 27; + twtp = 12; + tcke = 2; + tmod = 6; + tmrd = 2; + tmrw = 0; + tcwl = 3; + tcl = 3; + wr_latency = 1; + t_rdata_en = 1; + mr3 = 0; + mr2 = 0; + mr1 = 0; + mr0 = 0; + tdinit3 = 0; + tdinit2 = 0; + tdinit1 = 0; + tdinit0 = 0; + + break; + } + + /* Set mode registers */ + writel(mr0, &mctl_phy->dram_mr0); + writel(mr1, &mctl_phy->dram_mr1); + writel(mr2, &mctl_phy->dram_mr2); + writel(mr3, &mctl_phy->dram_mr3); + /* TODO: dram_odt_en is either 0x0 or 0x1, so right shift looks weird */ + writel((para->odt_en >> 4) & 0x3, &mctl_phy->lp3mr11); + + /* Set dram timing DRAMTMG0 - DRAMTMG5 */ + writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | (tras << 0), + &mctl_phy->dramtmg0); + writel((txp << 16) | (trtp << 8) | (trc << 0), + &mctl_phy->dramtmg1); + writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | (twr2rd << 0), + &mctl_phy->dramtmg2); + writel((tmrw << 16) | (tmrd << 12) | (tmod << 0), + &mctl_phy->dramtmg3); + writel((trcd << 24) | (tccd << 16) | (trrd << 8) | (trp << 0), + &mctl_phy->dramtmg4); + writel((tcksrx << 24) | (tcksrx << 16) | (tckesr << 8) | (tcke << 0), + &mctl_phy->dramtmg5); + + /* Set dual rank timing */ + clrsetbits_le32(&mctl_phy->dramtmg8, 0xf000ffff, + (para->clk < 800) ? 0xf0006610 : 0xf0007610); + + /* Set phy interface time PITMG0, PTR3, PTR4 */ + writel((0x2 << 24) | (t_rdata_en << 16) | BIT(8) | (wr_latency << 0), + &mctl_phy->pitmg0); + writel(((tdinit0 << 0) | (tdinit1 << 20)), &mctl_phy->ptr3); + writel(((tdinit2 << 0) | (tdinit3 << 20)), &mctl_phy->ptr4); + + /* Set refresh timing and mode */ + writel((trefi << 16) | (trfc << 0), &mctl_phy->rfshtmg); + writel((trefi << 15) & 0x0fff0000, &mctl_phy->rfshctl1); +} + +// Purpose of this routine seems to be to initialize the PLL driving +// the MBUS and sdram. +// +static int ccu_set_pll_ddr_clk(int index, struct dram_para *para) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + + unsigned int val, clk, n; + + if (para->tpr13 & BIT(6)) + clk = para->tpr9; + else + clk = para->clk; + + // set VCO clock divider + n = (clk * 2) / 24; + + val = readl(&ccm->pll5_cfg); + val &= ~0x0007ff03; // clear dividers + val |= (n - 1) << 8; // set PLL division + val |= BIT(31) | BIT(30); // enable PLL and LDO + writel(val | BIT(29), &ccm->pll5_cfg); + + // wait for PLL to lock + while ((readl(&ccm->pll5_cfg) & BIT(28)) == 0) {} + + udelay(20); + + // enable PLL output + setbits_le32(&ccm->pll1_cfg, BIT(27)); + + // turn clock gate on + val = readl(&ccm->dram_clk_cfg); + val &= ~0x03000303; // select DDR clk source, n=1, m=1 + val |= BIT(31); // turn clock on + writel(val, &ccm->dram_clk_cfg); + + return n * 24; +} + +/* Set up the PLL and clock gates for the DRAM controller and MBUS clocks. */ +static void mctl_sys_init(struct dram_para *para) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + // assert MBUS reset + clrbits_le32(&ccm->mbus_cfg, BIT(30)); + + // turn off sdram clock gate, assert sdram reset + clrbits_le32(&ccm->dram_gate_reset, 0x10001); + clrsetbits_le32(&ccm->dram_clk_cfg, BIT(31) | BIT(30), BIT(27)); + udelay(10); + + // set ddr pll clock + ccu_set_pll_ddr_clk(0, para); + udelay(100); + dram_disable_all_master(); + + // release sdram reset + setbits_le32(&ccm->dram_gate_reset, BIT(16)); + + // release MBUS reset + setbits_le32(&ccm->mbus_cfg, BIT(30)); + setbits_le32(&ccm->dram_clk_cfg, BIT(30)); + + udelay(5); + + // turn on sdram clock gate + setbits_le32(&ccm->dram_gate_reset, BIT(0)); + + // turn dram clock gate on, trigger sdr clock update + setbits_le32(&ccm->dram_clk_cfg, BIT(31) | BIT(27)); + udelay(5); + + // mCTL clock enable + writel(0x8000, &mctl_phy->clken); + udelay(10); +} + +// The main purpose of this routine seems to be to copy an address configuration +// from the dram_para1 and dram_para2 fields to the PHY configuration registers +// (MCTL_COM_WORK_MODE0, MCTL_COM_WORK_MODE1). +// +static void mctl_com_init(struct dram_para *para) +{ + struct sunxi_mctl_com_reg * const mctl_com = + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + uint32_t val, width; + u32 *ptr; + int i; + + // Setting controller wait time + clrsetbits_le32(&mctl_com->dbgcr, 0x3f00, 0x2000); + + // set SDRAM type and word width + val = readl(&mctl_com->work_mode0) & ~0x00fff000; + val |= (para->type & 0x7) << 16; // DRAM type + val |= (~para->para2 & 0x1) << 12; // DQ width + val |= BIT(22); // ?? + if (para->type == SUNXI_DRAM_TYPE_LPDDR2 || + para->type == SUNXI_DRAM_TYPE_LPDDR3) { + val |= BIT(19); // type 6 and 7 must use 1T + } else { + if (para->tpr13 & BIT(5)) + val |= BIT(19); + } + writel(val, &mctl_com->work_mode0); + + // init rank / bank / row for single/dual or two different ranks + if ((para->para2 & BIT(8)) && ((para->para2 & 0xf000) != 0x1000)) + width = 32; + else + width = 16; + + ptr = &mctl_com->work_mode0; + for (i = 0; i < width; i += 16) { + val = readl(ptr) & 0xfffff000; + + val |= (para->para2 >> 12) & 0x3; // rank + val |= ((para->para1 >> (i + 12)) << 2) & 0x4; // bank - 2 + val |= (((para->para1 >> (i + 4)) - 1) << 4) & 0xff; // row - 1 + + // convert from page size to column addr width - 3 + switch ((para->para1 >> i) & 0xf) { + case 8: val |= 0xa00; break; + case 4: val |= 0x900; break; + case 2: val |= 0x800; break; + case 1: val |= 0x700; break; + default: val |= 0x600; break; + } + writel(val, ptr); + ptr++; + } + + // set ODTMAP based on number of ranks in use + val = (readl(&mctl_com->work_mode0) & 0x1) ? 0x303 : 0x201; + writel(val, &mctl_phy->odtmap); + + // set mctl reg 3c4 to zero when using half DQ + if (para->para2 & BIT(0)) + writel(0, DXnGCR0(1)); + + // set dram address mapping from dram_tpr4 param + if (para->tpr4) { + setbits_le32(&mctl_com->work_mode0, (para->tpr4 & 0x3) << 25); + setbits_le32(&mctl_com->work_mode1, (para->tpr4 & 0x7fc) << 10); + } +} + +static const uint8_t ac_remapping_tables[][22] = { + [0] = { 0 }, + [1] = { 1, 9, 3, 7, 8, 18, 4, 13, 5, 6, 10, + 2, 14, 12, 0, 0, 21, 17, 20, 19, 11, 22 }, // Generic DDR3 Type1 + [2] = { 4, 9, 3, 7, 8, 18, 1, 13, 2, 6, 10, + 5, 14, 12, 0, 0, 21, 17, 20, 19, 11, 22 }, // Generic DDR3 Type C + [3] = { 1, 7, 8, 12, 10, 18, 4, 13, 5, 6, 3, + 2, 9, 0, 0, 0, 21, 17, 20, 19, 11, 22 }, // Generic DDR3 Type 8 + [4] = { 4, 12, 10, 7, 8, 18, 1, 13, 2, 6, 3, + 5, 9, 0, 0, 0, 21, 17, 20, 19, 11, 22 }, // Generic DDR3 Type 9 + [5] = { 13, 2, 7, 9, 12, 19, 5, 1, 6, 3, 4, + 8, 10, 0, 0, 0, 21, 22, 18, 17, 11, 20 }, // Generic DDR3 Type bf + [6] = { 3, 10, 7, 13, 9, 11, 1, 2, 4, 6, 8, + 5, 12, 0, 0, 0, 20, 1, 0, 21, 22, 17 }, // DDR2 + [7] = { 3, 2, 4, 7, 9, 1, 17, 12, 18, 14, 13, + 8, 15, 6, 10, 5, 19, 22, 16, 21, 20, 11 }, // DDR3 D1-H +}; + +/* + * This routine chooses one of several remapping tables for 22 lines. + * It is unclear which lines are being remapped. It seems to pick + * table cfg7 for the Nezha board. + */ +static void mctl_phy_ac_remapping(struct dram_para *para) +{ + struct sunxi_mctl_com_reg * const mctl_com = + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + + const uint8_t *cfg; + uint32_t fuse, val; + + /* + * It is unclear whether the LPDDRx types don't need any remapping, + * or whether the original code just didn't provide tables. + */ + if (para->type != SUNXI_DRAM_TYPE_DDR2 && + para->type != SUNXI_DRAM_TYPE_DDR3) + return; + + fuse = (readl(SUNXI_SID_BASE + 0x28) & 0xf00) >> 8; + debug("DDR efuse: 0x%x\n", fuse); + + if (para->type == SUNXI_DRAM_TYPE_DDR2) { + if (fuse == 15) + return; + cfg = ac_remapping_tables[6]; + } else { + if (para->tpr13 & 0xc0000) { + cfg = ac_remapping_tables[7]; + } else { + switch (fuse) { + case 8: cfg = ac_remapping_tables[2]; break; + case 9: cfg = ac_remapping_tables[3]; break; + case 10: cfg = ac_remapping_tables[5]; break; + case 11: cfg = ac_remapping_tables[4]; break; + default: + case 12: cfg = ac_remapping_tables[1]; break; + case 13: + case 14: cfg = ac_remapping_tables[0]; break; + } + } + } + + val = (cfg[4] << 25) | (cfg[3] << 20) | (cfg[2] << 15) | + (cfg[1] << 10) | (cfg[0] << 5); + writel(val, &mctl_com->remap0); + + val = (cfg[10] << 25) | (cfg[9] << 20) | (cfg[8] << 15) | + (cfg[ 7] << 10) | (cfg[6] << 5) | cfg[5]; + writel(val, &mctl_com->remap1); + + val = (cfg[15] << 20) | (cfg[14] << 15) | (cfg[13] << 10) | + (cfg[12] << 5) | cfg[11]; + writel(val, &mctl_com->remap2); + + val = (cfg[21] << 25) | (cfg[20] << 20) | (cfg[19] << 15) | + (cfg[18] << 10) | (cfg[17] << 5) | cfg[16]; + writel(val, &mctl_com->remap3); +} + +// Init the controller channel. The key part is placing commands in the main +// command register (PIR, 0x3103000) and checking command status (PGSR0, 0x3103010). +// +static unsigned int mctl_channel_init(unsigned int ch_index, + struct dram_para *para) +{ + struct sunxi_mctl_com_reg * const mctl_com = + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + unsigned int val, dqs_gating_mode; + + dqs_gating_mode = (para->tpr13 & 0xc) >> 2; + + // set DDR clock to half of CPU clock + clrsetbits_le32(&mctl_com->tmr, 0xfff, (para->clk / 2) - 1); + + // MRCTRL0 nibble 3 undocumented + clrsetbits_le32(&mctl_phy->pgcr2, 0xf00, 0x300); + + if (para->odt_en) + val = 0; + else + val = BIT(5); + + // DX0GCR0 + if (para->clk > 672) + clrsetbits_le32(DXnGCR0(0), 0xf63e, val); + else + clrsetbits_le32(DXnGCR0(0), 0xf03e, val); + + // DX1GCR0 + if (para->clk > 672) { + setbits_le32(DXnGCR0(0), 0x400); + clrsetbits_le32(DXnGCR0(1), 0xf63e, val); + } else { + clrsetbits_le32(DXnGCR0(1), 0xf03e, val); + } + + // ACIOCR0 undocumented + setbits_le32(&mctl_phy->aciocr0, BIT(1)); + + eye_delay_compensation(para); + + // set PLL SSCG ? + val = readl(&mctl_phy->pgcr2); + if (dqs_gating_mode == 1) { + clrsetbits_le32(&mctl_phy->pgcr2, 0xc0, 0); + clrbits_le32(&mctl_phy->dqsgmr, 0x107); + } else if (dqs_gating_mode == 2) { + clrsetbits_le32(&mctl_phy->pgcr2, 0xc0, 0x80); + + clrsetbits_le32(&mctl_phy->dqsgmr, 0x107, + (((para->tpr13 >> 16) & 0x1f) - 2) | 0x100); + clrsetbits_le32(&mctl_phy->dxccr, BIT(31), BIT(27)); + } else { + clrbits_le32(&mctl_phy->pgcr2, 0x40); + udelay(10); + setbits_le32(&mctl_phy->pgcr2, 0xc0); + } + + if (para->type == SUNXI_DRAM_TYPE_LPDDR2 || + para->type == SUNXI_DRAM_TYPE_LPDDR3) { + if (dqs_gating_mode == 1) + clrsetbits_le32(&mctl_phy->dxccr, 0x080000c0, 0x80000000); + else + clrsetbits_le32(&mctl_phy->dxccr, 0x77000000, 0x22000000); + } + + clrsetbits_le32(&mctl_phy->dtcr, 0x0fffffff, + (para->para2 & BIT(12)) ? 0x03000001 : 0x01000007); + + if (readl((SUNXI_R_CPUCFG_BASE + SUNXI_R_CPUCFG_SUP_STAN_FLAG)) & (1 << 16)) { + clrbits_le32((SUNXI_PRCM_BASE + VDD_SYS_PWROFF_GATING_REG), 0x2); + udelay(10); + } + + // Set ZQ config + clrsetbits_le32(&mctl_phy->zqcr, 0x3ffffff, + (para->zq & 0x00ffffff) | BIT(25)); + + // Initialise DRAM controller + if (dqs_gating_mode == 1) { + //writel(0x52, &mctl_phy->pir); // prep PHY reset + PLL init + z-cal + writel(0x53, &mctl_phy->pir); // Go + + while ((readl(&mctl_phy->pgsr0) & 0x1) == 0) {} // wait for IDONE + udelay(10); + + // 0x520 = prep DQS gating + DRAM init + d-cal + if (para->type == SUNXI_DRAM_TYPE_DDR3) + writel(0x5a0, &mctl_phy->pir); // + DRAM reset + else + writel(0x520, &mctl_phy->pir); + } else { + if ((readl((SUNXI_R_CPUCFG_BASE + SUNXI_R_CPUCFG_SUP_STAN_FLAG)) & (1 << 16)) == 0) { + // prep DRAM init + PHY reset + d-cal + PLL init + z-cal + if (para->type == SUNXI_DRAM_TYPE_DDR3) + writel(0x1f2, &mctl_phy->pir); // + DRAM reset + else + writel(0x172, &mctl_phy->pir); + } else { + // prep PHY reset + d-cal + z-cal + writel(0x62, &mctl_phy->pir); + } + } + + setbits_le32(&mctl_phy->pir, BIT(0)); // GO + + udelay(10); + while ((readl(&mctl_phy->pgsr0) & BIT(0)) == 0) {} // wait for IDONE + + if (readl((SUNXI_R_CPUCFG_BASE + SUNXI_R_CPUCFG_SUP_STAN_FLAG)) & BIT(16)) { + clrsetbits_le32(&mctl_phy->pgcr3, 0x06000000, 0x04000000); + udelay(10); + + setbits_le32(&mctl_phy->pwrctl, BIT(0)); + + while ((readl(&mctl_phy->statr) & 0x7) != 0x3) {} + + clrbits_le32((SUNXI_PRCM_BASE + VDD_SYS_PWROFF_GATING_REG), BIT(0)); + udelay(10); + + clrbits_le32(&mctl_phy->pwrctl, BIT(0)); + + while ((readl(&mctl_phy->statr) & 0x7) != 0x1) {} + + udelay(15); + + if (dqs_gating_mode == 1) { + clrbits_le32(&mctl_phy->pgcr2, 0xc0); + clrsetbits_le32(&mctl_phy->pgcr3, 0x06000000, 0x02000000); + udelay(1); + writel(0x401, &mctl_phy->pir); + + while ((readl(&mctl_phy->pgsr0) & BIT(0)) == 0) {} + } + } + + // Check for training error + if (readl(&mctl_phy->pgsr0) & BIT(20)) { + printf("ZQ calibration error, check external 240 ohm resistor\n"); + return 0; + } + + // STATR = Zynq STAT? Wait for status 'normal'? + while ((readl(&mctl_phy->statr) & BIT(0)) == 0) {} + + setbits_le32(&mctl_phy->rfshctl0, BIT(31)); + udelay(10); + clrbits_le32(&mctl_phy->rfshctl0, BIT(31)); + udelay(10); + setbits_le32(&mctl_com->cccr, BIT(31)); + udelay(10); + + clrbits_le32(&mctl_phy->pgcr3, 0x06000000); + + if (dqs_gating_mode == 1) + clrsetbits_le32(&mctl_phy->dxccr, 0xc0, 0x40); + + return 1; +} + +static unsigned int calculate_rank_size(uint32_t regval) +{ + unsigned int bits; + + bits = (regval >> 8) & 0xf; /* page size - 3 */ + bits += (regval >> 4) & 0xf; /* row width - 1 */ + bits += (regval >> 2) & 0x3; /* bank count - 2 */ + bits -= 14; /* 1MB = 20 bits, minus above 6 = 14 */ + + return 1U << bits; +} + +/* + * The below routine reads the dram config registers and extracts + * the number of address bits in each rank available. It then calculates + * total memory size in MB. + */ +static unsigned int DRAMC_get_dram_size(void) +{ + struct sunxi_mctl_com_reg * const mctl_com = + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + + uint32_t val; + unsigned int size; + + val = readl(&mctl_com->work_mode0); + size = calculate_rank_size(val); + + if ((val & 0x3) == 0) /* single rank? */ + return size; + + val = readl(&mctl_com->work_mode1); + if ((val & 0x3) == 0) /* two identical ranks? */ + return size * 2; + + /* add sizes of both ranks */ + return size + calculate_rank_size(val); +} + +/* + * The below routine reads the command status register to extract + * DQ width and rank count. This follows the DQS training command in + * channel_init. If error bit 22 is reset, we have two ranks and full DQ. + * If there was an error, figure out whether it was half DQ, single rank, + * or both. Set bit 12 and 0 in dram_para2 with the results. + */ +static int dqs_gate_detect(struct dram_para *para) +{ + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + uint32_t dx0 = 0, dx1 = 0; + + if ((readl(&mctl_phy->pgsr0) & BIT(22)) == 0) { + para->para2 = (para->para2 & ~0xf) | BIT(12); + debug("dual rank and full DQ\n"); + + return 1; + } + + dx0 = (readl(DXnGSR0(0)) & 0x3000000) >> 24; + if (dx0 == 0) { + para->para2 = (para->para2 & ~0xf) | 0x1001; + debug("dual rank and half DQ\n"); + + return 1; + } + + if (dx0 == 2) { + dx1 = (readl(DXnGSR0(1)) & 0x3000000) >> 24; + if (dx1 == 2) { + para->para2 = para->para2 & ~0xf00f; + debug("single rank and full DQ\n"); + } else { + para->para2 = (para->para2 & ~0xf00f) | BIT(0); + debug("single rank and half DQ\n"); + } + + return 1; + } + + if ((para->tpr13 & BIT(29)) == 0) + return 0; + + debug("DX0 state: %d\n", dx0); + debug("DX1 state: %d\n", dx1); + + return 0; +} + +static int dramc_simple_wr_test(unsigned int mem_mb, int len) +{ + unsigned int offs = (mem_mb / 2) << 18; // half of memory size + unsigned int patt1 = 0x01234567; + unsigned int patt2 = 0xfedcba98; + unsigned int *addr, v1, v2, i; + + addr = (unsigned int *)CONFIG_SYS_SDRAM_BASE; + for (i = 0; i != len; i++, addr++) { + writel(patt1 + i, (unsigned long)addr); + writel(patt2 + i, (unsigned long)(addr + offs)); + } + + addr = (unsigned int *)CONFIG_SYS_SDRAM_BASE; + for (i = 0; i != len; i++) { + v1 = readl((unsigned long)(addr + i)); + v2 = patt1 + i; + if (v1 != v2) { + printf("DRAM: simple test FAIL\n"); + printf("%x != %x at address %p\n", v1, v2, addr + i); + return 1; + } + v1 = readl((unsigned long)(addr + offs + i)); + v2 = patt2 + i; + if (v1 != v2) { + printf("DRAM: simple test FAIL\n"); + printf("%x != %x at address %p\n", v1, v2, addr + offs + i); + return 1; + } + } + + debug("DRAM: simple test OK\n"); + return 0; +} + +// Set the Vref mode for the controller +// +static void mctl_vrefzq_init(struct dram_para *para) +{ + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + if (para->tpr13 & BIT(17)) + return; + + clrsetbits_le32(&mctl_phy->iovcr0, 0x7f7f7f7f, para->tpr5); + + // IOCVR1 + if ((para->tpr13 & BIT(16)) == 0) + clrsetbits_le32(&mctl_phy->iovcr1, 0x7f, para->tpr6 & 0x7f); +} + +// Perform an init of the controller. This is actually done 3 times. The first +// time to establish the number of ranks and DQ width. The second time to +// establish the actual ram size. The third time is final one, with the final +// settings. +// +static int mctl_core_init(struct dram_para *para) +{ + mctl_sys_init(para); + + mctl_vrefzq_init(para); + + mctl_com_init(para); + + mctl_phy_ac_remapping(para); + + mctl_set_timing_params(para); + + return mctl_channel_init(0, para); +} + +/* + * This routine sizes a DRAM device by cycling through address lines and + * figuring out if they are connected to a real address line, or if the + * address is a mirror. + * First the column and bank bit allocations are set to low values (2 and 9 + * address lines). Then a maximum allocation (16 lines) is set for rows and + * this is tested. + * Next the BA2 line is checked. This seems to be placed above the column, + * BA0-1 and row addresses. Finally, the column address is allocated 13 lines + * and these are tested. The results are placed in dram_para1 and dram_para2. + */ +static int auto_scan_dram_size(struct dram_para *para) +{ + struct sunxi_mctl_com_reg * const mctl_com = + (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; + + uint32_t i = 0, j = 0, current_rank = 0; + uint32_t rank_count = 1, addr_line = 0; + uint32_t reg_val = 0, ret = 0, cnt = 0; + u32 *mc_work_mode; + uint32_t rank1_addr = CONFIG_SYS_SDRAM_BASE; + + // init core + if (mctl_core_init(para) == 0) { + debug("DRAM initial error : 0!\n"); + return 0; + } + + // Set rank_count to 2 + if ((((para->para2 >> 12) & 0xf) == 0x1)) + rank_count = 2; + + for (current_rank = 0; current_rank < rank_count; current_rank++) { + mc_work_mode = (&mctl_com->work_mode0 + 4 * current_rank); + + /* Set 16 Row 4Bank 512BPage for Rank 1 */ + if (current_rank == 1) { + clrsetbits_le32(&mctl_com->work_mode0, 0xf0c, 0x6f0); + clrsetbits_le32(&mctl_com->work_mode1, 0xf0c, 0x6f0); + /* update Rank 1 addr */ + rank1_addr = CONFIG_SYS_SDRAM_BASE + (0x1 << 27); + } + + /* write test pattern */ + for (i = 0; i < 64; i++) { + writel((i % 2) ? (CONFIG_SYS_SDRAM_BASE + 4 * i) : (~(CONFIG_SYS_SDRAM_BASE + 4 * i)), + CONFIG_SYS_SDRAM_BASE + 4 * i); + } + /* set row mode */ + clrsetbits_le32(mc_work_mode, 0xf0c, 0x6f0); + udelay(2); + + for (i = 11; i < 17; i++) { + ret = CONFIG_SYS_SDRAM_BASE + (1 << (i + 2 + 9)); /* row-bank-column */ + cnt = 0; + for (j = 0; j < 64; j++) { + reg_val = (j % 2) ? (rank1_addr + 4 * j) : (~(rank1_addr + 4 * j)); + if (reg_val == readl(ret + j * 4)) { + cnt++; + } else + break; + } + if (cnt == 64) { + break; + } + } + if (i >= 16) + i = 16; + addr_line += i; + + debug("rank %d row = %d \n", current_rank, i); + + /* Store rows in para 1 */ + para->para1 &= ~(0xffU << (16 * current_rank + 4)); + para->para1 |= (i << (16 * current_rank + 4)); + debug("para->para1 = 0x%x\n", para->para1); + + /* Set bank mode for current rank */ + if (current_rank == 1) { /* Set bank mode for rank0 */ + clrsetbits_le32(&mctl_com->work_mode0, 0xffc, 0x6a4); + } + + /* Set bank mode for current rank */ + clrsetbits_le32(mc_work_mode, 0xffc, 0x6a4); + udelay(1); + + for (i = 0; i < 1; i++) { + ret = CONFIG_SYS_SDRAM_BASE + (0x1U << (i + 2 + 9)); + cnt = 0; + for (j = 0; j < 64; j++) { + reg_val = (j % 2) ? (rank1_addr + 4 * j) : (~(rank1_addr + 4 * j)); + if (reg_val == readl(ret + j * 4)) { + cnt++; + } else + break; + } + if (cnt == 64) { + break; + } + } + + addr_line += i + 2; + debug("rank %d bank = %d \n", current_rank, (4 + i * 4)); + + /* Store bank in para 1 */ + para->para1 &= ~(0xfU << (16 * current_rank + 12)); + para->para1 |= (i << (16 * current_rank + 12)); + debug("para->para1 = 0x%x\n", para->para1); + + /* Set page mode for rank0 */ + if (current_rank == 1) { + clrsetbits_le32(mc_work_mode, 0xffc, 0xaa0); + } + + /* Set page mode for current rank */ + clrsetbits_le32(mc_work_mode, 0xffc, 0xaa0); + udelay(2); + + /* Scan per address line, until address wraps (i.e. see shadow) */ + for (i = 9; i <= 13; i++) { + ret = CONFIG_SYS_SDRAM_BASE + (0x1U << i); // column 40000000+(9~13) + cnt = 0; + for (j = 0; j < 64; j++) { + reg_val = (j % 2) ? (CONFIG_SYS_SDRAM_BASE + 4 * j) : (~(CONFIG_SYS_SDRAM_BASE + 4 * j)); + if (reg_val == readl(ret + j * 4)) { + cnt++; + } else { + break; + } + } + if (cnt == 64) { + break; + } + } + + if (i >= 13) { + i = 13; + } + + /* add page size */ + addr_line += i; + + if (i == 9) { + i = 0; + } else { + i = (0x1U << (i - 10)); + } + + debug("rank %d page size = %d KB \n", current_rank, i); + + /* Store page in para 1 */ + para->para1 &= ~(0xfU << (16 * current_rank)); + para->para1 |= (i << (16 * current_rank)); + debug("para->para1 = 0x%x\n", para->para1); + } + + /* check dual rank config */ + if (rank_count == 2) { + para->para2 &= 0xfffff0ff; + if ((para->para1 & 0xffff) == (para->para1 >> 16)) { + debug("rank1 config same as rank0\n"); + } else { + para->para2 |= 0x1 << 8; + debug("rank1 config different from rank0\n"); + } + } + return 1; +} + +/* + * This routine sets up parameters with dqs_gating_mode equal to 1 and two + * ranks enabled. It then configures the core and tests for 1 or 2 ranks and + * full or half DQ width. It then resets the parameters to the original values. + * dram_para2 is updated with the rank and width findings. + */ +static int auto_scan_dram_rank_width(struct dram_para *para) +{ + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + unsigned int s1 = para->tpr13; + unsigned int s2 = para->para1; + + para->para1 = 0x00b000b0; + para->para2 = (para->para2 & ~0xf) | BIT(12); + + /* set DQS probe mode */ + para->tpr13 = (para->tpr13 & ~0x8) | BIT(2) | BIT(0); + + mctl_core_init(para); + + if (readl(&mctl_phy->pgsr0) & BIT(20)) + return 0; + + if (dqs_gate_detect(para) == 0) + return 0; + + para->tpr13 = s1; + para->para1 = s2; + + return 1; +} + +/* + * This routine determines the SDRAM topology. It first establishes the number + * of ranks and the DQ width. Then it scans the SDRAM address lines to establish + * the size of each rank. It then updates dram_tpr13 to reflect that the sizes + * are now known: a re-init will not repeat the autoscan. + */ +static int auto_scan_dram_config(struct dram_para *para) +{ + if (((para->tpr13 & BIT(14)) == 0) && + (auto_scan_dram_rank_width(para) == 0)) { + printf("ERROR: auto scan dram rank & width failed\n"); + return 0; + } + + if (((para->tpr13 & BIT(0)) == 0) && + (auto_scan_dram_size(para) == 0)) { + printf("ERROR: auto scan dram size failed\n"); + return 0; + } + + if ((para->tpr13 & BIT(15)) == 0) + para->tpr13 |= BIT(14) | BIT(13) | BIT(1) | BIT(0); + + return 1; +} + +static int init_dram(struct dram_para *para) +{ + struct sunxi_sramc_reg * const sramc = + (struct sunxi_sramc_reg *)SUNXI_SRAMC_BASE; + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY_BASE; + + u32 rc, mem_size_mb; + + debug("DRAM BOOT DRIVE INFO: %s\n", "V0.24"); + debug("DRAM CLK = %d MHz\n", para->clk); + debug("DRAM Type = %d (2:DDR2,3:DDR3)\n", para->type); + if ((para->odt_en & 0x1) == 0) + debug("DRAMC read ODT off\n"); + else + debug("DRAMC ZQ value: 0x%x\n", para->zq); + + /* Test ZQ status */ + if (para->tpr13 & BIT(16)) { + debug("DRAM only have internal ZQ\n"); + setbits_le32(&sramc->zq_cal_ctrl, BIT(8)); + writel(0, &sramc->zq_res_ctrl); + udelay(10); + } else { + clrbits_le32(&sramc->zq_cal_ctrl, 0x3); + writel(para->tpr13 & (1 << 16), (SUNXI_PRCM_BASE + ANALOG_PWROFF_GATING_REG)); + udelay(10); + clrsetbits_le32(&sramc->zq_cal_ctrl, 0x108, BIT(1)); + udelay(10); + setbits_le32(&sramc->zq_cal_ctrl, BIT(0)); + udelay(20); + debug("ZQ value = 0x%x\n", readl(&sramc->zq_res_status)); + } + + dram_voltage_set(para); + + /* Set SDRAM controller auto config */ + if ((para->tpr13 & BIT(0)) == 0) { + if (auto_scan_dram_config(para) == 0) { + printf("auto_scan_dram_config() failed\n"); + return 0; + } + } + + /* report ODT */ + rc = para->mr1; + if ((rc & 0x44) == 0) + debug("DRAM ODT off\n"); + else + debug("DRAM ODT value: 0x%x\n", rc); + + /* Init core, final run */ + if (mctl_core_init(para) == 0) { + printf("mctl_core_init() failed\n"); + return 0; + } + + /* Get SDRAM size + * You can set para2 to force set the dram size + * TODO: who ever puts a negative number in the top half? + */ + rc = para->para2; + if (rc & BIT(31)) { + rc = (rc >> 16) & ~BIT(15); + } else { + rc = DRAMC_get_dram_size(); + debug("DRAM: size = %dMB\n", rc); + para->para2 = (para->para2 & 0xffffu) | rc << 16; + } + mem_size_mb = rc; + + /* Enable hardware auto refresh */ + if (para->tpr13 & BIT(30)) { + rc = para->tpr8; + if (rc == 0) + rc = 0x10000200; + writel(rc, &mctl_phy->asrtc); + writel(0x40a, &mctl_phy->asrc); + setbits_le32(&mctl_phy->pwrctl, BIT(0)); + debug("Enable Auto SR\n"); + } else { + clrbits_le32(&mctl_phy->asrtc, 0xffff); + clrbits_le32(&mctl_phy->pwrctl, 0x1); + } + + /* Set HDR/DDR dynamic */ + if (para->tpr13 & BIT(9)) { + clrsetbits_le32(&mctl_phy->pgcr0, 0xf000, 0x5000); + } else { + if (para->type != SUNXI_DRAM_TYPE_LPDDR2) + clrbits_le32(&mctl_phy->pgcr0, 0xf000); + } + + /* Disable ZQ calibration */ + setbits_le32(&mctl_phy->zqcr, BIT(31)); + + /* Set VTF feature */ + if (para->tpr13 & BIT(8)) + writel(readl(&mctl_phy->vtfcr) | 0x300, &mctl_phy->vtfcr); + + /* Set PAD Hold */ + if (para->tpr13 & BIT(16)) + clrbits_le32(&mctl_phy->pgcr2, BIT(13)); + else + setbits_le32(&mctl_phy->pgcr2, BIT(13)); + + /* Set LPDDR3 ODT delay */ + if (para->type == SUNXI_DRAM_TYPE_LPDDR3) + clrsetbits_le32(&mctl_phy->odtcfg, 0xf0000, 0x1000); + + dram_enable_all_master(); + if (para->tpr13 & BIT(28)) { + if ((readl(SUNXI_RTC_BASE + SP_STDBY_FLAG_REG) & BIT(16)) || dramc_simple_wr_test(mem_size_mb, 4096)) + return 0; + } + + return mem_size_mb; +} + +unsigned long sunxi_dram_init(void) +{ + struct dram_para para = { + .clk = CONFIG_DRAM_CLK, + .type = CONFIG_SUNXI_DRAM_TYPE, + .zq = CONFIG_DRAM_ZQ, + .odt_en = CONFIG_DRAM_SUNXI_ODT_EN, + .para1 = CONFIG_DRAM_SUNXI_PARA1, + .para2 = 0x0, + .mr1 = CONFIG_DRAM_SUNXI_MR1, + .mr3 = 0, + .tpr4 = 0, + .tpr5 = 0x48484848, + .tpr6 = 0x00000048, + .tpr8 = 0, + .tpr9 = 0, // clock? + .tpr10 = 0, + .tpr11 = CONFIG_DRAM_SUNXI_TPR11, + .tpr12 = CONFIG_DRAM_SUNXI_TPR12, + .tpr13 = CONFIG_DRAM_SUNXI_TPR13, + }; + + return init_dram(¶) * 1024UL * 1024; +} + +#ifdef CONFIG_RAM /* using the driver model */ +struct sunxi_ram_priv { + size_t size; +}; + +static int sunxi_ram_probe(struct udevice *dev) +{ + struct sunxi_ram_priv *priv = dev_get_priv(dev); + unsigned long dram_size; + + debug("%s: %s: probing\n", __func__, dev->name); + + dram_size = sunxi_dram_init(); + if (!dram_size) { + printf("DRAM init failed\n"); + return -ENODEV; + } + + priv->size = dram_size; + + return 0; +} + +static int sunxi_ram_get_info(struct udevice *dev, struct ram_info *info) +{ + struct sunxi_ram_priv *priv = dev_get_priv(dev); + + debug("%s: %s: getting info\n", __func__, dev->name); + + info->base = CONFIG_SYS_SDRAM_BASE; + info->size = priv->size; + + return 0; +} + +static struct ram_ops sunxi_ram_ops = { + .get_info = sunxi_ram_get_info, +}; + +static const struct udevice_id sunxi_ram_ids[] = { + { .compatible = "allwinner,sun20i-d1-mbus" }, + { } +}; + +U_BOOT_DRIVER(sunxi_ram) = { + .name = "sunxi_ram", + .id = UCLASS_RAM, + .of_match = sunxi_ram_ids, + .ops = &sunxi_ram_ops, + .probe = sunxi_ram_probe, + .priv_auto = sizeof(struct sunxi_ram_priv), +}; +#endif /* CONFIG_RAM (using driver model) */ diff --git a/arch/arm/mach-sunxi/dram_sun20i_d1.h b/arch/arm/mach-sunxi/dram_sun20i_d1.h new file mode 100644 index 000000000000..e093700f50f7 --- /dev/null +++ b/arch/arm/mach-sunxi/dram_sun20i_d1.h @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * D1/R528/T113 DRAM controller register and constant defines + * + * (C) Copyright 2022 Arm Ltd. + * (C) Copyright 2024 Iscle + * + * Based on H6 and H616 header, which are: + * (C) Copyright 2017 Icenowy Zheng + * (C) Copyright 2020 Jernej Skrabec + * + */ + +#ifndef _SUNXI_DRAM_SUN20I_D1_H +#define _SUNXI_DRAM_SUN20I_D1_H + +enum sunxi_dram_type { + SUNXI_DRAM_TYPE_DDR2 = 2, + SUNXI_DRAM_TYPE_DDR3 = 3, + SUNXI_DRAM_TYPE_LPDDR2 = 6, + SUNXI_DRAM_TYPE_LPDDR3 = 7, +}; + +struct sunxi_sidc_reg { + u8 reserved_0x00[0x21c]; /* 0x000 */ + u32 ldob_sid; /* 0x21c */ + u8 reserved_0x220[0x8]; /* 0x220 */ + u32 efuse; /* 0x228 */ +}; +check_member(sunxi_sidc_reg, ldob_sid, 0x21c); +check_member(sunxi_sidc_reg, efuse, 0x228); + +struct sunxi_sramc_reg { + u8 reserved_0x00[0x150]; /* 0x000 */ + u32 ldo_ctal; /* 0x150 */ + u8 reserved_0x154[0xc]; /* 0x154 */ + u32 zq_cal_ctrl; /* 0x160 */ + u8 reserved_0x164[0x4]; /* 0x164 */ + u32 zq_res_ctrl; /* 0x168 */ + u32 zq_res_status; /* 0x16c */ +}; +check_member(sunxi_sramc_reg, ldo_ctal, 0x150); +check_member(sunxi_sramc_reg, zq_cal_ctrl, 0x160); +check_member(sunxi_sramc_reg, zq_res_ctrl, 0x168); + +struct sunxi_mctl_com_reg { + u32 work_mode0; /* 0x000 */ + u32 work_mode1; /* 0x004 */ + u32 dbgcr; /* 0x008 */ + u32 tmr; /* 0x00c */ + u8 reserved_0x10[0x4]; /* 0x010 */ + u32 cccr; /* 0x014 */ + u8 reserved_0x18[0x8]; /* 0x018 */ + u32 maer0; /* 0x020 */ + u32 maer1; /* 0x024 */ + u32 maer2; /* 0x028 */ + u8 reserved_0x2c[0x4d4]; /* 0x02c */ + u32 remap0; /* 0x500 */ + u32 remap1; /* 0x504 */ + u32 remap2; /* 0x508 */ + u32 remap3; /* 0x50c */ +}; +check_member(sunxi_mctl_com_reg, cccr, 0x014); +check_member(sunxi_mctl_com_reg, maer0, 0x020); +check_member(sunxi_mctl_com_reg, remap0, 0x500); + +struct sunxi_mctl_phy_reg { + u32 pir; /* 0x000 */ + u32 pwrctl; /* 0x004 */ + u32 mrctrl0; /* 0x008 */ + u32 clken; /* 0x00c */ + u32 pgsr0; /* 0x010 */ + u32 pgsr1; /* 0x014 */ + u32 statr; /* 0x018 */ + u8 reserved_0x1c[0x10]; /* 0x01c */ + u32 lp3mr11; /* 0x02c */ + u32 dram_mr0; /* 0x030 */ + u32 dram_mr1; /* 0x034 */ + u32 dram_mr2; /* 0x038 */ + u32 dram_mr3; /* 0x03c */ + u8 reserved_0x40[0x4]; /* 0x040 */ + u32 ptr0; /* 0x044 */ + u8 reserved_0x48[0x4]; /* 0x048 */ + u32 ptr2; /* 0x04c */ + u32 ptr3; /* 0x050 */ + u32 ptr4; /* 0x054 */ + u32 dramtmg0; /* 0x058 */ + u32 dramtmg1; /* 0x05c */ + u32 dramtmg2; /* 0x060 */ + u32 dramtmg3; /* 0x064 */ + u32 dramtmg4; /* 0x068 */ + u32 dramtmg5; /* 0x06c */ + u32 dramtmg6; /* 0x070 */ + u32 dramtmg7; /* 0x074 */ + u32 dramtmg8; /* 0x078 */ + u32 odtcfg; /* 0x07c */ + u32 pitmg0; /* 0x080 */ + u32 pitmg1; /* 0x084 */ + u32 lptpr; /* 0x088 */ + u32 rfshctl0; /* 0x08c */ + u32 rfshtmg; /* 0x090 */ + u32 rfshctl1; /* 0x094 */ + u32 pwrtmg; /* 0x098 */ + u32 asrc; /* 0x09c */ + u32 asrtc; /* 0x0a0 */ + u8 reserved_0xa4[0x14]; /* 0x0a4 */ + u32 vtfcr; /* 0x0b8 */ + u32 dqsgmr; /* 0x0bc */ + u32 dtcr; /* 0x0c0 */ + u32 dtar0; /* 0x0c4 */ + u8 reserved_0xc8[0x38]; /* 0x0c8 */ + u32 pgcr0; /* 0x100 */ + u32 pgcr1; /* 0x104 */ + u32 pgcr2; /* 0x108 */ + u32 pgcr3; /* 0x10c */ + u32 iovcr0; /* 0x110 */ + u32 iovcr1; /* 0x114 */ + u8 reserved_0x118[0x4]; /* 0x118 */ + u32 dxccr; /* 0x11c */ + u32 odtmap; /* 0x120 */ + u32 zqctl0; /* 0x124 */ + u32 zqctl1; /* 0x128 */ + u8 reserved_0x12c[0x14]; /* 0x12c */ + u32 zqcr; /* 0x140 */ + u32 zqsr; /* 0x144 */ + u32 zqdr0; /* 0x148 */ + u32 zqdr1; /* 0x14c */ + u32 zqdr2; /* 0x150 */ + u8 reserved_0x154[0x6c]; /* 0x154 */ + u32 sched; /* 0x1c0 */ + u32 perfhpr0; /* 0x1c4 */ + u32 perfhpr1; /* 0x1c8 */ + u32 perflpr0; /* 0x1cc */ + u32 perflpr1; /* 0x1d0 */ + u32 perfwr0; /* 0x1d4 */ + u32 perfwr1; /* 0x1d8 */ + u8 reserved_0x1dc[0x24]; /* 0x1dc */ + u32 acmdlr; /* 0x200 */ + u32 acldlr; /* 0x204 */ + u32 aciocr0; /* 0x208 */ +}; +check_member(sunxi_mctl_phy_reg, lp3mr11, 0x02c); +check_member(sunxi_mctl_phy_reg, ptr0, 0x044); +check_member(sunxi_mctl_phy_reg, ptr2, 0x04c); +check_member(sunxi_mctl_phy_reg, vtfcr, 0x0b8); +check_member(sunxi_mctl_phy_reg, pgcr0, 0x100); +check_member(sunxi_mctl_phy_reg, dxccr, 0x11c); +check_member(sunxi_mctl_phy_reg, zqcr, 0x140); +check_member(sunxi_mctl_phy_reg, sched, 0x1c0); +check_member(sunxi_mctl_phy_reg, acmdlr, 0x200); + +#define ACIOCR1(x) (SUNXI_DRAM_PHY_BASE + 0x210 + 0x4 * x) +#define DXnMDLR(x) (SUNXI_DRAM_PHY_BASE + 0x300 + 0x80 * x) +#define DXnLDLR0(x) (SUNXI_DRAM_PHY_BASE + 0x304 + 0x80 * x) +#define DXnLDLR1(x) (SUNXI_DRAM_PHY_BASE + 0x308 + 0x80 * x) +#define DXnLDLR2(x) (SUNXI_DRAM_PHY_BASE + 0x30c + 0x80 * x) +#define DXIOCR (SUNXI_DRAM_PHY_BASE + 0x310) +#define DATX0IOCR(x) (SUNXI_DRAM_PHY_BASE + 0x310 + 0x4 * x) +#define DATX1IOCR(x) (SUNXI_DRAM_PHY_BASE + 0x390 + 0x4 * x) +#define DATX2IOCR(x) (SUNXI_DRAM_PHY_BASE + 0x410 + 0x4 * x) +#define DATX3IOCR(x) (SUNXI_DRAM_PHY_BASE + 0x490 + 0x4 * x) +#define DXnSDLR6(x) (SUNXI_DRAM_PHY_BASE + 0x33c + 0x80 * x) +#define DXnGTR(x) (SUNXI_DRAM_PHY_BASE + 0x340 + 0x80 * x) +#define DXnGCR0(x) (SUNXI_DRAM_PHY_BASE + 0x344 + 0x80 * x) +#define DXnGSR0(x) (SUNXI_DRAM_PHY_BASE + 0x348 + 0x80 * x) + +/* + * This structure contains a mixture of fixed configuration settings, + * variables that are used at runtime to communicate settings between + * different stages and functions, and unused values. + * This is copied from Allwinner's boot0 data structure, which can be + * found at offset 0x38 in any boot0 binary. To allow matching up some + * board specific settings, this struct is kept compatible, even though + * we don't need all members in our code. + */ +struct dram_para { + /* normal configuration */ + const u32 clk; + const u32 type; + const u32 zq; + const u32 odt_en; + + /* control configuration */ + u32 para1; + u32 para2; + + /* timing configuration */ + const u32 mr0; + const u32 mr1; + const u32 mr2; + const u32 mr3; + const u32 tpr0; /* DRAMTMG0 */ + const u32 tpr1; /* DRAMTMG1 */ + const u32 tpr2; /* DRAMTMG2 */ + const u32 tpr3; /* DRAMTMG3 */ + const u32 tpr4; /* DRAMTMG4 */ + const u32 tpr5; /* DRAMTMG5 */ + const u32 tpr6; /* DRAMTMG8 */ + const u32 tpr7; + const u32 tpr8; + const u32 tpr9; + const u32 tpr10; + const u32 tpr11; + const u32 tpr12; + /* contains a bitfield of DRAM setup settings */ + u32 tpr13; +}; + +#endif /* _SUNXI_DRAM_SUN20I_D1_H */ diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index e1a80dbf3b58..004da852b8da 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -73,13 +73,20 @@ #define SUN6I_CTL_MASTER BIT(1) #define SUN6I_CTL_SRST BIT(31) #define SUN6I_TCR_XCH BIT(31) +#define SUN6I_TCR_SDM BIT(13) /*****************************************************************************/ #define CCM_AHB_GATING0 (0x01C20000 + 0x60) -#define CCM_H6_SPI_BGR_REG (0x03001000 + 0x96c) -#if defined(CONFIG_MACH_SUN50I_H6) || defined(CONFIG_MACH_SUN8I_V831) -#define CCM_SPI0_CLK (0x03001000 + 0x940) +#ifdef CONFIG_SUNXI_GEN_NCAT2 +#define CCM_H6_BASE 0x02001000 +#else +#define CCM_H6_BASE 0x03001000 +#endif +#define CCM_H6_SPI_BGR_REG (CCM_H6_BASE + 0x96c) +#if defined(CONFIG_MACH_SUN50I_H6) || defined(CONFIG_MACH_SUN8I_V831) || \ + defined(CONFIG_SUNXI_GEN_NCAT2) +#define CCM_SPI0_CLK (CCM_H6_BASE + 0x940) #else #define CCM_SPI0_CLK (0x01C20000 + 0xA0) #endif @@ -100,11 +107,15 @@ * The H6 uses PC0, PC2, PC3, PC5. */ -/* The V831/V833 have the H6's SPI controller, at the H6's address */ +/* + * The V831/V833 have the H6's SPI controller, at the H6's address; the + * NCAT2 SoCs (V851s) the same with their own CCU and SPI0 addresses. + */ static bool is_h6_spi(void) { return IS_ENABLED(CONFIG_MACH_SUN50I_H6) || - IS_ENABLED(CONFIG_MACH_SUN8I_V831); + IS_ENABLED(CONFIG_MACH_SUN8I_V831) || + IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2); } static void spi0_pinmux_setup(unsigned int pin_function) { @@ -118,6 +129,12 @@ static void spi0_pinmux_setup(unsigned int pin_function) else sunxi_gpio_set_cfgpin(SUNXI_GPC(5), pin_function); + /* The V851s boot ROM sets up all of PC0-PC5 (with HOLD, WP) */ + if (IS_ENABLED(CONFIG_MACH_SUN8I_V851S)) { + sunxi_gpio_set_cfgpin(SUNXI_GPC(4), pin_function); + sunxi_gpio_set_cfgpin(SUNXI_GPC(5), pin_function); + } + /* Older generations use PC23 for CS, newer ones use PC3. */ if (IS_ENABLED(CONFIG_MACH_SUN4I) || IS_ENABLED(CONFIG_MACH_SUN7I) || IS_ENABLED(CONFIG_MACH_SUN8I_R40)) @@ -136,6 +153,9 @@ static uintptr_t spi0_base_address(void) if (IS_ENABLED(CONFIG_MACH_SUN8I_R40)) return 0x01C05000; + if (IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) + return 0x04025000; + if (is_h6_spi()) return 0x05010000; @@ -168,9 +188,10 @@ static void spi0_enable_clock(void) /* Divide by 32, clock source is AHB clock 200MHz */ writel(SPI0_CLK_DIV_BY_32, base + SUN6I_SPI0_CCTL); } else { - /* Divide by 4 */ - writel(SPI0_CLK_DIV_BY_4, base + (is_sun6i_gen_spi() ? - SUN6I_SPI0_CCTL : SUN4I_SPI0_CCTL)); + /* Divide by 4; NCAT2 controllers have no divider inside */ + if (!IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) + writel(SPI0_CLK_DIV_BY_4, base + (is_sun6i_gen_spi() ? + SUN6I_SPI0_CCTL : SUN4I_SPI0_CCTL)); /* 24MHz from OSC24M */ writel((1 << 31), CCM_SPI0_CLK); } @@ -182,6 +203,13 @@ static void spi0_enable_clock(void) /* Wait for completion */ while (readl(base + SUN6I_SPI0_GCR) & SUN6I_CTL_SRST) ; + + /* + * NCAT2: the sample mode depends on the input clock; from + * the 24 MHz OSC24M, normal sampling (TCR SDM) + */ + if (IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) + setbits_le32(base + SUN6I_SPI0_TCR, SUN6I_TCR_SDM); } else { /* Enable SPI in the master mode and reset FIFO */ setbits_le32(base + SUN4I_SPI0_CTL, SUN4I_CTL_MASTER | diff --git a/build.sh b/build.sh index 6a367d583aea..4adae07c5c8b 100755 --- a/build.sh +++ b/build.sh @@ -81,7 +81,7 @@ build_v83x() { build_soc() { soc=$1 case "${soc}" in - v831|v833) + v3s|v831|v833|v851s) build_v83x "${soc}" nor && build_v83x "${soc}" mmc ;; *) echo "unsupported SoC: ${soc}" >&2 @@ -89,7 +89,7 @@ build_soc() { esac } -ALL_SOCS="v831 v833" +ALL_SOCS="v3s v831 v833 v851s" SOCS="$@" [ -z "${SOCS}" ] && SOCS="${ALL_SOCS}" diff --git a/common/spl/Kconfig b/common/spl/Kconfig index 9ab484ee655f..f50b0750f904 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -117,7 +117,7 @@ config SPL_BSS_START_ADDR default 0x18200000 if ARCH_MX6 && !(MX6SX || MX6SL || MX6UL || MX6ULL) default 0x80a00000 if ARCH_OMAP2PLUS default 0x81f80000 if ARCH_SUNXI && MACH_SUNIV - default 0x43f80000 if ARCH_SUNXI && MACH_SUN8I_V831 + default 0x43f80000 if ARCH_SUNXI && (MACH_SUN8I_V831 || MACH_SUN8I_V851S) default 0x4ff80000 if ARCH_SUNXI && !(MACH_SUN9I || MACH_SUNIV) default 0x2ff80000 if ARCH_SUNXI && MACH_SUN9I default 0x1000 if ARCH_ZYNQMP @@ -263,7 +263,7 @@ config SPL_TEXT_BASE default 0x402F0400 if AM33XX default 0x40301350 if OMAP54XX default 0x10060 if MACH_SUN50I || MACH_SUN50I_H5 || MACH_SUN9I - default 0x20060 if SUN50I_GEN_H6 + default 0x20060 if SUN50I_GEN_H6 || SUNXI_GEN_NCAT2 default 0x00060 if ARCH_SUNXI default 0xfffc0000 if ARCH_ZYNQMP default 0x0 diff --git a/drivers/clk/sunxi/Kconfig b/drivers/clk/sunxi/Kconfig index 27753aefb92c..337101ea0371 100644 --- a/drivers/clk/sunxi/Kconfig +++ b/drivers/clk/sunxi/Kconfig @@ -89,7 +89,7 @@ config CLK_SUN8I_H3 config CLK_SUN50I_H6 bool "Clock driver for Allwinner H6" - default MACH_SUN50I_H6 || MACH_SUN8I_V831 + default MACH_SUN50I_H6 || MACH_SUN8I_V831 || MACH_SUN8I_V851S help This enables common clock driver support for platforms based on Allwinner H6 SoC. diff --git a/drivers/clk/sunxi/clk_sunxi.c b/drivers/clk/sunxi/clk_sunxi.c index d08602f9e673..b68ddf98f70d 100644 --- a/drivers/clk/sunxi/clk_sunxi.c +++ b/drivers/clk/sunxi/clk_sunxi.c @@ -198,6 +198,8 @@ static const struct udevice_id sunxi_clk_ids[] = { #ifdef CONFIG_CLK_SUN50I_H6 { .compatible = "allwinner,sun50i-h6-ccu", .data = (ulong)&h6_ccu_desc }, + { .compatible = "allwinner,sun8i-v851s-ccu", + .data = (ulong)&h6_ccu_desc }, { .compatible = "allwinner,sun8i-v831-ccu", .data = (ulong)&h6_ccu_desc }, #endif diff --git a/drivers/clk/sunxi/clk_v3s.c b/drivers/clk/sunxi/clk_v3s.c index f2fd11eac2c8..bb126bebdada 100644 --- a/drivers/clk/sunxi/clk_v3s.c +++ b/drivers/clk/sunxi/clk_v3s.c @@ -15,6 +15,7 @@ static struct ccu_clk_gate v3s_gates[] = { [CLK_BUS_MMC0] = GATE(0x060, BIT(8)), + [CLK_BUS_EMAC] = GATE(0x060, BIT(17)), [CLK_BUS_MMC1] = GATE(0x060, BIT(9)), [CLK_BUS_MMC2] = GATE(0x060, BIT(10)), [CLK_BUS_SPI0] = GATE(0x060, BIT(20)), @@ -24,6 +25,7 @@ static struct ccu_clk_gate v3s_gates[] = { [CLK_BUS_I2C0] = GATE(0x06c, BIT(0)), [CLK_BUS_I2C1] = GATE(0x06c, BIT(1)), + [CLK_BUS_EPHY] = GATE(0x070, BIT(0)), [CLK_BUS_UART0] = GATE(0x06c, BIT(16)), [CLK_BUS_UART1] = GATE(0x06c, BIT(17)), [CLK_BUS_UART2] = GATE(0x06c, BIT(18)), @@ -37,6 +39,7 @@ static struct ccu_reset v3s_resets[] = { [RST_USB_PHY0] = RESET(0x0cc, BIT(0)), [RST_BUS_MMC0] = RESET(0x2c0, BIT(8)), + [RST_BUS_EMAC] = RESET(0x2c0, BIT(17)), [RST_BUS_MMC1] = RESET(0x2c0, BIT(9)), [RST_BUS_MMC2] = RESET(0x2c0, BIT(10)), [RST_BUS_SPI0] = RESET(0x2c0, BIT(20)), @@ -44,6 +47,7 @@ static struct ccu_reset v3s_resets[] = { [RST_BUS_I2C0] = RESET(0x2d8, BIT(0)), [RST_BUS_I2C1] = RESET(0x2d8, BIT(1)), + [RST_BUS_EPHY] = RESET(0x2c8, BIT(2)), [RST_BUS_UART0] = RESET(0x2d8, BIT(16)), [RST_BUS_UART1] = RESET(0x2d8, BIT(17)), [RST_BUS_UART2] = RESET(0x2d8, BIT(18)), diff --git a/drivers/mmc/sunxi_mmc.c b/drivers/mmc/sunxi_mmc.c index 1bb7b6d0e92b..11cd207fdc5b 100644 --- a/drivers/mmc/sunxi_mmc.c +++ b/drivers/mmc/sunxi_mmc.c @@ -113,6 +113,7 @@ static bool sunxi_mmc_can_calibrate(void) return IS_ENABLED(CONFIG_MACH_SUN50I) || IS_ENABLED(CONFIG_MACH_SUN50I_H5) || IS_ENABLED(CONFIG_SUN50I_GEN_H6) || + IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) || IS_ENABLED(CONFIG_MACH_SUN8I_R40); } @@ -247,7 +248,8 @@ static int mmc_config_clock(struct sunxi_mmc_priv *priv, struct mmc *mmc) rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK; writel(rval, &priv->reg->clkcr); -#if defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) +#if defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) || \ + defined(CONFIG_SUNXI_GEN_NCAT2) /* A64 supports calibration of delays on MMC controller and we * have to set delay of zero before starting calibration. * Allwinner BSP driver sets a delay only in the case of @@ -558,7 +560,8 @@ struct mmc *sunxi_mmc_init(int sdc_no) cfg->host_caps = MMC_MODE_4BIT; if ((IS_ENABLED(CONFIG_MACH_SUN50I) || IS_ENABLED(CONFIG_MACH_SUN8I) || - IS_ENABLED(CONFIG_SUN50I_GEN_H6)) && (sdc_no == 2)) + IS_ENABLED(CONFIG_SUN50I_GEN_H6) || + IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) && (sdc_no == 2)) cfg->host_caps = MMC_MODE_8BIT; cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; @@ -572,7 +575,7 @@ struct mmc *sunxi_mmc_init(int sdc_no) /* config ahb clock */ debug("init mmc %d clock and io\n", sdc_no); -#if !defined(CONFIG_SUN50I_GEN_H6) +#if !defined(CONFIG_SUN50I_GEN_H6) && !defined(CONFIG_SUNXI_GEN_NCAT2) setbits_le32(&ccm->ahb_gate0, 1 << AHB_GATE_OFFSET_MMC(sdc_no)); #ifdef CONFIG_SUNXI_GEN_SUN6I @@ -646,7 +649,8 @@ static unsigned get_mclk_offset(void) if (IS_ENABLED(CONFIG_MACH_SUN9I_A80)) return 0x410; - if (IS_ENABLED(CONFIG_SUN50I_GEN_H6)) + if (IS_ENABLED(CONFIG_SUN50I_GEN_H6) || + IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) return 0x830; return 0x88; diff --git a/drivers/net/sun8i_emac.c b/drivers/net/sun8i_emac.c index 9cca8fa4e0a1..facd161efd53 100644 --- a/drivers/net/sun8i_emac.c +++ b/drivers/net/sun8i_emac.c @@ -902,6 +902,7 @@ static int sun8i_emac_eth_of_to_plat(struct udevice *dev) static const struct udevice_id sun8i_emac_eth_ids[] = { {.compatible = "allwinner,sun8i-h3-emac", .data = (uintptr_t)H3_EMAC }, + {.compatible = "allwinner,sun8i-v3s-emac", .data = (uintptr_t)H3_EMAC }, {.compatible = "allwinner,sun50i-a64-emac", .data = (uintptr_t)A64_EMAC }, {.compatible = "allwinner,sun8i-a83t-emac", diff --git a/drivers/pinctrl/sunxi/Kconfig b/drivers/pinctrl/sunxi/Kconfig index e02b6fe0b40e..d99e2e7991a4 100644 --- a/drivers/pinctrl/sunxi/Kconfig +++ b/drivers/pinctrl/sunxi/Kconfig @@ -114,6 +114,11 @@ config PINCTRL_SUN8I_V831 default MACH_SUN8I_V831 select PINCTRL_SUNXI +config PINCTRL_SUN8I_V851S + bool "Support for the Allwinner V851s PIO" + default MACH_SUN8I_V851S + select PINCTRL_SUNXI + config PINCTRL_SUN50I_H6_R bool "Support for the Allwinner H6 R-PIO" default MACH_SUN50I_H6 diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index add1f4c632a3..dd82f477a3ee 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -706,6 +706,22 @@ static const struct sunxi_pinctrl_desc __maybe_unused sun8i_v831_pinctrl_desc = .num_banks = 9, }; +static const struct sunxi_pinctrl_function sun8i_v851s_pinctrl_functions[] = { + { "gpio_in", 0 }, + { "gpio_out", 1 }, + { "mmc0", 2 }, /* PF0-PF5 */ + { "mmc1", 2 }, /* PG0-PG5 */ + { "spi0", 4 }, /* PC0-PC5 */ + { "uart0", 5 }, /* PH9-PH10 */ +}; + +static const struct sunxi_pinctrl_desc __maybe_unused sun8i_v851s_pinctrl_desc = { + .functions = sun8i_v851s_pinctrl_functions, + .num_functions = ARRAY_SIZE(sun8i_v851s_pinctrl_functions), + .first_bank = SUNXI_GPIO_A, + .num_banks = 9, +}; + static const struct sunxi_pinctrl_function sun50i_h616_pinctrl_functions[] = { { "emac0", 2 }, /* PI0-PI16 */ { "gpio_in", 0 }, @@ -894,6 +910,12 @@ static const struct udevice_id sunxi_pinctrl_ids[] = { .data = (ulong)&sun8i_v831_pinctrl_desc, }, #endif +#ifdef CONFIG_PINCTRL_SUN8I_V851S + { + .compatible = "allwinner,sun8i-v851s-pinctrl", + .data = (ulong)&sun8i_v851s_pinctrl_desc, + }, +#endif #ifdef CONFIG_PINCTRL_SUN50I_H616 { .compatible = "allwinner,sun50i-h616-pinctrl", diff --git a/drivers/spi/spi-sunxi.c b/drivers/spi/spi-sunxi.c index c56d82d998ad..edea754f33f4 100644 --- a/drivers/spi/spi-sunxi.c +++ b/drivers/spi/spi-sunxi.c @@ -117,6 +117,8 @@ enum sun4i_spi_bits { SPI_TCR_XCH, SPI_TCR_CS_MANUAL, SPI_TCR_CS_LEVEL, + SPI_TCR_SDC, + SPI_TCR_SDM, SPI_FCR_TF_RST, SPI_FCR_RF_RST, SPI_FSR_RF_CNT_MASK, @@ -128,6 +130,7 @@ struct sun4i_spi_variant { u32 fifo_depth; bool has_soft_reset; bool has_burst_ctl; + bool has_clk_ctl; }; struct sun4i_spi_plat { @@ -302,7 +305,18 @@ static int sun4i_spi_claim_bus(struct udevice *dev) setbits_le32(SPI_REG(priv, SPI_TCR), SPI_BIT(priv, SPI_TCR_CS_MANUAL) | SPI_BIT(priv, SPI_TCR_CS_ACTIVE_LOW)); - sun4i_spi_set_speed_mode(dev->parent); + if (priv->variant->has_clk_ctl) { + sun4i_spi_set_speed_mode(dev->parent); + } else { + /* + * No divider inside (R329, D1, V851s): the module clock, the + * 24 MHz HOSC, is the bus clock; sample in the normal mode + */ + clrsetbits_le32(SPI_REG(priv, SPI_TCR), + SPI_BIT(priv, SPI_TCR_SDC) | + SPI_BIT(priv, SPI_TCR_SDM), + SPI_BIT(priv, SPI_TCR_SDM)); + } return 0; } @@ -516,6 +530,8 @@ static const u32 sun6i_spi_bits[] = { [SPI_TCR_CS_MASK] = 0x30, [SPI_TCR_CS_MANUAL] = BIT(6), [SPI_TCR_CS_LEVEL] = BIT(7), + [SPI_TCR_SDC] = BIT(11), + [SPI_TCR_SDM] = BIT(13), [SPI_TCR_XCH] = BIT(31), [SPI_FCR_RF_RST] = BIT(15), [SPI_FCR_TF_RST] = BIT(31), @@ -526,6 +542,7 @@ static const struct sun4i_spi_variant sun4i_a10_spi_variant = { .regs = sun4i_spi_regs, .bits = sun4i_spi_bits, .fifo_depth = 64, + .has_clk_ctl = true, }; static const struct sun4i_spi_variant sun6i_a31_spi_variant = { @@ -534,6 +551,7 @@ static const struct sun4i_spi_variant sun6i_a31_spi_variant = { .fifo_depth = 128, .has_soft_reset = true, .has_burst_ctl = true, + .has_clk_ctl = true, }; static const struct sun4i_spi_variant sun8i_h3_spi_variant = { @@ -542,6 +560,15 @@ static const struct sun4i_spi_variant sun8i_h3_spi_variant = { .fifo_depth = 64, .has_soft_reset = true, .has_burst_ctl = true, + .has_clk_ctl = true, +}; + +static const struct sun4i_spi_variant sun50i_r329_spi_variant = { + .regs = sun6i_spi_regs, + .bits = sun6i_spi_bits, + .fifo_depth = 64, + .has_soft_reset = true, + .has_burst_ctl = true, }; static const struct udevice_id sun4i_spi_ids[] = { @@ -557,6 +584,10 @@ static const struct udevice_id sun4i_spi_ids[] = { .compatible = "allwinner,sun8i-h3-spi", .data = (ulong)&sun8i_h3_spi_variant, }, + { + .compatible = "allwinner,sun50i-r329-spi", + .data = (ulong)&sun50i_r329_spi_variant, + }, { /* sentinel */ } }; diff --git a/include/configs/openipc-common.h b/include/configs/openipc-common.h index db8bc7a60c22..dbb6e7ee736a 100644 --- a/include/configs/openipc-common.h +++ b/include/configs/openipc-common.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* - * Common OpenIPC configuration for the Allwinner V831/V833 targets. + * Common OpenIPC configuration for the Allwinner targets. * * The single place for the default environment, kept in step with * include/configs/openipc-common.h on the openipc-hisilicon branch: the @@ -26,10 +26,9 @@ /* * Kernel memory (mem=): all of the DDR in the package, as the vendor * U-Boot leaves it (it passes no mem=); the vendor media drivers take - * their buffers from the kernel's CMA. DDR3 is the V833's 128 MiB, DDR2 - * the V831's 64 MiB. + * their buffers from the kernel's CMA. */ -#if CONFIG_DRAM_SUN8I_V831_TYPE == 3 +#if PHYS_SDRAM_1_SIZE == 0x08000000 #define OPENIPC_OSMEM "128M" #else #define OPENIPC_OSMEM "64M" diff --git a/include/configs/v83x.h b/include/configs/openipc-sunxi.h similarity index 66% rename from include/configs/v83x.h rename to include/configs/openipc-sunxi.h index a863d52cb2ff..0b8fc1da1714 100644 --- a/include/configs/v83x.h +++ b/include/configs/openipc-sunxi.h @@ -1,12 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* - * Allwinner V831/V833 (sun8iw19p1) with the OpenIPC environment + * Allwinner camera SoCs with the OpenIPC environment: V3s, V831/V833, + * V851s */ #ifndef __CONFIG_H #define __CONFIG_H #include +#ifdef CONFIG_MACH_SUN8I_V831 /* * The nonsec switch finds the GIC through CBAR otherwise; name it, as * on the arndale: QEMU's Cortex-A7 reads CBAR as 0 @@ -19,6 +21,16 @@ #else #define PHYS_SDRAM_1_SIZE 0x04000000 #endif +#endif + +#ifdef CONFIG_MACH_SUN8I_V851S +#define CONFIG_ARM_GIC_BASE_ADDRESS SUNXI_GIC400_BASE +#endif + +#if defined(CONFIG_MACH_SUN8I_V3S) || defined(CONFIG_MACH_SUN8I_V851S) +/* 64 MiB of DDR2 in the package */ +#define PHYS_SDRAM_1_SIZE 0x04000000 +#endif /* The OpenIPC environment instead of sunxi-common's distro one */ #ifdef CONFIG_OPENIPC diff --git a/openipc/openipc_config b/openipc/openipc_config index 1f3494e5aab7..6cb753e2a6bd 100644 --- a/openipc/openipc_config +++ b/openipc/openipc_config @@ -1,11 +1,8 @@ -# OpenIPC base configuration for the Allwinner V831/V833 targets. build.sh +# OpenIPC base configuration for the Allwinner targets. build.sh # appends the per-SoC fragment and runs olddefconfig, so a later line # overrides an earlier one. CONFIG_ARM=y CONFIG_ARCH_SUNXI=y -CONFIG_MACH_SUN8I_V831=y -CONFIG_DEFAULT_DEVICE_TREE="sun8i-v831-openipc" -CONFIG_IDENT_STRING=" Allwinner V83x" CONFIG_CC_OPTIMIZE_FOR_SIZE=y # 32 MiB into the DDR: below what the smallest part (64 MiB) has CONFIG_SYS_TEXT_BASE=0x42000000 @@ -21,8 +18,6 @@ CONFIG_MISC_INIT_R=y # SPL: the boot ROM loads it from SPI NOR offset 0 (eGON.BT0) into SRAM; # it brings up clocks and DDR, then loads U-Boot from offset 0x8000 CONFIG_SPL=y -# end of SRAM A2, as on the H6 -CONFIG_SPL_STACK=0x118000 CONFIG_SPL_SPI_SUNXI=y CONFIG_SYS_SPI_U_BOOT_OFFS=0x8000 # U-Boot is LZMA-packed (build.sh), to fit the 256 KiB boot partition @@ -35,12 +30,11 @@ CONFIG_SPL_LZMA=y # CONFIG_SPL_DOS_PARTITION is not set # CONFIG_SPL_EFI_PARTITION is not set -# 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 -CONFIG_ARMV7_PSCI=y -CONFIG_ARMV7_PSCI_1_0=y -CONFIG_ARMV7_PSCI_NR_CPUS=1 +# Kernel hand-off: ATAGs for the kernels with their device tree appended +# (V3s, V851s); the V831/V833 fragments add theirs +CONFIG_SUPPORT_PASSING_ATAGS=y +CONFIG_SETUP_MEMORY_TAGS=y +CONFIG_CMDLINE_TAG=y # Console: UART0 CONFIG_CONS_INDEX=1 diff --git a/openipc/v3s_config b/openipc/v3s_config new file mode 100644 index 000000000000..d7135cc296f1 --- /dev/null +++ b/openipc/v3s_config @@ -0,0 +1,9 @@ +# V3s: 64 MiB of DDR2 in the package, mainline's V3s DRAM driver +# (dram_sunxi_dw, SUNXI_DRAM_DDR2_V3S) at mainline's 360 MHz +CONFIG_PRODUCT_SOC="v3s" +CONFIG_MACH_SUN8I_V3S=y +CONFIG_DEFAULT_DEVICE_TREE="sun8i-v3s-openipc" +CONFIG_IDENT_STRING=" Allwinner V3s" +CONFIG_DRAM_CLK=360 +# SPL stack: the end of the first 32 KiB of SRAM, as on the LicheePi Zero +CONFIG_SPL_STACK=0x8000 diff --git a/openipc/v831_config b/openipc/v831_config index 24761f95ce46..ee1b8b71de3d 100644 --- a/openipc/v831_config +++ b/openipc/v831_config @@ -1,6 +1,17 @@ # V831: 64 MiB of DDR2 in the package, the vendor's parameters (the # v831 reference boards' sys_config.fex, the same on every V831 flash seen) 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 +# 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 +CONFIG_ARMV7_PSCI=y +CONFIG_ARMV7_PSCI_1_0=y +CONFIG_ARMV7_PSCI_NR_CPUS=1 CONFIG_DRAM_CLK=528 CONFIG_DRAM_ZQ=3065 CONFIG_DRAM_SUN8I_V831_TYPE=2 diff --git a/openipc/v833_config b/openipc/v833_config index 68a35aee6f78..1d2b4f613609 100644 --- a/openipc/v833_config +++ b/openipc/v833_config @@ -2,6 +2,17 @@ # lindenis board's sys_config.fex); tpr13 bit 0 clear: the rank, width # and size are scanned 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 +# 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 +CONFIG_ARMV7_PSCI=y +CONFIG_ARMV7_PSCI_1_0=y +CONFIG_ARMV7_PSCI_NR_CPUS=1 CONFIG_DRAM_CLK=792 CONFIG_DRAM_ZQ=3067 CONFIG_DRAM_SUN8I_V831_TYPE=3 diff --git a/openipc/v851s_config b/openipc/v851s_config new file mode 100644 index 000000000000..8e1c91bfec41 --- /dev/null +++ b/openipc/v851s_config @@ -0,0 +1,15 @@ +# V851s: 64 MiB of DDR2 in the package, mainline's D1 DRAM driver with +# the parameters of OpenIPC's hardware-tested V851s U-Boot (mainline +# 2024.10), the Kconfig defaults +CONFIG_PRODUCT_SOC="v851s" +CONFIG_MACH_SUN8I_V851S=y +CONFIG_DEFAULT_DEVICE_TREE="sun8i-v851s-openipc" +CONFIG_IDENT_STRING=" Allwinner V851s" +# SPL stack: the end of SRAM A1 (0x20000-0x3ffff) +CONFIG_SPL_STACK=0x40000 +# Kernel hand-off: non-secure, PSCI from U-Boot's own monitor, as the +# OpenIPC V851s U-Boot does +CONFIG_ARMV7_NONSEC=y +CONFIG_ARMV7_PSCI=y +CONFIG_ARMV7_PSCI_1_0=y +CONFIG_ARMV7_PSCI_NR_CPUS=1 From a53b942c3f089ab4ce6bf5c7f0e779483b19afb8 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:07:27 +0000 Subject: [PATCH 3/4] docs: CLAUDE.md: the V3s and V851s; the DRAM scan under qemu-bench --- CLAUDE.md | 133 ++++++++++++++++++++------ arch/arm/mach-sunxi/dram_sun8i_v831.c | 2 +- 2 files changed, 104 insertions(+), 31 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 160089a6c818..1fdd0e3821d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,9 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## What this branch is -`openipc-allwinner` is OpenIPC's U-Boot for the Allwinner V831 and V833 -(sun8iw19p1): one Cortex-A7 with the H6's peripherals, clocks and memory -map, and the DDR in the package (V831: 64 MiB DDR2, V833: 128 MiB DDR3). +`openipc-allwinner` is OpenIPC's U-Boot for the Allwinner camera SoCs, +each a single Cortex-A7 with its DDR in the package: + +| SoC | Generation | DDR | DRAM driver | Kernel hand-off | +| --- | --- | --- | --- | --- | +| V3s (sun8iw8) | H3 (`SUNXI_GEN_SUN6I`) | 64 MiB DDR2 | mainline `dram_sunxi_dw.c` | ATAGs, secure SVC | +| V831 (sun8iw19) | H6 (`SUN50I_GEN_H6`) | 64 MiB DDR2 | `dram_sun8i_v831.c` | built-in DTB, HYP + PSCI | +| V833 (sun8iw19) | H6 | 128 MiB DDR3 | `dram_sun8i_v831.c` | built-in DTB, HYP + PSCI | +| V851s (sun8iw21) | D1/T113 (`SUNXI_GEN_NCAT2`) | 64 MiB DDR2 | `dram_sun20i_d1.c` | ATAGs, HYP + PSCI | + The tree is **mainline U-Boot v2022.10**, the same release as the other `openipc-*` branches, plus: @@ -14,9 +21,14 @@ The tree is **mainline U-Boot v2022.10**, the same release as the other (`common/openipc.c`, `include/openipc.h`, `include/configs/openipc-common.h`); - the V831/V833 support, as a new mainline `ARCH_SUNXI` SoC, `MACH_SUN8I_V831`, with its own SPL: no vendor boot0, OP-TEE or - boot package is needed or carried. - -The DRAM initialisation is the vendor boot0's DRAM library (`libdram`, + boot package is needed or carried; +- the V851s, `MACH_SUN8I_V851S`, and with it the D1 generation + (`SUNXI_GEN_NCAT2`, `SUNXI_NEW_PINCTRL`) backported from mainline + 2023-2024 as far as U-Boot uses it; +- the V3s, mainline's `MACH_SUN8I_V3S` as it is, with the OpenIPC + environment, its EMAC and the SPI NOR SPL. + +The V831/V833 DRAM initialisation is the vendor boot0's DRAM library (`libdram`, shipped only as a binary object), translated to C function for function (`arch/arm/mach-sunxi/dram_sun8i_v831.c`). It is the D1/T113 controller of mainline's `drivers/ram/sunxi/dram_sun20i_d1.c`, in an older revision of @@ -46,32 +58,50 @@ git diff --diff-filter=M v2022.10 HEAD # the mainline files we patched | Area | Location | | --- | --- | -| DRAM controller (SPL) | `arch/arm/mach-sunxi/dram_sun8i_v831.c`, `arch/arm/include/asm/arch-sunxi/dram_sun8i_v831.h` | -| PSCI monitor: version, features, SYSTEM_RESET (watchdog), CPU_OFF | `arch/arm/cpu/armv7/sunxi/psci_v831.c` | -| U-Boot's device tree | `arch/arm/dts/sun8i-v831.dtsi`, `sun8i-v831-openipc.dts` | -| Board: PHY clock, the kernel's device tree | `board/sunxi/v83x.c`, `v83x_kernel_dtb.S`, `v83x-kernel.dts` | -| Config header | `include/configs/v83x.h` (sunxi-common.h, then `openipc-common.h`) | +| DRAM controller (SPL), V831/V833 | `arch/arm/mach-sunxi/dram_sun8i_v831.c`, `arch/arm/include/asm/arch-sunxi/dram_sun8i_v831.h` | +| DRAM controller (SPL), V851s | `arch/arm/mach-sunxi/dram_sun20i_d1.{c,h}`: OpenIPC's V851s U-Boot's copy of mainline's `drivers/ram/sunxi/dram_sun20i_d1.c`, non-DM | +| NCAT2 memory map | `arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h` | +| PSCI monitor: version, features, SYSTEM_RESET (watchdog), CPU_OFF; V831/V833, V851s | `arch/arm/cpu/armv7/sunxi/psci_v831.c` | +| U-Boot's device trees | `arch/arm/dts/sun8i-v831.dtsi`, `sun8i-v831-openipc.dts`, `sun8i-v851s.dtsi`, `sun8i-v851s-openipc.dts`, `sun8i-v3s-openipc.dts` | +| Board: PHY clock, the kernel's device tree (V831/V833) | `board/sunxi/v83x.c`, `v83x_kernel_dtb.S`, `v83x-kernel.dts` | +| Config header | `include/configs/openipc-sunxi.h` (sunxi-common.h, the DDR size, then `openipc-common.h`) | | Kconfig fragments, the build | `openipc/`, `build.sh` | **Patched mainline files**, each small: -- `arch/arm/Kconfig`: `ARCH_SUNXI` selects `BINMAN` except on the V831 +- `arch/arm/Kconfig`: `ARCH_SUNXI` selects `BINMAN` except with `OPENIPC` (binman needs pylibfdt, which no longer builds with current setuptools; `build.sh` puts the image together). - `arch/arm/mach-sunxi/Kconfig`: `MACH_SUN8I_V831` (selects `SUN50I_GEN_H6`), `DRAM_SUN8I_V831` and the `DRAM_SUN8I_V831_*` parameters, SoC defaults (DRAM clock and ZQ, CPU 816 MHz, SPL stack in - DDR, `SYS_CONFIG_NAME` "v83x"); `SUN50I_GEN_H6` selects FIT only on arm64. + DDR); `SUN50I_GEN_H6` selects FIT only on arm64. `SYS_CONFIG_NAME` is + "openipc-sunxi" with `OPENIPC`. `MACH_SUN8I_V851S`, `SUNXI_GEN_NCAT2`, + `SUNXI_NEW_PINCTRL`, `DRAM_SUN20I_D1` and the `DRAM_SUNXI_*` parameters + (defaults: the hardware-tested OpenIPC V851s U-Boot's); SPI NOR SPL for + the V3s and V851s; no `VIDEO_SUNXI` on NCAT2. +- NCAT2 conditions next to the `SUN50I_GEN_H6` ones, as mainline has them: + `arch/arm/include/asm/arch-sunxi/{cpu.h,clock.h,clock_sun50i_h6.h,mmc.h,prcm.h,timer.h,watchdog.h,gpio.h}`, + `arch/arm/mach-sunxi/{board.c,clock_sun50i_h6.c,spl_spi_sunxi.c}`, + `drivers/mmc/sunxi_mmc.c`. `gpio.h`: the new pinctrl layout (banks 0x30 + apart, 4-bit drive). `watchdog.h`: the NCAT2 watchdog's 0x16AA key. +- `drivers/spi/spi-sunxi.c`: mainline's R329 variant (no clock divider in + the controller, normal sampling), for the V851s. +- V3s: `drivers/clk/sunxi/clk_v3s.c` (EMAC and EPHY gates and resets), + `drivers/net/sun8i_emac.c` (the V3s compatible, H3 variant). - `arch/arm/mach-sunxi/{board.c,clock_sun50i_h6.c,cpu_info.c,spl_spi_sunxi.c,Makefile}`, `arch/arm/include/asm/arch-sunxi/{clock_sun50i_h6.h,dram.h,gpio.h}`: UART0 on PH9/PH10, the boot0 clock set-up, the H6's SPI0 on the H3's pins (PC0-PC3), the boot device, an LZMA U-Boot from SPI. -- `arch/arm/cpu/armv7/sunxi/Makefile`: `psci_v831.o` instead of `psci.o`. +- `arch/arm/cpu/armv7/sunxi/Makefile`: `psci_v831.o` instead of `psci.o` + (V831/V833, V851s). - `arch/arm/dts/Makefile`, `drivers/clk/sunxi/{Kconfig,clk_sunxi.c}` - (the V831 CCU is the H6's for every gate U-Boot uses), - `drivers/pinctrl/sunxi/{Kconfig,pinctrl-sunxi.c}` (the V831's functions). + (the V831 and V851s CCUs are the H6's for every gate U-Boot uses, at + their own addresses), `drivers/pinctrl/sunxi/{Kconfig,pinctrl-sunxi.c}` + (the V831's and V851s's functions). - `board/sunxi/{board.c,Makefile}`: the V831 hooks; `misc_init_r()` → - `openipc_helper()`; `env_get_location()` falls back to the SPI flash. + `openipc_helper()`, then the SD boot source; `env_get_location()` falls + back to the SPI flash with `OPENIPC`. - `boot/image-fdt.c`: with `MACH_SUN8I_V831`, a plain uImage boots with the device tree at `${fdtaddr}` (as on `openipc-novatek`). - `common/spl/Kconfig`: the SPL BSS inside 64 MiB. @@ -83,9 +113,10 @@ git diff --diff-filter=M v2022.10 HEAD # the mainline files we patched ## Boot -The boot ROM reads the eGON.BT0 header at SPI NOR offset 0 and loads the -SPL (`spl/sunxi-spl.bin`, `mkimage -T sunxi_egon`) into SRAM A1 at -`0x20000`. The SPL: +Every SoC boots the same way, from the same image layout. The boot ROM +reads the eGON.BT0 header at SPI NOR offset 0 and loads the SPL +(`spl/sunxi-spl.bin`, `mkimage -T sunxi_egon`) into SRAM A1 (`0x20000` +on the V831 and V851s, `0` on the V3s). The V831's SPL: 1. sets the clocks as boot0 does (`clock_init_safe()`: PLL_PERI0 1.2 GHz, AHB 200 / APB1 100 MHz, MBUS reset released; the MBUS clock is a @@ -95,8 +126,13 @@ SPL (`spl/sunxi-spl.bin`, `mkimage -T sunxi_egon`) into SRAM A1 at 3. reads U-Boot from offset `0x8000` with the H6's SPI0 (PIO, 6 MHz): an LZMA-compressed legacy image (`build.sh`), unpacked to `0x42000000`. +The V3s's and V851s's SPLs do the same with mainline's clock set-up and +DRAM drivers (V3s: DRAM 360 MHz, CPU 1008 MHz; V851s: DRAM 528 MHz, CPU +912 MHz) and their SPI0 (V3s: the H3's at `0x01c68000`; V851s: at +`0x04025000`, PC0-PC5, no divider in the controller). + U-Boot does not fit the 256 KiB boot partition uncompressed (~257 KiB); -packed, the whole image is ~170 KiB. +packed, the whole image is 160-175 KiB. The NOR images do not rely on the boot ROM's boot-media byte in the eGON header: their SPL has no MMC loader, so it takes any valid header as @@ -119,7 +155,7 @@ partition at 1 MiB; the SPL loads U-Boot 32 KiB after itself (sunxi's `U_BOOT_SECTOR` 0x40, counted from the SPL). U-Boot is the NOR image's. - This is the unbricking path: a camera with a dead or blank NOR boots - from the card. `v83x_boot_source()` then sets `source=fatload mmc 0` + from the card. `openipc_boot_source()` then sets `source=fatload mmc 0` (as `srcmmc` does) for that boot, not saved, so `ub`, `ur`, `uk` and `fwupd` read `u-boot--nor.bin`, `rootfs.squashfs.` and `uImage.` from the card's FAT partition and write them to the NOR. @@ -136,6 +172,23 @@ partition at 1 MiB; the SPL loads U-Boot 32 KiB after itself (sunxi's ## DRAM +### V3s + +Mainline's `dram_sunxi_dw.c` (`SUNXI_DRAM_DDR2_V3S`), unchanged, at +mainline's 360 MHz. + +### V851s + +`dram_sun20i_d1.c` is OpenIPC's V851s U-Boot's (mainline 2024.10 with its +V851s changes: the LDO-B calibration from the SID, the standby flag), +non-DM, its parameters in Kconfig (`DRAM_SUNXI_*`, `SUNXI_DRAM_TYPE`, +`DRAM_CLK` 528, `DRAM_ZQ` 0x7b7bf9). Under QEMU, after this SPL and after +the published `u-boot-v851s-nor.bin` (OpenIPC/firmware), every DRAM +COM/PHY, CCU and PRCM register is equal, but SMHC0's clock (this U-Boot +probes the card at boot). + +### V831/V833 + `dram_sun8i_v831.c` is the vendor library, not a rewrite: the function names are the library's, every register write is the library's, in its order. It was checked against the vendor boot0 under QEMU: after the @@ -153,11 +206,18 @@ step at once, so this proves the sequence, not the timing on silicon. - Left out: the chip binding check (`bond_id_check()`), the PMIC DRAM supply (`dram_vol_set()`), the super standby resume path. - `sunxi_dram_init()` checks the size the library computed with - `get_ram_size()`. Under QEMU, which does not model the address mapping, - the library's scan reports 4160 MiB, as the vendor's boot0 does there. + `get_ram_size()`. qemu-bench models the controller's address mapping + (`aw-dramcom`) and single-rank training: the library's scan finds 64 + and 128 MiB, as the vendor's boot0 does there. Without that model both + report 4160 MiB. ## Hand-off to the kernel +- **V3s, V851s:** the kernels carry their device tree appended (OpenIPC's + V851s kernel: Linux 4.9, `ARM_APPENDED_DTB`, `ATAG_DTB_COMPAT`); U-Boot + passes ATAGs (`SUPPORT_PASSING_ATAGS`: memory, command line). The V851s + kernel is entered in HYP mode with PSCI 1.0 from `psci_v831.c`, as from + the OpenIPC V851s U-Boot; it resets through the watchdog itself. - OpenIPC's V83x kernels (Linux 4.9, `openipc/linux` `allwinner-v83x`) carry no device tree; the vendor U-Boot gives them its own. `board/sunxi/v83x-kernel.dts` is that tree: the Tina V83x SDK's @@ -179,6 +239,12 @@ step at once, so this proves the sequence, not the timing on silicon. ## Peripherals +V831/V833 below. The V3s: UART0 on PB8/PB9, SPI0 on PC0-PC3, SMHC0 on +PF, the EMAC with the internal PHY (`allwinner,sun8i-v3s-emac`, LEDs +active low). The V851s: UART0 on PH9/PH10, SPI0 on PC0-PC5 (function 4, +`spi-sunxi`'s R329 variant), SMHC0 on PF, no Ethernet (as OpenIPC's V851s +U-Boot); reset by the watchdog at `0x020500A0`. + - **Console:** UART0 on PH9/PH10 (function 5), 115200, 24 MHz. - **SPI NOR:** SPI0 (`spi-sunxi`, the H3 variant; 64-byte FIFO), PC0-PC3 function 4; DM SPI flash, the environment at 0x40000. @@ -193,7 +259,7 @@ step at once, so this proves the sequence, not the timing on silicon. ## Build ```sh -bash build.sh # v831 v833; continues past failures, exits non-zero if any failed +bash build.sh # v3s v831 v833 v851s; continues past failures, exits non-zero if any failed bash build.sh v831 # one SoC (CROSS_COMPILE defaults to arm-linux-gnueabi-) ``` @@ -209,14 +275,15 @@ bash build.sh v831 # one SoC (CROSS_COMPILE defaults to arm-linux-gnuea ## Verification The emulator is qemu-bench (OpenIPC/qemu-bench, formerly qemu-hisilicon), -machines `v831` and `v833` (`docs/allwinner-v83x.md` there). -`-M ,flash-file=` does the boot ROM's job; booting from an SD -card (`-drive if=sd`) needs qemu-bench #204. +machines `v831`, `v833` (`docs/allwinner-v83x.md` there), `v3s` and +`v851s` (`allwinner-cam.c`). `-M ,flash-file=` does the boot +ROM's job; booting from an SD card (`-drive if=sd`) needs qemu-bench #204, +the `v3s` and `v851s` machines the qemu-bench change that adds them. - **Smoke (what CI will run):** two boots of an 8 MiB image holding only U-Boot (`-mmc`: an erased NOR and a card with the image at 8 KiB); the - second must load the environment the first saved. Both SoCs, both - kinds pass (V831 64 MiB, V833 128 MiB). + second must load the environment the first saved. Every SoC, both + kinds pass (V3s, V831, V851s 64 MiB, V833 128 MiB), and `reset`. - SD unbricking: an erased NOR and a card with the `-mmc` image and the three files on FAT: `run ub`, `run fwupd`, then a boot without the card mounts the root from the NOR. @@ -228,6 +295,12 @@ card (`-drive if=sd`) needs qemu-bench #204. 0x250000 reaches a shell: PSCIv1.0, HYP, six `cmdlinepart` partitions on `spi0.0`, root on `mtdblock3`; its `sunxi-gmac` pings the host; `reboot -f` comes back through the SPL. + - V851s: the same with the published OpenIPC `uImage.v851s` and + `rootfs.squashfs.v851s`: HYP, ATAGs, root on `mtdblock3` (the vendor + SPI driver's DMA), `reboot -f` back to the SPL. The V3s has no OpenIPC + firmware to boot. + - V3s, V851s: SD unbricking (`run ub` from the card, then the NOR + alone). - U-Boot: `ping` and `tftpboot` (`-nic user,...`), `mmc info` and `fatls` on a card (`-drive if=sd,...`), `saveenv` + `reset`, the boot limit (two failed boots, then `altbootcmd` with `failsafe`). diff --git a/arch/arm/mach-sunxi/dram_sun8i_v831.c b/arch/arm/mach-sunxi/dram_sun8i_v831.c index d05691acf2e8..42f0b9c943cf 100644 --- a/arch/arm/mach-sunxi/dram_sun8i_v831.c +++ b/arch/arm/mach-sunxi/dram_sun8i_v831.c @@ -1058,7 +1058,7 @@ unsigned long sunxi_dram_init(void) /* * The geometry the library found, checked against the DRAM itself: - * past the part's size the addresses alias. (Under QEMU, which does + * past the part's size the addresses alias. (Under a QEMU that does * not model the controller's address mapping, the library's scan, * like the vendor's boot0, reports 4160 MiB.) */ From 8a3848bec9de843d662c1e4545e8ffbf27e64b3b Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:29:28 +0000 Subject: [PATCH 4/4] sunxi: V851s: address review -- PSCI in SRAM, SPI mode, pin disable, PLL_DDR - The PSCI monitor stayed in relocated U-Boot at the top of the DDR, reserved only in a device tree; the V851s kernels boot with ATAGs. Their own tree keeps that region no-map (the RISC-V core's), but that is the RISC-V firmware's place. HAS_ARMV7_SECURE_BASE now copies it to SRAM A1 at 0x38000 (32 KiB, free once the SPL is done; U-Boot's early stack is below 0x28000). - spi-sunxi's R329 variant skipped sun4i_spi_set_speed_mode() whole, and with it CPOL/CPHA: only the divider is skipped now. - SUNXI_GPIO_DISABLE is 0xf with the new pinctrl layout (7 is a function there): the SPL's spi0_deinit() and pinctrl's status use it. - dram_sun20i_d1.c set bit 27, the output enable, on PLL_CPU's register after locking PLL_DDR (as mainline and awboot do; PLL_DDR's is set at reset, so it worked): PLL_DDR's now. clock_get_pll6() keeps mainline's NCAT2 m = 1 (see the PR). Every image passes the smoke test and reset again; the V851s kernel boots to a shell with the monitor in SRAM, and reboots. --- arch/arm/cpu/armv7/Kconfig | 7 +++++-- arch/arm/include/asm/arch-sunxi/gpio.h | 5 +++++ arch/arm/mach-sunxi/dram_sun20i_d1.c | 5 +++-- drivers/spi/spi-sunxi.c | 26 ++++++++++++++------------ 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/arch/arm/cpu/armv7/Kconfig b/arch/arm/cpu/armv7/Kconfig index f1e4e26b8f00..e3208febf764 100644 --- a/arch/arm/cpu/armv7/Kconfig +++ b/arch/arm/cpu/armv7/Kconfig @@ -30,7 +30,8 @@ config ARMV7_BOOT_SEC_DEFAULT config HAS_ARMV7_SECURE_BASE bool "Enable support for a ahardware secure memory area" default y if ARCH_LS1021A || ARCH_MX7 || ARCH_MX7ULP || ARCH_STM32MP \ - || MACH_SUN6I || MACH_SUN7I || MACH_SUN8I || TEGRA124 + || MACH_SUN6I || MACH_SUN7I || MACH_SUN8I || TEGRA124 \ + || MACH_SUN8I_V851S config ARMV7_SECURE_BASE hex "Base address for secure mode memory" @@ -41,6 +42,7 @@ config ARMV7_SECURE_BASE default 0x10010000 if ARCH_LS1021A default 0x00900000 if ARCH_MX7 default 0x00044000 if MACH_SUN8I + default 0x00038000 if MACH_SUN8I_V851S default 0x00020000 if MACH_SUN6I || MACH_SUN7I config ARMV7_SECURE_RESERVE_SIZE @@ -53,9 +55,10 @@ config ARMV7_SECURE_RESERVE_SIZE config ARMV7_SECURE_MAX_SIZE hex depends on ARMV7_SECURE_BASE && ARCH_STM32MP || MACH_SUN6I \ - || MACH_SUN7I || MACH_SUN8I + || MACH_SUN7I || MACH_SUN8I || MACH_SUN8I_V851S default 0xbc00 if MACH_SUN8I && !MACH_SUN8I_H3 default 0x3c00 if MACH_SUN8I && MACH_SUN8I_H3 + default 0x8000 if MACH_SUN8I_V851S default 0x10000 config ARMV7_VIRT diff --git a/arch/arm/include/asm/arch-sunxi/gpio.h b/arch/arm/include/asm/arch-sunxi/gpio.h index c025bc7994eb..36643628a2d1 100644 --- a/arch/arm/include/asm/arch-sunxi/gpio.h +++ b/arch/arm/include/asm/arch-sunxi/gpio.h @@ -153,7 +153,12 @@ enum sunxi_gpio_number { /* GPIO pin function config */ #define SUNXI_GPIO_INPUT 0 #define SUNXI_GPIO_OUTPUT 1 +/* the D1 generation's 4-bit function fields: 0xf is "IO disabled" */ +#ifdef CONFIG_SUNXI_NEW_PINCTRL +#define SUNXI_GPIO_DISABLE 0xf +#else #define SUNXI_GPIO_DISABLE 7 +#endif #define SUN8I_H3_GPA_UART0 2 #define SUN8I_H3_GPA_UART2 2 diff --git a/arch/arm/mach-sunxi/dram_sun20i_d1.c b/arch/arm/mach-sunxi/dram_sun20i_d1.c index 20e0ec91bfa5..a1e741f90731 100644 --- a/arch/arm/mach-sunxi/dram_sun20i_d1.c +++ b/arch/arm/mach-sunxi/dram_sun20i_d1.c @@ -535,8 +535,9 @@ static int ccu_set_pll_ddr_clk(int index, struct dram_para *para) udelay(20); - // enable PLL output - setbits_le32(&ccm->pll1_cfg, BIT(27)); + // enable PLL output: PLL_DDR's (mainline and awboot set PLL_CPU's + // bit 27; PLL_DDR's is set at reset, so both worked) + setbits_le32(&ccm->pll5_cfg, BIT(27)); // turn clock gate on val = readl(&ccm->dram_clk_cfg); diff --git a/drivers/spi/spi-sunxi.c b/drivers/spi/spi-sunxi.c index edea754f33f4..0f52829166b2 100644 --- a/drivers/spi/spi-sunxi.c +++ b/drivers/spi/spi-sunxi.c @@ -238,6 +238,18 @@ static void sun4i_spi_set_speed_mode(struct udevice *dev) unsigned int div; u32 reg; + /* + * No divider inside (R329, D1, V851s): the module clock, the + * 24 MHz HOSC, is the bus clock; sample in the normal mode + */ + if (!priv->variant->has_clk_ctl) { + clrsetbits_le32(SPI_REG(priv, SPI_TCR), + SPI_BIT(priv, SPI_TCR_SDC) | + SPI_BIT(priv, SPI_TCR_SDM), + SPI_BIT(priv, SPI_TCR_SDM)); + goto mode; + } + /* * Setup clock divider. * @@ -274,6 +286,7 @@ static void sun4i_spi_set_speed_mode(struct udevice *dev) writel(reg, SPI_REG(priv, SPI_CCR)); +mode: reg = readl(SPI_REG(priv, SPI_TCR)); reg &= ~(SPI_BIT(priv, SPI_TCR_CPOL) | SPI_BIT(priv, SPI_TCR_CPHA)); @@ -305,18 +318,7 @@ static int sun4i_spi_claim_bus(struct udevice *dev) setbits_le32(SPI_REG(priv, SPI_TCR), SPI_BIT(priv, SPI_TCR_CS_MANUAL) | SPI_BIT(priv, SPI_TCR_CS_ACTIVE_LOW)); - if (priv->variant->has_clk_ctl) { - sun4i_spi_set_speed_mode(dev->parent); - } else { - /* - * No divider inside (R329, D1, V851s): the module clock, the - * 24 MHz HOSC, is the bus clock; sample in the normal mode - */ - clrsetbits_le32(SPI_REG(priv, SPI_TCR), - SPI_BIT(priv, SPI_TCR_SDC) | - SPI_BIT(priv, SPI_TCR_SDM), - SPI_BIT(priv, SPI_TCR_SDM)); - } + sun4i_spi_set_speed_mode(dev->parent); return 0; }