All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mario Behn <mario.behn@gmail.com>
To: Mark Brown <broonie@kernel.org>, Michal Simek <michal.simek@amd.com>
Cc: linux-spi@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, Mario Behn <mario.behn@gmail.com>
Subject: [PATCH v1 1/2] spi: zynq-qspi: read flash above 16 MiB via the extended address register
Date: Thu,  5 Nov 2026 21:37:33 +0100	[thread overview]
Message-ID: <20261105203734.979168-2-mario.behn@gmail.com> (raw)
In-Reply-To: <20261105203734.979168-1-mario.behn@gmail.com>


The Zynq QSPI controller sends at most 3 address bytes, and spi-nor needs
4-byte addresses for a flash larger than 16 MiB (W25Q256 on the Pluto+), so
the probe failed with -22 and no /dev/mtd* appeared.

Keep the flash in 3-byte mode and handle a 4-byte-address operation
inside the controller driver: write enable, Write Extended Address
Register with addr >> 24, the operation with the 3-byte opcode and the
low 24 address bits, then the register back to 0, so that U-Boot and the
BootROM find bank 0 after a reset. adjust_op_size keeps one read inside
a 16 MiB bank. EN4B and EX4B are swallowed so that the flash never
leaves 3-byte mode.

Also stop writing the address bytes through xqspi->txbuf into the
spi_mem_op structure; use a local buffer.

This stage is READ ONLY: every operation that does not read data (write
enable, program, erase, status register write) is refused with -EROFS.
Verified on the Pluto+ (W25Q256, ef4019): the md5 of all four partitions
equals the one read on 6.12, including the 30 MiB partition that crosses
the bank boundary.

Signed-off-by: Mario Behn <mario.behn@gmail.com>
---
 drivers/spi/spi-zynq-qspi.c | 152 +++++++++++++++++++++++++++++++++---
 1 file changed, 142 insertions(+), 10 deletions(-)

diff --git a/drivers/spi/spi-zynq-qspi.c b/drivers/spi/spi-zynq-qspi.c
index d762aaf452af..e20019a03964 100644
--- a/drivers/spi/spi-zynq-qspi.c
+++ b/drivers/spi/spi-zynq-qspi.c
@@ -16,6 +16,7 @@
 #include <linux/spi/spi.h>
 #include <linux/workqueue.h>
 #include <linux/spi/spi-mem.h>
+#include <linux/mtd/spi-nor.h>
 
 /* Register offset definitions */
 #define ZYNQ_QSPI_CONFIG_OFFSET		0x00 /* Configuration  Register, RW */
@@ -222,21 +223,69 @@ static void zynq_qspi_init_hw(struct zynq_qspi *xqspi, unsigned int num_cs)
 			ZYNQ_QSPI_ENABLE_ENABLE_MASK);
 }
 
+/*
+ * The controller sends at most 3 address bytes. A flash larger than 16 MiB
+ * is therefore kept in 3-byte mode and reached through the Extended Address
+ * Register (EAR): an operation with a 4-byte address is turned into
+ * WREN + WREAR(addr >> 24) + the 3-byte form of the operation, and the EAR is
+ * set back to 0 afterwards, so that U-Boot and the BootROM find the flash in
+ * bank 0 after a reset (the same scheme as the ADI 6.12 spi-nor code, but in
+ * the controller driver instead of the spi-nor core).
+ */
+#define ZYNQ_QSPI_BANK_SIZE		0x1000000U
+#define ZYNQ_QSPI_OP_WREAR		0xc5	/* Write Extended Address Register */
+
 static bool zynq_qspi_supports_op(struct spi_mem *mem,
 				  const struct spi_mem_op *op)
 {
-	if (!spi_mem_default_supports_op(mem, op))
+	struct spi_mem_op nop = *op;
+
+	/* check the shape of the operation as if the address had 3 bytes */
+	if (nop.addr.nbytes == 4)
+		nop.addr.nbytes = 3;
+	if (!spi_mem_default_supports_op(mem, &nop))
 		return false;
 
-	/*
-	 * The number of address bytes should be equal to or less than 3 bytes.
-	 */
-	if (op->addr.nbytes > 3)
+	if (op->addr.nbytes > 4)
 		return false;
 
 	return true;
 }
 
