18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * This file implements an irqchip for OPAL events. Whenever there is 48c2ecf20Sopenharmony_ci * an interrupt that is handled by OPAL we get passed a list of events 58c2ecf20Sopenharmony_ci * that Linux needs to do something about. These basically look like 68c2ecf20Sopenharmony_ci * interrupts to Linux so we implement an irqchip to handle them. 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright Alistair Popple, IBM Corporation 2014. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci#include <linux/bitops.h> 118c2ecf20Sopenharmony_ci#include <linux/irq.h> 128c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 138c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 148c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/kthread.h> 198c2ecf20Sopenharmony_ci#include <linux/delay.h> 208c2ecf20Sopenharmony_ci#include <linux/slab.h> 218c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include <asm/machdep.h> 248c2ecf20Sopenharmony_ci#include <asm/opal.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include "powernv.h" 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* Maximum number of events supported by OPAL firmware */ 298c2ecf20Sopenharmony_ci#define MAX_NUM_EVENTS 64 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct opal_event_irqchip { 328c2ecf20Sopenharmony_ci struct irq_chip irqchip; 338c2ecf20Sopenharmony_ci struct irq_domain *domain; 348c2ecf20Sopenharmony_ci unsigned long mask; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_cistatic struct opal_event_irqchip opal_event_irqchip; 378c2ecf20Sopenharmony_cistatic u64 last_outstanding_events; 388c2ecf20Sopenharmony_cistatic int opal_irq_count; 398c2ecf20Sopenharmony_cistatic struct resource *opal_irqs; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_civoid opal_handle_events(void) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci __be64 events = 0; 448c2ecf20Sopenharmony_ci u64 e; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci e = READ_ONCE(last_outstanding_events) & opal_event_irqchip.mask; 478c2ecf20Sopenharmony_ciagain: 488c2ecf20Sopenharmony_ci while (e) { 498c2ecf20Sopenharmony_ci int virq, hwirq; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci hwirq = fls64(e) - 1; 528c2ecf20Sopenharmony_ci e &= ~BIT_ULL(hwirq); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci local_irq_disable(); 558c2ecf20Sopenharmony_ci virq = irq_find_mapping(opal_event_irqchip.domain, hwirq); 568c2ecf20Sopenharmony_ci if (virq) { 578c2ecf20Sopenharmony_ci irq_enter(); 588c2ecf20Sopenharmony_ci generic_handle_irq(virq); 598c2ecf20Sopenharmony_ci irq_exit(); 608c2ecf20Sopenharmony_ci } 618c2ecf20Sopenharmony_ci local_irq_enable(); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci cond_resched(); 648c2ecf20Sopenharmony_ci } 658c2ecf20Sopenharmony_ci last_outstanding_events = 0; 668c2ecf20Sopenharmony_ci if (opal_poll_events(&events) != OPAL_SUCCESS) 678c2ecf20Sopenharmony_ci return; 688c2ecf20Sopenharmony_ci e = be64_to_cpu(events) & opal_event_irqchip.mask; 698c2ecf20Sopenharmony_ci if (e) 708c2ecf20Sopenharmony_ci goto again; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cibool opal_have_pending_events(void) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci if (last_outstanding_events & opal_event_irqchip.mask) 768c2ecf20Sopenharmony_ci return true; 778c2ecf20Sopenharmony_ci return false; 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic void opal_event_mask(struct irq_data *d) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci clear_bit(d->hwirq, &opal_event_irqchip.mask); 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic void opal_event_unmask(struct irq_data *d) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci set_bit(d->hwirq, &opal_event_irqchip.mask); 888c2ecf20Sopenharmony_ci if (opal_have_pending_events()) 898c2ecf20Sopenharmony_ci opal_wake_poller(); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int opal_event_set_type(struct irq_data *d, unsigned int flow_type) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci /* 958c2ecf20Sopenharmony_ci * For now we only support level triggered events. The irq 968c2ecf20Sopenharmony_ci * handler will be called continuously until the event has 978c2ecf20Sopenharmony_ci * been cleared in OPAL. 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci if (flow_type != IRQ_TYPE_LEVEL_HIGH) 1008c2ecf20Sopenharmony_ci return -EINVAL; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci return 0; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic struct opal_event_irqchip opal_event_irqchip = { 1068c2ecf20Sopenharmony_ci .irqchip = { 1078c2ecf20Sopenharmony_ci .name = "OPAL EVT", 1088c2ecf20Sopenharmony_ci .irq_mask = opal_event_mask, 1098c2ecf20Sopenharmony_ci .irq_unmask = opal_event_unmask, 1108c2ecf20Sopenharmony_ci .irq_set_type = opal_event_set_type, 1118c2ecf20Sopenharmony_ci }, 1128c2ecf20Sopenharmony_ci .mask = 0, 1138c2ecf20Sopenharmony_ci}; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic int opal_event_map(struct irq_domain *d, unsigned int irq, 1168c2ecf20Sopenharmony_ci irq_hw_number_t hwirq) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci irq_set_chip_data(irq, &opal_event_irqchip); 1198c2ecf20Sopenharmony_ci irq_set_chip_and_handler(irq, &opal_event_irqchip.irqchip, 1208c2ecf20Sopenharmony_ci handle_level_irq); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci return 0; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic irqreturn_t opal_interrupt(int irq, void *data) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci __be64 events; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci opal_handle_interrupt(virq_to_hw(irq), &events); 1308c2ecf20Sopenharmony_ci last_outstanding_events = be64_to_cpu(events); 1318c2ecf20Sopenharmony_ci if (opal_have_pending_events()) 1328c2ecf20Sopenharmony_ci opal_wake_poller(); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic int opal_event_match(struct irq_domain *h, struct device_node *node, 1388c2ecf20Sopenharmony_ci enum irq_domain_bus_token bus_token) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci return irq_domain_get_of_node(h) == node; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int opal_event_xlate(struct irq_domain *h, struct device_node *np, 1448c2ecf20Sopenharmony_ci const u32 *intspec, unsigned int intsize, 1458c2ecf20Sopenharmony_ci irq_hw_number_t *out_hwirq, unsigned int *out_flags) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci *out_hwirq = intspec[0]; 1488c2ecf20Sopenharmony_ci *out_flags = IRQ_TYPE_LEVEL_HIGH; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci return 0; 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic const struct irq_domain_ops opal_event_domain_ops = { 1548c2ecf20Sopenharmony_ci .match = opal_event_match, 1558c2ecf20Sopenharmony_ci .map = opal_event_map, 1568c2ecf20Sopenharmony_ci .xlate = opal_event_xlate, 1578c2ecf20Sopenharmony_ci}; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_civoid opal_event_shutdown(void) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci unsigned int i; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci /* First free interrupts, which will also mask them */ 1648c2ecf20Sopenharmony_ci for (i = 0; i < opal_irq_count; i++) { 1658c2ecf20Sopenharmony_ci if (!opal_irqs || !opal_irqs[i].start) 1668c2ecf20Sopenharmony_ci continue; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (in_interrupt() || irqs_disabled()) 1698c2ecf20Sopenharmony_ci disable_irq_nosync(opal_irqs[i].start); 1708c2ecf20Sopenharmony_ci else 1718c2ecf20Sopenharmony_ci free_irq(opal_irqs[i].start, NULL); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci opal_irqs[i].start = 0; 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ciint __init opal_event_init(void) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct device_node *dn, *opal_node; 1808c2ecf20Sopenharmony_ci bool old_style = false; 1818c2ecf20Sopenharmony_ci int i, rc = 0; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci opal_node = of_find_node_by_path("/ibm,opal"); 1848c2ecf20Sopenharmony_ci if (!opal_node) { 1858c2ecf20Sopenharmony_ci pr_warn("opal: Node not found\n"); 1868c2ecf20Sopenharmony_ci return -ENODEV; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci /* If dn is NULL it means the domain won't be linked to a DT 1908c2ecf20Sopenharmony_ci * node so therefore irq_of_parse_and_map(...) wont work. But 1918c2ecf20Sopenharmony_ci * that shouldn't be problem because if we're running a 1928c2ecf20Sopenharmony_ci * version of skiboot that doesn't have the dn then the 1938c2ecf20Sopenharmony_ci * devices won't have the correct properties and will have to 1948c2ecf20Sopenharmony_ci * fall back to the legacy method (opal_event_request(...)) 1958c2ecf20Sopenharmony_ci * anyway. */ 1968c2ecf20Sopenharmony_ci dn = of_find_compatible_node(NULL, NULL, "ibm,opal-event"); 1978c2ecf20Sopenharmony_ci opal_event_irqchip.domain = irq_domain_add_linear(dn, MAX_NUM_EVENTS, 1988c2ecf20Sopenharmony_ci &opal_event_domain_ops, &opal_event_irqchip); 1998c2ecf20Sopenharmony_ci of_node_put(dn); 2008c2ecf20Sopenharmony_ci if (!opal_event_irqchip.domain) { 2018c2ecf20Sopenharmony_ci pr_warn("opal: Unable to create irq domain\n"); 2028c2ecf20Sopenharmony_ci rc = -ENOMEM; 2038c2ecf20Sopenharmony_ci goto out; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* Look for new-style (standard) "interrupts" property */ 2078c2ecf20Sopenharmony_ci opal_irq_count = of_irq_count(opal_node); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* Absent ? Look for the old one */ 2108c2ecf20Sopenharmony_ci if (opal_irq_count < 1) { 2118c2ecf20Sopenharmony_ci /* Get opal-interrupts property and names if present */ 2128c2ecf20Sopenharmony_ci rc = of_property_count_u32_elems(opal_node, "opal-interrupts"); 2138c2ecf20Sopenharmony_ci if (rc > 0) 2148c2ecf20Sopenharmony_ci opal_irq_count = rc; 2158c2ecf20Sopenharmony_ci old_style = true; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci /* No interrupts ? Bail out */ 2198c2ecf20Sopenharmony_ci if (!opal_irq_count) 2208c2ecf20Sopenharmony_ci goto out; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci pr_debug("OPAL: Found %d interrupts reserved for OPAL using %s scheme\n", 2238c2ecf20Sopenharmony_ci opal_irq_count, old_style ? "old" : "new"); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* Allocate an IRQ resources array */ 2268c2ecf20Sopenharmony_ci opal_irqs = kcalloc(opal_irq_count, sizeof(struct resource), GFP_KERNEL); 2278c2ecf20Sopenharmony_ci if (WARN_ON(!opal_irqs)) { 2288c2ecf20Sopenharmony_ci rc = -ENOMEM; 2298c2ecf20Sopenharmony_ci goto out; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci /* Build the resources array */ 2338c2ecf20Sopenharmony_ci if (old_style) { 2348c2ecf20Sopenharmony_ci /* Old style "opal-interrupts" property */ 2358c2ecf20Sopenharmony_ci for (i = 0; i < opal_irq_count; i++) { 2368c2ecf20Sopenharmony_ci struct resource *r = &opal_irqs[i]; 2378c2ecf20Sopenharmony_ci const char *name = NULL; 2388c2ecf20Sopenharmony_ci u32 hw_irq; 2398c2ecf20Sopenharmony_ci int virq; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci rc = of_property_read_u32_index(opal_node, "opal-interrupts", 2428c2ecf20Sopenharmony_ci i, &hw_irq); 2438c2ecf20Sopenharmony_ci if (WARN_ON(rc < 0)) { 2448c2ecf20Sopenharmony_ci opal_irq_count = i; 2458c2ecf20Sopenharmony_ci break; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci of_property_read_string_index(opal_node, "opal-interrupts-names", 2488c2ecf20Sopenharmony_ci i, &name); 2498c2ecf20Sopenharmony_ci virq = irq_create_mapping(NULL, hw_irq); 2508c2ecf20Sopenharmony_ci if (!virq) { 2518c2ecf20Sopenharmony_ci pr_warn("Failed to map OPAL irq 0x%x\n", hw_irq); 2528c2ecf20Sopenharmony_ci continue; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci r->start = r->end = virq; 2558c2ecf20Sopenharmony_ci r->flags = IORESOURCE_IRQ | IRQ_TYPE_LEVEL_LOW; 2568c2ecf20Sopenharmony_ci r->name = name; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci } else { 2598c2ecf20Sopenharmony_ci /* new style standard "interrupts" property */ 2608c2ecf20Sopenharmony_ci rc = of_irq_to_resource_table(opal_node, opal_irqs, opal_irq_count); 2618c2ecf20Sopenharmony_ci if (WARN_ON(rc < 0)) { 2628c2ecf20Sopenharmony_ci opal_irq_count = 0; 2638c2ecf20Sopenharmony_ci kfree(opal_irqs); 2648c2ecf20Sopenharmony_ci goto out; 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci if (WARN_ON(rc < opal_irq_count)) 2678c2ecf20Sopenharmony_ci opal_irq_count = rc; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci /* Install interrupt handlers */ 2718c2ecf20Sopenharmony_ci for (i = 0; i < opal_irq_count; i++) { 2728c2ecf20Sopenharmony_ci struct resource *r = &opal_irqs[i]; 2738c2ecf20Sopenharmony_ci const char *name; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* Prefix name */ 2768c2ecf20Sopenharmony_ci if (r->name && strlen(r->name)) 2778c2ecf20Sopenharmony_ci name = kasprintf(GFP_KERNEL, "opal-%s", r->name); 2788c2ecf20Sopenharmony_ci else 2798c2ecf20Sopenharmony_ci name = kasprintf(GFP_KERNEL, "opal"); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci if (!name) 2828c2ecf20Sopenharmony_ci continue; 2838c2ecf20Sopenharmony_ci /* Install interrupt handler */ 2848c2ecf20Sopenharmony_ci rc = request_irq(r->start, opal_interrupt, r->flags & IRQD_TRIGGER_MASK, 2858c2ecf20Sopenharmony_ci name, NULL); 2868c2ecf20Sopenharmony_ci if (rc) { 2878c2ecf20Sopenharmony_ci pr_warn("Error %d requesting OPAL irq %d\n", rc, (int)r->start); 2888c2ecf20Sopenharmony_ci continue; 2898c2ecf20Sopenharmony_ci } 2908c2ecf20Sopenharmony_ci } 2918c2ecf20Sopenharmony_ci rc = 0; 2928c2ecf20Sopenharmony_ci out: 2938c2ecf20Sopenharmony_ci of_node_put(opal_node); 2948c2ecf20Sopenharmony_ci return rc; 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_cimachine_arch_initcall(powernv, opal_event_init); 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci/** 2998c2ecf20Sopenharmony_ci * opal_event_request(unsigned int opal_event_nr) - Request an event 3008c2ecf20Sopenharmony_ci * @opal_event_nr: the opal event number to request 3018c2ecf20Sopenharmony_ci * 3028c2ecf20Sopenharmony_ci * This routine can be used to find the linux virq number which can 3038c2ecf20Sopenharmony_ci * then be passed to request_irq to assign a handler for a particular 3048c2ecf20Sopenharmony_ci * opal event. This should only be used by legacy devices which don't 3058c2ecf20Sopenharmony_ci * have proper device tree bindings. Most devices should use 3068c2ecf20Sopenharmony_ci * irq_of_parse_and_map() instead. 3078c2ecf20Sopenharmony_ci */ 3088c2ecf20Sopenharmony_ciint opal_event_request(unsigned int opal_event_nr) 3098c2ecf20Sopenharmony_ci{ 3108c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(!opal_event_irqchip.domain)) 3118c2ecf20Sopenharmony_ci return 0; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci return irq_create_mapping(opal_event_irqchip.domain, opal_event_nr); 3148c2ecf20Sopenharmony_ci} 3158c2ecf20Sopenharmony_ciEXPORT_SYMBOL(opal_event_request); 316