bq48xx: Adding Benchmarq RTC chip emulations.

This commit is contained in:
Michael Zapf 2020-04-14 23:48:59 +02:00
parent cf078d736a
commit efeddba15b
5 changed files with 1348 additions and 0 deletions

View File

@ -872,6 +872,30 @@ if (MACHINES["BANKDEV"]~=null) then
}
end
---------------------------------------------------
--
--@src/devices/machine/bq4847.h,MACHINES["BQ4847"] = true
---------------------------------------------------
if (MACHINES["BQ4847"]~=null) then
files {
MAME_DIR .. "src/devices/machine/bq4847.cpp",
MAME_DIR .. "src/devices/machine/bq4847.h",
}
end
---------------------------------------------------
--
--@src/devices/machine/bq48x2.h,MACHINES["BQ4852"] = true
---------------------------------------------------
if (MACHINES["BQ4852"]~=null) then
files {
MAME_DIR .. "src/devices/machine/bq48x2.cpp",
MAME_DIR .. "src/devices/machine/bq48x2.h",
}
end
---------------------------------------------------
--
--@src/devices/machine/busmouse.h,MACHINES["BUSMOUSE"] = true

View File

@ -0,0 +1,530 @@
// 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.
Supports 24h/12h and Daylight saving
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_REGW (1U<<3) // Register write
#define LOG_WATCHDOG (1U<<4) // Watchdog
#define VERBOSE ( LOG_GENERAL | LOG_WARN )
#include "logmacro.h"
// device type definition
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 0x81 - 0x92 (12h)
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
{
FLAG_AIE = 0x08,
FLAG_PIE = 0x04,
FLAG_PWRIE = 0x02,
FLAG_ABE = 0x01,
FLAG_AF = 0x08,
FLAG_PF = 0x04,
FLAG_PWRF = 0x02,
FLAG_BVF = 0x01,
FLAG_UTI = 0x08,
FLAG_STOP = 0x04,
FLAG_24 = 0x02,
FLAG_DSE = 0x01
};
//-------------------------------------------------
// Constructors for basetype
//-------------------------------------------------
bq4847_device::bq4847_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, BQ4847, tag, owner, clock),
device_nvram_interface(mconfig, *this),
m_interrupt_cb(*this),
m_wdout_cb(*this),
m_watchdog_active(false),
m_writing(false)
{
}
bool bq4847_device::increment_bcd(uint8_t& bcdnumber, uint8_t limit, uint8_t min)
{
if (!valid_bcd(bcdnumber, min, limit))
{
bcdnumber = min;
return false;
}
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;
}
bool bq4847_device::valid_bcd(uint8_t value, uint8_t min, uint8_t max)
{
bool valid = ((value>=min) && (value<=max) && ((value&0x0f)<=9));
if (!valid) LOGMASKED(LOG_WARN, "Invalid BCD number %02x\n", value);
return valid;
}
uint8_t bq4847_device::to_bcd(uint8_t value)
{
return (((value / 10) << 4) & 0xf0) | (value % 10);
}
uint8_t bq4847_device::from_bcd(uint8_t value)
{
return ((value & 0xf0)>>4)*10 + (value & 0x0f);
}
// ----------------------------------------------------
/*
Update cycle, called every second
The BQ RTCs use BCD representation
*/
TIMER_CALLBACK_MEMBER(bq4847_device::rtc_clock_cb)
{
// BCD-encoded numbers
static const int days_in_month_table[12] =
{
0x31,0x28,0x31, 0x30,0x31,0x30,
0x31, 0x31, 0x30, 0x31, 0x30, 0x31
};
// 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_intreg[reg_seconds], 0x59, 0);
newsec = true;
}
if (carry)
carry = increment_bcd(m_intreg[reg_minutes], 0x59, 0);
if (carry)
{
// Handle DST
if (is_set(reg_control, FLAG_DSE)
&& (m_reg[reg_month]==4) && (m_reg[reg_days]==0) && (m_reg[reg_date] < 8) // first Sunday in April
&& (m_reg[reg_hours]==0x01))
m_reg[reg_hours] = 0x03;
else
{
if (!is_set(reg_control, FLAG_DSE)
|| (m_reg[reg_month]!=10) || (m_reg[reg_days]!=0) || (m_reg[reg_date] <= 23) // last Sunday in October
|| (m_reg[reg_hours]!=0x01))
carry = increment_bcd(m_intreg[reg_hours], 0x23, 0);
}
}
if (carry)
{
uint8_t month = m_intreg[reg_month];
if (!valid_bcd(month, 0x01, 0x12)) month = 1;
uint8_t days = days_in_month_table[month-1];
// Are leap years considered?
if ((month==2)
&& ((m_intreg[reg_year]%4)==0)
&& ((m_intreg[reg_year]%100)!=0
|| (m_intreg[reg_year]%400)==0))
days = 0x29;
increment_bcd(m_intreg[reg_days], 7, 1);
carry = increment_bcd(m_intreg[reg_date], days, 1);
}
if (carry)
carry = increment_bcd(m_intreg[reg_month], 0x12, 1);
if (carry)
carry = increment_bcd(m_intreg[reg_year], 0x99, 0);
LOGMASKED(LOG_CLOCK, "%s 20%02x-%02x-%02x %02x:%02x:%02x\n",
dow[m_intreg[reg_days]-1], m_intreg[reg_year], m_intreg[reg_month], m_intreg[reg_date],
m_intreg[reg_hours], m_intreg[reg_minutes], m_intreg[reg_seconds]);
// Copy into memory registers if the read bit is reset
if (newsec)
{
if (!is_set(reg_control, FLAG_UTI))
{
// Copy values from internal registers to accessible registers
transfer_to_access();
}
if (check_match(reg_date, reg_alarmdate) &&
check_match(reg_hours, reg_alarmhours) &&
check_match(reg_minutes, reg_alarmminutes) &&
check_match(reg_seconds, reg_alarmseconds))
{
set_register(reg_flags, FLAG_AF, true);
m_interrupt_cb(intrq_r());
}
}
}
bool bq4847_device::check_match(int now, int alarm)
{
// The ignore feature is active once the alarm has set in
// Will lead to a periodic alarm
bool ignore = (is_set(m_reg[alarm], 0x80) && is_set(m_reg[alarm], 0x40)) && is_set(reg_flags, FLAG_AF);
return ignore || (m_intreg[now] == (m_reg[alarm] & 0x3f));
}
// =========================================================
/*
Read from registers
*/
uint8_t bq4847_device::read(offs_t address)
{
int regnum = address & 0x0f;
uint8_t value = m_reg[regnum];
if (regnum == reg_flags)
{
set_register(reg_flags, 0xff, false);
m_interrupt_cb(intrq_r());
}
return value;
}
/*
Write to the registers
*/
void bq4847_device::write(offs_t address, uint8_t data)
{
int regnum = address & 0x0f;
if (regnum == reg_flags)
{
LOGMASKED(LOG_WARN, "Ignoring write attempt to flag bit register (%02x)\n", data);
return;
}
m_reg[regnum] = data;
if (regnum == reg_rates)
{
set_watchdog_timer(true);
set_periodic_timer();
}
else
{
if (regnum == reg_control)
{
// After we have written to the registers, transfer to the internal regs
if (is_set(reg_control, FLAG_UTI) && ((data & FLAG_UTI)==0) && m_writing)
transfer_to_int();
// We ignore the STOP* flag, since it only covers behaviour on power-off
// We ignore the 24h/12h flag here; it requires reloading the registers anyway
// The DSE flag will have effect on update
}
else
m_writing = true;
}
}
void bq4847_device::set_register(int number, uint8_t bits, bool set)
{
if (set)
m_reg[number] |= bits;
else
m_reg[number] &= ~bits;
}
uint8_t bq4847_device::ampmto24(uint8_t ampm)
{
uint8_t f24 = from_bcd(ampm);
if (f24==12) f24 = 0;
else
{
if (ampm & 0x80)
{
if (f24 == 92)
f24 = 12;
else
f24 = f24 - 68;
}
}
return to_bcd(f24);
}
uint8_t bq4847_device::ampmfrom24(uint8_t f24)
{
uint8_t ampm = from_bcd(f24);
if (ampm==0)
ampm = 12;
else
{
if (ampm == 12)
ampm = 92;
else
{
if (ampm > 12)
ampm = ampm + 68;
}
}
return to_bcd(ampm);
}
bool bq4847_device::is_set(int number, uint8_t flag)
{
return (m_reg[number] & flag)!=0;
}
void bq4847_device::transfer_to_int()
{
m_intreg[reg_year] = m_reg[reg_year];
m_intreg[reg_month] = m_reg[reg_month];
m_intreg[reg_date] = m_reg[reg_date];
m_intreg[reg_days] = m_reg[reg_days];
m_intreg[reg_minutes] = m_reg[reg_minutes];
m_intreg[reg_seconds] = m_reg[reg_seconds];
// Check: What is the real device's behavior on inconsistent time formats?
if (is_set(reg_control, FLAG_24))
m_intreg[reg_hours] = m_reg[reg_hours];
else
m_intreg[reg_hours] = ampmto24(m_reg[reg_hours]);
}
void bq4847_device::transfer_to_access()
{
m_reg[reg_year] = m_intreg[reg_year];
m_reg[reg_month] = m_intreg[reg_month];
m_reg[reg_date] = m_intreg[reg_date];
m_reg[reg_days] = m_intreg[reg_days];
m_reg[reg_minutes] = m_intreg[reg_minutes];
m_reg[reg_seconds] = m_intreg[reg_seconds];
// Convert to AM/PM if selected
if (is_set(reg_control, FLAG_24))
m_reg[reg_hours] = m_intreg[reg_hours];
else
m_reg[reg_hours] = ampmfrom24(m_intreg[reg_hours]);
// Clear the flag
m_writing = false;
}
void bq4847_device::set_periodic_timer()
{
uint8_t rateval = m_reg[reg_rates] & 0x0f;
int rate = 1<<(16-rateval);
if (rateval == 0)
m_periodic_timer->reset();
else
m_periodic_timer->adjust(attotime::from_hz(rate), 0, attotime::from_hz(rate));
}
void bq4847_device::set_watchdog_timer(bool on)
{
int val = (m_reg[reg_rates] & 0x70)>>4;
// val = 0 -> 1.5 sec
// val = 1 -> 3/128 sec
// val = 2 -> 3/64 sec
// ...
// val = 6 -> 3/4 sec
// val = 7 -> 3 sec
s64 time = 250000000L; // 250 ms
if (val > 0)
{
time <<= 1;
if (val < 7)
time = time / (4<<(6-val));
}
if (on) time *= 6; // delay to on is 6 times the delay to off
if (m_watchdog_active)
m_watchdog_timer->adjust(attotime::from_nsec(time)); // single shot
}
void bq4847_device::set_watchdog_active(bool active)
{
m_watchdog_active = active;
}
void bq4847_device::retrigger_watchdog()
{
m_wdout_cb(CLEAR_LINE);
m_watchdog_asserted = false;
set_watchdog_timer(true);
}
/*
Periodic cycle (called at defined intervals)
*/
TIMER_CALLBACK_MEMBER(bq4847_device::rtc_periodic_cb)
{
set_register(reg_flags, FLAG_PF, true);
if (intrq_r())
m_interrupt_cb(ASSERT_LINE);
}
/*
Watchdog callback (BQ4847)
*/
TIMER_CALLBACK_MEMBER(bq4847_device::rtc_watchdog_cb)
{
if (m_watchdog_active)
{
m_wdout_cb(m_watchdog_asserted? CLEAR_LINE : ASSERT_LINE);
set_watchdog_timer(!m_watchdog_asserted);
m_watchdog_asserted = !m_watchdog_asserted;
LOGMASKED(LOG_WATCHDOG, "Watchdog %s\n", m_watchdog_asserted? "asserted" : "cleared");
}
}
READ_LINE_MEMBER(bq4847_device::intrq_r)
{
bool alarm = is_set(reg_interrupts, FLAG_AIE) && is_set(reg_flags, FLAG_AF);
bool period = is_set(reg_interrupts, FLAG_PIE) && is_set(reg_flags, FLAG_PF);
// We ignore interrupts from power fail or battery low
return (alarm || period)? ASSERT_LINE : CLEAR_LINE;
}
void bq4847_device::connect_osc(bool conn)
{
if (conn)
{
// The internal update cycle is 1 sec
m_clock_timer->adjust(attotime::from_seconds(1), 0, attotime::from_seconds(1));
set_periodic_timer();
}
else
{
// Turn off completely
m_clock_timer->reset();
m_watchdog_timer->reset();
m_periodic_timer->reset();
}
}
void bq4847_device::get_system_time()
{
// Set time from system time
// We always use 24h internally
system_time systime;
machine().current_datetime(systime);
m_intreg[reg_hours] = to_bcd(systime.local_time.hour);
m_intreg[reg_minutes] = to_bcd(systime.local_time.minute);
m_intreg[reg_seconds] = to_bcd(systime.local_time.second);
m_intreg[reg_year] = to_bcd(systime.local_time.year%100);
m_intreg[reg_month] = to_bcd(systime.local_time.month+1);
m_intreg[reg_date] = to_bcd(systime.local_time.mday);
m_intreg[reg_days] = to_bcd(systime.local_time.weekday+1);
set_register(reg_control, FLAG_DSE, systime.local_time.is_dst);
}
void bq4847_device::device_start()
{
m_clock_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(bq4847_device::rtc_clock_cb), this));
// Periodic timer
m_periodic_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(bq4847_device::rtc_periodic_cb), this));
// Watchdog timer
m_watchdog_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(bq4847_device::rtc_watchdog_cb), this));
// Interrupt line
m_interrupt_cb.resolve_safe();
// Watchdog output
m_wdout_cb.resolve_safe();
// Interrupt enables are cleared on powerup
set_register(reg_interrupts, 0xff, false);
// State save
save_pointer(NAME(m_reg), 16);
save_pointer(NAME(m_intreg), 16);
// Start clock
get_system_time();
connect_osc(true);
}
// ----------------------------------------------------
void bq4847_device::nvram_default()
{
std::fill_n(m_reg, 16, 0);
}
void bq4847_device::nvram_read(emu_file &file)
{
file.read(m_reg, 16);
get_system_time();
transfer_to_access();
// Clear the saved flags
set_register(reg_flags, 0xff, false);
// Interrupts must be re-enabled on power-up
set_register(reg_interrupts, 0xff, false);
}
void bq4847_device::nvram_write(emu_file &file)
{
transfer_to_access();
file.write(m_reg, 16);
}

