From 6eb7c193e751f057b2d75af9b174cfe5dd060696 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 29 Apr 2026 19:56:43 +0200 Subject: mtd: spinand: Use secondary ops for continuous reads In case a chip supports continuous reads, but uses a slightly different cache operation for these, it may provide a secondary operation template which will be used only during continuous cache read operations. From a vendor driver point of view, enabling this feature implies providing a new set of templates for these continuous read operations. The core will automatically pick the fastest variant, depending on the hardware capabilities. Signed-off-by: Miquel Raynal --- include/linux/mtd/spinand.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'include/linux') diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index f53f5f0499b4..44f4347104d6 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -583,6 +583,7 @@ enum spinand_bus_interface { * @op_variants.read_cache: variants of the read-cache operation * @op_variants.write_cache: variants of the write-cache operation * @op_variants.update_cache: variants of the update-cache operation + * @op_variants.cont_read_cache: variants of the continuous read-cache operation * @vendor_ops: vendor specific operations * @select_target: function used to select a target/die. Required only for * multi-die chips @@ -607,6 +608,7 @@ struct spinand_info { const struct spinand_op_variants *read_cache; const struct spinand_op_variants *write_cache; const struct spinand_op_variants *update_cache; + const struct spinand_op_variants *cont_read_cache; } op_variants; const struct spinand_op_variants *vendor_ops; int (*select_target)(struct spinand_device *spinand, @@ -636,6 +638,14 @@ struct spinand_info { .update_cache = __update, \ } +#define SPINAND_INFO_OP_VARIANTS_WITH_CONT(__read, __write, __update, __cont_read) \ + { \ + .read_cache = __read, \ + .write_cache = __write, \ + .update_cache = __update, \ + .cont_read_cache = __cont_read, \ + } + #define SPINAND_INFO_VENDOR_OPS(__ops) \ .vendor_ops = __ops @@ -707,6 +717,7 @@ struct spinand_dirmap { * @read_cache: read cache op template * @write_cache: write cache op template * @update_cache: update cache op template + * @cont_read_cache: continuous read cache op template (optional) */ struct spinand_mem_ops { struct spi_mem_op reset; @@ -721,6 +732,7 @@ struct spinand_mem_ops { const struct spi_mem_op *read_cache; const struct spi_mem_op *write_cache; const struct spi_mem_op *update_cache; + const struct spi_mem_op *cont_read_cache; }; /** -- cgit v1.2.3 From 7c4e909b176f661e834853fa726a6e58acab00e1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:29 +0200 Subject: mtd: spi-nor: Improve opcodes documentation There are two status registers, named 1 and 2. The current wording is misleading as "1" may refer to the status register ID as well as the number of bytes required (which, in this case can be 1 or 2). Clarify the comments by aligning them on the same pattern: "{read,write} status {1,2} register" Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- include/linux/mtd/spi-nor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index cdcfe0fd2e7d..90a0cf583512 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -21,8 +21,8 @@ /* Flash opcodes. */ #define SPINOR_OP_WRDI 0x04 /* Write disable */ #define SPINOR_OP_WREN 0x06 /* Write enable */ -#define SPINOR_OP_RDSR 0x05 /* Read status register */ -#define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */ +#define SPINOR_OP_RDSR 0x05 /* Read status register 1 */ +#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_READ 0x03 /* Read data bytes (low frequency) */ -- cgit v1.2.3 From b7b63475903cc9967ae53c579bc1ad9ed2519080 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:41 +0200 Subject: mtd: spi-nor: Create a local SR cache In order to be able to generate debugfs output without having to actually reach the flash, create a SPI NOR local cache of the status registers. What matters in our case are all the bits related to sector locking. As such, in order to make it clear that this cache is not intended to be used anywhere else, we zero the irrelevant bits. The cache is initialized once during the early init, and then maintained every time the write protection scheme is updated. Suggested-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.c | 4 +++- drivers/mtd/spi-nor/core.h | 1 + drivers/mtd/spi-nor/swp.c | 35 +++++++++++++++++++++++++++++++++-- include/linux/mtd/spi-nor.h | 2 ++ 4 files changed, 39 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 2799c21d0b67..1fc71d6e0fec 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3326,10 +3326,12 @@ static int spi_nor_init(struct spi_nor *nor) * protection bits are volatile. The latter is indicated by * SNOR_F_SWP_IS_VOLATILE. */ + 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)) + nor->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 && diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 0753d0a6f8b8..cd355e94b97e 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -679,6 +679,7 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor, void spi_nor_init_default_locking_ops(struct spi_nor *nor); void spi_nor_try_unlock_all(struct spi_nor *nor); +void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr); void spi_nor_set_mtd_locking_ops(struct spi_nor *nor); void spi_nor_set_mtd_otp_ops(struct spi_nor *nor); diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 2d1b1c8a535a..cd37fec08c0e 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -162,6 +162,25 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, return 0; } +/* + * 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) +{ + u8 bp_mask = spi_nor_get_sr_bp_mask(nor); + u8 tb_mask = spi_nor_get_sr_tb_mask(nor); + + if (!sr) { + if (spi_nor_read_sr(nor, nor->bouncebuf)) + return; + + sr = nor->bouncebuf; + } + + nor->dfs_sr_cache[0] = sr[0] & (bp_mask | tb_mask | SR_SRWD); +} + /* * Lock a region of the flash. Compatible with ST Micro and similar flash. * Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status @@ -271,7 +290,13 @@ 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; - return spi_nor_write_sr_and_check(nor, status_new[0]); + ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (ret) + return ret; + + spi_nor_cache_sr_lock_bits(nor, status_new); + + return 0; } /* @@ -353,7 +378,13 @@ 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; - return spi_nor_write_sr_and_check(nor, status_new[0]); + ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (ret) + return ret; + + spi_nor_cache_sr_lock_bits(nor, status_new); + + return 0; } /* diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 90a0cf583512..9ad77f9e76c2 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -371,6 +371,7 @@ struct spi_nor_flash_parameter; * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations * @sfdp: the SFDP data of the flash * @debugfs_root: pointer to the debugfs directory + * @dfs_sr_cache: Status Register cached value for debugfs use only * @controller_ops: SPI NOR controller driver specific operations. * @params: [FLASH-SPECIFIC] SPI NOR flash parameters and settings. * The structure includes legacy flash parameters and @@ -409,6 +410,7 @@ struct spi_nor { enum spi_nor_cmd_ext cmd_ext_type; struct sfdp *sfdp; struct dentry *debugfs_root; + u8 dfs_sr_cache[2]; const struct spi_nor_controller_ops *controller_ops; -- cgit v1.2.3 From 54e1bc80af0c993afc6a2283722f8d4535b10d40 Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Tue, 5 May 2026 09:34:52 +0800 Subject: mtd: spinand: Add support for randomizer This patch adds support for the randomizer feature. It introduces a 'set_randomizer' callback in 'struct spinand_info' and 'struct spinand_device'. If a driver implements this callback, the core will invoke it during device initialization (spinand_init) to enable or disable the randomizer feature based on the device tree configuration. Signed-off-by: Cheng Ming Lin Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 20 ++++++++++++++++++++ include/linux/mtd/spinand.h | 9 +++++++++ 2 files changed, 29 insertions(+) (limited to 'include/linux') diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index f1084d5e04b9..f86786344d52 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1328,6 +1328,22 @@ static int spinand_create_dirmaps(struct spinand_device *spinand) return 0; } +static int spinand_randomizer_init(struct spinand_device *spinand) +{ + struct device_node *np = spinand->spimem->spi->dev.of_node; + u32 rand_val; + int ret; + + if (!spinand->set_randomizer) + return 0; + + ret = of_property_read_u32(np, "nand-randomizer", &rand_val); + if (ret) + return 0; + + return spinand->set_randomizer(spinand, rand_val); +} + static const struct nand_ops spinand_ops = { .erase = spinand_erase, .markbad = spinand_markbad, @@ -1619,6 +1635,7 @@ int spinand_match_and_init(struct spinand_device *spinand, spinand->user_otp = &table[i].user_otp; spinand->read_retries = table[i].read_retries; spinand->set_read_retry = table[i].set_read_retry; + spinand->set_randomizer = table[i].set_randomizer; /* I/O variants selection with single-spi SDR commands */ @@ -1942,6 +1959,9 @@ static int spinand_init(struct spinand_device *spinand) * ECC initialization must have happened previously. */ spinand_cont_read_init(spinand); + ret = spinand_randomizer_init(spinand); + if (ret) + goto err_cleanup_nanddev; mtd->_read_oob = spinand_mtd_read; mtd->_write_oob = spinand_mtd_write; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 44f4347104d6..ec6efcfeef83 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -593,6 +593,7 @@ enum spinand_bus_interface { * @user_otp: SPI NAND user OTP info. * @read_retries: the number of read retry modes supported * @set_read_retry: enable/disable read retry for data recovery + * @set_randomizer: enable/disable randomizer support * * Each SPI NAND manufacturer driver should have a spinand_info table * describing all the chips supported by the driver. @@ -622,6 +623,8 @@ struct spinand_info { unsigned int read_retries; int (*set_read_retry)(struct spinand_device *spinand, unsigned int read_retry); + int (*set_randomizer)(struct spinand_device *spinand, + bool enable); }; #define SPINAND_ID(__method, ...) \ @@ -686,6 +689,9 @@ struct spinand_info { .read_retries = __read_retries, \ .set_read_retry = __set_read_retry +#define SPINAND_RANDOMIZER(__set_randomizer) \ + .set_randomizer = __set_randomizer + #define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants, \ __flags, ...) \ { \ @@ -771,6 +777,7 @@ struct spinand_mem_ops { * @user_otp: SPI NAND user OTP info. * @read_retries: the number of read retry modes supported * @set_read_retry: Enable/disable the read retry feature + * @set_randomizer: Enable/disable the randomizer feature */ struct spinand_device { struct nand_device base; @@ -804,6 +811,8 @@ struct spinand_device { bool cont_read_possible; int (*set_cont_read)(struct spinand_device *spinand, bool enable); + int (*set_randomizer)(struct spinand_device *spinand, + bool enable); const struct spinand_fact_otp *fact_otp; const struct spinand_user_otp *user_otp; -- cgit v1.2.3 From 7fbdbc7d028a20a78b7d28a9510a216c76b5fbfd Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 25 May 2026 15:04:40 -0700 Subject: mtd: rawnand: qcom: embed nand_controller into qcom_nand_controller The qcom_nand_controller had a struct nand_controller *controller pointer that was assigned to (struct nand_controller *)&nandc[1], with the allocation oversized by sizeof(*controller) to make room. get_qcom_nand_controller() then walked backwards from chip->controller using sizeof()-based arithmetic to recover the enclosing nandc. Embed the nand_controller directly into qcom_nand_controller and use container_of() in get_qcom_nand_controller(). The header now needs the full rawnand.h definition rather than a forward declaration. Assisted-by: Claude:Opus-4.7 Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 16 ++++++---------- include/linux/mtd/nand-qpic-common.h | 4 +++- 2 files changed, 9 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index b7e79b76654d..4b80ce084d9a 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -128,8 +128,8 @@ static struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip) static struct qcom_nand_controller * get_qcom_nand_controller(struct nand_chip *chip) { - return (struct qcom_nand_controller *) - ((u8 *)chip->controller - sizeof(struct qcom_nand_controller)); + return container_of(chip->controller, struct qcom_nand_controller, + controller); } static u32 nandc_read(struct qcom_nand_controller *nandc, int offset) @@ -2034,8 +2034,8 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc) { u32 nand_ctrl; - nand_controller_init(nandc->controller); - nandc->controller->ops = &qcom_nandc_ops; + nand_controller_init(&nandc->controller); + nandc->controller.ops = &qcom_nandc_ops; /* kill onenand */ if (!nandc->props->nandc_part_of_qpic) @@ -2175,7 +2175,7 @@ static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc, chip->legacy.block_bad = qcom_nandc_block_bad; chip->legacy.block_markbad = qcom_nandc_block_markbad; - chip->controller = nandc->controller; + chip->controller = &nandc->controller; chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_SKIP_BBTSCAN; @@ -2256,21 +2256,17 @@ static int qcom_nandc_parse_dt(struct platform_device *pdev) static int qcom_nandc_probe(struct platform_device *pdev) { struct qcom_nand_controller *nandc; - struct nand_controller *controller; const void *dev_data; struct device *dev = &pdev->dev; struct resource *res; int ret; - nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc) + sizeof(*controller), - GFP_KERNEL); + nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc), GFP_KERNEL); if (!nandc) return -ENOMEM; - controller = (struct nand_controller *)&nandc[1]; platform_set_drvdata(pdev, nandc); nandc->dev = dev; - nandc->controller = controller; dev_data = of_device_get_match_data(dev); if (!dev_data) { diff --git a/include/linux/mtd/nand-qpic-common.h b/include/linux/mtd/nand-qpic-common.h index e8201d1b7cf9..006ca8c978a9 100644 --- a/include/linux/mtd/nand-qpic-common.h +++ b/include/linux/mtd/nand-qpic-common.h @@ -9,6 +9,8 @@ #ifndef __MTD_NAND_QPIC_COMMON_H__ #define __MTD_NAND_QPIC_COMMON_H__ +#include + /* NANDc reg offsets */ #define NAND_FLASH_CMD 0x00 #define NAND_ADDR0 0x04 @@ -394,7 +396,7 @@ struct qcom_nand_controller { const struct qcom_nandc_props *props; - struct nand_controller *controller; + struct nand_controller controller; struct qpic_spi_nand *qspi; struct list_head host_list; -- cgit v1.2.3 From e3fb31d8847fef2ce37c5f60bc77d3f731a2419b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:45 +0200 Subject: mtd: spi-nor: swp: Add support for the complement feature The current locking implementation allows to select a power of two number of blocks, which is going to be the protected amount, as well as telling whether this is the data at the top (end of the device) or the bottom (beginning of the device). This means at most we can cover half of the device or the entire device, but nothing in between. The complement feature allows a much finer grain of configuration, by allowing to invert what is considered locked and unlocked. Add support for this feature. The only known position for the CMP bit is bit 6 of the configuration register. The locking and unlocking logics are kept unchanged if the CMP bit is unavailable. Otherwise, once the regular logic has been applied, we check if we already found an optimal configuration. If not, we try with the CMP bit set. If the coverage is closer to the request, we use it. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.c | 3 + drivers/mtd/spi-nor/core.h | 4 + drivers/mtd/spi-nor/debugfs.c | 1 + drivers/mtd/spi-nor/swp.c | 214 +++++++++++++++++++++++++++++++++++------- include/linux/mtd/spi-nor.h | 1 + 5 files changed, 190 insertions(+), 33 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 1fc71d6e0fec..b25d1a870a22 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2974,6 +2974,9 @@ static void spi_nor_init_flags(struct spi_nor *nor) nor->flags |= SNOR_F_HAS_SR_BP3_BIT6; } + if (flags & SPI_NOR_HAS_CMP) + nor->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; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index ce46771ecdc8..ba2d1a862c9d 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -141,6 +141,7 @@ enum spi_nor_option_flags { SNOR_F_ECC = BIT(15), SNOR_F_NO_WP = BIT(16), SNOR_F_SWAP16 = BIT(17), + SNOR_F_HAS_SR2_CMP_BIT6 = BIT(18), }; struct spi_nor_read_command { @@ -491,6 +492,8 @@ struct spi_nor_id { * SPI_NOR_NO_ERASE: no erase command needed. * SPI_NOR_QUAD_PP: flash supports Quad Input Page Program. * SPI_NOR_RWW: flash supports reads while write. + * SPI_NOR_HAS_CMP: flash SR2 has complement (CMP) protect bit. Must + * be used with SPI_NOR_HAS_LOCK. * * @no_sfdp_flags: flags that indicate support that can be discovered via SFDP. * Used when SFDP tables are not defined in the flash. These @@ -539,6 +542,7 @@ struct flash_info { #define SPI_NOR_NO_ERASE BIT(6) #define SPI_NOR_QUAD_PP BIT(8) #define SPI_NOR_RWW BIT(9) +#define SPI_NOR_HAS_CMP BIT(10) u8 no_sfdp_flags; #define SPI_NOR_SKIP_SFDP BIT(0) diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index ef710bd4f307..e80c03dd5461 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -30,6 +30,7 @@ static const char *const snor_f_names[] = { SNOR_F_NAME(ECC), SNOR_F_NAME(NO_WP), SNOR_F_NAME(SWAP16), + SNOR_F_NAME(HAS_SR2_CMP_BIT6), }; #undef SNOR_F_NAME diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 2411d8f1012d..235070b215d1 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -34,6 +34,15 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) return 0; } +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) + return SR2_CMP_BIT6; + else + return 0; +} + u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) { unsigned int bp_slots, bp_slots_needed; @@ -61,8 +70,10 @@ void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, 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 cmp = sr[1] & cmp_mask; if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) val = (val & ~SR_BP3_BIT6) | SR_BP3; @@ -70,22 +81,37 @@ void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, bp = val >> SR_BP_SHIFT; if (!bp) { - /* No protection */ - *ofs = 0; - *len = 0; + if (!cmp) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + /* Full protection */ + *ofs = 0; + *len = nor->params->size; + } return; } min_prot_len = spi_nor_get_min_prot_length_sr(nor); *len = min_prot_len << (bp - 1); - if (*len > nor->params->size) *len = nor->params->size; - if (tb) - *ofs = 0; - else - *ofs = nor->params->size - *len; + if (cmp) + *len = nor->params->size - *len; + + if (!cmp) { + if (tb) + *ofs = 0; + else + *ofs = nor->params->size - *len; + } else { + if (tb) + *ofs = nor->params->size - *len; + else + *ofs = 0; + } } /* @@ -142,13 +168,15 @@ static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow) } static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, - u8 pow, bool use_top) + u8 pow, bool use_top, bool cmp) { 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); int ret; new_sr[0] = old_sr[0] & ~bp_mask & ~tb_mask; + new_sr[1] = old_sr[1] & ~cmp_mask; /* Build BP field */ ret = spi_nor_sr_set_bp_mask(nor, &new_sr[0], pow); @@ -156,9 +184,13 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, return ret; /* Build TB field */ - if (!use_top) + if ((!cmp && !use_top) || (cmp && use_top)) new_sr[0] |= tb_mask; + /* Build CMP field */ + if (cmp) + new_sr[1] |= cmp_mask; + return 0; } @@ -170,15 +202,27 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) { 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 sr_cr[2] = {}; + if (!sr) { if (spi_nor_read_sr(nor, nor->bouncebuf)) return; - sr = nor->bouncebuf; + sr_cr[0] = nor->bouncebuf[0]; + + if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (spi_nor_read_cr(nor, nor->bouncebuf)) + return; + } + + sr_cr[1] = nor->bouncebuf[0]; + sr = sr_cr; } nor->dfs_sr_cache[0] = sr[0] & (bp_mask | tb_mask | SR_SRWD); + nor->dfs_sr_cache[1] = sr[1] & cmp_mask; } /* @@ -187,10 +231,11 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) * register * (SR). Does not support these features found in newer SR bitfields: * - SEC: sector/block protect - only handle SEC=0 (block protect) - * - CMP: complement protect - only support CMP=0 (range is not complemented) * * Support for the following is provided conditionally for some flash: * - TB: top/bottom protect + * - CMP: complement protect (BP and TP describe the unlocked part, while + * the reminder is locked) * * Sample table portion for 8MB flash (Winbond w25q64fw): * @@ -217,11 +262,13 @@ 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) { u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); - u8 status_old[1] = {}, status_new[1] = {}; - loff_t ofs_old, ofs_new; - u64 len_old, len_new; + 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 = nor->flags & SNOR_F_HAS_SR_TB, + can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; u8 pow; @@ -232,6 +279,14 @@ 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)) { + 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; @@ -262,27 +317,60 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false); if (ret) return ret; + /* + * In case the region asked is not fully met, maybe we can try with the + * complement feature + */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + if (can_be_cmp && len_new != lock_len) { + pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + + /* + * ilog2() "floors" the result, which means in some cases we may have to + * manually reduce the scope when the complement feature is used. + * The uAPI is to never lock more than what is requested, but less is accepted. + * Make sure we are not covering a too wide range, reduce it otherwise. + */ + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp > lock_len) { + pow++; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + } + + /* Pick the CMP configuration if we cover a closer range */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp <= lock_len && + (lock_len - len_new_cmp) < (lock_len - len_new)) + best_status_new = status_new_cmp; + } + /* * Disallow further writes if WP# pin is neither left floating nor * 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)) - status_new[0] |= SR_SRWD; + best_status_new[0] |= SR_SRWD; spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); - spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new); /* Don't "lock" with no region! */ if (!len_new) return -EINVAL; /* Don't bother if they're the same */ - if (status_new[0] == status_old[0]) + if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1]) return 0; /* Only modify protection if it will not unlock other areas */ @@ -290,11 +378,14 @@ 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; - ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (nor->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); if (ret) return ret; - spi_nor_cache_sr_lock_bits(nor, status_new); + spi_nor_cache_sr_lock_bits(nor, best_status_new); return 0; } @@ -307,11 +398,13 @@ 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) { u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); - u8 status_old[1], status_new[1]; - loff_t ofs_old, ofs_new; - u64 len_old, len_new; + 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 = nor->flags & SNOR_F_HAS_SR_TB, + can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; u8 pow; @@ -322,6 +415,14 @@ 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)) { + 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; @@ -359,30 +460,66 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false); if (ret) return ret; + /* + * In case the region asked is not fully met, maybe we can try with the + * complement feature + */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + if (can_be_cmp && len_new != lock_len) { + pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + + /* + * ilog2() "floors" the result, which means in some cases we may have to + * manually reduce the scope when the complement feature is used. + * The uAPI is to never unlock more than what is requested, but less is accepted. + * Make sure we are not covering a too small range, increase it otherwise. + */ + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp < lock_len) { + pow--; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + } + + /* Pick the CMP configuration if we cover a closer range */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp <= lock_len && + (lock_len - len_new_cmp) < (lock_len - len_new)) + best_status_new = status_new_cmp; + } + /* Don't protect status register if we're fully unlocked */ if (lock_len == 0) - status_new[0] &= ~SR_SRWD; + best_status_new[0] &= ~SR_SRWD; /* Don't bother if they're the same */ - if (status_new[0] == status_old[0]) + if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1]) return 0; /* Only modify protection if it will not lock other areas */ spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); - spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new); if (len_old && len_new && (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new))) return -EINVAL; - ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (nor->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); if (ret) return ret; - spi_nor_cache_sr_lock_bits(nor, status_new); + spi_nor_cache_sr_lock_bits(nor, best_status_new); return 0; } @@ -396,13 +533,24 @@ 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) { + u8 sr_cr[2] = {}; int ret; ret = spi_nor_read_sr(nor, nor->bouncebuf); if (ret) return ret; - return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf); + sr_cr[0] = nor->bouncebuf[0]; + + if (!(nor->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); } static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = { diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 9ad77f9e76c2..4b92494827b1 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -125,6 +125,7 @@ #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 */ -- cgit v1.2.3