/************************************************************************* 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; } }