summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/machine/aic6250.cpp
blob: 6f08895ac44dec93deefe46ee68eb8395bddcc66 (plain) (tree)






















                                                                           
                                                         






                          










                                                                                     

                                                                                                              



                                                                                                                                 

                                                                                                                                 
                                                                

                          
                                    
                              
                                    



                              









                                                                                                                 



















                                                                                                           
                                                            






                                      







                                                                                                                  

















                                                                       
                                                  




                                      







                                                                                                                
















                                                         



















                                           
                                                                            















































                                                                                
                           

















































































                                                                                                                 

                    






































































                                                                                          

                    


















                                                                                  

                               
                                        




                                                                                                                       



























































































                                                                                                                


                                                                                








































                                                                    
                                                                                                
                                                                    
                                                                                                             









                                                      
                                                                          
                                                                                                           
                                           





































































































































































































































                                                                                                                                                                           
                                                            




















































                                                                                                          
                                                            



















































                                                                                                 

                                                    
                                                       
 





                                                                                

                                                    

































































































                                                                                                                    
                                      




                                                 






                                                                      
                    




                                                                      



                          



                                                                                          
 
                                                                     
 












                                                                   

 

                             



                                                                                          
 
                                                                            
 

                                                         
 







                                                   
                                                                   


                            

 

                                   






                                                                                           
 







                                                                 
 


                                      






                                                                                           
 









                                                                 
 
// license:BSD-3-Clause
// copyright-holders:Patrick Mackinlay

/*
 * An emulation of the Adaptec AIC-6250 SCSI Protocol Chip.
 *
 * Used in:
 *
 *   MIPS Rx2030
 *   Data General AViiON (AV100, 200, 300, 400 and 4300)
 *   Microbotics HardFrame (SCSI controller for Amiga 2000)
 *   Radio Shack 250-4161 (16 bit ISA hard/floppy controller card)
 *   pc532
 *
 * Sources:
 *
 *   http://bitsavers.org/pdf/adaptec/asic/AIC-6250_1988.pdf
 *
 * Status: very WIP, enough to load RISC/os on MIPS Rx2030 driver, but many
 * unimplemented and incorrect behaviours.
 *
 * TODO
 *   - fix problems with ATN
 *   - 16 bit DMA odd address start and HBV/LBV selection
 *   - disconnect/reselect
 *   - phase checks
 */

#include "emu.h"
#include "aic6250.h"

#define LOG_REG     (1U << 1)
#define LOG_STATE   (1U << 2)
#define LOG_CONFIG  (1U << 3)
#define LOG_INT     (1U << 4)
#define LOG_SCSI    (1U << 5)
#define LOG_DMA     (1U << 6)

//#define VERBOSE (LOG_GENERAL|LOG_REG|LOG_STATE|LOG_CONFIG|LOG_INT|LOG_SCSI|LOG_DMA)

#include "logmacro.h"

DEFINE_DEVICE_TYPE(AIC6250, aic6250_device, "aic6250", "Adaptec AIC-6250 High-Performance SCSI Protocol Chip")
DEFINE_DEVICE_TYPE(AIC6251A, aic6251a_device, "aic6251a", "Adaptec AIC-6251A Fast SCSI Protocol Chip")

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

aic6250_device::aic6250_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: nscsi_device(mconfig, type, tag, owner, clock)
	, nscsi_slot_card_interface(mconfig, *this, DEVICE_SELF)
	, m_int_cb(*this)
	, m_breq_cb(*this)
	, m_port_a_r_cb(*this, 0xff)
	, m_port_a_w_cb(*this)
	, m_port_b_r_cb(*this, 0xff)
	, m_port_b_w_cb(*this)
{
}

aic6250_device::aic6250_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: aic6250_device(mconfig, AIC6250, tag, owner, clock)
{
}

aic6251a_device::aic6251a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: aic6250_device(mconfig, AIC6251A, tag, owner, clock)
{
}

void aic6250_device::map(address_map &map)
{
	map(0x0, 0x0).rw(FUNC(aic6250_device::dma_count_l_r), FUNC(aic6250_device::dma_count_l_w));
	map(0x1, 0x1).rw(FUNC(aic6250_device::dma_count_m_r), FUNC(aic6250_device::dma_count_m_w));
	map(0x2, 0x2).rw(FUNC(aic6250_device::dma_count_h_r), FUNC(aic6250_device::dma_count_h_w));
	map(0x3, 0x3).w(FUNC(aic6250_device::int_msk_reg_0_w));
	map(0x4, 0x4).w(FUNC(aic6250_device::offset_cntrl_w));
	map(0x5, 0x5).rw(FUNC(aic6250_device::fifo_status_r), FUNC(aic6250_device::dma_cntrl_w));
	map(0x6, 0x6).rw(FUNC(aic6250_device::rev_cntrl_r), FUNC(aic6250_device::int_msk_reg_1_w));
	map(0x7, 0x7).rw(FUNC(aic6250_device::status_reg_0_r), FUNC(aic6250_device::control_reg_0_w));
	map(0x8, 0x8).rw(FUNC(aic6250_device::status_reg_1_r), FUNC(aic6250_device::control_reg_1_w));
	map(0x9, 0x9).rw(FUNC(aic6250_device::scsi_signal_reg_r), FUNC(aic6250_device::scsi_signal_reg_w));
	map(0xa, 0xa).rw(FUNC(aic6250_device::scsi_id_data_r), FUNC(aic6250_device::scsi_id_data_w));
	map(0xb, 0xb).r(FUNC(aic6250_device::source_dest_id_r));
	map(0xc, 0xc).rw(FUNC(aic6250_device::memory_data_r), FUNC(aic6250_device::memory_data_w));
	map(0xd, 0xd).rw(FUNC(aic6250_device::port_a_r), FUNC(aic6250_device::port_a_w));
	map(0xe, 0xe).rw(FUNC(aic6250_device::port_b_r), FUNC(aic6250_device::port_b_w));
	map(0xf, 0xf).rw(FUNC(aic6250_device::scsi_latch_data_r), FUNC(aic6250_device::scsi_bsy_rst_w));
}

