/*

    X68000 custom SASI Hard Disk controller

 0xe96001 (R/W) - SASI data I/O
 0xe96003 (W)   - SEL signal high (0)
 0xe96003 (R)   - SASI status
                  - bit 4 = MSG - if 1, content of data line is a message
                  - bit 3 = Command / Data - if 1, content of data line is a command or status, otherwise it is data.
                  - bit 2 = I/O - if 0, Host -> Controller (Output), otherwise Controller -> Host (Input).
                  - bit 1 = BSY - if 1, HD is busy.
                  - bit 0 = REQ - if 1, host is demanding data transfer to the host.
 0xe96005 (W/O) - data is arbitrary (?)
 0xe96007 (W/O) - SEL signal low (1)

*/

#include "x68k_hdc.h"
#include "imagedev/harddriv.h"
#include "image.h"

const device_type X68KHDC = &device_creator<x68k_hdc_image_device>;

x68k_hdc_image_device::x68k_hdc_image_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, X68KHDC, "SASI Hard Disk", tag, owner, clock, "x68k_hdc_image", __FILE__),
		device_image_interface(mconfig, *this)
{
}

void x68k_hdc_image_device::device_config_complete()
{
	update_names(X68KHDC, "sasihd", "sasi");
}

void x68k_hdc_image_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	m_req = 1;
	m_status_port |= 0x01;
}

void x68k_hdc_image_device::device_start()
{
	m_status = 0x00;
	m_status_port = 0x00;
	m_phase = SASI_PHASE_BUSFREE;
}

bool x68k_hdc_image_device::call_create(int format_type, option_resolution *format_options)
{
	// create 20MB HD
	int x;
	int ret;
	unsigned char sectordata[256];  // empty block data

	memset(sectordata,0,sizeof(sectordata));
	for(x=0;x<0x013c98;x++)  // 0x13c98 = number of blocks on a 20MB HD
	{
		ret = fwrite(sectordata,256);
		if(ret < 256)
			return IMAGE_INIT_FAIL;
	}

	return IMAGE_INIT_PASS;
}

