From bdf3fc107c0849c132c643f5d88409ab170b06cf Mon Sep 17 00:00:00 2001
From: popcornmix <popcornmix@gmail.com>
Date: Wed, 17 Jun 2015 15:41:33 +0100
Subject: [PATCH 019/171] Add Chris Boot's spi driver.

spi: bcm2708: add device tree support

Add DT support to driver and add to .dtsi file.
Setup pins and spidev in .dts file.
SPI is disabled by default.

Signed-off-by: Noralf Tronnes <notro@tronnes.org>

BCM2708: don't register SPI controller when using DT

The device for the SPI controller is in the Device Tree.
Only register the device when not using DT.

Signed-off-by: Noralf Tronnes <notro@tronnes.org>

spi: bcm2835: make driver available on ARCH_BCM2708

Make this driver available on ARCH_BCM2708

Signed-off-by: Noralf Tronnes <notro@tronnes.org>

bcm2708: Remove the prohibition on mixing SPIDEV and DT

spi-bcm2708: Prepare for Common Clock Framework migration

As part of migrating to use the Common Clock Framework, replace clk_enable()
with clk_prepare_enable() and clk_disable() with clk_disable_unprepare().
This does not affect behaviour under the current clock implementation.

Also add a missing clk_disable_unprepare() in the probe error path.

Signed-off-by: Noralf Tronnes <notro@tronnes.org>
---
 arch/arm/mach-bcm2708/Kconfig   |   7 +
 arch/arm/mach-bcm2708/bcm2708.c |  53 ++++
 arch/arm/mach-bcm2709/bcm2709.c |  53 ++++
 drivers/spi/Kconfig             |  10 +-
 drivers/spi/Makefile            |   1 +
 drivers/spi/spi-bcm2708.c       | 635 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 758 insertions(+), 1 deletion(-)
 create mode 100644 drivers/spi/spi-bcm2708.c

--- a/arch/arm/mach-bcm2708/Kconfig
+++ b/arch/arm/mach-bcm2708/Kconfig
@@ -35,4 +35,11 @@ config BCM2708_NOL2CACHE
         help
           Do not allow ARM to use GPU's L2 cache. Requires disable_l2cache in config.txt.
 
+config BCM2708_SPIDEV
+	bool "Bind spidev to SPI0 master"
+	depends on MACH_BCM2708
+	depends on SPI
+	default y
+	help
+	  Binds spidev driver to the SPI0 master
 endmenu
--- a/arch/arm/mach-bcm2708/bcm2708.c
+++ b/arch/arm/mach-bcm2708/bcm2708.c
@@ -34,6 +34,7 @@
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/of_platform.h>
+#include <linux/spi/spi.h>
 #include <linux/gpio/machine.h>
 
 #include <linux/version.h>
@@ -505,6 +506,50 @@ static struct platform_device bcm2708_al
 	       },
 };
 
+static struct resource bcm2708_spi_resources[] = {
+	{
+		.start = SPI0_BASE,
+		.end = SPI0_BASE + SZ_256 - 1,
+		.flags = IORESOURCE_MEM,
+	}, {
+		.start = IRQ_SPI,
+		.end = IRQ_SPI,
+		.flags = IORESOURCE_IRQ,
+	}
+};
+
+
+static u64 bcm2708_spi_dmamask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON);
+static struct platform_device bcm2708_spi_device = {
+	.name = "bcm2708_spi",
+	.id = 0,
+	.num_resources = ARRAY_SIZE(bcm2708_spi_resources),
+	.resource = bcm2708_spi_resources,
+	.dev = {
+		.dma_mask = &bcm2708_spi_dmamask,
+		.coherent_dma_mask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON)},
+};
+
+#ifdef CONFIG_BCM2708_SPIDEV
+static struct spi_board_info bcm2708_spi_devices[] = {
+#ifdef CONFIG_SPI_SPIDEV
+	{
+		.modalias = "spidev",
+		.max_speed_hz = 500000,
+		.bus_num = 0,
+		.chip_select = 0,
+		.mode = SPI_MODE_0,
+	}, {
+		.modalias = "spidev",
+		.max_speed_hz = 500000,
+		.bus_num = 0,
+		.chip_select = 1,
+		.mode = SPI_MODE_0,
+	}
+#endif
+};
+#endif
+
 static struct platform_device bcm2835_thermal_device = {
 	.name = "bcm2835_thermal",
 };
