summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/machine/ncr5385.cpp
blob: fe76078fa9512bc2aa40f64b87a057803b02e67d (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11

                               
 







                                                                                 


                              



                    










                                                                                           
 
                                                         






                                                  



                                                                                                          
                      


                             


 




































































                                                      

                                   











                                      
                                 
                                
                               






                                                                                                     



                                   










                                                
                      
 

                                                                        

                     

 
                                        
 



                                                                                                                                       

                                              

                                                                                                                       



                                                              


                                                                                               
         






























                                                                                         
         
                                                      



                                                              











































































































                                                                                              
                              























































                                                                                                                                                      
                              

                                                                                 
                              

                                                                
                              









                                                                             



                                                                                                                                  
                                            
                                               


                                                              



                                                                                                                                 
                                            
                                               







                                                              


         







                                                                                       
 






                                                                                          




                                                    

















































                                                                                       
         


































































                                                                                
                                         






































                                                                                    

                                                                                              











                                                                                    
                                              



                                                                    
                                   

                                 
                                                                            


                                                     
                                                                            












                                                                             
                                                                                                                         






                                                                

                                   













                                                                     
                                               


                                        
                                                                                      



                                                                           

                                              

                                                                                  
                                                                                    








                                                                       

                                   




                                 

                                                                            


                                                      
                                                                            


                                                       

                                                                           


                            
                                                                                                                          





                                                                

                                   








                                                                      
                                                                             






                                                                           



                                                      
 
                                               


                                        
                                                                                       



                                                                           

                                              




                                                               

                                   



                                 

                                                                            


                                                      
                                                                                                                          











                                                                
















                                                                            



































                                                                                  

         
// license:BSD-3-Clause
// copyright-holders:Ryan Holtz

/*
 * NCR 5385 SCSI Protocol Controller
 *
 * Sources:
 *  - NCR 5385 SCSI Protocol Controller, 1983, NCR Corporation, Dayton, Ohio, USA
 *  - NCR SCSI Engineering Notebook, 1984, NCR Microelectronics
 *
 * TODO:
 *  - target mode send/receive
 *  - disconnect/reselection
 */

#include "emu.h"
#include "ncr5385.h"

#define LOG_GENERAL  (1U << 0)
#define LOG_REGW     (1U << 1)
#define LOG_REGR     (1U << 2)
#define LOG_STATE    (1U << 3)
#define LOG_DMA      (1U << 4)
#define LOG_COMMAND  (1U << 5)

//#define VERBOSE (LOG_GENERAL|LOG_REGW|LOG_REGR|LOG_STATE|LOG_DMA|LOG_COMMAND)
#include "logmacro.h"

DEFINE_DEVICE_TYPE(NCR5385, ncr5385_device, "ncr5385", "NCR 5385 SCSI Protocol Controller")

// FIXME: would be better to reuse from nscsi_full_device
unsigned constexpr SCSI_ARB_DELAY = 2'400;
unsigned constexpr SCSI_BUS_CLEAR = 800;
unsigned constexpr SCSI_BUS_FREE = 800;
unsigned constexpr SCSI_BUS_SETTLE = 400;
unsigned constexpr SCSI_BUS_SKEW = 10;
unsigned constexpr SCSI_RST_HOLD = 25'000;
unsigned constexpr SCSI_SEL_TIMEOUT = 250'000'000;

ncr5385_device::ncr5385_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
	: nscsi_device(mconfig, NCR5385, tag, owner, clock)
	, nscsi_slot_card_interface(mconfig, *this, DEVICE_SELF)
	, m_int(*this)
	, m_dreq(*this)
	, m_int_state(false)
	, m_dreq_state(false)
{
}

enum state : u32
{
	IDLE,
	DIAGNOSTIC,

	ARB_BUS_FREE,
	ARB_START,
	ARB_EVALUATE,
	SEL_START,
	SEL_DELAY,
	SEL_WAIT_BSY,
	SEL_COMPLETE,
	SEL_WAIT_REQ,

	XFI_START,
	XFI_IN_REQ,
	XFI_IN_DRQ,
	XFI_IN_ACK,
	XFI_OUT_REQ,
	XFI_OUT_DRQ,
	XFI_OUT_ACK,
	XFI_OUT_PAD,
};

enum mode : u8
{
	DISCONNECTED,
	INITIATOR,
	TARGET,
};

enum diag_mask : u8
{
	DIAG_SELF = 0x07, // self-diagnostic status
	DIAG_CMD  = 0x38, // diagnostic command status
	DIAG_DONE = 0x80, // self-diagnostic complete

	DIAG_CMD_GP = 0x18, // diagnostic good parity
	DIAG_CMD_BP = 0x20, // diagnostic bad parity
};

enum int_mask : u8
{
	INT_FUNC_COMPLETE = 0x01,
	INT_BUS_SERVICE   = 0x02,
	INT_DISCONNECTED  = 0x04,
	INT_SELECTED      = 0x08,
	INT_RESELECTED    = 0x10,
	INT_INVALID_CMD   = 0x40,
};

enum aux_status_mask : u8
{
	AUX_STATUS_TC_ZERO    = 0x02,
	AUX_STATUS_PAUSED     = 0x04,
	AUX_STATUS_IO         = 0x08,
	AUX_STATUS_CD         = 0x10,
	AUX_STATUS_MSG        = 0x20,
	AUX_STATUS_PARITY_ERR = 0x40,
	AUX_STATUS_DATA_FULL  = 0x80,
};

enum cmd_mask : u8
{
	CMD_INT = 0x08, // interrupting
	CMD_SBX = 0x40, // single byte transfer
	CMD_DMA = 0x80, // DMA mode
};

void ncr5385_device::device_start()
{
	save_item(NAME(m_dat));
	save_item(NAME(m_cmd));
	save_item(NAME(m_ctl));
	save_item(NAME(m_dst_id));
	save_item(NAME(m_aux_status));
	save_item(NAME(m_own_id));
	save_item(NAME(m_int_status));
	save_item(NAME(m_src_id));
	save_item(NAME(m_dia_status));
	save_item(NAME(m_cnt));

	save_item(NAME(m_state));
	save_item(NAME(m_phase));
	save_item(NAME(m_mode));
	save_item(NAME(m_sbx));

	save_item(NAME(m_int_state));
	save_item(NAME(m_dreq_state));

	m_state_timer = timer_alloc(timer_expired_delegate(FUNC(ncr5385_device::state_timer), this));

	m_dia_status = DIAG_DONE;
}

void ncr5385_device::device_reset()
{
	m_cmd = 0;
	m_ctl = 0;
	m_dst_id = 0;
	m_aux_status = AUX_STATUS_TC_ZERO;
	m_int_status = 0;
	m_src_id = 0;
	m_dia_status &= (DIAG_DONE | DIAG_SELF);
	m_cnt = 0;

	m_state = IDLE;
	m_mode = DISCONNECTED;
	m_sbx = false;

	// monitor all control lines (device has no RST line)
	scsi_bus->ctrl_wait(scsi_refid, S_ALL & ~S_RST, S_ALL & ~S_RST);

	update_int();
}

void ncr5385_device::scsi_ctrl_changed()
{
	u32 const ctrl = scsi_bus->ctrl_r();

	static char const *const nscsi_phase[] = { "DATA OUT", "DATA IN", "COMMAND", "STATUS", "*", "*", "MESSAGE OUT", "MESSAGE IN" };

	if ((ctrl & S_BSY) && !(ctrl & S_SEL))
	{
		LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x phase %s%s%s\n", ctrl, nscsi_phase[ctrl & S_PHASE_MASK],
			ctrl & S_REQ ? " REQ" : "", ctrl & S_ACK ? " ACK" : "");

		if (m_state != IDLE)
			m_state_timer->adjust(attotime::zero);
	}
	else if (ctrl & S_BSY)
		LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x arbitration/selection\n", ctrl);
	else
	{
		LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x BUS FREE\n", ctrl);

		if (m_mode == INITIATOR)
		{
			m_mode = DISCONNECTED;
			m_int_status |= INT_DISCONNECTED;
			update_int();
		}
	}
}

