summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/machine/wd1010.cpp
blob: c093c11319b631d17ba26c16af607581b747f735 (plain) (tree)





























































































































































































































































































































































                                                                                                               

                                                       







































































































































































































































                                                                                                     
// license:GPL-2.0+
// copyright-holders:Dirk Best
/***************************************************************************

    Western Digital WD1010-05 Winchester Disk Controller

***************************************************************************/

#include "emu.h"
#include "wd1010.h"

//#define LOG_GENERAL (1U <<  0)
#define LOG_CMD     (1U <<  1)
#define LOG_INT     (1U <<  2)
#define LOG_SEEK    (1U <<  3)
#define LOG_REGS    (1U <<  4)
#define LOG_DATA    (1U <<  5)

//#define VERBOSE  (LOG_CMD | LOG_INT | LOG_SEEK | LOG_REGS | LOG_DATA)
//#define LOG_OUTPUT_STREAM std::cout

#include "logmacro.h"

#define LOGCMD(...)  LOGMASKED(LOG_CMD,  __VA_ARGS__)
#define LOGINT(...)  LOGMASKED(LOG_INT,  __VA_ARGS__)
#define LOGSEEK(...) LOGMASKED(LOG_SEEK, __VA_ARGS__)
#define LOGREGS(...) LOGMASKED(LOG_REGS, __VA_ARGS__)
#define LOGDATA(...) LOGMASKED(LOG_DATA, __VA_ARGS__)


//**************************************************************************
//  DEVICE DEFINITIONS
//**************************************************************************

DEFINE_DEVICE_TYPE(WD1010, wd1010_device, "wd1010", "Western Digital WD1010-05")


//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  wd1010_device - constructor
//-------------------------------------------------

wd1010_device::wd1010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, WD1010, tag, owner, clock),
	m_out_intrq_cb(*this),
	m_out_bcr_cb(*this),
	m_in_data_cb(*this),
	m_out_data_cb(*this),
	m_intrq(0),
	m_brdy(0),
	m_stepping_rate(0x00),
	m_command(0x00),
	m_error(0x00),
	m_precomp(0x00),
	m_sector_count(0x00),
	m_sector_number(0x00),
	m_cylinder(0x0000),
	m_sdh(0x00),
	m_status(0x00)
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void wd1010_device::device_start()
{
	// get connected drives
	for (int i = 0; i < 4; i++)
	{
		char name[2];
		name[0] = '0' + i;
		name[1] = 0;
		m_drives[i].drive = subdevice<harddisk_image_device>(name);
		m_drives[i].head = 0;
		m_drives[i].cylinder = 0;
		m_drives[i].sector = 0;
	}

	// resolve callbacks
	m_out_intrq_cb.resolve_safe();
	m_out_bcr_cb.resolve_safe();
	m_in_data_cb.resolve_safe(0);
	m_out_data_cb.resolve_safe();

	// allocate timer
	m_seek_timer = timer_alloc(TIMER_SEEK);
	m_data_timer = timer_alloc(TIMER_DATA);

	// register for save states
	save_item(NAME(m_intrq));
	save_item(NAME(m_brdy));
	save_item(NAME(m_stepping_rate));
	save_item(NAME(m_command));
	save_item(NAME(m_error));
	save_item(NAME(m_precomp));
	save_item(NAME(m_sector_count));
	save_item(NAME(m_sector_number));
	save_item(NAME(m_cylinder));
	save_item(NAME(m_sdh));
	save_item(NAME(m_status));
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void wd1010_device::device_reset()
{
}

//-------------------------------------------------
//  device_timer - device-specific timer
//-------------------------------------------------

void wd1010_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr)
{
	switch (tid)
	{
	case TIMER_SEEK:

		if ((m_command >> 4) != CMD_SCAN_ID)
		{
			LOGSEEK("Seek complete\n");
			m_drives[drive()].cylinder = param;
			m_status |= STATUS_SC;
		}

		switch (m_command >> 4)
		{
		case CMD_RESTORE:
			cmd_restore();
			break;

		case CMD_SEEK:
			cmd_seek();
			break;

		case CMD_READ_SECTOR:
			cmd_read_sector();
			break;

		case CMD_WRITE_SECTOR:
		case CMD_WRITE_FORMAT:
			cmd_write_sector();
			break;

		case CMD_SCAN_ID:
			cmd_scan_id();
			break;
		}

		break;

	case TIMER_DATA:

		// check if data is ready or continue waiting
		if (m_brdy)
			cmd_write_sector();
		else
			m_data_timer->adjust(attotime::from_usec(35));

		break;
	}
}

//-------------------------------------------------
//  set_error - set error and adjust status
//-------------------------------------------------

void wd1010_device::set_error(int error)
{
	if (error)
	{
		m_error |= error;
		m_status |= STATUS_ERR;
	}
	else
	{
		m_error = 0;
		m_status &= ~STATUS_ERR;
	}
}

