]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blob - drivers/irqchip/irq-ixp4xx.c
irqchip: Add driver for IXP4xx
[mirror_ubuntu-jammy-kernel.git] / drivers / irqchip / irq-ixp4xx.c
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 */
48 struct 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 */
56 static struct ixp4xx_irq ixirq;
57
58 /* GPIO Clocks */
59 #define IXP4XX_GPIO_CLK_0 14
60 #define IXP4XX_GPIO_CLK_1 15
61
62 static 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
70 static 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 */
90 static 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
106 asmlinkage 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
126 static 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
150 static 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 */
189 static 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 */
200 struct irq_domain *ixp4xx_get_irq_domain(void)
201 {
202 struct ixp4xx_irq *ixi = &ixirq;
203
204 return ixi->domain;
205 }
206 EXPORT_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 */
215 struct ixp4xx_irq_chunk {
216 int irq;
217 int hwirq;
218 int nr_irqs;
219 };
220
221 static 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 */
252 static 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 */
303 void __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 }
362 EXPORT_SYMBOL_GPL(ixp4xx_irq_init);