[PATCH V1 4/5] spi: Add Freescale QuadSpi driver

Brian Norris computersforpeace at gmail.com
Thu Aug 22 15:21:58 EDT 2013


Adding devicetree at vger.kernel.org

You might want to split out the devicetree binding documentation as a
separate patch from the driver submission, so that DT binding reviewers
will have an easier time.

On Mon, Aug 19, 2013 at 12:10:02PM +0800, Huang Shijie wrote:
> (0) What is the Quadspi controller?
> 
>     The Quadspi(Quad Serial Peripheral Interface) acts as an interface to
>     one single or two external serial flash devices, each with up to 4
>     bidirectional data lines.
> 
> (1) The Quadspi controller is driven by the LUT(Look-up Table) registers.
>     The LUT registers are a look-up-table for sequences of instructions.
>     A valid sequence consists of four LUT registers.
> 
> (2) The definition of the LUT register shows below:
> 
>     ---------------------------------------------------
>     | INSTR1 | PAD1 | OPRND1 | INSTR0 | PAD0 | OPRND0 |
>     ---------------------------------------------------
> 
>     There are several types of INSTRx, such as:
>     	CMD	: the SPI NOR command.
> 	ADDR	: the address for the SPI NOR command.
> 	DUMMY	: the dummy cycles needed by the SPI NOR command.
> 	....
> 
> (3) We connect the NOR the QuadSPI now. I am not sure, but i think the
>     QuadSPI will be only used for the NOR. We may connect other devices
>     to it. But, for the reason of (2), we have to parse out the SPI NOR
>     command for the QuadSPI.
> 
> (4) Test this driver with the JFFS2 and UBIFS:
> 
>     For jffs2:
>          #flash_eraseall /dev/mtd0
>          #mount -t jffs2 /dev/mtdblock0 tmp
>          #bonnie++ -d tmp -u 0 -s 10 -r 5
> 
>     For ubifs:
>          #flash_eraseall /dev/mtd0
>     	 #ubiattach /dev/ubi_ctrl -m 0
>     	 #ubimkvol /dev/ubi0 -N test -m
>          #mount -t ubifs ubi0:test tmp
>          #bonnie++ -d tmp -u 0 -s 10 -r 5
> 
> Signed-off-by: Huang Shijie <b32955 at freescale.com>
> ---
>  .../devicetree/bindings/spi/fsl-quadspi.txt        |   27 +
>  drivers/spi/Kconfig                                |    7 +
>  drivers/spi/Makefile                               |    1 +
>  drivers/spi/spi-fsl-quadspi.c                      |  930 ++++++++++++++++++++
>  4 files changed, 965 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/spi/fsl-quadspi.txt
>  create mode 100644 drivers/spi/spi-fsl-quadspi.c
> 
> diff --git a/Documentation/devicetree/bindings/spi/fsl-quadspi.txt b/Documentation/devicetree/bindings/spi/fsl-quadspi.txt
> new file mode 100644
> index 0000000..e5bfa82
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/spi/fsl-quadspi.txt
> @@ -0,0 +1,27 @@
> +* Freescale Quad Serial Peripheral Interface(QuadSPI)
> +
> +Required properties:
> +- compatible : Should be "fsl,vf610-qspi"
> +- reg : Offset and length of the register set for the device
> +- interrupts : Should contain the interrupt for the device
> +- fsl,spi-num-chipselects : Contains the number of the chipselect

Can this controller support more than one chip? If so, then the nor-size
property makes even less sense. See below.

> +- clocks : The clocks needed by the QuadSPI controller
> +- clock-names : the name of the clocks
> +
> +Optional properties:
> +- fsl,nor-size : The NOR size used by the QuadSPI mapping.

Why does the size of the NOR flash need to be in the controller's device
node? Shouldn't this be detected at run-time if possible? Or at least
included as a property of m25p80, if absolutely required?

