diff options
Diffstat (limited to 'src/devices/machine/icd2061a.cpp')
| -rw-r--r-- | src/devices/machine/icd2061a.cpp | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/devices/machine/icd2061a.cpp b/src/devices/machine/icd2061a.cpp new file mode 100644 index 00000000000..62c90f72055 --- /dev/null +++ b/src/devices/machine/icd2061a.cpp @@ -0,0 +1,312 @@ +// license:BSD-3-Clause +// copyright-holders:windyfairy +/*************************************************************************** + + IC Designs 2061A Dual Programmable Graphics Clock Generator + ____ ____ + SEL0/CLK -> 1 | \_/ | 16 <- /PWRDWN + SEL1/DATA -> 2 | | 15 <- INTCLK + AVDD -> 3 | | 14 <- INIT1 + /OUTDIS -> 4 | ICD2061A | 13 <- VDD + GND -> 5 | | 12 <- INIT0 + XTALIN -> 6 | | 11 <- FEATCLK + XTALOUT <- 7 | | 10 -> /ERROUT + MCLKOUT <- 8 |___________| 9 -> VCLKOUT + + + TODO: + - Not handled: MCLKOUT_HIGH_Z, VCLKOUT_HIGH_Z, VCLKOUT_FORCED_HIGH + +***************************************************************************/ + +#include "emu.h" +#include "icd2061a.h" + +#define LOG_PINS (1 << 1) +#define LOG_STATE (1 << 2) + +// #define VERBOSE (LOG_GENERAL | LOG_PINS | LOG_STATE) + +#include "logmacro.h" + + +DEFINE_DEVICE_TYPE(ICD2061A, icd2061a_device, "icd2061a", "IC Designs 2061A Dual Programmable Graphics Clock Generator") + + +icd2061a_device::icd2061a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, ICD2061A, tag, owner, clock) + , m_vclkout_changed_cb(*this) + , m_mclkout_changed_cb(*this) + , m_errout_cb(*this) + , m_init0(0), m_init1(0) + , m_outdis(1), m_pwrdwn(1) + , m_intclk(0) +{ +} + +void icd2061a_device::device_start() +{ + save_item(NAME(m_state)); + save_item(NAME(m_unlock_step)); + save_item(NAME(m_cur_bit)); + save_item(NAME(m_data)); + save_item(NAME(m_data_prev)); + save_item(NAME(m_clk)); + save_item(NAME(m_cmd)); + + save_item(NAME(m_init0)); + save_item(NAME(m_init1)); + save_item(NAME(m_sel0)); + save_item(NAME(m_sel1)); + save_item(NAME(m_outdis)); + save_item(NAME(m_pwrdwn)); + save_item(NAME(m_intclk)); + save_item(NAME(m_vclkout_select)); + save_item(NAME(m_mclkout_select)); + + save_item(NAME(m_reg_clocks)); + save_item(NAME(m_prescale)); + save_item(NAME(m_powerdown_mode)); + save_item(NAME(m_muxref_vclkout_source)); + save_item(NAME(m_timeout_interval)); + save_item(NAME(m_muxref_adjust)); + + save_item(NAME(m_featclock)); + save_item(NAME(m_vclkout_clock)); + save_item(NAME(m_mclkout_clock)); + + m_watchdog_timer = timer_alloc(FUNC(icd2061a_device::watchdog_callback), this); + m_watchdog_timer->adjust(attotime::never); + + m_update_timer = timer_alloc(FUNC(icd2061a_device::update_clock_callback), this); + m_update_timer->adjust(attotime::never); + + m_state = CLOCKGEN_UNLOCK; + m_unlock_step = 0; + m_cur_bit = 0; + m_data = m_data_prev = 1; + m_clk = 1; + m_cmd = 0; + + std::fill(std::begin(m_prescale), std::end(m_prescale), 2); + m_powerdown_mode = 0; + m_muxref_vclkout_source = 0; + m_timeout_interval = 5; + m_muxref_adjust = 1; + m_powerdown_divisor = 8; + m_sel0 = m_sel1 = 0; + m_vclkout_select = m_mclkout_select = -1; + m_vclkout_clock = m_mclkout_clock = 0; + + // Are these values derived from the XTALIN at all? + // The manual only gives the frequencies as these values, + // not p/q/m values that could be used to derive it from XTALIN + if (m_init0 == 0 && m_init1 == 0) { + m_reg_clocks[MREG] = 32'500'000; + m_reg_clocks[REG0] = 25'175'000; + m_reg_clocks[REG1] = m_reg_clocks[REG2] = 28'322'000; + } else if (m_init0 == 0 && m_init1 == 1) { + m_reg_clocks[MREG] = 40'000'000; + m_reg_clocks[REG0] = 25'175'000; + m_reg_clocks[REG1] = m_reg_clocks[REG2] = 28'322'000; + } else if (m_init0 == 1 && m_init1 == 0) { + m_reg_clocks[MREG] = 50'350'000; + m_reg_clocks[REG0] = 40'000'000; + m_reg_clocks[REG1] = m_reg_clocks[REG2] = 28'322'000; + } else if (m_init0 == 1 && m_init1 == 1) { + m_reg_clocks[MREG] = 56'644'000; + m_reg_clocks[REG0] = 40'000'000; + m_reg_clocks[REG1] = m_reg_clocks[REG2] = 50'350'000; + } + + m_errout_cb(1); // set no error start + update_clock_callback(0); +} + +void icd2061a_device::set_featclock(const uint32_t clock) +{ + m_featclock = clock; +} + +TIMER_CALLBACK_MEMBER( icd2061a_device::watchdog_callback ) +{ + // If the timeout is hit then rearm the locked mode and accept the last values as sel0/sel1 + LOG("watchdog timed out, setting sel0 = %d, sel1 = %d\n", m_clk, m_data); + + if (m_sel0 != m_clk || m_sel1 != m_data) { + m_sel0 = m_clk; + m_sel1 = m_data; + + update_clock(); + } + + m_state = CLOCKGEN_UNLOCK; + m_unlock_step = 0; + m_unlock_step = 0; + m_cur_bit = 0; + m_cmd = 0; +} + +TIMER_CALLBACK_MEMBER( icd2061a_device::update_clock_callback ) +{ + // mclkout + if (m_outdis == 0) + m_mclkout_select = MCLKOUT_HIGH_Z; + else if (m_outdis == 1 && m_pwrdwn == 1) + m_mclkout_select = MCLKOUT_MREG; + else if (m_outdis == 1 && m_pwrdwn == 0) + m_mclkout_select = MCLKOUT_PWRDWN; + + if (m_mclkout_select == MCLKOUT_PWRDWN) { + if (m_powerdown_mode == 1 || m_powerdown_divisor == 0) + m_reg_clocks[MREG] = 0; + else + m_reg_clocks[MREG] = clock() / ((17 - m_powerdown_divisor) * 2); // 1 = divisor of 32, 15 = divisor of 4 + } else if (m_mclkout_select == MCLKOUT_MREG) { + const int a = BIT(m_regs[MREG], 21, 2); // register addr + const int p = BIT(m_regs[MREG], 10, 7) + 3; // p counter value + const int m = BIT(m_regs[MREG], 7, 3); // post-vco divisor + const int q = BIT(m_regs[MREG], 0, 7) + 2; // q counter value + m_reg_clocks[MREG] = (clock() * m_prescale[a] * (p / double(q))) / (1 << m); + } + + if (m_reg_clocks[MREG] != m_mclkout_clock) { + m_mclkout_changed_cb(m_reg_clocks[MREG]); + m_mclkout_clock = m_reg_clocks[MREG]; + } + + // vclkout + if (m_outdis == 0) + m_vclkout_select = VCLKOUT_HIGH_Z; + else if (m_outdis == 1 && m_pwrdwn == 0) + m_vclkout_select = VCLKOUT_FORCED_HIGH; + else if (m_outdis == 1 && m_pwrdwn == 1 && m_sel1 == 0 && m_sel0 == 0) + m_vclkout_select = VCLKOUT_REG0; + else if (m_outdis == 1 && m_pwrdwn == 1 && m_sel1 == 0 && m_sel0 == 1) + m_vclkout_select = VCLKOUT_REG1; + else if (m_outdis == 1 && m_pwrdwn == 1 && m_intclk == 0 && m_sel1 == 1 && m_sel0 == 0) + m_vclkout_select = VCLKOUT_FEATCLK; + else if (m_outdis == 1 && m_pwrdwn == 1 && m_sel1 == 1 && (m_intclk == 1 || m_sel0 == 1)) + m_vclkout_select = VCLKOUT_REG2; + + if (m_vclkout_select == VCLKOUT_FEATCLK) { + m_reg_clocks[m_vclkout_select] = m_featclock; + } else if (m_vclkout_select >= VCLKOUT_REG0 && m_vclkout_select <= VCLKOUT_REG2) { + const int a = BIT(m_regs[m_vclkout_select], 21, 2); // register addr + const int p = BIT(m_regs[m_vclkout_select], 10, 7) + 3; // p counter value + const int m = BIT(m_regs[m_vclkout_select], 7, 3); // post-vco divisor + const int q = BIT(m_regs[m_vclkout_select], 0, 7) + 2; // q counter value + m_reg_clocks[m_vclkout_select] = (clock() * m_prescale[a] * (p / double(q))) / (1 << m); + } + + m_vclkout_changed_cb(m_reg_clocks[m_vclkout_select]); + m_vclkout_clock = m_reg_clocks[m_vclkout_select]; +} + +void icd2061a_device::update_clock() +{ + // Set muxed clock during transition period + m_vclkout_changed_cb(m_muxref_vclkout_source ? m_mclkout_clock : clock()); + + m_watchdog_timer->adjust(attotime::never); + m_update_timer->adjust(attotime::from_msec(m_timeout_interval)); +} + +void icd2061a_device::data_w(int state) +{ + LOGMASKED(LOG_PINS, "data_w %d\n", state); + + m_watchdog_timer->adjust(attotime::from_msec(m_timeout_interval)); + + m_data = state; +} + +void icd2061a_device::clk_w(int state) +{ + LOGMASKED(LOG_PINS, "clk_w %d\n", state); + + m_watchdog_timer->adjust(attotime::from_msec(m_timeout_interval)); + + if (!m_clk && state) { + if (m_state == CLOCKGEN_UNLOCK && m_data == 1) { + // Any number of 1s can be read until the final transition with data low + m_errout_cb(1); // clear any previous errors since we're in a good state now + + m_unlock_step++; + LOGMASKED(LOG_STATE, "unlock count = %d\n", m_unlock_step); + } else if (m_state == CLOCKGEN_UNLOCK && m_data == 0 && m_unlock_step >= 5) { + // Found last part of unlock sequence, move on to start bit + m_state = CLOCKGEN_START_BIT; + m_unlock_step = 0; + LOGMASKED(LOG_STATE, "found unlock end, state = CLOCKGEN_START_BIT\n"); + } else if (m_state == CLOCKGEN_START_BIT && m_data == 0) { + // Found start bit transition, move on to data + m_state = CLOCKGEN_DATA; + m_cur_bit = 0; + m_cmd = 0; + LOGMASKED(LOG_STATE, "found start bit, state = CLOCKGEN_DATA\n"); + } else if (m_state == CLOCKGEN_DATA && m_data_prev != m_data && m_cur_bit < 24) { + // Data uses modified Manchester encoding so the data bit read on each edge must be different + // Must read exactly 24 bits of data here + m_cmd |= m_data << m_cur_bit; + LOGMASKED(LOG_STATE, "data %d %06x\n", m_cur_bit, m_cmd); + m_cur_bit++; + } else if (m_state == CLOCKGEN_DATA && m_data == 1 && m_cur_bit == 24) { + // Found end bit transition, accept data and then rearm lock + const int idx = BIT(m_cmd, 21, 3); + + if (idx == 4) { + const int p = BIT(m_cmd, 17, 4); + LOG("PWRDWN register %06x p[%d]\n", m_cmd, p); + m_powerdown_divisor = p; + } else if (idx == 6) { + const int c = BIT(m_cmd, 15, 6); + const int ps = BIT(m_cmd, 12, 3); + + m_powerdown_mode = BIT(c, 5); + m_muxref_vclkout_source = BIT(c, 4); + m_timeout_interval = 5 * (1 << BIT(c, 3)); + m_muxref_adjust = BIT(c, 1); + + m_prescale[0] = 2 << BIT(ps, 0); + m_prescale[1] = 2 << BIT(ps, 1); + m_prescale[2] = 2 << BIT(ps, 2); + + LOG("CNTL program %06x c[%d] ps[%d]\n", m_cmd, c, ps); + } else if (idx <= 3) { + const int a = BIT(m_cmd, 21, 2); // register addr + const int i = BIT(m_cmd, 17, 4); // index, used to make sure clock is in expected range + const int p = BIT(m_cmd, 10, 7) + 3; // p counter value + const int m = BIT(m_cmd, 7, 3); // post-vco divisor + const int q = BIT(m_cmd, 0, 7) + 2; // q counter value + const double outclock = (clock() * m_prescale[a] * (p / double(q))) / 1000000.0; + const double outclock_scaled = outclock / (1 << m); + + m_regs[idx] = m_cmd; + + LOG("VCO program %06x a[%d] i[%d] p[%d] m[%d] q[%d] prescale[%d] clock[%lf] clock_scaled[%lf]\n", m_cmd, a, i, p, m, q, m_prescale[a], outclock, outclock_scaled); + } else { + LOG("Unknown register selected: %06x %d\n", m_cmd, idx); + } + + m_state = CLOCKGEN_UNLOCK; + m_unlock_step = 0; + + LOGMASKED(LOG_STATE, "accepted\n"); + + update_clock(); + } else { + // Error state, rearm lock + m_state = CLOCKGEN_UNLOCK; + m_unlock_step = 0; + + m_errout_cb(0); // notify of error + + LOGMASKED(LOG_STATE, "error\n"); + } + } else { + m_data_prev = m_data; + } + + m_clk = state; +} |
