summaryrefslogtreecommitdiffstats
path: root/qemu/roms/openbios/drivers/ide.c
diff options
context:
space:
mode:
authorYang Zhang <yang.z.zhang@intel.com>2015-08-28 09:58:54 +0800
committerYang Zhang <yang.z.zhang@intel.com>2015-09-01 12:44:00 +0800
commite44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch)
tree66b09f592c55df2878107a468a91d21506104d3f /qemu/roms/openbios/drivers/ide.c
parent9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff)
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/roms/openbios/drivers/ide.c')
-rw-r--r--qemu/roms/openbios/drivers/ide.c1685
1 files changed, 1685 insertions, 0 deletions
diff --git a/qemu/roms/openbios/drivers/ide.c b/qemu/roms/openbios/drivers/ide.c
new file mode 100644
index 000000000..327c64a40
--- /dev/null
+++ b/qemu/roms/openbios/drivers/ide.c
@@ -0,0 +1,1685 @@
+/*
+ * OpenBIOS polled ide driver
+ *
+ * Copyright (C) 2004 Jens Axboe <axboe@suse.de>
+ * Copyright (C) 2005 Stefan Reinauer <stepan@openbios.org>
+ *
+ * Credit goes to Hale Landis for his excellent ata demo software
+ * OF node handling and some fixes by Stefan Reinauer
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2
+ *
+ */
+
+#include "config.h"
+#include "libopenbios/bindings.h"
+#include "kernel/kernel.h"
+#include "libc/byteorder.h"
+#include "libc/vsprintf.h"
+
+#include "drivers/drivers.h"
+#include "ide.h"
+#include "hdreg.h"
+#include "timer.h"
+
+#ifdef CONFIG_DEBUG_IDE
+#define IDE_DPRINTF(fmt, args...) \
+do { printk("IDE - %s: " fmt, __func__ , ##args); } while (0)
+#else
+#define IDE_DPRINTF(fmt, args...) do { } while (0)
+#endif
+
+/* DECLARE data structures for the nodes. */
+DECLARE_UNNAMED_NODE( ob_ide, INSTALL_OPEN, sizeof(struct ide_drive*) );
+DECLARE_UNNAMED_NODE( ob_ide_ctrl, INSTALL_OPEN, sizeof(int));
+
+/*
+ * define to 2 for the standard 2 channels only
+ */
+#ifndef CONFIG_IDE_NUM_CHANNELS
+#define IDE_NUM_CHANNELS 4
+#else
+#define IDE_NUM_CHANNELS CONFIG_IDE_NUM_CHANNELS
+#endif
+#define IDE_MAX_CHANNELS 4
+
+#ifndef CONFIG_IDE_FIRST_UNIT
+#define FIRST_UNIT 0
+#else
+#define FIRST_UNIT CONFIG_IDE_FIRST_UNIT
+#endif
+
+#ifndef CONFIG_IDE_DEV_TYPE
+#define DEV_TYPE "ide"
+#else
+#define DEV_TYPE CONFIG_IDE_DEV_TYPE
+#endif
+
+#ifndef CONFIG_IDE_DEV_NAME
+#define DEV_NAME "ide%d"
+#else
+#define DEV_NAME CONFIG_IDE_DEV_NAME
+#endif
+
+static int current_channel = FIRST_UNIT;
+
+static struct ide_channel *channels = NULL;
+
+static inline void ide_add_channel(struct ide_channel *chan)
+{
+ chan->next = channels;
+ channels = chan;
+}
+
+static struct ide_channel *ide_seek_channel(const char *name)
+{
+ struct ide_channel *current;
+
+ current = channels;
+ while (current) {
+ if (!strcmp(current->name, name))
+ return current;
+ current = current->next;
+ }
+ return NULL;
+}
+
+/*
+ * don't be pedantic
+ */
+#undef ATA_PEDANTIC
+
+static void dump_drive(struct ide_drive *drive)
+{
+#ifdef CONFIG_DEBUG_IDE
+ printk("IDE DRIVE @%lx:\n", (unsigned long)drive);
+ printk("unit: %d\n",drive->unit);
+ printk("present: %d\n",drive->present);
+ printk("type: %d\n",drive->type);
+ printk("media: %d\n",drive->media);
+ printk("model: %s\n",drive->model);
+ printk("nr: %d\n",drive->nr);
+ printk("cyl: %d\n",drive->cyl);
+ printk("head: %d\n",drive->head);
+ printk("sect: %d\n",drive->sect);
+ printk("bs: %d\n",drive->bs);
+#endif
+}
+
+/*
+ * old style io port operations
+ */
+static unsigned char
+ob_ide_inb(struct ide_channel *chan, unsigned int port)
+{
+ return inb(chan->io_regs[port]);
+}
+
+static void
+ob_ide_outb(struct ide_channel *chan, unsigned char data, unsigned int port)
+{
+ outb(data, chan->io_regs[port]);
+}
+
+static void
+ob_ide_insw(struct ide_channel *chan,
+ unsigned int port, unsigned char *addr, unsigned int count)
+{
+ insw(chan->io_regs[port], addr, count);
+}
+
+static void
+ob_ide_outsw(struct ide_channel *chan,
+ unsigned int port, unsigned char *addr, unsigned int count)
+{
+ outsw(chan->io_regs[port], addr, count);
+}
+
+static inline unsigned char
+ob_ide_pio_readb(struct ide_drive *drive, unsigned int offset)
+{
+ struct ide_channel *chan = drive->channel;
+
+ return chan->obide_inb(chan, offset);
+}
+
+static inline void
+ob_ide_pio_writeb(struct ide_drive *drive, unsigned int offset,
+ unsigned char data)
+{
+ struct ide_channel *chan = drive->channel;
+
+ chan->obide_outb(chan, data, offset);
+}
+
+static inline void
+ob_ide_pio_insw(struct ide_drive *drive, unsigned int offset,
+ unsigned char *addr, unsigned int len)
+{
+ struct ide_channel *chan = drive->channel;
+
+ if (len & 1) {
+ IDE_DPRINTF("%d: command not word aligned\n", drive->nr);
+ return;
+ }
+
+ chan->obide_insw(chan, offset, addr, len / 2);
+}
+
+static inline void
+ob_ide_pio_outsw(struct ide_drive *drive, unsigned int offset,
+ unsigned char *addr, unsigned int len)
+{
+ struct ide_channel *chan = drive->channel;
+
+ if (len & 1) {
+ IDE_DPRINTF("%d: command not word aligned\n", drive->nr);
+ return;
+ }
+
+ chan->obide_outsw(chan, offset, addr, len / 2);
+}
+
+static void
+ob_ide_400ns_delay(struct ide_drive *drive)
+{
+ (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS);
+ (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS);
+ (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS);
+ (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS);
+
+ udelay(1);
+}
+
+static void
+ob_ide_error(struct ide_drive *drive, unsigned char stat, const char *msg)
+{
+#ifdef CONFIG_DEBUG_IDE
+ struct ide_channel *chan = drive->channel;
+ unsigned char err;
+#endif
+
+ if (!stat)
+ stat = ob_ide_pio_readb(drive, IDEREG_STATUS);
+
+ IDE_DPRINTF("ob_ide_error drive<%d>: %s:\n", drive->nr, msg);
+ IDE_DPRINTF(" cmd=%x, stat=%x", chan->ata_cmd.command, stat);
+
+ if ((stat & (BUSY_STAT | ERR_STAT)) == ERR_STAT) {
+#ifdef CONFIG_DEBUG_IDE
+ err =
+#endif
+ ob_ide_pio_readb(drive, IDEREG_ERROR);
+ IDE_DPRINTF(", err=%x", err);
+ }
+ IDE_DPRINTF("\n");
+
+#ifdef CONFIG_DEBUG_IDE
+ /*
+ * see if sense is valid and dump that
+ */
+ if (chan->ata_cmd.command == WIN_PACKET) {
+ struct atapi_command *cmd = &chan->atapi_cmd;
+ unsigned char old_cdb = cmd->cdb[0];
+
+ if (cmd->cdb[0] == ATAPI_REQ_SENSE) {
+ old_cdb = cmd->old_cdb;
+
+ IDE_DPRINTF(" atapi opcode=%02x", old_cdb);
+ } else {
+ int i;
+
+ IDE_DPRINTF(" cdb: ");
+ for (i = 0; i < sizeof(cmd->cdb); i++)
+ IDE_DPRINTF("%02x ", cmd->cdb[i]);
+ }
+ if (cmd->sense_valid)
+ IDE_DPRINTF(", sense: %02x/%02x/%02x",
+ cmd->sense.sense_key, cmd->sense.asc,
+ cmd->sense.ascq);
+ else
+ IDE_DPRINTF(", no sense");
+ IDE_DPRINTF("\n");
+ }
+#endif
+}
+
+/*
+ * wait for 'stat' to be set. returns 1 if failed, 0 if succesful
+ */
+static int
+ob_ide_wait_stat(struct ide_drive *drive, unsigned char ok_stat,
+ unsigned char bad_stat, unsigned char *ret_stat)
+{
+ unsigned char stat;
+ int i;
+
+ ob_ide_400ns_delay(drive);
+
+ for (i = 0; i < 5000; i++) {
+ stat = ob_ide_pio_readb(drive, IDEREG_STATUS);
+ if (!(stat & BUSY_STAT))
+ break;
+
+ udelay(1000);
+ }
+
+ if (ret_stat)
+ *ret_stat = stat;
+
+ if (stat & bad_stat)
+ return 1;
+
+ if ((stat & ok_stat) || !ok_stat)
+ return 0;
+
+ return 1;
+}
+
+static int
+ob_ide_select_drive(struct ide_drive *drive)
+{
+ struct ide_channel *chan = drive->channel;
+ unsigned char control = IDEHEAD_DEV0;
+
+ if (ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL)) {
+ IDE_DPRINTF("select_drive: timed out\n");
+ return 1;
+ }
+
+ /*
+ * don't select drive if already active. Note: we always
+ * wait for BUSY clear
+ */
+ if (drive->unit == chan->selected)
+ return 0;
+
+ if (drive->unit)
+ control = IDEHEAD_DEV1;
+
+ ob_ide_pio_writeb(drive, IDEREG_CURRENT, control);
+ ob_ide_400ns_delay(drive);
+
+ if (ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL)) {
+ IDE_DPRINTF("select_drive: timed out\n");
+ return 1;
+ }
+
+ chan->selected = drive->unit;
+ return 0;
+}
+
+static void
+ob_ide_write_tasklet(struct ide_drive *drive, struct ata_command *cmd)
+{
+ ob_ide_pio_writeb(drive, IDEREG_FEATURE, cmd->task[1]);
+ ob_ide_pio_writeb(drive, IDEREG_NSECTOR, cmd->task[3]);
+ ob_ide_pio_writeb(drive, IDEREG_SECTOR, cmd->task[7]);
+ ob_ide_pio_writeb(drive, IDEREG_LCYL, cmd->task[8]);
+ ob_ide_pio_writeb(drive, IDEREG_HCYL, cmd->task[9]);
+
+ ob_ide_pio_writeb(drive, IDEREG_FEATURE, cmd->task[0]);
+ ob_ide_pio_writeb(drive, IDEREG_NSECTOR, cmd->task[2]);
+ ob_ide_pio_writeb(drive, IDEREG_SECTOR, cmd->task[4]);
+ ob_ide_pio_writeb(drive, IDEREG_LCYL, cmd->task[5]);
+ ob_ide_pio_writeb(drive, IDEREG_HCYL, cmd->task[6]);
+
+ if (drive->unit)
+ cmd->device_head |= IDEHEAD_DEV1;
+
+ ob_ide_pio_writeb(drive, IDEREG_CURRENT, cmd->device_head);
+
+ ob_ide_pio_writeb(drive, IDEREG_COMMAND, cmd->command);
+ ob_ide_400ns_delay(drive);
+}
+
+static void
+ob_ide_write_registers(struct ide_drive *drive, struct ata_command *cmd)
+{
+ /*
+ * we are _always_ polled
+ */
+ ob_ide_pio_writeb(drive, IDEREG_CONTROL, cmd->control | IDECON_NIEN);
+
+ ob_ide_pio_writeb(drive, IDEREG_FEATURE, cmd->feature);
+ ob_ide_pio_writeb(drive, IDEREG_NSECTOR, cmd->nsector);
+ ob_ide_pio_writeb(drive, IDEREG_SECTOR, cmd->sector);
+ ob_ide_pio_writeb(drive, IDEREG_LCYL, cmd->lcyl);
+ ob_ide_pio_writeb(drive, IDEREG_HCYL, cmd->hcyl);
+
+ if (drive->unit)
+ cmd->device_head |= IDEHEAD_DEV1;
+
+ ob_ide_pio_writeb(drive, IDEREG_CURRENT, cmd->device_head);
+
+ ob_ide_pio_writeb(drive, IDEREG_COMMAND, cmd->command);
+ ob_ide_400ns_delay(drive);
+}
+
+/*
+ * execute command with "pio non data" protocol
+ */
+#if 0
+static int
+ob_ide_pio_non_data(struct ide_drive *drive, struct ata_command *cmd)
+{
+ if (ob_ide_select_drive(drive))
+ return 1;
+
+ ob_ide_write_registers(drive, cmd);
+
+ if (ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL))
+ return 1;
+
+ return 0;
+}
+#endif
+
+/*
+ * execute given command with a pio data-in phase.
+ */
+static int
+ob_ide_pio_data_in(struct ide_drive *drive, struct ata_command *cmd)
+{
+ unsigned char stat;
+ unsigned int bytes, timeout;
+
+ if (ob_ide_select_drive(drive))
+ return 1;
+
+ /*
+ * ATA must set ready and seek stat, ATAPI need only clear busy
+ */
+ timeout = 0;
+ do {
+ stat = ob_ide_pio_readb(drive, IDEREG_STATUS);
+
+ if (drive->type == ide_type_ata) {
+ /*
+ * this is BIOS code, don't be too pedantic
+ */
+#ifdef ATA_PEDANTIC
+ if ((stat & (BUSY_STAT | READY_STAT | SEEK_STAT)) ==
+ (READY_STAT | SEEK_STAT))
+ break;
+#else
+ if ((stat & (BUSY_STAT | READY_STAT)) == READY_STAT)
+ break;
+#endif
+ } else {
+ if (!(stat & BUSY_STAT))
+ break;
+ }
+ ob_ide_400ns_delay(drive);
+ } while (timeout++ < 1000);
+
+ if (timeout >= 1000) {
+ ob_ide_error(drive, stat, "drive timed out");
+ cmd->stat = stat;
+ return 1;
+ }
+
+ ob_ide_write_registers(drive, cmd);
+
+ /*
+ * now read the data
+ */
+ bytes = cmd->buflen;
+ do {
+ unsigned count = cmd->buflen;
+
+ if (count > drive->bs)
+ count = drive->bs;
+
+ /* delay 100ms for ATAPI? */
+
+ /*
+ * wait for BUSY clear
+ */
+ if (ob_ide_wait_stat(drive, 0, BUSY_STAT | ERR_STAT, &stat)) {
+ ob_ide_error(drive, stat, "timed out waiting for BUSY clear");
+ cmd->stat = stat;
+ break;
+ }
+
+ /*
+ * transfer the data
+ */
+ if ((stat & (BUSY_STAT | DRQ_STAT)) == DRQ_STAT) {
+ ob_ide_pio_insw(drive, IDEREG_DATA, cmd->buffer, count);
+ cmd->bytes -= count;
+ cmd->buffer += count;
+ bytes -= count;
+
+ ob_ide_400ns_delay(drive);
+ }
+
+ if (stat & (BUSY_STAT | WRERR_STAT | ERR_STAT)) {
+ cmd->stat = stat;
+ break;
+ }
+
+ if (!(stat & DRQ_STAT)) {
+ cmd->stat = stat;
+ break;
+ }
+ } while (bytes);
+
+ if (bytes)
+ IDE_DPRINTF("bytes=%d, stat=%x\n", bytes, stat);
+
+ return bytes ? 1 : 0;
+}
+
+/*
+ * execute ata command with pio packet protocol
+ */
+static int
+ob_ide_pio_packet(struct ide_drive *drive, struct atapi_command *cmd)
+{
+ unsigned char stat, reason, lcyl, hcyl;
+ struct ata_command *acmd = &drive->channel->ata_cmd;
+ unsigned char *buffer;
+ unsigned int bytes;
+
+ if (ob_ide_select_drive(drive))
+ return 1;
+
+ if (cmd->buflen && cmd->data_direction == atapi_ddir_none)
+ IDE_DPRINTF("non-zero buflen but no data direction\n");
+
+ memset(acmd, 0, sizeof(*acmd));
+ acmd->lcyl = cmd->buflen & 0xff;
+ acmd->hcyl = (cmd->buflen >> 8) & 0xff;
+ acmd->command = WIN_PACKET;
+ ob_ide_write_registers(drive, acmd);
+
+ /*
+ * BUSY must be set, _or_ DRQ | ERR
+ */
+ stat = ob_ide_pio_readb(drive, IDEREG_ASTATUS);
+ if ((stat & BUSY_STAT) == 0) {
+ if (!(stat & (DRQ_STAT | ERR_STAT))) {
+ ob_ide_error(drive, stat, "bad stat in atapi cmd");
+ cmd->stat = stat;
+ return 1;
+ }
+ }
+
+ if (ob_ide_wait_stat(drive, 0, BUSY_STAT | ERR_STAT, &stat)) {
+ ob_ide_error(drive, stat, "timeout, ATAPI BUSY clear");
+ cmd->stat = stat;
+ return 1;
+ }
+
+ if ((stat & (BUSY_STAT | DRQ_STAT | ERR_STAT)) != DRQ_STAT) {
+ /*
+ * if command isn't request sense, then we have a problem. if
+ * we are doing a sense, ERR_STAT == CHECK_CONDITION
+ */
+ if (cmd->cdb[0] != ATAPI_REQ_SENSE) {
+ IDE_DPRINTF("odd, drive didn't want to transfer %x\n",
+ stat);
+ return 1;
+ }
+ }
+
+ /*
+ * transfer cdb
+ */
+ ob_ide_pio_outsw(drive, IDEREG_DATA, cmd->cdb,sizeof(cmd->cdb));
+ ob_ide_400ns_delay(drive);
+
+ /*
+ * ok, cdb was sent to drive, now do data transfer (if any)
+ */
+ bytes = cmd->buflen;
+ buffer = cmd->buffer;
+ do {
+ unsigned int bc;
+
+ if (ob_ide_wait_stat(drive, 0, BUSY_STAT | ERR_STAT, &stat)) {
+ ob_ide_error(drive, stat, "busy not clear after cdb");
+ cmd->stat = stat;
+ break;
+ }
+
+ /*
+ * transfer complete!
+ */
+ if ((stat & (BUSY_STAT | DRQ_STAT)) == 0)
+ break;
+
+ if ((stat & (BUSY_STAT | DRQ_STAT)) != DRQ_STAT)
+ break;
+
+ reason = ob_ide_pio_readb(drive, IDEREG_NSECTOR);
+ lcyl = ob_ide_pio_readb(drive, IDEREG_LCYL);
+ hcyl = ob_ide_pio_readb(drive, IDEREG_HCYL);
+
+ /*
+ * check if the drive wants to transfer data in the same
+ * direction as we do...
+ */
+ if ((reason & IREASON_CD) && cmd->data_direction != atapi_ddir_read) {
+ ob_ide_error(drive, stat, "atapi, bad transfer ddir");
+ break;
+ }
+
+ bc = (hcyl << 8) | lcyl;
+ if (!bc)
+ break;
+
+ if (bc > bytes)
+ bc = bytes;
+
+ if (cmd->data_direction == atapi_ddir_read)
+ ob_ide_pio_insw(drive, IDEREG_DATA, buffer, bc);
+ else
+ ob_ide_pio_outsw(drive, IDEREG_DATA, buffer, bc);
+
+ bytes -= bc;
+ buffer += bc;
+
+ ob_ide_400ns_delay(drive);
+ } while (bytes);
+
+ if (cmd->data_direction != atapi_ddir_none)
+ (void) ob_ide_wait_stat(drive, 0, BUSY_STAT, &stat);
+
+ if (bytes)
+ IDE_DPRINTF("cdb failed, bytes=%d, stat=%x\n", bytes, stat);
+
+ return (stat & ERR_STAT) || bytes;
+}
+
+/*
+ * execute a packet command, with retries if appropriate
+ */
+static int
+ob_ide_atapi_packet(struct ide_drive *drive, struct atapi_command *cmd)
+{
+ int retries = 5, ret;
+
+ if (drive->type != ide_type_atapi)
+ return 1;
+ if (cmd->buflen > 0xffff)
+ return 1;
+
+ /*
+ * retry loop
+ */
+ do {
+ ret = ob_ide_pio_packet(drive, cmd);
+ if (!ret)
+ break;
+
+ /*
+ * request sense failed, bummer
+ */
+ if (cmd->cdb[0] == ATAPI_REQ_SENSE)
+ break;
+
+ if (ob_ide_atapi_request_sense(drive))
+ break;
+
+ /*
+ * we know sense is valid. retry if the drive isn't ready,
+ * otherwise don't bother.
+ */
+ if (cmd->sense.sense_key != ATAPI_SENSE_NOT_READY)
+ break;
+ /*
+ * ... except 'medium not present'
+ */
+ if (cmd->sense.asc == 0x3a)
+ break;
+
+ udelay(1000000);
+ } while (retries--);
+
+ if (ret)
+ ob_ide_error(drive, 0, "atapi command");
+
+ return ret;
+}
+
+static int
+ob_ide_atapi_request_sense(struct ide_drive *drive)
+{
+ struct atapi_command *cmd = &drive->channel->atapi_cmd;
+ unsigned char old_cdb;
+
+ /*
+ * save old cdb for debug error
+ */
+ old_cdb = cmd->cdb[0];
+
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->cdb[0] = ATAPI_REQ_SENSE;
+ cmd->cdb[4] = 18;
+ cmd->buffer = (unsigned char *) &cmd->sense;
+ cmd->buflen = 18;
+ cmd->data_direction = atapi_ddir_read;
+ cmd->old_cdb = old_cdb;
+
+ if (ob_ide_atapi_packet(drive, cmd))
+ return 1;
+
+ cmd->sense_valid = 1;
+ return 0;
+}
+
+/*
+ * make sure drive is ready and media loaded
+ */
+static int
+ob_ide_atapi_drive_ready(struct ide_drive *drive)
+{
+ struct atapi_command *cmd = &drive->channel->atapi_cmd;
+ struct atapi_capacity cap;
+
+ IDE_DPRINTF("ob_ide_atapi_drive_ready\n");
+
+ /*
+ * Test Unit Ready is like a ping
+ */
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->cdb[0] = ATAPI_TUR;
+
+ if (ob_ide_atapi_packet(drive, cmd)) {
+ IDE_DPRINTF("%d: TUR failed\n", drive->nr);
+ return 1;
+ }
+
+ /*
+ * don't force load of tray (bit 2 in byte 4 of cdb), it's
+ * annoying and we don't want to deal with errors from drives
+ * that cannot do it
+ */
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->cdb[0] = ATAPI_START_STOP_UNIT;
+ cmd->cdb[4] = 0x01;
+
+ if (ob_ide_atapi_packet(drive, cmd)) {
+ IDE_DPRINTF("%d: START_STOP unit failed\n", drive->nr);
+ return 1;
+ }
+
+ /*
+ * finally, get capacity and block size
+ */
+ memset(cmd, 0, sizeof(*cmd));
+ memset(&cap, 0, sizeof(cap));
+
+ cmd->cdb[0] = ATAPI_READ_CAPACITY;
+ cmd->buffer = (unsigned char *) &cap;
+ cmd->buflen = sizeof(cap);
+ cmd->data_direction = atapi_ddir_read;
+
+ if (ob_ide_atapi_packet(drive, cmd)) {
+ drive->sectors = 0x1fffff;
+ drive->bs = 2048;
+ return 1;
+ }
+
+ drive->sectors = __be32_to_cpu(cap.lba) + 1;
+ drive->bs = __be32_to_cpu(cap.block_size);
+ return 0;
+}
+
+/*
+ * read from an atapi device, using READ_10
+ */
+static int
+ob_ide_read_atapi(struct ide_drive *drive, unsigned long long block,
+ unsigned char *buf, unsigned int sectors)
+{
+ struct atapi_command *cmd = &drive->channel->atapi_cmd;
+
+ if (ob_ide_atapi_drive_ready(drive))
+ return 1;
+
+ memset(cmd, 0, sizeof(*cmd));
+
+ /*
+ * READ_10 should work on generally any atapi device
+ */
+ cmd->cdb[0] = ATAPI_READ_10;
+ cmd->cdb[2] = (block >> 24) & 0xff;
+ cmd->cdb[3] = (block >> 16) & 0xff;
+ cmd->cdb[4] = (block >> 8) & 0xff;
+ cmd->cdb[5] = block & 0xff;
+ cmd->cdb[7] = (sectors >> 8) & 0xff;
+ cmd->cdb[8] = sectors & 0xff;
+
+ cmd->buffer = buf;
+ cmd->buflen = sectors * 2048;
+ cmd->data_direction = atapi_ddir_read;
+
+ return ob_ide_atapi_packet(drive, cmd);
+}
+
+static int
+ob_ide_read_ata_chs(struct ide_drive *drive, unsigned long long block,
+ unsigned char *buf, unsigned int sectors)
+{
+ struct ata_command *cmd = &drive->channel->ata_cmd;
+ unsigned int track = (block / drive->sect);
+ unsigned int sect = (block % drive->sect) + 1;
+ unsigned int head = (track % drive->head);
+ unsigned int cyl = (track / drive->head);
+
+ /*
+ * fill in chs command to read from disk at given location
+ */
+ cmd->buffer = buf;
+ cmd->buflen = sectors * 512;
+
+ cmd->nsector = sectors & 0xff;
+ cmd->sector = sect;
+ cmd->lcyl = cyl;
+ cmd->hcyl = cyl >> 8;
+ cmd->device_head = head;
+
+ cmd->command = WIN_READ;
+
+ return ob_ide_pio_data_in(drive, cmd);
+}
+
+static int
+ob_ide_read_ata_lba28(struct ide_drive *drive, unsigned long long block,
+ unsigned char *buf, unsigned int sectors)
+{
+ struct ata_command *cmd = &drive->channel->ata_cmd;
+
+ memset(cmd, 0, sizeof(*cmd));
+
+ /*
+ * fill in 28-bit lba command to read from disk at given location
+ */
+ cmd->buffer = buf;
+ cmd->buflen = sectors * 512;
+
+ cmd->nsector = sectors;
+ cmd->sector = block;
+ cmd->lcyl = block >>= 8;
+ cmd->hcyl = block >>= 8;
+ cmd->device_head = ((block >> 8) & 0x0f);
+ cmd->device_head |= (1 << 6);
+
+ cmd->command = WIN_READ;
+
+ return ob_ide_pio_data_in(drive, cmd);
+}
+
+static int
+ob_ide_read_ata_lba48(struct ide_drive *drive, unsigned long long block,
+ unsigned char *buf, unsigned int sectors)
+{
+ struct ata_command *cmd = &drive->channel->ata_cmd;
+
+ memset(cmd, 0, sizeof(*cmd));
+
+ cmd->buffer = buf;
+ cmd->buflen = sectors * 512;
+
+ /*
+ * we are using tasklet addressing here
+ */
+ cmd->task[2] = sectors;
+ cmd->task[3] = sectors >> 8;
+ cmd->task[4] = block;
+ cmd->task[5] = block >> 8;
+ cmd->task[6] = block >> 16;
+ cmd->task[7] = block >> 24;
+ cmd->task[8] = (u64) block >> 32;
+ cmd->task[9] = (u64) block >> 40;
+
+ cmd->command = WIN_READ_EXT;
+
+ ob_ide_write_tasklet(drive, cmd);
+
+ return ob_ide_pio_data_in(drive, cmd);
+}
+/*
+ * read 'sectors' sectors from ata device
+ */
+static int
+ob_ide_read_ata(struct ide_drive *drive, unsigned long long block,
+ unsigned char *buf, unsigned int sectors)
+{
+ unsigned long long end_block = block + sectors;
+ const int need_lba48 = (end_block > (1ULL << 28)) || (sectors > 255);
+
+ if (end_block > drive->sectors)
+ return 1;
+ if (need_lba48 && drive->addressing != ide_lba48)
+ return 1;
+
+ /*
+ * use lba48 if we have to, otherwise use the faster lba28
+ */
+ if (need_lba48)
+ return ob_ide_read_ata_lba48(drive, block, buf, sectors);
+ else if (drive->addressing != ide_chs)
+ return ob_ide_read_ata_lba28(drive, block, buf, sectors);
+
+ return ob_ide_read_ata_chs(drive, block, buf, sectors);
+}
+
+static int
+ob_ide_read_sectors(struct ide_drive *drive, unsigned long long block,
+ unsigned char *buf, unsigned int sectors)
+{
+ if (!sectors)
+ return 1;
+ if (block + sectors > drive->sectors)
+ return 1;
+
+ IDE_DPRINTF("ob_ide_read_sectors: block=%lu sectors=%u\n",
+ (unsigned long) block, sectors);
+
+ if (drive->type == ide_type_ata)
+ return ob_ide_read_ata(drive, block, buf, sectors);
+ else
+ return ob_ide_read_atapi(drive, block, buf, sectors);
+}
+
+/*
+ * byte swap the string if necessay, and strip leading/trailing blanks
+ */
+static void
+ob_ide_fixup_string(unsigned char *s, unsigned int len)
+{
+ unsigned char *p = s, *end = &s[len & ~1];
+
+ /*
+ * if big endian arch, byte swap the string
+ */
+#ifdef CONFIG_BIG_ENDIAN
+ for (p = end ; p != s;) {
+ unsigned short *pp = (unsigned short *) (p -= 2);
+ *pp = __le16_to_cpu(*pp);
+ }
+#endif
+
+ while (s != end && *s == ' ')
+ ++s;
+ while (s != end && *s)
+ if (*s++ != ' ' || (s != end && *s && *s != ' '))
+ *p++ = *(s-1);
+ while (p != end)
+ *p++ = '\0';
+}
+
+/*
+ * it's big endian, we need to swap (if on little endian) the items we use
+ */
+static int
+ob_ide_fixup_id(struct hd_driveid *id)
+{
+ ob_ide_fixup_string(id->model, 40);
+ id->config = __le16_to_cpu(id->config);
+ id->lba_capacity = __le32_to_cpu(id->lba_capacity);
+ id->cyls = __le16_to_cpu(id->cyls);
+ id->heads = __le16_to_cpu(id->heads);
+ id->sectors = __le16_to_cpu(id->sectors);
+ id->command_set_2 = __le16_to_cpu(id->command_set_2);
+ id->cfs_enable_2 = __le16_to_cpu(id->cfs_enable_2);
+
+ return 0;
+}
+
+static int
+ob_ide_identify_drive(struct ide_drive *drive)
+{
+ struct ata_command *cmd = &drive->channel->ata_cmd;
+ struct hd_driveid id;
+
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->buffer = (unsigned char *) &id;
+ cmd->buflen = 512;
+
+ if (drive->type == ide_type_ata)
+ cmd->command = WIN_IDENTIFY;
+ else if (drive->type == ide_type_atapi)
+ cmd->command = WIN_IDENTIFY_PACKET;
+ else {
+ IDE_DPRINTF("%s: called with bad device type %d\n",
+ __FUNCTION__, drive->type);
+ return 1;
+ }
+
+ if (ob_ide_pio_data_in(drive, cmd))
+ return 1;
+
+ ob_ide_fixup_id(&id);
+
+ if (drive->type == ide_type_atapi) {
+ drive->media = (id.config >> 8) & 0x1f;
+ drive->sectors = 0x7fffffff;
+ drive->bs = 2048;
+ drive->max_sectors = 31;
+ } else {
+ drive->media = ide_media_disk;
+ drive->sectors = id.lba_capacity;
+ drive->bs = 512;
+ drive->max_sectors = 255;
+
+#ifdef CONFIG_IDE_LBA48
+ if ((id.command_set_2 & 0x0400) && (id.cfs_enable_2 & 0x0400)) {
+ drive->addressing = ide_lba48;
+ drive->max_sectors = 65535;
+ } else
+#endif
+ if (id.capability & 2)
+ drive->addressing = ide_lba28;
+ else {
+ drive->addressing = ide_chs;
+ }
+
+ /* only set these in chs mode? */
+ drive->cyl = id.cyls;
+ drive->head = id.heads;
+ drive->sect = id.sectors;
+ }
+
+ strncpy(drive->model, (char*)id.model, sizeof(id.model));
+ drive->model[40] = '\0';
+ return 0;
+}
+
+/*
+ * identify type of devices on channel. must have already been probed.
+ */
+static void
+ob_ide_identify_drives(struct ide_channel *chan)
+{
+ struct ide_drive *drive;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ drive = &chan->drives[i];
+
+ if (!drive->present)
+ continue;
+
+ ob_ide_identify_drive(drive);
+ }
+}
+
+/*
+ * software reset (ATA-4, section 8.3)
+ */
+static void
+ob_ide_software_reset(struct ide_drive *drive)
+{
+ struct ide_channel *chan = drive->channel;
+
+ ob_ide_pio_writeb(drive, IDEREG_CONTROL, IDECON_NIEN | IDECON_SRST);
+ ob_ide_400ns_delay(drive);
+ ob_ide_pio_writeb(drive, IDEREG_CONTROL, IDECON_NIEN);
+ ob_ide_400ns_delay(drive);
+
+ /*
+ * if master is present, wait for BUSY clear
+ */
+ if (chan->drives[0].present)
+ ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL);
+
+ /*
+ * if slave is present, wait until it allows register access
+ */
+ if (chan->drives[1].present) {
+ unsigned char sectorn, sectorc;
+ int timeout = 1000;
+
+ do {
+ /*
+ * select it
+ */
+ ob_ide_pio_writeb(drive, IDEREG_CURRENT, IDEHEAD_DEV1);
+ ob_ide_400ns_delay(drive);
+
+ sectorn = ob_ide_pio_readb(drive, IDEREG_SECTOR);
+ sectorc = ob_ide_pio_readb(drive, IDEREG_NSECTOR);
+
+ if (sectorc == 0x01 && sectorn == 0x01)
+ break;
+
+ } while (--timeout);
+ }
+
+ /*
+ * reset done, reselect original device
+ */
+ drive->channel->selected = -1;
+ ob_ide_select_drive(drive);
+}
+
+/*
+ * this serves as both a device check, and also to verify that the drives
+ * we initially "found" are really there
+ */
+static void
+ob_ide_device_type_check(struct ide_drive *drive)
+{
+ unsigned char sc, sn, cl, ch, st;
+
+ if (ob_ide_select_drive(drive))
+ return;
+
+ sc = ob_ide_pio_readb(drive, IDEREG_NSECTOR);
+ sn = ob_ide_pio_readb(drive, IDEREG_SECTOR);
+
+ if (sc == 0x01 && sn == 0x01) {
+ /*
+ * read device signature
+ */
+ cl = ob_ide_pio_readb(drive, IDEREG_LCYL);
+ ch = ob_ide_pio_readb(drive, IDEREG_HCYL);
+ st = ob_ide_pio_readb(drive, IDEREG_STATUS);
+ if (cl == 0x14 && ch == 0xeb)
+ drive->type = ide_type_atapi;
+ else if (cl == 0x00 && ch == 0x00 && st != 0x00)
+ drive->type = ide_type_ata;
+ else
+ drive->present = 0;
+ } else
+ drive->present = 0;
+}
+
+/*
+ * pure magic
+ */
+static void
+ob_ide_device_check(struct ide_drive *drive)
+{
+ unsigned char sc, sn;
+
+ /*
+ * non-existing io port should return 0xff, don't probe this
+ * channel at all then
+ */
+ if (ob_ide_pio_readb(drive, IDEREG_STATUS) == 0xff) {
+ drive->channel->present = 0;
+ return;
+ }
+
+ if (ob_ide_select_drive(drive))
+ return;
+
+ ob_ide_pio_writeb(drive, IDEREG_NSECTOR, 0x55);
+ ob_ide_pio_writeb(drive, IDEREG_SECTOR, 0xaa);
+ ob_ide_pio_writeb(drive, IDEREG_NSECTOR, 0xaa);
+ ob_ide_pio_writeb(drive, IDEREG_SECTOR, 0x55);
+ ob_ide_pio_writeb(drive, IDEREG_NSECTOR, 0x55);
+ ob_ide_pio_writeb(drive, IDEREG_SECTOR, 0xaa);
+
+ sc = ob_ide_pio_readb(drive, IDEREG_NSECTOR);
+ sn = ob_ide_pio_readb(drive, IDEREG_SECTOR);
+
+ /*
+ * we _think_ the device is there, we will make sure later
+ */
+ if (sc == 0x55 && sn == 0xaa) {
+ drive->present = 1;
+ drive->type = ide_type_unknown;
+ }
+}
+
+/*
+ * probe the legacy ide ports and find attached devices.
+ */
+static void
+ob_ide_probe(struct ide_channel *chan)
+{
+ struct ide_drive *drive;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ drive = &chan->drives[i];
+
+ ob_ide_device_check(drive);
+
+ /*
+ * no point in continuing
+ */
+ if (!chan->present)
+ break;
+
+ if (!drive->present)
+ continue;
+
+ /*
+ * select and reset device
+ */
+ if (ob_ide_select_drive(drive))
+ continue;
+
+ ob_ide_software_reset(drive);
+
+ ob_ide_device_type_check(drive);
+ }
+}
+
+/*
+ * The following functions are interfacing with OpenBIOS. They
+ * are device node methods. Thus they have to do proper stack handling.
+ *
+ */
+
+/*
+ * 255 sectors for ata lba28, 65535 for lba48, and 31 sectors for atapi
+ */
+static void
+ob_ide_max_transfer(int *idx)
+{
+ struct ide_drive *drive = *(struct ide_drive **)idx;
+
+ IDE_DPRINTF("max_transfer %x\n", drive->max_sectors * drive->bs);
+
+ PUSH(drive->max_sectors * drive->bs);
+}
+
+static void
+ob_ide_read_blocks(int *idx)
+{
+ cell n = POP(), cnt=n;
+ ucell blk = POP();
+ unsigned char *dest = (unsigned char *)cell2pointer(POP());
+ struct ide_drive *drive = *(struct ide_drive **)idx;
+
+ IDE_DPRINTF("ob_ide_read_blocks %lx block=%ld n=%ld\n",
+ (unsigned long)dest, (unsigned long)blk, (long)n);
+
+ while (n) {
+ int len = n;
+ if (len > drive->max_sectors)
+ len = drive->max_sectors;
+
+ if (ob_ide_read_sectors(drive, blk, dest, len)) {
+ IDE_DPRINTF("ob_ide_read_blocks: error\n");
+ RET(0);
+ }
+
+ dest += len * drive->bs;
+ n -= len;
+ blk += len;
+ }
+
+ PUSH(cnt);
+}
+
+static void
+ob_ide_block_size(int *idx)
+{
+ struct ide_drive *drive = *(struct ide_drive **)idx;
+
+ IDE_DPRINTF("ob_ide_block_size: block size %x\n", drive->bs);
+
+ PUSH(drive->bs);
+}
+
+static void
+ob_ide_initialize(int *idx)
+{
+ int props[3];
+ phandle_t ph=get_cur_dev();
+
+ push_str("block");
+ fword("device-type");
+
+ // Set dummy reg properties
+
+ set_int_property(ph, "#address-cells", 1);
+ set_int_property(ph, "#size-cells", 0);
+
+ props[0] = __cpu_to_be32(0); props[1] = __cpu_to_be32(0); props[2] = __cpu_to_be32(0);
+ set_property(ph, "reg", (char *)&props, 3*sizeof(int));
+
+ fword("is-deblocker");
+}
+
+static void
+ob_ide_open(int *idx)
+{
+ int ret=1, len;
+ phandle_t ph;
+ struct ide_drive *drive;
+ struct ide_channel *chan;
+ char *idename;
+ int unit;
+
+ fword("my-unit");
+ unit = POP();
+
+ fword("my-parent");
+ fword("ihandle>phandle");
+ ph=(phandle_t)POP();
+ idename=get_property(ph, "name", &len);
+
+ chan = ide_seek_channel(idename);
+ drive = &chan->drives[unit];
+ *(struct ide_drive **)idx = drive;
+
+ IDE_DPRINTF("opening channel %d unit %d\n", idx[1], idx[0]);
+ dump_drive(drive);
+
+ if (drive->type != ide_type_ata)
+ ret= !ob_ide_atapi_drive_ready(drive);
+
+ selfword("open-deblocker");
+
+ /* interpose disk-label */
+ ph = find_dev("/packages/disk-label");
+ fword("my-args");
+ PUSH_ph( ph );
+ fword("interpose");
+
+ RET ( -ret );
+}
+
+static void
+ob_ide_close(struct ide_drive *drive)
+{
+ selfword("close-deblocker");
+}
+
+NODE_METHODS(ob_ide) = {
+ { NULL, ob_ide_initialize },
+ { "open", ob_ide_open },
+ { "close", ob_ide_close },
+ { "read-blocks", ob_ide_read_blocks },
+ { "block-size", ob_ide_block_size },
+ { "max-transfer", ob_ide_max_transfer },
+};
+
+static void
+ob_ide_ctrl_initialize(int *idx)
+{
+ phandle_t ph=get_cur_dev();
+
+ /* set device type */
+ push_str(DEV_TYPE);
+ fword("device-type");
+
+ set_int_property(ph, "#address-cells", 1);
+ set_int_property(ph, "#size-cells", 0);
+}
+
+static void
+ob_ide_ctrl_decodeunit(int *idx)
+{
+ fword("parse-hex");
+}
+
+NODE_METHODS(ob_ide_ctrl) = {
+ { NULL, ob_ide_ctrl_initialize },
+ { "decode-unit", ob_ide_ctrl_decodeunit },
+};
+
+static void set_cd_alias(const char *path)
+{
+ phandle_t aliases;
+
+ aliases = find_dev("/aliases");
+
+ if (get_property(aliases, "cd", NULL))
+ return;
+
+ set_property(aliases, "cd", path, strlen(path) + 1);
+ set_property(aliases, "cdrom", path, strlen(path) + 1);
+}
+
+static void set_hd_alias(const char *path)
+{
+ phandle_t aliases;
+
+ aliases = find_dev("/aliases");
+
+ if (get_property(aliases, "hd", NULL))
+ return;
+
+ set_property(aliases, "hd", path, strlen(path) + 1);
+ set_property(aliases, "disk", path, strlen(path) + 1);
+}
+
+static void set_ide_alias(const char *path)
+{
+ phandle_t aliases;
+ static int ide_counter = 0;
+ char idestr[8];
+
+ aliases = find_dev("/aliases");
+
+ snprintf(idestr, sizeof(idestr), "ide%d", ide_counter++);
+ set_property(aliases, idestr, path, strlen(path) + 1);
+}
+
+int ob_ide_init(const char *path, uint32_t io_port0, uint32_t ctl_port0,
+ uint32_t io_port1, uint32_t ctl_port1)
+{
+ int i, j;
+ char nodebuff[128];
+ phandle_t dnode;
+ struct ide_channel *chan;
+ int io_ports[IDE_MAX_CHANNELS];
+ int ctl_ports[IDE_MAX_CHANNELS];
+ u32 props[6];
+
+ io_ports[0] = io_port0;
+ ctl_ports[0] = ctl_port0;
+ io_ports[1] = io_port1;
+ ctl_ports[1] = ctl_port1;
+
+ for (i = 0; i < IDE_NUM_CHANNELS; i++, current_channel++) {
+
+ chan = malloc(sizeof(struct ide_channel));
+
+ snprintf(chan->name, sizeof(chan->name),
+ DEV_NAME, current_channel);
+
+ chan->mmio = 0;
+
+ for (j = 0; j < 8; j++)
+ chan->io_regs[j] = io_ports[i] + j;
+
+ chan->io_regs[8] = ctl_ports[i];
+ chan->io_regs[9] = ctl_ports[i] + 1;
+
+ chan->obide_inb = ob_ide_inb;
+ chan->obide_insw = ob_ide_insw;
+ chan->obide_outb = ob_ide_outb;
+ chan->obide_outsw = ob_ide_outsw;
+
+ chan->selected = -1;
+
+ /*
+ * assume it's there, if not io port dead check will clear
+ */
+ chan->present = 1;
+
+ for (j = 0; j < 2; j++) {
+ chan->drives[j].present = 0;
+ chan->drives[j].unit = j;
+ chan->drives[j].channel = chan;
+ /* init with a decent value */
+ chan->drives[j].bs = 512;
+
+ chan->drives[j].nr = i * 2 + j;
+ }
+
+ ide_add_channel(chan);
+
+ ob_ide_probe(chan);
+
+ if (!chan->present)
+ continue;
+
+ ob_ide_identify_drives(chan);
+
+ snprintf(nodebuff, sizeof(nodebuff), "%s/" DEV_NAME, path,
+ current_channel);
+ REGISTER_NAMED_NODE(ob_ide_ctrl, nodebuff);
+
+ dnode = find_dev(nodebuff);
+
+#if !defined(CONFIG_PPC) && !defined(CONFIG_SPARC64)
+ props[0]=14; props[1]=0;
+ set_property(dnode, "interrupts",
+ (char *)&props, 2*sizeof(props[0]));
+#endif
+
+ props[0] = __cpu_to_be32(chan->io_regs[0]);
+ props[1] = __cpu_to_be32(1); props[2] = __cpu_to_be32(8);
+ props[3] = __cpu_to_be32(chan->io_regs[8]);
+ props[4] = __cpu_to_be32(1); props[5] = __cpu_to_be32(2);
+ set_property(dnode, "reg", (char *)&props, 6*sizeof(props[0]));
+
+ IDE_DPRINTF(DEV_NAME": [io ports 0x%x-0x%x,0x%x]\n",
+ current_channel, chan->io_regs[0],
+ chan->io_regs[0] + 7, chan->io_regs[8]);
+
+ for (j = 0; j < 2; j++) {
+ struct ide_drive *drive = &chan->drives[j];
+ const char *media = "UNKNOWN";
+
+ if (!drive->present)
+ continue;
+
+ IDE_DPRINTF(" drive%d [ATA%s ", j,
+ drive->type == ide_type_atapi ? "PI" : "");
+ switch (drive->media) {
+ case ide_media_floppy:
+ media = "floppy";
+ break;
+ case ide_media_cdrom:
+ media = "cdrom";
+ break;
+ case ide_media_optical:
+ media = "mo";
+ break;
+ case ide_media_disk:
+ media = "disk";
+ break;
+ }
+ IDE_DPRINTF("%s]: %s\n", media, drive->model);
+ snprintf(nodebuff, sizeof(nodebuff),
+ "%s/" DEV_NAME "/%s", path, current_channel,
+ media);
+ REGISTER_NAMED_NODE(ob_ide, nodebuff);
+ dnode=find_dev(nodebuff);
+ set_int_property(dnode, "reg", j);
+
+ /* create aliases */
+
+ set_ide_alias(nodebuff);
+ if (drive->media == ide_media_cdrom)
+ set_cd_alias(nodebuff);
+ if (drive->media == ide_media_disk)
+ set_hd_alias(nodebuff);
+ }
+ }
+
+ return 0;
+}
+
+#if defined(CONFIG_DRIVER_MACIO)
+static unsigned char
+macio_ide_inb(struct ide_channel *chan, unsigned int port)
+{
+ return in_8((unsigned char*)(chan->mmio + (port << 4)));
+}
+
+static void
+macio_ide_outb(struct ide_channel *chan, unsigned char data, unsigned int port)
+{
+ out_8((unsigned char*)(chan->mmio + (port << 4)), data);
+}
+
+static void
+macio_ide_insw(struct ide_channel *chan,
+ unsigned int port, unsigned char *addr, unsigned int count)
+{
+ _insw((uint16_t*)(chan->mmio + (port << 4)), addr, count);
+}
+
+static void
+macio_ide_outsw(struct ide_channel *chan,
+ unsigned int port, unsigned char *addr, unsigned int count)
+{
+ _outsw((uint16_t*)(chan->mmio + (port << 4)), addr, count);
+}
+
+#define MACIO_IDE_OFFSET 0x00020000
+#define MACIO_IDE_SIZE 0x00001000
+
+int macio_ide_init(const char *path, uint32_t addr, int nb_channels)
+{
+ int i, j;
+ char nodebuff[128];
+ phandle_t dnode;
+ u32 props[8];
+ struct ide_channel *chan;
+
+ /* IDE ports on Macs are numbered from 3.
+ * Also see comments in macio.c:openpic_init() */
+ current_channel = 3;
+
+ for (i = 0; i < nb_channels; i++, current_channel++) {
+
+ chan = malloc(sizeof(struct ide_channel));
+
+ snprintf(chan->name, sizeof(chan->name),
+ DEV_NAME, current_channel);
+
+ chan->mmio = addr + MACIO_IDE_OFFSET + i * MACIO_IDE_SIZE;
+
+ chan->obide_inb = macio_ide_inb;
+ chan->obide_insw = macio_ide_insw;
+ chan->obide_outb = macio_ide_outb;
+ chan->obide_outsw = macio_ide_outsw;
+
+ chan->selected = -1;
+
+ /*
+ * assume it's there, if not io port dead check will clear
+ */
+ chan->present = 1;
+
+ for (j = 0; j < 2; j++) {
+ chan->drives[j].present = 0;
+ chan->drives[j].unit = j;
+ chan->drives[j].channel = chan;
+ /* init with a decent value */
+ chan->drives[j].bs = 512;
+
+ chan->drives[j].nr = i * 2 + j;
+ }
+
+ ob_ide_probe(chan);
+
+ if (!chan->present) {
+ free(chan);
+ continue;
+ }
+
+ ide_add_channel(chan);
+
+ ob_ide_identify_drives(chan);
+
+ snprintf(nodebuff, sizeof(nodebuff), "%s/" DEV_NAME, path,
+ current_channel);
+ REGISTER_NAMED_NODE(ob_ide_ctrl, nodebuff);
+
+ dnode = find_dev(nodebuff);
+
+ set_property(dnode, "compatible", (is_oldworld() ?
+ "heathrow-ata" : "keylargo-ata"), 13);
+
+ props[0] = 0x00000526;
+ props[1] = 0x00000085;
+ props[2] = 0x00000025;
+ props[3] = 0x00000025;
+ props[4] = 0x00000025;
+ props[5] = 0x00000000;
+ props[6] = 0x00000000;
+ props[7] = 0x00000000;
+ OLDWORLD(set_property(dnode, "AAPL,pio-timing",
+ (char *)&props, 8*sizeof(props[0])));
+
+ /* The first interrupt entry is the ide interrupt, the second
+ the dbdma interrupt */
+ switch (i) {
+ case 0:
+ props[0] = 0x0000000d;
+ props[2] = 0x00000002;
+ break;
+ case 1:
+ props[0] = 0x0000000e;
+ props[2] = 0x00000003;
+ break;
+ case 2:
+ props[0] = 0x0000000f;
+ props[2] = 0x00000004;
+ break;
+ default:
+ props[0] = 0x00000000;
+ props[2] = 0x00000000;
+ break;
+ }
+ props[1] = 0x00000000; /* XXX level triggered on real hw */
+ props[3] = 0x00000000;
+ NEWWORLD(set_property(dnode, "interrupts",
+ (char *)&props, 4*sizeof(props[0])));
+ NEWWORLD(set_int_property(dnode, "#interrupt-cells", 2));
+
+ props[1] = props[2];
+ OLDWORLD(set_property(dnode, "AAPL,interrupts",
+ (char *)&props, 2*sizeof(props[0])));
+
+ props[0] = MACIO_IDE_OFFSET + i * MACIO_IDE_SIZE;
+ props[1] = MACIO_IDE_SIZE;
+ props[2] = 0x00008b00 + i * 0x0200;
+ props[3] = 0x0200;
+ set_property(dnode, "reg", (char *)&props, 4*sizeof(props[0]));
+
+ props[0] = addr + MACIO_IDE_OFFSET + i * MACIO_IDE_SIZE;
+ props[1] = addr + 0x00008b00 + i * 0x0200;
+ OLDWORLD(set_property(dnode, "AAPL,address",
+ (char *)&props, 2*sizeof(props[0])));
+
+ props[0] = 0;
+ OLDWORLD(set_property(dnode, "AAPL,bus-id", (char*)props,
+ 1 * sizeof(props[0])));
+ IDE_DPRINTF(DEV_NAME": [io ports 0x%lx]\n",
+ current_channel, chan->mmio);
+
+ for (j = 0; j < 2; j++) {
+ struct ide_drive *drive = &chan->drives[j];
+ const char *media = "UNKNOWN";
+
+ if (!drive->present)
+ continue;
+
+ IDE_DPRINTF(" drive%d [ATA%s ", j,
+ drive->type == ide_type_atapi ? "PI" : "");
+ switch (drive->media) {
+ case ide_media_floppy:
+ media = "floppy";
+ break;
+ case ide_media_cdrom:
+ media = "cdrom";
+ break;
+ case ide_media_optical:
+ media = "mo";
+ break;
+ case ide_media_disk:
+ media = "disk";
+ break;
+ }
+ IDE_DPRINTF("%s]: %s\n", media, drive->model);
+ snprintf(nodebuff, sizeof(nodebuff),
+ "%s/" DEV_NAME "/%s", path, current_channel,
+ media);
+ REGISTER_NAMED_NODE(ob_ide, nodebuff);
+ dnode = find_dev(nodebuff);
+ set_int_property(dnode, "reg", j);
+
+ /* create aliases */
+
+ set_ide_alias(nodebuff);
+ if (drive->media == ide_media_cdrom)
+ set_cd_alias(nodebuff);
+ if (drive->media == ide_media_disk)
+ set_hd_alias(nodebuff);
+ }
+ }
+
+ return 0;
+}
+#endif /* CONFIG_DRIVER_MACIO */