aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd/twl6040-irq.c
diff options
context:
space:
mode:
authorMisael Lopez Cruz <misael.lopez@ti.com>2011-04-27 03:14:07 -0400
committerPeter Ujfalusi <peter.ujfalusi@ti.com>2011-07-04 12:34:37 -0400
commitf19b2823f82499c60ec15d5fe8783193d77e3043 (patch)
tree75148aed2558ec27ca87cf94e7ec3f398cd77e96 /drivers/mfd/twl6040-irq.c
parent4ae6df5e1018796ce260be59b2c603bd0f9faa94 (diff)
mfd: twl6040: Add initial support
TWL6040 IC provides analog high-end audio codec functions for handset applications. It contains several audio analog inputs and outputs as well as vibrator support. It's connected to the host processor via PDM interface for audio data communication. The audio modules are controlled by internal registers that can be accessed by I2C and PDM interface. TWL6040 MFD will be registered as a child of TWL-CORE, and will have two children of its own: twl6040-codec and twl6040-vibra. This driver is based on TWL4030 and WM8350 MFD drivers. Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com> Signed-off-by: Peter Ujfalusi <peter.ujfalusi@ti.com> Acked-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd/twl6040-irq.c')
-rw-r--r--drivers/mfd/twl6040-irq.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/drivers/mfd/twl6040-irq.c b/drivers/mfd/twl6040-irq.c
new file mode 100644
index 000000000000..938053541520
--- /dev/null
+++ b/drivers/mfd/twl6040-irq.c
@@ -0,0 +1,205 @@
1/*
2 * Interrupt controller support for TWL6040
3 *
4 * Author: Misael Lopez Cruz <misael.lopez@ti.com>
5 *
6 * Copyright: (C) 2011 Texas Instruments, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA
21 *
22 */
23
24#include <linux/kernel.h>
25#include <linux/module.h>
26#include <linux/irq.h>
27#include <linux/interrupt.h>
28#include <linux/mfd/core.h>
29#include <linux/mfd/twl6040.h>
30
31struct twl6040_irq_data {
32 int mask;
33 int status;
34};
35
36static struct twl6040_irq_data twl6040_irqs[] = {
37 {
38 .mask = TWL6040_THMSK,
39 .status = TWL6040_THINT,
40 },
41 {
42 .mask = TWL6040_PLUGMSK,
43 .status = TWL6040_PLUGINT | TWL6040_UNPLUGINT,
44 },
45 {
46 .mask = TWL6040_HOOKMSK,
47 .status = TWL6040_HOOKINT,
48 },
49 {
50 .mask = TWL6040_HFMSK,
51 .status = TWL6040_HFINT,
52 },
53 {
54 .mask = TWL6040_VIBMSK,
55 .status = TWL6040_VIBINT,
56 },
57 {
58 .mask = TWL6040_READYMSK,
59 .status = TWL6040_READYINT,
60 },
61};
62
63static inline
64struct twl6040_irq_data *irq_to_twl6040_irq(struct twl6040 *twl6040,
65 int irq)
66{
67 return &twl6040_irqs[irq - twl6040->irq_base];
68}
69
70static void twl6040_irq_lock(struct irq_data *data)
71{
72 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
73
74 mutex_lock(&twl6040->irq_mutex);
75}
76
77static void twl6040_irq_sync_unlock(struct irq_data *data)
78{
79 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
80
81 /* write back to hardware any change in irq mask */
82 if (twl6040->irq_masks_cur != twl6040->irq_masks_cache) {
83 twl6040->irq_masks_cache = twl6040->irq_masks_cur;
84 twl6040_reg_write(twl6040, TWL6040_REG_INTMR,
85 twl6040->irq_masks_cur);
86 }
87
88 mutex_unlock(&twl6040->irq_mutex);
89}
90
91static void twl6040_irq_enable(struct irq_data *data)
92{
93 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
94 struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
95 data->irq);
96
97 twl6040->irq_masks_cur &= ~irq_data->mask;
98}
99
100static void twl6040_irq_disable(struct irq_data *data)
101{
102 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
103 struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
104 data->irq);
105
106 twl6040->irq_masks_cur |= irq_data->mask;
107}
108
109static struct irq_chip twl6040_irq_chip = {
110 .name = "twl6040",
111 .irq_bus_lock = twl6040_irq_lock,
112 .irq_bus_sync_unlock = twl6040_irq_sync_unlock,
113 .irq_enable = twl6040_irq_enable,
114 .irq_disable = twl6040_irq_disable,
115};
116
117static irqreturn_t twl6040_irq_thread(int irq, void *data)
118{
119 struct twl6040 *twl6040 = data;
120 u8 intid;
121 int i;
122
123 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
124
125 /* apply masking and report (backwards to handle READYINT first) */
126 for (i = ARRAY_SIZE(twl6040_irqs) - 1; i >= 0; i--) {
127 if (twl6040->irq_masks_cur & twl6040_irqs[i].mask)
128 intid &= ~twl6040_irqs[i].status;
129 if (intid & twl6040_irqs[i].status)
130 handle_nested_irq(twl6040->irq_base + i);
131 }
132
133 /* ack unmasked irqs */
134 twl6040_reg_write(twl6040, TWL6040_REG_INTID, intid);
135
136 return IRQ_HANDLED;
137}
138
139int twl6040_irq_init(struct twl6040 *twl6040)
140{
141 int cur_irq, ret;
142 u8 val;
143
144 mutex_init(&twl6040->irq_mutex);
145
146 /* mask the individual interrupt sources */
147 twl6040->irq_masks_cur = TWL6040_ALLINT_MSK;
148 twl6040->irq_masks_cache = TWL6040_ALLINT_MSK;
149 twl6040_reg_write(twl6040, TWL6040_REG_INTMR, TWL6040_ALLINT_MSK);
150
151 if (!twl6040->irq) {
152 dev_warn(twl6040->dev,
153 "no interrupt specified, no interrupts\n");
154 twl6040->irq_base = 0;
155 return 0;
156 }
157
158 if (!twl6040->irq_base) {
159 dev_err(twl6040->dev,
160 "no interrupt base specified, no interrupts\n");
161 return 0;
162 }
163
164 /* Register them with genirq */
165 for (cur_irq = twl6040->irq_base;
166 cur_irq < twl6040->irq_base + ARRAY_SIZE(twl6040_irqs);
167 cur_irq++) {
168 irq_set_chip_data(cur_irq, twl6040);
169 irq_set_chip_and_handler(cur_irq, &twl6040_irq_chip,
170 handle_level_irq);
171 irq_set_nested_thread(cur_irq, 1);
172
173 /* ARM needs us to explicitly flag the IRQ as valid
174 * and will set them noprobe when we do so. */
175#ifdef CONFIG_ARM
176 set_irq_flags(cur_irq, IRQF_VALID);
177#else
178 irq_set_noprobe(cur_irq);
179#endif
180 }
181
182 ret = request_threaded_irq(twl6040->irq, NULL, twl6040_irq_thread,
183 IRQF_ONESHOT, "twl6040", twl6040);
184 if (ret) {
185 dev_err(twl6040->dev, "failed to request IRQ %d: %d\n",
186 twl6040->irq, ret);
187 return ret;
188 }
189
190 /* reset interrupts */
191 val = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
192
193 /* interrupts cleared on write */
194 twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_INTCLRMODE);
195
196 return 0;
197}
198EXPORT_SYMBOL(twl6040_irq_init);
199
200void twl6040_irq_exit(struct twl6040 *twl6040)
201{
202 if (twl6040->irq)
203 free_irq(twl6040->irq, twl6040);
204}
205EXPORT_SYMBOL(twl6040_irq_exit);