View File

@ -0,0 +1,115 @@
// license:BSD-3-Clause
// copyright-holders:Michael Zapf
/*
Texas Instruments/Benchmarq BQ4847 Real-time clock
See bq4847.cpp for details.
Michael Zapf, April 2020
*/
#ifndef MAME_MACHINE_BQ4847_H
#define MAME_MACHINE_BQ4847_H
#pragma once
// ======================> bq4874_device
class bq4847_device : public device_t, public device_nvram_interface
{
public:
bq4847_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// The watchdog is inactive when the pin is not connected; we offer
// a method to activate it
void set_watchdog_active(bool active);
auto interrupt_cb() { return m_interrupt_cb.bind(); }
auto watchdog_cb() { return m_wdout_cb.bind(); }
// Retrigger the watchdog
void retrigger_watchdog();
uint8_t read(offs_t address);
void write(offs_t address, uint8_t data);
DECLARE_READ_LINE_MEMBER(intrq_r);
// Mainly used to disconnect from oscillator
void connect_osc(bool conn);
private:
// callback called when interrupt pin state changes (may be nullptr)
devcb_write_line m_interrupt_cb;
// callback called when watchdog times out changes (may be nullptr)
devcb_write_line m_wdout_cb;
// Accessible registers
uint8_t m_reg[16];
// Internal clock registers
// The clock operates on these registers and copies them to the
// accessible registers
uint8_t m_intreg[16];
TIMER_CALLBACK_MEMBER(rtc_clock_cb);
TIMER_CALLBACK_MEMBER(rtc_periodic_cb);
TIMER_CALLBACK_MEMBER(rtc_watchdog_cb);
void nvram_default() override;
void nvram_read(emu_file &file) override;
void nvram_write(emu_file &file) override;
void device_start() override;
// Sanity-check BCD number
bool valid_bcd(uint8_t value, uint8_t min, uint8_t max);
// Convert to/from BCD
uint8_t to_bcd(uint8_t value);
uint8_t from_bcd(uint8_t value);
// Check bits in register
bool is_set(int number, uint8_t flag);
// AM-PM / 24h conversion (BCD)
uint8_t ampmto24(uint8_t ampm);
uint8_t ampmfrom24(uint8_t ampm);
// Increment BCD number
bool increment_bcd(uint8_t& bcdnumber, uint8_t limit, uint8_t min);
// Set/Reset one or more bits in the register
void set_register(int number, uint8_t bits, bool set);
// Copy register contents from the internal registers to SRAM or back
void transfer_to_int();
void transfer_to_access();
// Check matching registers of time and alarm
bool check_match(int regint, int regalarm);
// Timers
emu_timer *m_clock_timer;
emu_timer *m_periodic_timer;
emu_timer *m_watchdog_timer;
// Set timers
void set_periodic_timer();
void set_watchdog_timer(bool on);
// Get time from system
void get_system_time();
// Get the delay until the next second
int get_delay();
// Flags
bool m_watchdog_active;
bool m_watchdog_asserted;
bool m_writing;
};
DECLARE_DEVICE_TYPE(BQ4847, bq4847_device)
#endif

