With these changes, SeaBIOS AHCI support works for both hard disk and
optical drives using AMD SB800. Read and write buffers must be word aligned.

Signed-off-by: Scott Duplichan <scott@notabs.org>


--- seabios-0.6.2-original\src\ahci.c	Mon Feb 28 21:10:57 2011
+++ seabios-0.6.2\src\ahci.c	Wed May 25 21:31:12 2011
@@ -105,7 +105,7 @@
 static int ahci_command(struct ahci_port_s *port, int iswrite, int isatapi,
                         void *buffer, u32 bsize)
 {
-    u32 val, status, success, flags;
+    u32 val, status, flags, intbits, error;
     struct ahci_ctrl_s *ctrl = GET_GLOBAL(port->ctrl);
     struct ahci_cmd_s  *cmd  = GET_GLOBAL(port->cmd);
     struct ahci_fis_s  *fis  = GET_GLOBAL(port->fis);
@@ -118,19 +118,24 @@
     SET_FLATPTR(cmd->prdt[0].baseu, 0);
     SET_FLATPTR(cmd->prdt[0].flags, bsize-1);
 
+    // clear interrupt status
+    val = ahci_port_readl(ctrl, pnr, PORT_IRQ_STAT);
+    ahci_port_writel(ctrl, pnr, PORT_IRQ_STAT, val);
+    
+    // toggle the start control
     val = ahci_port_readl(ctrl, pnr, PORT_CMD);
+    ahci_port_writel(ctrl, pnr, PORT_CMD, val & ~(PORT_CMD_START | PORT_CMD_FIS_RX));
     ahci_port_writel(ctrl, pnr, PORT_CMD, val | PORT_CMD_START);
 
     if (ahci_port_readl(ctrl, pnr, PORT_CMD_ISSUE))
         return -1;
 
     flags = ((1 << 16) | /* one prd entry */
-             (1 << 10) | /* clear busy on ok */
              (iswrite ? (1 << 6) : 0) |
              (isatapi ? (1 << 5) : 0) |
-             (4 << 0)); /* fis length (dwords) */
+             (5 << 0)); /* fis length (dwords) */
     SET_FLATPTR(list[0].flags, flags);
-    SET_FLATPTR(list[0].bytes,  bsize);
+    SET_FLATPTR(list[0].bytes,  0);
     SET_FLATPTR(list[0].base,   ((u32)(cmd)));
     SET_FLATPTR(list[0].baseu,  0);
 
@@ -138,19 +143,39 @@
     SET_FLATPTR(fis->rfis[2], 0);
     ahci_port_writel(ctrl, pnr, PORT_SCR_ACT, 1);
     ahci_port_writel(ctrl, pnr, PORT_CMD_ISSUE, 1);
-    while (ahci_port_readl(ctrl, pnr, PORT_CMD_ISSUE)) {
-        yield();
-    }
-    while ((status = GET_FLATPTR(fis->rfis[2])) == 0) {
+    while (1) {
+        intbits = ahci_port_readl(ctrl, pnr, PORT_IRQ_STAT) & 0x3f;
+        if (intbits) break;
         yield();
     }
-
-    success = (0x00 == (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_DF |
-                                  ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR)) &&
-               ATA_CB_STAT_RDY == (status & (ATA_CB_STAT_RDY)));
-    dprintf(2, "AHCI/%d: ... finished, status 0x%x, %s\n", pnr,
-            status, success ? "OK" : "ERROR");
-    return success ? 0 : -1;
+    status = 0xFFFF;
+    error = -2;
+    if (intbits == 0x01) { // Device to Host Register FIS Interrupt (DHRS)
+        status = GET_FLATPTR(fis->rfis[2]);
+        error = GET_FLATPTR(fis->rfis[3]);
+    }
+    if (intbits == 0x02) { // PIO Setup FIS Interrupt (PSS)
+        status = GET_FLATPTR(fis->psfis[2]);
+        error = GET_FLATPTR(fis->psfis[3]);
+    }
+    if (intbits == 0x04) { // DMA Setup FIS Interrupt (DSS)
+        status = GET_FLATPTR(fis->dsfis[2]);
+        error = GET_FLATPTR(fis->dsfis[3]);
+    }
+    if (intbits == 0x08) { // Set Device Bits Interrupt (SDBS)
+        status = GET_FLATPTR(fis->sdbfis[2]);
+        error = GET_FLATPTR(fis->sdbfis[3]);
+    }
+    if (intbits == 0x10) { // Unknown FIS Interrupt (UFS)
+        status = GET_FLATPTR(fis->ufis[2]);
+        error = GET_FLATPTR(fis->ufis[3]);
+    }
+
+    if (!error)
+        dprintf(2, "AHCI/%d: ... finished, status 0x%02x OK\n", pnr, status);
+    else
+        dprintf(2, "AHCI/%d: ... finished, status 0x%02x ERROR %02x\n", pnr, status, error);
+    return error;
 }
 
 #define CDROM_CDB_SIZE 12
@@ -172,7 +197,7 @@
     }
     rc = ahci_command(port, 0, 1, op->buf_fl,
                       op->count * blocksize);
-    if (rc < 0)
+    if (rc)
         return DISK_RET_EBADTRACK;
     return DISK_RET_SUCCESS;
 }
@@ -191,11 +216,12 @@
                       op->count * DISK_SECTOR_SIZE);
     dprintf(2, "ahci disk %s, lba %6x, count %3x, buf %p, rc %d\n",
             iswrite ? "write" : "read", (u32)op->lba, op->count, op->buf_fl, rc);
-    if (rc < 0)
+    if (rc)
         return DISK_RET_EBADTRACK;
     return DISK_RET_SUCCESS;
 }
 
+
 // command demuxer
 int process_ahci_op(struct disk_op_s *op)
 {
@@ -281,22 +307,10 @@
 static int
 ahci_port_probe(struct ahci_ctrl_s *ctrl, u32 pnr)
 {
-    u32 val, count = 0;
-
-    val = ahci_port_readl(ctrl, pnr, PORT_TFDATA);
-    while (val & ((1 << 7) /* BSY */ |
-                  (1 << 3) /* DRQ */)) {
-        ndelay(500);
-        val = ahci_port_readl(ctrl, pnr, PORT_TFDATA);
-        count++;
-        if (count >= AHCI_MAX_RETRIES)
-            return -1;
-    }
-
-    val = ahci_port_readl(ctrl, pnr, PORT_SCR_STAT);
-    if ((val & 0x07) != 0x03)
-        return -1;
-    return 0;
+    u32 val = ahci_port_readl(ctrl, pnr, PORT_SCR_STAT) & 7;
+    if (val == 3) // Device presence detected and Phy communication established
+        return 0;
+    return -1;
 }
 
 #define MAXMODEL 40
@@ -340,7 +354,7 @@
         port->atapi = 0;
         sata_prep_simple(&port->cmd->fis, ATA_CMD_IDENTIFY_DEVICE);
         rc = ahci_command(port, 0, 0, buffer, sizeof(buffer));
-        if (rc < 0)
+        if (rc)
             goto err;
     }
 
