/*Arculator 2.2 by Sarah Walker
  IDE emulation*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "arc.h"
#include "config.h"
#include "ide.h"
#include "ioc.h"
#include "timer.h"

/* Bits of 'atastat' */
#define ERR_STAT		0x01
#define DRQ_STAT		0x08 /* Data request */
#define DSC_STAT                0x10
#define SERVICE_STAT            0x10
#define READY_STAT		0x40
#define BUSY_STAT		0x80

/* Bits of 'error' */
#define ABRT_ERR		0x04 /* Command aborted */
#define MCR_ERR			0x08 /* Media change request */

ide_t ide_internal;

static void ide_update_irq(ide_t *ide)
{
	if (ide->irq_active[ide->drive] && ide->irq_enabled)
	{
		if (ide->irq_raise)
			ide->irq_raise(ide);
	}
	else
	{
		if (ide->irq_clear)
			ide->irq_clear(ide);
	}
}

static void ide_raise_irq(ide_t *ide)
{
	ide->irq_active[ide->drive] = 1;
	ide_update_irq(ide);
}
static void ide_clear_irq(ide_t *ide)
{
	ide->irq_active[ide->drive] = 0;
	ide_update_irq(ide);
}

void closeide(ide_t *ide)
{
	if (ide->hdfile[0])
		fclose(ide->hdfile[0]);
	if (ide->hdfile[1])
		fclose(ide->hdfile[1]);
}

void resetide(ide_t *ide,
		char *fn_pri, int pri_spt, int pri_hpc, int pri_cyl,
		char *fn_sec, int sec_spt, int sec_hpc, int sec_cyl,
		void (*irq_raise)(ide_t *ide), void (*irq_clear)(ide_t *ide))
{
	int c;

	closeide(ide);
	memset(ide, 0, sizeof(ide_t));

	ide->drive=0;
	ide->atastat = READY_STAT | DSC_STAT;
	ide->idebufferb = (uint8_t *)ide->idebuffer;
	ide->irq_raise = irq_raise;
	ide->irq_clear = irq_clear;

	for (c = 0; c < 2; c++)
	{
		if (!c)
			ide->hdfile[c] = fopen(fn_pri, "rb+");
		else
			ide->hdfile[c] = fopen(fn_sec, "rb+");

		if (ide->hdfile[c])
		{
			uint8_t log2secsize, sectors, heads, density;

			fseek(ide->hdfile[c], 0xFC0, SEEK_SET);
			log2secsize = getc(ide->hdfile[c]);
			sectors = getc(ide->hdfile[c]);
			heads = getc(ide->hdfile[c]);
			density = getc(ide->hdfile[c]);

			if ((log2secsize != 8 && log2secsize != 9) || !sectors || !heads || sectors > 63 || heads > 16 || density != 0)
				ide->skip512[c] = 0;
			else
				ide->skip512[c] = 1;
		}
//        rpclog("Drive %i - %i %i\n",c,ide->spt[c],ide->hpc[c]);
	}
	ide->def_spt[0] = ide->spt[0] = pri_spt;
	ide->def_hpc[0] = ide->hpc[0] = pri_hpc;
	ide->def_cyl[0] = ide->cyl[0] = pri_cyl;
	ide->def_spt[1] = ide->spt[1] = sec_spt;
	ide->def_hpc[1] = ide->hpc[1] = sec_hpc;
	ide->def_cyl[1] = ide->cyl[1] = sec_cyl;
	ide->max_sector[0] = pri_spt * pri_hpc * pri_cyl;
	ide->max_sector[1] = sec_spt * sec_hpc * sec_cyl;
//        ide->spt=63;
//        ide->hpc=16;
//        ide->spt=16;
//        ide->hpc=14;

	timer_add(&ide->timer, callbackide, ide, 0);
}

void writeidew(ide_t *ide, uint16_t val)
{
//        if (ide->sector==7) rpclog("Write data %08X %04X\n",ide->pos,val);
	ide->idebuffer[ide->pos>>1]=val;
	ide->pos+=2;
	if (ide->pos>=512)
	{
		ide->pos=0;
		ide->atastat = BUSY_STAT | DSC_STAT;
		timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
	}
}

