1 /*
2  * libtilemcore - Graphing calculator emulation library
3  *
4  * Copyright (C) 2009 Benjamin Moody
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include "tilem.h"
27 
28 /* Internal link port control */
29 
dbus_interrupt(TilemCalc * calc,dword inttype,dword mask)30 static inline void dbus_interrupt(TilemCalc* calc, dword inttype,
31 				  dword mask)
32 {
33 	if (!(calc->linkport.mode & mask))
34 		return;
35 
36 	calc->z80.interrupts |= inttype;
37 }
38 
dbus_set_lines(TilemCalc * calc,byte lines)39 static inline void dbus_set_lines(TilemCalc* calc, byte lines)
40 {
41 	if (lines != calc->linkport.lines) {
42 		calc->linkport.lines = lines;
43 		if (calc->linkport.linkemu == TILEM_LINK_EMULATOR_BLACK) {
44 			tilem_z80_stop(calc, TILEM_STOP_LINK_STATE);
45 		}
46 	}
47 }
48 
dbus_set_extlines(TilemCalc * calc,byte lines)49 static inline void dbus_set_extlines(TilemCalc* calc, byte lines)
50 {
51 	if ((lines ^ calc->linkport.extlines) & ~calc->linkport.lines) {
52 		dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ACTIVE,
53 			       TILEM_LINK_MODE_INT_ON_ACTIVE);
54 	}
55 	calc->linkport.extlines = lines;
56 }
57 
tilem_linkport_assist_timer(TilemCalc * calc,void * data TILEM_ATTR_UNUSED)58 void tilem_linkport_assist_timer(TilemCalc* calc,
59 				 void* data TILEM_ATTR_UNUSED)
60 {
61 	TilemLinkport* lp = &calc->linkport;
62 
63 	if (lp->assistflags & TILEM_LINK_ASSIST_WRITE_BUSY) {
64 		lp->assistflags &= ~TILEM_LINK_ASSIST_WRITE_BUSY;
65 		lp->assistflags |= TILEM_LINK_ASSIST_WRITE_ERROR;
66 	}
67 	else if (lp->assistflags & TILEM_LINK_ASSIST_READ_BUSY) {
68 		lp->assistflags &= ~TILEM_LINK_ASSIST_READ_BUSY;
69 		lp->assistflags |= TILEM_LINK_ASSIST_READ_ERROR;
70 	}
71 	else
72 		return;
73 
74 	dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ERROR,
75 		       TILEM_LINK_MODE_INT_ON_ERROR);
76 }
77 
assist_set_timeout(TilemCalc * calc)78 static inline void assist_set_timeout(TilemCalc* calc)
79 {
80 	if (calc->linkport.mode & TILEM_LINK_MODE_NO_TIMEOUT)
81 		return;
82 
83 	tilem_z80_set_timer(calc, TILEM_TIMER_LINK_ASSIST, 2000000, 0, 1);
84 }
85 
assist_clear_timeout(TilemCalc * calc)86 static inline void assist_clear_timeout(TilemCalc* calc)
87 {
88 	tilem_z80_set_timer(calc, TILEM_TIMER_LINK_ASSIST, 0, 0, 0);
89 }
90 
assist_update_write(TilemCalc * calc)91 static void assist_update_write(TilemCalc* calc)
92 {
93 	switch (calc->linkport.extlines) {
94 	case 0:
95 		if (calc->linkport.lines == 0 && calc->linkport.assistoutbits > 0) {
96 			/* Ready to send next bit */
97 			if (calc->linkport.assistout & 1)
98 				dbus_set_lines(calc, 2);
99 			else
100 				dbus_set_lines(calc, 1);
101 			calc->linkport.assistout >>= 1;
102 			calc->linkport.assistoutbits--;
103 			assist_set_timeout(calc); /* other device must
104 						     respond within 2
105 						     seconds */
106 		}
107 		else if (calc->linkport.lines == 0) {
108 			/* Finished sending a byte */
109 			calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_WRITE_BUSY;
110 			assist_clear_timeout(calc);
111 		}
112 		break;
113 
114 	case 1:
115 	case 2:
116 		if (calc->linkport.extlines == (calc->linkport.lines ^ 3)) {
117 			/* Other device acknowledged our bit.  Note
118 			   that the timeout is NOT set at this point.
119 			   My experiments indicate that the assist
120 			   will wait, apparently indefinitely, for the
121 			   other device to bring its lines high. */
122 			dbus_set_lines(calc, 0);
123 			assist_clear_timeout(calc);
124 		}
125 		break;
126 
127 	case 3:
128 		/* illegal line state; flag error */
129 		calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_WRITE_BUSY;
130 		calc->linkport.assistflags |= TILEM_LINK_ASSIST_WRITE_ERROR;
131 		dbus_set_lines(calc, 0);
132 		assist_clear_timeout(calc);
133 		dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ERROR,
134 			       TILEM_LINK_MODE_INT_ON_ERROR);
135 		break;
136 	}
137 }
138 
assist_update_read(TilemCalc * calc)139 static void assist_update_read(TilemCalc* calc)
140 {
141 	switch (calc->linkport.extlines) {
142 	case 0:
143 		/* Finished receiving a bit */
144 		if (calc->linkport.lines == 1) {
145 			calc->linkport.assistin >>= 1;
146 			calc->linkport.assistin |= 0x80;
147 			calc->linkport.assistinbits++;
148 		}
149 		else if (calc->linkport.lines == 2) {
150 			calc->linkport.assistin >>= 1;
151 			calc->linkport.assistinbits++;
152 		}
153 
154 		if (calc->linkport.assistinbits >= 8) {
155 			/* finished receiving a byte */
156 			calc->linkport.assistlastbyte = calc->linkport.assistin;
157 			calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_READ_BUSY;
158 			calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_BYTE;
159 			assist_clear_timeout(calc);
160 		}
161 		else {
162 			assist_set_timeout(calc); /* other device must
163 						     send next bit
164 						     within 2
165 						     seconds */
166 		}
167 
168 		dbus_set_lines(calc, 0); /* indicate we're ready to
169 					    receive */
170 		break;
171 
172 	case 1:
173 		/* other device sent a zero; acknowledge it */
174 		calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_BUSY;
175 		dbus_set_lines(calc, 2);
176 		assist_set_timeout(calc); /* other device must bring
177 					     both lines high again
178 					     within 2 seconds */
179 		break;
180 
181 	case 2:
182 		/* same as above, but other device sent a one */
183 		calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_BUSY;
184 		dbus_set_lines(calc, 1);
185 		assist_set_timeout(calc); /* other device must bring
186 					     both lines high again
187 					     within 2 seconds */
188 		break;
189 
190 	case 3:
191 		/* illegal line state; flag error */
192 		calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_READ_BUSY;
193 		calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_ERROR;
194 		dbus_set_lines(calc, 0);
195 		assist_clear_timeout(calc);
196 		dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ERROR,
197 			       TILEM_LINK_MODE_INT_ON_ERROR);
198 		break;
199 	}
200 }
201 
graylink_update_write(TilemCalc * calc)202 static void graylink_update_write(TilemCalc* calc)
203 {
204 	switch (calc->linkport.lines) {
205 	case 0:
206 		if (calc->linkport.extlines == 0 && calc->linkport.graylinkoutbits > 1) {
207 			/* Ready to send next bit */
208 			if (calc->linkport.graylinkout & 1)
209 				dbus_set_extlines(calc, 2);
210 			else
211 				dbus_set_extlines(calc, 1);
212 			calc->linkport.graylinkout >>= 1;
213 			calc->linkport.graylinkoutbits--;
214 		}
215 		else if (calc->linkport.extlines == 0) {
216 			/* Finished sending a byte */
217 			calc->linkport.graylinkoutbits = 0;
218 			tilem_z80_stop(calc, TILEM_STOP_LINK_WRITE_BYTE);
219 		}
220 		break;
221 
222 	case 1:
223 	case 2:
224 		if (calc->linkport.extlines == (calc->linkport.lines ^ 3))
225 			/* Other device acknowledged our bit */
226 			dbus_set_extlines(calc, 0);
227 		break;
228 
229 	case 3:
230 		/* illegal line state; flag error */
231 		dbus_set_extlines(calc, 0);
232 		calc->linkport.graylinkoutbits = 0;
233 		tilem_z80_stop(calc, TILEM_STOP_LINK_ERROR);
234 		break;
235 	}
236 }
237 
graylink_update_read(TilemCalc * calc)238 static void graylink_update_read(TilemCalc* calc)
239 {
240 	switch (calc->linkport.lines) {
241 	case 0:
242 		/* Finished receiving a bit */
243 		if (calc->linkport.extlines == 1) {
244 			calc->linkport.graylinkin >>= 1;
245 			calc->linkport.graylinkin |= 0x80;
246 			calc->linkport.graylinkinbits++;
247 		}
248 		else if (calc->linkport.extlines == 2) {
249 			calc->linkport.graylinkin >>= 1;
250 			calc->linkport.graylinkinbits++;
251 		}
252 
253 		if (calc->linkport.graylinkinbits >= 8) {
254 			/* finished receiving a byte */
255 			tilem_z80_stop(calc, TILEM_STOP_LINK_READ_BYTE);
256 		}
257 
258 		dbus_set_extlines(calc, 0);
259 		break;
260 
261 	case 1:
262 		/* other device sent a zero; acknowledge it */
263 		dbus_set_extlines(calc, 2);
264 		break;
265 
266 	case 2:
267 		/* same as above, but other device sent a one */
268 		dbus_set_extlines(calc, 1);
269 		break;
270 
271 	case 3:
272 		/* illegal line state; flag error */
273 		dbus_set_extlines(calc, 0);
274 		calc->linkport.graylinkinbits = 0;
275 		tilem_z80_stop(calc, TILEM_STOP_LINK_ERROR);
276 		break;
277 	}
278 }
279 
dbus_update(TilemCalc * calc)280 static void dbus_update(TilemCalc* calc)
281 {
282 	byte oldlines;
283 
284 	do {
285 		if (calc->linkport.linkemu == TILEM_LINK_EMULATOR_GRAY) {
286 			if (calc->linkport.graylinkoutbits) {
287 				graylink_update_write(calc);
288 			}
289 			else if (calc->linkport.graylinkinbits != 8) {
290 				graylink_update_read(calc);
291 			}
292 		}
293 
294 		oldlines = calc->linkport.lines;
295 		if (calc->linkport.assistflags & TILEM_LINK_ASSIST_WRITE_BUSY) {
296 			assist_update_write(calc);
297 		}
298 		else if (calc->linkport.mode & TILEM_LINK_MODE_ASSIST
299 			 && !(calc->linkport.assistflags & TILEM_LINK_ASSIST_READ_BYTE)) {
300 			assist_update_read(calc);
301 		}
302 	} while (oldlines != calc->linkport.lines);
303 
304 	if ((calc->linkport.assistflags & TILEM_LINK_ASSIST_READ_BYTE)
305 	    && (calc->linkport.mode & TILEM_LINK_MODE_INT_ON_READ))
306 		calc->z80.interrupts |= TILEM_INTERRUPT_LINK_READ;
307 	else
308 		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_READ;
309 
310 	if (!(calc->linkport.assistflags & (TILEM_LINK_ASSIST_READ_BUSY
311 					    | TILEM_LINK_ASSIST_WRITE_BUSY))
312 	    && (calc->linkport.mode & TILEM_LINK_MODE_INT_ON_IDLE))
313 		calc->z80.interrupts |= TILEM_INTERRUPT_LINK_IDLE;
314 	else
315 		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_IDLE;
316 }
317 
tilem_linkport_reset(TilemCalc * calc)318 void tilem_linkport_reset(TilemCalc* calc)
319 {
320 	dbus_set_lines(calc, 0);
321 	assist_clear_timeout(calc);
322 	calc->linkport.mode = 0;
323 	calc->linkport.assistflags = 0;
324 	calc->linkport.assistin = 0;
325 	calc->linkport.assistinbits = 0;
326 	calc->linkport.assistout = 0;
327 	calc->linkport.assistoutbits = 0;
328 	calc->linkport.assistlastbyte = 0;
329 }
330 
tilem_linkport_get_lines(TilemCalc * calc)331 byte tilem_linkport_get_lines(TilemCalc* calc)
332 {
333 	//dbus_update(calc);
334 	return (~calc->linkport.lines & ~calc->linkport.extlines & 3);
335 }
336 
tilem_linkport_set_lines(TilemCalc * calc,byte lines)337 void tilem_linkport_set_lines(TilemCalc* calc, byte lines)
338 {
339 	if (!(calc->linkport.mode & TILEM_LINK_MODE_ASSIST)
340 	    && !(calc->linkport.assistflags & TILEM_LINK_ASSIST_WRITE_BUSY))
341 		dbus_set_lines(calc, lines & 3);
342 
343 	dbus_update(calc);
344 }
345 
tilem_linkport_read_byte(TilemCalc * calc)346 byte tilem_linkport_read_byte(TilemCalc* calc)
347 {
348 	byte value = calc->linkport.assistin;
349 	calc->linkport.assistflags &= ~(TILEM_LINK_ASSIST_READ_BYTE
350 			     | TILEM_LINK_ASSIST_READ_BUSY);
351 	calc->linkport.assistinbits = 0;
352 	dbus_update(calc);
353 	return value;
354 }
355 
tilem_linkport_write_byte(TilemCalc * calc,byte data)356 void tilem_linkport_write_byte(TilemCalc* calc, byte data)
357 {
358 	if (calc->linkport.assistflags & (TILEM_LINK_ASSIST_READ_BUSY
359 			       | TILEM_LINK_ASSIST_WRITE_BUSY))
360 		return;
361 
362 	dbus_set_lines(calc, 0);
363 	calc->linkport.assistout = data;
364 	calc->linkport.assistoutbits = 8;
365 	calc->linkport.assistflags |= TILEM_LINK_ASSIST_WRITE_BUSY;
366 	dbus_update(calc);
367 }
368 
tilem_linkport_get_assist_flags(TilemCalc * calc)369 unsigned int tilem_linkport_get_assist_flags(TilemCalc* calc)
370 {
371 	//dbus_update(calc);
372 	return calc->linkport.assistflags;
373 }
374 
tilem_linkport_set_mode(TilemCalc * calc,unsigned int mode)375 void tilem_linkport_set_mode(TilemCalc* calc, unsigned int mode)
376 {
377 	if ((mode ^ calc->linkport.mode) & TILEM_LINK_MODE_ASSIST) {
378 		dbus_set_lines(calc, 0);
379 		calc->linkport.assistflags &= ~(TILEM_LINK_ASSIST_READ_BUSY
380 				     | TILEM_LINK_ASSIST_WRITE_BUSY);
381 		assist_clear_timeout(calc);
382 	}
383 
384 	if (!(mode & TILEM_LINK_MODE_INT_ON_ACTIVE))
385 		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ACTIVE;
386 	if (!(mode & TILEM_LINK_MODE_INT_ON_ERROR))
387 		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ERROR;
388 
389 	calc->linkport.mode = mode;
390 
391 	dbus_update(calc);
392 }
393 
394 
395 /* External BlackLink emulation */
396 
tilem_linkport_blacklink_set_lines(TilemCalc * calc,byte lines)397 void tilem_linkport_blacklink_set_lines(TilemCalc* calc, byte lines)
398 {
399 	dbus_set_extlines(calc, lines & 3);
400 	dbus_update(calc);
401 }
402 
tilem_linkport_blacklink_get_lines(TilemCalc * calc)403 byte tilem_linkport_blacklink_get_lines(TilemCalc* calc)
404 {
405 	dbus_update(calc);
406 	return (~calc->linkport.lines & ~calc->linkport.extlines & 3);
407 }
408 
409 
410 /* External GrayLink emulation */
411 
tilem_linkport_graylink_reset(TilemCalc * calc)412 void tilem_linkport_graylink_reset(TilemCalc* calc)
413 {
414 	calc->linkport.graylinkin = 0;
415 	calc->linkport.graylinkinbits = 0;
416 	calc->linkport.graylinkout = 0;
417 	calc->linkport.graylinkoutbits = 0;
418 	dbus_set_extlines(calc, 0);
419 	dbus_update(calc);
420 }
421 
tilem_linkport_graylink_ready(TilemCalc * calc)422 int tilem_linkport_graylink_ready(TilemCalc* calc)
423 {
424 	if (calc->linkport.graylinkoutbits
425 	    || calc->linkport.graylinkinbits)
426 		return 0;
427 	else
428 		return 1;
429 }
430 
tilem_linkport_graylink_send_byte(TilemCalc * calc,byte value)431 int tilem_linkport_graylink_send_byte(TilemCalc* calc, byte value)
432 {
433 	if (!tilem_linkport_graylink_ready(calc))
434 		return -1;
435 
436 	dbus_set_extlines(calc, 0);
437 
438 	/* set to 9 because we want to wait for the calc to bring both
439 	   link lines low before we send the first bit, and also after
440 	   we send the last bit */
441 	calc->linkport.graylinkoutbits = 9;
442 
443 	calc->linkport.graylinkout = value;
444 	dbus_update(calc);
445 	return 0;
446 }
447 
tilem_linkport_graylink_get_byte(TilemCalc * calc)448 int tilem_linkport_graylink_get_byte(TilemCalc* calc)
449 {
450 	dbus_update(calc);
451 	if (calc->linkport.graylinkinbits != 8)
452 		return -1;
453 
454 	calc->linkport.graylinkinbits = 0;
455 	return calc->linkport.graylinkin;
456 }
457