diff options
Diffstat (limited to 'src/emu/machine/6526cia.c')
-rw-r--r-- | src/emu/machine/6526cia.c | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/src/emu/machine/6526cia.c b/src/emu/machine/6526cia.c new file mode 100644 index 00000000000..0b3e1af3af2 --- /dev/null +++ b/src/emu/machine/6526cia.c @@ -0,0 +1,782 @@ +/********************************************************************** + + MOS 6526/8520 CIA interface and emulation + + This function emulates all the functionality of up to 2 MOS6526 or + MOS8520 complex interface adapters. + +**********************************************************************/ + +#include "driver.h" +#include "6526cia.h" + + + +/************************************* + * + * Constants + * + *************************************/ + +/* CIA registers */ +#define CIA_PRA 0 +#define CIA_PRB 1 +#define CIA_DDRA 2 +#define CIA_DDRB 3 +#define CIA_TALO 4 +#define CIA_TAHI 5 +#define CIA_TBLO 6 +#define CIA_TBHI 7 +#define CIA_TOD0 8 /* 6526: 1/10 seconds 8520: bits 0- 7 */ +#define CIA_TOD1 9 /* 6526: seconds 8520: bits 8-15 */ +#define CIA_TOD2 10 /* 6526: minutes 8520: bits 16-23 */ +#define CIA_TOD3 11 /* 6526: hours 8520: N/A */ +#define CIA_SDR 12 +#define CIA_ICR 13 +#define CIA_CRA 14 +#define CIA_CRB 15 + + + +/************************************* + * + * Type definitions + * + *************************************/ + +typedef struct _cia_timer cia_timer; +typedef struct _cia_port cia_port; +typedef struct _cia_state cia_state; + +struct _cia_timer +{ + UINT16 latch; + UINT16 count; + UINT8 mode; + UINT8 irq; + emu_timer *timer; + cia_state * cia; +}; + +struct _cia_port +{ + UINT8 ddr; + UINT8 latch; + UINT8 in; + UINT8 out; + UINT8 (*read)(void); + void (*write)(UINT8); +}; + +struct _cia_state +{ + int active; + cia_type_t type; + void (*irq_func)(int state); + int clock; + + cia_port port[2]; + cia_timer timer[2]; + + /* Time Of the Day clock (TOD) */ + UINT32 tod; + UINT32 tod_latch; + UINT8 tod_latched; + UINT8 tod_running; + UINT32 alarm; + + /* Interrupts */ + UINT8 icr; + UINT8 ics; + UINT8 irq; + + /* Serial */ + UINT8 loaded; + UINT8 sdr; + UINT8 sp; + UINT8 cnt; + UINT8 shift; + UINT8 serial; +}; + + + +/************************************* + * + * Globals + * + *************************************/ + +static cia_state cia_array[2]; + + + +/************************************* + * + * Prototypes + * + *************************************/ + +static TIMER_CALLBACK_PTR( cia_timer_proc ); +static void cia_timer_underflow(cia_state *cia, int timer); +static TIMER_CALLBACK( cia_clock_tod_callback ); + + + +/************************************* + * + * Prototypes + * + *************************************/ + + +/*************************************************************************** + + Setup and reset + +***************************************************************************/ + +static void cia_exit(running_machine *machine) +{ + memset(cia_array, 0, sizeof(*cia_array)); +} + + + +void cia_config(int which, const cia6526_interface *intf) +{ + int t, p; + cia_state *cia = &cia_array[which]; + + /* sanity checks */ + assert_always(mame_get_phase(Machine) == MAME_PHASE_INIT, "Can only call cia_config at init time!"); + assert_always((which >= 0) && (which < (sizeof(cia_array) / sizeof(cia_array[0]))), + "cia_config called on an invalid CIA!"); + + /* clear out CIA structure, and copy the interface */ + memset(cia, 0, sizeof(*cia)); + cia->active = TRUE; + cia->type = intf->type; + cia->clock = (intf->clock != 0) ? intf->clock : cpunum_get_clock(0); + cia->irq_func = intf->irq_func; + + /* setup ports */ + for (p = 0; p < (sizeof(cia->port) / sizeof(cia->port[0])); p++) + { + cia->port[p].read = intf->port[p].read; + cia->port[p].write = intf->port[p].write; + } + + /* setup timers */ + for (t = 0; t < (sizeof(cia->timer) / sizeof(cia->timer[0])); t++) + { + cia_timer *timer = &cia->timer[t]; + timer->timer = timer_alloc_ptr(cia_timer_proc, timer); + timer->cia = cia; + timer->irq = 0x01 << t; + } + + /* setup TOD timer, if appropriate */ + if (intf->tod_clock) + timer_pulse(ATTOTIME_IN_HZ(intf->tod_clock), which, cia_clock_tod_callback); + + /* special case; for the first CIA, set up an exit handler to clear things out */ + if (which == 0) + add_exit_callback(Machine, cia_exit); + + /* state save support */ + state_save_register_item("6526cia", which, cia->port[0].ddr); + state_save_register_item("6526cia", which, cia->port[0].latch); + state_save_register_item("6526cia", which, cia->port[0].in); + state_save_register_item("6526cia", which, cia->port[0].out); + state_save_register_item("6526cia", which, cia->port[1].ddr); + state_save_register_item("6526cia", which, cia->port[1].latch); + state_save_register_item("6526cia", which, cia->port[1].in); + state_save_register_item("6526cia", which, cia->port[1].out); + state_save_register_item("6526cia", which, cia->timer[0].latch); + state_save_register_item("6526cia", which, cia->timer[0].count); + state_save_register_item("6526cia", which, cia->timer[0].mode); + state_save_register_item("6526cia", which, cia->timer[0].irq); + state_save_register_item("6526cia", which, cia->timer[1].latch); + state_save_register_item("6526cia", which, cia->timer[1].count); + state_save_register_item("6526cia", which, cia->timer[1].mode); + state_save_register_item("6526cia", which, cia->timer[1].irq); + state_save_register_item("6526cia", which, cia->tod); + state_save_register_item("6526cia", which, cia->tod_latch); + state_save_register_item("6526cia", which, cia->tod_latched); + state_save_register_item("6526cia", which, cia->tod_running); + state_save_register_item("6526cia", which, cia->alarm); + state_save_register_item("6526cia", which, cia->icr); + state_save_register_item("6526cia", which, cia->ics); + state_save_register_item("6526cia", which, cia->irq); + state_save_register_item("6526cia", which, cia->loaded); + state_save_register_item("6526cia", which, cia->sdr); + state_save_register_item("6526cia", which, cia->sp); + state_save_register_item("6526cia", which, cia->cnt); + state_save_register_item("6526cia", which, cia->shift); + state_save_register_item("6526cia", which, cia->serial); +} + + + +void cia_reset(void) +{ + int i, t; + + /* loop over and set up initial values */ + for (i = 0; i < (sizeof(cia_array) / sizeof(cia_array[0])); i++) + { + cia_state *cia = &cia_array[i]; + + if (cia->active) + { + /* clear things out */ + cia->port[0].latch = 0x00; + cia->port[0].in = 0x00; + cia->port[0].out = 0x00; + cia->port[1].latch = 0x00; + cia->port[1].in = 0x00; + cia->port[1].out = 0x00; + cia->tod = 0; + cia->tod_latch = 0; + cia->alarm = 0; + cia->icr = 0x00; + cia->ics = 0x00; + cia->irq = 0; + + /* initialize data direction registers */ + cia->port[0].ddr = (i == 0) ? 0x03 : 0xff; + cia->port[1].ddr = (i == 0) ? 0x00 : 0xff; + + /* TOD running by default */ + cia->tod_running = TRUE; + + /* initialize timers */ + for (t = 0; t < 2; t++) + { + cia_timer *timer = &cia->timer[t]; + + timer->latch = 0xffff; + timer->count = 0x0000; + timer->mode = 0x00; + } + } + } +} + + +/*************************************************************************** + + CIA runtime + +***************************************************************************/ + +static void cia_update_interrupts(cia_state *cia) +{ + UINT8 new_irq; + + /* always update the high bit of ICS */ + if (cia->ics & 0x7f) + cia->ics |= 0x80; + else + cia->ics &= ~0x80; + + /* based on what is enabled, set/clear the IRQ via the custom chip */ + new_irq = (cia->ics & cia->icr) ? 1 : 0; + if (cia->irq != new_irq) + { + cia->irq = new_irq; + if (cia->irq_func) + cia->irq_func(cia->irq); + } +} + + +static int is_timer_active(emu_timer *timer) +{ + attotime t = timer_firetime(timer); + return attotime_compare(t, attotime_never) != 0; +} + + +/* updates the count and emu_timer for a given CIA timer */ +static void cia_timer_update(cia_timer *timer, INT32 new_count) +{ + int which = timer - timer->cia->timer; + + /* sanity check arguments */ + assert((new_count >= -1) && (new_count <= 0xffff)); + + /* update the timer count, if necessary */ + if ((new_count == -1) && is_timer_active(timer->timer)) + { + UINT16 current_count = attotime_to_double(attotime_mul(timer_timeelapsed(timer->timer), timer->cia->clock)); + timer->count = timer->count - MIN(timer->count, current_count); + } + + /* set the timer if we are instructed to */ + if (new_count != -1) + timer->count = new_count; + + /* now update the MAME timer */ + if ((timer->mode & 0x01) && ((timer->mode & (which ? 0x60 : 0x20)) == 0x00)) + { + /* timer is on and is connected to clock */ + attotime period = attotime_mul(ATTOTIME_IN_HZ(timer->cia->clock), (timer->count ? timer->count : 0x10000)); + timer_adjust_ptr(timer->timer, period, attotime_zero); + } + else + { + /* timer is off or not connected to clock */ + timer_adjust_ptr(timer->timer, attotime_never, attotime_zero); + } +} + + +static void cia_timer_bump(cia_state *cia, int timer) +{ + cia_timer_update(&cia->timer[timer], -1); + + if (cia->timer[timer].count == 0x00) + cia_timer_underflow(cia, timer); + else + cia_timer_update(&cia->timer[timer], cia->timer[timer].count - 1); +} + + +static void cia_timer_underflow(cia_state *cia, int timer) +{ + assert((timer == 0) || (timer == 1)); + + /* set the status and update interrupts */ + cia->ics |= cia->timer[timer].irq; + cia_update_interrupts(cia); + + /* if one-shot mode, turn it off */ + if (cia->timer[timer].mode & 0x08) + cia->timer[timer].mode &= 0xfe; + + /* reload the timer */ + cia_timer_update(&cia->timer[timer], cia->timer[timer].latch); + + /* timer A has some interesting properties */ + if (timer == 0) + { + /* such as cascading to timer B */ + if ((cia->timer[1].mode & 0x41) == 0x41) + { + if (cia->cnt || !(cia->timer[1].mode & 0x20)) + cia_timer_bump(cia, 1); + } + + /* also the serial line */ + if ((cia->timer[timer].irq == 0x01) && (cia->timer[timer].mode & 0x40)) + { + if (cia->shift || cia->loaded) + { + if (cia->cnt) + { + if (cia->shift == 0) + { + cia->loaded = 0; + cia->serial = cia->sdr; + } + cia->sp = (cia->serial & 0x80) ? 1 : 0; + cia->shift++; + cia->serial <<= 1; + cia->cnt = 0; + } + else + { + cia->cnt = 1; + if (cia->shift == 8) + { + cia->ics |= 0x08; + cia_update_interrupts(cia); + } + } + } + } + } +} + + +static TIMER_CALLBACK_PTR( cia_timer_proc ) +{ + cia_timer *timer = param; + cia_state *cia = timer->cia; + + cia_timer_underflow(cia, timer - cia->timer); +} + + +static UINT8 bcd_increment(UINT8 value) +{ + value++; + if ((value & 0x0f) >= 0x0a) + value += 0x10 - 0x0a; + return value; +} + + +static void cia6526_increment(cia_state *cia) +{ + /* break down TOD value into components */ + UINT8 subsecond = (UINT8) (cia->tod >> 0); + UINT8 second = (UINT8) (cia->tod >> 8); + UINT8 minute = (UINT8) (cia->tod >> 16); + UINT8 hour = (UINT8) (cia->tod >> 24); + + subsecond = bcd_increment(subsecond); + if (subsecond >= 0x10) + { + subsecond = 0x00; + second = bcd_increment(second); + if (second >= ((cia->timer[0].mode & 0x80) ? 0x50 : 0x60)) + { + second = 0x00; + minute = bcd_increment(minute); + if (minute >= 0x60) + { + minute = 0x00; + if (hour == 0x91) + hour = 0x00; + else if (hour == 0x89) + hour = 0x90; + else if (hour == 0x11) + hour = 0x80; + else if (hour == 0x09) + hour = 0x10; + else + hour++; + } + } + } + + /* update the TOD with new value */ + cia->tod = (((UINT32) subsecond) << 0) + | (((UINT32) second) << 8) + | (((UINT32) minute) << 16) + | (((UINT32) hour) << 24); +} + + +/* Update TOD on CIA A */ +void cia_clock_tod(int which) +{ + cia_state *cia; + + cia = &cia_array[which]; + + if (cia->tod_running) + { + switch(cia->type) + { + case CIA6526: + /* The 6526 split the value into hours, minutes, seconds and + * subseconds */ + cia6526_increment(cia); + break; + + case CIA8520: + /* the 8520 has a straight 24-bit counter */ + cia->tod++; + cia->tod &= 0xffffff; + break; + } + + if (cia->tod == cia->alarm) + { + cia->ics |= 0x04; + cia_update_interrupts(cia); + } + } +} + + +static TIMER_CALLBACK( cia_clock_tod_callback ) +{ + cia_clock_tod(param); +} + + +void cia_issue_index(int which) +{ + cia_state *cia = &cia_array[which]; + cia->ics |= 0x10; + cia_update_interrupts(cia); +} + + +void cia_set_input_sp(int which, int data) +{ + cia_state *cia = &cia_array[which]; + cia->sp = data; +} + + +void cia_set_input_cnt(int which, int data) +{ + cia_state *cia = &cia_array[which]; + + /* is this a rising edge? */ + if (!cia->cnt && data) + { + /* does timer #0 bump on CNT? */ + if ((cia->timer[0].mode & 0x21) == 0x21) + cia_timer_bump(cia, 0); + + /* does timer #1 bump on CNT? */ + if ((cia->timer[1].mode & 0x61) == 0x21) + cia_timer_bump(cia, 1); + + /* if the serial port is set to output, the CNT will shift the port */ + if (!(cia->timer[0].mode & 0x40)) + { + cia->serial >>= 1; + if (cia->sp) + cia->serial |= 0x80; + + if (++cia->shift == 8) + { + cia->sdr = cia->serial; + cia->serial = 0; + cia->shift = 0; + cia->ics |= 0x08; + cia_update_interrupts(cia); + } + } + } + cia->cnt = data ? 1 : 0; +} + + +UINT8 cia_read(int which, offs_t offset) +{ + cia_timer *timer; + cia_state *cia; + cia_port *port; + UINT8 data = 0x00; + + cia = &cia_array[which]; + offset &= 0x0F; + + switch(offset) + { + /* port A/B data */ + case CIA_PRA: + case CIA_PRB: + port = &cia->port[offset & 1]; + data = port->read ? (*port->read)() : 0; + //data = data & (port->latch | ~port->ddr); + data = (data & ~port->ddr) | (port->latch & port->ddr); + port->in = data; + + if (offset == CIA_PRB) + { + /* timer #0 can change PB6 */ + if (cia->timer[0].mode & 0x02) + { + cia_timer_update(&cia->timer[0], -1); + if (cia->timer[0].count != 0) + data |= 0x40; + else + data &= ~0x40; + } + + /* timer #1 can change PB7 */ + if (cia->timer[1].mode & 0x02) + { + cia_timer_update(&cia->timer[1], -1); + if (cia->timer[1].count != 0) + data |= 0x80; + else + data &= ~0x80; + } + } + break; + + /* port A/B direction */ + case CIA_DDRA: + case CIA_DDRB: + port = &cia->port[offset & 1]; + data = port->ddr; + break; + + /* timer A/B low byte */ + case CIA_TALO: + case CIA_TBLO: + timer = &cia->timer[(offset >> 1) & 1]; + cia_timer_update(timer, -1); + data = timer->count >> 0; + break; + + /* timer A/B high byte */ + case CIA_TAHI: + case CIA_TBHI: + timer = &cia->timer[(offset >> 1) & 1]; + cia_timer_update(timer, -1); + data = timer->count >> 8; + break; + + /* TOD counter */ + case CIA_TOD0: + case CIA_TOD1: + case CIA_TOD2: + case CIA_TOD3: + if (offset == CIA_TOD2) + { + cia->tod_latch = cia->tod; + cia->tod_latched = TRUE; + } + else if (offset == CIA_TOD0) + cia->tod_latched = FALSE; + + if (cia->tod_latched) + data = cia->tod_latch >> ((offset - CIA_TOD0) * 8); + else + data = cia->tod >> ((offset - CIA_TOD0) * 8); + break; + + /* serial data ready */ + case CIA_SDR: + data = cia->sdr; + break; + + /* interrupt status/clear */ + case CIA_ICR: + data = cia->ics; + cia->ics = 0; /* clear on read */ + cia_update_interrupts(cia); + break; + + /* timer A/B mode */ + case CIA_CRA: + case CIA_CRB: + timer = &cia->timer[offset & 1]; + data = timer->mode; + break; + } + return data; +} + + + +void cia_write(int which, offs_t offset, UINT8 data) +{ + cia_timer *timer; + cia_state *cia; + cia_port *port; + int shift; + + cia = &cia_array[which]; + offset &= 0x0F; + + switch(offset) + { + /* port A/B data */ + case CIA_PRA: + case CIA_PRB: + port = &cia->port[offset & 1]; + port->latch = data; + port->out = (data & port->ddr) | (port->in & ~port->ddr); + if (port->write) + (*port->write)(port->out); + break; + + /* port A/B direction */ + case CIA_DDRA: + case CIA_DDRB: + port = &cia->port[offset & 1]; + port->ddr = data; + break; + + /* timer A/B latch low */ + case CIA_TALO: + case CIA_TBLO: + timer = &cia->timer[(offset >> 1) & 1]; + timer->latch = (timer->latch & 0xff00) | (data << 0); + break; + + /* timer A latch high */ + case CIA_TAHI: + case CIA_TBHI: + timer = &cia->timer[(offset >> 1) & 1]; + timer->latch = (timer->latch & 0x00ff) | (data << 8); + + /* if the timer is one-shot, then force a start on it */ + if (timer->mode & 0x08) + { + timer->mode |= 1; + cia_timer_update(timer, timer->latch); + } + else + { + /* if the timer is off, update the count */ + if (!(timer->mode & 0x01)) + cia_timer_update(timer, timer->latch); + } + break; + + /* time of day latches */ + case CIA_TOD0: + case CIA_TOD1: + case CIA_TOD2: + shift = 8 * ((offset - CIA_TOD0)); + + /* alarm setting mode? */ + if (cia->timer[1].mode & 0x80) + cia->alarm = (cia->alarm & ~(0xff << shift)) | (data << shift); + + /* counter setting mode */ + else + { + cia->tod = (cia->tod & ~(0xff << shift)) | (data << shift); + + /* only enable the TOD once the LSB is written */ + cia->tod_running = (shift == 0); + } + break; + + /* serial data ready */ + case CIA_SDR: + cia->sdr = data; + if (cia->timer[0].mode & 0x40) + cia->loaded = 1; + break; + + /* interrupt control register */ + case CIA_ICR: + if (data & 0x80) + cia->icr |= data & 0x7f; + else + cia->icr &= ~(data & 0x7f); + cia_update_interrupts(cia); + break; + + /* timer A/B modes */ + case CIA_CRA: + case CIA_CRB: + timer = &cia->timer[offset & 1]; + timer->mode = data & 0xef; + + /* force load? */ + if (data & 0x10) + cia_timer_update(timer, timer->latch); + else + cia_timer_update(timer, -1); + break; + } +} + + + +UINT8 cia_get_output_a(int which) { return cia_array[which].port[0].out; } +UINT8 cia_get_output_b(int which) { return cia_array[which].port[1].out; } +int cia_get_irq(int which) { return cia_array[which].irq; } + +READ8_HANDLER( cia_0_r ) { return cia_read(0, offset); } +READ8_HANDLER( cia_1_r ) { return cia_read(1, offset); } + +WRITE8_HANDLER( cia_0_w ) { cia_write(0, offset, data); } +WRITE8_HANDLER( cia_1_w ) { cia_write(1, offset, data); } |