// license:BSD-3-Clause
// copyright-holders:Raphael Nabet
/***************************************************************************
mm58274c.c
mm58274c emulation
Reference:
* National Semiconductor MM58274C Microprocessor Compatible Real Time Clock
<http://www.national.com/ds/MM/MM58274C.pdf>
Todo:
* Clock initialization will only work with the BwG: we need to provide
a way to customize it.
* Save the config to NVRAM?
* Support interrupt pin output
Raphael Nabet, 2002
***************************************************************************/
#include "emu.h"
#include "machine/mm58274c.h"
enum
{
st_dcf = 0x8, /* data-changed flag */
st_if = 0x1, /* interrupt flag */
ctl_test = 0x8, /* test mode (0=normal, 1=test) (not emulated) */
ctl_clkstop = 0x4, /* clock start/stop (0=run, 1=stop) */
ctl_intsel = 0x2, /* interrupt select (0=clock setting register, 1=interrupt register) */
ctl_intstop = 0x1, /* interrupt start stop (0=interrupt run, 1=interrupt stop) */
clk_set_leap = 0xc, /* leap year counter (0 indicates a leap year) */
clk_set_leap_inc = 0x4, /* leap year increment */
clk_set_pm = 0x2, /* am/pm indicator (0 = am, 1 = pm, 0 in 24-hour mode) */
clk_set_24 = 0x1, /* 12/24-hour select bit (1= 24-hour mode) */
int_ctl_rpt = 0x8, /* 1 for repeated interrupt */
int_ctl_dly = 0x7 /* 0 no interrupt, 1 = .1 second, 2=.5, 3=1, 4=5, 5=10, 6=30, 7=60 */
};
DEFINE_DEVICE_TYPE(MM58274C, mm58274c_device, "mm58274c", "National Semiconductor MM58274C RTC")
mm58274c_device::mm58274c_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, MM58274C, tag, owner, clock)
, m_mode24(0)
, m_day1(0)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void mm58274c_device::device_start()
{
m_increment_rtc = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mm58274c_device::rtc_increment_cb),this));
m_increment_rtc->adjust(attotime::zero, 0, attotime::from_msec(100));
m_interrupt_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mm58274c_device::rtc_interrupt_cb),this));
// register for state saving
save_item(NAME(m_mode24));
save_item(NAME(m_day1));
save_item(NAME(m_status));
save_item(NAME(m_control));
save_item(NAME(m_clk_set));
save_item(NAME(m_int_ctl));
save_item(NAME(m_wday));
save_item(NAME(m_years1));
save_item(NAME(m_years2));
save_item(NAME(m_months1));
save_item(NAME(m_months2));
save_item(NAME(m_days1));
save_item(NAME(m_days2));
save_item(NAME(m_hours1));
save_item(NAME(m_hours2));
save_item(NAME(m_minutes1));
save_item(NAME(m_minutes2));
save_item(NAME(m_seconds1));
save_item(NAME(m_seconds2));
save_item(NAME(m_tenths));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void mm58274c_device::device_reset()
{
system_time systime;
/* get the current date/time from the core */
machine().current_datetime(systime);
m_clk_set = systime.local_time.year & 3 << 2;
if (m_mode24)
m_clk_set |= clk_set_24;
/* The clock count starts on 1st January 1900 */
m_wday = 1 + ((systime.local_time.weekday - m_day1) % 7);
m_years1 = (systime.local_time.year / 10) % 10;
m_years2 = systime.local_time.year % 10;
m_months1 = (systime.local_time.month + 1) / 10;
m_months2 = (systime.local_time.month + 1) % 10;
m_days1 = systime.local_time.mday / 10;
m_days2 = systime.local_time.mday % 10;
if (!m_mode24)
{
/* 12-hour mode */
if (systime.local_time.hour > 12)
{
systime.local_time.hour -= 12;
m_clk_set |= clk_set_pm;
}
if (systime.local_time.hour == 0)
systime.local_time.hour = 12;
}
m_hours1 = systime.local_time.hour / 10;
m_hours2 = systime.local_time.hour % 10;
m_minutes1 = systime.local_time.minute / 10;
m_minutes2 = systime.local_time.minute % 10;
m_seconds1 = systime.local_time.second / 10;
m_seconds2 = systime.local_time.second % 10;
m_tenths = 0;
m_status = 0;
m_control = 0;
}
attotime mm58274c_device::interrupt_period_table(int val)
{
switch(val)
{
case 0: return attotime::from_msec(0);
case 1: return attotime::from_msec(100);
case 2: return attotime::from_msec(500);
case 3: return attotime::from_seconds(1);
case 4: return attotime::from_seconds(5);
case 5: return attotime::from_seconds(10);
case 6: return attotime::from_seconds(30);
case 7: return attotime::from_seconds(60);
default: fatalerror("out of range\n");
}
}
READ8_MEMBER( mm58274c_device::read )
{
int reply;
offset &= 0xf;
switch (offset)
{
case 0x00: /* Control Register */
reply = m_status;
m_status = 0;
break;
case 0x01: /* Tenths of Seconds */
reply = m_tenths;
break;
case 0x02: /* Units Seconds */
reply = m_seconds2;
break;
case 0x03: /* Tens Seconds */
reply = m_seconds1;
break;
case 0x04: /* Units Minutes */
reply = m_minutes2;
break;
case 0x05: /* Tens Minutes */
reply = m_minutes1;
break;
case 0x06: /* Units Hours */
reply = m_hours2;
break;
case 0x07: /* Tens Hours */
reply = m_hours1;
break;
case 0x08: /* Units Days */
reply = m_days2;
break;
case 0x09: /* Tens Days */
reply = m_days1;
break;
case 0x0a: /* Units Months */
reply = m_months2;
break;
case 0x0b: /* Tens Months */
reply = m_months1;
break;
case 0x0c: /* Units Years */
reply = m_years2;
break;
case 0x0d: /* Tens Years */
reply = m_years1;
break;
case 0x0e: /* Day of Week */
reply = m_wday;
break;
case 0x0f: /* Clock Setting & Interrupt Registers */
if (m_control & ctl_intsel) /* interrupt register */
reply = m_int_ctl;
else /* clock setting register */
{
if (m_clk_set & clk_set_24) /* 24-hour mode */
reply = m_clk_set & ~clk_set_pm;
else /* 12-hour mode */
reply = m_clk_set;
}
break;
default:
reply = 0;
break;
}
return reply;
}
WRITE8_MEMBER( mm58274c_device::write )
{
offset &= 0xf;
data &= 0xf;
switch (offset)
{
case 0x00: /* Control Register (test mode and interrupt not emulated) */
if ((!(m_control & ctl_intstop)) && (data & ctl_intstop)) /* interrupt stop */
m_interrupt_timer->enable(0);
else if ((m_control & ctl_intstop) && (!(data & ctl_intstop))) /* interrupt run */
{
attotime period = interrupt_period_table(m_int_ctl & int_ctl_dly);
m_interrupt_timer->adjust(period, 0, m_int_ctl & int_ctl_rpt ? period : attotime::zero);
}
if (data & ctl_clkstop) /* stopping the clock clears the tenth counter */
m_tenths = 0;
m_control = data;
break;
case 0x01: /* Tenths of Seconds: cannot be written */
break;
case 0x02: /* Units Seconds */
m_seconds2 = data;
break;
case 0x03: /* Tens Seconds */
m_seconds1 = data;
break;
case 0x04: /* Units Minutes */
m_minutes2 = data;
break;
case 0x05: /* Tens Minutes */
m_minutes1 = data;
break;
case 0x06: /* Units Hours */
m_hours2 = data;
break;
case 0x07: /* Tens Hours */
m_hours1 = data;
break;
case 0x08: /* Units Days */
m_days2 = data;
break;
case 0x09: /* Tens Days */
m_days1 = data;
break;
case 0x0a: /* Units Months */
m_months2 = data;
break;
case 0x0b: /* Tens Months */
m_months1 = data;
break;
case 0x0c: /* Units Years */
m_years2 = data;
break;
case 0x0d: /* Tens Years */
m_years1 = data;
break;
case 0x0e: /* Day of Week */
m_wday = data;
break;
case 0x0f: /* Clock Setting & Interrupt Registers */
if (m_control & ctl_intsel) /* interrupt register (not emulated) */
{
m_int_ctl = data;
if (!(m_control & ctl_intstop)) /* interrupt run */
{
attotime period = interrupt_period_table(m_int_ctl & int_ctl_dly);
m_interrupt_timer->adjust(period, 0, m_int_ctl & int_ctl_rpt ? period : attotime::zero);
}
}
else /* clock setting register */
{
m_clk_set = data;
#if 0
if (m_clk_set & clk_set_24) /* 24-hour mode */
m_clk_set &= ~clk_set_pm;
#endif
}
break;
}
}
// Set RTC interrupt flag
TIMER_CALLBACK_MEMBER(mm58274c_device::rtc_interrupt_cb)
{
m_status |= st_if;
}
// Increment RTC clock (timed interrupt every 1/10s)
TIMER_CALLBACK_MEMBER(mm58274c_device::rtc_increment_cb)
{
if (!(m_control & ctl_clkstop))
{
m_status |= st_dcf;
if ((++m_tenths) == 10)
{
m_tenths = 0;
if ((++m_seconds2) == 10)
{
m_seconds2 = 0;
if ((++m_seconds1) == 6)
{
m_seconds1 = 0;
if ((++m_minutes2) == 10)
{
m_minutes2 = 0;
if ((++m_minutes1) == 6)
{
m_minutes1 = 0;
if ((++m_hours2) == 10)
{
m_hours2 = 0;
m_hours1++;
}
/* handle wrap-around */
if ((!(m_clk_set & clk_set_24))
&& ((m_hours1*10 + m_hours2) == 12))
{
m_clk_set ^= clk_set_pm;
}
if ((!(m_clk_set & clk_set_24))
&& ((m_hours1*10 + m_hours2) == 13))
{
m_hours1 = 0;
m_hours2 = 1;
}
if ((m_clk_set & clk_set_24)
&& ((m_hours1*10 + m_hours2) == 24))
{
m_hours1 = m_hours2 = 0;
}
/* increment day if needed */
if ((m_clk_set & clk_set_24)
? ((m_hours1*10 + m_hours2) == 0)
: (((m_hours1*10 + m_hours2) == 12)
&& (!(m_clk_set & clk_set_pm))))
{
int days_in_month;
if ((++m_days2) == 10)
{
m_days2 = 0;
m_days1++;
}
if ((++m_wday) == 8)
m_wday = 1;
{
static const int days_in_month_array[] =
{
31,28,31, 30,31,30,
31,31,30, 31,30,31
};
if (((m_months1*10 + m_months2) != 2) || (m_clk_set & clk_set_leap))
days_in_month = days_in_month_array[m_months1*10 + m_months2 - 1];
else
days_in_month = 29;
}
if ((m_days1*10 + m_days2) == days_in_month+1)
{
m_days1 = 0;
m_days2 = 1;
if ((++m_months2) == 10)
{
m_months2 = 0;
m_months1++;
}
if ((m_months1*10 + m_months2) == 13)
{
m_months1 = 0;
m_months2 = 1;
m_clk_set = (m_clk_set & ~clk_set_leap)
| ((m_clk_set + clk_set_leap_inc) & clk_set_leap);
if ((++m_years2) == 10)
{
m_years2 = 0;
if ((++m_years1) == 10)
m_years1 = 0;
}
}
}
}
}
}
}
}
}
}
}