/*
 * Western Digital 1770 disc controller emulation
 */

#undef DEBUG
#include "utils.h"
#include "WD1770.h"
#include "DiscEmul.h"

#include "dlc/dlc_register.h"

#include <stdio.h>

#define DATA_CYCLES 200
#define SPINUP_CYCLES 6000
#define SECTOR_SIZE 256

#define STEP_CYCLES ((val & 0x03) == 0 ? 6000 : ((val & 0x03) == 1 ? 12000 : \
	((val & 0x03) == 2 ? 20000 : 30000)))
#define MAKESTATUS status = ((motor ? 0x80 : 0) | (rdonly ? 0x40 : 0) | ((type == 1) ? \
	((spinup ? 0 : 0x20) | ((cur_track == 0) ? 0x4 : 0)) : (data_ready ? 0x2 : 0)) \
	| (busy ? 1 : 0)) | (nf_err ? 0x10 : 0)
#define HANDLESTART1 if (!motor && !(val & 0x08)) spinup = SPINUP_CYCLES;  \
	motor = !(val & 0x08); busy = 1; type = 1

// Use DiscEmul for the params
#define dfile the_disc->dfile
#define nsectors the_disc->nsectors
#define rdonly the_disc->rdonly

WD1770::WD1770 (void): MMIO_Dev (0xfffc, 0xfe84)
{
	// Model B/B+ 1770 upgrade defaults to mask 0xfffc value 0xfe84
	// Electron has &fcc4 mask fffc (don't know about drive control)
	addr.reg_mask = 3;
	flags = MMIO_IS_TICKER;
	the_disc = new DiscEmul (3, "WD1770 disc drives");
	the_disc->flags |= DISC_FLG_WD1770;	// It's for a WD1770
}

WD1770::~WD1770 (void)
{
	delete the_disc;
}

void WD1770::Reset (void)
{
	busy = motor = cycles = data_repeats = data = 0;
	cur_side = cur_track = 0;
	r_status = 0;
	r_track = r_sector = r_data = 0;
	step_dir = 1;
	nf_err = 0;
	spinup = 0;
	data_ready = 0;
	data_read = 0;
	bytes = 0;
	status = 0;
}

extern int trace;

void WD1770::DoTicks (int ticks)
{
	if (busy && !data)
	{
		cycles -= ticks;
		if (cycles <= 0)
		{
			// Command complete
			cycles = 0;
			busy = 0;
			MAKESTATUS;
			SignalInt ();
			return;
		}
	}
	if (spinup > 0 && (spinup -= ticks) <= 0)
	{
		spinup = 0;
		MAKESTATUS;
	}
	// Are we reading?
	if (data_read == 1 && data && (data -= ticks) <= 0)
	{
		dprintf ("Data ready for reading %d %d\n", bytes, buf[bytes]);
		
		// End of the sector?
		if (sector_end)
		{
			sector_end = 0;
			r_sector++;
			if (r_sector == nsectors)
			{
				r_track++;
				r_sector = 0;
				// Can't continue now anyway...
				data_repeats = 0;
				nf_err = 1;
			}
			// Read the next sector?
			if (data_repeats && --data_repeats > 0)
			{
				data = DATA_CYCLES;
				// Read the next sector for further data reads
				dprintf ("// Reading sector %d/%d/%d...\n",
					cur_track, cur_side, r_sector);
				nf_err = 0;
				if (dfile)
					fread (buf, SECTOR_SIZE, 1, dfile);
				bytes = 0;
			}
			else
			{
				// Command ended
				data = 0;
				busy = 0; data_ready = 0;
			}
			MAKESTATUS;
			SignalInt ();
			return;
		}
		
		// Supply the data
		r_data = buf[bytes++];
		data_ready = 1;
		MAKESTATUS;
		SignalInt ();
		
		if (bytes == SECTOR_SIZE)
			// End the command next time
			sector_end = 1;
		
		// Prepare for the next data cycle
		data = DATA_CYCLES;
	}
	else if (data_read == 0 && data && (data -= ticks) <= 0)
	{
		// Writing... request the data then take it
		if (bytes == SECTOR_SIZE)
		{
			// End of the sector
			// First, write out the data we have collected
			if (dfile)
				fwrite (buf, SECTOR_SIZE, 1, dfile);
			
			r_sector++;
			if (r_sector == nsectors)
			{
				r_track++;
				r_sector = 0;
				// Can't continue now anyway...
				data_repeats = 0;
				nf_err = 0;
			}
			// doing another sector?
			if (data_repeats && --data_repeats > 0)
			{
				data = DATA_CYCLES;
				nf_err = 0;
				bytes = 0;
			}
			else
			{
				// Command ended
				data = 0;
				busy = 0; data_ready = 0;
			}
			MAKESTATUS;
			SignalInt ();
			return;
		}
		
		// Request the next piece of data.
		bytes++;
		dprintf ("Write: Requesting %d\n", bytes);
		data_ready = 1;
		MAKESTATUS;
		SignalInt ();
		
		data = DATA_CYCLES;
	}
}

byte WD1770::MMIO_Read (int reg)
{
#ifdef DEBUG
	static int data_rd_num = 0;
#endif
	
	switch (reg)
	{
		case 0:		// Status reg.
		dprintf ("Reading status...\n");
		return status;
		
		case 1:		// Track reg
		dprintf ("Rd track reg %x\n", r_track);
		return r_track;
		
		case 2:
		return r_sector;
		
		case 3:
		dprintf ("read data %x %x\n", data_rd_num++, r_data);
		data_ready = 0;
		MAKESTATUS;
		return r_data;
		
		default:	// Shouldn't happen
		return 0;
	}
}

