diff options
-rw-r--r-- | drivers/mtd/spi-nor/Kconfig | 44 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/atmel.c | 128 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/core.c | 23 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/core.h | 8 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/esmt.c | 2 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/intel.c | 9 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/sst.c | 21 |
7 files changed, 211 insertions, 24 deletions
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index ffc4b380f2b1..24cd25de2b8b 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -24,6 +24,50 @@ config MTD_SPI_NOR_USE_4K_SECTORS Please note that some tools/drivers/filesystems may not work with 4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum). +choice + prompt "Software write protection at boot" + default MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE + +config MTD_SPI_NOR_SWP_DISABLE + bool "Disable SWP on any flashes (legacy behavior)" + help + This option disables the software write protection on any SPI + flashes at boot-up. + + Depending on the flash chip this either clears the block protection + bits or does a "Global Unprotect" command. + + Don't use this if you intent to use the software write protection + of your SPI flash. This is only to keep backwards compatibility. + +config MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE + bool "Disable SWP on flashes w/ volatile protection bits" + help + Some SPI flashes have volatile block protection bits, ie. after a + power-up or a reset the flash is software write protected by + default. + + This option disables the software write protection for these kind + of flashes while keeping it enabled for any other SPI flashes + which have non-volatile write protection bits. + + If the software write protection will be disabled depending on + the flash either the block protection bits are cleared or a + "Global Unprotect" command is issued. + + If you are unsure, select this option. + +config MTD_SPI_NOR_SWP_KEEP + bool "Keep software write protection as is" + help + If you select this option the software write protection of any + SPI flashes will not be changed. If your flash is software write + protected or will be automatically software write protected after + power-up you have to manually unlock it before you are able to + write to it. + +endchoice + source "drivers/mtd/spi-nor/controllers/Kconfig" endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c index deacf87a68a0..1fea5cab492c 100644 --- a/drivers/mtd/spi-nor/atmel.c +++ b/drivers/mtd/spi-nor/atmel.c @@ -8,6 +8,8 @@ #include "core.h" +#define ATMEL_SR_GLOBAL_PROTECT_MASK GENMASK(5, 2) + /* * The Atmel AT25FS010/AT25FS040 parts have some weird configuration for the * block protection bits. We don't support them. But legacy behavior in linux @@ -55,6 +57,104 @@ static const struct spi_nor_fixups atmel_at25fs_fixups = { .default_init = atmel_at25fs_default_init, }; +/** + * atmel_set_global_protection - Do a Global Protect or Unprotect command + * @nor: pointer to 'struct spi_nor' + * @ofs: offset in bytes + * @len: len in bytes + * @is_protect: if true do a Global Protect otherwise it is a Global Unprotect + * + * Return: 0 on success, -error otherwise. + */ +static int atmel_set_global_protection(struct spi_nor *nor, loff_t ofs, + uint64_t len, bool is_protect) +{ + int ret; + u8 sr; + + /* We only support locking the whole flash array */ + if (ofs || len != nor->params->size) + return -EINVAL; + + ret = spi_nor_read_sr(nor, nor->bouncebuf); + if (ret) + return ret; + + sr = nor->bouncebuf[0]; + + /* SRWD bit needs to be cleared, otherwise the protection doesn't change */ + if (sr & SR_SRWD) { + sr &= ~SR_SRWD; + ret = spi_nor_write_sr_and_check(nor, sr); + if (ret) { + dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n"); + return ret; + } + } + + if (is_protect) { + sr |= ATMEL_SR_GLOBAL_PROTECT_MASK; + /* + * Set the SRWD bit again as soon as we are protecting + * anything. This will ensure that the WP# pin is working + * correctly. By doing this we also behave the same as + * spi_nor_sr_lock(), which sets SRWD if any block protection + * is active. + */ + sr |= SR_SRWD; + } else { + sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK; + } + + nor->bouncebuf[0] = sr; + + /* + * We cannot use the spi_nor_write_sr_and_check() because this command + * isn't really setting any bits, instead it is an pseudo command for + * "Global Unprotect" or "Global Protect" + */ + return spi_nor_write_sr(nor, nor->bouncebuf, 1); +} + +static int atmel_global_protect(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return atmel_set_global_protection(nor, ofs, len, true); +} + +static int atmel_global_unprotect(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return atmel_set_global_protection(nor, ofs, len, false); +} + +static int atmel_is_global_protected(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int ret; + + if (ofs >= nor->params->size || (ofs + len) > nor->params->size) + return -EINVAL; + + ret = spi_nor_read_sr(nor, nor->bouncebuf); + if (ret) + return ret; + + return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK); +} + +static const struct spi_nor_locking_ops atmel_global_protection_ops = { + .lock = atmel_global_protect, + .unlock = atmel_global_unprotect, + .is_locked = atmel_is_global_protected, +}; + +static void atmel_global_protection_default_init(struct spi_nor *nor) +{ + nor->params->locking_ops = &atmel_global_protection_ops; +} + +static const struct spi_nor_fixups atmel_global_protection_fixups = { + .default_init = atmel_global_protection_default_init, +}; + static const struct flash_info atmel_parts[] = { /* Atmel -- some are (confusingly) marketed as "DataFlash" */ { "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K | SPI_NOR_HAS_LOCK) @@ -62,18 +162,32 @@ static const struct flash_info atmel_parts[] = { { "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K | SPI_NOR_HAS_LOCK) .fixups = &atmel_at25fs_fixups }, - { "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K | SPI_NOR_HAS_LOCK) }, - { "at25df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) }, - { "at25df321a", INFO(0x1f4701, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) }, - { "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_HAS_LOCK) }, + { "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) + .fixups = &atmel_global_protection_fixups }, + { "at25df321", INFO(0x1f4700, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) + .fixups = &atmel_global_protection_fixups }, + { "at25df321a", INFO(0x1f4701, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) + .fixups = &atmel_global_protection_fixups }, + { "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) + .fixups = &atmel_global_protection_fixups }, { "at25sl321", INFO(0x1f4216, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) }, - { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_HAS_LOCK) }, - { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_HAS_LOCK) }, - { "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) }, + { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) + .fixups = &atmel_global_protection_fixups }, + { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) + .fixups = &atmel_global_protection_fixups }, + { "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) + .fixups = &atmel_global_protection_fixups }, { "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) }, }; diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 53603efa4a4f..20df44b753da 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -377,7 +377,7 @@ int spi_nor_write_disable(struct spi_nor *nor) * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) +int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) { int ret; @@ -861,7 +861,7 @@ int spi_nor_wait_till_ready(struct spi_nor *nor) * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) +int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) { int ret; @@ -3140,6 +3140,8 @@ static void spi_nor_try_unlock_all(struct spi_nor *nor) if (!(nor->flags & SNOR_F_HAS_LOCK)) return; + dev_dbg(nor->dev, "Unprotecting entire flash array\n"); + ret = spi_nor_unlock(&nor->mtd, 0, nor->params->size); if (ret) dev_dbg(nor->dev, "Failed to unlock the entire flash memory array\n"); @@ -3161,7 +3163,20 @@ static int spi_nor_init(struct spi_nor *nor) return err; } - spi_nor_try_unlock_all(nor); + /* + * Some SPI NOR flashes are write protected by default after a power-on + * reset cycle, in order to avoid inadvertent writes during power-up. + * Backward compatibility imposes to unlock the entire flash memory + * array at power-up by default. Depending on the kernel configuration + * (1) do nothing, (2) always unlock the entire flash array or (3) + * unlock the entire flash array only when the software write + * protection bits are volatile. The latter is indicated by + * SNOR_F_SWP_IS_VOLATILE. + */ + 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)) + spi_nor_try_unlock_all(nor); if (nor->addr_width == 4 && nor->read_proto != SNOR_PROTO_8_8_8_DTR && @@ -3460,6 +3475,8 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, nor->flags |= SNOR_F_NO_OP_CHIP_ERASE; if (info->flags & USE_CLSR) nor->flags |= SNOR_F_USE_CLSR; + if (info->flags & SPI_NOR_SWP_IS_VOLATILE) + nor->flags |= SNOR_F_SWP_IS_VOLATILE; if (info->flags & SPI_NOR_4BIT_BP) { nor->flags |= SNOR_F_HAS_4BIT_BP; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index caf8b7def828..d631ee299de3 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -28,6 +28,7 @@ enum spi_nor_option_flags { SNOR_F_HAS_SR_BP3_BIT6 = BIT(13), SNOR_F_IO_MODE_EN_VOLATILE = BIT(14), SNOR_F_SOFT_RESET = BIT(15), + SNOR_F_SWP_IS_VOLATILE = BIT(16), }; struct spi_nor_read_command { @@ -332,6 +333,11 @@ struct flash_info { * available I/O mode via a * volatile bit. */ +#define SPI_NOR_SWP_IS_VOLATILE BIT(22) /* + * Flash has volatile software write + * protection bits. Usually these will + * power-up in a write-protected state. + */ /* Part specific fixup hooks. */ const struct spi_nor_fixups *fixups; @@ -433,6 +439,8 @@ void spi_nor_unlock_and_unprep(struct spi_nor *nor); int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor); int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor); int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor); +int spi_nor_read_sr(struct spi_nor *nor, u8 *sr); +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_xread_sr(struct spi_nor *nor, u8 *sr); diff --git a/drivers/mtd/spi-nor/esmt.c b/drivers/mtd/spi-nor/esmt.c index c93170008118..cfc9218c1053 100644 --- a/drivers/mtd/spi-nor/esmt.c +++ b/drivers/mtd/spi-nor/esmt.c @@ -11,7 +11,7 @@ static const struct flash_info esmt_parts[] = { /* ESMT */ { "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, - SECT_4K | SPI_NOR_HAS_LOCK) }, + SECT_4K | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "f25l32qa", INFO(0x8c4116, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) }, { "f25l64qa", INFO(0x8c4117, 0, 64 * 1024, 128, diff --git a/drivers/mtd/spi-nor/intel.c b/drivers/mtd/spi-nor/intel.c index 6c31bef3fc60..8ece9cceb3cf 100644 --- a/drivers/mtd/spi-nor/intel.c +++ b/drivers/mtd/spi-nor/intel.c @@ -10,9 +10,12 @@ static const struct flash_info intel_parts[] = { /* Intel/Numonyx -- xxxs33b */ - { "160s33b", INFO(0x898911, 0, 64 * 1024, 32, SPI_NOR_HAS_LOCK) }, - { "320s33b", INFO(0x898912, 0, 64 * 1024, 64, SPI_NOR_HAS_LOCK) }, - { "640s33b", INFO(0x898913, 0, 64 * 1024, 128, SPI_NOR_HAS_LOCK) }, + { "160s33b", INFO(0x898911, 0, 64 * 1024, 32, + SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, + { "320s33b", INFO(0x898912, 0, 64 * 1024, 64, + SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, + { "640s33b", INFO(0x898913, 0, 64 * 1024, 128, + SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, }; const struct spi_nor_manufacturer spi_nor_intel = { diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c index 0d9d319f61e6..00e48da0744a 100644 --- a/drivers/mtd/spi-nor/sst.c +++ b/drivers/mtd/spi-nor/sst.c @@ -11,27 +11,28 @@ static const struct flash_info sst_parts[] = { /* SST -- large erase sizes are "overlays", "sectors" are 4K */ { "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25vf064c", INFO(0xbf254b, 0, 64 * 1024, 128, - SECT_4K | SPI_NOR_4BIT_BP | SPI_NOR_HAS_LOCK) }, + SECT_4K | SPI_NOR_4BIT_BP | SPI_NOR_HAS_LOCK | + SPI_NOR_SWP_IS_VOLATILE) }, { "sst25wf512", INFO(0xbf2501, 0, 64 * 1024, 1, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25wf020a", INFO(0x621612, 0, 64 * 1024, 4, SECT_4K | SPI_NOR_HAS_LOCK) }, { "sst25wf040b", INFO(0x621613, 0, 64 * 1024, 8, SECT_4K | SPI_NOR_HAS_LOCK) }, { "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst25wf080", INFO(0xbf2505, 0, 64 * 1024, 16, - SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK) }, + SECT_4K | SST_WRITE | SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) }, { "sst26wf016b", INFO(0xbf2651, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, |