Skip to contentSkip to conversation

mtd: spinand: Add Octal DTR SPI (8D-8D-8D) mode support

PATCH In review Apurva Nandan 5 participants 38 messages Jan 1, 2022 – Mar 14, 2022

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

v3 cover mtd: spinand: Add Octal DTR SPI (8D-8D-8D) mode support

Hi,
This series proposes patches for adding the following functionality in SPI NAND core:

- Octal DTR SPI (8D-8D-8D) mode support

- ctrl_ops support

- Winbond W35N01JW SPI NAND chip support

This series has been tested on TI J721e EVM with the Winbond W35N01JW flash with following test utilities:

- nandtest
Test log: https://pastebin.com/raw/PusiLYVQ

- mtd_stresstest
Test log: https://pastebin.com/raw/gBtV2CAf

- UBIFS LTP stress test (NAND_XL_STRESS_DD_RW_UBIFS). Test log: https://pastebin.com/raw/J8WyLsz1

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
Changes in v3:

- Added support for ctrl_ops, data_ops and ctrl_ops_variants.

- Made the implementation generic in terms of spinand protocol and SPI mode changes.

- Added option to disable Octal DTR mode.

- Removed spinand_patch_op(), adjust_op() manufacturer_op.

- Removed Power-on-Reset functionality and its call from mtd_suspend(), now just disable Octal DTR at suspend.

Changes in v2:

- Removed *_ALL_ARGS() macros from spi-mem.h, and redefined DTR macros.

- Renamed spinand_setup_op() to spinand_patch_op(). Reduced one conditional check from this function. Had to keep tweaking in hot-path to avoid complicated implementation "hacks".

- Changes in commit messages and added comments.

- Dropped "Reject 8D-8D-8D op_templates if octal_dtr_enale() is missing in manufacturer_op" patch.

- Reduced PoR reset delay.

- Splitted "mtd: spinand: Add support for Winbond W35N01JW SPI NAND flash" into 3 independent patches.

Apurva Nandan (17):
spi: spi-mem: Add DTR templates for cmd, address, dummy and data phase
mtd: spinand: Define macros for Octal DTR ops
mtd: spinand: Add enum spinand_protocol to indicate current SPI IO
mode
mtd: spinand: Rename 'op_templates' to 'data_ops'
mtd: spinand: Define ctrl_ops for non-page read/write op templates
mtd: spinand: Define default ctrl_ops in the core
mtd: spinand: Switch from op macros usage to 'ctrl_ops' in the core
mtd: spinand: Add support for manufacturer-based ctrl_ops variations
mtd: spinand: Add change_mode() in manufacturer_ops
mtd: spinand: Add pointer to probed flash's spinand_info
mtd: spinand: Allow enabling/disabling Octal DTR mode in the core
mtd: spinand: Add mtd_suspend() to disable Octal DTR mode at suspend
mtd: spinand: winbond: Add support for write volatile configuration
register op
mtd: spinand: winbond: Add octal_dtr_enable/disable() in
manufacturer_ops
mtd: spianand: winbond: Add change_mode() manufacturer_ops
mtd: spinand: winbond: Rename cache op_variants struct variable
mtd: spinand: winbond: Add support for Winbond W35N01JW SPI NAND flash

 drivers/mtd/nand/spi/core.c    | 213 +++++++++++++++++++++++++----
 drivers/mtd/nand/spi/winbond.c | 242 +++++++++++++++++++++++++++++++--
 include/linux/mtd/spinand.h    | 154 +++++++++++++++++++--
 include/linux/spi/spi-mem.h    |  41 ++++++
 4 files changed, 599 insertions(+), 51 deletions(-)
Signature · 1 line
2.25.1
#1 lore raw

PATCH v3 1/17 spi: spi-mem: Add DTR templates for cmd, address, dummy and data phase

Setting dtr field of spi_mem_op is useful when creating templates for DTR ops in spinand.h. Also, 2 bytes cmd phases are required when operating in Octal DTR SPI mode.

Create new templates for dtr mode cmd, address, dummy and data phase in spi_mem_op, which set the dtr field to 1 and also allow passing the nbytes for the cmd phase.

---
 include/linux/spi/spi-mem.h | 41 +++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
include/linux/spi/spi-mem.h +41 -0
index 85e2ff7b840d..682378a9c600 100644
@@ -20,6 +20,14 @@
 		.nbytes = 1,					\
 	}
 