u8 aic6250_device::read(address_space &space, offs_t offset)
{
	u8 data = space.unmap();

	if (offset)
	{
		switch (m_address_reg)
		{
		case 0x0: data = dma_count_l_r(); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x1: data = dma_count_m_r(); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x2: data = dma_count_h_r(); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x3: if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x4: if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x5: data = fifo_status_r(); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x6: data = rev_cntrl_r(); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x7: data = status_reg_0_r(); if (!machine().side_effects_disabled()) m_address_reg++; break;

		case 0x8: data = status_reg_1_r(); break;
		case 0x9: data = scsi_signal_reg_r(); break;
		case 0xa: data = scsi_id_data_r(); break;
		case 0xb: data = source_dest_id_r(); break;
		case 0xc: data = memory_data_r(); break;
		case 0xd: data = port_a_r(); break;
		case 0xe: data = port_b_r(); break;
		case 0xf: data = scsi_latch_data_r(); break;
		}
	}
	else
		// FIXME: not sure if possible to read address register
		data = m_address_reg;

	return data;
}

void aic6250_device::write(offs_t offset, u8 data)
{
	if (offset)
	{
		switch (m_address_reg)
		{
		case 0x0: dma_count_l_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x1: dma_count_m_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x2: dma_count_h_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x3: int_msk_reg_0_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x4: offset_cntrl_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x5: dma_cntrl_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x6: int_msk_reg_1_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;
		case 0x7: control_reg_0_w(data); if (!machine().side_effects_disabled()) m_address_reg++; break;

		case 0x8: control_reg_1_w(data); break;
		case 0x9: scsi_signal_reg_w(data); break;
		case 0xa: scsi_id_data_w(data); break;
		case 0xb: break;
		case 0xc: memory_data_w(data); break;
		case 0xd: port_a_w(data); break;
		case 0xe: port_b_w(data); break;
		case 0xf: scsi_bsy_rst_w(data); break;
		}
	}
	else
		m_address_reg = data & 0xf;
}

void aic6250_device::device_start()
{
	save_item(NAME(m_dma_count));
	save_item(NAME(m_int_msk_reg_0));
	save_item(NAME(m_offset_cntrl));
	save_item(NAME(m_dma_cntrl));
	save_item(NAME(m_rev_cntrl));
	save_item(NAME(m_int_msk_reg_1));
	save_item(NAME(m_status_reg_0));
	save_item(NAME(m_control_reg_0));
	save_item(NAME(m_status_reg_1));
	save_item(NAME(m_control_reg_1));
	save_item(NAME(m_scsi_signal_reg));
	save_item(NAME(m_scsi_id_data));
	save_item(NAME(m_source_dest_id));
	save_item(NAME(m_memory_data));
	save_item(NAME(m_port_a_latch));
	save_item(NAME(m_port_b_latch));
	save_item(NAME(m_scsi_latch_data));

	m_rev_cntrl = 0x02;

	m_state_timer = timer_alloc(FUNC(aic6250_device::state_loop), this);
	m_state = IDLE;

	m_int_asserted = false;

	// FIXME: for now, let's just look at everything
	scsi_bus->ctrl_wait(scsi_refid, S_ALL, S_ALL);
}

/*
 * Register Conditions On Reset
 *
 * The AIC-6250 essentially has two modes of reset; i.e., the Power On Reset
 * (/POR) and the SCSI Reset (SCSI /RST) which could be asserted through
 * software on the SCSI bus. Registers 00, 01, 02, 09, 0a, 0b, 0c and 0f are
 * not affected by any reset condition and their content is unknown on power
 * up. At other times it would depend on the activity which preceded the reset
 * action. Also, these registers are relevant only when some activity has been
 * initiated.
 *
 * Registers 04, 06, 0d and 0e are reset (to inactive zeroes) by the /POR only.
 *
 * Registers 05 and 09 are reset (to inactive zeros) by both the /POR or SCSI
 * /RST. On reset, the state of register 05(R) would be XX110000. The other
 * registers have multiple conditions as outlined below.
 *
 * Register 03 is reset to zero by the /POR; however, bit 6 (ARB/SEL Start)
 * would be reset by SCSI /RST also.
 *
 * Register 07(R) bits 0,1 are unaffected, while bits 2-7 are reset by /POR.
 * Bits 2,3,5 are also reset and bit 7 is set to 1 by the SCSI /RST condition.
 * Bit 4 will be set to 1 if EN BUS FREE DETECT INT (Reg 06, bit 2) is set to 1.
 *
 * Register 07(W), all bits except bit 6 are reset by /POR, while bits 3-5
 * would also be reset by SCSI /RST.
 *
 * Register 08(R), bits 0-5 are reset by /POR while bits 0-2 are reset by SCSI
 * /RST also. The state of bits 3-5 on reset will be 0. Bit 6 will be 1, bit 7
 * will normally be 1 as determined by /BACK * /BREQ.
 *
 * Register 08(W) bit 5 is a don't care. Bits 1-7 are reset by /POR. Bit 0 is
 * set to 1 while bit 7 is also reset by SCSI /RST.
 */
