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

    6850acia.c

    6850 ACIA code

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

#include "emu.h"
#include "6850acia.h"
#include "devhelpr.h"


/***************************************************************************
    MACROS
***************************************************************************/

#define LOG 0

#define CR1_0	0x03
#define CR4_2	0x1C
#define CR6_5	0x60
#define CR7		0x80

#define TXD(_data) \
	m_out_tx_func(_data)

#define RTS(_data) \
	m_out_rts_func(_data)

/***************************************************************************
    LOCAL VARIABLES
***************************************************************************/

const int acia6850_device::ACIA6850_DIVIDE[3] = { 1, 16, 64 };

const int acia6850_device::ACIA6850_WORD[8][3] =
{
	{ 7, EVEN, 2 },
	{ 7, ODD,  2 },
	{ 7, EVEN, 1 },
	{ 7, ODD,  1 },
	{ 8, NONE, 2 },
	{ 8, NONE, 1 },
	{ 8, EVEN, 1 },
	{ 8, ODD,  1 }
};



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

// device type definition
const device_type ACIA6850 = &device_creator<acia6850_device>;

//-------------------------------------------------
//  acia6850_device - constructor
//-------------------------------------------------

acia6850_device::acia6850_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, ACIA6850, "6850 ACIA", tag, owner, clock)
{
	memset(static_cast<acia6850_interface *>(this), 0, sizeof(acia6850_interface));
}


//-------------------------------------------------
//  static_set_interface - set the interface
//  struct
//-------------------------------------------------

void acia6850_device::static_set_interface(device_t &device, const acia6850_interface &interface)
{
	acia6850_device &ptm = downcast<acia6850_device &>(device);
	static_cast<acia6850_interface &>(ptm) = interface;
}


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

void acia6850_device::device_start()
{
	// resolve callbacks
	m_in_rx_func.resolve(m_in_rx_cb, *this);
	m_out_tx_func.resolve(m_out_tx_cb, *this);
	m_in_cts_func.resolve(m_in_cts_cb, *this);
	m_out_rts_func.resolve(m_out_rts_cb, *this);
	m_in_dcd_func.resolve(m_in_dcd_cb, *this);
	m_out_irq_func.resolve(m_out_irq_cb, *this);

	m_tx_counter = 0;
	m_rx_counter = 0;
	m_rx_timer = timer_alloc(TIMER_ID_RECEIVE);
	m_tx_timer = timer_alloc(TIMER_ID_TRANSMIT);
	m_first_reset = 1;
	m_status_read = 0;
	m_brk = 0;

	m_rx_timer->reset();
	m_tx_timer->reset();

	save_item(NAME(m_ctrl));
	save_item(NAME(m_status));
	save_item(NAME(m_rx_clock));
	save_item(NAME(m_tx_clock));
	save_item(NAME(m_rx_counter));
	save_item(NAME(m_tx_counter));
	save_item(NAME(m_rx_shift));
	save_item(NAME(m_tx_shift));
	save_item(NAME(m_rdr));
	save_item(NAME(m_tdr));
	save_item(NAME(m_rx_bits));
	save_item(NAME(m_tx_bits));
	save_item(NAME(m_rx_parity));
	save_item(NAME(m_tx_parity));
	save_item(NAME(m_tx_int));

	save_item(NAME(m_divide));
	save_item(NAME(m_overrun));
	save_item(NAME(m_reset));
	save_item(NAME(m_first_reset));
	save_item(NAME(m_rts));
	save_item(NAME(m_brk));
	save_item(NAME(m_status_read));
}


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

void acia6850_device::device_reset()
{
	int cts = m_in_cts_func();
	int dcd = m_in_dcd_func();

	m_status = (cts << 3) | (dcd << 2) | ACIA6850_STATUS_TDRE;
	m_tdr = 0;
	m_rdr = 0;
	m_tx_shift = 0;
	m_rx_shift = 0;
	m_tx_counter = 0;
	m_rx_counter = 0;

	TXD(1);
	m_overrun = 0;
	m_status_read = 0;
	m_brk = 0;

	m_rx_state = START;
	m_tx_state = START;
	m_irq = 0;

	m_out_irq_func(1);

	if (m_first_reset)
	{
		m_first_reset = 0;

		RTS(1);
	}
	else
	{
		RTS(m_rts);
	}
}


//-------------------------------------------------
//  device_timer - handle timer callbacks
//-------------------------------------------------

