Commit | Line | Data |
---|---|---|
6b7ce892 AP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2012 Regents of the University of California | |
4 | * Copyright (C) 2017-2018 SiFive | |
5 | * Copyright (C) 2020 Western Digital Corporation or its affiliates. | |
6 | */ | |
7 | ||
8 | #define pr_fmt(fmt) "riscv-intc: " fmt | |
9 | #include <linux/atomic.h> | |
10 | #include <linux/bits.h> | |
11 | #include <linux/cpu.h> | |
12 | #include <linux/irq.h> | |
13 | #include <linux/irqchip.h> | |
14 | #include <linux/irqdomain.h> | |
15 | #include <linux/interrupt.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/of.h> | |
18 | #include <linux/smp.h> | |
19 | ||
20 | static struct irq_domain *intc_domain; | |
21 | ||
22 | static asmlinkage void riscv_intc_irq(struct pt_regs *regs) | |
23 | { | |
6b7ce892 AP |
24 | unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG; |
25 | ||
26 | if (unlikely(cause >= BITS_PER_LONG)) | |
27 | panic("unexpected interrupt cause"); | |
28 | ||
832f15f4 | 29 | generic_handle_domain_irq(intc_domain, cause); |
6b7ce892 AP |
30 | } |
31 | ||
32 | /* | |
33 | * On RISC-V systems local interrupts are masked or unmasked by writing | |
34 | * the SIE (Supervisor Interrupt Enable) CSR. As CSRs can only be written | |
35 | * on the local hart, these functions can only be called on the hart that | |
36 | * corresponds to the IRQ chip. | |
37 | */ | |
38 | ||
39 | static void riscv_intc_irq_mask(struct irq_data *d) | |
40 | { | |
41 | csr_clear(CSR_IE, BIT(d->hwirq)); | |
42 | } | |
43 | ||
44 | static void riscv_intc_irq_unmask(struct irq_data *d) | |
45 | { | |
46 | csr_set(CSR_IE, BIT(d->hwirq)); | |
47 | } | |
48 | ||
f8415f2d AP |
49 | static void riscv_intc_irq_eoi(struct irq_data *d) |
50 | { | |
51 | /* | |
52 | * The RISC-V INTC driver uses handle_percpu_devid_irq() flow | |
53 | * for the per-HART local interrupts and child irqchip drivers | |
54 | * (such as PLIC, SBI IPI, CLINT, APLIC, IMSIC, etc) implement | |
55 | * chained handlers for the per-HART local interrupts. | |
56 | * | |
57 | * In the absence of irq_eoi(), the chained_irq_enter() and | |
58 | * chained_irq_exit() functions (used by child irqchip drivers) | |
59 | * will do unnecessary mask/unmask of per-HART local interrupts | |
60 | * at the time of handling interrupts. To avoid this, we provide | |
61 | * an empty irq_eoi() callback for RISC-V INTC irqchip. | |
62 | */ | |
63 | } | |
64 | ||
6b7ce892 AP |
65 | static struct irq_chip riscv_intc_chip = { |
66 | .name = "RISC-V INTC", | |
67 | .irq_mask = riscv_intc_irq_mask, | |
68 | .irq_unmask = riscv_intc_irq_unmask, | |
f8415f2d | 69 | .irq_eoi = riscv_intc_irq_eoi, |
6b7ce892 AP |
70 | }; |
71 | ||
72 | static int riscv_intc_domain_map(struct irq_domain *d, unsigned int irq, | |
73 | irq_hw_number_t hwirq) | |
74 | { | |
75 | irq_set_percpu_devid(irq); | |
76 | irq_domain_set_info(d, irq, hwirq, &riscv_intc_chip, d->host_data, | |
77 | handle_percpu_devid_irq, NULL, NULL); | |
78 | ||
79 | return 0; | |
80 | } | |
81 | ||
832f15f4 AP |
82 | static int riscv_intc_domain_alloc(struct irq_domain *domain, |
83 | unsigned int virq, unsigned int nr_irqs, | |
84 | void *arg) | |
85 | { | |
86 | int i, ret; | |
87 | irq_hw_number_t hwirq; | |
88 | unsigned int type = IRQ_TYPE_NONE; | |
89 | struct irq_fwspec *fwspec = arg; | |
90 | ||
91 | ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type); | |
92 | if (ret) | |
93 | return ret; | |
94 | ||
95 | for (i = 0; i < nr_irqs; i++) { | |
96 | ret = riscv_intc_domain_map(domain, virq + i, hwirq + i); | |
97 | if (ret) | |
98 | return ret; | |
99 | } | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
6b7ce892 AP |
104 | static const struct irq_domain_ops riscv_intc_domain_ops = { |
105 | .map = riscv_intc_domain_map, | |
106 | .xlate = irq_domain_xlate_onecell, | |
832f15f4 | 107 | .alloc = riscv_intc_domain_alloc |
6b7ce892 AP |
108 | }; |
109 | ||
0c60a31c AP |
110 | static struct fwnode_handle *riscv_intc_hwnode(void) |
111 | { | |
112 | return intc_domain->fwnode; | |
113 | } | |
114 | ||
6b7ce892 AP |
115 | static int __init riscv_intc_init(struct device_node *node, |
116 | struct device_node *parent) | |
117 | { | |
ad635e72 S |
118 | int rc; |
119 | unsigned long hartid; | |
6b7ce892 | 120 | |
ad635e72 S |
121 | rc = riscv_of_parent_hartid(node, &hartid); |
122 | if (rc < 0) { | |
559fe74b | 123 | pr_warn("unable to find hart id for %pOF\n", node); |
6b7ce892 AP |
124 | return 0; |
125 | } | |
126 | ||
127 | /* | |
128 | * The DT will have one INTC DT node under each CPU (or HART) | |
129 | * DT node so riscv_intc_init() function will be called once | |
130 | * for each INTC DT node. We only need to do INTC initialization | |
131 | * for the INTC DT node belonging to boot CPU (or boot HART). | |
132 | */ | |
133 | if (riscv_hartid_to_cpuid(hartid) != smp_processor_id()) | |
134 | return 0; | |
135 | ||
136 | intc_domain = irq_domain_add_linear(node, BITS_PER_LONG, | |
137 | &riscv_intc_domain_ops, NULL); | |
138 | if (!intc_domain) { | |
139 | pr_err("unable to add IRQ domain\n"); | |
140 | return -ENXIO; | |
141 | } | |
142 | ||
143 | rc = set_handle_irq(&riscv_intc_irq); | |
144 | if (rc) { | |
145 | pr_err("failed to set irq handler\n"); | |
146 | return rc; | |
147 | } | |
148 | ||
0c60a31c AP |
149 | riscv_set_intc_hwnode_fn(riscv_intc_hwnode); |
150 | ||
6b7ce892 AP |
151 | pr_info("%d local interrupts mapped\n", BITS_PER_LONG); |
152 | ||
153 | return 0; | |
154 | } | |
155 | ||
156 | IRQCHIP_DECLARE(riscv, "riscv,cpu-intc", riscv_intc_init); |