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