projects/linux·blob master · refs

281 lines · 7.6 KiB

1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Aspeed AST2700 Interrupt Controller.
4 *
5 * Copyright (C) 2026 ASPEED Technology Inc.
6 */
7
8#include <linux/bitops.h>
9#include <linux/device.h>
10#include <linux/io.h>
11#include <linux/irq.h>
12#include <linux/irqchip.h>
13#include <linux/irqchip/chained_irq.h>
14#include <linux/irqdomain.h>
15#include <linux/of.h>
16#include <linux/of_irq.h>
17#include <linux/spinlock.h>
18
19#include "irq-ast2700.h"
20
21#define INTC1_IER 0x100
22#define INTC1_ISR 0x104
23#define INTC1_BANK_SIZE 0x10
24#define INTC1_SEL_BASE 0x80
25#define INTC1_SEL_BANK_SIZE 0x4
26#define INTC1_SEL_ROUTE_SIZE 0x20
27#define INTC1_IRQS_PER_BANK 32
28#define INTC1_BANK_NUM 6
29#define INTC1_ROUTE_NUM 7
30#define INTC1_IN_NUM 192
31#define INTC1_BOOTMCU_ROUTE 6
32#define INTC1_ROUTE_SELECTOR_BITS 3
33#define INTC1_ROUTE_IRQS_PER_GROUP 32
34#define INTC1_ROUTE_SHIFT 5
35
36struct aspeed_intc1 {
37 struct device *dev;
38 void __iomem *base;
39 raw_spinlock_t intc_lock;
40 struct irq_domain *local;
41 struct irq_domain *upstream;
42 struct aspeed_intc_interrupt_ranges ranges;
43};
44
45static void aspeed_intc1_disable_int(struct aspeed_intc1 *intc1)
46{
47 for (int i = 0; i < INTC1_BANK_NUM; i++)
48 writel(0, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * i));
49}
50
51static void aspeed_intc1_irq_handler(struct irq_desc *desc)
52{
53 struct aspeed_intc1 *intc1 = irq_desc_get_handler_data(desc);
54 struct irq_chip *chip = irq_desc_get_chip(desc);
55 unsigned long bit, status;
56
57 chained_irq_enter(chip, desc);
58
59 for (int bank = 0; bank < INTC1_BANK_NUM; bank++) {
60 status = readl(intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
61 if (!status)
62 continue;
63
64 for_each_set_bit(bit, &status, INTC1_IRQS_PER_BANK) {
65 generic_handle_domain_irq(intc1->local, (bank * INTC1_IRQS_PER_BANK) + bit);
66 writel(BIT(bit), intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
67 }
68 }
69
70 chained_irq_exit(chip, desc);
71}
72
73static void aspeed_intc1_irq_mask(struct irq_data *data)
74{
75 struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
76 int bank = data->hwirq / INTC1_IRQS_PER_BANK;
77 int bit = data->hwirq % INTC1_IRQS_PER_BANK;
78 u32 ier;
79
80 guard(raw_spinlock)(&intc1->intc_lock);
81 ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) & ~BIT(bit);
82 writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
83}
84
85static void aspeed_intc1_irq_unmask(struct irq_data *data)
86{
87 struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
88 int bank = data->hwirq / INTC1_IRQS_PER_BANK;
89 int bit = data->hwirq % INTC1_IRQS_PER_BANK;
90 u32 ier;
91
92 guard(raw_spinlock)(&intc1->intc_lock);
93 ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) | BIT(bit);
94 writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
95}
96
97static struct irq_chip aspeed_intc_chip = {
98 .name = "ASPEED INTC1",
99 .irq_mask = aspeed_intc1_irq_mask,
100 .irq_unmask = aspeed_intc1_irq_unmask,
101};
102
103static int aspeed_intc1_irq_domain_translate(struct irq_domain *domain,
104 struct irq_fwspec *fwspec,
105 unsigned long *hwirq,
106 unsigned int *type)
107{
108 if (fwspec->param_count != 1)
109 return -EINVAL;
110
111 *hwirq = fwspec->param[0];
112 *type = IRQ_TYPE_LEVEL_HIGH;
113 return 0;
114}
115
116static int aspeed_intc1_map_irq_domain(struct irq_domain *domain,
117 unsigned int irq,
118 irq_hw_number_t hwirq)
119{
120 irq_domain_set_info(domain, irq, hwirq, &aspeed_intc_chip,
121 domain->host_data, handle_level_irq, NULL, NULL);
122 return 0;
123}
124
125/*
126 * In-bound interrupts are progressively merged into one out-bound interrupt in
127 * groups of 32. Apply this fact to compress the route table in corresponding
128 * groups of 32.
129 */
130static const u32
131aspeed_intc1_routes[INTC1_IN_NUM / INTC1_ROUTE_IRQS_PER_GROUP][INTC1_ROUTE_NUM] = {
132 { 0, AST2700_INTC_INVALID_ROUTE, 10, 20, 30, 40, 50 },
133 { 1, AST2700_INTC_INVALID_ROUTE, 11, 21, 31, 41, 50 },
134 { 2, AST2700_INTC_INVALID_ROUTE, 12, 22, 32, 42, 50 },
135 { 3, AST2700_INTC_INVALID_ROUTE, 13, 23, 33, 43, 50 },
136 { 4, AST2700_INTC_INVALID_ROUTE, 14, 24, 34, 44, 50 },
137 { 5, AST2700_INTC_INVALID_ROUTE, 15, 25, 35, 45, 50 },
138};
139
140static int aspeed_intc1_irq_domain_activate(struct irq_domain *domain,
141 struct irq_data *data, bool reserve)
142{
143 struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
144 struct aspeed_intc_interrupt_range resolved;
145 int rc, bank, bit;
146 u32 mask;
147
148 if (WARN_ON_ONCE((data->hwirq >> INTC1_ROUTE_SHIFT) >= ARRAY_SIZE(aspeed_intc1_routes)))
149 return -EINVAL;
150
151 /*
152 * outpin may be an error if the upstream is the BootMCU APLIC node, or
153 * anything except a valid intc0 driver instance
154 */
155 rc = aspeed_intc0_resolve_route(intc1->upstream, INTC1_ROUTE_NUM,
156 aspeed_intc1_routes[data->hwirq >> INTC1_ROUTE_SHIFT],
157 intc1->ranges.nranges,
158 intc1->ranges.ranges, &resolved);
159 if (rc < 0) {
160 if (!of_device_is_compatible(to_of_node(intc1->upstream->fwnode),
161 "riscv,aplic")) {
162 dev_warn(intc1->dev,
163 "Failed to resolve interrupt route for hwirq %lu in domain %s\n",
164 data->hwirq, domain->name);
165 return rc;
166 }
167 rc = INTC1_BOOTMCU_ROUTE;
168 }
169
170 bank = data->hwirq / INTC1_IRQS_PER_BANK;
171 bit = data->hwirq % INTC1_IRQS_PER_BANK;
172 mask = BIT(bit);
173
174 guard(raw_spinlock)(&intc1->intc_lock);
175 for (int i = 0; i < INTC1_ROUTE_SELECTOR_BITS; i++) {
176 void __iomem *sel = intc1->base + INTC1_SEL_BASE +
177 (bank * INTC1_SEL_BANK_SIZE) +
178 (INTC1_SEL_ROUTE_SIZE * i);
179 u32 reg = readl(sel);
180
181 if (rc & BIT(i))
182 reg |= mask;
183 else
184 reg &= ~mask;
185
186 writel(reg, sel);
187 if (readl(sel) != reg)
188 return -EACCES;
189 }
190
191 return 0;
192}
193
194static const struct irq_domain_ops aspeed_intc1_irq_domain_ops = {
195 .map = aspeed_intc1_map_irq_domain,
196 .translate = aspeed_intc1_irq_domain_translate,
197 .activate = aspeed_intc1_irq_domain_activate,
198};
199
200static void aspeed_intc1_request_interrupts(struct aspeed_intc1 *intc1)
201{
202 for (unsigned int i = 0; i < intc1->ranges.nranges; i++) {
203 struct aspeed_intc_interrupt_range *r =
204 &intc1->ranges.ranges[i];
205
206 if (intc1->upstream != r->domain)
207 continue;
208
209 for (u32 k = 0; k < r->count; k++) {
210 struct of_phandle_args parent_irq;
211 int irq;
212
213 parent_irq.np = to_of_node(r->upstream.fwnode);
214 parent_irq.args_count = 1;
215 parent_irq.args[0] =
216 intc1->ranges.ranges[i].upstream.param[ASPEED_INTC_RANGES_BASE] + k;
217
218 irq = irq_create_of_mapping(&parent_irq);
219 if (!irq)
220 continue;
221
222 irq_set_chained_handler_and_data(irq,
223 aspeed_intc1_irq_handler, intc1);
224 }
225 }
226}
227
228static int aspeed_intc1_probe(struct platform_device *pdev,
229 struct device_node *parent)
230{
231 struct device_node *node = pdev->dev.of_node;
232 struct aspeed_intc1 *intc1;
233 struct irq_domain *host;
234 int ret;
235
236 if (!parent) {
237 dev_err(&pdev->dev, "missing parent interrupt node\n");
238 return -ENODEV;
239 }
240
241 if (!of_device_is_compatible(parent, "aspeed,ast2700-intc0"))
242 return -ENODEV;
243
244 host = irq_find_host(parent);
245 if (!host)
246 return -ENODEV;
247
248 intc1 = devm_kzalloc(&pdev->dev, sizeof(*intc1), GFP_KERNEL);
249 if (!intc1)
250 return -ENOMEM;
251
252 intc1->dev = &pdev->dev;
253 intc1->upstream = host;
254 intc1->base = devm_platform_ioremap_resource(pdev, 0);
255 if (IS_ERR(intc1->base))
256 return PTR_ERR(intc1->base);
257
258 aspeed_intc1_disable_int(intc1);
259
260 raw_spin_lock_init(&intc1->intc_lock);
261
262 intc1->local = irq_domain_create_linear(of_fwnode_handle(node),
263 INTC1_BANK_NUM * INTC1_IRQS_PER_BANK,
264 &aspeed_intc1_irq_domain_ops, intc1);
265 if (!intc1->local)
266 return -ENOMEM;
267
268 ret = aspeed_intc_populate_ranges(&pdev->dev, &intc1->ranges);
269 if (ret < 0) {
270 irq_domain_remove(intc1->local);
271 return ret;
272 }
273
274 aspeed_intc1_request_interrupts(intc1);
275
276 return 0;
277}
278
279IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc1)
280IRQCHIP_MATCH("aspeed,ast2700-intc1", aspeed_intc1_probe)
281IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc1)