WRITE16_MEMBER( x68k_hdc_image_device::hdc_w )
{
	unsigned int lba = 0;
	dynamic_array<char> blk;
	switch(offset)
	{
	case 0x00:  // data I/O
		if(m_phase == SASI_PHASE_WRITE)
		{
			if(m_transfer_byte_count == 0)
			{
				switch(m_command[0])
				{
				case SASI_CMD_SPECIFY:
					m_transfer_byte_total = 10;
					break;
				case SASI_CMD_WRITE:
					m_transfer_byte_total = (0x100 * m_command[4]);
					break;
				default:
					m_transfer_byte_total = 0x100;
				}
			}

			if(m_command[0] == SASI_CMD_SPECIFY)
			{
				logerror("SPECIFY: wrote 0x%02x\n",data);
			}

			if(m_command[0] == SASI_CMD_WRITE)
			{
				if(!exists())
				{
					m_phase = SASI_PHASE_STATUS;
					m_io = 1;  // Output
					m_status_port |= 0x04;  // C/D remains the same
					m_status = 0x02;
					logerror("SASI: No HD connected.\n");
				}
				else
				{
					fwrite(&data,1);
				}
			}

			m_req = 0;
			m_status_port &= ~0x01;
			timer_set(attotime::from_nsec(450));
			m_transfer_byte_count++;
			if(m_transfer_byte_count >= m_transfer_byte_total)
			{
				// End of transfer
				m_phase = SASI_PHASE_STATUS;
				m_io = 1;
				m_status_port |= 0x04;
				m_cd = 1;
				m_status_port |= 0x08;
				logerror("SASI: Write transfer complete\n");
			}
		}
		if(m_phase == SASI_PHASE_COMMAND)
		{
			if(m_command_byte_count == 0)
			{
				// first command byte
				m_current_command = data;
				switch(data >> 5)  // high 3 bits determine command class, and therefore, length
				{
				case 0:
					m_command_byte_total = 6;
					break;
				case 1:
					m_command_byte_total = 10;
					break;
				case 2:
					m_command_byte_total = 8;
					break;
				default:
					m_command_byte_total = 6;
				}
			}
			m_command[m_command_byte_count] = data;
			// reset REQ temporarily
			m_req = 0;
			m_status_port &= ~0x01;
			timer_set(attotime::from_nsec(450));

			m_command_byte_count++;
			if(m_command_byte_count >= m_command_byte_total)
			{
				// End of command

				switch(m_command[0])
				{
				case SASI_CMD_REZERO_UNIT:
					m_phase = SASI_PHASE_STATUS;
					m_io = 1;  // Output
					m_status_port |= 0x04;  // C/D remains the same
					logerror("SASI: REZERO UNIT\n");
					break;
				case SASI_CMD_REQUEST_SENSE:
					m_phase = SASI_PHASE_READ;
					m_io = 1;
					m_status_port |= 0x04;
					m_cd = 0;
					m_status_port &= ~0x08;
					m_transfer_byte_count = 0;
					m_transfer_byte_total = 0;
					logerror("SASI: REQUEST SENSE\n");
					break;
				case SASI_CMD_SPECIFY:
					m_phase = SASI_PHASE_WRITE;
					m_io = 0;
					m_status_port &= ~0x04;
					m_cd = 0;  // Data
					m_status_port &= ~0x08;
					m_transfer_byte_count = 0;
					m_transfer_byte_total = 0;
					logerror("SASI: SPECIFY\n");
					break;
				case SASI_CMD_READ:
					if(!exists())
					{
						m_phase = SASI_PHASE_STATUS;
						m_io = 1;  // Output
						m_status_port |= 0x04;  // C/D remains the same
						m_cd = 1;
						m_status_port |= 0x08;
						m_status = 0x02;
						logerror("SASI: No HD connected\n");
					}
					else
					{
						m_phase = SASI_PHASE_READ;
						m_io = 1;
						m_status_port |= 0x04;
						m_cd = 0;
						m_status_port &= ~0x08;
						m_transfer_byte_count = 0;
						m_transfer_byte_total = 0;
						lba = m_command[3];
						lba |= m_command[2] << 8;
						lba |= (m_command[1] & 0x1f) << 16;
						fseek(lba * 256,SEEK_SET);
						logerror("SASI: READ (LBA 0x%06x, blocks = %i)\n",lba,m_command[4]);
					}
					break;
				case SASI_CMD_WRITE:
					if(!exists())
					{
						m_phase = SASI_PHASE_STATUS;
						m_io = 1;  // Output
						m_status_port |= 0x04;  // C/D remains the same
						m_cd = 1;
						m_status_port |= 0x08;
						m_status = 0x02;
						logerror("SASI: No HD connected\n");
					}
					else
					{
						m_phase = SASI_PHASE_WRITE;
						m_io = 0;
						m_status_port &= ~0x04;
						m_cd = 0;
						m_status_port &= ~0x08;
						m_transfer_byte_count = 0;
						m_transfer_byte_total = 0;
						lba = m_command[3];
						lba |= m_command[2] << 8;
						lba |= (m_command[1] & 0x1f) << 16;
						fseek(lba * 256,SEEK_SET);
						logerror("SASI: WRITE (LBA 0x%06x, blocks = %i)\n",lba,m_command[4]);
					}
					break;
				case SASI_CMD_SEEK:
						m_phase = SASI_PHASE_STATUS;
						m_io = 1;  // Output
						m_status_port |= 0x04;  // C/D remains the same
						m_cd = 1;
						m_status_port |= 0x08;
						logerror("SASI: SEEK (LBA 0x%06x)\n",lba);
					break;
				case SASI_CMD_FORMAT_UNIT:
				case SASI_CMD_FORMAT_UNIT_06:
					/*
					    Format Unit command format  (differs from SASI spec?)
					    0 |   0x06
					    1 |   Unit number (0-7) | LBA MSB (high 5 bits)
					    2 |   LBA
					    3 |   LBA LSB
					    4 |   ??  (usually 0x01)
					    5 |   ??
					*/
						m_phase = SASI_PHASE_STATUS;
						m_io = 1;  // Output
						m_status_port |= 0x04;  // C/D remains the same
						m_cd = 1;
						m_status_port |= 0x08;
						lba = m_command[3];
						lba |= m_command[2] << 8;
						lba |= (m_command[1] & 0x1f) << 16;
						fseek(lba * 256,SEEK_SET);
						blk.resize_and_clear(256*33);
						// formats 33 256-byte blocks
						fwrite(blk,256*33);
						logerror("SASI: FORMAT UNIT (LBA 0x%06x)\n",lba);
					break;
				default:
					m_phase = SASI_PHASE_STATUS;
					m_io = 1;  // Output
					m_status_port |= 0x04;  // C/D remains the same
					m_status = 0x02;
					logerror("SASI: Invalid or unimplemented SASI command (0x%02x) received.\n",m_command[0]);
				}
			}
		}
		break;
	case 0x01:
		if(data == 0)
		{
			if(m_phase == SASI_PHASE_SELECTION)
			{
				// Go to Command phase
				m_phase = SASI_PHASE_COMMAND;
				m_cd = 1;   // data port expects a command or status
				m_status_port |= 0x08;
				m_command_byte_count = 0;
				m_command_byte_total = 0;
				timer_set(attotime::from_nsec(45));
			}
		}
		break;
	case 0x02:
		break;
	case 0x03:
		if(data != 0)
		{
			if(m_phase == SASI_PHASE_BUSFREE)
			{
				// Go to Selection phase
				m_phase = SASI_PHASE_SELECTION;
				m_bsy = 1;  // HDC is now busy
				m_status_port |= 0x02;
			}
		}
		break;
	}

//  logerror("SASI: write to HDC, offset %04x, data %04x\n",offset,data);
}

