[PATCH v2 00/30] Fix issues with QSPI and OSPI compare failures

A set of patches has been developed to resolve concerns regarding data integrity failures in QSPI and OSPI for the Versal, Versal NET, Zynq, and ZynqMP platforms.
The series has undergone testing with flashes on the default setup, and comprehensive testing is currently underway to test the series with all available flash parts.
These patches are built upon the v5 series, which can be found at the following link: https://lore.kernel.org/all/20231201031839.239567-1-venkatesh.abbarapu@amd.c...
Changes in v2: - Removed the SPI_NOR_HAS_TB flag for gd25lx256e and is25wx256 flashes since it already exists in a tree.
Algapally Santosh Sagar (1): mtd: spi-nor-ids: Add support for W25Q02NW
Ashok Reddy Soma (10): mtd: spi-nor: Enable mt35xu512aba_fixups for all mt35xx flashes mtd: spi-nor: Add support for cross die read in dual flash configuration mtd: spi-nor: Enable DTR octal flash program mtd: spi-nor: Send write disable cmd after every write enable mtd: spi-nor: Check SNOR_F_IO_MODE_EN_VOLATILE only if SFDP is enabled spi: cadence_qspi: Set tshsl_ns to at least one sclk_ns spi: cadence_qspi: Clean up registers in init spi: cadence_qspi: Initialize read and write watermark registers spi: cadence_qspi: Enable ECO bit for higher frequencies spi: cadence_qspi: Write aligned byte length to ahbbase
T Karthik Reddy (9): mtd: spi-nor: Add config to enable flash DTR mtd: spi-nor-core: Set dummy buswidth equal to data buswidth spi: mtd: Use split reads if multi-die flag is set mtd: spi-nor: program quad enable bit for winbond flashes spi: cadence_qspi: Setup ddr mode in cadence qspi driver spi: cadence-qspi: Switch SDR/DTR using SPI_FLASH_DTR_ENABLE config spi: cadence_ospi_versal: ospi ddr changes in cadence ospi versal driver spi: cadence_qspi: Add spi mem dtr support ops mtd: spi-nor: Add block protection support for micron flashes
Tejas Bhumkar (5): arm64: versal: Enable defconfig for Micron octal flashes mtd: spi-nor: Update erase operation function spi: cadence_qspi: Fix versal ospi indirect write timed out issue arm64: versal: Enable soft reset support for xspi flashes arm64: versal: Enable octal DTR mode
Venkatesh Yadav Abbarapu (5): mtd: spi-nor: Update block protection flags for flash parts mtd: spi-nor: Add support for locking on Macronix nor flashes mtd: spi-nor: Add support for locking on ISSI nor flashes mtd: spi-nor: Add support for locking on GIGADEVICE nor flashes mtd: spi-nor: Add support for locking on Spansion nor flashes
configs/xilinx_versal_virt_defconfig | 4 + drivers/mtd/spi/Kconfig | 7 + drivers/mtd/spi/sf_internal.h | 8 + drivers/mtd/spi/spi-nor-core.c | 2028 +++++++++++++++++++++++--- drivers/mtd/spi/spi-nor-ids.c | 34 +- drivers/spi/cadence_ospi_versal.c | 77 +- drivers/spi/cadence_qspi.c | 403 ++++- drivers/spi/cadence_qspi.h | 71 + drivers/spi/cadence_qspi_apb.c | 107 +- include/linux/mtd/spi-nor.h | 22 + include/spi.h | 4 +- 11 files changed, 2541 insertions(+), 224 deletions(-)

