// 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)
{
}