void acia6850_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
		case TIMER_ID_TRANSMIT:
			tx_tick();
			m_tx_counter = 0;
			break;

		case TIMER_ID_RECEIVE:
			rx_tick();
			m_rx_counter = 0;
			break;
	}
}


//-------------------------------------------------
//  acia6850_stat_r - Read Status Register
//-------------------------------------------------

READ8_MEMBER( acia6850_device::status_read )
{
	UINT8 status;

	m_status_read = 1;
	status = m_status;

	if (status & ACIA6850_STATUS_CTS)
	{
		status &= ~ACIA6850_STATUS_TDRE;
	}

	return status;
}


//-------------------------------------------------
//  control_write - Write Control Register
//-------------------------------------------------

WRITE8_MEMBER( acia6850_device::control_write )
{
	if (LOG) logerror("MC6850 '%s' Control: %02x\n", tag(), data);

	// Counter Divide Select Bits

	int divide = data & CR1_0;

	if (divide == 3)
	{
		m_reset = 1;
		device_reset();
	}
	else
	{
		m_reset = 0;
		m_divide = ACIA6850_DIVIDE[divide];
	}

	// Word Select Bits

	int wordsel = (data & CR4_2) >> 2;

	m_bits = ACIA6850_WORD[wordsel][0];
	m_parity = (parity_type)ACIA6850_WORD[wordsel][1];
	m_stopbits = ACIA6850_WORD[wordsel][2];

	// Transmitter Control Bits

	switch ((data & CR6_5) >> 5)
	{
	case 0:
		m_rts = 0;
		RTS(m_rts);

		m_tx_int = 0;
		m_brk = 0;
		break;

	case 1:
		m_rts = 0;
		RTS(m_rts);

		m_tx_int = 1;
		m_brk = 0;
		break;

	case 2:
		m_rts = 1;
		RTS(m_rts);

		m_tx_int = 0;
		m_brk = 0;
		break;

	case 3:
		m_rts = 0;
		RTS(m_rts);

		m_tx_int = 0;
		m_brk = 1;
		break;
	}

	check_interrupts();

	// After writing the word type, set the rx/tx clocks (provided the divide values have changed)

	if ((m_ctrl ^ data) & CR1_0)
	{
		if (!m_reset)
		{
			if (m_rx_clock)
			{
				attotime rx_period = attotime::from_hz(m_rx_clock) *  m_divide;
				m_rx_timer->adjust(rx_period, 0, rx_period);
			}

			if (m_tx_clock)
			{
				attotime tx_period = attotime::from_hz(m_tx_clock) * m_divide;
				m_tx_timer->adjust(tx_period, 0, tx_period);
			}
		}
	}
	m_ctrl = data;
}


//-------------------------------------------------
//  check_interrupts
//-------------------------------------------------

void acia6850_device::check_interrupts()
{
	int irq = (m_tx_int && (m_status & ACIA6850_STATUS_TDRE) && (~m_status & ACIA6850_STATUS_CTS)) ||
		((m_ctrl & 0x80) && ((m_status & (ACIA6850_STATUS_RDRF|ACIA6850_STATUS_DCD)) || m_overrun));

	if (irq != m_irq)
	{
		m_irq = irq;

		if (irq)
		{
			m_status |= ACIA6850_STATUS_IRQ;
			m_out_irq_func(0);
		}
		else
		{
			m_status &= ~ACIA6850_STATUS_IRQ;
			m_out_irq_func(1);
		}
	}
}


//-------------------------------------------------
//  data_write - Write transmit register
//-------------------------------------------------

WRITE8_MEMBER( acia6850_device::data_write )
{
	if (LOG) logerror("MC6850 '%s' Data: %02x\n", tag(), data);

	if (!m_reset)
	{
		m_tdr = data;
		m_status &= ~ACIA6850_STATUS_TDRE;
		check_interrupts();
	}
	else
	{
		logerror("%s:ACIA %p: Data write while in reset!\n", machine().describe_context(), this);
	}
}


//-------------------------------------------------
//  data_r - Read character
//-------------------------------------------------

