// 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 "emu.h" #include "upd1990a.h" //#define VERBOSE 1 #include "logmacro.h" //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(UPD1990A, upd1990a_device, "upd1990a", "uPD1990A RTC") DEFINE_DEVICE_TYPE(UPD4990A, upd4990a_device, "upd4990a", "uPD4990A RTC") //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // upd1990a_device - constructor //------------------------------------------------- upd1990a_device::upd1990a_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t variant) : device_t(mconfig, type, tag, owner, clock) , 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_t clock) : upd1990a_device(mconfig, UPD1990A, tag, owner, clock, TYPE_1990A) { } upd4990a_device::upd4990a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : upd1990a_device(mconfig, UPD4990A, tag, owner, clock, TYPE_4990A) { } 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(); for (auto & elem : m_shift_reg) elem = 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'768) 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; LOG("uPD1990A STB %u\n", 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; } LOG("uPD1990A Command %x\n", 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; LOG("uPD1990A CLK %u\n", 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 LOG("uPD1990A CS %u\n", state); m_cs = state; } WRITE_LINE_MEMBER( upd1990a_device::oe_w ) { // output enable LOG("uPD1990A OE %u\n", 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 ) { LOG("uPD1990A C0 %u\n", state); m_c_unlatched = (m_c_unlatched & 0x06) | state; } WRITE_LINE_MEMBER( upd1990a_device::c1_w ) { LOG("uPD1990A C1 %u\n", state); m_c_unlatched = (m_c_unlatched & 0x05) | (state << 1); } WRITE_LINE_MEMBER( upd1990a_device::c2_w ) { LOG("uPD1990A C2 %u\n", state); m_c_unlatched = (m_c_unlatched & 0x03) | (state << 2); } WRITE_LINE_MEMBER( upd1990a_device::data_in_w ) { // data input LOG("uPD1990A DATA IN %u\n", 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; }