All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cyrille Pitchen <cyrille.pitchen@atmel.com>
To: <nicolas.ferre@atmel.com>, <broonie@kernel.org>,
	<linux-spi@vger.kernel.org>, <dwmw2@infradead.org>,
	<computersforpeace@gmail.com>, <zajec5@gmail.com>,
	<beanhuo@micron.com>, <juhosg@openwrt.org>, <marex@denx.de>,
	<shijie.huang@intel.com>, <ben@decadent.org.uk>
Cc: <linux-kernel@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>,
	<devicetree@vger.kernel.org>, <robh+dt@kernel.org>,
	<pawel.moll@arm.com>, <mark.rutland@arm.com>,
	<ijc+devicetree@hellion.org.uk>, <galak@codeaurora.org>,
	<linux-mtd@lists.infradead.org>,
	Cyrille Pitchen <cyrille.pitchen@atmel.com>
Subject: [PATCH 7/7] mtd: atmel-quadspi: add driver for Atmel QSPI controller
Date: Thu, 16 Jul 2015 17:27:54 +0200	[thread overview]
Message-ID: <cf2522fc858b72af78d057ac76c4f029e80edf4a.1437059658.git.cyrille.pitchen@atmel.com> (raw)
In-Reply-To: <cover.1437059658.git.cyrille.pitchen@atmel.com>

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         |   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 <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_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 <cyrille.pitchen@atmel.com>");
+MODULE_DESCRIPTION("Atmel QSPI Controller driver");
+MODULE_LICENSE("GPL v2");
-- 
1.8.2.2


WARNING: multiple messages have this Message-ID (diff)
From: Cyrille Pitchen <cyrille.pitchen@atmel.com>
To: nicolas.ferre@atmel.com, broonie@kernel.org,
	linux-spi@vger.kernel.org, dwmw2@infradead.org,
	computersforpeace@gmail.com, zajec5@gmail.com,
	beanhuo@micron.com, juhosg@openwrt.org, marex@denx.de,
	shijie.huang@intel.com, ben@decadent.org.uk
Cc: linux-kernel@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
	robh+dt@kernel.org, pawel.moll@arm.com, mark.rutland@arm.com,
	ijc+devicetree@hellion.org.uk, galak@codeaurora.org,
	linux-mtd@lists.infradead.org,
	Cyrille Pitchen <cyrille.pitchen@atmel.com>
Subject: [PATCH 7/7] mtd: atmel-quadspi: add driver for Atmel QSPI controller
Date: Thu, 16 Jul 2015 17:27:54 +0200	[thread overview]
Message-ID: <cf2522fc858b72af78d057ac76c4f029e80edf4a.1437059658.git.cyrille.pitchen@atmel.com> (raw)
In-Reply-To: <cover.1437059658.git.cyrille.pitchen@atmel.com>

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         |   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 <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_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 <cyrille.pitchen@atmel.com>");
+MODULE_DESCRIPTION("Atmel QSPI Controller driver");
+MODULE_LICENSE("GPL v2");
-- 
1.8.2.2

WARNING: multiple messages have this Message-ID (diff)
From: cyrille.pitchen@atmel.com (Cyrille Pitchen)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 7/7] mtd: atmel-quadspi: add driver for Atmel QSPI controller
Date: Thu, 16 Jul 2015 17:27:54 +0200	[thread overview]
Message-ID: <cf2522fc858b72af78d057ac76c4f029e80edf4a.1437059658.git.cyrille.pitchen@atmel.com> (raw)
In-Reply-To: <cover.1437059658.git.cyrille.pitchen@atmel.com>

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         |   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 <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_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 <cyrille.pitchen@atmel.com>");
+MODULE_DESCRIPTION("Atmel QSPI Controller driver");
+MODULE_LICENSE("GPL v2");
-- 
1.8.2.2

  parent reply	other threads:[~2015-07-16 15:27 UTC|newest]

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-07-16 15:27 [PATCH 0/7] add driver for Atmel QSPI controller Cyrille Pitchen
2015-07-16 15:27 ` Cyrille Pitchen
2015-07-16 15:27 ` Cyrille Pitchen
2015-07-16 15:27 ` Cyrille Pitchen
2015-07-16 15:27 ` [PATCH 1/7] Documentation: mtd: add a DT property to set the number of dummy cycles Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27 ` [PATCH 2/7] mtd: spi-nor: notify (Q)SPI controller about protocol change Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27 ` [PATCH 3/7] mtd: spi-nor: allow to tune the number of dummy cycles Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27 ` [PATCH 4/7] Documentation: mtd: add a DT property to set the latency code of Spansion memory Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 17:44   ` Marek Vasut
2015-07-16 17:44     ` Marek Vasut
2015-07-16 17:44     ` Marek Vasut
2015-07-20  9:23     ` Cyrille Pitchen
2015-07-20  9:23       ` Cyrille Pitchen
2015-07-20  9:23       ` Cyrille Pitchen
2015-07-20  9:23       ` Cyrille Pitchen
2015-07-20 19:29       ` Marek Vasut
2015-07-20 19:29         ` Marek Vasut
2015-07-20 19:29         ` Marek Vasut
2015-07-16 15:27 ` [PATCH 5/7] mtd: spi-nor: allow the set the latency code on Spansion memories Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27 ` [PATCH 6/7] Documentation: atmel-quadspi: add binding file for Atmel QSPI driver Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-17 11:44   ` Sergei Shtylyov
2015-07-17 11:44     ` Sergei Shtylyov
2015-07-17 11:44     ` Sergei Shtylyov
2015-07-20  8:54     ` Cyrille Pitchen
2015-07-20  8:54       ` Cyrille Pitchen
2015-07-20  8:54       ` Cyrille Pitchen
2015-07-20  8:54       ` Cyrille Pitchen
2015-07-16 15:27 ` Cyrille Pitchen [this message]
2015-07-16 15:27   ` [PATCH 7/7] mtd: atmel-quadspi: add driver for Atmel QSPI controller Cyrille Pitchen
2015-07-16 15:27   ` Cyrille Pitchen
2015-07-17  9:04   ` Paul Bolle
2015-07-17  9:04     ` Paul Bolle
2015-07-17  9:04     ` Paul Bolle
2015-07-17  9:04     ` Paul Bolle
2015-07-20  8:55     ` Cyrille Pitchen
2015-07-20  8:55       ` Cyrille Pitchen
2015-07-20  8:55       ` Cyrille Pitchen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cf2522fc858b72af78d057ac76c4f029e80edf4a.1437059658.git.cyrille.pitchen@atmel.com \
    --to=cyrille.pitchen@atmel.com \
    --cc=beanhuo@micron.com \
    --cc=ben@decadent.org.uk \
    --cc=broonie@kernel.org \
    --cc=computersforpeace@gmail.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dwmw2@infradead.org \
    --cc=galak@codeaurora.org \
    --cc=ijc+devicetree@hellion.org.uk \
    --cc=juhosg@openwrt.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=linux-spi@vger.kernel.org \
    --cc=marex@denx.de \
    --cc=mark.rutland@arm.com \
    --cc=nicolas.ferre@atmel.com \
    --cc=pawel.moll@arm.com \
    --cc=robh+dt@kernel.org \
    --cc=shijie.huang@intel.com \
    --cc=zajec5@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.