READ8_MEMBER( acia6850_device::data_read )
{
	m_status &= ~(ACIA6850_STATUS_RDRF | ACIA6850_STATUS_IRQ | ACIA6850_STATUS_PE);

	if (m_status_read)
	{
		int dcd = m_in_dcd_func();

		m_status_read = 0;
		m_status &= ~(ACIA6850_STATUS_OVRN | ACIA6850_STATUS_DCD);

		if (dcd)
		{
			m_status |= ACIA6850_STATUS_DCD;
		}
	}

	if (m_overrun == 1)
	{
		m_status |= ACIA6850_STATUS_OVRN;
		m_overrun = 0;
	}

	check_interrupts();

	return m_rdr;
}


//-------------------------------------------------
//  tx_tick - Transmit a bit
//-------------------------------------------------

void acia6850_device::tx_tick()
{
	switch (m_tx_state)
	{
		case START:
		{
			if (m_brk)
			{
				// transmit break

				TXD(0);
			}
			else
			{
				int _cts = m_in_cts_func();

				if (_cts)
				{
					m_status |= ACIA6850_STATUS_CTS;
				}
				else
				{
					m_status &= ~ACIA6850_STATUS_CTS;
				}

				check_interrupts();

				if (m_status & ACIA6850_STATUS_TDRE)
				{
					// transmitter idle
					TXD(1);
				}
				else
				{
					// transmit character

					if (LOG) logerror("MC6850 '%s': TX DATA %x\n", tag(), m_tdr);
					if (LOG) logerror("MC6850 '%s': TX START BIT\n", tag());

					TXD(0);

					m_tx_bits = m_bits;
					m_tx_shift = m_tdr;
					m_tx_parity = 0;
					m_tx_state = DATA;
				}
			}
			break;
		}
		case DATA:
		{
			int val = m_tx_shift & 1;
			if (LOG) logerror("MC6850 '%s': TX DATA BIT %x\n", tag(), val);

			TXD(val);
			m_tx_parity ^= val;
			m_tx_shift >>= 1;

			if (--(m_tx_bits) == 0)
			{
				m_tx_state = (m_parity == NONE) ? STOP : PARITY;
			}

			break;
		}
		case PARITY:
		{
			int parity = 0;

			if (m_parity == EVEN)
			{
				parity = (m_tx_parity & 1) ? 1 : 0;
			}
			else
			{
				parity = (m_tx_parity & 1) ? 0 : 1;
			}

			TXD(parity);

			if (LOG) logerror("MC6850 '%s': TX PARITY BIT %x\n", tag(), parity);
			m_tx_state = STOP;
			break;
		}
		case STOP:
		{
			if (LOG) logerror("MC6850 '%s': TX STOP BIT\n", tag());
			TXD(1);

			if (m_stopbits == 1)
			{
				m_tx_state = START;
				m_status |= ACIA6850_STATUS_TDRE;
			}
			else
			{
				m_tx_state = STOP2;
			}
			break;
		}
		case STOP2:
		{
			if (LOG) logerror("MC6850 '%s': TX STOP BIT\n", tag());
			TXD(1);
			m_tx_state = START;
			m_status |= ACIA6850_STATUS_TDRE;
			break;
		}
	}
}


//-------------------------------------------------
//  tx_clock_in - As above, but using the tx pin
//-------------------------------------------------

void acia6850_device::tx_clock_in()
{
	int _cts = m_in_cts_func();

	if (_cts)
	{
		m_status |= ACIA6850_STATUS_CTS;
	}
	else
	{
		m_status &= ~ACIA6850_STATUS_CTS;
	}

	m_tx_counter ++;

	if ( m_tx_counter > m_divide - 1)
	{
		tx_tick();
		m_tx_counter = 0;
	}

}


//-------------------------------------------------
//  rx_tick - Receive a bit
//-------------------------------------------------

