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

    Driver for Williams/Midway Wolf-unit games.

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

#include "driver.h"
#include "cpu/tms34010/tms34010.h"
#include "cpu/m6809/m6809.h"
#include "audio/dcs.h"
#include "midwunit.h"
#include "midwayic.h"


/* code-related variables */
       UINT8 *	midwunit_decode_memory;

/* CMOS-related variables */
static UINT8	cmos_write_enable;

/* I/O-related variables */
static UINT16	iodata[8];
static UINT8	ioshuffle[16];
static UINT8	midxunit_analog_port;

/* UART-related variables */
static UINT8	uart[8];
static UINT8	security_bits;

/* prototype */
static READ16_HANDLER( midwunit_sound_state_r );
static void midxunit_dcs_output_full(int state);



/*************************************
 *
 *  State saving
 *
 *************************************/

static void register_state_saving(void)
{
	state_save_register_global(cmos_write_enable);
	state_save_register_global_array(iodata);
	state_save_register_global_array(ioshuffle);
	state_save_register_global(midxunit_analog_port);
	state_save_register_global_array(uart);
	state_save_register_global(security_bits);
}



/*************************************
 *
 *  CMOS reads/writes
 *
 *************************************/

WRITE16_HANDLER( midwunit_cmos_enable_w )
{
	cmos_write_enable = 1;
}


WRITE16_HANDLER( midwunit_cmos_w )
{
	if (cmos_write_enable)
	{
		COMBINE_DATA(generic_nvram16+offset);
		cmos_write_enable = 0;
	}
	else
	{
		logerror("%08X:Unexpected CMOS W @ %05X\n", activecpu_get_pc(), offset);
		popmessage("Bad CMOS write");
	}
}


WRITE16_HANDLER( midxunit_cmos_w )
{
	COMBINE_DATA(generic_nvram16+offset);
}


READ16_HANDLER( midwunit_cmos_r )
{
	return generic_nvram16[offset];
}



/*************************************
 *
 *  General I/O writes
 *
 *************************************/

WRITE16_HANDLER( midwunit_io_w )
{
	int oldword, newword;

	offset %= 8;
	oldword = iodata[offset];
	newword = oldword;
	COMBINE_DATA(&newword);

	switch (offset)
	{
		case 1:
			logerror("%08X:Control W @ %05X = %04X\n", activecpu_get_pc(), offset, data);

			/* bit 4 reset sound CPU */
			dcs_reset_w(newword & 0x10);

			/* bit 5 (active low) reset security chip */
			midway_serial_pic_reset_w(newword & 0x20);
			break;

		case 3:
			/* watchdog reset */
			/* MK3 resets with this enabled */
/*          watchdog_reset_w(0,0);*/
			break;

		default:
			logerror("%08X:Unknown I/O write to %d = %04X\n", activecpu_get_pc(), offset, data);
			break;
	}
	iodata[offset] = newword;
}


WRITE16_HANDLER( midxunit_io_w )
{
	int oldword, newword;

	offset = (offset / 2) % 8;
	oldword = iodata[offset];
	newword = oldword;
	COMBINE_DATA(&newword);

	switch (offset)
	{
		case 2:
			/* watchdog reset */
//          watchdog_reset_w(0,0);
			break;

		default:
			logerror("%08X:I/O write to %d = %04X\n", activecpu_get_pc(), offset, data);
//          logerror("%08X:Unknown I/O write to %d = %04X\n", activecpu_get_pc(), offset, data);
			break;
	}
	iodata[offset] = newword;
}


WRITE16_HANDLER( midxunit_unknown_w )
{
	int offs = offset / 0x40000;
	if (ACCESSING_LSB && offset % 0x40000 == 0)
		logerror("%08X:midxunit_unknown_w @ %d = %02X\n", activecpu_get_pc(), offs, data & 0xff);
}


/*************************************
 *
 *  General I/O reads
 *
 *************************************/

READ16_HANDLER( midwunit_io_r )
{
	/* apply I/O shuffling */
	offset = ioshuffle[offset % 16];

	switch (offset)
	{
		case 0:
		case 1:
		case 2:
		case 3:
			return readinputport(offset);

		case 4:
			return (midway_serial_pic_status_r() << 12) | midwunit_sound_state_r(0,0);

		default:
			logerror("%08X:Unknown I/O read from %d\n", activecpu_get_pc(), offset);
			break;
	}
	return ~0;
}


READ16_HANDLER( midxunit_io_r )
{
	offset = (offset / 2) % 8;

	switch (offset)
	{
		case 0:
		case 1:
		case 2:
		case 3:
			return readinputport(offset);

		default:
			logerror("%08X:Unknown I/O read from %d\n", activecpu_get_pc(), offset);
			break;
	}
	return ~0;
}


READ16_HANDLER( midxunit_analog_r )
{
	return readinputport(midxunit_analog_port);
}


WRITE16_HANDLER( midxunit_analog_select_w )
{
	if (offset == 0 && ACCESSING_LSB)
		midxunit_analog_port = data - 8 + 4;
}


