/*
    rtc65271 emulation

    This chip is an RTC for computer built by Epson and Spezial-Electronic (I
    think SE is the second source here).

    Reference:
    * Realtime Clock Module RTC-65271 Application Manual
        <http://www.bgmicro.com/pdf/rtc65271.pdf>

    Todo:
    * Support square wave pin output?
    * Support DSE mode?

    Raphael Nabet, 2003-2004
*/

#include "emu.h"
#include "rtc65271.h"

static void field_interrupts(device_t *device);
static TIMER_CALLBACK( rtc_SQW_callback );
static TIMER_CALLBACK( rtc_begin_update_callback );
static TIMER_CALLBACK( rtc_end_update_callback );

/* Delay between the beginning (UIP asserted) and the end (UIP cleared and
update interrupt asserted) of the update cycle */
#define UPDATE_CYCLE_TIME attotime::from_usec(1984)
/* Delay between the assertion of UIP and the effective start of the update
cycle */
/*#define UPDATE_CYCLE_DELAY attotime::from_usec(244)*/

typedef struct _rtc65271_state rtc65271_state;
struct _rtc65271_state
{
	/* 64 8-bit registers (10 clock registers, 4 control/status registers, and
    50 bytes of user RAM) */
	UINT8 regs[64];
	UINT8 cur_reg;

	/* extended RAM: 4kbytes of battery-backed RAM (in pages of 32 bytes) */
	UINT8 xram[4096];
	UINT8 cur_xram_page;

	/* update timer: called every second */
	emu_timer *update_timer;

	/* SQW timer: called every periodic clock half-period */
	emu_timer *SQW_timer;
	UINT8 SQW_internal_state;

	/* callback called when interrupt pin state changes (may be NULL) */
	void (*interrupt_callback)(device_t *device, int state);
};

INLINE rtc65271_state *get_safe_token(device_t *device)
{
	assert(device != NULL);
	assert(device->type() == RTC65271);

	return (rtc65271_state *)downcast<legacy_device_base *>(device)->token();
}


enum
{
	reg_second = 0,
	reg_alarm_second,
	reg_minute,
	reg_alarm_minute,
	reg_hour,
	reg_alarm_hour,
	reg_weekday,
	reg_monthday,
	reg_month,
	reg_year,
	reg_A,
	reg_B,
	reg_C,
	reg_D
};

enum
{
	reg_A_UIP	= 0x80,
	reg_A_DV	= 0x70,
	reg_A_RS	= 0x0F,

	reg_B_SET	= 0x80,
	reg_B_PIE	= 0x40,
	reg_B_AIE	= 0x20,
	reg_B_UIE	= 0x10,
	reg_B_SQW	= 0x08,
	reg_B_DM	= 0x04,
	reg_B_24h	= 0x02,
	reg_B_DSE	= 0x01,

	reg_C_IRQF	= 0x80,
	reg_C_PF	= 0x40,
	reg_C_AF	= 0x20,
	reg_C_UF	= 0x10,

	reg_D_VRT	= 0x80
};

static const int SQW_freq_table[16] =
{
	0,
	256,
	128,
	8192,
	4096,
	2048,
	1024,
	512,
	256,
	128,
	64,
	32,
	16,
	8,
	4,
	2,
};


/*
    BCD utilities
*/

/*
    Increment a binary-encoded UINT8
*/
static UINT8 increment_binary(UINT8 data)
{
	return data+1;
}


/*
    Increment a BCD-encoded UINT8
*/
static UINT8 increment_BCD(UINT8 data)
{
	if ((data & 0x0f) < 0x09)
	{
		if ((data & 0xf0) < 0xa0)
			data++;
		else
			data = data + 0x01 - 0xa0;
	}
	else
	{
		if ((data & 0xf0) < 0xa0)
			data = data - 0x09 + 0x10;
		else
			data = data - 0x09 - 0x90;
	}
	return data;
}


/*
    Convert a binary-encoded UINT8 to BCD
*/
static UINT8 binary_to_BCD(UINT8 data)
{
	data %= 100;

	return ((data / 10) << 4) | (data %10);
}


