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

  machine/psx.c

  Thanks to Olivier Galibert for IDCT information

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

#include <stdarg.h>
#include "driver.h"
#include "cpu/mips/psx.h"
#include "includes/psx.h"

#define VERBOSE_LEVEL ( 0 )

INLINE void verboselog( 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 );
		if( cpu_getactivecpu() != -1 )
		{
			logerror( "%08x: %s", activecpu_get_pc(), buf );
		}
		else
		{
			logerror( "(timer) : %s", buf );
		}
	}
}

UINT32 *g_p_n_psxram;
size_t g_n_psxramsize;

INLINE void psxwriteword( UINT32 n_address, UINT16 n_data )
{
	*( (UINT16 *)( (UINT8 *)g_p_n_psxram + WORD_XOR_LE( n_address ) ) ) = n_data;
}

INLINE UINT16 psxreadword( UINT32 n_address )
{
	return *( (UINT16 *)( (UINT8 *)g_p_n_psxram + WORD_XOR_LE( n_address ) ) );
}

static UINT32 psx_com_delay = 0;

WRITE32_HANDLER( psx_com_delay_w )
{
	COMBINE_DATA( &psx_com_delay );
	verboselog( 1, "psx_com_delay_w( %08x %08x )\n", data, mem_mask );
}

READ32_HANDLER( psx_com_delay_r )
{
	verboselog( 1, "psx_com_delay_r( %08x )\n", mem_mask );
	return psx_com_delay;
}

/* IRQ */

static UINT32 m_n_irqdata;
static UINT32 m_n_irqmask;

static void psx_irq_update( void )
{
	if( ( m_n_irqdata & m_n_irqmask ) != 0 )
	{
		verboselog( 2, "psx irq assert\n" );
		cpunum_set_input_line( 0, MIPS_IRQ0, ASSERT_LINE );
	}
	else
	{
		verboselog( 2, "psx irq clear\n" );
		cpunum_set_input_line( 0, MIPS_IRQ0, CLEAR_LINE );
	}
}

WRITE32_HANDLER( psx_irq_w )
{
	switch( offset )
	{
	case 0x00:
		verboselog( 2, "psx irq data ( %08x, %08x ) %08x -> %08x\n", data, mem_mask, m_n_irqdata, ( m_n_irqdata & mem_mask ) | ( m_n_irqdata & m_n_irqmask & data ) );
		m_n_irqdata = ( m_n_irqdata & mem_mask ) | ( m_n_irqdata & m_n_irqmask & data );
		psx_irq_update();
		break;
	case 0x01:
		verboselog( 2, "psx irq mask ( %08x, %08x ) %08x -> %08x\n", data, mem_mask, m_n_irqmask, ( m_n_irqmask & mem_mask ) | data );
		m_n_irqmask = ( m_n_irqmask & mem_mask ) | data;
		if( ( m_n_irqmask & ~( 0x1 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100 | 0x200 | 0x400 ) ) != 0 )
		{
			verboselog( 0, "psx_irq_w( %08x, %08x, %08x ) unknown irq\n", offset, data, mem_mask );
		}
		psx_irq_update();
		break;
	default:
		verboselog( 0, "psx_irq_w( %08x, %08x, %08x ) unknown register\n", offset, data, mem_mask );
		break;
	}
}

READ32_HANDLER( psx_irq_r )
{
	switch( offset )
	{
	case 0x00:
		verboselog( 1, "psx_irq_r irq data %08x\n", m_n_irqdata );
		return m_n_irqdata;
	case 0x01:
		verboselog( 1, "psx_irq_r irq mask %08x\n", m_n_irqmask );
		return m_n_irqmask;
	default:
		verboselog( 0, "psx_irq_r unknown register %d\n", offset );
		break;
	}
	return 0;
}

void psx_irq_set( UINT32 data )
{
	verboselog( 2, "psx_irq_set %08x\n", data );
	m_n_irqdata |= data;
	psx_irq_update();
}

/* DMA */

static UINT32 m_p_n_dmabase[ 7 ];
static UINT32 m_p_n_dmablockcontrol[ 7 ];
static UINT32 m_p_n_dmachannelcontrol[ 7 ];
static emu_timer *m_p_timer_dma[ 7 ];
static psx_dma_read_handler m_p_fn_dma_read[ 7 ];
static psx_dma_write_handler m_p_fn_dma_write[ 7 ];
static UINT32 m_p_n_dma_ticks[ 7 ];
static UINT32 m_p_b_dma_running[ 7 ];
static UINT32 m_n_dpcp;
static UINT32 m_n_dicr;

static void dma_start_timer( int n_channel, UINT32 n_ticks )
{
	timer_adjust( m_p_timer_dma[ n_channel ], attotime_mul(ATTOTIME_IN_HZ(33868800), n_ticks), n_channel, attotime_zero);
	m_p_n_dma_ticks[ n_channel ] = n_ticks;
	m_p_b_dma_running[ n_channel ] = 1;
}

static void dma_stop_timer( int n_channel )
{
	timer_adjust( m_p_timer_dma[ n_channel ], attotime_never, 0, attotime_never);
	m_p_b_dma_running[ n_channel ] = 0;
}

static void dma_timer_adjust( int n_channel )
{
	if( m_p_b_dma_running[ n_channel ] )
	{
		dma_start_timer( n_channel, m_p_n_dma_ticks[ n_channel ] );
	}
	else
	{
		dma_stop_timer( n_channel );
	}
}

static void dma_interrupt_update( void )
{
	int n_int;
	int n_mask;

	n_int = ( m_n_dicr >> 24 ) & 0x7f;
	n_mask = ( m_n_dicr >> 16 ) & 0xff;

	if( ( n_mask & 0x80 ) != 0 && ( n_int & n_mask ) != 0 )
	{
		verboselog( 2, "dma_interrupt_update( %02x, %02x ) interrupt triggered\n", n_int, n_mask );
		m_n_dicr |= 0x80000000;
		psx_irq_set( 0x0008 );
	}
	else if( ( m_n_dicr & 0x80000000 ) != 0 )
	{
		verboselog( 2, "dma_interrupt_update( %02x, %02x ) interrupt cleared\n", n_int, n_mask );
		m_n_dicr &= ~0x80000000;
	}
	else if( n_int != 0 )
	{
		verboselog( 2, "dma_interrupt_update( %02x, %02x ) interrupt not enabled\n", n_int, n_mask );
	}

	m_n_dicr &= 0x00ffffff | ( m_n_dicr << 8 );
}

static void dma_finished(int n_channel)
{
	if( m_p_n_dmachannelcontrol[ n_channel ] == 0x01000401 && n_channel == 2 )
	{
		UINT32 n_size;
		UINT32 n_total;
		UINT32 n_address = ( m_p_n_dmabase[ n_channel ] & 0xffffff );
		UINT32 n_adrmask = g_n_psxramsize - 1;
		UINT32 n_nextaddress;

		if( n_address != 0xffffff )
		{
			n_total = 0;
			for( ;; )
			{
				if( n_address == 0xffffff )
				{
					m_p_n_dmabase[ n_channel ] = n_address;
					dma_start_timer( n_channel, 19000 );
					return;
				}
				if( n_total > 65535 )
				{
					m_p_n_dmabase[ n_channel ] = n_address;
					//FIXME:
					// 16000 below is based on try and error.
					// Mametesters.org: sfex20103red
					//dma_start_timer( n_channel, 16 );
					dma_start_timer( n_channel, 16000 );
					return;
				}
				n_address &= n_adrmask;
				n_nextaddress = g_p_n_psxram[ n_address / 4 ];
				n_size = n_nextaddress >> 24;
				m_p_fn_dma_write[ n_channel ]( n_address + 4, n_size );
				//FIXME:
				// The following conditions will cause an endless loop.
				// If stopping the transfer is correct I cannot judge
				// The patch is meant as a hint for somebody who knows
				// the hardware.
				// Mametesters.org: psyforce0105u5red, raystorm0111u1red
				if ((n_nextaddress & 0xffffff) != 0xffffff)
					if (n_address == g_p_n_psxram[ (n_nextaddress & 0xffffff) / 4])
						break;
				if (n_address == (n_nextaddress & 0xffffff) )
					break;
				n_address = ( n_nextaddress & 0xffffff );

				n_total += ( n_size + 1 );
			}
		}
	}

	m_p_n_dmachannelcontrol[ n_channel ] &= ~( ( 1L << 0x18 ) | ( 1L << 0x1c ) );

	m_n_dicr |= 1 << ( 24 + n_channel );
	dma_interrupt_update();
	dma_stop_timer( n_channel );
}

