12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 29f0fd049SAlistair Popple /* 39f0fd049SAlistair Popple * This file implements an irqchip for OPAL events. Whenever there is 49f0fd049SAlistair Popple * an interrupt that is handled by OPAL we get passed a list of events 59f0fd049SAlistair Popple * that Linux needs to do something about. These basically look like 69f0fd049SAlistair Popple * interrupts to Linux so we implement an irqchip to handle them. 79f0fd049SAlistair Popple * 89f0fd049SAlistair Popple * Copyright Alistair Popple, IBM Corporation 2014. 99f0fd049SAlistair Popple */ 109f0fd049SAlistair Popple #include <linux/bitops.h> 119f0fd049SAlistair Popple #include <linux/irq.h> 129f0fd049SAlistair Popple #include <linux/irqchip.h> 139f0fd049SAlistair Popple #include <linux/irqdomain.h> 149f0fd049SAlistair Popple #include <linux/interrupt.h> 159f0fd049SAlistair Popple #include <linux/module.h> 169f0fd049SAlistair Popple #include <linux/of.h> 179f0fd049SAlistair Popple #include <linux/platform_device.h> 189f0fd049SAlistair Popple #include <linux/kthread.h> 199f0fd049SAlistair Popple #include <linux/delay.h> 209f0fd049SAlistair Popple #include <linux/slab.h> 2177b5f703SBenjamin Herrenschmidt #include <linux/of_irq.h> 229f0fd049SAlistair Popple 239f0fd049SAlistair Popple #include <asm/machdep.h> 249f0fd049SAlistair Popple #include <asm/opal.h> 259f0fd049SAlistair Popple 269f0fd049SAlistair Popple #include "powernv.h" 279f0fd049SAlistair Popple 289f0fd049SAlistair Popple /* Maximum number of events supported by OPAL firmware */ 299f0fd049SAlistair Popple #define MAX_NUM_EVENTS 64 309f0fd049SAlistair Popple 319f0fd049SAlistair Popple struct opal_event_irqchip { 329f0fd049SAlistair Popple struct irq_chip irqchip; 339f0fd049SAlistair Popple struct irq_domain *domain; 349f0fd049SAlistair Popple unsigned long mask; 359f0fd049SAlistair Popple }; 369f0fd049SAlistair Popple static struct opal_event_irqchip opal_event_irqchip; 3756c0b48bSNicholas Piggin static u64 last_outstanding_events; 3877b5f703SBenjamin Herrenschmidt static int opal_irq_count; 3977b5f703SBenjamin Herrenschmidt static struct resource *opal_irqs; 409f0fd049SAlistair Popple 4156c0b48bSNicholas Piggin void opal_handle_events(void) 4225642e14SAlistair Popple { 4356c0b48bSNicholas Piggin __be64 events = 0; 4456c0b48bSNicholas Piggin u64 e; 4525642e14SAlistair Popple 4656c0b48bSNicholas Piggin e = READ_ONCE(last_outstanding_events) & opal_event_irqchip.mask; 4756c0b48bSNicholas Piggin again: 4856c0b48bSNicholas Piggin while (e) { 49*2c899658SMarc Zyngier int hwirq; 5025642e14SAlistair Popple 5156c0b48bSNicholas Piggin hwirq = fls64(e) - 1; 5256c0b48bSNicholas Piggin e &= ~BIT_ULL(hwirq); 5356c0b48bSNicholas Piggin 5456c0b48bSNicholas Piggin local_irq_disable(); 5556c0b48bSNicholas Piggin irq_enter(); 56*2c899658SMarc Zyngier generic_handle_domain_irq(opal_event_irqchip.domain, hwirq); 5756c0b48bSNicholas Piggin irq_exit(); 5856c0b48bSNicholas Piggin local_irq_enable(); 5956c0b48bSNicholas Piggin 6056c0b48bSNicholas Piggin cond_resched(); 6125642e14SAlistair Popple } 6256c0b48bSNicholas Piggin last_outstanding_events = 0; 6356c0b48bSNicholas Piggin if (opal_poll_events(&events) != OPAL_SUCCESS) 6456c0b48bSNicholas Piggin return; 6556c0b48bSNicholas Piggin e = be64_to_cpu(events) & opal_event_irqchip.mask; 6656c0b48bSNicholas Piggin if (e) 6756c0b48bSNicholas Piggin goto again; 6856c0b48bSNicholas Piggin } 6956c0b48bSNicholas Piggin 7056c0b48bSNicholas Piggin bool opal_have_pending_events(void) 7156c0b48bSNicholas Piggin { 7256c0b48bSNicholas Piggin if (last_outstanding_events & opal_event_irqchip.mask) 7356c0b48bSNicholas Piggin return true; 7456c0b48bSNicholas Piggin return false; 7525642e14SAlistair Popple } 7625642e14SAlistair Popple 779f0fd049SAlistair Popple static void opal_event_mask(struct irq_data *d) 789f0fd049SAlistair Popple { 799f0fd049SAlistair Popple clear_bit(d->hwirq, &opal_event_irqchip.mask); 809f0fd049SAlistair Popple } 819f0fd049SAlistair Popple 829f0fd049SAlistair Popple static void opal_event_unmask(struct irq_data *d) 839f0fd049SAlistair Popple { 849f0fd049SAlistair Popple set_bit(d->hwirq, &opal_event_irqchip.mask); 8556c0b48bSNicholas Piggin if (opal_have_pending_events()) 8656c0b48bSNicholas Piggin opal_wake_poller(); 879f0fd049SAlistair Popple } 889f0fd049SAlistair Popple 899f0fd049SAlistair Popple static int opal_event_set_type(struct irq_data *d, unsigned int flow_type) 909f0fd049SAlistair Popple { 919f0fd049SAlistair Popple /* 929f0fd049SAlistair Popple * For now we only support level triggered events. The irq 939f0fd049SAlistair Popple * handler will be called continuously until the event has 949f0fd049SAlistair Popple * been cleared in OPAL. 959f0fd049SAlistair Popple */ 969f0fd049SAlistair Popple if (flow_type != IRQ_TYPE_LEVEL_HIGH) 979f0fd049SAlistair Popple return -EINVAL; 989f0fd049SAlistair Popple 999f0fd049SAlistair Popple return 0; 1009f0fd049SAlistair Popple } 1019f0fd049SAlistair Popple 1029f0fd049SAlistair Popple static struct opal_event_irqchip opal_event_irqchip = { 1039f0fd049SAlistair Popple .irqchip = { 1049f0fd049SAlistair Popple .name = "OPAL EVT", 1059f0fd049SAlistair Popple .irq_mask = opal_event_mask, 1069f0fd049SAlistair Popple .irq_unmask = opal_event_unmask, 1079f0fd049SAlistair Popple .irq_set_type = opal_event_set_type, 1089f0fd049SAlistair Popple }, 1099f0fd049SAlistair Popple .mask = 0, 1109f0fd049SAlistair Popple }; 1119f0fd049SAlistair Popple 1129f0fd049SAlistair Popple static int opal_event_map(struct irq_domain *d, unsigned int irq, 1139f0fd049SAlistair Popple irq_hw_number_t hwirq) 1149f0fd049SAlistair Popple { 1159f0fd049SAlistair Popple irq_set_chip_data(irq, &opal_event_irqchip); 1169f0fd049SAlistair Popple irq_set_chip_and_handler(irq, &opal_event_irqchip.irqchip, 1179f0fd049SAlistair Popple handle_level_irq); 1189f0fd049SAlistair Popple 1199f0fd049SAlistair Popple return 0; 1209f0fd049SAlistair Popple } 1219f0fd049SAlistair Popple 1229f0fd049SAlistair Popple static irqreturn_t opal_interrupt(int irq, void *data) 1239f0fd049SAlistair Popple { 1249f0fd049SAlistair Popple __be64 events; 1259f0fd049SAlistair Popple 1269f0fd049SAlistair Popple opal_handle_interrupt(virq_to_hw(irq), &events); 12756c0b48bSNicholas Piggin last_outstanding_events = be64_to_cpu(events); 12856c0b48bSNicholas Piggin if (opal_have_pending_events()) 12956c0b48bSNicholas Piggin opal_wake_poller(); 1309f0fd049SAlistair Popple 1319f0fd049SAlistair Popple return IRQ_HANDLED; 1329f0fd049SAlistair Popple } 1339f0fd049SAlistair Popple 134ad3aedfbSMarc Zyngier static int opal_event_match(struct irq_domain *h, struct device_node *node, 135ad3aedfbSMarc Zyngier enum irq_domain_bus_token bus_token) 1369f0fd049SAlistair Popple { 1375d4c9bc7SMarc Zyngier return irq_domain_get_of_node(h) == node; 1389f0fd049SAlistair Popple } 1399f0fd049SAlistair Popple 1409f0fd049SAlistair Popple static int opal_event_xlate(struct irq_domain *h, struct device_node *np, 1419f0fd049SAlistair Popple const u32 *intspec, unsigned int intsize, 1429f0fd049SAlistair Popple irq_hw_number_t *out_hwirq, unsigned int *out_flags) 1439f0fd049SAlistair Popple { 1449f0fd049SAlistair Popple *out_hwirq = intspec[0]; 1459f0fd049SAlistair Popple *out_flags = IRQ_TYPE_LEVEL_HIGH; 1469f0fd049SAlistair Popple 1479f0fd049SAlistair Popple return 0; 1489f0fd049SAlistair Popple } 1499f0fd049SAlistair Popple 1509f0fd049SAlistair Popple static const struct irq_domain_ops opal_event_domain_ops = { 1519f0fd049SAlistair Popple .match = opal_event_match, 1529f0fd049SAlistair Popple .map = opal_event_map, 1539f0fd049SAlistair Popple .xlate = opal_event_xlate, 1549f0fd049SAlistair Popple }; 1559f0fd049SAlistair Popple 1569f0fd049SAlistair Popple void opal_event_shutdown(void) 1579f0fd049SAlistair Popple { 1589f0fd049SAlistair Popple unsigned int i; 1599f0fd049SAlistair Popple 1609f0fd049SAlistair Popple /* First free interrupts, which will also mask them */ 1619f0fd049SAlistair Popple for (i = 0; i < opal_irq_count; i++) { 16277b5f703SBenjamin Herrenschmidt if (!opal_irqs || !opal_irqs[i].start) 163c6baa077SMichael Ellerman continue; 164c6baa077SMichael Ellerman 165c0beffc4SNicholas Piggin if (in_interrupt() || irqs_disabled()) 16677b5f703SBenjamin Herrenschmidt disable_irq_nosync(opal_irqs[i].start); 167c6baa077SMichael Ellerman else 16877b5f703SBenjamin Herrenschmidt free_irq(opal_irqs[i].start, NULL); 169c6baa077SMichael Ellerman 17077b5f703SBenjamin Herrenschmidt opal_irqs[i].start = 0; 1719f0fd049SAlistair Popple } 1729f0fd049SAlistair Popple } 1739f0fd049SAlistair Popple 1749f0fd049SAlistair Popple int __init opal_event_init(void) 1759f0fd049SAlistair Popple { 1769f0fd049SAlistair Popple struct device_node *dn, *opal_node; 17777b5f703SBenjamin Herrenschmidt bool old_style = false; 17877b5f703SBenjamin Herrenschmidt int i, rc = 0; 1799f0fd049SAlistair Popple 1809f0fd049SAlistair Popple opal_node = of_find_node_by_path("/ibm,opal"); 1819f0fd049SAlistair Popple if (!opal_node) { 1829f0fd049SAlistair Popple pr_warn("opal: Node not found\n"); 1839f0fd049SAlistair Popple return -ENODEV; 1849f0fd049SAlistair Popple } 1859f0fd049SAlistair Popple 1869f0fd049SAlistair Popple /* If dn is NULL it means the domain won't be linked to a DT 1879f0fd049SAlistair Popple * node so therefore irq_of_parse_and_map(...) wont work. But 1889f0fd049SAlistair Popple * that shouldn't be problem because if we're running a 1899f0fd049SAlistair Popple * version of skiboot that doesn't have the dn then the 1909f0fd049SAlistair Popple * devices won't have the correct properties and will have to 1919f0fd049SAlistair Popple * fall back to the legacy method (opal_event_request(...)) 1929f0fd049SAlistair Popple * anyway. */ 1939f0fd049SAlistair Popple dn = of_find_compatible_node(NULL, NULL, "ibm,opal-event"); 1949f0fd049SAlistair Popple opal_event_irqchip.domain = irq_domain_add_linear(dn, MAX_NUM_EVENTS, 1959f0fd049SAlistair Popple &opal_event_domain_ops, &opal_event_irqchip); 1969f0fd049SAlistair Popple of_node_put(dn); 1979f0fd049SAlistair Popple if (!opal_event_irqchip.domain) { 1989f0fd049SAlistair Popple pr_warn("opal: Unable to create irq domain\n"); 1999f0fd049SAlistair Popple rc = -ENOMEM; 2009f0fd049SAlistair Popple goto out; 2019f0fd049SAlistair Popple } 2029f0fd049SAlistair Popple 20377b5f703SBenjamin Herrenschmidt /* Look for new-style (standard) "interrupts" property */ 20477b5f703SBenjamin Herrenschmidt opal_irq_count = of_irq_count(opal_node); 20577b5f703SBenjamin Herrenschmidt 20677b5f703SBenjamin Herrenschmidt /* Absent ? Look for the old one */ 20777b5f703SBenjamin Herrenschmidt if (opal_irq_count < 1) { 2082717a33dSBenjamin Herrenschmidt /* Get opal-interrupts property and names if present */ 2092717a33dSBenjamin Herrenschmidt rc = of_property_count_u32_elems(opal_node, "opal-interrupts"); 21077b5f703SBenjamin Herrenschmidt if (rc > 0) 2112717a33dSBenjamin Herrenschmidt opal_irq_count = rc; 21277b5f703SBenjamin Herrenschmidt old_style = true; 2132717a33dSBenjamin Herrenschmidt } 2142717a33dSBenjamin Herrenschmidt 21577b5f703SBenjamin Herrenschmidt /* No interrupts ? Bail out */ 21677b5f703SBenjamin Herrenschmidt if (!opal_irq_count) 21777b5f703SBenjamin Herrenschmidt goto out; 21877b5f703SBenjamin Herrenschmidt 21977b5f703SBenjamin Herrenschmidt pr_debug("OPAL: Found %d interrupts reserved for OPAL using %s scheme\n", 22077b5f703SBenjamin Herrenschmidt opal_irq_count, old_style ? "old" : "new"); 22177b5f703SBenjamin Herrenschmidt 22277b5f703SBenjamin Herrenschmidt /* Allocate an IRQ resources array */ 22377b5f703SBenjamin Herrenschmidt opal_irqs = kcalloc(opal_irq_count, sizeof(struct resource), GFP_KERNEL); 22477b5f703SBenjamin Herrenschmidt if (WARN_ON(!opal_irqs)) { 22577b5f703SBenjamin Herrenschmidt rc = -ENOMEM; 22677b5f703SBenjamin Herrenschmidt goto out; 22777b5f703SBenjamin Herrenschmidt } 22877b5f703SBenjamin Herrenschmidt 22977b5f703SBenjamin Herrenschmidt /* Build the resources array */ 23077b5f703SBenjamin Herrenschmidt if (old_style) { 23177b5f703SBenjamin Herrenschmidt /* Old style "opal-interrupts" property */ 23277b5f703SBenjamin Herrenschmidt for (i = 0; i < opal_irq_count; i++) { 23377b5f703SBenjamin Herrenschmidt struct resource *r = &opal_irqs[i]; 23477b5f703SBenjamin Herrenschmidt const char *name = NULL; 23577b5f703SBenjamin Herrenschmidt u32 hw_irq; 23677b5f703SBenjamin Herrenschmidt int virq; 23777b5f703SBenjamin Herrenschmidt 23877b5f703SBenjamin Herrenschmidt rc = of_property_read_u32_index(opal_node, "opal-interrupts", 23977b5f703SBenjamin Herrenschmidt i, &hw_irq); 24077b5f703SBenjamin Herrenschmidt if (WARN_ON(rc < 0)) { 24177b5f703SBenjamin Herrenschmidt opal_irq_count = i; 24277b5f703SBenjamin Herrenschmidt break; 24377b5f703SBenjamin Herrenschmidt } 24477b5f703SBenjamin Herrenschmidt of_property_read_string_index(opal_node, "opal-interrupts-names", 24577b5f703SBenjamin Herrenschmidt i, &name); 24677b5f703SBenjamin Herrenschmidt virq = irq_create_mapping(NULL, hw_irq); 24777b5f703SBenjamin Herrenschmidt if (!virq) { 24877b5f703SBenjamin Herrenschmidt pr_warn("Failed to map OPAL irq 0x%x\n", hw_irq); 24977b5f703SBenjamin Herrenschmidt continue; 25077b5f703SBenjamin Herrenschmidt } 25177b5f703SBenjamin Herrenschmidt r->start = r->end = virq; 25277b5f703SBenjamin Herrenschmidt r->flags = IORESOURCE_IRQ | IRQ_TYPE_LEVEL_LOW; 25377b5f703SBenjamin Herrenschmidt r->name = name; 25477b5f703SBenjamin Herrenschmidt } 25577b5f703SBenjamin Herrenschmidt } else { 25677b5f703SBenjamin Herrenschmidt /* new style standard "interrupts" property */ 25777b5f703SBenjamin Herrenschmidt rc = of_irq_to_resource_table(opal_node, opal_irqs, opal_irq_count); 25877b5f703SBenjamin Herrenschmidt if (WARN_ON(rc < 0)) { 25977b5f703SBenjamin Herrenschmidt opal_irq_count = 0; 26077b5f703SBenjamin Herrenschmidt kfree(opal_irqs); 26177b5f703SBenjamin Herrenschmidt goto out; 26277b5f703SBenjamin Herrenschmidt } 26377b5f703SBenjamin Herrenschmidt if (WARN_ON(rc < opal_irq_count)) 26477b5f703SBenjamin Herrenschmidt opal_irq_count = rc; 26577b5f703SBenjamin Herrenschmidt } 2662717a33dSBenjamin Herrenschmidt 2672717a33dSBenjamin Herrenschmidt /* Install interrupt handlers */ 2682717a33dSBenjamin Herrenschmidt for (i = 0; i < opal_irq_count; i++) { 26977b5f703SBenjamin Herrenschmidt struct resource *r = &opal_irqs[i]; 27077b5f703SBenjamin Herrenschmidt const char *name; 2719f0fd049SAlistair Popple 27277b5f703SBenjamin Herrenschmidt /* Prefix name */ 27377b5f703SBenjamin Herrenschmidt if (r->name && strlen(r->name)) 27477b5f703SBenjamin Herrenschmidt name = kasprintf(GFP_KERNEL, "opal-%s", r->name); 2752717a33dSBenjamin Herrenschmidt else 2762717a33dSBenjamin Herrenschmidt name = kasprintf(GFP_KERNEL, "opal"); 2772717a33dSBenjamin Herrenschmidt 2789f0fd049SAlistair Popple /* Install interrupt handler */ 27977b5f703SBenjamin Herrenschmidt rc = request_irq(r->start, opal_interrupt, r->flags & IRQD_TRIGGER_MASK, 2802717a33dSBenjamin Herrenschmidt name, NULL); 2819f0fd049SAlistair Popple if (rc) { 28277b5f703SBenjamin Herrenschmidt pr_warn("Error %d requesting OPAL irq %d\n", rc, (int)r->start); 2839f0fd049SAlistair Popple continue; 2849f0fd049SAlistair Popple } 2859f0fd049SAlistair Popple } 28677b5f703SBenjamin Herrenschmidt rc = 0; 2879f0fd049SAlistair Popple out: 2889f0fd049SAlistair Popple of_node_put(opal_node); 2899f0fd049SAlistair Popple return rc; 2909f0fd049SAlistair Popple } 29102b6505cSAlistair Popple machine_arch_initcall(powernv, opal_event_init); 2929f0fd049SAlistair Popple 2939f0fd049SAlistair Popple /** 2949f0fd049SAlistair Popple * opal_event_request(unsigned int opal_event_nr) - Request an event 2959f0fd049SAlistair Popple * @opal_event_nr: the opal event number to request 2969f0fd049SAlistair Popple * 2979f0fd049SAlistair Popple * This routine can be used to find the linux virq number which can 2989f0fd049SAlistair Popple * then be passed to request_irq to assign a handler for a particular 2999f0fd049SAlistair Popple * opal event. This should only be used by legacy devices which don't 3009f0fd049SAlistair Popple * have proper device tree bindings. Most devices should use 3019f0fd049SAlistair Popple * irq_of_parse_and_map() instead. 3029f0fd049SAlistair Popple */ 3039f0fd049SAlistair Popple int opal_event_request(unsigned int opal_event_nr) 3049f0fd049SAlistair Popple { 30502b6505cSAlistair Popple if (WARN_ON_ONCE(!opal_event_irqchip.domain)) 306ef24ba70SMichael Ellerman return 0; 30702b6505cSAlistair Popple 3089f0fd049SAlistair Popple return irq_create_mapping(opal_event_irqchip.domain, opal_event_nr); 3099f0fd049SAlistair Popple } 3109f0fd049SAlistair Popple EXPORT_SYMBOL(opal_event_request); 311