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

    NEC uPD4990A

    Serial I/O Calendar & Clock IC used in the
        NEO GEO and probably a couple of other machines.


    Completed by ElSemi.

    I haven't found any schematics for this device
    so I had to make some assumptions about how it works.

    The three input bits seem to be used for a serial protocol

    bit 0 - data
    bit 1 - clock
    bit 2 - command end (?)

    the commands I've found so far are:

    0x0 - ?? sent after 2
    0x1 - Reset the (probable) shift register used for output
    0x2 - Store the contents of the shift reg to the current date
    0x3 - Load Shift register with current date

    0x7 - Switch test bit every frame
    0x8 - Switch test bit every half-second


    2009-06 Converted to be a device

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

#include "driver.h"
#include "machine/pd4990a.h"


/***************************************************************************
    CONSTANTS
***************************************************************************/

#define DATA_BIT	0x01
#define CLOCK_BIT	0x02
#define END_BIT		0x04

/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

typedef struct _upd4990a_state upd4990a_state;
struct _upd4990a_state
{
	int seconds;	/* seconds BCD */
	int minutes;	/* minutes BCD */
	int hours;		/* hours   BCD */
	int days;		/* days    BCD */
	int month;		/* month   Hexadecimal form */
	int year;		/* year    BCD */
	int weekday;	/* weekday BCD */

	UINT32 shiftlo;
	UINT32 shifthi;

	int retraces;	/* Assumes 60 retraces a second */
	int testwaits;
	int maxwaits;	/* Switch test every frame*/
	int testbit;	/* Pulses a bit in order to simulate test output */

	int outputbit;
	int bitno;
	INT8 reading;
	INT8 writing;

	int clock_line;
	int command_line;	//??
};



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

INLINE upd4990a_state *get_safe_token(const device_config *device)
{
	assert(device != NULL);
	assert(device->token != NULL);
	assert((device->type == UPD4990A));
	return (upd4990a_state *)device->token;
}

INLINE UINT8 convert_to_bcd(int val)
{
	return ((val / 10) << 4) | (val % 10);
}


/***************************************************************************
    IMPLEMENTATION
***************************************************************************/

/*-------------------------------------------------
    upd4990a_increment_month
-------------------------------------------------*/

static void upd4990a_increment_month( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	upd4990a->month++;
	if (upd4990a->month == 13)
	{
		upd4990a->month = 1;
		upd4990a->year++;
		if ((upd4990a->year & 0x0f) >= 10)
		{
			upd4990a->year &= 0xf0;
			upd4990a->year += 0x10;
		}
		if (upd4990a->year == 0xA0)
			upd4990a->year = 0;
	}
}

/*-------------------------------------------------
    upd4990a_increment_day
-------------------------------------------------*/

static void upd4990a_increment_day( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);
	int real_year;

	upd4990a->days++;
	if ((upd4990a->days & 0x0f) >= 10)
	{
		upd4990a->days &= 0xf0;
		upd4990a->days += 0x10;
	}

	upd4990a->weekday++;
	if (upd4990a->weekday == 7)
		upd4990a->weekday = 0;

	switch (upd4990a->month)
	{
		case 1: case 3: case 5: case 7: case 8: case 10: case 12:
			if (upd4990a->days == 0x32)
			{
				upd4990a->days = 1;
				upd4990a_increment_month(device);
			}
			break;
		case 2:
			real_year = (upd4990a->year >> 4) * 10 + (upd4990a->year & 0xf);
			if ((real_year % 4) && (!(real_year % 100) || (real_year % 400)))
			{
				if (upd4990a->days == 0x29)
				{
					upd4990a->days = 1;
					upd4990a_increment_month(device);
				}
			}
			else
			{
				if (upd4990a->days == 0x30)
				{
					upd4990a->days = 1;
					upd4990a_increment_month(device);
				}
			}
			break;
		case 4: case 6: case 9: case 11:
			if (upd4990a->days == 0x31)
			{
				upd4990a->days = 1;
				upd4990a_increment_month(device);
			}
			break;
	}
}

