From patchwork Thu Mar 26 11:42:55 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rasmus Villemoes X-Patchwork-Id: 244322 List-Id: U-Boot discussion From: rasmus.villemoes at prevas.dk (Rasmus Villemoes) Date: Thu, 26 Mar 2020 12:42:55 +0100 Subject: [PATCH 1/3] mtd: spi-nor: don't guard stm_*lock* family by preprocessor conditionals In-Reply-To: <20200326114257.1782-1-rasmus.villemoes@prevas.dk> References: <20200326114257.1782-1-rasmus.villemoes@prevas.dk> Message-ID: <20200326114257.1782-2-rasmus.villemoes@prevas.dk> Use C instead of cpp to choose whether the stm_lock and friends get compiled in. This provides better compile testing and reduces the amount of #ifdeffery. All of the previously guarded functions are static, so this does not add anything to the binary in the !(SPI_FLASH_STMICRO or SPI_FLASH_SST) case (and even if they were not static, the linker would eventually remove them). The test of SPI_FLASH_STMICRO or SPI_FLASH_SST is done inside the test for specific manufacturers or SPI_NOR_HAS_LOCK in flags, to prepare for adding support for locking on Macronix flashes. The compiler is perfectly capabable of compiling if (expression with no side effects) { if (0) { } } to nothing at all. Signed-off-by: Rasmus Villemoes --- drivers/mtd/spi/spi-nor-core.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 7b6ad495ac..af83d813fc 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -591,7 +591,6 @@ erase_err: return ret; } -#if defined(CONFIG_SPI_FLASH_STMICRO) || defined(CONFIG_SPI_FLASH_SST) /* Write status register and ensure bits in mask match written values */ static int write_sr_and_check(struct spi_nor *nor, u8 status_new, u8 mask) { @@ -877,7 +876,6 @@ static int stm_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) return stm_is_locked_sr(nor, ofs, len, status); } -#endif /* CONFIG_SPI_FLASH_STMICRO */ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) { @@ -2528,17 +2526,17 @@ int spi_nor_scan(struct spi_nor *nor) mtd->_erase = spi_nor_erase; mtd->_read = spi_nor_read; -#if defined(CONFIG_SPI_FLASH_STMICRO) || defined(CONFIG_SPI_FLASH_SST) /* NOR protection support for STmicro/Micron chips and similar */ if (JEDEC_MFR(info) == SNOR_MFR_ST || JEDEC_MFR(info) == SNOR_MFR_MICRON || JEDEC_MFR(info) == SNOR_MFR_SST || info->flags & SPI_NOR_HAS_LOCK) { - nor->flash_lock = stm_lock; - nor->flash_unlock = stm_unlock; - nor->flash_is_locked = stm_is_locked; + if (IS_ENABLED(CONFIG_SPI_FLASH_STMICRO) || IS_ENABLED(CONFIG_SPI_FLASH_SST)) { + nor->flash_lock = stm_lock; + nor->flash_unlock = stm_unlock; + nor->flash_is_locked = stm_is_locked; + } } -#endif #ifdef CONFIG_SPI_FLASH_SST /* From patchwork Thu Mar 26 11:42:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rasmus Villemoes X-Patchwork-Id: 244324 List-Id: U-Boot discussion From: rasmus.villemoes at prevas.dk (Rasmus Villemoes) Date: Thu, 26 Mar 2020 12:42:56 +0100 Subject: [PATCH 2/3] mtd: spi-nor: add support for locking on Macronix nor flashes In-Reply-To: <20200326114257.1782-1-rasmus.villemoes@prevas.dk> References: <20200326114257.1782-1-rasmus.villemoes@prevas.dk> Message-ID: <20200326114257.1782-3-rasmus.villemoes@prevas.dk> Macronix chips implements locking in (power-of-two multiple of) 64K blocks, not as a fraction of the chip's size. Bit 5 in the status register is not a top/bottom select bit, but instead a fourth value bit, allowing locking between 2^0 and 2^14 64K blocks (so up to 1GiB), either from top or bottom. The top/bottom select is instead done via a bit in the configuration register, which is OTP, so once set to use bottom protect, one cannot use top. On top of that, reading the configuration register uses a different opcode (0x15) than the existing SPINOR_OP_RDCR (0x35). These differences from the stm_* lock family means that most of it needs to be reimplemented. Doing the combined write of the status and configuration registers via write_sr_cr() could be reused, though, so this lifts that function from inside #if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND) - it will get eliminated as dead code if neither of those or CONFIG_SPI_FLASH_MACRONIX are set. Signed-off-by: Rasmus Villemoes --- drivers/mtd/spi/spi-nor-core.c | 258 +++++++++++++++++++++++++++++---- include/linux/mtd/spi-nor.h | 3 + 2 files changed, 231 insertions(+), 30 deletions(-) diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index af83d813fc..40bf2d5de5 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -591,6 +591,229 @@ erase_err: return ret; } +/* + * Write status Register and configuration register with 2 bytes + * The first byte will be written to the status register, while the + * second byte will be written to the configuration register. + * Return negative if error occurred. + */ +static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr) +{ + int ret; + + write_enable(nor); + + ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); + if (ret < 0) { + dev_dbg(nor->dev, + "error while writing configuration register\n"); + return -EINVAL; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret) { + dev_dbg(nor->dev, + "timeout while writing configuration register\n"); + return ret; + } + + return 0; +} + +static int mx_read_cr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDCR_MX, &val, 1); + if (ret < 0) { + dev_dbg(nor->dev, "error %d reading CR\n", ret); + return ret; + } + + return val; +} + +/* + * Macronix flashes do not work by locking some 1/2^k fraction of the + * flash - instead, the BP{0,1,2,3} bits define a number of protected + * 64K blocks. + */ +static void mx_get_locked_range(struct spi_nor *nor, u8 sr, u8 cr, + loff_t *ofs, uint64_t *len) +{ + struct mtd_info *mtd = &nor->mtd; + u8 mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; + int shift = ffs(mask) - 1; + int pow; + + pow = ((sr & mask) >> shift) - 1; + if (pow < 0) { + /* No protection */ + *ofs = 0; + *len = 0; + return; + } + *len = (uint64_t)SZ_64K << pow; + if (*len > mtd->size) + *len = mtd->size; + if (cr & CR_TB_MX) + *ofs = 0; + else + *ofs = mtd->size - *len; +} + +/* + * Return 1 if the entire region is locked (if @locked is true) or unlocked (if + * @locked is false); 0 otherwise + */ +static int mx_check_lock_status(struct spi_nor *nor, loff_t ofs, u64 len, + u8 sr, u8 cr, bool locked) +{ + loff_t lock_offs; + uint64_t lock_len; + + if (!len) + return 1; + + mx_get_locked_range(nor, sr, cr, &lock_offs, &lock_len); + + if (locked) + /* Requested range is a sub-range of locked range */ + return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); + else + /* Requested range does not overlap with locked range */ + return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); +} + +static int mx_lock_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len, bool lock) +{ + struct mtd_info *mtd = &nor->mtd; + int sr, cr, ret; + u8 sr_cr[2]; + u8 mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; + u8 shift = ffs(mask) - 1, val; + loff_t lock_len, blocks; + bool can_be_top, can_be_bottom; + bool use_top; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + cr = mx_read_cr(nor); + if (cr < 0) + return cr; + + /* CR_TB is OTP, so we can't use 'top' protection if that is already set. */ + can_be_top = !(cr & CR_TB_MX); + can_be_bottom = true; + + /* If the whole range is already locked (unlocked), we don't need to do anything */ + if (mx_check_lock_status(nor, ofs, len, sr, cr, lock)) + return 0; + + /* To use 'bottom' ('top') protection, everything below us must be locked (unlocked). */ + if (!mx_check_lock_status(nor, 0, ofs, sr, cr, lock)) { + if (lock) + can_be_bottom = false; + else + can_be_top = false; + } + + /* To use 'top' ('bottom') protection, everything above us must be locked (unlocked). */ + if (!mx_check_lock_status(nor, ofs + len, mtd->size - (ofs + len), sr, cr, lock)) { + if (lock) + can_be_top = false; + else + can_be_bottom = false; + } + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + use_top = can_be_top; + + /* lock_len: length of region that should end up locked */ + if (lock) { + lock_len = use_top ? mtd->size - ofs : ofs + len; + } else { + lock_len = use_top ? mtd->size - (ofs + len) : ofs; + } + + /* lock_len must be a power-of-2 (2^0 .. 2^14) multiple of 64K, or 0 */ + if (lock_len & (SZ_64K - 1)) + return -EINVAL; + + blocks = lock_len / SZ_64K; + if ((blocks != 0 && !is_power_of_2(blocks)) || blocks > 1 << 14) + return -EINVAL; + + /* Compute new values of sr/cr */ + val = blocks ? ilog2(blocks) + 1 : 0; + sr_cr[0] = sr & ~mask; + sr_cr[0] |= val << shift; + /* + * Disallow further writes if WP pin is asserted, but remove + * that bit if we unlocked the whole chip. + */ + if (lock_len) + sr_cr[0] |= SR_SRWD; + else + sr_cr[0] &= ~SR_SRWD; + + sr_cr[1] = cr | (use_top ? 0 : CR_TB_MX); + + /* Don't bother if they're the same */ + if (sr == sr_cr[0] && cr == sr_cr[1]) + return 0; + + ret = write_sr_cr(nor, sr_cr); + if (ret) + return ret; + + /* Check that the bits got written as expected */ + sr = read_sr(nor); + if (sr < 0) + return sr; + + cr = mx_read_cr(nor); + if (cr < 0) + return cr; + + if ((sr & mask) != (sr_cr[0] & mask) || + (cr & CR_TB_MX) != (sr_cr[1] & CR_TB_MX)) + return -EIO; + + return 0; +} + +static int mx_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return mx_lock_unlock(nor, ofs, len, true); +} + +static int mx_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return mx_lock_unlock(nor, ofs, len, false); +} + +static int mx_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int sr, cr; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + cr = mx_read_cr(nor); + if (cr < 0) + return cr; + + return mx_check_lock_status(nor, ofs, len, sr, cr, true); +} + /* Write status register and ensure bits in mask match written values */ static int write_sr_and_check(struct spi_nor *nor, u8 status_new, u8 mask) { @@ -1324,35 +1547,6 @@ static int macronix_quad_enable(struct spi_nor *nor) #endif #if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND) -/* - * Write status Register and configuration register with 2 bytes - * The first byte will be written to the status register, while the - * second byte will be written to the configuration register. - * Return negative if error occurred. - */ -static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr) -{ - int ret; - - write_enable(nor); - - ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); - if (ret < 0) { - dev_dbg(nor->dev, - "error while writing configuration register\n"); - return -EINVAL; - } - - ret = spi_nor_wait_till_ready(nor); - if (ret) { - dev_dbg(nor->dev, - "timeout while writing configuration register\n"); - return ret; - } - - return 0; -} - /** * spansion_read_cr_quad_enable() - set QE bit in Configuration Register. * @nor: pointer to a 'struct spi_nor' @@ -2531,7 +2725,11 @@ int spi_nor_scan(struct spi_nor *nor) JEDEC_MFR(info) == SNOR_MFR_MICRON || JEDEC_MFR(info) == SNOR_MFR_SST || info->flags & SPI_NOR_HAS_LOCK) { - if (IS_ENABLED(CONFIG_SPI_FLASH_STMICRO) || IS_ENABLED(CONFIG_SPI_FLASH_SST)) { + if (IS_ENABLED(CONFIG_SPI_FLASH_MACRONIX) && JEDEC_MFR(info) == SNOR_MFR_MACRONIX) { + nor->flash_lock = mx_lock; + nor->flash_unlock = mx_unlock; + nor->flash_is_locked = mx_is_locked; + } else if (IS_ENABLED(CONFIG_SPI_FLASH_STMICRO) || IS_ENABLED(CONFIG_SPI_FLASH_SST)) { nor->flash_lock = stm_lock; nor->flash_unlock = stm_unlock; nor->flash_is_locked = stm_is_locked; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index ec144a08d8..6ceac009eb 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -62,6 +62,7 @@ #define SPINOR_OP_RDID 0x9f /* Read JEDEC ID */ #define SPINOR_OP_RDSFDP 0x5a /* Read SFDP */ #define SPINOR_OP_RDCR 0x35 /* Read configuration register */ +#define SPINOR_OP_RDCR_MX 0x15 /* Read configuration register (Macronix) */ #define SPINOR_OP_RDFSR 0x70 /* Read flag status register */ #define SPINOR_OP_CLFSR 0x50 /* Clear flag status register */ #define SPINOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ @@ -131,6 +132,7 @@ #define SR_BP0 BIT(2) /* Block protect 0 */ #define SR_BP1 BIT(3) /* Block protect 1 */ #define SR_BP2 BIT(4) /* Block protect 2 */ +#define SR_BP3 BIT(5) /* Block protect 3 (Macronix) */ #define SR_TB BIT(5) /* Top/Bottom protect */ #define SR_SRWD BIT(7) /* SR write protect */ /* Spansion/Cypress specific status bits */ @@ -150,6 +152,7 @@ /* Configuration Register bits. */ #define CR_QUAD_EN_SPAN BIT(1) /* Spansion Quad I/O */ +#define CR_TB_MX BIT(3) /* Macronix Top/Bottom protect */ /* Status Register 2 bits. */ #define SR2_QUAD_EN_BIT7 BIT(7) From patchwork Thu Mar 26 11:42:57 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rasmus Villemoes X-Patchwork-Id: 244323 List-Id: U-Boot discussion From: rasmus.villemoes at prevas.dk (Rasmus Villemoes) Date: Thu, 26 Mar 2020 12:42:57 +0100 Subject: [PATCH 3/3] mtd: spi-nor: set SPI_NOR_HAS_LOCK for Macronix mx25l3205d In-Reply-To: <20200326114257.1782-1-rasmus.villemoes@prevas.dk> References: <20200326114257.1782-1-rasmus.villemoes@prevas.dk> Message-ID: <20200326114257.1782-4-rasmus.villemoes@prevas.dk> This sets the SPI_NOR_HAS_LOCK for the mx25l3205d nor flash, to enable use of the "sf protect" subcommand for that. Reading the data sheets for various other Macronix flashes suggest they should also be able to use the new mx_* lock functions, but this is the chip I've tested them on. Signed-off-by: Rasmus Villemoes --- drivers/mtd/spi/spi-nor-ids.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c index 973b6f86c9..5beb3cb3ad 100644 --- a/drivers/mtd/spi/spi-nor-ids.c +++ b/drivers/mtd/spi/spi-nor-ids.c @@ -143,7 +143,7 @@ const struct flash_info spi_nor_ids[] = { { INFO("mx25l4005a", 0xc22013, 0, 64 * 1024, 8, SECT_4K) }, { INFO("mx25l8005", 0xc22014, 0, 64 * 1024, 16, 0) }, { INFO("mx25l1606e", 0xc22015, 0, 64 * 1024, 32, SECT_4K) }, - { INFO("mx25l3205d", 0xc22016, 0, 64 * 1024, 64, SECT_4K) }, + { INFO("mx25l3205d", 0xc22016, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) }, { INFO("mx25l6405d", 0xc22017, 0, 64 * 1024, 128, SECT_4K) }, { INFO("mx25u2033e", 0xc22532, 0, 64 * 1024, 4, SECT_4K) }, { INFO("mx25u1635e", 0xc22535, 0, 64 * 1024, 32, SECT_4K) },