summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLinus Walleij <linus.walleij@linaro.org>2019-01-25 10:41:25 -0500
committerLinus Walleij <linus.walleij@linaro.org>2019-04-19 14:37:50 -0400
commit5b978c10665973d8ee7050b03ef6e97013066b03 (patch)
tree3130726f21c3cc3d7f1fdf6cbcecd1be9bc15b23 /drivers
parentdc8ef8cd3a05632bf15ce8714d6b84ece2836fe9 (diff)
irqchip: Add driver for IXP4xx
The IXP4xx (arch/arm/mach-ixp4xx) is an old Intel XScale platform that has very wide deployment and use. As part of modernizing the platform, we need to implement a proper irqchip in the irqchip subsystem. The IXP4xx irqchip is tightly jotted together with the GPIO controller, and whereas in the past we would deal with this complex logic by adding necessarily different code, we can nowadays modernize it using a hierarchical irqchip. The actual IXP4 irqchip is a simple active low level IRQ controller, whereas the GPIO functionality resides in a different memory area and adds edge trigger support for the interrupts. The interrupts from GPIO lines 0..12 are 1:1 mapped to a fixed set of hardware IRQs on this IRQchip, so we expect the child GPIO interrupt controller to go in and allocate descriptors for these interrupts. For the other interrupts, as we do not yet have DT support for this platform, we create a linear irqdomain and then go in and allocate the IRQs that the legacy boards use. This code will be removed on the DT probe path when we add DT support to the platform. We add some translation code for supporting DT translations for the fwnodes, but we leave most of that for later. Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: Jason Cooper <jason@lakedaemon.net> Acked-by: Marc Zyngier <marc.zyngier@arm.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/irqchip/Kconfig6
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-ixp4xx.c362
3 files changed, 369 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 5438abb1baba..cf7984991062 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -160,6 +160,12 @@ config IMGPDC_IRQ
160 select GENERIC_IRQ_CHIP 160 select GENERIC_IRQ_CHIP
161 select IRQ_DOMAIN 161 select IRQ_DOMAIN
162 162
163config IXP4XX_IRQ
164 bool
165 select IRQ_DOMAIN
166 select GENERIC_IRQ_MULTI_HANDLER
167 select SPARSE_IRQ
168
163config MADERA_IRQ 169config MADERA_IRQ
164 tristate 170 tristate
165 171
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 85972ae1bd7f..f8c66e958a64 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o irq-atmel-aic5.o
43obj-$(CONFIG_I8259) += irq-i8259.o 43obj-$(CONFIG_I8259) += irq-i8259.o
44obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o 44obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o
45obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o 45obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o
46obj-$(CONFIG_IXP4XX_IRQ) += irq-ixp4xx.o
46obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o 47obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o
47obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o 48obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o
48obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o 49obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o
diff --git a/drivers/irqchip/irq-ixp4xx.c b/drivers/irqchip/irq-ixp4xx.c
new file mode 100644
index 000000000000..89c80ce047a7
--- /dev/null
+++ b/drivers/irqchip/irq-ixp4xx.c
@@ -0,0 +1,362 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * irqchip for the IXP4xx interrupt controller
4 * Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org>
5 *
6 * Based on arch/arm/mach-ixp4xx/common.c
7 * Copyright 2002 (C) Intel Corporation
8 * Copyright 2003-2004 (C) MontaVista, Software, Inc.
9 * Copyright (C) Deepak Saxena <dsaxena@plexity.net>
10 */
11#include <linux/bitops.h>
12#include <linux/gpio/driver.h>
13#include <linux/irq.h>
14#include <linux/io.h>
15#include <linux/irqchip.h>
16#include <linux/irqchip/irq-ixp4xx.h>
17#include <linux/irqdomain.h>
18#include <linux/platform_device.h>
19#include <linux/cpu.h>
20
21#include <asm/exception.h>
22#include <asm/mach/irq.h>
23
24#define IXP4XX_ICPR 0x00 /* Interrupt Status */
25#define IXP4XX_ICMR 0x04 /* Interrupt Enable */
26#define IXP4XX_ICLR 0x08 /* Interrupt IRQ/FIQ Select */
27#define IXP4XX_ICIP 0x0C /* IRQ Status */
28#define IXP4XX_ICFP 0x10 /* FIQ Status */
29#define IXP4XX_ICHR 0x14 /* Interrupt Priority */
30#define IXP4XX_ICIH 0x18 /* IRQ Highest Pri Int */
31#define IXP4XX_ICFH 0x1C /* FIQ Highest Pri Int */
32
33/* IXP43x and IXP46x-only */
34#define IXP4XX_ICPR2 0x20 /* Interrupt Status 2 */
35#define IXP4XX_ICMR2 0x24 /* Interrupt Enable 2 */
36#define IXP4XX_ICLR2 0x28 /* Interrupt IRQ/FIQ Select 2 */
37#define IXP4XX_ICIP2 0x2C /* IRQ Status */
38#define IXP4XX_ICFP2 0x30 /* FIQ Status */
39#define IXP4XX_ICEEN 0x34 /* Error High Pri Enable */
40
41/**
42 * struct ixp4xx_irq - state container for the Faraday IRQ controller
43 * @irqbase: IRQ controller memory base in virtual memory
44 * @is_356: if this is an IXP43x, IXP45x or IX46x SoC (with 64 IRQs)
45 * @irqchip: irqchip for this instance
46 * @domain: IRQ domain for this instance
47 */
48struct ixp4xx_irq {
49 void __iomem *irqbase;
50 bool is_356;
51 struct irq_chip irqchip;
52 struct irq_domain *domain;
53};
54
55/* Local static state container */
56static struct ixp4xx_irq ixirq;
57
58/* GPIO Clocks */
59#define IXP4XX_GPIO_CLK_0 14
60#define IXP4XX_GPIO_CLK_1 15
61
62static int ixp4xx_set_irq_type(struct irq_data *d, unsigned int type)
63{
64 /* All are level active high (asserted) here */
65 if (type != IRQ_TYPE_LEVEL_HIGH)
66 return -EINVAL;
67 return 0;
68}
69
70static void ixp4xx_irq_mask(struct irq_data *d)
71{
72 struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d);
73 u32 val;
74
75 if (ixi->is_356 && d->hwirq >= 32) {
76 val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2);
77 val &= ~BIT(d->hwirq - 32);
78 __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2);
79 } else {
80 val = __raw_readl(ixi->irqbase + IXP4XX_ICMR);
81 val &= ~BIT(d->hwirq);
82 __raw_writel(val, ixi->irqbase + IXP4XX_ICMR);
83 }
84}
85
86/*
87 * Level triggered interrupts on GPIO lines can only be cleared when the
88 * interrupt condition disappears.
89 */
90static void ixp4xx_irq_unmask(struct irq_data *d)
91{
92 struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d);
93 u32 val;
94
95 if (ixi->is_356 && d->hwirq >= 32) {
96 val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2);
97 val |= BIT(d->hwirq - 32);
98 __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2);
99 } else {
100 val = __raw_readl(ixi->irqbase + IXP4XX_ICMR);
101 val |= BIT(d->hwirq);
102 __raw_writel(val, ixi->irqbase + IXP4XX_ICMR);
103 }
104}
105
106asmlinkage void __exception_irq_entry ixp4xx_handle_irq(struct pt_regs *regs)
107{
108 struct ixp4xx_irq *ixi = &ixirq;
109 unsigned long status;
110 int i;
111
112 status = __raw_readl(ixi->irqbase + IXP4XX_ICIP);
113 for_each_set_bit(i, &status, 32)
114 handle_domain_irq(ixi->domain, i, regs);
115
116 /*
117 * IXP465/IXP435 has an upper IRQ status register
118 */
119 if (ixi->is_356) {
120 status = __raw_readl(ixi->irqbase + IXP4XX_ICIP2);
121 for_each_set_bit(i, &status, 32)
122 handle_domain_irq(ixi->domain, i + 32, regs);
123 }
124}
125
126static int ixp4xx_irq_domain_translate(struct irq_domain *domain,
127 struct irq_fwspec *fwspec,
128 unsigned long *hwirq,
129 unsigned int *type)
130{
131 /* We support standard DT translation */
132 if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) {
133 *hwirq = fwspec->param[0];
134 *type = fwspec->param[1];
135 return 0;
136 }
137
138 if (is_fwnode_irqchip(fwspec->fwnode)) {
139 if (fwspec->param_count != 2)
140 return -EINVAL;
141 *hwirq = fwspec->param[0];
142 *type = fwspec->param[1];
143 WARN_ON(*type == IRQ_TYPE_NONE);
144 return 0;
145 }
146
147 return -EINVAL;
148}
149
150static int ixp4xx_irq_domain_alloc(struct irq_domain *d,
151 unsigned int irq, unsigned int nr_irqs,
152 void *data)
153{
154 struct ixp4xx_irq *ixi = d->host_data;
155 irq_hw_number_t hwirq;
156 unsigned int type = IRQ_TYPE_NONE;
157 struct irq_fwspec *fwspec = data;
158 int ret;
159 int i;
160
161 ret = ixp4xx_irq_domain_translate(d, fwspec, &hwirq, &type);
162 if (ret)
163 return ret;
164
165 for (i = 0; i < nr_irqs; i++) {
166 /*
167 * TODO: after converting IXP4xx to only device tree, set
168 * handle_bad_irq as default handler and assume all consumers
169 * call .set_type() as this is provided in the second cell in
170 * the device tree phandle.
171 */
172 irq_domain_set_info(d,
173 irq + i,
174 hwirq + i,
175 &ixi->irqchip,
176 ixi,
177 handle_level_irq,
178 NULL, NULL);
179 irq_set_probe(irq + i);
180 }
181
182 return 0;
183}
184
185/*
186 * This needs to be a hierarchical irqdomain to work well with the
187 * GPIO irqchip (which is lower in the hierarchy)
188 */
189static const struct irq_domain_ops ixp4xx_irqdomain_ops = {
190 .translate = ixp4xx_irq_domain_translate,
191 .alloc = ixp4xx_irq_domain_alloc,
192 .free = irq_domain_free_irqs_common,
193};
194
195/**
196 * ixp4xx_get_irq_domain() - retrieve the ixp4xx irq domain
197 *
198 * This function will go away when we transition to DT probing.
199 */
200struct irq_domain *ixp4xx_get_irq_domain(void)
201{
202 struct ixp4xx_irq *ixi = &ixirq;
203
204 return ixi->domain;
205}
206EXPORT_SYMBOL_GPL(ixp4xx_get_irq_domain);
207
208/*
209 * This is the Linux IRQ to hwirq mapping table. This goes away when
210 * we have DT support as all IRQ resources are defined in the device
211 * tree. It will register all the IRQs that are not used by the hierarchical
212 * GPIO IRQ chip. The "holes" inbetween these IRQs will be requested by
213 * the GPIO driver using . This is a step-gap solution.
214 */
215struct ixp4xx_irq_chunk {
216 int irq;
217 int hwirq;
218 int nr_irqs;
219};
220
221static const struct ixp4xx_irq_chunk ixp4xx_irq_chunks[] = {
222 {
223 .irq = 16,
224 .hwirq = 0,
225 .nr_irqs = 6,
226 },
227 {
228 .irq = 24,
229 .hwirq = 8,
230 .nr_irqs = 11,
231 },
232 {
233 .irq = 46,
234 .hwirq = 30,
235 .nr_irqs = 2,
236 },
237 /* Only on the 436 variants */
238 {
239 .irq = 48,
240 .hwirq = 32,
241 .nr_irqs = 10,
242 },
243};
244
245/**
246 * ixp4x_irq_setup() - Common setup code for the IXP4xx interrupt controller
247 * @ixi: State container
248 * @irqbase: Virtual memory base for the interrupt controller
249 * @fwnode: Corresponding fwnode abstraction for this controller
250 * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant
251 */
252static int ixp4xx_irq_setup(struct ixp4xx_irq *ixi,
253 void __iomem *irqbase,
254 struct fwnode_handle *fwnode,
255 bool is_356)
256{
257 int nr_irqs;
258
259 ixi->irqbase = irqbase;
260 ixi->is_356 = is_356;
261
262 /* Route all sources to IRQ instead of FIQ */
263 __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR);
264
265 /* Disable all interrupts */
266 __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR);
267
268 if (is_356) {
269 /* Route upper 32 sources to IRQ instead of FIQ */
270 __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR2);
271
272 /* Disable upper 32 interrupts */
273 __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR2);
274
275 nr_irqs = 64;
276 } else {
277 nr_irqs = 32;
278 }
279
280 ixi->irqchip.name = "IXP4xx";
281 ixi->irqchip.irq_mask = ixp4xx_irq_mask;
282 ixi->irqchip.irq_unmask = ixp4xx_irq_unmask;
283 ixi->irqchip.irq_set_type = ixp4xx_set_irq_type;
284
285 ixi->domain = irq_domain_create_linear(fwnode, nr_irqs,
286 &ixp4xx_irqdomain_ops,
287 ixi);
288 if (!ixi->domain) {
289 pr_crit("IXP4XX: can not add primary irqdomain\n");
290 return -ENODEV;
291 }
292
293 set_handle_irq(ixp4xx_handle_irq);
294
295 return 0;
296}
297
298/**
299 * ixp4xx_irq_init() - Function to initialize the irqchip from boardfiles
300 * @irqbase: physical base for the irq controller
301 * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant
302 */
303void __init ixp4xx_irq_init(resource_size_t irqbase,
304 bool is_356)
305{
306 struct ixp4xx_irq *ixi = &ixirq;
307 void __iomem *base;
308 struct fwnode_handle *fwnode;
309 struct irq_fwspec fwspec;
310 int nr_chunks;
311 int ret;
312 int i;
313
314 base = ioremap(irqbase, 0x100);
315 if (!base) {
316 pr_crit("IXP4XX: could not ioremap interrupt controller\n");
317 return;
318 }
319 fwnode = irq_domain_alloc_fwnode(base);
320 if (!fwnode) {
321 pr_crit("IXP4XX: no domain handle\n");
322 return;
323 }
324 ret = ixp4xx_irq_setup(ixi, base, fwnode, is_356);
325 if (ret) {
326 pr_crit("IXP4XX: failed to set up irqchip\n");
327 irq_domain_free_fwnode(fwnode);
328 }
329
330 nr_chunks = ARRAY_SIZE(ixp4xx_irq_chunks);
331 if (!is_356)
332 nr_chunks--;
333
334 /*
335 * After adding OF support, this is no longer needed: irqs
336 * will be allocated for the respective fwnodes.
337 */
338 for (i = 0; i < nr_chunks; i++) {
339 const struct ixp4xx_irq_chunk *chunk = &ixp4xx_irq_chunks[i];
340
341 pr_info("Allocate Linux IRQs %d..%d HW IRQs %d..%d\n",
342 chunk->irq, chunk->irq + chunk->nr_irqs - 1,
343 chunk->hwirq, chunk->hwirq + chunk->nr_irqs - 1);
344 fwspec.fwnode = fwnode;
345 fwspec.param[0] = chunk->hwirq;
346 fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
347 fwspec.param_count = 2;
348 ret = __irq_domain_alloc_irqs(ixi->domain,
349 chunk->irq,
350 chunk->nr_irqs,
351 NUMA_NO_NODE,
352 &fwspec,
353 false,
354 NULL);
355 if (ret < 0) {
356 pr_crit("IXP4XX: can not allocate irqs in hierarchy %d\n",
357 ret);
358 return;
359 }
360 }
361}
362EXPORT_SYMBOL_GPL(ixp4xx_irq_init);