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

    DALLAS DS2404

    RTC + BACKUP RAM

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

#include "emu.h"
#include "ds2404.h"
#include <time.h>
#include "devhelpr.h"


//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

// device type definition
const device_type DS2404 = &device_creator<ds2404_device>;

//-------------------------------------------------
//  ds2404_device - constructor
//-------------------------------------------------

ds2404_device::ds2404_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, DS2404, "DS2404", tag, owner, clock),
	  device_nvram_interface(mconfig, *this)
{
}


//-------------------------------------------------
//  static_set_ref_year - configuration helper
//  to set the reference year
//-------------------------------------------------

void ds2404_device::static_set_ref_year(device_t &device, UINT32 year)
{
	ds2404_device &ds2404 = downcast<ds2404_device &>(device);
	ds2404.m_ref_year = year;
}


//-------------------------------------------------
//  static_set_ref_month - configuration helper
//  to set the reference month
//-------------------------------------------------

void ds2404_device::static_set_ref_month(device_t &device, UINT8 month)
{
	ds2404_device &ds2404 = downcast<ds2404_device &>(device);
	ds2404.m_ref_month = month;
}


//-------------------------------------------------
//  static_set_ref_day - configuration helper
//  to set the reference day
//-------------------------------------------------

void ds2404_device::static_set_ref_day(device_t &device, UINT8 day)
{
	ds2404_device &ds2404 = downcast<ds2404_device &>(device);
	ds2404.m_ref_day = day;
}


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

void ds2404_device::device_start()
{
	struct tm ref_tm;

	memset(&ref_tm, 0, sizeof(ref_tm));
	ref_tm.tm_year = m_ref_year - 1900;
	ref_tm.tm_mon = m_ref_month - 1;
	ref_tm.tm_mday = m_ref_day;

	time_t ref_time = mktime(&ref_tm);

	time_t current_time;
	time(&current_time);
	current_time -= ref_time;

	m_rtc[0] = 0x0;
	m_rtc[1] = (current_time >> 0) & 0xff;
	m_rtc[2] = (current_time >> 8) & 0xff;
	m_rtc[3] = (current_time >> 16) & 0xff;
	m_rtc[4] = (current_time >> 24) & 0xff;

	emu_timer *timer = machine().scheduler().timer_alloc(FUNC(ds2404_tick_callback), (void *)this);
	timer->adjust(attotime::from_hz(256), 0, attotime::from_hz(256));
}


void ds2404_device::ds2404_rom_cmd(UINT8 cmd)
{
	switch(cmd)
	{
		case 0xcc:		/* Skip ROM */
			m_state[0] = DS2404_STATE_COMMAND;
			m_state_ptr = 0;
			break;

		default:
			fatalerror("DS2404: Unknown ROM command %02X", cmd);
			break;
	}
}

void ds2404_device::ds2404_cmd(UINT8 cmd)
{
	switch(cmd)
	{
		case 0x0f:		/* Write scratchpad */
			m_state[0] = DS2404_STATE_ADDRESS1;
			m_state[1] = DS2404_STATE_ADDRESS2;
			m_state[2] = DS2404_STATE_INIT_COMMAND;
			m_state[3] = DS2404_STATE_WRITE_SCRATCHPAD;
			m_state_ptr = 0;
			break;

		case 0x55:		/* Copy scratchpad */
			m_state[0] = DS2404_STATE_ADDRESS1;
			m_state[1] = DS2404_STATE_ADDRESS2;
			m_state[2] = DS2404_STATE_OFFSET;
			m_state[3] = DS2404_STATE_INIT_COMMAND;
			m_state[4] = DS2404_STATE_COPY_SCRATCHPAD;
			m_state_ptr = 0;
			break;

		case 0xf0:		/* Read memory */
			m_state[0] = DS2404_STATE_ADDRESS1;
			m_state[1] = DS2404_STATE_ADDRESS2;
			m_state[2] = DS2404_STATE_INIT_COMMAND;
			m_state[3] = DS2404_STATE_READ_MEMORY;
			m_state_ptr = 0;
			break;

		default:
			fatalerror("DS2404: Unknown command %02X", cmd);
			break;
	}
}

UINT8 ds2404_device::ds2404_readmem()
{
	if( m_address < 0x200 )
	{
		return m_sram[ m_address ];
	}
	else if( m_address >= 0x202 && m_address <= 0x206 )
	{
		return m_rtc[ m_address - 0x202 ];
	}
	return 0;
}

void ds2404_device::ds2404_writemem(UINT8 value)
{
	if( m_address < 0x200 )
	{
		m_sram[ m_address ] = value;
	}
	else if( m_address >= 0x202 && m_address <= 0x206 )
	{
		m_rtc[ m_address - 0x202 ] = value;
	}
}

WRITE8_DEVICE_HANDLER_TRAMPOLINE(ds2404, ds2404_1w_reset_w)
{
	m_state[0] = DS2404_STATE_IDLE;
	m_state_ptr = 0;
}

WRITE8_DEVICE_HANDLER_TRAMPOLINE(ds2404, ds2404_3w_reset_w)
{
	m_state[0] = DS2404_STATE_COMMAND;
	m_state_ptr = 0;
}

