[LINUX RFC v4 3/4] mtd: spi-nor: add stripe support

From: Naga Sureshkumar Relli
Date: Sun Nov 27 2016 - 03:40:33 EST


This patch adds stripe support and it is needed for GQSPI parallel
configuration mode by:

- Adding required parameters like stripe and shift to spi_nor
structure.
- Initializing all added parameters in spi_nor_scan()
- Updating read_sr() and read_fsr() for getting status from both
flashes
- Increasing page_size, sector_size, erase_size and toatal flash
size as and when required.
- Dividing address by 2
- Updating spi->master->flags for qspi driver to change CS

Signed-off-by: Naga Sureshkumar Relli <nagasure@xxxxxxxxxx>
---
Changes for v4:
- rename isparallel to stripe
Changes for v3:
- No change
Changes for v2:
- Splitted to separate MTD layer changes from SPI core changes
---
drivers/mtd/spi-nor/spi-nor.c | 130 ++++++++++++++++++++++++++++++++----------
include/linux/mtd/spi-nor.h | 2 +
2 files changed, 103 insertions(+), 29 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index d0fc165..4252239 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -22,6 +22,7 @@
#include <linux/of_platform.h>
#include <linux/spi/flash.h>
#include <linux/mtd/spi-nor.h>
+#include <linux/spi/spi.h>

/* Define max times to check status register before we give up. */

@@ -89,15 +90,24 @@ static const struct flash_info *spi_nor_match_id(const char *name);
static int read_sr(struct spi_nor *nor)
{
int ret;
- u8 val;
+ u8 val[2];

- ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val, 1);
- if (ret < 0) {
- pr_err("error %d reading SR\n", (int) ret);
- return ret;
+ if (nor->stripe) {
+ ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val[0], 2);
+ if (ret < 0) {
+ pr_err("error %d reading SR\n", (int) ret);
+ return ret;
+ }
+ val[0] |= val[1];
+ } else {
+ ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val[0], 1);
+ if (ret < 0) {
+ pr_err("error %d reading SR\n", (int) ret);
+ return ret;
+ }
}

- return val;
+ return val[0];
}

/*
@@ -108,15 +118,24 @@ static int read_sr(struct spi_nor *nor)
static int read_fsr(struct spi_nor *nor)
{
int ret;
- u8 val;
+ u8 val[2];

- ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val, 1);
- if (ret < 0) {
- pr_err("error %d reading FSR\n", ret);
- return ret;
+ if (nor->stripe) {
+ ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val[0], 2);
+ if (ret < 0) {
+ pr_err("error %d reading FSR\n", ret);
+ return ret;
+ }
+ val[0] &= val[1];
+ } else {
+ ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val[0], 1);
+ if (ret < 0) {
+ pr_err("error %d reading FSR\n", ret);
+ return ret;
+ }
}

- return val;
+ return val[0];
}

/*
@@ -290,9 +309,16 @@ static int spi_nor_wait_till_ready(struct spi_nor *nor)
*/
static int erase_chip(struct spi_nor *nor)
{
+ u32 ret;
+
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));

- return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0);
+ ret = nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0);
+ if (ret)
+ return ret;
+
+ return ret;
+
}

static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops)
@@ -349,7 +375,7 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct spi_nor *nor = mtd_to_spi_nor(mtd);
- u32 addr, len;
+ u32 addr, len, offset;
uint32_t rem;
int ret;

@@ -399,9 +425,13 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
/* "sector"-at-a-time erase */
} else {
while (len) {
+
write_enable(nor);
+ offset = addr;
+ if (nor->stripe)
+ offset /= 2;

- ret = spi_nor_erase_sector(nor, addr);
+ ret = spi_nor_erase_sector(nor, offset);
if (ret)
goto erase_err;

@@ -525,6 +555,8 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
bool use_top;
int ret;

+ ofs = ofs >> nor->shift;
+
status_old = read_sr(nor);
if (status_old < 0)
return status_old;
@@ -610,6 +642,8 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
bool use_top;
int ret;

+ ofs = ofs >> nor->shift;
+
status_old = read_sr(nor);
if (status_old < 0)
return status_old;
@@ -709,6 +743,8 @@ static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
if (ret)
return ret;

+ ofs = ofs >> nor->shift;
+
ret = nor->flash_lock(nor, ofs, len);

spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK);
@@ -724,6 +760,8 @@ static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
if (ret)
return ret;

+ ofs = ofs >> nor->shift;
+
ret = nor->flash_unlock(nor, ofs, len);

spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK);
@@ -1018,6 +1056,9 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
u8 id[SPI_NOR_MAX_ID_LEN];
const struct flash_info *info;

