Commit | Line | Data |
---|---|---|
dbb15226 JY |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> | |
4 | * Loongson Local IO Interrupt Controller support | |
5 | */ | |
6 | ||
7 | #include <linux/errno.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/types.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/ioport.h> | |
12 | #include <linux/irqchip.h> | |
13 | #include <linux/of_address.h> | |
14 | #include <linux/of_irq.h> | |
15 | #include <linux/io.h> | |
16 | #include <linux/smp.h> | |
17 | #include <linux/irqchip/chained_irq.h> | |
18 | ||
fa84f893 | 19 | #ifdef CONFIG_MIPS |
76e0c88d | 20 | #include <loongson.h> |
fa84f893 HC |
21 | #else |
22 | #include <asm/loongson.h> | |
23 | #endif | |
dbb15226 JY |
24 | |
25 | #define LIOINTC_CHIP_IRQ 32 | |
0858ed03 | 26 | #define LIOINTC_NUM_PARENT 4 |
b2c4c396 | 27 | #define LIOINTC_NUM_CORES 4 |
dbb15226 JY |
28 | |
29 | #define LIOINTC_INTC_CHIP_START 0x20 | |
30 | ||
a9c3ee5d | 31 | #define LIOINTC_REG_INTC_STATUS(core) (LIOINTC_INTC_CHIP_START + 0x20 + (core) * 8) |
dbb15226 JY |
32 | #define LIOINTC_REG_INTC_EN_STATUS (LIOINTC_INTC_CHIP_START + 0x04) |
33 | #define LIOINTC_REG_INTC_ENABLE (LIOINTC_INTC_CHIP_START + 0x08) | |
34 | #define LIOINTC_REG_INTC_DISABLE (LIOINTC_INTC_CHIP_START + 0x0c) | |
1d7471b4 JL |
35 | /* |
36 | * LIOINTC_REG_INTC_POL register is only valid for Loongson-2K series, and | |
37 | * Loongson-3 series behave as noops. | |
38 | */ | |
dbb15226 JY |
39 | #define LIOINTC_REG_INTC_POL (LIOINTC_INTC_CHIP_START + 0x10) |
40 | #define LIOINTC_REG_INTC_EDGE (LIOINTC_INTC_CHIP_START + 0x14) | |
41 | ||
42 | #define LIOINTC_SHIFT_INTx 4 | |
43 | ||
be09ef09 JY |
44 | #define LIOINTC_ERRATA_IRQ 10 |
45 | ||
6fac824f JY |
46 | #if defined(CONFIG_MIPS) |
47 | #define liointc_core_id get_ebase_cpunum() | |
48 | #else | |
49 | #define liointc_core_id get_csr_cpuid() | |
50 | #endif | |
51 | ||
dbb15226 JY |
52 | struct liointc_handler_data { |
53 | struct liointc_priv *priv; | |
54 | u32 parent_int_map; | |
55 | }; | |
56 | ||
57 | struct liointc_priv { | |
58 | struct irq_chip_generic *gc; | |
59 | struct liointc_handler_data handler[LIOINTC_NUM_PARENT]; | |
b2c4c396 | 60 | void __iomem *core_isr[LIOINTC_NUM_CORES]; |
dbb15226 | 61 | u8 map_cache[LIOINTC_CHIP_IRQ]; |
fc98adb9 HC |
62 | u32 int_pol; |
63 | u32 int_edge; | |
be09ef09 | 64 | bool has_lpc_irq_errata; |
dbb15226 JY |
65 | }; |
66 | ||
0858ed03 HC |
67 | struct fwnode_handle *liointc_handle; |
68 | ||
dbb15226 JY |
69 | static void liointc_chained_handle_irq(struct irq_desc *desc) |
70 | { | |
71 | struct liointc_handler_data *handler = irq_desc_get_handler_data(desc); | |
72 | struct irq_chip *chip = irq_desc_get_chip(desc); | |
73 | struct irq_chip_generic *gc = handler->priv->gc; | |
6fac824f | 74 | int core = liointc_core_id % LIOINTC_NUM_CORES; |
dbb15226 JY |
75 | u32 pending; |
76 | ||
77 | chained_irq_enter(chip, desc); | |
78 | ||
b2c4c396 | 79 | pending = readl(handler->priv->core_isr[core]); |
dbb15226 | 80 | |
be09ef09 JY |
81 | if (!pending) { |
82 | /* Always blame LPC IRQ if we have that bug */ | |
83 | if (handler->priv->has_lpc_irq_errata && | |
c9c73a05 | 84 | (handler->parent_int_map & gc->mask_cache & |
be09ef09 JY |
85 | BIT(LIOINTC_ERRATA_IRQ))) |
86 | pending = BIT(LIOINTC_ERRATA_IRQ); | |
87 | else | |
88 | spurious_interrupt(); | |
89 | } | |
dbb15226 JY |
90 | |
91 | while (pending) { | |
92 | int bit = __ffs(pending); | |
93 | ||
046a6ee2 | 94 | generic_handle_domain_irq(gc->domain, bit); |
dbb15226 JY |
95 | pending &= ~BIT(bit); |
96 | } | |
97 | ||
98 | chained_irq_exit(chip, desc); | |
99 | } | |
100 | ||
101 | static void liointc_set_bit(struct irq_chip_generic *gc, | |
102 | unsigned int offset, | |
103 | u32 mask, bool set) | |
104 | { | |
105 | if (set) | |
106 | writel(readl(gc->reg_base + offset) | mask, | |
107 | gc->reg_base + offset); | |
108 | else | |
109 | writel(readl(gc->reg_base + offset) & ~mask, | |
110 | gc->reg_base + offset); | |
111 | } | |
112 | ||
113 | static int liointc_set_type(struct irq_data *data, unsigned int type) | |
114 | { | |
115 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); | |
116 | u32 mask = data->mask; | |
117 | unsigned long flags; | |
118 | ||
119 | irq_gc_lock_irqsave(gc, flags); | |
120 | switch (type) { | |
121 | case IRQ_TYPE_LEVEL_HIGH: | |
122 | liointc_set_bit(gc, LIOINTC_REG_INTC_EDGE, mask, false); | |
1d7471b4 | 123 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, false); |
dbb15226 JY |
124 | break; |
125 | case IRQ_TYPE_LEVEL_LOW: | |
126 | liointc_set_bit(gc, LIOINTC_REG_INTC_EDGE, mask, false); | |
1d7471b4 | 127 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, true); |
dbb15226 JY |
128 | break; |
129 | case IRQ_TYPE_EDGE_RISING: | |
130 | liointc_set_bit(gc, LIOINTC_REG_INTC_EDGE, mask, true); | |
1d7471b4 | 131 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, false); |
dbb15226 JY |
132 | break; |
133 | case IRQ_TYPE_EDGE_FALLING: | |
134 | liointc_set_bit(gc, LIOINTC_REG_INTC_EDGE, mask, true); | |
1d7471b4 | 135 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, true); |
dbb15226 JY |
136 | break; |
137 | default: | |
fa03587c | 138 | irq_gc_unlock_irqrestore(gc, flags); |
dbb15226 JY |
139 | return -EINVAL; |
140 | } | |
141 | irq_gc_unlock_irqrestore(gc, flags); | |
142 | ||
143 | irqd_set_trigger_type(data, type); | |
144 | return 0; | |
145 | } | |
146 | ||
fc98adb9 HC |
147 | static void liointc_suspend(struct irq_chip_generic *gc) |
148 | { | |
149 | struct liointc_priv *priv = gc->private; | |
150 | ||
151 | priv->int_pol = readl(gc->reg_base + LIOINTC_REG_INTC_POL); | |
152 | priv->int_edge = readl(gc->reg_base + LIOINTC_REG_INTC_EDGE); | |
153 | } | |
154 | ||
dbb15226 JY |
155 | static void liointc_resume(struct irq_chip_generic *gc) |
156 | { | |
157 | struct liointc_priv *priv = gc->private; | |
158 | unsigned long flags; | |
159 | int i; | |
160 | ||
161 | irq_gc_lock_irqsave(gc, flags); | |
162 | /* Disable all at first */ | |
163 | writel(0xffffffff, gc->reg_base + LIOINTC_REG_INTC_DISABLE); | |
c9c73a05 | 164 | /* Restore map cache */ |
dbb15226 JY |
165 | for (i = 0; i < LIOINTC_CHIP_IRQ; i++) |
166 | writeb(priv->map_cache[i], gc->reg_base + i); | |
fc98adb9 HC |
167 | writel(priv->int_pol, gc->reg_base + LIOINTC_REG_INTC_POL); |
168 | writel(priv->int_edge, gc->reg_base + LIOINTC_REG_INTC_EDGE); | |
c9c73a05 HC |
169 | /* Restore mask cache */ |
170 | writel(gc->mask_cache, gc->reg_base + LIOINTC_REG_INTC_ENABLE); | |
dbb15226 JY |
171 | irq_gc_unlock_irqrestore(gc, flags); |
172 | } | |
173 | ||
0858ed03 HC |
174 | static int parent_irq[LIOINTC_NUM_PARENT]; |
175 | static u32 parent_int_map[LIOINTC_NUM_PARENT]; | |
176 | static const char *const parent_names[] = {"int0", "int1", "int2", "int3"}; | |
177 | static const char *const core_reg_names[] = {"isr0", "isr1", "isr2", "isr3"}; | |
b2c4c396 | 178 | |
0858ed03 HC |
179 | static int liointc_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, |
180 | const u32 *intspec, unsigned int intsize, | |
181 | unsigned long *out_hwirq, unsigned int *out_type) | |
b2c4c396 | 182 | { |
0858ed03 HC |
183 | if (WARN_ON(intsize < 1)) |
184 | return -EINVAL; | |
185 | *out_hwirq = intspec[0] - GSI_MIN_CPU_IRQ; | |
17343d0b JL |
186 | |
187 | if (intsize > 1) | |
188 | *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; | |
189 | else | |
190 | *out_type = IRQ_TYPE_NONE; | |
191 | ||
0858ed03 | 192 | return 0; |
b2c4c396 | 193 | } |
dbb15226 | 194 | |
0858ed03 HC |
195 | static const struct irq_domain_ops acpi_irq_gc_ops = { |
196 | .map = irq_map_generic_chip, | |
197 | .unmap = irq_unmap_generic_chip, | |
198 | .xlate = liointc_domain_xlate, | |
199 | }; | |
200 | ||
201 | static int liointc_init(phys_addr_t addr, unsigned long size, int revision, | |
202 | struct fwnode_handle *domain_handle, struct device_node *node) | |
dbb15226 | 203 | { |
0858ed03 HC |
204 | int i, err; |
205 | void __iomem *base; | |
206 | struct irq_chip_type *ct; | |
dbb15226 JY |
207 | struct irq_chip_generic *gc; |
208 | struct irq_domain *domain; | |
dbb15226 | 209 | struct liointc_priv *priv; |
dbb15226 JY |
210 | |
211 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
212 | if (!priv) | |
213 | return -ENOMEM; | |
214 | ||
0858ed03 HC |
215 | base = ioremap(addr, size); |
216 | if (!base) | |
217 | goto out_free_priv; | |
b2c4c396 | 218 | |
0858ed03 | 219 | for (i = 0; i < LIOINTC_NUM_CORES; i++) |
a9c3ee5d | 220 | priv->core_isr[i] = base + LIOINTC_REG_INTC_STATUS(i); |
b2c4c396 | 221 | |
0858ed03 HC |
222 | for (i = 0; i < LIOINTC_NUM_PARENT; i++) |
223 | priv->handler[i].parent_int_map = parent_int_map[i]; | |
dbb15226 | 224 | |
0858ed03 HC |
225 | if (revision > 1) { |
226 | for (i = 0; i < LIOINTC_NUM_CORES; i++) { | |
227 | int index = of_property_match_string(node, | |
228 | "reg-names", core_reg_names[i]); | |
dbb15226 | 229 | |
0858ed03 | 230 | if (index < 0) |
4a60a3cd | 231 | continue; |
dbb15226 | 232 | |
0858ed03 HC |
233 | priv->core_isr[i] = of_iomap(node, index); |
234 | } | |
4a60a3cd LP |
235 | |
236 | if (!priv->core_isr[0]) | |
237 | goto out_iounmap; | |
0858ed03 | 238 | } |
dbb15226 JY |
239 | |
240 | /* Setup IRQ domain */ | |
0858ed03 HC |
241 | if (!acpi_disabled) |
242 | domain = irq_domain_create_linear(domain_handle, LIOINTC_CHIP_IRQ, | |
243 | &acpi_irq_gc_ops, priv); | |
244 | else | |
245 | domain = irq_domain_create_linear(domain_handle, LIOINTC_CHIP_IRQ, | |
dbb15226 JY |
246 | &irq_generic_chip_ops, priv); |
247 | if (!domain) { | |
248 | pr_err("loongson-liointc: cannot add IRQ domain\n"); | |
0858ed03 | 249 | goto out_iounmap; |
dbb15226 JY |
250 | } |
251 | ||
0858ed03 HC |
252 | err = irq_alloc_domain_generic_chips(domain, LIOINTC_CHIP_IRQ, 1, |
253 | (node ? node->full_name : "LIOINTC"), | |
254 | handle_level_irq, 0, IRQ_NOPROBE, 0); | |
dbb15226 JY |
255 | if (err) { |
256 | pr_err("loongson-liointc: unable to register IRQ domain\n"); | |
257 | goto out_free_domain; | |
258 | } | |
259 | ||
260 | ||
261 | /* Disable all IRQs */ | |
262 | writel(0xffffffff, base + LIOINTC_REG_INTC_DISABLE); | |
263 | /* Set to level triggered */ | |
264 | writel(0x0, base + LIOINTC_REG_INTC_EDGE); | |
265 | ||
266 | /* Generate parent INT part of map cache */ | |
267 | for (i = 0; i < LIOINTC_NUM_PARENT; i++) { | |
268 | u32 pending = priv->handler[i].parent_int_map; | |
269 | ||
270 | while (pending) { | |
271 | int bit = __ffs(pending); | |
272 | ||
273 | priv->map_cache[bit] = BIT(i) << LIOINTC_SHIFT_INTx; | |
274 | pending &= ~BIT(bit); | |
275 | } | |
276 | } | |
277 | ||
278 | for (i = 0; i < LIOINTC_CHIP_IRQ; i++) { | |
279 | /* Generate core part of map cache */ | |
280 | priv->map_cache[i] |= BIT(loongson_sysconf.boot_cpu_id); | |
281 | writeb(priv->map_cache[i], base + i); | |
282 | } | |
283 | ||
284 | gc = irq_get_domain_generic_chip(domain, 0); | |
285 | gc->private = priv; | |
286 | gc->reg_base = base; | |
287 | gc->domain = domain; | |
fc98adb9 | 288 | gc->suspend = liointc_suspend; |
dbb15226 JY |
289 | gc->resume = liointc_resume; |
290 | ||
291 | ct = gc->chip_types; | |
292 | ct->regs.enable = LIOINTC_REG_INTC_ENABLE; | |
293 | ct->regs.disable = LIOINTC_REG_INTC_DISABLE; | |
294 | ct->chip.irq_unmask = irq_gc_unmask_enable_reg; | |
295 | ct->chip.irq_mask = irq_gc_mask_disable_reg; | |
296 | ct->chip.irq_mask_ack = irq_gc_mask_disable_reg; | |
297 | ct->chip.irq_set_type = liointc_set_type; | |
e01f9882 | 298 | ct->chip.flags = IRQCHIP_SKIP_SET_WAKE; |
dbb15226 | 299 | |
c9c73a05 | 300 | gc->mask_cache = 0; |
dbb15226 JY |
301 | priv->gc = gc; |
302 | ||
303 | for (i = 0; i < LIOINTC_NUM_PARENT; i++) { | |
304 | if (parent_irq[i] <= 0) | |
305 | continue; | |
306 | ||
307 | priv->handler[i].priv = priv; | |
308 | irq_set_chained_handler_and_data(parent_irq[i], | |
309 | liointc_chained_handle_irq, &priv->handler[i]); | |
310 | } | |
311 | ||
0858ed03 | 312 | liointc_handle = domain_handle; |
dbb15226 JY |
313 | return 0; |
314 | ||
315 | out_free_domain: | |
316 | irq_domain_remove(domain); | |
0858ed03 | 317 | out_iounmap: |
dbb15226 JY |
318 | iounmap(base); |
319 | out_free_priv: | |
320 | kfree(priv); | |
321 | ||
0858ed03 HC |
322 | return -EINVAL; |
323 | } | |
324 | ||
325 | #ifdef CONFIG_OF | |
326 | ||
327 | static int __init liointc_of_init(struct device_node *node, | |
328 | struct device_node *parent) | |
329 | { | |
330 | bool have_parent = FALSE; | |
331 | int sz, i, index, revision, err = 0; | |
332 | struct resource res; | |
333 | ||
334 | if (!of_device_is_compatible(node, "loongson,liointc-2.0")) { | |
335 | index = 0; | |
336 | revision = 1; | |
337 | } else { | |
338 | index = of_property_match_string(node, "reg-names", "main"); | |
339 | revision = 2; | |
340 | } | |
341 | ||
342 | if (of_address_to_resource(node, index, &res)) | |
343 | return -EINVAL; | |
344 | ||
345 | for (i = 0; i < LIOINTC_NUM_PARENT; i++) { | |
346 | parent_irq[i] = of_irq_get_byname(node, parent_names[i]); | |
347 | if (parent_irq[i] > 0) | |
348 | have_parent = TRUE; | |
349 | } | |
350 | if (!have_parent) | |
351 | return -ENODEV; | |
352 | ||
353 | sz = of_property_read_variable_u32_array(node, | |
354 | "loongson,parent_int_map", | |
355 | &parent_int_map[0], | |
356 | LIOINTC_NUM_PARENT, | |
357 | LIOINTC_NUM_PARENT); | |
358 | if (sz < 4) { | |
359 | pr_err("loongson-liointc: No parent_int_map\n"); | |
360 | return -ENODEV; | |
361 | } | |
362 | ||
363 | err = liointc_init(res.start, resource_size(&res), | |
364 | revision, of_node_to_fwnode(node), node); | |
365 | if (err < 0) | |
366 | return err; | |
367 | ||
368 | return 0; | |
dbb15226 JY |
369 | } |
370 | ||
371 | IRQCHIP_DECLARE(loongson_liointc_1_0, "loongson,liointc-1.0", liointc_of_init); | |
372 | IRQCHIP_DECLARE(loongson_liointc_1_0a, "loongson,liointc-1.0a", liointc_of_init); | |
b2c4c396 | 373 | IRQCHIP_DECLARE(loongson_liointc_2_0, "loongson,liointc-2.0", liointc_of_init); |
0858ed03 HC |
374 | |
375 | #endif | |
376 | ||
377 | #ifdef CONFIG_ACPI | |
70f7b6c0 HC |
378 | static int __init htintc_parse_madt(union acpi_subtable_headers *header, |
379 | const unsigned long end) | |
380 | { | |
381 | struct acpi_madt_ht_pic *htintc_entry = (struct acpi_madt_ht_pic *)header; | |
382 | struct irq_domain *parent = irq_find_matching_fwnode(liointc_handle, DOMAIN_BUS_ANY); | |
383 | ||
384 | return htvec_acpi_init(parent, htintc_entry); | |
385 | } | |
386 | ||
387 | static int __init acpi_cascade_irqdomain_init(void) | |
388 | { | |
389 | int r; | |
390 | ||
391 | r = acpi_table_parse_madt(ACPI_MADT_TYPE_HT_PIC, htintc_parse_madt, 0); | |
392 | if (r < 0) | |
393 | return r; | |
394 | ||
395 | return 0; | |
396 | } | |
397 | ||
0858ed03 HC |
398 | int __init liointc_acpi_init(struct irq_domain *parent, struct acpi_madt_lio_pic *acpi_liointc) |
399 | { | |
400 | int ret; | |
401 | struct fwnode_handle *domain_handle; | |
402 | ||
403 | parent_int_map[0] = acpi_liointc->cascade_map[0]; | |
404 | parent_int_map[1] = acpi_liointc->cascade_map[1]; | |
405 | ||
406 | parent_irq[0] = irq_create_mapping(parent, acpi_liointc->cascade[0]); | |
407 | parent_irq[1] = irq_create_mapping(parent, acpi_liointc->cascade[1]); | |
408 | ||
7e4fd7a1 | 409 | domain_handle = irq_domain_alloc_fwnode(&acpi_liointc->address); |
0858ed03 HC |
410 | if (!domain_handle) { |
411 | pr_err("Unable to allocate domain handle\n"); | |
412 | return -ENOMEM; | |
413 | } | |
70f7b6c0 | 414 | |
0858ed03 HC |
415 | ret = liointc_init(acpi_liointc->address, acpi_liointc->size, |
416 | 1, domain_handle, NULL); | |
70f7b6c0 HC |
417 | if (ret == 0) |
418 | ret = acpi_cascade_irqdomain_init(); | |
419 | else | |
0858ed03 HC |
420 | irq_domain_free_fwnode(domain_handle); |
421 | ||
422 | return ret; | |
423 | } | |
424 | #endif |