public inbox for u-boot@lists.denx.de
 help / color / mirror / Atom feed
* [PATCH 5/8] spi: atcspi200: Add spi-mem framework support
@ 2026-04-17  2:40 Leo Yu-Chi Liang
  2026-04-17  2:40 ` [PATCH 6/8] spi: atcspi200: Add data merge mode support Leo Yu-Chi Liang
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Leo Yu-Chi Liang @ 2026-04-17  2:40 UTC (permalink / raw)
  To: u-boot; +Cc: Tom Rini, Vignesh R, Takahiro Kuwano, Jagan Teki, ycliang

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


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

* [PATCH 6/8] spi: atcspi200: Add data merge mode support
  2026-04-17  2:40 [PATCH 5/8] spi: atcspi200: Add spi-mem framework support Leo Yu-Chi Liang
@ 2026-04-17  2:40 ` 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  2:40 ` [PATCH 8/8] configs: ae350: Enable ATCSPI200 data merge mode Leo Yu-Chi Liang
  2 siblings, 0 replies; 5+ messages in thread
From: Leo Yu-Chi Liang @ 2026-04-17  2:40 UTC (permalink / raw)
  To: u-boot; +Cc: Tom Rini, Vignesh R, Takahiro Kuwano, Jagan Teki, ycliang

Add CONFIG_ATCSPI200_SPI_DATA_MERGE option to enable 4-byte data
merge mode for the ATCSPI200 SPI controller. When enabled, each
write to the data register transmits four bytes, and each read
retrieves four bytes as a single word, improving throughput for
aligned transfers.

- Add DATA_MERGE bit (bit 7) in format register
- Add data_merge field to private data structure
- Update TX/RX helpers to handle u32 access when data_merge is active
- Dynamically enable/disable data merge based on transfer alignment
  (nbytes % 4 == 0) in both spi-mem exec_op and legacy xfer paths
- Use IS_ENABLED() consistently for the Kconfig check
- Add Kconfig entry with proper depends on ATCSPI200_SPI

Signed-off-by: Leo Yu-Chi Liang <ycliang@andestech.com>
---
 drivers/spi/Kconfig         |  9 ++++++
 drivers/spi/atcspi200_spi.c | 62 ++++++++++++++++++++++++++++++-------
 2 files changed, 59 insertions(+), 12 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 4ff17617d99..f238b2c2534 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -92,6 +92,15 @@ config ATCSPI200_SPI
 	  used to access the SPI flash on AE3XX and AE250 platforms embedding
 	  this Andestech IP core.
 
+config ATCSPI200_SPI_DATA_MERGE
+	bool "Enable ATCSPI200 data merge mode"
+	depends on ATCSPI200_SPI
+	help
+	  Enable data merge mode for the ATCSPI200 SPI controller.
+	  When enabled, each write to the data register transmits four
+	  bytes, and each read retrieves four bytes as a single word.
+	  This improves throughput for aligned transfers.
+
 config ATH79_SPI
 	bool "Atheros SPI driver"
 	depends on ARCH_ATH79
diff --git a/drivers/spi/atcspi200_spi.c b/drivers/spi/atcspi200_spi.c
index cef0bb8188d..b743f80fb8d 100644
--- a/drivers/spi/atcspi200_spi.c
+++ b/drivers/spi/atcspi200_spi.c
@@ -36,6 +36,7 @@
 /* FORMAT register fields */
 #define ADDR_LEN_MASK		GENMASK(17, 16)
 #define DATA_LEN_MASK		GENMASK(12, 8)
+#define DATA_MERGE		BIT(7)
 
 /* TCTRL register fields */
 #define TCTRL_CMD_EN		BIT(30)
@@ -89,6 +90,7 @@ struct atcspi200_priv {
 	size_t		tran_len;
 	void		*din;
 	const void	*dout;
+	bool		data_merge;
 	unsigned int	max_transfer_length;
 };
 
@@ -149,6 +151,10 @@ static int atcspi200_hw_claim_bus(struct atcspi200_priv *priv)
 	priv->cmd_len = 0;
 	format = priv->mode | FIELD_PREP(DATA_LEN_MASK, 8 - 1) |
 		 FIELD_PREP(ADDR_LEN_MASK, 3 - 1);
+	if (IS_ENABLED(CONFIG_ATCSPI200_SPI_DATA_MERGE)) {
+		format |= DATA_MERGE;
+		priv->data_merge = true;
+	}
 	atcspi200_write(priv, ATCSPI200_REG_FORMAT, format);
 	atcspi200_hw_set_speed(priv);
 
@@ -182,20 +188,27 @@ static int atcspi200_hw_stop(struct atcspi200_priv *priv)
 				 SPIBSY, false, SPI_TIMEOUT_MS, false);
 }
 
-static void atcspi200_tx_byte(struct atcspi200_priv *priv, const void *dout)
+static void atcspi200_tx(struct atcspi200_priv *priv, const void *dout)
 {
-	atcspi200_write(priv, ATCSPI200_REG_DATA, *(u8 *)dout);
+	if (priv->data_merge)
+		atcspi200_write(priv, ATCSPI200_REG_DATA, *(u32 *)dout);
+	else
+		atcspi200_write(priv, ATCSPI200_REG_DATA, *(u8 *)dout);
 }
 
-static void atcspi200_rx_byte(struct atcspi200_priv *priv, void *din)
+static void atcspi200_rx(struct atcspi200_priv *priv, void *din)
 {
-	*(u8 *)din = (u8)atcspi200_read(priv, ATCSPI200_REG_DATA);
+	if (priv->data_merge)
+		*(u32 *)din = atcspi200_read(priv, ATCSPI200_REG_DATA);
+	else
+		*(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 step = priv->data_merge ? 4 : 1;
 	unsigned int tx_remain = dout ? len : 0;
 	unsigned int rx_remain = din ? len : 0;
 	unsigned long start = get_timer(0);
@@ -205,15 +218,15 @@ static void atcspi200_pio_transfer(struct atcspi200_priv *priv,
 		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--;
+			atcspi200_tx(priv, dout);
+			dout = (const u8 *)dout + step;
+			tx_remain -= step;
 		}
 
 		if (rx_remain && (status & RXFVE_MASK)) {
-			atcspi200_rx_byte(priv, din);
-			din = (u8 *)din + 1;
-			rx_remain--;
+			atcspi200_rx(priv, din);
+			din = (u8 *)din + step;
+			rx_remain -= step;
 		}
 	}
 
@@ -232,6 +245,19 @@ static int atcspi200_hw_xfer(struct atcspi200_priv *priv,
 	unsigned long data_len = bitlen / 8;
 	int ret = 0;
 
+	if (IS_ENABLED(CONFIG_ATCSPI200_SPI_DATA_MERGE)) {
+		u32 format = atcspi200_read(priv, ATCSPI200_REG_FORMAT);
+
+		if (data_len % 4 == 0) {
+			format |= DATA_MERGE;
+			priv->data_merge = true;
+		} else {
+			format &= ~DATA_MERGE;
+			priv->data_merge = false;
+		}
+		atcspi200_write(priv, ATCSPI200_REG_FORMAT, format);
+	}
+
 	max_tran_len = priv->max_transfer_length;
 	switch (flags) {
 	case SPI_XFER_BEGIN:
@@ -335,13 +361,25 @@ static int atcspi200_spi_mem_exec_op(struct spi_slave *slave,
 	u32 format;
 	int ret;
 
+	format = atcspi200_read(priv, ATCSPI200_REG_FORMAT);
+
+	/* Dynamically enable/disable data merge based on alignment */
+	if (IS_ENABLED(CONFIG_ATCSPI200_SPI_DATA_MERGE)) {
+		if (op->data.nbytes && (op->data.nbytes % 4 == 0)) {
+			format |= DATA_MERGE;
+			priv->data_merge = true;
+		} else {
+			format &= ~DATA_MERGE;
+			priv->data_merge = false;
+		}
+	}
+
 	/* 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);
 	}
+	atcspi200_write(priv, ATCSPI200_REG_FORMAT, format);
 
 	/* Set up transfer control for this operation */
 	atcspi200_set_transfer_ctl(priv, op);
-- 
2.34.1


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

* [PATCH 7/8] mtd: spi-nor: Add Macronix MX25U quad-mode fixups
  2026-04-17  2:40 [PATCH 5/8] spi: atcspi200: Add spi-mem framework support Leo Yu-Chi Liang
  2026-04-17  2:40 ` [PATCH 6/8] spi: atcspi200: Add data merge mode support Leo Yu-Chi Liang
