From eb8655fe20ef40189e9cda1441d488c5a23c89f2 Mon Sep 17 00:00:00 2001 From: popcornmix 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 #include #include +#include #include #include @@ -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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); -- 1.9.1