static TIMER_CALLBACK( dma_finished_callback )
{
	dma_finished(param);
}

void psx_dma_install_read_handler( int n_channel, psx_dma_read_handler p_fn_dma_read )
{
	m_p_fn_dma_read[ n_channel ] = p_fn_dma_read;
}

void psx_dma_install_write_handler( int n_channel, psx_dma_read_handler p_fn_dma_write )
{
	m_p_fn_dma_write[ n_channel ] = p_fn_dma_write;
}

WRITE32_HANDLER( psx_dma_w )
{
	static int n_channel;
	n_channel = offset / 4;
	if( n_channel < 7 )
	{
		switch( offset % 4 )
		{
		case 0:
			verboselog( 2, "dmabase( %d ) = %08x\n", n_channel, data );
			m_p_n_dmabase[ n_channel ] = data;
			break;
		case 1:
			verboselog( 2, "dmablockcontrol( %d ) = %08x\n", n_channel, data );
			m_p_n_dmablockcontrol[ n_channel ] = data;
			break;
		case 2:
			verboselog( 2, "dmachannelcontrol( %d ) = %08x\n", n_channel, data );
			m_p_n_dmachannelcontrol[ n_channel ] = data;
			if( ( m_p_n_dmachannelcontrol[ n_channel ] & ( 1L << 0x18 ) ) != 0 && ( m_n_dpcp & ( 1 << ( 3 + ( n_channel * 4 ) ) ) ) != 0 )
			{
				INT32 n_size;
				UINT32 n_address;
				UINT32 n_nextaddress;
				UINT32 n_adrmask;

				n_adrmask = g_n_psxramsize - 1;

				n_address = ( m_p_n_dmabase[ n_channel ] & n_adrmask );
				n_size = m_p_n_dmablockcontrol[ n_channel ];
				if( ( m_p_n_dmachannelcontrol[ n_channel ] & 0x200 ) != 0 )
				{
					UINT32 n_ba;
					n_ba = m_p_n_dmablockcontrol[ n_channel ] >> 16;
					if( n_ba == 0 )
					{
						n_ba = 0x10000;
					}
					n_size = ( n_size & 0xffff ) * n_ba;
				}

				if( m_p_n_dmachannelcontrol[ n_channel ] == 0x01000000 &&
					m_p_fn_dma_read[ n_channel ] != NULL )
				{
					verboselog( 1, "dma %d read block %08x %08x\n", n_channel, n_address, n_size );
					m_p_fn_dma_read[ n_channel ]( n_address, n_size );
					dma_finished( n_channel );
				}
				else if( m_p_n_dmachannelcontrol[ n_channel ] == 0x01000200 &&
					m_p_fn_dma_read[ n_channel ] != NULL )
				{
					verboselog( 1, "dma %d read block %08x %08x\n", n_channel, n_address, n_size );
					m_p_fn_dma_read[ n_channel ]( n_address, n_size );
					if( n_channel == 1 )
					{
						dma_start_timer( n_channel, 26000 );
					}
					else
					{
						dma_finished( n_channel );
					}
				}
				else if( m_p_n_dmachannelcontrol[ n_channel ] == 0x01000201 &&
					m_p_fn_dma_write[ n_channel ] != NULL )
				{
					verboselog( 1, "dma %d write block %08x %08x\n", n_channel, n_address, n_size );
					m_p_fn_dma_write[ n_channel ]( n_address, n_size );
					dma_finished( n_channel );
				}
				else if( m_p_n_dmachannelcontrol[ n_channel ] == 0x11050100 &&
					m_p_fn_dma_write[ n_channel ] != NULL )
				{
					/* todo: check this is a write not a read... */
					verboselog( 1, "dma %d write block %08x %08x\n", n_channel, n_address, n_size );
					m_p_fn_dma_write[ n_channel ]( n_address, n_size );
					dma_finished( n_channel );
				}
				else if( m_p_n_dmachannelcontrol[ n_channel ] == 0x11150100 &&
					m_p_fn_dma_write[ n_channel ] != NULL )
				{
					/* todo: check this is a write not a read... */
					verboselog( 1, "dma %d write block %08x %08x\n", n_channel, n_address, n_size );
					m_p_fn_dma_write[ n_channel ]( n_address, n_size );
					dma_finished( n_channel );
				}
				else if( m_p_n_dmachannelcontrol[ n_channel ] == 0x01000401 &&
					n_channel == 2 &&
					m_p_fn_dma_write[ n_channel ] != NULL )
				{
					verboselog( 1, "dma %d write linked list %08x\n",
						n_channel, m_p_n_dmabase[ n_channel ] );

					dma_finished( n_channel );
				}
				else if( m_p_n_dmachannelcontrol[ n_channel ] == 0x11000002 &&
					n_channel == 6 )
				{
					verboselog( 1, "dma 6 reverse clear %08x %08x\n",
						m_p_n_dmabase[ n_channel ], m_p_n_dmablockcontrol[ n_channel ] );
					if( n_size > 0 )
					{
						n_size--;
						while( n_size > 0 )
						{
							n_nextaddress = ( n_address - 4 ) & 0xffffff;
							g_p_n_psxram[ n_address / 4 ] = n_nextaddress;
							n_address = n_nextaddress;
							n_size--;
						}
						g_p_n_psxram[ n_address / 4 ] = 0xffffff;
					}
					dma_start_timer( n_channel, 2150 );
				}
				else
				{
					verboselog( 0, "dma %d unknown mode %08x\n", n_channel, m_p_n_dmachannelcontrol[ n_channel ] );
				}
			}
			else if( m_p_n_dmachannelcontrol[ n_channel ] != 0 )
			{
				verboselog( 1, "psx_dma_w( %04x, %08x, %08x ) channel not enabled\n", offset, m_p_n_dmachannelcontrol[ n_channel ], mem_mask );
			}
			break;
		default:
			verboselog( 1, "psx_dma_w( %04x, %08x, %08x ) Unknown dma channel register\n", offset, data, mem_mask );
			break;
		}
	}
	else
	{
		switch( offset % 4 )
		{
		case 0x0:
			verboselog( 1, "psx_dma_w( %04x, %08x, %08x ) dpcp\n", offset, data, mem_mask );
			m_n_dpcp = ( m_n_dpcp & mem_mask ) | data;
			break;
		case 0x1:
			m_n_dicr = ( m_n_dicr & mem_mask ) |
				( ~mem_mask & 0x80000000 & m_n_dicr ) |
				( ~data & ~mem_mask & 0x7f000000 & m_n_dicr ) |
				( data & ~mem_mask & 0x00ffffff );
/* todo: find out whether to do this instead of dma_interrupt_update()

            if( ( m_n_dicr & 0x7f000000 ) != 0 )
            {
                m_n_dicr &= ~0x80000000;
            }
*/
			verboselog( 1, "psx_dma_w( %04x, %08x, %08x ) dicr -> %08x\n", offset, data, mem_mask, m_n_dicr );
			dma_interrupt_update();
			break;
		default:
			verboselog( 0, "psx_dma_w( %04x, %08x, %08x ) Unknown dma control register\n", offset, data, mem_mask );
			break;
		}
	}
}