@@ -655,6 +700,8 @@ void __init bcm2708_init(void)
 	for (i = 0; i < ARRAY_SIZE(bcm2708_alsa_devices); i++)
 		bcm_register_device_dt(&bcm2708_alsa_devices[i]);
 
+	bcm_register_device_dt(&bcm2708_spi_device);
+
 	bcm_register_device_dt(&bcm2835_thermal_device);
 
 	if (!use_dt) {
@@ -665,6 +712,12 @@ void __init bcm2708_init(void)
 	}
 	system_rev = boardrev;
 	system_serial_low = serial;
+
+#ifdef CONFIG_BCM2708_SPIDEV
+	if (!use_dt)
+	    spi_register_board_info(bcm2708_spi_devices,
+				    ARRAY_SIZE(bcm2708_spi_devices));
+#endif
 }
 
 static void timer_set_mode(enum clock_event_mode mode,
--- a/arch/arm/mach-bcm2709/bcm2709.c
+++ b/arch/arm/mach-bcm2709/bcm2709.c
@@ -34,6 +34,7 @@
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/of_platform.h>
+#include <linux/spi/spi.h>
 #include <linux/gpio/machine.h>
 
 #include <linux/version.h>
@@ -525,6 +526,50 @@ static struct platform_device bcm2708_al
 	       },
 };
 
+static struct resource bcm2708_spi_resources[] = {
+	{
+		.start = SPI0_BASE,
+		.end = SPI0_BASE + SZ_256 - 1,
+		.flags = IORESOURCE_MEM,
+	}, {
+		.start = IRQ_SPI,
+		.end = IRQ_SPI,
+		.flags = IORESOURCE_IRQ,
+	}
+};
+
+
+static u64 bcm2708_spi_dmamask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON);
+static struct platform_device bcm2708_spi_device = {
+	.name = "bcm2708_spi",
+	.id = 0,
+	.num_resources = ARRAY_SIZE(bcm2708_spi_resources),
+	.resource = bcm2708_spi_resources,
+	.dev = {
+		.dma_mask = &bcm2708_spi_dmamask,
+		.coherent_dma_mask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON)},
+};
+
+#ifdef CONFIG_BCM2708_SPIDEV
+static struct spi_board_info bcm2708_spi_devices[] = {
+#ifdef CONFIG_SPI_SPIDEV
+	{
+		.modalias = "spidev",
+		.max_speed_hz = 500000,
+		.bus_num = 0,
+		.chip_select = 0,
+		.mode = SPI_MODE_0,
+	}, {
+		.modalias = "spidev",
+		.max_speed_hz = 500000,
+		.bus_num = 0,
+		.chip_select = 1,
+		.mode = SPI_MODE_0,
+	}
+#endif
+};
+#endif
+
 static struct platform_device bcm2835_thermal_device = {
 	.name = "bcm2835_thermal",
 };
@@ -675,6 +720,8 @@ void __init bcm2709_init(void)
 	for (i = 0; i < ARRAY_SIZE(bcm2708_alsa_devices); i++)
 		bcm_register_device_dt(&bcm2708_alsa_devices[i]);
 
+	bcm_register_device_dt(&bcm2708_spi_device);
+
 	bcm_register_device_dt(&bcm2835_thermal_device);
 
 	if (!use_dt) {
@@ -685,6 +732,12 @@ void __init bcm2709_init(void)
 	}
 	system_rev = boardrev;
 	system_serial_low = serial;
+
+#ifdef CONFIG_BCM2708_SPIDEV
+	if (!use_dt)
+	    spi_register_board_info(bcm2708_spi_devices,
+				    ARRAY_SIZE(bcm2708_spi_devices));
+#endif
 }
 
 #ifdef SYSTEM_TIMER
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -77,7 +77,7 @@ config SPI_ATMEL
 
 config SPI_BCM2835
 	tristate "BCM2835 SPI controller"
-	depends on ARCH_BCM2835 || COMPILE_TEST
+	depends on ARCH_BCM2835 || ARCH_BCM2708 || ARCH_BCM2709 || COMPILE_TEST
 	depends on GPIOLIB
 	help
 	  This selects a driver for the Broadcom BCM2835 SPI master.