READ8_DEVICE_HANDLER_TRAMPOLINE(ds2404, ds2404_data_r)
{
	UINT8 value = 0;
	switch(m_state[m_state_ptr])
	{
		case DS2404_STATE_IDLE:
		case DS2404_STATE_COMMAND:
		case DS2404_STATE_ADDRESS1:
		case DS2404_STATE_ADDRESS2:
		case DS2404_STATE_OFFSET:
		case DS2404_STATE_INIT_COMMAND:
			break;

		case DS2404_STATE_READ_MEMORY:
			value = ds2404_readmem();
			break;

		case DS2404_STATE_READ_SCRATCHPAD:
			if(m_offset < 0x20)
			{
				value = m_ram[m_offset];
				m_offset++;
			}
			break;

		case DS2404_STATE_WRITE_SCRATCHPAD:
			break;

		case DS2404_STATE_COPY_SCRATCHPAD:
			break;
	}
	return value;
}

WRITE8_DEVICE_HANDLER_TRAMPOLINE(ds2404, ds2404_data_w)
{
	switch( m_state[m_state_ptr] )
	{
		case DS2404_STATE_IDLE:
			ds2404_rom_cmd(data & 0xff);
			break;

		case DS2404_STATE_COMMAND:
			ds2404_cmd(data & 0xff);
			break;

		case DS2404_STATE_ADDRESS1:
			m_a1 = data & 0xff;
			m_state_ptr++;
			break;

		case DS2404_STATE_ADDRESS2:
			m_a2 = data & 0xff;
			m_state_ptr++;
			break;

		case DS2404_STATE_OFFSET:
			m_end_offset = data & 0xff;
			m_state_ptr++;
			break;

		case DS2404_STATE_INIT_COMMAND:
			break;

		case DS2404_STATE_READ_MEMORY:
			break;

		case DS2404_STATE_READ_SCRATCHPAD:
			break;

		case DS2404_STATE_WRITE_SCRATCHPAD:
			if( m_offset < 0x20 )
			{
				m_ram[m_offset] = data & 0xff;
				m_offset++;
			}
			else
			{
				/* Set OF flag */
			}
			break;

		case DS2404_STATE_COPY_SCRATCHPAD:
			break;
	}

	if( m_state[m_state_ptr] == DS2404_STATE_INIT_COMMAND )
	{
		switch( m_state[m_state_ptr + 1] )
		{
			case DS2404_STATE_IDLE:
			case DS2404_STATE_COMMAND:
			case DS2404_STATE_ADDRESS1:
			case DS2404_STATE_ADDRESS2:
			case DS2404_STATE_OFFSET:
			case DS2404_STATE_INIT_COMMAND:
				break;

			case DS2404_STATE_READ_MEMORY:
				m_address = (m_a2 << 8) | m_a1;
				m_address -= 1;
				break;

			case DS2404_STATE_WRITE_SCRATCHPAD:
				m_address = (m_a2 << 8) | m_a1;
				m_offset = m_address & 0x1f;
				break;

			case DS2404_STATE_READ_SCRATCHPAD:
				m_address = (m_a2 << 8) | m_a1;
				m_offset = m_address & 0x1f;
				break;

			case DS2404_STATE_COPY_SCRATCHPAD:
				m_address = (m_a2 << 8) | m_a1;

				for(int i = 0; i <= m_end_offset; i++)
				{
					ds2404_writemem(m_ram[i]);
					m_address++;
				}
				break;
		}
		m_state_ptr++;
	}
}

WRITE8_DEVICE_HANDLER_TRAMPOLINE(ds2404, ds2404_clk_w)
{
	switch( m_state[m_state_ptr] )
	{
		case DS2404_STATE_IDLE:
		case DS2404_STATE_COMMAND:
		case DS2404_STATE_ADDRESS1:
		case DS2404_STATE_ADDRESS2:
		case DS2404_STATE_OFFSET:
		case DS2404_STATE_INIT_COMMAND:
			break;

		case DS2404_STATE_READ_MEMORY:
			m_address++;
			break;

		case DS2404_STATE_READ_SCRATCHPAD:
			break;

		case DS2404_STATE_WRITE_SCRATCHPAD:
			break;

		case DS2404_STATE_COPY_SCRATCHPAD:
			break;
	}
}

TIMER_CALLBACK( ds2404_device::ds2404_tick_callback )
{
	reinterpret_cast<ds2404_device*>(ptr)->ds2404_tick();
}

void ds2404_device::ds2404_tick()
{
	for(int i = 0; i < 5; i++)
	{
		m_rtc[ i ]++;
		if(m_rtc[ i ] != 0)
		{
			break;
		}
	}
}


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

void ds2404_device::nvram_default()
{
	memset(m_sram, 0, sizeof(m_sram));
}


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

void ds2404_device::nvram_read(emu_file &file)
{
	file.read(m_sram, sizeof(m_sram));
}


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

void ds2404_device::nvram_write(emu_file &file)
{
	file.write(m_sram, sizeof(m_sram));
}
