1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /*************************************************************************
4
5 ldvp931.c
6
7 Philips 22VP931 laserdisc emulation.
8
9 **************************************************************************
10
11 Still to do:
12
13 * determine actual slow/fast speeds
14 *
15
16 *************************************************************************/
17
18
19 #include "emu.h"
20 #include "ldvp931.h"
21
22
23
24 //**************************************************************************
25 // DEBUGGING
26 //**************************************************************************
27
28 #define LOG_COMMANDS 0
29 #define LOG_PORTS 0
30
31
32
33 //**************************************************************************
34 // CONSTANTS
35 //**************************************************************************
36
37 // scanning speeds
38 #define SCAN_SPEED (2000 / 30) // 2000 frames/second
39 #define SCAN_FAST_SPEED (4000 / 30) // 4000 frames/second
40
41
42
43 //**************************************************************************
44 // GLOBAL VARIABLES
45 //**************************************************************************
46
47 // devices
48 DEFINE_DEVICE_TYPE(PHILIPS_22VP931, philips_22vp931_device, "22vp931", "Philips 22VP931")
49
50
51
52 //**************************************************************************
53 // 22VP931 ROM AND MACHINE INTERFACES
54 //**************************************************************************
55
vp931_portmap(address_map & map)56 void philips_22vp931_device::vp931_portmap(address_map &map)
57 {
58 map(0x00, 0x00).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_keypad_r), FUNC(philips_22vp931_device::i8049_output0_w));
59 map(0x10, 0x10).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_unknown_r), FUNC(philips_22vp931_device::i8049_output1_w));
60 map(0x20, 0x20).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_datic_r), FUNC(philips_22vp931_device::i8049_lcd_w));
61 map(0x30, 0x30).mirror(0xcf).rw(FUNC(philips_22vp931_device::i8049_from_controller_r), FUNC(philips_22vp931_device::i8049_to_controller_w));
62 }
63
64
65 ROM_START( vp931 )
66 ROM_REGION( 0x800, "vp931", 0 )
CRC(e11b3c8d)67 ROM_LOAD( "at-6-1_a.bin", 0x000, 0x800, CRC(e11b3c8d) SHA1(ea2d7f6a044ed085ce5e09d8b1b1a21c37f0e9b8) )
68 ROM_END
69
70
71
72 //**************************************************************************
73 // PHILIPS 22VP931 IMPLEMENTATION
74 //**************************************************************************
75
76 //-------------------------------------------------
77 // philips_22vp931_device - constructor
78 //-------------------------------------------------
79
80 philips_22vp931_device::philips_22vp931_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
81 : laserdisc_device(mconfig, PHILIPS_22VP931, tag, owner, clock),
82 m_i8049_cpu(*this, "vp931"),
83 m_tracktimer(nullptr),
84 m_i8049_out0(0),
85 m_i8049_out1(0),
86 m_i8049_port1(0),
87 m_daticval(0),
88 m_daticerp(0),
89 m_datastrobe(0),
90 m_fromcontroller(0),
91 m_fromcontroller_pending(false),
92 m_tocontroller(0),
93 m_tocontroller_pending(false),
94 m_trackdir(0),
95 m_trackstate(0),
96 m_cmdcount(0),
97 m_advanced(0)
98 {
99 }
100
101
102 //-------------------------------------------------
103 // reset_w - write to the reset line
104 //-------------------------------------------------
105
reset_w(uint8_t data)106 void philips_22vp931_device::reset_w(uint8_t data)
107 {
108 // control the CPU state
109 m_i8049_cpu->set_input_line(INPUT_LINE_RESET, data);
110
111 // on an assert, reset the device state as well
112 if (data == ASSERT_LINE)
113 reset();
114 }
115
116
117 //-------------------------------------------------
118 // data_r - handle a parallel data read from the
119 // 22VP931
120 //-------------------------------------------------
121
data_r()122 uint8_t philips_22vp931_device::data_r()
123 {
124 // if data is pending, clear the pending flag and notify any callbacks
125 if (m_tocontroller_pending)
126 {
127 m_tocontroller_pending = false;
128 if (!m_data_ready.isnull())
129 m_data_ready(*this, false);
130 }
131
132 // also boost interleave for 4 scanlines to ensure proper communications
133 machine().scheduler().boost_interleave(attotime::zero, screen().scan_period() * 4);
134 return m_tocontroller;
135 }
136
137
138 //-------------------------------------------------
139 // device_start - device initialization
140 //-------------------------------------------------
141
device_start()142 void philips_22vp931_device::device_start()
143 {
144 // pass through to the parent
145 laserdisc_device::device_start();
146
147 // allocate a timer
148 m_tracktimer = timer_alloc(TID_HALF_TRACK);
149 }
150
151
152 //-------------------------------------------------
153 // device_reset - device reset
154 //-------------------------------------------------
155
device_reset()156 void philips_22vp931_device::device_reset()
157 {
158 // pass through to the parent
159 laserdisc_device::device_reset();
160
161 // reset our state
162 m_i8049_out0 = 0;
163 m_i8049_out1 = 0;
164 m_i8049_port1 = 0;
165
166 m_daticval = 0;
167 m_daticerp = 0;
168 m_datastrobe = 0;
169
170 m_fromcontroller = 0;
171 m_fromcontroller_pending = false;
172 m_tocontroller = 0;
173 m_tocontroller_pending = false;
174
175 m_trackdir = 0;
176 m_trackstate = 0;
177
178 m_cmdcount = 0;
179 m_advanced = 0;
180 }
181
182
183 //-------------------------------------------------
184 // device_timer - handle timers set by this
185 // device
186 //-------------------------------------------------
187
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)188 void philips_22vp931_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
189 {
190 switch (id)
191 {
192 case TID_VBI_DATA_FETCH:
193 {
194 uint32_t line = param >> 2;
195 int which = param & 3;
196 uint32_t code = 0;
197
198 // fetch the code and compute the DATIC latched value
199 if (line >= LASERDISC_CODE_LINE16 && line <= LASERDISC_CODE_LINE18)
200 code = get_field_code(laserdisc_field_code(line), false);
201
202 // at the start of each line, signal an interrupt and use a timer to turn it off
203 if (which == 0)
204 {
205 m_i8049_cpu->set_input_line(MCS48_INPUT_IRQ, ASSERT_LINE);
206 timer_set(attotime::from_nsec(5580), TID_IRQ_OFF);
207 }
208
209 // clock the data strobe on each subsequent callback
210 else if (code != 0)
211 {
212 m_daticval = code >> (8 * (3 - which));
213 m_datastrobe = 1;
214 timer_set(attotime::from_nsec(5000), TID_DATA_STROBE_OFF);
215 }
216
217 // determine the next bit to fetch and reprime ourself
218 if (++which == 4)
219 {
220 which = 0;
221 line++;
222 }
223 if (line <= LASERDISC_CODE_LINE18 + 1)
224 timer_set(screen().time_until_pos(line*2, which * 2 * screen().width() / 4), TID_VBI_DATA_FETCH, (line << 2) + which);
225 break;
226 }
227
228 case TID_DEFERRED_DATA:
229 // set the value and mark it pending
230 if (LOG_COMMANDS && m_fromcontroller_pending)
231 printf("Dropped previous command byte\n");
232 m_fromcontroller = param;
233 m_fromcontroller_pending = true;
234
235 // track the commands for debugging purposes
236 if (m_cmdcount < ARRAY_LENGTH(m_cmdbuf))
237 {
238 m_cmdbuf[m_cmdcount++ % 3] = param;
239 if (LOG_COMMANDS && m_cmdcount % 3 == 0)
240 printf("Cmd: %02X %02X %02X\n", m_cmdbuf[0], m_cmdbuf[1], m_cmdbuf[2]);
241 }
242 break;
243
244 case TID_IRQ_OFF:
245 m_i8049_cpu->set_input_line(MCS48_INPUT_IRQ, CLEAR_LINE);
246 break;
247
248 case TID_DATA_STROBE_OFF:
249 m_datastrobe = 0;
250 break;
251
252 case TID_ERP_OFF:
253 m_daticerp = 0;
254 break;
255
256 case TID_HALF_TRACK:
257 // advance by the count and toggle the state
258 m_trackstate ^= 1;
259 if ((m_trackdir < 0 && !m_trackstate) || (m_trackdir > 0 && m_trackstate))
260 {
261 advance_slider(m_trackdir);
262 m_advanced += m_trackdir;
263 }
264 break;
265
266 // pass everything else onto the parent
267 default:
268 laserdisc_device::device_timer(timer, id, param, ptr);
269 break;
270 }
271 }
272
273
274 //-------------------------------------------------
275 // device_rom_region - return a pointer to our
276 // ROM region definitions
277 //-------------------------------------------------
278
device_rom_region() const279 const tiny_rom_entry *philips_22vp931_device::device_rom_region() const
280 {
281 return ROM_NAME(vp931);
282 }
283
284
285 //-------------------------------------------------
286 // device_add_mconfig - add device configuration
287 //-------------------------------------------------
288
device_add_mconfig(machine_config & config)289 void philips_22vp931_device::device_add_mconfig(machine_config &config)
290 {
291 I8049(config, m_i8049_cpu, XTAL(11'000'000));
292 m_i8049_cpu->set_addrmap(AS_IO, &philips_22vp931_device::vp931_portmap);
293 m_i8049_cpu->p1_in_cb().set(FUNC(philips_22vp931_device::i8049_port1_r));
294 m_i8049_cpu->p1_out_cb().set(FUNC(philips_22vp931_device::i8049_port1_w));
295 m_i8049_cpu->p2_in_cb().set(FUNC(philips_22vp931_device::i8049_port2_r));
296 m_i8049_cpu->p2_out_cb().set(FUNC(philips_22vp931_device::i8049_port2_w));
297 m_i8049_cpu->t0_in_cb().set(FUNC(philips_22vp931_device::i8049_t0_r));
298 m_i8049_cpu->t1_in_cb().set(FUNC(philips_22vp931_device::i8049_t1_r));
299 }
300
301
302 //-------------------------------------------------
303 // player_vsync - VSYNC callback, called at the
304 // start of the blanking period
305 //-------------------------------------------------
306
player_vsync(const vbi_metadata & vbi,int fieldnum,const attotime & curtime)307 void philips_22vp931_device::player_vsync(const vbi_metadata &vbi, int fieldnum, const attotime &curtime)
308 {
309 // reset our command counter (debugging only)
310 m_cmdcount = 0;
311
312 // set the ERP signal to 1 to indicate start of frame, and set a timer to turn it off
313 m_daticerp = 1;
314 timer_set(screen().time_until_pos(15*2), TID_ERP_OFF);
315 }
316
317
318 //-------------------------------------------------
319 // player_update - update callback, called on
320 // the first visible line of the frame
321 //-------------------------------------------------
322
player_update(const vbi_metadata & vbi,int fieldnum,const attotime & curtime)323 int32_t philips_22vp931_device::player_update(const vbi_metadata &vbi, int fieldnum, const attotime &curtime)
324 {
325 // set the first VBI timer to go at the start of line 16
326 timer_set(screen().time_until_pos(16*2), TID_VBI_DATA_FETCH, LASERDISC_CODE_LINE16 << 2);
327
328 // play forward by default
329 return fieldnum;
330 }
331
332
333 //-------------------------------------------------
334 // i8049_output0_w - controls audio/video squelch
335 // and other bits
336 //-------------------------------------------------
337
i8049_output0_w(uint8_t data)338 void philips_22vp931_device::i8049_output0_w(uint8_t data)
339 {
340 /*
341 $80 = n/c
342 $40 = LED (?) -> C335
343 $20 = LED (?)
344 $10 = LED (?) -> CX
345 $08 = EJECT
346 $04 = inverted -> AUDIO MUTE II
347 $02 = inverted -> AUDIO MUTE I
348 $01 = inverted -> VIDEO MUTE
349 */
350
351 if (LOG_PORTS && (m_i8049_out0 ^ data) & 0xff)
352 {
353 std::string flags;
354 if ( (data & 0x80)) flags += " ???";
355 if ( (data & 0x40)) flags += " LED1";
356 if ( (data & 0x20)) flags += " LED2";
357 if ( (data & 0x10)) flags += " LED3";
358 if ( (data & 0x08)) flags += " EJECT";
359 if (!(data & 0x04)) flags += " AUDMUTE2";
360 if (!(data & 0x02)) flags += " AUDMUTE1";
361 if (!(data & 0x01)) flags += " VIDMUTE";
362
363 logerror("out0: %s %s\n", flags, machine().describe_context());
364 m_i8049_out0 = data;
365 }
366
367 // update a/v squelch
368 set_audio_squelch(!(data & 0x02), !(data & 0x04));
369 set_video_squelch(!(data & 0x01));
370 }
371
372
373 //-------------------------------------------------
374 // i8049_output1_w - controls scanning behaviors
375 //-------------------------------------------------
376
i8049_output1_w(uint8_t data)377 void philips_22vp931_device::i8049_output1_w(uint8_t data)
378 {
379 /*
380 $80 = n/c
381 $40 = n/c
382 $20 = n/c
383 $10 = n/c
384 $08 = inverted -> SMS
385 $04 = inverted -> SSS
386 $02 = inverted -> SCAN CMD
387 $01 = OSM
388 */
389
390 int32_t speed;
391
392 if (LOG_PORTS && (m_i8049_out1 ^ data) & 0x08)
393 {
394 std::string flags;
395 if (!(data & 0x08)) flags += " SMS";
396 logerror("out1: %s %s\n", flags, machine().describe_context());
397 m_i8049_out1 = data;
398 }
399
400 // speed is 0 unless SCAN CMD is clear
401 speed = 0;
402 if (!(data & 0x02))
403 {
404 // fast/slow is based on bit 2
405 speed = (data & 0x04) ? SCAN_FAST_SPEED : SCAN_SPEED;
406
407 // direction is based on bit 0
408 if (data & 0x01)
409 speed = -speed;
410 }
411
412 // update the speed
413 set_slider_speed(speed);
414 }
415
416
417 //-------------------------------------------------
418 // i8049_lcd_w - vestigial LCD frame display
419 //-------------------------------------------------
420
i8049_lcd_w(uint8_t data)421 void philips_22vp931_device::i8049_lcd_w(uint8_t data)
422 {
423 /*
424 Frame number is written as 5 digits here; however, it is not actually
425 connected
426 */
427 }
428
429
430 //-------------------------------------------------
431 // i8049_unknown_r - unknown input port
432 //-------------------------------------------------
433
i8049_unknown_r()434 uint8_t philips_22vp931_device::i8049_unknown_r()
435 {
436 // only bit $80 is checked and its effects are minor
437 return 0x00;
438 }
439
440
441 //-------------------------------------------------
442 // i8049_keypad_r - vestigial keypad/button
443 // controls
444 //-------------------------------------------------
445
i8049_keypad_r()446 uint8_t philips_22vp931_device::i8049_keypad_r()
447 {
448 /*
449 From the code, this is apparently a vestigial keypad with basic controls:
450 $01 = play
451 $02 = still
452 $04 = jump 25 frames backward
453 $08 = jump 25 frames forward
454 $10 = search for frame 50(?)
455 $20 = search for frame 350(?)
456 $40 = reset
457 $80 = play reverse
458 */
459 return 0x00;
460 }
461
462
463 //-------------------------------------------------
464 // i8049_datic_r - read the latched value from the
465 // DATIC circuit
466 //-------------------------------------------------
467
i8049_datic_r()468 uint8_t philips_22vp931_device::i8049_datic_r()
469 {
470 return m_daticval;
471 }
472
473
474 //-------------------------------------------------
475 // i8049_from_controller_r - read the value the
476 // external controller wrote
477 //-------------------------------------------------
478
i8049_from_controller_r()479 uint8_t philips_22vp931_device::i8049_from_controller_r()
480 {
481 // clear the pending flag and return the data
482 m_fromcontroller_pending = false;
483 return m_fromcontroller;
484 }
485
486
487 //-------------------------------------------------
488 // i8049_to_controller_w - write a value back to
489 // the external controller
490 //-------------------------------------------------
491
i8049_to_controller_w(uint8_t data)492 void philips_22vp931_device::i8049_to_controller_w(uint8_t data)
493 {
494 // set the pending flag and stash the data
495 m_tocontroller_pending = true;
496 m_tocontroller = data;
497
498 // signal to the callback if provided
499 if (!m_data_ready.isnull())
500 m_data_ready(*this, true);
501
502 // also boost interleave for 4 scanlines to ensure proper communications
503 machine().scheduler().boost_interleave(attotime::zero, screen().scan_period() * 4);
504 }
505
506
507 //-------------------------------------------------
508 // i8049_port1_r - read the 8048 I/O port 1
509 //-------------------------------------------------
510
i8049_port1_r()511 uint8_t philips_22vp931_device::i8049_port1_r()
512 {
513 /*
514 $80 = P17 = (in) unsure
515 $40 = P16 = (in) /ERP from datic circuit
516 $20 = P15 = (in) D105
517 */
518
519 uint8_t result = 0x00;
520 if (!m_daticerp)
521 result |= 0x40;
522 return result;
523 }
524
525
526 //-------------------------------------------------
527 // i8049_port1_w - write the 8048 I/O port 1
528 //-------------------------------------------------
529
i8049_port1_w(uint8_t data)530 void philips_22vp931_device::i8049_port1_w(uint8_t data)
531 {
532 /*
533 $10 = P14 = (out) D104 -> /SPEED
534 $08 = P13 = (out) D103 -> /TIMER ENABLE
535 $04 = P12 = (out) D102 -> /REV
536 $02 = P11 = (out) D101 -> /FORW
537 $01 = P10 = (out) D100 -> some op-amp then to C334, B56, B332
538 */
539
540 if (LOG_PORTS && (m_i8049_port1 ^ data) & 0x1f)
541 {
542 std::string flags;
543 if (!(data & 0x10)) flags += " SPEED";
544 if (!(data & 0x08)) flags += " TIMENABLE";
545 if (!(data & 0x04)) flags += " REV";
546 if (!(data & 0x02)) flags += " FORW";
547 if (!(data & 0x01)) flags += " OPAMP";
548 logerror("port1: %s %s\n", flags, machine().describe_context());
549 }
550
551 // if bit 0 is set, we are not tracking
552 if (data & 0x01)
553 m_trackdir = 0;
554
555 // if bit 0 is clear and we weren't tracking before, initialize the state
556 else if (m_trackdir == 0)
557 {
558 m_advanced = 0;
559
560 // if bit 2 is clear, we are moving backwards
561 if (!(data & 0x04))
562 {
563 m_trackdir = -1;
564 m_trackstate = 1;
565 }
566
567 // if bit 1 is clear, we are moving forward
568 else if (!(data & 0x02))
569 {
570 m_trackdir = 1;
571 m_trackstate = 0;
572 }
573 }
574
575 // if we have a timer, adjust it
576 if (m_tracktimer != nullptr)
577 {
578 // turn it off if we're not tracking
579 if (m_trackdir == 0)
580 m_tracktimer->reset();
581
582 // if we just started tracking, or if the speed was changed, reprime the timer
583 else if (((m_i8049_port1 ^ data) & 0x11) != 0)
584 {
585 // speeds here are just guesses, but work with the player logic; this is the time per half-track
586 attotime speed = (data & 0x10) ? attotime::from_usec(60) : attotime::from_usec(10);
587
588 // always start with an initial long delay; the code expects this
589 m_tracktimer->adjust(attotime::from_usec(100), 0, speed);
590 }
591 }
592
593 m_i8049_port1 = data;
594 }
595
596
597 //-------------------------------------------------
598 // i8049_port2_r - read from the 8048 I/O port 2
599 //-------------------------------------------------
600
i8049_port2_r()601 uint8_t philips_22vp931_device::i8049_port2_r()
602 {
603 /*
604 $80 = P27 = (in) set/reset latch; set by FOC LS, reset by IGR
605 $20 = P25 = (in) D125 -> 0 when data written to controller is preset, reset to 1 when read
606 $10 = P24 = (in) D124 -> 0 when data from controller is present, reset to 1 on a read
607 */
608
609 uint8_t result = 0x00;
610 if (!m_tocontroller_pending)
611 result |= 0x20;
612 if (!m_fromcontroller_pending)
613 result |= 0x10;
614 return result;
615 }
616
617
618 //-------------------------------------------------
619 // i8049_port2_w - write the 8048 I/O port 2
620 //-------------------------------------------------
621
i8049_port2_w(uint8_t data)622 void philips_22vp931_device::i8049_port2_w(uint8_t data)
623 {
624 /*
625 $40 = P26 = (out) cleared while data is sent back & forth; set afterwards
626 [Not actually connected, but this is done in the code]
627 */
628 }
629
630
631 //-------------------------------------------------
632 // i8049_t0_r - return the T0 line status, which is
633 // connected to the DATIC's data strobe line
634 //-------------------------------------------------
635
i8049_t0_r()636 int philips_22vp931_device::i8049_t0_r()
637 {
638 return m_datastrobe;
639 }
640
641
642 //-------------------------------------------------
643 // i8049_t1_r - return the T1 line status, which
644 // is connected to the tracking state and is used
645 // to count the number of tracks advanced
646 //-------------------------------------------------
647
i8049_t1_r()648 int philips_22vp931_device::i8049_t1_r()
649 {
650 return m_trackstate;
651 }
652