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

    Z80 SIO (Z8440) implementation

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

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

#include "emu.h"
#include "z80sio.h"
#include "cpu/z80/z80.h"
#include "cpu/z80/z80daisy.h"



//**************************************************************************
//  DEBUGGING
//**************************************************************************

#define VERBOSE		0

#define VPRINTF(x) do { if (VERBOSE) logerror x; } while (0)



//**************************************************************************
//  CONSTANTS
//**************************************************************************

// interrupt states
const int INT_TRANSMIT		= 0x00;		// not confirmed
const int INT_STATUS		= 0x01;
const int INT_RECEIVE		= 0x02;
const int INT_ERROR			= 0x03;

const int INT_CHB_TRANSMIT	= 0 + INT_TRANSMIT;
const int INT_CHB_STATUS	= 0 + INT_STATUS;
const int INT_CHB_RECEIVE	= 0 + INT_RECEIVE;
const int INT_CHB_ERROR		= 0 + INT_ERROR;
const int INT_CHA_TRANSMIT	= 4 + INT_TRANSMIT;
const int INT_CHA_STATUS	= 4 + INT_STATUS;
const int INT_CHA_RECEIVE	= 4 + INT_RECEIVE;
const int INT_CHA_ERROR		= 4 + INT_ERROR;

// SIO write register 0
const int SIO_WR0_RESET_MASK				= 0xc0;		// D7-D6: Reset control
const int SIO_WR0_RESET_NULL				=	0x00;	//  00 = NULL code
const int SIO_WR0_RESET_RX_CRC				=	0x40;	//  01 = Reset Rx CRC checker
const int SIO_WR0_RESET_TX_CRC				=	0x80;	//  10 = Reset Tx CRC generator
const int SIO_WR0_RESET_TX_LATCH			=	0xc0;	//  11 = Reset Tx Underrun/EOM latch
const int SIO_WR0_COMMAND_MASK				= 0x38;		// D5-D3: Command
const int SIO_WR0_COMMAND_NULL				=	0x00;	//  000 = NULL code
const int SIO_WR0_COMMAND_SET_ABORT			=	0x08;	//  001 = Set abort (SDLC)
const int SIO_WR0_COMMAND_RES_STATUS_INT	=	0x10;	//  010 = reset ext/status interrupts
const int SIO_WR0_COMMAND_CH_RESET			=	0x18;	//  011 = Channel reset
const int SIO_WR0_COMMAND_ENA_RX_INT		=	0x20;	//  100 = Enable int on next Rx character
const int SIO_WR0_COMMAND_RES_TX_INT		=	0x28;	//  101 = Reset Tx int pending
const int SIO_WR0_COMMAND_RES_ERROR			=	0x30;	//  110 = Error reset
const int SIO_WR0_COMMAND_RETI				=	0x38;	//  111 = Return from int (CH-A only)
const int SIO_WR0_REGISTER_MASK				= 0x07;		// D2-D0: Register select (0-7)

// SIO write register 1
const int SIO_WR1_READY_WAIT_ENA			= 0x80;		// D7 = READY/WAIT enable
const int SIO_WR1_READY_WAIT_FUNCTION		= 0x40;		// D6 = READY/WAIT function
const int SIO_WR1_READY_WAIT_ON_RT			= 0x20;		// D5 = READY/WAIT on R/T
const int SIO_WR1_RXINT_MASK				= 0x18;		// D4-D3 = Rx int control
const int SIO_WR1_RXINT_DISABLE				=	0x00;	//  00 = Rx int disable
const int SIO_WR1_RXINT_FIRST				=	0x08;	//  01 = Rx int on first character
const int SIO_WR1_RXINT_ALL_PARITY			=	0x10;	//  10 = int on all Rx characters (parity affects vector)
const int SIO_WR1_RXINT_ALL_NOPARITY		=	0x18;	//  11 = int on all Rx characters (parity ignored)
const int SIO_WR1_STATUS_AFFECTS_VECTOR		= 0x04;		// D2 = Status affects vector (CH-B only)
const int SIO_WR1_TXINT_ENABLE				= 0x02;		// D1 = Tx int enable
const int SIO_WR1_STATUSINT_ENABLE			= 0x01;		// D0 = Ext int enable

