]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
9f0fd049 AP |
2 | /* |
3 | * This file implements an irqchip for OPAL events. Whenever there is | |
4 | * an interrupt that is handled by OPAL we get passed a list of events | |
5 | * that Linux needs to do something about. These basically look like | |
6 | * interrupts to Linux so we implement an irqchip to handle them. | |
7 | * | |
8 | * Copyright Alistair Popple, IBM Corporation 2014. | |
9f0fd049 AP |
9 | */ |
10 | #include <linux/bitops.h> | |
11 | #include <linux/irq.h> | |
12 | #include <linux/irqchip.h> | |
13 | #include <linux/irqdomain.h> | |
14 | #include <linux/interrupt.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/of.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/kthread.h> | |
19 | #include <linux/delay.h> | |
20 | #include <linux/slab.h> | |
77b5f703 | 21 | #include <linux/of_irq.h> |
9f0fd049 AP |
22 | |
23 | #include <asm/machdep.h> | |
24 | #include <asm/opal.h> | |
25 | ||
26 | #include "powernv.h" | |
27 | ||
28 | /* Maximum number of events supported by OPAL firmware */ | |
29 | #define MAX_NUM_EVENTS 64 | |
30 | ||
31 | struct opal_event_irqchip { | |
32 | struct irq_chip irqchip; | |
33 | struct irq_domain *domain; | |
34 | unsigned long mask; | |
35 | }; | |
36 | static struct opal_event_irqchip opal_event_irqchip; | |
56c0b48b | 37 | static u64 last_outstanding_events; |
77b5f703 BH |
38 | static int opal_irq_count; |
39 | static struct resource *opal_irqs; | |
9f0fd049 | 40 | |
56c0b48b | 41 | void opal_handle_events(void) |
25642e14 | 42 | { |
56c0b48b NP |
43 | __be64 events = 0; |
44 | u64 e; | |
45 | ||
46 | e = READ_ONCE(last_outstanding_events) & opal_event_irqchip.mask; | |
47 | again: | |
48 | while (e) { | |
2c899658 | 49 | int hwirq; |
56c0b48b NP |
50 | |
51 | hwirq = fls64(e) - 1; | |
52 | e &= ~BIT_ULL(hwirq); | |
53 | ||
54 | local_irq_disable(); | |
2c899658 MZ |
55 | irq_enter(); |
56 | generic_handle_domain_irq(opal_event_irqchip.domain, hwirq); | |
57 | irq_exit(); | |
56c0b48b | 58 | local_irq_enable(); |
25642e14 | 59 | |
56c0b48b | 60 | cond_resched(); |
25642e14 | 61 | } |
56c0b48b NP |
62 | last_outstanding_events = 0; |
63 | if (opal_poll_events(&events) != OPAL_SUCCESS) | |
64 | return; | |
65 | e = be64_to_cpu(events) & opal_event_irqchip.mask; | |
66 | if (e) | |
67 | goto again; | |
68 | } | |
25642e14 | 69 | |
56c0b48b NP |
70 | bool opal_have_pending_events(void) |
71 | { | |
72 | if (last_outstanding_events & opal_event_irqchip.mask) | |
73 | return true; | |
74 | return false; | |
25642e14 AP |
75 | } |
76 | ||
9f0fd049 AP |
77 | static void opal_event_mask(struct irq_data *d) |
78 | { | |
79 | clear_bit(d->hwirq, &opal_event_irqchip.mask); | |
80 | } | |
81 | ||
82 | static void opal_event_unmask(struct irq_data *d) | |
83 | { | |
84 | set_bit(d->hwirq, &opal_event_irqchip.mask); | |
56c0b48b NP |
85 | if (opal_have_pending_events()) |
86 | opal_wake_poller(); | |
9f0fd049 AP |
87 | } |
88 | ||
89 | static int opal_event_set_type(struct irq_data *d, unsigned int flow_type) | |
90 | { | |
91 | /* | |
92 | * For now we only support level triggered events. The irq | |
93 | * handler will be called continuously until the event has | |
94 | * been cleared in OPAL. | |
95 | */ | |
96 | if (flow_type != IRQ_TYPE_LEVEL_HIGH) | |
97 | return -EINVAL; | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static struct opal_event_irqchip opal_event_irqchip = { | |
103 | .irqchip = { | |
104 | .name = "OPAL EVT", | |
105 | .irq_mask = opal_event_mask, | |
106 | .irq_unmask = opal_event_unmask, | |
107 | .irq_set_type = opal_event_set_type, | |
108 | }, | |
109 | .mask = 0, | |
110 | }; | |
111 | ||
112 | static int opal_event_map(struct irq_domain *d, unsigned int irq, | |
113 | irq_hw_number_t hwirq) | |
114 | { | |
115 | irq_set_chip_data(irq, &opal_event_irqchip); | |
116 | irq_set_chip_and_handler(irq, &opal_event_irqchip.irqchip, | |
117 | handle_level_irq); | |
118 | ||
119 | return 0; | |
120 | } | |
121 | ||
9f0fd049 AP |
122 | static irqreturn_t opal_interrupt(int irq, void *data) |
123 | { | |
124 | __be64 events; | |
125 | ||
126 | opal_handle_interrupt(virq_to_hw(irq), &events); | |
56c0b48b NP |
127 | last_outstanding_events = be64_to_cpu(events); |
128 | if (opal_have_pending_events()) | |
129 | opal_wake_poller(); | |
9f0fd049 AP |
130 | |
131 | return IRQ_HANDLED; | |
132 | } | |
133 | ||
ad3aedfb MZ |
134 | static int opal_event_match(struct irq_domain *h, struct device_node *node, |
135 | enum irq_domain_bus_token bus_token) | |
9f0fd049 | 136 | { |
5d4c9bc7 | 137 | return irq_domain_get_of_node(h) == node; |
9f0fd049 AP |
138 | } |
139 | ||
140 | static int opal_event_xlate(struct irq_domain *h, struct device_node *np, | |
141 | const u32 *intspec, unsigned int intsize, | |
142 | irq_hw_number_t *out_hwirq, unsigned int *out_flags) | |
143 | { | |
144 | *out_hwirq = intspec[0]; | |
145 | *out_flags = IRQ_TYPE_LEVEL_HIGH; | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | static const struct irq_domain_ops opal_event_domain_ops = { | |
151 | .match = opal_event_match, | |
152 | .map = opal_event_map, | |
153 | .xlate = opal_event_xlate, | |
154 | }; | |
155 | ||
156 | void opal_event_shutdown(void) | |
157 | { | |
158 | unsigned int i; | |
159 | ||
160 | /* First free interrupts, which will also mask them */ | |
161 | for (i = 0; i < opal_irq_count; i++) { | |
77b5f703 | 162 | if (!opal_irqs || !opal_irqs[i].start) |
c6baa077 ME |
163 | continue; |
164 | ||
c0beffc4 | 165 | if (in_interrupt() || irqs_disabled()) |
77b5f703 | 166 | disable_irq_nosync(opal_irqs[i].start); |
c6baa077 | 167 | else |
77b5f703 | 168 | free_irq(opal_irqs[i].start, NULL); |
c6baa077 | 169 | |
77b5f703 | 170 | opal_irqs[i].start = 0; |
9f0fd049 AP |
171 | } |
172 | } | |
173 | ||
174 | int __init opal_event_init(void) | |
175 | { | |
176 | struct device_node *dn, *opal_node; | |
77b5f703 BH |
177 | bool old_style = false; |
178 | int i, rc = 0; | |
9f0fd049 AP |
179 | |
180 | opal_node = of_find_node_by_path("/ibm,opal"); | |
181 | if (!opal_node) { | |
182 | pr_warn("opal: Node not found\n"); | |
183 | return -ENODEV; | |
184 | } | |
185 | ||
186 | /* If dn is NULL it means the domain won't be linked to a DT | |
187 | * node so therefore irq_of_parse_and_map(...) wont work. But | |
188 | * that shouldn't be problem because if we're running a | |
189 | * version of skiboot that doesn't have the dn then the | |
190 | * devices won't have the correct properties and will have to | |
191 | * fall back to the legacy method (opal_event_request(...)) | |
192 | * anyway. */ | |
193 | dn = of_find_compatible_node(NULL, NULL, "ibm,opal-event"); | |
194 | opal_event_irqchip.domain = irq_domain_add_linear(dn, MAX_NUM_EVENTS, | |
195 | &opal_event_domain_ops, &opal_event_irqchip); | |
196 | of_node_put(dn); | |
197 | if (!opal_event_irqchip.domain) { | |
198 | pr_warn("opal: Unable to create irq domain\n"); | |
199 | rc = -ENOMEM; | |
200 | goto out; | |
201 | } | |
202 | ||
77b5f703 BH |
203 | /* Look for new-style (standard) "interrupts" property */ |
204 | opal_irq_count = of_irq_count(opal_node); | |
2717a33d | 205 | |
77b5f703 BH |
206 | /* Absent ? Look for the old one */ |
207 | if (opal_irq_count < 1) { | |
208 | /* Get opal-interrupts property and names if present */ | |
209 | rc = of_property_count_u32_elems(opal_node, "opal-interrupts"); | |
210 | if (rc > 0) | |
211 | opal_irq_count = rc; | |
212 | old_style = true; | |
213 | } | |
9f0fd049 | 214 | |
77b5f703 BH |
215 | /* No interrupts ? Bail out */ |
216 | if (!opal_irq_count) | |
217 | goto out; | |
2717a33d | 218 | |
77b5f703 BH |
219 | pr_debug("OPAL: Found %d interrupts reserved for OPAL using %s scheme\n", |
220 | opal_irq_count, old_style ? "old" : "new"); | |
2717a33d | 221 | |
77b5f703 BH |
222 | /* Allocate an IRQ resources array */ |
223 | opal_irqs = kcalloc(opal_irq_count, sizeof(struct resource), GFP_KERNEL); | |
224 | if (WARN_ON(!opal_irqs)) { | |
225 | rc = -ENOMEM; | |
226 | goto out; | |
2717a33d BH |
227 | } |
228 | ||
77b5f703 BH |
229 | /* Build the resources array */ |
230 | if (old_style) { | |
231 | /* Old style "opal-interrupts" property */ | |
232 | for (i = 0; i < opal_irq_count; i++) { | |
233 | struct resource *r = &opal_irqs[i]; | |
234 | const char *name = NULL; | |
235 | u32 hw_irq; | |
236 | int virq; | |
237 | ||
238 | rc = of_property_read_u32_index(opal_node, "opal-interrupts", | |
239 | i, &hw_irq); | |
240 | if (WARN_ON(rc < 0)) { | |
241 | opal_irq_count = i; | |
242 | break; | |
243 | } | |
244 | of_property_read_string_index(opal_node, "opal-interrupts-names", | |
245 | i, &name); | |
246 | virq = irq_create_mapping(NULL, hw_irq); | |
247 | if (!virq) { | |
248 | pr_warn("Failed to map OPAL irq 0x%x\n", hw_irq); | |
249 | continue; | |
250 | } | |
251 | r->start = r->end = virq; | |
252 | r->flags = IORESOURCE_IRQ | IRQ_TYPE_LEVEL_LOW; | |
253 | r->name = name; | |
254 | } | |
255 | } else { | |
256 | /* new style standard "interrupts" property */ | |
257 | rc = of_irq_to_resource_table(opal_node, opal_irqs, opal_irq_count); | |
258 | if (WARN_ON(rc < 0)) { | |
259 | opal_irq_count = 0; | |
260 | kfree(opal_irqs); | |
261 | goto out; | |
262 | } | |
263 | if (WARN_ON(rc < opal_irq_count)) | |
264 | opal_irq_count = rc; | |
265 | } | |
2717a33d BH |
266 | |
267 | /* Install interrupt handlers */ | |
268 | for (i = 0; i < opal_irq_count; i++) { | |
77b5f703 BH |
269 | struct resource *r = &opal_irqs[i]; |
270 | const char *name; | |
9f0fd049 | 271 | |
77b5f703 BH |
272 | /* Prefix name */ |
273 | if (r->name && strlen(r->name)) | |
274 | name = kasprintf(GFP_KERNEL, "opal-%s", r->name); | |
2717a33d BH |
275 | else |
276 | name = kasprintf(GFP_KERNEL, "opal"); | |
277 | ||
9f0fd049 | 278 | /* Install interrupt handler */ |
77b5f703 | 279 | rc = request_irq(r->start, opal_interrupt, r->flags & IRQD_TRIGGER_MASK, |
2717a33d | 280 | name, NULL); |
9f0fd049 | 281 | if (rc) { |
77b5f703 | 282 | pr_warn("Error %d requesting OPAL irq %d\n", rc, (int)r->start); |
9f0fd049 AP |
283 | continue; |
284 | } | |
9f0fd049 | 285 | } |
77b5f703 BH |
286 | rc = 0; |
287 | out: | |
9f0fd049 AP |
288 | of_node_put(opal_node); |
289 | return rc; | |
290 | } | |
02b6505c | 291 | machine_arch_initcall(powernv, opal_event_init); |
9f0fd049 AP |
292 | |
293 | /** | |
294 | * opal_event_request(unsigned int opal_event_nr) - Request an event | |
295 | * @opal_event_nr: the opal event number to request | |
296 | * | |
297 | * This routine can be used to find the linux virq number which can | |
298 | * then be passed to request_irq to assign a handler for a particular | |
299 | * opal event. This should only be used by legacy devices which don't | |
300 | * have proper device tree bindings. Most devices should use | |
301 | * irq_of_parse_and_map() instead. | |
302 | */ | |
303 | int opal_event_request(unsigned int opal_event_nr) | |
304 | { | |
02b6505c | 305 | if (WARN_ON_ONCE(!opal_event_irqchip.domain)) |
ef24ba70 | 306 | return 0; |
02b6505c | 307 | |
9f0fd049 AP |
308 | return irq_create_mapping(opal_event_irqchip.domain, opal_event_nr); |
309 | } | |
310 | EXPORT_SYMBOL(opal_event_request); |