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

Hans de Goede hdegoede at redhat.com
Fri Jun 10 21:28:43 CEST 2016


Hi,

On 07-06-16 13:28, Siarhei Siamashka wrote:
> 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 existing SPI frameworks are not used in order to reduce the
> SPL code size. Right now the SPL size grows by ~370 bytes when
> CONFIG_SPL_SPI_SUNXI option is enabled.
>
> While there are no popular Allwinner devices with SPI flash at
> the moment, testing can be done using a SPI flash module (it
> can be bought for ~2$ on ebay) and jumper wires with the boards,
> which expose relevant pins on the expansion header. The SPI flash
> chips themselves are very cheap (some prices are even listed as
> low as 4 cents) and should not cost much if somebody decides to
> design a development board with an SPI flash chip soldered on
> the PCB.
>
> Another nice feature of the SPI flash is that it can be safely
> accessed in a device-independent way (since we know that the
> boot ROM is already probing these pins during the boot time).
> And if, for example, Olimex boards opted to use SPI flash instead
> of EEPROM, then they would have been able to have U-Boot installed
> in the SPI flash now and boot the rest of the system from the SATA
> hard drive. Hopefully we may see new interesting Allwinner based
> development boards in the future, now that the software support
> for the SPI flash is in a better shape :-)
>
> Testing can be done by enabling the CONFIG_SPL_SPI_SUNXI option
> in a board defconfig, then building U-Boot and finally flashing
> the resulting u-boot-sunxi-with-spl.bin binary over USB OTG with
> a help of the sunxi-fel tool:
>
>    sunxi-fel spiflash-write 0 u-boot-sunxi-with-spl.bin
>
> The device needs to be switched into FEL (USB recovery) mode first.
> The most suitable boards for testing are Orange Pi PC and Pine64.
> Because these boards are cheap, have no built-in NAND/eMMC and
> expose SPI0 pins on the Raspberry Pi compatible expansion header.
> The A13-OLinuXino-Micro board also can be used.
>
> Signed-off-by: Siarhei Siamashka <siarhei.siamashka at gmail.com>

Thanks, I've added this to u-boot-sunxi/next for merging into
v2016.09 as soon as the merge window for that opens.

Regards,

Hans



