1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2024 Inochi Amaoto <inochiama@gmail.com> |
4 | */ |
5 | |
6 | #define pr_fmt(fmt) "thead-c900-aclint-sswi: " fmt |
7 | #include <linux/cpu.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/io.h> |
10 | #include <linux/irq.h> |
11 | #include <linux/irqchip.h> |
12 | #include <linux/irqchip/chained_irq.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/of_irq.h> |
17 | #include <linux/pci.h> |
18 | #include <linux/spinlock.h> |
19 | #include <linux/smp.h> |
20 | #include <linux/string_choices.h> |
21 | #include <asm/sbi.h> |
22 | #include <asm/vendorid_list.h> |
23 | |
24 | #define THEAD_ACLINT_xSWI_REGISTER_SIZE 4 |
25 | |
26 | #define THEAD_C9XX_CSR_SXSTATUS 0x5c0 |
27 | #define THEAD_C9XX_SXSTATUS_CLINTEE BIT(17) |
28 | |
29 | static int sswi_ipi_virq __ro_after_init; |
30 | static DEFINE_PER_CPU(void __iomem *, sswi_cpu_regs); |
31 | |
32 | static void thead_aclint_sswi_ipi_send(unsigned int cpu) |
33 | { |
34 | writel(val: 0x1, per_cpu(sswi_cpu_regs, cpu)); |
35 | } |
36 | |
37 | static void thead_aclint_sswi_ipi_clear(void) |
38 | { |
39 | writel_relaxed(0x0, this_cpu_read(sswi_cpu_regs)); |
40 | } |
41 | |
42 | static void thead_aclint_sswi_ipi_handle(struct irq_desc *desc) |
43 | { |
44 | struct irq_chip *chip = irq_desc_get_chip(desc); |
45 | |
46 | chained_irq_enter(chip, desc); |
47 | |
48 | csr_clear(CSR_IP, IE_SIE); |
49 | thead_aclint_sswi_ipi_clear(); |
50 | |
51 | ipi_mux_process(); |
52 | |
53 | chained_irq_exit(chip, desc); |
54 | } |
55 | |
56 | static int thead_aclint_sswi_starting_cpu(unsigned int cpu) |
57 | { |
58 | enable_percpu_irq(irq: sswi_ipi_virq, type: irq_get_trigger_type(irq: sswi_ipi_virq)); |
59 | |
60 | return 0; |
61 | } |
62 | |
63 | static int thead_aclint_sswi_dying_cpu(unsigned int cpu) |
64 | { |
65 | thead_aclint_sswi_ipi_clear(); |
66 | |
67 | disable_percpu_irq(irq: sswi_ipi_virq); |
68 | |
69 | return 0; |
70 | } |
71 | |
72 | static int __init thead_aclint_sswi_parse_irq(struct fwnode_handle *fwnode, |
73 | void __iomem *reg) |
74 | { |
75 | struct of_phandle_args parent; |
76 | unsigned long hartid; |
77 | u32 contexts, i; |
78 | int rc, cpu; |
79 | |
80 | contexts = of_irq_count(to_of_node(fwnode)); |
81 | if (!(contexts)) { |
82 | pr_err("%pfwP: no ACLINT SSWI context available\n" , fwnode); |
83 | return -EINVAL; |
84 | } |
85 | |
86 | for (i = 0; i < contexts; i++) { |
87 | rc = of_irq_parse_one(to_of_node(fwnode), index: i, out_irq: &parent); |
88 | if (rc) |
89 | return rc; |
90 | |
91 | rc = riscv_of_parent_hartid(parent.np, &hartid); |
92 | if (rc) |
93 | return rc; |
94 | |
95 | if (parent.args[0] != RV_IRQ_SOFT) |
96 | return -ENOTSUPP; |
97 | |
98 | cpu = riscv_hartid_to_cpuid(hartid); |
99 | |
100 | per_cpu(sswi_cpu_regs, cpu) = reg + i * THEAD_ACLINT_xSWI_REGISTER_SIZE; |
101 | } |
102 | |
103 | pr_info("%pfwP: register %u CPU%s\n" , fwnode, contexts, str_plural(contexts)); |
104 | |
105 | return 0; |
106 | } |
107 | |
108 | static int __init thead_aclint_sswi_probe(struct fwnode_handle *fwnode) |
109 | { |
110 | struct irq_domain *domain; |
111 | void __iomem *reg; |
112 | int virq, rc; |
113 | |
114 | /* If it is T-HEAD CPU, check whether SSWI is enabled */ |
115 | if (riscv_cached_mvendorid(0) == THEAD_VENDOR_ID && |
116 | !(csr_read(THEAD_C9XX_CSR_SXSTATUS) & THEAD_C9XX_SXSTATUS_CLINTEE)) |
117 | return -ENOTSUPP; |
118 | |
119 | if (!is_of_node(fwnode)) |
120 | return -EINVAL; |
121 | |
122 | reg = of_iomap(to_of_node(fwnode), index: 0); |
123 | if (!reg) |
124 | return -ENOMEM; |
125 | |
126 | /* Parse SSWI setting */ |
127 | rc = thead_aclint_sswi_parse_irq(fwnode, reg); |
128 | if (rc < 0) |
129 | return rc; |
130 | |
131 | /* If mulitple SSWI devices are present, do not register irq again */ |
132 | if (sswi_ipi_virq) |
133 | return 0; |
134 | |
135 | /* Find riscv intc domain and create IPI irq mapping */ |
136 | domain = irq_find_matching_fwnode(fwnode: riscv_get_intc_hwnode(), bus_token: DOMAIN_BUS_ANY); |
137 | if (!domain) { |
138 | pr_err("%pfwP: Failed to find INTC domain\n" , fwnode); |
139 | return -ENOENT; |
140 | } |
141 | |
142 | sswi_ipi_virq = irq_create_mapping(domain, hwirq: RV_IRQ_SOFT); |
143 | if (!sswi_ipi_virq) { |
144 | pr_err("unable to create ACLINT SSWI IRQ mapping\n" ); |
145 | return -ENOMEM; |
146 | } |
147 | |
148 | /* Register SSWI irq and handler */ |
149 | virq = ipi_mux_create(BITS_PER_BYTE, mux_send: thead_aclint_sswi_ipi_send); |
150 | if (virq <= 0) { |
151 | pr_err("unable to create muxed IPIs\n" ); |
152 | irq_dispose_mapping(virq: sswi_ipi_virq); |
153 | return virq < 0 ? virq : -ENOMEM; |
154 | } |
155 | |
156 | irq_set_chained_handler(irq: sswi_ipi_virq, handle: thead_aclint_sswi_ipi_handle); |
157 | |
158 | cpuhp_setup_state(state: CPUHP_AP_IRQ_THEAD_ACLINT_SSWI_STARTING, |
159 | name: "irqchip/thead-aclint-sswi:starting" , |
160 | startup: thead_aclint_sswi_starting_cpu, |
161 | teardown: thead_aclint_sswi_dying_cpu); |
162 | |
163 | riscv_ipi_set_virq_range(virq, BITS_PER_BYTE); |
164 | |
165 | /* Announce that SSWI is providing IPIs */ |
166 | pr_info("providing IPIs using THEAD ACLINT SSWI\n" ); |
167 | |
168 | return 0; |
169 | } |
170 | |
171 | static int __init thead_aclint_sswi_early_probe(struct device_node *node, |
172 | struct device_node *parent) |
173 | { |
174 | return thead_aclint_sswi_probe(fwnode: &node->fwnode); |
175 | } |
176 | IRQCHIP_DECLARE(thead_aclint_sswi, "thead,c900-aclint-sswi" , thead_aclint_sswi_early_probe); |
177 | |