From: T Karthik Reddy t.karthik.reddy@amd.com
The spi-nor framework will set up the flash parameters by reading the flash id table flags, which include cmd opcodes, address width, dummy bytes, and bus width. In case, flash supports octal DTR mode and the controller does not support the DTR. There is no process to switch back to SDR mode. To avoid this issue, create a Kconfig option SPI_FLASH_DTR_ENABLE to explicitly specify to enable/disable flash DTR support. This config is disabled by default. Do not initialize the mt35xu512aba post sfdp fixups unless SPI_FLASH_DTR_ENABLE is enabled to avoid configuring DTR.
Signed-off-by: T Karthik Reddy t.karthik.reddy@amd.com Co-developed-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/Kconfig | 7 +++++++ drivers/mtd/spi/spi-nor-core.c | 12 +++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig index 732b076045..e0898074a2 100644 --- a/drivers/mtd/spi/Kconfig +++ b/drivers/mtd/spi/Kconfig @@ -202,6 +202,13 @@ config SPI_FLASH_MT35XU because the fixup hooks for this flash add extra size overhead. Boards that don't use the flash can disable this to save space.
+config SPI_FLASH_DTR_ENABLE + bool "Enable Flash DTR support" + help + Select this config to enable DTR mode by spi-nor framework. + This config provides an option to explicitly enable/disable the DTR + support even though the flash id flags specify flash supports DTR mode. + config SPI_FLASH_SST bool "SST SPI flash support" help diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 5c3ffc80eb..473d9f41f3 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -2962,7 +2962,8 @@ static int spi_nor_init_params(struct spi_nor *nor, SNOR_PROTO_1_1_8); }
- if (info->flags & SPI_NOR_OCTAL_DTR_READ) { + if (CONFIG_IS_ENABLED(SPI_FLASH_DTR_ENABLE) && + info->flags & SPI_NOR_OCTAL_DTR_READ) { 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_READ_FAST, @@ -2978,8 +2979,10 @@ static int spi_nor_init_params(struct spi_nor *nor, * 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, SNOR_PROTO_8_8_8_DTR); + if (CONFIG_IS_ENABLED(SPI_FLASH_DTR_ENABLE)) { + spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP_8_8_8_DTR], + SPINOR_OP_PP, SNOR_PROTO_8_8_8_DTR); + }
if (info->flags & SPI_NOR_QUAD_READ) { params->hwcaps.mask |= SNOR_HWCAPS_PP_1_1_4; @@ -4011,6 +4014,9 @@ static void mt35xu512aba_default_init(struct spi_nor *nor) static void mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor, struct spi_nor_flash_parameter *params) { + if (!CONFIG_IS_ENABLED(SPI_FLASH_DTR_ENABLE)) + return; + /* 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],

On Wed, Dec 6, 2023 at 3:02 PM Tejas Bhumkar tejas.arvind.bhumkar@amd.com wrote:
From: T Karthik Reddy t.karthik.reddy@amd.com
The spi-nor framework will set up the flash parameters by reading the flash id table flags, which include cmd opcodes, address width, dummy bytes, and bus width. In case, flash supports octal DTR mode and the controller does not support the DTR. There is no process to switch back to SDR mode. To avoid this issue, create a Kconfig option SPI_FLASH_DTR_ENABLE to explicitly specify to enable/disable flash DTR support. This config is disabled by default.
We cannot control controller fixup in flash, DTR read based on the DTR flag I don't think adding extra CONFIG to hack the controller with impact is.
Jagan,

[AMD Official Use Only - General]
Hi Jagan,
-----Original Message----- From: Jagan Teki jagan@amarulasolutions.com Sent: Wednesday, December 20, 2023 1:00 PM To: Bhumkar, Tejas Arvind tejas.arvind.bhumkar@amd.com Cc: u-boot@lists.denx.de; joe.hershberger@ni.com; rfried.dev@gmail.com; Simek, Michal michal.simek@amd.com; vigneshr@ti.com; git@xilinx.com; T Karthik Reddy t.karthik.reddy@amd.com Subject: Re: [PATCH v2 01/30] mtd: spi-nor: Add config to enable flash DTR
Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
On Wed, Dec 6, 2023 at 3:02 PM Tejas Bhumkar tejas.arvind.bhumkar@amd.com wrote:
From: T Karthik Reddy t.karthik.reddy@amd.com
The spi-nor framework will set up the flash parameters by reading the flash id table flags, which include cmd opcodes, address width, dummy bytes, and bus width. In case, flash supports octal DTR mode and the controller does not support the DTR. There is no process to switch back to SDR mode. To avoid this issue, create a Kconfig option SPI_FLASH_DTR_ENABLE to explicitly specify to enable/disable flash DTR support. This config is disabled by default.
We cannot control controller fixup in flash, DTR read based on the DTR flag I don't think adding extra CONFIG to hack the controller with impact is.
[Tejas] : By default, this configuration is set to Disabled. It serves as a convenient option for operating the flash between SDR and DDR without requiring any adjustments to the nor-id table flags.
Regards, Tejas.
Jagan,

On Sun, Dec 31, 2023 at 11:27 PM Bhumkar, Tejas Arvind tejas.arvind.bhumkar@amd.com wrote:
[AMD Official Use Only - General]
Hi Jagan,
-----Original Message----- From: Jagan Teki jagan@amarulasolutions.com Sent: Wednesday, December 20, 2023 1:00 PM To: Bhumkar, Tejas Arvind tejas.arvind.bhumkar@amd.com Cc: u-boot@lists.denx.de; joe.hershberger@ni.com; rfried.dev@gmail.com; Simek, Michal michal.simek@amd.com; vigneshr@ti.com; git@xilinx.com; T Karthik Reddy t.karthik.reddy@amd.com Subject: Re: [PATCH v2 01/30] mtd: spi-nor: Add config to enable flash DTR
Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
On Wed, Dec 6, 2023 at 3:02 PM Tejas Bhumkar tejas.arvind.bhumkar@amd.com wrote:
From: T Karthik Reddy t.karthik.reddy@amd.com
The spi-nor framework will set up the flash parameters by reading the flash id table flags, which include cmd opcodes, address width, dummy bytes, and bus width. In case, flash supports octal DTR mode and the controller does not support the DTR. There is no process to switch back to SDR mode. To avoid this issue, create a Kconfig option SPI_FLASH_DTR_ENABLE to explicitly specify to enable/disable flash DTR support. This config is disabled by default.
We cannot control controller fixup in flash, DTR read based on the DTR flag I don't think adding extra CONFIG to hack the controller with impact is.
[Tejas] : By default, this configuration is set to Disabled. It serves as a convenient option for operating the flash between SDR and DDR without requiring any adjustments to the nor-id table flags.
This look like controller hack to me, may be a clear negotiation b/w controller and flag might have proper solution.
Jagan.

From: T Karthik Reddy t.karthik.reddy@xilinx.com
In current implementation dummy buswidth is set equal to address buswidth. In case of quad spi (mode 1-1-4), where address width is 1 the dummy bytes will be calculated to 1(8 dummy cycles) and dummy buswidth is set to 1. Due to this, the controller driver will introduce 8 dummy cycles on data line(D0) during read operation.
But since we are using 4 data lines in case of qspi, we need to change this dummy bus width to 4. This will make dummy bytes to 4 inplace of 1. This will be taken care in controller driver by dividing with dummy buswidth again as in below code, which makes dummy cycles to 8 as earlier.
dummy_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth;
So with this change dummy cycles will be on all data lines(D0-D3) and it is taken care for all the configurations(single, dual, quad and octal).
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@xilinx.com Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 473d9f41f3..8949dab548 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -260,7 +260,7 @@ void spi_nor_setup_op(const struct spi_nor *nor, op->addr.buswidth = spi_nor_get_protocol_addr_nbits(proto);
if (op->dummy.nbytes) - op->dummy.buswidth = spi_nor_get_protocol_addr_nbits(proto); + op->dummy.buswidth = spi_nor_get_protocol_data_nbits(proto);
if (op->data.nbytes) op->data.buswidth = spi_nor_get_protocol_data_nbits(proto);

The Micron MT35 series octal flashes can be activated through the configuration option CONFIG_SPI_FLASH_MT35XU. To ensure their detection, enable this option in the default defconfig for octal flashes.
Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- configs/xilinx_versal_virt_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/configs/xilinx_versal_virt_defconfig b/configs/xilinx_versal_virt_defconfig index 6a2c03ccdd..ec7caacca0 100644 --- a/configs/xilinx_versal_virt_defconfig +++ b/configs/xilinx_versal_virt_defconfig @@ -95,6 +95,7 @@ CONFIG_MMC_SDHCI_ZYNQ=y CONFIG_ZYNQ_SDHCI_MIN_FREQ=100000 CONFIG_MTD=y CONFIG_DM_SPI_FLASH=y +CONFIG_SPI_FLASH_MT35XU=y CONFIG_SPI_FLASH_GIGADEVICE=y CONFIG_SPI_FLASH_ISSI=y CONFIG_SPI_FLASH_MACRONIX=y

From: Ashok Reddy Soma ashok.reddy.soma@amd.com
Enable mt35xu512aba_fixups for all mt35 series flashes to work in DTR mode, and return after nor->fixups is updated, otherwise it will get overwritten with macronix_octal_fixups. This flash works in DTR mode only if CONFIG_SPI_FLASH_MT35XU is enabled and SPI_NOR_OCTAL_DTR_READ flag is set in id table.
Additionally, a new flag, "SPI_XFER_SET_DDR," has been introduced to instruct the Ospi controller driver to switch to DDR mode.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 8 +++++++- include/spi.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 8949dab548..e505648e5d 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -3991,6 +3991,7 @@ static int spi_nor_micron_octal_dtr_enable(struct spi_nor *nor) if (ret) return ret;
+ nor->spi->flags |= SPI_XFER_SET_DDR; buf = SPINOR_MT_OCT_DTR; op = (struct spi_mem_op) SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1), @@ -4305,8 +4306,13 @@ void spi_nor_set_fixups(struct spi_nor *nor) #endif
#ifdef CONFIG_SPI_FLASH_MT35XU - if (!strcmp(nor->info->name, "mt35xu512aba")) + if (!strcmp(nor->info->name, "mt35xu512aba") || + !strcmp(nor->info->name, "mt35xl512aba") || + !strcmp(nor->info->name, "mt35xu01g") || + !strcmp(nor->info->name, "mt35xu02g")) { nor->fixups = &mt35xu512aba_fixups; + return; + } #endif
#if CONFIG_IS_ENABLED(SPI_FLASH_MACRONIX) diff --git a/include/spi.h b/include/spi.h index 6bc8808bb9..eb015ecbf5 100644 --- a/include/spi.h +++ b/include/spi.h @@ -171,6 +171,7 @@ struct spi_slave { #define SPI_XFER_ONCE (SPI_XFER_BEGIN | SPI_XFER_END) #define SPI_XFER_U_PAGE BIT(4) #define SPI_XFER_STACKED BIT(5) +#define SPI_XFER_SET_DDR BIT(6) /* * Flag indicating that the spi-controller has multi chip select * capability and can assert/de-assert more than one chip select

From: Ashok Reddy Soma ashok.reddy.soma@xilinx.com
In a dual parallel configuration, halve the read offset. Determine whether the read offset points to the lower or upper flash in a dual stacked configuration and set the corresponding flags accordingly.
Include support for cases where the read involves an odd number of bytes.
Extend support for cross-die reads in flash memory devices that contain multiple dies within them.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@xilinx.com Signed-off-by: Michal Simek michal.simek@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 61 ++++++++++++++++++++++++++++++---- include/spi.h | 3 +- 2 files changed, 56 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index e505648e5d..f6e7592458 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -1503,11 +1503,8 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, struct spi_nor *nor = mtd_to_spi_nor(mtd); int ret; u32 offset = from; - u32 stack_shift = 0; - u32 read_len = 0; - u32 rem_bank_len = 0; - u8 bank; - u8 is_ofst_odd = 0; + u32 bank_size, stack_shift = 0, read_len = 0, rem_bank_len = 0; + u8 bank, cur_bank, nxt_bank, is_ofst_odd = 0;
dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);
@@ -1541,6 +1538,40 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, } }
+ if (nor->addr_width == 4) { + /* + * Some flash devices like N25Q512 have multiple dies + * in it. Read operation in these devices is bounded + * by its die segment. In a continuous read, across + * multiple dies, when the last byte of the selected + * die segment is read, the next byte read is the + * first byte of the same die segment. This is Die + * cross over issue. So to handle this issue, split + * a read transaction, that spans across multiple + * banks, into one read per bank. Bank size is 16MB + * for single and dual stacked mode and 32MB for dual + * parallel mode. + */ + if (nor->spi && nor->spi->multi_die) { + bank_size = SZ_16M; + if (nor->flags & SNOR_F_HAS_PARALLEL) + bank_size <<= 1; + cur_bank = offset / bank_size; + nxt_bank = (offset + len) / bank_size; + if (cur_bank != nxt_bank) + rem_bank_len = (bank_size * + (cur_bank + 1)) - + offset; + else + rem_bank_len = (mtd->size >> + stack_shift) - + offset; + } else { + rem_bank_len = (mtd->size >> stack_shift) - + offset; + } + } + if (nor->flags & SNOR_F_HAS_PARALLEL) offset /= 2;
@@ -1552,6 +1583,15 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, #endif }
+ if (len < rem_bank_len) + read_len = len; + else + read_len = rem_bank_len; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto read_err; + ret = nor->read(nor, offset, read_len, buf); if (ret == 0) { /* We shouldn't see 0-length reads */ @@ -1561,8 +1601,15 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, if (ret < 0) goto read_err;
- *retlen += ret; - buf += ret; + if (is_ofst_odd == 1) { + memcpy(buf, (buf + 1), (len - 1)); + *retlen += (ret - 1); + buf += ret - 1; + is_ofst_odd = 0; + } else { + *retlen += ret; + buf += ret; + } from += ret; len -= ret; } diff --git a/include/spi.h b/include/spi.h index eb015ecbf5..9014066ee3 100644 --- a/include/spi.h +++ b/include/spi.h @@ -165,7 +165,7 @@ struct spi_slave { unsigned int max_write_size; void *memory_map;
- u8 flags; + u32 flags; #define SPI_XFER_BEGIN BIT(0) /* Assert CS before transfer */ #define SPI_XFER_END BIT(1) /* Deassert CS after transfer */ #define SPI_XFER_ONCE (SPI_XFER_BEGIN | SPI_XFER_END) @@ -179,6 +179,7 @@ struct spi_slave { */ bool multi_cs_cap; u32 bytemode; + bool multi_die; /* flash with multiple dies */ };
/**

From: Ashok Reddy Soma ashok.reddy.soma@amd.com
Define a flag SPI_NOR_OCTAL_DTR_PP and if enabled in spi-nor-ids table, enable octal DTR page program in the framework.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/sf_internal.h | 1 + drivers/mtd/spi/spi-nor-core.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index d3ef69ec74..9c09f97ce2 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -69,6 +69,7 @@ struct flash_info { #define SPI_NOR_HAS_SST26LOCK BIT(15) /* Flash supports lock/unlock via BPR */ #define SPI_NOR_OCTAL_READ BIT(16) /* Flash supports Octal Read */ #define SPI_NOR_OCTAL_DTR_READ BIT(17) /* Flash supports Octal DTR Read */ +#define SPI_NOR_OCTAL_DTR_PP BIT(18) /* Flash supports Octal DTR page program */ };
extern const struct flash_info spi_nor_ids[]; diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index f6e7592458..63f78baaf4 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -3026,7 +3026,8 @@ static int spi_nor_init_params(struct spi_nor *nor, * Since xSPI Page Program opcode is backward compatible with * Legacy SPI, use Legacy SPI opcode there as well. */ - if (CONFIG_IS_ENABLED(SPI_FLASH_DTR_ENABLE)) { + if (CONFIG_IS_ENABLED(SPI_FLASH_DTR_ENABLE) && info->flags & SPI_NOR_OCTAL_DTR_PP) { + params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR; spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP_8_8_8_DTR], SPINOR_OP_PP, SNOR_PROTO_8_8_8_DTR); }

From: T Karthik Reddy t.karthik.reddy@xilinx.com
Some flash devices have multiple dies in it & has die cross over issue. When SPI_NOR_MULTI_DIE flag is set in flash id table use it to enable split reads to avoid above issue. Define SPI_NOR_MULTI_DIE new flag to flash id flags. Remove SPI_FLASH_SPLIT_READ config and related code from the zynq and zynqmp qspi drivers as it is redundant.
Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/sf_internal.h | 1 + drivers/mtd/spi/spi-nor-core.c | 3 +++ 2 files changed, 4 insertions(+)
diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index 9c09f97ce2..2cbdea60b0 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -70,6 +70,7 @@ struct flash_info { #define SPI_NOR_OCTAL_READ BIT(16) /* Flash supports Octal Read */ #define SPI_NOR_OCTAL_DTR_READ BIT(17) /* Flash supports Octal DTR Read */ #define SPI_NOR_OCTAL_DTR_PP BIT(18) /* Flash supports Octal DTR page program */ +#define SPI_NOR_MULTI_DIE BIT(19) /* Flash has multi dies & need split reads*/ };
extern const struct flash_info spi_nor_ids[]; diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 63f78baaf4..ace5da9591 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -4485,6 +4485,9 @@ int spi_nor_scan(struct spi_nor *nor) if (info->flags & SPI_NOR_NO_ERASE) mtd->flags |= MTD_NO_ERASE;
+ if (info->flags & SPI_NOR_MULTI_DIE) + nor->spi->multi_die = true; + nor->page_size = params.page_size; mtd->writebufsize = nor->page_size;

From: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
The block protection flags for Gigadevice, Spansion, and ISSI flash memory have been modified. Additionally, new flags for SPI_NOR_OCTAL_DTR_READ and octal DTR page programming have been introduced for Micron OSPI flashes. Furthermore, the flashes mt35xu01g and mt35xu02g have been incorporated into the CONFIG_SPI_FLASH_MT35XU configuration, so that in driver mt35xu512aba_fixups will be applied.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-ids.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c index 3cb132dcff..0dbd385265 100644 --- a/drivers/mtd/spi/spi-nor-ids.c +++ b/drivers/mtd/spi/spi-nor-ids.c @@ -122,9 +122,9 @@ const struct flash_info spi_nor_ids[] = { {INFO("gd25b256", 0xc84019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_4B_OPCODES) }, {INFO("gd25b512", 0xc8471A, 0, 64 * 1024, 1024, SECT_4K | - SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_4B_OPCODES)}, + SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_4B_OPCODES | SPI_NOR_HAS_TB)}, {INFO("gd55b01g", 0xc8471B, 0, 64 * 1024, 2048, SECT_4K | - SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_4B_OPCODES)}, + SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_4B_OPCODES | SPI_NOR_HAS_TB)}, {INFO("gd55b02g", 0xc8471C, 0, 64 * 1024, 4096, SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_4B_OPCODES)}, {INFO("gd25f64", 0xc84317, 0, 64 * 1024, 128, SECT_4K | @@ -212,11 +212,11 @@ const struct flash_info spi_nor_ids[] = { { INFO("is25lp128", 0x9d6018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ) }, { INFO("is25lp256", 0x9d6019, 0, 64 * 1024, 512, - SECT_4K | SPI_NOR_DUAL_READ) }, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_HAS_TB) }, { INFO("is25lp512", 0x9d601a, 0, 64 * 1024, 1024, - SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_HAS_TB) }, { INFO("is25lp01g", 0x9d601b, 0, 64 * 1024, 2048, - SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_HAS_TB) }, { INFO("is25wp008", 0x9d7014, 0, 64 * 1024, 16, SPI_NOR_QUAD_READ) }, { INFO("is25wp016", 0x9d7015, 0, 64 * 1024, 32, SPI_NOR_QUAD_READ) }, { INFO("is25wp032", 0x9d7016, 0, 64 * 1024, 64, @@ -312,11 +312,15 @@ const struct flash_info spi_nor_ids[] = { { INFO("mt25qu02g", 0x20bb22, 0, 64 * 1024, 4096, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) }, { INFO("mt25ql02g", 0x20ba22, 0, 64 * 1024, 4096, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE | SPI_NOR_4B_OPCODES) }, #ifdef CONFIG_SPI_FLASH_MT35XU - { INFO("mt35xl512aba", 0x2c5a1a, 0, 128 * 1024, 512, USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES | SPI_NOR_OCTAL_DTR_READ) }, - { INFO("mt35xu512aba", 0x2c5b1a, 0, 128 * 1024, 512, USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES | SPI_NOR_OCTAL_DTR_READ) }, + { INFO("mt35xl512aba", 0x2c5a1a, 0, 128 * 1024, 512, + USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES | SPI_NOR_OCTAL_DTR_READ | SPI_NOR_OCTAL_DTR_PP) }, + { INFO("mt35xu512aba", 0x2c5b1a, 0, 128 * 1024, 512, + USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES | SPI_NOR_OCTAL_DTR_READ | SPI_NOR_OCTAL_DTR_PP) }, + { INFO6("mt35xu01g", 0x2c5b1b, 0x104100, 128 * 1024, 1024, + USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES | SPI_NOR_OCTAL_DTR_READ | SPI_NOR_OCTAL_DTR_PP) }, + { INFO("mt35xu02g", 0x2c5b1c, 0, 128 * 1024, 2048, + USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES | SPI_NOR_OCTAL_DTR_READ | SPI_NOR_OCTAL_DTR_PP) }, #endif /* CONFIG_SPI_FLASH_MT35XU */ - { INFO6("mt35xu01g", 0x2c5b1b, 0x104100, 128 * 1024, 1024, USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES) }, - { INFO("mt35xu02g", 0x2c5b1c, 0, 128 * 1024, 2048, USE_FSR | SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES) }, #endif #ifdef CONFIG_SPI_FLASH_SPANSION /* SPANSION */ /* Spansion/Cypress -- single (large) sector size only, at least @@ -325,8 +329,8 @@ const struct flash_info spi_nor_ids[] = { { INFO("s25sl032p", 0x010215, 0x4d00, 64 * 1024, 64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("s25sl064p", 0x010216, 0x4d00, 64 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("s25fl256s0", 0x010219, 0x4d00, 256 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, - { INFO("s25fl256s1", 0x010219, 0x4d01, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, - { INFO6("s25fl512s", 0x010220, 0x4d0080, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { INFO("s25fl256s1", 0x010219, 0x4d01, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR | SPI_NOR_HAS_TB) }, + { INFO6("s25fl512s", 0x010220, 0x4d0080, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR | SPI_NOR_HAS_TB) }, { INFO6("s25fs512s", 0x010220, 0x4d0081, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, { INFO("s25fl512s_256k", 0x010220, 0x4d00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, { INFO("s25fl512s_64k", 0x010220, 0x4d01, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, @@ -334,7 +338,7 @@ const struct flash_info spi_nor_ids[] = { { INFO("s70fs01gs_256k", 0x010221, 0x4d00, 256 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("s25sl12800", 0x012018, 0x0300, 256 * 1024, 64, 0) }, { INFO("s25sl12801", 0x012018, 0x0301, 64 * 1024, 256, 0) }, - { INFO6("s25fl128s", 0x012018, 0x4d0180, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { INFO6("s25fl128s", 0x012018, 0x4d0180, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR | SPI_NOR_HAS_TB) }, { INFO("s25fl129p0", 0x012018, 0x4d00, 256 * 1024, 64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, { INFO("s25fl129p1", 0x012018, 0x4d01, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, { INFO("s25sl008a", 0x010213, 0, 64 * 1024, 16, 0) },

From: Algapally Santosh Sagar santoshsagar.algapally@amd.com
Add support for Winbond 256MB flash W25Q02NW which supports 4byte opcodes and also dual and quad read.
Signed-off-by: Algapally Santosh Sagar santoshsagar.algapally@amd.com Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-ids.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c index 0dbd385265..51e8cce407 100644 --- a/drivers/mtd/spi/spi-nor-ids.c +++ b/drivers/mtd/spi/spi-nor-ids.c @@ -517,6 +517,12 @@ const struct flash_info spi_nor_ids[] = { SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) }, + { + INFO("w25q02nw", 0xef8022, 0, 64 * 1024, 4096, + SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4B_OPCODES | + SPI_NOR_MULTI_DIE) + }, { INFO("w25q80", 0xef5014, 0, 64 * 1024, 16, SECT_4K) }, { INFO("w25q80bl", 0xef4014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("w25q16cl", 0xef4015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },

From: T Karthik Reddy t.karthik.reddy@xilinx.com
Added support to program quad enable bit for Winbond flash memory. Previously, the quad enable function from Spansion was used for this purpose. However, for Winbond flash memory, the quad enable bit is configured by programming the Write Status Register-2 (SR-2) rather than the Configuration Register (CR).
Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Co-developed-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 48 ++++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 2 ++ 2 files changed, 50 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index ace5da9591..454ae6cd4e 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -2141,6 +2141,49 @@ static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr) return 0; }
+/** + * winbond_quad_enable() - Set QE bit in status register-2 + * @nor: pointer to a 'struct spi_nor' + * + * Return: 0 on success, -errno otherwise. + */ +static int winbond_quad_enable(struct spi_nor *nor) +{ + int ret; + u8 cr = 0; + + /* Check current Quad Enable bit value. */ + cr = read_cr(nor); + if (cr < 0) { + dev_dbg(nor->dev, + "error while reading configuration register\n"); + return -EINVAL; + } + + if (cr & SR2_QUAD_EN_BIT1) + return 0; + + cr |= SR2_QUAD_EN_BIT1; + + write_enable(nor); + + ret = nor->write_reg(nor, SPINOR_OP_WIN_WRSR2, &cr, 1); + if (ret < 0) { + dev_dbg(nor->dev, + "error while writing configuration register\n"); + return -EINVAL; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret) { + dev_dbg(nor->dev, + "timeout while writing configuration register\n"); + return ret; + } + + return write_disable(nor); +} + /** * spansion_read_cr_quad_enable() - set QE bit in Configuration Register. * @nor: pointer to a 'struct spi_nor' @@ -3052,6 +3095,11 @@ static int spi_nor_init_params(struct spi_nor *nor, case SNOR_MFR_MICRON: break;
+#if defined(CONFIG_SPI_FLASH_WINBOND) + case SNOR_MFR_WINBOND: + params->quad_enable = winbond_quad_enable; + break; +#endif default: #if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND) /* Kept only for backward compatibility purpose. */ diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 72206f51ad..34e0aedc24 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -48,6 +48,7 @@ #define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */ #define SPINOR_OP_RDSR2 0x3f /* Read status register 2 */ #define SPINOR_OP_WRSR2 0x3e /* Write status register 2 */ +#define SPINOR_OP_WIN_WRSR2 0x31 /* Winbond Write status register 2 */ #define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */ #define SPINOR_OP_READ_FAST 0x0b /* Read data bytes (high frequency) */ #define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual Output SPI) */ @@ -187,6 +188,7 @@
/* Status Register 2 bits. */ #define SR2_QUAD_EN_BIT7 BIT(7) +#define SR2_QUAD_EN_BIT1 BIT(1) /* Winbond Quad I/O */
/* * Maximum number of flashes that can be connected

From: Ashok Reddy Soma ashok.reddy.soma@xilinx.com
Write enable(06h) command will be sent to a flash device to set the write enable latch bit before every program, erase, write command. After that write disable command (04h) needs to be sent to clear the write enable latch.
This write_disable() is missing at the majority of the places in the driver, add it to clear write enable latch.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com Acked-by: Amit Kumar Mahapatra amit.kumar-mahapatra@xilinx.com --- drivers/mtd/spi/spi-nor-core.c | 47 +++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 454ae6cd4e..d790116994 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -873,6 +873,7 @@ static int spi_nor_wait_till_ready(struct spi_nor *nor) static int clean_bar(struct spi_nor *nor) { u8 cmd, bank_sel = 0; + int ret;
if (nor->bank_curr == 0) return 0; @@ -880,7 +881,11 @@ static int clean_bar(struct spi_nor *nor) nor->bank_curr = 0; write_enable(nor);
- return nor->write_reg(nor, cmd, &bank_sel, 1); + ret = nor->write_reg(nor, cmd, &bank_sel, 1); + if (ret) + return ret; + + return write_disable(nor); }
static int write_bar(struct spi_nor *nor, u32 offset) @@ -1070,11 +1075,15 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) nor->spi->flags &= ~SPI_XFER_U_PAGE; } } + if (nor->addr_width == 3) { #ifdef CONFIG_SPI_FLASH_BAR - ret = write_bar(nor, addr); - if (ret < 0) - goto erase_err; + /* Update Extended Address Register */ + ret = write_bar(nor, addr); + if (ret < 0) + goto erase_err; #endif + } + ret = write_enable(nor); if (ret < 0) goto erase_err; @@ -1195,6 +1204,10 @@ static int write_sr_and_check(struct spi_nor *nor, u8 status_new, u8 mask) if (ret) return ret;
+ ret = write_disable(nor); + if (ret) + return ret; + ret = read_sr(nor); if (ret < 0) return ret; @@ -1758,13 +1771,18 @@ static int sst26_lock_ctl(struct spi_nor *nor, loff_t ofs, uint64_t len, enum lo if (ctl == SST26_CTL_CHECK) return 0;
+ /* Write latch enable before write operation */ + ret = write_enable(nor); + if (ret) + return ret; + ret = nor->write_reg(nor, SPINOR_OP_WRITE_BPR, bpr_buff, bpr_size); if (ret < 0) { dev_err(nor->dev, "fail to write block-protection register\n"); return ret; }
- return 0; + return write_disable(nor); }
static int sst26_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) @@ -2138,7 +2156,7 @@ static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr) return ret; }
- return 0; + return write_disable(nor); }
/** @@ -4248,6 +4266,7 @@ static int spi_nor_octal_dtr_enable(struct spi_nor *nor)
static int spi_nor_init(struct spi_nor *nor) { + u8 sr2; int err;
if (nor->flags & SNOR_F_HAS_PARALLEL) @@ -4271,6 +4290,22 @@ static int spi_nor_init(struct spi_nor *nor) write_enable(nor); write_sr(nor, 0); spi_nor_wait_till_ready(nor); + + if (JEDEC_MFR(nor->info) == SNOR_MFR_WINBOND) { + write_enable(nor); + sr2 = 0; + err = nor->write_reg(nor, + SPINOR_OP_WIN_WRSR2, &sr2, 1); + if (err < 0) { + dev_dbg(nor->dev, + "error while writing SR-2 register\n"); + return -EINVAL; + } + spi_nor_wait_till_ready(nor); + } + err = write_disable(nor); + if (err) + return err; }
if (nor->quad_enable) {

If the system is in a dual parallel configuration, it's necessary to halve the erase size since the erase command operates on two flashes simultaneously. When dealing with a dual-stacked configuration, determine whether the erase offset refers to the top or bottom flash, and subsequently, adjust the flag for the relevant flash. Consequently, the argument for the spi_nor_erase_sector function has been modified from addr to offset.
Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index d790116994..43435e79cc 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -1078,7 +1078,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) if (nor->addr_width == 3) { #ifdef CONFIG_SPI_FLASH_BAR /* Update Extended Address Register */ - ret = write_bar(nor, addr); + ret = write_bar(nor, offset); if (ret < 0) goto erase_err; #endif @@ -1092,7 +1092,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { ret = spi_nor_erase_chip(nor); } else { - ret = spi_nor_erase_sector(nor, addr); + ret = spi_nor_erase_sector(nor, offset); } if (ret < 0) goto erase_err;

