// license:BSD-3-Clause
// copyright-holders:Angelo Salese, David Haywood
/***************************************************************************
rtc9701.c
Epson RTC-9701-JE
Serial Real Time Clock + EEPROM
***************************************************************************/
#include "emu.h"
#include "rtc9701.h"
ALLOW_SAVE_TYPE(rtc9701_device::state_t);
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
// device type definition
DEFINE_DEVICE_TYPE(RTC9701, rtc9701_device, "rtc9701", "Epson RTC-9701-JE RTC/EEPROM")
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// rtc9701_device - constructor
//-------------------------------------------------
rtc9701_device::rtc9701_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, RTC9701, tag, owner, clock)
, device_nvram_interface(mconfig, *this)
, device_rtc_interface(mconfig, *this)
, m_latch(0)
, m_reset_line(CLEAR_LINE)
, m_clock_line(CLEAR_LINE)
, rtc_state()
, cmd_stream_pos(0), current_cmd(0), rtc9701_address_pos(0), rtc9701_current_address(0), rtc9701_current_data(0), rtc9701_data_pos(0)
{
}
TIMER_CALLBACK_MEMBER(rtc9701_device::timer_callback)
{
static constexpr uint8_t dpm[12] = { 0x31, 0x28, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31 };
int dpm_count;
m_rtc.sec++;
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<<=1; m_rtc.hour = 0; }
if(m_rtc.wday & 0x80) { m_rtc.wday = 1; }
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; } //2000-2099 possible timeframe
}
//-------------------------------------------------
// device_validity_check - perform validity checks
// on this device
//-------------------------------------------------
void rtc9701_device::device_validity_check(validity_checker &valid) const
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void rtc9701_device::device_start()
{
/* let's call the timer callback every second */
m_timer = timer_alloc(FUNC(rtc9701_device::timer_callback), this);
m_timer->adjust(attotime::from_hz(clock() / XTAL(32'768)), 0, attotime::from_hz(clock() / XTAL(32'768)));
rtc_state = state_t::CMD_WAIT;
cmd_stream_pos = 0;
current_cmd = 0;
save_item(NAME(m_latch));
save_item(NAME(m_reset_line));
save_item(NAME(m_clock_line));
save_item(NAME(rtc_state));
save_item(NAME(cmd_stream_pos));
save_item(NAME(current_cmd));
save_item(NAME(rtc9701_address_pos));
save_item(NAME(rtc9701_current_address));
save_item(NAME(rtc9701_current_data));
save_item(NAME(rtc9701_data_pos));
save_item(NAME(rtc9701_data));
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));
}
//-------------------------------------------------
// rtc_clock_updated - update clock with real time
//-------------------------------------------------
void rtc9701_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 = 1 << (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);
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void rtc9701_device::device_reset()
{
}
//-------------------------------------------------
// nvram_default - called to initialize NVRAM to
// its default state
//-------------------------------------------------
void rtc9701_device::nvram_default()
{
for (auto & elem : rtc9701_data)
elem = 0xffff;
}
//-------------------------------------------------
// nvram_read - called to read NVRAM from the
// .nv file
//-------------------------------------------------
bool rtc9701_device::nvram_read(util::read_stream &file)
{
auto const [err, actual] = read(file, rtc9701_data, 0x200);
return !err && (actual == 0x200);
}
//-------------------------------------------------
// nvram_write - called to write NVRAM to the
// .nv file
//-------------------------------------------------
bool rtc9701_device::nvram_write(util::write_stream &file)
{
auto const [err, actual] = write(file, rtc9701_data, 0x200);
return !err;
}
//-------------------------------------------------
// rtc_read - used to route RTC reading registers
//-------------------------------------------------
inline uint8_t rtc9701_device::rtc_read(uint8_t offset)
{
uint8_t res;
res = 0;
switch(offset)
{
case 0: res = m_rtc.sec; break;
case 1: res = m_rtc.min; break;
case 2: res = m_rtc.hour; break;
case 3: res = m_rtc.wday; break; /* untested */
case 4: res = m_rtc.day; break;
case 5: res = m_rtc.month; break;
case 6: res = m_rtc.year & 0xff; break;
case 7: res = 0x20; break;
}
return res;
}
inline void rtc9701_device::rtc_write(uint8_t offset,uint8_t data)
{
switch(offset)
{
case 0: m_rtc.sec = data; break;
case 1: m_rtc.min = data; break;
case 2: m_rtc.hour = data; break;
case 3: m_rtc.wday = data; break; /* untested */
case 4: m_rtc.day = data; break;
case 5: m_rtc.month = data; break;
case 6: m_rtc.year = data; break;
case 7: break; // NOP
}
}
//**************************************************************************
// READ/WRITE HANDLERS
//**************************************************************************
void rtc9701_device::write_bit(int state)
{
m_latch = state;
}
int rtc9701_device::read_bit()
{
if (rtc_state == state_t::RTC_READ)
{
//printf("RTC data bits left c9701_data_pos %02x\n", rtc9701_data_pos);
return ((rtc9701_current_data) >> (rtc9701_data_pos-1))&1;
}
else if (rtc_state == state_t::EEPROM_READ)
{
//printf("EEPROM data bits left c9701_data_pos %02x\n", rtc9701_data_pos);
return ((rtc9701_current_data) >> (rtc9701_data_pos-1))&1;
}
else
{
//printf("read something else (status?) %02x\n", rtc9701_data_pos);
}
return 0;
}
void rtc9701_device::set_cs_line(int state)
{
//logerror("set reset line %d\n",state);
m_reset_line = state;
if (m_reset_line != CLEAR_LINE)
{
rtc_state = state_t::CMD_WAIT;
cmd_stream_pos = 0;
current_cmd = 0;
rtc9701_address_pos = 0;
rtc9701_current_address = 0;
rtc9701_current_data = 0;
rtc9701_data_pos = 0;
}
}
void rtc9701_device::set_clock_line(int state)
{
//logerror("set clock line %d\n",state);
if (m_reset_line == CLEAR_LINE)
{
if (state==1)
{
//logerror("write latched bit %d\n",m_latch);
switch (rtc_state)
{
case state_t::CMD_WAIT:
//logerror("xx\n");
current_cmd = (current_cmd << 1) | (m_latch&1);
cmd_stream_pos++;
if (cmd_stream_pos==4)
{
cmd_stream_pos = 0;
//logerror("Comamnd is %02x\n", current_cmd);
if (current_cmd==0x00) /* 0000 */
{
//logerror("WRITE RTC MODE\n");
rtc_state = state_t::RTC_WRITE;
cmd_stream_pos = 0;
rtc9701_address_pos = 0;
rtc9701_current_address = 0;
rtc9701_data_pos = 0;
rtc9701_current_data = 0;
}
else if (current_cmd==0x02) /* 0010 */
{
//logerror("WRITE EEPROM MODE\n");
rtc_state = state_t::EEPROM_WRITE;
cmd_stream_pos = 0;
rtc9701_address_pos = 0;
rtc9701_current_address = 0;
rtc9701_data_pos = 0;
rtc9701_current_data = 0;
}
else if (current_cmd==0x06) /* 0110 */
{
//logerror("WRITE ENABLE\n");
rtc_state = state_t::AFTER_WRITE_ENABLE;
cmd_stream_pos = 0;
}
else if (current_cmd==0x08) /* 1000 */
{
//logerror("READ RTC MODE\n");
rtc_state = state_t::RTC_READ;
cmd_stream_pos = 0;
rtc9701_address_pos = 0;
rtc9701_current_address = 0;
rtc9701_data_pos = 0;
rtc9701_current_data = 0;
}
else if (current_cmd==0x0a) /* 1010 */
{
//logerror("READ EEPROM MODE\n");
rtc_state = state_t::EEPROM_READ;
cmd_stream_pos = 0;
rtc9701_address_pos = 0;
rtc9701_current_address = 0;
rtc9701_data_pos = 0;
rtc9701_current_data = 0;
}
else
{
//logerror("RTC9701 UNKNOWN MODE\n");
}
current_cmd = 0;
}
break;
case state_t::AFTER_WRITE_ENABLE:
cmd_stream_pos++;
if (cmd_stream_pos==12)
{
cmd_stream_pos = 0;
//logerror("Written 12 bits, going back to WAIT mode\n");
rtc_state = state_t::CMD_WAIT;
}
break;
case state_t::RTC_WRITE:
cmd_stream_pos++;
if (cmd_stream_pos<=4)
{
rtc9701_address_pos++;
rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1);
if (cmd_stream_pos==4)
{
//printf("Set RTC Write Address To %04x\n", rtc9701_current_address );
}
}
if (cmd_stream_pos>4)
{
rtc9701_data_pos++;
rtc9701_current_data = (rtc9701_current_data << 1) | (m_latch&1);
}
if (cmd_stream_pos==12)
{
cmd_stream_pos = 0;
rtc_write(rtc9701_current_address,rtc9701_current_data);
//logerror("Written 12 bits, going back to WAIT mode\n");
rtc_state = state_t::CMD_WAIT;
}
break;
case state_t::EEPROM_READ:
cmd_stream_pos++;
if (cmd_stream_pos<=12)
{
rtc9701_address_pos++;
rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1);
if (cmd_stream_pos==12)
{
//printf("Set EEPROM Read Address To %04x - ", (rtc9701_current_address>>1)&0xff );
rtc9701_current_data = rtc9701_data[(rtc9701_current_address>>1)&0xff];
//printf("Setting data latch for reading to %04x\n", rtc9701_current_data);
rtc9701_data_pos = 16;
}
}
if (cmd_stream_pos>12)
{
rtc9701_data_pos--;
}
if (cmd_stream_pos==28)
{
cmd_stream_pos = 0;
// //logerror("accesed 28 bits, going back to WAIT mode\n");
// rtc_state = state_t::CMD_WAIT;
}
break;
case state_t::EEPROM_WRITE:
cmd_stream_pos++;
if (cmd_stream_pos<=12)
{
rtc9701_address_pos++;
rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1);
if (cmd_stream_pos==12)
{
//printf("Set EEPROM Write Address To %04x\n", rtc9701_current_address );
}
}
if (cmd_stream_pos>12)
{
rtc9701_data_pos++;
rtc9701_current_data = (rtc9701_current_data << 1) | (m_latch&1);
}
if (cmd_stream_pos==28)
{
cmd_stream_pos = 0;
//printf("written 28 bits - writing data %04x to %04x and going back to WAIT mode\n", rtc9701_current_data, (rtc9701_current_address>>1)&0xff);
rtc9701_data[(rtc9701_current_address>>1)&0xff] = rtc9701_current_data;
rtc_state = state_t::CMD_WAIT;
}
break;
case state_t::RTC_READ:
cmd_stream_pos++;
if (cmd_stream_pos<=4)
{
rtc9701_address_pos++;
rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1);
if (cmd_stream_pos==4)
{
//printf("Set RTC Read Address To %04x\n", rtc9701_current_address );
rtc9701_current_data = rtc_read(rtc9701_current_address);
//printf("Setting data latch for reading to %04x\n", rtc9701_current_data);
rtc9701_data_pos = 8;
}
}
if (cmd_stream_pos>4)
{
rtc9701_data_pos--;
}
if (cmd_stream_pos==12)
{
cmd_stream_pos = 0;
// //logerror("accessed 12 bits, going back to WAIT mode\n");
// rtc_state = state_t::CMD_WAIT;
}
break;
default:
break;
}
}
}
}