READ16_HANDLER( midxunit_status_r )
{
	/* low bit indicates whether the ADC is done reading the current input */
	return (midway_serial_pic_status_r() << 1) | 1;
}



/*************************************
 *
 *  Revolution X UART
 *
 *************************************/

void midxunit_dcs_output_full(int state)
{
	/* only signal if not in loopback state */
	if (uart[1] != 0x66)
		cpunum_set_input_line(1, 1, state ? ASSERT_LINE : CLEAR_LINE);
}


READ16_HANDLER( midxunit_uart_r )
{
	int result = 0;

	/* convert to a byte offset */
	if (offset & 1)
		return 0;
	offset /= 2;

	/* switch off the offset */
	switch (offset)
	{
		case 0:	/* register 0 must return 0x13 in order to pass the self test */
			result = 0x13;
			break;

		case 1:	/* register 1 contains the status */

			/* loopback case: data always ready, and always ok to send */
			if (uart[1] == 0x66)
				result |= 5;

			/* non-loopback case: bit 0 means data ready, bit 2 means ok to send */
			else
			{
				int temp = midwunit_sound_state_r(0, 0);
				result |= (temp & 0x800) >> 9;
				result |= (~temp & 0x400) >> 10;
				timer_call_after_resynch(NULL, 0, 0);
			}
			break;

		case 3:	/* register 3 contains the data read */

			/* loopback case: feed back last data wrtten */
			if (uart[1] == 0x66)
				result = uart[3];

			/* non-loopback case: read from the DCS system */
			else
				result = midwunit_sound_r(0, 0);
			break;

		case 5:	/* register 5 seems to be like 3, but with in/out swapped */

			/* loopback case: data always ready, and always ok to send */
			if (uart[1] == 0x66)
				result |= 5;

			/* non-loopback case: bit 0 means data ready, bit 2 means ok to send */
			else
			{
				int temp = midwunit_sound_state_r(0, 0);
				result |= (temp & 0x800) >> 11;
				result |= (~temp & 0x400) >> 8;
				timer_call_after_resynch(NULL, 0, 0);
			}
			break;

		default: /* everyone else reads themselves */
			result = uart[offset];
			break;
	}

/*  logerror("%08X:UART R @ %X = %02X\n", activecpu_get_pc(), offset, result);*/
	return result;
}


WRITE16_HANDLER( midxunit_uart_w )
{
	/* convert to a byte offset, ignoring MSB writes */
	if ((offset & 1) || !ACCESSING_LSB)
		return;
	offset /= 2;
	data &= 0xff;

	/* switch off the offset */
	switch (offset)
	{
		case 3:	/* register 3 contains the data to be sent */

			/* loopback case: don't feed through */
			if (uart[1] == 0x66)
				uart[3] = data;

			/* non-loopback case: send to the DCS system */
			else
				midwunit_sound_w(0, data, mem_mask);
			break;

		case 5:	/* register 5 write seems to reset things */
			dcs_data_r();
			break;

		default: /* everyone else just stores themselves */
			uart[offset] = data;
			break;
	}

/*  logerror("%08X:UART W @ %X = %02X\n", activecpu_get_pc(), offset, data);*/
}



/*************************************
 *
 *  Generic driver init
 *
 *************************************/

static void init_wunit_generic(void)
{
	UINT8 *base;
	int i, j;

	/* register for state saving */
	register_state_saving();

	/* load the graphics ROMs -- quadruples */
	midyunit_gfx_rom = base = memory_region(REGION_GFX1);
	for (i = 0; i < memory_region_length(REGION_GFX1) / 0x400000; i++)
	{
		memcpy(midwunit_decode_memory, base, 0x400000);
		for (j = 0; j < 0x100000; j++)
		{
			*base++ = midwunit_decode_memory[0x000000 + j];
			*base++ = midwunit_decode_memory[0x100000 + j];
			*base++ = midwunit_decode_memory[0x200000 + j];
			*base++ = midwunit_decode_memory[0x300000 + j];
		}
	}

	/* init sound */
	dcs_init();
}




/*************************************
 *
 *  Wolf-unit init (DCS)
 *
 *  music: ADSP2101
 *
 *************************************/

/********************** Mortal Kombat 3 **********************/

static void init_mk3_common(void)
{
	/* common init */
	init_wunit_generic();

	/* serial prefixes 439, 528 */
	midway_serial_pic_init(528);
}

DRIVER_INIT( mk3 )
{
	init_mk3_common();
}

DRIVER_INIT( mk3r20 )
{
	init_mk3_common();
}

DRIVER_INIT( mk3r10 )
{
	init_mk3_common();
}

DRIVER_INIT( umk3 )
{
	init_mk3_common();
}

DRIVER_INIT( umk3r11 )
{
	init_mk3_common();
}


/********************** 2 On 2 Open Ice Challenge **********************/

DRIVER_INIT( openice )
{
	/* common init */
	init_wunit_generic();

	/* serial prefixes 438, 528 */
	midway_serial_pic_init(528);
}


