aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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