2015-12-14 13:11:16

by Ranjit Waghmode

[permalink] [raw]
Subject: [LINUX RFC v3 0/4] spi: add dual parallel mode support in Zynq MPSoC GQSPI controller

This series adds dual parallel mode support for Zynq Ultrascale+
MPSoC GQSPI controller driver.

What is dual parallel mode?
---------------------------
ZynqMP GQSPI controller supports Dual Parallel mode with following functionalities:
1) Supporting two SPI flash memories operating in parallel. 8 I/O lines.
2) Chip selects and clock are shared to both the flash devices
3) This mode is targeted for faster read/write speed and also doubles the size
4) Commands/data can be transmitted/received from both the devices(mirror),
or only upper or only lower flash memory devices.
5) Data arrangement:
With stripe enabled,
Even bytes i.e. 0, 2, 4,... are transmitted on Lower Data Bus
Odd bytes i.e. 1, 3, 5,.. are transmitted on Upper Data Bus.

This series also updated MTD layer files for adding parallel mode support.

1) Added Support for two flashes
2) Support to enable/disable data stripe as and when required.
3) Added required parameters to spi_nor structure. Initialized all
added parameters in spi_nor_scan()
4) Added support for dual parallel in spi_nor_read/write/erase functions by:
a) Increasing page_size, sector_size, erase_size and toatal flash size
as and when required.
b) Dividing address by 2
c) Updating spi->master->flags for qspi driver to change CS
5) Updated read_sr() to get status of both flashes
6) Also updated read_fsr() to get status of both flashes
These all are very high level changes and expected to make an idea clear.

---
V3 Changes:
- Changed couple of comments to remove ambiguity in understanding

V2 Changes:
a) Splitted patches based on logical changes
b) Added error handling for newly added APIs in SPI core
---

Ranjit Waghmode (4):
spi: addng support for data stripe feature in core
mtd: add spi_device instance to spi_nor struct
mtd: spi-nor: add dual parallel mode support
spi: zynqmp: gqspi: add support for dual parallel mode configuration

drivers/mtd/devices/m25p80.c | 1 +
drivers/mtd/spi-nor/spi-nor.c | 89 ++++++++++++++++++++++++++++++++++--------
drivers/spi/spi-zynqmp-gqspi.c | 24 +++++++++++-
drivers/spi/spi.c | 8 ++++
include/linux/mtd/spi-nor.h | 3 ++
include/linux/spi/spi.h | 11 ++++++
6 files changed, 119 insertions(+), 17 deletions(-)

--
2.1.2


2015-12-14 13:11:15

by Ranjit Waghmode

[permalink] [raw]
Subject: [LINUX RFC v3 1/4] spi: addng support for data stripe feature in core

This patch enables data stripe feature in spi core. This feature is
required to support dual parallel mode of ZynqMP GQSPI controller.

To achieve the same an API SPI_MASTER_DATA_STRIPE is added.
With data stripe enabled,
- even bytes i.e. 0, 2, 4,... are transmitted on lower data bus
- odd bytes i.e. 1, 3, 5,.. are transmitted on upper data bus.

To support data stripe; need to assert both chip selects once.
This is achieved throught API SPI_MASTER_BOTH_CS.

Signed-off-by: Ranjit Waghmode <[email protected]>

---
V3 Changes:
- Updated comments for newly added APIs.
- Changed patch description for ease of understanding
V2 Changes:
- Added error handling condition for newly added features
---

drivers/spi/spi.c | 8 ++++++++
include/linux/spi/spi.h | 11 +++++++++++
2 files changed, 19 insertions(+)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 2b0a8ec..930dac3 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -2106,6 +2106,14 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
if (list_empty(&message->transfers))
return -EINVAL;

