]>
Commit | Line | Data |
---|---|---|
0c69996e AJ |
1 | /* |
2 | * ASPEED Interrupt Controller (New) | |
3 | * | |
4 | * Andrew Jeffery <andrew@aj.id.au> | |
5 | * | |
6 | * Copyright 2015, 2016 IBM Corp. | |
7 | * | |
8 | * This code is licensed under the GPL version 2 or later. See | |
9 | * the COPYING file in the top-level directory. | |
10 | */ | |
11 | ||
12 | /* The hardware exposes two register sets, a legacy set and a 'new' set. The | |
13 | * model implements the 'new' register set, and logs warnings on accesses to | |
14 | * the legacy IO space. | |
15 | * | |
16 | * The hardware uses 32bit registers to manage 51 IRQs, with low and high | |
17 | * registers for each conceptual register. The device model's implementation | |
18 | * uses 64bit data types to store both low and high register values (in the one | |
19 | * member), but must cope with access offset values in multiples of 4 passed to | |
20 | * the callbacks. As such the read() and write() implementations process the | |
21 | * provided offset to understand whether the access is requesting the lower or | |
22 | * upper 32 bits of the 64bit member. | |
23 | * | |
24 | * Additionally, the "Interrupt Enable", "Edge Status" and "Software Interrupt" | |
25 | * fields have separate "enable"/"status" and "clear" registers, where set bits | |
26 | * are written to one or the other to change state (avoiding a | |
27 | * read-modify-write sequence). | |
28 | */ | |
29 | ||
30 | #include "qemu/osdep.h" | |
0c69996e | 31 | #include "hw/intc/aspeed_vic.h" |
64552b6b | 32 | #include "hw/irq.h" |
d6454270 | 33 | #include "migration/vmstate.h" |
0c69996e | 34 | #include "qemu/bitops.h" |
22b31af2 | 35 | #include "qemu/log.h" |
0b8fa32f | 36 | #include "qemu/module.h" |
0c69996e AJ |
37 | #include "trace.h" |
38 | ||
39 | #define AVIC_NEW_BASE_OFFSET 0x80 | |
40 | ||
41 | #define AVIC_L_MASK 0xFFFFFFFFU | |
42 | #define AVIC_H_MASK 0x0007FFFFU | |
43 | #define AVIC_EVENT_W_MASK (0x78000ULL << 32) | |
44 | ||
45 | static void aspeed_vic_update(AspeedVICState *s) | |
46 | { | |
47 | uint64_t new = (s->raw & s->enable); | |
48 | uint64_t flags; | |
49 | ||
50 | flags = new & s->select; | |
51 | trace_aspeed_vic_update_fiq(!!flags); | |
52 | qemu_set_irq(s->fiq, !!flags); | |
53 | ||
54 | flags = new & ~s->select; | |
55 | trace_aspeed_vic_update_irq(!!flags); | |
56 | qemu_set_irq(s->irq, !!flags); | |
57 | } | |
58 | ||
59 | static void aspeed_vic_set_irq(void *opaque, int irq, int level) | |
60 | { | |
61 | uint64_t irq_mask; | |
62 | bool raise; | |
63 | AspeedVICState *s = (AspeedVICState *)opaque; | |
64 | ||
65 | if (irq > ASPEED_VIC_NR_IRQS) { | |
66 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", | |
67 | __func__, irq); | |
68 | return; | |
69 | } | |
70 | ||
71 | trace_aspeed_vic_set_irq(irq, level); | |
72 | ||
73 | irq_mask = BIT(irq); | |
74 | if (s->sense & irq_mask) { | |
75 | /* level-triggered */ | |
76 | if (s->event & irq_mask) { | |
77 | /* high-sensitive */ | |
78 | raise = level; | |
79 | } else { | |
80 | /* low-sensitive */ | |
81 | raise = !level; | |
82 | } | |
83 | s->raw = deposit64(s->raw, irq, 1, raise); | |
84 | } else { | |
85 | uint64_t old_level = s->level & irq_mask; | |
86 | ||
87 | /* edge-triggered */ | |
88 | if (s->dual_edge & irq_mask) { | |
89 | raise = (!!old_level) != (!!level); | |
90 | } else { | |
91 | if (s->event & irq_mask) { | |
92 | /* rising-sensitive */ | |
93 | raise = !old_level && level; | |
94 | } else { | |
95 | /* falling-sensitive */ | |
96 | raise = old_level && !level; | |
97 | } | |
98 | } | |
99 | if (raise) { | |
100 | s->raw = deposit64(s->raw, irq, 1, raise); | |
101 | } | |
102 | } | |
103 | s->level = deposit64(s->level, irq, 1, level); | |
104 | aspeed_vic_update(s); | |
105 | } | |
106 | ||
107 | static uint64_t aspeed_vic_read(void *opaque, hwaddr offset, unsigned size) | |
108 | { | |
0c69996e | 109 | AspeedVICState *s = (AspeedVICState *)opaque; |
ebd205c0 AJ |
110 | hwaddr n_offset; |
111 | uint64_t val; | |
112 | bool high; | |
0c69996e AJ |
113 | |
114 | if (offset < AVIC_NEW_BASE_OFFSET) { | |
ebd205c0 AJ |
115 | high = false; |
116 | n_offset = offset; | |
117 | } else { | |
118 | high = !!(offset & 0x4); | |
119 | n_offset = (offset & ~0x4); | |
0c69996e AJ |
120 | } |
121 | ||
0c69996e | 122 | switch (n_offset) { |
ebd205c0 AJ |
123 | case 0x80: /* IRQ Status */ |
124 | case 0x00: | |
0c69996e AJ |
125 | val = s->raw & ~s->select & s->enable; |
126 | break; | |
ebd205c0 AJ |
127 | case 0x88: /* FIQ Status */ |
128 | case 0x04: | |
0c69996e AJ |
129 | val = s->raw & s->select & s->enable; |
130 | break; | |
ebd205c0 AJ |
131 | case 0x90: /* Raw Interrupt Status */ |
132 | case 0x08: | |
0c69996e AJ |
133 | val = s->raw; |
134 | break; | |
ebd205c0 AJ |
135 | case 0x98: /* Interrupt Selection */ |
136 | case 0x0c: | |
0c69996e AJ |
137 | val = s->select; |
138 | break; | |
ebd205c0 AJ |
139 | case 0xa0: /* Interrupt Enable */ |
140 | case 0x10: | |
0c69996e AJ |
141 | val = s->enable; |
142 | break; | |
ebd205c0 AJ |
143 | case 0xb0: /* Software Interrupt */ |
144 | case 0x18: | |
0c69996e AJ |
145 | val = s->trigger; |
146 | break; | |
ebd205c0 AJ |
147 | case 0xc0: /* Interrupt Sensitivity */ |
148 | case 0x24: | |
0c69996e AJ |
149 | val = s->sense; |
150 | break; | |
ebd205c0 AJ |
151 | case 0xc8: /* Interrupt Both Edge Trigger Control */ |
152 | case 0x28: | |
0c69996e AJ |
153 | val = s->dual_edge; |
154 | break; | |
ebd205c0 AJ |
155 | case 0xd0: /* Interrupt Event */ |
156 | case 0x2c: | |
0c69996e AJ |
157 | val = s->event; |
158 | break; | |
ebd205c0 | 159 | case 0xe0: /* Edge Triggered Interrupt Status */ |
0c69996e AJ |
160 | val = s->raw & ~s->sense; |
161 | break; | |
162 | /* Illegal */ | |
ebd205c0 AJ |
163 | case 0xa8: /* Interrupt Enable Clear */ |
164 | case 0xb8: /* Software Interrupt Clear */ | |
165 | case 0xd8: /* Edge Triggered Interrupt Clear */ | |
0c69996e AJ |
166 | qemu_log_mask(LOG_GUEST_ERROR, |
167 | "%s: Read of write-only register with offset 0x%" | |
168 | HWADDR_PRIx "\n", __func__, offset); | |
169 | val = 0; | |
170 | break; | |
171 | default: | |
172 | qemu_log_mask(LOG_GUEST_ERROR, | |
173 | "%s: Bad register at offset 0x%" HWADDR_PRIx "\n", | |
174 | __func__, offset); | |
175 | val = 0; | |
176 | break; | |
177 | } | |
178 | if (high) { | |
179 | val = extract64(val, 32, 19); | |
ebd205c0 AJ |
180 | } else { |
181 | val = extract64(val, 0, 32); | |
0c69996e AJ |
182 | } |
183 | trace_aspeed_vic_read(offset, size, val); | |
184 | return val; | |
185 | } | |
186 | ||
187 | static void aspeed_vic_write(void *opaque, hwaddr offset, uint64_t data, | |
188 | unsigned size) | |
189 | { | |
0c69996e | 190 | AspeedVICState *s = (AspeedVICState *)opaque; |
ebd205c0 AJ |
191 | hwaddr n_offset; |
192 | bool high; | |
0c69996e AJ |
193 | |
194 | if (offset < AVIC_NEW_BASE_OFFSET) { | |
ebd205c0 AJ |
195 | high = false; |
196 | n_offset = offset; | |
197 | } else { | |
198 | high = !!(offset & 0x4); | |
199 | n_offset = (offset & ~0x4); | |
0c69996e AJ |
200 | } |
201 | ||
0c69996e AJ |
202 | trace_aspeed_vic_write(offset, size, data); |
203 | ||
204 | /* Given we have members using separate enable/clear registers, deposit64() | |
205 | * isn't quite the tool for the job. Instead, relocate the incoming bits to | |
206 | * the required bit offset based on the provided access address | |
207 | */ | |
208 | if (high) { | |
209 | data &= AVIC_H_MASK; | |
210 | data <<= 32; | |
211 | } else { | |
212 | data &= AVIC_L_MASK; | |
213 | } | |
214 | ||
215 | switch (n_offset) { | |
ebd205c0 AJ |
216 | case 0x98: /* Interrupt Selection */ |
217 | case 0x0c: | |
0c69996e AJ |
218 | /* Register has deposit64() semantics - overwrite requested 32 bits */ |
219 | if (high) { | |
220 | s->select &= AVIC_L_MASK; | |
221 | } else { | |
222 | s->select &= ((uint64_t) AVIC_H_MASK) << 32; | |
223 | } | |
224 | s->select |= data; | |
225 | break; | |
ebd205c0 AJ |
226 | case 0xa0: /* Interrupt Enable */ |
227 | case 0x10: | |
0c69996e AJ |
228 | s->enable |= data; |
229 | break; | |
ebd205c0 AJ |
230 | case 0xa8: /* Interrupt Enable Clear */ |
231 | case 0x14: | |
0c69996e AJ |
232 | s->enable &= ~data; |
233 | break; | |
ebd205c0 AJ |
234 | case 0xb0: /* Software Interrupt */ |
235 | case 0x18: | |
0c69996e AJ |
236 | qemu_log_mask(LOG_UNIMP, "%s: Software interrupts unavailable. " |
237 | "IRQs requested: 0x%016" PRIx64 "\n", __func__, data); | |
238 | break; | |
ebd205c0 AJ |
239 | case 0xb8: /* Software Interrupt Clear */ |
240 | case 0x1c: | |
0c69996e AJ |
241 | qemu_log_mask(LOG_UNIMP, "%s: Software interrupts unavailable. " |
242 | "IRQs to be cleared: 0x%016" PRIx64 "\n", __func__, data); | |
243 | break; | |
ebd205c0 | 244 | case 0xd0: /* Interrupt Event */ |
0c69996e AJ |
245 | /* Register has deposit64() semantics - overwrite the top four valid |
246 | * IRQ bits, as only the top four IRQs (GPIOs) can change their event | |
247 | * type */ | |
248 | if (high) { | |
249 | s->event &= ~AVIC_EVENT_W_MASK; | |
250 | s->event |= (data & AVIC_EVENT_W_MASK); | |
251 | } else { | |
252 | qemu_log_mask(LOG_GUEST_ERROR, | |
253 | "Ignoring invalid write to interrupt event register"); | |
254 | } | |
255 | break; | |
ebd205c0 AJ |
256 | case 0xd8: /* Edge Triggered Interrupt Clear */ |
257 | case 0x38: | |
0c69996e AJ |
258 | s->raw &= ~(data & ~s->sense); |
259 | break; | |
ebd205c0 AJ |
260 | case 0x80: /* IRQ Status */ |
261 | case 0x00: | |
262 | case 0x88: /* FIQ Status */ | |
263 | case 0x04: | |
264 | case 0x90: /* Raw Interrupt Status */ | |
265 | case 0x08: | |
266 | case 0xc0: /* Interrupt Sensitivity */ | |
267 | case 0x24: | |
268 | case 0xc8: /* Interrupt Both Edge Trigger Control */ | |
269 | case 0x28: | |
270 | case 0xe0: /* Edge Triggered Interrupt Status */ | |
0c69996e AJ |
271 | qemu_log_mask(LOG_GUEST_ERROR, |
272 | "%s: Write of read-only register with offset 0x%" | |
273 | HWADDR_PRIx "\n", __func__, offset); | |
274 | break; | |
275 | ||
276 | default: | |
277 | qemu_log_mask(LOG_GUEST_ERROR, | |
278 | "%s: Bad register at offset 0x%" HWADDR_PRIx "\n", | |
279 | __func__, offset); | |
280 | break; | |
281 | } | |
282 | aspeed_vic_update(s); | |
283 | } | |
284 | ||
285 | static const MemoryRegionOps aspeed_vic_ops = { | |
286 | .read = aspeed_vic_read, | |
287 | .write = aspeed_vic_write, | |
288 | .endianness = DEVICE_LITTLE_ENDIAN, | |
289 | .valid.min_access_size = 4, | |
290 | .valid.max_access_size = 4, | |
291 | .valid.unaligned = false, | |
292 | }; | |
293 | ||
294 | static void aspeed_vic_reset(DeviceState *dev) | |
295 | { | |
296 | AspeedVICState *s = ASPEED_VIC(dev); | |
297 | ||
298 | s->level = 0; | |
299 | s->raw = 0; | |
300 | s->select = 0; | |
301 | s->enable = 0; | |
302 | s->trigger = 0; | |
303 | s->sense = 0x1F07FFF8FFFFULL; | |
304 | s->dual_edge = 0xF800070000ULL; | |
305 | s->event = 0x5F07FFF8FFFFULL; | |
306 | } | |
307 | ||
308 | #define AVIC_IO_REGION_SIZE 0x20000 | |
309 | ||
310 | static void aspeed_vic_realize(DeviceState *dev, Error **errp) | |
311 | { | |
312 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); | |
313 | AspeedVICState *s = ASPEED_VIC(dev); | |
314 | ||
315 | memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_vic_ops, s, | |
316 | TYPE_ASPEED_VIC, AVIC_IO_REGION_SIZE); | |
317 | ||
318 | sysbus_init_mmio(sbd, &s->iomem); | |
319 | ||
320 | qdev_init_gpio_in(dev, aspeed_vic_set_irq, ASPEED_VIC_NR_IRQS); | |
321 | sysbus_init_irq(sbd, &s->irq); | |
322 | sysbus_init_irq(sbd, &s->fiq); | |
323 | } | |
324 | ||
325 | static const VMStateDescription vmstate_aspeed_vic = { | |
326 | .name = "aspeed.new-vic", | |
327 | .version_id = 1, | |
328 | .minimum_version_id = 1, | |
329 | .fields = (VMStateField[]) { | |
330 | VMSTATE_UINT64(level, AspeedVICState), | |
331 | VMSTATE_UINT64(raw, AspeedVICState), | |
332 | VMSTATE_UINT64(select, AspeedVICState), | |
333 | VMSTATE_UINT64(enable, AspeedVICState), | |
334 | VMSTATE_UINT64(trigger, AspeedVICState), | |
335 | VMSTATE_UINT64(sense, AspeedVICState), | |
336 | VMSTATE_UINT64(dual_edge, AspeedVICState), | |
337 | VMSTATE_UINT64(event, AspeedVICState), | |
338 | VMSTATE_END_OF_LIST() | |
339 | } | |
340 | }; | |
341 | ||
342 | static void aspeed_vic_class_init(ObjectClass *klass, void *data) | |
343 | { | |
344 | DeviceClass *dc = DEVICE_CLASS(klass); | |
345 | dc->realize = aspeed_vic_realize; | |
346 | dc->reset = aspeed_vic_reset; | |
347 | dc->desc = "ASPEED Interrupt Controller (New)"; | |
348 | dc->vmsd = &vmstate_aspeed_vic; | |
349 | } | |
350 | ||
351 | static const TypeInfo aspeed_vic_info = { | |
352 | .name = TYPE_ASPEED_VIC, | |
353 | .parent = TYPE_SYS_BUS_DEVICE, | |
354 | .instance_size = sizeof(AspeedVICState), | |
355 | .class_init = aspeed_vic_class_init, | |
356 | }; | |
357 | ||
358 | static void aspeed_vic_register_types(void) | |
359 | { | |
360 | type_register_static(&aspeed_vic_info); | |
361 | } | |
362 | ||
363 | type_init(aspeed_vic_register_types); |