[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