Add an SPL loader that talks to SPI NAND the same way the Allwinner V3s BROM does, so U-Boot proper can be fetched after BROM has loaded SPL. BROM still reports BOOT_DEVICE_SPI for SPI NAND, so this path is mutually exclusive with SPL_SPI_SUNXI.
Also avoid selecting SPI NOR for the environment in that case; prefer FAT when available. Signed-off-by: Zunyi Hu <[email protected]> --- arch/arm/mach-sunxi/Kconfig | 22 ++ arch/arm/mach-sunxi/Makefile | 1 + arch/arm/mach-sunxi/spl_spinand_sunxi.c | 449 ++++++++++++++++++++++++ board/sunxi/board.c | 9 + 4 files changed, 481 insertions(+) create mode 100644 arch/arm/mach-sunxi/spl_spinand_sunxi.c diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index b0c5d3cd903..b6c68698555 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -1208,6 +1208,28 @@ config SPL_SPI_SUNXI sunxi SPI Flash. It uses the same method as the boot ROM, so does not need any extra configuration. +config SPL_SPINAND_SUNXI + bool "Support for SPI NAND Flash on Allwinner V3s in SPL" + depends on MACH_SUN8I_V3S + depends on !SPL_SPI_SUNXI + select SPL_CRC32 + select SPL_LEGACY_IMAGE_FORMAT + help + Enable SPI NAND boot support in SPL. When the BROM loads SPL from + SPI NAND, it still reports BOOT_DEVICE_SPI. This driver then loads + U-Boot proper using the same BROM-compatible page layout (SPL + copies at a 32-page stride, U-Boot on an erase-block boundary). + Mutually exclusive with SPL_SPI_SUNXI (same boot device id). + Selects SPL_CRC32 for legacy uImage header CRC checks. + +config SPL_SPINAND_SUNXI_GENERIC_2K + bool "Use generic 2K-page profile for unknown SPI NAND chips" + depends on SPL_SPINAND_SUNXI + default y + help + Fall back to a generic 2K-page SPI NAND profile when the JEDEC ID + is not in the known chip table. + config PINE64_DT_SELECTION bool "Enable Pine64 device tree selection code" depends on MACH_SUN50I diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile index ff475d8fff5..f8c0dfc2a7d 100644 --- a/arch/arm/mach-sunxi/Makefile +++ b/arch/arm/mach-sunxi/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_DRAM_SUN8I_A33) += dram_sun8i_a33.o obj-$(CONFIG_DRAM_SUN8I_A83T) += dram_sun8i_a83t.o obj-$(CONFIG_DRAM_SUN9I) += dram_sun9i.o obj-$(CONFIG_SPL_SPI_SUNXI) += spl_spi_sunxi.o +obj-$(CONFIG_SPL_SPINAND_SUNXI) += spl_spinand_sunxi.o obj-$(CONFIG_SUNXI_DRAM_DW) += dram_sunxi_dw.o obj-$(CONFIG_SUNXI_DRAM_DW) += dram_timings/ obj-$(CONFIG_DRAM_SUN50I_H6) += dram_sun50i_h6.o dram_dw_helpers.o diff --git a/arch/arm/mach-sunxi/spl_spinand_sunxi.c b/arch/arm/mach-sunxi/spl_spinand_sunxi.c new file mode 100644 index 00000000000..ef7a9a171e2 --- /dev/null +++ b/arch/arm/mach-sunxi/spl_spinand_sunxi.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SPL loader for Allwinner SPI NAND (BROM-compatible layout). + * + * Copyright (C) 2019 Benedikt-Alexander Mokroß + * Copyright (C) 2026 Zunyi Hu <[email protected]> + * + * Mimics the Allwinner BROM SPI NAND read path (page load + cache read) + * so SPL can fetch U-Boot proper after the BROM has loaded SPL itself. + * + * Flash layout (BROM-compatible): multiple SPL copies at a 32-page stride, + * then U-Boot proper starting on an erase-block boundary after those copies. + */ + +#include <image.h> +#include <log.h> +#include <mapmem.h> +#include <spl.h> +#include <asm/arch/spl.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <sunxi_gpio.h> + +#ifdef CONFIG_SPL_OS_BOOT +#error CONFIG_SPL_OS_BOOT is not supported yet +#endif + +#define SUN6I_SPI0_CCTL 0x24 +#define SUN6I_SPI0_GCR 0x04 +#define SUN6I_SPI0_TCR 0x08 +#define SUN6I_SPI0_FIFO_STA 0x1C +#define SUN6I_SPI0_WCR 0x20 +#define SUN6I_SPI0_MBC 0x30 +#define SUN6I_SPI0_MTC 0x34 +#define SUN6I_SPI0_BCC 0x38 +#define SUN6I_SPI0_TXD 0x200 +#define SUN6I_SPI0_RXD 0x300 + +#define SUN6I_CTL_ENABLE BIT(0) +#define SUN6I_CTL_MASTER BIT(1) +#define SUN6I_CTL_SRST BIT(31) +#define SUN6I_TCR_XCH BIT(31) + +#define CCM_AHB_GATING0 (0x01C20000 + 0x60) +#define CCM_SPI0_CLK (0x01C20000 + 0xA0) +#define SUN6I_BUS_SOFT_RST_REG0 (0x01C20000 + 0x2C0) + +#define AHB_RESET_SPI0_SHIFT 20 +#define AHB_GATE_OFFSET_SPI0 20 + +#define SPI0_CLK_DIV_BY_4 0x1001 +#define DUMMY_BURST_BYTE 0x00 +#define EGON_BT0_HEADER_SIZE 40 +#define SPI_READ_MAX_SIZE 60 +/* Bound U-Boot search so a blank device cannot hang forever. */ +#define SPINAND_UBOOT_MAX_TRIES 64 + +struct sunxi_nand_config { + const char *name; + u32 jedec_id; + u32 page_mask; + u8 page_shift; + u32 addr_mask; + u8 addr_shift; + u32 block_size; + u32 page_size; +}; + +struct egon_bt0_header { + u32 jump; + u8 magic[8]; + u32 checksum; + u32 length; + u32 header_size; + u8 header_version[4]; + u8 boot_vsn[4]; + u8 egon_vsn[4]; + u8 platform[8]; +}; + +static const u8 egon_bt0_magic[8] = { + 'e', 'G', 'O', 'N', '.', 'B', 'T', '0' +}; + +static struct sunxi_nand_config sunxi_known_nands[] = { + { + .name = "Macronix MX35LF1GE4AB", + .jedec_id = 0x00c212c2, + .page_mask = 0x00ffffff, + .page_shift = 11, + .addr_mask = 0x7ff, + .addr_shift = 0, + .block_size = 128 * 1024, + .page_size = 2048, + }, + { + .name = "Winbond W25N01GVxxIG", + .jedec_id = 0x00efaa21, + .page_mask = 0x0000ffff, + .page_shift = 11, + .addr_mask = 0x7ff, + .addr_shift = 0, + .block_size = 128 * 1024, + .page_size = 2048, + }, + { + /* Some W25N01G samples report device id byte 0x20. */ + .name = "Winbond W25N01G", + .jedec_id = 0x00efaa20, + .page_mask = 0x0000ffff, + .page_shift = 11, + .addr_mask = 0x7ff, + .addr_shift = 0, + .block_size = 128 * 1024, + .page_size = 2048, + }, + { + .name = "GigaDevice GD5F1GQ4", + .jedec_id = 0x00c8b148, + .page_mask = 0x00ffffff, + .page_shift = 11, + .addr_mask = 0x7ff, + .addr_shift = 0, + .block_size = 128 * 1024, + .page_size = 2048, + }, + { /* sentinel */ } +}; + +#ifdef CONFIG_SPL_SPINAND_SUNXI_GENERIC_2K +static struct sunxi_nand_config sunxi_generic_nand_config = { + .name = "Generic 2K SPI-NAND", + .jedec_id = 0xffffffff, + .page_mask = 0x00ffffff, + .page_shift = 11, + .addr_mask = 0x7ff, + .addr_shift = 0, + .block_size = 128 * 1024, + .page_size = 2048, +}; +#endif + +static struct sunxi_nand_config *sunxi_spinand_enumerate(u32 jedec_id) +{ + struct sunxi_nand_config *ptr = sunxi_known_nands; + + while (ptr->name) { + if (jedec_id == ptr->jedec_id) + return ptr; + ptr++; + } +#ifdef CONFIG_SPL_SPINAND_SUNXI_GENERIC_2K + return &sunxi_generic_nand_config; +#else + return NULL; +#endif +} + +static uintptr_t spi0_base_address(void) +{ + /* V3s / sun6i-gen SPI0 */ + return 0x01c68000; +} + +static void spi0_pinmux_setup(unsigned int pin_function) +{ + unsigned int pin; + + for (pin = SUNXI_GPC(0); pin <= SUNXI_GPC(2); pin++) + sunxi_gpio_set_cfgpin(pin, pin_function); + sunxi_gpio_set_cfgpin(SUNXI_GPC(3), pin_function); +} + +static void spi0_enable_clock(void) +{ + uintptr_t base = spi0_base_address(); + + setbits_le32(SUN6I_BUS_SOFT_RST_REG0, BIT(AHB_RESET_SPI0_SHIFT)); + setbits_le32(CCM_AHB_GATING0, BIT(AHB_GATE_OFFSET_SPI0)); + writel(SPI0_CLK_DIV_BY_4, base + SUN6I_SPI0_CCTL); + writel(BIT(31), CCM_SPI0_CLK); + setbits_le32(base + SUN6I_SPI0_GCR, + SUN6I_CTL_MASTER | SUN6I_CTL_ENABLE | SUN6I_CTL_SRST); + while (readl(base + SUN6I_SPI0_GCR) & SUN6I_CTL_SRST) + ; +} + +static void spi0_disable_clock(void) +{ + uintptr_t base = spi0_base_address(); + + clrbits_le32(base + SUN6I_SPI0_GCR, SUN6I_CTL_MASTER | SUN6I_CTL_ENABLE); + writel(0, CCM_SPI0_CLK); + clrbits_le32(CCM_AHB_GATING0, BIT(AHB_GATE_OFFSET_SPI0)); + clrbits_le32(SUN6I_BUS_SOFT_RST_REG0, BIT(AHB_RESET_SPI0_SHIFT)); +} + +static void spi0_init(void) +{ + uintptr_t base = spi0_base_address(); + + spi0_pinmux_setup(SUNXI_GPC_SPI0); + spi0_enable_clock(); + writel(0x01, base + SUN6I_SPI0_WCR); +} + +static void spi0_deinit(void) +{ + spi0_disable_clock(); + spi0_pinmux_setup(SUNXI_GPIO_DISABLE); +} + +static void sunxi_spi0_load_page(struct sunxi_nand_config *config, u32 addr) +{ + uintptr_t base = spi0_base_address(); + u8 status = 0x01; + + addr = (addr >> config->page_shift) & config->page_mask; + + writel(4, base + SUN6I_SPI0_MBC); + writel(4, base + SUN6I_SPI0_MTC); + writel(4, base + SUN6I_SPI0_BCC); + + writeb(0x13, base + SUN6I_SPI0_TXD); + writeb((u8)(addr >> 16), base + SUN6I_SPI0_TXD); + writeb((u8)(addr >> 8), base + SUN6I_SPI0_TXD); + writeb((u8)addr, base + SUN6I_SPI0_TXD); + setbits_le32(base + SUN6I_SPI0_TCR, SUN6I_TCR_XCH); + + while ((readl(base + SUN6I_SPI0_FIFO_STA) & 0x7f0000) > 0) + ; + while ((readl(base + SUN6I_SPI0_FIFO_STA) & 0x7f) < 4) + ; + readl(base + SUN6I_SPI0_RXD); + + do { + ndelay(200); + writel(3, base + SUN6I_SPI0_MBC); + writel(2, base + SUN6I_SPI0_MTC); + writel(2, base + SUN6I_SPI0_BCC); + writeb(0x0f, base + SUN6I_SPI0_TXD); + writeb(0xc0, base + SUN6I_SPI0_TXD); + setbits_le32(base + SUN6I_SPI0_TCR, SUN6I_TCR_XCH); + while ((readl(base + SUN6I_SPI0_FIFO_STA) & 0x7f) < 3) + ; + readb(base + SUN6I_SPI0_RXD); + readb(base + SUN6I_SPI0_RXD); + status = readb(base + SUN6I_SPI0_RXD); + } while (status & 0x01); +} + +static void sunxi_spi0_read_cache(struct sunxi_nand_config *config, + u8 *buf, u32 addr, u32 len) +{ + uintptr_t base = spi0_base_address(); + + addr = (addr >> config->addr_shift) & config->addr_mask; + + writel(4 + len, base + SUN6I_SPI0_MBC); + writel(4, base + SUN6I_SPI0_MTC); + writel(4, base + SUN6I_SPI0_BCC); + + writeb(0x0b, base + SUN6I_SPI0_TXD); + writeb((u8)(addr >> 8), base + SUN6I_SPI0_TXD); + writeb((u8)addr, base + SUN6I_SPI0_TXD); + writeb(DUMMY_BURST_BYTE, base + SUN6I_SPI0_TXD); + setbits_le32(base + SUN6I_SPI0_TCR, SUN6I_TCR_XCH); + + while ((readl(base + SUN6I_SPI0_FIFO_STA) & 0x7f) < 4 + len) + ; + readl(base + SUN6I_SPI0_RXD); + + while (len-- > 0) + *buf++ = readb(base + SUN6I_SPI0_RXD); + + ndelay(100); +} + +static void spi0_read_data(struct sunxi_nand_config *config, void *buf, + u32 addr, u32 len) +{ + u8 *buf8 = buf; + u32 chunk_len; + u32 curr_page; + u32 last_page = (addr >> config->page_shift) & config->page_mask; + + sunxi_spi0_load_page(config, addr); + + while (len > 0) { + curr_page = (addr >> config->page_shift) & config->page_mask; + if (curr_page > last_page) { + sunxi_spi0_load_page(config, addr); + last_page = curr_page; + } + + chunk_len = min(len, (u32)SPI_READ_MAX_SIZE); + if ((((addr + chunk_len) >> config->page_shift) & + config->page_mask) > curr_page) + chunk_len = ((curr_page + 1) << config->page_shift) - + addr; + + sunxi_spi0_read_cache(config, buf8, addr, chunk_len); + len -= chunk_len; + buf8 += chunk_len; + addr += chunk_len; + } +} + +static u32 spi0_read_id(void) +{ + uintptr_t base = spi0_base_address(); + u8 idbuf[3]; + + writel(5, base + SUN6I_SPI0_MBC); + writel(2, base + SUN6I_SPI0_MTC); + writel(2, base + SUN6I_SPI0_BCC); + writeb(0x9f, base + SUN6I_SPI0_TXD); + writeb(DUMMY_BURST_BYTE, base + SUN6I_SPI0_TXD); + setbits_le32(base + SUN6I_SPI0_TCR, SUN6I_TCR_XCH); + while ((readl(base + SUN6I_SPI0_FIFO_STA) & 0x7f) < 5) + ; + readb(base + SUN6I_SPI0_RXD); + readb(base + SUN6I_SPI0_RXD); + idbuf[0] = readb(base + SUN6I_SPI0_RXD); + idbuf[1] = readb(base + SUN6I_SPI0_RXD); + idbuf[2] = readb(base + SUN6I_SPI0_RXD); + ndelay(100); + + return idbuf[2] | (idbuf[1] << 8) | (idbuf[0] << 16); +} + +static u32 sunxi_spi0_count_spls(struct sunxi_nand_config *config, + struct egon_bt0_header *self) +{ + u8 buffer[EGON_BT0_HEADER_SIZE]; + struct egon_bt0_header *egon = (struct egon_bt0_header *)buffer; + u32 entry_pages[8] = { 0, 32, 64, 96, 128, 160, 192, 224 }; + u32 spls = 0; + int i; + + for (i = 0; i < 8; i++) { + u32 address = entry_pages[i] * config->page_size; + int hits = 0; + + sunxi_spi0_load_page(config, address); + sunxi_spi0_read_cache(config, buffer, address, + EGON_BT0_HEADER_SIZE); + + if (!memcmp(egon->magic, egon_bt0_magic, 8)) { + spls++; + continue; + } + + if (egon->jump == self->jump) + hits++; + if (egon->checksum == self->checksum) + hits++; + if (egon->length == self->length) + hits++; + if (egon->header_size == self->header_size) + hits++; + if (!memcmp(egon->header_version, self->header_version, 4)) + hits++; + if (!memcmp(egon->boot_vsn, self->boot_vsn, 4)) + hits++; + if (!memcmp(egon->egon_vsn, self->egon_vsn, 4)) + hits++; + if (!memcmp(egon->platform, self->platform, 8)) + hits++; + if (hits > 4) + spls++; + } + + return spls; +} + +static int spl_spinand_load_image(struct spl_image_info *spl_image, + struct spl_boot_device *bootdev) +{ + struct legacy_img_hdr *header; + struct sunxi_nand_config *config; + struct egon_bt0_header *egon; + u32 id, uboot_offset, total_spl_size, spls; + int ret = -EINVAL; + int tries = 0; + + header = (struct legacy_img_hdr *)CONFIG_TEXT_BASE; + + spi0_init(); + id = spi0_read_id(); + if (!id) { + spi0_deinit(); + printf("sunxi SPI-NAND: no device\n"); + return -ENODEV; + } + + config = sunxi_spinand_enumerate(id); + if (!config) { + spi0_deinit(); + printf("sunxi SPI-NAND: unknown chip %06x\n", id); + return -ENODEV; + } + + debug("sunxi SPI-NAND: %s (%06x)\n", config->name, id); + + egon = map_sysmem(0x00000000, EGON_BT0_HEADER_SIZE); + spls = sunxi_spi0_count_spls(config, egon); + if (spls < 1) + spls = 1; + total_spl_size = ((config->page_size * 32) * (spls - 1)) + + ((config->page_size / 1024) * egon->length); + /* Align U-Boot proper to an erase-block boundary after the SPL copies */ + uboot_offset = round_up(total_spl_size, config->block_size); + unmap_sysmem(egon); + + do { + debug("sunxi SPI-NAND: try U-Boot @ 0x%x\n", uboot_offset); + spi0_read_data(config, (void *)header, uboot_offset, 0x40); + if (image_check_hcrc(header)) { + ret = spl_parse_image_header(spl_image, bootdev, header); + if (!ret) { + spi0_read_data(config, + (void *)(uintptr_t)spl_image->load_addr, + uboot_offset, spl_image->size); + break; + } + uboot_offset += round_up(image_get_image_size(header), + config->block_size); + } else { + uboot_offset += config->block_size; + } + } while (++tries < SPINAND_UBOOT_MAX_TRIES); + + spi0_deinit(); + + if (ret) + printf("sunxi SPI-NAND: U-Boot not found\n"); + + return ret; +} + +/* + * BROM reports SPI for SPI NAND on V3s; register on BOOT_DEVICE_SPI. + * Do not enable together with SPL_SPI_SUNXI (same boot device). + */ +SPL_LOAD_IMAGE_METHOD("sunxi SPI-NAND", 0, BOOT_DEVICE_SPI, + spl_spinand_load_image); diff --git a/board/sunxi/board.c b/board/sunxi/board.c index 3d1afec7c66..3e2fb48d981 100644 --- a/board/sunxi/board.c +++ b/board/sunxi/board.c @@ -160,6 +160,15 @@ enum env_location env_get_location(enum env_operation op, int prio) return ENVL_NAND; break; case BOOT_DEVICE_SPI: + /* + * On V3s, SPI NAND boot is still reported as BOOT_DEVICE_SPI. + * Do not treat that as SPI NOR for the environment. + */ + if (IS_ENABLED(CONFIG_SPL_SPINAND_SUNXI)) { + if (IS_ENABLED(CONFIG_ENV_IS_IN_FAT)) + return ENVL_FAT; + break; + } if (prio == 0 && IS_ENABLED(CONFIG_ENV_IS_IN_SPI_FLASH)) return ENVL_SPI_FLASH; if (IS_ENABLED(CONFIG_ENV_IS_IN_FAT)) -- 2.17.1
