| 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 | |