+#define SPI_MEM_OP_CMD_DTR(__nbytes, __opcode, __buswidth)	\
+	{							\
+		.nbytes = __nbytes,				\
+		.opcode = __opcode,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_ADDR(__nbytes, __val, __buswidth)		\
 	{							\
 		.nbytes = __nbytes,				\
@@ -27,6 +35,14 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_ADDR_DTR(__nbytes, __val, __buswidth)	\
+	{							\
+		.nbytes = __nbytes,				\
+		.val = __val,					\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_NO_ADDR	{ }
 
 #define SPI_MEM_OP_DUMMY(__nbytes, __buswidth)			\
@@ -35,6 +51,13 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_DUMMY_DTR(__nbytes, __buswidth)		\
+	{							\
+		.nbytes = __nbytes,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_NO_DUMMY	{ }
 
 #define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth)		\
@@ -45,6 +68,15 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_DATA_IN_DTR(__nbytes, __buf, __buswidth)	\
+	{							\
+		.dir = SPI_MEM_DATA_IN,				\
+		.nbytes = __nbytes,				\
+		.buf.in = __buf,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth)	\
 	{							\
 		.dir = SPI_MEM_DATA_OUT,			\
@@ -53,6 +85,15 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_DATA_OUT_DTR(__nbytes, __buf, __buswidth)	\
+	{							\
+		.dir = SPI_MEM_DATA_OUT,			\
+		.nbytes = __nbytes,				\
+		.buf.out = __buf,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_NO_DATA	{ }
 
 /**
Signature · 1 line
2.25.1
maintainer
#2 lore raw

Re: [PATCH v3 01/17] spi: spi-mem: Add DTR templates for cmd, address, dummy and data phase

Quoted 3 lines · Apurva Nandan

Setting dtr field of spi_mem_op is useful when creating templates for DTR ops in spinand.h. Also, 2 bytes cmd phases are required when operating in Octal DTR SPI mode.

Are people OK with this? It looks fine to me, if nobody objects I can send it in the next merge window so it's there for the MTD bits.

#3 lore raw

Re: [PATCH v3 01/17] spi: spi-mem: Add DTR templates for cmd, address, dummy and data phase

In reply to Apurva Nandan
Quoted 83 lines · On Sat, 1 Jan 2022 13:12:34 +0530 Apurva Nandan <[email protected]> wrote:

Setting dtr field of spi_mem_op is useful when creating templates for DTR ops in spinand.h. Also, 2 bytes cmd phases are required when operating in Octal DTR SPI mode.

Create new templates for dtr mode cmd, address, dummy and data phase in spi_mem_op, which set the dtr field to 1 and also allow passing the nbytes for the cmd phase.

---
 include/linux/spi/spi-mem.h | 41 +++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
include/linux/spi/spi-mem.h +34 -0
index 85e2ff7b840d..682378a9c600 100644
@@ -20,6 +20,14 @@
 		.nbytes = 1,					\
 	}
 
+#define SPI_MEM_OP_CMD_DTR(__nbytes, __opcode, __buswidth)	\
+	{							\
+		.nbytes = __nbytes,				\
+		.opcode = __opcode,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_ADDR(__nbytes, __val, __buswidth)		\
 	{							\
 		.nbytes = __nbytes,				\
@@ -27,6 +35,14 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_ADDR_DTR(__nbytes, __val, __buswidth)	\
+	{							\
+		.nbytes = __nbytes,				\
+		.val = __val,					\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_NO_ADDR	{ }
 
 #define SPI_MEM_OP_DUMMY(__nbytes, __buswidth)			\
@@ -35,6 +51,13 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_DUMMY_DTR(__nbytes, __buswidth)		\
+	{							\
+		.nbytes = __nbytes,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_NO_DUMMY	{ }
 
 #define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth)		\
@@ -45,6 +68,15 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_DATA_IN_DTR(__nbytes, __buf, __buswidth)	\
+	{							\
+		.dir = SPI_MEM_DATA_IN,				\
+		.nbytes = __nbytes,				\
+		.buf.in = __buf,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth)	\
 	{							\
 		.dir = SPI_MEM_DATA_OUT,			\
@@ -53,6 +85,15 @@
 		.buswidth = __buswidth,				\
 	}
 
+#define SPI_MEM_OP_DATA_OUT_DTR(__nbytes, __buf, __buswidth)	\
+	{							\

include/linux/spi/spi-mem.h line 99

Quoted 10 lines
include/linux/spi/spi-mem.h +7 -0
+		.dir = SPI_MEM_DATA_OUT,			\
+		.nbytes = __nbytes,				\
+		.buf.out = __buf,				\
+		.buswidth = __buswidth,				\
+		.dtr = 1,					\
+	}
+
 #define SPI_MEM_OP_NO_DATA	{ }
 
 /**

How about:

include/linux/spi/spi-mem.h +20 -5
index 85e2ff7b840d..9a8d42803026 100644
@@ -13,44 +13,59 @@
 
 #include <linux/spi/spi.h>
 
-#define SPI_MEM_OP_CMD(__opcode, __buswidth)                   \
+#define SPI_MEM_OP_DTR .dtr = 1
+
+#define SPI_MEM_OP_CMD(__opcode, __buswidth, ...)              \
        {                                                       \
                .buswidth = __buswidth,                         \
                .opcode = __opcode,                             \
                .nbytes = 1,                                    \
+               __VA_ARGS__                                     \
        }
 
-#define SPI_MEM_OP_ADDR(__nbytes, __val, __buswidth)           \
+#define SPI_MEM_OP_EXT_CMD(__nbytes, __opcode, __buswidth, ...)        \
+       {                                                       \
+               .buswidth = __buswidth,                         \
+               .opcode = __opcode,                             \
+               .nbytes = __nbytes,                             \
+               __VA_ARGS__                                     \
+       }
+
+#define SPI_MEM_OP_ADDR(__nbytes, __val, __buswidth, ...)      \
        {                                                       \
                .nbytes = __nbytes,                             \
                .val = __val,                                   \
                .buswidth = __buswidth,                         \
+               __VA_ARGS__                                     \
        }
 
 #define SPI_MEM_OP_NO_ADDR     { }
 
-#define SPI_MEM_OP_DUMMY(__nbytes, __buswidth)                 \
+#define SPI_MEM_OP_DUMMY(__nbytes, __buswidth, ...)            \
        {                                                       \
                .nbytes = __nbytes,                             \
                .buswidth = __buswidth,                         \
+               __VA_ARGS__                                     \
        }
 
 #define SPI_MEM_OP_NO_DUMMY    { }
 
-#define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth)                \
+#define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth, ...)   \
        {                                                       \
                .dir = SPI_MEM_DATA_IN,                         \
                .nbytes = __nbytes,                             \
                .buf.in = __buf,                                \
                .buswidth = __buswidth,                         \
+               __VA_ARGS__                                     \
        }
 
-#define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth)       \
+#define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth, ...)  \
        {                                                       \
                .dir = SPI_MEM_DATA_OUT,                        \
                .nbytes = __nbytes,                             \
                .buf.out = __buf,                               \
                .buswidth = __buswidth,                         \
+               __VA_ARGS__                                     \
        }
 
 #define SPI_MEM_OP_NO_DATA     { }

and you get to define a DTR op like that:

	struct spi_mem_op op =
		SPI_MEM_OP(SPI_MEM_OP_EXT_CMD(2, 0x1234, 8, SPI_MEM_OP_DTR),
			   SPI_MEM_OP_ADDR(4, 0xdeadbeef, 8, SPI_MEM_OP_DTR),
			   SPI_MEM_OP_DATA_OUT(128, buf, 8, SPI_MEM_OP_DTR));

This also means we can extend the struct without having to define new macros.

#4 lore raw

Re: [PATCH v3 01/17] spi: spi-mem: Add DTR templates for cmd, address, dummy and data phase

Quoted 8 lines · Boris Brezillon

and you get to define a DTR op like that:

	struct spi_mem_op op =
		SPI_MEM_OP(SPI_MEM_OP_EXT_CMD(2, 0x1234, 8, SPI_MEM_OP_DTR),
			   SPI_MEM_OP_ADDR(4, 0xdeadbeef, 8, SPI_MEM_OP_DTR),
			   SPI_MEM_OP_DATA_OUT(128, buf, 8, SPI_MEM_OP_DTR));

This also means we can extend the struct without having to define new macros.

I like this. It would also let us easily mix-and-match the ecc parameter that Miquel is adding.

Signature · 3 lines
Regards,
Pratyush Yadav
Texas Instruments Inc.
#5 lore raw

Re: [PATCH v3 01/17] spi: spi-mem: Add DTR templates for cmd, address, dummy and data phase

Quoted 8 lines · On Wed, 5 Jan 2022 11:20:55 +0530 Pratyush Yadav <[email protected]> wrote:
Quoted 7 lines · Boris Brezillon

and you get to define a DTR op like that:

	struct spi_mem_op op =
		SPI_MEM_OP(SPI_MEM_OP_EXT_CMD(2, 0x1234, 8, SPI_MEM_OP_DTR),
			   SPI_MEM_OP_ADDR(4, 0xdeadbeef, 8, SPI_MEM_OP_DTR),
			   SPI_MEM_OP_DATA_OUT(128, buf, 8, SPI_MEM_OP_DTR));
Quoted 5 lines
Quoted 1 line

This also means we can extend the struct without having to define new macros.

I like this. It would also let us easily mix-and-match the ecc parameter that Miquel is adding.

In practice, I doubt you'll ever set the ecc bit when declaring the op, it's more a modification you do afterwards if ECC needs to be enabled, but who knows...

#6 lore raw

Re: [PATCH v3 01/17] spi: spi-mem: Add DTR templates for cmd, address, dummy and data phase

In reply to Boris Brezillon
Quoted 8 lines · Boris Brezillon
include/linux/spi/spi-mem.h +2 -1
-#define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth)                \
+#define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth, ...)   \
        {                                                       \
                .dir = SPI_MEM_DATA_IN,                         \
                .nbytes = __nbytes,                             \
                .buf.in = __buf,                                \
                .buswidth = __buswidth,                         \
+               __VA_ARGS__                                     \

include/linux/spi/spi-mem.h

Quoted 10 lines
include/linux/spi/spi-mem.h +2 -1
        }
 
-#define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth)       \
+#define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth, ...)  \
        {                                                       \
                .dir = SPI_MEM_DATA_OUT,                        \
                .nbytes = __nbytes,                             \
                .buf.out = __buf,                               \
                .buswidth = __buswidth,                         \
+               __VA_ARGS__                                   

I like it too. This also comes in handy when we'll have to differentiate between register and memory accesses.

Cheers,
ta

#7 lore raw

PATCH v3 2/17 mtd: spinand: Define macros for Octal DTR ops

In reply to Apurva Nandan

Define new op templates for reset, write enable, set_feature, get_feature, block_erase, read/write page operations for Octal DTR SPI mode. These templates will be used in data_ops and ctrl_ops for performing all flash operations.

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
 include/linux/mtd/spinand.h | 54 +++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)
include/linux/mtd/spinand.h +54 -0
index 6988956b8492..69e06e741717 100644
@@ -26,12 +26,24 @@
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_NO_DATA)
 
+#define SPINAND_RESET_OP_OCTAL_DTR					\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0xffff, 8),			\
+		   SPI_MEM_OP_NO_ADDR,					\
+		   SPI_MEM_OP_NO_DUMMY,					\
+		   SPI_MEM_OP_NO_DATA)
+
 #define SPINAND_WR_EN_DIS_OP(enable)					\
 	SPI_MEM_OP(SPI_MEM_OP_CMD((enable) ? 0x06 : 0x04, 1),		\
 		   SPI_MEM_OP_NO_ADDR,					\
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_NO_DATA)
 
+#define SPINAND_WR_EN_DIS_OP_OCTAL_DTR(enable)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, (enable) ? 0x0606 : 0x0404, 8),\
+		   SPI_MEM_OP_NO_ADDR,					\
+		   SPI_MEM_OP_NO_DUMMY,					\
+		   SPI_MEM_OP_NO_DATA)
+
 #define SPINAND_READID_OP(naddr, ndummy, buf, len)			\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(0x9f, 1),				\
 		   SPI_MEM_OP_ADDR(naddr, 0, 1),			\
@@ -44,24 +56,48 @@
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_DATA_OUT(1, valptr, 1))
 
+#define SPINAND_SET_FEATURE_OP_OCTAL_DTR(reg, valptr)			\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0x1f1f, 8),			\
+		   SPI_MEM_OP_ADDR_DTR(2, reg, 8),			\
+		   SPI_MEM_OP_NO_DUMMY,					\
+		   SPI_MEM_OP_DATA_OUT_DTR(2, valptr, 8))
+
 #define SPINAND_GET_FEATURE_OP(reg, valptr)				\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(0x0f, 1),				\
 		   SPI_MEM_OP_ADDR(1, reg, 1),				\
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_DATA_IN(1, valptr, 1))
 
+#define SPINAND_GET_FEATURE_OP_OCTAL_DTR(reg, valptr)			\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0x0f0f, 8),			\
+		   SPI_MEM_OP_ADDR_DTR(2, reg, 8),			\
+		   SPI_MEM_OP_DUMMY_DTR(14, 8),				\
+		   SPI_MEM_OP_DATA_IN_DTR(2, valptr, 8))
+
 #define SPINAND_BLK_ERASE_OP(addr)					\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(0xd8, 1),				\
 		   SPI_MEM_OP_ADDR(3, addr, 1),				\
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_NO_DATA)
 
+#define SPINAND_BLK_ERASE_OP_OCTAL_DTR(addr)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0xd8d8, 8),			\
+		   SPI_MEM_OP_ADDR_DTR(2, addr, 8),			\
+		   SPI_MEM_OP_NO_DUMMY,					\
+		   SPI_MEM_OP_NO_DATA)
+
 #define SPINAND_PAGE_READ_OP(addr)					\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(0x13, 1),				\
 		   SPI_MEM_OP_ADDR(3, addr, 1),				\
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_NO_DATA)
 
+#define SPINAND_PAGE_READ_OP_OCTAL_DTR(addr)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0x1313, 8),			\
+		   SPI_MEM_OP_ADDR_DTR(2, addr, 8),			\
+		   SPI_MEM_OP_NO_DUMMY,					\
+		   SPI_MEM_OP_NO_DATA)
+
 #define SPINAND_PAGE_READ_FROM_CACHE_OP(fast, addr, ndummy, buf, len)	\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(fast ? 0x0b : 0x03, 1),		\
 		   SPI_MEM_OP_ADDR(2, addr, 1),				\
@@ -122,12 +158,24 @@
 		   SPI_MEM_OP_DUMMY(ndummy, 4),				\
 		   SPI_MEM_OP_DATA_IN(len, buf, 4))
 
+#define SPINAND_PAGE_READ_FROM_CACHE_OCTALIO_DTR_OP(addr, ndummy, buf, len) \
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0x9d9d, 8),			\
+		   SPI_MEM_OP_ADDR_DTR(2, addr, 8),			\
+		   SPI_MEM_OP_DUMMY_DTR(ndummy, 8),			\
+		   SPI_MEM_OP_DATA_IN_DTR(len, buf, 8))
+
 #define SPINAND_PROG_EXEC_OP(addr)					\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(0x10, 1),				\
 		   SPI_MEM_OP_ADDR(3, addr, 1),				\
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_NO_DATA)
 
+#define SPINAND_PROG_EXEC_OP_OCTAL_DTR(addr)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0x1010, 8),			\
+		   SPI_MEM_OP_ADDR_DTR(2, addr, 8),			\
+		   SPI_MEM_OP_NO_DUMMY,					\
+		   SPI_MEM_OP_NO_DATA)
+
 #define SPINAND_PROG_LOAD(reset, addr, buf, len)			\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(reset ? 0x02 : 0x84, 1),		\
 		   SPI_MEM_OP_ADDR(2, addr, 1),				\
@@ -140,6 +188,12 @@
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_DATA_OUT(len, buf, 4))
 
+#define SPINAND_PROG_LOAD_OCTALIO_DTR(reset, addr, buf, len)		\
+	SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, reset ? 0x0202 : 0x8484, 8),	\
+		   SPI_MEM_OP_ADDR_DTR(2, addr, 8),			\
+		   SPI_MEM_OP_NO_DUMMY,					\
+		   SPI_MEM_OP_DATA_OUT_DTR(len, buf, 8))
+
 /**
  * Standard SPI NAND flash commands
  */
Signature · 1 line
2.25.1
#8 lore raw

PATCH v3 3/17 mtd: spinand: Add enum spinand_protocol to indicate current SPI IO mode

In reply to Apurva Nandan

Unlike Dual and Quad SPI modes flashes, Octal DTR SPI NAND flashes require all instructions to be made in 8D-8D-8D protocol when the flash is in Octal DTR mode. Hence, storing the current SPI IO mode becomes necessary for operating the flash and switching between modes.

Store the current SPI IO mode in the spinand struct using a spinand_protocol enum. This would act as a flag, denoting that the core should use the given SPI protocol all types of flash operations.

---
 drivers/mtd/nand/spi/core.c |  2 ++
 include/linux/mtd/spinand.h | 16 ++++++++++++++++
 2 files changed, 18 insertions(+)
drivers/mtd/nand/spi/core.c +2 -0
index 2c8685f1f2fa..7d8c2873ab29 100644
@@ -1155,6 +1155,7 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
 	struct spinand_device *spinand = mtd_to_spinand(mtd);
 	int ret;
 
+	spinand->protocol = SPINAND_1S_1S_1S;
 	ret = spinand_reset_op(spinand);
 	if (ret)
 		return;
@@ -1181,6 +1182,7 @@ static int spinand_init(struct spinand_device *spinand)
 	if (!spinand->scratchbuf)
 		return -ENOMEM;
 
+	spinand->protocol = SPINAND_1S_1S_1S;
 	ret = spinand_detect(spinand);
 	if (ret)
 		goto err_free_bufs;
include/linux/mtd/spinand.h +16 -0
index 69e06e741717..77927afcea0f 100644
@@ -194,6 +194,18 @@
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_DATA_OUT_DTR(len, buf, 8))
 
+/**
+ * enum spinand_protocol - List of SPI protocols to denote the op protocol and
+ *			   SPI NAND flash IO modes.
+ */
+enum spinand_protocol {
+	SPINAND_1S_1S_1S,
+	SPINAND_2S_2S_2S,
+	SPINAND_4S_4S_4S,
+	SPINAND_8S_8S_8S,
+	SPINAND_8D_8D_8D,
+};
+
 /**
  * Standard SPI NAND flash commands
  */
@@ -461,6 +473,8 @@ struct spinand_dirmap {
  *		   this die. Only required if your chip exposes several dies
  * @cur_target: currently selected target/die
  * @eccinfo: on-die ECC information
+ * @protocol: SPI IO protocol in operation. Update on successful transition into
+ *	      a different SPI IO protocol.
  * @cfg_cache: config register cache. One entry per die
  * @databuf: bounce buffer for data
  * @oobbuf: bounce buffer for OOB data
@@ -492,6 +506,8 @@ struct spinand_device {
 
 	struct spinand_ecc_info eccinfo;
 
+	enum spinand_protocol protocol;
+
 	u8 *cfg_cache;
 	u8 *databuf;
 	u8 *oobbuf;
Signature · 1 line
2.25.1
#9 lore raw

Re: [PATCH v3 03/17] mtd: spinand: Add enum spinand_protocol to indicate current SPI IO mode

Quoted 8 lines · On Sat, 1 Jan 2022 13:12:36 +0530 Apurva Nandan <[email protected]> wrote:
include/linux/mtd/spinand.h +0 -0
index 69e06e741717..77927afcea0f 100644
@@ -194,6 +194,18 @@
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_DATA_OUT_DTR(len, buf, 8))
 

include/linux/mtd/spinand.h line 206

Quoted 10 lines
include/linux/mtd/spinand.h +10 -0
+/**
+ * enum spinand_protocol - List of SPI protocols to denote the op protocol and
+ *			   SPI NAND flash IO modes.
+ */
+enum spinand_protocol {
+	SPINAND_1S_1S_1S,
+	SPINAND_2S_2S_2S,
+	SPINAND_4S_4S_4S,
+	SPINAND_8S_8S_8S,
+	SPINAND_8D_8D_8D,

I'd just name those SPINAND_PROTO_<width><S or D> since what really matters is the command cycle.

#10 lore raw

PATCH v3 4/17 mtd: spinand: Rename 'op_templates' to 'data_ops'

In reply to Apurva Nandan

Manufacturers have been deviating from the standard SPI operations for NAND flashes. There have been variations in non-page read/write instructions too. Additionally, operations, including non-page r/w ops, vary when flash is in different SPI mode, eg. Octal DTR.

To avoid live-patching in hot-paths or vendor-specific adjustment, it is better to have a set of operation templates and variants for non-page read/write operations as well. These would get initialized at the probe time or when flash changes modes. These would be called 'ctrl_ops'.

To make code better understandable, create two types of op templates which are: data_ops and ctrl_ops. Reason for having two different type of templates is the difference in their use cases i.e. it is possible to have ops of different protocol for read/write/update simulatneously in the data_ops, but all the ops in the ctrl_ops follow same protocol.

Rename op_templates to data_ops, and the ctrl_ops would be introduced in later commits.

---
 drivers/mtd/nand/spi/core.c | 32 ++++++++++++++++----------------
 include/linux/mtd/spinand.h | 26 +++++++++++++-------------
 2 files changed, 29 insertions(+), 29 deletions(-)
drivers/mtd/nand/spi/core.c +16 -16
index 7d8c2873ab29..481516c9db79 100644
@@ -184,9 +184,9 @@ static int spinand_init_quad_enable(struct spinand_device *spinand)
 	if (!(spinand->flags & SPINAND_HAS_QE_BIT))
 		return 0;
 
-	if (spinand->op_templates.read_cache->data.buswidth == 4 ||
-	    spinand->op_templates.write_cache->data.buswidth == 4 ||
-	    spinand->op_templates.update_cache->data.buswidth == 4)
+	if (spinand->data_ops.read_cache->data.buswidth == 4 ||
+	    spinand->data_ops.write_cache->data.buswidth == 4 ||
+	    spinand->data_ops.update_cache->data.buswidth == 4)
 		enable = true;
 
 	return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
@@ -849,7 +849,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 	/* The plane number is passed in MSB just above the column address */
 	info.offset = plane << fls(nand->memorg.pagesize);
 
-	info.op_tmpl = *spinand->op_templates.update_cache;
+	info.op_tmpl = *spinand->data_ops.update_cache;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
 					  spinand->spimem, &info);
 	if (IS_ERR(desc))
@@ -857,7 +857,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 
 	spinand->dirmaps[plane].wdesc = desc;
 
-	info.op_tmpl = *spinand->op_templates.read_cache;
+	info.op_tmpl = *spinand->data_ops.read_cache;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
 					  spinand->spimem, &info);
 	if (IS_ERR(desc))
@@ -976,8 +976,8 @@ static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
 }
 
 static const struct spi_mem_op *
-spinand_select_op_variant(struct spinand_device *spinand,
-			  const struct spinand_op_variants *variants)
+spinand_select_data_op_variant(struct spinand_device *spinand,
+			       const struct spinand_op_variants *variants)
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
 	unsigned int i;
@@ -1050,23 +1050,23 @@ int spinand_match_and_init(struct spinand_device *spinand,
 		spinand->id.len = 1 + table[i].devid.len;
 		spinand->select_target = table[i].select_target;
 
-		op = spinand_select_op_variant(spinand,
-					       info->op_variants.read_cache);
+		op = spinand_select_data_op_variant(spinand,
+					info->data_ops_variants.read_cache);
 		if (!op)
 			return -ENOTSUPP;
 
-		spinand->op_templates.read_cache = op;
+		spinand->data_ops.read_cache = op;
 
-		op = spinand_select_op_variant(spinand,
-					       info->op_variants.write_cache);
+		op = spinand_select_data_op_variant(spinand,
+					info->data_ops_variants.write_cache);
 		if (!op)
 			return -ENOTSUPP;
 
-		spinand->op_templates.write_cache = op;
+		spinand->data_ops.write_cache = op;
 
-		op = spinand_select_op_variant(spinand,
-					       info->op_variants.update_cache);
-		spinand->op_templates.update_cache = op;
+		op = spinand_select_data_op_variant(spinand,
+					info->data_ops_variants.update_cache);
+		spinand->data_ops.update_cache = op;
 
 		return 0;
 	}
include/linux/mtd/spinand.h +13 -13
index 77927afcea0f..439d8ce40e1d 100644
@@ -391,10 +391,10 @@ struct spinand_ondie_ecc_conf {
  * @memorg: memory organization
  * @eccreq: ECC requirements
  * @eccinfo: on-die ECC info
- * @op_variants: operations variants
- * @op_variants.read_cache: variants of the read-cache operation
- * @op_variants.write_cache: variants of the write-cache operation
- * @op_variants.update_cache: variants of the update-cache operation
+ * @data_ops_variants: operations variants for page read/writes
+ * @data_ops_variants.read_cache: variants of the read-cache operation
+ * @data_ops_variants.write_cache: variants of the write-cache operation
+ * @data_ops_variants.update_cache: variants of the update-cache operation
  * @select_target: function used to select a target/die. Required only for
  *		   multi-die chips
  *
@@ -412,7 +412,7 @@ struct spinand_info {
 		const struct spinand_op_variants *read_cache;
 		const struct spinand_op_variants *write_cache;
 		const struct spinand_op_variants *update_cache;
-	} op_variants;
+	} data_ops_variants;
 	int (*select_target)(struct spinand_device *spinand,
 			     unsigned int target);
 };
@@ -440,14 +440,14 @@ struct spinand_info {
 #define SPINAND_SELECT_TARGET(__func)					\
 	.select_target = __func,
 
-#define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants,	\
-		     __flags, ...)					\
+#define SPINAND_INFO(__model, __id, __memorg, __eccreq,			\
+		     __data_ops_variants, __flags, ...)			\
 	{								\
 		.model = __model,					\
 		.devid = __id,						\
 		.memorg = __memorg,					\
 		.eccreq = __eccreq,					\
-		.op_variants = __op_variants,				\
+		.data_ops_variants = __data_ops_variants,		\
 		.flags = __flags,					\
 		__VA_ARGS__						\
 	}
@@ -464,10 +464,10 @@ struct spinand_dirmap {
  * @lock: lock used to serialize accesses to the NAND
  * @id: NAND ID as returned by READ_ID
  * @flags: NAND flags
- * @op_templates: various SPI mem op templates
- * @op_templates.read_cache: read cache op template
- * @op_templates.write_cache: write cache op template
- * @op_templates.update_cache: update cache op template
+ * @data_ops: various SPI mem op templates for reading and writing on pages
+ * @data_ops.read_cache: read cache op template
+ * @data_ops.write_cache: write cache op template
+ * @data_ops.update_cache: update cache op template
  * @select_target: select a specific target/die. Usually called before sending
  *		   a command addressing a page or an eraseblock embedded in
  *		   this die. Only required if your chip exposes several dies
@@ -496,7 +496,7 @@ struct spinand_device {
 		const struct spi_mem_op *read_cache;
 		const struct spi_mem_op *write_cache;
 		const struct spi_mem_op *update_cache;
-	} op_templates;
+	} data_ops;
 
 	struct spinand_dirmap *dirmaps;
 
Signature · 1 line
2.25.1
#11 lore raw

Re: [PATCH v3 04/17] mtd: spinand: Rename 'op_templates' to 'data_ops'

Quoted 11 lines · On Sat, 1 Jan 2022 13:12:37 +0530 Apurva Nandan <[email protected]> wrote:

Manufacturers have been deviating from the standard SPI operations for NAND flashes. There have been variations in non-page read/write instructions too. Additionally, operations, including non-page r/w ops, vary when flash is in different SPI mode, eg. Octal DTR.

To avoid live-patching in hot-paths or vendor-specific adjustment, it is better to have a set of operation templates and variants for non-page read/write operations as well. These would get initialized at the probe time or when flash changes modes. These would be called 'ctrl_ops'.

Quoted 8 lines

To make code better understandable, create two types of op templates which are: data_ops and ctrl_ops. Reason for having two different type of templates is the difference in their use cases i.e. it is possible to have ops of different protocol for read/write/update simulatneously in the data_ops, but all the ops in the ctrl_ops follow same protocol.

Rename op_templates to data_ops, and the ctrl_ops would be introduced in later commits.

Didn't read till the end, but I don't see any overlap between the control and data operations in the first half of this series, so I'm wondering if the split is actually necessary.

#12 lore raw

PATCH v3 5/17 mtd: spinand: Define ctrl_ops for non-page read/write op templates

In reply to Apurva Nandan

'ctrl_ops' are op templates for non-page read/write operations, which are: reset, get_feature, set_feature, write_enable, block_erase, page_read and program_execute ops. The 'ctrl_ops' struct contains in it op templates for each of this op, as well as enum spinand_protocol denoting protocol of all these ops.

We require these new op templates because of deviation in standard SPINAND ops by manufacturers and also due to changes when there is a change in SPI protocol/mode. This prevents the core from live-patching and vendor-specific adjustments in ops.

Define 'ctrl_ops', add macros to initialize it and add it in spinand_device.

---
 include/linux/mtd/spinand.h | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)
include/linux/mtd/spinand.h +33 -0
index 439d8ce40e1d..e5df6220ec1e 100644
@@ -356,6 +356,35 @@ struct spinand_op_variants {
 			sizeof(struct spi_mem_op),			\
 	}
 
+struct spinand_ctrl_ops {
+	const struct {
+		struct spi_mem_op reset;
+		struct spi_mem_op get_feature;
+		struct spi_mem_op set_feature;
+		struct spi_mem_op write_enable;
+		struct spi_mem_op block_erase;
+		struct spi_mem_op page_read;
+		struct spi_mem_op program_execute;
+	} ops;
+	const enum spinand_protocol protocol;
+};
+
+#define SPINAND_CTRL_OPS(__protocol, __reset, __get_feature, __set_feature,	\
+			 __write_enable, __block_erase, __page_read,		\
+			 __program_execute)					\
+	{									\
+		.ops = {							\
+			.reset = __reset,					\
+			.get_feature = __get_feature,				\
+			.set_feature = __set_feature,				\
+			.write_enable = __write_enable,				\
+			.block_erase = __block_erase,				\
+			.page_read = __page_read,				\
+			.program_execute = __program_execute,			\
+		},								\
+		.protocol = __protocol,						\
+	}
+
 /**
  * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
  *		      chip
@@ -468,6 +497,8 @@ struct spinand_dirmap {
  * @data_ops.read_cache: read cache op template
  * @data_ops.write_cache: write cache op template
  * @data_ops.update_cache: update cache op template
+ * @ctrl_ops: various SPI mem op templates for handling the flash device, i.e.
+ *	      non page-read/write ops.
  * @select_target: select a specific target/die. Usually called before sending
  *		   a command addressing a page or an eraseblock embedded in
  *		   this die. Only required if your chip exposes several dies
@@ -498,6 +529,8 @@ struct spinand_device {
 		const struct spi_mem_op *update_cache;
 	} data_ops;
 
+	const struct spinand_ctrl_ops *ctrl_ops;
+
 	struct spinand_dirmap *dirmaps;
 
 	int (*select_target)(struct spinand_device *spinand,
Signature · 1 line
2.25.1
#13 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

Quoted 28 lines · On Sat, 1 Jan 2022 13:12:38 +0530 Apurva Nandan <[email protected]> wrote:

'ctrl_ops' are op templates for non-page read/write operations, which are: reset, get_feature, set_feature, write_enable, block_erase, page_read and program_execute ops. The 'ctrl_ops' struct contains in it op templates for each of this op, as well as enum spinand_protocol denoting protocol of all these ops.

We require these new op templates because of deviation in standard SPINAND ops by manufacturers and also due to changes when there is a change in SPI protocol/mode. This prevents the core from live-patching and vendor-specific adjustments in ops.

Define 'ctrl_ops', add macros to initialize it and add it in spinand_device.

---
 include/linux/mtd/spinand.h | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)
include/linux/mtd/spinand.h +1 -0
index 439d8ce40e1d..e5df6220ec1e 100644
@@ -356,6 +356,35 @@ struct spinand_op_variants {
 			sizeof(struct spi_mem_op),			\
 	}
 
+struct spinand_ctrl_ops {

include/linux/mtd/spinand.h struct spinand_op_variants { · line 369

Quoted 10 lines
include/linux/mtd/spinand.h +10 -0
+	const struct {
+		struct spi_mem_op reset;
+		struct spi_mem_op get_feature;
+		struct spi_mem_op set_feature;
+		struct spi_mem_op write_enable;
+		struct spi_mem_op block_erase;
+		struct spi_mem_op page_read;
+		struct spi_mem_op program_execute;
+	} ops;
+	const enum spinand_protocol protocol;

Do you really need that protocol field?

Quoted 31 lines
include/linux/mtd/spinand.h +20 -0
+};
+
+#define SPINAND_CTRL_OPS(__protocol, __reset, __get_feature, __set_feature,	\
+			 __write_enable, __block_erase, __page_read,		\
+			 __program_execute)					\
+	{									\
+		.ops = {							\
+			.reset = __reset,					\
+			.get_feature = __get_feature,				\
+			.set_feature = __set_feature,				\
+			.write_enable = __write_enable,				\
+			.block_erase = __block_erase,				\
+			.page_read = __page_read,				\
+			.program_execute = __program_execute,			\
+		},								\
+		.protocol = __protocol,						\
+	}
+
 /**
  * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
  *		      chip
@@ -468,6 +497,8 @@ struct spinand_dirmap {
  * @data_ops.read_cache: read cache op template
  * @data_ops.write_cache: write cache op template
  * @data_ops.update_cache: update cache op template
+ * @ctrl_ops: various SPI mem op templates for handling the flash device, i.e.
+ *	      non page-read/write ops.
  * @select_target: select a specific target/die. Usually called before sending
  *		   a command addressing a page or an eraseblock embedded in
  *		   this die. Only required if your chip exposes several dies

include/linux/mtd/spinand.h struct spinand_device { · line 533

Quoted 5 lines
include/linux/mtd/spinand.h +2 -0
@@ -498,6 +529,8 @@ struct spinand_device {
 		const struct spi_mem_op *update_cache;
 	} data_ops;
 
+	const struct spinand_ctrl_ops *ctrl_ops;
+

Okay, I had something slightly different in mind. First, I'd put all templates in a struct:

struct spinand_op_templates {
	const struct spi_mem_op *read_cache;
	const struct spi_mem_op *write_cache;
	const struct spi_mem_op *update_cache;
	const struct spi_mem_op *reset;
	const struct spi_mem_op *get_feature;
	const struct spi_mem_op *set_feature;
	const struct spi_mem_op *write_enable;
	const struct spi_mem_op *block_erase;
	const struct spi_mem_op *page_load;
	const struct spi_mem_op *program_execute;
};

Then, at the spinand level, I'd define an array of templates:

enum spinand_protocol {
	SPINAND_1S_1S_1S,
	SPINAND_2S_2S_2S,
	SPINAND_4S_4S_4S,
	SPINAND_8S_8S_8S,
	SPINAND_8D_8D_8D,
	SPINAND_NUM_PROTOCOLS,
};
struct spinand_device {
	...
	enum spinand_protocol protocol;
	const struct spinand_op_templates *op_templates[SPINAND_NUM_PROTOCOLS];
	...
};

This way, you can easily pick the right set of operations based on the protocol/mode you're in:

#define spinand_get_op_template(spinand, opname) \
	((spinand)->op_templates[(spinand)->protocol]->opname)

static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)

{
	struct spi_mem_op op = *spinand_get_op_template(spinand, get_feature);
	int ret;

	...
}
#14 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

Quoted 4 lines · On Mon, 3 Jan 2022 11:01:07 +0100 Boris Brezillon…
struct spinand_device {
	...
	enum spinand_protocol protocol;
	const struct spinand_op_templates *op_templates[SPINAND_NUM_PROTOCOLS];

My bad, it should be:

	struct spinand_op_templates op_templates[SPINAND_NUM_PROTOCOLS];

since those templates get populated dynamically at probe time based on what the flash and the controller support.

Quoted 3 lines
	...
};
#15 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

In reply to Boris Brezillon

Hi Boris,

Quoted 119 lines · Boris Brezillon
Quoted 28 lines · On Sat, 1 Jan 2022 13:12:38 +0530 Apurva Nandan <[email protected]> wrote:

'ctrl_ops' are op templates for non-page read/write operations, which are: reset, get_feature, set_feature, write_enable, block_erase, page_read and program_execute ops. The 'ctrl_ops' struct contains in it op templates for each of this op, as well as enum spinand_protocol denoting protocol of all these ops.

We require these new op templates because of deviation in standard SPINAND ops by manufacturers and also due to changes when there is a change in SPI protocol/mode. This prevents the core from live-patching and vendor-specific adjustments in ops.

Define 'ctrl_ops', add macros to initialize it and add it in spinand_device.

---
  include/linux/mtd/spinand.h | 33 +++++++++++++++++++++++++++++++++
  1 file changed, 33 insertions(+)
include/linux/mtd/spinand.h +1 -0
index 439d8ce40e1d..e5df6220ec1e 100644
@@ -356,6 +356,35 @@ struct spinand_op_variants {
  			sizeof(struct spi_mem_op),			\
  	}
 
+struct spinand_ctrl_ops {

include/linux/mtd/spinand.h struct spinand_op_variants { · line 369

Quoted 10 lines
include/linux/mtd/spinand.h +10 -0
+	const struct {
+		struct spi_mem_op reset;
+		struct spi_mem_op get_feature;
+		struct spi_mem_op set_feature;
+		struct spi_mem_op write_enable;
+		struct spi_mem_op block_erase;
+		struct spi_mem_op page_read;
+		struct spi_mem_op program_execute;
+	} ops;
+	const enum spinand_protocol protocol;

Do you really need that protocol field?

Quoted 31 lines
include/linux/mtd/spinand.h +20 -0
+};
+
+#define SPINAND_CTRL_OPS(__protocol, __reset, __get_feature, __set_feature,	\
+			 __write_enable, __block_erase, __page_read,		\
+			 __program_execute)					\
+	{									\
+		.ops = {							\
+			.reset = __reset,					\
+			.get_feature = __get_feature,				\
+			.set_feature = __set_feature,				\
+			.write_enable = __write_enable,				\
+			.block_erase = __block_erase,				\
+			.page_read = __page_read,				\
+			.program_execute = __program_execute,			\
+		},								\
+		.protocol = __protocol,						\
+	}
+
  /**
   * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
   *		      chip
@@ -468,6 +497,8 @@ struct spinand_dirmap {
   * @data_ops.read_cache: read cache op template
   * @data_ops.write_cache: write cache op template
   * @data_ops.update_cache: update cache op template
+ * @ctrl_ops: various SPI mem op templates for handling the flash device, i.e.
+ *	      non page-read/write ops.
   * @select_target: select a specific target/die. Usually called before sending
   *		   a command addressing a page or an eraseblock embedded in
   *		   this die. Only required if your chip exposes several dies

include/linux/mtd/spinand.h struct spinand_device { · line 533

Quoted 5 lines
include/linux/mtd/spinand.h +2 -0
@@ -498,6 +529,8 @@ struct spinand_device {
  		const struct spi_mem_op *update_cache;
  	} data_ops;
 
+	const struct spinand_ctrl_ops *ctrl_ops;
+

Okay, I had something slightly different in mind. First, I'd put all templates in a struct:

struct spinand_op_templates {
	const struct spi_mem_op *read_cache;
	const struct spi_mem_op *write_cache;
	const struct spi_mem_op *update_cache;
	const struct spi_mem_op *reset;
	const struct spi_mem_op *get_feature;
	const struct spi_mem_op *set_feature;
	const struct spi_mem_op *write_enable;
	const struct spi_mem_op *block_erase;
	const struct spi_mem_op *page_load;
	const struct spi_mem_op *program_execute;
};

Then, at the spinand level, I'd define an array of templates:

enum spinand_protocol {
	SPINAND_1S_1S_1S,
	SPINAND_2S_2S_2S,
	SPINAND_4S_4S_4S,
	SPINAND_8S_8S_8S,
	SPINAND_8D_8D_8D,
	SPINAND_NUM_PROTOCOLS,
};
struct spinand_device {
	...
	enum spinand_protocol protocol;
	const struct spinand_op_templates *op_templates[SPINAND_NUM_PROTOCOLS];
	...
};

This way, you can easily pick the right set of operations based on the protocol/mode you're in:

#define spinand_get_op_template(spinand, opname) \
	((spinand)->op_templates[(spinand)->protocol]->opname)
Quoted 7 lines

static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)

{
	struct spi_mem_op op = *spinand_get_op_template(spinand, get_feature);
	int ret;

	...
}

I find a couple of issues with this  method,

1. read_cache, write_cache, update_cache op templates don't fit well with the other non-data ops, as these data ops are used to create a dirmap, and that can be done only once at probe time. Hence, there is a different mechanism of selecting of data ops and non-data ops. Hence, this division in the op templates struct as data_ops and ctrl_ops is required. Currently, the core only supports using a single protocol for data ops, chosen at the time of probing.

2. If we use this single op_templates struct, I can't think of any good way to initialize these in the manufacturers driver (winbond.c), refer to 17th patch in this series. Could you please suggest a macro implementation also for winbond.c with the suggested op_templates struct.

Thanks,
Apurva Nandan

#16 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

Hi Apurva,

Quoted 113 lines · On Tue, 15 Feb 2022 21:03:52 +0530 Apurva Nandan <[email protected]> wrote:

Hi Boris,

Quoted 110 lines · Boris Brezillon
Quoted 28 lines · On Sat, 1 Jan 2022 13:12:38 +0530 Apurva Nandan <[email protected]> wrote:

'ctrl_ops' are op templates for non-page read/write operations, which are: reset, get_feature, set_feature, write_enable, block_erase, page_read and program_execute ops. The 'ctrl_ops' struct contains in it op templates for each of this op, as well as enum spinand_protocol denoting protocol of all these ops.

We require these new op templates because of deviation in standard SPINAND ops by manufacturers and also due to changes when there is a change in SPI protocol/mode. This prevents the core from live-patching and vendor-specific adjustments in ops.

Define 'ctrl_ops', add macros to initialize it and add it in spinand_device.

---
  include/linux/mtd/spinand.h | 33 +++++++++++++++++++++++++++++++++
  1 file changed, 33 insertions(+)
include/linux/mtd/spinand.h +1 -0
index 439d8ce40e1d..e5df6220ec1e 100644
@@ -356,6 +356,35 @@ struct spinand_op_variants {
  			sizeof(struct spi_mem_op),			\
  	}
 
+struct spinand_ctrl_ops {

include/linux/mtd/spinand.h struct spinand_op_variants { · line 369

Quoted 10 lines
include/linux/mtd/spinand.h +10 -0
+	const struct {
+		struct spi_mem_op reset;
+		struct spi_mem_op get_feature;
+		struct spi_mem_op set_feature;
+		struct spi_mem_op write_enable;
+		struct spi_mem_op block_erase;
+		struct spi_mem_op page_read;
+		struct spi_mem_op program_execute;
+	} ops;
+	const enum spinand_protocol protocol;  

Do you really need that protocol field?

Quoted 31 lines
include/linux/mtd/spinand.h +20 -0
+};
+
+#define SPINAND_CTRL_OPS(__protocol, __reset, __get_feature, __set_feature,	\
+			 __write_enable, __block_erase, __page_read,		\
+			 __program_execute)					\
+	{									\
+		.ops = {							\
+			.reset = __reset,					\
+			.get_feature = __get_feature,				\
+			.set_feature = __set_feature,				\
+			.write_enable = __write_enable,				\
+			.block_erase = __block_erase,				\
+			.page_read = __page_read,				\
+			.program_execute = __program_execute,			\
+		},								\
+		.protocol = __protocol,						\
+	}
+
  /**
   * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
   *		      chip
@@ -468,6 +497,8 @@ struct spinand_dirmap {
   * @data_ops.read_cache: read cache op template
   * @data_ops.write_cache: write cache op template
   * @data_ops.update_cache: update cache op template
+ * @ctrl_ops: various SPI mem op templates for handling the flash device, i.e.
+ *	      non page-read/write ops.
   * @select_target: select a specific target/die. Usually called before sending
   *		   a command addressing a page or an eraseblock embedded in
   *		   this die. Only required if your chip exposes several dies

include/linux/mtd/spinand.h struct spinand_device { · line 533

Quoted 5 lines
include/linux/mtd/spinand.h +2 -0
@@ -498,6 +529,8 @@ struct spinand_device {
  		const struct spi_mem_op *update_cache;
  	} data_ops;
 
+	const struct spinand_ctrl_ops *ctrl_ops;
+  

Okay, I had something slightly different in mind. First, I'd put all templates in a struct:

struct spinand_op_templates {
	const struct spi_mem_op *read_cache;
	const struct spi_mem_op *write_cache;
	const struct spi_mem_op *update_cache;
	const struct spi_mem_op *reset;
	const struct spi_mem_op *get_feature;
	const struct spi_mem_op *set_feature;
	const struct spi_mem_op *write_enable;
	const struct spi_mem_op *block_erase;
	const struct spi_mem_op *page_load;
	const struct spi_mem_op *program_execute;
};

Then, at the spinand level, I'd define an array of templates:

enum spinand_protocol {
	SPINAND_1S_1S_1S,
	SPINAND_2S_2S_2S,
	SPINAND_4S_4S_4S,
	SPINAND_8S_8S_8S,
	SPINAND_8D_8D_8D,
	SPINAND_NUM_PROTOCOLS,
};
struct spinand_device {
	...
	enum spinand_protocol protocol;
	const struct spinand_op_templates *op_templates[SPINAND_NUM_PROTOCOLS];

It should probably be

	struct spinand_op_templates op_templates[SPINAND_NUM_PROTOCOLS];

with the spinand_op_templates struct defined as:

struct spinand_op_templates {
struct spi_mem_op read_cache;
struct spi_mem_op write_cache;
struct spi_mem_op update_cache;
struct spi_mem_op reset;
struct spi_mem_op get_feature;
struct spi_mem_op set_feature;
struct spi_mem_op write_enable;
struct spi_mem_op block_erase;
struct spi_mem_op page_load;
struct spi_mem_op program_execute;
};

so the NAND framework can populate these ops.

Or maybe even better, define an enum that contains all the ops:

enum spinand_op_id {
SPI_NAND_OP_READ_CACHE,
SPI_NAND_OP_WRITE_CACHE,
SPI_NAND_OP_UPDATE_CACHE,
SPI_NAND_OP_RESET,
...
SPI_NAND_NUM_OPS,
};

struct spinand_device {
	...
 	enum spinand_protocol protocol;
 	struct spi_mem_op op_templates[SPINAND_NUM_PROTOCOLS][SPI_NAND_NUM_OPS];
	...
};
Quoted 15 lines
Quoted 7 lines

This way, you can easily pick the right set of operations based on the protocol/mode you're in:

#define spinand_get_op_template(spinand, opname) \
	((spinand)->op_templates[(spinand)->protocol]->opname)
Quoted 7 lines

static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)

{
	struct spi_mem_op op = *spinand_get_op_template(spinand, get_feature);
	int ret;

	...
}  
Quoted 7 lines

I find a couple of issues with this  method,

1. read_cache, write_cache, update_cache op templates don't fit well with the other non-data ops, as
these data ops are used to create a dirmap, and that can be done only once at probe time. Hence, there
is a different mechanism of selecting of data ops and non-data ops.

Not sure I see why this is a problem. You can populate data-ops for all modes, and pick the one that provides the best perfs when you create the dirmap (which should really be at the end of the probe, if it's not already).

Quoted 4 lines

Hence, this division in the op templates
struct as data_ops and ctrl_ops is required. Currently, the core only supports using a single protocol for
data ops, chosen at the time of probing.

Again, I don't see why you need to differentiate the control and data ops when populating this table. Those are just operations the NAND supports, and the data operations is just a subset.

Quoted 6 lines

2. If we use this single op_templates struct, I can't think of any good way to initialize these in the
manufacturers driver (winbond.c), refer to 17th patch in this series. Could you please suggest a macro
implementation also for winbond.c with the suggested op_templates struct.

First replace the op_variants field by something more generic:

struct spinand_info {
...

	const struct spinand_op_variants **ops_variants;

...
};

#define SPINAND_OP_VARIANTS(_id, ...) \
	[SPI_NAND_OP_ ## _id] = { __VA_ARGS__ }
#define SPINAND_OPS_VARIANTS(name, ...)
	const struct spinand_op_variants name[]{
		__VA_ARGS__,
	};

#define SPINAND_INFO_OPS_VARIANTS(defs)
.ops_variants = defs

...

static SPINAND_OPS_VARIANTS(w35n01jw_ops_variants,

		SPINAND_OP_VARIANTS(READ_CACHE,
			SPINAND_PAGE_READ_FROM_CACHE_OCTALIO_DTR_OP(0, 24, NULL, 0),
			SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
			...)),
		SPINAND_OP_VARIANTS(WRITE_CACHE,
			SPINAND_PROG_LOAD_OCTALIO_DTR(true, 0, NULL, 0),
			SPINAND_PROG_LOAD(true, 0, NULL, 0)),
		...
		SPINAND_OP_VARIANTS(RESET,
			SPINAND_RESET_OP_OCTAL_DTR,
			SPINAND_RESET_OP,
		...
		);

...

	SPINAND_INFO("W35N01JW",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc),
		     NAND_MEMORG(1, 4096, 128, 64, 512, 20, 1, 1, 1),
		     NAND_ECCREQ(1, 512),
		     SPINAND_HAS_OCTAL_DTR_BIT | SPINAND_HAS_CR_FEAT_BIT,
		     SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
		     SPINAND_INFO_OPS_VARIANTS(&w35n01jw_ops_variants)),

You also need to adjust spinand_match_and_init() to account for this new layout and put each template op in the right subset based on op.cmd.width and op.cmd.dtr.

Regards,

Boris

#17 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

Hi Boris,

Quoted 230 lines · Boris Brezillon

Hi Apurva,

Quoted 113 lines · On Tue, 15 Feb 2022 21:03:52 +0530 Apurva Nandan<[email protected]> wrote:

Hi Boris,

Quoted 110 lines · Boris Brezillon
Quoted 28 lines · On Sat, 1 Jan 2022 13:12:38 +0530 Apurva Nandan<[email protected]> wrote:

'ctrl_ops' are op templates for non-page read/write operations, which are: reset, get_feature, set_feature, write_enable, block_erase, page_read and program_execute ops. The 'ctrl_ops' struct contains in it op templates for each of this op, as well as enum spinand_protocol denoting protocol of all these ops.

We require these new op templates because of deviation in standard SPINAND ops by manufacturers and also due to changes when there is a change in SPI protocol/mode. This prevents the core from live-patching and vendor-specific adjustments in ops.

Define 'ctrl_ops', add macros to initialize it and add it in spinand_device.

---
   include/linux/mtd/spinand.h | 33 +++++++++++++++++++++++++++++++++
   1 file changed, 33 insertions(+)
include/linux/mtd/spinand.h +1 -0
index 439d8ce40e1d..e5df6220ec1e 100644
@@ -356,6 +356,35 @@ struct spinand_op_variants {
   			sizeof(struct spi_mem_op),			\
   	}
 
+struct spinand_ctrl_ops {

include/linux/mtd/spinand.h struct spinand_op_variants { · line 369

Quoted 10 lines
include/linux/mtd/spinand.h +10 -0
+	const struct {
+		struct spi_mem_op reset;
+		struct spi_mem_op get_feature;
+		struct spi_mem_op set_feature;
+		struct spi_mem_op write_enable;
+		struct spi_mem_op block_erase;
+		struct spi_mem_op page_read;
+		struct spi_mem_op program_execute;
+	} ops;
+	const enum spinand_protocol protocol;

Do you really need that protocol field?

Quoted 31 lines
include/linux/mtd/spinand.h +20 -0
+};
+
+#define SPINAND_CTRL_OPS(__protocol, __reset, __get_feature, __set_feature,	\
+			 __write_enable, __block_erase, __page_read,		\
+			 __program_execute)					\
+	{									\
+		.ops = {							\
+			.reset = __reset,					\
+			.get_feature = __get_feature,				\
+			.set_feature = __set_feature,				\
+			.write_enable = __write_enable,				\
+			.block_erase = __block_erase,				\
+			.page_read = __page_read,				\
+			.program_execute = __program_execute,			\
+		},								\
+		.protocol = __protocol,						\
+	}
+
   /**
    * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
    *		      chip
@@ -468,6 +497,8 @@ struct spinand_dirmap {
    * @data_ops.read_cache: read cache op template
    * @data_ops.write_cache: write cache op template
    * @data_ops.update_cache: update cache op template
+ * @ctrl_ops: various SPI mem op templates for handling the flash device, i.e.
+ *	      non page-read/write ops.
    * @select_target: select a specific target/die. Usually called before sending
    *		   a command addressing a page or an eraseblock embedded in
    *		   this die. Only required if your chip exposes several dies

include/linux/mtd/spinand.h struct spinand_device { · line 533

Quoted 5 lines
include/linux/mtd/spinand.h +2 -0
@@ -498,6 +529,8 @@ struct spinand_device {
   		const struct spi_mem_op *update_cache;
   	} data_ops;
 
+	const struct spinand_ctrl_ops *ctrl_ops;
+

Okay, I had something slightly different in mind. First, I'd put all templates in a struct:

struct spinand_op_templates {
	const struct spi_mem_op *read_cache;
	const struct spi_mem_op *write_cache;
	const struct spi_mem_op *update_cache;
	const struct spi_mem_op *reset;
	const struct spi_mem_op *get_feature;
	const struct spi_mem_op *set_feature;
	const struct spi_mem_op *write_enable;
	const struct spi_mem_op *block_erase;
	const struct spi_mem_op *page_load;
	const struct spi_mem_op *program_execute;
};

Then, at the spinand level, I'd define an array of templates:

enum spinand_protocol {
	SPINAND_1S_1S_1S,
	SPINAND_2S_2S_2S,
	SPINAND_4S_4S_4S,
	SPINAND_8S_8S_8S,
	SPINAND_8D_8D_8D,
	SPINAND_NUM_PROTOCOLS,
};
struct spinand_device {
	...
	enum spinand_protocol protocol;
	const struct spinand_op_templates *op_templates[SPINAND_NUM_PROTOCOLS];

It should probably be

	struct spinand_op_templates op_templates[SPINAND_NUM_PROTOCOLS];

with the spinand_op_templates struct defined as:

struct spinand_op_templates {
struct spi_mem_op read_cache;
struct spi_mem_op write_cache;
struct spi_mem_op update_cache;
struct spi_mem_op reset;
struct spi_mem_op get_feature;
struct spi_mem_op set_feature;
struct spi_mem_op write_enable;
struct spi_mem_op block_erase;
struct spi_mem_op page_load;
struct spi_mem_op program_execute;
};

so the NAND framework can populate these ops.

Or maybe even better, define an enum that contains all the ops:

enum spinand_op_id {
SPI_NAND_OP_READ_CACHE,
SPI_NAND_OP_WRITE_CACHE,
SPI_NAND_OP_UPDATE_CACHE,
SPI_NAND_OP_RESET,
...
SPI_NAND_NUM_OPS,
};

struct spinand_device {
	...
  	enum spinand_protocol protocol;
  	struct spi_mem_op op_templates[SPINAND_NUM_PROTOCOLS][SPI_NAND_NUM_OPS];
	...
};
Quoted 13 lines
Quoted 6 lines

This way, you can easily pick the right set of operations based on the protocol/mode you're in:

#define spinand_get_op_template(spinand, opname) \
	((spinand)->op_templates[(spinand)->protocol]->opname)
Quoted 7 lines

static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)

{
	struct spi_mem_op op = *spinand_get_op_template(spinand, get_feature);
	int ret;

	...
}
Quoted 7 lines

I find a couple of issues with this  method,

1. read_cache, write_cache, update_cache op templates don't fit well with the other non-data ops, as
these data ops are used to create a dirmap, and that can be done only once at probe time. Hence, there
is a different mechanism of selecting of data ops and non-data ops.

Not sure I see why this is a problem. You can populate data-ops for all modes, and pick the one that provides the best perfs when you create the dirmap (which should really be at the end of the probe, if it's not already).

Quoted 4 lines

Hence, this division in the op templates
struct as data_ops and ctrl_ops is required. Currently, the core only supports using a single protocol for
data ops, chosen at the time of probing.

Again, I don't see why you need to differentiate the control and data ops when populating this table. Those are just operations the NAND supports, and the data operations is just a subset.

Quoted 5 lines

2. If we use this single op_templates struct, I can't think of any good way to initialize these in the
manufacturers driver (winbond.c), refer to 17th patch in this series. Could you please suggest a macro
implementation also for winbond.c with the suggested op_templates struct.

First replace the op_variants field by something more generic:

struct spinand_info {
...

	const struct spinand_op_variants **ops_variants;

...
};

#define SPINAND_OP_VARIANTS(_id, ...) \
	[SPI_NAND_OP_ ## _id] = { __VA_ARGS__ }
#define SPINAND_OPS_VARIANTS(name, ...)
	const struct spinand_op_variants name[]{
		__VA_ARGS__,
	};

#define SPINAND_INFO_OPS_VARIANTS(defs)
.ops_variants = defs

...

static SPINAND_OPS_VARIANTS(w35n01jw_ops_variants,

		SPINAND_OP_VARIANTS(READ_CACHE,
			SPINAND_PAGE_READ_FROM_CACHE_OCTALIO_DTR_OP(0, 24, NULL, 0),
			SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
			...)),
		SPINAND_OP_VARIANTS(WRITE_CACHE,
			SPINAND_PROG_LOAD_OCTALIO_DTR(true, 0, NULL, 0),
			SPINAND_PROG_LOAD(true, 0, NULL, 0)),
		...
		SPINAND_OP_VARIANTS(RESET,
			SPINAND_RESET_OP_OCTAL_DTR,
			SPINAND_RESET_OP,
		...
		);
Quoted 1 line

...

I find a issue with this implementation, please give corrective suggestions:

In type of op variant listing, there is no way to specify the protocol of the op in the variants struct itself.
    - This will lead to filtering/sorting/searching of ops for finding the protocols in the spinand core while in spinand_match_and_init(), which I don't feel is a good way for protocol based op categorization.
    - This would also lead to complexities in cases of mixed mode operations.
    - In addition, we can't simply choose the first supported protocol in each op id, as some ops have intendependency of protocol with other ops. This is because non-data ops (like reset, block erase..) cannot be in different protocols at same time, so it would make sense to have some form of protocol based arrangement while listing them.

Quoted 15 lines
	SPINAND_INFO("W35N01JW",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc),
		     NAND_MEMORG(1, 4096, 128, 64, 512, 20, 1, 1, 1),
		     NAND_ECCREQ(1, 512),
		     SPINAND_HAS_OCTAL_DTR_BIT | SPINAND_HAS_CR_FEAT_BIT,
		     SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
		     SPINAND_INFO_OPS_VARIANTS(&w35n01jw_ops_variants)),

You also need to adjust spinand_match_and_init() to account for this new layout and put each template op in the right subset based on op.cmd.width and op.cmd.dtr.

Regards,

Boris

#18 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

Quoted 60 lines · On Wed, 2 Mar 2022 21:00:55 +0530 Apurva Nandan <[email protected]> wrote:
Quoted 58 lines
Quoted 5 lines

1. read_cache, write_cache, update_cache op templates don't fit well with the other non-data ops, as
these data ops are used to create a dirmap, and that can be done only once at probe time. Hence, there
is a different mechanism of selecting of data ops and non-data ops.

Not sure I see why this is a problem. You can populate data-ops for all modes, and pick the one that provides the best perfs when you create the dirmap (which should really be at the end of the probe, if it's not already).

Quoted 4 lines

Hence, this division in the op templates
struct as data_ops and ctrl_ops is required. Currently, the core only supports using a single protocol for
data ops, chosen at the time of probing.

Again, I don't see why you need to differentiate the control and data ops when populating this table. Those are just operations the NAND supports, and the data operations is just a subset.

Quoted 5 lines

2. If we use this single op_templates struct, I can't think of any good way to initialize these in the
manufacturers driver (winbond.c), refer to 17th patch in this series. Could you please suggest a macro
implementation also for winbond.c with the suggested op_templates struct.

First replace the op_variants field by something more generic:

struct spinand_info {
...

	const struct spinand_op_variants **ops_variants;

...
};

#define SPINAND_OP_VARIANTS(_id, ...) \
	[SPI_NAND_OP_ ## _id] = { __VA_ARGS__ }
#define SPINAND_OPS_VARIANTS(name, ...)
	const struct spinand_op_variants name[]{
		__VA_ARGS__,
	};

#define SPINAND_INFO_OPS_VARIANTS(defs)
.ops_variants = defs

...

static SPINAND_OPS_VARIANTS(w35n01jw_ops_variants,

		SPINAND_OP_VARIANTS(READ_CACHE,
			SPINAND_PAGE_READ_FROM_CACHE_OCTALIO_DTR_OP(0, 24, NULL, 0),
			SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
			...)),
		SPINAND_OP_VARIANTS(WRITE_CACHE,
			SPINAND_PROG_LOAD_OCTALIO_DTR(true, 0, NULL, 0),
			SPINAND_PROG_LOAD(true, 0, NULL, 0)),
		...
		SPINAND_OP_VARIANTS(RESET,
			SPINAND_RESET_OP_OCTAL_DTR,
			SPINAND_RESET_OP,
		...
		);
Quoted 1 line

...

Quoted 8 lines

I find a issue with this implementation, please give corrective suggestions:

In type of op variant listing, there is no way to specify the protocol of the op in the variants struct itself.
    - This will lead to filtering/sorting/searching of ops for finding
the protocols in the spinand core
    while in spinand_match_and_init(), which I don't feel is a good way
for protocol based op categorization.

You'll have to go over all those operations to check which ones are supported by the controller anyway. And it's not like the classification is complicated since the cmd bus-width+DTR seems to be the discriminant, and it's stored directly in the operation template.

Quoted 2 lines

    - This would also lead to complexities in cases of mixed mode
operations.

Not sure what you mean by mixed mode. Are you referring to something like 1S-8D-8D? IIUC, all we care about is the mode used for the cmd cycle. I don't think there are commands to switch between stateless (1S-x[S,D]-x[S,D]) modes (assuming 1S-xD-xD is a thing).

Quoted 7 lines

    - In addition, we can't simply choose the first supported protocol
in each op id, as some ops have
    intendependency of protocol with other ops. This is because
non-data ops (like reset, block erase..)
    cannot be in different protocols at same time, so it would make
sense to have some form of protocol
    based arrangement while listing them.

I'm not suggesting to choose the first supported operation and use it unconditionally, but instead choose one operation per stateful mode (1S, 2S, 4S, ..., 8D) and use the appropriate template depending on the mode the flash is currently in.

#19 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

In reply to Boris Brezillon

Hi Boris,

Quoted 206 lines · Boris Brezillon

Hi Apurva,

Quoted 113 lines · On Tue, 15 Feb 2022 21:03:52 +0530 Apurva Nandan <[email protected]> wrote:

Hi Boris,

Quoted 110 lines · Boris Brezillon
Quoted 28 lines · On Sat, 1 Jan 2022 13:12:38 +0530 Apurva Nandan <[email protected]> wrote:

'ctrl_ops' are op templates for non-page read/write operations, which are: reset, get_feature, set_feature, write_enable, block_erase, page_read and program_execute ops. The 'ctrl_ops' struct contains in it op templates for each of this op, as well as enum spinand_protocol denoting protocol of all these ops.

We require these new op templates because of deviation in standard SPINAND ops by manufacturers and also due to changes when there is a change in SPI protocol/mode. This prevents the core from live-patching and vendor-specific adjustments in ops.

Define 'ctrl_ops', add macros to initialize it and add it in spinand_device.

---
   include/linux/mtd/spinand.h | 33 +++++++++++++++++++++++++++++++++
   1 file changed, 33 insertions(+)
include/linux/mtd/spinand.h +1 -0
index 439d8ce40e1d..e5df6220ec1e 100644
@@ -356,6 +356,35 @@ struct spinand_op_variants {
   			sizeof(struct spi_mem_op),			\
   	}
 
+struct spinand_ctrl_ops {

include/linux/mtd/spinand.h struct spinand_op_variants { · line 369

Quoted 10 lines
include/linux/mtd/spinand.h +10 -0
+	const struct {
+		struct spi_mem_op reset;
+		struct spi_mem_op get_feature;
+		struct spi_mem_op set_feature;
+		struct spi_mem_op write_enable;
+		struct spi_mem_op block_erase;
+		struct spi_mem_op page_read;
+		struct spi_mem_op program_execute;
+	} ops;
+	const enum spinand_protocol protocol;

Do you really need that protocol field?

Quoted 31 lines
include/linux/mtd/spinand.h +20 -0
+};
+
+#define SPINAND_CTRL_OPS(__protocol, __reset, __get_feature, __set_feature,	\
+			 __write_enable, __block_erase, __page_read,		\
+			 __program_execute)					\
+	{									\
+		.ops = {							\
+			.reset = __reset,					\
+			.get_feature = __get_feature,				\
+			.set_feature = __set_feature,				\
+			.write_enable = __write_enable,				\
+			.block_erase = __block_erase,				\
+			.page_read = __page_read,				\
+			.program_execute = __program_execute,			\
+		},								\
+		.protocol = __protocol,						\
+	}
+
   /**
    * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
    *		      chip
@@ -468,6 +497,8 @@ struct spinand_dirmap {
    * @data_ops.read_cache: read cache op template
    * @data_ops.write_cache: write cache op template
    * @data_ops.update_cache: update cache op template
+ * @ctrl_ops: various SPI mem op templates for handling the flash device, i.e.
+ *	      non page-read/write ops.
    * @select_target: select a specific target/die. Usually called before sending
    *		   a command addressing a page or an eraseblock embedded in
    *		   this die. Only required if your chip exposes several dies

include/linux/mtd/spinand.h struct spinand_device { · line 533

Quoted 5 lines
include/linux/mtd/spinand.h +2 -0
@@ -498,6 +529,8 @@ struct spinand_device {
   		const struct spi_mem_op *update_cache;
   	} data_ops;
 
+	const struct spinand_ctrl_ops *ctrl_ops;
+

Okay, I had something slightly different in mind. First, I'd put all templates in a struct:

struct spinand_op_templates {
	const struct spi_mem_op *read_cache;
	const struct spi_mem_op *write_cache;
	const struct spi_mem_op *update_cache;
	const struct spi_mem_op *reset;
	const struct spi_mem_op *get_feature;
	const struct spi_mem_op *set_feature;
	const struct spi_mem_op *write_enable;
	const struct spi_mem_op *block_erase;
	const struct spi_mem_op *page_load;
	const struct spi_mem_op *program_execute;
};

Then, at the spinand level, I'd define an array of templates:

enum spinand_protocol {
	SPINAND_1S_1S_1S,
	SPINAND_2S_2S_2S,
	SPINAND_4S_4S_4S,
	SPINAND_8S_8S_8S,
	SPINAND_8D_8D_8D,
	SPINAND_NUM_PROTOCOLS,
};
struct spinand_device {
	...
	enum spinand_protocol protocol;
	const struct spinand_op_templates *op_templates[SPINAND_NUM_PROTOCOLS];

It should probably be

	struct spinand_op_templates op_templates[SPINAND_NUM_PROTOCOLS];

with the spinand_op_templates struct defined as:

struct spinand_op_templates {
struct spi_mem_op read_cache;
struct spi_mem_op write_cache;
struct spi_mem_op update_cache;
struct spi_mem_op reset;
struct spi_mem_op get_feature;
struct spi_mem_op set_feature;
struct spi_mem_op write_enable;
struct spi_mem_op block_erase;
struct spi_mem_op page_load;
struct spi_mem_op program_execute;
};

so the NAND framework can populate these ops.

Or maybe even better, define an enum that contains all the ops:

enum spinand_op_id {
SPI_NAND_OP_READ_CACHE,
SPI_NAND_OP_WRITE_CACHE,
SPI_NAND_OP_UPDATE_CACHE,
SPI_NAND_OP_RESET,
...
SPI_NAND_NUM_OPS,
};

struct spinand_device {
	...
  	enum spinand_protocol protocol;
  	struct spi_mem_op op_templates[SPINAND_NUM_PROTOCOLS][SPI_NAND_NUM_OPS];
	...
};
Quoted 13 lines
Quoted 6 lines

This way, you can easily pick the right set of operations based on the protocol/mode you're in:

#define spinand_get_op_template(spinand, opname) \
	((spinand)->op_templates[(spinand)->protocol]->opname)
Quoted 7 lines

static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)

{
	struct spi_mem_op op = *spinand_get_op_template(spinand, get_feature);
	int ret;

	...
}
Quoted 7 lines

I find a couple of issues with this  method,

1. read_cache, write_cache, update_cache op templates don't fit well with the other non-data ops, as
these data ops are used to create a dirmap, and that can be done only once at probe time. Hence, there
is a different mechanism of selecting of data ops and non-data ops.

Not sure I see why this is a problem. You can populate data-ops for all modes, and pick the one that provides the best perfs when you create the dirmap (which should really be at the end of the probe, if it's not already).

Quoted 4 lines

Hence, this division in the op templates
struct as data_ops and ctrl_ops is required. Currently, the core only supports using a single protocol for
data ops, chosen at the time of probing.

Again, I don't see why you need to differentiate the control and data ops when populating this table. Those are just operations the NAND supports, and the data operations is just a subset.

Quoted 5 lines

2. If we use this single op_templates struct, I can't think of any good way to initialize these in the
manufacturers driver (winbond.c), refer to 17th patch in this series. Could you please suggest a macro
implementation also for winbond.c with the suggested op_templates struct.

First replace the op_variants field by something more generic:

struct spinand_info {
...

	const struct spinand_op_variants **ops_variants;

...
};

#define SPINAND_OP_VARIANTS(_id, ...) \
	[SPI_NAND_OP_ ## _id] = { __VA_ARGS__ }
Quoted 7 lines
#define SPINAND_OPS_VARIANTS(name, ...)
	const struct spinand_op_variants name[]{
		__VA_ARGS__,
	};

#define SPINAND_INFO_OPS_VARIANTS(defs)
.ops_variants = defs

If we modify these macros, it would require other spinand vendor drivers to change (toshiba, micron, etc). The older macros suit them well, should we go about changing them to this new macro (will require re-testing all of them), or can we keep them unchanged and have new set of macros with different name (please give suggestion for it) for op variants.

Quoted 35 lines

...

static SPINAND_OPS_VARIANTS(w35n01jw_ops_variants,

		SPINAND_OP_VARIANTS(READ_CACHE,
			SPINAND_PAGE_READ_FROM_CACHE_OCTALIO_DTR_OP(0, 24, NULL, 0),
			SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
			...)),
		SPINAND_OP_VARIANTS(WRITE_CACHE,
			SPINAND_PROG_LOAD_OCTALIO_DTR(true, 0, NULL, 0),
			SPINAND_PROG_LOAD(true, 0, NULL, 0)),
		...
		SPINAND_OP_VARIANTS(RESET,
			SPINAND_RESET_OP_OCTAL_DTR,
			SPINAND_RESET_OP,
		...
		);

...

	SPINAND_INFO("W35N01JW",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc),
		     NAND_MEMORG(1, 4096, 128, 64, 512, 20, 1, 1, 1),
		     NAND_ECCREQ(1, 512),
		     SPINAND_HAS_OCTAL_DTR_BIT | SPINAND_HAS_CR_FEAT_BIT,
		     SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
		     SPINAND_INFO_OPS_VARIANTS(&w35n01jw_ops_variants)),

You also need to adjust spinand_match_and_init() to account for this new layout and put each template op in the right subset based on op.cmd.width and op.cmd.dtr.

Regards,

Boris

#20 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

Quoted 57 lines · On Thu, 10 Mar 2022 13:27:06 +0530 Apurva Nandan <[email protected]> wrote:
Quoted 49 lines
Quoted 13 lines
Quoted 6 lines

This way, you can easily pick the right set of operations based on the protocol/mode you're in:

#define spinand_get_op_template(spinand, opname) \
	((spinand)->op_templates[(spinand)->protocol]->opname)
Quoted 7 lines

static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)

{
	struct spi_mem_op op = *spinand_get_op_template(spinand, get_feature);
	int ret;

	...
}  
Quoted 7 lines

I find a couple of issues with this  method,

1. read_cache, write_cache, update_cache op templates don't fit well with the other non-data ops, as
these data ops are used to create a dirmap, and that can be done only once at probe time. Hence, there
is a different mechanism of selecting of data ops and non-data ops.

Not sure I see why this is a problem. You can populate data-ops for all modes, and pick the one that provides the best perfs when you create the dirmap (which should really be at the end of the probe, if it's not already).

Quoted 4 lines

Hence, this division in the op templates
struct as data_ops and ctrl_ops is required. Currently, the core only supports using a single protocol for
data ops, chosen at the time of probing.

Again, I don't see why you need to differentiate the control and data ops when populating this table. Those are just operations the NAND supports, and the data operations is just a subset.

Quoted 5 lines

2. If we use this single op_templates struct, I can't think of any good way to initialize these in the
manufacturers driver (winbond.c), refer to 17th patch in this series. Could you please suggest a macro
implementation also for winbond.c with the suggested op_templates struct.

First replace the op_variants field by something more generic:

struct spinand_info {
...

	const struct spinand_op_variants **ops_variants;

...
};

#define SPINAND_OP_VARIANTS(_id, ...) \
	[SPI_NAND_OP_ ## _id] = { __VA_ARGS__ }
Quoted 7 lines
#define SPINAND_OPS_VARIANTS(name, ...)
	const struct spinand_op_variants name[]{
		__VA_ARGS__,
	};

#define SPINAND_INFO_OPS_VARIANTS(defs)
.ops_variants = defs

Quoted 6 lines

If we modify these macros, it would require other spinand vendor drivers to change (toshiba, micron, etc).
The older macros suit them well, should we go about changing them to this new macro (will require re-testing all of them),
or can we keep them unchanged and have new set of macros with different name (please give suggestion for it) for op variants.

I'd rather have everything converted to the new approach (we don't want 2 ways of describing the same thing), and I'm not sure you can make the old macros map to the new solution, so I fear you'll have to patch all vendors. This being said, I'm fine providing simple wrappers if that helps, but I don't see how they'd make the description simpler/more compact to be honest.

#21 lore raw

Re: [PATCH v3 05/17] mtd: spinand: Define ctrl_ops for non-page read/write op templates

Quoted 65 lines · Boris Brezillon
Quoted 54 lines · On Thu, 10 Mar 2022 13:27:06 +0530 Apurva Nandan <[email protected]> wrote:
Quoted 54 lines
Quoted 13 lines
Quoted 6 lines

This way, you can easily pick the right set of operations based on the protocol/mode you're in:

#define spinand_get_op_template(spinand, opname) \
	((spinand)->op_templates[(spinand)->protocol]->opname)
Quoted 7 lines

static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)

{
	struct spi_mem_op op = *spinand_get_op_template(spinand, get_feature);
	int ret;

	...
}
Quoted 7 lines

I find a couple of issues with this  method,

1. read_cache, write_cache, update_cache op templates don't fit well with the other non-data ops, as
these data ops are used to create a dirmap, and that can be done only once at probe time. Hence, there
is a different mechanism of selecting of data ops and non-data ops.

Not sure I see why this is a problem. You can populate data-ops for all modes, and pick the one that provides the best perfs when you create the dirmap (which should really be at the end of the probe, if it's not already).

Quoted 4 lines

Hence, this division in the op templates
struct as data_ops and ctrl_ops is required. Currently, the core only supports using a single protocol for
data ops, chosen at the time of probing.

Again, I don't see why you need to differentiate the control and data ops when populating this table. Those are just operations the NAND supports, and the data operations is just a subset.

Quoted 5 lines

2. If we use this single op_templates struct, I can't think of any good way to initialize these in the
manufacturers driver (winbond.c), refer to 17th patch in this series. Could you please suggest a macro
implementation also for winbond.c with the suggested op_templates struct.

First replace the op_variants field by something more generic:

struct spinand_info {
...

	const struct spinand_op_variants **ops_variants;

...
};

#define SPINAND_OP_VARIANTS(_id, ...) \
	[SPI_NAND_OP_ ## _id] = { __VA_ARGS__ }
#define SPINAND_OPS_VARIANTS(name, ...)
	const struct spinand_op_variants name[]{
		__VA_ARGS__,
	};
Quoted 8 lines
Quoted 2 lines

#define SPINAND_INFO_OPS_VARIANTS(defs)
.ops_variants = defs

If we modify these macros, it would require other spinand vendor drivers to change (toshiba, micron, etc).
The older macros suit them well, should we go about changing them to this new macro (will require re-testing all of them),
or can we keep them unchanged and have new set of macros with different name (please give suggestion for it) for op variants.

Quoted 6 lines

I'd rather have everything converted to the new approach (we don't want 2 ways of describing the same thing), and I'm not sure you can make the old macros map to the new solution, so I fear you'll have to patch all vendors. This being said, I'm fine providing simple wrappers if that helps, but I don't see how they'd make the description simpler/more compact to be honest.

Okay, I will convert all of the vendor drivers, but please note I don't have any way to test the changes on all the flashes.

#22 lore raw

PATCH v3 6/17 mtd: spinand: Define default ctrl_ops in the core

In reply to Apurva Nandan

Add default ctrl_ops in the core, which can be used when the op templates are commonly used ones. Till now, the core had used only fixed ctrl operations, so the default 'ctrl_ops' is just these ops macros initialized with default arguments. The default protocol is 1S_1S_1S.

---
 drivers/mtd/nand/spi/core.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
drivers/mtd/nand/spi/core.c +14 -0
index 481516c9db79..4a75eb06bb52 100644
@@ -904,6 +904,16 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
 	&winbond_spinand_manufacturer,
 };
 
+static const struct spinand_ctrl_ops spinand_default_ctrl_ops =
+			SPINAND_CTRL_OPS(SPINAND_1S_1S_1S,
+					 SPINAND_RESET_OP,
+					 SPINAND_GET_FEATURE_OP(0, NULL),
+					 SPINAND_SET_FEATURE_OP(0, NULL),
+					 SPINAND_WR_EN_DIS_OP(true),
+					 SPINAND_BLK_ERASE_OP(0),
+					 SPINAND_PAGE_READ_OP(0),
+					 SPINAND_PROG_EXEC_OP(0));
+
 static int spinand_manufacturer_match(struct spinand_device *spinand,
 				      enum spinand_readid_method rdid_method)
 {
@@ -1156,6 +1166,8 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
 	int ret;
 
 	spinand->protocol = SPINAND_1S_1S_1S;
+	spinand->ctrl_ops = &spinand_default_ctrl_ops;
+
 	ret = spinand_reset_op(spinand);
 	if (ret)
 		return;
@@ -1183,6 +1195,8 @@ static int spinand_init(struct spinand_device *spinand)
 		return -ENOMEM;
 
 	spinand->protocol = SPINAND_1S_1S_1S;
+	spinand->ctrl_ops = &spinand_default_ctrl_ops;
+
 	ret = spinand_detect(spinand);
 	if (ret)
 		goto err_free_bufs;
Signature · 1 line
2.25.1
#23 lore raw

PATCH v3 7/17 mtd: spinand: Switch from op macros usage to 'ctrl_ops' in the core

In reply to Apurva Nandan

Make use of the ctrl_ops struct, to introduce the usage of templates in non-page read/write operations as well. These templates are initialized at the probe time or at SPI modes switches.

---
 drivers/mtd/nand/spi/core.c | 36 ++++++++++++++++++++++++------------
 1 file changed, 24 insertions(+), 12 deletions(-)
drivers/mtd/nand/spi/core.c +24 -12
index 4a75eb06bb52..30e90527ee3c 100644
@@ -22,9 +22,11 @@
 
 static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
 {
-	struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg,
-						      spinand->scratchbuf);
 	int ret;
+	struct spi_mem_op op = spinand->ctrl_ops->ops.get_feature;
+
+	op.data.buf.out = spinand->scratchbuf;
+	memset(&op.addr.val, reg, op.addr.nbytes);
 
 	ret = spi_mem_exec_op(spinand->spimem, &op);
 	if (ret)
@@ -36,10 +38,12 @@ static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
 
 static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val)
 {
-	struct spi_mem_op op = SPINAND_SET_FEATURE_OP(reg,
-						      spinand->scratchbuf);
+	struct spi_mem_op op = spinand->ctrl_ops->ops.set_feature;
+
+	op.data.buf.out = spinand->scratchbuf;
+	memset(&op.addr.val, reg, op.addr.nbytes);
+	memset(spinand->scratchbuf, val, op.data.nbytes);
 
-	*spinand->scratchbuf = val;
 	return spi_mem_exec_op(spinand->spimem, &op);
 }
 
@@ -341,7 +345,7 @@ static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status)
 
 static int spinand_write_enable_op(struct spinand_device *spinand)
 {
-	struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true);
+	struct spi_mem_op op = spinand->ctrl_ops->ops.write_enable;
 
 	return spi_mem_exec_op(spinand->spimem, &op);
 }
@@ -351,7 +355,9 @@ static int spinand_load_page_op(struct spinand_device *spinand,
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
 	unsigned int row = nanddev_pos_to_row(nand, &req->pos);
-	struct spi_mem_op op = SPINAND_PAGE_READ_OP(row);
+	struct spi_mem_op op = spinand->ctrl_ops->ops.page_read;
+
+	op.addr.val = row;
 
 	return spi_mem_exec_op(spinand->spimem, &op);
 }
@@ -475,7 +481,9 @@ static int spinand_program_op(struct spinand_device *spinand,
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
 	unsigned int row = nanddev_pos_to_row(nand, &req->pos);
-	struct spi_mem_op op = SPINAND_PROG_EXEC_OP(row);
+	struct spi_mem_op op = spinand->ctrl_ops->ops.program_execute;
+
+	op.addr.val = row;
 
 	return spi_mem_exec_op(spinand->spimem, &op);
 }
@@ -485,7 +493,9 @@ static int spinand_erase_op(struct spinand_device *spinand,
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
 	unsigned int row = nanddev_pos_to_row(nand, pos);
-	struct spi_mem_op op = SPINAND_BLK_ERASE_OP(row);
+	struct spi_mem_op op = spinand->ctrl_ops->ops.block_erase;
+
+	op.addr.val = row;
 
 	return spi_mem_exec_op(spinand->spimem, &op);
 }
@@ -495,11 +505,13 @@ static int spinand_wait(struct spinand_device *spinand,
 			unsigned long poll_delay_us,
 			u8 *s)
 {
-	struct spi_mem_op op = SPINAND_GET_FEATURE_OP(REG_STATUS,
-						      spinand->scratchbuf);
+	struct spi_mem_op op = spinand->ctrl_ops->ops.get_feature;
 	u8 status;
 	int ret;
 
+	op.data.buf.out = spinand->scratchbuf;
+	memset(&op.addr.val, REG_STATUS, op.addr.nbytes);
+
 	ret = spi_mem_poll_status(spinand->spimem, &op, STATUS_BUSY, 0,
 				  initial_delay_us,
 				  poll_delay_us,
@@ -542,7 +554,7 @@ static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
 
 static int spinand_reset_op(struct spinand_device *spinand)
 {
-	struct spi_mem_op op = SPINAND_RESET_OP;
+	struct spi_mem_op op = spinand->ctrl_ops->ops.reset;
 	int ret;
 
 	ret = spi_mem_exec_op(spinand->spimem, &op);
Signature · 1 line
2.25.1
#24 lore raw

PATCH v3 8/17 mtd: spinand: Add support for manufacturer-based ctrl_ops variations

In reply to Apurva Nandan

Add ctrl_ops_variants, which can be used by the manufacturers' codes to define their SPI control operation variants. Add a macro to easily define ctrl_ops_varinats. This can be used to list out all the supported ctrl ops with their respective protocols by the vendors.

Add spinand_select_ctrl_ops_variant() helper function to search for a supported ctrl_ops variant with the required SPI protocol in a given list of variants.

---
 drivers/mtd/nand/spi/core.c | 36 ++++++++++++++++++++++++++++++++++++
 include/linux/mtd/spinand.h | 17 +++++++++++++++++
 2 files changed, 53 insertions(+)
drivers/mtd/nand/spi/core.c +36 -0
index 30e90527ee3c..9688fdfc174e 100644
@@ -1031,6 +1031,42 @@ spinand_select_data_op_variant(struct spinand_device *spinand,
 	return NULL;
 }
 
+static const struct spinand_ctrl_ops *
+spinand_select_ctrl_ops_variant(struct spinand_device *spinand,
+				const struct spinand_ctrl_ops_variants *variants,
+				const enum spinand_protocol protocol)
+{
+	unsigned int i;
+
+	for (i = 0; i < variants->nvariants; i++) {
+		const struct spinand_ctrl_ops *ctrl_ops =
+			&variants->ctrl_ops_list[i];
+
+		if (ctrl_ops->protocol != protocol)
+			continue;
+
+		if (!spi_mem_supports_op(spinand->spimem,
+					 &ctrl_ops->ops.reset) ||
+		    !spi_mem_supports_op(spinand->spimem,
+					 &ctrl_ops->ops.get_feature) ||
+		    !spi_mem_supports_op(spinand->spimem,
+					 &ctrl_ops->ops.set_feature) ||
+		    !spi_mem_supports_op(spinand->spimem,
+					 &ctrl_ops->ops.write_enable) ||
+		    !spi_mem_supports_op(spinand->spimem,
+					 &ctrl_ops->ops.block_erase) ||
+		    !spi_mem_supports_op(spinand->spimem,
+					 &ctrl_ops->ops.page_read) ||
+		    !spi_mem_supports_op(spinand->spimem,
+					 &ctrl_ops->ops.program_execute))
+			continue;
+
+		return ctrl_ops;
+	}
+
+	return NULL;
+}
+
 /**
  * spinand_match_and_init() - Try to find a match between a device ID and an
  *			      entry in a spinand_info table
include/linux/mtd/spinand.h +17 -0
index e5df6220ec1e..5dae0649f2fb 100644
@@ -385,6 +385,18 @@ struct spinand_ctrl_ops {
 		.protocol = __protocol,						\
 	}
 
+struct spinand_ctrl_ops_variants {
+	const struct spinand_ctrl_ops *ctrl_ops_list;
+	unsigned int nvariants;
+};
+
+#define SPINAND_CTRL_OPS_VARIANTS(name, ...)					\
+	const struct spinand_ctrl_ops_variants name = {				\
+		.ctrl_ops_list = (struct spinand_ctrl_ops[]){ __VA_ARGS__ },	\
+		.nvariants = sizeof((struct spinand_ctrl_ops[]){ __VA_ARGS__ })/\
+			     sizeof(struct spinand_ctrl_ops),			\
+	}
+
 /**
  * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
  *		      chip
@@ -442,6 +454,8 @@ struct spinand_info {
 		const struct spinand_op_variants *write_cache;
 		const struct spinand_op_variants *update_cache;
 	} data_ops_variants;
+
+	const struct spinand_ctrl_ops_variants *ctrl_ops_variants;
 	int (*select_target)(struct spinand_device *spinand,
 			     unsigned int target);
 };
@@ -460,6 +474,9 @@ struct spinand_info {
 		.update_cache = __update,				\
 	}
 
+#define SPINAND_INFO_CTRL_OPS_VARIANTS(__ctrl_ops_variants)		\
+	.ctrl_ops_variants = __ctrl_ops_variants
+
 #define SPINAND_ECCINFO(__ooblayout, __get_status)			\
 	.eccinfo = {							\
 		.ooblayout = __ooblayout,				\
Signature · 1 line
2.25.1
#25 lore raw

PATCH v3 9/17 mtd: spinand: Add change_mode() in manufacturer_ops

In reply to Apurva Nandan

Introduce change_mode() manufacturer_op to let the vendor provide the implementation of switching of SPI IO modes.

The method to switch to different SPI IO mode may vary across manufacturers. For example, for Winbond, Octal DTR is enabled by writing values to the volatile configuration register. So, let the manufacturer's code have their own implementation for switching to any given SPI IO mode. Manufacturer's code need to take care, if the requested protocol change is allowed/needed and how to apply it.

---
 include/linux/mtd/spinand.h | 3 +++
 1 file changed, 3 insertions(+)
include/linux/mtd/spinand.h +3 -0
index 5dae0649f2fb..ad924271a248 100644
@@ -298,6 +298,7 @@ struct spinand_devid {
 /**
  * struct manufacurer_ops - SPI NAND manufacturer specific operations
  * @init: initialize a SPI NAND device
+ * @change_mode: switch the SPI NAND flash to a specific SPI protocol
  * @cleanup: cleanup a SPI NAND device
  *
  * Each SPI NAND manufacturer driver should implement this interface so that
@@ -305,6 +306,8 @@ struct spinand_devid {
  */
 struct spinand_manufacturer_ops {
 	int (*init)(struct spinand_device *spinand);
+	int (*change_mode)(struct spinand_device *spinand,
+			   const enum spinand_protocol protocol);
 	void (*cleanup)(struct spinand_device *spinand);
 };
 
