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