aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCristian Birsan <cristian.birsan@microchip.com>2016-01-13 20:15:35 -0500
committerRalf Baechle <ralf@linux-mips.org>2016-01-23 20:52:20 -0500
commitaaa8666ada780e8a4a60870aa4379e5e29e395f3 (patch)
treea96279fec80f9ac5bbe50d11232f3b8adb4ee81d
parentedf2194dc9b9b527d003c47051bd4b09b61ddd55 (diff)
IRQCHIP: irq-pic32-evic: Add support for PIC32 interrupt controller
This adds support for the interrupt controller present on PIC32 class devices. It handles all internal and external interrupts. This controller exists outside of the CPU core and is the arbitrator of all interrupts (including interrupts from the CPU itself) before they are presented to the CPU. The following features are supported: - DT properties for EVIC and for devices/peripherals that use interrupt lines - Persistent and non-persistent interrupt handling - irqdomain and generic chip support - Configuration of external interrupt edge polarity Signed-off-by: Cristian Birsan <cristian.birsan@microchip.com> Signed-off-by: Joshua Henderson <joshua.henderson@microchip.com> Acked-by: Thomas Gleixner <tglx@linutronix.de> Cc: Jason Cooper <jason@lakedaemon.net> Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: linux-kernel@vger.kernel.org Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/12092/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
-rw-r--r--drivers/irqchip/Kconfig5
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-pic32-evic.c324
3 files changed, 330 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 4d7294e5d982..d5bafdd5f01f 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -117,6 +117,11 @@ config ORION_IRQCHIP
117 select IRQ_DOMAIN 117 select IRQ_DOMAIN
118 select MULTI_IRQ_HANDLER 118 select MULTI_IRQ_HANDLER
119 119
120config PIC32_EVIC
121 bool
122 select GENERIC_IRQ_CHIP
123 select IRQ_DOMAIN
124
120config RENESAS_INTC_IRQPIN 125config RENESAS_INTC_IRQPIN
121 bool 126 bool
122 select IRQ_DOMAIN 127 select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 177f78f6e6d6..5278893ffb4e 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -55,3 +55,4 @@ obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o
55obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o 55obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
56obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o 56obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
57obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o 57obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
58obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
diff --git a/drivers/irqchip/irq-pic32-evic.c b/drivers/irqchip/irq-pic32-evic.c
new file mode 100644
index 000000000000..e7155db01d55
--- /dev/null
+++ b/drivers/irqchip/irq-pic32-evic.c
@@ -0,0 +1,324 @@
1/*
2 * Cristian Birsan <cristian.birsan@microchip.com>
3 * Joshua Henderson <joshua.henderson@microchip.com>
4 * Copyright (C) 2016 Microchip Technology Inc. All rights reserved.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version.
10 */
11#include <linux/kernel.h>
12#include <linux/module.h>
13#include <linux/interrupt.h>
14#include <linux/irqdomain.h>
15#include <linux/of_address.h>
16#include <linux/slab.h>
17#include <linux/io.h>
18#include <linux/irqchip.h>
19#include <linux/irq.h>
20
21#include <asm/irq.h>
22#include <asm/traps.h>
23#include <asm/mach-pic32/pic32.h>
24
25#define REG_INTCON 0x0000
26#define REG_INTSTAT 0x0020
27#define REG_IFS_OFFSET 0x0040
28#define REG_IEC_OFFSET 0x00C0
29#define REG_IPC_OFFSET 0x0140
30#define REG_OFF_OFFSET 0x0540
31
32#define MAJPRI_MASK 0x07
33#define SUBPRI_MASK 0x03
34#define PRIORITY_MASK 0x1F
35
36#define PIC32_INT_PRI(pri, subpri) \
37 ((((pri) & MAJPRI_MASK) << 2) | ((subpri) & SUBPRI_MASK))
38
39struct evic_chip_data {
40 u32 irq_types[NR_IRQS];
41 u32 ext_irqs[8];
42};
43
44static struct irq_domain *evic_irq_domain;
45static void __iomem *evic_base;
46
47asmlinkage void __weak plat_irq_dispatch(void)
48{
49 unsigned int irq, hwirq;
50
51 hwirq = readl(evic_base + REG_INTSTAT) & 0xFF;
52 irq = irq_linear_revmap(evic_irq_domain, hwirq);
53 do_IRQ(irq);
54}
55
56static struct evic_chip_data *irqd_to_priv(struct irq_data *data)
57{
58 return (struct evic_chip_data *)data->domain->host_data;
59}
60
61static int pic32_set_ext_polarity(int bit, u32 type)
62{
63 /*
64 * External interrupts can be either edge rising or edge falling,
65 * but not both.
66 */
67 switch (type) {
68 case IRQ_TYPE_EDGE_RISING:
69 writel(BIT(bit), evic_base + PIC32_SET(REG_INTCON));
70 break;
71 case IRQ_TYPE_EDGE_FALLING:
72 writel(BIT(bit), evic_base + PIC32_CLR(REG_INTCON));
73 break;
74 default:
75 return -EINVAL;
76 }
77
78 return 0;
79}
80
81static int pic32_set_type_edge(struct irq_data *data,
82 unsigned int flow_type)
83{
84 struct evic_chip_data *priv = irqd_to_priv(data);
85 int ret;
86 int i;
87
88 if (!(flow_type & IRQ_TYPE_EDGE_BOTH))
89 return -EBADR;
90
91 /* set polarity for external interrupts only */
92 for (i = 0; i < ARRAY_SIZE(priv->ext_irqs); i++) {
93 if (priv->ext_irqs[i] == data->hwirq) {
94 ret = pic32_set_ext_polarity(i + 1, flow_type);
95 if (ret)
96 return ret;
97 }
98 }
99
100 irqd_set_trigger_type(data, flow_type);
101
102 return IRQ_SET_MASK_OK;
103}
104
105static void pic32_bind_evic_interrupt(int irq, int set)
106{
107 writel(set, evic_base + REG_OFF_OFFSET + irq * 4);
108}
109
110static void pic32_set_irq_priority(int irq, int priority)
111{
112 u32 reg, shift;
113
114 reg = irq / 4;
115 shift = (irq % 4) * 8;
116
117 writel(PRIORITY_MASK << shift,
118 evic_base + PIC32_CLR(REG_IPC_OFFSET + reg * 0x10));
119 writel(priority << shift,
120 evic_base + PIC32_SET(REG_IPC_OFFSET + reg * 0x10));
121}
122
123#define IRQ_REG_MASK(_hwirq, _reg, _mask) \
124 do { \
125 _reg = _hwirq / 32; \
126 _mask = 1 << (_hwirq % 32); \
127 } while (0)
128
129static int pic32_irq_domain_map(struct irq_domain *d, unsigned int virq,
130 irq_hw_number_t hw)
131{
132 struct evic_chip_data *priv = d->host_data;
133 struct irq_data *data;
134 int ret;
135 u32 iecclr, ifsclr;
136 u32 reg, mask;
137
138 ret = irq_map_generic_chip(d, virq, hw);
139 if (ret)
140 return ret;
141
142 /*
143 * Piggyback on xlate function to move to an alternate chip as necessary
144 * at time of mapping instead of allowing the flow handler/chip to be
145 * changed later. This requires all interrupts to be configured through
146 * DT.
147 */
148 if (priv->irq_types[hw] & IRQ_TYPE_SENSE_MASK) {
149 data = irq_domain_get_irq_data(d, virq);
150 irqd_set_trigger_type(data, priv->irq_types[hw]);
151 irq_setup_alt_chip(data, priv->irq_types[hw]);
152 }
153
154 IRQ_REG_MASK(hw, reg, mask);
155
156 iecclr = PIC32_CLR(REG_IEC_OFFSET + reg * 0x10);
157 ifsclr = PIC32_CLR(REG_IFS_OFFSET + reg * 0x10);
158
159 /* mask and clear flag */
160 writel(mask, evic_base + iecclr);
161 writel(mask, evic_base + ifsclr);
162
163 /* default priority is required */
164 pic32_set_irq_priority(hw, PIC32_INT_PRI(2, 0));
165
166 return ret;
167}
168
169int pic32_irq_domain_xlate(struct irq_domain *d, struct device_node *ctrlr,
170 const u32 *intspec, unsigned int intsize,
171 irq_hw_number_t *out_hwirq, unsigned int *out_type)
172{
173 struct evic_chip_data *priv = d->host_data;
174
175 if (WARN_ON(intsize < 2))
176 return -EINVAL;
177
178 if (WARN_ON(intspec[0] >= NR_IRQS))
179 return -EINVAL;
180
181 *out_hwirq = intspec[0];
182 *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
183
184 priv->irq_types[intspec[0]] = intspec[1] & IRQ_TYPE_SENSE_MASK;
185
186 return 0;
187}
188
189static const struct irq_domain_ops pic32_irq_domain_ops = {
190 .map = pic32_irq_domain_map,
191 .xlate = pic32_irq_domain_xlate,
192};
193
194static void __init pic32_ext_irq_of_init(struct irq_domain *domain)
195{
196 struct device_node *node = irq_domain_get_of_node(domain);
197 struct evic_chip_data *priv = domain->host_data;
198 struct property *prop;
199 const __le32 *p;
200 u32 hwirq;
201 int i = 0;
202 const char *pname = "microchip,external-irqs";
203
204 of_property_for_each_u32(node, pname, prop, p, hwirq) {
205 if (i >= ARRAY_SIZE(priv->ext_irqs)) {
206 pr_warn("More than %d external irq, skip rest\n",
207 ARRAY_SIZE(priv->ext_irqs));
208 break;
209 }
210
211 priv->ext_irqs[i] = hwirq;
212 i++;
213 }
214}
215
216static int __init pic32_of_init(struct device_node *node,
217 struct device_node *parent)
218{
219 struct irq_chip_generic *gc;
220 struct evic_chip_data *priv;
221 unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
222 int nchips, ret;
223 int i;
224
225 nchips = DIV_ROUND_UP(NR_IRQS, 32);
226
227 evic_base = of_iomap(node, 0);
228 if (!evic_base)
229 return -ENOMEM;
230
231 priv = kcalloc(nchips, sizeof(*priv), GFP_KERNEL);
232 if (!priv) {
233 ret = -ENOMEM;
234 goto err_iounmap;
235 }
236
237 evic_irq_domain = irq_domain_add_linear(node, nchips * 32,
238 &pic32_irq_domain_ops,
239 priv);
240 if (!evic_irq_domain) {
241 ret = -ENOMEM;
242 goto err_free_priv;
243 }
244
245 /*
246 * The PIC32 EVIC has a linear list of irqs and the type of each
247 * irq is determined by the hardware peripheral the EVIC is arbitrating.
248 * These irq types are defined in the datasheet as "persistent" and
249 * "non-persistent" which are mapped here to level and edge
250 * respectively. To manage the different flow handler requirements of
251 * each irq type, different chip_types are used.
252 */
253 ret = irq_alloc_domain_generic_chips(evic_irq_domain, 32, 2,
254 "evic-level", handle_level_irq,
255 clr, 0, 0);
256 if (ret)
257 goto err_domain_remove;
258
259 board_bind_eic_interrupt = &pic32_bind_evic_interrupt;
260
261 for (i = 0; i < nchips; i++) {
262 u32 ifsclr = PIC32_CLR(REG_IFS_OFFSET + (i * 0x10));
263 u32 iec = REG_IEC_OFFSET + (i * 0x10);
264
265 gc = irq_get_domain_generic_chip(evic_irq_domain, i * 32);
266
267 gc->reg_base = evic_base;
268 gc->unused = 0;
269
270 /*
271 * Level/persistent interrupts have a special requirement that
272 * the condition generating the interrupt be cleared before the
273 * interrupt flag (ifs) can be cleared. chip.irq_eoi is used to
274 * complete the interrupt with an ack.
275 */
276 gc->chip_types[0].type = IRQ_TYPE_LEVEL_MASK;
277 gc->chip_types[0].handler = handle_fasteoi_irq;
278 gc->chip_types[0].regs.ack = ifsclr;
279 gc->chip_types[0].regs.mask = iec;
280 gc->chip_types[0].chip.name = "evic-level";
281 gc->chip_types[0].chip.irq_eoi = irq_gc_ack_set_bit;
282 gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
283 gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
284 gc->chip_types[0].chip.flags = IRQCHIP_SKIP_SET_WAKE;
285
286 /* Edge interrupts */
287 gc->chip_types[1].type = IRQ_TYPE_EDGE_BOTH;
288 gc->chip_types[1].handler = handle_edge_irq;
289 gc->chip_types[1].regs.ack = ifsclr;
290 gc->chip_types[1].regs.mask = iec;
291 gc->chip_types[1].chip.name = "evic-edge";
292 gc->chip_types[1].chip.irq_ack = irq_gc_ack_set_bit;
293 gc->chip_types[1].chip.irq_mask = irq_gc_mask_clr_bit;
294 gc->chip_types[1].chip.irq_unmask = irq_gc_mask_set_bit;
295 gc->chip_types[1].chip.irq_set_type = pic32_set_type_edge;
296 gc->chip_types[1].chip.flags = IRQCHIP_SKIP_SET_WAKE;
297
298 gc->private = &priv[i];
299 }
300
301 irq_set_default_host(evic_irq_domain);
302
303 /*
304 * External interrupts have software configurable edge polarity. These
305 * interrupts are defined in DT allowing polarity to be configured only
306 * for these interrupts when requested.
307 */
308 pic32_ext_irq_of_init(evic_irq_domain);
309
310 return 0;
311
312err_domain_remove:
313 irq_domain_remove(evic_irq_domain);
314
315err_free_priv:
316 kfree(priv);
317
318err_iounmap:
319 iounmap(evic_base);
320
321 return ret;
322}
323
324IRQCHIP_DECLARE(pic32_evic, "microchip,pic32mzda-evic", pic32_of_init);