162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Interrupt management for most GSC and related devices. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (c) Copyright 1999 Alex deVries for The Puffin Group 662306a36Sopenharmony_ci * (c) Copyright 1999 Grant Grundler for Hewlett-Packard 762306a36Sopenharmony_ci * (c) Copyright 1999 Matthew Wilcox 862306a36Sopenharmony_ci * (c) Copyright 2000 Helge Deller 962306a36Sopenharmony_ci * (c) Copyright 2001 Matthew Wilcox for Hewlett-Packard 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/bitops.h> 1362306a36Sopenharmony_ci#include <linux/errno.h> 1462306a36Sopenharmony_ci#include <linux/init.h> 1562306a36Sopenharmony_ci#include <linux/interrupt.h> 1662306a36Sopenharmony_ci#include <linux/ioport.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/types.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <asm/hardware.h> 2162306a36Sopenharmony_ci#include <asm/io.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include "gsc.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#undef DEBUG 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#ifdef DEBUG 2862306a36Sopenharmony_ci#define DEBPRINTK printk 2962306a36Sopenharmony_ci#else 3062306a36Sopenharmony_ci#define DEBPRINTK(x,...) 3162306a36Sopenharmony_ci#endif 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ciint gsc_alloc_irq(struct gsc_irq *i) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci int irq = txn_alloc_irq(GSC_EIM_WIDTH); 3662306a36Sopenharmony_ci if (irq < 0) { 3762306a36Sopenharmony_ci printk("cannot get irq\n"); 3862306a36Sopenharmony_ci return irq; 3962306a36Sopenharmony_ci } 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci i->txn_addr = txn_alloc_addr(irq); 4262306a36Sopenharmony_ci i->txn_data = txn_alloc_data(irq); 4362306a36Sopenharmony_ci i->irq = irq; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci return irq; 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ciint gsc_claim_irq(struct gsc_irq *i, int irq) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci int c = irq; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci irq += CPU_IRQ_BASE; /* virtualize the IRQ first */ 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci irq = txn_claim_irq(irq); 5562306a36Sopenharmony_ci if (irq < 0) { 5662306a36Sopenharmony_ci printk("cannot claim irq %d\n", c); 5762306a36Sopenharmony_ci return irq; 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci i->txn_addr = txn_alloc_addr(irq); 6162306a36Sopenharmony_ci i->txn_data = txn_alloc_data(irq); 6262306a36Sopenharmony_ci i->irq = irq; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return irq; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ciEXPORT_SYMBOL(gsc_alloc_irq); 6862306a36Sopenharmony_ciEXPORT_SYMBOL(gsc_claim_irq); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci/* Common interrupt demultiplexer used by Asp, Lasi & Wax. */ 7162306a36Sopenharmony_ciirqreturn_t gsc_asic_intr(int gsc_asic_irq, void *dev) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci unsigned long irr; 7462306a36Sopenharmony_ci struct gsc_asic *gsc_asic = dev; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci irr = gsc_readl(gsc_asic->hpa + OFFSET_IRR); 7762306a36Sopenharmony_ci if (irr == 0) 7862306a36Sopenharmony_ci return IRQ_NONE; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci DEBPRINTK("%s intr, mask=0x%x\n", gsc_asic->name, irr); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci do { 8362306a36Sopenharmony_ci int local_irq = __ffs(irr); 8462306a36Sopenharmony_ci unsigned int irq = gsc_asic->global_irq[local_irq]; 8562306a36Sopenharmony_ci generic_handle_irq(irq); 8662306a36Sopenharmony_ci irr &= ~(1 << local_irq); 8762306a36Sopenharmony_ci } while (irr); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return IRQ_HANDLED; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ciint gsc_find_local_irq(unsigned int irq, int *global_irqs, int limit) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci int local_irq; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci for (local_irq = 0; local_irq < limit; local_irq++) { 9762306a36Sopenharmony_ci if (global_irqs[local_irq] == irq) 9862306a36Sopenharmony_ci return local_irq; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return NO_IRQ; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic void gsc_asic_mask_irq(struct irq_data *d) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct gsc_asic *irq_dev = irq_data_get_irq_chip_data(d); 10762306a36Sopenharmony_ci int local_irq = gsc_find_local_irq(d->irq, irq_dev->global_irq, 32); 10862306a36Sopenharmony_ci u32 imr; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, d->irq, 11162306a36Sopenharmony_ci irq_dev->name, imr); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* Disable the IRQ line by clearing the bit in the IMR */ 11462306a36Sopenharmony_ci imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); 11562306a36Sopenharmony_ci imr &= ~(1 << local_irq); 11662306a36Sopenharmony_ci gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic void gsc_asic_unmask_irq(struct irq_data *d) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci struct gsc_asic *irq_dev = irq_data_get_irq_chip_data(d); 12262306a36Sopenharmony_ci int local_irq = gsc_find_local_irq(d->irq, irq_dev->global_irq, 32); 12362306a36Sopenharmony_ci u32 imr; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, d->irq, 12662306a36Sopenharmony_ci irq_dev->name, imr); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* Enable the IRQ line by setting the bit in the IMR */ 12962306a36Sopenharmony_ci imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); 13062306a36Sopenharmony_ci imr |= 1 << local_irq; 13162306a36Sopenharmony_ci gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); 13262306a36Sopenharmony_ci /* 13362306a36Sopenharmony_ci * FIXME: read IPR to make sure the IRQ isn't already pending. 13462306a36Sopenharmony_ci * If so, we need to read IRR and manually call do_irq(). 13562306a36Sopenharmony_ci */ 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci#ifdef CONFIG_SMP 13962306a36Sopenharmony_cistatic int gsc_set_affinity_irq(struct irq_data *d, const struct cpumask *dest, 14062306a36Sopenharmony_ci bool force) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct gsc_asic *gsc_dev = irq_data_get_irq_chip_data(d); 14362306a36Sopenharmony_ci struct cpumask tmask; 14462306a36Sopenharmony_ci int cpu_irq; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci if (!cpumask_and(&tmask, dest, cpu_online_mask)) 14762306a36Sopenharmony_ci return -EINVAL; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci cpu_irq = cpu_check_affinity(d, &tmask); 15062306a36Sopenharmony_ci if (cpu_irq < 0) 15162306a36Sopenharmony_ci return cpu_irq; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci gsc_dev->gsc_irq.txn_addr = txn_affinity_addr(d->irq, cpu_irq); 15462306a36Sopenharmony_ci gsc_dev->eim = ((u32) gsc_dev->gsc_irq.txn_addr) | gsc_dev->gsc_irq.txn_data; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* switch IRQ's for devices below LASI/WAX to other CPU */ 15762306a36Sopenharmony_ci gsc_writel(gsc_dev->eim, gsc_dev->hpa + OFFSET_IAR); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci irq_data_update_effective_affinity(d, &tmask); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return IRQ_SET_MASK_OK; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci#endif 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic struct irq_chip gsc_asic_interrupt_type = { 16762306a36Sopenharmony_ci .name = "GSC-ASIC", 16862306a36Sopenharmony_ci .irq_unmask = gsc_asic_unmask_irq, 16962306a36Sopenharmony_ci .irq_mask = gsc_asic_mask_irq, 17062306a36Sopenharmony_ci#ifdef CONFIG_SMP 17162306a36Sopenharmony_ci .irq_set_affinity = gsc_set_affinity_irq, 17262306a36Sopenharmony_ci#endif 17362306a36Sopenharmony_ci}; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ciint gsc_assign_irq(struct irq_chip *type, void *data) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci static int irq = GSC_IRQ_BASE; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (irq > GSC_IRQ_MAX) 18062306a36Sopenharmony_ci return NO_IRQ; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci irq_set_chip_and_handler(irq, type, handle_simple_irq); 18362306a36Sopenharmony_ci irq_set_chip_data(irq, data); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci return irq++; 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_civoid gsc_asic_assign_irq(struct gsc_asic *asic, int local_irq, int *irqp) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci int irq = asic->global_irq[local_irq]; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci if (irq <= 0) { 19362306a36Sopenharmony_ci irq = gsc_assign_irq(&gsc_asic_interrupt_type, asic); 19462306a36Sopenharmony_ci if (irq == NO_IRQ) 19562306a36Sopenharmony_ci return; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci asic->global_irq[local_irq] = irq; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci *irqp = irq; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistruct gsc_fixup_struct { 20362306a36Sopenharmony_ci void (*choose_irq)(struct parisc_device *, void *); 20462306a36Sopenharmony_ci void *ctrl; 20562306a36Sopenharmony_ci}; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int gsc_fixup_irqs_callback(struct device *dev, void *data) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci struct parisc_device *padev = to_parisc_device(dev); 21062306a36Sopenharmony_ci struct gsc_fixup_struct *gf = data; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* work-around for 715/64 and others which have parent 21362306a36Sopenharmony_ci at path [5] and children at path [5/0/x] */ 21462306a36Sopenharmony_ci if (padev->id.hw_type == HPHW_FAULTY) 21562306a36Sopenharmony_ci gsc_fixup_irqs(padev, gf->ctrl, gf->choose_irq); 21662306a36Sopenharmony_ci gf->choose_irq(padev, gf->ctrl); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci return 0; 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_civoid gsc_fixup_irqs(struct parisc_device *parent, void *ctrl, 22262306a36Sopenharmony_ci void (*choose_irq)(struct parisc_device *, void *)) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci struct gsc_fixup_struct data = { 22562306a36Sopenharmony_ci .choose_irq = choose_irq, 22662306a36Sopenharmony_ci .ctrl = ctrl, 22762306a36Sopenharmony_ci }; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci device_for_each_child(&parent->dev, &data, gsc_fixup_irqs_callback); 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ciint gsc_common_setup(struct parisc_device *parent, struct gsc_asic *gsc_asic) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci struct resource *res; 23562306a36Sopenharmony_ci int i; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci gsc_asic->gsc = parent; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* Initialise local irq -> global irq mapping */ 24062306a36Sopenharmony_ci for (i = 0; i < 32; i++) { 24162306a36Sopenharmony_ci gsc_asic->global_irq[i] = NO_IRQ; 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* allocate resource region */ 24562306a36Sopenharmony_ci res = request_mem_region(gsc_asic->hpa, 0x100000, gsc_asic->name); 24662306a36Sopenharmony_ci if (res) { 24762306a36Sopenharmony_ci res->flags = IORESOURCE_MEM; /* do not mark it busy ! */ 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci#if 0 25162306a36Sopenharmony_ci printk(KERN_WARNING "%s IRQ %d EIM 0x%x", gsc_asic->name, 25262306a36Sopenharmony_ci parent->irq, gsc_asic->eim); 25362306a36Sopenharmony_ci if (gsc_readl(gsc_asic->hpa + OFFSET_IMR)) 25462306a36Sopenharmony_ci printk(" IMR is non-zero! (0x%x)", 25562306a36Sopenharmony_ci gsc_readl(gsc_asic->hpa + OFFSET_IMR)); 25662306a36Sopenharmony_ci printk("\n"); 25762306a36Sopenharmony_ci#endif 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci return 0; 26062306a36Sopenharmony_ci} 261