summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/machine/6850acia.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/machine/6850acia.c')
-rw-r--r--src/emu/machine/6850acia.c691
1 files changed, 691 insertions, 0 deletions
diff --git a/src/emu/machine/6850acia.c b/src/emu/machine/6850acia.c
new file mode 100644
index 00000000000..17686e96020
--- /dev/null
+++ b/src/emu/machine/6850acia.c
@@ -0,0 +1,691 @@
+/*
+ 6850 ACIA
+*/
+
+#include "driver.h"
+#include "timer.h"
+#include "6850acia.h"
+
+#define CR1_0 0x03
+#define CR4_2 0x1C
+#define CR6_5 0x60
+#define CR7 0x80
+
+enum serial_state
+{
+ START,
+ DATA,
+ PARITY,
+ STOP,
+ STOP2,
+};
+
+enum parity_type
+{
+ NONE,
+ ODD,
+ EVEN
+};
+
+static const int ACIA6850_DIVIDE[3] = { 1, 16, 64 };
+
+static const int 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 }
+};
+
+typedef struct _acia_6850_
+{
+ UINT8 ctrl;
+ UINT8 status;
+
+ UINT8 *rx_pin;
+ UINT8 *tx_pin;
+ UINT8 *cts_pin;
+ UINT8 *rts_pin;
+ UINT8 *dcd_pin;
+
+ UINT8 tdr;
+ UINT8 rdr;
+ UINT8 rx_shift;
+ UINT8 tx_shift;
+
+ int rx_clock;
+ int tx_clock;
+
+ int divide;
+
+ /* Counters */
+ int tx_bits;
+ int rx_bits;
+ int tx_parity;
+ int rx_parity;
+
+ /* TX/RX state */
+ int bits;
+ enum parity_type parity;
+ int stopbits;
+ int tx_int;
+
+ /* Signals */
+ int overrun;
+ int reset;
+ int rts;
+ int brk;
+ int first_reset;
+ int status_read;
+ enum serial_state rx_state;
+ enum serial_state tx_state;
+
+ emu_timer *rx_timer;
+ emu_timer *tx_timer;
+
+ void (*int_callback)(int param);
+} acia_6850;
+
+static acia_6850 acia[MAX_ACIA];
+
+static TIMER_CALLBACK( receive_event );
+static TIMER_CALLBACK( transmit_event );
+
+/*
+ Reset the chip
+*/
+static void acia6850_reset(int which)
+{
+ acia_6850 *acia_p = &acia[which];
+ int cts = 0, dcd = 0;
+
+ if (acia_p->cts_pin)
+ {
+ cts = *acia_p->cts_pin;
+ }
+
+ if (acia_p->dcd_pin)
+ {
+ dcd = *acia_p->dcd_pin;
+ }
+
+ acia_p->status = (cts << 3) | (dcd << 2) | ACIA6850_STATUS_TDRE;
+ acia_p->tdr = 0;
+ acia_p->rdr = 0;
+ acia_p->tx_shift = 0;
+ acia_p->rx_shift = 0;
+ *acia_p->tx_pin = 1;
+ acia_p->overrun = 0;
+ acia_p->status_read = 0;
+ acia_p->brk = 0;
+
+ acia_p->rx_state = START;
+ acia_p->tx_state = START;
+
+ if (acia_p->int_callback)
+ {
+ acia_p->int_callback(1);
+ }
+
+ if (acia_p->first_reset)
+ {
+ acia_p->first_reset = 0;
+
+ if (acia_p->rts_pin)
+ {
+ *acia_p->rts_pin = 1;
+ }
+ }
+ else
+ {
+ if (acia_p->rts_pin)
+ {
+ *acia_p->rts_pin = acia_p->rts;
+ }
+ }
+}
+
+/*
+ Called by drivers
+*/
+void acia6850_config(int which, const struct acia6850_interface *intf)
+{
+ acia_6850 *acia_p = &acia[which];
+
+ if (which >= MAX_ACIA)
+ {
+ return;
+ }
+
+ acia_p->rx_clock = intf->rx_clock;
+ acia_p->tx_clock = intf->tx_clock;
+ acia_p->rx_pin = intf->rx_pin;
+ acia_p->tx_pin = intf->tx_pin;
+ acia_p->cts_pin = intf->cts_pin;
+ acia_p->rts_pin = intf->rts_pin;
+ acia_p->dcd_pin = intf->dcd_pin;
+ acia_p->rx_timer = timer_alloc(receive_event);
+ acia_p->tx_timer = timer_alloc(transmit_event);
+ acia_p->int_callback = intf->int_callback;
+ acia_p->first_reset = 1;
+ acia_p->status_read = 0;
+ acia_p->brk = 0;
+
+ timer_reset(acia_p->rx_timer, attotime_never);
+ timer_reset(acia_p->tx_timer, attotime_never);
+
+ state_save_register_item("acia6850", which, acia_p->ctrl);
+ state_save_register_item("acia6850", which, acia_p->status);
+ state_save_register_item("acia6850", which, acia_p->rx_clock);
+ state_save_register_item("acia6850", which, acia_p->tx_clock);
+ state_save_register_item("acia6850", which, acia_p->rx_shift);
+ state_save_register_item("acia6850", which, acia_p->tx_shift);
+ state_save_register_item("acia6850", which, acia_p->rdr);
+ state_save_register_item("acia6850", which, acia_p->tdr);
+ state_save_register_item("acia6850", which, acia_p->rx_bits);
+ state_save_register_item("acia6850", which, acia_p->tx_bits);
+ state_save_register_item("acia6850", which, acia_p->rx_parity);
+ state_save_register_item("acia6850", which, acia_p->tx_parity);
+ state_save_register_item("acia6850", which, acia_p->tx_int);
+
+ state_save_register_item("acia6850", which, acia_p->divide);
+ state_save_register_item("acia6850", which, acia_p->overrun);
+ state_save_register_item("acia6850", which, acia_p->reset);
+ state_save_register_item("acia6850", which, acia_p->first_reset);
+ state_save_register_item("acia6850", which, acia_p->rts);
+ state_save_register_item("acia6850", which, acia_p->brk);
+ state_save_register_item("acia6850", which, acia_p->status_read);
+}
+
+/*
+ Read Status Register
+*/
+static UINT8 acia6850_stat_r(int which)
+{
+ acia_6850 *acia_p = &acia[which];
+
+ acia_p->status_read = 1;
+
+ return acia_p->status;
+}
+
+/*
+ Write Control Register
+*/
+static void acia6850_ctrl_w(int which, UINT8 data)
+{
+ acia_6850 *acia_p = &acia[which];
+
+ int wordsel;
+ int divide;
+
+ acia_p->ctrl = data;
+
+ // Counter Divide Select Bits
+
+ divide = data & CR1_0;
+
+ if (divide == 3)
+ {
+ acia_p->reset = 1;
+ acia6850_reset(which);
+ }
+ else
+ {
+ acia_p->reset = 0;
+ acia_p->divide = ACIA6850_DIVIDE[divide];
+ }
+
+ // Word Select Bits
+
+ wordsel = (data & CR4_2) >> 2;
+
+ acia_p->bits = ACIA6850_WORD[wordsel][0];
+ acia_p->parity = ACIA6850_WORD[wordsel][1];
+ acia_p->stopbits = ACIA6850_WORD[wordsel][2];
+
+ // Transmitter Control Bits
+
+ switch ((data & CR6_5) >> 5)
+ {
+ case 0:
+ if (acia_p->rts_pin)
+ {
+ *acia_p->rts_pin = acia_p->rts = 0;
+ }
+
+ acia_p->tx_int = 0;
+ acia_p->brk = 0;
+ break;
+
+ case 1:
+ if (acia_p->rts_pin)
+ {
+ *acia_p->rts_pin = acia_p->rts = 0;
+ }
+
+ acia_p->tx_int = 1;
+ acia_p->brk = 0;
+ break;
+
+ case 2:
+ if (acia_p->rts_pin)
+ {
+ *acia_p->rts_pin = acia_p->rts = 1;
+ }
+
+ acia_p->tx_int = 0;
+ acia_p->brk = 0;
+ break;
+
+ case 3:
+ if (acia_p->rts_pin)
+ {
+ *acia_p->rts_pin = acia_p->rts = 0;
+ }
+
+ acia_p->tx_int = 0;
+ acia_p->brk = 1;
+ break;
+ }
+
+ // After writing the word type, start receive clock
+
+ if (!acia_p->reset)
+ {
+ attotime rx_period = attotime_mul(ATTOTIME_IN_HZ(acia_p->rx_clock), acia_p->divide);
+ attotime tx_period = attotime_mul(ATTOTIME_IN_HZ(acia_p->tx_clock), acia_p->divide);
+ timer_adjust(acia_p->rx_timer, rx_period, which, rx_period);
+ timer_adjust(acia_p->tx_timer, tx_period, which, tx_period);
+ }
+}
+
+static void acia6850_check_interrupts(int which)
+{
+ acia_6850 *acia_p = &acia[which];
+
+ int irq = (acia_p->tx_int && (acia_p->status & ACIA6850_STATUS_TDRE)) ||
+ ((acia_p->ctrl & 0x80) && ((acia_p->status & (ACIA6850_STATUS_RDRF|ACIA6850_STATUS_DCD)) || acia_p->overrun));
+
+ if (irq)
+ {
+ acia_p->status |= ACIA6850_STATUS_IRQ;
+
+ if (acia_p->int_callback)
+ {
+ acia_p->int_callback(0);
+ }
+ }
+ else
+ {
+ acia_p->status &= ~ACIA6850_STATUS_IRQ;
+
+ if (acia_p->int_callback)
+ {
+ acia_p->int_callback(1);
+ }
+ }
+}
+
+/*
+ Write transmit register
+*/
+static void acia6850_data_w(int which, UINT8 data)
+{
+ acia_6850 *acia_p = &acia[which];
+
+ if (!acia_p->reset)
+ {
+ acia_p->tdr = data;
+ acia_p->status &= ~ACIA6850_STATUS_TDRE;
+ acia6850_check_interrupts(which);
+ }
+ else
+ {
+ logerror("ACIA %d: Data write while in reset! (%x)\n", which, activecpu_get_previouspc());
+ }
+}
+
+/*
+ Read character
+*/
+static UINT8 acia6850_data_r(int which)
+{
+ acia_6850 *acia_p = &acia[which];
+
+ acia_p->status &= ~(ACIA6850_STATUS_RDRF | ACIA6850_STATUS_IRQ | ACIA6850_STATUS_PE);
+
+ if (acia_p->status_read)
+ {
+ acia_p->status_read = 0;
+ acia_p->status &= ~(ACIA6850_STATUS_OVRN | ACIA6850_STATUS_DCD);
+
+ if (acia_p->dcd_pin && *acia_p->dcd_pin)
+ {
+ acia_p->status |= ACIA6850_STATUS_DCD;
+ }
+ }
+
+ if (acia_p->overrun == 1)
+ {
+ acia_p->status |= ACIA6850_STATUS_OVRN;
+ acia_p->overrun = 0;
+ }
+
+ acia6850_check_interrupts(which);
+
+ return acia_p->rdr;
+}
+
+/*
+ Transmit a bit
+*/
+static TIMER_CALLBACK( transmit_event )
+{
+ int which = param;
+ acia_6850 *acia_p = &acia[which];
+
+ switch (acia_p->tx_state)
+ {
+ case START:
+ {
+ if (acia_p->brk)
+ {
+ // transmit break
+
+ *acia_p->tx_pin = 0;
+ }
+ else
+ {
+ if (acia_p->status & ACIA6850_STATUS_TDRE)
+ {
+ // transmitter idle
+
+ *acia_p->tx_pin = 1;
+ }
+ else
+ {
+ // transmit character
+
+ //logerror("ACIA6850 #%u: TX DATA %x\n", which, acia_p->tdr);
+ //logerror("ACIA6850 #%u: TX START BIT\n", which);
+
+ *acia_p->tx_pin = 0;
+
+ acia_p->tx_bits = acia_p->bits;
+ acia_p->tx_shift = acia_p->tdr;
+
+ // inhibit TDRE bit if Clear-to-Send is high
+
+ if (!(acia_p->status & ACIA6850_STATUS_CTS))
+ {
+ acia_p->status |= ACIA6850_STATUS_TDRE;
+ }
+
+ acia6850_check_interrupts(which);
+
+ acia_p->tx_state = DATA;
+ }
+ }
+ break;
+ }
+ case DATA:
+ {
+ int val = acia_p->tx_shift & 1;
+ //logerror("ACIA6850 #%u: TX DATA BIT %x\n", which, val);
+
+ *acia_p->tx_pin = val;
+ acia_p->tx_parity ^= val;
+ acia_p->tx_shift >>= 1;
+
+ if (--(acia_p->tx_bits) == 0)
+ {
+ acia_p->tx_state = (acia_p->parity == NONE) ? STOP : PARITY;
+ }
+
+ break;
+ }
+ case PARITY:
+ {
+ if (acia_p->parity == EVEN)
+ {
+ *acia_p->tx_pin = (acia_p->tx_parity & 1) ? 1 : 0;
+ }
+ else
+ {
+ *acia_p->tx_pin = (acia_p->tx_parity & 1) ? 0 : 1;
+ }
+
+ //logerror("ACIA6850 #%u: TX PARITY BIT %x\n", which, *acia_p->tx_pin);
+ acia_p->tx_parity = 0;
+ acia_p->tx_state = STOP;
+ break;
+ }
+ case STOP:
+ {
+ //logerror("ACIA6850 #%u: TX STOP BIT\n", which);
+ *acia_p->tx_pin = 1;
+
+ if (acia_p->stopbits == 1)
+ {
+ acia_p->tx_state = START;
+ }
+ else
+ {
+ acia_p->tx_state = STOP2;
+ }
+ break;
+ }
+ case STOP2:
+ {
+ //logerror("ACIA6850 #%u: TX STOP BIT\n", which);
+ *acia_p->tx_pin = 1;
+ acia_p->tx_state = START;
+ break;
+ }
+ }
+}
+
+/* Called on receive timer event */
+static TIMER_CALLBACK( receive_event )
+{
+ int which = param;
+ acia_6850 *acia_p = &acia[which];
+
+ if (acia_p->dcd_pin && *acia_p->dcd_pin)
+ {
+ acia_p->status |= ACIA6850_STATUS_DCD;
+ acia6850_check_interrupts(which);
+ }
+ else if ((acia_p->status & (ACIA6850_STATUS_DCD|ACIA6850_STATUS_IRQ)) == ACIA6850_STATUS_DCD)
+ {
+ acia_p->status &= ~ACIA6850_STATUS_DCD;
+ }
+
+ if (acia_p->status & ACIA6850_STATUS_DCD)
+ {
+ acia_p->rx_state = START;
+ }
+ else
+ {
+ switch (acia_p->rx_state)
+ {
+ case START:
+ {
+ if (*acia_p->rx_pin == 0)
+ {
+ //logerror("ACIA6850 #%u: RX START BIT\n", which);
+ acia_p->rx_shift = 0;
+ acia_p->rx_parity = 0;
+ acia_p->rx_bits = acia_p->bits;
+ acia_p->rx_state = DATA;
+ }
+ break;
+ }
+ case DATA:
+ {
+ //logerror("ACIA6850 #%u: RX DATA BIT %x\n", which, *acia_p->rx_pin);
+ acia_p->rx_shift |= *acia_p->rx_pin ? 0x80 : 0;
+ acia_p->rx_parity ^= *acia_p->rx_pin;
+
+ if (--acia_p->rx_bits == 0)
+ {
+ if (acia_p->status & ACIA6850_STATUS_RDRF)
+ {
+ acia_p->overrun = 1;
+ acia6850_check_interrupts(which);
+ }
+
+ acia_p->rx_state = acia_p->parity == NONE ? STOP : PARITY;
+ }
+ else
+ {
+ acia_p->rx_shift >>= 1;
+ }
+ break;
+ }
+ case PARITY:
+ {
+ //logerror("ACIA6850 #%u: RX PARITY BIT %x\n", which, *acia_p->rx_pin);
+ acia_p->rx_parity ^= *acia_p->rx_pin;
+
+ if (acia_p->parity == EVEN)
+ {
+ if (acia_p->rx_parity)
+ {
+ acia_p->status |= ACIA6850_STATUS_PE;
+ }
+ }
+ else
+ {
+ if (!acia_p->rx_parity)
+ {
+ acia_p->status |= ACIA6850_STATUS_PE;
+ }
+ }
+
+ acia_p->rx_state = STOP;
+ break;
+ }
+ case STOP:
+ {
+ if (*acia_p->rx_pin == 1)
+ {
+ //logerror("ACIA6850 #%u: RX STOP BIT\n", which);
+ if (acia_p->stopbits == 1)
+ {
+ acia_p->status &= ~ACIA6850_STATUS_FE;
+
+ if (!(acia_p->status & ACIA6850_STATUS_RDRF))
+ {
+ //logerror("ACIA6850 #%u: RX DATA %x\n", which, acia_p->rx_shift);
+ acia_p->rdr = acia_p->rx_shift;
+ acia_p->status |= ACIA6850_STATUS_RDRF;
+ acia6850_check_interrupts(which);
+ }
+
+ acia_p->rx_state = START;
+ }
+ else
+ {
+ acia_p->rx_state = STOP2;
+ }
+ }
+ else
+ {
+ acia_p->status |= ACIA6850_STATUS_FE;
+ acia_p->rx_state = START;
+ }
+ break;
+ }
+ case STOP2:
+ {
+ if (*acia_p->rx_pin == 1)
+ {
+ //logerror("ACIA6850 #%u: RX STOP BIT\n", which);
+ acia_p->status &= ~ACIA6850_STATUS_FE;
+
+ if (!(acia_p->status & ACIA6850_STATUS_RDRF))
+ {
+ //logerror("ACIA6850 #%u: RX DATA %x\n", which, acia_p->rx_shift);
+ acia_p->rdr = acia_p->rx_shift;
+ acia_p->status |= ACIA6850_STATUS_RDRF;
+ acia6850_check_interrupts(which);
+ }
+
+ acia_p->rx_state = START;
+ }
+ else
+ {
+ acia_p->status |= ACIA6850_STATUS_FE;
+ acia_p->rx_state = START;
+ }
+ break;
+ }
+ }
+ }
+}
+
+WRITE8_HANDLER( acia6850_0_ctrl_w ) { acia6850_ctrl_w(0, data); }
+WRITE8_HANDLER( acia6850_1_ctrl_w ) { acia6850_ctrl_w(1, data); }
+WRITE8_HANDLER( acia6850_2_ctrl_w ) { acia6850_ctrl_w(2, data); }
+WRITE8_HANDLER( acia6850_3_ctrl_w ) { acia6850_ctrl_w(3, data); }
+
+WRITE8_HANDLER( acia6850_0_data_w ) { acia6850_data_w(0, data); }
+WRITE8_HANDLER( acia6850_1_data_w ) { acia6850_data_w(1, data); }
+WRITE8_HANDLER( acia6850_2_data_w ) { acia6850_data_w(2, data); }
+WRITE8_HANDLER( acia6850_3_data_w ) { acia6850_data_w(3, data); }
+
+READ8_HANDLER( acia6850_0_stat_r ) { return acia6850_stat_r(0); }
+READ8_HANDLER( acia6850_1_stat_r ) { return acia6850_stat_r(1); }
+READ8_HANDLER( acia6850_2_stat_r ) { return acia6850_stat_r(2); }
+READ8_HANDLER( acia6850_3_stat_r ) { return acia6850_stat_r(3); }
+
+READ8_HANDLER( acia6850_0_data_r ) { return acia6850_data_r(0); }
+READ8_HANDLER( acia6850_1_data_r ) { return acia6850_data_r(1); }
+READ8_HANDLER( acia6850_2_data_r ) { return acia6850_data_r(2); }
+READ8_HANDLER( acia6850_3_data_r ) { return acia6850_data_r(3); }
+
+READ16_HANDLER( acia6850_0_stat_lsb_r ) { return acia6850_stat_r(0); }
+READ16_HANDLER( acia6850_1_stat_lsb_r ) { return acia6850_stat_r(1); }
+READ16_HANDLER( acia6850_2_stat_lsb_r ) { return acia6850_stat_r(2); }
+READ16_HANDLER( acia6850_3_stat_lsb_r ) { return acia6850_stat_r(3); }
+
+READ16_HANDLER( acia6850_0_stat_msb_r ) { return acia6850_stat_r(0) << 8; }
+READ16_HANDLER( acia6850_1_stat_msb_r ) { return acia6850_stat_r(1) << 8; }
+READ16_HANDLER( acia6850_2_stat_msb_r ) { return acia6850_stat_r(2) << 8; }
+READ16_HANDLER( acia6850_3_stat_msb_r ) { return acia6850_stat_r(3) << 8; }
+
+READ16_HANDLER( acia6850_0_data_lsb_r ) { return acia6850_data_r(0); }
+READ16_HANDLER( acia6850_1_data_lsb_r ) { return acia6850_data_r(1); }
+READ16_HANDLER( acia6850_2_data_lsb_r ) { return acia6850_data_r(2); }
+READ16_HANDLER( acia6850_3_data_lsb_r ) { return acia6850_data_r(3); }
+
+READ16_HANDLER( acia6850_0_data_msb_r ) { return acia6850_data_r(0) << 8; }
+READ16_HANDLER( acia6850_1_data_msb_r ) { return acia6850_data_r(1) << 8; }
+READ16_HANDLER( acia6850_2_data_msb_r ) { return acia6850_data_r(2) << 8; }
+READ16_HANDLER( acia6850_3_data_msb_r ) { return acia6850_data_r(3) << 8; }
+
+WRITE16_HANDLER( acia6850_0_ctrl_msb_w ) { if (ACCESSING_MSB) acia6850_ctrl_w(0, (data >> 8) & 0xff); }
+WRITE16_HANDLER( acia6850_1_ctrl_msb_w ) { if (ACCESSING_MSB) acia6850_ctrl_w(1, (data >> 8) & 0xff); }
+WRITE16_HANDLER( acia6850_2_ctrl_msb_w ) { if (ACCESSING_MSB) acia6850_ctrl_w(2, (data >> 8) & 0xff); }
+WRITE16_HANDLER( acia6850_3_ctrl_msb_w ) { if (ACCESSING_MSB) acia6850_ctrl_w(3, (data >> 8) & 0xff); }
+
+WRITE16_HANDLER( acia6850_0_ctrl_lsb_w ) { if (ACCESSING_LSB) acia6850_ctrl_w(0, data & 0xff); }
+WRITE16_HANDLER( acia6850_1_ctrl_lsb_w ) { if (ACCESSING_LSB) acia6850_ctrl_w(1, data & 0xff); }
+WRITE16_HANDLER( acia6850_2_ctrl_lsb_w ) { if (ACCESSING_LSB) acia6850_ctrl_w(2, data & 0xff); }
+WRITE16_HANDLER( acia6850_3_ctrl_lsb_w ) { if (ACCESSING_LSB) acia6850_ctrl_w(3, data & 0xff); }
+
+WRITE16_HANDLER( acia6850_0_data_msb_w ) { if (ACCESSING_MSB) acia6850_data_w(0, (data >> 8) & 0xff); }
+WRITE16_HANDLER( acia6850_1_data_msb_w ) { if (ACCESSING_MSB) acia6850_data_w(1, (data >> 8) & 0xff); }
+WRITE16_HANDLER( acia6850_2_data_msb_w ) { if (ACCESSING_MSB) acia6850_data_w(2, (data >> 8) & 0xff); }
+WRITE16_HANDLER( acia6850_3_data_msb_w ) { if (ACCESSING_MSB) acia6850_data_w(3, (data >> 8) & 0xff); }
+
+WRITE16_HANDLER( acia6850_0_data_lsb_w ) { if (ACCESSING_LSB) acia6850_data_w(0, data & 0xff); }
+WRITE16_HANDLER( acia6850_1_data_lsb_w ) { if (ACCESSING_LSB) acia6850_data_w(1, data & 0xff); }
+WRITE16_HANDLER( acia6850_2_data_lsb_w ) { if (ACCESSING_LSB) acia6850_data_w(2, data & 0xff); }
+WRITE16_HANDLER( acia6850_3_data_lsb_w ) { if (ACCESSING_LSB) acia6850_data_w(3, data & 0xff); }