1 // license:BSD-3-Clause
2 // copyright-holders:Luca Elia, Mirko Buffoni, Takahiro Nogi,Stephane Humbert
3 /***************************************************************************
4 
5   machine.c
6 
7   Functions to emulate general aspects of the machine (RAM, ROM, interrupts,
8   I/O ports)
9 
10   The I8742 MCU takes care of handling the coin inputs and the tilt switch.
11   To simulate this, we read the status in the interrupt handler for the main
12   CPU and update the counters appropriately. We also must take care of
13   handling the coin/credit settings ourselves.
14 
15 ***************************************************************************/
16 
17 #include "emu.h"
18 #include "cpu/mcs48/mcs48.h"
19 #include "includes/tnzs.h"
20 
mcu_r(offs_t offset)21 uint8_t tnzs_mcu_state::mcu_r(offs_t offset)
22 {
23 	uint8_t data = m_mcu->upi41_master_r(offset & 1);
24 	m_subcpu->yield();
25 
26 //  logerror("%s: read %02x from mcu $c00%01x\n", m_maincpu->pcbase(), data, offset);
27 
28 	return data;
29 }
30 
mcu_w(offs_t offset,uint8_t data)31 void tnzs_mcu_state::mcu_w(offs_t offset, uint8_t data)
32 {
33 //  logerror("%s: write %02x to mcu $c00%01x\n", m_maincpu->pcbase(), data, offset);
34 
35 	m_mcu->upi41_master_w(offset & 1, data);
36 }
37 
mcu_port1_r()38 uint8_t tnzs_mcu_state::mcu_port1_r()
39 {
40 	int data = 0;
41 
42 	switch (m_input_select)
43 	{
44 		case 0x0a:  data = m_in2->read(); break;
45 		case 0x0c:  data = m_in0->read(); break;
46 		case 0x0d:  data = m_in1->read(); break;
47 		default:    data = 0xff; break;
48 	}
49 
50 //  logerror("%s:  Read %02x from port 1\n", m_maincpu->pcbase(), data);
51 
52 	return data;
53 }
54 
mcu_port2_w(uint8_t data)55 void tnzs_mcu_state::mcu_port2_w(uint8_t data)
56 {
57 	machine().bookkeeping().coin_lockout_w(0, (data & 0x40) != 0 ? m_lockout_level : !m_lockout_level);
58 	machine().bookkeeping().coin_lockout_w(1, (data & 0x80) != 0 ? m_lockout_level : !m_lockout_level);
59 	machine().bookkeeping().coin_counter_w(0, (~data & 0x10));
60 	machine().bookkeeping().coin_counter_w(1, (~data & 0x20));
61 
62 	m_input_select = data & 0xf;
63 }
64 
analog_r(offs_t offset)65 uint8_t tnzs_mcu_state::analog_r(offs_t offset)
66 {
67 	if (m_upd4701.found())
68 		return m_upd4701->read_xy(offset);
69 
70 	return 0;
71 }
72 
mcu_reset()73 void arknoid2_state::mcu_reset()
74 {
75 	m_mcu_initializing = 3;
76 	m_mcu_coinage_init = 0;
77 	m_mcu_coinage[0] = 1;
78 	m_mcu_coinage[1] = 1;
79 	m_mcu_coinage[2] = 1;
80 	m_mcu_coinage[3] = 1;
81 	m_mcu_coins_a = 0;
82 	m_mcu_coins_b = 0;
83 	m_mcu_credits = 0;
84 	m_mcu_reportcoin = 0;
85 	m_mcu_command = 0;
86 }
87 
mcu_handle_coins(int coin)88 void arknoid2_state::mcu_handle_coins( int coin )
89 {
90 	/* The coin inputs and coin counters are managed by the i8742 mcu. */
91 	/* Here we simulate it. */
92 	/* Credits are limited to 9, so more coins should be rejected */
93 	/* Coin/Play settings must also be taken into consideration */
94 
95 	if (coin & 0x08)    /* tilt */
96 		m_mcu_reportcoin = coin;
97 	else if (coin && coin != m_insertcoin)
98 	{
99 		if (coin & 0x01)    /* coin A */
100 		{
101 //          logerror("Coin dropped into slot A\n");
102 			machine().bookkeeping().coin_counter_w(0,1); machine().bookkeeping().coin_counter_w(0,0); /* Count slot A */
103 			m_mcu_coins_a++;
104 			if (m_mcu_coins_a >= m_mcu_coinage[0])
105 			{
106 				m_mcu_coins_a -= m_mcu_coinage[0];
107 				m_mcu_credits += m_mcu_coinage[1];
108 				if (m_mcu_credits >= 9)
109 				{
110 					m_mcu_credits = 9;
111 					machine().bookkeeping().coin_lockout_global_w(1); /* Lock all coin slots */
112 				}
113 				else
114 				{
115 					machine().bookkeeping().coin_lockout_global_w(0); /* Unlock all coin slots */
116 				}
117 			}
118 		}
119 
120 		if (coin & 0x02)    /* coin B */
121 		{
122 //          logerror("Coin dropped into slot B\n");
123 			machine().bookkeeping().coin_counter_w(1,1); machine().bookkeeping().coin_counter_w(1,0); /* Count slot B */
124 			m_mcu_coins_b++;
125 			if (m_mcu_coins_b >= m_mcu_coinage[2])
126 			{
127 				m_mcu_coins_b -= m_mcu_coinage[2];
128 				m_mcu_credits += m_mcu_coinage[3];
129 				if (m_mcu_credits >= 9)
130 				{
131 					m_mcu_credits = 9;
132 					machine().bookkeeping().coin_lockout_global_w(1); /* Lock all coin slots */
133 				}
134 				else
135 				{
136 					machine().bookkeeping().coin_lockout_global_w(0); /* Unlock all coin slots */
137 				}
138 			}
139 		}
140 
141 		if (coin & 0x04)    /* service */
142 		{
143 //          logerror("Coin dropped into service slot C\n");
144 			m_mcu_credits++;
145 		}
146 
147 		m_mcu_reportcoin = coin;
148 	}
149 	else
150 	{
151 		if (m_mcu_credits < 9)
152 			machine().bookkeeping().coin_lockout_global_w(0); /* Unlock all coin slots */
153 
154 		m_mcu_reportcoin = 0;
155 	}
156 	m_insertcoin = coin;
157 }
158 
159 /*********************************
160 
161 TNZS sync bug kludge
162 
163 In all TNZS versions there is code like this:
164 
165 0C5E: ld   ($EF10),a
166 0C61: ld   a,($EF10)
167 0C64: inc  a
168 0C65: ret  nz
169 0C66: jr   $0C61
170 
171 which is sometimes executed by the main cpu when it writes to shared RAM a
172 command for the second CPU. The intended purpose of the code is to wait an
173 acknowledge from the sub CPU: the sub CPU writes FF to the same location
174 after reading the command.
175 
176 However the above code is wrong. The "ret nz" instruction means that the
177 loop will be exited only when the contents of $EF10 are *NOT* $FF!!
178 On the real board, this casues little harm: the main CPU will just write
179 the command, read it back and, since it's not $FF, return immediately. There
180 is a chance that the command might go lost, but this will cause no major
181 harm, the worse that can happen is that the background tune will not change.
182 
183 In MAME, however, since CPU interleaving is not perfect, it can happen that
184 the main CPU ends its timeslice after writing to EF10 but before reading it
185 back. In the meantime, the sub CPU will run, read the command and write FF
186 there - therefore causing the main CPU to enter an endless loop.
187 
188 Unlike the usual sync problems in MAME, which can be fixed by increasing the
189 interleave factor, in this case increasing it will actually INCREASE the
190 chance of entering the endless loop - because it will increase the chances of
191 the main CPU ending its timeslice at the wrong moment.
192 
193 So what we do here is catch writes by the main CPU to the RAM location, and
194 process them using a timer, in order to
195 a) force a resync of the two CPUs
196 b) make sure the main CPU will be the first one to run after the location is
197    changed
198 
199 Since the answer from the sub CPU is ignored, we don't even need to boost
200 interleave.
201 
202 *********************************/
203 
204 /*
205 TIMER_CALLBACK_MEMBER(tnzs_base_state::kludge_callback)
206 {
207     tnzs_sharedram[0x0f10] = param;
208 }
209 
210 void tnzs_base_state::tnzs_sync_kludge_w(uint8_t data)
211 {
212     machine().scheduler().synchronize(timer_expired_delegate(FUNC(tnzs_base_state::kludge_callback),this), data);
213 }
214 */
215 
mcu_r(offs_t offset)216 uint8_t arknoid2_state::mcu_r(offs_t offset)
217 {
218 	static const char mcu_startup[] = "\x55\xaa\x5a";
219 
220 	//logerror("%s: read mcu %04x\n", m_maincpu->pc(), 0xc000 + offset);
221 
222 	if (offset == 0)
223 	{
224 		/* if the mcu has just been reset, return startup code */
225 		if (m_mcu_initializing)
226 		{
227 			m_mcu_initializing--;
228 			return mcu_startup[2 - m_mcu_initializing];
229 		}
230 
231 		switch (m_mcu_command)
232 		{
233 			case 0x41:
234 				return m_mcu_credits;
235 
236 			case 0xc1:
237 				/* Read the credit counter or the inputs */
238 				if (m_mcu_readcredits == 0)
239 				{
240 					m_mcu_readcredits = 1;
241 					if (m_mcu_reportcoin & 0x08)
242 					{
243 						m_mcu_initializing = 3;
244 						return 0xee;    /* tilt */
245 					}
246 					else return m_mcu_credits;
247 				}
248 				else return m_in0->read();  /* buttons */
249 
250 			default:
251 				logerror("error, unknown mcu command\n");
252 				/* should not happen */
253 				return 0xff;
254 		}
255 	}
256 	else
257 	{
258 		/*
259 		status bits:
260 		0 = mcu is ready to send data (read from c000)
261 		1 = mcu has read data (from c000)
262 		2 = unused
263 		3 = unused
264 		4-7 = coin code
265 		      0 = nothing
266 		      1,2,3 = coin switch pressed
267 		      e = tilt
268 		*/
269 		if (m_mcu_reportcoin & 0x08) return 0xe1;   /* tilt */
270 		if (m_mcu_reportcoin & 0x01) return 0x11;   /* coin 1 (will trigger "coin inserted" sound) */
271 		if (m_mcu_reportcoin & 0x02) return 0x21;   /* coin 2 (will trigger "coin inserted" sound) */
272 		if (m_mcu_reportcoin & 0x04) return 0x31;   /* coin 3 (will trigger "coin inserted" sound) */
273 		return 0x01;
274 	}
275 }
276 
mcu_w(offs_t offset,uint8_t data)277 void arknoid2_state::mcu_w(offs_t offset, uint8_t data)
278 {
279 	if (offset == 0)
280 	{
281 		//logerror("%s: write %02x to mcu %04x\n", m_maincpu->pc(), data, 0xc000 + offset);
282 		if (m_mcu_command == 0x41)
283 		{
284 			m_mcu_credits = (m_mcu_credits + data) & 0xff;
285 		}
286 	}
287 	else
288 	{
289 		/*
290 		0xc1: read number of credits, then buttons
291 		0x54+0x41: add value to number of credits
292 		0x15: sub 1 credit (when "Continue Play" only)
293 		0x84: coin 1 lockout (issued only in test mode)
294 		0x88: coin 2 lockout (issued only in test mode)
295 		0x80: release coin lockout (issued only in test mode)
296 		during initialization, a sequence of 4 bytes sets coin/credit settings
297 		*/
298 		//logerror("%s: write %02x to mcu %04x\n", m_maincpu->pc(), data, 0xc000 + offset);
299 
300 		if (m_mcu_initializing)
301 		{
302 			/* set up coin/credit settings */
303 			m_mcu_coinage[m_mcu_coinage_init++] = data;
304 			if (m_mcu_coinage_init == 4)
305 				m_mcu_coinage_init = 0; /* must not happen */
306 		}
307 
308 		if (data == 0xc1)
309 			m_mcu_readcredits = 0;  /* reset input port number */
310 
311 		if (data == 0x15)
312 		{
313 			m_mcu_credits = (m_mcu_credits - 1) & 0xff;
314 			if (m_mcu_credits == 0xff)
315 				m_mcu_credits = 0;
316 		}
317 		m_mcu_command = data;
318 	}
319 }
320 
INTERRUPT_GEN_MEMBER(arknoid2_state::mcu_interrupt)321 INTERRUPT_GEN_MEMBER(arknoid2_state::mcu_interrupt)
322 {
323 	int coin = ((m_coin1->read() & 1) << 0);
324 	coin |= ((m_coin2->read() & 1) << 1);
325 	coin |= ((m_in2->read() & 3) << 2);
326 	coin ^= 0x0c;
327 	mcu_handle_coins(coin);
328 
329 	device.execute().set_input_line(0, HOLD_LINE);
330 }
331 
machine_reset()332 void arknoid2_state::machine_reset()
333 {
334 	/* initialize the mcu simulation */
335 	mcu_reset();
336 
337 	m_mcu_readcredits = 0;
338 	m_insertcoin = 0;
339 }
340 
machine_reset()341 void kageki_state::machine_reset()
342 {
343 	tnzs_base_state::machine_reset();
344 	m_csport_sel = 0;
345 }
346 
machine_start()347 void tnzs_base_state::machine_start()
348 {
349 	uint8_t *sub = memregion("sub")->base();
350 
351 	m_bank2 = 0;
352 	m_mainbank->set_bank(2);
353 
354 	m_subbank->configure_entries(0, 4, &sub[0x08000], 0x2000);
355 	m_subbank->set_entry(m_bank2);
356 
357 	save_item(NAME(m_bank2));
358 }
359 
machine_start()360 void arknoid2_state::machine_start()
361 {
362 	tnzs_base_state::machine_start();
363 	save_item(NAME(m_mcu_readcredits));
364 	save_item(NAME(m_insertcoin));
365 	save_item(NAME(m_mcu_initializing));
366 	save_item(NAME(m_mcu_coinage_init));
367 	save_item(NAME(m_mcu_coinage));
368 	save_item(NAME(m_mcu_coins_a));
369 	save_item(NAME(m_mcu_coins_b));
370 	save_item(NAME(m_mcu_credits));
371 	save_item(NAME(m_mcu_reportcoin));
372 	save_item(NAME(m_mcu_command));
373 
374 	// kludge to make device work with active-high coin inputs
375 	m_upd4701->left_w(0);
376 	m_upd4701->middle_w(0);
377 }
378 
machine_start()379 void kageki_state::machine_start()
380 {
381 	tnzs_base_state::machine_start();
382 	save_item(NAME(m_csport_sel));
383 }
384 
machine_start()385 void kabukiz_state::machine_start()
386 {
387 	tnzs_base_state::machine_start();
388 	uint8_t *sound = memregion("audiocpu")->base();
389 	m_audiobank->configure_entries(0, 8, &sound[0x00000], 0x4000);
390 }
391 
ramrom_bankswitch_w(uint8_t data)392 void tnzs_base_state::ramrom_bankswitch_w(uint8_t data)
393 {
394 //  logerror("%s: writing %02x to bankswitch\n", m_maincpu->pc(),data);
395 
396 	/* bit 4 resets the second CPU */
397 	if (data & 0x10)
398 		m_subcpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
399 	else
400 		m_subcpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
401 
402 	/* bits 0-2 select RAM/ROM bank */
403 	m_mainbank->set_bank(data & 0x07);
404 }
405 
bankswitch1_w(uint8_t data)406 void arknoid2_state::bankswitch1_w(uint8_t data)
407 {
408 	tnzs_base_state::bankswitch1_w(data);
409 	if (data & 0x04)
410 		mcu_reset();
411 
412 	// never actually written by arknoid2 (though code exists to do it)
413 	m_upd4701->resetx_w(BIT(data, 5));
414 	m_upd4701->resety_w(BIT(data, 5));
415 }
416 
bankswitch1_w(uint8_t data)417 void insectx_state::bankswitch1_w(uint8_t data)
418 {
419 	tnzs_base_state::bankswitch1_w(data);
420 	machine().bookkeeping().coin_lockout_w(0, (~data & 0x04));
421 	machine().bookkeeping().coin_lockout_w(1, (~data & 0x08));
422 	machine().bookkeeping().coin_counter_w(0, (data & 0x10));
423 	machine().bookkeeping().coin_counter_w(1, (data & 0x20));
424 }
425 
bankswitch1_w(uint8_t data)426 void tnzsb_state::bankswitch1_w(uint8_t data) // kabukiz_state
427 {
428 	tnzs_base_state::bankswitch1_w(data);
429 	machine().bookkeeping().coin_lockout_w(0, (~data & 0x10));
430 	machine().bookkeeping().coin_lockout_w(1, (~data & 0x20));
431 	machine().bookkeeping().coin_counter_w(0, (data & 0x04));
432 	machine().bookkeeping().coin_counter_w(1, (data & 0x08));
433 }
434 
bankswitch1_w(uint8_t data)435 void kageki_state::bankswitch1_w(uint8_t data)
436 {
437 	tnzs_base_state::bankswitch1_w(data);
438 	machine().bookkeeping().coin_lockout_global_w((~data & 0x20));
439 	machine().bookkeeping().coin_counter_w(0, (data & 0x04));
440 	machine().bookkeeping().coin_counter_w(1, (data & 0x08));
441 }
442 
bankswitch1_w(uint8_t data)443 void tnzs_mcu_state::bankswitch1_w(uint8_t data)
444 {
445 	tnzs_base_state::bankswitch1_w(data);
446 	if ((data & 0x04) != 0 && m_mcu != nullptr)
447 		m_mcu->pulse_input_line(INPUT_LINE_RESET, attotime::zero);
448 
449 	// written only at startup by plumppop?
450 	if (m_upd4701.found())
451 	{
452 		m_upd4701->resetx_w(BIT(data, 5));
453 		m_upd4701->resety_w(BIT(data, 5));
454 	}
455 }
456 
bankswitch1_w(uint8_t data)457 void tnzs_base_state::bankswitch1_w(uint8_t data)
458 {
459 //  logerror("%s: writing %02x to bankswitch 1\n", m_maincpu->pc(),data);
460 
461 	/* bits 0-1 select ROM bank */
462 	m_bank2 = data & 0x03;
463 	m_subbank->set_entry(m_bank2);
464 }
465 
machine_reset()466 void jpopnics_state::machine_reset()
467 {
468 	tnzs_base_state::machine_reset();
469 }
470 
subbankswitch_w(uint8_t data)471 void jpopnics_state::subbankswitch_w(uint8_t data)
472 {
473 	// bits 0-1 select ROM bank
474 	m_subbank->set_entry(data & 0x03);
475 
476 	// written once at startup
477 	m_upd4701->resetx_w(BIT(data, 5));
478 	m_upd4701->resety_w(BIT(data, 5));
479 }
480 
sound_command_w(uint8_t data)481 void tnzsb_state::sound_command_w(uint8_t data)
482 {
483 	m_soundlatch->write(data);
484 	m_audiocpu->set_input_line_and_vector(0, HOLD_LINE, 0xff); // Z80
485 }
486 
487 /* handler called by the 2203 emulator when the internal timers cause an IRQ */
WRITE_LINE_MEMBER(tnzsb_state::ym2203_irqhandler)488 WRITE_LINE_MEMBER(tnzsb_state::ym2203_irqhandler)
489 {
490 	m_audiocpu->set_input_line(INPUT_LINE_NMI, state ? ASSERT_LINE : CLEAR_LINE);
491 }
492 
sound_bank_w(uint8_t data)493 void kabukiz_state::sound_bank_w(uint8_t data)
494 {
495 	// to avoid the write when the sound chip is initialized
496 	if (data != 0xff)
497 		m_audiobank->set_entry(data & 0x07);
498 }
499