[U-Boot] [PATCH v1] sunxi: Support booting from SPI flash

Siarhei Siamashka siarhei.siamashka at gmail.com
Sun Jun 5 01:51:35 CEST 2016


Allwinner devices support SPI flash as one of the possible
bootable media type. The SPI flash chip needs to be connected
to SPI0 pins (port C) to make this work. More information is
available at:

    https://linux-sunxi.org/Bootable_SPI_flash

This patch adds the initial support for booting from SPI flash.
The offset of the main U-Boot binary is assumed to be 0x8000.

The existing SPI frameworks are not used in order to reduce the
code size in the SPL.

Tested on the Orange Pi PC (Allwinner H3) and A13-OLinuXino-Micro
(Allwinner A13) boards.

Signed-off-by: Siarhei Siamashka <siarhei.siamashka at gmail.com>
---
 arch/arm/mach-sunxi/board.c | 176 ++++++++++++++++++++++++++++++++++++++++++++
 board/sunxi/board.c         |  98 ++++++++++++++++++++++++
 2 files changed, 274 insertions(+)

diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c
index bd15b9b..a07a0c8 100644
--- a/arch/arm/mach-sunxi/board.c
+++ b/arch/arm/mach-sunxi/board.c
@@ -131,13 +131,186 @@ static int gpio_init(void)
 	return 0;
 }
 