void acia6850_device::rx_tick()
{
	int dcd = m_in_dcd_func();

	if (dcd)
	{
		m_status |= ACIA6850_STATUS_DCD;
		check_interrupts();
	}
	else if ((m_status & (ACIA6850_STATUS_DCD | ACIA6850_STATUS_IRQ)) == ACIA6850_STATUS_DCD)
	{
		m_status &= ~ACIA6850_STATUS_DCD;
	}

	if (m_status & ACIA6850_STATUS_DCD)
	{
		m_rx_state = START;
	}
	else
	{
		int rxd = m_in_rx_func();

		switch (m_rx_state)
		{
			case START:
			{
				if (rxd == 0)
				{
					if (LOG) logerror("MC6850 '%s': RX START BIT\n", tag());
					m_rx_shift = 0;
					m_rx_parity = 0;
					m_rx_bits = m_bits;
					m_rx_state = DATA;
				}
				break;
			}
			case DATA:
			{
				if (LOG) logerror("MC6850 '%s': RX DATA BIT %x\n", tag(), rxd);
				m_rx_shift |= rxd ? 0x80 : 0;
				m_rx_parity ^= rxd;

				if (--m_rx_bits == 0)
				{
					if (m_status & ACIA6850_STATUS_RDRF)
					{
						m_overrun = 1;
						check_interrupts();
					}

					m_rx_state = m_parity == NONE ? STOP : PARITY;
				}
				else
				{
					m_rx_shift >>= 1;
				}
				break;
			}
			case PARITY:
			{
				if (LOG) logerror("MC6850 '%s': RX PARITY BIT %x\n", tag(), rxd);
				m_rx_parity ^= rxd;

				if (m_parity == EVEN)
				{
					if (m_rx_parity)
					{
						m_status |= ACIA6850_STATUS_PE;
					}
				}
				else
				{
					if (!m_rx_parity)
					{
						m_status |= ACIA6850_STATUS_PE;
					}
				}

				m_rx_state = STOP;
				break;
			}
			case STOP:
			{
				if (rxd == 1)
				{
					if (LOG) logerror("MC6850 '%s': RX STOP BIT\n", tag());
					if (m_stopbits == 1)
					{
						m_status &= ~ACIA6850_STATUS_FE;

						if (!(m_status & ACIA6850_STATUS_RDRF))
						{
							if (LOG) logerror("MC6850 '%s': RX DATA %x\n", tag(), m_rx_shift);
							m_rdr = m_rx_shift;
							m_status |= ACIA6850_STATUS_RDRF;
							check_interrupts();
						}

						m_rx_state = START;
					}
					else
					{
						m_rx_state = STOP2;
					}
				}
				else
				{
					m_status |= ACIA6850_STATUS_FE;
					m_rx_state = START;
				}
				break;
			}
			case STOP2:
			{
				if (rxd == 1)
				{
					if (LOG) logerror("MC6850 '%s': RX STOP BIT\n", tag());
					m_status &= ~ACIA6850_STATUS_FE;

					if (!(m_status & ACIA6850_STATUS_RDRF))
					{
						if (LOG) logerror("MC6850 '%s': RX DATA %x\n", tag(), m_rx_shift);
						m_rdr = m_rx_shift;
						m_status |= ACIA6850_STATUS_RDRF;
						check_interrupts();
					}

					m_rx_state = START;
				}
				else
				{
					m_status |= ACIA6850_STATUS_FE;
					m_rx_state = START;
				}
				break;
			}
		}
	}
}


//-------------------------------------------------
//  rx_clock_in - As above, but using the rx pin
//-------------------------------------------------

void acia6850_device::rx_clock_in()
{
	int dcd = m_in_dcd_func();

	if (dcd)
	{
		m_status |= ACIA6850_STATUS_DCD;
		check_interrupts();
	}
	else if ((m_status & (ACIA6850_STATUS_DCD|ACIA6850_STATUS_IRQ)) == ACIA6850_STATUS_DCD)
	{
		m_status &= ~ACIA6850_STATUS_DCD;
	}

	m_rx_counter ++;

	if ( m_rx_counter > m_divide - 1)
	{
		rx_tick();
		m_rx_counter = 0;
	}
}


//-------------------------------------------------
//  set_rx_clock - set receiver clock
//-------------------------------------------------

void acia6850_device::set_rx_clock(int clock)
{
	m_rx_clock = clock;

	if (m_rx_clock)
	{
		attotime rx_period = attotime::from_hz(m_rx_clock) * m_divide;
		m_rx_timer->adjust(rx_period, 0, rx_period);
	}
}


//-------------------------------------------------
//  set_tx_clock - set receiver clock
//-------------------------------------------------

void acia6850_device::set_tx_clock(int clock)
{
	m_tx_clock = clock;

	if (m_tx_clock)
	{
		attotime tx_period = attotime::from_hz(m_tx_clock) * m_divide;
		m_tx_timer->adjust(tx_period, 0, tx_period);
	}
}


//-------------------------------------------------
//  receive_data - receive data byte
//-------------------------------------------------

void acia6850_device::receive_data(UINT8 data)
{
	m_rdr = data;
	m_status |= ACIA6850_STATUS_RDRF;
	check_interrupts();
}
