summaryrefslogtreecommitdiffstatshomepage
path: root/src/mess/machine/zx8302.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mess/machine/zx8302.c')
-rw-r--r--src/mess/machine/zx8302.c685
1 files changed, 685 insertions, 0 deletions
diff --git a/src/mess/machine/zx8302.c b/src/mess/machine/zx8302.c
new file mode 100644
index 00000000000..bebd5110e4d
--- /dev/null
+++ b/src/mess/machine/zx8302.c
@@ -0,0 +1,685 @@
+/**********************************************************************
+
+ Sinclair ZX8302 emulation
+
+ Copyright MESS Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+**********************************************************************/
+
+/*
+
+ TODO:
+
+ - set time
+ - read from microdrive
+ - write to microdrive
+ - DTR/CTS handling
+ - network
+
+*/
+
+#include "emu.h"
+#include <time.h>
+#include "devices/microdrv.h"
+#include "machine/devhelpr.h"
+#include "zx8302.h"
+
+
+
+//**************************************************************************
+// MACROS / CONSTANTS
+//**************************************************************************
+
+#define LOG 0
+
+
+// IPC serial state
+enum
+{
+ IPC_START,
+ IPC_DATA,
+ IPC_STOP
+};
+
+
+// baud rate
+enum
+{
+ BAUD_19200 = 0x00,
+ BAUD_9600 = 0x01,
+ BAUD_4800 = 0x02,
+ BAUD_2400 = 0x03,
+ BAUD_1200 = 0x04,
+ BAUD_600 = 0x05,
+ BAUD_300 = 0x06,
+ BAUD_75 = 0x07,
+ BAUD_MASK = 0x07,
+};
+
+// transmit mode
+enum
+{
+ MODE_SER1 = 0x00,
+ MODE_SER2 = 0x08,
+ MODE_MDV = 0x10,
+ MODE_NET = 0x18,
+ MODE_MASK = 0x18,
+};
+
+// interrupts
+enum
+{
+ INT_GAP = 0x01,
+ INT_INTERFACE = 0x02,
+ INT_TRANSMIT = 0x04,
+ INT_FRAME = 0x08,
+ INT_EXTERNAL = 0x10,
+};
+
+// status register
+enum
+{
+ STATUS_NETWORK_PORT = 0x01,
+ STATUS_TX_BUFFER_FULL = 0x02,
+ STATUS_RX_BUFFER_FULL = 0x04,
+ STATUS_MICRODRIVE_GAP = 0x08,
+};
+
+// transmit bits
+enum
+{
+ TXD_START = 0,
+ TXD_STOP = 9,
+ TXD_STOP2 = 10,
+};
+
+
+// Monday 1st January 1979 00:00:00 UTC
+enum
+{
+ RTC_BASE_ADJUST = 283996800,
+};
+
+
+//**************************************************************************
+// DEVICE DEFINITIONS
+//**************************************************************************
+
+const device_type ZX8302 = &device_creator<zx8302_device>;
+
+//-------------------------------------------------
+// device_config_complete - perform any
+// operations now that the configuration is
+// complete
+//-------------------------------------------------
+
+void zx8302_device::device_config_complete()
+{
+ // inherit a copy of the static data
+ const zx8302_interface *intf = reinterpret_cast<const zx8302_interface *>(static_config());
+ if (intf != NULL)
+ *static_cast<zx8302_interface *>(this) = *intf;
+
+ // or initialize to defaults if none provided
+ else
+ {
+ memset(&out_ipl1l_cb, 0, sizeof(out_ipl1l_cb));
+ memset(&out_baudx4_cb, 0, sizeof(out_baudx4_cb));
+ memset(&out_comdata_cb, 0, sizeof(out_comdata_cb));
+ memset(&out_txd1_cb, 0, sizeof(out_txd1_cb));
+ memset(&out_txd2_cb, 0, sizeof(out_txd2_cb));
+ memset(&in_dtr1_cb, 0, sizeof(in_dtr1_cb));
+ memset(&in_cts2_cb, 0, sizeof(in_cts2_cb));
+ memset(&out_netout_cb, 0, sizeof(out_netout_cb));
+ memset(&in_netin_cb, 0, sizeof(in_netin_cb));
+ memset(&out_mdselck_cb, 0, sizeof(out_mdselck_cb));
+ memset(&out_mdseld_cb, 0, sizeof(out_mdseld_cb));
+ memset(&out_mdrdw_cb, 0, sizeof(out_mdrdw_cb));
+ memset(&out_erase_cb, 0, sizeof(out_erase_cb));
+ memset(&out_raw1_cb, 0, sizeof(out_raw1_cb));
+ memset(&in_raw1_cb, 0, sizeof(in_raw1_cb));
+ memset(&out_raw2_cb, 0, sizeof(out_raw2_cb));
+ memset(&in_raw2_cb, 0, sizeof(in_raw2_cb));
+ }
+}
+
+
+
+//**************************************************************************
+// INLINE HELPERS
+//**************************************************************************
+
+//-------------------------------------------------
+// trigger_interrupt -
+//-------------------------------------------------
+
+inline void zx8302_device::trigger_interrupt(UINT8 line)
+{
+ m_irq |= line;
+
+ m_out_ipl1l_func(ASSERT_LINE);
+}
+
+
+//-------------------------------------------------
+// transmit_ipc_data - transmit IPC data
+//-------------------------------------------------
+
+inline void zx8302_device::transmit_ipc_data()
+{
+ /*
+
+ IPC <-> ZX8302 serial link protocol
+ ***********************************
+
+ Send bit to IPC
+ ---------------
+
+ 1. ZX start bit (COMDATA = 0)
+ 2. IPC clock (COMCTL = 0, COMTL = 1)
+ 3. ZX data bit (COMDATA = 0/1)
+ 4. IPC clock (COMCTL = 0, COMTL = 1)
+ 5. ZX stop bit (COMDATA = 1)
+
+ Receive bit from IPC
+ --------------------
+
+ 1. ZX start bit (COMDATA = 0)
+ 2. IPC clock (COMCTL = 0, COMTL = 1)
+ 3. IPC data bit (COMDATA = 0/1)
+ 4. IPC clock (COMCTL = 0, COMTL = 1)
+ 5. IPC stop bit (COMDATA = 1)
+
+ */
+
+ switch (m_ipc_state)
+ {
+ case IPC_START:
+ if (LOG) logerror("ZX8302 '%s' COMDATA Start Bit\n", tag());
+
+ m_out_comdata_func(0);
+ m_ipc_busy = 1;
+ m_ipc_state = IPC_DATA;
+ break;
+
+ case IPC_DATA:
+ if (LOG) logerror("ZX8302 '%s' COMDATA Data Bit: %x\n", tag(), BIT(m_idr, 1));
+
+ m_comdata = BIT(m_idr, 1);
+ m_out_comdata_func(m_comdata);
+ m_ipc_state = IPC_STOP;
+ break;
+
+ case IPC_STOP:
+ if (!m_ipc_rx)
+ {
+ if (LOG) logerror("ZX8302 '%s' COMDATA Stop Bit\n", tag());
+
+ m_out_comdata_func(1);
+ m_ipc_busy = 0;
+ m_ipc_state = IPC_START;
+ }
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// transmit_bit - transmit serial bit
+//-------------------------------------------------
+
+inline void zx8302_device::transmit_bit(int state)
+{
+ switch (m_tcr & MODE_MASK)
+ {
+ case MODE_SER1:
+ m_out_txd1_func(state);
+ break;
+
+ case MODE_SER2:
+ m_out_txd2_func(state);
+ break;
+
+ case MODE_MDV:
+ // TODO
+ break;
+
+ case MODE_NET:
+ m_out_netout_func(state);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// transmit_data - transmit serial data
+//-------------------------------------------------
+
+inline void zx8302_device::transmit_serial_data()
+{
+ switch (m_tx_bits)
+ {
+ case TXD_START:
+ if (!(m_irq & INT_TRANSMIT))
+ {
+ transmit_bit(0);
+ m_tx_bits++;
+ }
+ break;
+
+ default:
+ transmit_bit(BIT(m_tdr, 0));
+ m_tdr >>= 1;
+ m_tx_bits++;
+ break;
+
+ case TXD_STOP:
+ transmit_bit(1);
+ m_tx_bits++;
+ break;
+
+ case TXD_STOP2:
+ transmit_bit(1);
+ m_tx_bits = TXD_START;
+ m_status &= ~STATUS_TX_BUFFER_FULL;
+ trigger_interrupt(INT_TRANSMIT);
+ break;
+ }
+}
+
+
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// zx8302_device - constructor
+//-------------------------------------------------
+zx8302_device::zx8302_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, ZX8302, "Sinclair ZX8302", tag, owner, clock),
+ m_idr(1),
+ m_irq(0),
+ m_ctr(time(NULL) + RTC_BASE_ADJUST),
+ m_status(0),
+ m_comdata(1),
+ m_comctl(1),
+ m_ipc_state(0),
+ m_ipc_rx(0),
+ m_ipc_busy(0),
+ m_track(0)
+{
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void zx8302_device::device_start()
+{
+ // resolve callbacks
+ m_out_ipl1l_func.resolve(out_ipl1l_cb, *this);
+ m_out_baudx4_func.resolve(out_baudx4_cb, *this);
+ m_out_comdata_func.resolve(out_comdata_cb, *this);
+ m_out_txd1_func.resolve(out_txd1_cb, *this);
+ m_out_txd2_func.resolve(out_txd2_cb, *this);
+ m_in_dtr1_func.resolve(in_dtr1_cb, *this);
+ m_in_cts2_func.resolve(in_cts2_cb, *this);
+ m_out_netout_func.resolve(out_netout_cb, *this);
+ m_in_netin_func.resolve(in_netin_cb, *this);
+ m_out_mdselck_func.resolve(out_mdselck_cb, *this);
+ m_out_mdseld_func.resolve(out_mdseld_cb, *this);
+ m_out_mdrdw_func.resolve(out_mdrdw_cb, *this);
+ m_out_erase_func.resolve(out_erase_cb, *this);
+ m_out_raw1_func.resolve(out_raw1_cb, *this);
+ m_in_raw1_func.resolve(in_raw1_cb, *this);
+ m_out_raw2_func.resolve(out_raw2_cb, *this);
+ m_in_raw2_func.resolve(in_raw2_cb, *this);
+
+ // allocate timers
+ m_txd_timer = timer_alloc(TIMER_TXD);
+ m_baudx4_timer = timer_alloc(TIMER_BAUDX4);
+ m_rtc_timer = timer_alloc(TIMER_RTC);
+ m_gap_timer = timer_alloc(TIMER_GAP);
+ m_ipc_timer = timer_alloc(TIMER_IPC);
+
+ m_rtc_timer->adjust(attotime::zero, 0, attotime::from_hz(rtc_clock / 32768));
+ m_gap_timer->adjust(attotime::zero, 0, attotime::from_msec(31));
+
+ // register for state saving
+ save_item(NAME(m_idr));
+ save_item(NAME(m_tcr));
+ save_item(NAME(m_tdr));
+ save_item(NAME(m_irq));
+ save_item(NAME(m_ctr));
+ save_item(NAME(m_status));
+ save_item(NAME(m_comdata));
+ save_item(NAME(m_comctl));
+ save_item(NAME(m_ipc_state));
+ save_item(NAME(m_ipc_rx));
+ save_item(NAME(m_ipc_busy));
+ save_item(NAME(m_baudx4));
+ save_item(NAME(m_tx_bits));
+ save_item(NAME(m_mdv_data));
+ save_item(NAME(m_track));
+}
+
+
+//-------------------------------------------------
+// device_timer - handler timer events
+//-------------------------------------------------
+
+void zx8302_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ switch (id)
+ {
+ case TIMER_TXD:
+ transmit_serial_data();
+ break;
+
+ case TIMER_BAUDX4:
+ m_baudx4 = !m_baudx4;
+ m_out_baudx4_func(m_baudx4);
+ break;
+
+ case TIMER_RTC:
+ m_ctr++;
+ break;
+
+ case TIMER_GAP:
+ trigger_interrupt(INT_GAP);
+ break;
+
+ case TIMER_IPC:
+ m_idr = param;
+ m_ipc_state = IPC_START;
+ m_ipc_rx = 0;
+
+ transmit_ipc_data();
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// rtc_r - real time clock read
+//-------------------------------------------------
+
+READ8_MEMBER( zx8302_device::rtc_r )
+{
+ UINT8 data = 0;
+
+ switch (offset)
+ {
+ case 0:
+ data = (m_ctr >> 24) & 0xff;
+ break;
+ case 1:
+ data = (m_ctr >> 16) & 0xff;
+ break;
+ case 2:
+ data = (m_ctr >> 8) & 0xff;
+ break;
+ case 3:
+ data = m_ctr & 0xff;
+ break;
+ }
+
+ return data;
+}
+
+
+//-------------------------------------------------
+// rtc_w - real time clock write
+//-------------------------------------------------
+
+WRITE8_MEMBER( zx8302_device::rtc_w )
+{
+ if (LOG) logerror("ZX8302 '%s' Set Real Time Clock: %02x\n", tag(), data);
+}
+
+
+//-------------------------------------------------
+// control_w - serial transmit clock
+//-------------------------------------------------
+
+WRITE8_MEMBER( zx8302_device::control_w )
+{
+ if (LOG) logerror("ZX8302 '%s' Transmit Control: %02x\n", tag(), data);
+
+ int baud = (19200 >> (data & BAUD_MASK));
+ int baudx4 = baud * 4;
+
+ m_tcr = data;
+
+ m_txd_timer->adjust(attotime::zero, 0, attotime::from_hz(baud));
+ m_baudx4_timer->adjust(attotime::zero, 0, attotime::from_hz(baudx4));
+}
+
+
+//-------------------------------------------------
+// mdv_track_r - microdrive track data
+//-------------------------------------------------
+
+READ8_MEMBER( zx8302_device::mdv_track_r )
+{
+ if (LOG) logerror("ZX8302 '%s' Microdrive Track %u: %02x\n", tag(), m_track, m_mdv_data[m_track]);
+
+ UINT8 data = m_mdv_data[m_track];
+
+ m_track = !m_track;
+
+ return data;
+}
+
+
+//-------------------------------------------------
+// status_r - status register
+//-------------------------------------------------
+
+READ8_MEMBER( zx8302_device::status_r )
+{
+ /*
+
+ bit description
+
+ 0 Network port
+ 1 Transmit buffer full
+ 2 Receive buffer full
+ 3 Microdrive GAP
+ 4 SER1 DTR
+ 5 SER2 CTS
+ 6 IPC busy
+ 7 COMDATA
+
+ */
+
+ UINT8 data = 0;
+
+ // TODO network port
+
+ // serial status
+ data |= m_status;
+
+ // TODO microdrive GAP
+
+ // data terminal ready
+ data |= m_in_dtr1_func() << 4;
+
+ // clear to send
+ data |= m_in_cts2_func() << 5;
+
+ // IPC busy
+ data |= m_ipc_busy << 6;
+
+ // COMDATA
+ data |= m_comdata << 7;
+
+ if (LOG) logerror("ZX8302 '%s' Status: %02x\n", tag(), data);
+
+ return data;
+}
+
+
+//-------------------------------------------------
+// ipc_command_w - IPC command
+//-------------------------------------------------
+
+WRITE8_MEMBER( zx8302_device::ipc_command_w )
+{
+ if (LOG) logerror("ZX8302 '%s' IPC Command: %02x\n", tag(), data);
+
+ if (data != 0x01)
+ {
+ m_ipc_timer->adjust(attotime::from_nsec(480), data);
+ }
+}
+
+
+//-------------------------------------------------
+// mdv_control_w - microdrive control
+//-------------------------------------------------
+
+WRITE8_MEMBER( zx8302_device::mdv_control_w )
+{
+ /*
+
+ bit description
+
+ 0 MDSELDH
+ 1 MDSELCKH
+ 2 MDRDWL
+ 3 ERASE
+ 4
+ 5
+ 6
+ 7
+
+ */
+
+ if (LOG) logerror("ZX8302 '%s' Microdrive Control: %02x\n", tag(), data);
+
+ m_out_mdseld_func(BIT(data, 0));
+ m_out_mdselck_func(BIT(data, 1));
+ m_out_mdrdw_func(BIT(data, 2));
+ m_out_erase_func(BIT(data, 3));
+
+ if (BIT(data, 1))
+ {
+ m_status &= ~STATUS_RX_BUFFER_FULL;
+ }
+}
+
+
+//-------------------------------------------------
+// irq_status_r - interrupt status
+//-------------------------------------------------
+
+READ8_MEMBER( zx8302_device::irq_status_r )
+{
+ if (LOG) logerror("ZX8302 '%s' Interrupt Status: %02x\n", tag(), m_irq);
+
+ return m_irq;
+}
+
+
+//-------------------------------------------------
+// irq_acknowledge_w - interrupt acknowledge
+//-------------------------------------------------
+
+WRITE8_MEMBER( zx8302_device::irq_acknowledge_w )
+{
+ if (LOG) logerror("ZX8302 '%s' Interrupt Acknowledge: %02x\n", tag(), data);
+
+ m_irq &= ~data;
+
+ if (!m_irq)
+ {
+ m_out_ipl1l_func(CLEAR_LINE);
+ }
+}
+
+
+//-------------------------------------------------
+// data_w - transmit buffer
+//-------------------------------------------------
+
+WRITE8_MEMBER( zx8302_device::data_w )
+{
+ if (LOG) logerror("ZX8302 '%s' Data Register: %02x\n", tag(), data);
+
+ m_tdr = data;
+ m_status |= STATUS_TX_BUFFER_FULL;
+}
+
+
+//-------------------------------------------------
+// vsync_w - vertical sync
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER( zx8302_device::vsync_w )
+{
+ if (state)
+ {
+ if (LOG) logerror("ZX8302 '%s' Frame Interrupt\n", tag());
+
+ trigger_interrupt(INT_FRAME);
+ }
+}
+
+
+//-------------------------------------------------
+// comctl_w - IPC COMCTL
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER( zx8302_device::comctl_w )
+{
+ if (LOG) logerror("ZX8302 '%s' COMCTL: %x\n", tag(), state);
+
+ if (state)
+ {
+ transmit_ipc_data();
+ }
+}
+
+
+//-------------------------------------------------
+// comdata_w - IPC COMDATA
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER( zx8302_device::comdata_w )
+{
+ if (LOG) logerror("ZX8302 '%s' COMDATA: %x\n", tag(), state);
+
+ if (m_ipc_state == IPC_DATA || m_ipc_state == IPC_STOP)
+ {
+ if (m_ipc_rx)
+ {
+ m_ipc_rx = 0;
+ m_ipc_busy = 0;
+ m_ipc_state = IPC_START;
+ }
+ else
+ {
+ m_ipc_rx = 1;
+ m_comdata = state;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// extint_w - external interrupt
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER( zx8302_device::extint_w )
+{
+ if (LOG) logerror("ZX8302 '%s' EXTINT: %x\n", tag(), state);
+
+ if (state == ASSERT_LINE)
+ {
+ trigger_interrupt(INT_EXTERNAL);
+ }
+}