void aic6250_device::device_reset()
{
	// registers 04, 06, 0d, 0e
	m_offset_cntrl = 0;
	m_dma_cntrl = 0;
	m_port_a_latch = 0;
	m_port_b_latch = 0;

	// registers 05, 09
	m_offset_count_zero = true;
	m_fifo.clear();
	m_scsi_signal_reg = 0;

	// register 03
	m_int_msk_reg_0 = 0;

	// registers 07(R) and 07(W)
	m_status_reg_0 &= (R07R_SCSI_PHASE_CHG_ATN | R07R_DMA_BYTE_CNT_ZERO);
	m_control_reg_0 &= R07W_P_MEM_RW;

	// registers 08(R) and 08(W)
	m_status_reg_1 = R08R_RESERVED;
}

void aic6250_device::scsi_reset()
{
	// registers 04, 06, 0d, 0e
	m_offset_count_zero = true;
	m_fifo.clear();
	m_scsi_signal_reg = 0;

	// register 03
	m_int_msk_reg_0 &= ~R03W_ARB_SEL_START;

	// register 07(R)
	m_status_reg_0 &= ~(R07R_SCSI_REQ_ON | R07R_SCSI_PARITY_ERR | R07R_PHASE_MISMATCH_ERR);
	m_status_reg_0 |= R07R_SCSI_RST_OCCURRED;
	if (m_int_msk_reg_1 & R06W_EN_BUS_FREE_DETECT_INT)
		m_status_reg_0 |= R07R_BUS_FREE_DETECT;

	// register 07(W)
	m_control_reg_0 &= ~(R07W_SCSI_INTERFACE_MODE | R07W_EN_PORT_A_INP_OR_OUT | R07W_TARGET_MODE);

	// registers 08(R) and 08(W)
	m_status_reg_1 &= ~(R08R_SELECTED | R08R_RESELECTED | R08R_SEL_OUT);
	m_control_reg_1 &= ~R08W_AUTO_SCSI_PIO_REQ;
}

void aic6250_device::int_msk_reg_0_w(u8 data)
{
	LOGMASKED(LOG_REG, "int_msk_reg_0_w 0x%02x\n", data);

	/*
	 * Writing a zero to Bits 0 and 1 of this register will reset the selected
	 * or reselected interrupt status in Status Register 1 (Register 08),
	 * causing the interrupt status to be lost.
	 */
	if (!(data & R03W_EN_SELECT_INT))
		m_status_reg_1 &= ~R08R_SELECTED;

	if (!(data & R03W_EN_RESEL_INT))
		m_status_reg_1 &= ~R08R_RESELECTED;

	/*
	 * This bit will be reset to 0 when the Enable Command Done interrupt bit (Reg 03, Bit 3) is set to zero.
	 */
	if (!(data & R03W_EN_CMD_DONE_INT))
		m_status_reg_1 &= ~R08R_CMD_DONE;

	if ((m_int_msk_reg_0 ^ data) & R03W_ARB_SEL_START)
	{
		if (data & R03W_ARB_SEL_START)
		{
			if (m_state != IDLE)
				fatalerror("attempted to start selection while not idle\n");

			m_state = ARB_BUS_FREE;
			m_state_timer->adjust(attotime::zero);
		}
		else
		{
			if (m_state == IDLE)
				fatalerror("attempted to abort selection while idle\n");

			m_state = IDLE;
		}
	}

	m_int_msk_reg_0 = data;

	int_check();
}

void aic6250_device::offset_cntrl_w(u8 data)
{
	LOGMASKED(LOG_REG, "offset_cntrl_w 0x%02x\n", data);

	if (VERBOSE & LOG_CONFIG)
	{
		if (data & R04W_OFFSET)
		{
			double divisor = 4.0 + ((data & R04W_SYNC_XFER_RATE) >> 4);

			LOGMASKED(LOG_CONFIG, "synchronous offset %d speed %.3f\n",
				data & R04W_OFFSET, clock() / divisor);
		}
		else
			LOGMASKED(LOG_CONFIG, "asynchronous transfer mode\n");
	}

	m_offset_cntrl = data;
}

u8 aic6250_device::fifo_status_r()
{
	u8 const data =
		(m_offset_count_zero ? R05R_OFFSET_COUNT_ZERO : 0) |
		(m_fifo.empty() ? R05R_FIFO_EMPTY : 0) |
		(m_fifo.full() ? R05R_FIFO_FULL : 0) |
		(m_fifo.queue_length() & R05R_FIFO_COUNTER);

	LOGMASKED(LOG_REG, "fifo_status_r 0x%02x\n", data);

	return data;
}

void aic6250_device::dma_cntrl_w(u8 data)
{
	LOGMASKED(LOG_REG, "dma_cntrl_w 0x%02x\n", data);

	if (!(m_dma_cntrl & R05W_DMA_XFER_EN) && (data & R05W_DMA_XFER_EN))
	{
		LOGMASKED(LOG_DMA, "dma transfer %s memory, count %d\n",
			data & R05W_TRANSFER_DIR ? "from" : "to", m_dma_count);

		if (m_state != IDLE)
			fatalerror("attempt to start dma while not idle\n");

		// FIXME: should we trigger the state machine directly, or from the dma_w?
		if (data & R05W_TRANSFER_DIR)
		{
			m_state = DMA_OUT;
			m_breq_cb(1);
		}
		else
		{
			m_state = DMA_IN;
			m_state_timer->adjust(attotime::zero);
		}
	}

	m_dma_cntrl = data;
}

