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

    ldpr8210.c

    Pioneer PR-8210 laserdisc emulation.

****************************************************************************

    Copyright Aaron Giles
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in
          the documentation and/or other materials provided with the
          distribution.
        * Neither the name 'MAME' nor the names of its contributors may be
          used to endorse or promote products derived from this software
          without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

**************************************************************************

    Still to do:

        * implement SLOW TRG
        * figure out Simutrek without jump hack
        * figure out serial protocol issues (current hack works nicely)
        * determine actual slow/fast speeds

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

#define ADDRESS_MAP_MODERN

#include "emu.h"
#include "ldpr8210.h"



//**************************************************************************
//  DEBUGGING
//**************************************************************************

#define LOG_VBLANK_VBI				0
#define LOG_SERIAL					0
#define LOG_SIMUTREK				0



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

// Overlay constants, related to 720-pixel wide capture
#define OVERLAY_GROUP0_X				(82.0f / 720.0f)
#define OVERLAY_GROUP1_X				(162.0f / 720.0f)
#define OVERLAY_GROUP2_X				(322.0f / 720.0f)
#define OVERLAY_GROUP3_X				(483.0f / 720.0f)
#define OVERLAY_Y						(104/2)
#define OVERLAY_PIXEL_WIDTH				(4.5f / 720.0f)
#define OVERLAY_PIXEL_HEIGHT			2
#define OVERLAY_X_PIXELS				5
#define OVERLAY_Y_PIXELS				7

// scanning speeds
#define SCAN_SPEED						(2000 / 30)			// 2000 frames/second
#define SEEK_FAST_SPEED					(4000 / 30)			// 4000 frames/second

// serial timing, mostly from the service manual, derived from the XTAL
#define SERIAL_CLOCK					XTAL_455kHz
#define SERIAL_0_BIT_TIME				attotime::from_hz((double)SERIAL_CLOCK / 512)
#define SERIAL_1_BIT_TIME				attotime::from_hz((double)SERIAL_CLOCK / 1024)
#define SERIAL_MIDPOINT_TIME			attotime::from_hz((double)SERIAL_CLOCK / 600)
#define SERIAL_MAX_BIT_TIME				attotime::from_hz((double)SERIAL_CLOCK / 4096)
#define SERIAL_MAX_WORD_TIME			attotime::from_hz((double)SERIAL_CLOCK / 11520)
#define SERIAL_REJECT_DUPLICATE_TIME	attotime::from_hz((double)SERIAL_CLOCK / 11520 / 4)



//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

// devices
const device_type PIONEER_PR8210 = &device_creator<pioneer_pr8210_device>;
const device_type SIMUTREK_SPECIAL = &device_creator<simutrek_special_device>;


// bitmaps for the characters
static const UINT8 text_bitmap[0x40][7] =
{
	{ 0 },									// @
	{ 0x20,0x50,0x88,0x88,0xf8,0x88,0x88 },	// A
	{ 0 },									// B
	{ 0x70,0x88,0x80,0x80,0x80,0x88,0x70 },	// C
	{ 0 },									// D
	{ 0xf8,0x80,0x80,0xf0,0x80,0x80,0xf8 },	// E
	{ 0xf8,0x80,0x80,0xf0,0x80,0x80,0x80 },	// F
	{ 0 },									// G
	{ 0x88,0x88,0x88,0xf8,0x88,0x88,0x88 },	// H
	{ 0 },									// I
	{ 0 },									// J
	{ 0 },									// K
	{ 0 },									// L
	{ 0x88,0xd8,0xa8,0xa8,0xa8,0x88,0x88 },	// M
	{ 0 },									// N
	{ 0 },									// O
	{ 0xf0,0x88,0x88,0xf0,0x80,0x80,0x80 },	// P
	{ 0 },									// Q
	{ 0xf0,0x88,0x88,0xf0,0xa0,0x90,0x88 },	// R
	{ 0x70,0x88,0x80,0x70,0x08,0x88,0x70 },	// S
	{ 0 },									// T
	{ 0 },									// U
	{ 0 },									// V
	{ 0 },									// W
	{ 0 },									// X
	{ 0 },									// Y
	{ 0 },									// Z
	{ 0 },									// [
	{ 0 },									// <backslash>
	{ 0 },									// ]
	{ 0 },									// ^
	{ 0 },									// _

	{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00 },	// <space>
	{ 0 },									// !
	{ 0 },									// "
	{ 0 },									// #
	{ 0 },									// $
	{ 0 },									// %
	{ 0 },									// &
	{ 0 },									// '
	{ 0 },									// (
	{ 0 },									// )
	{ 0 },									// *
	{ 0 },									// +
	{ 0 },									// ,
	{ 0 },									// -
	{ 0x00,0x00,0x00,0x00,0x00,0x00,0x40 },	// .
	{ 0 },									// /
	{ 0x70,0x88,0x88,0x88,0x88,0x88,0x70 },	// 0
	{ 0x20,0x60,0x20,0x20,0x20,0x20,0x70 },	// 1
	{ 0x70,0x88,0x08,0x70,0x80,0x80,0xf8 },	// 2
	{ 0xf8,0x08,0x10,0x30,0x08,0x88,0x70 },	// 3
	{ 0x10,0x30,0x50,0x90,0xf8,0x10,0x10 },	// 4
	{ 0xf8,0x80,0xf0,0x08,0x08,0x88,0x70 },	// 5
	{ 0x78,0x80,0x80,0xf0,0x88,0x88,0x70 },	// 6
	{ 0xf8,0x08,0x08,0x10,0x20,0x40,0x80 },	// 7
	{ 0x70,0x88,0x88,0x70,0x88,0x88,0x70 },	// 8
	{ 0x70,0x88,0x88,0x78,0x08,0x08,0xf0 },	// 9
	{ 0 },									// :
	{ 0 },									// ;
	{ 0 },									// <
	{ 0 },									// =
	{ 0 },									// >
	{ 0 }									// ?
};



