[PATCH v1 04/12] spi: airoha-snfi: add EN7528 SPI-NAND controller support

AK Sharma <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Extend the Airoha SNFI driver for the serial-NAND controller on the
EN7528, including the two-plane column handling required by the
MT29F2G01 SPI-NAND.

Signed-off-by: AK Sharma <[email protected]>
---
 drivers/spi/Kconfig           |   3 +-
 drivers/spi/airoha_snfi_spi.c | 409 ++++++++++++++++++++++++++++------
 2 files changed, 337 insertions(+), 75 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index cfbedd64..83a79bee 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -62,7 +62,8 @@ config ADI_SPI3
 
 config AIROHA_SNFI_SPI
 	bool "Airoha SPI memory controller driver"
-	depends on ARCH_AIROHA
+	depends on ARCH_AIROHA || ARCH_EN75XX
+	select REGMAP
 	select SPI_MEM
 	help
 	  Enable the Airoha SPI memory controller driver. This driver is
diff --git a/drivers/spi/airoha_snfi_spi.c b/drivers/spi/airoha_snfi_spi.c
index 769ec956..738a805d 100644
--- a/drivers/spi/airoha_snfi_spi.c
+++ b/drivers/spi/airoha_snfi_spi.c
@@ -9,6 +9,9 @@
  */
 
 #include <asm/unaligned.h>
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
+#include <asm/arch/scu-regmap.h>
+#endif
 #include <clk.h>
 #include <dm.h>
 #include <dm/device_compat.h>
@@ -16,11 +19,34 @@
 #include <linux/bitfield.h>
 #include <linux/dma-mapping.h>
 #include <linux/mtd/spinand.h>
+#include <linux/sizes.h>
 #include <linux/time.h>
 #include <regmap.h>
 #include <spi.h>
 #include <spi-mem.h>
 
+#if IS_ENABLED(CONFIG_ARCH_EN75XX)
+#include <asm/addrspace.h>
+/*
+ * EN7528 (MIPS 1004Kc) DMA coherency.
+ *
+ * This SoC inherits its cache setup from the vendor bootbase and U-Boot runs
+ * with CONFIG_MIPS_CACHE_SETUP disabled, where the dcache flush/invalidate
+ * primitives are not reliable.  dma_map_single() therefore cannot make the
+ * shared buffer coherent: a multi-page read returns the same stale cache line
+ * for every page.  Use the uncached KSEG1 alias for all CPU accesses to the
+ * DMA buffer and give the controller the physical address instead.
+ * (Same remedy as the airoha_eth ring/frame buffers on this SoC.)
+ */
+#define snfi_uncached(p)	((void *)CKSEG1ADDR((unsigned long)(p)))
+#define snfi_dma_addr(p, l, d)	((dma_addr_t)CPHYSADDR((unsigned long)(p)))
+#define snfi_dma_unmap(a, l, d)	do { } while (0)
+#else
+#define snfi_uncached(p)	(p)
+#define snfi_dma_addr(p, l, d)	dma_map_single((p), (l), (d))
+#define snfi_dma_unmap(a, l, d)	dma_unmap_single((a), (l), (d))
+#endif
+
 /* SPI */
 #define REG_SPI_CTRL_READ_MODE			0x0000
 #define REG_SPI_CTRL_READ_IDLE_EN		0x0004
@@ -80,12 +106,19 @@
 
 #define REG_SPI_CTRL_NFI2SPI_EN			0x0130
 #define SPI_CTRL_NFI2SPI_EN			BIT(0)
+#define REG_SCUCLK_BOOT_TRP			0x00b8
+#define SCUCLK_BOOT_TRP_BOOT_FROM_EMMC		BIT(6)
+#define SPI_NFI_SNF_NFI_CNFG_SPI_MODE			BIT(2)
+#define SPI_CTRL_SFC_STRAP_BOOT_FROM_SPI_NAND	BIT(1)
 
 /* NFI2SPI */
 #define REG_SPI_NFI_CNFG			0x0000
 #define SPI_NFI_DMA_MODE			BIT(0)
 #define SPI_NFI_READ_MODE			BIT(1)
 #define SPI_NFI_DMA_BURST_EN			BIT(2)
