2015-07-16 15:27:39

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 0/7] add driver for Atmel QSPI controller

Hi all,

in order to be able to use Micron Quad SPI memories with the new Atmel QSPI
controller I added a new set_protocol() callback into struct spi_nor.
Indeed, once the quad I/O mode has been enabled on a Micron QSPI flash by
clearing bit 7 in its Enhanced Volatile Configuration Register, this memory
expects all the following commands to use the QSPI 4-4-4 protocol. So the
QSPI controller needs to be notified about this protocol change before
reading the Status register, 0x05 command sent by the
spi_nor_wail_till_ready() function, in micron_quad_enable().

Reading spi-nor.h, I saw an interesting struct spi_nor_xfer_cfg and its
associated callbacks write_xfer() and read_xfer(). However these callbacks
don't seem to be used at all for now. They look to have been designed to
add QSPI support to the generic spi-nor framework. So before extending the
framework with my own callback, I'd like to know whether I should have
tried to used the read_xfer()/write_xfer() callbacks to join an already
existing effort to add QSPI support.
If so, I think either the read/write_reg() prototypes should be updated or
new callbacks should be added for register reads/writes. As explained
above, even for reading the Status register the QSPI 4-4-4 protocol must be
used with Micron memories in quad I/O mode. So the framework has to find a
way to tell the QSPI controller which protocol it should use.

So I'm interested in your comments! :)

Best Regards,

Cyrille

ChangeLog

v1:

This series of patches add support for the new Atmel QSPI controller
embedded inside sama5d2x SoCs.

These patches were first developped for linux-3.18-at91 and tested on a
sama5d27 Xplained ultra board, which embeds a Micron n25q128a13 QSPI NOR
flash memory. Then the series was adapted for mainline.

Cyrille Pitchen (7):
Documentation: mtd: add a DT property to set the number of dummy
cycles
mtd: spi-nor: notify (Q)SPI controller about protocol change
mtd: spi-nor: allow to tune the number of dummy cycles
Documentation: mtd: add a DT property to set the latency code of
Spansion memory
mtd: spi-nor: allow the set the latency code on Spansion memories
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 | 29 +
.../devicetree/bindings/mtd/jedec,spi-nor.txt | 6 +
.../devicetree/bindings/mtd/spansion-nor.txt | 22 +
drivers/mtd/spi-nor/Kconfig | 7 +
drivers/mtd/spi-nor/Makefile | 1 +
drivers/mtd/spi-nor/atmel-quadspi.c | 901 +++++++++++++++++++++
drivers/mtd/spi-nor/spi-nor.c | 160 +++-
include/linux/mtd/spi-nor.h | 15 +
8 files changed, 1122 insertions(+), 19 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
create mode 100644 Documentation/devicetree/bindings/mtd/spansion-nor.txt
create mode 100644 drivers/mtd/spi-nor/atmel-quadspi.c

--
1.8.2.2


2015-07-16 15:27:46

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 1/7] Documentation: mtd: add a DT property to set the number of dummy cycles

Depending on the SPI clock frequency, the Fast Read op code and the
Single/Dual Data Rate mode, the number of dummy cycles can be tuned to
improve transfer speed.
The actual number of dummy cycles is specific for each memory model and is
provided by the manufacturer thanks to the memory datasheet.

Signed-off-by: Cyrille Pitchen <[email protected]>
---
Documentation/devicetree/bindings/mtd/jedec,spi-nor.txt | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.txt b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.txt
index 2bee68103b01..4387567d8024 100644
--- a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.txt
+++ b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.txt
@@ -19,6 +19,11 @@ Optional properties:
all chips and support for it can not be detected at runtime.
Refer to your chips' datasheet to check if this is supported
by your chip.
+- m25p,num-dummy-cycles : Set the number of dummy cycles for Fast Read commands.
+ Depending on the manufacturer additional dedicated
+ commands are sent to the flash memory so the
+ controller and the memory can agree on the number of
+ dummy cycles to use.

Example:

@@ -29,4 +34,5 @@ Example:
reg = <0>;
spi-max-frequency = <40000000>;
m25p,fast-read;
+ m25p,num-dummy-cycles = <8>;
};
--
1.8.2.2

2015-07-16 15:29:52

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 2/7] mtd: spi-nor: notify (Q)SPI controller about protocol change

Once the Quad SPI mode has been enabled on a Micron flash memory, this
device expects ALL the following commands to use the SPI 4-4-4 protocol.
The (Q)SPI controller needs to be notified about the protocol change so it
can adapt and keep on dialoging with the Micron memory.

Signed-off-by: Cyrille Pitchen <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 17 +++++++++++++++++
include/linux/mtd/spi-nor.h | 13 +++++++++++++
2 files changed, 30 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index d78831b4422b..93627d4e6be8 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -163,6 +163,18 @@ static inline int write_disable(struct spi_nor *nor)
return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0, 0);
}

+/*
+ * Notify the (Q)SPI controller about the new protocol to be used.
+ */
+static inline int spi_nor_set_protocol(struct spi_nor *nor,
+ enum spi_protocol proto)
+{
+ if (nor->set_protocol)
+ return nor->set_protocol(nor, proto);
+
+ return 0;
+}
+
static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
{
return mtd->priv;
@@ -943,6 +955,11 @@ static int micron_quad_enable(struct spi_nor *nor)
return ret;
}

+ /* switch protocol to Quad CMD 4-4-4 */
+ ret = spi_nor_set_protocol(nor, SPI_PROTO_4_4_4);
+ if (ret)
+ return ret;
+
ret = spi_nor_wait_till_ready(nor);
if (ret)
return ret;
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index e5409524bb0a..1bf6f11310ef 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -87,6 +87,16 @@ enum read_mode {
SPI_NOR_QUAD,
};

