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

Philipp Tomsich philipp.tomsich at theobroma-systems.com
Mon Feb 20 23:40:36 UTC 2017


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 | 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
+
+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,
+};
-- 
1.9.1



More information about the U-Boot mailing list