summaryrefslogtreecommitdiff
path: root/arch/arm/mach-bcm2708
diff options
context:
space:
mode:
authorpopcornmix <popcornmix@gmail.com>2014-10-08 18:50:05 +0100
committerpopcornmix <popcornmix@gmail.com>2015-05-18 14:10:16 +0100
commit520bf118a7d466048d2a927d945e3d4b3ea1afc6 (patch)
treefd1eaff2f3cc9bc932eff30b8f5b7c015521080d /arch/arm/mach-bcm2708
parent08d998bd955c232b20083ebb696291d3bfb5e961 (diff)
downloadlinux-520bf118a7d466048d2a927d945e3d4b3ea1afc6.tar.gz
Add bcm2708_gpio driver
Signed-off-by: popcornmix <popcornmix@gmail.com> bcm2708: Add extension to configure internal pulls The bcm2708 gpio controller supports internal pulls to be used as pull-up, pull-down or being entirely disabled. As it can be useful for a driver to change the pull configuration from it's default pull-down state, add an extension which allows configuring the pull per gpio. Signed-off-by: Julian Scheel <julian@jusst.de> bcm2708-gpio: Revert the use of pinctrl_request_gpio In non-DT systems, pinctrl_request_gpio always fails causing "requests probe deferral" messages. In DT systems, it isn't useful because the reference counting is independent of the normal pinctrl pin reservations. gpio: Only clear the currently occurring interrupt. Avoids losing interrupts See: linux #760 bcm2708_gpio: Avoid calling irq_unmask for all interrupts When setting up the interrupts, specify that the handle_simple_irq handler should be used. This leaves interrupt acknowledgement to the caller, and prevents irq_unmask from being called for all interrupts. Issue: linux #760
Diffstat (limited to 'arch/arm/mach-bcm2708')
-rw-r--r--arch/arm/mach-bcm2708/Kconfig8
-rw-r--r--arch/arm/mach-bcm2708/Makefile1
-rw-r--r--arch/arm/mach-bcm2708/bcm2708.c28
-rw-r--r--arch/arm/mach-bcm2708/bcm2708_gpio.c426
-rw-r--r--arch/arm/mach-bcm2708/include/mach/gpio.h17
5 files changed, 480 insertions, 0 deletions
diff --git a/arch/arm/mach-bcm2708/Kconfig b/arch/arm/mach-bcm2708/Kconfig
index 1f1147895546..9355841e58e2 100644
--- a/arch/arm/mach-bcm2708/Kconfig
+++ b/arch/arm/mach-bcm2708/Kconfig
@@ -9,6 +9,14 @@ config MACH_BCM2708
help
Include support for the Broadcom(R) BCM2708 platform.
+config BCM2708_GPIO
+ bool "BCM2708 gpio support"
+ depends on MACH_BCM2708
+ select ARCH_REQUIRE_GPIOLIB
+ default y
+ help
+ Include support for the Broadcom(R) BCM2708 gpio.
+
config BCM2708_VCMEM
bool "Videocore Memory"
depends on MACH_BCM2708
diff --git a/arch/arm/mach-bcm2708/Makefile b/arch/arm/mach-bcm2708/Makefile
index c76f39bc31db..a722f3fb7257 100644
--- a/arch/arm/mach-bcm2708/Makefile
+++ b/arch/arm/mach-bcm2708/Makefile
@@ -3,4 +3,5 @@
#
obj-$(CONFIG_MACH_BCM2708) += clock.o bcm2708.o armctrl.o vcio.o power.o dma.o
+obj-$(CONFIG_BCM2708_GPIO) += bcm2708_gpio.o
obj-$(CONFIG_BCM2708_VCMEM) += vc_mem.o
diff --git a/arch/arm/mach-bcm2708/bcm2708.c b/arch/arm/mach-bcm2708/bcm2708.c
index 9b4e709ceecc..750364980af6 100644
--- a/arch/arm/mach-bcm2708/bcm2708.c
+++ b/arch/arm/mach-bcm2708/bcm2708.c
@@ -331,6 +331,31 @@ static struct platform_device bcm2708_vcio_device = {
},
};
+#ifdef CONFIG_BCM2708_GPIO
+#define BCM_GPIO_DRIVER_NAME "bcm2708_gpio"
+
+static struct resource bcm2708_gpio_resources[] = {
+ [0] = { /* general purpose I/O */
+ .start = GPIO_BASE,
+ .end = GPIO_BASE + SZ_4K - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static u64 gpio_dmamask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON);
+
+static struct platform_device bcm2708_gpio_device = {
+ .name = BCM_GPIO_DRIVER_NAME,
+ .id = -1, /* only one VideoCore I/O area */
+ .resource = bcm2708_gpio_resources,
+ .num_resources = ARRAY_SIZE(bcm2708_gpio_resources),
+ .dev = {
+ .dma_mask = &gpio_dmamask,
+ .coherent_dma_mask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON),
+ },
+};
+#endif
+
static struct resource bcm2708_systemtimer_resources[] = {
[0] = { /* system timer access */
.start = ST_BASE,
@@ -473,6 +498,9 @@ void __init bcm2708_init(void)
bcm_register_device(&bcm2708_dmaman_device);
bcm_register_device(&bcm2708_vcio_device);
+#ifdef CONFIG_BCM2708_GPIO
+ bcm_register_device(&bcm2708_gpio_device);
+#endif
bcm_register_device(&bcm2708_systemtimer_device);
bcm_register_device(&bcm2708_fb_device);
bcm_register_device(&bcm2708_usb_device);
diff --git a/arch/arm/mach-bcm2708/bcm2708_gpio.c b/arch/arm/mach-bcm2708/bcm2708_gpio.c
new file mode 100644
index 000000000000..c1e925422bdb
--- /dev/null
+++ b/arch/arm/mach-bcm2708/bcm2708_gpio.c
@@ -0,0 +1,426 @@
+/*
+ * linux/arch/arm/mach-bcm2708/bcm2708_gpio.c
+ *
+ * Copyright (C) 2010 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <mach/gpio.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <mach/platform.h>
+#include <linux/pinctrl/consumer.h>
+
+#include <linux/platform_data/bcm2708.h>
+
+#define BCM_GPIO_DRIVER_NAME "bcm2708_gpio"
+#define DRIVER_NAME BCM_GPIO_DRIVER_NAME
+#define BCM_GPIO_USE_IRQ 1
+
+#define GPIOFSEL(x) (0x00+(x)*4)
+#define GPIOSET(x) (0x1c+(x)*4)
+#define GPIOCLR(x) (0x28+(x)*4)
+#define GPIOLEV(x) (0x34+(x)*4)
+#define GPIOEDS(x) (0x40+(x)*4)
+#define GPIOREN(x) (0x4c+(x)*4)
+#define GPIOFEN(x) (0x58+(x)*4)
+#define GPIOHEN(x) (0x64+(x)*4)
+#define GPIOLEN(x) (0x70+(x)*4)
+#define GPIOAREN(x) (0x7c+(x)*4)
+#define GPIOAFEN(x) (0x88+(x)*4)
+#define GPIOUD(x) (0x94+(x)*4)
+#define GPIOUDCLK(x) (0x98+(x)*4)
+
+#define GPIO_BANKS 2
+
+enum { GPIO_FSEL_INPUT, GPIO_FSEL_OUTPUT,
+ GPIO_FSEL_ALT5, GPIO_FSEL_ALT_4,
+ GPIO_FSEL_ALT0, GPIO_FSEL_ALT1,
+ GPIO_FSEL_ALT2, GPIO_FSEL_ALT3,
+};
+
+ /* Each of the two spinlocks protects a different set of hardware
+ * regiters and data structurs. This decouples the code of the IRQ from
+ * the GPIO code. This also makes the case of a GPIO routine call from
+ * the IRQ code simpler.
+ */
+static DEFINE_SPINLOCK(lock); /* GPIO registers */
+
+struct bcm2708_gpio {
+ struct list_head list;
+ void __iomem *base;
+ struct gpio_chip gc;
+ unsigned long rising[(BCM2708_NR_GPIOS + 31) / 32];
+ unsigned long falling[(BCM2708_NR_GPIOS + 31) / 32];
+ unsigned long high[(BCM2708_NR_GPIOS + 31) / 32];
+ unsigned long low[(BCM2708_NR_GPIOS + 31) / 32];
+};
+
+static int bcm2708_set_function(struct gpio_chip *gc, unsigned offset,
+ int function)
+{
+ struct bcm2708_gpio *gpio = container_of(gc, struct bcm2708_gpio, gc);
+ unsigned long flags;
+ unsigned gpiodir;
+ unsigned gpio_bank = offset / 10;
+ unsigned gpio_field_offset = (offset - 10 * gpio_bank) * 3;
+
+//printk(KERN_ERR DRIVER_NAME ": bcm2708_gpio_set_function %p (%d,%d)\n", gc, offset, function);
+ if (offset >= BCM2708_NR_GPIOS)
+ return -EINVAL;
+
+ spin_lock_irqsave(&lock, flags);
+
+ gpiodir = readl(gpio->base + GPIOFSEL(gpio_bank));
+ gpiodir &= ~(7 << gpio_field_offset);
+ gpiodir |= function << gpio_field_offset;
+ writel(gpiodir, gpio->base + GPIOFSEL(gpio_bank));
+ spin_unlock_irqrestore(&lock, flags);
+ gpiodir = readl(gpio->base + GPIOFSEL(gpio_bank));
+
+ return 0;
+}
+
+static int bcm2708_gpio_dir_in(struct gpio_chip *gc, unsigned offset)
+{
+ return bcm2708_set_function(gc, offset, GPIO_FSEL_INPUT);
+}
+
+static void bcm2708_gpio_set(struct gpio_chip *gc, unsigned offset, int value);
+static int bcm2708_gpio_dir_out(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ int ret;
+ ret = bcm2708_set_function(gc, offset, GPIO_FSEL_OUTPUT);
+ if (ret >= 0)
+ bcm2708_gpio_set(gc, offset, value);
+ return ret;
+}
+
+static int bcm2708_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct bcm2708_gpio *gpio = container_of(gc, struct bcm2708_gpio, gc);
+ unsigned gpio_bank = offset / 32;
+ unsigned gpio_field_offset = (offset - 32 * gpio_bank);
+ unsigned lev;
+
+ if (offset >= BCM2708_NR_GPIOS)
+ return 0;
+ lev = readl(gpio->base + GPIOLEV(gpio_bank));
+//printk(KERN_ERR DRIVER_NAME ": bcm2708_gpio_get %p (%d)=%d\n", gc, offset, 0x1 & (lev>>gpio_field_offset));
+ return 0x1 & (lev >> gpio_field_offset);
+}
+
+static void bcm2708_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct bcm2708_gpio *gpio = container_of(gc, struct bcm2708_gpio, gc);
+ unsigned gpio_bank = offset / 32;
+ unsigned gpio_field_offset = (offset - 32 * gpio_bank);
+//printk(KERN_ERR DRIVER_NAME ": bcm2708_gpio_set %p (%d=%d)\n", gc, offset, value);
+ if (offset >= BCM2708_NR_GPIOS)
+ return;
+ if (value)
+ writel(1 << gpio_field_offset, gpio->base + GPIOSET(gpio_bank));
+ else
+ writel(1 << gpio_field_offset, gpio->base + GPIOCLR(gpio_bank));
+}
+
+/**********************
+ * extension to configure pullups
+ */
+int bcm2708_gpio_setpull(struct gpio_chip *gc, unsigned offset,
+ bcm2708_gpio_pull_t value)
+{
+ struct bcm2708_gpio *gpio = container_of(gc, struct bcm2708_gpio, gc);
+ unsigned gpio_bank = offset / 32;
+ unsigned gpio_field_offset = (offset - 32 * gpio_bank);
+
+ if (offset >= BCM2708_NR_GPIOS)
+ return -EINVAL;
+
+ switch (value) {
+ case BCM2708_PULL_UP:
+ writel(2, gpio->base + GPIOUD(0));
+ break;
+ case BCM2708_PULL_DOWN:
+ writel(1, gpio->base + GPIOUD(0));
+ break;
+ case BCM2708_PULL_OFF:
+ writel(0, gpio->base + GPIOUD(0));
+ break;
+ }
+
+ udelay(5);
+ writel(1 << gpio_field_offset, gpio->base + GPIOUDCLK(gpio_bank));
+ udelay(5);
+ writel(0, gpio->base + GPIOUD(0));
+ writel(0 << gpio_field_offset, gpio->base + GPIOUDCLK(gpio_bank));
+
+ return 0;
+}
+EXPORT_SYMBOL(bcm2708_gpio_setpull);
+
+/*************************************************************************************************************************
+ * bcm2708 GPIO IRQ
+ */
+
+#if BCM_GPIO_USE_IRQ
+
+static int bcm2708_gpio_to_irq(struct gpio_chip *chip, unsigned gpio)
+{
+ return gpio_to_irq(gpio);
+}
+
+static int bcm2708_gpio_irq_set_type(struct irq_data *d, unsigned type)
+{
+ unsigned irq = d->irq;
+ struct bcm2708_gpio *gpio = irq_get_chip_data(irq);
+ unsigned gn = irq_to_gpio(irq);
+ unsigned gb = gn / 32;
+ unsigned go = gn % 32;
+
+ gpio->rising[gb] &= ~(1 << go);
+ gpio->falling[gb] &= ~(1 << go);
+ gpio->high[gb] &= ~(1 << go);
+ gpio->low[gb] &= ~(1 << go);
+
+ if (type & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
+ return -EINVAL;
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ gpio->rising[gb] |= (1 << go);
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ gpio->falling[gb] |= (1 << go);
+ if (type & IRQ_TYPE_LEVEL_HIGH)
+ gpio->high[gb] |= (1 << go);
+ if (type & IRQ_TYPE_LEVEL_LOW)
+ gpio->low[gb] |= (1 << go);
+ return 0;
+}
+
+static void bcm2708_gpio_irq_mask(struct irq_data *d)
+{
+ unsigned irq = d->irq;
+ struct bcm2708_gpio *gpio = irq_get_chip_data(irq);
+ unsigned gn = irq_to_gpio(irq);
+ unsigned gb = gn / 32;
+ unsigned long rising = readl(gpio->base + GPIOREN(gb));
+ unsigned long falling = readl(gpio->base + GPIOFEN(gb));
+ unsigned long high = readl(gpio->base + GPIOHEN(gb));
+ unsigned long low = readl(gpio->base + GPIOLEN(gb));
+
+ gn = gn % 32;
+
+ writel(rising & ~(1 << gn), gpio->base + GPIOREN(gb));
+ writel(falling & ~(1 << gn), gpio->base + GPIOFEN(gb));
+ writel(high & ~(1 << gn), gpio->base + GPIOHEN(gb));
+ writel(low & ~(1 << gn), gpio->base + GPIOLEN(gb));
+}
+
+static void bcm2708_gpio_irq_unmask(struct irq_data *d)
+{
+ unsigned irq = d->irq;
+ struct bcm2708_gpio *gpio = irq_get_chip_data(irq);
+ unsigned gn = irq_to_gpio(irq);
+ unsigned gb = gn / 32;
+ unsigned go = gn % 32;
+ unsigned long rising = readl(gpio->base + GPIOREN(gb));
+ unsigned long falling = readl(gpio->base + GPIOFEN(gb));
+ unsigned long high = readl(gpio->base + GPIOHEN(gb));
+ unsigned long low = readl(gpio->base + GPIOLEN(gb));
+
+ if (gpio->rising[gb] & (1 << go)) {
+ writel(rising | (1 << go), gpio->base + GPIOREN(gb));
+ } else {
+ writel(rising & ~(1 << go), gpio->base + GPIOREN(gb));
+ }
+
+ if (gpio->falling[gb] & (1 << go)) {
+ writel(falling | (1 << go), gpio->base + GPIOFEN(gb));
+ } else {
+ writel(falling & ~(1 << go), gpio->base + GPIOFEN(gb));
+ }
+
+ if (gpio->high[gb] & (1 << go)) {
+ writel(high | (1 << go), gpio->base + GPIOHEN(gb));
+ } else {
+ writel(high & ~(1 << go), gpio->base + GPIOHEN(gb));
+ }
+
+ if (gpio->low[gb] & (1 << go)) {
+ writel(low | (1 << go), gpio->base + GPIOLEN(gb));
+ } else {
+ writel(low & ~(1 << go), gpio->base + GPIOLEN(gb));
+ }
+}
+
+static struct irq_chip bcm2708_irqchip = {
+ .name = "GPIO",
+ .irq_enable = bcm2708_gpio_irq_unmask,
+ .irq_disable = bcm2708_gpio_irq_mask,
+ .irq_unmask = bcm2708_gpio_irq_unmask,
+ .irq_mask = bcm2708_gpio_irq_mask,
+ .irq_set_type = bcm2708_gpio_irq_set_type,
+};
+
+static irqreturn_t bcm2708_gpio_interrupt(int irq, void *dev_id)
+{
+ unsigned long edsr;
+ unsigned bank;
+ int i;
+ unsigned gpio;
+ unsigned level_bits;
+ struct bcm2708_gpio *gpio_data = dev_id;
+
+ for (bank = 0; bank < GPIO_BANKS; bank++) {
+ edsr = readl(__io_address(GPIO_BASE) + GPIOEDS(bank));
+ level_bits = gpio_data->high[bank] | gpio_data->low[bank];
+
+ for_each_set_bit(i, &edsr, 32) {
+ gpio = i + bank * 32;
+ /* ack edge triggered IRQs immediately */
+ if (!(level_bits & (1<<i)))
+ writel(1<<i,
+ __io_address(GPIO_BASE) + GPIOEDS(bank));
+ generic_handle_irq(gpio_to_irq(gpio));
+ /* ack level triggered IRQ after handling them */
+ if (level_bits & (1<<i))
+ writel(1<<i,
+ __io_address(GPIO_BASE) + GPIOEDS(bank));
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+static struct irqaction bcm2708_gpio_irq = {
+ .name = "BCM2708 GPIO catchall handler",
+ .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
+ .handler = bcm2708_gpio_interrupt,
+};
+
+static void bcm2708_gpio_irq_init(struct bcm2708_gpio *ucb)
+{
+ unsigned irq;
+
+ ucb->gc.to_irq = bcm2708_gpio_to_irq;
+
+ for (irq = GPIO_IRQ_START; irq < (GPIO_IRQ_START + GPIO_IRQS); irq++) {
+ irq_set_chip_data(irq, ucb);
+ irq_set_chip_and_handler(irq, &bcm2708_irqchip,
+ handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+ }
+
+ bcm2708_gpio_irq.dev_id = ucb;
+ setup_irq(IRQ_GPIO3, &bcm2708_gpio_irq);
+}
+
+#else
+
+static void bcm2708_gpio_irq_init(struct bcm2708_gpio *ucb)
+{
+}
+
+#endif /* #if BCM_GPIO_USE_IRQ ***************************************************************************************************************** */
+
+static int bcm2708_gpio_probe(struct platform_device *dev)
+{
+ struct bcm2708_gpio *ucb;
+ struct resource *res;
+ int bank;
+ int err = 0;
+
+ printk(KERN_INFO DRIVER_NAME ": bcm2708_gpio_probe %p\n", dev);
+
+ ucb = kzalloc(sizeof(*ucb), GFP_KERNEL);
+ if (NULL == ucb) {
+ printk(KERN_ERR DRIVER_NAME ": failed to allocate "
+ "mailbox memory\n");
+ err = -ENOMEM;
+ goto err;
+ }
+
+ res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+
+ platform_set_drvdata(dev, ucb);
+ ucb->base = __io_address(GPIO_BASE);
+
+ ucb->gc.label = "bcm2708_gpio";
+ ucb->gc.base = 0;
+ ucb->gc.ngpio = BCM2708_NR_GPIOS;
+ ucb->gc.owner = THIS_MODULE;
+
+ ucb->gc.direction_input = bcm2708_gpio_dir_in;
+ ucb->gc.direction_output = bcm2708_gpio_dir_out;
+ ucb->gc.get = bcm2708_gpio_get;
+ ucb->gc.set = bcm2708_gpio_set;
+ ucb->gc.can_sleep = 0;
+
+ for (bank = 0; bank < GPIO_BANKS; bank++) {
+ writel(0, ucb->base + GPIOREN(bank));
+ writel(0, ucb->base + GPIOFEN(bank));
+ writel(0, ucb->base + GPIOHEN(bank));
+ writel(0, ucb->base + GPIOLEN(bank));
+ writel(0, ucb->base + GPIOAREN(bank));
+ writel(0, ucb->base + GPIOAFEN(bank));
+ writel(~0, ucb->base + GPIOEDS(bank));
+ }
+
+ bcm2708_gpio_irq_init(ucb);
+
+ err = gpiochip_add(&ucb->gc);
+
+err:
+ return err;
+
+}
+
+static int bcm2708_gpio_remove(struct platform_device *dev)
+{
+ int err = 0;
+ struct bcm2708_gpio *ucb = platform_get_drvdata(dev);
+
+ printk(KERN_ERR DRIVER_NAME ": bcm2708_gpio_remove %p\n", dev);
+
+ gpiochip_remove(&ucb->gc);
+
+ platform_set_drvdata(dev, NULL);
+ kfree(ucb);
+
+ return err;
+}
+
+static struct platform_driver bcm2708_gpio_driver = {
+ .probe = bcm2708_gpio_probe,
+ .remove = bcm2708_gpio_remove,
+ .driver = {
+ .name = "bcm2708_gpio"},
+};
+
+static int __init bcm2708_gpio_init(void)
+{
+ return platform_driver_register(&bcm2708_gpio_driver);
+}
+
+static void __exit bcm2708_gpio_exit(void)
+{
+ platform_driver_unregister(&bcm2708_gpio_driver);
+}
+
+module_init(bcm2708_gpio_init);
+module_exit(bcm2708_gpio_exit);
+
+MODULE_DESCRIPTION("Broadcom BCM2708 GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-bcm2708/include/mach/gpio.h b/arch/arm/mach-bcm2708/include/mach/gpio.h
new file mode 100644
index 000000000000..7965a9759b6e
--- /dev/null
+++ b/arch/arm/mach-bcm2708/include/mach/gpio.h
@@ -0,0 +1,17 @@
+/*
+ * arch/arm/mach-bcm2708/include/mach/gpio.h
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef __ASM_ARCH_GPIO_H
+#define __ASM_ARCH_GPIO_H
+
+#define BCM2708_NR_GPIOS 54 // number of gpio lines
+
+#define gpio_to_irq(x) ((x) + GPIO_IRQ_START)
+#define irq_to_gpio(x) ((x) - GPIO_IRQ_START)
+
+#endif