/*************************************************************************
ldvp931.c
Philips 22VP931 laserdisc emulation.
Copyright Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
**************************************************************************
Still to do:
* determine actual slow/fast speeds
*
*************************************************************************/
#include "ldcore.h"
#include "cpu/mcs48/mcs48.h"
/***************************************************************************
DEBUGGING
***************************************************************************/
#define LOG_COMMANDS 0
#define LOG_PORTS 0
/***************************************************************************
CONSTANTS
***************************************************************************/
/* Philips 22VP931 specific information */
#define VP931_SCAN_SPEED (2000 / 30) /* 2000 frames/second */
#define VP931_SCAN_FAST_SPEED (4000 / 30) /* 4000 frames/second */
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
/* player-specific data */
struct _ldplayer_data
{
/* low-level emulation data */
int cpunum; /* CPU index of the 8049 */
const device_config *tracktimer; /* timer device */
int lastframe;
UINT8 out0; /* output 0 state */
UINT8 out1; /* output 1 state */
UINT8 port1; /* port 1 state */
UINT8 daticval; /* latched DATIC value */
UINT8 daticerp; /* /ERP value from DATIC */
UINT8 datastrobe; /* DATA STROBE line from DATIC */
UINT8 fromcontroller; /* command byte from the controller */
UINT8 fromcontroller_pending; /* TRUE if data is pending */
UINT8 tocontroller; /* command byte to the controller */
UINT8 tocontroller_pending; /* TRUE if data is pending */
INT8 trackdir; /* direction of tracking */
UINT8 trackstate; /* state of tracking */
UINT8 cmdbuf[3]; /* 3 bytes worth of commands */
UINT8 cmdcount; /* number of command bytes seen */
INT16 advanced; /* number of frames advanced */
void (*data_ready_cb)(const device_config *device, int state); /* data ready callback */
};
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
static void vp931_init(laserdisc_state *ld);
static void vp931_vsync(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime);
static INT32 vp931_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime);
static void vp931_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data);
static UINT8 vp931_data_r(laserdisc_state *ld);
static UINT8 vp931_ready(laserdisc_state *ld);
static UINT8 vp931_data_ready(laserdisc_state *ld);
static WRITE8_HANDLER( output0_w );
static WRITE8_HANDLER( output1_w );
static WRITE8_HANDLER( lcd_w );
static READ8_HANDLER( keypad_r );
static READ8_HANDLER( datic_r );
static READ8_HANDLER( from_controller_r );
static WRITE8_HANDLER( to_controller_w );
static READ8_HANDLER( port1_r );
static WRITE8_HANDLER( port1_w );
static READ8_HANDLER( port2_r );
static WRITE8_HANDLER( port2_w );
static READ8_HANDLER( t0_r );
static READ8_HANDLER( t1_r );
static TIMER_CALLBACK( vbi_data_fetch );
static TIMER_CALLBACK( deferred_data_w );
static TIMER_CALLBACK( irq_off );
static TIMER_CALLBACK( datastrobe_off );
static TIMER_CALLBACK( erp_off );
static TIMER_DEVICE_CALLBACK( track_timer );
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
find_vp931 - find our device; assumes there
is only one
-------------------------------------------------*/
INLINE laserdisc_state *find_vp931(running_machine *machine)
{
return ldcore_get_safe_token(device_list_first(machine->config->devicelist, LASERDISC));
}
/***************************************************************************
22VP931 ROM AND MACHINE INTERFACES
***************************************************************************/
static ADDRESS_MAP_START( vp931_portmap, ADDRESS_SPACE_IO, 8 )
AM_RANGE(0x00, 0x00) AM_MIRROR(0xcf) AM_READWRITE(keypad_r, output0_w)
AM_RANGE(0x10, 0x10) AM_MIRROR(0xcf) AM_WRITE(output1_w)
AM_RANGE(0x20, 0x20) AM_MIRROR(0xcf) AM_READWRITE(datic_r, lcd_w)
AM_RANGE(0x30, 0x30) AM_MIRROR(0xcf) AM_READWRITE(from_controller_r, to_controller_w)
AM_RANGE(MCS48_PORT_P1, MCS48_PORT_P1) AM_READWRITE(port1_r, port1_w)
AM_RANGE(MCS48_PORT_P2, MCS48_PORT_P2) AM_READWRITE(port2_r, port2_w)
AM_RANGE(MCS48_PORT_T0, MCS48_PORT_T0) AM_READ(t0_r)
AM_RANGE(MCS48_PORT_T1, MCS48_PORT_T1) AM_READ(t1_r)
ADDRESS_MAP_END
static MACHINE_DRIVER_START( vp931 )
MDRV_CPU_ADD("vp931", I8049, XTAL_11MHz)
MDRV_CPU_IO_MAP(vp931_portmap,0)
MDRV_TIMER_ADD("tracktimer", track_timer)
MACHINE_DRIVER_END
ROM_START( vp931 )
ROM_REGION( 0x800, "vp931", ROMREGION_LOADBYNAME )
ROM_LOAD( "at-6-1_a.bin", 0x000, 0x800, CRC(e11b3c8d) SHA1(ea2d7f6a044ed085ce5e09d8b1b1a21c37f0e9b8) )
ROM_END
/***************************************************************************
PR-8210 PLAYER INTERFACE
***************************************************************************/
const ldplayer_interface vp931_interface =
{
LASERDISC_TYPE_PHILLIPS_22VP931, /* type of the player */
sizeof(ldplayer_data), /* size of the state */
"Phillips 22VP931", /* name of the player */
rom_vp931, /* pointer to ROM region information */
machine_config_vp931, /* pointer to machine configuration */
vp931_init, /* initialization callback */
vp931_vsync, /* vsync callback */
vp931_update, /* update callback */
NULL, /* overlay callback */
vp931_data_w, /* parallel data write */
{ /* single line write: */
NULL, /* LASERDISC_LINE_ENTER */
NULL /* LASERDISC_LINE_CONTROL */
},
vp931_data_r, /* parallel data read */
{ /* single line read: */
vp931_ready, /* LASERDISC_LINE_READY */
NULL, /* LASERDISC_LINE_STATUS */
NULL, /* LASERDISC_LINE_COMMAND */
vp931_data_ready, /* LASERDISC_LINE_DATA_AVAIL */
}
};
/***************************************************************************
PUBLIC FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
vp931_set_data_ready_callback - set the data
ready callback
-------------------------------------------------*/
void vp931_set_data_ready_callback(const device_config *device, void (*callback)(const device_config *device, int state))
{
laserdisc_state *ld = ldcore_get_safe_token(device);
ld->player->data_ready_cb = callback;
}
/***************************************************************************
PHILLIPS 22VP931 IMPLEMENTATION
***************************************************************************/
/*-------------------------------------------------
vp931_init - Pioneer PR-8210-specific
initialization
-------------------------------------------------*/
static void vp931_init(laserdisc_state *ld)
{
astring *tempstring = astring_alloc();
ldplayer_data *player = ld->player;
/* find our CPU */
astring_printf(tempstring, "%s:%s", ld->device->tag, "vp931");
player->cpunum = mame_find_cpu_index(ld->device->machine, astring_c(tempstring));
/* find our timer */
astring_printf(tempstring, "%s:%s", ld->device->tag, "tracktimer");
player->tracktimer = device_list_find_by_tag(ld->device->machine->config->devicelist, TIMER, astring_c(tempstring));
timer_device_set_ptr(player->tracktimer, ld);
astring_free(tempstring);
}
/*-------------------------------------------------
vp931_vsync - VSYNC callback, called at the
start of the blanking period
-------------------------------------------------*/
static void vp931_vsync(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime)
{
/* reset our command counter (debugging only) */
ld->player->cmdcount = 0;
/* set the ERP signal to 1 to indicate start of frame, and set a timer to turn it off */
ld->player->daticerp = 1;
timer_set(video_screen_get_time_until_pos(ld->screen, 15*2, 0), ld, 0, erp_off);
}
/*-------------------------------------------------
vp931_update - update callback, called on
the first visible line of the frame
-------------------------------------------------*/
static INT32 vp931_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime)
{
/* set the first VBI timer to go at the start of line 16 */
timer_set(video_screen_get_time_until_pos(ld->screen, 16*2, 0), ld, LASERDISC_CODE_LINE16 << 2, vbi_data_fetch);
/* play forward by default */
return fieldnum;
}
/*-------------------------------------------------
vp931_data_w - handle a parallel data write
to the 22VP931
-------------------------------------------------*/
static void vp931_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data)
{
/* set a timer to synchronize execution before sending the data */
timer_call_after_resynch(ld, data, deferred_data_w);
}
/*-------------------------------------------------
vp931_data_r - handle a parallel data read
from the 22VP931
-------------------------------------------------*/
static UINT8 vp931_data_r(laserdisc_state *ld)
{
ldplayer_data *player = ld->player;
/* if data is pending, clear the pending flag and notify any callbacks */
if (player->tocontroller_pending)
{
player->tocontroller_pending = FALSE;
if (player->data_ready_cb != NULL)
(*player->data_ready_cb)(ld->device, FALSE);
}
/* also boost interleave for 4 scanlines to ensure proper communications */
cpu_boost_interleave(attotime_zero, attotime_mul(video_screen_get_scan_period(ld->screen), 4));
return player->tocontroller;
}
/*-------------------------------------------------
vp931_ready - return the status of "ready"
to the caller (ready to accept another
command)
-------------------------------------------------*/
static UINT8 vp931_ready(laserdisc_state *ld)
{
/* if data is pending, we are not ready */
ldplayer_data *player = ld->player;
return player->fromcontroller_pending ? CLEAR_LINE : ASSERT_LINE;
}
/*-------------------------------------------------
vp931_data_ready - return the status of
"data available" to the caller
-------------------------------------------------*/
static UINT8 vp931_data_ready(laserdisc_state *ld)
{
ldplayer_data *player = ld->player;
return player->tocontroller_pending ? ASSERT_LINE : CLEAR_LINE;
}
/*-------------------------------------------------
output0_w - controls audio/video squelch
and other bits
-------------------------------------------------*/
static WRITE8_HANDLER( output0_w )
{
laserdisc_state *ld = find_vp931(machine);
ldplayer_data *player = ld->player;
/*
$80 = n/c
$40 = LED (?) -> C335
$20 = LED (?)
$10 = LED (?) -> CX
$08 = EJECT
$04 = inverted -> AUDIO MUTE II
$02 = inverted -> AUDIO MUTE I
$01 = inverted -> VIDEO MUTE
*/
if (LOG_PORTS && (player->out0 ^ data) & 0xff)
{
printf("%03X:out0:", activecpu_get_pc());
if ( (data & 0x80)) printf(" ???");
if ( (data & 0x40)) printf(" LED1");
if ( (data & 0x20)) printf(" LED2");
if ( (data & 0x10)) printf(" LED3");
if ( (data & 0x08)) printf(" EJECT");
if (!(data & 0x04)) printf(" AUDMUTE2");
if (!(data & 0x02)) printf(" AUDMUTE1");
if (!(data & 0x01)) printf(" VIDMUTE");
printf("\n");
player->out0 = data;
}
/* update a/v squelch */
ldcore_set_audio_squelch(ld, !(data & 0x02), !(data & 0x04));
ldcore_set_video_squelch(ld, !(data & 0x01));
}
/*-------------------------------------------------
output1_w - controls scanning behaviors
-------------------------------------------------*/
static WRITE8_HANDLER( output1_w )
{
laserdisc_state *ld = find_vp931(machine);
ldplayer_data *player = ld->player;
INT32 speed = 0;
/*
$80 = n/c
$40 = n/c
$20 = n/c
$10 = n/c
$08 = inverted -> SMS
$04 = inverted -> SSS
$02 = inverted -> SCAN CMD
$01 = OSM
*/
if (LOG_PORTS && (player->out1 ^ data) & 0x08)
{
mame_printf_debug("%03X:out1:", activecpu_get_pc());
if (!(data & 0x08)) mame_printf_debug(" SMS");
mame_printf_debug("\n");
player->out1 = data;
}
/* speed is 0 unless SCAN CMD is clear */
speed = 0;
if (!(data & 0x02))
{
/* fast/slow is based on bit 2 */
speed = (data & 0x04) ? VP931_SCAN_FAST_SPEED : VP931_SCAN_SPEED;
/* direction is based on bit 0 */
if (data & 0x01)
speed = -speed;
}
/* update the speed */
ldcore_set_slider_speed(ld, speed);
}
/*-------------------------------------------------
lcd_w - vestigial LCD frame display
-------------------------------------------------*/
static WRITE8_HANDLER( lcd_w )
{
/*
Frame number is written as 5 digits here; however, it is not actually
connected
*/
}
/*-------------------------------------------------
keypad_r - vestigial keypad/button controls
-------------------------------------------------*/
static READ8_HANDLER( keypad_r )
{
/*
From the code, this is apparently a vestigial keypad with basic controls:
$01 = play
$02 = still
$04 = jump 25 frames backward
$08 = jump 25 frames forward
$10 = search for frame 50(?)
$20 = search for frame 350(?)
$40 = reset
$80 = play reverse
*/
return 0x00;
}
/*-------------------------------------------------
datic_r - read the latched value from the
DATIC circuit
-------------------------------------------------*/
static READ8_HANDLER( datic_r )
{
laserdisc_state *ld = find_vp931(machine);
return ld->player->daticval;
}
/*-------------------------------------------------
from_controller_r - read the value the
external controller wrote
-------------------------------------------------*/
static READ8_HANDLER( from_controller_r )
{
laserdisc_state *ld = find_vp931(machine);
ldplayer_data *player = ld->player;
/* clear the pending flag and return the data */
player->fromcontroller_pending = FALSE;
return player->fromcontroller;
}
/*-------------------------------------------------
to_controller_w - write a value back to the
external controller
-------------------------------------------------*/
static WRITE8_HANDLER( to_controller_w )
{
laserdisc_state *ld = find_vp931(machine);
ldplayer_data *player = ld->player;
/* set the pending flag and stash the data */
player->tocontroller_pending = TRUE;
player->tocontroller = data;
/* signal to the callback if provided */
if (player->data_ready_cb != NULL)
(*player->data_ready_cb)(ld->device, TRUE);
/* also boost interleave for 4 scanlines to ensure proper communications */
cpu_boost_interleave(attotime_zero, attotime_mul(video_screen_get_scan_period(ld->screen), 4));
}
/*-------------------------------------------------
port1_r - read the 8048 I/O port 1
-------------------------------------------------*/
static READ8_HANDLER( port1_r )
{
laserdisc_state *ld = find_vp931(machine);
ldplayer_data *player = ld->player;
UINT8 result = 0x00;
/*
$80 = P17 = (in) unsure
$40 = P16 = (in) /ERP from datic circuit
$20 = P15 = (in) D105
*/
if (!player->daticerp)
result |= 0x40;
return result;
}
/*-------------------------------------------------
port1_w - write the 8048 I/O port 1
-------------------------------------------------*/
static WRITE8_HANDLER( port1_w )
{
laserdisc_state *ld = find_vp931(machine);
ldplayer_data *player = ld->player;
/*
$10 = P14 = (out) D104 -> /SPEED
$08 = P13 = (out) D103 -> /TIMER ENABLE
$04 = P12 = (out) D102 -> /REV
$02 = P11 = (out) D101 -> /FORW
$01 = P10 = (out) D100 -> some op-amp then to C334, B56, B332
*/
if (LOG_PORTS && (player->port1 ^ data) & 0x1f)
{
printf("%03X:port1:", activecpu_get_pc());
if (!(data & 0x10)) printf(" SPEED");
if (!(data & 0x08)) printf(" TIMENABLE");
if (!(data & 0x04)) printf(" REV");
if (!(data & 0x02)) printf(" FORW");
if (!(data & 0x01)) printf(" OPAMP");
printf("\n");
}
/* if bit 0 is set, we are not tracking */
if (data & 0x01)
player->trackdir = 0;
/* if bit 0 is clear and we weren't tracking before, initialize the state */
else if (player->trackdir == 0)
{
player->advanced = 0;
/* if bit 2 is clear, we are moving backwards */
if (!(data & 0x04))
{
player->trackdir = -1;
player->trackstate = 1;
}
/* if bit 1 is clear, we are moving forward */
else if (!(data & 0x02))
{
player->trackdir = 1;
player->trackstate = 0;
}
}
/* if we have a timer, adjust it */
if (player->tracktimer != NULL)
{
/* turn it off if we're not tracking */
if (player->trackdir == 0)
timer_device_adjust_periodic(player->tracktimer, attotime_never, 0, attotime_never);
/* if we just started tracking, or if the speed was changed, reprime the timer */
else if (((player->port1 ^ data) & 0x11) != 0)
{
/* speeds here are just guesses, but work with the player logic; this is the time per half-track */
attotime speed = (data & 0x10) ? ATTOTIME_IN_USEC(60) : ATTOTIME_IN_USEC(10);
/* always start with an initial long delay; the code expects this */
timer_device_adjust_periodic(player->tracktimer, ATTOTIME_IN_USEC(100), 0, speed);
}
}
player->port1 = data;
}
/*-------------------------------------------------
port2_r - read from the 8048 I/O port 2
-------------------------------------------------*/
static READ8_HANDLER( port2_r )
{
laserdisc_state *ld = find_vp931(machine);
ldplayer_data *player = ld->player;
UINT8 result = 0x00;
/*
$80 = P27 = (in) set/reset latch; set by FOC LS, reset by IGR
$20 = P25 = (in) D125 -> 0 when data written to controller is preset, reset to 1 when read
$10 = P24 = (in) D124 -> 0 when data from controller is present, reset to 1 on a read
*/
if (!player->tocontroller_pending)
result |= 0x20;
if (!player->fromcontroller_pending)
result |= 0x10;
return result;
}
/*-------------------------------------------------
port2_w - write the 8048 I/O port 2
-------------------------------------------------*/
static WRITE8_HANDLER( port2_w )
{
/*
$40 = P26 = (out) cleared while data is sent back & forth; set afterwards
[Not actually connected, but this is done in the code]
*/
}
/*-------------------------------------------------
t0_r - return the T0 line status, which is
connected to the DATIC's data strobe line
-------------------------------------------------*/
static READ8_HANDLER( t0_r )
{
laserdisc_state *ld = find_vp931(machine);
return ld->player->datastrobe;
}
/*-------------------------------------------------
t1_r - return the T1 line status, which is
connected to the tracking state and is used
to count the number of tracks advanced
-------------------------------------------------*/
static READ8_HANDLER( t1_r )
{
laserdisc_state *ld = find_vp931(machine);
return ld->player->trackstate;
}
/*-------------------------------------------------
vbi_data_fetch - called 4 times per scanline
on lines 16, 17, and 18 to feed the VBI data
through one byte at a time
-------------------------------------------------*/
static TIMER_CALLBACK( vbi_data_fetch )
{
laserdisc_state *ld = ptr;
ldplayer_data *player = ld->player;
int which = param & 3;
int line = param >> 2;
UINT32 code = 0;
/* fetch the code and compute the DATIC latched value */
if (line >= LASERDISC_CODE_LINE16 && line <= LASERDISC_CODE_LINE18)
code = laserdisc_get_field_code(ld->device, line);
/* at the start of each line, signal an interrupt and use a timer to turn it off */
if (which == 0)
{
cpunum_set_input_line(machine, player->cpunum, MCS48_INPUT_IRQ, ASSERT_LINE);
timer_set(ATTOTIME_IN_NSEC(5580), ld, 0, irq_off);
}
/* clock the data strobe on each subsequent callback */
else if (code != 0)
{
player->daticval = code >> (8 * (3 - which));
player->datastrobe = 1;
timer_set(ATTOTIME_IN_NSEC(5000), ld, 0, datastrobe_off);
}
/* determine the next bit to fetch and reprime ourself */
if (++which == 4)
{
which = 0;
line++;
}
if (line <= LASERDISC_CODE_LINE18 + 1)
timer_set(video_screen_get_time_until_pos(ld->screen, line*2, which * 2 * video_screen_get_width(ld->screen) / 4), ld, (line << 2) | which, vbi_data_fetch);
}
/*-------------------------------------------------
deferred_data_w - handle a write from the
external controller
-------------------------------------------------*/
static TIMER_CALLBACK( deferred_data_w )
{
laserdisc_state *ld = ptr;
ldplayer_data *player = ld->player;
/* set the value and mark it pending */
player->fromcontroller = param;
player->fromcontroller_pending = TRUE;
/* track the commands for debugging purposes */
if (player->cmdcount < ARRAY_LENGTH(player->cmdbuf))
{
player->cmdbuf[player->cmdcount++] = param;
if (LOG_COMMANDS && player->cmdcount == 3)
printf("Cmd: %02X %02X %02X\n", player->cmdbuf[0], player->cmdbuf[1], player->cmdbuf[2]);
}
}
/*-------------------------------------------------
irq_off - turn off the 8048 IRQ signal
-------------------------------------------------*/
static TIMER_CALLBACK( irq_off )
{
laserdisc_state *ld = ptr;
cpunum_set_input_line(machine, ld->player->cpunum, MCS48_INPUT_IRQ, CLEAR_LINE);
}
/*-------------------------------------------------
datastrobe_off - turn off the DATIC data
strobe signal
-------------------------------------------------*/
static TIMER_CALLBACK( datastrobe_off )
{
laserdisc_state *ld = ptr;
ld->player->datastrobe = 0;
}
/*-------------------------------------------------
erp_off - turn off the DATIC ERP signal
-------------------------------------------------*/
static TIMER_CALLBACK( erp_off )
{
laserdisc_state *ld = ptr;
ld->player->daticerp = 0;
}
/*-------------------------------------------------
track_timer - advance by one half-track
-------------------------------------------------*/
static TIMER_DEVICE_CALLBACK( track_timer )
{
laserdisc_state *ld = ptr;
ldplayer_data *player = ld->player;
/* advance by the count and toggle the state */
player->trackstate ^= 1;
if ((player->trackdir < 0 && !player->trackstate) || (player->trackdir > 0 && player->trackstate))
{
ldcore_advance_slider(ld, player->trackdir);
player->advanced += player->trackdir;
}
}