/*
    Convert a BCD-encoded UINT8 to binary
*/
static UINT8 BCD_to_binary(UINT8 data)
{
	if ((data & 0x0f) >= 0x0a)
		data = data - 0x0a + 0x10;
	if ((data & 0xf0) >= 0xa0)
		data = data - 0xa0;

	return (data & 0x0f) + (((data & 0xf0) >> 4) * 10);
}


/*
    Public functions
*/


/*
    load the SRAM and register contents from file
*/
static int rtc65271_file_load(device_t *device, emu_file &file)
{
	rtc65271_state *state = get_safe_token(device);
	UINT8 buf;


	/* version flag */
	if (file.read(&buf, 1) != 1)
		return 1;
	if (buf != 0)
		return 1;

	/* control registers */
	if (file.read(&buf, 1) != 1)
		return 1;
	state->regs[reg_A] = buf & (reg_A_DV /*| reg_A_RS*/);
	if (file.read(&buf, 1) != 1)
		return 1;
	state->regs[reg_B] = buf & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE);

	/* alarm registers */
	if (file.read(&state->regs[reg_alarm_second], 1) != 1)
		return 1;
	if (file.read(&state->regs[reg_alarm_minute], 1) != 1)
		return 1;
	if (file.read(&state->regs[reg_alarm_hour], 1) != 1)
		return 1;

	/* user RAM */
	if (file.read(state->regs+14, 50) != 50)
		return 1;

	/* extended RAM */
	if (file.read(state->xram, 4096) != 4096)
		return 1;

	state->regs[reg_D] |= reg_D_VRT;	/* the data was backed up successfully */
	/*state->dirty = FALSE;*/

	{
		system_time systime;

		/* get the current date/time from the core */
		device->machine().current_datetime(systime);

		/* set clock registers */
		state->regs[reg_second] = systime.local_time.second;
		state->regs[reg_minute] = systime.local_time.minute;
		if (state->regs[reg_B] & reg_B_24h)
			/* 24-hour mode */
			state->regs[reg_hour] = systime.local_time.hour;
		else
		{	/* 12-hour mode */
			if (systime.local_time.hour >= 12)
			{
				state->regs[reg_hour] = 0x80;
				systime.local_time.hour -= 12;
			}
			else
				state->regs[reg_hour] = 0;
			state->regs[reg_hour] |= systime.local_time.hour ? systime.local_time.hour : 12;
		}
		state->regs[reg_weekday] = systime.local_time.weekday + 1;
		state->regs[reg_monthday] = systime.local_time.mday;
		state->regs[reg_month] = systime.local_time.month + 1;
		state->regs[reg_year] = systime.local_time.year % 100;
		if (! (state->regs[reg_B] & reg_B_DM))
		{	/* BCD mode */
			state->regs[reg_second] = binary_to_BCD(state->regs[reg_second]);
			state->regs[reg_minute] = binary_to_BCD(state->regs[reg_minute]);
			state->regs[reg_hour] = (state->regs[reg_hour] & 0x80) | binary_to_BCD(state->regs[reg_hour] & 0x7f);
			/*state->regs[reg_weekday] = binary_to_BCD(state->regs[reg_weekday]);*/
			state->regs[reg_monthday] = binary_to_BCD(state->regs[reg_monthday]);
			state->regs[reg_month] = binary_to_BCD(state->regs[reg_month]);
			state->regs[reg_year] = binary_to_BCD(state->regs[reg_year]);
		}
	}

	return 0;
}

/*
    save the SRAM and register contents to file
*/
static int rtc65271_file_save(device_t *device, emu_file &file)
{
	rtc65271_state *state = get_safe_token(device);
	UINT8 buf;


	/* version flag */
	buf = 0;
	if (file.write(& buf, 1) != 1)
		return 1;

	/* control registers */
	buf = state->regs[reg_A] & (reg_A_DV | reg_A_RS);
	if (file.write(&buf, 1) != 1)
		return 1;
	buf = state->regs[reg_B] & (reg_B_SET | reg_B_DM | reg_B_24h | reg_B_DSE);
	if (file.write(&buf, 1) != 1)
		return 1;

	/* alarm registers */
	if (file.write(&state->regs[reg_alarm_second], 1) != 1)
		return 1;
	if (file.write(&state->regs[reg_alarm_minute], 1) != 1)
		return 1;
	if (file.write(&state->regs[reg_alarm_hour], 1) != 1)
		return 1;

	/* user RAM */
	if (file.write(state->regs+14, 50) != 50)
		return 1;

	/* extended RAM */
	if (file.write(state->xram, 4096) != 4096)
		return 1;

	return 0;
}