void writeide(ide_t *ide, uint32_t addr, uint8_t val)
{
//        if (addr!=0x1F0) rpclog("Write IDE %08X %02X %08X %08X\n",addr,val,PC-8,armregs[12]);
	switch (addr)
	{
		case 0x1F0:
		ide->idebufferb[ide->pos++]=val;
		if (ide->pos>=512)
		{
			ide->pos=0;
			ide->atastat = BUSY_STAT | DSC_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
		}
		return;
		case 0x1F1:
		ide->cylprecomp=val;
		return;
		case 0x1F2:
		ide->secount=val;
		if (!val) ide->secount=256;
//                rpclog("secount %i %02X\n",ide->secount,val);
		return;
		case 0x1F3:
		ide->sector=val;
		return;
		case 0x1F4:
		ide->cylinder=(ide->cylinder&0xFF00)|val;
		return;
		case 0x1F5:
		ide->cylinder=(ide->cylinder&0xFF)|(val<<8);
		return;
		case 0x1F6:
		ide->head=val&0xF;
		ide->drive=(val>>4)&1;
		ide_update_irq(ide);
//                rpclog("Write IDE head %02X %i,%i\n",val,ide->head,ide->drive);
		return;
		case 0x1F7: /*Command register*/
  //              rpclog("Starting new command %02X\n",val);
		ide->command=val;
		ide->error=0;
		switch (val)
		{
			case 0x10: /*Restore*/
			case 0x70: /*Seek*/
			ide->atastat = BUSY_STAT | READY_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			return;
			case 0x20: /*Read sector*/
			case 0x21: /*Read sector, no retry*/
/*                        if (ide->secount>1)
			{
				error("Read %i sectors from sector %i cylinder %i head %i\n",ide->secount,ide->sector,ide->cylinder,ide->head);
				exit(-1);
			}*/
			rpclog("Read %i sectors from sector %i cylinder %i head %i\n",ide->secount,ide->sector,ide->cylinder,ide->head);
			ide->atastat = BUSY_STAT | READY_STAT | DSC_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			return;
			case 0x30: /*Write sector*/
			case 0x31: /*Write sector, no retry*/
/*                        if (ide->secount>1)
			{
				error("Write %i sectors to sector %i cylinder %i head %i\n",ide->secount,ide->sector,ide->cylinder,ide->head);
				exit(-1);
			}*/
			rpclog("Write %i sectors to sector %i cylinder %i head %i\n",ide->secount,ide->sector,ide->cylinder,ide->head);
			ide->atastat = READY_STAT | DRQ_STAT | DSC_STAT;
			ide->pos=0;
			return;
			case 0x40: /*Read verify*/
			case 0x41:
//                        rpclog("Read verify %i sectors from sector %i cylinder %i head %i\n",ide->secount,ide->sector,ide->cylinder,ide->head);
			ide->atastat = BUSY_STAT | READY_STAT | DSC_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			return;
			case 0x50:
//                        rpclog("Format track %i head %i\n",ide->cylinder,ide->head);
			ide->atastat = READY_STAT | DRQ_STAT | DSC_STAT;
//                        idecallback=200;
			ide->pos=0;
			return;
			case 0x91: /*Set parameters*/
			ide->atastat = BUSY_STAT | READY_STAT | DSC_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			return;
			case 0xA1: /*Identify packet device*/
			case 0xE3: /*Idle*/
			ide->atastat = BUSY_STAT | READY_STAT | DSC_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			return;
			case 0xEC: /*Identify device*/
			ide->atastat = BUSY_STAT | READY_STAT | DSC_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			return;
			case 0xE5: /*Standby power check*/
			ide->atastat = BUSY_STAT | READY_STAT | DSC_STAT;
			timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			return;

			default:
			ide->atastat = READY_STAT | ERR_STAT | DSC_STAT;
			ide->error = ABRT_ERR;
			ide_raise_irq(ide);
			return;
		}
		return;
		case 0x3F6:
		if ((ide->fdisk&4) && !(val&4))
		{
			timer_set_delay_u64(&ide->timer, 5000 * TIMER_USEC);
			ide->reset=1;
			ide->atastat = BUSY_STAT | DSC_STAT;
//                        rpclog("IDE Reset\n");
		}
		ide->fdisk=val;
		ide->irq_enabled = !(val & 2);
		ide_update_irq(ide);
		return;
	}
#ifndef RELEASE_BUILD
	fatal("Bad IDE write %04X %02X\n",addr,val);
#endif
}

