diff mbox series

[U-Boot,U-BOOT,2/3] spi: nor: add support for is25wp256

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

Commit Message

Sagar Shrikant Kadam Aug. 13, 2019, 4:59 p.m. UTC
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(-)

Comments

Bin Meng Aug. 26, 2019, 10:02 a.m. UTC | #1
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 mbox series

Patch

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),
 };
 
 /**