View File

@ -0,0 +1,551 @@
// license:BSD-3-Clause
// copyright-holders:Michael Zapf
/*
Texas Instruments/Benchmarq BQ4842/52 Real-time clock
Michael Zapf, April 2020
*/
#include "emu.h"
#include "bq48x2.h"
#define LOG_WARN (1U<<1) // Warnings
#define LOG_CLOCK (1U<<2) // Clock operation
#define LOG_REGW (1U<<3) // Register write
#define LOG_WATCHDOG (1U<<4) // Watchdog
#define LOG_SRAM (1U<<5) // SRAM
#define VERBOSE ( LOG_GENERAL | LOG_WARN )
#include "logmacro.h"
// Need to check whether these chips really support leap year handling
#define DOLEAPYEARS 1
// device type definition
DEFINE_DEVICE_TYPE(BQ4842, bq4842_device, "bq4842", "Benchmarq BQ4842 RTC")
DEFINE_DEVICE_TYPE(BQ4852, bq4852_device, "bq4852", "Benchmarq BQ4852 RTC")
enum
{
reg_year = 0,
reg_month,
reg_date,
reg_days,
reg_hours,
reg_minutes,
reg_seconds,
reg_control,
reg_watchdog,
reg_interrupts,
reg_alarmdate,
reg_alarmhours,
reg_alarmminutes,
reg_alarmseconds,
reg_100ths,
reg_flags
};
enum
{
FLAG_FTE = 0x40,
FLAG_OSC = 0x80,
FLAG_W = 0x80,
FLAG_R = 0x40,
FLAG_WDS = 0x80,
FLAG_AIE = 0x80,
FLAG_PIE = 0x10,
FLAG_AF = 0x40,
FLAG_WDF = 0x80,
FLAG_PF = 0x08
};
//-------------------------------------------------
// Constructors for basetype
//-------------------------------------------------
bq48x2_device::bq48x2_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, int memsize)
: device_t(mconfig, type, tag, owner, 0),
device_nvram_interface(mconfig, *this),
m_interrupt_cb(*this),
m_resetout_cb(*this),
m_memsize(memsize)
{
}
//-------------------------------------------------
// Constructors for subtypes
//-------------------------------------------------
// 128 KiB memory (including clock registers)
bq4842_device::bq4842_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: bq48x2_device(mconfig, BQ4842, tag, owner, 128*1024)
{
}
// 512 KiB memory (including clock registers)
bq4852_device::bq4852_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: bq48x2_device(mconfig, BQ4852, tag, owner, 512*1024)
{
}
bool bq48x2_device::increment_bcd(uint8_t& bcdnumber, uint8_t limit, uint8_t min)
{
if (!valid_bcd(bcdnumber, min, limit))
{
bcdnumber = min;
return false;
}
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;
}
bool bq48x2_device::valid_bcd(uint8_t value, uint8_t min, uint8_t max)
{
bool valid = ((value>=min) && (value<=max) && ((value&0x0f)<=9));
if (!valid) LOGMASKED(LOG_WARN, "Invalid BCD number %02x\n", value);
return valid;
}
uint8_t bq48x2_device::to_bcd(uint8_t value)
{
return (((value / 10) << 4) & 0xf0) | (value % 10);
}
// ----------------------------------------------------
/*
Update cycle, called every second
The BQ RTCs use BCD representation
*/
TIMER_CALLBACK_MEMBER(bq48x2_device::rtc_clock_cb)
{
// BCD-encoded numbers
static const int days_in_month_table[12] =
{
0x31,0x28,0x31, 0x30,0x31,0x30,
0x31, 0x31, 0x30, 0x31, 0x30, 0x31
};
// Just for debugging
static const char* dow[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
bool carry = true;
bool newsec = false;
// Test mode (FTW) or oscillator stop (OSC)
if (get_register(reg_days, FLAG_FTE) || get_register(reg_seconds, FLAG_OSC))
return;
// When the timer ticks, the 100ths are 0.
m_intreg[reg_100ths] = 0;
if (carry)
{
carry = increment_bcd(m_intreg[reg_seconds], 0x59, 0);
newsec = true;
}
if (carry)
carry = increment_bcd(m_intreg[reg_minutes], 0x59, 0);
if (carry)
carry = increment_bcd(m_intreg[reg_hours], 0x23, 0);
if (carry)
{
uint8_t month = m_intreg[reg_month];
uint8_t days = 30;
if (valid_bcd(month, 0x01, 0x12))
days = days_in_month_table[month-1];
// Are leap years considered?
#if DOLEAPYEARS
if ((month==2)
&& ((m_intreg[reg_year]%4)==0)
&& ((m_intreg[reg_year]%100)!=0
|| (m_intreg[reg_year]%400)==0))
days = 0x29;
#endif
increment_bcd(m_intreg[reg_days], 7, 1); // increment the day-of-week (no carry)
carry = increment_bcd(m_intreg[reg_date], days, 1); // and now the day
}
if (carry)
carry = increment_bcd(m_intreg[reg_month], 0x12, 1);
if (carry)
carry = increment_bcd(m_intreg[reg_year], 0x99, 0);
LOGMASKED(LOG_CLOCK, "%s 20%02x-%02x-%02x %02x:%02x:%02x\n",
dow[m_intreg[reg_days]-1], m_intreg[reg_year], m_intreg[reg_month], m_intreg[reg_date],
m_intreg[reg_hours], m_intreg[reg_minutes], m_intreg[reg_seconds]);
// Copy into memory registers if the read bit is reset
if (newsec)
{
if (!is_set(reg_control, FLAG_R | FLAG_W))
{
// Copy values from internal registers to memory space
transfer_to_access();
}
if (check_match(reg_date, reg_alarmdate, 0x3f) &&
check_match(reg_hours, reg_alarmhours, 0x3f) &&
check_match(reg_minutes, reg_alarmminutes, 0x7f) &&
check_match(reg_seconds, reg_alarmseconds, 0x7f))
{
set_register(reg_flags, FLAG_AF, true);
m_interrupt_cb(intrq_r());
}
}
}
uint8_t bq48x2_device::get_register(int number, uint8_t mask)
{
return m_sram[m_memsize-1-number] & mask;
}
bool bq48x2_device::is_set(int number, uint8_t flag)
{
return get_register(number, flag)!=0;
}
void bq48x2_device::set_register(int number, uint8_t bits, bool set)
{
int addr = m_memsize-1-number;
if (set)
m_sram[addr] |= bits;
else
m_sram[addr] &= ~bits;
}
void bq48x2_device::set_register(int number, uint8_t value)
{
m_sram[m_memsize-1-number] = value;
}
// The 0 bits in these masks are the "unused bits" according to the specification;
// they are left unchanged
static const uint8_t regmask[] = { 0xff, 0x1f, 0x3f, 0x07, 0x3f, 0x7f, 0x7f, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xf8 };
void bq48x2_device::transfer_to_int()
{
uint8_t hds = m_intreg[reg_100ths];
for (int i=0; i < 16; i++)
m_intreg[i] = get_register(i, regmask[i]);
// If we set the 100ths not to be 0, the next second will occur earlier
if (hds != m_intreg[reg_100ths])
m_clock_timer->adjust(attotime::from_msec(get_delay()), 0, attotime::from_seconds(1));
}
void bq48x2_device::transfer_to_access()
{
for (int i=0; i < 16; i++)
set_register(i, get_register(i, ~regmask[i]) | (m_intreg[i] & regmask[i]));
}
bool bq48x2_device::check_match(int now, int alarm, uint8_t mask)
{
// The ignore feature is active once the alarm has set in
// Will lead to a periodic alarm
bool ignore = (is_set(alarm, 0x80) && is_set(reg_flags, FLAG_AF));
return ignore || ((m_intreg[now] & mask) == get_register(alarm, mask));
}
// =========================================================
/*
Read from SRAM or registers
*/
uint8_t bq48x2_device::read(offs_t address)
{
address = address & (m_memsize-1);
uint8_t value = m_sram[address];
if ((m_memsize-1-address) == reg_flags) // Read flag register
{
set_register(reg_flags, 0xf8, false); // reset all flags
m_interrupt_cb(intrq_r());
}
return value;
}
/*
Write to the SRAM or registers
*/
void bq48x2_device::write(offs_t address, uint8_t data)
{
address = address % m_memsize;
int regmask = (m_memsize - 1) & ~0x0f;
// Registers
if ((address & regmask) == regmask)
{
int regnum = 15 - (address & 0x0f);
switch (regnum)
{
// No special effect
case reg_year:
case reg_month:
case reg_date:
case reg_hours:
case reg_minutes:
case reg_alarmdate:
case reg_alarmhours:
case reg_alarmminutes:
case reg_alarmseconds:
case reg_100ths:
break;
case reg_days:
if (data & FLAG_FTE)
// Test mode
m_periodic_timer->adjust(attotime::from_hz(1024), 0, attotime::from_hz(1024));
else
{
// reset to periodic timing
set_periodic_timer();
}
break;
case reg_seconds:
// Start oscillator on falling edge
if (is_set(reg_seconds, FLAG_OSC) && ((data & FLAG_OSC) == 0))
connect_osc(true);
else
{
// Turn off oscillator on raising edge
if (!is_set(reg_seconds, FLAG_OSC) && ((data & FLAG_OSC) != 0))
connect_osc(false);
}
break;
case reg_control:
// Transfer to internal registers when W set to 0
if (is_set(reg_control, FLAG_W) && ((data & FLAG_W) == 0))
transfer_to_int();
// Calibration bits are ignored, we don't calibrate the
// backing PC clock
break;
case reg_watchdog:
set_register(regnum, data);
set_watchdog_timer();
break;
case reg_interrupts:
set_register(regnum, data);
set_periodic_timer();
return;
case reg_flags:
LOGMASKED(LOG_WARN, "Ignoring write attempt to flag bit register (%02x)\n", data);
return;
}
set_register(regnum, data);
}
else
{
LOGMASKED(LOG_SRAM, "sram %05x <- %02x\n", address, data);
m_sram[address] = data;
}
}
void bq48x2_device::set_periodic_timer()
{
uint8_t rateval = get_register(reg_interrupts, 0x0f);
int rate = 0;
switch (rateval)
{
case 0:
m_periodic_timer->reset();
break;
case 1:
m_periodic_timer->adjust(attotime::from_msec(10), 0, attotime::from_msec(10));
break;
case 2:
m_periodic_timer->adjust(attotime::from_msec(100), 0, attotime::from_msec(100));
break;
default:
rate = 1 << (16-rateval);
m_periodic_timer->adjust(attotime::from_hz(rate), 0, attotime::from_hz(rate));
break;
}
}
void bq48x2_device::set_watchdog_timer()
{
int multi = get_register(reg_watchdog, 0x7c)>>2;
int reso = get_register(reg_watchdog, 0x03);
// reso = 0 -> 1/16 s (2^-4) = 62500 us
// reso = 1 -> 1/4 s (2^-2)
// reso = 2 -> 1 s (2^0)
// reso = 3 -> 4 s (2^2)
int time = (1<<(reso*2))*62500 * multi;
m_watchdog_timer->adjust(attotime::from_usec(time)); // single shot
}
/*
Periodic cycle (called at defined intervals)
*/
TIMER_CALLBACK_MEMBER(bq48x2_device::rtc_periodic_cb)
{
// Test mode
if (get_register(reg_days, FLAG_FTE))
{
// Create a 1:1 on-off signal on the seconds' last bit
set_register(reg_seconds, get_register(reg_seconds, 0xff) ^ 0x01);
}
else
{
set_register(reg_flags, FLAG_PF, true);
// The INT line is only released by reading the flag register
if (intrq_r())
{
m_interrupt_cb(ASSERT_LINE);
}
}
}
/*
Watchdog callback
*/
TIMER_CALLBACK_MEMBER(bq48x2_device::rtc_watchdog_cb)
{
set_register(reg_flags, FLAG_WDF, true);
if (is_set(reg_watchdog, FLAG_WDS))
{
LOGMASKED(LOG_WATCHDOG, "Watchdog alarm, reset pulse\n");
m_resetout_cb(ASSERT_LINE);
// During the reset pulse, the watchdog register is cleared
set_register(reg_watchdog, 0);
m_resetout_cb(CLEAR_LINE);
}
else
{
LOGMASKED(LOG_WATCHDOG, "Watchdog alarm, interrupt\n");
m_interrupt_cb(intrq_r());
}
}
/*
Indicates that there is an interrupt condition. Also used to drive the
outgoing line.
*/
READ_LINE_MEMBER(bq48x2_device::intrq_r)
{
bool alarm = (is_set(reg_interrupts, FLAG_AIE) && is_set(reg_flags, FLAG_AF));
bool period = (is_set(reg_interrupts, FLAG_PIE) && is_set(reg_flags, FLAG_PF));
return (alarm || period)? ASSERT_LINE : CLEAR_LINE;
}
void bq48x2_device::connect_osc(bool conn)
{
if (conn)
{
// The internal update cycle is 1 sec
m_clock_timer->adjust(attotime::from_msec(get_delay()), 0, attotime::from_seconds(1));
set_periodic_timer();
}
else
{
// Turn off completely
m_clock_timer->reset();
m_watchdog_timer->reset();
m_periodic_timer->reset();
}
}
void bq48x2_device::get_system_time()
{
// Set time from system time
system_time systime;
machine().current_datetime(systime);
m_intreg[reg_hours] = to_bcd(systime.local_time.hour);
m_intreg[reg_minutes] = to_bcd(systime.local_time.minute);
m_intreg[reg_seconds] = to_bcd(systime.local_time.second);
m_intreg[reg_year] = to_bcd(systime.local_time.year%100);
m_intreg[reg_month] = to_bcd(systime.local_time.month+1);
m_intreg[reg_date] = to_bcd(systime.local_time.mday);
m_intreg[reg_days] = to_bcd(systime.local_time.weekday+1);
m_intreg[reg_100ths] = 0;
}
int bq48x2_device::get_delay()
{
int hds = ((m_intreg[reg_100ths] & 0xf0)>>16) * 10 + (m_intreg[reg_100ths] & 0x0f);
return 1000 - hds*10;
}
void bq48x2_device::device_start()
{
m_clock_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(bq48x2_device::rtc_clock_cb), this));
// Periodic timer
m_periodic_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(bq48x2_device::rtc_periodic_cb), this));
// Watchdog timer
m_watchdog_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(bq48x2_device::rtc_watchdog_cb), this));
// Interrupt line
m_interrupt_cb.resolve_safe();
// Reset output
m_resetout_cb.resolve_safe();
m_sram = std::make_unique<u8 []>(m_memsize);
// Interrupt enables are cleared on powerup
set_register(reg_interrupts, 0xff, false);
// State save
save_pointer(NAME(m_sram), m_memsize);
save_pointer(NAME(m_intreg), 8);
// Start clock
get_system_time();
connect_osc(true);
}
// ----------------------------------------------------
void bq48x2_device::nvram_default()
{
std::fill_n(m_sram.get(), m_memsize, 0);
}
void bq48x2_device::nvram_read(emu_file &file)
{
file.read(m_sram.get(), m_memsize);
get_system_time();
transfer_to_access(); // Transfer the system time into the readable registers
// Clear the saved flags
set_register(reg_flags, 0xf8, true);
}
void bq48x2_device::nvram_write(emu_file &file)
{
transfer_to_access();
file.write(m_sram.get(), m_memsize);
}

