From 2bd534c30688bcb3f70f1816fbcff813fc746103 Mon Sep 17 00:00:00 2001
From: John Crispin <blogic@openwrt.org>
Date: Sat, 27 Aug 2011 18:12:26 +0200
Subject: [PATCH 13/24] MIPS: lantiq: adds FALC-ON spi driver

The external bus unit (EBU) found on the FALC-ON SoC has spi emulation that is
designed for serial flash access.

Signed-off-by: Thomas Langer <thomas.langer@lantiq.com>
Signed-off-by: John Crispin <blogic@openwrt.org>
---
 arch/mips/lantiq/falcon/devices.c        |   12 +-
 arch/mips/lantiq/falcon/devices.h        |    4 +
 arch/mips/lantiq/falcon/mach-easy98000.c |   27 ++
 drivers/spi/Kconfig                      |    4 +
 drivers/spi/Makefile                     |    1 +
 drivers/spi/spi-falcon.c                 |  477 ++++++++++++++++++++++++++++++
 6 files changed, 523 insertions(+), 2 deletions(-)
 create mode 100644 drivers/spi/spi-falcon.c

--- a/arch/mips/lantiq/falcon/devices.c
+++ b/arch/mips/lantiq/falcon/devices.c
@@ -129,7 +129,7 @@ falcon_register_gpio_extra(void)
 
 /* i2c */
 static struct resource falcon_i2c_resources[] = {
-	MEM_RES("i2c", GPON_I2C_BASE,GPON_I2C_END),
+	MEM_RES("i2c", LTQ_I2C_BASE_ADDR, LTQ_I2C_SIZE),
 	IRQ_RES("i2c_lb", FALCON_IRQ_I2C_LBREQ),
 	IRQ_RES("i2c_b", FALCON_IRQ_I2C_BREQ),
 	IRQ_RES("i2c_err", FALCON_IRQ_I2C_I2C_ERR),
@@ -140,10 +140,18 @@ void __init falcon_register_i2c(void)
 {
 	platform_device_register_simple("i2c-falcon", 0,
 	falcon_i2c_resources, ARRAY_SIZE(falcon_i2c_resources));
-	sys1_hw_activate(ACTS_I2C_ACT);
+	ltq_sysctl_activate(SYSCTL_SYS1, ACTS_I2C_ACT);
 }
 
-void __init falcon_register_crypto(void)
+/* spi flash */
+static struct platform_device ltq_spi = {
+	.name			= "falcon_spi",
+	.num_resources		= 0,
+};
+
+void __init
+falcon_register_spi_flash(struct spi_board_info *data)
 {
-	platform_device_register_simple("ltq_falcon_deu", 0, NULL, 0);
+	spi_register_board_info(data, 1);
+	platform_device_register(&ltq_spi);
 }
--- a/arch/mips/lantiq/falcon/devices.h
+++ b/arch/mips/lantiq/falcon/devices.h
@@ -11,11 +11,15 @@
 #ifndef _FALCON_DEVICES_H__
 #define _FALCON_DEVICES_H__
 
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+
 #include "../devices.h"
 
 extern void falcon_register_nand(void);
 extern void falcon_register_gpio(void);
 extern void falcon_register_gpio_extra(void);
 extern void falcon_register_i2c(void);
+extern void falcon_register_spi_flash(struct spi_board_info *data);
 
 #endif
--- a/arch/mips/lantiq/falcon/mach-easy98000.c
+++ b/arch/mips/lantiq/falcon/mach-easy98000.c
@@ -40,6 +40,21 @@ struct physmap_flash_data easy98000_nor_
 	.parts		= easy98000_nor_partitions,
 };
 
