1 | /* |
2 | * Marvell Orion SoCs IRQ chip driver. |
3 | * |
4 | * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> |
5 | * |
6 | * This file is licensed under the terms of the GNU General Public |
7 | * License version 2. This program is licensed "as is" without any |
8 | * warranty of any kind, whether express or implied. |
9 | */ |
10 | |
11 | #include <linux/io.h> |
12 | #include <linux/irq.h> |
13 | #include <linux/irqchip.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/of_irq.h> |
17 | #include <asm/exception.h> |
18 | #include <asm/mach/irq.h> |
19 | |
20 | /* |
21 | * Orion SoC main interrupt controller |
22 | */ |
23 | #define ORION_IRQS_PER_CHIP 32 |
24 | |
25 | #define ORION_IRQ_CAUSE 0x00 |
26 | #define ORION_IRQ_MASK 0x04 |
27 | #define ORION_IRQ_FIQ_MASK 0x08 |
28 | #define ORION_IRQ_ENDP_MASK 0x0c |
29 | |
30 | static struct irq_domain *orion_irq_domain; |
31 | |
32 | static void |
33 | __exception_irq_entry orion_handle_irq(struct pt_regs *regs) |
34 | { |
35 | struct irq_domain_chip_generic *dgc = orion_irq_domain->gc; |
36 | int n, base = 0; |
37 | |
38 | for (n = 0; n < dgc->num_chips; n++, base += ORION_IRQS_PER_CHIP) { |
39 | struct irq_chip_generic *gc = |
40 | irq_get_domain_generic_chip(orion_irq_domain, base); |
41 | u32 stat = readl_relaxed(gc->reg_base + ORION_IRQ_CAUSE) & |
42 | gc->mask_cache; |
43 | while (stat) { |
44 | u32 hwirq = __fls(stat); |
45 | generic_handle_domain_irq(orion_irq_domain, |
46 | gc->irq_base + hwirq); |
47 | stat &= ~(1 << hwirq); |
48 | } |
49 | } |
50 | } |
51 | |
52 | static int __init orion_irq_init(struct device_node *np, |
53 | struct device_node *parent) |
54 | { |
55 | unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; |
56 | int n, ret, base, num_chips = 0; |
57 | struct resource r; |
58 | |
59 | /* count number of irq chips by valid reg addresses */ |
60 | num_chips = of_address_count(np); |
61 | |
62 | orion_irq_domain = irq_domain_add_linear(of_node: np, |
63 | size: num_chips * ORION_IRQS_PER_CHIP, |
64 | ops: &irq_generic_chip_ops, NULL); |
65 | if (!orion_irq_domain) |
66 | panic(fmt: "%pOFn: unable to add irq domain\n" , np); |
67 | |
68 | ret = irq_alloc_domain_generic_chips(orion_irq_domain, |
69 | ORION_IRQS_PER_CHIP, 1, np->full_name, |
70 | handle_level_irq, clr, 0, |
71 | IRQ_GC_INIT_MASK_CACHE); |
72 | if (ret) |
73 | panic(fmt: "%pOFn: unable to alloc irq domain gc\n" , np); |
74 | |
75 | for (n = 0, base = 0; n < num_chips; n++, base += ORION_IRQS_PER_CHIP) { |
76 | struct irq_chip_generic *gc = |
77 | irq_get_domain_generic_chip(d: orion_irq_domain, hw_irq: base); |
78 | |
79 | of_address_to_resource(dev: np, index: n, r: &r); |
80 | |
81 | if (!request_mem_region(r.start, resource_size(&r), np->name)) |
82 | panic(fmt: "%pOFn: unable to request mem region %d" , |
83 | np, n); |
84 | |
85 | gc->reg_base = ioremap(offset: r.start, size: resource_size(res: &r)); |
86 | if (!gc->reg_base) |
87 | panic(fmt: "%pOFn: unable to map resource %d" , np, n); |
88 | |
89 | gc->chip_types[0].regs.mask = ORION_IRQ_MASK; |
90 | gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; |
91 | gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; |
92 | |
93 | /* mask all interrupts */ |
94 | writel(val: 0, addr: gc->reg_base + ORION_IRQ_MASK); |
95 | } |
96 | |
97 | set_handle_irq(orion_handle_irq); |
98 | return 0; |
99 | } |
100 | IRQCHIP_DECLARE(orion_intc, "marvell,orion-intc" , orion_irq_init); |
101 | |
102 | /* |
103 | * Orion SoC bridge interrupt controller |
104 | */ |
105 | #define ORION_BRIDGE_IRQ_CAUSE 0x00 |
106 | #define ORION_BRIDGE_IRQ_MASK 0x04 |
107 | |
108 | static void orion_bridge_irq_handler(struct irq_desc *desc) |
109 | { |
110 | struct irq_domain *d = irq_desc_get_handler_data(desc); |
111 | |
112 | struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, hw_irq: 0); |
113 | u32 stat = readl_relaxed(gc->reg_base + ORION_BRIDGE_IRQ_CAUSE) & |
114 | gc->mask_cache; |
115 | |
116 | while (stat) { |
117 | u32 hwirq = __fls(word: stat); |
118 | |
119 | generic_handle_domain_irq(domain: d, hwirq: gc->irq_base + hwirq); |
120 | stat &= ~(1 << hwirq); |
121 | } |
122 | } |
123 | |
124 | /* |
125 | * Bridge IRQ_CAUSE is asserted regardless of IRQ_MASK register. |
126 | * To avoid interrupt events on stale irqs, we clear them before unmask. |
127 | */ |
128 | static unsigned int orion_bridge_irq_startup(struct irq_data *d) |
129 | { |
130 | struct irq_chip_type *ct = irq_data_get_chip_type(d); |
131 | |
132 | ct->chip.irq_ack(d); |
133 | ct->chip.irq_unmask(d); |
134 | return 0; |
135 | } |
136 | |
137 | static int __init orion_bridge_irq_init(struct device_node *np, |
138 | struct device_node *parent) |
139 | { |
140 | unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; |
141 | struct resource r; |
142 | struct irq_domain *domain; |
143 | struct irq_chip_generic *gc; |
144 | int ret, irq, nrirqs = 32; |
145 | |
146 | /* get optional number of interrupts provided */ |
147 | of_property_read_u32(np, propname: "marvell,#interrupts" , out_value: &nrirqs); |
148 | |
149 | domain = irq_domain_add_linear(of_node: np, size: nrirqs, |
150 | ops: &irq_generic_chip_ops, NULL); |
151 | if (!domain) { |
152 | pr_err("%pOFn: unable to add irq domain\n" , np); |
153 | return -ENOMEM; |
154 | } |
155 | |
156 | ret = irq_alloc_domain_generic_chips(domain, nrirqs, 1, np->name, |
157 | handle_edge_irq, clr, 0, IRQ_GC_INIT_MASK_CACHE); |
158 | if (ret) { |
159 | pr_err("%pOFn: unable to alloc irq domain gc\n" , np); |
160 | return ret; |
161 | } |
162 | |
163 | ret = of_address_to_resource(dev: np, index: 0, r: &r); |
164 | if (ret) { |
165 | pr_err("%pOFn: unable to get resource\n" , np); |
166 | return ret; |
167 | } |
168 | |
169 | if (!request_mem_region(r.start, resource_size(&r), np->name)) { |
170 | pr_err("%s: unable to request mem region\n" , np->name); |
171 | return -ENOMEM; |
172 | } |
173 | |
174 | /* Map the parent interrupt for the chained handler */ |
175 | irq = irq_of_parse_and_map(node: np, index: 0); |
176 | if (irq <= 0) { |
177 | pr_err("%pOFn: unable to parse irq\n" , np); |
178 | return -EINVAL; |
179 | } |
180 | |
181 | gc = irq_get_domain_generic_chip(d: domain, hw_irq: 0); |
182 | gc->reg_base = ioremap(offset: r.start, size: resource_size(res: &r)); |
183 | if (!gc->reg_base) { |
184 | pr_err("%pOFn: unable to map resource\n" , np); |
185 | return -ENOMEM; |
186 | } |
187 | |
188 | gc->chip_types[0].regs.ack = ORION_BRIDGE_IRQ_CAUSE; |
189 | gc->chip_types[0].regs.mask = ORION_BRIDGE_IRQ_MASK; |
190 | gc->chip_types[0].chip.irq_startup = orion_bridge_irq_startup; |
191 | gc->chip_types[0].chip.irq_ack = irq_gc_ack_clr_bit; |
192 | gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; |
193 | gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; |
194 | |
195 | /* mask and clear all interrupts */ |
196 | writel(val: 0, addr: gc->reg_base + ORION_BRIDGE_IRQ_MASK); |
197 | writel(val: 0, addr: gc->reg_base + ORION_BRIDGE_IRQ_CAUSE); |
198 | |
199 | irq_set_chained_handler_and_data(irq, handle: orion_bridge_irq_handler, |
200 | data: domain); |
201 | |
202 | return 0; |
203 | } |
204 | IRQCHIP_DECLARE(orion_bridge_intc, |
205 | "marvell,orion-bridge-intc" , orion_bridge_irq_init); |
206 | |