summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/machine/6850acia.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/machine/6850acia.c')
-rw-r--r--trunk/src/emu/machine/6850acia.c746
1 files changed, 746 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/6850acia.c b/trunk/src/emu/machine/6850acia.c
new file mode 100644
index 00000000000..3fe6cb64177
--- /dev/null
+++ b/trunk/src/emu/machine/6850acia.c
@@ -0,0 +1,746 @@
+/*********************************************************************
+
+ 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();
+}