READ32_HANDLER( psx_dma_r )
{
	static int n_channel;
	n_channel = offset / 4;
	if( n_channel < 7 )
	{
		switch( offset % 4 )
		{
		case 0:
			verboselog( 1, "psx_dma_r dmabase[ %d ] ( %08x )\n", n_channel, m_p_n_dmabase[ n_channel ] );
			return m_p_n_dmabase[ n_channel ];
		case 1:
			verboselog( 1, "psx_dma_r dmablockcontrol[ %d ] ( %08x )\n", n_channel, m_p_n_dmablockcontrol[ n_channel ] );
			return m_p_n_dmablockcontrol[ n_channel ];
		case 2:
			verboselog( 1, "psx_dma_r dmachannelcontrol[ %d ] ( %08x )\n", n_channel, m_p_n_dmachannelcontrol[ n_channel ] );
			return m_p_n_dmachannelcontrol[ n_channel ];
		default:
			verboselog( 0, "psx_dma_r( %08x, %08x ) Unknown dma channel register\n", offset, mem_mask );
			break;
		}
	}
	else
	{
		switch( offset % 4 )
		{
		case 0x0:
			verboselog( 1, "psx_dma_r dpcp ( %08x )\n", m_n_dpcp );
			return m_n_dpcp;
		case 0x1:
			verboselog( 1, "psx_dma_r dicr ( %08x )\n", m_n_dicr );
			return m_n_dicr;
		default:
			verboselog( 0, "psx_dma_r( %08x, %08x ) Unknown dma control register\n", offset, mem_mask );
			break;
		}
	}
	return 0;
}

/* Root Counters */

static emu_timer *m_p_timer_root[ 3 ];
static UINT16 m_p_n_root_count[ 3 ];
static UINT16 m_p_n_root_mode[ 3 ];
static UINT16 m_p_n_root_target[ 3 ];
static UINT32 m_p_n_root_start[ 3 ];

#define RC_STOP ( 0x01 )
#define RC_RESET ( 0x04 ) /* guess */
#define RC_COUNTTARGET ( 0x08 )
#define RC_IRQTARGET ( 0x10 )
#define RC_IRQOVERFLOW ( 0x20 )
#define RC_REPEAT ( 0x40 )
#define RC_CLC ( 0x100 )
#define RC_DIV ( 0x200 )

static UINT32 psxcpu_gettotalcycles( void )
{
	/* TODO: should return the start of the current tick. */
	return cpunum_gettotalcycles(0) * 2;
}

static int root_divider( int n_counter )
{
	if( n_counter == 0 && ( m_p_n_root_mode[ n_counter ] & RC_CLC ) != 0 )
	{
		/* TODO: pixel clock, probably based on resolution */
		return 5;
	}
	else if( n_counter == 1 && ( m_p_n_root_mode[ n_counter ] & RC_CLC ) != 0 )
	{
		return 2150;
	}
	else if( n_counter == 2 && ( m_p_n_root_mode[ n_counter ] & RC_DIV ) != 0 )
	{
		return 8;
	}
	return 1;
}

static UINT16 root_current( int n_counter )
{
	if( ( m_p_n_root_mode[ n_counter ] & RC_STOP ) != 0 )
	{
		return m_p_n_root_count[ n_counter ];
	}
	else
	{
		UINT32 n_current;
		n_current = psxcpu_gettotalcycles() - m_p_n_root_start[ n_counter ];
		n_current /= root_divider( n_counter );
		n_current += m_p_n_root_count[ n_counter ];
		if( n_current > 0xffff )
		{
			/* TODO: use timer for wrap on 0x10000. */
			m_p_n_root_count[ n_counter ] = n_current;
			m_p_n_root_start[ n_counter ] = psxcpu_gettotalcycles();
		}
		return n_current;
	}
}

static int root_target( int n_counter )
{
	if( ( m_p_n_root_mode[ n_counter ] & RC_COUNTTARGET ) != 0 ||
		( m_p_n_root_mode[ n_counter ] & RC_IRQTARGET ) != 0 )
	{
		return m_p_n_root_target[ n_counter ];
	}
	return 0x10000;
}

static void root_timer_adjust( int n_counter )
{
	if( ( m_p_n_root_mode[ n_counter ] & RC_STOP ) != 0 )
	{
		timer_adjust( m_p_timer_root[ n_counter ], attotime_never, n_counter, attotime_never);
	}
	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 );

		timer_adjust( m_p_timer_root[ n_counter ], attotime_mul(ATTOTIME_IN_HZ(33868800), n_duration), n_counter, attotime_zero);
	}
}

static TIMER_CALLBACK( root_finished )
{
	int n_counter = param;

	verboselog( 2, "root_finished( %d ) %04x\n", n_counter, root_current( n_counter ) );
//  if( ( m_p_n_root_mode[ n_counter ] & RC_COUNTTARGET ) != 0 )
	{
		/* TODO: wrap should be handled differently as RC_COUNTTARGET & RC_IRQTARGET don't have to be the same. */
		m_p_n_root_count[ n_counter ] = 0;
		m_p_n_root_start[ n_counter ] = psxcpu_gettotalcycles();
	}
	if( ( m_p_n_root_mode[ n_counter ] & RC_REPEAT ) != 0 )
	{
		root_timer_adjust( n_counter );
	}
	if( ( m_p_n_root_mode[ n_counter ] & RC_IRQOVERFLOW ) != 0 ||
		( m_p_n_root_mode[ n_counter ] & RC_IRQTARGET ) != 0 )
	{
		psx_irq_set( 0x10 << n_counter );
	}
}

WRITE32_HANDLER( psx_counter_w )
{
	int n_counter;

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

	n_counter = offset / 4;

	switch( offset % 4 )
	{
	case 0:
		m_p_n_root_count[ n_counter ] = data;
		m_p_n_root_start[ n_counter ] = psxcpu_gettotalcycles();
		break;
	case 1:
		m_p_n_root_count[ n_counter ] = root_current( n_counter );
		m_p_n_root_start[ n_counter ] = psxcpu_gettotalcycles();
		m_p_n_root_mode[ n_counter ] = data;

		if( ( m_p_n_root_mode[ n_counter ] & RC_RESET ) != 0 )
		{
			m_p_n_root_count[ n_counter ] = 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 );
//      }
		break;
	case 2:
		m_p_n_root_target[ n_counter ] = data;
		break;
	default:
		verboselog( 0, "psx_counter_w( %08x, %08x, %08x ) unknown register\n", offset, mem_mask, data );
		return;
	}

	root_timer_adjust( n_counter );
}

READ32_HANDLER( psx_counter_r )
{
	int n_counter;
	UINT32 data;

	n_counter = offset / 4;

	switch( offset % 4 )
	{
	case 0:
		data = root_current( n_counter );
		break;
	case 1:
		data = m_p_n_root_mode[ n_counter ];
		break;
	case 2:
		data = m_p_n_root_target[ n_counter ];
		break;
	default:
		verboselog( 0, "psx_counter_r( %08x, %08x ) unknown register\n", offset, mem_mask );
		return 0;
	}
	verboselog( 1, "psx_counter_r ( %08x, %08x ) %08x\n", offset, mem_mask, data );
	return data;
}

/* SIO */

#define SIO_BUF_SIZE ( 8 )

static UINT32 m_p_n_sio_status[ 2 ];
static UINT32 m_p_n_sio_mode[ 2 ];
static UINT32 m_p_n_sio_control[ 2 ];
static UINT32 m_p_n_sio_baud[ 2 ];
static UINT32 m_p_n_sio_tx[ 2 ];
static UINT32 m_p_n_sio_rx[ 2 ];
static UINT32 m_p_n_sio_tx_prev[ 2 ];
static UINT32 m_p_n_sio_rx_prev[ 2 ];
static UINT32 m_p_n_sio_tx_data[ 2 ];
static UINT32 m_p_n_sio_rx_data[ 2 ];
static UINT32 m_p_n_sio_tx_shift[ 2 ];
static UINT32 m_p_n_sio_rx_shift[ 2 ];
static UINT32 m_p_n_sio_tx_bits[ 2 ];
static UINT32 m_p_n_sio_rx_bits[ 2 ];

