// license:BSD-3-Clause // copyright-holders:Curt Coder /********************************************************************** OKI MSM58321RS Real Time Clock/Calendar emulation **********************************************************************/ /* TODO: - leap year - test - reference registers */ #include "emu.h" #include "msm58321.h" //#define VERBOSE 1 #include "logmacro.h" // device type definition DEFINE_DEVICE_TYPE(MSM58321, msm58321_device, "msm58321", "OKI MSM58321 RTC") //************************************************************************** // MACROS / CONSTANTS //************************************************************************** // registers enum { REGISTER_S1 = 0, REGISTER_S10, REGISTER_MI1, REGISTER_MI10, REGISTER_H1, REGISTER_H10, REGISTER_W, REGISTER_D1, REGISTER_D10, REGISTER_MO1, REGISTER_MO10, REGISTER_Y1, REGISTER_Y10, REGISTER_RESET, REGISTER_REF0, REGISTER_REF1 }; static const char *reg_name(uint8_t address) { switch(address) { case REGISTER_S1: return "S1"; case REGISTER_S10: return "S10"; case REGISTER_MI1: return "MI1"; case REGISTER_MI10: return "MI10"; case REGISTER_H1: return "H1"; case REGISTER_H10: return "H10"; case REGISTER_W: return "W"; case REGISTER_D1: return "D1"; case REGISTER_D10: return "D10"; case REGISTER_MO1: return "MO1"; case REGISTER_MO10: return "MO10"; case REGISTER_Y1: return "Y1"; case REGISTER_Y10: return "Y10"; case REGISTER_RESET: return "RESET"; case REGISTER_REF0: return "REF0"; case REGISTER_REF1: return "REF1"; } return "INVALID REGISTER"; } enum { H10_PM = 4, H10_24 = 8 }; //************************************************************************** // INLINE HELPERS //************************************************************************** //------------------------------------------------- // read_counter - //------------------------------------------------- inline int msm58321_device::read_counter(int counter) { int data = m_reg[counter]; if (counter == REGISTER_H1) { int h10 = m_reg[REGISTER_H10]; if (h10 & H10_24) { data += (h10 & 3) * 10; } else { data += (h10 & 1) * 10; if (h10 & H10_PM) { if (data != 12) { data += 12; } } else if (data == 12) { data = 0; } } } else { data += (m_reg[counter + 1] * 10); } return data; } //------------------------------------------------- // write_counter - //------------------------------------------------- inline void msm58321_device::write_counter(int address, int data) { int flag = 0; switch (address) { case REGISTER_H1: flag = m_reg[REGISTER_H10] & H10_24; if (!flag) { if (data >= 12) { data -= 12; flag = H10_PM; } if (data == 0) { data = 12; } } break; case REGISTER_D1: flag = (m_reg[REGISTER_D10] & ~3); break; } m_reg[address] = data % 10; m_reg[address + 1] = (data / 10) | flag; } //------------------------------------------------- // msm58321_device - constructor //------------------------------------------------- msm58321_device::msm58321_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, MSM58321, tag, owner, clock), device_rtc_interface(mconfig, *this), device_nvram_interface(mconfig, *this), m_year0(0), m_default_24h(false), m_d0_handler(*this), m_d1_handler(*this), m_d2_handler(*this), m_d3_handler(*this), m_busy_handler(*this), m_cs2(0), m_write(0), m_read(0), m_d0_in(0), m_d0_out(0), m_d1_in(0), m_d1_out(0), m_d2_in(0), m_d2_out(0), m_d3_in(0), m_d3_out(0), m_address_write(0), m_busy(0), m_stop(0), m_test(0), m_cs1(0), m_address(0xf) { memset(m_reg, 0x00, sizeof(m_reg)); } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void msm58321_device::device_start() { // resolve callbacks m_d0_handler.resolve_safe(); m_d1_handler.resolve_safe(); m_d2_handler.resolve_safe(); m_d3_handler.resolve_safe(); m_busy_handler.resolve_safe(); // allocate timers m_clock_timer = timer_alloc(TIMER_CLOCK); m_clock_timer->adjust(clocks_to_attotime(32768), 0, clocks_to_attotime(32768)); // busy signal active period is approximately 427 µs m_busy_timer = timer_alloc(TIMER_BUSY); m_busy_timer->adjust(clocks_to_attotime(32768 - 14), 0, clocks_to_attotime(32768)); // state saving save_item(NAME(m_cs2)); save_item(NAME(m_write)); save_item(NAME(m_read)); save_item(NAME(m_d0_in)); save_item(NAME(m_d0_out)); save_item(NAME(m_d1_in)); save_item(NAME(m_d1_out)); save_item(NAME(m_d2_in)); save_item(NAME(m_d2_out)); save_item(NAME(m_d3_in)); save_item(NAME(m_d3_out)); save_item(NAME(m_address_write)); save_item(NAME(m_busy)); save_item(NAME(m_stop)); save_item(NAME(m_test)); save_item(NAME(m_cs1)); save_item(NAME(m_address)); save_item(NAME(m_reg)); } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void msm58321_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TIMER_CLOCK: if (!m_stop) advance_seconds(); if (!m_busy) { m_busy = 1; m_busy_handler(m_busy); } break; case TIMER_BUSY: if (!m_cs1 || !m_cs2 || !m_write || m_address != REGISTER_RESET) { m_busy = 0; m_busy_handler(m_busy); } break; } } //------------------------------------------------- // rtc_clock_updated - //------------------------------------------------- void msm58321_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) { write_counter(REGISTER_Y1, (year - m_year0) % 100); write_counter(REGISTER_MO1, month); write_counter(REGISTER_D1, day); m_reg[REGISTER_W] = day_of_week; write_counter(REGISTER_H1, hour); write_counter(REGISTER_MI1, minute); write_counter(REGISTER_S1, second); update_output(); } //------------------------------------------------- // nvram_default - called to initialize NVRAM to // its default state //------------------------------------------------- void msm58321_device::nvram_default() { for (auto & elem : m_reg) elem = 0; if (m_default_24h) m_reg[REGISTER_H10] = H10_24; clock_updated(); } //------------------------------------------------- // nvram_read - called to read NVRAM from the // .nv file //------------------------------------------------- void msm58321_device::nvram_read(emu_file &file) { file.read(m_reg, sizeof(m_reg)); clock_updated(); } //------------------------------------------------- // nvram_write - called to write NVRAM to the // .nv file //------------------------------------------------- void msm58321_device::nvram_write(emu_file &file) { file.write(m_reg, sizeof(m_reg)); } //------------------------------------------------- // update_output - //------------------------------------------------- void msm58321_device::update_output() { uint8_t data = 0xf; if (m_cs1 && m_cs2 && m_read) { switch (m_address) { case REGISTER_RESET: data = 0; break; case REGISTER_REF0: case REGISTER_REF1: // TODO: output reference values data = 0; break; default: data = m_reg[m_address]; break; } LOG("MSM58321 Register Read %s (%01x): %01x\n", reg_name(m_address), m_address, data & 0x0f); } int d0 = (data >> 0) & 1; if (m_d0_out != d0) { m_d0_out = d0; m_d0_handler(d0); } int d1 = (data >> 1) & 1; if (m_d1_out != d1) { m_d1_out = d1; m_d1_handler(d1); } int d2 = (data >> 2) & 1; if (m_d2_out != d2) { m_d2_out = d2; m_d2_handler(d2); } int d3 = (data >> 3) & 1; if (m_d3_out != d3) { m_d3_out = d3; m_d3_handler(d3); } } //------------------------------------------------- // update_input() - //------------------------------------------------- void msm58321_device::update_input() { if (m_cs1 && m_cs2) { uint8_t data = m_d0_in | (m_d1_in << 1) | (m_d2_in << 2) | (m_d3_in << 3); if (m_address_write) { LOG("MSM58321 Latch Address %01x\n", data); // latch address m_address = data; } if (m_write) { switch(m_address) { case REGISTER_RESET: LOG("MSM58321 Reset\n"); if (!m_busy) { m_busy = 1; m_busy_handler(m_busy); } break; case REGISTER_REF0: case REGISTER_REF1: LOG("MSM58321 Reference Signal\n"); break; default: LOG("MSM58321 Register Write %s (%01x): %01x\n", reg_name(m_address), m_address, data); switch (m_address) { case REGISTER_S10: case REGISTER_MI10: case REGISTER_W: m_reg[m_address] = data & 7; break; case REGISTER_H10: if (data & H10_24) { // "When D3 = 1 is written, the D2 bit is reset inside the IC." // but it doesn't say if this is done immediately or on the next update m_reg[m_address] = data & ~H10_PM; } else { m_reg[m_address] = data; } break; case REGISTER_MO10: m_reg[m_address] = data & 1; break; default: m_reg[m_address] = data; break; } set_time(false, read_counter(REGISTER_Y1) + m_year0, read_counter(REGISTER_MO1), read_counter(REGISTER_D1), m_reg[REGISTER_W], read_counter(REGISTER_H1), read_counter(REGISTER_MI1), read_counter(REGISTER_S1)); break; } } } } //------------------------------------------------- // cs2_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::cs2_w ) { if (m_cs2 != state) { LOG("MSM58321 CS2: %u\n", state); m_cs2 = state; update_input(); } } //------------------------------------------------- // write_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::write_w ) { if (m_write != state) { LOG("MSM58321 WRITE: %u\n", state); m_write = state; update_input(); } } //------------------------------------------------- // read_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::read_w ) { if (m_read != state) { LOG("MSM58321 READ: %u\n", state); m_read = state; update_output(); } } //------------------------------------------------- // d0_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::d0_w ) { if (m_d0_in != state) { m_d0_in = state; update_input(); } } //------------------------------------------------- // d1_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::d1_w ) { if (m_d1_in != state) { m_d1_in = state; update_input(); } } //------------------------------------------------- // d2_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::d2_w ) { if (m_d2_in != state) { m_d2_in = state; update_input(); } } //------------------------------------------------- // d3_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::d3_w ) { if (m_d3_in != state) { m_d3_in = state; update_input(); } } //------------------------------------------------- // address_write_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::address_write_w ) { if (m_address_write != state) { LOG("MSM58321 ADDRESS WRITE: %u\n", state); m_address_write = state; update_input(); } } //------------------------------------------------- // stop_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::stop_w ) { if (m_stop != state) { LOG("MSM58321 STOP: %u\n", state); m_stop = state; } } //------------------------------------------------- // test_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::test_w ) { if (m_test != state) { LOG("MSM58321 TEST: %u\n", state); m_test = state; } } //------------------------------------------------- // cs1_w - //------------------------------------------------- WRITE_LINE_MEMBER( msm58321_device::cs1_w ) { if (m_cs1 != state) { LOG("MSM58321 CS1: %u\n", state); m_cs1 = state; update_input(); } }