+ /*
+ * Data stripe option is selected if and only if when
+ * two chips are enabled
+ */
+ if ((master->flags & SPI_MASTER_DATA_STRIPE)
+ && !(master->flags & SPI_MASTER_BOTH_CS))
+ return -EINVAL;
+
/* Half-duplex links include original MicroWire, and ones with
* only one data pin like SPI_3WIRE (switches direction) or where
* either MOSI or MISO is missing. They can also be caused by
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index cce80e6..e83b667 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -424,6 +424,17 @@ struct spi_master {
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
#define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
#define SPI_MASTER_MUST_TX BIT(4) /* requires tx */
+ /* Controller may support data stripe feature when more than one
+ * chips are present.
+ * Setting data stripe will send data in following manner:
+ * -> even bytes i.e. 0, 2, 4,... are transmitted on lower data bus
+ * -> odd bytes i.e. 1, 3, 5,.. are transmitted on upper data bus
+ */
+#define SPI_MASTER_DATA_STRIPE BIT(7) /* support data stripe */
+ /* Controller may support asserting more than one chip select at once.
+ * This flag will enable that feature.
+ */
+#define SPI_MASTER_BOTH_CS BIT(8) /* assert both chip selects */

/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
--
2.1.2

2015-12-14 13:25:56

by Ranjit Waghmode

[permalink] [raw]
Subject: [LINUX RFC v3 2/4] mtd: add spi_device instance to spi_nor struct

This patch adds struct spi_device instacne to the spi_nor structure.

Signed-off-by: Ranjit Waghmode <[email protected]>
---
V3 Changes:
- No change in this patch
V2 Changes:
- This is new patch, basically splitted on request of Mark Brown
---

drivers/mtd/devices/m25p80.c | 1 +
include/linux/mtd/spi-nor.h | 1 +
2 files changed, 2 insertions(+)

diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index fe9ceb7..bab4939 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -204,6 +204,7 @@ static int m25p_probe(struct spi_device *spi)

spi_set_drvdata(spi, flash);
flash->spi = spi;
+ nor->spi = spi;

if (spi->mode & SPI_RX_QUAD)
mode = SPI_NOR_QUAD;
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index c8723b6..872b2b4 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -155,6 +155,7 @@ struct spi_nor {
struct mtd_info mtd;
struct mutex lock;
struct device *dev;
+ struct spi_device *spi;
struct device_node *flash_node;
u32 page_size;
u8 addr_width;
--
2.1.2

2015-12-14 13:27:12

by Ranjit Waghmode

[permalink] [raw]
Subject: [LINUX RFC v3 3/4] mtd: spi-nor: add dual parallel mode support

This patch adds support for dual parallel configuration mode by:

- Adding required parameters like isparallel 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: Ranjit Waghmode <[email protected]>
---
V3 Changes:
- No change in this patch
V2 Changes:
- Splitted to separate MTD layer changes from SPI core changes
---

drivers/mtd/spi-nor/spi-nor.c | 89 +++++++++++++++++++++++++++++++++++--------
include/linux/mtd/spi-nor.h | 2 +
2 files changed, 75 insertions(+), 16 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 4988390..f7a6458 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. */

@@ -82,15 +83,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];
}

/*
@@ -100,16 +110,24 @@ static int read_sr(struct spi_nor *nor)
*/
static int read_fsr(struct spi_nor *nor)
{
- int ret;
- u8 val;
+ int ret, size;
+ u8 val[2];
+
+ size = 1;
+ val[1] = 0xff;

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

- return val;
+ val[0] &= val[1];
+
+ return val[0];
}

