Skip to contentSkip to conversation

mtd: spi-nor: parse SFDP tables to setup (Q)SPI memories

PATCH Applied Cyrille Pitchen 4 participants 27 messages Mar 22, 2017 – Apr 15, 2017

Subsystems (5)

v5 of 7 Applied Latest is v7 →

lore
Share & tools
lore
#0 lore raw

v5 cover mtd: spi-nor: parse SFDP tables to setup (Q)SPI memories

Hi all,

based on git-hub/spi-nor and likely applicable on linux-next within the next few days.

This new series of patchs aims to upgrade to spi-nor framework. We need to take into account latest SPI memories which cannot be handled correctly by the current implementation.

For instance, SPI NOR memories like Spansion S25FS512S or Macronix MX25U4035 support only the Fast Read 1-4-4 (EBh) command but not the Fast Read 1-1-4 (6Bh) command. However the current spi-nor framework supports only Fast Read 1-1-4 (6Bh).

Also the Spansion S25FS512S memory (and others) may use a non-uniform sector erase map, whereas the spi-nor framework assumes that a single sector erase size and opcode can be used anywhere inside the data array. This assumption is no longer valid.

Then parsing SFDP tables is an attempt to solve many of those issues by discovering dynamically most of the parameters and settings of the SPI NOR memory:
- the flash size
- the page size for Page Program commands
- the supported Fast Read commands with the associated opcodes and number of mode/wait-state (dummy) cycles.
- the supported Sector/Block Erase commands with the associated opcodes and sizes.
- the erase sector map (for non-uniform memory).

Besides, most QSPI controllers from the different vendors are capable to support the SPI 1-2-2 and 1-4-4 protocols but the spi-nor framework was not ready to use them. This series also fixes this issue and computes the best match between the hardware capabilies of both the SPI memory and the SPI controler (master) to select the right opcodes and dummy cycles for Fast Read and Page Program operations.

The new 'struct spi_nor_hwcaps' uses a bitmask to describe all the supported hardware capabilies and makes the difference between Fast Read and Page Program operations and also between the different SPI protocols (SPI 1-1-2 vs SPI 1-2-2 or SPI 1-1-4 vs SPI 1-4-4).

IMHO, the first 3 patches of this series are ready to be merged into the github/spi-nor tree. Marek, do you agree with that?

Artur, patch 1 introduces some basic support to Octal SPI to help you with your series. About the op code values, I would like to wait for some JEDEC specification to provide the reference values or at least the datasheets of different vendors to guess the actual standard. For now, IMHO, only one manufacturer is not enough.

Kamal, if you don't mind I've put your Signed-off-by in patch 3 since the original patch is from you so I wanted to give you the credits :) I've just fixed some issue: in the original version, set4_byte() was called from spi_nor_init() even if (nor->addr_width == 3).
I hope it will help you with the suspend/resume series.

The 3 last patches are RFC and are provided so it's more easy for reviewers to understand the direction I would like to follow to upgrade the spi-nor framework.

Marcin, I haven't finished yet another 7th patch to parse the sector erase map table from SFDP so for now patch 4 is almost useless. Besides I only tested it with uniform memories to avoid regressions. I may need your help to test it with non-uniform memories, if you don't mind :)

Best regards,

Cyrille

ChangeLog:

v4 -> v5
- rework the whole series.
- introduce support for Octo SPI protocols.
- introduce support for Double Transfer Rate (DTR) protocols.
- introduce support for memories with non-uniform sector erase sizes.

v3 -> v4
- replace dev_info() by dev_dbg() in patch 1.
- split former patch 2 into 2 patches:
+ new patch 2 deals with the rename of SPINOR_OP_READ4_* macros
+ new patch 3 deals with the alternative methode to support memory

Quoted 1 line

16MiB

- add comment in patch 3 to describe the dichotomic search performed by spi_nor_convert_opcode().
- change return type from int to void for m25p80_proto2nbits() in patch 6.
- remove former patches 8 & 9 from the v2 series: the support of the Macronix mx66l1g45g memory will be sent in a separated patch.

v2 -> v3
- tested with new samples: Micron n25q512, n25q01g and Macronix mx25v1635f, mx25l3235f, mx25l3273f.
- add "Reviewed-by: Jagan Teki <[email protected]>" on patch 1.
- add "Tested-by: Vignesh R <[email protected]>" on patch 2.
- fix some checkpatch warnings.
- add call of spi_nor_wait_till_ready() in spansion_new_quad_enable() and sr2_bit7_quad_enable(), as suggested by Joel Esponde on patch 6.
- test JESD216 rev A (minor 5) instead of rev B (minor 6) with the return code of spi_nor_parse_sfdp() from spi_nor_init_params() on patch 6. The seven additional DWORDs of the Basic Flash Parameter Table were introduced in rev A, not rev B, so the 15th DWORD was already available in rev A. The 15th DWORD provides us with the Quad Enable Requirements (QER) bits.
Basic Flash Parameter Table size:
+ JESD216 : 9 DWORDS
+ JESD216A: 16 DWORDS
+ JESD216B: 16 DWORDS

v1 -> v2
- fix patch 3 to resolve compiler errors on hisi-sfc.c and cadence-quadspi.c drivers

Cyrille Pitchen (6):
mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode
mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols
mtd: spi-nor: add spi_nor_init() function
mtd: spi-nor: add support to non-uniform SPI NOR flash memories
mtd: spi-nor: parse Serial Flash Discoverable Parameters (SFDP) tables
mtd: spi-nor: parse SFDP 4-byte Address Instruction Table

 drivers/mtd/devices/m25p80.c          |  130 ++-
 drivers/mtd/spi-nor/aspeed-smc.c      |   23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |   80 +-
 drivers/mtd/spi-nor/cadence-quadspi.c |   18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |    8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |   31 +-
 drivers/mtd/spi-nor/intel-spi.c       |    7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |   16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |   22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 1722 +++++++++++++++++++++++++++++----
 include/linux/mtd/spi-nor.h           |  228 ++++-
 11 files changed, 1983 insertions(+), 302 deletions(-)
Signature · 1 line
2.9.3
#1 lore raw

PATCH v5 1/6 mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

---
 drivers/mtd/devices/m25p80.c          |  16 +-
 drivers/mtd/spi-nor/aspeed-smc.c      |  23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |  80 +++---
 drivers/mtd/spi-nor/cadence-quadspi.c |  18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |   8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |  31 ++-
 drivers/mtd/spi-nor/intel-spi.c       |   7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |  16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |  22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 441 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h           | 158 +++++++++++-
 11 files changed, 643 insertions(+), 177 deletions(-)
drivers/mtd/devices/m25p80.c +9 -7
index c4df3b1bded0..68986a26c8fe 100644
@@ -111,10 +111,10 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 
 static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
 {
-	switch (nor->flash_read) {
-	case SPI_NOR_DUAL:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_2:
 		return 2;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		return 4;
 	default:
 		return 0;
@@ -196,7 +196,9 @@ static int m25p_probe(struct spi_device *spi)
 	struct flash_platform_data	*data;
 	struct m25p *flash;
 	struct spi_nor *nor;
-	enum read_mode mode = SPI_NOR_NORMAL;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ | SNOR_HWCAPS_PP),
+	};
 	char *flash_name;
 	int ret;
 
@@ -222,9 +224,9 @@ static int m25p_probe(struct spi_device *spi)
 	flash->spi = spi;
 
 	if (spi->mode & SPI_RX_QUAD)
-		mode = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	else if (spi->mode & SPI_RX_DUAL)
-		mode = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
 	if (data && data->name)
 		nor->mtd.name = data->name;
@@ -241,7 +243,7 @@ static int m25p_probe(struct spi_device *spi)
 	else
 		flash_name = spi->modalias;
 
-	ret = spi_nor_scan(nor, flash_name, mode);
+	ret = spi_nor_scan(nor, flash_name, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/aspeed-smc.c +13 -10
index 56051d30f000..723026d9cf0c 100644
@@ -585,14 +585,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 	 * TODO: Adjust clocks if fast read is supported and interpret
 	 * SPI-NOR flags to adjust controller settings.
 	 */
-	switch (chip->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-		cmd = CONTROL_COMMAND_MODE_NORMAL;
-		break;
-	case SPI_NOR_FAST:
-		cmd = CONTROL_COMMAND_MODE_FREAD;
-		break;
-	default:
+	if (chip->nor.read_proto == SNOR_PROTO_1_1_1) {
+		if (chip->nor.read_dummy == 0)
+			cmd = CONTROL_COMMAND_MODE_NORMAL;
+		else
+			cmd = CONTROL_COMMAND_MODE_FREAD;
+	} else {
 		dev_err(chip->nor.dev, "unsupported SPI read mode\n");
 		return -EINVAL;
 	}
@@ -608,6 +606,11 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 				  struct device_node *np, struct resource *r)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	const struct aspeed_smc_info *info = controller->info;
 	struct device *dev = controller->dev;
 	struct device_node *child;
@@ -671,11 +674,11 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 			break;
 
 		/*
-		 * TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
+		 * TODO: Add support for Dual and Quad SPI protocols
 		 * attach when board support is present as determined
 		 * by of property.
 		 */
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			break;
 
drivers/mtd/spi-nor/atmel-quadspi.c +53 -27

142 lines not on this page · raw message

drivers/mtd/spi-nor/cadence-quadspi.c +12 -6
index 9f8102de1b16..3f91a3e97892 100644
@@ -855,15 +855,14 @@ static int cqspi_set_protocol(struct spi_nor *nor, const int read)
 	f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 
 	if (read) {
-		switch (nor->flash_read) {
-		case SPI_NOR_NORMAL:
-		case SPI_NOR_FAST:
+		switch (nor->read_proto) {
+		case SNOR_PROTO_1_1_1:
 			f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 			break;
-		case SPI_NOR_DUAL:
+		case SNOR_PROTO_1_1_2:
 			f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
 			break;
-		case SPI_NOR_QUAD:
+		case SNOR_PROTO_1_1_4:
 			f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
 			break;
 		default:
@@ -1069,6 +1068,13 @@ static void cqspi_controller_init(struct cqspi_st *cqspi)
 
 static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct platform_device *pdev = cqspi->pdev;
 	struct device *dev = &pdev->dev;
 	struct cqspi_flash_pdata *f_pdata;
@@ -1123,7 +1129,7 @@ static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 			goto err;
 		}
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto err;
 
drivers/mtd/spi-nor/fsl-quadspi.c +7 -1
index 1476135e0d50..ec9c8e960fd2 100644
@@ -957,6 +957,12 @@ static void fsl_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
 
 static int fsl_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device_node *np = pdev->dev.of_node;
 	struct device *dev = &pdev->dev;
 	struct fsl_qspi *q;
@@ -1065,7 +1071,7 @@ static int fsl_qspi_probe(struct platform_device *pdev)
 		/* set the chip address for READID */
 		fsl_qspi_set_base_addr(q, nor);
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto mutex_failed;
 
drivers/mtd/spi-nor/hisi-sfc.c +23 -8
index a286350627a6..80e2d173abdd 100644
@@ -120,19 +120,24 @@ static inline int wait_op_finish(struct hifmc_host *host)
 		(reg & FMC_INT_OP_DONE), 0, FMC_WAIT_TIMEOUT);
 }
 
-static int get_if_type(enum read_mode flash_read)
+static int get_if_type(enum spi_nor_protocol proto)
 {
 	enum hifmc_iftype if_type;
 
-	switch (flash_read) {
-	case SPI_NOR_DUAL:
+	switch (proto) {
+	case SNOR_PROTO_1_1_2:
 		if_type = IF_TYPE_DUAL;
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_2_2:
+		if_type = IF_TYPE_DIO;
+		break;
+	case SNOR_PROTO_1_1_4:
 		if_type = IF_TYPE_QUAD;
 		break;
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	case SNOR_PROTO_1_4_4:
+		if_type = IF_TYPE_QIO;
+		break;
+	case SNOR_PROTO_1_1_1:
 	default:
 		if_type = IF_TYPE_STD;
 		break;
@@ -253,7 +258,10 @@ static int hisi_spi_nor_dma_transfer(struct spi_nor *nor, loff_t start_off,
 	writel(FMC_DMA_LEN_SET(len), host->regbase + FMC_DMA_LEN);
 
 	reg = OP_CFG_FM_CS(priv->chipselect);
-	if_type = get_if_type(nor->flash_read);
+	if (op_type == FMC_OP_READ)
+		if_type = get_if_type(nor->read_proto);
+	else
+		if_type = get_if_type(nor->write_proto);
 	reg |= OP_CFG_MEM_IF_TYPE(if_type);
 	if (op_type == FMC_OP_READ)
 		reg |= OP_CFG_DUMMY_NUM(nor->read_dummy >> 3);
@@ -321,6 +329,13 @@ static ssize_t hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
 static int hisi_spi_nor_register(struct device_node *np,
 				struct hifmc_host *host)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device *dev = host->dev;
 	struct spi_nor *nor;
 	struct hifmc_priv *priv;
@@ -362,7 +377,7 @@ static int hisi_spi_nor_register(struct device_node *np,
 	nor->read = hisi_spi_nor_read;
 	nor->write = hisi_spi_nor_write;
 	nor->erase = NULL;
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/intel-spi.c +6 -1
index 986a3d020a3a..515aa1f7f4f1 100644
@@ -715,6 +715,11 @@ static void intel_spi_fill_partition(struct intel_spi *ispi,
 struct intel_spi *intel_spi_probe(struct device *dev,
 	struct resource *mem, const struct intel_spi_boardinfo *info)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	struct mtd_partition part;
 	struct intel_spi *ispi;
 	int ret;
@@ -746,7 +751,7 @@ struct intel_spi *intel_spi_probe(struct device *dev,
 	ispi->nor.write = intel_spi_write;
 	ispi->nor.erase = intel_spi_erase;
 
-	ret = spi_nor_scan(&ispi->nor, NULL, SPI_NOR_NORMAL);
+	ret = spi_nor_scan(&ispi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_info(dev, "failed to locate the chip\n");
 		return ERR_PTR(ret);
drivers/mtd/spi-nor/mtk-quadspi.c +11 -5
index e661877c23de..615e258866f7 100644
@@ -121,20 +121,20 @@ static void mt8173_nor_set_read_mode(struct mt8173_nor *mt8173_nor)
 {
 	struct spi_nor *nor = &mt8173_nor->nor;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_1:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_FAST_READ, mt8173_nor->base +
 		       MTK_NOR_CFG1_REG);
 		break;
-	case SPI_NOR_DUAL:
+	case SNOR_PROTO_1_1_2:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_DUAL_READ_EN, mt8173_nor->base +
 		       MTK_NOR_DUAL_REG);
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA4_REG);
 		writeb(MTK_NOR_QUAD_READ_EN, mt8173_nor->base +
@@ -381,6 +381,12 @@ static int mt8173_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
 static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 			struct device_node *flash_node)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_PP),
+	};
 	int ret;
 	struct spi_nor *nor;
 
@@ -399,7 +405,7 @@ static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 	nor->write_reg = mt8173_nor_write_reg;
 	nor->mtd.name = "mtk_nor";
 	/* initialized with NULL */
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_DUAL);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/nxp-spifi.c +12 -10
index 73a14f40928b..c5992e099542 100644
@@ -240,13 +240,12 @@ static int nxp_spifi_erase(struct spi_nor *nor, loff_t offs)
 
 static int nxp_spifi_setup_memory_cmd(struct nxp_spifi *spifi)
 {
-	switch (spifi->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	switch (spifi->nor.read_proto) {
+	case SNOR_PROTO_1_1_1:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_ALL_SERIAL;
 		break;
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_2:
+	case SNOR_PROTO_1_1_4:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_QUAD_DUAL_DATA;
 		break;
 	default:
@@ -274,7 +273,11 @@ static void nxp_spifi_dummy_id_read(struct spi_nor *nor)
 static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 				 struct device_node *np)
 {
-	enum read_mode flash_read;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	u32 ctrl, property;
 	u16 mode = 0;
 	int ret;
@@ -308,13 +311,12 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 
 	if (mode & SPI_RX_DUAL) {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 	} else if (mode & SPI_RX_QUAD) {
 		ctrl &= ~SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	} else {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_NORMAL;
 	}
 
 	switch (mode & (SPI_CPHA | SPI_CPOL)) {
@@ -351,7 +353,7 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 	 */
 	nxp_spifi_dummy_id_read(&spifi->nor);
 
-	ret = spi_nor_scan(&spifi->nor, NULL, flash_read);
+	ret = spi_nor_scan(&spifi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_err(spifi->dev, "device scan failed\n");
 		return ret;
drivers/mtd/spi-nor/spi-nor.c +348 -93

519 lines not on this page · raw message

include/linux/mtd/spi-nor.h +149 -9

213 lines not on this page · raw message

Signature · 1 line
2.9.3
#2 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

Quoted 20 lines · Cyrille Pitchen

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

Quoted 7 lines

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

The Aspeed controller only supports Dual I/O and a helper similar to the one for Quad I/O would be needed to setup the chip for multiple I/O:

	int (*dual_enable)(struct spi_nor *nor);

Is the approach correct ? or maybe rename the current 'quad_enable()' to 'multiple_enable()' and add an extra parameter.

I have a bunch of patches queued for Dual I/O data support but I think they will conflict with your patches. I will wait for this one to be merged. Then, I can look at Dual I/O address + data support.

Thanks,

C.

Quoted 1,309 lines
---
 drivers/mtd/devices/m25p80.c          |  16 +-
 drivers/mtd/spi-nor/aspeed-smc.c      |  23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |  80 +++---
 drivers/mtd/spi-nor/cadence-quadspi.c |  18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |   8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |  31 ++-
 drivers/mtd/spi-nor/intel-spi.c       |   7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |  16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |  22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 441 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h           | 158 +++++++++++-
 11 files changed, 643 insertions(+), 177 deletions(-)
drivers/mtd/devices/m25p80.c +9 -7
index c4df3b1bded0..68986a26c8fe 100644
@@ -111,10 +111,10 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 
 static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
 {
-	switch (nor->flash_read) {
-	case SPI_NOR_DUAL:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_2:
 		return 2;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		return 4;
 	default:
 		return 0;
@@ -196,7 +196,9 @@ static int m25p_probe(struct spi_device *spi)
 	struct flash_platform_data	*data;
 	struct m25p *flash;
 	struct spi_nor *nor;
-	enum read_mode mode = SPI_NOR_NORMAL;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ | SNOR_HWCAPS_PP),
+	};
 	char *flash_name;
 	int ret;
 
@@ -222,9 +224,9 @@ static int m25p_probe(struct spi_device *spi)
 	flash->spi = spi;
 
 	if (spi->mode & SPI_RX_QUAD)
-		mode = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	else if (spi->mode & SPI_RX_DUAL)
-		mode = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
 	if (data && data->name)
 		nor->mtd.name = data->name;
@@ -241,7 +243,7 @@ static int m25p_probe(struct spi_device *spi)
 	else
 		flash_name = spi->modalias;
 
-	ret = spi_nor_scan(nor, flash_name, mode);
+	ret = spi_nor_scan(nor, flash_name, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/aspeed-smc.c +13 -10
index 56051d30f000..723026d9cf0c 100644
@@ -585,14 +585,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 	 * TODO: Adjust clocks if fast read is supported and interpret
 	 * SPI-NOR flags to adjust controller settings.
 	 */
-	switch (chip->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-		cmd = CONTROL_COMMAND_MODE_NORMAL;
-		break;
-	case SPI_NOR_FAST:
-		cmd = CONTROL_COMMAND_MODE_FREAD;
-		break;
-	default:
+	if (chip->nor.read_proto == SNOR_PROTO_1_1_1) {
+		if (chip->nor.read_dummy == 0)
+			cmd = CONTROL_COMMAND_MODE_NORMAL;
+		else
+			cmd = CONTROL_COMMAND_MODE_FREAD;
+	} else {
 		dev_err(chip->nor.dev, "unsupported SPI read mode\n");
 		return -EINVAL;
 	}
@@ -608,6 +606,11 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 				  struct device_node *np, struct resource *r)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	const struct aspeed_smc_info *info = controller->info;
 	struct device *dev = controller->dev;
 	struct device_node *child;
@@ -671,11 +674,11 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 			break;
 
 		/*
-		 * TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
+		 * TODO: Add support for Dual and Quad SPI protocols
 
 		 * attach when board support is present as determined
 		 * by of property.
 		 */
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			break;
drivers/mtd/spi-nor/atmel-quadspi.c +53 -27
index 47937d9beec6..9f579f7c1733 100644
@@ -275,14 +275,48 @@ static void atmel_qspi_debug_command(struct atmel_qspi *aq,
 
 static int atmel_qspi_run_command(struct atmel_qspi *aq,
 				  const struct atmel_qspi_command *cmd,
-				  u32 ifr_tfrtyp, u32 ifr_width)
+				  u32 ifr_tfrtyp, enum spi_nor_protocol proto)
 {
 	u32 iar, icr, ifr, sr;
 	int err = 0;
 
 	iar = 0;
 	icr = 0;
-	ifr = ifr_tfrtyp | ifr_width;
+	ifr = ifr_tfrtyp;
+
+	/* Set the SPI protocol */
+	switch (proto) {
+	case SNOR_PROTO_1_1_1:
+		ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
+		break;
+
+	case SNOR_PROTO_1_1_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_1_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_IO;
+		break;
+
+	case SNOR_PROTO_1_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_IO;
+		break;
+
+	case SNOR_PROTO_2_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_CMD;
+		break;
+
+	case SNOR_PROTO_4_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_CMD;
+		break;
+
+	default:
+		return -EINVAL;
+	}
 
 	/* Compute instruction parameters */
 	if (cmd->enable.bits.instruction) {
@@ -434,7 +468,7 @@ static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode,
 	cmd.rx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
@@ -450,7 +484,7 @@ static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
 	cmd.tx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
@@ -469,7 +503,7 @@ static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
 	cmd.tx_buf = write_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM,
-				     QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				     nor->write_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -484,7 +518,7 @@ static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs)
 	cmd.instruction = nor->erase_opcode;
 	cmd.address = (u32)offs;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
@@ -493,27 +527,8 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	struct atmel_qspi *aq = nor->priv;
 	struct atmel_qspi_command cmd;
 	u8 num_mode_cycles, num_dummy_cycles;
-	u32 ifr_width;
 	ssize_t ret;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
-		ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
-		break;
-
-	case SPI_NOR_DUAL:
-		ifr_width = QSPI_IFR_WIDTH_DUAL_OUTPUT;
-		break;
-
-	case SPI_NOR_QUAD:
-		ifr_width = QSPI_IFR_WIDTH_QUAD_OUTPUT;
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
 	if (nor->read_dummy >= 2) {
 		num_mode_cycles = 2;
 		num_dummy_cycles = nor->read_dummy - 2;
@@ -536,7 +551,7 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	cmd.rx_buf = read_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ_MEM,
-				     ifr_width);
+				     nor->read_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -590,6 +605,17 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
 
 static int atmel_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_2_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_READ_1_4_4 |
+			 SNOR_HWCAPS_PP |
+			 SNOR_HWCAPS_PP_1_1_4 |
+			 SNOR_HWCAPS_PP_1_4_4),
+	};
 	struct device_node *child, *np = pdev->dev.of_node;
 	struct atmel_qspi *aq;
 	struct resource *res;
@@ -679,7 +705,7 @@ static int atmel_qspi_probe(struct platform_device *pdev)
 	if (err)
 		goto disable_clk;
 
-	err = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	err = spi_nor_scan(nor, NULL, &hwcaps);
 	if (err)
 		goto disable_clk;
 
drivers/mtd/spi-nor/cadence-quadspi.c +12 -6
index 9f8102de1b16..3f91a3e97892 100644
@@ -855,15 +855,14 @@ static int cqspi_set_protocol(struct spi_nor *nor, const int read)
 	f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 
 	if (read) {
-		switch (nor->flash_read) {
-		case SPI_NOR_NORMAL:
-		case SPI_NOR_FAST:
+		switch (nor->read_proto) {
+		case SNOR_PROTO_1_1_1:
 			f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 			break;
-		case SPI_NOR_DUAL:
+		case SNOR_PROTO_1_1_2:
 			f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
 			break;
-		case SPI_NOR_QUAD:
+		case SNOR_PROTO_1_1_4:
 			f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
 			break;
 		default:
@@ -1069,6 +1068,13 @@ static void cqspi_controller_init(struct cqspi_st *cqspi)
 
 static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct platform_device *pdev = cqspi->pdev;
 	struct device *dev = &pdev->dev;
 	struct cqspi_flash_pdata *f_pdata;
@@ -1123,7 +1129,7 @@ static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 			goto err;
 		}
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto err;
 
drivers/mtd/spi-nor/fsl-quadspi.c +7 -1
index 1476135e0d50..ec9c8e960fd2 100644
@@ -957,6 +957,12 @@ static void fsl_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
 
 static int fsl_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device_node *np = pdev->dev.of_node;
 	struct device *dev = &pdev->dev;
 	struct fsl_qspi *q;
@@ -1065,7 +1071,7 @@ static int fsl_qspi_probe(struct platform_device *pdev)
 		/* set the chip address for READID */
 		fsl_qspi_set_base_addr(q, nor);
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto mutex_failed;
 
drivers/mtd/spi-nor/hisi-sfc.c +23 -8
index a286350627a6..80e2d173abdd 100644
@@ -120,19 +120,24 @@ static inline int wait_op_finish(struct hifmc_host *host)
 		(reg & FMC_INT_OP_DONE), 0, FMC_WAIT_TIMEOUT);
 }
 
-static int get_if_type(enum read_mode flash_read)
+static int get_if_type(enum spi_nor_protocol proto)
 {
 	enum hifmc_iftype if_type;
 
-	switch (flash_read) {
-	case SPI_NOR_DUAL:
+	switch (proto) {
+	case SNOR_PROTO_1_1_2:
 		if_type = IF_TYPE_DUAL;
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_2_2:
+		if_type = IF_TYPE_DIO;
+		break;
+	case SNOR_PROTO_1_1_4:
 		if_type = IF_TYPE_QUAD;
 		break;
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	case SNOR_PROTO_1_4_4:
+		if_type = IF_TYPE_QIO;
+		break;
+	case SNOR_PROTO_1_1_1:
 	default:
 		if_type = IF_TYPE_STD;
 		break;
@@ -253,7 +258,10 @@ static int hisi_spi_nor_dma_transfer(struct spi_nor *nor, loff_t start_off,
 	writel(FMC_DMA_LEN_SET(len), host->regbase + FMC_DMA_LEN);
 
 	reg = OP_CFG_FM_CS(priv->chipselect);
-	if_type = get_if_type(nor->flash_read);
+	if (op_type == FMC_OP_READ)
+		if_type = get_if_type(nor->read_proto);
+	else
+		if_type = get_if_type(nor->write_proto);
 	reg |= OP_CFG_MEM_IF_TYPE(if_type);
 	if (op_type == FMC_OP_READ)
 		reg |= OP_CFG_DUMMY_NUM(nor->read_dummy >> 3);
@@ -321,6 +329,13 @@ static ssize_t hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
 static int hisi_spi_nor_register(struct device_node *np,
 				struct hifmc_host *host)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device *dev = host->dev;
 	struct spi_nor *nor;
 	struct hifmc_priv *priv;
@@ -362,7 +377,7 @@ static int hisi_spi_nor_register(struct device_node *np,
 	nor->read = hisi_spi_nor_read;
 	nor->write = hisi_spi_nor_write;
 	nor->erase = NULL;
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/intel-spi.c +6 -1
index 986a3d020a3a..515aa1f7f4f1 100644
@@ -715,6 +715,11 @@ static void intel_spi_fill_partition(struct intel_spi *ispi,
 struct intel_spi *intel_spi_probe(struct device *dev,
 	struct resource *mem, const struct intel_spi_boardinfo *info)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	struct mtd_partition part;
 	struct intel_spi *ispi;
 	int ret;
@@ -746,7 +751,7 @@ struct intel_spi *intel_spi_probe(struct device *dev,
 	ispi->nor.write = intel_spi_write;
 	ispi->nor.erase = intel_spi_erase;
 
-	ret = spi_nor_scan(&ispi->nor, NULL, SPI_NOR_NORMAL);
+	ret = spi_nor_scan(&ispi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_info(dev, "failed to locate the chip\n");
 		return ERR_PTR(ret);
drivers/mtd/spi-nor/mtk-quadspi.c +11 -5
index e661877c23de..615e258866f7 100644
@@ -121,20 +121,20 @@ static void mt8173_nor_set_read_mode(struct mt8173_nor *mt8173_nor)
 {
 	struct spi_nor *nor = &mt8173_nor->nor;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_1:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_FAST_READ, mt8173_nor->base +
 		       MTK_NOR_CFG1_REG);
 		break;
-	case SPI_NOR_DUAL:
+	case SNOR_PROTO_1_1_2:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_DUAL_READ_EN, mt8173_nor->base +
 		       MTK_NOR_DUAL_REG);
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA4_REG);
 		writeb(MTK_NOR_QUAD_READ_EN, mt8173_nor->base +
@@ -381,6 +381,12 @@ static int mt8173_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
 static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 			struct device_node *flash_node)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_PP),
+	};
 	int ret;
 	struct spi_nor *nor;
 
@@ -399,7 +405,7 @@ static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 	nor->write_reg = mt8173_nor_write_reg;
 	nor->mtd.name = "mtk_nor";
 	/* initialized with NULL */
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_DUAL);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/nxp-spifi.c +12 -10
index 73a14f40928b..c5992e099542 100644
@@ -240,13 +240,12 @@ static int nxp_spifi_erase(struct spi_nor *nor, loff_t offs)
 
 static int nxp_spifi_setup_memory_cmd(struct nxp_spifi *spifi)
 {
-	switch (spifi->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	switch (spifi->nor.read_proto) {
+	case SNOR_PROTO_1_1_1:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_ALL_SERIAL;
 		break;
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_2:
+	case SNOR_PROTO_1_1_4:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_QUAD_DUAL_DATA;
 		break;
 	default:
@@ -274,7 +273,11 @@ static void nxp_spifi_dummy_id_read(struct spi_nor *nor)
 static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 				 struct device_node *np)
 {
-	enum read_mode flash_read;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	u32 ctrl, property;
 	u16 mode = 0;
 	int ret;
@@ -308,13 +311,12 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 
 	if (mode & SPI_RX_DUAL) {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 	} else if (mode & SPI_RX_QUAD) {
 		ctrl &= ~SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	} else {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_NORMAL;
 	}
 
 	switch (mode & (SPI_CPHA | SPI_CPOL)) {
@@ -351,7 +353,7 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 	 */
 	nxp_spifi_dummy_id_read(&spifi->nor);
 
-	ret = spi_nor_scan(&spifi->nor, NULL, flash_read);
+	ret = spi_nor_scan(&spifi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_err(spifi->dev, "device scan failed\n");
 		return ret;
drivers/mtd/spi-nor/spi-nor.c +348 -93
index d3cb44b28490..cc443c6cbae8 100644
@@ -150,24 +150,6 @@ static int read_cr(struct spi_nor *nor)
 }
 
 /*
- * Dummy Cycle calculation for different type of read.
- * It can be used to support more commands with
- * different dummy cycle requirements.
- */
-static inline int spi_nor_read_dummy_cycles(struct spi_nor *nor)
-{
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
-		return 8;
-	case SPI_NOR_NORMAL:
-		return 0;
-	}
-	return 0;
-}
-
-/*
  * Write status register 1 byte
  * Returns negative if error occurred.
  */
@@ -221,6 +203,10 @@ static inline u8 spi_nor_convert_3to4_read(u8 opcode)
 		{ SPINOR_OP_READ_1_2_2,	SPINOR_OP_READ_1_2_2_4B },
 		{ SPINOR_OP_READ_1_1_4,	SPINOR_OP_READ_1_1_4_4B },
 		{ SPINOR_OP_READ_1_4_4,	SPINOR_OP_READ_1_4_4_4B },
+
+		{ SPINOR_OP_READ_1_1_1_DTR,	SPINOR_OP_READ_1_1_1_DTR_4B },
+		{ SPINOR_OP_READ_1_2_2_DTR,	SPINOR_OP_READ_1_2_2_DTR_4B },
+		{ SPINOR_OP_READ_1_4_4_DTR,	SPINOR_OP_READ_1_4_4_DTR_4B },
 	};
 
 	return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
@@ -1459,30 +1445,6 @@ static int spansion_quad_enable(struct spi_nor *nor)
 	return 0;
 }
 
-static int set_quad_mode(struct spi_nor *nor, const struct flash_info *info)
-{
-	int status;
-
-	switch (JEDEC_MFR(info)) {
-	case SNOR_MFR_MACRONIX:
-		status = macronix_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Macronix quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	case SNOR_MFR_MICRON:
-		return 0;
-	default:
-		status = spansion_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Spansion quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	}
-}
-
 static int spi_nor_check(struct spi_nor *nor)
 {
 	if (!nor->dev || !nor->read || !nor->write ||
@@ -1535,8 +1497,322 @@ static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor)
 	return 0;
 }
 
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
+
+struct spi_nor_read_command {
+	u8			num_mode_clocks;
+	u8			num_wait_states;
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+struct spi_nor_pp_command {
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+enum spi_nor_read_command_index {
+	SNOR_CMD_READ,
+	SNOR_CMD_READ_FAST,
+	SNOR_CMD_READ_1_1_1_DTR,
+
+	/* Dual SPI */
+	SNOR_CMD_READ_1_1_2,
+	SNOR_CMD_READ_1_2_2,
+	SNOR_CMD_READ_2_2_2,
+	SNOR_CMD_READ_1_2_2_DTR,
+
+	/* Quad SPI */
+	SNOR_CMD_READ_1_1_4,
+	SNOR_CMD_READ_1_4_4,
+	SNOR_CMD_READ_4_4_4,
+	SNOR_CMD_READ_1_4_4_DTR,
+
+	/* Octo SPI */
+	SNOR_CMD_READ_1_1_8,
+	SNOR_CMD_READ_1_8_8,
+	SNOR_CMD_READ_8_8_8,
+	SNOR_CMD_READ_1_8_8_DTR,
+
+	SNOR_CMD_READ_MAX
+};
+
+enum spi_nor_pp_command_index {
+	SNOR_CMD_PP,
+
+	/* Quad SPI */
+	SNOR_CMD_PP_1_1_4,
+	SNOR_CMD_PP_1_4_4,
+	SNOR_CMD_PP_4_4_4,
+
+	/* Octo SPI */
+	SNOR_CMD_PP_1_1_8,
+	SNOR_CMD_PP_1_8_8,
+	SNOR_CMD_PP_8_8_8,
+
+	SNOR_CMD_PP_MAX
+};
+
+struct spi_nor_flash_parameter {
+	u64				size;
+	u32				page_size;
+
+	struct spi_nor_hwcaps		hwcaps;
+	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
+	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
+
+	int (*quad_enable)(struct spi_nor *nor);
+};
+
+
+static inline void
+spi_nor_set_read_settings(struct spi_nor_read_command *read,
+			  u8 num_mode_clocks,
+			  u8 num_wait_states,
+			  u8 opcode,
+			  enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = num_mode_clocks;
+	read->num_wait_states = num_wait_states;
+	read->opcode = opcode;
+	read->proto = proto;
+}
+
+static inline void
+spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+			u8 opcode,
+			enum spi_nor_protocol proto)
+{
+	pp->opcode = opcode;
+	pp->proto = proto;
+}
+
+static int spi_nor_init_params(struct spi_nor *nor,
+			       const struct flash_info *info,
+			       struct spi_nor_flash_parameter *params)
+{
+	/* Set legacy flash parameters as default. */
+	memset(params, 0, sizeof(*params));
+
+	/* Set SPI NOR sizes. */
+	params->size = info->sector_size * info->n_sectors;
+	params->page_size = info->page_size;
+
+	/* (Fast) Read settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_READ;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
+				  0, 0, SPINOR_OP_READ,
+				  SNOR_PROTO_1_1_1);
+	if (!(info->flags & SPI_NOR_NO_FR)) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
+					  0, 8, SPINOR_OP_READ_FAST,
+					  SNOR_PROTO_1_1_1);
+	}
+	if (info->flags & SPI_NOR_DUAL_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
+					  0, 8, SPINOR_OP_READ_1_1_2,
+					  SNOR_PROTO_1_1_2);
+	}
+	if (info->flags & SPI_NOR_QUAD_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+					  0, 8, SPINOR_OP_READ_1_1_4,
+					  SNOR_PROTO_1_1_4);
+	}
+
+	/* Page Program settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_PP;
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+	/* Select the procedure to set the Quad Enable bit. */
+	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
+				   SNOR_HWCAPS_PP_QUAD)) {
+		switch (JEDEC_MFR(info)) {
+		case SNOR_MFR_MACRONIX:
+			params->quad_enable = macronix_quad_enable;
+			break;
+
+		case SNOR_MFR_MICRON:
+			break;
+
+		default:
+			params->quad_enable = spansion_quad_enable;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps)
 {
+	switch (hwcaps) {
+	case SNOR_HWCAPS_READ:			return SNOR_CMD_READ;
+	case SNOR_HWCAPS_READ_FAST:		return SNOR_CMD_READ_FAST;
+	case SNOR_HWCAPS_READ_1_1_1_DTR:	return SNOR_CMD_READ_1_1_1_DTR;
+	case SNOR_HWCAPS_READ_1_1_2:		return SNOR_CMD_READ_1_1_2;
+	case SNOR_HWCAPS_READ_1_2_2:		return SNOR_CMD_READ_1_2_2;
+	case SNOR_HWCAPS_READ_2_2_2:		return SNOR_CMD_READ_2_2_2;
+	case SNOR_HWCAPS_READ_1_2_2_DTR:	return SNOR_CMD_READ_1_2_2_DTR;
+	case SNOR_HWCAPS_READ_1_1_4:		return SNOR_CMD_READ_1_1_4;
+	case SNOR_HWCAPS_READ_1_4_4:		return SNOR_CMD_READ_1_4_4;
+	case SNOR_HWCAPS_READ_4_4_4:		return SNOR_CMD_READ_4_4_4;
+	case SNOR_HWCAPS_READ_1_4_4_DTR:	return SNOR_CMD_READ_1_4_4_DTR;
+	case SNOR_HWCAPS_READ_1_1_8:		return SNOR_CMD_READ_1_1_8;
+	case SNOR_HWCAPS_READ_1_8_8:		return SNOR_CMD_READ_1_8_8;
+	case SNOR_HWCAPS_READ_8_8_8:		return SNOR_CMD_READ_8_8_8;
+	case SNOR_HWCAPS_READ_1_8_8_DTR:	return SNOR_CMD_READ_1_8_8_DTR;
+
+	case SNOR_HWCAPS_PP:			return SNOR_CMD_PP;
+	case SNOR_HWCAPS_PP_1_1_4:		return SNOR_CMD_PP_1_1_4;
+	case SNOR_HWCAPS_PP_1_4_4:		return SNOR_CMD_PP_1_4_4;
+	case SNOR_HWCAPS_PP_4_4_4:		return SNOR_CMD_PP_4_4_4;
+	case SNOR_HWCAPS_PP_1_1_8:		return SNOR_CMD_PP_1_1_8;
+	case SNOR_HWCAPS_PP_1_8_8:		return SNOR_CMD_PP_1_8_8;
+	case SNOR_HWCAPS_PP_8_8_8:		return SNOR_CMD_PP_8_8_8;
+	}
+
+	return -EINVAL;
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,
+			       const struct spi_nor_flash_parameter *params,
+			       u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+	const struct spi_nor_read_command *read;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	read = &params->reads[cmd];
+	nor->read_opcode = read->opcode;
+	nor->read_proto = read->proto;
+
+	/*
+	 * In the spi-nor framework, we don't need to make the difference
+	 * between mode clock cycles and wait state clock cycles.
+	 * Indeed, the value of the mode clock cycles is used by a QSPI
+	 * flash memory to know whether it should enter or leave its 0-4-4
+	 * (Continuous Read / XIP) mode.
+	 * eXecution In Place is out of the scope of the mtd sub-system.
+	 * Hence we choose to merge both mode and wait state clock cycles
+	 * into the so called dummy clock cycles.
+	 */
+	nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+	return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+			     const struct spi_nor_flash_parameter *params,
+			     u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+	const struct spi_nor_pp_command *pp;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	pp = &params->page_programs[cmd];
+	nor->program_opcode = pp->opcode;
+	nor->write_proto = pp->proto;
+	return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+				const struct flash_info *info)
+{
+	struct mtd_info *mtd = &nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+	return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params,
+			 const struct spi_nor_hwcaps *hwcaps)
+{
+	u32 ignored_mask, shared_mask;
+	bool enable_quad_io;
+	int err;
+
+	/*
+	 * Keep only the hardware capabilities supported by both the SPI
+	 * controller and the SPI flash memory.
+	 */
+	shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+	/* SPI protocol classes N-N-N are not supported yet. */
+	ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+			SNOR_HWCAPS_READ_4_4_4 |
+			SNOR_HWCAPS_READ_8_8_8 |
+			SNOR_HWCAPS_PP_4_4_4 |
+			SNOR_HWCAPS_PP_8_8_8);
+	if (shared_mask & ignored_mask) {
+		dev_dbg(nor->dev,
+			"SPI protocol classes N-N-N are not supported yet.\n");
+		shared_mask &= ~ignored_mask;
+	}
+
+	/* Select the (Fast) Read command. */
+	err = spi_nor_select_read(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid (fast) read\n");
+		return err;
+	}
+
+	/* Select the Page Program command. */
+	err = spi_nor_select_pp(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid page program\n");
+		return err;
+	}
+
+	/* Select the Sector Erase command. */
+	err = spi_nor_select_erase(nor, info);
+	if (err) {
+		dev_err(nor->dev, "invalid sector/block erase\n");
+		return err;
+	}
+
+	/* Enable Quad I/O if needed. */
+	enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+			  spi_nor_get_protocol_width(nor->write_proto) == 4);
+	if (enable_quad_io && params->quad_enable)
+		nor->flash_quad_enable = params->quad_enable;
+	else
+		nor->flash_quad_enable = NULL;
+
+	return 0;
+}
+
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps)
+{
+	struct spi_nor_flash_parameter params;
 	const struct flash_info *info = NULL;
 	struct device *dev = nor->dev;
 	struct mtd_info *mtd = &nor->mtd;
@@ -1548,6 +1824,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (ret)
 		return ret;
 
+	/* Reset SPI protocol for all commands */
+	nor->reg_proto = SNOR_PROTO_1_1_1;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
 	if (name)
 		info = spi_nor_match_id(name);
 	/* Try to auto-detect if chip name wasn't specified or not found */
@@ -1580,6 +1861,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		}
 	}
 
+	/* Parse the Serial Flash Discoverable Parameters table */
+	ret = spi_nor_init_params(nor, info, &params);
+	if (ret)
+		return ret;
+
 	mutex_init(&nor->lock);
 
 	/*
@@ -1610,7 +1896,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	mtd->type = MTD_NORFLASH;
 	mtd->writesize = 1;
 	mtd->flags = MTD_CAP_NORFLASH;
-	mtd->size = info->sector_size * info->n_sectors;
+	mtd->size = params.size;
 	mtd->_erase = spi_nor_erase;
 	mtd->_read = spi_nor_read;
 
@@ -1641,76 +1927,47 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (info->flags & NO_CHIP_ERASE)
 		nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
 
-#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
-	/* prefer "small sector" erase if possible */
-	if (info->flags & SECT_4K) {
-		nor->erase_opcode = SPINOR_OP_BE_4K;
-		mtd->erasesize = 4096;
-	} else if (info->flags & SECT_4K_PMC) {
-		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
-		mtd->erasesize = 4096;
-	} else
-#endif
-	{
-		nor->erase_opcode = SPINOR_OP_SE;
-		mtd->erasesize = info->sector_size;
-	}
-
 	if (info->flags & SPI_NOR_NO_ERASE)
 		mtd->flags |= MTD_NO_ERASE;
 
 	mtd->dev.parent = dev;
-	nor->page_size = info->page_size;
+	nor->page_size = params.page_size;
 	mtd->writebufsize = nor->page_size;
 
 	if (np) {
 		/* If we were instantiated by DT, use it */
 		if (of_property_read_bool(np, "m25p,fast-read"))
-			nor->flash_read = SPI_NOR_FAST;
+			params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 		else
-			nor->flash_read = SPI_NOR_NORMAL;
+			params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
 	} else {
 		/* If we weren't instantiated by DT, default to fast-read */
-		nor->flash_read = SPI_NOR_FAST;
+		params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 	}
 
 	/* Some devices cannot do fast-read, no matter what DT tells us */
 	if (info->flags & SPI_NOR_NO_FR)
-		nor->flash_read = SPI_NOR_NORMAL;
+		params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
+
+	/*
+	 * Configure the SPI memory:
+	 * - select op codes for (Fast) Read, Page Program and Sector Erase.
+	 * - set the number of dummy cycles (mode cycles + wait states).
+	 * - set the SPI protocols for register and memory accesses.
+	 * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
+	 */
+	ret = spi_nor_setup(nor, info, &params, hwcaps);
+	if (ret)
+		return ret;
 
-	/* Quad/Dual-read mode takes precedence over fast/normal */
-	if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
-		ret = set_quad_mode(nor, info);
+	if (nor->flash_quad_enable) {
+		ret = nor->flash_quad_enable(nor);
 		if (ret) {
 			dev_err(dev, "quad mode not supported\n");
 			return ret;
 		}
-		nor->flash_read = SPI_NOR_QUAD;
-	} else if (mode == SPI_NOR_DUAL && info->flags & SPI_NOR_DUAL_READ) {
-		nor->flash_read = SPI_NOR_DUAL;
 	}
 
-	/* Default commands */
-	switch (nor->flash_read) {
-	case SPI_NOR_QUAD:
-		nor->read_opcode = SPINOR_OP_READ_1_1_4;
-		break;
-	case SPI_NOR_DUAL:
-		nor->read_opcode = SPINOR_OP_READ_1_1_2;
-		break;
-	case SPI_NOR_FAST:
-		nor->read_opcode = SPINOR_OP_READ_FAST;
-		break;
-	case SPI_NOR_NORMAL:
-		nor->read_opcode = SPINOR_OP_READ;
-		break;
-	default:
-		dev_err(dev, "No Read opcode defined\n");
-		return -EINVAL;
-	}
-
-	nor->program_opcode = SPINOR_OP_PP;
-
 	if (info->addr_width)
 		nor->addr_width = info->addr_width;
 	else if (mtd->size > 0x1000000) {
@@ -1731,8 +1988,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		return -EINVAL;
 	}
 
-	nor->read_dummy = spi_nor_read_dummy_cycles(nor);
-
 	if (info->flags & SPI_S3AN) {
 		ret = s3an_nor_scan(info, nor);
 		if (ret)
include/linux/mtd/spi-nor.h +149 -9
index f2a718030476..732ee6cd5330 100644
@@ -73,6 +73,15 @@
 #define SPINOR_OP_BE_32K_4B	0x5c	/* Erase 32KiB block */
 #define SPINOR_OP_SE_4B		0xdc	/* Sector erase (usually 64KiB) */
 
+/* Double Transfer Rate opcodes - defined in JEDEC JESD216B. */
+#define SPINOR_OP_READ_1_1_1_DTR	0x0d
+#define SPINOR_OP_READ_1_2_2_DTR	0xbd
+#define SPINOR_OP_READ_1_4_4_DTR	0xed
+
+#define SPINOR_OP_READ_1_1_1_DTR_4B	0x0e
+#define SPINOR_OP_READ_1_2_2_DTR_4B	0xbe
+#define SPINOR_OP_READ_1_4_4_DTR_4B	0xee
+
 /* Used for SST flashes only. */
 #define SPINOR_OP_BP		0x02	/* Byte program */
 #define SPINOR_OP_WRDI		0x04	/* Write disable */
@@ -119,13 +128,75 @@
 /* Configuration Register bits. */
 #define CR_QUAD_EN_SPAN		BIT(1)	/* Spansion Quad I/O */
 
-enum read_mode {
-	SPI_NOR_NORMAL = 0,
-	SPI_NOR_FAST,
-	SPI_NOR_DUAL,
-	SPI_NOR_QUAD,
+
+/* Supported SPI protocols */
+#define SNOR_PROTO_WIDTH_MASK	GENMASK(7, 0)
+
+#define SNOR_PROTO_CLASS_MASK	GENMASK(9, 8)
+#define SNOR_PROTO_CLASS_1_1_N	(0x0u << 8)
+#define SNOR_PROTO_CLASS_1_N_N	(0x1u << 8)
+#define SNOR_PROTO_CLASS_N_N_N	(0x2u << 8)
+
+#define SNOR_PROTO_IS_DTR	BIT(10)	/* Double Transfer Rate */
+
+#define SNOR_PROTO_STR(_pclass, _pwidth) \
+	((_pclass) | (_pwidth))
+#define SNOR_PROTO_DTR(_pclass, _pwidth) \
+	(SNOR_PROTO_IS_DTR | (_pclass) | (_pwidth))
+
+enum spi_nor_protocol {
+	SNOR_PROTO_1_1_1 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_1_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 2),
+	SNOR_PROTO_1_1_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 4),
+	SNOR_PROTO_1_1_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 8),
+	SNOR_PROTO_1_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 8),
+	SNOR_PROTO_2_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 2),
+	SNOR_PROTO_4_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 4),
+	SNOR_PROTO_8_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 8),
+
+	SNOR_PROTO_1_1_1_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_2_2_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 8),
 };
 
+static inline bool spi_nor_protocol_is_dtr(enum spi_nor_protocol proto)
+{
+	return (proto & SNOR_PROTO_IS_DTR) == SNOR_PROTO_IS_DTR;
+}
+
+static inline u32 spi_nor_get_protocol_class(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_CLASS_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_width(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_WIDTH_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_inst_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) == SNOR_PROTO_CLASS_N_N_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_addr_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) != SNOR_PROTO_CLASS_1_1_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_data_width(enum spi_nor_protocol proto)
+{
+	return spi_nor_get_protocol_width(proto);
+}
+
+
 #define SPI_NOR_MAX_CMD_SIZE	8
 enum spi_nor_ops {
 	SPI_NOR_OPS_READ = 0,
@@ -154,9 +225,11 @@ enum spi_nor_option_flags {
  * @read_opcode:	the read opcode
  * @read_dummy:		the dummy needed by the read operation
  * @program_opcode:	the program opcode
- * @flash_read:		the mode of the read
  * @sst_write_second:	used by the SST write operation
  * @flags:		flag options for the current SPI-NOR (SNOR_F_*)
+ * @read_proto:		the SPI protocol for read operations
+ * @write_proto:	the SPI protocol for write operations
+ * @reg_proto		the SPI protocol for read_reg/write_reg/erase operations
  * @cmd_buf:		used by the write_reg
  * @prepare:		[OPTIONAL] do some preparations for the
  *			read/write/erase/lock/unlock operations
@@ -173,6 +246,7 @@ enum spi_nor_option_flags {
  * @flash_unlock:	[FLASH-SPECIFIC] unlock a region of the SPI NOR
  * @flash_is_locked:	[FLASH-SPECIFIC] check if a region of the SPI NOR is
  *			completely locked
+ * @flash_quad_enable:	[FLASH-SPECIFIC] set the Quad Enable bit of the SPI NOR
  * @priv:		the private data
  */
 struct spi_nor {
@@ -185,7 +259,9 @@ struct spi_nor {
 	u8			read_opcode;
 	u8			read_dummy;
 	u8			program_opcode;
-	enum read_mode		flash_read;
+	enum spi_nor_protocol	read_proto;
+	enum spi_nor_protocol	write_proto;
+	enum spi_nor_protocol	reg_proto;
 	bool			sst_write_second;
 	u32			flags;
 	u8			cmd_buf[SPI_NOR_MAX_CMD_SIZE];
@@ -204,6 +280,7 @@ struct spi_nor {
 	int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
+	int (*flash_quad_enable)(struct spi_nor *nor);
 
 	void *priv;
 };
@@ -219,11 +296,73 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
 	return mtd_get_of_node(&nor->mtd);
 }
 
+
+/**
+ * struct spi_nor_hwcaps - Structure for describing the hardware capabilies
+ * supported by the SPI controller (bus master).
+ * @mask:		the bitmask listing all the supported hw capabilies
+ */
+struct spi_nor_hwcaps {
+	u32	mask;
+};
+
+/*
+ *(Fast) Read capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * As a matter of performances, it is relevant to use Octo SPI protocols first,
+ * then Quad SPI protocols before Dual SPI protocols, Fast Read and lastly
+ * (Slow) Read.
+ */
+#define SNOR_HWCAPS_READ_MASK		GENMASK(15, 0)
+#define SNOR_HWCAPS_READ		BIT(0)
+#define SNOR_HWCAPS_READ_FAST		BIT(1)
+#define SNOR_HWCAPS_READ_1_1_1_DTR	BIT(2)
+
+#define SNOR_HWCAPS_READ_DUAL		GENMASK(7, 4)
+#define SNOR_HWCAPS_READ_1_1_2		BIT(4)
+#define SNOR_HWCAPS_READ_1_2_2		BIT(5)
+#define SNOR_HWCAPS_READ_2_2_2		BIT(6)
+#define SNOR_HWCAPS_READ_1_2_2_DTR	BIT(7)
+
+#define SNOR_HWCAPS_READ_QUAD		GENMASK(11, 8)
+#define SNOR_HWCAPS_READ_1_1_4		BIT(8)
+#define SNOR_HWCAPS_READ_1_4_4		BIT(9)
+#define SNOR_HWCAPS_READ_4_4_4		BIT(10)
+#define SNOR_HWCAPS_READ_1_4_4_DTR	BIT(11)
+
+#define SNOR_HWCPAS_READ_OCTO		GENMASK(15, 12)
+#define SNOR_HWCAPS_READ_1_1_8		BIT(12)
+#define SNOR_HWCAPS_READ_1_8_8		BIT(13)
+#define SNOR_HWCAPS_READ_8_8_8		BIT(14)
+#define SNOR_HWCAPS_READ_1_8_8_DTR	BIT(15)
+
+/*
+ * Page Program capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * Like (Fast) Read capabilities, Octo/Quad SPI protocols are preferred to the
+ * legacy SPI 1-1-1 protocol.
+ * Note that Dual Page Programs are not supported because there is no existing
+ * JEDEC/SFDP standard to define them. Also at this moment no SPI flash memory
+ * implements such commands.
+ */
+#define SNOR_HWCAPS_PP_MASK	GENMASK(22, 16)
+#define SNOR_HWCAPS_PP		BIT(16)
+
+#define SNOR_HWCAPS_PP_QUAD	GENMASK(19, 17)
+#define SNOR_HWCAPS_PP_1_1_4	BIT(17)
+#define SNOR_HWCAPS_PP_1_4_4	BIT(18)
+#define SNOR_HWCAPS_PP_4_4_4	BIT(19)
+
+#define SNOR_HWCAPS_PP_OCTO	GENMASK(22, 20)
+#define SNOR_HWCAPS_PP_1_1_8	BIT(20)
+#define SNOR_HWCAPS_PP_1_8_8	BIT(21)
+#define SNOR_HWCAPS_PP_8_8_8	BIT(22)
+
 /**
  * spi_nor_scan() - scan the SPI NOR
  * @nor:	the spi_nor structure
  * @name:	the chip type name
- * @mode:	the read mode supported by the driver
+ * @hwcaps:	the hardware capabilities supported by the controller driver
  *
  * The drivers can use this fuction to scan the SPI NOR.
  * In the scanning, it will try to get all the necessary information to
@@ -233,6 +372,7 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
  *
  * Return: 0 for success, others for failure.
  */
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode);
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps);
 
 #endif
#3 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

Hi Cédic,

Quoted 21 lines · Cédric Le Goater
Quoted 20 lines · Cyrille Pitchen

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

Quoted 6 lines
Quoted 6 lines

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad

.enable_quad_io() was renamed into .quad_enable()

Quoted 11 lines
Quoted 1 line

Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

The Aspeed controller only supports Dual I/O and a helper similar to the one for Quad I/O would be needed to setup the chip for multiple I/O:

	int (*dual_enable)(struct spi_nor *nor);

Is the approach correct ? or maybe rename the current 'quad_enable()' to 'multiple_enable()' and add an extra parameter.

No, nor->flash_quad_enable() [ = params->quad_enable() ] is a flash specific handler as described in spi-nor.h and like other flash specific handlers such as flash_lock(), flash_unlock() or flash_is_locked(), is only used internally in spi-nor.c: spi-nor controller drivers like aspeed-smc.c should not use those handlers at all.

the choice of the nor->flash_quad_enable() handler depends only on the memory manufacturer (and the memory part):
- (none): Micron
- macronix_quad_enable: Macronix
- spansion_quad_enable: Spansion, Winbond, ...
- spansion_new_quad_enable: Spansion (latest memories), ...
- sr2_bit7_quad_enable: ??? (defined in the JESD216B specification)

The purpose of those functions is to implement the vendor specific procedure to set the so called "Quad Enable" (QE) bit in some Status Register of the SPI NOR memory.

Indeed, most QSPI memories are pin to pin compatible with legacy SPI memories. 2 pins of those memories were dedicated to the Write Protect (WP) and Reset/Hold (RST) functions. Hence before using any Quad SPI commands, almost all QSPI memories (Micron being the only exception I know) require us to set their QE bit so the WP and RST pins are reassigned to functions IO2 and IO3, the 3rd and 4th IO lines needed by SPI 1-1-4 and SPI 1-4-4 protocols. Then the Write Protect and Hold/Reset functions are disabled.

Quoted 1 line

From a software point of view, there is nothing to do before using the

SPI 1-1-2 or SPI 1-2-2 protocols. As a matter of fact, with those protocols, MISO and MOSI pins are simply reassigned to functions IO0 and IO1.

Back to the SPI flash controller, if the hardware needs to be configured in some way to use any Dual or Quad SPI protocols, it has to be done before calling spi_nor_scan() or more likely directly inside the controller driver specific implementation of nor->read(), nor->write().

With nor->read(), the driver has to check the value of nor->read_proto to know the actual number of I/O lines used during Instruction (x), Address/Dummy (y) and Data (z) clock cycles: nor->read_proto provides the driver with the SPI x-y-z protocol to be used.

Also nor->write() has to check the value of nor->write_proto.

[ nor->erase() has to check the value of nor->reg_proto: I removed nor->erase_proto since it always had the same value as nor->reg_proto. ]

Both nor->write() and nor->read are controller driver specific: they must be implemented by the controller driver and set before calling spi_nor_scan().

Then nor->read_proto and nor->write_proto are chosen from spi_nor_scan() based on the actual hardware capabilities shared by both the SPI memory and controller. The SPI controller driver tells the spi-nor framework which SPI protocols it supports or wants to use by setting the new 'struct spi_nor_hwcaps' argument of spi_nor_scan() accordingly.

You can have a look at the atmel-quadspi.c driver to have an example of what to do to support the SPI 1-2-2 or SPI 1-4-4 protocols at the controller side.

I've updated the Atmel Quad SPI driver since I'm the maintainer of this driver so I know the exact hardware capabilities of this controller but I don't have such a knowledge for Quad SPI controllers of other vendors.

Please note that patch is conservative: if a controller driver like the Aspeed one currently doesn't support the Quad or Dual SPI protocols, the patch doesn't enable them in hwcaps.mask.

Maintainers of the controller drivers will have to, if they want, extend their driver to add the support of new SPI protocols, otherwise those drivers will keep on working exactly as they used to do before this series. For instance, a controller driver which used SPI_NOR_QUAD before still uses the SPI 1-1-4 protocol.

Best regards,

Cyrille

Quoted 1,325 lines

I have a bunch of patches queued for Dual I/O data support but I think they will conflict with your patches. I will wait for this one to be merged. Then, I can look at Dual I/O address + data support.

Thanks,

C.

Quoted 1,301 lines
---
 drivers/mtd/devices/m25p80.c          |  16 +-
 drivers/mtd/spi-nor/aspeed-smc.c      |  23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |  80 +++---
 drivers/mtd/spi-nor/cadence-quadspi.c |  18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |   8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |  31 ++-
 drivers/mtd/spi-nor/intel-spi.c       |   7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |  16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |  22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 441 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h           | 158 +++++++++++-
 11 files changed, 643 insertions(+), 177 deletions(-)
drivers/mtd/devices/m25p80.c +9 -7
index c4df3b1bded0..68986a26c8fe 100644
@@ -111,10 +111,10 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 
 static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
 {
-	switch (nor->flash_read) {
-	case SPI_NOR_DUAL:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_2:
 		return 2;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		return 4;
 	default:
 		return 0;
@@ -196,7 +196,9 @@ static int m25p_probe(struct spi_device *spi)
 	struct flash_platform_data	*data;
 	struct m25p *flash;
 	struct spi_nor *nor;
-	enum read_mode mode = SPI_NOR_NORMAL;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ | SNOR_HWCAPS_PP),
+	};
 	char *flash_name;
 	int ret;
 
@@ -222,9 +224,9 @@ static int m25p_probe(struct spi_device *spi)
 	flash->spi = spi;
 
 	if (spi->mode & SPI_RX_QUAD)
-		mode = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	else if (spi->mode & SPI_RX_DUAL)
-		mode = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
 	if (data && data->name)
 		nor->mtd.name = data->name;
@@ -241,7 +243,7 @@ static int m25p_probe(struct spi_device *spi)
 	else
 		flash_name = spi->modalias;
 
-	ret = spi_nor_scan(nor, flash_name, mode);
+	ret = spi_nor_scan(nor, flash_name, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/aspeed-smc.c +13 -10
index 56051d30f000..723026d9cf0c 100644
@@ -585,14 +585,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 	 * TODO: Adjust clocks if fast read is supported and interpret
 	 * SPI-NOR flags to adjust controller settings.
 	 */
-	switch (chip->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-		cmd = CONTROL_COMMAND_MODE_NORMAL;
-		break;
-	case SPI_NOR_FAST:
-		cmd = CONTROL_COMMAND_MODE_FREAD;
-		break;
-	default:
+	if (chip->nor.read_proto == SNOR_PROTO_1_1_1) {
+		if (chip->nor.read_dummy == 0)
+			cmd = CONTROL_COMMAND_MODE_NORMAL;
+		else
+			cmd = CONTROL_COMMAND_MODE_FREAD;
+	} else {
 		dev_err(chip->nor.dev, "unsupported SPI read mode\n");
 		return -EINVAL;
 	}
@@ -608,6 +606,11 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 				  struct device_node *np, struct resource *r)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	const struct aspeed_smc_info *info = controller->info;
 	struct device *dev = controller->dev;
 	struct device_node *child;
@@ -671,11 +674,11 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 			break;
 
 		/*
-		 * TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
+		 * TODO: Add support for Dual and Quad SPI protocols
 
 		 * attach when board support is present as determined
 		 * by of property.
 		 */
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			break;
drivers/mtd/spi-nor/atmel-quadspi.c +53 -27
index 47937d9beec6..9f579f7c1733 100644
@@ -275,14 +275,48 @@ static void atmel_qspi_debug_command(struct atmel_qspi *aq,
 
 static int atmel_qspi_run_command(struct atmel_qspi *aq,
 				  const struct atmel_qspi_command *cmd,
-				  u32 ifr_tfrtyp, u32 ifr_width)
+				  u32 ifr_tfrtyp, enum spi_nor_protocol proto)
 {
 	u32 iar, icr, ifr, sr;
 	int err = 0;
 
 	iar = 0;
 	icr = 0;
-	ifr = ifr_tfrtyp | ifr_width;
+	ifr = ifr_tfrtyp;
+
+	/* Set the SPI protocol */
+	switch (proto) {
+	case SNOR_PROTO_1_1_1:
+		ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
+		break;
+
+	case SNOR_PROTO_1_1_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_1_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_IO;
+		break;
+
+	case SNOR_PROTO_1_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_IO;
+		break;
+
+	case SNOR_PROTO_2_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_CMD;
+		break;
+
+	case SNOR_PROTO_4_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_CMD;
+		break;
+
+	default:
+		return -EINVAL;
+	}
 
 	/* Compute instruction parameters */
 	if (cmd->enable.bits.instruction) {
@@ -434,7 +468,7 @@ static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode,
 	cmd.rx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
@@ -450,7 +484,7 @@ static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
 	cmd.tx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
@@ -469,7 +503,7 @@ static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
 	cmd.tx_buf = write_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM,
-				     QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				     nor->write_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -484,7 +518,7 @@ static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs)
 	cmd.instruction = nor->erase_opcode;
 	cmd.address = (u32)offs;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
@@ -493,27 +527,8 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	struct atmel_qspi *aq = nor->priv;
 	struct atmel_qspi_command cmd;
 	u8 num_mode_cycles, num_dummy_cycles;
-	u32 ifr_width;
 	ssize_t ret;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
-		ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
-		break;
-
-	case SPI_NOR_DUAL:
-		ifr_width = QSPI_IFR_WIDTH_DUAL_OUTPUT;
-		break;
-
-	case SPI_NOR_QUAD:
-		ifr_width = QSPI_IFR_WIDTH_QUAD_OUTPUT;
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
 	if (nor->read_dummy >= 2) {
 		num_mode_cycles = 2;
 		num_dummy_cycles = nor->read_dummy - 2;
@@ -536,7 +551,7 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	cmd.rx_buf = read_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ_MEM,
-				     ifr_width);
+				     nor->read_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -590,6 +605,17 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
 
 static int atmel_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_2_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_READ_1_4_4 |
+			 SNOR_HWCAPS_PP |
+			 SNOR_HWCAPS_PP_1_1_4 |
+			 SNOR_HWCAPS_PP_1_4_4),
+	};
 	struct device_node *child, *np = pdev->dev.of_node;
 	struct atmel_qspi *aq;
 	struct resource *res;
@@ -679,7 +705,7 @@ static int atmel_qspi_probe(struct platform_device *pdev)
 	if (err)
 		goto disable_clk;
 
-	err = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	err = spi_nor_scan(nor, NULL, &hwcaps);
 	if (err)
 		goto disable_clk;
 
drivers/mtd/spi-nor/cadence-quadspi.c +12 -6
index 9f8102de1b16..3f91a3e97892 100644
@@ -855,15 +855,14 @@ static int cqspi_set_protocol(struct spi_nor *nor, const int read)
 	f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 
 	if (read) {
-		switch (nor->flash_read) {
-		case SPI_NOR_NORMAL:
-		case SPI_NOR_FAST:
+		switch (nor->read_proto) {
+		case SNOR_PROTO_1_1_1:
 			f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 			break;
-		case SPI_NOR_DUAL:
+		case SNOR_PROTO_1_1_2:
 			f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
 			break;
-		case SPI_NOR_QUAD:
+		case SNOR_PROTO_1_1_4:
 			f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
 			break;
 		default:
@@ -1069,6 +1068,13 @@ static void cqspi_controller_init(struct cqspi_st *cqspi)
 
 static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct platform_device *pdev = cqspi->pdev;
 	struct device *dev = &pdev->dev;
 	struct cqspi_flash_pdata *f_pdata;
@@ -1123,7 +1129,7 @@ static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 			goto err;
 		}
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto err;
 
drivers/mtd/spi-nor/fsl-quadspi.c +7 -1
index 1476135e0d50..ec9c8e960fd2 100644
@@ -957,6 +957,12 @@ static void fsl_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
 
 static int fsl_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device_node *np = pdev->dev.of_node;
 	struct device *dev = &pdev->dev;
 	struct fsl_qspi *q;
@@ -1065,7 +1071,7 @@ static int fsl_qspi_probe(struct platform_device *pdev)
 		/* set the chip address for READID */
 		fsl_qspi_set_base_addr(q, nor);
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto mutex_failed;
 
drivers/mtd/spi-nor/hisi-sfc.c +23 -8
index a286350627a6..80e2d173abdd 100644
@@ -120,19 +120,24 @@ static inline int wait_op_finish(struct hifmc_host *host)
 		(reg & FMC_INT_OP_DONE), 0, FMC_WAIT_TIMEOUT);
 }
 
-static int get_if_type(enum read_mode flash_read)
+static int get_if_type(enum spi_nor_protocol proto)
 {
 	enum hifmc_iftype if_type;
 
-	switch (flash_read) {
-	case SPI_NOR_DUAL:
+	switch (proto) {
+	case SNOR_PROTO_1_1_2:
 		if_type = IF_TYPE_DUAL;
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_2_2:
+		if_type = IF_TYPE_DIO;
+		break;
+	case SNOR_PROTO_1_1_4:
 		if_type = IF_TYPE_QUAD;
 		break;
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	case SNOR_PROTO_1_4_4:
+		if_type = IF_TYPE_QIO;
+		break;
+	case SNOR_PROTO_1_1_1:
 	default:
 		if_type = IF_TYPE_STD;
 		break;
@@ -253,7 +258,10 @@ static int hisi_spi_nor_dma_transfer(struct spi_nor *nor, loff_t start_off,
 	writel(FMC_DMA_LEN_SET(len), host->regbase + FMC_DMA_LEN);
 
 	reg = OP_CFG_FM_CS(priv->chipselect);
-	if_type = get_if_type(nor->flash_read);
+	if (op_type == FMC_OP_READ)
+		if_type = get_if_type(nor->read_proto);
+	else
+		if_type = get_if_type(nor->write_proto);
 	reg |= OP_CFG_MEM_IF_TYPE(if_type);
 	if (op_type == FMC_OP_READ)
 		reg |= OP_CFG_DUMMY_NUM(nor->read_dummy >> 3);
@@ -321,6 +329,13 @@ static ssize_t hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
 static int hisi_spi_nor_register(struct device_node *np,
 				struct hifmc_host *host)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device *dev = host->dev;
 	struct spi_nor *nor;
 	struct hifmc_priv *priv;
@@ -362,7 +377,7 @@ static int hisi_spi_nor_register(struct device_node *np,
 	nor->read = hisi_spi_nor_read;
 	nor->write = hisi_spi_nor_write;
 	nor->erase = NULL;
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/intel-spi.c +6 -1
index 986a3d020a3a..515aa1f7f4f1 100644
@@ -715,6 +715,11 @@ static void intel_spi_fill_partition(struct intel_spi *ispi,
 struct intel_spi *intel_spi_probe(struct device *dev,
 	struct resource *mem, const struct intel_spi_boardinfo *info)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	struct mtd_partition part;
 	struct intel_spi *ispi;
 	int ret;
@@ -746,7 +751,7 @@ struct intel_spi *intel_spi_probe(struct device *dev,
 	ispi->nor.write = intel_spi_write;
 	ispi->nor.erase = intel_spi_erase;
 
-	ret = spi_nor_scan(&ispi->nor, NULL, SPI_NOR_NORMAL);
+	ret = spi_nor_scan(&ispi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_info(dev, "failed to locate the chip\n");
 		return ERR_PTR(ret);
drivers/mtd/spi-nor/mtk-quadspi.c +11 -5
index e661877c23de..615e258866f7 100644
@@ -121,20 +121,20 @@ static void mt8173_nor_set_read_mode(struct mt8173_nor *mt8173_nor)
 {
 	struct spi_nor *nor = &mt8173_nor->nor;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_1:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_FAST_READ, mt8173_nor->base +
 		       MTK_NOR_CFG1_REG);
 		break;
-	case SPI_NOR_DUAL:
+	case SNOR_PROTO_1_1_2:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_DUAL_READ_EN, mt8173_nor->base +
 		       MTK_NOR_DUAL_REG);
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA4_REG);
 		writeb(MTK_NOR_QUAD_READ_EN, mt8173_nor->base +
@@ -381,6 +381,12 @@ static int mt8173_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
 static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 			struct device_node *flash_node)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_PP),
+	};
 	int ret;
 	struct spi_nor *nor;
 
@@ -399,7 +405,7 @@ static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 	nor->write_reg = mt8173_nor_write_reg;
 	nor->mtd.name = "mtk_nor";
 	/* initialized with NULL */
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_DUAL);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/nxp-spifi.c +12 -10
index 73a14f40928b..c5992e099542 100644
@@ -240,13 +240,12 @@ static int nxp_spifi_erase(struct spi_nor *nor, loff_t offs)
 
 static int nxp_spifi_setup_memory_cmd(struct nxp_spifi *spifi)
 {
-	switch (spifi->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	switch (spifi->nor.read_proto) {
+	case SNOR_PROTO_1_1_1:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_ALL_SERIAL;
 		break;
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_2:
+	case SNOR_PROTO_1_1_4:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_QUAD_DUAL_DATA;
 		break;
 	default:
@@ -274,7 +273,11 @@ static void nxp_spifi_dummy_id_read(struct spi_nor *nor)
 static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 				 struct device_node *np)
 {
-	enum read_mode flash_read;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	u32 ctrl, property;
 	u16 mode = 0;
 	int ret;
@@ -308,13 +311,12 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 
 	if (mode & SPI_RX_DUAL) {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 	} else if (mode & SPI_RX_QUAD) {
 		ctrl &= ~SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	} else {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_NORMAL;
 	}
 
 	switch (mode & (SPI_CPHA | SPI_CPOL)) {
@@ -351,7 +353,7 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 	 */
 	nxp_spifi_dummy_id_read(&spifi->nor);
 
-	ret = spi_nor_scan(&spifi->nor, NULL, flash_read);
+	ret = spi_nor_scan(&spifi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_err(spifi->dev, "device scan failed\n");
 		return ret;
drivers/mtd/spi-nor/spi-nor.c +348 -93
index d3cb44b28490..cc443c6cbae8 100644
@@ -150,24 +150,6 @@ static int read_cr(struct spi_nor *nor)
 }
 
 /*
- * Dummy Cycle calculation for different type of read.
- * It can be used to support more commands with
- * different dummy cycle requirements.
- */
-static inline int spi_nor_read_dummy_cycles(struct spi_nor *nor)
-{
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
-		return 8;
-	case SPI_NOR_NORMAL:
-		return 0;
-	}
-	return 0;
-}
-
-/*
  * Write status register 1 byte
  * Returns negative if error occurred.
  */
@@ -221,6 +203,10 @@ static inline u8 spi_nor_convert_3to4_read(u8 opcode)
 		{ SPINOR_OP_READ_1_2_2,	SPINOR_OP_READ_1_2_2_4B },
 		{ SPINOR_OP_READ_1_1_4,	SPINOR_OP_READ_1_1_4_4B },
 		{ SPINOR_OP_READ_1_4_4,	SPINOR_OP_READ_1_4_4_4B },
+
+		{ SPINOR_OP_READ_1_1_1_DTR,	SPINOR_OP_READ_1_1_1_DTR_4B },
+		{ SPINOR_OP_READ_1_2_2_DTR,	SPINOR_OP_READ_1_2_2_DTR_4B },
+		{ SPINOR_OP_READ_1_4_4_DTR,	SPINOR_OP_READ_1_4_4_DTR_4B },
 	};
 
 	return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
@@ -1459,30 +1445,6 @@ static int spansion_quad_enable(struct spi_nor *nor)
 	return 0;
 }
 
-static int set_quad_mode(struct spi_nor *nor, const struct flash_info *info)
-{
-	int status;
-
-	switch (JEDEC_MFR(info)) {
-	case SNOR_MFR_MACRONIX:
-		status = macronix_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Macronix quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	case SNOR_MFR_MICRON:
-		return 0;
-	default:
-		status = spansion_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Spansion quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	}
-}
-
 static int spi_nor_check(struct spi_nor *nor)
 {
 	if (!nor->dev || !nor->read || !nor->write ||
@@ -1535,8 +1497,322 @@ static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor)
 	return 0;
 }
 
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
+
+struct spi_nor_read_command {
+	u8			num_mode_clocks;
+	u8			num_wait_states;
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+struct spi_nor_pp_command {
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+enum spi_nor_read_command_index {
+	SNOR_CMD_READ,
+	SNOR_CMD_READ_FAST,
+	SNOR_CMD_READ_1_1_1_DTR,
+
+	/* Dual SPI */
+	SNOR_CMD_READ_1_1_2,
+	SNOR_CMD_READ_1_2_2,
+	SNOR_CMD_READ_2_2_2,
+	SNOR_CMD_READ_1_2_2_DTR,
+
+	/* Quad SPI */
+	SNOR_CMD_READ_1_1_4,
+	SNOR_CMD_READ_1_4_4,
+	SNOR_CMD_READ_4_4_4,
+	SNOR_CMD_READ_1_4_4_DTR,
+
+	/* Octo SPI */
+	SNOR_CMD_READ_1_1_8,
+	SNOR_CMD_READ_1_8_8,
+	SNOR_CMD_READ_8_8_8,
+	SNOR_CMD_READ_1_8_8_DTR,
+
+	SNOR_CMD_READ_MAX
+};
+
+enum spi_nor_pp_command_index {
+	SNOR_CMD_PP,
+
+	/* Quad SPI */
+	SNOR_CMD_PP_1_1_4,
+	SNOR_CMD_PP_1_4_4,
+	SNOR_CMD_PP_4_4_4,
+
+	/* Octo SPI */
+	SNOR_CMD_PP_1_1_8,
+	SNOR_CMD_PP_1_8_8,
+	SNOR_CMD_PP_8_8_8,
+
+	SNOR_CMD_PP_MAX
+};
+
+struct spi_nor_flash_parameter {
+	u64				size;
+	u32				page_size;
+
+	struct spi_nor_hwcaps		hwcaps;
+	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
+	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
+
+	int (*quad_enable)(struct spi_nor *nor);
+};
+
+
+static inline void
+spi_nor_set_read_settings(struct spi_nor_read_command *read,
+			  u8 num_mode_clocks,
+			  u8 num_wait_states,
+			  u8 opcode,
+			  enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = num_mode_clocks;
+	read->num_wait_states = num_wait_states;
+	read->opcode = opcode;
+	read->proto = proto;
+}
+
+static inline void
+spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+			u8 opcode,
+			enum spi_nor_protocol proto)
+{
+	pp->opcode = opcode;
+	pp->proto = proto;
+}
+
+static int spi_nor_init_params(struct spi_nor *nor,
+			       const struct flash_info *info,
+			       struct spi_nor_flash_parameter *params)
+{
+	/* Set legacy flash parameters as default. */
+	memset(params, 0, sizeof(*params));
+
+	/* Set SPI NOR sizes. */
+	params->size = info->sector_size * info->n_sectors;
+	params->page_size = info->page_size;
+
+	/* (Fast) Read settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_READ;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
+				  0, 0, SPINOR_OP_READ,
+				  SNOR_PROTO_1_1_1);
+	if (!(info->flags & SPI_NOR_NO_FR)) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
+					  0, 8, SPINOR_OP_READ_FAST,
+					  SNOR_PROTO_1_1_1);
+	}
+	if (info->flags & SPI_NOR_DUAL_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
+					  0, 8, SPINOR_OP_READ_1_1_2,
+					  SNOR_PROTO_1_1_2);
+	}
+	if (info->flags & SPI_NOR_QUAD_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+					  0, 8, SPINOR_OP_READ_1_1_4,
+					  SNOR_PROTO_1_1_4);
+	}
+
+	/* Page Program settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_PP;
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+	/* Select the procedure to set the Quad Enable bit. */
+	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
+				   SNOR_HWCAPS_PP_QUAD)) {
+		switch (JEDEC_MFR(info)) {
+		case SNOR_MFR_MACRONIX:
+			params->quad_enable = macronix_quad_enable;
+			break;
+
+		case SNOR_MFR_MICRON:
+			break;
+
+		default:
+			params->quad_enable = spansion_quad_enable;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps)
 {
+	switch (hwcaps) {
+	case SNOR_HWCAPS_READ:			return SNOR_CMD_READ;
+	case SNOR_HWCAPS_READ_FAST:		return SNOR_CMD_READ_FAST;
+	case SNOR_HWCAPS_READ_1_1_1_DTR:	return SNOR_CMD_READ_1_1_1_DTR;
+	case SNOR_HWCAPS_READ_1_1_2:		return SNOR_CMD_READ_1_1_2;
+	case SNOR_HWCAPS_READ_1_2_2:		return SNOR_CMD_READ_1_2_2;
+	case SNOR_HWCAPS_READ_2_2_2:		return SNOR_CMD_READ_2_2_2;
+	case SNOR_HWCAPS_READ_1_2_2_DTR:	return SNOR_CMD_READ_1_2_2_DTR;
+	case SNOR_HWCAPS_READ_1_1_4:		return SNOR_CMD_READ_1_1_4;
+	case SNOR_HWCAPS_READ_1_4_4:		return SNOR_CMD_READ_1_4_4;
+	case SNOR_HWCAPS_READ_4_4_4:		return SNOR_CMD_READ_4_4_4;
+	case SNOR_HWCAPS_READ_1_4_4_DTR:	return SNOR_CMD_READ_1_4_4_DTR;
+	case SNOR_HWCAPS_READ_1_1_8:		return SNOR_CMD_READ_1_1_8;
+	case SNOR_HWCAPS_READ_1_8_8:		return SNOR_CMD_READ_1_8_8;
+	case SNOR_HWCAPS_READ_8_8_8:		return SNOR_CMD_READ_8_8_8;
+	case SNOR_HWCAPS_READ_1_8_8_DTR:	return SNOR_CMD_READ_1_8_8_DTR;
+
+	case SNOR_HWCAPS_PP:			return SNOR_CMD_PP;
+	case SNOR_HWCAPS_PP_1_1_4:		return SNOR_CMD_PP_1_1_4;
+	case SNOR_HWCAPS_PP_1_4_4:		return SNOR_CMD_PP_1_4_4;
+	case SNOR_HWCAPS_PP_4_4_4:		return SNOR_CMD_PP_4_4_4;
+	case SNOR_HWCAPS_PP_1_1_8:		return SNOR_CMD_PP_1_1_8;
+	case SNOR_HWCAPS_PP_1_8_8:		return SNOR_CMD_PP_1_8_8;
+	case SNOR_HWCAPS_PP_8_8_8:		return SNOR_CMD_PP_8_8_8;
+	}
+
+	return -EINVAL;
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,
+			       const struct spi_nor_flash_parameter *params,
+			       u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+	const struct spi_nor_read_command *read;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	read = &params->reads[cmd];
+	nor->read_opcode = read->opcode;
+	nor->read_proto = read->proto;
+
+	/*
+	 * In the spi-nor framework, we don't need to make the difference
+	 * between mode clock cycles and wait state clock cycles.
+	 * Indeed, the value of the mode clock cycles is used by a QSPI
+	 * flash memory to know whether it should enter or leave its 0-4-4
+	 * (Continuous Read / XIP) mode.
+	 * eXecution In Place is out of the scope of the mtd sub-system.
+	 * Hence we choose to merge both mode and wait state clock cycles
+	 * into the so called dummy clock cycles.
+	 */
+	nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+	return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+			     const struct spi_nor_flash_parameter *params,
+			     u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+	const struct spi_nor_pp_command *pp;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	pp = &params->page_programs[cmd];
+	nor->program_opcode = pp->opcode;
+	nor->write_proto = pp->proto;
+	return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+				const struct flash_info *info)
+{
+	struct mtd_info *mtd = &nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+	return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params,
+			 const struct spi_nor_hwcaps *hwcaps)
+{
+	u32 ignored_mask, shared_mask;
+	bool enable_quad_io;
+	int err;
+
+	/*
+	 * Keep only the hardware capabilities supported by both the SPI
+	 * controller and the SPI flash memory.
+	 */
+	shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+	/* SPI protocol classes N-N-N are not supported yet. */
+	ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+			SNOR_HWCAPS_READ_4_4_4 |
+			SNOR_HWCAPS_READ_8_8_8 |
+			SNOR_HWCAPS_PP_4_4_4 |
+			SNOR_HWCAPS_PP_8_8_8);
+	if (shared_mask & ignored_mask) {
+		dev_dbg(nor->dev,
+			"SPI protocol classes N-N-N are not supported yet.\n");
+		shared_mask &= ~ignored_mask;
+	}
+
+	/* Select the (Fast) Read command. */
+	err = spi_nor_select_read(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid (fast) read\n");
+		return err;
+	}
+
+	/* Select the Page Program command. */
+	err = spi_nor_select_pp(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid page program\n");
+		return err;
+	}
+
+	/* Select the Sector Erase command. */
+	err = spi_nor_select_erase(nor, info);
+	if (err) {
+		dev_err(nor->dev, "invalid sector/block erase\n");
+		return err;
+	}
+
+	/* Enable Quad I/O if needed. */
+	enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+			  spi_nor_get_protocol_width(nor->write_proto) == 4);
+	if (enable_quad_io && params->quad_enable)
+		nor->flash_quad_enable = params->quad_enable;
+	else
+		nor->flash_quad_enable = NULL;
+
+	return 0;
+}
+
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps)
+{
+	struct spi_nor_flash_parameter params;
 	const struct flash_info *info = NULL;
 	struct device *dev = nor->dev;
 	struct mtd_info *mtd = &nor->mtd;
@@ -1548,6 +1824,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (ret)
 		return ret;
 
+	/* Reset SPI protocol for all commands */
+	nor->reg_proto = SNOR_PROTO_1_1_1;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
 	if (name)
 		info = spi_nor_match_id(name);
 	/* Try to auto-detect if chip name wasn't specified or not found */
@@ -1580,6 +1861,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		}
 	}
 
+	/* Parse the Serial Flash Discoverable Parameters table */
+	ret = spi_nor_init_params(nor, info, &params);
+	if (ret)
+		return ret;
+
 	mutex_init(&nor->lock);
 
 	/*
@@ -1610,7 +1896,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	mtd->type = MTD_NORFLASH;
 	mtd->writesize = 1;
 	mtd->flags = MTD_CAP_NORFLASH;
-	mtd->size = info->sector_size * info->n_sectors;
+	mtd->size = params.size;
 	mtd->_erase = spi_nor_erase;
 	mtd->_read = spi_nor_read;
 
@@ -1641,76 +1927,47 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (info->flags & NO_CHIP_ERASE)
 		nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
 
-#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
-	/* prefer "small sector" erase if possible */
-	if (info->flags & SECT_4K) {
-		nor->erase_opcode = SPINOR_OP_BE_4K;
-		mtd->erasesize = 4096;
-	} else if (info->flags & SECT_4K_PMC) {
-		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
-		mtd->erasesize = 4096;
-	} else
-#endif
-	{
-		nor->erase_opcode = SPINOR_OP_SE;
-		mtd->erasesize = info->sector_size;
-	}
-
 	if (info->flags & SPI_NOR_NO_ERASE)
 		mtd->flags |= MTD_NO_ERASE;
 
 	mtd->dev.parent = dev;
-	nor->page_size = info->page_size;
+	nor->page_size = params.page_size;
 	mtd->writebufsize = nor->page_size;
 
 	if (np) {
 		/* If we were instantiated by DT, use it */
 		if (of_property_read_bool(np, "m25p,fast-read"))
-			nor->flash_read = SPI_NOR_FAST;
+			params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 		else
-			nor->flash_read = SPI_NOR_NORMAL;
+			params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
 	} else {
 		/* If we weren't instantiated by DT, default to fast-read */
-		nor->flash_read = SPI_NOR_FAST;
+		params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 	}
 
 	/* Some devices cannot do fast-read, no matter what DT tells us */
 	if (info->flags & SPI_NOR_NO_FR)
-		nor->flash_read = SPI_NOR_NORMAL;
+		params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
+
+	/*
+	 * Configure the SPI memory:
+	 * - select op codes for (Fast) Read, Page Program and Sector Erase.
+	 * - set the number of dummy cycles (mode cycles + wait states).
+	 * - set the SPI protocols for register and memory accesses.
+	 * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
+	 */
+	ret = spi_nor_setup(nor, info, &params, hwcaps);
+	if (ret)
+		return ret;
 
-	/* Quad/Dual-read mode takes precedence over fast/normal */
-	if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
-		ret = set_quad_mode(nor, info);
+	if (nor->flash_quad_enable) {
+		ret = nor->flash_quad_enable(nor);
 		if (ret) {
 			dev_err(dev, "quad mode not supported\n");
 			return ret;
 		}
-		nor->flash_read = SPI_NOR_QUAD;
-	} else if (mode == SPI_NOR_DUAL && info->flags & SPI_NOR_DUAL_READ) {
-		nor->flash_read = SPI_NOR_DUAL;
 	}
 
-	/* Default commands */
-	switch (nor->flash_read) {
-	case SPI_NOR_QUAD:
-		nor->read_opcode = SPINOR_OP_READ_1_1_4;
-		break;
-	case SPI_NOR_DUAL:
-		nor->read_opcode = SPINOR_OP_READ_1_1_2;
-		break;
-	case SPI_NOR_FAST:
-		nor->read_opcode = SPINOR_OP_READ_FAST;
-		break;
-	case SPI_NOR_NORMAL:
-		nor->read_opcode = SPINOR_OP_READ;
-		break;
-	default:
-		dev_err(dev, "No Read opcode defined\n");
-		return -EINVAL;
-	}
-
-	nor->program_opcode = SPINOR_OP_PP;
-
 	if (info->addr_width)
 		nor->addr_width = info->addr_width;
 	else if (mtd->size > 0x1000000) {
@@ -1731,8 +1988,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		return -EINVAL;
 	}
 
-	nor->read_dummy = spi_nor_read_dummy_cycles(nor);
-
 	if (info->flags & SPI_S3AN) {
 		ret = s3an_nor_scan(info, nor);
 		if (ret)
include/linux/mtd/spi-nor.h +147 -8
index f2a718030476..732ee6cd5330 100644
@@ -73,6 +73,15 @@
 #define SPINOR_OP_BE_32K_4B	0x5c	/* Erase 32KiB block */
 #define SPINOR_OP_SE_4B		0xdc	/* Sector erase (usually 64KiB) */
 
+/* Double Transfer Rate opcodes - defined in JEDEC JESD216B. */
+#define SPINOR_OP_READ_1_1_1_DTR	0x0d
+#define SPINOR_OP_READ_1_2_2_DTR	0xbd
+#define SPINOR_OP_READ_1_4_4_DTR	0xed
+
+#define SPINOR_OP_READ_1_1_1_DTR_4B	0x0e
+#define SPINOR_OP_READ_1_2_2_DTR_4B	0xbe
+#define SPINOR_OP_READ_1_4_4_DTR_4B	0xee
+
 /* Used for SST flashes only. */
 #define SPINOR_OP_BP		0x02	/* Byte program */
 #define SPINOR_OP_WRDI		0x04	/* Write disable */
@@ -119,13 +128,75 @@
 /* Configuration Register bits. */
 #define CR_QUAD_EN_SPAN		BIT(1)	/* Spansion Quad I/O */
 
-enum read_mode {
-	SPI_NOR_NORMAL = 0,
-	SPI_NOR_FAST,
-	SPI_NOR_DUAL,
-	SPI_NOR_QUAD,
+
+/* Supported SPI protocols */
+#define SNOR_PROTO_WIDTH_MASK	GENMASK(7, 0)
+
+#define SNOR_PROTO_CLASS_MASK	GENMASK(9, 8)
+#define SNOR_PROTO_CLASS_1_1_N	(0x0u << 8)
+#define SNOR_PROTO_CLASS_1_N_N	(0x1u << 8)
+#define SNOR_PROTO_CLASS_N_N_N	(0x2u << 8)
+
+#define SNOR_PROTO_IS_DTR	BIT(10)	/* Double Transfer Rate */
+
+#define SNOR_PROTO_STR(_pclass, _pwidth) \
+	((_pclass) | (_pwidth))
+#define SNOR_PROTO_DTR(_pclass, _pwidth) \
+	(SNOR_PROTO_IS_DTR | (_pclass) | (_pwidth))
+
+enum spi_nor_protocol {
+	SNOR_PROTO_1_1_1 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_1_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 2),
+	SNOR_PROTO_1_1_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 4),
+	SNOR_PROTO_1_1_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 8),
+	SNOR_PROTO_1_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 8),
+	SNOR_PROTO_2_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 2),
+	SNOR_PROTO_4_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 4),
+	SNOR_PROTO_8_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 8),
+
+	SNOR_PROTO_1_1_1_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_2_2_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 8),
 };
 
+static inline bool spi_nor_protocol_is_dtr(enum spi_nor_protocol proto)
+{
+	return (proto & SNOR_PROTO_IS_DTR) == SNOR_PROTO_IS_DTR;
+}
+
+static inline u32 spi_nor_get_protocol_class(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_CLASS_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_width(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_WIDTH_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_inst_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) == SNOR_PROTO_CLASS_N_N_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_addr_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) != SNOR_PROTO_CLASS_1_1_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_data_width(enum spi_nor_protocol proto)
+{
+	return spi_nor_get_protocol_width(proto);
+}
+
+
 #define SPI_NOR_MAX_CMD_SIZE	8
 enum spi_nor_ops {
 	SPI_NOR_OPS_READ = 0,
@@ -154,9 +225,11 @@ enum spi_nor_option_flags {
  * @read_opcode:	the read opcode
  * @read_dummy:		the dummy needed by the read operation
  * @program_opcode:	the program opcode
- * @flash_read:		the mode of the read
  * @sst_write_second:	used by the SST write operation
  * @flags:		flag options for the current SPI-NOR (SNOR_F_*)
+ * @read_proto:		the SPI protocol for read operations
+ * @write_proto:	the SPI protocol for write operations
+ * @reg_proto		the SPI protocol for read_reg/write_reg/erase operations
  * @cmd_buf:		used by the write_reg
  * @prepare:		[OPTIONAL] do some preparations for the
  *			read/write/erase/lock/unlock operations
@@ -173,6 +246,7 @@ enum spi_nor_option_flags {
  * @flash_unlock:	[FLASH-SPECIFIC] unlock a region of the SPI NOR
  * @flash_is_locked:	[FLASH-SPECIFIC] check if a region of the SPI NOR is
  *			completely locked
+ * @flash_quad_enable:	[FLASH-SPECIFIC] set the Quad Enable bit of the SPI NOR
  * @priv:		the private data
  */
 struct spi_nor {
@@ -185,7 +259,9 @@ struct spi_nor {
 	u8			read_opcode;
 	u8			read_dummy;
 	u8			program_opcode;
-	enum read_mode		flash_read;
+	enum spi_nor_protocol	read_proto;
+	enum spi_nor_protocol	write_proto;
+	enum spi_nor_protocol	reg_proto;
 	bool			sst_write_second;
 	u32			flags;
 	u8			cmd_buf[SPI_NOR_MAX_CMD_SIZE];
@@ -204,6 +280,7 @@ struct spi_nor {
 	int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
+	int (*flash_quad_enable)(struct spi_nor *nor);
 
 	void *priv;
 };
@@ -219,11 +296,73 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
 	return mtd_get_of_node(&nor->mtd);
 }
 
+
+/**
+ * struct spi_nor_hwcaps - Structure for describing the hardware capabilies
+ * supported by the SPI controller (bus master).
+ * @mask:		the bitmask listing all the supported hw capabilies
+ */
+struct spi_nor_hwcaps {
+	u32	mask;
+};
+
+/*
+ *(Fast) Read capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * As a matter of performances, it is relevant to use Octo SPI protocols first,
+ * then Quad SPI protocols before Dual SPI protocols, Fast Read and lastly
+ * (Slow) Read.
+ */
+#define SNOR_HWCAPS_READ_MASK		GENMASK(15, 0)
+#define SNOR_HWCAPS_READ		BIT(0)
+#define SNOR_HWCAPS_READ_FAST		BIT(1)
+#define SNOR_HWCAPS_READ_1_1_1_DTR	BIT(2)
+
+#define SNOR_HWCAPS_READ_DUAL		GENMASK(7, 4)
+#define SNOR_HWCAPS_READ_1_1_2		BIT(4)
+#define SNOR_HWCAPS_READ_1_2_2		BIT(5)
+#define SNOR_HWCAPS_READ_2_2_2		BIT(6)
+#define SNOR_HWCAPS_READ_1_2_2_DTR	BIT(7)
+
+#define SNOR_HWCAPS_READ_QUAD		GENMASK(11, 8)
+#define SNOR_HWCAPS_READ_1_1_4		BIT(8)
+#define SNOR_HWCAPS_READ_1_4_4		BIT(9)
+#define SNOR_HWCAPS_READ_4_4_4		BIT(10)
+#define SNOR_HWCAPS_READ_1_4_4_DTR	BIT(11)
+
+#define SNOR_HWCPAS_READ_OCTO		GENMASK(15, 12)
+#define SNOR_HWCAPS_READ_1_1_8		BIT(12)
+#define SNOR_HWCAPS_READ_1_8_8		BIT(13)
+#define SNOR_HWCAPS_READ_8_8_8		BIT(14)
+#define SNOR_HWCAPS_READ_1_8_8_DTR	BIT(15)
+
+/*
+ * Page Program capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * Like (Fast) Read capabilities, Octo/Quad SPI protocols are preferred to the
+ * legacy SPI 1-1-1 protocol.
+ * Note that Dual Page Programs are not supported because there is no existing
+ * JEDEC/SFDP standard to define them. Also at this moment no SPI flash memory
+ * implements such commands.
+ */
+#define SNOR_HWCAPS_PP_MASK	GENMASK(22, 16)
+#define SNOR_HWCAPS_PP		BIT(16)
+
+#define SNOR_HWCAPS_PP_QUAD	GENMASK(19, 17)
+#define SNOR_HWCAPS_PP_1_1_4	BIT(17)
+#define SNOR_HWCAPS_PP_1_4_4	BIT(18)
+#define SNOR_HWCAPS_PP_4_4_4	BIT(19)
+
+#define SNOR_HWCAPS_PP_OCTO	GENMASK(22, 20)
+#define SNOR_HWCAPS_PP_1_1_8	BIT(20)
+#define SNOR_HWCAPS_PP_1_8_8	BIT(21)
+#define SNOR_HWCAPS_PP_8_8_8	BIT(22)
+
 /**
  * spi_nor_scan() - scan the SPI NOR
  * @nor:	the spi_nor structure
  * @name:	the chip type name
- * @mode:	the read mode supported by the driver
+ * @hwcaps:	the hardware capabilities supported by the controller driver
  *
  * The drivers can use this fuction to scan the SPI NOR.
  * In the scanning, it will try to get all the necessary information to

include/linux/mtd/spi-nor.h static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor) · line 378

Quoted 8 lines
include/linux/mtd/spi-nor.h +2 -1
@@ -233,6 +372,7 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
  *
  * Return: 0 for success, others for failure.
  */
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode);
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps);
 
 #endif

______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

#4 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

Quoted 45 lines · Cyrille Pitchen

Hi Cédic,

Quoted 21 lines · Cédric Le Goater
Quoted 20 lines · Cyrille Pitchen

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

Quoted 6 lines
Quoted 6 lines

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad

.enable_quad_io() was renamed into .quad_enable()

Quoted 11 lines
Quoted 1 line

Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

The Aspeed controller only supports Dual I/O and a helper similar to the one for Quad I/O would be needed to setup the chip for multiple I/O:

	int (*dual_enable)(struct spi_nor *nor);

Is the approach correct ? or maybe rename the current 'quad_enable()' to 'multiple_enable()' and add an extra parameter.

Quoted 5 lines

No, nor->flash_quad_enable() [ = params->quad_enable() ] is a flash specific handler as described in spi-nor.h and like other flash specific handlers such as flash_lock(), flash_unlock() or flash_is_locked(), is only used internally in spi-nor.c: spi-nor controller drivers like aspeed-smc.c should not use those handlers at all.

I was talking about adding a spi-nor handler to set up the chip for dual I/O because today we only have one for quad I/O, and some controllers (like the Aspeed) do not support Quad. So the only fast I/O we can do today for these controllers is dual I/O data (SPI_NOR_DUAL)

Quoted 7 lines

the choice of the nor->flash_quad_enable() handler depends only on the memory manufacturer (and the memory part):
- (none): Micron
- macronix_quad_enable: Macronix
- spansion_quad_enable: Spansion, Winbond, ...
- spansion_new_quad_enable: Spansion (latest memories), ...
- sr2_bit7_quad_enable: ??? (defined in the JESD216B specification)

yes. Some Micron have a VCONF register to set multiple I/O also.

Quoted 19 lines

The purpose of those functions is to implement the vendor specific procedure to set the so called "Quad Enable" (QE) bit in some Status Register of the SPI NOR memory.

Indeed, most QSPI memories are pin to pin compatible with legacy SPI memories. 2 pins of those memories were dedicated to the Write Protect (WP) and Reset/Hold (RST) functions. Hence before using any Quad SPI commands, almost all QSPI memories (Micron being the only exception I know) require us to set their QE bit so the WP and RST pins are reassigned to functions IO2 and IO3, the 3rd and 4th IO lines needed by SPI 1-1-4 and SPI 1-4-4 protocols. Then the Write Protect and Hold/Reset functions are disabled.

From a software point of view, there is nothing to do before using the SPI 1-1-2 or SPI 1-2-2 protocols. As a matter of fact, with those protocols, MISO and MOSI pins are simply reassigned to functions IO0 and IO1.

Quoted 4 lines

Back to the SPI flash controller, if the hardware needs to be configured in some way to use any Dual or Quad SPI protocols, it has to be done before calling spi_nor_scan() or more likely directly inside the controller driver specific implementation of nor->read(), nor->write().

yes.

Quoted 33 lines

With nor->read(), the driver has to check the value of nor->read_proto to know the actual number of I/O lines used during Instruction (x), Address/Dummy (y) and Data (z) clock cycles: nor->read_proto provides the driver with the SPI x-y-z protocol to be used.

Also nor->write() has to check the value of nor->write_proto.

[ nor->erase() has to check the value of nor->reg_proto: I removed nor->erase_proto since it always had the same value as nor->reg_proto. ]

Both nor->write() and nor->read are controller driver specific: they must be implemented by the controller driver and set before calling spi_nor_scan().

Then nor->read_proto and nor->write_proto are chosen from spi_nor_scan() based on the actual hardware capabilities shared by both the SPI memory and controller. The SPI controller driver tells the spi-nor framework which SPI protocols it supports or wants to use by setting the new 'struct spi_nor_hwcaps' argument of spi_nor_scan() accordingly.

You can have a look at the atmel-quadspi.c driver to have an example of what to do to support the SPI 1-2-2 or SPI 1-4-4 protocols at the controller side.

I've updated the Atmel Quad SPI driver since I'm the maintainer of this driver so I know the exact hardware capabilities of this controller but I don't have such a knowledge for Quad SPI controllers of other vendors.

Please note that patch is conservative: if a controller driver like the Aspeed one currently doesn't support the Quad or Dual SPI protocols, the patch doesn't enable them in hwcaps.mask.

Quoted 5 lines

Maintainers of the controller drivers will have to, if they want, extend their driver to add the support of new SPI protocols, otherwise those drivers will keep on working exactly as they used to do before this series. For instance, a controller driver which used SPI_NOR_QUAD before still uses the SPI 1-1-4 protocol.

Yes. I have some patches adding DUAL support to Aspeed but I will rebase on your patchset when it is merged because they currently conflict.

Thanks for the detailed explanations !

C.

Quoted 1,331 lines

Best regards,

Cyrille

Quoted 1,325 lines

I have a bunch of patches queued for Dual I/O data support but I think they will conflict with your patches. I will wait for this one to be merged. Then, I can look at Dual I/O address + data support.

Thanks,

C.

Quoted 1,301 lines
---
 drivers/mtd/devices/m25p80.c          |  16 +-
 drivers/mtd/spi-nor/aspeed-smc.c      |  23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |  80 +++---
 drivers/mtd/spi-nor/cadence-quadspi.c |  18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |   8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |  31 ++-
 drivers/mtd/spi-nor/intel-spi.c       |   7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |  16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |  22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 441 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h           | 158 +++++++++++-
 11 files changed, 643 insertions(+), 177 deletions(-)
drivers/mtd/devices/m25p80.c +9 -7
index c4df3b1bded0..68986a26c8fe 100644
@@ -111,10 +111,10 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 
 static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
 {
-	switch (nor->flash_read) {
-	case SPI_NOR_DUAL:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_2:
 		return 2;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		return 4;
 	default:
 		return 0;
@@ -196,7 +196,9 @@ static int m25p_probe(struct spi_device *spi)
 	struct flash_platform_data	*data;
 	struct m25p *flash;
 	struct spi_nor *nor;
-	enum read_mode mode = SPI_NOR_NORMAL;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ | SNOR_HWCAPS_PP),
+	};
 	char *flash_name;
 	int ret;
 
@@ -222,9 +224,9 @@ static int m25p_probe(struct spi_device *spi)
 	flash->spi = spi;
 
 	if (spi->mode & SPI_RX_QUAD)
-		mode = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	else if (spi->mode & SPI_RX_DUAL)
-		mode = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
 	if (data && data->name)
 		nor->mtd.name = data->name;
@@ -241,7 +243,7 @@ static int m25p_probe(struct spi_device *spi)
 	else
 		flash_name = spi->modalias;
 
-	ret = spi_nor_scan(nor, flash_name, mode);
+	ret = spi_nor_scan(nor, flash_name, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/aspeed-smc.c +13 -10
index 56051d30f000..723026d9cf0c 100644
@@ -585,14 +585,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 	 * TODO: Adjust clocks if fast read is supported and interpret
 	 * SPI-NOR flags to adjust controller settings.
 	 */
-	switch (chip->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-		cmd = CONTROL_COMMAND_MODE_NORMAL;
-		break;
-	case SPI_NOR_FAST:
-		cmd = CONTROL_COMMAND_MODE_FREAD;
-		break;
-	default:
+	if (chip->nor.read_proto == SNOR_PROTO_1_1_1) {
+		if (chip->nor.read_dummy == 0)
+			cmd = CONTROL_COMMAND_MODE_NORMAL;
+		else
+			cmd = CONTROL_COMMAND_MODE_FREAD;
+	} else {
 		dev_err(chip->nor.dev, "unsupported SPI read mode\n");
 		return -EINVAL;
 	}
@@ -608,6 +606,11 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 				  struct device_node *np, struct resource *r)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	const struct aspeed_smc_info *info = controller->info;
 	struct device *dev = controller->dev;
 	struct device_node *child;
@@ -671,11 +674,11 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 			break;
 
 		/*
-		 * TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
+		 * TODO: Add support for Dual and Quad SPI protocols
 
 		 * attach when board support is present as determined
 		 * by of property.
 		 */
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			break;
drivers/mtd/spi-nor/atmel-quadspi.c +53 -27
index 47937d9beec6..9f579f7c1733 100644
@@ -275,14 +275,48 @@ static void atmel_qspi_debug_command(struct atmel_qspi *aq,
 
 static int atmel_qspi_run_command(struct atmel_qspi *aq,
 				  const struct atmel_qspi_command *cmd,
-				  u32 ifr_tfrtyp, u32 ifr_width)
+				  u32 ifr_tfrtyp, enum spi_nor_protocol proto)
 {
 	u32 iar, icr, ifr, sr;
 	int err = 0;
 
 	iar = 0;
 	icr = 0;
-	ifr = ifr_tfrtyp | ifr_width;
+	ifr = ifr_tfrtyp;
+
+	/* Set the SPI protocol */
+	switch (proto) {
+	case SNOR_PROTO_1_1_1:
+		ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
+		break;
+
+	case SNOR_PROTO_1_1_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_1_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_IO;
+		break;
+
+	case SNOR_PROTO_1_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_IO;
+		break;
+
+	case SNOR_PROTO_2_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_CMD;
+		break;
+
+	case SNOR_PROTO_4_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_CMD;
+		break;
+
+	default:
+		return -EINVAL;
+	}
 
 	/* Compute instruction parameters */
 	if (cmd->enable.bits.instruction) {
@@ -434,7 +468,7 @@ static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode,
 	cmd.rx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
@@ -450,7 +484,7 @@ static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
 	cmd.tx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
@@ -469,7 +503,7 @@ static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
 	cmd.tx_buf = write_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM,
-				     QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				     nor->write_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -484,7 +518,7 @@ static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs)
 	cmd.instruction = nor->erase_opcode;
 	cmd.address = (u32)offs;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
@@ -493,27 +527,8 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	struct atmel_qspi *aq = nor->priv;
 	struct atmel_qspi_command cmd;
 	u8 num_mode_cycles, num_dummy_cycles;
-	u32 ifr_width;
 	ssize_t ret;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
-		ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
-		break;
-
-	case SPI_NOR_DUAL:
-		ifr_width = QSPI_IFR_WIDTH_DUAL_OUTPUT;
-		break;
-
-	case SPI_NOR_QUAD:
-		ifr_width = QSPI_IFR_WIDTH_QUAD_OUTPUT;
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
 	if (nor->read_dummy >= 2) {
 		num_mode_cycles = 2;
 		num_dummy_cycles = nor->read_dummy - 2;
@@ -536,7 +551,7 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	cmd.rx_buf = read_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ_MEM,
-				     ifr_width);
+				     nor->read_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -590,6 +605,17 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
 
 static int atmel_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_2_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_READ_1_4_4 |
+			 SNOR_HWCAPS_PP |
+			 SNOR_HWCAPS_PP_1_1_4 |
+			 SNOR_HWCAPS_PP_1_4_4),
+	};
 	struct device_node *child, *np = pdev->dev.of_node;
 	struct atmel_qspi *aq;
 	struct resource *res;
@@ -679,7 +705,7 @@ static int atmel_qspi_probe(struct platform_device *pdev)
 	if (err)
 		goto disable_clk;
 
-	err = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	err = spi_nor_scan(nor, NULL, &hwcaps);
 	if (err)
 		goto disable_clk;
 
drivers/mtd/spi-nor/cadence-quadspi.c +12 -6
index 9f8102de1b16..3f91a3e97892 100644
@@ -855,15 +855,14 @@ static int cqspi_set_protocol(struct spi_nor *nor, const int read)
 	f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 
 	if (read) {
-		switch (nor->flash_read) {
-		case SPI_NOR_NORMAL:
-		case SPI_NOR_FAST:
+		switch (nor->read_proto) {
+		case SNOR_PROTO_1_1_1:
 			f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 			break;
-		case SPI_NOR_DUAL:
+		case SNOR_PROTO_1_1_2:
 			f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
 			break;
-		case SPI_NOR_QUAD:
+		case SNOR_PROTO_1_1_4:
 			f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
 			break;
 		default:
@@ -1069,6 +1068,13 @@ static void cqspi_controller_init(struct cqspi_st *cqspi)
 
 static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct platform_device *pdev = cqspi->pdev;
 	struct device *dev = &pdev->dev;
 	struct cqspi_flash_pdata *f_pdata;
@@ -1123,7 +1129,7 @@ static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 			goto err;
 		}
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto err;
 
drivers/mtd/spi-nor/fsl-quadspi.c +7 -1
index 1476135e0d50..ec9c8e960fd2 100644
@@ -957,6 +957,12 @@ static void fsl_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
 
 static int fsl_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device_node *np = pdev->dev.of_node;
 	struct device *dev = &pdev->dev;
 	struct fsl_qspi *q;
@@ -1065,7 +1071,7 @@ static int fsl_qspi_probe(struct platform_device *pdev)
 		/* set the chip address for READID */
 		fsl_qspi_set_base_addr(q, nor);
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto mutex_failed;
 
drivers/mtd/spi-nor/hisi-sfc.c +23 -8
index a286350627a6..80e2d173abdd 100644
@@ -120,19 +120,24 @@ static inline int wait_op_finish(struct hifmc_host *host)
 		(reg & FMC_INT_OP_DONE), 0, FMC_WAIT_TIMEOUT);
 }
 
-static int get_if_type(enum read_mode flash_read)
+static int get_if_type(enum spi_nor_protocol proto)
 {
 	enum hifmc_iftype if_type;
 
-	switch (flash_read) {
-	case SPI_NOR_DUAL:
+	switch (proto) {
+	case SNOR_PROTO_1_1_2:
 		if_type = IF_TYPE_DUAL;
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_2_2:
+		if_type = IF_TYPE_DIO;
+		break;
+	case SNOR_PROTO_1_1_4:
 		if_type = IF_TYPE_QUAD;
 		break;
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	case SNOR_PROTO_1_4_4:
+		if_type = IF_TYPE_QIO;
+		break;
+	case SNOR_PROTO_1_1_1:
 	default:
 		if_type = IF_TYPE_STD;
 		break;
@@ -253,7 +258,10 @@ static int hisi_spi_nor_dma_transfer(struct spi_nor *nor, loff_t start_off,
 	writel(FMC_DMA_LEN_SET(len), host->regbase + FMC_DMA_LEN);
 
 	reg = OP_CFG_FM_CS(priv->chipselect);
-	if_type = get_if_type(nor->flash_read);
+	if (op_type == FMC_OP_READ)
+		if_type = get_if_type(nor->read_proto);
+	else
+		if_type = get_if_type(nor->write_proto);
 	reg |= OP_CFG_MEM_IF_TYPE(if_type);
 	if (op_type == FMC_OP_READ)
 		reg |= OP_CFG_DUMMY_NUM(nor->read_dummy >> 3);
@@ -321,6 +329,13 @@ static ssize_t hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
 static int hisi_spi_nor_register(struct device_node *np,
 				struct hifmc_host *host)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device *dev = host->dev;
 	struct spi_nor *nor;
 	struct hifmc_priv *priv;
@@ -362,7 +377,7 @@ static int hisi_spi_nor_register(struct device_node *np,
 	nor->read = hisi_spi_nor_read;
 	nor->write = hisi_spi_nor_write;
 	nor->erase = NULL;
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/intel-spi.c +6 -1
index 986a3d020a3a..515aa1f7f4f1 100644
@@ -715,6 +715,11 @@ static void intel_spi_fill_partition(struct intel_spi *ispi,
 struct intel_spi *intel_spi_probe(struct device *dev,
 	struct resource *mem, const struct intel_spi_boardinfo *info)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	struct mtd_partition part;
 	struct intel_spi *ispi;
 	int ret;
@@ -746,7 +751,7 @@ struct intel_spi *intel_spi_probe(struct device *dev,
 	ispi->nor.write = intel_spi_write;
 	ispi->nor.erase = intel_spi_erase;
 
-	ret = spi_nor_scan(&ispi->nor, NULL, SPI_NOR_NORMAL);
+	ret = spi_nor_scan(&ispi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_info(dev, "failed to locate the chip\n");
 		return ERR_PTR(ret);
drivers/mtd/spi-nor/mtk-quadspi.c +11 -5
index e661877c23de..615e258866f7 100644
@@ -121,20 +121,20 @@ static void mt8173_nor_set_read_mode(struct mt8173_nor *mt8173_nor)
 {
 	struct spi_nor *nor = &mt8173_nor->nor;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_1:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_FAST_READ, mt8173_nor->base +
 		       MTK_NOR_CFG1_REG);
 		break;
-	case SPI_NOR_DUAL:
+	case SNOR_PROTO_1_1_2:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_DUAL_READ_EN, mt8173_nor->base +
 		       MTK_NOR_DUAL_REG);
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA4_REG);
 		writeb(MTK_NOR_QUAD_READ_EN, mt8173_nor->base +
@@ -381,6 +381,12 @@ static int mt8173_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
 static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 			struct device_node *flash_node)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_PP),
+	};
 	int ret;
 	struct spi_nor *nor;
 
@@ -399,7 +405,7 @@ static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 	nor->write_reg = mt8173_nor_write_reg;
 	nor->mtd.name = "mtk_nor";
 	/* initialized with NULL */
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_DUAL);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/nxp-spifi.c +12 -10
index 73a14f40928b..c5992e099542 100644
@@ -240,13 +240,12 @@ static int nxp_spifi_erase(struct spi_nor *nor, loff_t offs)
 
 static int nxp_spifi_setup_memory_cmd(struct nxp_spifi *spifi)
 {
-	switch (spifi->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	switch (spifi->nor.read_proto) {
+	case SNOR_PROTO_1_1_1:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_ALL_SERIAL;
 		break;
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_2:
+	case SNOR_PROTO_1_1_4:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_QUAD_DUAL_DATA;
 		break;
 	default:
@@ -274,7 +273,11 @@ static void nxp_spifi_dummy_id_read(struct spi_nor *nor)
 static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 				 struct device_node *np)
 {
-	enum read_mode flash_read;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	u32 ctrl, property;
 	u16 mode = 0;
 	int ret;
@@ -308,13 +311,12 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 
 	if (mode & SPI_RX_DUAL) {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 	} else if (mode & SPI_RX_QUAD) {
 		ctrl &= ~SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	} else {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_NORMAL;
 	}
 
 	switch (mode & (SPI_CPHA | SPI_CPOL)) {
@@ -351,7 +353,7 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 	 */
 	nxp_spifi_dummy_id_read(&spifi->nor);
 
-	ret = spi_nor_scan(&spifi->nor, NULL, flash_read);
+	ret = spi_nor_scan(&spifi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_err(spifi->dev, "device scan failed\n");
 		return ret;
drivers/mtd/spi-nor/spi-nor.c +348 -93
index d3cb44b28490..cc443c6cbae8 100644
@@ -150,24 +150,6 @@ static int read_cr(struct spi_nor *nor)
 }
 
 /*
- * Dummy Cycle calculation for different type of read.
- * It can be used to support more commands with
- * different dummy cycle requirements.
- */
-static inline int spi_nor_read_dummy_cycles(struct spi_nor *nor)
-{
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
-		return 8;
-	case SPI_NOR_NORMAL:
-		return 0;
-	}
-	return 0;
-}
-
-/*
  * Write status register 1 byte
  * Returns negative if error occurred.
  */
@@ -221,6 +203,10 @@ static inline u8 spi_nor_convert_3to4_read(u8 opcode)
 		{ SPINOR_OP_READ_1_2_2,	SPINOR_OP_READ_1_2_2_4B },
 		{ SPINOR_OP_READ_1_1_4,	SPINOR_OP_READ_1_1_4_4B },
 		{ SPINOR_OP_READ_1_4_4,	SPINOR_OP_READ_1_4_4_4B },
+
+		{ SPINOR_OP_READ_1_1_1_DTR,	SPINOR_OP_READ_1_1_1_DTR_4B },
+		{ SPINOR_OP_READ_1_2_2_DTR,	SPINOR_OP_READ_1_2_2_DTR_4B },
+		{ SPINOR_OP_READ_1_4_4_DTR,	SPINOR_OP_READ_1_4_4_DTR_4B },
 	};
 
 	return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
@@ -1459,30 +1445,6 @@ static int spansion_quad_enable(struct spi_nor *nor)
 	return 0;
 }
 
-static int set_quad_mode(struct spi_nor *nor, const struct flash_info *info)
-{
-	int status;
-
-	switch (JEDEC_MFR(info)) {
-	case SNOR_MFR_MACRONIX:
-		status = macronix_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Macronix quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	case SNOR_MFR_MICRON:
-		return 0;
-	default:
-		status = spansion_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Spansion quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	}
-}
-
 static int spi_nor_check(struct spi_nor *nor)
 {
 	if (!nor->dev || !nor->read || !nor->write ||
@@ -1535,8 +1497,322 @@ static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor)
 	return 0;
 }
 
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
+
+struct spi_nor_read_command {
+	u8			num_mode_clocks;
+	u8			num_wait_states;
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+struct spi_nor_pp_command {
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+enum spi_nor_read_command_index {
+	SNOR_CMD_READ,
+	SNOR_CMD_READ_FAST,
+	SNOR_CMD_READ_1_1_1_DTR,
+
+	/* Dual SPI */
+	SNOR_CMD_READ_1_1_2,
+	SNOR_CMD_READ_1_2_2,
+	SNOR_CMD_READ_2_2_2,
+	SNOR_CMD_READ_1_2_2_DTR,
+
+	/* Quad SPI */
+	SNOR_CMD_READ_1_1_4,
+	SNOR_CMD_READ_1_4_4,
+	SNOR_CMD_READ_4_4_4,
+	SNOR_CMD_READ_1_4_4_DTR,
+
+	/* Octo SPI */
+	SNOR_CMD_READ_1_1_8,
+	SNOR_CMD_READ_1_8_8,
+	SNOR_CMD_READ_8_8_8,
+	SNOR_CMD_READ_1_8_8_DTR,
+
+	SNOR_CMD_READ_MAX
+};
+
+enum spi_nor_pp_command_index {
+	SNOR_CMD_PP,
+
+	/* Quad SPI */
+	SNOR_CMD_PP_1_1_4,
+	SNOR_CMD_PP_1_4_4,
+	SNOR_CMD_PP_4_4_4,
+
+	/* Octo SPI */
+	SNOR_CMD_PP_1_1_8,
+	SNOR_CMD_PP_1_8_8,
+	SNOR_CMD_PP_8_8_8,
+
+	SNOR_CMD_PP_MAX
+};
+
+struct spi_nor_flash_parameter {
+	u64				size;
+	u32				page_size;
+
+	struct spi_nor_hwcaps		hwcaps;
+	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
+	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
+
+	int (*quad_enable)(struct spi_nor *nor);
+};
+
+
+static inline void
+spi_nor_set_read_settings(struct spi_nor_read_command *read,
+			  u8 num_mode_clocks,
+			  u8 num_wait_states,
+			  u8 opcode,
+			  enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = num_mode_clocks;
+	read->num_wait_states = num_wait_states;
+	read->opcode = opcode;
+	read->proto = proto;
+}
+
+static inline void
+spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+			u8 opcode,
+			enum spi_nor_protocol proto)
+{
+	pp->opcode = opcode;
+	pp->proto = proto;
+}
+
+static int spi_nor_init_params(struct spi_nor *nor,
+			       const struct flash_info *info,
+			       struct spi_nor_flash_parameter *params)
+{
+	/* Set legacy flash parameters as default. */
+	memset(params, 0, sizeof(*params));
+
+	/* Set SPI NOR sizes. */
+	params->size = info->sector_size * info->n_sectors;
+	params->page_size = info->page_size;
+
+	/* (Fast) Read settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_READ;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
+				  0, 0, SPINOR_OP_READ,
+				  SNOR_PROTO_1_1_1);
+	if (!(info->flags & SPI_NOR_NO_FR)) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
+					  0, 8, SPINOR_OP_READ_FAST,
+					  SNOR_PROTO_1_1_1);
+	}
+	if (info->flags & SPI_NOR_DUAL_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
+					  0, 8, SPINOR_OP_READ_1_1_2,
+					  SNOR_PROTO_1_1_2);
+	}
+	if (info->flags & SPI_NOR_QUAD_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+					  0, 8, SPINOR_OP_READ_1_1_4,
+					  SNOR_PROTO_1_1_4);
+	}
+
+	/* Page Program settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_PP;
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+	/* Select the procedure to set the Quad Enable bit. */
+	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
+				   SNOR_HWCAPS_PP_QUAD)) {
+		switch (JEDEC_MFR(info)) {
+		case SNOR_MFR_MACRONIX:
+			params->quad_enable = macronix_quad_enable;
+			break;
+
+		case SNOR_MFR_MICRON:
+			break;
+
+		default:
+			params->quad_enable = spansion_quad_enable;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps)
 {
+	switch (hwcaps) {
+	case SNOR_HWCAPS_READ:			return SNOR_CMD_READ;
+	case SNOR_HWCAPS_READ_FAST:		return SNOR_CMD_READ_FAST;
+	case SNOR_HWCAPS_READ_1_1_1_DTR:	return SNOR_CMD_READ_1_1_1_DTR;
+	case SNOR_HWCAPS_READ_1_1_2:		return SNOR_CMD_READ_1_1_2;
+	case SNOR_HWCAPS_READ_1_2_2:		return SNOR_CMD_READ_1_2_2;
+	case SNOR_HWCAPS_READ_2_2_2:		return SNOR_CMD_READ_2_2_2;
+	case SNOR_HWCAPS_READ_1_2_2_DTR:	return SNOR_CMD_READ_1_2_2_DTR;
+	case SNOR_HWCAPS_READ_1_1_4:		return SNOR_CMD_READ_1_1_4;
+	case SNOR_HWCAPS_READ_1_4_4:		return SNOR_CMD_READ_1_4_4;
+	case SNOR_HWCAPS_READ_4_4_4:		return SNOR_CMD_READ_4_4_4;
+	case SNOR_HWCAPS_READ_1_4_4_DTR:	return SNOR_CMD_READ_1_4_4_DTR;
+	case SNOR_HWCAPS_READ_1_1_8:		return SNOR_CMD_READ_1_1_8;
+	case SNOR_HWCAPS_READ_1_8_8:		return SNOR_CMD_READ_1_8_8;
+	case SNOR_HWCAPS_READ_8_8_8:		return SNOR_CMD_READ_8_8_8;
+	case SNOR_HWCAPS_READ_1_8_8_DTR:	return SNOR_CMD_READ_1_8_8_DTR;
+
+	case SNOR_HWCAPS_PP:			return SNOR_CMD_PP;
+	case SNOR_HWCAPS_PP_1_1_4:		return SNOR_CMD_PP_1_1_4;
+	case SNOR_HWCAPS_PP_1_4_4:		return SNOR_CMD_PP_1_4_4;
+	case SNOR_HWCAPS_PP_4_4_4:		return SNOR_CMD_PP_4_4_4;
+	case SNOR_HWCAPS_PP_1_1_8:		return SNOR_CMD_PP_1_1_8;
+	case SNOR_HWCAPS_PP_1_8_8:		return SNOR_CMD_PP_1_8_8;
+	case SNOR_HWCAPS_PP_8_8_8:		return SNOR_CMD_PP_8_8_8;
+	}
+
+	return -EINVAL;
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,
+			       const struct spi_nor_flash_parameter *params,
+			       u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+	const struct spi_nor_read_command *read;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	read = &params->reads[cmd];
+	nor->read_opcode = read->opcode;
+	nor->read_proto = read->proto;
+
+	/*
+	 * In the spi-nor framework, we don't need to make the difference
+	 * between mode clock cycles and wait state clock cycles.
+	 * Indeed, the value of the mode clock cycles is used by a QSPI
+	 * flash memory to know whether it should enter or leave its 0-4-4
+	 * (Continuous Read / XIP) mode.
+	 * eXecution In Place is out of the scope of the mtd sub-system.
+	 * Hence we choose to merge both mode and wait state clock cycles
+	 * into the so called dummy clock cycles.
+	 */
+	nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+	return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+			     const struct spi_nor_flash_parameter *params,
+			     u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+	const struct spi_nor_pp_command *pp;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	pp = &params->page_programs[cmd];
+	nor->program_opcode = pp->opcode;
+	nor->write_proto = pp->proto;
+	return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+				const struct flash_info *info)
+{
+	struct mtd_info *mtd = &nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+	return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params,
+			 const struct spi_nor_hwcaps *hwcaps)
+{
+	u32 ignored_mask, shared_mask;
+	bool enable_quad_io;
+	int err;
+
+	/*
+	 * Keep only the hardware capabilities supported by both the SPI
+	 * controller and the SPI flash memory.
+	 */
+	shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+	/* SPI protocol classes N-N-N are not supported yet. */
+	ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+			SNOR_HWCAPS_READ_4_4_4 |
+			SNOR_HWCAPS_READ_8_8_8 |
+			SNOR_HWCAPS_PP_4_4_4 |
+			SNOR_HWCAPS_PP_8_8_8);
+	if (shared_mask & ignored_mask) {
+		dev_dbg(nor->dev,
+			"SPI protocol classes N-N-N are not supported yet.\n");
+		shared_mask &= ~ignored_mask;
+	}
+
+	/* Select the (Fast) Read command. */
+	err = spi_nor_select_read(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid (fast) read\n");
+		return err;
+	}
+
+	/* Select the Page Program command. */
+	err = spi_nor_select_pp(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid page program\n");
+		return err;
+	}
+
+	/* Select the Sector Erase command. */
+	err = spi_nor_select_erase(nor, info);
+	if (err) {
+		dev_err(nor->dev, "invalid sector/block erase\n");
+		return err;
+	}
+
+	/* Enable Quad I/O if needed. */
+	enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+			  spi_nor_get_protocol_width(nor->write_proto) == 4);
+	if (enable_quad_io && params->quad_enable)
+		nor->flash_quad_enable = params->quad_enable;
+	else
+		nor->flash_quad_enable = NULL;
+
+	return 0;
+}
+
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps)
+{
+	struct spi_nor_flash_parameter params;
 	const struct flash_info *info = NULL;
 	struct device *dev = nor->dev;
 	struct mtd_info *mtd = &nor->mtd;
@@ -1548,6 +1824,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (ret)
 		return ret;
 
+	/* Reset SPI protocol for all commands */
+	nor->reg_proto = SNOR_PROTO_1_1_1;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
 	if (name)
 		info = spi_nor_match_id(name);
 	/* Try to auto-detect if chip name wasn't specified or not found */
@@ -1580,6 +1861,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		}
 	}
 
+	/* Parse the Serial Flash Discoverable Parameters table */
+	ret = spi_nor_init_params(nor, info, &params);
+	if (ret)
+		return ret;
+
 	mutex_init(&nor->lock);
 
 	/*
@@ -1610,7 +1896,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	mtd->type = MTD_NORFLASH;
 	mtd->writesize = 1;
 	mtd->flags = MTD_CAP_NORFLASH;
-	mtd->size = info->sector_size * info->n_sectors;
+	mtd->size = params.size;
 	mtd->_erase = spi_nor_erase;
 	mtd->_read = spi_nor_read;
 
@@ -1641,76 +1927,47 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (info->flags & NO_CHIP_ERASE)
 		nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
 
-#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
-	/* prefer "small sector" erase if possible */
-	if (info->flags & SECT_4K) {
-		nor->erase_opcode = SPINOR_OP_BE_4K;
-		mtd->erasesize = 4096;
-	} else if (info->flags & SECT_4K_PMC) {
-		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
-		mtd->erasesize = 4096;
-	} else
-#endif
-	{
-		nor->erase_opcode = SPINOR_OP_SE;
-		mtd->erasesize = info->sector_size;
-	}
-
 	if (info->flags & SPI_NOR_NO_ERASE)
 		mtd->flags |= MTD_NO_ERASE;
 
 	mtd->dev.parent = dev;
-	nor->page_size = info->page_size;
+	nor->page_size = params.page_size;
 	mtd->writebufsize = nor->page_size;
 
 	if (np) {
 		/* If we were instantiated by DT, use it */
 		if (of_property_read_bool(np, "m25p,fast-read"))
-			nor->flash_read = SPI_NOR_FAST;
+			params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 		else
-			nor->flash_read = SPI_NOR_NORMAL;
+			params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
 	} else {
 		/* If we weren't instantiated by DT, default to fast-read */
-		nor->flash_read = SPI_NOR_FAST;
+		params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 	}
 
 	/* Some devices cannot do fast-read, no matter what DT tells us */
 	if (info->flags & SPI_NOR_NO_FR)
-		nor->flash_read = SPI_NOR_NORMAL;
+		params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
+
+	/*
+	 * Configure the SPI memory:
+	 * - select op codes for (Fast) Read, Page Program and Sector Erase.
+	 * - set the number of dummy cycles (mode cycles + wait states).
+	 * - set the SPI protocols for register and memory accesses.
+	 * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
+	 */
+	ret = spi_nor_setup(nor, info, &params, hwcaps);
+	if (ret)
+		return ret;
 
-	/* Quad/Dual-read mode takes precedence over fast/normal */
-	if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
-		ret = set_quad_mode(nor, info);
+	if (nor->flash_quad_enable) {
+		ret = nor->flash_quad_enable(nor);
 		if (ret) {
 			dev_err(dev, "quad mode not supported\n");
 			return ret;
 		}
-		nor->flash_read = SPI_NOR_QUAD;
-	} else if (mode == SPI_NOR_DUAL && info->flags & SPI_NOR_DUAL_READ) {
-		nor->flash_read = SPI_NOR_DUAL;
 	}
 
-	/* Default commands */
-	switch (nor->flash_read) {
-	case SPI_NOR_QUAD:
-		nor->read_opcode = SPINOR_OP_READ_1_1_4;
-		break;
-	case SPI_NOR_DUAL:
-		nor->read_opcode = SPINOR_OP_READ_1_1_2;
-		break;
-	case SPI_NOR_FAST:
-		nor->read_opcode = SPINOR_OP_READ_FAST;
-		break;
-	case SPI_NOR_NORMAL:
-		nor->read_opcode = SPINOR_OP_READ;
-		break;
-	default:
-		dev_err(dev, "No Read opcode defined\n");
-		return -EINVAL;
-	}
-
-	nor->program_opcode = SPINOR_OP_PP;
-
 	if (info->addr_width)
 		nor->addr_width = info->addr_width;
 	else if (mtd->size > 0x1000000) {
@@ -1731,8 +1988,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		return -EINVAL;
 	}
 
-	nor->read_dummy = spi_nor_read_dummy_cycles(nor);
-
 	if (info->flags & SPI_S3AN) {
 		ret = s3an_nor_scan(info, nor);
 		if (ret)
include/linux/mtd/spi-nor.h +147 -8
index f2a718030476..732ee6cd5330 100644
@@ -73,6 +73,15 @@
 #define SPINOR_OP_BE_32K_4B	0x5c	/* Erase 32KiB block */
 #define SPINOR_OP_SE_4B		0xdc	/* Sector erase (usually 64KiB) */
 
+/* Double Transfer Rate opcodes - defined in JEDEC JESD216B. */
+#define SPINOR_OP_READ_1_1_1_DTR	0x0d
+#define SPINOR_OP_READ_1_2_2_DTR	0xbd
+#define SPINOR_OP_READ_1_4_4_DTR	0xed
+
+#define SPINOR_OP_READ_1_1_1_DTR_4B	0x0e
+#define SPINOR_OP_READ_1_2_2_DTR_4B	0xbe
+#define SPINOR_OP_READ_1_4_4_DTR_4B	0xee
+
 /* Used for SST flashes only. */
 #define SPINOR_OP_BP		0x02	/* Byte program */
 #define SPINOR_OP_WRDI		0x04	/* Write disable */
@@ -119,13 +128,75 @@
 /* Configuration Register bits. */
 #define CR_QUAD_EN_SPAN		BIT(1)	/* Spansion Quad I/O */
 
-enum read_mode {
-	SPI_NOR_NORMAL = 0,
-	SPI_NOR_FAST,
-	SPI_NOR_DUAL,
-	SPI_NOR_QUAD,
+
+/* Supported SPI protocols */
+#define SNOR_PROTO_WIDTH_MASK	GENMASK(7, 0)
+
+#define SNOR_PROTO_CLASS_MASK	GENMASK(9, 8)
+#define SNOR_PROTO_CLASS_1_1_N	(0x0u << 8)
+#define SNOR_PROTO_CLASS_1_N_N	(0x1u << 8)
+#define SNOR_PROTO_CLASS_N_N_N	(0x2u << 8)
+
+#define SNOR_PROTO_IS_DTR	BIT(10)	/* Double Transfer Rate */
+
+#define SNOR_PROTO_STR(_pclass, _pwidth) \
+	((_pclass) | (_pwidth))
+#define SNOR_PROTO_DTR(_pclass, _pwidth) \
+	(SNOR_PROTO_IS_DTR | (_pclass) | (_pwidth))
+
+enum spi_nor_protocol {
+	SNOR_PROTO_1_1_1 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_1_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 2),
+	SNOR_PROTO_1_1_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 4),
+	SNOR_PROTO_1_1_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 8),
+	SNOR_PROTO_1_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 8),
+	SNOR_PROTO_2_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 2),
+	SNOR_PROTO_4_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 4),
+	SNOR_PROTO_8_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 8),
+
+	SNOR_PROTO_1_1_1_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_2_2_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 8),
 };
 
+static inline bool spi_nor_protocol_is_dtr(enum spi_nor_protocol proto)
+{
+	return (proto & SNOR_PROTO_IS_DTR) == SNOR_PROTO_IS_DTR;
+}
+
+static inline u32 spi_nor_get_protocol_class(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_CLASS_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_width(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_WIDTH_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_inst_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) == SNOR_PROTO_CLASS_N_N_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_addr_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) != SNOR_PROTO_CLASS_1_1_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_data_width(enum spi_nor_protocol proto)
+{
+	return spi_nor_get_protocol_width(proto);
+}
+
+
 #define SPI_NOR_MAX_CMD_SIZE	8
 enum spi_nor_ops {
 	SPI_NOR_OPS_READ = 0,
@@ -154,9 +225,11 @@ enum spi_nor_option_flags {
  * @read_opcode:	the read opcode
  * @read_dummy:		the dummy needed by the read operation
  * @program_opcode:	the program opcode
- * @flash_read:		the mode of the read
  * @sst_write_second:	used by the SST write operation
  * @flags:		flag options for the current SPI-NOR (SNOR_F_*)
+ * @read_proto:		the SPI protocol for read operations
+ * @write_proto:	the SPI protocol for write operations
+ * @reg_proto		the SPI protocol for read_reg/write_reg/erase operations
  * @cmd_buf:		used by the write_reg
  * @prepare:		[OPTIONAL] do some preparations for the
  *			read/write/erase/lock/unlock operations
@@ -173,6 +246,7 @@ enum spi_nor_option_flags {
  * @flash_unlock:	[FLASH-SPECIFIC] unlock a region of the SPI NOR
  * @flash_is_locked:	[FLASH-SPECIFIC] check if a region of the SPI NOR is
  *			completely locked
+ * @flash_quad_enable:	[FLASH-SPECIFIC] set the Quad Enable bit of the SPI NOR
  * @priv:		the private data
  */
 struct spi_nor {
@@ -185,7 +259,9 @@ struct spi_nor {
 	u8			read_opcode;
 	u8			read_dummy;
 	u8			program_opcode;
-	enum read_mode		flash_read;
+	enum spi_nor_protocol	read_proto;
+	enum spi_nor_protocol	write_proto;
+	enum spi_nor_protocol	reg_proto;
 	bool			sst_write_second;
 	u32			flags;
 	u8			cmd_buf[SPI_NOR_MAX_CMD_SIZE];
@@ -204,6 +280,7 @@ struct spi_nor {
 	int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
+	int (*flash_quad_enable)(struct spi_nor *nor);
 
 	void *priv;
 };
@@ -219,11 +296,73 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
 	return mtd_get_of_node(&nor->mtd);
 }
 
+
+/**
+ * struct spi_nor_hwcaps - Structure for describing the hardware capabilies
+ * supported by the SPI controller (bus master).
+ * @mask:		the bitmask listing all the supported hw capabilies
+ */
+struct spi_nor_hwcaps {
+	u32	mask;
+};
+
+/*
+ *(Fast) Read capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * As a matter of performances, it is relevant to use Octo SPI protocols first,
+ * then Quad SPI protocols before Dual SPI protocols, Fast Read and lastly
+ * (Slow) Read.
+ */
+#define SNOR_HWCAPS_READ_MASK		GENMASK(15, 0)
+#define SNOR_HWCAPS_READ		BIT(0)
+#define SNOR_HWCAPS_READ_FAST		BIT(1)
+#define SNOR_HWCAPS_READ_1_1_1_DTR	BIT(2)
+
+#define SNOR_HWCAPS_READ_DUAL		GENMASK(7, 4)
+#define SNOR_HWCAPS_READ_1_1_2		BIT(4)
+#define SNOR_HWCAPS_READ_1_2_2		BIT(5)
+#define SNOR_HWCAPS_READ_2_2_2		BIT(6)
+#define SNOR_HWCAPS_READ_1_2_2_DTR	BIT(7)
+
+#define SNOR_HWCAPS_READ_QUAD		GENMASK(11, 8)
+#define SNOR_HWCAPS_READ_1_1_4		BIT(8)
+#define SNOR_HWCAPS_READ_1_4_4		BIT(9)
+#define SNOR_HWCAPS_READ_4_4_4		BIT(10)
+#define SNOR_HWCAPS_READ_1_4_4_DTR	BIT(11)
+
+#define SNOR_HWCPAS_READ_OCTO		GENMASK(15, 12)
+#define SNOR_HWCAPS_READ_1_1_8		BIT(12)
+#define SNOR_HWCAPS_READ_1_8_8		BIT(13)
+#define SNOR_HWCAPS_READ_8_8_8		BIT(14)
+#define SNOR_HWCAPS_READ_1_8_8_DTR	BIT(15)
+
+/*
+ * Page Program capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * Like (Fast) Read capabilities, Octo/Quad SPI protocols are preferred to the
+ * legacy SPI 1-1-1 protocol.
+ * Note that Dual Page Programs are not supported because there is no existing
+ * JEDEC/SFDP standard to define them. Also at this moment no SPI flash memory
+ * implements such commands.
+ */
+#define SNOR_HWCAPS_PP_MASK	GENMASK(22, 16)
+#define SNOR_HWCAPS_PP		BIT(16)
+
+#define SNOR_HWCAPS_PP_QUAD	GENMASK(19, 17)
+#define SNOR_HWCAPS_PP_1_1_4	BIT(17)
+#define SNOR_HWCAPS_PP_1_4_4	BIT(18)
+#define SNOR_HWCAPS_PP_4_4_4	BIT(19)
+
+#define SNOR_HWCAPS_PP_OCTO	GENMASK(22, 20)
+#define SNOR_HWCAPS_PP_1_1_8	BIT(20)
+#define SNOR_HWCAPS_PP_1_8_8	BIT(21)
+#define SNOR_HWCAPS_PP_8_8_8	BIT(22)
+
 /**
  * spi_nor_scan() - scan the SPI NOR
  * @nor:	the spi_nor structure
  * @name:	the chip type name
- * @mode:	the read mode supported by the driver
+ * @hwcaps:	the hardware capabilities supported by the controller driver
  *
  * The drivers can use this fuction to scan the SPI NOR.
  * In the scanning, it will try to get all the necessary information to

include/linux/mtd/spi-nor.h static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor) · line 378

Quoted 8 lines
include/linux/mtd/spi-nor.h +2 -1
@@ -233,6 +372,7 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
  *
  * Return: 0 for success, others for failure.
  */
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode);
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps);
 
 #endif

______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

#5 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

Quoted 52 lines · Cédric Le Goater
Quoted 45 lines · Cyrille Pitchen

Hi Cédic,

Quoted 21 lines · Cédric Le Goater
Quoted 20 lines · Cyrille Pitchen

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

Quoted 6 lines
Quoted 6 lines

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad

.enable_quad_io() was renamed into .quad_enable()

Quoted 11 lines
Quoted 1 line

Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

The Aspeed controller only supports Dual I/O and a helper similar to the one for Quad I/O would be needed to setup the chip for multiple I/O:

	int (*dual_enable)(struct spi_nor *nor);

Is the approach correct ? or maybe rename the current 'quad_enable()' to 'multiple_enable()' and add an extra parameter.

Quoted 5 lines

No, nor->flash_quad_enable() [ = params->quad_enable() ] is a flash specific handler as described in spi-nor.h and like other flash specific handlers such as flash_lock(), flash_unlock() or flash_is_locked(), is only used internally in spi-nor.c: spi-nor controller drivers like aspeed-smc.c should not use those handlers at all.

Quoted 6 lines

I was talking about adding a spi-nor handler to set up the chip for dual I/O because today we only have one for quad I/O, and some controllers (like the Aspeed) do not support Quad. So the only fast I/O we can do today for these controllers is dual I/O data (SPI_NOR_DUAL)

OK but as I've explained below, there is nothing to do at the memory side before using the SPI 1-1-2 or SPI 1-2-2 protocols: there is no need to reassign pins IO0 and IO1 to other functions ;)

Hence, the JESD216B specification describes all the vendor specific procedures to be done before using SPI 1-1-4 or SPI 1-4-4 but no procedure is provided for the Dual SPI case simply because there is nothing to do!

There are also dedicated procedures for SPI 2-2-2 and 4-4-4 protocols but those protocols are not supported yet because they come with many associated issues but no real benefit.
They are state-full and the spi-nor framework would have to guess in which state the SPI NOR memory is when running spi_nor_scan(). Currently spi_nor_scan() relies on the READ JEDEC ID (9Fh) command to probe the memory but for instance Micron memories don't support this command once in 2-2-2 or 4-4-4 modes; only the READ JEDEC ID Multiple I/O (AFh) is supported. However this command is not supported at all by many other vendors. Winbond memories expect the 9Fh command even in 4-4-4 mode but I've found out that sending 9Fh with the SPI 4-4-4 protocol to many Micron QSPI memories has a bad side effect: it seems that they return in their reset state. Also, we don't know the right command to send before we actually know the memory vendor...

So it's a real mess to support the 2-2-2 and 4-4-4 modes. Besides, the bandwidth improvement would be so small that it is not worth.

So except for the 2-2-2 mode, there is no SPI command to send to the SPI flash memory before using Dual SPI protocol.

So everything should already be ready if you want to add support to the SPI 1-1-2 or SPI 1-2-2 protocols in the Aspeed driver.
If you need support or have more questions, I can help you of course :)

Quoted 1,416 lines
Quoted 7 lines

the choice of the nor->flash_quad_enable() handler depends only on the memory manufacturer (and the memory part):
- (none): Micron
- macronix_quad_enable: Macronix
- spansion_quad_enable: Spansion, Winbond, ...
- spansion_new_quad_enable: Spansion (latest memories), ...
- sr2_bit7_quad_enable: ??? (defined in the JESD216B specification)

yes. Some Micron have a VCONF register to set multiple I/O also.

Quoted 19 lines

The purpose of those functions is to implement the vendor specific procedure to set the so called "Quad Enable" (QE) bit in some Status Register of the SPI NOR memory.

Indeed, most QSPI memories are pin to pin compatible with legacy SPI memories. 2 pins of those memories were dedicated to the Write Protect (WP) and Reset/Hold (RST) functions. Hence before using any Quad SPI commands, almost all QSPI memories (Micron being the only exception I know) require us to set their QE bit so the WP and RST pins are reassigned to functions IO2 and IO3, the 3rd and 4th IO lines needed by SPI 1-1-4 and SPI 1-4-4 protocols. Then the Write Protect and Hold/Reset functions are disabled.

From a software point of view, there is nothing to do before using the SPI 1-1-2 or SPI 1-2-2 protocols. As a matter of fact, with those protocols, MISO and MOSI pins are simply reassigned to functions IO0 and IO1.

Quoted 4 lines

Back to the SPI flash controller, if the hardware needs to be configured in some way to use any Dual or Quad SPI protocols, it has to be done before calling spi_nor_scan() or more likely directly inside the controller driver specific implementation of nor->read(), nor->write().

yes.

Quoted 33 lines

With nor->read(), the driver has to check the value of nor->read_proto to know the actual number of I/O lines used during Instruction (x), Address/Dummy (y) and Data (z) clock cycles: nor->read_proto provides the driver with the SPI x-y-z protocol to be used.

Also nor->write() has to check the value of nor->write_proto.

[ nor->erase() has to check the value of nor->reg_proto: I removed nor->erase_proto since it always had the same value as nor->reg_proto. ]

Both nor->write() and nor->read are controller driver specific: they must be implemented by the controller driver and set before calling spi_nor_scan().

Then nor->read_proto and nor->write_proto are chosen from spi_nor_scan() based on the actual hardware capabilities shared by both the SPI memory and controller. The SPI controller driver tells the spi-nor framework which SPI protocols it supports or wants to use by setting the new 'struct spi_nor_hwcaps' argument of spi_nor_scan() accordingly.

You can have a look at the atmel-quadspi.c driver to have an example of what to do to support the SPI 1-2-2 or SPI 1-4-4 protocols at the controller side.

I've updated the Atmel Quad SPI driver since I'm the maintainer of this driver so I know the exact hardware capabilities of this controller but I don't have such a knowledge for Quad SPI controllers of other vendors.

Please note that patch is conservative: if a controller driver like the Aspeed one currently doesn't support the Quad or Dual SPI protocols, the patch doesn't enable them in hwcaps.mask.

Quoted 5 lines

Maintainers of the controller drivers will have to, if they want, extend their driver to add the support of new SPI protocols, otherwise those drivers will keep on working exactly as they used to do before this series. For instance, a controller driver which used SPI_NOR_QUAD before still uses the SPI 1-1-4 protocol.

Yes. I have some patches adding DUAL support to Aspeed but I will rebase on your patchset when it is merged because they currently conflict.

Thanks for the detailed explanations !

C.

Quoted 1,331 lines

Best regards,

Cyrille

Quoted 1,325 lines

I have a bunch of patches queued for Dual I/O data support but I think they will conflict with your patches. I will wait for this one to be merged. Then, I can look at Dual I/O address + data support.

Thanks,

C.

Quoted 1,301 lines
---
 drivers/mtd/devices/m25p80.c          |  16 +-
 drivers/mtd/spi-nor/aspeed-smc.c      |  23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |  80 +++---
 drivers/mtd/spi-nor/cadence-quadspi.c |  18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |   8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |  31 ++-
 drivers/mtd/spi-nor/intel-spi.c       |   7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |  16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |  22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 441 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h           | 158 +++++++++++-
 11 files changed, 643 insertions(+), 177 deletions(-)
drivers/mtd/devices/m25p80.c +9 -7
index c4df3b1bded0..68986a26c8fe 100644
@@ -111,10 +111,10 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 
 static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
 {
-	switch (nor->flash_read) {
-	case SPI_NOR_DUAL:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_2:
 		return 2;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		return 4;
 	default:
 		return 0;
@@ -196,7 +196,9 @@ static int m25p_probe(struct spi_device *spi)
 	struct flash_platform_data	*data;
 	struct m25p *flash;
 	struct spi_nor *nor;
-	enum read_mode mode = SPI_NOR_NORMAL;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ | SNOR_HWCAPS_PP),
+	};
 	char *flash_name;
 	int ret;
 
@@ -222,9 +224,9 @@ static int m25p_probe(struct spi_device *spi)
 	flash->spi = spi;
 
 	if (spi->mode & SPI_RX_QUAD)
-		mode = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	else if (spi->mode & SPI_RX_DUAL)
-		mode = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
 	if (data && data->name)
 		nor->mtd.name = data->name;
@@ -241,7 +243,7 @@ static int m25p_probe(struct spi_device *spi)
 	else
 		flash_name = spi->modalias;
 
-	ret = spi_nor_scan(nor, flash_name, mode);
+	ret = spi_nor_scan(nor, flash_name, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/aspeed-smc.c +13 -10
index 56051d30f000..723026d9cf0c 100644
@@ -585,14 +585,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 	 * TODO: Adjust clocks if fast read is supported and interpret
 	 * SPI-NOR flags to adjust controller settings.
 	 */
-	switch (chip->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-		cmd = CONTROL_COMMAND_MODE_NORMAL;
-		break;
-	case SPI_NOR_FAST:
-		cmd = CONTROL_COMMAND_MODE_FREAD;
-		break;
-	default:
+	if (chip->nor.read_proto == SNOR_PROTO_1_1_1) {
+		if (chip->nor.read_dummy == 0)
+			cmd = CONTROL_COMMAND_MODE_NORMAL;
+		else
+			cmd = CONTROL_COMMAND_MODE_FREAD;
+	} else {
 		dev_err(chip->nor.dev, "unsupported SPI read mode\n");
 		return -EINVAL;
 	}
@@ -608,6 +606,11 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 				  struct device_node *np, struct resource *r)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	const struct aspeed_smc_info *info = controller->info;
 	struct device *dev = controller->dev;
 	struct device_node *child;
@@ -671,11 +674,11 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 			break;
 
 		/*
-		 * TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
+		 * TODO: Add support for Dual and Quad SPI protocols
 
 		 * attach when board support is present as determined
 		 * by of property.
 		 */
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			break;
drivers/mtd/spi-nor/atmel-quadspi.c +53 -27
index 47937d9beec6..9f579f7c1733 100644
@@ -275,14 +275,48 @@ static void atmel_qspi_debug_command(struct atmel_qspi *aq,
 
 static int atmel_qspi_run_command(struct atmel_qspi *aq,
 				  const struct atmel_qspi_command *cmd,
-				  u32 ifr_tfrtyp, u32 ifr_width)
+				  u32 ifr_tfrtyp, enum spi_nor_protocol proto)
 {
 	u32 iar, icr, ifr, sr;
 	int err = 0;
 
 	iar = 0;
 	icr = 0;
-	ifr = ifr_tfrtyp | ifr_width;
+	ifr = ifr_tfrtyp;
+
+	/* Set the SPI protocol */
+	switch (proto) {
+	case SNOR_PROTO_1_1_1:
+		ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
+		break;
+
+	case SNOR_PROTO_1_1_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_1_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT;
+		break;
+
+	case SNOR_PROTO_1_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_IO;
+		break;
+
+	case SNOR_PROTO_1_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_IO;
+		break;
+
+	case SNOR_PROTO_2_2_2:
+		ifr |= QSPI_IFR_WIDTH_DUAL_CMD;
+		break;
+
+	case SNOR_PROTO_4_4_4:
+		ifr |= QSPI_IFR_WIDTH_QUAD_CMD;
+		break;
+
+	default:
+		return -EINVAL;
+	}
 
 	/* Compute instruction parameters */
 	if (cmd->enable.bits.instruction) {
@@ -434,7 +468,7 @@ static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode,
 	cmd.rx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
@@ -450,7 +484,7 @@ static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
 	cmd.tx_buf = buf;
 	cmd.buf_len = len;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
@@ -469,7 +503,7 @@ static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
 	cmd.tx_buf = write_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM,
-				     QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				     nor->write_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -484,7 +518,7 @@ static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs)
 	cmd.instruction = nor->erase_opcode;
 	cmd.address = (u32)offs;
 	return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
-				      QSPI_IFR_WIDTH_SINGLE_BIT_SPI);
+				      nor->reg_proto);
 }
 
 static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
@@ -493,27 +527,8 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	struct atmel_qspi *aq = nor->priv;
 	struct atmel_qspi_command cmd;
 	u8 num_mode_cycles, num_dummy_cycles;
-	u32 ifr_width;
 	ssize_t ret;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
-		ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
-		break;
-
-	case SPI_NOR_DUAL:
-		ifr_width = QSPI_IFR_WIDTH_DUAL_OUTPUT;
-		break;
-
-	case SPI_NOR_QUAD:
-		ifr_width = QSPI_IFR_WIDTH_QUAD_OUTPUT;
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
 	if (nor->read_dummy >= 2) {
 		num_mode_cycles = 2;
 		num_dummy_cycles = nor->read_dummy - 2;
@@ -536,7 +551,7 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
 	cmd.rx_buf = read_buf;
 	cmd.buf_len = len;
 	ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ_MEM,
-				     ifr_width);
+				     nor->read_proto);
 	return (ret < 0) ? ret : len;
 }
 
@@ -590,6 +605,17 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
 
 static int atmel_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_2_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_READ_1_4_4 |
+			 SNOR_HWCAPS_PP |
+			 SNOR_HWCAPS_PP_1_1_4 |
+			 SNOR_HWCAPS_PP_1_4_4),
+	};
 	struct device_node *child, *np = pdev->dev.of_node;
 	struct atmel_qspi *aq;
 	struct resource *res;
@@ -679,7 +705,7 @@ static int atmel_qspi_probe(struct platform_device *pdev)
 	if (err)
 		goto disable_clk;
 
-	err = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	err = spi_nor_scan(nor, NULL, &hwcaps);
 	if (err)
 		goto disable_clk;
 
drivers/mtd/spi-nor/cadence-quadspi.c +12 -6
index 9f8102de1b16..3f91a3e97892 100644
@@ -855,15 +855,14 @@ static int cqspi_set_protocol(struct spi_nor *nor, const int read)
 	f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 
 	if (read) {
-		switch (nor->flash_read) {
-		case SPI_NOR_NORMAL:
-		case SPI_NOR_FAST:
+		switch (nor->read_proto) {
+		case SNOR_PROTO_1_1_1:
 			f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
 			break;
-		case SPI_NOR_DUAL:
+		case SNOR_PROTO_1_1_2:
 			f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
 			break;
-		case SPI_NOR_QUAD:
+		case SNOR_PROTO_1_1_4:
 			f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
 			break;
 		default:
@@ -1069,6 +1068,13 @@ static void cqspi_controller_init(struct cqspi_st *cqspi)
 
 static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct platform_device *pdev = cqspi->pdev;
 	struct device *dev = &pdev->dev;
 	struct cqspi_flash_pdata *f_pdata;
@@ -1123,7 +1129,7 @@ static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
 			goto err;
 		}
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto err;
 
drivers/mtd/spi-nor/fsl-quadspi.c +7 -1
index 1476135e0d50..ec9c8e960fd2 100644
@@ -957,6 +957,12 @@ static void fsl_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
 
 static int fsl_qspi_probe(struct platform_device *pdev)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device_node *np = pdev->dev.of_node;
 	struct device *dev = &pdev->dev;
 	struct fsl_qspi *q;
@@ -1065,7 +1071,7 @@ static int fsl_qspi_probe(struct platform_device *pdev)
 		/* set the chip address for READID */
 		fsl_qspi_set_base_addr(q, nor);
 
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			goto mutex_failed;
 
drivers/mtd/spi-nor/hisi-sfc.c +23 -8
index a286350627a6..80e2d173abdd 100644
@@ -120,19 +120,24 @@ static inline int wait_op_finish(struct hifmc_host *host)
 		(reg & FMC_INT_OP_DONE), 0, FMC_WAIT_TIMEOUT);
 }
 
-static int get_if_type(enum read_mode flash_read)
+static int get_if_type(enum spi_nor_protocol proto)
 {
 	enum hifmc_iftype if_type;
 
-	switch (flash_read) {
-	case SPI_NOR_DUAL:
+	switch (proto) {
+	case SNOR_PROTO_1_1_2:
 		if_type = IF_TYPE_DUAL;
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_2_2:
+		if_type = IF_TYPE_DIO;
+		break;
+	case SNOR_PROTO_1_1_4:
 		if_type = IF_TYPE_QUAD;
 		break;
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	case SNOR_PROTO_1_4_4:
+		if_type = IF_TYPE_QIO;
+		break;
+	case SNOR_PROTO_1_1_1:
 	default:
 		if_type = IF_TYPE_STD;
 		break;
@@ -253,7 +258,10 @@ static int hisi_spi_nor_dma_transfer(struct spi_nor *nor, loff_t start_off,
 	writel(FMC_DMA_LEN_SET(len), host->regbase + FMC_DMA_LEN);
 
 	reg = OP_CFG_FM_CS(priv->chipselect);
-	if_type = get_if_type(nor->flash_read);
+	if (op_type == FMC_OP_READ)
+		if_type = get_if_type(nor->read_proto);
+	else
+		if_type = get_if_type(nor->write_proto);
 	reg |= OP_CFG_MEM_IF_TYPE(if_type);
 	if (op_type == FMC_OP_READ)
 		reg |= OP_CFG_DUMMY_NUM(nor->read_dummy >> 3);
@@ -321,6 +329,13 @@ static ssize_t hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
 static int hisi_spi_nor_register(struct device_node *np,
 				struct hifmc_host *host)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_READ_1_1_4 |
+			 SNOR_HWCAPS_PP),
+	};
 	struct device *dev = host->dev;
 	struct spi_nor *nor;
 	struct hifmc_priv *priv;
@@ -362,7 +377,7 @@ static int hisi_spi_nor_register(struct device_node *np,
 	nor->read = hisi_spi_nor_read;
 	nor->write = hisi_spi_nor_write;
 	nor->erase = NULL;
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/intel-spi.c +6 -1
index 986a3d020a3a..515aa1f7f4f1 100644
@@ -715,6 +715,11 @@ static void intel_spi_fill_partition(struct intel_spi *ispi,
 struct intel_spi *intel_spi_probe(struct device *dev,
 	struct resource *mem, const struct intel_spi_boardinfo *info)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	struct mtd_partition part;
 	struct intel_spi *ispi;
 	int ret;
@@ -746,7 +751,7 @@ struct intel_spi *intel_spi_probe(struct device *dev,
 	ispi->nor.write = intel_spi_write;
 	ispi->nor.erase = intel_spi_erase;
 
-	ret = spi_nor_scan(&ispi->nor, NULL, SPI_NOR_NORMAL);
+	ret = spi_nor_scan(&ispi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_info(dev, "failed to locate the chip\n");
 		return ERR_PTR(ret);
drivers/mtd/spi-nor/mtk-quadspi.c +11 -5
index e661877c23de..615e258866f7 100644
@@ -121,20 +121,20 @@ static void mt8173_nor_set_read_mode(struct mt8173_nor *mt8173_nor)
 {
 	struct spi_nor *nor = &mt8173_nor->nor;
 
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_1:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_FAST_READ, mt8173_nor->base +
 		       MTK_NOR_CFG1_REG);
 		break;
-	case SPI_NOR_DUAL:
+	case SNOR_PROTO_1_1_2:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA3_REG);
 		writeb(MTK_NOR_DUAL_READ_EN, mt8173_nor->base +
 		       MTK_NOR_DUAL_REG);
 		break;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		writeb(nor->read_opcode, mt8173_nor->base +
 		       MTK_NOR_PRGDATA4_REG);
 		writeb(MTK_NOR_QUAD_READ_EN, mt8173_nor->base +
@@ -381,6 +381,12 @@ static int mt8173_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
 static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 			struct device_node *flash_node)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_READ_1_1_2 |
+			 SNOR_HWCAPS_PP),
+	};
 	int ret;
 	struct spi_nor *nor;
 
@@ -399,7 +405,7 @@ static int mtk_nor_init(struct mt8173_nor *mt8173_nor,
 	nor->write_reg = mt8173_nor_write_reg;
 	nor->mtd.name = "mtk_nor";
 	/* initialized with NULL */
-	ret = spi_nor_scan(nor, NULL, SPI_NOR_DUAL);
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/nxp-spifi.c +12 -10
index 73a14f40928b..c5992e099542 100644
@@ -240,13 +240,12 @@ static int nxp_spifi_erase(struct spi_nor *nor, loff_t offs)
 
 static int nxp_spifi_setup_memory_cmd(struct nxp_spifi *spifi)
 {
-	switch (spifi->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-	case SPI_NOR_FAST:
+	switch (spifi->nor.read_proto) {
+	case SNOR_PROTO_1_1_1:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_ALL_SERIAL;
 		break;
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_2:
+	case SNOR_PROTO_1_1_4:
 		spifi->mcmd = SPIFI_CMD_FIELDFORM_QUAD_DUAL_DATA;
 		break;
 	default:
@@ -274,7 +273,11 @@ static void nxp_spifi_dummy_id_read(struct spi_nor *nor)
 static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 				 struct device_node *np)
 {
-	enum read_mode flash_read;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),
+	};
 	u32 ctrl, property;
 	u16 mode = 0;
 	int ret;
@@ -308,13 +311,12 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 
 	if (mode & SPI_RX_DUAL) {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 	} else if (mode & SPI_RX_QUAD) {
 		ctrl &= ~SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	} else {
 		ctrl |= SPIFI_CTRL_DUAL;
-		flash_read = SPI_NOR_NORMAL;
 	}
 
 	switch (mode & (SPI_CPHA | SPI_CPOL)) {
@@ -351,7 +353,7 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi,
 	 */
 	nxp_spifi_dummy_id_read(&spifi->nor);
 
-	ret = spi_nor_scan(&spifi->nor, NULL, flash_read);
+	ret = spi_nor_scan(&spifi->nor, NULL, &hwcaps);
 	if (ret) {
 		dev_err(spifi->dev, "device scan failed\n");
 		return ret;
drivers/mtd/spi-nor/spi-nor.c +348 -93
index d3cb44b28490..cc443c6cbae8 100644
@@ -150,24 +150,6 @@ static int read_cr(struct spi_nor *nor)
 }
 
 /*
- * Dummy Cycle calculation for different type of read.
- * It can be used to support more commands with
- * different dummy cycle requirements.
- */
-static inline int spi_nor_read_dummy_cycles(struct spi_nor *nor)
-{
-	switch (nor->flash_read) {
-	case SPI_NOR_FAST:
-	case SPI_NOR_DUAL:
-	case SPI_NOR_QUAD:
-		return 8;
-	case SPI_NOR_NORMAL:
-		return 0;
-	}
-	return 0;
-}
-
-/*
  * Write status register 1 byte
  * Returns negative if error occurred.
  */
@@ -221,6 +203,10 @@ static inline u8 spi_nor_convert_3to4_read(u8 opcode)
 		{ SPINOR_OP_READ_1_2_2,	SPINOR_OP_READ_1_2_2_4B },
 		{ SPINOR_OP_READ_1_1_4,	SPINOR_OP_READ_1_1_4_4B },
 		{ SPINOR_OP_READ_1_4_4,	SPINOR_OP_READ_1_4_4_4B },
+
+		{ SPINOR_OP_READ_1_1_1_DTR,	SPINOR_OP_READ_1_1_1_DTR_4B },
+		{ SPINOR_OP_READ_1_2_2_DTR,	SPINOR_OP_READ_1_2_2_DTR_4B },
+		{ SPINOR_OP_READ_1_4_4_DTR,	SPINOR_OP_READ_1_4_4_DTR_4B },
 	};
 
 	return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
@@ -1459,30 +1445,6 @@ static int spansion_quad_enable(struct spi_nor *nor)
 	return 0;
 }
 
-static int set_quad_mode(struct spi_nor *nor, const struct flash_info *info)
-{
-	int status;
-
-	switch (JEDEC_MFR(info)) {
-	case SNOR_MFR_MACRONIX:
-		status = macronix_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Macronix quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	case SNOR_MFR_MICRON:
-		return 0;
-	default:
-		status = spansion_quad_enable(nor);
-		if (status) {
-			dev_err(nor->dev, "Spansion quad-read not enabled\n");
-			return -EINVAL;
-		}
-		return status;
-	}
-}
-
 static int spi_nor_check(struct spi_nor *nor)
 {
 	if (!nor->dev || !nor->read || !nor->write ||
@@ -1535,8 +1497,322 @@ static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor)
 	return 0;
 }
 
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
+
+struct spi_nor_read_command {
+	u8			num_mode_clocks;
+	u8			num_wait_states;
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+struct spi_nor_pp_command {
+	u8			opcode;
+	enum spi_nor_protocol	proto;
+};
+
+enum spi_nor_read_command_index {
+	SNOR_CMD_READ,
+	SNOR_CMD_READ_FAST,
+	SNOR_CMD_READ_1_1_1_DTR,
+
+	/* Dual SPI */
+	SNOR_CMD_READ_1_1_2,
+	SNOR_CMD_READ_1_2_2,
+	SNOR_CMD_READ_2_2_2,
+	SNOR_CMD_READ_1_2_2_DTR,
+
+	/* Quad SPI */
+	SNOR_CMD_READ_1_1_4,
+	SNOR_CMD_READ_1_4_4,
+	SNOR_CMD_READ_4_4_4,
+	SNOR_CMD_READ_1_4_4_DTR,
+
+	/* Octo SPI */
+	SNOR_CMD_READ_1_1_8,
+	SNOR_CMD_READ_1_8_8,
+	SNOR_CMD_READ_8_8_8,
+	SNOR_CMD_READ_1_8_8_DTR,
+
+	SNOR_CMD_READ_MAX
+};
+
+enum spi_nor_pp_command_index {
+	SNOR_CMD_PP,
+
+	/* Quad SPI */
+	SNOR_CMD_PP_1_1_4,
+	SNOR_CMD_PP_1_4_4,
+	SNOR_CMD_PP_4_4_4,
+
+	/* Octo SPI */
+	SNOR_CMD_PP_1_1_8,
+	SNOR_CMD_PP_1_8_8,
+	SNOR_CMD_PP_8_8_8,
+
+	SNOR_CMD_PP_MAX
+};
+
+struct spi_nor_flash_parameter {
+	u64				size;
+	u32				page_size;
+
+	struct spi_nor_hwcaps		hwcaps;
+	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
+	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
+
+	int (*quad_enable)(struct spi_nor *nor);
+};
+
+
+static inline void
+spi_nor_set_read_settings(struct spi_nor_read_command *read,
+			  u8 num_mode_clocks,
+			  u8 num_wait_states,
+			  u8 opcode,
+			  enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = num_mode_clocks;
+	read->num_wait_states = num_wait_states;
+	read->opcode = opcode;
+	read->proto = proto;
+}
+
+static inline void
+spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+			u8 opcode,
+			enum spi_nor_protocol proto)
+{
+	pp->opcode = opcode;
+	pp->proto = proto;
+}
+
+static int spi_nor_init_params(struct spi_nor *nor,
+			       const struct flash_info *info,
+			       struct spi_nor_flash_parameter *params)
+{
+	/* Set legacy flash parameters as default. */
+	memset(params, 0, sizeof(*params));
+
+	/* Set SPI NOR sizes. */
+	params->size = info->sector_size * info->n_sectors;
+	params->page_size = info->page_size;
+
+	/* (Fast) Read settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_READ;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
+				  0, 0, SPINOR_OP_READ,
+				  SNOR_PROTO_1_1_1);
+	if (!(info->flags & SPI_NOR_NO_FR)) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
+					  0, 8, SPINOR_OP_READ_FAST,
+					  SNOR_PROTO_1_1_1);
+	}
+	if (info->flags & SPI_NOR_DUAL_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
+					  0, 8, SPINOR_OP_READ_1_1_2,
+					  SNOR_PROTO_1_1_2);
+	}
+	if (info->flags & SPI_NOR_QUAD_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+					  0, 8, SPINOR_OP_READ_1_1_4,
+					  SNOR_PROTO_1_1_4);
+	}
+
+	/* Page Program settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_PP;
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+	/* Select the procedure to set the Quad Enable bit. */
+	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
+				   SNOR_HWCAPS_PP_QUAD)) {
+		switch (JEDEC_MFR(info)) {
+		case SNOR_MFR_MACRONIX:
+			params->quad_enable = macronix_quad_enable;
+			break;
+
+		case SNOR_MFR_MICRON:
+			break;
+
+		default:
+			params->quad_enable = spansion_quad_enable;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps)
 {
+	switch (hwcaps) {
+	case SNOR_HWCAPS_READ:			return SNOR_CMD_READ;
+	case SNOR_HWCAPS_READ_FAST:		return SNOR_CMD_READ_FAST;
+	case SNOR_HWCAPS_READ_1_1_1_DTR:	return SNOR_CMD_READ_1_1_1_DTR;
+	case SNOR_HWCAPS_READ_1_1_2:		return SNOR_CMD_READ_1_1_2;
+	case SNOR_HWCAPS_READ_1_2_2:		return SNOR_CMD_READ_1_2_2;
+	case SNOR_HWCAPS_READ_2_2_2:		return SNOR_CMD_READ_2_2_2;
+	case SNOR_HWCAPS_READ_1_2_2_DTR:	return SNOR_CMD_READ_1_2_2_DTR;
+	case SNOR_HWCAPS_READ_1_1_4:		return SNOR_CMD_READ_1_1_4;
+	case SNOR_HWCAPS_READ_1_4_4:		return SNOR_CMD_READ_1_4_4;
+	case SNOR_HWCAPS_READ_4_4_4:		return SNOR_CMD_READ_4_4_4;
+	case SNOR_HWCAPS_READ_1_4_4_DTR:	return SNOR_CMD_READ_1_4_4_DTR;
+	case SNOR_HWCAPS_READ_1_1_8:		return SNOR_CMD_READ_1_1_8;
+	case SNOR_HWCAPS_READ_1_8_8:		return SNOR_CMD_READ_1_8_8;
+	case SNOR_HWCAPS_READ_8_8_8:		return SNOR_CMD_READ_8_8_8;
+	case SNOR_HWCAPS_READ_1_8_8_DTR:	return SNOR_CMD_READ_1_8_8_DTR;
+
+	case SNOR_HWCAPS_PP:			return SNOR_CMD_PP;
+	case SNOR_HWCAPS_PP_1_1_4:		return SNOR_CMD_PP_1_1_4;
+	case SNOR_HWCAPS_PP_1_4_4:		return SNOR_CMD_PP_1_4_4;
+	case SNOR_HWCAPS_PP_4_4_4:		return SNOR_CMD_PP_4_4_4;
+	case SNOR_HWCAPS_PP_1_1_8:		return SNOR_CMD_PP_1_1_8;
+	case SNOR_HWCAPS_PP_1_8_8:		return SNOR_CMD_PP_1_8_8;
+	case SNOR_HWCAPS_PP_8_8_8:		return SNOR_CMD_PP_8_8_8;
+	}
+
+	return -EINVAL;
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,
+			       const struct spi_nor_flash_parameter *params,
+			       u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+	const struct spi_nor_read_command *read;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	read = &params->reads[cmd];
+	nor->read_opcode = read->opcode;
+	nor->read_proto = read->proto;
+
+	/*
+	 * In the spi-nor framework, we don't need to make the difference
+	 * between mode clock cycles and wait state clock cycles.
+	 * Indeed, the value of the mode clock cycles is used by a QSPI
+	 * flash memory to know whether it should enter or leave its 0-4-4
+	 * (Continuous Read / XIP) mode.
+	 * eXecution In Place is out of the scope of the mtd sub-system.
+	 * Hence we choose to merge both mode and wait state clock cycles
+	 * into the so called dummy clock cycles.
+	 */
+	nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+	return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+			     const struct spi_nor_flash_parameter *params,
+			     u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+	const struct spi_nor_pp_command *pp;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	pp = &params->page_programs[cmd];
+	nor->program_opcode = pp->opcode;
+	nor->write_proto = pp->proto;
+	return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+				const struct flash_info *info)
+{
+	struct mtd_info *mtd = &nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+	return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params,
+			 const struct spi_nor_hwcaps *hwcaps)
+{
+	u32 ignored_mask, shared_mask;
+	bool enable_quad_io;
+	int err;
+
+	/*
+	 * Keep only the hardware capabilities supported by both the SPI
+	 * controller and the SPI flash memory.
+	 */
+	shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+	/* SPI protocol classes N-N-N are not supported yet. */
+	ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+			SNOR_HWCAPS_READ_4_4_4 |
+			SNOR_HWCAPS_READ_8_8_8 |
+			SNOR_HWCAPS_PP_4_4_4 |
+			SNOR_HWCAPS_PP_8_8_8);
+	if (shared_mask & ignored_mask) {
+		dev_dbg(nor->dev,
+			"SPI protocol classes N-N-N are not supported yet.\n");
+		shared_mask &= ~ignored_mask;
+	}
+
+	/* Select the (Fast) Read command. */
+	err = spi_nor_select_read(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid (fast) read\n");
+		return err;
+	}
+
+	/* Select the Page Program command. */
+	err = spi_nor_select_pp(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid page program\n");
+		return err;
+	}
+
+	/* Select the Sector Erase command. */
+	err = spi_nor_select_erase(nor, info);
+	if (err) {
+		dev_err(nor->dev, "invalid sector/block erase\n");
+		return err;
+	}
+
+	/* Enable Quad I/O if needed. */
+	enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+			  spi_nor_get_protocol_width(nor->write_proto) == 4);
+	if (enable_quad_io && params->quad_enable)
+		nor->flash_quad_enable = params->quad_enable;
+	else
+		nor->flash_quad_enable = NULL;
+
+	return 0;
+}
+
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps)
+{
+	struct spi_nor_flash_parameter params;
 	const struct flash_info *info = NULL;
 	struct device *dev = nor->dev;
 	struct mtd_info *mtd = &nor->mtd;
@@ -1548,6 +1824,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (ret)
 		return ret;
 
+	/* Reset SPI protocol for all commands */
+	nor->reg_proto = SNOR_PROTO_1_1_1;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
 	if (name)
 		info = spi_nor_match_id(name);
 	/* Try to auto-detect if chip name wasn't specified or not found */
@@ -1580,6 +1861,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		}
 	}
 
+	/* Parse the Serial Flash Discoverable Parameters table */
+	ret = spi_nor_init_params(nor, info, &params);
+	if (ret)
+		return ret;
+
 	mutex_init(&nor->lock);
 
 	/*
@@ -1610,7 +1896,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	mtd->type = MTD_NORFLASH;
 	mtd->writesize = 1;
 	mtd->flags = MTD_CAP_NORFLASH;
-	mtd->size = info->sector_size * info->n_sectors;
+	mtd->size = params.size;
 	mtd->_erase = spi_nor_erase;
 	mtd->_read = spi_nor_read;
 
@@ -1641,76 +1927,47 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (info->flags & NO_CHIP_ERASE)
 		nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
 
-#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
-	/* prefer "small sector" erase if possible */
-	if (info->flags & SECT_4K) {
-		nor->erase_opcode = SPINOR_OP_BE_4K;
-		mtd->erasesize = 4096;
-	} else if (info->flags & SECT_4K_PMC) {
-		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
-		mtd->erasesize = 4096;
-	} else
-#endif
-	{
-		nor->erase_opcode = SPINOR_OP_SE;
-		mtd->erasesize = info->sector_size;
-	}
-
 	if (info->flags & SPI_NOR_NO_ERASE)
 		mtd->flags |= MTD_NO_ERASE;
 
 	mtd->dev.parent = dev;
-	nor->page_size = info->page_size;
+	nor->page_size = params.page_size;
 	mtd->writebufsize = nor->page_size;
 
 	if (np) {
 		/* If we were instantiated by DT, use it */
 		if (of_property_read_bool(np, "m25p,fast-read"))
-			nor->flash_read = SPI_NOR_FAST;
+			params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 		else
-			nor->flash_read = SPI_NOR_NORMAL;
+			params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
 	} else {
 		/* If we weren't instantiated by DT, default to fast-read */
-		nor->flash_read = SPI_NOR_FAST;
+		params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
 	}
 
 	/* Some devices cannot do fast-read, no matter what DT tells us */
 	if (info->flags & SPI_NOR_NO_FR)
-		nor->flash_read = SPI_NOR_NORMAL;
+		params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
+
+	/*
+	 * Configure the SPI memory:
+	 * - select op codes for (Fast) Read, Page Program and Sector Erase.
+	 * - set the number of dummy cycles (mode cycles + wait states).
+	 * - set the SPI protocols for register and memory accesses.
+	 * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
+	 */
+	ret = spi_nor_setup(nor, info, &params, hwcaps);
+	if (ret)
+		return ret;
 
-	/* Quad/Dual-read mode takes precedence over fast/normal */
-	if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
-		ret = set_quad_mode(nor, info);
+	if (nor->flash_quad_enable) {
+		ret = nor->flash_quad_enable(nor);
 		if (ret) {
 			dev_err(dev, "quad mode not supported\n");
 			return ret;
 		}
-		nor->flash_read = SPI_NOR_QUAD;
-	} else if (mode == SPI_NOR_DUAL && info->flags & SPI_NOR_DUAL_READ) {
-		nor->flash_read = SPI_NOR_DUAL;
 	}
 
-	/* Default commands */
-	switch (nor->flash_read) {
-	case SPI_NOR_QUAD:
-		nor->read_opcode = SPINOR_OP_READ_1_1_4;
-		break;
-	case SPI_NOR_DUAL:
-		nor->read_opcode = SPINOR_OP_READ_1_1_2;
-		break;
-	case SPI_NOR_FAST:
-		nor->read_opcode = SPINOR_OP_READ_FAST;
-		break;
-	case SPI_NOR_NORMAL:
-		nor->read_opcode = SPINOR_OP_READ;
-		break;
-	default:
-		dev_err(dev, "No Read opcode defined\n");
-		return -EINVAL;
-	}
-
-	nor->program_opcode = SPINOR_OP_PP;
-
 	if (info->addr_width)
 		nor->addr_width = info->addr_width;
 	else if (mtd->size > 0x1000000) {
@@ -1731,8 +1988,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		return -EINVAL;
 	}
 
-	nor->read_dummy = spi_nor_read_dummy_cycles(nor);
-
 	if (info->flags & SPI_S3AN) {
 		ret = s3an_nor_scan(info, nor);
 		if (ret)
include/linux/mtd/spi-nor.h +147 -8
index f2a718030476..732ee6cd5330 100644
@@ -73,6 +73,15 @@
 #define SPINOR_OP_BE_32K_4B	0x5c	/* Erase 32KiB block */
 #define SPINOR_OP_SE_4B		0xdc	/* Sector erase (usually 64KiB) */
 
+/* Double Transfer Rate opcodes - defined in JEDEC JESD216B. */
+#define SPINOR_OP_READ_1_1_1_DTR	0x0d
+#define SPINOR_OP_READ_1_2_2_DTR	0xbd
+#define SPINOR_OP_READ_1_4_4_DTR	0xed
+
+#define SPINOR_OP_READ_1_1_1_DTR_4B	0x0e
+#define SPINOR_OP_READ_1_2_2_DTR_4B	0xbe
+#define SPINOR_OP_READ_1_4_4_DTR_4B	0xee
+
 /* Used for SST flashes only. */
 #define SPINOR_OP_BP		0x02	/* Byte program */
 #define SPINOR_OP_WRDI		0x04	/* Write disable */
@@ -119,13 +128,75 @@
 /* Configuration Register bits. */
 #define CR_QUAD_EN_SPAN		BIT(1)	/* Spansion Quad I/O */
 
-enum read_mode {
-	SPI_NOR_NORMAL = 0,
-	SPI_NOR_FAST,
-	SPI_NOR_DUAL,
-	SPI_NOR_QUAD,
+
+/* Supported SPI protocols */
+#define SNOR_PROTO_WIDTH_MASK	GENMASK(7, 0)
+
+#define SNOR_PROTO_CLASS_MASK	GENMASK(9, 8)
+#define SNOR_PROTO_CLASS_1_1_N	(0x0u << 8)
+#define SNOR_PROTO_CLASS_1_N_N	(0x1u << 8)
+#define SNOR_PROTO_CLASS_N_N_N	(0x2u << 8)
+
+#define SNOR_PROTO_IS_DTR	BIT(10)	/* Double Transfer Rate */
+
+#define SNOR_PROTO_STR(_pclass, _pwidth) \
+	((_pclass) | (_pwidth))
+#define SNOR_PROTO_DTR(_pclass, _pwidth) \
+	(SNOR_PROTO_IS_DTR | (_pclass) | (_pwidth))
+
+enum spi_nor_protocol {
+	SNOR_PROTO_1_1_1 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_1_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 2),
+	SNOR_PROTO_1_1_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 4),
+	SNOR_PROTO_1_1_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_1_N, 8),
+	SNOR_PROTO_1_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_1_N_N, 8),
+	SNOR_PROTO_2_2_2 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 2),
+	SNOR_PROTO_4_4_4 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 4),
+	SNOR_PROTO_8_8_8 = SNOR_PROTO_STR(SNOR_PROTO_CLASS_N_N_N, 8),
+
+	SNOR_PROTO_1_1_1_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_1_N, 1),
+	SNOR_PROTO_1_2_2_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 2),
+	SNOR_PROTO_1_4_4_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 4),
+	SNOR_PROTO_1_8_8_DTR = SNOR_PROTO_DTR(SNOR_PROTO_CLASS_1_N_N, 8),
 };
 
+static inline bool spi_nor_protocol_is_dtr(enum spi_nor_protocol proto)
+{
+	return (proto & SNOR_PROTO_IS_DTR) == SNOR_PROTO_IS_DTR;
+}
+
+static inline u32 spi_nor_get_protocol_class(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_CLASS_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_width(enum spi_nor_protocol proto)
+{
+	return proto & SNOR_PROTO_WIDTH_MASK;
+}
+
+static inline u8 spi_nor_get_protocol_inst_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) == SNOR_PROTO_CLASS_N_N_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_addr_width(enum spi_nor_protocol proto)
+{
+	return (spi_nor_get_protocol_class(proto) != SNOR_PROTO_CLASS_1_1_N) ?
+		spi_nor_get_protocol_width(proto) :
+		1u;
+}
+
+static inline u8 spi_nor_get_protocol_data_width(enum spi_nor_protocol proto)
+{
+	return spi_nor_get_protocol_width(proto);
+}
+
+
 #define SPI_NOR_MAX_CMD_SIZE	8
 enum spi_nor_ops {
 	SPI_NOR_OPS_READ = 0,
@@ -154,9 +225,11 @@ enum spi_nor_option_flags {
  * @read_opcode:	the read opcode
  * @read_dummy:		the dummy needed by the read operation
  * @program_opcode:	the program opcode
- * @flash_read:		the mode of the read
  * @sst_write_second:	used by the SST write operation
  * @flags:		flag options for the current SPI-NOR (SNOR_F_*)
+ * @read_proto:		the SPI protocol for read operations
+ * @write_proto:	the SPI protocol for write operations
+ * @reg_proto		the SPI protocol for read_reg/write_reg/erase operations
  * @cmd_buf:		used by the write_reg
  * @prepare:		[OPTIONAL] do some preparations for the
  *			read/write/erase/lock/unlock operations
@@ -173,6 +246,7 @@ enum spi_nor_option_flags {
  * @flash_unlock:	[FLASH-SPECIFIC] unlock a region of the SPI NOR
  * @flash_is_locked:	[FLASH-SPECIFIC] check if a region of the SPI NOR is
  *			completely locked
+ * @flash_quad_enable:	[FLASH-SPECIFIC] set the Quad Enable bit of the SPI NOR
  * @priv:		the private data
  */
 struct spi_nor {
@@ -185,7 +259,9 @@ struct spi_nor {
 	u8			read_opcode;
 	u8			read_dummy;
 	u8			program_opcode;
-	enum read_mode		flash_read;
+	enum spi_nor_protocol	read_proto;
+	enum spi_nor_protocol	write_proto;
+	enum spi_nor_protocol	reg_proto;
 	bool			sst_write_second;
 	u32			flags;
 	u8			cmd_buf[SPI_NOR_MAX_CMD_SIZE];
@@ -204,6 +280,7 @@ struct spi_nor {
 	int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
 	int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
+	int (*flash_quad_enable)(struct spi_nor *nor);
 
 	void *priv;
 };
@@ -219,11 +296,73 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
 	return mtd_get_of_node(&nor->mtd);
 }
 
+
+/**
+ * struct spi_nor_hwcaps - Structure for describing the hardware capabilies
+ * supported by the SPI controller (bus master).
+ * @mask:		the bitmask listing all the supported hw capabilies
+ */
+struct spi_nor_hwcaps {
+	u32	mask;
+};
+
+/*
+ *(Fast) Read capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * As a matter of performances, it is relevant to use Octo SPI protocols first,
+ * then Quad SPI protocols before Dual SPI protocols, Fast Read and lastly
+ * (Slow) Read.
+ */
+#define SNOR_HWCAPS_READ_MASK		GENMASK(15, 0)
+#define SNOR_HWCAPS_READ		BIT(0)
+#define SNOR_HWCAPS_READ_FAST		BIT(1)
+#define SNOR_HWCAPS_READ_1_1_1_DTR	BIT(2)
+
+#define SNOR_HWCAPS_READ_DUAL		GENMASK(7, 4)
+#define SNOR_HWCAPS_READ_1_1_2		BIT(4)
+#define SNOR_HWCAPS_READ_1_2_2		BIT(5)
+#define SNOR_HWCAPS_READ_2_2_2		BIT(6)
+#define SNOR_HWCAPS_READ_1_2_2_DTR	BIT(7)
+
+#define SNOR_HWCAPS_READ_QUAD		GENMASK(11, 8)
+#define SNOR_HWCAPS_READ_1_1_4		BIT(8)
+#define SNOR_HWCAPS_READ_1_4_4		BIT(9)
+#define SNOR_HWCAPS_READ_4_4_4		BIT(10)
+#define SNOR_HWCAPS_READ_1_4_4_DTR	BIT(11)
+
+#define SNOR_HWCPAS_READ_OCTO		GENMASK(15, 12)
+#define SNOR_HWCAPS_READ_1_1_8		BIT(12)
+#define SNOR_HWCAPS_READ_1_8_8		BIT(13)
+#define SNOR_HWCAPS_READ_8_8_8		BIT(14)
+#define SNOR_HWCAPS_READ_1_8_8_DTR	BIT(15)
+
+/*
+ * Page Program capabilities.
+ * MUST be ordered by priority: the higher bit position, the higher priority.
+ * Like (Fast) Read capabilities, Octo/Quad SPI protocols are preferred to the
+ * legacy SPI 1-1-1 protocol.
+ * Note that Dual Page Programs are not supported because there is no existing
+ * JEDEC/SFDP standard to define them. Also at this moment no SPI flash memory
+ * implements such commands.
+ */
+#define SNOR_HWCAPS_PP_MASK	GENMASK(22, 16)
+#define SNOR_HWCAPS_PP		BIT(16)
+
+#define SNOR_HWCAPS_PP_QUAD	GENMASK(19, 17)
+#define SNOR_HWCAPS_PP_1_1_4	BIT(17)
+#define SNOR_HWCAPS_PP_1_4_4	BIT(18)
+#define SNOR_HWCAPS_PP_4_4_4	BIT(19)
+
+#define SNOR_HWCAPS_PP_OCTO	GENMASK(22, 20)
+#define SNOR_HWCAPS_PP_1_1_8	BIT(20)
+#define SNOR_HWCAPS_PP_1_8_8	BIT(21)
+#define SNOR_HWCAPS_PP_8_8_8	BIT(22)
+
 /**
  * spi_nor_scan() - scan the SPI NOR
  * @nor:	the spi_nor structure
  * @name:	the chip type name
- * @mode:	the read mode supported by the driver
+ * @hwcaps:	the hardware capabilities supported by the controller driver
  *
  * The drivers can use this fuction to scan the SPI NOR.
  * In the scanning, it will try to get all the necessary information to

include/linux/mtd/spi-nor.h static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor) · line 378

Quoted 8 lines
include/linux/mtd/spi-nor.h +2 -1
@@ -233,6 +372,7 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
  *
  * Return: 0 for success, others for failure.
  */
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode);
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps);
 
 #endif

______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

#6 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

In reply to Cyrille Pitchen
Quoted 24 lines · Cyrille Pitchen

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Quoted 5 lines

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

Applied to github/spi-nor.

#7 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

In reply to Cyrille Pitchen
Quoted 62 lines · Cyrille Pitchen

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

---
 drivers/mtd/devices/m25p80.c          |  16 +-
 drivers/mtd/spi-nor/aspeed-smc.c      |  23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |  80 +++---
 drivers/mtd/spi-nor/cadence-quadspi.c |  18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |   8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |  31 ++-
 drivers/mtd/spi-nor/intel-spi.c       |   7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |  16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |  22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 441 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h           | 158 +++++++++++-
 11 files changed, 643 insertions(+), 177 deletions(-)
drivers/mtd/devices/m25p80.c +3 -3
index c4df3b1bded0..68986a26c8fe 100644
@@ -111,10 +111,10 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 
 static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
 {
-	switch (nor->flash_read) {
-	case SPI_NOR_DUAL:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_2:
 		return 2;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		return 4;
 	default:
 		return 0;

drivers/mtd/devices/m25p80.c static int m25p_probe(struct spi_device *spi) · line 200

Quoted 6 lines
drivers/mtd/devices/m25p80.c +2 -1
@@ -196,7 +196,9 @@ static int m25p_probe(struct spi_device *spi)
 	struct flash_platform_data	*data;
 	struct m25p *flash;
 	struct spi_nor *nor;
-	enum read_mode mode = SPI_NOR_NORMAL;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ | SNOR_HWCAPS_PP),

Drop the unneeded parenthesis.

Quoted 51 lines
drivers/mtd/devices/m25p80.c +4 -3
+	};
 	char *flash_name;
 	int ret;
 
@@ -222,9 +224,9 @@ static int m25p_probe(struct spi_device *spi)
 	flash->spi = spi;
 
 	if (spi->mode & SPI_RX_QUAD)
-		mode = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	else if (spi->mode & SPI_RX_DUAL)
-		mode = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
 	if (data && data->name)
 		nor->mtd.name = data->name;
@@ -241,7 +243,7 @@ static int m25p_probe(struct spi_device *spi)
 	else
 		flash_name = spi->modalias;
 
-	ret = spi_nor_scan(nor, flash_name, mode);
+	ret = spi_nor_scan(nor, flash_name, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/aspeed-smc.c +6 -8
index 56051d30f000..723026d9cf0c 100644
@@ -585,14 +585,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 	 * TODO: Adjust clocks if fast read is supported and interpret
 	 * SPI-NOR flags to adjust controller settings.
 	 */
-	switch (chip->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-		cmd = CONTROL_COMMAND_MODE_NORMAL;
-		break;
-	case SPI_NOR_FAST:
-		cmd = CONTROL_COMMAND_MODE_FREAD;
-		break;
-	default:
+	if (chip->nor.read_proto == SNOR_PROTO_1_1_1) {
+		if (chip->nor.read_dummy == 0)
+			cmd = CONTROL_COMMAND_MODE_NORMAL;
+		else
+			cmd = CONTROL_COMMAND_MODE_FREAD;
+	} else {
 		dev_err(chip->nor.dev, "unsupported SPI read mode\n");
 		return -EINVAL;
 	}

drivers/mtd/spi-nor/aspeed-smc.c static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) · line 612

Quoted 7 lines
drivers/mtd/spi-nor/aspeed-smc.c +4 -0
@@ -608,6 +606,11 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 				  struct device_node *np, struct resource *r)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),

Drop the extra parenthesis ... shouldn't the structure be const ?

Quoted 17 lines
drivers/mtd/spi-nor/aspeed-smc.c +3 -2
+	};
 	const struct aspeed_smc_info *info = controller->info;
 	struct device *dev = controller->dev;
 	struct device_node *child;
@@ -671,11 +674,11 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 			break;
 
 		/*
-		 * TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
+		 * TODO: Add support for Dual and Quad SPI protocols
 		 * attach when board support is present as determined
 		 * by of property.
 		 */
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			break;

[...]

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 9 lines
drivers/mtd/spi-nor/aspeed-smc.c +9 -0
+struct spi_nor_flash_parameter {
+	u64				size;
+	u32				page_size;
+
+	struct spi_nor_hwcaps		hwcaps;
+	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
+	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
+
+	int (*quad_enable)(struct spi_nor *nor);

This callback should be added by a separate patch, there's WAY too much crap in this patch.

Quoted 7 lines
drivers/mtd/spi-nor/aspeed-smc.c +7 -0
+};
+
+
+static inline void
+spi_nor_set_read_settings(struct spi_nor_read_command *read,
+			  u8 num_mode_clocks,
+			  u8 num_wait_states,

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+			  u8 opcode,
+			  enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = num_mode_clocks;
+	read->num_wait_states = num_wait_states;
+	read->opcode = opcode;
+	read->proto = proto;
+}
+
+static inline void

Drop the inline , the compiler will decide. Fix globally.

Quoted 14 lines
drivers/mtd/spi-nor/aspeed-smc.c +14 -0
+spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+			u8 opcode,
+			enum spi_nor_protocol proto)
+{
+	pp->opcode = opcode;
+	pp->proto = proto;
+}
+
+static int spi_nor_init_params(struct spi_nor *nor,
+			       const struct flash_info *info,
+			       struct spi_nor_flash_parameter *params)
+{
+	/* Set legacy flash parameters as default. */
+	memset(params, 0, sizeof(*params));

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+
+	/* Set SPI NOR sizes. */
+	params->size = info->sector_size * info->n_sectors;
+	params->page_size = info->page_size;
+
+	/* (Fast) Read settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_READ;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
+				  0, 0, SPINOR_OP_READ,
+				  SNOR_PROTO_1_1_1);

Newline

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 6 lines
drivers/mtd/spi-nor/aspeed-smc.c +6 -0
+	if (!(info->flags & SPI_NOR_NO_FR)) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
+					  0, 8, SPINOR_OP_READ_FAST,
+					  SNOR_PROTO_1_1_1);
+	}

Newline

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 6 lines
drivers/mtd/spi-nor/aspeed-smc.c +6 -0
+	if (info->flags & SPI_NOR_DUAL_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
+					  0, 8, SPINOR_OP_READ_1_1_2,
+					  SNOR_PROTO_1_1_2);
+	}

Newline ... this is really hard to read as it is.

Quoted 14 lines
drivers/mtd/spi-nor/aspeed-smc.c +14 -0
+	if (info->flags & SPI_NOR_QUAD_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+					  0, 8, SPINOR_OP_READ_1_1_4,
+					  SNOR_PROTO_1_1_4);
+	}
+
+	/* Page Program settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_PP;
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+	/* Select the procedure to set the Quad Enable bit. */
+	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+				   SNOR_HWCAPS_PP_QUAD)) {
+		switch (JEDEC_MFR(info)) {
+		case SNOR_MFR_MACRONIX:
+			params->quad_enable = macronix_quad_enable;
+			break;
+
+		case SNOR_MFR_MICRON:
+			break;
+
+		default:

Are you sure this is a good idea ?

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 9 lines
drivers/mtd/spi-nor/aspeed-smc.c +9 -0
+			params->quad_enable = spansion_quad_enable;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps)

const u32 hwcaps ...

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 3 lines

{

drivers/mtd/spi-nor/aspeed-smc.c +2 -0
+	switch (hwcaps) {
+	case SNOR_HWCAPS_READ:			return SNOR_CMD_READ;

You can do this as a table lookup or array indexing and it would be checkpatch clean.

Quoted 28 lines
drivers/mtd/spi-nor/aspeed-smc.c +28 -0
+	case SNOR_HWCAPS_READ_FAST:		return SNOR_CMD_READ_FAST;
+	case SNOR_HWCAPS_READ_1_1_1_DTR:	return SNOR_CMD_READ_1_1_1_DTR;
+	case SNOR_HWCAPS_READ_1_1_2:		return SNOR_CMD_READ_1_1_2;
+	case SNOR_HWCAPS_READ_1_2_2:		return SNOR_CMD_READ_1_2_2;
+	case SNOR_HWCAPS_READ_2_2_2:		return SNOR_CMD_READ_2_2_2;
+	case SNOR_HWCAPS_READ_1_2_2_DTR:	return SNOR_CMD_READ_1_2_2_DTR;
+	case SNOR_HWCAPS_READ_1_1_4:		return SNOR_CMD_READ_1_1_4;
+	case SNOR_HWCAPS_READ_1_4_4:		return SNOR_CMD_READ_1_4_4;
+	case SNOR_HWCAPS_READ_4_4_4:		return SNOR_CMD_READ_4_4_4;
+	case SNOR_HWCAPS_READ_1_4_4_DTR:	return SNOR_CMD_READ_1_4_4_DTR;
+	case SNOR_HWCAPS_READ_1_1_8:		return SNOR_CMD_READ_1_1_8;
+	case SNOR_HWCAPS_READ_1_8_8:		return SNOR_CMD_READ_1_8_8;
+	case SNOR_HWCAPS_READ_8_8_8:		return SNOR_CMD_READ_8_8_8;
+	case SNOR_HWCAPS_READ_1_8_8_DTR:	return SNOR_CMD_READ_1_8_8_DTR;
+
+	case SNOR_HWCAPS_PP:			return SNOR_CMD_PP;
+	case SNOR_HWCAPS_PP_1_1_4:		return SNOR_CMD_PP_1_1_4;
+	case SNOR_HWCAPS_PP_1_4_4:		return SNOR_CMD_PP_1_4_4;
+	case SNOR_HWCAPS_PP_4_4_4:		return SNOR_CMD_PP_4_4_4;
+	case SNOR_HWCAPS_PP_1_1_8:		return SNOR_CMD_PP_1_1_8;
+	case SNOR_HWCAPS_PP_1_8_8:		return SNOR_CMD_PP_1_8_8;
+	case SNOR_HWCAPS_PP_8_8_8:		return SNOR_CMD_PP_8_8_8;
+	}
+
+	return -EINVAL;
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+			       const struct spi_nor_flash_parameter *params,
+			       u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+	const struct spi_nor_read_command *read;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));

How does this work? Do we assume that hwcaps2cmd is always given a value with 1-bit set ? That's quite wasteful IMO.

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 13 lines
drivers/mtd/spi-nor/aspeed-smc.c +13 -0
+	if (cmd < 0)
+		return -EINVAL;
+
+	read = &params->reads[cmd];
+	nor->read_opcode = read->opcode;
+	nor->read_proto = read->proto;
+
+	/*
+	 * In the spi-nor framework, we don't need to make the difference
+	 * between mode clock cycles and wait state clock cycles.
+	 * Indeed, the value of the mode clock cycles is used by a QSPI
+	 * flash memory to know whether it should enter or leave its 0-4-4
+	 * (Continuous Read / XIP) mode.

0-4-4 ?

Quoted 70 lines
drivers/mtd/spi-nor/aspeed-smc.c +70 -0
+	 * eXecution In Place is out of the scope of the mtd sub-system.
+	 * Hence we choose to merge both mode and wait state clock cycles
+	 * into the so called dummy clock cycles.
+	 */
+	nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+	return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+			     const struct spi_nor_flash_parameter *params,
+			     u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+	const struct spi_nor_pp_command *pp;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	pp = &params->page_programs[cmd];
+	nor->program_opcode = pp->opcode;
+	nor->write_proto = pp->proto;
+	return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+				const struct flash_info *info)
+{
+	struct mtd_info *mtd = &nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+	return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params,
+			 const struct spi_nor_hwcaps *hwcaps)
+{
+	u32 ignored_mask, shared_mask;
+	bool enable_quad_io;
+	int err;
+
+	/*
+	 * Keep only the hardware capabilities supported by both the SPI
+	 * controller and the SPI flash memory.
+	 */
+	shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+	/* SPI protocol classes N-N-N are not supported yet. */
+	ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+			SNOR_HWCAPS_READ_4_4_4 |
+			SNOR_HWCAPS_READ_8_8_8 |
+			SNOR_HWCAPS_PP_4_4_4 |
+			SNOR_HWCAPS_PP_8_8_8);

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+	if (shared_mask & ignored_mask) {
+		dev_dbg(nor->dev,
+			"SPI protocol classes N-N-N are not supported yet.\n");
+		shared_mask &= ~ignored_mask;
+	}
+
+	/* Select the (Fast) Read command. */
+	err = spi_nor_select_read(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid (fast) read\n");

What does this information tell me, as an end user ? If I see this error message, what sort of conclusion can I derive from it ? I have no idea ...

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+		return err;
+	}
+
+	/* Select the Page Program command. */
+	err = spi_nor_select_pp(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid page program\n");
+		return err;
+	}
+

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+	/* Select the Sector Erase command. */
+	err = spi_nor_select_erase(nor, info);
+	if (err) {
+		dev_err(nor->dev, "invalid sector/block erase\n");
+		return err;
+	}
+
+	/* Enable Quad I/O if needed. */
+	enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+			  spi_nor_get_protocol_width(nor->write_proto) == 4);

What if read_proto != write_proto ? Also, this is awful code ... fix it.

Quoted 27 lines
drivers/mtd/spi-nor/aspeed-smc.c +17 -0
+	if (enable_quad_io && params->quad_enable)
+		nor->flash_quad_enable = params->quad_enable;
+	else
+		nor->flash_quad_enable = NULL;
+
+	return 0;
+}
+
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps)
+{
+	struct spi_nor_flash_parameter params;
 	const struct flash_info *info = NULL;
 	struct device *dev = nor->dev;
 	struct mtd_info *mtd = &nor->mtd;
@@ -1548,6 +1824,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (ret)
 		return ret;
 
+	/* Reset SPI protocol for all commands */
+	nor->reg_proto = SNOR_PROTO_1_1_1;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
 	if (name)
 		info = spi_nor_match_id(name);
 	/* Try to auto-detect if chip name wasn't specified or not found */

[...]

Signature · 2 lines
Best regards,
Marek Vasut
#8 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

Hi Marek,

thanks for the review.

my comments below:

Quoted 154 lines · Marek Vasut
Quoted 62 lines · Cyrille Pitchen

This patch changes the prototype of spi_nor_scan(): its 3rd parameter is replaced by a 'struct spi_nor_hwcaps' pointer, which tells the spi-nor framework about the actual hardware capabilities supported by the SPI controller and its driver.

Besides, this patch also introduces a new 'struct spi_nor_flash_parameter' telling the spi-nor framework about the hardware capabilities supported by the SPI flash memory and the associated settings required to use those hardware caps.

Currently the 'struct spi_nor_flash_parameter' is filled with legacy values but a later patch will allow to fill it dynamically by reading the JESD216 Serial Flash Discoverable Parameter (SFDP) tables from the SPI memory.

With both structures, the spi-nor framework can now compute the best match between hardware caps supported by both the (Q)SPI memory and controller hence selecting the relevant SPI protocols and op codes for (Fast) Read, Page Program and Sector Erase operations.

The 'struct spi_nor_flash_parameter' also provides the spi-nor framework with the number of dummy cycles to be used with each Fast Read commands and the erase block size associated to the erase block op codes.

Finally the 'struct spi_nor_flash_parameter', through the optional .enable_quad_io() hook, tells the spi-nor framework how to set the Quad Enable (QE) bit of the QSPI memory to enable its Quad SPI features.

---
 drivers/mtd/devices/m25p80.c          |  16 +-
 drivers/mtd/spi-nor/aspeed-smc.c      |  23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |  80 +++---
 drivers/mtd/spi-nor/cadence-quadspi.c |  18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |   8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |  31 ++-
 drivers/mtd/spi-nor/intel-spi.c       |   7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |  16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |  22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 441 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h           | 158 +++++++++++-
 11 files changed, 643 insertions(+), 177 deletions(-)
drivers/mtd/devices/m25p80.c +3 -3
index c4df3b1bded0..68986a26c8fe 100644
@@ -111,10 +111,10 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 
 static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
 {
-	switch (nor->flash_read) {
-	case SPI_NOR_DUAL:
+	switch (nor->read_proto) {
+	case SNOR_PROTO_1_1_2:
 		return 2;
-	case SPI_NOR_QUAD:
+	case SNOR_PROTO_1_1_4:
 		return 4;
 	default:
 		return 0;

drivers/mtd/devices/m25p80.c static int m25p_probe(struct spi_device *spi) · line 200

Quoted 6 lines
drivers/mtd/devices/m25p80.c +2 -1
@@ -196,7 +196,9 @@ static int m25p_probe(struct spi_device *spi)
 	struct flash_platform_data	*data;
 	struct m25p *flash;
 	struct spi_nor *nor;
-	enum read_mode mode = SPI_NOR_NORMAL;
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ | SNOR_HWCAPS_PP),

Drop the unneeded parenthesis.

Quoted 51 lines
drivers/mtd/devices/m25p80.c +4 -3
+	};
 	char *flash_name;
 	int ret;
 
@@ -222,9 +224,9 @@ static int m25p_probe(struct spi_device *spi)
 	flash->spi = spi;
 
 	if (spi->mode & SPI_RX_QUAD)
-		mode = SPI_NOR_QUAD;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
 	else if (spi->mode & SPI_RX_DUAL)
-		mode = SPI_NOR_DUAL;
+		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
 	if (data && data->name)
 		nor->mtd.name = data->name;
@@ -241,7 +243,7 @@ static int m25p_probe(struct spi_device *spi)
 	else
 		flash_name = spi->modalias;
 
-	ret = spi_nor_scan(nor, flash_name, mode);
+	ret = spi_nor_scan(nor, flash_name, &hwcaps);
 	if (ret)
 		return ret;
 
drivers/mtd/spi-nor/aspeed-smc.c +6 -8
index 56051d30f000..723026d9cf0c 100644
@@ -585,14 +585,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 	 * TODO: Adjust clocks if fast read is supported and interpret
 	 * SPI-NOR flags to adjust controller settings.
 	 */
-	switch (chip->nor.flash_read) {
-	case SPI_NOR_NORMAL:
-		cmd = CONTROL_COMMAND_MODE_NORMAL;
-		break;
-	case SPI_NOR_FAST:
-		cmd = CONTROL_COMMAND_MODE_FREAD;
-		break;
-	default:
+	if (chip->nor.read_proto == SNOR_PROTO_1_1_1) {
+		if (chip->nor.read_dummy == 0)
+			cmd = CONTROL_COMMAND_MODE_NORMAL;
+		else
+			cmd = CONTROL_COMMAND_MODE_FREAD;
+	} else {
 		dev_err(chip->nor.dev, "unsupported SPI read mode\n");
 		return -EINVAL;
 	}

drivers/mtd/spi-nor/aspeed-smc.c static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) · line 612

Quoted 7 lines
drivers/mtd/spi-nor/aspeed-smc.c +4 -0
@@ -608,6 +606,11 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
 static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 				  struct device_node *np, struct resource *r)
 {
+	struct spi_nor_hwcaps hwcaps = {
+		.mask = (SNOR_HWCAPS_READ |
+			 SNOR_HWCAPS_READ_FAST |
+			 SNOR_HWCAPS_PP),

Drop the extra parenthesis ... shouldn't the structure be const ?

Quoted 17 lines
drivers/mtd/spi-nor/aspeed-smc.c +3 -2
+	};
 	const struct aspeed_smc_info *info = controller->info;
 	struct device *dev = controller->dev;
 	struct device_node *child;
@@ -671,11 +674,11 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
 			break;
 
 		/*
-		 * TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
+		 * TODO: Add support for Dual and Quad SPI protocols
 		 * attach when board support is present as determined
 		 * by of property.
 		 */
-		ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
+		ret = spi_nor_scan(nor, NULL, &hwcaps);
 		if (ret)
 			break;

[...]

Quoted 13 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 9 lines
drivers/mtd/spi-nor/aspeed-smc.c +9 -0
+struct spi_nor_flash_parameter {
+	u64				size;
+	u32				page_size;
+
+	struct spi_nor_hwcaps		hwcaps;
+	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
+	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
+
+	int (*quad_enable)(struct spi_nor *nor);

This callback should be added by a separate patch, there's WAY too much crap in this patch.

The manufacturer/flash specific quad_enable() handler sets the Quad Enable (QE) bit in some internal status register of the SPI flash. The QE bit may not exist for some manufacturers (actually only Micron AFAIK) but when it does, we must set this bit before sending any flash command using any Quad SPI protocol.

So my point is that the use of the quad_enable() handler is tightly bound up with the possibility to use more (Quad) SPI protocols, which is the purpose of this patch.

Quoted 1 line

From the JESD216B (SFDP) specification, the Quad Enable Requirement

(QER) is part of the Basic Flash Parameter Table (BFPT). QER describes the procedure to set the QE bit. So this notion is translated into the quad_enable() handler being a member of the 'struct spi_nor_flash_parameter'.

The 'struct spi_nor_flash_parameter' is initialized in spi_nor_init_params(). This includes the (Fast) Read and Page Programs settings, which are also provided by the BFPT, as well as the choice of the right quad_enable() handler.
Then spi_nor_setup() selects the right settings and calls the quad_enable() handler, if needed: that is to say if any Quad SPI protocol was selected for Fast Read or Page Program operation.

Then if you don't mind, I'd rather keep the quad_enable() handler within this patch. IMHO, it makes more sense.

Quoted 83 lines
Quoted 7 lines
drivers/mtd/spi-nor/aspeed-smc.c +7 -0
+};
+
+
+static inline void
+spi_nor_set_read_settings(struct spi_nor_read_command *read,
+			  u8 num_mode_clocks,
+			  u8 num_wait_states,

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+			  u8 opcode,
+			  enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = num_mode_clocks;
+	read->num_wait_states = num_wait_states;
+	read->opcode = opcode;
+	read->proto = proto;
+}
+
+static inline void

Drop the inline , the compiler will decide. Fix globally.

Quoted 14 lines
drivers/mtd/spi-nor/aspeed-smc.c +14 -0
+spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+			u8 opcode,
+			enum spi_nor_protocol proto)
+{
+	pp->opcode = opcode;
+	pp->proto = proto;
+}
+
+static int spi_nor_init_params(struct spi_nor *nor,
+			       const struct flash_info *info,
+			       struct spi_nor_flash_parameter *params)
+{
+	/* Set legacy flash parameters as default. */
+	memset(params, 0, sizeof(*params));

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +10 -0
+
+	/* Set SPI NOR sizes. */
+	params->size = info->sector_size * info->n_sectors;
+	params->page_size = info->page_size;
+
+	/* (Fast) Read settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_READ;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
+				  0, 0, SPINOR_OP_READ,
+				  SNOR_PROTO_1_1_1);

Newline

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 6 lines
drivers/mtd/spi-nor/aspeed-smc.c +6 -0
+	if (!(info->flags & SPI_NOR_NO_FR)) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
+					  0, 8, SPINOR_OP_READ_FAST,
+					  SNOR_PROTO_1_1_1);
+	}

Newline

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 6 lines
drivers/mtd/spi-nor/aspeed-smc.c +6 -0
+	if (info->flags & SPI_NOR_DUAL_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
+					  0, 8, SPINOR_OP_READ_1_1_2,
+					  SNOR_PROTO_1_1_2);
+	}

Newline ... this is really hard to read as it is.

Quoted 18 lines
drivers/mtd/spi-nor/aspeed-smc.c +18 -0
+	if (info->flags & SPI_NOR_QUAD_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+					  0, 8, SPINOR_OP_READ_1_1_4,
+					  SNOR_PROTO_1_1_4);
+	}
+
+	/* Page Program settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_PP;
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+	/* Select the procedure to set the Quad Enable bit. */
+	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
+				   SNOR_HWCAPS_PP_QUAD)) {
+		switch (JEDEC_MFR(info)) {
+		case SNOR_MFR_MACRONIX:
+			params->quad_enable = macronix_quad_enable;
Quoted 8 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 6 lines
drivers/mtd/spi-nor/aspeed-smc.c +6 -0
+			break;
+
+		case SNOR_MFR_MICRON:
+			break;
+
+		default:

Are you sure this is a good idea ?

This is exactly what was done before this patch. Please have a look at the former set_quad_mode() function.

Is it a good idea to choose spansion_quad_enable() also for default case? I agree with you, it is not.

However this patch should be seen as a base for further patches fixing step by step the many pending known issues. Currently, I'm focusing on a smooth transition in changing the 3rd argument of spi_nor_scan(). Everything is expected to work just as before.

About the quad_enable() handler to be chosen for Spansion and other manufacturer SPI flash memories:
Even when regarding Spansion memories only, the procedure to set the QE bit has evolved based on whether or not the memory part supports the op code to read the Control Register 1 / Status Register 2.

If supported, a new procedure is described in the JESD216B specification. The new procedure is more efficient and reliable than the old one, ie spansion_quad_enable().

This issue is fixed by patch 5 of this series.

Quoted 13 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 10 lines
drivers/mtd/spi-nor/aspeed-smc.c +9 -0
+			params->quad_enable = spansion_quad_enable;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps)

const u32 hwcaps ...

Quoted 7 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 3 lines

{

drivers/mtd/spi-nor/aspeed-smc.c +2 -0
+	switch (hwcaps) {
+	case SNOR_HWCAPS_READ:			return SNOR_CMD_READ;

You can do this as a table lookup or array indexing and it would be checkpatch clean.

This patch has already passed the checkpatch test.

Quoted 33 lines
Quoted 33 lines
drivers/mtd/spi-nor/aspeed-smc.c +33 -0
+	case SNOR_HWCAPS_READ_FAST:		return SNOR_CMD_READ_FAST;
+	case SNOR_HWCAPS_READ_1_1_1_DTR:	return SNOR_CMD_READ_1_1_1_DTR;
+	case SNOR_HWCAPS_READ_1_1_2:		return SNOR_CMD_READ_1_1_2;
+	case SNOR_HWCAPS_READ_1_2_2:		return SNOR_CMD_READ_1_2_2;
+	case SNOR_HWCAPS_READ_2_2_2:		return SNOR_CMD_READ_2_2_2;
+	case SNOR_HWCAPS_READ_1_2_2_DTR:	return SNOR_CMD_READ_1_2_2_DTR;
+	case SNOR_HWCAPS_READ_1_1_4:		return SNOR_CMD_READ_1_1_4;
+	case SNOR_HWCAPS_READ_1_4_4:		return SNOR_CMD_READ_1_4_4;
+	case SNOR_HWCAPS_READ_4_4_4:		return SNOR_CMD_READ_4_4_4;
+	case SNOR_HWCAPS_READ_1_4_4_DTR:	return SNOR_CMD_READ_1_4_4_DTR;
+	case SNOR_HWCAPS_READ_1_1_8:		return SNOR_CMD_READ_1_1_8;
+	case SNOR_HWCAPS_READ_1_8_8:		return SNOR_CMD_READ_1_8_8;
+	case SNOR_HWCAPS_READ_8_8_8:		return SNOR_CMD_READ_8_8_8;
+	case SNOR_HWCAPS_READ_1_8_8_DTR:	return SNOR_CMD_READ_1_8_8_DTR;
+
+	case SNOR_HWCAPS_PP:			return SNOR_CMD_PP;
+	case SNOR_HWCAPS_PP_1_1_4:		return SNOR_CMD_PP_1_1_4;
+	case SNOR_HWCAPS_PP_1_4_4:		return SNOR_CMD_PP_1_4_4;
+	case SNOR_HWCAPS_PP_4_4_4:		return SNOR_CMD_PP_4_4_4;
+	case SNOR_HWCAPS_PP_1_1_8:		return SNOR_CMD_PP_1_1_8;
+	case SNOR_HWCAPS_PP_1_8_8:		return SNOR_CMD_PP_1_8_8;
+	case SNOR_HWCAPS_PP_8_8_8:		return SNOR_CMD_PP_8_8_8;
+	}
+
+	return -EINVAL;
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,
+			       const struct spi_nor_flash_parameter *params,
+			       u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+	const struct spi_nor_read_command *read;
Quoted 9 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 5 lines
drivers/mtd/spi-nor/aspeed-smc.c +5 -0
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));

How does this work? Do we assume that hwcaps2cmd is always given a value with 1-bit set ? That's quite wasteful IMO.

SNOR_HWCAPS_READ* and SNOR_HWCAPS_PP* are all defined with the BIT() macro since they are used to set the bitmask describing the hardware capabilities supported by the SPI flash memory and controller. Each of them can support many SPI commands hence the use of a bitmask.

Then we compute the best match from the hardware capabilities shared by both the memory and controller. It means we always select a single bit from the shared_hwcaps bitmask. So to answer your question, indeed, hwcaps2cmd() is always given a value with a single bit set.

Quoted 7 lines
Quoted 7 lines
drivers/mtd/spi-nor/aspeed-smc.c +7 -0
+	if (cmd < 0)
+		return -EINVAL;
+
+	read = &params->reads[cmd];
+	nor->read_opcode = read->opcode;
+	nor->read_proto = read->proto;
+
Quoted 8 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 6 lines
drivers/mtd/spi-nor/aspeed-smc.c +6 -0
+	/*
+	 * In the spi-nor framework, we don't need to make the difference
+	 * between mode clock cycles and wait state clock cycles.
+	 * Indeed, the value of the mode clock cycles is used by a QSPI
+	 * flash memory to know whether it should enter or leave its 0-4-4
+	 * (Continuous Read / XIP) mode.

0-4-4 ?

Some manufacturer datasheets name this mode the "Continuous Read" mode or the "eXecution In Place" mode but the JESD216B specification calls it the 0-4-4 mode, just to have a consistent naming with the 4-4-4 mode.

Once in 0-4-4 mode, the SPI flash memory expects later Fast Read commands to start directly from the address clocks cycles, skipping the instruction clock cycles, hence the leading 0.

The value of the mode cycles, between the address and wait state cycles, in the Fast Read x-4-4 command tells the SPI flash memory whether is the next SPI flash command will be a Fast Read 0-4-4 command or any other command with instruction clock cycles.

It is common to split SPI flash commands into 3 parts:
instruction | (address;mode;wait states) | data

So 0-4-4 means:
- no instruction clock cycles
- 4 I/O lines being used during address;mode;wait states clock cycles
- 4 I/O lines being used during data clock cycles

Quoted 77 lines
Quoted 77 lines
drivers/mtd/spi-nor/aspeed-smc.c +76 -0
+	 * eXecution In Place is out of the scope of the mtd sub-system.
+	 * Hence we choose to merge both mode and wait state clock cycles
+	 * into the so called dummy clock cycles.
+	 */
+	nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+	return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+			     const struct spi_nor_flash_parameter *params,
+			     u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+	const struct spi_nor_pp_command *pp;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	pp = &params->page_programs[cmd];
+	nor->program_opcode = pp->opcode;
+	nor->write_proto = pp->proto;
+	return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+				const struct flash_info *info)
+{
+	struct mtd_info *mtd = &nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+	return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params,
+			 const struct spi_nor_hwcaps *hwcaps)
+{
+	u32 ignored_mask, shared_mask;
+	bool enable_quad_io;
+	int err;
+
+	/*
+	 * Keep only the hardware capabilities supported by both the SPI
+	 * controller and the SPI flash memory.
+	 */
+	shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+	/* SPI protocol classes N-N-N are not supported yet. */
+	ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+			SNOR_HWCAPS_READ_4_4_4 |
+			SNOR_HWCAPS_READ_8_8_8 |
+			SNOR_HWCAPS_PP_4_4_4 |
+			SNOR_HWCAPS_PP_8_8_8);
+	if (shared_mask & ignored_mask) {
+		dev_dbg(nor->dev,
+			"SPI protocol classes N-N-N are not supported yet.\n");
+		shared_mask &= ~ignored_mask;
+	}
+
Quoted 9 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 4 lines
drivers/mtd/spi-nor/aspeed-smc.c +4 -0
+	/* Select the (Fast) Read command. */
+	err = spi_nor_select_read(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid (fast) read\n");

What does this information tell me, as an end user ? If I see this error message, what sort of conclusion can I derive from it ? I have no idea ...

I agree, this could be improved.

Quoted 14 lines
Quoted 14 lines
drivers/mtd/spi-nor/aspeed-smc.c +14 -0
+		return err;
+	}
+
+	/* Select the Page Program command. */
+	err = spi_nor_select_pp(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid page program\n");
+		return err;
+	}
+
+	/* Select the Sector Erase command. */
+	err = spi_nor_select_erase(nor, info);
+	if (err) {
+		dev_err(nor->dev, "invalid sector/block erase\n");
Quoted 9 lines

drivers/mtd/spi-nor/aspeed-smc.c

Quoted 6 lines
drivers/mtd/spi-nor/aspeed-smc.c +6 -0
+		return err;
+	}
+
+	/* Enable Quad I/O if needed. */
+	enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+			  spi_nor_get_protocol_width(nor->write_proto) == 4);

What if read_proto != write_proto ? Also, this is awful code ... fix it.

As explained earlier about the QE bit and the Quad Enable Requirement, this is indeed exactly what we want: we must set the QE bit, hence call nor->flash_quad_enable() whenever any Quad SPI protocol is used for any SPI flash command.

Currently Fast Read and Page Program are the only commands selected by the spi-nor protocols, which can use one Quad SPI protocol.

So this code works whether of not (read_proto == write_proto).

Quoted 19 lines
Quoted 19 lines
drivers/mtd/spi-nor/aspeed-smc.c +12 -0
+	if (enable_quad_io && params->quad_enable)
+		nor->flash_quad_enable = params->quad_enable;
+	else
+		nor->flash_quad_enable = NULL;
+
+	return 0;
+}
+
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps)
+{
+	struct spi_nor_flash_parameter params;
 	const struct flash_info *info = NULL;
 	struct device *dev = nor->dev;
 	struct mtd_info *mtd = &nor->mtd;
@@ -1548,6 +1824,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (ret)
 		return ret;
 
Quoted 10 lines
Quoted 8 lines
drivers/mtd/spi-nor/aspeed-smc.c +5 -0
+	/* Reset SPI protocol for all commands */
+	nor->reg_proto = SNOR_PROTO_1_1_1;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
 	if (name)
 		info = spi_nor_match_id(name);
 	/* Try to auto-detect if chip name wasn't specified or not found */

[...]

Best regards,

Cyrille

#9 lore raw

Re: [PATCH v5 1/6] mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode

Quoted 1 line · Cyrille Pitchen

Hi Marek,

Hi,

Quoted 1 line

thanks for the review.

[...]

Quoted 14 lines
Quoted 4 lines
Quoted 4 lines
drivers/mtd/spi-nor/spi-nor.c +4 -0
+struct spi_nor_flash_parameter {
+	u64				size;
+	u32				page_size;
+
Quoted 9 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1563

Quoted 5 lines
drivers/mtd/spi-nor/spi-nor.c +5 -0
+	struct spi_nor_hwcaps		hwcaps;
+	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
+	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
+
+	int (*quad_enable)(struct spi_nor *nor);

This callback should be added by a separate patch, there's WAY too much crap in this patch.

Quoted 5 lines

The manufacturer/flash specific quad_enable() handler sets the Quad Enable (QE) bit in some internal status register of the SPI flash. The QE bit may not exist for some manufacturers (actually only Micron AFAIK) but when it does, we must set this bit before sending any flash command using any Quad SPI protocol.

How does that matter for Dual Transfer Mode ? It doesn't , so it can be split away from this patch .

Quoted 3 lines

So my point is that the use of the quad_enable() handler is tightly bound up with the possibility to use more (Quad) SPI protocols, which is the purpose of this patch.

Which this patch does NOT enable ...

Quoted 16 lines

From the JESD216B (SFDP) specification, the Quad Enable Requirement (QER) is part of the Basic Flash Parameter Table (BFPT). QER describes the procedure to set the QE bit. So this notion is translated into the quad_enable() handler being a member of the 'struct spi_nor_flash_parameter'.

The 'struct spi_nor_flash_parameter' is initialized in spi_nor_init_params(). This includes the (Fast) Read and Page Programs settings, which are also provided by the BFPT, as well as the choice of the right quad_enable() handler.
Then spi_nor_setup() selects the right settings and calls the quad_enable() handler, if needed: that is to say if any Quad SPI protocol was selected for Fast Read or Page Program operation.

Then if you don't mind, I'd rather keep the quad_enable() handler within this patch. IMHO, it makes more sense.

[...]

Quoted 27 lines
Quoted 18 lines
Quoted 18 lines
drivers/mtd/spi-nor/spi-nor.c +18 -0
+	if (info->flags & SPI_NOR_QUAD_READ) {
+		params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+		spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+					  0, 8, SPINOR_OP_READ_1_1_4,
+					  SNOR_PROTO_1_1_4);
+	}
+
+	/* Page Program settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_PP;
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+	/* Select the procedure to set the Quad Enable bit. */
+	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
+				   SNOR_HWCAPS_PP_QUAD)) {
+		switch (JEDEC_MFR(info)) {
+		case SNOR_MFR_MACRONIX:
+			params->quad_enable = macronix_quad_enable;
Quoted 8 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1640

Quoted 6 lines
drivers/mtd/spi-nor/spi-nor.c +6 -0
+			break;
+
+		case SNOR_MFR_MICRON:
+			break;
+
+		default:

Are you sure this is a good idea ?

Quoted 5 lines

This is exactly what was done before this patch. Please have a look at the former set_quad_mode() function.

Is it a good idea to choose spansion_quad_enable() also for default case? I agree with you, it is not.

Then why don't we fix that first ?

Quoted 4 lines

However this patch should be seen as a base for further patches fixing step by step the many pending known issues. Currently, I'm focusing on a smooth transition in changing the 3rd argument of spi_nor_scan(). Everything is expected to work just as before.

OK

Quoted 34 lines

About the quad_enable() handler to be chosen for Spansion and other manufacturer SPI flash memories:
Even when regarding Spansion memories only, the procedure to set the QE bit has evolved based on whether or not the memory part supports the op code to read the Control Register 1 / Status Register 2.

If supported, a new procedure is described in the JESD216B specification. The new procedure is more efficient and reliable than the old one, ie spansion_quad_enable().

This issue is fixed by patch 5 of this series.

Quoted 13 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1649

Quoted 10 lines
drivers/mtd/spi-nor/spi-nor.c +9 -0
+			params->quad_enable = spansion_quad_enable;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps)

const u32 hwcaps ...

Quoted 7 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1652

Quoted 3 lines

{

drivers/mtd/spi-nor/spi-nor.c +2 -0
+	switch (hwcaps) {
+	case SNOR_HWCAPS_READ:			return SNOR_CMD_READ;

You can do this as a table lookup or array indexing and it would be checkpatch clean.

Quoted 1 line

This patch has already passed the checkpatch test.

This is weird, I wouldn't have expected this to be acceptable syntax.

Quoted 48 lines
Quoted 33 lines
Quoted 33 lines
drivers/mtd/spi-nor/spi-nor.c +33 -0
+	case SNOR_HWCAPS_READ_FAST:		return SNOR_CMD_READ_FAST;
+	case SNOR_HWCAPS_READ_1_1_1_DTR:	return SNOR_CMD_READ_1_1_1_DTR;
+	case SNOR_HWCAPS_READ_1_1_2:		return SNOR_CMD_READ_1_1_2;
+	case SNOR_HWCAPS_READ_1_2_2:		return SNOR_CMD_READ_1_2_2;
+	case SNOR_HWCAPS_READ_2_2_2:		return SNOR_CMD_READ_2_2_2;
+	case SNOR_HWCAPS_READ_1_2_2_DTR:	return SNOR_CMD_READ_1_2_2_DTR;
+	case SNOR_HWCAPS_READ_1_1_4:		return SNOR_CMD_READ_1_1_4;
+	case SNOR_HWCAPS_READ_1_4_4:		return SNOR_CMD_READ_1_4_4;
+	case SNOR_HWCAPS_READ_4_4_4:		return SNOR_CMD_READ_4_4_4;
+	case SNOR_HWCAPS_READ_1_4_4_DTR:	return SNOR_CMD_READ_1_4_4_DTR;
+	case SNOR_HWCAPS_READ_1_1_8:		return SNOR_CMD_READ_1_1_8;
+	case SNOR_HWCAPS_READ_1_8_8:		return SNOR_CMD_READ_1_8_8;
+	case SNOR_HWCAPS_READ_8_8_8:		return SNOR_CMD_READ_8_8_8;
+	case SNOR_HWCAPS_READ_1_8_8_DTR:	return SNOR_CMD_READ_1_8_8_DTR;
+
+	case SNOR_HWCAPS_PP:			return SNOR_CMD_PP;
+	case SNOR_HWCAPS_PP_1_1_4:		return SNOR_CMD_PP_1_1_4;
+	case SNOR_HWCAPS_PP_1_4_4:		return SNOR_CMD_PP_1_4_4;
+	case SNOR_HWCAPS_PP_4_4_4:		return SNOR_CMD_PP_4_4_4;
+	case SNOR_HWCAPS_PP_1_1_8:		return SNOR_CMD_PP_1_1_8;
+	case SNOR_HWCAPS_PP_1_8_8:		return SNOR_CMD_PP_1_8_8;
+	case SNOR_HWCAPS_PP_8_8_8:		return SNOR_CMD_PP_8_8_8;
+	}
+
+	return -EINVAL;
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,
+			       const struct spi_nor_flash_parameter *params,
+			       u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+	const struct spi_nor_read_command *read;
Quoted 9 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1690

Quoted 5 lines
drivers/mtd/spi-nor/spi-nor.c +5 -0
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));

How does this work? Do we assume that hwcaps2cmd is always given a value with 1-bit set ? That's quite wasteful IMO.

SNOR_HWCAPS_READ* and SNOR_HWCAPS_PP* are all defined with the BIT() macro since they are used to set the bitmask describing the hardware capabilities supported by the SPI flash memory and controller. Each of them can support many SPI commands hence the use of a bitmask.

Quoted 4 lines

Then we compute the best match from the hardware capabilities shared by both the memory and controller. It means we always select a single bit from the shared_hwcaps bitmask. So to answer your question, indeed, hwcaps2cmd() is always given a value with a single bit set.

Something tells me you might run out of bits very soon here ...

Quoted 2 lines
Quoted 2 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+	if (cmd < 0)
+		return -EINVAL;

return cmd ; ... propagate the errors .

Quoted 13 lines
Quoted 4 lines
Quoted 4 lines
drivers/mtd/spi-nor/spi-nor.c +4 -0
+	read = &params->reads[cmd];
+	nor->read_opcode = read->opcode;
+	nor->read_proto = read->proto;
+
Quoted 8 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1703

Quoted 6 lines
drivers/mtd/spi-nor/spi-nor.c +6 -0
+	/*
+	 * In the spi-nor framework, we don't need to make the difference
+	 * between mode clock cycles and wait state clock cycles.
+	 * Indeed, the value of the mode clock cycles is used by a QSPI
+	 * flash memory to know whether it should enter or leave its 0-4-4
+	 * (Continuous Read / XIP) mode.

0-4-4 ?

Quoted 3 lines

Some manufacturer datasheets name this mode the "Continuous Read" mode or the "eXecution In Place" mode but the JESD216B specification calls it the 0-4-4 mode, just to have a consistent naming with the 4-4-4 mode.

OK

Quoted 105 lines

Once in 0-4-4 mode, the SPI flash memory expects later Fast Read commands to start directly from the address clocks cycles, skipping the instruction clock cycles, hence the leading 0.

The value of the mode cycles, between the address and wait state cycles, in the Fast Read x-4-4 command tells the SPI flash memory whether is the next SPI flash command will be a Fast Read 0-4-4 command or any other command with instruction clock cycles.

It is common to split SPI flash commands into 3 parts:
instruction | (address;mode;wait states) | data

So 0-4-4 means:
- no instruction clock cycles
- 4 I/O lines being used during address;mode;wait states clock cycles
- 4 I/O lines being used during data clock cycles

Quoted 77 lines
Quoted 77 lines
drivers/mtd/spi-nor/spi-nor.c +76 -0
+	 * eXecution In Place is out of the scope of the mtd sub-system.
+	 * Hence we choose to merge both mode and wait state clock cycles
+	 * into the so called dummy clock cycles.
+	 */
+	nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+	return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+			     const struct spi_nor_flash_parameter *params,
+			     u32 shared_hwcaps)
+{
+	int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+	const struct spi_nor_pp_command *pp;
+
+	if (best_match < 0)
+		return -EINVAL;
+
+	cmd = spi_nor_hwcaps2cmd(BIT(best_match));
+	if (cmd < 0)
+		return -EINVAL;
+
+	pp = &params->page_programs[cmd];
+	nor->program_opcode = pp->opcode;
+	nor->write_proto = pp->proto;
+	return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+				const struct flash_info *info)
+{
+	struct mtd_info *mtd = &nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+	return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params,
+			 const struct spi_nor_hwcaps *hwcaps)
+{
+	u32 ignored_mask, shared_mask;
+	bool enable_quad_io;
+	int err;
+
+	/*
+	 * Keep only the hardware capabilities supported by both the SPI
+	 * controller and the SPI flash memory.
+	 */
+	shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+	/* SPI protocol classes N-N-N are not supported yet. */
+	ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+			SNOR_HWCAPS_READ_4_4_4 |
+			SNOR_HWCAPS_READ_8_8_8 |
+			SNOR_HWCAPS_PP_4_4_4 |
+			SNOR_HWCAPS_PP_8_8_8);
+	if (shared_mask & ignored_mask) {
+		dev_dbg(nor->dev,
+			"SPI protocol classes N-N-N are not supported yet.\n");
+		shared_mask &= ~ignored_mask;
+	}
+
Quoted 9 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1783

Quoted 4 lines
drivers/mtd/spi-nor/spi-nor.c +4 -0
+	/* Select the (Fast) Read command. */
+	err = spi_nor_select_read(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid (fast) read\n");

What does this information tell me, as an end user ? If I see this error message, what sort of conclusion can I derive from it ? I have no idea ...

Quoted 1 line

I agree, this could be improved.

Please do.

Quoted 29 lines
Quoted 14 lines
Quoted 14 lines
drivers/mtd/spi-nor/spi-nor.c +14 -0
+		return err;
+	}
+
+	/* Select the Page Program command. */
+	err = spi_nor_select_pp(nor, params, shared_mask);
+	if (err) {
+		dev_err(nor->dev, "invalid page program\n");
+		return err;
+	}
+
+	/* Select the Sector Erase command. */
+	err = spi_nor_select_erase(nor, info);
+	if (err) {
+		dev_err(nor->dev, "invalid sector/block erase\n");
Quoted 9 lines

drivers/mtd/spi-nor/spi-nor.c static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor) · line 1803

Quoted 6 lines
drivers/mtd/spi-nor/spi-nor.c +6 -0
+		return err;
+	}
+
+	/* Enable Quad I/O if needed. */
+	enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+			  spi_nor_get_protocol_width(nor->write_proto) == 4);

What if read_proto != write_proto ? Also, this is awful code ... fix it.

As explained earlier about the QE bit and the Quad Enable Requirement, this is indeed exactly what we want: we must set the QE bit, hence call nor->flash_quad_enable() whenever any Quad SPI protocol is used for any SPI flash command.

Quoted 4 lines

Currently Fast Read and Page Program are the only commands selected by the spi-nor protocols, which can use one Quad SPI protocol.

So this code works whether of not (read_proto == write_proto).

OK, then this should also be a separate patch .

Quoted 34 lines
Quoted 19 lines
Quoted 19 lines
drivers/mtd/spi-nor/spi-nor.c +12 -0
+	if (enable_quad_io && params->quad_enable)
+		nor->flash_quad_enable = params->quad_enable;
+	else
+		nor->flash_quad_enable = NULL;
+
+	return 0;
+}
+
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+		 const struct spi_nor_hwcaps *hwcaps)
+{
+	struct spi_nor_flash_parameter params;
 	const struct flash_info *info = NULL;
 	struct device *dev = nor->dev;
 	struct mtd_info *mtd = &nor->mtd;
@@ -1548,6 +1824,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (ret)
 		return ret;
 
Quoted 10 lines
Quoted 8 lines
drivers/mtd/spi-nor/spi-nor.c +5 -0
+	/* Reset SPI protocol for all commands */
+	nor->reg_proto = SNOR_PROTO_1_1_1;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
 	if (name)
 		info = spi_nor_match_id(name);
 	/* Try to auto-detect if chip name wasn't specified or not found */

[...]

Best regards,

Cyrille

Signature · 2 lines
Best regards,
Marek Vasut
#10 lore raw

PATCH v5 2/6 mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols

In reply to Cyrille Pitchen

Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, m25p80_write() only supported SPI 1-1-1.

This patch updates both m25p80_read() and m25p80_write() functions to let them support SPI 1-2-2 and SPI 1-4-4 protocols for Fast Read and Page Program SPI commands.

It adopts a conservative approach to avoid regressions. Hence the new implementations try to be as close as possible to the old implementations, so the main differences are:
- the tx_nbits values now being set properly for the spi_transfer structures carrying the (op code + address/dummy) bytes
- and the spi_transfer structure being split into 2 spi_transfer structures when the numbers of I/O lines are different for op code and for address/dummy byte transfers on the SPI bus.

Besides, the current spi-nor framework supports neither the SPI 2-2-2 nor the SPI 4-4-4 protocols. So, for now, we don't need to update the m25p80_{read|write}_reg() functions as SPI 1-1-1 is the only one possible protocol.

---
 drivers/mtd/devices/m25p80.c | 120 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 90 insertions(+), 30 deletions(-)
drivers/mtd/devices/m25p80.c +90 -30
index 68986a26c8fe..64d562efc25d 100644
@@ -34,6 +34,19 @@ struct m25p {
 	u8			command[MAX_CMD_SIZE];
 };
 
+static inline void m25p80_proto2nbits(enum spi_nor_protocol proto,
+				      unsigned int *inst_nbits,
+				      unsigned int *addr_nbits,
+				      unsigned int *data_nbits)
+{
+	if (inst_nbits)
+		*inst_nbits = spi_nor_get_protocol_inst_width(proto);
+	if (addr_nbits)
+		*addr_nbits = spi_nor_get_protocol_addr_width(proto);
+	if (data_nbits)
+		*data_nbits = spi_nor_get_protocol_data_width(proto);
+}
+
 static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
 {
 	struct m25p *flash = nor->priv;
@@ -78,11 +91,16 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 {
 	struct m25p *flash = nor->priv;
 	struct spi_device *spi = flash->spi;
-	struct spi_transfer t[2] = {};
+	unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
+	struct spi_transfer t[3] = {};
 	struct spi_message m;
 	int cmd_sz = m25p_cmdsz(nor);
 	ssize_t ret;
 
+	/* get transfer protocols. */
+	m25p80_proto2nbits(nor->write_proto, &inst_nbits,
+			   &addr_nbits, &data_nbits);
+
 	spi_message_init(&m);
 
 	if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
@@ -92,12 +110,27 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 	m25p_addr2cmd(nor, to, flash->command);
 
 	t[0].tx_buf = flash->command;
+	t[0].tx_nbits = inst_nbits;
 	t[0].len = cmd_sz;
 	spi_message_add_tail(&t[0], &m);
 
-	t[1].tx_buf = buf;
-	t[1].len = len;
-	spi_message_add_tail(&t[1], &m);
+	/* split the op code and address bytes into two transfers if needed. */
+	data_idx = 1;
+	if (addr_nbits != inst_nbits) {
+		t[0].len = 1;
+
+		t[1].tx_buf = &flash->command[1];
+		t[1].tx_nbits = addr_nbits;
+		t[1].len = cmd_sz - 1;
+		spi_message_add_tail(&t[1], &m);
+
+		data_idx = 2;
+	}
+
+	t[data_idx].tx_buf = buf;
+	t[data_idx].tx_nbits = data_nbits;
+	t[data_idx].len = len;
+	spi_message_add_tail(&t[data_idx], &m);
 
 	ret = spi_sync(spi, &m);
 	if (ret)
@@ -109,18 +142,6 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 	return ret;
 }
 
-static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
-{
-	switch (nor->read_proto) {
-	case SNOR_PROTO_1_1_2:
-		return 2;
-	case SNOR_PROTO_1_1_4:
-		return 4;
-	default:
-		return 0;
-	}
-}
-
 /*
  * Read an address range from the nor chip.  The address range
  * may be any size provided it is within the physical boundaries.
@@ -130,13 +151,19 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 {
 	struct m25p *flash = nor->priv;
 	struct spi_device *spi = flash->spi;
-	struct spi_transfer t[2];
+	unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
+	struct spi_transfer t[3];
 	struct spi_message m;
 	unsigned int dummy = nor->read_dummy;
 	ssize_t ret;
+	int cmd_sz;
+
+	/* get transfer protocols. */
+	m25p80_proto2nbits(nor->read_proto, &inst_nbits,
+			   &addr_nbits, &data_nbits);
 
 	/* convert the dummy cycles to the number of bytes */
-	dummy /= 8;
+	dummy = (dummy * addr_nbits) / 8;
 
 	if (spi_flash_read_supported(spi)) {
 		struct spi_flash_read_message msg;
@@ -149,10 +176,9 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 		msg.read_opcode = nor->read_opcode;
 		msg.addr_width = nor->addr_width;
 		msg.dummy_bytes = dummy;
-		/* TODO: Support other combinations */
-		msg.opcode_nbits = SPI_NBITS_SINGLE;
-		msg.addr_nbits = SPI_NBITS_SINGLE;
-		msg.data_nbits = m25p80_rx_nbits(nor);
+		msg.opcode_nbits = inst_nbits;
+		msg.addr_nbits = addr_nbits;
+		msg.data_nbits = data_nbits;
 
 		ret = spi_flash_read(spi, &msg);
 		if (ret < 0)
@@ -167,20 +193,45 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 	m25p_addr2cmd(nor, from, flash->command);
 
 	t[0].tx_buf = flash->command;
+	t[0].tx_nbits = inst_nbits;
 	t[0].len = m25p_cmdsz(nor) + dummy;
 	spi_message_add_tail(&t[0], &m);
 
-	t[1].rx_buf = buf;
-	t[1].rx_nbits = m25p80_rx_nbits(nor);
-	t[1].len = min3(len, spi_max_transfer_size(spi),
-			spi_max_message_size(spi) - t[0].len);
-	spi_message_add_tail(&t[1], &m);
+	/*
+	 * Set all dummy/mode cycle bits to avoid sending some manufacturer
+	 * specific pattern, which might make the memory enter its Continuous
+	 * Read mode by mistake.
+	 * Based on the different mode cycle bit patterns listed and described
+	 * in the JESD216B speficication, the 0xff value works for all memories
+	 * and all manufacturers.
+	 */
+	cmd_sz = t[0].len;
+	memset(flash->command + cmd_sz - dummy, 0xff, dummy);
+
+	/* split the op code and address bytes into two transfers if needed. */
+	data_idx = 1;
+	if (addr_nbits != inst_nbits) {
+		t[0].len = 1;
+
+		t[1].tx_buf = &flash->command[1];
+		t[1].tx_nbits = addr_nbits;
+		t[1].len = cmd_sz - 1;
+		spi_message_add_tail(&t[1], &m);
+
+		data_idx = 2;
+	}
+
+	t[data_idx].rx_buf = buf;
+	t[data_idx].rx_nbits = data_nbits;
+	t[data_idx].len = min3(len, spi_max_transfer_size(spi),
+			       spi_max_message_size(spi) - cmd_sz);
+	spi_message_add_tail(&t[data_idx], &m);
 
 	ret = spi_sync(spi, &m);
 	if (ret)
 		return ret;
 
-	ret = m.actual_length - m25p_cmdsz(nor) - dummy;
+	ret = m.actual_length - cmd_sz;
 	if (ret < 0)
 		return -EIO;
 	return ret;
@@ -223,11 +274,20 @@ static int m25p_probe(struct spi_device *spi)
 	spi_set_drvdata(spi, flash);
 	flash->spi = spi;
 
-	if (spi->mode & SPI_RX_QUAD)
+	if (spi->mode & SPI_RX_QUAD) {
 		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
-	else if (spi->mode & SPI_RX_DUAL)
+
+		if (spi->mode & SPI_TX_QUAD)
+			hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
+					SNOR_HWCAPS_PP_1_1_4 |
+					SNOR_HWCAPS_PP_1_4_4);
+	} else if (spi->mode & SPI_RX_DUAL) {
 		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
+		if (spi->mode & SPI_TX_DUAL)
+			hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
+	}
+
 	if (data && data->name)
 		nor->mtd.name = data->name;
 
Signature · 1 line
2.9.3
#11 lore raw

Re: [PATCH v5 2/6] mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols

Quoted 19 lines · Cyrille Pitchen

Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, m25p80_write() only supported SPI 1-1-1.

This patch updates both m25p80_read() and m25p80_write() functions to let them support SPI 1-2-2 and SPI 1-4-4 protocols for Fast Read and Page Program SPI commands.

It adopts a conservative approach to avoid regressions. Hence the new implementations try to be as close as possible to the old implementations, so the main differences are:
- the tx_nbits values now being set properly for the spi_transfer structures carrying the (op code + address/dummy) bytes
- and the spi_transfer structure being split into 2 spi_transfer structures when the numbers of I/O lines are different for op code and for address/dummy byte transfers on the SPI bus.

Quoted 6 lines

Besides, the current spi-nor framework supports neither the SPI 2-2-2 nor the SPI 4-4-4 protocols. So, for now, we don't need to update the m25p80_{read|write}_reg() functions as SPI 1-1-1 is the only one possible protocol.

Applied to github/spi-nor

#12 lore raw

Re: [PATCH v5 2/6] mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols

In reply to Cyrille Pitchen
Quoted 11 lines · Cyrille Pitchen

Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, m25p80_write() only supported SPI 1-1-1.

This patch updates both m25p80_read() and m25p80_write() functions to let them support SPI 1-2-2 and SPI 1-4-4 protocols for Fast Read and Page Program SPI commands.

It adopts a conservative approach to avoid regressions. Hence the new

                                             ^ FYI, regression != bug
Quoted 23 lines

implementations try to be as close as possible to the old implementations, so the main differences are:
- the tx_nbits values now being set properly for the spi_transfer structures carrying the (op code + address/dummy) bytes
- and the spi_transfer structure being split into 2 spi_transfer structures when the numbers of I/O lines are different for op code and for address/dummy byte transfers on the SPI bus.

Besides, the current spi-nor framework supports neither the SPI 2-2-2 nor the SPI 4-4-4 protocols. So, for now, we don't need to update the m25p80_{read|write}_reg() functions as SPI 1-1-1 is the only one possible protocol.

---
 drivers/mtd/devices/m25p80.c | 120 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 90 insertions(+), 30 deletions(-)
drivers/mtd/devices/m25p80.c +0 -0
index 68986a26c8fe..64d562efc25d 100644

drivers/mtd/devices/m25p80.c struct m25p { · line 41

Quoted 8 lines
drivers/mtd/devices/m25p80.c +5 -0
@@ -34,6 +34,19 @@ struct m25p {
 	u8			command[MAX_CMD_SIZE];
 };
 
+static inline void m25p80_proto2nbits(enum spi_nor_protocol proto,
+				      unsigned int *inst_nbits,
+				      unsigned int *addr_nbits,
+				      unsigned int *data_nbits)
+{

Why don't we just have some generic macros to extract the number of bits from $proto ?

Quoted 124 lines
drivers/mtd/devices/m25p80.c +44 -23
+	if (inst_nbits)
+		*inst_nbits = spi_nor_get_protocol_inst_width(proto);
+	if (addr_nbits)
+		*addr_nbits = spi_nor_get_protocol_addr_width(proto);
+	if (data_nbits)
+		*data_nbits = spi_nor_get_protocol_data_width(proto);
+}
+
 static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
 {
 	struct m25p *flash = nor->priv;
@@ -78,11 +91,16 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 {
 	struct m25p *flash = nor->priv;
 	struct spi_device *spi = flash->spi;
-	struct spi_transfer t[2] = {};
+	unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
+	struct spi_transfer t[3] = {};
 	struct spi_message m;
 	int cmd_sz = m25p_cmdsz(nor);
 	ssize_t ret;
 
+	/* get transfer protocols. */
+	m25p80_proto2nbits(nor->write_proto, &inst_nbits,
+			   &addr_nbits, &data_nbits);
+
 	spi_message_init(&m);
 
 	if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
@@ -92,12 +110,27 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 	m25p_addr2cmd(nor, to, flash->command);
 
 	t[0].tx_buf = flash->command;
+	t[0].tx_nbits = inst_nbits;
 	t[0].len = cmd_sz;
 	spi_message_add_tail(&t[0], &m);
 
-	t[1].tx_buf = buf;
-	t[1].len = len;
-	spi_message_add_tail(&t[1], &m);
+	/* split the op code and address bytes into two transfers if needed. */
+	data_idx = 1;
+	if (addr_nbits != inst_nbits) {
+		t[0].len = 1;
+
+		t[1].tx_buf = &flash->command[1];
+		t[1].tx_nbits = addr_nbits;
+		t[1].len = cmd_sz - 1;
+		spi_message_add_tail(&t[1], &m);
+
+		data_idx = 2;
+	}
+
+	t[data_idx].tx_buf = buf;
+	t[data_idx].tx_nbits = data_nbits;
+	t[data_idx].len = len;
+	spi_message_add_tail(&t[data_idx], &m);
 
 	ret = spi_sync(spi, &m);
 	if (ret)
@@ -109,18 +142,6 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 	return ret;
 }
 
-static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
-{
-	switch (nor->read_proto) {
-	case SNOR_PROTO_1_1_2:
-		return 2;
-	case SNOR_PROTO_1_1_4:
-		return 4;
-	default:
-		return 0;
-	}
-}
-
 /*
  * Read an address range from the nor chip.  The address range
  * may be any size provided it is within the physical boundaries.
@@ -130,13 +151,19 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 {
 	struct m25p *flash = nor->priv;
 	struct spi_device *spi = flash->spi;
-	struct spi_transfer t[2];
+	unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
+	struct spi_transfer t[3];
 	struct spi_message m;
 	unsigned int dummy = nor->read_dummy;
 	ssize_t ret;
+	int cmd_sz;
+
+	/* get transfer protocols. */
+	m25p80_proto2nbits(nor->read_proto, &inst_nbits,
+			   &addr_nbits, &data_nbits);
 
 	/* convert the dummy cycles to the number of bytes */
-	dummy /= 8;
+	dummy = (dummy * addr_nbits) / 8;
 
 	if (spi_flash_read_supported(spi)) {
 		struct spi_flash_read_message msg;
@@ -149,10 +176,9 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 		msg.read_opcode = nor->read_opcode;
 		msg.addr_width = nor->addr_width;
 		msg.dummy_bytes = dummy;
-		/* TODO: Support other combinations */
-		msg.opcode_nbits = SPI_NBITS_SINGLE;
-		msg.addr_nbits = SPI_NBITS_SINGLE;
-		msg.data_nbits = m25p80_rx_nbits(nor);
+		msg.opcode_nbits = inst_nbits;
+		msg.addr_nbits = addr_nbits;
+		msg.data_nbits = data_nbits;
 
 		ret = spi_flash_read(spi, &msg);
 		if (ret < 0)
@@ -167,20 +193,45 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 	m25p_addr2cmd(nor, from, flash->command);
 
 	t[0].tx_buf = flash->command;
+	t[0].tx_nbits = inst_nbits;
 	t[0].len = m25p_cmdsz(nor) + dummy;
 	spi_message_add_tail(&t[0], &m);
 
-	t[1].rx_buf = buf;

drivers/mtd/devices/m25p80.c static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len, · line 205

Quoted 10 lines
drivers/mtd/devices/m25p80.c +6 -4
-	t[1].rx_nbits = m25p80_rx_nbits(nor);
-	t[1].len = min3(len, spi_max_transfer_size(spi),
-			spi_max_message_size(spi) - t[0].len);
-	spi_message_add_tail(&t[1], &m);
+	/*
+	 * Set all dummy/mode cycle bits to avoid sending some manufacturer
+	 * specific pattern, which might make the memory enter its Continuous
+	 * Read mode by mistake.
+	 * Based on the different mode cycle bit patterns listed and described
+	 * in the JESD216B speficication, the 0xff value works for all memories
                           ^
                           specification, typo
Quoted 57 lines
drivers/mtd/devices/m25p80.c +35 -3
+	 * and all manufacturers.
+	 */
+	cmd_sz = t[0].len;
+	memset(flash->command + cmd_sz - dummy, 0xff, dummy);
+
+	/* split the op code and address bytes into two transfers if needed. */
+	data_idx = 1;
+	if (addr_nbits != inst_nbits) {
+		t[0].len = 1;
+
+		t[1].tx_buf = &flash->command[1];
+		t[1].tx_nbits = addr_nbits;
+		t[1].len = cmd_sz - 1;
+		spi_message_add_tail(&t[1], &m);
+
+		data_idx = 2;
+	}
+
+	t[data_idx].rx_buf = buf;
+	t[data_idx].rx_nbits = data_nbits;
+	t[data_idx].len = min3(len, spi_max_transfer_size(spi),
+			       spi_max_message_size(spi) - cmd_sz);
+	spi_message_add_tail(&t[data_idx], &m);
 
 	ret = spi_sync(spi, &m);
 	if (ret)
 		return ret;
 
-	ret = m.actual_length - m25p_cmdsz(nor) - dummy;
+	ret = m.actual_length - cmd_sz;
 	if (ret < 0)
 		return -EIO;
 	return ret;
@@ -223,11 +274,20 @@ static int m25p_probe(struct spi_device *spi)
 	spi_set_drvdata(spi, flash);
 	flash->spi = spi;
 
-	if (spi->mode & SPI_RX_QUAD)
+	if (spi->mode & SPI_RX_QUAD) {
 		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
-	else if (spi->mode & SPI_RX_DUAL)
+
+		if (spi->mode & SPI_TX_QUAD)
+			hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
+					SNOR_HWCAPS_PP_1_1_4 |
+					SNOR_HWCAPS_PP_1_4_4);
+	} else if (spi->mode & SPI_RX_DUAL) {
 		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
+		if (spi->mode & SPI_TX_DUAL)
+			hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
+	}
+
 	if (data && data->name)
 		nor->mtd.name = data->name;
 
Signature · 2 lines
Best regards,
Marek Vasut
#13 lore raw

Re: [PATCH v5 2/6] mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols

Hi Marek,

Quoted 40 lines · Marek Vasut
Quoted 11 lines · Cyrille Pitchen

Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, m25p80_write() only supported SPI 1-1-1.

This patch updates both m25p80_read() and m25p80_write() functions to let them support SPI 1-2-2 and SPI 1-4-4 protocols for Fast Read and Page Program SPI commands.

It adopts a conservative approach to avoid regressions. Hence the new

                                             ^ FYI, regression != bug
Quoted 26 lines

implementations try to be as close as possible to the old implementations, so the main differences are:
- the tx_nbits values now being set properly for the spi_transfer structures carrying the (op code + address/dummy) bytes
- and the spi_transfer structure being split into 2 spi_transfer structures when the numbers of I/O lines are different for op code and for address/dummy byte transfers on the SPI bus.

Besides, the current spi-nor framework supports neither the SPI 2-2-2 nor the SPI 4-4-4 protocols. So, for now, we don't need to update the m25p80_{read|write}_reg() functions as SPI 1-1-1 is the only one possible protocol.

---
 drivers/mtd/devices/m25p80.c | 120 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 90 insertions(+), 30 deletions(-)
drivers/mtd/devices/m25p80.c +0 -0
index 68986a26c8fe..64d562efc25d 100644
@@ -34,6 +34,19 @@ struct m25p {
 	u8			command[MAX_CMD_SIZE];
 };
 
Quoted 9 lines

drivers/mtd/devices/m25p80.c struct m25p { · line 41

Quoted 5 lines
drivers/mtd/devices/m25p80.c +5 -0
+static inline void m25p80_proto2nbits(enum spi_nor_protocol proto,
+				      unsigned int *inst_nbits,
+				      unsigned int *addr_nbits,
+				      unsigned int *data_nbits)
+{

Why don't we just have some generic macros to extract the number of bits from $proto ?

from Documentation/process/coding-style.rst:
"Generally, inline functions are preferable to macros resembling functions."

inline functions provide better type checking of their arguments and/or returned value than macros.

Type checking is also the reason I have chosen to create the 'enum spi_nor_protocol' rather than using constant macros.

Quoted 123 lines
Quoted 123 lines
drivers/mtd/devices/m25p80.c +44 -22
+	if (inst_nbits)
+		*inst_nbits = spi_nor_get_protocol_inst_width(proto);
+	if (addr_nbits)
+		*addr_nbits = spi_nor_get_protocol_addr_width(proto);
+	if (data_nbits)
+		*data_nbits = spi_nor_get_protocol_data_width(proto);
+}
+
 static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
 {
 	struct m25p *flash = nor->priv;
@@ -78,11 +91,16 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 {
 	struct m25p *flash = nor->priv;
 	struct spi_device *spi = flash->spi;
-	struct spi_transfer t[2] = {};
+	unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
+	struct spi_transfer t[3] = {};
 	struct spi_message m;
 	int cmd_sz = m25p_cmdsz(nor);
 	ssize_t ret;
 
+	/* get transfer protocols. */
+	m25p80_proto2nbits(nor->write_proto, &inst_nbits,
+			   &addr_nbits, &data_nbits);
+
 	spi_message_init(&m);
 
 	if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
@@ -92,12 +110,27 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 	m25p_addr2cmd(nor, to, flash->command);
 
 	t[0].tx_buf = flash->command;
+	t[0].tx_nbits = inst_nbits;
 	t[0].len = cmd_sz;
 	spi_message_add_tail(&t[0], &m);
 
-	t[1].tx_buf = buf;
-	t[1].len = len;
-	spi_message_add_tail(&t[1], &m);
+	/* split the op code and address bytes into two transfers if needed. */
+	data_idx = 1;
+	if (addr_nbits != inst_nbits) {
+		t[0].len = 1;
+
+		t[1].tx_buf = &flash->command[1];
+		t[1].tx_nbits = addr_nbits;
+		t[1].len = cmd_sz - 1;
+		spi_message_add_tail(&t[1], &m);
+
+		data_idx = 2;
+	}
+
+	t[data_idx].tx_buf = buf;
+	t[data_idx].tx_nbits = data_nbits;
+	t[data_idx].len = len;
+	spi_message_add_tail(&t[data_idx], &m);
 
 	ret = spi_sync(spi, &m);
 	if (ret)
@@ -109,18 +142,6 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
 	return ret;
 }
 
-static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
-{
-	switch (nor->read_proto) {
-	case SNOR_PROTO_1_1_2:
-		return 2;
-	case SNOR_PROTO_1_1_4:
-		return 4;
-	default:
-		return 0;
-	}
-}
-
 /*
  * Read an address range from the nor chip.  The address range
  * may be any size provided it is within the physical boundaries.
@@ -130,13 +151,19 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 {
 	struct m25p *flash = nor->priv;
 	struct spi_device *spi = flash->spi;
-	struct spi_transfer t[2];
+	unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
+	struct spi_transfer t[3];
 	struct spi_message m;
 	unsigned int dummy = nor->read_dummy;
 	ssize_t ret;
+	int cmd_sz;
+
+	/* get transfer protocols. */
+	m25p80_proto2nbits(nor->read_proto, &inst_nbits,
+			   &addr_nbits, &data_nbits);
 
 	/* convert the dummy cycles to the number of bytes */
-	dummy /= 8;
+	dummy = (dummy * addr_nbits) / 8;
 
 	if (spi_flash_read_supported(spi)) {
 		struct spi_flash_read_message msg;
@@ -149,10 +176,9 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 		msg.read_opcode = nor->read_opcode;
 		msg.addr_width = nor->addr_width;
 		msg.dummy_bytes = dummy;
-		/* TODO: Support other combinations */
-		msg.opcode_nbits = SPI_NBITS_SINGLE;
-		msg.addr_nbits = SPI_NBITS_SINGLE;
-		msg.data_nbits = m25p80_rx_nbits(nor);
+		msg.opcode_nbits = inst_nbits;
+		msg.addr_nbits = addr_nbits;
+		msg.data_nbits = data_nbits;
 
 		ret = spi_flash_read(spi, &msg);
 		if (ret < 0)
@@ -167,20 +193,45 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
 	m25p_addr2cmd(nor, from, flash->command);
 
 	t[0].tx_buf = flash->command;
+	t[0].tx_nbits = inst_nbits;
 	t[0].len = m25p_cmdsz(nor) + dummy;
 	spi_message_add_tail(&t[0], &m);
 
Quoted 14 lines

drivers/mtd/devices/m25p80.c static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len, · line 205

Quoted 11 lines
drivers/mtd/devices/m25p80.c +6 -5
-	t[1].rx_buf = buf;
-	t[1].rx_nbits = m25p80_rx_nbits(nor);
-	t[1].len = min3(len, spi_max_transfer_size(spi),
-			spi_max_message_size(spi) - t[0].len);
-	spi_message_add_tail(&t[1], &m);
+	/*
+	 * Set all dummy/mode cycle bits to avoid sending some manufacturer
+	 * specific pattern, which might make the memory enter its Continuous
+	 * Read mode by mistake.
+	 * Based on the different mode cycle bit patterns listed and described
+	 * in the JESD216B speficication, the 0xff value works for all memories
                           ^
                           specification, typo

good catch :)

Best regards,

Cyrille

Quoted 59 lines
Quoted 57 lines
drivers/mtd/devices/m25p80.c +35 -3
+	 * and all manufacturers.
+	 */
+	cmd_sz = t[0].len;
+	memset(flash->command + cmd_sz - dummy, 0xff, dummy);
+
+	/* split the op code and address bytes into two transfers if needed. */
+	data_idx = 1;
+	if (addr_nbits != inst_nbits) {
+		t[0].len = 1;
+
+		t[1].tx_buf = &flash->command[1];
+		t[1].tx_nbits = addr_nbits;
+		t[1].len = cmd_sz - 1;
+		spi_message_add_tail(&t[1], &m);
+
+		data_idx = 2;
+	}
+
+	t[data_idx].rx_buf = buf;
+	t[data_idx].rx_nbits = data_nbits;
+	t[data_idx].len = min3(len, spi_max_transfer_size(spi),
+			       spi_max_message_size(spi) - cmd_sz);
+	spi_message_add_tail(&t[data_idx], &m);
 
 	ret = spi_sync(spi, &m);
 	if (ret)
 		return ret;
 
-	ret = m.actual_length - m25p_cmdsz(nor) - dummy;
+	ret = m.actual_length - cmd_sz;
 	if (ret < 0)
 		return -EIO;
 	return ret;
@@ -223,11 +274,20 @@ static int m25p_probe(struct spi_device *spi)
 	spi_set_drvdata(spi, flash);
 	flash->spi = spi;
 
-	if (spi->mode & SPI_RX_QUAD)
+	if (spi->mode & SPI_RX_QUAD) {
 		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
-	else if (spi->mode & SPI_RX_DUAL)
+
+		if (spi->mode & SPI_TX_QUAD)
+			hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
+					SNOR_HWCAPS_PP_1_1_4 |
+					SNOR_HWCAPS_PP_1_4_4);
+	} else if (spi->mode & SPI_RX_DUAL) {
 		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
 
+		if (spi->mode & SPI_TX_DUAL)
+			hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
+	}
+
 	if (data && data->name)
 		nor->mtd.name = data->name;
 
#14 lore raw

Re: [PATCH v5 2/6] mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols

Quoted 53 lines · Cyrille Pitchen

Hi Marek,

Quoted 40 lines · Marek Vasut
Quoted 11 lines · Cyrille Pitchen

Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, m25p80_write() only supported SPI 1-1-1.

This patch updates both m25p80_read() and m25p80_write() functions to let them support SPI 1-2-2 and SPI 1-4-4 protocols for Fast Read and Page Program SPI commands.

It adopts a conservative approach to avoid regressions. Hence the new

                                             ^ FYI, regression != bug
Quoted 26 lines

implementations try to be as close as possible to the old implementations, so the main differences are:
- the tx_nbits values now being set properly for the spi_transfer structures carrying the (op code + address/dummy) bytes
- and the spi_transfer structure being split into 2 spi_transfer structures when the numbers of I/O lines are different for op code and for address/dummy byte transfers on the SPI bus.

Besides, the current spi-nor framework supports neither the SPI 2-2-2 nor the SPI 4-4-4 protocols. So, for now, we don't need to update the m25p80_{read|write}_reg() functions as SPI 1-1-1 is the only one possible protocol.

---
 drivers/mtd/devices/m25p80.c | 120 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 90 insertions(+), 30 deletions(-)
drivers/mtd/devices/m25p80.c +0 -0
index 68986a26c8fe..64d562efc25d 100644
@@ -34,6 +34,19 @@ struct m25p {
 	u8			command[MAX_CMD_SIZE];
 };
 
Quoted 9 lines

drivers/mtd/devices/m25p80.c struct m25p { · line 41

Quoted 5 lines
drivers/mtd/devices/m25p80.c +5 -0
+static inline void m25p80_proto2nbits(enum spi_nor_protocol proto,
+				      unsigned int *inst_nbits,
+				      unsigned int *addr_nbits,
+				      unsigned int *data_nbits)
+{

Why don't we just have some generic macros to extract the number of bits from $proto ?

Quoted 8 lines

from Documentation/process/coding-style.rst:
"Generally, inline functions are preferable to macros resembling functions."

inline functions provide better type checking of their arguments and/or returned value than macros.

Type checking is also the reason I have chosen to create the 'enum spi_nor_protocol' rather than using constant macros.

That part I get (no, not really [1], inline is compiler _hint_ and for static function, the compiler is smart enough to figure out it should inline it, so drop it. Also cf. __always_inline).

What I don't quite get is why don't we just encode the proto as ie.

#define PROTO_1_1_4 0x00010204 /* (== BIT(16) | BIT(8) | BIT(2)) */

in which case this whole function would turn into constant-time

return (proto >> (n * 8)) & 0xff;

where n is 0 for data, 1 for address , 2 for command .

[1] https://lwn.net/Articles/166172/

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
drivers/mtd/devices/m25p80.c +8 -0
+	if (inst_nbits)
+		*inst_nbits = spi_nor_get_protocol_inst_width(proto);
+	if (addr_nbits)
+		*addr_nbits = spi_nor_get_protocol_addr_width(proto);
+	if (data_nbits)
+		*data_nbits = spi_nor_get_protocol_data_width(proto);
+}
+

[...]

Signature · 2 lines
Best regards,
Marek Vasut
#15 lore raw

Re: [PATCH v5 2/6] mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols

Quoted 63 lines · Marek Vasut
Quoted 53 lines · Cyrille Pitchen

Hi Marek,

Quoted 40 lines · Marek Vasut
Quoted 11 lines · Cyrille Pitchen

Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, m25p80_write() only supported SPI 1-1-1.

This patch updates both m25p80_read() and m25p80_write() functions to let them support SPI 1-2-2 and SPI 1-4-4 protocols for Fast Read and Page Program SPI commands.

It adopts a conservative approach to avoid regressions. Hence the new

                                             ^ FYI, regression != bug
Quoted 26 lines

implementations try to be as close as possible to the old implementations, so the main differences are:
- the tx_nbits values now being set properly for the spi_transfer structures carrying the (op code + address/dummy) bytes
- and the spi_transfer structure being split into 2 spi_transfer structures when the numbers of I/O lines are different for op code and for address/dummy byte transfers on the SPI bus.

Besides, the current spi-nor framework supports neither the SPI 2-2-2 nor the SPI 4-4-4 protocols. So, for now, we don't need to update the m25p80_{read|write}_reg() functions as SPI 1-1-1 is the only one possible protocol.

---
 drivers/mtd/devices/m25p80.c | 120 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 90 insertions(+), 30 deletions(-)
drivers/mtd/devices/m25p80.c +0 -0
index 68986a26c8fe..64d562efc25d 100644
@@ -34,6 +34,19 @@ struct m25p {
 	u8			command[MAX_CMD_SIZE];
 };
 
Quoted 9 lines

drivers/mtd/devices/m25p80.c struct m25p { · line 41

Quoted 5 lines
drivers/mtd/devices/m25p80.c +5 -0
+static inline void m25p80_proto2nbits(enum spi_nor_protocol proto,
+				      unsigned int *inst_nbits,
+				      unsigned int *addr_nbits,
+				      unsigned int *data_nbits)
+{

Why don't we just have some generic macros to extract the number of bits from $proto ?

Quoted 8 lines

from Documentation/process/coding-style.rst:
"Generally, inline functions are preferable to macros resembling functions."

inline functions provide better type checking of their arguments and/or returned value than macros.

Type checking is also the reason I have chosen to create the 'enum spi_nor_protocol' rather than using constant macros.

Quoted 8 lines

That part I get (no, not really [1], inline is compiler _hint_ and for static function, the compiler is smart enough to figure out it should inline it, so drop it. Also cf. __always_inline).

What I don't quite get is why don't we just encode the proto as ie.

#define PROTO_1_1_4 0x00010204 /* (== BIT(16) | BIT(8) | BIT(2)) */

This is what I did in former versions of the patch: the scheme you propose requires more bits to encode the number of I/O lines for instruction, address and data: there would be less bits available for future extensions.

Also using the notion of protocol class (1-1-N, 1-N-N, N-N-N) in the encoding scheme prevents from setting impossible combinations like 4-1-4, 1-2-4, ...

Quoted 18 lines

in which case this whole function would turn into constant-time

return (proto >> (n * 8)) & 0xff;

where n is 0 for data, 1 for address , 2 for command .

[1] https://lwn.net/Articles/166172/

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
drivers/mtd/devices/m25p80.c +8 -0
+	if (inst_nbits)
+		*inst_nbits = spi_nor_get_protocol_inst_width(proto);
+	if (addr_nbits)
+		*addr_nbits = spi_nor_get_protocol_addr_width(proto);
+	if (data_nbits)
+		*data_nbits = spi_nor_get_protocol_data_width(proto);
+}
+

[...]

#16 lore raw

Re: [PATCH v5 2/6] mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols

Quoted 70 lines · Cyrille Pitchen
Quoted 69 lines · Marek Vasut
Quoted 53 lines · Cyrille Pitchen

Hi Marek,

Quoted 40 lines · Marek Vasut
Quoted 11 lines · Cyrille Pitchen

Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, m25p80_write() only supported SPI 1-1-1.

This patch updates both m25p80_read() and m25p80_write() functions to let them support SPI 1-2-2 and SPI 1-4-4 protocols for Fast Read and Page Program SPI commands.

It adopts a conservative approach to avoid regressions. Hence the new

                                             ^ FYI, regression != bug
Quoted 26 lines

implementations try to be as close as possible to the old implementations, so the main differences are:
- the tx_nbits values now being set properly for the spi_transfer structures carrying the (op code + address/dummy) bytes
- and the spi_transfer structure being split into 2 spi_transfer structures when the numbers of I/O lines are different for op code and for address/dummy byte transfers on the SPI bus.

Besides, the current spi-nor framework supports neither the SPI 2-2-2 nor the SPI 4-4-4 protocols. So, for now, we don't need to update the m25p80_{read|write}_reg() functions as SPI 1-1-1 is the only one possible protocol.

---
 drivers/mtd/devices/m25p80.c | 120 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 90 insertions(+), 30 deletions(-)
drivers/mtd/devices/m25p80.c +0 -0
index 68986a26c8fe..64d562efc25d 100644
@@ -34,6 +34,19 @@ struct m25p {
 	u8			command[MAX_CMD_SIZE];
 };
 
Quoted 9 lines

drivers/mtd/devices/m25p80.c struct m25p { · line 41

Quoted 5 lines
drivers/mtd/devices/m25p80.c +5 -0
+static inline void m25p80_proto2nbits(enum spi_nor_protocol proto,
+				      unsigned int *inst_nbits,
+				      unsigned int *addr_nbits,
+				      unsigned int *data_nbits)
+{

Why don't we just have some generic macros to extract the number of bits from $proto ?

Quoted 8 lines

from Documentation/process/coding-style.rst:
"Generally, inline functions are preferable to macros resembling functions."

inline functions provide better type checking of their arguments and/or returned value than macros.

Type checking is also the reason I have chosen to create the 'enum spi_nor_protocol' rather than using constant macros.

That part I get (no, not really [1], inline is compiler _hint_ and for static function, the compiler is smart enough to figure out it should inline it, so drop it. Also cf. __always_inline).

What I don't quite get is why don't we just encode the proto as ie.

Quoted 7 lines
Quoted 2 lines

#define PROTO_1_1_4 0x00010204 /* (== BIT(16) | BIT(8) | BIT(2)) */

This is what I did in former versions of the patch: the scheme you propose requires more bits to encode the number of I/O lines for instruction, address and data: there would be less bits available for future extensions.

Are we ever gonna reach 128bit SPI ? I don't think so. Yes, it requires more bits, but it also makes it easier to extract information from it without some elaborate loops.

Quoted 3 lines

Also using the notion of protocol class (1-1-N, 1-N-N, N-N-N) in the encoding scheme prevents from setting impossible combinations like 4-1-4, 1-2-4, ...

I'd suspect that the review process would catch this.

Quoted 19 lines
Quoted 18 lines

in which case this whole function would turn into constant-time

return (proto >> (n * 8)) & 0xff;

where n is 0 for data, 1 for address , 2 for command .

[1] https://lwn.net/Articles/166172/

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
drivers/mtd/devices/m25p80.c +8 -0
+	if (inst_nbits)
+		*inst_nbits = spi_nor_get_protocol_inst_width(proto);
+	if (addr_nbits)
+		*addr_nbits = spi_nor_get_protocol_addr_width(proto);
+	if (data_nbits)
+		*data_nbits = spi_nor_get_protocol_data_width(proto);
+}
+

[...]

Signature · 2 lines
Best regards,
Marek Vasut
#17 lore raw

PATCH v5 3/6 mtd: spi-nor: add spi_nor_init() function

In reply to Cyrille Pitchen

This patch extracts some chunks from spi_nor_scan() and moves them into a new spi_nor_init() function.

Indeed, spi_nor_init() regroups all the required SPI flash commands to be sent to the SPI flash memory before performing any runtime operations (Fast Read, Page Program, Sector Erase, ...). Hence spi_nor_init():
1) removes the flash protection if applicable for certain vendors.
2) sets the Quad Enable bit, if needed, before using Quad SPI protocols.
3) makes the memory enter its (stateful) 4-byte address mode, if needed, for SPI flash memory > 128Mbits not supporting the 4-byte address instruction set.

spi_nor_scan() now ends by calling spi_nor_init() once the probe phase has completed. Further patches could also use spi_nor_init() to implement the mtd->_resume() handler for the spi-nor framework.

---
 drivers/mtd/spi-nor/spi-nor.c | 71 ++++++++++++++++++++++++++++---------------
 include/linux/mtd/spi-nor.h   |  8 +++++
 2 files changed, 55 insertions(+), 24 deletions(-)
drivers/mtd/spi-nor/spi-nor.c +47 -24
index cc443c6cbae8..f648374d6824 100644
@@ -255,6 +255,8 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
 	nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
 	nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode);
 	nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode);
+
+	nor->flags |= SNOR_F_4B_OPCODES;
 }
 
 /* Enable/disable 4-byte addressing mode. */
@@ -1809,6 +1811,45 @@ static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
 	return 0;
 }
 
+static int spi_nor_init(struct spi_nor *nor)
+{
+	const struct flash_info *info = nor->info;
+	struct device *dev = nor->dev;
+	int ret;
+
+	/*
+	 * Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up
+	 * with the software protection bits set
+	 */
+
+	if (JEDEC_MFR(info) == SNOR_MFR_ATMEL ||
+	    JEDEC_MFR(info) == SNOR_MFR_INTEL ||
+	    JEDEC_MFR(info) == SNOR_MFR_SST ||
+	    info->flags & SPI_NOR_HAS_LOCK) {
+		write_enable(nor);
+		write_sr(nor, 0);
+		spi_nor_wait_till_ready(nor);
+	}
+
+	/* Set the Quad Enable bit, if needed. */
+	if (nor->flash_quad_enable) {
+		ret = nor->flash_quad_enable(nor);
+		if (ret) {
+			dev_err(dev, "quad mode not supported\n");
+			return ret;
+		}
+	}
+
+	/*
+	 * For SPI flash memories above 128Mib, enter the 4-byte address mode
+	 * only if the 4-byte address instruction set is not supported.
+	 */
+	if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES))
+		set_4byte(nor, info, 1);
+
+	return 0;
+}
+
 int spi_nor_scan(struct spi_nor *nor, const char *name,
 		 const struct spi_nor_hwcaps *hwcaps)
 {
@@ -1876,20 +1917,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
 	if (info->flags & SPI_S3AN)
 		nor->flags |=  SNOR_F_READY_XSR_RDY;
 
-	/*
-	 * Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up
-	 * with the software protection bits set
-	 */
-
-	if (JEDEC_MFR(info) == SNOR_MFR_ATMEL ||
-	    JEDEC_MFR(info) == SNOR_MFR_INTEL ||
-	    JEDEC_MFR(info) == SNOR_MFR_SST ||
-	    info->flags & SPI_NOR_HAS_LOCK) {
-		write_enable(nor);
-		write_sr(nor, 0);
-		spi_nor_wait_till_ready(nor);
-	}
-
 	if (!mtd->name)
 		mtd->name = dev_name(dev);
 	mtd->priv = nor;
@@ -1960,14 +1987,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
 	if (ret)
 		return ret;
 
-	if (nor->flash_quad_enable) {
-		ret = nor->flash_quad_enable(nor);
-		if (ret) {
-			dev_err(dev, "quad mode not supported\n");
-			return ret;
-		}
-	}
-
 	if (info->addr_width)
 		nor->addr_width = info->addr_width;
 	else if (mtd->size > 0x1000000) {
@@ -1976,8 +1995,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
 		if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
 		    info->flags & SPI_NOR_4B_OPCODES)
 			spi_nor_set_4byte_opcodes(nor, info);
-		else
-			set_4byte(nor, info, 1);
 	} else {
 		nor->addr_width = 3;
 	}
@@ -1994,6 +2011,12 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
 			return ret;
 	}
 
+	/* Send all the required SPI flash commands to initialize the memory. */
+	nor->info = info;
+	ret = spi_nor_init(nor);
+	if (ret)
+		return ret;
+
 	dev_info(dev, "%s (%lld Kbytes)\n", info->name,
 			(long long)mtd->size >> 10);
 
include/linux/mtd/spi-nor.h +8 -0
index 732ee6cd5330..d270788f5ab6 100644
@@ -212,9 +212,16 @@ enum spi_nor_option_flags {
 	SNOR_F_NO_OP_CHIP_ERASE	= BIT(2),
 	SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
 	SNOR_F_READY_XSR_RDY	= BIT(4),
+	SNOR_F_4B_OPCODES	= BIT(5),
 };
 
 /**
+ * struct flash_info -	Forward declaration of a structure used internally by
+ *			spi_nor_scan() and spi_nor_init().
+ */
+struct flash_info;
+
+/**
  * struct spi_nor - Structure for defining a the SPI NOR layer
  * @mtd:		point to a mtd_info structure
  * @lock:		the lock for the read/write/erase/lock/unlock operations
@@ -253,6 +260,7 @@ struct spi_nor {
 	struct mtd_info		mtd;
 	struct mutex		lock;
 	struct device		*dev;
+	const struct flash_info	*info;
 	u32			page_size;
 	u8			addr_width;
 	u8			erase_opcode;
Signature · 1 line
2.9.3
#18 lore raw

Re: [PATCH v5 3/6] mtd: spi-nor: add spi_nor_init() function

Quoted 12 lines · Cyrille Pitchen

This patch extracts some chunks from spi_nor_scan() and moves them into a new spi_nor_init() function.

Indeed, spi_nor_init() regroups all the required SPI flash commands to be sent to the SPI flash memory before performing any runtime operations (Fast Read, Page Program, Sector Erase, ...). Hence spi_nor_init():
1) removes the flash protection if applicable for certain vendors.
2) sets the Quad Enable bit, if needed, before using Quad SPI protocols.
3) makes the memory enter its (stateful) 4-byte address mode, if needed, for SPI flash memory > 128Mbits not supporting the 4-byte address instruction set.

Quoted 6 lines

spi_nor_scan() now ends by calling spi_nor_init() once the probe phase has completed. Further patches could also use spi_nor_init() to implement the mtd->_resume() handler for the spi-nor framework.

Applied to github/spi-nor

#19 lore raw

RFC PATCH v5 4/6 mtd: spi-nor: add support to non-uniform SPI NOR flash memories

In reply to Cyrille Pitchen

This patch is a first step in introducing the support of SPI memories with non-uniform erase sizes like Spansion s25fs512s.

It introduces the memory erase map which splits the memory array into one or many erase regions. Each erase region supports up to 4 erase commands, as defined by the JEDEC JESD216B (SFDP) specification.
In turn, an erase command is defined by an op code and a sector size.

To be backward compatible, the erase map of uniform SPI NOR flash memories is initialized so it contains only one erase region and this erase region supports only one erase command. Hence a single size is used to erase any sector/block of the memory.

Besides, since the algorithm used to erase sectors on non-uniform SPI NOR flash memories is quite expensive, when possible, the erase map is tuned to come back to the uniform case.

This is a transitional patch: non-uniform erase maps will be used later when initialized based on the SFDP data.

---
 drivers/mtd/spi-nor/spi-nor.c | 368 ++++++++++++++++++++++++++++++++++++++++--
 include/linux/mtd/spi-nor.h   |  56 +++++++
 2 files changed, 410 insertions(+), 14 deletions(-)
drivers/mtd/spi-nor/spi-nor.c +354 -14
index f648374d6824..2e54792d506d 100644
@@ -256,6 +256,18 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
 	nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode);
 	nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode);
 
+	if (!spi_nor_has_uniform_erase(nor)) {
+		struct spi_nor_erase_map *map = &nor->erase_map;
+		struct spi_nor_erase_command *cmd;
+		int i;
+
+		for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+			cmd = &map->commands[i];
+
+			cmd->opcode = spi_nor_convert_3to4_erase(cmd->opcode);
+		}
+	}
+
 	nor->flags |= SNOR_F_4B_OPCODES;
 }
 
@@ -456,6 +468,132 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
 	return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
 }
 
+static inline u64
+spi_nor_div_by_erase_size(const struct spi_nor_erase_command *cmd,
+			  u64 dividend, u32 *remainder)
+{
+	if (likely(cmd->size_shift)) {
+		*remainder = (u32)dividend & cmd->size_mask;
+		return dividend >> cmd->size_shift;
+	}
+
+	return div_u64_rem(dividend, cmd->size, remainder);
+}
+
+static bool
+spi_nor_test_erase_region(struct spi_nor *nor, u64 addr, u32 len,
+			  const struct spi_nor_erase_region *region,
+			  const struct spi_nor_erase_command **cmd)
+{
+	const struct spi_nor_erase_map *map = &nor->erase_map;
+	const struct spi_nor_erase_command *best_cmd = NULL;
+	const struct spi_nor_erase_command *tested_cmd;
+	u64 region_start, region_end, cmd_mask;
+	u32 rem;
+	int i;
+
+	region_start = region->offset & ~SNOR_CMD_ERASE_MASK;
+	region_end = region_start + region->size;
+
+	cmd_mask = region->offset & SNOR_CMD_ERASE_MASK;
+	for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+		/* Does the erase region support the tested erase command? */
+		if (!(cmd_mask & BIT(i)))
+			continue;
+
+		tested_cmd = &map->commands[i];
+
+		/* Don't erase more than what the user has asked for. */
+		if (tested_cmd->size > len)
+			continue;
+
+		/* 'addr' must be aligned to the erase size. */
+		spi_nor_div_by_erase_size(tested_cmd, addr, &rem);
+		if (rem)
+			continue;
+
+		/*
+		 * 'addr' must be inside the region.
+		 * Erase regions may overlap, so compute the actual start offset
+		 * of this erase region based on the size of the tested erase
+		 * command.
+		 */
+		spi_nor_div_by_erase_size(tested_cmd, region_start, &rem);
+		if (addr < (region_start - rem) || region_end <= addr)
+			continue;
+
+		/*
+		 * The tested erase size is valid but we still need to check
+		 * whether it is better than the current best erase command.
+		 */
+		if (!best_cmd || best_cmd->size < tested_cmd->size)
+			best_cmd = tested_cmd;
+	}
+
+	*cmd = best_cmd;
+	return (best_cmd != NULL);
+}
+
+static bool
+spi_nor_find_erase_region(struct spi_nor *nor, u64 addr, u32 len,
+			  const struct spi_nor_erase_region **region,
+			  const struct spi_nor_erase_command **cmd)
+{
+	const struct spi_nor_erase_map *map = &nor->erase_map;
+	const struct spi_nor_erase_region *best_region = NULL;
+	const struct spi_nor_erase_command *best_cmd = NULL;
+	int i;
+
+	for (i = 0; i < map->num_regions; i++) {
+		const struct spi_nor_erase_command *tested_cmd = NULL;
+
+		if (!spi_nor_test_erase_region(nor, addr, len, &map->regions[i],
+					       &tested_cmd))
+			continue;
+
+		if (!best_cmd || best_cmd->size < tested_cmd->size) {
+			best_region = &map->regions[i];
+			best_cmd = tested_cmd;
+		}
+	}
+
+	*region = best_region;
+	*cmd = best_cmd;
+	return (best_cmd != NULL);
+}
+
+static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u32 addr, u32 len)
+{
+	const struct spi_nor_erase_region *region;
+	const struct spi_nor_erase_command *cmd;
+	u64 region_start, region_end;
+	int ret;
+
+	while (len) {
+		if (!spi_nor_find_erase_region(nor, addr, len, &region, &cmd))
+			return -EINVAL;
+
+		nor->erase_opcode = cmd->opcode;
+
+		region_start = region->offset & ~SNOR_CMD_ERASE_MASK;
+		region_end = region_start + region->size;
+		while (len && (u64)addr < region_end) {
+			ret = spi_nor_erase_sector(nor, addr);
+			if (ret)
+				return ret;
+
+			addr += cmd->size;
+			len -= cmd->size;
+
+			ret = spi_nor_wait_till_ready(nor);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
 /*
  * Erase an address range on the nor chip.  The address range may extend
  * one or more erase sectors.  Return an error is there is a problem erasing.
@@ -470,9 +608,11 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 	dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
 			(long long)instr->len);
 
-	div_u64_rem(instr->len, mtd->erasesize, &rem);
-	if (rem)
-		return -EINVAL;
+	if (likely(spi_nor_has_uniform_erase(nor))) {
+		div_u64_rem(instr->len, mtd->erasesize, &rem);
+		if (rem)
+			return -EINVAL;
+	}
 
 	addr = instr->addr;
 	len = instr->len;
@@ -511,7 +651,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 	 */
 
 	/* "sector"-at-a-time erase */
-	} else {
+	} else if (likely(spi_nor_has_uniform_erase(nor))) {
 		while (len) {
 			write_enable(nor);
 
@@ -526,6 +666,12 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 			if (ret)
 				goto erase_err;
 		}
+
+	/* erase multiple sectors */
+	} else {
+		ret = spi_nor_erase_multi_sectors(nor, addr, len);
+		if (ret)
+			goto erase_err;
 	}
 
 	write_disable(nor);
@@ -1588,10 +1734,38 @@ spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
 	pp->proto = proto;
 }
 
+static inline void
+spi_nor_set_erase_command(struct spi_nor_erase_command *cmd,
+			  u32 size, u8 opcode)
+{
+	cmd->size = size;
+	cmd->opcode = opcode;
+
+	if (is_power_of_2(cmd->size))
+		cmd->size_shift = ffs(cmd->size) - 1;
+	else
+		cmd->size_shift = 0;
+
+	cmd->size_mask = (1 << cmd->size_shift) - 1;
+}
+
+static inline void
+spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map,
+			       u32 cmd_mask, u64 flash_size)
+{
+	map->num_regions = 1;
+	map->regions = &map->uniform_region;
+	map->uniform_region.offset = SNOR_CMD_ERASE_OFFSET(cmd_mask, 0);
+	map->uniform_region.size = flash_size;
+}
+
 static int spi_nor_init_params(struct spi_nor *nor,
 			       const struct flash_info *info,
 			       struct spi_nor_flash_parameter *params)
 {
+	struct spi_nor_erase_map *map = &nor->erase_map;
+	u32 erase_mask = 0;
+
 	/* Set legacy flash parameters as default. */
 	memset(params, 0, sizeof(*params));
 
@@ -1628,6 +1802,21 @@ static int spi_nor_init_params(struct spi_nor *nor,
 	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
 				SPINOR_OP_PP, SNOR_PROTO_1_1_1);
 
+	/* Sector Erase settings. */
+	erase_mask |= BIT(0);
+	spi_nor_set_erase_command(&map->commands[0],
+				  info->sector_size, SPINOR_OP_SE);
+	if (info->flags & SECT_4K_PMC) {
+		erase_mask |= BIT(1);
+		spi_nor_set_erase_command(&map->commands[1],
+					  4096u, SPINOR_OP_BE_4K_PMC);
+	} else if (info->flags & SECT_4K) {
+		erase_mask |= BIT(1);
+		spi_nor_set_erase_command(&map->commands[1],
+					  4096u, SPINOR_OP_BE_4K);
+	}
+	spi_nor_init_uniform_erase_map(map, erase_mask, params->size);
+
 	/* Select the procedure to set the Quad Enable bit. */
 	if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
 				   SNOR_HWCAPS_PP_QUAD)) {
@@ -1731,25 +1920,176 @@ static int spi_nor_select_pp(struct spi_nor *nor,
 	return 0;
 }
 
+static bool spi_nor_find_uniform_erase(const struct spi_nor *nor,
+				       u32 preferred_size,
+				       const struct spi_nor_erase_command **cmd)
+{
+	const struct spi_nor_erase_map *map = &nor->erase_map;
+	const struct spi_nor_erase_command *tested_cmd;
+	const struct spi_nor_erase_region *region;
+	const struct mtd_info *mtd = &nor->mtd;
+	u64 pos, region_start, region_end;
+	int cidx, ridx;
+	u32 rem;
+
+	/* Try all erase commands to find the best one (greatest erase size). */
+	*cmd = NULL;
+	for (cidx = 0; cidx < SNOR_CMD_ERASE_MAX; cidx++) {
+		tested_cmd = &map->commands[cidx];
+
+		/* The SPI flash size must be a multiple of the erase size. */
+		spi_nor_div_by_erase_size(tested_cmd, mtd->size, &rem);
+		if (rem)
+			continue;
+
+		/*
+		 * Walk through regions to check whether the whole memory can be
+		 * erased using only the tested erase command.
+		 */
+		pos = 0;
+		while (pos < mtd->size) {
+			for (ridx = 0; ridx < map->num_regions; ridx++) {
+				region = &map->regions[ridx];
+
+				/* The region must support the erase command. */
+				if (!(region->offset & BIT(cidx)))
+					continue;
+
+				/*
+				 * Compute the actual start and end offsets of
+				 * region based on the erase size.
+				 */
+				region_start = region->offset;
+				region_start &= ~SNOR_CMD_ERASE_MASK;
+				region_end = region_start + region->size;
+
+				spi_nor_div_by_erase_size(tested_cmd,
+							  region_start,
+							  &rem);
+				region_start -= rem;
+
+				spi_nor_div_by_erase_size(tested_cmd,
+							  region_end,
+							  &rem);
+				if (rem)
+					region_end += tested_cmd->size - rem;
+
+				/*
+				 * The current position must be the start offset
+				 * of the region.
+				 */
+				if (region_start == pos) {
+					/* Keep walking through regions. */
+					pos = region_end;
+					break;
+				}
+			}
+
+			/* No region found. */
+			if (ridx == map->num_regions)
+				break;
+		}
+
+		/*
+		 * If we can't erase the whole memory using only the current
+		 * erase command, stop here and try the next supported erase
+		 * command.
+		 */
+		if (pos != mtd->size)
+			continue;
+
+		/*
+		 * If the current erase size is the preferred one, stop here:
+		 * we have found the right uniform Sector Erase command.
+		 */
+		if (tested_cmd->size == preferred_size) {
+			*cmd = tested_cmd;
+			break;
+		}
+
+		/*
+		 * Otherwise, the current erase size is still a valid canditate:
+		 * we select the highest possible erase size.
+		 */
+		if (!(*cmd) || tested_cmd->size > (*cmd)->size)
+			*cmd = tested_cmd;
+	}
+
+	return (*cmd != NULL);
+}
+
 static int spi_nor_select_erase(struct spi_nor *nor,
 				const struct flash_info *info)
 {
+	struct spi_nor_erase_map *map = &nor->erase_map;
+	const struct spi_nor_erase_command *cmd = NULL;
+	u32 preferred_size = info->sector_size;
 	struct mtd_info *mtd = &nor->mtd;
+	bool is_uniform = false;
+	int i;
 
+	/*
+	 * The previous implementation handling Sector Erase commands assumed
+	 * that the SPI flash memory has an uniform layout then used only one
+	 * of the supported erase sizes for all Sector Erase commands.
+	 * So to be backward compatible, the new implementation also tries to
+	 * manage the SPI flash memory as uniform with a single erase sector
+	 * size.
+	 */
 #ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
 	/* prefer "small sector" erase if possible */
-	if (info->flags & SECT_4K) {
-		nor->erase_opcode = SPINOR_OP_BE_4K;
-		mtd->erasesize = 4096;
-	} else if (info->flags & SECT_4K_PMC) {
-		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
-		mtd->erasesize = 4096;
-	} else
+	if (info->flags & (SECT_4K | SECT_4K_PMC))
+		preferred_size = 4096u;
 #endif
-	{
-		nor->erase_opcode = SPINOR_OP_SE;
-		mtd->erasesize = info->sector_size;
+
+	if (spi_nor_has_uniform_erase(nor)) {
+		/* The SPI flash memory is a real uniform one. */
+		cmd = NULL;
+		for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+			if (!(map->uniform_region.offset & BIT(i)))
+				continue;
+
+			if (map->commands[i].size == preferred_size) {
+				cmd = &map->commands[i];
+				break;
+			}
+			if (!cmd || map->commands[i].size > cmd->size)
+				cmd = &map->commands[i];
+		}
+
+		if (!cmd || !cmd->size)
+			return -EINVAL;
+
+		/* Disable all other Sector Erase commands. */
+		map->uniform_region.offset &= ~SNOR_CMD_ERASE_MASK;
+		map->uniform_region.offset |= BIT(cmd - map->commands);
+		is_uniform = true;
+	} else if (spi_nor_find_uniform_erase(nor, preferred_size, &cmd)) {
+		/* The SPI flash memory can be managed as an uniform one. */
+		spi_nor_init_uniform_erase_map(map, BIT(cmd - map->commands),
+					       mtd->size);
+		is_uniform = true;
+	}
+
+	if (is_uniform) {
+		/* Set the Sector Erase opcode and the associated size. */
+		nor->erase_opcode = cmd->opcode;
+		mtd->erasesize = cmd->size;
+		return 0;
 	}
+
+	/*
+	 * For non-uniform SPI flash memory, set mtd->erasesize to the
+	 * maximum erase sector size. No need to set nor->erase_opcode.
+	 */
+	cmd = NULL;
+	for (i = 0; i < SNOR_CMD_ERASE_MAX; i++)
+		if (!cmd || map->commands[i].size > cmd->size)
+			cmd = &map->commands[i];
+	if (!cmd || !cmd->size)
+		return -EINVAL;
+
+	mtd->erasesize = cmd->size;
 	return 0;
 }
 
include/linux/mtd/spi-nor.h +56 -0

80 lines not on this page · raw message

Signature · 1 line
2.9.3
#20 lore raw

Re: [RFC PATCH v5 4/6] mtd: spi-nor: add support to non-uniform SPI NOR flash memories

On 03/23/2017 12:33 AM, Cyrille Pitchen wrote:

Hrmmmm, sigh, took me almost month to review this one, sorry :(

Quoted 13 lines

This patch is a first step in introducing the support of SPI memories with non-uniform erase sizes like Spansion s25fs512s.

It introduces the memory erase map which splits the memory array into one or many erase regions. Each erase region supports up to 4 erase commands, as defined by the JEDEC JESD216B (SFDP) specification.
In turn, an erase command is defined by an op code and a sector size.

To be backward compatible, the erase map of uniform SPI NOR flash memories is initialized so it contains only one erase region and this erase region supports only one erase command. Hence a single size is used to erase any sector/block of the memory.

Quoted 8 lines

Besides, since the algorithm used to erase sectors on non-uniform SPI NOR flash memories is quite expensive, when possible, the erase map is tuned to come back to the uniform case.

This is a transitional patch: non-uniform erase maps will be used later when initialized based on the SFDP data.

[...]

Before I dive into the code, I have two questions:

1) On ie. 128 MiB part, how many struct spi_nor_erase_region {} instances would be allocated in total (consider you support 4k, 64k and 32M erase opcodes) ? Three ?

2) Would it make sense to represent the erase regions as a tree instead? For example

[ region with 32MiB die erase opcode , start=0 , count=4 ]
|
V
[ region with 64k erase opcode ][ region with 64k erase opcode ]
[ start=0, count=1 ][ start=0, count=511 ]

            |
            V

[ region with 4k erase opcode ]
[ start=0, count=16 ]

I think it'd make the lookup for the best-fitting opcode combination faster if the user decides to erase some arbitrarily-aligned block of the flash.

What do you think ?

Note this tree-based approach does not handle the cases where erase regions would overlap, although I doubt that could be a problem .

Quoted 89 lines
include/linux/mtd/spi-nor.h +56 -0
index d270788f5ab6..c12cafe99bee 100644
@@ -216,6 +216,55 @@ enum spi_nor_option_flags {
 };
 
 /**
+ * struct spi_nor_erase_command - Structure to describe a SPI NOR erase command
+ * @size:		the size of the sector/block erased by the command.
+ * @size_shift:		the size shift: if @size is a power of 2 then the shift
+ *			is stored in @size_shift, otherwise @size_shift is zero.
+ * @size_mask:		the size mask based on @size_shift.
+ * @opcode:		the SPI command op code to erase the sector/block.
+ */
+struct spi_nor_erase_command {
+	u32	size;
+	u32	size_shift;
+	u32	size_mask;
+	u8	opcode;
+};
+
+/**
+ * struct spi_nor_erase_region - Structure to describe a SPI NOR erase region
+ * @offset:		the offset in the data array of erase region start.
+ *			LSB bits are used as a bitmask encoding the erase
+ *			commands supported inside this erase region.
+ * @size:		the size of the region in bytes.
+ */
+struct spi_nor_erase_region {
+	u64		offset;
+	u64		size;
+};
+
+#define SNOR_CMD_ERASE_MAX	4
+#define SNOR_CMD_ERASE_MASK	GENMASK_ULL(SNOR_CMD_ERASE_MAX - 1, 0)
+#define SNOR_CMD_ERASE_OFFSET(_cmd_mask, _offset)	\
+	((((u64)(_offset)) & ~SNOR_CMD_ERASE_MASK) |	\
+	 (((u64)(_cmd_mask)) & SNOR_CMD_ERASE_MASK))
+
+/**
+ * struct spi_nor_erase_map - Structure to describe the SPI NOR erase map
+ * @commands:		an array of erase commands shared by all the regions.
+ * @uniform_region:	a pre-allocated erase region for SPI NOR with a uniform
+ *			sector size (legacy implementation).
+ * @regions:		point to an array describing the boundaries of the erase
+ *			regions.
+ * @num_regions:	the number of elements in the @regions array.
+ */
+struct spi_nor_erase_map {
+	struct spi_nor_erase_command	commands[SNOR_CMD_ERASE_MAX];
+	struct spi_nor_erase_region	uniform_region;
+	struct spi_nor_erase_region	*regions;
+	u32				num_regions;
+};
+
+/**
  * struct flash_info -	Forward declaration of a structure used internally by
  *			spi_nor_scan() and spi_nor_init().
  */
@@ -238,6 +287,7 @@ struct flash_info;
  * @write_proto:	the SPI protocol for write operations
  * @reg_proto		the SPI protocol for read_reg/write_reg/erase operations
  * @cmd_buf:		used by the write_reg
+ * @erase_map:		the erase map of the SPI NOR
  * @prepare:		[OPTIONAL] do some preparations for the
  *			read/write/erase/lock/unlock operations
  * @unprepare:		[OPTIONAL] do some post work after the
@@ -273,6 +323,7 @@ struct spi_nor {
 	bool			sst_write_second;
 	u32			flags;
 	u8			cmd_buf[SPI_NOR_MAX_CMD_SIZE];
+	struct spi_nor_erase_map	erase_map;
 
 	int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
 	void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
@@ -293,6 +344,11 @@ struct spi_nor {
 	void *priv;
 };
 
+static inline bool spi_nor_has_uniform_erase(const struct spi_nor *nor)
+{
+	return (nor->erase_map.regions == &nor->erase_map.uniform_region);
+}
+
 static inline void spi_nor_set_flash_node(struct spi_nor *nor,
 					  struct device_node *np)
 {
Signature · 2 lines
Best regards,
Marek Vasut
#21 lore raw

RFC PATCH v5 5/6 mtd: spi-nor: parse Serial Flash Discoverable Parameters (SFDP) tables

In reply to Cyrille Pitchen

This patch adds support to the JESD216B standard and parses the SFDP tables to dynamically initialize the 'struct spi_nor_flash_parameter'.

---
 drivers/mtd/spi-nor/spi-nor.c | 558 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/mtd/spi-nor.h   |   6 +
 2 files changed, 564 insertions(+)
drivers/mtd/spi-nor/spi-nor.c +558 -0
index 2e54792d506d..ce8722055a9c 100644
@@ -17,6 +17,7 @@
 #include <linux/mutex.h>
 #include <linux/math64.h>
 #include <linux/sizes.h>
+#include <linux/slab.h>
 
 #include <linux/mtd/mtd.h>
 #include <linux/of_platform.h>
@@ -86,6 +87,7 @@ struct flash_info {
 					 * to support memory size above 128Mib.
 					 */
 #define NO_CHIP_ERASE		BIT(12) /* Chip does not support chip erase */
+#define SPI_NOR_SKIP_SFDP	BIT(13)	/* Skip parsing of SFDP tables */
 };
 
 #define JEDEC_MFR(info)	((info)->id[0])
@@ -1593,6 +1595,99 @@ static int spansion_quad_enable(struct spi_nor *nor)
 	return 0;
 }
 
+static int spansion_new_quad_enable(struct spi_nor *nor)
+{
+	u8 sr_cr[2];
+	int ret;
+
+	/* Check current Quad Enable bit value. */
+	ret = read_cr(nor);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while reading configuration register\n");
+		return -EINVAL;
+	}
+	sr_cr[1] = ret;
+	if (sr_cr[1] & CR_QUAD_EN_SPAN)
+		return 0;
+
+	dev_info(nor->dev, "setting Spansion Quad Enable (non-volatile) bit\n");
+
+	/* Keep the current value of the Status Register. */
+	ret = read_sr(nor);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while reading status register\n");
+		return -EINVAL;
+	}
+	sr_cr[0] = ret;
+	sr_cr[1] |= CR_QUAD_EN_SPAN;
+
+	write_enable(nor);
+
+	ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while writing configuration register\n");
+		return -EINVAL;
+	}
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret < 0) {
+		dev_err(nor->dev, "error while waiting for WRSR completion\n");
+		return ret;
+	}
+
+	/* read back and check it */
+	ret = read_cr(nor);
+	if (!(ret > 0 && (ret & CR_QUAD_EN_SPAN))) {
+		dev_err(nor->dev, "Spansion Quad bit not set\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sr2_bit7_quad_enable(struct spi_nor *nor)
+{
+	u8 sr2;
+	int ret;
+
+	/* Check current Quad Enable bit value. */
+	ret = nor->read_reg(nor, SPINOR_OP_RDSR2, &sr2, 1);
+	if (ret)
+		return ret;
+	if (sr2 & SR2_QUAD_EN_BIT7)
+		return 0;
+
+	/* Update the Quad Enable bit. */
+	sr2 |= SR2_QUAD_EN_BIT7;
+
+	write_enable(nor);
+
+	ret = nor->write_reg(nor, SPINOR_OP_WRSR2, &sr2, 1);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while writing status register 2\n");
+		return -EINVAL;
+	}
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret < 0) {
+		dev_err(nor->dev, "error while waiting for WRSR2 completion\n");
+		return ret;
+	}
+
+	/* Read back and check it. */
+	ret = nor->read_reg(nor, SPINOR_OP_RDSR2, &sr2, 1);
+	if (ret || !(sr2 & SR2_QUAD_EN_BIT7)) {
+		dev_err(nor->dev, "SR2 Quad bit not set\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int spi_nor_check(struct spi_nor *nor)
 {
 	if (!nor->dev || !nor->read || !nor->write ||
@@ -1759,6 +1854,465 @@ spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map,
 	map->uniform_region.size = flash_size;
 }
 
+
+/*
+ * SFDP parsing.
+ */
+
+static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr,
+			     size_t len, void *buf)
+{
+	u8 addr_width, read_opcode, read_dummy;
+	int ret;
+
+	read_opcode = nor->read_opcode;
+	addr_width = nor->addr_width;
+	read_dummy = nor->read_dummy;
+
+	nor->read_opcode = SPINOR_OP_RDSFDP;
+	nor->addr_width = 3;
+	nor->read_dummy = 8;
+
+	ret = nor->read(nor, addr, len, (u8 *)buf);
+
+	nor->read_opcode = read_opcode;
+	nor->addr_width = addr_width;
+	nor->read_dummy = read_dummy;
+
+	return (ret < 0) ? ret : 0;
+}
+
+struct sfdp_parameter_header {
+	u8		id_lsb;
+	u8		minor;
+	u8		major;
+	u8		length; /* in double words */
+	u8		parameter_table_pointer[3]; /* byte address */
+	u8		id_msb;
+};
+
+#define SFDP_PARAM_HEADER_ID(p)	((u16)(((p)->id_msb << 8) | (p)->id_lsb))
+#define SFDP_PARAM_HEADER_PTP(p) \
+	((u32)(((p)->parameter_table_pointer[2] << 16) | \
+	       ((p)->parameter_table_pointer[1] <<  8) | \
+	       ((p)->parameter_table_pointer[0] <<  0)))
+
+
+#define SFDP_BFPT_ID		0xff00u	/* Basic Flash Parameter Table */
+
+#define SFDP_SIGNATURE		0x50444653u
+#define SFDP_JESD216_MAJOR	1
+#define SFDP_JESD216_MINOR	0
+#define SFDP_JESD216A_MINOR	5
+#define SFDP_JESD216B_MINOR	6
+
+struct sfdp_header {
+	u32		signature; /* Ox50444653 <=> "SFDP" */
+	u8		minor;
+	u8		major;
+	u8		nph; /* 0-base number of parameter headers */
+	u8		unused;
+
+	/* Basic Flash Parameter Table. */
+	struct sfdp_parameter_header	bfpt_header;
+};
+
+/* Basic Flash Parameter Table */
+
+/*
+ * JESD216B defines a Basic Flash Parameter Table of 16 DWORDs.
+ * They are indexed from 1 but C arrays are indexed from 0.
+ */
+enum sfdp_bfpt_dword {
+	BFPT_DWORD1 = 0,
+	BFPT_DWORD2,
+	BFPT_DWORD3,
+	BFPT_DWORD4,
+	BFPT_DWORD5,
+	BFPT_DWORD6,
+	BFPT_DWORD7,
+	BFPT_DWORD8,
+	BFPT_DWORD9,
+	BFPT_DWORD10,
+	BFPT_DWORD11,
+	BFPT_DWORD12,
+	BFPT_DWORD13,
+	BFPT_DWORD14,
+	BFPT_DWORD15,
+	BFPT_DWORD16,
+
+	BFPT_DWORD_MAX
+};
+
+/* The first revision of JESB216 defined only 9 DWORDs. */
+#define BFPT_DWORD_MAX_JESD216			9
+
+/* 1st DWORD. */
+#define BFPT_DWORD1_FAST_READ_1_1_2		BIT(16)
+#define BFPT_DWORD1_ADDRESS_BYTES_MASK		GENMASK(18, 17)
+#define BFPT_DWORD1_ADDRESS_BYTES_3_ONLY	(0u << 17)
+#define BFPT_DWORD1_ADDRESS_BYTES_3_OR_4	(1u << 17)
+#define BFPT_DWORD1_ADDRESS_BYTES_4_ONLY	(2u << 17)
+#define BFPT_DWORD1_DTR				BIT(19)
+#define BFPT_DWORD1_FAST_READ_1_2_2		BIT(20)
+#define BFPT_DWORD1_FAST_READ_1_4_4		BIT(21)
+#define BFPT_DWORD1_FAST_READ_1_1_4		BIT(22)
+
+/* 5th DWORD. */
+#define BFPT_DWORD5_FAST_READ_2_2_2		BIT(0)
+#define BFPT_DWORD5_FAST_READ_4_4_4		BIT(4)
+
+/* 11th DWORD. */
+#define BFPT_DWORD11_PAGE_SIZE_SHIFT		4
+#define BFPT_DWORD11_PAGE_SIZE_MASK		GENMASK(7, 4)
+
+/* 15th DWORD. */
+
+/*
+ * (from JESD216B)
+ * Quad Enable Requirements (QER):
+ * - 000b: Device does not have a QE bit. Device detects 1-1-4 and 1-4-4
+ *         reads based on instruction. DQ3/HOLD# functions are hold during
+ *         instruction pahse.
+ * - 001b: QE is bit 1 of status register 2. It is set via Write Status with
+ *         two data bytes where bit 1 of the second byte is one.
+ *         [...]
+ *         Writing only one byte to the status register has the side-effect of
+ *         clearing status register 2, including the QE bit. The 100b code is
+ *         used if writing one byte to the status register does not modify
+ *         status register 2.
+ * - 010b: QE is bit 6 of status register 1. It is set via Write Status with
+ *         one data byte where bit 6 is one.
+ *         [...]
+ * - 011b: QE is bit 7 of status register 2. It is set via Write status
+ *         register 2 instruction 3Eh with one data byte where bit 7 is one.
+ *         [...]
+ *         The status register 2 is read using instruction 3Fh.
+ * - 100b: QE is bit 1 of status register 2. It is set via Write Status with
+ *         two data bytes where bit 1 of the second byte is one.
+ *         [...]
+ *         In contrast to the 001b code, writing one byte to the status
+ *         register does not modify status register 2.
+ * - 101b: QE is bit 1 of status register 2. Status register 1 is read using
+ *         Read Status instruction 05h. Status register2 is read using
+ *         instruction 35h. QE is set via Writ Status instruction 01h with
+ *         two data bytes where bit 1 of the second byte is one.
+ *         [...]
+ */
+#define BFPT_DWORD15_QER_MASK			GENMASK(22, 20)
+#define BFPT_DWORD15_QER_NONE			(0u << 20) /* Micron */
+#define BFPT_DWORD15_QER_SR2_BIT1_BUGGY		(1u << 20)
+#define BFPT_DWORD15_QER_SR1_BIT6		(2u << 20) /* Macronix */
+#define BFPT_DWORD15_QER_SR2_BIT7		(3u << 20)
+#define BFPT_DWORD15_QER_SR2_BIT1_NO_RD		(4u << 20)
+#define BFPT_DWORD15_QER_SR2_BIT1		(5u << 20) /* Spansion */
+
+
+struct sfdp_bfpt {
+	u32	dwords[BFPT_DWORD_MAX];
+};
+
+/* Fast Read settings. */
+
+static inline void
+spi_nor_set_read_settings_from_bfpt(struct spi_nor_read_command *read,
+				    u16 half,
+				    enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = (half >> 5) & 0x07u;
+	read->num_wait_states = (half >> 0) & 0x1Fu;
+	read->opcode = (half >> 8) & 0xFFu;
+	read->proto = proto;
+}
+
+struct sfdp_bfpt_read {
+	/* The Fast Read x-y-z hardware capability in params->hwcaps.mask. */
+	u32				hwcaps;
+
+	/*
+	 * The <supported_bit> bit in <supported_dword> BFPT DWORD tells us
+	 * whether the Fast Read x-y-z command is supported.
+	 */
+	enum sfdp_bfpt_dword		supported_dword;
+	u32				supported_bit;
+
+	/*
+	 * The half-word at offset <setting_shift> in <setting_dword> BFPT DWORD
+	 * encodes the op code, the number of mode clocks and the number of wait
+	 * states to be used by Fast Read x-y-z command.
+	 */
+	enum sfdp_bfpt_dword		settings_dword;
+	int				settings_shift;
+
+	/* The SPI protocol for this Fast Read x-y-z command. */
+	enum spi_nor_protocol		proto;
+};
+
+static const struct sfdp_bfpt_read sfdp_bfpt_reads[] = {
+	/* Fast Read 1-1-2 */
+	{
+		SNOR_HWCAPS_READ_1_1_2,
+		BFPT_DWORD1, BIT(16),	/* Supported bit */
+		BFPT_DWORD4, 0,		/* Settings */
+		SNOR_PROTO_1_1_2,
+	},
+
+	/* Fast Read 1-2-2 */
+	{
+		SNOR_HWCAPS_READ_1_2_2,
+		BFPT_DWORD1, BIT(20),	/* Supported bit */
+		BFPT_DWORD4, 16,	/* Settings */
+		SNOR_PROTO_1_2_2,
+	},
+
+	/* Fast Read 2-2-2 */
+	{
+		SNOR_HWCAPS_READ_2_2_2,
+		BFPT_DWORD5,  BIT(0),	/* Supported bit */
+		BFPT_DWORD6, 16,	/* Settings */
+		SNOR_PROTO_2_2_2,
+	},
+
+	/* Fast Read 1-1-4 */
+	{
+		SNOR_HWCAPS_READ_1_1_4,
+		BFPT_DWORD1, BIT(22),	/* Supported bit */
+		BFPT_DWORD3, 16,	/* Settings */
+		SNOR_PROTO_1_1_4,
+	},
+
+	/* Fast Read 1-4-4 */
+	{
+		SNOR_HWCAPS_READ_1_4_4,
+		BFPT_DWORD1, BIT(21),	/* Supported bit */
+		BFPT_DWORD3, 0,		/* Settings */
+		SNOR_PROTO_1_4_4,
+	},
+
+	/* Fast Read 4-4-4 */
+	{
+		SNOR_HWCAPS_READ_4_4_4,
+		BFPT_DWORD5, BIT(4),	/* Supported bit */
+		BFPT_DWORD7, 16,	/* Settings */
+		SNOR_PROTO_4_4_4,
+	},
+};
+
+
+/* Sector Erase settings. */
+
+static inline void
+spi_nor_set_erase_command_from_bfpt(struct spi_nor_erase_command *cmd,
+				    u16 half)
+{
+	u32 size = (half >> 0) & 0xff;
+	u8 opcode = (half >> 8) & 0xff;
+
+	/* size == 0 means this Erase Type is not supported. */
+	if (size)
+		size = (1u << size);
+	spi_nor_set_erase_command(cmd, size, opcode);
+}
+
+struct sfdp_bfpt_erase {
+	/*
+	 * The half-word at offset <shift> in DWORD <dword> encodes the
+	 * op code and erase sector size to be used by Sector Erase commands.
+	 */
+	enum sfdp_bfpt_dword	dword;
+	int			shift;
+};
+
+static const struct sfdp_bfpt_erase sfdp_bfpt_erases[SNOR_CMD_ERASE_MAX] = {
+	/* Erase Type 1 in DWORD8 bits[15:0] */
+	{BFPT_DWORD8, 0},
+
+	/* Erase Type 2 in DWORD8 bits[31:16] */
+	{BFPT_DWORD8, 16},
+
+	/* Erase Type 3 in DWORD9 bits[15:0] */
+	{BFPT_DWORD9, 0},
+
+	/* Erase Type 4: in DWORD9 bits[31:16] */
+	{BFPT_DWORD9, 16},
+};
+
+
+static int spi_nor_hwcaps2cmd(u32 hwcaps);
+
+static int spi_nor_parse_bfpt(struct spi_nor *nor,
+			      const struct sfdp_parameter_header *bfpt_header,
+			      struct spi_nor_flash_parameter *params)
+{
+	struct spi_nor_erase_map *map = &nor->erase_map;
+	struct sfdp_bfpt bfpt;
+	size_t len;
+	int i, cmd, err;
+	u32 addr, erase_mask;
+	u16 half;
+
+	/* JESD216 Basic Flash Parameter Table length is at least 9 DWORDs. */
+	if (bfpt_header->length < BFPT_DWORD_MAX_JESD216)
+		return -EINVAL;
+
+	/* Read the Basic Flash Parameter Table. */
+	len = min_t(size_t, sizeof(bfpt),
+		    bfpt_header->length * sizeof(uint32_t));
+	addr = SFDP_PARAM_HEADER_PTP(bfpt_header);
+	memset(&bfpt, 0, sizeof(bfpt));
+	err = spi_nor_read_sfdp(nor,  addr, len, &bfpt);
+	if (err)
+		return err;
+
+	/* Fix endianness of the BFPT DWORDs. */
+	for (i = 0; i < BFPT_DWORD_MAX; i++)
+		bfpt.dwords[i] = le32_to_cpu(bfpt.dwords[i]);
+
+	/* Flash Memory Density (in bits). */
+	params->size = bfpt.dwords[BFPT_DWORD2];
+	if (params->size & BIT(31)) {
+		params->size &= ~BIT(31);
+		params->size = 1ULL << params->size;
+	}
+	params->size >>= 3; /* Convert to bytes. */
+
+	/* Fast Read settings. */
+	for (i = 0; i < ARRAY_SIZE(sfdp_bfpt_reads); i++) {
+		const struct sfdp_bfpt_read *rd = &sfdp_bfpt_reads[i];
+		struct spi_nor_read_command *read;
+
+		if (!(bfpt.dwords[rd->supported_dword] & rd->supported_bit))
+			continue;
+
+		params->hwcaps.mask |= rd->hwcaps;
+		cmd = spi_nor_hwcaps2cmd(rd->hwcaps);
+		read = &params->reads[cmd];
+		half = bfpt.dwords[rd->settings_dword] >> rd->settings_shift;
+		spi_nor_set_read_settings_from_bfpt(read, half, rd->proto);
+	}
+
+	/* Sector Erase settings. */
+	erase_mask = 0;
+	for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+		const struct sfdp_bfpt_erase *er = &sfdp_bfpt_erases[i];
+
+		half = bfpt.dwords[er->dword] >> er->shift;
+		spi_nor_set_erase_command_from_bfpt(&map->commands[i], half);
+
+		if (map->commands[i].size)
+			erase_mask |= BIT(i);
+	}
+	spi_nor_init_uniform_erase_map(map, erase_mask, params->size);
+
+	/* Stop here if not JESD216 rev A or later. */
+	if (bfpt_header->length < BFPT_DWORD_MAX)
+		return 0;
+
+	/* Page size: this field specifies 'N' so the page size = 2^N bytes. */
+	params->page_size = bfpt.dwords[BFPT_DWORD11];
+	params->page_size &= BFPT_DWORD11_PAGE_SIZE_MASK;
+	params->page_size >>= BFPT_DWORD11_PAGE_SIZE_SHIFT;
+	params->page_size = (1u << params->page_size);
+
+	/* Enable Quad I/O. */
+	switch (bfpt.dwords[BFPT_DWORD15] & BFPT_DWORD15_QER_MASK) {
+	default:
+	case BFPT_DWORD15_QER_NONE:
+		break;
+
+	case BFPT_DWORD15_QER_SR2_BIT1_BUGGY:
+	case BFPT_DWORD15_QER_SR2_BIT1_NO_RD:
+		params->quad_enable = spansion_quad_enable;
+		break;
+
+	case BFPT_DWORD15_QER_SR1_BIT6:
+		params->quad_enable = macronix_quad_enable;
+		break;
+
+	case BFPT_DWORD15_QER_SR2_BIT7:
+		params->quad_enable = sr2_bit7_quad_enable;
+		break;
+
+	case BFPT_DWORD15_QER_SR2_BIT1:
+		params->quad_enable = spansion_new_quad_enable;
+		break;
+	}
+
+	return 0;
+}
+
+static int spi_nor_parse_sfdp(struct spi_nor *nor,
+			      struct spi_nor_flash_parameter *params)
+{
+	const struct sfdp_parameter_header *param_header, *bfpt_header;
+	struct sfdp_parameter_header *param_headers = NULL;
+	struct sfdp_header header;
+	size_t psize;
+	int i, err;
+
+	/* Get the SFDP header. */
+	err = spi_nor_read_sfdp(nor, 0, sizeof(header), &header);
+	if (err)
+		return err;
+
+	/* Check the SFDP header version. */
+	if (le32_to_cpu(header.signature) != SFDP_SIGNATURE ||
+	    header.major != SFDP_JESD216_MAJOR ||
+	    header.minor < SFDP_JESD216_MINOR)
+		return -EINVAL;
+
+	/*
+	 * Verify that the first and only mandatory parameter header is a
+	 * Basic Flash Parameter Table header as specified in JESD216.
+	 */
+	bfpt_header = &header.bfpt_header;
+	if (SFDP_PARAM_HEADER_ID(bfpt_header) != SFDP_BFPT_ID ||
+	    bfpt_header->major != SFDP_JESD216_MAJOR)
+		return -EINVAL;
+
+	/* Allocate memory for parameter headers. */
+	if (header.nph) {
+		psize = header.nph * sizeof(*param_headers);
+
+		param_headers = kmalloc(psize, GFP_KERNEL);
+		if (!param_headers) {
+			dev_err(nor->dev,
+				"failed to allocate memory for SFDP parameter headers\n");
+			return -ENOMEM;
+		}
+
+		err = spi_nor_read_sfdp(nor, sizeof(header),
+					psize, param_headers);
+		if (err) {
+			dev_err(nor->dev,
+				"failed to read SFDP parameter headers\n");
+			goto exit;
+		}
+	}
+
+	/*
+	 * Check other parameter headers to get the latest revision of
+	 * the basic flash parameter table.
+	 */
+	for (i = 0; i < header.nph; i++) {
+		param_header = &param_headers[i];
+
+		if (SFDP_PARAM_HEADER_ID(param_header) == SFDP_BFPT_ID &&
+		    param_header->major == SFDP_JESD216_MAJOR &&
+		    (param_header->minor > bfpt_header->minor ||
+		     (param_header->minor == bfpt_header->minor &&
+		      param_header->length > bfpt_header->length)))
+			bfpt_header = param_header;
+	}
+	err = spi_nor_parse_bfpt(nor, bfpt_header, params);
+	if (err)
+		goto exit;
+
+exit:
+	kfree(param_headers);
+	return err;
+}
+
 static int spi_nor_init_params(struct spi_nor *nor,
 			       const struct flash_info *info,
 			       struct spi_nor_flash_parameter *params)
@@ -1834,6 +2388,10 @@ static int spi_nor_init_params(struct spi_nor *nor,
 		}
 	}
 
+	/* Override the parameters with data read from SFDP tables. */
+	if (!(info->flags & SPI_NOR_SKIP_SFDP))
+		spi_nor_parse_sfdp(nor, params);
+
 	return 0;
 }
 
include/linux/mtd/spi-nor.h +6 -0
index c12cafe99bee..a0d21a973d60 100644
@@ -41,6 +41,8 @@
 #define SPINOR_OP_WREN		0x06	/* Write enable */
 #define SPINOR_OP_RDSR		0x05	/* Read status register */
 #define SPINOR_OP_WRSR		0x01	/* Write status register 1 byte */
+#define SPINOR_OP_RDSR2		0x3f	/* Read status register 2 */
+#define SPINOR_OP_WRSR2		0x3e	/* Write status register 2 */
 #define SPINOR_OP_READ		0x03	/* Read data bytes (low frequency) */
 #define SPINOR_OP_READ_FAST	0x0b	/* Read data bytes (high frequency) */
 #define SPINOR_OP_READ_1_1_2	0x3b	/* Read data bytes (Dual Output SPI) */
@@ -56,6 +58,7 @@
 #define SPINOR_OP_CHIP_ERASE	0xc7	/* Erase whole flash chip */
 #define SPINOR_OP_SE		0xd8	/* Sector erase (usually 64KiB) */
 #define SPINOR_OP_RDID		0x9f	/* Read JEDEC ID */
+#define SPINOR_OP_RDSFDP	0x5a	/* Read SFDP */
 #define SPINOR_OP_RDCR		0x35	/* Read configuration register */
 #define SPINOR_OP_RDFSR		0x70	/* Read flag status register */
 
@@ -128,6 +131,9 @@
 /* Configuration Register bits. */
 #define CR_QUAD_EN_SPAN		BIT(1)	/* Spansion Quad I/O */
 
+/* Status Register 2 bits. */
+#define SR2_QUAD_EN_BIT7	BIT(7)
+
 
 /* Supported SPI protocols */
 #define SNOR_PROTO_WIDTH_MASK	GENMASK(7, 0)
Signature · 1 line
2.9.3
#22 lore raw

Re: [RFC PATCH v5 5/6] mtd: spi-nor: parse Serial Flash Discoverable Parameters (SFDP) tables

Quoted 4 lines · Cyrille Pitchen

This patch adds support to the JESD216B standard and parses the SFDP tables to dynamically initialize the 'struct spi_nor_flash_parameter'.

Hi, mostly nits below.

Quoted 65 lines
---
 drivers/mtd/spi-nor/spi-nor.c | 558 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/mtd/spi-nor.h   |   6 +
 2 files changed, 564 insertions(+)
drivers/mtd/spi-nor/spi-nor.c +38 -0
index 2e54792d506d..ce8722055a9c 100644
@@ -17,6 +17,7 @@
 #include <linux/mutex.h>
 #include <linux/math64.h>
 #include <linux/sizes.h>
+#include <linux/slab.h>
 
 #include <linux/mtd/mtd.h>
 #include <linux/of_platform.h>
@@ -86,6 +87,7 @@ struct flash_info {
 					 * to support memory size above 128Mib.
 					 */
 #define NO_CHIP_ERASE		BIT(12) /* Chip does not support chip erase */
+#define SPI_NOR_SKIP_SFDP	BIT(13)	/* Skip parsing of SFDP tables */
 };
 
 #define JEDEC_MFR(info)	((info)->id[0])
@@ -1593,6 +1595,99 @@ static int spansion_quad_enable(struct spi_nor *nor)
 	return 0;
 }
 
+static int spansion_new_quad_enable(struct spi_nor *nor)
+{
+	u8 sr_cr[2];
+	int ret;
+
+	/* Check current Quad Enable bit value. */
+	ret = read_cr(nor);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while reading configuration register\n");
+		return -EINVAL;
+	}
+	sr_cr[1] = ret;
+	if (sr_cr[1] & CR_QUAD_EN_SPAN)
+		return 0;
+
+	dev_info(nor->dev, "setting Spansion Quad Enable (non-volatile) bit\n");
+
+	/* Keep the current value of the Status Register. */
+	ret = read_sr(nor);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while reading status register\n");
+		return -EINVAL;
+	}
+	sr_cr[0] = ret;
+	sr_cr[1] |= CR_QUAD_EN_SPAN;
+
+	write_enable(nor);
+
+	ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while writing configuration register\n");
+		return -EINVAL;
+	}

drivers/mtd/spi-nor/spi-nor.c static int spansion_quad_enable(struct spi_nor *nor) · line 1643

Quoted 10 lines
drivers/mtd/spi-nor/spi-nor.c +10 -0
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret < 0) {
+		dev_err(nor->dev, "error while waiting for WRSR completion\n");
+		return ret;
+	}
+
+	/* read back and check it */
+	ret = read_cr(nor);
+	if (!(ret > 0 && (ret & CR_QUAD_EN_SPAN))) {

Nit, you might want to align this with sr2_bit7_quad_enable() below, that is

if (ret || !(ret & CR_QUAD_ENABLE_SPAN))
...

Quoted 84 lines
drivers/mtd/spi-nor/spi-nor.c +77 -0
+		dev_err(nor->dev, "Spansion Quad bit not set\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sr2_bit7_quad_enable(struct spi_nor *nor)
+{
+	u8 sr2;
+	int ret;
+
+	/* Check current Quad Enable bit value. */
+	ret = nor->read_reg(nor, SPINOR_OP_RDSR2, &sr2, 1);
+	if (ret)
+		return ret;
+	if (sr2 & SR2_QUAD_EN_BIT7)
+		return 0;
+
+	/* Update the Quad Enable bit. */
+	sr2 |= SR2_QUAD_EN_BIT7;
+
+	write_enable(nor);
+
+	ret = nor->write_reg(nor, SPINOR_OP_WRSR2, &sr2, 1);
+	if (ret < 0) {
+		dev_err(nor->dev,
+			"error while writing status register 2\n");
+		return -EINVAL;
+	}
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret < 0) {
+		dev_err(nor->dev, "error while waiting for WRSR2 completion\n");
+		return ret;
+	}
+
+	/* Read back and check it. */
+	ret = nor->read_reg(nor, SPINOR_OP_RDSR2, &sr2, 1);
+	if (ret || !(sr2 & SR2_QUAD_EN_BIT7)) {
+		dev_err(nor->dev, "SR2 Quad bit not set\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int spi_nor_check(struct spi_nor *nor)
 {
 	if (!nor->dev || !nor->read || !nor->write ||
@@ -1759,6 +1854,465 @@ spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map,
 	map->uniform_region.size = flash_size;
 }
 
+
+/*
+ * SFDP parsing.
+ */
+
+static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr,
+			     size_t len, void *buf)
+{
+	u8 addr_width, read_opcode, read_dummy;
+	int ret;
+
+	read_opcode = nor->read_opcode;
+	addr_width = nor->addr_width;
+	read_dummy = nor->read_dummy;
+
+	nor->read_opcode = SPINOR_OP_RDSFDP;
+	nor->addr_width = 3;
+	nor->read_dummy = 8;
+
+	ret = nor->read(nor, addr, len, (u8 *)buf);
+
+	nor->read_opcode = read_opcode;
+	nor->addr_width = addr_width;
+	nor->read_dummy = read_dummy;
+
+	return (ret < 0) ? ret : 0;
+}
+
+struct sfdp_parameter_header {
+	u8		id_lsb;

drivers/mtd/spi-nor/spi-nor.c spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, · line 1896

Quoted 10 lines
drivers/mtd/spi-nor/spi-nor.c +10 -0
+	u8		minor;
+	u8		major;
+	u8		length; /* in double words */
+	u8		parameter_table_pointer[3]; /* byte address */
+	u8		id_msb;
+};
+
+#define SFDP_PARAM_HEADER_ID(p)	((u16)(((p)->id_msb << 8) | (p)->id_lsb))
+#define SFDP_PARAM_HEADER_PTP(p) \
+	((u32)(((p)->parameter_table_pointer[2] << 16) | \

Is the u32 cast needed ? And the u16 one above ?

Quoted 20 lines
drivers/mtd/spi-nor/spi-nor.c +20 -0
+	       ((p)->parameter_table_pointer[1] <<  8) | \
+	       ((p)->parameter_table_pointer[0] <<  0)))
+
+
+#define SFDP_BFPT_ID		0xff00u	/* Basic Flash Parameter Table */
+
+#define SFDP_SIGNATURE		0x50444653u
+#define SFDP_JESD216_MAJOR	1
+#define SFDP_JESD216_MINOR	0
+#define SFDP_JESD216A_MINOR	5
+#define SFDP_JESD216B_MINOR	6
+
+struct sfdp_header {
+	u32		signature; /* Ox50444653 <=> "SFDP" */
+	u8		minor;
+	u8		major;
+	u8		nph; /* 0-base number of parameter headers */
+	u8		unused;
+
+	/* Basic Flash Parameter Table. */

drivers/mtd/spi-nor/spi-nor.c spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, · line 1926

Quoted 10 lines
drivers/mtd/spi-nor/spi-nor.c +10 -0
+	struct sfdp_parameter_header	bfpt_header;
+};
+
+/* Basic Flash Parameter Table */
+
+/*
+ * JESD216B defines a Basic Flash Parameter Table of 16 DWORDs.
+ * They are indexed from 1 but C arrays are indexed from 0.
+ */
+enum sfdp_bfpt_dword {

Is this really useful at all ?

Quoted 86 lines
drivers/mtd/spi-nor/spi-nor.c +86 -0
+	BFPT_DWORD1 = 0,
+	BFPT_DWORD2,
+	BFPT_DWORD3,
+	BFPT_DWORD4,
+	BFPT_DWORD5,
+	BFPT_DWORD6,
+	BFPT_DWORD7,
+	BFPT_DWORD8,
+	BFPT_DWORD9,
+	BFPT_DWORD10,
+	BFPT_DWORD11,
+	BFPT_DWORD12,
+	BFPT_DWORD13,
+	BFPT_DWORD14,
+	BFPT_DWORD15,
+	BFPT_DWORD16,
+
+	BFPT_DWORD_MAX
+};
+
+/* The first revision of JESB216 defined only 9 DWORDs. */
+#define BFPT_DWORD_MAX_JESD216			9
+
+/* 1st DWORD. */
+#define BFPT_DWORD1_FAST_READ_1_1_2		BIT(16)
+#define BFPT_DWORD1_ADDRESS_BYTES_MASK		GENMASK(18, 17)
+#define BFPT_DWORD1_ADDRESS_BYTES_3_ONLY	(0u << 17)
+#define BFPT_DWORD1_ADDRESS_BYTES_3_OR_4	(1u << 17)
+#define BFPT_DWORD1_ADDRESS_BYTES_4_ONLY	(2u << 17)
+#define BFPT_DWORD1_DTR				BIT(19)
+#define BFPT_DWORD1_FAST_READ_1_2_2		BIT(20)
+#define BFPT_DWORD1_FAST_READ_1_4_4		BIT(21)
+#define BFPT_DWORD1_FAST_READ_1_1_4		BIT(22)
+
+/* 5th DWORD. */
+#define BFPT_DWORD5_FAST_READ_2_2_2		BIT(0)
+#define BFPT_DWORD5_FAST_READ_4_4_4		BIT(4)
+
+/* 11th DWORD. */
+#define BFPT_DWORD11_PAGE_SIZE_SHIFT		4
+#define BFPT_DWORD11_PAGE_SIZE_MASK		GENMASK(7, 4)
+
+/* 15th DWORD. */
+
+/*
+ * (from JESD216B)
+ * Quad Enable Requirements (QER):
+ * - 000b: Device does not have a QE bit. Device detects 1-1-4 and 1-4-4
+ *         reads based on instruction. DQ3/HOLD# functions are hold during
+ *         instruction pahse.
+ * - 001b: QE is bit 1 of status register 2. It is set via Write Status with
+ *         two data bytes where bit 1 of the second byte is one.
+ *         [...]
+ *         Writing only one byte to the status register has the side-effect of
+ *         clearing status register 2, including the QE bit. The 100b code is
+ *         used if writing one byte to the status register does not modify
+ *         status register 2.
+ * - 010b: QE is bit 6 of status register 1. It is set via Write Status with
+ *         one data byte where bit 6 is one.
+ *         [...]
+ * - 011b: QE is bit 7 of status register 2. It is set via Write status
+ *         register 2 instruction 3Eh with one data byte where bit 7 is one.
+ *         [...]
+ *         The status register 2 is read using instruction 3Fh.
+ * - 100b: QE is bit 1 of status register 2. It is set via Write Status with
+ *         two data bytes where bit 1 of the second byte is one.
+ *         [...]
+ *         In contrast to the 001b code, writing one byte to the status
+ *         register does not modify status register 2.
+ * - 101b: QE is bit 1 of status register 2. Status register 1 is read using
+ *         Read Status instruction 05h. Status register2 is read using
+ *         instruction 35h. QE is set via Writ Status instruction 01h with
+ *         two data bytes where bit 1 of the second byte is one.
+ *         [...]
+ */
+#define BFPT_DWORD15_QER_MASK			GENMASK(22, 20)
+#define BFPT_DWORD15_QER_NONE			(0u << 20) /* Micron */
+#define BFPT_DWORD15_QER_SR2_BIT1_BUGGY		(1u << 20)
+#define BFPT_DWORD15_QER_SR1_BIT6		(2u << 20) /* Macronix */
+#define BFPT_DWORD15_QER_SR2_BIT7		(3u << 20)
+#define BFPT_DWORD15_QER_SR2_BIT1_NO_RD		(4u << 20)
+#define BFPT_DWORD15_QER_SR2_BIT1		(5u << 20) /* Spansion */
+
+
+struct sfdp_bfpt {
+	u32	dwords[BFPT_DWORD_MAX];

drivers/mtd/spi-nor/spi-nor.c spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, · line 2022

Quoted 10 lines
drivers/mtd/spi-nor/spi-nor.c +10 -0
+};
+
+/* Fast Read settings. */
+
+static inline void
+spi_nor_set_read_settings_from_bfpt(struct spi_nor_read_command *read,
+				    u16 half,
+				    enum spi_nor_protocol proto)
+{
+	read->num_mode_clocks = (half >> 5) & 0x07u;

Is this u in 0x07u really needed here ?

Quoted 5 lines
drivers/mtd/spi-nor/spi-nor.c +5 -0
+	read->num_wait_states = (half >> 0) & 0x1Fu;
+	read->opcode = (half >> 8) & 0xFFu;
+	read->proto = proto;
+}
+

[...]

Quoted 27 lines
drivers/mtd/spi-nor/spi-nor.c +27 -0
+static int spi_nor_parse_sfdp(struct spi_nor *nor,
+			      struct spi_nor_flash_parameter *params)
+{
+	const struct sfdp_parameter_header *param_header, *bfpt_header;
+	struct sfdp_parameter_header *param_headers = NULL;
+	struct sfdp_header header;
+	size_t psize;
+	int i, err;
+
+	/* Get the SFDP header. */
+	err = spi_nor_read_sfdp(nor, 0, sizeof(header), &header);
+	if (err)
+		return err;
+
+	/* Check the SFDP header version. */
+	if (le32_to_cpu(header.signature) != SFDP_SIGNATURE ||
+	    header.major != SFDP_JESD216_MAJOR ||
+	    header.minor < SFDP_JESD216_MINOR)
+		return -EINVAL;
+
+	/*
+	 * Verify that the first and only mandatory parameter header is a
+	 * Basic Flash Parameter Table header as specified in JESD216.
+	 */
+	bfpt_header = &header.bfpt_header;
+	if (SFDP_PARAM_HEADER_ID(bfpt_header) != SFDP_BFPT_ID ||
+	    bfpt_header->major != SFDP_JESD216_MAJOR)

drivers/mtd/spi-nor/spi-nor.c spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, · line 2280

Quoted 10 lines
drivers/mtd/spi-nor/spi-nor.c +10 -0
+		return -EINVAL;
+
+	/* Allocate memory for parameter headers. */
+	if (header.nph) {
+		psize = header.nph * sizeof(*param_headers);
+
+		param_headers = kmalloc(psize, GFP_KERNEL);
+		if (!param_headers) {
+			dev_err(nor->dev,
+				"failed to allocate memory for SFDP parameter headers\n");

If malloc in kernel fails, you will most likely not be able to print anything because that printing will also need to allocate memory somewhere down the line. Nuke these prints , they are useless.

Quoted 39 lines
drivers/mtd/spi-nor/spi-nor.c +35 -0
+			return -ENOMEM;
+		}
+
+		err = spi_nor_read_sfdp(nor, sizeof(header),
+					psize, param_headers);
+		if (err) {
+			dev_err(nor->dev,
+				"failed to read SFDP parameter headers\n");
+			goto exit;
+		}
+	}
+
+	/*
+	 * Check other parameter headers to get the latest revision of
+	 * the basic flash parameter table.
+	 */
+	for (i = 0; i < header.nph; i++) {
+		param_header = &param_headers[i];
+
+		if (SFDP_PARAM_HEADER_ID(param_header) == SFDP_BFPT_ID &&
+		    param_header->major == SFDP_JESD216_MAJOR &&
+		    (param_header->minor > bfpt_header->minor ||
+		     (param_header->minor == bfpt_header->minor &&
+		      param_header->length > bfpt_header->length)))
+			bfpt_header = param_header;
+	}
+	err = spi_nor_parse_bfpt(nor, bfpt_header, params);
+	if (err)
+		goto exit;
+
+exit:
+	kfree(param_headers);
+	return err;
+}
+
 static int spi_nor_init_params(struct spi_nor *nor,
 			       const struct flash_info *info,
 			       struct spi_nor_flash_parameter *params)

drivers/mtd/spi-nor/spi-nor.c static int spi_nor_init_params(struct spi_nor *nor, · line 2393

Quoted 6 lines
drivers/mtd/spi-nor/spi-nor.c +3 -0
@@ -1834,6 +2388,10 @@ static int spi_nor_init_params(struct spi_nor *nor,
 		}
 	}
 
+	/* Override the parameters with data read from SFDP tables. */
+	if (!(info->flags & SPI_NOR_SKIP_SFDP))
+		spi_nor_parse_sfdp(nor, params);

This returns int, handle the retval.

Quoted 36 lines
drivers/mtd/spi-nor/spi-nor.c +1 -0
+
 	return 0;
 }
 
include/linux/mtd/spi-nor.h +6 -0
index c12cafe99bee..a0d21a973d60 100644
@@ -41,6 +41,8 @@
 #define SPINOR_OP_WREN		0x06	/* Write enable */
 #define SPINOR_OP_RDSR		0x05	/* Read status register */
 #define SPINOR_OP_WRSR		0x01	/* Write status register 1 byte */
+#define SPINOR_OP_RDSR2		0x3f	/* Read status register 2 */
+#define SPINOR_OP_WRSR2		0x3e	/* Write status register 2 */
 #define SPINOR_OP_READ		0x03	/* Read data bytes (low frequency) */
 #define SPINOR_OP_READ_FAST	0x0b	/* Read data bytes (high frequency) */
 #define SPINOR_OP_READ_1_1_2	0x3b	/* Read data bytes (Dual Output SPI) */
@@ -56,6 +58,7 @@
 #define SPINOR_OP_CHIP_ERASE	0xc7	/* Erase whole flash chip */
 #define SPINOR_OP_SE		0xd8	/* Sector erase (usually 64KiB) */
 #define SPINOR_OP_RDID		0x9f	/* Read JEDEC ID */
+#define SPINOR_OP_RDSFDP	0x5a	/* Read SFDP */
 #define SPINOR_OP_RDCR		0x35	/* Read configuration register */
 #define SPINOR_OP_RDFSR		0x70	/* Read flag status register */
 
@@ -128,6 +131,9 @@
 /* Configuration Register bits. */
 #define CR_QUAD_EN_SPAN		BIT(1)	/* Spansion Quad I/O */
 
+/* Status Register 2 bits. */
+#define SR2_QUAD_EN_BIT7	BIT(7)
+
 
 /* Supported SPI protocols */
 #define SNOR_PROTO_WIDTH_MASK	GENMASK(7, 0)
Signature · 2 lines
Best regards,
Marek Vasut
#23 lore raw

RFC PATCH v5 6/6 mtd: spi-nor: parse SFDP 4-byte Address Instruction Table

In reply to Cyrille Pitchen

This patch adds supports for SFDP (JESD216B) 4-byte Address Instruction Table. This table is optional but when available, we parse it to get the 4-byte address op codes supported by the memory.
Using these op codes is stateless as opposed to entering the 4-byte address mode or setting the Base Address Register (BAR).

---
 drivers/mtd/spi-nor/spi-nor.c | 166 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 165 insertions(+), 1 deletion(-)
drivers/mtd/spi-nor/spi-nor.c +165 -1
index ce8722055a9c..ea044efc4e6d 100644
@@ -1899,6 +1899,7 @@ struct sfdp_parameter_header {
 
 
 #define SFDP_BFPT_ID		0xff00u	/* Basic Flash Parameter Table */
+#define SFDP_4BAIT_ID		0xff84u	/* 4-byte Address Instruction Table */
 
 #define SFDP_SIGNATURE		0x50444653u
 #define SFDP_JESD216_MAJOR	1
@@ -2241,6 +2242,149 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 	return 0;
 }
 
+struct sfdp_4bait {
+	/* The hardware capability. */
+	u32		hwcaps;
+
+	/*
+	 * The <supported_bit> bit in DWORD1 of the 4BAIT tells us whether
+	 * the associated 4-byte address op code is supported.
+	 */
+	u32		supported_bit;
+};
+
+static int spi_nor_parse_4bait(struct spi_nor *nor,
+			       const struct sfdp_parameter_header *param_header,
+			       struct spi_nor_flash_parameter *params)
+{
+	static const struct sfdp_4bait reads[] = {
+		{ SNOR_HWCAPS_READ,		BIT(0) },
+		{ SNOR_HWCAPS_READ_FAST,	BIT(1) },
+		{ SNOR_HWCAPS_READ_1_1_2,	BIT(2) },
+		{ SNOR_HWCAPS_READ_1_2_2,	BIT(3) },
+		{ SNOR_HWCAPS_READ_1_1_4,	BIT(4) },
+		{ SNOR_HWCAPS_READ_1_4_4,	BIT(5) },
+		{ SNOR_HWCAPS_READ_1_1_1_DTR,	BIT(13) },
+		{ SNOR_HWCAPS_READ_1_2_2_DTR,	BIT(14) },
+		{ SNOR_HWCAPS_READ_1_4_4_DTR,	BIT(15) },
+	};
+	static const struct sfdp_4bait programs[] = {
+		{ SNOR_HWCAPS_PP,		BIT(6) },
+		{ SNOR_HWCAPS_PP_1_1_4,		BIT(7) },
+		{ SNOR_HWCAPS_PP_1_4_4,		BIT(8) },
+	};
+	static const struct sfdp_4bait erases[SNOR_CMD_ERASE_MAX] = {
+		{ 0u /* not used */,		BIT(9) },
+		{ 0u /* not used */,		BIT(10) },
+		{ 0u /* not used */,		BIT(11) },
+		{ 0u /* not used */,		BIT(12) },
+	};
+	u32 dwords[2], addr, discard_hwcaps, read_hwcaps, pp_hwcaps, erase_mask;
+	struct spi_nor_erase_map *map = &nor->erase_map;
+	int i, err;
+
+	if (param_header->major != SFDP_JESD216_MAJOR ||
+	    param_header->length < ARRAY_SIZE(dwords))
+		return -EINVAL;
+
+	/* Read the 4-byte Address Instruction Table. */
+	addr = SFDP_PARAM_HEADER_PTP(param_header);
+	err = spi_nor_read_sfdp(nor, addr, sizeof(dwords), dwords);
+	if (err)
+		return err;
+
+	/* Fix endianness of the 4BAIT DWORDs. */
+	for (i = 0; i < ARRAY_SIZE(dwords); i++)
+		dwords[i] = le32_to_cpu(dwords[i]);
+
+	/*
+	 * Compute the subset of (Fast) Read commands for which the 4-byte
+	 * version is supported.
+	 */
+	discard_hwcaps = 0;
+	read_hwcaps = 0;
+	for (i = 0; i < ARRAY_SIZE(reads); i++) {
+		const struct sfdp_4bait *read = &reads[i];
+
+		discard_hwcaps |= read->hwcaps;
+		if ((params->hwcaps.mask & read->hwcaps) &&
+		    (dwords[0] & read->supported_bit))
+			read_hwcaps |= read->hwcaps;
+	}
+
+	/*
+	 * Compute the subset of Page Program commands for which the 4-byte
+	 * version is supported.
+	 */
+	pp_hwcaps = 0;
+	for (i = 0; i < ARRAY_SIZE(programs); i++) {
+		const struct sfdp_4bait *program = &programs[i];
+
+		discard_hwcaps |= program->hwcaps;
+		if ((params->hwcaps.mask & program->hwcaps) &&
+		    (dwords[0] & program->supported_bit))
+			pp_hwcaps |= program->hwcaps;
+	}
+
+	/*
+	 * Compute the subet of Sector Erase commands for which the 4-byte
+	 * version is supported.
+	 */
+	erase_mask = 0;
+	for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+		const struct sfdp_4bait *erase = &erases[i];
+
+		if ((map->commands[i].size > 0) &&
+		    (dwords[0] & erase->supported_bit))
+			erase_mask |= BIT(i);
+	}
+
+	/*
+	 * We need at least one 4-byte op code per read, program and erase
+	 * operation; the .read(), .write() and .erase() hooks share the
+	 * nor->addr_width value.
+	 */
+	if (!read_hwcaps || !pp_hwcaps || !erase_mask)
+		return 0;
+
+	/*
+	 * Discard all operations from the 4-byte instruction set which are
+	 * not supported by this memory.
+	 */
+	params->hwcaps.mask &= ~discard_hwcaps;
+	params->hwcaps.mask |= (read_hwcaps | pp_hwcaps);
+
+	/* Use the 4-byte address instruction set. */
+	for (i = 0; i < SNOR_CMD_READ_MAX; i++) {
+		struct spi_nor_read_command *read_cmd = &params->reads[i];
+
+		read_cmd->opcode = spi_nor_convert_3to4_read(read_cmd->opcode);
+	}
+	for (i = 0; i < SNOR_CMD_PP_MAX; i++) {
+		struct spi_nor_pp_command *pp_cmd = &params->page_programs[i];
+
+		pp_cmd->opcode = spi_nor_convert_3to4_program(pp_cmd->opcode);
+	}
+	for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+		struct spi_nor_erase_command *erase_cmd = &map->commands[i];
+
+		if (erase_mask & BIT(i))
+			erase_cmd->opcode = (dwords[1] >> (i * 8)) & 0xFF;
+		else
+			spi_nor_set_erase_command(erase_cmd, 0u, 0xFF);
+	}
+
+	/*
+	 * We set nor->addr_width here to skip spi_nor_set_4byte_opcodes()
+	 * later because this latest function implements a legacy quirk for
+	 * the erase size of Spansion memory. However this quirk is no longer
+	 * needed with new SFDP compliant memories.
+	 */
+	nor->addr_width = 4;
+	nor->flags |= SNOR_F_4B_OPCODES;
+	return 0;
+}
+
 static int spi_nor_parse_sfdp(struct spi_nor *nor,
 			      struct spi_nor_flash_parameter *params)
 {
@@ -2308,6 +2452,23 @@ static int spi_nor_parse_sfdp(struct spi_nor *nor,
 	if (err)
 		goto exit;
 
+	/* Parse other parameter headers. */
+	for (i = 0; i < header.nph; i++) {
+		param_header = &param_headers[i];
+
+		switch (SFDP_PARAM_HEADER_ID(param_header)) {
+		case SFDP_4BAIT_ID:
+			err = spi_nor_parse_4bait(nor, param_header, params);
+			break;
+
+		default:
+			break;
+		}
+
+		if (err)
+			goto exit;
+	}
+
 exit:
 	kfree(param_headers);
 	return err;
@@ -2885,7 +3046,10 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
 	if (ret)
 		return ret;
 
-	if (info->addr_width)
+	if (nor->addr_width)
+		/* already configured by spi_nor_setup() */
+		;
+	else if (info->addr_width)
 		nor->addr_width = info->addr_width;
 	else if (mtd->size > 0x1000000) {
 		/* enable 4-byte addressing if the device exceeds 16MiB */
Signature · 1 line
2.9.3
#24 lore raw

Re: [RFC PATCH v5 6/6] mtd: spi-nor: parse SFDP 4-byte Address Instruction Table

Quoted 86 lines · Cyrille Pitchen

This patch adds supports for SFDP (JESD216B) 4-byte Address Instruction Table. This table is optional but when available, we parse it to get the 4-byte address op codes supported by the memory.
Using these op codes is stateless as opposed to entering the 4-byte address mode or setting the Base Address Register (BAR).

---
 drivers/mtd/spi-nor/spi-nor.c | 166 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 165 insertions(+), 1 deletion(-)
drivers/mtd/spi-nor/spi-nor.c +60 -0
index ce8722055a9c..ea044efc4e6d 100644
@@ -1899,6 +1899,7 @@ struct sfdp_parameter_header {
 
 
 #define SFDP_BFPT_ID		0xff00u	/* Basic Flash Parameter Table */
+#define SFDP_4BAIT_ID		0xff84u	/* 4-byte Address Instruction Table */
 
 #define SFDP_SIGNATURE		0x50444653u
 #define SFDP_JESD216_MAJOR	1
@@ -2241,6 +2242,149 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 	return 0;
 }
 
+struct sfdp_4bait {
+	/* The hardware capability. */
+	u32		hwcaps;
+
+	/*
+	 * The <supported_bit> bit in DWORD1 of the 4BAIT tells us whether
+	 * the associated 4-byte address op code is supported.
+	 */
+	u32		supported_bit;
+};
+
+static int spi_nor_parse_4bait(struct spi_nor *nor,
+			       const struct sfdp_parameter_header *param_header,
+			       struct spi_nor_flash_parameter *params)
+{
+	static const struct sfdp_4bait reads[] = {
+		{ SNOR_HWCAPS_READ,		BIT(0) },
+		{ SNOR_HWCAPS_READ_FAST,	BIT(1) },
+		{ SNOR_HWCAPS_READ_1_1_2,	BIT(2) },
+		{ SNOR_HWCAPS_READ_1_2_2,	BIT(3) },
+		{ SNOR_HWCAPS_READ_1_1_4,	BIT(4) },
+		{ SNOR_HWCAPS_READ_1_4_4,	BIT(5) },
+		{ SNOR_HWCAPS_READ_1_1_1_DTR,	BIT(13) },
+		{ SNOR_HWCAPS_READ_1_2_2_DTR,	BIT(14) },
+		{ SNOR_HWCAPS_READ_1_4_4_DTR,	BIT(15) },
+	};
+	static const struct sfdp_4bait programs[] = {
+		{ SNOR_HWCAPS_PP,		BIT(6) },
+		{ SNOR_HWCAPS_PP_1_1_4,		BIT(7) },
+		{ SNOR_HWCAPS_PP_1_4_4,		BIT(8) },
+	};
+	static const struct sfdp_4bait erases[SNOR_CMD_ERASE_MAX] = {
+		{ 0u /* not used */,		BIT(9) },
+		{ 0u /* not used */,		BIT(10) },
+		{ 0u /* not used */,		BIT(11) },
+		{ 0u /* not used */,		BIT(12) },
+	};
+	u32 dwords[2], addr, discard_hwcaps, read_hwcaps, pp_hwcaps, erase_mask;
+	struct spi_nor_erase_map *map = &nor->erase_map;
+	int i, err;
+
+	if (param_header->major != SFDP_JESD216_MAJOR ||
+	    param_header->length < ARRAY_SIZE(dwords))
+		return -EINVAL;
+
+	/* Read the 4-byte Address Instruction Table. */
+	addr = SFDP_PARAM_HEADER_PTP(param_header);
+	err = spi_nor_read_sfdp(nor, addr, sizeof(dwords), dwords);
+	if (err)
+		return err;
+
+	/* Fix endianness of the 4BAIT DWORDs. */
+	for (i = 0; i < ARRAY_SIZE(dwords); i++)
+		dwords[i] = le32_to_cpu(dwords[i]);
+
+	/*
+	 * Compute the subset of (Fast) Read commands for which the 4-byte
+	 * version is supported.
+	 */

drivers/mtd/spi-nor/spi-nor.c static int spi_nor_parse_bfpt(struct spi_nor *nor, · line 2313

Quoted 10 lines
drivers/mtd/spi-nor/spi-nor.c +10 -0
+	discard_hwcaps = 0;
+	read_hwcaps = 0;
+	for (i = 0; i < ARRAY_SIZE(reads); i++) {
+		const struct sfdp_4bait *read = &reads[i];
+
+		discard_hwcaps |= read->hwcaps;
+		if ((params->hwcaps.mask & read->hwcaps) &&
+		    (dwords[0] & read->supported_bit))
+			read_hwcaps |= read->hwcaps;
+	}

Looks like there is a bit of repeated stuff here, maybe this can be pulled out ?

Quoted 101 lines
drivers/mtd/spi-nor/spi-nor.c +90 -0
+	/*
+	 * Compute the subset of Page Program commands for which the 4-byte
+	 * version is supported.
+	 */
+	pp_hwcaps = 0;
+	for (i = 0; i < ARRAY_SIZE(programs); i++) {
+		const struct sfdp_4bait *program = &programs[i];
+
+		discard_hwcaps |= program->hwcaps;
+		if ((params->hwcaps.mask & program->hwcaps) &&
+		    (dwords[0] & program->supported_bit))
+			pp_hwcaps |= program->hwcaps;
+	}
+
+	/*
+	 * Compute the subet of Sector Erase commands for which the 4-byte
+	 * version is supported.
+	 */
+	erase_mask = 0;
+	for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+		const struct sfdp_4bait *erase = &erases[i];
+
+		if ((map->commands[i].size > 0) &&
+		    (dwords[0] & erase->supported_bit))
+			erase_mask |= BIT(i);
+	}
+
+	/*
+	 * We need at least one 4-byte op code per read, program and erase
+	 * operation; the .read(), .write() and .erase() hooks share the
+	 * nor->addr_width value.
+	 */
+	if (!read_hwcaps || !pp_hwcaps || !erase_mask)
+		return 0;
+
+	/*
+	 * Discard all operations from the 4-byte instruction set which are
+	 * not supported by this memory.
+	 */
+	params->hwcaps.mask &= ~discard_hwcaps;
+	params->hwcaps.mask |= (read_hwcaps | pp_hwcaps);
+
+	/* Use the 4-byte address instruction set. */
+	for (i = 0; i < SNOR_CMD_READ_MAX; i++) {
+		struct spi_nor_read_command *read_cmd = &params->reads[i];
+
+		read_cmd->opcode = spi_nor_convert_3to4_read(read_cmd->opcode);
+	}
+	for (i = 0; i < SNOR_CMD_PP_MAX; i++) {
+		struct spi_nor_pp_command *pp_cmd = &params->page_programs[i];
+
+		pp_cmd->opcode = spi_nor_convert_3to4_program(pp_cmd->opcode);
+	}
+	for (i = 0; i < SNOR_CMD_ERASE_MAX; i++) {
+		struct spi_nor_erase_command *erase_cmd = &map->commands[i];
+
+		if (erase_mask & BIT(i))
+			erase_cmd->opcode = (dwords[1] >> (i * 8)) & 0xFF;
+		else
+			spi_nor_set_erase_command(erase_cmd, 0u, 0xFF);
+	}
+
+	/*
+	 * We set nor->addr_width here to skip spi_nor_set_4byte_opcodes()
+	 * later because this latest function implements a legacy quirk for
+	 * the erase size of Spansion memory. However this quirk is no longer
+	 * needed with new SFDP compliant memories.
+	 */
+	nor->addr_width = 4;
+	nor->flags |= SNOR_F_4B_OPCODES;
+	return 0;
+}
+
 static int spi_nor_parse_sfdp(struct spi_nor *nor,
 			      struct spi_nor_flash_parameter *params)
 {
@@ -2308,6 +2452,23 @@ static int spi_nor_parse_sfdp(struct spi_nor *nor,
 	if (err)
 		goto exit;
 
+	/* Parse other parameter headers. */
+	for (i = 0; i < header.nph; i++) {
+		param_header = &param_headers[i];
+
+		switch (SFDP_PARAM_HEADER_ID(param_header)) {
+		case SFDP_4BAIT_ID:
+			err = spi_nor_parse_4bait(nor, param_header, params);
+			break;
+
+		default:
+			break;
+		}
+
+		if (err)
+			goto exit;
+	}
+
 exit:
 	kfree(param_headers);
 	return err;

drivers/mtd/spi-nor/spi-nor.c int spi_nor_scan(struct spi_nor *nor, const char *name, · line 3051

Quoted 7 lines
drivers/mtd/spi-nor/spi-nor.c +3 -1
@@ -2885,7 +3046,10 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
 	if (ret)
 		return ret;
 
-	if (info->addr_width)
+	if (nor->addr_width)
+		/* already configured by spi_nor_setup() */
+		;

I think you can drop this part.

Quoted 5 lines
drivers/mtd/spi-nor/spi-nor.c +1 -0
+	else if (info->addr_width)
 		nor->addr_width = info->addr_width;
 	else if (mtd->size > 0x1000000) {
 		/* enable 4-byte addressing if the device exceeds 16MiB */
Signature · 2 lines
Best regards,
Marek Vasut
#25 lore raw
In reply to Cyrille Pitchen

Hi all,

Quoted 39 lines · Cyrille Pitchen

Hi all,

based on git-hub/spi-nor and likely applicable on linux-next within the next few days.

This new series of patchs aims to upgrade to spi-nor framework. We need to take into account latest SPI memories which cannot be handled correctly by the current implementation.

For instance, SPI NOR memories like Spansion S25FS512S or Macronix MX25U4035 support only the Fast Read 1-4-4 (EBh) command but not the Fast Read 1-1-4 (6Bh) command. However the current spi-nor framework supports only Fast Read 1-1-4 (6Bh).

Also the Spansion S25FS512S memory (and others) may use a non-uniform sector erase map, whereas the spi-nor framework assumes that a single sector erase size and opcode can be used anywhere inside the data array. This assumption is no longer valid.

Then parsing SFDP tables is an attempt to solve many of those issues by discovering dynamically most of the parameters and settings of the SPI NOR memory:
- the flash size
- the page size for Page Program commands
- the supported Fast Read commands with the associated opcodes and number of mode/wait-state (dummy) cycles.
- the supported Sector/Block Erase commands with the associated opcodes and sizes.
- the erase sector map (for non-uniform memory).

Besides, most QSPI controllers from the different vendors are capable to support the SPI 1-2-2 and 1-4-4 protocols but the spi-nor framework was not ready to use them. This series also fixes this issue and computes the best match between the hardware capabilies of both the SPI memory and the SPI controler (master) to select the right opcodes and dummy cycles for Fast Read and Page Program operations.

Quoted 8 lines

The new 'struct spi_nor_hwcaps' uses a bitmask to describe all the supported hardware capabilies and makes the difference between Fast Read and Page Program operations and also between the different SPI protocols (SPI 1-1-2 vs SPI 1-2-2 or SPI 1-1-4 vs SPI 1-4-4).

IMHO, the first 3 patches of this series are ready to be merged into the github/spi-nor tree. Marek, do you agree with that?

Marek, if you have no comment on them, I will merge patches 1, 2, 3 in the spi-nor tree within the next few days. Hence, people like Cédric waiting for the series to merged could base their work on these patches.

Best regards,

Cyrille

Quoted 94 lines

Artur, patch 1 introduces some basic support to Octal SPI to help you with your series. About the op code values, I would like to wait for some JEDEC specification to provide the reference values or at least the datasheets of different vendors to guess the actual standard. For now, IMHO, only one manufacturer is not enough.

Kamal, if you don't mind I've put your Signed-off-by in patch 3 since the original patch is from you so I wanted to give you the credits :) I've just fixed some issue: in the original version, set4_byte() was called from spi_nor_init() even if (nor->addr_width == 3).
I hope it will help you with the suspend/resume series.

The 3 last patches are RFC and are provided so it's more easy for reviewers to understand the direction I would like to follow to upgrade the spi-nor framework.

Marcin, I haven't finished yet another 7th patch to parse the sector erase map table from SFDP so for now patch 4 is almost useless. Besides I only tested it with uniform memories to avoid regressions. I may need your help to test it with non-uniform memories, if you don't mind :)

Best regards,

Cyrille

ChangeLog:

v4 -> v5
- rework the whole series.
- introduce support for Octo SPI protocols.
- introduce support for Double Transfer Rate (DTR) protocols.
- introduce support for memories with non-uniform sector erase sizes.

v3 -> v4
- replace dev_info() by dev_dbg() in patch 1.
- split former patch 2 into 2 patches:
+ new patch 2 deals with the rename of SPINOR_OP_READ4_* macros
+ new patch 3 deals with the alternative methode to support memory

Quoted 1 line

16MiB

- add comment in patch 3 to describe the dichotomic search performed by spi_nor_convert_opcode().
- change return type from int to void for m25p80_proto2nbits() in patch 6.
- remove former patches 8 & 9 from the v2 series: the support of the Macronix mx66l1g45g memory will be sent in a separated patch.

v2 -> v3
- tested with new samples: Micron n25q512, n25q01g and Macronix mx25v1635f, mx25l3235f, mx25l3273f.
- add "Reviewed-by: Jagan Teki <[email protected]>" on patch 1.
- add "Tested-by: Vignesh R <[email protected]>" on patch 2.
- fix some checkpatch warnings.
- add call of spi_nor_wait_till_ready() in spansion_new_quad_enable() and sr2_bit7_quad_enable(), as suggested by Joel Esponde on patch 6.
- test JESD216 rev A (minor 5) instead of rev B (minor 6) with the return code of spi_nor_parse_sfdp() from spi_nor_init_params() on patch 6. The seven additional DWORDs of the Basic Flash Parameter Table were introduced in rev A, not rev B, so the 15th DWORD was already available in rev A. The 15th DWORD provides us with the Quad Enable Requirements (QER) bits.
Basic Flash Parameter Table size:
+ JESD216 : 9 DWORDS
+ JESD216A: 16 DWORDS
+ JESD216B: 16 DWORDS

v1 -> v2
- fix patch 3 to resolve compiler errors on hisi-sfc.c and cadence-quadspi.c drivers

Cyrille Pitchen (6):
mtd: spi-nor: introduce more SPI protocols and the Dual Transfer Mode
mtd: m25p80: add support of SPI 1-2-2 and 1-4-4 protocols
mtd: spi-nor: add spi_nor_init() function
mtd: spi-nor: add support to non-uniform SPI NOR flash memories
mtd: spi-nor: parse Serial Flash Discoverable Parameters (SFDP) tables
mtd: spi-nor: parse SFDP 4-byte Address Instruction Table

 drivers/mtd/devices/m25p80.c          |  130 ++-
 drivers/mtd/spi-nor/aspeed-smc.c      |   23 +-
 drivers/mtd/spi-nor/atmel-quadspi.c   |   80 +-
 drivers/mtd/spi-nor/cadence-quadspi.c |   18 +-
 drivers/mtd/spi-nor/fsl-quadspi.c     |    8 +-
 drivers/mtd/spi-nor/hisi-sfc.c        |   31 +-
 drivers/mtd/spi-nor/intel-spi.c       |    7 +-
 drivers/mtd/spi-nor/mtk-quadspi.c     |   16 +-
 drivers/mtd/spi-nor/nxp-spifi.c       |   22 +-
 drivers/mtd/spi-nor/spi-nor.c         | 1722 +++++++++++++++++++++++++++++----
 include/linux/mtd/spi-nor.h           |  228 ++++-
 11 files changed, 1983 insertions(+), 302 deletions(-)
#26 lore raw
Quoted 48 lines · Cyrille Pitchen

Hi all,

Quoted 45 lines · Cyrille Pitchen

Hi all,

based on git-hub/spi-nor and likely applicable on linux-next within the next few days.

This new series of patchs aims to upgrade to spi-nor framework. We need to take into account latest SPI memories which cannot be handled correctly by the current implementation.

For instance, SPI NOR memories like Spansion S25FS512S or Macronix MX25U4035 support only the Fast Read 1-4-4 (EBh) command but not the Fast Read 1-1-4 (6Bh) command. However the current spi-nor framework supports only Fast Read 1-1-4 (6Bh).

Also the Spansion S25FS512S memory (and others) may use a non-uniform sector erase map, whereas the spi-nor framework assumes that a single sector erase size and opcode can be used anywhere inside the data array. This assumption is no longer valid.

Then parsing SFDP tables is an attempt to solve many of those issues by discovering dynamically most of the parameters and settings of the SPI NOR memory:
- the flash size
- the page size for Page Program commands
- the supported Fast Read commands with the associated opcodes and number of mode/wait-state (dummy) cycles.
- the supported Sector/Block Erase commands with the associated opcodes and sizes.
- the erase sector map (for non-uniform memory).

Besides, most QSPI controllers from the different vendors are capable to support the SPI 1-2-2 and 1-4-4 protocols but the spi-nor framework was not ready to use them. This series also fixes this issue and computes the best match between the hardware capabilies of both the SPI memory and the SPI controler (master) to select the right opcodes and dummy cycles for Fast Read and Page Program operations.

The new 'struct spi_nor_hwcaps' uses a bitmask to describe all the supported hardware capabilies and makes the difference between Fast Read and Page Program operations and also between the different SPI protocols (SPI 1-1-2 vs SPI 1-2-2 or SPI 1-1-4 vs SPI 1-4-4).

Quoted 6 lines
Quoted 2 lines

IMHO, the first 3 patches of this series are ready to be merged into the github/spi-nor tree. Marek, do you agree with that?

Marek, if you have no comment on them, I will merge patches 1, 2, 3 in the spi-nor tree within the next few days. Hence, people like Cédric waiting for the series to merged could base their work on these patches.

Hm well, you could've at least poked me about this, I was so snowed under that I didn't have any chance to take a look :(

Signature · 2 lines
Best regards,
Marek Vasut