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

                               





                                                                      
                                                     
                                                                            
 

                                                                      
                     
                             

 



                                                                            
                             
 

 

                                                              

                                                   
                                 
                                                   
 
                                                                                                               
                                                                                               
                                                       
                           
                                                                                                  

                                    

                                 
                                

                                


 
                                                                                                                                                                                              


                                                                              
                                                                                                  

                                    

                                 
                                

                                

 






                                                   
                                   
                                          

                                                       
 

                                   
 
 


                                                   
 



                                                                           

                     

 



                                                   
                                                                                              
 
                   
         






                                                 
                 






                                                                                                                                           
                         



                                                                
                                 











                                                                    
                                         













                                                                                                                          
                                                 







                                                                                    
                                                         
                                                                                 
                                                         




                                                                                                 
                                                         
                                                 


                                         
 






                                                                                                                                                       
 


                                                                 
 


                                                          
         

 
 





                                                   
 
                                                   
                                

                                                 
 

                                            
 
                                                            
         

            
                                                   

         
                    
                                          
                  
                                             
 
                            

                       


 



                                                   
 
                                                
 
                                           
 
                            

                       


 



                                                   
 
                                                 
 
                                            


 
                                                   
                                                
                                                   
 
                                  
 

                                        
 
                 


 
                                                   






                                                    
 




















































































































                                                                    
                                                   
 
                                         
 
                            
                                            
 
                                         
 
                                                                          
 


                                                                               
 





                                                




                                                                                                                                   




                                                                          
 

























                                                                    
 



































                                                                                                  
 


















                                                                              
            



                                            


 
 


                                                   
 
                                     

                       

                       
               
                               


                      



                                             
                                                                                     
                                                        
                                                                     
                                                                                                      
                                                  

                              
                           
                                                                         
                                                                                             
                                                                              

                                                                                        
                              

                           
                                        
                                                         


                              
                                               





                              
                                                                                         
 



                    


                                                   
 
                                       

                         
                                                                                         
 

                       
               
                                             


                      
                                
                 


                                                           
                              




                                                          
                              











                                                                               
                              
 
                        

                                               
                 


                      
// license:BSD-3-Clause
// copyright-holders:Curt Coder
/*********************************************************************

    mc146818.c

    Implementation of the MC146818 chip

    Real time clock chip with CMOS battery backed ram
    Used in IBM PC/AT, several PC clones, Amstrad NC200, Apollo workstations

*********************************************************************/

#include "coreutil.h"
#include "machine/mc146818.h"


//**************************************************************************
//  DEBUGGING
//**************************************************************************

#define LOG_MC146818        0



// device type definition
const device_type MC146818 = &device_creator<mc146818_device>;

//-------------------------------------------------
//  mc146818_device - constructor
//-------------------------------------------------

mc146818_device::mc146818_device(const machine_config &mconfig, std::string tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, MC146818, "MC146818 RTC", tag, owner, clock, "mc146818", __FILE__),
		device_nvram_interface(mconfig, *this),
		m_index(0),
		m_last_refresh(attotime::zero), m_clock_timer(nullptr), m_periodic_timer(nullptr),
		m_write_irq(*this),
		m_century_index(-1),
		m_epoch(0),
		m_use_utc(false),
		m_binary(false),
		m_hour(false),
		m_binyear(false)
{
}

mc146818_device::mc146818_device(const machine_config &mconfig, device_type type, std::string name, std::string tag, device_t *owner, UINT32 clock, std::string shortname, std::string source)
	: device_t(mconfig, type, name, tag, owner, clock, shortname, source),
		device_nvram_interface(mconfig, *this),
		m_index(0),
		m_last_refresh(attotime::zero), m_clock_timer(nullptr), m_periodic_timer(nullptr),
		m_write_irq(*this),
		m_century_index(-1),
		m_epoch(0),
		m_use_utc(false),
		m_binary(false),
		m_hour(false),
		m_binyear(false)
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void mc146818_device::device_start()
{
	m_data.resize(data_size());
	m_last_refresh = machine().time();
	m_clock_timer = timer_alloc(TIMER_CLOCK);
	m_periodic_timer = timer_alloc(TIMER_PERIODIC);

	m_write_irq.resolve_safe();
}


