All of lore.kernel.org
 help / color / mirror / Atom feed
* [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI
@ 2017-03-01 21:29 Philipp Tomsich
  2017-03-01 21:29 ` [U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64 Philipp Tomsich
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:29 UTC (permalink / raw)
  To: u-boot

A new iteration of the dual-IO aware device-model based SPI driver
for sunxi (Allwinner SoCs).

As before, this has been tested with dual-IO flashes (Winbond
W25Q80DV) on our A64-uQ7 at up to 100MHz.

Changes in v2:
 * Uses wait_bit instead of readl_poll_... for better readability
 * To address Jagan's concerns regarding the layering violation from
   detecting SPI-NOR transactions and parsing the command-byte to
   detect dual-IO reads, I split this off into a separate patch (to
   make it easier to revert, once the SPI flash subsystem is able to
   handle this for us)---see the commit message for even more info.


Philipp Tomsich (2):
  spi: sunxi_spi: Add DM SPI driver for A31/A80/A64
  sunxi_spi: add support for dual-IO flashes

 drivers/spi/Kconfig     |  14 ++
 drivers/spi/Makefile    |   1 +
 drivers/spi/sunxi_spi.c | 582 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 597 insertions(+)
 create mode 100644 drivers/spi/sunxi_spi.c

-- 
1.9.1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64
  2017-03-01 21:29 [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI Philipp Tomsich
@ 2017-03-01 21:29 ` Philipp Tomsich
  2017-04-07  9:54   ` Jagan Teki
  2017-03-01 21:29 ` [U-Boot] [PATCH v2 2/2] sunxi_spi: add support for dual-IO flashes Philipp Tomsich
  2017-04-07  9:56 ` [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI Jagan Teki
  2 siblings, 1 reply; 5+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:29 UTC (permalink / raw)
  To: u-boot

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

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [U-Boot] [PATCH v2 2/2] sunxi_spi: add support for dual-IO flashes
  2017-03-01 21:29 [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI Philipp Tomsich
  2017-03-01 21:29 ` [U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64 Philipp Tomsich
@ 2017-03-01 21:29 ` Philipp Tomsich
  2017-04-07  9:56 ` [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI Jagan Teki
  2 siblings, 0 replies; 5+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:29 UTC (permalink / raw)
  To: u-boot

The SPI controller in the Allwinner A64 SoC supports dual-IO for
the RX phase of transmission.  This can be used with the command
'fast read dual output' (cmd, addr and dummy byte are transmitted
in single-IO mode; data is received in dual-IO mode) to quickly
read out SPI flashes, when the device-tree marks the flash as
having 'spi-rx-bus-width = <2>'.

Unfortunately, the SPI-NOR flash layer in U-Boot does not manage
the single-IO and dual-IO transition (partially due to the fact
that spi_xfer(...) does not allow to convery such information),
but correctly chooses the FAST_READ_DUAL_OUTPUT (0x3b) opcode.
The net result of this is that a dual-IO read is initiated, but
the data reception will capture only every other bit...

This change puts a temporary fix in place, which identifies a
0x3b opcode being sent in a transaction with a SPI flash and then
manages the switching to dual-IO within the driver.

This change should be reverted, once more permanent solutions
in the higher layers and in the SPI driver model have been agreed
on and have been put in place.

Tested on an A64 (sun50iw1p1) against a Winbond W25Q80DV flash at
up to 100MHz (i.e. 200MBit/s read bursts).

X-AffectedPlatforms: A64-uQ7
Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 drivers/spi/sunxi_spi.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 67 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/sunxi_spi.c b/drivers/spi/sunxi_spi.c
index f26becf..871c88d 100644
--- a/drivers/spi/sunxi_spi.c
+++ b/drivers/spi/sunxi_spi.c
@@ -24,6 +24,9 @@
 
 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;
@@ -47,7 +50,13 @@ 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 */
@@ -211,6 +220,43 @@ static inline uint8_t *spi_drain_readfifo(struct sunxi_spi_reg *spi,
 	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)
 {
@@ -239,6 +285,18 @@ static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
 	      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",
@@ -263,6 +321,12 @@ static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
 	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;
@@ -300,8 +364,10 @@ static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
 	}
 
  fail:
-	if (flags & SPI_XFER_END)
+	if (flags & SPI_XFER_END) {
 		sunxi_spi_cs_deactivate(dev, slave->cs);
+		sunxi_spi_trans_end(priv);
+	}
 
 	return 0;
 };
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64
  2017-03-01 21:29 ` [U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64 Philipp Tomsich
@ 2017-04-07  9:54   ` Jagan Teki
  0 siblings, 0 replies; 5+ messages in thread
From: Jagan Teki @ 2017-04-07  9:54 UTC (permalink / raw)
  To: u-boot

On Thu, Mar 2, 2017 at 2:59 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 | 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.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI
  2017-03-01 21:29 [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI Philipp Tomsich
  2017-03-01 21:29 ` [U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64 Philipp Tomsich
  2017-03-01 21:29 ` [U-Boot] [PATCH v2 2/2] sunxi_spi: add support for dual-IO flashes Philipp Tomsich
@ 2017-04-07  9:56 ` Jagan Teki
  2 siblings, 0 replies; 5+ messages in thread
From: Jagan Teki @ 2017-04-07  9:56 UTC (permalink / raw)
  To: u-boot

On Thu, Mar 2, 2017 at 2:59 AM, Philipp Tomsich
<philipp.tomsich@theobroma-systems.com> wrote:
> A new iteration of the dual-IO aware device-model based SPI driver
> for sunxi (Allwinner SoCs).
>
> As before, this has been tested with dual-IO flashes (Winbond
> W25Q80DV) on our A64-uQ7 at up to 100MHz.
>
> Changes in v2:
>  * Uses wait_bit instead of readl_poll_... for better readability
>  * To address Jagan's concerns regarding the layering violation from
>    detecting SPI-NOR transactions and parsing the command-byte to
>    detect dual-IO reads, I split this off into a separate patch (to
>    make it easier to revert, once the SPI flash subsystem is able to
>    handle this for us)---see the commit message for even more info.
>
>
> Philipp Tomsich (2):
>   spi: sunxi_spi: Add DM SPI driver for A31/A80/A64
>   sunxi_spi: add support for dual-IO flashes

Sorry, I don't want to keep the code, which will remove in future.(I
faced many issues like this)

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

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2017-04-07  9:56 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-03-01 21:29 [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI Philipp Tomsich
2017-03-01 21:29 ` [U-Boot] [PATCH v2 1/2] spi: sunxi_spi: Add DM SPI driver for A31/A80/A64 Philipp Tomsich
2017-04-07  9:54   ` Jagan Teki
2017-03-01 21:29 ` [U-Boot] [PATCH v2 2/2] sunxi_spi: add support for dual-IO flashes Philipp Tomsich
2017-04-07  9:56 ` [U-Boot] [PATCH v2 0/2] sunxi: DM-based driver for SPI Jagan Teki

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.