void WD1770::MMIO_Write (int reg, byte val)
{
	switch (reg)
	{
		case 0:		// Command reg.
		if ((val & 0xf0) == 0)	// Restore
		{
			dprintf ("Restore.\n");
			cycles += (STEP_CYCLES);
			cur_track = 0;
			r_track = 0;
			data = 0;
			HANDLESTART1;
			MAKESTATUS;
		}
		else if ((val & 0xf0) == 0x10)	// Seek
		{
			dprintf ("Seek from %d to %d %d\n", cur_track, r_data,
				r_track);
			//parent->cpu->Trace ();
			if (r_data > cur_track)
				cycles += (r_data - cur_track) * STEP_CYCLES;
			else
				cycles += (cur_track - r_data) * STEP_CYCLES;
			cur_track = r_data;
			r_track = r_data;
			HANDLESTART1;
			MAKESTATUS;
		}
		else if ((val & 0xe0) == 0x20)	// Step
		{
			cycles += STEP_CYCLES;
			cur_track += step_dir;
			if (cur_track < 0)
				cur_track = 0;
			HANDLESTART1;
			MAKESTATUS;
		}
		else if ((val & 0xe0) == 0x40)	// Step in
		{
			step_dir = 1;
			cur_track++;
			if (cur_track < 0)
				cur_track = 0;
			if (val & 0x10)
				r_track = cur_track;
			cycles += STEP_CYCLES;
			HANDLESTART1;
			MAKESTATUS;
		}
		else if ((val & 0xe0) == 0x60)	// Step out
		{
			step_dir = -1;
			cur_track--;
			if (val & 0x10)
				r_track = cur_track;
			cycles += STEP_CYCLES;
			HANDLESTART1;
			MAKESTATUS;
		}
		else if ((val & 0xe0) == 0x80)	// Read sector
		{
			// Read from cur_track/cur_side/cur_sector
			dprintf ("Read sector...\n");
			data_read = 1;
			data = DATA_CYCLES;
			type = 2;
			if (val & 0x10)
			{
				dprintf ("M set!\n");
				data_repeats = nsectors - r_sector;
			}
			else
				data_repeats = 0;
			if (!dfile)
			{
				dprintf ("No dfile!\n");
				nf_err = 1;
				MAKESTATUS;
				SignalInt ();
				return;
			}
			fseek (dfile, ((((cur_side * 80) + cur_track) * nsectors) +
				r_sector) * SECTOR_SIZE, SEEK_SET);
			dprintf ("Reading sector %d/%d/%d @ %x...\n", cur_side, cur_track, r_sector,
				((((cur_side * 80) + cur_track) * nsectors) +
				r_sector) * SECTOR_SIZE);
			if (r_sector == nsectors)
			{
				dprintf ("End of track.\n");
				data = 0;
				data_repeats = 0;
				busy = 0;
				data_ready = 1;
				nf_err = 1;
				cycles = 0;
				motor = 0;
				spinup = 0;
				
				MAKESTATUS;
				SignalInt ();
				return;
			}
			else
				nf_err = 0;
			fread (buf, SECTOR_SIZE, 1, dfile);
			bytes = 0;
			busy = 1;
			sector_end = 0;
			MAKESTATUS;
		}
		else if ((val & 0xe0) == 0xa0)	// Write sector
		{
			// Write to cur_track/cur_side/cur_sector
			if (rdonly)
			{
				// Error
				dprintf ("Can't write -- write-protected.\n");
				nf_err = 0;
				MAKESTATUS;
				SignalInt ();
				return;
			}
			dprintf ("Write %d/%d/%d...\n", cur_track, cur_side, r_sector);
			data_read = 0;
			data = DATA_CYCLES;
			type = 2;
			if (val & 0x10)
				data_repeats = nsectors - r_sector;
			else
				data_repeats = 0;
			if (!dfile)
			{
				nf_err = 0;
				MAKESTATUS;
				SignalInt ();
				return;
			}
			fseek (dfile, ((((cur_side * 80) + cur_track) * nsectors) +
				r_sector) * SECTOR_SIZE, SEEK_SET);
			bytes = 0;
			busy = 1;
			data_ready = 1;
			MAKESTATUS;
		}
		else if ((val & 0xf0) == 0xc0)	// Read address
		{
			dprintf ("Read address\n");
		}
		else if ((val & 0xf0) == 0xe0)	// Read track
		{
			dprintf ("Read track\n");
		}
		else if ((val & 0xf0) == 0xf0)	// Write track
		{
			dprintf ("Write track\n");
			data = DATA_CYCLES * 3;
			data_repeats = nsectors * 1070;
			data_read = 2;	// Format, just ignores everything
		}
		else if ((val & 0xf0) == 0xd0)	// Cause interrupt
		{
			if (val & 0x10)	// Immediate interrupt
				SignalInt ();
			if (busy)
			{
				busy = 0;
				cycles = 0;
				data = 0;
				data_repeats = 0;
			}
		}
		break;

		case 1:		// Track
		dprintf ("Set track reg. %d\n", val);
		r_track = val;
		break;
		
		case 2:		// Sector
		r_sector = val;
		break;
		
		case 3:
		dprintf ("Write data %x\n", val);
		r_data = val;
		if (data_read == 0 && data_ready)
		{
			dprintf ("Received write %x\n", val);
			buf[bytes] = val;
		}
		data_ready = 0;
		MAKESTATUS;
		break;
	}
}

void WD1770::SignalInt (void)
{
	SignalNMI ();
}

DLC_Register (WD1770);

/* End of file. */