@@ -87,6 +87,14 @@ config SPI_BCM2835
 	  is for the regular SPI controller. Slave mode operation is not also
 	  not supported.
 
+config SPI_BCM2708
+	tristate "BCM2708 SPI controller driver (SPI0)"
+	depends on MACH_BCM2708 || MACH_BCM2709
+	help
+	  This selects a driver for the Broadcom BCM2708 SPI master (SPI0). This
+	  driver is not compatible with the "Universal SPI Master" or the SPI slave
+	  device.
+
 config SPI_BFIN5XX
 	tristate "SPI controller driver for ADI Blackfin5xx"
 	depends on BLACKFIN && !BF60x
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_SPI_BCM63XX)		+= spi-bcm63x
 obj-$(CONFIG_SPI_BCM63XX_HSSPI)		+= spi-bcm63xx-hsspi.o
 obj-$(CONFIG_SPI_BFIN5XX)		+= spi-bfin5xx.o
 obj-$(CONFIG_SPI_ADI_V3)                += spi-adi-v3.o
+obj-$(CONFIG_SPI_BCM2708)		+= spi-bcm2708.o
 obj-$(CONFIG_SPI_BFIN_SPORT)		+= spi-bfin-sport.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi-bitbang.o
 obj-$(CONFIG_SPI_BUTTERFLY)		+= spi-butterfly.o
