diff options
Diffstat (limited to 'src/mess/machine/mos6530.c')
-rw-r--r-- | src/mess/machine/mos6530.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/src/mess/machine/mos6530.c b/src/mess/machine/mos6530.c new file mode 100644 index 00000000000..e8554a1d339 --- /dev/null +++ b/src/mess/machine/mos6530.c @@ -0,0 +1,459 @@ +/*************************************************************************** + + MIOT 6530 emulation + +The timer seems to follow these rules: +- When the timer flag changes from 0 to 1 the timer continues to count + down at a 1 cycle rate. +- When the timer is being read or written the timer flag is reset. +- When the timer flag is set and the timer contents are 0, the counting + stops. + +From the operation of the KIM1 it expects the irqflag to be set whenever +the unit is reset. This is something that is not clear from the datasheet +and should be verified against real hardware. + +***************************************************************************/ + +#include "emu.h" +#include "mos6530.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +enum +{ + TIMER_IDLE, + TIMER_COUNTING, + TIMER_FINISHING +}; + +#define TIMER_FLAG 0x80 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _mos6530_port mos6530_port; +struct _mos6530_port +{ + devcb_resolved_read8 in_port_func; + devcb_resolved_write8 out_port_func; + + UINT8 in; + UINT8 out; + UINT8 ddr; +}; + + +typedef struct _mos6530_state mos6530_state; +struct _mos6530_state +{ + devcb_resolved_write_line out_irq_func; + + mos6530_port port[2]; + + UINT8 irqstate; + UINT8 irqenable; + + UINT8 timershift; + UINT8 timerstate; + emu_timer * timer; + + UINT32 clock; +}; + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + get_safe_token - convert a device's token + into a mos6530_state +-------------------------------------------------*/ + +INLINE mos6530_state *get_safe_token(device_t *device) +{ + assert(device != NULL); + assert(device->type() == MOS6530); + return (mos6530_state *)downcast<legacy_device_base *>(device)->token(); +} + + +/*------------------------------------------------- + update_irqstate - update the IRQ state + based on interrupt enables +-------------------------------------------------*/ + +INLINE void update_irqstate(device_t *device) +{ + mos6530_state *miot = get_safe_token(device); + UINT8 out = miot->port[1].out; + + if ( miot->irqenable ) + out = ( ( miot->irqstate & TIMER_FLAG ) ? 0x00 : 0x80 ) | ( out & 0x7F ); + + if (!miot->port[1].out_port_func.isnull()) + miot->port[1].out_port_func(0, out); + else + logerror("6530MIOT chip %s: Port B is being written to but has no handler.\n", device->tag()); +} + + +/*------------------------------------------------- + get_timer - return the current timer value +-------------------------------------------------*/ + +INLINE UINT8 get_timer(mos6530_state *miot) +{ + /* if idle, return 0 */ + if (miot->timerstate == TIMER_IDLE) + return 0; + + /* if counting, return the number of ticks remaining */ + else if (miot->timerstate == TIMER_COUNTING) + return miot->timer->remaining().as_ticks(miot->clock) >> miot->timershift; + + /* if finishing, return the number of ticks without the shift */ + else + return miot->timer->remaining().as_ticks(miot->clock); +} + + +/*************************************************************************** + INTERNAL FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + timer_end_callback - callback to process the + timer +-------------------------------------------------*/ + +static TIMER_CALLBACK( timer_end_callback ) +{ + device_t *device = (device_t *)ptr; + mos6530_state *miot = get_safe_token(device); + + assert(miot->timerstate != TIMER_IDLE); + + /* if we finished counting, switch to the finishing state */ + if (miot->timerstate == TIMER_COUNTING) + { + miot->timerstate = TIMER_FINISHING; + miot->timer->adjust(attotime::from_ticks(256, miot->clock)); + + /* signal timer IRQ as well */ + miot->irqstate |= TIMER_FLAG; + update_irqstate(device); + } + + /* if we finished finishing, switch to the idle state */ + else if (miot->timerstate == TIMER_FINISHING) + { + miot->timerstate = TIMER_IDLE; + miot->timer->adjust(attotime::never); + } +} + + + +/*************************************************************************** + I/O ACCESS +***************************************************************************/ + +/*------------------------------------------------- + mos6530_w - master I/O write access +-------------------------------------------------*/ + +WRITE8_DEVICE_HANDLER( mos6530_w ) +{ + mos6530_state *miot = get_safe_token(device); + + /* if A2 == 1, we are writing to the timer */ + if (offset & 0x04) + { + static const UINT8 timershift[4] = { 0, 3, 6, 10 }; + attotime curtime = device->machine().time(); + INT64 target; + + /* A0-A1 contain the timer divisor */ + miot->timershift = timershift[offset & 3]; + + /* A3 contains the timer IRQ enable */ + if (offset & 8) + miot->irqenable |= TIMER_FLAG; + else + miot->irqenable &= ~TIMER_FLAG; + + /* writes here clear the timer flag */ + if (miot->timerstate != TIMER_FINISHING || get_timer(miot) != 0xff) + miot->irqstate &= ~TIMER_FLAG; + update_irqstate(device); + + /* update the timer */ + miot->timerstate = TIMER_COUNTING; + target = curtime.as_ticks(miot->clock) + 1 + (data << miot->timershift); + miot->timer->adjust(attotime::from_ticks(target, miot->clock) - curtime); + } + + /* if A2 == 0, we are writing to the I/O section */ + else + { + /* A1 selects the port */ + mos6530_port *port = &miot->port[(offset >> 1) & 1]; + + /* if A0 == 1, we are writing to the port's DDR */ + if (offset & 1) + port->ddr = data; + + /* if A0 == 0, we are writing to the port's output */ + else + { + UINT8 olddata = port->out; + port->out = data; + + if ( ( offset & 2 ) && miot->irqenable ) + { + olddata = ( ( miot->irqstate & TIMER_FLAG ) ? 0x00 : 0x80 ) | ( olddata & 0x7F ); + data = ( ( miot->irqstate & TIMER_FLAG ) ? 0x00 : 0x80 ) | ( data & 0x7F ); + } + + if (!port->out_port_func.isnull()) + port->out_port_func(0, data); + else + logerror("6530MIOT chip %s: Port %c is being written to but has no handler. PC: %08X - %02X\n", device->tag(), 'A' + (offset & 1), cpu_get_pc(device->machine().firstcpu), data); + } + } +} + + +/*------------------------------------------------- + mos6530_r - master I/O read access +-------------------------------------------------*/ + +READ8_DEVICE_HANDLER( mos6530_r ) +{ + mos6530_state *miot = get_safe_token(device); + UINT8 val = 0; + + /* if A2 == 1 and A0 == 1, we are reading interrupt flags */ + if ((offset & 0x05) == 0x05) + { + val = miot->irqstate; + } + + /* if A2 == 1 and A0 == 0, we are reading the timer */ + else if ((offset & 0x05) == 0x04) + { + val = get_timer(miot); + + /* A3 contains the timer IRQ enable */ + if (offset & 8) + miot->irqenable |= TIMER_FLAG; + else + miot->irqenable &= ~TIMER_FLAG; + + /* implicitly clears the timer flag */ + if (miot->timerstate != TIMER_FINISHING || val != 0xff) + miot->irqstate &= ~TIMER_FLAG; + update_irqstate(device); + } + + /* if A2 == 0 and A0 == anything, we are reading from ports */ + else + { + /* A1 selects the port */ + mos6530_port *port = &miot->port[(offset >> 1) & 1]; + + /* if A0 == 1, we are reading the port's DDR */ + if (offset & 1) + val = port->ddr; + + /* if A0 == 0, we are reading the port as an input */ + else + { + UINT8 out = port->out; + + if ( ( offset & 2 ) && miot->irqenable ) + out = ( ( miot->irqstate & TIMER_FLAG ) ? 0x00 : 0x80 ) | ( out & 0x7F ); + + /* call the input callback if it exists */ + if (!port->in_port_func.isnull()) + { + port->in = port->in_port_func(0); + } + else + logerror("6530MIOT chip %s: Port %c is being read but has no handler. PC: %08X\n", device->tag(), 'A' + (offset & 1), cpu_get_pc(device->machine().firstcpu)); + + /* apply the DDR to the result */ + val = (out & port->ddr) | (port->in & ~port->ddr); + } + } + return val; +} + + +/*------------------------------------------------- + mos6530_porta_in_set - set port A input + value +-------------------------------------------------*/ + +void mos6530_porta_in_set(device_t *device, UINT8 data, UINT8 mask) +{ + mos6530_state *miot = get_safe_token(device); + miot->port[0].in = (miot->port[0].in & ~mask) | (data & mask); +} + + +/*------------------------------------------------- + mos6530_portb_in_set - set port B input + value +-------------------------------------------------*/ + +void mos6530_portb_in_set(device_t *device, UINT8 data, UINT8 mask) +{ + mos6530_state *miot = get_safe_token(device); + miot->port[1].in = (miot->port[1].in & ~mask) | (data & mask); +} + + +/*------------------------------------------------- + mos6530_porta_in_get - return port A input + value +-------------------------------------------------*/ + +UINT8 mos6530_porta_in_get(device_t *device) +{ + mos6530_state *miot = get_safe_token(device); + return miot->port[0].in; +} + + +/*------------------------------------------------- + mos6530_portb_in_get - return port B input + value +-------------------------------------------------*/ + +UINT8 mos6530_portb_in_get(device_t *device) +{ + mos6530_state *miot = get_safe_token(device); + return miot->port[1].in; +} + + +/*------------------------------------------------- + mos6530_porta_in_get - return port A output + value +-------------------------------------------------*/ + +UINT8 mos6530_porta_out_get(device_t *device) +{ + mos6530_state *miot = get_safe_token(device); + return miot->port[0].out; +} + + +/*------------------------------------------------- + mos6530_portb_in_get - return port B output + value +-------------------------------------------------*/ + +UINT8 mos6530_portb_out_get(device_t *device) +{ + mos6530_state *miot = get_safe_token(device); + return miot->port[1].out; +} + + +/*************************************************************************** + DEVICE INTERFACE +***************************************************************************/ + +static DEVICE_START( mos6530 ) +{ + mos6530_state *miot = get_safe_token(device); + const mos6530_interface *intf = (const mos6530_interface*)device->static_config(); + + /* validate arguments */ + assert(device != NULL); + assert(device->tag() != NULL); + + /* set static values */ + miot->clock = device->clock(); + + /* resolve callbacks */ + miot->port[0].in_port_func.resolve(intf->in_pa_func, *device); + miot->port[1].in_port_func.resolve(intf->in_pb_func, *device); + miot->port[0].out_port_func.resolve(intf->out_pa_func, *device); + miot->port[1].out_port_func.resolve(intf->out_pb_func, *device); + + /* allocate timers */ + miot->timer = device->machine().scheduler().timer_alloc(FUNC(timer_end_callback), (void *)device); + + /* register for save states */ + device->save_item(NAME(miot->port[0].in)); + device->save_item(NAME(miot->port[0].out)); + device->save_item(NAME(miot->port[0].ddr)); + device->save_item(NAME(miot->port[1].in)); + device->save_item(NAME(miot->port[1].out)); + device->save_item(NAME(miot->port[1].ddr)); + + device->save_item(NAME(miot->irqstate)); + device->save_item(NAME(miot->irqenable)); + + device->save_item(NAME(miot->timershift)); + device->save_item(NAME(miot->timerstate)); +} + + +static DEVICE_RESET( mos6530 ) +{ + mos6530_state *miot = get_safe_token(device); + + /* reset I/O states */ + miot->port[0].out = 0; + miot->port[0].ddr = 0; + miot->port[1].out = 0; + miot->port[1].ddr = 0; + + /* reset IRQ states */ + miot->irqenable = 0; + miot->irqstate = TIMER_FLAG; + update_irqstate(device); + + /* reset timer states */ + miot->timershift = 0; + miot->timerstate = TIMER_IDLE; + miot->timer->adjust(attotime::never); +} + + +DEVICE_GET_INFO( mos6530 ) +{ + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(mos6530_state); 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(mos6530); break; + case DEVINFO_FCT_STOP: /* Nothing */ break; + case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(mos6530); break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: strcpy(info->s, "MOS6530"); break; + case DEVINFO_STR_FAMILY: strcpy(info->s, "MOS6500"); break; + case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright MESS Team"); break; + } +} + +DEFINE_LEGACY_DEVICE(MOS6530, mos6530); |