[PATCH v2 4/4] mach-sunxi: Add SPI-NAND SPL-Support

Benedikt-Alexander Mokroß u-boot at bamkrs.de
Tue Apr 28 16:26:18 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>
---
Changes for v2:
  - Removed a misplaced - in the line @@ -0,0 +1,496 @@ (I don't know what happened there)

  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,496 @@
+// 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