//**************************************************************************
//  PR-8210 ROM AND MACHINE INTERFACES
//**************************************************************************

static ADDRESS_MAP_START( pr8210_portmap, AS_IO, 8, pioneer_pr8210_device )
	AM_RANGE(0x00, 0xff) AM_READWRITE(i8049_pia_r, i8049_pia_w)
	AM_RANGE(MCS48_PORT_BUS, MCS48_PORT_BUS) AM_READ(i8049_bus_r)
	AM_RANGE(MCS48_PORT_P1, MCS48_PORT_P1) AM_WRITE(i8049_port1_w)
	AM_RANGE(MCS48_PORT_P2, MCS48_PORT_P2) AM_WRITE(i8049_port2_w)
	AM_RANGE(MCS48_PORT_T0, MCS48_PORT_T0) AM_READ(i8049_t0_r)
	AM_RANGE(MCS48_PORT_T1, MCS48_PORT_T1) AM_READ(i8049_t1_r)
ADDRESS_MAP_END


static MACHINE_CONFIG_FRAGMENT( pr8210 )
	MCFG_CPU_ADD("pr8210", I8049, XTAL_4_41MHz)
	MCFG_CPU_IO_MAP(pr8210_portmap)
MACHINE_CONFIG_END


ROM_START( pr8210 )
	ROM_REGION( 0x800, "pr8210", 0 )
	ROM_LOAD( "pr-8210_mcu_ud6005a.bin", 0x000, 0x800, CRC(120fa83b) SHA1(b514326ca1f52d6d89056868f9d17eabd4e3f31d) )
ROM_END



//**************************************************************************
//  PIONEER PR-8210 IMPLEMENTATION
//**************************************************************************

//-------------------------------------------------
//  pioneer_pr8210_device - constructor
//-------------------------------------------------

pioneer_pr8210_device::pioneer_pr8210_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: laserdisc_device(mconfig, PIONEER_PR8210, "Pioneer PR-8210", "pr8210", tag, owner, clock),
	  m_control(0),
	  m_lastcommand(0),
	  m_accumulator(0),
	  m_lastcommandtime(attotime::zero),
	  m_lastbittime(attotime::zero),
	  m_firstbittime(attotime::zero),
	  m_i8049_cpu(*this, "pr8210"),
	  m_slowtrg(attotime::zero),
	  m_vsync(false),
	  m_i8049_port1(0),
	  m_i8049_port2(0)
{
}

pioneer_pr8210_device::pioneer_pr8210_device(const machine_config &mconfig, device_type type, const char *name, const char *shortname, const char *tag, device_t *owner, UINT32 clock)
	: laserdisc_device(mconfig, type, name, shortname, tag, owner, clock),
	  m_control(0),
	  m_lastcommand(0),
	  m_accumulator(0),
	  m_lastcommandtime(attotime::zero),
	  m_lastbittime(attotime::zero),
	  m_firstbittime(attotime::zero),
	  m_i8049_cpu(*this, "pr8210"),
	  m_slowtrg(attotime::zero),
	  m_vsync(false),
	  m_i8049_port1(0),
	  m_i8049_port2(0)
{
}
	

//-------------------------------------------------
//  control_w - write callback when the CONTROL 
//  line is toggled
//-------------------------------------------------

