Commit | Line | Data |
---|---|---|
5296b56d GL |
1 | /* |
2 | * Copyright (C) 2008 | |
3 | * Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.de> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | ||
10 | #include <linux/init.h> | |
11 | #include <linux/err.h> | |
12 | #include <linux/spinlock.h> | |
13 | #include <linux/delay.h> | |
14 | #include <linux/clk.h> | |
15 | #include <linux/irq.h> | |
16 | #include <linux/io.h> | |
88289c80 | 17 | #include <linux/module.h> |
5296b56d GL |
18 | |
19 | #include <mach/ipu.h> | |
20 | ||
21 | #include "ipu_intern.h" | |
22 | ||
23 | /* | |
24 | * Register read / write - shall be inlined by the compiler | |
25 | */ | |
26 | static u32 ipu_read_reg(struct ipu *ipu, unsigned long reg) | |
27 | { | |
28 | return __raw_readl(ipu->reg_ipu + reg); | |
29 | } | |
30 | ||
31 | static void ipu_write_reg(struct ipu *ipu, u32 value, unsigned long reg) | |
32 | { | |
33 | __raw_writel(value, ipu->reg_ipu + reg); | |
34 | } | |
35 | ||
36 | ||
37 | /* | |
38 | * IPU IRQ chip driver | |
39 | */ | |
40 | ||
41 | #define IPU_IRQ_NR_FN_BANKS 3 | |
42 | #define IPU_IRQ_NR_ERR_BANKS 2 | |
43 | #define IPU_IRQ_NR_BANKS (IPU_IRQ_NR_FN_BANKS + IPU_IRQ_NR_ERR_BANKS) | |
44 | ||
45 | struct ipu_irq_bank { | |
46 | unsigned int control; | |
47 | unsigned int status; | |
48 | spinlock_t lock; | |
49 | struct ipu *ipu; | |
50 | }; | |
51 | ||
52 | static struct ipu_irq_bank irq_bank[IPU_IRQ_NR_BANKS] = { | |
53 | /* 3 groups of functional interrupts */ | |
54 | { | |
55 | .control = IPU_INT_CTRL_1, | |
56 | .status = IPU_INT_STAT_1, | |
57 | }, { | |
58 | .control = IPU_INT_CTRL_2, | |
59 | .status = IPU_INT_STAT_2, | |
60 | }, { | |
61 | .control = IPU_INT_CTRL_3, | |
62 | .status = IPU_INT_STAT_3, | |
63 | }, | |
64 | /* 2 groups of error interrupts */ | |
65 | { | |
66 | .control = IPU_INT_CTRL_4, | |
67 | .status = IPU_INT_STAT_4, | |
68 | }, { | |
69 | .control = IPU_INT_CTRL_5, | |
70 | .status = IPU_INT_STAT_5, | |
71 | }, | |
72 | }; | |
73 | ||
74 | struct ipu_irq_map { | |
75 | unsigned int irq; | |
76 | int source; | |
77 | struct ipu_irq_bank *bank; | |
78 | struct ipu *ipu; | |
79 | }; | |
80 | ||
81 | static struct ipu_irq_map irq_map[CONFIG_MX3_IPU_IRQS]; | |
82 | /* Protects allocations from the above array of maps */ | |
83 | static DEFINE_MUTEX(map_lock); | |
84 | /* Protects register accesses and individual mappings */ | |
289b4e7a | 85 | static DEFINE_RAW_SPINLOCK(bank_lock); |
5296b56d GL |
86 | |
87 | static struct ipu_irq_map *src2map(unsigned int src) | |
88 | { | |
89 | int i; | |
90 | ||
91 | for (i = 0; i < CONFIG_MX3_IPU_IRQS; i++) | |
92 | if (irq_map[i].source == src) | |
93 | return irq_map + i; | |
94 | ||
95 | return NULL; | |
96 | } | |
97 | ||
6a035138 | 98 | static void ipu_irq_unmask(struct irq_data *d) |
5296b56d | 99 | { |
6a035138 | 100 | struct ipu_irq_map *map = irq_data_get_irq_chip_data(d); |
5296b56d GL |
101 | struct ipu_irq_bank *bank; |
102 | uint32_t reg; | |
103 | unsigned long lock_flags; | |
104 | ||
289b4e7a | 105 | raw_spin_lock_irqsave(&bank_lock, lock_flags); |
5296b56d GL |
106 | |
107 | bank = map->bank; | |
108 | if (!bank) { | |
289b4e7a | 109 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
6a035138 | 110 | pr_err("IPU: %s(%u) - unmapped!\n", __func__, d->irq); |
5296b56d GL |
111 | return; |
112 | } | |
113 | ||
114 | reg = ipu_read_reg(bank->ipu, bank->control); | |
115 | reg |= (1UL << (map->source & 31)); | |
116 | ipu_write_reg(bank->ipu, reg, bank->control); | |
117 | ||
289b4e7a | 118 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
5296b56d GL |
119 | } |
120 | ||
6a035138 | 121 | static void ipu_irq_mask(struct irq_data *d) |
5296b56d | 122 | { |
6a035138 | 123 | struct ipu_irq_map *map = irq_data_get_irq_chip_data(d); |
5296b56d GL |
124 | struct ipu_irq_bank *bank; |
125 | uint32_t reg; | |
126 | unsigned long lock_flags; | |
127 | ||
289b4e7a | 128 | raw_spin_lock_irqsave(&bank_lock, lock_flags); |
5296b56d GL |
129 | |
130 | bank = map->bank; | |
131 | if (!bank) { | |
289b4e7a | 132 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
6a035138 | 133 | pr_err("IPU: %s(%u) - unmapped!\n", __func__, d->irq); |
5296b56d GL |
134 | return; |
135 | } | |
136 | ||
137 | reg = ipu_read_reg(bank->ipu, bank->control); | |
138 | reg &= ~(1UL << (map->source & 31)); | |
139 | ipu_write_reg(bank->ipu, reg, bank->control); | |
140 | ||
289b4e7a | 141 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
5296b56d GL |
142 | } |
143 | ||
6a035138 | 144 | static void ipu_irq_ack(struct irq_data *d) |
5296b56d | 145 | { |
6a035138 | 146 | struct ipu_irq_map *map = irq_data_get_irq_chip_data(d); |
5296b56d GL |
147 | struct ipu_irq_bank *bank; |
148 | unsigned long lock_flags; | |
149 | ||
289b4e7a | 150 | raw_spin_lock_irqsave(&bank_lock, lock_flags); |
5296b56d GL |
151 | |
152 | bank = map->bank; | |
153 | if (!bank) { | |
289b4e7a | 154 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
6a035138 | 155 | pr_err("IPU: %s(%u) - unmapped!\n", __func__, d->irq); |
5296b56d GL |
156 | return; |
157 | } | |
158 | ||
159 | ipu_write_reg(bank->ipu, 1UL << (map->source & 31), bank->status); | |
289b4e7a | 160 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
5296b56d GL |
161 | } |
162 | ||
163 | /** | |
164 | * ipu_irq_status() - returns the current interrupt status of the specified IRQ. | |
165 | * @irq: interrupt line to get status for. | |
166 | * @return: true if the interrupt is pending/asserted or false if the | |
167 | * interrupt is not pending. | |
168 | */ | |
169 | bool ipu_irq_status(unsigned int irq) | |
170 | { | |
6a035138 | 171 | struct ipu_irq_map *map = irq_get_chip_data(irq); |
5296b56d GL |
172 | struct ipu_irq_bank *bank; |
173 | unsigned long lock_flags; | |
174 | bool ret; | |
175 | ||
289b4e7a | 176 | raw_spin_lock_irqsave(&bank_lock, lock_flags); |
5296b56d GL |
177 | bank = map->bank; |
178 | ret = bank && ipu_read_reg(bank->ipu, bank->status) & | |
179 | (1UL << (map->source & 31)); | |
289b4e7a | 180 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
5296b56d GL |
181 | |
182 | return ret; | |
183 | } | |
184 | ||
185 | /** | |
186 | * ipu_irq_map() - map an IPU interrupt source to an IRQ number | |
187 | * @source: interrupt source bit position (see below) | |
188 | * @return: mapped IRQ number or negative error code | |
189 | * | |
190 | * The source parameter has to be explained further. On i.MX31 IPU has 137 IRQ | |
191 | * sources, they are broken down in 5 32-bit registers, like 32, 32, 24, 32, 17. | |
192 | * However, the source argument of this function is not the sequence number of | |
193 | * the possible IRQ, but rather its bit position. So, first interrupt in fourth | |
194 | * register has source number 96, and not 88. This makes calculations easier, | |
195 | * and also provides forward compatibility with any future IPU implementations | |
196 | * with any interrupt bit assignments. | |
197 | */ | |
198 | int ipu_irq_map(unsigned int source) | |
199 | { | |
200 | int i, ret = -ENOMEM; | |
201 | struct ipu_irq_map *map; | |
202 | ||
203 | might_sleep(); | |
204 | ||
205 | mutex_lock(&map_lock); | |
206 | map = src2map(source); | |
207 | if (map) { | |
208 | pr_err("IPU: Source %u already mapped to IRQ %u\n", source, map->irq); | |
209 | ret = -EBUSY; | |
210 | goto out; | |
211 | } | |
212 | ||
213 | for (i = 0; i < CONFIG_MX3_IPU_IRQS; i++) { | |
214 | if (irq_map[i].source < 0) { | |
215 | unsigned long lock_flags; | |
216 | ||
289b4e7a | 217 | raw_spin_lock_irqsave(&bank_lock, lock_flags); |
5296b56d GL |
218 | irq_map[i].source = source; |
219 | irq_map[i].bank = irq_bank + source / 32; | |
289b4e7a | 220 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
5296b56d GL |
221 | |
222 | ret = irq_map[i].irq; | |
223 | pr_debug("IPU: mapped source %u to IRQ %u\n", | |
224 | source, ret); | |
225 | break; | |
226 | } | |
227 | } | |
228 | out: | |
229 | mutex_unlock(&map_lock); | |
230 | ||
231 | if (ret < 0) | |
232 | pr_err("IPU: couldn't map source %u: %d\n", source, ret); | |
233 | ||
234 | return ret; | |
235 | } | |
236 | ||
237 | /** | |
238 | * ipu_irq_map() - map an IPU interrupt source to an IRQ number | |
239 | * @source: interrupt source bit position (see ipu_irq_map()) | |
240 | * @return: 0 or negative error code | |
241 | */ | |
242 | int ipu_irq_unmap(unsigned int source) | |
243 | { | |
244 | int i, ret = -EINVAL; | |
245 | ||
246 | might_sleep(); | |
247 | ||
248 | mutex_lock(&map_lock); | |
249 | for (i = 0; i < CONFIG_MX3_IPU_IRQS; i++) { | |
250 | if (irq_map[i].source == source) { | |
251 | unsigned long lock_flags; | |
252 | ||
253 | pr_debug("IPU: unmapped source %u from IRQ %u\n", | |
254 | source, irq_map[i].irq); | |
255 | ||
289b4e7a | 256 | raw_spin_lock_irqsave(&bank_lock, lock_flags); |
5296b56d GL |
257 | irq_map[i].source = -EINVAL; |
258 | irq_map[i].bank = NULL; | |
289b4e7a | 259 | raw_spin_unlock_irqrestore(&bank_lock, lock_flags); |
5296b56d GL |
260 | |
261 | ret = 0; | |
262 | break; | |
263 | } | |
264 | } | |
265 | mutex_unlock(&map_lock); | |
266 | ||
267 | return ret; | |
268 | } | |
269 | ||
270 | /* Chained IRQ handler for IPU error interrupt */ | |
271 | static void ipu_irq_err(unsigned int irq, struct irq_desc *desc) | |
272 | { | |
6a035138 | 273 | struct ipu *ipu = irq_get_handler_data(irq); |
5296b56d GL |
274 | u32 status; |
275 | int i, line; | |
276 | ||
277 | for (i = IPU_IRQ_NR_FN_BANKS; i < IPU_IRQ_NR_BANKS; i++) { | |
278 | struct ipu_irq_bank *bank = irq_bank + i; | |
279 | ||
289b4e7a | 280 | raw_spin_lock(&bank_lock); |
5296b56d GL |
281 | status = ipu_read_reg(ipu, bank->status); |
282 | /* | |
283 | * Don't think we have to clear all interrupts here, they will | |
284 | * be acked by ->handle_irq() (handle_level_irq). However, we | |
285 | * might want to clear unhandled interrupts after the loop... | |
286 | */ | |
287 | status &= ipu_read_reg(ipu, bank->control); | |
289b4e7a | 288 | raw_spin_unlock(&bank_lock); |
5296b56d GL |
289 | while ((line = ffs(status))) { |
290 | struct ipu_irq_map *map; | |
291 | ||
292 | line--; | |
293 | status &= ~(1UL << line); | |
294 | ||
289b4e7a | 295 | raw_spin_lock(&bank_lock); |
5296b56d GL |
296 | map = src2map(32 * i + line); |
297 | if (map) | |
298 | irq = map->irq; | |
289b4e7a | 299 | raw_spin_unlock(&bank_lock); |
5296b56d GL |
300 | |
301 | if (!map) { | |
302 | pr_err("IPU: Interrupt on unmapped source %u bank %d\n", | |
303 | line, i); | |
304 | continue; | |
305 | } | |
306 | generic_handle_irq(irq); | |
307 | } | |
308 | } | |
309 | } | |
310 | ||
311 | /* Chained IRQ handler for IPU function interrupt */ | |
312 | static void ipu_irq_fn(unsigned int irq, struct irq_desc *desc) | |
313 | { | |
6a035138 | 314 | struct ipu *ipu = irq_desc_get_handler_data(desc); |
5296b56d GL |
315 | u32 status; |
316 | int i, line; | |
317 | ||
318 | for (i = 0; i < IPU_IRQ_NR_FN_BANKS; i++) { | |
319 | struct ipu_irq_bank *bank = irq_bank + i; | |
320 | ||
289b4e7a | 321 | raw_spin_lock(&bank_lock); |
5296b56d GL |
322 | status = ipu_read_reg(ipu, bank->status); |
323 | /* Not clearing all interrupts, see above */ | |
324 | status &= ipu_read_reg(ipu, bank->control); | |
289b4e7a | 325 | raw_spin_unlock(&bank_lock); |
5296b56d GL |
326 | while ((line = ffs(status))) { |
327 | struct ipu_irq_map *map; | |
328 | ||
329 | line--; | |
330 | status &= ~(1UL << line); | |
331 | ||
289b4e7a | 332 | raw_spin_lock(&bank_lock); |
5296b56d GL |
333 | map = src2map(32 * i + line); |
334 | if (map) | |
335 | irq = map->irq; | |
289b4e7a | 336 | raw_spin_unlock(&bank_lock); |
5296b56d GL |
337 | |
338 | if (!map) { | |
339 | pr_err("IPU: Interrupt on unmapped source %u bank %d\n", | |
340 | line, i); | |
341 | continue; | |
342 | } | |
343 | generic_handle_irq(irq); | |
344 | } | |
345 | } | |
346 | } | |
347 | ||
348 | static struct irq_chip ipu_irq_chip = { | |
6a035138 TG |
349 | .name = "ipu_irq", |
350 | .irq_ack = ipu_irq_ack, | |
351 | .irq_mask = ipu_irq_mask, | |
352 | .irq_unmask = ipu_irq_unmask, | |
5296b56d GL |
353 | }; |
354 | ||
355 | /* Install the IRQ handler */ | |
234f2df5 | 356 | int __init ipu_irq_attach_irq(struct ipu *ipu, struct platform_device *dev) |
5296b56d | 357 | { |
88289c80 SG |
358 | unsigned int irq, i; |
359 | int irq_base = irq_alloc_descs(-1, 0, CONFIG_MX3_IPU_IRQS, | |
360 | numa_node_id()); | |
5296b56d | 361 | |
88289c80 SG |
362 | if (irq_base < 0) |
363 | return irq_base; | |
5296b56d GL |
364 | |
365 | for (i = 0; i < IPU_IRQ_NR_BANKS; i++) | |
366 | irq_bank[i].ipu = ipu; | |
367 | ||
368 | for (i = 0; i < CONFIG_MX3_IPU_IRQS; i++) { | |
369 | int ret; | |
370 | ||
371 | irq = irq_base + i; | |
6a035138 | 372 | ret = irq_set_chip(irq, &ipu_irq_chip); |
5296b56d GL |
373 | if (ret < 0) |
374 | return ret; | |
6a035138 | 375 | ret = irq_set_chip_data(irq, irq_map + i); |
5296b56d GL |
376 | if (ret < 0) |
377 | return ret; | |
378 | irq_map[i].ipu = ipu; | |
379 | irq_map[i].irq = irq; | |
380 | irq_map[i].source = -EINVAL; | |
6a035138 | 381 | irq_set_handler(irq, handle_level_irq); |
5296b56d GL |
382 | #ifdef CONFIG_ARM |
383 | set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); | |
384 | #endif | |
385 | } | |
386 | ||
6a035138 TG |
387 | irq_set_handler_data(ipu->irq_fn, ipu); |
388 | irq_set_chained_handler(ipu->irq_fn, ipu_irq_fn); | |
5296b56d | 389 | |
6a035138 TG |
390 | irq_set_handler_data(ipu->irq_err, ipu); |
391 | irq_set_chained_handler(ipu->irq_err, ipu_irq_err); | |
5296b56d | 392 | |
88289c80 SG |
393 | ipu->irq_base = irq_base; |
394 | ||
5296b56d GL |
395 | return 0; |
396 | } | |
397 | ||
398 | void ipu_irq_detach_irq(struct ipu *ipu, struct platform_device *dev) | |
399 | { | |
5296b56d GL |
400 | unsigned int irq, irq_base; |
401 | ||
88289c80 | 402 | irq_base = ipu->irq_base; |
5296b56d | 403 | |
6a035138 TG |
404 | irq_set_chained_handler(ipu->irq_fn, NULL); |
405 | irq_set_handler_data(ipu->irq_fn, NULL); | |
5296b56d | 406 | |
6a035138 TG |
407 | irq_set_chained_handler(ipu->irq_err, NULL); |
408 | irq_set_handler_data(ipu->irq_err, NULL); | |
5296b56d GL |
409 | |
410 | for (irq = irq_base; irq < irq_base + CONFIG_MX3_IPU_IRQS; irq++) { | |
411 | #ifdef CONFIG_ARM | |
412 | set_irq_flags(irq, 0); | |
413 | #endif | |
6a035138 TG |
414 | irq_set_chip(irq, NULL); |
415 | irq_set_chip_data(irq, NULL); | |
5296b56d GL |
416 | } |
417 | } |