diff options
author | Marc Zyngier <marc.zyngier@arm.com> | 2015-03-11 11:43:44 -0400 |
---|---|---|
committer | Jason Cooper <jason@lakedaemon.net> | 2015-03-14 20:55:24 -0400 |
commit | 783d31863fb826f1a3754a2d5959a022a1b12d54 (patch) | |
tree | 4610918642e934e9b3d0260a9d33bd569b89d55b /drivers/irqchip | |
parent | 08b55e2a9208e4841a17c9d9c2c454986392977d (diff) |
irqchip: crossbar: Convert dra7 crossbar to stacked domains
Support for the TI crossbar used on the DRA7 family of chips
is implemented as an ugly hack on the side of the GIC.
Converting it to stacked domains makes it slightly more
palatable, as it results in a cleanup.
Unfortunately, as the DT bindings failed to acknowledge the
fact that this is actually yet another interrupt controller
(the third, actually), we have yet another breakage. Oh well.
Acked-by: Tony Lindgren <tony@atomide.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Link: https://lkml.kernel.org/r/1426088629-15377-3-git-send-email-marc.zyngier@arm.com
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/irq-crossbar.c | 210 |
1 files changed, 123 insertions, 87 deletions
diff --git a/drivers/irqchip/irq-crossbar.c b/drivers/irqchip/irq-crossbar.c index bbbaf5de65d2..692fe2bc8197 100644 --- a/drivers/irqchip/irq-crossbar.c +++ b/drivers/irqchip/irq-crossbar.c | |||
@@ -11,11 +11,12 @@ | |||
11 | */ | 11 | */ |
12 | #include <linux/err.h> | 12 | #include <linux/err.h> |
13 | #include <linux/io.h> | 13 | #include <linux/io.h> |
14 | #include <linux/irqdomain.h> | ||
14 | #include <linux/of_address.h> | 15 | #include <linux/of_address.h> |
15 | #include <linux/of_irq.h> | 16 | #include <linux/of_irq.h> |
16 | #include <linux/slab.h> | 17 | #include <linux/slab.h> |
17 | #include <linux/irqchip/arm-gic.h> | 18 | |
18 | #include <linux/irqchip/irq-crossbar.h> | 19 | #include "irqchip.h" |
19 | 20 | ||
20 | #define IRQ_FREE -1 | 21 | #define IRQ_FREE -1 |
21 | #define IRQ_RESERVED -2 | 22 | #define IRQ_RESERVED -2 |
@@ -24,6 +25,7 @@ | |||
24 | 25 | ||
25 | /** | 26 | /** |
26 | * struct crossbar_device - crossbar device description | 27 | * struct crossbar_device - crossbar device description |
28 | * @lock: spinlock serializing access to @irq_map | ||
27 | * @int_max: maximum number of supported interrupts | 29 | * @int_max: maximum number of supported interrupts |
28 | * @safe_map: safe default value to initialize the crossbar | 30 | * @safe_map: safe default value to initialize the crossbar |
29 | * @max_crossbar_sources: Maximum number of crossbar sources | 31 | * @max_crossbar_sources: Maximum number of crossbar sources |
@@ -33,6 +35,7 @@ | |||
33 | * @write: register write function pointer | 35 | * @write: register write function pointer |
34 | */ | 36 | */ |
35 | struct crossbar_device { | 37 | struct crossbar_device { |
38 | raw_spinlock_t lock; | ||
36 | uint int_max; | 39 | uint int_max; |
37 | uint safe_map; | 40 | uint safe_map; |
38 | uint max_crossbar_sources; | 41 | uint max_crossbar_sources; |
@@ -44,72 +47,101 @@ struct crossbar_device { | |||
44 | 47 | ||
45 | static struct crossbar_device *cb; | 48 | static struct crossbar_device *cb; |
46 | 49 | ||
47 | static inline void crossbar_writel(int irq_no, int cb_no) | 50 | static void crossbar_writel(int irq_no, int cb_no) |
48 | { | 51 | { |
49 | writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); | 52 | writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); |
50 | } | 53 | } |
51 | 54 | ||
52 | static inline void crossbar_writew(int irq_no, int cb_no) | 55 | static void crossbar_writew(int irq_no, int cb_no) |
53 | { | 56 | { |
54 | writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); | 57 | writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); |
55 | } | 58 | } |
56 | 59 | ||
57 | static inline void crossbar_writeb(int irq_no, int cb_no) | 60 | static void crossbar_writeb(int irq_no, int cb_no) |
58 | { | 61 | { |
59 | writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); | 62 | writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); |
60 | } | 63 | } |
61 | 64 | ||
62 | static inline int get_prev_map_irq(int cb_no) | 65 | static struct irq_chip crossbar_chip = { |
63 | { | 66 | .name = "CBAR", |
64 | int i; | 67 | .irq_eoi = irq_chip_eoi_parent, |
65 | 68 | .irq_mask = irq_chip_mask_parent, | |
66 | for (i = cb->int_max - 1; i >= 0; i--) | 69 | .irq_unmask = irq_chip_unmask_parent, |
67 | if (cb->irq_map[i] == cb_no) | 70 | .irq_retrigger = irq_chip_retrigger_hierarchy, |
68 | return i; | 71 | .irq_set_wake = irq_chip_set_wake_parent, |
69 | 72 | #ifdef CONFIG_SMP | |
70 | return -ENODEV; | 73 | .irq_set_affinity = irq_chip_set_affinity_parent, |
71 | } | 74 | #endif |
75 | }; | ||
72 | 76 | ||
73 | static inline int allocate_free_irq(int cb_no) | 77 | static int allocate_gic_irq(struct irq_domain *domain, unsigned virq, |
78 | irq_hw_number_t hwirq) | ||
74 | { | 79 | { |
80 | struct of_phandle_args args; | ||
75 | int i; | 81 | int i; |
82 | int err; | ||
76 | 83 | ||
84 | raw_spin_lock(&cb->lock); | ||
77 | for (i = cb->int_max - 1; i >= 0; i--) { | 85 | for (i = cb->int_max - 1; i >= 0; i--) { |
78 | if (cb->irq_map[i] == IRQ_FREE) { | 86 | if (cb->irq_map[i] == IRQ_FREE) { |
79 | cb->irq_map[i] = cb_no; | 87 | cb->irq_map[i] = hwirq; |
80 | return i; | 88 | break; |
81 | } | 89 | } |
82 | } | 90 | } |
91 | raw_spin_unlock(&cb->lock); | ||
83 | 92 | ||
84 | return -ENODEV; | 93 | if (i < 0) |
85 | } | 94 | return -ENODEV; |
86 | 95 | ||
87 | static inline bool needs_crossbar_write(irq_hw_number_t hw) | 96 | args.np = domain->parent->of_node; |
88 | { | 97 | args.args_count = 3; |
89 | int cb_no; | 98 | args.args[0] = 0; /* SPI */ |
99 | args.args[1] = i; | ||
100 | args.args[2] = IRQ_TYPE_LEVEL_HIGH; | ||
90 | 101 | ||
91 | if (hw > GIC_IRQ_START) { | 102 | err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args); |
92 | cb_no = cb->irq_map[hw - GIC_IRQ_START]; | 103 | if (err) |
93 | if (cb_no != IRQ_RESERVED && cb_no != IRQ_SKIP) | 104 | cb->irq_map[i] = IRQ_FREE; |
94 | return true; | 105 | else |
95 | } | 106 | cb->write(i, hwirq); |
96 | 107 | ||
97 | return false; | 108 | return err; |
98 | } | 109 | } |
99 | 110 | ||
100 | static int crossbar_domain_map(struct irq_domain *d, unsigned int irq, | 111 | static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq, |
101 | irq_hw_number_t hw) | 112 | unsigned int nr_irqs, void *data) |
102 | { | 113 | { |
103 | if (needs_crossbar_write(hw)) | 114 | struct of_phandle_args *args = data; |
104 | cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]); | 115 | irq_hw_number_t hwirq; |
116 | int i; | ||
117 | |||
118 | if (args->args_count != 3) | ||
119 | return -EINVAL; /* Not GIC compliant */ | ||
120 | if (args->args[0] != 0) | ||
121 | return -EINVAL; /* No PPI should point to this domain */ | ||
122 | |||
123 | hwirq = args->args[1]; | ||
124 | if ((hwirq + nr_irqs) > cb->max_crossbar_sources) | ||
125 | return -EINVAL; /* Can't deal with this */ | ||
126 | |||
127 | for (i = 0; i < nr_irqs; i++) { | ||
128 | int err = allocate_gic_irq(d, virq + i, hwirq + i); | ||
129 | |||
130 | if (err) | ||
131 | return err; | ||
132 | |||
133 | irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i, | ||
134 | &crossbar_chip, NULL); | ||
135 | } | ||
105 | 136 | ||
106 | return 0; | 137 | return 0; |
107 | } | 138 | } |
108 | 139 | ||
109 | /** | 140 | /** |
110 | * crossbar_domain_unmap - unmap a crossbar<->irq connection | 141 | * crossbar_domain_free - unmap/free a crossbar<->irq connection |
111 | * @d: domain of irq to unmap | 142 | * @domain: domain of irq to unmap |
112 | * @irq: virq number | 143 | * @virq: virq number |
144 | * @nr_irqs: number of irqs to free | ||
113 | * | 145 | * |
114 | * We do not maintain a use count of total number of map/unmap | 146 | * We do not maintain a use count of total number of map/unmap |
115 | * calls for a particular irq to find out if a irq can be really | 147 | * calls for a particular irq to find out if a irq can be really |
@@ -117,14 +149,20 @@ static int crossbar_domain_map(struct irq_domain *d, unsigned int irq, | |||
117 | * after which irq is anyways unusable. So an explicit map has to be called | 149 | * after which irq is anyways unusable. So an explicit map has to be called |
118 | * after that. | 150 | * after that. |
119 | */ | 151 | */ |
120 | static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq) | 152 | static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq, |
153 | unsigned int nr_irqs) | ||
121 | { | 154 | { |
122 | irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq; | 155 | int i; |
123 | 156 | ||
124 | if (needs_crossbar_write(hw)) { | 157 | raw_spin_lock(&cb->lock); |
125 | cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE; | 158 | for (i = 0; i < nr_irqs; i++) { |
126 | cb->write(hw - GIC_IRQ_START, cb->safe_map); | 159 | struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); |
160 | |||
161 | irq_domain_reset_irq_data(d); | ||
162 | cb->irq_map[d->hwirq] = IRQ_FREE; | ||
163 | cb->write(d->hwirq, cb->safe_map); | ||
127 | } | 164 | } |
165 | raw_spin_unlock(&cb->lock); | ||
128 | } | 166 | } |
129 | 167 | ||
130 | static int crossbar_domain_xlate(struct irq_domain *d, | 168 | static int crossbar_domain_xlate(struct irq_domain *d, |
@@ -133,44 +171,22 @@ static int crossbar_domain_xlate(struct irq_domain *d, | |||
133 | unsigned long *out_hwirq, | 171 | unsigned long *out_hwirq, |
134 | unsigned int *out_type) | 172 | unsigned int *out_type) |
135 | { | 173 | { |
136 | int ret; | 174 | if (d->of_node != controller) |
137 | int req_num = intspec[1]; | 175 | return -EINVAL; /* Shouldn't happen, really... */ |
138 | int direct_map_num; | 176 | if (intsize != 3) |
139 | 177 | return -EINVAL; /* Not GIC compliant */ | |
140 | if (req_num >= cb->max_crossbar_sources) { | 178 | if (intspec[0] != 0) |
141 | direct_map_num = req_num - cb->max_crossbar_sources; | 179 | return -EINVAL; /* No PPI should point to this domain */ |
142 | if (direct_map_num < cb->int_max) { | 180 | |
143 | ret = cb->irq_map[direct_map_num]; | 181 | *out_hwirq = intspec[1]; |
144 | if (ret == IRQ_RESERVED || ret == IRQ_SKIP) { | 182 | *out_type = intspec[2]; |
145 | /* We use the interrupt num as h/w irq num */ | ||
146 | ret = direct_map_num; | ||
147 | goto found; | ||
148 | } | ||
149 | } | ||
150 | |||
151 | pr_err("%s: requested crossbar number %d > max %d\n", | ||
152 | __func__, req_num, cb->max_crossbar_sources); | ||
153 | return -EINVAL; | ||
154 | } | ||
155 | |||
156 | ret = get_prev_map_irq(req_num); | ||
157 | if (ret >= 0) | ||
158 | goto found; | ||
159 | |||
160 | ret = allocate_free_irq(req_num); | ||
161 | |||
162 | if (ret < 0) | ||
163 | return ret; | ||
164 | |||
165 | found: | ||
166 | *out_hwirq = ret + GIC_IRQ_START; | ||
167 | return 0; | 183 | return 0; |
168 | } | 184 | } |
169 | 185 | ||
170 | static const struct irq_domain_ops routable_irq_domain_ops = { | 186 | static const struct irq_domain_ops crossbar_domain_ops = { |
171 | .map = crossbar_domain_map, | 187 | .alloc = crossbar_domain_alloc, |
172 | .unmap = crossbar_domain_unmap, | 188 | .free = crossbar_domain_free, |
173 | .xlate = crossbar_domain_xlate | 189 | .xlate = crossbar_domain_xlate, |
174 | }; | 190 | }; |
175 | 191 | ||
176 | static int __init crossbar_of_init(struct device_node *node) | 192 | static int __init crossbar_of_init(struct device_node *node) |
@@ -293,7 +309,8 @@ static int __init crossbar_of_init(struct device_node *node) | |||
293 | cb->write(i, cb->safe_map); | 309 | cb->write(i, cb->safe_map); |
294 | } | 310 | } |
295 | 311 | ||
296 | register_routable_domain_ops(&routable_irq_domain_ops); | 312 | raw_spin_lock_init(&cb->lock); |
313 | |||
297 | return 0; | 314 | return 0; |
298 | 315 | ||
299 | err_reg_offset: | 316 | err_reg_offset: |
@@ -309,18 +326,37 @@ err_cb: | |||
309 | return ret; | 326 | return ret; |
310 | } | 327 | } |
311 | 328 | ||
312 | static const struct of_device_id crossbar_match[] __initconst = { | 329 | static int __init irqcrossbar_init(struct device_node *node, |
313 | { .compatible = "ti,irq-crossbar" }, | 330 | struct device_node *parent) |
314 | {} | ||
315 | }; | ||
316 | |||
317 | int __init irqcrossbar_init(void) | ||
318 | { | 331 | { |
319 | struct device_node *np; | 332 | struct irq_domain *parent_domain, *domain; |
320 | np = of_find_matching_node(NULL, crossbar_match); | 333 | int err; |
321 | if (!np) | 334 | |
335 | if (!parent) { | ||
336 | pr_err("%s: no parent, giving up\n", node->full_name); | ||
322 | return -ENODEV; | 337 | return -ENODEV; |
338 | } | ||
339 | |||
340 | parent_domain = irq_find_host(parent); | ||
341 | if (!parent_domain) { | ||
342 | pr_err("%s: unable to obtain parent domain\n", node->full_name); | ||
343 | return -ENXIO; | ||
344 | } | ||
345 | |||
346 | err = crossbar_of_init(node); | ||
347 | if (err) | ||
348 | return err; | ||
349 | |||
350 | domain = irq_domain_add_hierarchy(parent_domain, 0, | ||
351 | cb->max_crossbar_sources, | ||
352 | node, &crossbar_domain_ops, | ||
353 | NULL); | ||
354 | if (!domain) { | ||
355 | pr_err("%s: failed to allocated domain\n", node->full_name); | ||
356 | return -ENOMEM; | ||
357 | } | ||
323 | 358 | ||
324 | crossbar_of_init(np); | ||
325 | return 0; | 359 | return 0; |
326 | } | 360 | } |
361 | |||
362 | IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init); | ||