void ncr5385_device::map(address_map &map)
{
	map(0x0, 0x0).rw(FUNC(ncr5385_device::dat_r), FUNC(ncr5385_device::dat_w));
	map(0x1, 0x1).rw(FUNC(ncr5385_device::cmd_r), FUNC(ncr5385_device::cmd_w));
	map(0x2, 0x2).rw(FUNC(ncr5385_device::ctl_r), FUNC(ncr5385_device::ctl_w));
	map(0x3, 0x3).rw(FUNC(ncr5385_device::dst_id_r), FUNC(ncr5385_device::dst_id_w));
	map(0x4, 0x4).r(FUNC(ncr5385_device::aux_status_r));
	map(0x5, 0x5).r(FUNC(ncr5385_device::own_id_r));
	map(0x6, 0x6).r(FUNC(ncr5385_device::int_status_r));
	map(0x7, 0x7).r(FUNC(ncr5385_device::src_id_r));
	map(0x9, 0x9).r(FUNC(ncr5385_device::dia_status_r));
	map(0xc, 0xc).rw(FUNC(ncr5385_device::cnt_r<2>), FUNC(ncr5385_device::cnt_w<2>));
	map(0xd, 0xd).rw(FUNC(ncr5385_device::cnt_r<1>), FUNC(ncr5385_device::cnt_w<1>));
	map(0xe, 0xe).rw(FUNC(ncr5385_device::cnt_r<0>), FUNC(ncr5385_device::cnt_w<0>));
	map(0xf, 0xf).rw(FUNC(ncr5385_device::tst_r), FUNC(ncr5385_device::tst_w));
}

