summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/machine/rtc65271.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/machine/rtc65271.c')
-rw-r--r--src/emu/machine/rtc65271.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/src/emu/machine/rtc65271.c b/src/emu/machine/rtc65271.c
new file mode 100644
index 00000000000..5352a91c6d5
--- /dev/null
+++ b/src/emu/machine/rtc65271.c
@@ -0,0 +1,654 @@
+/*
+ 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
+*/
+
+#include "driver.h"
+#include "rtc65271.h"
+
+static void field_interrupts(void);
+static TIMER_CALLBACK( rtc_SQW_callback );
+static TIMER_CALLBACK( rtc_begin_update_callback );
+static TIMER_CALLBACK( rtc_end_update_callback );
+
+/* Delay between the beginning (UIP asserted) and the end (UIP cleared and
+update interrupt asserted) of the update cycle */
+#define UPDATE_CYCLE_TIME ATTOTIME_IN_USEC(1984)
+/* Delay between the assertion of UIP and the effective start of the update
+cycle */
+/*#define UPDATE_CYCLE_DELAY ATTOTIME_IN_USEC(244)*/
+
+static struct
+{
+ /* 64 8-bit registers (10 clock registers, 4 control/status registers, and
+ 50 bytes of user RAM) */
+ UINT8 regs[64];
+ int cur_reg;
+
+ /* extended RAM: 4kbytes of battery-backed RAM (in pages of 32 bytes) */
+ UINT8 *xram;
+ int cur_xram_page;
+
+ /* update timer: called every second */
+ emu_timer *update_timer;
+
+ /* SQW timer: called every periodic clock half-period */
+ emu_timer *SQW_timer;
+ int SQW_internal_state;
+
+ /* callback called when interrupt pin state changes (may be NULL) */
+ void (*interrupt_callback)(int state);
+} rtc;
+
+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
+*/
+
+
+/*
+ load the SRAM and register contents from file
+*/
+int rtc65271_file_load(mame_file *file)
+{
+ UINT8 buf;
+
+
+ /* version flag */
+ if (mame_fread(file, & buf, 1) != 1)
+ return 1;
+ if (buf != 0)
+ return 1;
+
+ /* control registers */
+ if (mame_fread(file, &buf, 1) != 1)
+ return 1;
+ rtc.regs[reg_A] = buf & (reg_A_DV /*| reg_A_RS*/);
+ if (mame_fread(file, &buf, 1) != 1)
+ return 1;
+ rtc.regs[reg_B] = buf & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE);
+
+ /* alarm registers */
+ if (mame_fread(file, &rtc.regs[reg_alarm_second], 1) != 1)
+ return 1;
+ if (mame_fread(file, &rtc.regs[reg_alarm_minute], 1) != 1)
+ return 1;
+ if (mame_fread(file, &rtc.regs[reg_alarm_hour], 1) != 1)
+ return 1;
+
+ /* user RAM */
+ if (mame_fread(file, rtc.regs+14, 50) != 50)
+ return 1;
+
+ /* extended RAM */
+ if (mame_fread(file, rtc.xram, 4096) != 4096)
+ return 1;
+
+ rtc.regs[reg_D] |= reg_D_VRT; /* the data was backed up successfully */
+ /*rtc.dirty = FALSE;*/
+
+ {
+ mame_system_time systime;
+
+ /* get the current date/time from the core */
+ mame_get_current_datetime(Machine, &systime);
+
+ /* set clock registers */
+ rtc.regs[reg_second] = systime.local_time.second;
+ rtc.regs[reg_minute] = systime.local_time.minute;
+ if (rtc.regs[reg_B] & reg_B_24h)
+ /* 24-hour mode */
+ rtc.regs[reg_hour] = systime.local_time.hour;
+ else
+ { /* 12-hour mode */
+ if (systime.local_time.hour >= 12)
+ {
+ rtc.regs[reg_hour] = 0x80;
+ systime.local_time.hour -= 12;
+ }
+ else
+ rtc.regs[reg_hour] = 0;
+ rtc.regs[reg_hour] |= systime.local_time.hour ? systime.local_time.hour : 12;
+ }
+ rtc.regs[reg_weekday] = systime.local_time.weekday + 1;
+ rtc.regs[reg_monthday] = systime.local_time.mday;
+ rtc.regs[reg_month] = systime.local_time.month + 1;
+ rtc.regs[reg_year] = systime.local_time.year % 100;
+ if (! (rtc.regs[reg_B] & reg_B_DM))
+ { /* BCD mode */
+ rtc.regs[reg_second] = binary_to_BCD(rtc.regs[reg_second]);
+ rtc.regs[reg_minute] = binary_to_BCD(rtc.regs[reg_minute]);
+ rtc.regs[reg_hour] = (rtc.regs[reg_hour] & 0x80) | binary_to_BCD(rtc.regs[reg_hour] & 0x7f);
+ /*rtc.regs[reg_weekday] = binary_to_BCD(rtc.regs[reg_weekday]);*/
+ rtc.regs[reg_monthday] = binary_to_BCD(rtc.regs[reg_monthday]);
+ rtc.regs[reg_month] = binary_to_BCD(rtc.regs[reg_month]);
+ rtc.regs[reg_year] = binary_to_BCD(rtc.regs[reg_year]);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ save the SRAM and register contents to file
+*/
+int rtc65271_file_save(mame_file *file)
+{
+ UINT8 buf;
+
+
+ /* version flag */
+ buf = 0;
+ if (mame_fwrite(file, & buf, 1) != 1)
+ return 1;
+
+ /* control registers */
+ buf = rtc.regs[reg_A] & (reg_A_DV | reg_A_RS);
+ if (mame_fwrite(file, &buf, 1) != 1)
+ return 1;
+ buf = rtc.regs[reg_B] & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE);
+ if (mame_fwrite(file, &buf, 1) != 1)
+ return 1;
+
+ /* alarm registers */
+ if (mame_fwrite(file, &rtc.regs[reg_alarm_second], 1) != 1)
+ return 1;
+ if (mame_fwrite(file, &rtc.regs[reg_alarm_minute], 1) != 1)
+ return 1;
+ if (mame_fwrite(file, &rtc.regs[reg_alarm_hour], 1) != 1)
+ return 1;
+
+ /* user RAM */
+ if (mame_fwrite(file, rtc.regs+14, 50) != 50)
+ return 1;
+
+ /* extended RAM */
+ if (mame_fwrite(file, rtc.xram, 4096) != 4096)
+ return 1;
+
+ return 0;
+}
+
+/*
+ Initialize clock
+
+ xram: pointer to 4kb RAM area
+ interrupt_callback: callback called when interrupt pin state changes (may
+ be NULL)
+*/
+void rtc65271_init(UINT8 *xram, void (*interrupt_callback)(int state))
+{
+ memset(&rtc, 0, sizeof(rtc));
+
+ rtc.xram = xram;
+
+ rtc.update_timer = timer_alloc(rtc_begin_update_callback);
+ timer_adjust(rtc.update_timer, ATTOTIME_IN_SEC(1), 0, ATTOTIME_IN_SEC(1));
+ rtc.SQW_timer = timer_alloc(rtc_SQW_callback);
+ rtc.interrupt_callback = interrupt_callback;
+}
+
+/*
+ Read a byte from clock
+
+ xramsel: select RTC register if 0, XRAM if 1
+ offset: address (A0-A5 pins)
+*/
+UINT8 rtc65271_r(int xramsel, offs_t offset)
+{
+ int reply;
+
+ if (xramsel)
+ {
+ if (offset & 0x20)
+ /* XRAM page register */
+ reply = rtc.cur_xram_page;
+ else
+ /* XRAM data */
+ reply = rtc.xram[(offset & 0x1f) + 0x0020*rtc.cur_xram_page];
+ }
+ else
+ {
+ if (offset & 0x01)
+ /* data register */
+ switch (rtc.cur_reg)
+ {
+ case reg_C:
+ reply = rtc.regs[rtc.cur_reg];
+ rtc.regs[rtc.cur_reg] = 0;
+ field_interrupts();
+ break;
+ case reg_D:
+ reply = rtc.regs[rtc.cur_reg];
+ rtc.regs[rtc.cur_reg] = /*0*/reg_D_VRT; /* set VRT flag so that the computer does not complain that the battery is low */
+ break;
+
+ default:
+ reply = rtc.regs[rtc.cur_reg];
+ break;
+ }
+ else
+ /* indirect address register */
+ reply = rtc.cur_reg;
+ }
+
+ return reply;
+}
+
+
+/*
+ Read a byte from clock
+
+ xramsel: select RTC register if 0, XRAM if 1
+ offset: address (A0-A5 pins)
+*/
+void rtc65271_w(int xramsel, offs_t offset, UINT8 data)
+{
+ if (xramsel)
+ {
+ if (offset & 0x20)
+ /* XRAM page register */
+ rtc.cur_xram_page = data & 0x7f;
+ else
+ /* XRAM data */
+ rtc.xram[(offset & 0x1f) + 0x0020*rtc.cur_xram_page] = data;
+ }
+ else
+ {
+ if (offset & 0x01)
+ /* data register */
+ switch (rtc.cur_reg)
+ {
+ case reg_second:
+ /* the data sheet says bit 7 is read-only. (I have no idea of
+ the reason why it is.) */
+ rtc.regs[reg_second] = data & 0x7f;
+ break;
+
+ case reg_A:
+ if ((data & reg_A_RS) != (rtc.regs[rtc.cur_reg] & reg_A_RS))
+ {
+ if (data & reg_A_RS)
+ {
+ attotime period = ATTOTIME_IN_HZ(SQW_freq_table[data & reg_A_RS]);
+ attotime half_period = attotime_div(period, 2);
+ attotime elapsed = timer_timeelapsed(rtc.update_timer);
+
+ if (attotime_compare(half_period, elapsed) > 0)
+ timer_adjust(rtc.SQW_timer, attotime_sub(half_period, elapsed), 0, attotime_never);
+ else
+ timer_adjust(rtc.SQW_timer, half_period, 0, attotime_never);
+ }
+ else
+ {
+ rtc.SQW_internal_state = 0; /* right??? */
+
+ /* Stop the divider used for SQW and periodic interrupts. */
+ timer_adjust(rtc.SQW_timer, attotime_never, 0, attotime_never);
+ }
+ }
+ /* The UIP bit is read-only */
+ rtc.regs[reg_A] = (data & ~reg_A_UIP) | (rtc.regs[reg_A] & reg_A_UIP);
+ break;
+
+ case reg_B:
+ rtc.regs[rtc.cur_reg] = data;
+ if (data & reg_B_SET)
+ {
+ /* if we are in SET mode, clear update cycle */
+ rtc.regs[reg_A] &= ~reg_A_UIP;
+ rtc.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:
+ rtc.regs[rtc.cur_reg] = data;
+ break;
+ }
+ else
+ /* indirect address register */
+ rtc.cur_reg = data & 0x3f;
+ }
+}
+
+static void field_interrupts(void)
+{
+ if (rtc.regs[reg_C] & rtc.regs[reg_B] & (reg_C_PF | reg_C_AF | reg_C_UF))
+ {
+ rtc.regs[reg_C] |= reg_C_IRQF;
+ if (rtc.interrupt_callback)
+ rtc.interrupt_callback(1);
+ }
+ else
+ {
+ rtc.regs[reg_C] &= ~reg_C_IRQF;
+ if (rtc.interrupt_callback)
+ rtc.interrupt_callback(0);
+ }
+}
+
+
+/*
+ Timer handlers
+*/
+
+/*
+ Update SQW output state each half-period and assert periodic interrupt each
+ period.
+*/
+static TIMER_CALLBACK( rtc_SQW_callback )
+{
+ attotime half_period;
+
+ rtc.SQW_internal_state = ! rtc.SQW_internal_state;
+ if (! rtc.SQW_internal_state)
+ {
+ /* high-to-low??? transition -> interrupt (or should it be low-to-high?) */
+ rtc.regs[reg_C] |= reg_C_PF;
+ field_interrupts();
+ }
+
+ half_period = attotime_div(ATTOTIME_IN_HZ(SQW_freq_table[rtc.regs[reg_A] & reg_A_RS]), 2);
+ timer_adjust(rtc.SQW_timer, half_period, 0, attotime_never);
+}
+
+/*
+ Begin update cycle (called every second)
+*/
+static TIMER_CALLBACK( rtc_begin_update_callback )
+{
+ if (((rtc.regs[reg_A] & reg_A_DV) == 0x20) && ! (rtc.regs[reg_B] & reg_B_SET))
+ {
+ rtc.regs[reg_A] |= reg_A_UIP;
+
+ /* schedule end of update cycle */
+ timer_set(UPDATE_CYCLE_TIME, 0, rtc_end_update_callback);
+ }
+}
+
+/*
+ End update cycle (called UPDATE_CYCLE_TIME = 1948us after start of update
+ cycle)
+*/
+static TIMER_CALLBACK( rtc_end_update_callback )
+{
+ 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 (! (rtc.regs[reg_A] & reg_A_UIP))
+ /* abort if update cycle has been canceled */
+ return;
+
+ if (rtc.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 (rtc.regs[reg_second] < c59)
+ rtc.regs[reg_second] = (*increment)(rtc.regs[reg_second]);
+ else
+ {
+ rtc.regs[reg_second] = 0;
+
+ /* increment minute */
+ if (rtc.regs[reg_minute] < c59)
+ rtc.regs[reg_minute] = (*increment)(rtc.regs[reg_minute]);
+ else
+ {
+ rtc.regs[reg_minute] = 0;
+
+ /* increment hour */
+ if (rtc.regs[reg_B] & reg_B_24h)
+ {
+ /* 24 hour mode */
+ if (rtc.regs[reg_hour] < c23)
+ rtc.regs[reg_hour] = (*increment)(rtc.regs[reg_hour]);
+ else
+ rtc.regs[reg_hour] = 0;
+ }
+ else
+ {
+ /* 12 hour mode */
+ if (rtc.regs[reg_hour] < c12)
+ {
+ if ((rtc.regs[reg_hour] & 0x7f) == c11)
+ rtc.regs[reg_hour] ^= 0x80;
+ rtc.regs[reg_hour] = ((*increment)(rtc.regs[reg_hour] & 0x7f) & 0x7f)
+ | (rtc.regs[reg_hour] & 0x80);
+ }
+ else
+ rtc.regs[reg_hour] = 1 | (rtc.regs[reg_hour] & 0x80);
+ }
+
+ /* increment day if needed */
+ if (rtc.regs[reg_hour] == ((rtc.regs[reg_B] & reg_B_24h) ? 0 : c12))
+ {
+ /* increment day */
+ int days_in_month;
+
+ if (rtc.regs[reg_weekday] < 7)
+ rtc.regs[reg_weekday]++;
+ else
+ rtc.regs[reg_weekday] = 1;
+
+ if ((rtc.regs[reg_month] != 2) || (rtc.regs[reg_year] & 0x03))
+ {
+ if (rtc.regs[reg_B] & reg_B_DM)
+ {
+ /* binary mode */
+ days_in_month = days_in_month_table[rtc.regs[reg_month] - 1];
+ }
+ else
+ {
+ /* BCD mode */
+ days_in_month = binary_to_BCD(days_in_month_table[BCD_to_binary(rtc.regs[reg_month]) - 1]);
+ }
+ }
+ else
+ days_in_month = c29;
+
+ if (rtc.regs[reg_monthday] < days_in_month)
+ rtc.regs[reg_monthday] = (*increment)(rtc.regs[reg_monthday]);
+ else
+ {
+ /* increment month */
+ rtc.regs[reg_monthday] = 1;
+
+ if (rtc.regs[reg_month] < c12)
+ rtc.regs[reg_month] = (*increment)(rtc.regs[reg_month]);
+ else
+ {
+ /* increment year */
+ rtc.regs[reg_month] = 1;
+
+ if (rtc.regs[reg_B] & reg_B_DM)
+ {
+ /* binary mode */
+ if (rtc.regs[reg_year] < 99)
+ rtc.regs[reg_year]++;
+ else
+ rtc.regs[reg_year] = 0;
+ }
+ else
+ {
+ /* BCD mode */
+ rtc.regs[reg_year] = increment_BCD(rtc.regs[reg_year]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ rtc.regs[reg_A] &= ~reg_A_UIP;
+ rtc.regs[reg_C] |= reg_C_UF;
+
+ /* test for alarm (values in range 0xc0-0xff mean "don't care") */
+ if ((((rtc.regs[reg_alarm_second] & 0xc0) == 0xc0) || (rtc.regs[reg_alarm_second] == rtc.regs[reg_second]))
+ && (((rtc.regs[reg_alarm_minute] & 0xc0) == 0xc0) || (rtc.regs[reg_alarm_minute] == rtc.regs[reg_minute]))
+ && (((rtc.regs[reg_alarm_hour] & 0xc0) == 0xc0) || (rtc.regs[reg_alarm_hour] == rtc.regs[reg_hour])))
+ rtc.regs[reg_C] |= reg_C_AF;
+
+ field_interrupts();
+}