//-------------------------------------------------
//  set_intrq - set interrupt status
//-------------------------------------------------

void wd1010_device::set_intrq(int state)
{
	if (m_intrq == 0 && state == 1)
	{
		LOGINT("INT 1\n");
		m_intrq = 1;
		m_out_intrq_cb(1);
	}
	else if (m_intrq == 1 && state == 0)
	{
		LOGINT("INT 0\n");
		m_intrq = 0;
		m_out_intrq_cb(0);
	}
}

//-------------------------------------------------
//  get_stepping_rate - calculate stepping rate
//-------------------------------------------------

attotime wd1010_device::get_stepping_rate()
{
	if (m_stepping_rate)
		return attotime::from_usec(500 * m_stepping_rate);
	else
		return attotime::from_usec(35);
}

//-------------------------------------------------
//  start_command - executed at the start of every command
//-------------------------------------------------

void wd1010_device::start_command()
{
	// now busy and command in progress, clear error
	m_status |= STATUS_BSY;
	m_status |= STATUS_CIP;
	set_error(0);

	switch (m_command >> 4)
	{
	case CMD_RESTORE:
		m_stepping_rate = m_command & 0x0f;
		LOGCMD("RESTORE\n");
		break;
	case CMD_READ_SECTOR:
		LOGCMD("READ SECTOR\n");
		break;
	case CMD_WRITE_SECTOR:
		LOGCMD("WRITE SECTOR\n");
		break;
	case CMD_SCAN_ID:
		LOGCMD("SCAN ID\n");
		break;
	case CMD_WRITE_FORMAT:
		LOGCMD("WRITE FORMAT ID\n");
		break;
	case CMD_SEEK:
		m_stepping_rate = m_command & 0x0f;
		LOGCMD("SEEK\n");
		break;
	}
}

//-------------------------------------------------
//  end_command - executed at end of every command
//-------------------------------------------------

void wd1010_device::end_command()
{
	m_out_bcr_cb(1);
	m_out_bcr_cb(0);

	m_status &= ~(STATUS_BSY | STATUS_CIP);

	set_intrq(1);
}

//-------------------------------------------------
//  get_lbasector - translate to lba
//-------------------------------------------------

int wd1010_device::get_lbasector()
{
	hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
	hard_disk_info *info = hard_disk_get_info(file);
	int lbasector;

	lbasector = m_cylinder;
	lbasector *= info->heads;
	lbasector += head();
	lbasector *= info->sectors;
	lbasector += m_sector_number;

	return lbasector;
}


//**************************************************************************
//  INTERFACE
//**************************************************************************

WRITE_LINE_MEMBER( wd1010_device::drdy_w )
{
	if (state)
		m_status |= STATUS_RDY;
	else
		m_status &= ~STATUS_RDY;
}

WRITE_LINE_MEMBER( wd1010_device::brdy_w )
{
	m_brdy = state;
}

READ8_MEMBER( wd1010_device::read )
{
	// if the controller is busy all reads return the status register
	if (m_status & STATUS_BSY)
	{
		LOG("Read while busy, return STATUS: %02x\n", m_status);
		return m_status;
	}

	switch (offset & 0x07)
	{
	case 0:
		LOGREGS("RD INVALID\n");
		return 0;

	case 1:
		LOGREGS("RD ERROR: %02x\n", m_error);
		return m_error;

	case 2:
		LOGREGS("RD SECTOR COUNT: %02x\n", m_sector_count);
		return m_sector_count;

	case 3:
		LOGREGS("RD SECTOR NUMBER: %02x\n", m_sector_number);
		return m_sector_number;

	case 4:
		LOGREGS("RD CYLINDER L: %02x\n", m_cylinder & 0xff);
		return m_cylinder & 0xff;

	case 5:
		LOGREGS("RD CYLINDER H: %02x\n", m_cylinder >> 8);
		return m_cylinder >> 8;

	case 6:
		LOGREGS("RD SDH: %02x\n", m_sdh);
		return m_sdh;

	case 7:
		LOGREGS("RD STATUS: %02x\n", m_status);

		// reading the status register clears the interrupt
		if (!machine().side_effects_disabled())
			set_intrq(0);

		return m_status;
	}

	// should never get here
	return 0xff;
}