// SIO write register 2 (CH-B only)
const int SIO_WR2_INT_VECTOR_MASK			= 0xff;		// D7-D0 = interrupt vector

// SIO write register 3
const int SIO_WR3_RX_DATABITS_MASK			= 0xc0;		// D7-D6 = Rx Data bits
const int SIO_WR3_RX_DATABITS_5				=	0x00;	//  00 = Rx 5 bits/character
const int SIO_WR3_RX_DATABITS_7				=	0x40;	//  01 = Rx 7 bits/character
const int SIO_WR3_RX_DATABITS_6				=	0x80;	//  10 = Rx 6 bits/character
const int SIO_WR3_RX_DATABITS_8				=	0xc0;	//  11 = Rx 8 bits/character
const int SIO_WR3_AUTO_ENABLES				= 0x20;		// D5 = Auto enables
const int SIO_WR3_ENTER_HUNT_PHASE			= 0x10;		// D4 = Enter hunt phase
const int SIO_WR3_RX_CRC_ENABLE				= 0x08;		// D3 = Rx CRC enable
const int SIO_WR3_ADDR_SEARCH_MODE			= 0x04;		// D2 = Address search mode (SDLC)
const int SIO_WR3_SYNC_LOAD_INHIBIT			= 0x02;		// D1 = Sync character load inhibit
const int SIO_WR3_RX_ENABLE					= 0x01;		// D0 = Rx enable

// SIO write register 4
const int SIO_WR4_CLOCK_MODE_MASK			= 0xc0;		// D7-D6 = Clock mode
const int SIO_WR4_CLOCK_MODE_x1				=	0x00;	//  00 = x1 clock mode
const int SIO_WR4_CLOCK_MODE_x16			=	0x40;	//  01 = x16 clock mode
const int SIO_WR4_CLOCK_MODE_x32			=	0x80;	//  10 = x32 clock mode
const int SIO_WR4_CLOCK_MODE_x64			=	0xc0;	//  11 = x64 clock mode
const int SIO_WR4_SYNC_MODE_MASK			= 0x30;		// D5-D4 = Sync mode
const int SIO_WR4_SYNC_MODE_8BIT			=	0x00;	//  00 = 8 bit sync character
const int SIO_WR4_SYNC_MODE_16BIT			=	0x10;	//  01 = 16 bit sync character
const int SIO_WR4_SYNC_MODE_SDLC			=	0x20;	//  10 = SDLC mode (01111110 flag)
const int SIO_WR4_SYNC_MODE_EXTERNAL		=	0x30;	//  11 = External sync mode
const int SIO_WR4_STOPBITS_MASK				= 0x0c;		// D3-D2 = Stop bits
const int SIO_WR4_STOPBITS_SYNC				=	0x00;	//  00 = Sync modes enable
const int SIO_WR4_STOPBITS_1				=	0x04;	//  01 = 1 stop bit/character
const int SIO_WR4_STOPBITS_15				=	0x08;	//  10 = 1.5 stop bits/character
const int SIO_WR4_STOPBITS_2				=	0x0c;	//  11 = 2 stop bits/character
const int SIO_WR4_PARITY_EVEN				= 0x02;		// D1 = Parity even/odd
const int SIO_WR4_PARITY_ENABLE				= 0x01;		// D0 = Parity enable

// SIO write register 5
const int SIO_WR5_DTR						= 0x80;		// D7 = DTR
const int SIO_WR5_TX_DATABITS_MASK			= 0x60;		// D6-D5 = Tx Data bits
const int SIO_WR5_TX_DATABITS_5				=	0x00;	//  00 = Tx 5 bits/character
const int SIO_WR5_TX_DATABITS_7				=	0x20;	//  01 = Tx 7 bits/character
const int SIO_WR5_TX_DATABITS_6				=	0x40;	//  10 = Tx 6 bits/character
const int SIO_WR5_TX_DATABITS_8				=	0x60;	//  11 = Tx 8 bits/character
const int SIO_WR5_SEND_BREAK				= 0x10;		// D4 = Send break
const int SIO_WR5_TX_ENABLE					= 0x08;		// D3 = Tx Enable
const int SIO_WR5_CRC16_SDLC				= 0x04;		// D2 = CRC-16/SDLC
const int SIO_WR5_RTS						= 0x02;		// D1 = RTS
const int SIO_WR5_TX_CRC_ENABLE				= 0x01;		// D0 = Tx CRC enable

