/************************************************************************* 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 *************************************************************************/ #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; const device_type SIMUTREK_SPECIAL = &device_creator; // 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 }, // { 0 }, // ] { 0 }, // ^ { 0 }, // _ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, // { 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", space.device().safe_pc(), 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", space.device().safe_pc(), 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(), space.device().safe_pc()); 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, space.device().safe_pc()); 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; }