--- /dev/null
+++ b/drivers/spi/spi-bcm2708.c
@@ -0,0 +1,635 @@
+/*
+ * Driver for Broadcom BCM2708 SPI Controllers
+ *
+ * Copyright (C) 2012 Chris Boot
+ *
+ * This driver is inspired by:
+ * spi-ath79.c, Copyright (C) 2009-2011 Gabor Juhos <juhosg@openwrt.org>
+ * spi-atmel.c, Copyright (C) 2006 Atmel Corporation
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/log2.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+/* SPI register offsets */
+#define SPI_CS			0x00
+#define SPI_FIFO		0x04
+#define SPI_CLK			0x08
+#define SPI_DLEN		0x0c
+#define SPI_LTOH		0x10
+#define SPI_DC			0x14
+
+/* Bitfields in CS */
+#define SPI_CS_LEN_LONG		0x02000000
+#define SPI_CS_DMA_LEN		0x01000000
+#define SPI_CS_CSPOL2		0x00800000
+#define SPI_CS_CSPOL1		0x00400000
+#define SPI_CS_CSPOL0		0x00200000
+#define SPI_CS_RXF		0x00100000
+#define SPI_CS_RXR		0x00080000
+#define SPI_CS_TXD		0x00040000
+#define SPI_CS_RXD		0x00020000
+#define SPI_CS_DONE		0x00010000
+#define SPI_CS_LEN		0x00002000
+#define SPI_CS_REN		0x00001000
+#define SPI_CS_ADCS		0x00000800
+#define SPI_CS_INTR		0x00000400
+#define SPI_CS_INTD		0x00000200
+#define SPI_CS_DMAEN		0x00000100
+#define SPI_CS_TA		0x00000080
+#define SPI_CS_CSPOL		0x00000040
+#define SPI_CS_CLEAR_RX		0x00000020
+#define SPI_CS_CLEAR_TX		0x00000010
+#define SPI_CS_CPOL		0x00000008
+#define SPI_CS_CPHA		0x00000004
+#define SPI_CS_CS_10		0x00000002
+#define SPI_CS_CS_01		0x00000001
+
+#define SPI_TIMEOUT_MS	150
+
+#define DRV_NAME	"bcm2708_spi"
+
+struct bcm2708_spi {
+	spinlock_t lock;
+	void __iomem *base;
+	int irq;
+	struct clk *clk;
+	bool stopping;
+
+	struct list_head queue;
+	struct workqueue_struct *workq;
+	struct work_struct work;
+	struct completion done;
+
+	const u8 *tx_buf;
+	u8 *rx_buf;
+	int len;
+};
+
+struct bcm2708_spi_state {
+	u32 cs;
+	u16 cdiv;
+};
+
+/*
+ * This function sets the ALT mode on the SPI pins so that we can use them with
+ * the SPI hardware.
+ *
+ * FIXME: This is a hack. Use pinmux / pinctrl.
+ */
+static void bcm2708_init_pinmode(void)
+{
+#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
+#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
+
+	int pin;
+	u32 *gpio = ioremap(GPIO_BASE, SZ_16K);
+
+	/* SPI is on GPIO 7..11 */
+	for (pin = 7; pin <= 11; pin++) {
+		INP_GPIO(pin);		/* set mode to GPIO input first */
+		SET_GPIO_ALT(pin, 0);	/* set mode to ALT 0 */
+	}
+
+	iounmap(gpio);
+
+#undef INP_GPIO
+#undef SET_GPIO_ALT
+}
+
+static inline u32 bcm2708_rd(struct bcm2708_spi *bs, unsigned reg)
+{
+	return readl(bs->base + reg);
+}
+
+static inline void bcm2708_wr(struct bcm2708_spi *bs, unsigned reg, u32 val)
+{
+	writel(val, bs->base + reg);
+}
+
+static inline void bcm2708_rd_fifo(struct bcm2708_spi *bs, int len)
+{
+	u8 byte;
+
+	while (len--) {
+		byte = bcm2708_rd(bs, SPI_FIFO);
+		if (bs->rx_buf)
+			*bs->rx_buf++ = byte;
+	}
+}
+
+static inline void bcm2708_wr_fifo(struct bcm2708_spi *bs, int len)
+{
+	u8 byte;
+	u16 val;
+
+	if (len > bs->len)
+		len = bs->len;
+
+	if (unlikely(bcm2708_rd(bs, SPI_CS) & SPI_CS_LEN)) {
+		/* LoSSI mode */
+		if (unlikely(len % 2)) {
+			printk(KERN_ERR"bcm2708_wr_fifo: length must be even, skipping.\n");
+			bs->len = 0;
+			return;
+		}
+		while (len) {
+			if (bs->tx_buf) {
+				val = *(const u16 *)bs->tx_buf;
+				bs->tx_buf += 2;
+			} else
+				val = 0;
+			bcm2708_wr(bs, SPI_FIFO, val);
+			bs->len -= 2;
+			len -= 2;
+		}
+		return;
+	}
+
+	while (len--) {
+		byte = bs->tx_buf ? *bs->tx_buf++ : 0;
+		bcm2708_wr(bs, SPI_FIFO, byte);
+		bs->len--;
+	}
+}
+
+static irqreturn_t bcm2708_spi_interrupt(int irq, void *dev_id)
+{
+	struct spi_master *master = dev_id;
+	struct bcm2708_spi *bs = spi_master_get_devdata(master);
+	u32 cs;
+
+	spin_lock(&bs->lock);
+
+	cs = bcm2708_rd(bs, SPI_CS);
+
+	if (cs & SPI_CS_DONE) {
+		if (bs->len) { /* first interrupt in a transfer */
+			/* fill the TX fifo with up to 16 bytes */
+			bcm2708_wr_fifo(bs, 16);
+		} else { /* transfer complete */
+			/* disable interrupts */
+			cs &= ~(SPI_CS_INTR | SPI_CS_INTD);
+			bcm2708_wr(bs, SPI_CS, cs);
+
+			/* drain RX FIFO */
+			while (cs & SPI_CS_RXD) {
+				bcm2708_rd_fifo(bs, 1);
+				cs = bcm2708_rd(bs, SPI_CS);
+			}
+
+			/* wake up our bh */
+			complete(&bs->done);
+		}
+	} else if (cs & SPI_CS_RXR) {
+		/* read 12 bytes of data */
+		bcm2708_rd_fifo(bs, 12);
+
+		/* write up to 12 bytes */
+		bcm2708_wr_fifo(bs, 12);
+	}
+
+	spin_unlock(&bs->lock);
+
+	return IRQ_HANDLED;
+}
+
+static int bcm2708_setup_state(struct spi_master *master,
+		struct device *dev, struct bcm2708_spi_state *state,
+		u32 hz, u8 csel, u8 mode, u8 bpw)
+{
+	struct bcm2708_spi *bs = spi_master_get_devdata(master);
+	int cdiv;
+	unsigned long bus_hz;
+	u32 cs = 0;
+
+	bus_hz = clk_get_rate(bs->clk);
+
+	if (hz >= bus_hz) {
+		cdiv = 2; /* bus_hz / 2 is as fast as we can go */
+	} else if (hz) {
+		cdiv = DIV_ROUND_UP(bus_hz, hz);
+
+		/* CDIV must be a power of 2, so round up */
+		cdiv = roundup_pow_of_two(cdiv);
+
+		if (cdiv > 65536) {
+			dev_dbg(dev,
+				"setup: %d Hz too slow, cdiv %u; min %ld Hz\n",
+				hz, cdiv, bus_hz / 65536);
+			return -EINVAL;
+		} else if (cdiv == 65536) {
+			cdiv = 0;
+		} else if (cdiv == 1) {
+			cdiv = 2; /* 1 gets rounded down to 0; == 65536 */
+		}
+	} else {
+		cdiv = 0;
+	}
+
+	switch (bpw) {
+	case 8:
+		break;
+	case 9:
+		/* Reading in LoSSI mode is a special case. See 'BCM2835 ARM Peripherals' datasheet */
+		cs |= SPI_CS_LEN;
+		break;
+	default:
+		dev_dbg(dev, "setup: invalid bits_per_word %u (must be 8 or 9)\n",
+			bpw);
+		return -EINVAL;
+	}
+
+	if (mode & SPI_CPOL)
+		cs |= SPI_CS_CPOL;
+	if (mode & SPI_CPHA)
+		cs |= SPI_CS_CPHA;
+
+	if (!(mode & SPI_NO_CS)) {
+		if (mode & SPI_CS_HIGH) {
+			cs |= SPI_CS_CSPOL;
+			cs |= SPI_CS_CSPOL0 << csel;
+		}
+
+		cs |= csel;
+	} else {
+		cs |= SPI_CS_CS_10 | SPI_CS_CS_01;
+	}
+
+	if (state) {
+		state->cs = cs;
+		state->cdiv = cdiv;
+		dev_dbg(dev, "setup: want %d Hz; "
+			"bus_hz=%lu / cdiv=%u == %lu Hz; "
+			"mode %u: cs 0x%08X\n",
+			hz, bus_hz, cdiv, bus_hz/cdiv, mode, cs);
+	}
+
+	return 0;
+}
+
+static int bcm2708_process_transfer(struct bcm2708_spi *bs,
+		struct spi_message *msg, struct spi_transfer *xfer)
+{
+	struct spi_device *spi = msg->spi;
+	struct bcm2708_spi_state state, *stp;
+	int ret;
+	u32 cs;
+
+	if (bs->stopping)
+		return -ESHUTDOWN;
+
+	if (xfer->bits_per_word || xfer->speed_hz) {
+		ret = bcm2708_setup_state(spi->master, &spi->dev, &state,
+			xfer->speed_hz ? xfer->speed_hz : spi->max_speed_hz,
+			spi->chip_select, spi->mode,
+			xfer->bits_per_word ? xfer->bits_per_word :
+				spi->bits_per_word);
+		if (ret)
+			return ret;
+
+		stp = &state;
+	} else {
+		stp = spi->controller_state;
+	}
+
+	reinit_completion(&bs->done);
+	bs->tx_buf = xfer->tx_buf;
+	bs->rx_buf = xfer->rx_buf;
+	bs->len = xfer->len;
+
+	cs = stp->cs | SPI_CS_INTR | SPI_CS_INTD | SPI_CS_TA;
+
+	bcm2708_wr(bs, SPI_CLK, stp->cdiv);
+	bcm2708_wr(bs, SPI_CS, cs);
+
+	ret = wait_for_completion_timeout(&bs->done,
+			msecs_to_jiffies(SPI_TIMEOUT_MS));
+	if (ret == 0) {
+		dev_err(&spi->dev, "transfer timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	if (xfer->delay_usecs)
+		udelay(xfer->delay_usecs);
+
+	if (list_is_last(&xfer->transfer_list, &msg->transfers) ||
+			xfer->cs_change) {
+		/* clear TA and interrupt flags */
+		bcm2708_wr(bs, SPI_CS, stp->cs);
+	}
+
+	msg->actual_length += (xfer->len - bs->len);
+
+	return 0;
+}
+
+static void bcm2708_work(struct work_struct *work)
+{
+	struct bcm2708_spi *bs = container_of(work, struct bcm2708_spi, work);
+	unsigned long flags;
+	struct spi_message *msg;
+	struct spi_transfer *xfer;
+	int status = 0;
+
+	spin_lock_irqsave(&bs->lock, flags);
+	while (!list_empty(&bs->queue)) {
+		msg = list_first_entry(&bs->queue, struct spi_message, queue);
+		list_del_init(&msg->queue);
+		spin_unlock_irqrestore(&bs->lock, flags);
+
+		list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+			status = bcm2708_process_transfer(bs, msg, xfer);
+			if (status)
+				break;
+		}
+
+		msg->status = status;
+		msg->complete(msg->context);
+
+		spin_lock_irqsave(&bs->lock, flags);
+	}
+	spin_unlock_irqrestore(&bs->lock, flags);
+}
+
+static int bcm2708_spi_setup(struct spi_device *spi)
+{
+	struct bcm2708_spi *bs = spi_master_get_devdata(spi->master);
+	struct bcm2708_spi_state *state;
+	int ret;
+
+	if (bs->stopping)
+		return -ESHUTDOWN;
+
+	if (!(spi->mode & SPI_NO_CS) &&
+			(spi->chip_select > spi->master->num_chipselect)) {
+		dev_dbg(&spi->dev,
+			"setup: invalid chipselect %u (%u defined)\n",
+			spi->chip_select, spi->master->num_chipselect);
+		return -EINVAL;
+	}
+
+	state = spi->controller_state;
+	if (!state) {
+		state = kzalloc(sizeof(*state), GFP_KERNEL);
+		if (!state)
+			return -ENOMEM;
+
+		spi->controller_state = state;
+	}
+
+	ret = bcm2708_setup_state(spi->master, &spi->dev, state,
+		spi->max_speed_hz, spi->chip_select, spi->mode,
+		spi->bits_per_word);
+	if (ret < 0) {
+		kfree(state);
+		spi->controller_state = NULL;
+                return ret;
+	}
+
+	dev_dbg(&spi->dev,
+		"setup: cd %d: %d Hz, bpw %u, mode 0x%x -> CS=%08x CDIV=%04x\n",
+		spi->chip_select, spi->max_speed_hz, spi->bits_per_word,
+		spi->mode, state->cs, state->cdiv);
+
+	return 0;
+}
+
+static int bcm2708_spi_transfer(struct spi_device *spi, struct spi_message *msg)
+{
+	struct bcm2708_spi *bs = spi_master_get_devdata(spi->master);
+	struct spi_transfer *xfer;
+	int ret;
+	unsigned long flags;
+
+	if (unlikely(list_empty(&msg->transfers)))
+		return -EINVAL;
+
+	if (bs->stopping)
+		return -ESHUTDOWN;
+
+	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+		if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
+			dev_dbg(&spi->dev, "missing rx or tx buf\n");
+			return -EINVAL;
+		}
+
+		if (!xfer->bits_per_word || xfer->speed_hz)
+			continue;
+
+		ret = bcm2708_setup_state(spi->master, &spi->dev, NULL,
+			xfer->speed_hz ? xfer->speed_hz : spi->max_speed_hz,
+			spi->chip_select, spi->mode,
+			xfer->bits_per_word ? xfer->bits_per_word :
+				spi->bits_per_word);
+		if (ret)
+			return ret;
+	}
+
+	msg->status = -EINPROGRESS;
+	msg->actual_length = 0;
+
+	spin_lock_irqsave(&bs->lock, flags);
+	list_add_tail(&msg->queue, &bs->queue);
+	queue_work(bs->workq, &bs->work);
+	spin_unlock_irqrestore(&bs->lock, flags);
+
+	return 0;
+}
+
+static void bcm2708_spi_cleanup(struct spi_device *spi)
+{
+	if (spi->controller_state) {
+		kfree(spi->controller_state);
+		spi->controller_state = NULL;
+	}
+}
+
+static int bcm2708_spi_probe(struct platform_device *pdev)
+{
+	struct resource *regs;
+	int irq, err = -ENOMEM;
+	struct clk *clk;
+	struct spi_master *master;
+	struct bcm2708_spi *bs;
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!regs) {
+		dev_err(&pdev->dev, "could not get IO memory\n");
+		return -ENXIO;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "could not get IRQ\n");
+		return irq;
+	}
+
+	clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk));
+		return PTR_ERR(clk);
+	}
+
+	bcm2708_init_pinmode();
+
+	master = spi_alloc_master(&pdev->dev, sizeof(*bs));
+	if (!master) {
+		dev_err(&pdev->dev, "spi_alloc_master() failed\n");
+		goto out_clk_put;
+	}
+
+	/* the spi->mode bits understood by this driver: */
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_NO_CS;
+
+	master->bus_num = pdev->id;
+	master->num_chipselect = 3;
+	master->setup = bcm2708_spi_setup;
+	master->transfer = bcm2708_spi_transfer;
+	master->cleanup = bcm2708_spi_cleanup;
+	master->dev.of_node = pdev->dev.of_node;
+	platform_set_drvdata(pdev, master);
+
+	bs = spi_master_get_devdata(master);
+
+	spin_lock_init(&bs->lock);
+	INIT_LIST_HEAD(&bs->queue);
+	init_completion(&bs->done);
+	INIT_WORK(&bs->work, bcm2708_work);
+
+	bs->base = ioremap(regs->start, resource_size(regs));
+	if (!bs->base) {
+		dev_err(&pdev->dev, "could not remap memory\n");
+		goto out_master_put;
+	}
+
+	bs->workq = create_singlethread_workqueue(dev_name(&pdev->dev));
+	if (!bs->workq) {
+		dev_err(&pdev->dev, "could not create workqueue\n");
+		goto out_iounmap;
+	}
+
+	bs->irq = irq;
+	bs->clk = clk;
+	bs->stopping = false;
+
+	err = request_irq(irq, bcm2708_spi_interrupt, 0, dev_name(&pdev->dev),
+			master);
+	if (err) {
+		dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
+		goto out_workqueue;
+	}
+
+	/* initialise the hardware */
+	clk_prepare_enable(clk);
+	bcm2708_wr(bs, SPI_CS, SPI_CS_REN | SPI_CS_CLEAR_RX | SPI_CS_CLEAR_TX);
+
+	err = spi_register_master(master);
+	if (err) {
+		dev_err(&pdev->dev, "could not register SPI master: %d\n", err);
+		goto out_free_irq;
+	}
+
+	dev_info(&pdev->dev, "SPI Controller at 0x%08lx (irq %d)\n",
+		(unsigned long)regs->start, irq);
+
+	return 0;
+
+out_free_irq:
+	free_irq(bs->irq, master);
+	clk_disable_unprepare(bs->clk);
+out_workqueue:
+	destroy_workqueue(bs->workq);
+out_iounmap:
+	iounmap(bs->base);
+out_master_put:
+	spi_master_put(master);
+out_clk_put:
+	clk_put(clk);
+	return err;
+}
+
+static int bcm2708_spi_remove(struct platform_device *pdev)
+{
+	struct spi_master *master = platform_get_drvdata(pdev);
+	struct bcm2708_spi *bs = spi_master_get_devdata(master);
+
+	/* reset the hardware and block queue progress */
+	spin_lock_irq(&bs->lock);
+	bs->stopping = true;
+	bcm2708_wr(bs, SPI_CS, SPI_CS_CLEAR_RX | SPI_CS_CLEAR_TX);
+	spin_unlock_irq(&bs->lock);
+
+	flush_work(&bs->work);
+
+	clk_disable_unprepare(bs->clk);
+	clk_put(bs->clk);
+	free_irq(bs->irq, master);
+	iounmap(bs->base);
+
+	spi_unregister_master(master);
+
+	return 0;
+}
+
+static const struct of_device_id bcm2708_spi_match[] = {
+	{ .compatible = "brcm,bcm2708-spi", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, bcm2708_spi_match);
+
+static struct platform_driver bcm2708_spi_driver = {
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+		.of_match_table = bcm2708_spi_match,
+	},
+	.probe		= bcm2708_spi_probe,
+	.remove		= bcm2708_spi_remove,
+};
+
+
+static int __init bcm2708_spi_init(void)
+{
+	return platform_driver_probe(&bcm2708_spi_driver, bcm2708_spi_probe);
+}
+module_init(bcm2708_spi_init);
+
+static void __exit bcm2708_spi_exit(void)
+{
+	platform_driver_unregister(&bcm2708_spi_driver);
+}
+module_exit(bcm2708_spi_exit);
+
+
+//module_platform_driver(bcm2708_spi_driver);
+
+MODULE_DESCRIPTION("SPI controller driver for Broadcom BCM2708");
+MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);