[PATCH 4/4] mtd: spi-nor: move fixups to manufacturer drivers

Takahiro Kuwano via B4 Relay devnull+takahiro.kuwano.infineon.com at kernel.org
Fri Jun 12 07:40:52 CEST 2026


From: Takahiro Kuwano <takahiro.kuwano at infineon.com>

Move chip specific fixups from core to manufacturer drivers.
As manufacturer drivers are used in tiny as well, all fixups migrated
from core need to be excluded in CONFIG_SPI_FLASH_TINY.

Some helper functions used in fixups are exposed from core and some need
to be guarded by CONFIG_SPI_FLASH_TINY to avoid name conflict with local
functions in tiny.

Signed-off-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>
---
 drivers/mtd/spi/macronix.c     |  89 ++++
 drivers/mtd/spi/micron-st.c    |  99 +++++
 drivers/mtd/spi/sf_internal.h  |  10 +
 drivers/mtd/spi/spansion.c     | 627 +++++++++++++++++++++++++++
 drivers/mtd/spi/spi-nor-core.c | 929 +----------------------------------------
 drivers/mtd/spi/winbond.c      | 104 +++++
 6 files changed, 938 insertions(+), 920 deletions(-)

diff --git a/drivers/mtd/spi/macronix.c b/drivers/mtd/spi/macronix.c
index 87843978cad..9ca55fc4f99 100644
--- a/drivers/mtd/spi/macronix.c
+++ b/drivers/mtd/spi/macronix.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
  */
 
+#include <dm/device_compat.h>
 #include <linux/mtd/spi-nor.h>
 #include "sf_internal.h"
 
@@ -59,7 +60,95 @@ static const struct flash_info macronix_nor_parts[] = {
 	{ INFO("mx25uw6345g",    0xc28437, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_OCTAL_DTR_READ | SPI_NOR_4B_OPCODES) },
 };
 
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+/**
+ * spi_nor_macronix_octal_dtr_enable() - Enable octal DTR on Macronix flashes.
+ * @nor:	pointer to a 'struct spi_nor'
+ *
+ * Set Macronix max dummy cycles 20 to allow the flash to run at fastest frequency.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_macronix_octal_dtr_enable(struct spi_nor *nor)
+{
+	struct spi_mem_op op;
+	int ret;
+	u8 buf;
+
+	ret = write_enable(nor);
+	if (ret)
+		return ret;
+
+	buf = SPINOR_REG_MXIC_DC_20;
+	op = (struct spi_mem_op)
+		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_CR2, 1),
+			   SPI_MEM_OP_ADDR(4, SPINOR_REG_MXIC_CR2_DC, 1),
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
+
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret)
+		return ret;
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret)
+		return ret;
+
+	nor->read_dummy = MXIC_MAX_DC;
+	ret = write_enable(nor);
+	if (ret)
+		return ret;
+
+	buf = SPINOR_REG_MXIC_OPI_DTR_EN;
+	op = (struct spi_mem_op)
+		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_CR2, 1),
+			   SPI_MEM_OP_ADDR(4, SPINOR_REG_MXIC_CR2_MODE, 1),
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
+
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret) {
+		dev_err(nor->dev, "Failed to enable octal DTR mode\n");
+		return ret;
+	}
+	nor->reg_proto = SNOR_PROTO_8_8_8_DTR;
+
+	return 0;
+}
+
+static void macronix_octal_late_init(struct spi_nor *nor,
+				     struct spi_nor_flash_parameter *params)
+{
+	nor->octal_dtr_enable = spi_nor_macronix_octal_dtr_enable;
+}
+
+static void macronix_octal_post_sfdp_fixup(struct spi_nor *nor,
+					 struct spi_nor_flash_parameter *params)
+{
+	/*
+	 * Adding SNOR_HWCAPS_PP_8_8_8_DTR in hwcaps.mask when
+	 * SPI_NOR_OCTAL_DTR_READ flag exists.
+	 */
+	if (params->hwcaps.mask & SNOR_HWCAPS_READ_8_8_8_DTR)
+		params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
+}
+
+static struct spi_nor_fixups macronix_octal_fixups = {
+	.post_sfdp = macronix_octal_post_sfdp_fixup,
+	.late_init = macronix_octal_late_init,
+};
+
+static void macronix_set_fixups(struct spi_nor *nor)
+{
+	if (nor->info->flags & SPI_NOR_OCTAL_DTR_READ)
+		nor->fixups = &macronix_octal_fixups;
+}
+#endif
+
 const struct spi_nor_manufacturer spi_nor_macronix = {
 	.parts = macronix_nor_parts,
 	.nparts = ARRAY_SIZE(macronix_nor_parts),
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+	.set_fixups = macronix_set_fixups,
+#endif
 };
diff --git a/drivers/mtd/spi/micron-st.c b/drivers/mtd/spi/micron-st.c
index 2d47f1c60c0..9982e9d660a 100644
--- a/drivers/mtd/spi/micron-st.c
+++ b/drivers/mtd/spi/micron-st.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
  */
 
+#include <dm/device_compat.h>
 #include <linux/mtd/spi-nor.h>
 #include "sf_internal.h"
 
