/* KallistiOS ##version##

   ide.c

   (c)1997,2002 Dan Potter

*/

#include <assert.h>
#include <stdio.h>
#include <navi/ide.h>
#include <dc/g2bus.h>

CVSID("$Id: navi_ide.c,v 1.1 2002/05/05 22:16:49 bardtx Exp $");

/*
   A *very* simple port-level ATA-IDE device driver. This was ported up
   from the _original_ KallistiOS =) (PC based)
 */

static uint16 dd[256];			/* hard disk parameters, read from controller */
static uint32 hd_cyls, hd_heads, hd_sects;			/* hard disk geometry */


static void ide_outp(int port, uint16 value, int size) {
	uint32 addr, val2;

	switch (port & 0xff0) {
	case 0x1f0:
		addr = 0xb4002000 + ((port & 7) << 2);
		break;
	case 0x3f0:
		addr = 0xb4001000 + ((port & 7) << 2);
		break;
	default:
		assert(0);
	}

	//printf("ide_outp %02x -> %04x(%08x)\n", value, port, addr);
	g2_write_16(addr, value);
}

uint16 ide_inp(int port, int size) {
	uint32 addr;
	uint16 value;

	switch (port & 0xff0) {
	case 0x1f0:
		addr = 0xb4002000 + ((port & 7) << 2);
		break;
	case 0x3f0:
		addr = 0xb4001000 + ((port & 7) << 2);
		break;
	default:
		assert(0);
	}

	value = g2_read_16(addr);
	if (!size)
		value &= 0xff;

	return value;
}

#define outp(x, y) ide_outp(x, y, 0)
#define outpw(x, y) ide_outp(x, y, 1)
#define inp(x) ide_inp(x, 0)
#define inpw(x) ide_inp(x, 1)


/* These are to synchronize us with the controller so we don't do something
   it's not ready for */
static void wait_controller() {
	int timeout = 1000;

	while ((inp(0x1f7) & 0x80) && timeout) {
		usleep(1*1000);
		timeout--;
	}

	if (!timeout) {
		dbglog(DBG_ERROR, "IDE controller timed out waiting for ready.. status = %x/%x\n",
			inp(0x1f7), inp(0x1f1));
	}
}

static void wait_data() {
	int timeout = 1000;

	while (!(inp(0x1f7) & 0x08) && timeout) {
		usleep(1*1000);
		timeout--;
	}

	if (!timeout) {
		dbglog(DBG_ERROR, "IDE controller timed out waiting for data.. status = %x/%x\n",
			inp(0x1f7), inp(0x1f1));
	}
}

/* Reads a chunk of ascii out of the hd parms table */
static char *get_ascii(uint16 *in_data, uint32 off_start, uint32 off_end) {
	static char ret_val [255];
	int loop, loop1;

	/* First, construct a string from the controller-retrieved data */
        for (loop = off_start, loop1 = 0; loop <= off_end; loop++) {
		ret_val [loop1++] = (char) (in_data [loop] / 256);  /* Get High byte */
		ret_val [loop1++] = (char) (in_data [loop] % 256);  /* Get Low byte */
	}

	// Now, go back and eliminate the blank spaces
	for ( ; (ret_val[loop1]<'A' || ret_val[loop1]>'z') && loop1>=0 ; loop1--)
		ret_val[loop1]='\0';

	return ret_val;
}

/* Read n sectors from the hard disk using PIO mode */
static int ide_read_chs(uint32 cyl,uint32 head,uint32 sector,uint32 numsects, uint8 *bufptr) {
	int	o;
	uint16	*bufptr16 = (uint16*)bufptr;

	//printf("reading C/H/S/Cnt %d/%d/%d/%d\n",
	//	cyl, head, sector, numsects);

	wait_controller();		/* wait for controller to be not busy */

	outp(0x1f2,numsects & 0xff);	/* number of sectors to read */
	outp(0x1f3,sector & 0xff);	/* sector number to read */
	outp(0x1f4,cyl & 0xff);		/* cylinder (low part) */
	outp(0x1f5,cyl >> 8);		/* cylinder (high part) */
	outp(0x1f6,0xa0 | (head&0xf));	/* select device 0 and head */
	outp(0x1f7,0x20);		/* command == read sector(s) w/retry */

	wait_data();			/* wait for data to be read */

	for (o=0; o<256; o++) {
		/*if (inp(0x1f7) & 1) {
			printf("as of %d, error code is %x/%x\n", o, inp(0x1f7), inp(0x1f1));
			return -1;
		} */
		bufptr16[o] = inpw(0x1f0);
	}

	if (inp(0x1f7) & 1) {
		printf("after read, status is %x/%x\n", inp(0x1f7), inp(0x1f1));
	}

	return 0;
}

