openwrtv4/target/linux/brcm2708/patches-3.10/0019-Backport-of-Chris-Boot-s-i2c-and-spi-drivers.patch

1312 lines
33 KiB
Diff
Raw Normal View History

From eb8655fe20ef40189e9cda1441d488c5a23c89f2 Mon Sep 17 00:00:00 2001
From: popcornmix <popcornmix@gmail.com>
Date: Wed, 3 Jul 2013 00:41:10 +0100
Subject: [PATCH 019/196] Backport of Chris Boot's i2c and spi drivers.
---
arch/arm/configs/bcmrpi_cutdown_defconfig | 10 +
arch/arm/configs/bcmrpi_defconfig | 5 +
arch/arm/mach-bcm2708/Kconfig | 7 +
arch/arm/mach-bcm2708/bcm2708.c | 104 ++++-
arch/arm/mach-bcm2708/include/mach/platform.h | 3 +
drivers/i2c/busses/Kconfig | 8 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-bcm2708.c | 396 +++++++++++++++++
drivers/spi/Kconfig | 8 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-bcm2708.c | 594 ++++++++++++++++++++++++++
11 files changed, 1135 insertions(+), 2 deletions(-)
create mode 100644 drivers/i2c/busses/i2c-bcm2708.c
create mode 100644 drivers/spi/spi-bcm2708.c
diff --git a/arch/arm/configs/bcmrpi_cutdown_defconfig b/arch/arm/configs/bcmrpi_cutdown_defconfig
index e519412..a61a915 100644
--- a/arch/arm/configs/bcmrpi_cutdown_defconfig
+++ b/arch/arm/configs/bcmrpi_cutdown_defconfig
@@ -492,3 +492,13 @@ CONFIG_CRYPTO_DEFLATE=m
# CONFIG_CRYPTO_HW is not set
CONFIG_CRC_ITU_T=y
CONFIG_LIBCRC32C=y
+CONFIG_I2C=y
+CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_COMPAT=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_HELPER_AUTO=y
+CONFIG_I2C_BCM2708=m
+CONFIG_SPI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_BCM2708=m
+
diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig
index df947e5..6219df3 100644
--- a/arch/arm/configs/bcmrpi_defconfig
+++ b/arch/arm/configs/bcmrpi_defconfig
@@ -214,6 +214,11 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
# CONFIG_HW_RANDOM is not set
CONFIG_RAW_DRIVER=y
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_BCM2708=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2708=m
CONFIG_GPIO_SYSFS=y
# CONFIG_HWMON is not set
CONFIG_WATCHDOG=y
diff --git a/arch/arm/mach-bcm2708/Kconfig b/arch/arm/mach-bcm2708/Kconfig
index 63bb76c..a35ff89 100644
--- a/arch/arm/mach-bcm2708/Kconfig
+++ b/arch/arm/mach-bcm2708/Kconfig
@@ -31,4 +31,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
diff --git a/arch/arm/mach-bcm2708/bcm2708.c b/arch/arm/mach-bcm2708/bcm2708.c
index 67f3608..03b8ec5 100644
--- a/arch/arm/mach-bcm2708/bcm2708.c
+++ b/arch/arm/mach-bcm2708/bcm2708.c
@@ -31,6 +31,7 @@
#include <linux/cnt32_to_63.h>
#include <linux/io.h>
#include <linux/module.h>
+#include <linux/spi/spi.h>
#include <linux/version.h>
#include <linux/clkdev.h>
@@ -198,7 +199,6 @@ static struct clk osc_clk = {
/* warning - the USB needs a clock > 34MHz */
-#ifdef CONFIG_MMC_BCM2708
static struct clk sdhost_clk = {
#ifdef CONFIG_ARCH_BCM2708_CHIPIT
.rate = 4000000, /* 4MHz */
@@ -206,7 +206,6 @@ static struct clk sdhost_clk = {
.rate = 250000000, /* 250MHz */
#endif
};
-#endif
static struct clk_lookup lookups[] = {
{ /* UART0 */
@@ -216,6 +215,15 @@ static struct clk_lookup lookups[] = {
{ /* USB */
.dev_id = "bcm2708_usb",
.clk = &osc_clk,
+ }, { /* SPI */
+ .dev_id = "bcm2708_spi.0",
+ .clk = &sdhost_clk,
+ }, { /* BSC0 */
+ .dev_id = "bcm2708_i2c.0",
+ .clk = &sdhost_clk,
+ }, { /* BSC1 */
+ .dev_id = "bcm2708_i2c.1",
+ .clk = &sdhost_clk,
}
};
@@ -434,6 +442,89 @@ static struct platform_device bcm2708_alsa_devices[] = {
},
};
+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 resource bcm2708_bsc0_resources[] = {
+ {
+ .start = BSC0_BASE,
+ .end = BSC0_BASE + SZ_256 - 1,
+ .flags = IORESOURCE_MEM,
+ }, {
+ .start = INTERRUPT_I2C,
+ .end = INTERRUPT_I2C,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+static struct platform_device bcm2708_bsc0_device = {
+ .name = "bcm2708_i2c",
+ .id = 0,
+ .num_resources = ARRAY_SIZE(bcm2708_bsc0_resources),
+ .resource = bcm2708_bsc0_resources,
+};
+
+
+static struct resource bcm2708_bsc1_resources[] = {
+ {
+ .start = BSC1_BASE,
+ .end = BSC1_BASE + SZ_256 - 1,
+ .flags = IORESOURCE_MEM,
+ }, {
+ .start = INTERRUPT_I2C,
+ .end = INTERRUPT_I2C,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+static struct platform_device bcm2708_bsc1_device = {
+ .name = "bcm2708_i2c",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(bcm2708_bsc1_resources),
+ .resource = bcm2708_bsc1_resources,
+};
+
int __init bcm_register_device(struct platform_device *pdev)
{
int ret;
@@ -542,12 +633,21 @@ void __init bcm2708_init(void)
for (i = 0; i < ARRAY_SIZE(bcm2708_alsa_devices); i++)
bcm_register_device(&bcm2708_alsa_devices[i]);
+ bcm_register_device(&bcm2708_spi_device);
+ bcm_register_device(&bcm2708_bsc0_device);
+ bcm_register_device(&bcm2708_bsc1_device);
+
for (i = 0; i < ARRAY_SIZE(amba_devs); i++) {
struct amba_device *d = amba_devs[i];
amba_device_register(d, &iomem_resource);
}
system_rev = boardrev;
system_serial_low = serial;
+
+#ifdef CONFIG_BCM2708_SPIDEV
+ spi_register_board_info(bcm2708_spi_devices,
+ ARRAY_SIZE(bcm2708_spi_devices));
+#endif
}
static void timer_set_mode(enum clock_event_mode mode,
diff --git a/arch/arm/mach-bcm2708/include/mach/platform.h b/arch/arm/mach-bcm2708/include/mach/platform.h
index 110ce07..4d3c15d 100644
--- a/arch/arm/mach-bcm2708/include/mach/platform.h
+++ b/arch/arm/mach-bcm2708/include/mach/platform.h
@@ -63,9 +63,12 @@
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO */
#define UART0_BASE (BCM2708_PERI_BASE + 0x201000) /* Uart 0 */
#define MMCI0_BASE (BCM2708_PERI_BASE + 0x202000) /* MMC interface */
+#define SPI0_BASE (BCM2708_PERI_BASE + 0x204000) /* SPI0 */
+#define BSC0_BASE (BCM2708_PERI_BASE + 0x205000) /* BSC0 I2C/TWI */
#define UART1_BASE (BCM2708_PERI_BASE + 0x215000) /* Uart 1 */
#define EMMC_BASE (BCM2708_PERI_BASE + 0x300000) /* eMMC interface */
#define SMI_BASE (BCM2708_PERI_BASE + 0x600000) /* SMI */
+#define BSC1_BASE (BCM2708_PERI_BASE + 0x804000) /* BSC1 I2C/TWI */
#define USB_BASE (BCM2708_PERI_BASE + 0x980000) /* DTC_OTG USB controller */
#define MCORE_BASE (BCM2708_PERI_BASE + 0x0000) /* Fake frame buffer device (actually the multicore sync block*/
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d4fe13e..290aee4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -347,6 +347,14 @@ config I2C_BCM2835
This support is also available as a module. If so, the module
will be called i2c-bcm2835.
+config I2C_BCM2708
+ tristate "BCM2708 BSC"
+ depends on MACH_BCM2708
+ help
+ Enabling this option will add BSC (Broadcom Serial Controller)
+ support for the BCM2708. BSC is a Broadcom proprietary bus compatible
+ with I2C/TWI/SMBus.
+
config I2C_BLACKFIN_TWI
tristate "Blackfin TWI I2C support"
depends on BLACKFIN
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..ef26c38 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o
obj-$(CONFIG_I2C_AT91) += i2c-at91.o
obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
+obj-$(CONFIG_I2C_BCM2708) += i2c-bcm2708.o
obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o
obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
diff --git a/drivers/i2c/busses/i2c-bcm2708.c b/drivers/i2c/busses/i2c-bcm2708.c
new file mode 100644
index 0000000..7cae615
--- /dev/null
+++ b/drivers/i2c/busses/i2c-bcm2708.c
@@ -0,0 +1,396 @@
+/*
+ * Driver for Broadcom BCM2708 BSC Controllers
+ *
+ * Copyright (C) 2012 Chris Boot & Frank Buss
+ *
+ * This driver is inspired by:
+ * i2c-ocores.c, by Peter Korsgaard <jacmet@sunsite.dk>
+ *
+ * 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/slab.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+/* BSC register offsets */
+#define BSC_C 0x00
+#define BSC_S 0x04
+#define BSC_DLEN 0x08
+#define BSC_A 0x0c
+#define BSC_FIFO 0x10
+#define BSC_DIV 0x14
+#define BSC_DEL 0x18
+#define BSC_CLKT 0x1c
+
+/* Bitfields in BSC_C */
+#define BSC_C_I2CEN 0x00008000
+#define BSC_C_INTR 0x00000400
+#define BSC_C_INTT 0x00000200
+#define BSC_C_INTD 0x00000100
+#define BSC_C_ST 0x00000080
+#define BSC_C_CLEAR_1 0x00000020
+#define BSC_C_CLEAR_2 0x00000010
+#define BSC_C_READ 0x00000001
+
+/* Bitfields in BSC_S */
+#define BSC_S_CLKT 0x00000200
+#define BSC_S_ERR 0x00000100
+#define BSC_S_RXF 0x00000080
+#define BSC_S_TXE 0x00000040
+#define BSC_S_RXD 0x00000020
+#define BSC_S_TXD 0x00000010
+#define BSC_S_RXR 0x00000008
+#define BSC_S_TXW 0x00000004
+#define BSC_S_DONE 0x00000002
+#define BSC_S_TA 0x00000001
+
+#define I2C_CLOCK_HZ 100000 /* FIXME: get from DT */
+#define I2C_TIMEOUT_MS 150
+
+#define DRV_NAME "bcm2708_i2c"
+
+struct bcm2708_i2c {
+ struct i2c_adapter adapter;
+
+ spinlock_t lock;
+ void __iomem *base;
+ int irq;
+ struct clk *clk;
+
+ struct completion done;
+
+ struct i2c_msg *msg;
+ int pos;
+ int nmsgs;
+ bool error;
+};
+
+/*
+ * This function sets the ALT mode on the I2C pins so that we can use them with
+ * the BSC hardware.
+ *
+ * FIXME: This is a hack. Use pinmux / pinctrl.
+ */
+static void bcm2708_i2c_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(0x20200000, SZ_16K);
+
+ /* BSC0 is on GPIO 0 & 1, BSC1 is on GPIO 2 & 3 */
+ for (pin = 0; pin <= 3; 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_i2c *bi, unsigned reg)
+{
+ return readl(bi->base + reg);
+}
+
+static inline void bcm2708_wr(struct bcm2708_i2c *bi, unsigned reg, u32 val)
+{
+ writel(val, bi->base + reg);
+}
+
+static inline void bcm2708_bsc_reset(struct bcm2708_i2c *bi)
+{
+ bcm2708_wr(bi, BSC_C, 0);
+ bcm2708_wr(bi, BSC_S, BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE);
+}
+
+static inline void bcm2708_bsc_fifo_drain(struct bcm2708_i2c *bi)
+{
+ while ((bcm2708_rd(bi, BSC_S) & BSC_S_RXD) && (bi->pos < bi->msg->len))
+ bi->msg->buf[bi->pos++] = bcm2708_rd(bi, BSC_FIFO);
+}
+
+static inline void bcm2708_bsc_fifo_fill(struct bcm2708_i2c *bi)
+{
+ while ((bcm2708_rd(bi, BSC_S) & BSC_S_TXD) && (bi->pos < bi->msg->len))
+ bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
+}
+
+static inline void bcm2708_bsc_setup(struct bcm2708_i2c *bi)
+{
+ unsigned long bus_hz;
+ u32 cdiv;
+ u32 c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_ST | BSC_C_CLEAR_1;
+
+ bus_hz = clk_get_rate(bi->clk);
+ cdiv = bus_hz / I2C_CLOCK_HZ;
+
+ if (bi->msg->flags & I2C_M_RD)
+ c |= BSC_C_INTR | BSC_C_READ;
+ else
+ c |= BSC_C_INTT;
+
+ bcm2708_wr(bi, BSC_DIV, cdiv);
+ bcm2708_wr(bi, BSC_A, bi->msg->addr);
+ bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
+ bcm2708_wr(bi, BSC_C, c);
+}
+
+static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
+{
+ struct bcm2708_i2c *bi = dev_id;
+ bool handled = true;
+ u32 s;
+
+ spin_lock(&bi->lock);
+
+ s = bcm2708_rd(bi, BSC_S);
+
+ if (s & (BSC_S_CLKT | BSC_S_ERR)) {
+ bcm2708_bsc_reset(bi);
+ bi->error = true;
+
+ /* wake up our bh */
+ complete(&bi->done);
+ } else if (s & BSC_S_DONE) {
+ bi->nmsgs--;
+
+ if (bi->msg->flags & I2C_M_RD)
+ bcm2708_bsc_fifo_drain(bi);
+
+ bcm2708_bsc_reset(bi);
+
+ if (bi->nmsgs) {
+ /* advance to next message */
+ bi->msg++;
+ bi->pos = 0;
+ bcm2708_bsc_setup(bi);
+ } else {
+ /* wake up our bh */
+ complete(&bi->done);
+ }
+ } else if (s & BSC_S_TXW) {
+ bcm2708_bsc_fifo_fill(bi);
+ } else if (s & BSC_S_RXR) {
+ bcm2708_bsc_fifo_drain(bi);
+ } else {
+ handled = false;
+ }
+
+ spin_unlock(&bi->lock);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int bcm2708_i2c_master_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct bcm2708_i2c *bi = adap->algo_data;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&bi->lock, flags);
+
+ INIT_COMPLETION(bi->done);
+ bi->msg = msgs;
+ bi->pos = 0;
+ bi->nmsgs = num;
+ bi->error = false;
+
+ spin_unlock_irqrestore(&bi->lock, flags);
+
+ bcm2708_bsc_setup(bi);
+
+ ret = wait_for_completion_timeout(&bi->done,
+ msecs_to_jiffies(I2C_TIMEOUT_MS));
+ if (ret == 0) {
+ dev_err(&adap->dev, "transfer timed out\n");
+ spin_lock_irqsave(&bi->lock, flags);
+ bcm2708_bsc_reset(bi);
+ spin_unlock_irqrestore(&bi->lock, flags);
+ return -ETIMEDOUT;
+ }
+
+ return bi->error ? -EIO : num;
+}
+
+static u32 bcm2708_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | /*I2C_FUNC_10BIT_ADDR |*/ I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm bcm2708_i2c_algorithm = {
+ .master_xfer = bcm2708_i2c_master_xfer,
+ .functionality = bcm2708_i2c_functionality,
+};
+
+static int bcm2708_i2c_probe(struct platform_device *pdev)
+{
+ struct resource *regs;
+ int irq, err = -ENOMEM;
+ struct clk *clk;
+ struct bcm2708_i2c *bi;
+ struct i2c_adapter *adap;
+
+ 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_i2c_init_pinmode();
+
+ bi = kzalloc(sizeof(*bi), GFP_KERNEL);
+ if (!bi)
+ goto out_clk_put;
+
+ platform_set_drvdata(pdev, bi);
+
+ adap = &bi->adapter;
+ adap->class = I2C_CLASS_HWMON | I2C_CLASS_DDC;
+ adap->algo = &bcm2708_i2c_algorithm;
+ adap->algo_data = bi;
+ adap->dev.parent = &pdev->dev;
+ adap->nr = pdev->id;
+ strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
+
+ switch (pdev->id) {
+ case 0:
+ adap->class = I2C_CLASS_HWMON;
+ break;
+ case 1:
+ adap->class = I2C_CLASS_DDC;
+ break;
+ default:
+ dev_err(&pdev->dev, "can only bind to BSC 0 or 1\n");
+ err = -ENXIO;
+ goto out_free_bi;
+ }
+
+ spin_lock_init(&bi->lock);
+ init_completion(&bi->done);
+
+ bi->base = ioremap(regs->start, resource_size(regs));
+ if (!bi->base) {
+ dev_err(&pdev->dev, "could not remap memory\n");
+ goto out_free_bi;
+ }
+
+ bi->irq = irq;
+ bi->clk = clk;
+
+ err = request_irq(irq, bcm2708_i2c_interrupt, IRQF_SHARED,
+ dev_name(&pdev->dev), bi);
+ if (err) {
+ dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
+ goto out_iounmap;
+ }
+
+ bcm2708_bsc_reset(bi);
+
+ err = i2c_add_numbered_adapter(adap);
+ if (err < 0) {
+ dev_err(&pdev->dev, "could not add I2C adapter: %d\n", err);
+ goto out_free_irq;
+ }
+
+ dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d)\n",
+ pdev->id, (unsigned long)regs->start, irq);
+
+ return 0;
+
+out_free_irq:
+ free_irq(bi->irq, bi);
+out_iounmap:
+ iounmap(bi->base);
+out_free_bi:
+ kfree(bi);
+out_clk_put:
+ clk_put(clk);
+ return err;
+}
+
+static int bcm2708_i2c_remove(struct platform_device *pdev)
+{
+ struct bcm2708_i2c *bi = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ i2c_del_adapter(&bi->adapter);
+ free_irq(bi->irq, bi);
+ iounmap(bi->base);
+ clk_disable(bi->clk);
+ clk_put(bi->clk);
+ kfree(bi);
+
+ return 0;
+}
+
+static struct platform_driver bcm2708_i2c_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = bcm2708_i2c_probe,
+ .remove = bcm2708_i2c_remove,
+};
+
+// module_platform_driver(bcm2708_i2c_driver);
+
+
+static int __init bcm2708_i2c_init(void)
+{
+ return platform_driver_register(&bcm2708_i2c_driver);
+}
+
+static void __exit bcm2708_i2c_exit(void)
+{
+ platform_driver_unregister(&bcm2708_i2c_driver);
+}
+
+module_init(bcm2708_i2c_init);
+module_exit(bcm2708_i2c_exit);
+
+
+
+MODULE_DESCRIPTION("BSC controller driver for Broadcom BCM2708");
+MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 92a9345..f0a2a9f 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -86,6 +86,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
+ 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
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 33f9c09..17b4737 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SPI_AU1550) += spi-au1550.o
obj-$(CONFIG_SPI_BCM2835) += spi-bcm2835.o
obj-$(CONFIG_SPI_BCM63XX) += spi-bcm63xx.o
obj-$(CONFIG_SPI_BFIN5XX) += spi-bfin5xx.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
diff --git a/drivers/spi/spi-bcm2708.c b/drivers/spi/spi-bcm2708.c
new file mode 100644
index 0000000..9f1580e
--- /dev/null
+++ b/drivers/spi/spi-bcm2708.c
@@ -0,0 +1,594 @@
+/*
+ * 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(0x20200000, 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;
+
+ if (len > bs->len)
+ len = bs->len;
+
+ 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;
+ default:
+ dev_dbg(dev, "setup: invalid bits_per_word %u (must be 8)\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;
+ }
+
+ 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,
+ spi->max_speed_hz, spi->chip_select, spi->mode,
+ spi->bits_per_word);
+ if (ret)
+ return ret;
+
+ stp = &state;
+ } else {
+ stp = spi->controller_state;
+ }
+
+ INIT_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;
+ }
+
+ 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;
+ 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_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);
+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_sync(&bs->work);
+
+ clk_disable(bs->clk);
+ clk_put(bs->clk);
+ free_irq(bs->irq, master);
+ iounmap(bs->base);
+
+ spi_unregister_master(master);
+
+ return 0;
+}
+
+static struct platform_driver bcm2708_spi_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .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);
--
1.9.1