@@ -59,9 +60,107 @@ static const struct flash_info st_nor_parts[] = {
 	{ INFO("m25px64",    0x207117,  0, 64 * 1024, 128, 0) },
 };
 
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY) && CONFIG_IS_ENABLED(SPI_FLASH_MT35XU)
+static int spi_nor_micron_octal_dtr_enable(struct spi_nor *nor)
+{
+	struct spi_mem_op op;
+	u8 buf;
+	u8 addr_width = 3;
+	int ret;
+
+	/* Set dummy cycles for Fast Read to the default of 20. */
+	ret = write_enable(nor);
+	if (ret)
+		return ret;
+
+	buf = 20;
+	op = (struct spi_mem_op)
+		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1),
+			   SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_MT_CFR1V, 1),
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret)
+		return ret;
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret)
+		return ret;
+
+	nor->read_dummy = 20;
+
+	ret = write_enable(nor);
+	if (ret)
+		return ret;
+
+	buf = SPINOR_MT_OCT_DTR;
+	op = (struct spi_mem_op)
+		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1),
+			   SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_MT_CFR0V, 1),
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret) {
+		dev_err(nor->dev, "Failed to enable octal DTR mode\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void mt35xu512aba_late_init(struct spi_nor *nor,
+				   struct spi_nor_flash_parameter *params)
+{
+	nor->octal_dtr_enable = spi_nor_micron_octal_dtr_enable;
+}
+
+static void mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor,
+					 struct spi_nor_flash_parameter *params)
+{
+	/* Set the Fast Read settings. */
+	params->hwcaps.mask |= SNOR_HWCAPS_READ_8_8_8_DTR;
+	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_8_8_8_DTR],
+				  0, 20, SPINOR_OP_MT_DTR_RD,
+				  SNOR_PROTO_8_8_8_DTR);
+
+	params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
+
+	nor->cmd_ext_type = SPI_NOR_EXT_REPEAT;
+	params->rdsr_dummy = 8;
+	params->rdsr_addr_nbytes = 0;
+
+	/*
+	 * SCCR Map 22nd DWORD does not indicate DTR Octal Mode Enable
+	 * for MT35XU512ABA but is actually supported by device.
+	 */
+	nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE;
+
+	/*
+	 * The BFPT quad enable field is set to a reserved value so the quad
+	 * enable function is ignored by spi_nor_parse_bfpt(). Make sure we
+	 * disable it.
+	 */
+	params->quad_enable = NULL;
+}
+
+static struct spi_nor_fixups mt35xu512aba_fixups = {
+	.post_sfdp = mt35xu512aba_post_sfdp_fixup,
+	.late_init = mt35xu512aba_late_init,
+};
+
+static void micron_set_fixups(struct spi_nor *nor)
+{
+	if (!strcmp(nor->info->name, "mt35xu512aba"))
+		nor->fixups = &mt35xu512aba_fixups;
+}
+#endif
+
 const struct spi_nor_manufacturer spi_nor_micron = {
 	.parts = micron_nor_parts,
 	.nparts = ARRAY_SIZE(micron_nor_parts),
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY) && CONFIG_IS_ENABLED(SPI_FLASH_MT35XU)
+	.set_fixups = micron_set_fixups,
+#endif
 };
 
 const struct spi_nor_manufacturer spi_nor_st = {
diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h
index 941729f5707..0ea19913701 100644
--- a/drivers/mtd/spi/sf_internal.h
+++ b/drivers/mtd/spi/sf_internal.h
@@ -202,6 +202,12 @@ static inline int spi_nor_parse_sfdp(struct spi_nor *nor,
 #endif
 
 #if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+int write_enable(struct spi_nor *nor);
+int set_4byte(struct spi_nor *nor, const struct flash_info *info, int enable);
+int spi_nor_sr_ready(struct spi_nor *nor);
+int spi_nor_wait_till_ready(struct spi_nor *nor);
+int spi_nor_read_write_reg(struct spi_nor *nor, struct spi_mem_op *op,
+			   void *buf);
 void spi_nor_set_read_settings(struct spi_nor_read_command *read,
 			       u8 num_mode_clocks,
 			       u8 num_wait_states,
@@ -211,6 +217,10 @@ int spansion_read_cr_quad_enable(struct spi_nor *nor);
 int macronix_quad_enable(struct spi_nor *nor);
 #endif
 
+void spi_nor_set_pp_settings(struct spi_nor_pp_command *pp, u8 opcode,
+			     enum spi_nor_protocol proto);
+int spi_nor_default_setup(struct spi_nor *nor, const struct flash_info *info,
+			  const struct spi_nor_flash_parameter *params);
 int spi_nor_hwcaps_read2cmd(u32 hwcaps);
 int spansion_no_read_cr_quad_enable(struct spi_nor *nor);
 
diff --git a/drivers/mtd/spi/spansion.c b/drivers/mtd/spi/spansion.c
index 405f5bf5e79..1dd38667062 100644
--- a/drivers/mtd/spi/spansion.c
+++ b/drivers/mtd/spi/spansion.c
@@ -5,6 +5,8 @@
  * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
  */
 
+#include <dm/device_compat.h>
+#include <linux/sizes.h>
 #include <linux/mtd/spi-nor.h>
 #include "sf_internal.h"
 
@@ -67,7 +69,632 @@ static const struct flash_info spansion_nor_parts[] = {
 #endif
 };
 
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+
+/* Use ID byte 4 to distinguish S25FS256T and S25Hx-T */
+#define S25FS256T_ID4	(0x08)
+
+/* Number of dummy cycle for Read Any Register (RDAR) op. */
+#define S25FS_S_RDAR_DUMMY	8
+
+static int spansion_read_any_reg(struct spi_nor *nor, u32 addr, u8 dummy,
+				 u8 *val)
+{
+	struct spi_mem_op op =
+		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 0),
+			   SPI_MEM_OP_ADDR(nor->addr_mode_nbytes, addr, 0),
+			   SPI_MEM_OP_DUMMY(dummy, 0),
+			   SPI_MEM_OP_DATA_IN(1, NULL, 0));
+	u8 buf[2];
+	int ret;
+
+	spi_nor_setup_op(nor, &op, nor->reg_proto);
+
+	/*
+	 * In Octal DTR mode, the number of address bytes is always 4 regardless
+	 * of addressing mode setting.
+	 */
+	if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR)
+		op.addr.nbytes = 4;
+
+	/*
+	 * We don't want to read only one byte in DTR mode. So, read 2 and then
+	 * discard the second byte.
+	 */
+	if (spi_nor_protocol_is_dtr(nor->reg_proto))
+		op.data.nbytes = 2;
+
+	ret = spi_nor_read_write_reg(nor, &op, buf);
+	if (ret)
+		return ret;
+
+	*val = buf[0];
+
+	return 0;
+}
+
+static int spansion_write_any_reg(struct spi_nor *nor, u32 addr, u8 val)
+{
+	struct spi_mem_op op =
+		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1),
+			   SPI_MEM_OP_ADDR(nor->addr_mode_nbytes, addr, 1),
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_DATA_OUT(1, NULL, 1));
+
+	return spi_nor_read_write_reg(nor, &op, &val);
+}
+
+/**
+ * spansion_quad_enable_volatile() - enable Quad I/O mode in volatile register.
+ * @nor:	pointer to a 'struct spi_nor'
+ * @addr_base:	base address of register (can be >0 in multi-die parts)
+ * @dummy:	number of dummy cycles for register read
+ *
+ * It is recommended to update volatile registers in the field application due
+ * to a risk of the non-volatile registers corruption by power interrupt. This
+ * function sets Quad Enable bit in CFR1 volatile.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spansion_quad_enable_volatile(struct spi_nor *nor, u32 addr_base,
+					 u8 dummy)
+{
+	u32 addr = addr_base + SPINOR_REG_CYPRESS_CFR1V;
+
+	u8 cr;
+	int ret;
+
+	/* Check current Quad Enable bit value. */
+	ret = spansion_read_any_reg(nor, addr, dummy, &cr);
+	if (ret < 0) {
+		dev_dbg(nor->dev,
+			"error while reading configuration register\n");
+		return -EINVAL;
+	}
+
+	if (cr & CR_QUAD_EN_SPAN)
+		return 0;
+
+	cr |= CR_QUAD_EN_SPAN;
+
+	write_enable(nor);
+
+	ret = spansion_write_any_reg(nor, addr, cr);
+
+	if (ret < 0) {
+		dev_dbg(nor->dev,
+			"error while writing configuration register\n");
+		return -EINVAL;
+	}
+
+	/* Read back and check it. */
+	ret = spansion_read_any_reg(nor, addr, dummy, &cr);
+	if (ret || !(cr & CR_QUAD_EN_SPAN)) {
+		dev_dbg(nor->dev, "Spansion Quad bit not set\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * Read status register 1 by using Read Any Register command to support multi
+ * die package parts.
+ */
+static int spansion_sr_ready(struct spi_nor *nor, u32 addr_base, u8 dummy)
+{
+	u32 reg_addr = addr_base + SPINOR_REG_CYPRESS_STR1V;
+	u8 sr;
+	int ret;
+
+	ret = spansion_read_any_reg(nor, reg_addr, dummy, &sr);
+	if (ret < 0)
+		return ret;
+
+	if (sr & (SR_E_ERR | SR_P_ERR)) {
+		if (sr & SR_E_ERR)
+			dev_dbg(nor->dev, "Erase Error occurred\n");
+		else
+			dev_dbg(nor->dev, "Programming Error occurred\n");
+
+		nor->write_reg(nor, SPINOR_OP_CYPRESS_CLPEF, NULL, 0);
+		return -EIO;
+	}
+
+	return !(sr & SR_WIP);
+}
+
+/**
+ * spansion_erase_non_uniform() - erase non-uniform sectors for Spansion/Cypress
+ *                                chips
+ * @nor:	pointer to a 'struct spi_nor'
+ * @addr:	address of the sector to erase
+ * @opcode_4k:	opcode for 4K sector erase
+ * @ovlsz_top:	size of overlaid portion at the top address
+ * @ovlsz_btm:	size of overlaid portion at the bottom address
+ *
+ * Erase an address range on the nor chip that can contain 4KB sectors overlaid
+ * on top and/or bottom. The appropriate erase opcode and size are chosen by
+ * address to erase and size of overlaid portion.
+ *
+ * Return: number of bytes erased on success, -errno otherwise.
+ */
+static int spansion_erase_non_uniform(struct spi_nor *nor, u32 addr,
+				      u8 opcode_4k, u32 ovlsz_top,
+				      u32 ovlsz_btm)
+{
+	struct spi_mem_op op =
+		SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 0),
+			   SPI_MEM_OP_ADDR(nor->addr_width, addr, 0),
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_NO_DATA);
+	struct mtd_info *mtd = &nor->mtd;
+	u32 erasesize;
+	int ret;
+
+	/* 4KB sectors */
+	if (op.addr.val < ovlsz_btm ||
+	    op.addr.val >= mtd->size - ovlsz_top) {
+		op.cmd.opcode = opcode_4k;
+		erasesize = SZ_4K;
+
+	/* Non-overlaid portion in the normal sector at the bottom */
+	} else if (op.addr.val == ovlsz_btm) {
+		op.cmd.opcode = nor->erase_opcode;
+		erasesize = mtd->erasesize - ovlsz_btm;
+
+	/* Non-overlaid portion in the normal sector at the top */
+	} else if (op.addr.val == mtd->size - mtd->erasesize) {
+		op.cmd.opcode = nor->erase_opcode;
+		erasesize = mtd->erasesize - ovlsz_top;
+
+	/* Normal sectors */
+	} else {
+		op.cmd.opcode = nor->erase_opcode;
+		erasesize = mtd->erasesize;
+	}
+
+	spi_nor_setup_op(nor, &op, nor->write_proto);
+
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret)
+		return ret;
+
+	return erasesize;
+}
+
+static int s25fs_s_quad_enable(struct spi_nor *nor)
+{
+	return spansion_quad_enable_volatile(nor, 0, S25FS_S_RDAR_DUMMY);
+}
+
+static int s25fs_s_erase_non_uniform(struct spi_nor *nor, loff_t addr)
+{
+	u8 opcode = nor->addr_width == 4 ? SPINOR_OP_BE_4K_4B : SPINOR_OP_BE_4K;
+
+	/* Support 8 x 4KB sectors at bottom */
+	return spansion_erase_non_uniform(nor, addr, opcode, 0, SZ_32K);
+}
+
+static int s25fs_s_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params)
+{
+	int ret;
+	u8 cfr3v;
+
+	/* Bank Address Register is not supported */
+	if (CONFIG_IS_ENABLED(SPI_FLASH_BAR))
+		return -EOPNOTSUPP;
+
+	/*
+	 * Read CR3V to check if uniform sector is selected. If not, assign an
+	 * erase hook that supports non-uniform erase.
+	 */
+	ret = spansion_read_any_reg(nor, SPINOR_REG_CYPRESS_CFR3V,
+				    S25FS_S_RDAR_DUMMY, &cfr3v);
+	if (ret)
+		return ret;
+	if (!(cfr3v & SPINOR_REG_CYPRESS_CFR3_UNISECT))
+		nor->erase = s25fs_s_erase_non_uniform;
+
+	return spi_nor_default_setup(nor, info, params);
+}
+
+static void s25fs_s_late_init(struct spi_nor *nor,
+			      struct spi_nor_flash_parameter *params)
+{
+	nor->setup = s25fs_s_setup;
+}
+
+static int s25fs_s_post_bfpt_fixup(struct spi_nor *nor,
+				   const struct sfdp_parameter_header *header,
+				   const struct sfdp_bfpt *bfpt,
+				   struct spi_nor_flash_parameter *params)
+{
+	/* The erase size is set to 4K from BFPT, but it's wrong. Fix it. */
+	nor->erase_opcode = SPINOR_OP_SE;
+	nor->mtd.erasesize = nor->info->sector_size;
+
+	/* The S25FS-S chip family reports 512-byte pages in BFPT but
+	 * in reality the write buffer still wraps at the safe default
+	 * of 256 bytes.  Overwrite the page size advertised by BFPT
+	 * to get the writes working.
+	 */
+	params->page_size = 256;
+
+	return 0;
+}
+
+static void s25fs_s_post_sfdp_fixup(struct spi_nor *nor,
+				    struct spi_nor_flash_parameter *params)
+{
+	/*
+	 * The S25FS064S(8MB) supports 1-1-2 and 1-1-4 commands, but params for
+	 * read ops in SFDP are wrong. The other density parts do not support
+	 * 1-1-2 and 1-1-4 commands.
+	 */
+	if (params->size == SZ_8M) {
+		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);
+		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);
+	} else {
+		params->hwcaps.mask &= ~SNOR_HWCAPS_READ_1_1_2;
+		params->hwcaps.mask &= ~SNOR_HWCAPS_READ_1_1_4;
+		params->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
+	}
+
+	/* Use volatile register to enable quad */
+	params->quad_enable = s25fs_s_quad_enable;
+}
+
+static struct spi_nor_fixups s25fs_s_fixups = {
+	.post_bfpt = s25fs_s_post_bfpt_fixup,
+	.post_sfdp = s25fs_s_post_sfdp_fixup,
+	.late_init = s25fs_s_late_init,
+};
+
+static int s25_s28_mdp_ready(struct spi_nor *nor)
+{
+	u32 addr;
+	int ret;
+
+	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
+		ret = spansion_sr_ready(nor, addr, nor->rdsr_dummy);
+		if (ret < 0)
+			return ret;
+		else if (ret == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+static int s25_quad_enable(struct spi_nor *nor)
+{
+	u32 addr;
+	int ret;
+
+	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
+		ret = spansion_quad_enable_volatile(nor, addr, 0);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int s25_s28_erase_non_uniform(struct spi_nor *nor, loff_t addr)
+{
+	/* Support 32 x 4KB sectors at bottom */
+	return spansion_erase_non_uniform(nor, addr, SPINOR_OP_BE_4K_4B, 0,
+					  SZ_128K);
+}
+
+static int s25_s28_setup(struct spi_nor *nor, const struct flash_info *info,
+			 const struct spi_nor_flash_parameter *params)
+{
+	int ret;
+	u8 cr;
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+	return -ENOTSUPP; /* Bank Address Register is not supported */
+#endif
+	/*
+	 * S25FS256T has multiple sector architecture options, with selection of
+	 * count and location of 128KB and 64KB sectors. This driver supports
+	 * uniform 128KB only due to complexity of non-uniform layout.
+	 */
+	if (nor->info->id[4] == S25FS256T_ID4) {
+		ret = spansion_read_any_reg(nor, SPINOR_REG_CYPRESS_ARCFN, 8,
+					    &cr);
+		if (ret)
+			return ret;
+
+		if (cr) /* Option 0 (ARCFN[7:0] == 0x00) is uniform */
+			return -EOPNOTSUPP;
+
+		return spi_nor_default_setup(nor, info, params);
+	}
+
+	/*
+	 * Read CFR3V to check if uniform sector is selected. If not, assign an
+	 * erase hook that supports non-uniform erase.
+	 */
+	ret = spansion_read_any_reg(nor, SPINOR_REG_CYPRESS_CFR3V, 0, &cr);
+	if (ret)
+		return ret;
+	if (!(cr & SPINOR_REG_CYPRESS_CFR3_UNISECT))
+		nor->erase = s25_s28_erase_non_uniform;
+
+	/*
+	 * For the multi-die package parts, the ready() hook is needed to check
+	 * all dies' status via read any register.
+	 */
+	if (nor->mtd.size > SZ_128M)
+		nor->ready = s25_s28_mdp_ready;
+
+	return spi_nor_default_setup(nor, info, params);
+}
+
+static void s25_late_init(struct spi_nor *nor,
+			  struct spi_nor_flash_parameter *params)
+{
+	nor->setup = s25_s28_setup;
+
+	/*
+	 * Programming is supported only in 16-byte ECC data unit granularity.
+	 * Byte-programming, bit-walking, or multiple program operations to the
+	 * same ECC data unit without an erase are not allowed.
+	 */
+	params->writesize = 16;
+}
+
+static int s25_s28_post_bfpt_fixup(struct spi_nor *nor,
+				   const struct sfdp_parameter_header *header,
+				   const struct sfdp_bfpt *bfpt,
+				   struct spi_nor_flash_parameter *params)
+{
+	int ret;
+	u32 addr;
+	u8 cfr3v;
+
+	/* erase size in case it is set to 4K from BFPT */
+	nor->erase_opcode = SPINOR_OP_SE_4B;
+	nor->mtd.erasesize = nor->info->sector_size;
+
+	/*
+	 * The default address mode in multi-die package parts (>1Gb) may be
+	 * 3- or 4-byte, depending on model number. BootROM code in some SoCs
+	 * use 3-byte mode for backward compatibility and should switch to
+	 * 4-byte mode after BootROM phase. Since registers in the 2nd die are
+	 * mapped within 32-bit address space, we need to make sure the flash is
+	 * in 4-byte address mode. The default address mode can be distinguished
+	 * by BFPT 16th DWORD. Power cycle exits 4-byte address mode if default
+	 * is 3-byte address mode.
+	 */
+	if (params->size > SZ_128M) {
+		if (bfpt->dwords[BFPT_DWORD(16)] & BFPT_DWORD16_EX4B_PWRCYC) {
+			ret = set_4byte(nor, nor->info, 1);
+			if (ret)
+				return ret;
+		}
+		nor->addr_mode_nbytes = 4;
+	}
+
+	/* The default address mode in S25FS256T is 4. */
+	if (nor->info->id[4] == S25FS256T_ID4)
+		nor->addr_mode_nbytes = 4;
+
+	/*
+	 * The page_size is set to 512B from BFPT, but it actually depends on
+	 * the configuration register. Look up the CFR3V and determine the
+	 * page_size. For multi-die package parts, use 512B only when the all
+	 * dies are configured to 512B buffer.
+	 */
+	for (addr = 0; addr < params->size; addr += SZ_128M) {
+		ret = spansion_read_any_reg(nor,
+					    addr + SPINOR_REG_CYPRESS_CFR3V, 0,
+					    &cfr3v);
+		if (ret)
+			return ret;
+
+		if (!(cfr3v & SPINOR_REG_CYPRESS_CFR3_PGSZ)) {
+			params->page_size = 256;
+			return 0;
+		}
+	}
+	params->page_size = 512;
+
+	return 0;
+}
+
+static void s25_post_sfdp_fixup(struct spi_nor *nor,
+				struct spi_nor_flash_parameter *params)
+{
+	if (nor->info->id[4] == S25FS256T_ID4) {
+		/* PP_1_1_4 is supported */
+		params->hwcaps.mask |= SNOR_HWCAPS_PP_1_1_4;
+	} else {
+		/* READ_FAST_4B (0Ch) requires mode cycles*/
+		params->reads[SNOR_CMD_READ_FAST].num_mode_clocks = 8;
+		/* PP_1_1_4 is not supported */
+		params->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
+		/* Use volatile register to enable quad */
+		params->quad_enable = s25_quad_enable;
+	}
+}
+
+static struct spi_nor_fixups s25_fixups = {
+	.post_bfpt = s25_s28_post_bfpt_fixup,
+	.post_sfdp = s25_post_sfdp_fixup,
+	.late_init = s25_late_init,
+};
+
+static int s25fl256l_setup(struct spi_nor *nor, const struct flash_info *info,
+			   const struct spi_nor_flash_parameter *params)
+{
+	return -ENOTSUPP; /* Bank Address Register is not supported */
+}
+
+static void s25fl256l_late_init(struct spi_nor *nor,
+				struct spi_nor_flash_parameter *params)
+{
+	nor->setup = s25fl256l_setup;
+}
+
+static struct spi_nor_fixups s25fl256l_fixups = {
+	.late_init = s25fl256l_late_init,
+};
+
+#ifdef CONFIG_SPI_FLASH_S28HX_T
+/**
+ * spi_nor_cypress_octal_dtr_enable() - Enable octal DTR on Cypress flashes.
+ * @nor:		pointer to a 'struct spi_nor'
+ *
+ * This also sets the memory access latency cycles to 24 to allow the flash to
+ * run at up to 200MHz.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_cypress_octal_dtr_enable(struct spi_nor *nor)
+{
+	u32 addr;
+	u8 buf;
+	int ret;
+
+	ret = write_enable(nor);
+	if (ret)
+		return ret;
+
+	/* Use 24 dummy cycles for memory array reads. */
+	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
+		ret = spansion_read_any_reg(nor,
+					    addr + SPINOR_REG_CYPRESS_CFR2V, 0,
+					    &buf);
+		if (ret)
+			return ret;
+
+		buf &= ~SPINOR_REG_CYPRESS_CFR2_MEMLAT_MASK;
+		buf |= SPINOR_REG_CYPRESS_CFR2_MEMLAT_11_24;
+		ret = spansion_write_any_reg(nor,
+					     addr + SPINOR_REG_CYPRESS_CFR2V,
+					     buf);
+		if (ret) {
+			dev_warn(nor->dev, "failed to set default memory latency value: %d\n", ret);
+			return ret;
+		}
+	}
+	nor->read_dummy = 24;
+
+	ret = write_enable(nor);
+	if (ret)
+		return ret;
+
+	/* Set the octal and DTR enable bits. */
+	buf = SPINOR_REG_CYPRESS_CFR5_OCT_DTR_EN;
+	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
+		ret = spansion_write_any_reg(nor,
+					     addr + SPINOR_REG_CYPRESS_CFR5V,
+					     buf);
+		if (ret) {
+			dev_warn(nor->dev, "Failed to enable octal DTR mode\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void s28hx_t_late_init(struct spi_nor *nor,
+			      struct spi_nor_flash_parameter *params)
+{
+	nor->octal_dtr_enable = spi_nor_cypress_octal_dtr_enable;
+	nor->setup = s25_s28_setup;
+
+	/*
+	 * Programming is supported only in 16-byte ECC data unit granularity.
+	 * Byte-programming, bit-walking, or multiple program operations to the
+	 * same ECC data unit without an erase are not allowed.
+	 */
+	params->writesize = 16;
+}
+
+static void s28hx_t_post_sfdp_fixup(struct spi_nor *nor,
+				    struct spi_nor_flash_parameter *params)
+{
+	/*
+	 * On older versions of the flash the xSPI Profile 1.0 table has the
+	 * 8D-8D-8D Fast Read opcode as 0x00. But it actually should be 0xEE.
+	 */
+	if (params->reads[SNOR_CMD_READ_8_8_8_DTR].opcode == 0)
+		params->reads[SNOR_CMD_READ_8_8_8_DTR].opcode =
+			SPINOR_OP_CYPRESS_RD_FAST;
+
+	params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
+
+	/* This flash is also missing the 4-byte Page Program opcode bit. */
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+				SPINOR_OP_PP_4B, SNOR_PROTO_1_1_1);
+	/*
+	 * Since xSPI Page Program opcode is backward compatible with
+	 * Legacy SPI, use Legacy SPI opcode there as well.
+	 */
+	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP_8_8_8_DTR],
+				SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR);
+
+	/*
+	 * The xSPI Profile 1.0 table advertises the number of additional
+	 * address bytes needed for Read Status Register command as 0 but the
+	 * actual value for that is 4.
+	 */
+	params->rdsr_addr_nbytes = 4;
+}
+
+static struct spi_nor_fixups s28hx_t_fixups = {
+	.post_sfdp = s28hx_t_post_sfdp_fixup,
+	.post_bfpt = s25_s28_post_bfpt_fixup,
+	.late_init = s28hx_t_late_init,
+};
+#endif /* CONFIG_SPI_FLASH_S28HX_T */
+
+static void spansion_set_fixups(struct spi_nor *nor)
+{
+	if (JEDEC_MFR(nor->info) == SNOR_MFR_CYPRESS) {
+		switch (nor->info->id[1]) {
+		case 0x2a: /* S25HL (QSPI, 3.3V) */
+		case 0x2b: /* S25HS (QSPI, 1.8V) */
+			nor->fixups = &s25_fixups;
+			break;
+
+#ifdef CONFIG_SPI_FLASH_S28HX_T
+		case 0x5a: /* S28HL (Octal, 3.3V) */
+		case 0x5b: /* S28HS (Octal, 1.8V) */
+			nor->fixups = &s28hx_t_fixups;
+			break;
+#endif
+
+		default:
+			break;
+		}
+	}
+
+	if (CONFIG_IS_ENABLED(SPI_FLASH_BAR) &&
+	    !strcmp(nor->info->name, "s25fl256l"))
+		nor->fixups = &s25fl256l_fixups;
+
+	/* For FS-S (family ID = 0x81)  */
+	if (JEDEC_MFR(nor->info) == SNOR_MFR_SPANSION && nor->info->id[5] == 0x81)
+		nor->fixups = &s25fs_s_fixups;
+}
+#endif /* !CONFIG_IS_ENABLED(SPI_FLASH_TINY) */
+
 const struct spi_nor_manufacturer spi_nor_spansion = {
 	.parts = spansion_nor_parts,
 	.nparts = ARRAY_SIZE(spansion_nor_parts),
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+	.set_fixups = spansion_set_fixups,
+#endif
 };
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c
index d107828092c..6e61b5653bd 100644
--- a/drivers/mtd/spi/spi-nor-core.c
+++ b/drivers/mtd/spi/spi-nor-core.c
@@ -123,8 +123,8 @@ void spi_nor_setup_op(const struct spi_nor *nor,
 	}
 }
 
