diff options
Diffstat (limited to 'trunk/src/emu/machine/6840ptm.c')
-rw-r--r-- | trunk/src/emu/machine/6840ptm.c | 728 |
1 files changed, 728 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/6840ptm.c b/trunk/src/emu/machine/6840ptm.c new file mode 100644 index 00000000000..14034d566ff --- /dev/null +++ b/trunk/src/emu/machine/6840ptm.c @@ -0,0 +1,728 @@ +/*************************************************************************** + + Motorola 6840 (PTM) + + Programmable Timer Module + + Written By J.Wallace based on previous work by Aaron Giles, + 'Re-Animator' and Mathis Rosenhauer. + + Todo: + Confirm handling for 'Single Shot' operation. + (Datasheet suggests that output starts high, going low + on timeout, opposite of continuous case) + Establish whether ptm6840_set_c? routines can replace + hard coding of external clock frequencies. + + + Operation: + The interface is arranged as follows: + + Internal Clock frequency, + Clock 1 frequency, Clock 2 frequency, Clock 3 frequency, + Clock 1 output, Clock 2 output, Clock 3 output, + IRQ function + + If the external clock frequencies are not fixed, they should be + entered as '0', and the ptm6840_set_c?(which, state) functions + should be used instead if necessary (This should allow the VBLANK + clock on the MCR units to operate). + + + 2009-06 Converted to be a device + +***************************************************************************/ + +#include "emu.h" +#include "6840ptm.h" +#include "devhelpr.h" + + +#define PTMVERBOSE 0 +#define PLOG(x) do { if (PTMVERBOSE) logerror x; } while (0) + +/*************************************************************************** + LOCAL VARIABLES +***************************************************************************/ + +const char *const ptm6840_device::opmode[] = +{ + "000 continuous mode", + "001 freq comparison mode", + "010 continuous mode", + "011 pulse width comparison mode", + "100 single shot mode", + "101 freq comparison mode", + "110 single shot mode", + "111 pulse width comparison mode" +}; + +/*************************************************************************** + IMPLEMENTATION +***************************************************************************/ + +// device type definition +const device_type PTM6840 = &device_creator<ptm6840_device>; + +//------------------------------------------------- +// ptm6840_device - constructor +//------------------------------------------------- + +ptm6840_device::ptm6840_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, PTM6840, "6840 PTM", tag, owner, clock) +{ + memset(static_cast<ptm6840_interface *>(this), 0, sizeof(ptm6840_interface)); +} + + +//------------------------------------------------- +// static_set_interface - set the interface +// struct +//------------------------------------------------- + +void ptm6840_device::static_set_interface(device_t &device, const ptm6840_interface &interface) +{ + ptm6840_device &ptm = downcast<ptm6840_device &>(device); + static_cast<ptm6840_interface &>(ptm) = interface; +} + + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void ptm6840_device::device_start() +{ + m_internal_clock = m_internal_clock; + // resolve callbacks + for (int i = 0; i < 3; i++) + { + m_out_func[i].resolve(m_out_cb[i], *this); + } + + for (int i = 0; i < 3; i++) + { + if ( m_external_clock[i] ) + { + m_external_clock[i] = m_external_clock[i]; + } + else + { + m_external_clock[i] = 1; + } + } + + + m_timer[0] = timer_alloc(0); + m_timer[1] = timer_alloc(1); + m_timer[2] = timer_alloc(2); + + for (int i = 0; i < 3; i++) + { + m_timer[i]->enable(false); + } + + m_irq_func.resolve(m_irq_cb, *this); + + // register for state saving + save_item(NAME(m_lsb_buffer)); + save_item(NAME(m_msb_buffer)); + save_item(NAME(m_status_read_since_int)); + save_item(NAME(m_status_reg)); + save_item(NAME(m_t3_divisor)); + save_item(NAME(m_t3_scaler)); + save_item(NAME(m_internal_clock)); + save_item(NAME(m_IRQ)); + + save_item(NAME(m_control_reg)); + save_item(NAME(m_output)); + save_item(NAME(m_gate)); + save_item(NAME(m_clk)); + save_item(NAME(m_mode)); + save_item(NAME(m_fired)); + save_item(NAME(m_enabled)); + save_item(NAME(m_external_clock)); + save_item(NAME(m_counter)); + save_item(NAME(m_latch)); +} + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void ptm6840_device::device_reset() +{ + m_control_reg[2] = 0; + m_control_reg[1] = 0; + m_control_reg[0] = 1; + m_status_reg = 0; + m_t3_divisor = 1; + m_status_read_since_int = 0; + m_IRQ = 0; + for (int i = 0; i < 3; i++) + { + m_counter[i] = 0xffff; + m_latch[i] = 0xffff; + m_output[i] = 0; + m_fired[i] = 0; + } +} + + +//------------------------------------------------- +// device_timer - handle timer callbacks +//------------------------------------------------- + +void ptm6840_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + timeout(id); +} + + +//------------------------------------------------- +// subtract_from_counter - Subtract from Counter +//------------------------------------------------- + +void ptm6840_device::subtract_from_counter(int counter, int count) +{ + double clock; + + // Determine the clock frequency for this timer + if (m_control_reg[counter] & 0x02) + { + clock = m_internal_clock; + } + else + { + clock = m_external_clock[counter]; + } + + // Dual-byte mode + if (m_control_reg[counter] & 0x04) + { + int lsb = m_counter[counter] & 0xff; + int msb = m_counter[counter] >> 8; + + // Count the clocks + lsb -= count; + + // Loop while we're less than zero + while (lsb < 0) + { + // Borrow from the MSB + lsb += (m_latch[counter] & 0xff) + 1; + msb--; + + // If MSB goes less than zero, we've expired + if (msb < 0) + { + timeout(counter); + msb = (m_latch[counter] >> 8) + 1; + } + } + + // Store the result + m_counter[counter] = (msb << 8) | lsb; + } + + // Word mode + else + { + int word = m_counter[counter]; + + // Count the clocks + word -= count; + + // loop while we're less than zero + while (word < 0) + { + // Borrow from the MSB + word += m_latch[counter] + 1; + + // We've expired + timeout(counter); + } + + // Store the result + m_counter[counter] = word; + } + + if (m_enabled[counter]) + { + attotime duration = attotime::from_hz(clock) * m_counter[counter]; + + if (counter == 2) + { + duration *= m_t3_divisor; + } + + m_timer[counter]->adjust(duration); + } +} + + + +//------------------------------------------------- +// tick +//------------------------------------------------- + +void ptm6840_device::tick(int counter, int count) +{ + if (counter == 2) + { + m_t3_scaler += count; + + if ( m_t3_scaler > m_t3_divisor - 1) + { + subtract_from_counter(counter, 1); + m_t3_scaler = 0; + } + } + else + { + subtract_from_counter(counter, count); + } +} + + + +//------------------------------------------------- +// update_interrupts - Update Internal Interrupts +//------------------------------------------------- + +void ptm6840_device::update_interrupts() +{ + int new_state = ((m_status_reg & 0x01) && (m_control_reg[0] & 0x40)) || + ((m_status_reg & 0x02) && (m_control_reg[1] & 0x40)) || + ((m_status_reg & 0x04) && (m_control_reg[2] & 0x40)); + + if (new_state != m_IRQ) + { + m_IRQ = new_state; + + if (m_IRQ) + { + m_status_reg |= 0x80; + } + else + { + m_status_reg &= ~0x80; + } + + m_irq_func(m_IRQ); + } +} + + + +//------------------------------------------------- +// compute_counter - Compute Counter +//------------------------------------------------- + +UINT16 ptm6840_device::compute_counter( int counter ) const +{ + double clock; + + // If there's no timer, return the count + if (!m_enabled[counter]) + { + PLOG(("MC6840 #%s: read counter(%d): %d\n", tag(), counter, m_counter[counter])); + return m_counter[counter]; + } + + // determine the clock frequency for this timer + if (m_control_reg[counter] & 0x02) + { + clock = m_internal_clock; + PLOG(("MC6840 #%s: %d internal clock freq %f \n", tag(), counter, clock)); + } + else + { + clock = m_external_clock[counter]; + PLOG(("MC6840 #%s: %d external clock freq %f \n", tag(), counter, clock)); + } + // See how many are left + int remaining = (m_timer[counter]->remaining() * clock).as_double(); + + // Adjust the count for dual byte mode + if (m_control_reg[counter] & 0x04) + { + int divisor = (m_counter[counter] & 0xff) + 1; + int msb = remaining / divisor; + int lsb = remaining % divisor; + remaining = (msb << 8) | lsb; + } + PLOG(("MC6840 #%s: read counter(%d): %d\n", tag(), counter, remaining)); + return remaining; +} + + + +//------------------------------------------------- +// reload_count - Reload Counter +//------------------------------------------------- + +void ptm6840_device::reload_count(int idx) +{ + double clock; + + // Copy the latched value in + m_counter[idx] = m_latch[idx]; + + // Determine the clock frequency for this timer + if (m_control_reg[idx] & 0x02) + { + clock = m_internal_clock; + PLOG(("MC6840 #%s: %d internal clock freq %f \n", tag(), idx, clock)); + } + else + { + clock = m_external_clock[idx]; + PLOG(("MC6840 #%s: %d external clock freq %f \n", tag(), idx, clock)); + } + + // Determine the number of clock periods before we expire + int count = m_counter[idx]; + if (m_control_reg[idx] & 0x04) + { + count = ((count >> 8) + 1) * ((count & 0xff) + 1); + } + else + { + count = count + 1; + } + + m_fired[idx] = 0; + + if ((m_mode[idx] == 4) || (m_mode[idx] == 6)) + { + m_output[idx] = 1; + if (!m_out_func[idx].isnull()) + { + m_out_func[idx](0, m_output[idx]); + } + } + + // Set the timer + PLOG(("MC6840 #%s: reload_count(%d): clock = %f count = %d\n", tag(), idx, clock, count)); + + attotime duration = attotime::from_hz(clock) * count; + if (idx == 2) + { + duration *= m_t3_divisor; + } + + PLOG(("MC6840 #%s: reload_count(%d): output = %lf\n", tag(), idx, duration.as_double())); + +#if 0 + if (!(m_control_reg[idx] & 0x02)) + { + if (!m_external_clock[idx]) + { + m_enabled[idx] = 0; + m_timer[idx]->enable(false); + } + } + else +#endif + { + m_enabled[idx] = 1; + m_timer[idx]->adjust(duration); + m_timer[idx]->enable(true); + } +} + + + +//------------------------------------------------- +// read - Read Timer +//------------------------------------------------- + +READ8_MEMBER( ptm6840_device::read ) +{ + int val; + + switch ( offset ) + { + case PTM_6840_CTRL1: + { + val = 0; + break; + } + + case PTM_6840_STATUS: + { + PLOG(("%s: MC6840 #%s: Status read = %04X\n", machine().describe_context(), tag(), m_status_reg)); + m_status_read_since_int |= m_status_reg & 0x07; + val = m_status_reg; + break; + } + + case PTM_6840_MSBBUF1: + case PTM_6840_MSBBUF2: + case PTM_6840_MSBBUF3: + { + int idx = (offset - 2) / 2; + int result = compute_counter(idx); + + // Clear the interrupt if the status has been read + if (m_status_read_since_int & (1 << idx)) + { + m_status_reg &= ~(1 << idx); + update_interrupts(); + } + + m_lsb_buffer = result & 0xff; + + PLOG(("%s: MC6840 #%s: Counter %d read = %04X\n", machine().describe_context(), tag(), idx, result >> 8)); + val = result >> 8; + break; + } + + case PTM_6840_LSB1: + case PTM_6840_LSB2: + case PTM_6840_LSB3: + { + val = m_lsb_buffer; + break; + } + + default: + { + val = 0; + break; + } + + } + return val; +} + + +//------------------------------------------------- +// write - Write Timer +//------------------------------------------------- + +WRITE8_MEMBER( ptm6840_device::write ) +{ + switch ( offset ) + { + case PTM_6840_CTRL1: + case PTM_6840_CTRL2: + { + int idx = (offset == 1) ? 1 : (m_control_reg[1] & 0x01) ? 0 : 2; + UINT8 diffs = data ^ m_control_reg[idx]; + m_t3_divisor = (m_control_reg[2] & 0x01) ? 8 : 1; + m_mode[idx] = (data >> 3) & 0x07; + m_control_reg[idx] = data; + + PLOG(("MC6840 #%s : Control register %d selected\n", tag(), idx)); + PLOG(("operation mode = %s\n", opmode[ m_mode[idx] ])); + PLOG(("value = %04X\n", m_control_reg[idx])); + PLOG(("t3divisor = %d\n", m_t3_divisor)); + + if (!(m_control_reg[idx] & 0x80 )) + { + // Output cleared + m_out_func[idx](0, 0); + } + // Reset? + if (idx == 0 && (diffs & 0x01)) + { + // Holding reset down + if (data & 0x01) + { + PLOG(("MC6840 #%s : Timer reset\n", tag())); + for (int i = 0; i < 3; i++) + { + m_timer[i]->enable(false); + m_enabled[i] = 0; + } + } + // Releasing reset + else + { + for (int i = 0; i < 3; i++) + { + reload_count(i); + } + } + + m_status_reg = 0; + update_interrupts(); + + // Changing the clock source? (e.g. Zwackery) + if (diffs & 0x02) + { + reload_count(idx); + } + } + break; + } + + case PTM_6840_MSBBUF1: + case PTM_6840_MSBBUF2: + case PTM_6840_MSBBUF3: + { + PLOG(("MC6840 #%s msbbuf%d = %02X\n", tag(), offset / 2, data)); + m_msb_buffer = data; + break; + } + + case PTM_6840_LSB1: + case PTM_6840_LSB2: + case PTM_6840_LSB3: + { + int idx = (offset - 3) / 2; + m_latch[idx] = (m_msb_buffer << 8) | (data & 0xff); + + // Clear the interrupt + m_status_reg &= ~(1 << idx); + update_interrupts(); + + // Reload the count if in an appropriate mode + if (!(m_control_reg[idx] & 0x10)) + { + reload_count(idx); + } + + PLOG(("%s:MC6840 #%s: Counter %d latch = %04X\n", machine().describe_context(), tag(), idx, m_latch[idx])); + break; + } + } +} + + +//------------------------------------------------- +// timeout - Called if timer is mature +//------------------------------------------------- + +void ptm6840_device::timeout(int idx) +{ + PLOG(("**ptm6840 %s t%d timeout**\n", tag(), idx)); + + // Set the interrupt flag + m_status_reg |= (1 << idx); + m_status_read_since_int &= ~(1 << idx); + update_interrupts(); + + if ( m_control_reg[idx] & 0x80 ) + { + if ((m_mode[idx] == 0)||(m_mode[idx] == 2)) + { + m_output[idx] = m_output[idx] ? 0 : 1; + PLOG(("**ptm6840 %s t%d output %d **\n", tag(), idx, m_output[idx])); + + m_out_func[idx](0, m_output[idx]); + } + if ((m_mode[idx] == 4)||(m_mode[idx] == 6)) + { + if (!m_fired[idx]) + { + m_output[idx] = 1; + PLOG(("**ptm6840 %s t%d output %d **\n", tag(), idx, m_output[idx])); + + m_out_func[idx](0, m_output[idx]); + + // No changes in output until reinit + m_fired[idx] = 1; + + m_status_reg |= (1 << idx); + m_status_read_since_int &= ~(1 << idx); + update_interrupts(); + } + } + } + m_enabled[idx]= 0; + reload_count(idx); +} + + +//------------------------------------------------- +// set_gate - set gate status (0 or 1) +//------------------------------------------------- + +void ptm6840_device::set_gate(int idx, int state) +{ + if ((m_mode[idx] & 1) == 0) + { + if (state == 0 && m_gate[idx]) + { + reload_count(idx); + } + } + m_gate[idx] = state; +} + +WRITE_LINE_MEMBER( ptm6840_device::set_g1 ) { set_gate(0, state); } +WRITE_LINE_MEMBER( ptm6840_device::set_g2 ) { set_gate(1, state); } +WRITE_LINE_MEMBER( ptm6840_device::set_g3 ) { set_gate(2, state); } + + +//------------------------------------------------- +// set_clock - set clock status (0 or 1) +//------------------------------------------------- + +void ptm6840_device::set_clock(int idx, int state) +{ + m_clk[idx] = state; + + if (!(m_control_reg[idx] & 0x02)) + { + if (state) + { + tick(idx, 1); + } + } +} + +WRITE_LINE_MEMBER( ptm6840_device::set_c1 ) { set_clock(0, state); } +WRITE_LINE_MEMBER( ptm6840_device::set_c2 ) { set_clock(1, state); } +WRITE_LINE_MEMBER( ptm6840_device::set_c3 ) { set_clock(2, state); } + + +//------------------------------------------------- +// set_ext_clock - set external clock frequency +//------------------------------------------------- + +void ptm6840_device::set_ext_clock(int counter, double clock) +{ + m_external_clock[counter] = clock; + + if (!(m_control_reg[counter] & 0x02)) + { + if (!m_external_clock[counter]) + { + m_enabled[counter] = 0; + m_timer[counter]->enable(false); + } + } + else + { + int count; + attotime duration; + + // Determine the number of clock periods before we expire + count = m_counter[counter]; + + if (m_control_reg[counter] & 0x04) + { + count = ((count >> 8) + 1) * ((count & 0xff) + 1); + } + else + { + count = count + 1; + } + + duration = attotime::from_hz(clock) * count; + + if (counter == 2) + { + duration *= m_t3_divisor; + } + + m_enabled[counter] = 1; + m_timer[counter]->adjust(duration); + m_timer[counter]->enable(true); + } +} |