uint8_t readide(ide_t *ide, uint32_t addr)
{
	uint8_t temp;
//        if (addr!=0x1F0) rpclog("Read IDE %08X %08X\n",addr,PC-8);
	switch (addr)
	{
		case 0x1F0:
//                rpclog("Read data %08X %02X\n",ide->pos,idebufferb[ide->pos]);
		temp = ide->idebufferb[ide->pos];
		ide->pos++;
		if (ide->pos>=512)
		{
			ide->pos=0;
			ide->atastat = READY_STAT | DSC_STAT;
		}
		return temp;
		case 0x1F1:
//                rpclog("Read IDEerror %02X\n",ide->atastat);
		return ide->error;
		case 0x1F2:
		return ide->secount;
		case 0x1F3:
		return ide->sector;
		case 0x1F4:
		return ide->cylinder&0xFF;
		case 0x1F5:
		return ide->cylinder>>8;
		case 0x1F6:
//                        rpclog("Read IDE Head Drive %02X %02X\n",ide->head|(ide->drive<<4),armregs[1]);
		return ide->head|(ide->drive<<4)|0xA0;
		case 0x1F7:
		ide_clear_irq(ide);
//                rpclog("Read ATAstat %02X %p\n",ide->atastat, ide);
		return ide->atastat;
		case 0x3F6:
//                rpclog("Read ATAstat %02X\n",ide->atastat);
		return ide->atastat;
	}
#ifndef RELEASE_BUILD
	fatal("Bad IDE read %04X\n",addr);
#endif
	return 0xff;
}

uint16_t readidew(ide_t *ide)
{
	uint16_t temp;
//        if (ide->sector==7) rpclog("Read data2 %08X %04X\n",ide->pos,idebuffer[ide->pos>>1]);
	temp = ide->idebuffer[ide->pos>>1];
//        rpclog("Read IDEW %04X %i\n",temp, ide->pos);
	ide->pos+=2;
	if (ide->pos>=512)
	{
		ide->pos=0;
		ide->atastat = READY_STAT | DSC_STAT;
		if (ide->command == 0x20 || ide->command == 0x21)
		{
			ide->secount--;
//                        rpclog("Sector done - secount %i\n",ide->secount);
			if (ide->secount)
			{
				ide->sector++;
				if (ide->sector==(ide->spt[ide->drive]+1))
				{
					ide->sector=1;
					ide->head++;
					if (ide->head==(ide->hpc[ide->drive]))
					{
						ide->head=0;
						ide->cylinder++;
					}
				}
				ide->atastat = BUSY_STAT | READY_STAT | DSC_STAT;
				timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
			}
		}
	}
	return temp;
}

void resetide_drive(ide_t *ide)
{
	timer_set_delay_u64(&ide->timer, 1000 * TIMER_USEC);
	ide->reset=1;
	ide->atastat = BUSY_STAT | DSC_STAT;
	rpclog("Requested reset\n");
}

static int ide_check_addr(ide_t *ide)
{
	if (ide->cylinder > ide->cyl[ide->drive] || ide->head > ide->hpc[ide->drive] || (ide->sector - 1) > ide->spt[ide->drive] ||
	    ((((ide->cylinder * ide->hpc[ide->drive]) + ide->head) * ide->spt[ide->drive]) + (ide->sector - 1)) >= ide->max_sector[ide->drive])
	{
		ide->atastat = READY_STAT | DSC_STAT | ERR_STAT;
		ide->error = 0x10;
		ide_raise_irq(ide);
		return 1;
	}
	return 0;
}