-static int spi_nor_read_write_reg(struct spi_nor *nor, struct spi_mem_op
-		*op, void *buf)
+int spi_nor_read_write_reg(struct spi_nor *nor, struct spi_mem_op *op,
+			   void *buf)
 {
 	if (op->data.dir == SPI_MEM_DATA_IN)
 		op->data.buf.in = buf;
@@ -165,55 +165,6 @@ static int spi_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
 	return spi_nor_read_write_reg(nor, &op, buf);
 }
 
-#ifdef CONFIG_SPI_FLASH_SPANSION
-static int spansion_read_any_reg(struct spi_nor *nor, u32 addr, u8 dummy,
-				 u8 *val)
-{
-	struct spi_mem_op op =
-		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 0),
-			   SPI_MEM_OP_ADDR(nor->addr_mode_nbytes, addr, 0),
-			   SPI_MEM_OP_DUMMY(dummy, 0),
-			   SPI_MEM_OP_DATA_IN(1, NULL, 0));
-	u8 buf[2];
-	int ret;
-
-	spi_nor_setup_op(nor, &op, nor->reg_proto);
-
-	/*
-	 * In Octal DTR mode, the number of address bytes is always 4 regardless
-	 * of addressing mode setting.
-	 */
-	if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR)
-		op.addr.nbytes = 4;
-
-	/*
-	 * We don't want to read only one byte in DTR mode. So, read 2 and then
-	 * discard the second byte.
-	 */
-	if (spi_nor_protocol_is_dtr(nor->reg_proto))
-		op.data.nbytes = 2;
-
-	ret = spi_nor_read_write_reg(nor, &op, buf);
-	if (ret)
-		return ret;
-
-	*val = buf[0];
-
-	return 0;
-}
-
-static int spansion_write_any_reg(struct spi_nor *nor, u32 addr, u8 val)
-{
-	struct spi_mem_op op =
-		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1),
-			   SPI_MEM_OP_ADDR(nor->addr_mode_nbytes, addr, 1),
-			   SPI_MEM_OP_NO_DUMMY,
-			   SPI_MEM_OP_DATA_OUT(1, NULL, 1));
-
-	return spi_nor_read_write_reg(nor, &op, &val);
-}
-#endif
-
 static ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
 				 u_char *buf)
 {
@@ -472,7 +423,7 @@ static int write_sr3(struct spi_nor *nor, u8 val)
  * Set write enable latch with Write Enable command.
  * Returns negative if error occurred.
  */
-static int write_enable(struct spi_nor *nor)
+int write_enable(struct spi_nor *nor)
 {
 	return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0);
 }
