OpenWrt – Blame information for rev 2
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | From cf908990d4a8ccdb73ee4484aa8cadad379ca314 Mon Sep 17 00:00:00 2001 |
2 | From: Jonas Gorski <jogo@openwrt.org> |
||
3 | Date: Sun, 30 Nov 2014 14:54:27 +0100 |
||
4 | Subject: [PATCH 2/5] irqchip: add support for bcm6345-style external |
||
5 | interrupt controller |
||
6 | |||
7 | Signed-off-by: Jonas Gorski <jogo@openwrt.org> |
||
8 | --- |
||
9 | .../interrupt-controller/brcm,bcm6345-ext-intc.txt | 29 ++ |
||
10 | drivers/irqchip/Kconfig | 4 + |
||
11 | drivers/irqchip/Makefile | 1 + |
||
12 | drivers/irqchip/irq-bcm6345-ext.c | 287 ++++++++++++++++++++ |
||
13 | include/linux/irqchip/irq-bcm6345-ext.h | 14 + |
||
14 | 5 files changed, 335 insertions(+) |
||
15 | create mode 100644 Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-ext-intc.txt |
||
16 | create mode 100644 drivers/irqchip/irq-bcm6345-ext.c |
||
17 | create mode 100644 include/linux/irqchip/irq-bcm6345-ext.h |
||
18 | |||
19 | --- /dev/null |
||
20 | +++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-ext-intc.txt |
||
21 | @@ -0,0 +1,29 @@ |
||
22 | +Broadcom BCM6345-style external interrupt controller |
||
23 | + |
||
24 | +Required properties: |
||
25 | + |
||
26 | +- compatible: Should be "brcm,bcm6345-ext-intc" or "brcm,bcm6318-ext-intc". |
||
27 | +- reg: Specifies the base physical addresses and size of the registers. |
||
28 | +- interrupt-controller: identifies the node as an interrupt controller. |
||
29 | +- #interrupt-cells: Specifies the number of cells needed to encode an interrupt |
||
30 | + source, Should be 2. |
||
31 | +- interrupt-parent: Specifies the phandle to the parent interrupt controller |
||
32 | + this one is cascaded from. |
||
33 | +- interrupts: Specifies the interrupt line(s) in the interrupt-parent controller |
||
34 | + node, valid values depend on the type of parent interrupt controller. |
||
35 | + |
||
36 | +Optional properties: |
||
37 | + |
||
38 | +- brcm,field-width: Size of each field (mask, clear, sense, ...) in bits in the |
||
39 | + register. Defaults to 4. |
||
40 | + |
||
41 | +Example: |
||
42 | + |
||
43 | +ext_intc: interrupt-controller@10000018 { |
||
44 | + compatible = "brcm,bcm6345-ext-intc"; |
||
45 | + interrupt-parent = <&periph_intc>; |
||
46 | + #interrupt-cells = <2>; |
||
47 | + reg = <0x10000018 0x4>; |
||
48 | + interrupt-controller; |
||
49 | + interrupts = <24>, <25>, <26>, <27>; |
||
50 | +}; |
||
51 | --- a/drivers/irqchip/Kconfig |
||
52 | +++ b/drivers/irqchip/Kconfig |
||
53 | @@ -110,6 +110,10 @@ config BRCMSTB_L2_IRQ |
||
54 | select GENERIC_IRQ_CHIP |
||
55 | select IRQ_DOMAIN |
||
56 | |||
57 | +config BCM6345_EXT_IRQ |
||
58 | + bool |
||
59 | + select IRQ_DOMAIN |
||
60 | + |
||
61 | config BCM6345_PERIPH_IRQ |
||
62 | bool |
||
63 | select IRQ_DOMAIN |
||
64 | --- a/drivers/irqchip/Makefile |
||
65 | +++ b/drivers/irqchip/Makefile |
||
66 | @@ -12,6 +12,7 @@ obj-$(CONFIG_ARCH_MMP) += irq-mmp.o |
||
67 | obj-$(CONFIG_IRQ_MXS) += irq-mxs.o |
||
68 | obj-$(CONFIG_ARCH_TEGRA) += irq-tegra.o |
||
69 | obj-$(CONFIG_ARCH_S3C24XX) += irq-s3c24xx.o |
||
70 | +obj-$(CONFIG_BCM6345_EXT_IRQ) += irq-bcm6345-ext.o |
||
71 | obj-$(CONFIG_BCM6345_PERIPH_IRQ) += irq-bcm6345-periph.o |
||
72 | obj-$(CONFIG_DW_APB_ICTL) += irq-dw-apb-ictl.o |
||
73 | obj-$(CONFIG_METAG) += irq-metag-ext.o |
||
74 | --- /dev/null |
||
75 | +++ b/drivers/irqchip/irq-bcm6345-ext.c |
||
76 | @@ -0,0 +1,301 @@ |
||
77 | +/* |
||
78 | + * This file is subject to the terms and conditions of the GNU General Public |
||
79 | + * License. See the file "COPYING" in the main directory of this archive |
||
80 | + * for more details. |
||
81 | + * |
||
82 | + * Copyright (C) 2014 Jonas Gorski <jogo@openwrt.org> |
||
83 | + */ |
||
84 | + |
||
85 | +#include <linux/ioport.h> |
||
86 | +#include <linux/irq.h> |
||
87 | +#include <linux/irqchip.h> |
||
88 | +#include <linux/irqchip/chained_irq.h> |
||
89 | +#include <linux/irqchip/irq-bcm6345-ext.h> |
||
90 | +#include <linux/kernel.h> |
||
91 | +#include <linux/of.h> |
||
92 | +#include <linux/of_irq.h> |
||
93 | +#include <linux/of_address.h> |
||
94 | +#include <linux/slab.h> |
||
95 | +#include <linux/spinlock.h> |
||
96 | + |
||
97 | +#ifdef CONFIG_BCM63XX |
||
98 | +#include <asm/mach-bcm63xx/bcm63xx_irq.h> |
||
99 | + |
||
100 | +#define VIRQ_BASE IRQ_EXTERNAL_BASE |
||
101 | +#else |
||
102 | +#define VIRQ_BASE 0 |
||
103 | +#endif |
||
104 | + |
||
105 | +#define MAX_IRQS 4 |
||
106 | + |
||
107 | +#define EXTIRQ_CFG_SENSE 0 |
||
108 | +#define EXTIRQ_CFG_STAT 1 |
||
109 | +#define EXTIRQ_CFG_CLEAR 2 |
||
110 | +#define EXTIRQ_CFG_MASK 3 |
||
111 | +#define EXTIRQ_CFG_BOTHEDGE 4 |
||
112 | +#define EXTIRQ_CFG_LEVELSENSE 5 |
||
113 | + |
||
114 | +struct intc_data { |
||
115 | + struct irq_chip chip; |
||
116 | + struct irq_domain *domain; |
||
117 | + raw_spinlock_t lock; |
||
118 | + |
||
119 | + int parent_irq[MAX_IRQS]; |
||
120 | + void __iomem *reg; |
||
121 | + int shift; |
||
122 | + unsigned int toggle_clear_on_ack:1; |
||
123 | +}; |
||
124 | + |
||
125 | +static void bcm6345_ext_intc_irq_handle(struct irq_desc *desc) |
||
126 | +{ |
||
127 | + struct intc_data *data = irq_desc_get_handler_data(desc); |
||
128 | + struct irq_chip *chip = irq_desc_get_chip(desc); |
||
129 | + unsigned int irq = irq_desc_get_irq(desc); |
||
130 | + unsigned int idx; |
||
131 | + |
||
132 | + chained_irq_enter(chip, desc); |
||
133 | + |
||
134 | + for (idx = 0; idx < MAX_IRQS; idx++) { |
||
135 | + if (data->parent_irq[idx] != irq) |
||
136 | + continue; |
||
137 | + |
||
138 | + generic_handle_irq(irq_find_mapping(data->domain, idx)); |
||
139 | + } |
||
140 | + |
||
141 | + chained_irq_exit(chip, desc); |
||
142 | +} |
||
143 | + |
||
144 | +static void bcm6345_ext_intc_irq_ack(struct irq_data *data) |
||
145 | +{ |
||
146 | + struct intc_data *priv = data->domain->host_data; |
||
147 | + irq_hw_number_t hwirq = irqd_to_hwirq(data); |
||
148 | + u32 reg; |
||
149 | + |
||
150 | + raw_spin_lock(&priv->lock); |
||
151 | + reg = __raw_readl(priv->reg); |
||
152 | + __raw_writel(reg | (1 << (hwirq + EXTIRQ_CFG_CLEAR * priv->shift)), |
||
153 | + priv->reg); |
||
154 | + if (priv->toggle_clear_on_ack) |
||
155 | + __raw_writel(reg, priv->reg); |
||
156 | + raw_spin_unlock(&priv->lock); |
||
157 | +} |
||
158 | + |
||
159 | +static void bcm6345_ext_intc_irq_mask(struct irq_data *data) |
||
160 | +{ |
||
161 | + struct intc_data *priv = data->domain->host_data; |
||
162 | + irq_hw_number_t hwirq = irqd_to_hwirq(data); |
||
163 | + u32 reg; |
||
164 | + |
||
165 | + raw_spin_lock(&priv->lock); |
||
166 | + reg = __raw_readl(priv->reg); |
||
167 | + reg &= ~(1 << (hwirq + EXTIRQ_CFG_MASK * priv->shift)); |
||
168 | + __raw_writel(reg, priv->reg); |
||
169 | + raw_spin_unlock(&priv->lock); |
||
170 | +} |
||
171 | + |
||
172 | +static void bcm6345_ext_intc_irq_unmask(struct irq_data *data) |
||
173 | +{ |
||
174 | + struct intc_data *priv = data->domain->host_data; |
||
175 | + irq_hw_number_t hwirq = irqd_to_hwirq(data); |
||
176 | + u32 reg; |
||
177 | + |
||
178 | + raw_spin_lock(&priv->lock); |
||
179 | + reg = __raw_readl(priv->reg); |
||
180 | + reg |= 1 << (hwirq + EXTIRQ_CFG_MASK * priv->shift); |
||
181 | + __raw_writel(reg, priv->reg); |
||
182 | + raw_spin_unlock(&priv->lock); |
||
183 | +} |
||
184 | + |
||
185 | +static int bcm6345_ext_intc_set_type(struct irq_data *data, |
||
186 | + unsigned int flow_type) |
||
187 | +{ |
||
188 | + struct intc_data *priv = data->domain->host_data; |
||
189 | + irq_hw_number_t hwirq = irqd_to_hwirq(data); |
||
190 | + bool levelsense = 0, sense = 0, bothedge = 0; |
||
191 | + u32 reg; |
||
192 | + |
||
193 | + flow_type &= IRQ_TYPE_SENSE_MASK; |
||
194 | + |
||
195 | + if (flow_type == IRQ_TYPE_NONE) |
||
196 | + flow_type = IRQ_TYPE_LEVEL_LOW; |
||
197 | + |
||
198 | + switch (flow_type) { |
||
199 | + case IRQ_TYPE_EDGE_BOTH: |
||
200 | + bothedge = 1; |
||
201 | + break; |
||
202 | + |
||
203 | + case IRQ_TYPE_EDGE_RISING: |
||
204 | + sense = 1; |
||
205 | + break; |
||
206 | + |
||
207 | + case IRQ_TYPE_EDGE_FALLING: |
||
208 | + break; |
||
209 | + |
||
210 | + case IRQ_TYPE_LEVEL_HIGH: |
||
211 | + levelsense = 1; |
||
212 | + sense = 1; |
||
213 | + break; |
||
214 | + |
||
215 | + case IRQ_TYPE_LEVEL_LOW: |
||
216 | + levelsense = 1; |
||
217 | + break; |
||
218 | + |
||
219 | + default: |
||
220 | + pr_err("bogus flow type combination given!\n"); |
||
221 | + return -EINVAL; |
||
222 | + } |
||
223 | + |
||
224 | + raw_spin_lock(&priv->lock); |
||
225 | + reg = __raw_readl(priv->reg); |
||
226 | + |
||
227 | + if (levelsense) |
||
228 | + reg |= 1 << (hwirq + EXTIRQ_CFG_LEVELSENSE * priv->shift); |
||
229 | + else |
||
230 | + reg &= ~(1 << (hwirq + EXTIRQ_CFG_LEVELSENSE * priv->shift)); |
||
231 | + if (sense) |
||
232 | + reg |= 1 << (hwirq + EXTIRQ_CFG_SENSE * priv->shift); |
||
233 | + else |
||
234 | + reg &= ~(1 << (hwirq + EXTIRQ_CFG_SENSE * priv->shift)); |
||
235 | + if (bothedge) |
||
236 | + reg |= 1 << (hwirq + EXTIRQ_CFG_BOTHEDGE * priv->shift); |
||
237 | + else |
||
238 | + reg &= ~(1 << (hwirq + EXTIRQ_CFG_BOTHEDGE * priv->shift)); |
||
239 | + |
||
240 | + __raw_writel(reg, priv->reg); |
||
241 | + raw_spin_unlock(&priv->lock); |
||
242 | + |
||
243 | + irqd_set_trigger_type(data, flow_type); |
||
244 | + if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) |
||
245 | + irq_set_handler_locked(data, handle_level_irq); |
||
246 | + else |
||
247 | + irq_set_handler_locked(data, handle_edge_irq); |
||
248 | + |
||
249 | + return 0; |
||
250 | +} |
||
251 | + |
||
252 | +static int bcm6345_ext_intc_map(struct irq_domain *d, unsigned int irq, |
||
253 | + irq_hw_number_t hw) |
||
254 | +{ |
||
255 | + struct intc_data *priv = d->host_data; |
||
256 | + |
||
257 | + irq_set_chip_and_handler(irq, &priv->chip, handle_level_irq); |
||
258 | + |
||
259 | + return 0; |
||
260 | +} |
||
261 | + |
||
262 | +static const struct irq_domain_ops bcm6345_ext_domain_ops = { |
||
263 | + .xlate = irq_domain_xlate_twocell, |
||
264 | + .map = bcm6345_ext_intc_map, |
||
265 | +}; |
||
266 | + |
||
267 | +static int __init __bcm6345_ext_intc_init(struct device_node *node, |
||
268 | + int num_irqs, int *irqs, |
||
269 | + void __iomem *reg, int shift, |
||
270 | + bool toggle_clear_on_ack) |
||
271 | +{ |
||
272 | + struct intc_data *data; |
||
273 | + unsigned int i; |
||
274 | + int start = VIRQ_BASE; |
||
275 | + |
||
276 | + data = kzalloc(sizeof(*data), GFP_KERNEL); |
||
277 | + if (!data) |
||
278 | + return -ENOMEM; |
||
279 | + |
||
280 | + raw_spin_lock_init(&data->lock); |
||
281 | + |
||
282 | + for (i = 0; i < num_irqs; i++) { |
||
283 | + data->parent_irq[i] = irqs[i]; |
||
284 | + |
||
285 | + irq_set_handler_data(irqs[i], data); |
||
286 | + irq_set_chained_handler(irqs[i], bcm6345_ext_intc_irq_handle); |
||
287 | + } |
||
288 | + |
||
289 | + data->reg = reg; |
||
290 | + data->shift = shift; |
||
291 | + data->toggle_clear_on_ack = toggle_clear_on_ack; |
||
292 | + |
||
293 | + data->chip.name = "bcm6345-ext-intc"; |
||
294 | + data->chip.irq_ack = bcm6345_ext_intc_irq_ack; |
||
295 | + data->chip.irq_mask = bcm6345_ext_intc_irq_mask; |
||
296 | + data->chip.irq_unmask = bcm6345_ext_intc_irq_unmask; |
||
297 | + data->chip.irq_set_type = bcm6345_ext_intc_set_type; |
||
298 | + |
||
299 | + /* |
||
300 | + * If we have less than 4 irqs, this is the second controller on |
||
301 | + * bcm63xx. So increase the VIRQ start to not overlap with the first |
||
302 | + * one, but only do so if we actually use a non-zero start. |
||
303 | + * |
||
304 | + * This can be removed when bcm63xx has no legacy users anymore. |
||
305 | + */ |
||
306 | + if (start && num_irqs < 4) |
||
307 | + start += 4; |
||
308 | + |
||
309 | + data->domain = irq_domain_add_simple(node, num_irqs, start, |
||
310 | + &bcm6345_ext_domain_ops, data); |
||
311 | + if (!data->domain) { |
||
312 | + kfree(data); |
||
313 | + return -ENOMEM; |
||
314 | + } |
||
315 | + |
||
316 | + return 0; |
||
317 | +} |
||
318 | + |
||
319 | +void __init bcm6345_ext_intc_init(int num_irqs, int *irqs, void __iomem *reg, |
||
320 | + int shift) |
||
321 | +{ |
||
322 | + __bcm6345_ext_intc_init(NULL, num_irqs, irqs, reg, shift, false); |
||
323 | +} |
||
324 | + |
||
325 | +#ifdef CONFIG_OF |
||
326 | +static int __init bcm6345_ext_intc_of_init(struct device_node *node, |
||
327 | + struct device_node *parent) |
||
328 | +{ |
||
329 | + int num_irqs, ret = -EINVAL; |
||
330 | + unsigned i; |
||
331 | + void __iomem *base; |
||
332 | + int irqs[MAX_IRQS] = { 0 }; |
||
333 | + u32 shift; |
||
334 | + bool toggle_clear_on_ack = false; |
||
335 | + |
||
336 | + num_irqs = of_irq_count(node); |
||
337 | + |
||
338 | + if (!num_irqs || num_irqs > MAX_IRQS) |
||
339 | + return -EINVAL; |
||
340 | + |
||
341 | + if (of_property_read_u32(node, "brcm,field-width", &shift)) |
||
342 | + shift = 4; |
||
343 | + |
||
344 | + /* on BCM6318 setting CLEAR seems to continuously mask interrupts */ |
||
345 | + if (of_device_is_compatible(node, "brcm,bcm6318-ext-intc")) |
||
346 | + toggle_clear_on_ack = true; |
||
347 | + |
||
348 | + for (i = 0; i < num_irqs; i++) { |
||
349 | + irqs[i] = irq_of_parse_and_map(node, i); |
||
350 | + if (!irqs[i]) { |
||
351 | + ret = -ENOMEM; |
||
352 | + goto out_unmap; |
||
353 | + } |
||
354 | + } |
||
355 | + |
||
356 | + base = of_iomap(node, 0); |
||
357 | + if (!base) |
||
358 | + goto out_unmap; |
||
359 | + |
||
360 | + ret = __bcm6345_ext_intc_init(node, num_irqs, irqs, base, shift, |
||
361 | + toggle_clear_on_ack); |
||
362 | + if (!ret) |
||
363 | + return 0; |
||
364 | +out_unmap: |
||
365 | + iounmap(base); |
||
366 | + |
||
367 | + for (i = 0; i < num_irqs; i++) |
||
368 | + irq_dispose_mapping(irqs[i]); |
||
369 | + |
||
370 | + return ret; |
||
371 | +} |
||
372 | + |
||
373 | +IRQCHIP_DECLARE(bcm6318_ext_intc, "brcm,bcm6318-ext-intc", |
||
374 | + bcm6345_ext_intc_of_init); |
||
375 | +IRQCHIP_DECLARE(bcm6345_ext_intc, "brcm,bcm6345-ext-intc", |
||
376 | + bcm6345_ext_intc_of_init); |
||
377 | +#endif |
||
378 | --- /dev/null |
||
379 | +++ b/include/linux/irqchip/irq-bcm6345-ext.h |
||
380 | @@ -0,0 +1,14 @@ |
||
381 | +/* |
||
382 | + * This file is subject to the terms and conditions of the GNU General Public |
||
383 | + * License. See the file "COPYING" in the main directory of this archive |
||
384 | + * for more details. |
||
385 | + * |
||
386 | + * Copyright (C) 2014 Jonas Gorski <jogo@openwrt.org> |
||
387 | + */ |
||
388 | + |
||
389 | +#ifndef __INCLUDE_LINUX_IRQCHIP_IRQ_BCM6345_EXT_H |
||
390 | +#define __INCLUDE_LINUX_IRQCHIP_IRQ_BCM6345_EXT_H |
||
391 | + |
||
392 | +void bcm6345_ext_intc_init(int n_irqs, int *irqs, void __iomem *reg, int shift); |
||
393 | + |
||
394 | +#endif /* __INCLUDE_LINUX_IRQCHIP_IRQ_BCM6345_EXT_H */ |