+#ifdef CONFIG_SPL_BUILD
+void board_spi_init(void);
+
+static void spi_data_transfer(void *buf,
+			      u32   bufsize,
+			      void *spi_ctl_reg,
+			      u32   spi_ctl_xch_bitmask,
+			      void *spi_fifo_reg,
+			      void *spi_tx_reg,
+			      void *spi_rx_reg,
+			      void *spi_bc_reg,
+			      void *spi_tc_reg,
+			      void *spi_bcc_reg)
+{
+	u32  cnt;
+	u32  rxsize  = bufsize;
+	u32  txsize  = bufsize;
+	u8  *rxbuf8 = buf;
+	u8  *txbuf8 = buf;
+	u32 *rxbuf;
+	u32 *txbuf;
+
+	/* sun6i uses 3 registers, sun4i only needs 2 */
+	writel(bufsize, spi_bc_reg);
+	writel(bufsize, spi_tc_reg);
+	if (spi_bcc_reg)
+		writel(bufsize, spi_bcc_reg);
+
+	/* Fill the TX buffer with some initial data */
+	cnt = (-(u32)txbuf8 & 3) + 60;
+	if (cnt > txsize)
+		cnt = txsize;
+	while (cnt-- > 0) {
+		writeb(*txbuf8++, spi_tx_reg);
+		txsize--;
+	}
+
+	/* Start the data transfer */
+	writel(readl(spi_ctl_reg) | spi_ctl_xch_bitmask, spi_ctl_reg);
+
+	/* Read the initial unaligned part of the data */
+	cnt = (-(u32)rxbuf8 & 3);
+	if (cnt > rxsize)
+		cnt = rxsize;
+	while (cnt > 0) {
+		u32 fiforeg = readl(spi_fifo_reg);
+		int rxfifo = fiforeg & 0x7F;
+		if (rxfifo > 0) {
+			*rxbuf8++ = readb(spi_rx_reg);
+			cnt--;
+			rxsize--;
+		}
+	}
+
+	/* Fast processing of the aligned part (read/write 32-bit at a time) */
+	rxbuf = (u32 *)rxbuf8;
+	txbuf = (u32 *)txbuf8;
+	while (rxsize >= 4) {
+		u32 fiforeg = readl(spi_fifo_reg);
+		int rxfifo = fiforeg & 0x7F;
+		int txfifo = (fiforeg >> 16) & 0x7F;
+		if (rxfifo >= 4) {
+			*rxbuf++ = readl(spi_rx_reg);
+			rxsize -= 4;
+		}
+		if (txfifo < 60 && txsize >= 4) {
+			writel(*txbuf++, spi_tx_reg);
+			txsize -= 4;
+		}
+	}
+
+	/* Handle the trailing part pf the data */
+	rxbuf8 = (u8 *)rxbuf;
+	txbuf8 = (u8 *)txbuf;
+	while (rxsize >= 1) {
+		u32 fiforeg = readl(spi_fifo_reg);
+		int rxfifo = fiforeg & 0x7F;
+		int txfifo = (fiforeg >> 16) & 0x7F;
+		if (rxfifo >= 1) {
+			*rxbuf8++ = readb(spi_rx_reg);
+			rxsize -= 1;
+		}
+		if (txfifo < 60 && txsize >= 1) {
+			writeb(*txbuf8++, spi_tx_reg);
+			txsize -= 1;
+		}
+	}
+}
+
+#define SUN4I_SPI0_CCTL             (void *)(0x01C05000 + 0x1C)
+#define SUN4I_SPI0_CTL              (void *)(0x01C05000 + 0x08)
+#define SUN4I_SPI0_RX               (void *)(0x01C05000 + 0x00)
+#define SUN4I_SPI0_TX               (void *)(0x01C05000 + 0x04)
+#define SUN4I_SPI0_FIFO_STA         (void *)(0x01C05000 + 0x28)
+#define SUN4I_SPI0_BC               (void *)(0x01C05000 + 0x20)
+#define SUN4I_SPI0_TC               (void *)(0x01C05000 + 0x24)
+
+#define SUN6I_SPI0_CCTL             (void *)(0x01C68000 + 0x24)
+#define SUN6I_SPI0_GCR              (void *)(0x01C68000 + 0x04)
+#define SUN6I_SPI0_TCR              (void *)(0x01C68000 + 0x08)
+#define SUN6I_SPI0_FIFO_STA         (void *)(0x01C68000 + 0x1C)
+#define SUN6I_SPI0_MBC              (void *)(0x01C68000 + 0x30)
+#define SUN6I_SPI0_MTC              (void *)(0x01C68000 + 0x34)
+#define SUN6I_SPI0_BCC              (void *)(0x01C68000 + 0x38)
+#define SUN6I_SPI0_TXD              (void *)(0x01C68000 + 0x200)
+#define SUN6I_SPI0_RXD              (void *)(0x01C68000 + 0x300)
+
+#define SUN4I_CTL_XCH               (1 << 10)
+
+#define SUN6I_CTL_MASTER            (1 << 1)
+#define SUN6I_CTL_ENABLE            (1 << 0)
+#define SUN6I_TCR_XCH               (1 << 31)
+
+static void spi_read_data(void *buf, u32 addr, u32 len)
+{
+	u8 *buf8 = buf;
+	u32 chunk_len;
+
+	while (len > 0) {
+		u8 tmpbuf[64] = { 0x03, addr >> 16, addr >> 8, addr };
+		chunk_len = len;
+		if (chunk_len > 60)
+			chunk_len = 60;
+#ifdef CONFIG_SUNXI_GEN_SUN6I
+		spi_data_transfer(tmpbuf, 4 + chunk_len,
+				  SUN6I_SPI0_TCR,
+				  SUN6I_TCR_XCH,
+				  SUN6I_SPI0_FIFO_STA,
+				  SUN6I_SPI0_TXD,
+				  SUN6I_SPI0_RXD,
+				  SUN6I_SPI0_MBC,
+				  SUN6I_SPI0_MTC,
+				  SUN6I_SPI0_BCC);
+#else
+		spi_data_transfer(tmpbuf, 4 + chunk_len,
+				  SUN4I_SPI0_CTL,
+				  SUN4I_CTL_XCH,
+				  SUN4I_SPI0_FIFO_STA,
+				  SUN4I_SPI0_TX,
+				  SUN4I_SPI0_RX,
+				  SUN4I_SPI0_BC,
+				  SUN4I_SPI0_TC,
+				  0);
+#endif
+
+		memcpy(buf8, tmpbuf + 4, chunk_len);
+		len  -= chunk_len;
+		buf8 += chunk_len;
+		addr += chunk_len;
+	}
+}
+
+int spl_board_load_image_spi(void)
+{
+	int err = 0;
+	struct image_header *header;
+	header = (struct image_header *)(CONFIG_SYS_TEXT_BASE);
+
+	board_spi_init();
+
+	spi_read_data((void *)header, 0x8000, 0x40);
+	err = spl_parse_image_header(header);
+	if (err)
+		return err;
+
+	spi_read_data((void *)spl_image.load_addr, 0x8000, spl_image.size);
+	return 0;
+}
+
 int spl_board_load_image(void)
 {
+	if (readb(SPL_ADDR + 0x28) == 3) /* SPI boot */
+		return spl_board_load_image_spi();
+
 	debug("Returning to FEL sp=%x, lr=%x\n", fel_stash.sp, fel_stash.lr);
 	return_to_fel(fel_stash.sp, fel_stash.lr);
 
 	return 0;
 }
+#endif
 
 void s_init(void)
 {
@@ -223,6 +396,9 @@ u32 spl_boot_device(void)
 	if (!is_boot0_magic(SPL_ADDR + 4)) /* eGON.BT0 */
 		return BOOT_DEVICE_BOARD;
 
+	if (readb(SPL_ADDR + 0x28) == 3) /* SPI boot */
+		return BOOT_DEVICE_BOARD;
+
 	/* The BROM will try to boot from mmc0 first, so try that first. */
 #ifdef CONFIG_MMC
 	mmc_initialize(gd->bd);
diff --git a/board/sunxi/board.c b/board/sunxi/board.c
index d09cf6d..c61258e 100644
--- a/board/sunxi/board.c
+++ b/board/sunxi/board.c
@@ -378,6 +378,104 @@ int board_mmc_init(bd_t *bis)
 }
 #endif
 