+static int zynq_qspi_adjust_op_size(struct spi_mem *mem,
+				    struct spi_mem_op *op)
+{
+	if (op->addr.nbytes == 4 && op->data.nbytes) {
+		u32 room = ZYNQ_QSPI_BANK_SIZE - (op->addr.val & (ZYNQ_QSPI_BANK_SIZE - 1));
+
+		/* one operation must not cross a 16 MiB bank */
+		if (op->data.nbytes > room)
+			op->data.nbytes = room;
+	}
+
+	return 0;
+}
+
+/* opcode with a 4-byte address -> the same opcode with a 3-byte address */
+static u8 zynq_qspi_opcode_3b(u8 opcode)
+{
+	switch (opcode) {
+	case SPINOR_OP_READ_4B:		return SPINOR_OP_READ;
+	case SPINOR_OP_READ_FAST_4B:	return SPINOR_OP_READ_FAST;
+	case SPINOR_OP_READ_1_1_2_4B:	return SPINOR_OP_READ_1_1_2;
+	case SPINOR_OP_READ_1_2_2_4B:	return SPINOR_OP_READ_1_2_2;
+	case SPINOR_OP_READ_1_1_4_4B:	return SPINOR_OP_READ_1_1_4;
+	case SPINOR_OP_READ_1_4_4_4B:	return SPINOR_OP_READ_1_4_4;
+	case SPINOR_OP_PP_4B:		return SPINOR_OP_PP;
+	case SPINOR_OP_PP_1_1_4_4B:	return SPINOR_OP_PP_1_1_4;
+	case SPINOR_OP_PP_1_4_4_4B:	return SPINOR_OP_PP_1_4_4;
+	case SPINOR_OP_BE_4K_4B:	return SPINOR_OP_BE_4K;
+	case SPINOR_OP_BE_32K_4B:	return SPINOR_OP_BE_32K;
+	case SPINOR_OP_SE_4B:		return SPINOR_OP_SE;
+	default:			return opcode;	/* already a 3-byte opcode */
+	}
+}
+
 /**
  * zynq_qspi_rxfifo_op - Read 1..4 bytes from RxFIFO to RX buffer
  * @xqspi:	Pointer to the zynq_qspi structure
@@ -522,12 +571,13 @@ static irqreturn_t zynq_qspi_irq(int irq, void *dev_id)
  *
  * Return: 0 in case of success, a negative error code otherwise.
  */
