summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/machine/z80ctc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/machine/z80ctc.c')
-rw-r--r--src/emu/machine/z80ctc.c483
1 files changed, 483 insertions, 0 deletions
diff --git a/src/emu/machine/z80ctc.c b/src/emu/machine/z80ctc.c
new file mode 100644
index 00000000000..8ff9351e821
--- /dev/null
+++ b/src/emu/machine/z80ctc.c
@@ -0,0 +1,483 @@
+/***************************************************************************
+
+ Z80 CTC (Z8430) implementation
+
+ based on original version (c) 1997, Tatsuyuki Satoh
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include "driver.h"
+#include "z80ctc.h"
+#include "cpu/z80/z80.h"
+#include "cpu/z80/z80daisy.h"
+
+
+
+/***************************************************************************
+ DEBUGGING
+***************************************************************************/
+
+#define VERBOSE 0
+
+#if VERBOSE
+#define VPRINTF(x) logerror x
+#else
+#define VPRINTF(x)
+#endif
+
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+/* these are the bits of the incoming commands to the CTC */
+#define INTERRUPT 0x80
+#define INTERRUPT_ON 0x80
+#define INTERRUPT_OFF 0x00
+
+#define MODE 0x40
+#define MODE_TIMER 0x00
+#define MODE_COUNTER 0x40
+
+#define PRESCALER 0x20
+#define PRESCALER_256 0x20
+#define PRESCALER_16 0x00
+
+#define EDGE 0x10
+#define EDGE_FALLING 0x00
+#define EDGE_RISING 0x10
+
+#define TRIGGER 0x08
+#define TRIGGER_AUTO 0x00
+#define TRIGGER_CLOCK 0x08
+
+#define CONSTANT 0x04
+#define CONSTANT_LOAD 0x04
+#define CONSTANT_NONE 0x00
+
+#define RESET 0x02
+#define RESET_CONTINUE 0x00
+#define RESET_ACTIVE 0x02
+
+#define CONTROL 0x01
+#define CONTROL_VECTOR 0x00
+#define CONTROL_WORD 0x01
+
+/* these extra bits help us keep things accurate */
+#define WAITING_FOR_TRIG 0x100
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+typedef struct _z80ctc z80ctc;
+struct _z80ctc
+{
+ UINT8 vector; /* interrupt vector */
+ UINT32 clock; /* system clock */
+ attotime period16; /* 16/system clock */
+ attotime period256; /* 256/system clock */
+ void (*intr)(int which); /* interrupt callback */
+ write8_handler zc[4]; /* zero crossing callbacks */
+ UINT8 notimer; /* no timer masks */
+ UINT16 mode[4]; /* current mode */
+ UINT16 tconst[4]; /* time constant */
+ UINT16 down[4]; /* down counter (clock mode only) */
+ UINT8 extclk[4]; /* current signal from the external clock */
+ emu_timer *timer[4]; /* array of active timers */
+ UINT8 int_state[4]; /* interrupt status (for daisy chain) */
+};
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static z80ctc ctcs[MAX_CTC];
+
+
+
+/***************************************************************************
+ INTERNAL STATE MANAGEMENT
+***************************************************************************/
+
+static void interrupt_check(int which)
+{
+ z80ctc *ctc = ctcs + which;
+
+ /* if we have a callback, update it with the current state */
+ if (ctc->intr)
+ (*ctc->intr)((z80ctc_irq_state(which) & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE);
+}
+
+
+static TIMER_CALLBACK( timercallback )
+{
+ int which = param >> 2;
+ int ch = param & 3;
+ z80ctc *ctc = ctcs + which;
+
+ /* down counter has reached zero - see if we should interrupt */
+ if ((ctc->mode[ch] & INTERRUPT) == INTERRUPT_ON)
+ {
+ ctc->int_state[ch] |= Z80_DAISY_INT;
+ VPRINTF(("CTC timer ch%d\n", ch));
+ interrupt_check(which);
+ }
+
+ /* generate the clock pulse */
+ if (ctc->zc[ch])
+ {
+ (*ctc->zc[ch])(0,1);
+ (*ctc->zc[ch])(0,0);
+ }
+
+ /* reset the down counter */
+ ctc->down[ch] = ctc->tconst[ch];
+}
+
+
+
+/***************************************************************************
+ INITIALIZATION/CONFIGURATION
+***************************************************************************/
+
+void z80ctc_init(int which, z80ctc_interface *intf)
+{
+ z80ctc *ctc = &ctcs[which];
+
+ assert(which < MAX_CTC);
+
+ memset(ctc, 0, sizeof(*ctc));
+
+ ctc->clock = intf->baseclock;
+ ctc->period16 = attotime_mul(ATTOTIME_IN_HZ(intf->baseclock), 16);
+ ctc->period256 = attotime_mul(ATTOTIME_IN_HZ(intf->baseclock), 256);
+ ctc->notimer = intf->notimer;
+ ctc->intr = intf->intr;
+ ctc->timer[0] = timer_alloc(timercallback);
+ ctc->timer[1] = timer_alloc(timercallback);
+ ctc->timer[2] = timer_alloc(timercallback);
+ ctc->timer[3] = timer_alloc(timercallback);
+ ctc->zc[0] = intf->zc0;
+ ctc->zc[1] = intf->zc1;
+ ctc->zc[2] = intf->zc2;
+ ctc->zc[3] = 0;
+ z80ctc_reset(which);
+
+ state_save_register_item("z80ctc", which, ctc->vector);
+ state_save_register_item_array("z80ctc", which, ctc->mode);
+ state_save_register_item_array("z80ctc", which, ctc->tconst);
+ state_save_register_item_array("z80ctc", which, ctc->down);
+ state_save_register_item_array("z80ctc", which, ctc->extclk);
+ state_save_register_item_array("z80ctc", which, ctc->int_state);
+}
+
+
+void z80ctc_reset(int which)
+{
+ z80ctc *ctc = ctcs + which;
+ int i;
+
+ /* set up defaults */
+ for (i = 0; i < 4; i++)
+ {
+ ctc->mode[i] = RESET_ACTIVE;
+ ctc->tconst[i] = 0x100;
+ timer_adjust(ctc->timer[i], attotime_never, 0, attotime_never);
+ ctc->int_state[i] = 0;
+ }
+ interrupt_check(which);
+ VPRINTF(("CTC Reset\n"));
+}
+
+
+attotime z80ctc_getperiod(int which, int ch)
+{
+ z80ctc *ctc = ctcs + which;
+ attotime period;
+
+ /* if reset active, no period */
+ if ((ctc->mode[ch] & RESET) == RESET_ACTIVE)
+ return attotime_zero;
+
+ /* if counter mode, no real period */
+ if ((ctc->mode[ch] & MODE) == MODE_COUNTER)
+ {
+ logerror("CTC %d is CounterMode : Can't calculate period\n", ch );
+ return attotime_zero;
+ }
+
+ /* compute the period */
+ period = ((ctc->mode[ch] & PRESCALER) == PRESCALER_16) ? ctc->period16 : ctc->period256;
+ return attotime_mul(period, ctc->tconst[ch]);
+}
+
+
+
+/***************************************************************************
+ WRITE HANDLERS
+***************************************************************************/
+
+void z80ctc_w(int which, int ch, UINT8 data)
+{
+ z80ctc *ctc = ctcs + which;
+ int mode;
+
+ /* get the current mode */
+ mode = ctc->mode[ch];
+
+ /* if we're waiting for a time constant, this is it */
+ if ((mode & CONSTANT) == CONSTANT_LOAD)
+ {
+ VPRINTF(("CTC ch.%d constant = %02x\n", ch, data));
+
+ /* set the time constant (0 -> 0x100) */
+ ctc->tconst[ch] = data ? data : 0x100;
+
+ /* clear the internal mode -- we're no longer waiting */
+ ctc->mode[ch] &= ~CONSTANT;
+
+ /* also clear the reset, since the constant gets it going again */
+ ctc->mode[ch] &= ~RESET;
+
+ /* if we're in timer mode.... */
+ if ((mode & MODE) == MODE_TIMER)
+ {
+ /* if we're triggering on the time constant, reset the down counter now */
+ if ((mode & TRIGGER) == TRIGGER_AUTO)
+ {
+ if (!(ctc->notimer & (1<<ch)))
+ {
+ attotime period = ((mode & PRESCALER) == PRESCALER_16) ? ctc->period16 : ctc->period256;
+ period = attotime_mul(period, ctc->tconst[ch]);
+
+ timer_adjust(ctc->timer[ch], period, (which << 2) + ch, period);
+ }
+ else
+ timer_adjust(ctc->timer[ch], attotime_never, 0, attotime_never);
+ }
+
+ /* else set the bit indicating that we're waiting for the appropriate trigger */
+ else
+ ctc->mode[ch] |= WAITING_FOR_TRIG;
+ }
+
+ /* also set the down counter in case we're clocking externally */
+ ctc->down[ch] = ctc->tconst[ch];
+
+ /* all done here */
+ return;
+ }
+
+ /* if we're writing the interrupt vector, handle it specially */
+#if 0 /* Tatsuyuki Satoh changes */
+ /* The 'Z80family handbook' wrote, */
+ /* interrupt vector is able to set for even channel (0 or 2) */
+ if ((data & CONTROL) == CONTROL_VECTOR && (ch&1) == 0)
+#else
+ if ((data & CONTROL) == CONTROL_VECTOR && ch == 0)
+#endif
+ {
+ ctc->vector = data & 0xf8;
+ logerror("CTC Vector = %02x\n", ctc->vector);
+ return;
+ }
+
+ /* this must be a control word */
+ if ((data & CONTROL) == CONTROL_WORD)
+ {
+ /* set the new mode */
+ ctc->mode[ch] = data;
+ VPRINTF(("CTC ch.%d mode = %02x\n", ch, data));
+
+ /* if we're being reset, clear out any pending timers for this channel */
+ if ((data & RESET) == RESET_ACTIVE)
+ {
+ timer_adjust(ctc->timer[ch], attotime_never, 0, attotime_zero);
+ /* note that we don't clear the interrupt state here! */
+ }
+
+ /* all done here */
+ return;
+ }
+}
+
+WRITE8_HANDLER( z80ctc_0_w ) { z80ctc_w(0, offset, data); }
+WRITE8_HANDLER( z80ctc_1_w ) { z80ctc_w(1, offset, data); }
+
+
+
+/***************************************************************************
+ READ HANDLERS
+***************************************************************************/
+
+UINT8 z80ctc_r(int which, int ch)
+{
+ z80ctc *ctc = ctcs + which;
+
+ /* if we're in counter mode, just return the count */
+ if ((ctc->mode[ch] & MODE) == MODE_COUNTER || (ctc->mode[ch] & WAITING_FOR_TRIG))
+ return ctc->down[ch];
+
+ /* else compute the down counter value */
+ else
+ {
+ attotime period = ((ctc->mode[ch] & PRESCALER) == PRESCALER_16) ? ctc->period16 : ctc->period256;
+
+ VPRINTF(("CTC clock %f\n",ATTOSECONDS_TO_HZ(period.attoseconds)));
+
+ if (ctc->timer[ch])
+ return ((int)(attotime_to_double(timer_timeleft(ctc->timer[ch])) * attotime_to_double(period)) + 1) & 0xff;
+ else
+ return 0;
+ }
+}
+
+READ8_HANDLER( z80ctc_0_r ) { return z80ctc_r(0, offset); }
+READ8_HANDLER( z80ctc_1_r ) { return z80ctc_r(1, offset); }
+
+
+
+/***************************************************************************
+ EXTERNAL TRIGGERS
+***************************************************************************/
+
+void z80ctc_trg_w(int which, int ch, UINT8 data)
+{
+ z80ctc *ctc = ctcs + which;
+
+ /* normalize data */
+ data = data ? 1 : 0;
+
+ /* see if the trigger value has changed */
+ if (data != ctc->extclk[ch])
+ {
+ ctc->extclk[ch] = data;
+
+ /* see if this is the active edge of the trigger */
+ if (((ctc->mode[ch] & EDGE) == EDGE_RISING && data) || ((ctc->mode[ch] & EDGE) == EDGE_FALLING && !data))
+ {
+ /* if we're waiting for a trigger, start the timer */
+ if ((ctc->mode[ch] & WAITING_FOR_TRIG) && (ctc->mode[ch] & MODE) == MODE_TIMER)
+ {
+ VPRINTF(("CTC clock %f\n",1.0/clock));
+
+ if (!(ctc->notimer & (1<<ch)))
+ {
+ attotime period = ((ctc->mode[ch] & PRESCALER) == PRESCALER_16) ? ctc->period16 : ctc->period256;
+ period = attotime_mul(period, ctc->tconst[ch]);
+
+ timer_adjust(ctc->timer[ch], period, (which << 2) + ch, period);
+ }
+ else
+ timer_adjust(ctc->timer[ch], attotime_never, 0, attotime_never);
+ }
+
+ /* we're no longer waiting */
+ ctc->mode[ch] &= ~WAITING_FOR_TRIG;
+
+ /* if we're clocking externally, decrement the count */
+ if ((ctc->mode[ch] & MODE) == MODE_COUNTER)
+ {
+ ctc->down[ch]--;
+
+ /* if we hit zero, do the same thing as for a timer interrupt */
+ if (!ctc->down[ch])
+ timercallback(Machine, (which << 2) + ch);
+ }
+ }
+ }
+}
+
+WRITE8_HANDLER( z80ctc_0_trg0_w ) { z80ctc_trg_w(0, 0, data); }
+WRITE8_HANDLER( z80ctc_0_trg1_w ) { z80ctc_trg_w(0, 1, data); }
+WRITE8_HANDLER( z80ctc_0_trg2_w ) { z80ctc_trg_w(0, 2, data); }
+WRITE8_HANDLER( z80ctc_0_trg3_w ) { z80ctc_trg_w(0, 3, data); }
+WRITE8_HANDLER( z80ctc_1_trg0_w ) { z80ctc_trg_w(1, 0, data); }
+WRITE8_HANDLER( z80ctc_1_trg1_w ) { z80ctc_trg_w(1, 1, data); }
+WRITE8_HANDLER( z80ctc_1_trg2_w ) { z80ctc_trg_w(1, 2, data); }
+WRITE8_HANDLER( z80ctc_1_trg3_w ) { z80ctc_trg_w(1, 3, data); }
+
+
+
+/***************************************************************************
+ DAISY CHAIN INTERFACE
+***************************************************************************/
+
+int z80ctc_irq_state(int which)
+{
+ z80ctc *ctc = ctcs + which;
+ int state = 0;
+ int ch;
+
+ VPRINTF(("CTC IRQ state = %d%d%d%d\n", ctc->int_state[0], ctc->int_state[1], ctc->int_state[2], ctc->int_state[3]));
+
+ /* loop over all channels */
+ for (ch = 0; ch < 4; ch++)
+ {
+ /* if we're servicing a request, don't indicate more interrupts */
+ if (ctc->int_state[ch] & Z80_DAISY_IEO)
+ {
+ state |= Z80_DAISY_IEO;
+ break;
+ }
+ state |= ctc->int_state[ch];
+ }
+
+ return state;
+}
+
+
+int z80ctc_irq_ack(int which)
+{
+ z80ctc *ctc = ctcs + which;
+ int ch;
+
+ /* loop over all channels */
+ for (ch = 0; ch < 4; ch++)
+
+ /* find the first channel with an interrupt requested */
+ if (ctc->int_state[ch] & Z80_DAISY_INT)
+ {
+ VPRINTF(("CTC IRQAck ch%d\n", ch));
+
+ /* clear interrupt, switch to the IEO state, and update the IRQs */
+ ctc->int_state[ch] = Z80_DAISY_IEO;
+ interrupt_check(which);
+ return ctc->vector + ch * 2;
+ }
+
+ logerror("z80ctc_irq_ack: failed to find an interrupt to ack!\n");
+ return ctc->vector;
+}
+
+
+void z80ctc_irq_reti(int which)
+{
+ z80ctc *ctc = ctcs + which;
+ int ch;
+
+ /* loop over all channels */
+ for (ch = 0; ch < 4; ch++)
+
+ /* find the first channel with an IEO pending */
+ if (ctc->int_state[ch] & Z80_DAISY_IEO)
+ {
+ VPRINTF(("CTC IRQReti ch%d\n", ch));
+
+ /* clear the IEO state and update the IRQs */
+ ctc->int_state[ch] &= ~Z80_DAISY_IEO;
+ interrupt_check(which);
+ return;
+ }
+
+ logerror("z80ctc_irq_reti: failed to find an interrupt to clear IEO on!\n");
+}