/*
 *   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;

	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", "heathrow-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 */
