public inbox for u-boot@lists.denx.de
 help / color / mirror / Atom feed
From: Leo Yu-Chi Liang <ycliang@andestech.com>
To: <u-boot@lists.denx.de>
Cc: Tom Rini <trini@konsulko.com>, Vignesh R <vigneshr@ti.com>,
	"Takahiro Kuwano" <takahiro.kuwano@infineon.com>,
	Jagan Teki <jagan@amarulasolutions.com>, <ycliang@andestech.com>
Subject: [PATCH 5/8] spi: atcspi200: Add spi-mem framework support
Date: Fri, 17 Apr 2026 10:40:31 +0800	[thread overview]
Message-ID: <20260417024034.4046667-1-ycliang@andestech.com> (raw)

Add spi_controller_mem_ops implementation to support SPI memory
operations with single, dual, and quad modes:

- Add atcspi200_spi_mem_exec_op() for executing SPI memory operations
  with proper command, address, dummy, and data phase handling
- Add atcspi200_set_transfer_ctl() to configure the transfer control
  register for different bus widths and transfer modes
- Add atcspi200_pio_transfer() as a shared PIO data pump used by both
  the spi-mem exec_op and legacy xfer paths
- Add atcspi200_spi_adjust_op_size() to cap transfers to hardware max
- Add new TCTRL field definitions for cmd/addr enable, address format,
  dual/quad mode, dummy count, and token enable
- Add ADDR_LEN_MASK for configurable address length in format register
- Wire spi_controller_mem_ops into dm_spi_ops.mem_ops
- Simplify hw_start() for the legacy xfer path since cmd/addr handling
  is now done by the spi-mem framework for flash operations

Signed-off-by: Leo Yu-Chi Liang <ycliang@andestech.com>
---
 drivers/spi/atcspi200_spi.c | 208 ++++++++++++++++++++++++++----------
 1 file changed, 151 insertions(+), 57 deletions(-)

diff --git a/drivers/spi/atcspi200_spi.c b/drivers/spi/atcspi200_spi.c
index 0e7f7b83081..cef0bb8188d 100644
--- a/drivers/spi/atcspi200_spi.c
+++ b/drivers/spi/atcspi200_spi.c
@@ -11,6 +11,7 @@
 #include <dm/device_compat.h>
 #include <log.h>
 #include <spi.h>
+#include <spi-mem.h>
 #include <time.h>
 #include <wait_bit.h>
 #include <asm/io.h>
@@ -33,9 +34,13 @@
 #define ATCSPI200_REG_TIMING	0x40
 
 /* FORMAT register fields */
+#define ADDR_LEN_MASK		GENMASK(17, 16)
 #define DATA_LEN_MASK		GENMASK(12, 8)
 
 /* TCTRL register fields */
+#define TCTRL_CMD_EN		BIT(30)
+#define TCTRL_ADDR_EN		BIT(29)
+#define TCTRL_ADDR_FMT		BIT(28)
 #define TRAMODE_MASK		GENMASK(27, 24)
 #define TRAMODE_WR_SYNC		FIELD_PREP(TRAMODE_MASK, 0)
 #define TRAMODE_WO		FIELD_PREP(TRAMODE_MASK, 1)
@@ -47,7 +52,11 @@
 #define TRAMODE_NONE		FIELD_PREP(TRAMODE_MASK, 7)
 #define TRAMODE_DW		FIELD_PREP(TRAMODE_MASK, 8)
 #define TRAMODE_DR		FIELD_PREP(TRAMODE_MASK, 9)
+#define TCTRL_DUAL_QUAD_MASK	GENMASK(23, 22)
+#define TCTRL_TOKEN_EN		BIT(21)
 #define WCNT_MASK		GENMASK(20, 12)
+#define TCTRL_TOKEN_VAL		BIT(11)
+#define TCTRL_DUMMY_CNT_MASK	GENMASK(10, 9)
 #define RCNT_MASK		GENMASK(8, 0)
 
 /* CTRL register fields */
@@ -78,8 +87,8 @@ struct atcspi200_priv {
 	u8		cmd_buf[16];
 	size_t		data_len;
 	size_t		tran_len;
-	u8		*din;
-	u8		*dout;
+	void		*din;
+	const void	*dout;
 	unsigned int	max_transfer_length;
 };
 