READ16_MEMBER( x68k_hdc_image_device::hdc_r )
{
	int retval = 0xff;

	switch(offset)
	{
	case 0x00:
		if(m_phase == SASI_PHASE_MESSAGE)
		{
			m_phase = SASI_PHASE_BUSFREE;
			m_msg = 0;
			m_cd = 0;
			m_io = 0;
			m_bsy = 0;
			m_req = 0;
			m_status = 0;
			m_status_port = 0;  // reset all status bits to 0
			return 0x00;
		}
		if(m_phase == SASI_PHASE_STATUS)
		{
			m_phase = SASI_PHASE_MESSAGE;
			m_msg = 1;
			m_status_port |= 0x10;
			// reset REQ temporarily
			m_req = 0;
			m_status_port &= ~0x01;
			timer_set(attotime::from_nsec(450));

			return m_status;
		}
		if(m_phase == SASI_PHASE_READ)
		{
			if(m_transfer_byte_count == 0)
			{
				switch(m_command[0])
				{
				case SASI_CMD_REQUEST_SENSE:
					// set up sense bytes
					m_sense[0] = 0x01;  // "No index signal"
					m_sense[1] = 0;
					m_sense[2] = 0;
					m_sense[3] = 0;
					if(m_command[3] == 0)
						m_transfer_byte_total = 4;
					else
						m_transfer_byte_total = m_command[3];
					break;
				case SASI_CMD_READ:
					m_transfer_byte_total = (0x100 * m_command[4]);
					m_transfer_byte_count = 0;
					break;
				default:
					m_transfer_byte_total = 0;
				}
			}

			switch(m_command[0])
			{
			case SASI_CMD_REQUEST_SENSE:
				retval = m_sense[m_transfer_byte_count];
				logerror("REQUEST SENSE: read value 0x%02x\n",retval);
				break;
			case SASI_CMD_READ:
				if(!exists())
				{
					m_phase = SASI_PHASE_STATUS;
					m_io = 1;  // Output
					m_status_port |= 0x04;  // C/D remains the same
					m_status = 0x02;
					logerror("SASI: No HD connected.\n");
				}
				else
				{
					unsigned char val;
					fread(&val,1);
					retval = val;
				}
				break;
			default:
				retval = 0;
			}

			m_req = 0;
			m_status_port &= ~0x01;
			timer_set(attotime::from_nsec(450));
			m_transfer_byte_count++;
			if(m_transfer_byte_count >= m_transfer_byte_total)
			{
				// End of transfer
				m_phase = SASI_PHASE_STATUS;
				m_io = 1;
				m_status_port |= 0x04;
				m_cd = 1;
				m_status_port |= 0x08;
				logerror("SASI: Read transfer complete\n");
			}

			return retval;
		}
		return 0x00;
	case 0x01:
//      logerror("SASI: [%08x] read from status port, read 0x%02x\n",activecpu_get_pc(),m_status_port);
		return m_status_port;
	case 0x02:
		return 0xff;  // write-only
	case 0x03:
		return 0xff;  // write-only
	default:
		return 0xff;
	}
}
