[RFC PATCH 3/5] mtd: spi-nor: Add Dual Parallel and Stacked support for Zynq QSPI

Naga Sureshkumar Relli
2018-03-23T12:23:41Z

This patch adds Xilinx Zynq QSPI dual parallel and stacked support.

Signed-off-by: Naga Sureshkumar Relli <nagasure@xilinx.com>
---
drivers/mtd/spi-nor/spi-nor.c | 241 ++++++++++++++++++++++++++++++++++++++----
include/linux/mtd/spi-nor.h | 2 +
2 files changed, 220 insertions(+), 23 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 7698a92..03aa8c9 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -106,15 +106,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->isparallel) {
+ 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];
}

/*
@@ -125,15 +134,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->isparallel) {
+ 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];
}

/*
@@ -451,7 +469,10 @@ static int write_ear(struct spi_nor *nor, u32 addr)
addr = addr % (u32) mtd->size;
ear = addr >> 24;

- if (ear == nor->curbank)
+ if ((!nor->isstacked) && (ear == nor->curbank))
+ return 0;
+
+ if (nor->isstacked && (mtd->size <= 0x2000000))
return 0;

if (nor->jedec_id == CFI_MFR_AMD)
@@ -480,9 +501,21 @@ static int erase_chip(struct spi_nor *nor)
u32 ret;
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));

+ if (nor->isstacked)
+ nor->spi->master->flags &= ~SPI_MASTER_U_PAGE;
ret = nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0);
if (ret)
return ret;
+ if (nor->isstacked) {
+ /* Wait until previous write command finished */
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ return ret;
+
+ nor->spi->master->flags |= SPI_MASTER_U_PAGE;
+
+ ret = nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0);
+ }

return ret;
}
@@ -619,6 +652,20 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
write_enable(nor);
offset = addr;

+ if (nor->isparallel == 1)
+ offset /= 2;
+
+ if (nor->isstacked == 1) {
+ if (offset >= (mtd->size / 2)) {
+ offset = offset - (mtd->size / 2);
+ nor->spi->master->flags |=
+ SPI_MASTER_U_PAGE;
+ } else {
+ nor->spi->master->flags &=
+ ~SPI_MASTER_U_PAGE;
+ }
+ }
+
if (nor->addr_width == 3) {
/* Update Extended Address Register */
ret = write_ear(nor, offset);
@@ -949,6 +996,16 @@ static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_LOCK);
if (ret)
return ret;
+ if (nor->isparallel == 1)
+ ofs = ofs >> nor->shift;
+
+ if (nor->isstacked == 1) {
+ if (ofs >= (mtd->size / 2)) {
+ ofs = ofs - (mtd->size / 2);
+ nor->spi->master->flags |= SPI_MASTER_U_PAGE;
+ } else
+ nor->spi->master->flags &= ~SPI_MASTER_U_PAGE;
+ }

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

@@ -964,6 +1021,16 @@ static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_UNLOCK);
if (ret)
return ret;
+ if (nor->isparallel == 1)
+ ofs = ofs >> nor->shift;
+
+ if (nor->isstacked == 1) {
+ if (ofs >= (mtd->size / 2)) {
+ ofs = ofs - (mtd->size / 2);
+ nor->spi->master->flags |= SPI_MASTER_U_PAGE;
+ } else
+ nor->spi->master->flags &= ~SPI_MASTER_U_PAGE;
+ }

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

@@ -1355,6 +1422,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;
+ u32 stack_shift = 0;
u32 read_len = 0;
u32 rem_bank_len = 0;
u8 bank;
@@ -1375,8 +1443,25 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
(bank + 1)) - from;
}
offset = from;
+ if (nor->isparallel == 1)
+ offset /= 2;
+
+ if (nor->isstacked == 1) {
+ stack_shift = 1;
+ if (offset >= (mtd->size / 2)) {
+ offset = offset - (mtd->size / 2);
+ nor->spi->master->flags |= SPI_MASTER_U_PAGE;
+ } else {
+ nor->spi->master->flags &= ~SPI_MASTER_U_PAGE;
+ }
+ }

/* Die cross over issue is not handled */
+ if (nor->addr_width == 4) {
+ rem_bank_len = (mtd->size >> stack_shift) -
+ (offset << nor->shift);
+ }
+
if (nor->addr_width == 3)
write_ear(nor, offset);
if (len < rem_bank_len)
@@ -1390,9 +1475,9 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
goto read_err;

if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
- addr = spi_nor_s3an_addr_convert(nor, addr);
+ addr = spi_nor_s3an_addr_convert(nor, offset);