+#define SPI_NFI_DMA_WR_BYTE_SWAP_EN		BIT(3)
+#define SPI_NFI_DMA_RD_BYTE_SWAP_EN		BIT(4)
+#define SPI_NFI_ECC_DATA_SOURCE_INV_EN		BIT(5)
 #define SPI_NFI_HW_ECC_EN			BIT(8)
 #define SPI_NFI_AUTO_FDM_EN			BIT(9)
 #define SPI_NFI_OPMODE				GENMASK(14, 12)
@@ -93,6 +126,8 @@
 #define REG_SPI_NFI_PAGEFMT			0x0004
 #define SPI_NFI_PAGE_SIZE			GENMASK(1, 0)
 #define SPI_NFI_SPARE_SIZE			GENMASK(5, 4)
+#define SPI_NFI_FDM_NUM				GENMASK(11, 8)
+#define SPI_NFI_FDM_ECC_NUM			GENMASK(15, 12)
 
 #define REG_SPI_NFI_CON				0x0008
 #define SPI_NFI_FIFO_FLUSH			BIT(0)
@@ -213,15 +248,89 @@ enum airoha_snand_cs {
 	SPI_CHIP_SEL_LOW,
 };
 
+enum airoha_snand_bootstrap {
+	AIROHA_SNAND_BOOTSTRAP_UNKNOWN,
+	AIROHA_SNAND_BOOTSTRAP_NAND,
+	AIROHA_SNAND_BOOTSTRAP_NOR,
+	AIROHA_SNAND_BOOTSTRAP_EMMC,
+};
+
 struct airoha_snand_priv {
+	struct udevice *dev;
 	struct regmap *regmap_ctrl;
 	struct regmap *regmap_nfi;
 	struct clk *spi_clk;
 
 	u8 *txrx_buf;
+	bool en751221;
 	int dma;
 };
 