/*
    Read a byte from clock

    xramsel: select RTC register if 0, XRAM if 1
    offset: address (A0-A5 pins)
*/
UINT8 rtc65271_r(device_t *device, int xramsel, offs_t offset)
{
	rtc65271_state *state = get_safe_token(device);
	int reply;

	if (xramsel)
	{
		if (offset & 0x20)
			/* XRAM page register */
			reply = state->cur_xram_page;
		else
			/* XRAM data */
			reply = state->xram[(offset & 0x1f) + 0x0020*state->cur_xram_page];
	}
	else
	{
		if (offset & 0x01)
			/* data register */
			switch (state->cur_reg)
			{
			case reg_C:
				reply = state->regs[state->cur_reg];
				state->regs[state->cur_reg] = 0;
				field_interrupts(device);
				break;
			case reg_D:
				reply = state->regs[state->cur_reg];
				state->regs[state->cur_reg] = /*0*/reg_D_VRT;	/* set VRT flag so that the computer does not complain that the battery is low */
				break;

			default:
				reply = state->regs[state->cur_reg];
				break;
			}
		else
			/* indirect address register */
			reply = state->cur_reg;
	}

	return reply;
}

READ8_DEVICE_HANDLER( rtc65271_rtc_r )
{
	return rtc65271_r( device, 0, offset );
}

READ8_DEVICE_HANDLER( rtc65271_xram_r )
{
	return rtc65271_r( device, 1, offset );
}

/*
    Write a byte to clock

    xramsel: select RTC register if 0, XRAM if 1
    offset: address (A0-A5 pins)
*/
void rtc65271_w(device_t *device, int xramsel, offs_t offset, UINT8 data)
{
	rtc65271_state *state = get_safe_token(device);
	if (xramsel)
	{
		if (offset & 0x20)
			/* XRAM page register */
			state->cur_xram_page = data & 0x7f;
		else
			/* XRAM data */
			state->xram[(offset & 0x1f) + 0x0020*state->cur_xram_page] = data;
	}
	else
	{
		if (offset & 0x01)
			/* data register */
			switch (state->cur_reg)
			{
			case reg_second:
				/* the data sheet says bit 7 is read-only.  (I have no idea of
                the reason why it is.) */
				state->regs[reg_second] = data & 0x7f;
				break;

			case reg_A:
				if ((data & reg_A_RS) != (state->regs[state->cur_reg] & reg_A_RS))
				{
					if (data & reg_A_RS)
					{
						attotime period = attotime::from_hz(SQW_freq_table[data & reg_A_RS]);
						attotime half_period = period / 2;
						attotime elapsed = state->update_timer->elapsed();

						if (half_period > elapsed)
							state->SQW_timer->adjust(half_period - elapsed);
						else
							state->SQW_timer->adjust(half_period);
					}
					else
					{
						state->SQW_internal_state = 0;	/* right??? */

						/* Stop the divider used for SQW and periodic interrupts. */
						state->SQW_timer->adjust(attotime::never);
					}
				}
				/* The UIP bit is read-only */
				state->regs[reg_A] = (data & ~reg_A_UIP) | (state->regs[reg_A] & reg_A_UIP);
				break;

			case reg_B:
				state->regs[state->cur_reg] = data;
				if (data & reg_B_SET)
				{
					/* if we are in SET mode, clear update cycle */
					state->regs[reg_A] &= ~reg_A_UIP;
					state->regs[reg_B] &= ~reg_B_UIE;	/* the data sheet tells this, but I wonder how much sense it makes */
					field_interrupts(device);
				}
				break;

			case reg_C:
			case reg_D:
				break;

			default:
				state->regs[state->cur_reg] = data;
				break;
			}
		else
			/* indirect address register */
			state->cur_reg = data & 0x3f;
	}
}

WRITE8_DEVICE_HANDLER( rtc65271_rtc_w )
{
	rtc65271_w( device, 0, offset, data );
}

