[PATCH RFC 00/15] Support SPI NAND booting on the T113

This series is my current working and tested setup for booting from SPI NAND chips on the Allwinner T113.
I have included the following patches from others. I may have modified them to work with the latest mainline:
https://lore.kernel.org/all/20221014030520.3067228-1-uwu@icenowy.me/ https://lore.kernel.org/all/20231111133432.755363-2-bigunclemax@gmail.com/
Hopefully this can get the ball rolling on how to properly implement SPI NAND support in mainline U-Boot.
Signed-off-by: John Watts contact@jookia.org --- Icenowy Zheng (5): sunxi: SPL SPI: extract code for doing SPI transfer sunxi: SPL SPI: add support for read command with 2 byte address sunxi: SPL SPI: allow multiple boot attempt sunxi: SPL SPI: add initial support for booting from SPI NAND sunxi: enable support for SPI NAND booting on SUNIV
John Watts (9): sunxi: Separate boot device and boot position spl: Add BOOT_DEVICE_SPINAND option sunxi: Implement BOOT_DEVICE_SPINAND in SPL spl: Add SPL_SPINAND configuration options sunxi: Use SPL_SPINAND for configuration nand: Add spinand_ helper functions sunxi: Implement spinand_ helpers spl: Support SPI NAND boot in UBI spl: Support loading FIT images in UBI
Maksim Kiselev (1): sunxi: SPL SPI: Add SPI boot support for the Allwinner R528/T113 SoCs
arch/arm/include/asm/arch-sunxi/spl.h | 3 +- arch/arm/include/asm/spl.h | 1 + arch/arm/mach-sunxi/Kconfig | 2 +- arch/arm/mach-sunxi/board.c | 31 +-- arch/arm/mach-sunxi/spl_spi_sunxi.c | 348 +++++++++++++++++++++++++--------- arch/mips/include/asm/spl.h | 1 + arch/riscv/include/asm/spl.h | 1 + arch/sandbox/include/asm/spl.h | 1 + common/spl/Kconfig | 21 ++ common/spl/spl_ubi.c | 49 ++++- include/nand.h | 3 + 11 files changed, 354 insertions(+), 107 deletions(-) --- base-commit: 777c28460947371ada40868dc994dfe8537d7115 change-id: 20240411-spinand-eb7d8319813b
Best regards,

