mame/src/devices/machine/m3002.cpp
AJR 19fde67618 clxvme186: Add RTC device (nw)
cms6502: Use XTAL value from schematics (nw)
2020-04-07 21:53:31 -04:00

508 lines
11 KiB
C++

// 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_resolve_objects - resolve objects that
// may be needed for other devices to set
// initial conditions at start time
//-------------------------------------------------
void m3002_device::device_resolve_objects()
{
m_irq_callback.resolve_safe();
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void m3002_device::device_start()
{
// Setup timer
m_second_timer = machine().scheduler().timer_alloc(timer_expired_delegate(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
//-------------------------------------------------
void m3002_device::nvram_read(emu_file &file)
{
file.read(&m_ram[0], 0x10);
}
//-------------------------------------------------
// nvram_write - write NVRAM to the file
//-------------------------------------------------
void m3002_device::nvram_write(emu_file &file)
{
file.write(&m_ram[0], 0x10);
}
//-------------------------------------------------
// 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) == 0x00 ? 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(ASSERT_LINE);
}
else if (m_irq_active && (m_ram[0xf] & 0x0c) == 0)
{
LOG("IRQ cleared\n");
m_irq_active = false;
m_irq_callback(CLEAR_LINE);
}
}
//-------------------------------------------------
// 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;
}
}