1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /*************************************************************************
4
5 ldpr8210.c
6
7 Pioneer PR-8210 laserdisc emulation.
8
9 **************************************************************************
10
11 Still to do:
12
13 * implement SLOW TRG
14 * figure out Simutrek without jump hack
15 * figure out serial protocol issues (current hack works nicely)
16 * determine actual slow/fast speeds
17
18 *************************************************************************/
19
20
21 #include "emu.h"
22 #include "ldpr8210.h"
23
24
25
26 //**************************************************************************
27 // DEBUGGING
28 //**************************************************************************
29
30 #define LOG_VBLANK_VBI 0
31 #define LOG_SERIAL 0
32 #define LOG_SIMUTREK 0
33
34
35
36 //**************************************************************************
37 // CONSTANTS
38 //**************************************************************************
39
40 // Overlay constants, related to 720-pixel wide capture
41 #define OVERLAY_GROUP0_X (82.0f / 720.0f)
42 #define OVERLAY_GROUP1_X (162.0f / 720.0f)
43 #define OVERLAY_GROUP2_X (322.0f / 720.0f)
44 #define OVERLAY_GROUP3_X (483.0f / 720.0f)
45 #define OVERLAY_Y (104/2)
46 #define OVERLAY_PIXEL_WIDTH (4.5f / 720.0f)
47 #define OVERLAY_PIXEL_HEIGHT 2
48 #define OVERLAY_X_PIXELS 5
49 #define OVERLAY_Y_PIXELS 7
50
51 // scanning speeds
52 #define SCAN_SPEED (2000 / 30) // 2000 frames/second
53 #define SEEK_FAST_SPEED (4000 / 30) // 4000 frames/second
54
55 // serial timing, mostly from the service manual, derived from the XTAL
56 #define SERIAL_CLOCK XTAL(455'000)
57 #define SERIAL_0_BIT_TIME attotime::from_hz(SERIAL_CLOCK / 512)
58 #define SERIAL_1_BIT_TIME attotime::from_hz(SERIAL_CLOCK / 1024)
59 #define SERIAL_MIDPOINT_TIME attotime::from_hz(SERIAL_CLOCK / 600)
60 #define SERIAL_MAX_BIT_TIME attotime::from_hz(SERIAL_CLOCK / 4096)
61 #define SERIAL_MAX_WORD_TIME attotime::from_hz(SERIAL_CLOCK / 11520)
62 #define SERIAL_REJECT_DUPLICATE_TIME attotime::from_hz(SERIAL_CLOCK / 11520 / 4)
63
64
65
66 //**************************************************************************
67 // GLOBAL VARIABLES
68 //**************************************************************************
69
70 // devices
71 DEFINE_DEVICE_TYPE(PIONEER_PR8210, pioneer_pr8210_device, "pr8210", "Pioneer PR-8210")
72 DEFINE_DEVICE_TYPE(SIMUTREK_SPECIAL, simutrek_special_device, "simutrek", "Simutrek Modified PR-8210")
73
74
75 // bitmaps for the characters
76 static const uint8_t text_bitmap[0x40][7] =
77 {
78 { 0 }, // @
79 { 0x20,0x50,0x88,0x88,0xf8,0x88,0x88 }, // A
80 { 0 }, // B
81 { 0x70,0x88,0x80,0x80,0x80,0x88,0x70 }, // C
82 { 0 }, // D
83 { 0xf8,0x80,0x80,0xf0,0x80,0x80,0xf8 }, // E
84 { 0xf8,0x80,0x80,0xf0,0x80,0x80,0x80 }, // F
85 { 0 }, // G
86 { 0x88,0x88,0x88,0xf8,0x88,0x88,0x88 }, // H
87 { 0 }, // I
88 { 0 }, // J
89 { 0 }, // K
90 { 0 }, // L
91 { 0x88,0xd8,0xa8,0xa8,0xa8,0x88,0x88 }, // M
92 { 0 }, // N
93 { 0 }, // O
94 { 0xf0,0x88,0x88,0xf0,0x80,0x80,0x80 }, // P
95 { 0 }, // Q
96 { 0xf0,0x88,0x88,0xf0,0xa0,0x90,0x88 }, // R
97 { 0x70,0x88,0x80,0x70,0x08,0x88,0x70 }, // S
98 { 0 }, // T
99 { 0 }, // U
100 { 0 }, // V
101 { 0 }, // W
102 { 0 }, // X
103 { 0 }, // Y
104 { 0 }, // Z
105 { 0 }, // [
106 { 0 }, // <backslash>
107 { 0 }, // ]
108 { 0 }, // ^
109 { 0 }, // _
110
111 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, // <space>
112 { 0 }, // !
113 { 0 }, // "
114 { 0 }, // #
115 { 0 }, // $
116 { 0 }, // %
117 { 0 }, // &
118 { 0 }, // '
119 { 0 }, // (
120 { 0 }, // )
121 { 0 }, // *
122 { 0 }, // +
123 { 0 }, // ,
124 { 0 }, // -
125 { 0x00,0x00,0x00,0x00,0x00,0x00,0x40 }, // .
126 { 0 }, // /
127 { 0x70,0x88,0x88,0x88,0x88,0x88,0x70 }, // 0
128 { 0x20,0x60,0x20,0x20,0x20,0x20,0x70 }, // 1
129 { 0x70,0x88,0x08,0x70,0x80,0x80,0xf8 }, // 2
130 { 0xf8,0x08,0x10,0x30,0x08,0x88,0x70 }, // 3
131 { 0x10,0x30,0x50,0x90,0xf8,0x10,0x10 }, // 4
132 { 0xf8,0x80,0xf0,0x08,0x08,0x88,0x70 }, // 5
133 { 0x78,0x80,0x80,0xf0,0x88,0x88,0x70 }, // 6
134 { 0xf8,0x08,0x08,0x10,0x20,0x40,0x80 }, // 7
135 { 0x70,0x88,0x88,0x70,0x88,0x88,0x70 }, // 8
136 { 0x70,0x88,0x88,0x78,0x08,0x08,0xf0 }, // 9
137 { 0 }, // :
138 { 0 }, // ;
139 { 0 }, // <
140 { 0 }, // =
141 { 0 }, // >
142 { 0 } // ?
143 };
144
145
146
147 //**************************************************************************
148 // PR-8210 ROM AND MACHINE INTERFACES
149 //**************************************************************************
150
pr8210_portmap(address_map & map)151 void pioneer_pr8210_device::pr8210_portmap(address_map &map)
152 {
153 map(0x00, 0xff).rw(FUNC(pioneer_pr8210_device::i8049_pia_r), FUNC(pioneer_pr8210_device::i8049_pia_w));
154 }
155
156 ROM_START( pr8210 )
157 ROM_REGION( 0x800, "pr8210", 0 )
158 ROM_LOAD( "pr-8210_mcu_ud6005a.bin", 0x000, 0x800, CRC(120fa83b) SHA1(b514326ca1f52d6d89056868f9d17eabd4e3f31d) )
159 ROM_END
160
161
162
163 //**************************************************************************
164 // PIONEER PR-8210 IMPLEMENTATION
165 //**************************************************************************
166
167 //-------------------------------------------------
168 // pioneer_pr8210_device - constructor
169 //-------------------------------------------------
170
pioneer_pr8210_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)171 pioneer_pr8210_device::pioneer_pr8210_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
172 : pioneer_pr8210_device(mconfig, PIONEER_PR8210, tag, owner, clock)
173 {
174 }
175
pioneer_pr8210_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)176 pioneer_pr8210_device::pioneer_pr8210_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
177 : laserdisc_device(mconfig, type, tag, owner, clock),
178 m_audio1(*this, "pr8210_audio1"),
179 m_audio2(*this, "pr8210_audio2"),
180 m_clv(*this, "pr8210_clv"),
181 m_cav(*this, "pr8210_cav"),
182 m_srev(*this, "pr8210_srev"),
183 m_sfwd(*this, "pr8210_sfwd"),
184 m_play(*this, "pr8210_play"),
185 m_step(*this, "pr8210_step"),
186 m_pause(*this, "pr8210_pause"),
187 m_standby(*this, "pr8210_standby"),
188 m_control(0),
189 m_lastcommand(0),
190 m_accumulator(0),
191 m_lastcommandtime(attotime::zero),
192 m_lastbittime(attotime::zero),
193 m_firstbittime(attotime::zero),
194 m_i8049_cpu(*this, "pr8210"),
195 m_slowtrg(attotime::zero),
196 m_vsync(false),
197 m_i8049_port1(0),
198 m_i8049_port2(0)
199 {
200 }
201
202
203 //-------------------------------------------------
204 // control_w - write callback when the CONTROL
205 // line is toggled
206 //-------------------------------------------------
207
control_w(uint8_t data)208 void pioneer_pr8210_device::control_w(uint8_t data)
209 {
210 // set the new value and remember the last
211 uint8_t prev = m_control;
212 m_control = data;
213
214 // handle rising edge
215 if (prev != ASSERT_LINE && data == ASSERT_LINE)
216 {
217 // get the time difference from the last assert
218 // and update our internal command time
219 attotime curtime = machine().time();
220 attotime delta = curtime - m_lastbittime;
221 m_lastbittime = curtime;
222
223 // if we timed out since the first bit, reset the accumulator
224 attotime overalldelta = curtime - m_firstbittime;
225 if (overalldelta > SERIAL_MAX_WORD_TIME || delta > SERIAL_MAX_BIT_TIME)
226 {
227 m_firstbittime = curtime;
228 m_accumulator = 0x5555;
229 if (LOG_SERIAL)
230 logerror("Reset accumulator\n");
231 }
232
233 // 0 bit delta is 1.05 msec, 1 bit delta is 2.11 msec
234 int longpulse = (delta < SERIAL_MIDPOINT_TIME) ? 0 : 1;
235 m_accumulator = (m_accumulator << 1) | longpulse;
236
237 // log the deltas for debugging
238 if (LOG_SERIAL)
239 {
240 int usecdiff = (int)(delta.attoseconds() / ATTOSECONDS_IN_USEC(1));
241 logerror("bitdelta = %5d (%d) - accum = %04X\n", usecdiff, longpulse, m_accumulator);
242 }
243
244 // if we have a complete command, signal it
245 // a complete command is 0,0,1 followed by 5 bits, followed by 0,0
246 if ((m_accumulator & 0x383) == 0x80)
247 {
248 // data is stored to the PIA in bit-reverse order
249 uint8_t newcommand = (m_accumulator >> 2) & 0x1f;
250 m_pia.porta = bitswap<8>(newcommand, 0,1,2,3,4,5,6,7);
251
252 // the MCU logic requires a 0 to execute many commands; however, nobody
253 // consistently sends a 0, whereas they do tend to send duplicate commands...
254 // if we assume that each duplicate causes a 0, we get the correct results
255 attotime rejectuntil = m_lastcommandtime + SERIAL_REJECT_DUPLICATE_TIME;
256 m_lastcommandtime = curtime;
257 if (m_pia.porta == m_lastcommand && curtime < rejectuntil)
258 m_pia.porta = 0x00;
259 else
260 m_lastcommand = m_pia.porta;
261
262 // log the command and wait for a keypress
263 if (LOG_SERIAL)
264 logerror("--- Command = %02X\n", m_pia.porta >> 3);
265
266 // reset the first bit time so that the accumulator clears on the next write
267 m_firstbittime = curtime - SERIAL_MAX_WORD_TIME;
268 }
269 }
270 }
271
272
273 //-------------------------------------------------
274 // device_start - device initialization
275 //-------------------------------------------------
276
device_start()277 void pioneer_pr8210_device::device_start()
278 {
279 // resolve outputs
280 m_audio1.resolve();
281 m_audio2.resolve();
282 m_clv.resolve();
283 m_cav.resolve();
284 m_srev.resolve();
285 m_sfwd.resolve();
286 m_play.resolve();
287 m_step.resolve();
288 m_pause.resolve();
289 m_standby.resolve();
290
291 // pass through to the parent
292 laserdisc_device::device_start();
293 }
294
295
296 //-------------------------------------------------
297 // device_reset - device reset
298 //-------------------------------------------------
299
device_reset()300 void pioneer_pr8210_device::device_reset()
301 {
302 // pass through to the parent
303 laserdisc_device::device_reset();
304
305 // reset our state
306 attotime curtime = machine().time();
307 m_lastcommandtime = curtime;
308 m_firstbittime = curtime;
309 m_lastbittime = curtime;
310 m_slowtrg = curtime;
311 }
312
313
314 //-------------------------------------------------
315 // device_timer - handle timers set by this
316 // device
317 //-------------------------------------------------
318
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)319 void pioneer_pr8210_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
320 {
321 switch (id)
322 {
323 // update the VBI data in the PIA as soon as it is ready;
324 // this must happen early in the frame because the player
325 // logic relies on fetching it here
326 case TID_VBI_DATA_FETCH:
327
328 // logging
329 if (LOG_VBLANK_VBI)
330 {
331 uint32_t line1718 = get_field_code(LASERDISC_CODE_LINE1718, false);
332 if ((line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
333 logerror("%3d:VBI(%05d)\n", screen().vpos(), VBI_CAV_PICTURE(line1718));
334 else
335 logerror("%3d:VBI()\n", screen().vpos());
336 }
337
338 // update PIA registers based on vbi code
339 m_pia.vbi1 = 0xff;
340 m_pia.vbi2 = 0xff;
341 if (focus_on() && laser_on())
342 {
343 uint32_t line16 = get_field_code(LASERDISC_CODE_LINE16, false);
344 uint32_t line1718 = get_field_code(LASERDISC_CODE_LINE1718, false);
345 if (line1718 == VBI_CODE_LEADIN)
346 m_pia.vbi1 &= ~0x01;
347 if (line1718 == VBI_CODE_LEADOUT)
348 m_pia.vbi1 &= ~0x02;
349 if (line16 == VBI_CODE_STOP)
350 m_pia.vbi1 &= ~0x04;
351 // unsure what this bit means: m_pia.vbi1 &= ~0x08;
352 if ((line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
353 {
354 m_pia.vbi1 &= ~0x10;
355 m_pia.frame[2] = 0xf0 | ((line1718 >> 16) & 0x07);
356 m_pia.frame[3] = 0xf0 | ((line1718 >> 12) & 0x0f);
357 m_pia.frame[4] = 0xf0 | ((line1718 >> 8) & 0x0f);
358 m_pia.frame[5] = 0xf0 | ((line1718 >> 4) & 0x0f);
359 m_pia.frame[6] = 0xf0 | ((line1718 >> 0) & 0x0f);
360 }
361 if ((line1718 & VBI_MASK_CHAPTER) == VBI_CODE_CHAPTER)
362 {
363 m_pia.vbi2 &= ~0x01;
364 m_pia.frame[0] = 0xf0 | ((line1718 >> 16) & 0x07);
365 m_pia.frame[1] = 0xf0 | ((line1718 >> 12) & 0x0f);
366 }
367 }
368 break;
369
370 // clear the VSYNC flag
371 case TID_VSYNC_OFF:
372 m_vsync = false;
373 break;
374
375 // pass everything else onto the parent
376 default:
377 laserdisc_device::device_timer(timer, id, param, ptr);
378 break;
379 }
380 }
381
382
383 //-------------------------------------------------
384 // device_rom_region - return a pointer to our
385 // ROM region definitions
386 //-------------------------------------------------
387
device_rom_region() const388 const tiny_rom_entry *pioneer_pr8210_device::device_rom_region() const
389 {
390 return ROM_NAME(pr8210);
391 }
392
393
394 //-------------------------------------------------
395 // device_add_mconfig - add device configuration
396 //-------------------------------------------------
397
device_add_mconfig(machine_config & config)398 void pioneer_pr8210_device::device_add_mconfig(machine_config &config)
399 {
400 I8049(config, m_i8049_cpu, XTAL(4'410'000));
401 m_i8049_cpu->set_addrmap(AS_IO, &pioneer_pr8210_device::pr8210_portmap);
402 m_i8049_cpu->bus_in_cb().set(FUNC(pioneer_pr8210_device::i8049_bus_r));
403 m_i8049_cpu->p1_out_cb().set(FUNC(pioneer_pr8210_device::i8049_port1_w));
404 m_i8049_cpu->p2_out_cb().set(FUNC(pioneer_pr8210_device::i8049_port2_w));
405 m_i8049_cpu->t0_in_cb().set(FUNC(pioneer_pr8210_device::i8049_t0_r));
406 m_i8049_cpu->t1_in_cb().set(FUNC(pioneer_pr8210_device::i8049_t1_r));
407 }
408
409
410 //-------------------------------------------------
411 // player_vsync - VSYNC callback, called at the
412 // start of the blanking period
413 //-------------------------------------------------
414
player_vsync(const vbi_metadata & vbi,int fieldnum,const attotime & curtime)415 void pioneer_pr8210_device::player_vsync(const vbi_metadata &vbi, int fieldnum, const attotime &curtime)
416 {
417 // logging
418 if (LOG_VBLANK_VBI)
419 {
420 if ((vbi.line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
421 logerror("%3d:VSYNC(%d,%05d)\n", screen().vpos(), fieldnum, VBI_CAV_PICTURE(vbi.line1718));
422 else
423 logerror("%3d:VSYNC(%d)\n", screen().vpos(), fieldnum);
424 }
425
426 // signal VSYNC and set a timer to turn it off
427 m_vsync = true;
428 timer_set(screen().scan_period() * 4, TID_VSYNC_OFF);
429
430 // also set a timer to fetch the VBI data when it is ready
431 timer_set(screen().time_until_pos(19*2), TID_VBI_DATA_FETCH);
432 }
433
434
435 //-------------------------------------------------
436 // player_update - update callback, called on the
437 // first visible line of the frame
438 //-------------------------------------------------
439
player_update(const vbi_metadata & vbi,int fieldnum,const attotime & curtime)440 int32_t pioneer_pr8210_device::player_update(const vbi_metadata &vbi, int fieldnum, const attotime &curtime)
441 {
442 // logging
443 if (LOG_VBLANK_VBI)
444 logerror("%3d:Update(%d)\n", screen().vpos(), fieldnum);
445
446 // if the spindle is on, we advance by 1 track after completing field #1
447 return spdl_on() ? fieldnum : 0;
448 }
449
450
451 //-------------------------------------------------
452 // player_overlay - overlay callback, called
453 // during frame processing in update to overlay
454 // player data
455 //-------------------------------------------------
456
player_overlay(bitmap_yuy16 & bitmap)457 void pioneer_pr8210_device::player_overlay(bitmap_yuy16 &bitmap)
458 {
459 // custom display
460 if (m_pia.display)
461 {
462 overlay_draw_group(bitmap, &m_pia.text[2], 5, OVERLAY_GROUP1_X);
463 overlay_draw_group(bitmap, &m_pia.text[7], 5, OVERLAY_GROUP2_X);
464 overlay_draw_group(bitmap, &m_pia.text[12], 5, OVERLAY_GROUP3_X);
465 }
466
467 // chapter/frame display
468 else
469 {
470 // frame display
471 if (m_pia.latchdisplay & 2)
472 overlay_draw_group(bitmap, &m_pia.text[2], 5, OVERLAY_GROUP1_X);
473
474 // chapter overlay
475 if (m_pia.latchdisplay & 1)
476 overlay_draw_group(bitmap, &m_pia.text[0], 2, OVERLAY_GROUP0_X);
477 }
478 m_pia.latchdisplay = 0;
479 }
480
481
482 //-------------------------------------------------
483 // i8049_pia_r - handle reads from the mystery
484 // Pioneer PIA
485 //-------------------------------------------------
486
i8049_pia_r(offs_t offset)487 uint8_t pioneer_pr8210_device::i8049_pia_r(offs_t offset)
488 {
489 uint8_t result = 0xff;
490 switch (offset)
491 {
492 // (20-26) 7 characters for the chapter/frame
493 case 0x20: case 0x21:
494 case 0x22: case 0x23: case 0x24: case 0x25: case 0x26:
495 result = m_pia.frame[offset - 0x20];
496 break;
497
498 // (1D-1F,27) invalid read but normal
499 case 0x1d: case 0x1e: case 0x1f:
500 case 0x27:
501 break;
502
503 // (A0) port A value (from serial decoder)
504 case 0xa0:
505 result = m_pia.porta;
506 break;
507
508 // (C0) VBI decoding state 1
509 case 0xc0:
510 if (LOG_VBLANK_VBI)
511 logerror("%3d:PIA(C0)\n", screen().vpos());
512 result = m_pia.vbi1;
513 break;
514
515 // (E0) VBI decoding state 2
516 case 0xe0:
517 if (LOG_VBLANK_VBI)
518 logerror("%3d:PIA(E0)\n", screen().vpos());
519 result = m_pia.vbi2;
520 break;
521
522 default:
523 logerror("%s Unknown PR-8210 PIA read from offset %02X\n", machine().describe_context(), offset);
524 break;
525 }
526 return result;
527 }
528
529
530 //-------------------------------------------------
531 // i8049_pia_w - handle writes to the mystery
532 // Pioneer PIA
533 //-------------------------------------------------
534
i8049_pia_w(offs_t offset,uint8_t data)535 void pioneer_pr8210_device::i8049_pia_w(offs_t offset, uint8_t data)
536 {
537 uint8_t value;
538 switch (offset)
539 {
540 // (20-30) 17 characters for the display
541 case 0x20: case 0x21:
542 case 0x22: case 0x23: case 0x24: case 0x25: case 0x26:
543 case 0x27: case 0x28: case 0x29: case 0x2a: case 0x2b:
544 case 0x2c: case 0x2d: case 0x2e: case 0x2f: case 0x30:
545 m_pia.text[offset - 0x20] = data;
546 break;
547
548 // (40) control lines
549 case 0x40:
550
551 // toggle bit 0 to latch chapter number into display area
552 if (!(data & 0x01) && (m_pia.control & 0x01))
553 {
554 memcpy(&m_pia.text[0], &m_pia.frame[0], 2);
555 m_pia.latchdisplay |= 1;
556 }
557
558 // toggle bit 1 to latch frame number into display area
559 if (!(data & 0x02) && (m_pia.control & 0x02))
560 {
561 memcpy(&m_pia.text[2], &m_pia.frame[2], 5);
562 m_pia.latchdisplay |= 2;
563 }
564 m_pia.control = data;
565 break;
566
567 // (60) port B value (LEDs)
568 case 0x60:
569
570 // these 4 are direct-connect
571 m_audio1 = BIT(data, 0);
572 m_audio2 = BIT(data, 1);
573 m_clv = BIT(data, 2);
574 m_cav = BIT(data, 3);
575
576 // remaining 3 bits select one of 5 LEDs via a mux
577 value = ((data & 0x40) >> 6) | ((data & 0x20) >> 4) | ((data & 0x10) >> 2);
578 m_srev = (value == 0);
579 m_sfwd = (value == 1);
580 m_play = (value == 2);
581 m_step = (value == 3);
582 m_pause = (value == 4);
583
584 m_pia.portb = data;
585 update_audio_squelch();
586 break;
587
588 // (80) display enable
589 case 0x80:
590 m_pia.display = data & 0x01;
591 break;
592
593 // no other writes known
594 default:
595 logerror("%s Unknown PR-8210 PIA write to offset %02X = %02X\n", machine().describe_context(), offset, data);
596 break;
597 }
598 }
599
600
601 //-------------------------------------------------
602 // i8049_bus_r - handle reads from the 8049 BUS
603 // input, which is enabled via the PIA above
604 //-------------------------------------------------
605
i8049_bus_r()606 uint8_t pioneer_pr8210_device::i8049_bus_r()
607 {
608 /*
609 $80 = n/c
610 $40 = (in) slider pot interrupt source (slider position limit detector, inside and outside)
611 $20 = n/c
612 $10 = (in) /FOCUS LOCK
613 $08 = (in) /SPDL LOCK
614 $04 = (in) SIZE 8/12
615 $02 = (in) FG via op-amp (spindle motor stop detector)
616 $01 = (in) SLOW TIMER OUT
617 */
618
619 uint8_t result = 0x00;
620
621 // bus bit 6: slider position limit detector, inside and outside
622 slider_position sliderpos = get_slider_position();
623 if (sliderpos != SLIDER_MINIMUM && sliderpos != SLIDER_MAXIMUM)
624 result |= 0x40;
625
626 // bus bit 4: /FOCUS LOCK
627 if (!focus_on())
628 result |= 0x10;
629
630 // bus bit 3: /SPDL LOCK
631 if (!spdl_on())
632 result |= 0x08;
633
634 // bus bit 1: spindle motor stop detector
635 if (!spdl_on())
636 result |= 0x02;
637
638 // bus bit 0: SLOW TIMER OUT
639
640 // loop at beginning waits for $40=0, $02=1
641 return result;
642 }
643
644
645 //-------------------------------------------------
646 // i8049_port1_w - handle writes to the 8049
647 // port #1
648 //-------------------------------------------------
649
i8049_port1_w(uint8_t data)650 void pioneer_pr8210_device::i8049_port1_w(uint8_t data)
651 {
652 /*
653 $80 = (out) SCAN C (F/R)
654 $40 = (out) AUDIO SQ
655 $20 = (out) VIDEO SQ
656 $10 = (out) /SPDL ON
657 $08 = (out) /FOCUS ON
658 $04 = (out) SCAN B (L/H)
659 $02 = (out) SCAN A (/SCAN)
660 $01 = (out) JUMP TRG (jump back trigger, clock on high->low)
661 */
662
663 // set the new value
664 uint8_t prev = m_i8049_port1;
665 m_i8049_port1 = data;
666
667 // bit 7 selects the direction of slider movement for JUMP TRG and scanning
668 int direction = (data & 0x80) ? 1 : -1;
669
670 // on the falling edge of bit 0, jump one track in either direction
671 if (!(data & 0x01) && (prev & 0x01))
672 {
673 // special override for the Simutrek, which takes over control of this is some situations
674 if (!override_control())
675 {
676 if (LOG_SIMUTREK)
677 logerror("%3d:JUMP TRG\n", screen().vpos());
678 advance_slider(direction);
679 }
680 else if (LOG_SIMUTREK)
681 logerror("%3d:Skipped JUMP TRG\n", screen().vpos());
682 }
683
684 // bit 1 low enables scanning
685 if (!(data & 0x02))
686 {
687 // bit 2 selects the speed
688 int delta = (data & 0x04) ? SCAN_SPEED : SEEK_FAST_SPEED;
689 set_slider_speed(delta * direction);
690 }
691
692 // bit 1 high stops scanning
693 else
694 set_slider_speed(0);
695
696 // video squelch is controlled by bit 5; audio squelch is controlled by bit 6
697 update_video_squelch();
698 update_audio_squelch();
699 }
700
701
702 //-------------------------------------------------
703 // i8049_port2_w - handle writes to the 8049
704 // port #2
705 //-------------------------------------------------
706
i8049_port2_w(uint8_t data)707 void pioneer_pr8210_device::i8049_port2_w(uint8_t data)
708 {
709 /*
710 $80 = (out) /CS on PIA
711 $40 = (out) 0 to self-generate IRQ
712 $20 = (out) SLOW TRG
713 $10 = (out) STANDBY LED
714 $08 = (out) TP2
715 $04 = (out) TP1
716 $02 = (out) ???
717 $01 = (out) LASER ON
718 */
719
720 // set the new value
721 uint8_t prev = m_i8049_port2;
722 m_i8049_port2 = data;
723
724 // on the falling edge of bit 5, start the slow timer
725 if (!BIT(data, 5) && BIT(prev, 5))
726 m_slowtrg = machine().time();
727
728 // bit 6 when low triggers an IRQ on the MCU
729 m_i8049_cpu->set_input_line(MCS48_INPUT_IRQ, BIT(data, 6) ? CLEAR_LINE : ASSERT_LINE);
730
731 // standby LED is set accordingly to bit 4
732 m_standby = BIT(data, 4);
733 }
734
735
736 //-------------------------------------------------
737 // i8049_t0_r - return the state of the 8049
738 // T0 input (connected to VSYNC)
739 //-------------------------------------------------
740
i8049_t0_r()741 int pioneer_pr8210_device::i8049_t0_r()
742 {
743 // returns VSYNC state
744 return !m_vsync;
745 }
746
747
748 //-------------------------------------------------
749 // i8049_t1_r - return the state of the 8049
750 // T1 input (pulled high)
751 //-------------------------------------------------
752
i8049_t1_r()753 int pioneer_pr8210_device::i8049_t1_r()
754 {
755 return 1;
756 }
757
758
759 //-------------------------------------------------
760 // overlay_draw_group - draw a single group of
761 // characters
762 //-------------------------------------------------
763
overlay_draw_group(bitmap_yuy16 & bitmap,const uint8_t * text,int count,float xstart)764 void pioneer_pr8210_device::overlay_draw_group(bitmap_yuy16 &bitmap, const uint8_t *text, int count, float xstart)
765 {
766 // rease the background
767 overlay_erase(bitmap, xstart, xstart + ((OVERLAY_X_PIXELS + 1) * count + 1) * OVERLAY_PIXEL_WIDTH);
768
769 // draw each character, suppressing leading 0's
770 bool skip = true;
771 for (int x = 0; x < count; x++)
772 if (!skip || x == count - 1 || (text[x] & 0x3f) != 0x30)
773 {
774 skip = false;
775 overlay_draw_char(bitmap, text[x], xstart + ((OVERLAY_X_PIXELS + 1) * x + 1) * OVERLAY_PIXEL_WIDTH);
776 }
777 }
778
779
780 //-------------------------------------------------
781 // overlay_erase - erase the background area
782 // where the text overlay will be displayed
783 //-------------------------------------------------
784
overlay_erase(bitmap_yuy16 & bitmap,float xstart,float xend)785 void pioneer_pr8210_device::overlay_erase(bitmap_yuy16 &bitmap, float xstart, float xend)
786 {
787 uint32_t xmin = uint32_t(xstart * 256.0f * float(bitmap.width()));
788 uint32_t xmax = uint32_t(xend * 256.0f * float(bitmap.width()));
789
790 for (uint32_t y = OVERLAY_Y; y < (OVERLAY_Y + (OVERLAY_Y_PIXELS + 2) * OVERLAY_PIXEL_HEIGHT); y++)
791 {
792 uint16_t *dest = &bitmap.pix(y, xmin >> 8);
793 uint16_t ymax = *dest >> 8;
794 uint16_t ymin = ymax * 3 / 8;
795 uint16_t yres = ymin + ((ymax - ymin) * (xmin & 0xff)) / 256;
796 *dest = (yres << 8) | (*dest & 0xff);
797 dest++;
798
799 for (uint32_t x = (xmin | 0xff) + 1; x < xmax; x += 0x100)
800 {
801 yres = (*dest >> 8) * 3 / 8;
802 *dest = (yres << 8) | (*dest & 0xff);
803 dest++;
804 }
805
806 ymax = *dest >> 8;
807 ymin = ymax * 3 / 8;
808 yres = ymin + ((ymax - ymin) * (~xmax & 0xff)) / 256;
809 *dest = (yres << 8) | (*dest & 0xff);
810 dest++;
811 }
812 }
813
814
815 //-------------------------------------------------
816 // overlay_draw_char - draw a single character
817 // of the text overlay
818 //-------------------------------------------------
819
overlay_draw_char(bitmap_yuy16 & bitmap,uint8_t ch,float xstart)820 void pioneer_pr8210_device::overlay_draw_char(bitmap_yuy16 &bitmap, uint8_t ch, float xstart)
821 {
822 uint32_t xminbase = uint32_t(xstart * 256.0f * float(bitmap.width()));
823 uint32_t xsize = uint32_t(OVERLAY_PIXEL_WIDTH * 256.0f * float(bitmap.width()));
824
825 // iterate over pixels
826 const uint8_t *chdataptr = &text_bitmap[ch & 0x3f][0];
827 for (uint32_t y = 0; y < OVERLAY_Y_PIXELS; y++)
828 {
829 uint8_t chdata = *chdataptr++;
830
831 for (uint32_t x = 0; x < OVERLAY_X_PIXELS; x++, chdata <<= 1)
832 if (chdata & 0x80)
833 {
834 uint32_t xmin = xminbase + x * xsize;
835 uint32_t xmax = xmin + xsize;
836 for (uint32_t yy = 0; yy < OVERLAY_PIXEL_HEIGHT; yy++)
837 {
838 uint16_t *dest = &bitmap.pix(OVERLAY_Y + (y + 1) * OVERLAY_PIXEL_HEIGHT + yy, xmin >> 8);
839 uint16_t ymax = 0xff;
840 uint16_t ymin = *dest >> 8;
841 uint16_t yres = ymin + ((ymax - ymin) * (~xmin & 0xff)) / 256;
842 *dest = (yres << 8) | (*dest & 0xff);
843 dest++;
844
845 for (uint32_t xx = (xmin | 0xff) + 1; xx < xmax; xx += 0x100)
846 *dest++ = 0xf080;
847
848 ymax = 0xff;
849 ymin = *dest >> 8;
850 yres = ymin + ((ymax - ymin) * (xmax & 0xff)) / 256;
851 *dest = (yres << 8) | (*dest & 0xff);
852 dest++;
853 }
854 }
855 }
856 }
857
858
859
860 //**************************************************************************
861 // SIMUTREK ROM AND MACHINE INTERFACES
862 //**************************************************************************
863
simutrek_portmap(address_map & map)864 void simutrek_special_device::simutrek_portmap(address_map &map)
865 {
866 map(0x00, 0xff).r(FUNC(simutrek_special_device::i8748_data_r));
867 }
868
869
870 ROM_START( simutrek )
871 ROM_REGION( 0x800, "pr8210", 0 )
872 ROM_LOAD( "pr-8210_mcu_ud6005a.bin", 0x000, 0x800, CRC(120fa83b) SHA1(b514326ca1f52d6d89056868f9d17eabd4e3f31d) )
873
874 ROM_REGION( 0x400, "simutrek", 0)
CRC(eed3e728)875 ROM_LOAD( "laser_player_interface_d8748_a308.bin", 0x0000, 0x0400, CRC(eed3e728) SHA1(1eb3467f1c41553375b2c21952cd593b167f5416) )
876 ROM_END
877
878
879
880 //**************************************************************************
881 // SIMUTREK IMPLEMENTATION
882 //**************************************************************************
883
884 //-------------------------------------------------
885 // simutrek_special_device - constructor
886 //-------------------------------------------------
887
888 simutrek_special_device::simutrek_special_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
889 : pioneer_pr8210_device(mconfig, SIMUTREK_SPECIAL, tag, owner, clock),
890 m_i8748_cpu(*this, "simutrek"),
891 m_audio_squelch(0),
892 m_data(0),
893 m_data_ready(false),
894 m_i8748_port2(0),
895 m_controlnext(0),
896 m_controlthis(0)
897 {
898 }
899
900
901 //-------------------------------------------------
902 // data_w - write callback when the parallel data
903 // port is written to
904 //-------------------------------------------------
905
data_w(uint8_t data)906 void simutrek_special_device::data_w(uint8_t data)
907 {
908 synchronize(TID_LATCH_DATA, data);
909 if (LOG_SIMUTREK)
910 logerror("%03d:**** Simutrek Command = %02X\n", screen().vpos(), data);
911 }
912
913
914 //-------------------------------------------------
915 // set_external_audio_squelch - Simutrek-specific
916 // command to enable/disable audio squelch
917 //-------------------------------------------------
918
set_external_audio_squelch(int state)919 void simutrek_special_device::set_external_audio_squelch(int state)
920 {
921 if (LOG_SIMUTREK && m_audio_squelch != (state == 0))
922 logerror("--> audio squelch = %d\n", state == 0);
923 m_audio_squelch = (state == 0);
924 update_audio_squelch();
925 }
926
927
928 //-------------------------------------------------
929 // player_vsync - VSYNC callback, called at the
930 // start of the blanking period
931 //-------------------------------------------------
932
player_vsync(const vbi_metadata & vbi,int fieldnum,const attotime & curtime)933 void simutrek_special_device::player_vsync(const vbi_metadata &vbi, int fieldnum, const attotime &curtime)
934 {
935 // latch the control state after the second field
936 if (fieldnum == 1)
937 {
938 m_controlthis = m_controlnext;
939 m_controlnext = 0;
940 }
941
942 // call the parent
943 if (LOG_SIMUTREK)
944 logerror("%3d:VSYNC(%d)\n", screen().vpos(), fieldnum);
945 pioneer_pr8210_device::player_vsync(vbi, fieldnum, curtime);
946
947 // process data
948 if (m_data_ready)
949 {
950 if (LOG_SIMUTREK)
951 logerror("%3d:VSYNC IRQ\n", screen().vpos());
952 m_i8748_cpu->set_input_line(MCS48_INPUT_IRQ, ASSERT_LINE);
953 timer_set(screen().scan_period(), TID_IRQ_OFF);
954 }
955 }
956
957
958 //-------------------------------------------------
959 // device_start - device initialization
960 //-------------------------------------------------
961
device_start()962 void simutrek_special_device::device_start()
963 {
964 // pass through to the parent
965 pioneer_pr8210_device::device_start();
966 }
967
968
969 //-------------------------------------------------
970 // device_reset - device reset
971 //-------------------------------------------------
972
device_reset()973 void simutrek_special_device::device_reset()
974 {
975 // standard PR-8210 initialization
976 pioneer_pr8210_device::device_reset();
977
978 // initialize the Simutrek state
979 // for proper synchronization of initial attract mode, this needs to be set
980 m_data_ready = true;
981 }
982
983
984 //-------------------------------------------------
985 // device_timer - handle timers set by this
986 // device
987 //-------------------------------------------------
988
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)989 void simutrek_special_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
990 {
991 switch (id)
992 {
993 // clear the 8748 IRQ
994 case TID_IRQ_OFF:
995 m_i8748_cpu->set_input_line(MCS48_INPUT_IRQ, CLEAR_LINE);
996 break;
997
998 // latch data
999 case TID_LATCH_DATA:
1000 m_data = param;
1001 m_data_ready = true;
1002 break;
1003
1004 // pass everything else onto the parent
1005 default:
1006 pioneer_pr8210_device::device_timer(timer, id, param, ptr);
1007 break;
1008 }
1009 }
1010
1011
1012 //-------------------------------------------------
1013 // device_rom_region - return a pointer to our
1014 // ROM region definitions
1015 //-------------------------------------------------
1016
device_rom_region() const1017 const tiny_rom_entry *simutrek_special_device::device_rom_region() const
1018 {
1019 return ROM_NAME(simutrek);
1020 }
1021
1022
1023 //-------------------------------------------------
1024 // device_add_mconfig - add device configuration
1025 //-------------------------------------------------
1026
device_add_mconfig(machine_config & config)1027 void simutrek_special_device::device_add_mconfig(machine_config &config)
1028 {
1029 i8748_device &special(I8748(config, "simutrek", XTAL(6'000'000)));
1030 special.set_addrmap(AS_IO, &simutrek_special_device::simutrek_portmap);
1031 special.p2_in_cb().set(FUNC(simutrek_special_device::i8748_port2_r));
1032 special.p2_out_cb().set(FUNC(simutrek_special_device::i8748_port2_w));
1033 special.t0_in_cb().set(FUNC(simutrek_special_device::i8748_t0_r));
1034
1035 pioneer_pr8210_device::device_add_mconfig(config);
1036 }
1037
1038
1039 //-------------------------------------------------
1040 // i8748_port2_r - handle reads from the 8748
1041 // port #2
1042 //-------------------------------------------------
1043
i8748_port2_r()1044 uint8_t simutrek_special_device::i8748_port2_r()
1045 {
1046 // bit $80 is the pr8210 video squelch
1047 return (m_i8049_port1 & 0x20) ? 0x00 : 0x80;
1048 }
1049
1050
1051 //-------------------------------------------------
1052 // i8748_port2_w - handle writes to the 8748
1053 // port #2
1054 //-------------------------------------------------
1055
i8748_port2_w(uint8_t data)1056 void simutrek_special_device::i8748_port2_w(uint8_t data)
1057 {
1058 // update stat
1059 uint8_t prev = m_i8748_port2;
1060 m_i8748_port2 = data;
1061
1062 // bit $20 goes to the serial line
1063 if ((data ^ prev) & 0x20)
1064 pioneer_pr8210_device::control_w((data & 0x20) ? ASSERT_LINE : CLEAR_LINE);
1065
1066 // bit $10 goes to JUMP TRG
1067 // bit $08 controls direction
1068 if (!(data & 0x10) && (prev & 0x10))
1069 {
1070 int direction = (data & 0x08) ? 1 : -1;
1071 if (LOG_SIMUTREK)
1072 logerror("%3d:JUMP TRG %s\n", screen().vpos(), machine().describe_context());
1073 advance_slider(direction);
1074 }
1075
1076 // bit $04 controls who owns the JUMP TRG command
1077 if (LOG_SIMUTREK && ((data ^ prev) & 0x04))
1078 logerror("%3d:Simutrek ownership line = %d %s\n", screen().vpos(), (data >> 2) & 1, machine().describe_context());
1079 m_controlnext = (~data >> 2) & 1;
1080
1081 // bits $03 control something (status?)
1082 if (LOG_SIMUTREK && ((data ^ prev) & 0x03))
1083 logerror("Simutrek Status = %d\n", data & 0x03);
1084 }
1085
1086
1087 //-------------------------------------------------
1088 // i8748_data_r - handle external 8748 data reads
1089 //-------------------------------------------------
1090
i8748_data_r()1091 uint8_t simutrek_special_device::i8748_data_r()
1092 {
1093 // acknowledge the read and clear the data ready flag
1094 m_data_ready = false;
1095 return m_data;
1096 }
1097
1098
1099 //-------------------------------------------------
1100 // i8748_t0_r - return the status of the 8748
1101 // T0 input
1102 //-------------------------------------------------
1103
i8748_t0_r()1104 int simutrek_special_device::i8748_t0_r()
1105 {
1106 // return 1 if data is waiting from main CPU
1107 return m_data_ready;
1108 }
1109