Blame view

drivers/irqchip/irq-crossbar.c 7.21 KB
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  /*
   *  drivers/irqchip/irq-crossbar.c
   *
   *  Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
   *  Author: Sricharan R <r.sricharan@ti.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation.
   *
   */
  #include <linux/err.h>
  #include <linux/io.h>
  #include <linux/of_address.h>
  #include <linux/of_irq.h>
  #include <linux/slab.h>
  #include <linux/irqchip/arm-gic.h>
4dbf45e3c   Nishanth Menon   irqchip: crossbar...
18
  #include <linux/irqchip/irq-crossbar.h>
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
19
20
  
  #define IRQ_FREE	-1
1d50d2ce6   Nishanth Menon   irqchip: crossbar...
21
  #define IRQ_RESERVED	-2
64e0f8ba5   Nishanth Menon   irqchip: crossbar...
22
  #define IRQ_SKIP	-3
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
23
  #define GIC_IRQ_START	32
e30ef8abb   Nishanth Menon   irqchip: crossbar...
24
25
  /**
   * struct crossbar_device - crossbar device description
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
26
   * @int_max: maximum number of supported interrupts
a35057d1d   Nishanth Menon   irqchip: crossbar...
27
   * @safe_map: safe default value to initialize the crossbar
2f7d2fb71   Nishanth Menon   irqchip: crossbar...
28
   * @max_crossbar_sources: Maximum number of crossbar sources
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
29
30
31
   * @irq_map: array of interrupts to crossbar number mapping
   * @crossbar_base: crossbar base address
   * @register_offsets: offsets for each irq number
e30ef8abb   Nishanth Menon   irqchip: crossbar...
32
   * @write: register write function pointer
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
33
34
35
   */
  struct crossbar_device {
  	uint int_max;
a35057d1d   Nishanth Menon   irqchip: crossbar...
36
  	uint safe_map;
2f7d2fb71   Nishanth Menon   irqchip: crossbar...
37
  	uint max_crossbar_sources;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
38
39
40
  	uint *irq_map;
  	void __iomem *crossbar_base;
  	int *register_offsets;
a35057d1d   Nishanth Menon   irqchip: crossbar...
41
  	void (*write)(int, int);
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  };
  
  static struct crossbar_device *cb;
  
  static inline void crossbar_writel(int irq_no, int cb_no)
  {
  	writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  }
  
  static inline void crossbar_writew(int irq_no, int cb_no)
  {
  	writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  }
  
  static inline void crossbar_writeb(int irq_no, int cb_no)
  {
  	writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  }
6f16fc878   Nishanth Menon   irqchip: crossbar...
60
61
62
  static inline int get_prev_map_irq(int cb_no)
  {
  	int i;
ddee0fb46   Nishanth Menon   irqchip: crossbar...
63
  	for (i = cb->int_max - 1; i >= 0; i--)
6f16fc878   Nishanth Menon   irqchip: crossbar...
64
65
66
67
68
  		if (cb->irq_map[i] == cb_no)
  			return i;
  
  	return -ENODEV;
  }
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
69
70
71
  static inline int allocate_free_irq(int cb_no)
  {
  	int i;
ddee0fb46   Nishanth Menon   irqchip: crossbar...
72
  	for (i = cb->int_max - 1; i >= 0; i--) {
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
73
74
75
76
77
78
79
80
  		if (cb->irq_map[i] == IRQ_FREE) {
  			cb->irq_map[i] = cb_no;
  			return i;
  		}
  	}
  
  	return -ENODEV;
  }
29918b679   Nishanth Menon   irqchip: crossbar...
81
82
  static inline bool needs_crossbar_write(irq_hw_number_t hw)
  {
d360892d3   Nishanth Menon   irqchip: crossbar...
83
84
85
86
87
88
89
  	int cb_no;
  
  	if (hw > GIC_IRQ_START) {
  		cb_no = cb->irq_map[hw - GIC_IRQ_START];
  		if (cb_no != IRQ_RESERVED && cb_no != IRQ_SKIP)
  			return true;
  	}
29918b679   Nishanth Menon   irqchip: crossbar...
90
91
92
  
  	return false;
  }
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
93
94
95
  static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
  			       irq_hw_number_t hw)
  {
29918b679   Nishanth Menon   irqchip: crossbar...
96
97
  	if (needs_crossbar_write(hw))
  		cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
98
99
  	return 0;
  }