+static const char *airoha_snand_bootstrap_name(enum airoha_snand_bootstrap type)
+{
+	switch (type) {
+	case AIROHA_SNAND_BOOTSTRAP_NAND:
+		return "NAND";
+	case AIROHA_SNAND_BOOTSTRAP_NOR:
+		return "NOR";
+	case AIROHA_SNAND_BOOTSTRAP_EMMC:
+		return "eMMC";
+	case AIROHA_SNAND_BOOTSTRAP_UNKNOWN:
+	default:
+		return "unknown";
+	}
+}
+
+static enum airoha_snand_bootstrap
+airoha_snand_get_bootstrap(struct airoha_snand_priv *priv, u32 *boot_trp,
+				   u32 *snf_nfi_cnfg, u32 *sfc_strap)
+{
+	int err;
+
+	*boot_trp = 0;
+	*snf_nfi_cnfg = 0;
+	*sfc_strap = 0;
+
+	if (!priv->en751221) {
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
+		struct regmap *regmap_scu = airoha_get_scu_regmap();
+
+		if (!regmap_scu)
+			return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+		err = regmap_read(regmap_scu, REG_SCUCLK_BOOT_TRP, boot_trp);
+		if (err)
+			return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+		if (*boot_trp & SCUCLK_BOOT_TRP_BOOT_FROM_EMMC)
+			return AIROHA_SNAND_BOOTSTRAP_EMMC;
+#else
+		return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+#endif
+	}
+
+	if (priv->regmap_nfi) {
+		err = regmap_read(priv->regmap_nfi,
+				  REG_SPI_NFI_SNF_NFI_CNFG,
+				  snf_nfi_cnfg);
+		if (err)
+			return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+		/* SNF_NFI_CNFG bit 2 selects SPI-NFI. If it is clear, the
+		 * boot source is still NAND through the parallel NAND path.
+		 */
+		if (!(*snf_nfi_cnfg & SPI_NFI_SNF_NFI_CNFG_SPI_MODE))
+			return AIROHA_SNAND_BOOTSTRAP_NAND;
+	}
+
+	err = regmap_read(priv->regmap_ctrl, REG_SPI_CTRL_SFC_STRAP, sfc_strap);
+	if (err)
+		return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+	return (*sfc_strap & SPI_CTRL_SFC_STRAP_BOOT_FROM_SPI_NAND) ?
+		AIROHA_SNAND_BOOTSTRAP_NAND : AIROHA_SNAND_BOOTSTRAP_NOR;
+}
+
 static int airoha_snand_set_fifo_op(struct airoha_snand_priv *priv,
 				    u8 op_cmd, int op_len)
 {
@@ -254,7 +363,19 @@ static int airoha_snand_set_fifo_op(struct airoha_snand_priv *priv,
 
 static int airoha_snand_set_cs(struct airoha_snand_priv *priv, u8 cs)
 {
-	return airoha_snand_set_fifo_op(priv, cs, sizeof(cs));
+	int count = priv->en751221 ? 2 : 1;
+	int err;
+
+	/* EN751221 sporadically drops writes unless the CS operation is sent
+	 * twice. This mirrors the quirk used by the Linux EcoNet port.
+	 */
+	while (count--) {
+		err = airoha_snand_set_fifo_op(priv, cs, sizeof(cs));
+		if (err)
+			return err;
+	}
+
+	return 0;
 }
 
 static int airoha_snand_write_data_to_fifo(struct airoha_snand_priv *priv,
@@ -367,7 +488,7 @@ static int airoha_snand_set_mode(struct airoha_snand_priv *priv,
 	case SPI_MODE_DMA:
 		err = regmap_write(priv->regmap_ctrl,
 				   REG_SPI_CTRL_NFI2SPI_EN,
-				   SPI_CTRL_MANUAL_EN);
+				   SPI_CTRL_NFI2SPI_EN);
 		if (err < 0)
 			return err;
 
@@ -465,17 +586,81 @@ static int airoha_snand_read_data(struct airoha_snand_priv *priv,
 	return 0;
 }
 
+static int airoha_snand_nfi_reset(struct airoha_snand_priv *priv)
+{
+	/* Same reset value used by the vendor driver: reset the NFI state
+	 * machine and flush its FIFO before a DMA transaction.
+	 */
+	return regmap_write(priv->regmap_nfi, REG_SPI_NFI_CON,
+			    SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+}
+
+static int airoha_snand_nfi_clear_intr(struct airoha_snand_priv *priv)
+{
+	/* NFI interrupt status bits are write-one-to-clear on this block.
+	 * Clear stale completion state before arming a new DMA transaction so
+	 * polling cannot succeed on an old AHB_DONE value.
+	 */
+	return regmap_write(priv->regmap_nfi, REG_SPI_NFI_INTR,
+			    SPI_NFI_ALL_IRQ_EN);
+}
+
+static int airoha_snand_nfi_clear_done(struct airoha_snand_priv *priv)
+{
+	/* READ_FROM_CACHE_DONE and LOAD_TO_CACHE_DONE are also sticky W1C bits.
+	 * Clear them before DMA so polling observes the current transaction only.
+	 */
+	return regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
+			    SPI_NFI_READ_FROM_CACHE_DONE |
+			    SPI_NFI_LOAD_TO_CACHE_DONE);
+}
+
 static int airoha_snand_nfi_init(struct airoha_snand_priv *priv)
 {
 	int err;
 
-	/* switch to SNFI mode */
+	/* Switch to SNFI mode. */
 	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_NFI_CNFG,
 			   SPI_NFI_SPI_MODE);
 	if (err)
 		return err;
 
-	/* Enable DMA */
+	err = airoha_snand_nfi_reset(priv);
+	if (err)
+		return err;
+
+	err = airoha_snand_nfi_clear_intr(priv);
+	if (err)
+		return err;
+
+	err = airoha_snand_nfi_clear_done(priv);
+	if (err)
+		return err;
+
+	/* Mirror the safe vendor configuration used when the SPI NAND uses its
+	 * internal ECC: disable controller ECC/FDM and byte-swap helpers.  The
+	 * SPI NAND core still controls on-die ECC, QE, die-select and status
+	 * handling through normal SPI-MEM commands.
+	 */
+	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
+				 SPI_NFI_DMA_WR_BYTE_SWAP_EN |
+				 SPI_NFI_DMA_RD_BYTE_SWAP_EN |
+				 SPI_NFI_ECC_DATA_SOURCE_INV_EN |
+				 SPI_NFI_HW_ECC_EN |
+				 SPI_NFI_AUTO_FDM_EN,
+				 0);
+	if (err)
+		return err;
+
+	/* FDM is disabled above, but clear its page-format fields as well so a
+	 * previous boot stage cannot leave stale vendor ECC layout behind.
+	 */
+	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_PAGEFMT,
+				 SPI_NFI_FDM_NUM | SPI_NFI_FDM_ECC_NUM, 0);
+	if (err)
+		return err;
+
+	/* Enable only the DMA completion interrupt source used by polling. */
 	return regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_INTR_EN,
 				  SPI_NFI_ALL_IRQ_EN, SPI_NFI_AHB_DONE_EN);
 }
