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