18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2012 Regents of the University of California 48c2ecf20Sopenharmony_ci * Copyright (C) 2017-2018 SiFive 58c2ecf20Sopenharmony_ci * Copyright (C) 2020 Western Digital Corporation or its affiliates. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "riscv-intc: " fmt 98c2ecf20Sopenharmony_ci#include <linux/atomic.h> 108c2ecf20Sopenharmony_ci#include <linux/bits.h> 118c2ecf20Sopenharmony_ci#include <linux/cpu.h> 128c2ecf20Sopenharmony_ci#include <linux/irq.h> 138c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 148c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/of.h> 188c2ecf20Sopenharmony_ci#include <linux/smp.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistatic struct irq_domain *intc_domain; 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic asmlinkage void riscv_intc_irq(struct pt_regs *regs) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci if (unlikely(cause >= BITS_PER_LONG)) 278c2ecf20Sopenharmony_ci panic("unexpected interrupt cause"); 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci switch (cause) { 308c2ecf20Sopenharmony_ci#ifdef CONFIG_SMP 318c2ecf20Sopenharmony_ci case RV_IRQ_SOFT: 328c2ecf20Sopenharmony_ci /* 338c2ecf20Sopenharmony_ci * We only use software interrupts to pass IPIs, so if a 348c2ecf20Sopenharmony_ci * non-SMP system gets one, then we don't know what to do. 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ci handle_IPI(regs); 378c2ecf20Sopenharmony_ci break; 388c2ecf20Sopenharmony_ci#endif 398c2ecf20Sopenharmony_ci default: 408c2ecf20Sopenharmony_ci handle_domain_irq(intc_domain, cause, regs); 418c2ecf20Sopenharmony_ci break; 428c2ecf20Sopenharmony_ci } 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* 468c2ecf20Sopenharmony_ci * On RISC-V systems local interrupts are masked or unmasked by writing 478c2ecf20Sopenharmony_ci * the SIE (Supervisor Interrupt Enable) CSR. As CSRs can only be written 488c2ecf20Sopenharmony_ci * on the local hart, these functions can only be called on the hart that 498c2ecf20Sopenharmony_ci * corresponds to the IRQ chip. 508c2ecf20Sopenharmony_ci */ 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic void riscv_intc_irq_mask(struct irq_data *d) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci csr_clear(CSR_IE, BIT(d->hwirq)); 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic void riscv_intc_irq_unmask(struct irq_data *d) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci csr_set(CSR_IE, BIT(d->hwirq)); 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic int riscv_intc_cpu_starting(unsigned int cpu) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci csr_set(CSR_IE, BIT(RV_IRQ_SOFT)); 658c2ecf20Sopenharmony_ci return 0; 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int riscv_intc_cpu_dying(unsigned int cpu) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci csr_clear(CSR_IE, BIT(RV_IRQ_SOFT)); 718c2ecf20Sopenharmony_ci return 0; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic struct irq_chip riscv_intc_chip = { 758c2ecf20Sopenharmony_ci .name = "RISC-V INTC", 768c2ecf20Sopenharmony_ci .irq_mask = riscv_intc_irq_mask, 778c2ecf20Sopenharmony_ci .irq_unmask = riscv_intc_irq_unmask, 788c2ecf20Sopenharmony_ci}; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic int riscv_intc_domain_map(struct irq_domain *d, unsigned int irq, 818c2ecf20Sopenharmony_ci irq_hw_number_t hwirq) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci irq_set_percpu_devid(irq); 848c2ecf20Sopenharmony_ci irq_domain_set_info(d, irq, hwirq, &riscv_intc_chip, d->host_data, 858c2ecf20Sopenharmony_ci handle_percpu_devid_irq, NULL, NULL); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci return 0; 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic const struct irq_domain_ops riscv_intc_domain_ops = { 918c2ecf20Sopenharmony_ci .map = riscv_intc_domain_map, 928c2ecf20Sopenharmony_ci .xlate = irq_domain_xlate_onecell, 938c2ecf20Sopenharmony_ci}; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic int __init riscv_intc_init(struct device_node *node, 968c2ecf20Sopenharmony_ci struct device_node *parent) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci int rc, hartid; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci hartid = riscv_of_parent_hartid(node); 1018c2ecf20Sopenharmony_ci if (hartid < 0) { 1028c2ecf20Sopenharmony_ci pr_warn("unable to find hart id for %pOF\n", node); 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* 1078c2ecf20Sopenharmony_ci * The DT will have one INTC DT node under each CPU (or HART) 1088c2ecf20Sopenharmony_ci * DT node so riscv_intc_init() function will be called once 1098c2ecf20Sopenharmony_ci * for each INTC DT node. We only need to do INTC initialization 1108c2ecf20Sopenharmony_ci * for the INTC DT node belonging to boot CPU (or boot HART). 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_ci if (riscv_hartid_to_cpuid(hartid) != smp_processor_id()) 1138c2ecf20Sopenharmony_ci return 0; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci intc_domain = irq_domain_add_linear(node, BITS_PER_LONG, 1168c2ecf20Sopenharmony_ci &riscv_intc_domain_ops, NULL); 1178c2ecf20Sopenharmony_ci if (!intc_domain) { 1188c2ecf20Sopenharmony_ci pr_err("unable to add IRQ domain\n"); 1198c2ecf20Sopenharmony_ci return -ENXIO; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci rc = set_handle_irq(&riscv_intc_irq); 1238c2ecf20Sopenharmony_ci if (rc) { 1248c2ecf20Sopenharmony_ci pr_err("failed to set irq handler\n"); 1258c2ecf20Sopenharmony_ci return rc; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci cpuhp_setup_state(CPUHP_AP_IRQ_RISCV_STARTING, 1298c2ecf20Sopenharmony_ci "irqchip/riscv/intc:starting", 1308c2ecf20Sopenharmony_ci riscv_intc_cpu_starting, 1318c2ecf20Sopenharmony_ci riscv_intc_cpu_dying); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci pr_info("%d local interrupts mapped\n", BITS_PER_LONG); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return 0; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(riscv, "riscv,cpu-intc", riscv_intc_init); 139