aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/irqchip
diff options
context:
space:
mode:
authorKevin Cernekee <cernekee@gmail.com>2014-12-25 12:49:06 -0500
committerRalf Baechle <ralf@linux-mips.org>2015-04-01 11:21:37 -0400
commit5f7f0317ed28b86bdae9baf65bb72d405b6f79ee (patch)
tree20b8cdaf9b909afd426e76d8b6d2a2b9a9fa6b35 /drivers/irqchip
parent7b7230e70e9eda75356cf15c450b65b77924486f (diff)
IRQCHIP: Add new driver for BCM7038-style level 1 interrupt controllers
This is the main peripheral IRQ controller on the BCM7xxx MIPS chips; it has the following characteristics: - 64 to 160+ level IRQs - Atomic set/clear registers - Reasonably predictable register layout (N status words, then N mask status words, then N mask set words, then N mask clear words) - SMP affinity supported on most systems - Typically connected to MIPS IRQ 2,3,2,3 on CPUs 0,1,2,3 This driver registers one IRQ domain and one IRQ chip to cover all instances of the block. Up to 4 instances of the block may appear, as it supports 4-way IRQ affinity on BCM7435. The same block exists on the ARM BCM7xxx chips, but typically the ARM GIC is used instead. So this driver is primarily intended for MIPS STB chips. Signed-off-by: Kevin Cernekee <cernekee@gmail.com> Cc: f.fainelli@gmail.com Cc: jaedon.shin@gmail.com Cc: abrestic@chromium.org Cc: tglx@linutronix.de Cc: jason@lakedaemon.net Cc: jogo@openwrt.org Cc: arnd@arndb.de Cc: computersforpeace@gmail.com Cc: linux-mips@linux-mips.org Cc: devicetree@vger.kernel.org Cc: linux-kernel@vger.kernel.org Patchwork: https://patchwork.linux-mips.org/patch/8844/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'drivers/irqchip')
-rw-r--r--drivers/irqchip/Kconfig5
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-bcm7038-l1.c335
3 files changed, 341 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index cc79d2a5a8c2..241a5b2dd6a1 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -60,6 +60,11 @@ config ATMEL_AIC5_IRQ
60 select MULTI_IRQ_HANDLER 60 select MULTI_IRQ_HANDLER
61 select SPARSE_IRQ 61 select SPARSE_IRQ
62 62
63config BCM7038_L1_IRQ
64 bool
65 select GENERIC_IRQ_CHIP
66 select IRQ_DOMAIN
67
63config BCM7120_L2_IRQ 68config BCM7120_L2_IRQ
64 bool 69 bool
65 select GENERIC_IRQ_CHIP 70 select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 42965d2476bb..89e45613de76 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o
37obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o 37obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
38obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o 38obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
39obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o 39obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
40obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
40obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o 41obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
41obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o 42obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o
42obj-$(CONFIG_KEYSTONE_IRQ) += irq-keystone.o 43obj-$(CONFIG_KEYSTONE_IRQ) += irq-keystone.o
diff --git a/drivers/irqchip/irq-bcm7038-l1.c b/drivers/irqchip/irq-bcm7038-l1.c
new file mode 100644
index 000000000000..d3b8c8be15f6
--- /dev/null
+++ b/drivers/irqchip/irq-bcm7038-l1.c
@@ -0,0 +1,335 @@
1/*
2 * Broadcom BCM7038 style Level 1 interrupt controller driver
3 *
4 * Copyright (C) 2014 Broadcom Corporation
5 * Author: Kevin Cernekee
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13
14#include <linux/bitops.h>
15#include <linux/kconfig.h>
16#include <linux/kernel.h>
17#include <linux/init.h>
18#include <linux/interrupt.h>
19#include <linux/io.h>
20#include <linux/ioport.h>
21#include <linux/irq.h>
22#include <linux/irqdomain.h>
23#include <linux/module.h>
24#include <linux/of.h>
25#include <linux/of_irq.h>
26#include <linux/of_address.h>
27#include <linux/of_platform.h>
28#include <linux/platform_device.h>
29#include <linux/slab.h>
30#include <linux/smp.h>
31#include <linux/types.h>
32#include <linux/irqchip/chained_irq.h>
33
34#include "irqchip.h"
35
36#define IRQS_PER_WORD 32
37#define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 4)
38#define MAX_WORDS 8
39
40struct bcm7038_l1_cpu;
41
42struct bcm7038_l1_chip {
43 raw_spinlock_t lock;
44 unsigned int n_words;
45 struct irq_domain *domain;
46 struct bcm7038_l1_cpu *cpus[NR_CPUS];
47 u8 affinity[MAX_WORDS * IRQS_PER_WORD];
48};
49
50struct bcm7038_l1_cpu {
51 void __iomem *map_base;
52 u32 mask_cache[0];
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
79static 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
85static 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
91static 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
97static 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
103static 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(reg);
109}
110
111static 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, reg);
117}
118
119static void bcm7038_l1_irq_handle(unsigned int irq, 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#ifdef CONFIG_SMP
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(cpu->map_base + reg_status(intc, 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_irq(irq_find_mapping(intc->domain,
146 base + hwirq));
147 }
148 }
149
150 chained_irq_exit(chip, desc);
151}
152
153static void __bcm7038_l1_unmask(struct irq_data *d, unsigned int cpu_idx)
154{
155 struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
156 u32 word = d->hwirq / IRQS_PER_WORD;
157 u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
158
159 intc->cpus[cpu_idx]->mask_cache[word] &= ~mask;
160 l1_writel(mask, intc->cpus[cpu_idx]->map_base +
161 reg_mask_clr(intc, word));
162}
163
164static void __bcm7038_l1_mask(struct irq_data *d, unsigned int cpu_idx)
165{
166 struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
167 u32 word = d->hwirq / IRQS_PER_WORD;
168 u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
169
170 intc->cpus[cpu_idx]->mask_cache[word] |= mask;
171 l1_writel(mask, intc->cpus[cpu_idx]->map_base +
172 reg_mask_set(intc, word));
173}
174
175static void bcm7038_l1_unmask(struct irq_data *d)
176{
177 struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
178 unsigned long flags;
179
180 raw_spin_lock_irqsave(&intc->lock, flags);
181 __bcm7038_l1_unmask(d, intc->affinity[d->hwirq]);
182 raw_spin_unlock_irqrestore(&intc->lock, flags);
183}
184
185static void bcm7038_l1_mask(struct irq_data *d)
186{
187 struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
188 unsigned long flags;
189
190 raw_spin_lock_irqsave(&intc->lock, flags);
191 __bcm7038_l1_mask(d, intc->affinity[d->hwirq]);
192 raw_spin_unlock_irqrestore(&intc->lock, flags);
193}
194
195static int bcm7038_l1_set_affinity(struct irq_data *d,
196 const struct cpumask *dest,
197 bool force)
198{
199 struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
200 unsigned long flags;
201 irq_hw_number_t hw = d->hwirq;
202 u32 word = hw / IRQS_PER_WORD;
203 u32 mask = BIT(hw % IRQS_PER_WORD);
204 unsigned int first_cpu = cpumask_any_and(dest, cpu_online_mask);
205 bool was_disabled;
206
207 raw_spin_lock_irqsave(&intc->lock, flags);
208
209 was_disabled = !!(intc->cpus[intc->affinity[hw]]->mask_cache[word] &
210 mask);
211 __bcm7038_l1_mask(d, intc->affinity[hw]);
212 intc->affinity[hw] = first_cpu;
213 if (!was_disabled)
214 __bcm7038_l1_unmask(d, first_cpu);
215
216 raw_spin_unlock_irqrestore(&intc->lock, flags);
217 return 0;
218}
219
220static int __init bcm7038_l1_init_one(struct device_node *dn,
221 unsigned int idx,
222 struct bcm7038_l1_chip *intc)
223{
224 struct resource res;
225 resource_size_t sz;
226 struct bcm7038_l1_cpu *cpu;
227 unsigned int i, n_words, parent_irq;
228
229 if (of_address_to_resource(dn, idx, &res))
230 return -EINVAL;
231 sz = resource_size(&res);
232 n_words = sz / REG_BYTES_PER_IRQ_WORD;
233
234 if (n_words > MAX_WORDS)
235 return -EINVAL;
236 else if (!intc->n_words)
237 intc->n_words = n_words;
238 else if (intc->n_words != n_words)
239 return -EINVAL;
240
241 cpu = intc->cpus[idx] = kzalloc(sizeof(*cpu) + n_words * sizeof(u32),
242 GFP_KERNEL);
243 if (!cpu)
244 return -ENOMEM;
245
246 cpu->map_base = ioremap(res.start, sz);
247 if (!cpu->map_base)
248 return -ENOMEM;
249
250 for (i = 0; i < n_words; i++) {
251 l1_writel(0xffffffff, cpu->map_base + reg_mask_set(intc, i));
252 cpu->mask_cache[i] = 0xffffffff;
253 }
254
255 parent_irq = irq_of_parse_and_map(dn, idx);
256 if (!parent_irq) {
257 pr_err("failed to map parent interrupt %d\n", parent_irq);
258 return -EINVAL;
259 }
260 irq_set_handler_data(parent_irq, intc);
261 irq_set_chained_handler(parent_irq, bcm7038_l1_irq_handle);
262
263 return 0;
264}
265
266static struct irq_chip bcm7038_l1_irq_chip = {
267 .name = "bcm7038-l1",
268 .irq_mask = bcm7038_l1_mask,
269 .irq_unmask = bcm7038_l1_unmask,
270 .irq_set_affinity = bcm7038_l1_set_affinity,
271};
272
273static int bcm7038_l1_map(struct irq_domain *d, unsigned int virq,
274 irq_hw_number_t hw_irq)
275{
276 irq_set_chip_and_handler(virq, &bcm7038_l1_irq_chip, handle_level_irq);
277 irq_set_chip_data(virq, d->host_data);
278 return 0;
279}
280
281static const struct irq_domain_ops bcm7038_l1_domain_ops = {
282 .xlate = irq_domain_xlate_onecell,
283 .map = bcm7038_l1_map,
284};
285
286int __init bcm7038_l1_of_init(struct device_node *dn,
287 struct device_node *parent)
288{
289 struct bcm7038_l1_chip *intc;
290 int idx, ret;
291
292 intc = kzalloc(sizeof(*intc), GFP_KERNEL);
293 if (!intc)
294 return -ENOMEM;
295
296 raw_spin_lock_init(&intc->lock);
297 for_each_possible_cpu(idx) {
298 ret = bcm7038_l1_init_one(dn, idx, intc);
299 if (ret < 0) {
300 if (idx)
301 break;
302 pr_err("failed to remap intc L1 registers\n");
303 goto out_free;
304 }
305 }
306
307 intc->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * intc->n_words,
308 &bcm7038_l1_domain_ops,
309 intc);
310 if (!intc->domain) {
311 ret = -ENOMEM;
312 goto out_unmap;
313 }
314
315 pr_info("registered BCM7038 L1 intc (mem: 0x%p, IRQs: %d)\n",
316 intc->cpus[0]->map_base, IRQS_PER_WORD * intc->n_words);
317
318 return 0;
319
320out_unmap:
321 for_each_possible_cpu(idx) {
322 struct bcm7038_l1_cpu *cpu = intc->cpus[idx];
323
324 if (cpu) {
325 if (cpu->map_base)
326 iounmap(cpu->map_base);
327 kfree(cpu);
328 }
329 }
330out_free:
331 kfree(intc);
332 return ret;
333}
334
335IRQCHIP_DECLARE(bcm7038_l1, "brcm,bcm7038-l1-intc", bcm7038_l1_of_init);