void aic6250_device::int_msk_reg_1_w(u8 data)
{
	LOGMASKED(LOG_REG, "int_msk_reg_1_w 0x%02x\n", data);

	if (!(data & R06W_EN_BUS_FREE_DETECT_INT))
		m_status_reg_0 &= ~R07R_BUS_FREE_DETECT;

	m_int_msk_reg_1 = data;

	int_check();
}

u8 aic6250_device::status_reg_0_r()
{
	u8 const dma_count_zero = !m_dma_count ? R07R_DMA_BYTE_CNT_ZERO : 0;

	return (m_status_reg_0 & ~R07R_DMA_BYTE_CNT_ZERO) | dma_count_zero;
}

void aic6250_device::control_reg_0_w(u8 data)
{
	LOGMASKED(LOG_REG, "control_reg_0_w 0x%02x\n", data);

	LOGMASKED(LOG_CONFIG, "scsi id %d, %s, port A %d, %s\n",
		data & R07W_SCSI_ID,
		data & R07W_SCSI_INTERFACE_MODE ? "differential" : "single-ended",
		data & R07W_EN_PORT_A_INP_OR_OUT ? "output" : "input",
		data & R07W_TARGET_MODE ? "target" : "initiator");

	m_control_reg_0 = data;

	if (data & R07W_P_MEM_CYCLE_REQ)
	{
		LOGMASKED(LOG_DMA, "processor memory %s request initiated\n", data & R07W_P_MEM_RW ? "write" : "read");
		m_status_reg_1 &= ~R08R_MEM_CYCLE_CMPL;
		m_breq_cb(1);
	}
}

u8 aic6250_device::status_reg_1_r()
{
	if (m_status_reg_0 & R07R_ERROR_MASK)
		return m_status_reg_1 | R08R_ERROR;
	else
		return m_status_reg_1;
}

void aic6250_device::control_reg_1_w(u8 data)
{
	LOGMASKED(LOG_REG, "control_reg_1_w 0x%02x\n", data);

	if (data & R08W_CHIP_SW_RESET)
	{
		LOG("chip software reset\n");

		m_state = IDLE;

		scsi_reset();
		device_reset();

		m_control_reg_1 = R08W_CHIP_SW_RESET;

		set_int_state(false);
	}
	else
	{
		LOGMASKED(LOG_CONFIG, "%s frequency, port B input/output %s, %d-bit memory bus%s\n",
			data & R08W_CLK_FREQ_MODE ? "high" : "low",
			data & R08W_EN_PORT_B_INP_OR_OUT ? "enabled" : "disabled",
			data & R08W_EN_16_BIT_MEM_BUS ? 16 : 8,
			data & R08W_AUTO_SCSI_PIO_REQ ? ", automatic PIO" : "");

		if (!(m_control_reg_1 & R08W_AUTO_SCSI_PIO_REQ) && (data & R08W_AUTO_SCSI_PIO_REQ))
		{
			if (m_state != IDLE)
				fatalerror("attempted to start auto pio while not idle\n");

			m_state = AUTO_PIO;
			m_state_timer->adjust(attotime::zero);
		}

		m_control_reg_1 = data;
	}
}

u8 aic6250_device::scsi_signal_reg_r()
{
	u32 const ctrl = scsi_bus->ctrl_r();

	u8 const data =
		((ctrl & S_ACK) ? R09R_SCSI_ACK_IN : 0) |
		((ctrl & S_REQ) ? R09R_SCSI_REQ_IN : 0) |
		((ctrl & S_BSY) ? R09R_SCSI_BSY_IN : 0) |
		((ctrl & S_SEL) ? R09R_SCSI_SEL_IN : 0) |
		((ctrl & S_ATN) ? R09R_SCSI_ATN_IN : 0) |
		((ctrl & S_MSG) ? R09R_SCSI_MSG_IN : 0) |
		((ctrl & S_INP) ? R09R_SCSI_IO_IN : 0) |
		((ctrl & S_CTL) ? R09R_SCSI_CD_IN : 0);

	LOGMASKED(LOG_REG, "scsi_signal_reg_r 0x%02x\n", data);

	return data;
}

