1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Broadcom BCM7038 style Level 1 interrupt controller driver |
4 | * |
5 | * Copyright (C) 2014 Broadcom Corporation |
6 | * Author: Kevin Cernekee |
7 | */ |
8 | |
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
10 | |
11 | #include <linux/bitops.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/init.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/io.h> |
16 | #include <linux/ioport.h> |
17 | #include <linux/irq.h> |
18 | #include <linux/irqdomain.h> |
19 | #include <linux/module.h> |
20 | #include <linux/of.h> |
21 | #include <linux/of_irq.h> |
22 | #include <linux/of_address.h> |
23 | #include <linux/platform_device.h> |
24 | #include <linux/slab.h> |
25 | #include <linux/smp.h> |
26 | #include <linux/types.h> |
27 | #include <linux/irqchip.h> |
28 | #include <linux/irqchip/chained_irq.h> |
29 | #include <linux/syscore_ops.h> |
30 | |
31 | #define IRQS_PER_WORD 32 |
32 | #define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 4) |
33 | #define MAX_WORDS 8 |
34 | |
35 | struct bcm7038_l1_cpu; |
36 | |
37 | struct bcm7038_l1_chip { |
38 | raw_spinlock_t lock; |
39 | unsigned int n_words; |
40 | struct irq_domain *domain; |
41 | struct bcm7038_l1_cpu *cpus[NR_CPUS]; |
42 | #ifdef CONFIG_PM_SLEEP |
43 | struct list_head list; |
44 | u32 wake_mask[MAX_WORDS]; |
45 | #endif |
46 | u32 irq_fwd_mask[MAX_WORDS]; |
47 | u8 affinity[MAX_WORDS * IRQS_PER_WORD]; |
48 | }; |
49 | |
50 | struct bcm7038_l1_cpu { |
51 | void __iomem *map_base; |
52 | u32 mask_cache[]; |
53 | }; |
54 | |
55 | /* |
56 | * STATUS/MASK_STATUS/MASK_SET/MASK_CLEAR are packed one right after another: |
57 | * |
58 | * 7038: |
59 | * 0x1000_1400: W0_STATUS |
60 | * 0x1000_1404: W1_STATUS |
61 | * 0x1000_1408: W0_MASK_STATUS |
62 | * 0x1000_140c: W1_MASK_STATUS |
63 | * 0x1000_1410: W0_MASK_SET |
64 | * 0x1000_1414: W1_MASK_SET |
65 | * 0x1000_1418: W0_MASK_CLEAR |
66 | * 0x1000_141c: W1_MASK_CLEAR |
67 | * |
68 | * 7445: |
69 | * 0xf03e_1500: W0_STATUS |
70 | * 0xf03e_1504: W1_STATUS |
71 | * 0xf03e_1508: W2_STATUS |
72 | * 0xf03e_150c: W3_STATUS |
73 | * 0xf03e_1510: W4_STATUS |
74 | * 0xf03e_1514: W0_MASK_STATUS |
75 | * 0xf03e_1518: W1_MASK_STATUS |
76 | * [...] |
77 | */ |
78 | |
79 | static inline unsigned int reg_status(struct bcm7038_l1_chip *intc, |
80 | unsigned int word) |
81 | { |
82 | return (0 * intc->n_words + word) * sizeof(u32); |
83 | } |
84 | |
85 | static inline unsigned int reg_mask_status(struct bcm7038_l1_chip *intc, |
86 | unsigned int word) |
87 | { |
88 | return (1 * intc->n_words + word) * sizeof(u32); |
89 | } |
90 | |
91 | static inline unsigned int reg_mask_set(struct bcm7038_l1_chip *intc, |
92 | unsigned int word) |
93 | { |
94 | return (2 * intc->n_words + word) * sizeof(u32); |
95 | } |
96 | |
97 | static inline unsigned int reg_mask_clr(struct bcm7038_l1_chip *intc, |
98 | unsigned int word) |
99 | { |
100 | return (3 * intc->n_words + word) * sizeof(u32); |
101 | } |
102 | |
103 | static inline u32 l1_readl(void __iomem *reg) |
104 | { |
105 | if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) |
106 | return ioread32be(reg); |
107 | else |
108 | return readl(addr: reg); |
109 | } |
110 | |
111 | static inline void l1_writel(u32 val, void __iomem *reg) |
112 | { |
113 | if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) |
114 | iowrite32be(val, reg); |
115 | else |
116 | writel(val, addr: reg); |
117 | } |
118 | |
119 | static void bcm7038_l1_irq_handle(struct irq_desc *desc) |
120 | { |
121 | struct bcm7038_l1_chip *intc = irq_desc_get_handler_data(desc); |
122 | struct bcm7038_l1_cpu *cpu; |
123 | struct irq_chip *chip = irq_desc_get_chip(desc); |
124 | unsigned int idx; |
125 | |
126 | #if defined(CONFIG_SMP) && defined(CONFIG_MIPS) |
127 | cpu = intc->cpus[cpu_logical_map(smp_processor_id())]; |
128 | #else |
129 | cpu = intc->cpus[0]; |
130 | #endif |
131 | |
132 | chained_irq_enter(chip, desc); |
133 | |
134 | for (idx = 0; idx < intc->n_words; idx++) { |
135 | int base = idx * IRQS_PER_WORD; |
136 | unsigned long pending, flags; |
137 | int hwirq; |
138 | |
139 | raw_spin_lock_irqsave(&intc->lock, flags); |
140 | pending = l1_readl(reg: cpu->map_base + reg_status(intc, word: idx)) & |
141 | ~cpu->mask_cache[idx]; |
142 | raw_spin_unlock_irqrestore(&intc->lock, flags); |
143 | |
144 | for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) |
145 | generic_handle_domain_irq(domain: intc->domain, hwirq: base + hwirq); |
146 | } |
147 | |
148 | chained_irq_exit(chip, desc); |
149 | } |
150 | |
151 | static void __bcm7038_l1_unmask(struct irq_data *d, unsigned int cpu_idx) |
152 | { |
153 | struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d); |
154 | u32 word = d->hwirq / IRQS_PER_WORD; |
155 | u32 mask = BIT(d->hwirq % IRQS_PER_WORD); |
156 | |
157 | intc->cpus[cpu_idx]->mask_cache[word] &= ~mask; |
158 | l1_writel(val: mask, reg: intc->cpus[cpu_idx]->map_base + |
159 | reg_mask_clr(intc, word)); |
160 | } |
161 | |
162 | static void __bcm7038_l1_mask(struct irq_data *d, unsigned int cpu_idx) |
163 | { |
164 | struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d); |
165 | u32 word = d->hwirq / IRQS_PER_WORD; |
166 | u32 mask = BIT(d->hwirq % IRQS_PER_WORD); |
167 | |
168 | intc->cpus[cpu_idx]->mask_cache[word] |= mask; |
169 | l1_writel(val: mask, reg: intc->cpus[cpu_idx]->map_base + |
170 | reg_mask_set(intc, word)); |
171 | } |
172 | |
173 | static void bcm7038_l1_unmask(struct irq_data *d) |
174 | { |
175 | struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d); |
176 | unsigned long flags; |
177 | |
178 | raw_spin_lock_irqsave(&intc->lock, flags); |
179 | __bcm7038_l1_unmask(d, cpu_idx: intc->affinity[d->hwirq]); |
180 | raw_spin_unlock_irqrestore(&intc->lock, flags); |
181 | } |
182 | |
183 | static void bcm7038_l1_mask(struct irq_data *d) |
184 | { |
185 | struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d); |
186 | unsigned long flags; |
187 | |
188 | raw_spin_lock_irqsave(&intc->lock, flags); |
189 | __bcm7038_l1_mask(d, cpu_idx: intc->affinity[d->hwirq]); |
190 | raw_spin_unlock_irqrestore(&intc->lock, flags); |
191 | } |
192 | |
193 | #if defined(CONFIG_MIPS) && defined(CONFIG_SMP) |
194 | static int bcm7038_l1_set_affinity(struct irq_data *d, |
195 | const struct cpumask *dest, |
196 | bool force) |
197 | { |
198 | struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d); |
199 | unsigned long flags; |
200 | irq_hw_number_t hw = d->hwirq; |
201 | u32 word = hw / IRQS_PER_WORD; |
202 | u32 mask = BIT(hw % IRQS_PER_WORD); |
203 | unsigned int first_cpu = cpumask_any_and(dest, cpu_online_mask); |
204 | bool was_disabled; |
205 | |
206 | raw_spin_lock_irqsave(&intc->lock, flags); |
207 | |
208 | was_disabled = !!(intc->cpus[intc->affinity[hw]]->mask_cache[word] & |
209 | mask); |
210 | __bcm7038_l1_mask(d, intc->affinity[hw]); |
211 | intc->affinity[hw] = first_cpu; |
212 | if (!was_disabled) |
213 | __bcm7038_l1_unmask(d, first_cpu); |
214 | |
215 | raw_spin_unlock_irqrestore(&intc->lock, flags); |
216 | irq_data_update_effective_affinity(d, cpumask_of(first_cpu)); |
217 | |
218 | return 0; |
219 | } |
220 | #endif |
221 | |
222 | static int __init bcm7038_l1_init_one(struct device_node *dn, |
223 | unsigned int idx, |
224 | struct bcm7038_l1_chip *intc) |
225 | { |
226 | struct resource res; |
227 | resource_size_t sz; |
228 | struct bcm7038_l1_cpu *cpu; |
229 | unsigned int i, n_words, parent_irq; |
230 | int ret; |
231 | |
232 | if (of_address_to_resource(dev: dn, index: idx, r: &res)) |
233 | return -EINVAL; |
234 | sz = resource_size(res: &res); |
235 | n_words = sz / REG_BYTES_PER_IRQ_WORD; |
236 | |
237 | if (n_words > MAX_WORDS) |
238 | return -EINVAL; |
239 | else if (!intc->n_words) |
240 | intc->n_words = n_words; |
241 | else if (intc->n_words != n_words) |
242 | return -EINVAL; |
243 | |
244 | ret = of_property_read_u32_array(np: dn , propname: "brcm,int-fwd-mask" , |
245 | out_values: intc->irq_fwd_mask, sz: n_words); |
246 | if (ret != 0 && ret != -EINVAL) { |
247 | /* property exists but has the wrong number of words */ |
248 | pr_err("invalid brcm,int-fwd-mask property\n" ); |
249 | return -EINVAL; |
250 | } |
251 | |
252 | cpu = intc->cpus[idx] = kzalloc(size: sizeof(*cpu) + n_words * sizeof(u32), |
253 | GFP_KERNEL); |
254 | if (!cpu) |
255 | return -ENOMEM; |
256 | |
257 | cpu->map_base = ioremap(offset: res.start, size: sz); |
258 | if (!cpu->map_base) |
259 | return -ENOMEM; |
260 | |
261 | for (i = 0; i < n_words; i++) { |
262 | l1_writel(val: ~intc->irq_fwd_mask[i], |
263 | reg: cpu->map_base + reg_mask_set(intc, word: i)); |
264 | l1_writel(val: intc->irq_fwd_mask[i], |
265 | reg: cpu->map_base + reg_mask_clr(intc, word: i)); |
266 | cpu->mask_cache[i] = ~intc->irq_fwd_mask[i]; |
267 | } |
268 | |
269 | parent_irq = irq_of_parse_and_map(node: dn, index: idx); |
270 | if (!parent_irq) { |
271 | pr_err("failed to map parent interrupt %d\n" , parent_irq); |
272 | return -EINVAL; |
273 | } |
274 | |
275 | if (of_property_read_bool(np: dn, propname: "brcm,irq-can-wake" )) |
276 | enable_irq_wake(irq: parent_irq); |
277 | |
278 | irq_set_chained_handler_and_data(irq: parent_irq, handle: bcm7038_l1_irq_handle, |
279 | data: intc); |
280 | |
281 | return 0; |
282 | } |
283 | |
284 | #ifdef CONFIG_PM_SLEEP |
285 | /* |
286 | * We keep a list of bcm7038_l1_chip used for suspend/resume. This hack is |
287 | * used because the struct chip_type suspend/resume hooks are not called |
288 | * unless chip_type is hooked onto a generic_chip. Since this driver does |
289 | * not use generic_chip, we need to manually hook our resume/suspend to |
290 | * syscore_ops. |
291 | */ |
292 | static LIST_HEAD(bcm7038_l1_intcs_list); |
293 | static DEFINE_RAW_SPINLOCK(bcm7038_l1_intcs_lock); |
294 | |
295 | static int bcm7038_l1_suspend(void) |
296 | { |
297 | struct bcm7038_l1_chip *intc; |
298 | int boot_cpu, word; |
299 | u32 val; |
300 | |
301 | /* Wakeup interrupt should only come from the boot cpu */ |
302 | #if defined(CONFIG_SMP) && defined(CONFIG_MIPS) |
303 | boot_cpu = cpu_logical_map(0); |
304 | #else |
305 | boot_cpu = 0; |
306 | #endif |
307 | |
308 | list_for_each_entry(intc, &bcm7038_l1_intcs_list, list) { |
309 | for (word = 0; word < intc->n_words; word++) { |
310 | val = intc->wake_mask[word] | intc->irq_fwd_mask[word]; |
311 | l1_writel(val: ~val, |
312 | reg: intc->cpus[boot_cpu]->map_base + reg_mask_set(intc, word)); |
313 | l1_writel(val, |
314 | reg: intc->cpus[boot_cpu]->map_base + reg_mask_clr(intc, word)); |
315 | } |
316 | } |
317 | |
318 | return 0; |
319 | } |
320 | |
321 | static void bcm7038_l1_resume(void) |
322 | { |
323 | struct bcm7038_l1_chip *intc; |
324 | int boot_cpu, word; |
325 | |
326 | #if defined(CONFIG_SMP) && defined(CONFIG_MIPS) |
327 | boot_cpu = cpu_logical_map(0); |
328 | #else |
329 | boot_cpu = 0; |
330 | #endif |
331 | |
332 | list_for_each_entry(intc, &bcm7038_l1_intcs_list, list) { |
333 | for (word = 0; word < intc->n_words; word++) { |
334 | l1_writel(val: intc->cpus[boot_cpu]->mask_cache[word], |
335 | reg: intc->cpus[boot_cpu]->map_base + reg_mask_set(intc, word)); |
336 | l1_writel(val: ~intc->cpus[boot_cpu]->mask_cache[word], |
337 | reg: intc->cpus[boot_cpu]->map_base + reg_mask_clr(intc, word)); |
338 | } |
339 | } |
340 | } |
341 | |
342 | static struct syscore_ops bcm7038_l1_syscore_ops = { |
343 | .suspend = bcm7038_l1_suspend, |
344 | .resume = bcm7038_l1_resume, |
345 | }; |
346 | |
347 | static int bcm7038_l1_set_wake(struct irq_data *d, unsigned int on) |
348 | { |
349 | struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d); |
350 | unsigned long flags; |
351 | u32 word = d->hwirq / IRQS_PER_WORD; |
352 | u32 mask = BIT(d->hwirq % IRQS_PER_WORD); |
353 | |
354 | raw_spin_lock_irqsave(&intc->lock, flags); |
355 | if (on) |
356 | intc->wake_mask[word] |= mask; |
357 | else |
358 | intc->wake_mask[word] &= ~mask; |
359 | raw_spin_unlock_irqrestore(&intc->lock, flags); |
360 | |
361 | return 0; |
362 | } |
363 | #endif |
364 | |
365 | static struct irq_chip bcm7038_l1_irq_chip = { |
366 | .name = "bcm7038-l1" , |
367 | .irq_mask = bcm7038_l1_mask, |
368 | .irq_unmask = bcm7038_l1_unmask, |
369 | #if defined(CONFIG_SMP) && defined(CONFIG_MIPS) |
370 | .irq_set_affinity = bcm7038_l1_set_affinity, |
371 | #endif |
372 | #ifdef CONFIG_PM_SLEEP |
373 | .irq_set_wake = bcm7038_l1_set_wake, |
374 | #endif |
375 | }; |
376 | |
377 | static int bcm7038_l1_map(struct irq_domain *d, unsigned int virq, |
378 | irq_hw_number_t hw_irq) |
379 | { |
380 | struct bcm7038_l1_chip *intc = d->host_data; |
381 | u32 mask = BIT(hw_irq % IRQS_PER_WORD); |
382 | u32 word = hw_irq / IRQS_PER_WORD; |
383 | |
384 | if (intc->irq_fwd_mask[word] & mask) |
385 | return -EPERM; |
386 | |
387 | irq_set_chip_and_handler(irq: virq, chip: &bcm7038_l1_irq_chip, handle: handle_level_irq); |
388 | irq_set_chip_data(irq: virq, data: d->host_data); |
389 | irqd_set_single_target(d: irq_get_irq_data(irq: virq)); |
390 | return 0; |
391 | } |
392 | |
393 | static const struct irq_domain_ops bcm7038_l1_domain_ops = { |
394 | .xlate = irq_domain_xlate_onecell, |
395 | .map = bcm7038_l1_map, |
396 | }; |
397 | |
398 | static int __init bcm7038_l1_of_init(struct device_node *dn, |
399 | struct device_node *parent) |
400 | { |
401 | struct bcm7038_l1_chip *intc; |
402 | int idx, ret; |
403 | |
404 | intc = kzalloc(size: sizeof(*intc), GFP_KERNEL); |
405 | if (!intc) |
406 | return -ENOMEM; |
407 | |
408 | raw_spin_lock_init(&intc->lock); |
409 | for_each_possible_cpu(idx) { |
410 | ret = bcm7038_l1_init_one(dn, idx, intc); |
411 | if (ret < 0) { |
412 | if (idx) |
413 | break; |
414 | pr_err("failed to remap intc L1 registers\n" ); |
415 | goto out_free; |
416 | } |
417 | } |
418 | |
419 | intc->domain = irq_domain_add_linear(of_node: dn, IRQS_PER_WORD * intc->n_words, |
420 | ops: &bcm7038_l1_domain_ops, |
421 | host_data: intc); |
422 | if (!intc->domain) { |
423 | ret = -ENOMEM; |
424 | goto out_unmap; |
425 | } |
426 | |
427 | #ifdef CONFIG_PM_SLEEP |
428 | /* Add bcm7038_l1_chip into a list */ |
429 | raw_spin_lock(&bcm7038_l1_intcs_lock); |
430 | list_add_tail(new: &intc->list, head: &bcm7038_l1_intcs_list); |
431 | raw_spin_unlock(&bcm7038_l1_intcs_lock); |
432 | |
433 | if (list_is_singular(head: &bcm7038_l1_intcs_list)) |
434 | register_syscore_ops(ops: &bcm7038_l1_syscore_ops); |
435 | #endif |
436 | |
437 | pr_info("registered BCM7038 L1 intc (%pOF, IRQs: %d)\n" , |
438 | dn, IRQS_PER_WORD * intc->n_words); |
439 | |
440 | return 0; |
441 | |
442 | out_unmap: |
443 | for_each_possible_cpu(idx) { |
444 | struct bcm7038_l1_cpu *cpu = intc->cpus[idx]; |
445 | |
446 | if (cpu) { |
447 | if (cpu->map_base) |
448 | iounmap(addr: cpu->map_base); |
449 | kfree(objp: cpu); |
450 | } |
451 | } |
452 | out_free: |
453 | kfree(objp: intc); |
454 | return ret; |
455 | } |
456 | |
457 | IRQCHIP_PLATFORM_DRIVER_BEGIN(bcm7038_l1) |
458 | IRQCHIP_MATCH("brcm,bcm7038-l1-intc" , bcm7038_l1_of_init) |
459 | IRQCHIP_PLATFORM_DRIVER_END(bcm7038_l1) |
460 | MODULE_DESCRIPTION("Broadcom STB 7038-style L1/L2 interrupt controller" ); |
461 | MODULE_LICENSE("GPL v2" ); |
462 | |