Signature · 1 line
2.25.1
#26 lore raw

Re: [PATCH v3 09/17] mtd: spinand: Add change_mode() in manufacturer_ops

Quoted 29 lines · On Sat, 1 Jan 2022 13:12:42 +0530 Apurva Nandan <[email protected]> wrote:

Introduce change_mode() manufacturer_op to let the vendor provide the implementation of switching of SPI IO modes.

The method to switch to different SPI IO mode may vary across manufacturers. For example, for Winbond, Octal DTR is enabled by writing values to the volatile configuration register. So, let the manufacturer's code have their own implementation for switching to any given SPI IO mode. Manufacturer's code need to take care, if the requested protocol change is allowed/needed and how to apply it.

---
 include/linux/mtd/spinand.h | 3 +++
 1 file changed, 3 insertions(+)
include/linux/mtd/spinand.h +1 -0
index 5dae0649f2fb..ad924271a248 100644
@@ -298,6 +298,7 @@ struct spinand_devid {
 /**
  * struct manufacurer_ops - SPI NAND manufacturer specific operations
  * @init: initialize a SPI NAND device
+ * @change_mode: switch the SPI NAND flash to a specific SPI protocol
  * @cleanup: cleanup a SPI NAND device
  *
  * Each SPI NAND manufacturer driver should implement this interface so that

include/linux/mtd/spinand.h struct spinand_devid { · line 310

Quoted 5 lines
include/linux/mtd/spinand.h +2 -0
@@ -305,6 +306,8 @@ struct spinand_devid {
  */
 struct spinand_manufacturer_ops {
 	int (*init)(struct spinand_device *spinand);
+	int (*change_mode)(struct spinand_device *spinand,
+			   const enum spinand_protocol protocol);

Protocol or mode? Pick one and stick to it. Given you already use protocol elsewhere, maybe s/select_mode/select_protocol/.

Quoted 3 lines
include/linux/mtd/spinand.h +0 -0
 	void (*cleanup)(struct spinand_device *spinand);
 };
 
#27 lore raw

PATCH v3 10/17 mtd: spinand: Add pointer to probed flash's spinand_info

In reply to Apurva Nandan

The data_ops_variants and ctrl_ops_variants defined in manufacturer's code are required again when changing flash modes, because they hold the op templates for the new protocol. It would be useful to have a pointer to the device description entry i.e. probed flash's spinand_info table in the spinand_device struct itself.

---
 drivers/mtd/nand/spi/core.c | 1 +
 include/linux/mtd/spinand.h | 3 +++
 2 files changed, 4 insertions(+)
drivers/mtd/nand/spi/core.c +1 -0
index 9688fdfc174e..1a602e4dd6bd 100644
@@ -1107,6 +1107,7 @@ int spinand_match_and_init(struct spinand_device *spinand,
 		spinand->flags = table[i].flags;
 		spinand->id.len = 1 + table[i].devid.len;
 		spinand->select_target = table[i].select_target;
+		spinand->desc_entry = &table[i];
 
 		op = spinand_select_data_op_variant(spinand,
 					info->data_ops_variants.read_cache);
include/linux/mtd/spinand.h +3 -0
index ad924271a248..a8c071983a27 100644
@@ -534,6 +534,8 @@ struct spinand_dirmap {
  *		passed in spi_mem_op be DMA-able, so we can't based the bufs on
  *		the stack
  * @manufacturer: SPI NAND manufacturer information
+ * @desc_entry: pointer to device description entry in the manufacturer's
+ *		spinand_info tables
  * @priv: manufacturer private data
  */
 struct spinand_device {
@@ -566,6 +568,7 @@ struct spinand_device {
 	u8 *oobbuf;
 	u8 *scratchbuf;
 	const struct spinand_manufacturer *manufacturer;
+	const struct spinand_info *desc_entry;
 	void *priv;
 };
 
Signature · 1 line
2.25.1
#28 lore raw

PATCH v3 11/17 mtd: spinand: Allow enabling/disabling Octal DTR mode in the core

In reply to Apurva Nandan

Enable Octal DTR SPI mode, i.e. 8D-8D-8D mode, if the SPI NAND flash device supports it. Mixed OSPI (1S-1S-8S & 1S-8S-8S), mixed DTR modes (1S-1D-8D), etc. aren't supported yet.

The method to switch to Octal DTR SPI mode may vary across manufacturers. For example, for Winbond, it is enabled by writing values to the volatile configuration register. So, let the manufacturer's code have their own implementation for switching to Octal DTR SPI mode.

Check for the SPI NAND device's support for Octal DTR mode using spinand flags, and if the data_ops and ctrl_ops are 8D-8D-8D, call change_mode() manufacturer op. If the SPI controller doesn't supports these modes, the selected data_ops and ctrl_ops will prevent switching to the Octal DTR mode. And finally update the spinand protocol and ctrl_ops on success. Similarly, for disabling Octal DTR mode, call change_mode(), and update protocol and ctrl_ops.

---
 drivers/mtd/nand/spi/core.c | 79 +++++++++++++++++++++++++++++++++++++
 include/linux/mtd/spinand.h |  1 +
 2 files changed, 80 insertions(+)
drivers/mtd/nand/spi/core.c +79 -0
index 1a602e4dd6bd..2fd08085db6f 100644
@@ -1067,6 +1067,81 @@ spinand_select_ctrl_ops_variant(struct spinand_device *spinand,
 	return NULL;
 }
 
+static bool spinand_op_is_octal_dtr(const struct spi_mem_op *op)
+{
+	return  op->cmd.buswidth == 8 && op->cmd.dtr &&
+		op->addr.buswidth == 8 && op->addr.dtr &&
+		op->data.buswidth == 8 && op->data.dtr;
+}
+
+static int spinand_init_octal_dtr_enable(struct spinand_device *spinand)
+{
+	struct device *dev = &spinand->spimem->spi->dev;
+	const struct spinand_ctrl_ops *octal_dtr_ctrl_ops;
+	int ret;
+
+	if (!(spinand->flags & SPINAND_HAS_OCTAL_DTR_BIT))
+		return 0;
+
+	if (!(spinand_op_is_octal_dtr(spinand->data_ops.read_cache) &&
+	      spinand_op_is_octal_dtr(spinand->data_ops.write_cache) &&
+	      spinand_op_is_octal_dtr(spinand->data_ops.update_cache)))
+		return 0;
+
+	octal_dtr_ctrl_ops = spinand_select_ctrl_ops_variant(spinand,
+					spinand->desc_entry->ctrl_ops_variants,
+					SPINAND_8D_8D_8D);
+
+	if (!octal_dtr_ctrl_ops)
+		return 0;
+
+	if (!spinand->manufacturer->ops->change_mode) {
+		dev_dbg(dev,
+			"Missing ->change_mode(), unable to switch mode\n");
+		return -EINVAL;
+	}
+
+	ret = spinand->manufacturer->ops->change_mode(spinand,
+						      SPINAND_8D_8D_8D);
+	if (ret) {
+		dev_err(dev,
+			"Failed to enable Octal DTR SPI mode (err = %d)\n",
+			ret);
+		return ret;
+	}
+
+	spinand->protocol = SPINAND_8D_8D_8D;
+	spinand->ctrl_ops = octal_dtr_ctrl_ops;
+
+	dev_dbg(dev,
+		"%s SPI NAND switched to Octal DTR SPI (8D-8D-8D) mode\n",
+		spinand->manufacturer->name);
+	return 0;
+}
+
+static int spinand_init_octal_dtr_disable(struct spinand_device *spinand)
+{
+	struct device *dev = &spinand->spimem->spi->dev;
+	int ret;
+
+	if (!spinand->manufacturer->ops->change_mode)
+		return -EINVAL;
+
+	ret = spinand->manufacturer->ops->change_mode(spinand,
+						      SPINAND_1S_1S_1S);
+
+	if (ret) {
+		dev_err(dev,
+			"Failed to disable Octal DTR SPI mode (err = %d)\n",
+			ret);
+		return ret;
+	}
+
+	spinand->protocol = SPINAND_1S_1S_1S;
+	spinand->ctrl_ops = &spinand_default_ctrl_ops;
+	return 0;
+}
+
 /**
  * spinand_match_and_init() - Try to find a match between a device ID and an
  *			      entry in a spinand_info table
@@ -1203,6 +1278,10 @@ static int spinand_init_flash(struct spinand_device *spinand)
 			break;
 	}
 
+	ret = spinand_init_octal_dtr_enable(spinand);
+	if (ret)
+		return ret;
+
 	if (ret)
 		spinand_manufacturer_cleanup(spinand);
 
include/linux/mtd/spinand.h +1 -0
index a8c071983a27..f12aa4516fab 100644
@@ -417,6 +417,7 @@ struct spinand_ecc_info {
 
 #define SPINAND_HAS_QE_BIT		BIT(0)
 #define SPINAND_HAS_CR_FEAT_BIT		BIT(1)
+#define SPINAND_HAS_OCTAL_DTR_BIT	BIT(2)
 
 /**
  * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure
Signature · 1 line
2.25.1
#29 lore raw

Re: [PATCH v3 11/17] mtd: spinand: Allow enabling/disabling Octal DTR mode in the core

Quoted 54 lines · On Sat, 1 Jan 2022 13:12:44 +0530 Apurva Nandan <[email protected]> wrote:

Enable Octal DTR SPI mode, i.e. 8D-8D-8D mode, if the SPI NAND flash device supports it. Mixed OSPI (1S-1S-8S & 1S-8S-8S), mixed DTR modes (1S-1D-8D), etc. aren't supported yet.

The method to switch to Octal DTR SPI mode may vary across manufacturers. For example, for Winbond, it is enabled by writing values to the volatile configuration register. So, let the manufacturer's code have their own implementation for switching to Octal DTR SPI mode.

Check for the SPI NAND device's support for Octal DTR mode using spinand flags, and if the data_ops and ctrl_ops are 8D-8D-8D, call change_mode() manufacturer op. If the SPI controller doesn't supports these modes, the selected data_ops and ctrl_ops will prevent switching to the Octal DTR mode. And finally update the spinand protocol and ctrl_ops on success. Similarly, for disabling Octal DTR mode, call change_mode(), and update protocol and ctrl_ops.

---
 drivers/mtd/nand/spi/core.c | 79 +++++++++++++++++++++++++++++++++++++
 include/linux/mtd/spinand.h |  1 +
 2 files changed, 80 insertions(+)
drivers/mtd/nand/spi/core.c +22 -0
index 1a602e4dd6bd..2fd08085db6f 100644
@@ -1067,6 +1067,81 @@ spinand_select_ctrl_ops_variant(struct spinand_device *spinand,
 	return NULL;
 }
 
+static bool spinand_op_is_octal_dtr(const struct spi_mem_op *op)
+{
+	return  op->cmd.buswidth == 8 && op->cmd.dtr &&
+		op->addr.buswidth == 8 && op->addr.dtr &&
+		op->data.buswidth == 8 && op->data.dtr;
+}
+
+static int spinand_init_octal_dtr_enable(struct spinand_device *spinand)
+{
+	struct device *dev = &spinand->spimem->spi->dev;
+	const struct spinand_ctrl_ops *octal_dtr_ctrl_ops;
+	int ret;
+
+	if (!(spinand->flags & SPINAND_HAS_OCTAL_DTR_BIT))
+		return 0;
+
+	if (!(spinand_op_is_octal_dtr(spinand->data_ops.read_cache) &&
+	      spinand_op_is_octal_dtr(spinand->data_ops.write_cache) &&
+	      spinand_op_is_octal_dtr(spinand->data_ops.update_cache)))
+		return 0;
+
+	octal_dtr_ctrl_ops = spinand_select_ctrl_ops_variant(spinand,

drivers/mtd/nand/spi/core.c spinand_select_ctrl_ops_variant(struct spinand_device *spinand, · line 1101

Quoted 10 lines
drivers/mtd/nand/spi/core.c +10 -0
+					spinand->desc_entry->ctrl_ops_variants,
+					SPINAND_8D_8D_8D);
+
+	if (!octal_dtr_ctrl_ops)
+		return 0;
+
+	if (!spinand->manufacturer->ops->change_mode) {
+		dev_dbg(dev,
+			"Missing ->change_mode(), unable to switch mode\n");
+		return -EINVAL;

Looks like something that's worth printing at the info level, and I think returning EOPNOTSUPP would be more appropriate.

Quoted 11 lines
drivers/mtd/nand/spi/core.c +11 -0
+	}
+
+	ret = spinand->manufacturer->ops->change_mode(spinand,
+						      SPINAND_8D_8D_8D);
+	if (ret) {
+		dev_err(dev,
+			"Failed to enable Octal DTR SPI mode (err = %d)\n",
+			ret);
+		return ret;
+	}
+

drivers/mtd/nand/spi/core.c spinand_select_ctrl_ops_variant(struct spinand_device *spinand, · line 1122

Quoted 10 lines
drivers/mtd/nand/spi/core.c +10 -0
+	spinand->protocol = SPINAND_8D_8D_8D;
+	spinand->ctrl_ops = octal_dtr_ctrl_ops;
+
+	dev_dbg(dev,
+		"%s SPI NAND switched to Octal DTR SPI (8D-8D-8D) mode\n",
+		spinand->manufacturer->name);
+	return 0;
+}
+
+static int spinand_init_octal_dtr_disable(struct spinand_device *spinand)

This function is never used. I guess it should be called in the suspend/shutdown path, at least.

Quoted 41 lines
drivers/mtd/nand/spi/core.c +26 -0
+{
+	struct device *dev = &spinand->spimem->spi->dev;
+	int ret;
+
+	if (!spinand->manufacturer->ops->change_mode)
+		return -EINVAL;
+
+	ret = spinand->manufacturer->ops->change_mode(spinand,
+						      SPINAND_1S_1S_1S);
+
+	if (ret) {
+		dev_err(dev,
+			"Failed to disable Octal DTR SPI mode (err = %d)\n",
+			ret);
+		return ret;
+	}
+
+	spinand->protocol = SPINAND_1S_1S_1S;
+	spinand->ctrl_ops = &spinand_default_ctrl_ops;
+	return 0;
+}
+
 /**
  * spinand_match_and_init() - Try to find a match between a device ID and an
  *			      entry in a spinand_info table
@@ -1203,6 +1278,10 @@ static int spinand_init_flash(struct spinand_device *spinand)
 			break;
 	}
 
+	ret = spinand_init_octal_dtr_enable(spinand);
+	if (ret)
+		return ret;
+
 	if (ret)
 		spinand_manufacturer_cleanup(spinand);
 
include/linux/mtd/spinand.h +0 -0
index a8c071983a27..f12aa4516fab 100644

include/linux/mtd/spinand.h struct spinand_ecc_info { · line 420

Quoted 4 lines
include/linux/mtd/spinand.h +1 -0
@@ -417,6 +417,7 @@ struct spinand_ecc_info {
 
 #define SPINAND_HAS_QE_BIT		BIT(0)
 #define SPINAND_HAS_CR_FEAT_BIT		BIT(1)
+#define SPINAND_HAS_OCTAL_DTR_BIT	BIT(2)

Do we really need a new flag for this? Isn't the template op initialization enough to reflect whether the NAND and controller can do 8DTR or not?

Quoted 3 lines
include/linux/mtd/spinand.h +0 -0
 /**
  * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure
#30 lore raw

PATCH v3 12/17 mtd: spinand: Add mtd_suspend() to disable Octal DTR mode at suspend

In reply to Apurva Nandan

The flash might or might not have gone a power-down during sleep. Hence, its SPI IO mode is unpredictable for the core at the time of resume. To ensure proper reinitialization during resume, disable the Octal DTR SPI IO mode and bring the flash to 1S-1S-1S mode when performing suspend using mtd_suspend().

---
 drivers/mtd/nand/spi/core.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
drivers/mtd/nand/spi/core.c +11 -0
index 2fd08085db6f..9d1c72634e5a 100644
@@ -1307,6 +1307,16 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
 	spinand_ecc_enable(spinand, false);
 }
 
+static int spinand_mtd_suspend(struct mtd_info *mtd)
+{
+	struct spinand_device *spinand = mtd_to_spinand(mtd);
+
+	if (spinand->ctrl_ops->protocol == SPINAND_8D_8D_8D)
+		return spinand_init_octal_dtr_disable(spinand);
+
+	return 0;
+}
+
 static int spinand_init(struct spinand_device *spinand)
 {
 	struct device *dev = &spinand->spimem->spi->dev;
@@ -1381,6 +1391,7 @@ static int spinand_init(struct spinand_device *spinand)
 	mtd->_erase = spinand_mtd_erase;
 	mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
 	mtd->_resume = spinand_mtd_resume;
+	mtd->_suspend = spinand_mtd_suspend;
 
 	if (nand->ecc.engine) {
 		ret = mtd_ooblayout_count_freebytes(mtd);
Signature · 1 line
2.25.1
#31 lore raw

Re: [PATCH v3 12/17] mtd: spinand: Add mtd_suspend() to disable Octal DTR mode at suspend

Quoted 16 lines · On Sat, 1 Jan 2022 13:12:45 +0530 Apurva Nandan <[email protected]> wrote:

The flash might or might not have gone a power-down during sleep. Hence, its SPI IO mode is unpredictable for the core at the time of resume. To ensure proper reinitialization during resume, disable the Octal DTR SPI IO mode and bring the flash to 1S-1S-1S mode when performing suspend using mtd_suspend().

---
 drivers/mtd/nand/spi/core.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
drivers/mtd/nand/spi/core.c +0 -0
index 2fd08085db6f..9d1c72634e5a 100644

drivers/mtd/nand/spi/core.c static void spinand_mtd_resume(struct mtd_info *mtd) · line 1315

Quoted 9 lines
drivers/mtd/nand/spi/core.c +6 -0
@@ -1307,6 +1307,16 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
 	spinand_ecc_enable(spinand, false);
 }
 
+static int spinand_mtd_suspend(struct mtd_info *mtd)
+{
+	struct spinand_device *spinand = mtd_to_spinand(mtd);
+
+	if (spinand->ctrl_ops->protocol == SPINAND_8D_8D_8D)
+		return spinand_init_octal_dtr_disable(spinand);

Ok, so you're calling it here. I'd re-order commmits so that DTR support is added after the suspend hook is defined, even if this hook does nothing at first. This way you don't have an unused warning in the previous commit, and most importantly, you don't enter 8DTR before you're sure things will be set back to 1S when entering suspend.

Quoted 15 lines
drivers/mtd/nand/spi/core.c +5 -0
+
+	return 0;
+}
+
 static int spinand_init(struct spinand_device *spinand)
 {
 	struct device *dev = &spinand->spimem->spi->dev;
@@ -1381,6 +1391,7 @@ static int spinand_init(struct spinand_device *spinand)
 	mtd->_erase = spinand_mtd_erase;
 	mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
 	mtd->_resume = spinand_mtd_resume;
+	mtd->_suspend = spinand_mtd_suspend;
 
 	if (nand->ecc.engine) {
 		ret = mtd_ooblayout_count_freebytes(mtd);
#32 lore raw

PATCH v3 13/17 mtd: spinand: winbond: Add support for write volatile configuration register op

In reply to Apurva Nandan

Volatile configuration register are a different set of configuration registers, i.e. they differ from the status registers. A different SPI instruction is required to write to these registers. Any changes to the Volatile Configuration Register get transferred directly to the Internal Configuration Register and instantly reflect on the device operation.

In Winbond W35N01JW, these volatile configuration register must be configured in order to switch to Octal DTR SPI mode.

Add support for writing to volatile configuration registers using a new WRITE_VCR_OP template.

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
 drivers/mtd/nand/spi/core.c    |  2 +-
 drivers/mtd/nand/spi/winbond.c | 43 ++++++++++++++++++++++++++++++++++
 include/linux/mtd/spinand.h    |  1 +
 3 files changed, 45 insertions(+), 1 deletion(-)
drivers/mtd/nand/spi/core.c +1 -1
index 9d1c72634e5a..21d3b4ebdeaa 100644
@@ -343,7 +343,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->ctrl_ops->ops.write_enable;
 
drivers/mtd/nand/spi/winbond.c +43 -0

55 lines not on this page · raw message

include/linux/mtd/spinand.h +1 -0
index f12aa4516fab..4c1925ee327a 100644
@@ -638,5 +638,6 @@ int spinand_match_and_init(struct spinand_device *spinand,
 
 int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
 int spinand_select_target(struct spinand_device *spinand, unsigned int target);
+int spinand_write_enable_op(struct spinand_device *spinand);
 
 #endif /* __LINUX_MTD_SPINAND_H */
Signature · 1 line
2.25.1
#33 lore raw

PATCH v3 14/17 mtd: spinand: winbond: Add octal_dtr_enable/disable() in manufacturer_ops

In reply to Apurva Nandan

Add implementation of octal_dtr_enable() and octal_dtr_disable() manufacturer_ops for Winbond. To switch to Ocatl DTR mode, setting programmable dummy cycles and SPI IO mode using the volatile configuration register is required. To function at max 120MHz SPI clock in Octal DTR mode, 12 programmable dummy clock cycle setting is required. (Default number of dummy cycle are 8 clocks)

Set the programmable dummy cycle to 12 clocks, and SPI IO mode to Octal DTR with Data Strobe in the VCR. Also, perform a READ ID operation in Octal DTR SPI mode to ensure the switch was successful. To disable Octal DTR mode, restore the VCR registers to their default values and verify it using READ ID operation.

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
 drivers/mtd/nand/spi/winbond.c | 85 ++++++++++++++++++++++++++++++++++
 1 file changed, 85 insertions(+)
drivers/mtd/nand/spi/winbond.c +85 -0
index 3e0829f58350..c7478faf6cee 100644
@@ -16,6 +16,16 @@
 
 #define WINBOND_CFG_BUF_READ		BIT(3)
 
+/* Octal DTR SPI mode (8D-8D-8D) with Data Strobe output*/
+#define WINBOND_VCR_IO_MODE_OCTAL_DTR	0xE7
+#define WINBOND_VCR_IO_MODE_SINGLE_STR	0xFF
+#define WINBOND_VCR_IO_MODE_ADDR	0x00
+
+/* Use 12 dummy clk cycles for using Octal DTR SPI at max 120MHZ */
+#define WINBOND_VCR_DUMMY_CLK_COUNT	12
+#define WINBOND_VCR_DUMMY_CLK_DEFAULT	0xFF
+#define WINBOND_VCR_DUMMY_CLK_ADDR	0x01
+
 static SPINAND_OP_VARIANTS(read_cache_variants,
 		SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
 		SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
@@ -157,6 +167,81 @@ static int winbond_write_vcr_op(struct spinand_device *spinand, u8 reg, u8 val)
 	return 0;
 }
 
+static int winbond_spinand_octal_dtr_enable(struct spinand_device *spinand)
+{
+	int ret;
+	struct spi_mem_op op;
+
+	ret = winbond_write_vcr_op(spinand, WINBOND_VCR_DUMMY_CLK_ADDR,
+				   WINBOND_VCR_DUMMY_CLK_COUNT);
+	if (ret)
+		return ret;
+
+	ret = winbond_write_vcr_op(spinand, WINBOND_VCR_IO_MODE_ADDR,
+				   WINBOND_VCR_IO_MODE_OCTAL_DTR);
+	if (ret)
+		return ret;
+
+	/* Read flash ID to make sure the switch was successful. */
+	op = (struct spi_mem_op)
+		SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0x9f9f, 8),
+			   SPI_MEM_OP_NO_ADDR,
+			   SPI_MEM_OP_DUMMY_DTR(16, 8),
+			   SPI_MEM_OP_DATA_IN_DTR(SPINAND_MAX_ID_LEN,
+						  spinand->scratchbuf, 8));
+
+	ret = spi_mem_exec_op(spinand->spimem, &op);
+	if (ret)
+		return ret;
+
+	if (memcmp(spinand->scratchbuf, spinand->id.data, SPINAND_MAX_ID_LEN))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int winbond_spinand_octal_dtr_disable(struct spinand_device *spinand)
+{
+	int ret;
+	struct spi_mem_op op =
+		SPI_MEM_OP(SPI_MEM_OP_CMD_DTR(2, 0x8181, 8),
+			   SPI_MEM_OP_ADDR_DTR(4, WINBOND_VCR_IO_MODE_ADDR, 8),
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_DATA_OUT_DTR(2, spinand->scratchbuf, 8));
+
+	*spinand->scratchbuf = WINBOND_VCR_IO_MODE_SINGLE_STR;
+
+	ret = spinand_write_enable_op(spinand);
+	if (ret)
+		return ret;
+
+	ret = spi_mem_exec_op(spinand->spimem, &op);
+	if (ret)
+		return ret;
+
+	ret = winbond_write_vcr_op(spinand, WINBOND_VCR_DUMMY_CLK_ADDR,
+				   WINBOND_VCR_DUMMY_CLK_DEFAULT);
+	if (ret)
+		return ret;
+
+	/* Read flash ID to make sure the switch was successful. */
+	op = (struct spi_mem_op)
+		SPI_MEM_OP(SPI_MEM_OP_CMD(0x9f, 1),
+			   SPI_MEM_OP_NO_ADDR,
+			   SPI_MEM_OP_DUMMY(1, 1),
+			   SPI_MEM_OP_DATA_IN(SPINAND_MAX_ID_LEN,
+					      spinand->scratchbuf, 1));
+
+	ret = spi_mem_exec_op(spinand->spimem, &op);
+	if (ret)
+		return ret;
+
+	if (memcmp(spinand->scratchbuf, spinand->id.data, SPINAND_MAX_ID_LEN))
+		return -EINVAL;
+
+	return 0;
+}
+
 static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = {
 	.init = winbond_spinand_init,
 };
Signature · 1 line
2.25.1
#34 lore raw

PATCH v3 15/17 mtd: spianand: winbond: Add change_mode() manufacturer_ops

In reply to Apurva Nandan

Add implementation of change_mode() for Winbond's manufacturer_ops, that executes octal_dtr_enable() and octal_dtr_disable() according to requested protocol.

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
 drivers/mtd/nand/spi/winbond.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)
drivers/mtd/nand/spi/winbond.c +25 -0
index c7478faf6cee..d8eccb40c80f 100644
@@ -242,8 +242,33 @@ static int winbond_spinand_octal_dtr_disable(struct spinand_device *spinand)
 	return 0;
 }
 
+static int winbond_change_spi_mode(struct spinand_device *spinand,
+				   const enum spinand_protocol protocol)
+{
+	if (spinand->protocol == protocol)
+		return 0;
+
+	switch (spinand->protocol) {
+	case SPINAND_1S_1S_1S:
+		if (protocol == SPINAND_8D_8D_8D)
+			return winbond_spinand_octal_dtr_enable(spinand);
+		break;
+
+	case SPINAND_8D_8D_8D:
+		if (protocol == SPINAND_1S_1S_1S)
+			return winbond_spinand_octal_dtr_disable(spinand);
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return -EOPNOTSUPP;
+}
+
 static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = {
 	.init = winbond_spinand_init,
+	.change_mode = winbond_change_spi_mode,
 };
 
 const struct spinand_manufacturer winbond_spinand_manufacturer = {
Signature · 1 line
2.25.1
#35 lore raw

Re: [PATCH v3 15/17] mtd: spianand: winbond: Add change_mode() manufacturer_ops

Quoted 31 lines · On Sat, 1 Jan 2022 13:12:48 +0530 Apurva Nandan <[email protected]> wrote:

Add implementation of change_mode() for Winbond's manufacturer_ops, that executes octal_dtr_enable() and octal_dtr_disable() according to requested protocol.

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
 drivers/mtd/nand/spi/winbond.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)
drivers/mtd/nand/spi/winbond.c +12 -0
index c7478faf6cee..d8eccb40c80f 100644
@@ -242,8 +242,33 @@ static int winbond_spinand_octal_dtr_disable(struct spinand_device *spinand)
 	return 0;
 }
 
+static int winbond_change_spi_mode(struct spinand_device *spinand,
+				   const enum spinand_protocol protocol)
+{
+	if (spinand->protocol == protocol)
+		return 0;
+
+	switch (spinand->protocol) {
+	case SPINAND_1S_1S_1S:
+		if (protocol == SPINAND_8D_8D_8D)
+			return winbond_spinand_octal_dtr_enable(spinand);
+		break;
+

drivers/mtd/nand/spi/winbond.c static int winbond_spinand_octal_dtr_disable(struct spinand_device *spinand) · line 266

Quoted 10 lines
drivers/mtd/nand/spi/winbond.c +10 -0
+	case SPINAND_8D_8D_8D:
+		if (protocol == SPINAND_1S_1S_1S)
+			return winbond_spinand_octal_dtr_disable(spinand);
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return -EOPNOTSUPP;

This version is slightly more concise:

	if (spinand->protocol == protocol)
		return 0;
	else if (spinand->protocol == SPINAND_1S && protocol ==	SPINAND_8D)
		return winbond_spinand_octal_dtr_enable(spinand);
	else if (spinand->protocol == SPINAND_8D && protocol ==	SPINAND_1S)
		return winbond_spinand_octal_dtr_disable(spinand);

	return -EOPNOTSUPP;
Quoted 8 lines
drivers/mtd/nand/spi/winbond.c +3 -0
+}
+
 static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = {
 	.init = winbond_spinand_init,
+	.change_mode = winbond_change_spi_mode,
 };
 
 const struct spinand_manufacturer winbond_spinand_manufacturer = {
#36 lore raw

PATCH v3 16/17 mtd: spinand: winbond: Rename cache op_variants struct variable

In reply to Apurva Nandan

Till now, supported Winbond SPI NAND flashes had same supported op_variants. W35N01JW introduces Octal DTR SPI IO mode, so now different op_variants struct variables are required for different Winbond flashes. Hence, rename and append the flash name in the op_variants struct variable.

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
 drivers/mtd/nand/spi/winbond.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)
drivers/mtd/nand/spi/winbond.c +9 -9
index d8eccb40c80f..5b943169120e 100644
@@ -26,7 +26,7 @@
 #define WINBOND_VCR_DUMMY_CLK_DEFAULT	0xFF
 #define WINBOND_VCR_DUMMY_CLK_ADDR	0x01
 
-static SPINAND_OP_VARIANTS(read_cache_variants,
+static SPINAND_OP_VARIANTS(read_cache_variants_w25xxgv,
 		SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
 		SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
 		SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
@@ -34,11 +34,11 @@ static SPINAND_OP_VARIANTS(read_cache_variants,
 		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
 		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
 
-static SPINAND_OP_VARIANTS(write_cache_variants,
+static SPINAND_OP_VARIANTS(write_cache_variants_w25xxgv,
 		SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
 		SPINAND_PROG_LOAD(true, 0, NULL, 0));
 
-static SPINAND_OP_VARIANTS(update_cache_variants,
+static SPINAND_OP_VARIANTS(update_cache_variants_w25xxgv,
 		SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
 		SPINAND_PROG_LOAD(false, 0, NULL, 0));
 
@@ -90,9 +90,9 @@ static const struct spinand_info winbond_spinand_table[] = {
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab),
 		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2),
 		     NAND_ECCREQ(1, 512),
-		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
-					      &write_cache_variants,
-					      &update_cache_variants),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_w25xxgv,
+					      &write_cache_variants_w25xxgv,
+					      &update_cache_variants_w25xxgv),
 		     0,
 		     SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
 		     SPINAND_SELECT_TARGET(w25m02gv_select_target)),
@@ -100,9 +100,9 @@ static const struct spinand_info winbond_spinand_table[] = {
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa),
 		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
 		     NAND_ECCREQ(1, 512),
-		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
-					      &write_cache_variants,
-					      &update_cache_variants),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_w25xxgv,
+					      &write_cache_variants_w25xxgv,
+					      &update_cache_variants_w25xxgv),
 		     0,
 		     SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
 };