+enum spi_protocol {
+ SPI_PROTO_1_1_1, /* SPI */
+ SPI_PROTO_1_1_2, /* Dual Output */
+ SPI_PROTO_1_1_4, /* Quad Output */
+ SPI_PROTO_1_2_2, /* Dual IO */
+ SPI_PROTO_1_4_4, /* Quad IO */
+ SPI_PROTO_2_2_2, /* Dual Command */
+ SPI_PROTO_4_4_4, /* Quad Command */
+};
+
/**
* struct spi_nor_xfer_cfg - Structure for defining a Serial Flash transfer
* @wren: command for "Write Enable", or 0x00 for not required
@@ -149,6 +159,7 @@ enum spi_nor_option_flags {
* read/write/erase/lock/unlock operations
* @read_xfer: [OPTIONAL] the read fundamental primitive
* @write_xfer: [OPTIONAL] the writefundamental primitive
+ * @set_protocol: [OPTIONAL] notify about protocol change
* @read_reg: [DRIVER-SPECIFIC] read out the register
* @write_reg: [DRIVER-SPECIFIC] write data to the register
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
@@ -185,6 +196,8 @@ struct spi_nor {
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
int write_enable);

+ int (*set_protocol)(struct spi_nor *nor, enum spi_protocol proto);
+
int (*read)(struct spi_nor *nor, loff_t from,
size_t len, size_t *retlen, u_char *read_buf);
void (*write)(struct spi_nor *nor, loff_t to,
--
1.8.2.2

2015-07-16 15:27:49

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 3/7] mtd: spi-nor: allow to tune the number of dummy cycles

The number of dummy cycles used during Fast Read commands can be reduced
to improve transfer performances. Each manufacturer has a dedicated set of
registers to provide the memory with the exact number of dummy cycles it
should expect. Both the memory and the (Q)SPI controller must agree on
this number of dummy cycles.

The number of dummy cycles can be found into the memory datasheet and
mostly depends on the SPI clock frequency, the Fast Read op code and the
Single/Dual Data Rate mode.

Probing JEDEC Serial Flash Discoverable Parameters (SFDP) tables would
only provide the driver with a high enough number of dummy cycles for each
Fast Read command to be used for all clock frequencies: this solution
would not be optimized.

Signed-off-by: Cyrille Pitchen <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 97 ++++++++++++++++++++++++++++++++++---------
include/linux/mtd/spi-nor.h | 2 +
2 files changed, 80 insertions(+), 19 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 93627d4e6be8..5df6e4712a9e 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -119,24 +119,6 @@ static int read_cr(struct spi_nor *nor)
}

/*
- * Dummy Cycle calculation for different type of read.
- * It can be used to support more commands with
- * different dummy cycle requirements.
- */
-static inline int spi_nor_read_dummy_cycles(struct spi_nor *nor)
-{
- switch (nor->flash_read) {
- case SPI_NOR_FAST:
- case SPI_NOR_DUAL:
- case SPI_NOR_QUAD:
- return 8;
- case SPI_NOR_NORMAL:
- return 0;
- }
- return 0;
-}
-
-/*
* Write status register 1 byte
* Returns negative if error occurred.
*/
@@ -1007,6 +989,81 @@ static int set_quad_mode(struct spi_nor *nor, struct flash_info *info)
}
}

+static int micron_set_dummy_cycles(struct spi_nor *nor)
+{
+ int ret;
+ u8 val, mask;
+
+ /* read the Volatile Configuration Register (VCR) */
+ ret = nor->read_reg(nor, SPINOR_OP_RD_VCR, &val, 1);
+ if (ret < 0) {
+ dev_err(nor->dev, "error %d reading VCR\n", ret);
+ return ret;
+ }
+
+ write_enable(nor);
+
+ /* update the number of dummy into the VCR */
+ mask = GENMASK(7, 4);
+ val &= ~mask;
+ val |= (nor->read_dummy << 4) & mask;
+ ret = nor->write_reg(nor, SPINOR_OP_WR_VCR, &val, 1, 0);
+ if (ret < 0) {
+ dev_err(nor->dev, "error while writing VCR register\n");
+ return ret;
+ }
+
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Dummy Cycle calculation for different type of read.
+ * It can be used to support more commands with
+ * different dummy cycle requirements.
+ */
+static int spi_nor_read_dummy_cycles(struct spi_nor *nor,
+ const struct flash_info *info)
+{
+ struct device_node *np = nor->dev->of_node;
+ u32 num_dummy_cycles;
+
+ if (np && !of_property_read_u32(np, "m25p,num-dummy-cycles",
+ &num_dummy_cycles)) {
+ nor->read_dummy = num_dummy_cycles;
+
+ /*
+ * This switch block might be moved after the if...then...else
+ * statement but it was not tested with all Spansion or Micron
+ * memories.
+ * Now the "m25p,num-dummy-cycles" property needs to be
+ * explicitly set in the device tree so the switch statement is
+ * executed. This should avoid unwanted side effects and keep
+ * backward compatibility.
+ */
+ switch (JEDEC_MFR(info)) {
+ case CFI_MFR_ST:
+ return micron_set_dummy_cycles(nor);
+ default:
+ break;
+ }
+ } else {
+ switch (nor->flash_read) {
+ case SPI_NOR_FAST:
+ case SPI_NOR_DUAL:
+ case SPI_NOR_QUAD:
+ nor->read_dummy = 8;
+ case SPI_NOR_NORMAL:
+ nor->read_dummy = 0;
+ }
+ }
+
+ return 0;
+}
+
static int spi_nor_check(struct spi_nor *nor)
{
if (!nor->dev || !nor->read || !nor->write ||
@@ -1211,7 +1268,9 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
nor->addr_width = 3;
}

- nor->read_dummy = spi_nor_read_dummy_cycles(nor);
+ ret = spi_nor_read_dummy_cycles(nor, info);
+ if (ret)
+ return ret;

dev_info(dev, "%s (%lld Kbytes)\n", id->name,
(long long)mtd->size >> 10);
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 1bf6f11310ef..e03a4c4053d3 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -59,6 +59,8 @@
/* Used for Micron flashes only. */
#define SPINOR_OP_RD_EVCR 0x65 /* Read EVCR register */
#define SPINOR_OP_WD_EVCR 0x61 /* Write EVCR register */
+#define SPINOR_OP_RD_VCR 0x85 /* Read VCR register */
+#define SPINOR_OP_WR_VCR 0x81 /* Write VCR register */

/* Status Register bits. */
#define SR_WIP 1 /* Write in progress */
--
1.8.2.2

2015-07-16 15:27:51

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 4/7] Documentation: mtd: add a DT property to set the latency code of Spansion memory

Both the SPI controller and the NOR flash memory need to agree on the
number of dummy cycles to use for Fast Read commands. For Spansion
memories, this number of dummy cycles is not given directly but through a
so called "latency code".
The latency code can be found into the memory datasheet and depends on the
SPI clock frequency, the Fast Read op code and the Single/Dual Data Rate
mode.

Signed-off-by: Cyrille Pitchen <[email protected]>
---
.../devicetree/bindings/mtd/spansion-nor.txt | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/spansion-nor.txt

diff --git a/Documentation/devicetree/bindings/mtd/spansion-nor.txt b/Documentation/devicetree/bindings/mtd/spansion-nor.txt
new file mode 100644
index 000000000000..a55c62db0e6f
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/spansion-nor.txt
@@ -0,0 +1,22 @@
+* Spansion NOR flash
+
+Optional properties:
+- spansion,latency-code : Only used when the "m25p,num-dummy-cycles" property is
+ set. The Spansion latency code tells the NOR flash
+ memory the number of dummy cycles to expect for each
+ Fast Read command. The value to be used is provided by
+ tables in the memory datasheet and depends on the SPI
+ clock frequency and on the Single/Dual Data Rate mode.
+ Then the value of "m25p,num-dummy-cycles" property
+ should match the (Fast) Read command to be used for
+ the chosen latency code.
+
+Example:
+
+ m25p80@0 {
+ compatible = "spansion,s25fl512s";
+ reg = <0>;
+ spi-max-frequency = <104000000>;
+ m25p,num-dummy-cycles = <8>;
+ spansion,latency-code = <2>;
+ };
--
1.8.2.2

2015-07-16 15:27:54

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 5/7] mtd: spi-nor: allow the set the latency code on Spansion memories