void aic6250_device::scsi_signal_reg_w(u8 data)
{
	LOGMASKED(LOG_REG, "scsi_signal_reg_w 0x%02x\n", data);

	if (m_control_reg_0 & R07W_TARGET_MODE)
		scsi_bus->ctrl_w(scsi_refid,
			((data & R09W_SCSI_REQ_OUT) ? S_REQ : 0) |
			((data & R09W_SCSI_BSY_OUT) ? S_BSY : 0) |
			((data & R09W_SCSI_SEL_OUT) ? S_SEL : 0) |
			((data & R09W_SCSI_MSG_OUT) ? S_MSG : 0) |
			((data & R09W_SCSI_IO_OUT) ? S_INP : 0) |
			((data & R09W_SCSI_CD_OUT) ? S_CTL : 0), S_REQ | S_BSY | S_SEL | S_MSG | S_INP | S_CTL);
	else
		scsi_bus->ctrl_w(scsi_refid,
			((data & R09W_SCSI_ACK_OUT) ? S_ACK : 0) |
			((data & R09W_SCSI_BSY_OUT) ? S_BSY : 0) |
			((data & R09W_SCSI_SEL_OUT) ? S_SEL : 0) |
			((data & R09W_SCSI_ATN_OUT) ? S_ATN : 0), S_ACK | S_BSY | S_SEL | S_ATN);

	if ((data ^ m_scsi_signal_reg) & R09R_PHASE_MASK)
		LOGMASKED(LOG_SCSI, "expecting phase %s\n", aic6250_phase[data >> 5]);

	if (!(m_control_reg_0 & R07W_TARGET_MODE) && phase_match(data, scsi_bus->ctrl_r()))
		m_status_reg_0 &= ~R07R_PHASE_MISMATCH_ERR;

	if (!(m_control_reg_0 & R07W_TARGET_MODE) && (data & R09W_SCSI_ACK_OUT))
		m_status_reg_0 &= ~R07R_SCSI_REQ_ON;

	m_scsi_signal_reg = data;

	// HACK: trigger check for phase match
	scsi_ctrl_changed();
}

u8 aic6250_device::scsi_id_data_r()
{
	// TODO: selection/reselection phase
	u8 const data = scsi_bus->data_r();

	LOGMASKED(LOG_REG, "scsi_id_data_r 0x%02x\n", data);

	return data;
}

void aic6250_device::scsi_id_data_w(u8 data)
{
	LOGMASKED(LOG_REG, "scsi_id_data_w 0x%02x\n", data);

	scsi_bus->data_w(scsi_refid, data);

	m_scsi_id_data = data;
}

u8 aic6250_device::memory_data_r()
{
	LOGMASKED(LOG_REG, "memory_data_r 0x%02x\n", m_memory_data);

	return m_memory_data;
}

void aic6250_device::memory_data_w(u8 data)
{
	LOGMASKED(LOG_REG, "memory_data_w 0x%02x\n", data);

	m_memory_data = data;
}

u8 aic6250_device::port_a_r()
{
	// FIXME: not sure if port A bits 2 and 7 can be read as GPIO in 8-bit differential mode
	u8 const data = (m_control_reg_0 & R07W_SCSI_INTERFACE_MODE)
		|| (m_control_reg_0 & R07W_EN_PORT_A_INP_OR_OUT) ? (m_port_a_latch ^ 0xff) : m_port_a_r_cb();

	LOGMASKED(LOG_REG, "port_a_r 0x%02x\n", data);

	return data;
}

void aic6250_device::port_a_w(u8 data)
{
	LOGMASKED(LOG_REG, "port_a_w 0x%02x\n", data);

	// Port A outputs are the inverse of data written to this register
	if (!(m_control_reg_0 & R07W_SCSI_INTERFACE_MODE) && (m_control_reg_0 & R07W_EN_PORT_A_INP_OR_OUT))
		m_port_a_w_cb(data ^ 0xff);

	m_port_a_latch = data;
}

u8 aic6250_device::port_b_r()
{
	u8 const data = ((m_control_reg_1 & R08W_EN_16_BIT_MEM_BUS)
		|| (m_control_reg_1 & R08W_EN_PORT_B_INP_OR_OUT)) ? m_port_b_latch : m_port_b_r_cb();

	LOGMASKED(LOG_REG, "port_b_r 0x%02x\n", data);

	return data;
}

void aic6250_device::port_b_w(u8 data)
{
	LOGMASKED(LOG_REG, "port_b_w 0x%02x\n", data);

	if (!(m_control_reg_1 & R08W_EN_16_BIT_MEM_BUS) && (m_control_reg_1 & R08W_EN_PORT_B_INP_OR_OUT))
		m_port_b_w_cb(data);

	m_port_b_latch = data;
}

