Commit | Line | Data |
---|---|---|
a5042de2 FF |
1 | /* |
2 | * Broadcom BCM7120 style Level 2 interrupt controller driver | |
3 | * | |
4 | * Copyright (C) 2014 Broadcom Corporation | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
12 | ||
13 | #include <linux/init.h> | |
14 | #include <linux/slab.h> | |
15 | #include <linux/module.h> | |
c17261fa | 16 | #include <linux/kconfig.h> |
a5042de2 FF |
17 | #include <linux/platform_device.h> |
18 | #include <linux/of.h> | |
19 | #include <linux/of_irq.h> | |
20 | #include <linux/of_address.h> | |
21 | #include <linux/of_platform.h> | |
22 | #include <linux/interrupt.h> | |
23 | #include <linux/irq.h> | |
24 | #include <linux/io.h> | |
25 | #include <linux/irqdomain.h> | |
26 | #include <linux/reboot.h> | |
c76acf4d | 27 | #include <linux/bitops.h> |
a5042de2 FF |
28 | #include <linux/irqchip/chained_irq.h> |
29 | ||
30 | #include "irqchip.h" | |
31 | ||
a5042de2 FF |
32 | /* Register offset in the L2 interrupt controller */ |
33 | #define IRQEN 0x00 | |
34 | #define IRQSTAT 0x04 | |
35 | ||
c76acf4d KC |
36 | #define MAX_WORDS 4 |
37 | #define IRQS_PER_WORD 32 | |
38 | ||
a5042de2 | 39 | struct bcm7120_l2_intc_data { |
c76acf4d KC |
40 | unsigned int n_words; |
41 | void __iomem *base[MAX_WORDS]; | |
a5042de2 FF |
42 | struct irq_domain *domain; |
43 | bool can_wake; | |
c76acf4d KC |
44 | u32 irq_fwd_mask[MAX_WORDS]; |
45 | u32 irq_map_mask[MAX_WORDS]; | |
a5042de2 FF |
46 | }; |
47 | ||
48 | static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc) | |
49 | { | |
50 | struct bcm7120_l2_intc_data *b = irq_desc_get_handler_data(desc); | |
51 | struct irq_chip *chip = irq_desc_get_chip(desc); | |
c76acf4d | 52 | unsigned int idx; |
a5042de2 FF |
53 | |
54 | chained_irq_enter(chip, desc); | |
55 | ||
c76acf4d KC |
56 | for (idx = 0; idx < b->n_words; idx++) { |
57 | int base = idx * IRQS_PER_WORD; | |
58 | struct irq_chip_generic *gc = | |
59 | irq_get_domain_generic_chip(b->domain, base); | |
60 | unsigned long pending; | |
61 | int hwirq; | |
62 | ||
63 | irq_gc_lock(gc); | |
c17261fa | 64 | pending = irq_reg_readl(gc, IRQSTAT) & gc->mask_cache; |
c76acf4d KC |
65 | irq_gc_unlock(gc); |
66 | ||
67 | for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) { | |
68 | generic_handle_irq(irq_find_mapping(b->domain, | |
69 | base + hwirq)); | |
70 | } | |
a5042de2 FF |
71 | } |
72 | ||
a5042de2 FF |
73 | chained_irq_exit(chip, desc); |
74 | } | |
75 | ||
76 | static void bcm7120_l2_intc_suspend(struct irq_data *d) | |
77 | { | |
78 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); | |
79 | struct bcm7120_l2_intc_data *b = gc->private; | |
a5042de2 FF |
80 | |
81 | irq_gc_lock(gc); | |
c17261fa KC |
82 | if (b->can_wake) |
83 | irq_reg_writel(gc, gc->mask_cache | gc->wake_active, IRQEN); | |
a5042de2 FF |
84 | irq_gc_unlock(gc); |
85 | } | |
86 | ||
87 | static void bcm7120_l2_intc_resume(struct irq_data *d) | |
88 | { | |
89 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); | |
a5042de2 FF |
90 | |
91 | /* Restore the saved mask */ | |
92 | irq_gc_lock(gc); | |
c17261fa | 93 | irq_reg_writel(gc, gc->mask_cache, IRQEN); |
a5042de2 FF |
94 | irq_gc_unlock(gc); |
95 | } | |
96 | ||
97 | static int bcm7120_l2_intc_init_one(struct device_node *dn, | |
98 | struct bcm7120_l2_intc_data *data, | |
99 | int irq, const __be32 *map_mask) | |
100 | { | |
101 | int parent_irq; | |
c76acf4d | 102 | unsigned int idx; |
a5042de2 FF |
103 | |
104 | parent_irq = irq_of_parse_and_map(dn, irq); | |
714710e1 | 105 | if (!parent_irq) { |
a5042de2 | 106 | pr_err("failed to map interrupt %d\n", irq); |
714710e1 | 107 | return -EINVAL; |
a5042de2 FF |
108 | } |
109 | ||
c76acf4d KC |
110 | /* For multiple parent IRQs with multiple words, this looks like: |
111 | * <irq0_w0 irq0_w1 irq1_w0 irq1_w1 ...> | |
112 | */ | |
113 | for (idx = 0; idx < data->n_words; idx++) | |
114 | data->irq_map_mask[idx] |= | |
115 | be32_to_cpup(map_mask + irq * data->n_words + idx); | |
a5042de2 FF |
116 | |
117 | irq_set_handler_data(parent_irq, data); | |
118 | irq_set_chained_handler(parent_irq, bcm7120_l2_intc_irq_handle); | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
123 | int __init bcm7120_l2_intc_of_init(struct device_node *dn, | |
124 | struct device_node *parent) | |
125 | { | |
126 | unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; | |
127 | struct bcm7120_l2_intc_data *data; | |
128 | struct irq_chip_generic *gc; | |
129 | struct irq_chip_type *ct; | |
130 | const __be32 *map_mask; | |
131 | int num_parent_irqs; | |
c76acf4d | 132 | int ret = 0, len; |
c17261fa | 133 | unsigned int idx, irq, flags; |
a5042de2 FF |
134 | |
135 | data = kzalloc(sizeof(*data), GFP_KERNEL); | |
136 | if (!data) | |
137 | return -ENOMEM; | |
138 | ||
c76acf4d KC |
139 | for (idx = 0; idx < MAX_WORDS; idx++) { |
140 | data->base[idx] = of_iomap(dn, idx); | |
141 | if (!data->base[idx]) | |
142 | break; | |
143 | data->n_words = idx + 1; | |
144 | } | |
145 | if (!data->n_words) { | |
a5042de2 FF |
146 | pr_err("failed to remap intc L2 registers\n"); |
147 | ret = -ENOMEM; | |
c76acf4d | 148 | goto out_unmap; |
a5042de2 FF |
149 | } |
150 | ||
c76acf4d KC |
151 | /* Enable all interrupts specified in the interrupt forward mask; |
152 | * disable all others. If the property doesn't exist (-EINVAL), | |
153 | * assume all zeroes. | |
a5042de2 | 154 | */ |
c76acf4d KC |
155 | ret = of_property_read_u32_array(dn, "brcm,int-fwd-mask", |
156 | data->irq_fwd_mask, data->n_words); | |
157 | if (ret == 0 || ret == -EINVAL) { | |
158 | for (idx = 0; idx < data->n_words; idx++) | |
159 | __raw_writel(data->irq_fwd_mask[idx], | |
160 | data->base[idx] + IRQEN); | |
161 | } else { | |
162 | /* property exists but has the wrong number of words */ | |
163 | pr_err("invalid int-fwd-mask property\n"); | |
164 | ret = -EINVAL; | |
165 | goto out_unmap; | |
166 | } | |
a5042de2 FF |
167 | |
168 | num_parent_irqs = of_irq_count(dn); | |
169 | if (num_parent_irqs <= 0) { | |
170 | pr_err("invalid number of parent interrupts\n"); | |
171 | ret = -ENOMEM; | |
172 | goto out_unmap; | |
173 | } | |
174 | ||
175 | map_mask = of_get_property(dn, "brcm,int-map-mask", &len); | |
c76acf4d KC |
176 | if (!map_mask || |
177 | (len != (sizeof(*map_mask) * num_parent_irqs * data->n_words))) { | |
a5042de2 FF |
178 | pr_err("invalid brcm,int-map-mask property\n"); |
179 | ret = -EINVAL; | |
180 | goto out_unmap; | |
181 | } | |
182 | ||
183 | for (irq = 0; irq < num_parent_irqs; irq++) { | |
184 | ret = bcm7120_l2_intc_init_one(dn, data, irq, map_mask); | |
185 | if (ret) | |
186 | goto out_unmap; | |
187 | } | |
188 | ||
c76acf4d KC |
189 | data->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * data->n_words, |
190 | &irq_generic_chip_ops, NULL); | |
a5042de2 FF |
191 | if (!data->domain) { |
192 | ret = -ENOMEM; | |
193 | goto out_unmap; | |
194 | } | |
195 | ||
c17261fa KC |
196 | /* MIPS chips strapped for BE will automagically configure the |
197 | * peripheral registers for CPU-native byte order. | |
198 | */ | |
199 | flags = IRQ_GC_INIT_MASK_CACHE; | |
200 | if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) | |
201 | flags |= IRQ_GC_BE_IO; | |
202 | ||
c76acf4d | 203 | ret = irq_alloc_domain_generic_chips(data->domain, IRQS_PER_WORD, 1, |
c17261fa | 204 | dn->full_name, handle_level_irq, clr, 0, flags); |
a5042de2 FF |
205 | if (ret) { |
206 | pr_err("failed to allocate generic irq chip\n"); | |
207 | goto out_free_domain; | |
208 | } | |
209 | ||
c76acf4d | 210 | if (of_property_read_bool(dn, "brcm,irq-can-wake")) |
a5042de2 | 211 | data->can_wake = true; |
c76acf4d KC |
212 | |
213 | for (idx = 0; idx < data->n_words; idx++) { | |
214 | irq = idx * IRQS_PER_WORD; | |
215 | gc = irq_get_domain_generic_chip(data->domain, irq); | |
216 | ||
217 | gc->unused = 0xffffffff & ~data->irq_map_mask[idx]; | |
218 | gc->reg_base = data->base[idx]; | |
219 | gc->private = data; | |
220 | ct = gc->chip_types; | |
221 | ||
222 | ct->regs.mask = IRQEN; | |
223 | ct->chip.irq_mask = irq_gc_mask_clr_bit; | |
224 | ct->chip.irq_unmask = irq_gc_mask_set_bit; | |
225 | ct->chip.irq_ack = irq_gc_noop; | |
226 | ct->chip.irq_suspend = bcm7120_l2_intc_suspend; | |
227 | ct->chip.irq_resume = bcm7120_l2_intc_resume; | |
228 | ||
229 | if (data->can_wake) { | |
230 | /* This IRQ chip can wake the system, set all | |
231 | * relevant child interupts in wake_enabled mask | |
232 | */ | |
233 | gc->wake_enabled = 0xffffffff; | |
234 | gc->wake_enabled &= ~gc->unused; | |
235 | ct->chip.irq_set_wake = irq_gc_set_wake; | |
236 | } | |
a5042de2 FF |
237 | } |
238 | ||
239 | pr_info("registered BCM7120 L2 intc (mem: 0x%p, parent IRQ(s): %d)\n", | |
c76acf4d | 240 | data->base[0], num_parent_irqs); |
a5042de2 FF |
241 | |
242 | return 0; | |
243 | ||
244 | out_free_domain: | |
245 | irq_domain_remove(data->domain); | |
246 | out_unmap: | |
c76acf4d KC |
247 | for (idx = 0; idx < MAX_WORDS; idx++) { |
248 | if (data->base[idx]) | |
249 | iounmap(data->base[idx]); | |
250 | } | |
a5042de2 FF |
251 | kfree(data); |
252 | return ret; | |
253 | } | |
a4fcbb86 | 254 | IRQCHIP_DECLARE(bcm7120_l2_intc, "brcm,bcm7120-l2-intc", |
a5042de2 | 255 | bcm7120_l2_intc_of_init); |