summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/machine/bq4847.cpp
blob: d2618f03f53bef87a807f460a05471ad56dc0912 (plain) (tree)
1
2
3
4
5
6
7
8
9


                                 
                                                      
 


                                                                     
 


                                                                           
 

                                        
 
                                            
 
                            



                   




                                                        
 
                                        


                         
                                                                           







                                                                           
                                                                                      





                                                          



                                                                                        




                                       



                               



                         



                            





                                                   














                                                                                                                               


 











                                                                                                             
                                                                                                                      
 





















                                                                                                                           

                                   
 

                                                                                 
                               








                                                
                              







                                                
  

                                      
 

                                                                              
  
                                                     
 







                                                                                        
                                                                        



                              
                                                                        

                  
                                            

                  
                                   

                                                                    

                                                                                                                 
 

                   
                                                                 
                 




                                                                                          

                 



                                                                     
                 

                                                           



                 



                                       



                                                                                                                                      


                                                                                                            


                                                                                                                                                  
                 
                                                                        

                                                             

                                                                              
                                 
                                                                  


                                                     


                                                                                     

                                                                              

                                          
                                                                                              

                                              
                                                                                              

                                              
                                                                                              


                                                     
                                                                                              


















                                                   
                                                              

                                   
                                                      

                                                                       
                                                                              

                            

                                                                                                


                  

                                                              
                 

                                                                     



                 
                                                   
 
                                                                                            

 


                                           
                                             


                                


                                               
         



                                                                   
 

                                                              


                     



                                                       

                                                             





                                                                                                  
                                                                    
 







                                                                      


                                
                                     

                                     
                                       
         

                                                                                                    
 

                                                                                        
                 

                                                                               
                         
                                                                                          
                         

                                          
                 


         
                                                 

                                                                                        

                                                                               

 
                                        
 

                                                                                   
 

                                                            

 
                                                     
 
                                     
         






                                                                                              
         

 
                                      
 




                                           

 
                                        
 
                                 
         
                                    
 


                                     

 
                                                       
 

                                           

 
                                                       
 

                                                                           
 





                                                                                                                                 

 
                                
 


                                                                                                                  
         

                                           


         
           
 

                                  


                                                                                     
 










                                             

 
                                  
 
                               

 
                                          
 


                                                                                        

 
                         


                                   



                                                                      
 




                                                                                 
 


                                                                    
 
                                                       
 

                                                                                       

 
                                                         
 

                                                                                        
 
// 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;
}