2 * SuperH interrupt controller module
4 * Copyright (c) 2007 Magnus Damm
5 * Based on sh_timer.c and arm_timer.c by Paul Brook
6 * Copyright (c) 2005-2006 CodeSourcery.
8 * This code is licensed under the GPL.
11 #include "qemu/osdep.h"
12 #include "qemu-common.h"
14 #include "hw/sh4/sh_intc.h"
16 #include "hw/sh4/sh.h"
19 //#define DEBUG_INTC_SOURCES
21 #define INTC_A7(x) ((x) & 0x1fffffff)
23 void sh_intc_toggle_source(struct intc_source
*source
,
24 int enable_adj
, int assert_adj
)
26 int enable_changed
= 0;
27 int pending_changed
= 0;
30 if ((source
->enable_count
== source
->enable_max
) && (enable_adj
== -1))
33 source
->enable_count
+= enable_adj
;
35 if (source
->enable_count
== source
->enable_max
)
38 source
->asserted
+= assert_adj
;
40 old_pending
= source
->pending
;
41 source
->pending
= source
->asserted
&&
42 (source
->enable_count
== source
->enable_max
);
44 if (old_pending
!= source
->pending
)
47 if (pending_changed
) {
48 if (source
->pending
) {
49 source
->parent
->pending
++;
50 if (source
->parent
->pending
== 1) {
51 cpu_interrupt(first_cpu
, CPU_INTERRUPT_HARD
);
54 source
->parent
->pending
--;
55 if (source
->parent
->pending
== 0) {
56 cpu_reset_interrupt(first_cpu
, CPU_INTERRUPT_HARD
);
61 if (enable_changed
|| assert_adj
|| pending_changed
) {
62 #ifdef DEBUG_INTC_SOURCES
63 printf("sh_intc: (%d/%d/%d/%d) interrupt source 0x%x %s%s%s\n",
64 source
->parent
->pending
,
69 source
->asserted
? "asserted " :
70 assert_adj
? "deasserted" : "",
71 enable_changed
== 1 ? "enabled " :
72 enable_changed
== -1 ? "disabled " : "",
73 source
->pending
? "pending" : "");
78 static void sh_intc_set_irq (void *opaque
, int n
, int level
)
80 struct intc_desc
*desc
= opaque
;
81 struct intc_source
*source
= &(desc
->sources
[n
]);
83 if (level
&& !source
->asserted
)
84 sh_intc_toggle_source(source
, 0, 1);
85 else if (!level
&& source
->asserted
)
86 sh_intc_toggle_source(source
, 0, -1);
89 int sh_intc_get_pending_vector(struct intc_desc
*desc
, int imask
)
93 /* slow: use a linked lists of pending sources instead */
94 /* wrong: take interrupt priority into account (one list per priority) */
97 return -1; /* FIXME, update code to include priority per source */
100 for (i
= 0; i
< desc
->nr_sources
; i
++) {
101 struct intc_source
*source
= desc
->sources
+ i
;
103 if (source
->pending
) {
104 #ifdef DEBUG_INTC_SOURCES
105 printf("sh_intc: (%d) returning interrupt source 0x%x\n",
106 desc
->pending
, source
->vect
);
115 #define INTC_MODE_NONE 0
116 #define INTC_MODE_DUAL_SET 1
117 #define INTC_MODE_DUAL_CLR 2
118 #define INTC_MODE_ENABLE_REG 3
119 #define INTC_MODE_MASK_REG 4
120 #define INTC_MODE_IS_PRIO 8
122 static unsigned int sh_intc_mode(unsigned long address
,
123 unsigned long set_reg
, unsigned long clr_reg
)
125 if ((address
!= INTC_A7(set_reg
)) &&
126 (address
!= INTC_A7(clr_reg
)))
127 return INTC_MODE_NONE
;
129 if (set_reg
&& clr_reg
) {
130 if (address
== INTC_A7(set_reg
))
131 return INTC_MODE_DUAL_SET
;
133 return INTC_MODE_DUAL_CLR
;
137 return INTC_MODE_ENABLE_REG
;
139 return INTC_MODE_MASK_REG
;
142 static void sh_intc_locate(struct intc_desc
*desc
,
143 unsigned long address
,
144 unsigned long **datap
,
150 unsigned int i
, mode
;
152 /* this is slow but works for now */
154 if (desc
->mask_regs
) {
155 for (i
= 0; i
< desc
->nr_mask_regs
; i
++) {
156 struct intc_mask_reg
*mr
= desc
->mask_regs
+ i
;
158 mode
= sh_intc_mode(address
, mr
->set_reg
, mr
->clr_reg
);
159 if (mode
== INTC_MODE_NONE
)
164 *enums
= mr
->enum_ids
;
165 *first
= mr
->reg_width
- 1;
171 if (desc
->prio_regs
) {
172 for (i
= 0; i
< desc
->nr_prio_regs
; i
++) {
173 struct intc_prio_reg
*pr
= desc
->prio_regs
+ i
;
175 mode
= sh_intc_mode(address
, pr
->set_reg
, pr
->clr_reg
);
176 if (mode
== INTC_MODE_NONE
)
179 *modep
= mode
| INTC_MODE_IS_PRIO
;
181 *enums
= pr
->enum_ids
;
182 *first
= (pr
->reg_width
/ pr
->field_width
) - 1;
183 *width
= pr
->field_width
;
191 static void sh_intc_toggle_mask(struct intc_desc
*desc
, intc_enum id
,
192 int enable
, int is_group
)
194 struct intc_source
*source
= desc
->sources
+ id
;
199 if (!source
->next_enum_id
&& (!source
->enable_max
|| !source
->vect
)) {
200 #ifdef DEBUG_INTC_SOURCES
201 printf("sh_intc: reserved interrupt source %d modified\n", id
);
207 sh_intc_toggle_source(source
, enable
? 1 : -1, 0);
211 printf("setting interrupt group %d to %d\n", id
, !!enable
);
215 if ((is_group
|| !source
->vect
) && source
->next_enum_id
) {
216 sh_intc_toggle_mask(desc
, source
->next_enum_id
, enable
, 1);
221 printf("setting interrupt group %d to %d - done\n", id
, !!enable
);
226 static uint64_t sh_intc_read(void *opaque
, hwaddr offset
,
229 struct intc_desc
*desc
= opaque
;
230 intc_enum
*enum_ids
= NULL
;
231 unsigned int first
= 0;
232 unsigned int width
= 0;
233 unsigned int mode
= 0;
234 unsigned long *valuep
;
237 printf("sh_intc_read 0x%lx\n", (unsigned long) offset
);
240 sh_intc_locate(desc
, (unsigned long)offset
, &valuep
,
241 &enum_ids
, &first
, &width
, &mode
);
245 static void sh_intc_write(void *opaque
, hwaddr offset
,
246 uint64_t value
, unsigned size
)
248 struct intc_desc
*desc
= opaque
;
249 intc_enum
*enum_ids
= NULL
;
250 unsigned int first
= 0;
251 unsigned int width
= 0;
252 unsigned int mode
= 0;
254 unsigned long *valuep
;
258 printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset
, value
);
261 sh_intc_locate(desc
, (unsigned long)offset
, &valuep
,
262 &enum_ids
, &first
, &width
, &mode
);
265 case INTC_MODE_ENABLE_REG
| INTC_MODE_IS_PRIO
: break;
266 case INTC_MODE_DUAL_SET
: value
|= *valuep
; break;
267 case INTC_MODE_DUAL_CLR
: value
= *valuep
& ~value
; break;
271 for (k
= 0; k
<= first
; k
++) {
272 mask
= ((1 << width
) - 1) << ((first
- k
) * width
);
274 if ((*valuep
& mask
) == (value
& mask
))
277 printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n",
278 k
, first
, enum_ids
[k
], (unsigned int)mask
);
280 sh_intc_toggle_mask(desc
, enum_ids
[k
], value
& mask
, 0);
286 printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset
, value
);
290 static const MemoryRegionOps sh_intc_ops
= {
291 .read
= sh_intc_read
,
292 .write
= sh_intc_write
,
293 .endianness
= DEVICE_NATIVE_ENDIAN
,
296 struct intc_source
*sh_intc_source(struct intc_desc
*desc
, intc_enum id
)
299 return desc
->sources
+ id
;
304 static unsigned int sh_intc_register(MemoryRegion
*sysmem
,
305 struct intc_desc
*desc
,
306 const unsigned long address
,
309 const unsigned int index
)
312 MemoryRegion
*iomem
, *iomem_p4
, *iomem_a7
;
318 iomem
= &desc
->iomem
;
319 iomem_p4
= desc
->iomem_aliases
+ index
;
320 iomem_a7
= iomem_p4
+ 1;
322 #define SH_INTC_IOMEM_FORMAT "interrupt-controller-%s-%s-%s"
323 snprintf(name
, sizeof(name
), SH_INTC_IOMEM_FORMAT
, type
, action
, "p4");
324 memory_region_init_alias(iomem_p4
, NULL
, name
, iomem
, INTC_A7(address
), 4);
325 memory_region_add_subregion(sysmem
, P4ADDR(address
), iomem_p4
);
327 snprintf(name
, sizeof(name
), SH_INTC_IOMEM_FORMAT
, type
, action
, "a7");
328 memory_region_init_alias(iomem_a7
, NULL
, name
, iomem
, INTC_A7(address
), 4);
329 memory_region_add_subregion(sysmem
, A7ADDR(address
), iomem_a7
);
330 #undef SH_INTC_IOMEM_FORMAT
332 /* used to increment aliases index */
336 static void sh_intc_register_source(struct intc_desc
*desc
,
338 struct intc_group
*groups
,
342 struct intc_source
*s
;
344 if (desc
->mask_regs
) {
345 for (i
= 0; i
< desc
->nr_mask_regs
; i
++) {
346 struct intc_mask_reg
*mr
= desc
->mask_regs
+ i
;
348 for (k
= 0; k
< ARRAY_SIZE(mr
->enum_ids
); k
++) {
349 if (mr
->enum_ids
[k
] != source
)
352 s
= sh_intc_source(desc
, mr
->enum_ids
[k
]);
359 if (desc
->prio_regs
) {
360 for (i
= 0; i
< desc
->nr_prio_regs
; i
++) {
361 struct intc_prio_reg
*pr
= desc
->prio_regs
+ i
;
363 for (k
= 0; k
< ARRAY_SIZE(pr
->enum_ids
); k
++) {
364 if (pr
->enum_ids
[k
] != source
)
367 s
= sh_intc_source(desc
, pr
->enum_ids
[k
]);
375 for (i
= 0; i
< nr_groups
; i
++) {
376 struct intc_group
*gr
= groups
+ i
;
378 for (k
= 0; k
< ARRAY_SIZE(gr
->enum_ids
); k
++) {
379 if (gr
->enum_ids
[k
] != source
)
382 s
= sh_intc_source(desc
, gr
->enum_ids
[k
]);
391 void sh_intc_register_sources(struct intc_desc
*desc
,
392 struct intc_vect
*vectors
,
394 struct intc_group
*groups
,
398 struct intc_source
*s
;
400 for (i
= 0; i
< nr_vectors
; i
++) {
401 struct intc_vect
*vect
= vectors
+ i
;
403 sh_intc_register_source(desc
, vect
->enum_id
, groups
, nr_groups
);
404 s
= sh_intc_source(desc
, vect
->enum_id
);
406 s
->vect
= vect
->vect
;
408 #ifdef DEBUG_INTC_SOURCES
409 printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
410 vect
->enum_id
, s
->vect
, s
->enable_count
, s
->enable_max
);
416 for (i
= 0; i
< nr_groups
; i
++) {
417 struct intc_group
*gr
= groups
+ i
;
419 s
= sh_intc_source(desc
, gr
->enum_id
);
420 s
->next_enum_id
= gr
->enum_ids
[0];
422 for (k
= 1; k
< ARRAY_SIZE(gr
->enum_ids
); k
++) {
423 if (!gr
->enum_ids
[k
])
426 s
= sh_intc_source(desc
, gr
->enum_ids
[k
- 1]);
427 s
->next_enum_id
= gr
->enum_ids
[k
];
430 #ifdef DEBUG_INTC_SOURCES
431 printf("sh_intc: registered group %d (%d/%d)\n",
432 gr
->enum_id
, s
->enable_count
, s
->enable_max
);
438 int sh_intc_init(MemoryRegion
*sysmem
,
439 struct intc_desc
*desc
,
441 struct intc_mask_reg
*mask_regs
,
443 struct intc_prio_reg
*prio_regs
,
449 desc
->nr_sources
= nr_sources
;
450 desc
->mask_regs
= mask_regs
;
451 desc
->nr_mask_regs
= nr_mask_regs
;
452 desc
->prio_regs
= prio_regs
;
453 desc
->nr_prio_regs
= nr_prio_regs
;
454 /* Allocate 4 MemoryRegions per register (2 actions * 2 aliases).
456 desc
->iomem_aliases
= g_new0(MemoryRegion
,
457 (nr_mask_regs
+ nr_prio_regs
) * 4);
460 i
= sizeof(struct intc_source
) * nr_sources
;
461 desc
->sources
= g_malloc0(i
);
463 for (i
= 0; i
< desc
->nr_sources
; i
++) {
464 struct intc_source
*source
= desc
->sources
+ i
;
466 source
->parent
= desc
;
469 desc
->irqs
= qemu_allocate_irqs(sh_intc_set_irq
, desc
, nr_sources
);
471 memory_region_init_io(&desc
->iomem
, NULL
, &sh_intc_ops
, desc
,
472 "interrupt-controller", 0x100000000ULL
);
474 #define INT_REG_PARAMS(reg_struct, type, action, j) \
475 reg_struct->action##_reg, #type, #action, j
476 if (desc
->mask_regs
) {
477 for (i
= 0; i
< desc
->nr_mask_regs
; i
++) {
478 struct intc_mask_reg
*mr
= desc
->mask_regs
+ i
;
480 j
+= sh_intc_register(sysmem
, desc
,
481 INT_REG_PARAMS(mr
, mask
, set
, j
));
482 j
+= sh_intc_register(sysmem
, desc
,
483 INT_REG_PARAMS(mr
, mask
, clr
, j
));
487 if (desc
->prio_regs
) {
488 for (i
= 0; i
< desc
->nr_prio_regs
; i
++) {
489 struct intc_prio_reg
*pr
= desc
->prio_regs
+ i
;
491 j
+= sh_intc_register(sysmem
, desc
,
492 INT_REG_PARAMS(pr
, prio
, set
, j
));
493 j
+= sh_intc_register(sysmem
, desc
,
494 INT_REG_PARAMS(pr
, prio
, clr
, j
));
497 #undef INT_REG_PARAMS
502 /* Assert level <n> IRL interrupt.
503 0:deassert. 1:lowest priority,... 15:highest priority. */
504 void sh_intc_set_irl(void *opaque
, int n
, int level
)
506 struct intc_source
*s
= opaque
;
507 int i
, irl
= level
^ 15;
508 for (i
= 0; (s
= sh_intc_source(s
->parent
, s
->next_enum_id
)); i
++) {
510 sh_intc_toggle_source(s
, s
->enable_count
?0:1, s
->asserted
?0:1);
513 sh_intc_toggle_source(s
, 0, -1);