WRITE8_DEVICE_HANDLER( rtc65271_xram_w )
{
	rtc65271_w( device, 1, offset, data );
}

static void field_interrupts(device_t *device)
{
	rtc65271_state *state = get_safe_token(device);

	if (state->regs[reg_C] & state->regs[reg_B] & (reg_C_PF | reg_C_AF | reg_C_UF))
	{
		state->regs[reg_C] |= reg_C_IRQF;
		if (state->interrupt_callback)
			state->interrupt_callback(device, 1);
	}
	else
	{
		state->regs[reg_C] &= ~reg_C_IRQF;
		if (state->interrupt_callback)
			state->interrupt_callback(device, 0);
	}
}


/*
    Timer handlers
*/

/*
    Update SQW output state each half-period and assert periodic interrupt each
    period.
*/
static TIMER_CALLBACK( rtc_SQW_callback )
{
	device_t *device = (device_t *)ptr;
	rtc65271_state *state = get_safe_token(device);
	attotime half_period;

	state->SQW_internal_state = ! state->SQW_internal_state;
	if (! state->SQW_internal_state)
	{
		/* high-to-low??? transition -> interrupt (or should it be low-to-high?) */
		state->regs[reg_C] |= reg_C_PF;
		field_interrupts(device);
	}

	half_period = attotime::from_hz(SQW_freq_table[state->regs[reg_A] & reg_A_RS]) / 2;
	state->SQW_timer->adjust(half_period);
}

/*
    Begin update cycle (called every second)
*/
static TIMER_CALLBACK( rtc_begin_update_callback )
{
	device_t *device = (device_t *)ptr;
	rtc65271_state *state = get_safe_token(device);

	if (((state->regs[reg_A] & reg_A_DV) == 0x20) && ! (state->regs[reg_B] & reg_B_SET))
	{
		state->regs[reg_A] |= reg_A_UIP;

		/* schedule end of update cycle */
		device->machine().scheduler().timer_set(UPDATE_CYCLE_TIME, FUNC(rtc_end_update_callback), 0, (void *)device);
	}
}

