[U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64

Jagan Teki jagannadh.teki at gmail.com
Fri Apr 7 09:54:13 UTC 2017


On Thu, Mar 2, 2017 at 2:59 AM, Philipp Tomsich
<philipp.tomsich at theobroma-systems.com> wrote:
> This adds a rewrite of the SPI driver we had in use for the A31-uQ7
> (sun6i), A80-Q7 (sun9i) and A64-uQ7 (sun50i) boards, which includes
> support for:
>  * cs-gpios (i.e. GPIOs as additional chip-selects)
>  * clocking, reset and pinctrl based on the device-model
>  * dual-IO data receive for controllers that support it (sun50i)
>
> The key difference to the earlier incarnation that we provided as part
> of our BSP is the removal of the legacy reset and clocking code and
> added resilience to configuration errors (i.e. timeouts for the inner
> loops) and converstion to the device-model. This was possible due to a
> non-device-model driver now being present for use with in the SPL.
>
> This has been verified against the A64-uQ7 with data rates up to
> 100MHz and dual-IO ("Fast Read Dual Output" opcode) from the on-board
> SPI-NOR flash.
>
> Signed-off-by: Philipp Tomsich <philipp.tomsich at theobroma-systems.com>
> ---
>  drivers/spi/Kconfig     |  14 ++
>  drivers/spi/Makefile    |   1 +
>  drivers/spi/sunxi_spi.c | 516 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 531 insertions(+)
>  create mode 100644 drivers/spi/sunxi_spi.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index f3f7dbe..64b6430 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -132,6 +132,20 @@ config STM32_QSPI
>           used to access the SPI NOR flash chips on platforms embedding
>           this ST IP core.
>
> +config SUNXI_SPI
> +       bool "Allwinner (sunxi) SPI driver"
> +       help
> +         Enable the SPI driver for Allwinner SoCs.
> +
> +         This driver can be used to access the SPI NOR flash on for
> +         communciation with SPI peripherals platforms embedding the
> +         Allwinner SoC.  This driver supports the device-model (only)
> +         and has support for GPIOs as additional chip-selects.
> +
> +         For recent platforms (e.g. sun50i), dual-IO receive mode is
> +         also supported, when configured for a SPI-NOR flash in the
> +         device tree.
> +
>  config TEGRA114_SPI
>         bool "nVidia Tegra114 SPI driver"
>         help
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index fa9a1d2..aab31b4 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o
>  obj-$(CONFIG_PIC32_SPI) += pic32_spi.o
>  obj-$(CONFIG_ROCKCHIP_SPI) += rk_spi.o
>  obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi.o
> +obj-$(CONFIG_SUNXI_SPI) += sunxi_spi.o

Is this the common spi driver for sunxi, I'm sure it's specific to 6i,
better name the same. If all yes, try to use the macro names in Linux
spi driver.

>  obj-$(CONFIG_SH_SPI) += sh_spi.o
>  obj-$(CONFIG_SH_QSPI) += sh_qspi.o
>  obj-$(CONFIG_STM32_QSPI) += stm32_qspi.o
> diff --git a/drivers/spi/sunxi_spi.c b/drivers/spi/sunxi_spi.c
> new file mode 100644
> index 0000000..f26becf
> --- /dev/null
> +++ b/drivers/spi/sunxi_spi.c
> @@ -0,0 +1,516 @@
> +/*
> + * SPI driver for Allwinner sunxi SoCs
> + *
> + * Copyright (C) 2015-2017 Theobroma Systems Design und Consulting GmbH
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <common.h>
> +#ifdef CONFIG_DM_GPIO
> +#include <asm/gpio.h>
> +#endif
> +#include <asm/io.h>
> +#include <clk.h>
> +#include <dm.h>
> +#include <errno.h>
> +#include <fdtdec.h>
> +#include <wait_bit.h>
> +#include <reset.h>
> +#include <spi.h>
> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +struct sunxi_spi_platdata {
> +       void *base;
> +       unsigned int max_hz;
> +
> +       struct reset_ctl reset_ctl;
> +       struct clk ahb_clk_gate;
> +       struct clk spi_clk;
> +
> +       /* We could do with a single delay counter, but it won't do harm
> +          to have two, as the same is the case for most other driver. */
> +       uint deactivate_delay_us;       /* Delay to wait after deactivate */
> +       uint activate_delay_us;         /* Delay to wait after activate */
> +
> +#if defined(CONFIG_DM_GPIO)
> +       int cs_gpios_num;
> +       struct gpio_desc *cs_gpios;
> +#endif
> +};
> +
> +struct sunxi_spi_driverdata {
> +       unsigned int  fifo_depth;
> +};
> +
> +struct sunxi_spi_privdata {
> +       ulong last_transaction_us;      /* Time of last transaction end */
> +       unsigned int hz_requested;      /* last requested bitrate */
> +       unsigned int hz_actual;         /* currently set bitrate */
> +};
> +
> +struct sunxi_spi_reg {
> +       u8      _rsvd[0x4];
> +       u32     GCR;   /* SPI Global Control register */
> +       u32     TCR;   /* SPI Transfer Control register */
> +       u8      _rsvd1[0x4];
> +       u32     IER;   /* SPI Interrupt Control register */
> +       u32     ISR;   /* SPI Interrupt Status register */
> +       u32     FCR;   /* SPI FIFO Control register */
> +       u32     FSR;   /* SPI FIFO Status register */
> +       u32     WCR;   /* SPI Wait Clock Counter register */
> +       u32     CCR;   /* SPI Clock Rate Control register */
> +       u8      _rsvd2[0x8];
> +       u32     MBC;   /* SPI Burst Counter register */
> +       u32     MTC;   /* SPI Transmit Counter register */
> +       u32     BCC;   /* SPI Burst Control register */
> +       u8      _rsvd3[0x4c];
> +       u32     NDMA_MODE_CTL;
> +       u8      _rsvd4[0x174];
> +       u32     TXD;   /* SPI TX Data register */
> +       u8      _rsvd5[0xfc];
> +       u32     RXD;   /* SPI RX Data register */
> +};
> +
> +
> +#define GCR_MASTER      BIT(1)
> +#define GCR_EN          BIT(0)
> +
> +#define TCR_XCH          BIT(31)
> +#define TCR_SDC          BIT(11)
> +#define TCR_DHB          BIT(8)
> +#define TCR_SSSEL_SHIFT  (4)
> +#define TCR_SSSEL_MASK   (0x3 << TCR_SSSEL_SHIFT)
> +#define TCR_SSLEVEL      BIT(7)
> +#define TCR_SSOWNER      BIT(6)
> +#define TCR_CPOL         BIT(1)
> +#define TCR_CPHA         BIT(0)
> +
> +#define FCR_RX_FIFO_RST  BIT(31)
> +#define FCR_TX_FIFO_RST  BIT(15)
> +
> +#define BCC_STC_MASK     (0x00FFFFFF)
> +
> +#define CCTL_SEL_CDR1    0
> +#define CCTL_SEL_CDR2    BIT(12)
> +#define CDR1(n)          ((n & 0xf) << 8)
> +#define CDR2(n)          (((n/2) - 1) & 0xff)
> +
> +static int sunxi_spi_cs_activate(struct udevice *dev, unsigned cs)
> +{
> +       struct udevice *bus = dev->parent;
> +       struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
> +       struct sunxi_spi_reg *spi = (struct sunxi_spi_reg *)plat->base;
> +       struct sunxi_spi_privdata *priv = dev_get_priv(bus);
> +       int ret = 0;
> +
> +       debug("%s (%s): cs %d cs_gpios_num %d cs_gpios %p\n",
> +             dev->name, __func__, cs, plat->cs_gpios_num, plat->cs_gpios);
> +
> +       /* If it's too soon to do another transaction, wait... */
> +       if (plat->deactivate_delay_us && priv->last_transaction_us) {
> +               ulong delay_us;
> +               delay_us = timer_get_us() - priv->last_transaction_us;
> +               if (delay_us < plat->deactivate_delay_us)
> +                       udelay(plat->deactivate_delay_us - delay_us);
> +       }
> +
> +#if defined(CONFIG_DM_GPIO)
> +       /* Use GPIOs as chip selects? */
> +       if (plat->cs_gpios) {
> +               /* Guard against out-of-bounds accesses */
> +               if (!(cs < plat->cs_gpios_num))
> +                       return -ENOENT;
> +
> +               if (dm_gpio_is_valid(&plat->cs_gpios[cs])) {
> +                       ret = dm_gpio_set_value(&plat->cs_gpios[cs], 1);
> +                       goto done;
> +               }
> +       }
> +#endif
> +       /* The hardware can control up to 4 CS, however not all of
> +          them will be going to pads. We don't try to second-guess
> +          the DT or higher-level drivers though and just test against
> +          the hard limit. */
> +
> +       if (!(cs < 4))
> +               return -ENOENT;

Please try to control this in  .cs_info

> +
> +       /* Control the positional CS output */
> +       clrsetbits_le32(&spi->TCR, TCR_SSSEL_MASK, cs << TCR_SSSEL_SHIFT);

Define the last argument with macro, like Linux does.

> +       clrsetbits_le32(&spi->TCR, TCR_SSLEVEL, TCR_SSOWNER);

Why we need last argument?

Can't we do singe clrsetbits_le32 with last argument can be set through
- TCR_SSLEVEL
- cs << TCR_SSSEL_SHIFT)

