/*
 * PlayStation Root Counter emulator
 *
 * Copyright 2003-2011 smf
 *
 */

#include "rcnt.h"
#include "includes/psx.h"

#define VERBOSE_LEVEL ( 0 )

INLINE void ATTR_PRINTF(3,4) verboselog( running_machine& machine, int n_level, const char *s_fmt, ... )
{
	if( VERBOSE_LEVEL >= n_level )
	{
		va_list v;
		char buf[ 32768 ];
		va_start( v, s_fmt );
		vsprintf( buf, s_fmt, v );
		va_end( v );
		logerror( "%s: %s", machine.describe_context(), buf );
	}
}

const device_type PSX_RCNT = &device_creator<psxrcnt_device>;

psxrcnt_device::psxrcnt_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, PSX_RCNT, "PSX RCNT", tag, owner, clock)
{
}

void psxrcnt_device::device_reset()
{
}

void psxrcnt_device::device_post_load()
{
	int n;
	for( n = 0; n < 3; n++ )
	{
		root_timer_adjust( n );
	}
}

void psxrcnt_device::device_start()
{
	int n;

	for( n = 0; n < 3; n++ )
	{
		root_counter[ n ].timer = machine().scheduler().timer_alloc( timer_expired_delegate( FUNC( psxrcnt_device::root_finished ), this ) );
		state_save_register_item( machine(), "psxroot", NULL, n, root_counter[ n ].n_count );
		state_save_register_item( machine(), "psxroot", NULL, n, root_counter[ n ].n_mode );
		state_save_register_item( machine(), "psxroot", NULL, n, root_counter[ n ].n_target );
		state_save_register_item( machine(), "psxroot", NULL, n, root_counter[ n ].n_start );
	}
}

WRITE32_MEMBER( psxrcnt_device::write )
{
	int n_counter = offset / 4;
	psx_root *root = &root_counter[ n_counter ];

	verboselog( machine(), 1, "psx_counter_w ( %08x, %08x, %08x )\n", offset, data, mem_mask );

	switch( offset % 4 )
	{
	case 0:
		root->n_count = data;
		root->n_start = gettotalcycles();
		break;
	case 1:
		root->n_count = root_current( n_counter );
		root->n_start = gettotalcycles();

		if( ( data & PSX_RC_RESET ) != 0 )
		{
			data &= ~( PSX_RC_RESET | PSX_RC_STOP );
			root->n_count = 0;
		}

		root->n_mode = data;

#if 0
		if( ( data & 0xfca6 ) != 0 ||
			( ( data & 0x0100 ) != 0 && n_counter != 0 && n_counter != 1 ) ||
			( ( data & 0x0200 ) != 0 && n_counter != 2 ) )
		{
			mame_printf_debug( "mode %d 0x%04x\n", n_counter, data & 0xfca6 );
		}
#endif
		break;
	case 2:
		root->n_target = data;
		break;
	default:
		verboselog( machine(), 0, "psx_counter_w( %08x, %08x, %08x ) unknown register\n", offset, mem_mask, data );
		return;
	}

	root_timer_adjust( n_counter );
}

READ32_MEMBER( psxrcnt_device::read )
{
	int n_counter = offset / 4;
	psx_root *root = &root_counter[ n_counter ];
	UINT32 data;

	switch( offset % 4 )
	{
	case 0:
		data = root_current( n_counter );
		break;
	case 1:
		data = root->n_mode;
		break;
	case 2:
		data = root->n_target;
		break;
	default:
		verboselog( machine(), 0, "psx_counter_r( %08x, %08x ) unknown register\n", offset, mem_mask );
		return 0;
	}
	verboselog( machine(), 1, "psx_counter_r ( %08x, %08x ) %08x\n", offset, mem_mask, data );
	return data;
}

UINT64 psxrcnt_device::gettotalcycles( void )
{
	/* TODO: should return the start of the current tick. */
	return machine().firstcpu->total_cycles() * 2;
}

int psxrcnt_device::root_divider( int n_counter )
{
	psx_root *root = &root_counter[ n_counter ];

	if( n_counter == 0 && ( root->n_mode & PSX_RC_CLC ) != 0 )
	{
		/* TODO: pixel clock, probably based on resolution */
		return 5;
	}
	else if( n_counter == 1 && ( root->n_mode & PSX_RC_CLC ) != 0 )
	{
		return 2150;
	}
	else if( n_counter == 2 && ( root->n_mode & PSX_RC_DIV ) != 0 )
	{
		return 8;
	}
	return 1;
}

UINT16 psxrcnt_device::root_current( int n_counter )
{
	psx_root *root = &root_counter[ n_counter ];

	if( ( root->n_mode & PSX_RC_STOP ) != 0 )
	{
		return root->n_count;
	}
	else
	{
		UINT64 n_current;
		n_current = gettotalcycles() - root->n_start;
		n_current /= root_divider( n_counter );
		n_current += root->n_count;
		if( n_current > 0xffff )
		{
			/* TODO: use timer for wrap on 0x10000. */
			root->n_count = n_current;
			root->n_start = gettotalcycles();
		}
		return n_current;
	}
}

int psxrcnt_device::root_target( int n_counter )
{
	psx_root *root = &root_counter[ n_counter ];

	if( ( root->n_mode & PSX_RC_COUNTTARGET ) != 0 ||
		( root->n_mode & PSX_RC_IRQTARGET ) != 0 )
	{
		return root->n_target;
	}
	return 0x10000;
}

void psxrcnt_device::root_timer_adjust( int n_counter )
{
	psx_root *root = &root_counter[ n_counter ];

	if( ( root->n_mode & PSX_RC_STOP ) != 0 )
	{
		root->timer->adjust( attotime::never, n_counter);
	}
	else
	{
		int n_duration;

		n_duration = root_target( n_counter ) - root_current( n_counter );
		if( n_duration < 1 )
		{
			n_duration += 0x10000;
		}

		n_duration *= root_divider( n_counter );

		root->timer->adjust( attotime::from_hz(33868800) * n_duration, n_counter);
	}
}

void psxrcnt_device::root_finished( void *ptr, int param )
{
	int n_counter = param;
	psx_root *root = &root_counter[ n_counter ];

	verboselog( machine(), 2, "root_finished( %d ) %04x\n", n_counter, root_current( n_counter ) );
	//if( ( root->n_mode & PSX_RC_COUNTTARGET ) != 0 )
	{
		/* TODO: wrap should be handled differently as PSX_RC_COUNTTARGET & PSX_RC_IRQTARGET don't have to be the same. */
		root->n_count = 0;
		root->n_start = gettotalcycles();
	}
	if( ( root->n_mode & PSX_RC_REPEAT ) != 0 )
	{
		root_timer_adjust( n_counter );
	}
	if( ( root->n_mode & PSX_RC_IRQOVERFLOW ) != 0 ||
		( root->n_mode & PSX_RC_IRQTARGET ) != 0 )
	{
		psx_irq_set( machine(), PSX_IRQ_ROOTCOUNTER0 << n_counter );
	}
}
