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 | 36 | uint32_t datacnt; |
| 37 | 37 | uint32_t status; |
| 38 | 38 | uint32_t mask[2]; |
| 39 | - uint32_t fifocnt; | |
| 40 | 39 | int fifo_pos; |
| 41 | 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 | 47 | uint32_t fifo[PL181_FIFO_LEN]; |
| 43 | 48 | qemu_irq irq[2]; |
| 44 | 49 | } pl181_state; |
| ... | ... | @@ -182,7 +187,8 @@ static void pl181_fifo_run(pl181_state *s) |
| 182 | 187 | int is_read; |
| 183 | 188 | |
| 184 | 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 | 192 | limit = is_read ? PL181_FIFO_LEN : 0; |
| 187 | 193 | n = 0; |
| 188 | 194 | value = 0; |
| ... | ... | @@ -217,7 +223,7 @@ static void pl181_fifo_run(pl181_state *s) |
| 217 | 223 | s->status |= PL181_STATUS_DATABLOCKEND; |
| 218 | 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 | 227 | s->datactrl &= ~PL181_DATA_ENABLE; |
| 222 | 228 | DPRINTF("Data engine idle\n"); |
| 223 | 229 | } else { |
| ... | ... | @@ -252,6 +258,7 @@ static void pl181_fifo_run(pl181_state *s) |
| 252 | 258 | static uint32_t pl181_read(void *opaque, target_phys_addr_t offset) |
| 253 | 259 | { |
| 254 | 260 | pl181_state *s = (pl181_state *)opaque; |
| 261 | + uint32_t tmp; | |
| 255 | 262 | |
| 256 | 263 | offset -= s->base; |
| 257 | 264 | if (offset >= 0xfe0 && offset < 0x1000) { |
| ... | ... | @@ -285,24 +292,42 @@ static uint32_t pl181_read(void *opaque, target_phys_addr_t offset) |
| 285 | 292 | case 0x30: /* DataCnt */ |
| 286 | 293 | return s->datacnt; |
| 287 | 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 | 302 | case 0x3c: /* Mask0 */ |
| 290 | 303 | return s->mask[0]; |
| 291 | 304 | case 0x40: /* Mask1 */ |
| 292 | 305 | return s->mask[1]; |
| 293 | 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 | 320 | case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ |
| 296 | 321 | case 0x90: case 0x94: case 0x98: case 0x9c: |
| 297 | 322 | case 0xa0: case 0xa4: case 0xa8: case 0xac: |
| 298 | 323 | case 0xb0: case 0xb4: case 0xb8: case 0xbc: |
| 299 | - if (s->fifocnt == 0) { | |
| 324 | + if (s->fifo_len == 0) { | |
| 300 | 325 | fprintf(stderr, "pl181: Unexpected FIFO read\n"); |
| 301 | 326 | return 0; |
| 302 | 327 | } else { |
| 303 | 328 | uint32_t value; |
| 304 | - s->fifocnt--; | |
| 305 | 329 | value = pl181_fifo_pop(s); |
| 330 | + s->linux_hack = 1; | |
| 306 | 331 | pl181_fifo_run(s); |
| 307 | 332 | pl181_update(s); |
| 308 | 333 | return value; |
| ... | ... | @@ -356,7 +381,6 @@ static void pl181_write(void *opaque, target_phys_addr_t offset, |
| 356 | 381 | s->datactrl = value & 0xff; |
| 357 | 382 | if (value & PL181_DATA_ENABLE) { |
| 358 | 383 | s->datacnt = s->datalength; |
| 359 | - s->fifocnt = (s->datalength + 3) >> 2; | |
| 360 | 384 | pl181_fifo_run(s); |
| 361 | 385 | } |
| 362 | 386 | break; |
| ... | ... | @@ -373,10 +397,9 @@ static void pl181_write(void *opaque, target_phys_addr_t offset, |
| 373 | 397 | case 0x90: case 0x94: case 0x98: case 0x9c: |
| 374 | 398 | case 0xa0: case 0xa4: case 0xa8: case 0xac: |
| 375 | 399 | case 0xb0: case 0xb4: case 0xb8: case 0xbc: |
| 376 | - if (s->fifocnt == 0) { | |
| 400 | + if (s->datacnt == 0) { | |
| 377 | 401 | fprintf(stderr, "pl181: Unexpected FIFO write\n"); |
| 378 | 402 | } else { |
| 379 | - s->fifocnt--; | |
| 380 | 403 | pl181_fifo_push(s, value); |
| 381 | 404 | pl181_fifo_run(s); |
| 382 | 405 | } |
| ... | ... | @@ -418,9 +441,9 @@ static void pl181_reset(void *opaque) |
| 418 | 441 | s->datactrl = 0; |
| 419 | 442 | s->datacnt = 0; |
| 420 | 443 | s->status = 0; |
| 444 | + s->linux_hack = 0; | |
| 421 | 445 | s->mask[0] = 0; |
| 422 | 446 | s->mask[1] = 0; |
| 423 | - s->fifocnt = 0; | |
| 424 | 447 | } |
| 425 | 448 | |
| 426 | 449 | void pl181_init(uint32_t base, BlockDriverState *bd, | ... | ... |