@ 2026-04-17  2:40 ` 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
  2 siblings, 1 reply; 5+ messages in thread
From: Leo Yu-Chi Liang @ 2026-04-17  2:40 UTC (permalink / raw)
  To: u-boot; +Cc: Tom Rini, Vignesh R, Takahiro Kuwano, Jagan Teki, ycliang

Add quad mode (1-4-4) read and page program fixups for Macronix
MX25U1635E and MX25U25635F SPI NOR flash chips. These chips do not
properly expose their quad capabilities via SFDP, requiring explicit
fixup hooks.

The fixup enables SNOR_HWCAPS_READ_1_4_4 and SNOR_HWCAPS_PP_1_4_4
capabilities with the appropriate opcodes and protocols.

Both chips share the same fixup function since their quad mode
configuration is identical.

Signed-off-by: Leo Yu-Chi Liang <ycliang@andestech.com>
---
 drivers/mtd/spi/spi-nor-core.c | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c
index 937d79af64e..d8a073fef3f 100644
--- a/drivers/mtd/spi/spi-nor-core.c
+++ b/drivers/mtd/spi/spi-nor-core.c
@@ -4248,6 +4248,21 @@ static struct spi_nor_fixups macronix_octal_fixups = {
 	.post_sfdp = macronix_octal_post_sfdp_fixup,
 	.late_init = macronix_octal_late_init,
 };
+
+static void macronix_quad_post_sfdp_fixup(struct spi_nor *nor,
+					  struct spi_nor_flash_parameter *params)
+{
+	params->hwcaps.mask |= SNOR_HWCAPS_READ_1_4_4 | SNOR_HWCAPS_PP_1_4_4;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_4_4],
+				  0, 8, SPINOR_OP_READ_1_4_4,
+				  SNOR_PROTO_1_4_4);
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP_1_4_4],
+				SPINOR_OP_PP_1_4_4, SNOR_PROTO_1_4_4);
+}
+
+static struct spi_nor_fixups macronix_quad_fixups = {
+	.post_sfdp = macronix_quad_post_sfdp_fixup,
+};
 #endif /* CONFIG_SPI_FLASH_MACRONIX */
 
 #if CONFIG_IS_ENABLED(SPI_FLASH_WINBOND)
@@ -4544,9 +4559,13 @@ void spi_nor_set_fixups(struct spi_nor *nor)
 #endif
 
 #if CONFIG_IS_ENABLED(SPI_FLASH_MACRONIX)
-	if (JEDEC_MFR(nor->info) == SNOR_MFR_MACRONIX &&
-	    nor->info->flags & SPI_NOR_OCTAL_DTR_READ)
-		nor->fixups = &macronix_octal_fixups;
+	if (JEDEC_MFR(nor->info) == SNOR_MFR_MACRONIX) {
+		if (nor->info->flags & SPI_NOR_OCTAL_DTR_READ)
+			nor->fixups = &macronix_octal_fixups;
+		else if (!strcmp(nor->info->name, "mx25u1635e") ||
+			 !strcmp(nor->info->name, "mx25u25635f"))
+			nor->fixups = &macronix_quad_fixups;
+	}
 #endif /* SPI_FLASH_MACRONIX */
 
 #if CONFIG_IS_ENABLED(SPI_FLASH_WINBOND)
-- 
2.34.1


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

* [PATCH 8/8] configs: ae350: Enable ATCSPI200 data merge mode
  2026-04-17  2:40 [PATCH 5/8] spi: atcspi200: Add spi-mem framework support Leo Yu-Chi Liang
  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  2:40 ` Leo Yu-Chi Liang
  2 siblings, 0 replies; 5+ messages in thread
