[PATCH v4 07/17] spi: dw: Add mem_ops

Sean Anderson seanga2 at gmail.com
Tue Feb 11 07:04:15 CET 2020


The dw spi devices on the Kendryte K210 must be operated in a specific
fasion which cannot be achived through multiple writes to via dw_spi_xfer
(as it is currently written). This patch adds an implementation of exec_op,
which gives correct behaviour when reading/writing spi flash.

I would like to be able to modify the existing dw_spi_xfer function such
that it works properly (e.g. with the mmc_spi driver). However, the only
example code I have to work off is Kendryte's sdk (which is written in the
exec_op style), and I do not have access to the datasheet (if anyone does,
I would love to have a look!).

Signed-off-by: Sean Anderson <seanga2 at gmail.com>
---

Changes in v4:
- New

 drivers/spi/designware_spi.c | 123 +++++++++++++++++++++++++++++++++--
 1 file changed, 119 insertions(+), 4 deletions(-)

diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c
index 04cc873754..277eb19a0b 100644
--- a/drivers/spi/designware_spi.c
+++ b/drivers/spi/designware_spi.c
@@ -17,6 +17,7 @@
 #include <errno.h>
 #include <malloc.h>
 #include <spi.h>
+#include <spi-mem.h>
 #include <fdtdec.h>
 #include <reset.h>
 #include <linux/compat.h>
@@ -107,8 +108,8 @@ struct dw_spi_priv {
 	int len;
 
 	u32 fifo_len;		/* depth of the FIFO buffer */
-	void *tx;
-	void *tx_end;
+	const void *tx;
+	const void *tx_end;
 	void *rx;
 	void *rx_end;
 
@@ -344,7 +345,7 @@ static void dw_writer(struct dw_spi_priv *priv)
 				txw = *(u16 *)(priv->tx);
 		}
 		dw_write(priv, DW_SPI_DR, txw);
-		debug("%s: tx=0x%02x\n", __func__, txw);
+		log_io("tx=0x%02x\n", txw);
 		priv->tx += priv->bits_per_word >> 3;
 	}
 }
@@ -356,7 +357,7 @@ static void dw_reader(struct dw_spi_priv *priv)
 
 	while (max--) {
 		rxw = dw_read(priv, DW_SPI_DR);
-		debug("%s: rx=0x%02x\n", __func__, rxw);
+		log_io("rx=0x%02x\n", rxw);
 
 		/* Care about rx if the transfer's original "rx" is not null */
 		if (priv->rx_end - priv->len) {
@@ -483,6 +484,115 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen,
 	return ret;
 }
 
+static int dw_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op)
+{
+	bool read = op->data.dir == SPI_MEM_DATA_IN;
+	int pos, i, ret = 0;
+	struct udevice *bus = slave->dev->parent;
+	struct dw_spi_platdata *plat = dev_get_platdata(bus);
+	struct dw_spi_priv *priv = dev_get_priv(bus);
+	u8 op_len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes;
+	u8 op_buf[op_len];
+	u32 cr0;
+
+	if (read)
+		priv->tmode = SPI_TMOD_EPROMREAD;
+	else
+		priv->tmode = SPI_TMOD_TO;
+
+	debug("%s: buf=%p len=%u [bytes]\n",
+	      __func__, op->data.buf.in, op->data.nbytes);
+
+	cr0 = GEN_CTRL0(priv, plat);
+	debug("%s: cr0=%08x\n", __func__, cr0);
+
+	spi_enable_chip(priv, 0);
+	dw_write(priv, DW_SPI_CTRL0, cr0);
+	if (read)
+		dw_write(priv, DW_SPI_CTRL1, op->data.nbytes - 1);
+	spi_enable_chip(priv, 1);
+
+	/* From spi_mem_exec_op */
+	pos = 0;
+	op_buf[pos++] = op->cmd.opcode;
+	if (op->addr.nbytes) {
+		for (i = 0; i < op->addr.nbytes; i++)
+			op_buf[pos + i] = op->addr.val >>
+				(8 * (op->addr.nbytes - i - 1));
+
+		pos += op->addr.nbytes;
+	}
+	if (op->dummy.nbytes)
+		memset(op_buf + pos, 0xff, op->dummy.nbytes);
+
+	priv->tx = &op_buf;
+	priv->tx_end = priv->tx + op_len;
+	while (priv->tx != priv->tx_end)
+		dw_writer(priv);
+
+	/*
+	 * XXX: The following are tight loops! Enabling debug messages may cause
+	 * them to fail because we are not reading/writing the fifo fast enough.
+	 *
+	 * We heuristically break out of the loop when we stop getting data.
+	 * This is to stop us from hanging if the device doesn't send any data
+	 * (either at all, or after sending a response). For example, one flash
+	 * chip I tested did not send anything back after the first 64K of data.
+	 */
+	if (read) {
+		/* If we have gotten any data back yet */
+		bool got_data = false;
+		/* How many times we have looped without reading anything */
+		int loops_since_read = 0;
+		struct spi_mem_op *mut_op = (struct spi_mem_op *)op;
+
+		priv->rx = op->data.buf.in;
+		priv->rx_end = priv->rx + op->data.nbytes;
+
+		dw_write(priv, DW_SPI_SER, 1 << spi_chip_select(slave->dev));
+		while (priv->rx != priv->rx_end) {
+			void *last_rx = priv->rx;
+
+			dw_reader(priv);
+			if (priv->rx == last_rx) {
+				loops_since_read++;
+				/* Thresholds are arbitrary */
+				if (loops_since_read > 256)
+					break;
+				else if (got_data && loops_since_read > 32)
+					break;
+			} else {
+				got_data = true;
+				loops_since_read = 0;
+			}
+		}
+
+		/* Update with the actual amount of data read */
+		mut_op->data.nbytes -= priv->rx_end - priv->rx;
+	} else {
+		u32 val;
+
+		priv->tx = op->data.buf.out;
+		priv->tx_end = priv->tx + op->data.nbytes;
+
+		/* Fill up the write fifo before starting the transfer */
+		dw_writer(priv);
+		dw_write(priv, DW_SPI_SER, 1 << spi_chip_select(slave->dev));
+		while (priv->tx != priv->tx_end)
+			dw_writer(priv);
+
+		if (readl_poll_timeout(priv->regs + DW_SPI_SR, val,
+				       (val & SR_TF_EMPT) && !(val & SR_BUSY),
+				       RX_TIMEOUT * 1000)) {
+			ret = -ETIMEDOUT;
+		}
+	}
+
+	dw_write(priv, DW_SPI_SER, 0);
+	debug("%s: %u bytes xfered\n", __func__, op->data.nbytes);
+	return ret;
+}
+
 static int dw_spi_set_speed(struct udevice *bus, uint speed)
 {
 	struct dw_spi_platdata *plat = dev_get_platdata(bus);
@@ -546,8 +656,13 @@ static int dw_spi_remove(struct udevice *bus)
 	return 0;
 }
 
+static const struct spi_controller_mem_ops dw_spi_mem_ops = {
+	.exec_op = dw_spi_exec_op,
+};
+
 static const struct dm_spi_ops dw_spi_ops = {
 	.xfer		= dw_spi_xfer,
+	.mem_ops	= &dw_spi_mem_ops,
 	.set_speed	= dw_spi_set_speed,
 	.set_mode	= dw_spi_set_mode,
 	/*
-- 
2.25.0



More information about the U-Boot mailing list