View File

@ -0,0 +1,128 @@
// license:BSD-3-Clause
// copyright-holders:Michael Zapf
/*
Texas Instruments/Benchmarq BQ4842/52 Real-time clock
See bq48x2.cpp for details.
Michael Zapf, April 2020
*/
#ifndef MAME_MACHINE_BQ48X2_H
#define MAME_MACHINE_BQ48X2_H
#pragma once
// ======================> bq48x2_device
class bq48x2_device : public device_t, public device_nvram_interface
{
public:
auto interrupt_cb() { return m_interrupt_cb.bind(); }
auto resetout_cb() { return m_resetout_cb.bind(); }
virtual uint8_t read(offs_t address);
virtual void write(offs_t address, uint8_t data);
DECLARE_READ_LINE_MEMBER(intrq_r);
// Mainly used to disconnect from oscillator
void connect_osc(bool conn);
protected:
// construction/destruction (called from the subtypes)
bq48x2_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, int memsize);
// callback called when interrupt pin state changes (may be nullptr)
devcb_write_line m_interrupt_cb;
// callback called when watchdog times out changes (may be nullptr)
devcb_write_line m_resetout_cb;
// Internal memory
std::unique_ptr<u8 []> m_sram;
// Internal clock registers
// The clock operates on these registers and copies them to the
// registers in the address space
uint8_t m_intreg[16];
TIMER_CALLBACK_MEMBER(rtc_clock_cb);
TIMER_CALLBACK_MEMBER(rtc_periodic_cb);
TIMER_CALLBACK_MEMBER(rtc_watchdog_cb);
void nvram_default() override;
void nvram_read(emu_file &file) override;
void nvram_write(emu_file &file) override;
void device_start() override;
void set_watchdog_timer();
private:
// Sanity-check BCD number
bool valid_bcd(uint8_t value, uint8_t min, uint8_t max);
// Convert to BCD
uint8_t to_bcd(uint8_t value);
// Increment BCD number
bool increment_bcd(uint8_t& bcdnumber, uint8_t limit, uint8_t min);
// Fetch register value from SRAM using register number
uint8_t get_register(int number, uint8_t mask);
// Set a register in SRAM using the register number
void set_register(int number, uint8_t value);
// Set/Reset one or more bits in the register
void set_register(int number, uint8_t bits, bool set);
// Check matching registers of time and alarm
bool check_match(int regint, int regalarm, uint8_t mask);
// Check bits in register
bool is_set(int number, uint8_t flag);
// Copy register contents from the internal registers to SRAM or back
void transfer_to_int();
void transfer_to_access();
// clock timer: called every second
emu_timer *m_clock_timer;
// Periodic timer
emu_timer *m_periodic_timer;
// Watchdog timer
emu_timer *m_watchdog_timer;
// Set timers
void set_periodic_timer();
// Get time from system
void get_system_time();
// Get the delay until the next second
int get_delay();
int m_memsize;
};
// Special types
class bq4842_device : public bq48x2_device
{
public:
bq4842_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};
class bq4852_device : public bq48x2_device
{
public:
bq4852_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};
DECLARE_DEVICE_TYPE(BQ4842, bq4842_device)
DECLARE_DEVICE_TYPE(BQ4852, bq4852_device)
#endif