18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2001 MontaVista Software Inc. 48c2ecf20Sopenharmony_ci * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright (C) 2001 Ralf Baechle 78c2ecf20Sopenharmony_ci * Copyright (C) 2005 MIPS Technologies, Inc. All rights reserved. 88c2ecf20Sopenharmony_ci * Author: Maciej W. Rozycki <macro@mips.com> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * This file define the irq handler for MIPS CPU interrupts. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci/* 148c2ecf20Sopenharmony_ci * Almost all MIPS CPUs define 8 interrupt sources. They are typically 158c2ecf20Sopenharmony_ci * level triggered (i.e., cannot be cleared from CPU; must be cleared from 168c2ecf20Sopenharmony_ci * device). 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * The first two are software interrupts (i.e. not exposed as pins) which 198c2ecf20Sopenharmony_ci * may be used for IPIs in multi-threaded single-core systems. 208c2ecf20Sopenharmony_ci * 218c2ecf20Sopenharmony_ci * The last one is usually the CPU timer interrupt if the counter register 228c2ecf20Sopenharmony_ci * is present, or for old CPUs with an external FPU by convention it's the 238c2ecf20Sopenharmony_ci * FPU exception interrupt. 248c2ecf20Sopenharmony_ci */ 258c2ecf20Sopenharmony_ci#include <linux/init.h> 268c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 278c2ecf20Sopenharmony_ci#include <linux/kernel.h> 288c2ecf20Sopenharmony_ci#include <linux/irq.h> 298c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 308c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#include <asm/irq_cpu.h> 338c2ecf20Sopenharmony_ci#include <asm/mipsregs.h> 348c2ecf20Sopenharmony_ci#include <asm/mipsmtregs.h> 358c2ecf20Sopenharmony_ci#include <asm/setup.h> 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic struct irq_domain *irq_domain; 388c2ecf20Sopenharmony_cistatic struct irq_domain *ipi_domain; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic inline void unmask_mips_irq(struct irq_data *d) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci set_c0_status(IE_SW0 << d->hwirq); 438c2ecf20Sopenharmony_ci irq_enable_hazard(); 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic inline void mask_mips_irq(struct irq_data *d) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci clear_c0_status(IE_SW0 << d->hwirq); 498c2ecf20Sopenharmony_ci irq_disable_hazard(); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic struct irq_chip mips_cpu_irq_controller = { 538c2ecf20Sopenharmony_ci .name = "MIPS", 548c2ecf20Sopenharmony_ci .irq_ack = mask_mips_irq, 558c2ecf20Sopenharmony_ci .irq_mask = mask_mips_irq, 568c2ecf20Sopenharmony_ci .irq_mask_ack = mask_mips_irq, 578c2ecf20Sopenharmony_ci .irq_unmask = unmask_mips_irq, 588c2ecf20Sopenharmony_ci .irq_eoi = unmask_mips_irq, 598c2ecf20Sopenharmony_ci .irq_disable = mask_mips_irq, 608c2ecf20Sopenharmony_ci .irq_enable = unmask_mips_irq, 618c2ecf20Sopenharmony_ci}; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci/* 648c2ecf20Sopenharmony_ci * Basically the same as above but taking care of all the MT stuff 658c2ecf20Sopenharmony_ci */ 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic unsigned int mips_mt_cpu_irq_startup(struct irq_data *d) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci unsigned int vpflags = dvpe(); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci clear_c0_cause(C_SW0 << d->hwirq); 728c2ecf20Sopenharmony_ci evpe(vpflags); 738c2ecf20Sopenharmony_ci unmask_mips_irq(d); 748c2ecf20Sopenharmony_ci return 0; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci/* 788c2ecf20Sopenharmony_ci * While we ack the interrupt interrupts are disabled and thus we don't need 798c2ecf20Sopenharmony_ci * to deal with concurrency issues. Same for mips_cpu_irq_end. 808c2ecf20Sopenharmony_ci */ 818c2ecf20Sopenharmony_cistatic void mips_mt_cpu_irq_ack(struct irq_data *d) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci unsigned int vpflags = dvpe(); 848c2ecf20Sopenharmony_ci clear_c0_cause(C_SW0 << d->hwirq); 858c2ecf20Sopenharmony_ci evpe(vpflags); 868c2ecf20Sopenharmony_ci mask_mips_irq(d); 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci#ifdef CONFIG_GENERIC_IRQ_IPI 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic void mips_mt_send_ipi(struct irq_data *d, unsigned int cpu) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci irq_hw_number_t hwirq = irqd_to_hwirq(d); 948c2ecf20Sopenharmony_ci unsigned long flags; 958c2ecf20Sopenharmony_ci int vpflags; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci local_irq_save(flags); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci /* We can only send IPIs to VPEs within the local core */ 1008c2ecf20Sopenharmony_ci WARN_ON(!cpus_are_siblings(smp_processor_id(), cpu)); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci vpflags = dvpe(); 1038c2ecf20Sopenharmony_ci settc(cpu_vpe_id(&cpu_data[cpu])); 1048c2ecf20Sopenharmony_ci write_vpe_c0_cause(read_vpe_c0_cause() | (C_SW0 << hwirq)); 1058c2ecf20Sopenharmony_ci evpe(vpflags); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci local_irq_restore(flags); 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci#endif /* CONFIG_GENERIC_IRQ_IPI */ 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic struct irq_chip mips_mt_cpu_irq_controller = { 1138c2ecf20Sopenharmony_ci .name = "MIPS", 1148c2ecf20Sopenharmony_ci .irq_startup = mips_mt_cpu_irq_startup, 1158c2ecf20Sopenharmony_ci .irq_ack = mips_mt_cpu_irq_ack, 1168c2ecf20Sopenharmony_ci .irq_mask = mask_mips_irq, 1178c2ecf20Sopenharmony_ci .irq_mask_ack = mips_mt_cpu_irq_ack, 1188c2ecf20Sopenharmony_ci .irq_unmask = unmask_mips_irq, 1198c2ecf20Sopenharmony_ci .irq_eoi = unmask_mips_irq, 1208c2ecf20Sopenharmony_ci .irq_disable = mask_mips_irq, 1218c2ecf20Sopenharmony_ci .irq_enable = unmask_mips_irq, 1228c2ecf20Sopenharmony_ci#ifdef CONFIG_GENERIC_IRQ_IPI 1238c2ecf20Sopenharmony_ci .ipi_send_single = mips_mt_send_ipi, 1248c2ecf20Sopenharmony_ci#endif 1258c2ecf20Sopenharmony_ci}; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ciasmlinkage void __weak plat_irq_dispatch(void) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci unsigned long pending = read_c0_cause() & read_c0_status() & ST0_IM; 1308c2ecf20Sopenharmony_ci unsigned int virq; 1318c2ecf20Sopenharmony_ci int irq; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (!pending) { 1348c2ecf20Sopenharmony_ci spurious_interrupt(); 1358c2ecf20Sopenharmony_ci return; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci pending >>= CAUSEB_IP; 1398c2ecf20Sopenharmony_ci while (pending) { 1408c2ecf20Sopenharmony_ci irq = fls(pending) - 1; 1418c2ecf20Sopenharmony_ci if (IS_ENABLED(CONFIG_GENERIC_IRQ_IPI) && irq < 2) 1428c2ecf20Sopenharmony_ci virq = irq_linear_revmap(ipi_domain, irq); 1438c2ecf20Sopenharmony_ci else 1448c2ecf20Sopenharmony_ci virq = irq_linear_revmap(irq_domain, irq); 1458c2ecf20Sopenharmony_ci do_IRQ(virq); 1468c2ecf20Sopenharmony_ci pending &= ~BIT(irq); 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic int mips_cpu_intc_map(struct irq_domain *d, unsigned int irq, 1518c2ecf20Sopenharmony_ci irq_hw_number_t hw) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci struct irq_chip *chip; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (hw < 2 && cpu_has_mipsmt) { 1568c2ecf20Sopenharmony_ci /* Software interrupts are used for MT/CMT IPI */ 1578c2ecf20Sopenharmony_ci chip = &mips_mt_cpu_irq_controller; 1588c2ecf20Sopenharmony_ci } else { 1598c2ecf20Sopenharmony_ci chip = &mips_cpu_irq_controller; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (cpu_has_vint) 1638c2ecf20Sopenharmony_ci set_vi_handler(hw, plat_irq_dispatch); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci irq_set_chip_and_handler(irq, chip, handle_percpu_irq); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci return 0; 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mips_cpu_intc_irq_domain_ops = { 1718c2ecf20Sopenharmony_ci .map = mips_cpu_intc_map, 1728c2ecf20Sopenharmony_ci .xlate = irq_domain_xlate_onecell, 1738c2ecf20Sopenharmony_ci}; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci#ifdef CONFIG_GENERIC_IRQ_IPI 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistruct cpu_ipi_domain_state { 1788c2ecf20Sopenharmony_ci DECLARE_BITMAP(allocated, 2); 1798c2ecf20Sopenharmony_ci}; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic int mips_cpu_ipi_alloc(struct irq_domain *domain, unsigned int virq, 1828c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *arg) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct cpu_ipi_domain_state *state = domain->host_data; 1858c2ecf20Sopenharmony_ci unsigned int i, hwirq; 1868c2ecf20Sopenharmony_ci int ret; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 1898c2ecf20Sopenharmony_ci hwirq = find_first_zero_bit(state->allocated, 2); 1908c2ecf20Sopenharmony_ci if (hwirq == 2) 1918c2ecf20Sopenharmony_ci return -EBUSY; 1928c2ecf20Sopenharmony_ci bitmap_set(state->allocated, hwirq, 1); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci ret = irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq, 1958c2ecf20Sopenharmony_ci &mips_mt_cpu_irq_controller, 1968c2ecf20Sopenharmony_ci NULL); 1978c2ecf20Sopenharmony_ci if (ret) 1988c2ecf20Sopenharmony_ci return ret; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci ret = irq_domain_set_hwirq_and_chip(domain->parent, virq + i, hwirq, 2018c2ecf20Sopenharmony_ci &mips_mt_cpu_irq_controller, 2028c2ecf20Sopenharmony_ci NULL); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (ret) 2058c2ecf20Sopenharmony_ci return ret; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci ret = irq_set_irq_type(virq + i, IRQ_TYPE_LEVEL_HIGH); 2088c2ecf20Sopenharmony_ci if (ret) 2098c2ecf20Sopenharmony_ci return ret; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci return 0; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic int mips_cpu_ipi_match(struct irq_domain *d, struct device_node *node, 2168c2ecf20Sopenharmony_ci enum irq_domain_bus_token bus_token) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci bool is_ipi; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci switch (bus_token) { 2218c2ecf20Sopenharmony_ci case DOMAIN_BUS_IPI: 2228c2ecf20Sopenharmony_ci is_ipi = d->bus_token == bus_token; 2238c2ecf20Sopenharmony_ci return (!node || (to_of_node(d->fwnode) == node)) && is_ipi; 2248c2ecf20Sopenharmony_ci default: 2258c2ecf20Sopenharmony_ci return 0; 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mips_cpu_ipi_chip_ops = { 2308c2ecf20Sopenharmony_ci .alloc = mips_cpu_ipi_alloc, 2318c2ecf20Sopenharmony_ci .match = mips_cpu_ipi_match, 2328c2ecf20Sopenharmony_ci}; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cistatic void mips_cpu_register_ipi_domain(struct device_node *of_node) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci struct cpu_ipi_domain_state *ipi_domain_state; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci ipi_domain_state = kzalloc(sizeof(*ipi_domain_state), GFP_KERNEL); 2398c2ecf20Sopenharmony_ci ipi_domain = irq_domain_add_hierarchy(irq_domain, 2408c2ecf20Sopenharmony_ci IRQ_DOMAIN_FLAG_IPI_SINGLE, 2418c2ecf20Sopenharmony_ci 2, of_node, 2428c2ecf20Sopenharmony_ci &mips_cpu_ipi_chip_ops, 2438c2ecf20Sopenharmony_ci ipi_domain_state); 2448c2ecf20Sopenharmony_ci if (!ipi_domain) 2458c2ecf20Sopenharmony_ci panic("Failed to add MIPS CPU IPI domain"); 2468c2ecf20Sopenharmony_ci irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI); 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci#else /* !CONFIG_GENERIC_IRQ_IPI */ 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistatic inline void mips_cpu_register_ipi_domain(struct device_node *of_node) {} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci#endif /* !CONFIG_GENERIC_IRQ_IPI */ 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic void __init __mips_cpu_irq_init(struct device_node *of_node) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci /* Mask interrupts. */ 2588c2ecf20Sopenharmony_ci clear_c0_status(ST0_IM); 2598c2ecf20Sopenharmony_ci clear_c0_cause(CAUSEF_IP); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci irq_domain = irq_domain_add_legacy(of_node, 8, MIPS_CPU_IRQ_BASE, 0, 2628c2ecf20Sopenharmony_ci &mips_cpu_intc_irq_domain_ops, 2638c2ecf20Sopenharmony_ci NULL); 2648c2ecf20Sopenharmony_ci if (!irq_domain) 2658c2ecf20Sopenharmony_ci panic("Failed to add irqdomain for MIPS CPU"); 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci /* 2688c2ecf20Sopenharmony_ci * Only proceed to register the software interrupt IPI implementation 2698c2ecf20Sopenharmony_ci * for CPUs which implement the MIPS MT (multi-threading) ASE. 2708c2ecf20Sopenharmony_ci */ 2718c2ecf20Sopenharmony_ci if (cpu_has_mipsmt) 2728c2ecf20Sopenharmony_ci mips_cpu_register_ipi_domain(of_node); 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_civoid __init mips_cpu_irq_init(void) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci __mips_cpu_irq_init(NULL); 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ciint __init mips_cpu_irq_of_init(struct device_node *of_node, 2818c2ecf20Sopenharmony_ci struct device_node *parent) 2828c2ecf20Sopenharmony_ci{ 2838c2ecf20Sopenharmony_ci __mips_cpu_irq_init(of_node); 2848c2ecf20Sopenharmony_ci return 0; 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(cpu_intc, "mti,cpu-interrupt-controller", mips_cpu_irq_of_init); 287