/************************************************************************* ldv1000.c Pioneer LD-V1000 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: * fix issues * add OSD *************************************************************************/ #include "emu.h" #include "ldv1000.h" #include "machine/i8255.h" #include "machine/z80ctc.h" #include "cpu/z80/z80.h" #include "cpu/z80/z80daisy.h" //************************************************************************** // DEBUGGING //************************************************************************** #define LOG_PORT_IO 0 #define LOG_STATUS_CHANGES 0 #define LOG_FRAMES_SEEN 0 #define LOG_COMMANDS 0 //************************************************************************** // CONSTANTS //************************************************************************** #define SCAN_SPEED (2000 / 30) // 2000 frames/second #define SEEK_FAST_SPEED (4000 / 30) // 4000 frames/second #define MULTIJUMP_TRACK_TIME attotime::from_usec(50) //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // devices const device_type PIONEER_LDV1000 = &device_creator; //************************************************************************** // LD-V1000 ROM AND MACHINE INTERFACES //************************************************************************** static ADDRESS_MAP_START( ldv1000_map, AS_PROGRAM, 8, pioneer_ldv1000_device ) AM_RANGE(0x0000, 0x1fff) AM_MIRROR(0x6000) AM_ROM AM_RANGE(0x8000, 0x87ff) AM_MIRROR(0x3800) AM_RAM AM_RANGE(0xc000, 0xc003) AM_MIRROR(0x9ff0) AM_DEVREADWRITE("ldvppi0", i8255_device, read, write) AM_RANGE(0xc004, 0xc007) AM_MIRROR(0x9ff0) AM_DEVREADWRITE("ldvppi1", i8255_device, read, write) ADDRESS_MAP_END static ADDRESS_MAP_START( ldv1000_portmap, AS_IO, 8, pioneer_ldv1000_device ) ADDRESS_MAP_GLOBAL_MASK(0xff) AM_RANGE(0x00, 0x07) AM_MIRROR(0x38) AM_READWRITE(z80_decoder_display_port_r, z80_decoder_display_port_w) AM_RANGE(0x40, 0x40) AM_MIRROR(0x3f) AM_READ(z80_controller_r) AM_RANGE(0x80, 0x80) AM_MIRROR(0x3f) AM_WRITE(z80_controller_w) AM_RANGE(0xc0, 0xc3) AM_MIRROR(0x3c) AM_DEVREADWRITE("ldvctc", z80ctc_device, read, write) ADDRESS_MAP_END static I8255_INTERFACE(ppi0intf) { DEVCB_NULL, DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ppi0_porta_w), DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ppi0_portb_r), DEVCB_NULL, DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ppi0_portc_r), DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ppi0_portc_w) }; static I8255_INTERFACE(ppi1intf) { DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ppi1_porta_r), DEVCB_NULL, DEVCB_NULL, DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ppi1_portb_w), DEVCB_NULL, DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ppi1_portc_w) }; static Z80CTC_INTERFACE( ctcintf ) { DEVCB_DEVICE_LINE_MEMBER(DEVICE_SELF_OWNER, pioneer_ldv1000_device, ctc_interrupt), DEVCB_NULL, DEVCB_NULL, DEVCB_NULL }; static const z80_daisy_config daisy_chain[] = { { "ldvctc" }, { NULL } }; static MACHINE_CONFIG_FRAGMENT( ldv1000 ) MCFG_CPU_ADD("ldv1000", Z80, XTAL_5MHz/2) MCFG_CPU_CONFIG(daisy_chain) MCFG_CPU_PROGRAM_MAP(ldv1000_map) MCFG_CPU_IO_MAP(ldv1000_portmap) MCFG_Z80CTC_ADD("ldvctc", XTAL_5MHz/2, ctcintf) MCFG_I8255_ADD("ldvppi0", ppi0intf) MCFG_I8255_ADD("ldvppi1", ppi1intf) MACHINE_CONFIG_END ROM_START( ldv1000 ) ROM_REGION( 0x2000, "ldv1000", 0 ) ROM_LOAD( "z03_1001_vyw-053_v1-0.bin", 0x0000, 0x2000, CRC(31ec4687) SHA1(52f91c304a878ba02b2fa1cda1a9489d6dd5a34f) ) ROM_END //************************************************************************** // PIONEER LD-V1000 IMPLEMENTATION //************************************************************************** //------------------------------------------------- // pioneer_ldv1000_device - constructor //------------------------------------------------- pioneer_ldv1000_device::pioneer_ldv1000_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : laserdisc_device(mconfig, PIONEER_LDV1000, "Pioneer LD-V1000", tag, owner, clock, "ldv1000", __FILE__), m_z80_cpu(*this, "ldv1000"), m_z80_ctc(*this, "ldvctc"), m_multitimer(NULL), m_command(0), m_status(0), m_vsync(false), m_counter_start(0), m_counter(0), m_portc0(0), m_portb1(0), m_portc1(0), m_portselect(0), m_dispindex(0), m_vbiready(false), m_vbiindex(0) { } //------------------------------------------------- // data_w - handle a parallel data write to the // LD-V1000 //------------------------------------------------- void pioneer_ldv1000_device::data_w(UINT8 data) { m_command = data; if (LOG_COMMANDS) printf("-> COMMAND = %02X (%s)\n", data, (m_portc1 & 0x10) ? "valid" : "invalid"); } //------------------------------------------------- // enter_w - set the state of the ENTER strobe //------------------------------------------------- void pioneer_ldv1000_device::enter_w(UINT8 data) { } //------------------------------------------------- // device_start - device initialization //------------------------------------------------- void pioneer_ldv1000_device::device_start() { // pass through to the parent laserdisc_device::device_start(); // allocate timers m_multitimer = timer_alloc(TID_MULTIJUMP); } //------------------------------------------------- // device_reset - device reset //------------------------------------------------- void pioneer_ldv1000_device::device_reset() { // pass through to the parent laserdisc_device::device_reset(); // reset our state m_command = 0; m_status = 0; m_vsync = false; m_counter_start = 0; m_counter = 0; m_portc0 = 0; m_portb1 = 0; m_portc1 = 0; m_portselect = 0; m_dispindex = 0; m_vbiready = false; m_vbiindex = 0; } //------------------------------------------------- // device_timer - handle timers set by this // device //------------------------------------------------- void pioneer_ldv1000_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TID_MULTIJUMP: { // bit 5 of port B on PPI 1 selects the direction of slider movement int direction = (m_portb1 & 0x20) ? 1 : -1; advance_slider(direction); // update down counter and reschedule if (--m_counter != 0) timer.adjust(MULTIJUMP_TRACK_TIME); break; } case TID_VSYNC_OFF: m_vsync = false; break; case TID_VBI_DATA_FETCH: { // appears to return data in reverse order UINT32 lines[3]; lines[0] = get_field_code(LASERDISC_CODE_LINE1718, false); lines[1] = get_field_code(LASERDISC_CODE_LINE17, false); lines[2] = get_field_code(LASERDISC_CODE_LINE16, false); // fill in the details memset(m_vbi, 0, sizeof(m_vbi)); if (focus_on() && laser_on()) { // loop over lines for (int line = 0; line < 3; line++) { UINT8 *dest = &m_vbi[line * 7]; UINT32 data = lines[line]; // the logic only processes leadin/leadout/frame number codes if (data == VBI_CODE_LEADIN || data == VBI_CODE_LEADOUT || (data & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE) { *dest++ = 0x09 | (((data & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE) ? 0x02 : 0x00); *dest++ = 0x08; *dest++ = (data >> 16) & 0x0f; *dest++ = (data >> 12) & 0x0f; *dest++ = (data >> 8) & 0x0f; *dest++ = (data >> 4) & 0x0f; *dest++ = (data >> 0) & 0x0f; } } } // signal that data is ready and reset the readback index m_vbiready = true; m_vbiindex = 0; 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_ldv1000_device::device_rom_region() const { return ROM_NAME(ldv1000); } //------------------------------------------------- // device_mconfig_additions - return a pointer to // our machine config fragment //------------------------------------------------- machine_config_constructor pioneer_ldv1000_device::device_mconfig_additions() const { return MACHINE_CONFIG_NAME(ldv1000); } //------------------------------------------------- // player_vsync - VSYNC callback, called at the // start of the blanking period //------------------------------------------------- void pioneer_ldv1000_device::player_vsync(const vbi_metadata &vbi, int fieldnum, attotime curtime) { // generate interrupts if we hit the edges slider_position sliderpos = get_slider_position(); m_z80_ctc->trigger(1, sliderpos == SLIDER_MINIMUM); m_z80_ctc->trigger(2, sliderpos == SLIDER_MAXIMUM); // 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); // boost interleave for the first 1ms to improve communications machine().scheduler().boost_interleave(attotime::zero, attotime::from_msec(1)); } //------------------------------------------------- // player_update - update callback, called on // the first visible line of the frame //------------------------------------------------- INT32 pioneer_ldv1000_device::player_update(const vbi_metadata &vbi, int fieldnum, attotime curtime) { if (LOG_FRAMES_SEEN) { int frame = frame_from_metadata(vbi); if (frame != FRAME_NOT_PRESENT) printf("== %d\n", frame); } return fieldnum; } //------------------------------------------------- // ctc_interrupt - called when the CTC triggers // an interrupt in the daisy chain //------------------------------------------------- WRITE_LINE_MEMBER( pioneer_ldv1000_device::ctc_interrupt ) { m_z80_cpu->set_input_line(0, state ? ASSERT_LINE : CLEAR_LINE); } //------------------------------------------------- // z80_decoder_display_port_w - handle writes to // the decoder/display chips //------------------------------------------------- WRITE8_MEMBER( pioneer_ldv1000_device::z80_decoder_display_port_w ) { /* TX/RX = /A0 (A0=0 -> TX, A0=1 -> RX) Display is 6-bit Decoder is 4-bit */ // writes to offset 0 select the target for reads/writes of actual data if (offset == 0) { m_portselect = data; m_dispindex = 0; } // writes to offset 2 constitute actual writes targeted toward the display and decoder chips else if (offset == 2) { // selections 0 and 1 represent the two display lines; only 6 bits are transferred if (m_portselect < 2) m_display[m_portselect][m_dispindex++ % 20] = data & 0x3f; } } //------------------------------------------------- // z80_decoder_display_port_r - handle reads from the // decoder/display chips //------------------------------------------------- READ8_MEMBER( pioneer_ldv1000_device::z80_decoder_display_port_r ) { // reads from offset 3 constitute actual reads from the display and decoder chips UINT8 result = 0; if (offset == 3) { // selection 4 represents the VBI data reading if (m_portselect == 4) { m_vbiready = false; result = m_vbi[m_vbiindex++ % ARRAY_LENGTH(m_vbi)]; } } return result; } //------------------------------------------------- // z80_controller_r - handle read of the data from // the controlling system //------------------------------------------------- READ8_MEMBER( pioneer_ldv1000_device::z80_controller_r ) { // note that this is a cheesy implementation; the real thing relies on exquisite timing UINT8 result = m_command ^ 0xff; m_command = 0xff; return result; } //------------------------------------------------- // z80_controller_w - handle status latch writes //------------------------------------------------- WRITE8_MEMBER( pioneer_ldv1000_device::z80_controller_w ) { if (LOG_STATUS_CHANGES && data != m_status) printf("%04X:CONTROLLER.W=%02X\n", space.device().safe_pc(), data); m_status = data; } //------------------------------------------------- // ppi0_porta_w - handle writes to port A of // PPI #0 //------------------------------------------------- WRITE8_MEMBER( pioneer_ldv1000_device::ppi0_porta_w ) { m_counter_start = data; if (LOG_PORT_IO) printf("%s:PORTA.0=%02X\n", machine().describe_context(), data); } //------------------------------------------------- // ppi0_portb_r - handle reads from port B of // PPI #0 //------------------------------------------------- READ8_MEMBER( pioneer_ldv1000_device::ppi0_portb_r ) { return m_counter; } //------------------------------------------------- // ppi0_portc_r - handle reads from port C of // PPI #0 //------------------------------------------------- READ8_MEMBER( pioneer_ldv1000_device::ppi0_portc_r ) { /* $10 = /VSYNC $20 = IRQ from decoder chip $40 = TRKG LOOP (N24-1) $80 = DUMP (N20-1) -- code reads the state and waits for it to change */ UINT8 result = 0x00; if (!m_vsync) result |= 0x10; if (!m_vbiready) result |= 0x20; return result; } //------------------------------------------------- // ppi0_portc_w - handle writes to port C of // PPI #0 //------------------------------------------------- WRITE8_MEMBER( pioneer_ldv1000_device::ppi0_portc_w ) { /* $01 = preload on up/down counters $02 = /MULTI JUMP TRIG $04 = SCAN MODE $08 = n/c */ // set the new value UINT8 prev = m_portc0; m_portc0 = data; if (LOG_PORT_IO && ((data ^ prev) & 0x0f) != 0) { printf("%s:PORTC.0=%02X", machine().describe_context(), data); if (data & 0x01) printf(" PRELOAD"); if (!(data & 0x02)) printf(" /MULTIJUMP"); if (data & 0x04) printf(" SCANMODE"); printf("\n"); } // on the rising edge of bit 0, clock the down counter load if ((data & 0x01) && !(prev & 0x01)) m_counter = m_counter_start; // on the falling edge of bit 1, start the multi-jump timer if (!(data & 0x02) && (prev & 0x02)) m_multitimer->adjust(MULTIJUMP_TRACK_TIME); } //------------------------------------------------- // ppi1_porta_r - handle reads from port A of // PPI #1 //------------------------------------------------- READ8_MEMBER( pioneer_ldv1000_device::ppi1_porta_r ) { /* $01 = /FOCS LOCK $02 = /SPDL LOCK $04 = INSIDE $08 = OUTSIDE $10 = MOTOR STOP $20 = +5V/test point $40 = /INT LOCK $80 = 8 INCH CHK */ slider_position sliderpos = get_slider_position(); UINT8 result = 0x00; // bit 0: /FOCUS LOCK if (!focus_on()) result |= 0x01; // bit 1: /SPDL LOCK if (!spdl_on()) result |= 0x02; // bit 2: INSIDE signal if (sliderpos == SLIDER_MINIMUM) result |= 0x04; // bit 3: OUTSIDE signal if (sliderpos == SLIDER_MAXIMUM) result |= 0x08; // bit 4: MOTOR STOP // bit 5: +5V/test point result |= 0x20; // bit 6: /INT LOCK // bit 7: 8 INCH CHK return result; } //------------------------------------------------- // ppi1_portb_w - handle writes to port B of // PPI #1 //------------------------------------------------- WRITE8_MEMBER( pioneer_ldv1000_device::ppi1_portb_w ) { /* $01 = /FOCS ON $02 = /SPDL RUN $04 = /JUMP TRIG $08 = /SCAN A $10 = SCAN B $20 = SCAN C $40 = /LASER ON $80 = /SYNC ST0 */ // set the new value UINT8 prev = m_portb1; m_portb1 = data; if (LOG_PORT_IO && ((data ^ prev) & 0xff) != 0) { printf("%s:PORTB.1=%02X:", machine().describe_context(), data); if (!(data & 0x01)) printf(" FOCSON"); if (!(data & 0x02)) printf(" SPDLRUN"); if (!(data & 0x04)) printf(" JUMPTRIG"); if (!(data & 0x08)) printf(" SCANA (%c %c)", (data & 0x10) ? 'L' : 'H', (data & 0x20) ? 'F' : 'R'); if ( (data & 0x40)) printf(" LASERON"); if (!(data & 0x80)) printf(" SYNCST0"); printf("\n"); } // bit 5 selects the direction of slider movement for JUMP TRG and scanning int direction = (data & 0x20) ? 1 : -1; // on the falling edge of bit 2, jump one track in either direction if (!(data & 0x04) && (prev & 0x04)) advance_slider(direction); // bit 3 low enables scanning if (!(data & 0x08)) { // bit 4 selects the speed int delta = (data & 0x10) ? SCAN_SPEED : SEEK_FAST_SPEED; set_slider_speed(delta * direction); } // bit 3 high stops scanning else set_slider_speed(0); } //------------------------------------------------- // ppi1_portc_w - handle writes to port C of // PPI #1 //------------------------------------------------- WRITE8_MEMBER( pioneer_ldv1000_device::ppi1_portc_w ) { /* $01 = AUD 1 $02 = AUD 2 $04 = AUDIO ENABLE $08 = /VIDEO SQ $10 = COMMAND $20 = STATUS $40 = SIZE 8/12 $80 = /LED CAV */ // set the new value UINT8 prev = m_portc1; m_portc1 = data; if (LOG_PORT_IO && ((data ^ prev) & 0xcf) != 0) { printf("%s:PORTC.1=%02X", machine().describe_context(), data); if (data & 0x01) printf(" AUD1"); if (data & 0x02) printf(" AUD2"); if (data & 0x04) printf(" AUDEN"); if (!(data & 0x08)) printf(" VIDEOSQ"); if (data & 0x10) printf(" COMMAND"); if (data & 0x20) printf(" STATUS"); if (data & 0x40) printf(" SIZE8"); if (!(data & 0x80)) printf(" CAV"); printf("\n"); } // video squelch is controlled by bit 3 set_video_squelch((data & 0x08) == 0); // audio squelch is controlled by bits 0-2 set_audio_squelch(!(data & 0x04) || !(data & 0x01), !(data & 0x04) || !(data & 0x02)); }