1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com>
4 * Loongson Local IO Interrupt Controller support
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>
16 #include <linux/smp.h>
17 #include <linux/irqchip/chained_irq.h>
22 #include <asm/loongson.h>
25 #include "irq-loongson.h"
27 #define LIOINTC_CHIP_IRQ 32
28 #define LIOINTC_NUM_PARENT 4
29 #define LIOINTC_NUM_CORES 4
31 #define LIOINTC_INTC_CHIP_START 0x20
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)
38 * LIOINTC_REG_INTC_POL register is only valid for Loongson-2K series, and
39 * Loongson-3 series behave as noops.
41 #define LIOINTC_REG_INTC_POL (LIOINTC_INTC_CHIP_START + 0x10)
42 #define LIOINTC_REG_INTC_EDGE (LIOINTC_INTC_CHIP_START + 0x14)
44 #define LIOINTC_SHIFT_INTx 4
46 #define LIOINTC_ERRATA_IRQ 10
48 #if defined(CONFIG_MIPS)
49 #define liointc_core_id get_ebase_cpunum()
51 #define liointc_core_id get_csr_cpuid()
54 struct liointc_handler_data
{
55 struct liointc_priv
*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
];
66 bool has_lpc_irq_errata
;
69 struct fwnode_handle
*liointc_handle
;
71 static void liointc_chained_handle_irq(struct irq_desc
*desc
)
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
;
79 chained_irq_enter(chip
, desc
);
81 pending
= readl(handler
->priv
->core_isr
[core
]);
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
);
94 int bit
= __ffs(pending
);
96 generic_handle_domain_irq(gc
->domain
, bit
);
100 chained_irq_exit(chip
, desc
);
103 static void liointc_set_bit(struct irq_chip_generic
*gc
,
108 writel(readl(gc
->reg_base
+ offset
) | mask
,
109 gc
->reg_base
+ offset
);
111 writel(readl(gc
->reg_base
+ offset
) & ~mask
,
112 gc
->reg_base
+ offset
);
115 static int liointc_set_type(struct irq_data
*data
, unsigned int type
)
117 struct irq_chip_generic
*gc
= irq_data_get_irq_chip_data(data
);
118 u32 mask
= data
->mask
;
121 irq_gc_lock_irqsave(gc
, flags
);
123 case IRQ_TYPE_LEVEL_HIGH
:
124 liointc_set_bit(gc
, LIOINTC_REG_INTC_EDGE
, mask
, false);
125 liointc_set_bit(gc
, LIOINTC_REG_INTC_POL
, mask
, false);
127 case IRQ_TYPE_LEVEL_LOW
:
128 liointc_set_bit(gc
, LIOINTC_REG_INTC_EDGE
, mask
, false);
129 liointc_set_bit(gc
, LIOINTC_REG_INTC_POL
, mask
, true);
131 case IRQ_TYPE_EDGE_RISING
:
132 liointc_set_bit(gc
, LIOINTC_REG_INTC_EDGE
, mask
, true);
133 liointc_set_bit(gc
, LIOINTC_REG_INTC_POL
, mask
, false);
135 case IRQ_TYPE_EDGE_FALLING
:
136 liointc_set_bit(gc
, LIOINTC_REG_INTC_EDGE
, mask
, true);
137 liointc_set_bit(gc
, LIOINTC_REG_INTC_POL
, mask
, true);
140 irq_gc_unlock_irqrestore(gc
, flags
);
143 irq_gc_unlock_irqrestore(gc
, flags
);
145 irqd_set_trigger_type(data
, type
);
149 static void liointc_suspend(struct irq_chip_generic
*gc
)
151 struct liointc_priv
*priv
= gc
->private;
153 priv
->int_pol
= readl(gc
->reg_base
+ LIOINTC_REG_INTC_POL
);
154 priv
->int_edge
= readl(gc
->reg_base
+ LIOINTC_REG_INTC_EDGE
);
157 static void liointc_resume(struct irq_chip_generic
*gc
)
159 struct liointc_priv
*priv
= gc
->private;
163 irq_gc_lock_irqsave(gc
, flags
);
164 /* Disable all at first */
165 writel(0xffffffff, gc
->reg_base
+ LIOINTC_REG_INTC_DISABLE
);
166 /* Restore map cache */
167 for (i
= 0; i
< LIOINTC_CHIP_IRQ
; i
++)
168 writeb(priv
->map_cache
[i
], gc
->reg_base
+ i
);
169 writel(priv
->int_pol
, gc
->reg_base
+ LIOINTC_REG_INTC_POL
);
170 writel(priv
->int_edge
, gc
->reg_base
+ LIOINTC_REG_INTC_EDGE
);
171 /* Restore mask cache */
172 writel(gc
->mask_cache
, gc
->reg_base
+ LIOINTC_REG_INTC_ENABLE
);
173 irq_gc_unlock_irqrestore(gc
, flags
);
176 static int parent_irq
[LIOINTC_NUM_PARENT
];
177 static u32 parent_int_map
[LIOINTC_NUM_PARENT
];
178 static const char *const parent_names
[] = {"int0", "int1", "int2", "int3"};
179 static const char *const core_reg_names
[] = {"isr0", "isr1", "isr2", "isr3"};
181 static int liointc_domain_xlate(struct irq_domain
*d
, struct device_node
*ctrlr
,
182 const u32
*intspec
, unsigned int intsize
,
183 unsigned long *out_hwirq
, unsigned int *out_type
)
185 if (WARN_ON(intsize
< 1))
187 *out_hwirq
= intspec
[0] - GSI_MIN_CPU_IRQ
;
190 *out_type
= intspec
[1] & IRQ_TYPE_SENSE_MASK
;
192 *out_type
= IRQ_TYPE_NONE
;
197 static const struct irq_domain_ops acpi_irq_gc_ops
= {
198 .map
= irq_map_generic_chip
,
199 .unmap
= irq_unmap_generic_chip
,
200 .xlate
= liointc_domain_xlate
,
203 static int liointc_init(phys_addr_t addr
, unsigned long size
, int revision
,
204 struct fwnode_handle
*domain_handle
, struct device_node
*node
)
208 struct irq_chip_type
*ct
;
209 struct irq_chip_generic
*gc
;
210 struct irq_domain
*domain
;
211 struct liointc_priv
*priv
;
213 priv
= kzalloc(sizeof(*priv
), GFP_KERNEL
);
217 base
= ioremap(addr
, size
);
221 for (i
= 0; i
< LIOINTC_NUM_CORES
; i
++)
222 priv
->core_isr
[i
] = base
+ LIOINTC_REG_INTC_STATUS(i
);
224 for (i
= 0; i
< LIOINTC_NUM_PARENT
; i
++)
225 priv
->handler
[i
].parent_int_map
= parent_int_map
[i
];
228 for (i
= 0; i
< LIOINTC_NUM_CORES
; i
++) {
229 int index
= of_property_match_string(node
,
230 "reg-names", core_reg_names
[i
]);
235 priv
->core_isr
[i
] = of_iomap(node
, index
);
238 if (!priv
->core_isr
[0])
242 /* Setup IRQ domain */
244 domain
= irq_domain_create_linear(domain_handle
, LIOINTC_CHIP_IRQ
,
245 &acpi_irq_gc_ops
, priv
);
247 domain
= irq_domain_create_linear(domain_handle
, LIOINTC_CHIP_IRQ
,
248 &irq_generic_chip_ops
, priv
);
250 pr_err("loongson-liointc: cannot add IRQ domain\n");
254 err
= irq_alloc_domain_generic_chips(domain
, LIOINTC_CHIP_IRQ
, 1,
255 (node
? node
->full_name
: "LIOINTC"),
256 handle_level_irq
, 0, IRQ_NOPROBE
, 0);
258 pr_err("loongson-liointc: unable to register IRQ domain\n");
259 goto out_free_domain
;
263 /* Disable all IRQs */
264 writel(0xffffffff, base
+ LIOINTC_REG_INTC_DISABLE
);
265 /* Set to level triggered */
266 writel(0x0, base
+ LIOINTC_REG_INTC_EDGE
);
268 /* Generate parent INT part of map cache */
269 for (i
= 0; i
< LIOINTC_NUM_PARENT
; i
++) {
270 u32 pending
= priv
->handler
[i
].parent_int_map
;
273 int bit
= __ffs(pending
);
275 priv
->map_cache
[bit
] = BIT(i
) << LIOINTC_SHIFT_INTx
;
276 pending
&= ~BIT(bit
);
280 for (i
= 0; i
< LIOINTC_CHIP_IRQ
; i
++) {
281 /* Generate core part of map cache */
282 priv
->map_cache
[i
] |= BIT(loongson_sysconf
.boot_cpu_id
);
283 writeb(priv
->map_cache
[i
], base
+ i
);
286 gc
= irq_get_domain_generic_chip(domain
, 0);
290 gc
->suspend
= liointc_suspend
;
291 gc
->resume
= liointc_resume
;
294 ct
->regs
.enable
= LIOINTC_REG_INTC_ENABLE
;
295 ct
->regs
.disable
= LIOINTC_REG_INTC_DISABLE
;
296 ct
->chip
.irq_unmask
= irq_gc_unmask_enable_reg
;
297 ct
->chip
.irq_mask
= irq_gc_mask_disable_reg
;
298 ct
->chip
.irq_mask_ack
= irq_gc_mask_disable_reg
;
299 ct
->chip
.irq_set_type
= liointc_set_type
;
300 ct
->chip
.flags
= IRQCHIP_SKIP_SET_WAKE
;
305 for (i
= 0; i
< LIOINTC_NUM_PARENT
; i
++) {
306 if (parent_irq
[i
] <= 0)
309 priv
->handler
[i
].priv
= priv
;
310 irq_set_chained_handler_and_data(parent_irq
[i
],
311 liointc_chained_handle_irq
, &priv
->handler
[i
]);
314 liointc_handle
= domain_handle
;
318 irq_domain_remove(domain
);
329 static int __init
liointc_of_init(struct device_node
*node
,
330 struct device_node
*parent
)
332 bool have_parent
= FALSE
;
333 int sz
, i
, index
, revision
, err
= 0;
336 if (!of_device_is_compatible(node
, "loongson,liointc-2.0")) {
340 index
= of_property_match_string(node
, "reg-names", "main");
344 if (of_address_to_resource(node
, index
, &res
))
347 for (i
= 0; i
< LIOINTC_NUM_PARENT
; i
++) {
348 parent_irq
[i
] = of_irq_get_byname(node
, parent_names
[i
]);
349 if (parent_irq
[i
] > 0)
355 sz
= of_property_read_variable_u32_array(node
,
356 "loongson,parent_int_map",
361 pr_err("loongson-liointc: No parent_int_map\n");
365 err
= liointc_init(res
.start
, resource_size(&res
),
366 revision
, of_node_to_fwnode(node
), node
);
373 IRQCHIP_DECLARE(loongson_liointc_1_0
, "loongson,liointc-1.0", liointc_of_init
);
374 IRQCHIP_DECLARE(loongson_liointc_1_0a
, "loongson,liointc-1.0a", liointc_of_init
);
375 IRQCHIP_DECLARE(loongson_liointc_2_0
, "loongson,liointc-2.0", liointc_of_init
);
380 static int __init
htintc_parse_madt(union acpi_subtable_headers
*header
,
381 const unsigned long end
)
383 struct acpi_madt_ht_pic
*htintc_entry
= (struct acpi_madt_ht_pic
*)header
;
384 struct irq_domain
*parent
= irq_find_matching_fwnode(liointc_handle
, DOMAIN_BUS_ANY
);
386 return htvec_acpi_init(parent
, htintc_entry
);
389 static int __init
acpi_cascade_irqdomain_init(void)
393 r
= acpi_table_parse_madt(ACPI_MADT_TYPE_HT_PIC
, htintc_parse_madt
, 0);
400 int __init
liointc_acpi_init(struct irq_domain
*parent
, struct acpi_madt_lio_pic
*acpi_liointc
)
403 struct fwnode_handle
*domain_handle
;
405 parent_int_map
[0] = acpi_liointc
->cascade_map
[0];
406 parent_int_map
[1] = acpi_liointc
->cascade_map
[1];
408 parent_irq
[0] = irq_create_mapping(parent
, acpi_liointc
->cascade
[0]);
409 parent_irq
[1] = irq_create_mapping(parent
, acpi_liointc
->cascade
[1]);
411 domain_handle
= irq_domain_alloc_fwnode(&acpi_liointc
->address
);
412 if (!domain_handle
) {
413 pr_err("Unable to allocate domain handle\n");
417 ret
= liointc_init(acpi_liointc
->address
, acpi_liointc
->size
,
418 1, domain_handle
, NULL
);
420 ret
= acpi_cascade_irqdomain_init();
422 irq_domain_free_fwnode(domain_handle
);