// license:BSD-3-Clause
// copyright-holders:smf
/*********************************************************************
6850acia.c
6850 ACIA code
*********************************************************************/
#include "emu.h"
#include "6850acia.h"
/***************************************************************************
MACROS
***************************************************************************/
#define VERBOSE 1
//#define LOG_OUTPUT_STREAM std::cout
#include "logmacro.h"
/***************************************************************************
LOCAL VARIABLES
***************************************************************************/
const int acia6850_device::counter_divide_select[4] =
{
1,
16,
64,
0
};
const int acia6850_device::word_select[8][3] =
{
// word length, parity, stop bits
{ 7, PARITY_EVEN, 2 },
{ 7, PARITY_ODD, 2 },
{ 7, PARITY_EVEN, 1 },
{ 7, PARITY_ODD, 1 },
{ 8, PARITY_NONE, 2 },
{ 8, PARITY_NONE, 1 },
{ 8, PARITY_EVEN, 1 },
{ 8, PARITY_ODD, 1 }
};
const int acia6850_device::transmitter_control[4][3] =
{
// rts, brk, tx irq
{ 0, 0, 0 },
{ 0, 0, 1 },
{ 1, 0, 0 },
{ 0, 1, 0 }
};
/***************************************************************************
LIVE DEVICE
***************************************************************************/
// device type definition
DEFINE_DEVICE_TYPE(ACIA6850, acia6850_device, "acia6850", "MC6850 ACIA")
//-------------------------------------------------
// acia6850_device - constructor
//-------------------------------------------------
acia6850_device::acia6850_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: acia6850_device(mconfig, ACIA6850, tag, owner, clock)
{
}
acia6850_device::acia6850_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, type, tag, owner, clock)
, m_txd_handler(*this)
, m_rts_handler(*this)
, m_irq_handler(*this)
, m_status(SR_TDRE)
, m_tdr(0)
, m_first_master_reset(true)
, m_dcd_irq_pending(false)
, m_overrun_pending(false)
, m_divide(0)
, m_rts(0)
, m_dcd(0)
, m_irq(0)
, m_txc(0)
, m_txd(0)
, m_tx_counter(0)
, m_tx_irq_enable(false)
, m_rxc(0)
, m_rxd(1)
, m_rx_irq_enable(false)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void acia6850_device::device_start()
{
// resolve callbacks
m_txd_handler.resolve_safe();
m_rts_handler.resolve_safe();
m_irq_handler.resolve_safe();
save_item(NAME(m_status));
save_item(NAME(m_tdr));
save_item(NAME(m_rdr));
save_item(NAME(m_first_master_reset));
save_item(NAME(m_dcd_irq_pending));
save_item(NAME(m_overrun_pending));
save_item(NAME(m_divide));
save_item(NAME(m_bits));
save_item(NAME(m_stopbits));
save_item(NAME(m_parity));
save_item(NAME(m_brk));
save_item(NAME(m_rts));
save_item(NAME(m_dcd));
save_item(NAME(m_irq));
save_item(NAME(m_txc));
save_item(NAME(m_txd));
save_item(NAME(m_tx_state));
save_item(NAME(m_tx_bits));
save_item(NAME(m_tx_shift));
save_item(NAME(m_tx_parity));
save_item(NAME(m_tx_counter));
save_item(NAME(m_tx_irq_enable));
save_item(NAME(m_rxc));
save_item(NAME(m_rxd));
save_item(NAME(m_rx_state));
save_item(NAME(m_rx_bits));
save_item(NAME(m_rx_shift));
save_item(NAME(m_rx_parity));
save_item(NAME(m_rx_counter));
save_item(NAME(m_rx_irq_enable));
}
void acia6850_device::device_reset()
{
output_txd(1);
output_rts(1);
output_irq(1);
}
READ8_MEMBER( acia6850_device::status_r )
{
uint8_t status = m_status;
if (status & SR_CTS)
{
status &= ~SR_TDRE;
}
if (m_dcd_irq_pending == DCD_IRQ_READ_STATUS)
{
m_dcd_irq_pending = DCD_IRQ_READ_DATA;
}
return status;
}
WRITE8_MEMBER( acia6850_device::control_w )
{
LOG("MC6850 '%s' Control: %02x\n", tag(), data);
// CR0 & CR1
int counter_divide_select_bits = (data >> 0) & 3;
m_divide = counter_divide_select[counter_divide_select_bits];
// CR2, CR3 & CR4
int word_select_bits = (data >> 2) & 7;
m_bits = word_select[word_select_bits][0];
m_parity = word_select[word_select_bits][1];
m_stopbits = word_select[word_select_bits][2];
// CR5 & CR6
int transmitter_control_bits = (data >> 5) & 3;
int rts = transmitter_control[transmitter_control_bits][0];
m_brk = transmitter_control[transmitter_control_bits][1];
m_tx_irq_enable = transmitter_control[transmitter_control_bits][2];
// CR7
m_rx_irq_enable = (data >> 7) & 1;
if (m_divide == 0)
{
if (m_first_master_reset)
{
/// TODO: find out whether you need to leave master reset before it stops counting as the first
rts = 1;
m_first_master_reset = false;
}
m_dcd_irq_pending = DCD_IRQ_NONE;
m_overrun_pending = false;
m_rx_state = STATE_START;
m_rx_counter = 0;
m_tx_state = STATE_START;
output_txd(1);
m_status &= SR_CTS;
if (m_dcd)
{
m_status |= SR_DCD;
}
}
output_rts(rts);
update_irq();
}
int acia6850_device::calculate_txirq()
{
return !(m_tx_irq_enable && ((m_status & SR_TDRE) && !(m_status & SR_CTS)));
}
int acia6850_device::calculate_rxirq()
{
return !(m_rx_irq_enable && ((m_status & SR_RDRF) || m_dcd_irq_pending != DCD_IRQ_NONE));
}
void acia6850_device::update_irq()
{
output_irq(calculate_txirq() && calculate_rxirq());
}
WRITE8_MEMBER( acia6850_device::data_w )
{
LOG("MC6850 '%s' Data: %02x\n", tag(), data);
/// TODO: find out if data stored during master reset is sent after divider is set
if (m_divide == 0)
{
logerror("%s:ACIA %p: Data write while in reset!\n", machine().describe_context(), (void *)this);
}
/// TODO: find out what happens if TDRE is already clear when you write
m_tdr = data;
m_status &= ~SR_TDRE;
update_irq();
}
READ8_MEMBER( acia6850_device::data_r )
{
if (m_overrun_pending)
{
m_status |= SR_OVRN;
m_overrun_pending = false;
}
else
{
m_status &= ~SR_OVRN;
m_status &= ~SR_RDRF;
}
if (m_dcd_irq_pending == DCD_IRQ_READ_DATA)
{
m_dcd_irq_pending = DCD_IRQ_NONE;
}
update_irq();
return m_rdr;
}
WRITE8_MEMBER( acia6850_device::write )
{
if (BIT(offset, 0))
data_w(space, 0, data);
else
control_w(space, 0, data);
}
READ8_MEMBER( acia6850_device::read )
{
return BIT(offset, 0) ? data_r(space, 0) : status_r(space, 0);
}
DECLARE_WRITE_LINE_MEMBER( acia6850_device::write_cts )
{
if (state)
{
m_status |= SR_CTS;
}
else
{
m_status &= ~SR_CTS;
}
}
DECLARE_WRITE_LINE_MEMBER( acia6850_device::write_dcd )
{
m_dcd = state;
}
WRITE_LINE_MEMBER( acia6850_device::write_rxc )
{
if (m_rxc != state)
{
m_rxc = state;
if (state && m_divide > 0)
{
if (m_dcd)
{
if (!(m_status & SR_DCD))
{
m_status |= SR_DCD;
m_dcd_irq_pending = DCD_IRQ_READ_STATUS;
}
m_rx_state = STATE_START;
m_rx_counter = 0;
}
else
{
if (m_dcd_irq_pending == DCD_IRQ_NONE)
{
m_status &= ~SR_DCD;
}
m_rx_counter++;
switch (m_rx_state)
{
case STATE_START:
if (m_rxd == 0)
{
if (m_rx_counter == 1)
{
LOG("MC6850 '%s': RX START BIT\n", tag());
}
if (m_rx_counter >= m_divide / 2)
{
m_rx_state = STATE_DATA;
m_rx_counter = 0;
m_rx_shift = 0;
m_rx_parity = 0;
m_rx_bits = 0;
}
}
else
{
if (m_rx_counter != 1)
{
LOG("MC6850 '%s': RX false START BIT\n", tag());
}
m_rx_counter = 0;
}
break;
case STATE_DATA:
if (m_rx_counter == m_divide)
{
m_rx_counter = 0;
if (m_rx_bits < m_bits)
{
LOG("MC6850 '%s': RX DATA BIT %d %d\n", tag(), m_rx_bits, m_rxd);
}
else
{
LOG("MC6850 '%s': RX PARITY BIT %x\n", tag(), m_rxd);
}
if (m_rxd)
{
m_rx_shift |= 1 << m_rx_bits;
}
m_rx_bits++;
m_rx_parity ^= m_rxd;
if ((m_rx_bits == m_bits && m_parity == PARITY_NONE) ||
(m_rx_bits == (m_bits + 1) && m_parity != PARITY_NONE))
{
if (m_status & SR_RDRF)
{
m_overrun_pending = true;
}
else
{
/// TODO: find out if this is the correct place to calculate parity
if (m_parity == PARITY_ODD)
{
m_rx_parity = !m_rx_parity;
}
if (m_parity != PARITY_NONE && m_rx_parity)
{
m_status |= SR_PE;
}
else
{
m_status &= ~SR_PE;
}
m_rdr = m_rx_shift;
if (m_bits == 7 && m_parity != PARITY_NONE)
{
m_rdr &= 0x7f;
}
m_status |= SR_RDRF;
}
m_rx_state = STATE_STOP;
}
}
break;
case STATE_STOP:
if (m_rx_counter == m_divide)
{
m_rx_counter = 0;
LOG("MC6850 '%s': RX STOP BIT\n", tag());
if (!m_rxd)
{
m_status |= SR_FE;
}
else
{
m_status &= ~SR_FE;
}
/// TODO: find out if 6850 only waits for 1 STOP bit when receiving
m_rx_state = STATE_START;
}
break;
}
}
update_irq();
}
}
}
DECLARE_WRITE_LINE_MEMBER( acia6850_device::write_rxd )
{
m_rxd = state;
}
WRITE_LINE_MEMBER( acia6850_device::write_txc )
{
if (m_txc != state)
{
m_txc = state;
if (!state && m_divide > 0)
{
m_tx_counter++;
/// TODO: check txd is correctly generated, check atarist mcu is reading data, start checking receive data.
switch (m_tx_state)
{
case STATE_START:
m_tx_counter = 0;
if (!(m_status & SR_TDRE) && !(m_status & SR_CTS))
{
LOG("MC6850 '%s': TX DATA %x\n", tag(), m_tdr);
m_tx_state = STATE_DATA;
m_tx_shift = m_tdr;
m_tx_bits = 0;
m_tx_parity = 0;
m_status |= SR_TDRE;
LOG("MC6850 '%s': TX START BIT\n", tag());
output_txd(0);
}
else
{
/// TODO: find out if break stops transmitter
output_txd(!m_brk);
}
break;
case STATE_DATA:
if (m_tx_counter == m_divide)
{
m_tx_counter = 0;
if (m_tx_bits < m_bits)
{
output_txd((m_tx_shift >> m_tx_bits) & 1);
m_tx_bits++;
m_tx_parity ^= m_txd;
LOG("MC6850 '%s': TX DATA BIT %d %d\n", tag(), m_tx_bits, m_txd);
}
else if (m_tx_bits == m_bits && m_parity != PARITY_NONE)
{
m_tx_bits++;
/// TODO: find out if this is the correct place to calculate parity
if (m_parity == PARITY_ODD)
{
m_tx_parity = !m_tx_parity;
}
output_txd(m_tx_parity);
LOG("MC6850 '%s': TX PARITY BIT %d\n", tag(), m_txd);
}
else
{
m_tx_state = STATE_STOP;
m_tx_bits = 0;
output_txd(1);
}
}
break;
case STATE_STOP:
if (m_tx_counter == m_divide)
{
m_tx_counter = 0;
m_tx_bits++;
LOG("MC6850 '%s': TX STOP BIT %d\n", tag(), m_tx_bits);
if (m_tx_bits == m_stopbits)
{
m_tx_state = STATE_START;
}
}
break;
}
}
update_irq();
}
}
void acia6850_device::output_txd(int txd)
{
if (m_txd != txd)
{
m_txd = txd;
m_txd_handler(m_txd);
}
}
void acia6850_device::output_rts(int rts)
{
if (m_rts != rts)
{
m_rts = rts;
m_rts_handler(m_rts);
}
}
void acia6850_device::output_irq(int irq)
{
if (m_irq != irq)
{
m_irq = irq;
machine().scheduler().synchronize(timer_expired_delegate(FUNC(acia6850_device::delayed_output_irq), this), irq);
}
}
TIMER_CALLBACK_MEMBER(acia6850_device::delayed_output_irq)
{
if (m_irq)
{
m_status &= ~SR_IRQ;
}
else
{
m_status |= SR_IRQ;
}
m_irq_handler(!m_irq);
}