diff options
| author | Mark Brown <broonie@kernel.org> | 2026-05-29 22:36:56 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-05-29 22:36:56 +0100 |
| commit | 7a822a9dd6e98ccec31f0f02bd4db6252ed7f188 (patch) | |
| tree | 4eeac637b212b37270f2fff28487531e1eef3f74 | |
| parent | d1304489d366f4f5afa7dd9643344b38bca94976 (diff) | |
| parent | df415c5e1de0f1aeefacb4e6252ff98d38c04437 (diff) | |
| download | linux-next-history-7a822a9dd6e98ccec31f0f02bd4db6252ed7f188.tar.gz | |
Merge branch 'spi-nor/next' of https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git
| -rw-r--r-- | Documentation/driver-api/mtd/spi-nor.rst | 170 | ||||
| -rw-r--r-- | drivers/mtd/spi-nor/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/mtd/spi-nor/core.c | 76 | ||||
| -rw-r--r-- | drivers/mtd/spi-nor/core.h | 25 | ||||
| -rw-r--r-- | drivers/mtd/spi-nor/debugfs.c | 72 | ||||
| -rw-r--r-- | drivers/mtd/spi-nor/spansion.c | 7 | ||||
| -rw-r--r-- | drivers/mtd/spi-nor/swp.c | 363 | ||||
| -rw-r--r-- | drivers/mtd/spi-nor/winbond.c | 41 | ||||
| -rw-r--r-- | include/linux/mtd/spi-nor.h | 7 |
9 files changed, 671 insertions, 91 deletions
diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst index 148fa4288760b..747a326fb6c0d 100644 --- a/Documentation/driver-api/mtd/spi-nor.rst +++ b/Documentation/driver-api/mtd/spi-nor.rst @@ -203,3 +203,173 @@ section, after the ``---`` marker. mtd.writesize = 1 mtd.oobsize = 0 regions = 0 + +5) If your flash supports locking, please go through the following test + procedure to make sure it correctly behaves. The below example + expects the typical situation where eraseblocks and lock sectors have + the same size. In case you enabled MTD_SPI_NOR_USE_4K_SECTORS, you + must adapt `bs` accordingly. + + Warning: These tests may hard lock your device! Make sure: + + - The device is not hard locked already (#WP strapped to low and + SR_SRWD bit set) + - If you have a WPn pin, you may want to set `no-wp` in your DT for + the time of the test, to only make use of software protection. + Otherwise, clearing the locking state depends on the WPn + signal and if it is tied to low, the flash will be permanently + locked. + + Test full chip locking and make sure expectations, the MEMISLOCKED + ioctl output, the debugfs output and experimental results are all + aligned:: + + root@1:~# alias show_sectors='grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params' + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -i /dev/mtd0 + Device: /dev/mtd0 + Start: 0 + Len: 0x4000000 + Lock status: unlocked + Return code: 0 + root@1:~# mtd_debug erase /dev/mtd0 0 2097152 + Erased 2097152 bytes from address 0x00000000 in flash + root@1:~# mtd_debug write /dev/mtd0 0 2097152 spi_test + Copied 2097152 bytes from spi_test to address 0x00000000 in flash + root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read + Copied 2097152 bytes from address 0x00000000 in flash to spi_read + root@1:~# sha256sum spi* + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03ffffff | unlocked | 1024 + + root@1:~# flash_lock -l /dev/mtd0 + root@1:~# flash_lock -i /dev/mtd0 + Device: /dev/mtd0 + Start: 0 + Len: 0x4000000 + Lock status: locked + Return code: 1 + root@1:~# mtd_debug erase /dev/mtd0 0 2097152 + Erased 2097152 bytes from address 0x00000000 in flash + root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read + Copied 2097152 bytes from address 0x00000000 in flash to spi_read + root@1:~# sha256sum spi* + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test + root@1:~# dd if=/dev/urandom of=./spi_test2 bs=1M count=2 + 2+0 records in + 2+0 records out + root@1:~# mtd_debug write /dev/mtd0 0 2097152 spi_test2 + Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash + root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read2 + Copied 2097152 bytes from address 0x00000000 in flash to spi_read2 + root@1:~# sha256sum spi* + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read2 + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test + bea9334df51c620440f86751cba0799214a016329f1736f9456d40cf40efdc88 spi_test2 + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03ffffff | locked | 1024 + root@1:~# flash_lock -u /dev/mtd0 + + Once we trust the debugfs output we can use it to test various + situations. Check top locking/unlocking (end of the device):: + + root@1:~# size=$(cat /sys/class/mtd/mtd0/size) + root@1:~# bs=$(cat /sys/class/mtd/mtd0/erasesize) + root@1:~# nsectors=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*unlocked | //') + root@1:~# ss=$(($size / $nsectors)) + root@1:~# bps=$(($ss / $bs)) + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 $(($size - (2 * $ss))) $((2 * $bps)) # last two + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03fdffff | unlocked | 1022 + 03fe0000-03ffffff | locked | 2 + root@1:~# flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $((1 * $bps)) # last one + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03feffff | unlocked | 1023 + 03ff0000-03ffffff | locked | 1 + + If the flash features 4 block protection bits (BP), we can protect + more than 4MB (typically 128 64kiB-blocks or more), with a finer + grain than locking the entire device:: + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 $(($size - (2**7 * $ss))) $((2**7 * $bps)) + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-037fffff | unlocked | 896 + 03800000-03ffffff | locked | 128 + + If the flash features a Top/Bottom (TB) bit, we can protect the + beginning of the flash:: + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 0 $((2 * $bps)) # first two + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0001ffff | locked | 2 + 00020000-03ffffff | unlocked | 1022 + root@1:~# flash_lock -u /dev/mtd0 $ss $((1 * $bps)) # first one + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0000ffff | locked | 1 + 00010000-03ffffff | unlocked | 1023 + + If the flash features a Complement (CMP) bit, we can protect with + more granularity above half of the capacity. Let's lock all but one + block, then unlock one more block:: + + root@1:~# all_but_one=$((($size / $bs) - ($ss / $bs))) + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0000ffff | unlocked | 1 + 00010000-03ffffff | locked | 1023 + root@1:~# flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0001ffff | unlocked | 2 + 00020000-03ffffff | locked | 1022 + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03feffff | locked | 1023 + 03ff0000-03ffffff | unlocked | 1 + root@1:~# flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03fdffff | locked | 1022 + 03fe0000-03ffffff | unlocked | 2 diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 24cd25de2b8b7..fd05a24d64a96 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -1,7 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only menuconfig MTD_SPI_NOR tristate "SPI NOR device support" - depends on MTD depends on MTD && SPI_MASTER select SPI_MEM help diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index a7bc458edc5cd..ccf4396cdcd04 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -869,8 +869,8 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) 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 && + } 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 @@ -977,6 +977,54 @@ int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) } /** + * 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. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +{ + 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); + 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->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; +} + +/** * 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. @@ -994,6 +1042,23 @@ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) } /** + * 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. + * + * Return: 0 on success, -errno otherwise. + */ +int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +{ + if (nor->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]); +} + +/** * spi_nor_write_sr2() - Write the Status Register 2 using the * SPINOR_OP_WRSR2 (3eh) command. * @nor: pointer to 'struct spi_nor'. @@ -2909,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; @@ -3261,10 +3329,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 e838c40a25897..ba2d1a862c9d4 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 { @@ -279,9 +280,17 @@ struct spi_nor_erase_map { /** * struct spi_nor_locking_ops - SPI NOR locking methods - * @lock: lock a region of the SPI NOR. - * @unlock: unlock a region of the SPI NOR. - * @is_locked: check if a region of the SPI NOR is completely locked + * @lock: lock a region of the SPI NOR, never locks more than what is + * requested, ie. may lock less. + * @unlock: unlock a region of the SPI NOR, may unlock more than what is + * requested. + * @is_locked: check if a region of the SPI NOR is completely locked, returns + * false otherwise. This feedback may be misleading because users + * may get an "unlocked" status even though a subpart of the region + * is effectively locked. + * + * If in doubt during development, check-out the debugfs output which tries to + * be more user friendly. */ struct spi_nor_locking_ops { int (*lock)(struct spi_nor *nor, loff_t ofs, u64 len); @@ -483,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 @@ -531,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) @@ -632,6 +644,7 @@ 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); ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf); @@ -672,7 +685,9 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_bfpt *bfpt); void spi_nor_init_default_locking_ops(struct spi_nor *nor); +bool spi_nor_has_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); @@ -705,6 +720,10 @@ static inline bool spi_nor_needs_sfdp(const struct spi_nor *nor) return !nor->info->size; } +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); +bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr); + #ifdef CONFIG_DEBUG_FS void spi_nor_debugfs_register(struct spi_nor *nor); void spi_nor_debugfs_shutdown(void); diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 14ba1680c3154..288e2866daedb 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -2,6 +2,7 @@ #include <linux/array_size.h> #include <linux/debugfs.h> +#include <linux/math64.h> #include <linux/mtd/spi-nor.h> #include <linux/spi/spi.h> #include <linux/spi/spi-mem.h> @@ -29,6 +30,8 @@ static const char *const snor_f_names[] = { SNOR_F_NAME(RWW), SNOR_F_NAME(ECC), SNOR_F_NAME(NO_WP), + SNOR_F_NAME(SWAP16), + SNOR_F_NAME(HAS_SR2_CMP_BIT6), }; #undef SNOR_F_NAME @@ -77,11 +80,14 @@ static void spi_nor_print_flags(struct seq_file *s, unsigned long flags, static int spi_nor_params_show(struct seq_file *s, void *data) { struct spi_nor *nor = s->private; + u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); 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); @@ -140,12 +146,12 @@ static int spi_nor_params_show(struct seq_file *s, void *data) if (!(nor->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", nor->params->die_erase_opcode, buf); + seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf); } seq_puts(s, "\nsector map\n"); seq_puts(s, " region (in hex) | erase mask | overlaid\n"); - seq_puts(s, " ------------------+------------+----------\n"); + seq_puts(s, " ------------------+------------+---------\n"); for (i = 0; i < erase_map->n_regions; i++) { u64 start = region[i].offset; u64 end = start + region[i].size - 1; @@ -160,10 +166,69 @@ static int spi_nor_params_show(struct seq_file *s, void *data) region[i].overlaid ? "yes" : "no"); } + if (!spi_nor_has_default_locking_ops(nor)) + return 0; + + seq_puts(s, "\nlocked sectors\n"); + seq_puts(s, " region (in hex) | status | #sectors\n"); + seq_puts(s, " ------------------+----------+---------\n"); + + spi_nor_get_locked_range_sr(nor, nor->dfs_sr_cache, &lock_start, &lock_length); + if (!lock_length || lock_length == params->size) { + seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, params->size - 1, + lock_length ? " locked" : "unlocked", + div_u64(params->size, min_prot_len)); + } else if (!lock_start) { + seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_length - 1, + " locked", div_u64(lock_length, min_prot_len)); + seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_length, params->size - 1, + "unlocked", div_u64(params->size - lock_length, min_prot_len)); + } else { + seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_start - 1, + "unlocked", div_u64(lock_start, min_prot_len)); + seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_start, params->size - 1, + " locked", div_u64(lock_length, min_prot_len)); + } + return 0; } DEFINE_SHOW_ATTRIBUTE(spi_nor_params); +static int spi_nor_locked_sectors_map_show(struct seq_file *s, void *data) +{ + struct spi_nor *nor = s->private; + struct spi_nor_flash_parameter *params = nor->params; + u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); + unsigned int sector = 0; + u64 offset = 0; + bool locked; + int i; + + seq_printf(s, "Locked sectors map (x: locked, .: unlocked, unit: %lldkiB)\n", + min_prot_len / 1024); + while (offset < params->size) { + seq_printf(s, " 0x%08llx (#%5d): ", offset, sector); + for (i = 0; i < 64 && offset < params->size; i++) { + locked = spi_nor_is_locked_sr(nor, offset, min_prot_len, + nor->dfs_sr_cache); + if (locked) + seq_puts(s, "x"); + else + seq_puts(s, "."); + + if (((i + 1) % 16) == 0) + seq_puts(s, " "); + + offset += min_prot_len; + sector++; + } + seq_puts(s, "\n"); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(spi_nor_locked_sectors_map); + static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap, struct spi_nor_read_command *cmd) { @@ -249,6 +314,9 @@ void spi_nor_debugfs_register(struct spi_nor *nor) debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops); debugfs_create_file("capabilities", 0444, d, nor, &spi_nor_capabilities_fops); + if (spi_nor_has_default_locking_ops(nor)) + debugfs_create_file("locked-sectors-map", 0444, d, nor, + &spi_nor_locked_sectors_map_fops); } void spi_nor_debugfs_shutdown(void) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 8498c7003d888..65227d989de13 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -674,7 +674,9 @@ static int s25hx_t_late_init(struct spi_nor *nor) params->ready = cypress_nor_sr_ready_and_clear; cypress_nor_ecc_init(nor); - params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + if (params->n_dice > 1) + params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + return 0; } @@ -760,6 +762,9 @@ static int s28hx_t_late_init(struct spi_nor *nor) params->ready = cypress_nor_sr_ready_and_clear; cypress_nor_ecc_init(nor); + if (params->n_dice > 1) + params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + return 0; } diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index e67a81dbb6bf6..235070b215d1e 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -34,7 +34,16 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) return 0; } -static u64 spi_nor_get_min_prot_length_sr(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) + 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; /* @@ -55,13 +64,16 @@ static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) return sector_size; } -static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs, - u64 *len) +void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, + u64 *len) { u64 min_prot_len; - u8 mask = spi_nor_get_sr_bp_mask(nor); + u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); - u8 bp, val = sr & mask; + 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; @@ -69,22 +81,37 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, 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 (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask) - *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; + } } /* @@ -92,7 +119,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs, * (if @locked is false); false otherwise. */ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, - u64 len, u8 sr, bool locked) + u64 len, const u8 *sr, bool locked) { loff_t lock_offs, lock_offs_max, offs_max; u64 lock_len; @@ -113,27 +140,102 @@ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, return (ofs >= lock_offs_max) || (offs_max <= lock_offs); } -static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, u8 sr) +bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr) { return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true); } static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len, - u8 sr) + const u8 *sr) { return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false); } +static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow) +{ + 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) + val = (val & ~SR_BP3) | SR_BP3_BIT6; + + if (val & ~mask) + return -EINVAL; + + sr[0] |= val; + + return 0; +} + +static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, + 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); + if (ret) + return ret; + + /* Build TB field */ + if ((!cmp && !use_top) || (cmp && use_top)) + new_sr[0] |= tb_mask; + + /* Build CMP field */ + if (cmp) + new_sr[1] |= cmp_mask; + + 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); + 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_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; +} + /* * 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 * 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): * @@ -159,20 +261,31 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len, */ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) { - u64 min_prot_len; - int ret, status_old, status_new; - u8 mask = spi_nor_get_sr_bp_mask(nor); - u8 tb_mask = spi_nor_get_sr_tb_mask(nor); - u8 pow, val; + 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 = nor->flags & SNOR_F_HAS_SR_TB, + can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; + int ret; + u8 pow; ret = spi_nor_read_sr(nor, nor->bouncebuf); if (ret) return ret; - status_old = nor->bouncebuf[0]; + 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)) @@ -199,25 +312,47 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) else lock_len = ofs + len; - if (lock_len == nor->params->size) { - val = mask; - } else { - min_prot_len = spi_nor_get_min_prot_length_sr(nor); + if (lock_len == nor->params->size) + pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0); + else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - val = pow << SR_BP_SHIFT; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) - val = (val & ~SR_BP3) | SR_BP3_BIT6; + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false); + if (ret) + return ret; - if (val & ~mask) - return -EINVAL; + /* + * 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; - /* Don't "lock" with no region! */ - if (!(val & mask)) - return -EINVAL; - } + /* + * 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; + } - status_new = (status_old & ~mask & ~tb_mask) | val; + /* 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 @@ -225,20 +360,34 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) * WP# pin hard strapped to GND can be a valid use case. */ if (!(nor->flags & SNOR_F_NO_WP)) - status_new |= SR_SRWD; + best_status_new[0] |= SR_SRWD; - if (!use_top) - status_new |= tb_mask; + spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); + 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 == status_old) + 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 */ - if ((status_new & mask) < (status_old & mask)) + if (len_old && + (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old))) return -EINVAL; - return spi_nor_write_sr_and_check(nor, status_new); + 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, best_status_new); + + return 0; } /* @@ -248,20 +397,31 @@ 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; - int ret, status_old, status_new; - u8 mask = spi_nor_get_sr_bp_mask(nor); - u8 tb_mask = spi_nor_get_sr_tb_mask(nor); - u8 pow, val; + 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 = nor->flags & SNOR_F_HAS_SR_TB, + can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; + int ret; + u8 pow; ret = spi_nor_read_sr(nor, nor->bouncebuf); if (ret) return ret; - status_old = nor->bouncebuf[0]; + 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)) @@ -282,45 +442,86 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) /* Prefer top, if both are valid */ use_top = can_be_top; - /* lock_len: length of region that should remain locked */ - if (use_top) + /* + * lock_len: length of region that should remain locked. + * + * When can_be_top and can_be_bottom booleans are true, both adjacent + * regions are unlocked, thus the entire flash can be unlocked. + */ + if (can_be_top && can_be_bottom) + lock_len = 0; + else if (use_top) lock_len = nor->params->size - (ofs + len); else lock_len = ofs; - if (lock_len == 0) { - val = 0; /* fully unlocked */ - } else { - min_prot_len = spi_nor_get_min_prot_length_sr(nor); + if (lock_len == 0) + pow = 0; /* fully unlocked */ + else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - val = pow << SR_BP_SHIFT; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) - val = (val & ~SR_BP3) | SR_BP3_BIT6; + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false); + if (ret) + return ret; - /* Some power-of-two sizes are not supported */ - if (val & ~mask) - return -EINVAL; - } + /* + * 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; - status_new = (status_old & ~mask & ~tb_mask) | val; + /* + * 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 &= ~SR_SRWD; - - if (!use_top) - status_new |= tb_mask; + best_status_new[0] &= ~SR_SRWD; /* Don't bother if they're the same */ - if (status_new == status_old) + 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 */ - if ((status_new & mask) > (status_old & mask)) + spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); + 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; - return spi_nor_write_sr_and_check(nor, status_new); + 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, best_status_new); + + return 0; } /* @@ -332,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[0]); + 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 = { @@ -352,6 +564,11 @@ void spi_nor_init_default_locking_ops(struct spi_nor *nor) nor->params->locking_ops = &spi_nor_sr_locking_ops; } +bool spi_nor_has_default_locking_ops(struct spi_nor *nor) +{ + return nor->params->locking_ops == &spi_nor_sr_locking_ops; +} + static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, u64 len) { struct spi_nor *nor = mtd_to_spi_nor(mtd); diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index eaa547d36aadf..8ebdbcec0b3fd 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -73,6 +73,26 @@ static const struct spi_nor_fixups w25q256_fixups = { .post_bfpt = w25q256_post_bfpt_fixups, }; +static int +winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + const struct sfdp_bfpt *bfpt) +{ + /* + * W25H02NW, unlike its W25H512NW nor W25H01NW cousins, improperly sets + * the QE BFPT configuration bits, indicating a non readable CR. This is + * 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; + + return 0; +} + +static const struct spi_nor_fixups winbond_rdcr_fixup = { + .post_bfpt = winbond_rdcr_post_bfpt_fixup, +}; + /** * winbond_nor_select_die() - Set active die. * @nor: pointer to 'struct spi_nor'. @@ -348,27 +368,36 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25Q01NWxxIQ */ .id = SNOR_ID(0xef, 0x60, 0x21), - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + .fixups = &winbond_rdcr_fixup, }, { /* W25Q01NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x21), - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { /* W25Q02NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x22), - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + .fixups = &winbond_rdcr_fixup, }, { /* W25H512NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x20), - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { /* W25H01NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x21), - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { /* W25H02NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x22), - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + .fixups = &winbond_rdcr_fixup, }, }; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index cdcfe0fd2e7d6..4b92494827b15 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) */ @@ -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 */ @@ -371,6 +372,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 +411,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; |
