xref: /openbmc/linux/drivers/irqchip/irq-crossbar.c (revision 96ca848ef7ea1be7e92d1cceb34ef3aa86053828)
1*96ca848eSSricharan R /*
2*96ca848eSSricharan R  *  drivers/irqchip/irq-crossbar.c
3*96ca848eSSricharan R  *
4*96ca848eSSricharan R  *  Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
5*96ca848eSSricharan R  *  Author: Sricharan R <r.sricharan@ti.com>
6*96ca848eSSricharan R  *
7*96ca848eSSricharan R  * This program is free software; you can redistribute it and/or modify
8*96ca848eSSricharan R  * it under the terms of the GNU General Public License version 2 as
9*96ca848eSSricharan R  * published by the Free Software Foundation.
10*96ca848eSSricharan R  *
11*96ca848eSSricharan R  */
12*96ca848eSSricharan R #include <linux/err.h>
13*96ca848eSSricharan R #include <linux/io.h>
14*96ca848eSSricharan R #include <linux/of_address.h>
15*96ca848eSSricharan R #include <linux/of_irq.h>
16*96ca848eSSricharan R #include <linux/slab.h>
17*96ca848eSSricharan R #include <linux/irqchip/arm-gic.h>
18*96ca848eSSricharan R 
19*96ca848eSSricharan R #define IRQ_FREE	-1
20*96ca848eSSricharan R #define GIC_IRQ_START	32
21*96ca848eSSricharan R 
22*96ca848eSSricharan R /*
23*96ca848eSSricharan R  * @int_max: maximum number of supported interrupts
24*96ca848eSSricharan R  * @irq_map: array of interrupts to crossbar number mapping
25*96ca848eSSricharan R  * @crossbar_base: crossbar base address
26*96ca848eSSricharan R  * @register_offsets: offsets for each irq number
27*96ca848eSSricharan R  */
28*96ca848eSSricharan R struct crossbar_device {
29*96ca848eSSricharan R 	uint int_max;
30*96ca848eSSricharan R 	uint *irq_map;
31*96ca848eSSricharan R 	void __iomem *crossbar_base;
32*96ca848eSSricharan R 	int *register_offsets;
33*96ca848eSSricharan R 	void (*write) (int, int);
34*96ca848eSSricharan R };
35*96ca848eSSricharan R 
36*96ca848eSSricharan R static struct crossbar_device *cb;
37*96ca848eSSricharan R 
38*96ca848eSSricharan R static inline void crossbar_writel(int irq_no, int cb_no)
39*96ca848eSSricharan R {
40*96ca848eSSricharan R 	writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
41*96ca848eSSricharan R }
42*96ca848eSSricharan R 
43*96ca848eSSricharan R static inline void crossbar_writew(int irq_no, int cb_no)
44*96ca848eSSricharan R {
45*96ca848eSSricharan R 	writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
46*96ca848eSSricharan R }
47*96ca848eSSricharan R 
48*96ca848eSSricharan R static inline void crossbar_writeb(int irq_no, int cb_no)
49*96ca848eSSricharan R {
50*96ca848eSSricharan R 	writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
51*96ca848eSSricharan R }
52*96ca848eSSricharan R 
53*96ca848eSSricharan R static inline int allocate_free_irq(int cb_no)
54*96ca848eSSricharan R {
55*96ca848eSSricharan R 	int i;
56*96ca848eSSricharan R 
57*96ca848eSSricharan R 	for (i = 0; i < cb->int_max; i++) {
58*96ca848eSSricharan R 		if (cb->irq_map[i] == IRQ_FREE) {
59*96ca848eSSricharan R 			cb->irq_map[i] = cb_no;
60*96ca848eSSricharan R 			return i;
61*96ca848eSSricharan R 		}
62*96ca848eSSricharan R 	}
63*96ca848eSSricharan R 
64*96ca848eSSricharan R 	return -ENODEV;
65*96ca848eSSricharan R }
66*96ca848eSSricharan R 
67*96ca848eSSricharan R static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
68*96ca848eSSricharan R 			       irq_hw_number_t hw)
69*96ca848eSSricharan R {
70*96ca848eSSricharan R 	cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
71*96ca848eSSricharan R 	return 0;
72*96ca848eSSricharan R }
73*96ca848eSSricharan R 
74*96ca848eSSricharan R static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
75*96ca848eSSricharan R {
76*96ca848eSSricharan R 	irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
77*96ca848eSSricharan R 
78*96ca848eSSricharan R 	if (hw > GIC_IRQ_START)
79*96ca848eSSricharan R 		cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
80*96ca848eSSricharan R }
81*96ca848eSSricharan R 
82*96ca848eSSricharan R static int crossbar_domain_xlate(struct irq_domain *d,
83*96ca848eSSricharan R 				 struct device_node *controller,
84*96ca848eSSricharan R 				 const u32 *intspec, unsigned int intsize,
85*96ca848eSSricharan R 				 unsigned long *out_hwirq,
86*96ca848eSSricharan R 				 unsigned int *out_type)
87*96ca848eSSricharan R {
88*96ca848eSSricharan R 	unsigned long ret;
89*96ca848eSSricharan R 
90*96ca848eSSricharan R 	ret = allocate_free_irq(intspec[1]);
91*96ca848eSSricharan R 
92*96ca848eSSricharan R 	if (IS_ERR_VALUE(ret))
93*96ca848eSSricharan R 		return ret;
94*96ca848eSSricharan R 
95*96ca848eSSricharan R 	*out_hwirq = ret + GIC_IRQ_START;
96*96ca848eSSricharan R 	return 0;
97*96ca848eSSricharan R }
98*96ca848eSSricharan R 
99*96ca848eSSricharan R const struct irq_domain_ops routable_irq_domain_ops = {
100*96ca848eSSricharan R 	.map = crossbar_domain_map,
101*96ca848eSSricharan R 	.unmap = crossbar_domain_unmap,
102*96ca848eSSricharan R 	.xlate = crossbar_domain_xlate
103*96ca848eSSricharan R };
104*96ca848eSSricharan R 
105*96ca848eSSricharan R static int __init crossbar_of_init(struct device_node *node)
106*96ca848eSSricharan R {
107*96ca848eSSricharan R 	int i, size, max, reserved = 0, entry;
108*96ca848eSSricharan R 	const __be32 *irqsr;
109*96ca848eSSricharan R 
110*96ca848eSSricharan R 	cb = kzalloc(sizeof(struct cb_device *), GFP_KERNEL);
111*96ca848eSSricharan R 
112*96ca848eSSricharan R 	if (!cb)
113*96ca848eSSricharan R 		return -ENOMEM;
114*96ca848eSSricharan R 
115*96ca848eSSricharan R 	cb->crossbar_base = of_iomap(node, 0);
116*96ca848eSSricharan R 	if (!cb->crossbar_base)
117*96ca848eSSricharan R 		goto err1;
118*96ca848eSSricharan R 
119*96ca848eSSricharan R 	of_property_read_u32(node, "ti,max-irqs", &max);
120*96ca848eSSricharan R 	cb->irq_map = kzalloc(max * sizeof(int), GFP_KERNEL);
121*96ca848eSSricharan R 	if (!cb->irq_map)
122*96ca848eSSricharan R 		goto err2;
123*96ca848eSSricharan R 
124*96ca848eSSricharan R 	cb->int_max = max;
125*96ca848eSSricharan R 
126*96ca848eSSricharan R 	for (i = 0; i < max; i++)
127*96ca848eSSricharan R 		cb->irq_map[i] = IRQ_FREE;
128*96ca848eSSricharan R 
129*96ca848eSSricharan R 	/* Get and mark reserved irqs */
130*96ca848eSSricharan R 	irqsr = of_get_property(node, "ti,irqs-reserved", &size);
131*96ca848eSSricharan R 	if (irqsr) {
132*96ca848eSSricharan R 		size /= sizeof(__be32);
133*96ca848eSSricharan R 
134*96ca848eSSricharan R 		for (i = 0; i < size; i++) {
135*96ca848eSSricharan R 			of_property_read_u32_index(node,
136*96ca848eSSricharan R 						   "ti,irqs-reserved",
137*96ca848eSSricharan R 						   i, &entry);
138*96ca848eSSricharan R 			if (entry > max) {
139*96ca848eSSricharan R 				pr_err("Invalid reserved entry\n");
140*96ca848eSSricharan R 				goto err3;
141*96ca848eSSricharan R 			}
142*96ca848eSSricharan R 			cb->irq_map[entry] = 0;
143*96ca848eSSricharan R 		}
144*96ca848eSSricharan R 	}
145*96ca848eSSricharan R 
146*96ca848eSSricharan R 	cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL);
147*96ca848eSSricharan R 	if (!cb->register_offsets)
148*96ca848eSSricharan R 		goto err3;
149*96ca848eSSricharan R 
150*96ca848eSSricharan R 	of_property_read_u32(node, "ti,reg-size", &size);
151*96ca848eSSricharan R 
152*96ca848eSSricharan R 	switch (size) {
153*96ca848eSSricharan R 	case 1:
154*96ca848eSSricharan R 		cb->write = crossbar_writeb;
155*96ca848eSSricharan R 		break;
156*96ca848eSSricharan R 	case 2:
157*96ca848eSSricharan R 		cb->write = crossbar_writew;
158*96ca848eSSricharan R 		break;
159*96ca848eSSricharan R 	case 4:
160*96ca848eSSricharan R 		cb->write = crossbar_writel;
161*96ca848eSSricharan R 		break;
162*96ca848eSSricharan R 	default:
163*96ca848eSSricharan R 		pr_err("Invalid reg-size property\n");
164*96ca848eSSricharan R 		goto err4;
165*96ca848eSSricharan R 		break;
166*96ca848eSSricharan R 	}
167*96ca848eSSricharan R 
168*96ca848eSSricharan R 	/*
169*96ca848eSSricharan R 	 * Register offsets are not linear because of the
170*96ca848eSSricharan R 	 * reserved irqs. so find and store the offsets once.
171*96ca848eSSricharan R 	 */
172*96ca848eSSricharan R 	for (i = 0; i < max; i++) {
173*96ca848eSSricharan R 		if (!cb->irq_map[i])
174*96ca848eSSricharan R 			continue;
175*96ca848eSSricharan R 
176*96ca848eSSricharan R 		cb->register_offsets[i] = reserved;
177*96ca848eSSricharan R 		reserved += size;
178*96ca848eSSricharan R 	}
179*96ca848eSSricharan R 
180*96ca848eSSricharan R 	register_routable_domain_ops(&routable_irq_domain_ops);
181*96ca848eSSricharan R 	return 0;
182*96ca848eSSricharan R 
183*96ca848eSSricharan R err4:
184*96ca848eSSricharan R 	kfree(cb->register_offsets);
185*96ca848eSSricharan R err3:
186*96ca848eSSricharan R 	kfree(cb->irq_map);
187*96ca848eSSricharan R err2:
188*96ca848eSSricharan R 	iounmap(cb->crossbar_base);
189*96ca848eSSricharan R err1:
190*96ca848eSSricharan R 	kfree(cb);
191*96ca848eSSricharan R 	return -ENOMEM;
192*96ca848eSSricharan R }
193*96ca848eSSricharan R 
194*96ca848eSSricharan R static const struct of_device_id crossbar_match[] __initconst = {
195*96ca848eSSricharan R 	{ .compatible = "ti,irq-crossbar" },
196*96ca848eSSricharan R 	{}
197*96ca848eSSricharan R };
198*96ca848eSSricharan R 
199*96ca848eSSricharan R int __init irqcrossbar_init(void)
200*96ca848eSSricharan R {
201*96ca848eSSricharan R 	struct device_node *np;
202*96ca848eSSricharan R 	np = of_find_matching_node(NULL, crossbar_match);
203*96ca848eSSricharan R 	if (!np)
204*96ca848eSSricharan R 		return -ENODEV;
205*96ca848eSSricharan R 
206*96ca848eSSricharan R 	crossbar_of_init(np);
207*96ca848eSSricharan R 	return 0;
208*96ca848eSSricharan R }
209