From: Leo Yu-Chi Liang @ 2026-04-17  2:40 UTC (permalink / raw)
  To: u-boot; +Cc: Tom Rini, Vignesh R, Takahiro Kuwano, Jagan Teki, ycliang

Enable CONFIG_ATCSPI200_SPI_DATA_MERGE for all AE350 board variants
to improve SPI throughput by using 4-byte FIFO access for aligned
transfers.

Signed-off-by: Leo Yu-Chi Liang <ycliang@andestech.com>
---
 configs/ae350_rv32_defconfig            | 1 +
 configs/ae350_rv32_falcon_defconfig     | 1 +
 configs/ae350_rv32_falcon_xip_defconfig | 1 +
 configs/ae350_rv32_spl_defconfig        | 1 +
 configs/ae350_rv32_spl_xip_defconfig    | 1 +
 configs/ae350_rv32_xip_defconfig        | 1 +
 configs/ae350_rv64_defconfig            | 1 +
 configs/ae350_rv64_falcon_defconfig     | 1 +
 configs/ae350_rv64_falcon_xip_defconfig | 1 +
 configs/ae350_rv64_spl_defconfig        | 1 +
 configs/ae350_rv64_spl_xip_defconfig    | 1 +
 configs/ae350_rv64_xip_defconfig        | 1 +
 12 files changed, 12 insertions(+)

