diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2008-04-21 18:40:55 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-04-21 18:40:55 -0400 |
commit | 85b375a613085b78531ec86369a51c2f3b922f95 (patch) | |
tree | 716437d598de92bbd7acaf24622e9a7d74fc209a /drivers/mfd | |
parent | ec965350bb98bd291eb34f6ecddfdcfc36da1e6e (diff) | |
parent | cf816ecb533ab96b883dfdc0db174598b5b5c4d2 (diff) |
Merge branch 'for-linus' of master.kernel.org:/home/rmk/linux-2.6-arm
* 'for-linus' of master.kernel.org:/home/rmk/linux-2.6-arm: (212 commits)
[ARM] pxa: Phycore pcm-990-specific code for the PXA270 Quick Capture driver
[ARM] pxa: V4L2 soc_camera driver for PXA270
[ARM] pxa: restrict availability of pxa2xx PCMCIA drivers
[ARM] 5005/1: BAST: Fix kset_name initialiser
[ARM] 4967/1: Adds functions to set clkout rate for Samsung S3C2410
[ARM] 4988/1: Add GPIO lib support to the EP93xx
[ARM] Add initial sparsemem support
[ARM] pxa: initialise PXA devices before platform init code
[ARM] 5002/1: tosa: add two more leds
[ARM] 5004/1: Tosa: make several unreferenced structures static.
[ARM] 5003/1: Shut up sparse warnings
[ARM] 4977/2: soc - pxa2xx-ac97 - Add missing clk_enable()
[ARM] 4976/1: zylonite: Configure GPIO for WM9713 IRQ line
[ARM] 4974/1: Drop unused leds-tosa.
[ARM] 4973/1: Tosa: use leds-gpio driver.
[ARM] 4972/1: Tosa: convert scoop GPIOs usage to generic gpio code
[ARM] 4971/1: pxaficp_ir: provide startup and shutdown hooks
[ARM] pxa: lubbock: move mis-placed SPI info
[ARM] 4970/1: tosa: correct gpio used for wake up.
[ARM] 4966/1: magician: add MFP pin configuration
...
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 16 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 3 | ||||
-rw-r--r-- | drivers/mfd/htc-egpio.c | 440 | ||||
-rw-r--r-- | drivers/mfd/htc-pasic3.c | 265 |
4 files changed, 724 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0c886c882385..2566479937c9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -22,6 +22,22 @@ 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 | |||
33 | config HTC_PASIC3 | ||
34 | tristate "HTC PASIC3 LED/DS1WM chip support" | ||
35 | help | ||
36 | This core driver provides register access for the LED/DS1WM | ||
37 | chips labeled "AIC2" and "AIC3", found on HTC Blueangel and | ||
38 | HTC Magician devices, respectively. Actual functionality is | ||
39 | handled by the leds-pasic3 and ds1wm drivers. | ||
40 | |||
25 | endmenu | 41 | endmenu |
26 | 42 | ||
27 | menu "Multimedia Capabilities Port drivers" | 43 | menu "Multimedia Capabilities Port drivers" |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 521cd5cb68af..eef4e26807df 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -5,6 +5,9 @@ | |||
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 | obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o | ||
10 | |||
8 | obj-$(CONFIG_MCP) += mcp-core.o | 11 | obj-$(CONFIG_MCP) += mcp-core.o |
9 | obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o | 12 | obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o |
10 | obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o | 13 | 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>"); | ||
diff --git a/drivers/mfd/htc-pasic3.c b/drivers/mfd/htc-pasic3.c new file mode 100644 index 000000000000..af66f4f28300 --- /dev/null +++ b/drivers/mfd/htc-pasic3.c | |||
@@ -0,0 +1,265 @@ | |||
1 | /* | ||
2 | * Core driver for HTC PASIC3 LED/DS1WM chip. | ||
3 | * | ||
4 | * Copyright (C) 2006 Philipp Zabel <philipp.zabel@gmail.com> | ||
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 as published by | ||
8 | * the Free Software Foundation; version 2 of the License. | ||
9 | */ | ||
10 | |||
11 | #include <linux/init.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/platform_device.h> | ||
14 | |||
15 | #include <linux/ds1wm.h> | ||
16 | #include <linux/gpio.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/irq.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/mfd/htc-pasic3.h> | ||
21 | |||
22 | #include <asm/arch/pxa-regs.h> | ||
23 | |||
24 | struct pasic3_data { | ||
25 | void __iomem *mapping; | ||
26 | unsigned int bus_shift; | ||
27 | struct platform_device *ds1wm_pdev; | ||
28 | struct platform_device *led_pdev; | ||
29 | }; | ||
30 | |||
31 | #define REG_ADDR 5 | ||
32 | #define REG_DATA 6 | ||
33 | #define NUM_REGS 7 | ||
34 | |||
35 | #define READ_MODE 0x80 | ||
36 | |||
37 | /* | ||
38 | * write to a secondary register on the PASIC3 | ||
39 | */ | ||
40 | void pasic3_write_register(struct device *dev, u32 reg, u8 val) | ||
41 | { | ||
42 | struct pasic3_data *asic = dev->driver_data; | ||
43 | int bus_shift = asic->bus_shift; | ||
44 | void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); | ||
45 | void __iomem *data = asic->mapping + (REG_DATA << bus_shift); | ||
46 | |||
47 | __raw_writeb(~READ_MODE & reg, addr); | ||
48 | __raw_writeb(val, data); | ||
49 | } | ||
50 | EXPORT_SYMBOL(pasic3_write_register); /* for leds-pasic3 */ | ||
51 | |||
52 | /* | ||
53 | * read from a secondary register on the PASIC3 | ||
54 | */ | ||
55 | u8 pasic3_read_register(struct device *dev, u32 reg) | ||
56 | { | ||
57 | struct pasic3_data *asic = dev->driver_data; | ||
58 | int bus_shift = asic->bus_shift; | ||
59 | void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); | ||
60 | void __iomem *data = asic->mapping + (REG_DATA << bus_shift); | ||
61 | |||
62 | __raw_writeb(READ_MODE | reg, addr); | ||
63 | return __raw_readb(data); | ||
64 | } | ||
65 | EXPORT_SYMBOL(pasic3_read_register); /* for leds-pasic3 */ | ||
66 | |||
67 | /* | ||
68 | * LEDs | ||
69 | */ | ||
70 | |||
71 | static int led_device_add(struct device *pasic3_dev, | ||
72 | const struct pasic3_leds_machinfo *pdata) | ||
73 | { | ||
74 | struct pasic3_data *asic = pasic3_dev->driver_data; | ||
75 | struct platform_device *pdev; | ||
76 | int ret; | ||
77 | |||
78 | pdev = platform_device_alloc("pasic3-led", -1); | ||
79 | if (!pdev) { | ||
80 | dev_dbg(pasic3_dev, "failed to allocate LED platform device\n"); | ||
81 | return -ENOMEM; | ||
82 | } | ||
83 | |||
84 | ret = platform_device_add_data(pdev, pdata, | ||
85 | sizeof(struct pasic3_leds_machinfo)); | ||
86 | if (ret < 0) { | ||
87 | dev_dbg(pasic3_dev, "failed to add LED platform data\n"); | ||
88 | goto exit_pdev_put; | ||
89 | } | ||
90 | |||
91 | pdev->dev.parent = pasic3_dev; | ||
92 | ret = platform_device_add(pdev); | ||
93 | if (ret < 0) { | ||
94 | dev_dbg(pasic3_dev, "failed to add LED platform device\n"); | ||
95 | goto exit_pdev_put; | ||
96 | } | ||
97 | |||
98 | asic->led_pdev = pdev; | ||
99 | return 0; | ||
100 | |||
101 | exit_pdev_put: | ||
102 | platform_device_put(pdev); | ||
103 | return ret; | ||
104 | } | ||
105 | |||
106 | /* | ||
107 | * DS1WM | ||
108 | */ | ||
109 | |||
110 | static void ds1wm_enable(struct platform_device *pdev) | ||
111 | { | ||
112 | struct device *dev = pdev->dev.parent; | ||
113 | int c; | ||
114 | |||
115 | c = pasic3_read_register(dev, 0x28); | ||
116 | pasic3_write_register(dev, 0x28, c & 0x7f); | ||
117 | |||
118 | dev_dbg(dev, "DS1WM OWM_EN low (active) %02x\n", c & 0x7f); | ||
119 | } | ||
120 | |||
121 | static void ds1wm_disable(struct platform_device *pdev) | ||
122 | { | ||
123 | struct device *dev = pdev->dev.parent; | ||
124 | int c; | ||
125 | |||
126 | c = pasic3_read_register(dev, 0x28); | ||
127 | pasic3_write_register(dev, 0x28, c | 0x80); | ||
128 | |||
129 | dev_dbg(dev, "DS1WM OWM_EN high (inactive) %02x\n", c | 0x80); | ||
130 | } | ||
131 | |||
132 | static struct ds1wm_platform_data ds1wm_pdata = { | ||
133 | .bus_shift = 2, | ||
134 | .enable = ds1wm_enable, | ||
135 | .disable = ds1wm_disable, | ||
136 | }; | ||
137 | |||
138 | static int ds1wm_device_add(struct device *pasic3_dev, int bus_shift) | ||
139 | { | ||
140 | struct pasic3_data *asic = pasic3_dev->driver_data; | ||
141 | struct platform_device *pdev; | ||
142 | int ret; | ||
143 | |||
144 | pdev = platform_device_alloc("ds1wm", -1); | ||
145 | if (!pdev) { | ||
146 | dev_dbg(pasic3_dev, "failed to allocate DS1WM platform device\n"); | ||
147 | return -ENOMEM; | ||
148 | } | ||
149 | |||
150 | ret = platform_device_add_resources(pdev, pdev->resource, | ||
151 | pdev->num_resources); | ||
152 | if (ret < 0) { | ||
153 | dev_dbg(pasic3_dev, "failed to add DS1WM resources\n"); | ||
154 | goto exit_pdev_put; | ||
155 | } | ||
156 | |||
157 | ds1wm_pdata.bus_shift = asic->bus_shift; | ||
158 | ret = platform_device_add_data(pdev, &ds1wm_pdata, | ||
159 | sizeof(struct ds1wm_platform_data)); | ||
160 | if (ret < 0) { | ||
161 | dev_dbg(pasic3_dev, "failed to add DS1WM platform data\n"); | ||
162 | goto exit_pdev_put; | ||
163 | } | ||
164 | |||
165 | pdev->dev.parent = pasic3_dev; | ||
166 | ret = platform_device_add(pdev); | ||
167 | if (ret < 0) { | ||
168 | dev_dbg(pasic3_dev, "failed to add DS1WM platform device\n"); | ||
169 | goto exit_pdev_put; | ||
170 | } | ||
171 | |||
172 | asic->ds1wm_pdev = pdev; | ||
173 | return 0; | ||
174 | |||
175 | exit_pdev_put: | ||
176 | platform_device_put(pdev); | ||
177 | return ret; | ||
178 | } | ||
179 | |||
180 | static int __init pasic3_probe(struct platform_device *pdev) | ||
181 | { | ||
182 | struct pasic3_platform_data *pdata = pdev->dev.platform_data; | ||
183 | struct device *dev = &pdev->dev; | ||
184 | struct pasic3_data *asic; | ||
185 | struct resource *r; | ||
186 | int ret; | ||
187 | |||
188 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
189 | if (!r) | ||
190 | return -ENXIO; | ||
191 | |||
192 | if (!request_mem_region(r->start, r->end - r->start + 1, "pasic3")) | ||
193 | return -EBUSY; | ||
194 | |||
195 | asic = kzalloc(sizeof(struct pasic3_data), GFP_KERNEL); | ||
196 | if (!asic) | ||
197 | return -ENOMEM; | ||
198 | |||
199 | platform_set_drvdata(pdev, asic); | ||
200 | |||
201 | if (pdata && pdata->bus_shift) | ||
202 | asic->bus_shift = pdata->bus_shift; | ||
203 | else | ||
204 | asic->bus_shift = 2; | ||
205 | |||
206 | asic->mapping = ioremap(r->start, r->end - r->start + 1); | ||
207 | if (!asic->mapping) { | ||
208 | dev_err(dev, "couldn't ioremap PASIC3\n"); | ||
209 | kfree(asic); | ||
210 | return -ENOMEM; | ||
211 | } | ||
212 | |||
213 | ret = ds1wm_device_add(dev, asic->bus_shift); | ||
214 | if (ret < 0) | ||
215 | dev_warn(dev, "failed to register DS1WM\n"); | ||
216 | |||
217 | if (pdata->led_pdata) { | ||
218 | ret = led_device_add(dev, pdata->led_pdata); | ||
219 | if (ret < 0) | ||
220 | dev_warn(dev, "failed to register LED device\n"); | ||
221 | } | ||
222 | |||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | static int pasic3_remove(struct platform_device *pdev) | ||
227 | { | ||
228 | struct pasic3_data *asic = platform_get_drvdata(pdev); | ||
229 | struct resource *r; | ||
230 | |||
231 | if (asic->led_pdev) | ||
232 | platform_device_unregister(asic->led_pdev); | ||
233 | if (asic->ds1wm_pdev) | ||
234 | platform_device_unregister(asic->ds1wm_pdev); | ||
235 | |||
236 | iounmap(asic->mapping); | ||
237 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
238 | release_mem_region(r->start, r->end - r->start + 1); | ||
239 | kfree(asic); | ||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | static struct platform_driver pasic3_driver = { | ||
244 | .driver = { | ||
245 | .name = "pasic3", | ||
246 | }, | ||
247 | .remove = pasic3_remove, | ||
248 | }; | ||
249 | |||
250 | static int __init pasic3_base_init(void) | ||
251 | { | ||
252 | return platform_driver_probe(&pasic3_driver, pasic3_probe); | ||
253 | } | ||
254 | |||
255 | static void __exit pasic3_base_exit(void) | ||
256 | { | ||
257 | platform_driver_unregister(&pasic3_driver); | ||
258 | } | ||
259 | |||
260 | module_init(pasic3_base_init); | ||
261 | module_exit(pasic3_base_exit); | ||
262 | |||
263 | MODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>"); | ||
264 | MODULE_DESCRIPTION("Core driver for HTC PASIC3"); | ||
265 | MODULE_LICENSE("GPL"); | ||