- ret = nor->read(nor, addr, len, buf);
+ ret = nor->read(nor, offset, len, buf);
if (ret == 0) {
/* We shouldn't see 0-length reads */
ret = -EIO;
@@ -1504,6 +1589,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, stack_shift = 0;
u8 bank = 0;
u32 rem_bank_len = 0;
#define OFFSET_16_MB 0x1000000
@@ -1517,7 +1603,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
for (i = 0; i < len; ) {
ssize_t written;
loff_t addr = to + i;
-
+ offset = (to + i);
if (nor->addr_width == 3) {
bank = (u32)to / (OFFSET_16_MB << nor->shift);
rem_bank_len = ((OFFSET_16_MB << nor->shift) *
@@ -1535,26 +1621,53 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
* modulus (do_div()) or not.
*/
if (hweight32(nor->page_size) == 1) {
- page_offset = addr & (nor->page_size - 1);
+ page_offset = offset & (nor->page_size - 1);
} else {
- uint64_t aux = addr;
+ uint64_t aux = offset;

page_offset = do_div(aux, nor->page_size);
}
+ if (nor->isparallel == 1)
+ offset /= 2;
+
+ if (nor->isstacked == 1) {
+ stack_shift = 1;
+ if (offset >= (mtd->size / 2)) {
+ offset = offset - (mtd->size / 2);
+ nor->spi->master->flags |= SPI_MASTER_U_PAGE;
+ } else {
+ nor->spi->master->flags &= ~SPI_MASTER_U_PAGE;
+ }
+ }
/* Die cross over issue is not handled */
+ if (nor->addr_width == 4)
+ rem_bank_len = (mtd->size >> stack_shift) - offset;
if (nor->addr_width == 3)
- write_ear(nor, addr);
- else {
- /* the size of data remaining on the first page */
+ write_ear(nor, offset);
+ if (nor->isstacked == 1) {
+ if (len <= rem_bank_len) {
+ page_remain = min_t(size_t,
+ nor->page_size - page_offset, len - i);
+ } else {
+ /*
+ * the size of data remaining
+ * on the first page
+ */
+ page_remain = rem_bank_len;
+ }
+ } else {
page_remain = min_t(size_t,
- nor->page_size - page_offset, len - i);
+ nor->page_size - page_offset, len - i);
}
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ goto write_err;

if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
addr = spi_nor_s3an_addr_convert(nor, addr);

write_enable(nor);
- ret = nor->write(nor, addr, page_remain, buf + i);
+ ret = nor->write(nor, offset, page_remain, buf + i);
if (ret < 0)
goto write_err;
written = ret;
@@ -2897,6 +3010,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
struct device_node *np_spi;
int ret;
int i;
+ u32 is_dual;

ret = spi_nor_check(nor);
if (ret)
@@ -2965,6 +3079,69 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
mtd->_read = spi_nor_read;
mtd->_resume = spi_nor_resume;

+#ifdef CONFIG_OF
+ np_spi = of_get_next_parent(np);
+ if ((of_property_match_string(np_spi, "compatible",
+ "xlnx,zynq-qspi-1.0") >= 0) ||
+ (of_property_match_string(np_spi, "compatible",
+ "xlnx,zynqmp-qspi-1.0") >= 0)) {
+ if (of_property_read_u32(np_spi, "is-dual",
+ &is_dual) < 0) {
+ /* Default to single if prop not defined */
+ nor->shift = 0;
+ nor->isstacked = 0;
+ nor->isparallel = 0;
+ } else {
+ if (is_dual == 1) {
+ /* dual parallel */
+ nor->shift = 1;
+ info->sector_size <<= nor->shift;
+ info->page_size <<= nor->shift;
+ mtd->size <<= nor->shift;
+ nor->isparallel = 1;
+ nor->isstacked = 0;
+ } else {
+#ifdef CONFIG_SPI_ZYNQ_QSPI_DUAL_STACKED
+ /* dual stacked */
+ nor->shift = 0;
+ mtd->size <<= 1;
+ info->n_sectors <<= 1;
+ nor->isstacked = 1;
+ nor->isparallel = 0;
+#else
+ u32 is_stacked;
+
+ if (of_property_read_u32(np_spi,
+ "is-stacked",
+ &is_stacked) < 0) {
+ is_stacked = 0;
+ }
+ if (is_stacked) {
+ /* dual stacked */
+ nor->shift = 0;
+ mtd->size <<= 1;
+ info->n_sectors <<= 1;
+ nor->isstacked = 1;
+ nor->isparallel = 0;
+ } else {
+ /* single */
+ nor->shift = 0;
+ nor->isstacked = 0;
+ nor->isparallel = 0;
+ }
+#endif
+ }
+ }
+ }
+
+#else
+ /* Default to single */
+ nor->shift = 0;
+ nor->isstacked = 0;
+ nor->isparallel = 0;
+#endif
+
+
/* NOR protection support for STmicro/Micron chips and similar */
if (JEDEC_MFR(info) == SNOR_MFR_MICRON ||
info->flags & SPI_NOR_HAS_LOCK) {
@@ -3063,6 +3240,24 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
info->flags & SPI_NOR_4B_OPCODES)
spi_nor_set_4byte_opcodes(nor, info);
+ else {
+ np_spi = of_get_next_parent(np);
+ if (of_property_match_string(np_spi,
+ "compatible",
+ "xlnx,xps-spi-2.00.a") >= 0) {
+ nor->addr_width = 3;
+ set_4byte(nor, info, 0);
+ } else {
+ set_4byte(nor, info, 1);
+ if (nor->isstacked) {
+ nor->spi->master->flags |=
+ SPI_MASTER_U_PAGE;
+ set_4byte(nor, info, 1);
+ nor->spi->master->flags &=
+ ~SPI_MASTER_U_PAGE;
+ }
+ }
+ }
#ifdef CONFIG_OF
}
#endif
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 64232b1..bdf6433 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -303,6 +303,8 @@ struct spi_nor {
enum spi_nor_protocol reg_proto;
bool sst_write_second;
bool shift;
+ bool isparallel;
+ bool isstacked;
u8 flags;
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];

--
2.7.4