1 | /* |
2 | * Samsung exynos4210 Interrupt Combiner |
3 | * |
4 | * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. |
5 | * All rights reserved. |
6 | * |
7 | * Evgeny Voevodin <e.voevodin@samsung.com> |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify it |
10 | * under the terms of the GNU General Public License as published by the |
11 | * Free Software Foundation; either version 2 of the License, or (at your |
12 | * option) any later version. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | * See the GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License along |
20 | * with this program; if not, see <http://www.gnu.org/licenses/>. |
21 | */ |
22 | |
23 | /* |
24 | * Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines |
25 | * IRQ sources into groups and provides signal output to GIC from each group. It |
26 | * is driven by common mask and enable/disable logic. Take a note that not all |
27 | * IRQs are passed to GIC through Combiner. |
28 | */ |
29 | |
30 | #include "qemu/osdep.h" |
31 | #include "hw/sysbus.h" |
32 | #include "migration/vmstate.h" |
33 | #include "qemu/module.h" |
34 | |
35 | #include "hw/arm/exynos4210.h" |
36 | #include "hw/hw.h" |
37 | #include "hw/irq.h" |
38 | #include "hw/qdev-properties.h" |
39 | |
40 | //#define DEBUG_COMBINER |
41 | |
42 | #ifdef DEBUG_COMBINER |
43 | #define DPRINTF(fmt, ...) \ |
44 | do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \ |
45 | ## __VA_ARGS__); } while (0) |
46 | #else |
47 | #define DPRINTF(fmt, ...) do {} while (0) |
48 | #endif |
49 | |
50 | #define IIC_NGRP 64 /* Internal Interrupt Combiner |
51 | Groups number */ |
52 | #define IIC_NIRQ (IIC_NGRP * 8)/* Internal Interrupt Combiner |
53 | Interrupts number */ |
54 | #define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */ |
55 | #define IIC_REGSET_SIZE 0x41 |
56 | |
57 | /* |
58 | * State for each output signal of internal combiner |
59 | */ |
60 | typedef struct CombinerGroupState { |
61 | uint8_t src_mask; /* 1 - source enabled, 0 - disabled */ |
62 | uint8_t src_pending; /* Pending source interrupts before masking */ |
63 | } CombinerGroupState; |
64 | |
65 | #define TYPE_EXYNOS4210_COMBINER "exynos4210.combiner" |
66 | #define EXYNOS4210_COMBINER(obj) \ |
67 | OBJECT_CHECK(Exynos4210CombinerState, (obj), TYPE_EXYNOS4210_COMBINER) |
68 | |
69 | typedef struct Exynos4210CombinerState { |
70 | SysBusDevice parent_obj; |
71 | |
72 | MemoryRegion iomem; |
73 | |
74 | struct CombinerGroupState group[IIC_NGRP]; |
75 | uint32_t reg_set[IIC_REGSET_SIZE]; |
76 | uint32_t icipsr[2]; |
77 | uint32_t external; /* 1 means that this combiner is external */ |
78 | |
79 | qemu_irq output_irq[IIC_NGRP]; |
80 | } Exynos4210CombinerState; |
81 | |
82 | static const VMStateDescription vmstate_exynos4210_combiner_group_state = { |
83 | .name = "exynos4210.combiner.groupstate" , |
84 | .version_id = 1, |
85 | .minimum_version_id = 1, |
86 | .fields = (VMStateField[]) { |
87 | VMSTATE_UINT8(src_mask, CombinerGroupState), |
88 | VMSTATE_UINT8(src_pending, CombinerGroupState), |
89 | VMSTATE_END_OF_LIST() |
90 | } |
91 | }; |
92 | |
93 | static const VMStateDescription vmstate_exynos4210_combiner = { |
94 | .name = "exynos4210.combiner" , |
95 | .version_id = 1, |
96 | .minimum_version_id = 1, |
97 | .fields = (VMStateField[]) { |
98 | VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0, |
99 | vmstate_exynos4210_combiner_group_state, CombinerGroupState), |
100 | VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState, |
101 | IIC_REGSET_SIZE), |
102 | VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2), |
103 | VMSTATE_UINT32(external, Exynos4210CombinerState), |
104 | VMSTATE_END_OF_LIST() |
105 | } |
106 | }; |
107 | |
108 | /* |
109 | * Get Combiner input GPIO into irqs structure |
110 | */ |
111 | void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev, |
112 | int ext) |
113 | { |
114 | int n; |
115 | int bit; |
116 | int max; |
117 | qemu_irq *irq; |
118 | |
119 | max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ : |
120 | EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; |
121 | irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq; |
122 | |
123 | /* |
124 | * Some IRQs of Int/External Combiner are going to two Combiners groups, |
125 | * so let split them. |
126 | */ |
127 | for (n = 0; n < max; n++) { |
128 | |
129 | bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n); |
130 | |
131 | switch (n) { |
132 | /* MDNIE_LCD1 INTG1 */ |
133 | case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ... |
134 | EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3): |
135 | irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), |
136 | irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]); |
137 | continue; |
138 | |
139 | /* TMU INTG3 */ |
140 | case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4): |
141 | irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), |
142 | irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]); |
143 | continue; |
144 | |
145 | /* LCD1 INTG12 */ |
146 | case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ... |
147 | EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3): |
148 | irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), |
149 | irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]); |
150 | continue; |
151 | |
152 | /* Multi-Core Timer INTG12 */ |
153 | case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ... |
154 | EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8): |
155 | irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), |
156 | irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); |
157 | continue; |
158 | |
159 | /* Multi-Core Timer INTG35 */ |
160 | case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ... |
161 | EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8): |
162 | irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), |
163 | irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); |
164 | continue; |
165 | |
166 | /* Multi-Core Timer INTG51 */ |
167 | case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ... |
168 | EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8): |
169 | irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), |
170 | irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); |
171 | continue; |
172 | |
173 | /* Multi-Core Timer INTG53 */ |
174 | case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ... |
175 | EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8): |
176 | irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), |
177 | irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); |
178 | continue; |
179 | } |
180 | |
181 | irq[n] = qdev_get_gpio_in(dev, n); |
182 | } |
183 | } |
184 | |
185 | static uint64_t |
186 | exynos4210_combiner_read(void *opaque, hwaddr offset, unsigned size) |
187 | { |
188 | struct Exynos4210CombinerState *s = |
189 | (struct Exynos4210CombinerState *)opaque; |
190 | uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and |
191 | get a start of corresponding group quad */ |
192 | uint32_t grp_quad_base_n; /* Base of group quad */ |
193 | uint32_t reg_n; /* Register number inside the quad */ |
194 | uint32_t val; |
195 | |
196 | req_quad_base_n = offset >> 4; |
197 | grp_quad_base_n = req_quad_base_n << 2; |
198 | reg_n = (offset - (req_quad_base_n << 4)) >> 2; |
199 | |
200 | if (req_quad_base_n >= IIC_NGRP) { |
201 | /* Read of ICIPSR register */ |
202 | return s->icipsr[reg_n]; |
203 | } |
204 | |
205 | val = 0; |
206 | |
207 | switch (reg_n) { |
208 | /* IISTR */ |
209 | case 2: |
210 | val |= s->group[grp_quad_base_n].src_pending; |
211 | val |= s->group[grp_quad_base_n + 1].src_pending << 8; |
212 | val |= s->group[grp_quad_base_n + 2].src_pending << 16; |
213 | val |= s->group[grp_quad_base_n + 3].src_pending << 24; |
214 | break; |
215 | /* IIMSR */ |
216 | case 3: |
217 | val |= s->group[grp_quad_base_n].src_mask & |
218 | s->group[grp_quad_base_n].src_pending; |
219 | val |= (s->group[grp_quad_base_n + 1].src_mask & |
220 | s->group[grp_quad_base_n + 1].src_pending) << 8; |
221 | val |= (s->group[grp_quad_base_n + 2].src_mask & |
222 | s->group[grp_quad_base_n + 2].src_pending) << 16; |
223 | val |= (s->group[grp_quad_base_n + 3].src_mask & |
224 | s->group[grp_quad_base_n + 3].src_pending) << 24; |
225 | break; |
226 | default: |
227 | if (offset >> 2 >= IIC_REGSET_SIZE) { |
228 | hw_error("exynos4210.combiner: overflow of reg_set by 0x" |
229 | TARGET_FMT_plx "offset\n" , offset); |
230 | } |
231 | val = s->reg_set[offset >> 2]; |
232 | return 0; |
233 | } |
234 | return val; |
235 | } |
236 | |
237 | static void exynos4210_combiner_update(void *opaque, uint8_t group_n) |
238 | { |
239 | struct Exynos4210CombinerState *s = |
240 | (struct Exynos4210CombinerState *)opaque; |
241 | |
242 | /* Send interrupt if needed */ |
243 | if (s->group[group_n].src_mask & s->group[group_n].src_pending) { |
244 | #ifdef DEBUG_COMBINER |
245 | if (group_n != 26) { |
246 | /* skip uart */ |
247 | DPRINTF("%s raise IRQ[%d]\n" , s->external ? "EXT" : "INT" , group_n); |
248 | } |
249 | #endif |
250 | |
251 | /* Set Combiner interrupt pending status after masking */ |
252 | if (group_n >= 32) { |
253 | s->icipsr[1] |= 1 << (group_n - 32); |
254 | } else { |
255 | s->icipsr[0] |= 1 << group_n; |
256 | } |
257 | |
258 | qemu_irq_raise(s->output_irq[group_n]); |
259 | } else { |
260 | #ifdef DEBUG_COMBINER |
261 | if (group_n != 26) { |
262 | /* skip uart */ |
263 | DPRINTF("%s lower IRQ[%d]\n" , s->external ? "EXT" : "INT" , group_n); |
264 | } |
265 | #endif |
266 | |
267 | /* Set Combiner interrupt pending status after masking */ |
268 | if (group_n >= 32) { |
269 | s->icipsr[1] &= ~(1 << (group_n - 32)); |
270 | } else { |
271 | s->icipsr[0] &= ~(1 << group_n); |
272 | } |
273 | |
274 | qemu_irq_lower(s->output_irq[group_n]); |
275 | } |
276 | } |
277 | |
278 | static void exynos4210_combiner_write(void *opaque, hwaddr offset, |
279 | uint64_t val, unsigned size) |
280 | { |
281 | struct Exynos4210CombinerState *s = |
282 | (struct Exynos4210CombinerState *)opaque; |
283 | uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and |
284 | get a start of corresponding group quad */ |
285 | uint32_t grp_quad_base_n; /* Base of group quad */ |
286 | uint32_t reg_n; /* Register number inside the quad */ |
287 | |
288 | req_quad_base_n = offset >> 4; |
289 | grp_quad_base_n = req_quad_base_n << 2; |
290 | reg_n = (offset - (req_quad_base_n << 4)) >> 2; |
291 | |
292 | if (req_quad_base_n >= IIC_NGRP) { |
293 | hw_error("exynos4210.combiner: unallowed write access at offset 0x" |
294 | TARGET_FMT_plx "\n" , offset); |
295 | return; |
296 | } |
297 | |
298 | if (reg_n > 1) { |
299 | hw_error("exynos4210.combiner: unallowed write access at offset 0x" |
300 | TARGET_FMT_plx "\n" , offset); |
301 | return; |
302 | } |
303 | |
304 | if (offset >> 2 >= IIC_REGSET_SIZE) { |
305 | hw_error("exynos4210.combiner: overflow of reg_set by 0x" |
306 | TARGET_FMT_plx "offset\n" , offset); |
307 | } |
308 | s->reg_set[offset >> 2] = val; |
309 | |
310 | switch (reg_n) { |
311 | /* IIESR */ |
312 | case 0: |
313 | /* FIXME: what if irq is pending, allowed by mask, and we allow it |
314 | * again. Interrupt will rise again! */ |
315 | |
316 | DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n" , |
317 | s->external ? "EXT" : "INT" , |
318 | grp_quad_base_n, |
319 | grp_quad_base_n + 1, |
320 | grp_quad_base_n + 2, |
321 | grp_quad_base_n + 3); |
322 | |
323 | /* Enable interrupt sources */ |
324 | s->group[grp_quad_base_n].src_mask |= val & 0xFF; |
325 | s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8; |
326 | s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16; |
327 | s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24; |
328 | |
329 | exynos4210_combiner_update(s, grp_quad_base_n); |
330 | exynos4210_combiner_update(s, grp_quad_base_n + 1); |
331 | exynos4210_combiner_update(s, grp_quad_base_n + 2); |
332 | exynos4210_combiner_update(s, grp_quad_base_n + 3); |
333 | break; |
334 | /* IIECR */ |
335 | case 1: |
336 | DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n" , |
337 | s->external ? "EXT" : "INT" , |
338 | grp_quad_base_n, |
339 | grp_quad_base_n + 1, |
340 | grp_quad_base_n + 2, |
341 | grp_quad_base_n + 3); |
342 | |
343 | /* Disable interrupt sources */ |
344 | s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF); |
345 | s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8); |
346 | s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16); |
347 | s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24); |
348 | |
349 | exynos4210_combiner_update(s, grp_quad_base_n); |
350 | exynos4210_combiner_update(s, grp_quad_base_n + 1); |
351 | exynos4210_combiner_update(s, grp_quad_base_n + 2); |
352 | exynos4210_combiner_update(s, grp_quad_base_n + 3); |
353 | break; |
354 | default: |
355 | hw_error("exynos4210.combiner: unallowed write access at offset 0x" |
356 | TARGET_FMT_plx "\n" , offset); |
357 | break; |
358 | } |
359 | } |
360 | |
361 | /* Get combiner group and bit from irq number */ |
362 | static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit) |
363 | { |
364 | *bit = irq - ((irq >> 3) << 3); |
365 | return irq >> 3; |
366 | } |
367 | |
368 | /* Process a change in an external IRQ input. */ |
369 | static void exynos4210_combiner_handler(void *opaque, int irq, int level) |
370 | { |
371 | struct Exynos4210CombinerState *s = |
372 | (struct Exynos4210CombinerState *)opaque; |
373 | uint8_t bit_n, group_n; |
374 | |
375 | group_n = get_combiner_group_and_bit(irq, &bit_n); |
376 | |
377 | if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) { |
378 | DPRINTF("%s unallowed IRQ group 0x%x\n" , s->external ? "EXT" : "INT" |
379 | , group_n); |
380 | return; |
381 | } |
382 | |
383 | if (level) { |
384 | s->group[group_n].src_pending |= 1 << bit_n; |
385 | } else { |
386 | s->group[group_n].src_pending &= ~(1 << bit_n); |
387 | } |
388 | |
389 | exynos4210_combiner_update(s, group_n); |
390 | } |
391 | |
392 | static void exynos4210_combiner_reset(DeviceState *d) |
393 | { |
394 | struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d; |
395 | |
396 | memset(&s->group, 0, sizeof(s->group)); |
397 | memset(&s->reg_set, 0, sizeof(s->reg_set)); |
398 | |
399 | s->reg_set[0xC0 >> 2] = 0x01010101; |
400 | s->reg_set[0xC4 >> 2] = 0x01010101; |
401 | s->reg_set[0xD0 >> 2] = 0x01010101; |
402 | s->reg_set[0xD4 >> 2] = 0x01010101; |
403 | } |
404 | |
405 | static const MemoryRegionOps exynos4210_combiner_ops = { |
406 | .read = exynos4210_combiner_read, |
407 | .write = exynos4210_combiner_write, |
408 | .endianness = DEVICE_NATIVE_ENDIAN, |
409 | }; |
410 | |
411 | /* |
412 | * Internal Combiner initialization. |
413 | */ |
414 | static void exynos4210_combiner_init(Object *obj) |
415 | { |
416 | DeviceState *dev = DEVICE(obj); |
417 | Exynos4210CombinerState *s = EXYNOS4210_COMBINER(obj); |
418 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
419 | unsigned int i; |
420 | |
421 | /* Allocate general purpose input signals and connect a handler to each of |
422 | * them */ |
423 | qdev_init_gpio_in(dev, exynos4210_combiner_handler, IIC_NIRQ); |
424 | |
425 | /* Connect SysBusDev irqs to device specific irqs */ |
426 | for (i = 0; i < IIC_NGRP; i++) { |
427 | sysbus_init_irq(sbd, &s->output_irq[i]); |
428 | } |
429 | |
430 | memory_region_init_io(&s->iomem, obj, &exynos4210_combiner_ops, s, |
431 | "exynos4210-combiner" , IIC_REGION_SIZE); |
432 | sysbus_init_mmio(sbd, &s->iomem); |
433 | } |
434 | |
435 | static Property exynos4210_combiner_properties[] = { |
436 | DEFINE_PROP_UINT32("external" , Exynos4210CombinerState, external, 0), |
437 | DEFINE_PROP_END_OF_LIST(), |
438 | }; |
439 | |
440 | static void exynos4210_combiner_class_init(ObjectClass *klass, void *data) |
441 | { |
442 | DeviceClass *dc = DEVICE_CLASS(klass); |
443 | |
444 | dc->reset = exynos4210_combiner_reset; |
445 | dc->props = exynos4210_combiner_properties; |
446 | dc->vmsd = &vmstate_exynos4210_combiner; |
447 | } |
448 | |
449 | static const TypeInfo exynos4210_combiner_info = { |
450 | .name = TYPE_EXYNOS4210_COMBINER, |
451 | .parent = TYPE_SYS_BUS_DEVICE, |
452 | .instance_size = sizeof(Exynos4210CombinerState), |
453 | .instance_init = exynos4210_combiner_init, |
454 | .class_init = exynos4210_combiner_class_init, |
455 | }; |
456 | |
457 | static void exynos4210_combiner_register_types(void) |
458 | { |
459 | type_register_static(&exynos4210_combiner_info); |
460 | } |
461 | |
462 | type_init(exynos4210_combiner_register_types) |
463 | |