void aic6250_device::scsi_ctrl_changed()
{
	u32 const control = scsi_bus->ctrl_r();

	if ((control & S_BSY) && !(control & S_SEL))
		LOGMASKED(LOG_SCSI, "scsi_ctrl_changed 0x%08x phase %s%s%s\n", control, nscsi_phase[control & S_PHASE_MASK],
			control & S_REQ ? " REQ" : "", control & S_ACK ? " ACK" : "");
	else if (control & S_BSY)
		LOGMASKED(LOG_SCSI, "scsi_ctrl_changed 0x%08x arbitration/selection\n", control);
	else
		LOGMASKED(LOG_SCSI, "scsi_ctrl_changed 0x%08x BUS FREE\n", control);

	// phase change/atn
	if (!(m_control_reg_0 & R07W_TARGET_MODE))
	{
		if ((control ^ m_scsi_ctrl_state) & S_PHASE_MASK)
		{
			if ((m_control_reg_1 & R08W_PHASE_CHANGE_MODE) || (control & S_REQ))
			{
				LOGMASKED(LOG_SCSI, "bus phase change\n");

				m_status_reg_0 |= R07R_SCSI_PHASE_CHG_ATN;
			}

			if ((control & S_REQ) && !phase_match(m_scsi_signal_reg, control))
			{
				LOGMASKED(LOG_SCSI, "bus phase mismatch expect %s found %s\n", aic6250_phase[m_scsi_signal_reg >> 5], nscsi_phase[control & S_PHASE_MASK]);

				m_status_reg_0 |= R07R_PHASE_MISMATCH_ERR;
			}
		}
	}
	else
		if (!(m_scsi_ctrl_state & S_ATN) && (control & S_ATN))
		{
			LOGMASKED(LOG_SCSI, "bus atn asserted\n");

			m_status_reg_0 |= R07R_SCSI_PHASE_CHG_ATN;
		}

	// scsi req on
	if (!(m_control_reg_0 & R07W_TARGET_MODE) && !(m_scsi_ctrl_state & S_REQ) && (control & S_REQ))
	{
		LOGMASKED(LOG_SCSI, "bus req asserted\n");

		m_status_reg_0 |= R07R_SCSI_REQ_ON;
	}

	// bus free
	if ((m_scsi_ctrl_state & (S_SEL | S_BSY | S_RST)) && !(control & (S_SEL | S_BSY | S_RST)))
	{
		LOGMASKED(LOG_SCSI, "bus free\n");

		m_status_reg_0 |= R07R_BUS_FREE_DETECT;
	}

	if (!(m_scsi_ctrl_state & S_RST) && (control & S_RST))
	{
		LOGMASKED(LOG_SCSI, "bus reset asserted\n");

		m_status_reg_0 |= R07R_SCSI_RST_OCCURRED;

		scsi_bus->data_w(scsi_refid, 0);
		scsi_bus->ctrl_w(scsi_refid, 0, S_ALL);
	}

	// record new state
	m_scsi_ctrl_state = control;

	int_check();

	// TODO: in future, probably schedule scsi engine, not just interrupt checks
	//m_state_timer->adjust(attotime::zero);
}


TIMER_CALLBACK_MEMBER(aic6250_device::state_loop)
{
	// step state machine until delay, idle state or interrupt
	int delay = state_step();

	// check for interrupts
	bool const interrupt = int_check();

	if (delay < 0)
		return;

	/*
	 * All clock cycles are referred to assuming the high-frequency mode, set
	 * by the Clock Frequency mode bit in Control Register 1 (Bit 2, Register
	 * 08). If the low-frequency mode is being used, the number of clock cycles
	 * must be divided by two.
	 */
	if (!(m_control_reg_1 & R08W_CLK_FREQ_MODE))
		delay >>= 1;

	if (m_state != IDLE && !interrupt)
		m_state_timer->adjust(attotime::from_ticks(delay, clock()));
}

