[U-Boot] [U-BOOT PATCH 2/3] spi: nor: add support for is25wp256
Sagar Shrikant Kadam
sagar.kadam at sifive.com
Tue Aug 13 16:59:30 UTC 2019
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 at gmail.com>
Signed-off-by: Sagar Shrikant Kadam <sagar.kadam at 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
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),
};
/**
--
2.7.4
More information about the U-Boot
mailing list