diff options
Diffstat (limited to 'src/devices/machine/mc6846.c')
-rw-r--r-- | src/devices/machine/mc6846.c | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/src/devices/machine/mc6846.c b/src/devices/machine/mc6846.c new file mode 100644 index 00000000000..18b9f723bfb --- /dev/null +++ b/src/devices/machine/mc6846.c @@ -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; +} |