/*
@@ -337,6 +355,9 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
if (ret)
return ret;

+ if (nor->isparallel)
+ nor->spi->master->flags |= SPI_MASTER_DATA_STRIPE;
+
/* whole-chip erase? */
if (len == mtd->size) {
unsigned long timeout;
@@ -371,6 +392,8 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
while (len) {
write_enable(nor);

+ addr = addr >> nor->shift;
+
if (nor->erase(nor, addr)) {
ret = -EIO;
goto erase_err;
@@ -392,11 +415,17 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);

+ if (nor->isparallel)
+ nor->spi->master->flags &= ~SPI_MASTER_DATA_STRIPE;
+
return ret;

erase_err:
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
instr->state = MTD_ERASE_FAILED;
+ if (nor->isparallel)
+ nor->spi->master->flags &= ~SPI_MASTER_DATA_STRIPE;
+
return ret;
}

@@ -463,6 +492,8 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
u8 shift = ffs(mask) - 1, pow, val;

+ ofs = ofs >> nor->shift;
+
status_old = read_sr(nor);

/* SPI NOR always locks to the end */
@@ -513,6 +544,8 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
u8 shift = ffs(mask) - 1, pow, val;

+ ofs = ofs >> nor->shift;
+
status_old = read_sr(nor);

/* Cannot unlock; would unlock larger region than requested */
@@ -576,6 +609,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);
@@ -591,6 +626,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);
@@ -884,7 +921,13 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
if (ret)
return ret;

- ret = nor->read(nor, from, len, retlen, buf);
+ if (nor->isparallel)
+ nor->spi->master->flags |= SPI_MASTER_DATA_STRIPE;
+
+ ret = nor->read(nor, from >> nor->shift, len, retlen, buf);
+
+ if (nor->isparallel)
+ nor->spi->master->flags &= ~SPI_MASTER_DATA_STRIPE;

spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
return ret;
@@ -980,11 +1023,11 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,

/* do all the bytes fit onto one page? */
if (page_offset + len <= nor->page_size) {
- nor->write(nor, to, len, retlen, buf);
+ nor->write(nor, to >> nor->shift, len, retlen, buf);
} else {
/* the size of data remaining on the first page */
page_size = nor->page_size - page_offset;
- nor->write(nor, to, page_size, retlen, buf);
+ nor->write(nor, to >> nor->shift, page_size, retlen, buf);

/* write everything in nor->page_size chunks */
for (i = page_size; i < len; i += page_size) {
@@ -998,7 +1041,8 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,

write_enable(nor);

- nor->write(nor, to + i, page_size, retlen, buf + i);
+ nor->write(nor, (to + i) >> nor->shift, page_size,
+ retlen, buf + i);
}
}

@@ -1216,6 +1260,19 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
mtd->_erase = spi_nor_erase;
mtd->_read = spi_nor_read;

