1 // license:BSD-3-Clause
2 // copyright-holders:David Haywood,AJR
3 
4 /***************************************************************************
5 
6    ZSU Sound Control Unit (Proyectado 21/4/86 J. Gamell)
7    ZSU1 Sound Control Unit (Proyectado 12/6/86 J. Gamell)
8    Cedar Magnet Sound Board
9 
10    The ZSU board is a component of the Z-Pinball hardware developed by
11    E.F.O. (Electrónica Funcional Operativa) S.A. of Barcelona, Spain. Its
12    sound generators are 2 AY-3-8910As and 1 OKI MSM5205, and 2 MF10s and
13    1 HC4066 are used to mix their outputs. The timing circuits are rather
14    intricate, using Z80-CTCs, HC74s and HC393s and various other gates to
15    drive both the 5205 and the SGS HCF40105BE (equivalent to CD40105B)
16    through which its samples are funneled.
17 
18    There are no available schematics for the Cedar Magnet video game
19    system (also designed by E.F.O.), but its sound board is believed to be
20    a close analogue of ZSU, since it includes all of the aforementioned
21    devices. The main known difference is that the Cedar Magnet sound
22    code and data are externally loaded into 64K of RAM (2xTMM41464P-15),
23    whereas ZSU's memory map consists primarily of a bank of up to 5 27256
24    EPROMs switched from two output lines of the first 8910, overlaid with
25    a mere 2K of RAM.
26 
27    irq vectors
28 
29    0xe6 - from ctc0 channel 3 (vector = E0) used to drive MSM5205 through FIFO
30    0xee - from ctc0 channel 3 (vector = E8) ^^
31    0xf6 - drive AY (once per frame?) triggered by ctc1 channel 3 (vector = F0)
32    0xff - read sound latch (triggered by write from master board; default vector set by 5K/+5 pullups on D0-D7)
33 
34 ***************************************************************************/
35 
36 #include "emu.h"
37 #include "audio/efo_zsu.h"
38 
39 #include "machine/clock.h"
40 #include "machine/input_merger.h"
41 #include "speaker.h"
42 
43 
44 DEFINE_DEVICE_TYPE(EFO_ZSU,            efo_zsu_device,            "efo_zsu",      "ZSU Sound Control Unit")
45 DEFINE_DEVICE_TYPE(EFO_ZSU1,           efo_zsu1_device,           "efo_zsu1",     "ZSU1 Sound Control Unit")
46 DEFINE_DEVICE_TYPE(CEDAR_MAGNET_SOUND, cedar_magnet_sound_device, "gedmag_sound", "Cedar Sound")
47 
48 
efo_zsu_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,u32 clock)49 efo_zsu_device::efo_zsu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock)
50 	: device_t(mconfig, type, tag, owner, clock)
51 	, m_ctc0(*this, "ctc0")
52 	, m_ctc1(*this, "ctc1")
53 	, m_soundlatch(*this, "soundlatch")
54 	, m_fifo(*this, "fifo")
55 	, m_adpcm(*this, "adpcm")
56 {
57 }
58 
59 
efo_zsu_device(const machine_config & mconfig,const char * tag,device_t * owner,u32 clock)60 efo_zsu_device::efo_zsu_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
61 	: efo_zsu_device(mconfig, EFO_ZSU, tag, owner, clock)
62 {
63 }
64 
65 
efo_zsu1_device(const machine_config & mconfig,const char * tag,device_t * owner,u32 clock)66 efo_zsu1_device::efo_zsu1_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
67 	: efo_zsu_device(mconfig, EFO_ZSU1, tag, owner, clock)
68 {
69 }
70 
71 
cedar_magnet_sound_device(const machine_config & mconfig,const char * tag,device_t * owner,u32 clock)72 cedar_magnet_sound_device::cedar_magnet_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
73 	: efo_zsu_device(mconfig, CEDAR_MAGNET_SOUND, tag, owner, clock)
74 	, cedar_magnet_board_interface(mconfig, *this, "soundcpu", "ram")
75 {
76 }
77 
78 
sound_command_w(u8 data)79 void efo_zsu_device::sound_command_w(u8 data)
80 {
81 	m_soundlatch->write(data);
82 }
83 
84 
85 
zsu_map(address_map & map)86 void efo_zsu_device::zsu_map(address_map &map)
87 {
88 	map(0x0000, 0x6fff).rom();
89 	map(0x7000, 0x77ff).mirror(0x0800).ram();
90 	map(0x8000, 0xffff).bankr("rombank");
91 }
92 
cedar_magnet_sound_map(address_map & map)93 void cedar_magnet_sound_device::cedar_magnet_sound_map(address_map &map)
94 {
95 	map(0x0000, 0xffff).ram().share("ram");
96 }
97 
zsu_io(address_map & map)98 void efo_zsu_device::zsu_io(address_map &map)
99 {
100 	map.global_mask(0xff);
101 	map.unmap_value_high();
102 
103 	map(0x00, 0x03).rw(m_ctc0, FUNC(z80ctc_device::read), FUNC(z80ctc_device::write));
104 	map(0x04, 0x07).rw(m_ctc1, FUNC(z80ctc_device::read), FUNC(z80ctc_device::write));
105 
106 	map(0x08, 0x08).w(FUNC(efo_zsu_device::adpcm_fifo_w));
107 
108 	map(0x0c, 0x0c).w("aysnd0", FUNC(ay8910_device::address_w));
109 	map(0x0d, 0x0d).w("aysnd0", FUNC(ay8910_device::data_w));
110 
111 	map(0x10, 0x10).w("aysnd1", FUNC(ay8910_device::address_w));
112 	map(0x11, 0x11).w("aysnd1", FUNC(ay8910_device::data_w));
113 
114 	map(0x14, 0x14).r(m_soundlatch, FUNC(generic_latch_8_device::read));
115 
116 }
117 
adpcm_fifo_w(u8 data)118 void efo_zsu_device::adpcm_fifo_w(u8 data)
119 {
120 	// Z80 code first unpacks 8 bytes of ADPCM sample data into nibbles
121 	// and, upon receiving interrupt vector E6, fills FIFO at once using OTIR
122 	// 4-bit data is shifted out of the FIFO to the MSM5205 by another timer
123 	m_fifo->write(data & 0x0f); // only low nibble is used here
124 	m_fifo->si_w(1);
125 	m_fifo->si_w(0);
126 }
127 
ay0_porta_w(u8 data)128 void cedar_magnet_sound_device::ay0_porta_w(u8 data)
129 {
130 	// unknown (not in ZSU schematic); 0x80 written on reset
131 }
132 
ay1_porta_w(u8 data)133 void efo_zsu_device::ay1_porta_w(u8 data)
134 {
135 	m_adpcm->reset_w(data & 1);
136 	if (data & 1)
137 		m_fifo->reset();
138 	// D4-D6 likely used to select clock for ctc0 channel 2
139 	// other bits probably used to modulate analog sound output
140 }
141 
WRITE_LINE_MEMBER(efo_zsu_device::ctc0_z0_w)142 WRITE_LINE_MEMBER(efo_zsu_device::ctc0_z0_w)
143 {
144 //  printf("USED ctc0_z0_w %d\n", state);
145 }
146 
WRITE_LINE_MEMBER(efo_zsu_device::ctc0_z1_w)147 WRITE_LINE_MEMBER(efo_zsu_device::ctc0_z1_w)
148 {
149 //  printf("USED  ctc0_z1_w %d\n", state);
150 }
151 
WRITE_LINE_MEMBER(efo_zsu_device::ctc1_z0_w)152 WRITE_LINE_MEMBER(efo_zsu_device::ctc1_z0_w)
153 {
154 	printf("ctc1_z0_w %d\n", state);
155 }
156 
WRITE_LINE_MEMBER(efo_zsu_device::ctc1_z1_w)157 WRITE_LINE_MEMBER(efo_zsu_device::ctc1_z1_w)
158 {
159 	printf("ctc1_z1_w %d\n", state);
160 }
161 
WRITE_LINE_MEMBER(efo_zsu_device::ctc1_z2_w)162 WRITE_LINE_MEMBER(efo_zsu_device::ctc1_z2_w)
163 {
164 	printf("ctc1_z2_w %d\n", state);
165 }
166 
WRITE_LINE_MEMBER(efo_zsu_device::ctc0_z2_w)167 WRITE_LINE_MEMBER(efo_zsu_device::ctc0_z2_w)
168 {
169 	printf("ctc0_z2_w %d\n", state);
170 }
171 
WRITE_LINE_MEMBER(efo_zsu_device::fifo_dor_w)172 WRITE_LINE_MEMBER(efo_zsu_device::fifo_dor_w)
173 {
174 	// combined with a clock signal and used to drive ctc0 channel 3
175 }
176 
177 static const z80_daisy_config daisy_chain[] =
178 {
179 	{ "ctc1" },
180 	{ "ctc0" },
181 	{ nullptr }
182 };
183 
TIMER_CALLBACK_MEMBER(cedar_magnet_sound_device::reset_assert_callback)184 TIMER_CALLBACK_MEMBER(cedar_magnet_sound_device::reset_assert_callback)
185 {
186 	cedar_magnet_board_interface::reset_assert_callback(ptr,param);
187 	// reset lines go to the ctc as well?
188 	m_ctc0->reset();
189 	m_ctc1->reset();
190 }
191 
192 
device_add_mconfig(machine_config & config)193 void efo_zsu_device::device_add_mconfig(machine_config &config)
194 {
195 	z80_device& soundcpu(Z80(config, "soundcpu", 4000000));
196 	soundcpu.set_addrmap(AS_PROGRAM, &efo_zsu_device::zsu_map);
197 	soundcpu.set_addrmap(AS_IO, &efo_zsu_device::zsu_io);
198 	soundcpu.set_daisy_config(daisy_chain);
199 
200 	Z80CTC(config, m_ctc0, 4000000);
201 	m_ctc0->intr_callback().set("soundirq", FUNC(input_merger_device::in_w<0>));
202 	m_ctc0->zc_callback<0>().set(FUNC(efo_zsu_device::ctc0_z0_w));
203 	m_ctc0->zc_callback<1>().set(FUNC(efo_zsu_device::ctc0_z1_w));
204 	m_ctc0->zc_callback<2>().set(FUNC(efo_zsu_device::ctc0_z2_w));
205 
206 	Z80CTC(config, m_ctc1, 4000000);
207 	m_ctc1->intr_callback().set("soundirq", FUNC(input_merger_device::in_w<1>));
208 	m_ctc1->zc_callback<0>().set(FUNC(efo_zsu_device::ctc1_z0_w));
209 	m_ctc1->zc_callback<1>().set(FUNC(efo_zsu_device::ctc1_z1_w));
210 	m_ctc1->zc_callback<2>().set(FUNC(efo_zsu_device::ctc1_z2_w));
211 
212 #if 0 // does nothing useful now
213 	clock_device &ck1mhz(CLOCK(config, "ck1mhz", 4000000/4);
214 	ck1mhz.signal_handler().set(m_ctc1, FUNC(z80ctc_device::trg0));
215 	ck1mhz.signal_handler().append(m_ctc1, FUNC(z80ctc_device::trg1));
216 	ck1mhz.signal_handler().append(m_ctc1, FUNC(z80ctc_device::trg2));
217 #endif
218 
219 	GENERIC_LATCH_8(config, m_soundlatch);
220 	m_soundlatch->data_pending_callback().set("soundirq", FUNC(input_merger_device::in_w<2>));
221 
222 	INPUT_MERGER_ANY_HIGH(config, "soundirq").output_handler().set_inputline("soundcpu", INPUT_LINE_IRQ0); // 74HC03 NAND gate
223 
224 	SPEAKER(config, "mono").front_center();
225 
226 	ay8910_device &aysnd0(AY8910(config, "aysnd0", 4000000/2));
227 	aysnd0.port_a_write_callback().set_membank("rombank").mask(0x03);
228 	aysnd0.add_route(ALL_OUTPUTS, "mono", 0.5);
229 
230 	ay8910_device &aysnd1(AY8910(config, "aysnd1", 4000000/2));
231 	aysnd1.port_a_write_callback().set(FUNC(efo_zsu_device::ay1_porta_w));
232 	aysnd1.add_route(ALL_OUTPUTS, "mono", 0.5);
233 
234 	CD40105(config, m_fifo, 0);
235 	m_fifo->out_ready_cb().set(FUNC(efo_zsu_device::fifo_dor_w));
236 	m_fifo->out_cb().set(m_adpcm, FUNC(msm5205_device::data_w));
237 
238 	MSM5205(config, m_adpcm, 4000000/8).add_route(ALL_OUTPUTS, "mono", 0.50);
239 }
240 
device_add_mconfig(machine_config & config)241 void cedar_magnet_sound_device::device_add_mconfig(machine_config &config)
242 {
243 	efo_zsu_device::device_add_mconfig(config);
244 
245 	subdevice<z80_device>("soundcpu")->set_addrmap(AS_PROGRAM, &cedar_magnet_sound_device::cedar_magnet_sound_map);
246 
247 	subdevice<ay8910_device>("aysnd0")->port_a_write_callback().set(FUNC(cedar_magnet_sound_device::ay0_porta_w));
248 }
249 
device_start()250 void efo_zsu_device::device_start()
251 {
252 	memory_bank *rombank = membank("rombank");
253 	rombank->configure_entries(0, 4, &static_cast<u8 *>(memregion("soundcpu")->base())[0x8000], 0x8000);
254 	rombank->set_entry(0); // 10K/GND pulldowns on banking lines
255 }
256 
device_start()257 void efo_zsu1_device::device_start()
258 {
259 	memory_bank *rombank = membank("rombank");
260 	rombank->configure_entries(0, 4, &static_cast<u8 *>(memregion("soundcpu")->base())[0x8000], 0x8000);
261 	rombank->set_entry(3); // 10K/+5 pullups on banking lines
262 }
263 
device_start()264 void cedar_magnet_sound_device::device_start()
265 {
266 }
267