/* Write n sectors to the hard disk using PIO mode */
static int ide_write_chs(uint32 cyl,uint32 head,uint32 sector,uint32 numsects, uint8 *bufptr) {
	int	o;
	uint16	*bufptr16 = (uint16*)bufptr;

	//printf("writing C/H/S/Cnt %d/%d/%d/%d\n",
	//	cyl, head, sector, numsects);

	wait_controller();		/* wait for controller to be not busy */

	outp(0x1f2,numsects & 0xff);	/* number of sectors to write */
	outp(0x1f3,sector & 0xff);	/* sector number to write */
	outp(0x1f4,cyl & 0xff);		/* cylinder (low part) */
	outp(0x1f5,cyl >> 8);		/* cylinder (high part) */
	outp(0x1f6,0xa0 | (head&0xf));	/* select device 0 and head */
	outp(0x1f7,0x30);		/* command == write sector(s) w/retry */

	wait_data();			/* wait for data to be ready */

	for (o=0; o<256; o++) {
		/* if (inp(0x1f7) & 1) {
			printf("as of %d, error code is %x/%x\n", o, inp(0x1f7), inp(0x1f1));
			return -1;
		} */
		outpw(0x1f0, bufptr16[o]);
	}

	if (inp(0x1f7) & 1) {
		printf("after write, status is %x/%x\n", inp(0x1f7), inp(0x1f1));
	}

	return 0;
}

/* Translate a linear sector address relative to the first of the partition
   to a CHS address suitable for feeding to the hard disk */
static void linear_to_chs(uint32 linear, uint32 * cyl, uint32 * head, uint32 * sector) {
	*sector = linear % hd_sects + 1;
	*head = (linear / hd_sects) % hd_heads;
	*cyl = linear / (hd_sects * hd_heads);
}

/* Read n sectors from the hard disk using PIO mode */
int ide_read(uint32 linear, uint32 numsects, void * bufptr) {
	int	i;
	uint32  cyl,head,sector;

	if (numsects > 1) {
		for (i=0; i<numsects; i++) {
			if (ide_read(linear+i, 1, ((uint8 *)bufptr)+i*512) < 0)
				return -1;
		}
	} else {
		linear_to_chs(linear, &cyl, &head, &sector);
		if (ide_read_chs(cyl, head, sector, numsects, (uint8*)bufptr) < 0)
			return -1;
	}

	return 0;
}

/* Write n sectors to the hard disk using PIO mode */
int ide_write(uint32 linear, uint32 numsects, void *bufptr) {
	int	i;
	uint32  cyl,head,sector;

	if (numsects > 1) {
		for (i=0; i<numsects; i++) {
			if (ide_write(linear+i, 1, ((uint8 *)bufptr)+i*512) < 0)
				return -1;
		}
	} else {
		linear_to_chs(linear, &cyl, &head, &sector);
		ide_write_chs(cyl, head, sector, numsects, (uint8*)bufptr);
	}

	return 0;
}

/* Get the available space */
uint32 ide_num_sectors() {
	return hd_cyls * hd_heads * hd_sects;
}

/* Initialize the device */
int ide_init() {
	int dd_off;
	
        dbglog(DBG_INFO, "ide_init: initializing\n");

	/* Reset */
	outp(0x3f6, 0x0e);
	usleep(10*1000);
	outp(0x3f6, 0x0a);
	usleep(10*1000);

	wait_controller();
	outp(0x1f6,0xa0);	/* get info on first drive. 0xb0 == 2nd */
	outp(0x1f7,0xec);	/* get drive info data */
	wait_data();
	for (dd_off=0; dd_off<256; dd_off++) {
		dd[dd_off] = inpw(0x1f0);
	}

	hd_cyls = dd[1];
	hd_heads = dd[3];
	hd_sects = dd[6];

        dbglog(DBG_INFO, "ide0: %s, %dMB, CHS (%d/%d/%d)\n",
		get_ascii(dd, 27, 46),
		(hd_cyls * hd_heads * hd_sects * 512L) / (1024L*1024L),
		hd_cyls, hd_heads, hd_sects);

	return 0;
}

void ide_shutdown() {
}


