// 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); }