int aic6250_device::state_step()
{
	u8 const scsi_id = 1 << (m_control_reg_0 & R07W_SCSI_ID);
	int delay = 0;

	switch (m_state)
	{
	case IDLE:
		break;

	case ARB_BUS_FREE:
		LOGMASKED(LOG_STATE, "arbitration: waiting for bus free\n");
		if (!(scsi_bus->ctrl_r() & (S_SEL | S_BSY | S_RST)))
		{
			m_state = ARB_START;
			delay = 16; // 800ns
		}
		break;

	case ARB_START:
		LOGMASKED(LOG_STATE, "arbitration: started\n");
		m_state = ARB_EVALUATE;

		// drive our SCSI ID and assert BSY
		scsi_bus->data_w(scsi_refid, scsi_id);
		scsi_bus->ctrl_w(scsi_refid, S_BSY, S_BSY);

		delay = 56; // 2800ns
		break;

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

		LOGMASKED(LOG_STATE, "arbitration: won\n");
		m_state = SEL_START;
		delay = 24; // 1200ns
		break;

	case SEL_START:
		LOGMASKED(LOG_STATE, "selection: SEL asserted\n");

		m_status_reg_1 |= R08R_SEL_OUT;

		m_state = SEL_DELAY;
		delay = 2; // 100ns

		// drive both SCSI IDs and assert SEL
		scsi_bus->data_w(scsi_refid, m_scsi_id_data);
		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 = 12; // 600ns

		// clear BSY, optionally assert ATN
		if (m_int_msk_reg_0 & R03W_EN_AUTO_ATN)
			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 (scsi_bus->ctrl_r() & S_BSY)
		{
			LOGMASKED(LOG_STATE, "selection: BSY asserted by target\n");

			m_state = SEL_COMPLETE;
			delay = 1; // 50ns
		}
		else
			delay = -1;
		break;

	case SEL_COMPLETE:
		LOGMASKED(LOG_STATE, "selection: complete\n");

		m_int_msk_reg_0 &= ~R03W_ARB_SEL_START;
		m_status_reg_1 &= ~R08R_SEL_OUT;
		m_status_reg_1 |= R08R_CMD_DONE;
		m_source_dest_id = m_scsi_id_data;

		m_state = IDLE;

		// clear data and SEL
		// FIXME: should not clear ATN
		scsi_bus->data_w(scsi_refid, 0);
		scsi_bus->ctrl_w(scsi_refid, 0, S_SEL | S_ATN);
		break;

	case DMA_IN:
		// FIXME: assert ack when: req asserted && phase match && count not zero && fifo not full
		if (!m_fifo.full())
		{
			u8 const data = scsi_bus->data_r();
			LOGMASKED(LOG_STATE, "dma in 0x%02x\n", data);

			m_status_reg_0 &= ~R07R_SCSI_REQ_ON;
			m_dma_count--;
			m_fifo.enqueue(data);

			m_state = DMA_IN_NEXT;

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

	case DMA_IN_NEXT:
		if (!(scsi_bus->ctrl_r() & S_REQ))
		{
			LOGMASKED(LOG_STATE, "dma in next count %d\n", m_dma_count);
			m_state = m_dma_count ? DMA_IN_REQ : DMA_IN_DRAIN;

			scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
		}
		break;

	case DMA_IN_REQ:
		if (scsi_bus->ctrl_r() & S_REQ)
			m_state = DMA_IN;
		break;

	case DMA_IN_DRAIN:
		if (!m_fifo.empty())
		{
			m_breq_cb(1);
			delay = -1;
		}
		else
			m_state = DMA_IN_DONE;
		break;

	case DMA_IN_DONE:
		LOGMASKED(LOG_STATE, "dma in done\n");
		m_status_reg_1 |= R08R_CMD_DONE;
		m_dma_cntrl &= ~R05W_DMA_XFER_EN;
		m_state = IDLE;
		break;

	case DMA_OUT:
		// FIXME: assert ack when: req asserted && phase match && count not zero && fifo not empty
		if (!m_fifo.empty())
		{
			u8 const data = m_fifo.dequeue();
			LOGMASKED(LOG_STATE, "dma out 0x%02x\n", data);

			m_status_reg_0 &= ~R07R_SCSI_REQ_ON;
			m_dma_count--;

			m_state = DMA_OUT_NEXT;

			// drive data, assert ACK
			scsi_bus->data_w(scsi_refid, data);
			scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK);
		}
		else
		{
			delay = -1;
			m_breq_cb(1);
		}
		break;

	case DMA_OUT_NEXT:
		if (!(scsi_bus->ctrl_r() & S_REQ))
		{
			LOGMASKED(LOG_STATE, "dma out next count %d\n", m_dma_count);
			m_state = m_dma_count ? DMA_OUT_REQ : DMA_OUT_DONE;

			scsi_bus->data_w(scsi_refid, 0);
			scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
		}
		break;

	case DMA_OUT_REQ:
		if (scsi_bus->ctrl_r() & S_REQ)
			m_state = DMA_OUT;
		break;

	case DMA_OUT_DONE:
		LOGMASKED(LOG_STATE, "dma out done\n");
		m_status_reg_1 |= R08R_CMD_DONE;
		m_dma_cntrl &= ~R05W_DMA_XFER_EN;

		m_state = IDLE;
		break;

	case AUTO_PIO:
		// TODO: test expected phase
		// out: wait for req, check phase match, ack
		if (scsi_bus->ctrl_r() & S_REQ)
		{
			LOGMASKED(LOG_STATE, "auto pio\n");

			m_state = (m_dma_cntrl & R05W_TRANSFER_DIR) ? AUTO_PIO_OUT : AUTO_PIO_IN;
		}
		break;

	case AUTO_PIO_IN:
		m_state = AUTO_PIO_DONE;

		m_status_reg_0 &= ~R07R_SCSI_REQ_ON;
		m_scsi_latch_data = scsi_bus->data_r();

		LOGMASKED(LOG_STATE, "auto pio in 0x%02x\n", m_scsi_latch_data);
		scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK);
		break;

	case AUTO_PIO_OUT:
		LOGMASKED(LOG_STATE, "auto pio out 0x%02x\n", m_scsi_id_data);
		m_status_reg_0 &= ~R07R_SCSI_REQ_ON;

		m_state = AUTO_PIO_DONE;

		scsi_bus->data_w(scsi_refid, m_scsi_id_data);
		scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK);
		break;

	case AUTO_PIO_DONE:
		if (!(scsi_bus->ctrl_r() & S_REQ))
		{
			LOGMASKED(LOG_STATE, "auto pio done\n");

			m_status_reg_1 |= R08R_CMD_DONE;
			m_control_reg_1 &= ~R08W_AUTO_SCSI_PIO_REQ;
			m_state = IDLE;

			scsi_bus->data_w(scsi_refid, 0);
			scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
		}
		break;
	}

	return delay;
}

bool aic6250_device::int_check()
{
	bool int_asserted = false;

	// status interrupts
	if (m_int_msk_reg_0 & m_status_reg_1 & R03W_INT_MASK)
		int_asserted = true;

	// error interrupts
	if ((m_int_msk_reg_0 & R03W_EN_ERROR_INT) && (m_int_msk_reg_1 & R06W_ERROR_INT_MASK))
	{
		// phase change/atn
		if ((m_int_msk_reg_1 & R06W_EN_PHASE_CHANGE_INT_INIT) && (m_status_reg_0 & R07R_SCSI_PHASE_CHG_ATN))
			int_asserted = true;

		// scsi parity
		if ((m_int_msk_reg_1 & R06W_EN_SCSI_PARITY_ERR_INT) && (m_status_reg_0 & R07R_SCSI_PARITY_ERR))
			int_asserted = true;

		// bus free
		if ((m_int_msk_reg_1 & R06W_EN_BUS_FREE_DETECT_INT) && (m_status_reg_0 & R07R_BUS_FREE_DETECT))
			int_asserted = true;

		// phase mismatch (initiator only)
		if ((m_int_msk_reg_1 & R06W_EN_PHASE_MISMATCH_INT) && (m_status_reg_0 & R07R_PHASE_MISMATCH_ERR))
			int_asserted = true;

		// memory parity
		if ((m_int_msk_reg_1 & R06W_EN_MEM_PARITY_ERROR_INT) && (m_status_reg_0 & R07R_MEMORY_PARITY_ERR))
			int_asserted = true;

		// scsi reset
		if ((m_int_msk_reg_1 & R06W_EN_SCSI_RST_INT) && (m_status_reg_0 & R07R_SCSI_RST_OCCURRED))
			int_asserted = true;

		// scsi req on (initiator only)
		if ((m_int_msk_reg_1 & R06W_EN_SCSI_REQ_ON_INT) && (m_status_reg_0 & R07R_SCSI_REQ_ON))
			int_asserted = true;
	}

	if (int_asserted)
		LOGMASKED(LOG_INT, "sr0 0x%02x sr1 0x%02x\n", m_status_reg_0, m_status_reg_1);

	// update int line state
	set_int_state(int_asserted);

	return int_asserted;
}

