diff options
author | 2015-09-13 08:41:44 +0200 | |
---|---|---|
committer | 2015-09-13 08:41:44 +0200 | |
commit | f88cefad27a1737c76e09d99c9fb43e173506081 (patch) | |
tree | 2d8167d03579c46e226471747eb4407bd00ed6fa /src/devices/machine/rtc65271.c | |
parent | e92ac9e0fa8e99869894bea00589bbb526be30aa (diff) |
Move all devices into separate part of src tree (nw)
Diffstat (limited to 'src/devices/machine/rtc65271.c')
-rw-r--r-- | src/devices/machine/rtc65271.c | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/src/devices/machine/rtc65271.c b/src/devices/machine/rtc65271.c new file mode 100644 index 00000000000..074b8d87f48 --- /dev/null +++ b/src/devices/machine/rtc65271.c @@ -0,0 +1,697 @@ +// license:BSD-3-Clause +// copyright-holders:Raphael Nabet, R. Belmont +/* + rtc65271 emulation + + This chip is an RTC for computer built by Epson and Spezial-Electronic (I + think SE is the second source here). + + Reference: + * Realtime Clock Module RTC-65271 Application Manual + <http://www.bgmicro.com/pdf/rtc65271.pdf> + + Todo: + * Support square wave pin output? + * Support DSE mode? + + Raphael Nabet, 2003-2004 + R. Belmont, 2012 +*/ + +#include "emu.h" +#include "rtc65271.h" + +/* Delay between the beginning (UIP asserted) and the end (UIP cleared and +update interrupt asserted) of the update cycle */ +#define UPDATE_CYCLE_TIME attotime::from_usec(1984) +/* Delay between the assertion of UIP and the effective start of the update +cycle */ +/*#define UPDATE_CYCLE_DELAY attotime::from_usec(244)*/ + +enum +{ + reg_second = 0, + reg_alarm_second, + reg_minute, + reg_alarm_minute, + reg_hour, + reg_alarm_hour, + reg_weekday, + reg_monthday, + reg_month, + reg_year, + reg_A, + reg_B, + reg_C, + reg_D +}; + +enum +{ + reg_A_UIP = 0x80, + reg_A_DV = 0x70, + reg_A_RS = 0x0F, + + reg_B_SET = 0x80, + reg_B_PIE = 0x40, + reg_B_AIE = 0x20, + reg_B_UIE = 0x10, + reg_B_SQW = 0x08, + reg_B_DM = 0x04, + reg_B_24h = 0x02, + reg_B_DSE = 0x01, + + reg_C_IRQF = 0x80, + reg_C_PF = 0x40, + reg_C_AF = 0x20, + reg_C_UF = 0x10, + + reg_D_VRT = 0x80 +}; + +static const int SQW_freq_table[16] = +{ + 0, + 256, + 128, + 8192, + 4096, + 2048, + 1024, + 512, + 256, + 128, + 64, + 32, + 16, + 8, + 4, + 2, +}; + + +/* + BCD utilities +*/ + +/* + Increment a binary-encoded UINT8 +*/ +static UINT8 increment_binary(UINT8 data) +{ + return data+1; +} + + +/* + Increment a BCD-encoded UINT8 +*/ +static UINT8 increment_BCD(UINT8 data) +{ + if ((data & 0x0f) < 0x09) + { + if ((data & 0xf0) < 0xa0) + data++; + else + data = data + 0x01 - 0xa0; + } + else + { + if ((data & 0xf0) < 0xa0) + data = data - 0x09 + 0x10; + else + data = data - 0x09 - 0x90; + } + return data; +} + + +/* + Convert a binary-encoded UINT8 to BCD +*/ +static UINT8 binary_to_BCD(UINT8 data) +{ + data %= 100; + + return ((data / 10) << 4) | (data %10); +} + + +/* + Convert a BCD-encoded UINT8 to binary +*/ +static UINT8 BCD_to_binary(UINT8 data) +{ + if ((data & 0x0f) >= 0x0a) + data = data - 0x0a + 0x10; + if ((data & 0xf0) >= 0xa0) + data = data - 0xa0; + + return (data & 0x0f) + (((data & 0xf0) >> 4) * 10); +} + + +/* + Public functions +*/ + +//------------------------------------------------- +// nvram_default - called to initialize NVRAM to +// its default state +//------------------------------------------------- + +void rtc65271_device::nvram_default() +{ + memset(m_regs,0, sizeof(m_regs)); + memset(m_xram,0, sizeof(m_xram)); + + m_regs[reg_B] |= reg_B_DM; // Firebeat assumes the chip factory defaults to non-BCD mode (or maybe Konami programs it that way?) +} + +//------------------------------------------------- +// nvram_read - called to read NVRAM from the +// .nv file +//------------------------------------------------- + +void rtc65271_device::nvram_read(emu_file &file) +{ + UINT8 buf; + + /* version flag */ + if (file.read(&buf, 1) != 1) + return; + if (buf != 0) + return; + + /* control registers */ + if (file.read(&buf, 1) != 1) + return; + m_regs[reg_A] = buf & (reg_A_DV /*| reg_A_RS*/); + if (file.read(&buf, 1) != 1) + return; + m_regs[reg_B] = buf & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE); + + /* alarm registers */ + if (file.read(&m_regs[reg_alarm_second], 1) != 1) + return; + if (file.read(&m_regs[reg_alarm_minute], 1) != 1) + return; + if (file.read(&m_regs[reg_alarm_hour], 1) != 1) + return; + + /* user RAM */ + if (file.read(m_regs+14, 50) != 50) + return; + + /* extended RAM */ + if (file.read(m_xram, 4096) != 4096) + return; + + m_regs[reg_D] |= reg_D_VRT; /* the data was backed up successfully */ + /*m_dirty = FALSE;*/ + + { + system_time systime; + + /* get the current date/time from the core */ + machine().current_datetime(systime); + + /* set clock registers */ + m_regs[reg_second] = systime.local_time.second; + m_regs[reg_minute] = systime.local_time.minute; + if (m_regs[reg_B] & reg_B_24h) + /* 24-hour mode */ + m_regs[reg_hour] = systime.local_time.hour; + else + { /* 12-hour mode */ + if (systime.local_time.hour >= 12) + { + m_regs[reg_hour] = 0x80; + systime.local_time.hour -= 12; + } + else + { + m_regs[reg_hour] = 0; + } + + // Firebeat indicates non-BCD 12-hour mode has 0-based hour, so 12 AM is 0x00 and 12 PM is 0x80 + m_regs[reg_hour] |= systime.local_time.hour; // ? systime.local_time.hour : 12; + } + m_regs[reg_weekday] = systime.local_time.weekday + 1; + m_regs[reg_monthday] = systime.local_time.mday; + m_regs[reg_month] = systime.local_time.month + 1; + m_regs[reg_year] = systime.local_time.year % 100; + if (! (m_regs[reg_B] & reg_B_DM)) + { /* BCD mode */ + m_regs[reg_second] = binary_to_BCD(m_regs[reg_second]); + m_regs[reg_minute] = binary_to_BCD(m_regs[reg_minute]); + m_regs[reg_hour] = (m_regs[reg_hour] & 0x80) | binary_to_BCD(m_regs[reg_hour] & 0x7f); + /*m_regs[reg_weekday] = binary_to_BCD(m_regs[reg_weekday]);*/ + m_regs[reg_monthday] = binary_to_BCD(m_regs[reg_monthday]); + m_regs[reg_month] = binary_to_BCD(m_regs[reg_month]); + m_regs[reg_year] = binary_to_BCD(m_regs[reg_year]); + } + } +} + +//------------------------------------------------- +// nvram_write - called to write NVRAM to the +// .nv file +//------------------------------------------------- + +void rtc65271_device::nvram_write(emu_file &file) +{ + UINT8 buf; + + + /* version flag */ + buf = 0; + if (file.write(& buf, 1) != 1) + return; + + /* control registers */ + buf = m_regs[reg_A] & (reg_A_DV | reg_A_RS); + if (file.write(&buf, 1) != 1) + return; + buf = m_regs[reg_B] & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE); + if (file.write(&buf, 1) != 1) + return; + + /* alarm registers */ + if (file.write(&m_regs[reg_alarm_second], 1) != 1) + return; + if (file.write(&m_regs[reg_alarm_minute], 1) != 1) + return; + if (file.write(&m_regs[reg_alarm_hour], 1) != 1) + return; + + /* user RAM */ + if (file.write(m_regs+14, 50) != 50) + return; + + /* extended RAM */ + if (file.write(m_xram, 4096) != 4096) + return; +} + +/* + Read a byte from clock + + xramsel: select RTC register if 0, XRAM if 1 + offset: address (A0-A5 pins) +*/ +UINT8 rtc65271_device::read(int xramsel, offs_t offset) +{ + int reply; + + if (xramsel) + { + if (offset & 0x20) + /* XRAM page register */ + reply = m_cur_xram_page; + else + /* XRAM data */ + reply = m_xram[(offset & 0x1f) + 0x0020*m_cur_xram_page]; + } + else + { + if (offset & 0x01) + /* data register */ + switch (m_cur_reg) + { + case reg_A: + reply = m_regs[m_cur_reg] & ~reg_A_DV; + reply |= 0x20; // indicate normal RTC operation + break; + + case reg_C: + reply = m_regs[m_cur_reg]; + m_regs[m_cur_reg] = 0; + field_interrupts(); + break; + case reg_D: + reply = m_regs[m_cur_reg]; + m_regs[m_cur_reg] = /*0*/reg_D_VRT; /* set VRT flag so that the computer does not complain that the battery is low */ + break; + + default: + reply = m_regs[m_cur_reg]; + break; + } + else + /* indirect address register */ + reply = m_cur_reg; + } + + return reply; +} + +READ8_MEMBER( rtc65271_device::rtc_r ) +{ + return read(0, offset ); +} + +READ8_MEMBER( rtc65271_device::xram_r ) +{ + return read(1, offset ); +} + +/* + Write a byte to clock + + xramsel: select RTC register if 0, XRAM if 1 + offset: address (A0-A5 pins) +*/ +void rtc65271_device::write(int xramsel, offs_t offset, UINT8 data) +{ + if (xramsel) + { + if (offset & 0x20) + /* XRAM page register */ + m_cur_xram_page = data & 0x7f; + else + /* XRAM data */ + m_xram[(offset & 0x1f) + 0x0020*m_cur_xram_page] = data; + } + else + { + if (offset & 0x01) + /* data register */ + switch (m_cur_reg) + { + case reg_second: + /* the data sheet says bit 7 is read-only. (I have no idea of + the reason why it is.) */ + m_regs[reg_second] = data & 0x7f; + break; + + case reg_A: + if ((data & reg_A_RS) != (m_regs[m_cur_reg] & reg_A_RS)) + { + if (data & reg_A_RS) + { + attotime period = attotime::from_hz(SQW_freq_table[data & reg_A_RS]); + attotime half_period = period / 2; + attotime elapsed = m_update_timer->elapsed(); + + if (half_period > elapsed) + m_SQW_timer->adjust(half_period - elapsed); + else + m_SQW_timer->adjust(half_period); + } + else + { + m_SQW_internal_state = 0; /* right??? */ + + /* Stop the divider used for SQW and periodic interrupts. */ + m_SQW_timer->adjust(attotime::never); + } + } + /* The UIP bit is read-only */ + m_regs[reg_A] = (data & ~reg_A_UIP) | (m_regs[reg_A] & reg_A_UIP); + break; + + case reg_B: + m_regs[m_cur_reg] = data; + if (data & reg_B_SET) + { + /* if we are in SET mode, clear update cycle */ + m_regs[reg_A] &= ~reg_A_UIP; + m_regs[reg_B] &= ~reg_B_UIE; /* the data sheet tells this, but I wonder how much sense it makes */ + field_interrupts(); + } + break; + + case reg_C: + case reg_D: + break; + + default: + m_regs[m_cur_reg] = data; + break; + } + else + /* indirect address register */ + m_cur_reg = data & 0x3f; + } +} + +WRITE8_MEMBER( rtc65271_device::rtc_w ) +{ + write(0, offset, data ); +} + +WRITE8_MEMBER( rtc65271_device::xram_w ) +{ + write(1, offset, data ); +} + +void rtc65271_device::field_interrupts() +{ + if (m_regs[reg_C] & m_regs[reg_B] & (reg_C_PF | reg_C_AF | reg_C_UF)) + { + m_regs[reg_C] |= reg_C_IRQF; + if (!m_interrupt_cb.isnull()) + m_interrupt_cb(1); + } + else + { + m_regs[reg_C] &= ~reg_C_IRQF; + if (!m_interrupt_cb.isnull()) + m_interrupt_cb(0); + } +} + + +/* + Timer handlers +*/ +TIMER_CALLBACK( rtc65271_device::rtc_SQW_callback ) +{ + rtc65271_device *rtc = reinterpret_cast<rtc65271_device *>(ptr); + rtc->rtc_SQW_cb(); +} + +TIMER_CALLBACK( rtc65271_device::rtc_begin_update_callback ) +{ + rtc65271_device *rtc = reinterpret_cast<rtc65271_device *>(ptr); + rtc->rtc_begin_update_cb(); +} + +TIMER_CALLBACK( rtc65271_device::rtc_end_update_callback ) +{ + rtc65271_device *rtc = reinterpret_cast<rtc65271_device *>(ptr); + rtc->rtc_end_update_cb(); +} +/* + Update SQW output state each half-period and assert periodic interrupt each + period. +*/ +void rtc65271_device::rtc_SQW_cb() +{ + attotime half_period; + + m_SQW_internal_state = ! m_SQW_internal_state; + if (! m_SQW_internal_state) + { + /* high-to-low??? transition -> interrupt (or should it be low-to-high?) */ + m_regs[reg_C] |= reg_C_PF; + field_interrupts(); + } + + half_period = attotime::from_hz(SQW_freq_table[m_regs[reg_A] & reg_A_RS]) / 2; + m_SQW_timer->adjust(half_period); +} + +/* + Begin update cycle (called every second) +*/ +void rtc65271_device::rtc_begin_update_cb() +{ + if (((m_regs[reg_A] & reg_A_DV) == 0x20) && ! (m_regs[reg_B] & reg_B_SET)) + { + m_regs[reg_A] |= reg_A_UIP; + + /* schedule end of update cycle */ + machine().scheduler().timer_set(UPDATE_CYCLE_TIME, FUNC(rtc_end_update_callback), 0, (void *)this); + } +} + +/* + End update cycle (called UPDATE_CYCLE_TIME = 1948us after start of update + cycle) +*/ +void rtc65271_device::rtc_end_update_cb() +{ + static const int days_in_month_table[12] = + { + 31,28,31, 30,31,30, + 31,31,30, 31,30,31 + }; + UINT8 (*increment)(UINT8 data); + int c59, c23, c12, c11, c29; + + if (! (m_regs[reg_A] & reg_A_UIP)) + /* abort if update cycle has been canceled */ + return; + + if (m_regs[reg_B] & reg_B_DM) + { + /* binary mode */ + increment = increment_binary; + c59 = 59; + c23 = 23; + c12 = 12; + c11 = 11; + c29 = 29; + } + else + { + /* BCD mode */ + increment = increment_BCD; + c59 = 0x59; + c23 = 0x23; + c12 = 0x12; + c11 = 0x11; + c29 = 0x29; + } + + /* increment second */ + if (m_regs[reg_second] < c59) + m_regs[reg_second] = (*increment)(m_regs[reg_second]); + else + { + m_regs[reg_second] = 0; + + /* increment minute */ + if (m_regs[reg_minute] < c59) + m_regs[reg_minute] = (*increment)(m_regs[reg_minute]); + else + { + m_regs[reg_minute] = 0; + + /* increment hour */ + if (m_regs[reg_B] & reg_B_24h) + { + /* 24 hour mode */ + if (m_regs[reg_hour] < c23) + m_regs[reg_hour] = (*increment)(m_regs[reg_hour]); + else + m_regs[reg_hour] = 0; + } + else + { + /* 12 hour mode */ + if (m_regs[reg_hour] < c12) + { + if ((m_regs[reg_hour] & 0x7f) == c11) + m_regs[reg_hour] ^= 0x80; + m_regs[reg_hour] = ((*increment)(m_regs[reg_hour] & 0x7f) & 0x7f) + | (m_regs[reg_hour] & 0x80); + } + else + m_regs[reg_hour] = 1 | (m_regs[reg_hour] & 0x80); + } + + /* increment day if needed */ + if (m_regs[reg_hour] == ((m_regs[reg_B] & reg_B_24h) ? 0 : c12)) + { + /* increment day */ + int days_in_month; + + if (m_regs[reg_weekday] < 7) + m_regs[reg_weekday]++; + else + m_regs[reg_weekday] = 1; + + if ((m_regs[reg_month] != 2) || (m_regs[reg_year] & 0x03)) + { + if (m_regs[reg_B] & reg_B_DM) + { + /* binary mode */ + days_in_month = days_in_month_table[m_regs[reg_month] - 1]; + } + else + { + /* BCD mode */ + days_in_month = binary_to_BCD(days_in_month_table[BCD_to_binary(m_regs[reg_month]) - 1]); + } + } + else + days_in_month = c29; + + if (m_regs[reg_monthday] < days_in_month) + m_regs[reg_monthday] = (*increment)(m_regs[reg_monthday]); + else + { + /* increment month */ + m_regs[reg_monthday] = 1; + + if (m_regs[reg_month] < c12) + m_regs[reg_month] = (*increment)(m_regs[reg_month]); + else + { + /* increment year */ + m_regs[reg_month] = 1; + + if (m_regs[reg_B] & reg_B_DM) + { + /* binary mode */ + if (m_regs[reg_year] < 99) + m_regs[reg_year]++; + else + m_regs[reg_year] = 0; + } + else + { + /* BCD mode */ + m_regs[reg_year] = increment_BCD(m_regs[reg_year]); + } + } + } + } + } + } + + m_regs[reg_A] &= ~reg_A_UIP; + m_regs[reg_C] |= reg_C_UF; + + /* test for alarm (values in range 0xc0-0xff mean "don't care") */ + if ((((m_regs[reg_alarm_second] & 0xc0) == 0xc0) || (m_regs[reg_alarm_second] == m_regs[reg_second])) + && (((m_regs[reg_alarm_minute] & 0xc0) == 0xc0) || (m_regs[reg_alarm_minute] == m_regs[reg_minute])) + && (((m_regs[reg_alarm_hour] & 0xc0) == 0xc0) || (m_regs[reg_alarm_hour] == m_regs[reg_hour]))) + m_regs[reg_C] |= reg_C_AF; + + field_interrupts(); +} + +// device type definition +const device_type RTC65271 = &device_creator<rtc65271_device>; + +//------------------------------------------------- +// rtc65271_device - constructor +//------------------------------------------------- + +rtc65271_device::rtc65271_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, RTC65271, "RTC-65271", tag, owner, clock, "rtc65271", __FILE__), + device_nvram_interface(mconfig, *this), + m_interrupt_cb(*this) +{ +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- +void rtc65271_device::device_start() +{ + m_update_timer = machine().scheduler().timer_alloc(FUNC(rtc_begin_update_callback), (void *)this); + m_update_timer->adjust(attotime::from_seconds(1), 0, attotime::from_seconds(1)); + m_SQW_timer = machine().scheduler().timer_alloc(FUNC(rtc_SQW_callback), (void *)this); + m_interrupt_cb.resolve(); + + save_item(NAME(m_regs)); + save_item(NAME(m_cur_reg)); + save_item(NAME(m_xram)); + save_item(NAME(m_cur_xram_page)); + save_item(NAME(m_SQW_internal_state)); +} |