From e37ee8734881a7f92dbbeecf8cc129dd8a6a76b8 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:41 +0200 Subject: mtd: spi-nor: move cmd_ext_type into spi_nor_flash_parameter Right now the SFDP parsing code is modifying both members of struct spi_nor_flash_parameter and struct spi_nor. This may lead to inconsistencies if the parsing fails because only the flash parameters are rolled back. To fix this, move cmd_ext_type into the struct spi_nor_flash_parameter. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260601125438.3481722-1-mwalle%40kernel.org?part=3 Signed-off-by: Michael Walle --- include/linux/mtd/spi-nor.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 4b92494827b1..e75c1b4cb4c0 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -366,7 +366,6 @@ struct spi_nor_flash_parameter; * @program_opcode: the program opcode * @sst_write_second: used by the SST write operation * @flags: flag options for the current SPI NOR (SNOR_F_*) - * @cmd_ext_type: the command opcode extension type for DTR mode. * @read_proto: the SPI protocol for read operations * @write_proto: the SPI protocol for write operations * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations @@ -408,7 +407,6 @@ struct spi_nor { enum spi_nor_protocol reg_proto; bool sst_write_second; u32 flags; - enum spi_nor_cmd_ext cmd_ext_type; struct sfdp *sfdp; struct dentry *debugfs_root; u8 dfs_sr_cache[2]; -- cgit v1.2.3 From 0ede46b4f5b38cb43225927abbf088dd568db54e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:42 +0200 Subject: mtd: spi-nor: move flags into spi_nor_flash_parameter Right now the SFDP parsing code is modifying both members of struct spi_nor_flash_parameter and struct spi_nor. This may lead to inconsistencies if the parsing fails because only the flash parameters are rolled back. To fix this, move the flags into the struct spi_nor_flash_parameter. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260601125438.3481722-1-mwalle%40kernel.org?part=3 Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 83 +++++++++++++++++++++++------------------ drivers/mtd/spi-nor/core.h | 2 + drivers/mtd/spi-nor/debugfs.c | 4 +- drivers/mtd/spi-nor/macronix.c | 2 +- drivers/mtd/spi-nor/micron-st.c | 6 +-- drivers/mtd/spi-nor/sfdp.c | 16 ++++---- drivers/mtd/spi-nor/spansion.c | 4 +- drivers/mtd/spi-nor/swp.c | 52 ++++++++++++++++---------- drivers/mtd/spi-nor/winbond.c | 4 +- include/linux/mtd/spi-nor.h | 2 - 10 files changed, 98 insertions(+), 77 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 789f522b9b47..916e2a1698f2 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -83,6 +83,7 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor, struct spi_mem_op *op, const enum spi_nor_protocol proto) { + struct spi_nor_flash_parameter *params = nor->params; u8 ext; op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(proto); @@ -116,7 +117,7 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor, op->cmd.nbytes = 2; } - if (proto == SNOR_PROTO_8_8_8_DTR && nor->flags & SNOR_F_SWAP16) + if (proto == SNOR_PROTO_8_8_8_DTR && params->flags & SNOR_F_SWAP16) op->data.swap16 = true; } @@ -633,7 +634,7 @@ int spi_nor_sr_ready(struct spi_nor *nor) */ static bool spi_nor_use_parallel_locking(struct spi_nor *nor) { - return nor->flags & SNOR_F_RWW; + return nor->params->flags & SNOR_F_RWW; } /* Locking helpers for status read operations */ @@ -860,12 +861,13 @@ static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1) */ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) { + struct spi_nor_flash_parameter *params = nor->params; int ret; u8 *sr_cr = nor->bouncebuf; u8 cr_written; /* Make sure we don't overwrite the contents of Status Register 2. */ - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, &sr_cr[1]); if (ret) return ret; @@ -906,7 +908,7 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) return -EIO; } - if (nor->flags & SNOR_F_NO_READ_CR) + if (params->flags & SNOR_F_NO_READ_CR) return 0; cr_written = sr_cr[1]; @@ -961,7 +963,7 @@ int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) return -EIO; } - if (nor->flags & SNOR_F_NO_READ_CR) + if (nor->params->flags & SNOR_F_NO_READ_CR) return 0; ret = spi_nor_read_cr(nor, &sr_cr[1]); @@ -1009,7 +1011,7 @@ static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *re return -EIO; } - if (nor->flags & SNOR_F_NO_READ_CR) + if (nor->params->flags & SNOR_F_NO_READ_CR) return 0; ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]); @@ -1035,7 +1037,7 @@ static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *re */ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) { - if (nor->flags & SNOR_F_HAS_16BIT_SR) + if (nor->params->flags & SNOR_F_HAS_16BIT_SR) return spi_nor_write_16bit_sr_and_check(nor, sr1); return spi_nor_write_sr1_and_check(nor, sr1); @@ -1052,7 +1054,7 @@ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) */ int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) { - if (nor->flags & SNOR_F_HAS_16BIT_SR) + if (nor->params->flags & SNOR_F_HAS_16BIT_SR) return spi_nor_write_16bit_sr_cr_and_check(nor, regs); return spi_nor_write_sr1_and_check(nor, regs[0]); @@ -1821,6 +1823,7 @@ static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr, static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) { struct spi_nor *nor = mtd_to_spi_nor(mtd); + struct spi_nor_flash_parameter *params = nor->params; u8 n_dice = nor->params->n_dice; bool multi_die_erase = false; u32 addr, len, rem; @@ -1852,7 +1855,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) return ret; /* chip (die) erase? */ - if ((len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) || + if ((len == mtd->size && !(params->flags & SNOR_F_NO_OP_CHIP_ERASE)) || multi_die_erase) { ret = spi_nor_erase_dice(nor, addr, len, die_size); if (ret) @@ -1942,7 +1945,7 @@ int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor) { int ret; - if (nor->flags & SNOR_F_NO_READ_CR) + if (nor->params->flags & SNOR_F_NO_READ_CR) return spi_nor_write_16bit_cr_and_check(nor, SR2_QUAD_EN_BIT1); ret = spi_nor_read_cr(nor, nor->bouncebuf); @@ -2502,7 +2505,7 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) * If the reset line is broken, we do not want to enter a stateful * mode. */ - if (nor->flags & SNOR_F_BROKEN_RESET) + if (params->flags & SNOR_F_BROKEN_RESET) *hwcaps &= ~(SNOR_HWCAPS_X_X_X | SNOR_HWCAPS_X_X_X_DTR); for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) { @@ -2526,13 +2529,13 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) } /* Some SPI controllers might not support CR read opcode. */ - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { struct spi_mem_op op = SPI_NOR_RDCR_OP(nor->bouncebuf); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); if (!spi_mem_supports_op(nor->spimem, &op)) - nor->flags |= SNOR_F_NO_READ_CR; + params->flags |= SNOR_F_NO_READ_CR; } } @@ -2749,6 +2752,8 @@ static int spi_nor_select_erase(struct spi_nor *nor) static int spi_nor_set_addr_nbytes(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; + if (nor->params->addr_nbytes) { nor->addr_nbytes = nor->params->addr_nbytes; } else if (nor->read_proto == SNOR_PROTO_8_8_8_DTR) { @@ -2783,8 +2788,8 @@ static int spi_nor_set_addr_nbytes(struct spi_nor *nor) } /* Set 4byte opcodes when possible. */ - if (nor->addr_nbytes == 4 && nor->flags & SNOR_F_4B_OPCODES && - !(nor->flags & SNOR_F_HAS_4BAIT)) + if (nor->addr_nbytes == 4 && params->flags & SNOR_F_4B_OPCODES && + !(params->flags & SNOR_F_HAS_4BAIT)) spi_nor_set_4byte_opcodes(nor); return 0; @@ -2947,39 +2952,40 @@ static void spi_nor_no_sfdp_init_params(struct spi_nor *nor) */ static void spi_nor_init_flags(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; struct device_node *np = spi_nor_get_flash_node(nor); const u16 flags = nor->info->flags; if (of_property_read_bool(np, "broken-flash-reset")) - nor->flags |= SNOR_F_BROKEN_RESET; + params->flags |= SNOR_F_BROKEN_RESET; if (of_property_read_bool(np, "no-wp")) - nor->flags |= SNOR_F_NO_WP; + params->flags |= SNOR_F_NO_WP; if (flags & SPI_NOR_SWP_IS_VOLATILE) - nor->flags |= SNOR_F_SWP_IS_VOLATILE; + params->flags |= SNOR_F_SWP_IS_VOLATILE; if (flags & SPI_NOR_HAS_LOCK) - nor->flags |= SNOR_F_HAS_LOCK; + params->flags |= SNOR_F_HAS_LOCK; if (flags & SPI_NOR_HAS_TB) { - nor->flags |= SNOR_F_HAS_SR_TB; + params->flags |= SNOR_F_HAS_SR_TB; if (flags & SPI_NOR_TB_SR_BIT6) - nor->flags |= SNOR_F_HAS_SR_TB_BIT6; + params->flags |= SNOR_F_HAS_SR_TB_BIT6; } if (flags & SPI_NOR_4BIT_BP) { - nor->flags |= SNOR_F_HAS_4BIT_BP; + params->flags |= SNOR_F_HAS_4BIT_BP; if (flags & SPI_NOR_BP3_SR_BIT6) - nor->flags |= SNOR_F_HAS_SR_BP3_BIT6; + params->flags |= SNOR_F_HAS_SR_BP3_BIT6; } if (flags & SPI_NOR_HAS_CMP) - nor->flags |= SNOR_F_HAS_SR2_CMP_BIT6; + params->flags |= SNOR_F_HAS_SR2_CMP_BIT6; if (flags & SPI_NOR_RWW && nor->params->n_banks > 1 && !nor->controller_ops) - nor->flags |= SNOR_F_RWW; + params->flags |= SNOR_F_RWW; } /** @@ -2992,13 +2998,14 @@ static void spi_nor_init_flags(struct spi_nor *nor) */ static void spi_nor_init_fixup_flags(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; const u8 fixup_flags = nor->info->fixup_flags; if (fixup_flags & SPI_NOR_4B_OPCODES) - nor->flags |= SNOR_F_4B_OPCODES; + params->flags |= SNOR_F_4B_OPCODES; if (fixup_flags & SPI_NOR_IO_MODE_EN_VOLATILE) - nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + params->flags |= SNOR_F_IO_MODE_EN_VOLATILE; } /** @@ -3043,7 +3050,7 @@ static int spi_nor_late_init_params(struct spi_nor *nor) * NOR protection support. When locking_ops are not provided, we pick * the default ones. */ - if (nor->flags & SNOR_F_HAS_LOCK && !nor->params->locking_ops) + if (params->flags & SNOR_F_HAS_LOCK && !nor->params->locking_ops) spi_nor_init_default_locking_ops(nor); if (params->n_banks > 1) @@ -3108,7 +3115,7 @@ static void spi_nor_init_default_params(struct spi_nor *nor) params->otp.org = info->otp; /* Default to 16-bit Write Status (01h) Command */ - nor->flags |= SNOR_F_HAS_16BIT_SR; + params->flags |= SNOR_F_HAS_16BIT_SR; /* Set SPI NOR sizes. */ params->writesize = 1; @@ -3229,7 +3236,7 @@ static int spi_nor_set_octal_dtr(struct spi_nor *nor, bool enable) nor->write_proto == SNOR_PROTO_8_8_8_DTR)) return 0; - if (!(nor->flags & SNOR_F_IO_MODE_EN_VOLATILE)) + if (!(nor->params->flags & SNOR_F_IO_MODE_EN_VOLATILE)) return 0; ret = nor->params->set_octal_dtr(nor, enable); @@ -3282,7 +3289,7 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) * reboots (e.g., crashes). Warn the user (or hopefully, system * designer) that this is bad. */ - WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET, + WARN_ONCE(nor->params->flags & SNOR_F_BROKEN_RESET, "enabling reset hack; may not recover from unexpected reboots\n"); } @@ -3303,6 +3310,7 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) static int spi_nor_init(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; int err; err = spi_nor_set_octal_dtr(nor, true); @@ -3330,13 +3338,13 @@ static int spi_nor_init(struct spi_nor *nor) spi_nor_cache_sr_lock_bits(nor, NULL); if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) || (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) && - nor->flags & SNOR_F_SWP_IS_VOLATILE)) { + params->flags & SNOR_F_SWP_IS_VOLATILE)) { spi_nor_try_unlock_all(nor); } if (nor->addr_nbytes == 4 && nor->read_proto != SNOR_PROTO_8_8_8_DTR && - !(nor->flags & SNOR_F_4B_OPCODES)) + !(params->flags & SNOR_F_4B_OPCODES)) return spi_nor_set_4byte_addr_mode(nor, true); return 0; @@ -3451,11 +3459,12 @@ static void spi_nor_put_device(struct mtd_info *mtd) static void spi_nor_restore(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; int ret; /* restore the addressing mode */ - if (nor->addr_nbytes == 4 && !(nor->flags & SNOR_F_4B_OPCODES) && - nor->flags & SNOR_F_BROKEN_RESET) { + if (nor->addr_nbytes == 4 && !(params->flags & SNOR_F_4B_OPCODES) && + params->flags & SNOR_F_BROKEN_RESET) { ret = spi_nor_set_4byte_addr_mode(nor, false); if (ret) /* @@ -3466,7 +3475,7 @@ static void spi_nor_restore(struct spi_nor *nor) dev_err(nor->dev, "Failed to exit 4-byte address mode, err = %d\n", ret); } - if (nor->flags & SNOR_F_SOFT_RESET) + if (params->flags & SNOR_F_SOFT_RESET) spi_nor_soft_reset(nor); } @@ -3583,7 +3592,7 @@ static int spi_nor_set_mtd_info(struct spi_nor *nor) mtd->type = MTD_NORFLASH; mtd->flags = MTD_CAP_NORFLASH; /* Unset BIT_WRITEABLE to enable JFFS2 write buffer for ECC'd NOR */ - if (nor->flags & SNOR_F_ECC) + if (nor->params->flags & SNOR_F_ECC) mtd->flags &= ~MTD_BIT_WRITEABLE; if (nor->info->flags & SPI_NOR_NO_ERASE) mtd->flags |= MTD_NO_ERASE; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 6a77ea2901a3..cc6601f51e63 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -348,6 +348,7 @@ struct spi_nor_otp { * * @bank_size: the flash memory bank density in bytes. * @size: the total flash memory density in bytes. + * @flags: flag options for the current SPI NOR (SNOR_F_*) * @writesize Minimal writable flash unit size. Defaults to 1. Set to * ECC unit size for ECC-ed flashes. * @page_size: the page size of the SPI NOR flash memory. @@ -386,6 +387,7 @@ struct spi_nor_otp { struct spi_nor_flash_parameter { u64 bank_size; u64 size; + u32 flags; u32 writesize; u32 page_size; u8 addr_nbytes; diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 37738e2b262e..095085a723e7 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -99,7 +99,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data) seq_printf(s, "address nbytes\t%u\n", nor->addr_nbytes); seq_puts(s, "flags\t\t"); - spi_nor_print_flags(s, nor->flags, snor_f_names, + spi_nor_print_flags(s, params->flags, snor_f_names, ARRAY_SIZE(snor_f_names)); seq_puts(s, "\n"); @@ -144,7 +144,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data) } } - if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { + if (!(params->flags & SNOR_F_NO_OP_CHIP_ERASE)) { string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf); } diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index 332e09c8332c..9610d9e14e4c 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -40,7 +40,7 @@ mx25l25635_post_bfpt_fixups(struct spi_nor *nor, * its BFPT table. */ if (bfpt->dwords[SFDP_DWORD(5)] & BFPT_DWORD5_FAST_READ_4_4_4) - nor->flags |= SNOR_F_4B_OPCODES; + nor->params->flags |= SNOR_F_4B_OPCODES; return 0; } diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 186362066c6b..ca2ce18050df 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -229,7 +229,7 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, const struct sfdp_bfpt *bfpt) { - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; return 0; } @@ -632,8 +632,8 @@ static int micron_st_nor_ready(struct spi_nor *nor) static void micron_st_nor_default_init(struct spi_nor *nor) { - nor->flags |= SNOR_F_HAS_LOCK; - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->flags |= SNOR_F_HAS_LOCK; + nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; nor->params->quad_enable = NULL; } diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 5b41ffec7374..49e6113557be 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -577,17 +577,17 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Read Configuration Register (35h) instruction is not * supported. */ - nor->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR; + params->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR; params->quad_enable = spi_nor_sr2_bit1_quad_enable; break; case BFPT_DWORD15_QER_SR1_BIT6: - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + params->flags &= ~SNOR_F_HAS_16BIT_SR; params->quad_enable = spi_nor_sr1_bit6_quad_enable; break; case BFPT_DWORD15_QER_SR2_BIT7: - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + params->flags &= ~SNOR_F_HAS_16BIT_SR; params->quad_enable = spi_nor_sr2_bit7_quad_enable; break; @@ -598,7 +598,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Register 2, so let's be cautious and keep the default * assumption of a 16-bit Write Status (01h) command. */ - nor->flags |= SNOR_F_HAS_16BIT_SR; + params->flags |= SNOR_F_HAS_16BIT_SR; params->quad_enable = spi_nor_sr2_bit1_quad_enable; break; @@ -620,7 +620,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, /* Soft Reset support. */ if (bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_SWRST_EN_RST) - nor->flags |= SNOR_F_SOFT_RESET; + params->flags |= SNOR_F_SOFT_RESET; /* Stop here if not JESD216 rev C or later. */ if (bfpt_header->length == BFPT_DWORD_MAX_JESD216B) @@ -673,7 +673,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, /* Byte order in 8D-8D-8D mode */ if (bfpt.dwords[SFDP_DWORD(18)] & BFPT_DWORD18_BYTE_ORDER_SWAPPED) - nor->flags |= SNOR_F_SWAP16; + params->flags |= SNOR_F_SWAP16; return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt); } @@ -1160,7 +1160,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor, * SFDP compliant memories. */ params->addr_nbytes = 4; - nor->flags |= SNOR_F_4B_OPCODES | SNOR_F_HAS_4BAIT; + params->flags |= SNOR_F_4B_OPCODES | SNOR_F_HAS_4BAIT; /* fall through */ out: @@ -1306,7 +1306,7 @@ static int spi_nor_parse_sccr(struct spi_nor *nor, if (FIELD_GET(SCCR_DWORD22_OCTAL_DTR_EN_VOLATILE, dwords[SFDP_DWORD(22)])) - nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + params->flags |= SNOR_F_IO_MODE_EN_VOLATILE; out: kfree(dwords); diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 092450375d4e..fc5da04aff07 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -537,7 +537,7 @@ static void cypress_nor_ecc_init(struct spi_nor *nor) * same ECC data unit without an erase are not allowed. */ nor->params->writesize = 16; - nor->flags |= SNOR_F_ECC; + nor->params->flags |= SNOR_F_ECC; } static int @@ -1139,7 +1139,7 @@ static int spansion_nor_late_init(struct spi_nor *nor) u8 mfr_flags = nor->info->mfr_flags; if (params->size > SZ_16M) { - nor->flags |= SNOR_F_4B_OPCODES; + params->flags |= SNOR_F_4B_OPCODES; /* No small sector erase for 4-byte command set */ nor->erase_opcode = SPINOR_OP_SE; nor->mtd.erasesize = nor->info->sector_size ?: diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 235070b215d1..62720045a9ab 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -13,12 +13,13 @@ static u8 spi_nor_get_sr_bp_mask(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; u8 mask = SR_BP2 | SR_BP1 | SR_BP0; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6) + if (params->flags & SNOR_F_HAS_SR_BP3_BIT6) return mask | SR_BP3_BIT6; - if (nor->flags & SNOR_F_HAS_4BIT_BP) + if (params->flags & SNOR_F_HAS_4BIT_BP) return mask | SR_BP3; return mask; @@ -26,9 +27,11 @@ static u8 spi_nor_get_sr_bp_mask(struct spi_nor *nor) static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) { - if (nor->flags & SNOR_F_HAS_SR_TB_BIT6) + struct spi_nor_flash_parameter *params = nor->params; + + if (params->flags & SNOR_F_HAS_SR_TB_BIT6) return SR_TB_BIT6; - else if (nor->flags & SNOR_F_HAS_SR_TB) + else if (params->flags & SNOR_F_HAS_SR_TB) return SR_TB_BIT5; else return 0; @@ -36,8 +39,10 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor) { - if (!(nor->flags & SNOR_F_NO_READ_CR) && - nor->flags & SNOR_F_HAS_SR2_CMP_BIT6) + struct spi_nor_flash_parameter *params = nor->params; + + if (!(params->flags & SNOR_F_NO_READ_CR) && + params->flags & SNOR_F_HAS_SR2_CMP_BIT6) return SR2_CMP_BIT6; else return 0; @@ -67,15 +72,16 @@ u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, u64 *len) { + struct spi_nor_flash_parameter *params = nor->params; u64 min_prot_len; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor); u8 bp, val = sr[0] & bp_mask; - bool tb = (nor->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0; + bool tb = (params->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0; bool cmp = sr[1] & cmp_mask; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) + if (params->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) val = (val & ~SR_BP3_BIT6) | SR_BP3; bp = val >> SR_BP_SHIFT; @@ -153,10 +159,11 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len, static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow) { + struct spi_nor_flash_parameter *params = nor->params; u8 mask = spi_nor_get_sr_bp_mask(nor); u8 val = pow << SR_BP_SHIFT; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) + if (params->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) val = (val & ~SR_BP3) | SR_BP3_BIT6; if (val & ~mask) @@ -200,6 +207,7 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, */ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) { + struct spi_nor_flash_parameter *params = nor->params; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor); @@ -212,7 +220,7 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) sr_cr[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { if (spi_nor_read_cr(nor, nor->bouncebuf)) return; } @@ -261,13 +269,14 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) */ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) { + struct spi_nor_flash_parameter *params = nor->params; u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {}; u8 *best_status_new = status_new; loff_t ofs_old, ofs_new, ofs_new_cmp; u64 len_old, len_new, len_new_cmp; loff_t lock_len; - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, + bool can_be_top = true, can_be_bottom = params->flags & SNOR_F_HAS_SR_TB, can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; @@ -279,7 +288,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) status_old[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, nor->bouncebuf); if (ret) return ret; @@ -313,7 +322,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) lock_len = ofs + len; if (lock_len == nor->params->size) - pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0); + pow = (params->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0); else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; @@ -359,7 +368,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) * wrongly tied to GND (that includes internal pull-downs). * WP# pin hard strapped to GND can be a valid use case. */ - if (!(nor->flags & SNOR_F_NO_WP)) + if (!(params->flags & SNOR_F_NO_WP)) best_status_new[0] |= SR_SRWD; spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); @@ -378,7 +387,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old))) return -EINVAL; - if (nor->flags & SNOR_F_NO_READ_CR) + if (params->flags & SNOR_F_NO_READ_CR) ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); else ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); @@ -397,13 +406,14 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) */ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) { + struct spi_nor_flash_parameter *params = nor->params; u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {}; u8 *best_status_new = status_new; loff_t ofs_old, ofs_new, ofs_new_cmp; u64 len_old, len_new, len_new_cmp; loff_t lock_len; - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, + bool can_be_top = true, can_be_bottom = params->flags & SNOR_F_HAS_SR_TB, can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; @@ -415,7 +425,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) status_old[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, nor->bouncebuf); if (ret) return ret; @@ -512,7 +522,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new))) return -EINVAL; - if (nor->flags & SNOR_F_NO_READ_CR) + if (params->flags & SNOR_F_NO_READ_CR) ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); else ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); @@ -533,6 +543,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) */ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) { + struct spi_nor_flash_parameter *params = nor->params; u8 sr_cr[2] = {}; int ret; @@ -542,7 +553,7 @@ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) sr_cr[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, nor->bouncebuf); if (ret) return ret; @@ -628,9 +639,10 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, u64 len) */ void spi_nor_try_unlock_all(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; int ret; - if (!(nor->flags & SNOR_F_HAS_LOCK)) + if (!(params->flags & SNOR_F_HAS_LOCK)) return; dev_dbg(nor->dev, "Unprotecting entire flash array\n"); diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 8ebdbcec0b3f..6b99d9488c40 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -64,7 +64,7 @@ w25q256_post_bfpt_fixups(struct spi_nor *nor, */ if (bfpt_header->major == SFDP_JESD216_MAJOR && bfpt_header->minor == SFDP_JESD216A_MINOR) - nor->flags |= SNOR_F_4B_OPCODES; + nor->params->flags |= SNOR_F_4B_OPCODES; return 0; } @@ -84,7 +84,7 @@ winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor, * both incorrect and impractical, as the chip features a CMP bit for its * locking scheme that lays in the Control Register, and needs to be read. */ - nor->flags &= ~SNOR_F_NO_READ_CR; + nor->params->flags &= ~SNOR_F_NO_READ_CR; return 0; } diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index e75c1b4cb4c0..099c5016b485 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -365,7 +365,6 @@ struct spi_nor_flash_parameter; * @read_dummy: the dummy needed by the read operation * @program_opcode: the program opcode * @sst_write_second: used by the SST write operation - * @flags: flag options for the current SPI NOR (SNOR_F_*) * @read_proto: the SPI protocol for read operations * @write_proto: the SPI protocol for write operations * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations @@ -406,7 +405,6 @@ struct spi_nor { enum spi_nor_protocol write_proto; enum spi_nor_protocol reg_proto; bool sst_write_second; - u32 flags; struct sfdp *sfdp; struct dentry *debugfs_root; u8 dfs_sr_cache[2]; -- cgit v1.2.3 From 90936c3c790ec22b02c6d8611a9df0aecb8139bb Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 18:22:03 +0530 Subject: mtd: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Miquel Raynal --- drivers/mtd/devices/docg3.c | 4 ++-- drivers/mtd/nand/onenand/onenand_base.c | 2 +- drivers/mtd/nand/raw/fsmc_nand.c | 2 +- drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 2 +- drivers/mtd/nand/raw/mtk_nand.c | 2 +- drivers/mtd/nand/raw/nand_base.c | 2 +- drivers/mtd/nand/raw/nand_legacy.c | 4 ++-- drivers/mtd/spi-nor/otp.c | 2 +- include/linux/mtd/bbm.h | 2 +- include/linux/mtd/rawnand.h | 2 +- include/linux/mtd/sh_flctl.h | 4 ++-- 11 files changed, 14 insertions(+), 14 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c index 603fd0efc2ea..84566997d665 100644 --- a/drivers/mtd/devices/docg3.c +++ b/drivers/mtd/devices/docg3.c @@ -1884,7 +1884,7 @@ static void doc_release_device(struct mtd_info *mtd) /** * docg3_resume - Awakens docg3 floor - * @pdev: platfrom device + * @pdev: platform device * * Returns 0 (always successful) */ @@ -1963,7 +1963,7 @@ static int docg3_suspend(struct platform_device *pdev, pm_message_t state) * @pdev: platform device * * Probes for a G3 chip at the specified IO space in the platform data - * ressources. The floor 0 must be available. + * resources. The floor 0 must be available. * * Returns 0 on success, -ENOMEM, -ENXIO on error */ diff --git a/drivers/mtd/nand/onenand/onenand_base.c b/drivers/mtd/nand/onenand/onenand_base.c index d08aeac86f9f..bca5a3bebd41 100644 --- a/drivers/mtd/nand/onenand/onenand_base.c +++ b/drivers/mtd/nand/onenand/onenand_base.c @@ -3279,7 +3279,7 @@ static void onenand_check_features(struct mtd_info *mtd) * * Now it has as following * KFM4G16Q4M has NOP 4 with version ID 0x0131 - * KFM4G16Q5M has NOP 1 with versoin ID 0x013e + * KFM4G16Q5M has NOP 1 with version ID 0x013e */ if ((this->version_id & 0xf) == 0xe) this->options |= ONENAND_HAS_NOP_1; diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c index b13b2b0c3f30..08ef4425559c 100644 --- a/drivers/mtd/nand/raw/fsmc_nand.c +++ b/drivers/mtd/nand/raw/fsmc_nand.c @@ -461,7 +461,7 @@ static int fsmc_correct_ecc1(struct nand_chip *chip, chip->ecc.size, sm_order); } -/* Count the number of 0's in buff upto a max of max_bits */ +/* Count the number of 0's in buff up to a max of max_bits */ static int count_written_bits(u8 *buff, int size, int max_bits) { int k, written_bits = 0; diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c index 527165ccc839..c61f3983a713 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -2271,7 +2271,7 @@ static int nand_boot_init(struct gpmi_nand_data *this) { nand_boot_set_geometry(this); - /* This is ROM arch-specific initilization before the BBT scanning. */ + /* This is ROM arch-specific initialization before the BBT scanning. */ if (GPMI_IS_MX23(this)) return mx23_boot_init(this); return 0; diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c index 21c7e1102746..c439b7814ebb 100644 --- a/drivers/mtd/nand/raw/mtk_nand.c +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -42,7 +42,7 @@ #define CON_BRD BIT(8) /* burst read */ #define CON_BWR BIT(9) /* burst write */ #define CON_SEC_SHIFT (12) -/* Timming control register */ +/* Timing control register */ #define NFI_ACCCON (0x0C) #define NFI_INTR_EN (0x10) #define INTR_AHB_DONE_EN BIT(6) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index a5b278ab9384..a225b10f8145 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -6205,7 +6205,7 @@ static int nand_scan_tail(struct nand_chip *chip) /* * FIXME: some NAND manufacturer drivers expect the first die to be * selected when manufacturer->init() is called. They should be fixed - * to explictly select the relevant die when interacting with the NAND + * to explicitly select the relevant die when interacting with the NAND * chip. */ nand_select_target(chip, 0); diff --git a/drivers/mtd/nand/raw/nand_legacy.c b/drivers/mtd/nand/raw/nand_legacy.c index 97700f80d5b8..94d5c7723c5a 100644 --- a/drivers/mtd/nand/raw/nand_legacy.c +++ b/drivers/mtd/nand/raw/nand_legacy.c @@ -319,7 +319,7 @@ static void nand_command(struct nand_chip *chip, unsigned int command, NAND_CTRL_CLE | NAND_CTRL_CHANGE); chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - /* EZ-NAND can take upto 250ms as per ONFi v4.0 */ + /* EZ-NAND can take up to 250ms as per ONFi v4.0 */ nand_wait_status_ready(chip, 250); return; @@ -459,7 +459,7 @@ static void nand_command_lp(struct nand_chip *chip, unsigned int command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - /* EZ-NAND can take upto 250ms as per ONFi v4.0 */ + /* EZ-NAND can take up to 250ms as per ONFi v4.0 */ nand_wait_status_ready(chip, 250); return; diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 7d0b145d78d8..61c55227e15d 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -25,7 +25,7 @@ * Read a security register by using the SPINOR_OP_RSECR commands. * * In Winbond/GigaDevice datasheets the term "security register" stands for - * an one-time-programmable memory area, consisting of multiple bytes (usually + * a one-time-programmable memory area, consisting of multiple bytes (usually * 256). Thus one "security register" maps to one OTP region. * * This method is used on GigaDevice and Winbond flashes. diff --git a/include/linux/mtd/bbm.h b/include/linux/mtd/bbm.h index d890805f5494..67929fdf2dff 100644 --- a/include/linux/mtd/bbm.h +++ b/include/linux/mtd/bbm.h @@ -75,7 +75,7 @@ struct nand_bbt_descr { * with NAND_BBT_CREATE. */ #define NAND_BBT_CREATE_EMPTY 0x00000400 -/* Write bbt if neccecary */ +/* Write bbt if necessary */ #define NAND_BBT_WRITE 0x00002000 /* Read and write back block contents when writing bbt */ #define NAND_BBT_SAVECONTENT 0x00004000 diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index 5c70e7bd3ed5..43fefff02025 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -1592,7 +1592,7 @@ void nand_wait_ready(struct nand_chip *chip); /* * Free resources held by the NAND device, must be called on error after a - * sucessful nand_scan(). + * successful nand_scan(). */ void nand_cleanup(struct nand_chip *chip); diff --git a/include/linux/mtd/sh_flctl.h b/include/linux/mtd/sh_flctl.h index 78fc2d4218c8..15adf2474fa0 100644 --- a/include/linux/mtd/sh_flctl.h +++ b/include/linux/mtd/sh_flctl.h @@ -60,8 +60,8 @@ * Some hardware uses bits called PULSEx instead of FCKSEL_E and QTSEL_E * to control the clock divider used between the High-Speed Peripheral Clock * and the FLCTL internal clock. If so, use CLK_8_BIT_xxx for connecting 8 bit - * and CLK_16_BIT_xxx for connecting 16 bit bus bandwith NAND chips. For the 16 - * bit version the divider is seperate for the pulse width of high and low + * and CLK_16_BIT_xxx for connecting 16 bit bus bandwidth NAND chips. For the 16 + * bit version the divider is separate for the pulse width of high and low * signals. */ #define PULSE3 (0x1 << 27) -- cgit v1.2.3 From f4f2a54cfef7cd23a54b8427a00c2dae83ffaf2e Mon Sep 17 00:00:00 2001 From: Bill Lee Date: Thu, 27 Aug 2026 15:39:12 -0500 Subject: mtd: spinand: add support for ISSI SPI NAND flash Add a new manufacturer driver for ISSI SPI NAND devices (manufacturer ID 0x9d). The following devices are supported: - IS37/IS38SMW01G8B: 1Gb, 1.8V - IS37/IS38SMW02G8B: 2Gb, 1.8V - IS37/IS38SML04G8B: 4Gb, 3.3V, 2-die stacked - IS37/IS38SMW04G8B: 4Gb, 1.8V, 2-die stacked Signed-off-by: Bill Lee Signed-off-by: Jeff Kim Signed-off-by: Han Xu Link: https://patch.msgid.link/20260825-spinand_issi-v1-1-d81bdd7c212d@nxp.com Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/Makefile | 2 +- drivers/mtd/nand/spi/core.c | 1 + drivers/mtd/nand/spi/issi.c | 187 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 1 + 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/nand/spi/issi.c (limited to 'include/linux') diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index b5ccb44860df..f6b2a094fafc 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 spinand-objs := core.o otp.o spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o heyangtek.o -spinand-objs += macronix.o micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o +spinand-objs += issi.o macronix.o micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 8bf9301f25e7..86a8d759682b 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1360,6 +1360,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = { &foresee_spinand_manufacturer, &gigadevice_spinand_manufacturer, &heyangtek_spinand_manufacturer, + &issi_spinand_manufacturer, ¯onix_spinand_manufacturer, µn_spinand_manufacturer, ¶gon_spinand_manufacturer, diff --git a/drivers/mtd/nand/spi/issi.c b/drivers/mtd/nand/spi/issi.c new file mode 100644 index 000000000000..122b3a6e2f62 --- /dev/null +++ b/drivers/mtd/nand/spi/issi.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 ISSI + * + * Authors: + * Bill Lee + * Jeff Kim + * Co-Author: + * Han Xu + */ + +#include +#include +#include +#include + +#define SPINAND_MFR_ISSI 0x9d + +#define ISSI_STATUS_ECC_MASK GENMASK(6, 4) +#define ISSI_STATUS_ECC_NO_BITFLIPS 0 +#define ISSI_STATUS_ECC_1TO3_BITFLIPS 1 +#define ISSI_STATUS_ECC_UNCOR_ERROR 2 +#define ISSI_STATUS_ECC_4TO6_BITFLIPS 3 +#define ISSI_STATUS_ECC_7TO8_BITFLIPS 5 + +/* + * As per datasheet, die selection is done by the 7th bit of Drive + * Strength Register (Address 0xD0). + */ +#define ISSI_DIE_SELECT_REG 0xD0 +#define ISSI_SELECT_DIE_MASK BIT(7) +#define ISSI_SELECT_DIE(x) ((x) << 7) + +static SPINAND_OP_VARIANTS( + quadio_read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0)); + +static SPINAND_OP_VARIANTS(x4_write_cache_variants, + SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0), + SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(x4_update_cache_variants, + SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0), + SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); + +static int issi_8_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = mtd->oobsize / 2; + region->length = mtd->oobsize / 2; + + return 0; +} + +static int issi_8_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = (mtd->oobsize / 2) - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops issi_8_ooblayout = { + .ecc = issi_8_ooblayout_ecc, + .free = issi_8_ooblayout_free, +}; + +static int issi_select_target(struct spinand_device *spinand, + unsigned int target) +{ + int ret; + u8 regval; + + if (target > 1) + return -EINVAL; + + ret = spinand_read_reg_op(spinand, ISSI_DIE_SELECT_REG, ®val); + if (ret) + return ret; + + regval &= ~ISSI_SELECT_DIE_MASK; + regval |= ISSI_SELECT_DIE(target); + + return spinand_write_reg_op(spinand, ISSI_DIE_SELECT_REG, regval); +} + +static int issi_8_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + switch (FIELD_GET(ISSI_STATUS_ECC_MASK, status)) { + case ISSI_STATUS_ECC_NO_BITFLIPS: + return 0; + + case ISSI_STATUS_ECC_1TO3_BITFLIPS: + return 3; + + case ISSI_STATUS_ECC_4TO6_BITFLIPS: + return 6; + + case ISSI_STATUS_ECC_7TO8_BITFLIPS: + return 8; + + case ISSI_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info issi_spinand_table[] = { + /* IS37/38SMW01G8B 1Gb 1.8V */ + SPINAND_INFO("IS37/38SMW01G8B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15), + NAND_MEMORG(1, 2048, 128, 64, 1024, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), + + /* IS37/38SMW02G8B 2Gb 1.8V */ + SPINAND_INFO("IS37/38SMW02G8B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), + + /* IS37/38SML04G8B 4Gb 3.3V */ + SPINAND_INFO("IS37/38SML04G8", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x34), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), + + /* IS37/38SMW04G8B 4Gb 1.8V */ + SPINAND_INFO("IS37/38SMW04G8B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), +}; + +static int issi_spinand_init(struct spinand_device *spinand) +{ + return 0; +} + +static const struct spinand_manufacturer_ops issi_spinand_manuf_ops = { + .init = issi_spinand_init, +}; + +const struct spinand_manufacturer issi_spinand_manufacturer = { + .id = SPINAND_MFR_ISSI, + .name = "ISSI", + .chips = issi_spinand_table, + .nchips = ARRAY_SIZE(issi_spinand_table), + .ops = &issi_spinand_manuf_ops, +}; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 5f4c00ae72a7..b42407b69db3 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -438,6 +438,7 @@ extern const struct spinand_manufacturer fmsh_spinand_manufacturer; extern const struct spinand_manufacturer foresee_spinand_manufacturer; extern const struct spinand_manufacturer gigadevice_spinand_manufacturer; extern const struct spinand_manufacturer heyangtek_spinand_manufacturer; +extern const struct spinand_manufacturer issi_spinand_manufacturer; extern const struct spinand_manufacturer macronix_spinand_manufacturer; extern const struct spinand_manufacturer micron_spinand_manufacturer; extern const struct spinand_manufacturer paragon_spinand_manufacturer; -- cgit v1.2.3 From e18c514e9c1ff330bec87fd62341251d041e7443 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 3 Sep 2026 14:24:23 +0300 Subject: mtd: rawnand: lpc32xx_slc: Remove platform data support Since NXP LPC32xx SoC support in the Linux kernel strictly depends on device tree support, the unused platform data support can be removed from the NAND SLC driver. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/lpc32xx_slc.c | 21 ++------------------- include/linux/mtd/lpc32xx_slc.h | 17 ----------------- 2 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 include/linux/mtd/lpc32xx_slc.h (limited to 'include/linux') diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c index 10c8080207f4..b4d90afa9267 100644 --- a/drivers/mtd/nand/raw/lpc32xx_slc.c +++ b/drivers/mtd/nand/raw/lpc32xx_slc.c @@ -25,7 +25,6 @@ #include #include #include -#include #define LPC32XX_MODNAME "lpc32xx-nand" @@ -213,7 +212,6 @@ struct lpc32xx_nand_cfg_slc { struct lpc32xx_nand_host { struct nand_chip nand_chip; - struct lpc32xx_slc_platform_data *pdata; struct clk *clk; struct gpio_desc *wp_gpio; void __iomem *io_base; @@ -727,24 +725,11 @@ static int lpc32xx_nand_write_page_raw_syndrome(struct nand_chip *chip, static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host) { struct mtd_info *mtd = nand_to_mtd(&host->nand_chip); - dma_cap_mask_t mask; host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx"); if (IS_ERR(host->dma_chan)) { - /* fallback to request using platform data */ - if (!host->pdata || !host->pdata->dma_filter) { - dev_err(mtd->dev.parent, "no DMA platform data\n"); - return -ENOENT; - } - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, "nand-slc"); - - if (!host->dma_chan) { - dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); - return -EBUSY; - } + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return PTR_ERR(host->dma_chan); } return 0; @@ -873,8 +858,6 @@ static int lpc32xx_nand_probe(struct platform_device *pdev) gpiod_set_consumer_name(host->wp_gpio, "NAND WP"); - host->pdata = dev_get_platdata(&pdev->dev); - chip = &host->nand_chip; mtd = nand_to_mtd(chip); nand_set_controller_data(chip, host); diff --git a/include/linux/mtd/lpc32xx_slc.h b/include/linux/mtd/lpc32xx_slc.h deleted file mode 100644 index a044b806566b..000000000000 --- a/include/linux/mtd/lpc32xx_slc.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Platform data for LPC32xx SoC SLC NAND controller - * - * Copyright © 2012 Roland Stigge - */ - -#ifndef __LINUX_MTD_LPC32XX_SLC_H -#define __LINUX_MTD_LPC32XX_SLC_H - -#include - -struct lpc32xx_slc_platform_data { - dma_filter_fn dma_filter; -}; - -#endif /* __LINUX_MTD_LPC32XX_SLC_H */ -- cgit v1.2.3 From def559a215699aa2f2d8ef98f30156b9d562a154 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 3 Sep 2026 14:24:24 +0300 Subject: mtd: rawnand: lpc32xx_mlc: Remove platform data support Since NXP LPC32xx SoC support in the Linux kernel strictly depends on device tree support, the unused platform data support can be removed from the NAND MLC driver. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/lpc32xx_mlc.c | 21 ++------------------- include/linux/mtd/lpc32xx_mlc.h | 17 ----------------- 2 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 include/linux/mtd/lpc32xx_mlc.h (limited to 'include/linux') diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c index 8f6a89d9ba83..8436015edcb4 100644 --- a/drivers/mtd/nand/raw/lpc32xx_mlc.c +++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -174,7 +173,6 @@ static struct nand_bbt_descr lpc32xx_nand_bbt_mirror = { struct lpc32xx_nand_host { struct platform_device *pdev; struct nand_chip nand_chip; - struct lpc32xx_mlc_platform_data *pdata; struct clk *clk; struct gpio_desc *wp_gpio; void __iomem *io_base; @@ -580,24 +578,11 @@ static void lpc32xx_ecc_enable(struct nand_chip *chip, int mode) static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host) { struct mtd_info *mtd = nand_to_mtd(&host->nand_chip); - dma_cap_mask_t mask; host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx"); if (IS_ERR(host->dma_chan)) { - /* fallback to request using platform data */ - if (!host->pdata || !host->pdata->dma_filter) { - dev_err(mtd->dev.parent, "no DMA platform data\n"); - return -ENOENT; - } - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, "nand-mlc"); - - if (!host->dma_chan) { - dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); - return -EBUSY; - } + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return PTR_ERR(host->dma_chan); } /* @@ -736,8 +721,6 @@ static int lpc32xx_nand_probe(struct platform_device *pdev) gpiod_set_consumer_name(host->wp_gpio, "NAND WP"); - host->pdata = dev_get_platdata(&pdev->dev); - /* link the private data structures */ nand_set_controller_data(nand_chip, host); nand_set_flash_node(nand_chip, pdev->dev.of_node); diff --git a/include/linux/mtd/lpc32xx_mlc.h b/include/linux/mtd/lpc32xx_mlc.h deleted file mode 100644 index 35e971be0950..000000000000 --- a/include/linux/mtd/lpc32xx_mlc.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Platform data for LPC32xx SoC MLC NAND controller - * - * Copyright © 2012 Roland Stigge - */ - -#ifndef __LINUX_MTD_LPC32XX_MLC_H -#define __LINUX_MTD_LPC32XX_MLC_H - -#include - -struct lpc32xx_mlc_platform_data { - dma_filter_fn dma_filter; -}; - -#endif /* __LINUX_MTD_LPC32XX_MLC_H */ -- cgit v1.2.3 From 63489002d397ebf136548c79af68a8e05b30d45c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:40 +0200 Subject: mtd: spi-nor: Refactor Read Status/Write Status support SPI NOR has a lot of history. Additions over additions, the subtleties of the JESD216 specification, their implementations by the manufacturers and the hardware mistakes have generated a gigantic maze, let's try to understand what is really needed. The specification explains the QE (Quad Enable) bitfield as describing where the bit to enable the Quad capability is, but also how to set it. Unfortunately, the specification is not precise about what opcodes are supported exactly in all the cases. There is an introduction that basically states: - Opcode 0x05 reads SR1 - Opcode 0x35 reads SR2 - Opcode 0x01 writes SR1, and then SR2 if another byte is written Then the bitfield, among indicating the location of the QE bit, may indicate: - Reading SR1 and SR2 in one operation is not possible (loops over the content of SR1) - Reading SR2 directly is possible - Only writing SR1 zeroes SR2. One problem with the current implementation, is that it only focuses on the QE bit. A quad_enable function was created for each case, even though in practice, the logic was always the same: read, modify, write, read back and verify. One problem comes when other features need to play with the status registers, like software block protection or OTP: you never know how to properly handle the QE bit, nor where it is, nor how to read/write the Status registers. This lead to approximations/guessing (in swp.c, otp.c and obviously in legacy controller drivers like atmel.c) but also to the implementation of a gazillon of helpers for reading/writing/checking the status registers. In addition, I believe some design decisions had a negative impact over the years. - All possible situations had to be flagged by the core. This is likely wrong, because we no longer know why we need specific quirks. It was ineherent to the state of the SPI NOR core before the great cleanup that had happened the past few years. I believe this creates confusion in the core today, and we should push this to vendor fixups instead. As an example, in 2023 Hsin-Yi was facing an issue because his chip was setting the wrong QER value, leading to RDCR being prevented, thus falling into a condition blindly setting a random QE bit (I strongy believe it is done like that for wrong reasons). His chip actually had RDCR support! The correct fix should have been to mark the capability in a device fixup instead of handling this in the core. Link: https://lore.kernel.org/lkml/CAJMQK-hR0eaO0b4Vd0U8_KAndLyZapqdHjVLAoe42rWi9rdLkA@mail.gmail.com/ - SFDP parsing is over cautious. I believe BFPT_DWORD15_QER_SR2_BIT1_NO_RD is abusive (nothing states that RDCR is not supported), and BFPT_DWORD15_QER_SR2_BIT1 is also out of specification when forcing 16-bit Status writes. - SNOR_F_HAS_16BIT_SR is only imposing 16bit Status writes, whereas reads can still be 8-bit wide. - The usage of helpers verifying the writes was also spread for IMHO no really good reason. Why shouldn't we trust spi operations when it comes to Status Registers? We do not read back our page reads, so why status registers should be treated with so much care, if it's not because we are unsure of what is being done? My proposal includes a check when it comes to the QE bit (done once) but we don't need these checks otherwise. If the QE bit was written properly, there are high chances that the other register accesses will just be fine, no? Asde from my main quest, I also observed no good reason to ask the read/write status register callers to use nor->bouncebuf while the low-level helpers could do it themselves (we are talking about one or two bytes being copied). So after these observations, my proposal is the following: - Create private low level helpers that just read or write a status register. They are flexible, we can give the opcode (which varies based on the SFDP QER field) and the length (1 or 2). - Create public generic accessors which will be used to read/write sr1 and/or sr2. This is where all the cleverness shall be. The helpers use the available opcodes for a given chip in order to fullfill the request. - Provide a single generic ->quad_enable() hook which generically does all the steps mentioned above (read, modify, write, read back and verify). - Create a list of opcodes for all 6 possible situations: {read, write} {sr1, sr2, sr1 and sr2}. These opcodes are filled/cleared based on the QER field. An opcode set to 0 indicates the absence of support (there is no 0x00 opcode in SPI NOR). I tried my best to analyze the current behavior and to mimic it as much as possible, but this is a risky cleanup. However, if we go for this, it will be *much* easier in the future to handle all kind of chip variations. We won't be limited to a couple of flags anymore, but rather we'll be able to just disable a read or write capability using a single line. Known deviations: - The Atmel manufacturer driver drives non SFDP chips which by default were receiving the HAS_16B_WR flag, forcing SR2 writes all the times, but it was deliberately making single bytes writes to SR1 for its locking operations. The flag is likely wrong for them, so I removed it. - I am proposing on purpose a single quad_enable helper. It should match all the cases, with 1 identified difference which I believe is harmless: in the SR2_BIT7 case, the helper would make an extra SR1 read/write which was not done before. This entry explicitly supports reading and writing the two registers. Note: no manufacturer has been identified to actually use that entry yet. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- Documentation/driver-api/mtd/spi-nor.rst | 2 +- drivers/mtd/spi-nor/atmel.c | 50 ++- drivers/mtd/spi-nor/core.c | 612 ++++++++++--------------------- drivers/mtd/spi-nor/core.h | 59 ++- drivers/mtd/spi-nor/debugfs.c | 2 - drivers/mtd/spi-nor/gigadevice.c | 6 +- drivers/mtd/spi-nor/issi.c | 3 +- drivers/mtd/spi-nor/macronix.c | 12 +- drivers/mtd/spi-nor/micron-st.c | 4 +- drivers/mtd/spi-nor/otp.c | 16 +- drivers/mtd/spi-nor/sfdp.c | 41 ++- drivers/mtd/spi-nor/spansion.c | 16 +- drivers/mtd/spi-nor/sst.c | 5 +- drivers/mtd/spi-nor/swp.c | 99 +++-- drivers/mtd/spi-nor/winbond.c | 4 +- include/linux/mtd/spi-nor.h | 4 - 16 files changed, 365 insertions(+), 570 deletions(-) (limited to 'include/linux') diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst index 747a326fb6c0..5441fa7d4f04 100644 --- a/Documentation/driver-api/mtd/spi-nor.rst +++ b/Documentation/driver-api/mtd/spi-nor.rst @@ -118,7 +118,7 @@ section, after the ``---`` marker. write size 1 page size 256 address nbytes 3 - flags HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | SWP_IS_VOLATILE + flags HAS_LOCK | SOFT_RESET | SWP_IS_VOLATILE opcodes read 0xeb diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c index 82c592f0a1e1..8741c0bda497 100644 --- a/drivers/mtd/spi-nor/atmel.c +++ b/drivers/mtd/spi-nor/atmel.c @@ -23,18 +23,28 @@ static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len) static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len) { + /* Write 0x00 to the status register to disable write protection */ + u8 sr = 0; int ret; /* We only support unlocking the whole flash array */ if (ofs || len != nor->params->size) return -EINVAL; - /* Write 0x00 to the status register to disable write protection */ - ret = spi_nor_write_sr_and_check(nor, 0); + ret = spi_nor_write_sr1(nor, &sr); + if (ret) + return ret; + + ret = spi_nor_read_sr1(nor, &sr); if (ret) + return ret; + + if (sr) { dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n"); + return -EIO; + } - return ret; + return 0; } static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) @@ -50,6 +60,7 @@ static const struct spi_nor_locking_ops at25fs_nor_locking_ops = { static int at25fs_nor_late_init(struct spi_nor *nor) { + nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR; nor->params->locking_ops = &at25fs_nor_locking_ops; return 0; @@ -69,6 +80,7 @@ static const struct spi_nor_fixups at25fs_nor_fixups = { * Return: 0 on success, -error otherwise. */ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs, + u64 len, bool is_protect) { int ret; @@ -78,19 +90,24 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs, if (ofs || len != nor->params->size) return -EINVAL; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - sr = nor->bouncebuf[0]; - /* SRWD bit needs to be cleared, otherwise the protection doesn't change */ if (sr & SR_SRWD) { sr &= ~SR_SRWD; - ret = spi_nor_write_sr_and_check(nor, sr); - if (ret) { - dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n"); + ret = spi_nor_write_sr1(nor, &sr); + if (ret) + return ret; + + ret = spi_nor_read_sr1(nor, &sr); + if (ret) return ret; + + if (sr & SR_SRWD) { + dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n"); + return -EIO; } } @@ -108,14 +125,7 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs, sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK; } - nor->bouncebuf[0] = sr; - - /* - * We cannot use the spi_nor_write_sr_and_check() because this command - * isn't really setting any bits, instead it is an pseudo command for - * "Global Unprotect" or "Global Protect" - */ - return spi_nor_write_sr(nor, nor->bouncebuf, 1); + return spi_nor_write_sr1(nor, &sr); } static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len) @@ -131,16 +141,17 @@ static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len) static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs, u64 len) { + u8 sr; int ret; if (ofs >= nor->params->size || (ofs + len) > nor->params->size) return -EINVAL; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK); + return ((sr & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK); } static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = { @@ -151,6 +162,7 @@ static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = { static int atmel_nor_global_protection_late_init(struct spi_nor *nor) { + nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR; nor->params->locking_ops = &atmel_nor_global_protection_ops; return 0; diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 886f3c956309..1f2a58acae1e 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -444,75 +444,6 @@ int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id, return ret; } -/** - * spi_nor_read_sr() - Read the Status Register. - * @nor: pointer to 'struct spi_nor'. - * @sr: pointer to a DMA-able buffer where the value of the - * Status Register will be written. Should be at least 2 bytes. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_RDSR_OP(sr); - - if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { - op.addr.nbytes = nor->params->rdsr_addr_nbytes; - op.dummy.nbytes = nor->params->rdsr_dummy; - /* - * We don't want to read only one byte in DTR mode. So, - * read 2 and then discard the second byte. - */ - op.data.nbytes = 2; - } - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR, sr, - 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d reading SR\n", ret); - - return ret; -} - -/** - * spi_nor_read_cr() - Read the Configuration Register using the - * SPINOR_OP_RDCR (35h) command. - * @nor: pointer to 'struct spi_nor' - * @cr: pointer to a DMA-able buffer where the value of the - * Configuration Register will be written. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_read_cr(struct spi_nor *nor, u8 *cr) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_RDCR_OP(cr); - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDCR, cr, - 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d reading CR\n", ret); - - return ret; -} - /** * spi_nor_set_4byte_addr_mode_en4b_ex4b() - Enter/Exit 4-byte address mode * using SPINOR_OP_EN4B/SPINOR_OP_EX4B. Typically used by @@ -618,12 +549,13 @@ int spi_nor_set_4byte_addr_mode_brwr(struct spi_nor *nor, bool enable) int spi_nor_sr_ready(struct spi_nor *nor) { int ret; + u8 sr; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - return !(nor->bouncebuf[0] & SR_WIP); + return !(sr & SR_WIP); } /** @@ -785,344 +717,285 @@ int spi_nor_global_block_unlock(struct spi_nor *nor) } /** - * spi_nor_write_sr() - Write the Status Register. + * spi_nor_read_sr_ll() - Low-level Status Registers read. * @nor: pointer to 'struct spi_nor'. - * @sr: pointer to DMA-able buffer to write to the Status Register. - * @len: number of bytes to write to the Status Register. + * @opcode: opcode for the status register read operation. + * @sr: pointer to buffer for storing the content of the status registers. + * @len: number of status registers to read (1 or 2). * * Return: 0 on success, -errno otherwise. */ -int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) +int spi_nor_read_sr_ll(struct spi_nor *nor, u8 opcode, u8 *sr, + unsigned int len) { - int ret; + int ret, i; - ret = spi_nor_write_enable(nor); - if (ret) - return ret; + if (len > 2) + return -EINVAL; + + for (i = 0; i < len; i++) + nor->bouncebuf[i] = 0; if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_WRSR_OP(sr, len); + struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, len); + + if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { + op.addr.nbytes = nor->params->rdsr_addr_nbytes; + op.dummy.nbytes = nor->params->rdsr_dummy; + /* + * We don't want to read only one byte in DTR mode. So, + * read 2 and then discard the second byte. + */ + op.data.nbytes = 2; + } spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR, sr, - len); + ret = spi_nor_controller_ops_read_reg(nor, opcode, nor->bouncebuf, len); } - if (ret) { - dev_dbg(nor->dev, "error %d writing SR\n", ret); - return ret; - } + memcpy(sr, nor->bouncebuf, len); - return spi_nor_wait_till_ready(nor); -} - -/** - * spi_nor_write_sr1_and_check() - Write one byte to the Status Register 1 and - * ensure that the byte written match the received value. - * @nor: pointer to a 'struct spi_nor'. - * @sr1: byte value to be written to the Status Register. - * - * Return: 0 on success, -errno otherwise. - */ -static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1) -{ - int ret; - - nor->bouncebuf[0] = sr1; - - ret = spi_nor_write_sr(nor, nor->bouncebuf, 1); if (ret) - return ret; - - ret = spi_nor_read_sr(nor, nor->bouncebuf); - if (ret) - return ret; + dev_dbg(nor->dev, "Error %d reading Status Registers\n", ret); - if (nor->bouncebuf[0] != sr1) { - dev_dbg(nor->dev, "SR1: read back test failed\n"); - return -EIO; - } - - return 0; + return ret; } /** - * spi_nor_write_16bit_sr_and_check() - Write the Status Register 1 and the - * Status Register 2 in one shot. Ensure that the byte written in the Status - * Register 1 match the received value, and that the 16-bit Write did not - * affect what was already in the Status Register 2. - * @nor: pointer to a 'struct spi_nor'. - * @sr1: byte value to be written to the Status Register 1. + * spi_nor_write_sr_ll() - Low-level Status Registers write. + * @nor: pointer to 'struct spi_nor'. + * @opcode: opcode for the status register write operation. + * @sr: pointer to status registers buffer to write. + * @len: number of status registers to write. * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) +static int spi_nor_write_sr_ll(struct spi_nor *nor, u8 opcode, const u8 *sr, + unsigned int len) { - struct spi_nor_flash_parameter *params = nor->params; - int ret; - u8 *sr_cr = nor->bouncebuf; - u8 cr_written; - - /* Make sure we don't overwrite the contents of Status Register 2. */ - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) - return ret; - } else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 || - spi_nor_get_protocol_width(nor->write_proto) == 4) && - nor->params->quad_enable) { - /* - * If the Status Register 2 Read command (35h) is not - * supported, we should at least be sure we don't - * change the value of the SR2 Quad Enable bit. - * - * When the Quad Enable method is set and the buswidth is 4, we - * can safely assume that the value of the QE bit is one, as a - * consequence of the nor->params->quad_enable() call. - * - * According to the JESD216 revB standard, BFPT DWORDS[15], - * bits 22:20, the 16-bit Write Status (01h) command is - * available just for the cases in which the QE bit is - * described in SR2 at BIT(1). - */ - sr_cr[1] = SR2_QUAD_EN_BIT1; - } else { - sr_cr[1] = 0; - } + int ret, i; - sr_cr[0] = sr1; + if (len > 2) + return -EINVAL; - ret = spi_nor_write_sr(nor, sr_cr, 2); + ret = spi_nor_write_enable(nor); if (ret) return ret; - ret = spi_nor_read_sr(nor, sr_cr); - if (ret) - return ret; + for (i = 0; i < len; i++) + nor->bouncebuf[i] = sr[i]; - if (sr1 != sr_cr[0]) { - dev_dbg(nor->dev, "SR: Read back test failed\n"); - return -EIO; - } + if (nor->spimem) { + struct spi_mem_op op = SPI_NOR_WRSR_OP(opcode, + nor->bouncebuf, len); - if (params->flags & SNOR_F_NO_READ_CR) - return 0; + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - cr_written = sr_cr[1]; + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_write_reg(nor, opcode, + nor->bouncebuf, len); + } - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) + if (ret) { + dev_dbg(nor->dev, "Error %d writing Status Registers\n", ret); return ret; - - if (cr_written != sr_cr[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; } - return 0; + return spi_nor_wait_till_ready(nor); } /** - * spi_nor_write_16bit_cr_and_check() - Write the Status Register 1 and the - * Configuration Register in one shot. Ensure that the byte written in the - * Configuration Register match the received value, and that the 16-bit Write - * did not affect what was already in the Status Register 1. - * @nor: pointer to a 'struct spi_nor'. - * @cr: byte value to be written to the Configuration Register. + * spi_nor_read_sr1() - Read SR1 only + * Useful for: + * - The core to offer a generic "read SR1 and SR2" capability + * - Manufacturer drivers (since they know the chip SR layout) + * - Polling the BUSY bit in the core * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr1: pointer to a valid SR1 buffer + * + * Return 0 or errno. */ -int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) +int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1) { - int ret; - u8 *sr_cr = nor->bouncebuf; - u8 sr_written; - - /* Keep the current value of the Status Register 1. */ - ret = spi_nor_read_sr(nor, sr_cr); - if (ret) - return ret; - - sr_cr[1] = cr; - - ret = spi_nor_write_sr(nor, sr_cr, 2); - if (ret) - return ret; - - sr_written = sr_cr[0]; - - ret = spi_nor_read_sr(nor, sr_cr); - if (ret) - return ret; - - if (sr_written != sr_cr[0]) { - dev_dbg(nor->dev, "SR: Read back test failed\n"); - return -EIO; - } - - if (nor->params->flags & SNOR_F_NO_READ_CR) - return 0; + return spi_nor_read_sr_ll(nor, nor->params->opcodes.read_sr1, sr1, 1); +} - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) - return ret; +/** + * spi_nor_read_sr2() - Read SR2 only + * Useful for: + * - The core to offer a generic "read SR1 and SR2" capability + * - Manufacturer drivers (since they know the chip SR layout) + * - SR2 based OTP configuration + * + * @nor: the spi_nor structure + * @sr2: pointer to a valid SR2 buffer + * + * Return 0 or errno. + */ +int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) +{ + struct spi_nor_flash_parameter *params = nor->params; - if (cr != sr_cr[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; - } + if (!params->opcodes.read_sr2) + return -EINVAL; - return 0; + return spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1); } /** - * spi_nor_write_16bit_sr_cr_and_check() - Write the Status Register 1 and the - * Configuration Register in one shot. Ensure that the bytes written in both - * registers match the received value. - * @nor: pointer to a 'struct spi_nor'. - * @regs: two-byte array with values to be written to the status and - * configuration registers. + * spi_nor_read_sr1_and_sr2() - Read SR1 then SR2 + * General purpose helper. * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. */ -static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr) { - u8 written_regs[2]; int ret; - written_regs[0] = regs[0]; - written_regs[1] = regs[1]; - nor->bouncebuf[0] = regs[0]; - nor->bouncebuf[1] = regs[1]; - - ret = spi_nor_write_sr(nor, nor->bouncebuf, 2); + ret = spi_nor_read_sr1(nor, &sr[0]); if (ret) return ret; - ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]); - if (ret) - return ret; - - if (written_regs[0] != nor->bouncebuf[0]) { - dev_dbg(nor->dev, "SR: Read back test failed\n"); - return -EIO; - } - - if (nor->params->flags & SNOR_F_NO_READ_CR) - return 0; - - ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]); - if (ret) - return ret; - - if (written_regs[1] != nor->bouncebuf[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; - } - - return 0; + return spi_nor_read_sr2(nor, &sr[1]); } /** - * spi_nor_write_sr_and_check() - Write the Status Register 1 and ensure that - * the byte written match the received value without affecting other bits in the - * Status Register 1 and 2. - * @nor: pointer to a 'struct spi_nor'. - * @sr1: byte value to be written to the Status Register. + * spi_nor_write_sr1() - Write SR1 only + * Useful for: + * - Manufacturer drivers (since they know the chip SR layout) * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr1: pointer to a valid SR1 buffer + * + * Return 0 or errno. */ -int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) +int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1) { - if (nor->params->flags & SNOR_F_HAS_16BIT_SR) - return spi_nor_write_16bit_sr_and_check(nor, sr1); + if (WARN_ONCE(!nor->params->opcodes.write_sr1, + "Restricted helper use, write SR1 not supported")) + return -EIO; - return spi_nor_write_sr1_and_check(nor, sr1); + return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr1, sr1, 1); } /** - * spi_nor_write_sr_cr_and_check() - Write the Status Register 1 and ensure that - * the byte written match the received value. Same for the Control Register if - * available. - * @nor: pointer to a 'struct spi_nor'. - * @regs: byte array to be written to the registers. + * spi_nor_write_sr2() - Write SR2 only + * Useful for: + * - Manufacturer drivers (since they know the chip SR layout) + * - SR2 based OTP configuration * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr2: pointer to a valid SR2 buffer + * + * Return 0 or errno. */ -int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2) { - if (nor->params->flags & SNOR_F_HAS_16BIT_SR) - return spi_nor_write_16bit_sr_cr_and_check(nor, regs); + if (WARN_ONCE(!nor->params->opcodes.write_sr2, + "Restricted helper use, write SR2 not supported")) + return -EIO; - return spi_nor_write_sr1_and_check(nor, regs[0]); + return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr2, sr2, 1); } /** - * spi_nor_write_sr2() - Write the Status Register 2 using the - * SPINOR_OP_WRSR2 (3eh) command. - * @nor: pointer to 'struct spi_nor'. - * @sr2: pointer to DMA-able buffer to write to the Status Register 2. + * spi_nor_write_sr1_and_sr2() - Write SR1 then SR2 + * General purpose helper, always safe to call. Will expectedly ignore + * SR2 on certain chips. * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. */ -static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2) +static int spi_nor_write_sr1_and_sr2(struct spi_nor *nor, const u8 *sr) { + struct spi_nor_flash_parameter *params = nor->params; int ret; - ret = spi_nor_write_enable(nor); + if (params->opcodes.write_sr1_and_sr2) + return spi_nor_write_sr_ll(nor, + params->opcodes.write_sr1_and_sr2, + sr, 2); + + ret = spi_nor_write_sr1(nor, &sr[0]); if (ret) return ret; - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2); + if (params->opcodes.write_sr2) + return spi_nor_write_sr2(nor, &sr[1]); - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + return 0; +} - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR2, - sr2, 1); - } +/** + * spi_nor_write_sr1_and_sr2_and_check() - Write SR1 then SR2, then read + * them back and verifies + * General purpose helper, always safe to call. Will expectedly ignore + * SR2 on certain chips. + * + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. + */ +int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr) +{ + u8 tmp[2]; + int ret; - if (ret) { - dev_dbg(nor->dev, "error %d writing SR2\n", ret); + ret = spi_nor_write_sr1_and_sr2(nor, sr); + if (ret) return ret; - } - return spi_nor_wait_till_ready(nor); + ret = spi_nor_read_sr1_and_sr2(nor, tmp); + if (ret) + return ret; + + if (sr[0] != tmp[0] || sr[1] != tmp[1]) + return -EIO; + + return 0; } /** - * spi_nor_read_sr2() - Read the Status Register 2 using the - * SPINOR_OP_RDSR2 (3fh) command. - * @nor: pointer to 'struct spi_nor'. - * @sr2: pointer to DMA-able buffer where the value of the - * Status Register 2 will be written. + * spi_nor_generic_quad_enable() - Read the status registers, + * apply the QE bit mask, + * write the status registers, + * read them back and check the content. + * + * @nor: pointer to 'struct spi_nor' * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) +static int spi_nor_generic_quad_enable(struct spi_nor *nor) { + u8 *qe_mask = nor->params->qe_mask; + u8 sr[2] = {}; int ret; - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2); + if (!qe_mask[0] && !qe_mask[1]) + return 0; - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_nor_read_sr1_and_sr2(nor, sr); + if (ret) + return ret; - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR2, sr2, - 1); - } + if (sr[0] & qe_mask[0] || sr[1] & qe_mask[1]) + return 0; - if (ret) - dev_dbg(nor->dev, "error %d reading SR2\n", ret); + sr[0] |= qe_mask[0]; + sr[1] |= qe_mask[1]; - return ret; + return spi_nor_write_sr1_and_sr2_and_check(nor, sr); } /** @@ -1914,106 +1787,6 @@ erase_err: return ret; } -/** - * spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status - * Register 1. - * @nor: pointer to a 'struct spi_nor' - * - * Bit 6 of the Status Register 1 is the QE bit for Macronix like QSPI memories. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor) -{ - int ret; - - ret = spi_nor_read_sr(nor, nor->bouncebuf); - if (ret) - return ret; - - if (nor->bouncebuf[0] & SR1_QUAD_EN_BIT6) - return 0; - - nor->bouncebuf[0] |= SR1_QUAD_EN_BIT6; - - return spi_nor_write_sr1_and_check(nor, nor->bouncebuf[0]); -} - -/** - * spi_nor_sr2_bit1_quad_enable() - set the Quad Enable BIT(1) in the Status - * Register 2. - * @nor: pointer to a 'struct spi_nor'. - * - * Bit 1 of the Status Register 2 is the QE bit for Spansion like QSPI memories. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor) -{ - int ret; - - if (nor->params->flags & SNOR_F_NO_READ_CR) - return spi_nor_write_16bit_cr_and_check(nor, SR2_QUAD_EN_BIT1); - - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - if (nor->bouncebuf[0] & SR2_QUAD_EN_BIT1) - return 0; - - nor->bouncebuf[0] |= SR2_QUAD_EN_BIT1; - - return spi_nor_write_16bit_cr_and_check(nor, nor->bouncebuf[0]); -} - -/** - * spi_nor_sr2_bit7_quad_enable() - set QE bit in Status Register 2. - * @nor: pointer to a 'struct spi_nor' - * - * Set the Quad Enable (QE) bit in the Status Register 2. - * - * This is one of the procedures to set the QE bit described in the SFDP - * (JESD216 rev B) specification but no manufacturer using this procedure has - * been identified yet, hence the name of the function. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor) -{ - u8 *sr2 = nor->bouncebuf; - int ret; - u8 sr2_written; - - /* Check current Quad Enable bit value. */ - ret = spi_nor_read_sr2(nor, sr2); - if (ret) - return ret; - if (*sr2 & SR2_QUAD_EN_BIT7) - return 0; - - /* Update the Quad Enable bit. */ - *sr2 |= SR2_QUAD_EN_BIT7; - - ret = spi_nor_write_sr2(nor, sr2); - if (ret) - return ret; - - sr2_written = *sr2; - - /* Read back and check it. */ - ret = spi_nor_read_sr2(nor, sr2); - if (ret) - return ret; - - if (*sr2 != sr2_written) { - dev_dbg(nor->dev, "SR2: Read back test failed\n"); - return -EIO; - } - - return 0; -} - static const struct spi_nor_manufacturer *manufacturers[] = { &spi_nor_atmel, &spi_nor_eon, @@ -2504,6 +2277,7 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) { struct spi_nor_flash_parameter *params = nor->params; unsigned int cap; + u8 opcode; /* X-X-X modes are not supported yet, mask them all. */ *hwcaps &= ~SNOR_HWCAPS_X_X_X; @@ -2535,14 +2309,15 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) *hwcaps &= ~BIT(cap); } - /* Some SPI controllers might not support CR read opcode. */ - if (!(params->flags & SNOR_F_NO_READ_CR)) { - struct spi_mem_op op = SPI_NOR_RDCR_OP(nor->bouncebuf); + /* Some SPI controllers might not support reading SR2 */ + opcode = nor->params->opcodes.read_sr2; + if (opcode) { + struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, 1); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); if (!spi_mem_supports_op(nor->spimem, &op)) - params->flags |= SNOR_F_NO_READ_CR; + nor->params->opcodes.read_sr2 = 0; } } @@ -3057,11 +2832,14 @@ static void spi_nor_init_default_params(struct spi_nor *nor) const struct flash_info *info = nor->info; struct device_node *np = spi_nor_get_flash_node(nor); - params->quad_enable = spi_nor_sr2_bit1_quad_enable; - params->otp.org = info->otp; + /* Default to 16-bit Read/Write Status commands */ + params->opcodes.read_sr1 = SPINOR_OP_RDSR; + params->opcodes.read_sr2 = SPINOR_OP_RDCR; + params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR; + params->quad_enable = spi_nor_generic_quad_enable; + params->qe_mask[1] = BIT(1); - /* Default to 16-bit Write Status (01h) Command */ - params->flags |= SNOR_F_HAS_16BIT_SR; + params->otp.org = info->otp; /* Set SPI NOR sizes. */ params->writesize = 1; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 4b36d99ce8f6..b8a0db02e0f1 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -37,36 +37,18 @@ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_NO_DATA) -#define SPI_NOR_RDSR_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0), \ +#define SPI_NOR_RDSR_OP(opcode, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \ SPI_MEM_OP_NO_ADDR, \ SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_IN(1, buf, 0)) + SPI_MEM_OP_DATA_IN(len, buf, 0)) -#define SPI_NOR_WRSR_OP(buf, len) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0), \ +#define SPI_NOR_WRSR_OP(opcode, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \ SPI_MEM_OP_NO_ADDR, \ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_DATA_OUT(len, buf, 0)) -#define SPI_NOR_RDSR2_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0), \ - SPI_MEM_OP_NO_ADDR, \ - SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_OUT(1, buf, 0)) - -#define SPI_NOR_WRSR2_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0), \ - SPI_MEM_OP_NO_ADDR, \ - SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_OUT(1, buf, 0)) - -#define SPI_NOR_RDCR_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0), \ - SPI_MEM_OP_NO_ADDR, \ - SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_IN(1, buf, 0)) - #define SPI_NOR_EN4B_EX4B_OP(enable) \ SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, 0), \ SPI_MEM_OP_NO_ADDR, \ @@ -131,8 +113,6 @@ enum spi_nor_option_flags { SNOR_F_4B_OPCODES = BIT(3), SNOR_F_HAS_4BAIT = BIT(4), SNOR_F_HAS_LOCK = BIT(5), - SNOR_F_HAS_16BIT_SR = BIT(6), - SNOR_F_NO_READ_CR = BIT(7), SNOR_F_HAS_SR_TB_BIT6 = BIT(8), SNOR_F_HAS_4BIT_BP = BIT(9), SNOR_F_HAS_SR_BP3_BIT6 = BIT(10), @@ -347,9 +327,19 @@ struct spi_nor_otp { * List of variable opcodes used by the chip. * * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE + * @read_sr1: opcode for reading SR1 alone + * @read_sr2: opcode for reading SR2 alone + * @write_sr1: opcode for writing SR1 alone + * @write_sr2: opcode for writing SR2 alone + * @write_sr1_and_sr2: opcode for writing SR1 then SR2 in one operation */ struct spi_nor_opcodes { u8 die_erase; + u8 read_sr1; + u8 read_sr2; + u8 write_sr1; + u8 write_sr2; + u8 write_sr1_and_sr2; }; /** @@ -388,6 +378,7 @@ struct spi_nor_opcodes { * @otp: SPI NOR OTP info. * @set_octal_dtr: enables or disables SPI NOR octal DTR mode. * @quad_enable: enables SPI NOR quad mode. + * @qe_mask: two bytes mask used to set/clear the QE bit * @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode. * @ready: (optional) flashes might use a different mechanism * than reading the status register to indicate they @@ -420,6 +411,7 @@ struct spi_nor_flash_parameter { int (*set_octal_dtr)(struct spi_nor *nor, bool enable); int (*quad_enable)(struct spi_nor *nor); + u8 qe_mask[2]; int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable); int (*ready)(struct spi_nor *nor); @@ -645,18 +637,17 @@ int spi_nor_wait_till_ready(struct spi_nor *nor); int spi_nor_global_block_unlock(struct spi_nor *nor); int spi_nor_prep_and_lock(struct spi_nor *nor); void spi_nor_unlock_and_unprep(struct spi_nor *nor); -int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor); -int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor); -int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor); int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id, enum spi_nor_protocol reg_proto); -int spi_nor_read_sr(struct spi_nor *nor, u8 *sr); int spi_nor_sr_ready(struct spi_nor *nor); -int spi_nor_read_cr(struct spi_nor *nor, u8 *cr); -int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len); -int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1); -int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr); -int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs); +int spi_nor_read_sr_ll(struct spi_nor *nor, u8 opcode, u8 *sr, + unsigned int len); +int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1); +int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2); +int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr); +int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1); +int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2); +int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr); ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf); diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 5a7372e7f802..51c0582f793a 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -18,8 +18,6 @@ static const char *const snor_f_names[] = { SNOR_F_NAME(4B_OPCODES), SNOR_F_NAME(HAS_4BAIT), SNOR_F_NAME(HAS_LOCK), - SNOR_F_NAME(HAS_16BIT_SR), - SNOR_F_NAME(NO_READ_CR), SNOR_F_NAME(HAS_SR_TB_BIT6), SNOR_F_NAME(HAS_4BIT_BP), SNOR_F_NAME(HAS_SR_BP3_BIT6), diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c index ef1edd0add70..4070a692e968 100644 --- a/drivers/mtd/spi-nor/gigadevice.c +++ b/drivers/mtd/spi-nor/gigadevice.c @@ -23,8 +23,10 @@ gd25q256_post_bfpt(struct spi_nor *nor, * GD25Q256E | SFDP_JESD216_MAJOR | SFDP_JESD216B_MINOR */ if (bfpt_header->major == SFDP_JESD216_MAJOR && - bfpt_header->minor == SFDP_JESD216_MINOR) - nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable; + bfpt_header->minor == SFDP_JESD216_MINOR) { + nor->params->qe_mask[0] = BIT(6); + nor->params->qe_mask[1] = 0; + } return 0; } diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c index 18d9a00aa22e..04db42b5141e 100644 --- a/drivers/mtd/spi-nor/issi.c +++ b/drivers/mtd/spi-nor/issi.c @@ -131,7 +131,8 @@ static const struct flash_info issi_nor_parts[] = { static void issi_nor_default_init(struct spi_nor *nor) { - nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable; + nor->params->qe_mask[0] = BIT(6); + nor->params->qe_mask[1] = 0; } static const struct spi_nor_fixups issi_fixups = { diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index 9610d9e14e4c..954e7df21ff9 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -65,11 +65,12 @@ mx25l3255e_late_init_fixups(struct spi_nor *nor) /* * SFDP of MX25L3255E is JESD216, which does not include the Quad - * Enable bit Requirement in BFPT. As a result, during BFPT parsing, - * the quad_enable method is not set to spi_nor_sr1_bit6_quad_enable. - * Therefore, it is necessary to correct this setting by late_init. + * Enable bit Requirement in BFPT. As a result, during BFPT parsing + * the quad_enable mask is reset. Therefore, it is necessary to + * correct this setting by late_init. */ - params->quad_enable = spi_nor_sr1_bit6_quad_enable; + params->qe_mask[0] = BIT(6); + params->qe_mask[1] = 0; /* * In addition, MX25L3255E also supports 1-4-4 page program in 3-byte @@ -340,7 +341,8 @@ static int macronix_nor_set_octal_dtr(struct spi_nor *nor, bool enable) static void macronix_nor_default_init(struct spi_nor *nor) { - nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable; + nor->params->qe_mask[0] = BIT(6); + nor->params->qe_mask[1] = 0; } static int macronix_nor_late_init(struct spi_nor *nor) diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 3c89bd9b575c..21d0ceac1ed7 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -229,7 +229,7 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, const struct sfdp_bfpt *bfpt) { - nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->opcodes.write_sr1 = 0; return 0; } @@ -633,7 +633,7 @@ static int micron_st_nor_ready(struct spi_nor *nor) static void micron_st_nor_default_init(struct spi_nor *nor) { nor->params->flags |= SNOR_F_HAS_LOCK; - nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->opcodes.write_sr1 = 0; nor->params->quad_enable = NULL; } diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 7d0b145d78d8..1eeb33a20091 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -175,24 +175,24 @@ static int spi_nor_otp_lock_bit_cr(unsigned int region) */ int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region) { - u8 *cr = nor->bouncebuf; int ret, lock_bit; + u8 sr[2]; lock_bit = spi_nor_otp_lock_bit_cr(region); if (lock_bit < 0) return lock_bit; - ret = spi_nor_read_cr(nor, cr); + ret = spi_nor_read_sr1_and_sr2(nor, sr); if (ret) return ret; /* no need to write the register if region is already locked */ - if (cr[0] & lock_bit) + if (sr[1] & lock_bit) return 0; - cr[0] |= lock_bit; + sr[1] |= lock_bit; - return spi_nor_write_16bit_cr_and_check(nor, cr[0]); + return spi_nor_write_sr1_and_sr2_and_check(nor, sr); } /** @@ -207,18 +207,18 @@ int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region) */ int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region) { - u8 *cr = nor->bouncebuf; int ret, lock_bit; + u8 sr2; lock_bit = spi_nor_otp_lock_bit_cr(region); if (lock_bit < 0) return lock_bit; - ret = spi_nor_read_cr(nor, cr); + ret = spi_nor_read_sr2(nor, &sr2); if (ret) return ret; - return cr[0] & lock_bit; + return sr2 & lock_bit; } static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index ab57fa60f718..20156914e254 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -592,10 +592,21 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, val >>= BFPT_DWORD11_PAGE_SIZE_SHIFT; params->page_size = 1U << val; + /* + * The standard declares various read and write status methods, some of + * them will be overloaded based on the QER field. + */ + params->opcodes.read_sr1 = SPINOR_OP_RDSR; + params->opcodes.read_sr2 = SPINOR_OP_RDCR; + params->opcodes.write_sr1 = SPINOR_OP_WRSR; + params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR; + + params->qe_mask[0] = 0; + params->qe_mask[1] = 0; + /* Quad Enable Requirements. */ switch (bfpt.dwords[SFDP_DWORD(15)] & BFPT_DWORD15_QER_MASK) { case BFPT_DWORD15_QER_NONE: - params->quad_enable = NULL; break; case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR: @@ -603,23 +614,28 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Writing only one byte to the Status Register has the * side-effect of clearing Status Register 2. */ + fallthrough; case BFPT_DWORD15_QER_SR2_BIT1_NO_RD: /* * Read Configuration Register (35h) instruction is not - * supported. + * supported. 16-bit writes expected. */ - params->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR; - params->quad_enable = spi_nor_sr2_bit1_quad_enable; + params->opcodes.read_sr2 = 0; + params->opcodes.write_sr1 = 0; + params->qe_mask[1] = BIT(1); break; case BFPT_DWORD15_QER_SR1_BIT6: - params->flags &= ~SNOR_F_HAS_16BIT_SR; - params->quad_enable = spi_nor_sr1_bit6_quad_enable; + params->opcodes.read_sr2 = 0; + params->opcodes.write_sr1_and_sr2 = 0; + params->qe_mask[0] = BIT(6); break; case BFPT_DWORD15_QER_SR2_BIT7: - params->flags &= ~SNOR_F_HAS_16BIT_SR; - params->quad_enable = spi_nor_sr2_bit7_quad_enable; + params->opcodes.read_sr2 = SPINOR_OP_RDSR2; + params->opcodes.write_sr1_and_sr2 = 0; + params->opcodes.write_sr2 = SPINOR_OP_WRSR2; + params->qe_mask[1] = BIT(7); break; case BFPT_DWORD15_QER_SR2_BIT1: @@ -629,9 +645,8 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Register 2, so let's be cautious and keep the default * assumption of a 16-bit Write Status (01h) command. */ - params->flags |= SNOR_F_HAS_16BIT_SR; - - params->quad_enable = spi_nor_sr2_bit1_quad_enable; + params->opcodes.write_sr1 = 0; + params->qe_mask[1] = BIT(1); break; default: @@ -639,6 +654,10 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, break; } + /* opcodes sanity check */ + WARN_ON(!params->opcodes.read_sr1 || + (!params->opcodes.write_sr1 && !params->opcodes.write_sr1_and_sr2)); + dword = bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_4B_ADDR_MODE_MASK; if (SFDP_MASK_CHECK(dword, BFPT_DWORD16_4B_ADDR_MODE_BRWR)) params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_brwr; diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 998fea1626bf..9c27c00a3a04 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -399,8 +399,9 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor, nor->bouncebuf); bool is3byte, is4byte; int ret; + u8 sr; - ret = spi_nor_read_sr(nor, &nor->bouncebuf[1]); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; @@ -408,7 +409,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor, if (ret) return ret; - is3byte = (nor->bouncebuf[0] == nor->bouncebuf[1]); + is3byte = (nor->bouncebuf[0] == sr); op = (struct spi_mem_op) CYPRESS_NOR_RD_ANY_REG_OP(4, SPINOR_REG_CYPRESS_STR1V, 0, @@ -417,7 +418,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor, if (ret) return ret; - is4byte = (nor->bouncebuf[0] == nor->bouncebuf[1]); + is4byte = (nor->bouncebuf[0] == sr); if (is3byte == is4byte) return -EIO; @@ -1105,13 +1106,14 @@ static const struct flash_info spansion_nor_parts[] = { static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor) { int ret; + u8 sr; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - if (nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) { - if (nor->bouncebuf[0] & SR_E_ERR) + if (sr & (SR_E_ERR | SR_P_ERR)) { + if (sr & SR_E_ERR) dev_err(nor->dev, "Erase Error occurred\n"); else dev_err(nor->dev, "Programming Error occurred\n"); @@ -1131,7 +1133,7 @@ static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor) return -EIO; } - return !(nor->bouncebuf[0] & SR_WIP); + return !(sr & SR_WIP); } static int spansion_nor_late_init(struct spi_nor *nor) diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c index db02c14ba16f..77a0bc41d4e5 100644 --- a/drivers/mtd/spi-nor/sst.c +++ b/drivers/mtd/spi-nor/sst.c @@ -21,16 +21,17 @@ static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len) static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len) { int ret; + u8 cr; /* We only support unlocking the entire flash array. */ if (ofs != 0 || len != nor->params->size) return -EINVAL; - ret = spi_nor_read_cr(nor, nor->bouncebuf); + ret = spi_nor_read_sr2(nor, &cr); if (ret) return ret; - if (!(nor->bouncebuf[0] & SST26VF_CR_BPNV)) { + if (!(cr & SST26VF_CR_BPNV)) { dev_dbg(nor->dev, "Any block has been permanently locked\n"); return -EINVAL; } diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 62720045a9ab..7e667e4ca84d 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -41,7 +41,7 @@ static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = nor->params; - if (!(params->flags & SNOR_F_NO_READ_CR) && + if (params->opcodes.read_sr2 && params->flags & SNOR_F_HAS_SR2_CMP_BIT6) return SR2_CMP_BIT6; else @@ -201,13 +201,51 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, return 0; } +/* + * Make sure we do our best to guess SR2. This has historically only be needed + * for swp.c, so let's keep this extra carefulness in this file. + */ +static int spi_nor_read_sr2_careful(struct spi_nor *nor, u8 *sr2) +{ + struct spi_nor_flash_parameter *params = nor->params; + int ret; + + if (params->opcodes.read_sr2) { + ret = spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1); + if (ret) + return ret; + } else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 || + spi_nor_get_protocol_width(nor->write_proto) == 4) && + nor->params->quad_enable) { + /* + * Make sure the QE bit is persistently kept. qe_mask[1] will be + * 0 if the QE bit is in SR1. + */ + *sr2 = params->qe_mask[1]; + } else { + return 0; + } + + return 0; +} + +static int spi_nor_read_sr1_and_sr2_careful(struct spi_nor *nor, u8 *sr) +{ + int ret; + + ret = spi_nor_read_sr1(nor, &sr[0]); + if (ret) + return ret; + + return spi_nor_read_sr2_careful(nor, &sr[1]); +} + /* * Keep a local cache containing all lock-related bits for debugfs use only. * This way, debugfs never needs to access the flash directly. */ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) { - struct spi_nor_flash_parameter *params = nor->params; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor); @@ -215,17 +253,9 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) if (!sr) { - if (spi_nor_read_sr(nor, nor->bouncebuf)) + if (spi_nor_read_sr1_and_sr2_careful(nor, sr_cr)) return; - sr_cr[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - if (spi_nor_read_cr(nor, nor->bouncebuf)) - return; - } - - sr_cr[1] = nor->bouncebuf[0]; sr = sr_cr; } @@ -282,20 +312,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) int ret; u8 pow; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1_and_sr2_careful(nor, status_old); if (ret) return ret; - status_old[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - status_old[1] = nor->bouncebuf[0]; - } - /* If nothing in our range is unlocked, we don't need to do anything */ if (spi_nor_is_locked_sr(nor, ofs, len, status_old)) return 0; @@ -387,10 +407,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old))) return -EINVAL; - if (params->flags & SNOR_F_NO_READ_CR) - ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); - else - ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); + ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new); if (ret) return ret; @@ -419,20 +436,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) int ret; u8 pow; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1_and_sr2_careful(nor, status_old); if (ret) return ret; - status_old[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - status_old[1] = nor->bouncebuf[0]; - } - /* If nothing in our range is locked, we don't need to do anything */ if (spi_nor_is_unlocked_sr(nor, ofs, len, status_old)) return 0; @@ -522,10 +529,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new))) return -EINVAL; - if (params->flags & SNOR_F_NO_READ_CR) - ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); - else - ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); + ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new); if (ret) return ret; @@ -543,24 +547,13 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) */ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) { - struct spi_nor_flash_parameter *params = nor->params; u8 sr_cr[2] = {}; int ret; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1_and_sr2_careful(nor, sr_cr); if (ret) return ret; - sr_cr[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - sr_cr[1] = nor->bouncebuf[0]; - } - return spi_nor_is_locked_sr(nor, ofs, len, sr_cr); } diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index f59587b859a9..df4688c637a2 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -520,12 +520,12 @@ static int winbond_nor_late_init(struct spi_nor *nor) * been to declare CR reads as unsupported, whereas the Jedec * specification doesn't clearly state that. In practice, all these * chips do support reading back the CR, which is needed for SWP support, - * so make sure that capability remains enabled (needed for SWP). + * so make sure that capability remains enabled. * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx) * which actually does not support this feature. */ if (nor->id[1] > 0x30) - nor->params->flags &= ~SNOR_F_NO_READ_CR; + params->opcodes.read_sr2 = SPINOR_OP_RDCR; /* * Winbond has reused many IDs, up to four times at this diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 099c5016b485..a901ddfac405 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -113,20 +113,16 @@ #define SR_E_ERR BIT(5) #define SR_P_ERR BIT(6) -#define SR1_QUAD_EN_BIT6 BIT(6) - #define SR_BP_SHIFT 2 /* Enhanced Volatile Configuration Register bits */ #define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */ /* Status Register 2 bits. */ -#define SR2_QUAD_EN_BIT1 BIT(1) #define SR2_LB1 BIT(3) /* Security Register Lock Bit 1 */ #define SR2_LB2 BIT(4) /* Security Register Lock Bit 2 */ #define SR2_LB3 BIT(5) /* Security Register Lock Bit 3 */ #define SR2_CMP_BIT6 BIT(6) -#define SR2_QUAD_EN_BIT7 BIT(7) /* Supported SPI protocols */ #define SNOR_PROTO_INST_MASK GENMASK(23, 16) -- cgit v1.2.3 From 9f1feb4162387295b0d8de6c879f00357852609b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:41 +0200 Subject: mtd: spi-nor: Add support for the new JESD216 rev F QER field Revision F of the JEDEC JESD216 standard includes yet another supported setting in the QER 3-bit bitfield: 110b. This setting implies that the QE bit is bit 1 of SR2 (which is an already supported case) but this time it makes it very clear that both reading and writing to SR2 is supported, but not with the usual opcodes. The write operation uses an alternate WRSR2 opcode: 0x31 (instead of 0x3E) and both the reads and writes manipulate a single register at a time. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 6 ++++++ drivers/mtd/spi-nor/sfdp.h | 8 ++++++++ include/linux/mtd/spi-nor.h | 1 + 3 files changed, 15 insertions(+) (limited to 'include/linux') diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 20156914e254..23c11fbc68c3 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -649,6 +649,12 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, params->qe_mask[1] = BIT(1); break; + case BFPT_DWORD15_QER_SR2_BIT1_1B: + params->qe_mask[1] = BIT(1); + params->opcodes.write_sr1_and_sr2 = 0; + params->opcodes.write_sr2 = SPINOR_OP_WRSR2_ALT; + break; + default: dev_dbg(nor->dev, "BFPT QER reserved value used\n"); break; diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h index d976c6c9a4ad..e8ed814c6530 100644 --- a/drivers/mtd/spi-nor/sfdp.h +++ b/drivers/mtd/spi-nor/sfdp.h @@ -12,6 +12,7 @@ #define SFDP_JESD216_MINOR 0 #define SFDP_JESD216A_MINOR 5 #define SFDP_JESD216B_MINOR 6 +#define SFDP_JESD216F_MINOR 10 /* SFDP DWORDS are indexed from 1 but C arrays are indexed from 0. */ #define SFDP_DWORD(i) ((i) - 1) @@ -81,6 +82,12 @@ struct sfdp_bfpt { * instruction 35h. QE is set via Write Status instruction 01h with * two data bytes where bit 1 of the second byte is one. * [...] + * (from JESD216 rev F) + * - 110b: QE is bit 1 of status register 2. Status register 1 is read using + * Read Status instruction 05h. Status register2 is read using + * instruction 35h, and status register 3 is read using instruction 15h. + * QE is set via Write Status Register instruction 31h with one data + * byte. */ #define BFPT_DWORD15_QER_MASK GENMASK(22, 20) #define BFPT_DWORD15_QER_NONE (0x0UL << 20) /* Micron */ @@ -89,6 +96,7 @@ struct sfdp_bfpt { #define BFPT_DWORD15_QER_SR2_BIT7 (0x3UL << 20) #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD (0x4UL << 20) #define BFPT_DWORD15_QER_SR2_BIT1 (0x5UL << 20) /* Spansion */ +#define BFPT_DWORD15_QER_SR2_BIT1_1B (0x6UL << 20) /* Winbond */ #define BFPT_DWORD16_EN4B_MASK GENMASK(31, 24) #define BFPT_DWORD16_EN4B_ALWAYS_4B BIT(30) diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index a901ddfac405..539eb514588c 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -25,6 +25,7 @@ #define SPINOR_OP_WRSR 0x01 /* Write status register 1 */ #define SPINOR_OP_RDSR2 0x3f /* Read status register 2 */ #define SPINOR_OP_WRSR2 0x3e /* Write status register 2 */ +#define SPINOR_OP_WRSR2_ALT 0x31 /* Write status register 2 (alternative) */ #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) */ -- cgit v1.2.3 From 98b89d868a178d47277feede51314d8227d4cb56 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:44 +0200 Subject: mtd: spi-nor: Create an indirection on the part name We currently print through sysfs and debugfs the name of the part if the .name field, which is legacy, has been filled in the ID table (otherwise "(null)" is printed). These IDs had an interest until manufacturers started re-using more and more extensively the so called unique JEDEC IDs. At this stage, a name that was tailored for a chip sold in 2016 may no longer be relevant for a chip sold in 2026. Still showing this name through the various filesystems may confuse people, removing these names cannot be done since they are now part of the user ABI (because of sysfs). In order to allow overwriting the .name field (which is part of a read-only structure), let's create a spi-nor pointer which will by default point to that .name, but which can be cleared in fixup hooks to make sure the name is hidden on newer parts. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 1 + drivers/mtd/spi-nor/debugfs.c | 3 +-- drivers/mtd/spi-nor/sysfs.c | 4 ++-- include/linux/mtd/spi-nor.h | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index d1fd94c391f4..e2b6efafdd8d 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3438,6 +3438,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, return PTR_ERR(info); nor->info = info; + nor->partname = info->name; mutex_init(&nor->lock); diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 51c0582f793a..5c738786145f 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -81,13 +81,12 @@ static int spi_nor_params_show(struct seq_file *s, void *data) struct spi_nor_flash_parameter *params = nor->params; struct spi_nor_erase_map *erase_map = ¶ms->erase_map; struct spi_nor_erase_region *region = erase_map->regions; - const struct flash_info *info = nor->info; char buf[16], *str; loff_t lock_start; u64 lock_length; unsigned int i; - seq_printf(s, "name\t\t%s\n", info->name); + seq_printf(s, "name\t\t%s\n", nor->partname); seq_printf(s, "id\t\t%*ph\n", SPI_NOR_MAX_ID_LEN, nor->id); string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); seq_printf(s, "size\t\t%s\n", buf); diff --git a/drivers/mtd/spi-nor/sysfs.c b/drivers/mtd/spi-nor/sysfs.c index 643513ee891b..a2dabe4c6422 100644 --- a/drivers/mtd/spi-nor/sysfs.c +++ b/drivers/mtd/spi-nor/sysfs.c @@ -25,7 +25,7 @@ static ssize_t partname_show(struct device *dev, struct spi_mem *spimem = spi_get_drvdata(spi); struct spi_nor *nor = spi_mem_get_drvdata(spimem); - return sysfs_emit(buf, "%s\n", nor->info->name); + return sysfs_emit(buf, "%s\n", nor->partname); } static DEVICE_ATTR_RO(partname); @@ -78,7 +78,7 @@ static umode_t spi_nor_sysfs_is_visible(struct kobject *kobj, if (attr == &dev_attr_manufacturer.attr && !nor->manufacturer) return 0; - if (attr == &dev_attr_partname.attr && !nor->info->name) + if (attr == &dev_attr_partname.attr && !nor->partname) return 0; if (attr == &dev_attr_jedec_id.attr && !nor->info->id && !nor->id) return 0; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 539eb514588c..b3e3c6b10186 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -393,6 +393,7 @@ struct spi_nor { u8 *id; const struct flash_info *info; const struct spi_nor_manufacturer *manufacturer; + const char *partname; u8 addr_nbytes; u8 erase_opcode; u8 read_opcode; -- cgit v1.2.3 From 7e874b1750a40f3dc9a629aeb72eba09c77f77e9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 9 Sep 2026 08:40:55 +0200 Subject: mtd: spinand: Fix spinand_manufacturer_ops kernel-doc The kernel-doc heading for struct spinand_manufacturer_ops misspells and truncates its name as manufacurer_ops. As a result, kernel-doc cannot match the comment to the structure declaration. Use the structure's actual name. Fixes: 7529df465248 ("mtd: nand: Add core infrastructure to support SPI NANDs") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Miquel Raynal --- include/linux/mtd/spinand.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index b42407b69db3..d7c99fcfb85e 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -399,7 +399,7 @@ struct spinand_devid { }; /** - * struct manufacurer_ops - SPI NAND manufacturer specific operations + * struct spinand_manufacturer_ops - SPI NAND manufacturer specific operations * @init: initialize a SPI NAND device * @cleanup: cleanup a SPI NAND device * -- cgit v1.2.3 From 55c5b6d5f59f59a8c98a7effc3195226cc20175c Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sun, 20 Sep 2026 22:16:18 +0200 Subject: mtd: nand: qpic_common: drop stray empty line from struct 'bam_transaction' Drop a superfluous empty line from the end of the 'bam_positions' structure group within the 'bam_transaction' structure. No functional changes. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- include/linux/mtd/nand-qpic-common.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/mtd/nand-qpic-common.h b/include/linux/mtd/nand-qpic-common.h index 437448995187..0f40964eefe7 100644 --- a/include/linux/mtd/nand-qpic-common.h +++ b/include/linux/mtd/nand-qpic-common.h @@ -262,7 +262,6 @@ struct bam_transaction { u32 tx_sgl_start; u32 rx_sgl_pos; u32 rx_sgl_start; - ); }; -- cgit v1.2.3 From cfae2182fa7b0f0e3caee02fa6acf5dc77410efc Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:17 +0200 Subject: mtd: rawnand: qcom: move common code to the 'qpic_common' module The 'qpic_common' module provides some generic functions which are usable by both the 'qcom_nandc' and by the 'spi-qpic-snand' driver. Move more code from the 'qcom_nandc' driver to there in order to allow using those also from the 'spi-qpic-snand' driver. No functional changes. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/qpic_common.c | 131 +++++++++++++++++++++++++++++++++++ drivers/mtd/nand/raw/qcom_nandc.c | 131 ----------------------------------- include/linux/mtd/nand-qpic-common.h | 17 +++++ 3 files changed, 148 insertions(+), 131 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/nand/qpic_common.c b/drivers/mtd/nand/qpic_common.c index 4f3e4dd766da..91ae970734f8 100644 --- a/drivers/mtd/nand/qpic_common.c +++ b/drivers/mtd/nand/qpic_common.c @@ -775,5 +775,136 @@ unalloc: } EXPORT_SYMBOL(qcom_nandc_alloc); +/* reads back FLASH_STATUS register set by the controller */ +int qcom_nandc_check_flash_errors(struct qcom_nand_controller *nandc, + int cw_cnt) +{ + int i; + + qcom_nandc_dev_to_mem(nandc, true); + + for (i = 0; i < cw_cnt; i++) { + u32 flash = le32_to_cpu(nandc->reg_read_buf[i]); + + if (flash & (FS_OP_ERR | FS_MPU_ERR)) + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL(qcom_nandc_check_flash_errors); + +/* + * Helper to prepare DMA descriptors for configuring registers + * before reading a NAND page. + */ +void qcom_nandc_config_page_read(struct qcom_nand_controller *nandc) +{ + qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + if (!nandc->props->qpic_version2) + qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr, + NAND_ERASED_CW_DETECT_CFG, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set, + NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); +} +EXPORT_SYMBOL(qcom_nandc_config_page_read); + +/* + * Helper to prepare DMA descriptors used to configure registers needed for + * before writing a NAND page. + */ +void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc) +{ + qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + if (!nandc->props->qpic_version2) + qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, + NAND_BAM_NEXT_SGL); +} +EXPORT_SYMBOL(qcom_nandc_config_page_write); + +/* + * Helper to prepare DMA descriptors for configuring registers + * before writing each codeword in NAND page. + */ +void qcom_nandc_config_cw_write(struct qcom_nand_controller *nandc) +{ + qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); + qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); + + qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); + + qcom_write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1, + NAND_BAM_NEXT_SGL); +} +EXPORT_SYMBOL(qcom_nandc_config_cw_write); + +/** + * qcom_nandc_set_read_loc_first() - to set read location first register + * @nandc: NAND controller's private data + * @reg_base: location register base + * @cw_offset: code word offset + * @read_size: code word read length + * @is_last_read_loc: is this the last read location + * + * This function will set location register value + */ +void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) +{ + __le32 locreg_val; + u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | + FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | + FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); + + locreg_val = cpu_to_le32(val); + + if (reg_base == NAND_READ_LOCATION_0) + nandc->regs->read_location0 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_1) + nandc->regs->read_location1 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_2) + nandc->regs->read_location2 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_3) + nandc->regs->read_location3 = locreg_val; +} +EXPORT_SYMBOL(qcom_nandc_set_read_loc_first); + +/** + * qcom_nandc_set_read_loc_last - to set read location last register + * @nandc: NAND controller's private data + * @reg_base: location register base + * @cw_offset: code word offset + * @read_size: code word read length + * @is_last_read_loc: is this the last read location + * + * This function will set location last register value + */ +void qcom_nandc_set_read_loc_last(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) +{ + __le32 locreg_val; + u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | + FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | + FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); + + locreg_val = cpu_to_le32(val); + + if (reg_base == NAND_READ_LOCATION_LAST_CW_0) + nandc->regs->read_location_last0 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_1) + nandc->regs->read_location_last1 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_2) + nandc->regs->read_location_last2 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_3) + nandc->regs->read_location_last3 = locreg_val; +} +EXPORT_SYMBOL(qcom_nandc_set_read_loc_last); + MODULE_DESCRIPTION("QPIC controller common api"); MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 99fe2c434ce7..d65c0101f471 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -149,68 +149,6 @@ static bool qcom_nandc_is_last_cw(struct nand_ecc_ctrl *ecc, int cw) return cw == (ecc->steps - 1); } -/** - * qcom_nandc_set_read_loc_first() - to set read location first register - * @nandc: NAND controller's private data - * @reg_base: location register base - * @cw_offset: code word offset - * @read_size: code word read length - * @is_last_read_loc: is this the last read location - * - * This function will set location register value - */ -static void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, - int reg_base, u32 cw_offset, - u32 read_size, u32 is_last_read_loc) -{ - __le32 locreg_val; - u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | - FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | - FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); - - locreg_val = cpu_to_le32(val); - - if (reg_base == NAND_READ_LOCATION_0) - nandc->regs->read_location0 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_1) - nandc->regs->read_location1 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_2) - nandc->regs->read_location2 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_3) - nandc->regs->read_location3 = locreg_val; -} - -/** - * qcom_nandc_set_read_loc_last - to set read location last register - * @nandc: NAND controller's private data - * @reg_base: location register base - * @cw_offset: code word offset - * @read_size: code word read length - * @is_last_read_loc: is this the last read location - * - * This function will set location last register value - */ -static void qcom_nandc_set_read_loc_last(struct qcom_nand_controller *nandc, - int reg_base, u32 cw_offset, - u32 read_size, u32 is_last_read_loc) -{ - __le32 locreg_val; - u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | - FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | - FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); - - locreg_val = cpu_to_le32(val); - - if (reg_base == NAND_READ_LOCATION_LAST_CW_0) - nandc->regs->read_location_last0 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_LAST_CW_1) - nandc->regs->read_location_last1 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_LAST_CW_2) - nandc->regs->read_location_last2 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_LAST_CW_3) - nandc->regs->read_location_last3 = locreg_val; -} - /* helper to configure location register values */ static void nandc_set_read_loc(struct nand_chip *chip, int cw, int reg, u32 cw_offset, u32 read_size, u32 is_last_read_loc) @@ -301,22 +239,6 @@ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, i host->cw_data : host->cw_size, 1); } -/* - * Helper to prepare DMA descriptors for configuring registers - * before reading a NAND page. - */ -static void qcom_nandc_config_page_read(struct qcom_nand_controller *nandc) -{ - qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - if (!nandc->props->qpic_version2) - qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); - qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); -} - /* * Helper to prepare DMA descriptors for configuring registers * before reading each codeword in NAND page. @@ -361,35 +283,6 @@ config_nand_single_cw_page_read(struct nand_chip *chip, config_nand_cw_read(chip, use_ecc, cw); } -/* - * Helper to prepare DMA descriptors used to configure registers needed for - * before writing a NAND page. - */ -static void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc) -{ - qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - if (!nandc->props->qpic_version2) - qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, - NAND_BAM_NEXT_SGL); -} - -/* - * Helper to prepare DMA descriptors for configuring registers - * before writing each codeword in NAND page. - */ -static void qcom_nandc_config_cw_write(struct qcom_nand_controller *nandc) -{ - qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); - qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); - - qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); - - qcom_write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0); - qcom_write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1, - NAND_BAM_NEXT_SGL); -} - /* * when using BCH ECC, the HW flags an error in NAND_FLASH_STATUS if it read * an erased CW, and reports an erased CW in NAND_ERASED_CW_DETECT_STATUS. @@ -438,30 +331,6 @@ static bool erased_chunk_check_and_fixup(u8 *data_buf, int data_len) return true; } -struct qcom_nandc_read_stats { - __le32 flash; - __le32 buffer; - __le32 erased_cw; -}; - -/* reads back FLASH_STATUS register set by the controller */ -static int qcom_nandc_check_flash_errors(struct qcom_nand_controller *nandc, - int cw_cnt) -{ - int i; - - qcom_nandc_dev_to_mem(nandc, true); - - for (i = 0; i < cw_cnt; i++) { - u32 flash = le32_to_cpu(nandc->reg_read_buf[i]); - - if (flash & (FS_OP_ERR | FS_MPU_ERR)) - return -EIO; - } - - return 0; -} - /* performs raw read for one codeword */ static int qcom_nandc_read_cw_raw(struct mtd_info *mtd, struct nand_chip *chip, diff --git a/include/linux/mtd/nand-qpic-common.h b/include/linux/mtd/nand-qpic-common.h index 0f40964eefe7..f31fd18d837f 100644 --- a/include/linux/mtd/nand-qpic-common.h +++ b/include/linux/mtd/nand-qpic-common.h @@ -457,6 +457,12 @@ struct qcom_nandc_props { bool use_codeword_fixup; }; +struct qcom_nandc_read_stats { + __le32 flash; + __le32 buffer; + __le32 erased_cw; +}; + void qcom_free_bam_transaction(struct qcom_nand_controller *nandc); struct bam_transaction *qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc); void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc); @@ -482,5 +488,16 @@ int qcom_submit_descs(struct qcom_nand_controller *nandc); void qcom_clear_read_regs(struct qcom_nand_controller *nandc); void qcom_nandc_unalloc(struct qcom_nand_controller *nandc); int qcom_nandc_alloc(struct qcom_nand_controller *nandc); +int qcom_nandc_check_flash_errors(struct qcom_nand_controller *nandc, + int cw_cnt); +void qcom_nandc_config_page_read(struct qcom_nand_controller *nandc); +void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc); +void qcom_nandc_config_cw_write(struct qcom_nand_controller *nandc); +void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc); +void qcom_nandc_set_read_loc_last(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc); #endif -- cgit v1.2.3