[PATCH v4 11/13] spi: dw: Add mem_ops

Sean Anderson seanga2 at gmail.com
Sat Oct 17 00:57:53 CEST 2020


The designware ssi device has "broken" chip select behaviour [1], and needs
specific manipulation to use the built-in chip select. The existing fix is
to use an external GPIO for chip select, but typically the K210 has SPI3
directly connected to a flash chip with dedicated pins. This makes it
impossible to use the spi_xfer function to use spi, since the CS is
de-asserted in between calls.  This patch adds an implementation of
exec_op, which gives correct behaviour when reading/writing spi flash.

This patch also rearranges the headers to conform to U-Boot style.

[1] https://lkml.org/lkml/2015/12/23/132

Signed-off-by: Sean Anderson <seanga2 at gmail.com>
Tested-by Eugeniy Paltsev <Eugeniy.Paltsev at synopsys.com>
---
This patch was previously part of
https://patchwork.ozlabs.org/project/uboot/list/?series=161576

Changes in v4:
- Overcome my sloth
- Rearrange headers in designware_spi.c

Changes in v3:
- Use constant 0x10000 instead of SZ_64K. The latter is not included on
  some platforms and I'm too lazy to figure out what the correct header is.

Changes in v2:
- Add external gpio cs support
- Clean up exec_op
- Convert debug to log_*
- Limit data transfers to 64k

 drivers/spi/designware_spi.c | 122 ++++++++++++++++++++++++++++++++---
 1 file changed, 113 insertions(+), 9 deletions(-)

diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c
index 9e02fce6c6..ce74ac0abc 100644
--- a/drivers/spi/designware_spi.c
+++ b/drivers/spi/designware_spi.c
@@ -12,21 +12,23 @@
 
 #define LOG_CATEGORY UCLASS_SPI
 #include <common.h>
-#include <log.h>
-#include <asm-generic/gpio.h>
 #include <clk.h>
 #include <dm.h>
-#include <errno.h>
-#include <malloc.h>
-#include <spi.h>
-#include <fdtdec.h>
-#include <reset.h>
 #include <dm/device_compat.h>
-#include <linux/bitops.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <log.h>
+#include <malloc.h>
+#include <reset.h>
+#include <spi.h>
+#include <spi-mem.h>
+#include <asm/io.h>
+#include <asm-generic/gpio.h>
 #include <linux/bitfield.h>
+#include <linux/bitops.h>
 #include <linux/compat.h>
 #include <linux/iopoll.h>
-#include <asm/io.h>
+#include <linux/sizes.h>
 
 /* Register offsets */
 #define DW_SPI_CTRLR0			0x00
@@ -560,6 +562,107 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen,
 	return ret;
 }
 
+/*
+ * This function is necessary for reading SPI flash with the native CS
+ * c.f. https://lkml.org/lkml/2015/12/23/132
+ */
+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_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 = CTRLR0_TMOD_EPROMREAD;
+	else
+		priv->tmode = CTRLR0_TMOD_TO;
+
+	cr0 = priv->update_cr0(priv);
+	dev_dbg(bus, "cr0=%08x buf=%p len=%u [bytes]\n", cr0, op->data.buf.in,
+		op->data.nbytes);
+
+	dw_write(priv, DW_SPI_SSIENR, 0);
+	dw_write(priv, DW_SPI_CTRLR0, cr0);
+	if (read)
+		dw_write(priv, DW_SPI_CTRLR1, op->data.nbytes - 1);
+	dw_write(priv, DW_SPI_SSIENR, 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);
+
+	external_cs_manage(slave->dev, false);
+
+	priv->tx = &op_buf;
+	priv->tx_end = priv->tx + op_len;
+	priv->rx = NULL;
+	priv->rx_end = NULL;
+	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.
+	 */
+	if (read) {
+		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)
+			dw_reader(priv);
+	} 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);
+	external_cs_manage(slave->dev, true);
+
+	dev_dbg(bus, "%u bytes xfered\n", op->data.nbytes);
+	return ret;
+}
+
+/* The size of ctrl1 limits data transfers to 64K */
+static int dw_spi_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op)
+{
+	op->data.nbytes = min(op->data.nbytes, (unsigned int)SZ_64K);
+
+	return 0;
+}
+
+static const struct spi_controller_mem_ops dw_spi_mem_ops = {
+	.exec_op = dw_spi_exec_op,
+	.adjust_op_size = dw_spi_adjust_op_size,
+};
+
 static int dw_spi_set_speed(struct udevice *bus, uint speed)
 {
 	struct dw_spi_platdata *plat = dev_get_platdata(bus);
@@ -624,6 +727,7 @@ static int dw_spi_remove(struct udevice *bus)
 
 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.28.0



More information about the U-Boot mailing list