summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/bq4847.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/bq4847.cpp')
-rw-r--r--src/devices/machine/bq4847.cpp511
1 files changed, 511 insertions, 0 deletions
diff --git a/src/devices/machine/bq4847.cpp b/src/devices/machine/bq4847.cpp
new file mode 100644
index 00000000000..d2618f03f53
--- /dev/null
+++ b/src/devices/machine/bq4847.cpp
@@ -0,0 +1,511 @@
+// license:BSD-3-Clause
+// copyright-holders:Michael Zapf
+/*
+ Texas Instruments/Benchmarq BQ4847 Real-time clock
+
+ Although featuring a similar interface, this chip is sufficiently
+ different from the BQ4842/BQ4852 that a separate implementation
+ makes sense.
+
+ This chip is functionally equivalent to the BQ4845; it does not support
+ a backup battery. Most datasheets about the BQ4847 are incomplete and
+ refer to the BQ4845.
+
+ Supports 24h/12h and Daylight saving
+ Supports leap years
+
+ No internal memory, only clock registers
+
+ Michael Zapf, April 2020
+*/
+#include "emu.h"
+#include "bq4847.h"
+
+#define LOG_WARN (1U << 1) // Warnings
+#define LOG_CLOCK (1U << 2) // Clock operation
+#define LOG_REG (1U << 3) // Register write
+#define LOG_WATCHDOG (1U << 4) // Watchdog
+#define LOG_TRANSFER (1U << 5) // Transfer
+
+#define VERBOSE (LOG_GENERAL | LOG_WARN)
+#include "logmacro.h"
+
+// device type definition
+DEFINE_DEVICE_TYPE(BQ4845, bq4845_device, "bq4845", "Benchmarq BQ4845 RTC")
+DEFINE_DEVICE_TYPE(BQ4847, bq4847_device, "bq4847", "Benchmarq BQ4847 RTC")
+
+enum
+{
+ reg_seconds = 0, // 0x00 - 0x59
+ reg_alarmseconds, // 0xc0 to ignore
+ reg_minutes, // 0x00 - 0x59
+ reg_alarmminutes, // 0xc0 to ignore
+ reg_hours, // 0x00 - 0x23 (24h) or 0x01-0x12 (AM), 0x81-0x92 (PM)
+ reg_alarmhours, // 0xc0 to ignore
+ reg_date, // 0x01 - 0x31
+ reg_alarmdate, // 0xc0 to ignore
+ reg_days, // 0x01 (sun) - 0x07 (sat)
+ reg_month, // 0x01 - 0x12
+ reg_year, // 0x00 - 0x99
+ reg_rates, // 0 [--WD--] [-------RS---------]
+ reg_interrupts, // 0 0 0 0 AIE PIE PWRIE ABE 0x00 on powerup
+ reg_flags, // 0 0 0 0 AF PF PWRF BVF 0x00 after reading
+ reg_control, // 0 0 0 0 UTI STOP* 24/12* DSE
+ reg_unused // 0x00
+};
+
+enum
+{
+ INTERRUPT_AIE = 0x08,
+ INTERRUPT_PIE = 0x04,
+ INTERRUPT_PWRIE = 0x02,
+ INTERRUPT_ABE = 0x01,
+ FLAG_AF = 0x08,
+ FLAG_PF = 0x04,
+ FLAG_PWRF = 0x02,
+ FLAG_BVF = 0x01,
+ CONTROL_UTI = 0x08,
+ CONTROL_STOP = 0x04,
+ CONTROL_24 = 0x02,
+ CONTROL_DSE = 0x01
+};
+
+//-------------------------------------------------
+// Constructors for basetype
+//-------------------------------------------------
+
+bq4847_device::bq4847_device(const machine_config& mconfig, device_type type, const char* tag, device_t* owner, uint32_t clock)
+ : device_t(mconfig, type, tag, owner, clock),
+ device_nvram_interface(mconfig, *this),
+ device_rtc_interface(mconfig, *this),
+ m_region(*this, DEVICE_SELF),
+ m_wdo_handler(*this),
+ m_int_handler(*this),
+ m_rst_handler(*this),
+ m_periodic_timer(nullptr),
+ m_watchdog_timer(nullptr),
+ m_wdo_state(1),
+ m_int_state(1),
+ m_rst_state(1),
+ m_wdi_state(-1),
+ m_writing(false)
+{
+}
+
+bq4847_device::bq4847_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock)
+ : bq4847_device(mconfig, BQ4847, tag, owner, clock)
+{
+}
+
+bq4845_device::bq4845_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock)
+ : bq4847_device(mconfig, BQ4845, tag, owner, clock)
+{
+}
+
+// device_rtc_interface
+
+void bq4847_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second)
+{
+ if ((m_register[reg_control] & CONTROL_STOP) != 0)
+ {
+ m_register[reg_hours] = ((m_register[reg_control] & CONTROL_24) != 0) ? convert_to_bcd(hour) :
+ (((hour % 24) >= 12) ? 0x80 : 0x00) | convert_to_bcd((hour % 12) ? (hour % 12) : 12);
+ m_register[reg_minutes] = convert_to_bcd(minute);
+ m_register[reg_seconds] = convert_to_bcd(second);
+ m_register[reg_year] = convert_to_bcd(year);
+ m_register[reg_month] = convert_to_bcd(month);
+ m_register[reg_date] = convert_to_bcd(day);
+ m_register[reg_days] = convert_to_bcd(day_of_week);
+ }
+
+ // Clear the saved flags (TODO: check that flags set before power down, or during battery backup are lost)
+ m_register[reg_flags] = 0x00;
+
+ // Interrupts must be re-enabled on power-up (TODO: check, datasheet does not explicitly say ABE & PIE are cleared)
+ m_register[reg_interrupts] = 0x00;
+
+ // TODO: check if user buffer is battery backed
+ // TODO: What if UTI is set?
+ std::copy_n(m_register, std::size(m_register), m_userbuffer);
+
+ // What about the DSE flag?
+}
+
+bool bq4847_device::increment_bcd(uint8_t& bcdnumber, uint8_t limit, uint8_t min)
+{
+ if (bcdnumber >= limit)
+ {
+ bcdnumber = min;
+ return true;
+ }
+ else
+ {
+ uint8_t dig0 = bcdnumber & 0x0f;
+ uint8_t dig1 = bcdnumber & 0xf0;
+
+ if (dig0 == 9)
+ {
+ bcdnumber = dig1 + 0x10;
+ }
+ else bcdnumber++;
+ }
+ return false;
+}
+
+/*
+ Update cycle, called every second
+ The BQ RTCs use BCD representation
+
+ TODO: We may not be able to use the parent class advance methods, since we
+ have to work with BCD (even with invalid values). Check this.
+*/
+TIMER_CALLBACK_MEMBER(bq4847_device::update_callback)
+{
+ // Just for debugging
+ static const char* dow[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+
+ bool carry = true;
+ bool newsec = false;
+
+ if (carry)
+ {
+ carry = increment_bcd(m_register[reg_seconds], 0x59, 0);
+ newsec = true;
+ }
+
+ if (carry)
+ carry = increment_bcd(m_register[reg_minutes], 0x59, 0);
+
+ if (carry)
+ carry = advance_hours_bcd();
+
+ if (carry)
+ advance_days_bcd();
+
+ LOGMASKED(LOG_CLOCK, "%s 20%02x-%02x-%02x %02x:%02x:%02x\n",
+ dow[m_register[reg_days] - 1], m_register[reg_year], m_register[reg_month], m_register[reg_date],
+ m_register[reg_hours], m_register[reg_minutes], m_register[reg_seconds]);
+
+ if (newsec)
+ {
+ if ((m_register[reg_control] & CONTROL_UTI) == 0)
+ {
+ LOGMASKED(LOG_TRANSFER, "Transfer to external regs\n");
+ for (int i = reg_seconds; i < reg_unused; i++)
+ {
+ if (is_clock_register(i)) m_userbuffer[i] = m_register[i];
+ }
+ }
+
+ if (check_alarm(reg_date, reg_alarmdate) &&
+ check_alarm(reg_hours, reg_alarmhours) &&
+ check_alarm(reg_minutes, reg_alarmminutes) &&
+ check_alarm(reg_seconds, reg_alarmseconds))
+ {
+ m_userbuffer[reg_flags] |= FLAG_AF;
+ update_int();
+ }
+ }
+}
+
+bool bq4847_device::advance_hours_bcd()
+{
+ bool carry = false;
+ // Handle DST
+ if ((m_register[reg_control] & CONTROL_DSE) != 0
+ && (m_register[reg_month] == 4) && (m_register[reg_days] == 0) && (m_register[reg_date] < 8) // first Sunday in April
+ && (m_register[reg_hours] == 0x01))
+ m_register[reg_hours] = 0x03;
+ else
+ {
+ // Increment hour unless the DSE bit is set and we are at 1:59 on the last Sunday in October
+ if ((m_register[reg_control] & CONTROL_DSE) == 0
+ || (m_register[reg_month] != 10) || (m_register[reg_days] != 0) || (m_register[reg_date] <= 23) // last Sunday in October
+ || (m_register[reg_hours] != 0x01))
+ {
+ if ((m_register[reg_control] & CONTROL_24) != 0)
+ {
+ // 24h: 0->1->...->23->0(+1)
+ increment_bcd(m_register[reg_hours], 0xff, 0);
+ if (m_register[reg_hours] == 0x24)
+ {
+ m_register[reg_hours] = 0;
+ carry = true;
+ }
+ }
+ else
+ {
+ // 12h: 12->1->2->...->11->12'->1'->...->11'->12(+1)
+ increment_bcd(m_register[reg_hours], 0xff, 0);
+ switch (m_register[reg_hours])
+ {
+ case 0x12:
+ m_register[reg_hours] = 0x92; // 11:59 am -> 12:00 pm
+ break;
+ case 0x93:
+ m_register[reg_hours] = 0x81; // 12:59 pm -> 01:00 pm
+ break;
+ case 0x92:
+ m_register[reg_hours] = 0x12; // 11:59 pm -> 12:00 am
+ carry = true;
+ break;
+ case 0x13:
+ m_register[reg_hours] = 0x01; // 12:59 am -> 01:00 am
+ break;
+ }
+ }
+ }
+ }
+ return carry;
+}
+
+void bq4847_device::advance_days_bcd()
+{
+ bool carry = false;
+
+ // BCD-encoded numbers
+ static const int days_in_month_table[12] =
+ {
+ 0x31, 0x28, 0x31, 0x30, 0x31, 0x30,
+ 0x31, 0x31, 0x30, 0x31, 0x30, 0x31
+ };
+
+ uint8_t month = bcd_to_integer(m_register[reg_month]);
+ if (month > 12) month = 12;
+
+ uint8_t days = days_in_month_table[month - 1];
+
+ // Leap years are indeed handled (but the year is only 2-digit)
+ if ((month == 2) && ((bcd_to_integer(m_register[reg_year]) % 4) == 0))
+ days = 0x29;
+
+ increment_bcd(m_register[reg_days], 7, 1); // Increment the day-of-week (without carry)
+ carry = increment_bcd(m_register[reg_date], days, 1);
+
+ if (carry)
+ {
+ increment_bcd(m_register[reg_month], 0xff, 1);
+ if (m_register[reg_month] == 0x13)
+ {
+ m_register[reg_month] = 0x01;
+ increment_bcd(m_register[reg_year], 0xff, 0);
+ }
+ }
+}
+
+bool bq4847_device::check_alarm(int now, int alarm)
+{
+ return (m_register[alarm] & 0xc0) == 0xc0 || (m_register[alarm] == m_register[now]);
+}
+
+uint8_t bq4847_device::read(offs_t address)
+{
+ int regnum = address & 0x0f;
+ uint8_t value = m_userbuffer[regnum];
+
+ if (regnum == reg_flags)
+ {
+ value &= 0x7f;
+ m_userbuffer[reg_flags] = 0x00;
+ update_int();
+ }
+ else if (regnum >= reg_interrupts && regnum <= reg_control)
+ value &= 0xf;
+ else if (regnum == reg_unused)
+ value = 0; // Reg 15 is locked to 0 in BQ4847
+
+ LOGMASKED(LOG_REG, "Reg %d -> %02x\n", regnum, value);
+
+ return value;
+}
+
+void bq4847_device::write(offs_t address, uint8_t data)
+{
+ int regnum = address & 0x0f;
+
+ LOGMASKED(LOG_REG, "Reg %d <- %02x\n", regnum, data);
+
+ if (regnum == reg_flags)
+ {
+ LOGMASKED(LOG_WARN, "Ignoring write attempt to flag bit register (%02x)\n", data);
+ return;
+ }
+
+ bool uti_set = (m_register[reg_control] & CONTROL_UTI) != 0;
+
+ m_userbuffer[regnum] = data;
+
+ // If inhibit is not set, any write to the time/date registers
+ // is immediately set
+ if (uti_set && is_clock_register(regnum))
+ m_writing = true;
+ else
+ m_register[regnum] = m_userbuffer[regnum];
+
+ if (regnum == reg_rates)
+ {
+ set_watchdog_timer();
+ set_periodic_timer();
+ }
+ else if (regnum == reg_control)
+ {
+ bool uti_set_now = (m_register[reg_control] & CONTROL_UTI) != 0;
+ LOGMASKED(LOG_TRANSFER, "Update transfer %s\n", uti_set_now ? "inhibit" : "enable");
+
+ // After we have written to the registers, transfer to the internal regs
+ if (uti_set && !uti_set_now && m_writing)
+ {
+ LOGMASKED(LOG_TRANSFER, "Transfer to internal regs\n");
+ for (int i = reg_seconds; i < reg_unused; i++)
+ {
+ if (is_clock_register(i)) m_register[i] = m_userbuffer[i];
+ }
+
+ m_writing = false;
+ }
+ }
+}
+
+bool bq4847_device::is_clock_register(int regnum)
+{
+ return (regnum == reg_seconds || regnum == reg_minutes || regnum == reg_hours ||
+ regnum == reg_date || regnum == reg_days || regnum == reg_month
+ || regnum == reg_year);
+}
+
+void bq4847_device::set_periodic_timer()
+{
+ uint8_t rs = m_register[reg_rates] & 0x0f;
+ attotime period = rs ? clocks_to_attotime(1 << (rs - 1)) : attotime::never;
+
+ if (m_periodic_timer)
+ m_periodic_timer->adjust(period, 0, period);
+}
+
+void bq4847_device::set_watchdog_timer(int rst_state)
+{
+ if (m_rst_state == rst_state)
+ {
+ int wd = (m_register[reg_rates] & 0x70) >> 4;
+ u32 t = (wd == 7) ? 16384 : (wd == 0) ? 8192 : 64 << wd;
+ if (m_rst_state) t *= 6;
+ attotime timeout = m_wdi_state >= 0 ? clocks_to_attotime(t) : attotime::never;
+
+ if (m_watchdog_timer)
+ m_watchdog_timer->adjust(timeout);
+ }
+}
+
+void bq4847_device::set_wdo(int state)
+{
+ if (m_wdo_state != state)
+ {
+ m_wdo_state = state;
+ m_wdo_handler(m_wdo_state);
+ }
+}
+
+void bq4847_device::write_wdi(int state)
+{
+ if (m_wdi_state != state)
+ {
+ m_wdi_state = state;
+
+ set_wdo(1);
+ set_watchdog_timer();
+ }
+}
+
+TIMER_CALLBACK_MEMBER(bq4847_device::periodic_callback)
+{
+ m_userbuffer[reg_flags] |= FLAG_PF;
+ update_int();
+}
+
+TIMER_CALLBACK_MEMBER(bq4847_device::watchdog_callback)
+{
+ m_rst_state = !m_rst_state;
+ set_watchdog_timer(m_rst_state); // force timer update during reset
+
+ m_rst_handler(m_rst_state);
+
+ if (!m_rst_state)
+ set_wdo(0);
+
+ LOGMASKED(LOG_WATCHDOG, "wdo %s rst %s\n", !m_wdo_state ? "asserted" : "cleared", !m_rst_state ? "asserted" : "cleared");
+}
+
+void bq4847_device::update_int()
+{
+ // TODO: check what happens if reg_interrupts is changed after the flag is set.
+ int int_state = !(m_register[reg_interrupts] & m_userbuffer[reg_flags] & (FLAG_AF | FLAG_PF | FLAG_PWRF));
+ if (m_int_state != int_state)
+ {
+ m_int_state = int_state;
+ m_int_handler(m_int_state);
+ }
+}
+
+// device_t
+
+void bq4847_device::device_start()
+{
+ m_update_timer = timer_alloc(FUNC(bq4847_device::update_callback), this);
+ m_periodic_timer = timer_alloc(FUNC(bq4847_device::periodic_callback), this);
+ m_watchdog_timer = timer_alloc(FUNC(bq4847_device::watchdog_callback), this);
+
+ m_wdo_handler(m_wdo_state);
+ m_int_handler(m_int_state);
+ m_rst_handler(m_rst_state);
+
+ save_pointer(NAME(m_userbuffer), 16);
+ save_pointer(NAME(m_register), 16);
+ save_item(NAME(m_wdo_state));
+ save_item(NAME(m_int_state));
+ save_item(NAME(m_rst_state));
+ save_item(NAME(m_wdi_state));
+ save_item(NAME(m_writing));
+}
+
+void bq4847_device::device_reset()
+{
+ device_clock_changed();
+}
+
+void bq4847_device::device_clock_changed()
+{
+ m_update_timer->adjust(clocks_to_attotime(32768), 0, clocks_to_attotime(32768));
+ set_watchdog_timer();
+ set_periodic_timer();
+}
+
+// device_nvram_interface
+
+void bq4847_device::nvram_default()
+{
+ if (m_region.found())
+ {
+ if (m_region->bytes() != std::size(m_register))
+ fatalerror("%s incorrect region size", tag());
+
+ std::copy_n(m_region->base(), std::size(m_register), m_register);
+ }
+ else
+ {
+ std::fill_n(m_register, std::size(m_register), 0);
+
+ m_register[reg_control] = CONTROL_STOP | CONTROL_24;
+ }
+}
+
+bool bq4847_device::nvram_read(util::read_stream& file)
+{
+ auto const [err, actual] = util::read(file, m_register, std::size(m_register));
+ return !err && (actual == std::size(m_register));
+}
+
+bool bq4847_device::nvram_write(util::write_stream& file)
+{
+ auto const [err, actual] = util::write(file, m_register, std::size(m_register));
+ return !err;
+}