From: Maksim Kiselev bigunclemax@gmail.com
R528/T113 SoCs uses the same SPI IP as the H6, also have the same clocks and reset bits layout, but the CCU base is different. Another difference is that the new SoCs do not have a clock divider inside. Instead of this we should configure sample mode depending on input clock rate.
The pin assignment is also different: the H6 uses PC0, the R528/T113 PC4 instead. This makes for a change in spi0_pinmux_setup() routine.
This patch extends the H6/H616 #ifdef guards to also cover the R528/T113, using the shared CONFIG_SUNXI_GEN_NCAT2 and CONFIG_MACH_SUN8I_R528 symbols. Also use CONFIG_SUNXI_GEN_NCAT2 symbol for the Kconfig dependency.
Signed-off-by: Maksim Kiselev bigunclemax@gmail.com Tested-by: Sam Edwards CFSworks@gmail.com --- arch/arm/mach-sunxi/Kconfig | 2 +- arch/arm/mach-sunxi/spl_spi_sunxi.c | 78 +++++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 22 deletions(-)
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index fe89aec6b9..ddf9414b08 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -1078,7 +1078,7 @@ config SPL_STACK_R_ADDR
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 || SUN50I_GEN_H6 || MACH_SUNIV + depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUNXI_H3_H5 || MACH_SUN50I || MACH_SUN8I_R40 || SUN50I_GEN_H6 || MACH_SUNIV || SUNXI_GEN_NCAT2 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/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index 72faa7171c..7acb44f52a 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -72,18 +72,27 @@ #define SUN6I_CTL_ENABLE BIT(0) #define SUN6I_CTL_MASTER BIT(1) #define SUN6I_CTL_SRST BIT(31) +#define SUN6I_TCR_SDM BIT(13) #define SUN6I_TCR_XCH BIT(31)
/*****************************************************************************/
-#define CCM_AHB_GATING0 (0x01C20000 + 0x60) -#define CCM_H6_SPI_BGR_REG (0x03001000 + 0x96c) -#ifdef CONFIG_SUN50I_GEN_H6 -#define CCM_SPI0_CLK (0x03001000 + 0x940) +#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) +#define CCM_BASE 0x03001000 +#elif IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) +#define CCM_BASE 0x02001000 #else -#define CCM_SPI0_CLK (0x01C20000 + 0xA0) +#define CCM_BASE 0x01C20000 #endif -#define SUN6I_BUS_SOFT_RST_REG0 (0x01C20000 + 0x2C0) + +#define CCM_AHB_GATING0 (CCM_BASE + 0x60) +#define CCM_H6_SPI_BGR_REG (CCM_BASE + 0x96c) +#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) +#define CCM_SPI0_CLK (CCM_BASE + 0x940) +#else +#define CCM_SPI0_CLK (CCM_BASE + 0xA0) +#endif +#define SUN6I_BUS_SOFT_RST_REG0 (CCM_BASE + 0x2C0)
#define AHB_RESET_SPI0_SHIFT 20 #define AHB_GATE_OFFSET_SPI0 20 @@ -101,17 +110,22 @@ */ static void spi0_pinmux_setup(unsigned int pin_function) { - /* All chips use PC0 and PC2. */ - sunxi_gpio_set_cfgpin(SUNXI_GPC(0), pin_function); + /* All chips use PC2. And all chips use PC0, except R528/T113 */ + if (!IS_ENABLED(CONFIG_MACH_SUN8I_R528)) + sunxi_gpio_set_cfgpin(SUNXI_GPC(0), pin_function); + sunxi_gpio_set_cfgpin(SUNXI_GPC(2), pin_function);
- /* All chips except H6 and H616 use PC1. */ - if (!IS_ENABLED(CONFIG_SUN50I_GEN_H6)) + /* All chips except H6/H616/R528/T113 use PC1. */ + if (!IS_ENABLED(CONFIG_SUN50I_GEN_H6) && + !IS_ENABLED(CONFIG_MACH_SUN8I_R528)) sunxi_gpio_set_cfgpin(SUNXI_GPC(1), pin_function);
- if (IS_ENABLED(CONFIG_MACH_SUN50I_H6)) + if (IS_ENABLED(CONFIG_MACH_SUN50I_H6) || + IS_ENABLED(CONFIG_MACH_SUN8I_R528)) sunxi_gpio_set_cfgpin(SUNXI_GPC(5), pin_function); - if (IS_ENABLED(CONFIG_MACH_SUN50I_H616)) + if (IS_ENABLED(CONFIG_MACH_SUN50I_H616) || + IS_ENABLED(CONFIG_MACH_SUN8I_R528)) sunxi_gpio_set_cfgpin(SUNXI_GPC(4), pin_function);
/* Older generations use PC23 for CS, newer ones use PC3. */ @@ -125,7 +139,8 @@ static void spi0_pinmux_setup(unsigned int pin_function) static bool is_sun6i_gen_spi(void) { return IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I) || - IS_ENABLED(CONFIG_SUN50I_GEN_H6); + IS_ENABLED(CONFIG_SUN50I_GEN_H6) || + IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2); }
static uintptr_t spi0_base_address(void) @@ -136,6 +151,9 @@ static uintptr_t spi0_base_address(void) if (IS_ENABLED(CONFIG_SUN50I_GEN_H6)) return 0x05010000;
+ if (IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) + return 0x04025000; + if (!is_sun6i_gen_spi() || IS_ENABLED(CONFIG_MACH_SUNIV)) return 0x01C05000; @@ -151,23 +169,30 @@ static void spi0_enable_clock(void) uintptr_t base = spi0_base_address();
/* Deassert SPI0 reset on SUN6I */ - if (IS_ENABLED(CONFIG_SUN50I_GEN_H6)) + if (IS_ENABLED(CONFIG_SUN50I_GEN_H6) || + IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) setbits_le32(CCM_H6_SPI_BGR_REG, (1U << 16) | 0x1); else if (is_sun6i_gen_spi()) setbits_le32(SUN6I_BUS_SOFT_RST_REG0, (1 << AHB_RESET_SPI0_SHIFT));
/* Open the SPI0 gate */ - if (!IS_ENABLED(CONFIG_SUN50I_GEN_H6)) + if (!IS_ENABLED(CONFIG_SUN50I_GEN_H6) && + !IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) setbits_le32(CCM_AHB_GATING0, (1 << AHB_GATE_OFFSET_SPI0));
if (IS_ENABLED(CONFIG_MACH_SUNIV)) { /* 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)); + /* New SoCs do not have a clock divider inside */ + if (!IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) { + /* Divide by 4 */ + 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); } @@ -179,6 +204,14 @@ static void spi0_enable_clock(void) /* Wait for completion */ while (readl(base + SUN6I_SPI0_GCR) & SUN6I_CTL_SRST) ; + + /* + * For new SoCs we should configure sample mode depending on + * input clock. As 24MHz from OSC24M is used, we could use + * normal sample mode by setting SDM bit in the TCR register + */ + 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 | @@ -205,11 +238,13 @@ static void spi0_disable_clock(void) writel(0, CCM_SPI0_CLK);
/* Close the SPI0 gate */ - if (!IS_ENABLED(CONFIG_SUN50I_GEN_H6)) + if (!IS_ENABLED(CONFIG_SUN50I_GEN_H6) && + !IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) clrbits_le32(CCM_AHB_GATING0, (1 << AHB_GATE_OFFSET_SPI0));
/* Assert SPI0 reset on SUN6I */ - if (IS_ENABLED(CONFIG_SUN50I_GEN_H6)) + if (IS_ENABLED(CONFIG_SUN50I_GEN_H6) || + IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) clrbits_le32(CCM_H6_SPI_BGR_REG, (1U << 16) | 0x1); else if (is_sun6i_gen_spi()) clrbits_le32(SUN6I_BUS_SOFT_RST_REG0, @@ -223,7 +258,8 @@ static void spi0_init(void) if (IS_ENABLED(CONFIG_MACH_SUN50I) || IS_ENABLED(CONFIG_SUN50I_GEN_H6)) pin_function = SUN50I_GPC_SPI0; - else if (IS_ENABLED(CONFIG_MACH_SUNIV)) + else if (IS_ENABLED(CONFIG_MACH_SUNIV) || + IS_ENABLED(CONFIG_MACH_SUN8I_R528)) pin_function = SUNIV_GPC_SPI0;
spi0_pinmux_setup(pin_function);

From: Icenowy Zheng uwu@icenowy.me
To support SPI NAND flashes, more commands than Read (03h) are needed.
Extract the code for doing SPI transfer from the reading code for code reuse.
Signed-off-by: Icenowy Zheng uwu@icenowy.me Reviewed-by: Samuel Holland samuel@sholland.org Tested-by: Samuel Holland samuel@sholland.org # Orange Pi Zero Plus --- arch/arm/mach-sunxi/spl_spi_sunxi.c | 105 ++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 46 deletions(-)
diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index 7acb44f52a..e85ae96485 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -282,77 +282,90 @@ static void spi0_deinit(void)
#define SPI_READ_MAX_SIZE 60 /* FIFO size, minus 4 bytes of the header */
-static void sunxi_spi0_read_data(u8 *buf, u32 addr, u32 bufsize, - ulong spi_ctl_reg, - ulong spi_ctl_xch_bitmask, - ulong spi_fifo_reg, - ulong spi_tx_reg, - ulong spi_rx_reg, - ulong spi_bc_reg, - ulong spi_tc_reg, - ulong spi_bcc_reg) +static void sunxi_spi0_xfer(const u8 *txbuf, u32 txlen, + u8 *rxbuf, u32 rxlen, + ulong spi_ctl_reg, + ulong spi_ctl_xch_bitmask, + ulong spi_fifo_reg, + ulong spi_tx_reg, + ulong spi_rx_reg, + ulong spi_bc_reg, + ulong spi_tc_reg, + ulong spi_bcc_reg) { - writel(4 + bufsize, spi_bc_reg); /* Burst counter (total bytes) */ - writel(4, spi_tc_reg); /* Transfer counter (bytes to send) */ + writel(txlen + rxlen, spi_bc_reg); /* Burst counter (total bytes) */ + writel(txlen, spi_tc_reg); /* Transfer counter (bytes to send) */ if (spi_bcc_reg) - writel(4, spi_bcc_reg); /* SUN6I also needs this */ + writel(txlen, spi_bcc_reg); /* SUN6I also needs this */
- /* Send the Read Data Bytes (03h) command header */ - writeb(0x03, spi_tx_reg); - writeb((u8)(addr >> 16), spi_tx_reg); - writeb((u8)(addr >> 8), spi_tx_reg); - writeb((u8)(addr), spi_tx_reg); + for (u32 i = 0; i < txlen; i++) + writeb(*(txbuf++), spi_tx_reg);
/* Start the data transfer */ setbits_le32(spi_ctl_reg, spi_ctl_xch_bitmask);
/* Wait until everything is received in the RX FIFO */ - while ((readl(spi_fifo_reg) & 0x7F) < 4 + bufsize) + while ((readl(spi_fifo_reg) & 0x7F) < txlen + rxlen) ;
- /* Skip 4 bytes */ - readl(spi_rx_reg); + /* Skip txlen bytes */ + for (u32 i = 0; i < txlen; i++) + readb(spi_rx_reg);
/* Read the data */ - while (bufsize-- > 0) - *buf++ = readb(spi_rx_reg); + while (rxlen-- > 0) + *rxbuf++ = readb(spi_rx_reg); +} + +static void spi0_xfer(const u8 *txbuf, u32 txlen, u8 *rxbuf, u32 rxlen) +{ + uintptr_t base = spi0_base_address();
- /* tSHSL time is up to 100 ns in various SPI flash datasheets */ - udelay(1); + if (is_sun6i_gen_spi()) { + sunxi_spi0_xfer(txbuf, txlen, rxbuf, rxlen, + base + SUN6I_SPI0_TCR, + SUN6I_TCR_XCH, + base + SUN6I_SPI0_FIFO_STA, + base + SUN6I_SPI0_TXD, + base + SUN6I_SPI0_RXD, + base + SUN6I_SPI0_MBC, + base + SUN6I_SPI0_MTC, + base + SUN6I_SPI0_BCC); + } else { + sunxi_spi0_xfer(txbuf, txlen, rxbuf, rxlen, + base + SUN4I_SPI0_CTL, + SUN4I_CTL_XCH, + base + SUN4I_SPI0_FIFO_STA, + base + SUN4I_SPI0_TX, + base + SUN4I_SPI0_RX, + base + SUN4I_SPI0_BC, + base + SUN4I_SPI0_TC, + 0); + } }
static void spi0_read_data(void *buf, u32 addr, u32 len) { u8 *buf8 = buf; u32 chunk_len; - uintptr_t base = spi0_base_address(); + u8 txbuf[4];
while (len > 0) { chunk_len = len; + + /* Configure the Read Data Bytes (03h) command header */ + txbuf[0] = 0x03; + txbuf[1] = (u8)(addr >> 16); + txbuf[2] = (u8)(addr >> 8); + txbuf[3] = (u8)(addr); + if (chunk_len > SPI_READ_MAX_SIZE) chunk_len = SPI_READ_MAX_SIZE;
- if (is_sun6i_gen_spi()) { - sunxi_spi0_read_data(buf8, addr, chunk_len, - base + SUN6I_SPI0_TCR, - SUN6I_TCR_XCH, - base + SUN6I_SPI0_FIFO_STA, - base + SUN6I_SPI0_TXD, - base + SUN6I_SPI0_RXD, - base + SUN6I_SPI0_MBC, - base + SUN6I_SPI0_MTC, - base + SUN6I_SPI0_BCC); - } else { - sunxi_spi0_read_data(buf8, addr, chunk_len, - base + SUN4I_SPI0_CTL, - SUN4I_CTL_XCH, - base + SUN4I_SPI0_FIFO_STA, - base + SUN4I_SPI0_TX, - base + SUN4I_SPI0_RX, - base + SUN4I_SPI0_BC, - base + SUN4I_SPI0_TC, - 0); - } + spi0_xfer(txbuf, 4, buf8, chunk_len); + + /* tSHSL time is up to 100 ns in various SPI flash datasheets */ + udelay(1);
len -= chunk_len; buf8 += chunk_len;

From: Icenowy Zheng uwu@icenowy.me
This kind of read command is utilized in SPI NANDs for reading data inside a selected page, which is obviously smaller than how much 2 byte address can address. So 2 bytes are used for the address and one dummy byte is needed after the real address. As the address is sent out in bit endian, this makes it not compatible with usual 3 byte address.
Signed-off-by: Icenowy Zheng uwu@icenowy.me Reviewed-by: Samuel Holland samuel@sholland.org Tested-by: Samuel Holland samuel@sholland.org # Orange Pi Zero Plus --- arch/arm/mach-sunxi/spl_spi_sunxi.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index e85ae96485..fe95878eae 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -344,7 +344,7 @@ static void spi0_xfer(const u8 *txbuf, u32 txlen, u8 *rxbuf, u32 rxlen) } }
-static void spi0_read_data(void *buf, u32 addr, u32 len) +static void spi0_read_data(void *buf, u32 addr, u32 len, u32 addr_len) { u8 *buf8 = buf; u32 chunk_len; @@ -355,9 +355,15 @@ static void spi0_read_data(void *buf, u32 addr, u32 len)
/* Configure the Read Data Bytes (03h) command header */ txbuf[0] = 0x03; - txbuf[1] = (u8)(addr >> 16); - txbuf[2] = (u8)(addr >> 8); - txbuf[3] = (u8)(addr); + if (addr_len == 3) { + txbuf[1] = (u8)(addr >> 16); + txbuf[2] = (u8)(addr >> 8); + txbuf[3] = (u8)(addr); + } else if (addr_len == 2) { + txbuf[1] = (u8)(addr >> 8); + txbuf[2] = (u8)(addr); + txbuf[3] = 0; /* dummy */ + }
if (chunk_len > SPI_READ_MAX_SIZE) chunk_len = SPI_READ_MAX_SIZE; @@ -376,7 +382,7 @@ static void spi0_read_data(void *buf, u32 addr, u32 len) static ulong spi_load_read(struct spl_load_info *load, ulong sector, ulong count, void *buf) { - spi0_read_data(buf, sector, count); + spi0_read_data(buf, sector, count, 3);
return count; } @@ -395,7 +401,7 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
spi0_init();
- spi0_read_data((void *)header, load_offset, 0x40); + spi0_read_data((void *)header, load_offset, 0x40, 3);
if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) && image_get_magic(header) == FDT_MAGIC) { @@ -412,7 +418,7 @@ static int spl_spi_load_image(struct spl_image_info *spl_image, return ret;
spi0_read_data((void *)spl_image->load_addr, - load_offset, spl_image->size); + load_offset, spl_image->size, 3); }
spi0_deinit();

From: Icenowy Zheng uwu@icenowy.me
As we're going to add support for SPI NAND to this code, add code that allows multiple boot attempts with different load offsets and functions.
To keep compatibility with loading raw binary on SPI NOR, a bool parameter is used to allow booting without valid magic number when booting with SPI NOR.
Signed-off-by: Icenowy Zheng uwu@icenowy.me Reviewed-by: Samuel Holland samuel@sholland.org Tested-by: Samuel Holland samuel@sholland.org # Orange Pi Zero Plus --- arch/arm/mach-sunxi/spl_spi_sunxi.c | 51 ++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-)
diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index fe95878eae..58b1422da1 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -379,8 +379,8 @@ static void spi0_read_data(void *buf, u32 addr, u32 len, u32 addr_len) } }
-static ulong spi_load_read(struct spl_load_info *load, ulong sector, - ulong count, void *buf) +static ulong spi_load_read_nor(struct spl_load_info *load, ulong sector, + ulong count, void *buf) { spi0_read_data(buf, sector, count, 3);
@@ -389,38 +389,55 @@ static ulong spi_load_read(struct spl_load_info *load, ulong sector,
/*****************************************************************************/
-static int spl_spi_load_image(struct spl_image_info *spl_image, - struct spl_boot_device *bootdev) +static int spl_spi_try_load(struct spl_image_info *spl_image, + struct spl_boot_device *bootdev, + struct spl_load_info *load, u32 offset, + bool allow_raw) { int ret = 0; struct legacy_img_hdr *header; - uint32_t load_offset = sunxi_get_spl_size(); - header = (struct legacy_img_hdr *)CONFIG_TEXT_BASE; - load_offset = max_t(uint32_t, load_offset, CONFIG_SYS_SPI_U_BOOT_OFFS);
- spi0_init(); - - spi0_read_data((void *)header, load_offset, 0x40, 3); + if (load->read(load, offset, 0x40, (void *)header) == 0) + return -EINVAL;
if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) && image_get_magic(header) == FDT_MAGIC) { - struct spl_load_info load;
debug("Found FIT image\n"); - spl_set_bl_len(&load, 1); - load.read = spi_load_read; - ret = spl_load_simple_fit(spl_image, &load, - load_offset, header); + ret = spl_load_simple_fit(spl_image, load, + offset, header); } else { + if (!allow_raw && image_get_magic(header) != IH_MAGIC) + return -EINVAL; + ret = spl_parse_image_header(spl_image, bootdev, header); if (ret) return ret;
- spi0_read_data((void *)spl_image->load_addr, - load_offset, spl_image->size, 3); + if (load->read(load, offset, spl_image->size, + (void *)spl_image->load_addr) == 0) + ret = -EINVAL; }
+ return ret; +} + +static int spl_spi_load_image(struct spl_image_info *spl_image, + struct spl_boot_device *bootdev) +{ + int ret = 0; + uint32_t load_offset = sunxi_get_spl_size(); + struct spl_load_info load; + + load_offset = max_t(uint32_t, load_offset, CONFIG_SYS_SPI_U_BOOT_OFFS); + + spi0_init(); + + spl_set_bl_len(&load, 1); + load.read = spi_load_read_nor; + ret = spl_spi_try_load(spl_image, bootdev, &load, load_offset, true); + spi0_deinit();
return ret;

From: Icenowy Zheng uwu@icenowy.me
This commit adds support for booting from SPI NAND to SPL SPI code by mimicing the behavior of boot ROM (use fixed page size and sequentially try SPI NOR and NAND).
Signed-off-by: Icenowy Zheng uwu@icenowy.me Tested-by: Samuel Holland samuel@sholland.org # Orange Pi Zero Plus --- arch/arm/mach-sunxi/Kconfig | 16 ++++++++ arch/arm/mach-sunxi/spl_spi_sunxi.c | 75 +++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+)
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index ddf9414b08..cc576fc84d 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -1084,6 +1084,22 @@ 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_SPI_SUNXI_NAND + bool "Support for SPI NAND Flash on Allwinner SoCs in SPL" + depends on SPL_SPI_SUNXI + help + Enable support for SPI NAND Flash. This option allows SPL to mimic + Allwinner boot ROM's behavior to gain support for SPI NAND Flash; + a fixed page size needs to be assumed when building the SPL image. + +config SPL_SPI_SUNXI_NAND_ASSUMED_PAGESIZE + hex "Assumed pagesize for SPI NAND Flash in SPL" + depends on SPL_SPI_SUNXI_NAND + default 0x400 if MACH_SUNIV + help + Set the page size assumed by the SPL SPI NAND code, the default + value is the same with the boot ROM. + config PINE64_DT_SELECTION bool "Enable Pine64 device tree selection code" depends on MACH_SUN50I diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index 58b1422da1..7ecde2b753 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -344,6 +344,49 @@ static void spi0_xfer(const u8 *txbuf, u32 txlen, u8 *rxbuf, u32 rxlen) } }
+#if defined(CONFIG_SPL_SPI_SUNXI_NAND) +static int spi0_nand_switch_page(u32 page) +{ + unsigned count; + u8 buf[4]; + + /* Configure the Page Data Read (13h) command header */ + buf[0] = 0x13; + buf[1] = (u8)(page >> 16); + buf[2] = (u8)(page >> 8); + buf[3] = (u8)(page); + + spi0_xfer(buf, 4, NULL, 0); + + /* Wait for NAND chip to exit busy state */ + buf[0] = 0x0f; + buf[1] = 0xc0; + + /* Load a NAND page can take up to 2-decimal-digit microseconds */ + for (count = 0; count < 100; count ++) { + udelay(1); + spi0_xfer(buf, 2, buf+2, 1); + if (!(buf[2] & 0x1)) + return 0; + } + + return -ETIMEDOUT; +} + +static void spi0_nand_reset(void) +{ + u8 buf[1]; + + /* Configure the Device RESET (ffh) command */ + buf[0] = 0xff; + + spi0_xfer(buf, 1, NULL, 0); + + /* Wait for the NAND to finish resetting */ + udelay(10); +} +#endif + static void spi0_read_data(void *buf, u32 addr, u32 len, u32 addr_len) { u8 *buf8 = buf; @@ -387,6 +430,28 @@ static ulong spi_load_read_nor(struct spl_load_info *load, ulong sector, return count; }
+#if defined(CONFIG_SPL_SPI_SUNXI_NAND) +static ulong spi_load_read_nand(struct spl_load_info *load, ulong sector, + ulong count, void *buf) +{ + const ulong pagesize = CONFIG_SPL_SPI_SUNXI_NAND_ASSUMED_PAGESIZE; + ulong remain = count; + + while (remain) { + ulong count_in_page = min(remain, pagesize - (sector % pagesize)); + ulong current_page = sector / pagesize; + if (spi0_nand_switch_page(current_page) != 0) + return 0; + spi0_read_data(buf, sector % pagesize, count_in_page, 2); + remain -= count_in_page; + sector += count_in_page; + buf += count_in_page; + } + + return count; +} +#endif + /*****************************************************************************/
static int spl_spi_try_load(struct spl_image_info *spl_image, @@ -434,10 +499,20 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
spi0_init();
+#if defined(CONFIG_SPL_SPI_SUNXI_NAND) + spi0_nand_reset(); + load.read = spi_load_read_nand; + spl_set_bl_len(&load, 1); + ret = spl_spi_try_load(spl_image, bootdev, &load, load_offset, false); + if (!ret) + goto out; +#endif + spl_set_bl_len(&load, 1); load.read = spi_load_read_nor; ret = spl_spi_try_load(spl_image, bootdev, &load, load_offset, true);
+out: spi0_deinit();
return ret;

From: Icenowy Zheng uwu@icenowy.me
As we added support for SPI NAND to the existing SPL SPI codepath, route the boot code to it when it detects the BROM loads SPL from SPI NAND, as for SoCs with both SPI NAND and boot media indicator support, the boot media indicator is the same for SPI NOR and NAND.
Signed-off-by: Icenowy Zheng uwu@icenowy.me Reviewed-by: Samuel Holland samuel@sholland.org --- arch/arm/mach-sunxi/board.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c index f4dbb2a740..9b0d68a7a0 100644 --- a/arch/arm/mach-sunxi/board.c +++ b/arch/arm/mach-sunxi/board.c @@ -223,12 +223,10 @@ static int suniv_get_boot_source(void) case SUNIV_BOOTED_FROM_MMC0: return SUNXI_BOOTED_FROM_MMC0; case SUNIV_BOOTED_FROM_SPI: + case SUNIV_BOOTED_FROM_NAND: return SUNXI_BOOTED_FROM_SPI; case SUNIV_BOOTED_FROM_MMC1: return SUNXI_BOOTED_FROM_MMC2; - /* SPI NAND is not supported yet. */ - case SUNIV_BOOTED_FROM_NAND: - return SUNXI_INVALID_BOOT_SOURCE; } /* If we get here something went wrong try to boot from FEL.*/ printf("Unknown boot source from BROM: 0x%x\n", brom_call);

While MMC1 and MMC2 each currently have only one upper byte possibility, SPI NAND has quite a few. To solve this, split up the byte handling across two functions in preparation for SPI NAND support.
I have not tested this patch to validate that MMC SPL offsets are working. It looks like it should work though.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/include/asm/arch-sunxi/spl.h | 2 -- arch/arm/mach-sunxi/board.c | 22 +++++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/arch/arm/include/asm/arch-sunxi/spl.h b/arch/arm/include/asm/arch-sunxi/spl.h index 14944a20ea..92be936d56 100644 --- a/arch/arm/include/asm/arch-sunxi/spl.h +++ b/arch/arm/include/asm/arch-sunxi/spl.h @@ -16,8 +16,6 @@ #define SUNXI_BOOTED_FROM_NAND 1 #define SUNXI_BOOTED_FROM_MMC2 2 #define SUNXI_BOOTED_FROM_SPI 3 -#define SUNXI_BOOTED_FROM_MMC0_HIGH 0x10 -#define SUNXI_BOOTED_FROM_MMC2_HIGH 0x12
/* * Values taken from the F1C200s BootROM stack diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c index 9b0d68a7a0..7f4ee92991 100644 --- a/arch/arm/mach-sunxi/board.c +++ b/arch/arm/mach-sunxi/board.c @@ -276,6 +276,7 @@ static int sunxi_get_boot_source(void) uint32_t sunxi_get_boot_device(void) { int boot_source = sunxi_get_boot_source(); + int boot_dev = (boot_source & 0xF); /* Low nibble is device */
/* * When booting from the SD card or NAND memory, the "eGON.BT0" @@ -293,16 +294,15 @@ uint32_t sunxi_get_boot_device(void) * binary over USB. If it is found, it determines where SPL was * read from. */ - switch (boot_source) { - case SUNXI_INVALID_BOOT_SOURCE: + if (boot_source == SUNXI_INVALID_BOOT_SOURCE) return BOOT_DEVICE_BOARD; + + switch (boot_dev) { case SUNXI_BOOTED_FROM_MMC0: - case SUNXI_BOOTED_FROM_MMC0_HIGH: return BOOT_DEVICE_MMC1; case SUNXI_BOOTED_FROM_NAND: return BOOT_DEVICE_NAND; case SUNXI_BOOTED_FROM_MMC2: - case SUNXI_BOOTED_FROM_MMC2_HIGH: return BOOT_DEVICE_MMC2; case SUNXI_BOOTED_FROM_SPI: return BOOT_DEVICE_SPI; @@ -312,6 +312,14 @@ uint32_t sunxi_get_boot_device(void) return -1; /* Never reached */ }
+uint32_t sunxi_get_boot_position(void) +{ + int boot_source = sunxi_get_boot_source(); + int boot_pos = ((boot_source >> 8) & 0xF); /* High nibble is position */ + + return boot_pos; +} + #ifdef CONFIG_SPL_BUILD uint32_t sunxi_get_spl_size(void) { @@ -343,12 +351,8 @@ unsigned long board_spl_mmc_get_uboot_raw_sector(struct mmc *mmc,
sector = max(raw_sect, spl_size / 512);
- switch (sunxi_get_boot_source()) { - case SUNXI_BOOTED_FROM_MMC0_HIGH: - case SUNXI_BOOTED_FROM_MMC2_HIGH: + if (sunxi_get_boot_position() == 1) sector += (128 - 8) * 2; - break; - }
return sector; }

Currently there are two different boot device options: SPI and NAND. One is intended for SPI NOR operation, the other is intended for dedicated NAND operation.
Add a new option for SPI NAND operation.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/include/asm/spl.h | 1 + arch/mips/include/asm/spl.h | 1 + arch/riscv/include/asm/spl.h | 1 + arch/sandbox/include/asm/spl.h | 1 + 4 files changed, 4 insertions(+)
diff --git a/arch/arm/include/asm/spl.h b/arch/arm/include/asm/spl.h index ee79a19c05..92deba8019 100644 --- a/arch/arm/include/asm/spl.h +++ b/arch/arm/include/asm/spl.h @@ -22,6 +22,7 @@ enum { BOOT_DEVICE_NOR, BOOT_DEVICE_UART, BOOT_DEVICE_SPI, + BOOT_DEVICE_SPINAND, BOOT_DEVICE_USB, BOOT_DEVICE_SATA, BOOT_DEVICE_I2C, diff --git a/arch/mips/include/asm/spl.h b/arch/mips/include/asm/spl.h index 0a847edec8..02b580079a 100644 --- a/arch/mips/include/asm/spl.h +++ b/arch/mips/include/asm/spl.h @@ -16,6 +16,7 @@ enum { BOOT_DEVICE_NOR, BOOT_DEVICE_UART, BOOT_DEVICE_SPI, + BOOT_DEVICE_SPINAND, BOOT_DEVICE_USB, BOOT_DEVICE_SATA, BOOT_DEVICE_I2C, diff --git a/arch/riscv/include/asm/spl.h b/arch/riscv/include/asm/spl.h index 9c0bf9755c..56b5bf9d7e 100644 --- a/arch/riscv/include/asm/spl.h +++ b/arch/riscv/include/asm/spl.h @@ -18,6 +18,7 @@ enum { BOOT_DEVICE_NOR, BOOT_DEVICE_UART, BOOT_DEVICE_SPI, + BOOT_DEVICE_SPINAND, BOOT_DEVICE_USB, BOOT_DEVICE_SATA, BOOT_DEVICE_NVME, diff --git a/arch/sandbox/include/asm/spl.h b/arch/sandbox/include/asm/spl.h index 4fab24cd15..70f8ad4b58 100644 --- a/arch/sandbox/include/asm/spl.h +++ b/arch/sandbox/include/asm/spl.h @@ -16,6 +16,7 @@ enum { BOOT_DEVICE_NOR, BOOT_DEVICE_SPI, BOOT_DEVICE_NAND, + BOOT_DEVICE_SPINAND, };
/**

Instead of trying to boot from SPI NAND then SPI NOR in series, select one based on the current boot device.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/include/asm/arch-sunxi/spl.h | 1 + arch/arm/mach-sunxi/board.c | 5 ++++- arch/arm/mach-sunxi/spl_spi_sunxi.c | 28 ++++++++++++++++++---------- 3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/arch/arm/include/asm/arch-sunxi/spl.h b/arch/arm/include/asm/arch-sunxi/spl.h index 92be936d56..a9b7c0daca 100644 --- a/arch/arm/include/asm/arch-sunxi/spl.h +++ b/arch/arm/include/asm/arch-sunxi/spl.h @@ -16,6 +16,7 @@ #define SUNXI_BOOTED_FROM_NAND 1 #define SUNXI_BOOTED_FROM_MMC2 2 #define SUNXI_BOOTED_FROM_SPI 3 +#define SUNXI_BOOTED_FROM_SPINAND 4
/* * Values taken from the F1C200s BootROM stack diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c index 7f4ee92991..e374b75ac2 100644 --- a/arch/arm/mach-sunxi/board.c +++ b/arch/arm/mach-sunxi/board.c @@ -222,11 +222,12 @@ static int suniv_get_boot_source(void) switch (brom_call) { case SUNIV_BOOTED_FROM_MMC0: return SUNXI_BOOTED_FROM_MMC0; - case SUNIV_BOOTED_FROM_SPI: case SUNIV_BOOTED_FROM_NAND: return SUNXI_BOOTED_FROM_SPI; case SUNIV_BOOTED_FROM_MMC1: return SUNXI_BOOTED_FROM_MMC2; + case SUNIV_BOOTED_FROM_SPI: + return SUNXI_BOOTED_FROM_SPINAND; } /* If we get here something went wrong try to boot from FEL.*/ printf("Unknown boot source from BROM: 0x%x\n", brom_call); @@ -306,6 +307,8 @@ uint32_t sunxi_get_boot_device(void) return BOOT_DEVICE_MMC2; case SUNXI_BOOTED_FROM_SPI: return BOOT_DEVICE_SPI; + case SUNXI_BOOTED_FROM_SPINAND: + return BOOT_DEVICE_SPINAND; }
panic("Unknown boot source %d\n", boot_source); diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index 7ecde2b753..46db2900ca 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -494,28 +494,36 @@ static int spl_spi_load_image(struct spl_image_info *spl_image, int ret = 0; uint32_t load_offset = sunxi_get_spl_size(); struct spl_load_info load; + bool allow_raw = false;
load_offset = max_t(uint32_t, load_offset, CONFIG_SYS_SPI_U_BOOT_OFFS);
spi0_init();
+ switch (bootdev->boot_device) { #if defined(CONFIG_SPL_SPI_SUNXI_NAND) - spi0_nand_reset(); - load.read = spi_load_read_nand; - spl_set_bl_len(&load, 1); - ret = spl_spi_try_load(spl_image, bootdev, &load, load_offset, false); - if (!ret) - goto out; + case BOOT_DEVICE_SPINAND: + spi0_nand_reset(); + load.read = spi_load_read_nand; + spl_set_bl_len(&load, 1); + break; #endif + case BOOT_DEVICE_SPI: + load.read = spi_load_read_nor; + spl_set_bl_len(&load, 1); + allow_raw = true; + break; + }
- spl_set_bl_len(&load, 1); - load.read = spi_load_read_nor; - ret = spl_spi_try_load(spl_image, bootdev, &load, load_offset, true); + ret = spl_spi_try_load(spl_image, bootdev, &load, load_offset, allow_raw);
-out: spi0_deinit();
return ret; } /* Use priorty 0 to override the default if it happens to be linked in */ SPL_LOAD_IMAGE_METHOD("sunxi SPI", 0, BOOT_DEVICE_SPI, spl_spi_load_image); + +#if IS_ENABLED(CONFIG_SPL_SPI_SUNXI_NAND) +SPL_LOAD_IMAGE_METHOD("sunxi SPI NAND", 0, BOOT_DEVICE_SPINAND, spl_spi_load_image); +#endif

Boards that support SPI NAND need to specify the page and eraseblock size. Add those as Kconfig options.
Signed-off-by: John Watts contact@jookia.org --- common/spl/Kconfig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/common/spl/Kconfig b/common/spl/Kconfig index 6405374bcc..51d1f9f59d 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -906,6 +906,27 @@ config SPL_MUSB_NEW the drivers in drivers/usb/musb-new as part of an SPL build. The old drivers are in drivers/usb/musb.
+config SPL_SPINAND_SUPPORT + bool "Support SPINAND flash" + help + Enable support for SPINAND (Negative AND) flash in SPL. SPINAND flash + can be used to allow SPL to load U-Boot from supported devices. + +config SPL_SPINAND_PAGE_SIZE + hex "SPINAND chip page size" + depends on SPL_SPINAND_SUPPORT + help + Number of data bytes in one page for the SPINAND chip on the + board, not including the OOB area. + +config SPL_SPINAND_BLOCK_SIZE + hex "SPINAND chip eraseblock size" + depends on SPL_SPINAND_SUPPORT + help + Number of data bytes in one eraseblock for the SPINAND chip on the + board. This is the multiple of SPINAND_PAGE_SIZE and the number of + pages. + config SPL_NAND_SUPPORT bool "Support NAND flash" select SPL_LOAD_BLOCK

Use the newly created SPL_SPINAND configuration options instead of sunxi-only options.
No backwards compatibility is needed as the SPI NAND patches are not mainline yet.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/mach-sunxi/Kconfig | 16 ---------------- arch/arm/mach-sunxi/spl_spi_sunxi.c | 10 +++++----- 2 files changed, 5 insertions(+), 21 deletions(-)
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index cc576fc84d..ddf9414b08 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -1084,22 +1084,6 @@ 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_SPI_SUNXI_NAND - bool "Support for SPI NAND Flash on Allwinner SoCs in SPL" - depends on SPL_SPI_SUNXI - help - Enable support for SPI NAND Flash. This option allows SPL to mimic - Allwinner boot ROM's behavior to gain support for SPI NAND Flash; - a fixed page size needs to be assumed when building the SPL image. - -config SPL_SPI_SUNXI_NAND_ASSUMED_PAGESIZE - hex "Assumed pagesize for SPI NAND Flash in SPL" - depends on SPL_SPI_SUNXI_NAND - default 0x400 if MACH_SUNIV - help - Set the page size assumed by the SPL SPI NAND code, the default - value is the same with the boot ROM. - config PINE64_DT_SELECTION bool "Enable Pine64 device tree selection code" depends on MACH_SUN50I diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index 46db2900ca..602ebfe8c5 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -344,7 +344,7 @@ static void spi0_xfer(const u8 *txbuf, u32 txlen, u8 *rxbuf, u32 rxlen) } }
-#if defined(CONFIG_SPL_SPI_SUNXI_NAND) +#if defined(CONFIG_SPL_SPINAND_SUPPORT) static int spi0_nand_switch_page(u32 page) { unsigned count; @@ -430,11 +430,11 @@ static ulong spi_load_read_nor(struct spl_load_info *load, ulong sector, return count; }
-#if defined(CONFIG_SPL_SPI_SUNXI_NAND) +#if defined(CONFIG_SPL_SPINAND_SUPPORT) static ulong spi_load_read_nand(struct spl_load_info *load, ulong sector, ulong count, void *buf) { - const ulong pagesize = CONFIG_SPL_SPI_SUNXI_NAND_ASSUMED_PAGESIZE; + const ulong pagesize = CONFIG_SPL_SPINAND_PAGE_SIZE; ulong remain = count;
while (remain) { @@ -501,7 +501,7 @@ static int spl_spi_load_image(struct spl_image_info *spl_image, spi0_init();
switch (bootdev->boot_device) { -#if defined(CONFIG_SPL_SPI_SUNXI_NAND) +#if defined(CONFIG_SPL_SPINAND_SUPPORT) case BOOT_DEVICE_SPINAND: spi0_nand_reset(); load.read = spi_load_read_nand; @@ -524,6 +524,6 @@ static int spl_spi_load_image(struct spl_image_info *spl_image, /* Use priorty 0 to override the default if it happens to be linked in */ SPL_LOAD_IMAGE_METHOD("sunxi SPI", 0, BOOT_DEVICE_SPI, spl_spi_load_image);
-#if IS_ENABLED(CONFIG_SPL_SPI_SUNXI_NAND) +#if IS_ENABLED(CONFIG_SPL_SPINAND_SUPPORT) SPL_LOAD_IMAGE_METHOD("sunxi SPI NAND", 0, BOOT_DEVICE_SPINAND, spl_spi_load_image); #endif

These are implemented by the board-specific SPL code for use with NAND-aware loaders like UBI.
Signed-off-by: John Watts contact@jookia.org --- include/nand.h | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/include/nand.h b/include/nand.h index 220ffa202e..9fb8941dce 100644 --- a/include/nand.h +++ b/include/nand.h @@ -10,6 +10,7 @@
#include <config.h>
+extern void spinand_init(void); extern void nand_init(void); void nand_reinit(void); unsigned long nand_size(void); @@ -110,7 +111,9 @@ int nand_get_lock_status(struct mtd_info *mtd, loff_t offset); u32 nand_spl_adjust_offset(u32 sector, u32 offs); int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst); int nand_spl_read_block(int block, int offset, int len, void *dst); +int spinand_spl_read_block(int block, int offset, int len, void *dst); void nand_deselect(void); +void spinand_deselect(void);
#ifdef CONFIG_SYS_NAND_SELECT_DEVICE void board_nand_select_device(struct nand_chip *nand, int chip);

These are used by NAND-aware loaders such as UBI.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/mach-sunxi/spl_spi_sunxi.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c index 602ebfe8c5..d6b03678d0 100644 --- a/arch/arm/mach-sunxi/spl_spi_sunxi.c +++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c @@ -450,6 +450,27 @@ static ulong spi_load_read_nand(struct spl_load_info *load, ulong sector,
return count; } + +void spinand_init(void) +{ + spi0_init(); + spi0_nand_reset(); +} + +void spinand_deselect(void) +{ + spi0_deinit(); +} + +int spinand_spl_read_block(int block, int offset, int len, void *dst) +{ + ulong byte_offset = (block * CONFIG_SPL_SPINAND_BLOCK_SIZE) + offset; + + spi_load_read_nand(NULL, byte_offset, len, dst); + + return 0; +} + #endif
/*****************************************************************************/

UBI supports traditional NAND and oneNAND devices already, so add support for booting from SPI NAND devices.
Signed-off-by: John Watts contact@jookia.org --- common/spl/spl_ubi.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/common/spl/spl_ubi.c b/common/spl/spl_ubi.c index d7ab9efd11..72b4b195ed 100644 --- a/common/spl/spl_ubi.c +++ b/common/spl/spl_ubi.c @@ -21,6 +21,13 @@ int spl_ubi_load_image(struct spl_image_info *spl_image, int ret = 1;
switch (bootdev->boot_device) { +#ifdef CONFIG_SPL_SPINAND_SUPPORT + case BOOT_DEVICE_SPINAND: + spinand_init(); + info.read = spinand_spl_read_block; + info.peb_size = CONFIG_SPL_SPINAND_BLOCK_SIZE; + break; +#endif #ifdef CONFIG_SPL_NAND_SUPPORT case BOOT_DEVICE_NAND: nand_init(); @@ -77,12 +84,18 @@ int spl_ubi_load_image(struct spl_image_info *spl_image, if (!ret) spl_parse_image_header(spl_image, bootdev, header); out: +#ifdef CONFIG_SPL_SPINAND_SUPPORT + if (bootdev->boot_device == BOOT_DEVICE_SPINAND) + spinand_deselect(); +#endif #ifdef CONFIG_SPL_NAND_SUPPORT if (bootdev->boot_device == BOOT_DEVICE_NAND) nand_deselect(); #endif return ret; } -/* Use priorty 0 so that Ubi will override NAND and ONENAND methods */ + +/* Use priority 0 so that UBI will override all NAND methods */ SPL_LOAD_IMAGE_METHOD("NAND", 0, BOOT_DEVICE_NAND, spl_ubi_load_image); SPL_LOAD_IMAGE_METHOD("OneNAND", 0, BOOT_DEVICE_ONENAND, spl_ubi_load_image); +SPL_LOAD_IMAGE_METHOD("SPINAND", 0, BOOT_DEVICE_SPINAND, spl_ubi_load_image);

The FIT loader doesn't support access through UBI, so load the FIT image ourself in to memory then boot it normally.
Signed-off-by: John Watts contact@jookia.org --- common/spl/spl_ubi.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-)
diff --git a/common/spl/spl_ubi.c b/common/spl/spl_ubi.c index 72b4b195ed..b64f595160 100644 --- a/common/spl/spl_ubi.c +++ b/common/spl/spl_ubi.c @@ -12,6 +12,16 @@ #include <ubispl.h> #include <spl.h>
+static ulong ram_spl_load_read(struct spl_load_info *load, ulong sector, + ulong count, void *buf) +{ + char *ubi_contents = load->priv; + + memcpy(buf, ubi_contents + sector, count); + + return count; +} + int spl_ubi_load_image(struct spl_image_info *spl_image, struct spl_boot_device *bootdev) { @@ -69,10 +79,11 @@ int spl_ubi_load_image(struct spl_image_info *spl_image, puts("Loading Linux failed, falling back to U-Boot.\n"); } #endif - header = spl_get_load_buffer(-sizeof(*header), sizeof(header)); + /* Ensure there's enough room for the full UBI volume! */ + header = (void *)CONFIG_SYS_LOAD_ADDR; #ifdef CONFIG_SPL_UBI_LOAD_BY_VOLNAME volumes[0].vol_id = -1; - strncpy(volumes[0].name, + strlcpy(volumes[0].name, CONFIG_SPL_UBI_LOAD_MONITOR_VOLNAME, UBI_VOL_NAME_MAX + 1); #else @@ -81,8 +92,23 @@ int spl_ubi_load_image(struct spl_image_info *spl_image, volumes[0].load_addr = (void *)header;
ret = ubispl_load_volumes(&info, volumes, 1); - if (!ret) - spl_parse_image_header(spl_image, bootdev, header); + if (ret) + goto out; + + spl_parse_image_header(spl_image, bootdev, header); + + if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) && + image_get_magic(header) == FDT_MAGIC) { + struct spl_load_info load; + + printf("Found FIT\n"); + load.priv = (char *)header; + load.read = ram_spl_load_read; + spl_set_bl_len(&load, 1); + + ret = spl_load_simple_fit(spl_image, &load, 0, header); + } + out: #ifdef CONFIG_SPL_SPINAND_SUPPORT if (bootdev->boot_device == BOOT_DEVICE_SPINAND)

On Wed, Apr 10, 2024 at 10:26 PM John Watts contact@jookia.org wrote:
This series is my current working and tested setup for booting from SPI NAND chips on the Allwinner T113.
I have included the following patches from others. I may have modified them to work with the latest mainline:
https://lore.kernel.org/all/20221014030520.3067228-1-uwu@icenowy.me/ https://lore.kernel.org/all/20231111133432.755363-2-bigunclemax@gmail.com/
Hopefully this can get the ball rolling on how to properly implement SPI NAND support in mainline U-Boot.
Signed-off-by: John Watts contact@jookia.org
Hi John,
It doesn't look like I was sent the whole series (only 00 and 01), but I was able to find it on Patchwork and sift through it. A few general comments follow:
The introduction of `SUNXI_BOOTED_FROM_SPINAND` is the right call, since the newer sunxis use this for SPI-NAND, and `SUNXI_BOOTED_FROM_SPI` for SPI-NOR. The older sunxis, however, will use `SUNXI_BOOTED_FROM_SPI` for "it's either SPI-NAND or SPI-NOR, have fun figuring out which." While the rationale in 09/15 ("Instead of trying to boot from SPI NAND then SPI NOR in series, select one based on the current boot device.") is solid, we still need some code (perhaps in/called from `sunxi_get_boot_device`?) to handle the `SUNXI_BOOTED_FROM_SPI` case by performing flash type detection -- disabled for those sunxis known to use `SUNXI_BOOTED_FROM_SPINAND`, of course.
06/15 ("sunxi: enable support for SPI NAND booting on SUNIV") should be dropped from the series. You are updating `suniv_get_boot_source` when introducing `SUNXI_BOOTED_FROM_SPINAND` anyway, so you can just hook up `SUNIV_BOOTED_FROM_NAND` at that time (and a heads-up: I think you got it wired backwards in this version of the series).
On a more fundamental note: I am hesitant about the overall approach of having NAND reading code in `arch/arm/mach-sunxi/spl_spi_sunxi.c`. NANDs are more complex than NORs (requiring e.g. bad block handling) and U-Boot generally keeps the SPL load methods in `common/spl/spl_*.c`. It's good that the UBI-on-SPI-NAND method is hooked up there, but the "U-Boot image follows the (good) blocks of the SPL in NAND" method should probably live in the same directory.
Here's what I'd like to explore: can we introduce a `common/spl/spl_spinand.c` and update the `drivers/mtd/nand/spi/*.c` code to support running in SPL? This way `arch/arm/mach-sunxi/spl_spi_sunxi.c` must only provide the SPL-mode implementation of SPI -- that is, the actual sunxi-specific bit. Also, updating the `drivers/mtd/nand/spi/*.c` drivers for SPL-friendliness will mean dropping those `SPL_SPINAND_{PAGE,BLOCK}_SIZE` configuration options: the tables in the drivers will be the ones providing those after NAND autodetection.
Thoughts?
Cheers, Sam
Icenowy Zheng (5): sunxi: SPL SPI: extract code for doing SPI transfer sunxi: SPL SPI: add support for read command with 2 byte address sunxi: SPL SPI: allow multiple boot attempt sunxi: SPL SPI: add initial support for booting from SPI NAND sunxi: enable support for SPI NAND booting on SUNIV
John Watts (9): sunxi: Separate boot device and boot position spl: Add BOOT_DEVICE_SPINAND option sunxi: Implement BOOT_DEVICE_SPINAND in SPL spl: Add SPL_SPINAND configuration options sunxi: Use SPL_SPINAND for configuration nand: Add spinand_ helper functions sunxi: Implement spinand_ helpers spl: Support SPI NAND boot in UBI spl: Support loading FIT images in UBI
Maksim Kiselev (1): sunxi: SPL SPI: Add SPI boot support for the Allwinner R528/T113 SoCs
arch/arm/include/asm/arch-sunxi/spl.h | 3 +- arch/arm/include/asm/spl.h | 1 + arch/arm/mach-sunxi/Kconfig | 2 +- arch/arm/mach-sunxi/board.c | 31 +-- arch/arm/mach-sunxi/spl_spi_sunxi.c | 348 +++++++++++++++++++++++++--------- arch/mips/include/asm/spl.h | 1 + arch/riscv/include/asm/spl.h | 1 + arch/sandbox/include/asm/spl.h | 1 + common/spl/Kconfig | 21 ++ common/spl/spl_ubi.c | 49 ++++- include/nand.h | 3 + 11 files changed, 354 insertions(+), 107 deletions(-)
base-commit: 777c28460947371ada40868dc994dfe8537d7115 change-id: 20240411-spinand-eb7d8319813b
Best regards,
John Watts contact@jookia.org

On Thu, Apr 11, 2024 at 05:27:08PM -0600, Sam Edwards wrote:
Hi John,
It doesn't look like I was sent the whole series (only 00 and 01), but I was able to find it on Patchwork and sift through it. A few general comments follow:
The introduction of `SUNXI_BOOTED_FROM_SPINAND` is the right call, since the newer sunxis use this for SPI-NAND, and `SUNXI_BOOTED_FROM_SPI` for SPI-NOR. The older sunxis, however, will use `SUNXI_BOOTED_FROM_SPI` for "it's either SPI-NAND or SPI-NOR, have fun figuring out which." While the rationale in 09/15 ("Instead of trying to boot from SPI NAND then SPI NOR in series, select one based on the current boot device.") is solid, we still need some code (perhaps in/called from `sunxi_get_boot_device`?) to handle the `SUNXI_BOOTED_FROM_SPI` case by performing flash type detection -- disabled for those sunxis known to use `SUNXI_BOOTED_FROM_SPINAND`, of course.
Is there already code that can do this probing somewhere in U-Boot? I'd rather not try and support older boards with an ambiguous boot method, they already don't work.
06/15 ("sunxi: enable support for SPI NAND booting on SUNIV") should be dropped from the series. You are updating `suniv_get_boot_source` when introducing `SUNXI_BOOTED_FROM_SPINAND` anyway, so you can just hook up `SUNIV_BOOTED_FROM_NAND` at that time (and a heads-up: I think you got it wired backwards in this version of the series).
Some of the redunancy in patches are from including Icenowy's patchset.
On a more fundamental note: I am hesitant about the overall approach of having NAND reading code in `arch/arm/mach-sunxi/spl_spi_sunxi.c`. NANDs are more complex than NORs (requiring e.g. bad block handling) and U-Boot generally keeps the SPL load methods in `common/spl/spl_*.c`. It's good that the UBI-on-SPI-NAND method is hooked up there, but the "U-Boot image follows the (good) blocks of the SPL in NAND" method should probably live in the same directory.
Here's what I'd like to explore: can we introduce a `common/spl/spl_spinand.c` and update the `drivers/mtd/nand/spi/*.c` code to support running in SPL? This way `arch/arm/mach-sunxi/spl_spi_sunxi.c` must only provide the SPL-mode implementation of SPI -- that is, the actual sunxi-specific bit. Also, updating the `drivers/mtd/nand/spi/*.c` drivers for SPL-friendliness will mean dropping those `SPL_SPINAND_{PAGE,BLOCK}_SIZE` configuration options: the tables in the drivers will be the ones providing those after NAND autodetection.
This is a little bit of a mess:
common/spl/spl_spi.c implements general SPI NOR reading drivers/mtd/spi/sf-uclass.c wraps SPI flash access using DM drivers/spi/spi-sunxi.c implements the SPI controller arch/arm/mach-sunxi/spl_spi_sunxi.c overrides spl_spi
It would be good to somehow have spl_spi provide functions spl_spi uses instead of having spl_spi_sunxi implement it directly.
common/spl/spl_nand.c implements general NAND reading common/spl/spl_ubi.c implements UBI reading drivers/mtd/nand/raw/sunxi_nand.c implements sunxi NAND reading drivers/mtd/nand/raw/nand_spl_simple.c implmements a generic solution drivers/mtd/nand/raw/sunxi_nand_spl.c implements sunxi SPL NAND reading
spl_ubi requires nand_spl_simple callbacks, sunxi_nand_spl doesn't implement these.
It's possible spl_spi, spl_nand and spl_ubi all work if the DM code is used for sunxi. Maybe that's a good goal here? Getting DM in SPL working on these boards then hooking it in to spl_ubi seems like it could solve some pain. I'm not too concerned about old SoCs at this point.
For non-DM Adding spl_spinand would work for the case of reading the next good block. I'm not actually sure if the SPI NAND code supports the bad block table yet. But we would still need an API for spl_ubi to access.
Thoughts?
Cheers, Sam
John.
participants (2)
-
John Watts
-
Sam Edwards