From c84deae9715538277c1886ee2e10c04f5f968643 Mon Sep 17 00:00:00 2001 From: Joakim Larsson Edstrom Date: Sat, 12 Sep 2015 07:43:41 +0200 Subject: [PATCH] added z80scc device, supporting just polled asynchrounous mode at the moment --- src/emu/machine/z80scc.c | 1837 ++++++++++++++++++++++++++++++++++++++ src/emu/machine/z80scc.h | 738 +++++++++++++++ 2 files changed, 2575 insertions(+) create mode 100644 src/emu/machine/z80scc.c create mode 100644 src/emu/machine/z80scc.h diff --git a/src/emu/machine/z80scc.c b/src/emu/machine/z80scc.c new file mode 100644 index 00000000000..d498df918fd --- /dev/null +++ b/src/emu/machine/z80scc.c @@ -0,0 +1,1837 @@ +// license:BSD-3-Clause +// copyright-holders:Curt Coder, Joakim Larsson Edstrom +/*************************************************************************** + + Z80-SCC Serial Communications Controller emulation + + The z80scc is an updated version of the z80sio, with additional support for CRC + checks and a number of data link layer protocols such as HDLC, SDLC and BiSync. + (See https://en.wikipedia.org/wiki/Zilog_SCC). The variants in the SCC + family are as follows: + Zbus Universal bus + NMOS Z8030 Z8530 + CMOS Z80C30 Z85C30 + ESCC Z80230 Z85230, Z8523L (L = low voltage) + EMSCC Z85233 + The difference between Zbus and Universal bus is mainly at hardware + design level and suitable for Intel oriented (Zbus) or Motorola oriented + chip designs. + +TODO: + NMOS CMOS ESCC EMSCC + ------------------------------------------------------------------- + Channels 2 FD 2 FD 2 FD 2 FD FD = Full Duplex + Synch data rates 2Mbps 4Mbps 5Mbps 5Mbps + 1Mbps (FM) + .5Mbps (NRZI) + -- asynchrounous features ------------------------------------------- + 5-8 bit per char Y Y Y Y + 1,1.5,2 stop bits Y Y Y Y + odd/even parity Y Y Y Y + x1,x16,x32,x64 Y Y Y Y + break det/gen Y Y Y Y + parity, framing & Y Y Y Y + overrun error det Y Y Y Y + -- byte oriented synchrounous features ------------------------------- + Int/ext char sync Y Y Y Y + 1/2 synch chars Y Y Y Y + Aut CRC gen/det Y Y Y Y + -- SDLC/HDLC capabilities -------------------------------------------- + Abort seq gen/chk Y Y Y Y + Aut zero ins/det Y Y Y Y + Aut flag insert Y Y Y Y + Addr field rec Y Y Y Y + I-fld resid hand Y Y Y Y + CRC gen/det Y Y Y Y + SDLC loop w EOP Y Y Y Y + -- + Receiver FIFO 3 3 8 8 + Transmitter FIFO 1 1 4 4 + NRZ, NRZI or Y Y Y Y + FM enc/dec Y Y Y Y + Manchester dec Y Y Y Y + Baud gen per chan Y Y Y Y + DPLL clock recov Y Y Y Y + -- Additional features CMOS versions ----------------------------------- + Status FIFO N Y Y Y + SWI ack feat N Y Y Y + higher bps w ext DPLL N 32Mbps 32Mbps 32Mbps + -- Additional features 85C30 ------------------------------------------- + New WR7 feat N 85C30 Y Y + Improved SDLC N 85C30 Y Y + Improved reg handl N 85C30 Y Y + Improved auto feat N 85C30 Y Y + -- Additional features ESCC ------------------------------------------- + Progrmbl FIFO int & + DMA req lev Y Y Y Y + Improved SDLC Y Y Y Y + DPLL counter as TXc Y Y Y Y + Improved reg handl Y Y Y Y + ------------------------------------------------------------------------- + * = Features that has been implemented n/a = features that will not +***************************************************************************/ + +#include "z80scc.h" + +//************************************************************************** +// MACROS / CONSTANTS +//************************************************************************** + +#define VERBOSE 0 + +#define LOG(x) do { if (VERBOSE) logerror x; } while (0) +#if VERBOSE == 2 +#define logerror printf +#endif + +#define CHANA_TAG "cha" +#define CHANB_TAG "chb" + +//************************************************************************** +// DEVICE DEFINITIONS +//************************************************************************** +// device type definition +const device_type Z80SCC = &device_creator; +const device_type Z80SCC_CHANNEL = &device_creator; +const device_type SCC8030 = &device_creator; +const device_type SCC80C30 = &device_creator; +const device_type SCC80230 = &device_creator; +const device_type SCC8530 = &device_creator; +const device_type SCC85C30 = &device_creator; +const device_type SCC85230 = &device_creator; +const device_type SCC85233 = &device_creator; +const device_type SCC8523L = &device_creator; + +//------------------------------------------------- +// device_mconfig_additions - +//------------------------------------------------- +MACHINE_CONFIG_FRAGMENT( z80scc ) + MCFG_DEVICE_ADD(CHANA_TAG, Z80SCC_CHANNEL, 0) + MCFG_DEVICE_ADD(CHANB_TAG, Z80SCC_CHANNEL, 0) +MACHINE_CONFIG_END + +machine_config_constructor z80scc_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( z80scc ); +} + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +//------------------------------------------------- +// z80scc_device - constructor +//------------------------------------------------- + +z80scc_device::z80scc_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, UINT32 variant, const char *shortname, const char *source) + : device_t(mconfig, type, name, tag, owner, clock, shortname, source), + device_z80daisy_interface(mconfig, *this), + m_chanA(*this, CHANA_TAG), + m_chanB(*this, CHANB_TAG), + m_rxca(0), + m_txca(0), + m_rxcb(0), + m_txcb(0), + m_out_txda_cb(*this), + m_out_dtra_cb(*this), + m_out_rtsa_cb(*this), + m_out_wrdya_cb(*this), + m_out_synca_cb(*this), + m_out_txdb_cb(*this), + m_out_dtrb_cb(*this), + m_out_rtsb_cb(*this), + m_out_wrdyb_cb(*this), + m_out_syncb_cb(*this), + m_out_int_cb(*this), + m_out_rxdrqa_cb(*this), + m_out_txdrqa_cb(*this), + m_out_rxdrqb_cb(*this), + m_out_txdrqb_cb(*this), + m_variant(variant) +{ + for (int i = 0; i < 8; i++) + m_int_state[i] = 0; +} + +z80scc_device::z80scc_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, Z80SCC, "Z80 SCC", tag, owner, clock, "z80scc", __FILE__), + device_z80daisy_interface(mconfig, *this), + m_chanA(*this, CHANA_TAG), + m_chanB(*this, CHANB_TAG), + m_rxca(0), + m_txca(0), + m_rxcb(0), + m_txcb(0), + m_out_txda_cb(*this), + m_out_dtra_cb(*this), + m_out_rtsa_cb(*this), + m_out_wrdya_cb(*this), + m_out_synca_cb(*this), + m_out_txdb_cb(*this), + m_out_dtrb_cb(*this), + m_out_rtsb_cb(*this), + m_out_wrdyb_cb(*this), + m_out_syncb_cb(*this), + m_out_int_cb(*this), + m_out_rxdrqa_cb(*this), + m_out_txdrqa_cb(*this), + m_out_rxdrqb_cb(*this), + m_out_txdrqb_cb(*this), + m_variant(TYPE_Z80SCC) +{ + for (int i = 0; i < 8; i++) + m_int_state[i] = 0; +} + +scc8030_device::scc8030_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC8530, "SCC 8030", tag, owner, clock, TYPE_SCC8030, "scc8030", __FILE__){ } + +scc80C30_device::scc80C30_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC85C30, "SCC 80C30", tag, owner, clock, TYPE_SCC80C30, "scc80C30", __FILE__){ } + +scc80230_device::scc80230_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC85230, "SCC 80230", tag, owner, clock, TYPE_SCC80230, "scc80230", __FILE__){ } + +scc8530_device::scc8530_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC8530, "SCC 8530", tag, owner, clock, TYPE_SCC8530, "scc8530", __FILE__){ } + +scc85C30_device::scc85C30_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC85C30, "SCC 85C30", tag, owner, clock, TYPE_SCC85C30, "scc85C30", __FILE__){ } + +scc85230_device::scc85230_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC85230, "SCC 85230", tag, owner, clock, TYPE_SCC85230, "scc85230", __FILE__){ } + +scc85233_device::scc85233_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC85233, "SCC 85233", tag, owner, clock, TYPE_SCC85233, "scc85233", __FILE__){ } + +scc8523L_device::scc8523L_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80scc_device(mconfig, SCC8523L, "SCC 8523L", tag, owner, clock, TYPE_SCC8523L, "scc8523L", __FILE__){ } + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void z80scc_device::device_start() +{ + // resolve callbacks + m_out_txda_cb.resolve_safe(); + m_out_dtra_cb.resolve_safe(); + m_out_rtsa_cb.resolve_safe(); + m_out_wrdya_cb.resolve_safe(); + m_out_synca_cb.resolve_safe(); + m_out_txdb_cb.resolve_safe(); + m_out_dtrb_cb.resolve_safe(); + m_out_rtsb_cb.resolve_safe(); + m_out_wrdyb_cb.resolve_safe(); + m_out_syncb_cb.resolve_safe(); + m_out_int_cb.resolve_safe(); + m_out_rxdrqa_cb.resolve_safe(); + m_out_txdrqa_cb.resolve_safe(); + m_out_rxdrqb_cb.resolve_safe(); + m_out_txdrqb_cb.resolve_safe(); + + // configure channel A + m_chanA->m_rxc = m_rxca; + m_chanA->m_txc = m_txca; + + // configure channel B + m_chanB->m_rxc = m_rxcb; + m_chanB->m_txc = m_txcb; + + // state saving + save_item(NAME(m_int_state)); +} + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void z80scc_device::device_reset() +{ + LOG(("Z80SCC \"%s\" Reset\n", tag())); + + m_chanA->reset(); + m_chanB->reset(); +} + +//------------------------------------------------- +// z80daisy_irq_state - get interrupt status +//------------------------------------------------- + +int z80scc_device::z80daisy_irq_state() +{ + int state = 0; + int i; + + LOG(("Z80SCC \"%s\" : Interrupt State A:%d%d%d%d B:%d%d%d%d\n", tag(), + m_int_state[0], m_int_state[1], m_int_state[2], m_int_state[3], + m_int_state[4], m_int_state[5], m_int_state[6], m_int_state[7])); + + // loop over all interrupt sources + for (i = 0; i < 8; i++) + { + // if we're servicing a request, don't indicate more interrupts + if (m_int_state[i] & Z80_DAISY_IEO) + { + state |= Z80_DAISY_IEO; + break; + } + state |= m_int_state[i]; + } + + LOG(("Z80SCC \"%s\" : Interrupt State %u\n", tag(), state)); + + return state; +} + + +//------------------------------------------------- +// z80daisy_irq_ack - interrupt acknowledge +//------------------------------------------------- + +int z80scc_device::z80daisy_irq_ack() +{ + int i; + + LOG(("Z80SCC \"%s\" Interrupt Acknowledge\n", tag())); + + // loop over all interrupt sources + for (i = 0; i < 8; i++) + { + // find the first channel with an interrupt requested + if (m_int_state[i] & Z80_DAISY_INT) + { + // clear interrupt, switch to the IEO state, and update the IRQs + m_int_state[i] = Z80_DAISY_IEO; + m_chanA->m_rr0 &= ~z80scc_channel::RR0_INTERRUPT_PENDING; + check_interrupts(); + + LOG(("Z80SCC \"%s\" : Interrupt Acknowledge Vector %02x\n", tag(), m_chanB->m_rr2)); + + return m_chanB->m_rr2; + } + } + + //logerror("z80scc_irq_ack: failed to find an interrupt to ack!\n"); + + return m_chanB->m_rr2; +} + + +//------------------------------------------------- +// z80daisy_irq_reti - return from interrupt +//------------------------------------------------- + +void z80scc_device::z80daisy_irq_reti() +{ + int i; + + LOG(("Z80SCC \"%s\" Return from Interrupt\n", tag())); + + // loop over all interrupt sources + for (i = 0; i < 8; i++) + { + // find the first channel with an IEO pending + if (m_int_state[i] & Z80_DAISY_IEO) + { + // clear the IEO state and update the IRQs + m_int_state[i] &= ~Z80_DAISY_IEO; + check_interrupts(); + return; + } + } + + //logerror("z80scc_irq_reti: failed to find an interrupt to clear IEO on!\n"); +} + + +//------------------------------------------------- +// check_interrupts - +//------------------------------------------------- + +void z80scc_device::check_interrupts() +{ + LOG(("Z80SCC \"%s\" : check_interrupts\n", m_owner->tag())); + int state = (z80daisy_irq_state() & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE; + m_out_int_cb(state); +} + + +//------------------------------------------------- +// reset_interrupts - +//------------------------------------------------- + +void z80scc_device::reset_interrupts() +{ + for (int i = 0; i < 8; i++) + { + m_int_state[i] = 0; + } + + check_interrupts(); +} + + +//------------------------------------------------- +// trigger_interrupt - +//------------------------------------------------- + +void z80scc_device::trigger_interrupt(int index, int state) +{ +#if 0 // TODO: SIO code Needs to be adapted for SCC + UINT8 vector = m_chanB->m_wr2; + int priority; + + if((m_variant == TYPE_I8274) || (m_variant == TYPE_UPD7201)) + { + int prio_level = 0; + switch(state) + { + case z80scc_channel::INT_TRANSMIT: + prio_level = 1; + break; + case z80scc_channel::INT_RECEIVE: + case z80scc_channel::INT_SPECIAL: + prio_level = 0; + break; + case z80scc_channel::INT_EXTERNAL: + prio_level = 2; + break; + } + + if(m_chanA->m_wr2 & z80scc_channel::WR2_PRIORITY) + { + priority = (prio_level * 2) + index; + } + else + { + priority = (prio_level == 2) ? index + 4 : ((index * 2) + prio_level); + } + if (m_chanB->m_wr1 & z80scc_channel::WR1_STATUS_VECTOR) + { + vector = (!index << 2) | state; + if((m_chanA->m_wr1 & 0x18) == z80scc_channel::WR2_MODE_8086_8088) + { + vector = (m_chanB->m_wr2 & 0xf8) | vector; + } + else + { + vector = (m_chanB->m_wr2 & 0xe3) | (vector << 2); + } + } + } + else + { + priority = (index << 2) | state; + if (m_chanB->m_wr1 & z80scc_channel::WR1_STATUS_VECTOR) + { + // status affects vector + vector = (m_chanB->m_wr2 & 0xf1) | (!index << 3) | (state << 1); + } +// } + + LOG(("Z80SCC \"%s\" Channel %c : Interrupt Request %u\n", tag(), 'A' + index, state)); + + // update vector register + m_chanB->m_rr2 = vector; + + // trigger interrupt + m_int_state[priority] |= Z80_DAISY_INT; + m_chanA->m_rr0 |= z80scc_channel::RR0_INTERRUPT_PENDING; + + // check for interrupt + check_interrupts(); +#endif +} + + +//------------------------------------------------- +// m1_r - interrupt acknowledge +//------------------------------------------------- + +int z80scc_device::m1_r() +{ + return z80daisy_irq_ack(); +} + + +//------------------------------------------------- +// cd_ba_r - Universal Bus read +//------------------------------------------------- +READ8_MEMBER( z80scc_device::cd_ba_r ) +{ + int ba = BIT(offset, 0); + int cd = BIT(offset, 1); + z80scc_channel *channel = ba ? m_chanB : m_chanA; + + /* Expell non-Universal Bus variants */ + if ( !(m_variant & SET_Z85X3X)) + { + logerror("Z80SCC cd_ba_r not supported by this device variant, you should probably use combinations of c*_r/w and d*_r/w (see z80scc.h)\n"); + return 0; + } + + // LOG(("z80scc_device::cd_ba_r ba:%02x cd:%02x\n", ba, cd)); + return cd ? channel->control_read() : channel->data_read(); +} + +//------------------------------------------------- +// cd_ba_w - Universal Bus write +//------------------------------------------------- +WRITE8_MEMBER( z80scc_device::cd_ba_w ) +{ + int ba = BIT(offset, 0); + int cd = BIT(offset, 1); + z80scc_channel *channel = ba ? m_chanB : m_chanA; + + /* Expell non-Universal Bus variants */ + if ( !(m_variant & SET_Z85X3X) ) + { + logerror("Z80SCC cd_ba_w not supported by this device variant, you should probably use combinations of c*_r/w and d*_r/w (see z80scc.h)\n"); + return; + } + + // LOG(("z80scc_device::cd_ba_w ba:%02x cd:%02x\n", ba, cd)); + if (cd) + channel->control_write(data); + else + channel->data_write(data); +} + + +//------------------------------------------------- +// ba_cd_r - Universal Bus read +//------------------------------------------------- + +READ8_MEMBER( z80scc_device::ba_cd_r ) +{ + int ba = BIT(offset, 1); + int cd = BIT(offset, 0); + z80scc_channel *channel = ba ? m_chanB : m_chanA; + + /* Expell non-Universal Bus variants */ + if ( !(m_variant & SET_Z85X3X) ) + { + logerror("Z80SCC ba_cd_r not supported by this device variant, you should probably use combinations of c*_r/w and d*_r/w (see z80scc.h)\n"); + return 0; + } + + // LOG(("z80scc_device::ba_cd_r ba:%02x cd:%02x\n", ba, cd)); + return cd ? channel->control_read() : channel->data_read(); +} + + +//------------------------------------------------- +// ba_cd_w - +//------------------------------------------------- + +WRITE8_MEMBER( z80scc_device::ba_cd_w ) +{ + int ba = BIT(offset, 1); + int cd = BIT(offset, 0); + z80scc_channel *channel = ba ? m_chanB : m_chanA; + + /* Expell non-Universal Bus variants */ + if ( !(m_variant & SET_Z85X3X) ) + { + logerror("Z80SCC ba_cd_w not supported by this device variant, you should probably use combinations of c*_r/w and d*_r/w (see z80scc.h)\n"); + return; + } + + LOG(("z80scc_device::ba_cd_w ba:%02x cd:%02x\n", ba, cd)); + if (cd) + channel->control_write(data); + else + channel->data_write(data); +} + +//************************************************************************** +// SCC CHANNEL +//************************************************************************** + +//------------------------------------------------- +// SCC_channel - constructor +//------------------------------------------------- + +z80scc_channel::z80scc_channel(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : z80sio_channel( mconfig, tag, owner, clock) +{ + // Reset all SCC specific registers; z80sio_channel:: manages the base registers + m_rr3 = m_rr4 = m_rr5 = m_rr6 = m_rr7 = m_rr8 = m_rr9 = + m_rr10 = m_rr11 = m_rr12 = m_rr13 = m_rr14 = m_rr15 = 0; + m_wr8 = m_wr9 = m_wr10 = m_wr11 = m_wr12 = m_wr13 = m_wr14 = m_wr15; + + for (int i = 0; i < 3; i++) // TODO adapt to SCC fifos + { + m_rx_data_fifo[i] = 0; + m_rx_error_fifo[i] = 0; + } +} + + +//------------------------------------------------- +// start - channel startup +//------------------------------------------------- + +void z80scc_channel::device_start() +{ + m_uart = downcast(owner()); + LOG(("Z80SCC device_start m_uart:%p\n", m_uart)); + m_index = m_uart->get_channel_index(this); + m_ph = 0; + m_variant = ((z80scc_device *)m_owner)->m_variant; + + // state saving + // m_rr0-m_rr2 is handled by the z80sio_channel driver, our base class + save_item(NAME(m_rr3)); + save_item(NAME(m_rr4)); + save_item(NAME(m_rr5)); + save_item(NAME(m_rr6)); + save_item(NAME(m_rr7)); + save_item(NAME(m_rr8)); + save_item(NAME(m_rr9)); + save_item(NAME(m_rr10)); + save_item(NAME(m_rr11)); + save_item(NAME(m_rr12)); + save_item(NAME(m_rr13)); + save_item(NAME(m_rr14)); + save_item(NAME(m_rr15)); + // m_wr0-m_wr7 is handled by the z80sio_channel driver, our base class + save_item(NAME(m_wr8)); + save_item(NAME(m_wr9)); + save_item(NAME(m_wr10)); + save_item(NAME(m_wr11)); + save_item(NAME(m_wr12)); + save_item(NAME(m_wr13)); + save_item(NAME(m_wr14)); + save_item(NAME(m_wr15)); + save_item(NAME(m_rx_data_fifo)); + save_item(NAME(m_rx_error_fifo)); + save_item(NAME(m_rx_error)); + save_item(NAME(m_rx_fifo)); + save_item(NAME(m_rx_clock)); + save_item(NAME(m_rx_first)); + save_item(NAME(m_rx_break)); + save_item(NAME(m_rx_rr0_latch)); + save_item(NAME(m_ri)); + save_item(NAME(m_cts)); + save_item(NAME(m_dcd)); + save_item(NAME(m_tx_data)); + save_item(NAME(m_tx_clock)); + save_item(NAME(m_dtr)); + save_item(NAME(m_rts)); + save_item(NAME(m_sync)); + save_item(NAME(m_ph)); + device_serial_interface::register_save_state(machine().save(), this); +} + + +//------------------------------------------------- +// reset - reset channel status +//------------------------------------------------- + +void z80scc_channel::device_reset() +{ + LOG(("Z80SCC \"%s\" Channel %c : %s\n", m_owner->tag(), 'A' + m_index, __func__)); + + receive_register_reset(); + transmit_register_reset(); + + // TODO SIO code, check SCC compliance + // disable receiver + m_wr3 &= ~WR3_RX_ENABLE; + + // disable transmitter + m_wr5 &= ~WR5_TX_ENABLE; + m_rr0 |= RR0_TX_BUFFER_EMPTY; + m_rr1 |= RR1_ALL_SENT; + + // reset external lines + set_rts(1); + set_dtr(1); + + // reset interrupts + if (m_index == z80scc_device::CHANNEL_A) + { + m_uart->reset_interrupts(); + } +} + +void z80scc_channel::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + device_serial_interface::device_timer(timer, id, param, ptr); +} + + +//------------------------------------------------- +// tra_callback - +//------------------------------------------------- + +void z80scc_channel::tra_callback() +{ + if (!(m_wr5 & WR5_TX_ENABLE)) + { + // transmit mark + if (m_index == z80scc_device::CHANNEL_A) + m_uart->m_out_txda_cb(1); + else + m_uart->m_out_txdb_cb(1); + } + else if (m_wr5 & WR5_SEND_BREAK) + { + // transmit break + if (m_index == z80scc_device::CHANNEL_A) + m_uart->m_out_txda_cb(0); + else + m_uart->m_out_txdb_cb(0); + } + else if (!is_transmit_register_empty()) + { + // transmit data + if (m_index == z80scc_device::CHANNEL_A) + m_uart->m_out_txda_cb(transmit_register_get_data_bit()); + else + m_uart->m_out_txdb_cb(transmit_register_get_data_bit()); + } +} + + +//------------------------------------------------- +// tra_complete - +//------------------------------------------------- + +void z80scc_channel::tra_complete() +{ + if ((m_wr5 & WR5_TX_ENABLE) && !(m_wr5 & WR5_SEND_BREAK) && !(m_rr0 & RR0_TX_BUFFER_EMPTY)) + { + LOG(("Z80SCC \"%s\" Channel %c : Transmit Data Byte '%02x'\n", m_owner->tag(), 'A' + m_index, m_tx_data)); + + transmit_register_setup(m_tx_data); + + // empty transmit buffer + m_rr0 |= RR0_TX_BUFFER_EMPTY; + + if (m_wr1 & WR1_TX_INT_ENABLE) + m_uart->trigger_interrupt(m_index, INT_TRANSMIT); + } + else if (m_wr5 & WR5_SEND_BREAK) + { + // transmit break + if (m_index == z80scc_device::CHANNEL_A) + m_uart->m_out_txda_cb(0); + else + m_uart->m_out_txdb_cb(0); + } + else + { + // transmit mark + if (m_index == z80scc_device::CHANNEL_A) + m_uart->m_out_txda_cb(1); + else + m_uart->m_out_txdb_cb(1); + } + + // if transmit buffer is empty + if (m_rr0 & RR0_TX_BUFFER_EMPTY) + { + // then all characters have been sent + m_rr1 |= RR1_ALL_SENT; + + // when the RTS bit is reset, the _RTS output goes high after the transmitter empties + if (!m_rts) + set_rts(1); + } +} + + +//------------------------------------------------- +// rcv_callback - +//------------------------------------------------- + +void z80scc_channel::rcv_callback() +{ + if (m_wr3 & WR3_RX_ENABLE) + { + receive_register_update_bit(m_rxd); + } +} + + +//------------------------------------------------- +// rcv_complete - +//------------------------------------------------- + +void z80scc_channel::rcv_complete() +{ + receive_register_extract(); + receive_data(get_received_char()); +} + + +//------------------------------------------------- +// get_clock_mode - get clock divisor +//------------------------------------------------- + +int z80scc_channel::get_clock_mode() +{ + int clocks = 1; + + switch (m_wr4 & WR4_CLOCK_RATE_MASK) + { + case WR4_CLOCK_RATE_X1: clocks = 1; break; + case WR4_CLOCK_RATE_X16: clocks = 16; break; + case WR4_CLOCK_RATE_X32: clocks = 32; break; + case WR4_CLOCK_RATE_X64: clocks = 64; break; + } + + return clocks; +} + +/* From Zilog SCC/ESCC USers manual, UM010902-0609: +"/RTSA, /RTSB. Request To Send (outputs, active Low). The /RTS pins can be used as generalpurpose +outputs or with the Auto Enable feature. When used with Auto Enable ON (WR3, D5=1) +in asynchronous mode, the /RTS pin goes High after the transmitter is empty. When Auto Enable +is OFF, the /RTS pins are used as general-purpose outputs, and, they strictly follow the inverse +state of WR5, bit D1. +ESCC and 85C30: In SDLC mode, the /RTS pins can be programmed to be deasserted when the closing +flag of the message clears the TxD pin, if WR7' D2 is set." +TODO: +- SDLC mode behaviour for ESCC/85C30 +*/ +void z80scc_channel::set_rts(int state) +{ + LOG(("Z80SCC \"%s\" Channel %c : %s(%d)\n", m_owner->tag(), 'A' + m_index, __func__, state)); + if (m_index == z80scc_device::CHANNEL_A) + m_uart->m_out_rtsa_cb(state); + else + m_uart->m_out_rtsb_cb(state); +} + +void z80scc_channel::update_rts() +{ + LOG(("Z80SCC \"%s\" Channel %c : %s\n", m_owner->tag(), 'A' + m_index, __func__)); + if (m_wr5 & WR5_RTS) + { + // when the RTS bit is set, the _RTS output goes low + set_rts(0); + m_rts = 1; + } + else + { + // when the RTS bit is reset, the _RTS output goes high after the transmitter empties + m_rts = 0; + } + + // data terminal ready output follows the state programmed into the DTR bit*/ + set_dtr((m_wr5 & WR5_DTR) ? 0 : 1); +} + +//------------------------------------------------- +// get_stop_bits - get number of stop bits +//------------------------------------------------- + +device_serial_interface::stop_bits_t z80scc_channel::get_stop_bits() +{ + switch (m_wr4 & WR4_STOP_BITS_MASK) + { + case WR4_STOP_BITS_1: return STOP_BITS_1; + case WR4_STOP_BITS_1_5: return STOP_BITS_1_5; + case WR4_STOP_BITS_2: return STOP_BITS_2; + } + + return STOP_BITS_0; +} + + +//------------------------------------------------- +// get_rx_word_length - get receive word length +//------------------------------------------------- + +int z80scc_channel::get_rx_word_length() +{ + int bits = 5; + + switch (m_wr3 & WR3_RX_WORD_LENGTH_MASK) + { + case WR3_RX_WORD_LENGTH_5: bits = 5; break; + case WR3_RX_WORD_LENGTH_6: bits = 6; break; + case WR3_RX_WORD_LENGTH_7: bits = 7; break; + case WR3_RX_WORD_LENGTH_8: bits = 8; break; + } + + return bits; +} + + +//------------------------------------------------- +// get_tx_word_length - get transmit word length +//------------------------------------------------- + +int z80scc_channel::get_tx_word_length() +{ + int bits = 5; + + switch (m_wr5 & WR5_TX_WORD_LENGTH_MASK) + { + case WR5_TX_WORD_LENGTH_5: bits = 5; break; + case WR5_TX_WORD_LENGTH_6: bits = 6; break; + case WR5_TX_WORD_LENGTH_7: bits = 7; break; + case WR5_TX_WORD_LENGTH_8: bits = 8; break; + } + + return bits; +} + +/* From Zilog SCC/ESCC USers manual, UM010902-0609: +"RR2 contains the interrupt vector written into WR2. When the register is accessed in Channel A, +the vector returned is the vector actually stored in WR2. When this register is accessed in Channel +B, the vector returned includes status information in bits 1, 2 and 3 or in bits 6, 5 and 4, depending +on the state of the Status High/Status Low bit in WR9 and independent of the state of the VIS bit +in WR9."*/ +UINT8 z80scc_channel::do_sccreg_rr2() +{ + LOG(("Z80SCC %s()\n", __func__)); + return m_rr2; // TODO Check that the value is maintained according to the section above for respective channel +} + +/* From Zilog SCC/ESCC USers manual, UM010902-0609: +RR3 is the interrupt Pending register. The status of each of the interrupt Pending bits in the SCC is +reported in this register. This register exists only in Channel A. If this register is accessed in Channel +B, all 0s are returned. The two unused bits are always returned as 0. Figure displays the bit positions for RR3." +*/ +UINT8 z80scc_channel::do_sccreg_rr3() +{ + LOG(("Z80SCC %s()\n", __func__)); + return m_rr3; // TODO Update all bits of this status register +} + + +/* (ESCC and 85C30 Only) */ +/*On the ESCC, Read Register 4 reflects the contents of Write Register 4 provided the Extended + Read option is enabled. Otherwise, this register returns an image of RR0. On the NMOS/CMOS version, + a read to this location returns an image of RR0.*/ +UINT8 z80scc_channel::do_sccreg_rr4() +{ + LOG(("Z80SCC %s()\n", __func__)); + if (m_variant & (SET_ESCC | z80scc_device::TYPE_SCC85C30)) + return (BIT(m_wr7, 6) ? m_wr4 : m_rr0); + else + return m_rr0; +} + + /* (ESCC and 85C30 Only) */ +/*On the ESCC, Read Register 5 reflects the contents of Write Register 5 provided the Extended + Read option is enabled. Otherwise, this register returns an image of RR1. On the NMOS/CMOS version, + a read to this register returns an image of RR1.*/ +UINT8 z80scc_channel::do_sccreg_rr5() +{ + LOG(("Z80SCC %s()\n", __func__)); + if (m_variant & (SET_ESCC | z80scc_device::TYPE_SCC85C30)) + return BIT(m_wr7, 6) ? m_wr5 : m_rr1; + else + return m_rr1; +} + +/* (not on NMOS) + On the CMOS and ESCC, Read Register 6 contains the least significant byte of the frame byte + count that is currently at the top of the Status FIFO. RR6 is displayed in Figure on page 183. This + register is readable only if the FIFO is enabled (refer to the description Write Register 15, bit D2, + and SDLC Frame Status FIFO on page 126). Otherwise, this register is an image of RR2. + On the NMOS version, a read to this register location returns an image of RR2.*/ +UINT8 z80scc_channel::do_sccreg_rr6() +{ + LOG(("Z80SCC %s()\n", __func__)); + if (!(m_variant & (SET_NMOS))) + { + logerror("Z80SCC %s() not implemented feature\n", __func__); + return 0; + } + return m_rr2; +} + +/* (not on NMOS) + On the CMOS and ESCC, Read Register 7 contains the most significant six bits of the frame byte + count that is currently at the top of the Status FIFO. Bit D7 is the FIFO Overflow Status and bit D6 + is the FIFO Data Available Status. The status indications are given in Table on page 184. RR7 is + displayed in Figure on page 183. This register is readable only if the FIFO is enabled (refer to the + description Write Register 15, bit D2). Otherwise this register is an image of RR3. Note, for proper + operation of the FIFO and byte count logic, the registers should be read in the following order: + RR7, RR6, RR1.*/ +UINT8 z80scc_channel::do_sccreg_rr7() +{ + LOG(("Z80SCC %s()\n", __func__)); + if (!(m_variant & (SET_NMOS))) + { + logerror("Z80SCC %s() not implemented feature\n", __func__); + return 0; + } + return m_rr3; +} + +#if 0 // Short cutted in control_read() +/* RR8 is the Receive Data register. */ +UINT8 z80scc_channel::do_sccreg_rr8() +{ + return data_read(): +} +#endif + +/* (ESCC and 85C30 Only) + On the ESCC, Read Register 9 reflects the contents of Write Register 3 provided the Extended + Read option has been enabled. On the NMOS/CMOS version, a read to this location returns an image + of RR13. TODO: Check what is returned if Extended Read option is turned off */ +UINT8 z80scc_channel::do_sccreg_rr9() +{ + LOG(("Z80SCC %s()\n", __func__)); + if (m_variant & (SET_ESCC | z80scc_device::TYPE_SCC85C30)) + return BIT(m_wr7, 6) ? m_wr3 : m_rr13; + else + return m_rr13; +} + +/* RR10 contains some SDLC related miscellaneous status bits. Unused bits are always 0. */ +UINT8 z80scc_channel::do_sccreg_rr10() +{ + LOG(("Z80SCC %s()\n", __func__)); + logerror("Z80SCC %s() not implemented feature\n", __func__); + return m_rr10; +} + +/* (ESCC and 85C30 Only) + On the ESCC, Read Register 11 reflects the contents of Write Register 10 provided the Extended + Read option has been enabled. Otherwise, this register returns an image of RR15. + On the NMOS/CMOS version, a read to this location returns an image of RR15.*/ +UINT8 z80scc_channel::do_sccreg_rr11() +{ + LOG(("Z80SCC %s()\n", __func__)); + if (m_variant & (SET_ESCC | z80scc_device::TYPE_SCC85C30)) + return BIT(m_wr7, 6) ? m_wr10 : m_rr15; + else + return m_rr15; +} + +/* + RR12 returns the value stored in WR12, the lower byte of the time constant, for the BRG.*/ +UINT8 z80scc_channel::do_sccreg_rr12() +{ + return m_wr12; +} + +/* + RR13 returns the value stored in WR13, the upper byte of the time constant for the BRG. */ +UINT8 z80scc_channel::do_sccreg_rr13() +{ + return m_wr13; +} + +/* (ESCC and 85C30 Only) +On the ESCC, Read Register 14 reflects the contents of Write Register 7 Prime provided the +Extended Read option has been enabled. Otherwise, this register returns an image of RR10. +On the NMOS/CMOS version, a read to this location returns an image of RR10.*/ +UINT8 z80scc_channel::do_sccreg_rr14() +{ + LOG(("Z80SCC %s()\n", __func__)); + if (m_variant & (SET_ESCC | z80scc_device::TYPE_SCC85C30)) + return BIT(m_wr7, 6) ? m_wr7 : m_rr10; + else + return m_rr10; +} + +/* + RR15 reflects the value stored in WR15, the External/Status IE bits. The two unused bits are + always returned as Os. */ +UINT8 z80scc_channel::do_sccreg_rr15() +{ + LOG(("Z80SCC %s()\n", __func__)); + logerror("Z80SCC %s() not implemented feature\n", __func__); + return m_wr15 & 0xf5; // Mask out the used bits +} + +//------------------------------------------------- +// control_read - read control register +//------------------------------------------------- +UINT8 z80scc_channel::control_read() +{ + UINT8 data = 0; + int reg = m_wr0; + int regmask = (WR0_REGISTER_MASK | m_ph); + + // LOG(("%s(%02x) reg %02x, regmask %02x, WR0 %02x\n", __func__, data, reg, regmask, m_wr0)); + + m_ph = 0; // The "Point High" command is only valid for one access + + reg &= regmask; + + if (reg != 0) + { + // mask out register index + m_wr0 &= ~regmask; + } + + /* TODO. Sort out 80X30 limitations in register access */ + switch (reg) + { + case REG_RR0_STATUS: data = do_sioreg_rr0(); break; // TODO: verify handling of SCC specific bits: D6 and D1 + case REG_RR1_SPEC_RCV_COND: data = do_sioreg_rr1(); break; + case REG_RR2_INTERRUPT_VECT: data = do_sccreg_rr2(); break; // Channel dependent and SCC specific handling compared to SIO + /* registers 3-7 are specific to SCC. TODO: Check variant and log/stop misuse */ + case REG_RR3_INTERUPPT_PEND: data = do_sccreg_rr3(); break; + case REG_RR4_WR4_OR_RR0: data = do_sccreg_rr4(); break; + case REG_RR5_WR5_OR_RR0: data = do_sccreg_rr5(); break; + case REG_RR6_LSB_OR_RR2: data = do_sccreg_rr6(); break; + case REG_RR7_MSB_OR_RR3: data = do_sccreg_rr7(); break; + /* registers 8-15 are specific to SCC */ + case REG_RR8_RECEIVE_DATA: data = data_read(); break; + case REG_RR9_WR3_OR_RR13: data = do_sccreg_rr9(); break; + case REG_RR10_MISC_STATUS: data = do_sccreg_rr10(); break; + case REG_RR11_WR10_OR_RR15: data = do_sccreg_rr11(); break; + case REG_RR12_LO_TIME_CONST: data = do_sccreg_rr12(); break; + case REG_RR13_HI_TIME_CONST: data = do_sccreg_rr13(); break; + case REG_RR14_WR7_OR_R10: data = do_sccreg_rr14(); break; + case REG_RR15_WR15_EXT_STAT: data = do_sccreg_rr15(); break; + default: + logerror("Z80SCC \"%s\" Channel %c : Unsupported RRx register:%02x\n", m_owner->tag(), 'A' + m_index, reg); + } + + //LOG(("Z80SCC \"%s\" Channel %c : Register R%d read '%02x'\n", m_owner->tag(), 'A' + m_index, reg, data)); + + return data; +} + +/**/ +void z80scc_channel::do_sccreg_wr0(UINT8 data) +{ + m_wr0 = data; + + /* Sort out SCC specific behaviours from legacy SIO behaviour */ + /* WR0_Z_* are Z80X30 specific commands */ + switch (data & WR0_COMMAND_MASK) + { + case WR0_POINT_HIGH: + /*This command effectively adds eight to the Register Pointer (D2-D0) by allowing + WR8 through WR15 to be accessed. The Point High command and the Register + Pointer bits are written simultaneously. This command is used in the Z85X30 + version of the SCC. Note that WR0 changes form depending upon the SCC version. + Register access for the Z80X30 version of the SCC is accomplished through direct + addressing*/ + if (m_variant & SET_Z85X3X) + { + LOG(("Z80SCC \"%s\" Channel %c : %s - Point High command\n", m_owner->tag(), 'A' + m_index, __func__)); + m_ph = 8; + } + else + LOG(("Z80SCC \"%s\" Channel %c : %s - NULL command 2\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR0_RESET_EXT_STATUS: // TODO: Take care of the Zero Count flag and the 2 slot fifo + /*After an External/Status interrupt (a change on a modem line or a break condition, + for example), the status bits in RR0 are latched. This command re-enables the bits + and allows interrupts to occur again as a result of a status change. Latching the + status bits captures short pulses until the CPU has time to read the change. + The SCC contains simple queueing logic associated with most of the external status + bits in RR0. If another External/Status condition changes while a previous condition + is still pending (Reset External/Status Interrupt has not yet been issued) and this + condition persists until after the command is issued, this second change causes another + External/Status interrupt. However, if this second status change does not persist + (there are two transitions), another interrupt is not generated. Exceptions to this + rule are detailed in the RR0 description.*/ + do_sioreg_wr0(data); + if (!m_zc) m_rr0 |= RR0_ZC; + LOG(("Z80SCC \"%s\" Channel %c : %s - Reset External/Status Interrupt\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR0_RESET_HIGHEST_IUS: + /* This command resets the highest priority Interrupt Under Service (IUS) bit, allowing lower + priority conditions to request interrupts. This command allows the use of the internal + daisy chain (even in systems without an external daisy chain) and is the last operation in + an interrupt service routine.TODO: Implement internal Daisychain */ + LOG(("Z80SCC \"%s\" Channel %c : Reset Highest IUS\n", m_owner->tag(), 'A' + m_index)); + break; + case WR0_SEND_ABORT: + data &= 0xef; // convert SCC SEND_ABORT command to a SIO SEND_ABORT command and fall through + /* The following commands relies on the SIO default behviour */ + case WR0_NULL: + case WR0_ENABLE_INT_NEXT_RX: + case WR0_RESET_TX_INT: + case WR0_ERROR_RESET: + default: + do_sioreg_wr0(data); + } + do_sioreg_wr0_resets(data); + if ( m_variant & SET_Z80X30) // TODO: Implement adress decoding for Z80X30 using the shift logic described below + { + /*The registers in the Z80X30 are addressed via the address on AD7-AD0 and are latched by the rising + edge of /AS. The Shift Right/Shift Left bit in the Channel B WR0 controls which bits are + decoded to form the register address. It is placed in this register to simplify programming when the + current state of the Shift Right/Shift Left bit is not known. + A hardware reset forces Shift Left mode where the address is decoded from AD5-AD1. In Shift + Right mode, the address is decoded from AD4-AD0. The Shift Right/Shift Left bit is written via a + command to make the software writing to WR0 independent of the state of the Shift Right/Shift + Left bit. + While in the Shift Left mode, the register address is placed on AD4-AD1 and the Channel Select + bit, A/B, is decoded from AD5. The register map for this case is listed in Table on page 21. In + Shift Right mode, the register address is again placed on AD4-AD1 but the channel select A/B is + decoded from AD0. The register map for this case is listed in Table on page 23. + Because the Z80X30 does not contain 16 read registers, the decoding of the read registers is not + complete; this is listed in Table on page 21 and Table on page 23 by parentheses around the register + name. These addresses may also be used to access the read registers. Also, note that the + Z80X30 contains only one WR2 and WR9; these registers may be written from either channel. + Shift Left Mode is used when Channel A and B are to be programmed differently. This allows the + software to sequence through the registers of one channel at a time. The Shift Right Mode is used + when the channels are programmed the same. By incrementing the address, the user can program + the same data value into both the Channel A and Channel B register.*/ + switch(data & WR0_Z_SHIFT_MASK) + { + case WR0_Z_SEL_SHFT_LEFT: + LOG(("Z80SCC \"%s\" Channel %c : %s - Shift Left Addressing Mode - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR0_Z_SEL_SHFT_RIGHT: + LOG(("Z80SCC \"%s\" Channel %c : %s - Shift Right Addressing Mode - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + default: + break; + // LOG(("Z80SCC \"%s\" Channel %c : %s - Null commands\n", m_owner->tag(), 'A' + m_index, __func__)); + } + } +} + +/* Write Register 1 is the control register for the various SCC interrupt and Wait/Request modes.*/ +void z80scc_channel::do_sccreg_wr1(UINT8 data) +{ + LOG(("Z80SCC \"%s\" Channel %c : %s - %02x\n", m_owner->tag(), 'A' + m_index, __func__, data)); + /* TODO: Sort out SCC specific behaviours from legacy SIO behaviours: + - Channel B only bits vs + - Parity Is Special Condition, bit2 */ + do_sioreg_wr1(data & ~0x40); // Lets SIO code handle it for now but mask out dangerous bits + m_uart->check_interrupts(); +} + +/*WR2 is the interrupt vector register. Only one vector register exists in the SCC, and it can be +accessed through either channel. The interrupt vector can be modified by status information. This +is controlled by the Vector Includes Status (VIS) and the Status High/Status Low bits in WR9.*/ +void z80scc_channel::do_sccreg_wr2(UINT8 data) +{ + LOG(("Z80SCC \"%s\" Channel %c : %s - Setting the interrupt vector to: %02x \n", m_owner->tag(), 'A' + m_index, __func__, data)); + m_wr2 = data; + m_uart->m_chanA->m_rr2 = data; + m_uart->m_chanB->m_rr2 = data; /* TODO: Sort out the setting of ChanB depending on bits in WR9 */ + + m_uart->check_interrupts(); +} + +/* + * NOTE: Register WR3-WR7 are identical to the Z80SIO so handled by z80sio.c + */ + +/* WR8 is the transmit buffer register */ +void z80scc_channel::do_sccreg_wr8(UINT8 data) +{ + LOG(("Z80SCC \"%s\" Channel %c : Transmit Buffer read %02x\n", m_owner->tag(), 'A' + m_index, data)); + data_write(data); +} + +/*WR9 is the Master Interrupt Control register and contains the Reset command bits. Only one WR9 +exists in the SCC and is accessed from either channel. The Interrupt control bits are programmed +at the same time as the Reset command, because these bits are only reset by a hardware reset */ +void z80scc_channel::do_sccreg_wr9(UINT8 data) +{ + switch (data & WR9_CMD_MASK) + { + case WR9_CMD_NORESET: + LOG(("Z80SCC \"%s\" Channel %c : Master Interrupt Control - No reset %02x\n", m_owner->tag(), 'A' + m_index, data)); + break; + case WR9_CMD_CHNB_RESET: + LOG(("Z80SCC \"%s\" Channel %c : Master Interrupt Control - Channel B reset %02x\n", m_owner->tag(), 'A' + m_index, data)); + m_uart->m_chanB->reset(); + break; + case WR9_CMD_CHNA_RESET: + LOG(("Z80SCC \"%s\" Channel %c : Master Interrupt Control - Channel A reset %02x\n", m_owner->tag(), 'A' + m_index, data)); + m_uart->m_chanA->reset(); + break; + case WR9_CMD_HW_RESET: + LOG(("Z80SCC \"%s\" Channel %c : Master Interrupt Control - Device reset %02x\n", m_owner->tag(), 'A' + m_index, data)); + /*"The effects of this command are identical to those of a hardware reset, except that the Shift Right/Shift Left bit is + not changed and the MIE, Status High/Status Low and DLC bits take the programmed values that accompany this command." + The Shift Right/Shift Left bits of the WR0 is only valid on SCC8030 device hence not implemented yet, just the SCC8530 */ + if (data & (WR9_BIT_MIE | WR9_BIT_IACK | WR9_BIT_SHSL | WR9_BIT_DLC | WR9_BIT_NV)) + logerror("Z80SCC: SCC Interrupt system not yet implemented, please be patient!\n"); + m_uart->device_reset(); + default: + logerror("Z80SCC Code is broken in WR9, please report!\n"); + } +} + +/* WR10 contains miscellaneous control bits for both the receiver and the transmitter. Bit positions +for WR10 are displayed in Figure . On the ESCC and 85C30 with the Extended Read option +enabled, this register may be read as RR11.*/ +void z80scc_channel::do_sccreg_wr10(UINT8 data) +{ + m_wr10 = data; + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Tx/Rx Control %02x - not implemented \n", m_owner->tag(), 'A' + m_index, __func__, data)); +} + +/* WR11 is the Clock Mode Control register. The bits in this register control the sources of both the +receive and transmit clocks, the type of signal on the /SYNC and /RTxC pins, and the direction of +the /TRxC pin.*/ +void z80scc_channel::do_sccreg_wr11(UINT8 data) +{ + LOG(("Z80SCC \"%s\" Channel %c : %s Clock Mode Control %02x - not implemented \n", m_owner->tag(), 'A' + m_index, __func__, data)); + m_wr11 = data; + /*Bit 7: This bit controls the type of input signal the SCC expects to see on the /RTxC pin. If this bit is set + to 0, the SCC expects a TTL-compatible signal as an input to this pin. If this bit is set to 1, the SCC + connects a high-gain amplifier between the /RTxC and /SYNC pins in expectation of a quartz + crystal being placed across the pins. + The output of this oscillator is available for use as a clocking source. In this mode of operation, the + /SYNC pin is unavailable for other use. The /SYNC signal is forced to zero internally. A hardware + reset forces /NO XTAL. (At least 20 ms should be allowed after this bit is set to allow the oscillator + to stabilize.)*/ + LOG((" Clock type %s\n", data & WR11_RCVCLK_TYPE ? "Crystal oscillator between RTxC and /SYNC pins" : "TTL level on RTxC pin")); + /*Bits 6 and 5: Receiver Clock select bits 1 and 0 + These bits determine the source of the receive clock as listed below. They do not + interfere with any of the modes of operation in the SCC, but simply control a multiplexer just + before the internal receive clock input. A hardware reset forces the receive clock to come from the + /RTxC pin.*/ + LOG((" Receive clock source is: ")); + switch (data & WR11_RCVCLK_SRC_MASK) + { + case WR11_RCVCLK_SRC_RTXC: LOG(("RTxC\n")); break; + case WR11_RCVCLK_SRC_TRXC: LOG(("TRxC\n")); break; + case WR11_RCVCLK_SRC_BR: LOG(("Baudrate Generator\n")); break; + case WR11_RCVCLK_SRC_DPLL: LOG(("DPLL\n")); break; + default: logerror("Wrong!\n");/* Will not happen unless someone messes with the mask */ + } + /*Bits 4 and 3: Transmit Clock select bits 1 and 0. + These bits determine the source of the transmit clock as listed in Table . They do not interfere with + any of the modes of operation of the SCC, but simply control a multiplexer just before the internal + transmit clock input. The DPLL output that is used to feed the transmitter in FM modes lags by 90 + degrees the output of the DPLL used by the receiver. This makes the received and transmitted bit + cells occur simultaneously, neglecting delays. A hardware reset selects the /TRxC pin as the + source of the transmit clocks.*/ + LOG((" Transmit clock source is: ")); + switch (data & WR11_TRACLK_SRC_MASK) + { + case WR11_TRACLK_SRC_RTXC: LOG(("RTxC\n")); break; + case WR11_TRACLK_SRC_TRXC: LOG(("TRxC\n")); break; + case WR11_TRACLK_SRC_BR: LOG(("Baudrate Generator\n")); break; + case WR11_TRACLK_SRC_DPLL: LOG(("DPLL\n")); break; + default: logerror("Wrong!\n");/* Will not happen unless someone messes with the mask */ + } + /* Bit 2: TRxC Pin I/O control bit + This bit determines the direction of the /TRxC pin. If this bit is set to 1, the /TRxC pin is an output + and carries the signal selected by D1 and D0 of this register. However, if either the receive or the + transmit clock is programmed to come from the /TRxC pin, /TRxC is an input, regardless of the + state of this bit. The /TRxC pin is also an input if this bit is set to 0. A hardware reset forces this bit + to 0.*/ + LOG((" TRxC pin is %s\n", data & WR11_TRXC_DIRECTION ? "Output" : "Input")); + /*Bits 1 and 0: /TRxC Output Source select bits 1 and 0 + These bits determine the signal to be echoed out of the SCC via the /TRxC pin as listed in Table + on page 167. No signal is produced if /TRxC has been programmed as the source of either the + receive or the transmit clock. If /TRxC O/I (bit 2) is set to 0, these bits are ignored. + If the XTAL oscillator output is programmed to be echoed, and the XTAL oscillator is not enabled, + the /TRxC pin goes High. The DPLL signal that is echoed is the DPLL signal used by the receiver. + Hardware reset selects the XTAL oscillator as the output source*/ + LOG((" TRxC clock source is: ")); + switch (data & WR11_TRXSRC_SRC_MASK) + { + case WR11_TRXSRC_SRC_XTAL: LOG(("the Oscillator\n")); break; + case WR11_TRXSRC_SRC_TRA: LOG(("Transmit clock\n")); break; + case WR11_TRXSRC_SRC_BR: LOG(("Baudrate Generator\n")); break; + case WR11_TRXSRC_SRC_DPLL: LOG(("DPLL\n")); break; + default: logerror("Wrong!\n");/* Will not happen unless someone messes with the mask */ + } +} + +/*WR12 contains the lower byte of the time constant for the baud rate generator. The time constant +can be changed at any time, but the new value does not take effect until the next time the time constant +is loaded into the down counter. No attempt is made to synchronize the loading of the time +constant into WR12 and WR13 with the clock driving the down counter. For this reason, it is +advisable to disable the baud rate generator while the new time constant is loaded into WR12 and +WR13. Ordinarily, this is done anyway to prevent a load of the down counter between the writing +of the upper and lower bytes of the time constant. +The formula for determining the appropriate time constant for a given baud is shown below, with +the desired rate in bits per second and the BR clock period in seconds. This formula is derived +because the counter decrements from N down to zero-plus-one-cycle for reloading the time constant. +This is then fed to a toggle flip-flop to make the output a square wave. + + Time Constant = Clock Frequency / (2 * Desired Rate * Baud Rate Clock Period) - 2 + +*/ +void z80scc_channel::do_sccreg_wr12(UINT8 data) +{ + m_wr12 = data; + LOG(("Z80SCC \"%s\" Channel %c : %s %02x Low byte of Time Constant for Baudrate generator - not implemented \n", m_owner->tag(), 'A' + m_index, __func__, data)); +} + +/* WR13 contains the upper byte of the time constant for the baud rate generator. */ +void z80scc_channel::do_sccreg_wr13(UINT8 data) +{ + m_wr13 = data; + LOG(("Z80SCC \"%s\" Channel %c : %s %02x High byte of Time Constant for Baudrate generator - not implemented \n", m_owner->tag(), 'A' + m_index, __func__, data)); +} + +/* + WR14 contains some miscellaneous control bits */ +void z80scc_channel::do_sccreg_wr14(UINT8 data) +{ + switch (data & WR14_DPLL_CMD_MASK) + { + case WR14_CMD_NULL: + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Control Bits Null Command %02x\n", m_owner->tag(), 'A' + m_index, __func__, data)); + break; + case WR14_CMD_ESM: +/* Issuing this command causes the DPLL to enter the Search mode, where the DPLL searches for a locking edge in the + incoming data stream. The action taken by the DPLL upon receipt of this command depends on the operating mode of + the DPLL. In NRZI mode, the output of the DPLL is High while the DPLL is waiting for an edge in the incoming data + stream. After the Search mode is entered, the first edge the DPLL sees is assumed to be a valid data edge, and + the DPLL begins the clock recovery operation from that point. The DPLL clock rate must be 32x the data rate in + NRZI mode. Upon leaving the Search mode, the first sampling edge of the DPLL occurs 16 of these 32x clocks after + the first data edge, and the second sampling occurs 48 of these 32x clocks after the first data edge. Beyond + this point, the DPLL begins normal operation, adjusting the output to remain in sync with the incoming data. + In FM mode, the output of the DPLL is Low while the DPLL is waiting for an edge in the incoming data stream. + The first edge the DPLL detects is assumed to be a valid clock edge. For this to be the case, the line must + contain only clock edges; i.e. with FM1 encoding, the line must be continuous 0s. With FM0 encoding the line must + be continuous 1s, whereas Manchester encoding requires alternating 1s and 0s on the line. The DPLL clock rate must + be 16 times the data rate in FM mode. The DPLL output causes the receiver to sample the data stream in the nominal + center of the two halves of the bit to decide whether the data was a 1 or a 0. After this command is issued, as in + NRZI mode, the DPLL starts sampling immediately after the first edge is detected. (In FM mode, the DPLL examines + the clock edge of every other bit to decide what correction must be made to remain in sync.) If the DPLL does not + see an edge during the expected window, the one clock missing bit in RR10 is set. If the DPLL does not see an edge + after two successive attempts, the two clocks missing bits in RR10 are set and the DPLL automatically enters the + Search mode. This command resets both clocks missing latches.*/ + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Control Bits Enter Search Mode Command - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR14_CMD_RMC: +/* Issuing this command disables the DPLL, resets the clock missing latches in RR10, and forces a continuous Search mode state */ + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Control Bits Reset Missing Clocks Command - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR14_CMD_DISABLE_DPLL: +/* Issuing this command disables the DPLL, resets the clock missing latches in RR10, and forces a continuous Search mode state.*/ + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Control Bits Disable DPLL Command - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR14_CMD_SS_BGR: +/* Issuing this command forces the clock for the DPLL to come from the output of the BRG. */ + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Control Bits Baudrate Generator Input DPLL Command - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR14_CMD_SS_RTXC: +/* Issuing the command forces the clock for the DPLL to come from the /RTxC pin or the crystal oscillator, depending on + the state of the XTAL/no XTAL bit in WR11. This mode is selected by a channel or hardware reset*/ + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Control Bits RTxC Input DPLL Command - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR14_CMD_SET_FM: +/* This command forces the DPLL to operate in the FM mode and is used to recover the clock from FM or Manchester-Encoded + data. (Manchester is decoded by placing the receiver in NRZ mode while the DPLL is in FM mode.)*/ + LOG(("Z80SCC \"%s\" Channel %c : %s Misc Control Bits Set FM Mode Command - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + case WR14_CMD_SET_NRZI: +/* Issuing this command forces the DPLL to operate in the NRZI mode. This mode is also selected by a hardware or channel reset.*/ + LOG(("Z80SCC \"%s\" Channel %c : %s Mics Control Bits Set NRZI Mode Command - not implemented\n", m_owner->tag(), 'A' + m_index, __func__)); + break; + default: + logerror("Z80SCC \"%s\" Channel %c : %s Mics Control Bits command %02x - not implemented \n", m_owner->tag(), 'A' + m_index, __func__, data); + } + // TODO: Add info on the other bits of this register + m_wr14 = data; +} + +/* WR15 is the External/Status Source Control register. If the External/Status interrupts are enabled +as a group via WR1, bits in this register control which External/Status conditions cause an interrupt. +Only the External/Status conditions that occur after the controlling bit is set to 1 cause an +interrupt. This is true, even if an External/Status condition is pending at the time the bit is set*/ +void z80scc_channel::do_sccreg_wr15(UINT8 data) +{ + LOG(("Z80SCC \"%s\" Channel %c : %s External/Status Source Control Bits %02x - not implemented \n", m_owner->tag(), 'A' + m_index, __func__, data)); + m_wr15 = data; +} + + +//------------------------------------------------- +// control_write - write control register +//------------------------------------------------- + +void z80scc_channel::control_write(UINT8 data) +{ + UINT8 reg = m_wr0; + UINT8 regmask = (WR0_REGISTER_MASK | m_ph); + + m_ph = 0; // The "Point High" command is only valid for one access + + reg &= regmask; + + if (reg != 0) + { + // mask out register index + m_wr0 &= ~regmask; + } + + LOG(("%s(%02x) reg %02x, regmask %02x, WR0 %02x\n", __func__, data, reg, regmask, m_wr0)); + + /* TODO. Sort out 80X30 & other SCC variants limitations in register access */ + switch (reg) + { + case REG_WR0_COMMAND_REGPT: do_sccreg_wr0(data); break; + case REG_WR1_INT_DMA_ENABLE: do_sccreg_wr1(data); m_uart->check_interrupts(); break; + case REG_WR2_INT_VECTOR: do_sccreg_wr2(data); break; + case REG_WR3_RX_CONTROL: do_sioreg_wr3(data); update_serial(); break; + case REG_WR4_RX_TX_MODES: do_sioreg_wr4(data); update_serial(); break; + case REG_WR5_TX_CONTROL: do_sioreg_wr5(data); update_serial(); update_rts(); break; + case REG_WR6_SYNC_OR_SDLC_A: do_sioreg_wr6(data); break; + case REG_WR7_SYNC_OR_SDLC_F: do_sioreg_wr7(data); break; + case REG_WR8_TRANSMIT_DATA: do_sccreg_wr8(data); break; + case REG_WR9_MASTER_INT_CTRL: do_sccreg_wr9(data); break; + case REG_WR10_MSC_RX_TX_CTRL: do_sccreg_wr10(data); break; + case REG_WR11_CLOCK_MODES: do_sccreg_wr11(data); break; + case REG_WR12_LO_BAUD_GEN: do_sccreg_wr12(data); break; + case REG_WR13_HI_BAUD_GEN: do_sccreg_wr13(data); break; + case REG_WR14_MISC_CTRL: do_sccreg_wr14(data); break; + case REG_WR15_EXT_ST_INT_CTRL: + LOG(("Z80SCC \"%s\" Channel %c : unsupported command: External/Status Control Bits %02x\n", m_owner->tag(), 'A' + m_index, data)); + break; + default: + logerror("Z80SCC \"%s\" Channel %c : Unsupported WRx register:%02x\n", m_owner->tag(), 'A' + m_index, reg); + } +} + + +//------------------------------------------------- +// data_read - read data register +//------------------------------------------------- + +UINT8 z80scc_channel::data_read() +{ + UINT8 data = 0; + + if (m_rx_fifo >= 0) + { + // load data from the FIFO + data = m_rx_data_fifo[m_rx_fifo]; + + // load error status from the FIFO + m_rr1 = (m_rr1 & ~(RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR | RR1_PARITY_ERROR)) | m_rx_error_fifo[m_rx_fifo]; + + // decrease FIFO pointer + m_rx_fifo--; + + if (m_rx_fifo < 0) + { + // no more characters available in the FIFO + m_rr0 &= ~ RR0_RX_CHAR_AVAILABLE; + } + } + + LOG(("Z80SCC \"%s\" Channel %c : Data Register Read '%02x'\n", m_owner->tag(), 'A' + m_index, data)); + + return data; +} + + +//------------------------------------------------- +// data_write - write data register +//------------------------------------------------- + +void z80scc_channel::data_write(UINT8 data) +{ + m_tx_data = data; + + if ((m_wr5 & WR5_TX_ENABLE) && is_transmit_register_empty()) + { + LOG(("Z80SCC \"%s\" Channel %c : Transmit Data Byte '%02x'\n", m_owner->tag(), 'A' + m_index, m_tx_data)); + + transmit_register_setup(m_tx_data); + + // empty transmit buffer + m_rr0 |= RR0_TX_BUFFER_EMPTY; + + if (m_wr1 & WR1_TX_INT_ENABLE) + m_uart->trigger_interrupt(m_index, INT_TRANSMIT); + } + else + { + m_rr0 &= ~RR0_TX_BUFFER_EMPTY; + } + + m_rr1 &= ~RR1_ALL_SENT; + + LOG(("Z80SCC \"%s\" Channel %c : Data Register Write '%02x'\n", m_owner->tag(), 'A' + m_index, data)); +} + + +//------------------------------------------------- +// receive_data - receive data word +//------------------------------------------------- + +void z80scc_channel::receive_data(UINT8 data) +{ + LOG(("Z80SCC \"%s\" Channel %c : Receive Data Byte '%02x'\n", m_owner->tag(), 'A' + m_index, data)); + + if (m_rx_fifo == 2) + { + // receive overrun error detected + m_rx_error |= RR1_RX_OVERRUN_ERROR; + + switch (m_wr1 & WR1_RX_INT_MODE_MASK) + { + case WR1_RX_INT_FIRST: + if (!m_rx_first) + { + m_uart->trigger_interrupt(m_index, INT_SPECIAL); + } + break; + + case WR1_RX_INT_ALL_PARITY: + case WR1_RX_INT_ALL: + m_uart->trigger_interrupt(m_index, INT_SPECIAL); + break; + } + } + else + { + m_rx_fifo++; + } + + // store received character and error status into FIFO + m_rx_data_fifo[m_rx_fifo] = data; + m_rx_error_fifo[m_rx_fifo] = m_rx_error; + + m_rr0 |= RR0_RX_CHAR_AVAILABLE; + + // receive interrupt + switch (m_wr1 & WR1_RX_INT_MODE_MASK) + { + case WR1_RX_INT_FIRST: + if (m_rx_first) + { + m_uart->trigger_interrupt(m_index, INT_RECEIVE); + + m_rx_first = 0; + } + break; + + case WR1_RX_INT_ALL_PARITY: + case WR1_RX_INT_ALL: + m_uart->trigger_interrupt(m_index, INT_RECEIVE); + break; + } +} + + +//------------------------------------------------- +// cts_w - clear to send handler +//------------------------------------------------- + +WRITE_LINE_MEMBER( z80scc_channel::cts_w ) +{ + LOG(("Z80SCC \"%s\" Channel %c : CTS %u\n", m_owner->tag(), 'A' + m_index, state)); + + if (m_cts != state) + { + // enable transmitter if in auto enables mode + if (!state) + if (m_wr3 & WR3_AUTO_ENABLES) + m_wr5 |= WR5_TX_ENABLE; + + // set clear to send + m_cts = state; + + if (!m_rx_rr0_latch) + { + if (!m_cts) + m_rr0 |= RR0_CTS; + else + m_rr0 &= ~RR0_CTS; + + // trigger interrupt + if (m_wr1 & WR1_EXT_INT_ENABLE) + { + // trigger interrupt + m_uart->trigger_interrupt(m_index, INT_EXTERNAL); + + // latch read register 0 + m_rx_rr0_latch = 1; + } + } + } +} + + +//------------------------------------------------- +// dcd_w - data carrier detected handler +//------------------------------------------------- + +WRITE_LINE_MEMBER( z80scc_channel::dcd_w ) +{ + LOG(("Z80SCC \"%s\" Channel %c : DCD %u\n", m_owner->tag(), 'A' + m_index, state)); + + if (m_dcd != state) + { + // enable receiver if in auto enables mode + if (!state) + if (m_wr3 & WR3_AUTO_ENABLES) + m_wr3 |= WR3_RX_ENABLE; + + // set data carrier detect + m_dcd = state; + + if (!m_rx_rr0_latch) + { + if (m_dcd) + m_rr0 |= RR0_DCD; + else + m_rr0 &= ~RR0_DCD; + + if (m_wr1 & WR1_EXT_INT_ENABLE) + { + // trigger interrupt + m_uart->trigger_interrupt(m_index, INT_EXTERNAL); + + // latch read register 0 + m_rx_rr0_latch = 1; + } + } + } +} + + +//------------------------------------------------- +// ri_w - ring indicator handler +//------------------------------------------------- + +WRITE_LINE_MEMBER( z80scc_channel::ri_w ) +{ + LOG(("Z80SCC \"%s\" Channel %c : RI %u\n", m_owner->tag(), 'A' + m_index, state)); + + if (m_ri != state) + { + // set ring indicator state + m_ri = state; + + if (!m_rx_rr0_latch) + { + if (m_ri) + m_rr0 |= RR0_RI; + else + m_rr0 &= ~RR0_RI; + + if (m_wr1 & WR1_EXT_INT_ENABLE) + { + // trigger interrupt + m_uart->trigger_interrupt(m_index, INT_EXTERNAL); + + // latch read register 0 + m_rx_rr0_latch = 1; + } + } + } +} + +//------------------------------------------------- +// sync_w - sync handler +//------------------------------------------------- +WRITE_LINE_MEMBER( z80scc_channel::sync_w ) +{ + LOG(("Z80SCC \"%s\" Channel %c : SYNC %u\n", m_owner->tag(), 'A' + m_index, state)); +} + +//------------------------------------------------- +// rxc_w - receive clock +//------------------------------------------------- +WRITE_LINE_MEMBER( z80scc_channel::rxc_w ) +{ + //LOG(("Z80SCC \"%s\" Channel %c : Receiver Clock Pulse\n", m_owner->tag(), m_index + 'A')); + int clocks = get_clock_mode(); + if (clocks == 1) + rx_clock_w(state); + else if(state) + { + rx_clock_w(m_rx_clock < clocks/2); + + m_rx_clock++; + if (m_rx_clock == clocks) + m_rx_clock = 0; + + } +} + +//------------------------------------------------- +// txc_w - transmit clock +//------------------------------------------------- +WRITE_LINE_MEMBER( z80scc_channel::txc_w ) +{ + //LOG(("Z80SCC \"%s\" Channel %c : Transmitter Clock Pulse\n", m_owner->tag(), m_index + 'A')); + int clocks = get_clock_mode(); + if (clocks == 1) + tx_clock_w(state); + else if(state) + { + tx_clock_w(m_tx_clock < clocks/2); + + m_tx_clock++; + if (m_tx_clock == clocks) + m_tx_clock = 0; + + } +} + +//------------------------------------------------- +// update_serial - +//------------------------------------------------- +void z80scc_channel::update_serial() +{ + int data_bit_count = get_rx_word_length(); + stop_bits_t stop_bits = get_stop_bits(); + parity_t parity; + + LOG(("Z80SCC update serial\n")); + + if (m_wr4 & WR4_PARITY_ENABLE) + { + if (m_wr4 & WR4_PARITY_EVEN) + parity = PARITY_EVEN; + else + parity = PARITY_ODD; + } + else + parity = PARITY_NONE; + + set_data_frame(1, data_bit_count, parity, stop_bits); + + int clocks = get_clock_mode(); + + if (m_rxc > 0) + { + set_rcv_rate(m_rxc / clocks); + } + + if (m_txc > 0) + { + set_tra_rate(m_txc / clocks); + } + receive_register_reset(); // if stop bits is changed from 0, receive register has to be reset +} + +//------------------------------------------------- +// set_dtr - +//------------------------------------------------- +void z80scc_channel::set_dtr(int state) +{ + m_dtr = state; + + LOG(("Z80SCC \"%s\" Channel %c : %s(%d)\n", m_owner->tag(), 'A' + m_index, __func__, state)); + if (m_index == z80scc_device::CHANNEL_A) + m_uart->m_out_dtra_cb(m_dtr); + else + m_uart->m_out_dtrb_cb(m_dtr); +} + + + +//------------------------------------------------- +// write_rx - called by terminal through rs232/diserial +// when character is sent to board +//------------------------------------------------- +WRITE_LINE_MEMBER(z80scc_channel::write_rx) +{ + // printf("%c", state ? '+' : 'o'); + m_rxd = state; + //only use rx_w when self-clocked + if(m_rxc) + device_serial_interface::rx_w(state); +} diff --git a/src/emu/machine/z80scc.h b/src/emu/machine/z80scc.h new file mode 100644 index 00000000000..110bff86a72 --- /dev/null +++ b/src/emu/machine/z80scc.h @@ -0,0 +1,738 @@ +// license:BSD-3-Clause +// copyright-holders:Joakim Larsson Edstrom +/*************************************************************************** + + Z80-SCC Serial Communications Controller emulation + +**************************************************************************** + _____ _____ _____ _____ + AD1 1|* \_/ |40 AD0 D1 1|* \_/ |40 D0 + AD3 2| |39 AD2 D3 2| |39 D2 + AD5 3| |38 AD4 D5 3| |38 D4 + AD7 4| |37 AD6 D7 4| |37 D6 + _INT 5| |36 _DS _INT 5| |36 _RD + IEO 6| |35 _AS IEO 6| |35 _WR + IEI 7| |34 R/_W IEI 7| |34 B/_A + _INTACK 8| |33 _CS0 _INTACK 8| |33 _CE + VCC 9| |32 CS1 VCC 9| |32 C/_D + _W//REQA 10| |31 GND _W//REQA 10| |31 GND + _SYNCA 11| Z8030 |30 _W/_REQ _SYNCA 11| Z8530 |30 _W/_REQB + _RTxCA 12| Z80C30 |29 _SYNCB _RTxCA 12| Z85C30 |29 _SYNCB + RxDA 13| Z80230 |28 _RTxCB RxDA 13| Z85230 |28 _RTxCB + _TRxCA 14| |27 RxDB _TRxCA 14| |27 RxDB + TxDA 15| |26 _TRxCB TxDA 15| |26 _TRxCB + _DTR//REQA 16| |25 TxDB _DTR//REQA 16| |25 TxDB + _RTSA 17| |24 _DTR/_REQB _RTSA 17| |24 _DTR/_REQB + _CTSA 18| |23 _RTSB _CTSA 18| |23 _RTSB + _DCDA 19| |22 _CTSB _DCDA 19| |22 _CTSB + PCLK 20|_____________|21 _DCDB PCLK 20|_____________|21 _DCDB + ZBUS Universal Bus + +***************************************************************************/ + +#ifndef __Z80SCC_H__ +#define __Z80SCC_H__ + +#include "emu.h" +#include "z80sio.h" +#include "cpu/z80/z80daisy.h" + +//************************************************************************** +// DEVICE CONFIGURATION MACROS +//************************************************************************** + +#define MCFG_Z80SCC_ADD(_tag, _clock, _rxa, _txa, _rxb, _txb) \ + MCFG_DEVICE_ADD(_tag, Z80SCC, _clock) \ + MCFG_Z80SCC_OFFSETS(_rxa, _txa, _rxb, _txb) + +#define MCFG_SCC8530_ADD(_tag, _clock, _rxa, _txa, _rxb, _txb) \ + MCFG_DEVICE_ADD(_tag, SCC8530, _clock) \ + MCFG_Z80SCC_OFFSETS(_rxa, _txa, _rxb, _txb) + +#define MCFG_Z80SCC_OFFSETS(_rxa, _txa, _rxb, _txb) \ + z80scc_device::configure_channels(*device, _rxa, _txa, _rxb, _txb); + +// Port A callbacks +#define MCFG_Z80SCC_OUT_TXDA_CB(_devcb) \ + devcb = &z80scc_device::set_out_txda_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_DTRA_CB(_devcb) \ + devcb = &z80scc_device::set_out_dtra_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_RTSA_CB(_devcb) \ + devcb = &z80scc_device::set_out_rtsa_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_WRDYA_CB(_devcb) \ + devcb = &z80scc_device::set_out_wrdya_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_SYNCA_CB(_devcb) \ + devcb = &z80scc_device::set_out_synca_callback(*device, DEVCB_##_devcb); + +// Port B callbacks +#define MCFG_Z80SCC_OUT_TXDB_CB(_devcb) \ + devcb = &z80scc_device::set_out_txdb_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_DTRB_CB(_devcb) \ + devcb = &z80scc_device::set_out_dtrb_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_RTSB_CB(_devcb) \ + devcb = &z80scc_device::set_out_rtsb_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_WRDYB_CB(_devcb) \ + devcb = &z80scc_device::set_out_wrdyb_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_SYNCB_CB(_devcb) \ + devcb = &z80scc_device::set_out_syncb_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_INT_CB(_devcb) \ + devcb = &z80scc_device::set_out_int_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_RXDRQA_CB(_devcb) \ + devcb = &z80scc_device::set_out_rxdrqa_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_TXDRQA_CB(_devcb) \ + devcb = &z80scc_device::set_out_txdrqa_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_RXDRQB_CB(_devcb) \ + devcb = &z80scc_device::set_out_rxdrqb_callback(*device, DEVCB_##_devcb); + +#define MCFG_Z80SCC_OUT_TXDRQB_CB(_devcb) \ + devcb = &z80scc_device::set_out_txdrqb_callback(*device, DEVCB_##_devcb); + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// ======================> z80scc_channel + +class z80scc_device; + +class z80scc_channel : public z80sio_channel +{ + friend class z80scc_device; + +public: + z80scc_channel(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + // device-level overrides + virtual void device_start(); + virtual void device_reset(); + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr); + + // device_serial_interface overrides + virtual void tra_callback(); + virtual void tra_complete(); + virtual void rcv_callback(); + virtual void rcv_complete(); + + // read register handlers + UINT8 do_sccreg_rr0(); + UINT8 do_sccreg_rr1(); + UINT8 do_sccreg_rr2(); + UINT8 do_sccreg_rr3(); + UINT8 do_sccreg_rr4(); + UINT8 do_sccreg_rr5(); + UINT8 do_sccreg_rr6(); + UINT8 do_sccreg_rr7(); + UINT8 do_sccreg_rr8(); + UINT8 do_sccreg_rr9(); + UINT8 do_sccreg_rr10(); + UINT8 do_sccreg_rr11(); + UINT8 do_sccreg_rr12(); + UINT8 do_sccreg_rr13(); + UINT8 do_sccreg_rr14(); + UINT8 do_sccreg_rr15(); + + // write register handlers + void do_sccreg_wr0(UINT8 data); + void do_sccreg_wr1(UINT8 data); + void do_sccreg_wr2(UINT8 data); + void do_sccreg_wr3(UINT8 data); + void do_sccreg_wr4(UINT8 data); + void do_sccreg_wr5(UINT8 data); + void do_sccreg_wr6(UINT8 data); + void do_sccreg_wr7(UINT8 data); + void do_sccreg_wr8(UINT8 data); + void do_sccreg_wr9(UINT8 data); + void do_sccreg_wr10(UINT8 data); + void do_sccreg_wr11(UINT8 data); + void do_sccreg_wr12(UINT8 data); + void do_sccreg_wr13(UINT8 data); + void do_sccreg_wr14(UINT8 data); + void do_sccreg_wr15(UINT8 data); + + UINT8 control_read(); + void control_write(UINT8 data); + + UINT8 data_read(); + void data_write(UINT8 data); + + void receive_data(UINT8 data); + + DECLARE_WRITE_LINE_MEMBER( write_rx ); + DECLARE_WRITE_LINE_MEMBER( cts_w ); + DECLARE_WRITE_LINE_MEMBER( dcd_w ); + DECLARE_WRITE_LINE_MEMBER( ri_w ); + DECLARE_WRITE_LINE_MEMBER( rxc_w ); + DECLARE_WRITE_LINE_MEMBER( txc_w ); + DECLARE_WRITE_LINE_MEMBER( sync_w ); + + int m_rxc; + int m_txc; + + // Register state + // read registers enum +#if 0 //defined by z80sio.h + UINT8 m_rr0; // REG_RR0_STATUS + UINT8 m_rr1; // REG_RR1_SPEC_RCV_COND + UINT8 m_rr2; // REG_RR2_INTERRUPT_VECT +#endif + UINT8 m_rr3; // REG_RR3_INTERUPPT_PEND + UINT8 m_rr4; // REG_RR4_WR4_OR_RR0 + UINT8 m_rr5; // REG_RR5_WR5_OR_RR0 + UINT8 m_rr6; // REG_RR6_LSB_OR_RR2 + UINT8 m_rr7; // REG_RR7_MSB_OR_RR3 + UINT8 m_rr8; // REG_RR8_RECEIVE_DATA + UINT8 m_rr9; // REG_RR9_WR3_OR_RR13 + UINT8 m_rr10; // REG_RR10_MISC_STATUS + UINT8 m_rr11; // REG_RR11_WR10_OR_RR15 + UINT8 m_rr12; // REG_RR12_LO_TIME_CONST + UINT8 m_rr13; // REG_RR13_HI_TIME_CONST + UINT8 m_rr14; // REG_RR14_WR7_OR_R10 + UINT8 m_rr15; // REG_RR15_WR15_EXT_STAT + + // write registers enum +#if 0 //defined by z80sio.h + UINT8 m_wr0; // REG_WR0_COMMAND_REGPT + UINT8 m_wr1; // REG_WR1_INT_DMA_ENABLE + UINT8 m_wr2; // REG_WR2_INT_VECTOR + UINT8 m_wr3; // REG_WR3_RX_CONTROL + UINT8 m_wr4; // REG_WR4_RX_TX_MODES + UINT8 m_wr5; // REG_WR5_TX_CONTROL + UINT8 m_wr6; // REG_WR6_SYNC_OR_SDLC_A + UINT8 m_wr7; // REG_WR7_SYNC_OR_SDLC_F +#endif + UINT8 m_wr8; // REG_WR8_TRANSMIT_DATA + UINT8 m_wr9; // REG_WR9_MASTER_INT_CTRL + UINT8 m_wr10; // REG_WR10_MSC_RX_TX_CTRL + UINT8 m_wr11; // REG_WR11_CLOCK_MODES + UINT8 m_wr12; // REG_WR12_LO_BAUD_GEN + UINT8 m_wr13; // REG_WR13_HI_BAUD_GEN + UINT8 m_wr14; // REG_WR14_MISC_CTRL + UINT8 m_wr15; // REG_WR15_EXT_ST_INT_CTRL + + +protected: + enum + { + INT_TRANSMIT = 0, + INT_EXTERNAL, + INT_RECEIVE, + INT_SPECIAL + }; + + // Read registers + enum + { + REG_RR0_STATUS = 0, // SIO + REG_RR1_SPEC_RCV_COND = 1, // SIO + REG_RR2_INTERRUPT_VECT = 2, // SIO + REG_RR3_INTERUPPT_PEND = 3, + REG_RR4_WR4_OR_RR0 = 4, + REG_RR5_WR5_OR_RR0 = 5, + REG_RR6_LSB_OR_RR2 = 6, + REG_RR7_MSB_OR_RR3 = 7, + REG_RR8_RECEIVE_DATA = 8, + REG_RR9_WR3_OR_RR13 = 9, + REG_RR10_MISC_STATUS = 10, + REG_RR11_WR10_OR_RR15 = 11, + REG_RR12_LO_TIME_CONST = 12, + REG_RR13_HI_TIME_CONST = 13, + REG_RR14_WR7_OR_R10 = 14, + REG_RR15_WR15_EXT_STAT = 15 + }; + + // Write registers + enum + { + REG_WR0_COMMAND_REGPT = 0, // SIO + REG_WR1_INT_DMA_ENABLE = 1, // SIO + REG_WR2_INT_VECTOR = 2, // SIO + REG_WR3_RX_CONTROL = 3, // SIO + REG_WR4_RX_TX_MODES = 4, // SIO + REG_WR5_TX_CONTROL = 5, // SIO + REG_WR6_SYNC_OR_SDLC_A = 6, // SIO + REG_WR7_SYNC_OR_SDLC_F = 7, // SIO + REG_WR8_TRANSMIT_DATA = 8, + REG_WR9_MASTER_INT_CTRL = 9, + REG_WR10_MSC_RX_TX_CTRL = 10, + REG_WR11_CLOCK_MODES = 11, + REG_WR12_LO_BAUD_GEN = 12, + REG_WR13_HI_BAUD_GEN = 13, + REG_WR14_MISC_CTRL = 14, + REG_WR15_EXT_ST_INT_CTRL= 15 + }; + + enum + { + RR0_RX_CHAR_AVAILABLE = 0x01, // SIO bit + RR0_ZC = 0x02, // SCC bit + RR0_TX_BUFFER_EMPTY = 0x04, // SIO + RR0_DCD = 0x08, // SIO + RR0_RI = 0x10, // DART bit? TODO: investigate function and remove + RR0_SYNC_HUNT = 0x10, // SIO bit, not supported + RR0_CTS = 0x20, // SIO bit + RR0_TX_UNDERRUN = 0x40, // SIO bit, not supported + RR0_BREAK_ABORT = 0x80 // SIO bit, not supported + }; + + enum + { + RR1_ALL_SENT = 0x01, // SIO/SCC bit + RR1_RESIDUE_CODE_MASK = 0x0e, // SIO/SCC bits, not supported + RR1_PARITY_ERROR = 0x10, // SIO/SCC bits + RR1_RX_OVERRUN_ERROR = 0x20, // SIO/SCC bits + RR1_CRC_FRAMING_ERROR = 0x40, // SIO/SCC bits + RR1_END_OF_FRAME = 0x80 // SIO/SCC bits, not supported + }; + + enum + { // TODO: overload SIO functionality + RR2_INT_VECTOR_MASK = 0xff, // SCC channel A, SIO channel B (special case) + RR2_INT_VECTOR_V1 = 0x02, // SIO (special case) /SCC Channel B + RR2_INT_VECTOR_V2 = 0x04, // SIO (special case) /SCC Channel B + RR2_INT_VECTOR_V3 = 0x08 // SIO (special case) /SCC Channel B + }; + + enum + { + RR3_CHANB_EXT_IP = 0x01, // SCC IP pending registers + RR3_CHANB_TX_IP = 0x02, // only read in Channel A (for both channels) + RR3_CHANB_RX_IP = 0x04, // channel B return all zero + RR3_CHANA_EXT_IP = 0x08, + RR3_CHANA_TX_IP = 0x10, + RR3_CHANA_RX_IP = 0x20 + }; + + enum // Universal Bus WR0 commands for 85X30 + { + WR0_REGISTER_MASK = 0x07, + WR0_COMMAND_MASK = 0x38, // COMMANDS + WR0_NULL = 0x00, // 0 0 0 + WR0_POINT_HIGH = 0x08, // 0 0 1 + WR0_RESET_EXT_STATUS = 0x10, // 0 1 0 + WR0_SEND_ABORT = 0x18, // 0 1 1 + WR0_ENABLE_INT_NEXT_RX = 0x20, // 1 0 0 + WR0_RESET_TX_INT = 0x28, // 1 0 1 + WR0_ERROR_RESET = 0x30, // 1 1 0 + WR0_RESET_HIGHEST_IUS = 0x38, // 1 1 1 + WR0_CRC_RESET_CODE_MASK = 0xc0, // RESET + WR0_CRC_RESET_NULL = 0x00, // 0 0 + WR0_CRC_RESET_RX = 0x40, // 0 1 + WR0_CRC_RESET_TX = 0x80, // 1 0 + WR0_CRC_RESET_TX_UNDERRUN = 0xc0 // 1 1 + }; + + enum // ZBUS WR0 commands or 80X30 + { + WR0_Z_COMMAND_MASK = 0x38, // COMMANDS + WR0_Z_NULL_1 = 0x00, // 0 0 0 + WR0_Z_NULL_2 = 0x08, // 0 0 1 + WR0_Z_RESET_EXT_STATUS = 0x10, // 0 1 0 + WR0_Z_SEND_ABORT = 0x18, // 0 1 1 + WR0_Z_ENABLE_INT_NEXT_RX = 0x20, // 1 0 0 + WR0_Z_RESET_TX_INT = 0x28, // 1 0 1 + WR0_Z_ERROR_RESET = 0x30, // 1 1 0 + WR0_Z_RESET_HIGHEST_IUS = 0x38, // 1 1 1 + WR0_Z_SHIFT_MASK = 0x03, // SHIFT mode SDLC chan B + WR0_Z_SEL_SHFT_LEFT = 0x02, // 1 0 + WR0_Z_SEL_SHFT_RIGHT = 0x03 // 1 1 + }; + + enum + { + WR1_EXT_INT_ENABLE = 0x01, + WR1_TX_INT_ENABLE = 0x02, + WR1_STATUS_VECTOR = 0x04, + WR1_RX_INT_MODE_MASK = 0x18, + WR1_RX_INT_DISABLE = 0x00, + WR1_RX_INT_FIRST = 0x08, + WR1_RX_INT_ALL_PARITY = 0x10, // not supported + WR1_RX_INT_ALL = 0x18, + WR1_WRDY_ON_RX_TX = 0x20, // not supported + WR1_WRDY_FUNCTION = 0x40, // not supported + WR1_WRDY_ENABLE = 0x80 // not supported + }; + + enum + { + WR2_DATA_XFER_INT = 0x00, // not supported + WR2_DATA_XFER_DMA_INT = 0x01, // not supported + WR2_DATA_XFER_DMA = 0x02, // not supported + WR2_DATA_XFER_ILLEGAL = 0x03, // not supported + WR2_DATA_XFER_MASK = 0x03, // not supported + WR2_PRIORITY = 0x04, // not supported + WR2_MODE_8085_1 = 0x00, // not supported + WR2_MODE_8085_2 = 0x08, // not supported + WR2_MODE_8086_8088 = 0x10, // not supported + WR2_MODE_ILLEGAL = 0x18, // not supported + WR2_MODE_MASK = 0x18, // not supported + WR2_VECTORED_INT = 0x20, // not supported + WR2_PIN10_SYNDETB_RTSB = 0x80 // not supported + }; + + enum + { + WR3_RX_ENABLE = 0x01, + WR3_SYNC_CHAR_LOAD_INHIBIT= 0x02, // not supported + WR3_ADDRESS_SEARCH_MODE = 0x04, // not supported + WR3_RX_CRC_ENABLE = 0x08, // not supported + WR3_ENTER_HUNT_PHASE = 0x10, // not supported + WR3_AUTO_ENABLES = 0x20, + WR3_RX_WORD_LENGTH_MASK = 0xc0, + WR3_RX_WORD_LENGTH_5 = 0x00, + WR3_RX_WORD_LENGTH_7 = 0x40, + WR3_RX_WORD_LENGTH_6 = 0x80, + WR3_RX_WORD_LENGTH_8 = 0xc0 + }; + + enum + { + WR4_PARITY_ENABLE = 0x01, + WR4_PARITY_EVEN = 0x02, + WR4_STOP_BITS_MASK = 0x0c, + WR4_STOP_BITS_1 = 0x04, + WR4_STOP_BITS_1_5 = 0x08, // not supported + WR4_STOP_BITS_2 = 0x0c, + WR4_SYNC_MODE_MASK = 0x30, // not supported + WR4_SYNC_MODE_8_BIT = 0x00, // not supported + WR4_SYNC_MODE_16_BIT = 0x10, // not supported + WR4_SYNC_MODE_SDLC = 0x20, // not supported + WR4_SYNC_MODE_EXT = 0x30, // not supported + WR4_CLOCK_RATE_MASK = 0xc0, + WR4_CLOCK_RATE_X1 = 0x00, + WR4_CLOCK_RATE_X16 = 0x40, + WR4_CLOCK_RATE_X32 = 0x80, + WR4_CLOCK_RATE_X64 = 0xc0 + }; + + enum + { + WR5_TX_CRC_ENABLE = 0x01, // not supported + WR5_RTS = 0x02, + WR5_CRC16 = 0x04, // not supported + WR5_TX_ENABLE = 0x08, + WR5_SEND_BREAK = 0x10, + WR5_TX_WORD_LENGTH_MASK = 0x60, + WR5_TX_WORD_LENGTH_5 = 0x00, + WR5_TX_WORD_LENGTH_6 = 0x40, + WR5_TX_WORD_LENGTH_7 = 0x20, + WR5_TX_WORD_LENGTH_8 = 0x60, + WR5_DTR = 0x80 + }; + + /* SCC specifics */ + enum + { + WR9_CMD_MASK = 0xC0, + WR9_CMD_NORESET = 0x00, + WR9_CMD_CHNB_RESET = 0x40, + WR9_CMD_CHNA_RESET = 0x80, + WR9_CMD_HW_RESET = 0xC0, + WR9_BIT_VIS = 0x01, + WR9_BIT_NV = 0x02, + WR9_BIT_DLC = 0x04, + WR9_BIT_MIE = 0x08, + WR9_BIT_SHSL = 0x10, + WR9_BIT_IACK = 0x20 + }; + + enum + { + WR11_RCVCLK_TYPE = 0x80, + WR11_RCVCLK_SRC_MASK = 0x60, // RCV CLOCK + WR11_RCVCLK_SRC_RTXC = 0x00, // 0 0 + WR11_RCVCLK_SRC_TRXC = 0x20, // 0 1 + WR11_RCVCLK_SRC_BR = 0x40, // 1 0 + WR11_RCVCLK_SRC_DPLL = 0x60, // 1 1 + WR11_TRACLK_SRC_MASK = 0x18, // TRA CLOCK + WR11_TRACLK_SRC_RTXC = 0x00, // 0 0 + WR11_TRACLK_SRC_TRXC = 0x08, // 0 1 + WR11_TRACLK_SRC_BR = 0x10, // 1 0 + WR11_TRACLK_SRC_DPLL = 0x18, // 1 1 + WR11_TRXC_DIRECTION = 0x04, + WR11_TRXSRC_SRC_MASK = 0x03, // TRXX CLOCK + WR11_TRXSRC_SRC_XTAL = 0x00, // 0 0 + WR11_TRXSRC_SRC_TRA = 0x01, // 0 1 + WR11_TRXSRC_SRC_BR = 0x02, // 1 0 + WR11_TRXSRC_SRC_DPLL = 0x03, // 1 1 + }; + + enum + { + WR14_DPLL_CMD_MASK = 0xe0, // Command + WR14_CMD_NULL = 0x00, // 0 0 0 + WR14_CMD_ESM = 0x20, // 0 0 1 + WR14_CMD_RMC = 0x40, // 0 1 0 + WR14_CMD_DISABLE_DPLL = 0x60, // 0 1 1 + WR14_CMD_SS_BGR = 0x80, // 1 0 0 + WR14_CMD_SS_RTXC = 0xa0, // 1 0 1 + WR14_CMD_SET_FM = 0xc0, // 1 1 0 + WR14_CMD_SET_NRZI = 0xe0 // 1 1 1 + }; + + void update_serial(); + void set_dtr(int state); + void set_rts(int state); + + int get_clock_mode(); + void update_rts(); + stop_bits_t get_stop_bits(); + int get_rx_word_length(); + int get_tx_word_length(); + + // receiver state + UINT8 m_rx_data_fifo[3]; // receive data FIFO + UINT8 m_rx_error_fifo[3]; // receive error FIFO + UINT8 m_rx_error; // current receive error + int m_rx_fifo; // receive FIFO pointer + + int m_rx_clock; // receive clock pulse count + int m_rx_first; // first character received + int m_rx_break; // receive break condition + UINT8 m_rx_rr0_latch; // read register 0 latched + + int m_rxd; + int m_ri; // ring indicator latch + int m_cts; // clear to send latch + int m_dcd; // data carrier detect latch + + // transmitter state + UINT8 m_tx_data; // transmit data register + int m_tx_clock; // transmit clock pulse count + + int m_dtr; // data terminal ready + int m_rts; // request to send + + // synchronous state + UINT16 m_sync; // sync character + + // int m_index; + z80scc_device *m_uart; + + // SCC specifics + int m_ph; // Point high command to access regs 08-0f + UINT8 m_zc; +}; + + +// ======================> z80scc_device + +class z80scc_device : public device_t + ,public device_z80daisy_interface +{ + friend class z80scc_channel; + +public: + // construction/destruction + z80scc_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, UINT32 variant, const char *shortname, const char *source); + z80scc_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + template static devcb_base &set_out_txda_callback(device_t &device, _Object object) { return downcast(device).m_out_txda_cb.set_callback(object); } + template static devcb_base &set_out_dtra_callback(device_t &device, _Object object) { return downcast(device).m_out_dtra_cb.set_callback(object); } + template static devcb_base &set_out_rtsa_callback(device_t &device, _Object object) { return downcast(device).m_out_rtsa_cb.set_callback(object); } + template static devcb_base &set_out_wrdya_callback(device_t &device, _Object object) { return downcast(device).m_out_wrdya_cb.set_callback(object); } + template static devcb_base &set_out_synca_callback(device_t &device, _Object object) { return downcast(device).m_out_synca_cb.set_callback(object); } + template static devcb_base &set_out_txdb_callback(device_t &device, _Object object) { return downcast(device).m_out_txdb_cb.set_callback(object); } + template static devcb_base &set_out_dtrb_callback(device_t &device, _Object object) { return downcast(device).m_out_dtrb_cb.set_callback(object); } + template static devcb_base &set_out_rtsb_callback(device_t &device, _Object object) { return downcast(device).m_out_rtsb_cb.set_callback(object); } + template static devcb_base &set_out_wrdyb_callback(device_t &device, _Object object) { return downcast(device).m_out_wrdyb_cb.set_callback(object); } + template static devcb_base &set_out_syncb_callback(device_t &device, _Object object) { return downcast(device).m_out_syncb_cb.set_callback(object); } + template static devcb_base &set_out_int_callback(device_t &device, _Object object) { return downcast(device).m_out_int_cb.set_callback(object); } + template static devcb_base &set_out_rxdrqa_callback(device_t &device, _Object object) { return downcast(device).m_out_rxdrqa_cb.set_callback(object); } + template static devcb_base &set_out_txdrqa_callback(device_t &device, _Object object) { return downcast(device).m_out_txdrqa_cb.set_callback(object); } + template static devcb_base &set_out_rxdrqb_callback(device_t &device, _Object object) { return downcast(device).m_out_rxdrqb_cb.set_callback(object); } + template static devcb_base &set_out_txdrqb_callback(device_t &device, _Object object) { return downcast(device).m_out_txdrqb_cb.set_callback(object); } + + static void configure_channels(device_t &device, int rxa, int txa, int rxb, int txb) + { + z80scc_device &dev = downcast(device); + dev.m_rxca = rxa; + dev.m_txca = txa; + dev.m_rxcb = rxb; + dev.m_txcb = txb; + } + + DECLARE_READ8_MEMBER( cd_ba_r ); + DECLARE_WRITE8_MEMBER( cd_ba_w ); + DECLARE_READ8_MEMBER( ba_cd_r ); + DECLARE_WRITE8_MEMBER( ba_cd_w ); + + DECLARE_READ8_MEMBER( da_r ) { return m_chanA->data_read(); } + DECLARE_WRITE8_MEMBER( da_w ) { m_chanA->data_write(data); } + DECLARE_READ8_MEMBER( db_r ) { return m_chanB->data_read(); } + DECLARE_WRITE8_MEMBER( db_w ) { m_chanB->data_write(data); } + + DECLARE_READ8_MEMBER( ca_r ) { return m_chanA->control_read(); } + DECLARE_WRITE8_MEMBER( ca_w ) { m_chanA->control_write(data); } + DECLARE_READ8_MEMBER( cb_r ) { return m_chanB->control_read(); } + DECLARE_WRITE8_MEMBER( cb_w ) { m_chanB->control_write(data); } + + // interrupt acknowledge + int m1_r(); + + DECLARE_WRITE_LINE_MEMBER( rxa_w ) { m_chanA->write_rx(state); } + DECLARE_WRITE_LINE_MEMBER( rxb_w ) { m_chanB->write_rx(state); } + DECLARE_WRITE_LINE_MEMBER( ctsa_w ) { m_chanA->cts_w(state); } + DECLARE_WRITE_LINE_MEMBER( ctsb_w ) { m_chanB->cts_w(state); } + DECLARE_WRITE_LINE_MEMBER( dcda_w ) { m_chanA->dcd_w(state); } + DECLARE_WRITE_LINE_MEMBER( dcdb_w ) { m_chanB->dcd_w(state); } + DECLARE_WRITE_LINE_MEMBER( ria_w ) { m_chanA->ri_w(state); } + DECLARE_WRITE_LINE_MEMBER( rib_w ) { m_chanB->ri_w(state); } + DECLARE_WRITE_LINE_MEMBER( rxca_w ) { m_chanA->rxc_w(state); } + DECLARE_WRITE_LINE_MEMBER( rxcb_w ) { m_chanB->rxc_w(state); } + DECLARE_WRITE_LINE_MEMBER( txca_w ) { m_chanA->txc_w(state); } + DECLARE_WRITE_LINE_MEMBER( txcb_w ) { m_chanB->txc_w(state); } + DECLARE_WRITE_LINE_MEMBER( rxtxcb_w ) { m_chanB->rxc_w(state); m_chanB->txc_w(state); } + DECLARE_WRITE_LINE_MEMBER( synca_w ) { m_chanA->sync_w(state); } + DECLARE_WRITE_LINE_MEMBER( syncb_w ) { m_chanB->sync_w(state); } + +protected: + // device-level overrides + virtual void device_start(); + virtual void device_reset(); + virtual machine_config_constructor device_mconfig_additions() const; + + // device_z80daisy_interface overrides + virtual int z80daisy_irq_state(); + virtual int z80daisy_irq_ack(); + virtual void z80daisy_irq_reti(); + + // internal interrupt management + void check_interrupts(); + void reset_interrupts(); + void trigger_interrupt(int index, int state); + int get_channel_index(z80scc_channel *ch) { return (ch == m_chanA) ? 0 : 1; } + + // Variants in the SCC family + enum + { + TYPE_Z80SCC = 0x001, + TYPE_SCC8030 = 0x002, + TYPE_SCC80C30 = 0x004, + TYPE_SCC80230 = 0x008, + TYPE_SCC8530 = 0x010, + TYPE_SCC85C30 = 0x020, + TYPE_SCC85230 = 0x040, + TYPE_SCC85233 = 0x080, + TYPE_SCC8523L = 0x100 + }; + +#define SET_NMOS ( z80scc_device::TYPE_SCC8030 | z80scc_device::TYPE_SCC8530 ) +#define SET_CMOS ( z80scc_device::TYPE_SCC80C30 | z80scc_device::TYPE_SCC85C30 ) +#define SET_ESCC ( z80scc_device::TYPE_SCC80230 | z80scc_device::TYPE_SCC85230 | z80scc_device::TYPE_SCC8523L ) +#define SET_EMSCC z80scc_device::TYPE_SCC85233 +#define SET_Z80X30 ( z80scc_device::TYPE_SCC8030 | z80scc_device::TYPE_SCC80C30 | z80scc_device::TYPE_SCC80230 ) +#define SET_Z85X3X ( z80scc_device::TYPE_SCC8530 | z80scc_device::TYPE_SCC85C30 | z80scc_device::TYPE_SCC85230 \ + | z80scc_device::TYPE_SCC8523L | z80scc_device::TYPE_SCC85233 ) + + enum + { + CHANNEL_A = 0, + CHANNEL_B + }; + + required_device m_chanA; + required_device m_chanB; + + // internal state + int m_rxca; + int m_txca; + int m_rxcb; + int m_txcb; + + devcb_write_line m_out_txda_cb; + devcb_write_line m_out_dtra_cb; + devcb_write_line m_out_rtsa_cb; + devcb_write_line m_out_wrdya_cb; + devcb_write_line m_out_synca_cb; + + devcb_write_line m_out_txdb_cb; + devcb_write_line m_out_dtrb_cb; + devcb_write_line m_out_rtsb_cb; + devcb_write_line m_out_wrdyb_cb; + devcb_write_line m_out_syncb_cb; + + devcb_write_line m_out_int_cb; + devcb_write_line m_out_rxdrqa_cb; + devcb_write_line m_out_txdrqa_cb; + devcb_write_line m_out_rxdrqb_cb; + devcb_write_line m_out_txdrqb_cb; + + int m_int_state[8]; // interrupt state + + int m_variant; +}; + +class scc8030_device : public z80scc_device +{ +public : + scc8030_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +class scc80C30_device : public z80scc_device +{ +public : + scc80C30_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +class scc80230_device : public z80scc_device +{ +public : + scc80230_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +class scc8530_device : public z80scc_device +{ +public : + scc8530_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +class scc85C30_device : public z80scc_device +{ +public : + scc85C30_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +class scc85230_device : public z80scc_device +{ +public : + scc85230_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +class scc85233_device : public z80scc_device +{ +public : + scc85233_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +class scc8523L_device : public z80scc_device +{ +public : + scc8523L_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + +// device type definition +extern const device_type Z80SCC; +extern const device_type Z80SCC_CHANNEL; +extern const device_type SCC8030; +extern const device_type SCC80C30; +extern const device_type SCC80230; +extern const device_type SCC8530; +extern const device_type SCC85C30; +extern const device_type SCC85230; +extern const device_type SCC85233; +extern const device_type SCC8523L; + +#endif // __Z80SCC_H__