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 | #define LIOINTC_CHIP_IRQ 32 |
26 | #define LIOINTC_NUM_PARENT 4 |
27 | #define LIOINTC_NUM_CORES 4 |
28 | |
29 | #define LIOINTC_INTC_CHIP_START 0x20 |
30 | |
31 | #define LIOINTC_REG_INTC_STATUS (LIOINTC_INTC_CHIP_START + 0x20) |
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) |
35 | /* |
36 | * LIOINTC_REG_INTC_POL register is only valid for Loongson-2K series, and |
37 | * Loongson-3 series behave as noops. |
38 | */ |
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 | |
44 | #define LIOINTC_ERRATA_IRQ 10 |
45 | |
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 | |
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]; |
60 | void __iomem *core_isr[LIOINTC_NUM_CORES]; |
61 | u8 map_cache[LIOINTC_CHIP_IRQ]; |
62 | u32 int_pol; |
63 | u32 int_edge; |
64 | bool has_lpc_irq_errata; |
65 | }; |
66 | |
67 | struct fwnode_handle *liointc_handle; |
68 | |
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; |
74 | int core = liointc_core_id % LIOINTC_NUM_CORES; |
75 | u32 pending; |
76 | |
77 | chained_irq_enter(chip, desc); |
78 | |
79 | pending = readl(addr: handler->priv->core_isr[core]); |
80 | |
81 | if (!pending) { |
82 | /* Always blame LPC IRQ if we have that bug */ |
83 | if (handler->priv->has_lpc_irq_errata && |
84 | (handler->parent_int_map & gc->mask_cache & |
85 | BIT(LIOINTC_ERRATA_IRQ))) |
86 | pending = BIT(LIOINTC_ERRATA_IRQ); |
87 | else |
88 | spurious_interrupt(); |
89 | } |
90 | |
91 | while (pending) { |
92 | int bit = __ffs(pending); |
93 | |
94 | generic_handle_domain_irq(domain: gc->domain, hwirq: bit); |
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(addr: gc->reg_base + offset) | mask, |
107 | addr: gc->reg_base + offset); |
108 | else |
109 | writel(readl(addr: gc->reg_base + offset) & ~mask, |
110 | addr: 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(d: 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, set: false); |
123 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, set: false); |
124 | break; |
125 | case IRQ_TYPE_LEVEL_LOW: |
126 | liointc_set_bit(gc, LIOINTC_REG_INTC_EDGE, mask, set: false); |
127 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, set: true); |
128 | break; |
129 | case IRQ_TYPE_EDGE_RISING: |
130 | liointc_set_bit(gc, LIOINTC_REG_INTC_EDGE, mask, set: true); |
131 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, set: false); |
132 | break; |
133 | case IRQ_TYPE_EDGE_FALLING: |
134 | liointc_set_bit(gc, LIOINTC_REG_INTC_EDGE, mask, set: true); |
135 | liointc_set_bit(gc, LIOINTC_REG_INTC_POL, mask, set: true); |
136 | break; |
137 | default: |
138 | irq_gc_unlock_irqrestore(gc, flags); |
139 | return -EINVAL; |
140 | } |
141 | irq_gc_unlock_irqrestore(gc, flags); |
142 | |
143 | irqd_set_trigger_type(d: data, type); |
144 | return 0; |
145 | } |
146 | |
147 | static void liointc_suspend(struct irq_chip_generic *gc) |
148 | { |
149 | struct liointc_priv *priv = gc->private; |
150 | |
151 | priv->int_pol = readl(addr: gc->reg_base + LIOINTC_REG_INTC_POL); |
152 | priv->int_edge = readl(addr: gc->reg_base + LIOINTC_REG_INTC_EDGE); |
153 | } |
154 | |
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(val: 0xffffffff, addr: gc->reg_base + LIOINTC_REG_INTC_DISABLE); |
164 | /* Restore map cache */ |
165 | for (i = 0; i < LIOINTC_CHIP_IRQ; i++) |
166 | writeb(val: priv->map_cache[i], addr: gc->reg_base + i); |
167 | writel(val: priv->int_pol, addr: gc->reg_base + LIOINTC_REG_INTC_POL); |
168 | writel(val: priv->int_edge, addr: gc->reg_base + LIOINTC_REG_INTC_EDGE); |
169 | /* Restore mask cache */ |
170 | writel(val: gc->mask_cache, addr: gc->reg_base + LIOINTC_REG_INTC_ENABLE); |
171 | irq_gc_unlock_irqrestore(gc, flags); |
172 | } |
173 | |
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" }; |
178 | |
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) |
182 | { |
183 | if (WARN_ON(intsize < 1)) |
184 | return -EINVAL; |
185 | *out_hwirq = intspec[0] - GSI_MIN_CPU_IRQ; |
186 | |
187 | if (intsize > 1) |
188 | *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; |
189 | else |
190 | *out_type = IRQ_TYPE_NONE; |
191 | |
192 | return 0; |
193 | } |
194 | |
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) |
203 | { |
204 | int i, err; |
205 | void __iomem *base; |
206 | struct irq_chip_type *ct; |
207 | struct irq_chip_generic *gc; |
208 | struct irq_domain *domain; |
209 | struct liointc_priv *priv; |
210 | |
211 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
212 | if (!priv) |
213 | return -ENOMEM; |
214 | |
215 | base = ioremap(offset: addr, size); |
216 | if (!base) |
217 | goto out_free_priv; |
218 | |
219 | for (i = 0; i < LIOINTC_NUM_CORES; i++) |
220 | priv->core_isr[i] = base + LIOINTC_REG_INTC_STATUS; |
221 | |
222 | for (i = 0; i < LIOINTC_NUM_PARENT; i++) |
223 | priv->handler[i].parent_int_map = parent_int_map[i]; |
224 | |
225 | if (revision > 1) { |
226 | for (i = 0; i < LIOINTC_NUM_CORES; i++) { |
227 | int index = of_property_match_string(np: node, |
228 | propname: "reg-names" , string: core_reg_names[i]); |
229 | |
230 | if (index < 0) |
231 | continue; |
232 | |
233 | priv->core_isr[i] = of_iomap(node, index); |
234 | } |
235 | |
236 | if (!priv->core_isr[0]) |
237 | goto out_iounmap; |
238 | } |
239 | |
240 | /* Setup IRQ domain */ |
241 | if (!acpi_disabled) |
242 | domain = irq_domain_create_linear(fwnode: domain_handle, LIOINTC_CHIP_IRQ, |
243 | ops: &acpi_irq_gc_ops, host_data: priv); |
244 | else |
245 | domain = irq_domain_create_linear(fwnode: domain_handle, LIOINTC_CHIP_IRQ, |
246 | ops: &irq_generic_chip_ops, host_data: priv); |
247 | if (!domain) { |
248 | pr_err("loongson-liointc: cannot add IRQ domain\n" ); |
249 | goto out_iounmap; |
250 | } |
251 | |
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); |
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(val: 0xffffffff, addr: base + LIOINTC_REG_INTC_DISABLE); |
263 | /* Set to level triggered */ |
264 | writel(val: 0x0, addr: 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(val: priv->map_cache[i], addr: base + i); |
282 | } |
283 | |
284 | gc = irq_get_domain_generic_chip(d: domain, hw_irq: 0); |
285 | gc->private = priv; |
286 | gc->reg_base = base; |
287 | gc->domain = domain; |
288 | gc->suspend = liointc_suspend; |
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; |
298 | ct->chip.flags = IRQCHIP_SKIP_SET_WAKE; |
299 | |
300 | gc->mask_cache = 0; |
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(irq: parent_irq[i], |
309 | handle: liointc_chained_handle_irq, data: &priv->handler[i]); |
310 | } |
311 | |
312 | liointc_handle = domain_handle; |
313 | return 0; |
314 | |
315 | out_free_domain: |
316 | irq_domain_remove(host: domain); |
317 | out_iounmap: |
318 | iounmap(addr: base); |
319 | out_free_priv: |
320 | kfree(objp: priv); |
321 | |
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(device: node, "loongson,liointc-2.0" )) { |
335 | index = 0; |
336 | revision = 1; |
337 | } else { |
338 | index = of_property_match_string(np: node, propname: "reg-names" , string: "main" ); |
339 | revision = 2; |
340 | } |
341 | |
342 | if (of_address_to_resource(dev: node, index, r: &res)) |
343 | return -EINVAL; |
344 | |
345 | for (i = 0; i < LIOINTC_NUM_PARENT; i++) { |
346 | parent_irq[i] = of_irq_get_byname(dev: node, name: 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(np: node, |
354 | propname: "loongson,parent_int_map" , |
355 | out_values: &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(addr: res.start, size: resource_size(res: &res), |
364 | revision, domain_handle: of_node_to_fwnode(node), node); |
365 | if (err < 0) |
366 | return err; |
367 | |
368 | return 0; |
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); |
373 | IRQCHIP_DECLARE(loongson_liointc_2_0, "loongson,liointc-2.0" , liointc_of_init); |
374 | |
375 | #endif |
376 | |
377 | #ifdef CONFIG_ACPI |
378 | static int __init htintc_parse_madt(union acpi_subtable_headers *, |
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(fwnode: liointc_handle, bus_token: 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(id: ACPI_MADT_TYPE_HT_PIC, handler: htintc_parse_madt, max_entries: 0); |
392 | if (r < 0) |
393 | return r; |
394 | |
395 | return 0; |
396 | } |
397 | |
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(host: parent, hwirq: acpi_liointc->cascade[0]); |
407 | parent_irq[1] = irq_create_mapping(host: parent, hwirq: acpi_liointc->cascade[1]); |
408 | |
409 | domain_handle = irq_domain_alloc_fwnode(pa: &acpi_liointc->address); |
410 | if (!domain_handle) { |
411 | pr_err("Unable to allocate domain handle\n" ); |
412 | return -ENOMEM; |
413 | } |
414 | |
415 | ret = liointc_init(addr: acpi_liointc->address, size: acpi_liointc->size, |
416 | revision: 1, domain_handle, NULL); |
417 | if (ret == 0) |
418 | ret = acpi_cascade_irqdomain_init(); |
419 | else |
420 | irq_domain_free_fwnode(fwnode: domain_handle); |
421 | |
422 | return ret; |
423 | } |
424 | #endif |
425 | |