Merge tag '5.15-rc-ksmbd-part2' of git://git.samba.org/ksmbd
[linux-2.6-microblaze.git] / drivers / irqchip / irq-crossbar.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  *  drivers/irqchip/irq-crossbar.c
4  *
5  *  Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
6  *  Author: Sricharan R <r.sricharan@ti.com>
7  */
8 #include <linux/err.h>
9 #include <linux/io.h>
10 #include <linux/irqchip.h>
11 #include <linux/irqdomain.h>
12 #include <linux/of_address.h>
13 #include <linux/of_irq.h>
14 #include <linux/slab.h>
15
16 #define IRQ_FREE        -1
17 #define IRQ_RESERVED    -2
18 #define IRQ_SKIP        -3
19 #define GIC_IRQ_START   32
20
21 /**
22  * struct crossbar_device - crossbar device description
23  * @lock: spinlock serializing access to @irq_map
24  * @int_max: maximum number of supported interrupts
25  * @safe_map: safe default value to initialize the crossbar
26  * @max_crossbar_sources: Maximum number of crossbar sources
27  * @irq_map: array of interrupts to crossbar number mapping
28  * @crossbar_base: crossbar base address
29  * @register_offsets: offsets for each irq number
30  * @write: register write function pointer
31  */
32 struct crossbar_device {
33         raw_spinlock_t lock;
34         uint int_max;
35         uint safe_map;
36         uint max_crossbar_sources;
37         uint *irq_map;
38         void __iomem *crossbar_base;
39         int *register_offsets;
40         void (*write)(int, int);
41 };
42
43 static struct crossbar_device *cb;
44
45 static void crossbar_writel(int irq_no, int cb_no)
46 {
47         writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
48 }
49
50 static void crossbar_writew(int irq_no, int cb_no)
51 {
52         writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
53 }
54
55 static void crossbar_writeb(int irq_no, int cb_no)
56 {
57         writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
58 }
59
60 static struct irq_chip crossbar_chip = {
61         .name                   = "CBAR",
62         .irq_eoi                = irq_chip_eoi_parent,
63         .irq_mask               = irq_chip_mask_parent,
64         .irq_unmask             = irq_chip_unmask_parent,
65         .irq_retrigger          = irq_chip_retrigger_hierarchy,
66         .irq_set_type           = irq_chip_set_type_parent,
67         .flags                  = IRQCHIP_MASK_ON_SUSPEND |
68                                   IRQCHIP_SKIP_SET_WAKE,
69 #ifdef CONFIG_SMP
70         .irq_set_affinity       = irq_chip_set_affinity_parent,
71 #endif
72 };
73
74 static int allocate_gic_irq(struct irq_domain *domain, unsigned virq,
75                             irq_hw_number_t hwirq)
76 {
77         struct irq_fwspec fwspec;
78         int i;
79         int err;
80
81         if (!irq_domain_get_of_node(domain->parent))
82                 return -EINVAL;
83
84         raw_spin_lock(&cb->lock);
85         for (i = cb->int_max - 1; i >= 0; i--) {
86                 if (cb->irq_map[i] == IRQ_FREE) {
87                         cb->irq_map[i] = hwirq;
88                         break;
89                 }
90         }
91         raw_spin_unlock(&cb->lock);
92
93         if (i < 0)
94                 return -ENODEV;
95
96         fwspec.fwnode = domain->parent->fwnode;
97         fwspec.param_count = 3;
98         fwspec.param[0] = 0;    /* SPI */
99         fwspec.param[1] = i;
100         fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
101
102         err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
103         if (err)
104                 cb->irq_map[i] = IRQ_FREE;
105         else
106                 cb->write(i, hwirq);
107
108         return err;
109 }
110
111 static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq,
112                                  unsigned int nr_irqs, void *data)
113 {
114         struct irq_fwspec *fwspec = data;
115         irq_hw_number_t hwirq;
116         int i;
117
118         if (fwspec->param_count != 3)
119                 return -EINVAL; /* Not GIC compliant */
120         if (fwspec->param[0] != 0)
121                 return -EINVAL; /* No PPI should point to this domain */
122
123         hwirq = fwspec->param[1];
124         if ((hwirq + nr_irqs) > cb->max_crossbar_sources)
125                 return -EINVAL; /* Can't deal with this */
126
127         for (i = 0; i < nr_irqs; i++) {
128                 int err = allocate_gic_irq(d, virq + i, hwirq + i);
129
130                 if (err)
131                         return err;
132
133                 irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i,
134                                               &crossbar_chip, NULL);
135         }
136
137         return 0;
138 }
139
140 /**
141  * crossbar_domain_free - unmap/free a crossbar<->irq connection
142  * @domain: domain of irq to unmap
143  * @virq: virq number
144  * @nr_irqs: number of irqs to free
145  *
146  * We do not maintain a use count of total number of map/unmap
147  * calls for a particular irq to find out if a irq can be really
148  * unmapped. This is because unmap is called during irq_dispose_mapping(irq),
149  * after which irq is anyways unusable. So an explicit map has to be called
150  * after that.
151  */
152 static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq,
153                                  unsigned int nr_irqs)
154 {
155         int i;
156
157         raw_spin_lock(&cb->lock);
158         for (i = 0; i < nr_irqs; i++) {
159                 struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
160
161                 irq_domain_reset_irq_data(d);
162                 cb->irq_map[d->hwirq] = IRQ_FREE;
163                 cb->write(d->hwirq, cb->safe_map);
164         }
165         raw_spin_unlock(&cb->lock);
166 }
167
168 static int crossbar_domain_translate(struct irq_domain *d,
169                                      struct irq_fwspec *fwspec,
170                                      unsigned long *hwirq,
171                                      unsigned int *type)
172 {
173         if (is_of_node(fwspec->fwnode)) {
174                 if (fwspec->param_count != 3)
175                         return -EINVAL;
176
177                 /* No PPI should point to this domain */
178                 if (fwspec->param[0] != 0)
179                         return -EINVAL;
180
181                 *hwirq = fwspec->param[1];
182                 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
183                 return 0;
184         }
185
186         return -EINVAL;
187 }
188
189 static const struct irq_domain_ops crossbar_domain_ops = {
190         .alloc          = crossbar_domain_alloc,
191         .free           = crossbar_domain_free,
192         .translate      = crossbar_domain_translate,
193 };
194
195 static int __init crossbar_of_init(struct device_node *node)
196 {
197         u32 max = 0, entry, reg_size;
198         int i, size, reserved = 0;
199         const __be32 *irqsr;
200         int ret = -ENOMEM;
201
202         cb = kzalloc(sizeof(*cb), GFP_KERNEL);
203
204         if (!cb)
205                 return ret;
206
207         cb->crossbar_base = of_iomap(node, 0);
208         if (!cb->crossbar_base)
209                 goto err_cb;
210
211         of_property_read_u32(node, "ti,max-crossbar-sources",
212                              &cb->max_crossbar_sources);
213         if (!cb->max_crossbar_sources) {
214                 pr_err("missing 'ti,max-crossbar-sources' property\n");
215                 ret = -EINVAL;
216                 goto err_base;
217         }
218
219         of_property_read_u32(node, "ti,max-irqs", &max);
220         if (!max) {
221                 pr_err("missing 'ti,max-irqs' property\n");
222                 ret = -EINVAL;
223                 goto err_base;
224         }
225         cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
226         if (!cb->irq_map)
227                 goto err_base;
228
229         cb->int_max = max;
230
231         for (i = 0; i < max; i++)
232                 cb->irq_map[i] = IRQ_FREE;
233
234         /* Get and mark reserved irqs */
235         irqsr = of_get_property(node, "ti,irqs-reserved", &size);
236         if (irqsr) {
237                 size /= sizeof(__be32);
238
239                 for (i = 0; i < size; i++) {
240                         of_property_read_u32_index(node,
241                                                    "ti,irqs-reserved",
242                                                    i, &entry);
243                         if (entry >= max) {
244                                 pr_err("Invalid reserved entry\n");
245                                 ret = -EINVAL;
246                                 goto err_irq_map;
247                         }
248                         cb->irq_map[entry] = IRQ_RESERVED;
249                 }
250         }
251
252         /* Skip irqs hardwired to bypass the crossbar */
253         irqsr = of_get_property(node, "ti,irqs-skip", &size);
254         if (irqsr) {
255                 size /= sizeof(__be32);
256
257                 for (i = 0; i < size; i++) {
258                         of_property_read_u32_index(node,
259                                                    "ti,irqs-skip",
260                                                    i, &entry);
261                         if (entry >= max) {
262                                 pr_err("Invalid skip entry\n");
263                                 ret = -EINVAL;
264                                 goto err_irq_map;
265                         }
266                         cb->irq_map[entry] = IRQ_SKIP;
267                 }
268         }
269
270
271         cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
272         if (!cb->register_offsets)
273                 goto err_irq_map;
274
275         of_property_read_u32(node, "ti,reg-size", &reg_size);
276
277         switch (reg_size) {
278         case 1:
279                 cb->write = crossbar_writeb;
280                 break;
281         case 2:
282                 cb->write = crossbar_writew;
283                 break;
284         case 4:
285                 cb->write = crossbar_writel;
286                 break;
287         default:
288                 pr_err("Invalid reg-size property\n");
289                 ret = -EINVAL;
290                 goto err_reg_offset;
291                 break;
292         }
293
294         /*
295          * Register offsets are not linear because of the
296          * reserved irqs. so find and store the offsets once.
297          */
298         for (i = 0; i < max; i++) {
299                 if (cb->irq_map[i] == IRQ_RESERVED)
300                         continue;
301
302                 cb->register_offsets[i] = reserved;
303                 reserved += reg_size;
304         }
305
306         of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
307         /* Initialize the crossbar with safe map to start with */
308         for (i = 0; i < max; i++) {
309                 if (cb->irq_map[i] == IRQ_RESERVED ||
310                     cb->irq_map[i] == IRQ_SKIP)
311                         continue;
312
313                 cb->write(i, cb->safe_map);
314         }
315
316         raw_spin_lock_init(&cb->lock);
317
318         return 0;
319
320 err_reg_offset:
321         kfree(cb->register_offsets);
322 err_irq_map:
323         kfree(cb->irq_map);
324 err_base:
325         iounmap(cb->crossbar_base);
326 err_cb:
327         kfree(cb);
328
329         cb = NULL;
330         return ret;
331 }
332
333 static int __init irqcrossbar_init(struct device_node *node,
334                                    struct device_node *parent)
335 {
336         struct irq_domain *parent_domain, *domain;
337         int err;
338
339         if (!parent) {
340                 pr_err("%pOF: no parent, giving up\n", node);
341                 return -ENODEV;
342         }
343
344         parent_domain = irq_find_host(parent);
345         if (!parent_domain) {
346                 pr_err("%pOF: unable to obtain parent domain\n", node);
347                 return -ENXIO;
348         }
349
350         err = crossbar_of_init(node);
351         if (err)
352                 return err;
353
354         domain = irq_domain_add_hierarchy(parent_domain, 0,
355                                           cb->max_crossbar_sources,
356                                           node, &crossbar_domain_ops,
357                                           NULL);
358         if (!domain) {
359                 pr_err("%pOF: failed to allocated domain\n", node);
360                 return -ENOMEM;
361         }
362
363         return 0;
364 }
365
366 IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);