
#include "driver.h"
#include "includes/gamecom.h"
#include "cpu/sm8500/sm8500.h"
#include "image.h"

typedef struct {
	int enabled;
	int transfer_mode;
	int decrement_y;
	int decrement_x;
	int overwrite_mode;
	int width_x;
	int width_y;
	int width_x_count;
	int width_y_count;
	int source_x;
	int source_x_current;
	int source_y;
	int source_width;
	int dest_x;
	int dest_x_current;
	int dest_y;
	int dest_width;
	int state_count;
	int state_pixel;
	int state_limit;
	UINT8 palette[4];
	UINT8 *source_bank;
	unsigned int source_current;
	unsigned int source_line;
	unsigned int source_mask;
	UINT8 *dest_bank;
	unsigned int dest_current;
	unsigned int dest_line;
	unsigned int dest_mask;
} GAMECOM_DMA;

typedef struct {
	int enabled;
	int state_count;
	int state_limit;
	int counter;
	int check_value;
} GAMECOM_TIMER;

static UINT8 *internal_ram;
UINT8 gamecom_internal_registers[0x80];
UINT8 gamecom_vram[16*1024];
static UINT8 *cartridge1 = NULL;
static UINT8 *cartridge2 = NULL;
static UINT8 *cartridge = NULL;
static UINT8 *dummy_bank = NULL;

static emu_timer *gamecom_clock_timer = NULL;
static GAMECOM_DMA gamecom_dma;
static GAMECOM_TIMER gamecom_timer[2];

//static const int gamecom_timer_limit[8] = { 2/2, 1024/2, 2048/2, 4096/2, 8192/2, 16384/2, 32768/2, 65536/2 };
static const int gamecom_timer_limit[8] = { 2, 1024, 2048, 4096, 8192, 16384, 32768, 65536 };

static void gamecom_dma_init(running_machine *machine);

static TIMER_CALLBACK(gamecom_clock_timer_callback)
{
	UINT8 val = ( ( gamecom_internal_registers[SM8521_CLKT] & 0x3F ) + 1 ) & 0x3F;
	gamecom_internal_registers[SM8521_CLKT] = ( gamecom_internal_registers[SM8521_CLKT] & 0xC0 ) | val;
	cpu_set_input_line(machine->cpu[0], CK_INT, HOLD_LINE );
}

MACHINE_RESET( gamecom )
{
	UINT8 *rom = memory_region(machine, "user1");
	memory_set_bankptr( machine, 1, rom );
	memory_set_bankptr( machine, 2, rom );
	memory_set_bankptr( machine, 3, rom );
	memory_set_bankptr( machine, 4, rom );

	/* should possibly go in a DRIVER_INIT piece? */
	gamecom_clock_timer = timer_alloc(machine,  gamecom_clock_timer_callback , NULL);

	/* intialize the empty dummy bank */
	if ( dummy_bank == NULL ) 
	{
		dummy_bank = auto_malloc( 8 * 1024 );
	}
	memset( dummy_bank, 0xff, 8 * 1024 );
	cartridge = NULL;
	/* disable DMA and timer */
	gamecom_dma.enabled = 0;

	memset( gamecom_internal_registers, 0x00, sizeof(gamecom_internal_registers) );
	gamecom_internal_w( cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM), SM8521_URTT, 0xFF );
	gamecom_internal_w( cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM), SM8521_URTS, 0x42 );
	gamecom_internal_w( cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM), SM8521_WDTC, 0x38 );
}

static void gamecom_set_mmu( running_machine *machine, int mmu, UINT8 data ) 
{
	if ( data < 32 ) 
	{
		/* select internal ROM bank */
		memory_set_bankptr( machine, mmu, memory_region(machine, "user1") + (data << 13) );
	} 
	else 
	{
		/* select cartridge bank */
		if ( cartridge == NULL ) 
		{
			memory_set_bankptr( machine, mmu, dummy_bank );
		} 
		else 
		{
			memory_set_bankptr( machine, mmu, cartridge + ( data << 13 ) );
		}
	}
}