//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void mc146818_device::device_reset()
{
	m_data[REG_B] &= ~(REG_B_UIE | REG_B_AIE | REG_B_PIE | REG_B_SQWE);
	m_data[REG_C] = 0;

	update_irq();
}

//-------------------------------------------------
//  device_timer - handler timer events
//-------------------------------------------------

void mc146818_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
	case TIMER_PERIODIC:
		m_data[REG_C] |= REG_C_PF;
		update_irq();
		break;

	case TIMER_CLOCK:
		if (!(m_data[REG_B] & REG_B_SET))
		{
			/// TODO: find out how the real chip deals with updates when binary/bcd values are already outside the normal range
			int seconds = get_seconds() + 1;
			if (seconds < 60)
			{
				set_seconds(seconds);
			}
			else
			{
				set_seconds(0);

				int minutes = get_minutes() + 1;
				if (minutes < 60)
				{
					set_minutes(minutes);
				}
				else
				{
					set_minutes(0);

					int hours = get_hours() + 1;
					if (hours < 24)
					{
						set_hours(hours);
					}
					else
					{
						set_hours(0);

						int dayofweek = get_dayofweek() + 1;
						if (dayofweek <= 7)
						{
							set_dayofweek(dayofweek);
						}
						else
						{
							set_dayofweek(1);
						}

						int dayofmonth = get_dayofmonth() + 1;
						if (dayofmonth <= gregorian_days_in_month(get_month(), get_year() + 2000))
						{
							set_dayofmonth(dayofmonth);
						}
						else
						{
							set_dayofmonth(1);

							int month = get_month() + 1;
							if (month <= 12)
							{
								set_month(month);
							}
							else
							{
								set_month(1);

								set_year((get_year() + 1) % 100);
							}
						}
					}
				}
			}

			if ((m_data[REG_ALARM_SECONDS] == m_data[REG_SECONDS] || (m_data[REG_ALARM_SECONDS] & ALARM_DONTCARE) == ALARM_DONTCARE) &&
				(m_data[REG_ALARM_MINUTES] == m_data[REG_MINUTES] || (m_data[REG_ALARM_MINUTES] & ALARM_DONTCARE) == ALARM_DONTCARE) &&
				(m_data[REG_ALARM_HOURS] == m_data[REG_HOURS] || (m_data[REG_ALARM_HOURS] & ALARM_DONTCARE) == ALARM_DONTCARE))
			{
				// set the alarm interrupt flag AF
				m_data[REG_C] |= REG_C_AF;
			}

			// set the update-ended interrupt Flag UF
			m_data[REG_C] |=  REG_C_UF;
			update_irq();

			m_last_refresh = machine().time();
		}
		break;
	}
}


//-------------------------------------------------
//  nvram_default - called to initialize NVRAM to
//  its default state
//-------------------------------------------------

void mc146818_device::nvram_default()
{
	// populate from a memory region if present
	if (m_region != nullptr)
	{
		UINT32 bytes = m_region->bytes();

		if (bytes > data_size())
			bytes = data_size();

		memcpy(&m_data[0], m_region->base(), bytes);
	}
	else
	{
		memset(&m_data[0], 0, data_size());
	}

	if(m_binary)
		m_data[REG_B] |= REG_B_DM;
	if(m_hour)
		m_data[REG_B] |= REG_B_24_12;

	set_base_datetime();
	update_timer();
	update_irq();
}


//-------------------------------------------------
//  nvram_read - called to read NVRAM from the
//  .nv file
//-------------------------------------------------

void mc146818_device::nvram_read(emu_file &file)
{
	file.read(&m_data[0], data_size());

	set_base_datetime();
	update_timer();
	update_irq();
}


//-------------------------------------------------
//  nvram_write - called to write NVRAM to the
//  .nv file
//-------------------------------------------------

void mc146818_device::nvram_write(emu_file &file)
{
	file.write(&m_data[0], data_size());
}


//-------------------------------------------------
//  to_ram - convert value to current ram format
//-------------------------------------------------

int mc146818_device::to_ram(int a)
{
	if (!(m_data[REG_B] & REG_B_DM))
		return dec_2_bcd(a);

	return a;
}


//-------------------------------------------------
//  from_ram - convert value from current ram format
//-------------------------------------------------

int mc146818_device::from_ram(int a)
{
	if (!(m_data[REG_B] & REG_B_DM))
		return bcd_2_dec(a);

	return a;
}