static emu_timer *m_p_timer_sio[ 2 ];
static psx_sio_handler m_p_f_sio_handler[ 2 ];

#define SIO_STATUS_TX_RDY ( 1 << 0 )
#define SIO_STATUS_RX_RDY ( 1 << 1 )
#define SIO_STATUS_TX_EMPTY ( 1 << 2 )
#define SIO_STATUS_OVERRUN ( 1 << 4 )
#define SIO_STATUS_DSR ( 1 << 7 )
#define SIO_STATUS_IRQ ( 1 << 9 )

#define SIO_CONTROL_TX_ENA ( 1 << 0 )
#define SIO_CONTROL_IACK ( 1 << 4 )
#define SIO_CONTROL_RESET ( 1 << 6 )
#define SIO_CONTROL_TX_IENA ( 1 << 10 )
#define SIO_CONTROL_RX_IENA ( 1 << 11 )
#define SIO_CONTROL_DSR_IENA ( 1 << 12 )
#define SIO_CONTROL_DTR ( 1 << 13 )

static void sio_interrupt( int n_port )
{
	verboselog( 1, "sio_interrupt( %d )\n", n_port );
	m_p_n_sio_status[ n_port ] |= SIO_STATUS_IRQ;
	if( n_port == 0 )
	{
		psx_irq_set( 0x80 );
	}
	else
	{
		psx_irq_set( 0x100 );
	}
}

static void sio_timer_adjust( int n_port )
{
	attotime n_time;
	if( ( m_p_n_sio_status[ n_port ] & SIO_STATUS_TX_EMPTY ) == 0 || m_p_n_sio_tx_bits[ n_port ] != 0 )
	{
		int n_prescaler;

		switch( m_p_n_sio_mode[ n_port ] & 3 )
		{
		case 1:
			n_prescaler = 1;
			break;
		case 2:
			n_prescaler = 16;
			break;
		case 3:
			n_prescaler = 64;
			break;
		default:
			n_prescaler = 0;
			break;
		}

		if( m_p_n_sio_baud[ n_port ] != 0 && n_prescaler != 0 )
		{
			n_time = attotime_mul(ATTOTIME_IN_HZ(33868800), n_prescaler * m_p_n_sio_baud[n_port]);
			verboselog( 2, "sio_timer_adjust( %d ) = %f ( %d x %d )\n", n_port, n_time, n_prescaler, m_p_n_sio_baud[ n_port ] );
		}
		else
		{
			n_time = attotime_never;
			verboselog( 0, "sio_timer_adjust( %d ) invalid baud rate ( %d x %d )\n", n_port, n_prescaler, m_p_n_sio_baud[ n_port ] );
		}
	}
	else
	{
		n_time = attotime_never;
		verboselog( 2, "sio_timer_adjust( %d ) finished\n", n_port );
	}
	timer_adjust( m_p_timer_sio[ n_port ], n_time, n_port, attotime_zero);
}

static TIMER_CALLBACK( sio_clock )
{
	int n_port = param;
	verboselog( 2, "sio tick\n" );

	if( m_p_n_sio_tx_bits[ n_port ] == 0 &&
		( m_p_n_sio_control[ n_port ] & SIO_CONTROL_TX_ENA ) != 0 &&
		( m_p_n_sio_status[ n_port ] & SIO_STATUS_TX_EMPTY ) == 0 )
	{
		m_p_n_sio_tx_bits[ n_port ] = 8;
		m_p_n_sio_tx_shift[ n_port ] = m_p_n_sio_tx_data[ n_port ];
		if( n_port == 0 )
		{
			m_p_n_sio_rx_bits[ n_port ] = 8;
			m_p_n_sio_rx_shift[ n_port ] = 0;
		}
		m_p_n_sio_status[ n_port ] |= SIO_STATUS_TX_EMPTY;
		m_p_n_sio_status[ n_port ] |= SIO_STATUS_TX_RDY;
	}

	if( m_p_n_sio_tx_bits[ n_port ] != 0 )
	{
		m_p_n_sio_tx[ n_port ] = ( m_p_n_sio_tx[ n_port ] & ~PSX_SIO_OUT_DATA ) | ( ( m_p_n_sio_tx_shift[ n_port ] & 1 ) * PSX_SIO_OUT_DATA );
		m_p_n_sio_tx_shift[ n_port ] >>= 1;
		m_p_n_sio_tx_bits[ n_port ]--;

		if( m_p_f_sio_handler[ n_port ] != NULL )
		{
			if( n_port == 0 )
			{
				m_p_n_sio_tx[ n_port ] &= ~PSX_SIO_OUT_CLOCK;
				m_p_f_sio_handler[ n_port ]( m_p_n_sio_tx[ n_port ] );
				m_p_n_sio_tx[ n_port ] |= PSX_SIO_OUT_CLOCK;
			}
			m_p_f_sio_handler[ n_port ]( m_p_n_sio_tx[ n_port ] );
		}

		if( m_p_n_sio_tx_bits[ n_port ] == 0 &&
			( m_p_n_sio_control[ n_port ] & SIO_CONTROL_TX_IENA ) != 0 )
		{
			sio_interrupt( n_port );
		}
	}

	if( m_p_n_sio_rx_bits[ n_port ] != 0 )
	{
		m_p_n_sio_rx_shift[ n_port ] = ( m_p_n_sio_rx_shift[ n_port ] >> 1 ) | ( ( ( m_p_n_sio_rx[ n_port ] & PSX_SIO_IN_DATA ) / PSX_SIO_IN_DATA ) << 7 );
		m_p_n_sio_rx_bits[ n_port ]--;

		if( m_p_n_sio_rx_bits[ n_port ] == 0 )
		{
			if( ( m_p_n_sio_status[ n_port ] & SIO_STATUS_RX_RDY ) != 0 )
			{
				m_p_n_sio_status[ n_port ] |= SIO_STATUS_OVERRUN;
			}
			else
			{
				m_p_n_sio_rx_data[ n_port ] = m_p_n_sio_rx_shift[ n_port ];
				m_p_n_sio_status[ n_port ] |= SIO_STATUS_RX_RDY;
			}
			if( ( m_p_n_sio_control[ n_port ] & SIO_CONTROL_RX_IENA ) != 0 )
			{
				sio_interrupt( n_port );
			}
		}
	}

	sio_timer_adjust( n_port );
}

void psx_sio_input( int n_port, int n_mask, int n_data )
{
	verboselog( 1, "psx_sio_input( %d, %02x, %02x )\n", n_port, n_mask, n_data );
	m_p_n_sio_rx[ n_port ] = ( m_p_n_sio_rx[ n_port ] & ~n_mask ) | ( n_data & n_mask );

	if( ( m_p_n_sio_rx[ n_port ] & PSX_SIO_IN_DSR ) != 0 )
	{
		m_p_n_sio_status[ n_port ] |= SIO_STATUS_DSR;
		if( ( m_p_n_sio_rx_prev[ n_port ] & PSX_SIO_IN_DSR ) == 0 &&
			( m_p_n_sio_control[ n_port ] & SIO_CONTROL_DSR_IENA ) != 0 )
		{
			sio_interrupt( n_port );
		}
	}
	else
	{
		m_p_n_sio_status[ n_port ] &= ~SIO_STATUS_DSR;
	}
	m_p_n_sio_rx_prev[ n_port ] = m_p_n_sio_rx[ n_port ];
}

