// license:BSD-3-Clause // copyright-holders:Aaron Giles /************************************************************************* ldvp931.c Philips 22VP931 laserdisc emulation. ************************************************************************** Still to do: * determine actual slow/fast speeds * *************************************************************************/ #include "emu.h" #include "ldvp931.h" //************************************************************************** // DEBUGGING //************************************************************************** #define LOG_COMMANDS 0 #define LOG_PORTS 0 //************************************************************************** // CONSTANTS //************************************************************************** // scanning speeds #define SCAN_SPEED (2000 / 30) // 2000 frames/second #define SCAN_FAST_SPEED (4000 / 30) // 4000 frames/second //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // devices DEFINE_DEVICE_TYPE(PHILIPS_22VP931, philips_22vp931_device, "22vp931", "Philips 22VP931") //************************************************************************** // 22VP931 ROM AND MACHINE INTERFACES //************************************************************************** void philips_22vp931_device::vp931_portmap(address_map &map) { map(0x00, 0x00).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_keypad_r), FUNC(philips_22vp931_device::i8049_output0_w)); map(0x10, 0x10).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_unknown_r), FUNC(philips_22vp931_device::i8049_output1_w)); map(0x20, 0x20).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_datic_r), FUNC(philips_22vp931_device::i8049_lcd_w)); map(0x30, 0x30).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_from_controller_r), FUNC(philips_22vp931_device::i8049_to_controller_w)); } ROM_START( vp931 ) ROM_REGION( 0x800, "vp931", 0 ) ROM_LOAD( "at-6-1_a.bin", 0x000, 0x800, CRC(e11b3c8d) SHA1(ea2d7f6a044ed085ce5e09d8b1b1a21c37f0e9b8) ) ROM_END //************************************************************************** // PHILIPS 22VP931 IMPLEMENTATION //************************************************************************** //------------------------------------------------- // philips_22vp931_device - constructor //------------------------------------------------- philips_22vp931_device::philips_22vp931_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : laserdisc_device(mconfig, PHILIPS_22VP931, tag, owner, clock), m_i8049_cpu(*this, "vp931"), m_tracktimer(nullptr), m_i8049_out0(0), m_i8049_out1(0), m_i8049_port1(0), m_daticval(0), m_daticerp(0), m_datastrobe(0), m_fromcontroller(0), m_fromcontroller_pending(false), m_tocontroller(0), m_tocontroller_pending(false), m_trackdir(0), m_trackstate(0), m_cmdcount(0), m_advanced(0) { } //------------------------------------------------- // reset_w - write to the reset line //------------------------------------------------- void philips_22vp931_device::reset_w(uint8_t data) { // control the CPU state m_i8049_cpu->set_input_line(INPUT_LINE_RESET, data); // on an assert, reset the device state as well if (data == ASSERT_LINE) reset(); } //------------------------------------------------- // data_r - handle a parallel data read from the // 22VP931 //------------------------------------------------- uint8_t philips_22vp931_device::data_r() { // if data is pending, clear the pending flag and notify any callbacks if (m_tocontroller_pending) { m_tocontroller_pending = false; if (!m_data_ready.isnull()) m_data_ready(*this, false); } // also boost interleave for 4 scanlines to ensure proper communications machine().scheduler().boost_interleave(attotime::zero, screen().scan_period() * 4); return m_tocontroller; } //------------------------------------------------- // device_start - device initialization //------------------------------------------------- void philips_22vp931_device::device_start() { // pass through to the parent laserdisc_device::device_start(); // allocate a timer m_tracktimer = timer_alloc(TID_HALF_TRACK); } //------------------------------------------------- // device_reset - device reset //------------------------------------------------- void philips_22vp931_device::device_reset() { // pass through to the parent laserdisc_device::device_reset(); // reset our state m_i8049_out0 = 0; m_i8049_out1 = 0; m_i8049_port1 = 0; m_daticval = 0; m_daticerp = 0; m_datastrobe = 0; m_fromcontroller = 0; m_fromcontroller_pending = false; m_tocontroller = 0; m_tocontroller_pending = false; m_trackdir = 0; m_trackstate = 0; m_cmdcount = 0; m_advanced = 0; } //------------------------------------------------- // device_timer - handle timers set by this // device //------------------------------------------------- void philips_22vp931_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TID_VBI_DATA_FETCH: { uint32_t line = param >> 2; int which = param & 3; uint32_t code = 0; // fetch the code and compute the DATIC latched value if (line >= LASERDISC_CODE_LINE16 && line <= LASERDISC_CODE_LINE18) code = get_field_code(laserdisc_field_code(line), false); // at the start of each line, signal an interrupt and use a timer to turn it off if (which == 0) { m_i8049_cpu->set_input_line(MCS48_INPUT_IRQ, ASSERT_LINE); timer_set(attotime::from_nsec(5580), TID_IRQ_OFF); } // clock the data strobe on each subsequent callback else if (code != 0) { m_daticval = code >> (8 * (3 - which)); m_datastrobe = 1; timer_set(attotime::from_nsec(5000), TID_DATA_STROBE_OFF); } // determine the next bit to fetch and reprime ourself if (++which == 4) { which = 0; line++; } if (line <= LASERDISC_CODE_LINE18 + 1) timer_set(screen().time_until_pos(line*2, which * 2 * screen().width() / 4), TID_VBI_DATA_FETCH, (line << 2) + which); break; } case TID_DEFERRED_DATA: // set the value and mark it pending if (LOG_COMMANDS && m_fromcontroller_pending) printf("Dropped previous command byte\n"); m_fromcontroller = param; m_fromcontroller_pending = true; // track the commands for debugging purposes if (m_cmdcount < ARRAY_LENGTH(m_cmdbuf)) { m_cmdbuf[m_cmdcount++ % 3] = param; if (LOG_COMMANDS && m_cmdcount % 3 == 0) printf("Cmd: %02X %02X %02X\n", m_cmdbuf[0], m_cmdbuf[1], m_cmdbuf[2]); } break; case TID_IRQ_OFF: m_i8049_cpu->set_input_line(MCS48_INPUT_IRQ, CLEAR_LINE); break; case TID_DATA_STROBE_OFF: m_datastrobe = 0; break; case TID_ERP_OFF: m_daticerp = 0; break; case TID_HALF_TRACK: // advance by the count and toggle the state m_trackstate ^= 1; if ((m_trackdir < 0 && !m_trackstate) || (m_trackdir > 0 && m_trackstate)) { advance_slider(m_trackdir); m_advanced += m_trackdir; } 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 tiny_rom_entry *philips_22vp931_device::device_rom_region() const { return ROM_NAME(vp931); } //------------------------------------------------- // device_add_mconfig - add device configuration //------------------------------------------------- void philips_22vp931_device::device_add_mconfig(machine_config &config) { I8049(config, m_i8049_cpu, XTAL(11'000'000)); m_i8049_cpu->set_addrmap(AS_IO, &philips_22vp931_device::vp931_portmap); m_i8049_cpu->p1_in_cb().set(FUNC(philips_22vp931_device::i8049_port1_r)); m_i8049_cpu->p1_out_cb().set(FUNC(philips_22vp931_device::i8049_port1_w)); m_i8049_cpu->p2_in_cb().set(FUNC(philips_22vp931_device::i8049_port2_r)); m_i8049_cpu->p2_out_cb().set(FUNC(philips_22vp931_device::i8049_port2_w)); m_i8049_cpu->t0_in_cb().set(FUNC(philips_22vp931_device::i8049_t0_r)); m_i8049_cpu->t1_in_cb().set(FUNC(philips_22vp931_device::i8049_t1_r)); } //------------------------------------------------- // player_vsync - VSYNC callback, called at the // start of the blanking period //------------------------------------------------- void philips_22vp931_device::player_vsync(const vbi_metadata &vbi, int fieldnum, const attotime &curtime) { // reset our command counter (debugging only) m_cmdcount = 0; // set the ERP signal to 1 to indicate start of frame, and set a timer to turn it off m_daticerp = 1; timer_set(screen().time_until_pos(15*2), TID_ERP_OFF); } //------------------------------------------------- // player_update - update callback, called on // the first visible line of the frame //------------------------------------------------- int32_t philips_22vp931_device::player_update(const vbi_metadata &vbi, int fieldnum, const attotime &curtime) { // set the first VBI timer to go at the start of line 16 timer_set(screen().time_until_pos(16*2), TID_VBI_DATA_FETCH, LASERDISC_CODE_LINE16 << 2); // play forward by default return fieldnum; } //------------------------------------------------- // i8049_output0_w - controls audio/video squelch // and other bits //------------------------------------------------- WRITE8_MEMBER( philips_22vp931_device::i8049_output0_w ) { /* $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 && (m_i8049_out0 ^ data) & 0xff) { std::string flags; if ( (data & 0x80)) flags += " ???"; if ( (data & 0x40)) flags += " LED1"; if ( (data & 0x20)) flags += " LED2"; if ( (data & 0x10)) flags += " LED3"; if ( (data & 0x08)) flags += " EJECT"; if (!(data & 0x04)) flags += " AUDMUTE2"; if (!(data & 0x02)) flags += " AUDMUTE1"; if (!(data & 0x01)) flags += " VIDMUTE"; logerror("out0: %s %s\n", flags, machine().describe_context()); m_i8049_out0 = data; } // update a/v squelch set_audio_squelch(!(data & 0x02), !(data & 0x04)); set_video_squelch(!(data & 0x01)); } //------------------------------------------------- // i8049_output1_w - controls scanning behaviors //------------------------------------------------- WRITE8_MEMBER( philips_22vp931_device::i8049_output1_w ) { /* $80 = n/c $40 = n/c $20 = n/c $10 = n/c $08 = inverted -> SMS $04 = inverted -> SSS $02 = inverted -> SCAN CMD $01 = OSM */ int32_t speed; if (LOG_PORTS && (m_i8049_out1 ^ data) & 0x08) { std::string flags; if (!(data & 0x08)) flags += " SMS"; logerror("out1: %s %s\n", flags, machine().describe_context()); m_i8049_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) ? SCAN_FAST_SPEED : SCAN_SPEED; // direction is based on bit 0 if (data & 0x01) speed = -speed; } // update the speed set_slider_speed(speed); } //------------------------------------------------- // i8049_lcd_w - vestigial LCD frame display //------------------------------------------------- WRITE8_MEMBER( philips_22vp931_device::i8049_lcd_w ) { /* Frame number is written as 5 digits here; however, it is not actually connected */ } //------------------------------------------------- // i8049_unknown_r - unknown input port //------------------------------------------------- READ8_MEMBER( philips_22vp931_device::i8049_unknown_r ) { // only bit $80 is checked and its effects are minor return 0x00; } //------------------------------------------------- // i8049_keypad_r - vestigial keypad/button // controls //------------------------------------------------- READ8_MEMBER( philips_22vp931_device::i8049_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; } //------------------------------------------------- // i8049_datic_r - read the latched value from the // DATIC circuit //------------------------------------------------- READ8_MEMBER( philips_22vp931_device::i8049_datic_r ) { return m_daticval; } //------------------------------------------------- // i8049_from_controller_r - read the value the // external controller wrote //------------------------------------------------- READ8_MEMBER( philips_22vp931_device::i8049_from_controller_r ) { // clear the pending flag and return the data m_fromcontroller_pending = false; return m_fromcontroller; } //------------------------------------------------- // i8049_to_controller_w - write a value back to // the external controller //------------------------------------------------- WRITE8_MEMBER( philips_22vp931_device::i8049_to_controller_w ) { // set the pending flag and stash the data m_tocontroller_pending = true; m_tocontroller = data; // signal to the callback if provided if (!m_data_ready.isnull()) m_data_ready(*this, true); // also boost interleave for 4 scanlines to ensure proper communications machine().scheduler().boost_interleave(attotime::zero, screen().scan_period() * 4); } //------------------------------------------------- // i8049_port1_r - read the 8048 I/O port 1 //------------------------------------------------- READ8_MEMBER( philips_22vp931_device::i8049_port1_r ) { /* $80 = P17 = (in) unsure $40 = P16 = (in) /ERP from datic circuit $20 = P15 = (in) D105 */ uint8_t result = 0x00; if (!m_daticerp) result |= 0x40; return result; } //------------------------------------------------- // i8049_port1_w - write the 8048 I/O port 1 //------------------------------------------------- WRITE8_MEMBER( philips_22vp931_device::i8049_port1_w ) { /* $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 && (m_i8049_port1 ^ data) & 0x1f) { std::string flags; if (!(data & 0x10)) flags += " SPEED"; if (!(data & 0x08)) flags += " TIMENABLE"; if (!(data & 0x04)) flags += " REV"; if (!(data & 0x02)) flags += " FORW"; if (!(data & 0x01)) flags += " OPAMP"; logerror("port1: %s %s\n", flags, machine().describe_context()); } // if bit 0 is set, we are not tracking if (data & 0x01) m_trackdir = 0; // if bit 0 is clear and we weren't tracking before, initialize the state else if (m_trackdir == 0) { m_advanced = 0; // if bit 2 is clear, we are moving backwards if (!(data & 0x04)) { m_trackdir = -1; m_trackstate = 1; } // if bit 1 is clear, we are moving forward else if (!(data & 0x02)) { m_trackdir = 1; m_trackstate = 0; } } // if we have a timer, adjust it if (m_tracktimer != nullptr) { // turn it off if we're not tracking if (m_trackdir == 0) m_tracktimer->reset(); // if we just started tracking, or if the speed was changed, reprime the timer else if (((m_i8049_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::from_usec(60) : attotime::from_usec(10); // always start with an initial long delay; the code expects this m_tracktimer->adjust(attotime::from_usec(100), 0, speed); } } m_i8049_port1 = data; } //------------------------------------------------- // i8049_port2_r - read from the 8048 I/O port 2 //------------------------------------------------- READ8_MEMBER( philips_22vp931_device::i8049_port2_r ) { /* $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 */ uint8_t result = 0x00; if (!m_tocontroller_pending) result |= 0x20; if (!m_fromcontroller_pending) result |= 0x10; return result; } //------------------------------------------------- // i8049_port2_w - write the 8048 I/O port 2 //------------------------------------------------- WRITE8_MEMBER( philips_22vp931_device::i8049_port2_w ) { /* $40 = P26 = (out) cleared while data is sent back & forth; set afterwards [Not actually connected, but this is done in the code] */ } //------------------------------------------------- // i8049_t0_r - return the T0 line status, which is // connected to the DATIC's data strobe line //------------------------------------------------- READ_LINE_MEMBER( philips_22vp931_device::i8049_t0_r ) { return m_datastrobe; } //------------------------------------------------- // i8049_t1_r - return the T1 line status, which // is connected to the tracking state and is used // to count the number of tracks advanced //------------------------------------------------- READ_LINE_MEMBER( philips_22vp931_device::i8049_t1_r ) { return m_trackstate; }