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