WRITE32_HANDLER( psx_sio_w )
{
	int n_port;

	n_port = offset / 4;

	switch( offset % 4 )
	{
	case 0:
		verboselog( 1, "psx_sio_w %d data %02x (%08x)\n", n_port, data, mem_mask );
		m_p_n_sio_tx_data[ n_port ] = data;
		m_p_n_sio_status[ n_port ] &= ~( SIO_STATUS_TX_RDY );
		m_p_n_sio_status[ n_port ] &= ~( SIO_STATUS_TX_EMPTY );
		sio_timer_adjust( n_port );
		break;
	case 1:
		verboselog( 0, "psx_sio_w( %08x, %08x, %08x )\n", offset, data, mem_mask );
		break;
	case 2:
		if( ACCESSING_LSW32 )
		{
			m_p_n_sio_mode[ n_port ] = data & 0xffff;
			verboselog( 1, "psx_sio_w %d mode %04x\n", n_port, data & 0xffff );
		}
		if( ACCESSING_MSW32 )
		{
			verboselog( 1, "psx_sio_w %d control %04x\n", n_port, data >> 16 );
			m_p_n_sio_control[ n_port ] = data >> 16;

			if( ( m_p_n_sio_control[ n_port ] & SIO_CONTROL_RESET ) != 0 )
			{
				verboselog( 1, "psx_sio_w reset\n" );
				m_p_n_sio_status[ n_port ] |= SIO_STATUS_TX_EMPTY | SIO_STATUS_TX_RDY;
				m_p_n_sio_status[ n_port ] &= ~( SIO_STATUS_RX_RDY | SIO_STATUS_OVERRUN | SIO_STATUS_IRQ );
			}
			if( ( m_p_n_sio_control[ n_port ] & SIO_CONTROL_IACK ) != 0 )
			{
				verboselog( 1, "psx_sio_w iack\n" );
				m_p_n_sio_status[ n_port ] &= ~( SIO_STATUS_IRQ );
				m_p_n_sio_control[ n_port ] &= ~( SIO_CONTROL_IACK );
			}
			if( ( m_p_n_sio_control[ n_port ] & SIO_CONTROL_DTR ) != 0 )
			{
				m_p_n_sio_tx[ n_port ] |= PSX_SIO_OUT_DTR;
			}
			else
			{
				m_p_n_sio_tx[ n_port ] &= ~PSX_SIO_OUT_DTR;
			}

			if( ( ( m_p_n_sio_tx[ n_port ] ^ m_p_n_sio_tx_prev[ n_port ] ) & PSX_SIO_OUT_DTR ) != 0 )
			{
				if( m_p_f_sio_handler[ n_port ] != NULL )
				{
					m_p_f_sio_handler[ n_port ]( m_p_n_sio_tx[ n_port ] );
				}
			}
			m_p_n_sio_tx_prev[ n_port ] = m_p_n_sio_tx[ n_port ];

		}
		break;
	case 3:
		if( ACCESSING_LSW32 )
		{
			verboselog( 0, "psx_sio_w( %08x, %08x, %08x )\n", offset, data, mem_mask );
		}
		if( ACCESSING_MSW32 )
		{
			m_p_n_sio_baud[ n_port ] = data >> 16;
			verboselog( 1, "psx_sio_w %d baud %04x\n", n_port, data >> 16 );
		}
		break;
	default:
		verboselog( 0, "psx_sio_w( %08x, %08x, %08x )\n", offset, data, mem_mask );
		break;
	}
}

READ32_HANDLER( psx_sio_r )
{
	UINT32 data;
	int n_port;

	n_port = offset / 4;

	switch( offset % 4 )
	{
	case 0:
		data = m_p_n_sio_rx_data[ n_port ];
		m_p_n_sio_status[ n_port ] &= ~( SIO_STATUS_RX_RDY );
		m_p_n_sio_rx_data[ n_port ] = 0xff;
		verboselog( 1, "psx_sio_r %d data %02x (%08x)\n", n_port, data, mem_mask );
		break;
	case 1:
		data = m_p_n_sio_status[ n_port ];
		if( ACCESSING_LSW32 )
		{
			verboselog( 1, "psx_sio_r %d status %04x\n", n_port, data & 0xffff );
		}
		if( ACCESSING_MSW32 )
		{
			verboselog( 0, "psx_sio_r( %08x, %08x ) %08x\n", offset, mem_mask, data );
		}
		break;
	case 2:
		data = ( m_p_n_sio_control[ n_port ] << 16 ) | m_p_n_sio_mode[ n_port ];
		if( ACCESSING_LSW32 )
		{
			verboselog( 1, "psx_sio_r %d mode %04x\n", n_port, data & 0xffff );
		}
		if( ACCESSING_MSW32 )
		{
			verboselog( 1, "psx_sio_r %d control %04x\n", n_port, data >> 16 );
		}
		break;
	case 3:
		data = m_p_n_sio_baud[ n_port ] << 16;
		if( ACCESSING_LSW32 )
		{
			verboselog( 0, "psx_sio_r( %08x, %08x ) %08x\n", offset, mem_mask, data );
		}
		if( ACCESSING_MSW32 )
		{
			verboselog( 1, "psx_sio_r %d baud %04x\n", n_port, data >> 16 );
		}
		break;
	default:
		data = 0;
		verboselog( 0, "psx_sio_r( %08x, %08x ) %08x\n", offset, mem_mask, data );
		break;
	}
	return data;
}

void psx_sio_install_handler( int n_port, psx_sio_handler p_f_sio_handler )
{
	m_p_f_sio_handler[ n_port ] = p_f_sio_handler;
}

/* MDEC */

static int mdec_decoded = 0;
static int mdec_offset = 0;
static UINT16 mdec_output[ 24 * 16 ];

#define	DCTSIZE ( 8 )
#define	DCTSIZE2 ( DCTSIZE * DCTSIZE )

static INT32 m_p_n_mdec_quantize_y[ DCTSIZE2 ];
static INT32 m_p_n_mdec_quantize_uv[ DCTSIZE2 ];
static INT32 m_p_n_mdec_cos[ DCTSIZE2 ];
static INT32 m_p_n_mdec_cos_precalc[ DCTSIZE2 * DCTSIZE2 ];

static UINT32 m_n_mdec0_command;
static UINT32 m_n_mdec0_address;
static UINT32 m_n_mdec0_size;
static UINT32 m_n_mdec1_command;
static UINT32 m_n_mdec1_status;

static UINT16 m_p_n_mdec_clamp8[ 256 * 3 ];
static UINT16 m_p_n_mdec_r5[ 256 * 3 ];
static UINT16 m_p_n_mdec_g5[ 256 * 3 ];
static UINT16 m_p_n_mdec_b5[ 256 * 3 ];

static const UINT32 m_p_n_mdec_zigzag[ DCTSIZE2 ] =
{
	 0,  1,  8, 16,  9,  2,  3, 10,
	17, 24, 32, 25, 18, 11,  4,  5,
	12, 19, 26, 33, 40, 48, 41, 34,
	27, 20, 13,  6,  7, 14, 21, 28,
	35, 42, 49, 56, 57, 50, 43, 36,
	29, 22, 15, 23, 30, 37, 44, 51,
	58, 59, 52, 45, 38, 31, 39, 46,
	53, 60, 61, 54, 47, 55, 62, 63
};

static INT32 m_p_n_mdec_unpacked[ DCTSIZE2 * 6 * 2 ];

#define MDEC_COS_PRECALC_BITS ( 21 )

static void mdec_cos_precalc( void )
{
	UINT32 n_x;
	UINT32 n_y;
	UINT32 n_u;
	UINT32 n_v;
	INT32 *p_n_precalc;

	p_n_precalc = m_p_n_mdec_cos_precalc;

	for( n_y = 0; n_y < 8; n_y++ )
	{
		for( n_x = 0; n_x < 8; n_x++ )
		{
			for( n_v = 0; n_v < 8; n_v++ )
			{
				for( n_u = 0; n_u < 8; n_u++ )
				{
					*( p_n_precalc++ ) =
						( ( m_p_n_mdec_cos[ ( n_u * 8 ) + n_x ] *
						m_p_n_mdec_cos[ ( n_v * 8 ) + n_y ] ) >> ( 30 - MDEC_COS_PRECALC_BITS ) );
				}
			}
		}
	}
}

