// license:BSD-3-Clause
// copyright-holders:Raphael Nabet, R. Belmont
/*
rtc65271 emulation
This chip is an RTC for computer built by Epson and Spezial-Electronic (I
think SE is the second source here).
Reference:
* Realtime Clock Module RTC-65271 Application Manual
<http://www.bgmicro.com/pdf/rtc65271.pdf>
Todo:
* Support square wave pin output?
* Support DSE mode?
Raphael Nabet, 2003-2004
R. Belmont, 2012
*/
#include "emu.h"
#include "rtc65271.h"
/* Delay between the beginning (UIP asserted) and the end (UIP cleared and
update interrupt asserted) of the update cycle */
#define UPDATE_CYCLE_TIME attotime::from_usec(1984)
/* Delay between the assertion of UIP and the effective start of the update
cycle */
/*#define UPDATE_CYCLE_DELAY attotime::from_usec(244)*/
enum
{
reg_second = 0,
reg_alarm_second,
reg_minute,
reg_alarm_minute,
reg_hour,
reg_alarm_hour,
reg_weekday,
reg_monthday,
reg_month,
reg_year,
reg_A,
reg_B,
reg_C,
reg_D
};
enum
{
reg_A_UIP = 0x80,
reg_A_DV = 0x70,
reg_A_RS = 0x0F,
reg_B_SET = 0x80,
reg_B_PIE = 0x40,
reg_B_AIE = 0x20,
reg_B_UIE = 0x10,
reg_B_SQW = 0x08,
reg_B_DM = 0x04,
reg_B_24h = 0x02,
reg_B_DSE = 0x01,
reg_C_IRQF = 0x80,
reg_C_PF = 0x40,
reg_C_AF = 0x20,
reg_C_UF = 0x10,
reg_D_VRT = 0x80
};
static const int SQW_freq_table[16] =
{
0,
256,
128,
8192,
4096,
2048,
1024,
512,
256,
128,
64,
32,
16,
8,
4,
2,
};
/*
BCD utilities
*/
/*
Increment a binary-encoded uint8_t
*/
static uint8_t increment_binary(uint8_t data)
{
return data+1;
}
/*
Increment a BCD-encoded uint8_t
*/
static uint8_t increment_BCD(uint8_t data)
{
if ((data & 0x0f) < 0x09)
{
if ((data & 0xf0) < 0xa0)
data++;
else
data = data + 0x01 - 0xa0;
}
else
{
if ((data & 0xf0) < 0xa0)
data = data - 0x09 + 0x10;
else
data = data - 0x09 - 0x90;
}
return data;
}
/*
Convert a binary-encoded uint8_t to BCD
*/
static uint8_t binary_to_BCD(uint8_t data)
{
data %= 100;
return ((data / 10) << 4) | (data %10);
}
/*
Convert a BCD-encoded uint8_t to binary
*/
static uint8_t BCD_to_binary(uint8_t data)
{
if ((data & 0x0f) >= 0x0a)
data = data - 0x0a + 0x10;
if ((data & 0xf0) >= 0xa0)
data = data - 0xa0;
return (data & 0x0f) + (((data & 0xf0) >> 4) * 10);
}
/*
Public functions
*/
//-------------------------------------------------
// nvram_default - called to initialize NVRAM to
// its default state
//-------------------------------------------------
void rtc65271_device::nvram_default()
{
memset(m_regs,0, sizeof(m_regs));
memset(m_xram,0, sizeof(m_xram));
m_regs[reg_B] |= reg_B_DM; // Firebeat assumes the chip factory defaults to non-BCD mode (or maybe Konami programs it that way?)
}
//-------------------------------------------------
// nvram_read - called to read NVRAM from the
// .nv file
//-------------------------------------------------
void rtc65271_device::nvram_read(emu_file &file)
{
uint8_t buf;
/* version flag */
if (file.read(&buf, 1) != 1)
return;
if (buf != 0)
return;
/* control registers */
if (file.read(&buf, 1) != 1)
return;
m_regs[reg_A] = buf & (reg_A_DV /*| reg_A_RS*/);
if (file.read(&buf, 1) != 1)
return;
m_regs[reg_B] = buf & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE);
/* alarm registers */
if (file.read(&m_regs[reg_alarm_second], 1) != 1)
return;
if (file.read(&m_regs[reg_alarm_minute], 1) != 1)
return;
if (file.read(&m_regs[reg_alarm_hour], 1) != 1)
return;
/* user RAM */
if (file.read(m_regs+14, 50) != 50)
return;
/* extended RAM */
if (file.read(m_xram, 4096) != 4096)
return;
m_regs[reg_D] |= reg_D_VRT; /* the data was backed up successfully */
/*m_dirty = false;*/
{
system_time systime;
/* get the current date/time from the core */
machine().current_datetime(systime);
/* set clock registers */
m_regs[reg_second] = systime.local_time.second;
m_regs[reg_minute] = systime.local_time.minute;
if (m_regs[reg_B] & reg_B_24h)
/* 24-hour mode */
m_regs[reg_hour] = systime.local_time.hour;
else
{ /* 12-hour mode */
if (systime.local_time.hour >= 12)
{
m_regs[reg_hour] = 0x80;
systime.local_time.hour -= 12;
}
else
{
m_regs[reg_hour] = 0;
}
// Firebeat indicates non-BCD 12-hour mode has 0-based hour, so 12 AM is 0x00 and 12 PM is 0x80
m_regs[reg_hour] |= systime.local_time.hour; // ? systime.local_time.hour : 12;
}
m_regs[reg_weekday] = systime.local_time.weekday + 1;
m_regs[reg_monthday] = systime.local_time.mday;
m_regs[reg_month] = systime.local_time.month + 1;
m_regs[reg_year] = systime.local_time.year % 100;
if (! (m_regs[reg_B] & reg_B_DM))
{ /* BCD mode */
m_regs[reg_second] = binary_to_BCD(m_regs[reg_second]);
m_regs[reg_minute] = binary_to_BCD(m_regs[reg_minute]);
m_regs[reg_hour] = (m_regs[reg_hour] & 0x80) | binary_to_BCD(m_regs[reg_hour] & 0x7f);
/*m_regs[reg_weekday] = binary_to_BCD(m_regs[reg_weekday]);*/
m_regs[reg_monthday] = binary_to_BCD(m_regs[reg_monthday]);
m_regs[reg_month] = binary_to_BCD(m_regs[reg_month]);
m_regs[reg_year] = binary_to_BCD(m_regs[reg_year]);
}
}
}
//-------------------------------------------------
// nvram_write - called to write NVRAM to the
// .nv file
//-------------------------------------------------
void rtc65271_device::nvram_write(emu_file &file)
{
uint8_t buf;
/* version flag */
buf = 0;
if (file.write(& buf, 1) != 1)
return;
/* control registers */
buf = m_regs[reg_A] & (reg_A_DV | reg_A_RS);
if (file.write(&buf, 1) != 1)
return;
buf = m_regs[reg_B] & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE);
if (file.write(&buf, 1) != 1)
return;
/* alarm registers */
if (file.write(&m_regs[reg_alarm_second], 1) != 1)
return;
if (file.write(&m_regs[reg_alarm_minute], 1) != 1)
return;
if (file.write(&m_regs[reg_alarm_hour], 1) != 1)
return;
/* user RAM */
if (file.write(m_regs+14, 50) != 50)
return;
/* extended RAM */
if (file.write(m_xram, 4096) != 4096)
return;
}
/*
Read a byte from clock
xramsel: select RTC register if 0, XRAM if 1
offset: address (A0-A5 pins)
*/
uint8_t rtc65271_device::read(int xramsel, offs_t offset)
{
int reply;
if (xramsel)
{
if (offset & 0x20)
/* XRAM page register */
reply = m_cur_xram_page;
else
/* XRAM data */
reply = m_xram[(offset & 0x1f) + 0x0020*m_cur_xram_page];
}
else
{
if (offset & 0x01)
/* data register */
switch (m_cur_reg)
{
case reg_A:
reply = m_regs[m_cur_reg] & ~reg_A_DV;
reply |= 0x20; // indicate normal RTC operation
break;
case reg_C:
reply = m_regs[m_cur_reg];
m_regs[m_cur_reg] = 0;
field_interrupts();
break;
case reg_D:
reply = m_regs[m_cur_reg];
m_regs[m_cur_reg] = /*0*/reg_D_VRT; /* set VRT flag so that the computer does not complain that the battery is low */
break;
default:
reply = m_regs[m_cur_reg];
break;
}
else
/* indirect address register */
reply = m_cur_reg;
}
return reply;
}
READ8_MEMBER( rtc65271_device::rtc_r )
{
return read(0, offset );
}
READ8_MEMBER( rtc65271_device::xram_r )
{
return read(1, offset );
}
/*
Write a byte to clock
xramsel: select RTC register if 0, XRAM if 1
offset: address (A0-A5 pins)
*/
void rtc65271_device::write(int xramsel, offs_t offset, uint8_t data)
{
if (xramsel)
{
if (offset & 0x20)
/* XRAM page register */
m_cur_xram_page = data & 0x7f;
else
/* XRAM data */
m_xram[(offset & 0x1f) + 0x0020*m_cur_xram_page] = data;
}
else
{
if (offset & 0x01)
/* data register */
switch (m_cur_reg)
{
case reg_second:
/* the data sheet says bit 7 is read-only. (I have no idea of
the reason why it is.) */
m_regs[reg_second] = data & 0x7f;
break;
case reg_A:
if ((data & reg_A_RS) != (m_regs[m_cur_reg] & reg_A_RS))
{
if (data & reg_A_RS)
{
attotime period = attotime::from_hz(SQW_freq_table[data & reg_A_RS]);
attotime half_period = period / 2;
attotime elapsed = m_update_timer->elapsed();
if (half_period > elapsed)
m_SQW_timer->adjust(half_period - elapsed);
else
m_SQW_timer->adjust(half_period);
}
else
{
m_SQW_internal_state = 0; /* right??? */
/* Stop the divider used for SQW and periodic interrupts. */
m_SQW_timer->adjust(attotime::never);
}
}
/* The UIP bit is read-only */
m_regs[reg_A] = (data & ~reg_A_UIP) | (m_regs[reg_A] & reg_A_UIP);
break;
case reg_B:
m_regs[m_cur_reg] = data;
if (data & reg_B_SET)
{
/* if we are in SET mode, clear update cycle */
m_regs[reg_A] &= ~reg_A_UIP;
m_regs[reg_B] &= ~reg_B_UIE; /* the data sheet tells this, but I wonder how much sense it makes */
field_interrupts();
}
break;
case reg_C:
case reg_D:
break;
default:
m_regs[m_cur_reg] = data;
break;
}
else
/* indirect address register */
m_cur_reg = data & 0x3f;
}
}
WRITE8_MEMBER( rtc65271_device::rtc_w )
{
write(0, offset, data );
}
WRITE8_MEMBER( rtc65271_device::xram_w )
{
write(1, offset, data );
}
void rtc65271_device::field_interrupts()
{
if (m_regs[reg_C] & m_regs[reg_B] & (reg_C_PF | reg_C_AF | reg_C_UF))
{
m_regs[reg_C] |= reg_C_IRQF;
if (!m_interrupt_cb.isnull())
m_interrupt_cb(1);
}
else
{
m_regs[reg_C] &= ~reg_C_IRQF;
if (!m_interrupt_cb.isnull())
m_interrupt_cb(0);
}
}
/*
Update SQW output state each half-period and assert periodic interrupt each
period.
*/
TIMER_CALLBACK_MEMBER(rtc65271_device::rtc_SQW_cb)
{
attotime half_period;
m_SQW_internal_state = ! m_SQW_internal_state;
if (! m_SQW_internal_state)
{
/* high-to-low??? transition -> interrupt (or should it be low-to-high?) */
m_regs[reg_C] |= reg_C_PF;
field_interrupts();
}
half_period = attotime::from_hz(SQW_freq_table[m_regs[reg_A] & reg_A_RS]) / 2;
m_SQW_timer->adjust(half_period);
}
/*
Begin update cycle (called every second)
*/
TIMER_CALLBACK_MEMBER(rtc65271_device::rtc_begin_update_cb)
{
if (((m_regs[reg_A] & reg_A_DV) == 0x20) && ! (m_regs[reg_B] & reg_B_SET))
{
m_regs[reg_A] |= reg_A_UIP;
/* schedule end of update cycle */
machine().scheduler().timer_set(UPDATE_CYCLE_TIME, timer_expired_delegate(FUNC(rtc65271_device::rtc_end_update_cb), this));
}
}
/*
End update cycle (called UPDATE_CYCLE_TIME = 1948us after start of update
cycle)
*/
TIMER_CALLBACK_MEMBER(rtc65271_device::rtc_end_update_cb)
{
static const int days_in_month_table[12] =
{
31,28,31, 30,31,30,
31,31,30, 31,30,31
};
uint8_t (*increment)(uint8_t data);
int c59, c23, c12, c11, c29;
if (! (m_regs[reg_A] & reg_A_UIP))
/* abort if update cycle has been canceled */
return;
if (m_regs[reg_B] & reg_B_DM)
{
/* binary mode */
increment = increment_binary;
c59 = 59;
c23 = 23;
c12 = 12;
c11 = 11;
c29 = 29;
}
else
{
/* BCD mode */
increment = increment_BCD;
c59 = 0x59;
c23 = 0x23;
c12 = 0x12;
c11 = 0x11;
c29 = 0x29;
}
/* increment second */
if (m_regs[reg_second] < c59)
m_regs[reg_second] = (*increment)(m_regs[reg_second]);
else
{
m_regs[reg_second] = 0;
/* increment minute */
if (m_regs[reg_minute] < c59)
m_regs[reg_minute] = (*increment)(m_regs[reg_minute]);
else
{
m_regs[reg_minute] = 0;
/* increment hour */
if (m_regs[reg_B] & reg_B_24h)
{
/* 24 hour mode */
if (m_regs[reg_hour] < c23)
m_regs[reg_hour] = (*increment)(m_regs[reg_hour]);
else
m_regs[reg_hour] = 0;
}
else
{
/* 12 hour mode */
if (m_regs[reg_hour] < c12)
{
if ((m_regs[reg_hour] & 0x7f) == c11)
m_regs[reg_hour] ^= 0x80;
m_regs[reg_hour] = ((*increment)(m_regs[reg_hour] & 0x7f) & 0x7f)
| (m_regs[reg_hour] & 0x80);
}
else
m_regs[reg_hour] = 1 | (m_regs[reg_hour] & 0x80);
}
/* increment day if needed */
if (m_regs[reg_hour] == ((m_regs[reg_B] & reg_B_24h) ? 0 : c12))
{
/* increment day */
int days_in_month;
if (m_regs[reg_weekday] < 7)
m_regs[reg_weekday]++;
else
m_regs[reg_weekday] = 1;
if ((m_regs[reg_month] != 2) || (m_regs[reg_year] & 0x03))
{
if (m_regs[reg_B] & reg_B_DM)
{
/* binary mode */
days_in_month = days_in_month_table[m_regs[reg_month] - 1];
}
else
{
/* BCD mode */
days_in_month = binary_to_BCD(days_in_month_table[BCD_to_binary(m_regs[reg_month]) - 1]);
}
}
else
days_in_month = c29;
if (m_regs[reg_monthday] < days_in_month)
m_regs[reg_monthday] = (*increment)(m_regs[reg_monthday]);
else
{
/* increment month */
m_regs[reg_monthday] = 1;
if (m_regs[reg_month] < c12)
m_regs[reg_month] = (*increment)(m_regs[reg_month]);
else
{
/* increment year */
m_regs[reg_month] = 1;
if (m_regs[reg_B] & reg_B_DM)
{
/* binary mode */
if (m_regs[reg_year] < 99)
m_regs[reg_year]++;
else
m_regs[reg_year] = 0;
}
else
{
/* BCD mode */
m_regs[reg_year] = increment_BCD(m_regs[reg_year]);
}
}
}
}
}
}
m_regs[reg_A] &= ~reg_A_UIP;
m_regs[reg_C] |= reg_C_UF;
/* test for alarm (values in range 0xc0-0xff mean "don't care") */
if ((((m_regs[reg_alarm_second] & 0xc0) == 0xc0) || (m_regs[reg_alarm_second] == m_regs[reg_second]))
&& (((m_regs[reg_alarm_minute] & 0xc0) == 0xc0) || (m_regs[reg_alarm_minute] == m_regs[reg_minute]))
&& (((m_regs[reg_alarm_hour] & 0xc0) == 0xc0) || (m_regs[reg_alarm_hour] == m_regs[reg_hour])))
m_regs[reg_C] |= reg_C_AF;
field_interrupts();
}
// device type definition
const device_type RTC65271 = &device_creator<rtc65271_device>;
//-------------------------------------------------
// rtc65271_device - constructor
//-------------------------------------------------
rtc65271_device::rtc65271_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, RTC65271, "RTC-65271", tag, owner, clock, "rtc65271", __FILE__),
device_nvram_interface(mconfig, *this),
m_interrupt_cb(*this)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void rtc65271_device::device_start()
{
m_update_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(rtc65271_device::rtc_begin_update_cb), this));
m_update_timer->adjust(attotime::from_seconds(1), 0, attotime::from_seconds(1));
m_SQW_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(rtc65271_device::rtc_SQW_cb), this));
m_interrupt_cb.resolve();
save_item(NAME(m_regs));
save_item(NAME(m_cur_reg));
save_item(NAME(m_xram));
save_item(NAME(m_cur_xram_page));
save_item(NAME(m_SQW_internal_state));
}