> ---
>
> Changes in v2:
>  - Add Kconfig option (CONFIG_SPL_SPI_SUNXI) and move the SPI flash
>    support code into a separate source file
>  - Use CONFIG_SYS_SPI_U_BOOT_OFFS instead of the hardcoded constant
>  - Deinitialize the SPI controller and undo pin muxing after the job
>    is done
>  - Size reduction of the SPI transfer function
>  - Add delay after each SPI transfer to ensure that the chip select
>    deassert timing requirements (tSHSL) are always satisfied
>  - More comments in the code
>
>
>  arch/arm/include/asm/arch-sunxi/gpio.h |   3 +
>  arch/arm/mach-sunxi/board.c            |   5 +
>  common/spl/spl.c                       |   4 +-
>  drivers/mtd/spi/Kconfig                |  12 ++
>  drivers/mtd/spi/Makefile               |   1 +
>  drivers/mtd/spi/sunxi_spi_spl.c        | 283 +++++++++++++++++++++++++++++++++
>  include/configs/sunxi-common.h         |   5 +
>  7 files changed, 311 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/mtd/spi/sunxi_spi_spl.c
>
> diff --git a/arch/arm/include/asm/arch-sunxi/gpio.h b/arch/arm/include/asm/arch-sunxi/gpio.h
> index 1ace548..bff7d14 100644
> --- a/arch/arm/include/asm/arch-sunxi/gpio.h
> +++ b/arch/arm/include/asm/arch-sunxi/gpio.h
> @@ -141,6 +141,7 @@ enum sunxi_gpio_number {
>  /* GPIO pin function config */
>  #define SUNXI_GPIO_INPUT	0
>  #define SUNXI_GPIO_OUTPUT	1
> +#define SUNXI_GPIO_DISABLE	7
>
>  #define SUNXI_GPA_EMAC		2
>  #define SUN6I_GPA_GMAC		2
> @@ -162,8 +163,10 @@ enum sunxi_gpio_number {
>  #define SUN50I_GPB_UART0	4
>
>  #define SUNXI_GPC_NAND		2
> +#define SUNXI_GPC_SPI0		3
>  #define SUNXI_GPC_SDC2		3
>  #define SUN6I_GPC_SDC3		4
> +#define SUN50I_GPC_SPI0		4
>
>  #define SUN8I_GPD_SDC1		3
>  #define SUNXI_GPD_LCD0		2
> diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c
> index bd15b9b..025df4d 100644
> --- a/arch/arm/mach-sunxi/board.c
> +++ b/arch/arm/mach-sunxi/board.c
> @@ -223,6 +223,11 @@ u32 spl_boot_device(void)
>  	if (!is_boot0_magic(SPL_ADDR + 4)) /* eGON.BT0 */
>  		return BOOT_DEVICE_BOARD;
>
> +#ifdef CONFIG_SPL_SPI_SUNXI
> +	if (readb(SPL_ADDR + 0x28) == SUNXI_BOOTED_FROM_SPI)
> +		return BOOT_DEVICE_SPI;
> +#endif
> +
>  	/* The BROM will try to boot from mmc0 first, so try that first. */
>  #ifdef CONFIG_MMC
>  	mmc_initialize(gd->bd);
> diff --git a/common/spl/spl.c b/common/spl/spl.c
> index c8dfc14..d49ce2b 100644
> --- a/common/spl/spl.c
> +++ b/common/spl/spl.c
> @@ -265,7 +265,7 @@ struct boot_device_name boot_name_table[] = {
>  #ifdef CONFIG_SPL_YMODEM_SUPPORT
>  	{ BOOT_DEVICE_UART, "UART" },
>  #endif
> -#ifdef CONFIG_SPL_SPI_SUPPORT
> +#if defined(CONFIG_SPL_SPI_SUPPORT) || defined(CONFIG_SPL_SPI_FLASH_SUPPORT)
>  	{ BOOT_DEVICE_SPI, "SPI" },
>  #endif
>  #ifdef CONFIG_SPL_ETH_SUPPORT
> @@ -341,7 +341,7 @@ static int spl_load_image(u32 boot_device)
>  	case BOOT_DEVICE_UART:
>  		return spl_ymodem_load_image();
>  #endif
> -#ifdef CONFIG_SPL_SPI_SUPPORT
> +#if defined(CONFIG_SPL_SPI_SUPPORT) || defined(CONFIG_SPL_SPI_FLASH_SUPPORT)
>  	case BOOT_DEVICE_SPI:
>  		return spl_spi_load_image();
>  #endif
> diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig
> index 3f7433c..1f23c8e 100644
> --- a/drivers/mtd/spi/Kconfig
> +++ b/drivers/mtd/spi/Kconfig
> @@ -128,4 +128,16 @@ config SPI_FLASH_MTD
>
>  	  If unsure, say N
>
> +if SPL
> +
> +config SPL_SPI_SUNXI
> +	bool "Support for SPI Flash on Allwinner SoCs in SPL"
> +	depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUN8I_H3 || MACH_SUN50I
> +	---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
> +	not need any extra configuration.
> +
> +endif
> +
>  endmenu # menu "SPI Flash Support"
> diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile
> index c665836..6f47a66 100644
> --- a/drivers/mtd/spi/Makefile
> +++ b/drivers/mtd/spi/Makefile
> @@ -10,6 +10,7 @@ obj-$(CONFIG_DM_SPI_FLASH) += sf-uclass.o
>  ifdef CONFIG_SPL_BUILD
>  obj-$(CONFIG_SPL_SPI_LOAD)	+= spi_spl_load.o
>  obj-$(CONFIG_SPL_SPI_BOOT)	+= fsl_espi_spl.o
> +obj-$(CONFIG_SPL_SPI_SUNXI)	+= sunxi_spi_spl.o
>  endif
>
>  obj-$(CONFIG_SPI_FLASH) += sf_probe.o spi_flash.o sf_params.o sf.o
> diff --git a/drivers/mtd/spi/sunxi_spi_spl.c b/drivers/mtd/spi/sunxi_spi_spl.c
> new file mode 100644
> index 0000000..e3ded5b
> --- /dev/null
> +++ b/drivers/mtd/spi/sunxi_spi_spl.c
> @@ -0,0 +1,283 @@
> +/*
> + * Copyright (C) 2016 Siarhei Siamashka <siarhei.siamashka at gmail.com>
> + *
> + * SPDX-License-Identifier:	GPL-2.0+
> + */
> +
> +#include <common.h>
> +#include <spl.h>
> +#include <asm/gpio.h>
> +#include <asm/io.h>
> +
> +#ifdef CONFIG_SPL_OS_BOOT
> +#error CONFIG_SPL_OS_BOOT is not supported yet
> +#endif
> +
> +/*
> + * This is a very simple U-Boot image loading implementation, trying to
> + * replicate what the boot ROM is doing when loading the SPL. Because we
> + * know the exact pins where the SPI Flash is connected and also know
> + * that the Read Data Bytes (03h) command is supported, the hardware
> + * configuration is very simple and we don't need the extra flexibility
> + * of the SPI framework. Moreover, we rely on the default settings of
> + * the SPI controler hardware registers and only adjust what needs to
> + * be changed. This is good for the code size and this implementation
> + * adds less than 400 bytes to the SPL.
> + *
> + * There are two variants of the SPI controller in Allwinner SoCs:
> + * A10/A13/A20 (sun4i variant) and everything else (sun6i variant).
> + * Both of them are supported.
> + *
> + * The pin mixing part is SoC specific and only A10/A13/A20/H3/A64 are
> + * supported at the moment.
> + */
> +
> +/*****************************************************************************/
> +/* SUN4I variant of the SPI controller                                       */
> +/*****************************************************************************/
> +
> +#define SUN4I_SPI0_CCTL             (0x01C05000 + 0x1C)
> +#define SUN4I_SPI0_CTL              (0x01C05000 + 0x08)
> +#define SUN4I_SPI0_RX               (0x01C05000 + 0x00)
> +#define SUN4I_SPI0_TX               (0x01C05000 + 0x04)
> +#define SUN4I_SPI0_FIFO_STA         (0x01C05000 + 0x28)
> +#define SUN4I_SPI0_BC               (0x01C05000 + 0x20)
> +#define SUN4I_SPI0_TC               (0x01C05000 + 0x24)
> +
> +#define SUN4I_CTL_ENABLE            BIT(0)
> +#define SUN4I_CTL_MASTER            BIT(1)
> +#define SUN4I_CTL_TF_RST            BIT(8)
> +#define SUN4I_CTL_RF_RST            BIT(9)
> +#define SUN4I_CTL_XCH               BIT(10)
> +
> +/*****************************************************************************/
> +/* SUN6I variant of the SPI controller                                       */
> +/*****************************************************************************/
> +
> +#define SUN6I_SPI0_CCTL             (0x01C68000 + 0x24)
> +#define SUN6I_SPI0_GCR              (0x01C68000 + 0x04)
> +#define SUN6I_SPI0_TCR              (0x01C68000 + 0x08)
> +#define SUN6I_SPI0_FIFO_STA         (0x01C68000 + 0x1C)
> +#define SUN6I_SPI0_MBC              (0x01C68000 + 0x30)
> +#define SUN6I_SPI0_MTC              (0x01C68000 + 0x34)
> +#define SUN6I_SPI0_BCC              (0x01C68000 + 0x38)
> +#define SUN6I_SPI0_TXD              (0x01C68000 + 0x200)
> +#define SUN6I_SPI0_RXD              (0x01C68000 + 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_2           0x1000
> +#define SPI0_CLK_DIV_BY_4           0x1001
> +
> +/*****************************************************************************/
> +
> +/*
> + * Allwinner A10/A20 SoCs were using pins PC0,PC1,PC2,PC23 for booting
> + * from SPI Flash, everything else is using pins PC0,PC1,PC2,PC3.
> + */
> +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);
> +
> +	if (IS_ENABLED(CONFIG_MACH_SUN4I) || IS_ENABLED(CONFIG_MACH_SUN7I))
> +		sunxi_gpio_set_cfgpin(SUNXI_GPC(23), pin_function);
> +	else
> +		sunxi_gpio_set_cfgpin(SUNXI_GPC(3), pin_function);
> +}
> +
> +/*
> + * Setup 6 MHz from OSC24M (because the BROM is doing the same).
> + */
> +static void spi0_enable_clock(void)
> +{
> +	/* Deassert SPI0 reset on SUN6I */
> +	if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I))
> +		setbits_le32(SUN6I_BUS_SOFT_RST_REG0,
> +			     (1 << AHB_RESET_SPI0_SHIFT));
> +
> +	/* Open the SPI0 gate */
> +	setbits_le32(CCM_AHB_GATING0, (1 << AHB_GATE_OFFSET_SPI0));
> +
> +	/* Divide by 4 */
> +	writel(SPI0_CLK_DIV_BY_4, IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I) ?
> +				  SUN6I_SPI0_CCTL : SUN4I_SPI0_CCTL);
> +	/* 24MHz from OSC24M */
> +	writel((1 << 31), CCM_SPI0_CLK);
> +
> +	if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I)) {
> +		/* Enable SPI in the master mode and do a soft reset */
> +		setbits_le32(SUN6I_SPI0_GCR, SUN6I_CTL_MASTER |
> +					     SUN6I_CTL_ENABLE |
> +					     SUN6I_CTL_SRST);
> +		/* Wait for completion */
> +		while (readl(SUN6I_SPI0_GCR) & SUN6I_CTL_SRST)
> +			;
> +	} else {
> +		/* Enable SPI in the master mode and reset FIFO */
> +		setbits_le32(SUN4I_SPI0_CTL, SUN4I_CTL_MASTER |
> +					     SUN4I_CTL_ENABLE |
> +					     SUN4I_CTL_TF_RST |
> +					     SUN4I_CTL_RF_RST);
> +	}
> +}
> +
> +static void spi0_disable_clock(void)
> +{
> +	/* Disable the SPI0 controller */
> +	if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I))
> +		clrbits_le32(SUN6I_SPI0_GCR, SUN6I_CTL_MASTER |
> +					     SUN6I_CTL_ENABLE);
> +	else
> +		clrbits_le32(SUN4I_SPI0_CTL, SUN4I_CTL_MASTER |
> +					     SUN4I_CTL_ENABLE);
> +
> +	/* Disable the SPI0 clock */
> +	writel(0, CCM_SPI0_CLK);
> +
> +	/* Close the SPI0 gate */
> +	clrbits_le32(CCM_AHB_GATING0, (1 << AHB_GATE_OFFSET_SPI0));
> +
> +	/* Assert SPI0 reset on SUN6I */
> +	if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I))
> +		clrbits_le32(SUN6I_BUS_SOFT_RST_REG0,
> +			     (1 << AHB_RESET_SPI0_SHIFT));
> +}
> +
> +static int spi0_init(void)
> +{
> +	unsigned int pin_function = SUNXI_GPC_SPI0;
> +	if (IS_ENABLED(CONFIG_MACH_SUN50I))
> +		pin_function = SUN50I_GPC_SPI0;
> +
> +	spi0_pinmux_setup(pin_function);
> +	spi0_enable_clock();
> +}
> +
> +static void spi0_deinit(void)
> +{
> +	/* New SoCs can disable pins, older could only set them as input */
> +	unsigned int pin_function = SUNXI_GPIO_INPUT;
> +	if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I))
> +		pin_function = SUNXI_GPIO_DISABLE;
> +
> +	spi0_disable_clock();
> +	spi0_pinmux_setup(pin_function);
> +}
> +
> +/*****************************************************************************/
> +
> +#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,
> +				 u32 spi_ctl_reg,
> +				 u32 spi_ctl_xch_bitmask,
> +				 u32 spi_fifo_reg,
> +				 u32 spi_tx_reg,
> +				 u32 spi_rx_reg,
> +				 u32 spi_bc_reg,
> +				 u32 spi_tc_reg,
> +				 u32 spi_bcc_reg)
> +{
> +	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 (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);
> +
> +	/* 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 */
> +	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 */
> +	udelay(1);
> +}
> +
> +static void spi0_read_data(void *buf, u32 addr, u32 len)
> +{
> +	u8 *buf8 = buf;
> +	u32 chunk_len;
> +
> +	while (len > 0) {
> +		chunk_len = len;
> +		if (chunk_len > SPI_READ_MAX_SIZE)
> +			chunk_len = SPI_READ_MAX_SIZE;
> +
> +		if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I)) {
> +			sunxi_spi0_read_data(buf8, addr, 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 {
> +			sunxi_spi0_read_data(buf8, addr, 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);
> +		}
> +
> +		len  -= chunk_len;
> +		buf8 += chunk_len;
> +		addr += chunk_len;
> +	}
> +}
> +
> +/*****************************************************************************/
> +
> +int spl_spi_load_image(void)
> +{
> +	int err;
> +	struct image_header *header;
> +	header = (struct image_header *)(CONFIG_SYS_TEXT_BASE);
> +
> +	spi0_init();
> +
> +	spi0_read_data((void *)header, CONFIG_SYS_SPI_U_BOOT_OFFS, 0x40);
> +	err = spl_parse_image_header(header);
> +	if (err)
> +		return err;
> +
> +	spi0_read_data((void *)spl_image.load_addr, CONFIG_SYS_SPI_U_BOOT_OFFS,
> +		       spl_image.size);
> +
> +	spi0_deinit();
> +	return 0;
> +}
> diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h
> index b33cfb8..7d244d9 100644
> --- a/include/configs/sunxi-common.h
> +++ b/include/configs/sunxi-common.h
> @@ -137,6 +137,11 @@
>  #define CONFIG_SPL_NAND_SUPPORT 1
>  #endif
>
> +#ifdef CONFIG_SPL_SPI_SUNXI
> +#define CONFIG_SPL_SPI_FLASH_SUPPORT	1
> +#define CONFIG_SYS_SPI_U_BOOT_OFFS	0x8000
> +#endif
> +
>  /* mmc config */
>  #ifdef CONFIG_MMC
>  #define CONFIG_GENERIC_MMC
>


More information about the U-Boot mailing list