Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1285355 > unrolled thread
| Started by | Cyrille Pitchen <cyrille.pitchen@atmel.com> |
|---|---|
| First post | 2015-12-07 15:10 +0100 |
| Last post | 2015-12-08 15:40 +0100 |
| Articles | 14 — 6 participants |
Back to article view | Back to linux.kernel
[PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller Cyrille Pitchen <cyrille.pitchen@atmel.com> - 2015-12-07 15:10 +0100
[PATCH linux-next 3/5] mtd: m25p80: add support of dual and quad spi protocols to all commands Cyrille Pitchen <cyrille.pitchen@atmel.com> - 2015-12-07 15:20 +0100
[PATCH linux-next 5/5] mtd: atmel-quadspi: add driver for Atmel QSPI controller Cyrille Pitchen <cyrille.pitchen@atmel.com> - 2015-12-07 15:20 +0100
[PATCH] mtd: atmel-quadspi: fix odd_ptr_err.cocci warnings kbuild test robot <lkp@intel.com> - 2015-12-07 16:30 +0100
[PATCH] mtd: atmel-quadspi: fix compare_const_fl.cocci warnings kbuild test robot <lkp@intel.com> - 2015-12-07 16:30 +0100
Re: [PATCH linux-next 5/5] mtd: atmel-quadspi: add driver for Atmel QSPI controller Nicolas Ferre <nicolas.ferre@atmel.com> - 2015-12-11 16:00 +0100
[PATCH linux-next 4/5] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver Cyrille Pitchen <cyrille.pitchen@atmel.com> - 2015-12-07 15:20 +0100
Re: [PATCH linux-next 4/5] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver Rob Herring <robh@kernel.org> - 2015-12-09 04:20 +0100
Re: [PATCH linux-next 4/5] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver Nicolas Ferre <nicolas.ferre@atmel.com> - 2015-12-11 10:30 +0100
Re: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller Brian Norris <computersforpeace@gmail.com> - 2015-12-07 20:40 +0100
RE: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller Bean Huo 霍斌斌 (beanhuo) <beanhuo@micron.com> - 2015-12-08 07:30 +0100
RE: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller Bean Huo 霍斌斌 (beanhuo) <beanhuo@micron.com> - 2015-12-08 07:50 +0100
Re: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller Cyrille Pitchen <cyrille.pitchen@atmel.com> - 2015-12-08 11:30 +0100
Re: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller Cyrille Pitchen <cyrille.pitchen@atmel.com> - 2015-12-08 15:40 +0100
| From | Cyrille Pitchen <cyrille.pitchen@atmel.com> |
|---|---|
| Date | 2015-12-07 15:10 +0100 |
| Subject | [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller |
| Message-ID | <qD4Ei-5uQ-13@gated-at.bofh.it> |
Hi all,
this series of patches adds support to the Atmel QSPI controller available
on sama5d2 SoCs. It was tested on a sama5d2 xplained ultra board with a
Micron n25q128a13 QSPI memory and a at25df321a SPI memory.
In order to use the Micron memory in its Quad SPI mode, the spi-nor
framework needed to be patched to fix the support of Quad/Dual SPI
protocols with some memory manufacturers such as Spansion, Micron and
Macronix. There are many comments in the source code to explain the
implementation choices based on the datasheets from memory manufacturers.
This series was based and tested on linux-next-20151207
1 - Atmel QSPI + Micron n25q128a13 (atmel-quadspi.c driver)
SPI 1-1-1: This mode was tested replacing SPI_NOR_QUAD by SPI_NOR_FAST as
argument to spi_nor_scan() called from atmel_qspi_probe().
SPI 1-1-4: Bootloaders (at91bootstrap/uboot) don't enable the Quad SPI
mode of the Micron memory. When probed from Linux, the memory
uses its Extended SPI mode and replies to the regular Read ID
(0x9f) command.
SPI 4-4-4: The romcode enabled the Quad SPI mode the of Micron memory
before loading the at91bootstrap. When probed from Linux, the
memory uses its Quad SPI mode and no longer replies to the
regular Read ID (0x9f) command but instead to the Read ID
Multiple I/O (0xaf) command. The memory expects ALL commands
to use the SPI 4-4-4 protocol.
2 - Atmel SPI + at25df321a (m25p80.c driver)
SPI 1-1-1: tested with the m25p80 driver for non regression purpose.
mtd_speedtest was run with the Atmel QSPI controller + Micron memory to
compare the performances of normal and quad SPI protocols. The SPI
bus clock was configured to 83 MHz.
1 - Fast Read 1-1-1
mtd_speedtest: testing eraseblock write speed
mtd_speedtest: eraseblock read speed is 9319 KiB/s
[...]
mtd_speedtest: testing page read speed
mtd_speedtest: page read speed is 6649 KiB/s
[...]
mtd_speedtest: testing 2 page read speed
mtd_speedtest: 2 page read speed is 7757 KiB/s
2 - Fast Read 1-1-4
mtd_speedtest: testing eraseblock read speed
mtd_speedtest: eraseblock read speed is 30117 KiB/s
[...]
mtd_speedtest: testing page read speed
mtd_speedtest: page read speed is 13096 KiB/s
[...]
mtd_speedtest: testing 2 page read speed
mtd_speedtest: 2 page read speed is 18224 KiB/s
So the performance improvements are:
eraseblock read speed (65536 bytes) : +223%
page read speed (512 bytes) : +97%
2 page read speed (1024 bytes) : +135%
Best Regards,
Cyrille
Cyrille Pitchen (5):
mtd: spi-nor: properly detect the memory when it boots in Quad or Dual
mode
mtd: spi-nor: fix Quad SPI mode support for Spansion, Micron and
Macronix
mtd: m25p80: add support of dual and quad spi protocols to all
commands
Documentation: atmel-quadspi: add binding file for Atmel QSPI driver
mtd: atmel-quadspi: add driver for Atmel QSPI controller
.../devicetree/bindings/mtd/atmel-quadspi.txt | 32 +
drivers/mtd/devices/m25p80.c | 233 +++++-
drivers/mtd/spi-nor/Kconfig | 8 +
drivers/mtd/spi-nor/Makefile | 3 +-
drivers/mtd/spi-nor/atmel-quadspi.c | 877 +++++++++++++++++++++
drivers/mtd/spi-nor/spi-nor.c | 835 +++++++++++++++++---
include/linux/mtd/spi-nor.h | 38 +-
7 files changed, 1880 insertions(+), 146 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
create mode 100644 drivers/mtd/spi-nor/atmel-quadspi.c
--
1.8.2.2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [next] | [standalone]
| From | Cyrille Pitchen <cyrille.pitchen@atmel.com> |
|---|---|
| Date | 2015-12-07 15:20 +0100 |
| Subject | [PATCH linux-next 3/5] mtd: m25p80: add support of dual and quad spi protocols to all commands |
| Message-ID | <qD4NY-5Bp-13@gated-at.bofh.it> |
| In reply to | #1285355 |
Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, all other m25p80_*() hooks only supported SPI 1-1-1.
However once their Quad mode enabled, Micron and Macronix spi-nor memories
expect all commands to use the SPI 4-4-4 protocol.
Also, once their Dual mode enabled, Micron spi-nor memories expect all
commands to use the SPI-2-2-2 protocol.
So this patch adds support to all currently existing SPI protocols to
cover as many protocols as possible.
Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
---
drivers/mtd/devices/m25p80.c | 233 +++++++++++++++++++++++++++++++++++--------
1 file changed, 193 insertions(+), 40 deletions(-)
diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index c9c3b7fa3051..8b09f77eeffb 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -27,22 +27,114 @@
#include <linux/spi/flash.h>
#include <linux/mtd/spi-nor.h>
-#define MAX_CMD_SIZE 6
+#define MAX_CMD_SIZE 8
struct m25p {
struct spi_device *spi;
struct spi_nor spi_nor;
u8 command[MAX_CMD_SIZE];
};
+static inline int m25p80_proto2nbits(enum spi_protocol proto,
+ unsigned *code_nbits,
+ unsigned *addr_nbits,
+ unsigned *data_nbits)
+{
+ unsigned code, addr, data;
+
+ switch (proto) {
+ case SPI_PROTO_1_1_1:
+ code = SPI_NBITS_SINGLE;
+ addr = SPI_NBITS_SINGLE;
+ data = SPI_NBITS_SINGLE;
+ break;
+
+ case SPI_PROTO_1_1_2:
+ code = SPI_NBITS_SINGLE;
+ addr = SPI_NBITS_SINGLE;
+ data = SPI_NBITS_DUAL;
+ break;
+
+ case SPI_PROTO_1_1_4:
+ code = SPI_NBITS_SINGLE;
+ addr = SPI_NBITS_SINGLE;
+ data = SPI_NBITS_QUAD;
+ break;
+
+ case SPI_PROTO_1_2_2:
+ code = SPI_NBITS_SINGLE;
+ addr = SPI_NBITS_DUAL;
+ data = SPI_NBITS_DUAL;
+ break;
+
+ case SPI_PROTO_1_4_4:
+ code = SPI_NBITS_SINGLE;
+ addr = SPI_NBITS_QUAD;
+ data = SPI_NBITS_QUAD;
+ break;
+
+ case SPI_PROTO_2_2_2:
+ code = SPI_NBITS_DUAL;
+ addr = SPI_NBITS_DUAL;
+ data = SPI_NBITS_DUAL;
+ break;
+
+ case SPI_PROTO_4_4_4:
+ code = SPI_NBITS_QUAD;
+ addr = SPI_NBITS_QUAD;
+ data = SPI_NBITS_QUAD;
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+
+ if (code_nbits)
+ *code_nbits = code;
+ if (addr_nbits)
+ *addr_nbits = addr;
+ if (data_nbits)
+ *data_nbits = data;
+
+ return 0;
+}
+
static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
+ unsigned code_nbits, data_nbits;
+ struct spi_transfer xfers[2];
int ret;
- ret = spi_write_then_read(spi, &code, 1, val, len);
+ /* Check the total length of command op code and data. */
+ if (len + 1 > MAX_CMD_SIZE)
+ return -EINVAL;
+
+ /* Get transfer protocols (addr_nbits is not relevant here). */
+ ret = m25p80_proto2nbits(nor->reg_proto,
+ &code_nbits, NULL, &data_nbits);
+ if (ret < 0)
+ return ret;
+
+ /* Set up transfers. */
+ memset(xfers, 0, sizeof(xfers));
+
+ flash->command[0] = code;
+ xfers[0].len = 1;
+ xfers[0].tx_buf = flash->command;
+ xfers[0].tx_nbits = code_nbits;
+
+ xfers[1].len = len;
+ xfers[1].rx_buf = &flash->command[1];
+ xfers[1].rx_nbits = data_nbits;
+
+ /* Process command. */
+ ret = spi_sync_transfer(spi, xfers, 2);
if (ret < 0)
dev_err(&spi->dev, "error %d reading %x\n", ret, code);
+ else
+ memcpy(val, &flash->command[1], len);
return ret;
}
@@ -65,12 +157,42 @@ static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
+ unsigned code_nbits, data_nbits, num_xfers = 1;
+ struct spi_transfer xfers[2];
+ int ret;
+
+ /* Check the total length of command op code and data. */
+ if (buf && (len + 1 > MAX_CMD_SIZE))
+ return -EINVAL;
+
+ /* Get transfer protocols (addr_nbits is not relevant here). */
+ ret = m25p80_proto2nbits(nor->reg_proto,
+ &code_nbits, NULL, &data_nbits);
+ if (ret < 0)
+ return ret;
+
+ /* Set up transfer(s). */
+ memset(xfers, 0, sizeof(xfers));
flash->command[0] = opcode;
- if (buf)
+ xfers[0].len = 1;
+ xfers[0].tx_buf = flash->command;
+ xfers[0].tx_nbits = code_nbits;
+
+ if (buf) {
memcpy(&flash->command[1], buf, len);
+ if (data_nbits == code_nbits) {
+ xfers[0].len += len;
+ } else {
+ xfers[1].len = len;
+ xfers[1].tx_buf = &flash->command[1];
+ xfers[1].tx_nbits = data_nbits;
+ num_xfers++;
+ }
+ }
- return spi_write(spi, flash->command, len + 1);
+ /* Process command. */
+ return spi_sync_transfer(spi, xfers, num_xfers);
}
static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
@@ -78,43 +200,54 @@ static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
- struct spi_transfer t[2] = {};
+ unsigned code_nbits, addr_nbits, data_nbits, num_xfers = 1;
+ struct spi_transfer xfers[3];
struct spi_message m;
- int cmd_sz = m25p_cmdsz(nor);
-
- spi_message_init(&m);
+ int ret, cmd_sz = m25p_cmdsz(nor);
if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
cmd_sz = 1;
- flash->command[0] = nor->program_opcode;
- m25p_addr2cmd(nor, to, flash->command);
+ /* Get transfer protocols. */
+ ret = m25p80_proto2nbits(nor->write_proto,
+ &code_nbits, &addr_nbits, &data_nbits);
+ if (ret < 0) {
+ *retlen = 0;
+ return;
+ }
- t[0].tx_buf = flash->command;
- t[0].len = cmd_sz;
- spi_message_add_tail(&t[0], &m);
+ /* Set up transfers. */
+ memset(xfers, 0, sizeof(xfers));
- t[1].tx_buf = buf;
- t[1].len = len;
- spi_message_add_tail(&t[1], &m);
+ flash->command[0] = nor->program_opcode;
+ xfers[0].len = 1;
+ xfers[0].tx_buf = flash->command;
+ xfers[0].tx_nbits = code_nbits;
+
+ if (cmd_sz > 1) {
+ m25p_addr2cmd(nor, to, flash->command);
+ if (addr_nbits == code_nbits) {
+ xfers[0].len += nor->addr_width;
+ } else {
+ xfers[1].len = nor->addr_width;
+ xfers[1].tx_buf = &flash->command[1];
+ xfers[1].tx_nbits = addr_nbits;
+ num_xfers++;
+ }
+ }
+
+ xfers[num_xfers].len = len;
+ xfers[num_xfers].tx_buf = buf;
+ xfers[num_xfers].tx_nbits = data_nbits;
+ num_xfers++;
+ /* Process command. */
+ spi_message_init_with_transfers(&m, xfers, num_xfers);
spi_sync(spi, &m);
*retlen += m.actual_length - cmd_sz;
}
-static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
-{
- switch (nor->flash_read) {
- case SPI_NOR_DUAL:
- return 2;
- case SPI_NOR_QUAD:
- return 4;
- default:
- return 0;
- }
-}
-
/*
* Read an address range from the nor chip. The address range
* may be any size provided it is within the physical boundaries.
@@ -124,28 +257,48 @@ static int m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
- struct spi_transfer t[2];
- struct spi_message m;
+ unsigned code_nbits, addr_nbits, data_nbits, num_xfers = 1;
unsigned int dummy = nor->read_dummy;
+ struct spi_transfer xfers[3];
+ struct spi_message m;
+ int ret;
+
+ /* Get transfer protocols. */
+ ret = m25p80_proto2nbits(nor->read_proto,
+ &code_nbits, &addr_nbits, &data_nbits);
+ if (ret < 0) {
+ *retlen = 0;
+ return ret;
+ }
/* convert the dummy cycles to the number of bytes */
dummy /= 8;
- spi_message_init(&m);
- memset(t, 0, (sizeof t));
+ /* Set up transfers. */
+ memset(xfers, 0, sizeof(xfers));
flash->command[0] = nor->read_opcode;
- m25p_addr2cmd(nor, from, flash->command);
+ xfers[0].len = 1;
+ xfers[0].tx_buf = flash->command;
+ xfers[0].tx_nbits = code_nbits;
- t[0].tx_buf = flash->command;
- t[0].len = m25p_cmdsz(nor) + dummy;
- spi_message_add_tail(&t[0], &m);
+ m25p_addr2cmd(nor, from, flash->command);
+ if (addr_nbits == code_nbits) {
+ xfers[0].len += nor->addr_width + dummy;
+ } else {
+ xfers[1].len = nor->addr_width + dummy;
+ xfers[1].tx_buf = &flash->command[1];
+ xfers[1].tx_nbits = addr_nbits;
+ num_xfers++;
+ }
- t[1].rx_buf = buf;
- t[1].rx_nbits = m25p80_rx_nbits(nor);
- t[1].len = len;
- spi_message_add_tail(&t[1], &m);
+ xfers[num_xfers].len = len;
+ xfers[num_xfers].rx_buf = buf;
+ xfers[num_xfers].rx_nbits = data_nbits;
+ num_xfers++;
+ /* Process command. */
+ spi_message_init_with_transfers(&m, xfers, num_xfers);
spi_sync(spi, &m);
*retlen = m.actual_length - m25p_cmdsz(nor) - dummy;
--
1.8.2.2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Cyrille Pitchen <cyrille.pitchen@atmel.com> |
|---|---|
| Date | 2015-12-07 15:20 +0100 |
| Subject | [PATCH linux-next 5/5] mtd: atmel-quadspi: add driver for Atmel QSPI controller |
| Message-ID | <qD4NZ-5Bp-19@gated-at.bofh.it> |
| In reply to | #1285355 |
This driver add support to the new Atmel QSPI controller embedded into
sama5d2x SoCs. It expects a NOR memory to be connected to the QSPI
controller.
Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
---
drivers/mtd/spi-nor/Kconfig | 8 +
drivers/mtd/spi-nor/Makefile | 3 +-
drivers/mtd/spi-nor/atmel-quadspi.c | 877 ++++++++++++++++++++++++++++++++++++
3 files changed, 887 insertions(+), 1 deletion(-)
create mode 100644 drivers/mtd/spi-nor/atmel-quadspi.c
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 0dc927540b3d..15f45dbbfe0d 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -37,6 +37,14 @@ config SPI_FSL_QUADSPI
This controller does not support generic SPI. It only supports
SPI NOR.
+config SPI_ATMEL_QUADSPI
+ tristate "Atmel Quad SPI Controller"
+ depends on OF && HAS_DMA && (ARCH_AT91 || COMPILE_TEST)
+ help
+ This enables support for the Quad SPI controller in master mode.
+ This driver does not support generic SPI. The implementation only
+ supports SPI NOR.
+
config SPI_NXP_SPIFI
tristate "NXP SPI Flash Interface (SPIFI)"
depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index 0bf3a7f81675..0cbed0e4ae8c 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
-obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
+obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
+obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/mtd/spi-nor/atmel-quadspi.c
new file mode 100644
index 000000000000..bdebfdc92842
--- /dev/null
+++ b/drivers/mtd/spi-nor/atmel-quadspi.c
@@ -0,0 +1,877 @@
+/*
+ * Driver for Atmel QSPI Controller
+ *
+ * Copyright (C) 2015 Atmel Corporation
+ *
+ * Author: Cyrille Pitchen <cyrille.pitchen@atmel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is based on drivers/mtd/spi-nor/fsl-quadspi.c from Freescale.
+ */
+
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/platform_data/atmel.h>
+#include <linux/platform_data/dma-atmel.h>
+#include <linux/of.h>
+
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/pinctrl/consumer.h>
+
+/* QSPI register offsets */
+#define QSPI_CR 0x0000 /* Control Register */
+#define QSPI_MR 0x0004 /* Mode Register */
+#define QSPI_RD 0x0008 /* Receive Data Register */
+#define QSPI_TD 0x000c /* Transmit Data Register */
+#define QSPI_SR 0x0010 /* Status Register */
+#define QSPI_IER 0x0014 /* Interrupt Enable Register */
+#define QSPI_IDR 0x0018 /* Interrupt Disable Register */
+#define QSPI_IMR 0x001c /* Interrupt Mask Register */
+#define QSPI_SCR 0x0020 /* Serial Clock Register */
+
+#define QSPI_IAR 0x0030 /* Instruction Address Register */
+#define QSPI_ICR 0x0034 /* Instruction Code Register */
+#define QSPI_IFR 0x0038 /* Instruction Frame Register */
+
+#define QSPI_SMR 0x0040 /* Scrambling Mode Register */
+#define QSPI_SKR 0x0044 /* Scrambling Key Register */
+
+#define QSPI_WPMR 0x00E4 /* Write Protection Mode Register */
+#define QSPI_WPSR 0x00E8 /* Write Protection Status Register */
+
+#define QSPI_VERSION 0x00FC /* Version Register */
+
+
+/* Bitfields in QSPI_CR (Control Register) */
+#define QSPI_CR_QSPIEN BIT(0)
+#define QSPI_CR_QSPIDIS BIT(1)
+#define QSPI_CR_SWRST BIT(7)
+#define QSPI_CR_LASTXFER BIT(24)
+
+/* Bitfields in QSPI_MR (Mode Register) */
+#define QSPI_MR_SSM BIT(0)
+#define QSPI_MR_LLB BIT(1)
+#define QSPI_MR_WDRBT BIT(2)
+#define QSPI_MR_SMRM BIT(3)
+#define QSPI_MR_CSMODE_MASK GENMASK(5, 4)
+#define QSPI_MR_CSMODE_NOT_RELOADED (0 << 4)
+#define QSPI_MR_CSMODE_LASTXFER (1 << 4)
+#define QSPI_MR_CSMODE_SYSTEMATICALLY (2 << 4)
+#define QSPI_MR_NBBITS_MASK GENMASK(11, 8)
+#define QSPI_MR_NBBITS(n) ((((n) - 8) << 8) & QSPI_MR_NBBITS_MASK)
+#define QSPI_MR_DLYBCT_MASK GENMASK(23, 16)
+#define QSPI_MR_DLYBCT(n) (((n) << 16) & QSPI_MR_DLYBCT_MASK)
+#define QSPI_MR_DLYCS_MASK GENMASK(31, 24)
+#define QSPI_MR_DLYCS(n) (((n) << 24) & QSPI_MR_DLYCS_MASK)
+
+/* Bitfields in QSPI_SR/QSPI_IER/QSPI_IDR/QSPI_IMR */
+#define QSPI_SR_RDRF BIT(0)
+#define QSPI_SR_TDRE BIT(1)
+#define QSPI_SR_TXEMPTY BIT(2)
+#define QSPI_SR_OVRES BIT(3)
+#define QSPI_SR_CSR BIT(8)
+#define QSPI_SR_CSS BIT(9)
+#define QSPI_SR_INSTRE BIT(10)
+#define QSPI_SR_QSPIENS BIT(24)
+
+/* Bitfields in QSPI_SCR (Serial Clock Register) */
+#define QSPI_SCR_CPOL BIT(0)
+#define QSPI_SCR_CPHA BIT(1)
+#define QSPI_SCR_SCBR_MASK GENMASK(15, 8)
+#define QSPI_SCR_SCBR(n) (((n) << 8) & QSPI_SCR_SCBR_MASK)
+#define QSPI_SCR_DLYBS_MASK GENMASK(23, 16)
+#define QSPI_SCR_DLYBS(n) (((n) << 16) & QSPI_SCR_DLYBS_MASK)
+
+/* Bitfields in QSPI_ICR (Instruction Code Register) */
+#define QSPI_ICR_INST_MASK GENMASK(7, 0)
+#define QSPI_ICR_INST(inst) (((inst) << 0) & QSPI_ICR_INST_MASK)
+#define QSPI_ICR_OPT_MASK GENMASK(23, 16)
+#define QSPI_ICR_OPT(opt) (((opt) << 16) & QSPI_ICR_OPT_MASK)
+
+/* Bitfields in QSPI_IFR (Instruction Frame Register) */
+#define QSPI_IFR_WIDTH_MASK GENMASK(2, 0)
+#define QSPI_IFR_WIDTH_SINGLE_BIT_SPI (0 << 0)
+#define QSPI_IFR_WIDTH_DUAL_OUTPUT (1 << 0)
+#define QSPI_IFR_WIDTH_QUAD_OUTPUT (2 << 0)
+#define QSPI_IFR_WIDTH_DUAL_IO (3 << 0)
+#define QSPI_IFR_WIDTH_QUAD_IO (4 << 0)
+#define QSPI_IFR_WIDTH_DUAL_CMD (5 << 0)
+#define QSPI_IFR_WIDTH_QUAD_CMD (6 << 0)
+#define QSPI_IFR_INSTEN BIT(4)
+#define QSPI_IFR_ADDREN BIT(5)
+#define QSPI_IFR_OPTEN BIT(6)
+#define QSPI_IFR_DATAEN BIT(7)
+#define QSPI_IFR_OPTL_MASK GENMASK(9, 8)
+#define QSPI_IFR_OPTL_1BIT (0 << 8)
+#define QSPI_IFR_OPTL_2BIT (1 << 8)
+#define QSPI_IFR_OPTL_4BIT (2 << 8)
+#define QSPI_IFR_OPTL_8BIT (3 << 8)
+#define QSPI_IFR_ADDRL BIT(10)
+#define QSPI_IFR_TFRTYP_MASK GENMASK(13, 12)
+#define QSPI_IFR_TFRTYP_TRSFR_READ (0 << 12)
+#define QSPI_IFR_TFRTYP_TRSFR_READ_MEM (1 << 12)
+#define QSPI_IFR_TFRTYP_TRSFR_WRITE (2 << 12)
+#define QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM (3 << 13)
+#define QSPI_IFR_CRM BIT(14)
+#define QSPI_IFR_NBDUM_MASK GENMASK(20, 16)
+#define QSPI_IFR_NBDUM(n) (((n) << 16) & QSPI_IFR_NBDUM_MASK)
+
+/* Bitfields in QSPI_SMR (Scrambling Mode Register) */
+#define QSPI_SMR_SCREN BIT(0)
+#define QSPI_SMR_RVDIS BIT(1)
+
+/* Bitfields in QSPI_WPMR (Write Protection Mode Register) */
+#define QSPI_WPMR_WPEN BIT(0)
+#define QSPI_WPMR_WPKEY_MASK GENMASK(31, 8)
+#define QSPI_WPMR_WPKEY(wpkey) (((wpkey) << 8) & QSPI_WPMR_WPKEY_MASK)
+
+/* Bitfields in QSPI_WPSR (Write Protection Status Register) */
+#define QSPI_WPSR_WPVS BIT(0)
+#define QSPI_WPSR_WPVSRC_MASK GENMASK(15, 8)
+#define QSPI_WPSR_WPVSRC(src) (((src) << 8) & QSPI_WPSR_WPVSRC)
+
+
+struct atmel_qspi {
+ void __iomem *regs;
+ void __iomem *mem;
+ dma_addr_t phys_addr;
+ struct dma_chan *chan;
+ struct clk *clk;
+ struct platform_device *pdev;
+ u32 pending;
+
+ struct spi_nor nor;
+ u32 clk_rate;
+ struct completion cmd_completion;
+ struct completion dma_completion;
+
+#ifdef DEBUG
+ u8 last_instruction;
+#endif
+};
+
+struct atmel_qspi_command {
+ u32 ifr;
+ union {
+ struct {
+ u32 instruction:1;
+ u32 address:3;
+ u32 mode:1;
+ u32 dummy:1;
+ u32 data:1;
+ u32 dma:1;
+ u32 reserved:24;
+ } bits;
+ u32 word;
+ } enable;
+ u8 instruction;
+ u8 mode;
+ u8 num_mode_cycles;
+ u8 num_dummy_cycles;
+ u32 address;
+
+ size_t buf_len;
+ const void *tx_buf;
+ void *rx_buf;
+};
+
+/* Register access functions */
+static inline u32 qspi_readl(struct atmel_qspi *aq, u32 reg)
+{
+ return readl_relaxed(aq->regs + reg);
+}
+
+static inline void qspi_writel(struct atmel_qspi *aq, u32 reg, u32 value)
+{
+ writel_relaxed(value, aq->regs + reg);
+}
+
+
+#define QSPI_DMA_THRESHOLD 32
+
+static void atmel_qspi_dma_callback(void *arg)
+{
+ struct completion *dma_completion = arg;
+
+ complete(dma_completion);
+}
+
+static int atmel_qspi_run_dma_transfer(struct atmel_qspi *aq,
+ const struct atmel_qspi_command *cmd)
+{
+ u32 offset = (cmd->enable.bits.address) ? cmd->address : 0;
+ struct dma_chan *chan = aq->chan;
+ struct device *dev = &aq->pdev->dev;
+ enum dma_data_direction direction;
+ dma_addr_t phys_addr, dst, src;
+ struct dma_async_tx_descriptor *desc;
+ dma_cookie_t cookie;
+ int err = 0;
+
+ if (cmd->tx_buf) {
+ direction = DMA_TO_DEVICE;
+ phys_addr = dma_map_single(dev, (void *)cmd->tx_buf,
+ cmd->buf_len, direction);
+ src = phys_addr;
+ dst = aq->phys_addr + offset;
+ } else {
+ direction = DMA_FROM_DEVICE;
+ phys_addr = dma_map_single(dev, (void *)cmd->rx_buf,
+ cmd->buf_len, direction);
+ src = aq->phys_addr + offset;
+ dst = phys_addr;
+ }
+ if (dma_mapping_error(dev, phys_addr))
+ return -ENOMEM;
+
+ desc = chan->device->device_prep_dma_memcpy(chan, dst, src,
+ cmd->buf_len,
+ DMA_PREP_INTERRUPT);
+ if (!desc) {
+ err = -ENOMEM;
+ goto unmap_single;
+ }
+
+ reinit_completion(&aq->dma_completion);
+ desc->callback = atmel_qspi_dma_callback;
+ desc->callback_param = &aq->dma_completion;
+ cookie = dmaengine_submit(desc);
+ err = dma_submit_error(cookie);
+ if (err)
+ goto unmap_single;
+ dma_async_issue_pending(chan);
+
+ if (!wait_for_completion_timeout(&aq->dma_completion,
+ msecs_to_jiffies(1000)))
+ err = -ETIMEDOUT;
+
+ if (dma_async_is_tx_complete(chan, cookie, NULL, NULL) != DMA_COMPLETE)
+ err = -ETIMEDOUT;
+
+ if (err)
+ dmaengine_terminate_all(chan);
+unmap_single:
+ dma_unmap_single(dev, phys_addr, cmd->buf_len, direction);
+
+ return err;
+}
+
+static int atmel_qspi_run_transfer(struct atmel_qspi *aq,
+ const struct atmel_qspi_command *cmd)
+{
+ void __iomem *ahb_mem;
+
+ /* First try a DMA transfer */
+ if (aq->chan && cmd->enable.bits.dma &&
+ cmd->buf_len >= QSPI_DMA_THRESHOLD)
+ return atmel_qspi_run_dma_transfer(aq, cmd);
+
+ /* Then fallback to a PIO transfer (memcpy() DOES NOT work!) */
+ ahb_mem = aq->mem;
+ if (cmd->enable.bits.address)
+ ahb_mem += cmd->address;
+ if (cmd->tx_buf)
+ _memcpy_toio(ahb_mem, cmd->tx_buf, cmd->buf_len);
+ else
+ _memcpy_fromio(cmd->rx_buf, ahb_mem, cmd->buf_len);
+
+ return 0;
+}
+
+#ifdef DEBUG
+static void atmel_qspi_debug_command(struct atmel_qspi *aq,
+ const struct atmel_qspi_command *cmd)
+{
+ u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
+ size_t len = 0;
+ int i;
+
+ if (cmd->enable.bits.instruction) {
+ if (aq->last_instruction == cmd->instruction)
+ return;
+ aq->last_instruction = cmd->instruction;
+ }
+
+ if (cmd->enable.bits.instruction)
+ cmd_buf[len++] = cmd->instruction;
+
+ for (i = cmd->enable.bits.address-1; i >= 0; --i)
+ cmd_buf[len++] = (cmd->address >> (i << 3)) & 0xff;
+
+ if (cmd->enable.bits.mode)
+ cmd_buf[len++] = cmd->mode;
+
+ if (cmd->enable.bits.dummy) {
+ int num = cmd->num_dummy_cycles;
+
+ switch (cmd->ifr & QSPI_IFR_WIDTH_MASK) {
+ case QSPI_IFR_WIDTH_SINGLE_BIT_SPI:
+ case QSPI_IFR_WIDTH_DUAL_OUTPUT:
+ case QSPI_IFR_WIDTH_QUAD_OUTPUT:
+ num >>= 3;
+ break;
+ case QSPI_IFR_WIDTH_DUAL_IO:
+ case QSPI_IFR_WIDTH_DUAL_CMD:
+ num >>= 2;
+ break;
+ case QSPI_IFR_WIDTH_QUAD_IO:
+ case QSPI_IFR_WIDTH_QUAD_CMD:
+ num >>= 1;
+ break;
+ default:
+ return;
+ }
+
+ for (i = 0; i < num; ++i)
+ cmd_buf[len++] = 0;
+ }
+
+ /* Dump the SPI command */
+ print_hex_dump(KERN_DEBUG, "qspi cmd: ", DUMP_PREFIX_NONE,
+ 32, 1, cmd_buf, len, false);
+
+#ifdef VERBOSE_DEBUG
+ /* If verbose debug is enabled, also dump the TX data */
+ if (cmd->enable.bits.data && cmd->tx_buf)
+ print_hex_dump(KERN_DEBUG, "qspi tx : ", DUMP_PREFIX_NONE,
+ 32, 1, cmd->tx_buf, cmd->buf_len, false);
+#endif
+}
+#else
+#define atmel_qspi_debug_command(aq, cmd)
+#endif
+
+static int atmel_qspi_run_command(struct atmel_qspi *aq,
+ const struct atmel_qspi_command *cmd)
+{
+ u32 iar, icr, ifr, sr;
+ int err = 0;
+
+ iar = 0;
+ icr = 0;
+ ifr = cmd->ifr;
+
+ /* Compute instruction parameters */
+ if (cmd->enable.bits.instruction) {
+ icr |= QSPI_ICR_INST(cmd->instruction);
+ ifr |= QSPI_IFR_INSTEN;
+ }
+
+ /* Compute address parameters */
+ switch (cmd->enable.bits.address) {
+ case 4:
+ ifr |= QSPI_IFR_ADDRL;
+ /* fall through to the 24bit (3 byte) address case. */
+ case 3:
+ iar = (cmd->enable.bits.data) ? 0 : cmd->address;
+ ifr |= QSPI_IFR_ADDREN;
+ break;
+ case 0:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Compute option parameters */
+ if (cmd->enable.bits.mode && cmd->num_mode_cycles) {
+ u32 mode_cycle_bits, mode_bits;
+
+ icr |= QSPI_ICR_OPT(cmd->mode);
+ ifr |= QSPI_IFR_OPTEN;
+
+ switch (ifr & QSPI_IFR_WIDTH_MASK) {
+ case QSPI_IFR_WIDTH_SINGLE_BIT_SPI:
+ case QSPI_IFR_WIDTH_DUAL_OUTPUT:
+ case QSPI_IFR_WIDTH_QUAD_OUTPUT:
+ mode_cycle_bits = 1;
+ break;
+ case QSPI_IFR_WIDTH_DUAL_IO:
+ case QSPI_IFR_WIDTH_DUAL_CMD:
+ mode_cycle_bits = 2;
+ break;
+ case QSPI_IFR_WIDTH_QUAD_IO:
+ case QSPI_IFR_WIDTH_QUAD_CMD:
+ mode_cycle_bits = 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mode_bits = cmd->num_mode_cycles * mode_cycle_bits;
+ switch (mode_bits) {
+ case 1:
+ ifr |= QSPI_IFR_OPTL_1BIT;
+ break;
+
+ case 2:
+ ifr |= QSPI_IFR_OPTL_2BIT;
+ break;
+
+ case 4:
+ ifr |= QSPI_IFR_OPTL_4BIT;
+ break;
+
+ case 8:
+ ifr |= QSPI_IFR_OPTL_8BIT;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* Set number of dummy cycles */
+ if (cmd->enable.bits.dummy)
+ ifr |= QSPI_IFR_NBDUM(cmd->num_dummy_cycles);
+
+ /* Set data enable */
+ if (cmd->enable.bits.data) {
+ ifr |= QSPI_IFR_DATAEN;
+
+ /* Special case for Continuous Read Mode */
+ if (!cmd->tx_buf && !cmd->rx_buf)
+ ifr |= QSPI_IFR_CRM;
+ }
+
+ /* Set QSPI Instruction Frame registers */
+ atmel_qspi_debug_command(aq, cmd);
+ qspi_writel(aq, QSPI_IAR, iar);
+ qspi_writel(aq, QSPI_ICR, icr);
+ qspi_writel(aq, QSPI_IFR, ifr);
+
+ /* Skip to the final steps if there is no data */
+ if (!cmd->enable.bits.data)
+ goto no_data;
+
+ /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */
+ (void)qspi_readl(aq, QSPI_IFR);
+
+ /* Stop here for continuous read */
+ if (!cmd->tx_buf && !cmd->rx_buf)
+ return 0;
+ /* Send/Receive data */
+ err = atmel_qspi_run_transfer(aq, cmd);
+
+ /* Release the chip-select */
+ qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER);
+
+ if (err)
+ return err;
+
+#if defined(DEBUG) && defined(VERBOSE_DEBUG)
+ /*
+ * If verbose debug is enabled, also dump the RX data in addition to
+ * the SPI command previously dumped by atmel_qspi_debug_command()
+ */
+ if (cmd->rx_buf)
+ print_hex_dump(KERN_DEBUG, "qspi rx : ", DUMP_PREFIX_NONE,
+ 32, 1, cmd->rx_buf, cmd->buf_len, false);
+#endif
+no_data:
+ /* Poll INSTRuction End status */
+ sr = qspi_readl(aq, QSPI_SR);
+ if (sr & QSPI_SR_INSTRE)
+ return err;
+
+ /* Wait for INSTRuction End interrupt */
+ reinit_completion(&aq->cmd_completion);
+ aq->pending = 0;
+ qspi_writel(aq, QSPI_IER, QSPI_SR_INSTRE);
+ if (!wait_for_completion_timeout(&aq->cmd_completion,
+ msecs_to_jiffies(1000)))
+ err = -ETIMEDOUT;
+ qspi_writel(aq, QSPI_IDR, QSPI_SR_INSTRE);
+
+ return err;
+}
+
+static int atmel_qspi_command_set_ifr(struct atmel_qspi_command *cmd,
+ u32 ifr_tfrtyp,
+ enum spi_protocol proto)
+{
+ cmd->ifr = ifr_tfrtyp;
+
+ switch (proto) {
+ case SPI_PROTO_1_1_1:
+ cmd->ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
+ break;
+ case SPI_PROTO_1_1_2:
+ cmd->ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT;
+ break;
+ case SPI_PROTO_1_1_4:
+ cmd->ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT;
+ break;
+ case SPI_PROTO_1_2_2:
+ cmd->ifr |= QSPI_IFR_WIDTH_DUAL_IO;
+ break;
+ case SPI_PROTO_1_4_4:
+ cmd->ifr |= QSPI_IFR_WIDTH_QUAD_IO;
+ break;
+ case SPI_PROTO_2_2_2:
+ cmd->ifr |= QSPI_IFR_WIDTH_DUAL_CMD;
+ break;
+ case SPI_PROTO_4_4_4:
+ cmd->ifr |= QSPI_IFR_WIDTH_QUAD_CMD;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode,
+ u8 *buf, int len)
+{
+ struct atmel_qspi *aq = nor->priv;
+ struct atmel_qspi_command cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.data = 1;
+ cmd.instruction = opcode;
+ cmd.rx_buf = buf;
+ cmd.buf_len = len;
+
+ ret = atmel_qspi_command_set_ifr(&cmd,
+ QSPI_IFR_TFRTYP_TRSFR_READ,
+ nor->reg_proto);
+ if (ret)
+ return ret;
+
+ return atmel_qspi_run_command(aq, &cmd);
+}
+
+static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
+ u8 *buf, int len)
+{
+ struct atmel_qspi *aq = nor->priv;
+ struct atmel_qspi_command cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.data = (buf != NULL && len > 0);
+ cmd.instruction = opcode;
+ cmd.tx_buf = buf;
+ cmd.buf_len = len;
+
+ ret = atmel_qspi_command_set_ifr(&cmd,
+ QSPI_IFR_TFRTYP_TRSFR_WRITE,
+ nor->reg_proto);
+ if (ret)
+ return ret;
+
+ return atmel_qspi_run_command(aq, &cmd);
+}
+
+static void atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
+ size_t *retlen, const u_char *write_buf)
+{
+ struct atmel_qspi *aq = nor->priv;
+ struct atmel_qspi_command cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.address = nor->addr_width;
+ cmd.enable.bits.data = 1;
+ cmd.enable.bits.dma = 1;
+ cmd.instruction = nor->program_opcode;
+ cmd.address = (u32)to;
+ cmd.tx_buf = write_buf;
+ cmd.buf_len = len;
+
+ if (atmel_qspi_command_set_ifr(&cmd,
+ QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM,
+ nor->write_proto))
+ return;
+
+ if (!atmel_qspi_run_command(aq, &cmd))
+ *retlen += len;
+}
+
+static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs)
+{
+ struct atmel_qspi *aq = nor->priv;
+ struct atmel_qspi_command cmd;
+ int ret;
+
+ dev_dbg(nor->dev, "%dKiB at 0x%08x\n",
+ nor->mtd.erasesize / 1024, (u32)offs);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.address = nor->addr_width;
+ cmd.instruction = nor->erase_opcode;
+ cmd.address = (u32)offs;
+
+ ret = atmel_qspi_command_set_ifr(&cmd,
+ QSPI_IFR_TFRTYP_TRSFR_WRITE,
+ nor->erase_proto);
+ if (ret)
+ return ret;
+
+ return atmel_qspi_run_command(aq, &cmd);
+}
+
+static int atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
+ size_t *retlen, u_char *read_buf)
+{
+ struct atmel_qspi *aq = nor->priv;
+ struct atmel_qspi_command cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.address = nor->addr_width;
+ cmd.enable.bits.dummy = (nor->read_dummy > 0);
+ cmd.enable.bits.data = 1;
+ cmd.enable.bits.dma = 1;
+ cmd.instruction = nor->read_opcode;
+ cmd.address = (u32)from;
+ cmd.num_dummy_cycles = nor->read_dummy;
+ cmd.rx_buf = read_buf;
+ cmd.buf_len = len;
+
+ ret = atmel_qspi_command_set_ifr(&cmd,
+ QSPI_IFR_TFRTYP_TRSFR_READ_MEM,
+ nor->read_proto);
+ if (ret)
+ return ret;
+
+ ret = atmel_qspi_run_command(aq, &cmd);
+ if (ret)
+ return ret;
+
+ *retlen += len;
+ return 0;
+}
+
+static int atmel_qspi_init(struct atmel_qspi *aq)
+{
+ unsigned long src_rate;
+ u32 mr, scr, scbr;
+
+ /* Reset the QSPI controller */
+ qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST);
+
+ /* Set the QSPI controller in Serial Memory Mode */
+ mr = QSPI_MR_SSM | QSPI_MR_NBBITS(8);
+ qspi_writel(aq, QSPI_MR, mr);
+
+ src_rate = clk_get_rate(aq->clk);
+ if (!src_rate)
+ return -EINVAL;
+
+ /* Compute the QSPI baudrate */
+ scbr = DIV_ROUND_UP(src_rate, aq->clk_rate);
+ if (scbr > 0)
+ scbr--;
+ scr = QSPI_SCR_SCBR(scbr);
+ qspi_writel(aq, QSPI_SCR, scr);
+
+ /* Enable the QSPI controller */
+ qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIEN);
+
+ return 0;
+}
+
+static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
+{
+ struct atmel_qspi *aq = (struct atmel_qspi *)dev_id;
+ u32 status, mask, pending;
+
+ status = qspi_readl(aq, QSPI_SR);
+ mask = qspi_readl(aq, QSPI_IMR);
+ pending = status & mask;
+
+ if (!pending)
+ return IRQ_NONE;
+
+ aq->pending |= pending;
+ if (pending & QSPI_SR_INSTRE)
+ complete(&aq->cmd_completion);
+
+ return IRQ_HANDLED;
+}
+
+static int atmel_qspi_probe(struct platform_device *pdev)
+{
+ struct device_node *child, *np = pdev->dev.of_node;
+ struct atmel_qspi *aq;
+ struct resource *res;
+ dma_cap_mask_t mask;
+ struct spi_nor *nor;
+ struct mtd_info *mtd;
+ int irq, err = 0;
+
+ if (of_get_child_count(np) != 1)
+ return -ENODEV;
+ child = of_get_next_child(np, NULL);
+
+ aq = devm_kzalloc(&pdev->dev, sizeof(*aq), GFP_KERNEL);
+ if (!aq) {
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ platform_set_drvdata(pdev, aq);
+ init_completion(&aq->cmd_completion);
+ init_completion(&aq->dma_completion);
+ aq->pdev = pdev;
+
+ /* Map the registers */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_base");
+ aq->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(aq->regs)) {
+ dev_err(&pdev->dev, "missing registers\n");
+ err = PTR_ERR(aq->regs);
+ goto exit;
+ }
+
+ /* Map the AHB memory */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mmap");
+ aq->mem = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(aq->mem)) {
+ dev_err(&pdev->dev, "missing AHB memory\n");
+ err = PTR_ERR(aq->regs);
+ goto exit;
+ }
+ aq->phys_addr = (dma_addr_t)res->start;
+
+ /* Get the peripheral clock */
+ aq->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(aq->clk)) {
+ dev_err(&pdev->dev, "missing peripheral clock\n");
+ err = PTR_ERR(aq->clk);
+ goto exit;
+ }
+
+ /* Enable the peripheral clock */
+ err = clk_prepare_enable(aq->clk);
+ if (err) {
+ dev_err(&pdev->dev, "failed to enable the peripheral clock\n");
+ goto exit;
+ }
+
+ /* Request the IRQ */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "missing IRQ\n");
+ err = irq;
+ goto disable_clk;
+ }
+ err = devm_request_irq(&pdev->dev, irq, atmel_qspi_interrupt,
+ 0, dev_name(&pdev->dev), aq);
+ if (err)
+ goto disable_clk;
+
+ /* Try to get a DMA channel for memcpy() operation */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+ aq->chan = dma_request_channel(mask, NULL, NULL);
+ if (!aq->chan)
+ dev_warn(&pdev->dev, "no available DMA channel\n");
+
+ /* Setup the spi-nor */
+ nor = &aq->nor;
+ mtd = &nor->mtd;
+
+ nor->dev = &pdev->dev;
+ spi_nor_set_flash_node(nor, child);
+ nor->priv = aq;
+ mtd->priv = nor;
+
+ nor->read_reg = atmel_qspi_read_reg;
+ nor->write_reg = atmel_qspi_write_reg;
+ nor->read = atmel_qspi_read;
+ nor->write = atmel_qspi_write;
+ nor->erase = atmel_qspi_erase;
+
+ err = of_property_read_u32(child, "spi-max-frequency", &aq->clk_rate);
+ if (err < 0)
+ goto release_channel;
+
+ err = atmel_qspi_init(aq);
+ if (err)
+ goto release_channel;
+
+ err = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
+ if (err)
+ goto release_channel;
+
+ err = mtd_device_register(mtd, NULL, 0);
+ if (err)
+ goto release_channel;
+
+ of_node_put(child);
+
+ return 0;
+
+release_channel:
+ if (aq->chan)
+ dma_release_channel(aq->chan);
+disable_clk:
+ clk_disable_unprepare(aq->clk);
+exit:
+ of_node_put(child);
+
+ return err;
+}
+
+static int atmel_qspi_remove(struct platform_device *pdev)
+{
+ struct atmel_qspi *aq = platform_get_drvdata(pdev);
+
+ mtd_device_unregister(&aq->nor.mtd);
+ qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIDIS);
+ if (aq->chan)
+ dma_release_channel(aq->chan);
+ clk_disable_unprepare(aq->clk);
+ return 0;
+}
+
+
+static const struct of_device_id atmel_qspi_dt_ids[] = {
+ { .compatible = "atmel,sama5d2-qspi" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, atmel_qspi_dt_ids);
+
+static struct platform_driver atmel_qspi_driver = {
+ .driver = {
+ .name = "atmel_qspi",
+ .of_match_table = atmel_qspi_dt_ids,
+ },
+ .probe = atmel_qspi_probe,
+ .remove = atmel_qspi_remove,
+};
+module_platform_driver(atmel_qspi_driver);
+
+MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>");
+MODULE_DESCRIPTION("Atmel QSPI Controller driver");
+MODULE_LICENSE("GPL v2");
--
1.8.2.2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | kbuild test robot <lkp@intel.com> |
|---|---|
| Date | 2015-12-07 16:30 +0100 |
| Subject | [PATCH] mtd: atmel-quadspi: fix odd_ptr_err.cocci warnings |
| Message-ID | <qD5TI-6js-37@gated-at.bofh.it> |
| In reply to | #1285366 |
drivers/mtd/spi-nor/atmel-quadspi.c:758:5-11: inconsistent IS_ERR and PTR_ERR on line 760.
PTR_ERR should access the value just tested by IS_ERR
Semantic patch information:
There can be false positives in the patch case, where it is the call to
IS_ERR that is wrong.
Generated by: scripts/coccinelle/tests/odd_ptr_err.cocci
CC: Cyrille Pitchen <cyrille.pitchen@atmel.com>
Signed-off-by: Fengguang Wu <fengguang.wu@intel.com>
---
Please take the patch only if it's a positive warning. Thanks!
atmel-quadspi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/mtd/spi-nor/atmel-quadspi.c
+++ b/drivers/mtd/spi-nor/atmel-quadspi.c
@@ -757,7 +757,7 @@ static int atmel_qspi_probe(struct platf
aq->mem = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(aq->mem)) {
dev_err(&pdev->dev, "missing AHB memory\n");
- err = PTR_ERR(aq->regs);
+ err = PTR_ERR(aq->mem);
goto exit;
}
aq->phys_addr = (dma_addr_t)res->start;
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | kbuild test robot <lkp@intel.com> |
|---|---|
| Date | 2015-12-07 16:30 +0100 |
| Subject | [PATCH] mtd: atmel-quadspi: fix compare_const_fl.cocci warnings |
| Message-ID | <qD5TJ-6js-51@gated-at.bofh.it> |
| In reply to | #1285366 |
drivers/mtd/spi-nor/atmel-quadspi.c:682:6-17: Move constant to right. Move constants to the right of binary operators. Semantic patch information: Depends on personal taste in some cases. Generated by: scripts/coccinelle/misc/compare_const_fl.cocci CC: Cyrille Pitchen <cyrille.pitchen@atmel.com> Signed-off-by: Fengguang Wu <fengguang.wu@intel.com> --- Please take the patch only if it's a positive warning. Thanks! atmel-quadspi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/mtd/spi-nor/atmel-quadspi.c +++ b/drivers/mtd/spi-nor/atmel-quadspi.c @@ -679,7 +679,7 @@ static int atmel_qspi_init(struct atmel_ qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST); /* Set the QSPI controller in Serial Memory Mode */ - mr = QSPI_MR_SSM | QSPI_MR_NBBITS(8); + mr = QSPI_MR_NBBITS(8) | QSPI_MR_SSM; qspi_writel(aq, QSPI_MR, mr); src_rate = clk_get_rate(aq->clk); -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Nicolas Ferre <nicolas.ferre@atmel.com> |
|---|---|
| Date | 2015-12-11 16:00 +0100 |
| Subject | Re: [PATCH linux-next 5/5] mtd: atmel-quadspi: add driver for Atmel QSPI controller |
| Message-ID | <qExkS-5QI-21@gated-at.bofh.it> |
| In reply to | #1285366 |
Le 07/12/2015 15:09, Cyrille Pitchen a écrit :
> This driver add support to the new Atmel QSPI controller embedded into
> sama5d2x SoCs. It expects a NOR memory to be connected to the QSPI
> controller.
>
> Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
Brian,
This driver has been maturing for months and my only goal is to keep on
supporting it.
As I already gave my Ack this summer, here is it now for sure:
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
I recall that Marek gave his as well and might also agree with the
changes and re-iterate his support.
Together with the current work of Cyrille on the spi-nor Quad SPI
support ("[PATCH linux-next 0/4] mtd: spi-nor: fix Quad SPI memory
support"), I have the feeling that this driver deserves an inclusion in
linux-next.
The infrastructure put in place and then used by this driver will
certainly help other developers to build their driver on top. I had the
impression that at least Marek was considering to use this for his
Cadence driver:
His quote from mid-September:
"
Are there any news on this patch series? I'd like to use that for my own
QSPI driver (the Cadence one), so I'd like to check on the status. Are you
still working on this please ?
"
Can we find a way to give this work even more exposure and consider at
least the spi-nor and m25p80 parts mentioned just above for mainline
inclusion?
Best regards,
> ---
> drivers/mtd/spi-nor/Kconfig | 8 +
> drivers/mtd/spi-nor/Makefile | 3 +-
> drivers/mtd/spi-nor/atmel-quadspi.c | 877 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 887 insertions(+), 1 deletion(-)
> create mode 100644 drivers/mtd/spi-nor/atmel-quadspi.c
>
> diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
> index 0dc927540b3d..15f45dbbfe0d 100644
> --- a/drivers/mtd/spi-nor/Kconfig
> +++ b/drivers/mtd/spi-nor/Kconfig
> @@ -37,6 +37,14 @@ config SPI_FSL_QUADSPI
> This controller does not support generic SPI. It only supports
> SPI NOR.
>
> +config SPI_ATMEL_QUADSPI
> + tristate "Atmel Quad SPI Controller"
> + depends on OF && HAS_DMA && (ARCH_AT91 || COMPILE_TEST)
> + help
> + This enables support for the Quad SPI controller in master mode.
> + This driver does not support generic SPI. The implementation only
> + supports SPI NOR.
> +
> config SPI_NXP_SPIFI
> tristate "NXP SPI Flash Interface (SPIFI)"
> depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
> diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
> index 0bf3a7f81675..0cbed0e4ae8c 100644
> --- a/drivers/mtd/spi-nor/Makefile
> +++ b/drivers/mtd/spi-nor/Makefile
> @@ -1,4 +1,5 @@
> obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
> obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
> -obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
> +obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
> +obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
> obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
> diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/mtd/spi-nor/atmel-quadspi.c
> new file mode 100644
> index 000000000000..bdebfdc92842
> --- /dev/null
> +++ b/drivers/mtd/spi-nor/atmel-quadspi.c
> @@ -0,0 +1,877 @@
> +/*
> + * Driver for Atmel QSPI Controller
> + *
> + * Copyright (C) 2015 Atmel Corporation
> + *
> + * Author: Cyrille Pitchen <cyrille.pitchen@atmel.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program. If not, see <http://www.gnu.org/licenses/>.
> + *
> + * This driver is based on drivers/mtd/spi-nor/fsl-quadspi.c from Freescale.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/clk.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmaengine.h>
> +#include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/mtd/spi-nor.h>
> +#include <linux/platform_data/atmel.h>
> +#include <linux/platform_data/dma-atmel.h>
> +#include <linux/of.h>
> +
> +#include <linux/io.h>
> +#include <linux/gpio.h>
> +#include <linux/pinctrl/consumer.h>
> +
> +/* QSPI register offsets */
> +#define QSPI_CR 0x0000 /* Control Register */
> +#define QSPI_MR 0x0004 /* Mode Register */
> +#define QSPI_RD 0x0008 /* Receive Data Register */
> +#define QSPI_TD 0x000c /* Transmit Data Register */
> +#define QSPI_SR 0x0010 /* Status Register */
> +#define QSPI_IER 0x0014 /* Interrupt Enable Register */
> +#define QSPI_IDR 0x0018 /* Interrupt Disable Register */
> +#define QSPI_IMR 0x001c /* Interrupt Mask Register */
> +#define QSPI_SCR 0x0020 /* Serial Clock Register */
> +
> +#define QSPI_IAR 0x0030 /* Instruction Address Register */
> +#define QSPI_ICR 0x0034 /* Instruction Code Register */
> +#define QSPI_IFR 0x0038 /* Instruction Frame Register */
> +
> +#define QSPI_SMR 0x0040 /* Scrambling Mode Register */
> +#define QSPI_SKR 0x0044 /* Scrambling Key Register */
> +
> +#define QSPI_WPMR 0x00E4 /* Write Protection Mode Register */
> +#define QSPI_WPSR 0x00E8 /* Write Protection Status Register */
> +
> +#define QSPI_VERSION 0x00FC /* Version Register */
> +
> +
> +/* Bitfields in QSPI_CR (Control Register) */
> +#define QSPI_CR_QSPIEN BIT(0)
> +#define QSPI_CR_QSPIDIS BIT(1)
> +#define QSPI_CR_SWRST BIT(7)
> +#define QSPI_CR_LASTXFER BIT(24)
> +
> +/* Bitfields in QSPI_MR (Mode Register) */
> +#define QSPI_MR_SSM BIT(0)
> +#define QSPI_MR_LLB BIT(1)
> +#define QSPI_MR_WDRBT BIT(2)
> +#define QSPI_MR_SMRM BIT(3)
> +#define QSPI_MR_CSMODE_MASK GENMASK(5, 4)
> +#define QSPI_MR_CSMODE_NOT_RELOADED (0 << 4)
> +#define QSPI_MR_CSMODE_LASTXFER (1 << 4)
> +#define QSPI_MR_CSMODE_SYSTEMATICALLY (2 << 4)
> +#define QSPI_MR_NBBITS_MASK GENMASK(11, 8)
> +#define QSPI_MR_NBBITS(n) ((((n) - 8) << 8) & QSPI_MR_NBBITS_MASK)
> +#define QSPI_MR_DLYBCT_MASK GENMASK(23, 16)
> +#define QSPI_MR_DLYBCT(n) (((n) << 16) & QSPI_MR_DLYBCT_MASK)
> +#define QSPI_MR_DLYCS_MASK GENMASK(31, 24)
> +#define QSPI_MR_DLYCS(n) (((n) << 24) & QSPI_MR_DLYCS_MASK)
> +
> +/* Bitfields in QSPI_SR/QSPI_IER/QSPI_IDR/QSPI_IMR */
> +#define QSPI_SR_RDRF BIT(0)
> +#define QSPI_SR_TDRE BIT(1)
> +#define QSPI_SR_TXEMPTY BIT(2)
> +#define QSPI_SR_OVRES BIT(3)
> +#define QSPI_SR_CSR BIT(8)
> +#define QSPI_SR_CSS BIT(9)
> +#define QSPI_SR_INSTRE BIT(10)
> +#define QSPI_SR_QSPIENS BIT(24)
> +
> +/* Bitfields in QSPI_SCR (Serial Clock Register) */
> +#define QSPI_SCR_CPOL BIT(0)
> +#define QSPI_SCR_CPHA BIT(1)
> +#define QSPI_SCR_SCBR_MASK GENMASK(15, 8)
> +#define QSPI_SCR_SCBR(n) (((n) << 8) & QSPI_SCR_SCBR_MASK)
> +#define QSPI_SCR_DLYBS_MASK GENMASK(23, 16)
> +#define QSPI_SCR_DLYBS(n) (((n) << 16) & QSPI_SCR_DLYBS_MASK)
> +
> +/* Bitfields in QSPI_ICR (Instruction Code Register) */
> +#define QSPI_ICR_INST_MASK GENMASK(7, 0)
> +#define QSPI_ICR_INST(inst) (((inst) << 0) & QSPI_ICR_INST_MASK)
> +#define QSPI_ICR_OPT_MASK GENMASK(23, 16)
> +#define QSPI_ICR_OPT(opt) (((opt) << 16) & QSPI_ICR_OPT_MASK)
> +
> +/* Bitfields in QSPI_IFR (Instruction Frame Register) */
> +#define QSPI_IFR_WIDTH_MASK GENMASK(2, 0)
> +#define QSPI_IFR_WIDTH_SINGLE_BIT_SPI (0 << 0)
> +#define QSPI_IFR_WIDTH_DUAL_OUTPUT (1 << 0)
> +#define QSPI_IFR_WIDTH_QUAD_OUTPUT (2 << 0)
> +#define QSPI_IFR_WIDTH_DUAL_IO (3 << 0)
> +#define QSPI_IFR_WIDTH_QUAD_IO (4 << 0)
> +#define QSPI_IFR_WIDTH_DUAL_CMD (5 << 0)
> +#define QSPI_IFR_WIDTH_QUAD_CMD (6 << 0)
> +#define QSPI_IFR_INSTEN BIT(4)
> +#define QSPI_IFR_ADDREN BIT(5)
> +#define QSPI_IFR_OPTEN BIT(6)
> +#define QSPI_IFR_DATAEN BIT(7)
> +#define QSPI_IFR_OPTL_MASK GENMASK(9, 8)
> +#define QSPI_IFR_OPTL_1BIT (0 << 8)
> +#define QSPI_IFR_OPTL_2BIT (1 << 8)
> +#define QSPI_IFR_OPTL_4BIT (2 << 8)
> +#define QSPI_IFR_OPTL_8BIT (3 << 8)
> +#define QSPI_IFR_ADDRL BIT(10)
> +#define QSPI_IFR_TFRTYP_MASK GENMASK(13, 12)
> +#define QSPI_IFR_TFRTYP_TRSFR_READ (0 << 12)
> +#define QSPI_IFR_TFRTYP_TRSFR_READ_MEM (1 << 12)
> +#define QSPI_IFR_TFRTYP_TRSFR_WRITE (2 << 12)
> +#define QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM (3 << 13)
> +#define QSPI_IFR_CRM BIT(14)
> +#define QSPI_IFR_NBDUM_MASK GENMASK(20, 16)
> +#define QSPI_IFR_NBDUM(n) (((n) << 16) & QSPI_IFR_NBDUM_MASK)
> +
> +/* Bitfields in QSPI_SMR (Scrambling Mode Register) */
> +#define QSPI_SMR_SCREN BIT(0)
> +#define QSPI_SMR_RVDIS BIT(1)
> +
> +/* Bitfields in QSPI_WPMR (Write Protection Mode Register) */
> +#define QSPI_WPMR_WPEN BIT(0)
> +#define QSPI_WPMR_WPKEY_MASK GENMASK(31, 8)
> +#define QSPI_WPMR_WPKEY(wpkey) (((wpkey) << 8) & QSPI_WPMR_WPKEY_MASK)
> +
> +/* Bitfields in QSPI_WPSR (Write Protection Status Register) */
> +#define QSPI_WPSR_WPVS BIT(0)
> +#define QSPI_WPSR_WPVSRC_MASK GENMASK(15, 8)
> +#define QSPI_WPSR_WPVSRC(src) (((src) << 8) & QSPI_WPSR_WPVSRC)
> +
> +
> +struct atmel_qspi {
> + void __iomem *regs;
> + void __iomem *mem;
> + dma_addr_t phys_addr;
> + struct dma_chan *chan;
> + struct clk *clk;
> + struct platform_device *pdev;
> + u32 pending;
> +
> + struct spi_nor nor;
> + u32 clk_rate;
> + struct completion cmd_completion;
> + struct completion dma_completion;
> +
> +#ifdef DEBUG
> + u8 last_instruction;
> +#endif
> +};
> +
> +struct atmel_qspi_command {
> + u32 ifr;
> + union {
> + struct {
> + u32 instruction:1;
> + u32 address:3;
> + u32 mode:1;
> + u32 dummy:1;
> + u32 data:1;
> + u32 dma:1;
> + u32 reserved:24;
> + } bits;
> + u32 word;
> + } enable;
> + u8 instruction;
> + u8 mode;
> + u8 num_mode_cycles;
> + u8 num_dummy_cycles;
> + u32 address;
> +
> + size_t buf_len;
> + const void *tx_buf;
> + void *rx_buf;
> +};
> +
> +/* Register access functions */
> +static inline u32 qspi_readl(struct atmel_qspi *aq, u32 reg)
> +{
> + return readl_relaxed(aq->regs + reg);
> +}
> +
> +static inline void qspi_writel(struct atmel_qspi *aq, u32 reg, u32 value)
> +{
> + writel_relaxed(value, aq->regs + reg);
> +}
> +
> +
> +#define QSPI_DMA_THRESHOLD 32
> +
> +static void atmel_qspi_dma_callback(void *arg)
> +{
> + struct completion *dma_completion = arg;
> +
> + complete(dma_completion);
> +}
> +
> +static int atmel_qspi_run_dma_transfer(struct atmel_qspi *aq,
> + const struct atmel_qspi_command *cmd)
> +{
> + u32 offset = (cmd->enable.bits.address) ? cmd->address : 0;
> + struct dma_chan *chan = aq->chan;
> + struct device *dev = &aq->pdev->dev;
> + enum dma_data_direction direction;
> + dma_addr_t phys_addr, dst, src;
> + struct dma_async_tx_descriptor *desc;
> + dma_cookie_t cookie;
> + int err = 0;
> +
> + if (cmd->tx_buf) {
> + direction = DMA_TO_DEVICE;
> + phys_addr = dma_map_single(dev, (void *)cmd->tx_buf,
> + cmd->buf_len, direction);
> + src = phys_addr;
> + dst = aq->phys_addr + offset;
> + } else {
> + direction = DMA_FROM_DEVICE;
> + phys_addr = dma_map_single(dev, (void *)cmd->rx_buf,
> + cmd->buf_len, direction);
> + src = aq->phys_addr + offset;
> + dst = phys_addr;
> + }
> + if (dma_mapping_error(dev, phys_addr))
> + return -ENOMEM;
> +
> + desc = chan->device->device_prep_dma_memcpy(chan, dst, src,
> + cmd->buf_len,
> + DMA_PREP_INTERRUPT);
> + if (!desc) {
> + err = -ENOMEM;
> + goto unmap_single;
> + }
> +
> + reinit_completion(&aq->dma_completion);
> + desc->callback = atmel_qspi_dma_callback;
> + desc->callback_param = &aq->dma_completion;
> + cookie = dmaengine_submit(desc);
> + err = dma_submit_error(cookie);
> + if (err)
> + goto unmap_single;
> + dma_async_issue_pending(chan);
> +
> + if (!wait_for_completion_timeout(&aq->dma_completion,
> + msecs_to_jiffies(1000)))
> + err = -ETIMEDOUT;
> +
> + if (dma_async_is_tx_complete(chan, cookie, NULL, NULL) != DMA_COMPLETE)
> + err = -ETIMEDOUT;
> +
> + if (err)
> + dmaengine_terminate_all(chan);
> +unmap_single:
> + dma_unmap_single(dev, phys_addr, cmd->buf_len, direction);
> +
> + return err;
> +}
> +
> +static int atmel_qspi_run_transfer(struct atmel_qspi *aq,
> + const struct atmel_qspi_command *cmd)
> +{
> + void __iomem *ahb_mem;
> +
> + /* First try a DMA transfer */
> + if (aq->chan && cmd->enable.bits.dma &&
> + cmd->buf_len >= QSPI_DMA_THRESHOLD)
> + return atmel_qspi_run_dma_transfer(aq, cmd);
> +
> + /* Then fallback to a PIO transfer (memcpy() DOES NOT work!) */
> + ahb_mem = aq->mem;
> + if (cmd->enable.bits.address)
> + ahb_mem += cmd->address;
> + if (cmd->tx_buf)
> + _memcpy_toio(ahb_mem, cmd->tx_buf, cmd->buf_len);
> + else
> + _memcpy_fromio(cmd->rx_buf, ahb_mem, cmd->buf_len);
> +
> + return 0;
> +}
> +
> +#ifdef DEBUG
> +static void atmel_qspi_debug_command(struct atmel_qspi *aq,
> + const struct atmel_qspi_command *cmd)
> +{
> + u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
> + size_t len = 0;
> + int i;
> +
> + if (cmd->enable.bits.instruction) {
> + if (aq->last_instruction == cmd->instruction)
> + return;
> + aq->last_instruction = cmd->instruction;
> + }
> +
> + if (cmd->enable.bits.instruction)
> + cmd_buf[len++] = cmd->instruction;
> +
> + for (i = cmd->enable.bits.address-1; i >= 0; --i)
> + cmd_buf[len++] = (cmd->address >> (i << 3)) & 0xff;
> +
> + if (cmd->enable.bits.mode)
> + cmd_buf[len++] = cmd->mode;
> +
> + if (cmd->enable.bits.dummy) {
> + int num = cmd->num_dummy_cycles;
> +
> + switch (cmd->ifr & QSPI_IFR_WIDTH_MASK) {
> + case QSPI_IFR_WIDTH_SINGLE_BIT_SPI:
> + case QSPI_IFR_WIDTH_DUAL_OUTPUT:
> + case QSPI_IFR_WIDTH_QUAD_OUTPUT:
> + num >>= 3;
> + break;
> + case QSPI_IFR_WIDTH_DUAL_IO:
> + case QSPI_IFR_WIDTH_DUAL_CMD:
> + num >>= 2;
> + break;
> + case QSPI_IFR_WIDTH_QUAD_IO:
> + case QSPI_IFR_WIDTH_QUAD_CMD:
> + num >>= 1;
> + break;
> + default:
> + return;
> + }
> +
> + for (i = 0; i < num; ++i)
> + cmd_buf[len++] = 0;
> + }
> +
> + /* Dump the SPI command */
> + print_hex_dump(KERN_DEBUG, "qspi cmd: ", DUMP_PREFIX_NONE,
> + 32, 1, cmd_buf, len, false);
> +
> +#ifdef VERBOSE_DEBUG
> + /* If verbose debug is enabled, also dump the TX data */
> + if (cmd->enable.bits.data && cmd->tx_buf)
> + print_hex_dump(KERN_DEBUG, "qspi tx : ", DUMP_PREFIX_NONE,
> + 32, 1, cmd->tx_buf, cmd->buf_len, false);
> +#endif
> +}
> +#else
> +#define atmel_qspi_debug_command(aq, cmd)
> +#endif
> +
> +static int atmel_qspi_run_command(struct atmel_qspi *aq,
> + const struct atmel_qspi_command *cmd)
> +{
> + u32 iar, icr, ifr, sr;
> + int err = 0;
> +
> + iar = 0;
> + icr = 0;
> + ifr = cmd->ifr;
> +
> + /* Compute instruction parameters */
> + if (cmd->enable.bits.instruction) {
> + icr |= QSPI_ICR_INST(cmd->instruction);
> + ifr |= QSPI_IFR_INSTEN;
> + }
> +
> + /* Compute address parameters */
> + switch (cmd->enable.bits.address) {
> + case 4:
> + ifr |= QSPI_IFR_ADDRL;
> + /* fall through to the 24bit (3 byte) address case. */
> + case 3:
> + iar = (cmd->enable.bits.data) ? 0 : cmd->address;
> + ifr |= QSPI_IFR_ADDREN;
> + break;
> + case 0:
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + /* Compute option parameters */
> + if (cmd->enable.bits.mode && cmd->num_mode_cycles) {
> + u32 mode_cycle_bits, mode_bits;
> +
> + icr |= QSPI_ICR_OPT(cmd->mode);
> + ifr |= QSPI_IFR_OPTEN;
> +
> + switch (ifr & QSPI_IFR_WIDTH_MASK) {
> + case QSPI_IFR_WIDTH_SINGLE_BIT_SPI:
> + case QSPI_IFR_WIDTH_DUAL_OUTPUT:
> + case QSPI_IFR_WIDTH_QUAD_OUTPUT:
> + mode_cycle_bits = 1;
> + break;
> + case QSPI_IFR_WIDTH_DUAL_IO:
> + case QSPI_IFR_WIDTH_DUAL_CMD:
> + mode_cycle_bits = 2;
> + break;
> + case QSPI_IFR_WIDTH_QUAD_IO:
> + case QSPI_IFR_WIDTH_QUAD_CMD:
> + mode_cycle_bits = 4;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + mode_bits = cmd->num_mode_cycles * mode_cycle_bits;
> + switch (mode_bits) {
> + case 1:
> + ifr |= QSPI_IFR_OPTL_1BIT;
> + break;
> +
> + case 2:
> + ifr |= QSPI_IFR_OPTL_2BIT;
> + break;
> +
> + case 4:
> + ifr |= QSPI_IFR_OPTL_4BIT;
> + break;
> +
> + case 8:
> + ifr |= QSPI_IFR_OPTL_8BIT;
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> + }
> +
> + /* Set number of dummy cycles */
> + if (cmd->enable.bits.dummy)
> + ifr |= QSPI_IFR_NBDUM(cmd->num_dummy_cycles);
> +
> + /* Set data enable */
> + if (cmd->enable.bits.data) {
> + ifr |= QSPI_IFR_DATAEN;
> +
> + /* Special case for Continuous Read Mode */
> + if (!cmd->tx_buf && !cmd->rx_buf)
> + ifr |= QSPI_IFR_CRM;
> + }
> +
> + /* Set QSPI Instruction Frame registers */
> + atmel_qspi_debug_command(aq, cmd);
> + qspi_writel(aq, QSPI_IAR, iar);
> + qspi_writel(aq, QSPI_ICR, icr);
> + qspi_writel(aq, QSPI_IFR, ifr);
> +
> + /* Skip to the final steps if there is no data */
> + if (!cmd->enable.bits.data)
> + goto no_data;
> +
> + /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */
> + (void)qspi_readl(aq, QSPI_IFR);
> +
> + /* Stop here for continuous read */
> + if (!cmd->tx_buf && !cmd->rx_buf)
> + return 0;
> + /* Send/Receive data */
> + err = atmel_qspi_run_transfer(aq, cmd);
> +
> + /* Release the chip-select */
> + qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER);
> +
> + if (err)
> + return err;
> +
> +#if defined(DEBUG) && defined(VERBOSE_DEBUG)
> + /*
> + * If verbose debug is enabled, also dump the RX data in addition to
> + * the SPI command previously dumped by atmel_qspi_debug_command()
> + */
> + if (cmd->rx_buf)
> + print_hex_dump(KERN_DEBUG, "qspi rx : ", DUMP_PREFIX_NONE,
> + 32, 1, cmd->rx_buf, cmd->buf_len, false);
> +#endif
> +no_data:
> + /* Poll INSTRuction End status */
> + sr = qspi_readl(aq, QSPI_SR);
> + if (sr & QSPI_SR_INSTRE)
> + return err;
> +
> + /* Wait for INSTRuction End interrupt */
> + reinit_completion(&aq->cmd_completion);
> + aq->pending = 0;
> + qspi_writel(aq, QSPI_IER, QSPI_SR_INSTRE);
> + if (!wait_for_completion_timeout(&aq->cmd_completion,
> + msecs_to_jiffies(1000)))
> + err = -ETIMEDOUT;
> + qspi_writel(aq, QSPI_IDR, QSPI_SR_INSTRE);
> +
> + return err;
> +}
> +
> +static int atmel_qspi_command_set_ifr(struct atmel_qspi_command *cmd,
> + u32 ifr_tfrtyp,
> + enum spi_protocol proto)
> +{
> + cmd->ifr = ifr_tfrtyp;
> +
> + switch (proto) {
> + case SPI_PROTO_1_1_1:
> + cmd->ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
> + break;
> + case SPI_PROTO_1_1_2:
> + cmd->ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT;
> + break;
> + case SPI_PROTO_1_1_4:
> + cmd->ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT;
> + break;
> + case SPI_PROTO_1_2_2:
> + cmd->ifr |= QSPI_IFR_WIDTH_DUAL_IO;
> + break;
> + case SPI_PROTO_1_4_4:
> + cmd->ifr |= QSPI_IFR_WIDTH_QUAD_IO;
> + break;
> + case SPI_PROTO_2_2_2:
> + cmd->ifr |= QSPI_IFR_WIDTH_DUAL_CMD;
> + break;
> + case SPI_PROTO_4_4_4:
> + cmd->ifr |= QSPI_IFR_WIDTH_QUAD_CMD;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode,
> + u8 *buf, int len)
> +{
> + struct atmel_qspi *aq = nor->priv;
> + struct atmel_qspi_command cmd;
> + int ret;
> +
> + memset(&cmd, 0, sizeof(cmd));
> + cmd.enable.bits.instruction = 1;
> + cmd.enable.bits.data = 1;
> + cmd.instruction = opcode;
> + cmd.rx_buf = buf;
> + cmd.buf_len = len;
> +
> + ret = atmel_qspi_command_set_ifr(&cmd,
> + QSPI_IFR_TFRTYP_TRSFR_READ,
> + nor->reg_proto);
> + if (ret)
> + return ret;
> +
> + return atmel_qspi_run_command(aq, &cmd);
> +}
> +
> +static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
> + u8 *buf, int len)
> +{
> + struct atmel_qspi *aq = nor->priv;
> + struct atmel_qspi_command cmd;
> + int ret;
> +
> + memset(&cmd, 0, sizeof(cmd));
> + cmd.enable.bits.instruction = 1;
> + cmd.enable.bits.data = (buf != NULL && len > 0);
> + cmd.instruction = opcode;
> + cmd.tx_buf = buf;
> + cmd.buf_len = len;
> +
> + ret = atmel_qspi_command_set_ifr(&cmd,
> + QSPI_IFR_TFRTYP_TRSFR_WRITE,
> + nor->reg_proto);
> + if (ret)
> + return ret;
> +
> + return atmel_qspi_run_command(aq, &cmd);
> +}
> +
> +static void atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
> + size_t *retlen, const u_char *write_buf)
> +{
> + struct atmel_qspi *aq = nor->priv;
> + struct atmel_qspi_command cmd;
> +
> + memset(&cmd, 0, sizeof(cmd));
> + cmd.enable.bits.instruction = 1;
> + cmd.enable.bits.address = nor->addr_width;
> + cmd.enable.bits.data = 1;
> + cmd.enable.bits.dma = 1;
> + cmd.instruction = nor->program_opcode;
> + cmd.address = (u32)to;
> + cmd.tx_buf = write_buf;
> + cmd.buf_len = len;
> +
> + if (atmel_qspi_command_set_ifr(&cmd,
> + QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM,
> + nor->write_proto))
> + return;
> +
> + if (!atmel_qspi_run_command(aq, &cmd))
> + *retlen += len;
> +}
> +
> +static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs)
> +{
> + struct atmel_qspi *aq = nor->priv;
> + struct atmel_qspi_command cmd;
> + int ret;
> +
> + dev_dbg(nor->dev, "%dKiB at 0x%08x\n",
> + nor->mtd.erasesize / 1024, (u32)offs);
> +
> + memset(&cmd, 0, sizeof(cmd));
> + cmd.enable.bits.instruction = 1;
> + cmd.enable.bits.address = nor->addr_width;
> + cmd.instruction = nor->erase_opcode;
> + cmd.address = (u32)offs;
> +
> + ret = atmel_qspi_command_set_ifr(&cmd,
> + QSPI_IFR_TFRTYP_TRSFR_WRITE,
> + nor->erase_proto);
> + if (ret)
> + return ret;
> +
> + return atmel_qspi_run_command(aq, &cmd);
> +}
> +
> +static int atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
> + size_t *retlen, u_char *read_buf)
> +{
> + struct atmel_qspi *aq = nor->priv;
> + struct atmel_qspi_command cmd;
> + int ret;
> +
> + memset(&cmd, 0, sizeof(cmd));
> + cmd.enable.bits.instruction = 1;
> + cmd.enable.bits.address = nor->addr_width;
> + cmd.enable.bits.dummy = (nor->read_dummy > 0);
> + cmd.enable.bits.data = 1;
> + cmd.enable.bits.dma = 1;
> + cmd.instruction = nor->read_opcode;
> + cmd.address = (u32)from;
> + cmd.num_dummy_cycles = nor->read_dummy;
> + cmd.rx_buf = read_buf;
> + cmd.buf_len = len;
> +
> + ret = atmel_qspi_command_set_ifr(&cmd,
> + QSPI_IFR_TFRTYP_TRSFR_READ_MEM,
> + nor->read_proto);
> + if (ret)
> + return ret;
> +
> + ret = atmel_qspi_run_command(aq, &cmd);
> + if (ret)
> + return ret;
> +
> + *retlen += len;
> + return 0;
> +}
> +
> +static int atmel_qspi_init(struct atmel_qspi *aq)
> +{
> + unsigned long src_rate;
> + u32 mr, scr, scbr;
> +
> + /* Reset the QSPI controller */
> + qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST);
> +
> + /* Set the QSPI controller in Serial Memory Mode */
> + mr = QSPI_MR_SSM | QSPI_MR_NBBITS(8);
> + qspi_writel(aq, QSPI_MR, mr);
> +
> + src_rate = clk_get_rate(aq->clk);
> + if (!src_rate)
> + return -EINVAL;
> +
> + /* Compute the QSPI baudrate */
> + scbr = DIV_ROUND_UP(src_rate, aq->clk_rate);
> + if (scbr > 0)
> + scbr--;
> + scr = QSPI_SCR_SCBR(scbr);
> + qspi_writel(aq, QSPI_SCR, scr);
> +
> + /* Enable the QSPI controller */
> + qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIEN);
> +
> + return 0;
> +}
> +
> +static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
> +{
> + struct atmel_qspi *aq = (struct atmel_qspi *)dev_id;
> + u32 status, mask, pending;
> +
> + status = qspi_readl(aq, QSPI_SR);
> + mask = qspi_readl(aq, QSPI_IMR);
> + pending = status & mask;
> +
> + if (!pending)
> + return IRQ_NONE;
> +
> + aq->pending |= pending;
> + if (pending & QSPI_SR_INSTRE)
> + complete(&aq->cmd_completion);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int atmel_qspi_probe(struct platform_device *pdev)
> +{
> + struct device_node *child, *np = pdev->dev.of_node;
> + struct atmel_qspi *aq;
> + struct resource *res;
> + dma_cap_mask_t mask;
> + struct spi_nor *nor;
> + struct mtd_info *mtd;
> + int irq, err = 0;
> +
> + if (of_get_child_count(np) != 1)
> + return -ENODEV;
> + child = of_get_next_child(np, NULL);
> +
> + aq = devm_kzalloc(&pdev->dev, sizeof(*aq), GFP_KERNEL);
> + if (!aq) {
> + err = -ENOMEM;
> + goto exit;
> + }
> +
> + platform_set_drvdata(pdev, aq);
> + init_completion(&aq->cmd_completion);
> + init_completion(&aq->dma_completion);
> + aq->pdev = pdev;
> +
> + /* Map the registers */
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_base");
> + aq->regs = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(aq->regs)) {
> + dev_err(&pdev->dev, "missing registers\n");
> + err = PTR_ERR(aq->regs);
> + goto exit;
> + }
> +
> + /* Map the AHB memory */
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mmap");
> + aq->mem = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(aq->mem)) {
> + dev_err(&pdev->dev, "missing AHB memory\n");
> + err = PTR_ERR(aq->regs);
> + goto exit;
> + }
> + aq->phys_addr = (dma_addr_t)res->start;
> +
> + /* Get the peripheral clock */
> + aq->clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(aq->clk)) {
> + dev_err(&pdev->dev, "missing peripheral clock\n");
> + err = PTR_ERR(aq->clk);
> + goto exit;
> + }
> +
> + /* Enable the peripheral clock */
> + err = clk_prepare_enable(aq->clk);
> + if (err) {
> + dev_err(&pdev->dev, "failed to enable the peripheral clock\n");
> + goto exit;
> + }
> +
> + /* Request the IRQ */
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(&pdev->dev, "missing IRQ\n");
> + err = irq;
> + goto disable_clk;
> + }
> + err = devm_request_irq(&pdev->dev, irq, atmel_qspi_interrupt,
> + 0, dev_name(&pdev->dev), aq);
> + if (err)
> + goto disable_clk;
> +
> + /* Try to get a DMA channel for memcpy() operation */
> + dma_cap_zero(mask);
> + dma_cap_set(DMA_MEMCPY, mask);
> + aq->chan = dma_request_channel(mask, NULL, NULL);
> + if (!aq->chan)
> + dev_warn(&pdev->dev, "no available DMA channel\n");
> +
> + /* Setup the spi-nor */
> + nor = &aq->nor;
> + mtd = &nor->mtd;
> +
> + nor->dev = &pdev->dev;
> + spi_nor_set_flash_node(nor, child);
> + nor->priv = aq;
> + mtd->priv = nor;
> +
> + nor->read_reg = atmel_qspi_read_reg;
> + nor->write_reg = atmel_qspi_write_reg;
> + nor->read = atmel_qspi_read;
> + nor->write = atmel_qspi_write;
> + nor->erase = atmel_qspi_erase;
> +
> + err = of_property_read_u32(child, "spi-max-frequency", &aq->clk_rate);
> + if (err < 0)
> + goto release_channel;
> +
> + err = atmel_qspi_init(aq);
> + if (err)
> + goto release_channel;
> +
> + err = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
> + if (err)
> + goto release_channel;
> +
> + err = mtd_device_register(mtd, NULL, 0);
> + if (err)
> + goto release_channel;
> +
> + of_node_put(child);
> +
> + return 0;
> +
> +release_channel:
> + if (aq->chan)
> + dma_release_channel(aq->chan);
> +disable_clk:
> + clk_disable_unprepare(aq->clk);
> +exit:
> + of_node_put(child);
> +
> + return err;
> +}
> +
> +static int atmel_qspi_remove(struct platform_device *pdev)
> +{
> + struct atmel_qspi *aq = platform_get_drvdata(pdev);
> +
> + mtd_device_unregister(&aq->nor.mtd);
> + qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIDIS);
> + if (aq->chan)
> + dma_release_channel(aq->chan);
> + clk_disable_unprepare(aq->clk);
> + return 0;
> +}
> +
> +
> +static const struct of_device_id atmel_qspi_dt_ids[] = {
> + { .compatible = "atmel,sama5d2-qspi" },
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, atmel_qspi_dt_ids);
> +
> +static struct platform_driver atmel_qspi_driver = {
> + .driver = {
> + .name = "atmel_qspi",
> + .of_match_table = atmel_qspi_dt_ids,
> + },
> + .probe = atmel_qspi_probe,
> + .remove = atmel_qspi_remove,
> +};
> +module_platform_driver(atmel_qspi_driver);
> +
> +MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>");
> +MODULE_DESCRIPTION("Atmel QSPI Controller driver");
> +MODULE_LICENSE("GPL v2");
>
--
Nicolas Ferre
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Cyrille Pitchen <cyrille.pitchen@atmel.com> |
|---|---|
| Date | 2015-12-07 15:20 +0100 |
| Subject | [PATCH linux-next 4/5] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver |
| Message-ID | <qD4O0-5Bp-51@gated-at.bofh.it> |
| In reply to | #1285355 |
This patch documents the DT bindings for the driver of the Atmel QSPI
controller embedded inside sama5d2x SoCs.
Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
---
.../devicetree/bindings/mtd/atmel-quadspi.txt | 32 ++++++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
diff --git a/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
new file mode 100644
index 000000000000..e81f20f9faf1
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
@@ -0,0 +1,32 @@
+* Atmel Quad Serial Peripheral Interface (QSPI)
+
+Required properties:
+- compatible: Should be "atmel,sama5d2-qspi".
+- reg: Should contain the locations and lengths of the base registers
+ and the mapped memory.
+- reg-names: Should contain the resource reg names:
+ - qspi_base: configuration register address space
+ - qspi_mmap: memory mapped address space
+- interrupts: Should contain the interrupt for the device.
+- clocks: The phandle of the clock needed by the QSPI controller.
+- #address-cells: Should be <1>.
+- #size-cells: Should be <0>.
+
+Example:
+
+spi@f0020000 {
+ compatible = "atmel,sama5d2-qspi";
+ reg = <0xf0020000 0x100>, <0xd0000000 0x8000000>;
+ reg-names = "qpsi_base", "qspi_mmap";
+ interrupts = <52 IRQ_TYPE_LEVEL_HIGH 7>;
+ clocks = <&spi0_clk>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_spi0_default>;
+ status = "okay";
+
+ m25p80@0 {
+ ...
+ };
+};
--
1.8.2.2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Rob Herring <robh@kernel.org> |
|---|---|
| Date | 2015-12-09 04:20 +0100 |
| Subject | Re: [PATCH linux-next 4/5] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver |
| Message-ID | <qDDsl-2Lw-7@gated-at.bofh.it> |
| In reply to | #1285378 |
On Mon, Dec 07, 2015 at 03:09:13PM +0100, Cyrille Pitchen wrote:
> This patch documents the DT bindings for the driver of the Atmel QSPI
> controller embedded inside sama5d2x SoCs.
>
> Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
Acked-by: Rob Herring <robh@kernel.org>
> ---
> .../devicetree/bindings/mtd/atmel-quadspi.txt | 32 ++++++++++++++++++++++
> 1 file changed, 32 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
>
> diff --git a/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
> new file mode 100644
> index 000000000000..e81f20f9faf1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
> @@ -0,0 +1,32 @@
> +* Atmel Quad Serial Peripheral Interface (QSPI)
> +
> +Required properties:
> +- compatible: Should be "atmel,sama5d2-qspi".
> +- reg: Should contain the locations and lengths of the base registers
> + and the mapped memory.
> +- reg-names: Should contain the resource reg names:
> + - qspi_base: configuration register address space
> + - qspi_mmap: memory mapped address space
> +- interrupts: Should contain the interrupt for the device.
> +- clocks: The phandle of the clock needed by the QSPI controller.
> +- #address-cells: Should be <1>.
> +- #size-cells: Should be <0>.
> +
> +Example:
> +
> +spi@f0020000 {
> + compatible = "atmel,sama5d2-qspi";
> + reg = <0xf0020000 0x100>, <0xd0000000 0x8000000>;
> + reg-names = "qpsi_base", "qspi_mmap";
> + interrupts = <52 IRQ_TYPE_LEVEL_HIGH 7>;
> + clocks = <&spi0_clk>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_spi0_default>;
> + status = "okay";
> +
> + m25p80@0 {
> + ...
> + };
> +};
> --
> 1.8.2.2
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Nicolas Ferre <nicolas.ferre@atmel.com> |
|---|---|
| Date | 2015-12-11 10:30 +0100 |
| Subject | Re: [PATCH linux-next 4/5] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver |
| Message-ID | <qEsbw-2gC-5@gated-at.bofh.it> |
| In reply to | #1285378 |
Le 07/12/2015 15:09, Cyrille Pitchen a écrit :
> This patch documents the DT bindings for the driver of the Atmel QSPI
> controller embedded inside sama5d2x SoCs.
>
> Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
The change is very small from previous one and moreover accepted by Rob.
So, for sure:
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
> ---
> .../devicetree/bindings/mtd/atmel-quadspi.txt | 32 ++++++++++++++++++++++
> 1 file changed, 32 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
>
> diff --git a/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
> new file mode 100644
> index 000000000000..e81f20f9faf1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
> @@ -0,0 +1,32 @@
> +* Atmel Quad Serial Peripheral Interface (QSPI)
> +
> +Required properties:
> +- compatible: Should be "atmel,sama5d2-qspi".
> +- reg: Should contain the locations and lengths of the base registers
> + and the mapped memory.
> +- reg-names: Should contain the resource reg names:
> + - qspi_base: configuration register address space
> + - qspi_mmap: memory mapped address space
> +- interrupts: Should contain the interrupt for the device.
> +- clocks: The phandle of the clock needed by the QSPI controller.
> +- #address-cells: Should be <1>.
> +- #size-cells: Should be <0>.
> +
> +Example:
> +
> +spi@f0020000 {
> + compatible = "atmel,sama5d2-qspi";
> + reg = <0xf0020000 0x100>, <0xd0000000 0x8000000>;
> + reg-names = "qpsi_base", "qspi_mmap";
> + interrupts = <52 IRQ_TYPE_LEVEL_HIGH 7>;
> + clocks = <&spi0_clk>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_spi0_default>;
> + status = "okay";
> +
> + m25p80@0 {
> + ...
> + };
> +};
>
--
Nicolas Ferre
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Brian Norris <computersforpeace@gmail.com> |
|---|---|
| Date | 2015-12-07 20:40 +0100 |
| Subject | Re: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller |
| Message-ID | <qD9NE-lE-21@gated-at.bofh.it> |
| In reply to | #1285355 |
+ Bean Huo
Hi Cyrille,
On Mon, Dec 07, 2015 at 03:09:09PM +0100, Cyrille Pitchen wrote:
> Hi all,
>
> this series of patches adds support to the Atmel QSPI controller available
> on sama5d2 SoCs. It was tested on a sama5d2 xplained ultra board with a
> Micron n25q128a13 QSPI memory and a at25df321a SPI memory.
>
> In order to use the Micron memory in its Quad SPI mode, the spi-nor
> framework needed to be patched to fix the support of Quad/Dual SPI
> protocols with some memory manufacturers such as Spansion, Micron and
> Macronix. There are many comments in the source code to explain the
> implementation choices based on the datasheets from memory manufacturers.
>
>
> This series was based and tested on linux-next-20151207
>
> 1 - Atmel QSPI + Micron n25q128a13 (atmel-quadspi.c driver)
>
> SPI 1-1-1: This mode was tested replacing SPI_NOR_QUAD by SPI_NOR_FAST as
> argument to spi_nor_scan() called from atmel_qspi_probe().
>
> SPI 1-1-4: Bootloaders (at91bootstrap/uboot) don't enable the Quad SPI
> mode of the Micron memory. When probed from Linux, the memory
> uses its Extended SPI mode and replies to the regular Read ID
> (0x9f) command.
>
> SPI 4-4-4: The romcode enabled the Quad SPI mode the of Micron memory
> before loading the at91bootstrap. When probed from Linux, the
> memory uses its Quad SPI mode and no longer replies to the
> regular Read ID (0x9f) command but instead to the Read ID
> Multiple I/O (0xaf) command. The memory expects ALL commands
> to use the SPI 4-4-4 protocol.
I'll admit I'm a little fuzzy on the differences between dual and quad
modes on various flash manufacturers. Can you help clear it up for me?
I think some of the comments on patch 2 help too, but I'll just comment
here for now.
It looks like the current driver has problems regarding the non 1-x-y
modes (e.g., 4-4-4), right? But I see that spi-nor.c never tries to
send a 4_4_4 command; it only sets read_opcode to
SPINOR_OP_READ_1_1_{1,2,4}. So is this an oversight in patches like
Bean's patch?
commit 548cd3ab54da ("mtd: spi-nor: Add quad I/O support for Micron
SPI NOR")
Why would we even need to enable quad modes like that, if we're not
going to send the 4-4-4 opcodes?
My next question (if my understanding is roughly correct) is, do we need
the 4-4-4 modes, and what risks come with them? I understand we can
shorten the command and address phases, but does that alone yield much
performance benefit? And I think the risk is that a given system might
not be prepared for the flash to be in a 4-4-4 mode, if the boot code
tries to use 1-x-y commands.
Also, I see a lot of good comments in patch 2 about Spansion vs.
Macronix vs. Micron memories. I wonder if previous developers have
completely tested their patches, or if they're just reading the
datasheets... so, what kind have testing have you done? Do you have
samples of all these flash to test?
Regards,
Brian
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Bean Huo 霍斌斌 (beanhuo) <beanhuo@micron.com> |
|---|---|
| Date | 2015-12-08 07:30 +0100 |
| Subject | RE: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller |
| Message-ID | <qDjWG-76S-9@gated-at.bofh.it> |
| In reply to | #1285872 |
PiAtLS0tLU9yaWdpbmFsIE1lc3NhZ2UtLS0tLQ0KPiBGcm9tOiBCcmlhbiBOb3JyaXMgW21haWx0 bzpjb21wdXRlcnNmb3JwZWFjZUBnbWFpbC5jb21dDQo+IFNlbnQ6IFR1ZXNkYXksIERlY2VtYmVy IDA4LCAyMDE1IDM6MzUgQU0NCj4gVG86IEN5cmlsbGUgUGl0Y2hlbg0KPiBDYzogbGludXgtbXRk QGxpc3RzLmluZnJhZGVhZC5vcmc7IG5pY29sYXMuZmVycmVAYXRtZWwuY29tOyBtYXJleEBkZW54 LmRlOw0KPiB2aWduZXNockB0aS5jb207IGxpbnV4LWtlcm5lbEB2Z2VyLmtlcm5lbC5vcmc7DQo+ IGxpbnV4LWFybS1rZXJuZWxAbGlzdHMuaW5mcmFkZWFkLm9yZzsgZGV2aWNldHJlZUB2Z2VyLmtl cm5lbC5vcmc7DQo+IHJvYmgrZHRAa2VybmVsLm9yZzsgcGF3ZWwubW9sbEBhcm0uY29tOyBtYXJr LnJ1dGxhbmRAYXJtLmNvbTsNCj4gaWpjK2RldmljZXRyZWVAaGVsbGlvbi5vcmcudWs7IGdhbGFr QGNvZGVhdXJvcmEub3JnOyBCZWFuIEh1byC79LHzsfMNCj4gKGJlYW5odW8pDQo+IFN1YmplY3Q6 IFJlOiBbUEFUQ0ggbGludXgtbmV4dCAwLzVdIG10ZDogc3BpLW5vcjogYWRkIGRyaXZlciBmb3Ig QXRtZWwgUVNQSQ0KPiBjb250cm9sbGVyDQo+IA0KPiArIEJlYW4gSHVvDQo+IA0KPiBIaSBDeXJp bGxlLA0KPiANCj4gT24gTW9uLCBEZWMgMDcsIDIwMTUgYXQgMDM6MDk6MDlQTSArMDEwMCwgQ3ly aWxsZSBQaXRjaGVuIHdyb3RlOg0KPiA+IEhpIGFsbCwNCj4gPg0KPiA+IHRoaXMgc2VyaWVzIG9m IHBhdGNoZXMgYWRkcyBzdXBwb3J0IHRvIHRoZSBBdG1lbCBRU1BJIGNvbnRyb2xsZXINCj4gPiBh dmFpbGFibGUgb24gc2FtYTVkMiBTb0NzLiBJdCB3YXMgdGVzdGVkIG9uIGEgc2FtYTVkMiB4cGxh aW5lZCB1bHRyYQ0KPiA+IGJvYXJkIHdpdGggYSBNaWNyb24gbjI1cTEyOGExMyBRU1BJIG1lbW9y eSBhbmQgYSBhdDI1ZGYzMjFhIFNQSQ0KPiBtZW1vcnkuDQo+ID4NCj4gPiBJbiBvcmRlciB0byB1 c2UgdGhlIE1pY3JvbiBtZW1vcnkgaW4gaXRzIFF1YWQgU1BJIG1vZGUsIHRoZSBzcGktbm9yDQo+ ID4gZnJhbWV3b3JrIG5lZWRlZCB0byBiZSBwYXRjaGVkIHRvIGZpeCB0aGUgc3VwcG9ydCBvZiBR dWFkL0R1YWwgU1BJDQo+ID4gcHJvdG9jb2xzIHdpdGggc29tZSBtZW1vcnkgbWFudWZhY3R1cmVy cyBzdWNoIGFzIFNwYW5zaW9uLCBNaWNyb24gYW5kDQo+ID4gTWFjcm9uaXguIFRoZXJlIGFyZSBt YW55IGNvbW1lbnRzIGluIHRoZSBzb3VyY2UgY29kZSB0byBleHBsYWluIHRoZQ0KPiA+IGltcGxl bWVudGF0aW9uIGNob2ljZXMgYmFzZWQgb24gdGhlIGRhdGFzaGVldHMgZnJvbSBtZW1vcnkNCj4g bWFudWZhY3R1cmVycy4NCj4gPg0KPiA+DQo+ID4gVGhpcyBzZXJpZXMgd2FzIGJhc2VkIGFuZCB0 ZXN0ZWQgb24gbGludXgtbmV4dC0yMDE1MTIwNw0KPiA+DQo+ID4gMSAtIEF0bWVsIFFTUEkgKyBN aWNyb24gbjI1cTEyOGExMyAoYXRtZWwtcXVhZHNwaS5jIGRyaXZlcikNCj4gPg0KPiA+IFNQSSAx LTEtMTogVGhpcyBtb2RlIHdhcyB0ZXN0ZWQgcmVwbGFjaW5nIFNQSV9OT1JfUVVBRCBieQ0KPiBT UElfTk9SX0ZBU1QgYXMNCj4gPiAgICAgICAgICAgIGFyZ3VtZW50IHRvIHNwaV9ub3Jfc2Nhbigp IGNhbGxlZCBmcm9tIGF0bWVsX3FzcGlfcHJvYmUoKS4NCj4gPg0KPiA+IFNQSSAxLTEtNDogQm9v dGxvYWRlcnMgKGF0OTFib290c3RyYXAvdWJvb3QpIGRvbid0IGVuYWJsZSB0aGUgUXVhZCBTUEkN Cj4gPiAgICAgICAgICAgIG1vZGUgb2YgdGhlIE1pY3JvbiBtZW1vcnkuIFdoZW4gcHJvYmVkIGZy b20gTGludXgsIHRoZQ0KPiBtZW1vcnkNCj4gPiAgICAgICAgICAgIHVzZXMgaXRzIEV4dGVuZGVk IFNQSSBtb2RlIGFuZCByZXBsaWVzIHRvIHRoZSByZWd1bGFyIFJlYWQgSUQNCj4gPiAgICAgICAg ICAgICgweDlmKSBjb21tYW5kLg0KPiA+DQo+ID4gU1BJIDQtNC00OiBUaGUgcm9tY29kZSBlbmFi bGVkIHRoZSBRdWFkIFNQSSBtb2RlIHRoZSBvZiBNaWNyb24gbWVtb3J5DQo+ID4gICAgICAgICAg ICBiZWZvcmUgbG9hZGluZyB0aGUgYXQ5MWJvb3RzdHJhcC4gV2hlbiBwcm9iZWQgZnJvbSBMaW51 eCwNCj4gdGhlDQo+ID4gICAgICAgICAgICBtZW1vcnkgdXNlcyBpdHMgUXVhZCBTUEkgbW9kZSBh bmQgbm8gbG9uZ2VyIHJlcGxpZXMgdG8gdGhlDQo+ID4gICAgICAgICAgICByZWd1bGFyIFJlYWQg SUQgKDB4OWYpIGNvbW1hbmQgYnV0IGluc3RlYWQgdG8gdGhlIFJlYWQgSUQNCj4gPiAgICAgICAg ICAgIE11bHRpcGxlIEkvTyAoMHhhZikgY29tbWFuZC4gVGhlIG1lbW9yeSBleHBlY3RzIEFMTA0K PiBjb21tYW5kcw0KPiA+ICAgICAgICAgICAgdG8gdXNlIHRoZSBTUEkgNC00LTQgcHJvdG9jb2wu DQo+IA0KPiBJJ2xsIGFkbWl0IEknbSBhIGxpdHRsZSBmdXp6eSBvbiB0aGUgZGlmZmVyZW5jZXMg YmV0d2VlbiBkdWFsIGFuZCBxdWFkIG1vZGVzIG9uDQo+IHZhcmlvdXMgZmxhc2ggbWFudWZhY3R1 cmVycy4gQ2FuIHlvdSBoZWxwIGNsZWFyIGl0IHVwIGZvciBtZT8NCg0KRm9yIE1pY3JvbiBTUEkg Tk9SIHNwaSBxdWFkIG1vZGUsIG1lYW5zIHRoYXQgUWF1ZCBJL08gcHJvdG90b2NvbCwgaXQgZm9s bG93cyBJL08NCkJ1cyB3aWR0aCBpcyBjb21tYW5kLWFkZHJlc3MtRGF0YSA0LTQtNCwgYXQgdGhp cyB0aW1lLCBEUTAsRFExLERRMixEUTMNCmFyZSBhbGwgdXNlZCB0byB0cmFuc2ZlciBhZGRyZXNz L2NvbW1hbmQvZGF0YS4gRm9yIHRoaXMgbWF5YmUgbm90IHRoZSBzYW1lIGJldHdlZW4NCmRpZmZl cmVudCBmbGFzaCBtYW51ZmFjdHVyZXMuIEZvciBleGFtcGxlLCBmb3IgU3BhbnNpb24gUXNwaSBO T1IsIGl0cyBhbGwgaW5zdHJ1Y3Rpb25zIGFyZQ0KdHJhbnNmZXJyZWQgZnJvbSBob3N0IHRvIG1l bW9yeSBhcyBhIHNpbmdsZSBiaXQgc2VyaWFsIHNlcXVlbmNlIG9uIHRoZSBEUTAgc2lnbmFsLCBl dmVuIHVuZGVyDQpRdWFkIG1vZGUuICBEdWFsIG1vZGUgdGhlIHNhbWUgYXMgUWF1ZCBtb2RlIHNj ZW5hcmlvLg0KDQpmb3IgU1BJIE5PUiAxLTEtNCwgbWVhbnMgY29tbWFuZCBhbmQgYWRkcmVzcyBh cmUgdHJhbnNmZXJyZWQgb24gdGhlIERRMCwNCmJ1dCBmb3IgZGF0YSwgYmVpbmcgdHJhbnNmZXJy ZWQgb24gRFEwLERRMSxEUTIsRFEzLkZvciB0aGlzLCBpdCBpcyB0aGUgc2FtZQ0KYmV0d2VlbiBk aWZmZXJlbnQgZmxhc2ggbWFudWZhY3R1cmVycy4gT2YgY291cnNlLCBhdCB0aGlzIG1vbWVudCwg U1BJIE5PUg0Kc2hvdWxkIHdvcmsgdW5kZXIgZXh0ZW5kZWQgSS9PIG1vZGUuDQoNCj4gSSB0aGlu ayBzb21lIG9mIHRoZSBjb21tZW50cyBvbiBwYXRjaCAyIGhlbHAgdG9vLCBidXQgSSdsbCBqdXN0 IGNvbW1lbnQgaGVyZQ0KPiBmb3Igbm93Lg0KPiBJdCBsb29rcyBsaWtlIHRoZSBjdXJyZW50IGRy aXZlciBoYXMgcHJvYmxlbXMgcmVnYXJkaW5nIHRoZSBub24gMS14LXkgbW9kZXMNCj4gKGUuZy4s IDQtNC00KSwgcmlnaHQ/IEJ1dCBJIHNlZSB0aGF0IHNwaS1ub3IuYyBuZXZlciB0cmllcyB0byBz ZW5kIGEgNF80XzQNCj4gY29tbWFuZDsgaXQgb25seSBzZXRzIHJlYWRfb3Bjb2RlIHRvIFNQSU5P Ul9PUF9SRUFEXzFfMV97MSwyLDR9LiBTbyBpcw0KPiB0aGlzIGFuIG92ZXJzaWdodCBpbiBwYXRj aGVzIGxpa2UgQmVhbidzIHBhdGNoPw0KDQpGb3IgU1BJTk9SX09QX1JFQURfMV8xX3sxLDIsNH0g Y29tbWFuZHMsIFNwaSBOT1IgYWN0dWFsbHkgd29ya3MgdW5kZXINCkV4dGVuZGVkIEkvTyBtb2Rl LCBub3QgUXVhZCBtb2RlLiBUaGV5IGp1c3QgcHVzaCBTcGkgTk9SIG91dHB1dCBkYXRhIGJ5IFF1 YWQgbW9kZSwNCkNvbW1hbmQgYW5kIGFkZHJlc3Mgc3RpbGwgZm9sbG93aW5nIGV4dGVuZGVkIEkv TyBtb2RlLg0KRm9yIDQtNC00IEkvTyBwcm90b2NvbCwgU1BJIE5PUiBzaG91bGQgY2hhbmdlIHRv IFF1YWQgbW9kZShqdXN0IGFzIG15IHBhdGNoKSwgDQpvZiBjb3Vyc2UsIFNQSSBjb250cm9sbGVy IHNob3VsZCBzdXBwb3J0IHRoaXMuIGZvciBNaWNyb24gUXNwaSBOT1IsIHVuZGVyIHF1YWQgbW9k ZSwNCmFsbCBjb21tYW5kcy9hZGRyZXNzL2RhdGEgYXJlIHRyYW5zZmVycmVkIG9uIERRMCxEUTEs RFEyLERRMyBzaWduYWxzLiBObyBtYXR0ZXIgd2hhdA0Ka2luZCBvZiBjb21tYW5kLiANCg0KDQo+ ICAgICBjb21taXQgNTQ4Y2QzYWI1NGRhICgibXRkOiBzcGktbm9yOiBBZGQgcXVhZCBJL08gc3Vw cG9ydCBmb3IgTWljcm9uDQo+ICAgICBTUEkgTk9SIikNCj4gDQo+IFdoeSB3b3VsZCB3ZSBldmVu IG5lZWQgdG8gZW5hYmxlIHF1YWQgbW9kZXMgbGlrZSB0aGF0LCBpZiB3ZSdyZSBub3QgZ29pbmcN Cj4gdG8gc2VuZCB0aGUgNC00LTQgb3Bjb2Rlcz8NCkkgdGhpbmssIGluIG9yZGVyIHRvIGhpZ2gg c3BlZWQgU1BJIE5PUiwgYWZ0ZXIgZW5hYmxlIHF1YWQgbW9kZSwgDQpTUElOT1JfT1BfUkVBRF8x XzFfezEsMiw0fSBjb21tYW5kcyBkb24ndCBuZWVkIGFueSBtb3JlLCBub3JtYWwgcmVhZCBjb21t YW5kICgweDAzKQ0KQ2FuIGltcGxlbWVudCBhcyB0aGVtLg0KDQo+IE15IG5leHQgcXVlc3Rpb24g KGlmIG15IHVuZGVyc3RhbmRpbmcgaXMgcm91Z2hseSBjb3JyZWN0KSBpcywgZG8gd2UgbmVlZCB0 aGUNCj4gNC00LTQgbW9kZXMsIGFuZCB3aGF0IHJpc2tzIGNvbWUgd2l0aCB0aGVtPyBJIHVuZGVy c3RhbmQgd2UgY2FuIHNob3J0ZW4NCj4gdGhlIGNvbW1hbmQgYW5kIGFkZHJlc3MgcGhhc2VzLCBi dXQgZG9lcyB0aGF0IGFsb25lIHlpZWxkIG11Y2gNCj4gcGVyZm9ybWFuY2UgYmVuZWZpdD8gQW5k IEkgdGhpbmsgdGhlIHJpc2sgaXMgdGhhdCBhIGdpdmVuIHN5c3RlbSBtaWdodCBub3QgYmUNCj4g cHJlcGFyZWQgZm9yIHRoZSBmbGFzaCB0byBiZSBpbiBhIDQtNC00IG1vZGUsIGlmIHRoZSBib290 IGNvZGUgdHJpZXMgdG8gdXNlIDEteC15DQo+IGNvbW1hbmRzLg0KDQpBcyBmYXIgYXMgbXkgY3Vy cmVudCBleHBlcmllbmNlIGFuZCBrbm93bGVkZ2UsIHRoaXMgc3RpbGwgbmVlZCB0byBiZSBlbmFi bGVkLCBlc3BlY2lhbGx5IGZvcg0KZmFzdCBib290LCBhbmQgc29tZSBJT1QgZGV2aWNlcyB0byBz dG9yZSBpbmZvIGludG8gU1BJIE5PUi4NCkZvciB0aGlzIHBhdGNoZXMsIG15IGN1cnJlbnQgY29u Y2VybiBpcyB0aGF0IGhvc3Qgc2lkZSBob3cgdG8gZ2V0IGRpZmZlcmVudCBJL08gcHJvdG9jb2wg Y2hhbmdlcywNCmFuZCBkaXN0aW5ndWlzaCBiZXR3ZWVuIGRpZmZlcmVudCBmbGFzaCBtYW51ZmFj dHVyZXJzIEkvTyBtb2RlLg0KDQo+IEFsc28sIEkgc2VlIGEgbG90IG9mIGdvb2QgY29tbWVudHMg aW4gcGF0Y2ggMiBhYm91dCBTcGFuc2lvbiB2cy4NCj4gTWFjcm9uaXggdnMuIE1pY3JvbiBtZW1v cmllcy4gSSB3b25kZXIgaWYgcHJldmlvdXMgZGV2ZWxvcGVycyBoYXZlDQo+IGNvbXBsZXRlbHkg dGVzdGVkIHRoZWlyIHBhdGNoZXMsIG9yIGlmIHRoZXkncmUganVzdCByZWFkaW5nIHRoZSBkYXRh c2hlZXRzLi4uIHNvLA0KPiB3aGF0IGtpbmQgaGF2ZSB0ZXN0aW5nIGhhdmUgeW91IGRvbmU/IERv IHlvdSBoYXZlIHNhbXBsZXMgb2YgYWxsIHRoZXNlIGZsYXNoDQo+IHRvIHRlc3Q/DQo+IFJlZ2Fy ZHMsDQo+IEJyaWFuDQo= -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Bean Huo 霍斌斌 (beanhuo) <beanhuo@micron.com> |
|---|---|
| Date | 2015-12-08 07:50 +0100 |
| Subject | RE: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller |
| Message-ID | <qDkg1-7d6-5@gated-at.bofh.it> |
| In reply to | #1285872 |
PiANCj4gSSdsbCBhZG1pdCBJJ20gYSBsaXR0bGUgZnV6enkgb24gdGhlIGRpZmZlcmVuY2VzIGJl dHdlZW4gZHVhbCBhbmQgcXVhZCBtb2RlcyBvbg0KPiB2YXJpb3VzIGZsYXNoIG1hbnVmYWN0dXJl cnMuIENhbiB5b3UgaGVscCBjbGVhciBpdCB1cCBmb3IgbWU/DQoNCkZvciBTUEkgTk9SLCBjdXJy ZW50bHksIGRvbid0IGhhdmUgYW4gb2ZmaWNpYWwgc3RhbmRhcmQgdG8gZGVmaW5lIGFuIHVuaWZv cm0gUXVhZA0KSS9PIG1vZGUgcHJvdG9jb2wuIFNvIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUg c29tZSBkaWZmZXJlbmNlIGJldHdlZW4gRmxhc2gNClZlbmRvci4NCg== -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Cyrille Pitchen <cyrille.pitchen@atmel.com> |
|---|---|
| Date | 2015-12-08 11:30 +0100 |
| Subject | Re: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller |
| Message-ID | <qDnGW-18s-17@gated-at.bofh.it> |
| In reply to | #1285872 |
Hi Brian,
Le 07/12/2015 20:34, Brian Norris a écrit :
> + Bean Huo
>
> Hi Cyrille,
>
> On Mon, Dec 07, 2015 at 03:09:09PM +0100, Cyrille Pitchen wrote:
>> Hi all,
>>
>> this series of patches adds support to the Atmel QSPI controller available
>> on sama5d2 SoCs. It was tested on a sama5d2 xplained ultra board with a
>> Micron n25q128a13 QSPI memory and a at25df321a SPI memory.
>>
>> In order to use the Micron memory in its Quad SPI mode, the spi-nor
>> framework needed to be patched to fix the support of Quad/Dual SPI
>> protocols with some memory manufacturers such as Spansion, Micron and
>> Macronix. There are many comments in the source code to explain the
>> implementation choices based on the datasheets from memory manufacturers.
>>
>>
>> This series was based and tested on linux-next-20151207
>>
>> 1 - Atmel QSPI + Micron n25q128a13 (atmel-quadspi.c driver)
>>
>> SPI 1-1-1: This mode was tested replacing SPI_NOR_QUAD by SPI_NOR_FAST as
>> argument to spi_nor_scan() called from atmel_qspi_probe().
>>
>> SPI 1-1-4: Bootloaders (at91bootstrap/uboot) don't enable the Quad SPI
>> mode of the Micron memory. When probed from Linux, the memory
>> uses its Extended SPI mode and replies to the regular Read ID
>> (0x9f) command.
>>
>> SPI 4-4-4: The romcode enabled the Quad SPI mode the of Micron memory
>> before loading the at91bootstrap. When probed from Linux, the
>> memory uses its Quad SPI mode and no longer replies to the
>> regular Read ID (0x9f) command but instead to the Read ID
>> Multiple I/O (0xaf) command. The memory expects ALL commands
>> to use the SPI 4-4-4 protocol.
>
> I'll admit I'm a little fuzzy on the differences between dual and quad
> modes on various flash manufacturers. Can you help clear it up for me?
> I think some of the comments on patch 2 help too, but I'll just comment
> here for now.
>
> It looks like the current driver has problems regarding the non 1-x-y
> modes (e.g., 4-4-4), right? But I see that spi-nor.c never tries to
> send a 4_4_4 command; it only sets read_opcode to
> SPINOR_OP_READ_1_1_{1,2,4}. So is this an oversight in patches like
> Bean's patch?
>
> commit 548cd3ab54da ("mtd: spi-nor: Add quad I/O support for Micron
> SPI NOR")
>
> Why would we even need to enable quad modes like that, if we're not
> going to send the 4-4-4 opcodes?
First let me clarify one point. This series focuses on Spansion, Macronix
and Micron because when I wrote its patches few months ago, the spi-nor.c
driver supported QSPI protocols only for these three only manufacturers.
Today I notice that now some Winbond memories also use the
SPI_NOR_QUAD_READ flag. Though I try to deal with all manufacturers on a
equal foot, I currently have no knowledge on Windond memories so my
explanations only apply to Spansion, Macronix and Micron memories.
About the SPI 4-4-4 protocol, it is only supported by Micron and Macronix
memories but not by Spansion. Both Micron and Macronix memories provide us
with a special mode, let's call it the Quad SPI mode. As Bean Huo explained
for Micron, once this mode is enabled, the memory expects all commands to
use the SPI 4-4-4 protocol. Hence even if the spi-nor.c driver uses the Fast
Read Quad Output 1-1-4 (0x6b/0x6c) or the Fast Read Quad I/O 1-4-4 (0xeb/0xec)
op codes, the command is interpreted as a Fast Read Quad Command 4-4-4 so must
use the SPI 4-4-4 protocol. As far as I know, there is no currently existing
op code dedicated to the Fast Read Quad Command 4-4-4.
So the op codes may be confusing but when the Quad SPI mode is enabled we
actually use Fast Read 4-4-4 commands.
>
> My next question (if my understanding is roughly correct) is, do we need
> the 4-4-4 modes, and what risks come with them? I understand we can
> shorten the command and address phases, but does that alone yield much
> performance benefit? And I think the risk is that a given system might
> not be prepared for the flash to be in a 4-4-4 mode, if the boot code
> tries to use 1-x-y commands.
>
I did not run comparative tests between Fast Read 1-1-4, Fast Read 1-4-4
and Fast Read 4-4-4 yet. Honestly I don't expect much difference. However
performances were not the main purpose when I wrote these patches.
Actually the Quad SPI mode of Macronix and Micron comes with a side effect.
Once enable, not only the memory now expects all commands to use the
SPI 4-4-4 protocol but it no longer replies to the regular Read JEDEC ID
command (0x9f). Instead it replies to a new command, the Read JEDEC ID
Multiple I/O (0xaf).
Now let's imagine that the Quad SPI mode is either permanently enabled at
reset using non-volatile configuration registers or enabled by a early
bootloader. When the spi-nor driver tries to probe the memory, the 0x9f
command fails: the driver should adapt, that's why it tries other protocols
such as SPI 4-4-4 (Micron and Macronix) and SPI 2-2-2 (Micron only when in
Dual SPI mode) with the 0xaf command. When we finally get a valid JEDEC ID,
we also know the current mode of memory.
Then the global policy of this series of patches is:
1 - try to configure the QSPI memory as asked by the user, ie according to
the read_mode argument of spi_nor_scan().
2 - use volatile bits in configuration registers as much as possible, try to
avoid changing non-volatile bits hence bootloaders should drive the
QSPI memory always in the same state after reset.
3 - check the current state/mode the the QSPI memory and adapt to it in order
to limit the configuration changes
4 - try not to introduce regression ;)
For instance with Micron memories, about the choice between the Extended
SPI mode and the Quad SPI mode, I've currently chosen to keep on using the
detected mode during the probing phase (Read JEDED ID).
Indeed the Extended SPI mode does support the Fast Read 1-1-4 command,
which should not be very different from Fast Read x-4-4 commands thinking
about performances. So I thought it was safer not to change the mode rather
than forcing the Quad SPI mode. Actually using Fast Read 1-1-4 in Extended
SPI mode only requires the SPI_RX_QUAD capability from the SPI controller
whereas the Quad SPI mode would also require the SPI_TX_QUAD capability.
Referring to the m25p80 driver, the mode argument of spi_nor_scan() is set
to SPI_NOR_QUAD if and only if the SPI controller has the SPI_RX_QUAD flag.
That's not enough to know whether the SPI controller could support to
enable the Quad SPI mode of the Micron memory.
However, if the Quad SPI mode was already enabled and the spi-nor driver
succeeded in probing the Micron memory with the 0xaf command in SPI 4-4-4
protocol, we know that the SPI controller does support the SPI 4-4-4
protocol then we can safely leave the Quad SPI mode of the Micron memory
enabled. Same thing with Micron Dual SPI mode ant the support of the
SPI 2-2-2 protocol by the SPI controller.
Here is my understanding of the volatile/non-volatile bits in
the configuration registers for each QSPI manufacturer according to their
datasheets. Please note that some manufacturers such as Micron provide
both volatile and non-volatile registers to configure some settings.
Micron Macronix Spansion
Quad/Dual SPI Mode volatile capable persistent only persistent only
# of dummy cycles volatile capable volatile capable persistent only
I think the only place where I may change a non-volatile bit is in
spansion_set_quad_mode() when setting the QUAD bit in the Configuration
Register 1 (CR1) if the user asked to use a Quad SPI protocol but the QUAD
bit was not set yet. That's why I added a dev_warn() message.
About the number of dummy cycles, the spi-nor framework adapts to the current
setting of a Spansion memory.
> Also, I see a lot of good comments in patch 2 about Spansion vs.
> Macronix vs. Micron memories. I wonder if previous developers have
> completely tested their patches, or if they're just reading the
> datasheets... so, what kind have testing have you done? Do you have
> samples of all these flash to test?
>
I want to be totally honest on this point: I did NOT test with Macronix
memories yet simply because I have no such memory from this manufacturer.
I guess Atmel planed to purchase some samples because we also need to test
their support in the sama5d2 romcode when booting from QSPI.
So for the Macronix case, the patches are only based on my current
understanding of Macronix datasheets (MX66L1G45G, 3V, 1Gb, v1.0.pdf).
On the other hand I did many tests with both Micron and Spansion memories
with sama5d2 SoCs, either with Linux or with the sama5d2 romcode ("normal"
Fast Read x-y-4 but also eXecution In Place using the Continuous Read mode:
the XIP mode is not relevant for the Linux spi-nor framework but it should
take care of the "dummy cycles" it sends to avoid entering into the
Continuous Read mode by mistake).
I used at least these memories under Linux:
- Micron n25q128a13: sama5d2 xplained ultra + linux-next 20151207 /
at91 linux 4.1
- Micron n25q256: sama5d2 prototype (FPGA) + at91 linux 3.18
- Spansion s25fl512: sama5d2 prototype (FPGA) + at91 linux 3.18
Maybe Marek also did some tests on his side.
I hope I've answered your questions, please don't hesitate to comment/ask more
questions. I'll do my best to answer them! :)
> Regards,
> Brian
>
Best Regards,
Cyrille
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Cyrille Pitchen <cyrille.pitchen@atmel.com> |
|---|---|
| Date | 2015-12-08 15:40 +0100 |
| Subject | Re: [PATCH linux-next 0/5] mtd: spi-nor: add driver for Atmel QSPI controller |
| Message-ID | <qDrAS-3yq-39@gated-at.bofh.it> |
| In reply to | #1286381 |
Le 08/12/2015 11:25, Cyrille Pitchen a écrit :
> Hi Brian,
>
> Le 07/12/2015 20:34, Brian Norris a écrit :
[...]
>> Also, I see a lot of good comments in patch 2 about Spansion vs.
>> Macronix vs. Micron memories. I wonder if previous developers have
>> completely tested their patches, or if they're just reading the
>> datasheets... so, what kind have testing have you done? Do you have
>> samples of all these flash to test?
>>
>
> I want to be totally honest on this point: I did NOT test with Macronix
> memories yet simply because I have no such memory from this manufacturer.
> I guess Atmel planed to purchase some samples because we also need to test
> their support in the sama5d2 romcode when booting from QSPI.
> So for the Macronix case, the patches are only based on my current
> understanding of Macronix datasheets (MX66L1G45G, 3V, 1Gb, v1.0.pdf).
>
> On the other hand I did many tests with both Micron and Spansion memories
> with sama5d2 SoCs, either with Linux or with the sama5d2 romcode ("normal"
> Fast Read x-y-4 but also eXecution In Place using the Continuous Read mode:
> the XIP mode is not relevant for the Linux spi-nor framework but it should
> take care of the "dummy cycles" it sends to avoid entering into the
> Continuous Read mode by mistake).
> I used at least these memories under Linux:
> - Micron n25q128a13: sama5d2 xplained ultra + linux-next 20151207 /
> at91 linux 4.1
> - Micron n25q256: sama5d2 prototype (FPGA) + at91 linux 3.18
> - Spansion s25fl512: sama5d2 prototype (FPGA) + at91 linux 3.18
>
I forgot few points. First, I did all Quad SPI tests using the Atmel QSPI
controller and driver.
I did no Dual SPI protocol test yet, only Quad SPI.
I'm able to test neither Quad nor Dual SPI protocols with the Atmel QSPI
protocol + m25p80 driver: this driver is not really suited for handling
the Atmel QSPI controller in its Serial Memory Mode.
However I did some non regression tests with the m25p80 driver used on the
regular Atmel SPI controller and driver (drivers/spi/spi-atmel.c) to
access a at25df312a memory (SPI 1-1-1 protocol).
Nonetheless the m25p80 driver was taken into account when I wrote the series.
Indeed Micron, Macronix and Spansion's datasheets provide tables giving the
number of dummy cycles to use depending on both the SPI bus clock
frequency and the Fast Read command. Hence in spi-nor.c, patch 2 sets
nor->read_dummy to the relevant number of dummy cycles (not bits).
Taking Spansion memories as an example, for their factory default Latency
Code value of 0, they expect:
- 8 dummy cycles for Fast Read 1-1-1 (0x0b / 0x0c)
- 8 dummy cycles for Fast Read 1-1-2 (0x3b / 0x3c)
- 8 dummy cycles for Fast Read 1-1-4 (0x6b / 0x6c)
Fast Read 1-2-2 and 1-4-4 are not used with Spansion memories since the
number of dummy cycles to be used with one of these op codes is not suited
for the m25p80 driver.
For Micron and Macronix, except when their Quad SPI mode is enabled, the
Fast Read 1-1-4 or Fast Read 1-1-2 commands are preferred to the
Fast Read 1-4-4 or Fast Read 1-2-2 commands. The number of dummy cycles is
set to 0 for Read command (0x03) and 8 for other Fast Read commands since
this setting update can be done safely writing into volatile bits inside
configuration registers. A multiple of 8 dummy cycles is suited for the
m25p80_read() implementation.
Then looking at the m25p80 driver, the original (unchanged) cycles/bytes
conversion from the m25p80_read() hook only works with SPI 1-1-x protocols:
/* convert the dummy cycles to the number of bytes */
dummy /= 8;
As a matter of fact, 8 dummy cycles stand for:
- 8 bits (1 byte ) for SPI 1-1-z protocols
- 16 bits (2 bytes) for SPI x-2-z protocols
- 32 bits (4 bytes) for SPI x-4-z protocols
So I should also fix the above conversion in patch 3 to cover more SPI
protocols.
Best Regards,
Cyrille
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web