// 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 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_t */ static uint8_t increment_binary(uint8_t data) { return data+1; } /* Increment a BCD-encoded uint8_t */ static uint8_t increment_BCD(uint8_t 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_t to BCD */ static uint8_t binary_to_BCD(uint8_t data) { data %= 100; return ((data / 10) << 4) | (data %10); } /* Convert a BCD-encoded uint8_t to binary */ static uint8_t BCD_to_binary(uint8_t 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_t 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_t 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_t 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; } uint8_t rtc65271_device::rtc_r(offs_t offset) { return read(0, offset ); } uint8_t rtc65271_device::xram_r(offs_t offset) { 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_t 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; } } void rtc65271_device::rtc_w(offs_t offset, uint8_t data) { write(0, offset, data ); } void rtc65271_device::xram_w(offs_t offset, uint8_t data) { 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); } } /* Update SQW output state each half-period and assert periodic interrupt each period. */ TIMER_CALLBACK_MEMBER(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) */ TIMER_CALLBACK_MEMBER(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, timer_expired_delegate(FUNC(rtc65271_device::rtc_end_update_cb), this)); } } /* End update cycle (called UPDATE_CYCLE_TIME = 1948us after start of update cycle) */ TIMER_CALLBACK_MEMBER(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_t (*increment)(uint8_t 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 DEFINE_DEVICE_TYPE(RTC65271, rtc65271_device, "rtc65271", "Epson RTC-65271 RTC") //------------------------------------------------- // rtc65271_device - constructor //------------------------------------------------- rtc65271_device::rtc65271_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, RTC65271, tag, owner, clock) , 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(timer_expired_delegate(FUNC(rtc65271_device::rtc_begin_update_cb), this)); m_update_timer->adjust(attotime::from_seconds(1), 0, attotime::from_seconds(1)); m_SQW_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(rtc65271_device::rtc_SQW_cb), 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)); }