Commit c26448c48448266480e1b6c371f897167060ceaf
Committed by
Samuel Ortiz
1 parent
39368eda96
Exists in
master
and in
7 other branches
mfd: Add basic tps6586x interrupt support
Add support for enabling and disabling tps6586x subdevice interrupts Signed-off-by: Gary King <gking@nvidia.com> Acked-by: Mike Rapoport <mike@compulab.co.il> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Showing 3 changed files with 233 additions and 2 deletions Side-by-side Diff
drivers/mfd/Kconfig
... | ... | @@ -563,8 +563,8 @@ |
563 | 563 | This driver is necessary for jz4740-battery and jz4740-hwmon driver. |
564 | 564 | |
565 | 565 | config MFD_TPS6586X |
566 | - tristate "TPS6586x Power Management chips" | |
567 | - depends on I2C && GPIOLIB | |
566 | + bool "TPS6586x Power Management chips" | |
567 | + depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS | |
568 | 568 | select MFD_CORE |
569 | 569 | help |
570 | 570 | If you say yes here you get support for the TPS6586X series of |
drivers/mfd/tps6586x.c
... | ... | @@ -15,6 +15,8 @@ |
15 | 15 | * published by the Free Software Foundation. |
16 | 16 | */ |
17 | 17 | |
18 | +#include <linux/interrupt.h> | |
19 | +#include <linux/irq.h> | |
18 | 20 | #include <linux/kernel.h> |
19 | 21 | #include <linux/module.h> |
20 | 22 | #include <linux/mutex.h> |
21 | 23 | |
22 | 24 | |
... | ... | @@ -29,16 +31,76 @@ |
29 | 31 | #define TPS6586X_GPIOSET1 0x5d |
30 | 32 | #define TPS6586X_GPIOSET2 0x5e |
31 | 33 | |
34 | +/* interrupt control registers */ | |
35 | +#define TPS6586X_INT_ACK1 0xb5 | |
36 | +#define TPS6586X_INT_ACK2 0xb6 | |
37 | +#define TPS6586X_INT_ACK3 0xb7 | |
38 | +#define TPS6586X_INT_ACK4 0xb8 | |
39 | + | |
40 | +/* interrupt mask registers */ | |
41 | +#define TPS6586X_INT_MASK1 0xb0 | |
42 | +#define TPS6586X_INT_MASK2 0xb1 | |
43 | +#define TPS6586X_INT_MASK3 0xb2 | |
44 | +#define TPS6586X_INT_MASK4 0xb3 | |
45 | +#define TPS6586X_INT_MASK5 0xb4 | |
46 | + | |
32 | 47 | /* device id */ |
33 | 48 | #define TPS6586X_VERSIONCRC 0xcd |
34 | 49 | #define TPS658621A_VERSIONCRC 0x15 |
35 | 50 | |
51 | +struct tps6586x_irq_data { | |
52 | + u8 mask_reg; | |
53 | + u8 mask_mask; | |
54 | +}; | |
55 | + | |
56 | +#define TPS6586X_IRQ(_reg, _mask) \ | |
57 | + { \ | |
58 | + .mask_reg = (_reg) - TPS6586X_INT_MASK1, \ | |
59 | + .mask_mask = (_mask), \ | |
60 | + } | |
61 | + | |
62 | +static const struct tps6586x_irq_data tps6586x_irqs[] = { | |
63 | + [TPS6586X_INT_PLDO_0] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 0), | |
64 | + [TPS6586X_INT_PLDO_1] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 1), | |
65 | + [TPS6586X_INT_PLDO_2] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 2), | |
66 | + [TPS6586X_INT_PLDO_3] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 3), | |
67 | + [TPS6586X_INT_PLDO_4] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 4), | |
68 | + [TPS6586X_INT_PLDO_5] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 5), | |
69 | + [TPS6586X_INT_PLDO_6] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 6), | |
70 | + [TPS6586X_INT_PLDO_7] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 7), | |
71 | + [TPS6586X_INT_COMP_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK4, 1 << 0), | |
72 | + [TPS6586X_INT_ADC] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 1), | |
73 | + [TPS6586X_INT_PLDO_8] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 2), | |
74 | + [TPS6586X_INT_PLDO_9] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 3), | |
75 | + [TPS6586X_INT_PSM_0] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 4), | |
76 | + [TPS6586X_INT_PSM_1] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 5), | |
77 | + [TPS6586X_INT_PSM_2] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 6), | |
78 | + [TPS6586X_INT_PSM_3] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 7), | |
79 | + [TPS6586X_INT_RTC_ALM1] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 4), | |
80 | + [TPS6586X_INT_ACUSB_OVP] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 0x03), | |
81 | + [TPS6586X_INT_USB_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 2), | |
82 | + [TPS6586X_INT_AC_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 3), | |
83 | + [TPS6586X_INT_BAT_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK3, 1 << 0), | |
84 | + [TPS6586X_INT_CHG_STAT] = TPS6586X_IRQ(TPS6586X_INT_MASK4, 0xfc), | |
85 | + [TPS6586X_INT_CHG_TEMP] = TPS6586X_IRQ(TPS6586X_INT_MASK3, 0x06), | |
86 | + [TPS6586X_INT_PP] = TPS6586X_IRQ(TPS6586X_INT_MASK3, 0xf0), | |
87 | + [TPS6586X_INT_RESUME] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 5), | |
88 | + [TPS6586X_INT_LOW_SYS] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 6), | |
89 | + [TPS6586X_INT_RTC_ALM2] = TPS6586X_IRQ(TPS6586X_INT_MASK4, 1 << 1), | |
90 | +}; | |
91 | + | |
36 | 92 | struct tps6586x { |
37 | 93 | struct mutex lock; |
38 | 94 | struct device *dev; |
39 | 95 | struct i2c_client *client; |
40 | 96 | |
41 | 97 | struct gpio_chip gpio; |
98 | + struct irq_chip irq_chip; | |
99 | + struct mutex irq_lock; | |
100 | + int irq_base; | |
101 | + u32 irq_en; | |
102 | + u8 mask_cache[5]; | |
103 | + u8 mask_reg[5]; | |
42 | 104 | }; |
43 | 105 | |
44 | 106 | static inline int __tps6586x_read(struct i2c_client *client, |
... | ... | @@ -262,6 +324,129 @@ |
262 | 324 | return device_for_each_child(tps6586x->dev, NULL, __remove_subdev); |
263 | 325 | } |
264 | 326 | |
327 | +static void tps6586x_irq_lock(unsigned int irq) | |
328 | +{ | |
329 | + struct tps6586x *tps6586x = get_irq_chip_data(irq); | |
330 | + | |
331 | + mutex_lock(&tps6586x->irq_lock); | |
332 | +} | |
333 | + | |
334 | +static void tps6586x_irq_enable(unsigned int irq) | |
335 | +{ | |
336 | + struct tps6586x *tps6586x = get_irq_chip_data(irq); | |
337 | + unsigned int __irq = irq - tps6586x->irq_base; | |
338 | + const struct tps6586x_irq_data *data = &tps6586x_irqs[__irq]; | |
339 | + | |
340 | + tps6586x->mask_reg[data->mask_reg] &= ~data->mask_mask; | |
341 | + tps6586x->irq_en |= (1 << __irq); | |
342 | +} | |
343 | + | |
344 | +static void tps6586x_irq_disable(unsigned int irq) | |
345 | +{ | |
346 | + struct tps6586x *tps6586x = get_irq_chip_data(irq); | |
347 | + | |
348 | + unsigned int __irq = irq - tps6586x->irq_base; | |
349 | + const struct tps6586x_irq_data *data = &tps6586x_irqs[__irq]; | |
350 | + | |
351 | + tps6586x->mask_reg[data->mask_reg] |= data->mask_mask; | |
352 | + tps6586x->irq_en &= ~(1 << __irq); | |
353 | +} | |
354 | + | |
355 | +static void tps6586x_irq_sync_unlock(unsigned int irq) | |
356 | +{ | |
357 | + struct tps6586x *tps6586x = get_irq_chip_data(irq); | |
358 | + int i; | |
359 | + | |
360 | + for (i = 0; i < ARRAY_SIZE(tps6586x->mask_reg); i++) { | |
361 | + if (tps6586x->mask_reg[i] != tps6586x->mask_cache[i]) { | |
362 | + if (!WARN_ON(tps6586x_write(tps6586x->dev, | |
363 | + TPS6586X_INT_MASK1 + i, | |
364 | + tps6586x->mask_reg[i]))) | |
365 | + tps6586x->mask_cache[i] = tps6586x->mask_reg[i]; | |
366 | + } | |
367 | + } | |
368 | + | |
369 | + mutex_unlock(&tps6586x->irq_lock); | |
370 | +} | |
371 | + | |
372 | +static irqreturn_t tps6586x_irq(int irq, void *data) | |
373 | +{ | |
374 | + struct tps6586x *tps6586x = data; | |
375 | + u32 acks; | |
376 | + int ret = 0; | |
377 | + | |
378 | + ret = tps6586x_reads(tps6586x->dev, TPS6586X_INT_ACK1, | |
379 | + sizeof(acks), (uint8_t *)&acks); | |
380 | + | |
381 | + if (ret < 0) { | |
382 | + dev_err(tps6586x->dev, "failed to read interrupt status\n"); | |
383 | + return IRQ_NONE; | |
384 | + } | |
385 | + | |
386 | + acks = le32_to_cpu(acks); | |
387 | + | |
388 | + while (acks) { | |
389 | + int i = __ffs(acks); | |
390 | + | |
391 | + if (tps6586x->irq_en & (1 << i)) | |
392 | + handle_nested_irq(tps6586x->irq_base + i); | |
393 | + | |
394 | + acks &= ~(1 << i); | |
395 | + } | |
396 | + | |
397 | + return IRQ_HANDLED; | |
398 | +} | |
399 | + | |
400 | +static int __devinit tps6586x_irq_init(struct tps6586x *tps6586x, int irq, | |
401 | + int irq_base) | |
402 | +{ | |
403 | + int i, ret; | |
404 | + u8 tmp[4]; | |
405 | + | |
406 | + if (!irq_base) { | |
407 | + dev_warn(tps6586x->dev, "No interrupt support on IRQ base\n"); | |
408 | + return -EINVAL; | |
409 | + } | |
410 | + | |
411 | + mutex_init(&tps6586x->irq_lock); | |
412 | + for (i = 0; i < 5; i++) { | |
413 | + tps6586x->mask_cache[i] = 0xff; | |
414 | + tps6586x->mask_reg[i] = 0xff; | |
415 | + tps6586x_write(tps6586x->dev, TPS6586X_INT_MASK1 + i, 0xff); | |
416 | + } | |
417 | + | |
418 | + tps6586x_reads(tps6586x->dev, TPS6586X_INT_ACK1, sizeof(tmp), tmp); | |
419 | + | |
420 | + tps6586x->irq_base = irq_base; | |
421 | + | |
422 | + tps6586x->irq_chip.name = "tps6586x"; | |
423 | + tps6586x->irq_chip.enable = tps6586x_irq_enable; | |
424 | + tps6586x->irq_chip.disable = tps6586x_irq_disable; | |
425 | + tps6586x->irq_chip.bus_lock = tps6586x_irq_lock; | |
426 | + tps6586x->irq_chip.bus_sync_unlock = tps6586x_irq_sync_unlock; | |
427 | + | |
428 | + for (i = 0; i < ARRAY_SIZE(tps6586x_irqs); i++) { | |
429 | + int __irq = i + tps6586x->irq_base; | |
430 | + set_irq_chip_data(__irq, tps6586x); | |
431 | + set_irq_chip_and_handler(__irq, &tps6586x->irq_chip, | |
432 | + handle_simple_irq); | |
433 | + set_irq_nested_thread(__irq, 1); | |
434 | +#ifdef CONFIG_ARM | |
435 | + set_irq_flags(__irq, IRQF_VALID); | |
436 | +#endif | |
437 | + } | |
438 | + | |
439 | + ret = request_threaded_irq(irq, NULL, tps6586x_irq, IRQF_ONESHOT, | |
440 | + "tps6586x", tps6586x); | |
441 | + | |
442 | + if (!ret) { | |
443 | + device_init_wakeup(tps6586x->dev, 1); | |
444 | + enable_irq_wake(irq); | |
445 | + } | |
446 | + | |
447 | + return ret; | |
448 | +} | |
449 | + | |
265 | 450 | static int __devinit tps6586x_add_subdevs(struct tps6586x *tps6586x, |
266 | 451 | struct tps6586x_platform_data *pdata) |
267 | 452 | { |
... | ... | @@ -327,6 +512,15 @@ |
327 | 512 | |
328 | 513 | mutex_init(&tps6586x->lock); |
329 | 514 | |
515 | + if (client->irq) { | |
516 | + ret = tps6586x_irq_init(tps6586x, client->irq, | |
517 | + pdata->irq_base); | |
518 | + if (ret) { | |
519 | + dev_err(&client->dev, "IRQ init failed: %d\n", ret); | |
520 | + goto err_irq_init; | |
521 | + } | |
522 | + } | |
523 | + | |
330 | 524 | ret = tps6586x_add_subdevs(tps6586x, pdata); |
331 | 525 | if (ret) { |
332 | 526 | dev_err(&client->dev, "add devices failed: %d\n", ret); |
... | ... | @@ -338,6 +532,9 @@ |
338 | 532 | return 0; |
339 | 533 | |
340 | 534 | err_add_devs: |
535 | + if (client->irq) | |
536 | + free_irq(client->irq, tps6586x); | |
537 | +err_irq_init: | |
341 | 538 | kfree(tps6586x); |
342 | 539 | return ret; |
343 | 540 | } |
... | ... | @@ -347,6 +544,9 @@ |
347 | 544 | struct tps6586x *tps6586x = i2c_get_clientdata(client); |
348 | 545 | struct tps6586x_platform_data *pdata = client->dev.platform_data; |
349 | 546 | int ret; |
547 | + | |
548 | + if (client->irq) | |
549 | + free_irq(client->irq, tps6586x); | |
350 | 550 | |
351 | 551 | if (pdata->gpio_base) { |
352 | 552 | ret = gpiochip_remove(&tps6586x->gpio); |
include/linux/mfd/tps6586x.h
... | ... | @@ -18,6 +18,36 @@ |
18 | 18 | TPS6586X_ID_LDO_RTC, |
19 | 19 | }; |
20 | 20 | |
21 | +enum { | |
22 | + TPS6586X_INT_PLDO_0, | |
23 | + TPS6586X_INT_PLDO_1, | |
24 | + TPS6586X_INT_PLDO_2, | |
25 | + TPS6586X_INT_PLDO_3, | |
26 | + TPS6586X_INT_PLDO_4, | |
27 | + TPS6586X_INT_PLDO_5, | |
28 | + TPS6586X_INT_PLDO_6, | |
29 | + TPS6586X_INT_PLDO_7, | |
30 | + TPS6586X_INT_COMP_DET, | |
31 | + TPS6586X_INT_ADC, | |
32 | + TPS6586X_INT_PLDO_8, | |
33 | + TPS6586X_INT_PLDO_9, | |
34 | + TPS6586X_INT_PSM_0, | |
35 | + TPS6586X_INT_PSM_1, | |
36 | + TPS6586X_INT_PSM_2, | |
37 | + TPS6586X_INT_PSM_3, | |
38 | + TPS6586X_INT_RTC_ALM1, | |
39 | + TPS6586X_INT_ACUSB_OVP, | |
40 | + TPS6586X_INT_USB_DET, | |
41 | + TPS6586X_INT_AC_DET, | |
42 | + TPS6586X_INT_BAT_DET, | |
43 | + TPS6586X_INT_CHG_STAT, | |
44 | + TPS6586X_INT_CHG_TEMP, | |
45 | + TPS6586X_INT_PP, | |
46 | + TPS6586X_INT_RESUME, | |
47 | + TPS6586X_INT_LOW_SYS, | |
48 | + TPS6586X_INT_RTC_ALM2, | |
49 | +}; | |
50 | + | |
21 | 51 | struct tps6586x_subdev_info { |
22 | 52 | int id; |
23 | 53 | const char *name; |
... | ... | @@ -29,6 +59,7 @@ |
29 | 59 | struct tps6586x_subdev_info *subdevs; |
30 | 60 | |
31 | 61 | int gpio_base; |
62 | + int irq_base; | |
32 | 63 | }; |
33 | 64 | |
34 | 65 | /* |