static void handle_stylus_press( running_machine *machine, UINT8 column ) 
{
	static const UINT16 row_data[10] = { 0x3FE, 0x3FD, 0x3FB, 0x3F7, 0x3EF, 0x3DF, 0x3BF, 0x37F, 0x2FF, 0x1FF };
	static UINT32 stylus_x;
	static UINT32 stylus_y;

	if ( column == 0 ) 
	{
		if ( ! ( input_port_read(machine, "IN2") & 0x04 ) ) 
		{
			stylus_x = input_port_read(machine, "IN3") >> 4;
			stylus_y = input_port_read(machine, "IN4") >> 4;
		} 
		else 
		{
			stylus_x = 16;
			stylus_y = 16;
		}
	}

	if ( stylus_x == column ) 
	{
		cpu_set_reg( machine->cpu[0], SM8500_P0, row_data[stylus_y] & 0xFF );
		cpu_set_reg( machine->cpu[0], SM8500_P1, ( cpu_get_reg( machine->cpu[0], SM8500_P1 ) & 0xFC ) | ( ( row_data[stylus_y] >> 8 ) & 0x03 ) );
	} 
	else 
	{
		cpu_set_reg( machine->cpu[0], SM8500_P0, 0xFF );
		cpu_set_reg( machine->cpu[0], SM8500_P1, ( cpu_get_reg( machine->cpu[0], SM8500_P1 ) & 0xFC ) | 0x03 );
	}
}

