summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Tomsich <philipp.tomsich@theobroma-systems.com>2017-02-20 22:17:50 +0100
committerPhilipp Tomsich <philipp.tomsich@theobroma-systems.com>2017-03-09 01:40:21 +0100
commitc40cccd0c8cce2ffe20811454baeea2d467fe8d1 (patch)
tree3c63d03b13e35fb59e2985f91be55449f6c888f0
parentbb8850c23d8c5230b9d52ef14b71c697b53f241d (diff)
spi: sunxi_spi: Add DM SPI driver for A31/A80/A64
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@theobroma-systems.com>
-rw-r--r--drivers/spi/Kconfig14
-rw-r--r--drivers/spi/Makefile1
-rw-r--r--drivers/spi/sunxi_spi.c516
3 files changed, 531 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index f3f7dbe089..64b6430d27 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 fa9a1d2496..aab31b4fd7 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
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 0000000000..f26becf368
--- /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;
+
+ /* Control the positional CS output */
+ clrsetbits_le32(&spi->TCR, TCR_SSSEL_MASK, cs << TCR_SSSEL_SHIFT);
+ clrsetbits_le32(&spi->TCR, TCR_SSLEVEL, TCR_SSOWNER);
+
+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);
+ /* All CS control is manual and set them to inactive */
+ clrbits_le32(&spi->TCR, TCR_SSSEL_MASK);
+ setbits_le32(&spi->TCR, TCR_SSOWNER);
+ /* 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);
+
+#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);
+
+ 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;
+ }
+
+ /* 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;
+
+ /* 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);
+
+ return 0;
+};
+
+static int sunxi_spi_set_mode(struct udevice *bus, unsigned int mode)
+{
+ return 0;
+};
+
+static const struct dm_spi_ops sunxi_spi_ops = {
+ .claim_bus = sunxi_spi_claim_bus,
+ .release_bus = sunxi_spi_release_bus,
+ .xfer = sunxi_spi_xfer,
+ .set_speed = sunxi_spi_set_speed,
+ .set_mode = sunxi_spi_set_mode,
+ /*
+ * cs_info is not needed, since we require all chip selects to be
+ * in the device tree explicitly
+ */
+};
+
+static struct sunxi_spi_driverdata sun6i_a31_data = {
+ .fifo_depth = 128,
+};
+
+static struct sunxi_spi_driverdata sun50i_a64_data = {
+ .fifo_depth = 64,
+};
+
+static const struct udevice_id sunxi_spi_ids[] = {
+ { .compatible = "allwinner,sun6i-a31-spi",
+ .data = (uintptr_t)&sun6i_a31_data },
+ { .compatible = "allwinner,sun8i-h3-spi",
+ .data = (uintptr_t)&sun50i_a64_data },
+ { }
+};
+
+U_BOOT_DRIVER(sunxi_spi) = {
+ .name = "sunxi_spi",
+ .id = UCLASS_SPI,
+ .of_match = sunxi_spi_ids,
+ .ofdata_to_platdata = sunxi_spi_ofdata_to_platdata,
+ .platdata_auto_alloc_size = sizeof(struct sunxi_spi_platdata),
+ .priv_auto_alloc_size = sizeof(struct sunxi_spi_privdata),
+ .probe = sunxi_spi_probe,
+ .ops = &sunxi_spi_ops,
+};