diff options
author | Richard Röjfors <richard.rojfors@mocean-labs.com> | 2009-12-15 19:46:18 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-16 10:20:00 -0500 |
commit | 35570ac6039ef490b9c5abde1fee4803a39bf4e1 (patch) | |
tree | 59b743b2ff1b1e054fa83b91d5c0c83be98e3cfc | |
parent | 4efec6272e8e61fc77132b4d2bae56d61b289956 (diff) |
gpio: add GPIO driver for the Timberdale FPGA
A GPIO driver for the Timberdale FPGA found on the Intel Atom board
Russellville.
The GPIO driver also has an IRQ-chip to support interrupts on the pins.
Signed-off-by: Richard Röjfors <richard.rojfors@mocean-labs.com>
Cc: David Brownell <david-b@pacbell.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/gpio/Kconfig | 6 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/timbgpio.c | 342 | ||||
-rw-r--r-- | include/linux/timb_gpio.h | 37 |
4 files changed, 386 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 57ca339924ef..a019b49ecc9b 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -206,6 +206,12 @@ config GPIO_LANGWELL | |||
206 | help | 206 | help |
207 | Say Y here to support Intel Moorestown platform GPIO. | 207 | Say Y here to support Intel Moorestown platform GPIO. |
208 | 208 | ||
209 | config GPIO_TIMBERDALE | ||
210 | bool "Support for timberdale GPIO IP" | ||
211 | depends on MFD_TIMBERDALE && GPIOLIB && HAS_IOMEM | ||
212 | ---help--- | ||
213 | Add support for the GPIO IP in the timberdale FPGA. | ||
214 | |||
209 | comment "SPI GPIO expanders:" | 215 | comment "SPI GPIO expanders:" |
210 | 216 | ||
211 | config GPIO_MAX7301 | 217 | config GPIO_MAX7301 |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 270b6d7839f5..52fe4cf734c7 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -13,6 +13,7 @@ obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o | |||
13 | obj-$(CONFIG_GPIO_PCA953X) += pca953x.o | 13 | obj-$(CONFIG_GPIO_PCA953X) += pca953x.o |
14 | obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o | 14 | obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o |
15 | obj-$(CONFIG_GPIO_PL061) += pl061.o | 15 | obj-$(CONFIG_GPIO_PL061) += pl061.o |
16 | obj-$(CONFIG_GPIO_TIMBERDALE) += timbgpio.o | ||
16 | obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o | 17 | obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o |
17 | obj-$(CONFIG_GPIO_UCB1400) += ucb1400_gpio.o | 18 | obj-$(CONFIG_GPIO_UCB1400) += ucb1400_gpio.o |
18 | obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o | 19 | obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o |
diff --git a/drivers/gpio/timbgpio.c b/drivers/gpio/timbgpio.c new file mode 100644 index 000000000000..a4d344ba8e5c --- /dev/null +++ b/drivers/gpio/timbgpio.c | |||
@@ -0,0 +1,342 @@ | |||
1 | /* | ||
2 | * timbgpio.c timberdale FPGA GPIO driver | ||
3 | * Copyright (c) 2009 Intel Corporation | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
17 | */ | ||
18 | |||
19 | /* Supports: | ||
20 | * Timberdale FPGA GPIO | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/gpio.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/io.h> | ||
27 | #include <linux/timb_gpio.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | |||
30 | #define DRIVER_NAME "timb-gpio" | ||
31 | |||
32 | #define TGPIOVAL 0x00 | ||
33 | #define TGPIODIR 0x04 | ||
34 | #define TGPIO_IER 0x08 | ||
35 | #define TGPIO_ISR 0x0c | ||
36 | #define TGPIO_IPR 0x10 | ||
37 | #define TGPIO_ICR 0x14 | ||
38 | #define TGPIO_FLR 0x18 | ||
39 | #define TGPIO_LVR 0x1c | ||
40 | |||
41 | struct timbgpio { | ||
42 | void __iomem *membase; | ||
43 | spinlock_t lock; /* mutual exclusion */ | ||
44 | struct gpio_chip gpio; | ||
45 | int irq_base; | ||
46 | }; | ||
47 | |||
48 | static int timbgpio_update_bit(struct gpio_chip *gpio, unsigned index, | ||
49 | unsigned offset, bool enabled) | ||
50 | { | ||
51 | struct timbgpio *tgpio = container_of(gpio, struct timbgpio, gpio); | ||
52 | u32 reg; | ||
53 | |||
54 | spin_lock(&tgpio->lock); | ||
55 | reg = ioread32(tgpio->membase + offset); | ||
56 | |||
57 | if (enabled) | ||
58 | reg |= (1 << index); | ||
59 | else | ||
60 | reg &= ~(1 << index); | ||
61 | |||
62 | iowrite32(reg, tgpio->membase + offset); | ||
63 | spin_unlock(&tgpio->lock); | ||
64 | |||
65 | return 0; | ||
66 | } | ||
67 | |||
68 | static int timbgpio_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) | ||
69 | { | ||
70 | return timbgpio_update_bit(gpio, nr, TGPIODIR, true); | ||
71 | } | ||
72 | |||
73 | static int timbgpio_gpio_get(struct gpio_chip *gpio, unsigned nr) | ||
74 | { | ||
75 | struct timbgpio *tgpio = container_of(gpio, struct timbgpio, gpio); | ||
76 | u32 value; | ||
77 | |||
78 | value = ioread32(tgpio->membase + TGPIOVAL); | ||
79 | return (value & (1 << nr)) ? 1 : 0; | ||
80 | } | ||
81 | |||
82 | static int timbgpio_gpio_direction_output(struct gpio_chip *gpio, | ||
83 | unsigned nr, int val) | ||
84 | { | ||
85 | return timbgpio_update_bit(gpio, nr, TGPIODIR, false); | ||
86 | } | ||
87 | |||
88 | static void timbgpio_gpio_set(struct gpio_chip *gpio, | ||
89 | unsigned nr, int val) | ||
90 | { | ||
91 | timbgpio_update_bit(gpio, nr, TGPIOVAL, val != 0); | ||
92 | } | ||
93 | |||
94 | static int timbgpio_to_irq(struct gpio_chip *gpio, unsigned offset) | ||
95 | { | ||
96 | struct timbgpio *tgpio = container_of(gpio, struct timbgpio, gpio); | ||
97 | |||
98 | if (tgpio->irq_base <= 0) | ||
99 | return -EINVAL; | ||
100 | |||
101 | return tgpio->irq_base + offset; | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * GPIO IRQ | ||
106 | */ | ||
107 | static void timbgpio_irq_disable(unsigned irq) | ||
108 | { | ||
109 | struct timbgpio *tgpio = get_irq_chip_data(irq); | ||
110 | int offset = irq - tgpio->irq_base; | ||
111 | |||
112 | timbgpio_update_bit(&tgpio->gpio, offset, TGPIO_IER, 0); | ||
113 | } | ||
114 | |||
115 | static void timbgpio_irq_enable(unsigned irq) | ||
116 | { | ||
117 | struct timbgpio *tgpio = get_irq_chip_data(irq); | ||
118 | int offset = irq - tgpio->irq_base; | ||
119 | |||
120 | timbgpio_update_bit(&tgpio->gpio, offset, TGPIO_IER, 1); | ||
121 | } | ||
122 | |||
123 | static int timbgpio_irq_type(unsigned irq, unsigned trigger) | ||
124 | { | ||
125 | struct timbgpio *tgpio = get_irq_chip_data(irq); | ||
126 | int offset = irq - tgpio->irq_base; | ||
127 | unsigned long flags; | ||
128 | u32 lvr, flr; | ||
129 | |||
130 | if (offset < 0 || offset > tgpio->gpio.ngpio) | ||
131 | return -EINVAL; | ||
132 | |||
133 | spin_lock_irqsave(&tgpio->lock, flags); | ||
134 | |||
135 | lvr = ioread32(tgpio->membase + TGPIO_LVR); | ||
136 | flr = ioread32(tgpio->membase + TGPIO_FLR); | ||
137 | |||
138 | if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) { | ||
139 | flr &= ~(1 << offset); | ||
140 | if (trigger & IRQ_TYPE_LEVEL_HIGH) | ||
141 | lvr |= 1 << offset; | ||
142 | else | ||
143 | lvr &= ~(1 << offset); | ||
144 | } | ||
145 | |||
146 | if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) | ||
147 | return -EINVAL; | ||
148 | else { | ||
149 | flr |= 1 << offset; | ||
150 | /* opposite compared to the datasheet, but it mirrors the | ||
151 | * reality | ||
152 | */ | ||
153 | if (trigger & IRQ_TYPE_EDGE_FALLING) | ||
154 | lvr |= 1 << offset; | ||
155 | else | ||
156 | lvr &= ~(1 << offset); | ||
157 | } | ||
158 | |||
159 | iowrite32(lvr, tgpio->membase + TGPIO_LVR); | ||
160 | iowrite32(flr, tgpio->membase + TGPIO_FLR); | ||
161 | iowrite32(1 << offset, tgpio->membase + TGPIO_ICR); | ||
162 | spin_unlock_irqrestore(&tgpio->lock, flags); | ||
163 | |||
164 | return 0; | ||
165 | } | ||
166 | |||
167 | static void timbgpio_irq(unsigned int irq, struct irq_desc *desc) | ||
168 | { | ||
169 | struct timbgpio *tgpio = get_irq_data(irq); | ||
170 | unsigned long ipr; | ||
171 | int offset; | ||
172 | |||
173 | desc->chip->ack(irq); | ||
174 | ipr = ioread32(tgpio->membase + TGPIO_IPR); | ||
175 | iowrite32(ipr, tgpio->membase + TGPIO_ICR); | ||
176 | |||
177 | for_each_bit(offset, &ipr, tgpio->gpio.ngpio) | ||
178 | generic_handle_irq(timbgpio_to_irq(&tgpio->gpio, offset)); | ||
179 | } | ||
180 | |||
181 | static struct irq_chip timbgpio_irqchip = { | ||
182 | .name = "GPIO", | ||
183 | .enable = timbgpio_irq_enable, | ||
184 | .disable = timbgpio_irq_disable, | ||
185 | .set_type = timbgpio_irq_type, | ||
186 | }; | ||
187 | |||
188 | static int __devinit timbgpio_probe(struct platform_device *pdev) | ||
189 | { | ||
190 | int err, i; | ||
191 | struct gpio_chip *gc; | ||
192 | struct timbgpio *tgpio; | ||
193 | struct resource *iomem; | ||
194 | struct timbgpio_platform_data *pdata = pdev->dev.platform_data; | ||
195 | int irq = platform_get_irq(pdev, 0); | ||
196 | |||
197 | if (!pdata || pdata->nr_pins > 32) { | ||
198 | err = -EINVAL; | ||
199 | goto err_mem; | ||
200 | } | ||
201 | |||
202 | iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
203 | if (!iomem) { | ||
204 | err = -EINVAL; | ||
205 | goto err_mem; | ||
206 | } | ||
207 | |||
208 | tgpio = kzalloc(sizeof(*tgpio), GFP_KERNEL); | ||
209 | if (!tgpio) { | ||
210 | err = -EINVAL; | ||
211 | goto err_mem; | ||
212 | } | ||
213 | tgpio->irq_base = pdata->irq_base; | ||
214 | |||
215 | spin_lock_init(&tgpio->lock); | ||
216 | |||
217 | if (!request_mem_region(iomem->start, resource_size(iomem), | ||
218 | DRIVER_NAME)) { | ||
219 | err = -EBUSY; | ||
220 | goto err_request; | ||
221 | } | ||
222 | |||
223 | tgpio->membase = ioremap(iomem->start, resource_size(iomem)); | ||
224 | if (!tgpio->membase) { | ||
225 | err = -ENOMEM; | ||
226 | goto err_ioremap; | ||
227 | } | ||
228 | |||
229 | gc = &tgpio->gpio; | ||
230 | |||
231 | gc->label = dev_name(&pdev->dev); | ||
232 | gc->owner = THIS_MODULE; | ||
233 | gc->dev = &pdev->dev; | ||
234 | gc->direction_input = timbgpio_gpio_direction_input; | ||
235 | gc->get = timbgpio_gpio_get; | ||
236 | gc->direction_output = timbgpio_gpio_direction_output; | ||
237 | gc->set = timbgpio_gpio_set; | ||
238 | gc->to_irq = (irq >= 0 && tgpio->irq_base > 0) ? timbgpio_to_irq : NULL; | ||
239 | gc->dbg_show = NULL; | ||
240 | gc->base = pdata->gpio_base; | ||
241 | gc->ngpio = pdata->nr_pins; | ||
242 | gc->can_sleep = 0; | ||
243 | |||
244 | err = gpiochip_add(gc); | ||
245 | if (err) | ||
246 | goto err_chipadd; | ||
247 | |||
248 | platform_set_drvdata(pdev, tgpio); | ||
249 | |||
250 | /* make sure to disable interrupts */ | ||
251 | iowrite32(0x0, tgpio->membase + TGPIO_IER); | ||
252 | |||
253 | if (irq < 0 || tgpio->irq_base <= 0) | ||
254 | return 0; | ||
255 | |||
256 | for (i = 0; i < pdata->nr_pins; i++) { | ||
257 | set_irq_chip_and_handler_name(tgpio->irq_base + i, | ||
258 | &timbgpio_irqchip, handle_simple_irq, "mux"); | ||
259 | set_irq_chip_data(tgpio->irq_base + i, tgpio); | ||
260 | #ifdef CONFIG_ARM | ||
261 | set_irq_flags(tgpio->irq_base + i, IRQF_VALID | IRQF_PROBE); | ||
262 | #endif | ||
263 | } | ||
264 | |||
265 | set_irq_data(irq, tgpio); | ||
266 | set_irq_chained_handler(irq, timbgpio_irq); | ||
267 | |||
268 | return 0; | ||
269 | |||
270 | err_chipadd: | ||
271 | iounmap(tgpio->membase); | ||
272 | err_ioremap: | ||
273 | release_mem_region(iomem->start, resource_size(iomem)); | ||
274 | err_request: | ||
275 | kfree(tgpio); | ||
276 | err_mem: | ||
277 | printk(KERN_ERR DRIVER_NAME": Failed to register GPIOs: %d\n", err); | ||
278 | |||
279 | return err; | ||
280 | } | ||
281 | |||
282 | static int __devexit timbgpio_remove(struct platform_device *pdev) | ||
283 | { | ||
284 | int err; | ||
285 | struct timbgpio_platform_data *pdata = pdev->dev.platform_data; | ||
286 | struct timbgpio *tgpio = platform_get_drvdata(pdev); | ||
287 | struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
288 | int irq = platform_get_irq(pdev, 0); | ||
289 | |||
290 | if (irq >= 0 && tgpio->irq_base > 0) { | ||
291 | int i; | ||
292 | for (i = 0; i < pdata->nr_pins; i++) { | ||
293 | set_irq_chip(tgpio->irq_base + i, NULL); | ||
294 | set_irq_chip_data(tgpio->irq_base + i, NULL); | ||
295 | } | ||
296 | |||
297 | set_irq_handler(irq, NULL); | ||
298 | set_irq_data(irq, NULL); | ||
299 | } | ||
300 | |||
301 | err = gpiochip_remove(&tgpio->gpio); | ||
302 | if (err) | ||
303 | printk(KERN_ERR DRIVER_NAME": failed to remove gpio_chip\n"); | ||
304 | |||
305 | iounmap(tgpio->membase); | ||
306 | release_mem_region(iomem->start, resource_size(iomem)); | ||
307 | kfree(tgpio); | ||
308 | |||
309 | platform_set_drvdata(pdev, NULL); | ||
310 | |||
311 | return 0; | ||
312 | } | ||
313 | |||
314 | static struct platform_driver timbgpio_platform_driver = { | ||
315 | .driver = { | ||
316 | .name = DRIVER_NAME, | ||
317 | .owner = THIS_MODULE, | ||
318 | }, | ||
319 | .probe = timbgpio_probe, | ||
320 | .remove = timbgpio_remove, | ||
321 | }; | ||
322 | |||
323 | /*--------------------------------------------------------------------------*/ | ||
324 | |||
325 | static int __init timbgpio_init(void) | ||
326 | { | ||
327 | return platform_driver_register(&timbgpio_platform_driver); | ||
328 | } | ||
329 | |||
330 | static void __exit timbgpio_exit(void) | ||
331 | { | ||
332 | platform_driver_unregister(&timbgpio_platform_driver); | ||
333 | } | ||
334 | |||
335 | module_init(timbgpio_init); | ||
336 | module_exit(timbgpio_exit); | ||
337 | |||
338 | MODULE_DESCRIPTION("Timberdale GPIO driver"); | ||
339 | MODULE_LICENSE("GPL v2"); | ||
340 | MODULE_AUTHOR("Mocean Laboratories"); | ||
341 | MODULE_ALIAS("platform:"DRIVER_NAME); | ||
342 | |||
diff --git a/include/linux/timb_gpio.h b/include/linux/timb_gpio.h new file mode 100644 index 000000000000..ce456eaae861 --- /dev/null +++ b/include/linux/timb_gpio.h | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * timb_gpio.h timberdale FPGA GPIO driver, platform data definition | ||
3 | * Copyright (c) 2009 Intel Corporation | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
17 | */ | ||
18 | |||
19 | #ifndef _LINUX_TIMB_GPIO_H | ||
20 | #define _LINUX_TIMB_GPIO_H | ||
21 | |||
22 | /** | ||
23 | * struct timbgpio_platform_data - Platform data of the Timberdale GPIO driver | ||
24 | * @gpio_base The number of the first GPIO pin, set to -1 for | ||
25 | * dynamic number allocation. | ||
26 | * @nr_pins Number of pins that is supported by the hardware (1-32) | ||
27 | * @irq_base If IRQ is supported by the hardware, this is the base | ||
28 | * number of IRQ:s. One IRQ per pin will be used. Set to | ||
29 | * -1 if IRQ:s is not supported. | ||
30 | */ | ||
31 | struct timbgpio_platform_data { | ||
32 | int gpio_base; | ||
33 | int nr_pins; | ||
34 | int irq_base; | ||
35 | }; | ||
36 | |||
37 | #endif | ||