Both the SPI controller and the flash memory must agree on the number of
dummy cycles to use for Fast Read commands. For Spansion memories, this
number of dummy cycles is configured through a so called latency code in
their Control Register.
The right latency code can be found in the memory datasheet and depends
on the SPI clock frequency, the op code of the Fast Read command and the
Single/Dual Data Rate mode.

Signed-off-by: Cyrille Pitchen <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 46 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 5df6e4712a9e..32fddf06da3f 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -989,6 +989,50 @@ static int set_quad_mode(struct spi_nor *nor, struct flash_info *info)
}
}

+static int spansion_set_latency_code(struct spi_nor *nor)
+{
+ struct device_node *np = nor->dev->of_node;
+ u8 cr, mask = GENMASK(7, 6);
+ u32 lc;
+ int ret;
+
+ if (!np || of_property_read_u32(np, "spansion,latency-code", &lc))
+ return 0;
+
+ if (lc & ~(mask >> 6)) {
+ dev_err(nor->dev, "invalid latency code: %u\n", lc);
+ return -EINVAL;
+ }
+
+ ret = read_cr(nor);
+ if (ret < 0) {
+ dev_err(nor->dev,
+ "error while reading configuration register\n");
+ return ret;
+ }
+
+ write_enable(nor);
+
+ cr = ret;
+ cr &= ~mask;
+ cr |= (lc << 6);
+ ret = write_sr_cr(nor, cr << 8);
+ if (ret < 0) {
+ dev_err(nor->dev,
+ "error while updating configuration register\n");
+ return -EINVAL;
+ }
+
+ /* read back and check it */
+ ret = read_cr(nor);
+ if (!(ret >= 0 && (ret & mask) == (lc << 6))) {
+ dev_err(nor->dev, "Spansion latency code not set\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int micron_set_dummy_cycles(struct spi_nor *nor)
{
int ret;
@@ -1045,6 +1089,8 @@ static int spi_nor_read_dummy_cycles(struct spi_nor *nor,
* backward compatibility.
*/
switch (JEDEC_MFR(info)) {
+ case CFI_MFR_AMD:
+ return spansion_set_latency_code(nor);
case CFI_MFR_ST:
return micron_set_dummy_cycles(nor);
default:
--
1.8.2.2

2015-07-16 15:28:45

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 6/7] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver

This patch documents the DT bindings for the driver of the Atmel QSPI
controller embedded inside sama5d2x SoCs.

Signed-off-by: Cyrille Pitchen <[email protected]>
---
.../devicetree/bindings/mtd/atmel-quadspi.txt | 29 ++++++++++++++++++++++
1 file changed, 29 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..a0d60ac7ae10
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
@@ -0,0 +1,29 @@
+* Atmel Quad Serial Peripheral Interface (QSPI)
+
+Required properties:
+ - compatible : Should be "atmel,sama5d2-qspi"
+ - reg : the first contains the register location and length,
+ the second contains the memory mapping address and length
+ - interrupts : Should contain the interrupt for the device
+ - clocks : The clock needed by the QSPI controller
+ - #address-cells : should be 1
+ - #size-cells : should be 0
+
+Example:
+
+qspi0: qspi@f0020000 {
+ compatible = "atmel,sama5d2-qspi";
+ reg = <0xf0020000 0x100>,
+ <0xd0000000 0x08000000>;
+ interrupts = <52 IRQ_TYPE_LEVEL_HIGH 7>;
+ clocks = <&qspi0_clk>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_qspi0_default>;
+ status = "okay";
+
+ m25p80@0 {
+ ...
+ };
+};
--
1.8.2.2

2015-07-16 15:27:59

by Cyrille Pitchen

[permalink] [raw]
Subject: [PATCH 7/7] mtd: atmel-quadspi: add driver for Atmel QSPI controller

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 <[email protected]>
---
drivers/mtd/spi-nor/Kconfig | 7 +
drivers/mtd/spi-nor/Makefile | 1 +
drivers/mtd/spi-nor/atmel-quadspi.c | 901 ++++++++++++++++++++++++++++++++++++
3 files changed, 909 insertions(+)
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 64a4f0edabc7..bcdda302f5ab 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -28,4 +28,11 @@ config SPI_FSL_QUADSPI
This enables support for the Quad SPI controller in master mode.
We only connect the NOR to this controller now.

+config SPI_ATMEL_QUADSPI
+ tristate "Atmel Quad SPI Controller"
+ depends on (ARCH_AT91 || COMPILE_TEST)
+ help
+ This enables support for the Quad SPI controller in master mode.
+ We only connect the NOR to this controller now.
+
endif # MTD_SPI_NOR
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index 6a7ce1462247..243ea8a479ef 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
+obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.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..b39d799f8fe4
--- /dev/null
+++ b/drivers/mtd/spi-nor/atmel-quadspi.c
@@ -0,0 +1,901 @@
+/*
+ * Driver for Atmel QSPI Controller
+ *
+ * Copyright (C) 2015 Atmel Corporation
+ *
+ * Author: Cyrille Pitchen <[email protected]>
+ *
+ * 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_OFFSET 0
+#define QSPI_CR_QSPIEN_SIZE 1
+#define QSPI_CR_QSPIDIS_OFFSET 1
+#define QSPI_CR_QSPIDIS_SIZE 1
+#define QSPI_CR_SWRST_OFFSET 7
+#define QSPI_CR_SWRST_SIZE 1
+#define QSPI_CR_LASTXFER_OFFSET 24
+#define QSPI_CR_LASTXFER_SIZE 1
+
+/* Bitfields in QSPI_MR (Mode Register) */
+#define QSPI_MR_SSM_OFFSET 0
+#define QSPI_MR_SSM_SIZE 1
+#define QSPI_MR_LLB_OFFSET 1
+#define QSPI_MR_LLB_SIZE 1
+#define QSPI_MR_WDRBT_OFFSET 2
+#define QSPI_MR_WDRBT_SIZE 1
+#define QPSI_MR_SMRM_OFFSET 3
+#define QSPI_MR_SMRM_SIZE 1
+#define QSPI_MR_CSMODE_OFFSET 4
+#define QSPI_MR_CSMODE_SIZE 2
+#define QSPI_MR_NBBITS_OFFSET 8
+#define QSPI_MR_NBBITS_SIZE 4
+#define QSPI_MR_NBBITS_8_BIT 0
+#define QSPI_MR_NBBITS_9_BIT 1
+#define QSPI_MR_NBBITS_10_BIT 2
+#define QSPI_MR_NBBITS_11_BIT 3
+#define QSPI_MR_NBBITS_12_BIT 4
+#define QSPI_MR_NBBITS_13_BIT 5
+#define QSPI_MR_NBBITS_14_BIT 6
+#define QSPI_MR_NBBITS_15_BIT 7
+#define QSPI_MR_NBBITS_16_BIT 8
+#define QSPI_MR_DLYBCT_OFFSET 16
+#define QSPI_MR_DLYBCT_SIZE 8
+#define QSPI_MR_DLYCS_OFFSET 24
+#define QSPI_MR_DLYCS_SIZE 8
+
+/* Bitfields in QSPI_SR/QSPI_IER/QSPI_IDR/QSPI_IMR */
+#define QSPI_SR_RDRF_OFFSET 0
+#define QSPI_SR_RDRF_SIZE 1
+#define QSPI_SR_TDRE_OFFSET 1
+#define QSPI_SR_TDRE_SIZE 1
+#define QSPI_SR_TXEMPTY_OFFSET 2
+#define QSPI_SR_TXEMPTY_SIZE 1
+#define QSPI_SR_OVRES_OFFSET 3
+#define QSPI_SR_OVRES_SIZE 1
+#define QSPI_SR_CSR_OFFSET 8
+#define QSPI_SR_CSR_SIZE 1
+#define QPSI_SR_CSS_OFFSET 9
+#define QPSI_SR_CSS_SIZE 1
+#define QSPI_SR_INSTRE_OFFSET 10
+#define QSPI_SR_INSTRE_SIZE 1
+#define QSPI_SR_QSPIENS_OFFSET 24
+#define QSPI_SR_QSPIENS_SIZE 1
+
+/* Bitfields in QSPI_SCR (Serial Clock Register) */
+#define QSPI_SCR_CPOL_OFFSET 0
+#define QSPI_SCR_CPOL_SIZE 1
+#define QSPI_SCR_CPHA_OFFSET 1
+#define QSPI_SCR_CPHA_SIZE 1
+#define QSPI_SCR_SCBR_OFFSET 8
+#define QSPI_SCR_SCBR_SIZE 8
+#define QSPI_SCR_DLYBS_OFFSET 16
+#define QSPI_SCR_DLYBS_SIZE 8
+
+/* Bitfields in QSPI_ICR (Instruction Code Register) */
+#define QSPI_ICR_INST_OFFSET 0
+#define QSPI_ICR_INST_SIZE 8
+#define QSPI_ICR_OPT_OFFSET 16
+#define QSPI_ICR_OPT_SIZE 8
+
+/* Bitfields in QSPI_IFR (Instruction Frame Register) */
+#define QSPI_IFR_WIDTH_OFFSET 0
+#define QSPI_IFR_WIDTH_SIZE 3
+#define QSPI_IFR_WIDTH_SINGLE_BIT_SPI 0
+#define QSPI_IFR_WIDTH_DUAL_OUTPUT 1
+#define QSPI_IFR_WIDTH_QUAD_OUTPUT 2
+#define QSPI_IFR_WIDTH_DUAL_IO 3
+#define QSPI_IFR_WIDTH_QUAD_IO 4
+#define QSPI_IFR_WIDTH_DUAL_CMD 5
+#define QSPI_IFR_WIDTH_QUAD_CMD 6
+#define QSPI_IFR_INSTEN_OFFSET 4
+#define QSPI_IFR_INSTEN_SIZE 1
+#define QSPI_IFR_ADDREN_OFFSET 5
+#define QSPI_IFR_ADDREN_SIZE 1
+#define QSPI_IFR_OPTEN_OFFSET 6
+#define QSPI_IFR_OPTEN_SIZE 1
+#define QSPI_IFR_DATAEN_OFFSET 7
+#define QSPI_IFR_DATAEN_SIZE 1
+#define QSPI_IFR_OPTL_OFFSET 8
+#define QSPI_IFR_OPTL_SIZE 2
+#define QSPI_IFR_OPTL_OPTION_1BIT 0
+#define QSPI_IFR_OPTL_OPTION_2BIT 1
+#define QSPI_IFR_OPTL_OPTION_4BIT 2
+#define QSPI_IFR_OPTL_OPTION_8BIT 3
+#define QSPI_IFR_ADDRL_OFFSET 10
+#define QSPI_IFR_ADDRL_SIZE 1
+#define QSPI_IFR_TFRTYP_OFFSET 12
+#define QSPI_IFR_TFRTYP_SIZE 2
+#define QSPI_IFR_TFRTYP_TRSFR_READ 0
+#define QSPI_IFR_TFRTYP_TRSFR_READ_MEMORY 1
+#define QSPI_IFR_TFRTYP_TRSFR_WRITE 2
+#define QSPI_IFR_TFRTYP_TRSFR_WRITE_MEMORY 3
+#define QSPI_IFR_CRM_OFFSET 14
+#define QSPI_IFR_CRM_SIZE 1
+#define QSPI_IFR_NBDUM_OFFSET 16
+#define QSPI_IFR_NBDUM_SIZE 5
+
+/* Bitfields in QSPI_SMR (Scrambling Mode Register) */
+#define QSPI_SMR_SCREN_OFFSET 0
+#define QSPI_SMR_SCREN_SIZE 1
+#define QSPI_SMR_RVDIS_OFFSET 1
+#define QSPI_SMR_RVDIS_SIZE 1
+
+/* Bitfields in QSPI_WPMR (Write Protection Mode Register) */
+#define QSPI_WPMR_WPEN_OFFSET 0
+#define QSPI_WPMR_WPEN_SIZE 1
+#define QSPI_WPMR_WPKEY_OFFSET 8
+#define QSPI_WPMR_WPKEY_SIZE 24
+
+/* Bitfields in QSPI_WPSR (Write Protection Status Register) */
+#define QSPI_WPSR_WPVS_OFFSET 0
+#define QSPI_WPSR_WPVS_SIZE 1
+#define QSPI_WPSR_WPVSRC_OFFSET 8
+#define QSPI_WPSR_WPVSRC_SIZE 8
+
+/* Bit manipulation macros */
+#define QSPI_BIT(name) \
+ (1 << QSPI_##name##_OFFSET)
+#define QSPI_BF(name, value) \
+ (((value) & ((1 << QSPI_##name##_SIZE) - 1)) << QSPI_##name##_OFFSET)
+#define QSPI_BFEXT(name, value) \
+ (((value) >> QSPI_##name##_OFFSET) & ((1 << QSPI_##name##_SIZE) - 1))
+#define QSPI_BFINS(name, value, old) \
+ (((old) & ~(((1 << QSPI_##name##_SIZE) - 1) << QSPI_##name##_OFFSET)) \
+ | QSPI_BF(name, value))
+
+/* Register access macros */
+#define qspi_readl(port, reg) \
+ readl_relaxed((port)->regs + QSPI_##reg)
+#define qspi_writel(port, reg, value) \
+ writel_relaxed((value), (port)->regs + QSPI_##reg)
+
+#define qspi_readw(port, reg) \
+ readw_relaxed((port)->regs + QSPI_##reg)
+#define qspi_writew(port, reg, value) \
+ writew_relaxed((value), (port)->regs + QSPI_##reg)
+
+#define qspi_readb(port, reg) \
+ readb_relaxed((port)->regs + QSPI_##reg)
+#define qspi_writeb(port, reg, value) \
+ writeb_relaxed((value), (port)->regs + QSPI_##reg)
+
+
+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 ifr_width;
+ u32 pending;
+
+ struct mtd_info mtd;
+ struct spi_nor nor;
+ u32 clk_rate;
+ struct completion completion;
+
+#ifdef DEBUG
+ u8 last_instruction;
+#endif
+};
+
+struct atmel_qspi_command {
+ u32 ifr_tfrtyp;
+ 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;
+};
+
+#define QSPI_DMA_THRESHOLD 32
+
+static void atmel_qspi_dma_callback(void *arg)
+{
+ struct completion *c = arg;
+
+ complete(c);
+}
+
+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;
+ struct completion completion;
+ 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;
+ }
+ err = dma_mapping_error(dev, phys_addr);
+ if (err)
+ goto exit;
+
+ desc = chan->device->device_prep_dma_memcpy(chan, dst, src,
+ cmd->buf_len,
+ DMA_PREP_INTERRUPT);
+ if (!desc) {
+ err = -ENOMEM;
+ goto unmap_single;
+ }
+
+ init_completion(&completion);
+ desc->callback = atmel_qspi_dma_callback;
+ desc->callback_param = &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(&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);
+exit:
+ 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 */
+ 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 (aq->ifr_width) {
+ 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;
+ }
+
+ print_hex_dump(KERN_DEBUG, "qspi cmd: ", DUMP_PREFIX_NONE,
+ 32, 1, cmd_buf, len, false);
+
+#ifdef VERBOSE_DEBUG
+ 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 = (QSPI_BF(IFR_WIDTH, aq->ifr_width) |
+ QSPI_BF(IFR_TFRTYP, cmd->ifr_tfrtyp));
+
+
+ /* Compute instruction parameters */
+ if (cmd->enable.bits.instruction) {
+ icr |= QSPI_BF(ICR_INST, cmd->instruction);
+ ifr |= QSPI_BIT(IFR_INSTEN);
+ }
+
+ /* Compute address parameters */
+ switch (cmd->enable.bits.address) {
+ case 4:
+ ifr |= QSPI_BIT(IFR_ADDRL);
+ /*break;*/ /* fallback to the 24bit address case */
+ case 3:
+ iar = (cmd->enable.bits.data) ? 0 : cmd->address;
+ ifr |= QSPI_BIT(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_BF(ICR_OPT, cmd->mode);
+ ifr |= QSPI_BIT(IFR_OPTEN);
+
+ switch (QSPI_BFEXT(IFR_WIDTH, ifr)) {
+ 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_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_1BIT);
+ break;
+
+ case 2:
+ ifr |= QSPI_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_2BIT);
+ break;
+
+ case 4:
+ ifr |= QSPI_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_4BIT);
+ break;
+
+ case 8:
+ ifr |= QSPI_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_8BIT);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* Set number of dummy cycles */
+ if (cmd->enable.bits.dummy)
+ ifr |= QSPI_BF(IFR_NBDUM, cmd->num_dummy_cycles);
+ else
+ ifr |= QSPI_BF(IFR_NBDUM, 0);
+
+ /* Set data enable */
+ if (cmd->enable.bits.data) {
+ ifr |= QSPI_BIT(IFR_DATAEN);
+
+ /* Special case for Continuous Read Mode */
+ if (!cmd->tx_buf && !cmd->rx_buf)
+ ifr |= QSPI_BIT(IFR_CRM);
+ }
+
+ /* Set QSPI Instruction Frame registers */
+ atmel_qspi_debug_command(aq, cmd);
+ qspi_writel(aq, IAR, iar);
+ qspi_writel(aq, ICR, icr);
+ qspi_writel(aq, 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, 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, CR, QSPI_BIT(CR_LASTXFER));
+
+ if (err)
+ return err;
+#ifdef VERBOSE_DEBUG
+ 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, SR);
+ if (sr & QSPI_BIT(SR_INSTRE))
+ return err;
+
+ /* Wait for INSTRuction End interrupt */
+ init_completion(&aq->completion);
+ aq->pending = 0;
+ qspi_writel(aq, IER, QSPI_BIT(SR_INSTRE));
+ if (!wait_for_completion_timeout(&aq->completion,
+ msecs_to_jiffies(1000)))
+ err = -ETIMEDOUT;
+ qspi_writel(aq, IDR, QSPI_BIT(SR_INSTRE));
+
+ return err;
+}
+
+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;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_READ;
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.data = 1;
+ cmd.instruction = opcode;
+ cmd.rx_buf = buf;
+ cmd.buf_len = len;
+ return atmel_qspi_run_command(aq, &cmd);
+}
+
+static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
+ u8 *buf, int len,
+ int write_enable)
+{
+ struct atmel_qspi *aq = nor->priv;
+ struct atmel_qspi_command cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_WRITE;
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.data = (buf != NULL && len > 0);
+ cmd.instruction = opcode;
+ cmd.tx_buf = buf;
+ cmd.buf_len = len;
+ 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.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_WRITE_MEMORY;
+ 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_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;
+
+ dev_dbg(nor->dev, "%dKiB at 0x%08x\n",
+ aq->mtd.erasesize / 1024, (u32)offs);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_WRITE;
+ cmd.enable.bits.instruction = 1;
+ cmd.enable.bits.address = nor->addr_width;
+ cmd.instruction = nor->erase_opcode;
+ cmd.address = (u32)offs;
+ 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 err;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_READ_MEMORY;
+ 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;
+ err = atmel_qspi_run_command(aq, &cmd);
+ if (err)
+ return err;
+
+ *retlen += len;
+ return 0;
+}
+
+static int atmel_qspi_set_protocol(struct spi_nor *nor, enum spi_protocol proto)
+{
+ struct atmel_qspi *aq = nor->priv;
+
+ switch (proto) {
+ case SPI_PROTO_1_1_1:
+ aq->ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
+ break;
+ case SPI_PROTO_1_1_2:
+ aq->ifr_width = QSPI_IFR_WIDTH_DUAL_OUTPUT;
+ break;
+ case SPI_PROTO_1_1_4:
+ aq->ifr_width = QSPI_IFR_WIDTH_QUAD_OUTPUT;
+ break;
+ case SPI_PROTO_1_2_2:
+ aq->ifr_width = QSPI_IFR_WIDTH_DUAL_IO;
+ break;
+ case SPI_PROTO_1_4_4:
+ aq->ifr_width = QSPI_IFR_WIDTH_QUAD_IO;
+ break;
+ case SPI_PROTO_2_2_2:
+ aq->ifr_width = QSPI_IFR_WIDTH_DUAL_CMD;
+ break;
+ case SPI_PROTO_4_4_4:
+ aq->ifr_width = QSPI_IFR_WIDTH_QUAD_CMD;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ 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, CR, QSPI_BIT(CR_SWRST));
+
+ /* Set the QSPI controller in Serial Memory Mode */
+ mr = (QSPI_BIT(MR_SSM) |
+ QSPI_BF(MR_NBBITS, QSPI_MR_NBBITS_8_BIT));
+ qspi_writel(aq, 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_BF(SCR_SCBR, scbr);
+ qspi_writel(aq, SCR, scr);
+
+ /* Enable the QSPI controller */
+ qspi_writel(aq, CR, QSPI_BIT(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, SR);
+ mask = qspi_readl(aq, IMR);
+ pending = status & mask;
+
+ if (!pending)
+ return IRQ_NONE;
+
+ aq->pending |= pending;
+ if (pending & QSPI_BIT(SR_INSTRE))
+ complete(&aq->completion);
+
+ return IRQ_HANDLED;
+}
+
+static int atmel_qspi_probe(struct platform_device *pdev)
+{
+ struct device_node *child, *np = pdev->dev.of_node;
+ struct mtd_part_parser_data ppdata;
+ struct atmel_qspi *aq;
+ struct resource *res;
+ dma_cap_mask_t mask;
+ struct spi_nor *nor;
+ struct mtd_info *mtd;
+ char modalias[40];
+ 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)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, aq);
+ aq->pdev = pdev;
+ /* Start in Extended SPI (1-1-1) */
+ aq->ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
+
+ /* Map the registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ 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(pdev, IORESOURCE_MEM, 1);
+ 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 = &aq->mtd;
+
+ nor->mtd = mtd;
+ nor->dev = &pdev->dev;
+ 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;
+ nor->set_protocol = atmel_qspi_set_protocol;
+
+ if (of_modalias_node(child, modalias, sizeof(modalias)) < 0) {
+ err = -ENODEV;
+ goto release_channel;
+ }
+
+ 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;
+
+ nor->dev->of_node = child;
+ err = spi_nor_scan(nor, modalias, SPI_NOR_QUAD);
+ nor->dev->of_node = np;
+ if (err)
+ goto release_channel;
+
+ ppdata.of_node = child;
+ err = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
+ if (err)
+ goto release_channel;
+
+ return 0;
+
+release_channel:
+ if (aq->chan)
+ dma_release_channel(aq->chan);
+disable_clk:
+ clk_disable_unprepare(aq->clk);
+exit:
+ return err;
+}
+
+static int atmel_qspi_remove(struct platform_device *pdev)
+{
+ struct atmel_qspi *aq = platform_get_drvdata(pdev);
+
+ mtd_device_unregister(&aq->mtd);
+ qspi_writel(aq, CR, QSPI_BIT(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",
+ .bus = &platform_bus_type,
+ .of_match_table = of_match_ptr(atmel_qspi_dt_ids),
+ },
+ .probe = atmel_qspi_probe,
+ .remove = atmel_qspi_remove,
+};
+module_platform_driver(atmel_qspi_driver);
+
+MODULE_AUTHOR("Cyrille Pitchen <[email protected]>");
+MODULE_DESCRIPTION("Atmel QSPI Controller driver");
+MODULE_LICENSE("GPL v2");
--
1.8.2.2

2015-07-16 17:44:32

by Marek Vasut

[permalink] [raw]
Subject: Re: [PATCH 4/7] Documentation: mtd: add a DT property to set the latency code of Spansion memory

On Thursday, July 16, 2015 at 05:27:51 PM, Cyrille Pitchen wrote:

Hi!

> Both the SPI controller and the NOR flash memory need to agree on the
> number of dummy cycles to use for Fast Read commands. For Spansion
> memories, this number of dummy cycles is not given directly but through a
> so called "latency code".
> The latency code can be found into the memory datasheet and depends on the
> SPI clock frequency, the Fast Read op code and the Single/Dual Data Rate
> mode.

Shouldn't you be able to derive the latency code from the above information,
which you already know then ?

Best regards,
Marek Vasut

2015-07-17 09:04:44

by Paul Bolle

[permalink] [raw]
Subject: Re: [PATCH 7/7] mtd: atmel-quadspi: add driver for Atmel QSPI controller

On do, 2015-07-16 at 17:27 +0200, Cyrille Pitchen wrote:
> --- /dev/null
> +++ b/drivers/mtd/spi-nor/atmel-quadspi.c

> +static struct platform_driver atmel_qspi_driver = {
> + .driver = {
> + [...]
> + .bus = &platform_bus_type,
> + [...]
> +};
> +module_platform_driver(atmel_qspi_driver);

Nit: on module init this will basically do
__platform_driver_register(&atmel_qspi_driver, THIS_MODULE);

which will again set bus to &platform_bus_type. So you might as well
drop that line.


Paul Bolle

2015-07-17 11:44:56

by Sergei Shtylyov

[permalink] [raw]
Subject: Re: [PATCH 6/7] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver

Hello.

On 7/16/2015 6:27 PM, 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 <[email protected]>
> ---
> .../devicetree/bindings/mtd/atmel-quadspi.txt | 29 ++++++++++++++++++++++
> 1 file changed, 29 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..a0d60ac7ae10
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
> @@ -0,0 +1,29 @@
> +* Atmel Quad Serial Peripheral Interface (QSPI)
> +
> +Required properties:
> + - compatible : Should be "atmel,sama5d2-qspi"
> + - reg : the first contains the register location and length,
> + the second contains the memory mapping address and length
> + - interrupts : Should contain the interrupt for the device
> + - clocks : The clock needed by the QSPI controller
> + - #address-cells : should be 1
> + - #size-cells : should be 0
> +
> +Example:
> +
> +qspi0: qspi@f0020000 {

Once again, the ePAPR standard tells up to call the node just "spi", not
"qspi".

> + compatible = "atmel,sama5d2-qspi";
> + reg = <0xf0020000 0x100>,
> + <0xd0000000 0x08000000>;

Either you use the leading zeroes or you don't. :-)

[...]

MBR, Sergei

2015-07-20 08:54:35

by Cyrille Pitchen

[permalink] [raw]
Subject: Re: [PATCH 6/7] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver

Hi Sergei,

thanks for the review. For the next series I've removed all references to
"qspi" in the node example but the one from the compatible string.
I've also removed the leading 0 in the size of the second memory region.

Best Regards,

Cyrille

Le 17/07/2015 13:44, Sergei Shtylyov a ?crit :
> Hello.
>
> On 7/16/2015 6:27 PM, 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 <[email protected]>
>> ---
>> .../devicetree/bindings/mtd/atmel-quadspi.txt | 29 ++++++++++++++++++++++
>> 1 file changed, 29 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..a0d60ac7ae10
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt
>> @@ -0,0 +1,29 @@
>> +* Atmel Quad Serial Peripheral Interface (QSPI)
>> +
>> +Required properties:
>> + - compatible : Should be "atmel,sama5d2-qspi"
>> + - reg : the first contains the register location and length,
>> + the second contains the memory mapping address and length
>> + - interrupts : Should contain the interrupt for the device
>> + - clocks : The clock needed by the QSPI controller
>> + - #address-cells : should be 1
>> + - #size-cells : should be 0
>> +
>> +Example:
>> +
>> +qspi0: qspi@f0020000 {
>
> Once again, the ePAPR standard tells up to call the node just "spi", not "qspi".
>
>> + compatible = "atmel,sama5d2-qspi";
>> + reg = <0xf0020000 0x100>,
>> + <0xd0000000 0x08000000>;
>
> Either you use the leading zeroes or you don't. :-)
>
> [...]
>
> MBR, Sergei
>

2015-07-20 08:56:24

by Cyrille Pitchen

[permalink] [raw]
Subject: Re: [PATCH 7/7] mtd: atmel-quadspi: add driver for Atmel QSPI controller

Hi Paul,

I've removed the ".bus = &plaform_bus_type" for the next series.
Thank you for the explanation and the review :)

Best Regards,

Cyrille

Le 17/07/2015 11:04, Paul Bolle a écrit :
> On do, 2015-07-16 at 17:27 +0200, Cyrille Pitchen wrote:
>> --- /dev/null
>> +++ b/drivers/mtd/spi-nor/atmel-quadspi.c
>
>> +static struct platform_driver atmel_qspi_driver = {
>> + .driver = {
>> + [...]
>> + .bus = &platform_bus_type,
>> + [...]
>> +};
>> +module_platform_driver(atmel_qspi_driver);
>
> Nit: on module init this will basically do
> __platform_driver_register(&atmel_qspi_driver, THIS_MODULE);
>
> which will again set bus to &platform_bus_type. So you might as well
> drop that line.
>
>
> Paul Bolle
>

2015-07-20 09:23:46

by Cyrille Pitchen

[permalink] [raw]
Subject: Re: [PATCH 4/7] Documentation: mtd: add a DT property to set the latency code of Spansion memory

Hi Marek,

Le 16/07/2015 19:44, Marek Vasut a ?crit :
> On Thursday, July 16, 2015 at 05:27:51 PM, Cyrille Pitchen wrote:
>
> Hi!
>
>> Both the SPI controller and the NOR flash memory need to agree on the
>> number of dummy cycles to use for Fast Read commands. For Spansion
>> memories, this number of dummy cycles is not given directly but through a
>> so called "latency code".
>> The latency code can be found into the memory datasheet and depends on the
>> SPI clock frequency, the Fast Read op code and the Single/Dual Data Rate
>> mode.
>
> Shouldn't you be able to derive the latency code from the above information,
> which you already know then ?
Yes I agree with you; this could have been done adding static tables inside the
driver instead of creating a new DT property dedicated to Spansion memories.

When I wrote this patch, I had a close look at the s25fl512s datasheet but only
overviewed few datasheets for other Spansion QSPI flash memories. So I don't
know whether a single latency code table could be shared among all Spansion
memories or many tables should be added to support different memory models.

That's why I've chosen to add a dedicated DT property to support Spansion
memories as it avoids to add tables to guess the proper latency code to be
used. I thought it would be more flexible.

Maybe I will remove the support of Spansion QSPI memories from this series for
now. Their support can still be implemented later.

Anyway, thanks for your review :)

>
> Best regards,
> Marek Vasut
>

Best Regards,

Cyrille

2015-07-20 19:29:51

by Marek Vasut

[permalink] [raw]
Subject: Re: [PATCH 4/7] Documentation: mtd: add a DT property to set the latency code of Spansion memory

On Monday, July 20, 2015 at 11:23:39 AM, Cyrille Pitchen wrote:
> Hi Marek,

Hi!

> Le 16/07/2015 19:44, Marek Vasut a ?crit :
> > On Thursday, July 16, 2015 at 05:27:51 PM, Cyrille Pitchen wrote:
> >
> > Hi!
> >
> >> Both the SPI controller and the NOR flash memory need to agree on the
> >> number of dummy cycles to use for Fast Read commands. For Spansion
> >> memories, this number of dummy cycles is not given directly but through
> >> a so called "latency code".
> >> The latency code can be found into the memory datasheet and depends on
> >> the SPI clock frequency, the Fast Read op code and the Single/Dual Data
> >> Rate mode.
> >
> > Shouldn't you be able to derive the latency code from the above
> > information, which you already know then ?
>
> Yes I agree with you; this could have been done adding static tables inside
> the driver instead of creating a new DT property dedicated to Spansion
> memories.

OK, I see now. The latency code can not be calculed from "SPI clock frequency,
the Fast Read op code and the Single/Dual Data Rate mode" easily, you need to
index into some table to obtain some ad-hoc value. Got it. Sorry for the noise!

> When I wrote this patch, I had a close look at the s25fl512s datasheet but
> only overviewed few datasheets for other Spansion QSPI flash memories. So
> I don't know whether a single latency code table could be shared among all
> Spansion memories or many tables should be added to support different
> memory models.
>
> That's why I've chosen to add a dedicated DT property to support Spansion
> memories as it avoids to add tables to guess the proper latency code to be
> used. I thought it would be more flexible.
>
> Maybe I will remove the support of Spansion QSPI memories from this series
> for now. Their support can still be implemented later.
>
> Anyway, thanks for your review :)

Let's wait for more comments :)

Best regards,
Marek Vasut