1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/arch/arm/mach-mmp/irq.c |
4 | * |
5 | * Generic IRQ handling, GPIO IRQ demultiplexing, etc. |
6 | * Copyright (C) 2008 - 2012 Marvell Technology Group Ltd. |
7 | * |
8 | * Author: Bin Yang <bin.yang@marvell.com> |
9 | * Haojian Zhuang <haojian.zhuang@gmail.com> |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/init.h> |
14 | #include <linux/irq.h> |
15 | #include <linux/irqchip.h> |
16 | #include <linux/irqchip/chained_irq.h> |
17 | #include <linux/irqdomain.h> |
18 | #include <linux/io.h> |
19 | #include <linux/ioport.h> |
20 | #include <linux/of_address.h> |
21 | #include <linux/of_irq.h> |
22 | |
23 | #include <asm/exception.h> |
24 | #include <asm/hardirq.h> |
25 | |
26 | #define MAX_ICU_NR 16 |
27 | |
28 | #define PJ1_INT_SEL 0x10c |
29 | #define PJ4_INT_SEL 0x104 |
30 | |
31 | /* bit fields in PJ1_INT_SEL and PJ4_INT_SEL */ |
32 | #define SEL_INT_PENDING (1 << 6) |
33 | #define SEL_INT_NUM_MASK 0x3f |
34 | |
35 | #define MMP2_ICU_INT_ROUTE_PJ4_IRQ (1 << 5) |
36 | #define MMP2_ICU_INT_ROUTE_PJ4_FIQ (1 << 6) |
37 | |
38 | struct icu_chip_data { |
39 | int nr_irqs; |
40 | unsigned int virq_base; |
41 | unsigned int cascade_irq; |
42 | void __iomem *reg_status; |
43 | void __iomem *reg_mask; |
44 | unsigned int conf_enable; |
45 | unsigned int conf_disable; |
46 | unsigned int conf_mask; |
47 | unsigned int conf2_mask; |
48 | unsigned int clr_mfp_irq_base; |
49 | unsigned int clr_mfp_hwirq; |
50 | struct irq_domain *domain; |
51 | }; |
52 | |
53 | struct mmp_intc_conf { |
54 | unsigned int conf_enable; |
55 | unsigned int conf_disable; |
56 | unsigned int conf_mask; |
57 | unsigned int conf2_mask; |
58 | }; |
59 | |
60 | static void __iomem *mmp_icu_base; |
61 | static void __iomem *mmp_icu2_base; |
62 | static struct icu_chip_data icu_data[MAX_ICU_NR]; |
63 | static int max_icu_nr; |
64 | |
65 | extern void mmp2_clear_pmic_int(void); |
66 | |
67 | static void icu_mask_ack_irq(struct irq_data *d) |
68 | { |
69 | struct irq_domain *domain = d->domain; |
70 | struct icu_chip_data *data = (struct icu_chip_data *)domain->host_data; |
71 | int hwirq; |
72 | u32 r; |
73 | |
74 | hwirq = d->irq - data->virq_base; |
75 | if (data == &icu_data[0]) { |
76 | r = readl_relaxed(mmp_icu_base + (hwirq << 2)); |
77 | r &= ~data->conf_mask; |
78 | r |= data->conf_disable; |
79 | writel_relaxed(r, mmp_icu_base + (hwirq << 2)); |
80 | } else { |
81 | #ifdef CONFIG_CPU_MMP2 |
82 | if ((data->virq_base == data->clr_mfp_irq_base) |
83 | && (hwirq == data->clr_mfp_hwirq)) |
84 | mmp2_clear_pmic_int(); |
85 | #endif |
86 | r = readl_relaxed(data->reg_mask) | (1 << hwirq); |
87 | writel_relaxed(r, data->reg_mask); |
88 | } |
89 | } |
90 | |
91 | static void icu_mask_irq(struct irq_data *d) |
92 | { |
93 | struct irq_domain *domain = d->domain; |
94 | struct icu_chip_data *data = (struct icu_chip_data *)domain->host_data; |
95 | int hwirq; |
96 | u32 r; |
97 | |
98 | hwirq = d->irq - data->virq_base; |
99 | if (data == &icu_data[0]) { |
100 | r = readl_relaxed(mmp_icu_base + (hwirq << 2)); |
101 | r &= ~data->conf_mask; |
102 | r |= data->conf_disable; |
103 | writel_relaxed(r, mmp_icu_base + (hwirq << 2)); |
104 | |
105 | if (data->conf2_mask) { |
106 | /* |
107 | * ICU1 (above) only controls PJ4 MP1; if using SMP, |
108 | * we need to also mask the MP2 and MM cores via ICU2. |
109 | */ |
110 | r = readl_relaxed(mmp_icu2_base + (hwirq << 2)); |
111 | r &= ~data->conf2_mask; |
112 | writel_relaxed(r, mmp_icu2_base + (hwirq << 2)); |
113 | } |
114 | } else { |
115 | r = readl_relaxed(data->reg_mask) | (1 << hwirq); |
116 | writel_relaxed(r, data->reg_mask); |
117 | } |
118 | } |
119 | |
120 | static void icu_unmask_irq(struct irq_data *d) |
121 | { |
122 | struct irq_domain *domain = d->domain; |
123 | struct icu_chip_data *data = (struct icu_chip_data *)domain->host_data; |
124 | int hwirq; |
125 | u32 r; |
126 | |
127 | hwirq = d->irq - data->virq_base; |
128 | if (data == &icu_data[0]) { |
129 | r = readl_relaxed(mmp_icu_base + (hwirq << 2)); |
130 | r &= ~data->conf_mask; |
131 | r |= data->conf_enable; |
132 | writel_relaxed(r, mmp_icu_base + (hwirq << 2)); |
133 | } else { |
134 | r = readl_relaxed(data->reg_mask) & ~(1 << hwirq); |
135 | writel_relaxed(r, data->reg_mask); |
136 | } |
137 | } |
138 | |
139 | struct irq_chip icu_irq_chip = { |
140 | .name = "icu_irq" , |
141 | .irq_mask = icu_mask_irq, |
142 | .irq_mask_ack = icu_mask_ack_irq, |
143 | .irq_unmask = icu_unmask_irq, |
144 | }; |
145 | |
146 | static void icu_mux_irq_demux(struct irq_desc *desc) |
147 | { |
148 | unsigned int irq = irq_desc_get_irq(desc); |
149 | struct irq_chip *chip = irq_desc_get_chip(desc); |
150 | struct irq_domain *domain; |
151 | struct icu_chip_data *data; |
152 | int i; |
153 | unsigned long mask, status, n; |
154 | |
155 | chained_irq_enter(chip, desc); |
156 | |
157 | for (i = 1; i < max_icu_nr; i++) { |
158 | if (irq == icu_data[i].cascade_irq) { |
159 | domain = icu_data[i].domain; |
160 | data = (struct icu_chip_data *)domain->host_data; |
161 | break; |
162 | } |
163 | } |
164 | if (i >= max_icu_nr) { |
165 | pr_err("Spurious irq %d in MMP INTC\n" , irq); |
166 | goto out; |
167 | } |
168 | |
169 | mask = readl_relaxed(data->reg_mask); |
170 | while (1) { |
171 | status = readl_relaxed(data->reg_status) & ~mask; |
172 | if (status == 0) |
173 | break; |
174 | for_each_set_bit(n, &status, BITS_PER_LONG) { |
175 | generic_handle_irq(irq: icu_data[i].virq_base + n); |
176 | } |
177 | } |
178 | |
179 | out: |
180 | chained_irq_exit(chip, desc); |
181 | } |
182 | |
183 | static int mmp_irq_domain_map(struct irq_domain *d, unsigned int irq, |
184 | irq_hw_number_t hw) |
185 | { |
186 | irq_set_chip_and_handler(irq, chip: &icu_irq_chip, handle: handle_level_irq); |
187 | return 0; |
188 | } |
189 | |
190 | static int mmp_irq_domain_xlate(struct irq_domain *d, struct device_node *node, |
191 | const u32 *intspec, unsigned int intsize, |
192 | unsigned long *out_hwirq, |
193 | unsigned int *out_type) |
194 | { |
195 | *out_hwirq = intspec[0]; |
196 | return 0; |
197 | } |
198 | |
199 | static const struct irq_domain_ops mmp_irq_domain_ops = { |
200 | .map = mmp_irq_domain_map, |
201 | .xlate = mmp_irq_domain_xlate, |
202 | }; |
203 | |
204 | static const struct mmp_intc_conf mmp_conf = { |
205 | .conf_enable = 0x51, |
206 | .conf_disable = 0x0, |
207 | .conf_mask = 0x7f, |
208 | }; |
209 | |
210 | static const struct mmp_intc_conf mmp2_conf = { |
211 | .conf_enable = 0x20, |
212 | .conf_disable = 0x0, |
213 | .conf_mask = MMP2_ICU_INT_ROUTE_PJ4_IRQ | |
214 | MMP2_ICU_INT_ROUTE_PJ4_FIQ, |
215 | }; |
216 | |
217 | static struct mmp_intc_conf mmp3_conf = { |
218 | .conf_enable = 0x20, |
219 | .conf_disable = 0x0, |
220 | .conf_mask = MMP2_ICU_INT_ROUTE_PJ4_IRQ | |
221 | MMP2_ICU_INT_ROUTE_PJ4_FIQ, |
222 | .conf2_mask = 0xf0, |
223 | }; |
224 | |
225 | static void __exception_irq_entry mmp_handle_irq(struct pt_regs *regs) |
226 | { |
227 | int hwirq; |
228 | |
229 | hwirq = readl_relaxed(mmp_icu_base + PJ1_INT_SEL); |
230 | if (!(hwirq & SEL_INT_PENDING)) |
231 | return; |
232 | hwirq &= SEL_INT_NUM_MASK; |
233 | generic_handle_domain_irq(icu_data[0].domain, hwirq); |
234 | } |
235 | |
236 | static void __exception_irq_entry mmp2_handle_irq(struct pt_regs *regs) |
237 | { |
238 | int hwirq; |
239 | |
240 | hwirq = readl_relaxed(mmp_icu_base + PJ4_INT_SEL); |
241 | if (!(hwirq & SEL_INT_PENDING)) |
242 | return; |
243 | hwirq &= SEL_INT_NUM_MASK; |
244 | generic_handle_domain_irq(icu_data[0].domain, hwirq); |
245 | } |
246 | |
247 | static int __init mmp_init_bases(struct device_node *node) |
248 | { |
249 | int ret, nr_irqs, irq, i = 0; |
250 | |
251 | ret = of_property_read_u32(np: node, propname: "mrvl,intc-nr-irqs" , out_value: &nr_irqs); |
252 | if (ret) { |
253 | pr_err("Not found mrvl,intc-nr-irqs property\n" ); |
254 | return ret; |
255 | } |
256 | |
257 | mmp_icu_base = of_iomap(node, index: 0); |
258 | if (!mmp_icu_base) { |
259 | pr_err("Failed to get interrupt controller register\n" ); |
260 | return -ENOMEM; |
261 | } |
262 | |
263 | icu_data[0].virq_base = 0; |
264 | icu_data[0].domain = irq_domain_add_linear(of_node: node, size: nr_irqs, |
265 | ops: &mmp_irq_domain_ops, |
266 | host_data: &icu_data[0]); |
267 | for (irq = 0; irq < nr_irqs; irq++) { |
268 | ret = irq_create_mapping(host: icu_data[0].domain, hwirq: irq); |
269 | if (!ret) { |
270 | pr_err("Failed to mapping hwirq\n" ); |
271 | goto err; |
272 | } |
273 | if (!irq) |
274 | icu_data[0].virq_base = ret; |
275 | } |
276 | icu_data[0].nr_irqs = nr_irqs; |
277 | return 0; |
278 | err: |
279 | if (icu_data[0].virq_base) { |
280 | for (i = 0; i < irq; i++) |
281 | irq_dispose_mapping(virq: icu_data[0].virq_base + i); |
282 | } |
283 | irq_domain_remove(host: icu_data[0].domain); |
284 | iounmap(addr: mmp_icu_base); |
285 | return -EINVAL; |
286 | } |
287 | |
288 | static int __init mmp_of_init(struct device_node *node, |
289 | struct device_node *parent) |
290 | { |
291 | int ret; |
292 | |
293 | ret = mmp_init_bases(node); |
294 | if (ret < 0) |
295 | return ret; |
296 | |
297 | icu_data[0].conf_enable = mmp_conf.conf_enable; |
298 | icu_data[0].conf_disable = mmp_conf.conf_disable; |
299 | icu_data[0].conf_mask = mmp_conf.conf_mask; |
300 | set_handle_irq(mmp_handle_irq); |
301 | max_icu_nr = 1; |
302 | return 0; |
303 | } |
304 | IRQCHIP_DECLARE(mmp_intc, "mrvl,mmp-intc" , mmp_of_init); |
305 | |
306 | static int __init mmp2_of_init(struct device_node *node, |
307 | struct device_node *parent) |
308 | { |
309 | int ret; |
310 | |
311 | ret = mmp_init_bases(node); |
312 | if (ret < 0) |
313 | return ret; |
314 | |
315 | icu_data[0].conf_enable = mmp2_conf.conf_enable; |
316 | icu_data[0].conf_disable = mmp2_conf.conf_disable; |
317 | icu_data[0].conf_mask = mmp2_conf.conf_mask; |
318 | set_handle_irq(mmp2_handle_irq); |
319 | max_icu_nr = 1; |
320 | return 0; |
321 | } |
322 | IRQCHIP_DECLARE(mmp2_intc, "mrvl,mmp2-intc" , mmp2_of_init); |
323 | |
324 | static int __init mmp3_of_init(struct device_node *node, |
325 | struct device_node *parent) |
326 | { |
327 | int ret; |
328 | |
329 | mmp_icu2_base = of_iomap(node, index: 1); |
330 | if (!mmp_icu2_base) { |
331 | pr_err("Failed to get interrupt controller register #2\n" ); |
332 | return -ENODEV; |
333 | } |
334 | |
335 | ret = mmp_init_bases(node); |
336 | if (ret < 0) { |
337 | iounmap(addr: mmp_icu2_base); |
338 | return ret; |
339 | } |
340 | |
341 | icu_data[0].conf_enable = mmp3_conf.conf_enable; |
342 | icu_data[0].conf_disable = mmp3_conf.conf_disable; |
343 | icu_data[0].conf_mask = mmp3_conf.conf_mask; |
344 | icu_data[0].conf2_mask = mmp3_conf.conf2_mask; |
345 | |
346 | if (!parent) { |
347 | /* This is the main interrupt controller. */ |
348 | set_handle_irq(mmp2_handle_irq); |
349 | } |
350 | |
351 | max_icu_nr = 1; |
352 | return 0; |
353 | } |
354 | IRQCHIP_DECLARE(mmp3_intc, "marvell,mmp3-intc" , mmp3_of_init); |
355 | |
356 | static int __init mmp2_mux_of_init(struct device_node *node, |
357 | struct device_node *parent) |
358 | { |
359 | int i, ret, irq, j = 0; |
360 | u32 nr_irqs, mfp_irq; |
361 | u32 reg[4]; |
362 | |
363 | if (!parent) |
364 | return -ENODEV; |
365 | |
366 | i = max_icu_nr; |
367 | ret = of_property_read_u32(np: node, propname: "mrvl,intc-nr-irqs" , |
368 | out_value: &nr_irqs); |
369 | if (ret) { |
370 | pr_err("Not found mrvl,intc-nr-irqs property\n" ); |
371 | return -EINVAL; |
372 | } |
373 | |
374 | /* |
375 | * For historical reasons, the "regs" property of the |
376 | * mrvl,mmp2-mux-intc is not a regular "regs" property containing |
377 | * addresses on the parent bus, but offsets from the intc's base. |
378 | * That is why we can't use of_address_to_resource() here. |
379 | */ |
380 | ret = of_property_read_variable_u32_array(np: node, propname: "reg" , out_values: reg, |
381 | ARRAY_SIZE(reg), |
382 | ARRAY_SIZE(reg)); |
383 | if (ret < 0) { |
384 | pr_err("Not found reg property\n" ); |
385 | return -EINVAL; |
386 | } |
387 | icu_data[i].reg_status = mmp_icu_base + reg[0]; |
388 | icu_data[i].reg_mask = mmp_icu_base + reg[2]; |
389 | icu_data[i].cascade_irq = irq_of_parse_and_map(node, index: 0); |
390 | if (!icu_data[i].cascade_irq) |
391 | return -EINVAL; |
392 | |
393 | icu_data[i].virq_base = 0; |
394 | icu_data[i].domain = irq_domain_add_linear(of_node: node, size: nr_irqs, |
395 | ops: &mmp_irq_domain_ops, |
396 | host_data: &icu_data[i]); |
397 | for (irq = 0; irq < nr_irqs; irq++) { |
398 | ret = irq_create_mapping(host: icu_data[i].domain, hwirq: irq); |
399 | if (!ret) { |
400 | pr_err("Failed to mapping hwirq\n" ); |
401 | goto err; |
402 | } |
403 | if (!irq) |
404 | icu_data[i].virq_base = ret; |
405 | } |
406 | icu_data[i].nr_irqs = nr_irqs; |
407 | if (!of_property_read_u32(np: node, propname: "mrvl,clr-mfp-irq" , |
408 | out_value: &mfp_irq)) { |
409 | icu_data[i].clr_mfp_irq_base = icu_data[i].virq_base; |
410 | icu_data[i].clr_mfp_hwirq = mfp_irq; |
411 | } |
412 | irq_set_chained_handler(irq: icu_data[i].cascade_irq, |
413 | handle: icu_mux_irq_demux); |
414 | max_icu_nr++; |
415 | return 0; |
416 | err: |
417 | if (icu_data[i].virq_base) { |
418 | for (j = 0; j < irq; j++) |
419 | irq_dispose_mapping(virq: icu_data[i].virq_base + j); |
420 | } |
421 | irq_domain_remove(host: icu_data[i].domain); |
422 | return -EINVAL; |
423 | } |
424 | IRQCHIP_DECLARE(mmp2_mux_intc, "mrvl,mmp2-mux-intc" , mmp2_mux_of_init); |
425 | |