1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> |
4 | * Loongson PCH PIC support |
5 | */ |
6 | |
7 | #define pr_fmt(fmt) "pch-pic: " fmt |
8 | |
9 | #include <linux/interrupt.h> |
10 | #include <linux/irq.h> |
11 | #include <linux/irqchip.h> |
12 | #include <linux/irqdomain.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_address.h> |
17 | #include <linux/of_irq.h> |
18 | #include <linux/syscore_ops.h> |
19 | |
20 | /* Registers */ |
21 | #define PCH_PIC_MASK 0x20 |
22 | #define PCH_PIC_HTMSI_EN 0x40 |
23 | #define PCH_PIC_EDGE 0x60 |
24 | #define PCH_PIC_CLR 0x80 |
25 | #define PCH_PIC_AUTO0 0xc0 |
26 | #define PCH_PIC_AUTO1 0xe0 |
27 | #define PCH_INT_ROUTE(irq) (0x100 + irq) |
28 | #define PCH_INT_HTVEC(irq) (0x200 + irq) |
29 | #define PCH_PIC_POL 0x3e0 |
30 | |
31 | #define PIC_COUNT_PER_REG 32 |
32 | #define PIC_REG_COUNT 2 |
33 | #define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT) |
34 | #define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG) |
35 | #define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG) |
36 | |
37 | static int nr_pics; |
38 | |
39 | struct pch_pic { |
40 | void __iomem *base; |
41 | struct irq_domain *pic_domain; |
42 | u32 ht_vec_base; |
43 | raw_spinlock_t pic_lock; |
44 | u32 vec_count; |
45 | u32 gsi_base; |
46 | u32 saved_vec_en[PIC_REG_COUNT]; |
47 | u32 saved_vec_pol[PIC_REG_COUNT]; |
48 | u32 saved_vec_edge[PIC_REG_COUNT]; |
49 | }; |
50 | |
51 | static struct pch_pic *pch_pic_priv[MAX_IO_PICS]; |
52 | |
53 | struct fwnode_handle *pch_pic_handle[MAX_IO_PICS]; |
54 | |
55 | static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit) |
56 | { |
57 | u32 reg; |
58 | void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4; |
59 | |
60 | raw_spin_lock(&priv->pic_lock); |
61 | reg = readl(addr); |
62 | reg |= BIT(PIC_REG_BIT(bit)); |
63 | writel(val: reg, addr); |
64 | raw_spin_unlock(&priv->pic_lock); |
65 | } |
66 | |
67 | static void pch_pic_bitclr(struct pch_pic *priv, int offset, int bit) |
68 | { |
69 | u32 reg; |
70 | void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4; |
71 | |
72 | raw_spin_lock(&priv->pic_lock); |
73 | reg = readl(addr); |
74 | reg &= ~BIT(PIC_REG_BIT(bit)); |
75 | writel(val: reg, addr); |
76 | raw_spin_unlock(&priv->pic_lock); |
77 | } |
78 | |
79 | static void pch_pic_mask_irq(struct irq_data *d) |
80 | { |
81 | struct pch_pic *priv = irq_data_get_irq_chip_data(d); |
82 | |
83 | pch_pic_bitset(priv, PCH_PIC_MASK, bit: d->hwirq); |
84 | irq_chip_mask_parent(data: d); |
85 | } |
86 | |
87 | static void pch_pic_unmask_irq(struct irq_data *d) |
88 | { |
89 | struct pch_pic *priv = irq_data_get_irq_chip_data(d); |
90 | |
91 | writel(BIT(PIC_REG_BIT(d->hwirq)), |
92 | addr: priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4); |
93 | |
94 | irq_chip_unmask_parent(data: d); |
95 | pch_pic_bitclr(priv, PCH_PIC_MASK, bit: d->hwirq); |
96 | } |
97 | |
98 | static int pch_pic_set_type(struct irq_data *d, unsigned int type) |
99 | { |
100 | struct pch_pic *priv = irq_data_get_irq_chip_data(d); |
101 | int ret = 0; |
102 | |
103 | switch (type) { |
104 | case IRQ_TYPE_EDGE_RISING: |
105 | pch_pic_bitset(priv, PCH_PIC_EDGE, bit: d->hwirq); |
106 | pch_pic_bitclr(priv, PCH_PIC_POL, bit: d->hwirq); |
107 | irq_set_handler_locked(data: d, handler: handle_edge_irq); |
108 | break; |
109 | case IRQ_TYPE_EDGE_FALLING: |
110 | pch_pic_bitset(priv, PCH_PIC_EDGE, bit: d->hwirq); |
111 | pch_pic_bitset(priv, PCH_PIC_POL, bit: d->hwirq); |
112 | irq_set_handler_locked(data: d, handler: handle_edge_irq); |
113 | break; |
114 | case IRQ_TYPE_LEVEL_HIGH: |
115 | pch_pic_bitclr(priv, PCH_PIC_EDGE, bit: d->hwirq); |
116 | pch_pic_bitclr(priv, PCH_PIC_POL, bit: d->hwirq); |
117 | irq_set_handler_locked(data: d, handler: handle_level_irq); |
118 | break; |
119 | case IRQ_TYPE_LEVEL_LOW: |
120 | pch_pic_bitclr(priv, PCH_PIC_EDGE, bit: d->hwirq); |
121 | pch_pic_bitset(priv, PCH_PIC_POL, bit: d->hwirq); |
122 | irq_set_handler_locked(data: d, handler: handle_level_irq); |
123 | break; |
124 | default: |
125 | ret = -EINVAL; |
126 | break; |
127 | } |
128 | |
129 | return ret; |
130 | } |
131 | |
132 | static void pch_pic_ack_irq(struct irq_data *d) |
133 | { |
134 | unsigned int reg; |
135 | struct pch_pic *priv = irq_data_get_irq_chip_data(d); |
136 | |
137 | reg = readl(addr: priv->base + PCH_PIC_EDGE + PIC_REG_IDX(d->hwirq) * 4); |
138 | if (reg & BIT(PIC_REG_BIT(d->hwirq))) { |
139 | writel(BIT(PIC_REG_BIT(d->hwirq)), |
140 | addr: priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4); |
141 | } |
142 | irq_chip_ack_parent(data: d); |
143 | } |
144 | |
145 | static struct irq_chip pch_pic_irq_chip = { |
146 | .name = "PCH PIC" , |
147 | .irq_mask = pch_pic_mask_irq, |
148 | .irq_unmask = pch_pic_unmask_irq, |
149 | .irq_ack = pch_pic_ack_irq, |
150 | .irq_set_affinity = irq_chip_set_affinity_parent, |
151 | .irq_set_type = pch_pic_set_type, |
152 | .flags = IRQCHIP_SKIP_SET_WAKE, |
153 | }; |
154 | |
155 | static int pch_pic_domain_translate(struct irq_domain *d, |
156 | struct irq_fwspec *fwspec, |
157 | unsigned long *hwirq, |
158 | unsigned int *type) |
159 | { |
160 | struct pch_pic *priv = d->host_data; |
161 | struct device_node *of_node = to_of_node(fwspec->fwnode); |
162 | |
163 | if (of_node) { |
164 | if (fwspec->param_count < 2) |
165 | return -EINVAL; |
166 | |
167 | *hwirq = fwspec->param[0]; |
168 | *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; |
169 | } else { |
170 | if (fwspec->param_count < 1) |
171 | return -EINVAL; |
172 | |
173 | *hwirq = fwspec->param[0] - priv->gsi_base; |
174 | if (fwspec->param_count > 1) |
175 | *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; |
176 | else |
177 | *type = IRQ_TYPE_NONE; |
178 | } |
179 | |
180 | return 0; |
181 | } |
182 | |
183 | static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq, |
184 | unsigned int nr_irqs, void *arg) |
185 | { |
186 | int err; |
187 | unsigned int type; |
188 | unsigned long hwirq; |
189 | struct irq_fwspec *fwspec = arg; |
190 | struct irq_fwspec parent_fwspec; |
191 | struct pch_pic *priv = domain->host_data; |
192 | |
193 | err = pch_pic_domain_translate(d: domain, fwspec, hwirq: &hwirq, type: &type); |
194 | if (err) |
195 | return err; |
196 | |
197 | parent_fwspec.fwnode = domain->parent->fwnode; |
198 | parent_fwspec.param_count = 1; |
199 | parent_fwspec.param[0] = hwirq + priv->ht_vec_base; |
200 | |
201 | err = irq_domain_alloc_irqs_parent(domain, irq_base: virq, nr_irqs: 1, arg: &parent_fwspec); |
202 | if (err) |
203 | return err; |
204 | |
205 | irq_domain_set_info(domain, virq, hwirq, |
206 | chip: &pch_pic_irq_chip, chip_data: priv, |
207 | handler: handle_level_irq, NULL, NULL); |
208 | irq_set_probe(irq: virq); |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static const struct irq_domain_ops pch_pic_domain_ops = { |
214 | .translate = pch_pic_domain_translate, |
215 | .alloc = pch_pic_alloc, |
216 | .free = irq_domain_free_irqs_parent, |
217 | }; |
218 | |
219 | static void pch_pic_reset(struct pch_pic *priv) |
220 | { |
221 | int i; |
222 | |
223 | for (i = 0; i < PIC_COUNT; i++) { |
224 | /* Write vector ID */ |
225 | writeb(val: priv->ht_vec_base + i, addr: priv->base + PCH_INT_HTVEC(i)); |
226 | /* Hardcode route to HT0 Lo */ |
227 | writeb(val: 1, addr: priv->base + PCH_INT_ROUTE(i)); |
228 | } |
229 | |
230 | for (i = 0; i < PIC_REG_COUNT; i++) { |
231 | /* Clear IRQ cause registers, mask all interrupts */ |
232 | writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * i); |
233 | writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * i); |
234 | /* Clear auto bounce, we don't need that */ |
235 | writel_relaxed(0, priv->base + PCH_PIC_AUTO0 + 4 * i); |
236 | writel_relaxed(0, priv->base + PCH_PIC_AUTO1 + 4 * i); |
237 | /* Enable HTMSI transformer */ |
238 | writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_HTMSI_EN + 4 * i); |
239 | } |
240 | } |
241 | |
242 | static int pch_pic_suspend(void) |
243 | { |
244 | int i, j; |
245 | |
246 | for (i = 0; i < nr_pics; i++) { |
247 | for (j = 0; j < PIC_REG_COUNT; j++) { |
248 | pch_pic_priv[i]->saved_vec_pol[j] = |
249 | readl(addr: pch_pic_priv[i]->base + PCH_PIC_POL + 4 * j); |
250 | pch_pic_priv[i]->saved_vec_edge[j] = |
251 | readl(addr: pch_pic_priv[i]->base + PCH_PIC_EDGE + 4 * j); |
252 | pch_pic_priv[i]->saved_vec_en[j] = |
253 | readl(addr: pch_pic_priv[i]->base + PCH_PIC_MASK + 4 * j); |
254 | } |
255 | } |
256 | |
257 | return 0; |
258 | } |
259 | |
260 | static void pch_pic_resume(void) |
261 | { |
262 | int i, j; |
263 | |
264 | for (i = 0; i < nr_pics; i++) { |
265 | pch_pic_reset(priv: pch_pic_priv[i]); |
266 | for (j = 0; j < PIC_REG_COUNT; j++) { |
267 | writel(val: pch_pic_priv[i]->saved_vec_pol[j], |
268 | addr: pch_pic_priv[i]->base + PCH_PIC_POL + 4 * j); |
269 | writel(val: pch_pic_priv[i]->saved_vec_edge[j], |
270 | addr: pch_pic_priv[i]->base + PCH_PIC_EDGE + 4 * j); |
271 | writel(val: pch_pic_priv[i]->saved_vec_en[j], |
272 | addr: pch_pic_priv[i]->base + PCH_PIC_MASK + 4 * j); |
273 | } |
274 | } |
275 | } |
276 | |
277 | static struct syscore_ops pch_pic_syscore_ops = { |
278 | .suspend = pch_pic_suspend, |
279 | .resume = pch_pic_resume, |
280 | }; |
281 | |
282 | static int pch_pic_init(phys_addr_t addr, unsigned long size, int vec_base, |
283 | struct irq_domain *parent_domain, struct fwnode_handle *domain_handle, |
284 | u32 gsi_base) |
285 | { |
286 | struct pch_pic *priv; |
287 | |
288 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
289 | if (!priv) |
290 | return -ENOMEM; |
291 | |
292 | raw_spin_lock_init(&priv->pic_lock); |
293 | priv->base = ioremap(offset: addr, size); |
294 | if (!priv->base) |
295 | goto free_priv; |
296 | |
297 | priv->ht_vec_base = vec_base; |
298 | priv->vec_count = ((readq(addr: priv->base) >> 48) & 0xff) + 1; |
299 | priv->gsi_base = gsi_base; |
300 | |
301 | priv->pic_domain = irq_domain_create_hierarchy(parent: parent_domain, flags: 0, |
302 | size: priv->vec_count, fwnode: domain_handle, |
303 | ops: &pch_pic_domain_ops, host_data: priv); |
304 | |
305 | if (!priv->pic_domain) { |
306 | pr_err("Failed to create IRQ domain\n" ); |
307 | goto iounmap_base; |
308 | } |
309 | |
310 | pch_pic_reset(priv); |
311 | pch_pic_handle[nr_pics] = domain_handle; |
312 | pch_pic_priv[nr_pics++] = priv; |
313 | |
314 | if (nr_pics == 1) |
315 | register_syscore_ops(ops: &pch_pic_syscore_ops); |
316 | |
317 | return 0; |
318 | |
319 | iounmap_base: |
320 | iounmap(addr: priv->base); |
321 | free_priv: |
322 | kfree(objp: priv); |
323 | |
324 | return -EINVAL; |
325 | } |
326 | |
327 | #ifdef CONFIG_OF |
328 | |
329 | static int pch_pic_of_init(struct device_node *node, |
330 | struct device_node *parent) |
331 | { |
332 | int err, vec_base; |
333 | struct resource res; |
334 | struct irq_domain *parent_domain; |
335 | |
336 | if (of_address_to_resource(dev: node, index: 0, r: &res)) |
337 | return -EINVAL; |
338 | |
339 | parent_domain = irq_find_host(node: parent); |
340 | if (!parent_domain) { |
341 | pr_err("Failed to find the parent domain\n" ); |
342 | return -ENXIO; |
343 | } |
344 | |
345 | if (of_property_read_u32(np: node, propname: "loongson,pic-base-vec" , out_value: &vec_base)) { |
346 | pr_err("Failed to determine pic-base-vec\n" ); |
347 | return -EINVAL; |
348 | } |
349 | |
350 | err = pch_pic_init(addr: res.start, size: resource_size(res: &res), vec_base, |
351 | parent_domain, domain_handle: of_node_to_fwnode(node), gsi_base: 0); |
352 | if (err < 0) |
353 | return err; |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0" , pch_pic_of_init); |
359 | |
360 | #endif |
361 | |
362 | #ifdef CONFIG_ACPI |
363 | int find_pch_pic(u32 gsi) |
364 | { |
365 | int i; |
366 | |
367 | /* Find the PCH_PIC that manages this GSI. */ |
368 | for (i = 0; i < MAX_IO_PICS; i++) { |
369 | struct pch_pic *priv = pch_pic_priv[i]; |
370 | |
371 | if (!priv) |
372 | return -1; |
373 | |
374 | if (gsi >= priv->gsi_base && gsi < (priv->gsi_base + priv->vec_count)) |
375 | return i; |
376 | } |
377 | |
378 | pr_err("ERROR: Unable to locate PCH_PIC for GSI %d\n" , gsi); |
379 | return -1; |
380 | } |
381 | |
382 | static int __init pch_lpc_parse_madt(union acpi_subtable_headers *, |
383 | const unsigned long end) |
384 | { |
385 | struct acpi_madt_lpc_pic *pchlpc_entry = (struct acpi_madt_lpc_pic *)header; |
386 | |
387 | return pch_lpc_acpi_init(pch_pic_priv[0]->pic_domain, pchlpc_entry); |
388 | } |
389 | |
390 | static int __init acpi_cascade_irqdomain_init(void) |
391 | { |
392 | int r; |
393 | |
394 | r = acpi_table_parse_madt(id: ACPI_MADT_TYPE_LPC_PIC, handler: pch_lpc_parse_madt, max_entries: 0); |
395 | if (r < 0) |
396 | return r; |
397 | |
398 | return 0; |
399 | } |
400 | |
401 | int __init pch_pic_acpi_init(struct irq_domain *parent, |
402 | struct acpi_madt_bio_pic *acpi_pchpic) |
403 | { |
404 | int ret; |
405 | struct fwnode_handle *domain_handle; |
406 | |
407 | if (find_pch_pic(gsi: acpi_pchpic->gsi_base) >= 0) |
408 | return 0; |
409 | |
410 | domain_handle = irq_domain_alloc_fwnode(pa: &acpi_pchpic->address); |
411 | if (!domain_handle) { |
412 | pr_err("Unable to allocate domain handle\n" ); |
413 | return -ENOMEM; |
414 | } |
415 | |
416 | ret = pch_pic_init(addr: acpi_pchpic->address, size: acpi_pchpic->size, |
417 | vec_base: 0, parent_domain: parent, domain_handle, gsi_base: acpi_pchpic->gsi_base); |
418 | |
419 | if (ret < 0) { |
420 | irq_domain_free_fwnode(fwnode: domain_handle); |
421 | return ret; |
422 | } |
423 | |
424 | if (acpi_pchpic->id == 0) |
425 | ret = acpi_cascade_irqdomain_init(); |
426 | |
427 | return ret; |
428 | } |
429 | #endif |
430 | |