]>
Commit | Line | Data |
---|---|---|
6717f587 GE |
1 | /* |
2 | * Raspberry Pi emulation (c) 2012 Gregory Estrade | |
3 | * This code is licensed under the GNU GPLv2 and later. | |
4 | */ | |
5 | ||
6 | #include "qemu/osdep.h" | |
da34e65c | 7 | #include "qapi/error.h" |
6717f587 | 8 | #include "hw/dma/bcm2835_dma.h" |
03dd024f | 9 | #include "qemu/log.h" |
0b8fa32f | 10 | #include "qemu/module.h" |
6717f587 GE |
11 | |
12 | /* DMA CS Control and Status bits */ | |
13 | #define BCM2708_DMA_ACTIVE (1 << 0) | |
14 | #define BCM2708_DMA_END (1 << 1) /* GE */ | |
15 | #define BCM2708_DMA_INT (1 << 2) | |
16 | #define BCM2708_DMA_ISPAUSED (1 << 4) /* Pause requested or not active */ | |
17 | #define BCM2708_DMA_ISHELD (1 << 5) /* Is held by DREQ flow control */ | |
18 | #define BCM2708_DMA_ERR (1 << 8) | |
19 | #define BCM2708_DMA_ABORT (1 << 30) /* stop current CB, go to next, WO */ | |
20 | #define BCM2708_DMA_RESET (1 << 31) /* WO, self clearing */ | |
21 | ||
22 | /* DMA control block "info" field bits */ | |
23 | #define BCM2708_DMA_INT_EN (1 << 0) | |
24 | #define BCM2708_DMA_TDMODE (1 << 1) | |
25 | #define BCM2708_DMA_WAIT_RESP (1 << 3) | |
26 | #define BCM2708_DMA_D_INC (1 << 4) | |
27 | #define BCM2708_DMA_D_WIDTH (1 << 5) | |
28 | #define BCM2708_DMA_D_DREQ (1 << 6) | |
29 | #define BCM2708_DMA_D_IGNORE (1 << 7) | |
30 | #define BCM2708_DMA_S_INC (1 << 8) | |
31 | #define BCM2708_DMA_S_WIDTH (1 << 9) | |
32 | #define BCM2708_DMA_S_DREQ (1 << 10) | |
33 | #define BCM2708_DMA_S_IGNORE (1 << 11) | |
34 | ||
35 | /* Register offsets */ | |
36 | #define BCM2708_DMA_CS 0x00 /* Control and Status */ | |
37 | #define BCM2708_DMA_ADDR 0x04 /* Control block address */ | |
38 | /* the current control block appears in the following registers - read only */ | |
39 | #define BCM2708_DMA_INFO 0x08 | |
40 | #define BCM2708_DMA_SOURCE_AD 0x0c | |
41 | #define BCM2708_DMA_DEST_AD 0x10 | |
42 | #define BCM2708_DMA_TXFR_LEN 0x14 | |
43 | #define BCM2708_DMA_STRIDE 0x18 | |
44 | #define BCM2708_DMA_NEXTCB 0x1C | |
45 | #define BCM2708_DMA_DEBUG 0x20 | |
46 | ||
47 | #define BCM2708_DMA_INT_STATUS 0xfe0 /* Interrupt status of each channel */ | |
48 | #define BCM2708_DMA_ENABLE 0xff0 /* Global enable bits for each channel */ | |
49 | ||
50 | #define BCM2708_DMA_CS_RW_MASK 0x30ff0001 /* All RW bits in DMA_CS */ | |
51 | ||
52 | static void bcm2835_dma_update(BCM2835DMAState *s, unsigned c) | |
53 | { | |
54 | BCM2835DMAChan *ch = &s->chan[c]; | |
55 | uint32_t data, xlen, ylen; | |
56 | int16_t dst_stride, src_stride; | |
57 | ||
58 | if (!(s->enable & (1 << c))) { | |
59 | return; | |
60 | } | |
61 | ||
62 | while ((s->enable & (1 << c)) && (ch->conblk_ad != 0)) { | |
63 | /* CB fetch */ | |
64 | ch->ti = ldl_le_phys(&s->dma_as, ch->conblk_ad); | |
65 | ch->source_ad = ldl_le_phys(&s->dma_as, ch->conblk_ad + 4); | |
66 | ch->dest_ad = ldl_le_phys(&s->dma_as, ch->conblk_ad + 8); | |
67 | ch->txfr_len = ldl_le_phys(&s->dma_as, ch->conblk_ad + 12); | |
68 | ch->stride = ldl_le_phys(&s->dma_as, ch->conblk_ad + 16); | |
69 | ch->nextconbk = ldl_le_phys(&s->dma_as, ch->conblk_ad + 20); | |
70 | ||
71 | if (ch->ti & BCM2708_DMA_TDMODE) { | |
72 | /* 2D transfer mode */ | |
73 | ylen = (ch->txfr_len >> 16) & 0x3fff; | |
74 | xlen = ch->txfr_len & 0xffff; | |
75 | dst_stride = ch->stride >> 16; | |
76 | src_stride = ch->stride & 0xffff; | |
77 | } else { | |
78 | ylen = 1; | |
79 | xlen = ch->txfr_len; | |
80 | dst_stride = 0; | |
81 | src_stride = 0; | |
82 | } | |
83 | ||
84 | while (ylen != 0) { | |
85 | /* Normal transfer mode */ | |
86 | while (xlen != 0) { | |
87 | if (ch->ti & BCM2708_DMA_S_IGNORE) { | |
88 | /* Ignore reads */ | |
89 | data = 0; | |
90 | } else { | |
91 | data = ldl_le_phys(&s->dma_as, ch->source_ad); | |
92 | } | |
93 | if (ch->ti & BCM2708_DMA_S_INC) { | |
94 | ch->source_ad += 4; | |
95 | } | |
96 | ||
97 | if (ch->ti & BCM2708_DMA_D_IGNORE) { | |
98 | /* Ignore writes */ | |
99 | } else { | |
100 | stl_le_phys(&s->dma_as, ch->dest_ad, data); | |
101 | } | |
102 | if (ch->ti & BCM2708_DMA_D_INC) { | |
103 | ch->dest_ad += 4; | |
104 | } | |
105 | ||
106 | /* update remaining transfer length */ | |
107 | xlen -= 4; | |
108 | if (ch->ti & BCM2708_DMA_TDMODE) { | |
109 | ch->txfr_len = (ylen << 16) | xlen; | |
110 | } else { | |
111 | ch->txfr_len = xlen; | |
112 | } | |
113 | } | |
114 | ||
115 | if (--ylen != 0) { | |
116 | ch->source_ad += src_stride; | |
117 | ch->dest_ad += dst_stride; | |
118 | } | |
119 | } | |
120 | ch->cs |= BCM2708_DMA_END; | |
121 | if (ch->ti & BCM2708_DMA_INT_EN) { | |
122 | ch->cs |= BCM2708_DMA_INT; | |
123 | s->int_status |= (1 << c); | |
124 | qemu_set_irq(ch->irq, 1); | |
125 | } | |
126 | ||
127 | /* Process next CB */ | |
128 | ch->conblk_ad = ch->nextconbk; | |
129 | } | |
130 | ||
131 | ch->cs &= ~BCM2708_DMA_ACTIVE; | |
132 | ch->cs |= BCM2708_DMA_ISPAUSED; | |
133 | } | |
134 | ||
135 | static void bcm2835_dma_chan_reset(BCM2835DMAChan *ch) | |
136 | { | |
137 | ch->cs = 0; | |
138 | ch->conblk_ad = 0; | |
139 | } | |
140 | ||
141 | static uint64_t bcm2835_dma_read(BCM2835DMAState *s, hwaddr offset, | |
142 | unsigned size, unsigned c) | |
143 | { | |
144 | BCM2835DMAChan *ch; | |
145 | uint32_t res = 0; | |
146 | ||
147 | assert(size == 4); | |
148 | assert(c < BCM2835_DMA_NCHANS); | |
149 | ||
150 | ch = &s->chan[c]; | |
151 | ||
152 | switch (offset) { | |
153 | case BCM2708_DMA_CS: | |
154 | res = ch->cs; | |
155 | break; | |
156 | case BCM2708_DMA_ADDR: | |
157 | res = ch->conblk_ad; | |
158 | break; | |
159 | case BCM2708_DMA_INFO: | |
160 | res = ch->ti; | |
161 | break; | |
162 | case BCM2708_DMA_SOURCE_AD: | |
163 | res = ch->source_ad; | |
164 | break; | |
165 | case BCM2708_DMA_DEST_AD: | |
166 | res = ch->dest_ad; | |
167 | break; | |
168 | case BCM2708_DMA_TXFR_LEN: | |
169 | res = ch->txfr_len; | |
170 | break; | |
171 | case BCM2708_DMA_STRIDE: | |
172 | res = ch->stride; | |
173 | break; | |
174 | case BCM2708_DMA_NEXTCB: | |
175 | res = ch->nextconbk; | |
176 | break; | |
177 | case BCM2708_DMA_DEBUG: | |
178 | res = ch->debug; | |
179 | break; | |
180 | default: | |
181 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
182 | __func__, offset); | |
183 | break; | |
184 | } | |
185 | return res; | |
186 | } | |
187 | ||
188 | static void bcm2835_dma_write(BCM2835DMAState *s, hwaddr offset, | |
189 | uint64_t value, unsigned size, unsigned c) | |
190 | { | |
191 | BCM2835DMAChan *ch; | |
192 | uint32_t oldcs; | |
193 | ||
194 | assert(size == 4); | |
195 | assert(c < BCM2835_DMA_NCHANS); | |
196 | ||
197 | ch = &s->chan[c]; | |
198 | ||
199 | switch (offset) { | |
200 | case BCM2708_DMA_CS: | |
201 | oldcs = ch->cs; | |
202 | if (value & BCM2708_DMA_RESET) { | |
203 | bcm2835_dma_chan_reset(ch); | |
204 | } | |
205 | if (value & BCM2708_DMA_ABORT) { | |
206 | /* abort is a no-op, since we always run to completion */ | |
207 | } | |
208 | if (value & BCM2708_DMA_END) { | |
209 | ch->cs &= ~BCM2708_DMA_END; | |
210 | } | |
211 | if (value & BCM2708_DMA_INT) { | |
212 | ch->cs &= ~BCM2708_DMA_INT; | |
213 | s->int_status &= ~(1 << c); | |
214 | qemu_set_irq(ch->irq, 0); | |
215 | } | |
216 | ch->cs &= ~BCM2708_DMA_CS_RW_MASK; | |
217 | ch->cs |= (value & BCM2708_DMA_CS_RW_MASK); | |
218 | if (!(oldcs & BCM2708_DMA_ACTIVE) && (ch->cs & BCM2708_DMA_ACTIVE)) { | |
219 | bcm2835_dma_update(s, c); | |
220 | } | |
221 | break; | |
222 | case BCM2708_DMA_ADDR: | |
223 | ch->conblk_ad = value; | |
224 | break; | |
225 | case BCM2708_DMA_DEBUG: | |
226 | ch->debug = value; | |
227 | break; | |
228 | default: | |
229 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
230 | __func__, offset); | |
231 | break; | |
232 | } | |
233 | } | |
234 | ||
235 | static uint64_t bcm2835_dma0_read(void *opaque, hwaddr offset, unsigned size) | |
236 | { | |
237 | BCM2835DMAState *s = opaque; | |
238 | ||
239 | if (offset < 0xf00) { | |
240 | return bcm2835_dma_read(s, (offset & 0xff), size, (offset >> 8) & 0xf); | |
241 | } else { | |
242 | switch (offset) { | |
243 | case BCM2708_DMA_INT_STATUS: | |
244 | return s->int_status; | |
245 | case BCM2708_DMA_ENABLE: | |
246 | return s->enable; | |
247 | default: | |
248 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
249 | __func__, offset); | |
250 | return 0; | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | static uint64_t bcm2835_dma15_read(void *opaque, hwaddr offset, unsigned size) | |
256 | { | |
257 | return bcm2835_dma_read(opaque, (offset & 0xff), size, 15); | |
258 | } | |
259 | ||
260 | static void bcm2835_dma0_write(void *opaque, hwaddr offset, uint64_t value, | |
261 | unsigned size) | |
262 | { | |
263 | BCM2835DMAState *s = opaque; | |
264 | ||
265 | if (offset < 0xf00) { | |
266 | bcm2835_dma_write(s, (offset & 0xff), value, size, (offset >> 8) & 0xf); | |
267 | } else { | |
268 | switch (offset) { | |
269 | case BCM2708_DMA_INT_STATUS: | |
270 | break; | |
271 | case BCM2708_DMA_ENABLE: | |
272 | s->enable = (value & 0xffff); | |
273 | break; | |
274 | default: | |
275 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
276 | __func__, offset); | |
277 | } | |
278 | } | |
279 | ||
280 | } | |
281 | ||
282 | static void bcm2835_dma15_write(void *opaque, hwaddr offset, uint64_t value, | |
283 | unsigned size) | |
284 | { | |
285 | bcm2835_dma_write(opaque, (offset & 0xff), value, size, 15); | |
286 | } | |
287 | ||
288 | static const MemoryRegionOps bcm2835_dma0_ops = { | |
289 | .read = bcm2835_dma0_read, | |
290 | .write = bcm2835_dma0_write, | |
291 | .endianness = DEVICE_NATIVE_ENDIAN, | |
292 | .valid.min_access_size = 4, | |
293 | .valid.max_access_size = 4, | |
294 | }; | |
295 | ||
296 | static const MemoryRegionOps bcm2835_dma15_ops = { | |
297 | .read = bcm2835_dma15_read, | |
298 | .write = bcm2835_dma15_write, | |
299 | .endianness = DEVICE_NATIVE_ENDIAN, | |
300 | .valid.min_access_size = 4, | |
301 | .valid.max_access_size = 4, | |
302 | }; | |
303 | ||
304 | static const VMStateDescription vmstate_bcm2835_dma_chan = { | |
305 | .name = TYPE_BCM2835_DMA "-chan", | |
306 | .version_id = 1, | |
307 | .minimum_version_id = 1, | |
308 | .fields = (VMStateField[]) { | |
309 | VMSTATE_UINT32(cs, BCM2835DMAChan), | |
310 | VMSTATE_UINT32(conblk_ad, BCM2835DMAChan), | |
311 | VMSTATE_UINT32(ti, BCM2835DMAChan), | |
312 | VMSTATE_UINT32(source_ad, BCM2835DMAChan), | |
313 | VMSTATE_UINT32(dest_ad, BCM2835DMAChan), | |
314 | VMSTATE_UINT32(txfr_len, BCM2835DMAChan), | |
315 | VMSTATE_UINT32(stride, BCM2835DMAChan), | |
316 | VMSTATE_UINT32(nextconbk, BCM2835DMAChan), | |
317 | VMSTATE_UINT32(debug, BCM2835DMAChan), | |
318 | VMSTATE_END_OF_LIST() | |
319 | } | |
320 | }; | |
321 | ||
322 | static const VMStateDescription vmstate_bcm2835_dma = { | |
323 | .name = TYPE_BCM2835_DMA, | |
324 | .version_id = 1, | |
325 | .minimum_version_id = 1, | |
326 | .fields = (VMStateField[]) { | |
327 | VMSTATE_STRUCT_ARRAY(chan, BCM2835DMAState, BCM2835_DMA_NCHANS, 1, | |
328 | vmstate_bcm2835_dma_chan, BCM2835DMAChan), | |
329 | VMSTATE_UINT32(int_status, BCM2835DMAState), | |
330 | VMSTATE_UINT32(enable, BCM2835DMAState), | |
331 | VMSTATE_END_OF_LIST() | |
332 | } | |
333 | }; | |
334 | ||
335 | static void bcm2835_dma_init(Object *obj) | |
336 | { | |
337 | BCM2835DMAState *s = BCM2835_DMA(obj); | |
338 | int n; | |
339 | ||
340 | /* DMA channels 0-14 occupy a contiguous block of IO memory, along | |
341 | * with the global enable and interrupt status bits. Channel 15 | |
342 | * has the same register map, but is mapped at a discontiguous | |
343 | * address in a separate IO block. | |
344 | */ | |
345 | memory_region_init_io(&s->iomem0, OBJECT(s), &bcm2835_dma0_ops, s, | |
346 | TYPE_BCM2835_DMA, 0x1000); | |
347 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem0); | |
348 | ||
349 | memory_region_init_io(&s->iomem15, OBJECT(s), &bcm2835_dma15_ops, s, | |
350 | TYPE_BCM2835_DMA "-chan15", 0x100); | |
351 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem15); | |
352 | ||
353 | for (n = 0; n < 16; n++) { | |
354 | sysbus_init_irq(SYS_BUS_DEVICE(s), &s->chan[n].irq); | |
355 | } | |
356 | } | |
357 | ||
358 | static void bcm2835_dma_reset(DeviceState *dev) | |
359 | { | |
360 | BCM2835DMAState *s = BCM2835_DMA(dev); | |
361 | int n; | |
362 | ||
363 | s->enable = 0xffff; | |
364 | s->int_status = 0; | |
365 | for (n = 0; n < BCM2835_DMA_NCHANS; n++) { | |
366 | bcm2835_dma_chan_reset(&s->chan[n]); | |
367 | } | |
368 | } | |
369 | ||
370 | static void bcm2835_dma_realize(DeviceState *dev, Error **errp) | |
371 | { | |
372 | BCM2835DMAState *s = BCM2835_DMA(dev); | |
373 | Error *err = NULL; | |
374 | Object *obj; | |
375 | ||
376 | obj = object_property_get_link(OBJECT(dev), "dma-mr", &err); | |
377 | if (obj == NULL) { | |
378 | error_setg(errp, "%s: required dma-mr link not found: %s", | |
379 | __func__, error_get_pretty(err)); | |
380 | return; | |
381 | } | |
382 | ||
383 | s->dma_mr = MEMORY_REGION(obj); | |
384 | address_space_init(&s->dma_as, s->dma_mr, NULL); | |
385 | ||
386 | bcm2835_dma_reset(dev); | |
387 | } | |
388 | ||
389 | static void bcm2835_dma_class_init(ObjectClass *klass, void *data) | |
390 | { | |
391 | DeviceClass *dc = DEVICE_CLASS(klass); | |
392 | ||
393 | dc->realize = bcm2835_dma_realize; | |
394 | dc->reset = bcm2835_dma_reset; | |
395 | dc->vmsd = &vmstate_bcm2835_dma; | |
396 | } | |
397 | ||
398 | static TypeInfo bcm2835_dma_info = { | |
399 | .name = TYPE_BCM2835_DMA, | |
400 | .parent = TYPE_SYS_BUS_DEVICE, | |
401 | .instance_size = sizeof(BCM2835DMAState), | |
402 | .class_init = bcm2835_dma_class_init, | |
403 | .instance_init = bcm2835_dma_init, | |
404 | }; | |
405 | ||
406 | static void bcm2835_dma_register_types(void) | |
407 | { | |
408 | type_register_static(&bcm2835_dma_info); | |
409 | } | |
410 | ||
411 | type_init(bcm2835_dma_register_types) |