+static struct flash_platform_data easy98000_spi_flash_platform_data = {
+	.name = "sflash",
+	.parts = easy98000_nor_partitions,
+	.nr_parts = ARRAY_SIZE(easy98000_nor_partitions)
+};
+
+static struct spi_board_info easy98000_spi_flash_data __initdata = {
+	.modalias		= "m25p80",
+	.bus_num		= 0,
+	.chip_select		= 0,
+	.max_speed_hz		= 10 * 1000 * 1000,
+	.mode			= SPI_MODE_3,
+	.platform_data		= &easy98000_spi_flash_platform_data
+};
+
 /* setup gpio based spi bus/device for access to the eeprom on the board */
 #define SPI_GPIO_MRST		102
 #define SPI_GPIO_MTSR		103
@@ -93,6 +108,13 @@ easy98000_init(void)
 }
 
 static void __init
+easy98000sf_init(void)
+{
+	easy98000_init_common();
+	falcon_register_spi_flash(&easy98000_spi_flash_data);
+}
+
+static void __init
 easy98000nand_init(void)
 {
 	easy98000_init_common();
@@ -104,6 +126,11 @@ MIPS_MACHINE(LANTIQ_MACH_EASY98000,
 			"EASY98000 Eval Board",
 			easy98000_init);
 
+MIPS_MACHINE(LANTIQ_MACH_EASY98000SF,
+			"EASY98000SF",
+			"EASY98000 Eval Board (Serial Flash)",
+			easy98000sf_init);
+
 MIPS_MACHINE(LANTIQ_MACH_EASY98000NAND,
 			"EASY98000NAND",
 			"EASY98000 Eval Board (NAND Flash)",
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -189,6 +189,10 @@ config SPI_MPC52xx
 	  This drivers supports the MPC52xx SPI controller in master SPI
 	  mode.
 
+config SPI_FALCON
+	tristate "Falcon SPI controller support"
+	depends on SOC_FALCON
+
 config SPI_MPC52xx_PSC
 	tristate "Freescale MPC52xx PSC SPI controller"
 	depends on PPC_MPC52xx && EXPERIMENTAL
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_SPI_DW_MMIO)		+= spi-dw-mmi
 obj-$(CONFIG_SPI_DW_PCI)		+= spi-dw-midpci.o
 spi-dw-midpci-objs			:= spi-dw-pci.o spi-dw-mid.o
 obj-$(CONFIG_SPI_EP93XX)		+= spi-ep93xx.o
+obj-$(CONFIG_SPI_FALCON)		+= spi-falcon.o
 obj-$(CONFIG_SPI_FSL_LIB)		+= spi-fsl-lib.o
 obj-$(CONFIG_SPI_FSL_ESPI)		+= spi-fsl-espi.o
 obj-$(CONFIG_SPI_FSL_SPI)		+= spi-fsl-spi.o
--- /dev/null
+++ b/drivers/spi/spi-falcon.c
@@ -0,0 +1,477 @@
+/*
+ *  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.
+ *
+ *  Copyright (C) 2010 Thomas Langer <thomas.langer@lantiq.com>
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+
+#include <lantiq_soc.h>
+
+#define DRV_NAME			"falcon_spi"
+
+#define FALCON_SPI_XFER_BEGIN		(1 << 0)
+#define FALCON_SPI_XFER_END		(1 << 1)
+
+/* Bus Read Configuration Register0 */
+#define LTQ_BUSRCON0	0x00000010
+/* Bus Write Configuration Register0 */
+#define LTQ_BUSWCON0	0x00000018
+/* Serial Flash Configuration Register */
+#define LTQ_SFCON	0x00000080
+/* Serial Flash Time Register */
+#define LTQ_SFTIME	0x00000084
+/* Serial Flash Status Register */
+#define LTQ_SFSTAT	0x00000088
+/* Serial Flash Command Register */
+#define LTQ_SFCMD	0x0000008C
+/* Serial Flash Address Register */
+#define LTQ_SFADDR	0x00000090
+/* Serial Flash Data Register */
+#define LTQ_SFDATA	0x00000094
+/* Serial Flash I/O Control Register */
+#define LTQ_SFIO	0x00000098
+/* EBU Clock Control Register */
+#define LTQ_EBUCC	0x000000C4
+
+/* Dummy Phase Length */
+#define SFCMD_DUMLEN_OFFSET	16
+#define SFCMD_DUMLEN_MASK	0x000F0000
+/* Chip Select */
+#define SFCMD_CS_OFFSET		24
+#define SFCMD_CS_MASK		0x07000000
+/* field offset */
+#define SFCMD_ALEN_OFFSET	20
+#define SFCMD_ALEN_MASK		0x00700000
+/* SCK Rise-edge Position */
+#define SFTIME_SCKR_POS_OFFSET	8
+#define SFTIME_SCKR_POS_MASK	0x00000F00
+/* SCK Period */
+#define SFTIME_SCK_PER_OFFSET	0
+#define SFTIME_SCK_PER_MASK	0x0000000F
+/* SCK Fall-edge Position */
+#define SFTIME_SCKF_POS_OFFSET	12
+#define SFTIME_SCKF_POS_MASK	0x0000F000
+/* Device Size */
+#define SFCON_DEV_SIZE_A23_0	0x03000000
+#define SFCON_DEV_SIZE_MASK	0x0F000000
+/* Read Data Position */
+#define SFTIME_RD_POS_MASK	0x000F0000
+/* Data Output */
+#define SFIO_UNUSED_WD_MASK	0x0000000F
+/* Command Opcode mask */
+#define SFCMD_OPC_MASK		0x000000FF
+/* dlen bytes of data to write */
+#define SFCMD_DIR_WRITE		0x00000100
+/* Data Length offset */
+#define SFCMD_DLEN_OFFSET	9
+/* Command Error */
+#define SFSTAT_CMD_ERR		0x20000000
+/* Access Command Pending */
+#define SFSTAT_CMD_PEND		0x00400000
+/* Frequency set to 100MHz. */
+#define EBUCC_EBUDIV_SELF100	0x00000001
+/* Serial Flash */
+#define BUSRCON0_AGEN_SERIAL_FLASH	0xF0000000
+/* 8-bit multiplexed */
+#define BUSRCON0_PORTW_8_BIT_MUX	0x00000000
+/* Serial Flash */
+#define BUSWCON0_AGEN_SERIAL_FLASH	0xF0000000
+/* Chip Select after opcode */
+#define SFCMD_KEEP_CS_KEEP_SELECTED	0x00008000
+
+struct falcon_spi {
+	u32 sfcmd; /* for caching of opcode, direction, ... */
+	struct spi_master *master;
+};
+
+int
+falcon_spi_xfer(struct spi_device *spi,
+		    struct spi_transfer *t,
+		    unsigned long flags)
+{
+	struct device *dev = &spi->dev;
+	struct falcon_spi *priv = spi_master_get_devdata(spi->master);
+	const u8 *txp = t->tx_buf;
+	u8 *rxp = t->rx_buf;
+	unsigned int bytelen = ((8 * t->len + 7) / 8);
+	unsigned int len, alen, dumlen;
+	u32 val;
+	enum {
+		state_init,
+		state_command_prepare,
+		state_write,
+		state_read,
+		state_disable_cs,
+		state_end
+	} state = state_init;
+
+	do {
+		switch (state) {
+		case state_init: /* detect phase of upper layer sequence */
+		{
+			/* initial write ? */
+			if (flags & FALCON_SPI_XFER_BEGIN) {
+				if (!txp) {
+					dev_err(dev,
+						"BEGIN without tx data!\n");
+					return -1;
+				}
+				/*
+				 * Prepare the parts of the sfcmd register,
+				 * which should not
+				 * change during a sequence!
+				 * Only exception are the length fields,
+				 * especially alen and dumlen.
+				 */
+
+				priv->sfcmd = ((spi->chip_select
+						<< SFCMD_CS_OFFSET)
+					       & SFCMD_CS_MASK);
+				priv->sfcmd |= SFCMD_KEEP_CS_KEEP_SELECTED;
+				priv->sfcmd |= *txp;
+				txp++;
+				bytelen--;
+				if (bytelen) {
+					/* more data:
+					 * maybe address and/or dummy */
+					state = state_command_prepare;
+					break;
+				} else {
+					dev_dbg(dev, "write cmd %02X\n",
+						priv->sfcmd & SFCMD_OPC_MASK);
+				}
+			}
+			/* continued write ? */
+			if (txp && bytelen) {
+				state = state_write;
+				break;
+			}
+			/* read data? */
+			if (rxp && bytelen) {
+				state = state_read;
+				break;
+			}
+			/* end of sequence? */
+			if (flags & FALCON_SPI_XFER_END)
+				state = state_disable_cs;
+			else
+				state = state_end;
+			break;
+		}
+		case state_command_prepare: /* collect tx data for
+					       address and dummy phase */
+		{
+			/* txp is valid, already checked */
+			val = 0;
+			alen = 0;
+			dumlen = 0;
+			while (bytelen > 0) {
+				if (alen < 3) {
+					val = (val<<8)|(*txp++);
+					alen++;
+				} else if ((dumlen < 15) && (*txp == 0)) {
+					/*
+					 * assume dummy bytes are set to 0
+					 * from upper layer
+					 */
+					dumlen++;
+					txp++;
+				} else
+					break;
+				bytelen--;
+			}
+			priv->sfcmd &= ~(SFCMD_ALEN_MASK | SFCMD_DUMLEN_MASK);
+			priv->sfcmd |= (alen << SFCMD_ALEN_OFFSET) |
+					 (dumlen << SFCMD_DUMLEN_OFFSET);
+			if (alen > 0)
+				ltq_ebu_w32(val, LTQ_SFADDR);
+
+			dev_dbg(dev, "write cmd %02X, alen=%d "
+				"(addr=%06X) dumlen=%d\n",
+				priv->sfcmd & SFCMD_OPC_MASK,
+				alen, val, dumlen);
+
+			if (bytelen > 0) {
+				/* continue with write */
+				state = state_write;
+			} else if (flags & FALCON_SPI_XFER_END) {
+				/* end of sequence? */
+				state = state_disable_cs;
+			} else {
+				/* go to end and expect another
+				 * call (read or write) */
+				state = state_end;
+			}
+			break;
+		}
+		case state_write:
+		{
+			/* txp still valid */
+			priv->sfcmd |= SFCMD_DIR_WRITE;
+			len = 0;
+			val = 0;
+			do {
+				if (bytelen--)
+					val |= (*txp++) << (8 * len++);
+				if ((flags & FALCON_SPI_XFER_END)
+				    && (bytelen == 0)) {
+					priv->sfcmd &=
+						~SFCMD_KEEP_CS_KEEP_SELECTED;
+				}
+				if ((len == 4) || (bytelen == 0)) {
+					ltq_ebu_w32(val, LTQ_SFDATA);
+					ltq_ebu_w32(priv->sfcmd
+						| (len<<SFCMD_DLEN_OFFSET),
+						LTQ_SFCMD);
+					len = 0;
+					val = 0;
+					priv->sfcmd &= ~(SFCMD_ALEN_MASK
+							 | SFCMD_DUMLEN_MASK);
+				}
+			} while (bytelen);
+			state = state_end;
+			break;
+		}
+		case state_read:
+		{
+			/* read data */
+			priv->sfcmd &= ~SFCMD_DIR_WRITE;
+			do {
+				if ((flags & FALCON_SPI_XFER_END)
+				    && (bytelen <= 4)) {
+					priv->sfcmd &=
+						~SFCMD_KEEP_CS_KEEP_SELECTED;
+				}
+				len = (bytelen > 4) ? 4 : bytelen;
+				bytelen -= len;
+				ltq_ebu_w32(priv->sfcmd
+					|(len<<SFCMD_DLEN_OFFSET), LTQ_SFCMD);
+				priv->sfcmd &= ~(SFCMD_ALEN_MASK
+						 | SFCMD_DUMLEN_MASK);
+				do {
+					val = ltq_ebu_r32(LTQ_SFSTAT);
+					if (val & SFSTAT_CMD_ERR) {
+						/* reset error status */
+						dev_err(dev, "SFSTAT: CMD_ERR "
+							"(%x)\n", val);
+						ltq_ebu_w32(SFSTAT_CMD_ERR,
+							LTQ_SFSTAT);
+						return -1;
+					}
+				} while (val & SFSTAT_CMD_PEND);
+				val = ltq_ebu_r32(LTQ_SFDATA);
+				do {
+					*rxp = (val & 0xFF);
+					rxp++;
+					val >>= 8;
+					len--;
+				} while (len);
+			} while (bytelen);
+			state = state_end;
+			break;
+		}
+		case state_disable_cs:
+		{
+			priv->sfcmd &= ~SFCMD_KEEP_CS_KEEP_SELECTED;
+			ltq_ebu_w32(priv->sfcmd | (0 << SFCMD_DLEN_OFFSET),
+				LTQ_SFCMD);
+			val = ltq_ebu_r32(LTQ_SFSTAT);
+			if (val & SFSTAT_CMD_ERR) {
+				/* reset error status */
+				dev_err(dev, "SFSTAT: CMD_ERR (%x)\n", val);
+				ltq_ebu_w32(SFSTAT_CMD_ERR, LTQ_SFSTAT);
+				return -1;
+			}
+			state = state_end;
+			break;
+		}
+		case state_end:
+			break;
+		}
+	} while (state != state_end);
+
+	return 0;
+}
+
+static int
+falcon_spi_setup(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	const u32 ebuclk = CLOCK_100M;
+	unsigned int i;
+	unsigned long flags;
+
+	dev_dbg(dev, "setup\n");
+
+	if (spi->master->bus_num > 0 || spi->chip_select > 0)
+		return -ENODEV;
+
+	spin_lock_irqsave(&ebu_lock, flags);
+
+	if (ebuclk < spi->max_speed_hz) {
+		/* set EBU clock to 100 MHz */
+		ltq_sys1_w32_mask(0, EBUCC_EBUDIV_SELF100, LTQ_EBUCC);
+		i = 1; /* divider */
+	} else {
+		/* set EBU clock to 50 MHz */
+		ltq_sys1_w32_mask(EBUCC_EBUDIV_SELF100, 0, LTQ_EBUCC);
+
+		/* search for suitable divider */
+		for (i = 1; i < 7; i++) {
+			if (ebuclk / i <= spi->max_speed_hz)
+				break;
+		}
+	}
+
+	/* setup period of serial clock */
+	ltq_ebu_w32_mask(SFTIME_SCKF_POS_MASK
+		     | SFTIME_SCKR_POS_MASK
+		     | SFTIME_SCK_PER_MASK,
+		     (i << SFTIME_SCKR_POS_OFFSET)
+		     | (i << (SFTIME_SCK_PER_OFFSET + 1)),
+		     LTQ_SFTIME);
+
+	/* set some bits of unused_wd, to not trigger HOLD/WP
+	 * signals on non QUAD flashes */
+	ltq_ebu_w32((SFIO_UNUSED_WD_MASK & (0x8 | 0x4)), LTQ_SFIO);
+
+	ltq_ebu_w32(BUSRCON0_AGEN_SERIAL_FLASH | BUSRCON0_PORTW_8_BIT_MUX,
+		LTQ_BUSRCON0);
+	ltq_ebu_w32(BUSWCON0_AGEN_SERIAL_FLASH, LTQ_BUSWCON0);
+	/* set address wrap around to maximum for 24-bit addresses */
+	ltq_ebu_w32_mask(SFCON_DEV_SIZE_MASK, SFCON_DEV_SIZE_A23_0, LTQ_SFCON);
+
+	spin_unlock_irqrestore(&ebu_lock, flags);
+
+	return 0;
+}
+
+static int
+falcon_spi_transfer(struct spi_device *spi, struct spi_message *m)
+{
+	struct falcon_spi *priv = spi_master_get_devdata(spi->master);
+	struct spi_transfer *t;
+	unsigned long spi_flags;
+	unsigned long flags;
+	int ret = 0;
+
+	priv->sfcmd = 0;
+	m->actual_length = 0;
+
+	spi_flags = FALCON_SPI_XFER_BEGIN;
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		if (list_is_last(&t->transfer_list, &m->transfers))
+			spi_flags |= FALCON_SPI_XFER_END;
+
+		spin_lock_irqsave(&ebu_lock, flags);
+		ret = falcon_spi_xfer(spi, t, spi_flags);
+		spin_unlock_irqrestore(&ebu_lock, flags);
+
+		if (ret)
+			break;
+
+		m->actual_length += t->len;
+
+		if (t->delay_usecs || t->cs_change)
+			BUG();
+
+		spi_flags = 0;
+	}
+
+	m->status = ret;
+	m->complete(m->context);
+
+	return 0;
+}
+
+static void
+falcon_spi_cleanup(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+
+	dev_dbg(dev, "cleanup\n");
+}
+
+static int __devinit
+falcon_spi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct falcon_spi *priv;
+	struct spi_master *master;
+	int ret;
+
+	dev_dbg(dev, "probing\n");
+
+	master = spi_alloc_master(&pdev->dev, sizeof(*priv));
+	if (!master) {
+		dev_err(dev, "no memory for spi_master\n");
+		return -ENOMEM;
+	}
+
+	priv = spi_master_get_devdata(master);
+	priv->master = master;
+
+	master->mode_bits = SPI_MODE_3;
+	master->num_chipselect = 1;
+	master->bus_num = 0;
+
+	master->setup = falcon_spi_setup;
+	master->transfer = falcon_spi_transfer;
+	master->cleanup = falcon_spi_cleanup;
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = spi_register_master(master);
+	if (ret)
+		spi_master_put(master);
+
+	return ret;
+}
+
+static int __devexit
+falcon_spi_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct falcon_spi *priv = platform_get_drvdata(pdev);
+
+	dev_dbg(dev, "removed\n");
+
+	spi_unregister_master(priv->master);
+
+	return 0;
+}
+
+static struct platform_driver falcon_spi_driver = {
+	.probe	= falcon_spi_probe,
+	.remove	= __devexit_p(falcon_spi_remove),
+	.driver = {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE
+	}
+};
+
+static int __init
+falcon_spi_init(void)
+{
+	return platform_driver_register(&falcon_spi_driver);
+}
+
+static void __exit
+falcon_spi_exit(void)
+{
+	platform_driver_unregister(&falcon_spi_driver);
+}
+
+module_init(falcon_spi_init);
+module_exit(falcon_spi_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Lantiq Falcon SPI controller driver");
--- a/arch/mips/include/asm/mach-lantiq/falcon/lantiq_soc.h
+++ b/arch/mips/include/asm/mach-lantiq/falcon/lantiq_soc.h
@@ -48,6 +48,10 @@
 
 #define LTQ_EBU_MODCON  0x000C
 
+/* I2C */
+#define LTQ_I2C_BASE_ADDR	0x1E200000
+#define LTQ_I2C_SIZE		0x00010000
+
 /* GPIO */
 #define LTQ_GPIO0_BASE_ADDR     0x1D810000
 #define LTQ_GPIO0_SIZE          0x0080
@@ -92,6 +96,7 @@
 
 /* Activation Status Register */
 #define ACTS_ASC1_ACT	0x00000800
+#define ACTS_I2C_ACT	0x00004000
 #define ACTS_P0		0x00010000
 #define ACTS_P1		0x00010000
 #define ACTS_P2		0x00020000