Merge tag 'drm-misc-next-2023-01-03' of git://anongit.freedesktop.org/drm/drm-misc...
[linux-2.6-microblaze.git] / drivers / irqchip / irq-loongson-htvec.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *  Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com>
4  *  Loongson HyperTransport Interrupt Vector support
5  */
6
7 #define pr_fmt(fmt) "htvec: " 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/irqchip/chained_irq.h>
14 #include <linux/kernel.h>
15 #include <linux/platform_device.h>
16 #include <linux/of_address.h>
17 #include <linux/of_irq.h>
18 #include <linux/of_platform.h>
19 #include <linux/syscore_ops.h>
20
21 /* Registers */
22 #define HTVEC_EN_OFF            0x20
23 #define HTVEC_MAX_PARENT_IRQ    8
24 #define VEC_COUNT_PER_REG       32
25 #define VEC_REG_IDX(irq_id)     ((irq_id) / VEC_COUNT_PER_REG)
26 #define VEC_REG_BIT(irq_id)     ((irq_id) % VEC_COUNT_PER_REG)
27
28 struct htvec {
29         int                     num_parents;
30         void __iomem            *base;
31         struct irq_domain       *htvec_domain;
32         raw_spinlock_t          htvec_lock;
33         u32                     saved_vec_en[HTVEC_MAX_PARENT_IRQ];
34 };
35
36 static struct htvec *htvec_priv;
37
38 static void htvec_irq_dispatch(struct irq_desc *desc)
39 {
40         int i;
41         u32 pending;
42         bool handled = false;
43         struct irq_chip *chip = irq_desc_get_chip(desc);
44         struct htvec *priv = irq_desc_get_handler_data(desc);
45
46         chained_irq_enter(chip, desc);
47
48         for (i = 0; i < priv->num_parents; i++) {
49                 pending = readl(priv->base + 4 * i);
50                 while (pending) {
51                         int bit = __ffs(pending);
52
53                         generic_handle_domain_irq(priv->htvec_domain,
54                                                   bit + VEC_COUNT_PER_REG * i);
55                         pending &= ~BIT(bit);
56                         handled = true;
57                 }
58         }
59
60         if (!handled)
61                 spurious_interrupt();
62
63         chained_irq_exit(chip, desc);
64 }
65
66 static void htvec_ack_irq(struct irq_data *d)
67 {
68         struct htvec *priv = irq_data_get_irq_chip_data(d);
69
70         writel(BIT(VEC_REG_BIT(d->hwirq)),
71                priv->base + VEC_REG_IDX(d->hwirq) * 4);
72 }
73
74 static void htvec_mask_irq(struct irq_data *d)
75 {
76         u32 reg;
77         void __iomem *addr;
78         struct htvec *priv = irq_data_get_irq_chip_data(d);
79
80         raw_spin_lock(&priv->htvec_lock);
81         addr = priv->base + HTVEC_EN_OFF;
82         addr += VEC_REG_IDX(d->hwirq) * 4;
83         reg = readl(addr);
84         reg &= ~BIT(VEC_REG_BIT(d->hwirq));
85         writel(reg, addr);
86         raw_spin_unlock(&priv->htvec_lock);
87 }
88
89 static void htvec_unmask_irq(struct irq_data *d)
90 {
91         u32 reg;
92         void __iomem *addr;
93         struct htvec *priv = irq_data_get_irq_chip_data(d);
94
95         raw_spin_lock(&priv->htvec_lock);
96         addr = priv->base + HTVEC_EN_OFF;
97         addr += VEC_REG_IDX(d->hwirq) * 4;
98         reg = readl(addr);
99         reg |= BIT(VEC_REG_BIT(d->hwirq));
100         writel(reg, addr);
101         raw_spin_unlock(&priv->htvec_lock);
102 }
103
104 static struct irq_chip htvec_irq_chip = {
105         .name                   = "LOONGSON_HTVEC",
106         .irq_mask               = htvec_mask_irq,
107         .irq_unmask             = htvec_unmask_irq,
108         .irq_ack                = htvec_ack_irq,
109 };
110
111 static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq,
112                               unsigned int nr_irqs, void *arg)
113 {
114         int ret;
115         unsigned long hwirq;
116         unsigned int type, i;
117         struct htvec *priv = domain->host_data;
118
119         ret = irq_domain_translate_onecell(domain, arg, &hwirq, &type);
120         if (ret)
121                 return ret;
122
123         for (i = 0; i < nr_irqs; i++) {
124                 irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip,
125                                     priv, handle_edge_irq, NULL, NULL);
126         }
127
128         return 0;
129 }
130
131 static void htvec_domain_free(struct irq_domain *domain, unsigned int virq,
132                                   unsigned int nr_irqs)
133 {
134         int i;
135
136         for (i = 0; i < nr_irqs; i++) {
137                 struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
138
139                 irq_set_handler(virq + i, NULL);
140                 irq_domain_reset_irq_data(d);
141         }
142 }
143
144 static const struct irq_domain_ops htvec_domain_ops = {
145         .translate      = irq_domain_translate_onecell,
146         .alloc          = htvec_domain_alloc,
147         .free           = htvec_domain_free,
148 };
149
150 static void htvec_reset(struct htvec *priv)
151 {
152         u32 idx;
153
154         /* Clear IRQ cause registers, mask all interrupts */
155         for (idx = 0; idx < priv->num_parents; idx++) {
156                 writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx);
157                 writel_relaxed(0xFFFFFFFF, priv->base + 4 * idx);
158         }
159 }
160
161 static int htvec_suspend(void)
162 {
163         int i;
164
165         for (i = 0; i < htvec_priv->num_parents; i++)
166                 htvec_priv->saved_vec_en[i] = readl(htvec_priv->base + HTVEC_EN_OFF + 4 * i);
167
168         return 0;
169 }
170
171 static void htvec_resume(void)
172 {
173         int i;
174
175         for (i = 0; i < htvec_priv->num_parents; i++)
176                 writel(htvec_priv->saved_vec_en[i], htvec_priv->base + HTVEC_EN_OFF + 4 * i);
177 }
178
179 static struct syscore_ops htvec_syscore_ops = {
180         .suspend = htvec_suspend,
181         .resume = htvec_resume,
182 };
183
184 static int htvec_init(phys_addr_t addr, unsigned long size,
185                 int num_parents, int parent_irq[], struct fwnode_handle *domain_handle)
186 {
187         int i;
188         struct htvec *priv;
189
190         priv = kzalloc(sizeof(*priv), GFP_KERNEL);
191         if (!priv)
192                 return -ENOMEM;
193
194         priv->num_parents = num_parents;
195         priv->base = ioremap(addr, size);
196         raw_spin_lock_init(&priv->htvec_lock);
197
198         /* Setup IRQ domain */
199         priv->htvec_domain = irq_domain_create_linear(domain_handle,
200                                         (VEC_COUNT_PER_REG * priv->num_parents),
201                                         &htvec_domain_ops, priv);
202         if (!priv->htvec_domain) {
203                 pr_err("loongson-htvec: cannot add IRQ domain\n");
204                 goto iounmap_base;
205         }
206
207         htvec_reset(priv);
208
209         for (i = 0; i < priv->num_parents; i++) {
210                 irq_set_chained_handler_and_data(parent_irq[i],
211                                                  htvec_irq_dispatch, priv);
212         }
213
214         htvec_priv = priv;
215
216         register_syscore_ops(&htvec_syscore_ops);
217
218         return 0;
219
220 iounmap_base:
221         iounmap(priv->base);
222         kfree(priv);
223
224         return -EINVAL;
225 }
226
227 #ifdef CONFIG_OF
228
229 static int htvec_of_init(struct device_node *node,
230                                 struct device_node *parent)
231 {
232         int i, err;
233         int parent_irq[8];
234         int num_parents = 0;
235         struct resource res;
236
237         if (of_address_to_resource(node, 0, &res))
238                 return -EINVAL;
239
240         /* Interrupt may come from any of the 8 interrupt lines */
241         for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) {
242                 parent_irq[i] = irq_of_parse_and_map(node, i);
243                 if (parent_irq[i] <= 0)
244                         break;
245
246                 num_parents++;
247         }
248
249         err = htvec_init(res.start, resource_size(&res),
250                         num_parents, parent_irq, of_node_to_fwnode(node));
251         if (err < 0)
252                 return err;
253
254         return 0;
255 }
256
257 IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init);
258
259 #endif
260
261 #ifdef CONFIG_ACPI
262 static int __init pch_pic_parse_madt(union acpi_subtable_headers *header,
263                                         const unsigned long end)
264 {
265         struct acpi_madt_bio_pic *pchpic_entry = (struct acpi_madt_bio_pic *)header;
266
267         return pch_pic_acpi_init(htvec_priv->htvec_domain, pchpic_entry);
268 }
269
270 static int __init pch_msi_parse_madt(union acpi_subtable_headers *header,
271                                         const unsigned long end)
272 {
273         struct acpi_madt_msi_pic *pchmsi_entry = (struct acpi_madt_msi_pic *)header;
274
275         return pch_msi_acpi_init(htvec_priv->htvec_domain, pchmsi_entry);
276 }
277
278 static int __init acpi_cascade_irqdomain_init(void)
279 {
280         int r;
281
282         r = acpi_table_parse_madt(ACPI_MADT_TYPE_BIO_PIC, pch_pic_parse_madt, 0);
283         if (r < 0)
284                 return r;
285
286         r = acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 0);
287         if (r < 0)
288                 return r;
289
290         return 0;
291 }
292
293 int __init htvec_acpi_init(struct irq_domain *parent,
294                                    struct acpi_madt_ht_pic *acpi_htvec)
295 {
296         int i, ret;
297         int num_parents, parent_irq[8];
298         struct fwnode_handle *domain_handle;
299
300         if (!acpi_htvec)
301                 return -EINVAL;
302
303         num_parents = HTVEC_MAX_PARENT_IRQ;
304
305         domain_handle = irq_domain_alloc_fwnode(&acpi_htvec->address);
306         if (!domain_handle) {
307                 pr_err("Unable to allocate domain handle\n");
308                 return -ENOMEM;
309         }
310
311         /* Interrupt may come from any of the 8 interrupt lines */
312         for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++)
313                 parent_irq[i] = irq_create_mapping(parent, acpi_htvec->cascade[i]);
314
315         ret = htvec_init(acpi_htvec->address, acpi_htvec->size,
316                         num_parents, parent_irq, domain_handle);
317
318         if (ret == 0)
319                 ret = acpi_cascade_irqdomain_init();
320         else
321                 irq_domain_free_fwnode(domain_handle);
322
323         return ret;
324 }
325
326 #endif