diff options
Diffstat (limited to 'drivers/irqchip/irq-meson-gpio.c')
-rw-r--r-- | drivers/irqchip/irq-meson-gpio.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c new file mode 100644 index 000000000000..a59bdbc0b9bb --- /dev/null +++ b/drivers/irqchip/irq-meson-gpio.c | |||
@@ -0,0 +1,419 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2015 Endless Mobile, Inc. | ||
3 | * Author: Carlo Caione <carlo@endlessm.com> | ||
4 | * Copyright (c) 2016 BayLibre, SAS. | ||
5 | * Author: Jerome Brunet <jbrunet@baylibre.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of version 2 of the GNU General Public License as | ||
9 | * published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | * The full GNU General Public License is included in this distribution | ||
19 | * in the file called COPYING. | ||
20 | */ | ||
21 | |||
22 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
23 | |||
24 | #include <linux/io.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/irq.h> | ||
27 | #include <linux/irqdomain.h> | ||
28 | #include <linux/irqchip.h> | ||
29 | #include <linux/of.h> | ||
30 | #include <linux/of_address.h> | ||
31 | |||
32 | #define NUM_CHANNEL 8 | ||
33 | #define MAX_INPUT_MUX 256 | ||
34 | |||
35 | #define REG_EDGE_POL 0x00 | ||
36 | #define REG_PIN_03_SEL 0x04 | ||
37 | #define REG_PIN_47_SEL 0x08 | ||
38 | #define REG_FILTER_SEL 0x0c | ||
39 | |||
40 | #define REG_EDGE_POL_MASK(x) (BIT(x) | BIT(16 + (x))) | ||
41 | #define REG_EDGE_POL_EDGE(x) BIT(x) | ||
42 | #define REG_EDGE_POL_LOW(x) BIT(16 + (x)) | ||
43 | #define REG_PIN_SEL_SHIFT(x) (((x) % 4) * 8) | ||
44 | #define REG_FILTER_SEL_SHIFT(x) ((x) * 4) | ||
45 | |||
46 | struct meson_gpio_irq_params { | ||
47 | unsigned int nr_hwirq; | ||
48 | }; | ||
49 | |||
50 | static const struct meson_gpio_irq_params meson8_params = { | ||
51 | .nr_hwirq = 134, | ||
52 | }; | ||
53 | |||
54 | static const struct meson_gpio_irq_params meson8b_params = { | ||
55 | .nr_hwirq = 119, | ||
56 | }; | ||
57 | |||
58 | static const struct meson_gpio_irq_params gxbb_params = { | ||
59 | .nr_hwirq = 133, | ||
60 | }; | ||
61 | |||
62 | static const struct meson_gpio_irq_params gxl_params = { | ||
63 | .nr_hwirq = 110, | ||
64 | }; | ||
65 | |||
66 | static const struct of_device_id meson_irq_gpio_matches[] = { | ||
67 | { .compatible = "amlogic,meson8-gpio-intc", .data = &meson8_params }, | ||
68 | { .compatible = "amlogic,meson8b-gpio-intc", .data = &meson8b_params }, | ||
69 | { .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_params }, | ||
70 | { .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_params }, | ||
71 | { } | ||
72 | }; | ||
73 | |||
74 | struct meson_gpio_irq_controller { | ||
75 | unsigned int nr_hwirq; | ||
76 | void __iomem *base; | ||
77 | u32 channel_irqs[NUM_CHANNEL]; | ||
78 | DECLARE_BITMAP(channel_map, NUM_CHANNEL); | ||
79 | spinlock_t lock; | ||
80 | }; | ||
81 | |||
82 | static void meson_gpio_irq_update_bits(struct meson_gpio_irq_controller *ctl, | ||
83 | unsigned int reg, u32 mask, u32 val) | ||
84 | { | ||
85 | u32 tmp; | ||
86 | |||
87 | tmp = readl_relaxed(ctl->base + reg); | ||
88 | tmp &= ~mask; | ||
89 | tmp |= val; | ||
90 | writel_relaxed(tmp, ctl->base + reg); | ||
91 | } | ||
92 | |||
93 | static unsigned int meson_gpio_irq_channel_to_reg(unsigned int channel) | ||
94 | { | ||
95 | return (channel < 4) ? REG_PIN_03_SEL : REG_PIN_47_SEL; | ||
96 | } | ||
97 | |||
98 | static int | ||
99 | meson_gpio_irq_request_channel(struct meson_gpio_irq_controller *ctl, | ||
100 | unsigned long hwirq, | ||
101 | u32 **channel_hwirq) | ||
102 | { | ||
103 | unsigned int reg, idx; | ||
104 | |||
105 | spin_lock(&ctl->lock); | ||
106 | |||
107 | /* Find a free channel */ | ||
108 | idx = find_first_zero_bit(ctl->channel_map, NUM_CHANNEL); | ||
109 | if (idx >= NUM_CHANNEL) { | ||
110 | spin_unlock(&ctl->lock); | ||
111 | pr_err("No channel available\n"); | ||
112 | return -ENOSPC; | ||
113 | } | ||
114 | |||
115 | /* Mark the channel as used */ | ||
116 | set_bit(idx, ctl->channel_map); | ||
117 | |||
118 | /* | ||
119 | * Setup the mux of the channel to route the signal of the pad | ||
120 | * to the appropriate input of the GIC | ||
121 | */ | ||
122 | reg = meson_gpio_irq_channel_to_reg(idx); | ||
123 | meson_gpio_irq_update_bits(ctl, reg, | ||
124 | 0xff << REG_PIN_SEL_SHIFT(idx), | ||
125 | hwirq << REG_PIN_SEL_SHIFT(idx)); | ||
126 | |||
127 | /* | ||
128 | * Get the hwirq number assigned to this channel through | ||
129 | * a pointer the channel_irq table. The added benifit of this | ||
130 | * method is that we can also retrieve the channel index with | ||
131 | * it, using the table base. | ||
132 | */ | ||
133 | *channel_hwirq = &(ctl->channel_irqs[idx]); | ||
134 | |||
135 | spin_unlock(&ctl->lock); | ||
136 | |||
137 | pr_debug("hwirq %lu assigned to channel %d - irq %u\n", | ||
138 | hwirq, idx, **channel_hwirq); | ||
139 | |||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | static unsigned int | ||
144 | meson_gpio_irq_get_channel_idx(struct meson_gpio_irq_controller *ctl, | ||
145 | u32 *channel_hwirq) | ||
146 | { | ||
147 | return channel_hwirq - ctl->channel_irqs; | ||
148 | } | ||
149 | |||
150 | static void | ||
151 | meson_gpio_irq_release_channel(struct meson_gpio_irq_controller *ctl, | ||
152 | u32 *channel_hwirq) | ||
153 | { | ||
154 | unsigned int idx; | ||
155 | |||
156 | idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq); | ||
157 | clear_bit(idx, ctl->channel_map); | ||
158 | } | ||
159 | |||
160 | static int meson_gpio_irq_type_setup(struct meson_gpio_irq_controller *ctl, | ||
161 | unsigned int type, | ||
162 | u32 *channel_hwirq) | ||
163 | { | ||
164 | u32 val = 0; | ||
165 | unsigned int idx; | ||
166 | |||
167 | idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq); | ||
168 | |||
169 | /* | ||
170 | * The controller has a filter block to operate in either LEVEL or | ||
171 | * EDGE mode, then signal is sent to the GIC. To enable LEVEL_LOW and | ||
172 | * EDGE_FALLING support (which the GIC does not support), the filter | ||
173 | * block is also able to invert the input signal it gets before | ||
174 | * providing it to the GIC. | ||
175 | */ | ||
176 | type &= IRQ_TYPE_SENSE_MASK; | ||
177 | |||
178 | if (type == IRQ_TYPE_EDGE_BOTH) | ||
179 | return -EINVAL; | ||
180 | |||
181 | if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) | ||
182 | val |= REG_EDGE_POL_EDGE(idx); | ||
183 | |||
184 | if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING)) | ||
185 | val |= REG_EDGE_POL_LOW(idx); | ||
186 | |||
187 | spin_lock(&ctl->lock); | ||
188 | |||
189 | meson_gpio_irq_update_bits(ctl, REG_EDGE_POL, | ||
190 | REG_EDGE_POL_MASK(idx), val); | ||
191 | |||
192 | spin_unlock(&ctl->lock); | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static unsigned int meson_gpio_irq_type_output(unsigned int type) | ||
198 | { | ||
199 | unsigned int sense = type & IRQ_TYPE_SENSE_MASK; | ||
200 | |||
201 | type &= ~IRQ_TYPE_SENSE_MASK; | ||
202 | |||
203 | /* | ||
204 | * The polarity of the signal provided to the GIC should always | ||
205 | * be high. | ||
206 | */ | ||
207 | if (sense & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) | ||
208 | type |= IRQ_TYPE_LEVEL_HIGH; | ||
209 | else if (sense & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) | ||
210 | type |= IRQ_TYPE_EDGE_RISING; | ||
211 | |||
212 | return type; | ||
213 | } | ||
214 | |||
215 | static int meson_gpio_irq_set_type(struct irq_data *data, unsigned int type) | ||
216 | { | ||
217 | struct meson_gpio_irq_controller *ctl = data->domain->host_data; | ||
218 | u32 *channel_hwirq = irq_data_get_irq_chip_data(data); | ||
219 | int ret; | ||
220 | |||
221 | ret = meson_gpio_irq_type_setup(ctl, type, channel_hwirq); | ||
222 | if (ret) | ||
223 | return ret; | ||
224 | |||
225 | return irq_chip_set_type_parent(data, | ||
226 | meson_gpio_irq_type_output(type)); | ||
227 | } | ||
228 | |||
229 | static struct irq_chip meson_gpio_irq_chip = { | ||
230 | .name = "meson-gpio-irqchip", | ||
231 | .irq_mask = irq_chip_mask_parent, | ||
232 | .irq_unmask = irq_chip_unmask_parent, | ||
233 | .irq_eoi = irq_chip_eoi_parent, | ||
234 | .irq_set_type = meson_gpio_irq_set_type, | ||
235 | .irq_retrigger = irq_chip_retrigger_hierarchy, | ||
236 | #ifdef CONFIG_SMP | ||
237 | .irq_set_affinity = irq_chip_set_affinity_parent, | ||
238 | #endif | ||
239 | .flags = IRQCHIP_SET_TYPE_MASKED, | ||
240 | }; | ||
241 | |||
242 | static int meson_gpio_irq_domain_translate(struct irq_domain *domain, | ||
243 | struct irq_fwspec *fwspec, | ||
244 | unsigned long *hwirq, | ||
245 | unsigned int *type) | ||
246 | { | ||
247 | if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) { | ||
248 | *hwirq = fwspec->param[0]; | ||
249 | *type = fwspec->param[1]; | ||
250 | return 0; | ||
251 | } | ||
252 | |||
253 | return -EINVAL; | ||
254 | } | ||
255 | |||
256 | static int meson_gpio_irq_allocate_gic_irq(struct irq_domain *domain, | ||
257 | unsigned int virq, | ||
258 | u32 hwirq, | ||
259 | unsigned int type) | ||
260 | { | ||
261 | struct irq_fwspec fwspec; | ||
262 | |||
263 | fwspec.fwnode = domain->parent->fwnode; | ||
264 | fwspec.param_count = 3; | ||
265 | fwspec.param[0] = 0; /* SPI */ | ||
266 | fwspec.param[1] = hwirq; | ||
267 | fwspec.param[2] = meson_gpio_irq_type_output(type); | ||
268 | |||
269 | return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); | ||
270 | } | ||
271 | |||
272 | static int meson_gpio_irq_domain_alloc(struct irq_domain *domain, | ||
273 | unsigned int virq, | ||
274 | unsigned int nr_irqs, | ||
275 | void *data) | ||
276 | { | ||
277 | struct irq_fwspec *fwspec = data; | ||
278 | struct meson_gpio_irq_controller *ctl = domain->host_data; | ||
279 | unsigned long hwirq; | ||
280 | u32 *channel_hwirq; | ||
281 | unsigned int type; | ||
282 | int ret; | ||
283 | |||
284 | if (WARN_ON(nr_irqs != 1)) | ||
285 | return -EINVAL; | ||
286 | |||
287 | ret = meson_gpio_irq_domain_translate(domain, fwspec, &hwirq, &type); | ||
288 | if (ret) | ||
289 | return ret; | ||
290 | |||
291 | ret = meson_gpio_irq_request_channel(ctl, hwirq, &channel_hwirq); | ||
292 | if (ret) | ||
293 | return ret; | ||
294 | |||
295 | ret = meson_gpio_irq_allocate_gic_irq(domain, virq, | ||
296 | *channel_hwirq, type); | ||
297 | if (ret < 0) { | ||
298 | pr_err("failed to allocate gic irq %u\n", *channel_hwirq); | ||
299 | meson_gpio_irq_release_channel(ctl, channel_hwirq); | ||
300 | return ret; | ||
301 | } | ||
302 | |||
303 | irq_domain_set_hwirq_and_chip(domain, virq, hwirq, | ||
304 | &meson_gpio_irq_chip, channel_hwirq); | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static void meson_gpio_irq_domain_free(struct irq_domain *domain, | ||
310 | unsigned int virq, | ||
311 | unsigned int nr_irqs) | ||
312 | { | ||
313 | struct meson_gpio_irq_controller *ctl = domain->host_data; | ||
314 | struct irq_data *irq_data; | ||
315 | u32 *channel_hwirq; | ||
316 | |||
317 | if (WARN_ON(nr_irqs != 1)) | ||
318 | return; | ||
319 | |||
320 | irq_domain_free_irqs_parent(domain, virq, 1); | ||
321 | |||
322 | irq_data = irq_domain_get_irq_data(domain, virq); | ||
323 | channel_hwirq = irq_data_get_irq_chip_data(irq_data); | ||
324 | |||
325 | meson_gpio_irq_release_channel(ctl, channel_hwirq); | ||
326 | } | ||
327 | |||
328 | static const struct irq_domain_ops meson_gpio_irq_domain_ops = { | ||
329 | .alloc = meson_gpio_irq_domain_alloc, | ||
330 | .free = meson_gpio_irq_domain_free, | ||
331 | .translate = meson_gpio_irq_domain_translate, | ||
332 | }; | ||
333 | |||
334 | static int __init meson_gpio_irq_parse_dt(struct device_node *node, | ||
335 | struct meson_gpio_irq_controller *ctl) | ||
336 | { | ||
337 | const struct of_device_id *match; | ||
338 | const struct meson_gpio_irq_params *params; | ||
339 | int ret; | ||
340 | |||
341 | match = of_match_node(meson_irq_gpio_matches, node); | ||
342 | if (!match) | ||
343 | return -ENODEV; | ||
344 | |||
345 | params = match->data; | ||
346 | ctl->nr_hwirq = params->nr_hwirq; | ||
347 | |||
348 | ret = of_property_read_variable_u32_array(node, | ||
349 | "amlogic,channel-interrupts", | ||
350 | ctl->channel_irqs, | ||
351 | NUM_CHANNEL, | ||
352 | NUM_CHANNEL); | ||
353 | if (ret < 0) { | ||
354 | pr_err("can't get %d channel interrupts\n", NUM_CHANNEL); | ||
355 | return ret; | ||
356 | } | ||
357 | |||
358 | return 0; | ||
359 | } | ||
360 | |||
361 | static int __init meson_gpio_irq_of_init(struct device_node *node, | ||
362 | struct device_node *parent) | ||
363 | { | ||
364 | struct irq_domain *domain, *parent_domain; | ||
365 | struct meson_gpio_irq_controller *ctl; | ||
366 | int ret; | ||
367 | |||
368 | if (!parent) { | ||
369 | pr_err("missing parent interrupt node\n"); | ||
370 | return -ENODEV; | ||
371 | } | ||
372 | |||
373 | parent_domain = irq_find_host(parent); | ||
374 | if (!parent_domain) { | ||
375 | pr_err("unable to obtain parent domain\n"); | ||
376 | return -ENXIO; | ||
377 | } | ||
378 | |||
379 | ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); | ||
380 | if (!ctl) | ||
381 | return -ENOMEM; | ||
382 | |||
383 | spin_lock_init(&ctl->lock); | ||
384 | |||
385 | ctl->base = of_iomap(node, 0); | ||
386 | if (!ctl->base) { | ||
387 | ret = -ENOMEM; | ||
388 | goto free_ctl; | ||
389 | } | ||
390 | |||
391 | ret = meson_gpio_irq_parse_dt(node, ctl); | ||
392 | if (ret) | ||
393 | goto free_channel_irqs; | ||
394 | |||
395 | domain = irq_domain_create_hierarchy(parent_domain, 0, ctl->nr_hwirq, | ||
396 | of_node_to_fwnode(node), | ||
397 | &meson_gpio_irq_domain_ops, | ||
398 | ctl); | ||
399 | if (!domain) { | ||
400 | pr_err("failed to add domain\n"); | ||
401 | ret = -ENODEV; | ||
402 | goto free_channel_irqs; | ||
403 | } | ||
404 | |||
405 | pr_info("%d to %d gpio interrupt mux initialized\n", | ||
406 | ctl->nr_hwirq, NUM_CHANNEL); | ||
407 | |||
408 | return 0; | ||
409 | |||
410 | free_channel_irqs: | ||
411 | iounmap(ctl->base); | ||
412 | free_ctl: | ||
413 | kfree(ctl); | ||
414 | |||
415 | return ret; | ||
416 | } | ||
417 | |||
418 | IRQCHIP_DECLARE(meson_gpio_intc, "amlogic,meson-gpio-intc", | ||
419 | meson_gpio_irq_of_init); | ||