WRITE8_HANDLER( gamecom_internal_w )
{
	if ( offset >= 0x80 ) 
	{
		internal_ram[offset] = data;
		return;
	}

	switch( offset ) 
	{
	case SM8521_IE0:	cpu_set_reg( space->machine->cpu[0], SM8500_IE0, data ); return;
	case SM8521_IE1:	cpu_set_reg( space->machine->cpu[0], SM8500_IE1, data ); return;
	case SM8521_IR0:	cpu_set_reg( space->machine->cpu[0], SM8500_IR0, data ); return;
	case SM8521_IR1:	cpu_set_reg( space->machine->cpu[0], SM8500_IR1, data ); return;
	case SM8521_P0:		cpu_set_reg( space->machine->cpu[0], SM8500_P0, data ); return;
	case SM8521_P1:		cpu_set_reg( space->machine->cpu[0], SM8500_P1, data ); return;
	case SM8521_P2:		cpu_set_reg( space->machine->cpu[0], SM8500_P2, data );
				switch( ( cpu_get_reg( space->machine->cpu[0], SM8500_P1 ) << 8 ) | data ) 
				{
				case 0xFBFF:	/* column #0 */
						/* P0 bit 0 cleared => 01 */
						/* P0 bit 1 cleared => 0E */
						/* P0 bit 2 cleared => 1B */
						/* P0 bit 3 cleared => etc */
						/* P0 bit 4 cleared => */
						/* P0 bit 5 cleared => */
						/* P0 bit 6 cleared => */
						/* P0 bit 7 cleared => */
						/* P1 bit 0 cleared => */
						/* P1 bit 1 cleared => */
					handle_stylus_press( space->machine, 0 );
					break;
				case 0xF7FF:	/* column #1 */
					handle_stylus_press( space->machine, 1 );
					break;
				case 0xEFFF:	/* column #2 */
					handle_stylus_press( space->machine, 2 );
					break;
				case 0xDFFF:	/* column #3 */
					handle_stylus_press( space->machine, 3 );
					break;
				case 0xBFFF:	/* column #4 */
					handle_stylus_press( space->machine, 4 );
					break;
				case 0x7FFF:	/* column #5 */
					handle_stylus_press( space->machine, 5 );
					break;
				case 0xFFFE:	/* column #6 */
					handle_stylus_press( space->machine, 6 );
					break;
				case 0xFFFD:	/* column #7 */
					handle_stylus_press( space->machine, 7 );
					break;
				case 0xFFFB:	/* column #8 */
					handle_stylus_press( space->machine, 8 );
					break;
				case 0xFFF7:	/* column #9 */
					handle_stylus_press( space->machine, 9 );
					break;
				case 0xFFEF:	/* column #10 */
					handle_stylus_press( space->machine, 10 );
					break;
				case 0xFFDF:	/* column #11 */
					handle_stylus_press( space->machine, 11 );
					break;
				case 0xFFBF:	/* column #12 */
					handle_stylus_press( space->machine, 12 );
					break;
				case 0xFF7F:	/* keys #1 */
						/* P0 bit 0 cleared => 83 (up) */
						/* P0 bit 1 cleared => 84 (down) */
						/* P0 bit 2 cleared => 85 (left) */
						/* P0 bit 3 cleared => 86 (right) */
						/* P0 bit 4 cleared => 87 (menu) */
						/* P0 bit 5 cleared => 8A (pause) */
						/* P0 bit 6 cleared => 89 (sound) */
						/* P0 bit 7 cleared => 8B (button A) */
						/* P1 bit 0 cleared => 8C (button B) */
						/* P1 bit 1 cleared => 8D (button C) */
					cpu_set_reg( space->machine->cpu[0], SM8500_P0, input_port_read(space->machine, "IN0") );
					cpu_set_reg( space->machine->cpu[0], SM8500_P1, ( cpu_get_reg( space->machine->cpu[0], SM8500_P1 ) & 0xFC ) | ( input_port_read(space->machine, "IN1") & 0x03 ) );
					break;
				case 0xFFFF:	/* keys #2 */
						/* P0 bit 0 cleared => 88 (power) */
						/* P0 bit 1 cleared => 8E (button D) */
						/* P0 bit 2 cleared => A0 */
						/* P0 bit 3 cleared => A0 */
						/* P0 bit 4 cleared => A0 */
						/* P0 bit 5 cleared => A0 */
						/* P0 bit 6 cleared => A0 */
						/* P0 bit 7 cleared => A0 */
						/* P1 bit 0 cleared => A0 */
						/* P1 bit 1 cleared => A0 */
					cpu_set_reg( space->machine->cpu[0], SM8500_P0, ( cpu_get_reg( space->machine->cpu[0], SM8500_P0 ) & 0xFC ) | ( input_port_read(space->machine, "IN2") & 0x03 ) );
					cpu_set_reg( space->machine->cpu[0], SM8500_P1, 0xFF );
					break;
				}
				return;
	case SM8521_P3:
				/* P3 bit7 clear, bit6 set -> enable cartridge port #0? */
				/* P3 bit6 clear, bit7 set -> enable cartridge port #1? */
				cpu_set_reg( space->machine->cpu[0], SM8500_P3, data );
				switch( data & 0xc0 ) 
				{
				case 0x40: cartridge = cartridge1; break;
				case 0x80: cartridge = cartridge2; break;
				default:   cartridge = NULL;       break;
				}
				/* update banks to reflect possible change of cartridge slot */
				gamecom_set_mmu( space->machine, 1, gamecom_internal_registers[SM8521_MMU1] );
				gamecom_set_mmu( space->machine, 2, gamecom_internal_registers[SM8521_MMU2] );
				gamecom_set_mmu( space->machine, 3, gamecom_internal_registers[SM8521_MMU3] );
				gamecom_set_mmu( space->machine, 4, gamecom_internal_registers[SM8521_MMU4] );
				return;
	case SM8521_SYS:	cpu_set_reg( space->machine->cpu[0], SM8500_SYS, data ); return;
	case SM8521_CKC:	cpu_set_reg( space->machine->cpu[0], SM8500_CKC, data ); return;
	case SM8521_SPH:	cpu_set_reg( space->machine->cpu[0], SM8500_SPH, data ); return;
	case SM8521_SPL:	cpu_set_reg( space->machine->cpu[0], SM8500_SPL, data ); return;
	case SM8521_PS0:	cpu_set_reg( space->machine->cpu[0], SM8500_PS0, data ); return;
	case SM8521_PS1:	cpu_set_reg( space->machine->cpu[0], SM8500_PS1, data ); return;
	case SM8521_P0C:	cpu_set_reg( space->machine->cpu[0], SM8500_P0C, data ); return;
	case SM8521_P1C:	cpu_set_reg( space->machine->cpu[0], SM8500_P1C, data ); return;
	case SM8521_P2C:	cpu_set_reg( space->machine->cpu[0], SM8500_P2C, data ); return;
	case SM8521_P3C:	cpu_set_reg( space->machine->cpu[0], SM8500_P3C, data ); return;
	case SM8521_MMU0:	/* disable bootstrap ROM? most likely not written to on game.com */
		logerror( "Write to MMU0\n" );
		break;
	case SM8521_MMU1:
		gamecom_set_mmu( space->machine, 1, data );
		break;
	case SM8521_MMU2:
		gamecom_set_mmu( space->machine, 2, data );
		break;
	case SM8521_MMU3:
		gamecom_set_mmu( space->machine, 3, data );
		break;
	case SM8521_MMU4:
		gamecom_set_mmu( space->machine, 4, data );
		break;

	/* Video hardware and DMA */
	case SM8521_LCDC:
//		logerror( "%X: Setting LCDC to %X\n", activecpu_get_pc(), data );
		break;
	case SM8521_LCH:
		break;
	case SM8521_LCV:
		break;
	case SM8521_DMC:
		gamecom_dma.overwrite_mode = data & 0x01;
		gamecom_dma.transfer_mode = data & 0x06;
		gamecom_dma.decrement_x = data & 0x08;
		gamecom_dma.decrement_y = data & 0x10;
		gamecom_dma.enabled = data & 0x80;
		if ( gamecom_dma.enabled ) 
		{
			gamecom_dma_init(space->machine);
		}
		break;
	case SM8521_DMX1:
		break;
	case SM8521_DMY1:
		break;
	case SM8521_DMDX:
		break;
	case SM8521_DMDY:
		break;
	case SM8521_DMX2:
		break;
	case SM8521_DMY2:
		break;
	case SM8521_DMPL:
		break;
	case SM8521_DMBR:
		break;
	case SM8521_DMVP:
		break;
	case SM8521_TM0D:
		gamecom_timer[0].check_value = data;
		break;
	case SM8521_TM0C:
		gamecom_timer[0].enabled = data & 0x80;
		gamecom_timer[0].state_limit = gamecom_timer_limit[data & 0x07];
		gamecom_timer[0].state_count = 0;
		gamecom_timer[0].counter = 0;
		break;
	case SM8521_TM1D:
		gamecom_timer[1].check_value = data;
		break;
	case SM8521_TM1C:
		gamecom_timer[1].enabled = data & 0x80;
		gamecom_timer[1].state_limit = gamecom_timer_limit[data & 0x07];
		gamecom_timer[1].state_count = 0;
		gamecom_timer[1].counter = 0;
		break;
	case SM8521_CLKT:	/* bit 6-7 */
		if ( data & 0x80 ) 
		{
			/* timer run */
			if ( data & 0x40 ) 
			{
				/* timer resolution 1 minute */
				timer_adjust_periodic(gamecom_clock_timer, ATTOTIME_IN_SEC(1), 0, ATTOTIME_IN_SEC(60));
			} 
			else 
			{
				/* TImer resolution 1 second */
				timer_adjust_periodic(gamecom_clock_timer, ATTOTIME_IN_SEC(1), 0, ATTOTIME_IN_SEC(1));
			}
		} 
		else 
		{
			/* disable timer reset */
			timer_enable( gamecom_clock_timer, 0 );
			data &= 0xC0;
		}
		break;

	/* Reserved addresses */
	case SM8521_18: case SM8521_1B:
	case SM8521_29: case SM8521_2A: case SM8521_2F:
	case SM8521_33: case SM8521_3E: case SM8521_3F:
	case SM8521_41: case SM8521_43: case SM8521_45: case SM8521_4B:
	case SM8521_4F:
	case SM8521_55: case SM8521_56: case SM8521_57: case SM8521_58:
	case SM8521_59: case SM8521_5A: case SM8521_5B: case SM8521_5C:
	case SM8521_5D:
		logerror( "%X: Write to reserved address (0x%02X). Value written: 0x%02X\n", cpu_get_pc(space->cpu), offset, data );
		break;
	}
	gamecom_internal_registers[offset] = data;
}

