diff options
Diffstat (limited to 'src/devices/machine/upd1990a.cpp')
-rw-r--r-- | src/devices/machine/upd1990a.cpp | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/src/devices/machine/upd1990a.cpp b/src/devices/machine/upd1990a.cpp new file mode 100644 index 00000000000..f40bb30af48 --- /dev/null +++ b/src/devices/machine/upd1990a.cpp @@ -0,0 +1,499 @@ +// license:BSD-3-Clause +// copyright-holders:Curt Coder, hap +/********************************************************************** + + NEC uPD1990AC Serial I/O Calendar & Clock emulation + +**********************************************************************/ + +/* + + TODO: + - test mode is mostly untested + - how does timer-interval differ from timer-pulse? + +*/ + +#include "upd1990a.h" + + + +//************************************************************************** +// MACROS / CONSTANTS +//************************************************************************** + +#define LOG 0 + + + +//************************************************************************** +// DEVICE DEFINITIONS +//************************************************************************** + +// device type definition +const device_type UPD1990A = &device_creator<upd1990a_device>; +const device_type UPD4990A = &device_creator<upd4990a_device>; + + + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +//------------------------------------------------- +// upd1990a_device - constructor +//------------------------------------------------- + +upd1990a_device::upd1990a_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, UINT32 variant, const char *shortname, const char *source) + : device_t(mconfig, type, name, tag, owner, clock, shortname, source), + device_rtc_interface(mconfig, *this), + m_write_data(*this), + m_write_tp(*this), + m_variant(variant) +{ +} + +upd1990a_device::upd1990a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, UPD1990A, "uPD1990A", tag, owner, clock, "upd1990a", __FILE__), + device_rtc_interface(mconfig, *this), + m_write_data(*this), + m_write_tp(*this), + m_variant(TYPE_1990A) +{ +} + +upd4990a_device::upd4990a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : upd1990a_device(mconfig, UPD4990A, "uPD4990A RTC", tag, owner, clock, TYPE_4990A, "upd4990a", __FILE__) { } + + +bool upd1990a_device::is_serial_mode() +{ + // uPD4990A is in serial mode if c0/1/2 = high/VDD + return (m_variant == TYPE_4990A && m_c_unlatched == 7); +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void upd1990a_device::device_start() +{ + (void)m_variant; + // resolve callbacks + m_write_data.resolve_safe(); + m_write_tp.resolve_safe(); + + // initialize + set_current_time(machine()); + + for (int i = 0; i < 7; i++) + m_shift_reg[i] = 0; + + m_oe = 0; + m_cs = 0; + m_stb = 0; + m_data_in = 0; + m_data_out = 0; + m_c = 0; + m_clk = 0; + m_tp = 0; + m_c_unlatched = 0; + m_testmode = false; + + // allocate timers + m_timer_clock = timer_alloc(TIMER_CLOCK); + m_timer_clock->adjust(attotime::from_hz(clock() / 32768.0), 0, attotime::from_hz(clock() / 32768.0)); // 1 second on XTAL_32_768kHz + m_timer_tp = timer_alloc(TIMER_TP); + m_timer_data_out = timer_alloc(TIMER_DATA_OUT); + m_timer_test_mode = timer_alloc(TIMER_TEST_MODE); + + // state saving + save_item(NAME(m_time_counter)); + save_item(NAME(m_shift_reg)); + save_item(NAME(m_oe)); + save_item(NAME(m_cs)); + save_item(NAME(m_stb)); + save_item(NAME(m_data_in)); + save_item(NAME(m_data_out)); + save_item(NAME(m_c)); + save_item(NAME(m_clk)); + save_item(NAME(m_tp)); + save_item(NAME(m_c_unlatched)); + save_item(NAME(m_testmode)); +} + + +//------------------------------------------------- +// rtc_clock_updated - +//------------------------------------------------- + +void upd1990a_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) +{ + m_time_counter[0] = convert_to_bcd(second); + m_time_counter[1] = convert_to_bcd(minute); + m_time_counter[2] = convert_to_bcd(hour); + m_time_counter[3] = convert_to_bcd(day); + m_time_counter[4] = (month << 4) | (day_of_week - 1); + m_time_counter[5] = convert_to_bcd(year); +} + + +//------------------------------------------------- +// device_timer - handler timer events +//------------------------------------------------- + +void upd1990a_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) + { + case TIMER_CLOCK: + advance_seconds(); + break; + + case TIMER_TP: + m_tp = !m_tp; + m_write_tp(m_tp); + break; + + case TIMER_DATA_OUT: + m_data_out = !m_data_out; + m_write_data(get_data_out()); + break; + + case TIMER_TEST_MODE: + if (m_oe) + { + /* TODO: completely untested */ + /* time counter is advanced from "Second" counter input */ + int max_shift = is_serial_mode() ? 6 : 5; + m_data_out = (m_time_counter[max_shift - 1] == 0); + m_write_data(get_data_out()); + + for (int i = 0; i < max_shift; i++) + { + m_time_counter[i]++; + if (m_time_counter[i] != 0) + return; + } + } + else + { + /* each counter is advanced in parallel, overflow carry does not affect next counter */ + m_data_out = 0; + + int max_shift = is_serial_mode() ? 6 : 5; + for (int i = 0; i < max_shift; i++) + { + m_time_counter[i]++; + m_data_out |= (m_time_counter[i] == 0); + } + m_write_data(get_data_out()); + } + + break; + } +} + + +//------------------------------------------------- +// stb_w - +//------------------------------------------------- + +WRITE_LINE_MEMBER( upd1990a_device::stb_w ) +{ + if (!m_cs) + return; + + if (LOG) logerror("uPD1990A '%s' STB %u\n", tag(), state); + + // rising edge + if (!m_stb && state) + { + // read command + if (is_serial_mode()) + m_c = m_shift_reg[6]; + else + { + m_c = m_c_unlatched; + if (m_c == 7) + m_c = MODE_TEST; + } + + if (LOG) logerror("uPD1990A '%s' Command %x\n", tag(), m_c); + + // common functions + if (m_c == MODE_REGISTER_HOLD || (m_c >= MODE_TP_64HZ && m_c < MODE_TEST)) + { + // enable time counter + m_timer_clock->enable(1); + + // disable testmode + m_testmode = false; + m_timer_test_mode->enable(0); + } + + switch (m_c) + { + case MODE_REGISTER_HOLD: + // 1Hz data out pulse + m_timer_data_out->adjust(attotime::zero, 0, attotime::from_hz((clock() / 32768.0) * 2.0)); + + // 64Hz time pulse + m_timer_tp->adjust(attotime::zero, 0, attotime::from_hz((clock() / 512.0) * 2.0)); + break; + + case MODE_SHIFT: + // enable time counter + if (!m_testmode) + m_timer_clock->enable(1); + + // data out LSB of shift register + m_timer_data_out->enable(0); + m_data_out = m_shift_reg[0] & 1; + m_write_data(get_data_out()); + + // 32Hz time pulse in testmode + if (m_testmode) + m_timer_tp->adjust(attotime::zero, 0, attotime::from_hz((clock() / 1024.0) * 2.0)); + + break; + + case MODE_TIME_SET: + { + // disable time counter + m_timer_clock->enable(0); + + // data out LSB of shift register + m_timer_data_out->enable(0); + m_data_out = m_shift_reg[0] & 1; + m_write_data(get_data_out()); + + // load shift register data into time counter + int max_shift = is_serial_mode() ? 6 : 5; + for (int i = 0; i < max_shift; i++) + m_time_counter[i] = m_shift_reg[i]; + + set_time(false, + bcd_to_integer(m_time_counter[5]), + m_time_counter[4] >> 4, + bcd_to_integer(m_time_counter[3]), + (m_time_counter[4] & 0xf) + 1, + bcd_to_integer(m_time_counter[2]), + bcd_to_integer(m_time_counter[1]), + bcd_to_integer(m_time_counter[0]) + ); + + // reset stage 10-15 of clock divider + m_timer_clock->adjust(attotime::from_ticks(m_timer_clock->remaining().as_ticks(clock()) % (clock() / 512), clock()), 0, attotime::from_hz(clock() / 32768.0)); + + // disable(low) time pulse in testmode + if (m_testmode) + { + m_timer_tp->enable(0); + m_tp = 0; + m_write_tp(m_tp); + } + + break; + } + + case MODE_TIME_READ: + { + // enable time counter + if (!m_testmode) + m_timer_clock->enable(1); + + // load time counter data into shift register + int max_shift = is_serial_mode() ? 6 : 5; + for (int i = 0; i < max_shift; i++) + m_shift_reg[i] = m_time_counter[i]; + + // data out pulse: uPD4990A: 1Hz, uPD1990A: 512Hz in testmode, 0.5Hz in normal mode + double div; + if (m_variant == TYPE_4990A) + div = 32768.0; + else if (m_testmode) + div = 64.0; + else div = 65536.0; + + m_timer_data_out->adjust(attotime::zero, 0, attotime::from_hz((clock() / div) * 2.0)); + + // 32Hz time pulse in testmode + if (m_testmode) + m_timer_tp->adjust(attotime::zero, 0, attotime::from_hz((clock() / 1024.0) * 2.0)); + + break; + } + + case MODE_TP_64HZ: + case MODE_TP_256HZ: + case MODE_TP_2048HZ: + case MODE_TP_4096HZ: + { + // set timer pulse + const double div[4] = { 512.0, 128.0, 16.0, 8.0 }; + m_timer_tp->adjust(attotime::zero, 0, attotime::from_hz((clock() / div[m_c - MODE_TP_64HZ]) * 2.0)); + + break; + } + + case MODE_TP_1S_INT: + case MODE_TP_10S_INT: + case MODE_TP_30S_INT: + case MODE_TP_60S_INT: + { + // set timer pulse + attotime one_second = attotime::from_hz(clock() / 32768.0); + const double mul[4] = { 1.0, 10.0, 30.0, 60.0 }; + m_timer_tp->adjust(attotime::zero, 0, one_second * mul[m_c - MODE_TP_1S_INT] / 2.0); + + break; + } + + case MODE_INT_RESET_OUTPUT: + case MODE_INT_RUN_CLOCK: + case MODE_INT_STOP_CLOCK: + // TODO + break; + + case MODE_TEST: + { + // disable time counter + m_timer_clock->enable(0); + + // disable data out pulse + m_timer_data_out->enable(0); + + // enable testmode + m_testmode = true; + m_timer_test_mode->enable(1); + const float div = (m_variant == TYPE_4990A) ? 4.0 : 32.0; // uPD4990A: 8192Hz, uPD1990A: 1024Hz + m_timer_test_mode->adjust(attotime::zero, 0, attotime::from_hz(clock() / div)); + break; + } + + default: + break; + } + } + + m_stb = state; +} + + +//------------------------------------------------- +// clk_w - +//------------------------------------------------- + +WRITE_LINE_MEMBER( upd1990a_device::clk_w ) +{ + if (!m_cs) + return; + + if (LOG) logerror("uPD1990A '%s' CLK %u\n", tag(), state); + + // rising edge + if (!m_clk && state) + { + int in = m_data_in; + + if (is_serial_mode()) + { + // always clock serial command register + in = m_shift_reg[6] & 1; + m_shift_reg[6] >>= 1; + m_shift_reg[6] |= (m_data_in << 3); + } + + if (m_c == MODE_SHIFT) + { + // clock shift register + int max_shift = is_serial_mode() ? 6 : 5; + for (int i = 0; i < max_shift; i++) + { + m_shift_reg[i] >>= 1; + if (i == (max_shift - 1)) + m_shift_reg[i] |= (in << 7); // shift in new bit + else + m_shift_reg[i] |= (m_shift_reg[i + 1] << 7 & 0x80); + } + + // data out LSB of shift register + m_data_out = m_shift_reg[0] & 1; + m_write_data(get_data_out()); + } + } + + m_clk = state; +} + + +//------------------------------------------------- +// misc input pins +//------------------------------------------------- + +WRITE_LINE_MEMBER( upd1990a_device::cs_w ) +{ + // chip select + if (LOG) logerror("uPD1990A '%s' CS %u\n", tag(), state); + m_cs = state; +} + +WRITE_LINE_MEMBER( upd1990a_device::oe_w ) +{ + // output enable + if (LOG) logerror("uPD1990A '%s' OE %u\n", tag(), state); + + int prev_oe = m_oe; + m_oe = state; + + if (m_oe != prev_oe && m_c != MODE_TEST) + m_write_data(get_data_out()); +} + +WRITE_LINE_MEMBER( upd1990a_device::c0_w ) +{ + if (LOG) logerror("uPD1990A '%s' C0 %u\n", tag(), state); + m_c_unlatched = (m_c_unlatched & 0x06) | state; +} + +WRITE_LINE_MEMBER( upd1990a_device::c1_w ) +{ + if (LOG) logerror("uPD1990A '%s' C1 %u\n", tag(), state); + m_c_unlatched = (m_c_unlatched & 0x05) | (state << 1); +} + +WRITE_LINE_MEMBER( upd1990a_device::c2_w ) +{ + if (LOG) logerror("uPD1990A '%s' C2 %u\n", tag(), state); + m_c_unlatched = (m_c_unlatched & 0x03) | (state << 2); +} + +WRITE_LINE_MEMBER( upd1990a_device::data_in_w ) +{ + // data input + if (LOG) logerror("uPD1990A '%s' DATA IN %u\n", tag(), state); + m_data_in = state; +} + + +//------------------------------------------------- +// output pins +//------------------------------------------------- + +int upd1990a_device::get_data_out() +{ + // except when in testmode, data_out is high impedance when OE is low + return (m_oe || m_testmode) ? m_data_out : 1; +} + + +READ_LINE_MEMBER( upd1990a_device::data_out_r ) +{ + return get_data_out(); +} + +READ_LINE_MEMBER( upd1990a_device::tp_r ) +{ + return m_tp; +} |