summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/m3002.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/m3002.cpp')
-rw-r--r--src/devices/machine/m3002.cpp497
1 files changed, 497 insertions, 0 deletions
diff --git a/src/devices/machine/m3002.cpp b/src/devices/machine/m3002.cpp
new file mode 100644
index 00000000000..8c9fe20db05
--- /dev/null
+++ b/src/devices/machine/m3002.cpp
@@ -0,0 +1,497 @@
+// license:BSD-3-Clause
+// copyright-holders:AJR
+/**********************************************************************
+
+ EM Microelectronic-Marin (µem) M 3002 Real Time Clock
+
+ TODO:
+ - Pulse output (256 Hz, second, minute, hour)
+ - Test mode
+ - Emulate M 3000 differences (if any meaningful ones exist)
+
+**********************************************************************/
+
+#include "emu.h"
+#include "m3002.h"
+
+#define VERBOSE 0
+#include "logmacro.h"
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+DEFINE_DEVICE_TYPE(M3002, m3002_device, "m3002", "EM M 3002 Real Time Clock")
+DEFINE_DEVICE_TYPE(M3000, m3000_device, "m3000", "EM M 3000 Real Time Clock")
+
+//**************************************************************************
+// DEVICE CONSTRUCTION AND INITIALIZATION
+//**************************************************************************
+
+ALLOW_SAVE_TYPE(m3002_device::mux_state);
+
+//-------------------------------------------------
+// m3002_device - constructor
+//-------------------------------------------------
+
+m3002_device::m3002_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_irq_callback(*this)
+ , m_address(0)
+ , m_mux_state(mux_state::INIT)
+ , m_irq_active(false)
+ , m_update_deferred(false)
+ , m_second_timer(nullptr)
+{
+ std::fill_n(&m_ram[0], 0x10, 0);
+}
+
+m3002_device::m3002_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : m3002_device(mconfig, M3002, tag, owner, clock)
+{
+}
+
+
+//-------------------------------------------------
+// m3000_device - constructor
+//-------------------------------------------------
+
+m3000_device::m3000_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : m3002_device(mconfig, M3000, tag, owner, clock)
+{
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void m3002_device::device_start()
+{
+ // Setup timer
+ m_second_timer = timer_alloc(FUNC(m3002_device::second_timer), this);
+
+ // Save internal state
+ save_item(NAME(m_ram));
+ save_item(NAME(m_address));
+ save_item(NAME(m_mux_state));
+ save_item(NAME(m_irq_active));
+ save_item(NAME(m_update_deferred));
+}
+
+
+//-------------------------------------------------
+// device_clock_changed - called when the
+// device clock is altered in any way
+//-------------------------------------------------
+
+void m3002_device::device_clock_changed()
+{
+ attotime second = clocks_to_attotime(32768);
+ m_second_timer->adjust(second, 0, second);
+}
+
+
+//-------------------------------------------------
+// nvram_read - read NVRAM from the file
+//-------------------------------------------------
+
+bool m3002_device::nvram_read(util::read_stream &file)
+{
+ auto const [err, actual] = util::read(file, &m_ram[0], 0x10);
+ return !err && (actual == 0x10);
+}
+
+
+//-------------------------------------------------
+// nvram_write - write NVRAM to the file
+//-------------------------------------------------
+
+bool m3002_device::nvram_write(util::write_stream &file)
+{
+ auto const [err, actual] = util::write(file, &m_ram[0], 0x10);
+ return !err;
+}
+
+
+//-------------------------------------------------
+// nvram_default - initialize NVRAM to its
+// default state
+//-------------------------------------------------
+
+void m3002_device::nvram_default()
+{
+ // Set watch, alarm and timer registers to minimum values
+ m_ram[0x0] = m_ram[0x8] = m_ram[0xc] = 0x00;
+ m_ram[0x1] = m_ram[0x9] = m_ram[0xd] = 0x00;
+ m_ram[0x2] = m_ram[0xa] = m_ram[0xe] = 0x00;
+ m_ram[0x3] = m_ram[0xb] = 0x01;
+ m_ram[0x4] = 0x01;
+ m_ram[0x5] = 0x00;
+ m_ram[0x6] = 0x01;
+ m_ram[0x7] = 0x01;
+
+ // Allow watch to operate
+ m_ram[0xf] = 0x01;
+}
+
+
+//-------------------------------------------------
+// rtc_clock_updated - set emulated time
+//-------------------------------------------------
+
+void m3002_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second)
+{
+ if (!BIT(m_ram[0xf], 0))
+ return;
+
+ m_ram[0x0] = convert_to_bcd(second);
+ m_ram[0x1] = convert_to_bcd(minute);
+ m_ram[0x2] = convert_to_bcd(hour);
+ m_ram[0x3] = convert_to_bcd(day);
+ m_ram[0x4] = convert_to_bcd(month);
+ m_ram[0x5] = convert_to_bcd(year);
+ m_ram[0x6] = convert_to_bcd(day_of_week);
+
+ unsigned day_of_year = day - 1;
+ for (int n = 1; n < month; n++)
+ {
+ if (n == 2)
+ day_of_year += (year % 4) == 0 ? 29 : 28;
+ else
+ day_of_year += BIT(0xa50, n) ? 30 : 31;
+ }
+ m_ram[0x7] = convert_to_bcd((day_of_year + 7 - day_of_week) / 7 + 1);
+}
+
+
+//**************************************************************************
+// INTERNAL UPDATE CYCLES
+//**************************************************************************
+
+//-------------------------------------------------
+// internal_busy - return true if in the middle
+// of a once-per-second update
+//-------------------------------------------------
+
+bool m3002_device::internal_busy() const
+{
+ // Each internal update lasts between 0.73 ms and 6 ms
+ return attotime_to_clocks(m_second_timer->remaining()) < 239;
+}
+
+
+//-------------------------------------------------
+// bcd_increment - increment the value of one RAM
+// location using BCD sequence
+//-------------------------------------------------
+
+void m3002_device::bcd_increment(u8 location)
+{
+ if ((m_ram[location] & 0x09) != 0x09)
+ m_ram[location]++;
+ else
+ {
+ if ((m_ram[location] & 0x90) != 0x90)
+ m_ram[location] = (m_ram[location] & 0xf0) + 0x10;
+ else
+ m_ram[location] = 0x00;
+ }
+}
+
+
+//-------------------------------------------------
+// max_date - return the maximum date (BCD) in
+// the current month
+//-------------------------------------------------
+
+u8 m3002_device::max_date() const
+{
+ if (m_ram[4] == 0x02)
+ return (m_ram[5] & 0x03) == ((m_ram[5] & 0x10) >> 3) ? 0x29 : 0x28;
+ else
+ return BIT(0x20250, m_ram[4]) ? 0x30 : 0x31;
+}
+
+
+//-------------------------------------------------
+// watch_update - update watch registers
+//-------------------------------------------------
+
+void m3002_device::watch_update()
+{
+ // Count seconds
+ if (m_ram[0x0] != 0x59)
+ {
+ bcd_increment(0x0);
+ return;
+ }
+ m_ram[0x0] = 0x00;
+
+ // Count minutes
+ if (m_ram[0x1] != 0x59)
+ {
+ bcd_increment(0x1);
+ return;
+ }
+ m_ram[0x1] = 0x00;
+
+ // Count hours
+ if (m_ram[0x2] != 0x23)
+ {
+ bcd_increment(0x2);
+ return;
+ }
+ m_ram[0x2] = 0x00;
+
+ // Count weekdays
+ if (m_ram[0x6] != 0x07)
+ bcd_increment(0x6);
+ else
+ {
+ m_ram[0x6] = 0x01;
+
+ // Count weeks
+ bcd_increment(0x7);
+ }
+
+ // Count date
+ if (m_ram[0x3] != max_date())
+ {
+ bcd_increment(0x3);
+ return;
+ }
+ m_ram[0x3] = 0x01;
+
+ // Count months
+ if (m_ram[0x4] != 0x12)
+ {
+ bcd_increment(0x4);
+ return;
+ }
+ m_ram[0x4] = 0x01;
+ m_ram[0x7] = 0x01;
+
+ // Count years
+ bcd_increment(5);
+}
+
+
+//-------------------------------------------------
+// alarm_update - compare alarm registers against
+// watch registers
+//-------------------------------------------------
+
+void m3002_device::alarm_update()
+{
+ // Compare seconds, minutes, hours, date
+ for (int r = 0x0; r <= 0x3; r++)
+ if (m_ram[0x8 + r] != 0xff && m_ram[0x8 + r] != m_ram[r])
+ return;
+
+ // Alarm IRQ
+ m_ram[0xf] |= 0x04;
+}
+
+
+//-------------------------------------------------
+// timer_update - update timer registers
+//-------------------------------------------------
+
+void m3002_device::timer_update()
+{
+ // Count seconds
+ if (m_ram[0xc] != 0x59)
+ {
+ bcd_increment(0xc);
+ return;
+ }
+ m_ram[0xc] = 0x00;
+
+ // Count minutes
+ if (m_ram[0xd] != 0x59)
+ {
+ bcd_increment(0xd);
+ return;
+ }
+ m_ram[0xd] = 0x00;
+
+ // Count hours
+ if (m_ram[0xe] != 0x23)
+ {
+ bcd_increment(0xe);
+ return;
+ }
+ m_ram[0xe] = 0x00;
+
+ // Timer IRQ (23:59:59 -> 00:00:00)
+ m_ram[0xf] |= 0x08;
+}
+
+
+//-------------------------------------------------
+// irq_update - update IRQ output
+//-------------------------------------------------
+
+void m3002_device::irq_update()
+{
+ if (!m_irq_active && (m_ram[0xf] & 0x0c) != 0)
+ {
+ LOG("IRQ occurred\n");
+ m_irq_active = true;
+ m_irq_callback(0);
+ }
+ else if (m_irq_active && (m_ram[0xf] & 0x0c) == 0)
+ {
+ LOG("IRQ cleared\n");
+ m_irq_active = false;
+ m_irq_callback(1);
+ }
+}
+
+
+//-------------------------------------------------
+// update - perform one full internal update
+//-------------------------------------------------
+
+void m3002_device::update()
+{
+ if (BIT(m_ram[0xf], 0))
+ {
+ watch_update();
+ if (BIT(m_ram[0xf], 1))
+ alarm_update();
+ }
+
+ if (BIT(m_ram[0xf], 4))
+ timer_update();
+
+ irq_update();
+}
+
+
+//-------------------------------------------------
+// second_timer - called once each second
+//-------------------------------------------------
+
+TIMER_CALLBACK_MEMBER(m3002_device::second_timer)
+{
+ if (m_update_deferred)
+ {
+ m_update_deferred = false;
+ update();
+ }
+ else if (m_mux_state != mux_state::INIT)
+ m_update_deferred = true;
+
+ if (!m_update_deferred)
+ update();
+}
+
+//**************************************************************************
+// MICROPROCESSOR ACCESS
+//**************************************************************************
+
+const char *const m3002_device::s_register_names[0x10] =
+{
+ "watch seconds",
+ "watch minutes",
+ "watch hours",
+ "watch date",
+ "watch month",
+ "watch year",
+ "watch weekday",
+ "watch week",
+ "alarm seconds",
+ "alarm minutes",
+ "alarm hours",
+ "alarm date",
+ "timer seconds",
+ "timer minutes",
+ "timer hours",
+ "status/control"
+};
+
+//-------------------------------------------------
+// set_init_state - return the multiplexer to the
+// initial state
+//-------------------------------------------------
+
+void m3002_device::set_init_state()
+{
+ m_mux_state = mux_state::INIT;
+ if (m_update_deferred)
+ {
+ m_update_deferred = false;
+ update();
+ }
+ irq_update();
+}
+
+
+//-------------------------------------------------
+// read - read data or busy status using 3-step
+// access sequence
+//-------------------------------------------------
+
+u8 m3002_device::read()
+{
+ u8 data = 0x0;
+
+ switch (m_mux_state)
+ {
+ case mux_state::INIT:
+ if (internal_busy())
+ data = 0xf;
+ break;
+
+ case mux_state::TENS:
+ data = m_ram[m_address] >> 4;
+ if (!machine().side_effects_disabled())
+ m_mux_state = mux_state::UNITS;
+ break;
+
+ case mux_state::UNITS:
+ data = m_ram[m_address] & 0x0f;
+ if (!machine().side_effects_disabled())
+ {
+ LOG("%s: Read %02X from %s register\n", machine().describe_context(), m_ram[m_address], s_register_names[m_address]);
+ set_init_state();
+ }
+ break;
+ }
+
+ return data;
+}
+
+
+//-------------------------------------------------
+// write - write address and/or data using 3-step
+// access sequence
+//-------------------------------------------------
+
+void m3002_device::write(u8 data)
+{
+ data &= 0xf;
+
+ switch (m_mux_state)
+ {
+ case mux_state::INIT:
+ m_address = data;
+ m_mux_state = mux_state::TENS;
+ break;
+
+ case mux_state::TENS:
+ m_ram[m_address] = (data << 4) | (m_ram[m_address] & 0x0f);
+ m_mux_state = mux_state::UNITS;
+ break;
+
+ case mux_state::UNITS:
+ m_ram[m_address] = (m_ram[m_address] & 0xf0) | data;
+ LOG("%s: Writing %02X to %s register\n", machine().describe_context(), m_ram[m_address], s_register_names[m_address]);
+ set_init_state();
+ break;
+ }
+}