1 // license:GPL-2.0+
2 // copyright-holders:Juergen Buchmueller
3 /***************************************************************************
4     vtech2.c
5 
6     machine driver
7     Juergen Buchmueller <pullmoll@t-online.de> MESS driver, Jan 2000
8     Davide Moretti <dave@rimini.com> ROM dump and hardware description
9 
10     TODO:
11         RS232 support.
12         Check if the FDC is really the same as in the
13         Laser 210/310 (aka VZ200/300) series.
14 
15 ****************************************************************************/
16 
17 #include "emu.h"
18 #include "includes/vtech2.h"
19 
20 /* public */
21 
22 /* static */
23 
24 #define TRKSIZE_VZ  0x9a0   /* arbitrary (actually from analyzing format) */
25 
26 static const uint8_t laser_fdc_wrprot[2] = {0x80, 0x80};
27 
init_laser()28 void vtech2_state::init_laser()
29 {
30 	init_waitstates();
31 
32 	uint8_t *gfx = memregion("gfx2")->base();
33 	int i;
34 
35 	m_laser_track_x2[0] = m_laser_track_x2[1] = 80;
36 	m_laser_fdc_bits = 8;
37 	m_laser_drive = -1;
38 	m_cart_size = 0;
39 
40 	for (i = 0; i < 256; i++)
41 		gfx[i] = i;
42 
43 	m_laser_latch = -1;
44 
45 	// check ROM expansion
46 	std::string region_tag;
47 	m_cart_rom = memregion(region_tag.assign(m_cart->tag()).append(GENERIC_ROM_REGION_TAG).c_str());
48 }
49 
50 
DEVICE_IMAGE_LOAD_MEMBER(vtech2_state::cart_load)51 DEVICE_IMAGE_LOAD_MEMBER( vtech2_state::cart_load )
52 {
53 	m_cart_size = m_cart->common_get_size("rom");
54 
55 	if (m_cart_size > 0x10000)
56 	{
57 		image.seterror(IMAGE_ERROR_UNSPECIFIED, "Cartridge bigger than 64k");
58 		m_cart_size = 0;
59 		return image_init_result::FAIL;
60 	}
61 
62 	m_cart->rom_alloc(m_cart_size, GENERIC_ROM8_WIDTH, ENDIANNESS_LITTLE);
63 	m_cart->common_load_rom(m_cart->get_rom_base(), m_cart_size, "rom");
64 
65 	return image_init_result::PASS;
66 }
67 
machine_reset()68 void vtech2_state::machine_reset()
69 {
70 	m_language = m_io_keyboard[5]->read() & 0x30;
71 }
72 
machine_start()73 void vtech2_state::machine_start()
74 {
75 	save_item(NAME(m_laser_frame_message));
76 	save_item(NAME(m_laser_frame_time));
77 	save_item(NAME(m_laser_latch));
78 	save_item(NAME(m_laser_track_x2));
79 	save_item(NAME(m_laser_fdc_status));
80 	save_item(NAME(m_laser_fdc_data));
81 	save_item(NAME(m_laser_data));
82 	save_item(NAME(m_laser_fdc_edge));
83 	save_item(NAME(m_laser_fdc_bits));
84 	save_item(NAME(m_laser_drive));
85 	save_item(NAME(m_laser_fdc_start));
86 	save_item(NAME(m_laser_fdc_write));
87 	save_item(NAME(m_laser_fdc_offs));
88 	save_item(NAME(m_laser_fdc_latch));
89 	save_item(NAME(m_level_old));
90 	save_item(NAME(m_cassette_bit));
91 	save_item(NAME(m_laser_bg_mode));
92 	save_item(NAME(m_laser_two_color));
93 	save_item(NAME(m_language));
94 	save_item(NAME(m_cart_size));
95 }
96 
cart_r(offs_t offset)97 uint8_t vtech2_state::cart_r(offs_t offset)
98 {
99 	if (offset >= m_cart_size)
100 		return 0xff;
101 	return m_cart->read_rom(offset);
102 }
103 
104 // The ULA inserts one waitstate for every read and write in each space (MT 07094, MT 07141)
init_waitstates()105 void vtech2_state::init_waitstates()
106 {
107 	address_space &mem = m_maincpu->space(AS_PROGRAM);
108 	address_space &io = m_maincpu->space(AS_IO);
109 
110 	mem.install_read_tap(0x0000, 0xffff, "mem_wait_r", [this](offs_t offset, u8 &data, u8 mem_mask)
111 	{
112 		if (!machine().side_effects_disabled())
113 			m_maincpu->adjust_icount(-1);
114 		return data;
115 	});
116 	mem.install_write_tap(0x0000, 0xffff, "mem_wait_w", [this](offs_t offset, u8 &data, u8 mem_mask)
117 	{
118 		if (!machine().side_effects_disabled())
119 			m_maincpu->adjust_icount(-1);
120 		return data;
121 	});
122 	io.install_read_tap(0x00, 0xff, "io_wait_r", [this](offs_t offset, u8 &data, u8 mem_mask)
123 	{
124 		if (!machine().side_effects_disabled())
125 			m_maincpu->adjust_icount(-1);
126 		return data;
127 	});
128 	io.install_write_tap(0x00, 0xff, "io_wait_w", [this](offs_t offset, u8 &data, u8 mem_mask)
129 	{
130 		if (!machine().side_effects_disabled())
131 			m_maincpu->adjust_icount(-1);
132 		return data;
133 	});
134 }
135 
136 
137 /*************************************************
138  * memory mapped I/O read
139  * bit  function
140  * 7    not assigned
141  * 6    column 6
142  * 5    column 5
143  * 4    column 4
144  * 3    column 3
145  * 2    column 2
146  * 1    column 1
147  * 0    column 0
148  ************************************************/
mmio_r(offs_t offset)149 uint8_t vtech2_state::mmio_r(offs_t offset)
150 {
151 	u8 data = 0x7f;
152 
153 	offset = ~offset & 0x7ff;
154 	if (BIT(offset, 10))
155 		data &= m_io_keyboard[BIT(offset,8,2) + 8]->read();     // ROW A-D
156 	else
157 	for (u8 i = 0; i < 8; i++)
158 		if (BIT(offset, i))
159 			data &= m_io_keyboard[i]->read();    // ROW 0-7
160 
161 	/* BIT 7 - tape input */
162 	data |= (m_cassette->input() > +0.02) ? 0x80 : 0;
163 
164 	return data;
165 }
166 
167 /*************************************************
168  * memory mapped I/O write
169  * bit  function
170  * 7-6  not assigned
171  * 5    speaker B ???
172  * 4    ???
173  * 3    mode: 1 graphics, 0 text
174  * 2    cassette out (MSB)
175  * 1    cassette out (LSB)
176  * 0    speaker A
177  ************************************************/
mmio_w(uint8_t data)178 void vtech2_state::mmio_w(uint8_t data)
179 {
180 	m_speaker->level_w(data & 1);
181 	m_laser_latch = data;
182 	m_cassette->output( BIT(data, 2) ? -1.0 : +1.0);
183 }
184 
185 
laser_get_track()186 void vtech2_state::laser_get_track()
187 {
188 	sprintf(m_laser_frame_message, "#%d get track %02d", m_laser_drive, m_laser_track_x2[m_laser_drive]/2);
189 	m_laser_frame_time = 30;
190 	/* drive selected or and image file ok? */
191 	if( m_laser_drive >= 0 && m_laser_file[m_laser_drive].found() )
192 	{
193 		device_image_interface &image = *m_laser_file[m_laser_drive];
194 		int size = TRKSIZE_VZ;
195 		int offs = TRKSIZE_VZ * m_laser_track_x2[m_laser_drive]/2;
196 		image.fseek(offs, SEEK_SET);
197 		size = image.fread(m_laser_fdc_data, size);
198 		logerror("get track @$%05x $%04x bytes\n", offs, size);
199 	}
200 	m_laser_fdc_offs = 0;
201 	m_laser_fdc_write = 0;
202 }
203 
laser_put_track()204 void vtech2_state::laser_put_track()
205 {
206 	/* drive selected and image file ok? */
207 	if( m_laser_drive >= 0 && m_laser_file[m_laser_drive].found() )
208 	{
209 		device_image_interface &image = *m_laser_file[m_laser_drive];
210 		int offs = TRKSIZE_VZ * m_laser_track_x2[m_laser_drive]/2;
211 		image.fseek(offs + m_laser_fdc_start, SEEK_SET);
212 		int size = image.fwrite(&m_laser_fdc_data[m_laser_fdc_start], m_laser_fdc_write);
213 		logerror("put track @$%05X+$%X $%04X/$%04X bytes\n", offs, m_laser_fdc_start, size, m_laser_fdc_write);
214 	}
215 }
216 
217 #define PHI0(n) (((n)>>0)&1)
218 #define PHI1(n) (((n)>>1)&1)
219 #define PHI2(n) (((n)>>2)&1)
220 #define PHI3(n) (((n)>>3)&1)
221 
laser_fdc_r(offs_t offset)222 uint8_t vtech2_state::laser_fdc_r(offs_t offset)
223 {
224 	int data = 0xff;
225 	switch( offset )
226 	{
227 	case 1: /* data (read-only) */
228 		if( m_laser_fdc_bits > 0 )
229 		{
230 			if( m_laser_fdc_status & 0x80 )
231 				m_laser_fdc_bits--;
232 			data = (m_laser_data >> m_laser_fdc_bits) & 0xff;
233 #if 0
234 			logerror("laser_fdc_r bits %d%d%d%d%d%d%d%d\n",
235 				(data>>7)&1,(data>>6)&1,(data>>5)&1,(data>>4)&1,
236 				(data>>3)&1,(data>>2)&1,(data>>1)&1,(data>>0)&1 );
237 #endif
238 		}
239 		if( m_laser_fdc_bits == 0 )
240 		{
241 			m_laser_data = m_laser_fdc_data[m_laser_fdc_offs];
242 			logerror("laser_fdc_r %d : data ($%04X) $%02X\n", offset, m_laser_fdc_offs, m_laser_data);
243 			if( m_laser_fdc_status & 0x80 )
244 			{
245 				m_laser_fdc_bits = 8;
246 				m_laser_fdc_offs = (m_laser_fdc_offs + 1) % TRKSIZE_FM;
247 			}
248 			m_laser_fdc_status &= ~0x80;
249 		}
250 		break;
251 	case 2: /* polling (read-only) */
252 		/* fake */
253 		if( m_laser_drive >= 0 )
254 			m_laser_fdc_status |= 0x80;
255 		data = m_laser_fdc_status;
256 		break;
257 	case 3: /* write protect status (read-only) */
258 		if( m_laser_drive >= 0 )
259 			data = laser_fdc_wrprot[m_laser_drive];
260 		logerror("laser_fdc_r %d : write_protect $%02X\n", offset, data);
261 		break;
262 	}
263 	return data;
264 }
265 
laser_fdc_w(offs_t offset,uint8_t data)266 void vtech2_state::laser_fdc_w(offs_t offset, uint8_t data)
267 {
268 	int drive;
269 
270 	switch( offset )
271 	{
272 	case 0: /* latch (write-only) */
273 		drive = (data & 0x10) ? 0 : (data & 0x80) ? 1 : -1;
274 		if( drive != m_laser_drive )
275 		{
276 			m_laser_drive = drive;
277 			if( m_laser_drive >= 0 )
278 				laser_get_track();
279 		}
280 		if( m_laser_drive >= 0 )
281 		{
282 			if( (PHI0(data) && !(PHI1(data) || PHI2(data) || PHI3(data)) && PHI1(m_laser_fdc_latch)) ||
283 				(PHI1(data) && !(PHI0(data) || PHI2(data) || PHI3(data)) && PHI2(m_laser_fdc_latch)) ||
284 				(PHI2(data) && !(PHI0(data) || PHI1(data) || PHI3(data)) && PHI3(m_laser_fdc_latch)) ||
285 				(PHI3(data) && !(PHI0(data) || PHI1(data) || PHI2(data)) && PHI0(m_laser_fdc_latch)) )
286 			{
287 				if( m_laser_track_x2[m_laser_drive] > 0 )
288 					m_laser_track_x2[m_laser_drive]--;
289 				logerror("laser_fdc_w(%d) $%02X drive %d: stepout track #%2d.%d\n", offset, data, m_laser_drive, m_laser_track_x2[m_laser_drive]/2,5*(m_laser_track_x2[m_laser_drive]&1));
290 				if( (m_laser_track_x2[m_laser_drive] & 1) == 0 )
291 					laser_get_track();
292 			}
293 			else
294 			if( (PHI0(data) && !(PHI1(data) || PHI2(data) || PHI3(data)) && PHI3(m_laser_fdc_latch)) ||
295 				(PHI1(data) && !(PHI0(data) || PHI2(data) || PHI3(data)) && PHI0(m_laser_fdc_latch)) ||
296 				(PHI2(data) && !(PHI0(data) || PHI1(data) || PHI3(data)) && PHI1(m_laser_fdc_latch)) ||
297 				(PHI3(data) && !(PHI0(data) || PHI1(data) || PHI2(data)) && PHI2(m_laser_fdc_latch)) )
298 			{
299 				if( m_laser_track_x2[m_laser_drive] < 2*40 )
300 					m_laser_track_x2[m_laser_drive]++;
301 				logerror("laser_fdc_w(%d) $%02X drive %d: stepin track #%2d.%d\n", offset, data, m_laser_drive, m_laser_track_x2[m_laser_drive]/2,5*(m_laser_track_x2[m_laser_drive]&1));
302 				if( (m_laser_track_x2[m_laser_drive] & 1) == 0 )
303 					laser_get_track();
304 			}
305 			if( (data & 0x40) == 0 )
306 			{
307 				m_laser_data <<= 1;
308 				if( (m_laser_fdc_latch ^ data) & 0x20 )
309 					m_laser_data |= 1;
310 				if( (m_laser_fdc_edge ^= 1) == 0 )
311 				{
312 					if( --m_laser_fdc_bits == 0 )
313 					{
314 						uint8_t value = 0;
315 						m_laser_data &= 0xffff;
316 						if( m_laser_data & 0x4000 ) value |= 0x80;
317 						if( m_laser_data & 0x1000 ) value |= 0x40;
318 						if( m_laser_data & 0x0400 ) value |= 0x20;
319 						if( m_laser_data & 0x0100 ) value |= 0x10;
320 						if( m_laser_data & 0x0040 ) value |= 0x08;
321 						if( m_laser_data & 0x0010 ) value |= 0x04;
322 						if( m_laser_data & 0x0004 ) value |= 0x02;
323 						if( m_laser_data & 0x0001 ) value |= 0x01;
324 						logerror("laser_fdc_w(%d) data($%04X) $%02X <- $%02X ($%04X)\n", offset, m_laser_fdc_offs, m_laser_fdc_data[m_laser_fdc_offs], value, m_laser_data);
325 						m_laser_fdc_data[m_laser_fdc_offs] = value;
326 						m_laser_fdc_offs = (m_laser_fdc_offs + 1) % TRKSIZE_FM;
327 						m_laser_fdc_write++;
328 						m_laser_fdc_bits = 8;
329 					}
330 				}
331 			}
332 			/* change of write signal? */
333 			if( (m_laser_fdc_latch ^ data) & 0x40 )
334 			{
335 				/* falling edge? */
336 				if ( m_laser_fdc_latch & 0x40 )
337 				{
338 					sprintf(m_laser_frame_message, "#%d put track %02d", m_laser_drive, m_laser_track_x2[m_laser_drive]/2);
339 					m_laser_frame_time = 30;
340 					m_laser_fdc_start = m_laser_fdc_offs;
341 					m_laser_fdc_edge = 0;
342 				}
343 				else
344 				{
345 					/* data written to track before? */
346 					if( m_laser_fdc_write )
347 						laser_put_track();
348 				}
349 				m_laser_fdc_bits = 8;
350 				m_laser_fdc_write = 0;
351 			}
352 		}
353 		m_laser_fdc_latch = data;
354 		break;
355 	}
356 }
357