static void mdec_idct( INT32 *p_n_src, INT32 *p_n_dst )
{
	UINT32 n_yx;
	UINT32 n_vu;
	INT32 p_n_z[ 8 ];
	INT32 *p_n_data;
	INT32 *p_n_precalc;

	p_n_precalc = m_p_n_mdec_cos_precalc;

	for( n_yx = 0; n_yx < DCTSIZE2; n_yx++ )
	{
		p_n_data = p_n_src;

		memset( p_n_z, 0, sizeof( p_n_z ) );

		for( n_vu = 0; n_vu < DCTSIZE2 / 8; n_vu++ )
		{
			p_n_z[ 0 ] += p_n_data[ 0 ] * p_n_precalc[ 0 ];
			p_n_z[ 1 ] += p_n_data[ 1 ] * p_n_precalc[ 1 ];
			p_n_z[ 2 ] += p_n_data[ 2 ] * p_n_precalc[ 2 ];
			p_n_z[ 3 ] += p_n_data[ 3 ] * p_n_precalc[ 3 ];
			p_n_z[ 4 ] += p_n_data[ 4 ] * p_n_precalc[ 4 ];
			p_n_z[ 5 ] += p_n_data[ 5 ] * p_n_precalc[ 5 ];
			p_n_z[ 6 ] += p_n_data[ 6 ] * p_n_precalc[ 6 ];
			p_n_z[ 7 ] += p_n_data[ 7 ] * p_n_precalc[ 7 ];
			p_n_data += 8;
			p_n_precalc += 8;
		}

		*( p_n_dst++ ) = ( p_n_z[ 0 ] + p_n_z[ 1 ] + p_n_z[ 2 ] + p_n_z[ 3 ] +
			p_n_z[ 4 ] + p_n_z[ 5 ] + p_n_z[ 6 ] + p_n_z[ 7 ] ) >> ( MDEC_COS_PRECALC_BITS + 2 );
	}
}

INLINE UINT16 mdec_unpack_run( UINT16 n_packed )
{
	return n_packed >> 10;
}

INLINE INT32 mdec_unpack_val( UINT16 n_packed )
{
	return ( ( (INT32)n_packed ) << 22 ) >> 22;
}

static UINT32 mdec_unpack( UINT32 n_address )
{
	UINT8 n_z;
	INT32 n_qscale;
	UINT16 n_packed;
	UINT32 n_block;
	INT32 *p_n_block;
	INT32 p_n_unpacked[ 64 ];
	INT32 *p_n_q;

	p_n_q = m_p_n_mdec_quantize_uv;
	p_n_block = m_p_n_mdec_unpacked;

	for( n_block = 0; n_block < 6; n_block++ )
	{
		memset( p_n_unpacked, 0, sizeof( p_n_unpacked ) );

		if( n_block == 2 )
		{
			p_n_q = m_p_n_mdec_quantize_y;
		}
		n_packed = psxreadword( n_address );
		n_address += 2;
		if( n_packed == 0xfe00 )
		{
			break;
		}

		n_qscale = mdec_unpack_run( n_packed );
		p_n_unpacked[ 0 ] = mdec_unpack_val( n_packed ) * p_n_q[ 0 ];

		n_z = 0;
		for( ;; )
		{
			n_packed = psxreadword( n_address );
			n_address += 2;

			if( n_packed == 0xfe00 )
			{
				break;
			}
			n_z += mdec_unpack_run( n_packed ) + 1;
			if( n_z > 63 )
			{
				break;
			}
			p_n_unpacked[ m_p_n_mdec_zigzag[ n_z ] ] = ( mdec_unpack_val( n_packed ) * p_n_q[ n_z ] * n_qscale ) / 8;
		}
		mdec_idct( p_n_unpacked, p_n_block );
		p_n_block += DCTSIZE2;
	}
	return n_address;
}

INLINE INT32 mdec_cr_to_r( INT32 n_cr )
{
	return ( 1435 * n_cr ) >> 10;
}

INLINE INT32 mdec_cr_to_g( INT32 n_cr )
{
	return ( -731 * n_cr ) >> 10;
}

INLINE INT32 mdec_cb_to_g( INT32 n_cb )
{
	return ( -351 * n_cb ) >> 10;
}

INLINE INT32 mdec_cb_to_b( INT32 n_cb )
{
	return ( 1814 * n_cb ) >> 10;
}

INLINE UINT16 mdec_clamp_r5( INT32 n_r )
{
	return m_p_n_mdec_r5[ n_r + 128 + 256 ];
}

INLINE UINT16 mdec_clamp_g5( INT32 n_g )
{
	return m_p_n_mdec_g5[ n_g + 128 + 256 ];
}

INLINE UINT16 mdec_clamp_b5( INT32 n_b )
{
	return m_p_n_mdec_b5[ n_b + 128 + 256 ];
}

INLINE void mdec_makergb15( UINT32 n_address, INT32 n_r, INT32 n_g, INT32 n_b, INT32 *p_n_y, UINT16 n_stp )
{
	mdec_output[ WORD_XOR_LE( n_address + 0 ) / 2 ] = n_stp |
		mdec_clamp_r5( p_n_y[ 0 ] + n_r ) |
		mdec_clamp_g5( p_n_y[ 0 ] + n_g ) |
		mdec_clamp_b5( p_n_y[ 0 ] + n_b );

	mdec_output[ WORD_XOR_LE( n_address + 2 ) / 2 ] = n_stp |
		mdec_clamp_r5( p_n_y[ 1 ] + n_r ) |
		mdec_clamp_g5( p_n_y[ 1 ] + n_g ) |
		mdec_clamp_b5( p_n_y[ 1 ] + n_b );
}

static void mdec_yuv2_to_rgb15( void )
{
	INT32 n_r;
	INT32 n_g;
	INT32 n_b;
	INT32 n_cb;
	INT32 n_cr;
	INT32 *p_n_cb;
	INT32 *p_n_cr;
	INT32 *p_n_y;
	UINT32 n_x;
	UINT32 n_y;
	UINT32 n_z;
	UINT16 n_stp;
	int n_address = 0;

	if( ( m_n_mdec0_command & ( 1L << 25 ) ) != 0 )
	{
		n_stp = 0x8000;
	}
	else
	{
		n_stp = 0x0000;
	}

	p_n_cr = &m_p_n_mdec_unpacked[ 0 ];
	p_n_cb = &m_p_n_mdec_unpacked[ DCTSIZE2 ];
	p_n_y = &m_p_n_mdec_unpacked[ DCTSIZE2 * 2 ];

	for( n_z = 0; n_z < 2; n_z++ )
	{
		for( n_y = 0; n_y < 4; n_y++ )
		{
			for( n_x = 0; n_x < 4; n_x++ )
			{
				n_cr = *( p_n_cr );
				n_cb = *( p_n_cb );
				n_r = mdec_cr_to_r( n_cr );
				n_g = mdec_cr_to_g( n_cr ) + mdec_cb_to_g( n_cb );
				n_b = mdec_cb_to_b( n_cb );

				mdec_makergb15( ( n_address +  0 ), n_r, n_g, n_b, p_n_y, n_stp );
				mdec_makergb15( ( n_address + 32 ), n_r, n_g, n_b, p_n_y + 8, n_stp );

				n_cr = *( p_n_cr + 4 );
				n_cb = *( p_n_cb + 4 );
				n_r = mdec_cr_to_r( n_cr );
				n_g = mdec_cr_to_g( n_cr ) + mdec_cb_to_g( n_cb );
				n_b = mdec_cb_to_b( n_cb );

				mdec_makergb15( ( n_address + 16 ), n_r, n_g, n_b, p_n_y + DCTSIZE2, n_stp );
				mdec_makergb15( ( n_address + 48 ), n_r, n_g, n_b, p_n_y + DCTSIZE2 + 8, n_stp );

				p_n_cr++;
				p_n_cb++;
				p_n_y += 2;
				n_address += 4;
			}
			p_n_cr += 4;
			p_n_cb += 4;
			p_n_y += 8;
			n_address += 48;
		}
		p_n_y += DCTSIZE2;
	}
	mdec_decoded = ( 16 * 16 ) / 2;
}

