// license:BSD-3-Clause // copyright-holders:Angelo Salese /*************************************************************************** Seiko/Epson S-3520CF preliminary device by Angelo Salese TODO: - kludge on address? - SRAM hook-ups; - SRAM load/save; - system bits; ***************************************************************************/ #include "emu.h" #include "s3520cf.h" //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(S3520CF, s3520cf_device, "s3520cf", "Seiko Epson S-3520CF RTC") DEFINE_DEVICE_TYPE(RTC4553, rtc4553_device, "rtc4553", "Epson RTC-4553 RTC/SRAM") // functionally same as above but integrated oscillator //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // s3520cf_device - constructor //------------------------------------------------- s3520cf_device::s3520cf_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : s3520cf_device(mconfig, S3520CF, tag, owner, clock) { } s3520cf_device::s3520cf_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, type, tag, owner, clock) , device_nvram_interface(mconfig, *this) , device_rtc_interface(mconfig, *this) , m_region(*this, DEVICE_SELF) , m_dir(0), m_latch(0), m_reset_line(0), m_read_latch(0), m_bitstream(0), m_stream_pos(0), m_mode(0), m_sysr(0), m_cntrl1(0), m_cntrl2(0) { } void s3520cf_device::check_overflow() { static constexpr u8 dpm[12] = { 0x31, 0x28, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31 }; int dpm_count; if ((m_rtc.sec & 0x0f) >= 0x0a) { m_rtc.sec += 0x10; m_rtc.sec &= 0xf0; } if ((m_rtc.sec & 0xf0) >= 0x60) { m_rtc.min++; m_rtc.sec = 0; } if ((m_rtc.min & 0x0f) >= 0x0a) { m_rtc.min += 0x10; m_rtc.min &= 0xf0; } if ((m_rtc.min & 0xf0) >= 0x60) { m_rtc.hour++; m_rtc.min = 0; } if ((m_rtc.hour & 0x0f) >= 0x0a) { m_rtc.hour += 0x10; m_rtc.hour &= 0xf0; } if ((m_rtc.hour & 0xff) >= 0x24) { m_rtc.day++; m_rtc.wday++; m_rtc.hour = 0; } if (m_rtc.wday >= 7) { m_rtc.wday = 0; } if ((m_rtc.day & 0x0f) >= 0x0a) { m_rtc.day += 0x10; m_rtc.day &= 0xf0; } /* TODO: crude leap year support */ dpm_count = (m_rtc.month & 0xf) + (((m_rtc.month & 0x10) >> 4) * 10) - 1; if (((m_rtc.year % 4) == 0) && m_rtc.month == 2) { if ((m_rtc.day & 0xff) >= dpm[dpm_count] + 1 + 1) { m_rtc.month++; m_rtc.day = 0x01; } } else if ((m_rtc.day & 0xff) >= dpm[dpm_count] + 1) { m_rtc.month++; m_rtc.day = 0x01; } if ((m_rtc.month & 0x0f) >= 0x0a) { m_rtc.month = 0x10; } if (m_rtc.month >= 0x13) { m_rtc.year++; m_rtc.month = 1; } if ((m_rtc.year & 0x0f) >= 0x0a) { m_rtc.year += 0x10; m_rtc.year &= 0xf0; } if ((m_rtc.year & 0xf0) >= 0xa0) { m_rtc.year = 0; } //1901-2000 possible timeframe } TIMER_CALLBACK_MEMBER(s3520cf_device::timer_callback) { m_rtc.sec++; check_overflow(); } //------------------------------------------------- // device_validity_check - perform validity checks // on this device //------------------------------------------------- void s3520cf_device::device_validity_check(validity_checker &valid) const { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void s3520cf_device::device_start() { /* let's call the timer callback every second for now */ m_timer = timer_alloc(FUNC(s3520cf_device::timer_callback), this); m_timer->adjust(attotime::from_hz(clock() / XTAL(32'768)), 0, attotime::from_hz(clock() / XTAL(32'768))); save_item(NAME(m_dir)); save_item(NAME(m_latch)); save_item(NAME(m_reset_line)); save_item(NAME(m_read_latch)); save_item(NAME(m_bitstream)); save_item(NAME(m_stream_pos)); save_item(NAME(m_mode)); save_item(NAME(m_sysr)); save_item(NAME(m_cntrl1)); save_item(NAME(m_cntrl2)); save_item(NAME(m_rtc.sec)); save_item(NAME(m_rtc.min)); save_item(NAME(m_rtc.hour)); save_item(NAME(m_rtc.day)); save_item(NAME(m_rtc.wday)); save_item(NAME(m_rtc.month)); save_item(NAME(m_rtc.year)); save_item(NAME(m_nvdata)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void s3520cf_device::device_reset() { m_mode = 0; } //------------------------------------------------- // nvram_default - called to initialize NVRAM to // its default state //------------------------------------------------- void s3520cf_device::nvram_default() { for (auto & elem : m_nvdata) elem = 0x00; if (!m_region.found()) logerror("s3520cf(%s) region not found\n", tag()); else if (m_region->bytes() != 15) logerror("s3520cf(%s) region length 0x%x expected 0x%x\n", tag(), m_region->bytes(), 15); else memcpy(m_nvdata, m_region->base(), 15); } //------------------------------------------------- // nvram_read - called to read NVRAM from the // .nv file //------------------------------------------------- bool s3520cf_device::nvram_read(util::read_stream &file) { auto const [err, actual] = read(file, m_nvdata, 15); return !err && (actual == 15); } //------------------------------------------------- // nvram_write - called to write NVRAM to the // .nv file //------------------------------------------------- bool s3520cf_device::nvram_write(util::write_stream &file) { auto const [err, actual] = write(file, m_nvdata, 15); return !err; } void s3520cf_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) { m_rtc.day = ((day / 10)<<4) | ((day % 10) & 0xf); m_rtc.month = ((month / 10) << 4) | ((month % 10) & 0xf); m_rtc.wday = day_of_week - 1; m_rtc.year = (((year % 100)/10)<<4) | ((year % 10) & 0xf); m_rtc.hour = ((hour / 10)<<4) | ((hour % 10) & 0xf); m_rtc.min = ((minute / 10)<<4) | ((minute % 10) & 0xf); m_rtc.sec = ((second / 10)<<4) | ((second % 10) & 0xf); } //------------------------------------------------- // rtc_read - used to route RTC reading registers //------------------------------------------------- inline u8 s3520cf_device::rtc_read(u8 offset) { u8 res = 0; if (offset == 0xf) res = (m_sysr << 3) | m_mode; else if (m_mode > 1) { if (m_mode > 2) offset += 15; res = (m_nvdata[offset / 2] >> ((offset & 1) * 4)) & 0xf; } else { switch (offset) { case 0x0: res = m_rtc.sec & 0xf; break; case 0x1: res = m_rtc.sec >> 4; break; case 0x2: res = m_rtc.min & 0xf; break; case 0x3: res = m_rtc.min >> 4; break; case 0x4: res = m_rtc.hour & 0xf; break; case 0x5: res = m_rtc.hour >> 4; break; case 0x6: res = m_rtc.wday & 0xf; break; case 0x7: res = m_rtc.day & 0xf; break; case 0x8: res = m_rtc.day >> 4; break; case 0x9: res = m_rtc.month & 0xf; break; case 0xa: res = m_rtc.month >> 4; break; case 0xb: res = m_rtc.year & 0xf; break; case 0xc: res = m_rtc.year >> 4; break; case 0xd: res = m_cntrl1; break; case 0xe: res = m_cntrl2; break; } } return res; } inline void s3520cf_device::rtc_write(u8 offset,u8 data) { if(offset == 0xf) { m_mode = data & 3; m_sysr = (data & 8) >> 3; if (m_sysr) { m_rtc.wday = m_rtc.hour = m_rtc.min = m_rtc.sec = 0; m_rtc.year = m_rtc.month = m_rtc.day = 1; } } else { if (m_mode > 1) { if (m_mode > 2) offset += 15; if (offset & 1) m_nvdata[offset / 2] = (m_nvdata[offset / 2] & 0xf) | (data << 4); else m_nvdata[offset / 2] = (m_nvdata[offset / 2] & 0xf0) | (data & 0xf); } else switch (offset) { case 0x0: m_rtc.sec = (m_cntrl1 & 2) ? 0 : m_rtc.sec + 1; check_overflow(); break; case 0x1: m_rtc.sec = (m_cntrl1 & 2) ? 0 : m_rtc.sec + 0x10; check_overflow(); break; case 0x2: m_rtc.min = (m_cntrl1 & 2) ? 0 : m_rtc.min + 1; check_overflow(); break; case 0x3: m_rtc.min = (m_cntrl1 & 2) ? 0 : m_rtc.min + 0x10; check_overflow(); break; case 0x4: m_rtc.hour = (m_cntrl1 & 2) ? 0 : m_rtc.hour + 1; check_overflow(); break; case 0x5: m_rtc.hour = (m_cntrl1 & 2) ? 0 : m_rtc.hour; check_overflow(); break; case 0x6: m_rtc.wday = (m_cntrl1 & 2) ? 0 : m_rtc.wday + 1; check_overflow(); break; case 0x7: m_rtc.day = (m_cntrl1 & 2) ? 1 : m_rtc.day + 1; check_overflow(); break; case 0x8: m_rtc.day = (m_cntrl1 & 2) ? 1 : m_rtc.day + 0x10; check_overflow(); break; case 0x9: m_rtc.month = (m_cntrl1 & 2) ? 1: m_rtc.month + 1; check_overflow(); break; case 0xa: m_rtc.month = (m_cntrl1 & 2) ? 1 : m_rtc.month + 0x10; check_overflow(); break; case 0xb: m_rtc.year = (m_cntrl1 & 2) ? m_rtc.year & 0xf0 : m_rtc.year + 1; check_overflow(); break; case 0xc: m_rtc.year = (m_cntrl1 & 2) ? m_rtc.year & 0x0f : m_rtc.year + 0x10; check_overflow(); break; case 0xd: m_cntrl1 = data & 0xf; break; case 0xe: m_cntrl2 = data & 0xf; break; } } } //************************************************************************** // READ/WRITE HANDLERS //************************************************************************** int s3520cf_device::read_bit() { return m_read_latch; } void s3520cf_device::set_dir_line(int state) { //printf("%d DIR LINE\n",state); m_dir = state; } void s3520cf_device::set_cs_line(int state) { m_reset_line = state; //printf("%d CS LINE\n",state); if(m_reset_line != CLEAR_LINE) { //printf("Reset asserted\n"); m_stream_pos = 0; //m_latch = 0; // should be high impedance } } void s3520cf_device::write_bit(int state) { m_latch = state; // printf("%d LATCH LINE\n",state); } void s3520cf_device::set_clock_line(int state) { // NOTE: this device use 1-cycle (8 clocks) delayed data output if(state == 1 && m_reset_line == CLEAR_LINE) { //printf("%d %d\n",m_latch, m_dir); m_read_latch = m_bitstream & 1; m_bitstream = (m_bitstream >> 1) | ((m_latch & 1) << 7); m_stream_pos = (m_stream_pos + 1) & 7; if (m_stream_pos == 0) { u8 addr = m_bitstream & 0xf; if (m_dir == 0) // Write rtc_write(addr, m_bitstream >> 4); // Read/Verify m_bitstream = addr | (rtc_read(addr) << 4); } } } rtc4553_device::rtc4553_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : s3520cf_device(mconfig, RTC4553, tag, owner, clock) { }