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