1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2020-2022 Loongson Technology Corporation Limited |
4 | */ |
5 | |
6 | #include <linux/init.h> |
7 | #include <linux/kernel.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/irq.h> |
10 | #include <linux/irqchip.h> |
11 | #include <linux/irqdomain.h> |
12 | |
13 | #include <asm/loongarch.h> |
14 | #include <asm/setup.h> |
15 | |
16 | static struct irq_domain *irq_domain; |
17 | struct fwnode_handle *cpuintc_handle; |
18 | |
19 | static u32 lpic_gsi_to_irq(u32 gsi) |
20 | { |
21 | /* Only pch irqdomain transferring is required for LoongArch. */ |
22 | if (gsi >= GSI_MIN_PCH_IRQ && gsi <= GSI_MAX_PCH_IRQ) |
23 | return acpi_register_gsi(NULL, gsi, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH); |
24 | |
25 | return 0; |
26 | } |
27 | |
28 | static struct fwnode_handle *lpic_get_gsi_domain_id(u32 gsi) |
29 | { |
30 | int id; |
31 | struct fwnode_handle *domain_handle = NULL; |
32 | |
33 | switch (gsi) { |
34 | case GSI_MIN_CPU_IRQ ... GSI_MAX_CPU_IRQ: |
35 | if (liointc_handle) |
36 | domain_handle = liointc_handle; |
37 | break; |
38 | |
39 | case GSI_MIN_LPC_IRQ ... GSI_MAX_LPC_IRQ: |
40 | if (pch_lpc_handle) |
41 | domain_handle = pch_lpc_handle; |
42 | break; |
43 | |
44 | case GSI_MIN_PCH_IRQ ... GSI_MAX_PCH_IRQ: |
45 | id = find_pch_pic(gsi); |
46 | if (id >= 0 && pch_pic_handle[id]) |
47 | domain_handle = pch_pic_handle[id]; |
48 | break; |
49 | } |
50 | |
51 | return domain_handle; |
52 | } |
53 | |
54 | static void mask_loongarch_irq(struct irq_data *d) |
55 | { |
56 | clear_csr_ecfg(ECFGF(d->hwirq)); |
57 | } |
58 | |
59 | static void unmask_loongarch_irq(struct irq_data *d) |
60 | { |
61 | set_csr_ecfg(ECFGF(d->hwirq)); |
62 | } |
63 | |
64 | static struct irq_chip cpu_irq_controller = { |
65 | .name = "CPUINTC" , |
66 | .irq_mask = mask_loongarch_irq, |
67 | .irq_unmask = unmask_loongarch_irq, |
68 | }; |
69 | |
70 | static void handle_cpu_irq(struct pt_regs *regs) |
71 | { |
72 | int hwirq; |
73 | unsigned int estat = read_csr_estat() & CSR_ESTAT_IS; |
74 | |
75 | while ((hwirq = ffs(estat))) { |
76 | estat &= ~BIT(hwirq - 1); |
77 | generic_handle_domain_irq(domain: irq_domain, hwirq: hwirq - 1); |
78 | } |
79 | } |
80 | |
81 | static int loongarch_cpu_intc_map(struct irq_domain *d, unsigned int irq, |
82 | irq_hw_number_t hwirq) |
83 | { |
84 | irq_set_noprobe(irq); |
85 | irq_set_chip_and_handler(irq, chip: &cpu_irq_controller, handle: handle_percpu_irq); |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static const struct irq_domain_ops loongarch_cpu_intc_irq_domain_ops = { |
91 | .map = loongarch_cpu_intc_map, |
92 | .xlate = irq_domain_xlate_onecell, |
93 | }; |
94 | |
95 | #ifdef CONFIG_OF |
96 | static int __init cpuintc_of_init(struct device_node *of_node, |
97 | struct device_node *parent) |
98 | { |
99 | cpuintc_handle = of_node_to_fwnode(node: of_node); |
100 | |
101 | irq_domain = irq_domain_create_linear(fwnode: cpuintc_handle, size: EXCCODE_INT_NUM, |
102 | ops: &loongarch_cpu_intc_irq_domain_ops, NULL); |
103 | if (!irq_domain) |
104 | panic(fmt: "Failed to add irqdomain for loongarch CPU" ); |
105 | |
106 | set_handle_irq(&handle_cpu_irq); |
107 | |
108 | return 0; |
109 | } |
110 | IRQCHIP_DECLARE(cpu_intc, "loongson,cpu-interrupt-controller" , cpuintc_of_init); |
111 | #endif |
112 | |
113 | static int __init liointc_parse_madt(union acpi_subtable_headers *, |
114 | const unsigned long end) |
115 | { |
116 | struct acpi_madt_lio_pic *liointc_entry = (struct acpi_madt_lio_pic *)header; |
117 | |
118 | return liointc_acpi_init(irq_domain, liointc_entry); |
119 | } |
120 | |
121 | static int __init eiointc_parse_madt(union acpi_subtable_headers *, |
122 | const unsigned long end) |
123 | { |
124 | struct acpi_madt_eio_pic *eiointc_entry = (struct acpi_madt_eio_pic *)header; |
125 | |
126 | return eiointc_acpi_init(irq_domain, eiointc_entry); |
127 | } |
128 | |
129 | static int __init acpi_cascade_irqdomain_init(void) |
130 | { |
131 | int r; |
132 | |
133 | r = acpi_table_parse_madt(id: ACPI_MADT_TYPE_LIO_PIC, handler: liointc_parse_madt, max_entries: 0); |
134 | if (r < 0) |
135 | return r; |
136 | |
137 | r = acpi_table_parse_madt(id: ACPI_MADT_TYPE_EIO_PIC, handler: eiointc_parse_madt, max_entries: 0); |
138 | if (r < 0) |
139 | return r; |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | static int __init cpuintc_acpi_init(union acpi_subtable_headers *, |
145 | const unsigned long end) |
146 | { |
147 | int ret; |
148 | |
149 | if (irq_domain) |
150 | return 0; |
151 | |
152 | /* Mask interrupts. */ |
153 | clear_csr_ecfg(ECFG0_IM); |
154 | clear_csr_estat(ESTATF_IP); |
155 | |
156 | cpuintc_handle = irq_domain_alloc_named_fwnode(name: "CPUINTC" ); |
157 | irq_domain = irq_domain_create_linear(fwnode: cpuintc_handle, size: EXCCODE_INT_NUM, |
158 | ops: &loongarch_cpu_intc_irq_domain_ops, NULL); |
159 | |
160 | if (!irq_domain) |
161 | panic(fmt: "Failed to add irqdomain for LoongArch CPU" ); |
162 | |
163 | set_handle_irq(&handle_cpu_irq); |
164 | acpi_set_irq_model(model: ACPI_IRQ_MODEL_LPIC, lpic_get_gsi_domain_id); |
165 | acpi_set_gsi_to_irq_fallback(lpic_gsi_to_irq); |
166 | ret = acpi_cascade_irqdomain_init(); |
167 | |
168 | return ret; |
169 | } |
170 | |
171 | IRQCHIP_ACPI_DECLARE(cpuintc_v1, ACPI_MADT_TYPE_CORE_PIC, |
172 | NULL, ACPI_MADT_CORE_PIC_VERSION_V1, cpuintc_acpi_init); |
173 | |