-static int zynq_qspi_exec_mem_op(struct spi_mem *mem,
-				 const struct spi_mem_op *op)
+static int zynq_qspi_exec_plain_op(struct spi_mem *mem,
+				   const struct spi_mem_op *op)
 {
 	struct zynq_qspi *xqspi = spi_controller_get_devdata(mem->spi->controller);
 	int err = 0, i;
 	u8 *tmpbuf;
+	u8 addrbuf[3];
 
 	zynq_qspi_chipselect(mem->spi, true);
 	zynq_qspi_config_op(xqspi, mem->spi, op);
@@ -547,10 +597,10 @@ static int zynq_qspi_exec_mem_op(struct spi_mem *mem,
 	}
 
 	if (op->addr.nbytes) {
-		for (i = 0; i < op->addr.nbytes; i++) {
-			xqspi->txbuf[i] = op->addr.val >>
+		for (i = 0; i < op->addr.nbytes; i++)
+			addrbuf[i] = op->addr.val >>
 					(8 * (op->addr.nbytes - i - 1));
-		}
+		xqspi->txbuf = addrbuf;
 
 		reinit_completion(&xqspi->data_completion);
 		xqspi->rxbuf = NULL;
@@ -611,9 +661,91 @@ static int zynq_qspi_exec_mem_op(struct spi_mem *mem,
 	return err;
 }
 
+/* Stage 2: the flash is read-only through this driver. */
+static bool zynq_qspi_op_is_read(const struct spi_mem_op *op)
+{
+	if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_IN)
+		return true;
+	return false;
+}
+
+static int zynq_qspi_ear_cmds(struct spi_mem *mem, const struct spi_mem_op *like,
+			      u8 ear, bool wren_after)
+{
+	struct spi_mem_op wren = SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 1),
+					    SPI_MEM_OP_NO_ADDR,
+					    SPI_MEM_OP_NO_DUMMY,
+					    SPI_MEM_OP_NO_DATA);
+	u8 *buf = kmalloc(1, GFP_KERNEL);
+	struct spi_mem_op wrear = SPI_MEM_OP(SPI_MEM_OP_CMD(ZYNQ_QSPI_OP_WREAR, 1),
+					     SPI_MEM_OP_NO_ADDR,
+					     SPI_MEM_OP_NO_DUMMY,
+					     SPI_MEM_OP_DATA_OUT(1, buf, 1));
+	int ret;
+
+	if (!buf)
+		return -ENOMEM;
+	*buf = ear;
+	wren.max_freq = like->max_freq;
+	wrear.max_freq = like->max_freq;
+
+	ret = zynq_qspi_exec_plain_op(mem, &wren);
+	if (!ret)
+		ret = zynq_qspi_exec_plain_op(mem, &wrear);
+	if (!ret && wren_after)
+		ret = zynq_qspi_exec_plain_op(mem, &wren);
+	kfree(buf);
+	return ret;
+}
+
+static int zynq_qspi_exec_mem_op(struct spi_mem *mem,
+				 const struct spi_mem_op *op)
+{
+	struct spi_mem_op nop = *op;
+	u8 ear;
+	int ret, ret2;
+
+	/* Leaving 4-byte mode is not needed: the flash is never put in it. */
+	if (!op->addr.nbytes && !op->data.nbytes && !op->dummy.nbytes &&
+	    (op->cmd.opcode == SPINOR_OP_EN4B || op->cmd.opcode == SPINOR_OP_EX4B))
+		return 0;
+
+	/* read-only guard: only operations that read data from the flash */
+	if (!zynq_qspi_op_is_read(op)) {
+		dev_warn_ratelimited(&mem->spi->dev,
+				     "read-only: op %#x refused\n", op->cmd.opcode);
+		return -EROFS;
+	}
+
+	if (op->addr.nbytes != 4)
+		return zynq_qspi_exec_plain_op(mem, op);
+
+	ear = (op->addr.val >> 24) & 0xff;
+	nop.cmd.opcode = zynq_qspi_opcode_3b(op->cmd.opcode);
+	nop.addr.nbytes = 3;
+	nop.addr.val = op->addr.val & (ZYNQ_QSPI_BANK_SIZE - 1);
+
+	if (ear) {
+		ret = zynq_qspi_ear_cmds(mem, op, ear, false);
+		if (ret)
+			return ret;
+	}
+
+	ret = zynq_qspi_exec_plain_op(mem, &nop);
+
+	if (ear) {
+		ret2 = zynq_qspi_ear_cmds(mem, op, 0, false);
+		if (!ret)
+			ret = ret2;
+	}
+
+	return ret;
+}
+
 static const struct spi_controller_mem_ops zynq_qspi_mem_ops = {
 	.supports_op = zynq_qspi_supports_op,
 	.exec_op = zynq_qspi_exec_mem_op,
+	.adjust_op_size = zynq_qspi_adjust_op_size,
 };
 
 static const struct spi_controller_mem_caps zynq_qspi_mem_caps = {
-- 
2.53.0


  reply	other threads:[~2026-11-05 20:38 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 20:37 [PATCH v1 0/2] spi: zynq-qspi: use a flash larger than 16 MiB Mario Behn
2026-11-05 20:37 ` Mario Behn [this message]
2026-11-05 20:37 ` [PATCH v1 2/2] spi: zynq-qspi: program and erase flash above 16 MiB via the EAR Mario Behn
2026-11-08 14:43 ` [PATCH v1 0/2] spi: zynq-qspi: use a flash larger than 16 MiB Mark Brown
  -- strict thread matches above, loose matches on Subject: below --
2026-11-13  0:46 [PATCH v1 1/2] spi: zynq-qspi: read flash above 16 MiB via the extended address register kernel test robot

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=20261105203734.979168-2-mario.behn@gmail.com \
    --to=mario.behn@gmail.com \
    --cc=broonie@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-spi@vger.kernel.org \
    --cc=michal.simek@amd.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 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.