[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/platform/pc/ide.c b/src/bsp/lk/platform/pc/ide.c
new file mode 100644
index 0000000..48ee309
--- /dev/null
+++ b/src/bsp/lk/platform/pc/ide.c
@@ -0,0 +1,908 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <reg.h>
+#include <debug.h>
+#include <trace.h>
+#include <assert.h>
+#include <err.h>
+#include <malloc.h>
+#include <arch/x86.h>
+#include <sys/types.h>
+#include <platform/interrupts.h>
+#include <platform/ide.h>
+#include <platform/pc.h>
+#include <platform.h>
+#include <dev/pci.h>
+#include <dev/driver.h>
+#include <dev/class/block.h>
+#include <kernel/event.h>
+
+#define LOCAL_TRACE 1
+
+// status register bits
+#define IDE_CTRL_BSY   0x80
+#define IDE_DRV_RDY    0x40
+#define IDE_DRV_WRTFLT 0x20
+#define IDE_DRV_SKCOMP 0x10
+#define IDE_DRV_DRQ    0x08
+#define IDE_DRV_CORDAT 0x04
+#define IDE_DRV_IDX    0x02
+#define IDE_DRV_ERR    0x01
+
+// ATA commands
+#define ATA_NOP            0x00
+#define ATA_ATAPIRESET     0x08
+#define ATA_RECALIBRATE    0x10
+#define ATA_READMULT_RET   0x20
+#define ATA_READMULT       0x21
+#define ATA_READECC_RET    0x22
+#define ATA_READECC        0x23
+#define ATA_WRITEMULT_RET  0x30
+#define ATA_WRITEMULT      0x31
+#define ATA_WRITEECC_RET   0x32
+#define ATA_WRITEECC       0x33
+#define ATA_VERIFYMULT_RET 0x40
+#define ATA_VERIFYMULT     0x41
+#define ATA_FORMATTRACK    0x50
+#define ATA_SEEK           0x70
+#define ATA_DIAG           0x90
+#define ATA_INITPARAMS     0x91
+#define ATA_ATAPIPACKET    0xA0
+#define ATA_ATAPIIDENTIFY  0xA1
+#define ATA_ATAPISERVICE   0xA2
+#define ATA_READ_DMA        0xC8
+#define ATA_READ_DMA_EXT    0x25
+#define ATA_WRITE_DMA       0xCA
+#define ATA_WRITE_DMA_EXT   0x35
+#define ATA_GETDEVINFO     0xEC
+#define ATA_ATAPISETFEAT   0xEF
+
+// error codes
+#define IDE_NOERROR         0
+#define IDE_ADDRESSMARK     1
+#define IDE_CYLINDER0       2
+#define IDE_INVALIDCOMMAND  3
+#define IDE_MEDIAREQ        4
+#define IDE_SECTNOTFOUND    5
+#define IDE_MEDIACHANGED    6
+#define IDE_BADDATA         7
+#define IDE_BADSECTOR       8
+#define IDE_TIMEOUT         9
+#define IDE_DMAERROR        10
+
+enum {
+    IDE_REG_DATA            = 0,
+    IDE_REG_ERROR           = 1,
+    IDE_REG_PRECOMP         = 1,
+    IDE_REG_SECTOR_COUNT    = 2,
+    IDE_REG_SECTOR_NUM      = 3,
+    IDE_REG_CYLINDER_LOW    = 4,
+    IDE_REG_CYLINDER_HIGH   = 5,
+    IDE_REG_DRIVE_HEAD      = 6,
+    IDE_REG_STATUS          = 7,
+    IDE_REG_COMMAND         = 7,
+    IDE_REG_ALT_STATUS      = 8,
+    IDE_REG_DEVICE_CONTROL  = 8,
+
+    IDE_REG_NUM,
+};
+
+enum {
+    TYPE_NONE,
+    TYPE_UNKNOWN,
+    TYPE_FLOPPY,
+    TYPE_IDECDROM,
+    TYPE_SCSICDROM,
+    TYPE_IDEDISK,
+    TYPE_SCSIDISK
+};
+
+static const char *ide_type_str[] = {
+    "None",
+    "Unknown",
+    "Floppy",
+    "IDE CDROM",
+    "SCSI CDROM",
+    "IDE Disk",
+    "SCSI Disk",
+};
+
+static const char *ide_error_str[] = {
+    "Unknown error",
+    "Address mark not found",
+    "Cylinder 0 not found",
+    "Command aborted - invalid command",
+    "Media change requested",
+    "ID or target sector not found",
+    "Media changed",
+    "Uncorrectable data error",
+    "Bad sector detected",
+    "Command timed out",
+    "DMA error"
+};
+
+struct ide_driver_state {
+    int irq;
+    const uint16_t *regs;
+
+    event_t completion;
+
+    int type[2];
+    struct {
+        int sectors;
+        int sector_size;
+    } drive[2];
+};
+
+static const uint16_t ide_device_regs[][IDE_REG_NUM] = {
+    { 0x01F0, 0x01F1, 0x01F2, 0x01F3, 0x01F4, 0x01F5, 0x01F6, 0x01F7, 0x03F6 },
+    { 0x0170, 0x0171, 0x0172, 0x0173, 0x0174, 0x0175, 0x0176, 0x0177, 0x0376 },
+};
+
+static const int ide_device_irqs[] = {
+    INT_IDE0,
+    INT_IDE1,
+};
+
+static status_t ide_init(struct device *dev);
+
+static enum handler_return ide_irq_handler(void *arg);
+
+static status_t ide_init(struct device *dev);
+static ssize_t ide_get_block_size(struct device *dev);
+static ssize_t ide_get_block_count(struct device *dev);
+static ssize_t ide_write(struct device *dev, off_t offset, const void *buf, size_t count);
+static ssize_t ide_read(struct device *dev, off_t offset, void *buf, size_t count);
+
+static struct block_ops the_ops = {
+    .std = {
+        .init = ide_init,
+    },
+    .get_block_size = ide_get_block_size,
+    .get_block_count = ide_get_block_count,
+    .write = ide_write,
+    .read = ide_read,
+};
+
+DRIVER_EXPORT(ide, &the_ops.std);
+
+static uint8_t ide_read_reg8(struct device *dev, int index);
+static uint16_t ide_read_reg16(struct device *dev, int index);
+static uint32_t ide_read_reg32(struct device *dev, int index);
+
+static void ide_write_reg8(struct device *dev, int index, uint8_t value);
+static void ide_write_reg16(struct device *dev, int index, uint16_t value);
+static void ide_write_reg32(struct device *dev, int index, uint32_t value);
+
+static void ide_read_reg8_array(struct device *dev, int index, void *buf, size_t count);
+static void ide_read_reg16_array(struct device *dev, int index, void *buf, size_t count);
+static void ide_read_reg32_array(struct device *dev, int index, void *buf, size_t count);
+
+static void ide_write_reg8_array(struct device *dev, int index, const void *buf, size_t count);
+static void ide_write_reg16_array(struct device *dev, int index, const void *buf, size_t count);
+static void ide_write_reg32_array(struct device *dev, int index, const void *buf, size_t count);
+
+static void ide_device_select(struct device *dev, int index);
+static void ide_device_reset(struct device *dev);
+static void ide_delay_400ns(struct device *dev);
+static int ide_poll_status(struct device *dev, uint8_t on_mask, uint8_t off_mask);
+static int ide_eval_error(struct device *dev);
+static void ide_detect_drives(struct device *dev);
+static int ide_wait_for_completion(struct device *dev);
+static int ide_detect_ata(struct device *dev, int index);
+static void ide_lba_setup(struct device *dev, uint32_t addr, int index);
+
+static status_t ide_init(struct device *dev)
+{
+    pci_location_t loc;
+    pci_config_t pci_config;
+    status_t res = NO_ERROR;
+    uint32_t i;
+    int err;
+
+    if (!dev)
+        return ERR_INVALID_ARGS;
+
+    if (!dev->config)
+        return ERR_NOT_CONFIGURED;
+
+    __UNUSED const struct platform_ide_config *config = dev->config;
+
+    err = pci_find_pci_class_code(&loc, 0x010180, 0);
+    if (err != _PCI_SUCCESSFUL) {
+        LTRACEF("Failed to find IDE device\n");
+        res = ERR_NOT_FOUND;
+    }
+
+    LTRACEF("Found IDE device at %02x:%02x\n", loc.bus, loc.dev_fn);
+
+    for (i=0; i < sizeof(pci_config) / sizeof(uint32_t); i++) {
+        uint32_t reg = sizeof(uint32_t) * i;
+
+        err = pci_read_config_word(&loc, reg, ((uint32_t *) &pci_config) + i);
+        if (err != _PCI_SUCCESSFUL) {
+            LTRACEF("Failed to read config reg %d: 0x%02x\n", reg, err);
+            res = ERR_NOT_CONFIGURED;
+            goto done;
+        }
+    }
+
+    for (i=0; i < 6; i++) {
+        LTRACEF("BAR[%d]: 0x%08x\n", i, pci_config.base_addresses[i]);
+    }
+
+    struct ide_driver_state *state = malloc(sizeof(struct ide_driver_state));
+    if (!state) {
+        res = ERR_NO_MEMORY;
+        goto done;
+    }
+    dev->state = state;
+
+    /* TODO: select io regs and irq based on device index */
+    state->irq = ide_device_irqs[0];
+    state->regs = ide_device_regs[0];
+    state->type[0] = state->type[1] = TYPE_NONE;
+
+    event_init(&state->completion, false, EVENT_FLAG_AUTOUNSIGNAL);
+
+    register_int_handler(state->irq, ide_irq_handler, dev);
+    unmask_interrupt(state->irq);
+
+    /* enable interrupts */
+    ide_write_reg8(dev, IDE_REG_DEVICE_CONTROL, 0);
+
+    /* detect drives */
+    ide_detect_drives(dev);
+
+done:
+    return res;
+}
+
+static enum handler_return ide_irq_handler(void *arg)
+{
+    struct device *dev = arg;
+    struct ide_driver_state *state = dev->state;
+    uint8_t val;
+
+    val = ide_read_reg8(dev, IDE_REG_STATUS);
+
+    if ((val & IDE_DRV_ERR) == 0) {
+        event_signal(&state->completion, false);
+
+        return INT_RESCHEDULE;
+    } else {
+        return INT_NO_RESCHEDULE;
+    }
+}
+
+static ssize_t ide_get_block_size(struct device *dev)
+{
+    DEBUG_ASSERT(dev);
+    DEBUG_ASSERT(dev->state);
+
+    struct ide_driver_state *state = dev->state;
+    return state->drive[0].sector_size;
+}
+
+static ssize_t ide_get_block_count(struct device *dev)
+{
+    DEBUG_ASSERT(dev);
+    DEBUG_ASSERT(dev->state);
+
+    struct ide_driver_state *state = dev->state;
+    return state->drive[0].sectors;
+}
+
+static ssize_t ide_write(struct device *dev, off_t offset, const void *buf, size_t count)
+{
+    DEBUG_ASSERT(dev);
+    DEBUG_ASSERT(dev->state);
+
+    __UNUSED struct ide_driver_state *state = dev->state;
+
+    size_t sectors, do_sectors, i;
+    const uint16_t *ubuf = buf;
+    int index = 0; // hard code drive for now
+    ssize_t ret = 0;
+    int err;
+
+    ide_device_select(dev, index);
+    ide_delay_400ns(dev);
+
+    err = ide_poll_status(dev, 0, IDE_CTRL_BSY | IDE_DRV_DRQ);
+    if (err) {
+        LTRACEF("Error while waiting for controller: %s\n", ide_error_str[err]);
+        ret = ERR_GENERIC;
+        goto done;
+    }
+
+    sectors = count;
+
+    while (sectors > 0) {
+        do_sectors = sectors;
+
+        if (do_sectors > 256)
+            do_sectors = 256;
+
+        err = ide_poll_status(dev, 0, IDE_CTRL_BSY);
+        if (err) {
+            LTRACEF("Error while waiting for controller: %s\n", ide_error_str[err]);
+            ret = ERR_GENERIC;
+            goto done;
+        }
+
+        ide_lba_setup(dev, offset, index);
+
+        if (do_sectors == 256)
+            ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0);
+        else
+            ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, do_sectors);
+
+        err = ide_poll_status(dev, IDE_DRV_RDY, 0);
+        if (err) {
+            LTRACEF("Error while waiting for controller: %s\n", ide_error_str[err]);
+            ret = ERR_GENERIC;
+            goto done;
+        }
+
+        ide_write_reg8(dev, IDE_REG_COMMAND, ATA_WRITEMULT_RET);
+        ide_delay_400ns(dev);
+
+        for (i=0; i < do_sectors; i++) {
+            err = ide_poll_status(dev, IDE_DRV_DRQ, 0);
+            if (err) {
+                LTRACEF("Error while waiting for drive: %s\n", ide_error_str[err]);
+                ret = ERR_GENERIC;
+                goto done;
+            }
+
+            ide_write_reg16_array(dev, IDE_REG_DATA, ubuf, 256);
+
+            ubuf += 256;
+        }
+
+        err = ide_wait_for_completion(dev);
+        if (err) {
+            LTRACEF("Error waiting for completion: %s\n", ide_error_str[err]);
+            ret = ERR_TIMED_OUT;
+            goto done;
+        }
+
+        sectors -= do_sectors;
+        offset += do_sectors;
+    }
+
+    ret = count;
+
+done:
+    return ret;
+}
+
+static ssize_t ide_read(struct device *dev, off_t offset, void *buf, size_t count)
+{
+    DEBUG_ASSERT(dev);
+    DEBUG_ASSERT(dev->state);
+
+    __UNUSED struct ide_driver_state *state = dev->state;
+
+    size_t sectors, do_sectors, i;
+    uint16_t *ubuf = buf;
+    int index = 0; // hard code drive for now
+    ssize_t ret = 0;
+    int err;
+
+    ide_device_select(dev, index);
+    ide_delay_400ns(dev);
+
+    err = ide_poll_status(dev, 0, IDE_CTRL_BSY | IDE_DRV_DRQ);
+    if (err) {
+        LTRACEF("Error while waiting for controller: %s\n", ide_error_str[err]);
+        ret = ERR_GENERIC;
+        goto done;
+    }
+
+    sectors = count;
+
+    while (sectors > 0) {
+        do_sectors = sectors;
+
+        if (do_sectors > 256)
+            do_sectors = 256;
+
+        err = ide_poll_status(dev, 0, IDE_CTRL_BSY);
+        if (err) {
+            LTRACEF("Error while waiting for controller: %s\n", ide_error_str[err]);
+            ret = ERR_GENERIC;
+            goto done;
+        }
+
+        ide_lba_setup(dev, offset, index);
+
+        if (do_sectors == 256)
+            ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0);
+        else
+            ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, do_sectors);
+
+        err = ide_poll_status(dev, IDE_DRV_RDY, 0);
+        if (err) {
+            LTRACEF("Error while waiting for controller: %s\n", ide_error_str[err]);
+            ret = ERR_GENERIC;
+            goto done;
+        }
+
+        ide_write_reg8(dev, IDE_REG_COMMAND, ATA_READMULT_RET);
+        ide_delay_400ns(dev);
+
+        for (i=0; i < do_sectors; i++) {
+            err = ide_poll_status(dev, IDE_DRV_DRQ, 0);
+            if (err) {
+                LTRACEF("Error while waiting for drive: %s\n", ide_error_str[err]);
+                ret = ERR_GENERIC;
+                goto done;
+            }
+
+            ide_read_reg16_array(dev, IDE_REG_DATA, ubuf, 256);
+
+            ubuf += 256;
+        }
+
+        err = ide_wait_for_completion(dev);
+        if (err) {
+            LTRACEF("Error waiting for completion: %s\n", ide_error_str[err]);
+            ret = ERR_TIMED_OUT;
+            goto done;
+        }
+
+        sectors -= do_sectors;
+        offset += do_sectors;
+    }
+
+    ret = count;
+
+done:
+    return ret;
+}
+
+static uint8_t ide_read_reg8(struct device *dev, int index)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    return inp(state->regs[index]);
+}
+
+static uint16_t ide_read_reg16(struct device *dev, int index)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    return inpw(state->regs[index]);
+}
+
+static uint32_t ide_read_reg32(struct device *dev, int index)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    return inpd(state->regs[index]);
+}
+
+static void ide_read_reg8_array(struct device *dev, int index, void *buf, size_t count)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    inprep(state->regs[index], (uint8_t *) buf, count);
+}
+
+static void ide_read_reg16_array(struct device *dev, int index, void *buf, size_t count)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    inpwrep(state->regs[index], (uint16_t *) buf, count);
+}
+
+static void ide_read_reg32_array(struct device *dev, int index, void *buf, size_t count)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    inpdrep(state->regs[index], (uint32_t *) buf, count);
+}
+
+static void ide_write_reg8_array(struct device *dev, int index, const void *buf, size_t count)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    outprep(state->regs[index], (uint8_t *) buf, count);
+}
+
+static void ide_write_reg16_array(struct device *dev, int index, const void *buf, size_t count)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    outpwrep(state->regs[index], (uint16_t *) buf, count);
+}
+
+static void ide_write_reg32_array(struct device *dev, int index, const void *buf, size_t count)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    outpdrep(state->regs[index], (uint32_t *) buf, count);
+}
+
+static void ide_write_reg8(struct device *dev, int index, uint8_t value)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    outp(state->regs[index], value);
+}
+
+static void ide_write_reg16(struct device *dev, int index, uint16_t value)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    outpw(state->regs[index], value);
+}
+
+static void ide_write_reg32(struct device *dev, int index, uint32_t value)
+{
+    DEBUG_ASSERT(index >= 0 && index < IDE_REG_NUM);
+
+    struct ide_driver_state *state = dev->state;
+
+    outpd(state->regs[index], value);
+}
+
+static void ide_device_select(struct device *dev, int index)
+{
+    ide_write_reg8(dev, IDE_REG_DRIVE_HEAD, (index & 1) << 4);
+}
+
+static void ide_delay_400ns(struct device *dev)
+{
+    ide_read_reg8(dev, IDE_REG_ALT_STATUS);
+    ide_read_reg8(dev, IDE_REG_ALT_STATUS);
+    ide_read_reg8(dev, IDE_REG_ALT_STATUS);
+    ide_read_reg8(dev, IDE_REG_ALT_STATUS);
+}
+
+static void ide_device_reset(struct device *dev)
+{
+    struct ide_driver_state *state = dev->state;
+
+    lk_time_t start;
+    uint8_t sect_cnt, sect_num;
+    int err;
+
+    ide_device_select(dev, 0);
+    ide_delay_400ns(dev);
+
+    // set bit 2 for at least 4.8us
+    ide_write_reg8(dev, IDE_REG_DEVICE_CONTROL, 1<<2);
+
+    // delay 5us
+    spin(5);
+
+    ide_write_reg8(dev, IDE_REG_DEVICE_CONTROL, 0x00);
+
+    err = ide_poll_status(dev, 0, IDE_CTRL_BSY);
+    if (err) {
+        LTRACEF("Failed while waiting for controller to be ready: %s\n", ide_error_str[err]);
+        return;
+    }
+
+    // make sure the slave is ready if present
+    if (state->type[1] != TYPE_NONE) {
+        ide_device_select(dev, 1);
+        ide_delay_400ns(dev);
+
+        start = current_time();
+
+        do {
+            sect_cnt = ide_read_reg8(dev, IDE_REG_SECTOR_COUNT);
+            sect_num = ide_read_reg8(dev, IDE_REG_SECTOR_NUM);
+
+            if (sect_cnt == 1 && sect_num == 1) {
+                err = ide_poll_status(dev, 0, IDE_CTRL_BSY);
+                if (err) {
+                    LTRACEF("Failed while waiting for slave ready: %s\n", ide_error_str[err]);
+                    return;
+                }
+
+                break;
+            }
+        } while (TIME_LTE(current_time(), start + 20000));
+
+        err = ide_read_reg8(dev, IDE_REG_ALT_STATUS);
+        if (err & IDE_DRV_ERR) {
+            err = ide_eval_error(dev);
+            LTRACEF("Failed while resetting controller: %s\n", ide_error_str[err]);
+            return;
+        }
+    }
+}
+
+static int ide_eval_error(struct device *dev)
+{
+    int err = 0;
+    uint8_t data = 0;
+
+    data = ide_read_reg8(dev, IDE_REG_ERROR);
+
+    if (data & 0x01) {
+        err = IDE_ADDRESSMARK;
+    } else if (data & 0x02) {
+        err = IDE_CYLINDER0;
+    } else if (data & 0x04) {
+        err = IDE_INVALIDCOMMAND;
+    } else if (data & 0x08) {
+        err = IDE_MEDIAREQ;
+    } else if (data & 0x10) {
+        err = IDE_SECTNOTFOUND;
+    } else if (data & 0x20) {
+        err = IDE_MEDIACHANGED;
+    } else if (data & 0x40) {
+        err = IDE_BADDATA;
+    } else if (data & 0x80) {
+        err = IDE_BADSECTOR;
+    } else {
+        err = IDE_NOERROR;
+    }
+
+    return err;
+}
+
+static int ide_poll_status(struct device *dev, uint8_t on_mask, uint8_t off_mask)
+{
+    int err;
+    uint8_t value;
+    lk_time_t start = current_time();
+
+    do {
+        value = ide_read_reg8(dev, IDE_REG_ALT_STATUS);
+
+        if (value & IDE_DRV_ERR) {
+            err = ide_eval_error(dev);
+            LTRACEF("Error while polling status: %s\n", ide_error_str[err]);
+            return err;
+        }
+
+        if ((value & on_mask) == on_mask && (value & off_mask) == 0)
+            return IDE_NOERROR;
+    } while (TIME_LTE(current_time(), start + 20000));
+
+    return IDE_TIMEOUT;
+}
+
+static void ide_detect_drives(struct device *dev)
+{
+    struct ide_driver_state *state = dev->state;
+    uint8_t sc = 0, sn = 0, st = 0, cl = 0, ch = 0;
+
+    ide_device_select(dev, 0);
+    ide_delay_400ns(dev);
+    ide_delay_400ns(dev);
+
+    ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0x55);
+    ide_write_reg8(dev, IDE_REG_SECTOR_NUM, 0xaa);
+    ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0xaa);
+    ide_write_reg8(dev, IDE_REG_SECTOR_NUM, 0x55);
+    ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0x55);
+    ide_write_reg8(dev, IDE_REG_SECTOR_NUM, 0xaa);
+
+    sc = ide_read_reg8(dev, IDE_REG_SECTOR_COUNT);
+    sn = ide_read_reg8(dev, IDE_REG_SECTOR_NUM);
+
+    if (sc == 0x55 && sn == 0xaa) {
+        state->type[0] = TYPE_UNKNOWN;
+    }
+
+    // check for device 1
+    ide_device_select(dev, 1);
+    ide_delay_400ns(dev);
+
+    ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0x55);
+    ide_write_reg8(dev, IDE_REG_SECTOR_NUM, 0xaa);
+    ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0xaa);
+    ide_write_reg8(dev, IDE_REG_SECTOR_NUM, 0x55);
+    ide_write_reg8(dev, IDE_REG_SECTOR_COUNT, 0x55);
+    ide_write_reg8(dev, IDE_REG_SECTOR_NUM, 0xaa);
+
+    sc = ide_read_reg8(dev, IDE_REG_SECTOR_COUNT);
+    sn = ide_read_reg8(dev, IDE_REG_SECTOR_NUM);
+
+    if (sc == 0x55 && sn == 0xaa) {
+        state->type[1] = TYPE_UNKNOWN;
+    }
+
+    // now the drives present should be known
+    // soft reset now
+    ide_device_select(dev, 0);
+    ide_delay_400ns(dev);
+    ide_device_reset(dev);
+
+    ide_device_select(dev, 0);
+    ide_delay_400ns(dev);
+
+    sc = ide_read_reg8(dev, IDE_REG_SECTOR_COUNT);
+    sn = ide_read_reg8(dev, IDE_REG_SECTOR_NUM);
+    if (sc == 0x01 && sn == 0x01) {
+        state->type[0] = TYPE_UNKNOWN;
+
+        st = ide_read_reg8(dev, IDE_REG_STATUS);
+        cl = ide_read_reg8(dev, IDE_REG_CYLINDER_LOW);
+        ch = ide_read_reg8(dev, IDE_REG_CYLINDER_HIGH);
+
+        // PATAPI or SATAPI respectively
+        if ((cl == 0x14 && ch == 0xeb) || (cl == 0x69 && ch == 0x96)) {
+            state->type[0] = TYPE_IDECDROM;
+        } else if (st != 0 && ((cl == 0x00 && ch == 0x00) || (cl == 0x3c && ch == 0xc3))) {
+            state->type[0] = TYPE_IDEDISK;
+        }
+    }
+
+    ide_device_select(dev, 1);
+    ide_delay_400ns(dev);
+
+    sc = ide_read_reg8(dev, IDE_REG_SECTOR_COUNT);
+    sn = ide_read_reg8(dev, IDE_REG_SECTOR_NUM);
+    if (sc == 0x01 && sn == 0x01) {
+        state->type[1] = TYPE_UNKNOWN;
+
+        st = ide_read_reg8(dev, IDE_REG_STATUS);
+        cl = ide_read_reg8(dev, IDE_REG_CYLINDER_LOW);
+        ch = ide_read_reg8(dev, IDE_REG_CYLINDER_HIGH);
+
+        // PATAPI or SATAPI respectively
+        if ((cl == 0x14 && ch == 0xeb) || (cl == 0x69 && ch == 0x96)) {
+            state->type[1] = TYPE_IDECDROM;
+        } else if (st != 0 && ((cl == 0x00 && ch == 0x00) || (cl == 0x3c && ch == 0xc3))) {
+            state->type[1] = TYPE_IDEDISK;
+        }
+    }
+
+    LTRACEF("Detected drive 0: %s\n", ide_type_str[state->type[0]]);
+    LTRACEF("Detected drive 1: %s\n", ide_type_str[state->type[1]]);
+
+    switch (state->type[0]) {
+        case TYPE_IDEDISK:
+            ide_detect_ata(dev, 0);
+            break;
+
+        default:
+            break;
+    }
+
+    switch (state->type[1]) {
+        case TYPE_IDEDISK:
+            ide_detect_ata(dev, 1);
+            break;
+
+        default:
+            break;
+    }
+}
+
+static int ide_wait_for_completion(struct device *dev)
+{
+    struct ide_driver_state *state = dev->state;
+    status_t err;
+
+    err = event_wait_timeout(&state->completion, 20000);
+    if (err)
+        return IDE_TIMEOUT;
+
+    return IDE_NOERROR;
+}
+
+static status_t ide_detect_ata(struct device *dev, int index)
+{
+    struct ide_driver_state *state = dev->state;
+    status_t res = NO_ERROR;
+    uint8_t *info = NULL;
+    int err;
+
+    ide_device_select(dev, index);
+    ide_delay_400ns(dev);
+
+    err = ide_poll_status(dev, 0, IDE_CTRL_BSY | IDE_DRV_DRQ);
+    if (err) {
+        LTRACEF("Error while detecting drive %d: %s\n", index, ide_error_str[err]);
+        res = ERR_TIMED_OUT;
+        goto error;
+    }
+
+    ide_device_select(dev, index);
+    ide_delay_400ns(dev);
+
+    err = ide_poll_status(dev, 0, IDE_CTRL_BSY | IDE_DRV_DRQ);
+    if (err) {
+        LTRACEF("Error while detecting drive %d: %s\n", index, ide_error_str[err]);
+        res = ERR_TIMED_OUT;
+        goto error;
+    }
+
+    // try to wait for the selected drive to be ready, but don't quit if not
+    // since CD-ROMs don't seem to respond to this when they're masters
+    ide_poll_status(dev, IDE_DRV_RDY, 0);
+
+    // send the "identify device" command
+    ide_write_reg8(dev, IDE_REG_COMMAND, ATA_GETDEVINFO);
+    ide_delay_400ns(dev);
+
+    err = ide_wait_for_completion(dev);
+    if (err) {
+        LTRACEF("Error while waiting for command: %s\n", ide_error_str[err]);
+        res = ERR_TIMED_OUT;
+        goto error;
+    }
+
+    info = malloc(512);
+    if (!info) {
+        res = ERR_NO_MEMORY;
+        goto error;
+    }
+
+    LTRACEF("Found ATA hard disk on channel %d!\n", index);
+
+    ide_read_reg16_array(dev, IDE_REG_DATA, info, 256);
+
+    state->drive[index].sectors = *((uint32_t *) (info + 120));
+    state->drive[index].sector_size = 512;
+
+    LTRACEF("Disk supports %u sectors for a total of %u bytes\n", state->drive[index].sectors,
+            state->drive[index].sectors * 512);
+
+error:
+    free(info);
+    return res;
+}
+
+static void ide_lba_setup(struct device *dev, uint32_t addr, int drive)
+{
+    ide_write_reg8(dev, IDE_REG_DRIVE_HEAD, 0xe0 | ((drive & 0x00000001) << 4) | ((addr >> 24) & 0xf));
+    ide_write_reg8(dev, IDE_REG_CYLINDER_LOW, (addr >> 8) & 0xff);
+    ide_write_reg8(dev, IDE_REG_CYLINDER_HIGH, (addr >> 16) & 0xff);
+    ide_write_reg8(dev, IDE_REG_SECTOR_NUM, addr & 0xff);
+    ide_write_reg8(dev, IDE_REG_PRECOMP, 0xff);
+}
+