READ8_HANDLER( gamecom_internal_r )
{
	/* NPW 22-Dec-2008 - Shouldn't this entire function be moved to an internal memory map of the CPU core? */
	if ( offset >= 0x80 ) 
	{
		return internal_ram[offset];
	}

	switch( offset ) 
	{
	case SM8521_R0:  case SM8521_R1:  case SM8521_R2:  case SM8521_R3:
	case SM8521_R4:  case SM8521_R5:  case SM8521_R6:  case SM8521_R7:
	case SM8521_R8:  case SM8521_R9:  case SM8521_R10: case SM8521_R11:
	case SM8521_R12: case SM8521_R13: case SM8521_R14: case SM8521_R15:
		return 0;	/* NPW 22-Dec-2008 - Not sure what to do here */
		/*return sm85cpu_mem_readbyte( state, offset );*/
	case SM8521_IE0:	return cpu_get_reg( space->machine->cpu[0], SM8500_IE0 );
	case SM8521_IE1:	return cpu_get_reg( space->machine->cpu[0], SM8500_IE1 );
	case SM8521_IR0:	return cpu_get_reg( space->machine->cpu[0], SM8500_IR0 );
	case SM8521_IR1:	return cpu_get_reg( space->machine->cpu[0], SM8500_IR1 );
	case SM8521_P0:		/* logerror( "%X: Read from P0\n", cpu_get_pc(space->cpu) ); */ return cpu_get_reg( space->machine->cpu[0], SM8500_P0 );
	case SM8521_P1:		/* logerror( "%X: Read from P1\n", cpu_get_pc(space->cpu) ); */ return cpu_get_reg( space->machine->cpu[0], SM8500_P1 );
	case SM8521_P2:		/* logerror( "%X: Read from P2\n", cpu_get_pc(space->cpu) ); */ return cpu_get_reg( space->machine->cpu[0], SM8500_P2 );
	case SM8521_P3:		return cpu_get_reg( space->machine->cpu[0], SM8500_P3 );
	case SM8521_SYS:	return cpu_get_reg( space->machine->cpu[0], SM8500_SYS );
	case SM8521_CKC:	return cpu_get_reg( space->machine->cpu[0], SM8500_CKC );
	case SM8521_SPH:	return cpu_get_reg( space->machine->cpu[0], SM8500_SPH );
	case SM8521_SPL:	return cpu_get_reg( space->machine->cpu[0], SM8500_SPL );
	case SM8521_PS0:	return cpu_get_reg( space->machine->cpu[0], SM8500_PS0 );
	case SM8521_PS1:	return cpu_get_reg( space->machine->cpu[0], SM8500_PS1 );
	case SM8521_P0C:	return cpu_get_reg( space->machine->cpu[0], SM8500_P0C );
	case SM8521_P1C:	return cpu_get_reg( space->machine->cpu[0], SM8500_P1C );
	case SM8521_P2C:	return cpu_get_reg( space->machine->cpu[0], SM8500_P2C );
	case SM8521_P3C:	return cpu_get_reg( space->machine->cpu[0], SM8500_P3C );
	case SM8521_TM0D:	return gamecom_timer[0].counter;
	case SM8521_TM1D:	return gamecom_timer[1].counter;
//	case SM8521_CLKT:	/* bit 0-5 read only, 6-7 read/write */
//			return ( gamecom_internal_registers[offset] & 0xC0 ) | ( clock_timer_val & 0x3F );

	/* Reserved addresses */
	case SM8521_18: case SM8521_1B:
	case SM8521_29: case SM8521_2A: case SM8521_2F:
	case SM8521_33: case SM8521_3E: case SM8521_3F:
	case SM8521_41: case SM8521_43: case SM8521_45: case SM8521_4B:
	case SM8521_4F:
	case SM8521_55: case SM8521_56: case SM8521_57: case SM8521_58:
	case SM8521_59: case SM8521_5A: case SM8521_5B: case SM8521_5C:
	case SM8521_5D:
//		logerror( "%X: Read from reserved address (0x%02X)\n", cpu_get_pc(space->cpu), offset );
		break;

	}
	return gamecom_internal_registers[offset];
}

