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