162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * arch/powerpc/sysdev/uic.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * IBM PowerPC 4xx Universal Interrupt Controller 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright 2007 David Gibson <dwg@au1.ibm.com>, IBM Corporation. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/errno.h> 1262306a36Sopenharmony_ci#include <linux/reboot.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/stddef.h> 1562306a36Sopenharmony_ci#include <linux/sched.h> 1662306a36Sopenharmony_ci#include <linux/signal.h> 1762306a36Sopenharmony_ci#include <linux/device.h> 1862306a36Sopenharmony_ci#include <linux/spinlock.h> 1962306a36Sopenharmony_ci#include <linux/irq.h> 2062306a36Sopenharmony_ci#include <linux/interrupt.h> 2162306a36Sopenharmony_ci#include <linux/kernel_stat.h> 2262306a36Sopenharmony_ci#include <linux/of.h> 2362306a36Sopenharmony_ci#include <linux/of_irq.h> 2462306a36Sopenharmony_ci#include <asm/irq.h> 2562306a36Sopenharmony_ci#include <asm/io.h> 2662306a36Sopenharmony_ci#include <asm/dcr.h> 2762306a36Sopenharmony_ci#include <asm/uic.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define NR_UIC_INTS 32 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define UIC_SR 0x0 3262306a36Sopenharmony_ci#define UIC_ER 0x2 3362306a36Sopenharmony_ci#define UIC_CR 0x3 3462306a36Sopenharmony_ci#define UIC_PR 0x4 3562306a36Sopenharmony_ci#define UIC_TR 0x5 3662306a36Sopenharmony_ci#define UIC_MSR 0x6 3762306a36Sopenharmony_ci#define UIC_VR 0x7 3862306a36Sopenharmony_ci#define UIC_VCR 0x8 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistruct uic *primary_uic; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistruct uic { 4362306a36Sopenharmony_ci int index; 4462306a36Sopenharmony_ci int dcrbase; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci raw_spinlock_t lock; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci /* The remapper for this UIC */ 4962306a36Sopenharmony_ci struct irq_domain *irqhost; 5062306a36Sopenharmony_ci}; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic void uic_unmask_irq(struct irq_data *d) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct uic *uic = irq_data_get_irq_chip_data(d); 5562306a36Sopenharmony_ci unsigned int src = irqd_to_hwirq(d); 5662306a36Sopenharmony_ci unsigned long flags; 5762306a36Sopenharmony_ci u32 er, sr; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci sr = 1 << (31-src); 6062306a36Sopenharmony_ci raw_spin_lock_irqsave(&uic->lock, flags); 6162306a36Sopenharmony_ci /* ack level-triggered interrupts here */ 6262306a36Sopenharmony_ci if (irqd_is_level_type(d)) 6362306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_SR, sr); 6462306a36Sopenharmony_ci er = mfdcr(uic->dcrbase + UIC_ER); 6562306a36Sopenharmony_ci er |= sr; 6662306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_ER, er); 6762306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&uic->lock, flags); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic void uic_mask_irq(struct irq_data *d) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct uic *uic = irq_data_get_irq_chip_data(d); 7362306a36Sopenharmony_ci unsigned int src = irqd_to_hwirq(d); 7462306a36Sopenharmony_ci unsigned long flags; 7562306a36Sopenharmony_ci u32 er; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci raw_spin_lock_irqsave(&uic->lock, flags); 7862306a36Sopenharmony_ci er = mfdcr(uic->dcrbase + UIC_ER); 7962306a36Sopenharmony_ci er &= ~(1 << (31 - src)); 8062306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_ER, er); 8162306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&uic->lock, flags); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void uic_ack_irq(struct irq_data *d) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci struct uic *uic = irq_data_get_irq_chip_data(d); 8762306a36Sopenharmony_ci unsigned int src = irqd_to_hwirq(d); 8862306a36Sopenharmony_ci unsigned long flags; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci raw_spin_lock_irqsave(&uic->lock, flags); 9162306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_SR, 1 << (31-src)); 9262306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&uic->lock, flags); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic void uic_mask_ack_irq(struct irq_data *d) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct uic *uic = irq_data_get_irq_chip_data(d); 9862306a36Sopenharmony_ci unsigned int src = irqd_to_hwirq(d); 9962306a36Sopenharmony_ci unsigned long flags; 10062306a36Sopenharmony_ci u32 er, sr; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci sr = 1 << (31-src); 10362306a36Sopenharmony_ci raw_spin_lock_irqsave(&uic->lock, flags); 10462306a36Sopenharmony_ci er = mfdcr(uic->dcrbase + UIC_ER); 10562306a36Sopenharmony_ci er &= ~sr; 10662306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_ER, er); 10762306a36Sopenharmony_ci /* On the UIC, acking (i.e. clearing the SR bit) 10862306a36Sopenharmony_ci * a level irq will have no effect if the interrupt 10962306a36Sopenharmony_ci * is still asserted by the device, even if 11062306a36Sopenharmony_ci * the interrupt is already masked. Therefore 11162306a36Sopenharmony_ci * we only ack the egde interrupts here, while 11262306a36Sopenharmony_ci * level interrupts are ack'ed after the actual 11362306a36Sopenharmony_ci * isr call in the uic_unmask_irq() 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_ci if (!irqd_is_level_type(d)) 11662306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_SR, sr); 11762306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&uic->lock, flags); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic int uic_set_irq_type(struct irq_data *d, unsigned int flow_type) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct uic *uic = irq_data_get_irq_chip_data(d); 12362306a36Sopenharmony_ci unsigned int src = irqd_to_hwirq(d); 12462306a36Sopenharmony_ci unsigned long flags; 12562306a36Sopenharmony_ci int trigger, polarity; 12662306a36Sopenharmony_ci u32 tr, pr, mask; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci switch (flow_type & IRQ_TYPE_SENSE_MASK) { 12962306a36Sopenharmony_ci case IRQ_TYPE_NONE: 13062306a36Sopenharmony_ci uic_mask_irq(d); 13162306a36Sopenharmony_ci return 0; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci case IRQ_TYPE_EDGE_RISING: 13462306a36Sopenharmony_ci trigger = 1; polarity = 1; 13562306a36Sopenharmony_ci break; 13662306a36Sopenharmony_ci case IRQ_TYPE_EDGE_FALLING: 13762306a36Sopenharmony_ci trigger = 1; polarity = 0; 13862306a36Sopenharmony_ci break; 13962306a36Sopenharmony_ci case IRQ_TYPE_LEVEL_HIGH: 14062306a36Sopenharmony_ci trigger = 0; polarity = 1; 14162306a36Sopenharmony_ci break; 14262306a36Sopenharmony_ci case IRQ_TYPE_LEVEL_LOW: 14362306a36Sopenharmony_ci trigger = 0; polarity = 0; 14462306a36Sopenharmony_ci break; 14562306a36Sopenharmony_ci default: 14662306a36Sopenharmony_ci return -EINVAL; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci mask = ~(1 << (31 - src)); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci raw_spin_lock_irqsave(&uic->lock, flags); 15262306a36Sopenharmony_ci tr = mfdcr(uic->dcrbase + UIC_TR); 15362306a36Sopenharmony_ci pr = mfdcr(uic->dcrbase + UIC_PR); 15462306a36Sopenharmony_ci tr = (tr & mask) | (trigger << (31-src)); 15562306a36Sopenharmony_ci pr = (pr & mask) | (polarity << (31-src)); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_PR, pr); 15862306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_TR, tr); 15962306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_SR, ~mask); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&uic->lock, flags); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return 0; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic struct irq_chip uic_irq_chip = { 16762306a36Sopenharmony_ci .name = "UIC", 16862306a36Sopenharmony_ci .irq_unmask = uic_unmask_irq, 16962306a36Sopenharmony_ci .irq_mask = uic_mask_irq, 17062306a36Sopenharmony_ci .irq_mask_ack = uic_mask_ack_irq, 17162306a36Sopenharmony_ci .irq_ack = uic_ack_irq, 17262306a36Sopenharmony_ci .irq_set_type = uic_set_irq_type, 17362306a36Sopenharmony_ci}; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic int uic_host_map(struct irq_domain *h, unsigned int virq, 17662306a36Sopenharmony_ci irq_hw_number_t hw) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct uic *uic = h->host_data; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci irq_set_chip_data(virq, uic); 18162306a36Sopenharmony_ci /* Despite the name, handle_level_irq() works for both level 18262306a36Sopenharmony_ci * and edge irqs on UIC. FIXME: check this is correct */ 18362306a36Sopenharmony_ci irq_set_chip_and_handler(virq, &uic_irq_chip, handle_level_irq); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* Set default irq type */ 18662306a36Sopenharmony_ci irq_set_irq_type(virq, IRQ_TYPE_NONE); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci return 0; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic const struct irq_domain_ops uic_host_ops = { 19262306a36Sopenharmony_ci .map = uic_host_map, 19362306a36Sopenharmony_ci .xlate = irq_domain_xlate_twocell, 19462306a36Sopenharmony_ci}; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic void uic_irq_cascade(struct irq_desc *desc) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 19962306a36Sopenharmony_ci struct irq_data *idata = irq_desc_get_irq_data(desc); 20062306a36Sopenharmony_ci struct uic *uic = irq_desc_get_handler_data(desc); 20162306a36Sopenharmony_ci u32 msr; 20262306a36Sopenharmony_ci int src; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci raw_spin_lock(&desc->lock); 20562306a36Sopenharmony_ci if (irqd_is_level_type(idata)) 20662306a36Sopenharmony_ci chip->irq_mask(idata); 20762306a36Sopenharmony_ci else 20862306a36Sopenharmony_ci chip->irq_mask_ack(idata); 20962306a36Sopenharmony_ci raw_spin_unlock(&desc->lock); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci msr = mfdcr(uic->dcrbase + UIC_MSR); 21262306a36Sopenharmony_ci if (!msr) /* spurious interrupt */ 21362306a36Sopenharmony_ci goto uic_irq_ret; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci src = 32 - ffs(msr); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci generic_handle_domain_irq(uic->irqhost, src); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ciuic_irq_ret: 22062306a36Sopenharmony_ci raw_spin_lock(&desc->lock); 22162306a36Sopenharmony_ci if (irqd_is_level_type(idata)) 22262306a36Sopenharmony_ci chip->irq_ack(idata); 22362306a36Sopenharmony_ci if (!irqd_irq_disabled(idata) && chip->irq_unmask) 22462306a36Sopenharmony_ci chip->irq_unmask(idata); 22562306a36Sopenharmony_ci raw_spin_unlock(&desc->lock); 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic struct uic * __init uic_init_one(struct device_node *node) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct uic *uic; 23162306a36Sopenharmony_ci const u32 *indexp, *dcrreg; 23262306a36Sopenharmony_ci int len; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci BUG_ON(! of_device_is_compatible(node, "ibm,uic")); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci uic = kzalloc(sizeof(*uic), GFP_KERNEL); 23762306a36Sopenharmony_ci if (! uic) 23862306a36Sopenharmony_ci return NULL; /* FIXME: panic? */ 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci raw_spin_lock_init(&uic->lock); 24162306a36Sopenharmony_ci indexp = of_get_property(node, "cell-index", &len); 24262306a36Sopenharmony_ci if (!indexp || (len != sizeof(u32))) { 24362306a36Sopenharmony_ci printk(KERN_ERR "uic: Device node %pOF has missing or invalid " 24462306a36Sopenharmony_ci "cell-index property\n", node); 24562306a36Sopenharmony_ci return NULL; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci uic->index = *indexp; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci dcrreg = of_get_property(node, "dcr-reg", &len); 25062306a36Sopenharmony_ci if (!dcrreg || (len != 2*sizeof(u32))) { 25162306a36Sopenharmony_ci printk(KERN_ERR "uic: Device node %pOF has missing or invalid " 25262306a36Sopenharmony_ci "dcr-reg property\n", node); 25362306a36Sopenharmony_ci return NULL; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci uic->dcrbase = *dcrreg; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci uic->irqhost = irq_domain_add_linear(node, NR_UIC_INTS, &uic_host_ops, 25862306a36Sopenharmony_ci uic); 25962306a36Sopenharmony_ci if (! uic->irqhost) 26062306a36Sopenharmony_ci return NULL; /* FIXME: panic? */ 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci /* Start with all interrupts disabled, level and non-critical */ 26362306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_ER, 0); 26462306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_CR, 0); 26562306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_TR, 0); 26662306a36Sopenharmony_ci /* Clear any pending interrupts, in case the firmware left some */ 26762306a36Sopenharmony_ci mtdcr(uic->dcrbase + UIC_SR, 0xffffffff); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci printk ("UIC%d (%d IRQ sources) at DCR 0x%x\n", uic->index, 27062306a36Sopenharmony_ci NR_UIC_INTS, uic->dcrbase); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci return uic; 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_civoid __init uic_init_tree(void) 27662306a36Sopenharmony_ci{ 27762306a36Sopenharmony_ci struct device_node *np; 27862306a36Sopenharmony_ci struct uic *uic; 27962306a36Sopenharmony_ci const u32 *interrupts; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci /* First locate and initialize the top-level UIC */ 28262306a36Sopenharmony_ci for_each_compatible_node(np, NULL, "ibm,uic") { 28362306a36Sopenharmony_ci interrupts = of_get_property(np, "interrupts", NULL); 28462306a36Sopenharmony_ci if (!interrupts) 28562306a36Sopenharmony_ci break; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci BUG_ON(!np); /* uic_init_tree() assumes there's a UIC as the 28962306a36Sopenharmony_ci * top-level interrupt controller */ 29062306a36Sopenharmony_ci primary_uic = uic_init_one(np); 29162306a36Sopenharmony_ci if (!primary_uic) 29262306a36Sopenharmony_ci panic("Unable to initialize primary UIC %pOF\n", np); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci irq_set_default_host(primary_uic->irqhost); 29562306a36Sopenharmony_ci of_node_put(np); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci /* The scan again for cascaded UICs */ 29862306a36Sopenharmony_ci for_each_compatible_node(np, NULL, "ibm,uic") { 29962306a36Sopenharmony_ci interrupts = of_get_property(np, "interrupts", NULL); 30062306a36Sopenharmony_ci if (interrupts) { 30162306a36Sopenharmony_ci /* Secondary UIC */ 30262306a36Sopenharmony_ci int cascade_virq; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci uic = uic_init_one(np); 30562306a36Sopenharmony_ci if (! uic) 30662306a36Sopenharmony_ci panic("Unable to initialize a secondary UIC %pOF\n", 30762306a36Sopenharmony_ci np); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci cascade_virq = irq_of_parse_and_map(np, 0); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci irq_set_handler_data(cascade_virq, uic); 31262306a36Sopenharmony_ci irq_set_chained_handler(cascade_virq, uic_irq_cascade); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci /* FIXME: setup critical cascade?? */ 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci/* Return an interrupt vector or 0 if no interrupt is pending. */ 32062306a36Sopenharmony_ciunsigned int uic_get_irq(void) 32162306a36Sopenharmony_ci{ 32262306a36Sopenharmony_ci u32 msr; 32362306a36Sopenharmony_ci int src; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci BUG_ON(! primary_uic); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci msr = mfdcr(primary_uic->dcrbase + UIC_MSR); 32862306a36Sopenharmony_ci src = 32 - ffs(msr); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci return irq_linear_revmap(primary_uic->irqhost, src); 33162306a36Sopenharmony_ci} 332