/*-------------------------------------------------
    upd4990a_addretrace
-------------------------------------------------*/

void upd4990a_addretrace( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	++upd4990a->testwaits;
	if(upd4990a->testwaits >= upd4990a->maxwaits)
	{
		upd4990a->testbit ^= 1;
		upd4990a->testwaits = 0;
	}

	upd4990a->retraces++;
	if (upd4990a->retraces < 60)
		return;

	upd4990a->retraces = 0;
	upd4990a->seconds++;
	if ((upd4990a->seconds & 0x0f) < 10)
		return;

	upd4990a->seconds &= 0xf0;
	upd4990a->seconds += 0x10;
	if (upd4990a->seconds < 0x60)
		return;

	upd4990a->seconds = 0;
	upd4990a->minutes++;
	if ((upd4990a->minutes & 0x0f) < 10)
		return;

	upd4990a->minutes &= 0xf0;
	upd4990a->minutes += 0x10;
	if (upd4990a->minutes < 0x60)
		return;

	upd4990a->minutes = 0;
	upd4990a->hours++;
	if ((upd4990a->hours & 0x0f) < 10)
		return;

	upd4990a->hours &= 0xf0;
	upd4990a->hours += 0x10;
	if (upd4990a->hours < 0x24)
		return;

	upd4990a->hours = 0;
	upd4990a_increment_day(device);
}

/*-------------------------------------------------
    upd4990a_testbit_r
-------------------------------------------------*/

READ8_DEVICE_HANDLER( upd4990a_testbit_r )
{
	upd4990a_state *upd4990a = get_safe_token(device);
	return upd4990a->testbit;
}

/*-------------------------------------------------
    upd4990a_databit_r
-------------------------------------------------*/

READ8_DEVICE_HANDLER( upd4990a_databit_r )
{
	upd4990a_state *upd4990a = get_safe_token(device);
	return upd4990a->outputbit;
}

/*-------------------------------------------------
    upd4990a_readbit
-------------------------------------------------*/

static void upd4990a_readbit( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	switch (upd4990a->bitno)
	{
		case 0x00: case 0x01: case 0x02: case 0x03:
		case 0x04: case 0x05: case 0x06: case 0x07:
			upd4990a->outputbit = (upd4990a->seconds >> upd4990a->bitno) & 0x01;
			break;
		case 0x08: case 0x09: case 0x0a: case 0x0b:
		case 0x0c: case 0x0d: case 0x0e: case 0x0f:
			upd4990a->outputbit = (upd4990a->minutes >> (upd4990a->bitno - 0x08)) & 0x01;
			break;
		case 0x10: case 0x11: case 0x12: case 0x13:
		case 0x14: case 0x15: case 0x16: case 0x17:
			upd4990a->outputbit = (upd4990a->hours >> (upd4990a->bitno - 0x10)) & 0x01;
			break;
		case 0x18: case 0x19: case 0x1a: case 0x1b:
		case 0x1c: case 0x1d: case 0x1e: case 0x1f:
			upd4990a->outputbit = (upd4990a->days >> (upd4990a->bitno - 0x18)) & 0x01;
			break;
		case 0x20: case 0x21: case 0x22: case 0x23:
			upd4990a->outputbit = (upd4990a->weekday >> (upd4990a->bitno - 0x20)) & 0x01;
			break;
		case 0x24: case 0x25: case 0x26: case 0x27:
			upd4990a->outputbit = (upd4990a->month >> (upd4990a->bitno - 0x24)) & 0x01;
			break;
		case 0x28: case 0x29: case 0x2a: case 0x2b:
		case 0x2c: case 0x2d: case 0x2e: case 0x2f:
			upd4990a->outputbit = (upd4990a->year >> (upd4990a->bitno - 0x28)) & 0x01;
			break;
		case 0x30: case 0x31: case 0x32: case 0x33:
			//unknown
			break;
	}
}