8b09a45dc   Sricharan R   irqchip: crossbar...
100
101
102
103
104
105
106
107
108
109
110
  /**
   * crossbar_domain_unmap - unmap a crossbar<->irq connection
   * @d: domain of irq to unmap
   * @irq: virq number
   *
   * We do not maintain a use count of total number of map/unmap
   * calls for a particular irq to find out if a irq can be really
   * unmapped. This is because unmap is called during irq_dispose_mapping(irq),
   * after which irq is anyways unusable. So an explicit map has to be called
   * after that.
   */
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
111
112
113
  static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
  {
  	irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
29918b679   Nishanth Menon   irqchip: crossbar...
114
  	if (needs_crossbar_write(hw)) {
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
115
  		cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
a35057d1d   Nishanth Menon   irqchip: crossbar...
116
117
  		cb->write(hw - GIC_IRQ_START, cb->safe_map);
  	}
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
118
119
120
121
122
123
124
125
  }
  
  static int crossbar_domain_xlate(struct irq_domain *d,
  				 struct device_node *controller,
  				 const u32 *intspec, unsigned int intsize,
  				 unsigned long *out_hwirq,
  				 unsigned int *out_type)
  {
d4922a95a   Nishanth Menon   irqchip: crossbar...
126
  	int ret;
2f7d2fb71   Nishanth Menon   irqchip: crossbar...
127
  	int req_num = intspec[1];
d360892d3   Nishanth Menon   irqchip: crossbar...
128
  	int direct_map_num;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
129

2f7d2fb71   Nishanth Menon   irqchip: crossbar...
130
  	if (req_num >= cb->max_crossbar_sources) {
d360892d3   Nishanth Menon   irqchip: crossbar...
131
132
133
134
135
136
137
138
139
  		direct_map_num = req_num - cb->max_crossbar_sources;
  		if (direct_map_num < cb->int_max) {
  			ret = cb->irq_map[direct_map_num];
  			if (ret == IRQ_RESERVED || ret == IRQ_SKIP) {
  				/* We use the interrupt num as h/w irq num */
  				ret = direct_map_num;
  				goto found;
  			}
  		}
2f7d2fb71   Nishanth Menon   irqchip: crossbar...
140
141
142
143
144
145
146
  		pr_err("%s: requested crossbar number %d > max %d
  ",
  		       __func__, req_num, cb->max_crossbar_sources);
  		return -EINVAL;
  	}
  
  	ret = get_prev_map_irq(req_num);
d4922a95a   Nishanth Menon   irqchip: crossbar...
147
  	if (ret >= 0)
6f16fc878   Nishanth Menon   irqchip: crossbar...
148
  		goto found;
2f7d2fb71   Nishanth Menon   irqchip: crossbar...
149
  	ret = allocate_free_irq(req_num);
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
150

d4922a95a   Nishanth Menon   irqchip: crossbar...
151
  	if (ret < 0)
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
152
  		return ret;
6f16fc878   Nishanth Menon   irqchip: crossbar...
153
  found:
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
154
155
156
  	*out_hwirq = ret + GIC_IRQ_START;
  	return 0;
  }
