diff options
-rw-r--r-- | arch/arm/mach-bcm2708/Kconfig | 7 | ||||
-rw-r--r-- | arch/arm/mach-bcm2708/bcm2708.c | 104 | ||||
-rw-r--r-- | drivers/i2c/busses/Kconfig | 19 | ||||
-rw-r--r-- | drivers/i2c/busses/Makefile | 2 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-bcm2708.c | 449 | ||||
-rw-r--r-- | drivers/spi/Kconfig | 8 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-bcm2708.c | 626 |
8 files changed, 1214 insertions, 2 deletions
diff --git a/arch/arm/mach-bcm2708/Kconfig b/arch/arm/mach-bcm2708/Kconfig index 9355841e58e2..e151ed4efbbb 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 bae8ba5b704f..752dd46cefe6 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> @@ -205,7 +206,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 */ @@ -213,7 +213,6 @@ static struct clk sdhost_clk = { .rate = 250000000, /* 250MHz */ #endif }; -#endif static struct clk_lookup lookups[] = { { /* UART0 */ @@ -223,6 +222,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, } }; @@ -499,6 +507,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, +}; + static struct platform_device bcm2835_hwmon_device = { .name = "bcm2835_hwmon", }; @@ -619,6 +710,10 @@ 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); + bcm_register_device(&bcm2835_hwmon_device); bcm_register_device(&bcm2835_thermal_device); @@ -628,6 +723,11 @@ void __init bcm2708_init(void) } 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/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 22da9c2ffa22..00c7baa24658 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -8,6 +8,25 @@ menu "I2C Hardware Bus support" comment "PC SMBus host controller drivers" depends on PCI +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_BCM2708_BAUDRATE + prompt "BCM2708 I2C baudrate" + depends on I2C_BCM2708 + int + default 100000 + help + Set the I2C baudrate. This will alter the default value. A + different baudrate can be set by using a module parameter as well. If + no parameter is provided when loading, this is the value that will be + used. + config I2C_ALI1535 tristate "ALI 1535" depends on PCI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 3638feb6677e..4905faeedfbc 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -2,6 +2,8 @@ # Makefile for the i2c bus drivers. # +obj-$(CONFIG_I2C_BCM2708) += i2c-bcm2708.o + # ACPI drivers obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o diff --git a/drivers/i2c/busses/i2c-bcm2708.c b/drivers/i2c/busses/i2c-bcm2708.c new file mode 100644 index 000000000000..7d385a324038 --- /dev/null +++ b/drivers/i2c/busses/i2c-bcm2708.c @@ -0,0 +1,449 @@ +/* + * 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_TIMEOUT_MS 150 + +#define DRV_NAME "bcm2708_i2c" + +static unsigned int baudrate = CONFIG_I2C_BCM2708_BAUDRATE; +module_param(baudrate, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); +MODULE_PARM_DESC(baudrate, "The I2C baudrate"); + +static bool combined = false; +module_param(combined, bool, 0644); +MODULE_PARM_DESC(combined, "Use combined transactions"); + +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(int id) +{ +#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); + + BUG_ON(id != 0 && id != 1); + /* BSC0 is on GPIO 0 & 1, BSC1 is on GPIO 2 & 3 */ + for (pin = id*2+0; pin <= id*2+1; pin++) { +printk("bcm2708_i2c_init_pinmode(%d,%d)\n", id, 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, s; + 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 / baudrate; + if (cdiv > 0xffff) + cdiv = 0xffff; + + 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); + if (combined) + { + /* Do the next two messages meet combined transaction criteria? + - Current message is a write, next message is a read + - Both messages to same slave address + - Write message can fit inside FIFO (16 bytes or less) */ + if ( (bi->nmsgs > 1) && + !(bi->msg[0].flags & I2C_M_RD) && (bi->msg[1].flags & I2C_M_RD) && + (bi->msg[0].addr == bi->msg[1].addr) && (bi->msg[0].len <= 16)) { + /* Fill FIFO with entire write message (16 byte FIFO) */ + while (bi->pos < bi->msg->len) + bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]); + /* Start write transfer (no interrupts, don't clear FIFO) */ + bcm2708_wr(bi, BSC_C, BSC_C_I2CEN | BSC_C_ST); + /* poll for transfer start bit (should only take 1-20 polls) */ + do { + s = bcm2708_rd(bi, BSC_S); + } while (!(s & (BSC_S_TA | BSC_S_ERR | BSC_S_CLKT | BSC_S_DONE))); + /* Send next read message before the write transfer finishes. */ + bi->nmsgs--; + bi->msg++; + bi->pos = 0; + bcm2708_wr(bi, BSC_DLEN, bi->msg->len); + c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_INTR | BSC_C_ST | BSC_C_READ; + } + } + 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); + + /* we may see camera interrupts on the "other" I2C channel + Just return if we've not sent anything */ + if (!bi->nmsgs || !bi->msg ) + goto early_exit; + + 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; + } + +early_exit: + 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); + + reinit_completion(&bi->done); + bi->msg = msgs; + bi->pos = 0; + bi->nmsgs = num; + bi->error = false; + + bcm2708_bsc_setup(bi); + + /* unlockig _after_ the setup to avoid races with the interrupt routine */ + spin_unlock_irqrestore(&bi->lock, flags); + + 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; + unsigned long bus_hz; + u32 cdiv; + + 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(pdev->id); + + 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; + } + + bus_hz = clk_get_rate(bi->clk); + cdiv = bus_hz / baudrate; + if (cdiv > 0xffff) { + cdiv = 0xffff; + baudrate = bus_hz / cdiv; + } + + dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d) (baudrate %d)\n", + pdev->id, (unsigned long)regs->start, irq, baudrate); + + 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 ab8dfbef6f1b..9628e75de77f 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 && !BF60x diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index d8cbf654976b..0dcd03d062bb 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_SPI_BCM63XX) += spi-bcm63xx.o 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 diff --git a/drivers/spi/spi-bcm2708.c b/drivers/spi/spi-bcm2708.c new file mode 100644 index 000000000000..b04a57db0466 --- /dev/null +++ b/drivers/spi/spi-bcm2708.c @@ -0,0 +1,626 @@ +/* + * 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; + 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(&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); |