aboutsummaryrefslogtreecommitdiffstats
diff options
authorMark Brown <broonie@kernel.org>2026-05-29 22:36:56 +0100
committerMark Brown <broonie@kernel.org>2026-05-29 22:36:56 +0100
commit7a822a9dd6e98ccec31f0f02bd4db6252ed7f188 (patch)
tree4eeac637b212b37270f2fff28487531e1eef3f74
parentd1304489d366f4f5afa7dd9643344b38bca94976 (diff)
parentdf415c5e1de0f1aeefacb4e6252ff98d38c04437 (diff)
downloadlinux-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.rst170
-rw-r--r--drivers/mtd/spi-nor/Kconfig1
-rw-r--r--drivers/mtd/spi-nor/core.c76
-rw-r--r--drivers/mtd/spi-nor/core.h25
-rw-r--r--drivers/mtd/spi-nor/debugfs.c72
-rw-r--r--drivers/mtd/spi-nor/spansion.c7
-rw-r--r--drivers/mtd/spi-nor/swp.c363
-rw-r--r--drivers/mtd/spi-nor/winbond.c41
-rw-r--r--include/linux/mtd/spi-nor.h7
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 = &params->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;