@@ -577,8 +528,7 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
 #endif /* !CONFIG_IS_ENABLED(SPI_FLASH_BAR) */
 
 /* Enable/disable 4-byte addressing mode. */
-static int set_4byte(struct spi_nor *nor, const struct flash_info *info,
-		     int enable)
+int set_4byte(struct spi_nor *nor, const struct flash_info *info, int enable)
 {
 	int status;
 	bool need_wren = false;
@@ -626,36 +576,7 @@ static int set_4byte(struct spi_nor *nor, const struct flash_info *info,
 	}
 }
 
-#ifdef CONFIG_SPI_FLASH_SPANSION
-/*
- * Read status register 1 by using Read Any Register command to support multi
- * die package parts.
- */
-static int spansion_sr_ready(struct spi_nor *nor, u32 addr_base, u8 dummy)
-{
-	u32 reg_addr = addr_base + SPINOR_REG_CYPRESS_STR1V;
-	u8 sr;
-	int ret;
-
-	ret = spansion_read_any_reg(nor, reg_addr, dummy, &sr);
-	if (ret < 0)
-		return ret;
-
-	if (sr & (SR_E_ERR | SR_P_ERR)) {
-		if (sr & SR_E_ERR)
-			dev_dbg(nor->dev, "Erase Error occurred\n");
-		else
-			dev_dbg(nor->dev, "Programming Error occurred\n");
-
-		nor->write_reg(nor, SPINOR_OP_CYPRESS_CLPEF, NULL, 0);
-		return -EIO;
-	}
-
-	return !(sr & SR_WIP);
-}
-#endif
-
-static int spi_nor_sr_ready(struct spi_nor *nor)
+int spi_nor_sr_ready(struct spi_nor *nor)
 {
 	int sr = read_sr(nor);
 
@@ -745,7 +666,7 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
 	return -ETIMEDOUT;
 }
 
