// 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 - constructor //------------------------------------------------- mc146818_device::mc146818_device(const machine_config &mconfig, std::string 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_clock_timer(nullptr), m_periodic_timer(nullptr), m_write_irq(*this), m_century_index(-1), m_epoch(0), m_use_utc(false), m_binary(false), m_hour(false), m_binyear(false) { } mc146818_device::mc146818_device(const machine_config &mconfig, device_type type, std::string name, std::string tag, device_t *owner, UINT32 clock, std::string shortname, std::string 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_clock_timer(nullptr), m_periodic_timer(nullptr), m_write_irq(*this), m_century_index(-1), m_epoch(0), m_use_utc(false), m_binary(false), m_hour(false), m_binyear(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 != nullptr) { 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); if(m_binyear) set_year((current_time.year - m_epoch) % (m_data[REG_B] & REG_B_DM ? 0x100 : 100)); // pcd actually depends on this else 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; } }