/* The manual is not conclusive as to which bit of the DMVP register (offset 0x3D) determines
   which page for source or destination is used */
static void gamecom_dma_init(running_machine *machine) 
{
	if ( gamecom_dma.decrement_x || gamecom_dma.decrement_y ) 
	{
		logerror( "TODO: Decrement-x and decrement-y are not supported yet\n" );
	}
	gamecom_dma.width_x = gamecom_internal_registers[SM8521_DMDX];
	gamecom_dma.width_x_count = 0;
	gamecom_dma.width_y = gamecom_internal_registers[SM8521_DMDY];
	gamecom_dma.width_y_count = 0;
	gamecom_dma.source_x = gamecom_internal_registers[SM8521_DMX1];
	gamecom_dma.source_x_current = gamecom_dma.source_x;
	gamecom_dma.source_y = gamecom_internal_registers[SM8521_DMY1];
	gamecom_dma.source_width = ( gamecom_internal_registers[SM8521_LCH] & 0x20 ) ? 50 : 40;
	gamecom_dma.dest_x = gamecom_internal_registers[SM8521_DMX2];
	gamecom_dma.dest_x_current = gamecom_dma.dest_x;
	gamecom_dma.dest_y = gamecom_internal_registers[SM8521_DMY2];
	gamecom_dma.dest_width = ( gamecom_internal_registers[SM8521_LCH] & 0x20 ) ? 50 : 40;
	gamecom_dma.palette[0] = ( gamecom_internal_registers[SM8521_DMPL] & 0x03 );
	gamecom_dma.palette[1] = ( gamecom_internal_registers[SM8521_DMPL] & 0x0C ) >> 2;
	gamecom_dma.palette[2] = ( gamecom_internal_registers[SM8521_DMPL] & 0x30 ) >> 4;
	gamecom_dma.palette[3] = ( gamecom_internal_registers[SM8521_DMPL] & 0xC0 ) >> 6;
	gamecom_dma.source_mask = 0x1FFF;
	gamecom_dma.dest_mask = 0x1FFF;
//	logerror("DMA: width %Xx%X, source (%X,%X), dest (%X,%X), transfer_mode %X, banks %X \n", gamecom_dma.width_x, gamecom_dma.width_y, gamecom_dma.source_x, gamecom_dma.source_y, gamecom_dma.dest_x, gamecom_dma.dest_y, gamecom_dma.transfer_mode, gamecom_internal_registers[SM8521_DMVP] );
//	logerror( "   Palette: %d, %d, %d, %d\n", gamecom_dma.palette[0], gamecom_dma.palette[1], gamecom_dma.palette[2], gamecom_dma.palette[3] );
	switch( gamecom_dma.transfer_mode ) 
	{
	case 0x00:
		/* VRAM->VRAM */
		gamecom_dma.source_bank = &gamecom_vram[(gamecom_internal_registers[SM8521_DMVP] & 0x01) ? 0x2000 : 0x0000];
		gamecom_dma.dest_bank = &gamecom_vram[(gamecom_internal_registers[SM8521_DMVP] & 0x02) ? 0x2000 : 0x0000];
		break;
	case 0x02:
		/* ROM->VRAM */
//		logerror( "DMA DMBR = %X\n", gamecom_internal_registers[SM8521_DMBR] );
		gamecom_dma.source_width = 64;
		if ( gamecom_internal_registers[SM8521_DMBR] < 16 ) 
		{
			gamecom_dma.source_bank = memory_region(machine, "user1") + (gamecom_internal_registers[SM8521_DMBR] << 14);
			gamecom_dma.source_mask = 0x3FFF;
		} 
		else 
		{
			logerror( "TODO: Reading from external ROMs not supported yet\n" );
			gamecom_dma.source_bank = memory_region(machine, "user1");
		}
		gamecom_dma.dest_bank = &gamecom_vram[(gamecom_internal_registers[SM8521_DMVP] & 0x02) ? 0x2000 : 0x0000];
		break;
	case 0x04:
		/* Extend RAM->VRAM */
		gamecom_dma.source_width = 64;
		gamecom_dma.source_bank = memory_get_read_ptr(cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM),  0xE000 );
		gamecom_dma.dest_bank = &gamecom_vram[(gamecom_internal_registers[SM8521_DMVP] & 0x02) ? 0x2000 : 0x0000];
		break;
	case 0x06:
		/* VRAM->Extend RAM */
		gamecom_dma.source_bank = &gamecom_vram[(gamecom_internal_registers[SM8521_DMVP] & 0x01) ? 0x2000 : 0x0000];
		gamecom_dma.dest_width = 64;
		gamecom_dma.dest_bank = memory_get_read_ptr(cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM), 0xE000 );
		break;
	}
	gamecom_dma.source_current = gamecom_dma.source_width * gamecom_dma.source_y;
	gamecom_dma.source_current += gamecom_dma.source_x >> 2;
	gamecom_dma.dest_current = gamecom_dma.dest_width * gamecom_dma.dest_y;
	gamecom_dma.dest_current += gamecom_dma.dest_x >> 2;
	gamecom_dma.source_line = gamecom_dma.source_current;
	gamecom_dma.dest_line = gamecom_dma.dest_current;
	gamecom_dma.state_count = 0;
}