void pioneer_pr8210_device::control_w(UINT8 data)
{
	// set the new value and remember the last
	UINT8 prev = m_control;
	m_control = data;
	
	// handle rising edge
	if (prev != ASSERT_LINE && data == ASSERT_LINE)
	{
		// get the time difference from the last assert
		// and update our internal command time
		attotime curtime = machine().time();
		attotime delta = curtime - m_lastbittime;
		m_lastbittime = curtime;

		// if we timed out since the first bit, reset the accumulator
		attotime overalldelta = curtime - m_firstbittime;
		if (overalldelta > SERIAL_MAX_WORD_TIME || delta > SERIAL_MAX_BIT_TIME)
		{
			m_firstbittime = curtime;
			m_accumulator = 0x5555;
			if (LOG_SERIAL)
				printf("Reset accumulator\n");
		}

		// 0 bit delta is 1.05 msec, 1 bit delta is 2.11 msec
		int longpulse = (delta < SERIAL_MIDPOINT_TIME) ? 0 : 1;
		m_accumulator = (m_accumulator << 1) | longpulse;

		// log the deltas for debugging
		if (LOG_SERIAL)
		{
			int usecdiff = (int)(delta.attoseconds / ATTOSECONDS_IN_USEC(1));
			printf("bitdelta = %5d (%d) - accum = %04X\n", usecdiff, longpulse, m_accumulator);
		}

		// if we have a complete command, signal it
		// a complete command is 0,0,1 followed by 5 bits, followed by 0,0
		if ((m_accumulator & 0x383) == 0x80)
		{
			// data is stored to the PIA in bit-reverse order
			UINT8 newcommand = (m_accumulator >> 2) & 0x1f;
			m_pia.porta = BITSWAP8(newcommand, 0,1,2,3,4,5,6,7);

			// the MCU logic requires a 0 to execute many commands; however, nobody
            // consistently sends a 0, whereas they do tend to send duplicate commands...
            // if we assume that each duplicate causes a 0, we get the correct results
			attotime rejectuntil = m_lastcommandtime + SERIAL_REJECT_DUPLICATE_TIME;
			m_lastcommandtime = curtime;
			if (m_pia.porta == m_lastcommand && curtime < rejectuntil)
				m_pia.porta = 0x00;
			else
				m_lastcommand = m_pia.porta;

			// log the command and wait for a keypress
			if (LOG_SERIAL)
				printf("--- Command = %02X\n", m_pia.porta >> 3);

			// reset the first bit time so that the accumulator clears on the next write
			m_firstbittime = curtime - SERIAL_MAX_WORD_TIME;
		}
	}
}


//-------------------------------------------------
//  device_start - device initialization
//-------------------------------------------------

void pioneer_pr8210_device::device_start()
{
	// pass through to the parent
	laserdisc_device::device_start();
}


//-------------------------------------------------
//  device_reset - device reset
//-------------------------------------------------

void pioneer_pr8210_device::device_reset()
{
	// pass through to the parent
	laserdisc_device::device_reset();

	// reset our state
	attotime curtime = machine().time();
	m_lastcommandtime = curtime;
	m_firstbittime = curtime;
	m_lastbittime = curtime;
	m_slowtrg = curtime;
}


//-------------------------------------------------
//  device_timer - handle timers set by this
//  device
//-------------------------------------------------

