Merge tag '5.15-rc-ksmbd-part2' of git://git.samba.org/ksmbd
[linux-2.6-microblaze.git] / drivers / irqchip / irq-mst-intc.c
1 // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
2 /*
3  * Copyright (c) 2020 MediaTek Inc.
4  * Author Mark-PK Tsai <mark-pk.tsai@mediatek.com>
5  */
6 #include <linux/interrupt.h>
7 #include <linux/io.h>
8 #include <linux/irq.h>
9 #include <linux/irqchip.h>
10 #include <linux/irqdomain.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_irq.h>
14 #include <linux/slab.h>
15 #include <linux/spinlock.h>
16 #include <linux/syscore_ops.h>
17
18 #define MST_INTC_MAX_IRQS       64
19
20 #define INTC_MASK               0x0
21 #define INTC_REV_POLARITY       0x10
22 #define INTC_EOI                0x20
23
24 #ifdef CONFIG_PM_SLEEP
25 static LIST_HEAD(mst_intc_list);
26 #endif
27
28 struct mst_intc_chip_data {
29         raw_spinlock_t  lock;
30         unsigned int    irq_start, nr_irqs;
31         void __iomem    *base;
32         bool            no_eoi;
33 #ifdef CONFIG_PM_SLEEP
34         struct list_head entry;
35         u16 saved_polarity_conf[DIV_ROUND_UP(MST_INTC_MAX_IRQS, 16)];
36 #endif
37 };
38
39 static void mst_set_irq(struct irq_data *d, u32 offset)
40 {
41         irq_hw_number_t hwirq = irqd_to_hwirq(d);
42         struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d);
43         u16 val, mask;
44         unsigned long flags;
45
46         mask = 1 << (hwirq % 16);
47         offset += (hwirq / 16) * 4;
48
49         raw_spin_lock_irqsave(&cd->lock, flags);
50         val = readw_relaxed(cd->base + offset) | mask;
51         writew_relaxed(val, cd->base + offset);
52         raw_spin_unlock_irqrestore(&cd->lock, flags);
53 }
54
55 static void mst_clear_irq(struct irq_data *d, u32 offset)
56 {
57         irq_hw_number_t hwirq = irqd_to_hwirq(d);
58         struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d);
59         u16 val, mask;
60         unsigned long flags;
61
62         mask = 1 << (hwirq % 16);
63         offset += (hwirq / 16) * 4;
64
65         raw_spin_lock_irqsave(&cd->lock, flags);
66         val = readw_relaxed(cd->base + offset) & ~mask;
67         writew_relaxed(val, cd->base + offset);
68         raw_spin_unlock_irqrestore(&cd->lock, flags);
69 }
70
71 static void mst_intc_mask_irq(struct irq_data *d)
72 {
73         mst_set_irq(d, INTC_MASK);
74         irq_chip_mask_parent(d);
75 }
76
77 static void mst_intc_unmask_irq(struct irq_data *d)
78 {
79         mst_clear_irq(d, INTC_MASK);
80         irq_chip_unmask_parent(d);
81 }
82
83 static void mst_intc_eoi_irq(struct irq_data *d)
84 {
85         struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d);
86
87         if (!cd->no_eoi)
88                 mst_set_irq(d, INTC_EOI);
89
90         irq_chip_eoi_parent(d);
91 }
92
93 static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
94 {
95         switch (type) {
96         case IRQ_TYPE_LEVEL_LOW:
97         case IRQ_TYPE_EDGE_FALLING:
98                 mst_set_irq(data, INTC_REV_POLARITY);
99                 break;
100         case IRQ_TYPE_LEVEL_HIGH:
101         case IRQ_TYPE_EDGE_RISING:
102                 mst_clear_irq(data, INTC_REV_POLARITY);
103                 break;
104         default:
105                 return -EINVAL;
106         }
107
108         return irq_chip_set_type_parent(data, IRQ_TYPE_LEVEL_HIGH);
109 }
110
111 static struct irq_chip mst_intc_chip = {
112         .name                   = "mst-intc",
113         .irq_mask               = mst_intc_mask_irq,
114         .irq_unmask             = mst_intc_unmask_irq,
115         .irq_eoi                = mst_intc_eoi_irq,
116         .irq_get_irqchip_state  = irq_chip_get_parent_state,
117         .irq_set_irqchip_state  = irq_chip_set_parent_state,
118         .irq_set_affinity       = irq_chip_set_affinity_parent,
119         .irq_set_vcpu_affinity  = irq_chip_set_vcpu_affinity_parent,
120         .irq_set_type           = mst_irq_chip_set_type,
121         .irq_retrigger          = irq_chip_retrigger_hierarchy,
122         .flags                  = IRQCHIP_SET_TYPE_MASKED |
123                                   IRQCHIP_SKIP_SET_WAKE |
124                                   IRQCHIP_MASK_ON_SUSPEND,
125 };
126
127 #ifdef CONFIG_PM_SLEEP
128 static void mst_intc_polarity_save(struct mst_intc_chip_data *cd)
129 {
130         int i;
131         void __iomem *addr = cd->base + INTC_REV_POLARITY;
132
133         for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++)
134                 cd->saved_polarity_conf[i] = readw_relaxed(addr + i * 4);
135 }
136
137 static void mst_intc_polarity_restore(struct mst_intc_chip_data *cd)
138 {
139         int i;
140         void __iomem *addr = cd->base + INTC_REV_POLARITY;
141
142         for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++)
143                 writew_relaxed(cd->saved_polarity_conf[i], addr + i * 4);
144 }
145
146 static void mst_irq_resume(void)
147 {
148         struct mst_intc_chip_data *cd;
149
150         list_for_each_entry(cd, &mst_intc_list, entry)
151                 mst_intc_polarity_restore(cd);
152 }
153
154 static int mst_irq_suspend(void)
155 {
156         struct mst_intc_chip_data *cd;
157
158         list_for_each_entry(cd, &mst_intc_list, entry)
159                 mst_intc_polarity_save(cd);
160         return 0;
161 }
162
163 static struct syscore_ops mst_irq_syscore_ops = {
164         .suspend        = mst_irq_suspend,
165         .resume         = mst_irq_resume,
166 };
167
168 static int __init mst_irq_pm_init(void)
169 {
170         register_syscore_ops(&mst_irq_syscore_ops);
171         return 0;
172 }
173 late_initcall(mst_irq_pm_init);
174 #endif
175
176 static int mst_intc_domain_translate(struct irq_domain *d,
177                                      struct irq_fwspec *fwspec,
178                                      unsigned long *hwirq,
179                                      unsigned int *type)
180 {
181         struct mst_intc_chip_data *cd = d->host_data;
182
183         if (is_of_node(fwspec->fwnode)) {
184                 if (fwspec->param_count != 3)
185                         return -EINVAL;
186
187                 /* No PPI should point to this domain */
188                 if (fwspec->param[0] != 0)
189                         return -EINVAL;
190
191                 if (fwspec->param[1] >= cd->nr_irqs)
192                         return -EINVAL;
193
194                 *hwirq = fwspec->param[1];
195                 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
196                 return 0;
197         }
198
199         return -EINVAL;
200 }
201
202 static int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq,
203                                  unsigned int nr_irqs, void *data)
204 {
205         int i;
206         irq_hw_number_t hwirq;
207         struct irq_fwspec parent_fwspec, *fwspec = data;
208         struct mst_intc_chip_data *cd = domain->host_data;
209
210         /* Not GIC compliant */
211         if (fwspec->param_count != 3)
212                 return -EINVAL;
213
214         /* No PPI should point to this domain */
215         if (fwspec->param[0])
216                 return -EINVAL;
217
218         hwirq = fwspec->param[1];
219         for (i = 0; i < nr_irqs; i++)
220                 irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
221                                               &mst_intc_chip,
222                                               domain->host_data);
223
224         parent_fwspec = *fwspec;
225         parent_fwspec.fwnode = domain->parent->fwnode;
226         parent_fwspec.param[1] = cd->irq_start + hwirq;
227
228         /*
229          * mst-intc latch the interrupt request if it's edge triggered,
230          * so the output signal to parent GIC is always level sensitive.
231          * And if the irq signal is active low, configure it to active high
232          * to meet GIC SPI spec in mst_irq_chip_set_type via REV_POLARITY bit.
233          */
234         parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
235
236         return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec);
237 }
238
239 static const struct irq_domain_ops mst_intc_domain_ops = {
240         .translate      = mst_intc_domain_translate,
241         .alloc          = mst_intc_domain_alloc,
242         .free           = irq_domain_free_irqs_common,
243 };
244
245 static int __init mst_intc_of_init(struct device_node *dn,
246                                    struct device_node *parent)
247 {
248         struct irq_domain *domain, *domain_parent;
249         struct mst_intc_chip_data *cd;
250         u32 irq_start, irq_end;
251
252         domain_parent = irq_find_host(parent);
253         if (!domain_parent) {
254                 pr_err("mst-intc: interrupt-parent not found\n");
255                 return -EINVAL;
256         }
257
258         if (of_property_read_u32_index(dn, "mstar,irqs-map-range", 0, &irq_start) ||
259             of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end))
260                 return -EINVAL;
261
262         cd = kzalloc(sizeof(*cd), GFP_KERNEL);
263         if (!cd)
264                 return -ENOMEM;
265
266         cd->base = of_iomap(dn, 0);
267         if (!cd->base) {
268                 kfree(cd);
269                 return -ENOMEM;
270         }
271
272         cd->no_eoi = of_property_read_bool(dn, "mstar,intc-no-eoi");
273         raw_spin_lock_init(&cd->lock);
274         cd->irq_start = irq_start;
275         cd->nr_irqs = irq_end - irq_start + 1;
276         domain = irq_domain_add_hierarchy(domain_parent, 0, cd->nr_irqs, dn,
277                                           &mst_intc_domain_ops, cd);
278         if (!domain) {
279                 iounmap(cd->base);
280                 kfree(cd);
281                 return -ENOMEM;
282         }
283
284 #ifdef CONFIG_PM_SLEEP
285         INIT_LIST_HEAD(&cd->entry);
286         list_add_tail(&cd->entry, &mst_intc_list);
287 #endif
288         return 0;
289 }
290
291 IRQCHIP_DECLARE(mst_intc, "mstar,mst-intc", mst_intc_of_init);