aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristoph Hellwig <hch@lst.de>2018-07-26 10:27:00 -0400
committerPalmer Dabbelt <palmer@sifive.com>2018-08-13 11:31:32 -0400
commit8237f8bc4f6eb7e5ce2a19276079cfd3a7c6314a (patch)
tree68c054430cfba76983a2d0e3db12a7d563d72230
parent94f592f0e5b9c17a7505119a2d6c0f1f529ae93d (diff)
irqchip: add a SiFive PLIC driver
Add a driver for the SiFive implementation of the RISC-V Platform Level Interrupt Controller (PLIC). The PLIC connects global interrupt sources to the local interrupt controller on each hart. This driver is based on the driver in the RISC-V tree from Palmer Dabbelt, but has been almost entirely rewritten since, and includes many fixes from Atish Patra. Signed-off-by: Christoph Hellwig <hch@lst.de> Acked-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Atish Patra <atish.patra@wdc.com> [Binding update by Palmer] Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
-rw-r--r--arch/riscv/configs/defconfig1
-rw-r--r--drivers/irqchip/Kconfig12
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-sifive-plic.c260
4 files changed, 274 insertions, 0 deletions
diff --git a/arch/riscv/configs/defconfig b/arch/riscv/configs/defconfig
index 07326466871b..36473d7dbaac 100644
--- a/arch/riscv/configs/defconfig
+++ b/arch/riscv/configs/defconfig
@@ -76,3 +76,4 @@ CONFIG_ROOT_NFS=y
76CONFIG_CRYPTO_USER_API_HASH=y 76CONFIG_CRYPTO_USER_API_HASH=y
77CONFIG_MODULES=y 77CONFIG_MODULES=y
78CONFIG_MODULE_UNLOAD=y 78CONFIG_MODULE_UNLOAD=y
79CONFIG_SIFIVE_PLIC=y
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e9233db16e03..df345b878ac2 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -372,3 +372,15 @@ config QCOM_PDC
372 IRQs for Qualcomm Technologies Inc (QTI) mobile chips. 372 IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
373 373
374endmenu 374endmenu
375
376config SIFIVE_PLIC
377 bool "SiFive Platform-Level Interrupt Controller"
378 depends on RISCV
379 help
380 This enables support for the PLIC chip found in SiFive (and
381 potentially other) RISC-V systems. The PLIC controls devices
382 interrupts and connects them to each core's local interrupt
383 controller. Aside from timer and software interrupts, all other
384 interrupt sources are subordinate to the PLIC.
385
386 If you don't know what to do here, say Y.
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 15f268f646bf..fbd1ec8070ef 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -87,3 +87,4 @@ obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o
87obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o 87obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o
88obj-$(CONFIG_NDS32) += irq-ativic32.o 88obj-$(CONFIG_NDS32) += irq-ativic32.o
89obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o 89obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
90obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o
diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c
new file mode 100644
index 000000000000..532e9d68c704
--- /dev/null
+++ b/drivers/irqchip/irq-sifive-plic.c
@@ -0,0 +1,260 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2017 SiFive
4 * Copyright (C) 2018 Christoph Hellwig
5 */
6#define pr_fmt(fmt) "plic: " fmt
7#include <linux/interrupt.h>
8#include <linux/io.h>
9#include <linux/irq.h>
10#include <linux/irqchip.h>
11#include <linux/irqdomain.h>
12#include <linux/module.h>
13#include <linux/of.h>
14#include <linux/of_address.h>
15#include <linux/of_irq.h>
16#include <linux/platform_device.h>
17#include <linux/spinlock.h>
18
19/*
20 * This driver implements a version of the RISC-V PLIC with the actual layout
21 * specified in chapter 8 of the SiFive U5 Coreplex Series Manual:
22 *
23 * https://static.dev.sifive.com/U54-MC-RVCoreIP.pdf
24 *
25 * The largest number supported by devices marked as 'sifive,plic-1.0.0', is
26 * 1024, of which device 0 is defined as non-existent by the RISC-V Privileged
27 * Spec.
28 */
29
30#define MAX_DEVICES 1024
31#define MAX_CONTEXTS 15872
32
33/*
34 * Each interrupt source has a priority register associated with it.
35 * We always hardwire it to one in Linux.
36 */
37#define PRIORITY_BASE 0
38#define PRIORITY_PER_ID 4
39
40/*
41 * Each hart context has a vector of interrupt enable bits associated with it.
42 * There's one bit for each interrupt source.
43 */
44#define ENABLE_BASE 0x2000
45#define ENABLE_PER_HART 0x80
46
47/*
48 * Each hart context has a set of control registers associated with it. Right
49 * now there's only two: a source priority threshold over which the hart will
50 * take an interrupt, and a register to claim interrupts.
51 */
52#define CONTEXT_BASE 0x200000
53#define CONTEXT_PER_HART 0x1000
54#define CONTEXT_THRESHOLD 0x00
55#define CONTEXT_CLAIM 0x04
56
57static void __iomem *plic_regs;
58
59struct plic_handler {
60 bool present;
61 int ctxid;
62};
63static DEFINE_PER_CPU(struct plic_handler, plic_handlers);
64
65static inline void __iomem *plic_hart_offset(int ctxid)
66{
67 return plic_regs + CONTEXT_BASE + ctxid * CONTEXT_PER_HART;
68}
69
70static inline u32 __iomem *plic_enable_base(int ctxid)
71{
72 return plic_regs + ENABLE_BASE + ctxid * ENABLE_PER_HART;
73}
74
75/*
76 * Protect mask operations on the registers given that we can't assume that
77 * atomic memory operations work on them.
78 */
79static DEFINE_RAW_SPINLOCK(plic_toggle_lock);
80
81static inline void plic_toggle(int ctxid, int hwirq, int enable)
82{
83 u32 __iomem *reg = plic_enable_base(ctxid) + (hwirq / 32);
84 u32 hwirq_mask = 1 << (hwirq % 32);
85
86 raw_spin_lock(&plic_toggle_lock);
87 if (enable)
88 writel(readl(reg) | hwirq_mask, reg);
89 else
90 writel(readl(reg) & ~hwirq_mask, reg);
91 raw_spin_unlock(&plic_toggle_lock);
92}
93
94static inline void plic_irq_toggle(struct irq_data *d, int enable)
95{
96 int cpu;
97
98 writel(enable, plic_regs + PRIORITY_BASE + d->hwirq * PRIORITY_PER_ID);
99 for_each_cpu(cpu, irq_data_get_affinity_mask(d)) {
100 struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);
101
102 if (handler->present)
103 plic_toggle(handler->ctxid, d->hwirq, enable);
104 }
105}
106
107static void plic_irq_enable(struct irq_data *d)
108{
109 plic_irq_toggle(d, 1);
110}
111
112static void plic_irq_disable(struct irq_data *d)
113{
114 plic_irq_toggle(d, 0);
115}
116
117static struct irq_chip plic_chip = {
118 .name = "SiFive PLIC",
119 /*
120 * There is no need to mask/unmask PLIC interrupts. They are "masked"
121 * by reading claim and "unmasked" when writing it back.
122 */
123 .irq_enable = plic_irq_enable,
124 .irq_disable = plic_irq_disable,
125};
126
127static int plic_irqdomain_map(struct irq_domain *d, unsigned int irq,
128 irq_hw_number_t hwirq)
129{
130 irq_set_chip_and_handler(irq, &plic_chip, handle_simple_irq);
131 irq_set_chip_data(irq, NULL);
132 irq_set_noprobe(irq);
133 return 0;
134}
135
136static const struct irq_domain_ops plic_irqdomain_ops = {
137 .map = plic_irqdomain_map,
138 .xlate = irq_domain_xlate_onecell,
139};
140
141static struct irq_domain *plic_irqdomain;
142
143/*
144 * Handling an interrupt is a two-step process: first you claim the interrupt
145 * by reading the claim register, then you complete the interrupt by writing
146 * that source ID back to the same claim register. This automatically enables
147 * and disables the interrupt, so there's nothing else to do.
148 */
149static void plic_handle_irq(struct pt_regs *regs)
150{
151 struct plic_handler *handler = this_cpu_ptr(&plic_handlers);
152 void __iomem *claim = plic_hart_offset(handler->ctxid) + CONTEXT_CLAIM;
153 irq_hw_number_t hwirq;
154
155 WARN_ON_ONCE(!handler->present);
156
157 csr_clear(sie, SIE_SEIE);
158 while ((hwirq = readl(claim))) {
159 int irq = irq_find_mapping(plic_irqdomain, hwirq);
160
161 if (unlikely(irq <= 0))
162 pr_warn_ratelimited("can't find mapping for hwirq %lu\n",
163 hwirq);
164 else
165 generic_handle_irq(irq);
166 writel(hwirq, claim);
167 }
168 csr_set(sie, SIE_SEIE);
169}
170
171/*
172 * Walk up the DT tree until we find an active RISC-V core (HART) node and
173 * extract the cpuid from it.
174 */
175static int plic_find_hart_id(struct device_node *node)
176{
177 for (; node; node = node->parent) {
178 if (of_device_is_compatible(node, "riscv"))
179 return riscv_of_processor_hart(node);
180 }
181
182 return -1;
183}
184
185static int __init plic_init(struct device_node *node,
186 struct device_node *parent)
187{
188 int error = 0, nr_handlers, nr_mapped = 0, i;
189 u32 nr_irqs;
190
191 if (plic_regs) {
192 pr_warn("PLIC already present.\n");
193 return -ENXIO;
194 }
195
196 plic_regs = of_iomap(node, 0);
197 if (WARN_ON(!plic_regs))
198 return -EIO;
199
200 error = -EINVAL;
201 of_property_read_u32(node, "riscv,ndev", &nr_irqs);
202 if (WARN_ON(!nr_irqs))
203 goto out_iounmap;
204
205 nr_handlers = of_irq_count(node);
206 if (WARN_ON(!nr_handlers))
207 goto out_iounmap;
208 if (WARN_ON(nr_handlers < num_possible_cpus()))
209 goto out_iounmap;
210
211 error = -ENOMEM;
212 plic_irqdomain = irq_domain_add_linear(node, nr_irqs + 1,
213 &plic_irqdomain_ops, NULL);
214 if (WARN_ON(!plic_irqdomain))
215 goto out_iounmap;
216
217 for (i = 0; i < nr_handlers; i++) {
218 struct of_phandle_args parent;
219 struct plic_handler *handler;
220 irq_hw_number_t hwirq;
221 int cpu;
222
223 if (of_irq_parse_one(node, i, &parent)) {
224 pr_err("failed to parse parent for context %d.\n", i);
225 continue;
226 }
227
228 /* skip context holes */
229 if (parent.args[0] == -1)
230 continue;
231
232 cpu = plic_find_hart_id(parent.np);
233 if (cpu < 0) {
234 pr_warn("failed to parse hart ID for context %d.\n", i);
235 continue;
236 }
237
238 handler = per_cpu_ptr(&plic_handlers, cpu);
239 handler->present = true;
240 handler->ctxid = i;
241
242 /* priority must be > threshold to trigger an interrupt */
243 writel(0, plic_hart_offset(i) + CONTEXT_THRESHOLD);
244 for (hwirq = 1; hwirq <= nr_irqs; hwirq++)
245 plic_toggle(i, hwirq, 0);
246 nr_mapped++;
247 }
248
249 pr_info("mapped %d interrupts to %d (out of %d) handlers.\n",
250 nr_irqs, nr_mapped, nr_handlers);
251 set_handle_irq(plic_handle_irq);
252 return 0;
253
254out_iounmap:
255 iounmap(plic_regs);
256 return error;
257}
258
259IRQCHIP_DECLARE(sifive_plic, "sifive,plic-1.0.0", plic_init);
260IRQCHIP_DECLARE(riscv_plic0, "riscv,plic0", plic_init); /* for legacy systems */