u8 ncr5385_device::dat_r()
{
	if (m_aux_status & AUX_STATUS_DATA_FULL)
	{
		m_aux_status &= ~AUX_STATUS_DATA_FULL;

		if (m_state != IDLE)
			m_state_timer->adjust(attotime::zero);
	}
	else
		logerror("data register empty (%s)\n", machine().describe_context());

	return m_dat;
}

u8 ncr5385_device::cmd_r()
{
	return m_cmd;
}

u8 ncr5385_device::ctl_r()
{
	return m_ctl;
}

u8 ncr5385_device::dst_id_r()
{
	return m_dst_id;
}

u8 ncr5385_device::aux_status_r()
{
	u8 data = m_aux_status;

	if (!m_int_status)
	{
		// return current phase
		u32 const ctrl = scsi_bus->ctrl_r();
		if (ctrl & S_MSG)
			data |= AUX_STATUS_MSG;
		if (ctrl & S_CTL)
			data |= AUX_STATUS_CD;
		if (ctrl & S_INP)
			data |= AUX_STATUS_IO;
	}
	LOGMASKED(LOG_REGR, "aux_status_r 0x%02x (%s)\n", data, machine().describe_context());

	return data;
}

u8 ncr5385_device::own_id_r()
{
	return m_own_id;
}

u8 ncr5385_device::int_status_r()
{
	u8 const data = m_int_status;
	LOGMASKED(LOG_REGR, "int_status_r 0x%02x (%s)\n", data, machine().describe_context());
	m_aux_status &= ~AUX_STATUS_PARITY_ERR;
	m_int_status = 0;
	update_int();

	if (m_state != IDLE)
		m_state_timer->adjust(attotime::zero);

	return data;
}

u8 ncr5385_device::src_id_r()
{
	return m_src_id;
}

u8 ncr5385_device::dia_status_r()
{
	return m_dia_status;
}

template <unsigned N> u8 ncr5385_device::cnt_r()
{
	return u8(m_cnt >> (N * 8));
}

u8 ncr5385_device::tst_r()
{
	return 0;
}

void ncr5385_device::dat_w(u8 data)
{
	LOGMASKED(LOG_REGW, "dat_w 0x%02x (%s)\n", data, machine().describe_context());

	if (!(m_aux_status & AUX_STATUS_DATA_FULL))
	{
		m_dat = data;
		m_aux_status |= AUX_STATUS_DATA_FULL;

		if (m_state != IDLE)
			m_state_timer->adjust(attotime::zero);
	}
	else
		logerror("data register full\n");
}

