18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Interrupt management for most GSC and related devices. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (c) Copyright 1999 Alex deVries for The Puffin Group 68c2ecf20Sopenharmony_ci * (c) Copyright 1999 Grant Grundler for Hewlett-Packard 78c2ecf20Sopenharmony_ci * (c) Copyright 1999 Matthew Wilcox 88c2ecf20Sopenharmony_ci * (c) Copyright 2000 Helge Deller 98c2ecf20Sopenharmony_ci * (c) Copyright 2001 Matthew Wilcox for Hewlett-Packard 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/bitops.h> 138c2ecf20Sopenharmony_ci#include <linux/errno.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci#include <linux/ioport.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/types.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <asm/hardware.h> 218c2ecf20Sopenharmony_ci#include <asm/io.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include "gsc.h" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#undef DEBUG 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#ifdef DEBUG 288c2ecf20Sopenharmony_ci#define DEBPRINTK printk 298c2ecf20Sopenharmony_ci#else 308c2ecf20Sopenharmony_ci#define DEBPRINTK(x,...) 318c2ecf20Sopenharmony_ci#endif 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ciint gsc_alloc_irq(struct gsc_irq *i) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci int irq = txn_alloc_irq(GSC_EIM_WIDTH); 368c2ecf20Sopenharmony_ci if (irq < 0) { 378c2ecf20Sopenharmony_ci printk("cannot get irq\n"); 388c2ecf20Sopenharmony_ci return irq; 398c2ecf20Sopenharmony_ci } 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci i->txn_addr = txn_alloc_addr(irq); 428c2ecf20Sopenharmony_ci i->txn_data = txn_alloc_data(irq); 438c2ecf20Sopenharmony_ci i->irq = irq; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci return irq; 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ciint gsc_claim_irq(struct gsc_irq *i, int irq) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci int c = irq; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci irq += CPU_IRQ_BASE; /* virtualize the IRQ first */ 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci irq = txn_claim_irq(irq); 558c2ecf20Sopenharmony_ci if (irq < 0) { 568c2ecf20Sopenharmony_ci printk("cannot claim irq %d\n", c); 578c2ecf20Sopenharmony_ci return irq; 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci i->txn_addr = txn_alloc_addr(irq); 618c2ecf20Sopenharmony_ci i->txn_data = txn_alloc_data(irq); 628c2ecf20Sopenharmony_ci i->irq = irq; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci return irq; 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ciEXPORT_SYMBOL(gsc_alloc_irq); 688c2ecf20Sopenharmony_ciEXPORT_SYMBOL(gsc_claim_irq); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci/* Common interrupt demultiplexer used by Asp, Lasi & Wax. */ 718c2ecf20Sopenharmony_ciirqreturn_t gsc_asic_intr(int gsc_asic_irq, void *dev) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci unsigned long irr; 748c2ecf20Sopenharmony_ci struct gsc_asic *gsc_asic = dev; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci irr = gsc_readl(gsc_asic->hpa + OFFSET_IRR); 778c2ecf20Sopenharmony_ci if (irr == 0) 788c2ecf20Sopenharmony_ci return IRQ_NONE; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci DEBPRINTK("%s intr, mask=0x%x\n", gsc_asic->name, irr); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci do { 838c2ecf20Sopenharmony_ci int local_irq = __ffs(irr); 848c2ecf20Sopenharmony_ci unsigned int irq = gsc_asic->global_irq[local_irq]; 858c2ecf20Sopenharmony_ci generic_handle_irq(irq); 868c2ecf20Sopenharmony_ci irr &= ~(1 << local_irq); 878c2ecf20Sopenharmony_ci } while (irr); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return IRQ_HANDLED; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ciint gsc_find_local_irq(unsigned int irq, int *global_irqs, int limit) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci int local_irq; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci for (local_irq = 0; local_irq < limit; local_irq++) { 978c2ecf20Sopenharmony_ci if (global_irqs[local_irq] == irq) 988c2ecf20Sopenharmony_ci return local_irq; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return NO_IRQ; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic void gsc_asic_mask_irq(struct irq_data *d) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci struct gsc_asic *irq_dev = irq_data_get_irq_chip_data(d); 1078c2ecf20Sopenharmony_ci int local_irq = gsc_find_local_irq(d->irq, irq_dev->global_irq, 32); 1088c2ecf20Sopenharmony_ci u32 imr; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, d->irq, 1118c2ecf20Sopenharmony_ci irq_dev->name, imr); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci /* Disable the IRQ line by clearing the bit in the IMR */ 1148c2ecf20Sopenharmony_ci imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); 1158c2ecf20Sopenharmony_ci imr &= ~(1 << local_irq); 1168c2ecf20Sopenharmony_ci gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic void gsc_asic_unmask_irq(struct irq_data *d) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct gsc_asic *irq_dev = irq_data_get_irq_chip_data(d); 1228c2ecf20Sopenharmony_ci int local_irq = gsc_find_local_irq(d->irq, irq_dev->global_irq, 32); 1238c2ecf20Sopenharmony_ci u32 imr; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, d->irq, 1268c2ecf20Sopenharmony_ci irq_dev->name, imr); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci /* Enable the IRQ line by setting the bit in the IMR */ 1298c2ecf20Sopenharmony_ci imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); 1308c2ecf20Sopenharmony_ci imr |= 1 << local_irq; 1318c2ecf20Sopenharmony_ci gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); 1328c2ecf20Sopenharmony_ci /* 1338c2ecf20Sopenharmony_ci * FIXME: read IPR to make sure the IRQ isn't already pending. 1348c2ecf20Sopenharmony_ci * If so, we need to read IRR and manually call do_irq(). 1358c2ecf20Sopenharmony_ci */ 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci#ifdef CONFIG_SMP 1398c2ecf20Sopenharmony_cistatic int gsc_set_affinity_irq(struct irq_data *d, const struct cpumask *dest, 1408c2ecf20Sopenharmony_ci bool force) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci struct gsc_asic *gsc_dev = irq_data_get_irq_chip_data(d); 1438c2ecf20Sopenharmony_ci struct cpumask tmask; 1448c2ecf20Sopenharmony_ci int cpu_irq; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (!cpumask_and(&tmask, dest, cpu_online_mask)) 1478c2ecf20Sopenharmony_ci return -EINVAL; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci cpu_irq = cpu_check_affinity(d, &tmask); 1508c2ecf20Sopenharmony_ci if (cpu_irq < 0) 1518c2ecf20Sopenharmony_ci return cpu_irq; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci gsc_dev->gsc_irq.txn_addr = txn_affinity_addr(d->irq, cpu_irq); 1548c2ecf20Sopenharmony_ci gsc_dev->eim = ((u32) gsc_dev->gsc_irq.txn_addr) | gsc_dev->gsc_irq.txn_data; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* switch IRQ's for devices below LASI/WAX to other CPU */ 1578c2ecf20Sopenharmony_ci gsc_writel(gsc_dev->eim, gsc_dev->hpa + OFFSET_IAR); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci irq_data_update_effective_affinity(d, &tmask); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return IRQ_SET_MASK_OK; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci#endif 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic struct irq_chip gsc_asic_interrupt_type = { 1678c2ecf20Sopenharmony_ci .name = "GSC-ASIC", 1688c2ecf20Sopenharmony_ci .irq_unmask = gsc_asic_unmask_irq, 1698c2ecf20Sopenharmony_ci .irq_mask = gsc_asic_mask_irq, 1708c2ecf20Sopenharmony_ci#ifdef CONFIG_SMP 1718c2ecf20Sopenharmony_ci .irq_set_affinity = gsc_set_affinity_irq, 1728c2ecf20Sopenharmony_ci#endif 1738c2ecf20Sopenharmony_ci}; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ciint gsc_assign_irq(struct irq_chip *type, void *data) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci static int irq = GSC_IRQ_BASE; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci if (irq > GSC_IRQ_MAX) 1808c2ecf20Sopenharmony_ci return NO_IRQ; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci irq_set_chip_and_handler(irq, type, handle_simple_irq); 1838c2ecf20Sopenharmony_ci irq_set_chip_data(irq, data); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci return irq++; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_civoid gsc_asic_assign_irq(struct gsc_asic *asic, int local_irq, int *irqp) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci int irq = asic->global_irq[local_irq]; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci if (irq <= 0) { 1938c2ecf20Sopenharmony_ci irq = gsc_assign_irq(&gsc_asic_interrupt_type, asic); 1948c2ecf20Sopenharmony_ci if (irq == NO_IRQ) 1958c2ecf20Sopenharmony_ci return; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci asic->global_irq[local_irq] = irq; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci *irqp = irq; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistruct gsc_fixup_struct { 2038c2ecf20Sopenharmony_ci void (*choose_irq)(struct parisc_device *, void *); 2048c2ecf20Sopenharmony_ci void *ctrl; 2058c2ecf20Sopenharmony_ci}; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic int gsc_fixup_irqs_callback(struct device *dev, void *data) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci struct parisc_device *padev = to_parisc_device(dev); 2108c2ecf20Sopenharmony_ci struct gsc_fixup_struct *gf = data; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci /* work-around for 715/64 and others which have parent 2138c2ecf20Sopenharmony_ci at path [5] and children at path [5/0/x] */ 2148c2ecf20Sopenharmony_ci if (padev->id.hw_type == HPHW_FAULTY) 2158c2ecf20Sopenharmony_ci gsc_fixup_irqs(padev, gf->ctrl, gf->choose_irq); 2168c2ecf20Sopenharmony_ci gf->choose_irq(padev, gf->ctrl); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci return 0; 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_civoid gsc_fixup_irqs(struct parisc_device *parent, void *ctrl, 2228c2ecf20Sopenharmony_ci void (*choose_irq)(struct parisc_device *, void *)) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci struct gsc_fixup_struct data = { 2258c2ecf20Sopenharmony_ci .choose_irq = choose_irq, 2268c2ecf20Sopenharmony_ci .ctrl = ctrl, 2278c2ecf20Sopenharmony_ci }; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci device_for_each_child(&parent->dev, &data, gsc_fixup_irqs_callback); 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ciint gsc_common_setup(struct parisc_device *parent, struct gsc_asic *gsc_asic) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci struct resource *res; 2358c2ecf20Sopenharmony_ci int i; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci gsc_asic->gsc = parent; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci /* Initialise local irq -> global irq mapping */ 2408c2ecf20Sopenharmony_ci for (i = 0; i < 32; i++) { 2418c2ecf20Sopenharmony_ci gsc_asic->global_irq[i] = NO_IRQ; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci /* allocate resource region */ 2458c2ecf20Sopenharmony_ci res = request_mem_region(gsc_asic->hpa, 0x100000, gsc_asic->name); 2468c2ecf20Sopenharmony_ci if (res) { 2478c2ecf20Sopenharmony_ci res->flags = IORESOURCE_MEM; /* do not mark it busy ! */ 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci#if 0 2518c2ecf20Sopenharmony_ci printk(KERN_WARNING "%s IRQ %d EIM 0x%x", gsc_asic->name, 2528c2ecf20Sopenharmony_ci parent->irq, gsc_asic->eim); 2538c2ecf20Sopenharmony_ci if (gsc_readl(gsc_asic->hpa + OFFSET_IMR)) 2548c2ecf20Sopenharmony_ci printk(" IMR is non-zero! (0x%x)", 2558c2ecf20Sopenharmony_ci gsc_readl(gsc_asic->hpa + OFFSET_IMR)); 2568c2ecf20Sopenharmony_ci printk("\n"); 2578c2ecf20Sopenharmony_ci#endif 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci return 0; 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ciextern struct parisc_driver lasi_driver; 2638c2ecf20Sopenharmony_ciextern struct parisc_driver asp_driver; 2648c2ecf20Sopenharmony_ciextern struct parisc_driver wax_driver; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_civoid __init gsc_init(void) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci#ifdef CONFIG_GSC_LASI 2698c2ecf20Sopenharmony_ci register_parisc_driver(&lasi_driver); 2708c2ecf20Sopenharmony_ci register_parisc_driver(&asp_driver); 2718c2ecf20Sopenharmony_ci#endif 2728c2ecf20Sopenharmony_ci#ifdef CONFIG_GSC_WAX 2738c2ecf20Sopenharmony_ci register_parisc_driver(&wax_driver); 2748c2ecf20Sopenharmony_ci#endif 2758c2ecf20Sopenharmony_ci} 276