/* For now the increment/decrement-x and increment/decrement-y parts are NOT supported.
   Their usage is also not explained properly in the manuals. Guess we'll have to wait
   for them to show up in some rom images...
 */
void gamecom_handle_dma( const device_config *device, int cycles ) 
{
	unsigned y_count, x_count;
	/* If not enabled, ignore */
	if ( ! gamecom_dma.enabled ) 
	{
		return;
	}
	for( y_count = 0; y_count <= gamecom_dma.width_y; y_count++ ) 
	{
		for( x_count = 0; x_count <= gamecom_dma.width_x; x_count++ ) 
		{
			int source_pixel = 0;
			int dest_pixel = 0;
			int src_addr = gamecom_dma.source_current & gamecom_dma.source_mask;
			int dest_addr = gamecom_dma.dest_current & gamecom_dma.dest_mask;
			/* handle DMA for 1 pixel */
			/* Read pixel data */
			switch ( gamecom_dma.source_x_current & 0x03 ) 
			{
			case 0x00: source_pixel = ( gamecom_dma.source_bank[src_addr] & 0xC0 ) >> 6; break;
			case 0x01: source_pixel = ( gamecom_dma.source_bank[src_addr] & 0x30 ) >> 4; break;
			case 0x02: source_pixel = ( gamecom_dma.source_bank[src_addr] & 0x0C ) >> 2; break;
			case 0x03: source_pixel = ( gamecom_dma.source_bank[src_addr] & 0x03 );      break;
			}

			if ( !gamecom_dma.overwrite_mode && source_pixel == 0 ) 
			{
				switch ( gamecom_dma.dest_x_current & 0x03 ) 
				{
				case 0x00: dest_pixel = ( gamecom_dma.dest_bank[dest_addr] & 0xC0 ) >> 6; break;
				case 0x01: dest_pixel = ( gamecom_dma.dest_bank[dest_addr] & 0x30 ) >> 4; break;
				case 0x02: dest_pixel = ( gamecom_dma.dest_bank[dest_addr] & 0x0C ) >> 2; break;
				case 0x03: dest_pixel = ( gamecom_dma.dest_bank[dest_addr] & 0x03 );      break;
				}
				source_pixel = dest_pixel;
			}

			/* Translate pixel data using DMA palette. */
			/* Not sure if this should be done before the compound stuff - WP */
			source_pixel = gamecom_dma.palette[ source_pixel ];
			/* Write pixel data */
			switch( gamecom_dma.dest_x_current & 0x03 ) 
			{
			case 0x00:
				gamecom_dma.dest_bank[dest_addr] = ( gamecom_dma.dest_bank[dest_addr] & 0x3F ) | ( source_pixel << 6 );
				break;
			case 0x01:
				gamecom_dma.dest_bank[dest_addr] = ( gamecom_dma.dest_bank[dest_addr] & 0xCF ) | ( source_pixel << 4 );
				break;
			case 0x02:
				gamecom_dma.dest_bank[dest_addr] = ( gamecom_dma.dest_bank[dest_addr] & 0xF3 ) | ( source_pixel << 2 );
				break;
			case 0x03:
				gamecom_dma.dest_bank[dest_addr] = ( gamecom_dma.dest_bank[dest_addr] & 0xFC ) | source_pixel;
				break;
			}

			/* Advance a pixel */
			if ( gamecom_dma.decrement_x ) 
			{
				gamecom_dma.source_x_current--;
				if ( ( gamecom_dma.source_x_current & 0x03 ) == 0x03 ) 
				{
					gamecom_dma.source_current--;
				}
			} 
			else 
			{
				gamecom_dma.source_x_current++;
				if ( ( gamecom_dma.source_x_current & 0x03 ) == 0x00 ) 
				{
					gamecom_dma.source_current++;
				}
			}
			gamecom_dma.dest_x_current++;
			if ( ( gamecom_dma.dest_x_current & 0x03 ) == 0x00 ) 
			{
				gamecom_dma.dest_current++;
			}
		}

		/* Advance a line */
		gamecom_dma.source_x_current = gamecom_dma.source_x;
		gamecom_dma.dest_x_current = gamecom_dma.dest_x;
		gamecom_dma.source_line += gamecom_dma.source_width;
		gamecom_dma.source_current = gamecom_dma.source_line;
		gamecom_dma.dest_line += gamecom_dma.dest_width;
		gamecom_dma.dest_current = gamecom_dma.dest_line;
	}
	gamecom_dma.enabled = 0;
	cpu_set_input_line(device->machine->cpu[0], DMA_INT, HOLD_LINE );
}