-static int spi_nor_wait_till_ready(struct spi_nor *nor)
+int spi_nor_wait_till_ready(struct spi_nor *nor)
 {
 	return spi_nor_wait_till_ready_with_timeout(nor,
 						    DEFAULT_READY_WAIT_JIFFIES);
@@ -1035,67 +956,6 @@ err:
 	return ret;
 }
 
-#ifdef CONFIG_SPI_FLASH_SPANSION
-/**
- * spansion_erase_non_uniform() - erase non-uniform sectors for Spansion/Cypress
- *                                chips
- * @nor:	pointer to a 'struct spi_nor'
- * @addr:	address of the sector to erase
- * @opcode_4k:	opcode for 4K sector erase
- * @ovlsz_top:	size of overlaid portion at the top address
- * @ovlsz_btm:	size of overlaid portion at the bottom address
- *
- * Erase an address range on the nor chip that can contain 4KB sectors overlaid
- * on top and/or bottom. The appropriate erase opcode and size are chosen by
- * address to erase and size of overlaid portion.
- *
- * Return: number of bytes erased on success, -errno otherwise.
- */
-static int spansion_erase_non_uniform(struct spi_nor *nor, u32 addr,
-				      u8 opcode_4k, u32 ovlsz_top,
-				      u32 ovlsz_btm)
-{
-	struct spi_mem_op op =
-		SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 0),
-			   SPI_MEM_OP_ADDR(nor->addr_width, addr, 0),
-			   SPI_MEM_OP_NO_DUMMY,
-			   SPI_MEM_OP_NO_DATA);
-	struct mtd_info *mtd = &nor->mtd;
-	u32 erasesize;
-	int ret;
-
-	/* 4KB sectors */
-	if (op.addr.val < ovlsz_btm ||
-	    op.addr.val >= mtd->size - ovlsz_top) {
-		op.cmd.opcode = opcode_4k;
-		erasesize = SZ_4K;
-
-	/* Non-overlaid portion in the normal sector at the bottom */
-	} else if (op.addr.val == ovlsz_btm) {
-		op.cmd.opcode = nor->erase_opcode;
-		erasesize = mtd->erasesize - ovlsz_btm;
-
-	/* Non-overlaid portion in the normal sector at the top */
-	} else if (op.addr.val == mtd->size - mtd->erasesize) {
-		op.cmd.opcode = nor->erase_opcode;
-		erasesize = mtd->erasesize - ovlsz_top;
-
-	/* Normal sectors */
-	} else {
-		op.cmd.opcode = nor->erase_opcode;
-		erasesize = mtd->erasesize;
-	}
-
-	spi_nor_setup_op(nor, &op, nor->write_proto);
-
-	ret = spi_mem_exec_op(nor->spi, &op);
-	if (ret)
-		return ret;
-
-	return erasesize;
-}
-#endif
-
 #if defined(CONFIG_SPI_FLASH_LOCK)
 #if defined(CONFIG_SPI_FLASH_STMICRO) || defined(CONFIG_SPI_FLASH_SST)
 /* Write status register and ensure bits in mask match written values */
@@ -1980,61 +1840,6 @@ int macronix_quad_enable(struct spi_nor *nor)
 }
 #endif
 