/********************** NBA Hangtime & NBA Maximum Hangtime **********************/

DRIVER_INIT( nbahangt )
{
	/* common init */
	init_wunit_generic();

	/* serial prefixes 459, 470, 528 */
	midway_serial_pic_init(528);
}


/********************** WWF Wrestlemania **********************/

static WRITE16_HANDLER( wwfmania_io_0_w )
{
	int i;

	/* start with the originals */
	for (i = 0; i < 16; i++)
		ioshuffle[i] = i % 8;

	/* based on the data written, shuffle */
	switch (data)
	{
		case 0:
			break;

		case 1:
			ioshuffle[4] = 0;
			ioshuffle[8] = 1;
			ioshuffle[1] = 2;
			ioshuffle[9] = 3;
			ioshuffle[2] = 4;
			break;

		case 2:
			ioshuffle[8] = 0;
			ioshuffle[2] = 1;
			ioshuffle[4] = 2;
			ioshuffle[6] = 3;
			ioshuffle[1] = 4;
			break;

		case 3:
			ioshuffle[1] = 0;
			ioshuffle[8] = 1;
			ioshuffle[2] = 2;
			ioshuffle[10] = 3;
			ioshuffle[5] = 4;
			break;

		case 4:
			ioshuffle[2] = 0;
			ioshuffle[4] = 1;
			ioshuffle[1] = 2;
			ioshuffle[7] = 3;
			ioshuffle[8] = 4;
			break;
	}
	logerror("Changed I/O swiching to %d\n", data);
}

DRIVER_INIT( wwfmania )
{
	/* common init */
	init_wunit_generic();

	/* enable I/O shuffling */
	memory_install_write16_handler(0, ADDRESS_SPACE_PROGRAM, 0x01800000, 0x0180000f, 0, 0, wwfmania_io_0_w);

	/* serial prefixes 430, 528 */
	midway_serial_pic_init(528);
}


/********************** Rampage World Tour **********************/

DRIVER_INIT( rmpgwt )
{
	/* common init */
	init_wunit_generic();

	/* serial prefixes 465, 528 */
	midway_serial_pic_init(528);
}


/********************** Revolution X **********************/

DRIVER_INIT( revx )
{
	UINT8 *base;
	int i, j;

	/* register for state saving */
	register_state_saving();

	/* load the graphics ROMs -- quadruples */
	midyunit_gfx_rom = base = memory_region(REGION_GFX1);
	for (i = 0; i < memory_region_length(REGION_GFX1) / 0x200000; i++)
	{
		memcpy(midwunit_decode_memory, base, 0x200000);
		for (j = 0; j < 0x80000; j++)
		{
			*base++ = midwunit_decode_memory[0x000000 + j];
			*base++ = midwunit_decode_memory[0x080000 + j];
			*base++ = midwunit_decode_memory[0x100000 + j];
			*base++ = midwunit_decode_memory[0x180000 + j];
		}
	}

	/* init sound */
	dcs_init();

	/* serial prefixes 419, 420 */
	midway_serial_pic_init(419);
}



/*************************************
 *
 *  Machine init
 *
 *************************************/

MACHINE_RESET( midwunit )
{
	int i;

	/* reset sound */
	dcs_reset_w(1);
	dcs_reset_w(0);

	/* reset I/O shuffling */
	for (i = 0; i < 16; i++)
		ioshuffle[i] = i % 8;
}


MACHINE_RESET( midxunit )
{
	machine_reset_midwunit(machine);
	dcs_set_io_callbacks(midxunit_dcs_output_full, NULL);
}



/*************************************
 *
 *  Security chip I/O
 *
 *************************************/

READ16_HANDLER( midwunit_security_r )
{
	return midway_serial_pic_r();
}


WRITE16_HANDLER( midwunit_security_w )
{
	if (offset == 0 && ACCESSING_LSB)
		midway_serial_pic_w(data);
}


WRITE16_HANDLER( midxunit_security_w )
{
	if (ACCESSING_LSB)
		security_bits = data & 0x0f;
}


WRITE16_HANDLER( midxunit_security_clock_w )
{
	if (offset == 0 && ACCESSING_LSB)
		midway_serial_pic_w(((~data & 2) << 3) | security_bits);
}



/*************************************
 *
 *  Sound write handlers
 *
 *************************************/

READ16_HANDLER( midwunit_sound_r )
{
	logerror("%08X:Sound read\n", activecpu_get_pc());

	return dcs_data_r() & 0xff;
}


READ16_HANDLER( midwunit_sound_state_r )
{
	return dcs_control_r();
}


WRITE16_HANDLER( midwunit_sound_w )
{
	/* check for out-of-bounds accesses */
	if (offset)
	{
		logerror("%08X:Unexpected write to sound (hi) = %04X\n", activecpu_get_pc(), data);
		return;
	}

	/* call through based on the sound type */
	if (ACCESSING_LSB)
	{
		logerror("%08X:Sound write = %04X\n", activecpu_get_pc(), data);
		dcs_data_w(data & 0xff);
	}
}
