diff options
Diffstat (limited to 'src/devices/machine/m3002.cpp')
-rw-r--r-- | src/devices/machine/m3002.cpp | 497 |
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; + } +} |