-#ifdef CONFIG_SPI_FLASH_SPANSION
-/**
- * spansion_quad_enable_volatile() - enable Quad I/O mode in volatile register.
- * @nor:	pointer to a 'struct spi_nor'
- * @addr_base:	base address of register (can be >0 in multi-die parts)
- * @dummy:	number of dummy cycles for register read
- *
- * It is recommended to update volatile registers in the field application due
- * to a risk of the non-volatile registers corruption by power interrupt. This
- * function sets Quad Enable bit in CFR1 volatile.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spansion_quad_enable_volatile(struct spi_nor *nor, u32 addr_base,
-					 u8 dummy)
-{
-	u32 addr = addr_base + SPINOR_REG_CYPRESS_CFR1V;
-
-	u8 cr;
-	int ret;
-
-	/* Check current Quad Enable bit value. */
-	ret = spansion_read_any_reg(nor, addr, dummy, &cr);
-	if (ret < 0) {
-		dev_dbg(nor->dev,
-			"error while reading configuration register\n");
-		return -EINVAL;
-	}
-
-	if (cr & CR_QUAD_EN_SPAN)
-		return 0;
-
-	cr |= CR_QUAD_EN_SPAN;
-
-	write_enable(nor);
-
-	ret = spansion_write_any_reg(nor, addr, cr);
-
-	if (ret < 0) {
-		dev_dbg(nor->dev,
-			"error while writing configuration register\n");
-		return -EINVAL;
-	}
-
-	/* Read back and check it. */
-	ret = spansion_read_any_reg(nor, addr, dummy, &cr);
-	if (ret || !(cr & CR_QUAD_EN_SPAN)) {
-		dev_dbg(nor->dev, "Spansion Quad bit not set\n");
-		return -EINVAL;
-	}
-
-	return 0;
-}
-#endif
-
 #if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND)
 /*
  * Write status Register and configuration register with 2 bytes
@@ -2165,7 +1970,7 @@ spi_nor_set_read_settings(struct spi_nor_read_command *read,
 	read->proto = proto;
 }
 
-static void
+void
 spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
 			u8 opcode,
 			enum spi_nor_protocol proto)
@@ -2755,9 +2560,8 @@ static int spi_nor_select_erase(struct spi_nor *nor,
 	return 0;
 }
 
-static int spi_nor_default_setup(struct spi_nor *nor,
-				 const struct flash_info *info,
-				 const struct spi_nor_flash_parameter *params)
+int spi_nor_default_setup(struct spi_nor *nor, const struct flash_info *info,
+			  const struct spi_nor_flash_parameter *params)
 {
 	u32 shared_mask;
 	bool enable_quad_io;
@@ -2809,664 +2613,6 @@ static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
 	return nor->setup(nor, info, params);
 }
 
-#ifdef CONFIG_SPI_FLASH_SPANSION
-
-/* Use ID byte 4 to distinguish S25FS256T and S25Hx-T */
-#define S25FS256T_ID4	(0x08)
-
-/* Number of dummy cycle for Read Any Register (RDAR) op. */
-#define S25FS_S_RDAR_DUMMY	8
-
-static int s25fs_s_quad_enable(struct spi_nor *nor)
-{
-	return spansion_quad_enable_volatile(nor, 0, S25FS_S_RDAR_DUMMY);
-}
-
-static int s25fs_s_erase_non_uniform(struct spi_nor *nor, loff_t addr)
-{
-	u8 opcode = nor->addr_width == 4 ? SPINOR_OP_BE_4K_4B : SPINOR_OP_BE_4K;
-
-	/* Support 8 x 4KB sectors at bottom */
-	return spansion_erase_non_uniform(nor, addr, opcode, 0, SZ_32K);
-}
-
-static int s25fs_s_setup(struct spi_nor *nor, const struct flash_info *info,
-			 const struct spi_nor_flash_parameter *params)
-{
-	int ret;
-	u8 cfr3v;
-
-	/* Bank Address Register is not supported */
-	if (CONFIG_IS_ENABLED(SPI_FLASH_BAR))
-		return -EOPNOTSUPP;
-
-	/*
-	 * Read CR3V to check if uniform sector is selected. If not, assign an
-	 * erase hook that supports non-uniform erase.
-	 */
-	ret = spansion_read_any_reg(nor, SPINOR_REG_CYPRESS_CFR3V,
-				    S25FS_S_RDAR_DUMMY, &cfr3v);
-	if (ret)
-		return ret;
-	if (!(cfr3v & SPINOR_REG_CYPRESS_CFR3_UNISECT))
-		nor->erase = s25fs_s_erase_non_uniform;
-
-	return spi_nor_default_setup(nor, info, params);
-}
-
-static void s25fs_s_late_init(struct spi_nor *nor,
-			      struct spi_nor_flash_parameter *params)
-{
-	nor->setup = s25fs_s_setup;
-}
-
-static int s25fs_s_post_bfpt_fixup(struct spi_nor *nor,
-				   const struct sfdp_parameter_header *header,
-				   const struct sfdp_bfpt *bfpt,
-				   struct spi_nor_flash_parameter *params)
-{
-	/* The erase size is set to 4K from BFPT, but it's wrong. Fix it. */
-	nor->erase_opcode = SPINOR_OP_SE;
-	nor->mtd.erasesize = nor->info->sector_size;
-
-	/* The S25FS-S chip family reports 512-byte pages in BFPT but
-	 * in reality the write buffer still wraps at the safe default
-	 * of 256 bytes.  Overwrite the page size advertised by BFPT
-	 * to get the writes working.
-	 */
-	params->page_size = 256;
-
-	return 0;
-}
-
-static void s25fs_s_post_sfdp_fixup(struct spi_nor *nor,
-				    struct spi_nor_flash_parameter *params)
-{
-	/*
-	 * The S25FS064S(8MB) supports 1-1-2 and 1-1-4 commands, but params for
-	 * read ops in SFDP are wrong. The other density parts do not support
-	 * 1-1-2 and 1-1-4 commands.
-	 */
-	if (params->size == SZ_8M) {
-		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);
-		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);
-	} else {
-		params->hwcaps.mask &= ~SNOR_HWCAPS_READ_1_1_2;
-		params->hwcaps.mask &= ~SNOR_HWCAPS_READ_1_1_4;
-		params->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
-	}
-
-	/* Use volatile register to enable quad */
-	params->quad_enable = s25fs_s_quad_enable;
-}
-
-static struct spi_nor_fixups s25fs_s_fixups = {
-	.post_bfpt = s25fs_s_post_bfpt_fixup,
-	.post_sfdp = s25fs_s_post_sfdp_fixup,
-	.late_init = s25fs_s_late_init,
-};
-
-static int s25_s28_mdp_ready(struct spi_nor *nor)
-{
-	u32 addr;
-	int ret;
-
-	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
-		ret = spansion_sr_ready(nor, addr, nor->rdsr_dummy);
-		if (ret < 0)
-			return ret;
-		else if (ret == 0)
-			return 0;
-	}
-
-	return 1;
-}
-
-static int s25_quad_enable(struct spi_nor *nor)
-{
-	u32 addr;
-	int ret;
-
-	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
-		ret = spansion_quad_enable_volatile(nor, addr, 0);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
-}
-
-static int s25_s28_erase_non_uniform(struct spi_nor *nor, loff_t addr)
-{
-	/* Support 32 x 4KB sectors at bottom */
-	return spansion_erase_non_uniform(nor, addr, SPINOR_OP_BE_4K_4B, 0,
-					  SZ_128K);
-}
-
-static int s25_s28_setup(struct spi_nor *nor, const struct flash_info *info,
-			 const struct spi_nor_flash_parameter *params)
-{
-	int ret;
-	u8 cr;
-
-#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
-	return -ENOTSUPP; /* Bank Address Register is not supported */
-#endif
-	/*
-	 * S25FS256T has multiple sector architecture options, with selection of
-	 * count and location of 128KB and 64KB sectors. This driver supports
-	 * uniform 128KB only due to complexity of non-uniform layout.
-	 */
-	if (nor->info->id[4] == S25FS256T_ID4) {
-		ret = spansion_read_any_reg(nor, SPINOR_REG_CYPRESS_ARCFN, 8,
-					    &cr);
-		if (ret)
-			return ret;
-
-		if (cr) /* Option 0 (ARCFN[7:0] == 0x00) is uniform */
-			return -EOPNOTSUPP;
-
-		return spi_nor_default_setup(nor, info, params);
-	}
-
-	/*
-	 * Read CFR3V to check if uniform sector is selected. If not, assign an
-	 * erase hook that supports non-uniform erase.
-	 */
-	ret = spansion_read_any_reg(nor, SPINOR_REG_CYPRESS_CFR3V, 0, &cr);
-	if (ret)
-		return ret;
-	if (!(cr & SPINOR_REG_CYPRESS_CFR3_UNISECT))
-		nor->erase = s25_s28_erase_non_uniform;
-
-	/*
-	 * For the multi-die package parts, the ready() hook is needed to check
-	 * all dies' status via read any register.
-	 */
-	if (nor->mtd.size > SZ_128M)
-		nor->ready = s25_s28_mdp_ready;
-
-	return spi_nor_default_setup(nor, info, params);
-}
-
-static void s25_late_init(struct spi_nor *nor,
-			  struct spi_nor_flash_parameter *params)
-{
-	nor->setup = s25_s28_setup;
-
-	/*
-	 * Programming is supported only in 16-byte ECC data unit granularity.
-	 * Byte-programming, bit-walking, or multiple program operations to the
-	 * same ECC data unit without an erase are not allowed.
-	 */
-	params->writesize = 16;
-}
-
-static int s25_s28_post_bfpt_fixup(struct spi_nor *nor,
-				   const struct sfdp_parameter_header *header,
-				   const struct sfdp_bfpt *bfpt,
-				   struct spi_nor_flash_parameter *params)
-{
-	int ret;
-	u32 addr;
-	u8 cfr3v;
-
-	/* erase size in case it is set to 4K from BFPT */
-	nor->erase_opcode = SPINOR_OP_SE_4B;
-	nor->mtd.erasesize = nor->info->sector_size;
-
-	/*
-	 * The default address mode in multi-die package parts (>1Gb) may be
-	 * 3- or 4-byte, depending on model number. BootROM code in some SoCs
-	 * use 3-byte mode for backward compatibility and should switch to
-	 * 4-byte mode after BootROM phase. Since registers in the 2nd die are
-	 * mapped within 32-bit address space, we need to make sure the flash is
-	 * in 4-byte address mode. The default address mode can be distinguished
-	 * by BFPT 16th DWORD. Power cycle exits 4-byte address mode if default
-	 * is 3-byte address mode.
-	 */
-	if (params->size > SZ_128M) {
-		if (bfpt->dwords[BFPT_DWORD(16)] & BFPT_DWORD16_EX4B_PWRCYC) {
-			ret = set_4byte(nor, nor->info, 1);
-			if (ret)
-				return ret;
-		}
-		nor->addr_mode_nbytes = 4;
-	}
-
-	/* The default address mode in S25FS256T is 4. */
-	if (nor->info->id[4] == S25FS256T_ID4)
-		nor->addr_mode_nbytes = 4;
-
-	/*
-	 * The page_size is set to 512B from BFPT, but it actually depends on
-	 * the configuration register. Look up the CFR3V and determine the
-	 * page_size. For multi-die package parts, use 512B only when the all
-	 * dies are configured to 512B buffer.
-	 */
-	for (addr = 0; addr < params->size; addr += SZ_128M) {
-		ret = spansion_read_any_reg(nor,
-					    addr + SPINOR_REG_CYPRESS_CFR3V, 0,
-					    &cfr3v);
-		if (ret)
-			return ret;
-
-		if (!(cfr3v & SPINOR_REG_CYPRESS_CFR3_PGSZ)) {
-			params->page_size = 256;
-			return 0;
-		}
-	}
-	params->page_size = 512;
-
-	return 0;
-}
-
-static void s25_post_sfdp_fixup(struct spi_nor *nor,
-				struct spi_nor_flash_parameter *params)
-{
-	if (nor->info->id[4] == S25FS256T_ID4) {
-		/* PP_1_1_4 is supported */
-		params->hwcaps.mask |= SNOR_HWCAPS_PP_1_1_4;
-	} else {
-		/* READ_FAST_4B (0Ch) requires mode cycles*/
-		params->reads[SNOR_CMD_READ_FAST].num_mode_clocks = 8;
-		/* PP_1_1_4 is not supported */
-		params->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
-		/* Use volatile register to enable quad */
-		params->quad_enable = s25_quad_enable;
-	}
-}
-
-static struct spi_nor_fixups s25_fixups = {
-	.post_bfpt = s25_s28_post_bfpt_fixup,
-	.post_sfdp = s25_post_sfdp_fixup,
-	.late_init = s25_late_init,
-};
-
-static int s25fl256l_setup(struct spi_nor *nor, const struct flash_info *info,
-			   const struct spi_nor_flash_parameter *params)
-{
-	return -ENOTSUPP; /* Bank Address Register is not supported */
-}
-
-static void s25fl256l_late_init(struct spi_nor *nor,
-				struct spi_nor_flash_parameter *params)
-{
-	nor->setup = s25fl256l_setup;
-}
-
-static struct spi_nor_fixups s25fl256l_fixups = {
-	.late_init = s25fl256l_late_init,
-};
-#endif
-
-#ifdef CONFIG_SPI_FLASH_S28HX_T
-/**
- * spi_nor_cypress_octal_dtr_enable() - Enable octal DTR on Cypress flashes.
- * @nor:		pointer to a 'struct spi_nor'
- *
- * This also sets the memory access latency cycles to 24 to allow the flash to
- * run at up to 200MHz.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_cypress_octal_dtr_enable(struct spi_nor *nor)
-{
-	u32 addr;
-	u8 buf;
-	int ret;
-
-	ret = write_enable(nor);
-	if (ret)
-		return ret;
-
-	/* Use 24 dummy cycles for memory array reads. */
-	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
-		ret = spansion_read_any_reg(nor,
-					    addr + SPINOR_REG_CYPRESS_CFR2V, 0,
-					    &buf);
-		if (ret)
-			return ret;
-
-		buf &= ~SPINOR_REG_CYPRESS_CFR2_MEMLAT_MASK;
-		buf |= SPINOR_REG_CYPRESS_CFR2_MEMLAT_11_24;
-		ret = spansion_write_any_reg(nor,
-					     addr + SPINOR_REG_CYPRESS_CFR2V,
-					     buf);
-		if (ret) {
-			dev_warn(nor->dev, "failed to set default memory latency value: %d\n", ret);
-			return ret;
-		}
-	}
-	nor->read_dummy = 24;
-
-	ret = write_enable(nor);
-	if (ret)
-		return ret;
-
-	/* Set the octal and DTR enable bits. */
-	buf = SPINOR_REG_CYPRESS_CFR5_OCT_DTR_EN;
-	for (addr = 0; addr < nor->mtd.size; addr += SZ_128M) {
-		ret = spansion_write_any_reg(nor,
-					     addr + SPINOR_REG_CYPRESS_CFR5V,
-					     buf);
-		if (ret) {
-			dev_warn(nor->dev, "Failed to enable octal DTR mode\n");
-			return ret;
-		}
-	}
-
-	return 0;
-}
-
-static void s28hx_t_late_init(struct spi_nor *nor,
-			      struct spi_nor_flash_parameter *params)
-{
-	nor->octal_dtr_enable = spi_nor_cypress_octal_dtr_enable;
-	nor->setup = s25_s28_setup;
-
-	/*
-	 * Programming is supported only in 16-byte ECC data unit granularity.
-	 * Byte-programming, bit-walking, or multiple program operations to the
-	 * same ECC data unit without an erase are not allowed.
-	 */
-	params->writesize = 16;
-}
-
-static void s28hx_t_post_sfdp_fixup(struct spi_nor *nor,
-				    struct spi_nor_flash_parameter *params)
-{
-	/*
-	 * On older versions of the flash the xSPI Profile 1.0 table has the
-	 * 8D-8D-8D Fast Read opcode as 0x00. But it actually should be 0xEE.
-	 */
-	if (params->reads[SNOR_CMD_READ_8_8_8_DTR].opcode == 0)
-		params->reads[SNOR_CMD_READ_8_8_8_DTR].opcode =
-			SPINOR_OP_CYPRESS_RD_FAST;
-
-	params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
-
-	/* This flash is also missing the 4-byte Page Program opcode bit. */
-	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
-				SPINOR_OP_PP_4B, SNOR_PROTO_1_1_1);
-	/*
-	 * Since xSPI Page Program opcode is backward compatible with
-	 * Legacy SPI, use Legacy SPI opcode there as well.
-	 */
-	spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP_8_8_8_DTR],
-				SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR);
-
-	/*
-	 * The xSPI Profile 1.0 table advertises the number of additional
-	 * address bytes needed for Read Status Register command as 0 but the
-	 * actual value for that is 4.
-	 */
-	params->rdsr_addr_nbytes = 4;
-}
-
-static struct spi_nor_fixups s28hx_t_fixups = {
-	.post_sfdp = s28hx_t_post_sfdp_fixup,
-	.post_bfpt = s25_s28_post_bfpt_fixup,
-	.late_init = s28hx_t_late_init,
-};
-#endif /* CONFIG_SPI_FLASH_S28HX_T */
-
-#ifdef CONFIG_SPI_FLASH_MT35XU
-static int spi_nor_micron_octal_dtr_enable(struct spi_nor *nor)
-{
-	struct spi_mem_op op;
-	u8 buf;
-	u8 addr_width = 3;
-	int ret;
-
-	/* Set dummy cycles for Fast Read to the default of 20. */
-	ret = write_enable(nor);
-	if (ret)
-		return ret;
-
-	buf = 20;
-	op = (struct spi_mem_op)
-		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1),
-			   SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_MT_CFR1V, 1),
-			   SPI_MEM_OP_NO_DUMMY,
-			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
-	ret = spi_mem_exec_op(nor->spi, &op);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_wait_till_ready(nor);
-	if (ret)
-		return ret;
-
-	nor->read_dummy = 20;
-
-	ret = write_enable(nor);
-	if (ret)
-		return ret;
-
-	buf = SPINOR_MT_OCT_DTR;
-	op = (struct spi_mem_op)
-		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1),
-			   SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_MT_CFR0V, 1),
-			   SPI_MEM_OP_NO_DUMMY,
-			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
-	ret = spi_mem_exec_op(nor->spi, &op);
-	if (ret) {
-		dev_err(nor->dev, "Failed to enable octal DTR mode\n");
-		return ret;
-	}
-
-	return 0;
-}
-
-static void mt35xu512aba_late_init(struct spi_nor *nor,
-				   struct spi_nor_flash_parameter *params)
-{
-	nor->octal_dtr_enable = spi_nor_micron_octal_dtr_enable;
-}
-
-static void mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor,
-					 struct spi_nor_flash_parameter *params)
-{
-	/* Set the Fast Read settings. */
-	params->hwcaps.mask |= SNOR_HWCAPS_READ_8_8_8_DTR;
-	spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_8_8_8_DTR],
-				  0, 20, SPINOR_OP_MT_DTR_RD,
-				  SNOR_PROTO_8_8_8_DTR);
-
-	params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
-
-	nor->cmd_ext_type = SPI_NOR_EXT_REPEAT;
-	params->rdsr_dummy = 8;
-	params->rdsr_addr_nbytes = 0;
-
-	/*
-	 * SCCR Map 22nd DWORD does not indicate DTR Octal Mode Enable
-	 * for MT35XU512ABA but is actually supported by device.
-	 */
-	nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE;
-
-	/*
-	 * The BFPT quad enable field is set to a reserved value so the quad
-	 * enable function is ignored by spi_nor_parse_bfpt(). Make sure we
-	 * disable it.
-	 */
-	params->quad_enable = NULL;
-}
-
-static struct spi_nor_fixups mt35xu512aba_fixups = {
-	.post_sfdp = mt35xu512aba_post_sfdp_fixup,
-	.late_init = mt35xu512aba_late_init,
-};
-#endif /* CONFIG_SPI_FLASH_MT35XU */
-
-#if CONFIG_IS_ENABLED(SPI_FLASH_MACRONIX)
-/**
- * spi_nor_macronix_octal_dtr_enable() - Enable octal DTR on Macronix flashes.
- * @nor:	pointer to a 'struct spi_nor'
- *
- * Set Macronix max dummy cycles 20 to allow the flash to run at fastest frequency.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_macronix_octal_dtr_enable(struct spi_nor *nor)
-{
-	struct spi_mem_op op;
-	int ret;
-	u8 buf;
-
-	ret = write_enable(nor);
-	if (ret)
-		return ret;
-
-	buf = SPINOR_REG_MXIC_DC_20;
-	op = (struct spi_mem_op)
-		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_CR2, 1),
-			   SPI_MEM_OP_ADDR(4, SPINOR_REG_MXIC_CR2_DC, 1),
-			   SPI_MEM_OP_NO_DUMMY,
-			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
-
-	ret = spi_mem_exec_op(nor->spi, &op);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_wait_till_ready(nor);
-	if (ret)
-		return ret;
-
-	nor->read_dummy = MXIC_MAX_DC;
-	ret = write_enable(nor);
-	if (ret)
-		return ret;
-
-	buf = SPINOR_REG_MXIC_OPI_DTR_EN;
-	op = (struct spi_mem_op)
-		SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_CR2, 1),
-			   SPI_MEM_OP_ADDR(4, SPINOR_REG_MXIC_CR2_MODE, 1),
-			   SPI_MEM_OP_NO_DUMMY,
-			   SPI_MEM_OP_DATA_OUT(1, &buf, 1));
-
-	ret = spi_mem_exec_op(nor->spi, &op);
-	if (ret) {
-		dev_err(nor->dev, "Failed to enable octal DTR mode\n");
-		return ret;
-	}
-	nor->reg_proto = SNOR_PROTO_8_8_8_DTR;
-
-	return 0;
-}
-
-static void macronix_octal_late_init(struct spi_nor *nor,
-				     struct spi_nor_flash_parameter *params)
-{
-	nor->octal_dtr_enable = spi_nor_macronix_octal_dtr_enable;
-}
-
-static void macronix_octal_post_sfdp_fixup(struct spi_nor *nor,
-					 struct spi_nor_flash_parameter *params)
-{
-	/*
-	 * Adding SNOR_HWCAPS_PP_8_8_8_DTR in hwcaps.mask when
-	 * SPI_NOR_OCTAL_DTR_READ flag exists.
-	 */
-	if (params->hwcaps.mask & SNOR_HWCAPS_READ_8_8_8_DTR)
-		params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
-}
-
-static struct spi_nor_fixups macronix_octal_fixups = {
-	.post_sfdp = macronix_octal_post_sfdp_fixup,
-	.late_init = macronix_octal_late_init,
-};
-#endif /* CONFIG_SPI_FLASH_MACRONIX */
-
-#if CONFIG_IS_ENABLED(SPI_FLASH_WINBOND)
-
-#define WINBOND_NOR_OP_SELDIE  0xc2    /* Select active die */
-
-struct winbond_nor_priv {
-	unsigned int n_dice;
-};
-
-/**
- * winbond_nor_select_die() - Set active die.
- * @nor:       pointer to 'struct spi_nor'.
- * @die:       die to set active.
- *
- * Certain Winbond chips feature more than a single die. This is mostly hidden
- * to the user, except that some chips may experience time deviation when
- * modifying the status bits between dies, which in some corner cases may
- * produce problematic races. Being able to explicitly select a die to check its
- * state in this case may be useful.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int winbond_nor_select_die(struct spi_nor *nor, u8 die)
-{
-	struct spi_mem_op op =
-		SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_SELDIE, 1),
-			   SPI_MEM_OP_NO_ADDR,
-			   SPI_MEM_OP_NO_DUMMY,
-			   SPI_MEM_OP_DATA_OUT(1, &die, 0));
-
-	int ret;
-
-	spi_nor_setup_op(nor, &op, SNOR_PROTO_1_1_1);
-	ret = spi_mem_exec_op(nor->spi, &op);
-	if (ret)
-		debug("Error %d selecting die %d\n", ret, die);
-
-	return ret;
-}
-
-static int winbond_nor_multi_die_ready(struct spi_nor *nor)
-{
-	struct winbond_nor_priv *winbond_priv = nor->priv;
-	unsigned int n_dice = winbond_priv ? winbond_priv->n_dice : 1;
-	int ret, i;
-
-	for (i = 0; i < n_dice; i++) {
-		ret = winbond_nor_select_die(nor, i);
-		if (ret)
-			return ret;
-
-		ret = spi_nor_sr_ready(nor);
-		if (ret <= 0)
-			return ret;
-	}
-
-	return 1;
-}
-
-static void winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor,
-						   struct spi_nor_flash_parameter *param)
-{
-	struct winbond_nor_priv *winbond_priv;
-
-	winbond_priv = kmalloc(sizeof(*winbond_priv), GFP_KERNEL);
-	if (!winbond_priv)
-		return;
-
-	winbond_priv->n_dice = param->size / SZ_64M;
-
-	/*
-	 * SFDP supports dice numbers, but this information is only available in
-	 * optional additional tables which are not provided by these chips.
-	 * Dice number has an impact though, because these devices need extra
-	 * care when reading the busy bit.
-	 */
-	nor->priv = winbond_priv;
-	nor->ready = winbond_nor_multi_die_ready;
-}
-
-static struct spi_nor_fixups winbond_nor_multi_die_fixups = {
-	.post_sfdp = winbond_nor_multi_die_post_sfdp_fixups,
-};
-#endif /* CONFIG_SPI_FLASH_WINBOND */
-
 /** spi_nor_octal_dtr_enable() - enable Octal DTR I/O if needed
  * @nor:                 pointer to a 'struct spi_nor'
  *
@@ -3644,63 +2790,6 @@ void spi_nor_set_fixups(struct spi_nor *nor)
 {
 	if (nor->manufacturer->set_fixups)
 		nor->manufacturer->set_fixups(nor);
-
-#ifdef CONFIG_SPI_FLASH_SPANSION
-	if (JEDEC_MFR(nor->info) == SNOR_MFR_CYPRESS) {
-		switch (nor->info->id[1]) {
-		case 0x2a: /* S25HL (QSPI, 3.3V) */
-		case 0x2b: /* S25HS (QSPI, 1.8V) */
-			nor->fixups = &s25_fixups;
-			break;
-
-#ifdef CONFIG_SPI_FLASH_S28HX_T
-		case 0x5a: /* S28HL (Octal, 3.3V) */
-		case 0x5b: /* S28HS (Octal, 1.8V) */
-			nor->fixups = &s28hx_t_fixups;
-			break;
-#endif
-
-		default:
-			break;
-		}
-	}
-
-	if (CONFIG_IS_ENABLED(SPI_FLASH_BAR) &&
-	    !strcmp(nor->info->name, "s25fl256l"))
-		nor->fixups = &s25fl256l_fixups;
-
-	/* For FS-S (family ID = 0x81)  */
-	if (JEDEC_MFR(nor->info) == SNOR_MFR_SPANSION && nor->info->id[5] == 0x81)
-		nor->fixups = &s25fs_s_fixups;
-#endif
-
-#ifdef CONFIG_SPI_FLASH_MT35XU
-	if (!strcmp(nor->info->name, "mt35xu512aba"))
-		nor->fixups = &mt35xu512aba_fixups;
-#endif
-
-#if CONFIG_IS_ENABLED(SPI_FLASH_MACRONIX)
-	if (JEDEC_MFR(nor->info) == SNOR_MFR_MACRONIX &&
-	    nor->info->flags & SPI_NOR_OCTAL_DTR_READ)
-		nor->fixups = &macronix_octal_fixups;
-#endif /* SPI_FLASH_MACRONIX */
-
-#if CONFIG_IS_ENABLED(SPI_FLASH_WINBOND)
-	if (JEDEC_MFR(nor->info) == SNOR_MFR_WINBOND) {
-		u8 multi_die_models[][2] = {
-			{ 0x40, 0x21 }, /* W25Q01JV */
-			{ 0x70, 0x22 }, /* W25Q02JV */
-		};
-		int i;
-
-		for (i = 0; i < sizeof(multi_die_models) / 2; i++) {
-			if (!memcmp(nor->info->id + 1, multi_die_models[i], 2)) {
-				nor->fixups = &winbond_nor_multi_die_fixups;
-				break;
-			}
-		}
-	}
-#endif /* SPI_FLASH_WINBOND */
 }
 
 int spi_nor_scan(struct spi_nor *nor)
