mirror of
https://github.com/holub/mame
synced 2025-06-04 20:06:28 +03:00

- fixed the Apollo floppy disk emulation - added the media option -node_id resp. -ni to set the node ID from a node ID rom image file - fixed the unmapped ISA Bus access to return 0xff instead of 0x00 - removed excessive log entries from unmapped ISA Bus access - fixed date (and some other issues) in mc146818 (new in MAME 0166)
605 lines
13 KiB
C
605 lines
13 KiB
C
// license:BSD-3-Clause
|
|
// copyright-holders:Curt Coder
|
|
/*********************************************************************
|
|
|
|
mc146818.c
|
|
|
|
Implementation of the MC146818 chip
|
|
|
|
Real time clock chip with CMOS battery backed ram
|
|
Used in IBM PC/AT, several PC clones, Amstrad NC200, Apollo workstations
|
|
|
|
*********************************************************************/
|
|
|
|
#include "coreutil.h"
|
|
#include "machine/mc146818.h"
|
|
|
|
|
|
//**************************************************************************
|
|
// DEBUGGING
|
|
//**************************************************************************
|
|
|
|
#define LOG_MC146818 0
|
|
|
|
|
|
|
|
// device type definition
|
|
const device_type MC146818 = &device_creator<mc146818_device>;
|
|
|
|
//-------------------------------------------------
|
|
// mc146818_device - constructor
|
|
//-------------------------------------------------
|
|
|
|
mc146818_device::mc146818_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
|
|
: device_t(mconfig, MC146818, "MC146818 RTC", tag, owner, clock, "mc146818", __FILE__),
|
|
device_nvram_interface(mconfig, *this),
|
|
m_index(0),
|
|
m_last_refresh(attotime::zero),
|
|
m_write_irq(*this),
|
|
m_century_index(-1),
|
|
m_epoch(0),
|
|
m_use_utc(false),
|
|
m_binary(false),
|
|
m_hour(false)
|
|
{
|
|
}
|
|
|
|
mc146818_device::mc146818_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
|
|
: device_t(mconfig, type, name, tag, owner, clock, shortname, source),
|
|
device_nvram_interface(mconfig, *this),
|
|
m_index(0),
|
|
m_last_refresh(attotime::zero),
|
|
m_write_irq(*this),
|
|
m_century_index(-1),
|
|
m_epoch(0),
|
|
m_use_utc(false),
|
|
m_binary(false),
|
|
m_hour(false)
|
|
{
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_start - device-specific startup
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::device_start()
|
|
{
|
|
m_data.resize(data_size());
|
|
m_last_refresh = machine().time();
|
|
m_clock_timer = timer_alloc(TIMER_CLOCK);
|
|
m_periodic_timer = timer_alloc(TIMER_PERIODIC);
|
|
|
|
m_write_irq.resolve_safe();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// device_reset - device-specific reset
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::device_reset()
|
|
{
|
|
m_data[REG_B] &= ~(REG_B_UIE | REG_B_AIE | REG_B_PIE | REG_B_SQWE);
|
|
m_data[REG_C] = 0;
|
|
|
|
update_irq();
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_timer - handler timer events
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
|
{
|
|
switch (id)
|
|
{
|
|
case TIMER_PERIODIC:
|
|
m_data[REG_C] |= REG_C_PF;
|
|
update_irq();
|
|
break;
|
|
|
|
case TIMER_CLOCK:
|
|
if (!(m_data[REG_B] & REG_B_SET))
|
|
{
|
|
/// TODO: find out how the real chip deals with updates when binary/bcd values are already outside the normal range
|
|
int seconds = get_seconds() + 1;
|
|
if (seconds < 60)
|
|
{
|
|
set_seconds(seconds);
|
|
}
|
|
else
|
|
{
|
|
set_seconds(0);
|
|
|
|
int minutes = get_minutes() + 1;
|
|
if (minutes < 60)
|
|
{
|
|
set_minutes(minutes);
|
|
}
|
|
else
|
|
{
|
|
set_minutes(0);
|
|
|
|
int hours = get_hours() + 1;
|
|
if (hours < 24)
|
|
{
|
|
set_hours(hours);
|
|
}
|
|
else
|
|
{
|
|
set_hours(0);
|
|
|
|
int dayofweek = get_dayofweek() + 1;
|
|
if (dayofweek <= 7)
|
|
{
|
|
set_dayofweek(dayofweek);
|
|
}
|
|
else
|
|
{
|
|
set_dayofweek(1);
|
|
}
|
|
|
|
int dayofmonth = get_dayofmonth() + 1;
|
|
if (dayofmonth <= gregorian_days_in_month(get_month(), get_year() + 2000))
|
|
{
|
|
set_dayofmonth(dayofmonth);
|
|
}
|
|
else
|
|
{
|
|
set_dayofmonth(1);
|
|
|
|
int month = get_month() + 1;
|
|
if (month <= 12)
|
|
{
|
|
set_month(month);
|
|
}
|
|
else
|
|
{
|
|
set_month(1);
|
|
|
|
set_year((get_year() + 1) % 100);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((m_data[REG_ALARM_SECONDS] == m_data[REG_SECONDS] || (m_data[REG_ALARM_SECONDS] & ALARM_DONTCARE) == ALARM_DONTCARE) &&
|
|
(m_data[REG_ALARM_MINUTES] == m_data[REG_MINUTES] || (m_data[REG_ALARM_MINUTES] & ALARM_DONTCARE) == ALARM_DONTCARE) &&
|
|
(m_data[REG_ALARM_HOURS] == m_data[REG_HOURS] || (m_data[REG_ALARM_HOURS] & ALARM_DONTCARE) == ALARM_DONTCARE))
|
|
{
|
|
// set the alarm interrupt flag AF
|
|
m_data[REG_C] |= REG_C_AF;
|
|
}
|
|
|
|
// set the update-ended interrupt Flag UF
|
|
m_data[REG_C] |= REG_C_UF;
|
|
update_irq();
|
|
|
|
m_last_refresh = machine().time();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// nvram_default - called to initialize NVRAM to
|
|
// its default state
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::nvram_default()
|
|
{
|
|
// populate from a memory region if present
|
|
if (m_region != NULL)
|
|
{
|
|
UINT32 bytes = m_region->bytes();
|
|
|
|
if (bytes > data_size())
|
|
bytes = data_size();
|
|
|
|
memcpy(&m_data[0], m_region->base(), bytes);
|
|
}
|
|
else
|
|
{
|
|
memset(&m_data[0], 0, data_size());
|
|
}
|
|
|
|
if(m_binary)
|
|
m_data[REG_B] |= REG_B_DM;
|
|
if(m_hour)
|
|
m_data[REG_B] |= REG_B_24_12;
|
|
|
|
set_base_datetime();
|
|
update_timer();
|
|
update_irq();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// nvram_read - called to read NVRAM from the
|
|
// .nv file
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::nvram_read(emu_file &file)
|
|
{
|
|
file.read(&m_data[0], data_size());
|
|
|
|
set_base_datetime();
|
|
update_timer();
|
|
update_irq();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// nvram_write - called to write NVRAM to the
|
|
// .nv file
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::nvram_write(emu_file &file)
|
|
{
|
|
file.write(&m_data[0], data_size());
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// to_ram - convert value to current ram format
|
|
//-------------------------------------------------
|
|
|
|
int mc146818_device::to_ram(int a)
|
|
{
|
|
if (!(m_data[REG_B] & REG_B_DM))
|
|
return dec_2_bcd(a);
|
|
|
|
return a;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// from_ram - convert value from current ram format
|
|
//-------------------------------------------------
|
|
|
|
int mc146818_device::from_ram(int a)
|
|
{
|
|
if (!(m_data[REG_B] & REG_B_DM))
|
|
return bcd_2_dec(a);
|
|
|
|
return a;
|
|
}
|
|
|
|
|
|
int mc146818_device::get_seconds()
|
|
{
|
|
return from_ram(m_data[REG_SECONDS]);
|
|
}
|
|
|
|
void mc146818_device::set_seconds(int seconds)
|
|
{
|
|
m_data[REG_SECONDS] = to_ram(seconds);
|
|
}
|
|
|
|
int mc146818_device::get_minutes()
|
|
{
|
|
return from_ram(m_data[REG_MINUTES]);
|
|
}
|
|
|
|
void mc146818_device::set_minutes(int minutes)
|
|
{
|
|
m_data[REG_MINUTES] = to_ram(minutes);
|
|
}
|
|
|
|
int mc146818_device::get_hours()
|
|
{
|
|
if (!(m_data[REG_B] & REG_B_24_12))
|
|
{
|
|
int hours = from_ram(m_data[REG_HOURS] & ~HOURS_PM);
|
|
|
|
if (hours == 12)
|
|
{
|
|
hours = 0;
|
|
}
|
|
|
|
if (m_data[REG_HOURS] & HOURS_PM)
|
|
{
|
|
hours += 12;
|
|
}
|
|
|
|
return hours;
|
|
}
|
|
else
|
|
{
|
|
return from_ram(m_data[REG_HOURS]);
|
|
}
|
|
}
|
|
|
|
void mc146818_device::set_hours(int hours)
|
|
{
|
|
if (!(m_data[REG_B] & REG_B_24_12))
|
|
{
|
|
int pm = 0;
|
|
|
|
if (hours >= 12)
|
|
{
|
|
hours -= 12;
|
|
pm = HOURS_PM;
|
|
}
|
|
|
|
if (hours == 0)
|
|
{
|
|
hours = 12;
|
|
}
|
|
|
|
m_data[REG_HOURS] = to_ram(hours) | pm;
|
|
}
|
|
else
|
|
{
|
|
m_data[REG_HOURS] = to_ram(hours);
|
|
}
|
|
}
|
|
|
|
int mc146818_device::get_dayofweek()
|
|
{
|
|
return from_ram(m_data[REG_DAYOFWEEK]);
|
|
}
|
|
|
|
void mc146818_device::set_dayofweek(int dayofweek)
|
|
{
|
|
m_data[REG_DAYOFWEEK] = to_ram(dayofweek);
|
|
}
|
|
|
|
int mc146818_device::get_dayofmonth()
|
|
{
|
|
return from_ram(m_data[REG_DAYOFMONTH]);
|
|
}
|
|
|
|
void mc146818_device::set_dayofmonth(int dayofmonth)
|
|
{
|
|
m_data[REG_DAYOFMONTH] = to_ram(dayofmonth);
|
|
}
|
|
|
|
int mc146818_device::get_month()
|
|
{
|
|
return from_ram(m_data[REG_MONTH]);
|
|
}
|
|
|
|
void mc146818_device::set_month(int month)
|
|
{
|
|
m_data[REG_MONTH] = to_ram(month);
|
|
}
|
|
|
|
int mc146818_device::get_year()
|
|
{
|
|
return from_ram(m_data[REG_YEAR]);
|
|
}
|
|
|
|
void mc146818_device::set_year(int year)
|
|
{
|
|
m_data[REG_YEAR] = to_ram(year);
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------
|
|
// set_base_datetime - update clock with real time
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::set_base_datetime()
|
|
{
|
|
system_time systime;
|
|
system_time::full_time current_time;
|
|
|
|
machine().base_datetime(systime);
|
|
|
|
current_time = (m_use_utc) ? systime.utc_time: systime.local_time;
|
|
|
|
// logerror("mc146818_set_base_datetime %02d/%02d/%02d %02d:%02d:%02d\n",
|
|
// current_time.year % 100, current_time.month + 1, current_time.mday,
|
|
// current_time.hour,current_time.minute, current_time.second);
|
|
|
|
set_seconds(current_time.second);
|
|
set_minutes(current_time.minute);
|
|
set_hours(current_time.hour);
|
|
set_dayofweek(current_time.weekday + 1);
|
|
set_dayofmonth(current_time.mday);
|
|
set_month(current_time.month + 1);
|
|
set_year((current_time.year - m_epoch) % 100);
|
|
|
|
if (m_century_index >= 0)
|
|
m_data[m_century_index] = to_ram(current_time.year / 100);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// update_timer - update timer based on A register
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::update_timer()
|
|
{
|
|
int bypass;
|
|
|
|
switch (m_data[REG_A] & (REG_A_DV2 | REG_A_DV1 | REG_A_DV0))
|
|
{
|
|
case 0:
|
|
bypass = 0;
|
|
break;
|
|
|
|
case REG_A_DV0:
|
|
bypass = 2;
|
|
break;
|
|
|
|
case REG_A_DV1:
|
|
bypass = 7;
|
|
break;
|
|
|
|
case REG_A_DV2 | REG_A_DV1:
|
|
case REG_A_DV2 | REG_A_DV1 | REG_A_DV0:
|
|
bypass = 22;
|
|
break;
|
|
|
|
default:
|
|
// TODO: other combinations of divider bits are used for test purposes only
|
|
bypass = 22;
|
|
break;
|
|
}
|
|
|
|
|
|
attotime update_period = attotime::never;
|
|
attotime update_interval = attotime::never;
|
|
attotime periodic_period = attotime::never;
|
|
attotime periodic_interval = attotime::never;
|
|
|
|
if (bypass < 22)
|
|
{
|
|
int shift = 22 - bypass;
|
|
|
|
double update_hz = (double) clock() / (1 << shift);
|
|
|
|
// TODO: take the time since last timer into account
|
|
update_period = attotime::from_hz(update_hz * 2);
|
|
update_interval = attotime::from_hz(update_hz);
|
|
|
|
int rate_select = m_data[REG_A] & (REG_A_RS3 | REG_A_RS2 | REG_A_RS1 | REG_A_RS0);
|
|
if (rate_select != 0)
|
|
{
|
|
shift = (rate_select + 6) - bypass;
|
|
if (shift <= 1)
|
|
shift += 7;
|
|
|
|
double periodic_hz = (double) clock() / (1 << shift);
|
|
|
|
// TODO: take the time since last timer into account
|
|
periodic_period = attotime::from_hz(periodic_hz * 2);
|
|
periodic_interval = attotime::from_hz(periodic_hz);
|
|
}
|
|
}
|
|
|
|
m_clock_timer->adjust(update_period, 0, update_interval);
|
|
m_periodic_timer->adjust(periodic_period, 0, periodic_interval);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// update_irq - Update irq based on B & C register
|
|
//-------------------------------------------------
|
|
|
|
void mc146818_device::update_irq()
|
|
{
|
|
// IRQ line is active low
|
|
if (((m_data[REG_C] & REG_C_UF) && (m_data[REG_B] & REG_B_UIE)) ||
|
|
((m_data[REG_C] & REG_C_AF) && (m_data[REG_B] & REG_B_AIE)) ||
|
|
((m_data[REG_C] & REG_C_PF) && (m_data[REG_B] & REG_B_PIE)))
|
|
{
|
|
m_data[REG_C] |= REG_C_IRQF;
|
|
m_write_irq(CLEAR_LINE);
|
|
}
|
|
else
|
|
{
|
|
m_data[REG_C] &= REG_C_IRQF;
|
|
m_write_irq(ASSERT_LINE);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------
|
|
// read - I/O handler for reading
|
|
//-------------------------------------------------
|
|
|
|
READ8_MEMBER( mc146818_device::read )
|
|
{
|
|
UINT8 data = 0;
|
|
switch (offset)
|
|
{
|
|
case 0:
|
|
data = m_index;
|
|
break;
|
|
|
|
case 1:
|
|
switch (m_index)
|
|
{
|
|
case REG_A:
|
|
data = m_data[REG_A];
|
|
// Update In Progress (UIP) time for 32768 Hz is 244+1984usec
|
|
/// TODO: support other dividers
|
|
/// TODO: don't set this if update is stopped
|
|
if ((space.machine().time() - m_last_refresh) < attotime::from_usec(244+1984))
|
|
data |= REG_A_UIP;
|
|
break;
|
|
|
|
case REG_C:
|
|
// the unused bits b0 ... b3 are always read as 0
|
|
data = m_data[REG_C] & (REG_C_IRQF | REG_C_PF | REG_C_AF | REG_C_UF);
|
|
// read 0x0c will clear all IRQ flags in register 0x0c
|
|
m_data[REG_C] &= ~(REG_C_IRQF | REG_C_PF | REG_C_AF | REG_C_UF);
|
|
update_irq();
|
|
break;
|
|
|
|
case REG_D:
|
|
/* battery ok */
|
|
data = m_data[REG_D] | REG_D_VRT;
|
|
break;
|
|
|
|
default:
|
|
data = m_data[m_index];
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (LOG_MC146818)
|
|
logerror("mc146818_port_r(): index=0x%02x data=0x%02x\n", m_index, data);
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// write - I/O handler for writing
|
|
//-------------------------------------------------
|
|
|
|
WRITE8_MEMBER( mc146818_device::write )
|
|
{
|
|
if (LOG_MC146818)
|
|
logerror("mc146818_port_w(): index=0x%02x data=0x%02x\n", m_index, data);
|
|
|
|
switch (offset)
|
|
{
|
|
case 0:
|
|
m_index = data % data_size();
|
|
break;
|
|
|
|
case 1:
|
|
switch (m_index)
|
|
{
|
|
case REG_SECONDS:
|
|
// top bit of SECONDS is read only
|
|
m_data[REG_SECONDS] = data & ~0x80;
|
|
break;
|
|
|
|
case REG_A:
|
|
// top bit of A is read only
|
|
m_data[REG_A] = data & ~REG_A_UIP;
|
|
update_timer();
|
|
break;
|
|
|
|
case REG_B:
|
|
if ((data & REG_B_SET) && !(m_data[REG_B] & REG_B_SET))
|
|
data &= ~REG_B_UIE;
|
|
|
|
m_data[REG_B] = data;
|
|
update_irq();
|
|
break;
|
|
|
|
case REG_C:
|
|
case REG_D:
|
|
// register C & D is readonly
|
|
break;
|
|
|
|
default:
|
|
m_data[m_index] = data;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|