@@ -551,6 +736,54 @@ static int airoha_snand_dirmap_create(struct spi_mem_dirmap_desc *desc)
 	return 0;
 }
 
+
+static ssize_t airoha_snand_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
+					    u64 offs, size_t len, const void *buf)
+{
+	struct spi_mem_op op = desc->info.op_tmpl;
+	int err;
+
+	op.addr.val = desc->info.offset + offs;
+	op.data.buf.out = buf;
+	op.data.nbytes = len;
+
+	err = spi_mem_exec_op(desc->slave, &op);
+	if (err)
+		return err;
+
+	return op.data.nbytes;
+}
+
+static bool airoha_snand_dma_write_ok(struct spi_mem_dirmap_desc *desc,
+					      u64 offs, size_t len)
+{
+	/*
+	 * The NFI DMA path programs a contiguous cache window.  Do not emulate
+	 * unaligned/short writes by padding the beginning or the end with 0xff:
+	 * PROGRAM LOAD/RANDOM LOAD operates on the SPI NAND cache, so padding can
+	 * poison bytes that the caller did not ask us to touch.  This is especially
+	 * dangerous for boot blocks updated in small chunks.
+	 *
+	 * Keep DMA only for exact transfers that the controller can issue without
+	 * synthetic padding.  Everything else falls back to the manual SPI-MEM path,
+	 * which sends the exact column address and exact byte count requested by the
+	 * SPI NAND core.
+	 */
+	if (!len)
+		return false;
+
+	if (!IS_ALIGNED(desc->info.offset + offs, 16))
+		return false;
+
+	if (!IS_ALIGNED(len, 64))
+		return false;
+
+	if (len > SPI_NAND_CACHE_SIZE)
+		return false;
+
+	return true;
+}
+
 static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 					u64 offs, size_t len, void *buf)
 {
@@ -609,9 +842,15 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	if (err < 0)
 		return err;
 
-	/* NFI reset */
-	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_CON,
-			   SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+	err = airoha_snand_nfi_reset(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_intr(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_done(priv);
 	if (err)
 		goto error_dma_mode_off;
 
@@ -653,7 +892,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_mode_off;
 
-	dma_addr = dma_map_single(txrx_buf, SPI_NAND_CACHE_SIZE,
+	dma_addr = snfi_dma_addr(txrx_buf, SPI_NAND_CACHE_SIZE,
 				  DMA_FROM_DEVICE);
 
 	/* set dma addr */
@@ -721,9 +960,8 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	 * SPI_NFI_READ_FROM_CACHE_DONE bit must be written at the end
 	 * of dirmap_read operation even if it is already set.
 	 */
-	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
-				 SPI_NFI_READ_FROM_CACHE_DONE,
-				 SPI_NFI_READ_FROM_CACHE_DONE);
+	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
+			   SPI_NFI_READ_FROM_CACHE_DONE);
 	if (err)
 		goto error_dma_unmap;
 
@@ -736,7 +974,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	/* DMA read need delay for data ready from controller to DRAM */
 	udelay(1);
 
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
 
 	err = airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	if (err < 0)
@@ -747,7 +985,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	return len;
 
 error_dma_unmap:
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
 error_dma_mode_off:
 	airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	return err;
@@ -765,22 +1003,10 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	size_t bytes;
 	int err;
 
-	if (!priv->dma) {
-		/* simplified version of spi_mem_no_dirmap_write() */
-		struct spi_mem_op op = desc->info.op_tmpl;
-
-		op.addr.val = desc->info.offset + offs;
-		op.data.buf.out = buf;
-		op.data.nbytes = len;
-		err = spi_mem_exec_op(desc->slave, &op);
-		if (err)
-			return err;
+	if (!priv->dma || !airoha_snand_dma_write_ok(desc, offs, len))
+		return airoha_snand_no_dirmap_write(desc, offs, len, buf);
 
-		return op.data.nbytes;
-	}
-
-	/* minimum oob size is 64 */
-	bytes = round_up(offs + len, 64);
+	bytes = len;
 
 	opcode = desc->info.op_tmpl.cmd.opcode;
 	switch (opcode) {
@@ -797,19 +1023,21 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 		return -EOPNOTSUPP;
 	}
 
-	if (offs > 0)
-		memset(txrx_buf, 0xff, offs);
-	memcpy(txrx_buf + offs, buf, len);
-	if (bytes > offs + len)
-		memset(txrx_buf + offs + len, 0xff, bytes - offs - len);
+	memcpy(txrx_buf, buf, len);
 
 	err = airoha_snand_set_mode(priv, SPI_MODE_DMA);
 	if (err < 0)
 		return err;
 
-	/* NFI reset */
-	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_CON,
-			   SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+	err = airoha_snand_nfi_reset(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_intr(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_done(priv);
 	if (err)
 		goto error_dma_mode_off;
 
@@ -851,7 +1079,7 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_mode_off;
 
-	dma_addr = dma_map_single(txrx_buf, SPI_NAND_CACHE_SIZE,
+	dma_addr = snfi_dma_addr(txrx_buf, SPI_NAND_CACHE_SIZE,
 				  DMA_TO_DEVICE);
 
 	/* set dma addr */
@@ -887,9 +1115,9 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_unmap;
 
-	/* set write addr: zero page offset + descriptor write offset */
+	/* set write addr: descriptor base column + requested dirmap offset */
 	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_PG_CTL2,
-			   desc->info.offset);
+			   desc->info.offset + offs);
 	if (err)
 		goto error_dma_unmap;
 
@@ -908,6 +1136,9 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_unmap;
 
+	/* Vendor code gives the DMA engine a short settle time after WR_TRIG. */
+	udelay(1);
+
 	err = regmap_read_poll_timeout(priv->regmap_nfi, REG_SPI_NFI_INTR,
 				       val, (val & SPI_NFI_AHB_DONE), 0,
 				       1 * MSEC_PER_SEC);
@@ -925,13 +1156,12 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	 * SPI_NFI_LOAD_TO_CACHE_DONE bit must be written at the end
 	 * of dirmap_write operation even if it is already set.
 	 */
-	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
-				 SPI_NFI_LOAD_TO_CACHE_DONE,
-				 SPI_NFI_LOAD_TO_CACHE_DONE);
+	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
+			   SPI_NFI_LOAD_TO_CACHE_DONE);
 	if (err)
 		goto error_dma_unmap;
 
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
 
 	err = airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	if (err < 0)
@@ -940,7 +1170,7 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	return len;
 
 error_dma_unmap:
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
 error_dma_mode_off:
 	airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	return err;
@@ -986,7 +1216,7 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 	err = airoha_snand_write_data(priv, data, op_len,
 				      op->cmd.buswidth);
 	if (err)
-		return err;
+		goto out_cs_high;
 
 	/* addr part */
 	data += op_len;
@@ -994,7 +1224,7 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 		err = airoha_snand_write_data(priv, data, addr_len,
 					      op->addr.buswidth);
 		if (err)
-			return err;
+			goto out_cs_high;
 	}
 
 	/* dummy */