void ncr5385_device::cmd_w(u8 data)
{
	LOGMASKED(LOG_REGW, "cmd_w 0x%02x (%s)\n", data, machine().describe_context());
	if (!(data & 0x18))
	{
		// immediate commands

		switch (data & 0x1f)
		{
		case 0x00:
			LOGMASKED(LOG_COMMAND, "reset\n");
			reset();
			break;
		case 0x01: // disconnect
			LOGMASKED(LOG_COMMAND, "disconnect\n");
			m_mode = DISCONNECTED;
			break;
		case 0x02: // pause
			LOGMASKED(LOG_COMMAND, "pause\n");
			break;
		case 0x03: // set atn
			LOGMASKED(LOG_COMMAND, "set atn\n");
			scsi_bus->ctrl_w(scsi_refid, S_ATN, S_ATN);
			break;
		case 0x04: // message accepted
			LOGMASKED(LOG_COMMAND, "message accepted\n");
			scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
			break;
		case 0x05: // chip disabled
			LOGMASKED(LOG_COMMAND, "chip disabled\n");
			break;
		case 0x06: case 0x07:
			// reserved
			break;
		}
	}
	else
	{
		// interrupting commands
		m_aux_status &= ~AUX_STATUS_DATA_FULL;
		m_cmd = data;

		switch (data & 0x1f)
		{
		case 0x08: // select w/atn
			LOGMASKED(LOG_COMMAND, "select %d w/atn (timeout %d)\n", m_dst_id, attotime::from_ticks(m_cnt * 1024, clock()).to_string());
			m_state = ARB_BUS_FREE;
			m_state_timer->adjust(attotime::zero);
			break;
		case 0x09: // select w/o atn
			LOGMASKED(LOG_COMMAND, "select %d w/o atn (timeout %d)\n", m_dst_id, attotime::from_ticks(m_cnt * 1024, clock()).to_string());
			m_state = ARB_BUS_FREE;
			m_state_timer->adjust(attotime::zero);
			break;
		case 0x0a: // reselect
			LOGMASKED(LOG_COMMAND, "reselect\n");
			break;
		case 0x0b: // diagnostic
			LOGMASKED(LOG_COMMAND, "diagnostic (%s parity)\n", BIT(data, 6) ? "bad" : "good");
			m_state = DIAGNOSTIC;
			break;
		case 0x0c: // receive command
			LOGMASKED(LOG_COMMAND, "receive command\n");
			break;
		case 0x0d: // receive data
			LOGMASKED(LOG_COMMAND, "receive data\n");
			break;
		case 0x0e: // receive message out
			LOGMASKED(LOG_COMMAND, "receive message out\n");
			break;
		case 0x0f: // receive unspecified info out
			LOGMASKED(LOG_COMMAND, "receive unspecified info out\n");
			break;
		case 0x10: // send status
			LOGMASKED(LOG_COMMAND, "send status\n");
			break;
		case 0x11: // send data
			LOGMASKED(LOG_COMMAND, "send data\n");
			break;
		case 0x12: // send message in
			LOGMASKED(LOG_COMMAND, "send message in\n");
			break;
		case 0x13: // send unspecified info in
			LOGMASKED(LOG_COMMAND, "send unspecified info in\n");
			break;
		case 0x14: // transfer info
			if (data & CMD_SBX)
				LOGMASKED(LOG_COMMAND, "transfer info (%s, single byte)\n", (data & CMD_DMA) ? "dma" : "pio");
			else
				LOGMASKED(LOG_COMMAND, "transfer info (%s, count=%d)\n", (data & CMD_DMA) ? "dma" : "pio", m_cnt);
			m_state = XFI_START;
			m_sbx = data & CMD_SBX;
			m_state_timer->adjust(attotime::zero);
			break;
		case 0x15: // transfer pad
			if (data & CMD_SBX)
				LOGMASKED(LOG_COMMAND, "transfer pad (%s, single byte)\n", (data & CMD_DMA) ? "dma" : "pio");
			else
				LOGMASKED(LOG_COMMAND, "transfer pad (%s, count=%d)\n", (data & CMD_DMA) ? "dma" : "pio", m_cnt);
			m_state = XFI_START;
			m_sbx = data & CMD_SBX;
			m_state_timer->adjust(attotime::zero);
			break;
		case 0x16: case 0x17:
		case 0x18: case 0x19: case 0x1a: case 0x1b:
		case 0x1c: case 0x1d: case 0x1e: case 0x1f:
			// reserved
			break;
		}
	}
}

