diff options
Diffstat (limited to 'src/mess/machine/mc6846.c')
-rw-r--r-- | src/mess/machine/mc6846.c | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/src/mess/machine/mc6846.c b/src/mess/machine/mc6846.c new file mode 100644 index 00000000000..16f5fd12b0e --- /dev/null +++ b/src/mess/machine/mc6846.c @@ -0,0 +1,635 @@ +/********************************************************************** + + 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 + + + +/******************* internal chip data structure ******************/ + + +typedef struct +{ + + const mc6846_interface* iface; + + /* registers */ + UINT8 csr; /* 0,4: combination status register */ + UINT8 pcr; /* 1: peripheral control register */ + UINT8 ddr; /* 2: data direction register */ + UINT8 pdr; /* 3: peripheral data register (last cpu write) */ + UINT8 tcr; /* 5: timer control register */ + + /* lines */ + UINT8 cp1; /* 1-bit input */ + UINT8 cp2; /* 1-bit input/output: last external write */ + UINT8 cp2_cpu; /* last cpu write */ + UINT8 cto; /* 1-bit timer output (unmasked) */ + + /* internal state */ + UINT8 time_MSB; /* MSB buffer register */ + UINT8 csr0_to_be_cleared; + UINT8 csr1_to_be_cleared; + UINT8 csr2_to_be_cleared; + UINT16 latch; /* timer latch */ + UINT16 preset; /* preset value */ + UINT8 timer_started; + + /* timers */ + emu_timer *interval; /* interval programmable timer */ + emu_timer *one_shot; /* 1-us x factor one-shot timer */ + + int old_cif; + int old_cto; +} mc6846_t; + + + +/******************* utility function and macros ********************/ + +#define LOG(x) do { if (VERBOSE) logerror x; } while (0) + +#define PORT \ + ((mc6846->pdr & mc6846->ddr) | \ + ((mc6846->iface->in_port_func ? mc6846->iface->in_port_func( device, 0 ) : 0) & \ + ~mc6846->ddr)) + +#define CTO \ + ((MODE == 0x30 || (mc6846->tcr & 0x80)) ? mc6846->cto : 0) + +#define MODE (mc6846->tcr & 0x38) + +#define FACTOR ((mc6846->tcr & 4) ? 8 : 1) + + + +INLINE mc6846_t* get_safe_token( device_t *device ) +{ + assert( device != NULL ); + assert( device->type() == MC6846 ); + return (mc6846_t*) downcast<legacy_device_base *>(device)->token(); +} + + +INLINE UINT16 mc6846_counter( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + if ( mc6846->timer_started ) + { + attotime delay = mc6846->interval ->remaining( ); + return delay.as_ticks(1000000) / FACTOR; + } + else + return mc6846->preset; +} + + + +INLINE void mc6846_update_irq( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + int cif = 0; + /* composite interrupt flag */ + if ( ( (mc6846->csr & 1) && (mc6846->tcr & 0x40) ) || + ( (mc6846->csr & 2) && (mc6846->pcr & 1) ) || + ( (mc6846->csr & 4) && (mc6846->pcr & 8) && ! (mc6846->pcr & 0x20) ) ) + cif = 1; + if ( mc6846->old_cif != cif ) + { + LOG (( "%f: mc6846 interrupt %i (time=%i cp1=%i cp2=%i)\n", + device->machine().time().as_double(), cif, + mc6846->csr & 1, (mc6846->csr >> 1 ) & 1, (mc6846->csr >> 2 ) & 1 )); + mc6846->old_cif = cif; + } + if ( cif ) + { + mc6846->csr |= 0x80; + if ( mc6846->iface->irq_func ) + mc6846->iface->irq_func( device, 1 ); + } + else + { + mc6846->csr &= ~0x80; + if ( mc6846->iface->irq_func ) + mc6846->iface->irq_func( device, 0 ); + } +} + + + +INLINE void mc6846_update_cto ( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + int cto = CTO; + if ( cto != mc6846->old_cto ) + { + LOG (( "%f: mc6846 CTO set to %i\n", device->machine().time().as_double(), cto )); + mc6846->old_cto = cto; + } + if ( mc6846->iface->out_cto_func ) + mc6846->iface->out_cto_func( device, 0, cto ); +} + + + +INLINE void mc6846_timer_launch ( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + int delay = FACTOR * (mc6846->preset+1); + LOG (( "%f: mc6846 timer launch called, mode=%i, preset=%i (x%i)\n", device->machine().time().as_double(), MODE, mc6846->preset, FACTOR )); + + if ( ! (mc6846->tcr & 2) ) + { + logerror( "mc6846 external clock CTC not implemented\n" ); + } + + switch( MODE ) + { + + case 0x00: + case 0x10: /* continuous */ + mc6846->cto = 0; + break; + + case 0x20: /* single-shot */ + mc6846->cto = 0; + mc6846->one_shot->reset( attotime::from_usec(FACTOR) ); + break; + + case 0x30: /* cascaded single-shot */ + break; + + default: + logerror( "mc6846 timer mode %i not implemented\n", MODE ); + mc6846->interval->reset( ); + mc6846->timer_started = 0; + return; + } + + mc6846->interval->reset( attotime::from_usec(delay) ); + mc6846->timer_started = 1; + + mc6846->csr &= ~1; + mc6846_update_cto( device ); + mc6846_update_irq( device ); +} + + + +/******************* timer callbacks *********************************/ + +static TIMER_CALLBACK( mc6846_timer_expire ) +{ + device_t* device = (device_t*) ptr; + mc6846_t* mc6846 = get_safe_token( device ); + int delay = FACTOR * (mc6846->latch+1); + + LOG (( "%f: mc6846 timer expire called, mode=%i, latch=%i (x%i)\n", device->machine().time().as_double(), MODE, mc6846->latch, FACTOR )); + + /* latch => counter */ + mc6846->preset = mc6846->latch; + + if ( ! (mc6846->tcr & 2) ) + logerror( "mc6846 external clock CTC not implemented\n" ); + + switch ( MODE ) + { + case 0x00: + case 0x10: /* continuous */ + mc6846->cto = 1 ^ mc6846->cto; + break; + + case 0x20: /* single-shot */ + mc6846->cto = 0; + break; + + case 0x30: /* cascaded single-shot */ + mc6846->cto = ( mc6846->tcr & 0x80 ) ? 1 : 0; + break; + + default: + logerror( "mc6846 timer mode %i not implemented\n", MODE ); + mc6846->interval->reset( ); + mc6846->timer_started = 0; + return; + } + + mc6846->interval->reset( attotime::from_usec(delay) ); + + mc6846->csr |= 1; + mc6846_update_cto( device ); + mc6846_update_irq( device ); +} + + + +static TIMER_CALLBACK( mc6846_timer_one_shot ) +{ + device_t* device = (device_t*) ptr; + mc6846_t* mc6846 = get_safe_token( device ); + LOG (( "%f: mc6846 timer one shot called\n", device->machine().time().as_double() )); + + /* 1 micro second after one-shot launch, we put cto to high */ + mc6846->cto = 1; + mc6846_update_cto( device ); +} + + + +/************************** CPU interface ****************************/ + + +READ8_DEVICE_HANDLER ( mc6846_r ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + switch ( offset ) + { + case 0: + case 4: + LOG (( "$%04x %f: mc6846 CSR read $%02X intr=%i (timer=%i, cp1=%i, cp2=%i)\n", + cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), + mc6846->csr, (mc6846->csr >> 7) & 1, + mc6846->csr & 1, (mc6846->csr >> 1) & 1, (mc6846->csr >> 2) & 1 )); + mc6846->csr0_to_be_cleared = mc6846->csr & 1; + mc6846->csr1_to_be_cleared = mc6846->csr & 2; + mc6846->csr2_to_be_cleared = mc6846->csr & 4; + return mc6846->csr; + + case 1: + LOG (( "$%04x %f: mc6846 PCR read $%02X\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), mc6846->pcr )); + return mc6846->pcr; + + case 2: + LOG (( "$%04x %f: mc6846 DDR read $%02X\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), mc6846->ddr )); + return mc6846->ddr; + + case 3: + LOG (( "$%04x %f: mc6846 PORT read $%02X\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), PORT )); + if ( ! (mc6846->pcr & 0x80) ) + { + if ( mc6846->csr1_to_be_cleared ) + mc6846->csr &= ~2; + if ( mc6846->csr2_to_be_cleared ) + mc6846->csr &= ~4; + mc6846_update_irq( device ); + mc6846->csr1_to_be_cleared = 0; + mc6846->csr2_to_be_cleared = 0; + } + return PORT; + + case 5: + LOG (( "$%04x %f: mc6846 TCR read $%02X\n",cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), mc6846->tcr )); + return mc6846->tcr; + + case 6: + LOG (( "$%04x %f: mc6846 COUNTER hi read $%02X\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), mc6846_counter( device ) >> 8 )); + if ( mc6846->csr0_to_be_cleared ) + { + mc6846->csr &= ~1; + mc6846_update_irq( device ); + } + mc6846->csr0_to_be_cleared = 0; + return mc6846_counter( device ) >> 8; + + case 7: + LOG (( "$%04x %f: mc6846 COUNTER low read $%02X\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), mc6846_counter( device ) & 0xff )); + if ( mc6846->csr0_to_be_cleared ) + { + mc6846->csr &= ~1; + mc6846_update_irq( device ); + } + mc6846->csr0_to_be_cleared = 0; + return mc6846_counter( device ) & 0xff; + + default: + logerror( "$%04x mc6846 invalid read offset %i\n", cpu_get_previouspc( device->machine().firstcpu ), offset ); + } + return 0; +} + + + +WRITE8_DEVICE_HANDLER ( mc6846_w ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + 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 (( "$%04x %f: mc6846 PCR write $%02X reset=%i cp2=%s cp1=%s\n", + cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), data, + (data >> 7) & 1, cp2[ (data >> 3) & 7 ], cp1[ data & 7 ] )); + + } + mc6846->pcr = data; + if ( data & 0x80 ) + { /* data reset */ + mc6846->pdr = 0; + mc6846->ddr = 0; + mc6846->csr &= ~6; + mc6846_update_irq( device ); + } + if ( data & 4 ) + logerror( "$%04x mc6846 CP1 latching not implemented\n", cpu_get_previouspc( device->machine().firstcpu ) ); + if (data & 0x20) + { + if (data & 0x10) + { + mc6846->cp2_cpu = (data >> 3) & 1; + if ( mc6846->iface->out_cp2_func ) + mc6846->iface->out_cp2_func( device, 0, mc6846->cp2_cpu ); + } + else + logerror( "$%04x mc6846 acknowledge not implemented\n", cpu_get_previouspc( device->machine().firstcpu ) ); + } + break; + + case 2: + LOG (( "$%04x %f: mc6846 DDR write $%02X\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), data )); + if ( ! (mc6846->pcr & 0x80) ) + { + mc6846->ddr = data; + if ( mc6846->iface->out_port_func ) + mc6846->iface->out_port_func( device, 0, mc6846->pdr & mc6846->ddr ); + } + break; + + case 3: + LOG (( "$%04x %f: mc6846 PORT write $%02X (mask=$%02X)\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), data,mc6846->ddr )); + if ( ! (mc6846->pcr & 0x80) ) + { + mc6846->pdr = data; + if ( mc6846->iface->out_port_func ) + mc6846->iface->out_port_func( device, 0, mc6846->pdr & mc6846->ddr ); + if ( mc6846->csr1_to_be_cleared && (mc6846->csr & 2) ) + { + mc6846->csr &= ~2; + LOG (( "$%04x %f: mc6846 CP1 intr reset\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double() )); + } + if ( mc6846->csr2_to_be_cleared && (mc6846->csr & 4) ) + { + mc6846->csr &= ~4; + LOG (( "$%04x %f: mc6846 CP2 intr reset\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double() )); + } + mc6846->csr1_to_be_cleared = 0; + mc6846->csr2_to_be_cleared = 0; + mc6846_update_irq( device ); + } + break; + + case 5: + { + static const char *const mode[8] = + { + "continuous", "cascaded", "continuous", "one-shot", + "freq-cmp", "freq-cmp", "pulse-cmp", "pulse-cmp" + }; + LOG (( "$%04x %f: mc6846 TCR write $%02X reset=%i clock=%s scale=%i mode=%s out=%s\n", + cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), data, + (data >> 7) & 1, (data & 0x40) ? "extern" : "sys", + (data & 0x40) ? 1 : 8, mode[ (data >> 1) & 7 ], + (data & 1) ? "enabled" : "0" )); + + mc6846->tcr = data; + if ( mc6846->tcr & 1 ) + { + /* timer preset = initialization without launch */ + mc6846->preset = mc6846->latch; + mc6846->csr &= ~1; + if ( MODE != 0x30 ) + mc6846->cto = 0; + mc6846_update_cto( device ); + mc6846->interval->reset( ); + mc6846->one_shot->reset( ); + mc6846->timer_started = 0; + } + else + { + /* timer launch */ + if ( ! mc6846->timer_started ) + mc6846_timer_launch( device ); + } + mc6846_update_irq( device ); + } + break; + + case 6: + mc6846->time_MSB = data; + break; + + case 7: + mc6846->latch = ( ((UINT16) mc6846->time_MSB) << 8 ) + data; + LOG (( "$%04x %f: mc6846 COUNT write %i\n", cpu_get_previouspc( device->machine().firstcpu ), device->machine().time().as_double(), mc6846->latch )); + if (!(mc6846->tcr & 0x38)) + { + /* timer initialization */ + mc6846->preset = mc6846->latch; + mc6846->csr &= ~1; + mc6846_update_irq( device ); + mc6846->cto = 0; + mc6846_update_cto( device ); + /* launch only if started */ + if (!(mc6846->tcr & 1)) + mc6846_timer_launch( device ); + } + break; + + default: + logerror( "$%04x mc6846 invalid write offset %i\n", cpu_get_previouspc( device->machine().firstcpu ), offset ); + } +} + + + +/******************** outside world interface ************************/ + + + +void mc6846_set_input_cp1 ( device_t *device, int data ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + data = (data != 0 ); + if ( data == mc6846->cp1 ) + return; + mc6846->cp1 = data; + LOG (( "%f: mc6846 input CP1 set to %i\n", device->machine().time().as_double(), data )); + if (( data && (mc6846->pcr & 2)) || (!data && !(mc6846->pcr & 2))) + { + mc6846->csr |= 2; + mc6846_update_irq( device ); + } +} + +void mc6846_set_input_cp2 ( device_t *device, int data ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + data = (data != 0 ); + if ( data == mc6846->cp2 ) + return; + mc6846->cp2 = data; + LOG (( "%f: mc6846 input CP2 set to %i\n", device->machine().time().as_double(), data )); + if (mc6846->pcr & 0x20) + { + if (( data && (mc6846->pcr & 0x10)) || (!data && !(mc6846->pcr & 0x10))) + { + mc6846->csr |= 4; + mc6846_update_irq( device ); + } + } +} + + + +/************************ accessors **********************************/ + + + +UINT8 mc6846_get_output_port ( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + return PORT; +} + + + +UINT8 mc6846_get_output_cto ( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + return CTO; +} + + + +UINT8 mc6846_get_output_cp2 ( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + return mc6846->cp2_cpu; +} + + + +UINT16 mc6846_get_preset ( device_t *device ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + return mc6846->preset; +} + + + +/************************ reset *****************************/ + + +static DEVICE_RESET( mc6846 ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + LOG (( "mc6846_reset\n" )); + mc6846->cto = 0; + mc6846->csr = 0; + mc6846->pcr = 0x80; + mc6846->ddr = 0; + mc6846->pdr = 0; + mc6846->tcr = 1; + mc6846->cp1 = 0; + mc6846->cp2 = 0; + mc6846->cp2_cpu = 0; + mc6846->latch = 0xffff; + mc6846->preset = 0xffff; + mc6846->time_MSB = 0; + mc6846->csr0_to_be_cleared = 0; + mc6846->csr1_to_be_cleared = 0; + mc6846->csr2_to_be_cleared = 0; + mc6846->timer_started = 0; + mc6846->interval->reset( ); + mc6846->one_shot->reset( ); +} + + +/************************ start *****************************/ + +static DEVICE_START( mc6846 ) +{ + mc6846_t* mc6846 = get_safe_token( device ); + + mc6846->iface = (const mc6846_interface*)device->static_config(); + mc6846->interval = device->machine().scheduler().timer_alloc(FUNC(mc6846_timer_expire), (void*) device ); + mc6846->one_shot = device->machine().scheduler().timer_alloc(FUNC(mc6846_timer_one_shot), (void*) device ); + + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->csr ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->pcr ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->ddr ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->pdr ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->tcr ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->cp1 ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->cp2 ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->cp2_cpu ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->cto ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->time_MSB ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->csr0_to_be_cleared ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->csr1_to_be_cleared ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->csr2_to_be_cleared ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->latch ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->preset ); + state_save_register_item( device->machine(), "mc6846", device->tag(), 0, mc6846->timer_started ); +} + + +/************************** configuration ****************************/ + + +DEVICE_GET_INFO( mc6846 ) { + switch ( state ) { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(mc6846_t); break; + case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = 0; break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(mc6846); break; + case DEVINFO_FCT_STOP: /* nothing */ break; + case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(mc6846); break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: strcpy(info->s, "Motorola MC6846 programmable timer"); break; + case DEVINFO_STR_FAMILY: strcpy(info->s, "MC6846"); break; + case DEVINFO_STR_VERSION: strcpy(info->s, "1.00"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright the MAME and MESS Teams"); break; + } +} + +DEFINE_LEGACY_DEVICE(MC6846, mc6846); |