[PATCH 4/4] mach-sunxi: Add SPI-NAND SPL-Support
Benedikt-Alexander Mokroß
u-boot at bamkrs.de
Tue Apr 28 16:04:14 CEST 2020
This patch uses the refactored sunxi SPI-SPL driver from the previous
patches of this series to add support for SPL booting from SPI-NAND.
SPI-NAND-Boot is supported by, at least, the V3s.
Signed-off-by: Benedikt-Alexander Mokroß <u-boot at bamkrs.de>
---
arch/arm/mach-sunxi/spl/spi/Kconfig | 16 +
arch/arm/mach-sunxi/spl/spi/Makefile | 1 +
.../mach-sunxi/spl/spi/spl_spi_sunxi_nand.c | 497 ++++++++++++++++++
3 files changed, 678 insertions(+)
create mode 100644 arch/arm/mach-sunxi/spl/spi/spl_spi_sunxi_nand.c
diff --git a/arch/arm/mach-sunxi/spl/spi/Kconfig b/arch/arm/mach-sunxi/spl/spi/Kconfig
index 04e2e506d3..ef14e14ea7 100644
--- a/arch/arm/mach-sunxi/spl/spi/Kconfig
+++ b/arch/arm/mach-sunxi/spl/spi/Kconfig
@@ -8,6 +8,22 @@ config SPL_SPI_SUNXI_NOR
sunxi SPI-NOR 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 MACH_SUN8I_V3S
+ help
+ Enable support for SPI-NAND Flash. This option allows SPL to read from
+ sunxi SPI-NAND Flash. It uses the same method as the boot ROM, so does
+ not need any extra configuration.
+
+config SPL_SPI_SUNXI_NAND_USE_GENERIC2K_ON_UNKNOWN
+ bool "Generic SPI-NAND config with 2K page-size"
+ depends on SPL_SPI_SUNXI_NAND
+ default n
+ help
+ If no known spi-nand is found, try with generic settings for spi-nand with a
+ page-size of 2K.
+
choice
prompt "SPI Clockdivider"
depends on SPL_SPI_SUNXI
diff --git a/arch/arm/mach-sunxi/spl/spi/Makefile b/arch/arm/mach-sunxi/spl/spi/Makefile
index 52e58a3c65..696f436e98 100644
--- a/arch/arm/mach-sunxi/spl/spi/Makefile
+++ b/arch/arm/mach-sunxi/spl/spi/Makefile
@@ -10,6 +10,7 @@
ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_SPL_SPI_SUNXI) += spl_spi_sunxi.o
+obj-$(CONFIG_SPL_SPI_SUNXI_NAND) += spl_spi_sunxi_nand.o
obj-$(CONFIG_SPL_SPI_SUNXI_NOR) += spl_spi_sunxi_nor.o
endif
diff --git a/arch/arm/mach-sunxi/spl/spi/spl_spi_sunxi_nand.c b/arch/arm/mach-sunxi/spl/spi/spl_spi_sunxi_nand.c
new file mode 100644
index 0000000000..66cec60839
--- /dev/null
+++ b/arch/arm/mach-sunxi/spl/spi/spl_spi_sunxi_nand.c
@@ -0,0 +-1,497 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 Benedikt-Alexander Mokroß <u-boot at bamkrs.de>
+ */
+
+#include "spl_spi_sunxi.h"
+
+/*****************************************************************************/
+
+#define DUMMY_BURST_BYTE 0x00
+
+#ifndef CONFIG_SPL_SPINAND_SUNXI_SPL_SIZE
+#define CONFIG_SPL_SPINAND_SUNXI_SPL_SIZE 0x6000
+#endif
+#ifndef CONFIG_SPL_SPINAND_SUNXI_UBOOT_PADDING
+#define CONFIG_SPL_SPINAND_SUNXI_UBOOT_PADDING 0x2000
+#endif
+#ifndef CONFIG_SPL_SPINAND_SUNXI_PAGESIZE
+#define CONFIG_SPL_SPINAND_SUNXI_PAGESIZE 2048
+#endif
+#ifndef CONFIG_SYS_SPI_U_BOOT_OFFS
+#define CONFIG_SYS_SPI_U_BOOT_OFFS (CONFIG_SPL_SPINAND_SUNXI_SPL_SIZE * (CONFIG_SPL_SPINAND_SUNXI_PAGESIZE / 1024)) + CONFIG_SPL_SPINAND_SUNXI_UBOOT_PADDING
+#endif
+
+/*****************************************************************************/
+
+struct sunxi_nand_config {
+ const char *name;
+ u32 jedec_id;
+ u32 page_mask;
+ u8 page_shift;
+ u32 addr_mask;
+ u8 addr_shift;
+};
+
+/*****************************************************************************/
+
+static const char *sunxi_spl_name = "sunxi SPI-NAND";
+
+/**
+ * List of known NANDS. For now, all of them are generic 2k devices.
+ */
+static struct sunxi_nand_config sunxi_known_nands[] = {
+ {
+ .name = "Macronix MX35LF1GE4AB",
+ .jedec_id = 0x00C212C2, /* MX35LFxGE4AB repeat the C2x2 over and over again */
+ .page_mask = 0x00FFFFFF,
+ .page_shift = 11,
+ .addr_mask = 0x7FF,
+ .addr_shift = 0,
+ },
+ {
+ .name = "Macronix MX35LF2GE4AB",
+ .jedec_id = 0x00C222C2,
+ .page_mask = 0x00FFFFFF,
+ .page_shift = 11,
+ .addr_mask = 0x7FF,
+ .addr_shift = 0,
+ },
+ {
+ .name = "Winbond W25N01GVxxIG",
+ .jedec_id = 0x00EFAA21,
+ .page_mask = 0x0000FFFF,
+ .page_shift = 11,
+ .addr_mask = 0x7FF,
+ .addr_shift = 0,
+ },
+ {
+ .name = "GigaDevice GD5F1GQ4RCxxG",
+ .jedec_id = 0x00C8B148, /* 3.3v */
+ .page_mask = 0x00FFFFFF,
+ .page_shift = 11,
+ .addr_mask = 0x7FF,
+ .addr_shift = 0,
+ },
+ {
+ .name = "GigaDevice GD5F1GQ4RCxxG",
+ .jedec_id = 0x00C8A148, /* 1.8V */
+ .page_mask = 0x00FFFFFF,
+ .page_shift = 11,
+ .addr_mask = 0x7FF,
+ .addr_shift = 0,
+ },
+
+ { /* Sentinel */
+ .name = NULL,
+ }
+};
+
+#ifdef CONFIG_SPL_SPI_SUNXI_NAND_USE_GENERIC2K_ON_UNKNOWN
+/**
+ * Generic 2k device-configuration
+ */
+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,
+};
+#endif
+
+/*
+ * Enumerate all known nands and return the config if found.
+ * Returns NULL if none is found or if CONFIG_SPL_SPI_SUNXI_NAND_USE_GENERIC2K_ON_UNKNOWN
+ * is set, returns a generic configuration compatible to most standard 2k Page-Size SPI-NANDs
+ */
+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_SPI_SUNXI_NAND_USE_GENERIC2K_ON_UNKNOWN
+ return &sunxi_generic_nand_config;
+#else
+ return NULL;
+#endif
+}
+
+/*****************************************************************************/
+
+#define SPI_READ_MAX_SIZE 60 /* FIFO size, minus 4 bytes of the header */
+
+/**
+ * Load a page in devices cache
+ */
+static void sunxi_spi0_load_page(struct sunxi_nand_config *config, u32 addr, 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) {
+ /* Read Page in Cache */
+ u8 status = 0x01;
+
+ addr = addr >> (config->page_shift);
+ addr = addr & config->page_mask;
+
+ writel(4, spi_bc_reg); /* Burst counter (total bytes) */
+ writel(4, spi_tc_reg); /* Transfer counter (bytes to send) */
+ if (spi_bcc_reg)
+ writel(4, spi_bcc_reg); /* SUN6I also needs this */
+
+ /* Send the Read Data Bytes (13h) command header */
+ writeb(0x13, spi_tx_reg);
+ writeb((u8)(addr >> 16), spi_tx_reg);
+ writeb((u8)(addr >> 8), spi_tx_reg);
+ writeb((u8)(addr), spi_tx_reg);
+
+ /* Start the data transfer */
+ setbits_le32(spi_ctl_reg, spi_ctl_xch_bitmask);
+
+ /* Wait till all bytes are send */
+ while ((readl(spi_fifo_reg) & 0x7F0000) > 0)
+ ;
+
+ /* wait till all bytes are read */
+ while ((readl(spi_fifo_reg) & 0x7F) < 4)
+ ;
+
+ /* Discard the 4 empty bytes from our send */
+ readl(spi_rx_reg);
+
+ /* Wait until page loaded */
+ do {
+ /* tCS = 100ns + tRD_ECC 70ns -> 200ns wait */
+ ndelay(200);
+
+ /* Poll */
+ writel(2 + 1, spi_bc_reg); /* Burst counter (total bytes) */
+ writel(2, spi_tc_reg); /* Transfer counter (bytes to send) */
+ if (spi_bcc_reg)
+ writel(2, spi_bcc_reg); /* SUN6I also needs this */
+ /* Send the Read Status Bytes (0FC0h) command header */
+ writeb(0x0F, spi_tx_reg);
+ writeb(0xC0, spi_tx_reg);
+
+ /* Start the data transfer */
+ setbits_le32(spi_ctl_reg, spi_ctl_xch_bitmask);
+
+ while ((readl(spi_fifo_reg) & 0x7F) < 2 + 1)
+ ;
+
+ /* skip 2 since we send 2 */
+ readb(spi_rx_reg);
+ readb(spi_rx_reg);
+
+ status = readb(spi_rx_reg);
+
+ } while ((status & 0x01) == 0x01);
+}
+
+static void spi0_load_page(struct sunxi_nand_config *config, u32 addr)
+{
+ uintptr_t base = sunxi_spi0_base_address();
+
+ if (is_sun6i_gen_spi()) {
+ sunxi_spi0_load_page(config, addr,
+ 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_load_page(config, addr,
+ 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);
+ }
+}
+
+/**
+ * Read data from devices cache
+ */
+static void sunxi_spi0_read_cache(struct sunxi_nand_config *config, 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)
+{
+ addr = addr >> (config->addr_shift);
+ addr = addr & config->addr_mask;
+
+ writel(4 + bufsize, spi_bc_reg); /* Burst counter (total bytes) */
+ writel(4, spi_tc_reg); /* Transfer counter (bytes to send) */
+ if (spi_bcc_reg)
+ writel(4, spi_bcc_reg); /* SUN6I also needs this */
+
+ /* Send the Read Data Bytes (0Bh) command header */
+ writeb(0x0B, spi_tx_reg);
+ writeb((u8)((addr >> 8)), spi_tx_reg);
+ writeb((u8)(addr), spi_tx_reg);
+ writeb(DUMMY_BURST_BYTE, 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)
+ ;
+
+ /* Skip 4 bytes since we send 4 */
+ readl(spi_rx_reg);
+
+ /* Read the data */
+ while (bufsize-- > 0)
+ *buf++ = readb(spi_rx_reg);
+
+ /* tSHSL time is up to 100 ns in various SPI flash datasheets */
+ ndelay(100);
+}
+
+static void spi0_read_cache(struct sunxi_nand_config *config, void *buf, u32 addr, u32 len)
+{
+ uintptr_t base = sunxi_spi0_base_address();
+
+ if (is_sun6i_gen_spi()) {
+ sunxi_spi0_read_cache(config, buf, addr, 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_cache(config, buf, addr, 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);
+ }
+}
+
+/**
+ * Load (bulk) data to cache an read it
+ * Handles chunking to SPI's buffsize
+ */
+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;
+
+ /* Load first page */
+ spi0_load_page(config, addr);
+
+ while (len > 0) {
+ /* Check if new data must be loaded in cache */
+ curr_page = (addr >> (config->page_shift)) & config->page_mask;
+ if (curr_page > last_page) {
+ spi0_load_page(config, addr);
+ last_page = curr_page;
+ }
+
+ /* Chunk to SPI-Buffers max size */
+ chunk_len = len;
+ if (chunk_len > SPI_READ_MAX_SIZE) {
+ chunk_len = SPI_READ_MAX_SIZE;
+ }
+
+ /* Check if chunk length exceeds page */
+ if ((((addr + chunk_len) >> (config->page_shift)) & config->page_mask) > curr_page) {
+ chunk_len = ((curr_page + 1) << (config->page_shift)) - addr;
+ }
+
+ /* Read data from cache */
+ spi0_read_cache(config, buf8, addr, chunk_len);
+ len -= chunk_len;
+ buf8 += chunk_len;
+ addr += chunk_len;
+ }
+}
+
+/**
+ * Read ID Bytes register
+ */
+static u32 sunxi_spi0_read_id(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)
+{
+ u8 idbuf[3];
+
+ writel(2 + 3, spi_bc_reg); /* Burst counter (total bytes) */
+ writel(2, spi_tc_reg); /* Transfer counter (bytes to send) */
+ if (spi_bcc_reg)
+ writel(2, spi_bcc_reg); /* SUN6I also needs this */
+
+ /* Send the Read ID Bytes (9Fh) command header */
+ writeb(0x9F, spi_tx_reg);
+ writeb(DUMMY_BURST_BYTE, 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) < 2 + 3)
+ ;
+
+ /* Skip 2 bytes */
+ readb(spi_rx_reg);
+ readb(spi_rx_reg);
+
+ /* Read the data */
+ //while (bufsize-- > 0)
+ idbuf[0] = readb(spi_rx_reg);
+ idbuf[1] = readb(spi_rx_reg);
+ idbuf[2] = readb(spi_rx_reg);
+
+ /* tSHSL time is up to 100 ns in various SPI flash datasheets */
+ ndelay(100);
+
+ return idbuf[2] | (idbuf[1] << 8) | (idbuf[0] << 16);
+}
+
+static u32 spi0_read_id(void)
+{
+ uintptr_t base = sunxi_spi0_base_address();
+
+ if (is_sun6i_gen_spi()) {
+ return sunxi_spi0_read_id(
+ 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 {
+ return sunxi_spi0_read_id(
+ 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 ulong spi_load_read(struct spl_load_info *load, ulong sector, ulong count, void *buf)
+{
+ spi0_read_data((struct sunxi_nand_config *)load->priv, buf, sector, count);
+
+ return count;
+}
+
+/*****************************************************************************/
+
+static int spl_spi_nand_load_image(struct spl_image_info *spl_image,
+ struct spl_boot_device *bootdev)
+{
+ int ret = 0;
+ u32 id = 0;
+ struct image_header *header;
+ struct sunxi_nand_config *config;
+
+ header = (struct image_header *)(CONFIG_SYS_TEXT_BASE);
+
+ sunxi_spi0_init();
+ id = spi0_read_id();
+
+ /*
+ * If we receive only zeros, there is most definetly no device attached.
+ */
+ if (id == 0) {
+ sunxi_spi0_deinit();
+ printf("%s: Received only zeros on jedec_id probe, assuming no spi-nand attached.\n", sunxi_spl_name);
+ return -1;
+ }
+
+ /*
+ * Check if device is known and compatible.
+ */
+ config = sunxi_spinand_enumerate(id);
+ if (!config) {
+ sunxi_spi0_deinit();
+ printf("%s: Unknown chip %x\n", sunxi_spl_name, id);
+ return -1;
+ }
+
+ printf("%s: Found %s (%x)\n", sunxi_spl_name, config->name, id);
+
+ /*
+ * Read the header data from the image and parse it for validity.
+ */
+ spi0_read_data(config, (void *)header, CONFIG_SYS_SPI_U_BOOT_OFFS, 0x40);
+ if (image_check_hcrc(header)) {
+ printf("%s: u-boot hcrc OK!\n", sunxi_spl_name);
+
+ /* If it is an FIT-Image, load as FIT, if not, parse header normaly */
+ if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) && image_get_magic(header) == FDT_MAGIC) {
+ struct spl_load_info load;
+
+ debug("%s: Found FIT image\n", sunxi_spl_name);
+ load.dev = NULL;
+ load.priv = (void *)config;
+ load.filename = NULL;
+ load.bl_len = 1;
+ load.read = spi_load_read;
+ ret = spl_load_simple_fit(spl_image, &load, CONFIG_SYS_SPI_U_BOOT_OFFS, header);
+
+ } else {
+ ret = spl_parse_image_header(spl_image, header);
+ if (ret) {
+ printf("%s: spl_parse_image_header: %x\n", sunxi_spl_name, ret);
+ sunxi_spi0_deinit();
+ return ret;
+ }
+ /*
+ * If everything is fine, read the rest of u-boot and start
+ */
+ spi0_read_data(config, (void *)spl_image->load_addr, CONFIG_SYS_SPI_U_BOOT_OFFS, spl_image->size);
+ return 0;
+ }
+ }
+ printf("%s: u-boot hcrc ERROR!\n", sunxi_spl_name);
+ sunxi_spi0_deinit();
+ return -1;
+}
+
+/* Use priority 0 to override the default if it happens to be linked in */
+SPL_LOAD_IMAGE_METHOD("sunxi SPI-NAND", 0, BOOT_DEVICE_SPI, spl_spi_nand_load_image);
--
2.20.1
More information about the U-Boot
mailing list