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