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)