[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 = ¯onix_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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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 = ¯onix_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