// SIO write register 6
const int SIO_WR6_SYNC_7_0_MASK				= 0xff;		// D7-D0 = Sync bits 7-0

// SIO write register 7
const int SIO_WR7_SYNC_15_8_MASK			= 0xff;		// D7-D0 = Sync bits 15-8

// SIO read register 0
const int SIO_RR0_BREAK_ABORT				= 0x80;		// D7 = Break/abort
const int SIO_RR0_TX_UNDERRUN				= 0x40;		// D6 = Tx underrun/EOM
const int SIO_RR0_CTS						= 0x20;		// D5 = CTS
const int SIO_RR0_SYNC_HUNT					= 0x10;		// D4 = Sync/hunt
const int SIO_RR0_DCD						= 0x08;		// D3 = DCD
const int SIO_RR0_TX_BUFFER_EMPTY			= 0x04;		// D2 = Tx buffer empty
const int SIO_RR0_INT_PENDING				= 0x02;		// D1 = int pending (CH-A only)
const int SIO_RR0_RX_CHAR_AVAILABLE			= 0x01;		// D0 = Rx character available

// SIO read register 1
const int SIO_RR1_END_OF_FRAME				= 0x80;		// D7 = End of frame (SDLC)
const int SIO_RR1_CRC_FRAMING_ERROR			= 0x40;		// D6 = CRC/Framing error
const int SIO_RR1_RX_OVERRUN_ERROR			= 0x20;		// D5 = Rx overrun error
const int SIO_RR1_PARITY_ERROR				= 0x10;		// D4 = Parity error
const int SIO_RR1_IFIELD_BITS_MASK			= 0x0e;		// D3-D1 = I field bits
														//  100 = 0 in prev, 3 in 2nd prev
														//  010 = 0 in prev, 4 in 2nd prev
														//  110 = 0 in prev, 5 in 2nd prev
														//  001 = 0 in prev, 6 in 2nd prev
														//  101 = 0 in prev, 7 in 2nd prev
														//  011 = 0 in prev, 8 in 2nd prev
														//  111 = 1 in prev, 8 in 2nd prev
														//  000 = 2 in prev, 8 in 2nd prev
const int SIO_RR1_ALL_SENT					= 0x01;		// D0 = All sent

// SIO read register 2 (CH-B only)
const int SIO_RR2_VECTOR_MASK				= 0xff;		// D7-D0 = Interrupt vector


