diff options
Diffstat (limited to 'src/devices/machine/icm7170.cpp')
-rw-r--r-- | src/devices/machine/icm7170.cpp | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/src/devices/machine/icm7170.cpp b/src/devices/machine/icm7170.cpp new file mode 100644 index 00000000000..97be23ee0d2 --- /dev/null +++ b/src/devices/machine/icm7170.cpp @@ -0,0 +1,329 @@ +// license:BSD-3-Clause +// copyright-holders:R. Belmont +/********************************************************************** + + Intersil/Renesas ICM7170 Real Time Clock + +*********************************************************************/ + +#include "emu.h" +#include "icm7170.h" + +#include "coreutil.h" + +#define VERBOSE (0) +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(ICM7170, icm7170_device, "icm7170", "Intersil/Renesas ICM7170 Real Time Clock") + +// internal registers +enum +{ + REG_CNT_100TH_SEC = 0, + REG_CNT_HOURS, + REG_CNT_MINUTES, + REG_CNT_SECONDS, + REG_CNT_MONTH, + REG_CNT_DAY, + REG_CNT_YEAR, + REG_CNT_DAY_OF_WEEK, + REG_RAM_100TH_SEC, + REG_RAM_HOURS, + REG_RAM_MINUTES, + REG_RAM_SECONDS, + REG_RAM_MONTH, + REG_RAM_DAY, + REG_RAM_YEAR, + REG_RAM_DAY_OF_WEEK, + REG_INT_STATUS_AND_MASK, + REG_COMMAND +}; + +enum +{ + CMD_REG_TEST_MODE = 0x20, + CMD_REG_IRQ_ENABLE = 0x10, + CMD_REG_RUN = 0x08, + CMD_REG_24_HOUR = 0x04, + CMD_REG_FREQ_MASK = 0x03 +}; + +enum +{ + IRQ_BIT_GLOBAL = 0x80, + IRQ_BIT_DAY = 0x40, + IRQ_BIT_HOUR = 0x20, + IRQ_BIT_MINUTE = 0x10, + IRQ_BIT_SECOND = 0x08, + IRQ_BIT_10TH_SECOND = 0x04, + IRQ_BIT_100TH_SECOND = 0x02, + IRQ_BIT_ALARM = 0x01 +}; + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +//------------------------------------------------- +// icm7170_device - constructor +//------------------------------------------------- + +icm7170_device::icm7170_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, ICM7170, tag, owner, clock), + device_rtc_interface(mconfig, *this), + device_nvram_interface(mconfig, *this), + m_out_irq_cb(*this), + m_out_irq_state(false) +{ +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void icm7170_device::device_start() +{ + m_timer = timer_alloc(FUNC(icm7170_device::clock_tick), this); + + // TODO: frequency should be based on input clock and divisor + m_timer->adjust(attotime::from_hz(100), 0, attotime::from_hz(100)); + m_timer->enable(false); + + save_item(NAME(m_regs)); + save_item(NAME(m_out_irq_state)); +} + +//------------------------------------------------- +// device_reset - device-specific reset handling +//------------------------------------------------- + +void icm7170_device::device_reset() +{ + m_regs[REG_COMMAND] &= ~CMD_REG_RUN; + m_irq_status = 0; + recalc_irqs(); +} + +//------------------------------------------------- +// clock_tick - advance the RTC's counters +//------------------------------------------------- + +TIMER_CALLBACK_MEMBER(icm7170_device::clock_tick) +{ + // advance hundredths + m_irq_status |= IRQ_BIT_100TH_SECOND; + if (m_regs[REG_CNT_100TH_SEC]++ == 99) + { + m_regs[REG_CNT_100TH_SEC] = 0; + + if (m_regs[REG_COMMAND] & CMD_REG_24_HOUR) + LOG("clock_tick %02d-%02d-%02d %02d:%02d:%02d\n", + m_regs[REG_CNT_YEAR], m_regs[REG_CNT_MONTH], m_regs[REG_CNT_DAY], + m_regs[REG_CNT_HOURS], m_regs[REG_CNT_MINUTES], m_regs[REG_CNT_SECONDS]); + else + LOG("clock_tick %02d-%02d-%02d %02d:%02d:%02d %s\n", + m_regs[REG_CNT_YEAR], m_regs[REG_CNT_MONTH], m_regs[REG_CNT_DAY], + m_regs[REG_CNT_HOURS] & 0xf, m_regs[REG_CNT_MINUTES], m_regs[REG_CNT_SECONDS], + (m_regs[REG_CNT_HOURS] & 0x80) ? "pm" : "am"); + + // advance seconds + m_irq_status |= IRQ_BIT_SECOND; + if (m_regs[REG_CNT_SECONDS]++ == 59) + { + m_regs[REG_CNT_SECONDS] = 0; + + // advance minutes + m_irq_status |= IRQ_BIT_MINUTE; + if (m_regs[REG_CNT_MINUTES]++ == 59) + { + m_regs[REG_CNT_MINUTES] = 0; + + // invert am/pm bit at 12:00 + if (!(m_regs[REG_COMMAND] & CMD_REG_24_HOUR) && (m_regs[REG_CNT_HOURS] & 0xf) == 11) + m_regs[REG_CNT_HOURS] ^= 0x80; + + // advance hours + m_irq_status |= IRQ_BIT_HOUR; + if (((m_regs[REG_COMMAND] & CMD_REG_24_HOUR) && (m_regs[REG_CNT_HOURS] == 23)) + || (!(m_regs[REG_COMMAND] & CMD_REG_24_HOUR) && (m_regs[REG_CNT_HOURS] & 0xf) == 12)) + { + if (m_regs[REG_COMMAND] & CMD_REG_24_HOUR) + m_regs[REG_CNT_HOURS] = 0; + else + m_regs[REG_CNT_HOURS] = (m_regs[REG_CNT_HOURS] & 0x80) | 1; + + // advance days + m_irq_status |= IRQ_BIT_DAY; + if (m_regs[REG_CNT_DAY]++ == gregorian_days_in_month(m_regs[REG_CNT_MONTH] & 0xf, (m_regs[REG_CNT_YEAR] & 0x7f) + 2000)) + { + m_regs[REG_CNT_DAY] = 1; + + // advance months + if (m_regs[REG_CNT_MONTH]++ == 12) + { + m_regs[REG_CNT_MONTH] = 1; + + // advance years + if (m_regs[REG_CNT_YEAR]++ == 99) + m_regs[REG_CNT_YEAR] = 0; + } + } + + // advance day of week + if (m_regs[REG_CNT_DAY_OF_WEEK]++ == 6) + m_regs[REG_CNT_DAY_OF_WEEK] = 0; + } + else + m_regs[REG_CNT_HOURS]++; + } + } + } + + // check tenths interrupt + if (!(m_regs[REG_CNT_100TH_SEC] % 10)) + m_irq_status |= IRQ_BIT_10TH_SECOND; + + // check alarm + if (m_irq_mask & IRQ_BIT_ALARM) + if (BIT(m_regs[REG_RAM_YEAR], 7) || !((m_regs[REG_RAM_YEAR] ^ m_regs[REG_CNT_YEAR]) & 0x7f)) + if (BIT(m_regs[REG_RAM_MONTH], 7) || !((m_regs[REG_RAM_MONTH] ^ m_regs[REG_CNT_MONTH]) & 0xf)) + if (BIT(m_regs[REG_RAM_DAY], 7) || !((m_regs[REG_RAM_DAY] ^ m_regs[REG_CNT_DAY]) & 0x1f)) + if (BIT(m_regs[REG_RAM_HOURS], 6) + || ((m_regs[REG_COMMAND] & CMD_REG_24_HOUR) && !((m_regs[REG_RAM_HOURS] ^ m_regs[REG_CNT_HOURS]) & 0x1f)) + || (!(m_regs[REG_COMMAND] & CMD_REG_24_HOUR) && !((m_regs[REG_RAM_HOURS] ^ m_regs[REG_CNT_HOURS]) & 0x8f))) + if (BIT(m_regs[REG_RAM_MINUTES], 7) || !((m_regs[REG_RAM_MINUTES] ^ m_regs[REG_CNT_MINUTES]) & 0x3f)) + if (BIT(m_regs[REG_RAM_SECONDS], 7) || !((m_regs[REG_RAM_SECONDS] ^ m_regs[REG_CNT_SECONDS]) & 0x3f)) + if (BIT(m_regs[REG_RAM_100TH_SEC], 7) || !((m_regs[REG_RAM_100TH_SEC] ^ m_regs[REG_CNT_100TH_SEC]) & 0x7f)) + m_irq_status |= IRQ_BIT_ALARM; + + recalc_irqs(); +} + + +//------------------------------------------------- +// rtc_clock_updated - called when the host time changes +//------------------------------------------------- + +void icm7170_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) +{ + if (m_regs[REG_COMMAND] & CMD_REG_RUN) + { + m_regs[REG_CNT_YEAR] = year % 99; + m_regs[REG_CNT_MONTH] = month; + m_regs[REG_CNT_DAY] = day; + m_regs[REG_CNT_DAY_OF_WEEK] = day_of_week; + if (!(m_regs[REG_COMMAND] & CMD_REG_24_HOUR)) + { + // transform hours from 0..23 to 1..12 + m_regs[REG_CNT_HOURS] = (hour > 12) ? hour - 12 : hour ? hour : 1; + + // set am/pm bit + if (hour > 11) + m_regs[REG_CNT_HOURS] |= 0x80; + } + else + m_regs[REG_CNT_HOURS] = hour; + m_regs[REG_CNT_MINUTES] = minute; + m_regs[REG_CNT_SECONDS] = second; + + if (m_regs[REG_COMMAND] & CMD_REG_24_HOUR) + LOG("rtc_clock_updated %02d-%02d-%02d %02d:%02d:%02d\n", + m_regs[REG_CNT_YEAR], m_regs[REG_CNT_MONTH], m_regs[REG_CNT_DAY], + m_regs[REG_CNT_HOURS], m_regs[REG_CNT_MINUTES], m_regs[REG_CNT_SECONDS]); + else + LOG("rtc_clock_updated %02d-%02d-%02d %02d:%02d:%02d %s\n", + m_regs[REG_CNT_YEAR], m_regs[REG_CNT_MONTH], m_regs[REG_CNT_DAY], + m_regs[REG_CNT_HOURS] & 0xf, m_regs[REG_CNT_MINUTES], m_regs[REG_CNT_SECONDS], + (m_regs[REG_CNT_HOURS] & 0x80) ? "pm" : "am"); + } +} + + +//------------------------------------------------- +// nvram_default - called to initialize NVRAM to +// its default state +//------------------------------------------------- + +void icm7170_device::nvram_default() +{ + memset(m_regs, 0, 0x20); +} + + +//------------------------------------------------- +// nvram_read - called to read NVRAM from the +// .nv file +//------------------------------------------------- + +bool icm7170_device::nvram_read(util::read_stream &file) +{ + auto const [err, actual] = util::read(file, m_regs, 0x20); + return !err && (actual == 0x20); +} + + +//------------------------------------------------- +// nvram_write - called to write NVRAM to the +// .nv file +//------------------------------------------------- + +bool icm7170_device::nvram_write(util::write_stream &file) +{ + auto const [err, actual] = util::write(file, m_regs, 0x20); + return !err; +} + +// non-inherited device functions +uint8_t icm7170_device::read(offs_t offset) +{ + uint8_t data = m_regs[offset & 0x1f]; + + if ((offset & 0x1f) == REG_INT_STATUS_AND_MASK) + { + data = m_irq_status; + m_irq_status = 0; + recalc_irqs(); + } + + LOG("ICM7170 Register %d Read %02x\n", offset, data); + + return data; +} + +void icm7170_device::write(offs_t offset, uint8_t data) +{ + // TODO: unused register bits should be masked + switch (offset & 0x1f) + { + case REG_INT_STATUS_AND_MASK: + m_irq_mask = data; + LOG("ICM7170 IRQ Mask Write %02x\n", data); + recalc_irqs(); + break; + + case REG_COMMAND: + m_timer->enable(data & CMD_REG_RUN); + [[fallthrough]]; + + default: + m_regs[offset & 0x1f] = data; + LOG("ICM7170 Register %d Write %02x\n", offset & 0x1f, data); + break; + } +} + +void icm7170_device::recalc_irqs() +{ + if (m_irq_status & m_irq_mask & ~IRQ_BIT_GLOBAL) + m_irq_status |= IRQ_BIT_GLOBAL; + else + m_irq_status &= ~IRQ_BIT_GLOBAL; + + bool const irq_state = (m_regs[REG_COMMAND] & CMD_REG_IRQ_ENABLE) && (m_irq_status & IRQ_BIT_GLOBAL); + if (m_out_irq_state != irq_state) + { + m_out_irq_state = irq_state; + m_out_irq_cb(m_out_irq_state); + } +} + |