@@ -138,7 +147,8 @@ static int atcspi200_hw_claim_bus(struct atcspi200_priv *priv)
 		return ret;
 
 	priv->cmd_len = 0;
-	format = priv->mode | FIELD_PREP(DATA_LEN_MASK, 8 - 1);
+	format = priv->mode | FIELD_PREP(DATA_LEN_MASK, 8 - 1) |
+		 FIELD_PREP(ADDR_LEN_MASK, 3 - 1);
 	atcspi200_write(priv, ATCSPI200_REG_FORMAT, format);
 	atcspi200_hw_set_speed(priv);
 
@@ -147,22 +157,15 @@ static int atcspi200_hw_claim_bus(struct atcspi200_priv *priv)
 
 static int atcspi200_hw_start(struct atcspi200_priv *priv)
 {
-	int i, olen = 0;
-	u32 tc;
+	u32 tc = 0;
 
-	tc = atcspi200_read(priv, ATCSPI200_REG_TCTRL);
-	tc &= ~(WCNT_MASK | RCNT_MASK | TRAMODE_MASK);
-
-	if (priv->din && priv->cmd_len)
-		tc |= TRAMODE_WR;
-	else if (priv->din)
+	if (priv->din && !priv->dout)
 		tc |= TRAMODE_RO;
-	else
+	else if (priv->dout && !priv->din)
 		tc |= TRAMODE_WO;
 
 	if (priv->dout)
-		olen = priv->tran_len;
-	tc |= FIELD_PREP(WCNT_MASK, priv->cmd_len + olen - 1);
+		tc |= FIELD_PREP(WCNT_MASK, priv->tran_len - 1);
 
 	if (priv->din)
 		tc |= FIELD_PREP(RCNT_MASK, priv->tran_len - 1);
@@ -170,9 +173,6 @@ static int atcspi200_hw_start(struct atcspi200_priv *priv)
 	atcspi200_write(priv, ATCSPI200_REG_TCTRL, tc);
 	atcspi200_write(priv, ATCSPI200_REG_CMD, 1);
 
-	for (i = 0; i < priv->cmd_len; i++)
-		atcspi200_write(priv, ATCSPI200_REG_DATA, priv->cmd_buf[i]);
-
 	return 0;
 }
 
@@ -192,19 +192,45 @@ static void atcspi200_rx_byte(struct atcspi200_priv *priv, void *din)
 	*(u8 *)din = (u8)atcspi200_read(priv, ATCSPI200_REG_DATA);
 }
 
