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