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