+#if defined(CONFIG_SPL_BUILD)
+
+#define AHB_RESET_SPI0_SHIFT        (20)
+#define AHB_GATE_OFFSET_SPI0        (20)
+
+#define SUNXI_GPC_SPI0              (3)
+#define SUN50I_GPC_SPI0             (4)
+
+#define SUN4I_CTL_ENABLE            (1 << 0)
+#define SUN4I_CTL_MASTER            (1 << 1)
+#define SUN4I_CTL_TF_RST            (1 << 8)
+#define SUN4I_CTL_RF_RST            (1 << 9)
+#define SUN4I_CTL_XCH               (1 << 10)
+
+#define SUN6I_CTL_MASTER            (1 << 1)
+#define SUN6I_CTL_ENABLE            (1 << 0)
+
+#define SUN6I_TCR_XCH               (1 << 31)
+
+#define SUN4I_SPI0_CCTL             (void *)(0x01C05000 + 0x1C)
+#define SUN4I_SPI0_CTL              (void *)(0x01C05000 + 0x08)
+#define SUN4I_SPI0_RX               (void *)(0x01C05000 + 0x00)
+#define SUN4I_SPI0_TX               (void *)(0x01C05000 + 0x04)
+#define SUN4I_SPI0_FIFO_STA         (void *)(0x01C05000 + 0x28)
+#define SUN4I_SPI0_BC               (void *)(0x01C05000 + 0x20)
+#define SUN4I_SPI0_TC               (void *)(0x01C05000 + 0x24)
+
+#define SUN6I_SPI0_CCTL             (void *)(0x01C68000 + 0x24)
+#define SUN6I_SPI0_GCR              (void *)(0x01C68000 + 0x04)
+#define SUN6I_SPI0_TCR              (void *)(0x01C68000 + 0x08)
+#define SUN6I_SPI0_FIFO_STA         (void *)(0x01C68000 + 0x1C)
+#define SUN6I_SPI0_MBC              (void *)(0x01C68000 + 0x30)
+#define SUN6I_SPI0_MTC              (void *)(0x01C68000 + 0x34)
+#define SUN6I_SPI0_BCC              (void *)(0x01C68000 + 0x38)
+#define SUN6I_SPI0_TXD              (void *)(0x01C68000 + 0x200)
+#define SUN6I_SPI0_RXD              (void *)(0x01C68000 + 0x300)
+
+#define SPI0_CLK_DIV_BY_2           (0x1000)
+#define SPI0_CLK_DIV_BY_4           (0x1001)
+
+static void spi_pinmux_setup(void)
+{
+	unsigned int pin;
+	unsigned int spi_function = SUNXI_GPC_SPI0;
+
+	if (IS_ENABLED(CONFIG_MACH_SUN50I))
+		spi_function = SUN50I_GPC_SPI0;
+
+	for (pin = SUNXI_GPC(0); pin <= SUNXI_GPC(2); pin++)
+		sunxi_gpio_set_cfgpin(pin, spi_function);
+
+	if (IS_ENABLED(CONFIG_MACH_SUN4I) || IS_ENABLED(CONFIG_MACH_SUN7I)) {
+		sunxi_gpio_set_cfgpin(SUNXI_GPC(23), spi_function);
+	} else {
+		sunxi_gpio_set_cfgpin(SUNXI_GPC(3), spi_function);
+	}
+}
+
+static void spi_clock_setup(void)
+{
+	unsigned int reg_val;
+	struct sunxi_ccm_reg *const ccm =
+		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+
+#ifdef CONFIG_SUNXI_GEN_SUN6I
+	setbits_le32(&ccm->ahb_reset0_cfg, (1 << AHB_RESET_SPI0_SHIFT));
+#endif
+	setbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_SPI0));
+
+	if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I))
+		writel(SPI0_CLK_DIV_BY_4, SUN6I_SPI0_CCTL);
+	else
+		writel(SPI0_CLK_DIV_BY_4, SUN4I_SPI0_CCTL);
+
+	writel((1 << 31), &ccm->spi0_clk_cfg);
+
+	if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I)) {
+		/* Enable SPI in the master mode and do a soft reset */
+		reg_val = readl(SUN6I_SPI0_GCR);
+		reg_val |= (1 << 31) | 3;
+		writel(reg_val, SUN6I_SPI0_GCR);
+		/* Wait for completion */
+		while (readl(SUN6I_SPI0_GCR) & (1 << 31)) {}
+	} else {
+		reg_val = readl(SUN4I_SPI0_CTL);
+		reg_val |= SUN4I_CTL_MASTER;
+		reg_val |= SUN4I_CTL_ENABLE | SUN4I_CTL_TF_RST | SUN4I_CTL_RF_RST;
+		writel(reg_val, SUN4I_SPI0_CTL);
+	}
+}
+
+void board_spi_init(void)
+{
+	spi_pinmux_setup();
+	spi_clock_setup();
+}
+#endif
+
 void i2c_init_board(void)
 {
 #ifdef CONFIG_I2C0_ENABLE
-- 
2.7.3



More information about the U-Boot mailing list