WRITE8_MEMBER( wd1010_device::write )
{
	switch (offset & 0x07)
	{
	case 0:
		LOGREGS("WR INVALID: %02x\n", data);
		break;

	case 1:
		LOGREGS("WR PRECOMP: %02x\n", data);
		m_precomp = data;
		break;

	case 2:
		LOGREGS("WR SECTOR COUNT: %02x\n", data);
		m_sector_count = data;
		break;

	case 3:
		LOGREGS("WR SECTOR NUMBER: %02x\n", data);
		m_sector_number = data;
		break;

	case 4:
		LOGREGS("WR CYLINDER L: %02x\n", data);
		m_cylinder = (m_cylinder & 0xff00) | (data << 0);
		break;

	case 5:
		LOGREGS("WR CYLINDER H: %02x\n", data);
		m_cylinder = (m_cylinder & 0x00ff) | (data << 8);
		break;

	case 6:
		LOGREGS("WR SDH: %02x\n", data);
		m_sdh = data;
		break;

	case 7:
		// writes to the command register are ignored when a command is in progress
		if (m_status & STATUS_CIP)
			return;

		// writing the command register clears the interrupt
		set_intrq(0);

		// check drive status
		if (!(m_status & STATUS_RDY))
		{
			// selected drive not ready
			LOG("--> Drive not ready, aborting\n");

			set_error(ERR_AC);
			end_command();
		}
		else
		{
			m_command = data;

			start_command();

			// all other command imply a seek
			if ((m_command >> 4) != CMD_SCAN_ID)
				m_status &= ~STATUS_SC;

			int amount = 0;
			int target = 0;

			switch (m_command >> 4)
			{
			case CMD_RESTORE:
				amount = m_drives[drive()].cylinder;
				target = 0;
				break;

			case CMD_SEEK:
			case CMD_READ_SECTOR:
			case CMD_WRITE_SECTOR:
			case CMD_WRITE_FORMAT:
				amount = abs(m_drives[drive()].cylinder - m_cylinder);
				target = m_cylinder;
				break;
			}

			if ((m_command >> 4) != CMD_SCAN_ID)
				LOGSEEK("Seeking %d cylinders to %d\n", amount, target);

			m_seek_timer->adjust(get_stepping_rate() * amount, target);
		}

		break;
	}
}


//**************************************************************************
//  IMPLEMENTATION
//**************************************************************************

void wd1010_device::cmd_restore()
{
	LOGCMD("--> RESTORE done\n");
	end_command();
}

void wd1010_device::cmd_read_sector()
{
	hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
	hard_disk_info *info = hard_disk_get_info(file);

	m_out_bcr_cb(1);
	m_out_bcr_cb(0);

	// verify that we can read
	if (head() > info->heads)
	{
		// out of range
		LOG("--> Head out of range, aborting\n");

		set_error(ERR_AC);
		end_command();
	}
	else
	{
		uint8_t buffer[512];

		while (m_sector_count > 0)
		{
			LOGDATA("--> Transferring sector to buffer (lba = %08x)\n", get_lbasector());

			hard_disk_read(file, get_lbasector(), buffer);

			for (int i = 0; i < 512; i++)
				m_out_data_cb(buffer[i]);

			m_out_bcr_cb(1);
			m_out_bcr_cb(0);

			// save last read head and sector number
			m_drives[drive()].head = head();
			m_drives[drive()].sector = m_sector_number;

			if (BIT(m_command, 2))
			{
				m_sector_number++;
				m_sector_count--;
			}
			else
				break;
		}

		end_command();
	}
}

void wd1010_device::cmd_write_sector()
{
	if (!(m_status & STATUS_DRQ))
	{
		LOGDATA("Setting DATA REQUEST\n");
		m_status |= STATUS_DRQ;
		m_data_timer->adjust(attotime::from_usec(35));
		return;
	}

	if (m_brdy == 0)
	{
		m_data_timer->adjust(attotime::from_usec(35));
		return;
	}

	LOGDATA("Clearing DATA REQUEST\n");
	m_status &= ~STATUS_DRQ;

	hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
	uint8_t buffer[512];

	while (m_sector_count > 0)
	{
		if ((m_command >> 4) == CMD_WRITE_FORMAT)
		{
			// we ignore the format specification and fill everything with 0xe5
			std::fill(std::begin(buffer), std::end(buffer), 0xe5);
		}
		else
		{
			// get data for sector from buffer chip
			for (int i = 0; i < 512; i++)
				buffer[i] = m_in_data_cb();
		}

		hard_disk_write(file, get_lbasector(), buffer);

		// save last read head and sector number
		m_drives[drive()].head = head();
		m_drives[drive()].sector = m_sector_number;

		if (BIT(m_command, 2))
		{
			m_sector_number++;
			m_sector_count--;
		}
		else
			break;
	}

	end_command();
}

void wd1010_device::cmd_scan_id()
{
	// update head, sector size, sector number and cylinder
	m_sdh = (m_sdh & 0xf8) | (m_drives[drive()].head & 0x07);
	m_sector_number = m_drives[drive()].sector;
	m_cylinder = m_drives[drive()].cylinder;

	end_command();
}

void wd1010_device::cmd_seek()
{
	LOGCMD("--> SEEK done\n");
	end_command();
}