Signature · 1 line
2.25.1
#37 lore raw

PATCH v3 17/17 mtd: spinand: winbond: Add support for Winbond W35N01JW SPI NAND flash

In reply to Apurva Nandan

Winbond W35N01JW is a SPI NAND flash supporting Octal DTR SPI protocol. Add op_variants and ctrl_ops_variants for W35N01JW, thus adding all required Octal DTR ops. Add W35N01JW's OOB layout functions for the mtd_ooblayout_ops. Finally, add an entry for W35N01JW in spinand_info table.

Datasheet: https://www.winbond.com/export/sites/winbond/datasheet/W35N01JW_Datasheet_Brief.pdf

---
 drivers/mtd/nand/spi/winbond.c | 71 ++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)
drivers/mtd/nand/spi/winbond.c +71 -0
index 5b943169120e..42fdb578f731 100644
@@ -42,6 +42,37 @@ static SPINAND_OP_VARIANTS(update_cache_variants_w25xxgv,
 		SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
 		SPINAND_PROG_LOAD(false, 0, NULL, 0));
 
+static SPINAND_OP_VARIANTS(read_cache_variants_w35n01jw,
+		SPINAND_PAGE_READ_FROM_CACHE_OCTALIO_DTR_OP(0, 24, NULL, 0),
+		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
+		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants_w35n01jw,
+		SPINAND_PROG_LOAD_OCTALIO_DTR(true, 0, NULL, 0),
+		SPINAND_PROG_LOAD(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants_w35n01jw,
+		SPINAND_PROG_LOAD_OCTALIO_DTR(false, 0, NULL, 0),
+		SPINAND_PROG_LOAD(false, 0, NULL, 0));
+
+static SPINAND_CTRL_OPS_VARIANTS(ctrl_ops_variants_w35n01jw,
+		SPINAND_CTRL_OPS(SPINAND_8D_8D_8D,
+				 SPINAND_RESET_OP_OCTAL_DTR,
+				 SPINAND_GET_FEATURE_OP_OCTAL_DTR(0, NULL),
+				 SPINAND_SET_FEATURE_OP_OCTAL_DTR(0, NULL),
+				 SPINAND_WR_EN_DIS_OP_OCTAL_DTR(true),
+				 SPINAND_BLK_ERASE_OP_OCTAL_DTR(0),
+				 SPINAND_PAGE_READ_OP_OCTAL_DTR(0),
+				 SPINAND_PROG_EXEC_OP_OCTAL_DTR(0)),
+		SPINAND_CTRL_OPS(SPINAND_1S_1S_1S,
+				 SPINAND_RESET_OP,
+				 SPINAND_GET_FEATURE_OP(0, NULL),
+				 SPINAND_SET_FEATURE_OP(0, NULL),
+				 SPINAND_WR_EN_DIS_OP(true),
+				 SPINAND_BLK_ERASE_OP(0),
+				 SPINAND_PAGE_READ_OP(0),
+				 SPINAND_PROG_EXEC_OP(0)));
+
 static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section,
 				  struct mtd_oob_region *region)
 {
@@ -66,11 +97,40 @@ static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section,
 	return 0;
 }
 