int mc146818_device::get_seconds()
{
	return from_ram(m_data[REG_SECONDS]);
}

void mc146818_device::set_seconds(int seconds)
{
	m_data[REG_SECONDS] = to_ram(seconds);
}

int mc146818_device::get_minutes()
{
	return from_ram(m_data[REG_MINUTES]);
}

void mc146818_device::set_minutes(int minutes)
{
	m_data[REG_MINUTES] = to_ram(minutes);
}

int mc146818_device::get_hours()
{
	if (!(m_data[REG_B] & REG_B_24_12))
	{
		int hours = from_ram(m_data[REG_HOURS] & ~HOURS_PM);

		if (hours == 12)
		{
			hours = 0;
		}

		if (m_data[REG_HOURS] & HOURS_PM)
		{
			hours += 12;
		}

		return hours;
	}
	else
	{
		return from_ram(m_data[REG_HOURS]);
	}
}

void mc146818_device::set_hours(int hours)
{
	if (!(m_data[REG_B] & REG_B_24_12))
	{
		int pm = 0;

		if (hours >= 12)
		{
			hours -= 12;
			pm = HOURS_PM;
		}

		if (hours == 0)
		{
			hours = 12;
		}

		m_data[REG_HOURS] = to_ram(hours) | pm;
	}
	else
	{
		m_data[REG_HOURS] = to_ram(hours);
	}
}

int mc146818_device::get_dayofweek()
{
	return from_ram(m_data[REG_DAYOFWEEK]);
}

void mc146818_device::set_dayofweek(int dayofweek)
{
	m_data[REG_DAYOFWEEK] = to_ram(dayofweek);
}

int mc146818_device::get_dayofmonth()
{
	return from_ram(m_data[REG_DAYOFMONTH]);
}

void mc146818_device::set_dayofmonth(int dayofmonth)
{
	m_data[REG_DAYOFMONTH] = to_ram(dayofmonth);
}

int mc146818_device::get_month()
{
	return from_ram(m_data[REG_MONTH]);
}

void mc146818_device::set_month(int month)
{
	m_data[REG_MONTH] = to_ram(month);
}

int mc146818_device::get_year()
{
	return from_ram(m_data[REG_YEAR]);
}

void mc146818_device::set_year(int year)
{
	m_data[REG_YEAR] = to_ram(year);
}



//-------------------------------------------------
//  set_base_datetime - update clock with real time
//-------------------------------------------------

void mc146818_device::set_base_datetime()
{
	system_time systime;
	system_time::full_time current_time;

	machine().base_datetime(systime);

	current_time = (m_use_utc) ? systime.utc_time: systime.local_time;

//  logerror("mc146818_set_base_datetime %02d/%02d/%02d %02d:%02d:%02d\n",
//          current_time.year % 100, current_time.month + 1, current_time.mday,
//          current_time.hour,current_time.minute, current_time.second);

	set_seconds(current_time.second);
	set_minutes(current_time.minute);
	set_hours(current_time.hour);
	set_dayofweek(current_time.weekday + 1);
	set_dayofmonth(current_time.mday);
	set_month(current_time.month + 1);

	if(m_binyear)
		set_year((current_time.year - m_epoch) % (m_data[REG_B] & REG_B_DM ? 0x100 : 100)); // pcd actually depends on this
	else
		set_year((current_time.year - m_epoch) % 100);

	if (m_century_index >= 0)
		m_data[m_century_index] = to_ram(current_time.year / 100);
}


//-------------------------------------------------
//  update_timer - update timer based on A register
//-------------------------------------------------