void ncr5385_device::ctl_w(u8 data)
{
	LOGMASKED(LOG_REGW, "ctl_w 0x%02x (%s)\n", data, machine().describe_context());

	m_ctl = data & 7;
}

void ncr5385_device::dst_id_w(u8 data)
{
	LOGMASKED(LOG_REGW, "dst_id_w 0x%02x (%s)\n", data, machine().describe_context());
	m_dst_id = (data & 7);
}

template <unsigned N> void ncr5385_device::cnt_w(u8 data)
{
	m_cnt = (m_cnt & ~(u32(0xff) << (N * 8))) | (u32(data) << (N * 8));

	if (m_cnt)
		m_aux_status &= ~AUX_STATUS_TC_ZERO;
	else
		m_aux_status |= AUX_STATUS_TC_ZERO;
}

void ncr5385_device::tst_w(u8 data)
{
	LOGMASKED(LOG_REGW, "tst_w 0x%02x (%s)\n", data, machine().describe_context());
}

u8 ncr5385_device::dma_r()
{
	u8 const data = m_dat;
	m_aux_status &= ~AUX_STATUS_DATA_FULL;

	set_dreq(false);
	m_state_timer->adjust(attotime::zero);

	return data;
}

void ncr5385_device::dma_w(u8 data)
{
	m_dat = data;
	m_aux_status |= AUX_STATUS_DATA_FULL;

	set_dreq(false);
	m_state_timer->adjust(attotime::zero);
}

void ncr5385_device::state_timer(s32 param)
{
	// step state machine
	int const delay = state_step();

	// check for data stall
	if (delay < 0)
		return;

	// repeat until idle
	if (m_state != IDLE)
		m_state_timer->adjust(attotime::from_nsec(delay));
}

