[ARM] 5584/1: nomadik: add gpio driver and devices
authorAlessandro Rubini <rubini@gnudd.com>
Thu, 2 Jul 2009 14:29:12 +0000 (15:29 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Thu, 2 Jul 2009 20:20:45 +0000 (21:20 +0100)
Signed-off-by: Alessandro Rubini <rubini@unipv.it>
Acked-by: Andrea Gallo <andrea.gallo@stericsson.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/Kconfig
arch/arm/mach-nomadik/Makefile
arch/arm/mach-nomadik/cpu-8815.c
arch/arm/mach-nomadik/gpio.c [new file with mode: 0644]
arch/arm/mach-nomadik/include/mach/gpio.h [new file with mode: 0644]

index d8cfd70..b1093d8 100644 (file)
@@ -336,7 +336,8 @@ config ARCH_NOMADIK
        select COMMON_CLKDEV
        select GENERIC_TIME
        select GENERIC_CLOCKEVENTS
-
+       select GENERIC_GPIO
+       select ARCH_REQUIRE_GPIOLIB
        help
          Support for the Nomadik platform by ST-Ericsson
 
index fa97769..3fc4354 100644 (file)
@@ -7,7 +7,7 @@
 
 # Object file lists.
 
-obj-y                  += clock.o timer.o
+obj-y                  += clock.o timer.o gpio.o
 
 # Cpu revision
 obj-$(CONFIG_NOMADIK_8815) += cpu-8815.o
index dec42da..9917a9a 100644 (file)
 #include <linux/types.h>
 #include <linux/init.h>
 #include <linux/device.h>
+#include <linux/amba/bus.h>
+#include <linux/gpio.h>
 
 #include <mach/hardware.h>
 #include <mach/irqs.h>
 #include <asm/mach/map.h>
 #include <asm/hardware/vic.h>
 
+/* The 8815 has 4 GPIO blocks, let's register them immediately */
+static struct nmk_gpio_platform_data cpu8815_gpio[] = {
+       {
+               .name = "GPIO-0-31",
+               .first_gpio = 0,
+               .first_irq = NOMADIK_GPIO_TO_IRQ(0),
+               .parent_irq = IRQ_GPIO0,
+       }, {
+               .name = "GPIO-32-63",
+               .first_gpio = 32,
+               .first_irq = NOMADIK_GPIO_TO_IRQ(32),
+               .parent_irq = IRQ_GPIO1,
+       }, {
+               .name = "GPIO-64-95",
+               .first_gpio = 64,
+               .first_irq = NOMADIK_GPIO_TO_IRQ(64),
+               .parent_irq = IRQ_GPIO2,
+       }, {
+               .name = "GPIO-96-127", /* 124..127 not routed to pin */
+               .first_gpio = 96,
+               .first_irq = NOMADIK_GPIO_TO_IRQ(96),
+               .parent_irq = IRQ_GPIO3,
+       }
+};
+
+#define __MEM_4K_RESOURCE(x) \
+       .res = {.start = (x), .end = (x) + SZ_4K - 1, .flags = IORESOURCE_MEM}
+
+static struct amba_device cpu8815_amba_gpio[] = {
+       {
+               .dev = {
+                       .init_name = "gpio0",
+                       .platform_data = cpu8815_gpio + 0,
+               },
+               __MEM_4K_RESOURCE(NOMADIK_GPIO0_BASE),
+       }, {
+               .dev = {
+                       .init_name = "gpio1",
+                       .platform_data = cpu8815_gpio + 1,
+               },
+               __MEM_4K_RESOURCE(NOMADIK_GPIO1_BASE),
+       }, {
+               .dev = {
+                       .init_name = "gpio2",
+                       .platform_data = cpu8815_gpio + 2,
+               },
+               __MEM_4K_RESOURCE(NOMADIK_GPIO2_BASE),
+       }, {
+               .dev = {
+                       .init_name = "gpio3",
+                       .platform_data = cpu8815_gpio + 3,
+               },
+               __MEM_4K_RESOURCE(NOMADIK_GPIO3_BASE),
+       },
+};
+
+static struct amba_device *amba_devs[] __initdata = {
+       cpu8815_amba_gpio + 0,
+       cpu8815_amba_gpio + 1,
+       cpu8815_amba_gpio + 2,
+       cpu8815_amba_gpio + 3,
+};
+
+static int __init cpu8815_init(void)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(amba_devs); i++)
+               amba_device_register(amba_devs[i], &iomem_resource);
+       return 0;
+}
+arch_initcall(cpu8815_init);
+
 /* All SoC devices live in the same area (see hardware.h) */
 static struct map_desc nomadik_io_desc[] __initdata = {
        {
diff --git a/arch/arm/mach-nomadik/gpio.c b/arch/arm/mach-nomadik/gpio.c
new file mode 100644 (file)
index 0000000..9a09b27
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ * Generic GPIO driver for logic cells found in the Nomadik SoC
+ *
+ * Copyright (C) 2008,2009 STMicroelectronics
+ * Copyright (C) 2009 Alessandro Rubini <rubini@unipv.it>
+ *   Rewritten based on work by Prafulla WADASKAR <prafulla.wadaskar@st.com>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/amba/bus.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+
+/*
+ * The GPIO module in the Nomadik family of Systems-on-Chip is an
+ * AMBA device, managing 32 pins and alternate functions.  The logic block
+ * is currently only used in the Nomadik.
+ *
+ * Symbols in this file are called "nmk_gpio" for "nomadik gpio"
+ */
+
+#define NMK_GPIO_PER_CHIP 32
+struct nmk_gpio_chip {
+       struct gpio_chip chip;
+       void __iomem *addr;
+       unsigned int parent_irq;
+       spinlock_t *lock;
+       /* Keep track of configured edges */
+       u32 edge_rising;
+       u32 edge_falling;
+};
+
+/* Mode functions */
+int nmk_gpio_set_mode(int gpio, int gpio_mode)
+{
+       struct nmk_gpio_chip *nmk_chip;
+       unsigned long flags;
+       u32 afunc, bfunc, bit;
+
+       nmk_chip = get_irq_chip_data(NOMADIK_GPIO_TO_IRQ(gpio));
+       if (!nmk_chip)
+               return -EINVAL;
+
+       bit = 1 << (gpio - nmk_chip->chip.base);
+
+       spin_lock_irqsave(&nmk_chip->lock, flags);
+       afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & ~bit;
+       bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & ~bit;
+       if (gpio_mode & NMK_GPIO_ALT_A)
+               afunc |= bit;
+       if (gpio_mode & NMK_GPIO_ALT_B)
+               bfunc |= bit;
+       writel(afunc, nmk_chip->addr + NMK_GPIO_AFSLA);
+       writel(bfunc, nmk_chip->addr + NMK_GPIO_AFSLB);
+       spin_unlock_irqrestore(&nmk_chip->lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL(nmk_gpio_set_mode);
+
+int nmk_gpio_get_mode(int gpio)
+{
+       struct nmk_gpio_chip *nmk_chip;
+       u32 afunc, bfunc, bit;
+
+       nmk_chip = get_irq_chip_data(NOMADIK_GPIO_TO_IRQ(gpio));
+       if (!nmk_chip)
+               return -EINVAL;
+
+       bit = 1 << (gpio - nmk_chip->chip.base);
+
+       afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & bit;
+       bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & bit;
+
+       return (afunc ? NMK_GPIO_ALT_A : 0) | (bfunc ? NMK_GPIO_ALT_B : 0);
+}
+EXPORT_SYMBOL(nmk_gpio_get_mode);
+
+
+/* IRQ functions */
+static inline int nmk_gpio_get_bitmask(int gpio)
+{
+       return 1 << (gpio % 32);
+}
+
+static void nmk_gpio_irq_ack(unsigned int irq)
+{
+       int gpio;
+       struct nmk_gpio_chip *nmk_chip;
+
+       gpio = NOMADIK_IRQ_TO_GPIO(irq);
+       nmk_chip = get_irq_chip_data(irq);
+       if (!nmk_chip)
+               return;
+       writel(nmk_gpio_get_bitmask(gpio), nmk_chip->addr + NMK_GPIO_IC);
+}
+
+static void nmk_gpio_irq_mask(unsigned int irq)
+{
+       int gpio;
+       struct nmk_gpio_chip *nmk_chip;
+       unsigned long flags;
+       u32 bitmask, reg;
+
+       gpio = NOMADIK_IRQ_TO_GPIO(irq);
+       nmk_chip = get_irq_chip_data(irq);
+       bitmask = nmk_gpio_get_bitmask(gpio);
+       if (!nmk_chip)
+               return;
+
+       /* we must individually clear the two edges */
+       spin_lock_irqsave(&nmk_chip->lock, flags);
+       if (nmk_chip->edge_rising & bitmask) {
+               reg = readl(nmk_chip->addr + NMK_GPIO_RWIMSC);
+               reg &= ~bitmask;
+               writel(reg, nmk_chip->addr + NMK_GPIO_RWIMSC);
+       }
+       if (nmk_chip->edge_falling & bitmask) {
+               reg = readl(nmk_chip->addr + NMK_GPIO_FWIMSC);
+               reg &= ~bitmask;
+               writel(reg, nmk_chip->addr + NMK_GPIO_FWIMSC);
+       }
+       spin_unlock_irqrestore(&nmk_chip->lock, flags);
+};
+
+static void nmk_gpio_irq_unmask(unsigned int irq)
+{
+       int gpio;
+       struct nmk_gpio_chip *nmk_chip;
+       unsigned long flags;
+       u32 bitmask, reg;
+
+       gpio = NOMADIK_IRQ_TO_GPIO(irq);
+       nmk_chip = get_irq_chip_data(irq);
+       bitmask = nmk_gpio_get_bitmask(gpio);
+       if (!nmk_chip)
+               return;
+
+       /* we must individually set the two edges */
+       spin_lock_irqsave(&nmk_chip->lock, flags);
+       if (nmk_chip->edge_rising & bitmask) {
+               reg = readl(nmk_chip->addr + NMK_GPIO_RWIMSC);
+               reg |= bitmask;
+               writel(reg, nmk_chip->addr + NMK_GPIO_RWIMSC);
+       }
+       if (nmk_chip->edge_falling & bitmask) {
+               reg = readl(nmk_chip->addr + NMK_GPIO_FWIMSC);
+               reg |= bitmask;
+               writel(reg, nmk_chip->addr + NMK_GPIO_FWIMSC);
+       }
+       spin_unlock_irqrestore(&nmk_chip->lock, flags);
+}
+
+static int nmk_gpio_irq_set_type(unsigned int irq, unsigned int type)
+{
+       int gpio;
+       struct nmk_gpio_chip *nmk_chip;
+       unsigned long flags;
+       u32 bitmask;
+
+       gpio = NOMADIK_IRQ_TO_GPIO(irq);
+       nmk_chip = get_irq_chip_data(irq);
+       bitmask = nmk_gpio_get_bitmask(gpio);
+       if (!nmk_chip)
+               return -EINVAL;
+
+       if (type & IRQ_TYPE_LEVEL_HIGH)
+               return -EINVAL;
+       if (type & IRQ_TYPE_LEVEL_LOW)
+               return -EINVAL;
+
+       spin_lock_irqsave(&nmk_chip->lock, flags);
+
+       nmk_chip->edge_rising &= ~bitmask;
+       if (type & IRQ_TYPE_EDGE_RISING)
+               nmk_chip->edge_rising |= bitmask;
+       writel(nmk_chip->edge_rising, nmk_chip->addr + NMK_GPIO_RIMSC);
+
+       nmk_chip->edge_falling &= ~bitmask;
+       if (type & IRQ_TYPE_EDGE_FALLING)
+               nmk_chip->edge_falling |= bitmask;
+       writel(nmk_chip->edge_falling, nmk_chip->addr + NMK_GPIO_FIMSC);
+
+       spin_unlock_irqrestore(&nmk_chip->lock, flags);
+
+       nmk_gpio_irq_unmask(irq);
+
+       return 0;
+}
+
+static struct irq_chip nmk_gpio_irq_chip = {
+       .name           = "Nomadik-GPIO",
+       .ack            = nmk_gpio_irq_ack,
+       .mask           = nmk_gpio_irq_mask,
+       .unmask         = nmk_gpio_irq_unmask,
+       .set_type       = nmk_gpio_irq_set_type,
+};
+
+static void nmk_gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+       struct nmk_gpio_chip *nmk_chip;
+       struct irq_chip *host_chip;
+       unsigned int gpio_irq;
+       u32 pending;
+       unsigned int first_irq;
+
+       nmk_chip = get_irq_data(irq);
+       first_irq = NOMADIK_GPIO_TO_IRQ(nmk_chip->chip.base);
+       while ( (pending = readl(nmk_chip->addr + NMK_GPIO_IS)) ) {
+               gpio_irq = first_irq + __ffs(pending);
+               generic_handle_irq(gpio_irq);
+       }
+       if (0) {/* don't ack parent irq, as ack == disable */
+               host_chip = get_irq_chip(irq);
+               host_chip->ack(irq);
+       }
+}
+
+static int nmk_gpio_init_irq(struct nmk_gpio_chip *nmk_chip)
+{
+       unsigned int first_irq;
+       int i;
+
+       first_irq = NOMADIK_GPIO_TO_IRQ(nmk_chip->chip.base);
+       for (i = first_irq; i < first_irq + NMK_GPIO_PER_CHIP; i++) {
+               set_irq_chip(i, &nmk_gpio_irq_chip);
+               set_irq_handler(i, handle_edge_irq);
+               set_irq_flags(i, IRQF_VALID);
+               set_irq_chip_data(i, nmk_chip);
+       }
+       set_irq_chained_handler(nmk_chip->parent_irq, nmk_gpio_irq_handler);
+       set_irq_data(nmk_chip->parent_irq, nmk_chip);
+       return 0;
+}
+
+/* I/O Functions */
+static int nmk_gpio_make_input(struct gpio_chip *chip, unsigned offset)
+{
+       struct nmk_gpio_chip *nmk_chip =
+               container_of(chip, struct nmk_gpio_chip, chip);
+
+       writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRC);
+       return 0;
+}
+
+static int nmk_gpio_make_output(struct gpio_chip *chip, unsigned offset,
+                               int val)
+{
+       struct nmk_gpio_chip *nmk_chip =
+               container_of(chip, struct nmk_gpio_chip, chip);
+
+       writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRS);
+       return 0;
+}
+
+static int nmk_gpio_get_input(struct gpio_chip *chip, unsigned offset)
+{
+       struct nmk_gpio_chip *nmk_chip =
+               container_of(chip, struct nmk_gpio_chip, chip);
+       u32 bit = 1 << offset;
+
+       return (readl(nmk_chip->addr + NMK_GPIO_DAT) & bit) != 0;
+}
+
+static void nmk_gpio_set_output(struct gpio_chip *chip, unsigned offset,
+                               int val)
+{
+       struct nmk_gpio_chip *nmk_chip =
+               container_of(chip, struct nmk_gpio_chip, chip);
+       u32 bit = 1 << offset;
+
+       if (val)
+               writel(bit, nmk_chip->addr + NMK_GPIO_DATS);
+       else
+               writel(bit, nmk_chip->addr + NMK_GPIO_DATC);
+}
+
+/* This structure is replicated for each GPIO block allocated at probe time */
+static struct gpio_chip nmk_gpio_template = {
+       .direction_input        = nmk_gpio_make_input,
+       .get                    = nmk_gpio_get_input,
+       .direction_output       = nmk_gpio_make_output,
+       .set                    = nmk_gpio_set_output,
+       .ngpio                  = NMK_GPIO_PER_CHIP,
+       .can_sleep              = 0,
+};
+
+static int __init nmk_gpio_probe(struct amba_device *dev, struct amba_id *id)
+{
+       struct nmk_gpio_platform_data *pdata;
+       struct nmk_gpio_chip *nmk_chip;
+       struct gpio_chip *chip;
+       int ret;
+
+       pdata = dev->dev.platform_data;
+       ret = amba_request_regions(dev, pdata->name);
+       if (ret)
+               return ret;
+
+       nmk_chip = kzalloc(sizeof(*nmk_chip), GFP_KERNEL);
+       if (!nmk_chip) {
+               ret = -ENOMEM;
+               goto out_amba;
+       }
+       /*
+        * The virt address in nmk_chip->addr is in the nomadik register space,
+        * so we can simply convert the resource address, without remapping
+        */
+       nmk_chip->addr = io_p2v(dev->res.start);
+       nmk_chip->chip = nmk_gpio_template;
+       nmk_chip->parent_irq = pdata->parent_irq;
+
+       chip = &nmk_chip->chip;
+       chip->base = pdata->first_gpio;
+       chip->label = pdata->name;
+       chip->dev = &dev->dev;
+       chip->owner = THIS_MODULE;
+
+       ret = gpiochip_add(&nmk_chip->chip);
+       if (ret)
+               goto out_free;
+
+       amba_set_drvdata(dev, nmk_chip);
+
+       nmk_gpio_init_irq(nmk_chip);
+
+       dev_info(&dev->dev, "Bits %i-%i at address %p\n",
+                nmk_chip->chip.base, nmk_chip->chip.base+31, nmk_chip->addr);
+       return 0;
+
+ out_free:
+       kfree(nmk_chip);
+ out_amba:
+       amba_release_regions(dev);
+       dev_err(&dev->dev, "Failure %i for GPIO %i-%i\n", ret,
+                 pdata->first_gpio, pdata->first_gpio+31);
+       return ret;
+}
+
+static int nmk_gpio_remove(struct amba_device *dev)
+{
+       struct nmk_gpio_chip *nmk_chip;
+
+       nmk_chip = amba_get_drvdata(dev);
+       gpiochip_remove(&nmk_chip->chip);
+       kfree(nmk_chip);
+       amba_release_regions(dev);
+       return 0;
+}
+
+
+/* We have 0x1f080060 and 0x1f180060, accept both using the mask */
+static struct amba_id nmk_gpio_ids[] = {
+       {
+               .id     = 0x1f080060,
+               .mask   = 0xffefffff,
+       },
+       {0, 0},
+};
+
+static struct amba_driver nmk_gpio_driver = {
+       .drv = {
+               .owner = THIS_MODULE,
+               .name = "gpio",
+               },
+       .probe = nmk_gpio_probe,
+       .remove = nmk_gpio_remove,
+       .suspend = NULL, /* to be done */
+       .resume = NULL,
+       .id_table = nmk_gpio_ids,
+};
+
+static int __init nmk_gpio_init(void)
+{
+       return amba_driver_register(&nmk_gpio_driver);
+}
+
+arch_initcall(nmk_gpio_init);
+
+MODULE_AUTHOR("Prafulla WADASKAR and Alessandro Rubini");
+MODULE_DESCRIPTION("Nomadik GPIO Driver");
+MODULE_LICENSE("GPL");
+
+
diff --git a/arch/arm/mach-nomadik/include/mach/gpio.h b/arch/arm/mach-nomadik/include/mach/gpio.h
new file mode 100644 (file)
index 0000000..61577c9
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Structures and registers for GPIO access in the Nomadik SoC
+ *
+ * Copyright (C) 2008 STMicroelectronics
+ *     Author: Prafulla WADASKAR <prafulla.wadaskar@st.com>
+ * Copyright (C) 2009 Alessandro Rubini <rubini@unipv.it>
+ *
+ * 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.
+ */
+#ifndef __ASM_ARCH_GPIO_H
+#define __ASM_ARCH_GPIO_H
+
+#include <asm-generic/gpio.h>
+
+/*
+ * These currently cause a function call to happen, they may be optimized
+ * if needed by adding cpu-specific defines to identify blocks
+ * (see mach-pxa/include/mach/gpio.h as an example using GPLR etc)
+ */
+#define gpio_get_value  __gpio_get_value
+#define gpio_set_value  __gpio_set_value
+#define gpio_cansleep   __gpio_cansleep
+#define gpio_to_irq     __gpio_to_irq
+
+/*
+ * "nmk_gpio" and "NMK_GPIO" stand for "Nomadik GPIO", leaving
+ * the "gpio" namespace for generic and cross-machine functions
+ */
+
+/* Register in the logic block */
+#define NMK_GPIO_DAT   0x00
+#define NMK_GPIO_DATS  0x04
+#define NMK_GPIO_DATC  0x08
+#define NMK_GPIO_PDIS  0x0c
+#define NMK_GPIO_DIR   0x10
+#define NMK_GPIO_DIRS  0x14
+#define NMK_GPIO_DIRC  0x18
+#define NMK_GPIO_SLPC  0x1c
+#define NMK_GPIO_AFSLA 0x20
+#define NMK_GPIO_AFSLB 0x24
+
+#define NMK_GPIO_RIMSC 0x40
+#define NMK_GPIO_FIMSC 0x44
+#define NMK_GPIO_IS    0x48
+#define NMK_GPIO_IC    0x4c
+#define NMK_GPIO_RWIMSC        0x50
+#define NMK_GPIO_FWIMSC        0x54
+#define NMK_GPIO_WKS   0x58
+
+/* Alternate functions: function C is set in hw by setting both A and B */
+#define NMK_GPIO_ALT_GPIO      0
+#define NMK_GPIO_ALT_A 1
+#define NMK_GPIO_ALT_B 2
+#define NMK_GPIO_ALT_C (NMK_GPIO_ALT_A | NMK_GPIO_ALT_B)
+
+extern int nmk_gpio_set_mode(int gpio, int gpio_mode);
+extern int nmk_gpio_get_mode(int gpio);
+
+/*
+ * Platform data to register a block: only the initial gpio/irq number.
+ */
+struct nmk_gpio_platform_data {
+       char *name;
+       int first_gpio;
+       int first_irq;
+       int parent_irq;
+};
+
+#endif /* __ASM_ARCH_GPIO_H */