/*-------------------------------------------------
    upd4990a_resetbitstream
-------------------------------------------------*/

static void upd4990a_resetbitstream( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	upd4990a->shiftlo = 0;
	upd4990a->shifthi = 0;
	upd4990a->bitno = 0;
}

/*-------------------------------------------------
    upd4990a_writebit
-------------------------------------------------*/

static void upd4990a_writebit( const device_config *device , UINT8 bit )
{
	upd4990a_state *upd4990a = get_safe_token(device);
	if (upd4990a->bitno <= 31)	//low part
		upd4990a->shiftlo |= bit << upd4990a->bitno;
	else	//high part
		upd4990a->shifthi |= bit << (upd4990a->bitno - 32);
}

/*-------------------------------------------------
    upd4990a_nextbit
-------------------------------------------------*/

static void upd4990a_nextbit( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);
	++upd4990a->bitno;

	if (upd4990a->reading)
		upd4990a_readbit(device);

	if (upd4990a->reading && upd4990a->bitno == 0x34)
	{
		upd4990a->reading = 0;
		upd4990a_resetbitstream(device);
	}

}

/*-------------------------------------------------
    upd4990a_getcommand
-------------------------------------------------*/

static UINT8 upd4990a_getcommand( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);
	//Warning: problems if the 4 bits are in different
	//parts, It's very strange that this case could happen.
	if(upd4990a->bitno <= 31)
		return upd4990a->shiftlo >> (upd4990a->bitno - 4);
	else
		return upd4990a->shifthi >> (upd4990a->bitno - 32 - 4);
}

/*-------------------------------------------------
    upd4990a_update_date
-------------------------------------------------*/

static void upd4990a_update_date( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	upd4990a->seconds = (upd4990a->shiftlo >> 0 ) & 0xff;
	upd4990a->minutes = (upd4990a->shiftlo >> 8 ) & 0xff;
	upd4990a->hours   = (upd4990a->shiftlo >> 16) & 0xff;
	upd4990a->days    = (upd4990a->shiftlo >> 24) & 0xff;
	upd4990a->weekday = (upd4990a->shifthi >> 0 ) & 0x0f;
	upd4990a->month   = (upd4990a->shifthi >> 4 ) & 0x0f;
	upd4990a->year    = (upd4990a->shifthi >> 8 ) & 0xff;
}

/*-------------------------------------------------
    upd4990a_process_command
-------------------------------------------------*/

static void upd4990a_process_command( const device_config *device )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	switch(upd4990a_getcommand(device))
	{
		case 0x1:	//load output register
			upd4990a->bitno = 0;
			if (upd4990a->reading)
				upd4990a_readbit(device);	//prepare first bit
			upd4990a->shiftlo = 0;
			upd4990a->shifthi = 0;
			break;
		case 0x2:
			upd4990a->writing = 0;	//store register to current date
			upd4990a_update_date(device);
			break;
		case 0x3:	//start reading
			upd4990a->reading = 1;
			break;
		case 0x7:	//switch testbit every frame
			upd4990a->maxwaits = 1;
			break;
		case 0x8:	//switch testbit every half-second
			upd4990a->maxwaits = 30;
			break;
	}
	upd4990a_resetbitstream(device);
}

/*-------------------------------------------------
    upd4990a_serial_control
-------------------------------------------------*/

static void upd4990a_serial_control( const device_config *device, UINT8 data )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	//Check for command end
	if(upd4990a->command_line && !(data & END_BIT)) //end of command
	{
		upd4990a_process_command(device);
	}
	upd4990a->command_line = data & END_BIT;

	if(upd4990a->clock_line && !(data & CLOCK_BIT))	//clock lower edge
	{
		upd4990a_writebit(device, data & DATA_BIT);
		upd4990a_nextbit(device);
	}
	upd4990a->clock_line = data & CLOCK_BIT;
}

/*-------------------------------------------------
    upd4990a_control_16_w
-------------------------------------------------*/