+#ifdef CONFIG_OF
+ struct device_node *np_spi = of_get_next_parent(np);
+ u32 is_dual;
+ if (of_property_read_u32(np_spi, "is-dual", &is_dual) > 0) {
+ nor->shift = 1;
+ info->sector_size <<= nor->shift;
+ info->page_size <<= nor->shift;
+ mtd->size <<= nor->shift;
+ nor->isparallel = 1;
+ nor->spi->master->flags |= SPI_MASTER_BOTH_CS;
+ }
+#endif
+
/* NOR protection support for STmicro/Micron chips and similar */
if (JEDEC_MFR(info) == SNOR_MFR_MICRON ||
JEDEC_MFR(info) == SNOR_MFR_WINBOND) {
@@ -1243,10 +1300,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
{
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 872b2b4..145a2b9 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -164,6 +164,8 @@ struct spi_nor {
u8 read_dummy;
u8 program_opcode;
enum read_mode flash_read;
+ bool shift;
+ bool isparallel;
bool sst_write_second;
u32 flags;
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
--
2.1.2

2015-12-14 13:25:35

by Ranjit Waghmode

[permalink] [raw]
Subject: [LINUX RFC v3 4/4] spi: zynqmp: gqspi: add support for dual parallel mode configuration

This patch adds support of dual parallel mode configuration
for Zynq Ultrascale+ MPSoC GQSPI controller driver.

Signed-off-by: Ranjit Waghmode <[email protected]>
---
V3 Changes:
- No change in this patch
V2 Changes:
- No change in this patch
---

drivers/spi/spi-zynqmp-gqspi.c | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c
index f23f36e..7a72781 100644
--- a/drivers/spi/spi-zynqmp-gqspi.c
+++ b/drivers/spi/spi-zynqmp-gqspi.c
@@ -153,6 +153,7 @@ enum mode_type {GQSPI_MODE_IO, GQSPI_MODE_DMA};
* @dma_rx_bytes: Remaining bytes to receive by DMA mode
* @dma_addr: DMA address after mapping the kernel buffer
* @genfifoentry: Used for storing the genfifoentry instruction.
+ * @isinstr: To determine whether the transfer is instruction
* @mode: Defines the mode in which QSPI is operating
*/
struct zynqmp_qspi {
@@ -170,6 +171,7 @@ struct zynqmp_qspi {
u32 dma_rx_bytes;
dma_addr_t dma_addr;
u32 genfifoentry;
+ bool isinstr;
enum mode_type mode;
};

@@ -405,9 +407,20 @@ static void zynqmp_qspi_chipselect(struct spi_device *qspi, bool is_high)
genfifoentry |= GQSPI_GENFIFO_MODE_SPI;
genfifoentry |= xqspi->genfifobus;

+ if (qspi->master->flags & SPI_MASTER_BOTH_CS) {
+ zynqmp_gqspi_selectslave(xqspi,
+ GQSPI_SELECT_FLASH_CS_BOTH,
+ GQSPI_SELECT_FLASH_BUS_BOTH);
+ } else {
+ zynqmp_gqspi_selectslave(xqspi,
+ GQSPI_SELECT_FLASH_CS_LOWER,
+ GQSPI_SELECT_FLASH_BUS_LOWER);
+ }
+
if (!is_high) {
genfifoentry |= xqspi->genfifocs;
genfifoentry |= GQSPI_GENFIFO_CS_SETUP;
+ xqspi->isinstr = true;
} else {
genfifoentry |= GQSPI_GENFIFO_CS_HOLD;
}
@@ -664,6 +677,7 @@ static irqreturn_t zynqmp_qspi_irq(int irq, void *dev_id)
if ((xqspi->bytes_to_receive == 0) && (xqspi->bytes_to_transfer == 0)
&& ((status & GQSPI_IRQ_MASK) == GQSPI_IRQ_MASK)) {
zynqmp_gqspi_write(xqspi, GQSPI_IDR_OFST, GQSPI_ISR_IDR_MASK);
+ xqspi->isinstr = false;
spi_finalize_current_transfer(master);
ret = IRQ_HANDLED;
}
@@ -827,6 +841,9 @@ static int zynqmp_qspi_start_transfer(struct spi_master *master,
genfifoentry |= xqspi->genfifocs;
genfifoentry |= xqspi->genfifobus;

+ if ((!xqspi->isinstr) && (master->flags & SPI_MASTER_DATA_STRIPE))
+ genfifoentry |= GQSPI_GENFIFO_STRIPE;
+
zynqmp_qspi_txrxsetup(xqspi, transfer, &genfifoentry);

if (xqspi->mode == GQSPI_MODE_DMA)
@@ -983,6 +1000,7 @@ static int zynqmp_qspi_probe(struct platform_device *pdev)
struct zynqmp_qspi *xqspi;
struct resource *res;
struct device *dev = &pdev->dev;
+ u32 num_cs;

master = spi_alloc_master(&pdev->dev, sizeof(*xqspi));
if (!master)
@@ -1043,7 +1061,11 @@ static int zynqmp_qspi_probe(struct platform_device *pdev)
goto clk_dis_all;
}

- master->num_chipselect = GQSPI_DEFAULT_NUM_CS;
+ ret = of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs);
+ if (ret < 0)
+ master->num_chipselect = GQSPI_DEFAULT_NUM_CS;
+ else
+ master->num_chipselect = num_cs;

master->setup = zynqmp_qspi_setup;
master->set_cs = zynqmp_qspi_chipselect;
--
2.1.2