[go: nahoru, domu]

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#include <linux/irqchip/irq-crossbar.h>
19
20#define IRQ_FREE	-1
21#define IRQ_RESERVED	-2
22#define IRQ_SKIP	-3
23#define GIC_IRQ_START	32
24
25/**
26 * struct crossbar_device - crossbar device description
27 * @int_max: maximum number of supported interrupts
28 * @safe_map: safe default value to initialize the crossbar
29 * @max_crossbar_sources: Maximum number of crossbar sources
30 * @irq_map: array of interrupts to crossbar number mapping
31 * @crossbar_base: crossbar base address
32 * @register_offsets: offsets for each irq number
33 * @write: register write function pointer
34 */
35struct crossbar_device {
36	uint int_max;
37	uint safe_map;
38	uint max_crossbar_sources;
39	uint *irq_map;
40	void __iomem *crossbar_base;
41	int *register_offsets;
42	void (*write)(int, int);
43};
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
62static inline int get_prev_map_irq(int cb_no)
63{
64	int i;
65
66	for (i = cb->int_max - 1; i >= 0; i--)
67		if (cb->irq_map[i] == cb_no)
68			return i;
69
70	return -ENODEV;
71}
72
73static inline int allocate_free_irq(int cb_no)
74{
75	int i;
76
77	for (i = cb->int_max - 1; i >= 0; i--) {
78		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
87static inline bool needs_crossbar_write(irq_hw_number_t hw)
88{
89	int cb_no;
90
91	if (hw > GIC_IRQ_START) {
92		cb_no = cb->irq_map[hw - GIC_IRQ_START];
93		if (cb_no != IRQ_RESERVED && cb_no != IRQ_SKIP)
94			return true;
95	}
96
97	return false;
98}
99
100static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
101			       irq_hw_number_t hw)
102{
103	if (needs_crossbar_write(hw))
104		cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
105
106	return 0;
107}
108
109/**
110 * crossbar_domain_unmap - unmap a crossbar<->irq connection
111 * @d: domain of irq to unmap
112 * @irq: virq number
113 *
114 * 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
116 * unmapped. This is because unmap is called during irq_dispose_mapping(irq),
117 * after which irq is anyways unusable. So an explicit map has to be called
118 * after that.
119 */
120static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
121{
122	irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
123
124	if (needs_crossbar_write(hw)) {
125		cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
126		cb->write(hw - GIC_IRQ_START, cb->safe_map);
127	}
128}
129
130static int crossbar_domain_xlate(struct irq_domain *d,
131				 struct device_node *controller,
132				 const u32 *intspec, unsigned int intsize,
133				 unsigned long *out_hwirq,
134				 unsigned int *out_type)
135{
136	int ret;
137	int req_num = intspec[1];
138	int direct_map_num;
139
140	if (req_num >= cb->max_crossbar_sources) {
141		direct_map_num = req_num - cb->max_crossbar_sources;
142		if (direct_map_num < cb->int_max) {
143			ret = cb->irq_map[direct_map_num];
144			if (ret == IRQ_RESERVED || ret == IRQ_SKIP) {
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
165found:
166	*out_hwirq = ret + GIC_IRQ_START;
167	return 0;
168}
169
170static const struct irq_domain_ops routable_irq_domain_ops = {
171	.map = crossbar_domain_map,
172	.unmap = crossbar_domain_unmap,
173	.xlate = crossbar_domain_xlate
174};
175
176static int __init crossbar_of_init(struct device_node *node)
177{
178	int i, size, max = 0, reserved = 0, entry;
179	const __be32 *irqsr;
180	int ret = -ENOMEM;
181
182	cb = kzalloc(sizeof(*cb), GFP_KERNEL);
183
184	if (!cb)
185		return ret;
186
187	cb->crossbar_base = of_iomap(node, 0);
188	if (!cb->crossbar_base)
189		goto err_cb;
190
191	of_property_read_u32(node, "ti,max-crossbar-sources",
192			     &cb->max_crossbar_sources);
193	if (!cb->max_crossbar_sources) {
194		pr_err("missing 'ti,max-crossbar-sources' property\n");
195		ret = -EINVAL;
196		goto err_base;
197	}
198
199	of_property_read_u32(node, "ti,max-irqs", &max);
200	if (!max) {
201		pr_err("missing 'ti,max-irqs' property\n");
202		ret = -EINVAL;
203		goto err_base;
204	}
205	cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
206	if (!cb->irq_map)
207		goto err_base;
208
209	cb->int_max = max;
210
211	for (i = 0; i < max; i++)
212		cb->irq_map[i] = IRQ_FREE;
213
214	/* Get and mark reserved irqs */
215	irqsr = of_get_property(node, "ti,irqs-reserved", &size);
216	if (irqsr) {
217		size /= sizeof(__be32);
218
219		for (i = 0; i < size; i++) {
220			of_property_read_u32_index(node,
221						   "ti,irqs-reserved",
222						   i, &entry);
223			if (entry >= max) {
224				pr_err("Invalid reserved entry\n");
225				ret = -EINVAL;
226				goto err_irq_map;
227			}
228			cb->irq_map[entry] = IRQ_RESERVED;
229		}
230	}
231
232	/* Skip irqs hardwired to bypass the crossbar */
233	irqsr = of_get_property(node, "ti,irqs-skip", &size);
234	if (irqsr) {
235		size /= sizeof(__be32);
236
237		for (i = 0; i < size; i++) {
238			of_property_read_u32_index(node,
239						   "ti,irqs-skip",
240						   i, &entry);
241			if (entry >= max) {
242				pr_err("Invalid skip entry\n");
243				ret = -EINVAL;
244				goto err_irq_map;
245			}
246			cb->irq_map[entry] = IRQ_SKIP;
247		}
248	}
249
250
251	cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
252	if (!cb->register_offsets)
253		goto err_irq_map;
254
255	of_property_read_u32(node, "ti,reg-size", &size);
256
257	switch (size) {
258	case 1:
259		cb->write = crossbar_writeb;
260		break;
261	case 2:
262		cb->write = crossbar_writew;
263		break;
264	case 4:
265		cb->write = crossbar_writel;
266		break;
267	default:
268		pr_err("Invalid reg-size property\n");
269		ret = -EINVAL;
270		goto err_reg_offset;
271		break;
272	}
273
274	/*
275	 * Register offsets are not linear because of the
276	 * reserved irqs. so find and store the offsets once.
277	 */
278	for (i = 0; i < max; i++) {
279		if (cb->irq_map[i] == IRQ_RESERVED)
280			continue;
281
282		cb->register_offsets[i] = reserved;
283		reserved += size;
284	}
285
286	of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
287	/* Initialize the crossbar with safe map to start with */
288	for (i = 0; i < max; i++) {
289		if (cb->irq_map[i] == IRQ_RESERVED ||
290		    cb->irq_map[i] == IRQ_SKIP)
291			continue;
292
293		cb->write(i, cb->safe_map);
294	}
295
296	register_routable_domain_ops(&routable_irq_domain_ops);
297	return 0;
298
299err_reg_offset:
300	kfree(cb->register_offsets);
301err_irq_map:
302	kfree(cb->irq_map);
303err_base:
304	iounmap(cb->crossbar_base);
305err_cb:
306	kfree(cb);
307
308	cb = NULL;
309	return ret;
310}
311
312static const struct of_device_id crossbar_match[] __initconst = {
313	{ .compatible = "ti,irq-crossbar" },
314	{}
315};
316
317int __init irqcrossbar_init(void)
318{
319	struct device_node *np;
320	np = of_find_matching_node(NULL, crossbar_match);
321	if (!np)
322		return -ENODEV;
323
324	crossbar_of_init(np);
325	return 0;
326}
327