Message ID | 1565715571-26558-3-git-send-email-sagar.kadam@sifive.com |
---|---|
State | Changes Requested |
Delegated to: | Andes |
Headers | show |
Series | add support for spi-nor device on HiFive Unleashed board | expand |
On Wed, Aug 14, 2019 at 1:23 AM Sagar Shrikant Kadam <sagar.kadam@sifive.com> wrote: > > Enable support for spi nor device(is25wp256) mounted on > HiFive Unleashed Rev A00 board. > > Thanks to Bhargav Shah for porting this patch which is based on > linux patches https://lkml.org/lkml/2019/7/2/859. > > Additionally, set the proper number of sectors in the device id table, > so that the sf probe shows the correct size of the flash device. > Added SPI_NOR_HAS_BP3 bit to indicate that this nor device has BP3 bit > present for the lock/unlock mechanism. > Registered a post bfpt fixup handler for this device as the address width > advertised by the flash during nor scan is not correct. > > This flash is tested for plain SPI mode although it also supports QUAD > I/O mode. > > Signed-off-by: Bhargav Shah <bhargavshah1988@gmail.com> > Signed-off-by: Sagar Shrikant Kadam <sagar.kadam@sifive.com> > --- > board/sifive/fu540/Kconfig | 5 + > drivers/mtd/spi/sf_internal.h | 18 +++ > drivers/mtd/spi/spi-nor-core.c | 340 +++++++++++++++++++++++++++++++++++------ > drivers/mtd/spi/spi-nor-ids.c | 5 + > include/linux/mtd/spi-nor.h | 8 + > 5 files changed, 326 insertions(+), 50 deletions(-) > > diff --git a/board/sifive/fu540/Kconfig b/board/sifive/fu540/Kconfig > index 5d65080..f9d5ec1 100644 > --- a/board/sifive/fu540/Kconfig > +++ b/board/sifive/fu540/Kconfig > @@ -40,6 +40,11 @@ config BOARD_SPECIFIC_OPTIONS # dummy > imply SIFIVE_SERIAL > imply SPI > imply SPI_SIFIVE > + imply SPI_FLASH > + imply SPI_FLASH_ISSI > + imply SPI_FLASH_SFDP_SUPPORT > + imply CMD_MTD > + imply CMD_SF > imply MMC > imply MMC_SPI > imply MMC_BROKEN_CD This should be a separate patch. > diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h > index c5e68d8..6523107 100644 > --- a/drivers/mtd/spi/sf_internal.h > +++ b/drivers/mtd/spi/sf_internal.h > @@ -65,6 +65,13 @@ struct flash_info { > #define NO_CHIP_ERASE BIT(12) /* Chip does not support chip erase */ > #define SPI_NOR_SKIP_SFDP BIT(13) /* Skip parsing of SFDP tables */ > #define USE_CLSR BIT(14) /* use CLSR command */ > +#define SPI_NOR_HAS_BP3 BIT(15) /* > + * Flash SR has block protect bits > + * for lock/unlock purpose, few support > + * BP0-BP2 while few support BP0-BP3. > + * This flag identifies devices that > + * support BP3 bit. > + */ > > #ifdef CONFIG_SPI_FLASH_SFDP_SUPPORT > /* Part specific fixup hooks */ > @@ -72,6 +79,17 @@ struct flash_info { > #endif > }; > > +#ifdef CONFIG_SPI_FLASH_SFDP_SUPPORT > +/* > + * Declare manufacturer specific fixup handlers that > + * can be registered as fixup's in flash info table > + * so as to update any wrong/broken SFDP parameter. > + */ > +#ifdef CONFIG_SPI_FLASH_ISSI > +extern struct spi_nor_fixups is25wp256_fixups; > +#endif > +#endif > + > extern const struct flash_info spi_nor_ids[]; > > #define JEDEC_MFR(info) ((info)->id[0]) > diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c > index 4306d19..46d278d 100644 > --- a/drivers/mtd/spi/spi-nor-core.c > +++ b/drivers/mtd/spi/spi-nor-core.c > @@ -582,7 +582,8 @@ erase_err: > return ret; > } > > -#if defined(CONFIG_SPI_FLASH_STMICRO) || defined(CONFIG_SPI_FLASH_SST) > +#if defined(CONFIG_SPI_FLASH_STMICRO) || defined(CONFIG_SPI_FLASH_SST) || \ > + defined(CONFIG_SPI_FLASH_ISSI) > /* 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) > { > @@ -604,14 +605,45 @@ static int write_sr_and_check(struct spi_nor *nor, u8 status_new, u8 mask) > return ((ret & mask) != (status_new & mask)) ? -EIO : 0; > } > > +/** > + * spi_nor_read_fr() -read function register > + * @nor: pointer to a 'struct spi_nor'. > + * > + * ISSI devices have top/bottom area protection bits selection into function > + * reg. The bits in FR are OTP. So once it's written, it cannot be changed. > + * > + * Return: Value in function register or negative if error. > + */ > +static int spi_nor_read_fr(struct spi_nor *nor) > +{ > + int ret; > + u8 val; > + > + ret = nor->read_reg(nor, SPINOR_OP_RDFR, &val, 1); > + if (ret < 0) { > + pr_err("error %d reading FR\n", ret); > + return ret; > + } > + > + return val; > +} > + > static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, > uint64_t *len) > { > struct mtd_info *mtd = &nor->mtd; > - u8 mask = SR_BP2 | SR_BP1 | SR_BP0; > - int shift = ffs(mask) - 1; > + u8 mask = 0; > + u8 fr = 0; > + int shift = 0; > int pow; > > + if (nor->flags & SNOR_F_HAS_BP3) > + mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; > + else > + mask = SR_BP2 | SR_BP1 | SR_BP0; > + > + shift = ffs(mask) - 1; > + > if (!(sr & mask)) { > /* No protection */ > *ofs = 0; > @@ -619,10 +651,20 @@ static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, > } else { > pow = ((sr & mask) ^ mask) >> shift; > *len = mtd->size >> pow; > - if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) > - *ofs = 0; > - else > - *ofs = mtd->size - *len; > + > + /* ISSI device's have top/bottom select bit in func reg */ > + if (JEDEC_MFR(nor->info) == SNOR_MFR_ISSI) { > + fr = spi_nor_read_fr(nor); > + if (nor->flags & SNOR_F_HAS_SR_TB && fr & FR_TB) > + *ofs = 0; > + else > + *ofs = mtd->size - *len; > + } else { > + if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) > + *ofs = 0; > + else > + *ofs = mtd->size - *len; > + } > } > } > > @@ -649,18 +691,109 @@ static int stm_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, u64 len, > return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); > } > > -static int stm_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > - u8 sr) > +/* > + * check if memory region is locked > + * > + * Returns false if region is locked 0 otherwise. > + */ > +static int spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > + u8 sr) > { > return stm_check_lock_status_sr(nor, ofs, len, sr, true); > } > > -static int stm_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > - u8 sr) > +/* > + * check if memory region is unlocked > + * > + * Returns false if region is locked 0 otherwise. > + */ > +static int spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > + u8 sr) > { > return stm_check_lock_status_sr(nor, ofs, len, sr, false); > } > > +/** > + * spi_nor_select_zone() - Select top area or bottom area to lock/unlock > + * @nor: pointer to a 'struct spi_nor'. > + * @ofs: offset from which to lock memory. > + * @len: number of bytes to unlock. > + * @sr: status register > + * @tb: pointer to top/bottom bool used in caller function > + * @op: zone selection is for lock/unlock operation. 1: lock 0:unlock > + * > + * Select the top area / bottom area pattern to protect memory blocks. > + * > + * Returns negative on errors, 0 on success. > + */ > +static int spi_nor_select_zone(struct spi_nor *nor, loff_t ofs, uint64_t len, > + u8 sr, bool *tb, bool op) > +{ > + int retval; > + bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; > + > + if (op) { > + /* Select for lock zone operation */ > + > + /* > + * If nothing in our range is unlocked, we don't need > + * to do anything. > + */ > + if (spi_nor_is_locked_sr(nor, ofs, len, sr)) > + return 0; > + > + /* > + * If anything below us is unlocked, we can't use 'bottom' > + * protection. > + */ > + if (!spi_nor_is_locked_sr(nor, 0, ofs, sr)) > + can_be_bottom = false; > + > + /* > + * If anything above us is unlocked, we can't use 'top' > + * protection. > + */ > + if (!spi_nor_is_locked_sr(nor, ofs + len, > + nor->mtd.size - (ofs + len), sr)) > + can_be_top = false; > + } else { > + /* Select unlock zone */ > + > + /* > + * If nothing in our range is locked, we don't need to > + * do anything. > + */ > + if (spi_nor_is_unlocked_sr(nor, ofs, len, sr)) > + return 0; > + > + /* > + * If anything below us is locked, we can't use 'top' > + * protection > + */ > + if (!spi_nor_is_unlocked_sr(nor, 0, ofs, sr)) > + can_be_top = false; > + > + /* > + * If anything above us is locked, we can't use 'bottom' > + * protection > + */ > + if (!spi_nor_is_unlocked_sr(nor, ofs + len, > + nor->mtd.size - (ofs + len), sr)) > + can_be_bottom = false; > + } > + > + if (!can_be_bottom && !can_be_top) { > + retval = -EINVAL; > + } else { > + /* Prefer top, if both are valid */ > + *tb = can_be_top; > + retval = 1; > + } > + > + return retval; > +} > + > +#if !defined(CONFIG_SPI_FLASH_ISSI) > /* > * Lock a region of the flash. Compatible with ST Micro and similar flash. > * Supports the block protection bits BP{0,1,2} in the status register > @@ -698,33 +831,19 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) > struct mtd_info *mtd = &nor->mtd; > int status_old, status_new; > u8 mask = SR_BP2 | SR_BP1 | SR_BP0; > - u8 shift = ffs(mask) - 1, pow, val; > + u8 shift = ffs(mask) - 1, pow, val, ret; > loff_t lock_len; > - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; > bool use_top; > > status_old = read_sr(nor); > if (status_old < 0) > return status_old; > > - /* If nothing in our range is unlocked, we don't need to do anything */ > - if (stm_is_locked_sr(nor, ofs, len, status_old)) > + ret = spi_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); > + if (!ret) > return 0; > - > - /* If anything below us is unlocked, we can't use 'bottom' protection */ > - if (!stm_is_locked_sr(nor, 0, ofs, status_old)) > - can_be_bottom = false; > - > - /* If anything above us is unlocked, we can't use 'top' protection */ > - if (!stm_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len), > - status_old)) > - can_be_top = false; > - > - if (!can_be_bottom && !can_be_top) > - return -EINVAL; > - > - /* Prefer top, if both are valid */ > - use_top = can_be_top; > + else if (ret < 0) > + return ret; > > /* lock_len: length of region that should end up locked */ > if (use_top) > @@ -778,33 +897,19 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) > struct mtd_info *mtd = &nor->mtd; > int status_old, status_new; > u8 mask = SR_BP2 | SR_BP1 | SR_BP0; > - u8 shift = ffs(mask) - 1, pow, val; > + u8 shift = ffs(mask) - 1, pow, val, ret; > loff_t lock_len; > - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; > bool use_top; > > status_old = read_sr(nor); > if (status_old < 0) > return status_old; > > - /* If nothing in our range is locked, we don't need to do anything */ > - if (stm_is_unlocked_sr(nor, ofs, len, status_old)) > + ret = spi_nor_select_zone(nor, ofs, len, status_old, &use_top, 0); > + if (!ret) > return 0; > - > - /* If anything below us is locked, we can't use 'top' protection */ > - if (!stm_is_unlocked_sr(nor, 0, ofs, status_old)) > - can_be_top = false; > - > - /* If anything above us is locked, we can't use 'bottom' protection */ > - if (!stm_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len), > - status_old)) > - can_be_bottom = false; > - > - if (!can_be_bottom && !can_be_top) > - return -EINVAL; > - > - /* Prefer top, if both are valid */ > - use_top = can_be_top; > + else if (ret < 0) > + return ret; > > /* lock_len: length of region that should remain locked */ > if (use_top) > @@ -866,8 +971,9 @@ static int stm_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) > if (status < 0) > return status; > > - return stm_is_locked_sr(nor, ofs, len, status); > + return spi_nor_is_locked_sr(nor, ofs, len, status); > } > +#endif /* !CONFIG_SPI_FLASH_ISSI*/ > #endif /* CONFIG_SPI_FLASH_STMICRO */ > > static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) > @@ -1142,6 +1248,105 @@ static int macronix_quad_enable(struct spi_nor *nor) > } > #endif > > +/** > + * issi_lock() - set BP[0123] write-protection. > + * @nor: pointer to a 'struct spi_nor'. > + * @ofs: offset from which to lock memory. > + * @len: number of bytes to unlock. > + * > + * Lock a region of the flash.Implementation is based on stm_lock > + * Supports the block protection bits BP{0,1,2,3} in status register > + * > + * Return: 0 on success, -errno otherwise. > + */ > +static int issi_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) > +{ > + int status_old, status_new, blk_prot; > + u8 mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; > + u8 shift = ffs(mask) - 1; > + u8 pow, ret; > + bool use_top = false; > + loff_t lock_len; > + > + status_old = read_sr(nor); > + > + /* if status reg is Write protected don't update bit protection */ > + if (status_old & SR_SRWD) { > + dev_err(nor->dev, > + "SR is write protected, can't update BP bits...\n"); > + return -EINVAL; > + } > + > + ret = spi_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); > + if (!ret) > + /* Older protected blocks include the new requested block's */ > + return 0; > + else if (ret < 0) > + return ret; > + > + /* lock_len: length of region that should end up locked */ > + if (use_top) > + lock_len = nor->mtd.size - ofs; > + else > + lock_len = ofs + len; > + > + pow = order_base_2(lock_len); > + blk_prot = mask & (((pow + 1) & 0xf) << shift); > + if (lock_len <= 0) { > + dev_err(nor->dev, "invalid Length to protect"); > + return -EINVAL; > + } > + > + status_new = status_old | blk_prot; > + if (status_old == status_new) > + return 0; > + > + return write_sr_and_check(nor, status_new, mask); > +} > + > +/** > + * issi_unlock() - clear BP[0123] write-protection. > + * @nor: pointer to a 'struct spi_nor'. > + * @ofs: offset from which to unlock memory. > + * @len: number of bytes to unlock. > + * > + * Bits [2345] of the Status Register are BP[0123]. > + * ISSI chips use a different block protection scheme than other chips. > + * Just disable the write-protect unilaterally. > + * > + * Return: 0 on success, -errno otherwise. > + */ > +static int issi_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) > +{ > + int ret, val; > + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3; > + > + val = read_sr(nor); > + if (val < 0) > + return val; > + if (!(val & mask)) > + return 0; > + > + write_enable(nor); > + > + write_sr(nor, val & ~mask); > + > + ret = spi_nor_wait_till_ready(nor); > + if (ret) > + return ret; > + > + ret = read_sr(nor); > + if (!(ret & mask)) { > + dev_info(nor->dev, "ISSI block protect bits cleared SR: 0x%x\n", > + ret); > + ret = 0; > + } else { > + dev_err(nor->dev, "ISSI block protect bits not cleared\n"); > + ret = -EINVAL; > + } > + return ret; > +} > + > #if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND) > /* > * Write status Register and configuration register with 2 bytes > @@ -1649,6 +1854,28 @@ spi_nor_post_bfpt_fixups(struct spi_nor *nor, > return 0; > } > > +static int is25wp256_post_bfpt_fixups(struct spi_nor *nor, > + const struct sfdp_parameter_header > + *bfpt_header, > + const struct sfdp_bfpt *bfpt, > + struct spi_nor_flash_parameter *params) > + > +{ > + /* IS25WP256 supports 4B opcodes, but the BFPT advertises a > + * BFPT_DWORD1_ADDRESS_BYTES_3_ONLY address width. > + * Overwrite the address width advertised by the BFPT. > + */ > + if ((bfpt->dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) == > + BFPT_DWORD1_ADDRESS_BYTES_3_ONLY) > + nor->addr_width = 4; > + > + return 0; > +} > + > +struct spi_nor_fixups is25wp256_fixups = { > + .post_bfpt = is25wp256_post_bfpt_fixups, > +}; > + > /** > * spi_nor_parse_bfpt() - read and parse the Basic Flash Parameter Table. > * @nor: pointer to a 'struct spi_nor' > @@ -2318,6 +2545,16 @@ int spi_nor_scan(struct spi_nor *nor) > mtd->_erase = spi_nor_erase; > mtd->_read = spi_nor_read; > > +#if defined(CONFIG_SPI_FLASH_ISSI) > + /* NOR protection support for ISSI chips */ > + if (JEDEC_MFR(info) == SNOR_MFR_ISSI && > + info->flags & SPI_NOR_HAS_LOCK && > + info->flags & SPI_NOR_HAS_BP3) { > + nor->flash_lock = issi_lock; > + nor->flash_unlock = issi_unlock; > + } > +#endif > + > #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 || > @@ -2347,6 +2584,8 @@ int spi_nor_scan(struct spi_nor *nor) > if (info->flags & USE_CLSR) > nor->flags |= SNOR_F_USE_CLSR; > > + if (info->flags & SPI_NOR_HAS_BP3) > + nor->flags |= SNOR_F_HAS_BP3; > if (info->flags & SPI_NOR_NO_ERASE) > mtd->flags |= MTD_NO_ERASE; > > @@ -2377,6 +2616,7 @@ int spi_nor_scan(struct spi_nor *nor) > /* enable 4-byte addressing if the device exceeds 16MiB */ > nor->addr_width = 4; > if (JEDEC_MFR(info) == SNOR_MFR_SPANSION || > + JEDEC_MFR(info) == SNOR_MFR_ISSI || > info->flags & SPI_NOR_4B_OPCODES) > spi_nor_set_4byte_opcodes(nor, info); > #else > diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c > index a3920ba..0d97aa9 100644 > --- a/drivers/mtd/spi/spi-nor-ids.c > +++ b/drivers/mtd/spi/spi-nor-ids.c > @@ -128,6 +128,11 @@ const struct flash_info spi_nor_ids[] = { > SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, > { INFO("is25wp128", 0x9d7018, 0, 64 * 1024, 256, > SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, > + { INFO("is25wp256", 0x9d7019, 0, 64 * 1024, 512, > + SECT_4K | SPI_NOR_4B_OPCODES | SPI_NOR_HAS_LOCK | > + SPI_NOR_HAS_TB | SPI_NOR_HAS_BP3) > + .fixups = &is25wp256_fixups Updating spi-nor-ids.c should be a separate patch following the spi-nor core changes. > + }, > #endif > #ifdef CONFIG_SPI_FLASH_MACRONIX /* MACRONIX */ > /* Macronix */ > diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h > index 88e80af..2fe7812 100644 > --- a/include/linux/mtd/spi-nor.h > +++ b/include/linux/mtd/spi-nor.h > @@ -26,6 +26,7 @@ > #define SNOR_MFR_SPANSION CFI_MFR_AMD > #define SNOR_MFR_SST CFI_MFR_SST > #define SNOR_MFR_WINBOND 0xef /* Also used by some Spansion */ > +#define SNOR_MFR_ISSI 0x9d > > /* > * Note on opcode nomenclature: some opcodes have a format like > @@ -39,6 +40,8 @@ > #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_RDFR 0x48 /* Read Function register */ > +#define SPINOR_OP_WRFR 0x42 /* Write Function register 1 byte */ > #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) */ > @@ -119,6 +122,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 */ > #define SR_TB BIT(5) /* Top/Bottom protect */ > #define SR_SRWD BIT(7) /* SR write protect */ > /* Spansion/Cypress specific status bits */ > @@ -130,6 +134,9 @@ > /* Enhanced Volatile Configuration Register bits */ > #define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */ > > +/* Function register bit */ > +#define FR_TB BIT(1) /*ISSI: Top/Bottom protect */ > + > /* Flag Status Register bits */ > #define FSR_READY BIT(7) /* Device status, 0 = Busy, 1 = Ready */ > #define FSR_E_ERR BIT(5) /* Erase operation status */ > @@ -234,6 +241,7 @@ enum spi_nor_option_flags { > SNOR_F_READY_XSR_RDY = BIT(4), > SNOR_F_USE_CLSR = BIT(5), > SNOR_F_BROKEN_RESET = BIT(6), > + SNOR_F_HAS_BP3 = BIT(7), > }; > > /** Regards, Bin
diff --git a/board/sifive/fu540/Kconfig b/board/sifive/fu540/Kconfig index 5d65080..f9d5ec1 100644 --- a/board/sifive/fu540/Kconfig +++ b/board/sifive/fu540/Kconfig @@ -40,6 +40,11 @@ config BOARD_SPECIFIC_OPTIONS # dummy imply SIFIVE_SERIAL imply SPI imply SPI_SIFIVE + imply SPI_FLASH + imply SPI_FLASH_ISSI + imply SPI_FLASH_SFDP_SUPPORT + imply CMD_MTD + imply CMD_SF imply MMC imply MMC_SPI imply MMC_BROKEN_CD diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index c5e68d8..6523107 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -65,6 +65,13 @@ struct flash_info { #define NO_CHIP_ERASE BIT(12) /* Chip does not support chip erase */ #define SPI_NOR_SKIP_SFDP BIT(13) /* Skip parsing of SFDP tables */ #define USE_CLSR BIT(14) /* use CLSR command */ +#define SPI_NOR_HAS_BP3 BIT(15) /* + * Flash SR has block protect bits + * for lock/unlock purpose, few support + * BP0-BP2 while few support BP0-BP3. + * This flag identifies devices that + * support BP3 bit. + */ #ifdef CONFIG_SPI_FLASH_SFDP_SUPPORT /* Part specific fixup hooks */ @@ -72,6 +79,17 @@ struct flash_info { #endif }; +#ifdef CONFIG_SPI_FLASH_SFDP_SUPPORT +/* + * Declare manufacturer specific fixup handlers that + * can be registered as fixup's in flash info table + * so as to update any wrong/broken SFDP parameter. + */ +#ifdef CONFIG_SPI_FLASH_ISSI +extern struct spi_nor_fixups is25wp256_fixups; +#endif +#endif + extern const struct flash_info spi_nor_ids[]; #define JEDEC_MFR(info) ((info)->id[0]) diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 4306d19..46d278d 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -582,7 +582,8 @@ erase_err: return ret; } -#if defined(CONFIG_SPI_FLASH_STMICRO) || defined(CONFIG_SPI_FLASH_SST) +#if defined(CONFIG_SPI_FLASH_STMICRO) || defined(CONFIG_SPI_FLASH_SST) || \ + defined(CONFIG_SPI_FLASH_ISSI) /* 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) { @@ -604,14 +605,45 @@ static int write_sr_and_check(struct spi_nor *nor, u8 status_new, u8 mask) return ((ret & mask) != (status_new & mask)) ? -EIO : 0; } +/** + * spi_nor_read_fr() -read function register + * @nor: pointer to a 'struct spi_nor'. + * + * ISSI devices have top/bottom area protection bits selection into function + * reg. The bits in FR are OTP. So once it's written, it cannot be changed. + * + * Return: Value in function register or negative if error. + */ +static int spi_nor_read_fr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDFR, &val, 1); + if (ret < 0) { + pr_err("error %d reading FR\n", ret); + return ret; + } + + return val; +} + static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, uint64_t *len) { struct mtd_info *mtd = &nor->mtd; - u8 mask = SR_BP2 | SR_BP1 | SR_BP0; - int shift = ffs(mask) - 1; + u8 mask = 0; + u8 fr = 0; + int shift = 0; int pow; + if (nor->flags & SNOR_F_HAS_BP3) + mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; + else + mask = SR_BP2 | SR_BP1 | SR_BP0; + + shift = ffs(mask) - 1; + if (!(sr & mask)) { /* No protection */ *ofs = 0; @@ -619,10 +651,20 @@ static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, } else { pow = ((sr & mask) ^ mask) >> shift; *len = mtd->size >> pow; - if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) - *ofs = 0; - else - *ofs = mtd->size - *len; + + /* ISSI device's have top/bottom select bit in func reg */ + if (JEDEC_MFR(nor->info) == SNOR_MFR_ISSI) { + fr = spi_nor_read_fr(nor); + if (nor->flags & SNOR_F_HAS_SR_TB && fr & FR_TB) + *ofs = 0; + else + *ofs = mtd->size - *len; + } else { + if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) + *ofs = 0; + else + *ofs = mtd->size - *len; + } } } @@ -649,18 +691,109 @@ static int stm_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, u64 len, return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); } -static int stm_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, - u8 sr) +/* + * check if memory region is locked + * + * Returns false if region is locked 0 otherwise. + */ +static int spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) { return stm_check_lock_status_sr(nor, ofs, len, sr, true); } -static int stm_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, - u8 sr) +/* + * check if memory region is unlocked + * + * Returns false if region is locked 0 otherwise. + */ +static int spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) { return stm_check_lock_status_sr(nor, ofs, len, sr, false); } +/** + * spi_nor_select_zone() - Select top area or bottom area to lock/unlock + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * @sr: status register + * @tb: pointer to top/bottom bool used in caller function + * @op: zone selection is for lock/unlock operation. 1: lock 0:unlock + * + * Select the top area / bottom area pattern to protect memory blocks. + * + * Returns negative on errors, 0 on success. + */ +static int spi_nor_select_zone(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr, bool *tb, bool op) +{ + int retval; + bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; + + if (op) { + /* Select for lock zone operation */ + + /* + * If nothing in our range is unlocked, we don't need + * to do anything. + */ + if (spi_nor_is_locked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is unlocked, we can't use 'bottom' + * protection. + */ + if (!spi_nor_is_locked_sr(nor, 0, ofs, sr)) + can_be_bottom = false; + + /* + * If anything above us is unlocked, we can't use 'top' + * protection. + */ + if (!spi_nor_is_locked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_top = false; + } else { + /* Select unlock zone */ + + /* + * If nothing in our range is locked, we don't need to + * do anything. + */ + if (spi_nor_is_unlocked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is locked, we can't use 'top' + * protection + */ + if (!spi_nor_is_unlocked_sr(nor, 0, ofs, sr)) + can_be_top = false; + + /* + * If anything above us is locked, we can't use 'bottom' + * protection + */ + if (!spi_nor_is_unlocked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_bottom = false; + } + + if (!can_be_bottom && !can_be_top) { + retval = -EINVAL; + } else { + /* Prefer top, if both are valid */ + *tb = can_be_top; + retval = 1; + } + + return retval; +} + +#if !defined(CONFIG_SPI_FLASH_ISSI) /* * Lock a region of the flash. Compatible with ST Micro and similar flash. * Supports the block protection bits BP{0,1,2} in the status register @@ -698,33 +831,19 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) struct mtd_info *mtd = &nor->mtd; int status_old, status_new; u8 mask = SR_BP2 | SR_BP1 | SR_BP0; - u8 shift = ffs(mask) - 1, pow, val; + u8 shift = ffs(mask) - 1, pow, val, ret; loff_t lock_len; - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; status_old = read_sr(nor); if (status_old < 0) return status_old; - /* If nothing in our range is unlocked, we don't need to do anything */ - if (stm_is_locked_sr(nor, ofs, len, status_old)) + ret = spi_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); + if (!ret) return 0; - - /* If anything below us is unlocked, we can't use 'bottom' protection */ - if (!stm_is_locked_sr(nor, 0, ofs, status_old)) - can_be_bottom = false; - - /* If anything above us is unlocked, we can't use 'top' protection */ - if (!stm_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len), - status_old)) - can_be_top = false; - - if (!can_be_bottom && !can_be_top) - return -EINVAL; - - /* Prefer top, if both are valid */ - use_top = can_be_top; + else if (ret < 0) + return ret; /* lock_len: length of region that should end up locked */ if (use_top) @@ -778,33 +897,19 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) struct mtd_info *mtd = &nor->mtd; int status_old, status_new; u8 mask = SR_BP2 | SR_BP1 | SR_BP0; - u8 shift = ffs(mask) - 1, pow, val; + u8 shift = ffs(mask) - 1, pow, val, ret; loff_t lock_len; - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; status_old = read_sr(nor); if (status_old < 0) return status_old; - /* If nothing in our range is locked, we don't need to do anything */ - if (stm_is_unlocked_sr(nor, ofs, len, status_old)) + ret = spi_nor_select_zone(nor, ofs, len, status_old, &use_top, 0); + if (!ret) return 0; - - /* If anything below us is locked, we can't use 'top' protection */ - if (!stm_is_unlocked_sr(nor, 0, ofs, status_old)) - can_be_top = false; - - /* If anything above us is locked, we can't use 'bottom' protection */ - if (!stm_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len), - status_old)) - can_be_bottom = false; - - if (!can_be_bottom && !can_be_top) - return -EINVAL; - - /* Prefer top, if both are valid */ - use_top = can_be_top; + else if (ret < 0) + return ret; /* lock_len: length of region that should remain locked */ if (use_top) @@ -866,8 +971,9 @@ static int stm_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) if (status < 0) return status; - return stm_is_locked_sr(nor, ofs, len, status); + return spi_nor_is_locked_sr(nor, ofs, len, status); } +#endif /* !CONFIG_SPI_FLASH_ISSI*/ #endif /* CONFIG_SPI_FLASH_STMICRO */ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) @@ -1142,6 +1248,105 @@ static int macronix_quad_enable(struct spi_nor *nor) } #endif +/** + * issi_lock() - set BP[0123] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * + * Lock a region of the flash.Implementation is based on stm_lock + * Supports the block protection bits BP{0,1,2,3} in status register + * + * Return: 0 on success, -errno otherwise. + */ +static int issi_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int status_old, status_new, blk_prot; + u8 mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; + u8 shift = ffs(mask) - 1; + u8 pow, ret; + bool use_top = false; + loff_t lock_len; + + status_old = read_sr(nor); + + /* if status reg is Write protected don't update bit protection */ + if (status_old & SR_SRWD) { + dev_err(nor->dev, + "SR is write protected, can't update BP bits...\n"); + return -EINVAL; + } + + ret = spi_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); + if (!ret) + /* Older protected blocks include the new requested block's */ + return 0; + else if (ret < 0) + return ret; + + /* lock_len: length of region that should end up locked */ + if (use_top) + lock_len = nor->mtd.size - ofs; + else + lock_len = ofs + len; + + pow = order_base_2(lock_len); + blk_prot = mask & (((pow + 1) & 0xf) << shift); + if (lock_len <= 0) { + dev_err(nor->dev, "invalid Length to protect"); + return -EINVAL; + } + + status_new = status_old | blk_prot; + if (status_old == status_new) + return 0; + + return write_sr_and_check(nor, status_new, mask); +} + +/** + * issi_unlock() - clear BP[0123] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to unlock memory. + * @len: number of bytes to unlock. + * + * Bits [2345] of the Status Register are BP[0123]. + * ISSI chips use a different block protection scheme than other chips. + * Just disable the write-protect unilaterally. + * + * Return: 0 on success, -errno otherwise. + */ +static int issi_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int ret, val; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3; + + val = read_sr(nor); + if (val < 0) + return val; + if (!(val & mask)) + return 0; + + write_enable(nor); + + write_sr(nor, val & ~mask); + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = read_sr(nor); + if (!(ret & mask)) { + dev_info(nor->dev, "ISSI block protect bits cleared SR: 0x%x\n", + ret); + ret = 0; + } else { + dev_err(nor->dev, "ISSI block protect bits not cleared\n"); + ret = -EINVAL; + } + return ret; +} + #if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND) /* * Write status Register and configuration register with 2 bytes @@ -1649,6 +1854,28 @@ spi_nor_post_bfpt_fixups(struct spi_nor *nor, return 0; } +static int is25wp256_post_bfpt_fixups(struct spi_nor *nor, + const struct sfdp_parameter_header + *bfpt_header, + const struct sfdp_bfpt *bfpt, + struct spi_nor_flash_parameter *params) + +{ + /* IS25WP256 supports 4B opcodes, but the BFPT advertises a + * BFPT_DWORD1_ADDRESS_BYTES_3_ONLY address width. + * Overwrite the address width advertised by the BFPT. + */ + if ((bfpt->dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) == + BFPT_DWORD1_ADDRESS_BYTES_3_ONLY) + nor->addr_width = 4; + + return 0; +} + +struct spi_nor_fixups is25wp256_fixups = { + .post_bfpt = is25wp256_post_bfpt_fixups, +}; + /** * spi_nor_parse_bfpt() - read and parse the Basic Flash Parameter Table. * @nor: pointer to a 'struct spi_nor' @@ -2318,6 +2545,16 @@ int spi_nor_scan(struct spi_nor *nor) mtd->_erase = spi_nor_erase; mtd->_read = spi_nor_read; +#if defined(CONFIG_SPI_FLASH_ISSI) + /* NOR protection support for ISSI chips */ + if (JEDEC_MFR(info) == SNOR_MFR_ISSI && + info->flags & SPI_NOR_HAS_LOCK && + info->flags & SPI_NOR_HAS_BP3) { + nor->flash_lock = issi_lock; + nor->flash_unlock = issi_unlock; + } +#endif + #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 || @@ -2347,6 +2584,8 @@ int spi_nor_scan(struct spi_nor *nor) if (info->flags & USE_CLSR) nor->flags |= SNOR_F_USE_CLSR; + if (info->flags & SPI_NOR_HAS_BP3) + nor->flags |= SNOR_F_HAS_BP3; if (info->flags & SPI_NOR_NO_ERASE) mtd->flags |= MTD_NO_ERASE; @@ -2377,6 +2616,7 @@ int spi_nor_scan(struct spi_nor *nor) /* enable 4-byte addressing if the device exceeds 16MiB */ nor->addr_width = 4; if (JEDEC_MFR(info) == SNOR_MFR_SPANSION || + JEDEC_MFR(info) == SNOR_MFR_ISSI || info->flags & SPI_NOR_4B_OPCODES) spi_nor_set_4byte_opcodes(nor, info); #else diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c index a3920ba..0d97aa9 100644 --- a/drivers/mtd/spi/spi-nor-ids.c +++ b/drivers/mtd/spi/spi-nor-ids.c @@ -128,6 +128,11 @@ const struct flash_info spi_nor_ids[] = { SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("is25wp128", 0x9d7018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { INFO("is25wp256", 0x9d7019, 0, 64 * 1024, 512, + SECT_4K | SPI_NOR_4B_OPCODES | SPI_NOR_HAS_LOCK | + SPI_NOR_HAS_TB | SPI_NOR_HAS_BP3) + .fixups = &is25wp256_fixups + }, #endif #ifdef CONFIG_SPI_FLASH_MACRONIX /* MACRONIX */ /* Macronix */ diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 88e80af..2fe7812 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -26,6 +26,7 @@ #define SNOR_MFR_SPANSION CFI_MFR_AMD #define SNOR_MFR_SST CFI_MFR_SST #define SNOR_MFR_WINBOND 0xef /* Also used by some Spansion */ +#define SNOR_MFR_ISSI 0x9d /* * Note on opcode nomenclature: some opcodes have a format like @@ -39,6 +40,8 @@ #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_RDFR 0x48 /* Read Function register */ +#define SPINOR_OP_WRFR 0x42 /* Write Function register 1 byte */ #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) */ @@ -119,6 +122,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 */ #define SR_TB BIT(5) /* Top/Bottom protect */ #define SR_SRWD BIT(7) /* SR write protect */ /* Spansion/Cypress specific status bits */ @@ -130,6 +134,9 @@ /* Enhanced Volatile Configuration Register bits */ #define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */ +/* Function register bit */ +#define FR_TB BIT(1) /*ISSI: Top/Bottom protect */ + /* Flag Status Register bits */ #define FSR_READY BIT(7) /* Device status, 0 = Busy, 1 = Ready */ #define FSR_E_ERR BIT(5) /* Erase operation status */ @@ -234,6 +241,7 @@ enum spi_nor_option_flags { SNOR_F_READY_XSR_RDY = BIT(4), SNOR_F_USE_CLSR = BIT(5), SNOR_F_BROKEN_RESET = BIT(6), + SNOR_F_HAS_BP3 = BIT(7), }; /**