3 * Samsung S3C24XX IRQ controller emulation
5 * Copyright 2009 Daniel Silverstone and Vincent Sanders
7 * Copyright 2010, 2013 Stefan Weil
9 * This file is under the terms of the GNU General Public License Version 2.
12 #include "qemu/osdep.h"
15 #include "exec/address-spaces.h" /* get_system_memory */
16 #include "migration/register.h" /* register_savevm_live */
20 /* IRQ request status RW WORD */
21 #define S3C_IRQ_SRCPND 0
22 /* Interrupt mode control WR WORD */
23 #define S3C_IRQ_INTMOD 1
24 /* Interrupt mask control RW WORD */
25 #define S3C_IRQ_INTMSK 2
26 /* IRQ priority control WR WORD */
27 #define S3C_IRQ_PRIORITY 3
28 /* Interrupt request status RW WORD */
29 #define S3C_IRQ_INTPND 4
30 /* Interrupt request source offset RO WORD */
31 #define S3C_IRQ_OFFSET 5
32 /* Sub-source pending RW WORD */
33 #define S3C_IRQ_SUBSRCPND 6
34 /* Interrupt sub-mask RW WORD */
35 #define S3C_IRQ_INTSUBMSK 7
37 /* Interrupt controller state */
38 struct s3c24xx_irq_state_s
{
44 uint32_t irq_main_level
, irq_subsrc_level
;
49 /* Take the status of the srcpnd register, percolate it through, raise to CPU
53 s3c24xx_percolate_interrupt(struct s3c24xx_irq_state_s
*s
)
55 uint32_t ints
= (s
->irq_reg
[S3C_IRQ_SRCPND
] & ~s
->irq_reg
[S3C_IRQ_INTMSK
]);
56 int fsb
= ctz32(ints
) + 1;
58 /* TODO: Priority encoder could go here */
59 if (ints
& s
->irq_reg
[S3C_IRQ_INTMOD
]) {
61 cpu_interrupt(CPU(s
->cpu
), CPU_INTERRUPT_FIQ
);
64 /* No FIQ here today */
65 cpu_reset_interrupt(CPU(s
->cpu
), CPU_INTERRUPT_FIQ
);
68 /* No FIQ, check for a normal IRQ */
70 if ((s
->irq_reg
[S3C_IRQ_INTPND
] == 0) ||
71 (s
->irq_reg
[S3C_IRQ_INTPND
] > 1<<(fsb
-1))) {
72 /* Current INTPND is lower priority than fsb of ints (or empty) */
73 s
->irq_reg
[S3C_IRQ_INTPND
] = 1<<(fsb
-1);
74 s
->irq_reg
[S3C_IRQ_OFFSET
] = fsb
-1;
77 /* No FSB, thus no IRQ, thus nothing to do yet */
80 if (s
->irq_reg
[S3C_IRQ_INTPND
] != 0) {
81 cpu_interrupt(CPU(s
->cpu
), CPU_INTERRUPT_HARD
);
83 cpu_reset_interrupt(CPU(s
->cpu
), CPU_INTERRUPT_HARD
);
88 s3c24xx_percolate_subsrc_interrupt(struct s3c24xx_irq_state_s
*s
)
92 s
->irq_reg
[S3C_IRQ_SRCPND
] |= s
->irq_main_level
;
93 s
->irq_reg
[S3C_IRQ_SUBSRCPND
] |= s
->irq_subsrc_level
;
95 ints
= (s
->irq_reg
[S3C_IRQ_SUBSRCPND
] &
96 ~s
->irq_reg
[S3C_IRQ_INTSUBMSK
]);
98 /* If UART0 has asserted, raise that */
100 s
->irq_reg
[S3C_IRQ_SRCPND
] |= (1<<28);
105 s
->irq_reg
[S3C_IRQ_SRCPND
] |= (1<<23);
109 s
->irq_reg
[S3C_IRQ_SRCPND
] |= (1<<15);
111 /* And percolate it through */
112 s3c24xx_percolate_interrupt(s
);
115 static void s3c24xx_irq_write(void *opaque
, hwaddr addr_
,
116 uint64_t value
, unsigned size
)
118 struct s3c24xx_irq_state_s
*s
= opaque
;
119 int addr
= (addr_
>> 2) & 7;
121 if (addr
== S3C_IRQ_SRCPND
||
122 addr
== S3C_IRQ_INTPND
||
123 addr
== S3C_IRQ_SUBSRCPND
) {
124 s
->irq_reg
[addr
] &= ~value
;
126 s
->irq_reg
[addr
] = value
;
129 /* Start at the subsrc irqs and percolate from there */
130 s3c24xx_percolate_subsrc_interrupt(s
);
133 static uint64_t s3c24xx_irq_read(void *opaque
, hwaddr addr_
,
136 struct s3c24xx_irq_state_s
*s
= opaque
;
137 int addr
= (addr_
>> 2) & 0x7;
139 return s
->irq_reg
[addr
];
142 static const MemoryRegionOps s3c24xx_irq_ops
= {
143 .read
= s3c24xx_irq_read
,
144 .write
= s3c24xx_irq_write
,
145 .endianness
= DEVICE_NATIVE_ENDIAN
,
147 .min_access_size
= 1,
153 s3c24xx_irq_set_interrupt_level(struct s3c24xx_irq_state_s
*s
, int irq_num
, int level
, int set_level
)
157 s
->irq_main_level
|= 1<<irq_num
;
158 s
->irq_reg
[S3C_IRQ_SRCPND
] |= 1<<irq_num
;
160 s
->irq_main_level
&= ~(1<<irq_num
);
161 s
->irq_reg
[S3C_IRQ_SRCPND
] &= ~(1<<irq_num
);
163 s3c24xx_percolate_subsrc_interrupt(s
);
167 s3c24xx_irq_set_subsrc_interrupt_level(struct s3c24xx_irq_state_s
*s
, int irq_num
, int level
, int set_level
)
171 s
->irq_subsrc_level
|= 1<<irq_num
;
172 s
->irq_reg
[S3C_IRQ_SUBSRCPND
] |= 1<<irq_num
;
174 s
->irq_subsrc_level
&= ~(1<<irq_num
);
175 s
->irq_reg
[S3C_IRQ_SUBSRCPND
] &= ~(1<<irq_num
);
177 s3c24xx_percolate_subsrc_interrupt(s
);
181 s3c24xx_irq_handler(void *opaque
, int _n
, int level
)
183 struct s3c24xx_irq_state_s
*s
= opaque
;
184 int irq_num
= _n
% 32;
185 int is_subsrc
= (_n
& 32)?1:0;
186 int is_level
= (_n
& 64)?1:0;
189 s3c24xx_irq_set_interrupt_level(s
, irq_num
, level
, is_level
);
191 s3c24xx_irq_set_subsrc_interrupt_level(s
, irq_num
, level
, is_level
);
194 static void s3c24xx_irq_save(QEMUFile
*f
, void *opaque
)
196 struct s3c24xx_irq_state_s
*s
= opaque
;
199 for (i
= 0; i
< 8; i
++)
200 qemu_put_be32s(f
, &s
->irq_reg
[i
]);
203 static int s3c24xx_irq_load(QEMUFile
*f
, void *opaque
, int version_id
)
205 struct s3c24xx_irq_state_s
*s
= opaque
;
208 for (i
= 0; i
< 8; i
++)
209 qemu_get_be32s(f
, &s
->irq_reg
[i
]);
214 static SaveVMHandlers savevm_s3c24xx_irq
= {
215 .save_state
= s3c24xx_irq_save
,
216 .load_state
= s3c24xx_irq_load
219 struct s3c24xx_irq_state_s
*
220 s3c24xx_irq_init(S3CState
*soc
, hwaddr base_addr
)
222 struct s3c24xx_irq_state_s
*s
= g_new0(struct s3c24xx_irq_state_s
, 1);
224 /* Samsung S3C24XX IRQ registration. */
225 memory_region_init_io(&s
->mmio
, OBJECT(s
), &s3c24xx_irq_ops
, s
,
226 "s3c24xx.irq", 8 * 4);
227 memory_region_add_subregion(get_system_memory(), base_addr
, &s
->mmio
);
228 register_savevm_live(NULL
, "s3c24xx_irq", 0, 0, &savevm_s3c24xx_irq
, s
);
232 /* Set up registers to power on values */
233 s
->irq_reg
[S3C_IRQ_SRCPND
] = 0x00;
234 s
->irq_reg
[S3C_IRQ_INTMOD
] = 0x00;
235 s
->irq_reg
[S3C_IRQ_INTMSK
] = 0xFFFFFFFF;
236 s
->irq_reg
[S3C_IRQ_PRIORITY
] = 0x7F;
237 s
->irq_reg
[S3C_IRQ_INTPND
] = 0x00;
238 s
->irq_reg
[S3C_IRQ_OFFSET
] = 0x00;
239 s
->irq_reg
[S3C_IRQ_SUBSRCPND
] = 0x00;
240 s
->irq_reg
[S3C_IRQ_INTSUBMSK
] = 0x7FF;
242 /* Allocate the interrupts and return them. All 64 potential ones.
243 * We return them doubled up because the latter half are level where
244 * the former half are edge.
246 s
->irqs
= qemu_allocate_irqs(s3c24xx_irq_handler
, s
, 128);
251 /* get the qemu interrupt from an irq number */
253 s3c24xx_get_irq(struct s3c24xx_irq_state_s
*s
, unsigned inum
)
256 return s
->irqs
[inum
];