[U-Boot] [PATCH v3 8/8] sf: add driver for Atmel QSPI controller

Jagan Teki jagannadh.teki at gmail.com
Wed Aug 30 13:58:19 UTC 2017


On Tue, Jul 25, 2017 at 12:31 PM, Wenyou Yang <wenyou.yang at microchip.com> wrote:
> From: Cyrille Pitchen <cyrille.pitchen at atmel.com>
>
> This patch adds support to the Atmel Quad SPI controller.

So, this is SPI flash driver not Generic SPI - the problem with these
were resides at drivers/spi is entire SPI layer becomes SPI-flash
oriented which is absolutely a wrong indication where SPI layer
getting effected more with flash operation.

>
> Signed-off-by: Cyrille Pitchen <cyrille.pitchen at atmel.com>
> Signed-off-by: Wenyou Yang <wenyou.yang at microchip.com>
> ---
>
> Changes in v3: None
> Changes in v2:
>  - Rebase on the latest u-boot/master(2710d54f5).
>
>  drivers/spi/Kconfig      |   7 +
>  drivers/spi/Makefile     |   1 +
>  drivers/spi/atmel_qspi.c | 404 +++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/spi/atmel_qspi.h | 169 ++++++++++++++++++++
>  4 files changed, 581 insertions(+)
>  create mode 100644 drivers/spi/atmel_qspi.c
>  create mode 100644 drivers/spi/atmel_qspi.h
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 8a8e8e480f..b3e8cb941e 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -32,6 +32,13 @@ config ATH79_SPI
>           uses driver model and requires a device tree binding to operate.
>           please refer to doc/device-tree-bindings/spi/spi-ath79.txt.
>
> +config ATMEL_QSPI
> +       bool "Atmel QSPI driver"
> +       depends on ARCH_AT91
> +       help
> +         Enable the Ateml Quad-SPI (QSPI) driver. This driver can only be
> +         used to access SPI NOR flashes.
> +
>  config ATMEL_SPI
>         bool "Atmel SPI driver"
>         depends on ARCH_AT91
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 9f8b86de76..358db2b065 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -18,6 +18,7 @@ endif
>  obj-$(CONFIG_ALTERA_SPI) += altera_spi.o
>  obj-$(CONFIG_ATH79_SPI) += ath79_spi.o
>  obj-$(CONFIG_ATMEL_DATAFLASH_SPI) += atmel_dataflash_spi.o
> +obj-$(CONFIG_ATMEL_QSPI) += atmel_qspi.o
>  obj-$(CONFIG_ATMEL_SPI) += atmel_spi.o
>  obj-$(CONFIG_CADENCE_QSPI) += cadence_qspi.o cadence_qspi_apb.o
>  obj-$(CONFIG_CF_SPI) += cf_spi.o
> diff --git a/drivers/spi/atmel_qspi.c b/drivers/spi/atmel_qspi.c
> new file mode 100644
> index 0000000000..0af7a4dc0a
> --- /dev/null
> +++ b/drivers/spi/atmel_qspi.c
> @@ -0,0 +1,404 @@
> +/*
> + * Copyright (C) 2017 Atmel Corporation
> + *
> + * SPDX-License-Identifier:    GPL-2.0+
> + */
> +#include <common.h>
> +#include <clk.h>
> +#include <dm.h>
> +#include <fdtdec.h>
> +#include <errno.h>
> +#include <spi.h>
> +#include <asm/io.h>
> +#include <mach/clk.h>
> +#include "atmel_qspi.h"
> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +static void atmel_qspi_memcpy_fromio(void *dst, unsigned long src, size_t len)
> +{
> +       u8 *d = (u8 *)dst;
> +
> +       while (len--) {
> +               *d++ = readb(src);
> +               src++;
> +       }
> +}
> +
> +static void atmel_qspi_memcpy_toio(unsigned long dst, const void *src,
> +                                  size_t len)
> +{
> +       const u8 *s = (const u8 *)src;
> +
> +       while (len--) {
> +               writeb(*s, dst);
> +               dst++;
> +               s++;
> +       }
> +}
> +
> +static int atmel_qspi_set_ifr_tfrtype(u8 flags, u32 *ifr)
> +{
> +       u32 ifr_tfrtype;
> +
> +       switch (flags & SPI_FCMD_TYPE) {
> +       case SPI_FCMD_READ:
> +               ifr_tfrtype = QSPI_IFR_TFRTYPE_READ_MEMORY;
> +               break;
> +
> +       case SPI_FCMD_WRITE:
> +               ifr_tfrtype = QSPI_IFR_TFRTYPE_WRITE_MEMORY;
> +               break;
> +
> +       case SPI_FCMD_ERASE:
> +       case SPI_FCMD_WRITE_REG:
> +               ifr_tfrtype = QSPI_IFR_TFRTYPE_WRITE;
> +               break;
> +
> +       case SPI_FCMD_READ_REG:
> +               ifr_tfrtype = QSPI_IFR_TFRTYPE_READ;
> +               break;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       *ifr = (*ifr & ~QSPI_IFR_TFRTYPE) | ifr_tfrtype;
> +       return 0;
> +}
> +
> +static int atmel_qpsi_set_ifr_width(enum spi_flash_protocol proto, u32 *ifr)
> +{
> +       u32 ifr_width;
> +
> +       switch (proto) {
> +       case SPI_FPROTO_1_1_1:
> +               ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
> +               break;
> +
> +       case SPI_FPROTO_1_1_2:
> +               ifr_width = QSPI_IFR_WIDTH_DUAL_OUTPUT;
> +               break;
> +
> +       case SPI_FPROTO_1_2_2:
> +               ifr_width = QSPI_IFR_WIDTH_DUAL_IO;
> +               break;
> +
> +       case SPI_FPROTO_2_2_2:
> +               ifr_width = QSPI_IFR_WIDTH_DUAL_CMD;
> +               break;
> +
> +       case SPI_FPROTO_1_1_4:
> +               ifr_width = QSPI_IFR_WIDTH_QUAD_OUTPUT;
> +               break;
> +
> +       case SPI_FPROTO_1_4_4:
> +               ifr_width = QSPI_IFR_WIDTH_QUAD_IO;
> +               break;
> +
> +       case SPI_FPROTO_4_4_4:
> +               ifr_width = QSPI_IFR_WIDTH_QUAD_CMD;
> +               break;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       *ifr = (*ifr & ~QSPI_IFR_WIDTH) | ifr_width;
> +       return 0;
> +}
> +
> +static int atmel_qspi_xfer(struct udevice *dev, unsigned int bitlen,
> +                          const void *dout, void *din, unsigned long flags)
> +{
> +       /* This controller can only be used with SPI NOR flashes. */
> +       return -EINVAL;
> +}
> +
> +static int atmel_qspi_set_speed(struct udevice *bus, uint hz)
> +{
> +       struct atmel_qspi_priv *aq = dev_get_priv(bus);
> +       u32 scr, scbr, mask, new_value;
> +
> +       /* Compute the QSPI baudrate */
> +       scbr = DIV_ROUND_UP(aq->bus_clk_rate, hz);
> +       if (scbr > 0)
> +               scbr--;
> +
> +       new_value = QSPI_SCR_SCBR_(scbr);
> +       mask = QSPI_SCR_SCBR;
> +
> +       scr = qspi_readl(aq, QSPI_SCR);
> +       if ((scr & mask) == new_value)
> +               return 0;
> +
> +       scr = (scr & ~mask) | new_value;
> +       qspi_writel(aq, QSPI_SCR, scr);
> +
> +       return 0;
> +}
> +
> +static int atmel_qspi_set_mode(struct udevice *bus, uint mode)
> +{
> +       struct atmel_qspi_priv *aq = dev_get_priv(bus);
> +       u32 scr, mask, new_value;
> +
> +       new_value = (QSPI_SCR_CPOL_((mode & SPI_CPOL) != 0) |
> +                    QSPI_SCR_CPHA_((mode & SPI_CPHA) != 0));
> +       mask = (QSPI_SCR_CPOL | QSPI_SCR_CPHA);
> +
> +       scr = qspi_readl(aq, QSPI_SCR);
> +       if ((scr & mask) == new_value)
> +               return 0;
> +
> +       scr = (scr & ~mask) | new_value;
> +       qspi_writel(aq, QSPI_SCR, scr);
> +
> +       return 0;
> +}
> +
> +static bool
> +atmel_qspi_is_flash_command_supported(struct udevice *dev,
> +                                     const struct spi_flash_command *cmd)
> +{
> +       return true;
> +}
> +
> +static int atmel_qspi_exec_flash_command(struct udevice *dev,
> +                                        const struct spi_flash_command *cmd)
> +{
> +       struct udevice *bus = dev_get_parent(dev);
> +       struct atmel_qspi_priv *aq = dev_get_priv(bus);
> +       unsigned int iar, icr, ifr;
> +       unsigned int offset;
> +       unsigned int imr, sr;
> +       unsigned long memaddr;
> +       int err;
> +
> +       iar = 0;
> +       icr = 0;
> +       ifr = 0;
> +
> +       err = atmel_qspi_set_ifr_tfrtype(cmd->flags, &ifr);
> +       if (err)
> +               return err;
> +
> +       err = atmel_qpsi_set_ifr_width(cmd->proto, &ifr);
> +       if (err)
> +               return err;
> +
> +       /* Compute instruction parameters */
> +       icr |= QSPI_ICR_INST_(cmd->inst);
> +       ifr |= QSPI_IFR_INSTEN;
> +
> +       /* Compute address parameters. */
> +       switch (cmd->addr_len) {
> +       case 4:
> +               ifr |= QSPI_IFR_ADDRL_32_BIT;
> +               /*break;*/ /* fall through the 24bit (3 byte) address case */
> +       case 3:
> +               iar = cmd->data_len ? 0 : cmd->addr;
> +               ifr |= QSPI_IFR_ADDREN;
> +               offset = cmd->addr;
> +               break;
> +       case 0:
> +               offset = 0;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       /* Compute option parameters. */
> +       if (cmd->num_mode_cycles) {
> +               unsigned int mode_cycle_bits, mode_bits;
> +
> +               icr |= QSPI_ICR_OPT_(cmd->mode);
> +               ifr |= QSPI_IFR_OPTEN;
> +
> +               switch (ifr & QSPI_IFR_WIDTH) {
> +               case QSPI_IFR_WIDTH_SINGLE_BIT_SPI:
> +               case QSPI_IFR_WIDTH_DUAL_OUTPUT:
> +               case QSPI_IFR_WIDTH_QUAD_OUTPUT:
> +                       mode_cycle_bits = 1;
> +                       break;
> +               case QSPI_IFR_WIDTH_DUAL_IO:
> +               case QSPI_IFR_WIDTH_DUAL_CMD:
> +                       mode_cycle_bits = 2;
> +                       break;
> +               case QSPI_IFR_WIDTH_QUAD_IO:
> +               case QSPI_IFR_WIDTH_QUAD_CMD:
> +                       mode_cycle_bits = 4;
> +                       break;
> +               default:
> +                       return -EINVAL;
> +               }
> +
> +               mode_bits = cmd->num_mode_cycles * mode_cycle_bits;
> +               switch (mode_bits) {
> +               case 1:
> +                       ifr |= QSPI_IFR_OPTL_1BIT;
> +                       break;
> +
> +               case 2:
> +                       ifr |= QSPI_IFR_OPTL_2BIT;
> +                       break;
> +
> +               case 4:
> +                       ifr |= QSPI_IFR_OPTL_4BIT;
> +                       break;
> +
> +               case 8:
> +                       ifr |= QSPI_IFR_OPTL_8BIT;
> +                       break;
> +
> +               default:
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       /* Set the number of dummy cycles. */
> +       if (cmd->num_wait_states)
> +               ifr |= QSPI_IFR_NBDUM_(cmd->num_wait_states);
> +
> +       /* Set data enable. */
> +       if (cmd->data_len)
> +               ifr |= QSPI_IFR_DATAEN;
> +
> +       /* Clear pending interrupts. */
> +       (void)qspi_readl(aq, QSPI_SR);
> +
> +       /* Set QSPI Instruction Frame registers. */
> +       qspi_writel(aq, QSPI_IAR, iar);
> +       qspi_writel(aq, QSPI_ICR, icr);
> +       qspi_writel(aq, QSPI_IFR, ifr);
> +
> +       /* Skip to the final steps if there is no data. */
> +       if (!cmd->data_len)
> +               goto no_data;
> +
> +       /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses. */
> +       (void)qspi_readl(aq, QSPI_IFR);
> +
> +       /* Stop here for Continuous Read. */
> +       memaddr = (unsigned long)(aq->membase + offset);
> +       if (cmd->tx_data)
> +               /* Write data. */
> +               atmel_qspi_memcpy_toio(memaddr, cmd->tx_data, cmd->data_len);
> +       else if (cmd->rx_data)
> +               /* Read data. */
> +               atmel_qspi_memcpy_fromio(cmd->rx_data, memaddr, cmd->data_len);
> +
> +       /* Release the chip-select. */
> +       qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER);
> +
> +no_data:
> +       /* Poll INSTruction End and Chip Select Rise flags. */
> +       imr = QSPI_SR_INSTRE | QSPI_SR_CSR;
> +       sr = 0;
> +       while (sr != (QSPI_SR_INSTRE | QSPI_SR_CSR))
> +               sr |= qspi_readl(aq, QSPI_SR) & imr;
> +
> +       return 0;
> +}

>From v3 1/8 comments, this can be local function where we each IO
opeartions can initiate this run command.

> +
> +
> +static const struct dm_spi_ops atmel_qspi_ops = {
> +       .xfer                           = atmel_qspi_xfer,
> +       .set_speed                      = atmel_qspi_set_speed,
> +       .set_mode                       = atmel_qspi_set_mode,
> +       .is_flash_command_supported     = atmel_qspi_is_flash_command_supported,
> +       .exec_flash_command             = atmel_qspi_exec_flash_command,
> +};
> +
> +static int atmel_qspi_enable_clk(struct udevice *bus)
> +{
> +       struct atmel_qspi_priv *aq = dev_get_priv(bus);
> +       struct clk clk;
> +       ulong clk_rate;
> +       int ret;
> +
> +       ret = clk_get_by_index(bus, 0, &clk);
> +       if (ret)
> +               return -EINVAL;
> +
> +       ret = clk_enable(&clk);
> +       if (ret)
> +               goto free_clock;
> +
> +       clk_rate = clk_get_rate(&clk);
> +       if (!clk_rate) {
> +               ret = -EINVAL;
> +               goto free_clock;
> +       }
> +
> +       aq->bus_clk_rate = clk_rate;
> +
> +free_clock:
> +       clk_free(&clk);
> +
> +       return ret;
> +}
> +
> +static int atmel_qspi_probe(struct udevice *bus)
> +{
> +       const struct atmel_qspi_platdata *plat = dev_get_platdata(bus);
> +       struct atmel_qspi_priv *aq = dev_get_priv(bus);
> +       u32 mr;
> +       int ret;
> +
> +       ret = atmel_qspi_enable_clk(bus);
> +       if (ret)
> +               return ret;
> +
> +       aq->regbase = plat->regbase;
> +       aq->membase = plat->membase;
> +
> +       /* Reset the QSPI controler */
> +       qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST);
> +
> +       /* Set the QSPI controller in Serial Memory Mode */
> +       mr = (QSPI_MR_NBBITS_8_BIT |
> +             QSPI_MR_SMM_MEMORY |
> +             QSPI_MR_CSMODE_LASTXFER);
> +       qspi_writel(aq, QSPI_MR, mr);
> +
> +       /* Enable the QSPI controller */
> +       qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIEN);
> +
> +       return 0;
> +}
> +
> +static int atmel_qspi_ofdata_to_platdata(struct udevice *bus)
> +{
> +       struct atmel_qspi_platdata *plat = dev_get_platdata(bus);
> +       const void *blob = gd->fdt_blob;
> +       int node = dev_of_offset(bus);
> +       u32 data[4];
> +       int ret;
> +
> +       ret = fdtdec_get_int_array(blob, node, "reg", data, ARRAY_SIZE(data));
> +       if (ret) {
> +               printf("Error: Can't get base addresses (ret=%d)!\n", ret);
> +               return -ENODEV;
> +       }
> +       plat->regbase = (void *)data[0];
> +       plat->membase = (void *)data[2];
> +
> +       return 0;
> +}
> +
> +static const struct udevice_id atmel_qspi_ids[] = {
> +       { .compatible = "atmel,sama5d2-qspi" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(atmel_qspi) = {
> +       .name           = "atmel_qspi",
> +       .id             = UCLASS_SPI,

Summary, better to write UCLASS_SPI_FLASH driver.

thanks!
-- 
Jagan Teki
Free Software Engineer | www.openedev.com
U-Boot, Linux | Upstream Maintainer
Hyderabad, India.


More information about the U-Boot mailing list