void mc146818_device::update_timer()
{
	int bypass;

	switch (m_data[REG_A] & (REG_A_DV2 | REG_A_DV1 | REG_A_DV0))
	{
	case 0:
		bypass = 0;
		break;

	case REG_A_DV0:
		bypass = 2;
		break;

	case REG_A_DV1:
		bypass = 7;
		break;

	case REG_A_DV2 | REG_A_DV1:
	case REG_A_DV2 | REG_A_DV1 | REG_A_DV0:
		bypass = 22;
		break;

	default:
		// TODO: other combinations of divider bits are used for test purposes only
		bypass = 22;
		break;
	}


	attotime update_period = attotime::never;
	attotime update_interval = attotime::never;
	attotime periodic_period = attotime::never;
	attotime periodic_interval = attotime::never;

	if (bypass < 22)
	{
		int shift = 22 - bypass;

		double update_hz = (double) clock() / (1 << shift);

		// TODO: take the time since last timer into account
		update_period = attotime::from_hz(update_hz * 2);
		update_interval = attotime::from_hz(update_hz);

		int rate_select = m_data[REG_A] & (REG_A_RS3 | REG_A_RS2 | REG_A_RS1 | REG_A_RS0);
		if (rate_select != 0)
		{
			shift = (rate_select + 6) - bypass;
			if (shift <= 1)
				shift += 7;

			double periodic_hz = (double) clock() / (1 << shift);

			// TODO: take the time since last timer into account
			periodic_period = attotime::from_hz(periodic_hz * 2);
			periodic_interval = attotime::from_hz(periodic_hz);
		}
	}

	m_clock_timer->adjust(update_period, 0, update_interval);
	m_periodic_timer->adjust(periodic_period, 0, periodic_interval);
}


//-------------------------------------------------
//  update_irq - Update irq based on B & C register
//-------------------------------------------------

void mc146818_device::update_irq()
{
	// IRQ line is active low
	if (((m_data[REG_C] & REG_C_UF) && (m_data[REG_B] & REG_B_UIE)) ||
		((m_data[REG_C] & REG_C_AF) && (m_data[REG_B] & REG_B_AIE)) ||
		((m_data[REG_C] & REG_C_PF) && (m_data[REG_B] & REG_B_PIE)))
	{
		m_data[REG_C] |= REG_C_IRQF;
		m_write_irq(CLEAR_LINE);
	}
	else
	{
		m_data[REG_C] &= REG_C_IRQF;
		m_write_irq(ASSERT_LINE);
	}
}



//-------------------------------------------------
//  read - I/O handler for reading
//-------------------------------------------------

READ8_MEMBER( mc146818_device::read )
{
	UINT8 data = 0;
	switch (offset)
	{
	case 0:
		data = m_index;
		break;

	case 1:
		switch (m_index)
		{
		case REG_A:
			data = m_data[REG_A];
			// Update In Progress (UIP) time for 32768 Hz is 244+1984usec
			/// TODO: support other dividers
			/// TODO: don't set this if update is stopped
			if ((space.machine().time() - m_last_refresh) < attotime::from_usec(244+1984))
				data |= REG_A_UIP;
			break;

		case REG_C:
			// the unused bits b0 ... b3 are always read as 0
			data = m_data[REG_C] & (REG_C_IRQF | REG_C_PF | REG_C_AF | REG_C_UF);
			// read 0x0c will clear all IRQ flags in register 0x0c
			m_data[REG_C] &= ~(REG_C_IRQF | REG_C_PF | REG_C_AF | REG_C_UF);
			update_irq();
			break;

		case REG_D:
			/* battery ok */
			data = m_data[REG_D] | REG_D_VRT;
			break;

		default:
			data = m_data[m_index];
			break;
		}
		break;
	}

	if (LOG_MC146818)
		logerror("mc146818_port_r(): index=0x%02x data=0x%02x\n", m_index, data);

	return data;
}


//-------------------------------------------------
//  write - I/O handler for writing
//-------------------------------------------------

WRITE8_MEMBER( mc146818_device::write )
{
	if (LOG_MC146818)
		logerror("mc146818_port_w(): index=0x%02x data=0x%02x\n", m_index, data);

	switch (offset)
	{
	case 0:
		m_index = data % data_size();
		break;

	case 1:
		switch (m_index)
		{
		case REG_SECONDS:
			// top bit of SECONDS is read only
			m_data[REG_SECONDS] = data & ~0x80;
			break;

		case REG_A:
			// top bit of A is read only
			m_data[REG_A] = data & ~REG_A_UIP;
			update_timer();
			break;

		case REG_B:
			if ((data & REG_B_SET) && !(m_data[REG_B] & REG_B_SET))
				data &= ~REG_B_UIE;

			m_data[REG_B] = data;
			update_irq();
			break;

		case REG_C:
		case REG_D:
			// register C & D is readonly
			break;

		default:
			m_data[m_index] = data;
			break;
		}
		break;
	}
}