INLINE UINT16 mdec_clamp8( INT32 n_r )
{
	return m_p_n_mdec_clamp8[ n_r + 128 + 256 ];
}

INLINE void mdec_makergb24( UINT32 n_address, INT32 n_r, INT32 n_g, INT32 n_b, INT32 *p_n_y, UINT32 n_stp )
{
	mdec_output[ WORD_XOR_LE( n_address + 0 ) / 2 ] = ( mdec_clamp8( p_n_y[ 0 ] + n_g ) << 8 ) | mdec_clamp8( p_n_y[ 0 ] + n_r );
	mdec_output[ WORD_XOR_LE( n_address + 2 ) / 2 ] = ( mdec_clamp8( p_n_y[ 1 ] + n_r ) << 8 ) | mdec_clamp8( p_n_y[ 0 ] + n_b );
	mdec_output[ WORD_XOR_LE( n_address + 4 ) / 2 ] = ( mdec_clamp8( p_n_y[ 1 ] + n_b ) << 8 ) | mdec_clamp8( p_n_y[ 1 ] + n_g );
}

static void mdec_yuv2_to_rgb24( void )
{
	INT32 n_r;
	INT32 n_g;
	INT32 n_b;
	INT32 n_cb;
	INT32 n_cr;
	INT32 *p_n_cb;
	INT32 *p_n_cr;
	INT32 *p_n_y;
	UINT32 n_x;
	UINT32 n_y;
	UINT32 n_z;
	UINT32 n_stp;
	int n_address = 0;

	if( ( m_n_mdec0_command & ( 1L << 25 ) ) != 0 )
	{
		n_stp = 0x80008000;
	}
	else
	{
		n_stp = 0x00000000;
	}

	p_n_cr = &m_p_n_mdec_unpacked[ 0 ];
	p_n_cb = &m_p_n_mdec_unpacked[ DCTSIZE2 ];
	p_n_y = &m_p_n_mdec_unpacked[ DCTSIZE2 * 2 ];

	for( n_z = 0; n_z < 2; n_z++ )
	{
		for( n_y = 0; n_y < 4; n_y++ )
		{
			for( n_x = 0; n_x < 4; n_x++ )
			{
				n_cr = *( p_n_cr );
				n_cb = *( p_n_cb );
				n_r = mdec_cr_to_r( n_cr );
				n_g = mdec_cr_to_g( n_cr ) + mdec_cb_to_g( n_cb );
				n_b = mdec_cb_to_b( n_cb );

				mdec_makergb24( ( n_address +  0 ), n_r, n_g, n_b, p_n_y, n_stp );
				mdec_makergb24( ( n_address + 48 ), n_r, n_g, n_b, p_n_y + 8, n_stp );

				n_cr = *( p_n_cr + 4 );
				n_cb = *( p_n_cb + 4 );
				n_r = mdec_cr_to_r( n_cr );
				n_g = mdec_cr_to_g( n_cr ) + mdec_cb_to_g( n_cb );
				n_b = mdec_cb_to_b( n_cb );

				mdec_makergb24( ( n_address + 24 ), n_r, n_g, n_b, p_n_y + DCTSIZE2, n_stp );
				mdec_makergb24( ( n_address + 72 ), n_r, n_g, n_b, p_n_y + DCTSIZE2 + 8, n_stp );

				p_n_cr++;
				p_n_cb++;
				p_n_y += 2;
				n_address += 6;
			}
			p_n_cr += 4;
			p_n_cb += 4;
			p_n_y += 8;
			n_address += 72;
		}
		p_n_y += DCTSIZE2;
	}
	mdec_decoded = ( 24 * 16 ) / 2;
}

static void mdec0_write( UINT32 n_address, INT32 n_size )
{
	int n_index;

	verboselog( 2, "mdec0_write( %08x, %08x )\n", n_address, n_size );
	switch( m_n_mdec0_command >> 28 )
	{
	case 0x3:
		verboselog( 1, "mdec decode %08x %08x %08x\n", m_n_mdec0_command, n_address, n_size );
		m_n_mdec0_address = n_address;
		m_n_mdec0_size = n_size * 4;
		m_n_mdec1_status |= ( 1L << 29 );
		break;
	case 0x4:
		verboselog( 1, "mdec quantize table %08x %08x %08x\n", m_n_mdec0_command, n_address, n_size );
		n_index = 0;
		while( n_size > 0 )
		{
			if( n_index < DCTSIZE2 )
			{
				m_p_n_mdec_quantize_y[ n_index + 0 ] = ( g_p_n_psxram[ n_address / 4 ] >> 0 ) & 0xff;
				m_p_n_mdec_quantize_y[ n_index + 1 ] = ( g_p_n_psxram[ n_address / 4 ] >> 8 ) & 0xff;
				m_p_n_mdec_quantize_y[ n_index + 2 ] = ( g_p_n_psxram[ n_address / 4 ] >> 16 ) & 0xff;
				m_p_n_mdec_quantize_y[ n_index + 3 ] = ( g_p_n_psxram[ n_address / 4 ] >> 24 ) & 0xff;
			}
			else if( n_index < DCTSIZE2 * 2 )
			{
				m_p_n_mdec_quantize_uv[ n_index + 0 - DCTSIZE2 ] = ( g_p_n_psxram[ n_address / 4 ] >> 0 ) & 0xff;
				m_p_n_mdec_quantize_uv[ n_index + 1 - DCTSIZE2 ] = ( g_p_n_psxram[ n_address / 4 ] >> 8 ) & 0xff;
				m_p_n_mdec_quantize_uv[ n_index + 2 - DCTSIZE2 ] = ( g_p_n_psxram[ n_address / 4 ] >> 16 ) & 0xff;
				m_p_n_mdec_quantize_uv[ n_index + 3 - DCTSIZE2 ] = ( g_p_n_psxram[ n_address / 4 ] >> 24 ) & 0xff;
			}
			n_index += 4;
			n_address += 4;
			n_size--;
		}
		break;
	case 0x6:
		verboselog( 1, "mdec cosine table %08x %08x %08x\n", m_n_mdec0_command, n_address, n_size );
		n_index = 0;
		while( n_size > 0 )
		{
			m_p_n_mdec_cos[ n_index + 0 ] = (INT16)( ( g_p_n_psxram[ n_address / 4 ] >> 0 ) & 0xffff );
			m_p_n_mdec_cos[ n_index + 1 ] = (INT16)( ( g_p_n_psxram[ n_address / 4 ] >> 16 ) & 0xffff );
			n_index += 2;
			n_address += 4;
			n_size--;
		}
		mdec_cos_precalc();
		break;
	default:
		verboselog( 0, "mdec unknown command %08x %08x %08x\n", m_n_mdec0_command, n_address, n_size );
		break;
	}
}

