blob: 83f803bfab768d32843b7ff5709996f304de69e4 [file] [log] [blame]
Sricharan R96ca8482013-12-03 15:57:23 +05301/*
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>
Nishanth Menon4dbf45e2014-06-26 12:40:25 +053018#include <linux/irqchip/irq-crossbar.h>
Sricharan R96ca8482013-12-03 15:57:23 +053019
20#define IRQ_FREE -1
Nishanth Menon1d50d2c2014-06-26 12:40:19 +053021#define IRQ_RESERVED -2
Nishanth Menon64e0f8b2014-06-26 12:40:21 +053022#define IRQ_SKIP -3
Sricharan R96ca8482013-12-03 15:57:23 +053023#define GIC_IRQ_START 32
24
Nishanth Menone30ef8a2014-06-26 12:40:26 +053025/**
26 * struct crossbar_device - crossbar device description
Sricharan R96ca8482013-12-03 15:57:23 +053027 * @int_max: maximum number of supported interrupts
Nishanth Menona35057d2014-06-26 12:40:22 +053028 * @safe_map: safe default value to initialize the crossbar
Nishanth Menon2f7d2fb2014-06-26 12:40:31 +053029 * @max_crossbar_sources: Maximum number of crossbar sources
Sricharan R96ca8482013-12-03 15:57:23 +053030 * @irq_map: array of interrupts to crossbar number mapping
31 * @crossbar_base: crossbar base address
32 * @register_offsets: offsets for each irq number
Nishanth Menone30ef8a2014-06-26 12:40:26 +053033 * @write: register write function pointer
Sricharan R96ca8482013-12-03 15:57:23 +053034 */
35struct crossbar_device {
36 uint int_max;
Nishanth Menona35057d2014-06-26 12:40:22 +053037 uint safe_map;
Nishanth Menon2f7d2fb2014-06-26 12:40:31 +053038 uint max_crossbar_sources;
Sricharan R96ca8482013-12-03 15:57:23 +053039 uint *irq_map;
40 void __iomem *crossbar_base;
41 int *register_offsets;
Nishanth Menona35057d2014-06-26 12:40:22 +053042 void (*write)(int, int);
Sricharan R96ca8482013-12-03 15:57:23 +053043};
44
45static struct crossbar_device *cb;
46
47static inline void crossbar_writel(int irq_no, int cb_no)
48{
49 writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
50}
51
52static inline void crossbar_writew(int irq_no, int cb_no)
53{
54 writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
55}
56
57static inline void crossbar_writeb(int irq_no, int cb_no)
58{
59 writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
60}
61
Nishanth Menon6f16fc82014-06-26 12:40:20 +053062static inline int get_prev_map_irq(int cb_no)
63{
64 int i;
65
Nishanth Menonddee0fb2014-06-26 12:40:23 +053066 for (i = cb->int_max - 1; i >= 0; i--)
Nishanth Menon6f16fc82014-06-26 12:40:20 +053067 if (cb->irq_map[i] == cb_no)
68 return i;
69
70 return -ENODEV;
71}
72
Sricharan R96ca8482013-12-03 15:57:23 +053073static inline int allocate_free_irq(int cb_no)
74{
75 int i;
76
Nishanth Menonddee0fb2014-06-26 12:40:23 +053077 for (i = cb->int_max - 1; i >= 0; i--) {
Sricharan R96ca8482013-12-03 15:57:23 +053078 if (cb->irq_map[i] == IRQ_FREE) {
79 cb->irq_map[i] = cb_no;
80 return i;
81 }
82 }
83
84 return -ENODEV;
85}
86
Nishanth Menon29918b62014-06-26 12:40:32 +053087static inline bool needs_crossbar_write(irq_hw_number_t hw)
88{
89 if (hw > GIC_IRQ_START)
90 return true;
91
92 return false;
93}
94
Sricharan R96ca8482013-12-03 15:57:23 +053095static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
96 irq_hw_number_t hw)
97{
Nishanth Menon29918b62014-06-26 12:40:32 +053098 if (needs_crossbar_write(hw))
99 cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
100
Sricharan R96ca8482013-12-03 15:57:23 +0530101 return 0;
102}
103
Sricharan R8b09a452014-06-26 12:40:30 +0530104/**
105 * crossbar_domain_unmap - unmap a crossbar<->irq connection
106 * @d: domain of irq to unmap
107 * @irq: virq number
108 *
109 * We do not maintain a use count of total number of map/unmap
110 * calls for a particular irq to find out if a irq can be really
111 * unmapped. This is because unmap is called during irq_dispose_mapping(irq),
112 * after which irq is anyways unusable. So an explicit map has to be called
113 * after that.
114 */
Sricharan R96ca8482013-12-03 15:57:23 +0530115static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
116{
117 irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
118
Nishanth Menon29918b62014-06-26 12:40:32 +0530119 if (needs_crossbar_write(hw)) {
Sricharan R96ca8482013-12-03 15:57:23 +0530120 cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
Nishanth Menona35057d2014-06-26 12:40:22 +0530121 cb->write(hw - GIC_IRQ_START, cb->safe_map);
122 }
Sricharan R96ca8482013-12-03 15:57:23 +0530123}
124
125static int crossbar_domain_xlate(struct irq_domain *d,
126 struct device_node *controller,
127 const u32 *intspec, unsigned int intsize,
128 unsigned long *out_hwirq,
129 unsigned int *out_type)
130{
Nishanth Menond4922a92014-06-26 12:40:24 +0530131 int ret;
Nishanth Menon2f7d2fb2014-06-26 12:40:31 +0530132 int req_num = intspec[1];
Sricharan R96ca8482013-12-03 15:57:23 +0530133
Nishanth Menon2f7d2fb2014-06-26 12:40:31 +0530134 if (req_num >= cb->max_crossbar_sources) {
135 pr_err("%s: requested crossbar number %d > max %d\n",
136 __func__, req_num, cb->max_crossbar_sources);
137 return -EINVAL;
138 }
139
140 ret = get_prev_map_irq(req_num);
Nishanth Menond4922a92014-06-26 12:40:24 +0530141 if (ret >= 0)
Nishanth Menon6f16fc82014-06-26 12:40:20 +0530142 goto found;
143
Nishanth Menon2f7d2fb2014-06-26 12:40:31 +0530144 ret = allocate_free_irq(req_num);
Sricharan R96ca8482013-12-03 15:57:23 +0530145
Nishanth Menond4922a92014-06-26 12:40:24 +0530146 if (ret < 0)
Sricharan R96ca8482013-12-03 15:57:23 +0530147 return ret;
148
Nishanth Menon6f16fc82014-06-26 12:40:20 +0530149found:
Sricharan R96ca8482013-12-03 15:57:23 +0530150 *out_hwirq = ret + GIC_IRQ_START;
151 return 0;
152}
153
Nishanth Menon4dbf45e2014-06-26 12:40:25 +0530154static const struct irq_domain_ops routable_irq_domain_ops = {
Sricharan R96ca8482013-12-03 15:57:23 +0530155 .map = crossbar_domain_map,
156 .unmap = crossbar_domain_unmap,
157 .xlate = crossbar_domain_xlate
158};
159
160static int __init crossbar_of_init(struct device_node *node)
161{
Nishanth Menonedb442d2014-06-26 12:40:27 +0530162 int i, size, max = 0, reserved = 0, entry;
Sricharan R96ca8482013-12-03 15:57:23 +0530163 const __be32 *irqsr;
Nishanth Menonedb442d2014-06-26 12:40:27 +0530164 int ret = -ENOMEM;
Sricharan R96ca8482013-12-03 15:57:23 +0530165
Dan Carpenter3894e9e2014-04-03 10:21:34 +0300166 cb = kzalloc(sizeof(*cb), GFP_KERNEL);
Sricharan R96ca8482013-12-03 15:57:23 +0530167
168 if (!cb)
Nishanth Menonedb442d2014-06-26 12:40:27 +0530169 return ret;
Sricharan R96ca8482013-12-03 15:57:23 +0530170
171 cb->crossbar_base = of_iomap(node, 0);
172 if (!cb->crossbar_base)
Nishanth Menon3c44d512014-06-26 12:40:28 +0530173 goto err_cb;
Sricharan R96ca8482013-12-03 15:57:23 +0530174
Nishanth Menon2f7d2fb2014-06-26 12:40:31 +0530175 of_property_read_u32(node, "ti,max-crossbar-sources",
176 &cb->max_crossbar_sources);
177 if (!cb->max_crossbar_sources) {
178 pr_err("missing 'ti,max-crossbar-sources' property\n");
179 ret = -EINVAL;
180 goto err_base;
181 }
182
Sricharan R96ca8482013-12-03 15:57:23 +0530183 of_property_read_u32(node, "ti,max-irqs", &max);
Nishanth Menonedb442d2014-06-26 12:40:27 +0530184 if (!max) {
185 pr_err("missing 'ti,max-irqs' property\n");
186 ret = -EINVAL;
Nishanth Menon3c44d512014-06-26 12:40:28 +0530187 goto err_base;
Nishanth Menonedb442d2014-06-26 12:40:27 +0530188 }
Nishanth Menon4dbf45e2014-06-26 12:40:25 +0530189 cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
Sricharan R96ca8482013-12-03 15:57:23 +0530190 if (!cb->irq_map)
Nishanth Menon3c44d512014-06-26 12:40:28 +0530191 goto err_base;
Sricharan R96ca8482013-12-03 15:57:23 +0530192
193 cb->int_max = max;
194
195 for (i = 0; i < max; i++)
196 cb->irq_map[i] = IRQ_FREE;
197
198 /* Get and mark reserved irqs */
199 irqsr = of_get_property(node, "ti,irqs-reserved", &size);
200 if (irqsr) {
201 size /= sizeof(__be32);
202
203 for (i = 0; i < size; i++) {
204 of_property_read_u32_index(node,
205 "ti,irqs-reserved",
206 i, &entry);
207 if (entry > max) {
208 pr_err("Invalid reserved entry\n");
Nishanth Menonedb442d2014-06-26 12:40:27 +0530209 ret = -EINVAL;
Nishanth Menon3c44d512014-06-26 12:40:28 +0530210 goto err_irq_map;
Sricharan R96ca8482013-12-03 15:57:23 +0530211 }
Nishanth Menon1d50d2c2014-06-26 12:40:19 +0530212 cb->irq_map[entry] = IRQ_RESERVED;
Sricharan R96ca8482013-12-03 15:57:23 +0530213 }
214 }
215
Nishanth Menon64e0f8b2014-06-26 12:40:21 +0530216 /* Skip irqs hardwired to bypass the crossbar */
217 irqsr = of_get_property(node, "ti,irqs-skip", &size);
218 if (irqsr) {
219 size /= sizeof(__be32);
220
221 for (i = 0; i < size; i++) {
222 of_property_read_u32_index(node,
223 "ti,irqs-skip",
224 i, &entry);
225 if (entry > max) {
226 pr_err("Invalid skip entry\n");
227 ret = -EINVAL;
Nishanth Menon3c44d512014-06-26 12:40:28 +0530228 goto err_irq_map;
Nishanth Menon64e0f8b2014-06-26 12:40:21 +0530229 }
230 cb->irq_map[entry] = IRQ_SKIP;
231 }
232 }
233
234
Nishanth Menon4dbf45e2014-06-26 12:40:25 +0530235 cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
Sricharan R96ca8482013-12-03 15:57:23 +0530236 if (!cb->register_offsets)
Nishanth Menon3c44d512014-06-26 12:40:28 +0530237 goto err_irq_map;
Sricharan R96ca8482013-12-03 15:57:23 +0530238
239 of_property_read_u32(node, "ti,reg-size", &size);
240
241 switch (size) {
242 case 1:
243 cb->write = crossbar_writeb;
244 break;
245 case 2:
246 cb->write = crossbar_writew;
247 break;
248 case 4:
249 cb->write = crossbar_writel;
250 break;
251 default:
252 pr_err("Invalid reg-size property\n");
Nishanth Menonedb442d2014-06-26 12:40:27 +0530253 ret = -EINVAL;
Nishanth Menon3c44d512014-06-26 12:40:28 +0530254 goto err_reg_offset;
Sricharan R96ca8482013-12-03 15:57:23 +0530255 break;
256 }
257
258 /*
259 * Register offsets are not linear because of the
260 * reserved irqs. so find and store the offsets once.
261 */
262 for (i = 0; i < max; i++) {
Nishanth Menon1d50d2c2014-06-26 12:40:19 +0530263 if (cb->irq_map[i] == IRQ_RESERVED)
Sricharan R96ca8482013-12-03 15:57:23 +0530264 continue;
265
266 cb->register_offsets[i] = reserved;
267 reserved += size;
268 }
269
Nishanth Menona35057d2014-06-26 12:40:22 +0530270 of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
Nishanth Menona35057d2014-06-26 12:40:22 +0530271 /* Initialize the crossbar with safe map to start with */
272 for (i = 0; i < max; i++) {
273 if (cb->irq_map[i] == IRQ_RESERVED ||
274 cb->irq_map[i] == IRQ_SKIP)
275 continue;
276
277 cb->write(i, cb->safe_map);
278 }
279
Sricharan R96ca8482013-12-03 15:57:23 +0530280 register_routable_domain_ops(&routable_irq_domain_ops);
281 return 0;
282
Nishanth Menon3c44d512014-06-26 12:40:28 +0530283err_reg_offset:
Sricharan R96ca8482013-12-03 15:57:23 +0530284 kfree(cb->register_offsets);
Nishanth Menon3c44d512014-06-26 12:40:28 +0530285err_irq_map:
Sricharan R96ca8482013-12-03 15:57:23 +0530286 kfree(cb->irq_map);
Nishanth Menon3c44d512014-06-26 12:40:28 +0530287err_base:
Sricharan R96ca8482013-12-03 15:57:23 +0530288 iounmap(cb->crossbar_base);
Nishanth Menon3c44d512014-06-26 12:40:28 +0530289err_cb:
Sricharan R96ca8482013-12-03 15:57:23 +0530290 kfree(cb);
Sricharan R99e37d0e2014-06-26 12:40:29 +0530291
292 cb = NULL;
Nishanth Menonedb442d2014-06-26 12:40:27 +0530293 return ret;
Sricharan R96ca8482013-12-03 15:57:23 +0530294}
295
296static const struct of_device_id crossbar_match[] __initconst = {
297 { .compatible = "ti,irq-crossbar" },
298 {}
299};
300
301int __init irqcrossbar_init(void)
302{
303 struct device_node *np;
304 np = of_find_matching_node(NULL, crossbar_match);
305 if (!np)
306 return -ENODEV;
307
308 crossbar_of_init(np);
309 return 0;
310}