const UINT8 z80sio_device::k_int_priority[] =
{
	INT_CHA_RECEIVE,
	INT_CHA_TRANSMIT,
	INT_CHA_STATUS,
	INT_CHA_ERROR,
	INT_CHB_RECEIVE,
	INT_CHB_TRANSMIT,
	INT_CHB_STATUS,
	INT_CHB_ERROR
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/


/*

    Interrupt priorities:
        Ch A receive
        Ch A transmit
        Ch A external/status
        Ch B receive
        Ch B transmit
        Ch B external/status


    Initial configuration (both channels):
        005D:sio_reg_w(0,4) = 44
                    01 = x16 clock mode
                    00 = 8 bit sync character
                    01 = 1 stop bit/character
                    Parity odd
                    Parity disabled

        005D:sio_reg_w(0,3) = C1
                    11 = Rx 8 bits/character
                    No auto enables
                    No enter hunt phase
                    No Rx CRC enable
                    No address search mode
                    No sync character load inhibit
                    Rx enable

        005D:sio_reg_w(0,5) = 68
                    DTR = 0
                    11 = Tx 8 bits/character
                    No send break
                    Tx enable
                    SDLC
                    No RTS
                    No CRC enable

        005D:sio_reg_w(0,2) = 40
                    Vector = 0x40

        005D:sio_reg_w(0,1) = 1D
                    No READY/WAIT
                    No READY/WAIT function
                    No READY/WAIT on R/T
                    11 = int on all Rx characters (parity ignored)
                    Status affects vector
                    No Tx int enable
                    Ext int enable

*/


//**************************************************************************
//  INLINE FUNCTIONS
//**************************************************************************

//-------------------------------------------------
//  update_interrupt_state - update the interrupt
//  state to the external world
//-------------------------------------------------

inline void z80sio_device::update_interrupt_state()
{
	// if we have a callback, update it with the current state
	if (m_config.m_irq_cb != NULL)
		(*m_config.m_irq_cb)(this, (z80daisy_irq_state() & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE);
}


//-------------------------------------------------
//  set_interrupt - set the given interrupt state
//  on this channel and update the overall device
//  state
//-------------------------------------------------

inline void z80sio_device::sio_channel::set_interrupt(int type)
{
	int inum = (this == &m_device->m_channel[0] ? 4 : 0) + type;
	m_device->m_int_state[inum] = Z80_DAISY_INT;
	m_device->update_interrupt_state();
}


//-------------------------------------------------
//  clear_interrupt - clear the given interrupt
//  state on this channel and update the overall
//  device state
//-------------------------------------------------

inline void z80sio_device::sio_channel::clear_interrupt(int type)
{
	int inum = (this == &m_device->m_channel[0] ? 4 : 0) + type;
	m_device->m_int_state[inum] &= ~Z80_DAISY_INT;
	m_device->update_interrupt_state();
}


//-------------------------------------------------
//  compute_time_per_character - compute the
//  serial clocking period
//-------------------------------------------------

inline attotime z80sio_device::sio_channel::compute_time_per_character()
{
	// fix me -- should compute properly and include data, stop, parity bit
	return attotime_mul(ATTOTIME_IN_HZ(9600), 10);
}



//**************************************************************************
//  DEVICE CONFIGURATION
//**************************************************************************

//-------------------------------------------------
//  z80sio_device_config - constructor
//-------------------------------------------------

z80sio_device_config::z80sio_device_config(const machine_config &mconfig, const char *tag, const device_config *owner, UINT32 clock)
	: device_config(mconfig, static_alloc_device_config, "Zilog Z80 SIO", tag, owner, clock),
	  device_config_z80daisy_interface(mconfig, *this)
{
}


//-------------------------------------------------
//  static_alloc_device_config - allocate a new
//  configuration object
//-------------------------------------------------

device_config *z80sio_device_config::static_alloc_device_config(const machine_config &mconfig, const char *tag, const device_config *owner, UINT32 clock)
{
	return global_alloc(z80sio_device_config(mconfig, tag, owner, clock));
}


//-------------------------------------------------
//  alloc_device - allocate a new device object
//-------------------------------------------------

device_t *z80sio_device_config::alloc_device(running_machine &machine) const
{
	return auto_alloc(&machine, z80sio_device(machine, *this));
}


//-------------------------------------------------
//  device_config_complete - perform any
//  operations now that the configuration is
//  complete
//-------------------------------------------------

void z80sio_device_config::device_config_complete()
{
	// inherit a copy of the static data
	const z80sio_interface *intf = reinterpret_cast<const z80sio_interface *>(static_config());
	if (intf != NULL)
		*static_cast<z80sio_interface *>(this) = *intf;

	// or initialize to defaults if none provided
	else
	{
		m_irq_cb = NULL;
		m_dtr_changed_cb = NULL;
		m_rts_changed_cb = NULL;
		m_break_changed_cb = NULL;
		m_transmit_cb = NULL;
		m_receive_poll_cb = NULL;
	}
}



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

//-------------------------------------------------
//  z80sio_device - constructor
//-------------------------------------------------

z80sio_device::z80sio_device(running_machine &_machine, const z80sio_device_config &config)
	: device_t(_machine, config),
	  device_z80daisy_interface(_machine, config, *this),
	  m_config(config)
{
}


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

void z80sio_device::device_start()
{
	m_channel[0].start(this, 0);
	m_channel[1].start(this, 1);
}


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

void z80sio_device::device_reset()
{
	// loop over channels
	for (int ch = 0; ch < 2; ch++)
		m_channel[ch].reset();
}



//**************************************************************************
//  DAISY CHAIN INTERFACE
//**************************************************************************

//-------------------------------------------------
//  z80daisy_irq_state - return the overall IRQ
//  state for this device
//-------------------------------------------------

int z80sio_device::z80daisy_irq_state()
{
	int state = 0;

	VPRINTF(("sio IRQ state = B:%d%d%d%d A:%d%d%d%d\n",
				m_int_state[0], m_int_state[1], m_int_state[2], m_int_state[3],
				m_int_state[4], m_int_state[5], m_int_state[6], m_int_state[7]));

	// loop over all interrupt sources
	for (int irqsource = 0; irqsource < 8; irqsource++)
	{
		int inum = k_int_priority[irqsource];

		// if we're servicing a request, don't indicate more interrupts
		if (m_int_state[inum] & Z80_DAISY_IEO)
		{
			state |= Z80_DAISY_IEO;
			break;
		}
		state |= m_int_state[inum];
	}

	return state;
}


//-------------------------------------------------
//  z80daisy_irq_ack - acknowledge an IRQ and
//  return the appropriate vector
//-------------------------------------------------

int z80sio_device::z80daisy_irq_ack()
{
	// loop over all interrupt sources
	for (int irqsource = 0; irqsource < 8; irqsource++)
	{
		int inum = k_int_priority[irqsource];

		// find the first channel with an interrupt requested
		if (m_int_state[inum] & Z80_DAISY_INT)
		{
			VPRINTF(("sio IRQAck %d\n", inum));

			// clear interrupt, switch to the IEO state, and update the IRQs
			m_int_state[inum] = Z80_DAISY_IEO;
			update_interrupt_state();
			return m_channel[1].m_regs[2] + inum * 2;
		}
	}

	logerror("z80sio_irq_ack: failed to find an interrupt to ack!\n");
	return m_channel[1].m_regs[2];
}


//-------------------------------------------------
//  z80daisy_irq_reti - clear the interrupt
//  pending state to allow other interrupts through
//-------------------------------------------------

void z80sio_device::z80daisy_irq_reti()
{
	// loop over all interrupt sources
	for (int irqsource = 0; irqsource < 8; irqsource++)
	{
		int inum = k_int_priority[irqsource];

		// find the first channel with an IEO pending
		if (m_int_state[inum] & Z80_DAISY_IEO)
		{
			VPRINTF(("sio IRQReti %d\n", inum));

			// clear the IEO state and update the IRQs
			m_int_state[inum] &= ~Z80_DAISY_IEO;
			update_interrupt_state();
			return;
		}
	}

	logerror("z80sio_irq_reti: failed to find an interrupt to clear IEO on!\n");
}



//**************************************************************************
//  SIO CHANNEL
//**************************************************************************

//-------------------------------------------------
//  sio_channel - constructor
//-------------------------------------------------

z80sio_device::sio_channel::sio_channel()
	: m_device(NULL),
	  m_index(0),
	  m_inbuf(-1),
	  m_outbuf(-1),
	  m_int_on_next_rx(false),
	  m_receive_timer(NULL),
	  m_receive_inptr(0),
	  m_receive_outptr(0)
{
	memset(m_regs, 0, sizeof(m_regs));
	memset(m_status, 0, sizeof(m_status));
	memset(m_receive_buffer, 0, sizeof(m_receive_buffer));
}


//-------------------------------------------------
//  start - channel-specific startup
//-------------------------------------------------

void z80sio_device::sio_channel::start(z80sio_device *device, int index)
{
	m_device = device;
	m_index = index;
	m_receive_timer = timer_alloc(&m_device->m_machine, static_serial_callback, this);
}


//-------------------------------------------------
//  reset - reset a single SIO channel
//-------------------------------------------------

void z80sio_device::sio_channel::reset()
{
	m_status[0] = SIO_RR0_TX_BUFFER_EMPTY;
	m_status[1] = 0x00;
	m_status[2] = 0x00;
	m_int_on_next_rx = false;
	m_outbuf = -1;

	// reset interrupts
	clear_interrupt(INT_TRANSMIT);
	clear_interrupt(INT_STATUS);
	clear_interrupt(INT_RECEIVE);
	clear_interrupt(INT_ERROR);

	// start the receive timer running
	attotime tpc = compute_time_per_character();
	timer_adjust_periodic(m_receive_timer, tpc, 0, tpc);
}


//-------------------------------------------------
//  control_write - write to a control register
//-------------------------------------------------

void z80sio_device::sio_channel::control_write(UINT8 data)
{
	int regnum = m_regs[0] & 7;

	if (regnum != 0 || (regnum & 0xf8) != 0)
		VPRINTF(("%s:sio_reg_w(%c,%d) = %02X\n", cpuexec_describe_context(&m_device->m_machine), 'A' + m_index, regnum, data));

	// write a new value to the selected register
	UINT8 old = m_regs[regnum];
	m_regs[regnum] = data;

	// clear the register number for the next write
	if (regnum != 0)
		m_regs[0] &= ~7;

	// switch off the register for live state changes
	switch (regnum)
	{
		// SIO write register 0
		case 0:
			switch (data & SIO_WR0_COMMAND_MASK)
			{
				case SIO_WR0_COMMAND_CH_RESET:
					VPRINTF(("%s:SIO reset channel %c\n", cpuexec_describe_context(&m_device->m_machine), 'A' + m_index));
					reset();
					break;

				case SIO_WR0_COMMAND_RES_STATUS_INT:
					clear_interrupt(INT_STATUS);
					break;

				case SIO_WR0_COMMAND_ENA_RX_INT:
					m_int_on_next_rx = true;
					m_device->update_interrupt_state();
					break;

				case SIO_WR0_COMMAND_RES_TX_INT:
					clear_interrupt(INT_TRANSMIT);
					break;

				case SIO_WR0_COMMAND_RES_ERROR:
					clear_interrupt(INT_ERROR);
					break;
			}
			break;

		// SIO write register 1
		case 1:
			m_device->update_interrupt_state();
			break;

		// SIO write register 5
		case 5:
			if (((old ^ data) & SIO_WR5_DTR) && m_device->m_config.m_dtr_changed_cb)
				(*m_device->m_config.m_dtr_changed_cb)(m_device, m_index, (data & SIO_WR5_DTR) != 0);
			if (((old ^ data) & SIO_WR5_SEND_BREAK) && m_device->m_config.m_break_changed_cb)
				(*m_device->m_config.m_break_changed_cb)(m_device, m_index, (data & SIO_WR5_SEND_BREAK) != 0);
			if (((old ^ data) & SIO_WR5_RTS) && m_device->m_config.m_rts_changed_cb)
				(*m_device->m_config.m_rts_changed_cb)(m_device, m_index, (data & SIO_WR5_RTS) != 0);
			break;
	}
}


//-------------------------------------------------
//  control_read - read from a control register
//-------------------------------------------------

UINT8 z80sio_device::sio_channel::control_read()
{
	int regnum = m_regs[0] & 7;
	UINT8 result = m_status[regnum];

	// switch off the register for live state changes
	switch (regnum)
	{
		// SIO read register 0
		case 0:
			result &= ~SIO_RR0_INT_PENDING;
			if (m_device->z80daisy_irq_state() & Z80_DAISY_INT)
				result |= SIO_RR0_INT_PENDING;
			break;
	}

	VPRINTF(("%s:sio_reg_r(%c,%d) = %02x\n", cpuexec_describe_context(&m_device->m_machine), 'A' + m_index, regnum, m_status[regnum]));

	return m_status[regnum];
}


//-------------------------------------------------
//  data_write - write to a data register
//-------------------------------------------------

void z80sio_device::sio_channel::data_write(UINT8 data)
{
	VPRINTF(("%s:sio_data_w(%c) = %02X\n", cpuexec_describe_context(&m_device->m_machine), 'A' + m_index, data));

	// if tx not enabled, just ignore it
	if (!(m_regs[5] & SIO_WR5_TX_ENABLE))
		return;

	// update the status register
	m_status[0] &= ~SIO_RR0_TX_BUFFER_EMPTY;

	// reset the transmit interrupt
	clear_interrupt(INT_TRANSMIT);

	// stash the character
	m_outbuf = data;
}


//-------------------------------------------------
//  data_read - read from a data register
//-------------------------------------------------

UINT8 z80sio_device::sio_channel::data_read()
{
	// update the status register
	m_status[0] &= ~SIO_RR0_RX_CHAR_AVAILABLE;

	// reset the receive interrupt
	clear_interrupt(INT_RECEIVE);

	VPRINTF(("%s:sio_data_r(%c) = %02X\n", cpuexec_describe_context(&m_device->m_machine), 'A' + m_index, m_inbuf));

	return m_inbuf;
}


//-------------------------------------------------
//  dtr - return the state of the DTR line
//-------------------------------------------------

int z80sio_device::sio_channel::dtr()
{
	return ((m_regs[5] & SIO_WR5_DTR) != 0);
}


//-------------------------------------------------
//  rts - return the state of the RTS line
//-------------------------------------------------

int z80sio_device::sio_channel::rts()
{
	return ((m_regs[5] & SIO_WR5_RTS) != 0);
}


//-------------------------------------------------
//  set_cts - set the state of the CTS line
//-------------------------------------------------

void z80sio_device::sio_channel::set_cts(int state)
{
	timer_call_after_resynch(&m_device->m_machine, this, (SIO_RR0_CTS << 1) + (state != 0), static_change_input_line);
}


//-------------------------------------------------
//  set_dcd - set the state of the DCD line
//-------------------------------------------------

void z80sio_device::sio_channel::set_dcd(int state)
{
	timer_call_after_resynch(&m_device->m_machine, this, (SIO_RR0_DCD << 1) + (state != 0), static_change_input_line);
}


//-------------------------------------------------
//  receive_data - receive data on the input lines
//-------------------------------------------------

void z80sio_device::sio_channel::receive_data(int data)
{
	// put it on the queue
	int newinptr = (m_receive_inptr + 1) % ARRAY_LENGTH(m_receive_buffer);
	if (newinptr != m_receive_outptr)
	{
		m_receive_buffer[m_receive_inptr] = data;
		m_receive_inptr = newinptr;
	}
	else
		logerror("z80sio_receive_data: buffer overrun\n");
}


//-------------------------------------------------
//  change_input_line - generically change the
//  state of an input line; designed to be called
//  from a timer callback
//-------------------------------------------------

void z80sio_device::sio_channel::change_input_line(int line, int state)
{
	VPRINTF(("sio_change_input_line(%c, %s) = %d\n", 'A' + m_index, (line == SIO_RR0_CTS) ? "CTS" : "DCD", state));

	// remember the old value
	UINT8 old = m_status[0];

	// set the bit in the status register
	m_status[0] &= ~line;
	if (state)
		m_status[0] |= line;

	// if state change interrupts are enabled, signal
	if (((old ^ m_status[0]) & line) && (m_regs[1] & SIO_WR1_STATUSINT_ENABLE))
		set_interrupt(INT_STATUS);
}


//-------------------------------------------------
//  serial_callback - callback to pump
//  data through
//-------------------------------------------------

void z80sio_device::sio_channel::serial_callback()
{
	int data = -1;

	// first perform any outstanding transmits
	if (m_outbuf != -1)
	{
		VPRINTF(("serial_callback(%c): Transmitting %02x\n", 'A' + m_index, m_outbuf));

		// actually transmit the character
		if (m_device->m_config.m_transmit_cb != NULL)
			(*m_device->m_config.m_transmit_cb)(m_device, m_index, m_outbuf);

		// update the status register
		m_status[0] |= SIO_RR0_TX_BUFFER_EMPTY;

		// set the transmit buffer empty interrupt if enabled
		if (m_regs[1] & SIO_WR1_TXINT_ENABLE)
			set_interrupt(INT_TRANSMIT);

		// reset the output buffer
		m_outbuf = -1;
	}

	// ask the polling callback if there is data to receive
	if (m_device->m_config.m_receive_poll_cb != NULL)
		data = (*m_device->m_config.m_receive_poll_cb)(m_device, m_index);

	// if we have buffered data, pull it
	if (m_receive_inptr != m_receive_outptr)
	{
		data = m_receive_buffer[m_receive_outptr];
		m_receive_outptr = (m_receive_outptr + 1) % ARRAY_LENGTH(m_receive_buffer);
	}

	// if we have data, receive it
	if (data != -1)
	{
		VPRINTF(("serial_callback(%c): Receiving %02x\n", 'A' + m_index, data));

		// if rx not enabled, just ignore it
		if (!(m_regs[3] & SIO_WR3_RX_ENABLE))
		{
			VPRINTF(("  (ignored because receive is disabled)\n"));
			return;
		}

		// stash the data and update the status
		m_inbuf = data;
		m_status[0] |= SIO_RR0_RX_CHAR_AVAILABLE;

		// update our interrupt state
		switch (m_regs[1] & SIO_WR1_RXINT_MASK)
		{
			case SIO_WR1_RXINT_FIRST:
				if (!m_int_on_next_rx)
					break;

			case SIO_WR1_RXINT_ALL_NOPARITY:
			case SIO_WR1_RXINT_ALL_PARITY:
				set_interrupt(INT_RECEIVE);
				break;
		}
		m_int_on_next_rx = false;
	}
}



//**************************************************************************
//  GLOBAL STUBS
//**************************************************************************

WRITE8_DEVICE_HANDLER( z80sio_c_w ) { downcast<z80sio_device *>(device)->control_write(offset & 1, data); }
WRITE8_DEVICE_HANDLER( z80sio_d_w ) { downcast<z80sio_device *>(device)->data_write(offset & 1, data); }
READ8_DEVICE_HANDLER( z80sio_c_r ) { return downcast<z80sio_device *>(device)->control_read(offset & 1); }
READ8_DEVICE_HANDLER( z80sio_d_r ) { return downcast<z80sio_device *>(device)->data_read(offset & 1); }

READ8_DEVICE_HANDLER( z80sio_get_dtr ) { return downcast<z80sio_device *>(device)->dtr(offset & 1); }
READ8_DEVICE_HANDLER( z80sio_get_rts ) { return downcast<z80sio_device *>(device)->rts(offset & 1); }

WRITE8_DEVICE_HANDLER( z80sio_set_cts ) { downcast<z80sio_device *>(device)->set_cts(offset & 1, data); }
WRITE8_DEVICE_HANDLER( z80sio_set_dcd ) { downcast<z80sio_device *>(device)->set_dcd(offset & 1, data); }
WRITE8_DEVICE_HANDLER( z80sio_receive_data ) { downcast<z80sio_device *>(device)->receive_data(offset & 1, data); }

READ8_DEVICE_HANDLER( z80sio_cd_ba_r )
{
	switch (offset & 3)
	{
		case 0: return z80sio_d_r(device, 0);
		case 1: return z80sio_d_r(device, 1);
		case 2: return z80sio_c_r(device, 0);
		case 3: return z80sio_c_r(device, 1);
	}

	return 0xff;
}

WRITE8_DEVICE_HANDLER( z80sio_cd_ba_w )
{
	switch (offset & 3)
	{
		case 0: z80sio_d_w(device, 0, data); break;
		case 1: z80sio_d_w(device, 1, data); break;
		case 2: z80sio_c_w(device, 0, data); break;
		case 3: z80sio_c_w(device, 1, data); break;
	}
}

READ8_DEVICE_HANDLER( z80sio_ba_cd_r )
{
	switch (offset & 3)
	{
		case 0: return z80sio_d_r(device, 0);
		case 1: return z80sio_c_r(device, 0);
		case 2: return z80sio_d_r(device, 1);
		case 3: return z80sio_c_r(device, 1);
	}

	return 0xff;
}

WRITE8_DEVICE_HANDLER( z80sio_ba_cd_w )
{
	switch (offset & 3)
	{
		case 0: z80sio_d_w(device, 0, data); break;
		case 1: z80sio_c_w(device, 0, data); break;
		case 2: z80sio_d_w(device, 1, data); break;
		case 3: z80sio_c_w(device, 1, data); break;
	}
}

const device_type Z80SIO = z80sio_device_config::static_alloc_device_config;
