diff options
author | Philipp Zabel <philipp.zabel@gmail.com> | 2008-04-09 14:20:34 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2008-04-19 06:29:07 -0400 |
commit | a1635b8fe59de2c5223cda5ca8397b875c901904 (patch) | |
tree | 047db24bdba7da410937e1f8da821d30e9052a9f /drivers | |
parent | e6816f34e4ee237fc1b54e0357e9ea31462ad411 (diff) |
[ARM] 4947/1: htc-egpio, a driver for GPIO/IRQ expanders with fixed input/output pins
implemented in CPLD chips on several HTC devices.
The original driver was written by Kevin O'Connor, I have adapted it to
use gpiolib and made the bus/register widths configurable.
Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/Kconfig | 8 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 2 | ||||
-rw-r--r-- | drivers/mfd/htc-egpio.c | 440 |
3 files changed, 450 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0c886c882385..284b2dc03444 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -22,6 +22,14 @@ config MFD_ASIC3 | |||
22 | This driver supports the ASIC3 multifunction chip found on many | 22 | This driver supports the ASIC3 multifunction chip found on many |
23 | PDAs (mainly iPAQ and HTC based ones) | 23 | PDAs (mainly iPAQ and HTC based ones) |
24 | 24 | ||
25 | config HTC_EGPIO | ||
26 | bool "HTC EGPIO support" | ||
27 | depends on GENERIC_HARDIRQS && HAVE_GPIO_LIB | ||
28 | help | ||
29 | This driver supports the CPLD egpio chip present on | ||
30 | several HTC phones. It provides basic support for input | ||
31 | pins, output pins, and irqs. | ||
32 | |||
25 | endmenu | 33 | endmenu |
26 | 34 | ||
27 | menu "Multimedia Capabilities Port drivers" | 35 | menu "Multimedia Capabilities Port drivers" |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 521cd5cb68af..f81f8d2e2c03 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -5,6 +5,8 @@ | |||
5 | obj-$(CONFIG_MFD_SM501) += sm501.o | 5 | obj-$(CONFIG_MFD_SM501) += sm501.o |
6 | obj-$(CONFIG_MFD_ASIC3) += asic3.o | 6 | obj-$(CONFIG_MFD_ASIC3) += asic3.o |
7 | 7 | ||
8 | obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o | ||
9 | |||
8 | obj-$(CONFIG_MCP) += mcp-core.o | 10 | obj-$(CONFIG_MCP) += mcp-core.o |
9 | obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o | 11 | obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o |
10 | obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o | 12 | obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o |
diff --git a/drivers/mfd/htc-egpio.c b/drivers/mfd/htc-egpio.c new file mode 100644 index 000000000000..8872cc077519 --- /dev/null +++ b/drivers/mfd/htc-egpio.c | |||
@@ -0,0 +1,440 @@ | |||
1 | /* | ||
2 | * Support for the GPIO/IRQ expander chips present on several HTC phones. | ||
3 | * These are implemented in CPLD chips present on the board. | ||
4 | * | ||
5 | * Copyright (c) 2007 Kevin O'Connor <kevin@koconnor.net> | ||
6 | * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com> | ||
7 | * | ||
8 | * This file may be distributed under the terms of the GNU GPL license. | ||
9 | */ | ||
10 | |||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/errno.h> | ||
13 | #include <linux/interrupt.h> | ||
14 | #include <linux/irq.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/spinlock.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/mfd/htc-egpio.h> | ||
20 | |||
21 | struct egpio_chip { | ||
22 | int reg_start; | ||
23 | int cached_values; | ||
24 | unsigned long is_out; | ||
25 | struct device *dev; | ||
26 | struct gpio_chip chip; | ||
27 | }; | ||
28 | |||
29 | struct egpio_info { | ||
30 | spinlock_t lock; | ||
31 | |||
32 | /* iomem info */ | ||
33 | void __iomem *base_addr; | ||
34 | int bus_shift; /* byte shift */ | ||
35 | int reg_shift; /* bit shift */ | ||
36 | int reg_mask; | ||
37 | |||
38 | /* irq info */ | ||
39 | int ack_register; | ||
40 | int ack_write; | ||
41 | u16 irqs_enabled; | ||
42 | uint irq_start; | ||
43 | int nirqs; | ||
44 | uint chained_irq; | ||
45 | |||
46 | /* egpio info */ | ||
47 | struct egpio_chip *chip; | ||
48 | int nchips; | ||
49 | }; | ||
50 | |||
51 | static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg) | ||
52 | { | ||
53 | writew(value, ei->base_addr + (reg << ei->bus_shift)); | ||
54 | } | ||
55 | |||
56 | static inline u16 egpio_readw(struct egpio_info *ei, int reg) | ||
57 | { | ||
58 | return readw(ei->base_addr + (reg << ei->bus_shift)); | ||
59 | } | ||
60 | |||
61 | /* | ||
62 | * IRQs | ||
63 | */ | ||
64 | |||
65 | static inline void ack_irqs(struct egpio_info *ei) | ||
66 | { | ||
67 | egpio_writew(ei->ack_write, ei, ei->ack_register); | ||
68 | pr_debug("EGPIO ack - write %x to base+%x\n", | ||
69 | ei->ack_write, ei->ack_register << ei->bus_shift); | ||
70 | } | ||
71 | |||
72 | static void egpio_ack(unsigned int irq) | ||
73 | { | ||
74 | } | ||
75 | |||
76 | /* There does not appear to be a way to proactively mask interrupts | ||
77 | * on the egpio chip itself. So, we simply ignore interrupts that | ||
78 | * aren't desired. */ | ||
79 | static void egpio_mask(unsigned int irq) | ||
80 | { | ||
81 | struct egpio_info *ei = get_irq_chip_data(irq); | ||
82 | ei->irqs_enabled &= ~(1 << (irq - ei->irq_start)); | ||
83 | pr_debug("EGPIO mask %d %04x\n", irq, ei->irqs_enabled); | ||
84 | } | ||
85 | static void egpio_unmask(unsigned int irq) | ||
86 | { | ||
87 | struct egpio_info *ei = get_irq_chip_data(irq); | ||
88 | ei->irqs_enabled |= 1 << (irq - ei->irq_start); | ||
89 | pr_debug("EGPIO unmask %d %04x\n", irq, ei->irqs_enabled); | ||
90 | } | ||
91 | |||
92 | static struct irq_chip egpio_muxed_chip = { | ||
93 | .name = "htc-egpio", | ||
94 | .ack = egpio_ack, | ||
95 | .mask = egpio_mask, | ||
96 | .unmask = egpio_unmask, | ||
97 | }; | ||
98 | |||
99 | static void egpio_handler(unsigned int irq, struct irq_desc *desc) | ||
100 | { | ||
101 | struct egpio_info *ei = get_irq_data(irq); | ||
102 | int irqpin; | ||
103 | |||
104 | /* Read current pins. */ | ||
105 | unsigned long readval = egpio_readw(ei, ei->ack_register); | ||
106 | pr_debug("IRQ reg: %x\n", (unsigned int)readval); | ||
107 | /* Ack/unmask interrupts. */ | ||
108 | ack_irqs(ei); | ||
109 | /* Process all set pins. */ | ||
110 | readval &= ei->irqs_enabled; | ||
111 | for_each_bit(irqpin, &readval, ei->nirqs) { | ||
112 | /* Run irq handler */ | ||
113 | pr_debug("got IRQ %d\n", irqpin); | ||
114 | irq = ei->irq_start + irqpin; | ||
115 | desc = &irq_desc[irq]; | ||
116 | desc->handle_irq(irq, desc); | ||
117 | } | ||
118 | } | ||
119 | |||
120 | int htc_egpio_get_wakeup_irq(struct device *dev) | ||
121 | { | ||
122 | struct egpio_info *ei = dev_get_drvdata(dev); | ||
123 | |||
124 | /* Read current pins. */ | ||
125 | u16 readval = egpio_readw(ei, ei->ack_register); | ||
126 | /* Ack/unmask interrupts. */ | ||
127 | ack_irqs(ei); | ||
128 | /* Return first set pin. */ | ||
129 | readval &= ei->irqs_enabled; | ||
130 | return ei->irq_start + ffs(readval) - 1; | ||
131 | } | ||
132 | EXPORT_SYMBOL(htc_egpio_get_wakeup_irq); | ||
133 | |||
134 | static inline int egpio_pos(struct egpio_info *ei, int bit) | ||
135 | { | ||
136 | return bit >> ei->reg_shift; | ||
137 | } | ||
138 | |||
139 | static inline int egpio_bit(struct egpio_info *ei, int bit) | ||
140 | { | ||
141 | return 1 << (bit & ((1 << ei->reg_shift)-1)); | ||
142 | } | ||
143 | |||
144 | /* | ||
145 | * Input pins | ||
146 | */ | ||
147 | |||
148 | static int egpio_get(struct gpio_chip *chip, unsigned offset) | ||
149 | { | ||
150 | struct egpio_chip *egpio; | ||
151 | struct egpio_info *ei; | ||
152 | unsigned bit; | ||
153 | int reg; | ||
154 | int value; | ||
155 | |||
156 | pr_debug("egpio_get_value(%d)\n", chip->base + offset); | ||
157 | |||
158 | egpio = container_of(chip, struct egpio_chip, chip); | ||
159 | ei = dev_get_drvdata(egpio->dev); | ||
160 | bit = egpio_bit(ei, offset); | ||
161 | reg = egpio->reg_start + egpio_pos(ei, offset); | ||
162 | |||
163 | value = egpio_readw(ei, reg); | ||
164 | pr_debug("readw(%p + %x) = %x\n", | ||
165 | ei->base_addr, reg << ei->bus_shift, value); | ||
166 | return value & bit; | ||
167 | } | ||
168 | |||
169 | static int egpio_direction_input(struct gpio_chip *chip, unsigned offset) | ||
170 | { | ||
171 | struct egpio_chip *egpio; | ||
172 | |||
173 | egpio = container_of(chip, struct egpio_chip, chip); | ||
174 | return test_bit(offset, &egpio->is_out) ? -EINVAL : 0; | ||
175 | } | ||
176 | |||
177 | |||
178 | /* | ||
179 | * Output pins | ||
180 | */ | ||
181 | |||
182 | static void egpio_set(struct gpio_chip *chip, unsigned offset, int value) | ||
183 | { | ||
184 | unsigned long flag; | ||
185 | struct egpio_chip *egpio; | ||
186 | struct egpio_info *ei; | ||
187 | unsigned bit; | ||
188 | int pos; | ||
189 | int reg; | ||
190 | int shift; | ||
191 | |||
192 | pr_debug("egpio_set(%s, %d(%d), %d)\n", | ||
193 | chip->label, offset, offset+chip->base, value); | ||
194 | |||
195 | egpio = container_of(chip, struct egpio_chip, chip); | ||
196 | ei = dev_get_drvdata(egpio->dev); | ||
197 | bit = egpio_bit(ei, offset); | ||
198 | pos = egpio_pos(ei, offset); | ||
199 | reg = egpio->reg_start + pos; | ||
200 | shift = pos << ei->reg_shift; | ||
201 | |||
202 | pr_debug("egpio %s: reg %d = 0x%04x\n", value ? "set" : "clear", | ||
203 | reg, (egpio->cached_values >> shift) & ei->reg_mask); | ||
204 | |||
205 | spin_lock_irqsave(&ei->lock, flag); | ||
206 | if (value) | ||
207 | egpio->cached_values |= (1 << offset); | ||
208 | else | ||
209 | egpio->cached_values &= ~(1 << offset); | ||
210 | egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg); | ||
211 | spin_unlock_irqrestore(&ei->lock, flag); | ||
212 | } | ||
213 | |||
214 | static int egpio_direction_output(struct gpio_chip *chip, | ||
215 | unsigned offset, int value) | ||
216 | { | ||
217 | struct egpio_chip *egpio; | ||
218 | |||
219 | egpio = container_of(chip, struct egpio_chip, chip); | ||
220 | if (test_bit(offset, &egpio->is_out)) { | ||
221 | egpio_set(chip, offset, value); | ||
222 | return 0; | ||
223 | } else { | ||
224 | return -EINVAL; | ||
225 | } | ||
226 | } | ||
227 | |||
228 | static void egpio_write_cache(struct egpio_info *ei) | ||
229 | { | ||
230 | int i; | ||
231 | struct egpio_chip *egpio; | ||
232 | int shift; | ||
233 | |||
234 | for (i = 0; i < ei->nchips; i++) { | ||
235 | egpio = &(ei->chip[i]); | ||
236 | if (!egpio->is_out) | ||
237 | continue; | ||
238 | |||
239 | for (shift = 0; shift < egpio->chip.ngpio; | ||
240 | shift += (1<<ei->reg_shift)) { | ||
241 | |||
242 | int reg = egpio->reg_start + egpio_pos(ei, shift); | ||
243 | |||
244 | if (!((egpio->is_out >> shift) & ei->reg_mask)) | ||
245 | continue; | ||
246 | |||
247 | pr_debug("EGPIO: setting %x to %x, was %x\n", reg, | ||
248 | (egpio->cached_values >> shift) & ei->reg_mask, | ||
249 | egpio_readw(ei, reg)); | ||
250 | |||
251 | egpio_writew((egpio->cached_values >> shift) | ||
252 | & ei->reg_mask, ei, reg); | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | |||
258 | /* | ||
259 | * Setup | ||
260 | */ | ||
261 | |||
262 | static int __init egpio_probe(struct platform_device *pdev) | ||
263 | { | ||
264 | struct htc_egpio_platform_data *pdata = pdev->dev.platform_data; | ||
265 | struct resource *res; | ||
266 | struct egpio_info *ei; | ||
267 | struct gpio_chip *chip; | ||
268 | unsigned int irq, irq_end; | ||
269 | int i; | ||
270 | int ret; | ||
271 | |||
272 | /* Initialize ei data structure. */ | ||
273 | ei = kzalloc(sizeof(*ei), GFP_KERNEL); | ||
274 | if (!ei) | ||
275 | return -ENOMEM; | ||
276 | |||
277 | spin_lock_init(&ei->lock); | ||
278 | |||
279 | /* Find chained irq */ | ||
280 | ret = -EINVAL; | ||
281 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
282 | if (res) | ||
283 | ei->chained_irq = res->start; | ||
284 | |||
285 | /* Map egpio chip into virtual address space. */ | ||
286 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
287 | if (!res) | ||
288 | goto fail; | ||
289 | ei->base_addr = ioremap_nocache(res->start, res->end - res->start); | ||
290 | if (!ei->base_addr) | ||
291 | goto fail; | ||
292 | pr_debug("EGPIO phys=%08x virt=%p\n", res->start, ei->base_addr); | ||
293 | |||
294 | if ((pdata->bus_width != 16) && (pdata->bus_width != 32)) | ||
295 | goto fail; | ||
296 | ei->bus_shift = fls(pdata->bus_width - 1) - 3; | ||
297 | pr_debug("bus_shift = %d\n", ei->bus_shift); | ||
298 | |||
299 | if ((pdata->reg_width != 8) && (pdata->reg_width != 16)) | ||
300 | goto fail; | ||
301 | ei->reg_shift = fls(pdata->reg_width - 1); | ||
302 | pr_debug("reg_shift = %d\n", ei->reg_shift); | ||
303 | |||
304 | ei->reg_mask = (1 << pdata->reg_width) - 1; | ||
305 | |||
306 | platform_set_drvdata(pdev, ei); | ||
307 | |||
308 | ei->nchips = pdata->num_chips; | ||
309 | ei->chip = kzalloc(sizeof(struct egpio_chip) * ei->nchips, GFP_KERNEL); | ||
310 | if (!ei) { | ||
311 | ret = -ENOMEM; | ||
312 | goto fail; | ||
313 | } | ||
314 | for (i = 0; i < ei->nchips; i++) { | ||
315 | ei->chip[i].reg_start = pdata->chip[i].reg_start; | ||
316 | ei->chip[i].cached_values = pdata->chip[i].initial_values; | ||
317 | ei->chip[i].is_out = pdata->chip[i].direction; | ||
318 | ei->chip[i].dev = &(pdev->dev); | ||
319 | chip = &(ei->chip[i].chip); | ||
320 | chip->label = "htc-egpio"; | ||
321 | chip->get = egpio_get; | ||
322 | chip->set = egpio_set; | ||
323 | chip->direction_input = egpio_direction_input; | ||
324 | chip->direction_output = egpio_direction_output; | ||
325 | chip->base = pdata->chip[i].gpio_base; | ||
326 | chip->ngpio = pdata->chip[i].num_gpios; | ||
327 | |||
328 | gpiochip_add(chip); | ||
329 | } | ||
330 | |||
331 | /* Set initial pin values */ | ||
332 | egpio_write_cache(ei); | ||
333 | |||
334 | ei->irq_start = pdata->irq_base; | ||
335 | ei->nirqs = pdata->num_irqs; | ||
336 | ei->ack_register = pdata->ack_register; | ||
337 | |||
338 | if (ei->chained_irq) { | ||
339 | /* Setup irq handlers */ | ||
340 | ei->ack_write = 0xFFFF; | ||
341 | if (pdata->invert_acks) | ||
342 | ei->ack_write = 0; | ||
343 | irq_end = ei->irq_start + ei->nirqs; | ||
344 | for (irq = ei->irq_start; irq < irq_end; irq++) { | ||
345 | set_irq_chip(irq, &egpio_muxed_chip); | ||
346 | set_irq_chip_data(irq, ei); | ||
347 | set_irq_handler(irq, handle_simple_irq); | ||
348 | set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); | ||
349 | } | ||
350 | set_irq_type(ei->chained_irq, IRQ_TYPE_EDGE_RISING); | ||
351 | set_irq_data(ei->chained_irq, ei); | ||
352 | set_irq_chained_handler(ei->chained_irq, egpio_handler); | ||
353 | ack_irqs(ei); | ||
354 | |||
355 | device_init_wakeup(&pdev->dev, 1); | ||
356 | } | ||
357 | |||
358 | return 0; | ||
359 | |||
360 | fail: | ||
361 | printk(KERN_ERR "EGPIO failed to setup\n"); | ||
362 | kfree(ei); | ||
363 | return ret; | ||
364 | } | ||
365 | |||
366 | static int __exit egpio_remove(struct platform_device *pdev) | ||
367 | { | ||
368 | struct egpio_info *ei = platform_get_drvdata(pdev); | ||
369 | unsigned int irq, irq_end; | ||
370 | |||
371 | if (ei->chained_irq) { | ||
372 | irq_end = ei->irq_start + ei->nirqs; | ||
373 | for (irq = ei->irq_start; irq < irq_end; irq++) { | ||
374 | set_irq_chip(irq, NULL); | ||
375 | set_irq_handler(irq, NULL); | ||
376 | set_irq_flags(irq, 0); | ||
377 | } | ||
378 | set_irq_chained_handler(ei->chained_irq, NULL); | ||
379 | device_init_wakeup(&pdev->dev, 0); | ||
380 | } | ||
381 | iounmap(ei->base_addr); | ||
382 | kfree(ei->chip); | ||
383 | kfree(ei); | ||
384 | |||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | #ifdef CONFIG_PM | ||
389 | static int egpio_suspend(struct platform_device *pdev, pm_message_t state) | ||
390 | { | ||
391 | struct egpio_info *ei = platform_get_drvdata(pdev); | ||
392 | |||
393 | if (ei->chained_irq && device_may_wakeup(&pdev->dev)) | ||
394 | enable_irq_wake(ei->chained_irq); | ||
395 | return 0; | ||
396 | } | ||
397 | |||
398 | static int egpio_resume(struct platform_device *pdev) | ||
399 | { | ||
400 | struct egpio_info *ei = platform_get_drvdata(pdev); | ||
401 | |||
402 | if (ei->chained_irq && device_may_wakeup(&pdev->dev)) | ||
403 | disable_irq_wake(ei->chained_irq); | ||
404 | |||
405 | /* Update registers from the cache, in case | ||
406 | the CPLD was powered off during suspend */ | ||
407 | egpio_write_cache(ei); | ||
408 | return 0; | ||
409 | } | ||
410 | #else | ||
411 | #define egpio_suspend NULL | ||
412 | #define egpio_resume NULL | ||
413 | #endif | ||
414 | |||
415 | |||
416 | static struct platform_driver egpio_driver = { | ||
417 | .driver = { | ||
418 | .name = "htc-egpio", | ||
419 | }, | ||
420 | .remove = __exit_p(egpio_remove), | ||
421 | .suspend = egpio_suspend, | ||
422 | .resume = egpio_resume, | ||
423 | }; | ||
424 | |||
425 | static int __init egpio_init(void) | ||
426 | { | ||
427 | return platform_driver_probe(&egpio_driver, egpio_probe); | ||
428 | } | ||
429 | |||
430 | static void __exit egpio_exit(void) | ||
431 | { | ||
432 | platform_driver_unregister(&egpio_driver); | ||
433 | } | ||
434 | |||
435 | /* start early for dependencies */ | ||
436 | subsys_initcall(egpio_init); | ||
437 | module_exit(egpio_exit) | ||
438 | |||
439 | MODULE_LICENSE("GPL"); | ||
440 | MODULE_AUTHOR("Kevin O'Connor <kevin@koconnor.net>"); | ||