From: Ashok Reddy Soma ashok.reddy.soma@amd.com
With 'commit bebdc237507c ("mtd: spi-nor: Parse SFDP SCCR Map")', support for spi_nor_parse_sccr is added under SFDP. But the flag SNOR_F_IO_MODE_EN_VOLATILE in spi_nor_octal_dtr_enable is always checked. Check this flag only if SPI_FLASH_SFDP_SUPPORT enabled.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 43435e79cc..ccda722df5 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -4252,8 +4252,9 @@ static int spi_nor_octal_dtr_enable(struct spi_nor *nor) nor->write_proto == SNOR_PROTO_8_8_8_DTR)) return 0;
- if (!(nor->flags & SNOR_F_IO_MODE_EN_VOLATILE)) - return 0; + if (CONFIG_IS_ENABLED(SPI_FLASH_SFDP_SUPPORT)) + if (!(nor->flags & SNOR_F_IO_MODE_EN_VOLATILE)) + return 0;
ret = nor->octal_dtr_enable(nor); if (ret)

From: T Karthik Reddy t.karthik.reddy@xilinx.com
To switch the OSPI controller from SDR to DDR mode, the RX delay should be configured through flash tuning. Begin by establishing a constant value for the TX delay and then increase the RX delay by inspecting the flash IDs. To fine-tune the RX delay, compare the flash IDs with a set of predetermined "golden" flash IDs. This tuning process is borrowed from the Linux Cadence driver. Moreover, make the necessary adjustments in the DDR PHY setup to facilitate the transition from SDR to DDR mode within the OSPI controller.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_ospi_versal.c | 25 +++ drivers/spi/cadence_qspi.c | 321 +++++++++++++++++++++++++++++- drivers/spi/cadence_qspi.h | 52 +++++ drivers/spi/cadence_qspi_apb.c | 33 ++- 4 files changed, 416 insertions(+), 15 deletions(-)
diff --git a/drivers/spi/cadence_ospi_versal.c b/drivers/spi/cadence_ospi_versal.c index e02a3b3de3..74994a690d 100644 --- a/drivers/spi/cadence_ospi_versal.c +++ b/drivers/spi/cadence_ospi_versal.c @@ -15,6 +15,7 @@ #include <cpu_func.h> #include <zynqmp_firmware.h> #include <asm/arch/hardware.h> +#include <soc.h> #include "cadence_qspi.h" #include <dt-bindings/power/xlnx-versal-power.h>
@@ -129,6 +130,30 @@ int cadence_qspi_apb_wait_for_dma_cmplt(struct cadence_spi_priv *priv) return 0; }
+static const struct soc_attr matches[] = { + { .family = "Versal", .revision = "v2" }, + { } +}; + +/* + * cadence_qspi_versal_set_dll_mode checks for silicon version + * and set the DLL mode. + * Returns 0 in case of success, -ENOTSUPP in case of failure. + */ +int cadence_qspi_versal_set_dll_mode(struct udevice *dev) +{ + struct cadence_spi_priv *priv = dev_get_priv(dev); + const struct soc_attr *attr; + + attr = soc_device_match(matches); + if (attr) { + priv->dll_mode = CQSPI_DLL_MODE_MASTER; + return 0; + } + + return -ENOTSUPP; +} + #if defined(CONFIG_DM_GPIO) int cadence_qspi_versal_flash_reset(struct udevice *dev) { diff --git a/drivers/spi/cadence_qspi.c b/drivers/spi/cadence_qspi.c index cc3a54f295..32f91825fd 100644 --- a/drivers/spi/cadence_qspi.c +++ b/drivers/spi/cadence_qspi.c @@ -89,11 +89,21 @@ static int spi_calibration(struct udevice *bus, uint hz) /* Enable QSPI */ cadence_qspi_apb_controller_enable(base);
- /* read the ID which will be our golden value */ - err = cadence_spi_read_id(priv, 3, (u8 *)&idcode); - if (err) { - puts("SF: Calibration failed (read)\n"); - return err; + if (!priv->ddr_init) { + /* read the ID which will be our golden value */ + err = cadence_spi_read_id(priv, CQSPI_READ_ID_LEN, (u8 *)&idcode); + if (err) { + puts("SF: Calibration failed (read)\n"); + return err; + } + + /* Save flash id's for further use */ + priv->device_id[0] = (u8)idcode; + priv->device_id[1] = (u8)(idcode >> 8); + priv->device_id[2] = (u8)(idcode >> 16); + } else { + idcode = priv->device_id[0] | priv->device_id[1] << 8 | + priv->device_id[2] << 16; }
/* use back the intended clock and find low range */ @@ -109,7 +119,7 @@ static int spi_calibration(struct udevice *bus, uint hz) cadence_qspi_apb_controller_enable(base);
/* issue a RDID to get the ID value */ - err = cadence_spi_read_id(priv, 3, (u8 *)&temp); + err = cadence_spi_read_id(priv, CQSPI_READ_ID_LEN, (u8 *)&temp); if (err) { puts("SF: Calibration failed (read)\n"); return err; @@ -190,6 +200,20 @@ static int cadence_spi_set_speed(struct udevice *bus, uint hz) return 0; }
+static int cadence_spi_child_pre_probe(struct udevice *bus) +{ + struct spi_slave *slave = dev_get_parent_priv(bus); + + slave->bytemode = SPI_4BYTE_MODE; + + return 0; +} + +__weak int cadence_qspi_versal_set_dll_mode(struct udevice *dev) +{ + return -ENOTSUPP; +} + static int cadence_spi_probe(struct udevice *bus) { struct cadence_spi_plat *plat = dev_get_plat(bus); @@ -247,6 +271,14 @@ static int cadence_spi_probe(struct udevice *bus) priv->qspi_is_init = 1; }
+ priv->edge_mode = CQSPI_EDGE_MODE_SDR; + priv->dll_mode = CQSPI_DLL_MODE_BYPASS; + + /* Select dll mode */ + ret = cadence_qspi_versal_set_dll_mode(bus); + if (ret == -ENOTSUPP) + debug("DLL mode set to bypass mode : %x\n", ret); + priv->wr_delay = 50 * DIV_ROUND_UP(NSEC_PER_SEC, priv->ref_clk_hz);
/* Versal and Versal-NET use spi calibration to set read delay */ @@ -290,6 +322,279 @@ static int cadence_spi_set_mode(struct udevice *bus, uint mode) return 0; }
+static int cadence_qspi_rx_dll_tuning(struct cadence_spi_priv *priv, + u32 txtap, u8 extra_dummy) +{ + void *regbase = priv->regbase; + int ret, i, j; + u8 id[CQSPI_READ_ID_LEN + 1], min_rxtap = 0, max_rxtap = 0, avg_rxtap, + max_tap, windowsize, dummy_flag = 0, max_index = 0, min_index = 0; + s8 max_windowsize = -1; + bool id_matched, rxtapfound = false; + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(CQSPI_READ_ID | (CQSPI_READ_ID << 8), 8), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_DUMMY(8, 8), + SPI_MEM_OP_DATA_IN(CQSPI_READ_ID_LEN, id, 8)); + + op.cmd.nbytes = 2; + op.dummy.nbytes *= 2; + op.cmd.dtr = true; + op.addr.dtr = true; + op.data.dtr = true; + + max_tap = CQSPI_MAX_DLL_TAPS; + /* + * Rx dll tuning is done by setting tx delay and increment rx + * delay and check for correct flash id's by reading from flash. + */ + for (i = 0; i <= max_tap; i++) { + /* Set DLL reset bit */ + writel((txtap | i | CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK), + regbase + CQSPI_REG_PHY_CONFIG); + /* + * Re-synchronisation delay lines to update them + * with values from TX DLL Delay and RX DLL Delay fields + */ + writel((CQSPI_REG_PHY_CONFIG_RESYNC_FLD_MASK | txtap | i | + CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK), + regbase + CQSPI_REG_PHY_CONFIG); + /* Check lock of loopback */ + if (priv->dll_mode == CQSPI_DLL_MODE_MASTER) { + ret = wait_for_bit_le32 + (regbase + CQSPI_REG_DLL_LOWER, + CQSPI_REG_DLL_LOWER_LPBK_LOCK_MASK, 1, + CQSPI_TIMEOUT_MS, 0); + if (ret) { + printf("LOWER_DLL_LOCK bit err: %i\n", ret); + return ret; + } + } + + ret = cadence_qspi_apb_command_read_setup(priv, &op); + if (!ret) { + ret = cadence_qspi_apb_command_read(priv, &op); + if (ret < 0) { + printf("error %d reading JEDEC ID\n", ret); + return ret; + } + } + + id_matched = true; + for (j = 0; j < CQSPI_READ_ID_LEN; j++) { + if (priv->device_id[j] != id[j]) { + id_matched = false; + break; + } + } + + if (id_matched && !rxtapfound) { + if (priv->dll_mode == CQSPI_DLL_MODE_MASTER) { + min_rxtap = + readl(regbase + CQSPI_REG_DLL_OBSVBLE_UPPER) & + CQSPI_REG_DLL_UPPER_RX_FLD_MASK; + max_rxtap = min_rxtap; + max_index = i; + min_index = i; + } else { + min_rxtap = i; + max_rxtap = i; + } + rxtapfound = true; + } + + if (id_matched && rxtapfound) { + if (priv->dll_mode == CQSPI_DLL_MODE_MASTER) { + max_rxtap = + readl(regbase + + CQSPI_REG_DLL_OBSVBLE_UPPER) & + CQSPI_REG_DLL_UPPER_RX_FLD_MASK; + max_index = i; + } else { + max_rxtap = i; + } + } + + if ((!id_matched || i == max_tap) && rxtapfound) { + windowsize = max_rxtap - min_rxtap + 1; + if (windowsize > max_windowsize) { + dummy_flag = extra_dummy; + max_windowsize = windowsize; + if (priv->dll_mode == CQSPI_DLL_MODE_MASTER) + avg_rxtap = (max_index + min_index); + else + avg_rxtap = (max_rxtap + min_rxtap); + avg_rxtap /= 2; + } + + if (windowsize >= 3) + i = max_tap; + + rxtapfound = false; + } + } + + if (!extra_dummy) { + rxtapfound = false; + min_rxtap = 0; + max_rxtap = 0; + } + + if (!dummy_flag) + priv->extra_dummy = false; + + if (max_windowsize < 3) + return -EINVAL; + + return avg_rxtap; +} + +static int cadence_spi_setdlldelay(struct udevice *bus) +{ + struct cadence_spi_priv *priv = dev_get_priv(bus); + void *regbase = priv->regbase; + u32 txtap; + int ret, rxtap; + u8 extra_dummy; + + ret = wait_for_bit_le32(regbase + CQSPI_REG_CONFIG, + 1 << CQSPI_REG_CONFIG_IDLE_LSB, + 1, CQSPI_TIMEOUT_MS, 0); + if (ret) { + printf("spi_wait_idle error : 0x%x\n", ret); + return ret; + } + + if (priv->dll_mode == CQSPI_DLL_MODE_MASTER) { + /* Drive DLL reset bit to low */ + writel(0, regbase + CQSPI_REG_PHY_CONFIG); + + /* Set initial delay value */ + writel(CQSPI_REG_PHY_INITIAL_DLY, + regbase + CQSPI_REG_PHY_MASTER_CTRL); + /* Set DLL reset bit */ + writel(CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK, + regbase + CQSPI_REG_PHY_CONFIG); + + /* Check for loopback lock */ + ret = wait_for_bit_le32(regbase + CQSPI_REG_DLL_LOWER, + CQSPI_REG_DLL_LOWER_LPBK_LOCK_MASK, + 1, CQSPI_TIMEOUT_MS, 0); + if (ret) { + printf("Loopback lock bit error (%i)\n", ret); + return ret; + } + + /* Re-synchronize slave DLLs */ + writel(CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK, + regbase + CQSPI_REG_PHY_CONFIG); + writel(CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK | + CQSPI_REG_PHY_CONFIG_RESYNC_FLD_MASK, + regbase + CQSPI_REG_PHY_CONFIG); + + txtap = CQSPI_TX_TAP_MASTER << + CQSPI_REG_PHY_CONFIG_TX_DLL_DLY_LSB; + } + + priv->extra_dummy = false; + for (extra_dummy = 0; extra_dummy <= 1; extra_dummy++) { + if (extra_dummy) + priv->extra_dummy = true; + + rxtap = cadence_qspi_rx_dll_tuning(priv, txtap, extra_dummy); + if (extra_dummy && rxtap < 0) { + printf("Failed RX dll tuning\n"); + return rxtap; + } + } + debug("RXTAP: %d\n", rxtap); + + writel((txtap | rxtap | CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK), + regbase + CQSPI_REG_PHY_CONFIG); + writel((CQSPI_REG_PHY_CONFIG_RESYNC_FLD_MASK | txtap | rxtap | + CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK), + regbase + CQSPI_REG_PHY_CONFIG); + + if (priv->dll_mode == CQSPI_DLL_MODE_MASTER) { + ret = wait_for_bit_le32(regbase + CQSPI_REG_DLL_LOWER, + CQSPI_REG_DLL_LOWER_LPBK_LOCK_MASK, + 1, CQSPI_TIMEOUT_MS, 0); + if (ret) { + printf("LOWER_DLL_LOCK bit err: %i\n", ret); + return ret; + } + } + + return 0; +} + +static int priv_setup_ddrmode(struct udevice *bus) +{ + struct cadence_spi_priv *priv = dev_get_priv(bus); + void *regbase = priv->regbase; + int ret; + + ret = wait_for_bit_le32(regbase + CQSPI_REG_CONFIG, + 1 << CQSPI_REG_CONFIG_IDLE_LSB, + 1, CQSPI_TIMEOUT_MS, 0); + if (ret) { + printf("spi_wait_idle error : 0x%x\n", ret); + return ret; + } + + /* Disable QSPI */ + cadence_qspi_apb_controller_disable(regbase); + + /* Disable DAC mode */ + if (priv->use_dac_mode) { + clrbits_le32(regbase + CQSPI_REG_CONFIG, + CQSPI_REG_CONFIG_DIRECT); + priv->use_dac_mode = false; + } + + setbits_le32(regbase + CQSPI_REG_CONFIG, + CQSPI_REG_CONFIG_PHY_ENABLE_MASK); + + /* Program POLL_CNT */ + clrsetbits_le32(regbase + CQSPI_REG_WRCOMPLETION, + CQSPI_REG_WRCOMPLETION_POLLCNT_MASK, + CQSPI_REG_WRCOMPLETION_POLLCNT << + CQSPI_REG_WRCOMPLETION_POLLCNY_LSB); + + setbits_le32(regbase + CQSPI_REG_CONFIG, + CQSPI_REG_CONFIG_DTR_PROT_EN_MASK); + + clrsetbits_le32(regbase + CQSPI_REG_RD_DATA_CAPTURE, + (CQSPI_REG_RD_DATA_CAPTURE_DELAY_MASK << + CQSPI_REG_RD_DATA_CAPTURE_DELAY_LSB), + CQSPI_REG_READCAPTURE_DQS_ENABLE); + + /* Enable QSPI */ + cadence_qspi_apb_controller_enable(regbase); + + return 0; +} + +static int cadence_spi_setup_ddrmode(struct udevice *bus) +{ + struct cadence_spi_priv *priv = dev_get_priv(bus); + int ret; + + ret = priv_setup_ddrmode(bus); + if (ret) + return ret; + + priv->edge_mode = CQSPI_EDGE_MODE_DDR; + ret = cadence_spi_setdlldelay(bus); + if (ret) { + printf("DDR tuning failed with error %d\n", ret); + return ret; + } + priv->ddr_init = 1; + + return 0; +} + static int cadence_spi_mem_exec_op(struct spi_slave *spi, const struct spi_mem_op *op) { @@ -350,6 +655,9 @@ static int cadence_spi_mem_exec_op(struct spi_slave *spi, break; }
+ if (!priv->ddr_init && (spi->flags & SPI_XFER_SET_DDR)) + err = cadence_spi_setup_ddrmode(bus); + return err; }
@@ -466,6 +774,7 @@ U_BOOT_DRIVER(cadence_spi) = { .plat_auto = sizeof(struct cadence_spi_plat), .priv_auto = sizeof(struct cadence_spi_priv), .probe = cadence_spi_probe, + .child_pre_probe = cadence_spi_child_pre_probe, .remove = cadence_spi_remove, .flags = DM_FLAG_OS_PREPARE, }; diff --git a/drivers/spi/cadence_qspi.h b/drivers/spi/cadence_qspi.h index 1c59d1a9d9..eec090a7ed 100644 --- a/drivers/spi/cadence_qspi.h +++ b/drivers/spi/cadence_qspi.h @@ -10,6 +10,7 @@ #include <reset.h> #include <linux/mtd/spi-nor.h> #include <spi-mem.h> +#include <wait_bit.h>
#define CQSPI_IS_ADDR(cmd_len) (cmd_len > 1 ? 1 : 0)
@@ -21,6 +22,8 @@ #define CQSPI_REG_RETRY 10000 #define CQSPI_POLL_IDLE_RETRY 3
+#define CQSPI_TIMEOUT_MS 1000 + /* Transfer mode */ #define CQSPI_INST_TYPE_SINGLE 0 #define CQSPI_INST_TYPE_DUAL 1 @@ -33,6 +36,23 @@ #define CQSPI_DUMMY_BYTES_MAX 4 #define CQSPI_DUMMY_CLKS_MAX 31
+#define CQSPI_TX_TAP_MASTER 0x1E +#define CQSPI_MAX_DLL_TAPS 127 + +#define CQSPI_DLL_MODE_MASTER 0 +#define CQSPI_DLL_MODE_BYPASS 1 + +#define CQSPI_EDGE_MODE_SDR 0 +#define CQSPI_EDGE_MODE_DDR 1 + +#define SILICON_VER_MASK 0xFF +#define SILICON_VER_1 0x10 + +#define CQSPI_READ_ID 0x9F +#define CQSPI_READ_ID_LEN 3 +#define CQSPI_READID_LOOP_MAX 10 +#define TERA_MACRO 1000000000000l + /**************************************************************************** * Controller's configuration and status register (offset from QSPI_BASE) ****************************************************************************/ @@ -65,6 +85,7 @@ #define CQSPI_REG_RD_INSTR_TYPE_ADDR_MASK 0x3 #define CQSPI_REG_RD_INSTR_TYPE_DATA_MASK 0x3 #define CQSPI_REG_RD_INSTR_DUMMY_MASK 0x1F +#define CQSPI_REG_RD_INSTR_DDR_ENABLE BIT(10)
#define CQSPI_REG_WR_INSTR 0x08 #define CQSPI_REG_WR_INSTR_OPCODE_LSB 0 @@ -109,9 +130,14 @@
#define CQSPI_REG_WR_COMPLETION_CTRL 0x38 #define CQSPI_REG_WR_DISABLE_AUTO_POLL BIT(14) +#define CQSPI_REG_WRCOMPLETION 0x38 +#define CQSPI_REG_WRCOMPLETION_POLLCNT_MASK 0xFF0000 +#define CQSPI_REG_WRCOMPLETION_POLLCNY_LSB 16 +#define CQSPI_REG_WRCOMPLETION_POLLCNT 3
#define CQSPI_REG_IRQSTATUS 0x40 #define CQSPI_REG_IRQMASK 0x44 +#define CQSPI_REG_ECO 0x48
#define CQSPI_REG_INDIRECTRD 0x60 #define CQSPI_REG_INDIRECTRD_START BIT(0) @@ -162,7 +188,31 @@ #define CQSPI_REG_OP_EXT_STIG_LSB 0
#define CQSPI_REG_PHY_CONFIG 0xB4 +#define CQSPI_REG_PHY_CONFIG_RESYNC_FLD_MASK 0x80000000 #define CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK 0x40000000 +#define CQSPI_REG_PHY_CONFIG_TX_DLL_DLY_LSB 16 + +#define CQSPI_REG_PHY_MASTER_CTRL 0xB8 +#define CQSPI_REG_PHY_INITIAL_DLY 0x4 +#define CQSPI_REG_DLL_LOWER 0xBC +#define CQSPI_REG_DLL_LOWER_LPBK_LOCK_MASK 0x8000 +#define CQSPI_REG_DLL_LOWER_DLL_LOCK_MASK 0x1 + +#define CQSPI_REG_DLL_OBSVBLE_UPPER 0xC0 +#define CQSPI_REG_DLL_UPPER_RX_FLD_MASK 0x7F + +#define CQSPI_REG_EXT_OP_LOWER 0xE0 +#define CQSPI_REG_EXT_STIG_OP_MASK 0xFF +#define CQSPI_REG_EXT_READ_OP_MASK 0xFF000000 +#define CQSPI_REG_EXT_READ_OP_SHIFT 24 +#define CQSPI_REG_EXT_WRITE_OP_MASK 0xFF0000 +#define CQSPI_REG_EXT_WRITE_OP_SHIFT 16 +#define CQSPI_REG_DMA_SRC_ADDR 0x1000 +#define CQSPI_REG_DMA_DST_ADDR 0x1800 +#define CQSPI_REG_DMA_DST_SIZE 0x1804 +#define CQSPI_REG_DMA_DST_STS 0x1808 +#define CQSPI_REG_DMA_DST_CTRL 0x180C +#define CQSPI_REG_DMA_DST_CTRL_VAL 0xF43FFA00
#define CQSPI_DMA_DST_ADDR_REG 0x1800 #define CQSPI_DMA_DST_SIZE_REG 0x1804 @@ -247,6 +297,7 @@ struct cadence_spi_priv { u32 tsd2d_ns; u32 tchsh_ns; u32 tslch_ns; + u8 device_id[CQSPI_READ_ID_LEN]; u8 edge_mode; u8 dll_mode; bool extra_dummy; @@ -304,6 +355,7 @@ int cadence_qspi_apb_dma_read(struct cadence_spi_priv *priv, int cadence_qspi_apb_wait_for_dma_cmplt(struct cadence_spi_priv *priv); int cadence_qspi_apb_exec_flash_cmd(void *reg_base, unsigned int reg); int cadence_qspi_versal_flash_reset(struct udevice *dev); +int cadence_qspi_versal_set_dll_mode(struct udevice *dev); void cadence_qspi_apb_enable_linear_mode(bool enable);
#endif /* __CADENCE_QSPI_H__ */ diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index 9ce2c0f254..d1a5a4c679 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -465,6 +465,9 @@ int cadence_qspi_apb_command_read(struct cadence_spi_priv *priv, unsigned int dummy_clk; u8 opcode;
+ if (priv->dtr) + rxlen = ((rxlen % 2) != 0) ? (rxlen + 1) : rxlen; + if (priv->dtr) opcode = op->cmd.opcode >> 8; else @@ -477,6 +480,9 @@ int cadence_qspi_apb_command_read(struct cadence_spi_priv *priv, if (dummy_clk > CQSPI_DUMMY_CLKS_MAX) return -ENOTSUPP;
+ if (priv->extra_dummy) + dummy_clk++; + if (dummy_clk) reg |= (dummy_clk & CQSPI_REG_CMDCTRL_DUMMY_MASK) << CQSPI_REG_CMDCTRL_DUMMY_LSB; @@ -553,6 +559,9 @@ int cadence_qspi_apb_command_write(struct cadence_spi_priv *priv, void *reg_base = priv->regbase; u8 opcode;
+ reg = cadence_qspi_calc_rdreg(priv); + writel(reg, reg_base + CQSPI_REG_RD_INSTR); + if (priv->dtr) opcode = op->cmd.opcode >> 8; else @@ -609,7 +618,6 @@ int cadence_qspi_apb_command_write(struct cadence_spi_priv *priv, int cadence_qspi_apb_read_setup(struct cadence_spi_priv *priv, const struct spi_mem_op *op) { - unsigned int reg; unsigned int rd_reg; unsigned int dummy_clk; unsigned int dummy_bytes = op->dummy.nbytes; @@ -647,6 +655,9 @@ int cadence_qspi_apb_read_setup(struct cadence_spi_priv *priv, if (dummy_clk > CQSPI_DUMMY_CLKS_MAX) return -ENOTSUPP;
+ if (priv->extra_dummy) + dummy_clk++; + if (dummy_clk) rd_reg |= (dummy_clk & CQSPI_REG_RD_INSTR_DUMMY_MASK) << CQSPI_REG_RD_INSTR_DUMMY_LSB; @@ -655,10 +666,10 @@ int cadence_qspi_apb_read_setup(struct cadence_spi_priv *priv, writel(rd_reg, priv->regbase + CQSPI_REG_RD_INSTR);
/* set device size */ - reg = readl(priv->regbase + CQSPI_REG_SIZE); - reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK; - reg |= (op->addr.nbytes - 1); - writel(reg, priv->regbase + CQSPI_REG_SIZE); + clrsetbits_le32(priv->regbase + CQSPI_REG_SIZE, + CQSPI_REG_SIZE_ADDRESS_MASK, + op->addr.nbytes - 1); + return 0; }
@@ -828,10 +839,10 @@ int cadence_qspi_apb_write_setup(struct cadence_spi_priv *priv, writel(reg, priv->regbase + CQSPI_REG_WR_COMPLETION_CTRL); }
- reg = readl(priv->regbase + CQSPI_REG_SIZE); - reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK; - reg |= (op->addr.nbytes - 1); - writel(reg, priv->regbase + CQSPI_REG_SIZE); + clrsetbits_le32(priv->regbase + CQSPI_REG_SIZE, + CQSPI_REG_SIZE_ADDRESS_MASK, + op->addr.nbytes - 1); + return 0; }
@@ -846,6 +857,10 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_priv *priv, unsigned int write_bytes; int ret;
+ if (priv->edge_mode == CQSPI_EDGE_MODE_DDR && (n_tx % 2) != 0) + n_tx++; + remaining = n_tx; + /* * Use bounce buffer for non 32 bit aligned txbuf to avoid data * aborts

From: T Karthik Reddy t.karthik.reddy@amd.com
Cadence driver need to switch from SDR to DTR and vice versa based on the commands from spi-nor framework and based on SPI_FLASH_DTR_ENABLE config. If SPI_FLASH_DTR_ENABLE is not defined, do not switch to DTR mode as it represents that controller does not support DTR. Also do not reinitilize the SDR/DTR tuning if it is already done. Also added the functionality to reset the controller when switching from DTR to STR mode. Use reset_assert, reset_deassert api's to reset the ospi controller. In mini U-Boot case as ZYNQMP_FIRMWARE config is disabled, reset the controller directly using register writes.
The configuration of the chip select in the Cadence QSPI driver is now determined based on the flags received from SPI-NOR framework.
Signed-off-by: T Karthik Reddy t.karthik.reddy@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_ospi_versal.c | 29 +++++++++++++ drivers/spi/cadence_qspi.c | 69 ++++++++++++++++++++++++++++--- drivers/spi/cadence_qspi.h | 7 ++++ 3 files changed, 99 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/cadence_ospi_versal.c b/drivers/spi/cadence_ospi_versal.c index 74994a690d..243de6efaf 100644 --- a/drivers/spi/cadence_ospi_versal.c +++ b/drivers/spi/cadence_ospi_versal.c @@ -154,6 +154,35 @@ int cadence_qspi_versal_set_dll_mode(struct udevice *dev) return -ENOTSUPP; }
+int cadence_spi_versal_ctrl_reset(struct cadence_spi_priv *priv) +{ + int ret; + + if (CONFIG_IS_ENABLED(ZYNQMP_FIRMWARE)) { + /* Assert ospi controller */ + ret = reset_assert(priv->resets->resets); + if (ret) + return ret; + + udelay(10); + + /* Deassert ospi controller */ + ret = reset_deassert(priv->resets->resets); + if (ret) + return ret; + } else { + /* Assert ospi controller */ + setbits_le32((u32 *)OSPI_CTRL_RST, 1); + + udelay(10); + + /* Deassert ospi controller */ + clrbits_le32((u32 *)OSPI_CTRL_RST, 1); + } + + return 0; +} + #if defined(CONFIG_DM_GPIO) int cadence_qspi_versal_flash_reset(struct udevice *dev) { diff --git a/drivers/spi/cadence_qspi.c b/drivers/spi/cadence_qspi.c index 32f91825fd..710c4a532d 100644 --- a/drivers/spi/cadence_qspi.c +++ b/drivers/spi/cadence_qspi.c @@ -154,7 +154,7 @@ static int spi_calibration(struct udevice *bus, uint hz)
/* just to ensure we do once only when speed or chip select change */ priv->qspi_calibrated_hz = hz; - priv->qspi_calibrated_cs = spi_chip_select(bus); + priv->qspi_calibrated_cs = priv->cs;
return 0; } @@ -179,7 +179,7 @@ static int cadence_spi_set_speed(struct udevice *bus, uint hz) priv->read_delay); } else if (priv->previous_hz != hz || priv->qspi_calibrated_hz != hz || - priv->qspi_calibrated_cs != spi_chip_select(bus)) { + priv->qspi_calibrated_cs != priv->cs) { /* * Calibration required for different current SCLK speed, * requested SCLK speed or chip select @@ -580,6 +580,9 @@ static int cadence_spi_setup_ddrmode(struct udevice *bus) struct cadence_spi_priv *priv = dev_get_priv(bus); int ret;
+ if (priv->ddr_init) + return 0; + ret = priv_setup_ddrmode(bus); if (ret) return ret; @@ -590,7 +593,47 @@ static int cadence_spi_setup_ddrmode(struct udevice *bus) printf("DDR tuning failed with error %d\n", ret); return ret; } - priv->ddr_init = 1; + priv->ddr_init = true; + + return 0; +} + +static int cadence_spi_setup_strmode(struct udevice *bus) +{ + struct cadence_spi_priv *priv = dev_get_priv(bus); + void *base = priv->regbase; + int ret; + + if (!priv->ddr_init) + return 0; + + /* Reset ospi controller */ + ret = cadence_spi_versal_ctrl_reset(priv); + if (ret) { + printf("Cadence ctrl reset failed err: %d\n", ret); + return ret; + } + + ret = wait_for_bit_le32(base + CQSPI_REG_CONFIG, + BIT(CQSPI_REG_CONFIG_IDLE_LSB), + 1, CQSPI_TIMEOUT_MS, 0); + if (ret) { + printf("spi_wait_idle error : 0x%x\n", ret); + return ret; + } + + cadence_qspi_apb_controller_init(priv); + priv->edge_mode = CQSPI_EDGE_MODE_SDR; + priv->extra_dummy = 0; + priv->previous_hz = 0; + priv->qspi_calibrated_hz = 0; + + /* Setup default speed and calibrate */ + ret = cadence_spi_set_speed(bus, 0); + if (ret) + return ret; + + priv->ddr_init = false;
return 0; } @@ -604,9 +647,22 @@ static int cadence_spi_mem_exec_op(struct spi_slave *spi, int err = 0; u32 mode;
+ if (!op->cmd.dtr) { + err = cadence_spi_setup_strmode(bus); + if (err) + return err; + } + + if (!CONFIG_IS_ENABLED(SPI_FLASH_DTR_ENABLE) && op->cmd.dtr) + return 0; + + if (spi->flags & SPI_XFER_U_PAGE) + priv->cs = CQSPI_CS1; + else + priv->cs = CQSPI_CS0; + /* Set Chip select */ - cadence_qspi_apb_chipselect(base, spi_chip_select(spi->dev), - priv->is_decoded_cs); + cadence_qspi_apb_chipselect(base, priv->cs, priv->is_decoded_cs);
if (op->data.dir == SPI_MEM_DATA_IN && op->data.buf.in) { /* @@ -655,7 +711,8 @@ static int cadence_spi_mem_exec_op(struct spi_slave *spi, break; }
- if (!priv->ddr_init && (spi->flags & SPI_XFER_SET_DDR)) + if (CONFIG_IS_ENABLED(SPI_FLASH_DTR_ENABLE) && + (spi->flags & SPI_XFER_SET_DDR)) err = cadence_spi_setup_ddrmode(bus);
return err; diff --git a/drivers/spi/cadence_qspi.h b/drivers/spi/cadence_qspi.h index eec090a7ed..505f59bed9 100644 --- a/drivers/spi/cadence_qspi.h +++ b/drivers/spi/cadence_qspi.h @@ -18,6 +18,9 @@ #define CQSPI_DECODER_MAX_CS 16 #define CQSPI_READ_CAPTURE_MAX_DELAY 16
+#define CQSPI_CS0 0 +#define CQSPI_CS1 1 + #define CQSPI_REG_POLL_US 1 /* 1us */ #define CQSPI_REG_RETRY 10000 #define CQSPI_POLL_IDLE_RETRY 3 @@ -53,6 +56,8 @@ #define CQSPI_READID_LOOP_MAX 10 #define TERA_MACRO 1000000000000l
+#define OSPI_CTRL_RST 0xF1260304 + /**************************************************************************** * Controller's configuration and status register (offset from QSPI_BASE) ****************************************************************************/ @@ -284,6 +289,7 @@ struct cadence_spi_priv { size_t data_len;
int qspi_is_init; + unsigned int cs; unsigned int qspi_calibrated_hz; unsigned int qspi_calibrated_cs; unsigned int previous_hz; @@ -357,5 +363,6 @@ int cadence_qspi_apb_exec_flash_cmd(void *reg_base, unsigned int reg); int cadence_qspi_versal_flash_reset(struct udevice *dev); int cadence_qspi_versal_set_dll_mode(struct udevice *dev); void cadence_qspi_apb_enable_linear_mode(bool enable); +int cadence_spi_versal_ctrl_reset(struct cadence_spi_priv *priv);
#endif /* __CADENCE_QSPI_H__ */

From: T Karthik Reddy t.karthik.reddy@xilinx.com
Set cmd, address & data buswidth to octal. Handle dummy clock cycles incase of reads & writes. Convert odd bytes to even bytes lengths in ddr mode, as we cannot rx/tx odd data in ddr mode.
Disable the DMA once the transfer is done to avoid disabling it at other places.
Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_ospi_versal.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/drivers/spi/cadence_ospi_versal.c b/drivers/spi/cadence_ospi_versal.c index 243de6efaf..9db1911110 100644 --- a/drivers/spi/cadence_ospi_versal.c +++ b/drivers/spi/cadence_ospi_versal.c @@ -21,12 +21,13 @@
#define CMD_4BYTE_READ 0x13 #define CMD_4BYTE_FAST_READ 0x0C +#define CMD_4BYTE_OCTAL_READ 0x7c
int cadence_qspi_apb_dma_read(struct cadence_spi_priv *priv, const struct spi_mem_op *op) { u32 reg, ret, rx_rem, n_rx, bytes_to_dma, data; - u8 opcode, addr_bytes, *rxbuf, dummy_cycles; + u8 opcode, addr_bytes, *rxbuf, dummy_cycles, unaligned_byte;
n_rx = op->data.nbytes; rxbuf = op->data.buf.in; @@ -70,13 +71,14 @@ int cadence_qspi_apb_dma_read(struct cadence_spi_priv *priv, writel(CQSPI_REG_INDIRECTRD_DONE, priv->regbase + CQSPI_REG_INDIRECTRD); rxbuf += bytes_to_dma; - }
- if (rx_rem) { + /* Disable DMA on completion */ reg = readl(priv->regbase + CQSPI_REG_CONFIG); reg &= ~CQSPI_REG_CONFIG_ENBL_DMA; writel(reg, priv->regbase + CQSPI_REG_CONFIG); + }
+ if (rx_rem) { reg = readl(priv->regbase + CQSPI_REG_INDIRECTRDSTARTADDR); reg += bytes_to_dma; writel(reg, priv->regbase + CQSPI_REG_CMDADDRESS); @@ -84,10 +86,10 @@ int cadence_qspi_apb_dma_read(struct cadence_spi_priv *priv, addr_bytes = readl(priv->regbase + CQSPI_REG_SIZE) & CQSPI_REG_SIZE_ADDRESS_MASK;
- opcode = CMD_4BYTE_FAST_READ; - dummy_cycles = 8; - writel((dummy_cycles << CQSPI_REG_RD_INSTR_DUMMY_LSB) | opcode, - priv->regbase + CQSPI_REG_RD_INSTR); + opcode = (u8)readl(priv->regbase + CQSPI_REG_RD_INSTR); + if (opcode == CMD_4BYTE_OCTAL_READ && + priv->edge_mode != CQSPI_EDGE_MODE_DDR) + opcode = CMD_4BYTE_FAST_READ;
reg = opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB; reg |= (0x1 << CQSPI_REG_CMDCTRL_RD_EN_LSB); @@ -99,7 +101,12 @@ int cadence_qspi_apb_dma_read(struct cadence_spi_priv *priv, CQSPI_REG_RD_INSTR_DUMMY_MASK; reg |= (dummy_cycles & CQSPI_REG_CMDCTRL_DUMMY_MASK) << CQSPI_REG_CMDCTRL_DUMMY_LSB; - reg |= (((rx_rem - 1) & CQSPI_REG_CMDCTRL_RD_BYTES_MASK) << + if (priv->edge_mode == CQSPI_EDGE_MODE_DDR && (rx_rem % 2) != 0) + unaligned_byte = 1; + else + unaligned_byte = 0; + reg |= (((rx_rem - 1 + unaligned_byte) & + CQSPI_REG_CMDCTRL_RD_BYTES_MASK) << CQSPI_REG_CMDCTRL_RD_BYTES_LSB); ret = cadence_qspi_apb_exec_flash_cmd(priv->regbase, reg); if (ret)

To reduce the CPU load in waiting for the OSPI internal SRAM to clear in indirect mode, it's better to use the CQSPI_REG_IRQSTATUS register to check for indirect operation to complete. Enabled interrupt for Indirect Complete and Transfer Watermark Breach interrupt status register bits and using readl_poll_timeout function to poll for Indirect Operation Complete bit gets set.
Here not enabling IRQ coming to GIC, only IRQ from IP itself is able to poll bits.
It is observed that the Indirect Operation Complete bit is getting set at an average time of 0.172 usec.
Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_qspi.h | 11 +++++++++++ drivers/spi/cadence_qspi_apb.c | 24 +++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/drivers/spi/cadence_qspi.h b/drivers/spi/cadence_qspi.h index 505f59bed9..73ef44fc1c 100644 --- a/drivers/spi/cadence_qspi.h +++ b/drivers/spi/cadence_qspi.h @@ -252,6 +252,17 @@ (((readl((reg_base) + CQSPI_REG_SDRAMLEVEL)) >> \ CQSPI_REG_SDRAMLEVEL_WR_LSB) & CQSPI_REG_SDRAMLEVEL_WR_MASK)
+/* Interrupt status bits */ +#define CQSPI_REG_IRQ_UNDERFLOW BIT(1) +#define CQSPI_REG_IRQ_IND_COMP BIT(2) +#define CQSPI_REG_IRQ_WATERMARK BIT(6) + +#define CQSPI_IRQ_MASK_WR (CQSPI_REG_IRQ_IND_COMP | \ + CQSPI_REG_IRQ_WATERMARK | \ + CQSPI_REG_IRQ_UNDERFLOW) + +#define CQSPI_IRQ_STATUS_MASK GENMASK(16, 0) + struct cadence_spi_plat { unsigned int max_hz; void *regbase; diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index d1a5a4c679..1b649abf21 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -32,6 +32,7 @@ #include <linux/bitops.h> #include <linux/delay.h> #include <linux/errno.h> +#include <linux/iopoll.h> #include <wait_bit.h> #include <spi.h> #include <spi-mem.h> @@ -855,7 +856,7 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_priv *priv, const u8 *bb_txbuf = txbuf; void *bounce_buf = NULL; unsigned int write_bytes; - int ret; + int ret, cr;
if (priv->edge_mode == CQSPI_EDGE_MODE_DDR && (n_tx % 2) != 0) n_tx++; @@ -873,9 +874,15 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_priv *priv, bb_txbuf = bounce_buf; }
- /* Configure the indirect read transfer bytes */ + /* Configure the indirect write transfer bytes */ writel(n_tx, priv->regbase + CQSPI_REG_INDIRECTWRBYTES);
+ /* Clear all interrupts */ + writel(CQSPI_IRQ_STATUS_MASK, priv->regbase + CQSPI_REG_IRQSTATUS); + + /* Enable interrupt for corresponding interrupt status register bit's */ + writel(CQSPI_IRQ_MASK_WR, priv->regbase + CQSPI_REG_IRQMASK); + /* Start the indirect write transfer */ writel(CQSPI_REG_INDIRECTWR_START, priv->regbase + CQSPI_REG_INDIRECTWR); @@ -894,9 +901,10 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_priv *priv, bb_txbuf + rounddown(write_bytes, 4), write_bytes % 4);
- ret = wait_for_bit_le32(priv->regbase + CQSPI_REG_SDRAMLEVEL, - CQSPI_REG_SDRAMLEVEL_WR_MASK << - CQSPI_REG_SDRAMLEVEL_WR_LSB, 0, 10, 0); + /* Wait up to Indirect Operation Complete bit to set */ + ret = readl_poll_timeout(priv->regbase + CQSPI_REG_IRQSTATUS, cr, + cr & CQSPI_REG_IRQ_IND_COMP, 10); + if (ret) { printf("Indirect write timed out (%i)\n", ret); goto failwr; @@ -914,6 +922,9 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_priv *priv, goto failwr; }
+ /* Disable interrupt. */ + writel(0, priv->regbase + CQSPI_REG_IRQMASK); + /* Clear indirect completion status */ writel(CQSPI_REG_INDIRECTWR_DONE, priv->regbase + CQSPI_REG_INDIRECTWR); @@ -931,6 +942,9 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_priv *priv, return 0;
failwr: + /* Disable interrupt. */ + writel(0, priv->regbase + CQSPI_REG_IRQMASK); + /* Cancel the indirect write */ writel(CQSPI_REG_INDIRECTWR_CANCEL, priv->regbase + CQSPI_REG_INDIRECTWR);

From: Ashok Reddy Soma ashok.reddy.soma@amd.com
tshsl_ns is the clock delay for chip select deassert. This is the delay in master reference clocks for the length that the master mode chip select outputs are de-asserted between transactions.
The minimum delay is always SCLK period to ensure the chip select is never re-asserted within one SCLK period.
That is why tshsl_ns delay should be at least one sclk_ns value. If it is less than sclk_ns, set it equal to sclk_ns.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_qspi_apb.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index 1b649abf21..0bb46f6ac2 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -306,6 +306,10 @@ void cadence_qspi_apb_delay(void *reg_base, tshsl_ns -= sclk_ns + ref_clk_ns; if (tchsh_ns >= sclk_ns + 3 * ref_clk_ns) tchsh_ns -= sclk_ns + 3 * ref_clk_ns; + + if (tshsl_ns < sclk_ns) + tshsl_ns = sclk_ns; + tshsl = DIV_ROUND_UP(tshsl_ns, ref_clk_ns); tchsh = DIV_ROUND_UP(tchsh_ns, ref_clk_ns); tslch = DIV_ROUND_UP(tslch_ns, ref_clk_ns);

From: Ashok Reddy Soma ashok.reddy.soma@xilinx.com
This patch cleans up the cadence qspi registers in the init. The register contents may be invalid if this controller is used in previous boot and comes to uboot after a softreset (no power on reset). This may cause issues in uboot.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_qspi_apb.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+)
diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index 0bb46f6ac2..5fc5279061 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -346,12 +346,34 @@ void cadence_qspi_apb_controller_init(struct cadence_spi_priv *priv) /* Configure the remap address register, no remap */ writel(0, priv->regbase + CQSPI_REG_REMAP);
+ /* Clear instruction read config register */ + writel(0, priv->regbase + CQSPI_REG_RD_INSTR); + + /* Reset the Delay lines */ + writel(CQSPI_REG_PHY_CONFIG_RESET_FLD_MASK, + priv->regbase + CQSPI_REG_PHY_CONFIG); + + reg = readl(priv->regbase + CQSPI_REG_RD_DATA_CAPTURE); + reg &= ~CQSPI_REG_READCAPTURE_DQS_ENABLE; + reg &= ~(CQSPI_REG_RD_DATA_CAPTURE_DELAY_MASK + << CQSPI_REG_RD_DATA_CAPTURE_DELAY_LSB); + writel(reg, priv->regbase + CQSPI_REG_RD_DATA_CAPTURE); + /* Indirect mode configurations */ writel(priv->fifo_depth / 2, priv->regbase + CQSPI_REG_SRAMPARTITION);
/* Disable all interrupts */ writel(0, priv->regbase + CQSPI_REG_IRQMASK);
+ reg = readl(priv->regbase + CQSPI_REG_CONFIG); + reg &= ~CQSPI_REG_CONFIG_DTR_PROT_EN_MASK; + reg &= ~CQSPI_REG_CONFIG_PHY_ENABLE_MASK; + reg &= ~CQSPI_REG_CONFIG_DIRECT; + reg &= ~(CQSPI_REG_CONFIG_CHIPSELECT_MASK + << CQSPI_REG_CONFIG_CHIPSELECT_LSB); + + writel(reg, priv->regbase + CQSPI_REG_CONFIG); + cadence_qspi_apb_controller_enable(priv->regbase); }

From: Ashok Reddy Soma ashok.reddy.soma@amd.com
Read and Write watermark registers are not initialized. Set read watermark to half of the FIFO and write watermark to 1/8 of the FIFO size.
Read watermark indicates if SRAM fill level is above this watermark, interrupt will be generated and read or DMA can be performed.
Write watermark indicates the maximum fill level of SRAM when write is performed to device.
These values of 1/2 for read and 1/8 for write are chosen similar to Linux driver.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_qspi_apb.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index 5fc5279061..052d7a1766 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -362,6 +362,14 @@ void cadence_qspi_apb_controller_init(struct cadence_spi_priv *priv) /* Indirect mode configurations */ writel(priv->fifo_depth / 2, priv->regbase + CQSPI_REG_SRAMPARTITION);
+ /* Program read watermark -- 1/2 of the FIFO. */ + writel(priv->fifo_depth * priv->fifo_width / 2, + priv->regbase + CQSPI_REG_INDIRECTRDWATERMARK); + + /* Program write watermark -- 1/8 of the FIFO. */ + writel(priv->fifo_depth * priv->fifo_width / 8, + priv->regbase + CQSPI_REG_INDIRECTWRWATERMARK); + /* Disable all interrupts */ writel(0, priv->regbase + CQSPI_REG_IRQMASK);

From: Ashok Reddy Soma ashok.reddy.soma@amd.com
Enable ECO bit for Versal for frequencies above 120Mhz for octal spi to work properly.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_qspi.h | 1 + drivers/spi/cadence_qspi_apb.c | 4 ++++ 2 files changed, 5 insertions(+)
diff --git a/drivers/spi/cadence_qspi.h b/drivers/spi/cadence_qspi.h index 73ef44fc1c..4906b9ef96 100644 --- a/drivers/spi/cadence_qspi.h +++ b/drivers/spi/cadence_qspi.h @@ -55,6 +55,7 @@ #define CQSPI_READ_ID_LEN 3 #define CQSPI_READID_LOOP_MAX 10 #define TERA_MACRO 1000000000000l +#define TAP_GRAN_SEL_MIN_FREQ 120000000
#define OSPI_CTRL_RST 0xF1260304
diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index 052d7a1766..43dc4c6e70 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -382,6 +382,10 @@ void cadence_qspi_apb_controller_init(struct cadence_spi_priv *priv)
writel(reg, priv->regbase + CQSPI_REG_CONFIG);
+ if (IS_ENABLED(CONFIG_ARCH_VERSAL) && + priv->ref_clk_hz >= TAP_GRAN_SEL_MIN_FREQ) + writel(1, priv->regbase + CQSPI_REG_ECO); + cadence_qspi_apb_controller_enable(priv->regbase); }

From: T Karthik Reddy t.karthik.reddy@xilinx.com
In DDR mode, current default spi_mem_dtr_supports_op() function does not allow mixed DTR operation functionality. So implement cadence specific cadence_spi_mem_dtr_supports_op() function to verifying only the command buswidth and command opcode bytes which satisfies the DTR protocol.
Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_qspi.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/cadence_qspi.c b/drivers/spi/cadence_qspi.c index 710c4a532d..282028c845 100644 --- a/drivers/spi/cadence_qspi.c +++ b/drivers/spi/cadence_qspi.c @@ -718,6 +718,21 @@ static int cadence_spi_mem_exec_op(struct spi_slave *spi, return err; }
+static bool cadence_spi_mem_dtr_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + /* + * In DTR mode, except op->cmd all other parameters like address, + * dummy and data could be 0. + * So lets only check if the cmd buswidth and number of opcode bytes + * are true for DTR to support. + */ + if (op->cmd.buswidth == 8 && op->cmd.nbytes % 2) + return false; + + return true; +} + static bool cadence_spi_mem_supports_op(struct spi_slave *slave, const struct spi_mem_op *op) { @@ -740,7 +755,7 @@ static bool cadence_spi_mem_supports_op(struct spi_slave *slave, return false;
if (all_true) - return spi_mem_dtr_supports_op(slave, op); + return cadence_spi_mem_dtr_supports_op(slave, op); else return spi_mem_default_supports_op(slave, op); }

From: Ashok Reddy Soma ashok.reddy.soma@amd.com
Incase of non-aligned length of flash data, ahbbase address is written directly with byte count. This is causing AHB bus error's sometimes and resulting in kernel crash while booting linux. To avoid this write 4 byte aligned byte count to ahbbase address.
Also use a temporary variable with 0xffffffff data and overwrite this temp with unaligned bytes data before writing to ahbbase.
The value 0xffffffff is chosen as this is flash memory, worst case we will write 0xff to any location which doesn't effect any bits.
Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/spi/cadence_qspi_apb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index 43dc4c6e70..1d0e6c2180 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -934,10 +934,12 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_priv *priv, while (remaining > 0) { write_bytes = remaining > page_size ? page_size : remaining; writesl(priv->ahbbase, bb_txbuf, write_bytes >> 2); - if (write_bytes % 4) - writesb(priv->ahbbase, - bb_txbuf + rounddown(write_bytes, 4), - write_bytes % 4); + if (write_bytes % 4) { + unsigned int temp = 0xffffffff; + + memcpy(&temp, bb_txbuf + rounddown(write_bytes, 4), write_bytes % 4); + writel(temp, priv->ahbbase); + }
/* Wait up to Indirect Operation Complete bit to set */ ret = readl_poll_timeout(priv->regbase + CQSPI_REG_IRQSTATUS, cr,

Activate the xSPI Software Reset support, which will be utilized to transition from octal DTR mode to legacy mode during shutdown and boot (if enabled).
Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- configs/xilinx_versal_virt_defconfig | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/configs/xilinx_versal_virt_defconfig b/configs/xilinx_versal_virt_defconfig index ec7caacca0..ee247b905f 100644 --- a/configs/xilinx_versal_virt_defconfig +++ b/configs/xilinx_versal_virt_defconfig @@ -95,6 +95,8 @@ CONFIG_MMC_SDHCI_ZYNQ=y CONFIG_ZYNQ_SDHCI_MIN_FREQ=100000 CONFIG_MTD=y CONFIG_DM_SPI_FLASH=y +CONFIG_SPI_FLASH_SOFT_RESET=y +CONFIG_SPI_FLASH_SOFT_RESET_ON_BOOT=y CONFIG_SPI_FLASH_MT35XU=y CONFIG_SPI_FLASH_GIGADEVICE=y CONFIG_SPI_FLASH_ISSI=y

The Cadence driver must switch between SDR and DTR modes based on commands from the SPI-NOR framework and the configuration set by SPI_FLASH_DTR_ENABLE. If SPI_FLASH_DTR_ENABLE is enabled, the driver should transition to DTR mode; if it is not defined, the driver should avoid switching to DTR mode, signaling that the controller does not support DTR.
Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- configs/xilinx_versal_virt_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/configs/xilinx_versal_virt_defconfig b/configs/xilinx_versal_virt_defconfig index ee247b905f..69835fce55 100644 --- a/configs/xilinx_versal_virt_defconfig +++ b/configs/xilinx_versal_virt_defconfig @@ -98,6 +98,7 @@ CONFIG_DM_SPI_FLASH=y CONFIG_SPI_FLASH_SOFT_RESET=y CONFIG_SPI_FLASH_SOFT_RESET_ON_BOOT=y CONFIG_SPI_FLASH_MT35XU=y +CONFIG_SPI_FLASH_DTR_ENABLE=y CONFIG_SPI_FLASH_GIGADEVICE=y CONFIG_SPI_FLASH_ISSI=y CONFIG_SPI_FLASH_MACRONIX=y

From: T Karthik Reddy t.karthik.reddy@xilinx.com
Micron nor flashes provide block protection support using BP0, BP1, BP2, BP3 & TB bits in status register. This patch supports for micron nor flashes with manufacturer id 0x20 and 0x2c.
Where BP(Block Protection) bits defines memory to be software protected against PROGRAM or ERASE operations. When one or more block protect bits are set to 1, a designated memory area is protected from PROGRAM and ERASE operations. TB(Top/Bottom) bit determines whether the protected memory area defined by the block protect bits starts from the top or bottom of the memory array.
Block Protection table for N25Q00AA with size 128MB, sector size 64KB and with 2048 sectors.
Top protection: -------------- TB BP3 BP2 BP1 BP0 Protected Area Un-Protected Area 0 0 0 0 0 None All sectors 0 0 0 0 1 Sector 2047 Sectors (0 to 2046) 0 0 0 1 0 Sectors (2046 to 2047) Sectors (0 to 2045) 0 0 0 1 1 Sectors (2044 to 2047) Sectors (0 to 2043) 0 0 1 0 0 Sectors (2040 to 2047) Sectors (0 to 2039) 0 0 1 0 1 Sectors (2032 to 2047) Sectors (0 to 2031) 0 0 1 1 0 Sectors (2016 to 2047) Sectors (0 to 2015) 0 0 1 1 1 Sectors (1984 to 2047) Sectors (0 to 1983) 0 1 0 0 0 Sectors (1920 to 2047) Sectors (0 to 1919) 0 1 0 0 1 Sectors (1792 to 2047) Sectors (0 to 1791) 0 1 0 1 0 Sectors (1936 to 2047) Sectors (0 to 1535) 0 1 0 1 1 Sectors (1024 to 2047) None
Bottom protection: ----------------- TB BP3 BP2 BP1 BP0 Protected Area Un-protected Area 1 0 0 0 0 None All sectors 1 0 0 0 1 sector 0 Sectors (1 to 2047) 1 0 0 1 0 Sectors (0 to 1) Sectors (2 to 2047) 1 0 0 1 1 Sectors (0 to 3) Sectors (4 to 2047) 1 0 1 0 0 Sectors (0 to 7) Sectors (8 to 2047) 1 0 1 0 1 Sectors (0 to 15) Sectors (16 to 2047) 1 0 1 1 0 Sectors (0 to 31) Sectors (32 to 2047) 1 0 1 1 1 Sectors (0 to 63) Sectors (64 to 2047) 1 1 0 0 0 Sectors (0 to 127) Sectors (128 to 2047) 1 1 0 0 1 Sectors (0 to 255) Sectors (256 to 2047) 1 1 0 1 0 Sectors (0 to 511) Sectors (512 to 2047) 1 1 0 1 1 Sectors (0 to 1023) Sectors (1024 to 2047)
Signed-off-by: Siva Durga Prasad Paladugu siva.durga.paladugu@xilinx.com Signed-off-by: Ashok Reddy Soma ashok.reddy.soma@xilinx.com Signed-off-by: T Karthik Reddy t.karthik.reddy@xilinx.com Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 255 +++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 7 + 2 files changed, 262 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index ccda722df5..c40899a281 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -4336,6 +4336,253 @@ static int spi_nor_init(struct spi_nor *nor) return 0; }
+#if defined(CONFIG_SPI_FLASH_LOCK) +#if defined(CONFIG_SPI_FLASH_STMICRO) +static inline uint16_t min_lockable_sectors(struct spi_nor *nor, + uint16_t n_sectors) +{ + /* lock granularity */ + return 1; +} + +static inline uint32_t get_protected_area_start(struct spi_nor *nor, + u8 lock_bits, + bool is_bottom) +{ + u16 n_sectors; + u32 sector_size, flash_size; + int ret; + + sector_size = nor->sector_size; + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + n_sectors = (nor->size >> 0x01) / sector_size; + flash_size = nor->size >> 0x01; + } else { + n_sectors = nor->size / sector_size; + flash_size = nor->size; + } + + if (!is_bottom) + ret = flash_size - ((1 << (lock_bits - 1)) * sector_size * + min_lockable_sectors(nor, n_sectors)); + else + ret = (1 << (lock_bits - 1)) * sector_size * + min_lockable_sectors(nor, n_sectors); + + return ret; +} + +static uint8_t min_protected_area_including_offset(struct spi_nor *nor, + u32 offset, + bool is_bottom) +{ + u8 lock_bits, lockbits_limit; + + lockbits_limit = MAX_LOCKBITS; + + for (lock_bits = 1; lock_bits < lockbits_limit; lock_bits++) { + if (!is_bottom) { + /* top protection */ + if (offset >= get_protected_area_start(nor, + lock_bits, + is_bottom)) + break; + } else { + /* bottom protection */ + if (offset <= get_protected_area_start(nor, + lock_bits, + is_bottom)) + break; + } + } + return lock_bits; +} + +static int write_sr_modify_protection(struct spi_nor *nor, u8 status, + u8 lock_bits, bool is_bottom) +{ + u8 status_new, bp_mask; + int ret; + + status_new = status & ~BP_MASK; + bp_mask = (lock_bits << BP_SHIFT) & BP_MASK; + + status_new &= ~SR_BP3; + /* Protected area starts from top */ + status_new &= ~SR_TB; + + /* If bottom area is to be Protected set SR_TB */ + if (is_bottom) + status_new |= SR_TB; + + if (lock_bits > 7) + bp_mask |= SR_BP3; + + status_new |= bp_mask; + + write_enable(nor); + + nor->spi->flags |= SPI_XFER_LOWER; + + ret = write_sr(nor, status_new); + if (ret) + return ret; + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + nor->spi->flags |= SPI_XFER_UPPER; + ret = write_sr(nor, status_new); + if (ret) + return ret; + } + + return write_disable(nor); +} + +static void micron_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, + uint64_t *len) +{ + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + int shift = ffs(mask) - 1; + int pow; + u64 norsize = nor->size; + + if (nor->flags & SNOR_F_HAS_PARALLEL) + norsize /= 2; + + if (!(sr & (mask | SR_BP3))) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + pow = (sr & mask) >> shift; + pow |= sr & SR_BP3 ? BIT(3) : 0; + + if (pow) + pow--; + + *len = nor->sector_size << pow; + if (*len >= norsize) + *len = norsize; + + if (!(sr & SR_TB)) + *ofs = norsize - *len; + else + *ofs = 0; + + debug("%s, ofs:0x%lx, len:0x%lx\n", __func__, + (unsigned long)*ofs, (unsigned long)*len); + } +} + +static int micron_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + loff_t lock_offs; + u64 lock_len; + int given_range, locked_range; + bool locked_value = false; + bool offset_value = false; + + if (nor->flags & SNOR_F_HAS_PARALLEL) + ofs /= 2; + + /* Avoid dividing by 2 of data length for size 1 */ + if (nor->flags & SNOR_F_HAS_PARALLEL && len != 1) + len /= 2; + + debug("%s, ofs:0x%lx, len:0x%lx\n", __func__, + (unsigned long)ofs, + (unsigned long)len); + + micron_get_locked_range(nor, sr, &lock_offs, &lock_len); + + given_range = ofs + len; + locked_range = lock_offs + lock_len; + + if (given_range <= locked_range) + locked_value = true; + + if (ofs >= lock_offs) + offset_value = true; + + if (sr & SR_TB) + return !locked_value; + + return !(locked_value && offset_value); +} + +static int micron_flash_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + u8 status_old, status_old_up; + u8 lock_bits; + loff_t lock_len; + int ret = 0; + bool is_bottom = false; /* Use TOP protection by default */ + + if (nor->flags & SNOR_F_HAS_PARALLEL) + nor->spi->flags |= SPI_XFER_LOWER; + + status_old = read_sr(nor); + if (status_old < 0) + return status_old; + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + nor->spi->flags |= SPI_XFER_UPPER; + status_old_up = read_sr(nor); + if (status_old_up < 0) + return status_old_up; + if ((status_old & BPTB_MASK) != (status_old_up & BPTB_MASK)) { + printf("BP is different in both flashes lo:0x%x, up:0x%x\n", + status_old, status_old_up); + return -EINVAL; + } + } + + if (ofs < nor->size / 2) + is_bottom = true; /* Change it to bottom protection */ + + debug("Status in both flashes lo:0x%x, up:0x%x\n", + status_old, status_old_up); + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + ofs /= 2; + len /= 2; + } + + if (!is_bottom) + lock_len = ofs; + else + lock_len = ofs + len; + + lock_bits = min_protected_area_including_offset(nor, lock_len, + is_bottom); + + if (lock_bits > ((status_old & (BP_MASK << BP_SHIFT)) >> 2)) + ret = write_sr_modify_protection(nor, status_old, lock_bits, + is_bottom); + + return ret; +} + +static int micron_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return write_sr_modify_protection(nor, 0, 0, 0); +} + +static int micron_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int status; + + status = read_sr(nor); + if (status < 0) + return status; + + return micron_is_unlocked_sr(nor, ofs, len, status); +} +#endif /* CONFIG_SPI_FLASH_STMICRO */ +#endif /* CONFIG_SPI_FLASH_LOCK */ + #ifdef CONFIG_SPI_FLASH_SOFT_RESET /** * spi_nor_soft_reset() - perform the JEDEC Software Reset sequence @@ -4545,6 +4792,14 @@ int spi_nor_scan(struct spi_nor *nor) } #endif
+#if defined(CONFIG_SPI_FLASH_STMICRO) + if (JEDEC_MFR(info) == SNOR_MFR_ST || JEDEC_MFR(info) == SNOR_MFR_MICRON) { + nor->flash_lock = micron_flash_lock; + nor->flash_unlock = micron_flash_unlock; + nor->flash_is_unlocked = micron_is_unlocked; + } +#endif + #ifdef CONFIG_SPI_FLASH_SST /* * sst26 series block protection implementation differs from other diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 34e0aedc24..03413063ae 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -166,8 +166,15 @@ #define SR_BP0 BIT(2) /* Block protect 0 */ #define SR_BP1 BIT(3) /* Block protect 1 */ #define SR_BP2 BIT(4) /* Block protect 2 */ +#define SR_BP3 BIT(6) /* Block protect 3 */ #define SR_TB BIT(5) /* Top/Bottom protect */ #define SR_SRWD BIT(7) /* SR write protect */ + +#define BPTB_MASK 0x7C /* BP & TB bits mask */ +#define BP_MASK 0x1C /* BP bits mask */ +#define BP_SHIFT 0x2 /* BP shift*/ +#define MAX_LOCKBITS 15 + /* Spansion/Cypress specific status bits */ #define SR_E_ERR BIT(5) #define SR_P_ERR BIT(6)

From: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
Macronix chips implements locking in (power-of-two multiple of) 64K blocks, not as a fraction of the chip's size. Bit 5 in the status register is not a top/bottom select bit, but instead a fourth value bit, allowing locking between 2^0 and 2^14 64K blocks (so up to 1GiB), either from top or bottom.
The top/bottom select is instead done via a bit in the configuration register, which is OTP, so once set to use bottom protect, one cannot use top. On top of that, reading the configuration register uses a different opcode (0x15) than the existing SPINOR_OP_RDCR (0x35).
Here's an attempt to implement locking support for Macronix flash memory devices. This implementation has been tested and confirmed to work on the specific chip used on our boards. Additionally, based on the information from data sheets of various other Macronix chips, suggest they behave in the same way.
Used bottom protect to test the locking and unlocking functionality on the zc1751+dc1 board.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 259 +++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 3 + 2 files changed, 262 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index c40899a281..08f6fb66be 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -4581,6 +4581,257 @@ static int micron_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) return micron_is_unlocked_sr(nor, ofs, len, status); } #endif /* CONFIG_SPI_FLASH_STMICRO */ + +#if defined(CONFIG_SPI_FLASH_MACRONIX) +/** + * mx_write_sr_cr() - write status and configuration register + * @nor: pointer to a 'struct spi_nor' + * @sr_cr: pointer status and configuration register + * + * Write status Register and configuration register with 2 bytes + * The first byte will be written to the status register, while the + * second byte will be written to the configuration register. + * + * Return: 0 on success, -errno if error occurred. + */ +static int mx_write_sr_cr(struct spi_nor *nor, u8 *sr_cr) +{ + int ret; + + write_enable(nor); + + ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); + if (ret < 0) { + dev_dbg(nor->dev, + "error while writing configuration register\n"); + return -EINVAL; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret) { + dev_dbg(nor->dev, + "timeout while writing configuration register\n"); + return ret; + } + + ret = write_disable(nor); + if (ret) + return ret; + + return 0; +} + +static int mx_read_cr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDCR_MX, &val, 1); + if (ret < 0) { + dev_dbg(nor->dev, "error %d reading CR\n", ret); + return ret; + } + + return val; +} + +/** + * mx_get_locked_range() - get the locked range + * @nor: pointer to a 'struct spi_nor' + * @sr: status register + * @cr: configuration register + * @ofs: flash offset + * @len: length to be locked + * + * Macronix flashes do not work by locking some 1/2^k fraction of the + * flash - instead, the BP{0,1,2,3} bits define a number of protected + * 64K blocks. + */ +static void mx_get_locked_range(struct spi_nor *nor, u8 sr, u8 cr, + loff_t *ofs, uint64_t *len) +{ + struct mtd_info *mtd = &nor->mtd; + int pow, shift; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_MX; + + shift = ffs(mask) - 1; + + pow = ((sr & mask) >> shift) - 1; + if (pow < 0) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + *len = (uint64_t)SZ_64K << pow; + if (*len > mtd->size) + *len = mtd->size; + if (cr & CR_TB_MX) + *ofs = 0; + else + *ofs = mtd->size - *len; + } +} + +/** + * mx_check_lock_status() - check the locked status + * @nor: pointer to a 'struct spi_nor' + * @ofs: flash offset + * @len: length to be locked + * @sr: status register + * @cr: configuration register + * @locked: locked:1 unlocked:0 value + * + * Return: 1 if the entire region is locked (if @locked is true) or unlocked (if + * @locked is false); 0 otherwise. + */ +static int mx_check_lock_status(struct spi_nor *nor, loff_t ofs, u64 len, + u8 sr, u8 cr, bool locked) +{ + loff_t lock_offs; + u64 lock_len; + + if (!len) + return 1; + + mx_get_locked_range(nor, sr, cr, &lock_offs, &lock_len); + + if (locked) + /* Requested range is a sub-range of locked range */ + return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); + + /* Requested range does not overlap with locked range */ + return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); +} + +static int mx_lock_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len, bool lock) +{ + struct mtd_info *mtd = &nor->mtd; + int sr, cr, ret, val; + loff_t lock_len, blocks; + bool can_be_top, can_be_bottom, use_top; + u8 sr_cr[2], shift; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_MX; + + shift = ffs(mask) - 1; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + cr = mx_read_cr(nor); + if (cr < 0) + return cr; + + log_debug("SPI Protection: %s\n", (cr & CR_TB_MX) ? "bottom" : "top"); + + /* CR_TB is OTP, so we can't use 'top' protection if that is already set. */ + can_be_top = !(cr & CR_TB_MX); + can_be_bottom = true; + + /* If the whole range is already locked (unlocked), we don't need to do anything */ + if (mx_check_lock_status(nor, ofs, len, sr, cr, lock)) + return 0; + + /* To use 'bottom' ('top') protection, everything below us must be locked (unlocked). */ + if (!mx_check_lock_status(nor, 0, ofs, sr, cr, lock)) { + if (lock) + can_be_bottom = false; + else + can_be_top = false; + } + + /* To use 'top' ('bottom') protection, everything above us must be locked (unlocked). */ + if (!mx_check_lock_status(nor, ofs + len, mtd->size - (ofs + len), sr, cr, lock)) { + if (lock) + can_be_top = false; + else + can_be_bottom = false; + } + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + use_top = can_be_top; + + /* lock_len: length of region that should end up locked */ + if (lock) + lock_len = use_top ? mtd->size - ofs : ofs + len; + else + lock_len = use_top ? mtd->size - (ofs + len) : ofs; + + /* lock_len must be a power-of-2 (2^0 .. 2^14) multiple of 64K, or 0 */ + if (lock_len & (SZ_64K - 1)) + return -EINVAL; + + blocks = lock_len / SZ_64K; + if ((blocks != 0 && !is_power_of_2(blocks)) || blocks > 1 << 14) + return -EINVAL; + + /* Compute new values of sr/cr */ + val = blocks ? ilog2(blocks) + 1 : 0; + sr_cr[0] = sr & ~mask; + sr_cr[0] |= val << shift; + /* + * Disallow further writes if WP pin is asserted, but remove + * that bit if we unlocked the whole chip. + */ + if (lock_len) + sr_cr[0] |= SR_SRWD; + else + sr_cr[0] &= ~SR_SRWD; + + sr_cr[1] = cr | (use_top ? 0 : CR_TB_MX); + + /* Don't bother if they're the same */ + if (sr == sr_cr[0] && cr == sr_cr[1]) + return 0; + + ret = mx_write_sr_cr(nor, sr_cr); + if (ret) + return ret; + + /* Check that the bits got written as expected */ + sr = read_sr(nor); + if (sr < 0) + return sr; + + cr = mx_read_cr(nor); + if (cr < 0) + return cr; + + if ((sr & mask) != (sr_cr[0] & mask) || + (cr & CR_TB_MX) != (sr_cr[1] & CR_TB_MX)) + return -EIO; + + return 0; +} + +static int mx_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return mx_lock_unlock(nor, ofs, len, true); +} + +static int mx_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return mx_lock_unlock(nor, ofs, len, false); +} + +static int mx_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int sr, cr; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + cr = mx_read_cr(nor); + if (cr < 0) + return cr; + + return mx_check_lock_status(nor, ofs, len, sr, cr, false); +} +#endif /* CONFIG_SPI_FLASH_MACRONIX */ #endif /* CONFIG_SPI_FLASH_LOCK */
#ifdef CONFIG_SPI_FLASH_SOFT_RESET @@ -4800,6 +5051,14 @@ int spi_nor_scan(struct spi_nor *nor) } #endif
+#if defined(CONFIG_SPI_FLASH_MACRONIX) + if (JEDEC_MFR(info) == SNOR_MFR_MACRONIX) { + nor->flash_lock = mx_lock; + nor->flash_unlock = mx_unlock; + nor->flash_is_unlocked = mx_is_unlocked; + } +#endif + #ifdef CONFIG_SPI_FLASH_SST /* * sst26 series block protection implementation differs from other diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 03413063ae..8ca874068a 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -70,6 +70,7 @@ #define SPINOR_OP_RDID 0x9f /* Read JEDEC ID */ #define SPINOR_OP_RDSFDP 0x5a /* Read SFDP */ #define SPINOR_OP_RDCR 0x35 /* Read configuration register */ +#define SPINOR_OP_RDCR_MX 0x15 /* Read configuration register (Macronix) */ #define SPINOR_OP_RDFSR 0x70 /* Read flag status register */ #define SPINOR_OP_CLFSR 0x50 /* Clear flag status register */ #define SPINOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ @@ -167,6 +168,7 @@ #define SR_BP1 BIT(3) /* Block protect 1 */ #define SR_BP2 BIT(4) /* Block protect 2 */ #define SR_BP3 BIT(6) /* Block protect 3 */ +#define SR_BP3_MX BIT(5) /* Block protect 3 (Macronix) */ #define SR_TB BIT(5) /* Top/Bottom protect */ #define SR_SRWD BIT(7) /* SR write protect */
@@ -192,6 +194,7 @@
/* Configuration Register bits. */ #define CR_QUAD_EN_SPAN BIT(1) /* Spansion Quad I/O */ +#define CR_TB_MX BIT(3) /* Macronix Top/Bottom protect */
/* Status Register 2 bits. */ #define SR2_QUAD_EN_BIT7 BIT(7)

From: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
ISSI chips implements locking in (power-of-two multiple of) 64K blocks, not as a fraction of the chip's size. Bit 5 in the status register is not a top/bottom select bit, but instead a fourth value bit, allowing locking between 2^0 and 2^14 64K blocks (so up to 1GiB), either from top or bottom.
The top/bottom select is instead done via a bit in the function register, which is OTP, so once set to use bottom protect, one cannot use top. On top of that, reading the function register uses a opcode SPINOR_OP_RDFR (0x48) and writing to the function register uses the opcode SPINOR_OP_WRFR(0x42).
Here's an attempt at implementing a locking feature for ISSI flash memory devices. This implementation has been rigorously tested and proven to work on the chip used in our boards. Additionally, after examining data sheets for various other ISSI chips, it appears that they follow a similar behavior.
Used "bottom protect" to test the lock and unlock functionality on the Versal Tenzing board. Please note that the Block Protection table is specific to the IS25LP01G, which has a size of 128MB, a sector size of 64KB, and comprises 2048 sectors.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/sf_internal.h | 6 + drivers/mtd/spi/spi-nor-core.c | 340 +++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 7 + 3 files changed, 353 insertions(+)
diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index 2cbdea60b0..aaa8520838 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -71,6 +71,12 @@ struct flash_info { #define SPI_NOR_OCTAL_DTR_READ BIT(17) /* Flash supports Octal DTR Read */ #define SPI_NOR_OCTAL_DTR_PP BIT(18) /* Flash supports Octal DTR page program */ #define SPI_NOR_MULTI_DIE BIT(19) /* Flash has multi dies & need split reads*/ +#define SPI_NOR_HAS_BP3 BIT(20) /* Flash SR has block protect bits + * for lock/unlock purpose, few support + * BP0-BP2 while few support BP0-BP3. + * This flag identifies devices that + * support BP3 bit. + */ };
extern const struct flash_info spi_nor_ids[]; diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 08f6fb66be..3bf1e5471b 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -4832,6 +4832,338 @@ static int mx_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) return mx_check_lock_status(nor, ofs, len, sr, cr, false); } #endif /* CONFIG_SPI_FLASH_MACRONIX */ + +#if defined(CONFIG_SPI_FLASH_ISSI) +/** + * spi_nor_read_fr() - read function register + * @nor: pointer to a 'struct spi_nor'. + * + * ISSI devices have top/bottom area protection bits selection into function + * reg. The bits in FR are OTP. So once it's written, it cannot be changed. + * + * Return: Value in function register or negative if error. + */ +static int spi_nor_read_fr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDFR, &val, 1); + if (ret < 0) { + dev_dbg(nor->dev, "error %d reading FR\n", ret); + return ret; + } + + return val; +} + +static void issi_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, + uint64_t *len) +{ + struct mtd_info *mtd = &nor->mtd; + u8 mask = 0, fr = 0; + int pow, shift; + u32 sector_size; + + mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_ISSI; + shift = ffs(mask) - 1; + sector_size = nor->sector_size; + + if (nor->flags & SNOR_F_HAS_PARALLEL) + sector_size >>= 1; + + if (!(sr & mask)) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + pow = ((sr & mask) >> shift) - 1; + *len = sector_size << pow; + if (*len > mtd->size) + *len = mtd->size; + /* ISSI device's have top/bottom select bit in function reg */ + fr = spi_nor_read_fr(nor); + if (fr & FR_TB) + *ofs = 0; + else + *ofs = mtd->size - *len; + } +} + +/** + * issi_check_lock_status_sr() - check the status register and return + * the region is locked or unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * @locked: locked:1 unlocked:0 value + * + * Return: 1 if the entire region is locked (if @locked is true) or unlocked (if + * @locked is false); 0 otherwise. + */ +static int issi_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, u64 len, + u8 sr, bool locked) +{ + loff_t lock_offs; + u64 lock_len; + + if (!len) + return 1; + + issi_get_locked_range(nor, sr, &lock_offs, &lock_len); + if (locked) + /* Requested range is a sub-range of locked range */ + return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); + + /* Requested range does not overlap with locked range */ + return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); +} + +/** + * spi_nor_is_locked_sr() - check if the memory region is locked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is locked. + * + * Return: false if region is locked 0 otherwise. + */ +static int spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return issi_check_lock_status_sr(nor, ofs, len, sr, true); +} + +/** + * spi_nor_is_unlocked_sr() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is unlocked. + * + * Return: false if region is locked 0 otherwise. + */ +static int spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return issi_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * issi_is_unlocked() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * + * Check if memory region is unlocked + * + * Return: false if region is locked 0 otherwise. + */ +static int issi_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int sr; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + return issi_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * spi_nor_select_zone() - Select top area or bottom area to lock/unlock + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * @sr: status register + * @tb: pointer to top/bottom bool used in caller function + * @op: zone selection is for lock/unlock operation. 1: lock 0:unlock + * + * Select the top area / bottom area pattern to protect memory blocks. + * + * Return: negative on errors, 0 on success. + */ +static int spi_nor_select_zone(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr, bool *tb, bool op) +{ + int retval = 1; + bool can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, can_be_top = true; + + if (op) { + /* Select for lock zone operation */ + + /* + * If nothing in our range is unlocked, we don't need + * to do anything. + */ + if (spi_nor_is_locked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is unlocked, we can't use 'bottom' + * protection. + */ + if (!spi_nor_is_locked_sr(nor, 0, ofs, sr)) + can_be_bottom = false; + + /* + * If anything above us is unlocked, we can't use 'top' + * protection. + */ + if (!spi_nor_is_locked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_top = false; + } else { + /* Select unlock zone */ + + /* + * If nothing in our range is locked, we don't need to + * do anything. + */ + if (spi_nor_is_unlocked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is locked, we can't use 'top' + * protection + */ + if (!spi_nor_is_unlocked_sr(nor, 0, ofs, sr)) + can_be_top = false; + + /* + * If anything above us is locked, we can't use 'bottom' + * protection + */ + if (!spi_nor_is_unlocked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_bottom = false; + } + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + *tb = can_be_top; + return retval; +} + +/** + * issi_flash_lock() - set BP[0123] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * + * Lock a region of the flash.Implementation is based on stm_lock + * Supports the block protection bits BP{0,1,2,3} in status register + * + * Return: 0 on success, -errno otherwise. + */ +static int issi_flash_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + u32 sector_size; + u16 n_sectors; + unsigned int bp_slots, bp_slots_needed; + int status_old, status_new, blk_prot, fr, shift; + loff_t lock_len; + u8 pow, ret; + bool use_top = false; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_ISSI; + + shift = ffs(mask) - 1; + + status_old = read_sr(nor); + /* if status reg is Write protected don't update bit protection */ + if (status_old & SR_SRWD) { + dev_err(nor->dev, + "SR is write protected, can't update BP bits...\n"); + return -EINVAL; + } + + fr = spi_nor_read_fr(nor); + log_debug("SPI Protection: %s\n", (fr & FR_TB) ? "bottom" : "top"); + + ret = spi_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); + /* Older protected blocks include the new requested block's */ + if (ret <= 0) + return ret; + + /* lock_len: length of region that should end up locked */ + if (use_top) + lock_len = nor->mtd.size - ofs; + else + lock_len = ofs + len; + + sector_size = nor->sector_size; + n_sectors = (nor->size) / sector_size; + + bp_slots = (1 << hweight8(mask)) - 2; + bp_slots_needed = ilog2(n_sectors); + + if (bp_slots_needed > bp_slots) + sector_size <<= (bp_slots_needed - bp_slots); + + pow = ilog2(lock_len) - ilog2(sector_size) + 1; + blk_prot = pow << shift; + status_new = status_old | blk_prot; + if (status_old == status_new) + return 0; + + return write_sr_and_check(nor, status_new, mask); +} + +/** + * issi_flash_unlock() - clear BP[0123] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to unlock memory. + * @len: number of bytes to unlock. + * + * Bits [2345] of the Status Register are BP[0123]. + * ISSI chips use a different block protection scheme than other chips. + * Just disable the write-protect unilaterally. + * + * Return: 0 on success, -errno otherwise. + */ +static int issi_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int ret, val; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_ISSI; + + val = read_sr(nor); + if (val < 0) + return val; + + if (!(val & mask)) + return 0; + + write_enable(nor); + + write_sr(nor, val & ~mask); + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = write_disable(nor); + if (ret) + return ret; + + ret = read_sr(nor); + if (ret > 0 && !(ret & mask)) { + dev_info(nor->dev, "ISSI block protect bits cleared SR: 0x%x\n", + ret); + ret = 0; + } else { + dev_err(nor->dev, "ISSI block protect bits not cleared\n"); + ret = -EINVAL; + } + return ret; +} +#endif /* CONFIG_SPI_FLASH_ISSI */ #endif /* CONFIG_SPI_FLASH_LOCK */
#ifdef CONFIG_SPI_FLASH_SOFT_RESET @@ -5059,6 +5391,14 @@ int spi_nor_scan(struct spi_nor *nor) } #endif
+#if defined(CONFIG_SPI_FLASH_ISSI) + if (JEDEC_MFR(info) == SNOR_MFR_ISSI) { + nor->flash_lock = issi_flash_lock; + nor->flash_unlock = issi_flash_unlock; + nor->flash_is_unlocked = issi_is_unlocked; + } +#endif + #ifdef CONFIG_SPI_FLASH_SST /* * sst26 series block protection implementation differs from other diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 8ca874068a..9a560d94a2 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -77,6 +77,8 @@ #define SPINOR_OP_WREAR 0xc5 /* Write Extended Address Register */ #define SPINOR_OP_SRSTEN 0x66 /* Software Reset Enable */ #define SPINOR_OP_SRST 0x99 /* Software Reset */ +#define SPINOR_OP_RDFR 0x48 /* Read Function register */ +#define SPINOR_OP_WRFR 0x42 /* Write Function register 1 byte */
/* 4-byte address opcodes - used on Spansion and some Macronix flashes. */ #define SPINOR_OP_READ_4B 0x13 /* Read data bytes (low frequency) */ @@ -169,6 +171,7 @@ #define SR_BP2 BIT(4) /* Block protect 2 */ #define SR_BP3 BIT(6) /* Block protect 3 */ #define SR_BP3_MX BIT(5) /* Block protect 3 (Macronix) */ +#define SR_BP3_ISSI BIT(5) /* Block protect 3 (ISSI) */ #define SR_TB BIT(5) /* Top/Bottom protect */ #define SR_SRWD BIT(7) /* SR write protect */
@@ -192,6 +195,9 @@ #define FSR_P_ERR BIT(4) /* Program operation status */ #define FSR_PT_ERR BIT(1) /* Protection error bit */
+/* Function register bit */ +#define FR_TB BIT(1) /*ISSI: Top/Bottom protect */ + /* Configuration Register bits. */ #define CR_QUAD_EN_SPAN BIT(1) /* Spansion Quad I/O */ #define CR_TB_MX BIT(3) /* Macronix Top/Bottom protect */ @@ -322,6 +328,7 @@ enum spi_nor_option_flags { SNOR_F_IO_MODE_EN_VOLATILE = BIT(8), SNOR_F_HAS_STACKED = BIT(9), SNOR_F_HAS_PARALLEL = BIT(10), + SNOR_F_HAS_BP3 = BIT(11), };
struct spi_nor;

From: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
GIGADEVICE nor flashes provide block protection support using BP0, BP1, BP2, BP3 & TB bits in status register.
BP(Block Protection) bits defines memory to be software protected against PROGRAM or ERASE operations. When one or more block protect bits are set to 1, a designated memory area is protected from PROGRAM and ERASE operations. TB(Top/Bottom) bit determines whether the protected memory area defined by the block protect bits starts from the top or bottom of the memory array.
Used bottom/top protect to test lock/unlock on zc1751+dc1 board.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 309 +++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 2 + 2 files changed, 311 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 3bf1e5471b..3e2491cc3e 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -5164,6 +5164,307 @@ static int issi_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) return ret; } #endif /* CONFIG_SPI_FLASH_ISSI */ + +#if defined(CONFIG_SPI_FLASH_GIGADEVICE) +static void giga_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, + uint64_t *len) +{ + struct mtd_info *mtd = &nor->mtd; + int shift = 0; + int pow; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_GIGA; + u32 sector_size; + + sector_size = nor->sector_size; + if (nor->flags & SNOR_F_HAS_PARALLEL) + sector_size >>= 1; + + shift = ffs(mask) - 1; + + if (!(sr & mask)) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + pow = ((sr & mask) >> shift) - 1; + *len = sector_size << pow; + if (*len > mtd->size) + *len = mtd->size; + /* GIGA device's have top/bottom select bit in status reg */ + if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB_GIGA) + *ofs = 0; + else + *ofs = mtd->size - *len; + } +} + +/** + * giga_check_lock_status_sr() - check the status register and return + * the region is locked or unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * @locked: locked:1 unlocked:0 value + * + * Return: 1 if the entire region is locked (if @locked is true) or unlocked (if + * @locked is false); 0 otherwise. + */ +static int giga_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, u64 len, + u8 sr, bool locked) +{ + loff_t lock_offs; + u64 lock_len; + + if (!len) + return 1; + + giga_get_locked_range(nor, sr, &lock_offs, &lock_len); + if (locked) + /* Requested range is a sub-range of locked range */ + return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); + + /* Requested range does not overlap with locked range */ + return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); +} + +/** + * giga_is_locked_sr() - check if the memory region is locked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is locked. + * + * Return: false if region is locked 0 otherwise. + */ +static int giga_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return giga_check_lock_status_sr(nor, ofs, len, sr, true); +} + +/** + * giga_is_unlocked_sr() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is unlocked. + * + * Return: false if region is locked 0 otherwise. + */ +static int giga_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return giga_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * giga_is_unlocked() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * + * Check if memory region is unlocked + * + * Return: false if region is locked 0 otherwise. + */ +static int giga_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int sr; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + return giga_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * giga_nor_select_zone() - Select top area or bottom area to lock/unlock + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * @sr: status register + * @tb: pointer to top/bottom bool used in caller function + * @op: zone selection is for lock/unlock operation. 1: lock 0:unlock + * + * Select the top area / bottom area pattern to protect memory blocks. + * + * Return: negative on errors, 0 on success. + */ +static int giga_nor_select_zone(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr, bool *tb, bool op) +{ + int retval = 1; + bool can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, can_be_top = true; + + if (op) { + /* Select for lock zone operation */ + + /* + * If nothing in our range is unlocked, we don't need + * to do anything. + */ + if (giga_is_locked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is unlocked, we can't use 'bottom' + * protection. + */ + if (!giga_is_locked_sr(nor, 0, ofs, sr)) + can_be_bottom = false; + + /* + * If anything above us is unlocked, we can't use 'top' + * protection. + */ + if (!giga_is_locked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_top = false; + } else { + /* Select unlock zone */ + + /* + * If nothing in our range is locked, we don't need to + * do anything. + */ + if (giga_is_unlocked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is locked, we can't use 'top' + * protection + */ + if (!giga_is_unlocked_sr(nor, 0, ofs, sr)) + can_be_top = false; + + /* + * If anything above us is locked, we can't use 'bottom' + * protection + */ + if (!giga_is_unlocked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_bottom = false; + } + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + *tb = can_be_top; + return retval; +} + +/** + * giga_flash_lock() - set BP[0123] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * + * Lock a region of the flash.Implementation is based on stm_lock + * Supports the block protection bits BP{0,1,2,3} in status register + * + * Return: 0 on success, -errno otherwise. + */ +static int giga_flash_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int status_old, status_new, blk_prot; + loff_t lock_len; + u8 pow, ret, shift; + bool use_top = false; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_GIGA; + + shift = ffs(mask) - 1; + + status_old = read_sr(nor); + /* if status reg is Write protected don't update bit protection */ + if (status_old & SR_SRWD) { + dev_err(nor->dev, + "SR is write protected, can't update BP bits...\n"); + return -EINVAL; + } + + log_debug("SPI Protection: %s\n", (status_old & SR_TB_GIGA) ? "bottom" : "top"); + + ret = giga_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); + /* Older protected blocks include the new requested block's */ + if (ret <= 0) + return ret; + + /* lock_len: length of region that should end up locked */ + if (use_top) + lock_len = nor->mtd.size - ofs; + else + lock_len = ofs + len; + + pow = order_base_2(lock_len); + blk_prot = mask & (((pow + 1) & 0xf) << shift); + if (lock_len <= 0) { + dev_err(nor->dev, "invalid Length to protect"); + return -EINVAL; + } + + status_new = status_old | blk_prot; + if (!use_top) + status_new |= SR_TB_GIGA; + else + status_new &= ~SR_TB_GIGA; + + if (status_old == status_new) + return 0; + + return write_sr_and_check(nor, status_new, mask); +} + +/** + * giga_flash_unlock() - clear BP[0123] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to unlock memory. + * @len: number of bytes to unlock. + * + * Bits [2345] of the Status Register are BP[0123]. + * Clear the corresponding BP status bits. + * + * Return: 0 on success, -errno otherwise. + */ +static int giga_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int ret, val; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_GIGA; + + val = read_sr(nor); + if (val < 0) + return val; + + if (!(val & mask)) + return 0; + + write_enable(nor); + + write_sr(nor, val & ~mask); + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = write_disable(nor); + if (ret) + return ret; + + ret = read_sr(nor); + if (ret > 0 && !(ret & mask)) { + dev_info(nor->dev, "block protect bits cleared SR: 0x%x\n", + ret); + ret = 0; + } + return ret; +} +#endif /* CONFIG_SPI_FLASH_GIGADEVICE */ #endif /* CONFIG_SPI_FLASH_LOCK */
#ifdef CONFIG_SPI_FLASH_SOFT_RESET @@ -5399,6 +5700,14 @@ int spi_nor_scan(struct spi_nor *nor) } #endif
+#if defined(CONFIG_SPI_FLASH_GIGADEVICE) + if (JEDEC_MFR(info) == SNOR_MFR_GIGADEVICE) { + nor->flash_lock = giga_flash_lock; + nor->flash_unlock = giga_flash_unlock; + nor->flash_is_unlocked = giga_is_unlocked; + } +#endif + #ifdef CONFIG_SPI_FLASH_SST /* * sst26 series block protection implementation differs from other diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 9a560d94a2..797f0f275a 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -172,6 +172,8 @@ #define SR_BP3 BIT(6) /* Block protect 3 */ #define SR_BP3_MX BIT(5) /* Block protect 3 (Macronix) */ #define SR_BP3_ISSI BIT(5) /* Block protect 3 (ISSI) */ +#define SR_TB_GIGA BIT(6) /* Top/Bottom protect (GIGADEVICE)*/ +#define SR_BP3_GIGA BIT(5) /* Block protect 3 (GIGADEVICE) */ #define SR_TB BIT(5) /* Top/Bottom protect */ #define SR_SRWD BIT(7) /* SR write protect */

