]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
mtd: spinand: winbond: Enable high-speed modes on w35n0xjw
authorMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 18 Jun 2025 12:14:24 +0000 (14:14 +0200)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 30 Jul 2025 09:32:16 +0000 (11:32 +0200)
w35n0xjw chips can run at up to 166MHz in octal mode, but this is only
possible after programming various VCR registers.

Implement the new ->configure_chip() hook for this purpose.

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
drivers/mtd/nand/spi/core.c
drivers/mtd/nand/spi/winbond.c
include/linux/mtd/spinand.h

index 93d22b412dd3f79b5492a3120ab350d6125fe6a6..b0898990b2a5b0416d2f2cef10bfe178d1885c65 100644 (file)
@@ -360,7 +360,7 @@ static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status)
                engine_conf->status = status;
 }
 
-static int spinand_write_enable_op(struct spinand_device *spinand)
+int spinand_write_enable_op(struct spinand_device *spinand)
 {
        struct spi_mem_op op = SPINAND_WR_EN_DIS_1S_0_0_OP(true);
 
index 18ae6f58a546e184a54ce0ea74b9b3fe03a10f72..53890b1da65cd4b2a956edde48c591c043242399 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/kernel.h>
 #include <linux/mtd/spinand.h>
 #include <linux/units.h>
+#include <linux/delay.h>
 
 #define SPINAND_MFR_WINBOND            0xEF
 
 #define W25N0XJW_SR4                   0xD0
 #define W25N0XJW_SR4_HS                        BIT(2)
 
+#define W35N01JW_VCR_IO_MODE                   0x00
+#define W35N01JW_VCR_IO_MODE_SINGLE_SDR                0xFF
+#define W35N01JW_VCR_IO_MODE_OCTAL_SDR         0xDF
+#define W35N01JW_VCR_IO_MODE_OCTAL_DDR_DS      0xE7
+#define W35N01JW_VCR_IO_MODE_OCTAL_DDR         0xC7
+#define W35N01JW_VCR_DUMMY_CLOCK_REG   0x01
+
 /*
  * "X2" in the core is equivalent to "dual output" in the datasheets,
  * "X4" in the core is equivalent to "quad output" in the datasheets.
  */
 
 static SPINAND_OP_VARIANTS(read_cache_octal_variants,
+               SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 3, NULL, 0, 120 * HZ_PER_MHZ),
                SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 2, NULL, 0, 105 * HZ_PER_MHZ),
+               SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 20, NULL, 0, 0),
                SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 16, NULL, 0, 162 * HZ_PER_MHZ),
+               SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 12, NULL, 0, 124 * HZ_PER_MHZ),
+               SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 8, NULL, 0, 86 * HZ_PER_MHZ),
+               SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 2, NULL, 0, 0),
                SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 1, NULL, 0, 133 * HZ_PER_MHZ),
                SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
                SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
@@ -269,6 +282,79 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand)
        return 0;
 }
 
+static int w35n0xjw_write_vcr(struct spinand_device *spinand, u8 reg, u8 val)
+{
+       struct spi_mem_op op =
+               SPI_MEM_OP(SPI_MEM_OP_CMD(0x81, 1),
+                          SPI_MEM_OP_ADDR(3, reg, 1),
+                          SPI_MEM_OP_NO_DUMMY,
+                          SPI_MEM_OP_DATA_OUT(1, spinand->scratchbuf, 1));
+       int ret;
+
+       *spinand->scratchbuf = val;
+
+       ret = spinand_write_enable_op(spinand);
+       if (ret)
+               return ret;
+
+       ret = spi_mem_exec_op(spinand->spimem, &op);
+       if (ret)
+               return ret;
+
+       /*
+        * Write VCR operation doesn't set the busy bit in SR, which means we
+        * cannot perform a status poll. Minimum time of 50ns is needed to
+        * complete the write.
+        */
+       ndelay(50);
+
+       return 0;
+}
+
+static int w35n0xjw_vcr_cfg(struct spinand_device *spinand)
+{
+       const struct spi_mem_op *op;
+       unsigned int dummy_cycles;
+       bool dtr, single;
+       u8 io_mode;
+       int ret;
+
+       op = spinand->op_templates.read_cache;
+
+       single = (op->cmd.buswidth == 1 && op->addr.buswidth == 1 && op->data.buswidth == 1);
+       dtr = (op->cmd.dtr || op->addr.dtr || op->data.dtr);
+       if (single && !dtr)
+               io_mode = W35N01JW_VCR_IO_MODE_SINGLE_SDR;
+       else if (!single && !dtr)
+               io_mode = W35N01JW_VCR_IO_MODE_OCTAL_SDR;
+       else if (!single && dtr)
+               io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR;
+       else
+               return -EINVAL;
+
+       ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_IO_MODE, io_mode);
+       if (ret)
+               return ret;
+
+       dummy_cycles = ((op->dummy.nbytes * 8) / op->dummy.buswidth) / (op->dummy.dtr ? 2 : 1);
+       switch (dummy_cycles) {
+       case 8:
+       case 12:
+       case 16:
+       case 20:
+       case 24:
+       case 28:
+               break;
+       default:
+               return -EINVAL;
+       }
+       ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_DUMMY_CLOCK_REG, dummy_cycles);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
 static const struct spinand_info winbond_spinand_table[] = {
        /* 512M-bit densities */
        SPINAND_INFO("W25N512GW", /* 1.8V */
@@ -326,7 +412,8 @@ static const struct spinand_info winbond_spinand_table[] = {
                                              &write_cache_octal_variants,
                                              &update_cache_octal_variants),
                     0,
-                    SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL)),
+                    SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
+                    SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)),
        SPINAND_INFO("W35N02JW", /* 1.8V */
                     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22),
                     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1),
@@ -335,7 +422,8 @@ static const struct spinand_info winbond_spinand_table[] = {
                                              &write_cache_octal_variants,
                                              &update_cache_octal_variants),
                     0,
-                    SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL)),
+                    SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
+                    SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)),
        SPINAND_INFO("W35N04JW", /* 1.8V */
                     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
                     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),
@@ -344,7 +432,8 @@ static const struct spinand_info winbond_spinand_table[] = {
                                              &write_cache_octal_variants,
                                              &update_cache_octal_variants),
                     0,
-                    SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL)),
+                    SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
+                    SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)),
        /* 2G-bit densities */
        SPINAND_INFO("W25M02GV", /* 2x1G-bit 3.3V */
                     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21),
index 53c881e41fc7edac7a526fa7eb6134c4f520054d..27a45bdab7ec97a68482ae28723f7ddc0f95980b 100644 (file)
@@ -741,6 +741,7 @@ int spinand_match_and_init(struct spinand_device *spinand,
 int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
 int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val);
 int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val);
+int spinand_write_enable_op(struct spinand_device *spinand);
 int spinand_select_target(struct spinand_device *spinand, unsigned int target);
 
 int spinand_wait(struct spinand_device *spinand, unsigned long initial_delay_us,