aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2011-10-28 17:50:49 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2011-11-08 06:29:48 -0500
commitf8beab2bb611d735767871e0e1a12dc6a0def7b1 (patch)
treea30103e40ae9d4473025edad6a2b3a28e3133fb5 /drivers/base
parent1ea6b8f48918282bdca0b32a34095504ee65bab5 (diff)
regmap: Add a reusable irq_chip for regmap based interrupt controllers
There seem to be lots of regmap-using devices with very similar interrupt controllers with a small bank of interrupt registers and mask registers with an interrupt per bit. This won't cover everything but it's a good start. Each chip supplies a base for the status registers, a base for the mask registers, an optional base for writing acknowledgements (which may be the same as the status registers) and an array of bits within each of these register banks which indicate the interrupt. There is an assumption that the bit for each interrupt will be the same in each of the register bank. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/regmap/Kconfig3
-rw-r--r--drivers/base/regmap/Makefile1
-rw-r--r--drivers/base/regmap/regmap-irq.c284
3 files changed, 288 insertions, 0 deletions
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index 2fc6a66f39a4..0f6c7fb418e8 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -13,3 +13,6 @@ config REGMAP_I2C
13 13
14config REGMAP_SPI 14config REGMAP_SPI
15 tristate 15 tristate
16
17config REGMAP_IRQ
18 bool
diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
index 0573c8a9dacb..ce2d18a6465b 100644
--- a/drivers/base/regmap/Makefile
+++ b/drivers/base/regmap/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o regcache-rbtree.o
2obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o 2obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o
3obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o 3obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o
4obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o 4obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o
5obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o
diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c
new file mode 100644
index 000000000000..bd54f63be9ed
--- /dev/null
+++ b/drivers/base/regmap/regmap-irq.c
@@ -0,0 +1,284 @@
1/*
2 * regmap based irq_chip
3 *
4 * Copyright 2011 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
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 */
12
13#include <linux/export.h>
14#include <linux/regmap.h>
15#include <linux/irq.h>
16#include <linux/interrupt.h>
17#include <linux/slab.h>
18
19#include "internal.h"
20
21struct regmap_irq_chip_data {
22 struct mutex lock;
23
24 struct regmap *map;
25 struct regmap_irq_chip *chip;
26
27 int irq_base;
28
29 void *status_reg_buf;
30 unsigned int *status_buf;
31 unsigned int *mask_buf;
32 unsigned int *mask_buf_def;
33};
34
35static inline const
36struct regmap_irq *irq_to_regmap_irq(struct regmap_irq_chip_data *data,
37 int irq)
38{
39 return &data->chip->irqs[irq - data->irq_base];
40}
41
42static void regmap_irq_lock(struct irq_data *data)
43{
44 struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
45
46 mutex_lock(&d->lock);
47}
48
49static void regmap_irq_sync_unlock(struct irq_data *data)
50{
51 struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
52 int i, ret;
53
54 /*
55 * If there's been a change in the mask write it back to the
56 * hardware. We rely on the use of the regmap core cache to
57 * suppress pointless writes.
58 */
59 for (i = 0; i < d->chip->num_regs; i++) {
60 ret = regmap_update_bits(d->map, d->chip->mask_base + i,
61 d->mask_buf_def[i], d->mask_buf[i]);
62 if (ret != 0)
63 dev_err(d->map->dev, "Failed to sync masks in %x\n",
64 d->chip->mask_base + i);
65 }
66
67 mutex_unlock(&d->lock);
68}
69
70static void regmap_irq_enable(struct irq_data *data)
71{
72 struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
73 const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->irq);
74
75 d->mask_buf[irq_data->reg_offset] &= ~irq_data->mask;
76}
77
78static void regmap_irq_disable(struct irq_data *data)
79{
80 struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
81 const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->irq);
82
83 d->mask_buf[irq_data->reg_offset] |= irq_data->mask;
84}
85
86static struct irq_chip regmap_irq_chip = {
87 .name = "regmap",
88 .irq_bus_lock = regmap_irq_lock,
89 .irq_bus_sync_unlock = regmap_irq_sync_unlock,
90 .irq_disable = regmap_irq_disable,
91 .irq_enable = regmap_irq_enable,
92};
93
94static irqreturn_t regmap_irq_thread(int irq, void *d)
95{
96 struct regmap_irq_chip_data *data = d;
97 struct regmap_irq_chip *chip = data->chip;
98 struct regmap *map = data->map;
99 int ret, i;
100 u8 *buf8 = data->status_reg_buf;
101 u16 *buf16 = data->status_reg_buf;
102 u32 *buf32 = data->status_reg_buf;
103
104 ret = regmap_bulk_read(map, chip->status_base, data->status_reg_buf,
105 chip->num_regs);
106 if (ret != 0) {
107 dev_err(map->dev, "Failed to read IRQ status: %d\n", ret);
108 return IRQ_NONE;
109 }
110
111 /*
112 * Ignore masked IRQs and ack if we need to; we ack early so
113 * there is no race between handling and acknowleding the
114 * interrupt. We assume that typically few of the interrupts
115 * will fire simultaneously so don't worry about overhead from
116 * doing a write per register.
117 */
118 for (i = 0; i < data->chip->num_regs; i++) {
119 switch (map->format.val_bytes) {
120 case 1:
121 data->status_buf[i] = buf8[i];
122 break;
123 case 2:
124 data->status_buf[i] = buf16[i];
125 break;
126 case 4:
127 data->status_buf[i] = buf32[i];
128 break;
129 default:
130 BUG();
131 return IRQ_NONE;
132 }
133
134 data->status_buf[i] &= ~data->mask_buf[i];
135
136 if (data->status_buf[i] && chip->ack_base) {
137 ret = regmap_write(map, chip->ack_base + i,
138 data->status_buf[i]);
139 if (ret != 0)
140 dev_err(map->dev, "Failed to ack 0x%x: %d\n",
141 chip->ack_base + i, ret);
142 }
143 }
144
145 for (i = 0; i < chip->num_irqs; i++) {
146 if (data->status_buf[chip->irqs[i].reg_offset] &
147 chip->irqs[i].mask) {
148 handle_nested_irq(data->irq_base + i);
149 }
150 }
151
152 return IRQ_HANDLED;
153}
154
155/**
156 * regmap_add_irq_chip(): Use standard regmap IRQ controller handling
157 *
158 * map: The regmap for the device.
159 * irq: The IRQ the device uses to signal interrupts
160 * irq_flags: The IRQF_ flags to use for the primary interrupt.
161 * chip: Configuration for the interrupt controller.
162 * data: Runtime data structure for the controller, allocated on success
163 *
164 * Returns 0 on success or an errno on failure.
165 *
166 * In order for this to be efficient the chip really should use a
167 * register cache. The chip driver is responsible for restoring the
168 * register values used by the IRQ controller over suspend and resume.
169 */
170int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
171 int irq_base, struct regmap_irq_chip *chip,
172 struct regmap_irq_chip_data **data)
173{
174 struct regmap_irq_chip_data *d;
175 int cur_irq, i;
176 int ret = -ENOMEM;
177
178 irq_base = irq_alloc_descs(irq_base, 0, chip->num_irqs, 0);
179 if (irq_base < 0) {
180 dev_warn(map->dev, "Failed to allocate IRQs: %d\n",
181 irq_base);
182 return irq_base;
183 }
184
185 d = kzalloc(sizeof(*d), GFP_KERNEL);
186 if (!d)
187 return -ENOMEM;
188
189 d->status_buf = kzalloc(sizeof(unsigned int) * chip->num_regs,
190 GFP_KERNEL);
191 if (!d->status_buf)
192 goto err_alloc;
193
194 d->status_reg_buf = kzalloc(map->format.val_bytes * chip->num_regs,
195 GFP_KERNEL);
196 if (!d->status_reg_buf)
197 goto err_alloc;
198
199 d->mask_buf = kzalloc(sizeof(unsigned int) * chip->num_regs,
200 GFP_KERNEL);
201 if (!d->mask_buf)
202 goto err_alloc;
203
204 d->mask_buf_def = kzalloc(sizeof(unsigned int) * chip->num_regs,
205 GFP_KERNEL);
206 if (!d->mask_buf_def)
207 goto err_alloc;
208
209 d->map = map;
210 d->chip = chip;
211 d->irq_base = irq_base;
212 mutex_init(&d->lock);
213
214 for (i = 0; i < chip->num_irqs; i++)
215 d->mask_buf_def[chip->irqs[i].reg_offset]
216 |= chip->irqs[i].mask;
217
218 /* Mask all the interrupts by default */
219 for (i = 0; i < chip->num_regs; i++) {
220 d->mask_buf[i] = d->mask_buf_def[i];
221 ret = regmap_write(map, chip->mask_base + i, d->mask_buf[i]);
222 if (ret != 0) {
223 dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
224 chip->mask_base + i, ret);
225 goto err_alloc;
226 }
227 }
228
229 /* Register them with genirq */
230 for (cur_irq = irq_base;
231 cur_irq < chip->num_irqs + irq_base;
232 cur_irq++) {
233 irq_set_chip_data(cur_irq, d);
234 irq_set_chip_and_handler(cur_irq, &regmap_irq_chip,
235 handle_edge_irq);
236 irq_set_nested_thread(cur_irq, 1);
237
238 /* ARM needs us to explicitly flag the IRQ as valid
239 * and will set them noprobe when we do so. */
240#ifdef CONFIG_ARM
241 set_irq_flags(cur_irq, IRQF_VALID);
242#else
243 irq_set_noprobe(cur_irq);
244#endif
245 }
246
247 ret = request_threaded_irq(irq, NULL, regmap_irq_thread, irq_flags,
248 chip->name, d);
249 if (ret != 0) {
250 dev_err(map->dev, "Failed to request IRQ %d: %d\n", irq, ret);
251 goto err_alloc;
252 }
253
254 return 0;
255
256err_alloc:
257 kfree(d->mask_buf_def);
258 kfree(d->mask_buf);
259 kfree(d->status_reg_buf);
260 kfree(d->status_buf);
261 kfree(d);
262 return ret;
263}
264EXPORT_SYMBOL_GPL(regmap_add_irq_chip);
265
266/**
267 * regmap_del_irq_chip(): Stop interrupt handling for a regmap IRQ chip
268 *
269 * @irq: Primary IRQ for the device
270 * @d: regmap_irq_chip_data allocated by regmap_add_irq_chip()
271 */
272void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d)
273{
274 if (!d)
275 return;
276
277 free_irq(irq, d);
278 kfree(d->mask_buf_def);
279 kfree(d->mask_buf);
280 kfree(d->status_reg_buf);
281 kfree(d->status_buf);
282 kfree(d);
283}
284EXPORT_SYMBOL_GPL(regmap_del_irq_chip);