@@ -1003,7 +1233,7 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 		err = airoha_snand_write_data(priv, data, dummy_len,
 					      op->dummy.buswidth);
 		if (err)
-			return err;
+			goto out_cs_high;
 	}
 
 	/* data */
@@ -1017,23 +1247,36 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 						      op->data.nbytes,
 						      op->data.buswidth);
 		if (err)
-			return err;
+			goto out_cs_high;
 	}
 
-	return airoha_snand_set_cs(priv, SPI_CHIP_SEL_HIGH);
+out_cs_high:
+	/* Always release CS.  Leaving CS asserted after a FIFO timeout can lock
+	 * the shared SPI bus, which is visible on boards using NOR + NAND.
+	 */
+	if (airoha_snand_set_cs(priv, SPI_CHIP_SEL_HIGH) && !err)
+		err = -EIO;
+
+	return err;
 }
 
 static int airoha_snand_probe(struct udevice *dev)
 {
 	struct airoha_snand_priv *priv = dev_get_priv(dev);
+	enum airoha_snand_bootstrap type;
+	u32 boot_trp, snf_nfi_cnfg, sfc_strap;
 	int ret;
-	u32 sfc_strap;
 
-	priv->txrx_buf = memalign(ARCH_DMA_MINALIGN, SPI_NAND_CACHE_SIZE);
+	priv->txrx_buf = snfi_uncached(memalign(ARCH_DMA_MINALIGN,
+						  SPI_NAND_CACHE_SIZE));
 	if (!priv->txrx_buf) {
-		dev_err(dev, "failed to alloacate memory for dirmap\n");
+		dev_err(dev, "failed to allocate memory for dirmap\n");
 		return -ENOMEM;
 	}
+	priv->dev = dev;
+	priv->en751221 = of_machine_is_compatible("econet,en751221") ||
+			   of_machine_is_compatible("econet,en7512") ||
+			   of_machine_is_compatible("econet,en7521");
 
 	ret = regmap_init_mem_index(dev_ofnode(dev), &priv->regmap_ctrl, 0);
 	if (ret) {
@@ -1043,36 +1286,48 @@ static int airoha_snand_probe(struct udevice *dev)
 
 	ret = regmap_init_mem_index(dev_ofnode(dev), &priv->regmap_nfi, 1);
 	if (ret) {
-		dev_err(dev, "failed to init spi nfi regmap\n");
-		return ret;
+		if (!priv->en751221) {
+			dev_err(dev, "failed to init spi nfi regmap\n");
+			return ret;
+		}
+
+		/* EN751221 has no separate NFI/SNFI DMA register bank. */
+		priv->regmap_nfi = NULL;
 	}
 
-	priv->spi_clk = devm_clk_get(dev, "spi");
+	priv->spi_clk = devm_clk_get_optional(dev, "spi");
 	if (IS_ERR(priv->spi_clk)) {
-		dev_err(dev, "unable to get spi clk\n");
-		return PTR_ERR(priv->regmap_ctrl);
-	}
-	clk_enable(priv->spi_clk);
+		ret = PTR_ERR(priv->spi_clk);
+		if (ret != -ENOSYS) {
+			dev_err(dev, "unable to get spi clk\n");
+			return ret;
+		}
 
-	priv->dma = 1;
-	if (device_is_compatible(dev, "airoha,en7523-snand")){
-		ret = regmap_read(priv->regmap_ctrl, REG_SPI_CTRL_SFC_STRAP, &sfc_strap);
+		/* EN751221 does not require the U-Boot clock framework. */
+		priv->spi_clk = NULL;
+	}
+	if (priv->spi_clk) {
+		ret = clk_enable(priv->spi_clk);
 		if (ret)
 			return ret;
-
-		if (!(sfc_strap & 0x04)) {
-			priv->dma = 0;
-			printf("\n"
-				"=== WARNING ======================================================\n"
-				"Detected booting in RESERVED mode (UART_TXD was short to GND).\n"
-				"This mode is known for incorrect DMA reading of some flashes.\n"
-				"Usage of DMA for flash operations will be disabled to prevent data\n"
-				"damage. Unplug your serial console and power cycle the board\n"
-				"to boot with full performance.\n"
-				"==================================================================\n\n");
-		}
 	}
 
+	type = airoha_snand_get_bootstrap(priv, &boot_trp, &snf_nfi_cnfg,
+					   &sfc_strap);
+
+	dev_dbg(priv->dev,
+		 "bootstrap: %s (boot_trp=0x%08x, snf_nfi_cnfg=0x%08x, sfc_strap=0x%08x)\n",
+		 airoha_snand_bootstrap_name(type), boot_trp, snf_nfi_cnfg,
+		 sfc_strap);
+
+	/* EN751221 uses only the manual FIFO path. EN7523/AN7581 can use
+	 * the second register bank for SNFI DMA operations.
+	 */
+	priv->dma = !!priv->regmap_nfi;
+
+	if (!priv->regmap_nfi)
+		return 0;
+
 	return airoha_snand_nfi_init(priv);
 }
 
@@ -1081,6 +1336,10 @@ static int airoha_snand_nfi_set_speed(struct udevice *bus, uint speed)
 	struct airoha_snand_priv *priv = dev_get_priv(bus);
 	int ret;
 
+	/* EN751221 leaves the SFC clock configured by the previous stage. */
+	if (!priv->spi_clk)
+		return 0;
+
 	ret = clk_set_rate(priv->spi_clk, speed);
 	if (ret < 0)
 		return ret;
@@ -1108,6 +1367,7 @@ static const struct dm_spi_ops airoha_snfi_spi_ops = {
 };
 
 static const struct udevice_id airoha_snand_ids[] = {
+	{ .compatible = "airoha,en7523-spi" },
 	{ .compatible = "airoha,en7581-snand" },
 	{ }
 };
@@ -1120,3 +1380,4 @@ U_BOOT_DRIVER(airoha_snfi_spi) = {
 	.priv_auto = sizeof(struct airoha_snand_priv),
 	.probe = airoha_snand_probe,
 };
+
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.