summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/mm58274c.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/mm58274c.cpp')
-rw-r--r--src/devices/machine/mm58274c.cpp470
1 files changed, 470 insertions, 0 deletions
diff --git a/src/devices/machine/mm58274c.cpp b/src/devices/machine/mm58274c.cpp
new file mode 100644
index 00000000000..0690f775b5d
--- /dev/null
+++ b/src/devices/machine/mm58274c.cpp
@@ -0,0 +1,470 @@
+// 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 */
+};
+
+
+
+const device_type MM58274C = &device_creator<mm58274c_device>;
+
+
+mm58274c_device::mm58274c_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, MM58274C, "National Semiconductor MM58274C", tag, owner, clock, "mm58274c", __FILE__),
+ 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_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;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}