qdev scsi bus infrastructure
[qemu] / hw / pl181.c
index cf731cc..ab4e978 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
  * Arm PrimeCell PL181 MultiMedia Card Interface
  *
  * Copyright (c) 2007 CodeSourcery.
@@ -7,23 +7,23 @@
  * This code is licenced under the GPL.
  */
 
-#include "vl.h"
+#include "hw.h"
+#include "primecell.h"
 #include "sd.h"
 
 //#define DEBUG_PL181 1
 
 #ifdef DEBUG_PL181
-#define DPRINTF(fmt, args...) \
-do { printf("pl181: " fmt , ##args); } while (0)
+#define DPRINTF(fmt, ...) \
+do { printf("pl181: " fmt , ## __VA_ARGS__); } while (0)
 #else
-#define DPRINTF(fmt, args...) do {} while(0)
+#define DPRINTF(fmt, ...) do {} while(0)
 #endif
 
 #define PL181_FIFO_LEN 16
 
 typedef struct {
     SDState *card;
-    uint32_t base;
     uint32_t clock;
     uint32_t power;
     uint32_t cmdarg;
@@ -36,9 +36,14 @@ typedef struct {
     uint32_t datacnt;
     uint32_t status;
     uint32_t mask[2];
-    uint32_t fifocnt;
     int fifo_pos;
     int fifo_len;
+    /* The linux 2.6.21 driver is buggy, and misbehaves if new data arrives
+       while it is reading the FIFO.  We hack around this be defering
+       subsequent transfers until after the driver polls the status word.
+       http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=4446/1
+     */
+    int linux_hack;
     uint32_t fifo[PL181_FIFO_LEN];
     qemu_irq irq[2];
 } pl181_state;
@@ -130,7 +135,7 @@ static uint32_t pl181_fifo_pop(pl181_state *s)
 
 static void pl181_send_command(pl181_state *s)
 {
-    struct sd_request_s request;
+    SDRequest request;
     uint8_t response[16];
     int rlen;
 
@@ -155,7 +160,7 @@ static void pl181_send_command(pl181_state *s)
             s->response[2] = RWORD(8);
             s->response[3] = RWORD(12) & ~1;
         }
-        DPRINTF("Response recieved\n");
+        DPRINTF("Response received\n");
         s->status |= PL181_STATUS_CMDRESPEND;
 #undef RWORD
     } else {
@@ -169,10 +174,10 @@ error:
     s->status |= PL181_STATUS_CMDTIMEOUT;
 }
 
-/* Transfer data between teh card and the FIFO.  This is complicated by
+/* Transfer data between the card and the FIFO.  This is complicated by
    the FIFO holding 32-bit words and the card taking data in single byte
    chunks.  FIFO bytes are transferred in little-endian order.  */
-   
+
 static void pl181_fifo_run(pl181_state *s)
 {
     uint32_t bits;
@@ -182,7 +187,8 @@ static void pl181_fifo_run(pl181_state *s)
     int is_read;
 
     is_read = (s->datactrl & PL181_DATA_DIRECTION) != 0;
-    if (s->datacnt != 0 && (!is_read || sd_data_ready(s->card))) {
+    if (s->datacnt != 0 && (!is_read || sd_data_ready(s->card))
+            && !s->linux_hack) {
         limit = is_read ? PL181_FIFO_LEN : 0;
         n = 0;
         value = 0;
@@ -217,7 +223,7 @@ static void pl181_fifo_run(pl181_state *s)
         s->status |= PL181_STATUS_DATABLOCKEND;
         DPRINTF("Transfer Complete\n");
     }
-    if (s->datacnt == 0 && s->fifocnt == 0) {
+    if (s->datacnt == 0 && s->fifo_len == 0) {
         s->datactrl &= ~PL181_DATA_ENABLE;
         DPRINTF("Data engine idle\n");
     } else {
@@ -252,8 +258,8 @@ static void pl181_fifo_run(pl181_state *s)
 static uint32_t pl181_read(void *opaque, target_phys_addr_t offset)
 {
     pl181_state *s = (pl181_state *)opaque;
+    uint32_t tmp;
 
-    offset -= s->base;
     if (offset >= 0xfe0 && offset < 0x1000) {
         return pl181_id[(offset - 0xfe0) >> 2];
     }
@@ -285,30 +291,48 @@ static uint32_t pl181_read(void *opaque, target_phys_addr_t offset)
     case 0x30: /* DataCnt */
         return s->datacnt;
     case 0x34: /* Status */
-        return s->status;
+        tmp = s->status;
+        if (s->linux_hack) {
+            s->linux_hack = 0;
+            pl181_fifo_run(s);
+            pl181_update(s);
+        }
+        return tmp;
     case 0x3c: /* Mask0 */
         return s->mask[0];
     case 0x40: /* Mask1 */
         return s->mask[1];
     case 0x48: /* FifoCnt */
-        return s->fifocnt;
+        /* The documentation is somewhat vague about exactly what FifoCnt
+           does.  On real hardware it appears to be when decrememnted
+           when a word is transfered between the FIFO and the serial
+           data engine.  DataCnt is decremented after each byte is
+           transfered between the serial engine and the card.
+           We don't emulate this level of detail, so both can be the same.  */
+        tmp = (s->datacnt + 3) >> 2;
+        if (s->linux_hack) {
+            s->linux_hack = 0;
+            pl181_fifo_run(s);
+            pl181_update(s);
+        }
+        return tmp;
     case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */
     case 0x90: case 0x94: case 0x98: case 0x9c:
     case 0xa0: case 0xa4: case 0xa8: case 0xac:
     case 0xb0: case 0xb4: case 0xb8: case 0xbc:
-        if (s->fifocnt == 0) {
+        if (s->fifo_len == 0) {
             fprintf(stderr, "pl181: Unexpected FIFO read\n");
             return 0;
         } else {
             uint32_t value;
-            s->fifocnt--;
             value = pl181_fifo_pop(s);
+            s->linux_hack = 1;
             pl181_fifo_run(s);
             pl181_update(s);
             return value;
         }
     default:
-        cpu_abort (cpu_single_env, "pl181_read: Bad offset %x\n", offset);
+        hw_error("pl181_read: Bad offset %x\n", (int)offset);
         return 0;
     }
 }
@@ -318,7 +342,6 @@ static void pl181_write(void *opaque, target_phys_addr_t offset,
 {
     pl181_state *s = (pl181_state *)opaque;
 
-    offset -= s->base;
     switch (offset) {
     case 0x00: /* Power */
         s->power = value & 0xff;
@@ -356,7 +379,6 @@ static void pl181_write(void *opaque, target_phys_addr_t offset,
         s->datactrl = value & 0xff;
         if (value & PL181_DATA_ENABLE) {
             s->datacnt = s->datalength;
-            s->fifocnt = (s->datalength + 3) >> 2;
             pl181_fifo_run(s);
         }
         break;
@@ -373,16 +395,15 @@ static void pl181_write(void *opaque, target_phys_addr_t offset,
     case 0x90: case 0x94: case 0x98: case 0x9c:
     case 0xa0: case 0xa4: case 0xa8: case 0xac:
     case 0xb0: case 0xb4: case 0xb8: case 0xbc:
-        if (s->fifocnt == 0) {
+        if (s->datacnt == 0) {
             fprintf(stderr, "pl181: Unexpected FIFO write\n");
         } else {
-            s->fifocnt--;
             pl181_fifo_push(s, value);
             pl181_fifo_run(s);
         }
         break;
     default:
-        cpu_abort (cpu_single_env, "pl181_write: Bad offset %x\n", offset);
+        hw_error("pl181_write: Bad offset %x\n", (int)offset);
     }
     pl181_update(s);
 }
@@ -418,9 +439,9 @@ static void pl181_reset(void *opaque)
     s->datactrl = 0;
     s->datacnt = 0;
     s->status = 0;
+    s->linux_hack = 0;
     s->mask[0] = 0;
     s->mask[1] = 0;
-    s->fifocnt = 0;
 }
 
 void pl181_init(uint32_t base, BlockDriverState *bd,
@@ -432,9 +453,8 @@ void pl181_init(uint32_t base, BlockDriverState *bd,
     s = (pl181_state *)qemu_mallocz(sizeof(pl181_state));
     iomemtype = cpu_register_io_memory(0, pl181_readfn,
                                        pl181_writefn, s);
-    cpu_register_physical_memory(base, 0x00000fff, iomemtype);
-    s->base = base;
-    s->card = sd_init(bd);
+    cpu_register_physical_memory(base, 0x00001000, iomemtype);
+    s->card = sd_init(bd, 0);
     s->irq[0] = irq0;
     s->irq[1] = irq1;
     qemu_register_reset(pl181_reset, s);