> +
> +done:
> +       /* We'll delay, even it this is an error return... */
> +       if (plat->activate_delay_us)
> +               udelay(plat->activate_delay_us);
> +
> +       return ret;
> +}
> +
> +static void sunxi_spi_cs_deactivate(struct udevice *dev, unsigned cs)
> +{
> +       struct udevice *bus = dev->parent;
> +       struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
> +       struct sunxi_spi_reg *spi = (struct sunxi_spi_reg *)plat->base;
> +       struct sunxi_spi_privdata *priv = dev_get_priv(bus);
> +
> +#if defined(CONFIG_DM_GPIO)
> +       /* Use GPIOs as chip selects? */
> +       if (plat->cs_gpios) {
> +               if (dm_gpio_is_valid(&plat->cs_gpios[cs])) {
> +                       dm_gpio_set_value(&plat->cs_gpios[cs], 0);
> +                       return;
> +               }
> +       }
> +#endif
> +
> +       /* We have only the hardware chip select, so use those */
> +       setbits_le32(&spi->TCR, TCR_SSLEVEL | TCR_SSOWNER);
> +
> +       /* Remember time of this transaction for the next delay */
> +       if (plat->deactivate_delay_us)
> +               priv->last_transaction_us = timer_get_us();
> +}
> +
> +static inline uint8_t *spi_fill_writefifo(struct sunxi_spi_reg *spi,
> +                                         uint8_t *dout, int cnt)
> +{
> +       debug("%s: dout = %p, cnt = %d\n", __func__, dout, cnt);
> +
> +       if (dout) {
> +               int i;
> +
> +               for (i = 0; i < cnt; i++)
> +                       writeb(dout[i], &spi->TXD);
> +
> +               dout += cnt;
> +       }
> +
> +       return dout;
> +}
> +
> +static inline uint8_t *spi_drain_readfifo(struct sunxi_spi_reg *spi,
> +                                         uint8_t *din, int cnt)
> +{
> +       debug("%s: din = %p, cnt = %d\n", __func__, din, cnt);
> +
> +       if (din) {
> +               int i;
> +
> +               for (i = 0; i < cnt; i++)
> +                       din[i] = readb(&spi->RXD);
> +
> +               din += cnt;
> +       }
> +
> +       return din;
> +}
> +
> +static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
> +                         const void *out, void *in, unsigned long flags)
> +{
> +       struct udevice *bus = dev->parent;
> +       struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
> +       struct sunxi_spi_privdata *priv = dev_get_priv(bus);
> +       struct sunxi_spi_reg *spi = (struct sunxi_spi_reg *)plat->base;
> +       struct sunxi_spi_driverdata *data =
> +               (struct sunxi_spi_driverdata *)dev_get_driver_data(dev->parent);
> +       struct dm_spi_slave_platdata *slave = dev_get_parent_platdata(dev);
> +       uint8_t *dout = (uint8_t *)out;
> +       uint8_t *din = (uint8_t *)in;
> +       int fifo_depth = data->fifo_depth;
> +       unsigned int n_bytes = DIV_ROUND_UP(bitlen, 8);
> +       int ret = 0;
> +       /*
> +        * We assume that 1ms (for any delays within the module to
> +        * start the transfer) + 2x the time to transfer a full FIFO
> +        * (for the data- and bitrate-dependent part) is a reasonable
> +        * timeout to detect the module being stuck.
> +        */
> +       ulong timeout_ms =
> +               (DIV_ROUND_UP(fifo_depth * 16000, priv->hz_actual)) + 1;
> +
> +       debug("%s (%s): regs %p bitlen %d din %p flags %lx fifo_depth %d\n",
> +             dev->name, __func__, spi, bitlen, din, flags, fifo_depth);
> +
> +       if (flags & SPI_XFER_BEGIN) {
> +               ret = sunxi_spi_cs_activate(dev, slave->cs);
> +               if (ret < 0) {
> +                       error("%s: failed to activate chip-select %d\n",
> +                             dev->name, slave->cs);
> +                       return ret;
> +               }
> +       }
> +
> +       /* Reset FIFO */
> +       writel(FCR_RX_FIFO_RST | FCR_TX_FIFO_RST, &spi->FCR);
> +       /* Wait until the FIFO reset autoclears */
> +       ret = wait_for_bit(dev->name, &spi->FCR,
> +                          FCR_RX_FIFO_RST | FCR_TX_FIFO_RST,
> +                          false, 10, true);
> +       if (ret < 0) {
> +               error("%s: failed to reset FIFO within 10ms\n", bus->name);
> +               return ret;
> +       }
> +
> +       /* Set the discard burst bits depending on whether we are receiving */
> +       clrbits_le32(&spi->TCR, TCR_DHB);
> +       if (!din)
> +               setbits_le32(&spi->TCR, TCR_DHB);
> +
> +       /* Transfer in blocks of FIFO_DEPTH */
> +       while (n_bytes > 0) {
> +               int cnt = (n_bytes < fifo_depth) ? n_bytes : fifo_depth;
> +               int txcnt = dout ? cnt : 0;
> +
> +               /* We need to set up the transfer counters in every
> +                  iteration, as the hardware block counts those down
> +                  to 0 and leaves the 0 in the register (i.e. there's
> +                  no shadow register within the controller that these
> +                  values are copied into). */
> +
> +               /* master burst counter:     total length (tx + rx + dummy) */
> +               writel(cnt, &spi->MBC);
> +               /* master transmit counter:  tx */
> +               writel(txcnt, &spi->MTC);
> +               /* burst control counter:    single-mode tx */
> +               clrsetbits_le32(&spi->BCC, BCC_STC_MASK, txcnt & BCC_STC_MASK);
> +
> +               dout = spi_fill_writefifo(spi, dout, txcnt);
> +
> +               /* Start transfer ... */
> +               setbits_le32(&spi->TCR, TCR_XCH);
> +               /* ... and wait until it finshes. */
> +               ret = wait_for_bit(dev->name, &spi->TCR, TCR_XCH,
> +                                  false, timeout_ms, true);
> +               if (ret < 0) {
> +                       error("%s: stuck in XCH for %ld ms\n",
> +                             bus->name, timeout_ms);
> +                       goto fail;
> +               }
> +
> +               din = spi_drain_readfifo(spi, din, cnt);
> +
> +               n_bytes -= cnt;
> +       }
> +
> + fail:
> +       if (flags & SPI_XFER_END)
> +               sunxi_spi_cs_deactivate(dev, slave->cs);
> +
> +       return 0;
> +};
> +
> +static int sunxi_spi_ofdata_to_platdata(struct udevice *dev)
> +{
> +       struct sunxi_spi_platdata *plat = dev_get_platdata(dev);
> +       const void *blob = gd->fdt_blob;
> +       int node = dev->of_offset;
> +       fdt_addr_t addr;
> +       fdt_size_t size;
> +       int ret;
> +
> +       debug("%s: %p\n", __func__, dev);
> +
> +       addr = fdtdec_get_addr_size_auto_noparent(blob, node, "reg", 0,
> +                                                 &size, false);
> +       if (addr == FDT_ADDR_T_NONE) {
> +               debug("%s: failed to find base address\n", dev->name);
> +               return -ENODEV;
> +       }
> +       plat->base = (void *)addr;
> +       plat->max_hz = fdtdec_get_int(blob, node, "spi-max-frequency", 0);
> +       plat->activate_delay_us = fdtdec_get_int(blob, node,
> +                                                "spi-activate_delay", 0);
> +       plat->deactivate_delay_us = fdtdec_get_int(blob, node,
> +                                                  "spi-deactivate-delay", 0);
> +
> +#if defined(CONFIG_DM_GPIO)
> +       plat->cs_gpios_num = gpio_get_list_count(dev, "cs-gpios");
> +       if (plat->cs_gpios_num > 0) {
> +               int i;
> +
> +               plat->cs_gpios = calloc(plat->cs_gpios_num,
> +                                       sizeof(struct gpio_desc));
> +               if (!plat->cs_gpios)
> +                       return -ENOMEM;
> +
> +               for (i = 0; i < plat->cs_gpios_num; ++i)
> +                       gpio_request_by_name(dev, "cs-gpios", i,
> +                                            &plat->cs_gpios[i], 0);
> +       }
> +#endif
> +
> +       ret = reset_get_by_index(dev, 0, &plat->reset_ctl);
> +       if (ret) {
> +               error("%s: reset_get_by_index() with return code %d\n",
> +                     dev->name, ret);
> +               return ret;
> +       }
> +
> +       if (clk_get_by_name(dev, "ahb", &plat->ahb_clk_gate) ||
> +           clk_get_by_name(dev, "spi", &plat->spi_clk)) {
> +               error("%s: failed to get our clocks: ahb, spi\n", dev->name);
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int sunxi_spi_probe(struct udevice *dev)
> +{
> +       return 0;
> +}
> +
> +static int sunxi_spi_claim_bus(struct udevice *dev)
> +{
> +       struct sunxi_spi_platdata *plat = dev_get_platdata(dev->parent);
> +       struct spi_slave *spi_slave = dev_get_parent_priv(dev);
> +       struct sunxi_spi_reg *spi = (struct sunxi_spi_reg *)plat->base;
> +
> +       debug("%s: %p %p\n", __func__, dev, dev->parent);
> +
> +       /* Enable in master-mode */
> +       setbits_le32(&spi->GCR, GCR_MASTER | GCR_EN);

Yes, this is the only reg init required in claim.

> +       /* All CS control is manual and set them to inactive */
> +       clrbits_le32(&spi->TCR, TCR_SSSEL_MASK);
> +       setbits_le32(&spi->TCR, TCR_SSOWNER);

Better we can move this spi_cs_activate

> +       /* Apply polarity and phase from the mode bits */
> +       if (spi_slave->mode & SPI_CPOL)
> +               setbits_le32(&spi->TCR, TCR_CPOL);
> +       if (spi_slave->mode & SPI_CPHA)
> +               setbits_le32(&spi->TCR, TCR_CPHA);

Add this mode set in .set_mode .

> +
> +#if defined(DM_GPIO)
> +       /* Set all cs-gpios to inactive */
> +       for (i = 0; i < plat->cs_gpios_num; ++i)
> +               if (dm_gpio_is_valid(&plat->cs_gpios[i]))
> +                       dm_gpio_set_value(&plat->cs_gpios[i], 0);
> +#endif
> +
> +
> +       return 0;
> +}
> +
> +static int sunxi_spi_release_bus(struct udevice *dev)
> +{
> +       struct sunxi_spi_platdata *plat = dev_get_platdata(dev->parent);
> +       struct sunxi_spi_reg *spi = (struct sunxi_spi_reg *)plat->base;
> +
> +       clrbits_le32(&spi->GCR, GCR_EN);

Nice.

> +
> +       return 0;
> +}
> +
> +static int sunxi_spi_set_speed(struct udevice *bus, unsigned int hz)
> +{
> +       struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
> +       struct sunxi_spi_reg *spi = (struct sunxi_spi_reg *)plat->base;
> +       struct sunxi_spi_privdata *priv = dev_get_priv(bus);
> +       unsigned sclk_shift, hz_ahb, hz_sclk;
> +
> +       debug("%s: %p, %d\n", __func__, bus, hz);
> +
> +       if (plat->max_hz && (hz > plat->max_hz)) {
> +               debug("%s: selected speed (%d) exceeds maximum of %d\n",
> +                     bus->name, hz, plat->max_hz);
> +               hz = plat->max_hz;
> +       }

This look ok, like an assertion check.

> +
> +       /* If the last request was for the same speed, we're done */
> +       if (priv->hz_requested == hz)
> +               return 0;
> +
> +       /* The CCU section in the manual recommends to have the module
> +          reset deasserted before the module clock gate is opened. */
> +       reset_deassert(&plat->reset_ctl);
> +
> +       /* Enable and set the module clock.
> +        *
> +        * At least for the A31, there's a requirements to provide at
> +        * least 2x the sample clock, so we should never go below that
> +        * ratio between the AHB clock and the (ampling) SCLK. On the
> +        * low end of the clock, we use the provide two step-downs for
> +        * clocks on the low end (below 375kHz).
> +        *
> +        * However, testing shows that for high-speed modes (on the
> +        * A64), we may not divide SCLK from the AHB clock.
> +        */
> +       if (hz < 100000)
> +               sclk_shift = 8;
> +       else if (hz < 50000000)
> +               sclk_shift = 2;
> +       else
> +               sclk_shift = 0;

This definetly require a div formual instead of assiging shift values.

> +
> +       /* Program the SPI clock control */
> +       writel(CCTL_SEL_CDR1 | CDR1(sclk_shift), &spi->CCR);
> +
> +       hz_ahb = clk_set_rate(&plat->spi_clk, hz * (1 << sclk_shift));
> +       clk_enable(&plat->spi_clk);
> +       /* Pass the clock to the module */
> +       clk_enable(&plat->ahb_clk_gate);
> +
> +       hz_sclk = hz_ahb >> sclk_shift;
> +       priv->hz_actual = hz_sclk;
> +       debug("%s: hz_ahb %d  hz_sclk %d\n", bus->name, hz_ahb, hz_sclk);
> +
> +       /* If this is a high-speed mode (which we define---based upon
> +          empirical testing---to be above 50 MHz), we need to move the
> +          sampling point during data read. */
> +       if (hz_sclk > 50000000)
> +               setbits_le32(&spi->TCR, TCR_SDC);
> +       else
> +               clrbits_le32(&spi->TCR, TCR_SDC);

All we need here to calcuate the div value on specific baud on reg-set
and try to write those value, can you please indicate the correct div
calulation.

> +
> +       return 0;
> +};
> +
> +static int sunxi_spi_set_mode(struct udevice *bus, unsigned int mode)
> +{
> +       return 0;
> +};

Add mode set's here.

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


More information about the U-Boot mailing list