summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/6850acia.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/6850acia.c')
-rw-r--r--src/devices/machine/6850acia.c597
1 files changed, 597 insertions, 0 deletions
diff --git a/src/devices/machine/6850acia.c b/src/devices/machine/6850acia.c
new file mode 100644
index 00000000000..7c1fcc85178
--- /dev/null
+++ b/src/devices/machine/6850acia.c
@@ -0,0 +1,597 @@
+// license:BSD-3-Clause
+// copyright-holders:smf
+/*********************************************************************
+
+ 6850acia.c
+
+ 6850 ACIA code
+
+*********************************************************************/
+
+#include "emu.h"
+#include "6850acia.h"
+
+
+/***************************************************************************
+ MACROS
+***************************************************************************/
+
+#define LOG 0
+
+/***************************************************************************
+ 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
+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, "acia6850", __FILE__),
+ 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)
+{
+}
+
+acia6850_device::acia6850_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
+ : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
+ 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));
+
+ output_txd(1);
+ output_rts(1);
+ output_irq(1);
+}
+
+READ8_MEMBER( acia6850_device::status_r )
+{
+ UINT8 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 )
+{
+ if (LOG) logerror("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 )
+{
+ if (LOG) logerror("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;
+}
+
+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)
+ {
+ if (LOG) logerror("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)
+ {
+ if (LOG) logerror("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)
+ {
+ if (LOG) logerror("MC6850 '%s': RX DATA BIT %d %d\n", tag(), m_rx_bits, m_rxd);
+ }
+ else
+ {
+ if (LOG) logerror("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;
+
+ if (LOG) logerror("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))
+ {
+ if (LOG) logerror("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;
+
+ if (LOG) logerror("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;
+
+ if (LOG) logerror("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);
+
+ if (LOG) logerror("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++;
+
+ if (LOG) logerror("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;
+
+ if (irq)
+ {
+ m_status &= ~SR_IRQ;
+ }
+ else
+ {
+ m_status |= SR_IRQ;
+ }
+
+ m_irq_handler(!m_irq);
+ }
+}