aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2010-03-26 12:49:15 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2010-04-05 14:18:07 -0400
commitc9fbf7e070bbf9cc7adc1420df87706c62cb04ed (patch)
tree38770c150750f3f11b59643302312762494b546e /drivers/mfd
parent8c0d8fa26ba5ec1f69e88044c671dab58c51e0e1 (diff)
mfd: Add WM8994 interrupt controller support
The WM8994 has an interrupt controller which supports interrupts for both CODEC and GPIO portions of the chip. Support this using genirq, while allowing for systems that do not have an interrupt hooked up. Wrapper functions are provided for the IRQ request and free to simplify the code in consumer drivers when handling cases where IRQs are not set up. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Acked-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig4
-rw-r--r--drivers/mfd/Makefile2
-rw-r--r--drivers/mfd/wm8994-core.c43
-rw-r--r--drivers/mfd/wm8994-irq.c310
4 files changed, 353 insertions, 6 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index b2073e0266f5..de3e74cde51c 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -301,9 +301,9 @@ config MFD_WM8350_I2C
301 selected to enable support for the functionality of the chip. 301 selected to enable support for the functionality of the chip.
302 302
303config MFD_WM8994 303config MFD_WM8994
304 tristate "Support Wolfson Microelectronics WM8994" 304 bool "Support Wolfson Microelectronics WM8994"
305 select MFD_CORE 305 select MFD_CORE
306 depends on I2C 306 depends on I2C=y && GENERIC_HARDIRQS
307 help 307 help
308 The WM8994 is a highly integrated hi-fi CODEC designed for 308 The WM8994 is a highly integrated hi-fi CODEC designed for
309 smartphone applicatiosn. As well as audio functionality it 309 smartphone applicatiosn. As well as audio functionality it
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4fbf8f89a49b..87935f967aa0 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -26,7 +26,7 @@ wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o
26wm8350-objs += wm8350-irq.o 26wm8350-objs += wm8350-irq.o
27obj-$(CONFIG_MFD_WM8350) += wm8350.o 27obj-$(CONFIG_MFD_WM8350) += wm8350.o
28obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o 28obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o
29obj-$(CONFIG_MFD_WM8994) += wm8994-core.o 29obj-$(CONFIG_MFD_WM8994) += wm8994-core.o wm8994-irq.o
30 30
31obj-$(CONFIG_TPS65010) += tps65010.o 31obj-$(CONFIG_TPS65010) += tps65010.o
32obj-$(CONFIG_MENELAUS) += menelaus.o 32obj-$(CONFIG_MENELAUS) += menelaus.o
diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c
index 844e1c1b7d90..39cde82afb48 100644
--- a/drivers/mfd/wm8994-core.c
+++ b/drivers/mfd/wm8994-core.c
@@ -172,9 +172,34 @@ static struct mfd_cell wm8994_regulator_devs[] = {
172 { .name = "wm8994-ldo", .id = 2 }, 172 { .name = "wm8994-ldo", .id = 2 },
173}; 173};
174 174
175static struct resource wm8994_codec_resources[] = {
176 {
177 .start = WM8994_IRQ_TEMP_SHUT,
178 .end = WM8994_IRQ_TEMP_WARN,
179 .flags = IORESOURCE_IRQ,
180 },
181};
182
183static struct resource wm8994_gpio_resources[] = {
184 {
185 .start = WM8994_IRQ_GPIO(1),
186 .end = WM8994_IRQ_GPIO(11),
187 .flags = IORESOURCE_IRQ,
188 },
189};
190
175static struct mfd_cell wm8994_devs[] = { 191static struct mfd_cell wm8994_devs[] = {
176 { .name = "wm8994-codec" }, 192 {
177 { .name = "wm8994-gpio" }, 193 .name = "wm8994-codec",
194 .num_resources = ARRAY_SIZE(wm8994_codec_resources),
195 .resources = wm8994_codec_resources,
196 },
197
198 {
199 .name = "wm8994-gpio",
200 .num_resources = ARRAY_SIZE(wm8994_gpio_resources),
201 .resources = wm8994_gpio_resources,
202 },
178}; 203};
179 204
180/* 205/*
@@ -235,6 +260,11 @@ static int wm8994_device_resume(struct device *dev)
235 return ret; 260 return ret;
236 } 261 }
237 262
263 ret = wm8994_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK,
264 WM8994_NUM_IRQ_REGS * 2, &wm8994->irq_masks_cur);
265 if (ret < 0)
266 dev_err(dev, "Failed to restore interrupt masks: %d\n", ret);
267
238 ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2, 268 ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2,
239 &wm8994->ldo_regs); 269 &wm8994->ldo_regs);
240 if (ret < 0) 270 if (ret < 0)
@@ -347,6 +377,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq)
347 377
348 378
349 if (pdata) { 379 if (pdata) {
380 wm8994->irq_base = pdata->irq_base;
350 wm8994->gpio_base = pdata->gpio_base; 381 wm8994->gpio_base = pdata->gpio_base;
351 382
352 /* GPIO configuration is only applied if it's non-zero */ 383 /* GPIO configuration is only applied if it's non-zero */
@@ -374,16 +405,20 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq)
374 WM8994_LDO1_DISCH, 0); 405 WM8994_LDO1_DISCH, 0);
375 } 406 }
376 407
408 wm8994_irq_init(wm8994);
409
377 ret = mfd_add_devices(wm8994->dev, -1, 410 ret = mfd_add_devices(wm8994->dev, -1,
378 wm8994_devs, ARRAY_SIZE(wm8994_devs), 411 wm8994_devs, ARRAY_SIZE(wm8994_devs),
379 NULL, 0); 412 NULL, 0);
380 if (ret != 0) { 413 if (ret != 0) {
381 dev_err(wm8994->dev, "Failed to add children: %d\n", ret); 414 dev_err(wm8994->dev, "Failed to add children: %d\n", ret);
382 goto err_enable; 415 goto err_irq;
383 } 416 }
384 417
385 return 0; 418 return 0;
386 419
420err_irq:
421 wm8994_irq_exit(wm8994);
387err_enable: 422err_enable:
388 regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), 423 regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
389 wm8994->supplies); 424 wm8994->supplies);
@@ -400,6 +435,7 @@ err:
400static void wm8994_device_exit(struct wm8994 *wm8994) 435static void wm8994_device_exit(struct wm8994 *wm8994)
401{ 436{
402 mfd_remove_devices(wm8994->dev); 437 mfd_remove_devices(wm8994->dev);
438 wm8994_irq_exit(wm8994);
403 regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), 439 regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
404 wm8994->supplies); 440 wm8994->supplies);
405 regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); 441 regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies);
@@ -468,6 +504,7 @@ static int wm8994_i2c_probe(struct i2c_client *i2c,
468 wm8994->control_data = i2c; 504 wm8994->control_data = i2c;
469 wm8994->read_dev = wm8994_i2c_read_device; 505 wm8994->read_dev = wm8994_i2c_read_device;
470 wm8994->write_dev = wm8994_i2c_write_device; 506 wm8994->write_dev = wm8994_i2c_write_device;
507 wm8994->irq = i2c->irq;
471 508
472 return wm8994_device_init(wm8994, id->driver_data, i2c->irq); 509 return wm8994_device_init(wm8994, id->driver_data, i2c->irq);
473} 510}
diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c
new file mode 100644
index 000000000000..8400eb1ee5db
--- /dev/null
+++ b/drivers/mfd/wm8994-irq.c
@@ -0,0 +1,310 @@
1/*
2 * wm8994-irq.c -- Interrupt controller support for Wolfson WM8994
3 *
4 * Copyright 2010 Wolfson Microelectronics PLC.
5 *
6 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 *
13 */
14
15#include <linux/kernel.h>
16#include <linux/module.h>
17#include <linux/i2c.h>
18#include <linux/irq.h>
19#include <linux/mfd/core.h>
20#include <linux/interrupt.h>
21
22#include <linux/mfd/wm8994/core.h>
23#include <linux/mfd/wm8994/registers.h>
24
25#include <linux/delay.h>
26
27struct wm8994_irq_data {
28 int reg;
29 int mask;
30};
31
32static struct wm8994_irq_data wm8994_irqs[] = {
33 [WM8994_IRQ_TEMP_SHUT] = {
34 .reg = 2,
35 .mask = WM8994_TEMP_SHUT_EINT,
36 },
37 [WM8994_IRQ_MIC1_DET] = {
38 .reg = 2,
39 .mask = WM8994_MIC1_DET_EINT,
40 },
41 [WM8994_IRQ_MIC1_SHRT] = {
42 .reg = 2,
43 .mask = WM8994_MIC1_SHRT_EINT,
44 },
45 [WM8994_IRQ_MIC2_DET] = {
46 .reg = 2,
47 .mask = WM8994_MIC2_DET_EINT,
48 },
49 [WM8994_IRQ_MIC2_SHRT] = {
50 .reg = 2,
51 .mask = WM8994_MIC2_SHRT_EINT,
52 },
53 [WM8994_IRQ_FLL1_LOCK] = {
54 .reg = 2,
55 .mask = WM8994_FLL1_LOCK_EINT,
56 },
57 [WM8994_IRQ_FLL2_LOCK] = {
58 .reg = 2,
59 .mask = WM8994_FLL2_LOCK_EINT,
60 },
61 [WM8994_IRQ_SRC1_LOCK] = {
62 .reg = 2,
63 .mask = WM8994_SRC1_LOCK_EINT,
64 },
65 [WM8994_IRQ_SRC2_LOCK] = {
66 .reg = 2,
67 .mask = WM8994_SRC2_LOCK_EINT,
68 },
69 [WM8994_IRQ_AIF1DRC1_SIG_DET] = {
70 .reg = 2,
71 .mask = WM8994_AIF1DRC1_SIG_DET,
72 },
73 [WM8994_IRQ_AIF1DRC2_SIG_DET] = {
74 .reg = 2,
75 .mask = WM8994_AIF1DRC2_SIG_DET_EINT,
76 },
77 [WM8994_IRQ_AIF2DRC_SIG_DET] = {
78 .reg = 2,
79 .mask = WM8994_AIF2DRC_SIG_DET_EINT,
80 },
81 [WM8994_IRQ_FIFOS_ERR] = {
82 .reg = 2,
83 .mask = WM8994_FIFOS_ERR_EINT,
84 },
85 [WM8994_IRQ_WSEQ_DONE] = {
86 .reg = 2,
87 .mask = WM8994_WSEQ_DONE_EINT,
88 },
89 [WM8994_IRQ_DCS_DONE] = {
90 .reg = 2,
91 .mask = WM8994_DCS_DONE_EINT,
92 },
93 [WM8994_IRQ_TEMP_WARN] = {
94 .reg = 2,
95 .mask = WM8994_TEMP_WARN_EINT,
96 },
97 [WM8994_IRQ_GPIO(1)] = {
98 .reg = 1,
99 .mask = WM8994_GP1_EINT,
100 },
101 [WM8994_IRQ_GPIO(2)] = {
102 .reg = 1,
103 .mask = WM8994_GP2_EINT,
104 },
105 [WM8994_IRQ_GPIO(3)] = {
106 .reg = 1,
107 .mask = WM8994_GP3_EINT,
108 },
109 [WM8994_IRQ_GPIO(4)] = {
110 .reg = 1,
111 .mask = WM8994_GP4_EINT,
112 },
113 [WM8994_IRQ_GPIO(5)] = {
114 .reg = 1,
115 .mask = WM8994_GP5_EINT,
116 },
117 [WM8994_IRQ_GPIO(6)] = {
118 .reg = 1,
119 .mask = WM8994_GP6_EINT,
120 },
121 [WM8994_IRQ_GPIO(7)] = {
122 .reg = 1,
123 .mask = WM8994_GP7_EINT,
124 },
125 [WM8994_IRQ_GPIO(8)] = {
126 .reg = 1,
127 .mask = WM8994_GP8_EINT,
128 },
129 [WM8994_IRQ_GPIO(9)] = {
130 .reg = 1,
131 .mask = WM8994_GP8_EINT,
132 },
133 [WM8994_IRQ_GPIO(10)] = {
134 .reg = 1,
135 .mask = WM8994_GP10_EINT,
136 },
137 [WM8994_IRQ_GPIO(11)] = {
138 .reg = 1,
139 .mask = WM8994_GP11_EINT,
140 },
141};
142
143static inline int irq_data_to_status_reg(struct wm8994_irq_data *irq_data)
144{
145 return WM8994_INTERRUPT_STATUS_1 - 1 + irq_data->reg;
146}
147
148static inline int irq_data_to_mask_reg(struct wm8994_irq_data *irq_data)
149{
150 return WM8994_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg;
151}
152
153static inline struct wm8994_irq_data *irq_to_wm8994_irq(struct wm8994 *wm8994,
154 int irq)
155{
156 return &wm8994_irqs[irq - wm8994->irq_base];
157}
158
159static void wm8994_irq_lock(unsigned int irq)
160{
161 struct wm8994 *wm8994 = get_irq_chip_data(irq);
162
163 mutex_lock(&wm8994->irq_lock);
164}
165
166static void wm8994_irq_sync_unlock(unsigned int irq)
167{
168 struct wm8994 *wm8994 = get_irq_chip_data(irq);
169 int i;
170
171 for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) {
172 /* If there's been a change in the mask write it back
173 * to the hardware. */
174 if (wm8994->irq_masks_cur[i] != wm8994->irq_masks_cache[i]) {
175 wm8994->irq_masks_cache[i] = wm8994->irq_masks_cur[i];
176 wm8994_reg_write(wm8994,
177 WM8994_INTERRUPT_STATUS_1_MASK + i,
178 wm8994->irq_masks_cur[i]);
179 }
180 }
181
182 mutex_unlock(&wm8994->irq_lock);
183}
184
185static void wm8994_irq_unmask(unsigned int irq)
186{
187 struct wm8994 *wm8994 = get_irq_chip_data(irq);
188 struct wm8994_irq_data *irq_data = irq_to_wm8994_irq(wm8994, irq);
189
190 wm8994->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask;
191}
192
193static void wm8994_irq_mask(unsigned int irq)
194{
195 struct wm8994 *wm8994 = get_irq_chip_data(irq);
196 struct wm8994_irq_data *irq_data = irq_to_wm8994_irq(wm8994, irq);
197
198 wm8994->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask;
199}
200
201static struct irq_chip wm8994_irq_chip = {
202 .name = "wm8994",
203 .bus_lock = wm8994_irq_lock,
204 .bus_sync_unlock = wm8994_irq_sync_unlock,
205 .mask = wm8994_irq_mask,
206 .unmask = wm8994_irq_unmask,
207};
208
209/* The processing of the primary interrupt occurs in a thread so that
210 * we can interact with the device over I2C or SPI. */
211static irqreturn_t wm8994_irq_thread(int irq, void *data)
212{
213 struct wm8994 *wm8994 = data;
214 unsigned int i;
215 u16 status[WM8994_NUM_IRQ_REGS];
216 int ret;
217
218 ret = wm8994_bulk_read(wm8994, WM8994_INTERRUPT_STATUS_1,
219 WM8994_NUM_IRQ_REGS, status);
220 if (ret < 0) {
221 dev_err(wm8994->dev, "Failed to read interrupt status: %d\n",
222 ret);
223 return IRQ_NONE;
224 }
225
226 /* Apply masking */
227 for (i = 0; i < WM8994_NUM_IRQ_REGS; i++)
228 status[i] &= ~wm8994->irq_masks_cur[i];
229
230 /* Report */
231 for (i = 0; i < ARRAY_SIZE(wm8994_irqs); i++) {
232 if (status[wm8994_irqs[i].reg - 1] & wm8994_irqs[i].mask)
233 handle_nested_irq(wm8994->irq_base + i);
234 }
235
236 /* Ack any unmasked IRQs */
237 for (i = 0; i < ARRAY_SIZE(status); i++) {
238 if (status[i])
239 wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1 + i,
240 status[i]);
241 }
242
243 return IRQ_HANDLED;
244}
245
246int wm8994_irq_init(struct wm8994 *wm8994)
247{
248 int i, cur_irq, ret;
249
250 mutex_init(&wm8994->irq_lock);
251
252 /* Mask the individual interrupt sources */
253 for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) {
254 wm8994->irq_masks_cur[i] = 0xffff;
255 wm8994->irq_masks_cache[i] = 0xffff;
256 wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK + i,
257 0xffff);
258 }
259
260 if (!wm8994->irq) {
261 dev_warn(wm8994->dev,
262 "No interrupt specified, no interrupts\n");
263 wm8994->irq_base = 0;
264 return 0;
265 }
266
267 if (!wm8994->irq_base) {
268 dev_err(wm8994->dev,
269 "No interrupt base specified, no interrupts\n");
270 return 0;
271 }
272
273 /* Register them with genirq */
274 for (cur_irq = wm8994->irq_base;
275 cur_irq < ARRAY_SIZE(wm8994_irqs) + wm8994->irq_base;
276 cur_irq++) {
277 set_irq_chip_data(cur_irq, wm8994);
278 set_irq_chip_and_handler(cur_irq, &wm8994_irq_chip,
279 handle_edge_irq);
280 set_irq_nested_thread(cur_irq, 1);
281
282 /* ARM needs us to explicitly flag the IRQ as valid
283 * and will set them noprobe when we do so. */
284#ifdef CONFIG_ARM
285 set_irq_flags(cur_irq, IRQF_VALID);
286#else
287 set_irq_noprobe(cur_irq);
288#endif
289 }
290
291 ret = request_threaded_irq(wm8994->irq, NULL, wm8994_irq_thread,
292 IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
293 "wm8994", wm8994);
294 if (ret != 0) {
295 dev_err(wm8994->dev, "Failed to request IRQ %d: %d\n",
296 wm8994->irq, ret);
297 return ret;
298 }
299
300 /* Enable top level interrupt if it was masked */
301 wm8994_reg_write(wm8994, WM8994_INTERRUPT_CONTROL, 0);
302
303 return 0;
304}
305
306void wm8994_irq_exit(struct wm8994 *wm8994)
307{
308 if (wm8994->irq)
309 free_irq(wm8994->irq, wm8994);
310}