root / hw / mpcore.c @ c171148c
History | View | Annotate | Download (8.4 kB)
1 | 9ee6e8bb | pbrook | /*
|
---|---|---|---|
2 | 9ee6e8bb | pbrook | * ARM MPCore internal peripheral emulation.
|
3 | 9ee6e8bb | pbrook | *
|
4 | 9ee6e8bb | pbrook | * Copyright (c) 2006-2007 CodeSourcery.
|
5 | 9ee6e8bb | pbrook | * Written by Paul Brook
|
6 | 9ee6e8bb | pbrook | *
|
7 | 9ee6e8bb | pbrook | * This code is licenced under the GPL.
|
8 | 9ee6e8bb | pbrook | */
|
9 | 9ee6e8bb | pbrook | |
10 | 87ecb68b | pbrook | #include "hw.h" |
11 | 87ecb68b | pbrook | #include "qemu-timer.h" |
12 | 87ecb68b | pbrook | #include "primecell.h" |
13 | 9ee6e8bb | pbrook | |
14 | 9ee6e8bb | pbrook | #define MPCORE_PRIV_BASE 0x10100000 |
15 | 9ee6e8bb | pbrook | #define NCPU 4 |
16 | 9ee6e8bb | pbrook | /* ??? The MPCore TRM says the on-chip controller has 224 external IRQ lines
|
17 | 9ee6e8bb | pbrook | (+ 32 internal). However my test chip only exposes/reports 32.
|
18 | 9ee6e8bb | pbrook | More importantly Linux falls over if more than 32 are present! */
|
19 | 9ee6e8bb | pbrook | #define GIC_NIRQ 64 |
20 | 9ee6e8bb | pbrook | |
21 | 9ee6e8bb | pbrook | static inline int |
22 | 9ee6e8bb | pbrook | gic_get_current_cpu(void)
|
23 | 9ee6e8bb | pbrook | { |
24 | 9ee6e8bb | pbrook | return cpu_single_env->cpu_index;
|
25 | 9ee6e8bb | pbrook | } |
26 | 9ee6e8bb | pbrook | |
27 | 9ee6e8bb | pbrook | #include "arm_gic.c" |
28 | 9ee6e8bb | pbrook | |
29 | 9ee6e8bb | pbrook | /* MPCore private memory region. */
|
30 | 9ee6e8bb | pbrook | |
31 | 9ee6e8bb | pbrook | typedef struct { |
32 | 9ee6e8bb | pbrook | uint32_t count; |
33 | 9ee6e8bb | pbrook | uint32_t load; |
34 | 9ee6e8bb | pbrook | uint32_t control; |
35 | 9ee6e8bb | pbrook | uint32_t status; |
36 | 9ee6e8bb | pbrook | uint32_t old_status; |
37 | 9ee6e8bb | pbrook | int64_t tick; |
38 | 9ee6e8bb | pbrook | QEMUTimer *timer; |
39 | 9ee6e8bb | pbrook | struct mpcore_priv_state *mpcore;
|
40 | 9ee6e8bb | pbrook | int id; /* Encodes both timer/watchdog and CPU. */ |
41 | 9ee6e8bb | pbrook | } mpcore_timer_state; |
42 | 9ee6e8bb | pbrook | |
43 | 9ee6e8bb | pbrook | typedef struct mpcore_priv_state { |
44 | 9ee6e8bb | pbrook | gic_state *gic; |
45 | 9ee6e8bb | pbrook | uint32_t scu_control; |
46 | 9ee6e8bb | pbrook | mpcore_timer_state timer[8];
|
47 | 9ee6e8bb | pbrook | } mpcore_priv_state; |
48 | 9ee6e8bb | pbrook | |
49 | 9ee6e8bb | pbrook | /* Per-CPU Timers. */
|
50 | 9ee6e8bb | pbrook | |
51 | 9ee6e8bb | pbrook | static inline void mpcore_timer_update_irq(mpcore_timer_state *s) |
52 | 9ee6e8bb | pbrook | { |
53 | 9ee6e8bb | pbrook | if (s->status & ~s->old_status) {
|
54 | 9ee6e8bb | pbrook | gic_set_pending_private(s->mpcore->gic, s->id >> 1, 29 + (s->id & 1)); |
55 | 9ee6e8bb | pbrook | } |
56 | 9ee6e8bb | pbrook | s->old_status = s->status; |
57 | 9ee6e8bb | pbrook | } |
58 | 9ee6e8bb | pbrook | |
59 | 9ee6e8bb | pbrook | /* Return conversion factor from mpcore timer ticks to qemu timer ticks. */
|
60 | 9ee6e8bb | pbrook | static inline uint32_t mpcore_timer_scale(mpcore_timer_state *s) |
61 | 9ee6e8bb | pbrook | { |
62 | 9ee6e8bb | pbrook | return (((s->control >> 8) & 0xff) + 1) * 10; |
63 | 9ee6e8bb | pbrook | } |
64 | 9ee6e8bb | pbrook | |
65 | 9ee6e8bb | pbrook | static void mpcore_timer_reload(mpcore_timer_state *s, int restart) |
66 | 9ee6e8bb | pbrook | { |
67 | 9ee6e8bb | pbrook | if (s->count == 0) |
68 | 9ee6e8bb | pbrook | return;
|
69 | 9ee6e8bb | pbrook | if (restart)
|
70 | 9ee6e8bb | pbrook | s->tick = qemu_get_clock(vm_clock); |
71 | 9ee6e8bb | pbrook | s->tick += (int64_t)s->count * mpcore_timer_scale(s); |
72 | 9ee6e8bb | pbrook | qemu_mod_timer(s->timer, s->tick); |
73 | 9ee6e8bb | pbrook | } |
74 | 9ee6e8bb | pbrook | |
75 | 9ee6e8bb | pbrook | static void mpcore_timer_tick(void *opaque) |
76 | 9ee6e8bb | pbrook | { |
77 | 9ee6e8bb | pbrook | mpcore_timer_state *s = (mpcore_timer_state *)opaque; |
78 | 9ee6e8bb | pbrook | s->status = 1;
|
79 | 9ee6e8bb | pbrook | if (s->control & 2) { |
80 | 9ee6e8bb | pbrook | s->count = s->load; |
81 | 9ee6e8bb | pbrook | mpcore_timer_reload(s, 0);
|
82 | 9ee6e8bb | pbrook | } else {
|
83 | 9ee6e8bb | pbrook | s->count = 0;
|
84 | 9ee6e8bb | pbrook | } |
85 | 9ee6e8bb | pbrook | mpcore_timer_update_irq(s); |
86 | 9ee6e8bb | pbrook | } |
87 | 9ee6e8bb | pbrook | |
88 | 9ee6e8bb | pbrook | static uint32_t mpcore_timer_read(mpcore_timer_state *s, int offset) |
89 | 9ee6e8bb | pbrook | { |
90 | 9ee6e8bb | pbrook | int64_t val; |
91 | 9ee6e8bb | pbrook | switch (offset) {
|
92 | 9ee6e8bb | pbrook | case 0: /* Load */ |
93 | 9ee6e8bb | pbrook | return s->load;
|
94 | 9ee6e8bb | pbrook | /* Fall through. */
|
95 | 9ee6e8bb | pbrook | case 4: /* Counter. */ |
96 | 9ee6e8bb | pbrook | if (((s->control & 1) == 0) || (s->count == 0)) |
97 | 9ee6e8bb | pbrook | return 0; |
98 | 9ee6e8bb | pbrook | /* Slow and ugly, but hopefully won't happen too often. */
|
99 | 9ee6e8bb | pbrook | val = s->tick - qemu_get_clock(vm_clock); |
100 | 9ee6e8bb | pbrook | val /= mpcore_timer_scale(s); |
101 | 9ee6e8bb | pbrook | if (val < 0) |
102 | 9ee6e8bb | pbrook | val = 0;
|
103 | 9ee6e8bb | pbrook | return val;
|
104 | 9ee6e8bb | pbrook | case 8: /* Control. */ |
105 | 9ee6e8bb | pbrook | return s->control;
|
106 | 9ee6e8bb | pbrook | case 12: /* Interrupt status. */ |
107 | 9ee6e8bb | pbrook | return s->status;
|
108 | 9ee6e8bb | pbrook | } |
109 | 9ee6e8bb | pbrook | } |
110 | 9ee6e8bb | pbrook | |
111 | 9ee6e8bb | pbrook | static void mpcore_timer_write(mpcore_timer_state *s, int offset, |
112 | 9ee6e8bb | pbrook | uint32_t value) |
113 | 9ee6e8bb | pbrook | { |
114 | 9ee6e8bb | pbrook | int64_t old; |
115 | 9ee6e8bb | pbrook | switch (offset) {
|
116 | 9ee6e8bb | pbrook | case 0: /* Load */ |
117 | 9ee6e8bb | pbrook | s->load = value; |
118 | 9ee6e8bb | pbrook | /* Fall through. */
|
119 | 9ee6e8bb | pbrook | case 4: /* Counter. */ |
120 | 9ee6e8bb | pbrook | if ((s->control & 1) && s->count) { |
121 | 9ee6e8bb | pbrook | /* Cancel the previous timer. */
|
122 | 9ee6e8bb | pbrook | qemu_del_timer(s->timer); |
123 | 9ee6e8bb | pbrook | } |
124 | 9ee6e8bb | pbrook | s->count = value; |
125 | 9ee6e8bb | pbrook | if (s->control & 1) { |
126 | 9ee6e8bb | pbrook | mpcore_timer_reload(s, 1);
|
127 | 9ee6e8bb | pbrook | } |
128 | 9ee6e8bb | pbrook | break;
|
129 | 9ee6e8bb | pbrook | case 8: /* Control. */ |
130 | 9ee6e8bb | pbrook | old = s->control; |
131 | 9ee6e8bb | pbrook | s->control = value; |
132 | 9ee6e8bb | pbrook | if (((old & 1) == 0) && (value & 1)) { |
133 | 9ee6e8bb | pbrook | if (s->count == 0 && (s->control & 2)) |
134 | 9ee6e8bb | pbrook | s->count = s->load; |
135 | 9ee6e8bb | pbrook | mpcore_timer_reload(s, 1);
|
136 | 9ee6e8bb | pbrook | } |
137 | 9ee6e8bb | pbrook | break;
|
138 | 9ee6e8bb | pbrook | case 12: /* Interrupt status. */ |
139 | 9ee6e8bb | pbrook | s->status &= ~value; |
140 | 9ee6e8bb | pbrook | mpcore_timer_update_irq(s); |
141 | 9ee6e8bb | pbrook | break;
|
142 | 9ee6e8bb | pbrook | } |
143 | 9ee6e8bb | pbrook | } |
144 | 9ee6e8bb | pbrook | |
145 | 9ee6e8bb | pbrook | static void mpcore_timer_init(mpcore_priv_state *mpcore, |
146 | 9ee6e8bb | pbrook | mpcore_timer_state *s, int id)
|
147 | 9ee6e8bb | pbrook | { |
148 | 9ee6e8bb | pbrook | s->id = id; |
149 | 9ee6e8bb | pbrook | s->mpcore = mpcore; |
150 | 9ee6e8bb | pbrook | s->timer = qemu_new_timer(vm_clock, mpcore_timer_tick, s); |
151 | 9ee6e8bb | pbrook | } |
152 | 9ee6e8bb | pbrook | |
153 | 9ee6e8bb | pbrook | |
154 | 9ee6e8bb | pbrook | /* Per-CPU private memory mapped IO. */
|
155 | 9ee6e8bb | pbrook | |
156 | 9ee6e8bb | pbrook | static uint32_t mpcore_priv_read(void *opaque, target_phys_addr_t offset) |
157 | 9ee6e8bb | pbrook | { |
158 | 9ee6e8bb | pbrook | mpcore_priv_state *s = (mpcore_priv_state *)opaque; |
159 | 9ee6e8bb | pbrook | int id;
|
160 | 9ee6e8bb | pbrook | offset &= 0xfff;
|
161 | 9ee6e8bb | pbrook | if (offset < 0x100) { |
162 | 9ee6e8bb | pbrook | /* SCU */
|
163 | 9ee6e8bb | pbrook | switch (offset) {
|
164 | 9ee6e8bb | pbrook | case 0x00: /* Control. */ |
165 | 9ee6e8bb | pbrook | return s->scu_control;
|
166 | 9ee6e8bb | pbrook | case 0x04: /* Configuration. */ |
167 | 9ee6e8bb | pbrook | return 0xf3; |
168 | 9ee6e8bb | pbrook | case 0x08: /* CPU status. */ |
169 | 9ee6e8bb | pbrook | return 0; |
170 | 9ee6e8bb | pbrook | case 0x0c: /* Invalidate all. */ |
171 | 9ee6e8bb | pbrook | return 0; |
172 | 9ee6e8bb | pbrook | default:
|
173 | 9ee6e8bb | pbrook | goto bad_reg;
|
174 | 9ee6e8bb | pbrook | } |
175 | 9ee6e8bb | pbrook | } else if (offset < 0x600) { |
176 | 9ee6e8bb | pbrook | /* Interrupt controller. */
|
177 | 9ee6e8bb | pbrook | if (offset < 0x200) { |
178 | 9ee6e8bb | pbrook | id = gic_get_current_cpu(); |
179 | 9ee6e8bb | pbrook | } else {
|
180 | 9ee6e8bb | pbrook | id = (offset - 0x200) >> 8; |
181 | 9ee6e8bb | pbrook | } |
182 | 9ee6e8bb | pbrook | return gic_cpu_read(s->gic, id, offset & 0xff); |
183 | 9ee6e8bb | pbrook | } else if (offset < 0xb00) { |
184 | 9ee6e8bb | pbrook | /* Timers. */
|
185 | 9ee6e8bb | pbrook | if (offset < 0x700) { |
186 | 9ee6e8bb | pbrook | id = gic_get_current_cpu(); |
187 | 9ee6e8bb | pbrook | } else {
|
188 | 9ee6e8bb | pbrook | id = (offset - 0x700) >> 8; |
189 | 9ee6e8bb | pbrook | } |
190 | 9ee6e8bb | pbrook | id <<= 1;
|
191 | 9ee6e8bb | pbrook | if (offset & 0x20) |
192 | 9ee6e8bb | pbrook | id++; |
193 | 9ee6e8bb | pbrook | return mpcore_timer_read(&s->timer[id], offset & 0xf); |
194 | 9ee6e8bb | pbrook | } |
195 | 9ee6e8bb | pbrook | bad_reg:
|
196 | 9ee6e8bb | pbrook | cpu_abort(cpu_single_env, "mpcore_priv_read: Bad offset %x\n",
|
197 | 9ee6e8bb | pbrook | (int)offset);
|
198 | 9ee6e8bb | pbrook | return 0; |
199 | 9ee6e8bb | pbrook | } |
200 | 9ee6e8bb | pbrook | |
201 | 9ee6e8bb | pbrook | static void mpcore_priv_write(void *opaque, target_phys_addr_t offset, |
202 | 9ee6e8bb | pbrook | uint32_t value) |
203 | 9ee6e8bb | pbrook | { |
204 | 9ee6e8bb | pbrook | mpcore_priv_state *s = (mpcore_priv_state *)opaque; |
205 | 9ee6e8bb | pbrook | int id;
|
206 | 9ee6e8bb | pbrook | offset &= 0xfff;
|
207 | 9ee6e8bb | pbrook | if (offset < 0x100) { |
208 | 9ee6e8bb | pbrook | /* SCU */
|
209 | 9ee6e8bb | pbrook | switch (offset) {
|
210 | 9ee6e8bb | pbrook | case 0: /* Control register. */ |
211 | 9ee6e8bb | pbrook | s->scu_control = value & 1;
|
212 | 9ee6e8bb | pbrook | break;
|
213 | 9ee6e8bb | pbrook | case 0x0c: /* Invalidate all. */ |
214 | 9ee6e8bb | pbrook | /* This is a no-op as cache is not emulated. */
|
215 | 9ee6e8bb | pbrook | break;
|
216 | 9ee6e8bb | pbrook | default:
|
217 | 9ee6e8bb | pbrook | goto bad_reg;
|
218 | 9ee6e8bb | pbrook | } |
219 | 9ee6e8bb | pbrook | } else if (offset < 0x600) { |
220 | 9ee6e8bb | pbrook | /* Interrupt controller. */
|
221 | 9ee6e8bb | pbrook | if (offset < 0x200) { |
222 | 9ee6e8bb | pbrook | id = gic_get_current_cpu(); |
223 | 9ee6e8bb | pbrook | } else {
|
224 | 9ee6e8bb | pbrook | id = (offset - 0x200) >> 8; |
225 | 9ee6e8bb | pbrook | } |
226 | 9ee6e8bb | pbrook | gic_cpu_write(s->gic, id, offset & 0xff, value);
|
227 | 9ee6e8bb | pbrook | } else if (offset < 0xb00) { |
228 | 9ee6e8bb | pbrook | /* Timers. */
|
229 | 9ee6e8bb | pbrook | if (offset < 0x700) { |
230 | 9ee6e8bb | pbrook | id = gic_get_current_cpu(); |
231 | 9ee6e8bb | pbrook | } else {
|
232 | 9ee6e8bb | pbrook | id = (offset - 0x700) >> 8; |
233 | 9ee6e8bb | pbrook | } |
234 | 9ee6e8bb | pbrook | id <<= 1;
|
235 | 9ee6e8bb | pbrook | if (offset & 0x20) |
236 | 9ee6e8bb | pbrook | id++; |
237 | 9ee6e8bb | pbrook | mpcore_timer_write(&s->timer[id], offset & 0xf, value);
|
238 | 9ee6e8bb | pbrook | return;
|
239 | 9ee6e8bb | pbrook | } |
240 | 9ee6e8bb | pbrook | return;
|
241 | 9ee6e8bb | pbrook | bad_reg:
|
242 | 9ee6e8bb | pbrook | cpu_abort(cpu_single_env, "mpcore_priv_read: Bad offset %x\n",
|
243 | 9ee6e8bb | pbrook | (int)offset);
|
244 | 9ee6e8bb | pbrook | } |
245 | 9ee6e8bb | pbrook | |
246 | 9ee6e8bb | pbrook | static CPUReadMemoryFunc *mpcore_priv_readfn[] = {
|
247 | 9ee6e8bb | pbrook | mpcore_priv_read, |
248 | 9ee6e8bb | pbrook | mpcore_priv_read, |
249 | 9ee6e8bb | pbrook | mpcore_priv_read |
250 | 9ee6e8bb | pbrook | }; |
251 | 9ee6e8bb | pbrook | |
252 | 9ee6e8bb | pbrook | static CPUWriteMemoryFunc *mpcore_priv_writefn[] = {
|
253 | 9ee6e8bb | pbrook | mpcore_priv_write, |
254 | 9ee6e8bb | pbrook | mpcore_priv_write, |
255 | 9ee6e8bb | pbrook | mpcore_priv_write |
256 | 9ee6e8bb | pbrook | }; |
257 | 9ee6e8bb | pbrook | |
258 | 9ee6e8bb | pbrook | |
259 | 9ee6e8bb | pbrook | static qemu_irq *mpcore_priv_init(uint32_t base, qemu_irq *pic_irq)
|
260 | 9ee6e8bb | pbrook | { |
261 | 9ee6e8bb | pbrook | mpcore_priv_state *s; |
262 | 9ee6e8bb | pbrook | int iomemtype;
|
263 | 9ee6e8bb | pbrook | int i;
|
264 | 9ee6e8bb | pbrook | |
265 | 9ee6e8bb | pbrook | s = (mpcore_priv_state *)qemu_mallocz(sizeof(mpcore_priv_state));
|
266 | 9ee6e8bb | pbrook | if (!s)
|
267 | 9ee6e8bb | pbrook | return NULL; |
268 | 9ee6e8bb | pbrook | s->gic = gic_init(base, pic_irq); |
269 | 9ee6e8bb | pbrook | if (!s->gic)
|
270 | 9ee6e8bb | pbrook | return NULL; |
271 | 9ee6e8bb | pbrook | iomemtype = cpu_register_io_memory(0, mpcore_priv_readfn,
|
272 | 9ee6e8bb | pbrook | mpcore_priv_writefn, s); |
273 | 9ee6e8bb | pbrook | cpu_register_physical_memory(base, 0x00001000, iomemtype);
|
274 | 9ee6e8bb | pbrook | for (i = 0; i < 8; i++) { |
275 | 9ee6e8bb | pbrook | mpcore_timer_init(s, &s->timer[i], i); |
276 | 9ee6e8bb | pbrook | } |
277 | 9ee6e8bb | pbrook | return s->gic->in;
|
278 | 9ee6e8bb | pbrook | } |
279 | 9ee6e8bb | pbrook | |
280 | 9ee6e8bb | pbrook | /* Dummy PIC to route IRQ lines. The baseboard has 4 independent IRQ
|
281 | 9ee6e8bb | pbrook | controllers. The output of these, plus some of the raw input lines
|
282 | 9ee6e8bb | pbrook | are fed into a single SMP-aware interrupt controller on the CPU. */
|
283 | 9ee6e8bb | pbrook | typedef struct { |
284 | 9ee6e8bb | pbrook | qemu_irq *cpuic; |
285 | 9ee6e8bb | pbrook | qemu_irq *rvic[4];
|
286 | 9ee6e8bb | pbrook | } mpcore_rirq_state; |
287 | 9ee6e8bb | pbrook | |
288 | 9ee6e8bb | pbrook | /* Map baseboard IRQs onto CPU IRQ lines. */
|
289 | 9ee6e8bb | pbrook | static const int mpcore_irq_map[32] = { |
290 | 9ee6e8bb | pbrook | -1, -1, -1, -1, 1, 2, -1, -1, |
291 | 9ee6e8bb | pbrook | -1, -1, 6, -1, 4, 5, -1, -1, |
292 | 9ee6e8bb | pbrook | -1, 14, 15, 0, 7, 8, -1, -1, |
293 | 9ee6e8bb | pbrook | -1, -1, -1, -1, 9, 3, -1, -1, |
294 | 9ee6e8bb | pbrook | }; |
295 | 9ee6e8bb | pbrook | |
296 | 9ee6e8bb | pbrook | static void mpcore_rirq_set_irq(void *opaque, int irq, int level) |
297 | 9ee6e8bb | pbrook | { |
298 | 9ee6e8bb | pbrook | mpcore_rirq_state *s = (mpcore_rirq_state *)opaque; |
299 | 9ee6e8bb | pbrook | int i;
|
300 | 9ee6e8bb | pbrook | |
301 | 9ee6e8bb | pbrook | for (i = 0; i < 4; i++) { |
302 | 9ee6e8bb | pbrook | qemu_set_irq(s->rvic[i][irq], level); |
303 | 9ee6e8bb | pbrook | } |
304 | 9ee6e8bb | pbrook | if (irq < 32) { |
305 | 9ee6e8bb | pbrook | irq = mpcore_irq_map[irq]; |
306 | 9ee6e8bb | pbrook | if (irq >= 0) { |
307 | 9ee6e8bb | pbrook | qemu_set_irq(s->cpuic[irq], level); |
308 | 9ee6e8bb | pbrook | } |
309 | 9ee6e8bb | pbrook | } |
310 | 9ee6e8bb | pbrook | } |
311 | 9ee6e8bb | pbrook | |
312 | 9ee6e8bb | pbrook | qemu_irq *mpcore_irq_init(qemu_irq *cpu_irq) |
313 | 9ee6e8bb | pbrook | { |
314 | 9ee6e8bb | pbrook | mpcore_rirq_state *s; |
315 | 9ee6e8bb | pbrook | int n;
|
316 | 9ee6e8bb | pbrook | |
317 | 9ee6e8bb | pbrook | /* ??? IRQ routing is hardcoded to "normal" mode. */
|
318 | 9ee6e8bb | pbrook | s = qemu_mallocz(sizeof(mpcore_rirq_state));
|
319 | 9ee6e8bb | pbrook | s->cpuic = mpcore_priv_init(MPCORE_PRIV_BASE, cpu_irq); |
320 | 9ee6e8bb | pbrook | for (n = 0; n < 4; n++) { |
321 | 9ee6e8bb | pbrook | s->rvic[n] = realview_gic_init(0x10040000 + n * 0x10000, |
322 | 9ee6e8bb | pbrook | s->cpuic[10 + n]);
|
323 | 9ee6e8bb | pbrook | } |
324 | 9ee6e8bb | pbrook | return qemu_allocate_irqs(mpcore_rirq_set_irq, s, 64); |
325 | 9ee6e8bb | pbrook | } |