diff options
author | Graeme Gregory <gg@slimlogic.co.uk> | 2011-05-02 17:20:04 -0400 |
---|---|---|
committer | Liam Girdwood <lrg@slimlogic.co.uk> | 2011-05-27 05:48:43 -0400 |
commit | e3471bdc2784ee20a0d636c5904200c2d1148ef9 (patch) | |
tree | b8031223f737830b05d59189fae09452cabfd8aa | |
parent | 2537df722d338ab687d7ed91dc589265c0d14aec (diff) |
TPS65910: IRQ: Add interrupt controller
This module controls the interrupt handling for the tps chip. The
interrupt sources are the following:
- GPIO falling/rising edge detection
- Battery voltage below/above threshold
- PWRON signal
- PWRHOLD signal
- Temperature detection
- RTC alarm and periodic event
Signed-off-by: Graeme Gregory <gg@slimlogic.co.uk>
Signed-off-by: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>
Reviewed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Liam Girdwood <lrg@slimlogic.co.uk>
-rw-r--r-- | drivers/mfd/Makefile | 2 | ||||
-rw-r--r-- | drivers/mfd/tps65910-irq.c | 187 | ||||
-rw-r--r-- | drivers/mfd/tps65910.c | 12 | ||||
-rw-r--r-- | include/linux/mfd/tps65910.h | 5 |
4 files changed, 205 insertions, 1 deletions
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c64fa8d48f45..bdb8eab5668b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -93,4 +93,4 @@ obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o | |||
93 | obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o | 93 | obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o |
94 | obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o | 94 | obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o |
95 | obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o | 95 | obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o |
96 | obj-$(CONFIG_MFD_TPS65910) += tps65910.o tps65910-gpio.o | 96 | obj-$(CONFIG_MFD_TPS65910) += tps65910.o tps65910-gpio.o tps65910-irq.o |
diff --git a/drivers/mfd/tps65910-irq.c b/drivers/mfd/tps65910-irq.c new file mode 100644 index 000000000000..b8435e045f81 --- /dev/null +++ b/drivers/mfd/tps65910-irq.c | |||
@@ -0,0 +1,187 @@ | |||
1 | /* | ||
2 | * tps65910-irq.c -- TI TPS6591x | ||
3 | * | ||
4 | * Copyright 2010 Texas Instruments Inc. | ||
5 | * | ||
6 | * Author: Graeme Gregory <gg@slimlogic.co.uk> | ||
7 | * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify it | ||
10 | * under the terms of the GNU General Public License as published by the | ||
11 | * Free Software Foundation; either version 2 of the License, or (at your | ||
12 | * option) any later version. | ||
13 | * | ||
14 | */ | ||
15 | |||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/init.h> | ||
19 | #include <linux/bug.h> | ||
20 | #include <linux/device.h> | ||
21 | #include <linux/interrupt.h> | ||
22 | #include <linux/irq.h> | ||
23 | #include <linux/gpio.h> | ||
24 | #include <linux/mfd/tps65910.h> | ||
25 | |||
26 | static inline int irq_to_tps65910_irq(struct tps65910 *tps65910, | ||
27 | int irq) | ||
28 | { | ||
29 | return (irq - tps65910->irq_base); | ||
30 | } | ||
31 | |||
32 | /* | ||
33 | * This is a threaded IRQ handler so can access I2C/SPI. Since all | ||
34 | * interrupts are clear on read the IRQ line will be reasserted and | ||
35 | * the physical IRQ will be handled again if another interrupt is | ||
36 | * asserted while we run - in the normal course of events this is a | ||
37 | * rare occurrence so we save I2C/SPI reads. We're also assuming that | ||
38 | * it's rare to get lots of interrupts firing simultaneously so try to | ||
39 | * minimise I/O. | ||
40 | */ | ||
41 | static irqreturn_t tps65910_irq(int irq, void *irq_data) | ||
42 | { | ||
43 | struct tps65910 *tps65910 = irq_data; | ||
44 | u16 irq_sts; | ||
45 | u16 irq_mask; | ||
46 | u8 reg; | ||
47 | int i; | ||
48 | |||
49 | tps65910->read(tps65910, TPS65910_INT_STS, 1, ®); | ||
50 | irq_sts = reg; | ||
51 | tps65910->read(tps65910, TPS65910_INT_STS2, 1, ®); | ||
52 | irq_sts |= reg << 8; | ||
53 | |||
54 | tps65910->read(tps65910, TPS65910_INT_MSK, 1, ®); | ||
55 | irq_mask = reg; | ||
56 | tps65910->read(tps65910, TPS65910_INT_MSK2, 1, ®); | ||
57 | irq_mask |= reg << 8; | ||
58 | |||
59 | irq_sts &= ~irq_mask; | ||
60 | |||
61 | if (!irq_sts) | ||
62 | return IRQ_NONE; | ||
63 | |||
64 | for (i = 0; i < TPS65910_NUM_IRQ; i++) { | ||
65 | |||
66 | if (!(irq_sts & (1 << i))) | ||
67 | continue; | ||
68 | |||
69 | handle_nested_irq(tps65910->irq_base + i); | ||
70 | } | ||
71 | |||
72 | /* Write the STS register back to clear IRQs we handled */ | ||
73 | reg = irq_sts & 0xFF; | ||
74 | tps65910->write(tps65910, TPS65910_INT_STS, 1, ®); | ||
75 | reg = irq_sts >> 8; | ||
76 | tps65910->write(tps65910, TPS65910_INT_STS2, 1, ®); | ||
77 | |||
78 | return IRQ_HANDLED; | ||
79 | } | ||
80 | |||
81 | static void tps65910_irq_lock(struct irq_data *data) | ||
82 | { | ||
83 | struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); | ||
84 | |||
85 | mutex_lock(&tps65910->irq_lock); | ||
86 | } | ||
87 | |||
88 | static void tps65910_irq_sync_unlock(struct irq_data *data) | ||
89 | { | ||
90 | struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); | ||
91 | u16 reg_mask; | ||
92 | u8 reg; | ||
93 | |||
94 | tps65910->read(tps65910, TPS65910_INT_MSK, 1, ®); | ||
95 | reg_mask = reg; | ||
96 | tps65910->read(tps65910, TPS65910_INT_MSK2, 1, ®); | ||
97 | reg_mask |= reg << 8; | ||
98 | |||
99 | if (tps65910->irq_mask != reg_mask) { | ||
100 | reg = tps65910->irq_mask & 0xFF; | ||
101 | tps65910->write(tps65910, TPS65910_INT_MSK, 1, ®); | ||
102 | reg = tps65910->irq_mask >> 8; | ||
103 | tps65910->write(tps65910, TPS65910_INT_MSK2, 1, ®); | ||
104 | } | ||
105 | mutex_unlock(&tps65910->irq_lock); | ||
106 | } | ||
107 | |||
108 | static void tps65910_irq_enable(struct irq_data *data) | ||
109 | { | ||
110 | struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); | ||
111 | |||
112 | tps65910->irq_mask &= ~( 1 << irq_to_tps65910_irq(tps65910, data->irq)); | ||
113 | } | ||
114 | |||
115 | static void tps65910_irq_disable(struct irq_data *data) | ||
116 | { | ||
117 | struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); | ||
118 | |||
119 | tps65910->irq_mask |= ( 1 << irq_to_tps65910_irq(tps65910, data->irq)); | ||
120 | } | ||
121 | |||
122 | static struct irq_chip tps65910_irq_chip = { | ||
123 | .name = "tps65910", | ||
124 | .irq_bus_lock = tps65910_irq_lock, | ||
125 | .irq_bus_sync_unlock = tps65910_irq_sync_unlock, | ||
126 | .irq_disable = tps65910_irq_disable, | ||
127 | .irq_enable = tps65910_irq_enable, | ||
128 | }; | ||
129 | |||
130 | int tps65910_irq_init(struct tps65910 *tps65910, int irq, | ||
131 | struct tps65910_platform_data *pdata) | ||
132 | { | ||
133 | int ret, cur_irq; | ||
134 | int flags = IRQF_ONESHOT; | ||
135 | u8 reg; | ||
136 | |||
137 | if (!irq) { | ||
138 | dev_warn(tps65910->dev, "No interrupt support, no core IRQ\n"); | ||
139 | return -EINVAL; | ||
140 | } | ||
141 | |||
142 | if (!pdata || !pdata->irq_base) { | ||
143 | dev_warn(tps65910->dev, "No interrupt support, no IRQ base\n"); | ||
144 | return -EINVAL; | ||
145 | } | ||
146 | |||
147 | /* Mask top level interrupts */ | ||
148 | reg = 0xFF; | ||
149 | tps65910->write(tps65910, TPS65910_INT_MSK, 1, ®); | ||
150 | reg = 0x03; | ||
151 | tps65910->write(tps65910, TPS65910_INT_MSK2, 1, ®); | ||
152 | |||
153 | mutex_init(&tps65910->irq_lock); | ||
154 | tps65910->chip_irq = irq; | ||
155 | tps65910->irq_base = pdata->irq_base; | ||
156 | |||
157 | /* Register with genirq */ | ||
158 | for (cur_irq = tps65910->irq_base; | ||
159 | cur_irq < TPS65910_NUM_IRQ + tps65910->irq_base; | ||
160 | cur_irq++) { | ||
161 | irq_set_chip_data(cur_irq, tps65910); | ||
162 | irq_set_chip_and_handler(cur_irq, &tps65910_irq_chip, | ||
163 | handle_edge_irq); | ||
164 | irq_set_nested_thread(cur_irq, 1); | ||
165 | |||
166 | /* ARM needs us to explicitly flag the IRQ as valid | ||
167 | * and will set them noprobe when we do so. */ | ||
168 | #ifdef CONFIG_ARM | ||
169 | set_irq_flags(cur_irq, IRQF_VALID); | ||
170 | #else | ||
171 | irq_set_noprobe(cur_irq); | ||
172 | #endif | ||
173 | } | ||
174 | |||
175 | ret = request_threaded_irq(irq, NULL, tps65910_irq, flags, | ||
176 | "tps65910", tps65910); | ||
177 | if (ret != 0) | ||
178 | dev_err(tps65910->dev, "Failed to request IRQ: %d\n", ret); | ||
179 | |||
180 | return ret; | ||
181 | } | ||
182 | |||
183 | int tps65910_irq_exit(struct tps65910 *tps65910) | ||
184 | { | ||
185 | free_irq(tps65910->chip_irq, tps65910); | ||
186 | return 0; | ||
187 | } | ||
diff --git a/drivers/mfd/tps65910.c b/drivers/mfd/tps65910.c index 5a6a7be1f8cf..bf649cf6a0ae 100644 --- a/drivers/mfd/tps65910.c +++ b/drivers/mfd/tps65910.c | |||
@@ -136,12 +136,20 @@ static int tps65910_i2c_probe(struct i2c_client *i2c, | |||
136 | { | 136 | { |
137 | struct tps65910 *tps65910; | 137 | struct tps65910 *tps65910; |
138 | struct tps65910_board *pmic_plat_data; | 138 | struct tps65910_board *pmic_plat_data; |
139 | struct tps65910_platform_data *init_data; | ||
139 | int ret = 0; | 140 | int ret = 0; |
140 | 141 | ||
141 | pmic_plat_data = dev_get_platdata(&i2c->dev); | 142 | pmic_plat_data = dev_get_platdata(&i2c->dev); |
142 | if (!pmic_plat_data) | 143 | if (!pmic_plat_data) |
143 | return -EINVAL; | 144 | return -EINVAL; |
144 | 145 | ||
146 | init_data = kzalloc(sizeof(struct tps65910_platform_data), GFP_KERNEL); | ||
147 | if (init_data == NULL) | ||
148 | return -ENOMEM; | ||
149 | |||
150 | init_data->irq = pmic_plat_data->irq; | ||
151 | init_data->irq_base = pmic_plat_data->irq; | ||
152 | |||
145 | tps65910 = kzalloc(sizeof(struct tps65910), GFP_KERNEL); | 153 | tps65910 = kzalloc(sizeof(struct tps65910), GFP_KERNEL); |
146 | if (tps65910 == NULL) | 154 | if (tps65910 == NULL) |
147 | return -ENOMEM; | 155 | return -ENOMEM; |
@@ -161,6 +169,10 @@ static int tps65910_i2c_probe(struct i2c_client *i2c, | |||
161 | 169 | ||
162 | tps65910_gpio_init(tps65910, pmic_plat_data->gpio_base); | 170 | tps65910_gpio_init(tps65910, pmic_plat_data->gpio_base); |
163 | 171 | ||
172 | ret = tps65910_irq_init(tps65910, init_data->irq, init_data); | ||
173 | if (ret < 0) | ||
174 | goto err; | ||
175 | |||
164 | return ret; | 176 | return ret; |
165 | 177 | ||
166 | err: | 178 | err: |
diff --git a/include/linux/mfd/tps65910.h b/include/linux/mfd/tps65910.h index 0e01d504ab58..8afe91c85587 100644 --- a/include/linux/mfd/tps65910.h +++ b/include/linux/mfd/tps65910.h | |||
@@ -715,6 +715,8 @@ | |||
715 | 715 | ||
716 | struct tps65910_board { | 716 | struct tps65910_board { |
717 | int gpio_base; | 717 | int gpio_base; |
718 | int irq; | ||
719 | int irq_base; | ||
718 | struct regulator_init_data *tps65910_pmic_init_data; | 720 | struct regulator_init_data *tps65910_pmic_init_data; |
719 | }; | 721 | }; |
720 | 722 | ||
@@ -745,11 +747,14 @@ struct tps65910 { | |||
745 | }; | 747 | }; |
746 | 748 | ||
747 | struct tps65910_platform_data { | 749 | struct tps65910_platform_data { |
750 | int irq; | ||
748 | int irq_base; | 751 | int irq_base; |
749 | }; | 752 | }; |
750 | 753 | ||
751 | int tps65910_set_bits(struct tps65910 *tps65910, u8 reg, u8 mask); | 754 | int tps65910_set_bits(struct tps65910 *tps65910, u8 reg, u8 mask); |
752 | int tps65910_clear_bits(struct tps65910 *tps65910, u8 reg, u8 mask); | 755 | int tps65910_clear_bits(struct tps65910 *tps65910, u8 reg, u8 mask); |
753 | void tps65910_gpio_init(struct tps65910 *tps65910, int gpio_base); | 756 | void tps65910_gpio_init(struct tps65910 *tps65910, int gpio_base); |
757 | int tps65910_irq_init(struct tps65910 *tps65910, int irq, | ||
758 | struct tps65910_platform_data *pdata); | ||
754 | 759 | ||
755 | #endif /* __LINUX_MFD_TPS65910_H */ | 760 | #endif /* __LINUX_MFD_TPS65910_H */ |