summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/mc6846.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/mc6846.cpp')
-rw-r--r--src/devices/machine/mc6846.cpp568
1 files changed, 568 insertions, 0 deletions
diff --git a/src/devices/machine/mc6846.cpp b/src/devices/machine/mc6846.cpp
new file mode 100644
index 00000000000..18b9f723bfb
--- /dev/null
+++ b/src/devices/machine/mc6846.cpp
@@ -0,0 +1,568 @@
+// license:BSD-3-Clause
+// copyright-holders:Antoine Mine
+/**********************************************************************
+
+ Copyright (C) Antoine Mine' 2006
+
+ Motorola 6846 emulation.
+
+ The MC6846 chip provides ROM (2048 bytes), I/O (8-bit directional data port +
+ 2 control lines) and a programmable timer.
+ It may be interfaced with a M6809 cpu.
+ It is used in some Thomson computers.
+
+ Not yet implemented:
+ - external clock (CTC)
+ - latching of port on CP1
+ - gate input (CTG)
+ - timer comparison modes (frequency and pulse width)
+ - CP2 acknowledge modes
+
+**********************************************************************/
+
+#include "emu.h"
+#include "mc6846.h"
+
+#define VERBOSE 0
+
+
+/******************* utility function and macros ********************/
+
+#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
+
+#define PORT \
+ ((m_pdr & m_ddr) | \
+ ((!m_in_port_cb.isnull() ? m_in_port_cb( 0 ) : 0) & \
+ ~m_ddr))
+
+#define CTO \
+ ((MODE == 0x30 || (m_tcr & 0x80)) ? m_cto : 0)
+
+#define MODE (m_tcr & 0x38)
+
+#define FACTOR ((m_tcr & 4) ? 8 : 1)
+
+
+const device_type MC6846 = &device_creator<mc6846_device>;
+
+mc6846_device::mc6846_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, MC6846, "MC6846 Programmable Timer", tag, owner, clock, "mc6846", __FILE__),
+ m_out_port_cb(*this),
+ m_out_cp1_cb(*this),
+ m_out_cp2_cb(*this),
+ m_in_port_cb(*this),
+ m_out_cto_cb(*this),
+ m_irq_cb(*this)
+{
+}
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void mc6846_device::device_start()
+{
+ m_interval = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mc6846_device::timer_expire), this));
+ m_one_shot = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mc6846_device::timer_one_shot), this));
+
+ m_out_port_cb.resolve(); /* 8-bit output */
+ m_out_cp1_cb.resolve_safe(); /* 1-bit output */
+ m_out_cp2_cb.resolve(); /* 1-bit output */
+
+ /* CPU read from the outside through chip */
+ m_in_port_cb.resolve(); /* 8-bit input */
+
+ /* asynchronous timer output to outside world */
+ m_out_cto_cb.resolve(); /* 1-bit output */
+
+ /* timer interrupt */
+ m_irq_cb.resolve();
+
+ save_item(NAME(m_csr));
+ save_item(NAME(m_pcr));
+ save_item(NAME(m_ddr));
+ save_item(NAME(m_pdr));
+ save_item(NAME(m_tcr));
+ save_item(NAME(m_cp1));
+ save_item(NAME(m_cp2));
+ save_item(NAME(m_cp2_cpu));
+ save_item(NAME(m_cto));
+ save_item(NAME(m_time_MSB));
+ save_item(NAME(m_csr0_to_be_cleared));
+ save_item(NAME(m_csr1_to_be_cleared));
+ save_item(NAME(m_csr2_to_be_cleared));
+ save_item(NAME(m_latch));
+ save_item(NAME(m_preset));
+ save_item(NAME(m_timer_started));
+}
+
+//-------------------------------------------------
+// device_reset - device-specific reset
+//-------------------------------------------------
+
+void mc6846_device::device_reset()
+{
+ m_cto = 0;
+ m_csr = 0;
+ m_pcr = 0x80;
+ m_ddr = 0;
+ m_pdr = 0;
+ m_tcr = 1;
+ m_cp1 = 0;
+ m_cp2 = 0;
+ m_cp2_cpu = 0;
+ m_latch = 0xffff;
+ m_preset = 0xffff;
+ m_time_MSB = 0;
+ m_csr0_to_be_cleared = 0;
+ m_csr1_to_be_cleared = 0;
+ m_csr2_to_be_cleared = 0;
+ m_timer_started = 0;
+ m_old_cif = 0;
+ m_old_cto = 0;
+ m_interval->reset();
+ m_one_shot->reset();
+}
+
+
+inline UINT16 mc6846_device::counter()
+{
+ if ( m_timer_started )
+ {
+ attotime delay = m_interval ->remaining( );
+ return delay.as_ticks(1000000) / FACTOR;
+ }
+ else
+ return m_preset;
+}
+
+
+
+inline void mc6846_device::update_irq()
+{
+ int cif = 0;
+ /* composite interrupt flag */
+ if ( ( (m_csr & 1) && (m_tcr & 0x40) ) ||
+ ( (m_csr & 2) && (m_pcr & 1) ) ||
+ ( (m_csr & 4) && (m_pcr & 8) && ! (m_pcr & 0x20) ) )
+ cif = 1;
+ if ( m_old_cif != cif )
+ {
+ LOG (( "%f: mc6846 interrupt %i (time=%i cp1=%i cp2=%i)\n",
+ machine().time().as_double(), cif,
+ m_csr & 1, (m_csr >> 1 ) & 1, (m_csr >> 2 ) & 1 ));
+ m_old_cif = cif;
+ }
+ if ( cif )
+ {
+ m_csr |= 0x80;
+ if ( !m_irq_cb.isnull() )
+ m_irq_cb( 1 );
+ }
+ else
+ {
+ m_csr &= ~0x80;
+ if ( !m_irq_cb.isnull() )
+ m_irq_cb( 0 );
+ }
+}
+
+
+
+inline void mc6846_device::update_cto()
+{
+ int cto = CTO;
+ if ( cto != m_old_cto )
+ {
+ LOG (( "%f: mc6846 CTO set to %i\n", machine().time().as_double(), cto ));
+ m_old_cto = cto;
+ }
+ if ( !m_out_cto_cb.isnull() )
+ m_out_cto_cb( (offs_t) 0, cto );
+}
+
+
+
+inline void mc6846_device::timer_launch()
+{
+ int delay = FACTOR * (m_preset+1);
+ LOG (( "%f: mc6846 timer launch called, mode=%i, preset=%i (x%i)\n", machine().time().as_double(), MODE, m_preset, FACTOR ));
+
+ if ( ! (m_tcr & 2) )
+ {
+ logerror( "mc6846 external clock CTC not implemented\n" );
+ }
+
+ switch( MODE )
+ {
+ case 0x00:
+ case 0x10: /* continuous */
+ m_cto = 0;
+ break;
+
+ case 0x20: /* single-shot */
+ m_cto = 0;
+ m_one_shot->reset( attotime::from_usec(FACTOR) );
+ break;
+
+ case 0x30: /* cascaded single-shot */
+ break;
+
+ default:
+ logerror( "mc6846 timer mode %i not implemented\n", MODE );
+ m_interval->reset();
+ m_timer_started = 0;
+ return;
+ }
+
+ m_interval->reset( attotime::from_usec(delay) );
+ m_timer_started = 1;
+
+ m_csr &= ~1;
+ update_cto();
+ update_irq();
+}
+
+
+
+/******************* timer callbacks *********************************/
+
+TIMER_CALLBACK_MEMBER( mc6846_device::timer_expire )
+{
+ int delay = FACTOR * (m_latch+1);
+
+ LOG (( "%f: mc6846 timer expire called, mode=%i, latch=%i (x%i)\n", machine().time().as_double(), MODE, m_latch, FACTOR ));
+
+ /* latch => counter */
+ m_preset = m_latch;
+
+ if ( ! (m_tcr & 2) )
+ logerror( "mc6846 external clock CTC not implemented\n" );
+
+ switch ( MODE )
+ {
+ case 0x00:
+ case 0x10: /* continuous */
+ m_cto = 1 ^ m_cto;
+ break;
+
+ case 0x20: /* single-shot */
+ m_cto = 0;
+ break;
+
+ case 0x30: /* cascaded single-shot */
+ m_cto = ( m_tcr & 0x80 ) ? 1 : 0;
+ break;
+
+ default:
+ logerror( "mc6846 timer mode %i not implemented\n", MODE );
+ m_interval->reset( );
+ m_timer_started = 0;
+ return;
+ }
+
+ m_interval->reset( attotime::from_usec(delay) );
+
+ m_csr |= 1;
+ update_cto();
+ update_irq();
+}
+
+
+
+TIMER_CALLBACK_MEMBER( mc6846_device::timer_one_shot )
+{
+ LOG (( "%f: mc6846 timer one shot called\n", machine().time().as_double() ));
+
+ /* 1 micro second after one-shot launch, we put cto to high */
+ m_cto = 1;
+ update_cto();
+}
+
+
+
+/************************** CPU interface ****************************/
+
+
+READ8_MEMBER(mc6846_device::read)
+{
+ switch ( offset )
+ {
+ case 0:
+ case 4:
+ LOG (( "%s %f: mc6846 CSR read $%02X intr=%i (timer=%i, cp1=%i, cp2=%i)\n",
+ machine().describe_context(), space.machine().time().as_double(),
+ m_csr, (m_csr >> 7) & 1,
+ m_csr & 1, (m_csr >> 1) & 1, (m_csr >> 2) & 1 ));
+ m_csr0_to_be_cleared = m_csr & 1;
+ m_csr1_to_be_cleared = m_csr & 2;
+ m_csr2_to_be_cleared = m_csr & 4;
+ return m_csr;
+
+ case 1:
+ LOG (( "%s %f: mc6846 PCR read $%02X\n", machine().describe_context(), space.machine().time().as_double(), m_pcr ));
+ return m_pcr;
+
+ case 2:
+ LOG (( "%s %f: mc6846 DDR read $%02X\n", machine().describe_context(), space.machine().time().as_double(), m_ddr ));
+ return m_ddr;
+
+ case 3:
+ LOG (( "%s %f: mc6846 PORT read $%02X\n", machine().describe_context(), space.machine().time().as_double(), PORT ));
+ if ( ! (m_pcr & 0x80) )
+ {
+ if ( m_csr1_to_be_cleared )
+ m_csr &= ~2;
+ if ( m_csr2_to_be_cleared )
+ m_csr &= ~4;
+ update_irq();
+ m_csr1_to_be_cleared = 0;
+ m_csr2_to_be_cleared = 0;
+ }
+ return PORT;
+
+ case 5:
+ LOG (( "%s %f: mc6846 TCR read $%02X\n",machine().describe_context(), space.machine().time().as_double(), m_tcr ));
+ return m_tcr;
+
+ case 6:
+ LOG (( "%s %f: mc6846 COUNTER hi read $%02X\n", machine().describe_context(), space.machine().time().as_double(), counter() >> 8 ));
+ if ( m_csr0_to_be_cleared )
+ {
+ m_csr &= ~1;
+ update_irq();
+ }
+ m_csr0_to_be_cleared = 0;
+ return counter() >> 8;
+
+ case 7:
+ LOG (( "%s %f: mc6846 COUNTER low read $%02X\n", machine().describe_context(), space.machine().time().as_double(), counter() & 0xff ));
+ if ( m_csr0_to_be_cleared )
+ {
+ m_csr &= ~1;
+ update_irq();
+ }
+ m_csr0_to_be_cleared = 0;
+ return counter() & 0xff;
+
+ default:
+ logerror( "%s mc6846 invalid read offset %i\n", machine().describe_context(), offset );
+ }
+ return 0;
+}
+
+
+
+WRITE8_MEMBER(mc6846_device::write)
+{
+ switch ( offset )
+ {
+ case 0:
+ case 4:
+ /* CSR is read-only */
+ break;
+
+ case 1:
+ {
+ static const char *const cp2[8] =
+ {
+ "in,neg-edge", "in,neg-edge,intr", "in,pos-edge", "in,pos-edge,intr",
+ "out,intr-ack", "out,i/o-ack", "out,0", "out,1"
+ };
+ static const char *const cp1[8] =
+ {
+ "neg-edge", "neg-edge,intr", "pos-edge", "pos-edge,intr",
+ "latched,neg-edge", "latched,neg-edge,intr",
+ "latcged,pos-edge", "latcged,pos-edge,intr"
+ };
+ LOG (( "%s %f: mc6846 PCR write $%02X reset=%i cp2=%s cp1=%s\n",
+ machine().describe_context(), space.machine().time().as_double(), data,
+ (data >> 7) & 1, cp2[ (data >> 3) & 7 ], cp1[ data & 7 ] ));
+
+ }
+ m_pcr = data;
+ if ( data & 0x80 )
+ { /* data reset */
+ m_pdr = 0;
+ m_ddr = 0;
+ m_csr &= ~6;
+ update_irq();
+ }
+ if ( data & 4 )
+ logerror( "%s mc6846 CP1 latching not implemented\n", machine().describe_context() );
+ if (data & 0x20)
+ {
+ if (data & 0x10)
+ {
+ m_cp2_cpu = (data >> 3) & 1;
+ if ( !m_out_cp2_cb.isnull() )
+ m_out_cp2_cb( (offs_t) 0, m_cp2_cpu );
+ }
+ else
+ logerror( "%s mc6846 acknowledge not implemented\n", machine().describe_context() );
+ }
+ break;
+
+ case 2:
+ LOG (( "%s %f: mc6846 DDR write $%02X\n", machine().describe_context(), space.machine().time().as_double(), data ));
+ if ( ! (m_pcr & 0x80) )
+ {
+ m_ddr = data;
+ if ( !m_out_port_cb.isnull() )
+ m_out_port_cb( (offs_t) 0, m_pdr & m_ddr );
+ }
+ break;
+
+ case 3:
+ LOG (( "%s %f: mc6846 PORT write $%02X (mask=$%02X)\n", machine().describe_context(), space.machine().time().as_double(), data,m_ddr ));
+ if ( ! (m_pcr & 0x80) )
+ {
+ m_pdr = data;
+ if ( !m_out_port_cb.isnull() )
+ m_out_port_cb( (offs_t) 0, m_pdr & m_ddr );
+ if ( m_csr1_to_be_cleared && (m_csr & 2) )
+ {
+ m_csr &= ~2;
+ LOG (( "%s %f: mc6846 CP1 intr reset\n", machine().describe_context(), space.machine().time().as_double() ));
+ }
+ if ( m_csr2_to_be_cleared && (m_csr & 4) )
+ {
+ m_csr &= ~4;
+ LOG (( "%s %f: mc6846 CP2 intr reset\n", machine().describe_context(), space.machine().time().as_double() ));
+ }
+ m_csr1_to_be_cleared = 0;
+ m_csr2_to_be_cleared = 0;
+ update_irq();
+ }
+ break;
+
+ case 5:
+ {
+ static const char *const mode[8] =
+ {
+ "continuous", "cascaded", "continuous", "one-shot",
+ "freq-cmp", "freq-cmp", "pulse-cmp", "pulse-cmp"
+ };
+ LOG (( "%s %f: mc6846 TCR write $%02X reset=%i clock=%s scale=%i mode=%s out=%s\n",
+ machine().describe_context(), space.machine().time().as_double(), data,
+ (data >> 7) & 1, (data & 0x40) ? "extern" : "sys",
+ (data & 0x40) ? 1 : 8, mode[ (data >> 1) & 7 ],
+ (data & 1) ? "enabled" : "0" ));
+
+ m_tcr = data;
+ if ( m_tcr & 1 )
+ {
+ /* timer preset = initialization without launch */
+ m_preset = m_latch;
+ m_csr &= ~1;
+ if ( MODE != 0x30 )
+ m_cto = 0;
+ update_cto();
+ m_interval->reset();
+ m_one_shot->reset();
+ m_timer_started = 0;
+ }
+ else
+ {
+ /* timer launch */
+ if ( ! m_timer_started )
+ timer_launch();
+ }
+ update_irq();
+ }
+ break;
+
+ case 6:
+ m_time_MSB = data;
+ break;
+
+ case 7:
+ m_latch = ( ((UINT16) m_time_MSB) << 8 ) + data;
+ LOG (( "%s %f: mc6846 COUNT write %i\n", machine().describe_context(), space.machine().time().as_double(), m_latch ));
+ if (!(m_tcr & 0x38))
+ {
+ /* timer initialization */
+ m_preset = m_latch;
+ m_csr &= ~1;
+ update_irq();
+ m_cto = 0;
+ update_cto();
+ /* launch only if started */
+ if (!(m_tcr & 1))
+ timer_launch();
+ }
+ break;
+
+ default:
+ logerror( "%s mc6846 invalid write offset %i\n", machine().describe_context(), offset );
+ }
+}
+
+
+
+/******************** outside world interface ************************/
+
+
+
+void mc6846_device::set_input_cp1(int data)
+{
+ data = (data != 0 );
+ if ( data == m_cp1 )
+ return;
+ m_cp1 = data;
+ LOG (( "%f: mc6846 input CP1 set to %i\n", machine().time().as_double(), data ));
+ if (( data && (m_pcr & 2)) || (!data && !(m_pcr & 2)))
+ {
+ m_csr |= 2;
+ update_irq();
+ }
+}
+
+void mc6846_device::set_input_cp2(int data)
+{
+ data = (data != 0 );
+ if ( data == m_cp2 )
+ return;
+ m_cp2 = data;
+ LOG (( "%f: mc6846 input CP2 set to %i\n", machine().time().as_double(), data ));
+ if (m_pcr & 0x20)
+ {
+ if (( data && (m_pcr & 0x10)) || (!data && !(m_pcr & 0x10)))
+ {
+ m_csr |= 4;
+ update_irq();
+ }
+ }
+}
+
+
+
+/************************ accessors **********************************/
+
+
+
+UINT8 mc6846_device::get_output_port()
+{
+ return PORT;
+}
+
+
+
+UINT8 mc6846_device::get_output_cto()
+{
+ return CTO;
+}
+
+
+
+UINT8 mc6846_device::get_output_cp2()
+{
+ return m_cp2_cpu;
+}
+
+
+
+UINT16 mc6846_device::get_preset()
+{
+ return m_preset;
+}