]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/irqchip/irq-crossbar.c
irqchip: crossbar: Return proper error value
[karo-tx-linux.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/of_address.h>
15 #include <linux/of_irq.h>
16 #include <linux/slab.h>
17 #include <linux/irqchip/arm-gic.h>
18 #include <linux/irqchip/irq-crossbar.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  * @int_max: maximum number of supported interrupts
28  * @safe_map: safe default value to initialize the crossbar
29  * @irq_map: array of interrupts to crossbar number mapping
30  * @crossbar_base: crossbar base address
31  * @register_offsets: offsets for each irq number
32  * @write: register write function pointer
33  */
34 struct crossbar_device {
35         uint int_max;
36         uint safe_map;
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 inline 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 inline 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 inline 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 inline int get_prev_map_irq(int cb_no)
61 {
62         int i;
63
64         for (i = cb->int_max - 1; i >= 0; i--)
65                 if (cb->irq_map[i] == cb_no)
66                         return i;
67
68         return -ENODEV;
69 }
70
71 static inline int allocate_free_irq(int cb_no)
72 {
73         int i;
74
75         for (i = cb->int_max - 1; i >= 0; i--) {
76                 if (cb->irq_map[i] == IRQ_FREE) {
77                         cb->irq_map[i] = cb_no;
78                         return i;
79                 }
80         }
81
82         return -ENODEV;
83 }
84
85 static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
86                                irq_hw_number_t hw)
87 {
88         cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
89         return 0;
90 }
91
92 static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
93 {
94         irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
95
96         if (hw > GIC_IRQ_START) {
97                 cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
98                 cb->write(hw - GIC_IRQ_START, cb->safe_map);
99         }
100 }
101
102 static int crossbar_domain_xlate(struct irq_domain *d,
103                                  struct device_node *controller,
104                                  const u32 *intspec, unsigned int intsize,
105                                  unsigned long *out_hwirq,
106                                  unsigned int *out_type)
107 {
108         int ret;
109
110         ret = get_prev_map_irq(intspec[1]);
111         if (ret >= 0)
112                 goto found;
113
114         ret = allocate_free_irq(intspec[1]);
115
116         if (ret < 0)
117                 return ret;
118
119 found:
120         *out_hwirq = ret + GIC_IRQ_START;
121         return 0;
122 }
123
124 static const struct irq_domain_ops routable_irq_domain_ops = {
125         .map = crossbar_domain_map,
126         .unmap = crossbar_domain_unmap,
127         .xlate = crossbar_domain_xlate
128 };
129
130 static int __init crossbar_of_init(struct device_node *node)
131 {
132         int i, size, max = 0, reserved = 0, entry;
133         const __be32 *irqsr;
134         int ret = -ENOMEM;
135
136         cb = kzalloc(sizeof(*cb), GFP_KERNEL);
137
138         if (!cb)
139                 return ret;
140
141         cb->crossbar_base = of_iomap(node, 0);
142         if (!cb->crossbar_base)
143                 goto err1;
144
145         of_property_read_u32(node, "ti,max-irqs", &max);
146         if (!max) {
147                 pr_err("missing 'ti,max-irqs' property\n");
148                 ret = -EINVAL;
149                 goto err2;
150         }
151         cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
152         if (!cb->irq_map)
153                 goto err2;
154
155         cb->int_max = max;
156
157         for (i = 0; i < max; i++)
158                 cb->irq_map[i] = IRQ_FREE;
159
160         /* Get and mark reserved irqs */
161         irqsr = of_get_property(node, "ti,irqs-reserved", &size);
162         if (irqsr) {
163                 size /= sizeof(__be32);
164
165                 for (i = 0; i < size; i++) {
166                         of_property_read_u32_index(node,
167                                                    "ti,irqs-reserved",
168                                                    i, &entry);
169                         if (entry > max) {
170                                 pr_err("Invalid reserved entry\n");
171                                 ret = -EINVAL;
172                                 goto err3;
173                         }
174                         cb->irq_map[entry] = IRQ_RESERVED;
175                 }
176         }
177
178         /* Skip irqs hardwired to bypass the crossbar */
179         irqsr = of_get_property(node, "ti,irqs-skip", &size);
180         if (irqsr) {
181                 size /= sizeof(__be32);
182
183                 for (i = 0; i < size; i++) {
184                         of_property_read_u32_index(node,
185                                                    "ti,irqs-skip",
186                                                    i, &entry);
187                         if (entry > max) {
188                                 pr_err("Invalid skip entry\n");
189                                 ret = -EINVAL;
190                                 goto err3;
191                         }
192                         cb->irq_map[entry] = IRQ_SKIP;
193                 }
194         }
195
196
197         cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
198         if (!cb->register_offsets)
199                 goto err3;
200
201         of_property_read_u32(node, "ti,reg-size", &size);
202
203         switch (size) {
204         case 1:
205                 cb->write = crossbar_writeb;
206                 break;
207         case 2:
208                 cb->write = crossbar_writew;
209                 break;
210         case 4:
211                 cb->write = crossbar_writel;
212                 break;
213         default:
214                 pr_err("Invalid reg-size property\n");
215                 ret = -EINVAL;
216                 goto err4;
217                 break;
218         }
219
220         /*
221          * Register offsets are not linear because of the
222          * reserved irqs. so find and store the offsets once.
223          */
224         for (i = 0; i < max; i++) {
225                 if (cb->irq_map[i] == IRQ_RESERVED)
226                         continue;
227
228                 cb->register_offsets[i] = reserved;
229                 reserved += size;
230         }
231
232         of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
233
234         /* Initialize the crossbar with safe map to start with */
235         for (i = 0; i < max; i++) {
236                 if (cb->irq_map[i] == IRQ_RESERVED ||
237                     cb->irq_map[i] == IRQ_SKIP)
238                         continue;
239
240                 cb->write(i, cb->safe_map);
241         }
242
243         register_routable_domain_ops(&routable_irq_domain_ops);
244         return 0;
245
246 err4:
247         kfree(cb->register_offsets);
248 err3:
249         kfree(cb->irq_map);
250 err2:
251         iounmap(cb->crossbar_base);
252 err1:
253         kfree(cb);
254         return ret;
255 }
256
257 static const struct of_device_id crossbar_match[] __initconst = {
258         { .compatible = "ti,irq-crossbar" },
259         {}
260 };
261
262 int __init irqcrossbar_init(void)
263 {
264         struct device_node *np;
265         np = of_find_matching_node(NULL, crossbar_match);
266         if (!np)
267                 return -ENODEV;
268
269         crossbar_of_init(np);
270         return 0;
271 }