int ncr5385_device::state_step()
{
	u32 const ctrl = scsi_bus->ctrl_r();
	int delay = 0;

	u8 const oid = 1 << m_own_id;
	u8 const tid = 1 << m_dst_id;

	switch (m_state)
	{
	case DIAGNOSTIC:
		m_dia_status &= DIAG_DONE | DIAG_SELF;
		if (BIT(m_cmd, 6))
		{
			m_aux_status |= AUX_STATUS_PARITY_ERR;
			m_dia_status |= DIAG_CMD_BP;
		}
		else
		{
			m_aux_status &= ~AUX_STATUS_PARITY_ERR;
			m_dia_status |= DIAG_CMD_GP;
		}

		m_int_status |= INT_FUNC_COMPLETE;
		m_state = IDLE;

		update_int();
		break;

	case ARB_BUS_FREE:
		LOGMASKED(LOG_STATE, "arbitration: waiting for bus free\n");
		if (!(ctrl & (S_SEL | S_BSY | S_RST)))
		{
			m_state = ARB_START;
			delay = SCSI_BUS_FREE;
		}
		break;
	case ARB_START:
		LOGMASKED(LOG_STATE, "arbitration: started\n");
		m_state = ARB_EVALUATE;
		delay = SCSI_ARB_DELAY;

		// assert own ID and BSY
		scsi_bus->data_w(scsi_refid, oid);
		scsi_bus->ctrl_w(scsi_refid, S_BSY, S_BSY);
		break;
	case ARB_EVALUATE:
		// check if SEL asserted, or if there's a higher ID on the bus
		if ((ctrl & S_SEL) || (scsi_bus->data_r() & ~((oid - 1) | oid)))
		{
			LOGMASKED(LOG_STATE, "arbitration: lost\n");
			m_state = ARB_BUS_FREE;

			// clear data and BSY
			scsi_bus->data_w(scsi_refid, 0);
			scsi_bus->ctrl_w(scsi_refid, 0, S_BSY);
		}
		else
		{
			LOGMASKED(LOG_STATE, "arbitration: won\n");
			m_state = SEL_START;
			delay = SCSI_BUS_CLEAR + SCSI_BUS_SETTLE;
		}
		break;

	case SEL_START:
		LOGMASKED(LOG_STATE, "selection: SEL asserted\n");
		m_state = SEL_DELAY;
		delay = SCSI_BUS_SKEW * 2;

		// assert own and target ID and SEL
		scsi_bus->data_w(scsi_refid, oid | tid);
		scsi_bus->ctrl_w(scsi_refid, S_SEL, S_SEL);
		break;
	case SEL_DELAY:
		LOGMASKED(LOG_STATE, "selection: BSY cleared\n");
		m_state = SEL_WAIT_BSY;
		delay = SCSI_SEL_TIMEOUT;

		// clear BSY, optionally assert ATN
		if (!BIT(m_cmd, 0))
			scsi_bus->ctrl_w(scsi_refid, S_ATN, S_BSY | S_ATN);
		else
			scsi_bus->ctrl_w(scsi_refid, 0, S_BSY);
		break;
	case SEL_WAIT_BSY:
		if (ctrl & S_BSY)
		{
			LOGMASKED(LOG_STATE, "selection: BSY asserted by target\n");
			m_state = SEL_COMPLETE;
			delay = SCSI_BUS_SKEW * 2;
		}
		else
		{
			LOGMASKED(LOG_STATE, "selection: timed out\n");
			m_int_status |= INT_DISCONNECTED;
			m_state = IDLE;

			scsi_bus->ctrl_w(scsi_refid, 0, S_ATN | S_SEL);

			update_int();
		}
		break;
	case SEL_COMPLETE:
		LOGMASKED(LOG_STATE, "selection: complete\n");
		m_int_status |= INT_FUNC_COMPLETE;
		m_mode = INITIATOR;
		m_state = SEL_WAIT_REQ;
		delay = -1;

		update_int();

		// clear data and SEL
		scsi_bus->data_w(scsi_refid, 0);
		scsi_bus->ctrl_w(scsi_refid, 0, S_SEL);
		break;
	case SEL_WAIT_REQ:
		// don't generate bus service interrupt until the function complete is cleared
		if ((ctrl & S_REQ) && !m_int_state)
		{
			LOGMASKED(LOG_STATE, "selection: REQ asserted by target\n");
			m_int_status |= INT_BUS_SERVICE;
			m_state = IDLE;

			update_int();
		}
		else
			delay = -1;
		break;

	case XFI_START:
		m_phase = ctrl & S_PHASE_MASK;
		m_state = (ctrl & S_INP) ? XFI_IN_REQ : XFI_OUT_REQ;
		break;

	case XFI_IN_REQ:
		// TODO: disconnect
		if (ctrl & S_REQ)
		{
			if (remaining() && (ctrl & S_PHASE_MASK) == m_phase)
			{
				m_state = XFI_IN_DRQ;

				// transfer pad in doesn't transfer any data
				if (!BIT(m_cmd, 0))
				{
					m_aux_status |= AUX_STATUS_DATA_FULL;
					m_dat = scsi_bus->data_r();

					if (m_cmd & CMD_DMA)
						set_dreq(true);

					delay = -1;
				}
			}
			else
			{
				LOGMASKED(LOG_STATE, "xfi_in: %s\n", remaining() ? "phase change" : "transfer complete");

				m_int_status |= INT_BUS_SERVICE;
				m_state = IDLE;

				update_int();
			}
		}
		else
			delay = -1;
		break;
	case XFI_IN_DRQ:
		m_state = XFI_IN_ACK;

		LOGMASKED(LOG_STATE, "xfi_in: data 0x%02x\n", m_dat);

		// assert ACK
		scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK);
		break;
	case XFI_IN_ACK:
		if (!(ctrl & S_REQ))
		{
			m_state = XFI_IN_REQ;

			if (!(m_cmd & CMD_SBX))
			{
				m_cnt--;

				LOGMASKED(LOG_STATE, "xfi_in: %d remaining\n", m_cnt);

				if (!m_cnt)
					m_aux_status |= AUX_STATUS_TC_ZERO;
			}
			else
				m_sbx = false;

			// clear ACK except after last byte of message input phase
			if (!remaining() && (ctrl & S_PHASE_MASK) == S_PHASE_MSG_IN)
			{
				m_int_status |= INT_FUNC_COMPLETE;
				m_state = IDLE;

				update_int();
			}
			else
				scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
		}
		else
			delay = -1;
		break;

	case XFI_OUT_REQ:
		if (ctrl & S_REQ)
		{
			// TODO: disconnect
			if (remaining() && (ctrl & S_PHASE_MASK) == m_phase)
			{
				m_state = XFI_OUT_DRQ;

				// FIXME: only one byte dma for transfer pad
				if (m_cmd & CMD_DMA)
					set_dreq(true);

				if (!(m_aux_status & AUX_STATUS_DATA_FULL))
					delay = -1;
			}
			else
			{
				LOGMASKED(LOG_STATE, "xfi_out: %s\n", remaining() ? "phase change" : "transfer complete");
				m_int_status |= INT_BUS_SERVICE;
				m_state = IDLE;

				update_int();
			}
		}
		else
			delay = -1;
		break;
	case XFI_OUT_DRQ:
		m_state = XFI_OUT_ACK;
		m_aux_status &= ~AUX_STATUS_DATA_FULL;

		LOGMASKED(LOG_STATE, "xfi_out: data 0x%02x\n", m_dat);

		// assert data and ACK
		scsi_bus->data_w(scsi_refid, m_dat);
		if (remaining(1) && (ctrl & S_PHASE_MASK) == S_PHASE_MSG_OUT)
			scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK | S_ATN);
		else
			scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK);
		break;
	case XFI_OUT_ACK:
		if (!(ctrl & S_REQ))
		{
			if (BIT(m_cmd, 0))
				m_state = XFI_OUT_PAD;
			else
				m_state = XFI_OUT_REQ;

			if (!(m_cmd & CMD_SBX))
			{
				m_cnt--;

				LOGMASKED(LOG_STATE, "xfi_out: %d remaining\n", m_cnt);

				if (!m_cnt)
					m_aux_status |= AUX_STATUS_TC_ZERO;
			}
			else
				m_sbx = false;

			// clear data and ACK
			scsi_bus->data_w(scsi_refid, 0);
			scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
		}
		else
			delay = -1;
		break;
	case XFI_OUT_PAD:
		if (ctrl & S_REQ)
		{
			// TODO: disconnect
			if (remaining() && (ctrl & S_PHASE_MASK) == m_phase)
				m_state = XFI_OUT_DRQ;
			else
			{
				LOGMASKED(LOG_STATE, "xfi_out: %s\n", remaining() ? "phase change" : "transfer complete");
				m_int_status |= INT_BUS_SERVICE;
				m_state = IDLE;

				update_int();
			}
		}
		break;
	}

	return delay;
}