diff --git a/configs/ae350_rv32_defconfig b/configs/ae350_rv32_defconfig
index d8716d2b8f2..e58e9b1bde3 100644
--- a/configs/ae350_rv32_defconfig
+++ b/configs/ae350_rv32_defconfig
@@ -48,6 +48,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
diff --git a/configs/ae350_rv32_falcon_defconfig b/configs/ae350_rv32_falcon_defconfig
index 659be8287ba..7f2e4270cce 100644
--- a/configs/ae350_rv32_falcon_defconfig
+++ b/configs/ae350_rv32_falcon_defconfig
@@ -56,4 +56,5 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_BINMAN_FDT is not set
diff --git a/configs/ae350_rv32_falcon_xip_defconfig b/configs/ae350_rv32_falcon_xip_defconfig
index 93b80ef789d..a6ad9a90873 100644
--- a/configs/ae350_rv32_falcon_xip_defconfig
+++ b/configs/ae350_rv32_falcon_xip_defconfig
@@ -58,4 +58,5 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_BINMAN_FDT is not set
diff --git a/configs/ae350_rv32_spl_defconfig b/configs/ae350_rv32_spl_defconfig
index 4b0f52ff42b..9f56d0c4e7c 100644
--- a/configs/ae350_rv32_spl_defconfig
+++ b/configs/ae350_rv32_spl_defconfig
@@ -56,6 +56,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
diff --git a/configs/ae350_rv32_spl_xip_defconfig b/configs/ae350_rv32_spl_xip_defconfig
index f076f36c73c..375d6f6cc5c 100644
--- a/configs/ae350_rv32_spl_xip_defconfig
+++ b/configs/ae350_rv32_spl_xip_defconfig
@@ -57,6 +57,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
diff --git a/configs/ae350_rv32_xip_defconfig b/configs/ae350_rv32_xip_defconfig
index 4eb59d31bcc..c3fbdb59bc1 100644
--- a/configs/ae350_rv32_xip_defconfig
+++ b/configs/ae350_rv32_xip_defconfig
@@ -49,6 +49,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
diff --git a/configs/ae350_rv64_defconfig b/configs/ae350_rv64_defconfig
index ca3acabdbf0..b24c64db6d8 100644
--- a/configs/ae350_rv64_defconfig
+++ b/configs/ae350_rv64_defconfig
@@ -48,6 +48,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
diff --git a/configs/ae350_rv64_falcon_defconfig b/configs/ae350_rv64_falcon_defconfig
index c7c44671419..e1b95a34777 100644
--- a/configs/ae350_rv64_falcon_defconfig
+++ b/configs/ae350_rv64_falcon_defconfig
@@ -56,4 +56,5 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_BINMAN_FDT is not set
diff --git a/configs/ae350_rv64_falcon_xip_defconfig b/configs/ae350_rv64_falcon_xip_defconfig
index ae9c7cfd933..044b3c84c52 100644
--- a/configs/ae350_rv64_falcon_xip_defconfig
+++ b/configs/ae350_rv64_falcon_xip_defconfig
@@ -58,4 +58,5 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_BINMAN_FDT is not set
diff --git a/configs/ae350_rv64_spl_defconfig b/configs/ae350_rv64_spl_defconfig
index af000ca58ce..0fa0b1cb0d5 100644
--- a/configs/ae350_rv64_spl_defconfig
+++ b/configs/ae350_rv64_spl_defconfig
@@ -56,6 +56,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
diff --git a/configs/ae350_rv64_spl_xip_defconfig b/configs/ae350_rv64_spl_xip_defconfig
index 8c6e2773723..03862ce4ba2 100644
--- a/configs/ae350_rv64_spl_xip_defconfig
+++ b/configs/ae350_rv64_spl_xip_defconfig
@@ -57,6 +57,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
diff --git a/configs/ae350_rv64_xip_defconfig b/configs/ae350_rv64_xip_defconfig
index 617940c830f..01e53998c87 100644
--- a/configs/ae350_rv64_xip_defconfig
+++ b/configs/ae350_rv64_xip_defconfig
@@ -49,6 +49,7 @@ CONFIG_FTMAC100=y
 CONFIG_SYS_NS16550=y
 CONFIG_SPI=y
 CONFIG_ATCSPI200_SPI=y