+ nor->spi->master->flags &= ~(SPI_MASTER_BOTH_CS |
+ SPI_MASTER_DATA_STRIPE);
+
tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN);
if (tmp < 0) {
dev_dbg(nor->dev, "error %d reading JEDEC ID\n", tmp);
@@ -1041,6 +1082,7 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
{
struct spi_nor *nor = mtd_to_spi_nor(mtd);
int ret;
+ u32 offset = from;

dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);

@@ -1049,7 +1091,13 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
return ret;

while (len) {
- ret = nor->read(nor, from, len, buf);
+
+ offset = from;
+
+ if (nor->stripe)
+ offset /= 2;
+
+ ret = nor->read(nor, offset, len, buf);
if (ret == 0) {
/* We shouldn't see 0-length reads */
ret = -EIO;
@@ -1161,6 +1209,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
struct spi_nor *nor = mtd_to_spi_nor(mtd);
size_t page_offset, page_remain, i;
ssize_t ret;
+ u32 offset;

dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);

@@ -1178,9 +1227,13 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
/* the size of data remaining on the first page */
page_remain = min_t(size_t,
nor->page_size - page_offset, len - i);
+ offset = (to + i);
+
+ if (nor->stripe)
+ offset /= 2;

write_enable(nor);
- ret = nor->write(nor, to + i, page_remain, buf + i);
+ ret = nor->write(nor, (offset), page_remain, buf + i);
if (ret < 0)
goto write_err;
written = ret;
@@ -1302,22 +1355,22 @@ static int spi_nor_check(struct spi_nor *nor)

int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
{
- const struct flash_info *info = NULL;
+ struct flash_info *info = NULL;
struct device *dev = nor->dev;
struct mtd_info *mtd = &nor->mtd;
struct device_node *np = spi_nor_get_flash_node(nor);
- int ret;
- int i;
+ struct device_node *np_spi;
+ int ret, i, xlnx_qspi_mode;

ret = spi_nor_check(nor);
if (ret)
return ret;

if (name)
- info = spi_nor_match_id(name);
+ info = (struct flash_info *)spi_nor_match_id(name);
/* Try to auto-detect if chip name wasn't specified or not found */
if (!info)
- info = spi_nor_read_id(nor);
+ info = (struct flash_info *)spi_nor_read_id(nor);
if (IS_ERR_OR_NULL(info))
return -ENOENT;

@@ -1341,7 +1394,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
*/
dev_warn(dev, "found %s, expected %s\n",
jinfo->name, info->name);
- info = jinfo;
+ info = (struct flash_info *)jinfo;
}
}

@@ -1370,6 +1423,27 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
mtd->size = info->sector_size * info->n_sectors;
mtd->_erase = spi_nor_erase;
mtd->_read = spi_nor_read;
+#ifdef CONFIG_OF
+ np_spi = of_get_next_parent(np);
+
+ if (of_property_read_u32(np_spi, "xlnx,qspi-mode",
+ &xlnx_qspi_mode) < 0) {
+ nor->shift = 0;
+ nor->stripe = 0;
+ } else if (xlnx_qspi_mode == 2) {
+ nor->shift = 1;
+ info->sector_size <<= nor->shift;
+ info->page_size <<= nor->shift;
+ mtd->size <<= nor->shift;
+ nor->stripe = 1;
+ nor->spi->master->flags |= (SPI_MASTER_BOTH_CS |
+ SPI_MASTER_DATA_STRIPE);
+ }
+#else
+ /* Default to single */
+ nor->shift = 0;
+ nor->stripe = 0;
+#endif

/* NOR protection support for STmicro/Micron chips and similar */
if (JEDEC_MFR(info) == SNOR_MFR_MICRON ||
@@ -1400,10 +1474,10 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
/* prefer "small sector" erase if possible */
if (info->flags & SECT_4K) {
nor->erase_opcode = SPINOR_OP_BE_4K;
- mtd->erasesize = 4096;
+ mtd->erasesize = 4096 << nor->shift;
} else if (info->flags & SECT_4K_PMC) {
nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
- mtd->erasesize = 4096;
+ mtd->erasesize = 4096 << nor->shift;
} else
#endif
{
@@ -1508,16 +1582,14 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
(long long)mtd->size >> 10);

- dev_dbg(dev,
- "mtd .name = %s, .size = 0x%llx (%lldMiB), "
+ dev_dbg(dev, "mtd .name = %s, .size = 0x%llx (%lldMiB), "
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
mtd->name, (long long)mtd->size, (long long)(mtd->size >> 20),
mtd->erasesize, mtd->erasesize / 1024, mtd->numeraseregions);

if (mtd->numeraseregions)
for (i = 0; i < mtd->numeraseregions; i++)
- dev_dbg(dev,
- "mtd.eraseregions[%d] = { .offset = 0x%llx, "
+ dev_dbg(dev, "mtd.eraseregions[%d] = { .offset = 0x%llx, "
".erasesize = 0x%.8x (%uKiB), "
".numblocks = %d }\n",
i, (long long)mtd->eraseregions[i].offset,
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 84f3ce5..673ec68 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -165,6 +165,8 @@ struct spi_nor {
u8 read_dummy;
u8 program_opcode;
enum read_mode flash_read;
+ bool shift;
+ bool stripe;
bool sst_write_second;
u32 flags;
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
--
2.10.2