static void mdec1_read( UINT32 n_address, INT32 n_size )
{
	UINT32 n_this;
	UINT32 n_nextaddress;

	verboselog( 2, "mdec1_read( %08x, %08x )\n", n_address, n_size );
	if( ( m_n_mdec0_command & ( 1L << 29 ) ) != 0 && m_n_mdec0_size != 0 )
	{
		while( n_size > 0 )
		{
			if( mdec_decoded == 0 )
			{
				if( (int)m_n_mdec0_size <= 0 )
				{
					mame_printf_debug( "ran out of data %08x\n", n_size );
					m_n_mdec0_size = 0;
					break;
				}

				n_nextaddress = mdec_unpack( m_n_mdec0_address );
				m_n_mdec0_size -= n_nextaddress - m_n_mdec0_address;
				m_n_mdec0_address = n_nextaddress;

				if( ( m_n_mdec0_command & ( 1L << 27 ) ) != 0 )
				{
					mdec_yuv2_to_rgb15();
				}
				else
				{
					mdec_yuv2_to_rgb24();
				}
				mdec_offset = 0;
			}

			n_this = mdec_decoded;
			if( n_this > n_size )
			{
				n_this = n_size;
			}
			mdec_decoded -= n_this;

			memcpy( (UINT8 *)g_p_n_psxram + n_address, (UINT8 *)mdec_output + mdec_offset, n_this * 4 );
			mdec_offset += n_this * 4;
			n_address += n_this * 4;
			n_size -= n_this;
		}

		if( (int)m_n_mdec0_size < 0 )
		{
			mame_printf_debug( "ran out of data %d\n", m_n_mdec0_size );
		}
	}
	else
	{
		mame_printf_debug( "mdec1_read no conversion :%08x:%08x:\n", m_n_mdec0_command, m_n_mdec0_size );
	}
	m_n_mdec1_status &= ~( 1L << 29 );
}

WRITE32_HANDLER( psx_mdec_w )
{
	switch( offset )
	{
	case 0:
		verboselog( 2, "mdec 0 command %08x\n", data );
		m_n_mdec0_command = data;
		break;
	case 1:
		verboselog( 2, "mdec 1 command %08x\n", data );
		m_n_mdec1_command = data;
		break;
	}
}

READ32_HANDLER( psx_mdec_r )
{
	switch( offset )
	{
	case 0:
		verboselog( 2, "mdec 0 status %08x\n", 0 );
		return 0;
	case 1:
		verboselog( 2, "mdec 1 status %08x\n", m_n_mdec1_status );
		return m_n_mdec1_status;
	}
	return 0;
}

static void gpu_read( UINT32 n_address, INT32 n_size )
{
	psx_gpu_read( &g_p_n_psxram[ n_address / 4 ], n_size );
}

static void gpu_write( UINT32 n_address, INT32 n_size )
{
	psx_gpu_write( &g_p_n_psxram[ n_address / 4 ], n_size );
}

void psx_machine_init( void )
{
	int n;

	/* irq */
	m_n_irqdata = 0;
	m_n_irqmask = 0;

	/* dma */
	m_n_dpcp = 0;
	m_n_dicr = 0;

	m_n_mdec0_command = 0;
	m_n_mdec0_address = 0;
	m_n_mdec0_size = 0;
	m_n_mdec1_command = 0;
	m_n_mdec1_status = 0;

	for( n = 0; n < 7; n++ )
	{
		dma_stop_timer( n );
	}

	for( n = 0; n < 2; n++ )
	{
		m_p_n_sio_status[ n ] = SIO_STATUS_TX_EMPTY | SIO_STATUS_TX_RDY;
		m_p_n_sio_mode[ n ] = 0;
		m_p_n_sio_control[ n ] = 0;
		m_p_n_sio_baud[ n ] = 0;
		m_p_n_sio_tx[ n ] = 0;
		m_p_n_sio_rx[ n ] = 0;
		m_p_n_sio_tx_prev[ n ] = 0;
		m_p_n_sio_rx_prev[ n ] = 0;
		m_p_n_sio_rx_data[ n ] = 0;
		m_p_n_sio_tx_data[ n ] = 0;
		m_p_n_sio_rx_shift[ n ] = 0;
		m_p_n_sio_tx_shift[ n ] = 0;
		m_p_n_sio_rx_bits[ n ] = 0;
		m_p_n_sio_tx_bits[ n ] = 0;
	}

	psx_gpu_reset();
}

static void psx_postload( void )
{
	int n;

	psx_irq_update();

	for( n = 0; n < 7; n++ )
	{
		dma_timer_adjust( n );
	}

	for( n = 0; n < 3; n++ )
	{
		root_timer_adjust( n );
	}

	for( n = 0; n < 2; n++ )
	{
		sio_timer_adjust( n );
	}

	mdec_cos_precalc();
}

void psx_driver_init( void )
{
	int n;

	for( n = 0; n < 7; n++ )
	{
		m_p_timer_dma[ n ] = timer_alloc( dma_finished_callback , NULL);
		m_p_fn_dma_read[ n ] = NULL;
		m_p_fn_dma_write[ n ] = NULL;
	}

	for( n = 0; n < 3; n++ )
	{
		m_p_timer_root[ n ] = timer_alloc( root_finished , NULL);
	}

	for( n = 0; n < 2; n++ )
	{
		m_p_timer_sio[ n ] = timer_alloc( sio_clock , NULL);
	}

	for( n = 0; n < 256; n++ )
	{
		m_p_n_mdec_clamp8[ n ] = 0;
		m_p_n_mdec_clamp8[ n + 256 ] = n;
		m_p_n_mdec_clamp8[ n + 512 ] = 255;

		m_p_n_mdec_r5[ n ] = 0;
		m_p_n_mdec_r5[ n + 256 ] = ( n >> 3 );
		m_p_n_mdec_r5[ n + 512 ] = ( 255 >> 3 );

		m_p_n_mdec_g5[ n ] = 0;
		m_p_n_mdec_g5[ n + 256 ] = ( n >> 3 ) << 5;
		m_p_n_mdec_g5[ n + 512 ] = ( 255 >> 3 ) << 5;

		m_p_n_mdec_b5[ n ] = 0;
		m_p_n_mdec_b5[ n + 256 ] = ( n >> 3 ) << 10;
		m_p_n_mdec_b5[ n + 512 ] = ( 255 >> 3 ) << 10;
	}

	for( n = 0; n < 2; n++ )
	{
		m_p_f_sio_handler[ n ] = NULL;
	}

	psx_dma_install_read_handler( 1, mdec1_read );
	psx_dma_install_read_handler( 2, gpu_read );

	psx_dma_install_write_handler( 0, mdec0_write );
	psx_dma_install_write_handler( 2, gpu_write );

	state_save_register_global( m_n_irqdata );
	state_save_register_global( m_n_irqmask );
	state_save_register_global_array( m_p_n_dmabase );
	state_save_register_global_array( m_p_n_dmablockcontrol );
	state_save_register_global_array( m_p_n_dmachannelcontrol );
	state_save_register_global_array( m_p_n_dma_ticks );
	state_save_register_global_array( m_p_b_dma_running );
	state_save_register_global( m_n_dpcp );
	state_save_register_global( m_n_dicr );
	state_save_register_global_array( m_p_n_root_count );
	state_save_register_global_array( m_p_n_root_mode );
	state_save_register_global_array( m_p_n_root_target );
	state_save_register_global_array( m_p_n_root_start );

	state_save_register_global_array( m_p_n_sio_status );
	state_save_register_global_array( m_p_n_sio_mode );
	state_save_register_global_array( m_p_n_sio_control );
	state_save_register_global_array( m_p_n_sio_baud );
	state_save_register_global_array( m_p_n_sio_tx );
	state_save_register_global_array( m_p_n_sio_rx );
	state_save_register_global_array( m_p_n_sio_tx_prev );
	state_save_register_global_array( m_p_n_sio_rx_prev );
	state_save_register_global_array( m_p_n_sio_rx_data );
	state_save_register_global_array( m_p_n_sio_tx_data );
	state_save_register_global_array( m_p_n_sio_rx_shift );
	state_save_register_global_array( m_p_n_sio_tx_shift );
	state_save_register_global_array( m_p_n_sio_rx_bits );
	state_save_register_global_array( m_p_n_sio_tx_bits );

	state_save_register_global( m_n_mdec0_command );
	state_save_register_global( m_n_mdec0_address );
	state_save_register_global( m_n_mdec0_size );
	state_save_register_global( m_n_mdec1_command );
	state_save_register_global( m_n_mdec1_status );
	state_save_register_global_array( m_p_n_mdec_quantize_y );
	state_save_register_global_array( m_p_n_mdec_quantize_uv );
	state_save_register_global_array( m_p_n_mdec_cos );

	state_save_register_func_postload( psx_postload );
}