4dbf45e3c   Nishanth Menon   irqchip: crossbar...
157
  static const struct irq_domain_ops routable_irq_domain_ops = {
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
158
159
160
161
162
163
164
  	.map = crossbar_domain_map,
  	.unmap = crossbar_domain_unmap,
  	.xlate = crossbar_domain_xlate
  };
  
  static int __init crossbar_of_init(struct device_node *node)
  {
edb442def   Nishanth Menon   irqchip: crossbar...
165
  	int i, size, max = 0, reserved = 0, entry;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
166
  	const __be32 *irqsr;
edb442def   Nishanth Menon   irqchip: crossbar...
167
  	int ret = -ENOMEM;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
168

3894e9e82   Dan Carpenter   irqchip: irq-cros...
169
  	cb = kzalloc(sizeof(*cb), GFP_KERNEL);
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
170
171
  
  	if (!cb)
edb442def   Nishanth Menon   irqchip: crossbar...
172
  		return ret;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
173
174
175
  
  	cb->crossbar_base = of_iomap(node, 0);
  	if (!cb->crossbar_base)
3c44d5151   Nishanth Menon   irqchip: crossbar...
176
  		goto err_cb;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
177

2f7d2fb71   Nishanth Menon   irqchip: crossbar...
178
179
180
181
182
183
184
185
  	of_property_read_u32(node, "ti,max-crossbar-sources",
  			     &cb->max_crossbar_sources);
  	if (!cb->max_crossbar_sources) {
  		pr_err("missing 'ti,max-crossbar-sources' property
  ");
  		ret = -EINVAL;
  		goto err_base;
  	}
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
186
  	of_property_read_u32(node, "ti,max-irqs", &max);
edb442def   Nishanth Menon   irqchip: crossbar...
187
188
189
190
  	if (!max) {
  		pr_err("missing 'ti,max-irqs' property
  ");
  		ret = -EINVAL;
3c44d5151   Nishanth Menon   irqchip: crossbar...
191
  		goto err_base;
edb442def   Nishanth Menon   irqchip: crossbar...
192
  	}
4dbf45e3c   Nishanth Menon   irqchip: crossbar...
193
  	cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
194
  	if (!cb->irq_map)
3c44d5151   Nishanth Menon   irqchip: crossbar...
195
  		goto err_base;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
  
  	cb->int_max = max;
  
  	for (i = 0; i < max; i++)
  		cb->irq_map[i] = IRQ_FREE;
  
  	/* Get and mark reserved irqs */
  	irqsr = of_get_property(node, "ti,irqs-reserved", &size);
  	if (irqsr) {
  		size /= sizeof(__be32);
  
  		for (i = 0; i < size; i++) {
  			of_property_read_u32_index(node,
  						   "ti,irqs-reserved",
  						   i, &entry);
702f7e36f   Dan Carpenter   irqchip: crossbar...
211
  			if (entry >= max) {
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
212
213
  				pr_err("Invalid reserved entry
  ");
edb442def   Nishanth Menon   irqchip: crossbar...
214
  				ret = -EINVAL;
3c44d5151   Nishanth Menon   irqchip: crossbar...
215
  				goto err_irq_map;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
216
  			}
1d50d2ce6   Nishanth Menon   irqchip: crossbar...
217
  			cb->irq_map[entry] = IRQ_RESERVED;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
218
219
  		}
  	}
64e0f8ba5   Nishanth Menon   irqchip: crossbar...
220
221
222
223
224
225
226
227
228
  	/* Skip irqs hardwired to bypass the crossbar */
  	irqsr = of_get_property(node, "ti,irqs-skip", &size);
  	if (irqsr) {
  		size /= sizeof(__be32);
  
  		for (i = 0; i < size; i++) {
  			of_property_read_u32_index(node,
  						   "ti,irqs-skip",
  						   i, &entry);
702f7e36f   Dan Carpenter   irqchip: crossbar...
229
  			if (entry >= max) {
64e0f8ba5   Nishanth Menon   irqchip: crossbar...
230
231
232
  				pr_err("Invalid skip entry
  ");
  				ret = -EINVAL;
3c44d5151   Nishanth Menon   irqchip: crossbar...
233
  				goto err_irq_map;
64e0f8ba5   Nishanth Menon   irqchip: crossbar...
234
235
236
237
  			}
  			cb->irq_map[entry] = IRQ_SKIP;
  		}
  	}
4dbf45e3c   Nishanth Menon   irqchip: crossbar...
238
  	cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
239
  	if (!cb->register_offsets)
3c44d5151   Nishanth Menon   irqchip: crossbar...
240
  		goto err_irq_map;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
  
  	of_property_read_u32(node, "ti,reg-size", &size);
  
  	switch (size) {
  	case 1:
  		cb->write = crossbar_writeb;
  		break;
  	case 2:
  		cb->write = crossbar_writew;
  		break;
  	case 4:
  		cb->write = crossbar_writel;
  		break;
  	default:
  		pr_err("Invalid reg-size property
  ");
edb442def   Nishanth Menon   irqchip: crossbar...
257
  		ret = -EINVAL;
3c44d5151   Nishanth Menon   irqchip: crossbar...
258
  		goto err_reg_offset;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
259
260
261
262
263
264
265
266
  		break;
  	}
  
  	/*
  	 * Register offsets are not linear because of the
  	 * reserved irqs. so find and store the offsets once.
  	 */
  	for (i = 0; i < max; i++) {
1d50d2ce6   Nishanth Menon   irqchip: crossbar...
267
  		if (cb->irq_map[i] == IRQ_RESERVED)
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
268
269
270
271
272
  			continue;
  
  		cb->register_offsets[i] = reserved;
  		reserved += size;
  	}
a35057d1d   Nishanth Menon   irqchip: crossbar...
273
  	of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
a35057d1d   Nishanth Menon   irqchip: crossbar...
274
275
276
277
278
279
280
281
  	/* Initialize the crossbar with safe map to start with */
  	for (i = 0; i < max; i++) {
  		if (cb->irq_map[i] == IRQ_RESERVED ||
  		    cb->irq_map[i] == IRQ_SKIP)
  			continue;
  
  		cb->write(i, cb->safe_map);
  	}
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
282
283
  	register_routable_domain_ops(&routable_irq_domain_ops);
  	return 0;
3c44d5151   Nishanth Menon   irqchip: crossbar...
284
  err_reg_offset:
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
285
  	kfree(cb->register_offsets);
3c44d5151   Nishanth Menon   irqchip: crossbar...
286
  err_irq_map:
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
287
  	kfree(cb->irq_map);
3c44d5151   Nishanth Menon   irqchip: crossbar...
288
  err_base:
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
289
  	iounmap(cb->crossbar_base);
3c44d5151   Nishanth Menon   irqchip: crossbar...
290
  err_cb:
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
291
  	kfree(cb);
99e37d0ea   Sricharan R   irqchip: crossbar...
292
293
  
  	cb = NULL;
edb442def   Nishanth Menon   irqchip: crossbar...
294
  	return ret;
96ca848ef   Sricharan R   DRIVERS: IRQCHIP:...
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
  }
  
  static const struct of_device_id crossbar_match[] __initconst = {
  	{ .compatible = "ti,irq-crossbar" },
  	{}
  };
  
  int __init irqcrossbar_init(void)
  {
  	struct device_node *np;
  	np = of_find_matching_node(NULL, crossbar_match);
  	if (!np)
  		return -ENODEV;
  
  	crossbar_of_init(np);
  	return 0;
  }