Commit 6361cdb630afa036dcff8586735c38d1aca55a7c
1 parent
70cf0b63
ARM PL181 MMCI fixes.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2964 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
1 changed file
with
34 additions
and
11 deletions
hw/pl181.c
@@ -36,9 +36,14 @@ typedef struct { | @@ -36,9 +36,14 @@ typedef struct { | ||
36 | uint32_t datacnt; | 36 | uint32_t datacnt; |
37 | uint32_t status; | 37 | uint32_t status; |
38 | uint32_t mask[2]; | 38 | uint32_t mask[2]; |
39 | - uint32_t fifocnt; | ||
40 | int fifo_pos; | 39 | int fifo_pos; |
41 | int fifo_len; | 40 | int fifo_len; |
41 | + /* The linux 2.6.21 driver is buggy, and misbehaves if new data arrives | ||
42 | + while it is reading the FIFO. We hack around this be defering | ||
43 | + subsequent transfers until after the driver polls the status word. | ||
44 | + http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=4446/1 | ||
45 | + */ | ||
46 | + int linux_hack; | ||
42 | uint32_t fifo[PL181_FIFO_LEN]; | 47 | uint32_t fifo[PL181_FIFO_LEN]; |
43 | qemu_irq irq[2]; | 48 | qemu_irq irq[2]; |
44 | } pl181_state; | 49 | } pl181_state; |
@@ -182,7 +187,8 @@ static void pl181_fifo_run(pl181_state *s) | @@ -182,7 +187,8 @@ static void pl181_fifo_run(pl181_state *s) | ||
182 | int is_read; | 187 | int is_read; |
183 | 188 | ||
184 | is_read = (s->datactrl & PL181_DATA_DIRECTION) != 0; | 189 | is_read = (s->datactrl & PL181_DATA_DIRECTION) != 0; |
185 | - if (s->datacnt != 0 && (!is_read || sd_data_ready(s->card))) { | 190 | + if (s->datacnt != 0 && (!is_read || sd_data_ready(s->card)) |
191 | + && !s->linux_hack) { | ||
186 | limit = is_read ? PL181_FIFO_LEN : 0; | 192 | limit = is_read ? PL181_FIFO_LEN : 0; |
187 | n = 0; | 193 | n = 0; |
188 | value = 0; | 194 | value = 0; |
@@ -217,7 +223,7 @@ static void pl181_fifo_run(pl181_state *s) | @@ -217,7 +223,7 @@ static void pl181_fifo_run(pl181_state *s) | ||
217 | s->status |= PL181_STATUS_DATABLOCKEND; | 223 | s->status |= PL181_STATUS_DATABLOCKEND; |
218 | DPRINTF("Transfer Complete\n"); | 224 | DPRINTF("Transfer Complete\n"); |
219 | } | 225 | } |
220 | - if (s->datacnt == 0 && s->fifocnt == 0) { | 226 | + if (s->datacnt == 0 && s->fifo_len == 0) { |
221 | s->datactrl &= ~PL181_DATA_ENABLE; | 227 | s->datactrl &= ~PL181_DATA_ENABLE; |
222 | DPRINTF("Data engine idle\n"); | 228 | DPRINTF("Data engine idle\n"); |
223 | } else { | 229 | } else { |
@@ -252,6 +258,7 @@ static void pl181_fifo_run(pl181_state *s) | @@ -252,6 +258,7 @@ static void pl181_fifo_run(pl181_state *s) | ||
252 | static uint32_t pl181_read(void *opaque, target_phys_addr_t offset) | 258 | static uint32_t pl181_read(void *opaque, target_phys_addr_t offset) |
253 | { | 259 | { |
254 | pl181_state *s = (pl181_state *)opaque; | 260 | pl181_state *s = (pl181_state *)opaque; |
261 | + uint32_t tmp; | ||
255 | 262 | ||
256 | offset -= s->base; | 263 | offset -= s->base; |
257 | if (offset >= 0xfe0 && offset < 0x1000) { | 264 | if (offset >= 0xfe0 && offset < 0x1000) { |
@@ -285,24 +292,42 @@ static uint32_t pl181_read(void *opaque, target_phys_addr_t offset) | @@ -285,24 +292,42 @@ static uint32_t pl181_read(void *opaque, target_phys_addr_t offset) | ||
285 | case 0x30: /* DataCnt */ | 292 | case 0x30: /* DataCnt */ |
286 | return s->datacnt; | 293 | return s->datacnt; |
287 | case 0x34: /* Status */ | 294 | case 0x34: /* Status */ |
288 | - return s->status; | 295 | + tmp = s->status; |
296 | + if (s->linux_hack) { | ||
297 | + s->linux_hack = 0; | ||
298 | + pl181_fifo_run(s); | ||
299 | + pl181_update(s); | ||
300 | + } | ||
301 | + return tmp; | ||
289 | case 0x3c: /* Mask0 */ | 302 | case 0x3c: /* Mask0 */ |
290 | return s->mask[0]; | 303 | return s->mask[0]; |
291 | case 0x40: /* Mask1 */ | 304 | case 0x40: /* Mask1 */ |
292 | return s->mask[1]; | 305 | return s->mask[1]; |
293 | case 0x48: /* FifoCnt */ | 306 | case 0x48: /* FifoCnt */ |
294 | - return s->fifocnt; | 307 | + /* The documentation is somewhat vague about exactly what FifoCnt |
308 | + does. On real hardware it appears to be when decrememnted | ||
309 | + when a word is transfered between the FIFO and the serial | ||
310 | + data engine. DataCnt is decremented after each byte is | ||
311 | + transfered between the serial engine and the card. | ||
312 | + We don't emulate this level of detail, so both can be the same. */ | ||
313 | + tmp = (s->datacnt + 3) >> 2; | ||
314 | + if (s->linux_hack) { | ||
315 | + s->linux_hack = 0; | ||
316 | + pl181_fifo_run(s); | ||
317 | + pl181_update(s); | ||
318 | + } | ||
319 | + return tmp; | ||
295 | case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ | 320 | case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ |
296 | case 0x90: case 0x94: case 0x98: case 0x9c: | 321 | case 0x90: case 0x94: case 0x98: case 0x9c: |
297 | case 0xa0: case 0xa4: case 0xa8: case 0xac: | 322 | case 0xa0: case 0xa4: case 0xa8: case 0xac: |
298 | case 0xb0: case 0xb4: case 0xb8: case 0xbc: | 323 | case 0xb0: case 0xb4: case 0xb8: case 0xbc: |
299 | - if (s->fifocnt == 0) { | 324 | + if (s->fifo_len == 0) { |
300 | fprintf(stderr, "pl181: Unexpected FIFO read\n"); | 325 | fprintf(stderr, "pl181: Unexpected FIFO read\n"); |
301 | return 0; | 326 | return 0; |
302 | } else { | 327 | } else { |
303 | uint32_t value; | 328 | uint32_t value; |
304 | - s->fifocnt--; | ||
305 | value = pl181_fifo_pop(s); | 329 | value = pl181_fifo_pop(s); |
330 | + s->linux_hack = 1; | ||
306 | pl181_fifo_run(s); | 331 | pl181_fifo_run(s); |
307 | pl181_update(s); | 332 | pl181_update(s); |
308 | return value; | 333 | return value; |
@@ -356,7 +381,6 @@ static void pl181_write(void *opaque, target_phys_addr_t offset, | @@ -356,7 +381,6 @@ static void pl181_write(void *opaque, target_phys_addr_t offset, | ||
356 | s->datactrl = value & 0xff; | 381 | s->datactrl = value & 0xff; |
357 | if (value & PL181_DATA_ENABLE) { | 382 | if (value & PL181_DATA_ENABLE) { |
358 | s->datacnt = s->datalength; | 383 | s->datacnt = s->datalength; |
359 | - s->fifocnt = (s->datalength + 3) >> 2; | ||
360 | pl181_fifo_run(s); | 384 | pl181_fifo_run(s); |
361 | } | 385 | } |
362 | break; | 386 | break; |
@@ -373,10 +397,9 @@ static void pl181_write(void *opaque, target_phys_addr_t offset, | @@ -373,10 +397,9 @@ static void pl181_write(void *opaque, target_phys_addr_t offset, | ||
373 | case 0x90: case 0x94: case 0x98: case 0x9c: | 397 | case 0x90: case 0x94: case 0x98: case 0x9c: |
374 | case 0xa0: case 0xa4: case 0xa8: case 0xac: | 398 | case 0xa0: case 0xa4: case 0xa8: case 0xac: |
375 | case 0xb0: case 0xb4: case 0xb8: case 0xbc: | 399 | case 0xb0: case 0xb4: case 0xb8: case 0xbc: |
376 | - if (s->fifocnt == 0) { | 400 | + if (s->datacnt == 0) { |
377 | fprintf(stderr, "pl181: Unexpected FIFO write\n"); | 401 | fprintf(stderr, "pl181: Unexpected FIFO write\n"); |
378 | } else { | 402 | } else { |
379 | - s->fifocnt--; | ||
380 | pl181_fifo_push(s, value); | 403 | pl181_fifo_push(s, value); |
381 | pl181_fifo_run(s); | 404 | pl181_fifo_run(s); |
382 | } | 405 | } |
@@ -418,9 +441,9 @@ static void pl181_reset(void *opaque) | @@ -418,9 +441,9 @@ static void pl181_reset(void *opaque) | ||
418 | s->datactrl = 0; | 441 | s->datactrl = 0; |
419 | s->datacnt = 0; | 442 | s->datacnt = 0; |
420 | s->status = 0; | 443 | s->status = 0; |
444 | + s->linux_hack = 0; | ||
421 | s->mask[0] = 0; | 445 | s->mask[0] = 0; |
422 | s->mask[1] = 0; | 446 | s->mask[1] = 0; |
423 | - s->fifocnt = 0; | ||
424 | } | 447 | } |
425 | 448 | ||
426 | void pl181_init(uint32_t base, BlockDriverState *bd, | 449 | void pl181_init(uint32_t base, BlockDriverState *bd, |