aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBaruch Siach <baruch@tkos.co.il>2009-06-18 19:48:58 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-06-19 19:46:03 -0400
commit1e9c28599879040039f610c5b177e61ef65ff100 (patch)
tree308fea743fbadefa3ea9a88ff3ae51f6fe0c624a
parent0732f87761dbe417cb6e084b712d07e879e876ef (diff)
gpio: driver for PrimeCell PL061 GPIO controller
This is a driver for the ARM PrimeCell PL061 GPIO AMBA peripheral. The driver is implemented using the gpiolib framework. This driver also includes support for the use of the PL061 as an interrupt controller (secondary). Signed-off-by: Baruch Siach <baruch@tkos.co.il> Cc: David Brownell <david-b@pacbell.net> Acked-by: Russell King <rmk@arm.linux.org.uk> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--drivers/gpio/Kconfig6
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/pl061.c341
-rw-r--r--include/linux/amba/pl061.h15
4 files changed, 363 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 11f373971fa5..3582c39f9725 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -67,6 +67,12 @@ config GPIO_SYSFS
67 67
68comment "Memory mapped GPIO expanders:" 68comment "Memory mapped GPIO expanders:"
69 69
70config GPIO_PL061
71 bool "PrimeCell PL061 GPIO support"
72 depends on ARM_AMBA
73 help
74 Say yes here to support the PrimeCell PL061 GPIO device
75
70config GPIO_XILINX 76config GPIO_XILINX
71 bool "Xilinx GPIO support" 77 bool "Xilinx GPIO support"
72 depends on PPC_OF || MICROBLAZE 78 depends on PPC_OF || MICROBLAZE
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 49ac64e515e6..ef90203e8f3c 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_GPIO_MAX732X) += max732x.o
9obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o 9obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o
10obj-$(CONFIG_GPIO_PCA953X) += pca953x.o 10obj-$(CONFIG_GPIO_PCA953X) += pca953x.o
11obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o 11obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o
12obj-$(CONFIG_GPIO_PL061) += pl061.o
12obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o 13obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o
13obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o 14obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o
14obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o 15obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o
diff --git a/drivers/gpio/pl061.c b/drivers/gpio/pl061.c
new file mode 100644
index 000000000000..aa8e7cb020d9
--- /dev/null
+++ b/drivers/gpio/pl061.c
@@ -0,0 +1,341 @@
1/*
2 * linux/drivers/gpio/pl061.c
3 *
4 * Copyright (C) 2008, 2009 Provigent Ltd.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * Driver for the ARM PrimeCell(tm) General Purpose Input/Output (PL061)
11 *
12 * Data sheet: ARM DDI 0190B, September 2000
13 */
14#include <linux/spinlock.h>
15#include <linux/errno.h>
16#include <linux/module.h>
17#include <linux/list.h>
18#include <linux/io.h>
19#include <linux/ioport.h>
20#include <linux/irq.h>
21#include <linux/bitops.h>
22#include <linux/workqueue.h>
23#include <linux/gpio.h>
24#include <linux/device.h>
25#include <linux/amba/bus.h>
26#include <linux/amba/pl061.h>
27
28#define GPIODIR 0x400
29#define GPIOIS 0x404
30#define GPIOIBE 0x408
31#define GPIOIEV 0x40C
32#define GPIOIE 0x410
33#define GPIORIS 0x414
34#define GPIOMIS 0x418
35#define GPIOIC 0x41C
36
37#define PL061_GPIO_NR 8
38
39struct pl061_gpio {
40 /* We use a list of pl061_gpio structs for each trigger IRQ in the main
41 * interrupts controller of the system. We need this to support systems
42 * in which more that one PL061s are connected to the same IRQ. The ISR
43 * interates through this list to find the source of the interrupt.
44 */
45 struct list_head list;
46
47 /* Each of the two spinlocks protects a different set of hardware
48 * regiters and data structurs. This decouples the code of the IRQ from
49 * the GPIO code. This also makes the case of a GPIO routine call from
50 * the IRQ code simpler.
51 */
52 spinlock_t lock; /* GPIO registers */
53 spinlock_t irq_lock; /* IRQ registers */
54
55 void __iomem *base;
56 unsigned irq_base;
57 struct gpio_chip gc;
58};
59
60static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
61{
62 struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc);
63 unsigned long flags;
64 unsigned char gpiodir;
65
66 if (offset >= gc->ngpio)
67 return -EINVAL;
68
69 spin_lock_irqsave(&chip->lock, flags);
70 gpiodir = readb(chip->base + GPIODIR);
71 gpiodir &= ~(1 << offset);
72 writeb(gpiodir, chip->base + GPIODIR);
73 spin_unlock_irqrestore(&chip->lock, flags);
74
75 return 0;
76}
77
78static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
79 int value)
80{
81 struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc);
82 unsigned long flags;
83 unsigned char gpiodir;
84
85 if (offset >= gc->ngpio)
86 return -EINVAL;
87
88 spin_lock_irqsave(&chip->lock, flags);
89 writeb(!!value << offset, chip->base + (1 << (offset + 2)));
90 gpiodir = readb(chip->base + GPIODIR);
91 gpiodir |= 1 << offset;
92 writeb(gpiodir, chip->base + GPIODIR);
93 spin_unlock_irqrestore(&chip->lock, flags);
94
95 return 0;
96}
97
98static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
99{
100 struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc);
101
102 return !!readb(chip->base + (1 << (offset + 2)));
103}
104
105static void pl061_set_value(struct gpio_chip *gc, unsigned offset, int value)
106{
107 struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc);
108
109 writeb(!!value << offset, chip->base + (1 << (offset + 2)));
110}
111
112/*
113 * PL061 GPIO IRQ
114 */
115static void pl061_irq_disable(unsigned irq)
116{
117 struct pl061_gpio *chip = get_irq_chip_data(irq);
118 int offset = irq - chip->irq_base;
119 unsigned long flags;
120 u8 gpioie;
121
122 spin_lock_irqsave(&chip->irq_lock, flags);
123 gpioie = readb(chip->base + GPIOIE);
124 gpioie &= ~(1 << offset);
125 writeb(gpioie, chip->base + GPIOIE);
126 spin_unlock_irqrestore(&chip->irq_lock, flags);
127}
128
129static void pl061_irq_enable(unsigned irq)
130{
131 struct pl061_gpio *chip = get_irq_chip_data(irq);
132 int offset = irq - chip->irq_base;
133 unsigned long flags;
134 u8 gpioie;
135
136 spin_lock_irqsave(&chip->irq_lock, flags);
137 gpioie = readb(chip->base + GPIOIE);
138 gpioie |= 1 << offset;
139 writeb(gpioie, chip->base + GPIOIE);
140 spin_unlock_irqrestore(&chip->irq_lock, flags);
141}
142
143static int pl061_irq_type(unsigned irq, unsigned trigger)
144{
145 struct pl061_gpio *chip = get_irq_chip_data(irq);
146 int offset = irq - chip->irq_base;
147 unsigned long flags;
148 u8 gpiois, gpioibe, gpioiev;
149
150 if (offset < 0 || offset > PL061_GPIO_NR)
151 return -EINVAL;
152
153 spin_lock_irqsave(&chip->irq_lock, flags);
154
155 gpioiev = readb(chip->base + GPIOIEV);
156
157 gpiois = readb(chip->base + GPIOIS);
158 if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
159 gpiois |= 1 << offset;
160 if (trigger & IRQ_TYPE_LEVEL_HIGH)
161 gpioiev |= 1 << offset;
162 else
163 gpioiev &= ~(1 << offset);
164 } else
165 gpiois &= ~(1 << offset);
166 writeb(gpiois, chip->base + GPIOIS);
167
168 gpioibe = readb(chip->base + GPIOIBE);
169 if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH)
170 gpioibe |= 1 << offset;
171 else {
172 gpioibe &= ~(1 << offset);
173 if (trigger & IRQ_TYPE_EDGE_RISING)
174 gpioiev |= 1 << offset;
175 else
176 gpioiev &= ~(1 << offset);
177 }
178 writeb(gpioibe, chip->base + GPIOIBE);
179
180 writeb(gpioiev, chip->base + GPIOIEV);
181
182 spin_unlock_irqrestore(&chip->irq_lock, flags);
183
184 return 0;
185}
186
187static struct irq_chip pl061_irqchip = {
188 .name = "GPIO",
189 .enable = pl061_irq_enable,
190 .disable = pl061_irq_disable,
191 .set_type = pl061_irq_type,
192};
193
194static void pl061_irq_handler(unsigned irq, struct irq_desc *desc)
195{
196 struct list_head *chip_list = get_irq_chip_data(irq);
197 struct list_head *ptr;
198 struct pl061_gpio *chip;
199
200 desc->chip->ack(irq);
201 list_for_each(ptr, chip_list) {
202 unsigned long pending;
203 int gpio;
204
205 chip = list_entry(ptr, struct pl061_gpio, list);
206 pending = readb(chip->base + GPIOMIS);
207 writeb(pending, chip->base + GPIOIC);
208
209 if (pending == 0)
210 continue;
211
212 for_each_bit(gpio, &pending, PL061_GPIO_NR)
213 generic_handle_irq(gpio_to_irq(gpio));
214 }
215 desc->chip->unmask(irq);
216}
217
218static int __init pl061_probe(struct amba_device *dev, struct amba_id *id)
219{
220 struct pl061_platform_data *pdata;
221 struct pl061_gpio *chip;
222 struct list_head *chip_list;
223 int ret, irq, i;
224 static unsigned long init_irq[BITS_TO_LONGS(NR_IRQS)];
225
226 pdata = dev->dev.platform_data;
227 if (pdata == NULL)
228 return -ENODEV;
229
230 chip = kzalloc(sizeof(*chip), GFP_KERNEL);
231 if (chip == NULL)
232 return -ENOMEM;
233
234 if (!request_mem_region(dev->res.start,
235 resource_size(&dev->res), "pl061")) {
236 ret = -EBUSY;
237 goto free_mem;
238 }
239
240 chip->base = ioremap(dev->res.start, resource_size(&dev->res));
241 if (chip->base == NULL) {
242 ret = -ENOMEM;
243 goto release_region;
244 }
245
246 spin_lock_init(&chip->lock);
247 spin_lock_init(&chip->irq_lock);
248 INIT_LIST_HEAD(&chip->list);
249
250 chip->gc.direction_input = pl061_direction_input;
251 chip->gc.direction_output = pl061_direction_output;
252 chip->gc.get = pl061_get_value;
253 chip->gc.set = pl061_set_value;
254 chip->gc.base = pdata->gpio_base;
255 chip->gc.ngpio = PL061_GPIO_NR;
256 chip->gc.label = dev_name(&dev->dev);
257 chip->gc.dev = &dev->dev;
258 chip->gc.owner = THIS_MODULE;
259
260 chip->irq_base = pdata->irq_base;
261
262 ret = gpiochip_add(&chip->gc);
263 if (ret)
264 goto iounmap;
265
266 /*
267 * irq_chip support
268 */
269
270 if (chip->irq_base == (unsigned) -1)
271 return 0;
272
273 writeb(0, chip->base + GPIOIE); /* disable irqs */
274 irq = dev->irq[0];
275 if (irq < 0) {
276 ret = -ENODEV;
277 goto iounmap;
278 }
279 set_irq_chained_handler(irq, pl061_irq_handler);
280 if (!test_and_set_bit(irq, init_irq)) { /* list initialized? */
281 chip_list = kmalloc(sizeof(*chip_list), GFP_KERNEL);
282 if (chip_list == NULL) {
283 ret = -ENOMEM;
284 goto iounmap;
285 }
286 INIT_LIST_HEAD(chip_list);
287 set_irq_chip_data(irq, chip_list);
288 } else
289 chip_list = get_irq_chip_data(irq);
290 list_add(&chip->list, chip_list);
291
292 for (i = 0; i < PL061_GPIO_NR; i++) {
293 if (pdata->directions & (1 << i))
294 pl061_direction_output(&chip->gc, i,
295 pdata->values & (1 << i));
296 else
297 pl061_direction_input(&chip->gc, i);
298
299 set_irq_chip(i+chip->irq_base, &pl061_irqchip);
300 set_irq_handler(i+chip->irq_base, handle_simple_irq);
301 set_irq_flags(i+chip->irq_base, IRQF_VALID);
302 set_irq_chip_data(i+chip->irq_base, chip);
303 }
304
305 return 0;
306
307iounmap:
308 iounmap(chip->base);
309release_region:
310 release_mem_region(dev->res.start, resource_size(&dev->res));
311free_mem:
312 kfree(chip);
313
314 return ret;
315}
316
317static struct amba_id pl061_ids[] __initdata = {
318 {
319 .id = 0x00041061,
320 .mask = 0x000fffff,
321 },
322 { 0, 0 },
323};
324
325static struct amba_driver pl061_gpio_driver = {
326 .drv = {
327 .name = "pl061_gpio",
328 },
329 .id_table = pl061_ids,
330 .probe = pl061_probe,
331};
332
333static int __init pl061_gpio_init(void)
334{
335 return amba_driver_register(&pl061_gpio_driver);
336}
337subsys_initcall(pl061_gpio_init);
338
339MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");
340MODULE_DESCRIPTION("PL061 GPIO driver");
341MODULE_LICENSE("GPL");
diff --git a/include/linux/amba/pl061.h b/include/linux/amba/pl061.h
new file mode 100644
index 000000000000..b4fbd9862606
--- /dev/null
+++ b/include/linux/amba/pl061.h
@@ -0,0 +1,15 @@
1/* platform data for the PL061 GPIO driver */
2
3struct pl061_platform_data {
4 /* number of the first GPIO */
5 unsigned gpio_base;
6
7 /* number of the first IRQ.
8 * If the IRQ functionality in not desired this must be set to
9 * (unsigned) -1.
10 */
11 unsigned irq_base;
12
13 u8 directions; /* startup directions, 1: out, 0: in */
14 u8 values; /* startup values */
15};