WRITE16_DEVICE_HANDLER( upd4990a_control_16_w )
{
	upd4990a_serial_control(device, data & 0x7);
}


/*-------------------------------------------------
    DEVICE_START( upd4990a )
-------------------------------------------------*/

static DEVICE_START( upd4990a )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	mame_system_time curtime, *systime = &curtime;
	mame_get_current_datetime(device->machine, &curtime);

#if 0
	upd4990a->seconds = 0x00;
	upd4990a->minutes = 0x00;
	upd4990a->hours = 0x00;
	upd4990a->days = 0x09;
	upd4990a->month = 9;
	upd4990a->year = 0x73;
	upd4990a->weekday = 1;
#endif
	/* HACK: load time counter from system time */
	upd4990a->seconds = convert_to_bcd(systime->local_time.second);
	upd4990a->minutes = convert_to_bcd(systime->local_time.minute);
	upd4990a->hours = convert_to_bcd(systime->local_time.hour);
	upd4990a->days = convert_to_bcd(systime->local_time.mday);
	upd4990a->month = systime->local_time.month + 1;
	upd4990a->year = ((((systime->local_time.year - 1900) % 100) / 10) << 4) | ((systime->local_time.year - 1900) % 10);
	upd4990a->weekday = systime->local_time.weekday;

	/* register for state saving */
	state_save_register_device_item(device, 0, upd4990a->seconds);
	state_save_register_device_item(device, 0, upd4990a->minutes);
	state_save_register_device_item(device, 0, upd4990a->hours);
	state_save_register_device_item(device, 0, upd4990a->days);
	state_save_register_device_item(device, 0, upd4990a->month);
	state_save_register_device_item(device, 0, upd4990a->year);
	state_save_register_device_item(device, 0, upd4990a->weekday);

	state_save_register_device_item(device, 0, upd4990a->shiftlo);
	state_save_register_device_item(device, 0, upd4990a->shifthi);

	state_save_register_device_item(device, 0, upd4990a->retraces);
	state_save_register_device_item(device, 0, upd4990a->testwaits);
	state_save_register_device_item(device, 0, upd4990a->maxwaits);
	state_save_register_device_item(device, 0, upd4990a->testbit);

	state_save_register_device_item(device, 0, upd4990a->outputbit);
	state_save_register_device_item(device, 0, upd4990a->bitno);
	state_save_register_device_item(device, 0, upd4990a->reading);
	state_save_register_device_item(device, 0, upd4990a->writing);

	state_save_register_device_item(device, 0, upd4990a->clock_line);
	state_save_register_device_item(device, 0, upd4990a->command_line);
}


/*-------------------------------------------------
    DEVICE_RESET( upd4990a )
-------------------------------------------------*/

static DEVICE_RESET( upd4990a )
{
	upd4990a_state *upd4990a = get_safe_token(device);

	upd4990a->shiftlo = 0;
	upd4990a->shifthi = 0;

	upd4990a->retraces = 0;
	upd4990a->testwaits = 0;
	upd4990a->maxwaits = 1;
	upd4990a->testbit = 0;

	upd4990a->outputbit = 0;
	upd4990a->bitno = 0;
	upd4990a->reading = 0;
	upd4990a->writing = 0;

	upd4990a->clock_line = 0;
	upd4990a->command_line = 0;
}

/*-------------------------------------------------
    device definition
-------------------------------------------------*/

static const char DEVTEMPLATE_SOURCE[] = __FILE__;

#define DEVTEMPLATE_ID(p,s)		p##upd4990a##s
#define DEVTEMPLATE_FEATURES	DT_HAS_START | DT_HAS_RESET
#define DEVTEMPLATE_NAME		"NEC uPD4990A"
#define DEVTEMPLATE_FAMILY		"NEC uPD4990A Calendar & Clock"
#define DEVTEMPLATE_CLASS		DEVICE_CLASS_PERIPHERAL
#include "devtempl.h"