> +
> +Example:
> +
> +qspi0: quadspi at 40044000 {
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	compatible = "fsl,vf610-qspi";
> +	reg = <0x40044000 0x1000>;
> +	interrupts = <0 24 0x04>;
> +	clocks = <&clks VF610_CLK_QSPI0_EN>,
> +		<&clks VF610_CLK_QSPI0>;
> +	clock-names = "qspi_en", "qspi";
> +	fsl,nor-size = <0x1000000>;
> +	status = "disabled";
> +};
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 92b2373..dc38063 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -187,6 +187,13 @@ config SPI_FALCON
>  	  has only been tested with m25p80 type chips. The hardware has no
>  	  support for other types of SPI peripherals.
>  
> +config SPI_FSL_QUADSPI
> +	tristate "Freescale Quad SPI controller"
> +	depends on ARCH_MXC
> +	help
> +	  This enables support for the Quad SPI controller in master mode.
> +	  We only connect the NOR to this controller now.
> +
>  config SPI_GPIO
>  	tristate "GPIO-based bitbanging SPI Master"
>  	depends on GPIOLIB
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index b25f385..7fe505c 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -37,6 +37,7 @@ obj-$(CONFIG_SPI_FSL_ESPI)		+= spi-fsl-espi.o
>  obj-$(CONFIG_SPI_FSL_SPI)		+= spi-fsl-spi.o
>  obj-$(CONFIG_SPI_GPIO)			+= spi-gpio.o
>  obj-$(CONFIG_SPI_IMX)			+= spi-imx.o
> +obj-$(CONFIG_SPI_FSL_QUADSPI)           += spi-fsl-quadspi.o
>  obj-$(CONFIG_SPI_LM70_LLP)		+= spi-lm70llp.o
>  obj-$(CONFIG_SPI_MPC512x_PSC)		+= spi-mpc512x-psc.o
>  obj-$(CONFIG_SPI_MPC52xx_PSC)		+= spi-mpc52xx-psc.o
> diff --git a/drivers/spi/spi-fsl-quadspi.c b/drivers/spi/spi-fsl-quadspi.c
> new file mode 100644
> index 0000000..de71a4e
> --- /dev/null
> +++ b/drivers/spi/spi-fsl-quadspi.c
> @@ -0,0 +1,930 @@
> +/*
> + * Freescale Quad SPI driver.
> + *
> + * Copyright (C) 2013 Freescale Semiconductor, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/errno.h>
> +#include <linux/platform_device.h>
> +#include <linux/sched.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/spi/spi.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/timer.h>
> +#include <linux/jiffies.h>
> +#include <linux/completion.h>
> +#include <linux/mtd/spi-nor.h>
> +
> +/* The registers */
> +#define QUADSPI_MCR			0x00
> +#define QUADSPI_MCR_MDIS_SHIFT		14
> +#define QUADSPI_MCR_MDIS_MASK		(1 << QUADSPI_MCR_MDIS_SHIFT)
> +#define QUADSPI_MCR_CLR_TXF_SHIFT	11
> +#define QUADSPI_MCR_CLR_TXF_MASK	(1 << QUADSPI_MCR_CLR_TXF_SHIFT)
> +#define QUADSPI_MCR_CLR_RXF_SHIFT	10
> +#define QUADSPI_MCR_CLR_RXF_MASK	(1 << QUADSPI_MCR_CLR_RXF_SHIFT)
> +#define QUADSPI_MCR_DDR_EN_SHIFT	7
> +#define QUADSPI_MCR_DDR_EN_MASK		(1 << QUADSPI_MCR_DDR_EN_SHIFT)
> +#define QUADSPI_MCR_RESERVED_SHIFT	16
> +#define QUADSPI_MCR_RESERVED_MASK	(0xF << QUADSPI_MCR_RESERVED_SHIFT)
> +#define QUADSPI_MCR_SWRSTHD_SHIFT	1
> +#define QUADSPI_MCR_SWRSTHD_MASK	(1 << QUADSPI_MCR_SWRSTHD_SHIFT)
> +#define QUADSPI_MCR_SWRSTSD_SHIFT	0
> +#define QUADSPI_MCR_SWRSTSD_MASK	(1 << QUADSPI_MCR_SWRSTSD_SHIFT)
> +
> +#define QUADSPI_IPCR			0x08
> +#define QUADSPI_IPCR_SEQID_SHIFT	24
> +#define QUADSPI_IPCR_SEQID_MASK		(0xF << QUADSPI_IPCR_SEQID_SHIFT)
> +
> +#define QUADSPI_BUF0CR			0x10
> +#define QUADSPI_BUF1CR			0x14
> +#define QUADSPI_BUF2CR			0x18
> +#define QUADSPI_BUFXCR_INVALID_MSTRID	0xe
> +
> +#define QUADSPI_BUF3CR			0x1c
> +#define QUADSPI_BUF3CR_ALLMST_SHIFT	31
> +#define QUADSPI_BUF3CR_ALLMST		(1 << QUADSPI_BUF3CR_ALLMST_SHIFT)
> +
> +#define QUADSPI_BFGENCR			0x20
> +#define QUADSPI_BFGENCR_PAR_EN_SHIFT	16
> +#define QUADSPI_BFGENCR_PAR_EN_MASK	(1 << (QUADSPI_BFGENCR_PAR_EN_SHIFT))
> +#define QUADSPI_BFGENCR_SEQID_SHIFT	12
> +#define QUADSPI_BFGENCR_SEQID_MASK	(0xF << QUADSPI_BFGENCR_SEQID_SHIFT)
> +
> +#define QUADSPI_BUF0IND			0x30
> +#define QUADSPI_BUF1IND			0x34
> +#define QUADSPI_BUF2IND			0x38
> +#define QUADSPI_SFAR			0x100
> +
> +#define QUADSPI_SMPR			0x108
> +#define QUADSPI_SMPR_DDRSMP_SHIFT	16
> +#define QUADSPI_SMPR_DDRSMP_MASK	(7 << QUADSPI_SMPR_DDRSMP_SHIFT)
> +#define QUADSPI_SMPR_FSDLY_SHIFT	6
> +#define QUADSPI_SMPR_FSDLY_MASK		(1 << QUADSPI_SMPR_FSDLY_SHIFT)
> +#define QUADSPI_SMPR_FSPHS_SHIFT	5
> +#define QUADSPI_SMPR_FSPHS_MASK		(1 << QUADSPI_SMPR_FSPHS_SHIFT)
> +#define QUADSPI_SMPR_HSENA_SHIFT	0
> +#define QUADSPI_SMPR_HSENA_MASK		(1 << QUADSPI_SMPR_HSENA_SHIFT)
> +
> +#define QUADSPI_RBSR			0x10c
> +#define QUADSPI_RBSR_RDBFL_SHIFT	8
> +#define QUADSPI_RBSR_RDBFL_MASK		(0x3F << QUADSPI_RBSR_RDBFL_SHIFT)
> +
> +#define QUADSPI_RBCT			0x110
> +#define QUADSPI_RBCT_WMRK_MASK		0x1F
> +#define QUADSPI_RBCT_RXBRD_SHIFT	8
> +#define QUADSPI_RBCT_RXBRD_USEIPS	(0x1 << QUADSPI_RBCT_RXBRD_SHIFT)
> +
> +#define QUADSPI_TBSR			0x150
> +#define QUADSPI_TBDR			0x154
> +
> +#define QUADSPI_SR			0x15c
> +#define QUADSPI_SR_TXFULL_SHIFT		27
> +#define QUADSPI_SR_TXFULL_MASK		(1 << QUADSPI_SR_TXFULL_SHIFT)
> +#define QUADSPI_SR_AHBTRN_SHIFT		6
> +#define QUADSPI_SR_AHBTRN_MASK		(1 << QUADSPI_SR_AHBTRN_SHIFT)
> +#define QUADSPI_SR_AHB_ACC_SHIFT	2
> +#define QUADSPI_SR_AHB_ACC_MASK		(1 << QUADSPI_SR_AHB_ACC_SHIFT)
> +#define QUADSPI_SR_IP_ACC_SHIFT		1
> +#define QUADSPI_SR_IP_ACC_MASK		(1 << QUADSPI_SR_IP_ACC_SHIFT)
> +#define QUADSPI_SR_BUSY_SHIFT		0
> +#define QUADSPI_SR_BUSY_MASK		(1 << QUADSPI_SR_BUSY_SHIFT)
> +
> +#define QUADSPI_FR			0x160
> +#define QUADSPI_FR_TFF_MASK		0x1
> +
> +#define QUADSPI_SFA1AD			0x180
> +#define QUADSPI_SFA2AD			0x184
> +#define QUADSPI_SFB1AD			0x188
> +#define QUADSPI_SFB2AD			0x18c
> +#define QUADSPI_RBDR			0x200
> +
> +#define QUADSPI_LUTKEY			0x300
> +#define QUADSPI_LUTKEY_VALUE		0x5AF05AF0
> +
> +#define QUADSPI_LCKCR			0x304
> +#define QUADSPI_LCKER_LOCK		0x1
> +#define QUADSPI_LCKER_UNLOCK		0x2
> +
> +#define QUADSPI_RSER			0x164
> +#define QUADSPI_RSER_TFIE       	(0x1 << 0)
> +
> +#define QUADSPI_LUT_BASE		0x310
> +
> +/* Field definitions for LUT register. */
> +#define OPRND0_SHIFT		0
> +#define PAD0_SHIFT		8
> +#define INSTR0_SHIFT		10
> +#define OPRND1_SHIFT		16
> +
> +/* Instruction set for the LUT register. */
> +#define CMD			1
> +#define ADDR			2
> +#define DUMMY			3
> +#define MODE			4
> +#define MODE2			5
> +#define MODE4			6
> +#define READ			7
> +#define WRITE			8
> +#define JMP_ON_CS		9
> +#define ADDR_DDR		10
> +#define MODE_DDR		11
> +#define MODE2_DDR		12
> +#define MODE4_DDR		13
> +
> +/*
> + * The PAD definitions for LUT register.
> + *
> + * The pad stands for the lines number of IO[0:3].
> + * For example, the Quad read need four IO lines, so you should
> + * set PAD4 which means we use four IO lines.
> + */
> +#define PAD1			0
> +#define PAD2			1
> +#define PAD4			2
> +
> +/* Oprands for the LUT register. */
> +#define ADDR24BIT		0x18
> +
> +/* Macros for constructing the LUT register. */
> +#define QUADSPI_LUT0(ins, pad, opr)					\
> +		(((opr) << OPRND0_SHIFT) | ((pad) << PAD0_SHIFT) |	\
> +		((ins) << INSTR0_SHIFT))
> +
> +#define QUADSPI_LUT1(ins, pad, opr) \
> +		(QUADSPI_LUT0((ins), (pad), (opr)) << OPRND1_SHIFT)
> +
> +/* other macros for LUT register. */
> +#define QUADSPI_LUT(x)          (QUADSPI_LUT_BASE + (x) * 4)
> +#define QUADSPI_LUT_NUM		64
> +
> +/* SEQID */
> +#define SEQID_QUAD_READ		0
> +#define SEQID_WREN		1
> +#define SEQID_FAST_READ 	2
> +#define SEQID_RDSR		3
> +#define SEQID_SE		4
> +#define SEQID_CHIP_ERASE	5
> +#define SEQID_PP		6
> +#define SEQID_RDID		7
> +#define SEQID_WRSR		8
> +#define SEQID_RDCR		9
> +
> +struct fsl_qspi_handler {
> +	int (*setup)(struct spi_device *);
> +	int (*do_one_msg)(struct spi_master *, struct spi_message *);
> +};
> +
> +enum fsl_qspi_devtype {
> +	FSL_QUADSPI_VYBRID,
> +	FSL_QUADSPI_IMX6SLX
> +};
> +
> +struct fsl_qspi_devtype_data {
> +	enum fsl_qspi_devtype devtype;
> +	u32 memmap_base;
> +	int rxfifo;
> +	int txfifo;
> +};
> +
> +static struct fsl_qspi_devtype_data vybrid_data = {
> +	.devtype = FSL_QUADSPI_VYBRID,
> +	.memmap_base = 0x20000000,
> +	.rxfifo = 128,
> +	.txfifo = 64
> +};
> +
> +struct fsl_qspi {
> +	void __iomem *iobase;
> +	struct clk *clk, *clk_en;
> +	struct device *dev;
> +	struct fsl_qspi_handler *h;
> +	struct completion c;
> +	struct fsl_qspi_devtype_data *devtype_data;
> +	void __iomem *ahb_base; /* Used when read from AHB bus */
> +	unsigned int addr;
> +	u32 nor_size; /* for mapping */
> +	u8 cmd;
> +	unsigned int quad_read_enabled:1;
> +};
> +
> +static inline int is_vybrid_qspi(struct fsl_qspi *q)
> +{
> +	return q->devtype_data->devtype == FSL_QUADSPI_VYBRID;
> +}
> +
> +/*
> + * An IC bug makes us to re-arrange the 32-bit data.
> + * The following chips, such as IMX6SLX, have fixed this bug.
> + */
> +static inline u32 fsl_qspi_endian_xchg(struct fsl_qspi *q, u32 a)
> +{
> +	return is_vybrid_qspi(q) ? __swab32(a) : a;
> +}
> +
> +static inline void qspi_unlock_lut(struct fsl_qspi *q)
> +{
> +	writel(QUADSPI_LUTKEY_VALUE, q->iobase + QUADSPI_LUTKEY);
> +	writel(QUADSPI_LCKER_UNLOCK, q->iobase + QUADSPI_LCKCR);
> +}
> +
> +static inline void qspi_lock_lut(struct fsl_qspi *q)
> +{
> +	writel(QUADSPI_LUTKEY_VALUE, q->iobase + QUADSPI_LUTKEY);
> +	writel(QUADSPI_LCKER_LOCK, q->iobase + QUADSPI_LCKCR);
> +}
> +
> +static irqreturn_t fsl_qspi_irq_handler(int irq, void *dev_id)
> +{
> +	struct fsl_qspi *q = dev_id;
> +	u32 reg;
> +
> +	/* clear interrupt */
> +	reg = readl(q->iobase + QUADSPI_FR);
> +	writel(reg, q->iobase + QUADSPI_FR);
> +
> +	if (reg & QUADSPI_FR_TFF_MASK)
> +		complete(&q->c);
> +
> +	dev_dbg(q->dev, "QUADSPI_FR : 0x%.8x\n", reg);
> +	return IRQ_HANDLED;
> +}
> +
> +/* Init the LUT table. */
> +static void fsl_qspi_init_lut(struct fsl_qspi *q)
> +{
> +	void *__iomem base = q->iobase;
> +	int rxfifo = q->devtype_data->rxfifo;
> +	u32 lut_base;
> +	int i;
> +
> +	qspi_unlock_lut(q);
> +
> +	/* Clear all the LUT table */
> +	for (i = 0; i < QUADSPI_LUT_NUM; i++)
> +		writel(0, base + QUADSPI_LUT_BASE + i * 4);
> +
> +	/* Quad Read */
> +	lut_base = SEQID_QUAD_READ * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_QIOR)
> +			| QUADSPI_LUT1(ADDR, PAD4, ADDR24BIT),
> +			base + QUADSPI_LUT(lut_base));
> +	writel(QUADSPI_LUT0(MODE, PAD4, 0xff) | QUADSPI_LUT1(DUMMY, PAD4, 4),
> +			base + QUADSPI_LUT(lut_base + 1));
> +	writel(QUADSPI_LUT0(READ, PAD4, rxfifo),
> +			base + QUADSPI_LUT(lut_base + 2));
> +
> +	/* Write enable */
> +	lut_base = SEQID_WREN * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_WREN),
> +			base + QUADSPI_LUT(lut_base));
> +
> +	/* Fast Read */
> +	lut_base = SEQID_FAST_READ * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_FAST_READ)
> +			| QUADSPI_LUT1(ADDR, PAD1, ADDR24BIT),
> +			base + QUADSPI_LUT(lut_base));
> +	writel(QUADSPI_LUT0(DUMMY, PAD1, 8) | QUADSPI_LUT1(READ, PAD1, rxfifo),
> +			base + QUADSPI_LUT(lut_base + 1));
> +
> +	/* Page Program */
> +	lut_base = SEQID_PP * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_PP)
> +			| QUADSPI_LUT1(ADDR, PAD1, ADDR24BIT),
> +			base + QUADSPI_LUT(lut_base));
> +	writel(QUADSPI_LUT0(WRITE, PAD1, 0),
> +			base + QUADSPI_LUT(lut_base + 1));
> +
> +	/* Read Status */
> +	lut_base = SEQID_RDSR * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_RDSR)
> +			| QUADSPI_LUT1(READ, PAD1, 0x1),
> +			base + QUADSPI_LUT(lut_base));
> +
> +	/* Erase a sector */
> +	lut_base = SEQID_SE * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_SE)
> +			| QUADSPI_LUT1(ADDR, PAD1, ADDR24BIT),
> +			base + QUADSPI_LUT(lut_base));
> +
> +	/* Erase the whole chip */
> +	lut_base = SEQID_CHIP_ERASE * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_CHIP_ERASE),
> +			base + QUADSPI_LUT(lut_base));
> +
> +	/* READ ID */
> +	lut_base = SEQID_RDID * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_RDID)
> +			| QUADSPI_LUT1(READ, PAD1, 0x8),
> +			base + QUADSPI_LUT(lut_base));
> +
> +	/* Write Register */
> +	lut_base = SEQID_WRSR * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_WRSR)
> +			| QUADSPI_LUT1(WRITE, PAD1, 0x2),
> +			base + QUADSPI_LUT(lut_base));
> +
> +	/* Read Configuration Register */
> +	lut_base = SEQID_RDCR * 4;
> +	writel(QUADSPI_LUT0(CMD, PAD1, OPCODE_RDCR)
> +			| QUADSPI_LUT1(READ, PAD1, 0x1),
> +			base + QUADSPI_LUT(lut_base));
> +	qspi_lock_lut(q);
> +}
> +
> +/* Get the SEQID for the command */
> +static int fsl_qspi_get_seqid(struct fsl_qspi *q, u8 cmd)
> +{
> +	switch (cmd) {
> +	case OPCODE_WREN:
> +		return SEQID_WREN;
> +	case OPCODE_RDSR:
> +		return SEQID_RDSR;
> +	case OPCODE_SE:
> +		return SEQID_SE;
> +	case OPCODE_CHIP_ERASE:
> +		return SEQID_CHIP_ERASE;
> +	case OPCODE_PP:
> +		return SEQID_PP;
> +	case OPCODE_RDID:
> +		return SEQID_RDID;
> +	case OPCODE_WRSR:
> +		return SEQID_WRSR;
> +	case OPCODE_RDCR:
> +		return SEQID_RDCR;
> +	default:
> +		dev_err(q->dev, "Unsupported cmd 0x%.2x\n", cmd);
> +		break;
> +	}
> +	return -1;
> +}
> +
> +static int
> +fsl_qspi_runcmd(struct fsl_qspi *q, u8 cmd, unsigned int addr, int len)
> +{
> +	int seqid;
> +	u32 reg;
> +	int err;
> +
> +	init_completion(&q->c);
> +	dev_dbg(q->dev, "to @%.8x, len:%d, cmd:%.2x\n", addr, len, cmd);
> +
> +	/* save the reg */
> +	reg = readl(q->iobase + QUADSPI_MCR);
> +
> +	writel(q->devtype_data->memmap_base + addr, q->iobase + QUADSPI_SFAR);
> +	writel(QUADSPI_RBCT_WMRK_MASK | QUADSPI_RBCT_RXBRD_USEIPS,
> +			q->iobase + QUADSPI_RBCT);
> +	writel(QUADSPI_MCR_CLR_RXF_MASK | QUADSPI_MCR_RESERVED_MASK,
> +			q->iobase + QUADSPI_MCR);
> +
> +	/* trigger the LUT now */
> +	seqid = fsl_qspi_get_seqid(q, cmd);
> +	writel((seqid << QUADSPI_IPCR_SEQID_SHIFT) | len,
> +		q->iobase + QUADSPI_IPCR);
> +
> +	/* Wait for the interrupt. */
> +	err = wait_for_completion_timeout(&q->c, msecs_to_jiffies(1000));
> +	if (!err) {
> +		dev_err(q->dev,
> +			"cmd 0x%.2x timeout, addr@%.8x, FR:0x%.8x, SR:0x%.8x\n",
> +			cmd, addr, readl(q->iobase + QUADSPI_FR),
> +			readl(q->iobase + QUADSPI_SR));
> +		err = -ETIMEDOUT;
> +	} else {
> +		err = 0;
> +	}
> +
> +	/* restore the MCR */
> +	writel(reg, q->iobase + QUADSPI_MCR);
> +
> +	return err;
> +}
> +
> +/* Get the address from the tx buffer. */
> +static unsigned int
> +fsl_qspi_get_addr(struct fsl_qspi *q, struct spi_transfer *t)
> +{
> +	unsigned int addr;
> +	u8 *buf = (u8 *)t->tx_buf;
> +
> +	/* 3-byte address */
> +	if (q->nor_size <= SZ_16M)
> +		addr = (buf[1] << 16) | (buf[2] << 8) |  buf[3];
> +	return addr;
> +}
> +
> +/* Read out the data from the buffer registers. */
> +static void fsl_qspi_read_data(struct fsl_qspi *q, int len, u32 *rxbuf)
> +{
> +	u32 tmp;
> +	int i = 0;
> +
> +	while (len > 0) {
> +		tmp = readl(q->iobase + QUADSPI_RBDR + i * 4);
> +		*rxbuf = fsl_qspi_endian_xchg(q, tmp);
> +		dev_dbg(q->dev, "rcv: 0x%.8x, tmp : 0x%.8x\n", *rxbuf, tmp);
> +
> +		rxbuf++;
> +		len -= 4;
> +		i++;
> +	}
> +}
> +
> +/* Read out the data directly from the AHB buffer.*/
> +static int fsl_qspi_read_data_ahb(struct fsl_qspi *q, struct spi_transfer *t)
> +{
> +	dev_dbg(q->dev, "cmd [%x],read from 0x%.8x,len:%d\n",
> +		q->cmd, q->addr, t->len);
> +	memcpy(t->rx_buf, q->ahb_base + q->addr, t->len);
> +	return 0;
> +}
> +
> +static u32 fsl_qspi_read_sr(struct fsl_qspi *q)
> +{
> +	u32 val = -EINVAL;
> +	int ret;
> +
> +	ret = fsl_qspi_runcmd(q, OPCODE_RDSR, 0, 1);
> +	if (!ret)
> +		fsl_qspi_read_data(q, 1, &val);
> +	return val;
> +}
> +
> +static int fsl_qspi_wait_till_ready(struct fsl_qspi *q)
> +{
> +	unsigned long deadline;
> +	u32 sr;
> +
> +	deadline = jiffies + msecs_to_jiffies(40000);
> +
> +	do {
> +		if ((sr = fsl_qspi_read_sr(q)) < 0)
> +			break;
> +		else if (!(sr & SR_WIP))
> +			return 0;
> +
> +		cond_resched();
> +
> +	} while (!time_after_eq(jiffies, deadline));
> +
> +	return 1;
> +}
> +
> +/*
> + * If we have changed the content of the flash by writing or erasing,
> + * we need to invalidate the AHB buffer. If we do not do so, we may read out
> + * the wrong data. The spec tells us reset the AHB domain and Serial Flash
> + * domain at the same time.
> + */
> +static inline void fsl_qspi_invalid(struct fsl_qspi *q)
> +{
> +	u32 reg;
> +
> +	reg = readl(q->iobase + QUADSPI_MCR);
> +	reg |= QUADSPI_MCR_SWRSTHD_MASK | QUADSPI_MCR_SWRSTSD_MASK;
> +	writel(reg, q->iobase + QUADSPI_MCR);
> +
> +	/*
> +	 * The minimum delay : 1 AHB + 2 SFCK clocks.
> +	 * Delay 1 us is enough.
> +	 */
> +	udelay(1);
> +
> +	reg &= ~(QUADSPI_MCR_SWRSTHD_MASK | QUADSPI_MCR_SWRSTSD_MASK);
> +	writel(reg, q->iobase + QUADSPI_MCR);
> +}
> +
> +static int fsl_qspi_nor_write(struct fsl_qspi *q, u32 *txbuf, unsigned count)
> +{
> +	unsigned int addr = q->addr;
> +	int txfifo_size = q->devtype_data->txfifo;
> +	int ret = 0;
> +	int tx_size;
> +	u32 tmp;
> +	int i, j;
> +	u8 cmd = q->cmd;
> +
> +	q->cmd = -1; /* clear the cmd */
> +	dev_dbg(q->dev, "to @%.8x, len : %d\n", addr, count);
> +
> +	while (count > 0) {
> +		tx_size = (count > txfifo_size) ? txfifo_size : count;
> +
> +		/* clear the TX FIFO. */
> +		tmp = readl(q->iobase + QUADSPI_MCR);
> +		writel(tmp | QUADSPI_MCR_CLR_RXF_MASK, q->iobase + QUADSPI_MCR);
> +
> +		/* fill the TX data to the FIFO */
> +		for (j = 0, i = ((tx_size + 3) / 4); j < i; j++) {
> +			tmp = fsl_qspi_endian_xchg(q, *txbuf);
> +			writel(tmp, q->iobase + QUADSPI_TBDR);
> +			txbuf++;
> +		}
> +
> +		/* Trigger it */
> +		ret = fsl_qspi_runcmd(q, cmd, addr, tx_size);
> +
> +		addr += tx_size;
> +		count -= tx_size;
> +
> +		/*
> +		 * If the TX FIFO is smaller then the size of Page Program,
> +		 * we have to wait until this Write is finished.
> +		 * For example, the TX FIFO is 64 bytes in the Vybrid,
> +		 * but the Page Program may writes 265 bytes per time.
> +		 * We are lucky that some chip(IMX6SLX) has increase the TX FIFO
> +		 * to 512 bytes.
> +		 *
> +		 * If we can change the @m25p->page_size, we can remove the
> +		 * following code.
> +		 */
> +		if (count > 0) {
> +			ret = fsl_qspi_wait_till_ready(q);
> +			if (ret) {
> +				dev_err(q->dev, "Reading SR, err:%d\n", ret);
> +				break;
> +			}
> +
> +			/* Write Enable again. */
> +			ret = fsl_qspi_runcmd(q, OPCODE_WREN, 0, 0);
> +			if (ret) {
> +				dev_err(q->dev, "Write Enable, err:%d\n", ret);
> +				break;
> +			}
> +		}
> +	}
> +	return ret;
> +}
> +
> +/* Switch to Quad read now. */
> +static inline void fsl_qspi_enable_quad_read(struct fsl_qspi *q)
> +{
> +	writel(SEQID_QUAD_READ << QUADSPI_BFGENCR_SEQID_SHIFT,
> +		q->iobase + QUADSPI_BFGENCR);
> +	q->quad_read_enabled = 1;
> +}
> +
> +static int fsl_qspi_nor_tx(struct fsl_qspi *q, struct spi_transfer *t)
> +{
> +	unsigned int addr = 0;
> +	bool need_invalid = false;
> +	int ret = 0;
> +	u32 val;
> +	u8 cmd;
> +
> +	/* This is the second spi_transfer for Page Program. */
> +	if (q->cmd == OPCODE_PP) {
> +		ret = fsl_qspi_nor_write(q, (u32 *)t->tx_buf, t->len);
> +		need_invalid = true;
> +		goto qspi_tx_out;
> +	}
> +
> +	cmd = *(u8 *)t->tx_buf;
> +	dev_dbg(q->dev, "NOR cmd is [0x%.2x], len : %d.\n", cmd, t->len);
> +
> +	switch (cmd) {
> +	case OPCODE_SE:
> +		addr = fsl_qspi_get_addr(q, t);
> +		/* fall through */
> +	case OPCODE_CHIP_ERASE:
> +		need_invalid = true;
> +	case OPCODE_WREN:
> +		ret = fsl_qspi_runcmd(q, cmd, addr, 0);
> +		q->cmd = -1;
> +		break;
> +
> +	case OPCODE_QIOR:
> +		if (!q->quad_read_enabled)
> +			fsl_qspi_enable_quad_read(q);
> +		/* fall through */
> +	case OPCODE_FAST_READ:
> +	case OPCODE_PP:
> +		q->cmd = cmd;
> +		q->addr = fsl_qspi_get_addr(q, t);
> +		break;
> +
> +	case OPCODE_WRSR:
> +		q->addr = 0;
> +		q->cmd = cmd;
> +		/* skip the cmd */
> +		memcpy((void *) &val, ((u8 *)t->tx_buf) + 1, t->len -1);
> +		ret = fsl_qspi_nor_write(q, &val, t->len - 1);
> +		break;
> +
> +	default:
> +		q->cmd = cmd;
> +		break;
> +	}
> +
> +qspi_tx_out:
> +	if (need_invalid)
> +		fsl_qspi_invalid(q);
> +	return ret;
> +}
> +
> +static int fsl_qspi_nor_rx(struct fsl_qspi *q, struct spi_transfer *t)
> +{
> +	int ret = 0;
> +
> +	switch (q->cmd) {
> +	case OPCODE_RDSR:
> +	case OPCODE_RDCR:
> +	case OPCODE_RDID:
> +		ret = fsl_qspi_runcmd(q, q->cmd, 0, t->len);
> +		if (!ret)
> +			fsl_qspi_read_data(q, t->len, t->rx_buf);
> +		break;
> +
> +	case OPCODE_QIOR:
> +	case OPCODE_FAST_READ:
> +		ret = fsl_qspi_read_data_ahb(q, t);
> +		break;
> +	default:
> +		dev_err(q->dev, "Unsupported cmd : %x\n", q->cmd);
> +		return -EINVAL;
> +	}
> +	return ret;
> +}
> +
> +static int fsl_qspi_nor_do_one_msg(struct spi_master *master,
> +		struct spi_message *m)
> +{
> +	struct fsl_qspi *q = spi_master_get_devdata(master);
> +	struct spi_transfer *t;
> +	int ret = 0;
> +
> +	list_for_each_entry(t, &m->transfers, transfer_list) {
> +		if (t->rx_buf && t->tx_buf) {
> +			dev_err(q->dev,
> +				"Can't send and receive simultaneously\n");
> +			ret = -EINVAL;
> +			break;
> +		}
> +
> +		if (t->tx_buf) {
> +			ret = fsl_qspi_nor_tx(q, t);
> +			if (!ret)
> +				m->actual_length += t->len;
> +			continue;
> +		}
> +
> +		if (t->rx_buf) {
> +			ret = fsl_qspi_nor_rx(q, t);
> +			if (!ret)
> +				m->actual_length += t->len;
> +		}
> +	}
> +
> +	m->status = ret;
> +	spi_finalize_current_message(master);
> +	return ret;
> +}
> +
> +/*
> + * There are two different ways to read out the data from the flash:
> + *  the "IP Command Read" and the "AHB Command Read".
> + *
> + * The IC guy suggests we use the "AHB Command Read" which is faster
> + * then the "IP Command Read". (What's more is that there is a bug in
> + * the "IP Command Read" in the Vybrid.)
> + *
> + * After we set up the registers for the "AHB Command Read", we can use
> + * the memcpy to read the data directly. A "missed" access to the buffer
> + * causes the controller to clear the buffer, and use the sequence pointed
> + * by the QUADSPI_BFGENCR[SEQID] to initiate a read from the flash.
> + */
> +static int fsl_qspi_init_abh_read(struct fsl_qspi *q)
> +{
> +	void __iomem *base = q->iobase;
> +	u32 memmap_base = q->devtype_data->memmap_base;
> +	int nor_size = q->nor_size;
> +
> +	/* Map the SPI NOR to accessiable address */
> +	writel(nor_size | memmap_base, base + QUADSPI_SFA1AD);
> +	writel(nor_size | memmap_base, base + QUADSPI_SFA2AD);
> +	writel((nor_size * 2) | memmap_base, base + QUADSPI_SFB1AD);
> +	writel((nor_size * 2) | memmap_base, base + QUADSPI_SFB2AD);
> +
> +	/* AHB configuration for access buffer 0/1/2 .*/
> +	writel(QUADSPI_BUFXCR_INVALID_MSTRID, base + QUADSPI_BUF0CR);
> +	writel(QUADSPI_BUFXCR_INVALID_MSTRID, base + QUADSPI_BUF1CR);
> +	writel(QUADSPI_BUFXCR_INVALID_MSTRID, base + QUADSPI_BUF2CR);
> +	writel(QUADSPI_BUF3CR_ALLMST, base + QUADSPI_BUF3CR);
> +
> +	/* We only use the buffer3 */
> +	writel(0, base + QUADSPI_BUF0IND);
> +	writel(0, base + QUADSPI_BUF1IND);
> +	writel(0, base + QUADSPI_BUF2IND);
> +
> +	/* Set the default lut sequence for AHB Read. */
> +	writel(SEQID_FAST_READ << QUADSPI_BFGENCR_SEQID_SHIFT,
> +		base + QUADSPI_BFGENCR);
> +
> +	/* Map the AHB address for read. */
> +	q->ahb_base = ioremap(memmap_base, nor_size);
> +	if (!q->ahb_base)
> +		return -ENOMEM;
> +	return 0;
> +}
> +
> +static int fsl_qspi_nor_setup(struct spi_device *spi)
> +{
> +	struct fsl_qspi *q = spi_master_get_devdata(spi->master);
> +	void __iomem *base = q->iobase;
> +	u32 reg_val, smpr_val;
> +	int ret;
> +
> +	writel(QUADSPI_MCR_RESERVED_MASK | QUADSPI_MCR_MDIS_MASK,
> +		base + QUADSPI_MCR);
> +
> +	reg_val = readl(base + QUADSPI_SMPR);
> +	writel(reg_val & ~(QUADSPI_SMPR_FSDLY_MASK
> +			| QUADSPI_SMPR_FSPHS_MASK
> +			| QUADSPI_SMPR_HSENA_MASK), base + QUADSPI_SMPR);
> +
> +	writel(QUADSPI_MCR_RESERVED_MASK, base + QUADSPI_MCR);
> +
> +	fsl_qspi_init_lut(q);
> +	ret = fsl_qspi_init_abh_read(q);
> +	if (ret < 0)
> +		return ret;
> +
> +	reg_val = 0;
> +	reg_val |= QUADSPI_MCR_RESERVED_MASK;
> +	smpr_val = readl(base + QUADSPI_SMPR);
> +	smpr_val &= ~QUADSPI_SMPR_DDRSMP_MASK;
> +	writel(smpr_val, base + QUADSPI_SMPR);
> +	reg_val &= ~QUADSPI_MCR_DDR_EN_MASK;
> +	writel(reg_val, base + QUADSPI_MCR);
> +
> +	/* enable the interrupt */
> +	writel(QUADSPI_RSER_TFIE, q->iobase + QUADSPI_RSER);
> +	return 0;
> +}
> +
> +/* We only support the NOR now. */
> +static struct fsl_qspi_handler fsl_qspi_nor_handler = {
> +	.setup = fsl_qspi_nor_setup,
> +	.do_one_msg = fsl_qspi_nor_do_one_msg,
> +};
> +
> +static int fsl_qspi_setup(struct spi_device *spi)
> +{
> +	struct fsl_qspi *q = spi_master_get_devdata(spi->master);
> +
> +	return q->h->setup(spi);
> +}
> +
> +static int fsl_qspi_do_one_msg(struct spi_master *master,
> +		struct spi_message *m)
> +{
> +	struct fsl_qspi *q = spi_master_get_devdata(master);
> +
> +	return q->h->do_one_msg(master, m);
> +}
> +
> +static struct of_device_id fsl_qspi_dt_ids[] = {
> +	{ .compatible = "fsl,vf610-qspi", .data = (void*)&vybrid_data, },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, fsl_qspi_dt_ids);
> +
> +static int fsl_qspi_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct spi_master *master;
> +	struct fsl_qspi *q;
> +	struct resource *res;
> +	int num_cs, ret = 0;
> +	const struct of_device_id *of_id =
> +			of_match_device(fsl_qspi_dt_ids, &pdev->dev);
> +
> +	ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "can't get the spi-mum-chipselects\n");
> +		return ret;
> +	}
> +
> +	master = spi_alloc_master(&pdev->dev, sizeof(*q));
> +	if (!master)
> +		return -ENOMEM;
> +	q = spi_master_get_devdata(master);
> +
> +	ret = of_property_read_u32(np, "fsl,nor-size", &q->nor_size);
> +	if (ret < 0)
> +		dev_dbg(&pdev->dev, "can't get the nor size.\n");
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	q->iobase = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(q->iobase)) {
> +		dev_err(&pdev->dev, "ioremap failed\n");
> +		ret = PTR_ERR(q->iobase);
> +		goto map_failed;
> +	}
> +
> +	q->clk_en = devm_clk_get(&pdev->dev, "qspi_en");
> +	q->clk = devm_clk_get(&pdev->dev, "qspi");
> +	if (IS_ERR(q->clk_en) || IS_ERR(q->clk)) {
> +		dev_err(&pdev->dev, "failed to get clocks\n");
> +		ret = -ENOENT;
> +		goto map_failed;
> +	}
> +
> +	ret = clk_prepare_enable(q->clk_en);
> +	if (ret) {
> +		dev_err(&pdev->dev, "can not enable the qspi_en clock\n");
> +		goto map_failed;
> +	}
> +
> +	ret = clk_prepare_enable(q->clk);
> +	if (ret) {
> +		clk_disable_unprepare(q->clk_en);
> +		dev_err(&pdev->dev, "can not enable the qspi clock\n");
> +		goto map_failed;
> +	}
> +
> +	ret = platform_get_irq(pdev, 0);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to get the irq\n");
> +		goto irq_failed;
> +	}
> +
> +	ret = devm_request_irq(&pdev->dev, ret,
> +			fsl_qspi_irq_handler, 0, pdev->name, q);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to request irq.\n");
> +		goto irq_failed;
> +	}
> +
> +	q->dev = &pdev->dev;
> +	q->devtype_data = (struct fsl_qspi_devtype_data *)of_id->data;
> +
> +	/* The default handler is for NOR. */
> +	q->h = &fsl_qspi_nor_handler;
> +
> +	master->bus_num = pdev->id;
> +	master->num_chipselect = num_cs;
> +	master->dev.of_node = pdev->dev.of_node;
> +
> +	master->setup = fsl_qspi_setup;
> +	master->transfer_one_message = fsl_qspi_do_one_msg;
> +	platform_set_drvdata(pdev, master);
> +
> +	ret = spi_register_master(master);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register the spi master.\n");
> +		goto irq_failed;
> +	}
> +	dev_info(&pdev->dev, "QuadSPI bus driver\n");
> +	return 0;
> +
> +irq_failed:
> +	clk_disable_unprepare(q->clk);
> +	clk_disable_unprepare(q->clk_en);
> +map_failed:
> +	spi_master_put(master);
> +
> +	dev_err(&pdev->dev, "Freescale QuadSPI probe failed\n");
> +	return ret;
> +}
> +
> +static int fsl_qspi_remove(struct platform_device *pdev)
> +{
> +	struct spi_master *master = platform_get_drvdata(pdev);
> +	struct fsl_qspi *q = spi_master_get_devdata(master);
> +
> +	/* disable the hardware */
> +	writel(0x0, q->iobase + QUADSPI_MCR);
> +	writel(0x0, q->iobase + QUADSPI_RSER);
> +
> +	clk_disable_unprepare(q->clk);
> +	clk_disable_unprepare(q->clk_en);
> +	spi_master_put(master);
> +	return 0;
> +}
> +
> +static struct platform_driver fsl_qspi_driver = {
> +	.driver = {
> +		.name	= "fsl-quadspi",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = fsl_qspi_dt_ids,
> +	},
> +	.probe          = fsl_qspi_probe,
> +	.remove		= fsl_qspi_remove,
> +};
> +module_platform_driver(fsl_qspi_driver);
> +
> +MODULE_DESCRIPTION("Freescale QuadSPI Controller Driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.7.1
> 
> 

Brian



More information about the linux-arm-kernel mailing list