From: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
Spansion nor flashes provide block protection support using BP0, BP1, BP2 bits in status register.
The top/bottom select is instead done via a bit in the configuration register, which is OTP, so once set to use bottom protect, one cannot use top. On top of that, reading the configuration register uses a different opcode (0x15) than the existing SPINOR_OP_RDCR (0x35).
Used bottom protect to test s25fl512s flash part lock/unlock on zc1751+dc1 board.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 372 +++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 1 + 2 files changed, 373 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 3e2491cc3e..42483caaaa 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -5465,6 +5465,370 @@ static int giga_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) return ret; } #endif /* CONFIG_SPI_FLASH_GIGADEVICE */ + +#if defined(CONFIG_SPI_FLASH_SPANSION) +/** + * spansion_read_cr() - read configuration register + * @nor: pointer to a 'struct spi_nor'. + * + * Spansion devices have top/bottom area protection bits selection into + * configuration reg. The bits in CR are OTP. So once it's written, + * it cannot be changed. + * + * Return: Value in configuration register or negative if error. + */ +static int spansion_read_cr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDCR, &val, 1); + if (ret < 0) { + dev_dbg(nor->dev, "error %d reading CR\n", ret); + return ret; + } + + return val; +} + +static void spansion_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, + uint64_t *len) +{ + struct mtd_info *mtd = &nor->mtd; + int pow, cr; + int shift = 0; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + + shift = ffs(mask) - 1; + + cr = spansion_read_cr(nor); + if (!(sr & mask)) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + pow = ((sr & mask) ^ mask) >> shift; + *len = mtd->size >> pow; + /* SPANSION device's have top/bottom select bit in configuration reg */ + if (nor->flags & SNOR_F_HAS_SR_TB && cr & CR_TB_SPAN) + *ofs = 0; + else + *ofs = mtd->size - *len; + } +} + +/** + * spansion_check_lock_status_sr() - check the status register and return + * the region is locked or unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * @locked: locked:1 unlocked:0 value + * + * Return: 1 if the entire region is locked (if @locked is true) or unlocked (if + * @locked is false); 0 otherwise. + */ +static int spansion_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, u64 len, + u8 sr, bool locked) +{ + loff_t lock_offs; + u64 lock_len; + + if (!len) + return 1; + + spansion_get_locked_range(nor, sr, &lock_offs, &lock_len); + if (locked) + /* Requested range is a sub-range of locked range */ + return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); + + /* Requested range does not overlap with locked range */ + return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); +} + +/** + * spansion_is_locked_sr() - check if the memory region is locked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is locked. + * + * Return: false if region is locked 0 otherwise. + */ +static int spansion_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return spansion_check_lock_status_sr(nor, ofs, len, sr, true); +} + +/** + * spansion_is_unlocked_sr() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is unlocked. + * + * Return: false if region is locked 0 otherwise. + */ +static int spansion_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return spansion_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * spansion_is_unlocked() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * + * check if memory region is unlocked + * + * Return: false if region is locked 0 otherwise. + */ +static int spansion_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int sr; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + return spansion_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * spansion_nor_select_zone() - Select top area or bottom area to lock/unlock + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * @sr: status register + * @tb: pointer to top/bottom bool used in caller function + * @op: zone selection is for lock/unlock operation. 1: lock 0:unlock + * + * Select the top area / bottom area pattern to protect memory blocks. + * + * Return: negative on errors, 0 on success. + */ +static int spansion_nor_select_zone(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr, bool *tb, bool op) +{ + int retval = 1; + bool can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, can_be_top = true; + + if (op) { + /* Select for lock zone operation */ + + /* + * If nothing in our range is unlocked, we don't need + * to do anything. + */ + if (spansion_is_locked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is unlocked, we can't use 'bottom' + * protection. + */ + if (!spansion_is_locked_sr(nor, 0, ofs, sr)) + can_be_bottom = false; + + /* + * If anything above us is unlocked, we can't use 'top' + * protection. + */ + if (!spansion_is_locked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_top = false; + } else { + /* Select unlock zone */ + + /* + * If nothing in our range is locked, we don't need to + * do anything. + */ + if (spansion_is_unlocked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is locked, we can't use 'top' + * protection + */ + if (!spansion_is_unlocked_sr(nor, 0, ofs, sr)) + can_be_top = false; + + /* + * If anything above us is locked, we can't use 'bottom' + * protection + */ + if (!spansion_is_unlocked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_bottom = false; + } + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + *tb = can_be_top; + return retval; +} + +static int spansion_write_sr_cr(struct spi_nor *nor, u8 *sr_cr) +{ + int ret; + + write_enable(nor); + + ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); + if (ret < 0) { + dev_dbg(nor->dev, + "error while writing configuration register\n"); + return -EINVAL; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret) { + dev_dbg(nor->dev, + "timeout while writing configuration register\n"); + return ret; + } + + ret = write_disable(nor); + if (ret) + return ret; + + return 0; +} + +/** + * spansion_flash_lock() - set BP[012] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * + * Lock a region of the flash.Implementation is based on stm_lock + * Supports the block protection bits BP{0,1,2} in status register + * + * Return: 0 on success, -errno otherwise. + */ +static int spansion_flash_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + u64 lock_len; + u32 sector_size; + u16 n_sectors; + unsigned int bp_slots, bp_slots_needed; + int cr, can_be_top, val, status_old; + u8 pow, ret, shift, sr_cr[2]; + bool use_top = false; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + + shift = ffs(mask) - 1; + + status_old = read_sr(nor); + /* if status reg is Write protected don't update bit protection */ + if (status_old & SR_SRWD) { + dev_err(nor->dev, + "SR is write protected, can't update BP bits...\n"); + return -EINVAL; + } + + cr = spansion_read_cr(nor); + if (cr < 0) + return cr; + + log_debug("SPI Protection: %s\n", (cr & CR_TB_SPAN) ? "bottom" : "top"); + + /* CR_TB is OTP, so we can't use 'top' protection if that is already set. */ + can_be_top = !(cr & CR_TB_SPAN); + + ret = spansion_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); + /* Older protected blocks include the new requested block's */ + if (ret <= 0) + return ret; + + use_top = can_be_top; + + /* lock_len: length of region that should end up locked */ + if (use_top) + lock_len = nor->mtd.size - ofs; + else + lock_len = ofs + len; + + sector_size = nor->sector_size; + n_sectors = (nor->size) / sector_size; + + bp_slots = (1 << hweight8(mask)) - 2; + bp_slots_needed = ilog2(n_sectors); + + if (bp_slots_needed > bp_slots) + sector_size <<= (bp_slots_needed - bp_slots); + + pow = ilog2(lock_len) - ilog2(sector_size) + 1; + val = pow << shift; + sr_cr[0] = status_old & ~mask; + sr_cr[0] |= val; + + sr_cr[1] = cr | (use_top ? 0 : CR_TB_SPAN); + ret = spansion_write_sr_cr(nor, sr_cr); + if (ret) + return ret; + + /* Check that the bits got written as expected */ + ret = read_sr(nor); + if (ret < 0) + return ret; + + ret = spansion_read_cr(nor); + if (ret < 0) + return ret; + + return 0; +} + +/** + * spansion_flash_unlock() - clear BP[012] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to unlock memory. + * @len: number of bytes to unlock. + * + * Bits [234] of the Status Register are BP[012]. + * Clear the corresponding BP status bits. + * + * Return: 0 on success, -errno otherwise. + */ +static int spansion_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int ret, val; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + + val = read_sr(nor); + if (val < 0) + return val; + + if (!(val & mask)) + return 0; + + write_enable(nor); + + write_sr(nor, val & ~mask); + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = write_disable(nor); + if (ret) + return ret; + + return 0; +} +#endif /* CONFIG_SPI_FLASH_SPANSION */ #endif /* CONFIG_SPI_FLASH_LOCK */
#ifdef CONFIG_SPI_FLASH_SOFT_RESET @@ -5708,6 +6072,14 @@ int spi_nor_scan(struct spi_nor *nor) } #endif
+#if defined(CONFIG_SPI_FLASH_SPANSION) + if (JEDEC_MFR(info) == SNOR_MFR_SPANSION) { + nor->flash_lock = spansion_flash_lock; + nor->flash_unlock = spansion_flash_unlock; + nor->flash_is_unlocked = spansion_is_unlocked; + } +#endif + #ifdef CONFIG_SPI_FLASH_SST /* * sst26 series block protection implementation differs from other diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 797f0f275a..07d6c4ff93 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -203,6 +203,7 @@ /* Configuration Register bits. */ #define CR_QUAD_EN_SPAN BIT(1) /* Spansion Quad I/O */ #define CR_TB_MX BIT(3) /* Macronix Top/Bottom protect */ +#define CR_TB_SPAN BIT(5) /* Spansion Top/Bottom protect */
/* Status Register 2 bits. */ #define SR2_QUAD_EN_BIT7 BIT(7)

