summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/icd2061a.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/icd2061a.cpp')
-rw-r--r--src/devices/machine/icd2061a.cpp312
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;
+}