diff options
Diffstat (limited to 'trunk/src/emu/machine/z80ctc.c')
-rw-r--r-- | trunk/src/emu/machine/z80ctc.c | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/z80ctc.c b/trunk/src/emu/machine/z80ctc.c new file mode 100644 index 00000000000..3b66cfcaf41 --- /dev/null +++ b/trunk/src/emu/machine/z80ctc.c @@ -0,0 +1,526 @@ +/*************************************************************************** + + Z80 CTC (Z8430) implementation + + based on original version (c) 1997, Tatsuyuki Satoh + + Copyright Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "emu.h" +#include "z80ctc.h" +#include "cpu/z80/z80.h" +#include "cpu/z80/z80daisy.h" + + + +//************************************************************************** +// DEBUGGING +//************************************************************************** + +#define VERBOSE 0 + +#define VPRINTF(x) do { if (VERBOSE) logerror x; } while (0) + + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +// these are the bits of the incoming commands to the CTC +const int INTERRUPT = 0x80; +const int INTERRUPT_ON = 0x80; +const int INTERRUPT_OFF = 0x00; + +const int MODE = 0x40; +const int MODE_TIMER = 0x00; +const int MODE_COUNTER = 0x40; + +const int PRESCALER = 0x20; +const int PRESCALER_256 = 0x20; +const int PRESCALER_16 = 0x00; + +const int EDGE = 0x10; +const int EDGE_FALLING = 0x00; +const int EDGE_RISING = 0x10; + +const int TRIGGER = 0x08; +const int TRIGGER_AUTO = 0x00; +const int TRIGGER_CLOCK = 0x08; + +const int CONSTANT = 0x04; +const int CONSTANT_LOAD = 0x04; +const int CONSTANT_NONE = 0x00; + +const int RESET = 0x02; +const int RESET_CONTINUE = 0x00; +const int RESET_ACTIVE = 0x02; + +const int CONTROL = 0x01; +const int CONTROL_VECTOR = 0x00; +const int CONTROL_WORD = 0x01; + +// these extra bits help us keep things accurate +const int WAITING_FOR_TRIG = 0x100; + + + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +// device type definition +const device_type Z80CTC = &device_creator<z80ctc_device>; + +//------------------------------------------------- +// z80ctc_device - constructor +//------------------------------------------------- + +z80ctc_device::z80ctc_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, Z80CTC, "Zilog Z80 CTC", tag, owner, clock), + device_z80daisy_interface(mconfig, *this) +{ +} + + +//------------------------------------------------- +// device_config_complete - perform any +// operations now that the configuration is +// complete +//------------------------------------------------- + +void z80ctc_device::device_config_complete() +{ + // inherit a copy of the static data + const z80ctc_interface *intf = reinterpret_cast<const z80ctc_interface *>(static_config()); + if (intf != NULL) + *static_cast<z80ctc_interface *>(this) = *intf; + + // or initialize to defaults if none provided + else + { + m_notimer = 0; + memset(&m_intr_cb, 0, sizeof(m_intr_cb)); + memset(&m_zc0_cb, 0, sizeof(m_zc0_cb)); + memset(&m_zc1_cb, 0, sizeof(m_zc1_cb)); + memset(&m_zc2_cb, 0, sizeof(m_zc2_cb)); + } +} + + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void z80ctc_device::device_start() +{ + m_period16 = attotime::from_hz(m_clock) * 16; + m_period256 = attotime::from_hz(m_clock) * 256; + + // resolve callbacks + m_intr.resolve(m_intr_cb, *this); + + // start each channel + devcb_write_line nullcb = DEVCB_NULL; + m_channel[0].start(this, 0, (m_notimer & NOTIMER_0) != 0, m_zc0_cb); + m_channel[1].start(this, 1, (m_notimer & NOTIMER_1) != 0, m_zc1_cb); + m_channel[2].start(this, 2, (m_notimer & NOTIMER_2) != 0, m_zc2_cb); + m_channel[3].start(this, 3, (m_notimer & NOTIMER_3) != 0, nullcb); + + // register for save states + save_item(NAME(m_vector)); +} + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void z80ctc_device::device_reset() +{ + // reset each channel + m_channel[0].reset(); + m_channel[1].reset(); + m_channel[2].reset(); + m_channel[3].reset(); + + // check for interrupts + interrupt_check(); + VPRINTF(("CTC Reset\n")); +} + + + +//************************************************************************** +// DAISY CHAIN INTERFACE +//************************************************************************** + +//------------------------------------------------- +// z80daisy_irq_state - return the overall IRQ +// state for this device +//------------------------------------------------- + +int z80ctc_device::z80daisy_irq_state() +{ + VPRINTF(("CTC IRQ state = %d%d%d%d\n", m_channel[0].m_int_state, m_channel[1].m_int_state, m_channel[2].m_int_state, m_channel[3].m_int_state)); + + // loop over all channels + int state = 0; + for (int ch = 0; ch < 4; ch++) + { + ctc_channel &channel = m_channel[ch]; + + // if we're servicing a request, don't indicate more interrupts + if (channel.m_int_state & Z80_DAISY_IEO) + { + state |= Z80_DAISY_IEO; + break; + } + state |= channel.m_int_state; + } + + return state; +} + + +//------------------------------------------------- +// z80daisy_irq_ack - acknowledge an IRQ and +// return the appropriate vector +//------------------------------------------------- + +int z80ctc_device::z80daisy_irq_ack() +{ + // loop over all channels + for (int ch = 0; ch < 4; ch++) + { + ctc_channel &channel = m_channel[ch]; + + // find the first channel with an interrupt requested + if (channel.m_int_state & Z80_DAISY_INT) + { + VPRINTF(("CTC IRQAck ch%d\n", ch)); + + // clear interrupt, switch to the IEO state, and update the IRQs + channel.m_int_state = Z80_DAISY_IEO; + interrupt_check(); + return m_vector + ch * 2; + } + } + + logerror("z80ctc_irq_ack: failed to find an interrupt to ack!\n"); + return m_vector; +} + + +//------------------------------------------------- +// z80daisy_irq_reti - clear the interrupt +// pending state to allow other interrupts through +//------------------------------------------------- + +void z80ctc_device::z80daisy_irq_reti() +{ + // loop over all channels + for (int ch = 0; ch < 4; ch++) + { + ctc_channel &channel = m_channel[ch]; + + // find the first channel with an IEO pending + if (channel.m_int_state & Z80_DAISY_IEO) + { + VPRINTF(("CTC IRQReti ch%d\n", ch)); + + // clear the IEO state and update the IRQs + channel.m_int_state &= ~Z80_DAISY_IEO; + interrupt_check(); + return; + } + } + + logerror("z80ctc_irq_reti: failed to find an interrupt to clear IEO on!\n"); +} + + + +//************************************************************************** +// INTERNAL STATE MANAGEMENT +//************************************************************************** + +//------------------------------------------------- +// interrupt_check - look for pending interrupts +// and update the line +//------------------------------------------------- + +void z80ctc_device::interrupt_check() +{ + int state = (z80daisy_irq_state() & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE; + m_intr(state); +} + + + +//************************************************************************* +// CTC CHANNELS +//************************************************************************** + +//------------------------------------------------- +// ctc_channel - constructor +//------------------------------------------------- + +z80ctc_device::ctc_channel::ctc_channel() + : m_notimer(false), + m_mode(0), + m_tconst(0), + m_down(0), + m_extclk(0), + m_timer(NULL), + m_int_state(0) +{ + memset(&m_zc, 0, sizeof(m_zc)); +} + + +//------------------------------------------------- +// start - set up at device start time +//------------------------------------------------- + +void z80ctc_device::ctc_channel::start(z80ctc_device *device, int index, bool notimer, const devcb_write_line &write_line) +{ + // initialize state + m_device = device; + m_index = index; + m_zc.resolve(write_line, *m_device); + m_notimer = notimer; + m_timer = m_device->machine().scheduler().timer_alloc(FUNC(static_timer_callback), this); + + // register for save states + m_device->save_item(NAME(m_mode), m_index); + m_device->save_item(NAME(m_tconst), m_index); + m_device->save_item(NAME(m_down), m_index); + m_device->save_item(NAME(m_extclk), m_index); + m_device->save_item(NAME(m_int_state), m_index); +} + + +//------------------------------------------------- +// reset - reset the channel +//------------------------------------------------- + +void z80ctc_device::ctc_channel::reset() +{ + m_mode = RESET_ACTIVE; + m_tconst = 0x100; + m_timer->adjust(attotime::never); + m_int_state = 0; +} + + +//------------------------------------------------- +// period - return the current channel's period +//------------------------------------------------- + +attotime z80ctc_device::ctc_channel::period() const +{ + // if reset active, no period + if ((m_mode & RESET) == RESET_ACTIVE) + return attotime::zero; + + // if counter mode, no real period + if ((m_mode & MODE) == MODE_COUNTER) + { + logerror("CTC %d is CounterMode : Can't calculate period\n", m_index); + return attotime::zero; + } + + // compute the period + attotime period = ((m_mode & PRESCALER) == PRESCALER_16) ? m_device->m_period16 : m_device->m_period256; + return period * m_tconst; +} + + +//------------------------------------------------- +// read - read the channel's state +//------------------------------------------------- + +UINT8 z80ctc_device::ctc_channel::read() +{ + // if we're in counter mode, just return the count + if ((m_mode & MODE) == MODE_COUNTER || (m_mode & WAITING_FOR_TRIG)) + return m_down; + + // else compute the down counter value + else + { + attotime period = ((m_mode & PRESCALER) == PRESCALER_16) ? m_device->m_period16 : m_device->m_period256; + + VPRINTF(("CTC clock %f\n",ATTOSECONDS_TO_HZ(period.attoseconds))); + + if (m_timer != NULL) + return ((int)(m_timer->remaining().as_double() / period.as_double()) + 1) & 0xff; + else + return 0; + } +} + + +//------------------------------------------------- +// write - handle writes to a channel +//------------------------------------------------- + +void z80ctc_device::ctc_channel::write(UINT8 data) +{ + // if we're waiting for a time constant, this is it + if ((m_mode & CONSTANT) == CONSTANT_LOAD) + { + VPRINTF(("CTC ch.%d constant = %02x\n", m_index, data)); + + // set the time constant (0 -> 0x100) + m_tconst = data ? data : 0x100; + + // clear the internal mode -- we're no longer waiting + m_mode &= ~CONSTANT; + + // also clear the reset, since the constant gets it going again + m_mode &= ~RESET; + + // if we're in timer mode.... + if ((m_mode & MODE) == MODE_TIMER) + { + // if we're triggering on the time constant, reset the down counter now + if ((m_mode & TRIGGER) == TRIGGER_AUTO) + { + if (!m_notimer) + { + attotime curperiod = period(); + m_timer->adjust(curperiod, m_index, curperiod); + } + else + m_timer->adjust(attotime::never); + } + + // else set the bit indicating that we're waiting for the appropriate trigger + else + m_mode |= WAITING_FOR_TRIG; + } + + // also set the down counter in case we're clocking externally + m_down = m_tconst; + } + + // 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) + else if ((data & CONTROL) == CONTROL_VECTOR && (m_index & 1) == 0) +#else + else if ((data & CONTROL) == CONTROL_VECTOR && m_index == 0) +#endif + { + m_device->m_vector = data & 0xf8; + logerror("CTC Vector = %02x\n", m_device->m_vector); + } + + // this must be a control word + else if ((data & CONTROL) == CONTROL_WORD) + { + // set the new mode + m_mode = data; + VPRINTF(("CTC ch.%d mode = %02x\n", m_index, data)); + + // if we're being reset, clear out any pending timers for this channel + if ((data & RESET) == RESET_ACTIVE) + { + m_timer->adjust(attotime::never); + // note that we don't clear the interrupt state here! + } + } +} + + +//------------------------------------------------- +// trigger - clock this channel and handle any +// side-effects +//------------------------------------------------- + +void z80ctc_device::ctc_channel::trigger(UINT8 data) +{ + // normalize data + data = data ? 1 : 0; + + // see if the trigger value has changed + if (data != m_extclk) + { + m_extclk = data; + + // see if this is the active edge of the trigger + if (((m_mode & EDGE) == EDGE_RISING && data) || ((m_mode & EDGE) == EDGE_FALLING && !data)) + { + // if we're waiting for a trigger, start the timer + if ((m_mode & WAITING_FOR_TRIG) && (m_mode & MODE) == MODE_TIMER) + { + if (!m_notimer) + { + attotime curperiod = period(); + VPRINTF(("CTC period %s\n", curperiod.as_string())); + m_timer->adjust(curperiod, m_index, curperiod); + } + else + { + VPRINTF(("CTC disabled\n")); + m_timer->adjust(attotime::never); + } + } + + // we're no longer waiting + m_mode &= ~WAITING_FOR_TRIG; + + // if we're clocking externally, decrement the count + if ((m_mode & MODE) == MODE_COUNTER) + { + // if we hit zero, do the same thing as for a timer interrupt + if (--m_down == 0) + timer_callback(); + } + } + } +} + + +//------------------------------------------------- +// trigger - clock this channel and handle any +// side-effects +//------------------------------------------------- + +void z80ctc_device::ctc_channel::timer_callback() +{ + // down counter has reached zero - see if we should interrupt + if ((m_mode & INTERRUPT) == INTERRUPT_ON) + { + m_int_state |= Z80_DAISY_INT; + VPRINTF(("CTC timer ch%d\n", m_index)); + m_device->interrupt_check(); + } + + // generate the clock pulse + m_zc(1); + m_zc(0); + + // reset the down counter + m_down = m_tconst; +} + + + +//************************************************************************** +// GLOBAL STUBS +//************************************************************************** + +WRITE8_DEVICE_HANDLER( z80ctc_w ) { downcast<z80ctc_device *>(device)->write(offset & 3, data); } +READ8_DEVICE_HANDLER( z80ctc_r ) { return downcast<z80ctc_device *>(device)->read(offset & 3); } + +WRITE_LINE_DEVICE_HANDLER( z80ctc_trg0_w ) { downcast<z80ctc_device *>(device)->trigger(0, state); } +WRITE_LINE_DEVICE_HANDLER( z80ctc_trg1_w ) { downcast<z80ctc_device *>(device)->trigger(1, state); } +WRITE_LINE_DEVICE_HANDLER( z80ctc_trg2_w ) { downcast<z80ctc_device *>(device)->trigger(2, state); } +WRITE_LINE_DEVICE_HANDLER( z80ctc_trg3_w ) { downcast<z80ctc_device *>(device)->trigger(3, state); } |