+CONFIG_ATCSPI200_SPI_DATA_MERGE=y
 # CONFIG_WATCHDOG_AUTOSTART is not set
 CONFIG_WDT=y
 CONFIG_WDT_ATCWDT200=y
-- 
2.34.1


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

* RE: [PATCH 7/8] mtd: spi-nor: Add Macronix MX25U quad-mode fixups
  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
  0 siblings, 0 replies; 5+ messages in thread
From: Takahiro.Kuwano @ 2026-04-17  6:56 UTC (permalink / raw)
  To: ycliang, u-boot; +Cc: trini, vigneshr, jagan

Hi,

SPI-NOR subsystem needs cleanup [1] and we should refrain from patching on top
of current code.

It would be really helpful and appreciated if you can test your patch on top
of the series [2] and share the result. The series intends to isolate the
logic specific to AMD platforms. We need to identify the problems if any in
other platforms.

[1] https://lore.kernel.org/u-boot/20260213164607.GI2747538@bill-the-cat/#t
[2] https://patchwork.ozlabs.org/project/uboot/cover/20251119-amd-cleanup-v1-0-c555bd85e6e4@infineon.com/

Best Regards,
Takahiro Kuwano <takahiro.kuwano@infineon.com>


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

end of thread, other threads:[~2026-04-17 12:47 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-17  2:40 [PATCH 5/8] spi: atcspi200: Add spi-mem framework support Leo Yu-Chi Liang
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox