diff mbox

[U-Boot,v1] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64

Message ID 1487634036-45310-2-git-send-email-philipp.tomsich@theobroma-systems.com
State Superseded
Delegated to: Jagannadha Sutradharudu Teki
Headers show

Commit Message

Philipp Tomsich Feb. 20, 2017, 11:40 p.m. UTC
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>
---
 drivers/spi/Kconfig     |  14 ++
 drivers/spi/Makefile    |   1 +
 drivers/spi/sunxi_spi.c | 571 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 586 insertions(+)
 create mode 100644 drivers/spi/sunxi_spi.c

Comments

Jagan Teki Feb. 25, 2017, 8:43 a.m. UTC | #1
On Tue, Feb 21, 2017 at 5:10 AM, Philipp Tomsich
<philipp.tomsich@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@theobroma-systems.com>
> ---
>  drivers/spi/Kconfig     |  14 ++
>  drivers/spi/Makefile    |   1 +
>  drivers/spi/sunxi_spi.c | 571 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 586 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
>  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..cd2cb1d
> --- /dev/null
> +++ b/drivers/spi/sunxi_spi.c
> @@ -0,0 +1,571 @@
> +/*
> + * 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 <linux/iopoll.h>
> +#include <reset.h>
> +#include <spi.h>
> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +/* The SPI flash opcode for a FAST READ DUAL OUTPUT operation. */
> +#define CMD_READ_DUAL_OUTPUT_FAST 0x3b

Flash(slave) specific opcodes shouldn't use it on spi driver, try to
implement the spi in generic way instead of making to handle only
specific slave.

thanks!
Philipp Tomsich Feb. 25, 2017, 11:33 a.m. UTC | #2
Jagan,

> On 25 Feb 2017, at 09:43, Jagan Teki <jagan@openedev.com> wrote:
> 
>> +/* The SPI flash opcode for a FAST READ DUAL OUTPUT operation. */
>> +#define CMD_READ_DUAL_OUTPUT_FAST 0x3b
> 
> Flash(slave) specific opcodes shouldn't use it on spi driver, try to
> implement the spi in generic way instead of making to handle only
> specific slave.

I do agree, but there’s unfortunately a reason why this needs to be
in a SPI driver in U-Boot at this time: the SPI-NOR flash layer does
not correctly understand how a “FAST READ DUAL OUTPUT” cmd
is to be sent:
	— the dual-IO fast-read needs to transmit the (a) cmd and
	(b) 4-byte address as single-IO, then switch to Hi-Z while
	sending a dummy-byte and then receive in dual-IO
	— the SPI-NOR flash layer only sets the SPI_RX_DUAL mode
	flag for the all exchanges and issues two separate spi_xfer
	calls for the write and read phases (with the same mode).

I had a prototype implementation that added an additional spi_xfer
interface that allowed to specify a complete transaction (i.e. single-io,
dummy and dual-io phases), but switching the SPI-NOR layer to this
caused significant disruption there (it started to look like a rewrite, as
the probed READ_CMD couldn’t be described as a single-byte any
longer, but needed info on the transmission modes for CMD, ADDR,
dummy and data-in)… so it looks like a longer-term project.

Please note that
	drivers/spi/ich.c
	drivers/spi/fsl_qspi.c
use similar mechanisms to detect flash transactions and handle them
in a special mode.

Note that with the current implementation, this only code-path only 
has an effect if a dual-IO flash is configured as a slave (we even
check that the slave device is a flash-device according tot he DM).

Regards,
Philipp.
diff mbox

Patch

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
 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..cd2cb1d
--- /dev/null
+++ b/drivers/spi/sunxi_spi.c
@@ -0,0 +1,571 @@ 
+/*
+ * 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 <linux/iopoll.h>
+#include <reset.h>
+#include <spi.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* The SPI flash opcode for a FAST READ DUAL OUTPUT operation. */
+#define CMD_READ_DUAL_OUTPUT_FAST 0x3b
+
+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;
+};
+
+enum {
+	NONE = 0,
+	OPC_READ_DUAL_CMD,
+};
+
+struct sunxi_spi_privdata {
+	int  transaction_type;
+	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_trans_setup(struct sunxi_spi_privdata *priv,
+				 const uint8_t *dout,
+				 const uint8_t *din,
+				 unsigned int n_bytes)
+{
+	if (!dout) {
+		error("%s: SPI flash command requires at least an opcode\n",
+		      __func__);
+		return -EPROTO;
+	}
+
+	/* Detect dual-IO read commands */
+	if (dout[0] == CMD_READ_DUAL_OUTPUT_FAST) {
+		/* This is always called as two xfer-requests from the
+		 * higher layers:
+		 *  1. a write-only request with the 1-byte opcode,
+		 *     4-byte address and a dummy byte
+		 *  2. a read-only for the requested amount of data
+		 */
+
+		/* TODO: The "cmd, addr, dummy" sequence should be
+		 *       changed to "cmd, addr" w/ the controller
+		 *       generating the dummy cycles, so the Hi-Z
+		 *       state for IO0 and IO1 can already be
+		 *       generated during the dummy cycles.
+		 */
+		priv->transaction_type = OPC_READ_DUAL_CMD;
+	}
+
+	return 0;
+}
+
+static void sunxi_spi_trans_end(struct sunxi_spi_privdata *priv)
+{
+	priv->transaction_type = NONE;
+}
+
+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;
+	uint32_t regval;
+	/* We assume that 1000us (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_us =
+		(DIV_ROUND_UP(fifo_depth * 16000000, priv->hz_actual)) + 1000;
+
+	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) {
+		/* For dual-IO support, we need to detect flash read
+		 * commands here... this is actually a layering
+		 * violation, but can't be fixed non-intrusively now
+		 * and other drivers (e.g. Freescale QSPI, Intel ICH)
+		 * follow this pattern as well.
+		 */
+		if (device_get_uclass_id(dev) == UCLASS_SPI_FLASH) {
+			ret = sunxi_spi_trans_setup(priv, dout, din, n_bytes);
+			if (ret < 0)
+				return ret;
+		}
+
+		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 = readl_poll_timeout(&spi->FCR, regval,
+				 !(regval & (FCR_RX_FIFO_RST | FCR_TX_FIFO_RST)),
+				 1000);
+	if (ret < 0) {
+		error("%s: failed to reset FIFO within 1000us\n", bus->name);
+		return -ETIMEDOUT;
+	}
+
+	/* 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);
+
+	/* Set the dual-mode input bit */
+	if (priv->transaction_type == OPC_READ_DUAL_CMD)
+		setbits_le32(&spi->BCC, BIT(28));
+	else
+		clrbits_le32(&spi->BCC, BIT(28));
+
+	/* 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 and wait for it to finish... */
+		setbits_le32(&spi->TCR, TCR_XCH);
+		ret = readl_poll_timeout(&spi->TCR, regval,
+					 !(regval & TCR_XCH), timeout_us);
+		if (ret < 0) {
+			error("%s: stuck in XCH for %ld us\n",
+			      bus->name, timeout_us);
+			return -ETIMEDOUT;
+		}
+
+		din = spi_drain_readfifo(spi, din, cnt);
+
+		n_bytes -= cnt;
+	}
+
+	if (flags & SPI_XFER_END) {
+		sunxi_spi_cs_deactivate(dev, slave->cs);
+		sunxi_spi_trans_end(priv);
+	}
+
+	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 */
+	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);
+
+	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,
+};