/********************************************************************** Ricoh RP5C15 Real Time Clock emulation Copyright MESS Team. Visit http://mamedev.org for licensing and usage restrictions. *********************************************************************/ /* TODO: - 12 hour clock - test register - timer reset */ #include "rp5c15.h" //************************************************************************** // MACROS / CONSTANTS //************************************************************************** #define LOG 0 // registers enum { REGISTER_1_SECOND = 0, REGISTER_CLOCK_OUTPUT = REGISTER_1_SECOND, REGISTER_10_SECOND, REGISTER_ADJUST = REGISTER_10_SECOND, REGISTER_1_MINUTE, REGISTER_10_MINUTE, REGISTER_1_HOUR, REGISTER_10_HOUR, REGISTER_DAY_OF_THE_WEEK, REGISTER_1_DAY, REGISTER_10_DAY, REGISTER_1_MONTH, REGISTER_10_MONTH, REGISTER_12_24_SELECT = REGISTER_10_MONTH, REGISTER_1_YEAR, REGISTER_LEAP_YEAR = REGISTER_1_YEAR, REGISTER_10_YEAR, REGISTER_MODE, REGISTER_TEST, REGISTER_RESET }; // clock output select enum { CLKOUT_Z = 0, CLKOUT_16384_HZ, CLKOUT_1024_HZ, CLKOUT_128_HZ, CLKOUT_16_HZ, CLKOUT_1_HZ, CLKOUT_1_DIV_60_HZ, CLKOUT_L }; // register write mask static const int REGISTER_WRITE_MASK[2][16] = { { 0xf, 0x7, 0xf, 0x7, 0xf, 0x3, 0x7, 0xf, 0x3, 0xf, 0x1, 0xf, 0xf, 0xf, 0xf, 0xf }, { 0x3, 0x1, 0xf, 0x7, 0xf, 0x3, 0x7, 0xf, 0x3, 0x0, 0x1, 0x3, 0x0, 0xf, 0xf, 0xf } }; // days per month static const int DAYS_PER_MONTH[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // modes enum { MODE00 = 0, MODE01 }; // mode register #define MODE_MASK 0x01 #define MODE_ALARM_EN 0x04 #define MODE_TIMER_EN 0x08 // test register #define TEST_0 0x01 #define TEST_1 0x02 #define TEST_2 0x04 #define TEST_3 0x08 // reset register #define RESET_ALARM 0x01 #define RESET_TIMER 0x02 #define RESET_16_HZ 0x04 #define RESET_1_HZ 0x08 //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // devices const device_type RP5C15 = &device_creator; //************************************************************************** // INLINE HELPERS //************************************************************************** //------------------------------------------------- // set_alarm_line - //------------------------------------------------- inline void rp5c15_device::set_alarm_line() { int alarm = ((m_mode & MODE_ALARM_EN) ? m_alarm_on : 1) & ((m_reset & RESET_16_HZ) ? 1 : m_16hz) & ((m_reset & RESET_1_HZ) ? 1 : m_1hz); if (m_alarm != alarm) { if (LOG) logerror("RP5C15 '%s' Alarm %u\n", tag(), alarm); m_out_alarm_func(alarm); m_alarm = alarm; } } //------------------------------------------------- // read_counter - //------------------------------------------------- inline int rp5c15_device::read_counter(int counter) { return (m_reg[MODE00][counter + 1] * 10) + m_reg[MODE00][counter]; } //------------------------------------------------- // write_counter - //------------------------------------------------- inline void rp5c15_device::write_counter(int counter, int value) { m_reg[MODE00][counter] = value % 10; m_reg[MODE00][counter + 1] = value / 10; } //------------------------------------------------- // advance_seconds - //------------------------------------------------- inline void rp5c15_device::advance_seconds() { int seconds = read_counter(REGISTER_1_SECOND); seconds++; if (seconds > 59) { seconds = 0; advance_minutes(); } write_counter(REGISTER_1_SECOND, seconds); } //------------------------------------------------- // advance_minutes - //------------------------------------------------- inline void rp5c15_device::advance_minutes() { int minutes = read_counter(REGISTER_1_MINUTE); int hours = read_counter(REGISTER_1_HOUR); int days = read_counter(REGISTER_1_DAY); int month = read_counter(REGISTER_1_MONTH); int year = read_counter(REGISTER_1_YEAR); int day_of_week = m_reg[MODE00][REGISTER_DAY_OF_THE_WEEK]; minutes++; if (minutes > 59) { minutes = 0; hours++; } if (hours > 23) { hours = 0; days++; day_of_week++; } if (day_of_week > 6) { day_of_week++; } if (days > DAYS_PER_MONTH[month - 1]) { days = 1; month++; } if (month > 12) { month = 1; year++; m_reg[MODE01][REGISTER_LEAP_YEAR]++; m_reg[MODE01][REGISTER_LEAP_YEAR] &= 0x03; } if (year > 99) { year = 0; } write_counter(REGISTER_1_MINUTE, minutes); write_counter(REGISTER_1_HOUR, hours); write_counter(REGISTER_1_DAY, days); write_counter(REGISTER_1_MONTH, month); write_counter(REGISTER_1_YEAR, year); m_reg[MODE00][REGISTER_DAY_OF_THE_WEEK] = day_of_week; check_alarm(); set_alarm_line(); } //------------------------------------------------- // adjust_seconds - //------------------------------------------------- inline void rp5c15_device::adjust_seconds() { int seconds = read_counter(REGISTER_1_SECOND); if (seconds < 30) { write_counter(REGISTER_1_SECOND, 0); } else { write_counter(REGISTER_1_SECOND, 0); advance_minutes(); } } //------------------------------------------------- // check_alarm - //------------------------------------------------- inline void rp5c15_device::check_alarm() { bool all_match = true; bool all_zeroes = true; for (int i = REGISTER_1_MINUTE; i < REGISTER_1_MONTH; i++) { if (m_reg[MODE01][i] != 0) all_zeroes = false; if (m_reg[MODE01][i] != m_reg[MODE00][i]) all_match = false; } m_alarm_on = (all_match || (!m_alarm_on && all_zeroes)) ? 0 : 1; } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // rp5c15_device - constructor //------------------------------------------------- rp5c15_device::rp5c15_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, RP5C15, "RP5C15", tag, owner, clock), device_rtc_interface(mconfig, *this), m_alarm(1), m_alarm_on(1), m_1hz(1), m_16hz(1), m_clkout(1) { } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void rp5c15_device::device_config_complete() { // inherit a copy of the static data const rp5c15_interface *intf = reinterpret_cast(static_config()); if (intf != NULL) *static_cast(this) = *intf; // or initialize to defaults if none provided else { memset(&m_out_alarm_cb, 0, sizeof(m_out_alarm_cb)); memset(&m_out_clkout_cb, 0, sizeof(m_out_clkout_cb)); } } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void rp5c15_device::device_start() { // resolve callbacks m_out_alarm_func.resolve(m_out_alarm_cb, *this); m_out_clkout_func.resolve(m_out_clkout_cb, *this); // allocate timers m_clock_timer = timer_alloc(TIMER_CLOCK); m_clock_timer->adjust(attotime::from_hz(clock() / 16384), 0, attotime::from_hz(clock() / 16384)); m_16hz_timer = timer_alloc(TIMER_16HZ); m_16hz_timer->adjust(attotime::from_hz(clock() / 1024), 0, attotime::from_hz(clock() / 1024)); m_clkout_timer = timer_alloc(TIMER_CLKOUT); // state saving save_item(NAME(m_reg[MODE00])); save_item(NAME(m_reg[MODE01])); save_item(NAME(m_mode)); save_item(NAME(m_reset)); save_item(NAME(m_alarm)); save_item(NAME(m_alarm_on)); save_item(NAME(m_1hz)); save_item(NAME(m_16hz)); save_item(NAME(m_clkout)); } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void rp5c15_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TIMER_CLOCK: if (m_1hz && (m_mode & MODE_TIMER_EN)) { advance_seconds(); } m_1hz = !m_1hz; set_alarm_line(); break; case TIMER_16HZ: m_16hz = !m_16hz; set_alarm_line(); break; case TIMER_CLKOUT: m_clkout = !m_clkout; m_out_clkout_func(m_clkout); break; } } //------------------------------------------------- // rtc_set_time - called to initialize the RTC to // a known state //------------------------------------------------- void rp5c15_device::rtc_set_time(int year, int month, int day, int day_of_week, int hour, int minute, int second) { write_counter(REGISTER_1_YEAR, year); write_counter(REGISTER_1_MONTH, month); write_counter(REGISTER_1_DAY, day); m_reg[MODE00][REGISTER_DAY_OF_THE_WEEK] = day_of_week; write_counter(REGISTER_1_HOUR, hour); write_counter(REGISTER_1_MINUTE, minute); write_counter(REGISTER_1_SECOND, second); } //------------------------------------------------- // read - //------------------------------------------------- READ8_MEMBER( rp5c15_device::read ) { UINT8 data = 0; switch (offset & 0x0f) { case REGISTER_MODE: data = m_mode; break; case REGISTER_TEST: case REGISTER_RESET: // write only break; default: data = m_reg[m_mode & MODE_MASK][offset]; break; } if (LOG) logerror("RP5C15 '%s' Register %u Read %02x\n", tag(), offset & 0x0f, data); return data & 0x0f; } //------------------------------------------------- // write - //------------------------------------------------- WRITE8_MEMBER( rp5c15_device::write ) { int mode = m_mode & MODE_MASK; switch (offset & 0x0f) { case REGISTER_MODE: m_mode = data & 0x0f; if (LOG) { logerror("RP5C15 '%s' Mode %u\n", tag(), data & MODE_MASK); logerror("RP5C15 '%s' Timer %s\n", tag(), (data & MODE_TIMER_EN) ? "enabled" : "disabled"); logerror("RP5C15 '%s' Alarm %s\n", tag(), (data & MODE_ALARM_EN) ? "enabled" : "disabled"); } break; case REGISTER_TEST: if (LOG) logerror("RP5C15 '%s' Test %u not supported!\n", tag(), data); break; case REGISTER_RESET: m_reset = data & 0x0f; if (data & RESET_ALARM) { int i; // reset alarm registers for (i = REGISTER_1_MINUTE; i < REGISTER_1_MONTH; i++) { m_reg[MODE01][i] = 0; } } if (LOG) { if (data & RESET_ALARM) logerror("RP5C15 '%s' Alarm Reset\n", tag()); if (data & RESET_TIMER) logerror("RP5C15 '%s' Timer Reset not supported!\n", tag()); logerror("RP5C15 '%s' 16Hz Signal %s\n", tag(), (data & RESET_16_HZ) ? "disabled" : "enabled"); logerror("RP5C15 '%s' 1Hz Signal %s\n", tag(), (data & RESET_1_HZ) ? "disabled" : "enabled"); } break; default: switch (mode) { case MODE00: m_reg[mode][offset & 0x0f] = data & REGISTER_WRITE_MASK[mode][offset & 0x0f]; break; case MODE01: switch (offset & 0x0f) { case REGISTER_CLOCK_OUTPUT: switch (data & 0x07) { case CLKOUT_16384_HZ: m_clkout_timer->adjust(attotime::from_hz(clock()), 0, attotime::from_hz(clock())); break; case CLKOUT_1024_HZ: m_clkout_timer->adjust(attotime::from_hz(clock() / 16), 0, attotime::from_hz(clock() / 16)); break; case CLKOUT_128_HZ: m_clkout_timer->adjust(attotime::from_hz(clock() / 128), 0, attotime::from_hz(clock() / 128)); break; case CLKOUT_16_HZ: m_clkout_timer->adjust(attotime::from_hz(clock() / 1024), 0, attotime::from_hz(clock() / 1024)); break; case CLKOUT_1_HZ: m_clkout_timer->adjust(attotime::from_hz(clock() / 16384), 0, attotime::from_hz(clock() / 16384)); break; case CLKOUT_1_DIV_60_HZ: // TODO break; case CLKOUT_L: case CLKOUT_Z: m_clkout = 1; m_clkout_timer->adjust(attotime::zero, 0); break; } m_reg[mode][offset & 0x0f] = data & REGISTER_WRITE_MASK[mode][offset & 0x0f]; break; case REGISTER_ADJUST: if (BIT(data, 0)) { adjust_seconds(); } m_reg[mode][offset & 0x0f] = data & REGISTER_WRITE_MASK[mode][offset & 0x0f]; break; default: m_reg[mode][offset & 0x0f] = data & REGISTER_WRITE_MASK[mode][offset & 0x0f]; break; } break; } if (LOG) logerror("RP5C15 '%s' Register %u Write %02x\n", tag(), offset & 0x0f, data); break; } }