void callbackide(void *p)
{
	ide_t *ide = p;
	int addr,c;
	rpclog("IDE callback: drive=%i reset=%i command=%02x skip512=%i %p\n", ide->drive, ide->reset, ide->command, ide->skip512[ide->drive], ide);
//        rpclog("IDE callback %08X %i %02X\n",hdfile[ide->drive],ide->drive,ide->command);
	if (!ide->hdfile[ide->drive])
	{
		ide->atastat = READY_STAT | DSC_STAT | ERR_STAT;
		ide->error=4;
		ide_raise_irq(ide);
		return;
	}
	if (ide->reset)
	{
		ide->atastat = READY_STAT | DSC_STAT;
		ide->error=0;
		ide->secount=1;
		ide->sector=1;
		ide->head=0;
		ide->cylinder=0;
		ide->reset=0;
//                rpclog("Reset callback\n");
		return;
	}
	switch (ide->command)
	{
		case 0x10: /*Restore*/
		case 0x70: /*Seek*/
//                rpclog("Restore callback\n");
		ide->atastat = READY_STAT | DSC_STAT;
		ide_raise_irq(ide);
		return;
		case 0x20: /*Read sectors*/
		case 0x21: /*Read sectors, no retry*/
		if (!ide->secount)
		{
			ide->atastat = READY_STAT | DSC_STAT;
			return;
		}
		if (ide_check_addr(ide))
			return;
		readflash[0]=1;
		addr=((((ide->cylinder*ide->hpc[ide->drive])+ide->head)*ide->spt[ide->drive])+(ide->sector))*512;
		if (!ide->skip512[ide->drive]) addr-=512;
		/*                if (ide->cylinder || ide->head)
		{
			error("Read from other cylinder/head");
			exit(-1);
		}*/
		rpclog("Seek to %08X\n",addr);
		fseek(ide->hdfile[ide->drive],addr,SEEK_SET);
		fread(ide->idebuffer,512,1,ide->hdfile[ide->drive]);
		ide->pos=0;
		ide->atastat = READY_STAT | DRQ_STAT | DSC_STAT;
//                rpclog("Read sector callback %i %i %i offset %08X %i left %i\n",ide->sector,ide->cylinder,ide->head,addr,ide->secount,ide->spt[ide->drive]);
		ide_raise_irq(ide);
		return;
		case 0x30: /*Write sector*/
		case 0x31: /*Write sector, no retry*/
		if (ide_check_addr(ide))
			return;
		readflash[0]=2;
		addr=((((ide->cylinder*ide->hpc[ide->drive])+ide->head)*ide->spt[ide->drive])+(ide->sector))*512;
		if (!ide->skip512[ide->drive]) addr-=512;
//                rpclog("Write sector callback %i %i %i offset %08X %i left %i %i %i\n",ide->sector,ide->cylinder,ide->head,addr,ide->secount,ide->spt[ide->drive],ide->hpc[ide->drive],ide->drive);
		fseek(ide->hdfile[ide->drive],addr,SEEK_SET);
		fwrite(ide->idebuffer,512,1,ide->hdfile[ide->drive]);
		ide_raise_irq(ide);
		ide->secount--;
		if (ide->secount)
		{
			ide->atastat = READY_STAT | DRQ_STAT | DSC_STAT;
			ide->pos=0;
			ide->sector++;
			if (ide->sector==(ide->spt[ide->drive]+1))
			{
				ide->sector=1;
				ide->head++;
				if (ide->head==(ide->hpc[ide->drive]))
				{
					ide->head=0;
					ide->cylinder++;
				}
			}
		}
		else
			ide->atastat = READY_STAT | DSC_STAT;
		return;
		case 0x40: /*Read verify*/
		case 0x41:
		if (ide_check_addr(ide))
			return;
		ide->pos=0;
		ide->atastat = READY_STAT | DSC_STAT;
//                rpclog("Read verify callback %i %i %i offset %08X %i left\n",ide->sector,ide->cylinder,ide->head,addr,ide->secount);
		ide_raise_irq(ide);
		return;
		case 0x50: /*Format track*/
		if (ide_check_addr(ide))
			return;
		addr=(((ide->cylinder*ide->hpc[ide->drive])+ide->head)*ide->spt[ide->drive])*512;
		if (!ide->skip512[ide->drive]) addr-=512;
//                rpclog("Format cyl %i head %i offset %08X secount %I\n",ide->cylinder,ide->head,addr,ide->secount);
		fseek(ide->hdfile[ide->drive],addr,SEEK_SET);
		memset(ide->idebufferb,0,512);
		for (c=0;c<ide->secount;c++)
		{
			fwrite(ide->idebuffer,512,1,ide->hdfile[ide->drive]);
		}
		ide->atastat = READY_STAT | DSC_STAT;
		ide_raise_irq(ide);
		return;
		case 0x91: /*Set parameters*/
		ide->spt[ide->drive]=ide->secount;
		ide->hpc[ide->drive]=ide->head+1;
		ide->cyl[ide->drive] = (ide->def_cyl[ide->drive] * ide->def_hpc[ide->drive] * ide->def_spt[ide->drive]) /
				       (ide->hpc[ide->drive] * ide->spt[ide->drive]);
		ide->atastat = READY_STAT | DSC_STAT;
		ide_raise_irq(ide);
		return;
		case 0xA1:
		case 0xE3:
			case 0xE5:
		ide->atastat = READY_STAT | DSC_STAT | ERR_STAT;
		ide->error=4;
		ide_raise_irq(ide);
		return;
		case 0xEC:
//                        rpclog("Callback EC\n");
		memset(ide->idebuffer,0,512);
		ide->idebuffer[1] = ide->def_cyl[ide->drive]; /*Cylinders*/
		ide->idebuffer[3] = ide->def_hpc[ide->drive];  /*Heads*/
		ide->idebuffer[6] = ide->def_spt[ide->drive];  /*Sectors*/
		for (addr=10;addr<20;addr++)
		    ide->idebuffer[addr]=0x2020;
		ide->idebuffer[10]=0x3030;
		for (addr=23;addr<47;addr++)
		    ide->idebuffer[addr]=0x2020;
		ide->idebufferb[46^1]='v'; /*Firmware version*/
		ide->idebufferb[47^1]='2';
		ide->idebufferb[48^1]='.';
		ide->idebufferb[49^1]='0';
		ide->idebufferb[54^1]='A'; /*Drive model*/
		ide->idebufferb[55^1]='r';
		ide->idebufferb[56^1]='c';
		ide->idebufferb[57^1]='u';
		ide->idebufferb[58^1]='l';
		ide->idebufferb[59^1]='a';
		ide->idebufferb[60^1]='t';
		ide->idebufferb[61^1]='o';
		ide->idebufferb[62^1]='r';
		ide->idebufferb[63^1]='H';
		ide->idebufferb[64^1]='D';
		ide->idebuffer[50]=0x4000; /*Capabilities*/
		ide->idebuffer[53] = 1;
		ide->idebuffer[54] = ide->cyl[ide->drive];
		ide->idebuffer[55] = ide->hpc[ide->drive];
		ide->idebuffer[56] = ide->spt[ide->drive];
		ide->idebuffer[57] = (ide->cyl[ide->drive] * ide->hpc[ide->drive] * ide->spt[ide->drive]) & 0xffff;
		ide->idebuffer[58] = (ide->cyl[ide->drive] * ide->hpc[ide->drive] * ide->spt[ide->drive]) >> 16;
		ide->pos=0;
		ide->atastat = READY_STAT | DRQ_STAT | DSC_STAT;
//                rpclog("ID callback\n");
		ide_raise_irq(ide);
		return;
	}
}
/*Read 1F1*/
/*Error &108A1 - parameters not recognised*/
