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