/*
    End update cycle (called UPDATE_CYCLE_TIME = 1948us after start of update
    cycle)
*/
static TIMER_CALLBACK( rtc_end_update_callback )
{
	static const int days_in_month_table[12] =
	{
		31,28,31, 30,31,30,
		31,31,30, 31,30,31
	};
	device_t *device = (device_t *)ptr;
	rtc65271_state *state = get_safe_token(device);
	UINT8 (*increment)(UINT8 data);
	int c59, c23, c12, c11, c29;

	if (! (state->regs[reg_A] & reg_A_UIP))
		/* abort if update cycle has been canceled */
		return;

	if (state->regs[reg_B] & reg_B_DM)
	{
		/* binary mode */
		increment = increment_binary;
		c59 = 59;
		c23 = 23;
		c12 = 12;
		c11 = 11;
		c29 = 29;
	}
	else
	{
		/* BCD mode */
		increment = increment_BCD;
		c59 = 0x59;
		c23 = 0x23;
		c12 = 0x12;
		c11 = 0x11;
		c29 = 0x29;
	}

	/* increment second */
	if (state->regs[reg_second] < c59)
		state->regs[reg_second] = (*increment)(state->regs[reg_second]);
	else
	{
		state->regs[reg_second] = 0;

		/* increment minute */
		if (state->regs[reg_minute] < c59)
			state->regs[reg_minute] = (*increment)(state->regs[reg_minute]);
		else
		{
			state->regs[reg_minute] = 0;

			/* increment hour */
			if (state->regs[reg_B] & reg_B_24h)
			{
				/* 24 hour mode */
				if (state->regs[reg_hour] < c23)
					state->regs[reg_hour] = (*increment)(state->regs[reg_hour]);
				else
					state->regs[reg_hour] = 0;
			}
			else
			{
				/* 12 hour mode */
				if (state->regs[reg_hour] < c12)
				{
					if ((state->regs[reg_hour] & 0x7f) == c11)
						state->regs[reg_hour] ^= 0x80;
					state->regs[reg_hour] = ((*increment)(state->regs[reg_hour] & 0x7f) & 0x7f)
											| (state->regs[reg_hour] & 0x80);
				}
				else
					state->regs[reg_hour] = 1 | (state->regs[reg_hour] & 0x80);
			}

			/* increment day if needed */
			if (state->regs[reg_hour] == ((state->regs[reg_B] & reg_B_24h) ? 0 : c12))
			{
				/* increment day */
				int days_in_month;

				if (state->regs[reg_weekday] < 7)
					state->regs[reg_weekday]++;
				else
					state->regs[reg_weekday] = 1;

				if ((state->regs[reg_month] != 2) || (state->regs[reg_year] & 0x03))
				{
					if (state->regs[reg_B] & reg_B_DM)
					{
						/* binary mode */
						days_in_month = days_in_month_table[state->regs[reg_month] - 1];
					}
					else
					{
						/* BCD mode */
						days_in_month = binary_to_BCD(days_in_month_table[BCD_to_binary(state->regs[reg_month]) - 1]);
					}
				}
				else
					days_in_month = c29;

				if (state->regs[reg_monthday] < days_in_month)
					state->regs[reg_monthday] = (*increment)(state->regs[reg_monthday]);
				else
				{
					/* increment month */
					state->regs[reg_monthday] = 1;

					if (state->regs[reg_month] < c12)
						state->regs[reg_month] = (*increment)(state->regs[reg_month]);
					else
					{
						/* increment year */
						state->regs[reg_month] = 1;

						if (state->regs[reg_B] & reg_B_DM)
						{
							/* binary mode */
							if (state->regs[reg_year] < 99)
								state->regs[reg_year]++;
							else
								state->regs[reg_year] = 0;
						}
						else
						{
							/* BCD mode */
							state->regs[reg_year] = increment_BCD(state->regs[reg_year]);
						}
					}
				}
			}
		}
	}

	state->regs[reg_A] &= ~reg_A_UIP;
	state->regs[reg_C] |= reg_C_UF;

	/* test for alarm (values in range 0xc0-0xff mean "don't care") */
	if ((((state->regs[reg_alarm_second] & 0xc0) == 0xc0) || (state->regs[reg_alarm_second] == state->regs[reg_second]))
			&& (((state->regs[reg_alarm_minute] & 0xc0) == 0xc0) || (state->regs[reg_alarm_minute] == state->regs[reg_minute]))
			&& (((state->regs[reg_alarm_hour] & 0xc0) == 0xc0) || (state->regs[reg_alarm_hour] == state->regs[reg_hour])))
		state->regs[reg_C] |= reg_C_AF;

	field_interrupts(device);
}

/*
    Initialize clock

    interrupt_callback: callback called when interrupt pin state changes (may
        be NULL)
*/

static DEVICE_START( rtc65271 )
{
	rtc65271_config *config = (rtc65271_config *)downcast<const legacy_device_base *>(device)->inline_config();
	rtc65271_state *state = get_safe_token(device);

	state->update_timer = device->machine().scheduler().timer_alloc(FUNC(rtc_begin_update_callback), (void *)device);
	state->update_timer->adjust(attotime::from_seconds(1), 0, attotime::from_seconds(1));
	state->SQW_timer = device->machine().scheduler().timer_alloc(FUNC(rtc_SQW_callback), (void *)device);
	state->interrupt_callback = config->interrupt_callback;

	device->save_item(NAME(state->regs));
	device->save_item(NAME(state->cur_reg));
	device->save_item(NAME(state->xram));
	device->save_item(NAME(state->cur_xram_page));
	device->save_item(NAME(state->SQW_internal_state));
}


static DEVICE_NVRAM( rtc65271 )
{
	if (read_or_write)
		rtc65271_file_save(device, *file);
	else if (file)
		rtc65271_file_load(device, *file);
}


static const char DEVTEMPLATE_SOURCE[] = __FILE__;

#define DEVTEMPLATE_ID(p,s)		p##rtc65271##s
#define DEVTEMPLATE_FEATURES	DT_HAS_START | DT_HAS_NVRAM | DT_HAS_INLINE_CONFIG
#define DEVTEMPLATE_NAME		"RTC65271"
#define DEVTEMPLATE_FAMILY		"RTC"
#include "devtempl.h"


DEFINE_LEGACY_NVRAM_DEVICE(RTC65271, rtc65271);