/*
 * Returns a boolean indicating whether any data remains to be transferred:
 *
 *  - for single byte transfer commands, m_sbx indicates data remaining
 *  - alternatively, return if there's a specific amount of data to transfer
 *  - otherwise, check if there's any data to transfer
 */
bool ncr5385_device::remaining(u32 const count) const
{
	if (m_cmd & CMD_SBX)
		return m_sbx;
	else if (count)
		return m_cnt == count;
	else
		return m_cnt;
}

void ncr5385_device::set_dreq(bool dreq)
{
	if (m_dreq_state != dreq)
	{
		LOGMASKED(LOG_DMA, "set_dreq %d\n", dreq);

		m_dreq_state = dreq;
		m_dreq(m_dreq_state);
	}
}

void ncr5385_device::update_int()
{
	bool const int_state = m_int_status & 0x5f;

	if (m_int_state != int_state)
	{
		LOG("update_int %d\n", int_state);

		m_aux_status &= ~(AUX_STATUS_MSG | AUX_STATUS_CD | AUX_STATUS_IO);
		if (int_state)
		{
			m_cmd = 0;

			// latch current phase
			u32 const ctrl = scsi_bus->ctrl_r();
			if (ctrl & S_MSG)
				m_aux_status |= AUX_STATUS_MSG;
			if (ctrl & S_CTL)
				m_aux_status |= AUX_STATUS_CD;
			if (ctrl & S_INP)
				m_aux_status |= AUX_STATUS_IO;
		}

		m_int_state = int_state;
		m_int(m_int_state);
	}
}