void aic6250_device::set_int_state(bool asserted)
{
	if (m_int_asserted != asserted)
	{
		LOGMASKED(LOG_INT, "set_int_state interrupt %s\n", asserted ? "asserted" : "cleared");

		m_int_asserted = asserted;

		// line is active low
		m_int_cb(asserted ? 0 : 1);
	}
}

/*
 * This implementation has a simplistic DMA approach. DMA transfers to memory
 * start with the SCSI interface filling up the FIFO. When it's full (or the
 * count exhausted), B̅R̅E̅Q̅ is asserted and cleared in a loop until the FIFO is
 * empty, after which the SCSI interface is scheduled again.
 *
 * Transfers from memory start with B̅R̅E̅Q̅ being asserted and cleared until the
 * FIFO is full, after which the SCSI interface is scheduled. If the SCSI
 * interface requires more data, B̅R̅E̅Q̅ is asserted and the cycle repeats. When
 * the DMA transfer count falls below 8, data is transferred via individual
 * cycles on demand rather than prefetched.
 */
void aic6250_device::back_w(int state)
{
	LOGMASKED(LOG_DMA, "back_w %d\n", state);

	m_breq_cb(0);

	if (!(m_control_reg_0 & R07W_P_MEM_CYCLE_REQ))
	{
		if (m_dma_cntrl & R05W_TRANSFER_DIR)
			if (m_fifo.full() || m_dma_count < 8)
				m_state_timer->adjust(attotime::zero);
			else
				m_breq_cb(1);
		else
			if (m_fifo.empty())
				m_state_timer->adjust(attotime::zero);
			else
				m_breq_cb(1);
	}
}

u8 aic6250_device::dma_r()
{
	if ((m_control_reg_0 & R07W_P_MEM_CYCLE_REQ) && (m_control_reg_0 & R07W_P_MEM_RW))
	{
		// 8-bit memory write cycle
		u8 const data = m_memory_data;

		LOGMASKED(LOG_DMA, "DMA 0x%02x from reg 0C\n", data);

		m_status_reg_1 |= R08R_MEM_CYCLE_CMPL;
		m_control_reg_0 &= ~R07W_P_MEM_CYCLE_REQ;

		return data;
	}
	else
	{
		u8 const data = m_fifo.dequeue();

		LOGMASKED(LOG_DMA, "DMA 0x%02x from FIFO\n", data);

		return data;
	}
}

u16 aic6250_device::dma16_r()
{
	if ((m_control_reg_0 & R07W_P_MEM_CYCLE_REQ) && (m_control_reg_0 & R07W_P_MEM_RW))
	{
		// 16-bit memory write cycle
		u16 const data = m_memory_data | (u16(m_port_b_latch) << 8);

		LOGMASKED(LOG_DMA, "DMA 0x%04x from reg 0C and 0E\n", data);

		m_status_reg_1 |= R08R_MEM_CYCLE_CMPL;
		m_control_reg_0 &= ~R07W_P_MEM_CYCLE_REQ;

		return data;
	}
	else
	{
		u16 data = m_fifo.dequeue();

		data |= u16(m_fifo.dequeue()) << 8;

		LOGMASKED(LOG_DMA, "DMA 0x%04x from FIFO\n", data);

		return data;
	}
}

void aic6250_device::dma_w(u8 data)
{
	if ((m_control_reg_0 & R07W_P_MEM_CYCLE_REQ) && !(m_control_reg_0 & R07W_P_MEM_RW))
	{
		// 8-bit memory read cycle
		LOGMASKED(LOG_DMA, "DMA 0x%02x to reg 0C\n", data);

		m_status_reg_1 |= R08R_MEM_CYCLE_CMPL;
		m_control_reg_0 &= ~R07W_P_MEM_CYCLE_REQ;

		m_memory_data = data;
	}
	else
	{
		LOGMASKED(LOG_DMA, "DMA 0x%02x to FIFO\n", data);

		m_fifo.enqueue(data);
	}
}

void aic6250_device::dma16_w(u16 data)
{
	if ((m_control_reg_0 & R07W_P_MEM_CYCLE_REQ) && !(m_control_reg_0 & R07W_P_MEM_RW))
	{
		// 16-bit memory read cycle
		LOGMASKED(LOG_DMA, "DMA 0x%04x to reg 0C and 0E\n", data);

		m_status_reg_1 |= R08R_MEM_CYCLE_CMPL;
		m_control_reg_0 &= ~R07W_P_MEM_CYCLE_REQ;

		m_memory_data = data;
		m_port_b_latch = data >> 8;
	}
	else
	{
		LOGMASKED(LOG_DMA, "DMA 0x%04x to FIFO\n", data);

		m_fifo.enqueue(data);
		m_fifo.enqueue(data >> 8);
	}
}