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