+static int w35n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	region->offset = (16 * section) + 12;
+	region->length = 4;
+
+	return 0;
+}
+
+static int w35n01jw_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	region->offset = (16 * section) + 2;
+	region->length = 10;
+
+	return 0;
+}
+
 static const struct mtd_ooblayout_ops w25m02gv_ooblayout = {
 	.ecc = w25m02gv_ooblayout_ecc,
 	.free = w25m02gv_ooblayout_free,
 };
 
+static const struct mtd_ooblayout_ops w35n01jw_ooblayout = {
+	.ecc = w35n01jw_ooblayout_ecc,
+	.free = w35n01jw_ooblayout_free,
+};
+
 static int w25m02gv_select_target(struct spinand_device *spinand,
 				  unsigned int target)
 {
@@ -105,6 +165,17 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants_w25xxgv),
 		     0,
 		     SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
+	SPINAND_INFO("W35N01JW",
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc),
+		     NAND_MEMORG(1, 4096, 128, 64, 512, 20, 1, 1, 1),
+		     NAND_ECCREQ(1, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_w35n01jw,
+					      &write_cache_variants_w35n01jw,
+					      &update_cache_variants_w35n01jw),
+		     SPINAND_HAS_OCTAL_DTR_BIT | SPINAND_HAS_CR_FEAT_BIT,
+		     SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
+		     SPINAND_INFO_CTRL_OPS_VARIANTS(&ctrl_ops_variants_w35n01jw)),
+
 };
 
 static int winbond_spinand_init(struct spinand_device *spinand)
Signature · 1 line
2.25.1