void gamecom_update_timers( const device_config *device, int cycles ) 
{
	if ( gamecom_timer[0].enabled ) 
	{
		gamecom_timer[0].state_count += cycles;
		while ( gamecom_timer[0].state_count >= gamecom_timer[0].state_limit ) 
		{
			gamecom_timer[0].state_count -= gamecom_timer[0].state_limit;
			gamecom_timer[0].counter++;
			if ( gamecom_timer[0].counter == gamecom_timer[0].check_value ) 
			{
				gamecom_timer[0].counter = 0;
				cpu_set_input_line(device->machine->cpu[0], TIM0_INT, HOLD_LINE );
			}
		}
	}
	if ( gamecom_timer[1].enabled ) 
	{
		gamecom_timer[1].state_count += cycles;
		while ( gamecom_timer[1].state_count >= gamecom_timer[1].state_limit ) 
		{
			gamecom_timer[1].state_count -= gamecom_timer[1].state_limit;
			gamecom_timer[1].counter++;
			if ( gamecom_timer[1].counter == gamecom_timer[1].check_value ) 
			{
				gamecom_timer[1].counter = 0;
				cpu_set_input_line(device->machine->cpu[0], TIM1_INT, HOLD_LINE );
			}
		}
	}
}

WRITE8_HANDLER( gamecom_vram_w ) 
{
	gamecom_vram[offset] = data;
}

