aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/irqchip/irq-crossbar.c
diff options
context:
space:
mode:
authorSricharan R <r.sricharan@ti.com>2013-12-03 05:27:23 -0500
committerSricharan R <r.sricharan@ti.com>2014-02-05 09:38:34 -0500
commit96ca848ef7ea1be7e92d1cceb34ef3aa86053828 (patch)
treee57b452cb1a6f383f5c9dcc346bf53e062ced0da /drivers/irqchip/irq-crossbar.c
parent006e983bbc805431c44e2135e13841f66059a045 (diff)
DRIVERS: IRQCHIP: CROSSBAR: Add support for Crossbar IP
Some socs have a large number of interrupts requests to service the needs of its many peripherals and subsystems. All of the interrupt lines from the subsystems are not needed at the same time, so they have to be muxed to the irq-controller appropriately. In such places a interrupt controllers are preceded by an CROSSBAR that provides flexibility in muxing the device requests to the controller inputs. This driver takes care a allocating a free irq and then configuring the crossbar IP as a part of the mpu's irqchip callbacks. crossbar_init should be called right before the irqchip_init, so that it is setup to handle the irqchip callbacks. Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Linus Walleij <linus.walleij@linaro.org> Cc: Santosh Shilimkar <santosh.shilimkar@ti.com> Cc: Russell King <linux@arm.linux.org.uk> Cc: Tony Lindgren <tony@atomide.com> Cc: Rajendra Nayak <rnayak@ti.com> Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: Grant Likely <grant.likely@linaro.org> Cc: Rob Herring <rob.herring@calxeda.com> Signed-off-by: Sricharan R <r.sricharan@ti.com> Acked-by: Kumar Gala <galak@codeaurora.org> (for DT binding portion) Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com> Acked-by: Linus Walleij <linus.walleij@linaro.org> Acked-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'drivers/irqchip/irq-crossbar.c')
-rw-r--r--drivers/irqchip/irq-crossbar.c208
1 files changed, 208 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-crossbar.c b/drivers/irqchip/irq-crossbar.c
new file mode 100644
index 000000000000..fc817d28d1fe
--- /dev/null
+++ b/drivers/irqchip/irq-crossbar.c
@@ -0,0 +1,208 @@
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 GIC_IRQ_START 32
21
22/*
23 * @int_max: maximum number of supported interrupts
24 * @irq_map: array of interrupts to crossbar number mapping
25 * @crossbar_base: crossbar base address
26 * @register_offsets: offsets for each irq number
27 */
28struct crossbar_device {
29 uint int_max;
30 uint *irq_map;
31 void __iomem *crossbar_base;
32 int *register_offsets;
33 void (*write) (int, int);
34};
35
36static struct crossbar_device *cb;
37
38static inline void crossbar_writel(int irq_no, int cb_no)
39{
40 writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
41}
42
43static inline void crossbar_writew(int irq_no, int cb_no)
44{
45 writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
46}
47
48static inline void crossbar_writeb(int irq_no, int cb_no)
49{
50 writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
51}
52
53static inline int allocate_free_irq(int cb_no)
54{
55 int i;
56
57 for (i = 0; i < cb->int_max; i++) {
58 if (cb->irq_map[i] == IRQ_FREE) {
59 cb->irq_map[i] = cb_no;
60 return i;
61 }
62 }
63
64 return -ENODEV;
65}
66
67static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
68 irq_hw_number_t hw)
69{
70 cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
71 return 0;
72}
73
74static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
75{
76 irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
77
78 if (hw > GIC_IRQ_START)
79 cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
80}
81
82static int crossbar_domain_xlate(struct irq_domain *d,
83 struct device_node *controller,
84 const u32 *intspec, unsigned int intsize,
85 unsigned long *out_hwirq,
86 unsigned int *out_type)
87{
88 unsigned long ret;
89
90 ret = allocate_free_irq(intspec[1]);
91
92 if (IS_ERR_VALUE(ret))
93 return ret;
94
95 *out_hwirq = ret + GIC_IRQ_START;
96 return 0;
97}
98
99const struct irq_domain_ops routable_irq_domain_ops = {
100 .map = crossbar_domain_map,
101 .unmap = crossbar_domain_unmap,
102 .xlate = crossbar_domain_xlate
103};
104
105static int __init crossbar_of_init(struct device_node *node)
106{
107 int i, size, max, reserved = 0, entry;
108 const __be32 *irqsr;
109
110 cb = kzalloc(sizeof(struct cb_device *), GFP_KERNEL);
111
112 if (!cb)
113 return -ENOMEM;
114
115 cb->crossbar_base = of_iomap(node, 0);
116 if (!cb->crossbar_base)
117 goto err1;
118
119 of_property_read_u32(node, "ti,max-irqs", &max);
120 cb->irq_map = kzalloc(max * sizeof(int), GFP_KERNEL);
121 if (!cb->irq_map)
122 goto err2;
123
124 cb->int_max = max;
125
126 for (i = 0; i < max; i++)
127 cb->irq_map[i] = IRQ_FREE;
128
129 /* Get and mark reserved irqs */
130 irqsr = of_get_property(node, "ti,irqs-reserved", &size);
131 if (irqsr) {
132 size /= sizeof(__be32);
133
134 for (i = 0; i < size; i++) {
135 of_property_read_u32_index(node,
136 "ti,irqs-reserved",
137 i, &entry);
138 if (entry > max) {
139 pr_err("Invalid reserved entry\n");
140 goto err3;
141 }
142 cb->irq_map[entry] = 0;
143 }
144 }
145
146 cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL);
147 if (!cb->register_offsets)
148 goto err3;
149
150 of_property_read_u32(node, "ti,reg-size", &size);
151
152 switch (size) {
153 case 1:
154 cb->write = crossbar_writeb;
155 break;
156 case 2:
157 cb->write = crossbar_writew;
158 break;
159 case 4:
160 cb->write = crossbar_writel;
161 break;
162 default:
163 pr_err("Invalid reg-size property\n");
164 goto err4;
165 break;
166 }
167
168 /*
169 * Register offsets are not linear because of the
170 * reserved irqs. so find and store the offsets once.
171 */
172 for (i = 0; i < max; i++) {
173 if (!cb->irq_map[i])
174 continue;
175
176 cb->register_offsets[i] = reserved;
177 reserved += size;
178 }
179
180 register_routable_domain_ops(&routable_irq_domain_ops);
181 return 0;
182
183err4:
184 kfree(cb->register_offsets);
185err3:
186 kfree(cb->irq_map);
187err2:
188 iounmap(cb->crossbar_base);
189err1:
190 kfree(cb);
191 return -ENOMEM;
192}
193
194static const struct of_device_id crossbar_match[] __initconst = {
195 { .compatible = "ti,irq-crossbar" },
196 {}
197};
198
199int __init irqcrossbar_init(void)
200{
201 struct device_node *np;
202 np = of_find_matching_node(NULL, crossbar_match);
203 if (!np)
204 return -ENODEV;
205
206 crossbar_of_init(np);
207 return 0;
208}