void pioneer_pr8210_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
		// update the VBI data in the PIA as soon as it is ready;
		// this must happen early in the frame because the player 
		// logic relies on fetching it here
		case TID_VBI_DATA_FETCH:

			// logging
			if (LOG_VBLANK_VBI)
			{
				UINT32 line1718 = get_field_code(LASERDISC_CODE_LINE1718, FALSE);
				if ((line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
					printf("%3d:VBI(%05d)\n", screen().vpos(), VBI_CAV_PICTURE(line1718));
				else
					printf("%3d:VBI()\n", screen().vpos());
			}

			// update PIA registers based on vbi code
			m_pia.vbi1 = 0xff;
			m_pia.vbi2 = 0xff;
			if (focus_on() && laser_on())
			{
				UINT32 line16 = get_field_code(LASERDISC_CODE_LINE16, FALSE);
				UINT32 line1718 = get_field_code(LASERDISC_CODE_LINE1718, FALSE);
				if (line1718 == VBI_CODE_LEADIN)
					m_pia.vbi1 &= ~0x01;
				if (line1718 == VBI_CODE_LEADOUT)
					m_pia.vbi1 &= ~0x02;
				if (line16 == VBI_CODE_STOP)
					m_pia.vbi1 &= ~0x04;
				// unsure what this bit means: m_pia.vbi1 &= ~0x08;
				if ((line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
				{
					m_pia.vbi1 &= ~0x10;
					m_pia.frame[2] = 0xf0 | ((line1718 >> 16) & 0x07);
					m_pia.frame[3] = 0xf0 | ((line1718 >> 12) & 0x0f);
					m_pia.frame[4] = 0xf0 | ((line1718 >>  8) & 0x0f);
					m_pia.frame[5] = 0xf0 | ((line1718 >>  4) & 0x0f);
					m_pia.frame[6] = 0xf0 | ((line1718 >>  0) & 0x0f);
				}
				if ((line1718 & VBI_MASK_CHAPTER) == VBI_CODE_CHAPTER)
				{
					m_pia.vbi2 &= ~0x01;
					m_pia.frame[0] = 0xf0 | ((line1718 >> 16) & 0x07);
					m_pia.frame[1] = 0xf0 | ((line1718 >> 12) & 0x0f);
				}
			}
			break;
		
		// clear the VSYNC flag
		case TID_VSYNC_OFF:
			m_vsync = false;
			break;
	
		// pass everything else onto the parent
		default:
			laserdisc_device::device_timer(timer, id, param, ptr);
			break;
	}
}


//-------------------------------------------------
//  device_rom_region - return a pointer to our
//  ROM region definitions
//-------------------------------------------------

const rom_entry *pioneer_pr8210_device::device_rom_region() const
{
	return ROM_NAME(pr8210);
}


//-------------------------------------------------
//  device_mconfig_additions - return a pointer to
//  our machine config fragment
//-------------------------------------------------

machine_config_constructor pioneer_pr8210_device::device_mconfig_additions() const
{
	return MACHINE_CONFIG_NAME(pr8210);
}


//-------------------------------------------------
//  player_vsync - VSYNC callback, called at the
//  start of the blanking period
//-------------------------------------------------

void pioneer_pr8210_device::player_vsync(const vbi_metadata &vbi, int fieldnum, attotime curtime)
{
	// logging
	if (LOG_VBLANK_VBI)
	{
		if ((vbi.line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
			printf("%3d:VSYNC(%d,%05d)\n", screen().vpos(), fieldnum, VBI_CAV_PICTURE(vbi.line1718));
		else
			printf("%3d:VSYNC(%d)\n", screen().vpos(), fieldnum);
	}

	// signal VSYNC and set a timer to turn it off
	m_vsync = true;
	timer_set(screen().scan_period() * 4, TID_VSYNC_OFF);

	// also set a timer to fetch the VBI data when it is ready
	timer_set(screen().time_until_pos(19*2), TID_VBI_DATA_FETCH);
}


//-------------------------------------------------
//  player_update - update callback, called on the 
//  first visible line of the frame
//-------------------------------------------------

INT32 pioneer_pr8210_device::player_update(const vbi_metadata &vbi, int fieldnum, attotime curtime)
{
	// logging
	if (LOG_VBLANK_VBI)
		printf("%3d:Update(%d)\n", screen().vpos(), fieldnum);

	// if the spindle is on, we advance by 1 track after completing field #1
	return spdl_on() ? fieldnum : 0;
}


//-------------------------------------------------
//  player_overlay - overlay callback, called
//  during frame processing in update to overlay
//  player data
//-------------------------------------------------

void pioneer_pr8210_device::player_overlay(bitmap_yuy16 &bitmap)
{
	// custom display
	if (m_pia.display)
	{
		overlay_draw_group(bitmap, &m_pia.text[2], 5, OVERLAY_GROUP1_X);
		overlay_draw_group(bitmap, &m_pia.text[7], 5, OVERLAY_GROUP2_X);
		overlay_draw_group(bitmap, &m_pia.text[12], 5, OVERLAY_GROUP3_X);
	}

	// chapter/frame display
	else
	{
		// frame display
		if (m_pia.latchdisplay & 2)
			overlay_draw_group(bitmap, &m_pia.text[2], 5, OVERLAY_GROUP1_X);

		// chapter overlay
		if (m_pia.latchdisplay & 1)
			overlay_draw_group(bitmap, &m_pia.text[0], 2, OVERLAY_GROUP0_X);
	}
	m_pia.latchdisplay = 0;
}


//-------------------------------------------------
//  i8049_pia_r - handle reads from the mystery
//  Pioneer PIA
//-------------------------------------------------

READ8_MEMBER( pioneer_pr8210_device::i8049_pia_r )
{
	UINT8 result = 0xff;
	switch (offset)
	{
		// (20-26) 7 characters for the chapter/frame
		case 0x20:	case 0x21:
		case 0x22:	case 0x23:	case 0x24:	case 0x25:	case 0x26:
			result = m_pia.frame[offset - 0x20];
			break;

		// (1D-1F,27) invalid read but normal
		case 0x1d:	case 0x1e:	case 0x1f:
		case 0x27:
			break;

		// (A0) port A value (from serial decoder)
		case 0xa0:
			result = m_pia.porta;
			break;

		// (C0) VBI decoding state 1
		case 0xc0:
			if (LOG_VBLANK_VBI)
				printf("%3d:PIA(C0)\n", screen().vpos());
			result = m_pia.vbi1;
			break;

		// (E0) VBI decoding state 2
		case 0xe0:
			if (LOG_VBLANK_VBI)
				printf("%3d:PIA(E0)\n", screen().vpos());
			result = m_pia.vbi2;
			break;

		default:
			mame_printf_debug("%03X:Unknown PR-8210 PIA read from offset %02X\n", cpu_get_pc(&space.device()), offset);
			break;
	}
	return result;
}


//-------------------------------------------------
//  i8049_pia_w - handle writes to the mystery
//  Pioneer PIA
//-------------------------------------------------

WRITE8_MEMBER( pioneer_pr8210_device::i8049_pia_w )
{
	UINT8 value;
	switch (offset)
	{
		// (20-30) 17 characters for the display
		case 0x20:	case 0x21:
		case 0x22:	case 0x23:	case 0x24:	case 0x25:	case 0x26:
		case 0x27:	case 0x28:	case 0x29:	case 0x2a:	case 0x2b:
		case 0x2c:	case 0x2d:	case 0x2e:	case 0x2f:	case 0x30:
			m_pia.text[offset - 0x20] = data;
			break;

		// (40) control lines
		case 0x40:

			// toggle bit 0 to latch chapter number into display area
			if (!(data & 0x01) && (m_pia.control & 0x01))
			{
				memcpy(&m_pia.text[0], &m_pia.frame[0], 2);
				m_pia.latchdisplay |= 1;
			}

			// toggle bit 1 to latch frame number into display area
			if (!(data & 0x02) && (m_pia.control & 0x02))
			{
				memcpy(&m_pia.text[2], &m_pia.frame[2], 5);
				m_pia.latchdisplay |= 2;
			}
			m_pia.control = data;
			break;

		// (60) port B value (LEDs)
		case 0x60:

			// these 4 are direct-connect
			output_set_value("pr8210_audio1", (data & 0x01) != 0);
			output_set_value("pr8210_audio2", (data & 0x02) != 0);
			output_set_value("pr8210_clv", (data & 0x04) != 0);
			output_set_value("pr8210_cav", (data & 0x08) != 0);

			// remaining 3 bits select one of 5 LEDs via a mux
			value = ((data & 0x40) >> 6) | ((data & 0x20) >> 4) | ((data & 0x10) >> 2);
			output_set_value("pr8210_srev", (value == 0));
			output_set_value("pr8210_sfwd", (value == 1));
			output_set_value("pr8210_play", (value == 2));
			output_set_value("pr8210_step", (value == 3));
			output_set_value("pr8210_pause", (value == 4));

			m_pia.portb = data;
			update_audio_squelch();
			break;

		// (80) display enable
		case 0x80:
			m_pia.display = data & 0x01;
			break;

		// no other writes known
		default:
			mame_printf_debug("%03X:Unknown PR-8210 PIA write to offset %02X = %02X\n", cpu_get_pc(&space.device()), offset, data);
			break;
	}
}


//-------------------------------------------------
//  i8049_bus_r - handle reads from the 8049 BUS
//  input, which is enabled via the PIA above
//-------------------------------------------------

READ8_MEMBER( pioneer_pr8210_device::i8049_bus_r )
{
	/*
       $80 = n/c
       $40 = (in) slider pot interrupt source (slider position limit detector, inside and outside)
       $20 = n/c
       $10 = (in) /FOCUS LOCK
       $08 = (in) /SPDL LOCK
       $04 = (in) SIZE 8/12
       $02 = (in) FG via op-amp (spindle motor stop detector)
       $01 = (in) SLOW TIMER OUT
	*/

	UINT8 result = 0x00;

	// bus bit 6: slider position limit detector, inside and outside
	slider_position sliderpos = get_slider_position();
	if (sliderpos != SLIDER_MINIMUM && sliderpos != SLIDER_MAXIMUM)
		result |= 0x40;

	// bus bit 4: /FOCUS LOCK
	if (!focus_on())
		result |= 0x10;

	// bus bit 3: /SPDL LOCK
	if (!spdl_on())
		result |= 0x08;

	// bus bit 1: spindle motor stop detector
	if (!spdl_on())
		result |= 0x02;

	// bus bit 0: SLOW TIMER OUT

	// loop at beginning waits for $40=0, $02=1
	return result;
}


//-------------------------------------------------
//  i8049_port1_w - handle writes to the 8049
//  port #1
//-------------------------------------------------

WRITE8_MEMBER( pioneer_pr8210_device::i8049_port1_w )
{
	/*
       $80 = (out) SCAN C (F/R)
       $40 = (out) AUDIO SQ
       $20 = (out) VIDEO SQ
       $10 = (out) /SPDL ON
       $08 = (out) /FOCUS ON
       $04 = (out) SCAN B (L/H)
       $02 = (out) SCAN A (/SCAN)
       $01 = (out) JUMP TRG (jump back trigger, clock on high->low)
    */
    
	// set the new value
	UINT8 prev = m_i8049_port1;
	m_i8049_port1 = data;

	// bit 7 selects the direction of slider movement for JUMP TRG and scanning
	int direction = (data & 0x80) ? 1 : -1;

	// on the falling edge of bit 0, jump one track in either direction
	if (!(data & 0x01) && (prev & 0x01))
	{
		// special override for the Simutrek, which takes over control of this is some situations
		if (!override_control())
		{
			if (LOG_SIMUTREK)
				printf("%3d:JUMP TRG\n", screen().vpos());
			advance_slider(direction);
		}
		else if (LOG_SIMUTREK)
			printf("%3d:Skipped JUMP TRG\n", screen().vpos());
	}

	// bit 1 low enables scanning
	if (!(data & 0x02))
	{
		// bit 2 selects the speed
		int delta = (data & 0x04) ? SCAN_SPEED : SEEK_FAST_SPEED;
		set_slider_speed(delta * direction);
	}

	// bit 1 high stops scanning
	else
		set_slider_speed(0);

	// video squelch is controlled by bit 5; audio squelch is controlled by bit 6
	update_video_squelch();
	update_audio_squelch();
}


//-------------------------------------------------
//  i8049_port2_w - handle writes to the 8049
//  port #2
//-------------------------------------------------

WRITE8_MEMBER( pioneer_pr8210_device::i8049_port2_w )
{
	/*
       $80 = (out) /CS on PIA
       $40 = (out) 0 to self-generate IRQ
       $20 = (out) SLOW TRG
       $10 = (out) STANDBY LED
       $08 = (out) TP2
       $04 = (out) TP1
       $02 = (out) ???
       $01 = (out) LASER ON
    */

	// set the new value
	UINT8 prev = m_i8049_port2;
	m_i8049_port2 = data;

	// on the falling edge of bit 5, start the slow timer
	if (!(data & 0x20) && (prev & 0x20))
		m_slowtrg = machine().time();

	// bit 6 when low triggers an IRQ on the MCU
	m_i8049_cpu->set_input_line(MCS48_INPUT_IRQ, (data & 0x40) ? CLEAR_LINE : ASSERT_LINE);

	// standby LED is set accordingl to bit 4
	output_set_value("pr8210_standby", (data & 0x10) != 0);
}


//-------------------------------------------------
//  i8049_t0_r - return the state of the 8049
//  T0 input (connected to VSYNC)
//-------------------------------------------------

READ8_MEMBER( pioneer_pr8210_device::i8049_t0_r )
{
	// returns VSYNC state
	return !m_vsync;
}


//-------------------------------------------------
//  i8049_t1_r - return the state of the 8049
//  T1 input (pulled high)
//-------------------------------------------------

READ8_MEMBER( pioneer_pr8210_device::i8049_t1_r )
{
	return 1;
}


//-------------------------------------------------
//  overlay_draw_group - draw a single group of
//  characters
//-------------------------------------------------

void pioneer_pr8210_device::overlay_draw_group(bitmap_yuy16 &bitmap, const UINT8 *text, int count, float xstart)
{
	// rease the background
	overlay_erase(bitmap, xstart, xstart + ((OVERLAY_X_PIXELS + 1) * count + 1) * OVERLAY_PIXEL_WIDTH);

	// draw each character, suppressing leading 0's
	bool skip = true;
	for (int x = 0; x < count; x++)
		if (!skip || x == count - 1 || (text[x] & 0x3f) != 0x30)
		{
			skip = false;
			overlay_draw_char(bitmap, text[x], xstart + ((OVERLAY_X_PIXELS + 1) * x + 1) * OVERLAY_PIXEL_WIDTH);
		}
}


//-------------------------------------------------
//  overlay_erase - erase the background area
//  where the text overlay will be displayed
//-------------------------------------------------

void pioneer_pr8210_device::overlay_erase(bitmap_yuy16 &bitmap, float xstart, float xend)
{
	UINT32 xmin = (UINT32)(xstart * 256.0f * float(bitmap.width()));
	UINT32 xmax = (UINT32)(xend * 256.0f * float(bitmap.width()));

	for (UINT32 y = OVERLAY_Y; y < (OVERLAY_Y + (OVERLAY_Y_PIXELS + 2) * OVERLAY_PIXEL_HEIGHT); y++)
	{
		UINT16 *dest = &bitmap.pix16(y, xmin >> 8);
		UINT16 ymax = *dest >> 8;
		UINT16 ymin = ymax * 3 / 8;
		UINT16 yres = ymin + ((ymax - ymin) * (xmin & 0xff)) / 256;
		*dest = (yres << 8) | (*dest & 0xff);
		dest++;

		for (UINT32 x = (xmin | 0xff) + 1; x < xmax; x += 0x100)
		{
			yres = (*dest >> 8) * 3 / 8;
			*dest = (yres << 8) | (*dest & 0xff);
			dest++;
		}

		ymax = *dest >> 8;
		ymin = ymax * 3 / 8;
		yres = ymin + ((ymax - ymin) * (~xmax & 0xff)) / 256;
		*dest = (yres << 8) | (*dest & 0xff);
		dest++;
	}
}


//-------------------------------------------------
//  overlay_draw_char - draw a single character
//  of the text overlay
//-------------------------------------------------

void pioneer_pr8210_device::overlay_draw_char(bitmap_yuy16 &bitmap, UINT8 ch, float xstart)
{
	UINT32 xminbase = (UINT32)(xstart * 256.0f * float(bitmap.width()));
	UINT32 xsize = (UINT32)(OVERLAY_PIXEL_WIDTH * 256.0f * float(bitmap.width()));

	// iterate over pixels
	const UINT8 *chdataptr = &text_bitmap[ch & 0x3f][0];
	for (UINT32 y = 0; y < OVERLAY_Y_PIXELS; y++)
	{
		UINT8 chdata = *chdataptr++;

		for (UINT32 x = 0; x < OVERLAY_X_PIXELS; x++, chdata <<= 1)
			if (chdata & 0x80)
			{
				UINT32 xmin = xminbase + x * xsize;
				UINT32 xmax = xmin + xsize;
				for (UINT32 yy = 0; yy < OVERLAY_PIXEL_HEIGHT; yy++)
				{
					UINT16 *dest = &bitmap.pix16(OVERLAY_Y + (y + 1) * OVERLAY_PIXEL_HEIGHT + yy, xmin >> 8);
					UINT16 ymax = 0xff;
					UINT16 ymin = *dest >> 8;
					UINT16 yres = ymin + ((ymax - ymin) * (~xmin & 0xff)) / 256;
					*dest = (yres << 8) | (*dest & 0xff);
					dest++;

					for (UINT32 xx = (xmin | 0xff) + 1; xx < xmax; xx += 0x100)
						*dest++ = 0xf080;

					ymax = 0xff;
					ymin = *dest >> 8;
					yres = ymin + ((ymax - ymin) * (xmax & 0xff)) / 256;
					*dest = (yres << 8) | (*dest & 0xff);
					dest++;
				}
			}
	}
}



//**************************************************************************
//  SIMUTREK ROM AND MACHINE INTERFACES
//**************************************************************************

static ADDRESS_MAP_START( simutrek_portmap, AS_IO, 8, simutrek_special_device )
	AM_RANGE(0x00, 0xff) AM_READ(i8748_data_r)
	AM_RANGE(MCS48_PORT_P2, MCS48_PORT_P2) AM_READWRITE(i8748_port2_r, i8748_port2_w)
	AM_RANGE(MCS48_PORT_T0, MCS48_PORT_T0) AM_READ(i8748_t0_r)
ADDRESS_MAP_END


static MACHINE_CONFIG_FRAGMENT( simutrek )
	MCFG_CPU_ADD("simutrek", I8748, XTAL_6MHz)
	MCFG_CPU_IO_MAP(simutrek_portmap)

	MCFG_FRAGMENT_ADD(pr8210)
MACHINE_CONFIG_END


ROM_START( simutrek )
	ROM_REGION( 0x800, "pr8210", 0 )
	ROM_LOAD( "pr-8210_mcu_ud6005a.bin", 0x000, 0x800, CRC(120fa83b) SHA1(b514326ca1f52d6d89056868f9d17eabd4e3f31d) )

	ROM_REGION( 0x400, "simutrek", 0)
	ROM_LOAD( "laser_player_interface_d8748_a308.bin", 0x0000, 0x0400, CRC(eed3e728) SHA1(1eb3467f1c41553375b2c21952cd593b167f5416) )
ROM_END



//**************************************************************************
//  SIMUTREK IMPLEMENTATION
//**************************************************************************

//-------------------------------------------------
// simutrek_special_device - constructor
//-------------------------------------------------

simutrek_special_device::simutrek_special_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: pioneer_pr8210_device(mconfig, SIMUTREK_SPECIAL, "Simutrek Modified PR-8210", "simutrek", tag, owner, clock),
	  m_i8748_cpu(*this, "simutrek"),
	  m_audio_squelch(0),
	  m_data(0),
	  m_data_ready(false),
	  m_i8748_port2(0),
	  m_controlnext(0),
	  m_controlthis(0)
{
}


//-------------------------------------------------
//  data_w - write callback when the parallel data 
//  port is written to
//-------------------------------------------------

void simutrek_special_device::data_w(UINT8 data)
{
	synchronize(TID_LATCH_DATA, data);
	if (LOG_SIMUTREK)
		printf("%03d:**** Simutrek Command = %02X\n", screen().vpos(), data);
}


//-------------------------------------------------
//  set_external_audio_squelch - Simutrek-specific
//  command to enable/disable audio squelch
//-------------------------------------------------

void simutrek_special_device::set_external_audio_squelch(int state)
{
	if (LOG_SIMUTREK && m_audio_squelch != (state == 0))
		printf("--> audio squelch = %d\n", state == 0);
	m_audio_squelch = (state == 0);
	update_audio_squelch();
}


//-------------------------------------------------
//  player_vsync - VSYNC callback, called at the
//  start of the blanking period
//-------------------------------------------------

void simutrek_special_device::player_vsync(const vbi_metadata &vbi, int fieldnum, attotime curtime)
{
	// latch the control state after the second field
	if (fieldnum == 1)
	{
		m_controlthis = m_controlnext;
		m_controlnext = 0;
	}

	// call the parent
	if (LOG_SIMUTREK)
		printf("%3d:VSYNC(%d)\n", screen().vpos(), fieldnum);
	pioneer_pr8210_device::player_vsync(vbi, fieldnum, curtime);

	// process data
	if (m_data_ready)
	{
		if (LOG_SIMUTREK)
			printf("%3d:VSYNC IRQ\n", screen().vpos());
		m_i8748_cpu->set_input_line(MCS48_INPUT_IRQ, ASSERT_LINE);
		timer_set(screen().scan_period(), TID_IRQ_OFF);
	}
}


//-------------------------------------------------
//  device_start - device initialization
//-------------------------------------------------

void simutrek_special_device::device_start()
{
	// pass through to the parent
	pioneer_pr8210_device::device_start();
}


//-------------------------------------------------
//  device_reset - device reset
//-------------------------------------------------

void simutrek_special_device::device_reset()
{
	// standard PR-8210 initialization
	pioneer_pr8210_device::device_reset();

	// initialize the Simutrek state
	// for proper synchronization of initial attract mode, this needs to be set
	m_data_ready = true;
}


//-------------------------------------------------
//  device_timer - handle timers set by this
//  device
//-------------------------------------------------

void simutrek_special_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
		// clear the 8748 IRQ
		case TID_IRQ_OFF:
			m_i8748_cpu->set_input_line(MCS48_INPUT_IRQ, CLEAR_LINE);
			break;
		
		// latch data
		case TID_LATCH_DATA:
			m_data = param;
			m_data_ready = true;
			break;

		// pass everything else onto the parent
		default:
			pioneer_pr8210_device::device_timer(timer, id, param, ptr);
			break;
	}
}


//-------------------------------------------------
//  device_rom_region - return a pointer to our
//  ROM region definitions
//-------------------------------------------------

const rom_entry *simutrek_special_device::device_rom_region() const
{
	return ROM_NAME(simutrek);
}


//-------------------------------------------------
//  device_mconfig_additions - return a pointer to
//  our machine config fragment
//-------------------------------------------------

machine_config_constructor simutrek_special_device::device_mconfig_additions() const
{
	return MACHINE_CONFIG_NAME(simutrek);
}


//-------------------------------------------------
//  i8748_port2_r - handle reads from the 8748
//  port #2
//-------------------------------------------------

READ8_MEMBER( simutrek_special_device::i8748_port2_r )
{
	// bit $80 is the pr8210 video squelch
	return (m_i8049_port1 & 0x20) ? 0x00 : 0x80;
}


//-------------------------------------------------
//  i8748_port2_w - handle writes to the 8748
//  port #2
//-------------------------------------------------

WRITE8_MEMBER( simutrek_special_device::i8748_port2_w )
{
	// update stat
	UINT8 prev = m_i8748_port2;
	m_i8748_port2 = data;

	// bit $20 goes to the serial line
	if ((data ^ prev) & 0x20)
		pioneer_pr8210_device::control_w((data & 0x20) ? ASSERT_LINE : CLEAR_LINE);

	// bit $10 goes to JUMP TRG
	// bit $08 controls direction
	if (!(data & 0x10) && (prev & 0x10))
	{
		int direction = (data & 0x08) ? 1 : -1;
		if (LOG_SIMUTREK)
			printf("%3d:JUMP TRG (Simutrek PC=%03X)\n", screen().vpos(), cpu_get_pc(&space.device()));
		advance_slider(direction);
	}

	// bit $04 controls who owns the JUMP TRG command
	if (LOG_SIMUTREK && ((data ^ prev) & 0x04))
		printf("%3d:Simutrek ownership line = %d (Simutrek PC=%03X)\n", screen().vpos(), (data >> 2) & 1, cpu_get_pc(&space.device()));
	m_controlnext = (~data >> 2) & 1;

	// bits $03 control something (status?)
	if (LOG_SIMUTREK && ((data ^ prev) & 0x03))
		printf("Simutrek Status = %d\n", data & 0x03);
}


//-------------------------------------------------
//  i8748_data_r - handle external 8748 data reads
//-------------------------------------------------

READ8_MEMBER( simutrek_special_device::i8748_data_r )
{
	// acknowledge the read and clear the data ready flag
	m_data_ready = false;
	return m_data;
}


//-------------------------------------------------
//  i8748_t0_r - return the status of the 8748
//  T0 input
//-------------------------------------------------

READ8_MEMBER( simutrek_special_device::i8748_t0_r )
{
	// return 1 if data is waiting from main CPU
	return m_data_ready;
}