READ8_HANDLER( gamecom_vram_r ) 
{
	return gamecom_vram[offset];
}

DEVICE_START( gamecom_cart )
{
	internal_ram = device_get_info_ptr(device->machine->cpu[0], CPUINFO_PTR_SM8500_INTERNAL_RAM);
}

DEVICE_IMAGE_LOAD( gamecom_cart )
{
	int filesize;
	int load_offset = 0;

	/* allocate memory on first load of a cartridge */
	if ( cartridge1 == NULL ) 
	{
		cartridge1 = auto_malloc( 2048 * 1024 );
	}
	filesize = image_length( image );
	switch( filesize ) 
	{
	case 0x008000: load_offset = 0;        break;  /* 32 KB */
	case 0x040000: load_offset = 0;        break;  /* 256KB */
	case 0x080000: load_offset = 0;        break;  /* 512KB */
	case 0x100000: load_offset = 0;        break;  /* 1  MB */
	case 0x1c0000: load_offset = 0x040000; break;  /* 1.8MB */
	case 0x200000: load_offset = 0;        break;  /* 2  MB */
	default:                                       /* otherwise */
		logerror( "Error loading cartridge: Invalid file size.\n" );
		return INIT_FAIL;
	}
	if ( image_fread( image, cartridge1 + load_offset, filesize ) != filesize ) 
	{
		logerror( "Error loading cartridge: Unable to read from file: %s.\n", image_filename(image) );
		return INIT_FAIL;
	}
	if ( filesize < 0x010000 ) { memcpy( cartridge1 + 0x008000, cartridge1, 0x008000 ); } /* ->64KB */
	if ( filesize < 0x020000 ) { memcpy( cartridge1 + 0x010000, cartridge1, 0x010000 ); } /* ->128KB */
	if ( filesize < 0x040000 ) { memcpy( cartridge1 + 0x020000, cartridge1, 0x020000 ); } /* ->256KB */
	if ( filesize < 0x080000 ) { memcpy( cartridge1 + 0x040000, cartridge1, 0x040000 ); } /* ->512KB */
	if ( filesize < 0x100000 ) { memcpy( cartridge1 + 0x080000, cartridge1, 0x080000 ); } /* ->1MB */
	if ( filesize < 0x1c0000 ) { memcpy( cartridge1 + 0x100000, cartridge1, 0x100000 ); } /* -> >=1.8MB */
	return INIT_PASS;
}

