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

Reply via email to