1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /*************************************************************************
4
5 ldv1000.c
6
7 Pioneer LD-V1000 laserdisc emulation.
8
9 **************************************************************************
10
11 Still to do:
12
13 * fix issues
14 * add OSD
15
16 *************************************************************************/
17
18
19 #include "emu.h"
20 #include "ldv1000.h"
21 #include "machine/i8255.h"
22 #include "machine/z80ctc.h"
23 #include "cpu/z80/z80.h"
24 #include "machine/z80daisy.h"
25
26
27
28 //**************************************************************************
29 // DEBUGGING
30 //**************************************************************************
31
32 #define LOG_PORT_IO 0
33 #define LOG_STATUS_CHANGES 0
34 #define LOG_FRAMES_SEEN 0
35 #define LOG_COMMANDS 0
36
37
38
39 //**************************************************************************
40 // CONSTANTS
41 //**************************************************************************
42
43 #define SCAN_SPEED (2000 / 30) // 2000 frames/second
44 #define SEEK_FAST_SPEED (4000 / 30) // 4000 frames/second
45
46 #define MULTIJUMP_TRACK_TIME attotime::from_usec(50)
47
48
49
50 //**************************************************************************
51 // GLOBAL VARIABLES
52 //**************************************************************************
53
54 // devices
55 DEFINE_DEVICE_TYPE(PIONEER_LDV1000, pioneer_ldv1000_device, "ldv1000", "Pioneer LD-V1000")
56
57
58
59 //**************************************************************************
60 // LD-V1000 ROM AND MACHINE INTERFACES
61 //**************************************************************************
62
ldv1000_map(address_map & map)63 void pioneer_ldv1000_device::ldv1000_map(address_map &map)
64 {
65 map(0x0000, 0x1fff).mirror(0x6000).rom();
66 map(0x8000, 0x87ff).mirror(0x3800).ram();
67 map(0xc000, 0xc003).mirror(0x1ff0).rw("ldvppi0", FUNC(i8255_device::read), FUNC(i8255_device::write));
68 map(0xc004, 0xc007).mirror(0x1ff0).rw("ldvppi1", FUNC(i8255_device::read), FUNC(i8255_device::write));
69 }
70
71
ldv1000_portmap(address_map & map)72 void pioneer_ldv1000_device::ldv1000_portmap(address_map &map)
73 {
74 map.global_mask(0xff);
75 map(0x00, 0x07).mirror(0x38).rw(FUNC(pioneer_ldv1000_device::z80_decoder_display_port_r), FUNC(pioneer_ldv1000_device::z80_decoder_display_port_w));
76 map(0x40, 0x40).mirror(0x3f).r(FUNC(pioneer_ldv1000_device::z80_controller_r));
77 map(0x80, 0x80).mirror(0x3f).w(FUNC(pioneer_ldv1000_device::z80_controller_w));
78 map(0xc0, 0xc3).mirror(0x3c).rw(m_z80_ctc, FUNC(z80ctc_device::read), FUNC(z80ctc_device::write));
79 }
80
81
82 static const z80_daisy_config daisy_chain[] =
83 {
84 { "ldvctc" },
85 { nullptr }
86 };
87
88
89 ROM_START( ldv1000 )
90 ROM_REGION( 0x2000, "ldv1000", 0 )
91 ROM_LOAD( "z03_1001_vyw-053_v1-0.bin", 0x0000, 0x2000, CRC(31ec4687) SHA1(52f91c304a878ba02b2fa1cda1a9489d6dd5a34f) )
92 ROM_END
93
94
95
96 //**************************************************************************
97 // PIONEER LD-V1000 IMPLEMENTATION
98 //**************************************************************************
99
100 //-------------------------------------------------
101 // pioneer_ldv1000_device - constructor
102 //-------------------------------------------------
103
pioneer_ldv1000_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)104 pioneer_ldv1000_device::pioneer_ldv1000_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
105 : laserdisc_device(mconfig, PIONEER_LDV1000, tag, owner, clock),
106 m_z80_cpu(*this, "ldv1000"),
107 m_z80_ctc(*this, "ldvctc"),
108 m_multitimer(nullptr),
109 m_command_strobe_cb(*this),
110 m_command(0),
111 m_status(0),
112 m_vsync(false),
113 m_counter_start(0),
114 m_counter(0),
115 m_portc0(0),
116 m_portb1(0),
117 m_portc1(0),
118 m_portselect(0),
119 m_dispindex(0),
120 m_vbiready(false),
121 m_vbiindex(0)
122 {
123 }
124
125
126 //-------------------------------------------------
127 // data_w - handle a parallel data write to the
128 // LD-V1000
129 //-------------------------------------------------
130
data_w(uint8_t data)131 void pioneer_ldv1000_device::data_w(uint8_t data)
132 {
133 m_command = data;
134 if (LOG_COMMANDS)
135 logerror("-> COMMAND = %02X (%s)\n", data, (m_portc1 & 0x10) ? "valid" : "invalid");
136 }
137
138
139 //-------------------------------------------------
140 // enter_w - set the state of the ENTER strobe
141 //-------------------------------------------------
142
enter_w(uint8_t data)143 void pioneer_ldv1000_device::enter_w(uint8_t data)
144 {
145 }
146
147
148 //-------------------------------------------------
149 // device_start - device initialization
150 //-------------------------------------------------
151
device_start()152 void pioneer_ldv1000_device::device_start()
153 {
154 // pass through to the parent
155 laserdisc_device::device_start();
156
157 // allocate timers
158 m_multitimer = timer_alloc(TID_MULTIJUMP);
159
160 m_command_strobe_cb.resolve_safe();
161 }
162
163
164 //-------------------------------------------------
165 // device_reset - device reset
166 //-------------------------------------------------
167
device_reset()168 void pioneer_ldv1000_device::device_reset()
169 {
170 // pass through to the parent
171 laserdisc_device::device_reset();
172
173 // reset our state
174 m_command = 0;
175 m_status = 0;
176 m_vsync = false;
177 m_counter_start = 0;
178 m_counter = 0;
179 m_portc0 = 0;
180 m_portb1 = 0;
181 m_portc1 = 0;
182 m_portselect = 0;
183 m_dispindex = 0;
184 m_vbiready = false;
185 m_vbiindex = 0;
186 }
187
188
189 //-------------------------------------------------
190 // device_timer - handle timers set by this
191 // device
192 //-------------------------------------------------
193
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)194 void pioneer_ldv1000_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
195 {
196 switch (id)
197 {
198 case TID_MULTIJUMP:
199 {
200 // bit 5 of port B on PPI 1 selects the direction of slider movement
201 int direction = (m_portb1 & 0x20) ? 1 : -1;
202 advance_slider(direction);
203
204 // update down counter and reschedule
205 if (--m_counter != 0)
206 timer.adjust(MULTIJUMP_TRACK_TIME);
207 break;
208 }
209
210 case TID_VSYNC_OFF:
211 m_vsync = false;
212 break;
213
214 case TID_VBI_DATA_FETCH:
215 {
216 // appears to return data in reverse order
217 uint32_t lines[3];
218 lines[0] = get_field_code(LASERDISC_CODE_LINE1718, false);
219 lines[1] = get_field_code(LASERDISC_CODE_LINE17, false);
220 lines[2] = get_field_code(LASERDISC_CODE_LINE16, false);
221
222 // fill in the details
223 memset(m_vbi, 0, sizeof(m_vbi));
224 if (focus_on() && laser_on())
225 {
226 // loop over lines
227 for (int line = 0; line < 3; line++)
228 {
229 uint8_t *dest = &m_vbi[line * 7];
230 uint32_t data = lines[line];
231
232 // the logic only processes leadin/leadout/frame number codes
233 if (data == VBI_CODE_LEADIN || data == VBI_CODE_LEADOUT || (data & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
234 {
235 *dest++ = 0x09 | (((data & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE) ? 0x02 : 0x00);
236 *dest++ = 0x08;
237 *dest++ = (data >> 16) & 0x0f;
238 *dest++ = (data >> 12) & 0x0f;
239 *dest++ = (data >> 8) & 0x0f;
240 *dest++ = (data >> 4) & 0x0f;
241 *dest++ = (data >> 0) & 0x0f;
242 }
243 }
244 }
245
246 // signal that data is ready and reset the readback index
247 m_vbiready = true;
248 m_vbiindex = 0;
249 break;
250 }
251
252 // pass everything else onto the parent
253 default:
254 laserdisc_device::device_timer(timer, id, param, ptr);
255 break;
256 }
257 }
258
259
260 //-------------------------------------------------
261 // device_rom_region - return a pointer to our
262 // ROM region definitions
263 //-------------------------------------------------
264
device_rom_region() const265 const tiny_rom_entry *pioneer_ldv1000_device::device_rom_region() const
266 {
267 return ROM_NAME(ldv1000);
268 }
269
270
271 //-------------------------------------------------
272 // device_add_mconfig - add device configuration
273 //-------------------------------------------------
274
device_add_mconfig(machine_config & config)275 void pioneer_ldv1000_device::device_add_mconfig(machine_config &config)
276 {
277 Z80(config, m_z80_cpu, XTAL(5'000'000)/2);
278 m_z80_cpu->set_daisy_config(daisy_chain);
279 m_z80_cpu->set_addrmap(AS_PROGRAM, &pioneer_ldv1000_device::ldv1000_map);
280 m_z80_cpu->set_addrmap(AS_IO, &pioneer_ldv1000_device::ldv1000_portmap);
281
282 Z80CTC(config, m_z80_ctc, XTAL(5'000'000)/2);
283 m_z80_ctc->intr_callback().set(FUNC(pioneer_ldv1000_device::ctc_interrupt));
284
285 i8255_device &ldvppi0(I8255(config, "ldvppi0"));
286 ldvppi0.out_pa_callback().set(FUNC(pioneer_ldv1000_device::ppi0_porta_w));
287 ldvppi0.in_pb_callback().set(FUNC(pioneer_ldv1000_device::ppi0_portb_r));
288 ldvppi0.in_pc_callback().set(FUNC(pioneer_ldv1000_device::ppi0_portc_r));
289 ldvppi0.out_pc_callback().set(FUNC(pioneer_ldv1000_device::ppi0_portc_w));
290
291 i8255_device &ldvppi1(I8255(config, "ldvppi1"));
292 ldvppi1.in_pa_callback().set(FUNC(pioneer_ldv1000_device::ppi1_porta_r));
293 ldvppi1.out_pb_callback().set(FUNC(pioneer_ldv1000_device::ppi1_portb_w));
294 ldvppi1.out_pc_callback().set(FUNC(pioneer_ldv1000_device::ppi1_portc_w));
295 }
296
297
298 //-------------------------------------------------
299 // player_vsync - VSYNC callback, called at the
300 // start of the blanking period
301 //-------------------------------------------------
302
player_vsync(const vbi_metadata & vbi,int fieldnum,const attotime & curtime)303 void pioneer_ldv1000_device::player_vsync(const vbi_metadata &vbi, int fieldnum, const attotime &curtime)
304 {
305 // generate interrupts if we hit the edges
306 slider_position sliderpos = get_slider_position();
307 m_z80_ctc->trg1(sliderpos == SLIDER_MINIMUM);
308 m_z80_ctc->trg2(sliderpos == SLIDER_MAXIMUM);
309
310 // signal VSYNC and set a timer to turn it off
311 m_vsync = true;
312 timer_set(screen().scan_period() * 4, TID_VSYNC_OFF);
313
314 // also set a timer to fetch the VBI data when it is ready
315 timer_set(screen().time_until_pos(19*2), TID_VBI_DATA_FETCH);
316
317 // boost interleave for the first 1ms to improve communications
318 machine().scheduler().boost_interleave(attotime::zero, attotime::from_msec(1));
319 }
320
321
322 //-------------------------------------------------
323 // player_update - update callback, called on
324 // the first visible line of the frame
325 //-------------------------------------------------
326
player_update(const vbi_metadata & vbi,int fieldnum,const attotime & curtime)327 int32_t pioneer_ldv1000_device::player_update(const vbi_metadata &vbi, int fieldnum, const attotime &curtime)
328 {
329 if (LOG_FRAMES_SEEN)
330 {
331 int frame = frame_from_metadata(vbi);
332 if (frame != FRAME_NOT_PRESENT) logerror("== %d\n", frame);
333 }
334 return fieldnum;
335 }
336
337
338 //-------------------------------------------------
339 // ctc_interrupt - called when the CTC triggers
340 // an interrupt in the daisy chain
341 //-------------------------------------------------
342
ctc_interrupt(int state)343 void pioneer_ldv1000_device::ctc_interrupt(int state)
344 {
345 m_z80_cpu->set_input_line(0, state ? ASSERT_LINE : CLEAR_LINE);
346 }
347
348
349 //-------------------------------------------------
350 // z80_decoder_display_port_w - handle writes to
351 // the decoder/display chips
352 //-------------------------------------------------
353
z80_decoder_display_port_w(offs_t offset,uint8_t data)354 void pioneer_ldv1000_device::z80_decoder_display_port_w(offs_t offset, uint8_t data)
355 {
356 /*
357 TX/RX = /A0 (A0=0 -> TX, A0=1 -> RX)
358
359 Display is 6-bit
360 Decoder is 4-bit
361 */
362
363 // writes to offset 0 select the target for reads/writes of actual data
364 if (offset == 0)
365 {
366 m_portselect = data;
367 m_dispindex = 0;
368 }
369
370 // writes to offset 2 constitute actual writes targeted toward the display and decoder chips
371 else if (offset == 2)
372 {
373 // selections 0 and 1 represent the two display lines; only 6 bits are transferred
374 if (m_portselect < 2)
375 m_display[m_portselect][m_dispindex++ % 20] = data & 0x3f;
376 }
377 }
378
379
380 //-------------------------------------------------
381 // z80_decoder_display_port_r - handle reads from the
382 // decoder/display chips
383 //-------------------------------------------------
384
z80_decoder_display_port_r(offs_t offset)385 uint8_t pioneer_ldv1000_device::z80_decoder_display_port_r(offs_t offset)
386 {
387 // reads from offset 3 constitute actual reads from the display and decoder chips
388 uint8_t result = 0;
389 if (offset == 3)
390 {
391 // selection 4 represents the VBI data reading
392 if (m_portselect == 4)
393 {
394 m_vbiready = false;
395 result = m_vbi[m_vbiindex++ % ARRAY_LENGTH(m_vbi)];
396 }
397 }
398 return result;
399 }
400
401
402 //-------------------------------------------------
403 // z80_controller_r - handle read of the data from
404 // the controlling system
405 //-------------------------------------------------
406
z80_controller_r()407 uint8_t pioneer_ldv1000_device::z80_controller_r()
408 {
409 // note that this is a cheesy implementation; the real thing relies on exquisite timing
410 uint8_t result = m_command ^ 0xff;
411 m_command = 0xff;
412 return result;
413 }
414
415
416 //-------------------------------------------------
417 // z80_controller_w - handle status latch writes
418 //-------------------------------------------------
419
z80_controller_w(uint8_t data)420 void pioneer_ldv1000_device::z80_controller_w(uint8_t data)
421 {
422 if (LOG_STATUS_CHANGES && data != m_status)
423 logerror("%s:CONTROLLER.W=%02X\n", machine().describe_context(), data);
424 m_status = data;
425 }
426
427
428 //-------------------------------------------------
429 // ppi0_porta_w - handle writes to port A of
430 // PPI #0
431 //-------------------------------------------------
432
ppi0_porta_w(uint8_t data)433 void pioneer_ldv1000_device::ppi0_porta_w(uint8_t data)
434 {
435 m_counter_start = data;
436 if (LOG_PORT_IO)
437 logerror("%s:PORTA.0=%02X\n", machine().describe_context(), data);
438 }
439
440
441 //-------------------------------------------------
442 // ppi0_portb_r - handle reads from port B of
443 // PPI #0
444 //-------------------------------------------------
445
ppi0_portb_r()446 uint8_t pioneer_ldv1000_device::ppi0_portb_r()
447 {
448 return m_counter;
449 }
450
451
452 //-------------------------------------------------
453 // ppi0_portc_r - handle reads from port C of
454 // PPI #0
455 //-------------------------------------------------
456
ppi0_portc_r()457 uint8_t pioneer_ldv1000_device::ppi0_portc_r()
458 {
459 /*
460 $10 = /VSYNC
461 $20 = IRQ from decoder chip
462 $40 = TRKG LOOP (N24-1)
463 $80 = DUMP (N20-1) -- code reads the state and waits for it to change
464 */
465
466 uint8_t result = 0x00;
467 if (!m_vsync)
468 result |= 0x10;
469 if (!m_vbiready)
470 result |= 0x20;
471 return result;
472 }
473
474
475 //-------------------------------------------------
476 // ppi0_portc_w - handle writes to port C of
477 // PPI #0
478 //-------------------------------------------------
479
ppi0_portc_w(uint8_t data)480 void pioneer_ldv1000_device::ppi0_portc_w(uint8_t data)
481 {
482 /*
483 $01 = preload on up/down counters
484 $02 = /MULTI JUMP TRIG
485 $04 = SCAN MODE
486 $08 = n/c
487 */
488
489 // set the new value
490 uint8_t prev = m_portc0;
491 m_portc0 = data;
492 if (LOG_PORT_IO && ((data ^ prev) & 0x0f) != 0)
493 {
494 logerror("%s:PORTC.0=%02X%s%s%s\n", machine().describe_context(), data,
495 (data & 0x01) ? " PRELOAD" : "",
496 !(data & 0x02) ? " /MULTIJUMP" : "",
497 (data & 0x04) ? " SCANMODE" : "");
498 }
499
500 // on the rising edge of bit 0, clock the down counter load
501 if ((data & 0x01) && !(prev & 0x01))
502 m_counter = m_counter_start;
503
504 // on the falling edge of bit 1, start the multi-jump timer
505 if (!(data & 0x02) && (prev & 0x02))
506 m_multitimer->adjust(MULTIJUMP_TRACK_TIME);
507 }
508
509
510 //-------------------------------------------------
511 // ppi1_porta_r - handle reads from port A of
512 // PPI #1
513 //-------------------------------------------------
514
ppi1_porta_r()515 uint8_t pioneer_ldv1000_device::ppi1_porta_r()
516 {
517 /*
518 $01 = /FOCS LOCK
519 $02 = /SPDL LOCK
520 $04 = INSIDE
521 $08 = OUTSIDE
522 $10 = MOTOR STOP
523 $20 = +5V/test point
524 $40 = /INT LOCK
525 $80 = 8 INCH CHK
526 */
527
528 slider_position sliderpos = get_slider_position();
529 uint8_t result = 0x00;
530
531 // bit 0: /FOCUS LOCK
532 if (!focus_on())
533 result |= 0x01;
534
535 // bit 1: /SPDL LOCK
536 if (!spdl_on())
537 result |= 0x02;
538
539 // bit 2: INSIDE signal
540 if (sliderpos == SLIDER_MINIMUM)
541 result |= 0x04;
542
543 // bit 3: OUTSIDE signal
544 if (sliderpos == SLIDER_MAXIMUM)
545 result |= 0x08;
546
547 // bit 4: MOTOR STOP
548
549 // bit 5: +5V/test point
550 result |= 0x20;
551
552 // bit 6: /INT LOCK
553
554 // bit 7: 8 INCH CHK
555
556 return result;
557 }
558
559
560 //-------------------------------------------------
561 // ppi1_portb_w - handle writes to port B of
562 // PPI #1
563 //-------------------------------------------------
564
ppi1_portb_w(uint8_t data)565 void pioneer_ldv1000_device::ppi1_portb_w(uint8_t data)
566 {
567 /*
568 $01 = /FOCS ON
569 $02 = /SPDL RUN
570 $04 = /JUMP TRIG
571 $08 = /SCAN A
572 $10 = SCAN B
573 $20 = SCAN C
574 $40 = /LASER ON
575 $80 = /SYNC ST0
576 */
577
578 // set the new value
579 uint8_t prev = m_portb1;
580 m_portb1 = data;
581 if (LOG_PORT_IO && ((data ^ prev) & 0xff) != 0)
582 {
583 logerror("%s:PORTB.1=%02X: %s%s%s%s%s%s\n", machine().describe_context(), data,
584 !(data & 0x01) ? " FOCSON" : "",
585 !(data & 0x02) ? " SPDLRUN" : "",
586 !(data & 0x04) ? " JUMPTRIG" : "",
587 !(data & 0x08) ? string_format(" SCANA (%c %c)", (data & 0x10) ? 'L' : 'H', (data & 0x20) ? 'F' : 'R') : "",
588 (data & 0x40) ? " LASERON" : "",
589 !(data & 0x80) ? " SYNCST0" : "");
590 }
591
592 // bit 5 selects the direction of slider movement for JUMP TRG and scanning
593 int direction = (data & 0x20) ? 1 : -1;
594
595 // on the falling edge of bit 2, jump one track in either direction
596 if (!(data & 0x04) && (prev & 0x04))
597 advance_slider(direction);
598
599 // bit 3 low enables scanning
600 if (!(data & 0x08))
601 {
602 // bit 4 selects the speed
603 int delta = (data & 0x10) ? SCAN_SPEED : SEEK_FAST_SPEED;
604 set_slider_speed(delta * direction);
605 }
606
607 // bit 3 high stops scanning
608 else
609 set_slider_speed(0);
610 }
611
612
613 //-------------------------------------------------
614 // ppi1_portc_w - handle writes to port C of
615 // PPI #1
616 //-------------------------------------------------
617
ppi1_portc_w(uint8_t data)618 void pioneer_ldv1000_device::ppi1_portc_w(uint8_t data)
619 {
620 /*
621 $01 = AUD 1
622 $02 = AUD 2
623 $04 = AUDIO ENABLE
624 $08 = /VIDEO SQ
625 $10 = COMMAND
626 $20 = STATUS
627 $40 = SIZE 8/12
628 $80 = /LED CAV
629 */
630
631 // set the new value
632 uint8_t prev = m_portc1;
633 m_portc1 = data;
634 if (LOG_PORT_IO && ((data ^ prev) & 0xcf) != 0)
635 {
636 logerror("%s:PORTC.1=%02X%s%s%s%s%s%s%s%s\n", machine().describe_context(), data,
637 (data & 0x01) ? " AUD1" : "",
638 (data & 0x02) ? " AUD2" : "",
639 (data & 0x04) ? " AUDEN" : "",
640 !(data & 0x08) ? " VIDEOSQ" : "",
641 (data & 0x10) ? " COMMAND" : "",
642 (data & 0x20) ? " STATUS" : "",
643 (data & 0x40) ? " SIZE8" : "",
644 !(data & 0x80) ? " CAV" : "");
645 }
646
647 // bit 4 sends a command strobe signal to Host CPU
648 m_command_strobe_cb(bool(data & 0x10));
649
650 // video squelch is controlled by bit 3
651 set_video_squelch((data & 0x08) == 0);
652
653 // audio squelch is controlled by bits 0-2
654 set_audio_squelch(!(data & 0x04) || !(data & 0x01), !(data & 0x04) || !(data & 0x02));
655 }
656