+static void atcspi200_pio_transfer(struct atcspi200_priv *priv,
+				   void *din, const void *dout,
+				   unsigned int len)
+{
+	unsigned int tx_remain = dout ? len : 0;
+	unsigned int rx_remain = din ? len : 0;
+	unsigned long start = get_timer(0);
+
+	while ((tx_remain || rx_remain) &&
+	       get_timer(start) <= SPI_TIMEOUT_MS) {
+		u32 status = atcspi200_read(priv, ATCSPI200_REG_STATUS);
+
+		if (tx_remain && !(status & TXFFL)) {
+			atcspi200_tx_byte(priv, dout);
+			dout = (const u8 *)dout + 1;
+			tx_remain--;
+		}
+
+		if (rx_remain && (status & RXFVE_MASK)) {
+			atcspi200_rx_byte(priv, din);
+			din = (u8 *)din + 1;
+			rx_remain--;
+		}
+	}
+
+	if (tx_remain || rx_remain)
+		debug("%s: timeout, tx_remain=%u rx_remain=%u\n",
+		      __func__, tx_remain, rx_remain);
+}
+
 static int atcspi200_hw_xfer(struct atcspi200_priv *priv,
 			     unsigned int bitlen, const void *data_out,
 			     void *data_in, unsigned long flags)
 {
-	unsigned int event;
-	const void *dout = NULL;
-	void *din = NULL;
-	int num_blks, num_chunks, max_tran_len, tran_len;
+	unsigned int num_chunks, max_tran_len, tran_len;
 	u8 *cmd_buf = priv->cmd_buf;
 	size_t cmd_len = priv->cmd_len;
 	unsigned long data_len = bitlen / 8;
 	int ret = 0;
-	unsigned long start;
 
 	max_tran_len = priv->max_transfer_length;
 	switch (flags) {
@@ -218,19 +244,14 @@ static int atcspi200_hw_xfer(struct atcspi200_priv *priv,
 		if (bitlen == 0)
 			return 0;
 		priv->data_len = data_len;
-		priv->din = (u8 *)data_in;
-		priv->dout = (u8 *)data_out;
+		priv->din = data_in;
+		priv->dout = data_out;
 		break;
 
 	case SPI_XFER_BEGIN | SPI_XFER_END:
-		priv->data_len = 0;
-		priv->din = 0;
-		priv->dout = 0;
-		cmd_len = priv->cmd_len = data_len;
-		memcpy(cmd_buf, data_out, cmd_len);
-		data_out = 0;
-		data_len = 0;
-		atcspi200_hw_start(priv);
+		priv->data_len = data_len;
+		priv->din = data_in;
+		priv->dout = data_out;
 		break;
 	}
 
@@ -240,35 +261,11 @@ static int atcspi200_hw_xfer(struct atcspi200_priv *priv,
 		      data_in, data_len);
 
 	num_chunks = DIV_ROUND_UP(data_len, max_tran_len);
-	din = data_in;
-	dout = data_out;
 	while (num_chunks--) {
 		tran_len = min((size_t)data_len, (size_t)max_tran_len);
 		priv->tran_len = tran_len;
-		num_blks = tran_len;
-		start = get_timer(0);
 		atcspi200_hw_start(priv);
-
-		while (num_blks) {
-			if (get_timer(start) > SPI_TIMEOUT_MS) {
-				debug("spi_xfer: %s() timeout\n", __func__);
-				break;
-			}
-
-			event = atcspi200_read(priv, ATCSPI200_REG_STATUS);
-
-			if ((event & TXEPTY) && data_out) {
-				atcspi200_tx_byte(priv, dout);
-				num_blks--;
-				dout++;
-			}
-
-			if ((event & RXFVE_MASK) && data_in) {
-				atcspi200_rx_byte(priv, din);
-				num_blks--;
-				din = (unsigned char *)din + 1;
-			}
-		}
+		atcspi200_pio_transfer(priv, priv->din, priv->dout, tran_len);
 
 		data_len -= tran_len;
 		if (data_len) {
@@ -288,6 +285,97 @@ static int atcspi200_hw_xfer(struct atcspi200_priv *priv,
 	return ret;
 }
 
+static void atcspi200_set_transfer_ctl(struct atcspi200_priv *priv,
+				       const struct spi_mem_op *op)
+{
+	u32 tc = 0;
+
+	if (op->cmd.nbytes)
+		tc |= TCTRL_CMD_EN;
+
+	if (op->addr.nbytes) {
+		tc |= TCTRL_ADDR_EN;
+		if (op->addr.buswidth > 1)
+			tc |= TCTRL_ADDR_FMT;
+	}
+
+	if (op->data.nbytes) {
+		if (op->data.buswidth == 2)
+			tc |= FIELD_PREP(TCTRL_DUAL_QUAD_MASK, 1);
+		else if (op->data.buswidth == 4)
+			tc |= FIELD_PREP(TCTRL_DUAL_QUAD_MASK, 2);
+
+		if (op->data.dir == SPI_MEM_DATA_IN) {
+			if (op->dummy.nbytes) {
+				tc |= TRAMODE_DR;
+				tc |= FIELD_PREP(TCTRL_DUMMY_CNT_MASK,
+						 op->dummy.nbytes - 1);
+				if (op->data.buswidth == 4)
+					tc |= TCTRL_TOKEN_EN;
+			} else {
+				tc |= TRAMODE_RO;
+			}
+			tc |= FIELD_PREP(RCNT_MASK, op->data.nbytes - 1);
+		} else {
+			tc |= TRAMODE_WO;
+			tc |= FIELD_PREP(WCNT_MASK, op->data.nbytes - 1);
+		}
+	} else {
+		tc |= TRAMODE_NONE;
+	}
+
+	atcspi200_write(priv, ATCSPI200_REG_TCTRL, tc);
+}
+
+static int atcspi200_spi_mem_exec_op(struct spi_slave *slave,
+				     const struct spi_mem_op *op)
+{
+	struct udevice *bus = slave->dev->parent;
+	struct atcspi200_priv *priv = dev_get_priv(bus);
+	u32 format;
+	int ret;
+
+	/* Update address length in format register if needed */
+	if (op->addr.nbytes) {
+		format = atcspi200_read(priv, ATCSPI200_REG_FORMAT);
+		format &= ~ADDR_LEN_MASK;
+		format |= FIELD_PREP(ADDR_LEN_MASK, op->addr.nbytes - 1);
+		atcspi200_write(priv, ATCSPI200_REG_FORMAT, format);
+	}
+
+	/* Set up transfer control for this operation */
+	atcspi200_set_transfer_ctl(priv, op);
+
+	/* Write address and command to hardware */
+	if (op->addr.nbytes)
+		atcspi200_write(priv, ATCSPI200_REG_ADDR, op->addr.val);
+	atcspi200_write(priv, ATCSPI200_REG_CMD, op->cmd.opcode);
+
+	/* Transfer data via PIO */
+	if (op->data.nbytes) {
+		void *din = NULL;
+		const void *dout = NULL;
+
+		if (op->data.dir == SPI_MEM_DATA_IN)
+			din = op->data.buf.in;
+		else
+			dout = op->data.buf.out;
+
+		atcspi200_pio_transfer(priv, din, dout, op->data.nbytes);
+	}
+
+	ret = atcspi200_hw_stop(priv);
+
+	return ret;
+}
+
+static int atcspi200_spi_adjust_op_size(struct spi_slave *slave,
+					struct spi_mem_op *op)
+{
+	op->data.nbytes = min(op->data.nbytes, (unsigned int)MAX_TRANSFER_LEN);
+	return 0;
+}
+
 static int atcspi200_spi_set_speed(struct udevice *bus, uint max_hz)
 {
 	struct atcspi200_priv *priv = dev_get_priv(bus);
@@ -390,12 +478,18 @@ static int atcspi200_spi_of_to_plat(struct udevice *bus)
 	return 0;
 }
 
+static const struct spi_controller_mem_ops atcspi200_spi_mem_ops = {
+	.exec_op = atcspi200_spi_mem_exec_op,
+	.adjust_op_size = atcspi200_spi_adjust_op_size,
+};
+
 static const struct dm_spi_ops atcspi200_spi_ops = {
 	.claim_bus	= atcspi200_spi_claim_bus,
 	.release_bus	= atcspi200_spi_release_bus,
 	.xfer		= atcspi200_spi_xfer,
 	.set_speed	= atcspi200_spi_set_speed,
 	.set_mode	= atcspi200_spi_set_mode,
+	.mem_ops	= &atcspi200_spi_mem_ops,
 };
 
 static const struct udevice_id atcspi200_spi_ids[] = {
-- 
2.34.1


             reply	other threads:[~2026-04-17  2:41 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-17  2:40 Leo Yu-Chi Liang [this message]
2026-04-17  2:40 ` [PATCH 6/8] spi: atcspi200: Add data merge mode support Leo Yu-Chi Liang
2026-04-17  2:40 ` [PATCH 7/8] mtd: spi-nor: Add Macronix MX25U quad-mode fixups Leo Yu-Chi Liang
2026-04-17  6:56   ` Takahiro.Kuwano
2026-04-17  2:40 ` [PATCH 8/8] configs: ae350: Enable ATCSPI200 data merge mode Leo Yu-Chi Liang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260417024034.4046667-1-ycliang@andestech.com \
    --to=ycliang@andestech.com \
    --cc=jagan@amarulasolutions.com \
    --cc=takahiro.kuwano@infineon.com \
    --cc=trini@konsulko.com \
    --cc=u-boot@lists.denx.de \
    --cc=vigneshr@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox