18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Cristian Birsan <cristian.birsan@microchip.com> 48c2ecf20Sopenharmony_ci * Joshua Henderson <joshua.henderson@microchip.com> 58c2ecf20Sopenharmony_ci * Copyright (C) 2016 Microchip Technology Inc. All rights reserved. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include <linux/kernel.h> 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 108c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 118c2ecf20Sopenharmony_ci#include <linux/of_address.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 158c2ecf20Sopenharmony_ci#include <linux/irq.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <asm/irq.h> 188c2ecf20Sopenharmony_ci#include <asm/traps.h> 198c2ecf20Sopenharmony_ci#include <asm/mach-pic32/pic32.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define REG_INTCON 0x0000 228c2ecf20Sopenharmony_ci#define REG_INTSTAT 0x0020 238c2ecf20Sopenharmony_ci#define REG_IFS_OFFSET 0x0040 248c2ecf20Sopenharmony_ci#define REG_IEC_OFFSET 0x00C0 258c2ecf20Sopenharmony_ci#define REG_IPC_OFFSET 0x0140 268c2ecf20Sopenharmony_ci#define REG_OFF_OFFSET 0x0540 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define MAJPRI_MASK 0x07 298c2ecf20Sopenharmony_ci#define SUBPRI_MASK 0x03 308c2ecf20Sopenharmony_ci#define PRIORITY_MASK 0x1F 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define PIC32_INT_PRI(pri, subpri) \ 338c2ecf20Sopenharmony_ci ((((pri) & MAJPRI_MASK) << 2) | ((subpri) & SUBPRI_MASK)) 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistruct evic_chip_data { 368c2ecf20Sopenharmony_ci u32 irq_types[NR_IRQS]; 378c2ecf20Sopenharmony_ci u32 ext_irqs[8]; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic struct irq_domain *evic_irq_domain; 418c2ecf20Sopenharmony_cistatic void __iomem *evic_base; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ciasmlinkage void __weak plat_irq_dispatch(void) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci unsigned int irq, hwirq; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci hwirq = readl(evic_base + REG_INTSTAT) & 0xFF; 488c2ecf20Sopenharmony_ci irq = irq_linear_revmap(evic_irq_domain, hwirq); 498c2ecf20Sopenharmony_ci do_IRQ(irq); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic struct evic_chip_data *irqd_to_priv(struct irq_data *data) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci return (struct evic_chip_data *)data->domain->host_data; 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int pic32_set_ext_polarity(int bit, u32 type) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci /* 608c2ecf20Sopenharmony_ci * External interrupts can be either edge rising or edge falling, 618c2ecf20Sopenharmony_ci * but not both. 628c2ecf20Sopenharmony_ci */ 638c2ecf20Sopenharmony_ci switch (type) { 648c2ecf20Sopenharmony_ci case IRQ_TYPE_EDGE_RISING: 658c2ecf20Sopenharmony_ci writel(BIT(bit), evic_base + PIC32_SET(REG_INTCON)); 668c2ecf20Sopenharmony_ci break; 678c2ecf20Sopenharmony_ci case IRQ_TYPE_EDGE_FALLING: 688c2ecf20Sopenharmony_ci writel(BIT(bit), evic_base + PIC32_CLR(REG_INTCON)); 698c2ecf20Sopenharmony_ci break; 708c2ecf20Sopenharmony_ci default: 718c2ecf20Sopenharmony_ci return -EINVAL; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci return 0; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int pic32_set_type_edge(struct irq_data *data, 788c2ecf20Sopenharmony_ci unsigned int flow_type) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci struct evic_chip_data *priv = irqd_to_priv(data); 818c2ecf20Sopenharmony_ci int ret; 828c2ecf20Sopenharmony_ci int i; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (!(flow_type & IRQ_TYPE_EDGE_BOTH)) 858c2ecf20Sopenharmony_ci return -EBADR; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* set polarity for external interrupts only */ 888c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(priv->ext_irqs); i++) { 898c2ecf20Sopenharmony_ci if (priv->ext_irqs[i] == data->hwirq) { 908c2ecf20Sopenharmony_ci ret = pic32_set_ext_polarity(i, flow_type); 918c2ecf20Sopenharmony_ci if (ret) 928c2ecf20Sopenharmony_ci return ret; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci } 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci irqd_set_trigger_type(data, flow_type); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci return IRQ_SET_MASK_OK; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic void pic32_bind_evic_interrupt(int irq, int set) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci writel(set, evic_base + REG_OFF_OFFSET + irq * 4); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic void pic32_set_irq_priority(int irq, int priority) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci u32 reg, shift; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci reg = irq / 4; 1118c2ecf20Sopenharmony_ci shift = (irq % 4) * 8; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci writel(PRIORITY_MASK << shift, 1148c2ecf20Sopenharmony_ci evic_base + PIC32_CLR(REG_IPC_OFFSET + reg * 0x10)); 1158c2ecf20Sopenharmony_ci writel(priority << shift, 1168c2ecf20Sopenharmony_ci evic_base + PIC32_SET(REG_IPC_OFFSET + reg * 0x10)); 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci#define IRQ_REG_MASK(_hwirq, _reg, _mask) \ 1208c2ecf20Sopenharmony_ci do { \ 1218c2ecf20Sopenharmony_ci _reg = _hwirq / 32; \ 1228c2ecf20Sopenharmony_ci _mask = 1 << (_hwirq % 32); \ 1238c2ecf20Sopenharmony_ci } while (0) 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic int pic32_irq_domain_map(struct irq_domain *d, unsigned int virq, 1268c2ecf20Sopenharmony_ci irq_hw_number_t hw) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci struct evic_chip_data *priv = d->host_data; 1298c2ecf20Sopenharmony_ci struct irq_data *data; 1308c2ecf20Sopenharmony_ci int ret; 1318c2ecf20Sopenharmony_ci u32 iecclr, ifsclr; 1328c2ecf20Sopenharmony_ci u32 reg, mask; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci ret = irq_map_generic_chip(d, virq, hw); 1358c2ecf20Sopenharmony_ci if (ret) 1368c2ecf20Sopenharmony_ci return ret; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* 1398c2ecf20Sopenharmony_ci * Piggyback on xlate function to move to an alternate chip as necessary 1408c2ecf20Sopenharmony_ci * at time of mapping instead of allowing the flow handler/chip to be 1418c2ecf20Sopenharmony_ci * changed later. This requires all interrupts to be configured through 1428c2ecf20Sopenharmony_ci * DT. 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_ci if (priv->irq_types[hw] & IRQ_TYPE_SENSE_MASK) { 1458c2ecf20Sopenharmony_ci data = irq_domain_get_irq_data(d, virq); 1468c2ecf20Sopenharmony_ci irqd_set_trigger_type(data, priv->irq_types[hw]); 1478c2ecf20Sopenharmony_ci irq_setup_alt_chip(data, priv->irq_types[hw]); 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci IRQ_REG_MASK(hw, reg, mask); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci iecclr = PIC32_CLR(REG_IEC_OFFSET + reg * 0x10); 1538c2ecf20Sopenharmony_ci ifsclr = PIC32_CLR(REG_IFS_OFFSET + reg * 0x10); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci /* mask and clear flag */ 1568c2ecf20Sopenharmony_ci writel(mask, evic_base + iecclr); 1578c2ecf20Sopenharmony_ci writel(mask, evic_base + ifsclr); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* default priority is required */ 1608c2ecf20Sopenharmony_ci pic32_set_irq_priority(hw, PIC32_INT_PRI(2, 0)); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci return ret; 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ciint pic32_irq_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, 1668c2ecf20Sopenharmony_ci const u32 *intspec, unsigned int intsize, 1678c2ecf20Sopenharmony_ci irq_hw_number_t *out_hwirq, unsigned int *out_type) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci struct evic_chip_data *priv = d->host_data; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (WARN_ON(intsize < 2)) 1728c2ecf20Sopenharmony_ci return -EINVAL; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (WARN_ON(intspec[0] >= NR_IRQS)) 1758c2ecf20Sopenharmony_ci return -EINVAL; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci *out_hwirq = intspec[0]; 1788c2ecf20Sopenharmony_ci *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci priv->irq_types[intspec[0]] = intspec[1] & IRQ_TYPE_SENSE_MASK; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci return 0; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic const struct irq_domain_ops pic32_irq_domain_ops = { 1868c2ecf20Sopenharmony_ci .map = pic32_irq_domain_map, 1878c2ecf20Sopenharmony_ci .xlate = pic32_irq_domain_xlate, 1888c2ecf20Sopenharmony_ci}; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic void __init pic32_ext_irq_of_init(struct irq_domain *domain) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct device_node *node = irq_domain_get_of_node(domain); 1938c2ecf20Sopenharmony_ci struct evic_chip_data *priv = domain->host_data; 1948c2ecf20Sopenharmony_ci struct property *prop; 1958c2ecf20Sopenharmony_ci const __le32 *p; 1968c2ecf20Sopenharmony_ci u32 hwirq; 1978c2ecf20Sopenharmony_ci int i = 0; 1988c2ecf20Sopenharmony_ci const char *pname = "microchip,external-irqs"; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci of_property_for_each_u32(node, pname, prop, p, hwirq) { 2018c2ecf20Sopenharmony_ci if (i >= ARRAY_SIZE(priv->ext_irqs)) { 2028c2ecf20Sopenharmony_ci pr_warn("More than %d external irq, skip rest\n", 2038c2ecf20Sopenharmony_ci ARRAY_SIZE(priv->ext_irqs)); 2048c2ecf20Sopenharmony_ci break; 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci priv->ext_irqs[i] = hwirq; 2088c2ecf20Sopenharmony_ci i++; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cistatic int __init pic32_of_init(struct device_node *node, 2138c2ecf20Sopenharmony_ci struct device_node *parent) 2148c2ecf20Sopenharmony_ci{ 2158c2ecf20Sopenharmony_ci struct irq_chip_generic *gc; 2168c2ecf20Sopenharmony_ci struct evic_chip_data *priv; 2178c2ecf20Sopenharmony_ci unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; 2188c2ecf20Sopenharmony_ci int nchips, ret; 2198c2ecf20Sopenharmony_ci int i; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci nchips = DIV_ROUND_UP(NR_IRQS, 32); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci evic_base = of_iomap(node, 0); 2248c2ecf20Sopenharmony_ci if (!evic_base) 2258c2ecf20Sopenharmony_ci return -ENOMEM; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci priv = kcalloc(nchips, sizeof(*priv), GFP_KERNEL); 2288c2ecf20Sopenharmony_ci if (!priv) { 2298c2ecf20Sopenharmony_ci ret = -ENOMEM; 2308c2ecf20Sopenharmony_ci goto err_iounmap; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci evic_irq_domain = irq_domain_add_linear(node, nchips * 32, 2348c2ecf20Sopenharmony_ci &pic32_irq_domain_ops, 2358c2ecf20Sopenharmony_ci priv); 2368c2ecf20Sopenharmony_ci if (!evic_irq_domain) { 2378c2ecf20Sopenharmony_ci ret = -ENOMEM; 2388c2ecf20Sopenharmony_ci goto err_free_priv; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci /* 2428c2ecf20Sopenharmony_ci * The PIC32 EVIC has a linear list of irqs and the type of each 2438c2ecf20Sopenharmony_ci * irq is determined by the hardware peripheral the EVIC is arbitrating. 2448c2ecf20Sopenharmony_ci * These irq types are defined in the datasheet as "persistent" and 2458c2ecf20Sopenharmony_ci * "non-persistent" which are mapped here to level and edge 2468c2ecf20Sopenharmony_ci * respectively. To manage the different flow handler requirements of 2478c2ecf20Sopenharmony_ci * each irq type, different chip_types are used. 2488c2ecf20Sopenharmony_ci */ 2498c2ecf20Sopenharmony_ci ret = irq_alloc_domain_generic_chips(evic_irq_domain, 32, 2, 2508c2ecf20Sopenharmony_ci "evic-level", handle_level_irq, 2518c2ecf20Sopenharmony_ci clr, 0, 0); 2528c2ecf20Sopenharmony_ci if (ret) 2538c2ecf20Sopenharmony_ci goto err_domain_remove; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci board_bind_eic_interrupt = &pic32_bind_evic_interrupt; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci for (i = 0; i < nchips; i++) { 2588c2ecf20Sopenharmony_ci u32 ifsclr = PIC32_CLR(REG_IFS_OFFSET + (i * 0x10)); 2598c2ecf20Sopenharmony_ci u32 iec = REG_IEC_OFFSET + (i * 0x10); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci gc = irq_get_domain_generic_chip(evic_irq_domain, i * 32); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci gc->reg_base = evic_base; 2648c2ecf20Sopenharmony_ci gc->unused = 0; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci /* 2678c2ecf20Sopenharmony_ci * Level/persistent interrupts have a special requirement that 2688c2ecf20Sopenharmony_ci * the condition generating the interrupt be cleared before the 2698c2ecf20Sopenharmony_ci * interrupt flag (ifs) can be cleared. chip.irq_eoi is used to 2708c2ecf20Sopenharmony_ci * complete the interrupt with an ack. 2718c2ecf20Sopenharmony_ci */ 2728c2ecf20Sopenharmony_ci gc->chip_types[0].type = IRQ_TYPE_LEVEL_MASK; 2738c2ecf20Sopenharmony_ci gc->chip_types[0].handler = handle_fasteoi_irq; 2748c2ecf20Sopenharmony_ci gc->chip_types[0].regs.ack = ifsclr; 2758c2ecf20Sopenharmony_ci gc->chip_types[0].regs.mask = iec; 2768c2ecf20Sopenharmony_ci gc->chip_types[0].chip.name = "evic-level"; 2778c2ecf20Sopenharmony_ci gc->chip_types[0].chip.irq_eoi = irq_gc_ack_set_bit; 2788c2ecf20Sopenharmony_ci gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; 2798c2ecf20Sopenharmony_ci gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; 2808c2ecf20Sopenharmony_ci gc->chip_types[0].chip.flags = IRQCHIP_SKIP_SET_WAKE; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci /* Edge interrupts */ 2838c2ecf20Sopenharmony_ci gc->chip_types[1].type = IRQ_TYPE_EDGE_BOTH; 2848c2ecf20Sopenharmony_ci gc->chip_types[1].handler = handle_edge_irq; 2858c2ecf20Sopenharmony_ci gc->chip_types[1].regs.ack = ifsclr; 2868c2ecf20Sopenharmony_ci gc->chip_types[1].regs.mask = iec; 2878c2ecf20Sopenharmony_ci gc->chip_types[1].chip.name = "evic-edge"; 2888c2ecf20Sopenharmony_ci gc->chip_types[1].chip.irq_ack = irq_gc_ack_set_bit; 2898c2ecf20Sopenharmony_ci gc->chip_types[1].chip.irq_mask = irq_gc_mask_clr_bit; 2908c2ecf20Sopenharmony_ci gc->chip_types[1].chip.irq_unmask = irq_gc_mask_set_bit; 2918c2ecf20Sopenharmony_ci gc->chip_types[1].chip.irq_set_type = pic32_set_type_edge; 2928c2ecf20Sopenharmony_ci gc->chip_types[1].chip.flags = IRQCHIP_SKIP_SET_WAKE; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci gc->private = &priv[i]; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci irq_set_default_host(evic_irq_domain); 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci /* 3008c2ecf20Sopenharmony_ci * External interrupts have software configurable edge polarity. These 3018c2ecf20Sopenharmony_ci * interrupts are defined in DT allowing polarity to be configured only 3028c2ecf20Sopenharmony_ci * for these interrupts when requested. 3038c2ecf20Sopenharmony_ci */ 3048c2ecf20Sopenharmony_ci pic32_ext_irq_of_init(evic_irq_domain); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci return 0; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_cierr_domain_remove: 3098c2ecf20Sopenharmony_ci irq_domain_remove(evic_irq_domain); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_cierr_free_priv: 3128c2ecf20Sopenharmony_ci kfree(priv); 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_cierr_iounmap: 3158c2ecf20Sopenharmony_ci iounmap(evic_base); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci return ret; 3188c2ecf20Sopenharmony_ci} 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(pic32_evic, "microchip,pic32mzda-evic", pic32_of_init); 321