/********************************************************************* mc146818.c Implementation of the MC146818 chip Real time clock chip with battery buffered ram (or CMOS) Used in IBM PC/AT, several PC clones, Amstrad NC200 Nathan Woods (npwoods@mess.org) Peter Trauner (peter.trauner@jk.uni-linz.ac.at) PC CMOS info (based on info from Padgett Peterson): Clock Related: 0x00 Seconds (BCD 00-59, Hex 00-3B) Note: Bit 7 is read only 0x01 Second Alarm (BCD 00-59, Hex 00-3B; "don't care" if C0-FF) 0x02 Minutes (BCD 00-59, Hex 00-3B) 0x03 Minute Alarm (BCD 00-59, Hex 00-3B; "don't care" if C0-FF)) 0x04 Hours (BCD 00-23, Hex 00-17 if 24 hr mode) (BCD 01-12, Hex 01-0C if 12 hr am) (BCD 81-92. Hex 81-8C if 12 hr pm) 0x05 Hour Alarm (same as hours; "don't care" if C0-FF)) 0x06 Day of Week (01-07 Sunday=1) 0x07 Date of Month (BCD 01-31, Hex 01-1F) 0x08 Month (BCD 01-12, Hex 01-0C) 0x09 Year (BCD 00-99, Hex 00-63) 0x0B Status Register B (read/write) Bit 7 - 1 enables cycle update, 0 disables Bit 6 - 1 enables periodic interrupt Bit 5 - 1 enables alarm interrupt Bit 4 - 1 enables update-ended interrupt Bit 3 - 1 enables square wave output Bit 2 - Data Mode - 0: BCD, 1: Binary Bit 1 - 24/12 hour selection - 1 enables 24 hour mode Bit 0 - Daylight Savings Enable - 1 enables 0x0C Status Register C (Read only) Bit 7 - Interrupt request flag - 1 when any or all of bits 6-4 are 1 and appropriate enables (Register B) are set to 1. Generates IRQ 8 when triggered. Bit 6 - Periodic Interrupt flag Bit 5 - Alarm Interrupt flag Bit 4 - Update-Ended Interrupt Flag Bit 3-0 ??? 0x0D Status Register D (read only) Bit 7 - Valid RAM - 1 indicates batery power good, 0 if dead or disconnected. Bit 6-0 ??? Non-clock related: 0x0E (PS/2) Diagnostic Status Byte Bit 7 - When set (1) indicates clock has lost power Bit 6 - (1) indicates incorrect checksum Bit 5 - (1) indicates that equipment configuration is incorrect power-on check requires that atleast one floppy be installed Bit 4 - (1) indicates error in memory size Bit 3 - (1) indicates that controller or disk drive failed initialization Bit 2 - (1) indicates that time is invalid Bit 1 - (1) indicates installed adaptors do not match configuration Bit 0 - (1) indicates a time-out while reading adaptor ID 0x0E (AMSTRAD) 6 BYTEs time and date machine last used 0x0F Reset Code (IBM PS/2 "Shutdown Status Byte") 0x00-0x03 perform power-on reset 0x04 INT 19h reboot 0x05 flush keyboard and jump via 0040:0067 0x06-0x07 reserved 0x08 used by POST during protected-mode RAM test 0x09 used for INT 15/87h (block move) support 0x0A jump via 0040:0067 0x0B-0xFF perform power-on reset *********************************************************************/ #include "emu.h" #include "coreutil.h" #include "machine/mc146818.h" //************************************************************************** // DEBUGGING //************************************************************************** #define LOG_MC146818 0 //************************************************************************** // MACROS //************************************************************************** #define USE_UTC 1 #define HOURS_24 (m_data[0xb]&2) #define BCD_MODE !(m_data[0xb]&4) // book has other description! #define CENTURY m_data[100] #define YEAR m_data[9] #define MONTH m_data[8] #define DAY m_data[7] #define WEEK_DAY m_data[6] //************************************************************************** // LIVE DEVICE //************************************************************************** // device type definition const device_type MC146818 = &device_creator; //------------------------------------------------- // mc146818_device - constructor //------------------------------------------------- mc146818_device::mc146818_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, MC146818, "NVRAM", tag, owner, clock), device_rtc_interface(mconfig, *this), device_nvram_interface(mconfig, *this), m_type(MC146818_STANDARD), m_index(0), m_eindex(0), m_updated(false), m_last_refresh(attotime::zero) { } //------------------------------------------------- // static_set_interface - configuration helper // to set the interface //------------------------------------------------- void mc146818_device::static_set_type(device_t &device, mc146818_type type) { downcast(device).m_type = type; } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void mc146818_device::device_start() { m_last_refresh = machine().time(); m_clock_timer = timer_alloc(TIMER_CLOCK); m_periodic_timer = timer_alloc(TIMER_PERIODIC); memset(m_data, 0, sizeof(m_data)); if (m_type == MC146818_UTC) { // hack: for apollo we increase the update frequency to stay in sync with real time m_clock_timer->adjust(attotime::from_hz(2), 0, attotime::from_hz(2)); } else { m_clock_timer->adjust(attotime::from_hz(1), 0, attotime::from_hz(1)); } m_periodic_timer->adjust(attotime::never); m_period = attotime::never; set_base_datetime(); m_out_irq_func.resolve(m_out_irq_cb, *this); } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void mc146818_device::device_config_complete() { // inherit a copy of the static data const mc146818_interface *intf = reinterpret_cast(static_config()); if (intf != NULL) *static_cast(this) = *intf; // or initialize to defaults if none provided else { memset(&m_out_irq_cb, 0, sizeof(m_out_irq_cb)); } } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void mc146818_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { int year/*, month*/; if (id == TIMER_PERIODIC) { m_data[0x0c] |= 0xc0; if (!m_out_irq_func.isnull()) m_out_irq_func(CLEAR_LINE); return; } if (m_type == MC146818_UTC) { // hack: set correct real time even for overloaded emulation // (at least for apollo) static osd_ticks_t t0 = 0; osd_ticks_t t1 = osd_ticks(); int n_seconds; if (t0 == 0) { t0 = t1; } n_seconds = (t1 - t0) / osd_ticks_per_second(); t0 = t1 - (t1 - t0) % osd_ticks_per_second(); if (n_seconds <= 0) { // we were called to early return; } m_data[0] += n_seconds; } else { m_data[0] += 1; } if (BCD_MODE) { m_data[0]=bcd_adjust(m_data[0]/*+1*/); if (m_data[0]>=0x60) { m_data[0]=0; m_data[2]=bcd_adjust(m_data[2]+1); if (m_data[2]>=0x60) { m_data[2]=0; m_data[4]=bcd_adjust(m_data[4]+1); // different handling of hours if (m_data[4]>=0x24) { m_data[4]=0; WEEK_DAY=bcd_adjust(WEEK_DAY+1)%7; DAY=bcd_adjust(DAY+1); //month=bcd_2_dec(MONTH); year=bcd_2_dec(YEAR); if (m_type!=MC146818_IGNORE_CENTURY) year+=bcd_2_dec(CENTURY)*100; else year+=2000; // save for julian_days_in_month calculation DAY=bcd_adjust(DAY+1); if (DAY>gregorian_days_in_month(MONTH, year)) { DAY=1; MONTH=bcd_adjust(MONTH+1); if (MONTH>0x12) { MONTH=1; YEAR=year=bcd_adjust(YEAR+1); if (m_type!=MC146818_IGNORE_CENTURY) { if (year>=0x100) { CENTURY=bcd_adjust(CENTURY+1); } } } } } } } } else { /*m_data[0]=m_data[0]+1;*/ if (m_data[0]>=60) { m_data[0] -= 60; m_data[2]=m_data[2]+1; if (m_data[2]>=60) { m_data[2]=0; m_data[4]=m_data[4]+1; // different handling of hours //? if (m_data[4]>=24) { m_data[4]=0; WEEK_DAY=(WEEK_DAY+1)%7; year=YEAR; if (m_type!=MC146818_IGNORE_CENTURY) year+=CENTURY*100; else year+=2000; // save for julian_days_in_month calculation if (++DAY>gregorian_days_in_month(MONTH, year)) { DAY=1; if (++MONTH>12) { MONTH=1; YEAR++; if (m_type!=MC146818_IGNORE_CENTURY) { if (YEAR>=100) { CENTURY++;YEAR=0; } } else { YEAR%=100; } } } } } } } if (m_data[1] == m_data[0] && // m_data[3] == m_data[2] && // m_data[5] == m_data[4]) { // set the alarm interrupt flag AF m_data[0x0c] |= 0x20; } else { // clear the alarm interrupt flag AF m_data[0x0c] &= ~0x20; if ((m_data[0x0c] & 0x70) == 0) { // clear IRQF m_data[0x0c] &= ~0x80; } } // set the update-ended interrupt Flag UF m_data[0x0c] |= 0x10; // set the interrupt request flag IRQF // FIXME: should throw IRQ line as well if ((m_data[0x0b] & m_data[0x0c] & 0x30) != 0) { m_data[0x0c] |= 0x80; } // IRQ line is active low if (!m_out_irq_func.isnull()) m_out_irq_func((m_data[0x0c] & 0x80) ? CLEAR_LINE : ASSERT_LINE); m_updated = true; /* clock has been updated */ m_last_refresh = machine().time(); } //------------------------------------------------- // rtc_set_time - called to initialize the RTC to // a known state //------------------------------------------------- void mc146818_device::rtc_set_time(int year, int month, int day, int day_of_week, int hour, int minute, int second) { YEAR = year; MONTH = month; DAY = day; WEEK_DAY = day_of_week; m_data[4] = hour; m_data[2] = minute; m_data[0] = second; } //------------------------------------------------- // nvram_default - called to initialize NVRAM to // its default state //------------------------------------------------- void mc146818_device::nvram_default() { set_base_datetime(); } //------------------------------------------------- // nvram_read - called to read NVRAM from the // .nv file //------------------------------------------------- void mc146818_device::nvram_read(emu_file &file) { file.read(m_data, sizeof(m_data)); set_base_datetime(); } //------------------------------------------------- // nvram_write - called to write NVRAM to the // .nv file //------------------------------------------------- void mc146818_device::nvram_write(emu_file &file) { file.write(m_data, sizeof(m_data)); } //------------------------------------------------- // dec_2_local - convert from decimal to BCD if // necessary //------------------------------------------------- inline int mc146818_device::dec_2_local(int a) { return BCD_MODE ? dec_2_bcd(a) : a; } //------------------------------------------------- // dec_2_local - convert from decimal to BCD if // necessary //------------------------------------------------- void mc146818_device::set_base_datetime() { system_time systime; system_time::full_time current_time; machine().base_datetime(systime); current_time = (m_type == MC146818_UTC) ? systime.utc_time: systime.local_time; // temporary hack to go back 20 year (e.g. from 2010 -> 1990) // current_time.year -= 20; // 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); if (HOURS_24 || (current_time.hour < 12)) m_data[4] = dec_2_local(current_time.hour); else m_data[4] = dec_2_local(current_time.hour - 12) | 0x80; if (m_type != MC146818_IGNORE_CENTURY) CENTURY = dec_2_local(current_time.year /100); m_data[0] = dec_2_local(current_time.second); m_data[2] = dec_2_local(current_time.minute); DAY = dec_2_local(current_time.mday); MONTH = dec_2_local(current_time.month + 1); YEAR = dec_2_local(current_time.year % 100); WEEK_DAY = current_time.weekday; if (current_time.is_dst) m_data[0xb] |= 1; else m_data[0xb] &= ~1; } //------------------------------------------------- // 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 % MC146818_DATA_SIZE) { case 0xa: data = m_data[m_index % MC146818_DATA_SIZE]; if ((space.machine().time() - m_last_refresh) < attotime::from_hz(32768)) data |= 0x80; #if 0 /* for pc1512 bios realtime clock test */ m_data[m_index % MC146818_DATA_SIZE] ^= 0x80; /* 0x80 update in progress */ #endif break; case 0xc: // if(m_updated) /* the clock has been updated */ // data = 0x10; // else // data = 0x00; // the unused bits b0 ... b3 are always read as 0 data = m_data[m_index % MC146818_DATA_SIZE] & 0xf0; // read 0x0c will clear all IRQ flags in register 0x0c m_data[m_index % MC146818_DATA_SIZE] &= 0x0f; if (!m_out_irq_func.isnull()) m_out_irq_func(ASSERT_LINE); break; case 0xd: /* battery ok */ data = m_data[m_index % MC146818_DATA_SIZE] | 0x80; break; default: data = m_data[m_index % MC146818_DATA_SIZE]; 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 ) { attotime rate; if (LOG_MC146818) logerror("mc146818_port_w(): index=0x%02x data=0x%02x\n", m_index, data); switch (offset) { case 0: m_index = data; break; case 1: switch(m_index % MC146818_DATA_SIZE) { case 0x0a: // fixme: allow different time base data &= 0x0f; if (m_data[0x0b] & 0x40) { if (data > 2) m_period = attotime::from_hz(32768 >> (data - 1)); else if (data > 0) m_period = attotime::from_hz(32768 >> (data + 6)); else m_period = attotime::never; rate = attotime::zero; } else rate = attotime::never; m_periodic_timer->adjust(rate, 0, m_period); data |= m_data[m_index % MC146818_DATA_SIZE] & 0xf0; m_data[m_index % MC146818_DATA_SIZE] = data; break; case 0x0b: if(data & 0x80) m_updated = false; // this probably isn't right but otherwise // you'll be making a lot of unnecessary callbacks if (data & 0x40) m_periodic_timer->adjust(attotime::zero, 0, m_period); else m_periodic_timer->adjust(attotime::never); m_data[m_index % MC146818_DATA_SIZE] = data; break; case 0x0c: // register 0x0c is readonly break; default: m_data[m_index % MC146818_DATA_SIZE] = data; } break; } }