diff --git a/drivers/mtd/spi/winbond.c b/drivers/mtd/spi/winbond.c
index 02c29ccf78c..d1030959b2c 100644
--- a/drivers/mtd/spi/winbond.c
+++ b/drivers/mtd/spi/winbond.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
  */
 
+#include <linux/sizes.h>
 #include <linux/mtd/spi-nor.h>
 #include "sf_internal.h"
 
@@ -140,7 +141,110 @@ static const struct flash_info winbond_nor_parts[] = {
 	{ INFO("w77q51nw", 0xef8a1a, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
 };
 
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+
+#define WINBOND_NOR_OP_SELDIE  0xc2    /* Select active die */
+
+struct winbond_nor_priv {
+	unsigned int n_dice;
+};
+
+/**
+ * winbond_nor_select_die() - Set active die.
+ * @nor:       pointer to 'struct spi_nor'.
+ * @die:       die to set active.
+ *
+ * Certain Winbond chips feature more than a single die. This is mostly hidden
+ * to the user, except that some chips may experience time deviation when
+ * modifying the status bits between dies, which in some corner cases may
+ * produce problematic races. Being able to explicitly select a die to check its
+ * state in this case may be useful.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int winbond_nor_select_die(struct spi_nor *nor, u8 die)
+{
+	struct spi_mem_op op =
+		SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_SELDIE, 1),
+			   SPI_MEM_OP_NO_ADDR,
+			   SPI_MEM_OP_NO_DUMMY,
+			   SPI_MEM_OP_DATA_OUT(1, &die, 0));
+
+	int ret;
+
+	spi_nor_setup_op(nor, &op, SNOR_PROTO_1_1_1);
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret)
+		debug("Error %d selecting die %d\n", ret, die);
+
+	return ret;
+}
+
+static int winbond_nor_multi_die_ready(struct spi_nor *nor)
+{
+	struct winbond_nor_priv *winbond_priv = nor->priv;
+	unsigned int n_dice = winbond_priv ? winbond_priv->n_dice : 1;
+	int ret, i;
+
+	for (i = 0; i < n_dice; i++) {
+		ret = winbond_nor_select_die(nor, i);
+		if (ret)
+			return ret;
+
+		ret = spi_nor_sr_ready(nor);
+		if (ret <= 0)
+			return ret;
+	}
+
+	return 1;
+}
+
+static void winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor,
+						   struct spi_nor_flash_parameter *param)
+{
+	struct winbond_nor_priv *winbond_priv;
+
+	winbond_priv = kmalloc(sizeof(*winbond_priv), GFP_KERNEL);
+	if (!winbond_priv)
+		return;
+
+	winbond_priv->n_dice = param->size / SZ_64M;
+
+	/*
+	 * SFDP supports dice numbers, but this information is only available in
+	 * optional additional tables which are not provided by these chips.
+	 * Dice number has an impact though, because these devices need extra
+	 * care when reading the busy bit.
+	 */
+	nor->priv = winbond_priv;
+	nor->ready = winbond_nor_multi_die_ready;
+}
+
+static struct spi_nor_fixups winbond_nor_multi_die_fixups = {
+	.post_sfdp = winbond_nor_multi_die_post_sfdp_fixups,
+};
+
+static void winbond_set_fixups(struct spi_nor *nor)
+{
+	u8 multi_die_models[][2] = {
+		{ 0x40, 0x21 }, /* W25Q01JV */
+		{ 0x70, 0x22 }, /* W25Q02JV */
+	};
+	int i;
+
+	for (i = 0; i < sizeof(multi_die_models) / 2; i++) {
+		if (!memcmp(nor->info->id + 1, multi_die_models[i], 2)) {
+			nor->fixups = &winbond_nor_multi_die_fixups;
+			break;
+		}
+	}
+}
+#endif /* !CONFIG_IS_ENABLED(SPI_FLASH_TINY) */
+
 const struct spi_nor_manufacturer spi_nor_winbond = {
 	.parts = winbond_nor_parts,
 	.nparts = ARRAY_SIZE(winbond_nor_parts),
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+	.set_fixups = winbond_set_fixups,
+#endif
 };

-- 
2.43.0




More information about the U-Boot mailing list