On Wed, Dec 6, 2023 at 3:02 PM Tejas Bhumkar tejas.arvind.bhumkar@amd.com wrote:
A set of patches has been developed to resolve concerns regarding data integrity failures in QSPI and OSPI for the Versal, Versal NET, Zynq, and ZynqMP platforms.
The series has undergone testing with flashes on the default setup, and comprehensive testing is currently underway to test the series with all available flash parts.
These patches are built upon the v5 series, which can be found at the following link: https://lore.kernel.org/all/20231201031839.239567-1-venkatesh.abbarapu@amd.c...
Changes in v2:
- Removed the SPI_NOR_HAS_TB flag for gd25lx256e and is25wx256 flashes since it already exists in a tree.
Algapally Santosh Sagar (1): mtd: spi-nor-ids: Add support for W25Q02NW
Ashok Reddy Soma (10): mtd: spi-nor: Enable mt35xu512aba_fixups for all mt35xx flashes mtd: spi-nor: Add support for cross die read in dual flash configuration mtd: spi-nor: Enable DTR octal flash program mtd: spi-nor: Send write disable cmd after every write enable mtd: spi-nor: Check SNOR_F_IO_MODE_EN_VOLATILE only if SFDP is enabled spi: cadence_qspi: Set tshsl_ns to at least one sclk_ns spi: cadence_qspi: Clean up registers in init spi: cadence_qspi: Initialize read and write watermark registers spi: cadence_qspi: Enable ECO bit for higher frequencies spi: cadence_qspi: Write aligned byte length to ahbbase
T Karthik Reddy (9): mtd: spi-nor: Add config to enable flash DTR mtd: spi-nor-core: Set dummy buswidth equal to data buswidth spi: mtd: Use split reads if multi-die flag is set mtd: spi-nor: program quad enable bit for winbond flashes spi: cadence_qspi: Setup ddr mode in cadence qspi driver spi: cadence-qspi: Switch SDR/DTR using SPI_FLASH_DTR_ENABLE config spi: cadence_ospi_versal: ospi ddr changes in cadence ospi versal driver spi: cadence_qspi: Add spi mem dtr support ops mtd: spi-nor: Add block protection support for micron flashes
Tejas Bhumkar (5): arm64: versal: Enable defconfig for Micron octal flashes mtd: spi-nor: Update erase operation function spi: cadence_qspi: Fix versal ospi indirect write timed out issue arm64: versal: Enable soft reset support for xspi flashes arm64: versal: Enable octal DTR mode
Venkatesh Yadav Abbarapu (5): mtd: spi-nor: Update block protection flags for flash parts mtd: spi-nor: Add support for locking on Macronix nor flashes mtd: spi-nor: Add support for locking on ISSI nor flashes mtd: spi-nor: Add support for locking on GIGADEVICE nor flashes mtd: spi-nor: Add support for locking on Spansion nor flashes
Look like there are 3 or more topics are covered in single patch set, please break them and send separate series.
Jagan
participants (3)
-
Bhumkar, Tejas Arvind
-
Jagan Teki
-
Tejas Bhumkar