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