1 /*
2 * KCemu -- The emulator for the KC85 homecomputer series and much more.
3 * Copyright (C) 1997-2010 Torsten Paul
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <iostream>
21 #include <iomanip>
22
23 #include "kc/system.h"
24
25 #include "kc/kc.h"
26 #include "kc/pio.h"
27 #include "kc/z80.h"
28 #include "kc/tape.h"
29 #include "kc/memory.h"
30
31 #include "libdbg/dbg.h"
32
33 //#define PIO_OUT_CTRL_DEBUG
34
35 using namespace std;
36
PIO(void)37 PIO::PIO(void) : InterfaceCircuit("PIO")
38 {
39 _first_out[A] = true;
40 _first_out[B] = true;
41
42 _cb_a_in = 0;
43 _cb_a_out = 0;
44 _cb_b_in = 0;
45 _cb_b_out = 0;
46 reset(true);
47 z80->register_ic(this);
48 _z80_irq_mask = z80->get_irq_mask();
49 }
50
~PIO(void)51 PIO::~PIO(void)
52 {
53 z80->unregister_ic(this);
54 }
55
56 void
reset(bool power_on)57 PIO::reset(bool power_on)
58 {
59 _irq[A] = _irq[B] = 0;
60 _value[A] = _value[B] = 0;
61 _irq_vector[A] = _irq_vector[B] = 0;
62 _ready[A] = _ready[B] = 1;
63 _strobe[A] = _strobe[B] = 0;
64 _mode[A] = _mode[B] = MODE_INPUT;
65
66 _irq_enable[A] = _irq_enable[B] = 0;
67 _irq_active[A] = _irq_active[B] = 0;
68
69 _ext[A] = 0xff;
70 _ext[B] = 0xff;
71 _ext_fn[A] = 0;
72 _ext_fn[B] = 0;
73
74 _bit_mode[A] = 0;
75 _bit_mode[B] = 0;
76 _bit_mode_follows[A] = false;
77 _bit_mode_follows[B] = false;
78
79 _irq_mask[A] = 0xff;
80 _irq_mask[B] = 0xff;
81 _irq_h_l[A] = 0;
82 _irq_h_l[B] = 0;
83 _irq_and_or[A] = 0;
84 _irq_and_or[B] = 0;
85 _irq_mask_follows[A] = false;
86 _irq_mask_follows[B] = false;
87 }
88
89 byte_t
in_A_DATA(void)90 PIO::in_A_DATA(void)
91 {
92 int cb;
93 byte_t ret;
94
95 cb = -1;
96 if (_cb_a_in)
97 cb = _cb_a_in->callback_A_in();
98
99 if (_mode[A] == MODE_CONTROL)
100 {
101 if (cb >= 0)
102 _ext[A] = cb;
103 ret = (_value[A] & ~_bit_mode[A]) | (_ext[A] & _bit_mode[A]);
104 DBG(2, form("KCemu/PIO/A/in_DATA",
105 "PIO::in(): %04xh: port A DATA (mode %d): val = %02x, mask = %02x, ext = %02x\n",
106 z80->getPC(), _mode[A], ret, _bit_mode[A], _ext[A]));
107 }
108 else
109 {
110 ret = _value[A];
111 if (cb >= 0)
112 ret = cb;
113 DBG(2, form("KCemu/PIO/A/in_DATA",
114 "PIO::in(): %04xh: port A DATA (mode %d): val = %02x\n",
115 z80->getPC(), _mode[A], ret));
116 }
117
118 return ret;
119 }
120
121 byte_t
in_B_DATA(void)122 PIO::in_B_DATA(void) {
123 int cb;
124 byte_t ret;
125
126 cb = -1;
127 if (_cb_b_in)
128 cb = _cb_b_in->callback_B_in();
129
130 if (_mode[B] == MODE_CONTROL)
131 {
132 if (cb >= 0)
133 _ext[B] = cb;
134 ret = (_value[B] & ~_bit_mode[B]) | (_ext[B] & _bit_mode[B]);
135 DBG(2, form("KCemu/PIO/B/in_DATA",
136 "PIO::in(): %04xh: port B DATA (mode %d): val = %02x, mask = %02x, ext = %02x\n",
137 z80->getPC(), _mode[B], ret, _bit_mode[B], _ext[B]));
138 }
139 else
140 {
141 ret = _value[B];
142 if (cb >= 0)
143 ret = cb;
144 DBG(2, form("KCemu/PIO/B/in_DATA",
145 "PIO::in(): %04xh: port B DATA (mode %d): val = %02x\n",
146 z80->getPC(), _mode[B], ret));
147 }
148
149 return ret;
150 }
151
152 byte_t
in_A_CTRL(void)153 PIO::in_A_CTRL(void) {
154 byte_t ret = 0xff;
155
156 DBG(2, form("KCemu/PIO/A/in_CTRL",
157 "PIO::in(): %04xh: port A CTRL (mode %d): val = %02x\n",
158 z80->getPC(), _mode[A], ret));
159
160 return ret; /* FIXME: */
161 }
162
163 byte_t
in_B_CTRL(void)164 PIO::in_B_CTRL(void) {
165 byte_t ret = 0xff;
166
167 DBG(2, form("KCemu/PIO/B/in_CTRL",
168 "PIO::in(): %04xh: port B CTRL (mode %d): val = %02x\n",
169 z80->getPC(), _mode[B], ret));
170
171 return ret; /* FIXME: */
172 }
173
174 void
out_A_DATA(byte_t val)175 PIO::out_A_DATA(byte_t val)
176 {
177 DBG(2, form("KCemu/PIO/A/out_DATA",
178 "PIO::out(): %04xh: port A DATA (mode %d): val = %02x\n",
179 z80->getPC(), _mode[B], val));
180
181 if (_mode[A] == MODE_INPUT)
182 {
183 _value[A] = val;
184 return;
185 }
186
187 if (_first_out[A])
188 {
189 _value[A] = ~val;
190 _first_out[A] = false;
191 }
192
193 change_A(_value[A] ^ val, val);
194 _value[A] = val;
195 _ready[A] = 1;
196
197 if (_cb_a_out)
198 _cb_a_out->callback_A_out(val);
199 }
200
201 void
out_B_DATA(byte_t val)202 PIO::out_B_DATA(byte_t val)
203 {
204 DBG(2, form("KCemu/PIO/B/out_DATA",
205 "PIO::out(): %04xh: port B DATA (mode %d): val = %02x\n",
206 z80->getPC(), _mode[B], val));
207
208 if (_mode[B] == MODE_INPUT)
209 {
210 _value[B] = val;
211 return;
212 }
213
214 if (_first_out[B])
215 {
216 _value[B] = ~val;
217 _first_out[B] = false;
218 }
219
220 change_B(_value[B] ^ val, val);
221 _value[B] = val;
222 _irq[B] = 1;
223 _ready[B] = 1;
224
225 if (_cb_b_out)
226 _cb_b_out->callback_B_out(val);
227 }
228
229 void
out_CTRL(int port,byte_t val)230 PIO::out_CTRL(int port, byte_t val)
231 {
232 byte_t old_mode;
233 char p = (port == A) ? 'A' : 'B';
234
235 if (port == A)
236 {
237 DBG(2, form("KCemu/PIO/A/out_CTRL",
238 "PIO::out(): %04xh: port A CTRL (mode %d): val = %02x\n",
239 z80->getPC(), _mode[A], val));
240 }
241 else
242 {
243 DBG(2, form("KCemu/PIO/B/out_CTRL",
244 "PIO::out(): %04xh: port B CTRL (mode %d): val = %02x\n",
245 z80->getPC(), _mode[B], val));
246 }
247
248 /*
249 * bit mode
250 *
251 * +-----+-----+-----+-----+-----+-----+-----+-----+
252 * | IO7 | IO6 | IO5 | IO4 | IO3 | IO2 | IO1 | IO0 |
253 * +-----+-----+-----+-----+-----+-----+-----+-----+
254 */
255 if (_bit_mode_follows[port])
256 {
257 _bit_mode[port] = val;
258 _bit_mode_follows[port] = false;
259
260 DBG(2, form("KCemu/PIO/control",
261 "PIO: %04xh: [%c] new bit mode: %02x (0 = out/ 1 = in)\n",
262 z80->getPC(), p, _bit_mode[port]));
263
264 return;
265 }
266
267 /*
268 * interrupt mask (for bit input/output)
269 *
270 * +-----+-----+-----+-----+-----+-----+-----+-----+
271 * | MB7 | MB6 | MB5 | MB4 | MB3 | MB2 | MB1 | MB0 |
272 * +-----+-----+-----+-----+-----+-----+-----+-----+
273 */
274 if (_irq_mask_follows[port])
275 {
276 /*
277 * we store the mask negated because a bit set to 0 means
278 * this bit is used!
279 */
280 _irq_mask[port] = ~val;
281 _irq_mask_follows[port] = false;
282
283 DBG(2, form("KCemu/PIO/control",
284 "PIO: %04xh: [%c] new irq mask (inverted): %02x\n",
285 z80->getPC(), p, _irq_mask[port]));
286
287 return;
288 }
289
290 /*
291 * interrupt vector
292 *
293 * +----+----+----+----+----+----+----+---+
294 * | V7 | V6 | V5 | V4 | V3 | V2 | V1 | 0 |
295 * +----+----+----+----+----+----+----+---+
296 */
297 if ((val & 1) == 0)
298 {
299 _irq_vector[port] = val;
300
301 DBG(2, form("KCemu/PIO/control",
302 "PIO: %04xh: [%c] new irq vector: 0x%02x\n",
303 z80->getPC(), p, val));
304
305 return;
306 }
307
308 switch (val & 0x0f)
309 {
310 /*
311 * interrupt enable/disable
312 *
313 * +----+---+---+---+---+---+---+---+
314 * | EI | X | X | X | 0 | 0 | 1 | 1 |
315 * +----+---+---+---+---+---+---+---+
316 */
317 case 0x03:
318 _irq[port] = (val >> 7) & 0x01;
319 if (_irq[port])
320 {
321 DBG(2, form("KCemu/PIO/control",
322 "PIO: %04xh: [%c] irq enabled\n",
323 z80->getPC(), p));
324
325 _irq_enable[port] = 1;
326 }
327 else
328 {
329 DBG(2, form("KCemu/PIO/control",
330 "PIO: %04xh: [%c] irq disabled\n",
331 z80->getPC(), p));
332
333 _irq_enable[port] = 0;
334 }
335 break;
336
337 /*
338 * interrupt control word
339 *
340 * +----+-----+-----+----+---+---+---+---+
341 * | EI | A/O | H/L | MF | 0 | 1 | 1 | 1 |
342 * +----+-----+-----+----+---+---+---+---+
343 */
344 case 0x07:
345 _irq[port] = (val >> 7) & 1;
346 if (_irq[port])
347 {
348 DBG(2, form("KCemu/PIO/control",
349 "PIO: %04xh: [%c] irq enabled\n",
350 z80->getPC(), p));
351
352 _irq_enable[port] = 1;
353 }
354 else
355 {
356 DBG(2, form("KCemu/PIO/control",
357 "PIO: %04xh: [%c] irq disabled\n",
358 z80->getPC(), p));
359
360 _irq_enable[port] = 0;
361 }
362
363 _irq_and_or[port] = (val >> 6) & 1;
364 DBG(2, form("KCemu/PIO/control",
365 "PIO: %04xh: [%c] AND/OR mode set to %s\n",
366 z80->getPC(), p, _irq_and_or[port] ? "AND" : "OR"));
367
368 _irq_h_l[port] = (val >> 5) & 1;
369 DBG(2, form("KCemu/PIO/control",
370 "PIO: %04xh: [%c] H/L mode set to %c\n",
371 z80->getPC(), p, _irq_h_l[port] ? 'H' : 'L'));
372
373 if (val & 0x10)
374 {
375 _irq_mask_follows[port] = true;
376 DBG(2, form("KCemu/PIO/control",
377 "PIO: %04xh: [%c] irq mask will be set with next control write\n",
378 z80->getPC(), p));
379 }
380
381 break;
382
383 /*
384 * mode control word
385 *
386 * +----+----+---+---+---+---+---+---+
387 * | M1 | M0 | X | X | 1 | 1 | 1 | 1 |
388 * +----+----+---+---+---+---+---+---+
389 */
390 case 0x0f:
391 old_mode = _mode[port];
392 _mode[port] = (val >> 6) & 0x03;
393
394 switch (_mode[port])
395 {
396 case MODE_OUTPUT:
397 DBG(2, form("KCemu/PIO/control",
398 "PIO: %04xh: [%c] new mode: %d - BYTE OUTPUT\n",
399 z80->getPC(), p, _mode[port]));
400 break;
401 case MODE_INPUT:
402 DBG(2, form("KCemu/PIO/control",
403 "PIO: %04xh: [%c] new mode: %d - BYTE INPUT\n",
404 z80->getPC(), p, _mode[port]));
405 break;
406 case MODE_BIDIRECTIONAL:
407 DBG(2, form("KCemu/PIO/control",
408 "PIO: %04xh: [%c] new mode: %d - BIDIRECTIONAL\n",
409 z80->getPC(), p, _mode[port]));
410 break;
411 case MODE_CONTROL:
412 DBG(2, form("KCemu/PIO/control",
413 "PIO: %04xh: [%c] new mode: %d - CONTROL (bit mode)\n",
414 z80->getPC(), p, _mode[port]));
415 _bit_mode_follows[port] = true;
416 break;
417 }
418
419 if (old_mode == MODE_INPUT)
420 {
421 if (port == A)
422 out_A_DATA(_value[A]);
423 else
424 out_B_DATA(_value[B]);
425 }
426
427 break;
428 default:
429 DBG(2, form("KCemu/PIO/control",
430 "PIO: %04xh: [%c] ??? unknown control byte %02x (%d)\n",
431 z80->getPC(), p, val, val));
432 break;
433 }
434 }
435
436 void
out_A_CTRL(byte_t val)437 PIO::out_A_CTRL(byte_t val)
438 {
439 out_CTRL(A, val);
440 }
441
442 void
out_B_CTRL(byte_t val)443 PIO::out_B_CTRL(byte_t val)
444 {
445 out_CTRL(B, val);
446 }
447
448 void
set_EXT(int port,byte_t mask,byte_t val)449 PIO::set_EXT(int port, byte_t mask, byte_t val)
450 {
451 byte_t old;
452 char p = "AB"[port];
453
454 /*
455 if (_irq_active[port] )
456 {
457 cout << "PIO::set_EXT() - irq active" << endl;
458 return;
459 }
460 */
461
462 old = _ext_fn[port];
463 _ext[port] = ((_ext[port] & ~mask) | (val & mask));
464
465 if (_irq_and_or[port])
466 {
467 /* AND */
468 if (_irq_h_l[port])
469 _ext_fn[port] = ((_ext[port] & _irq_mask[port]) == _irq_mask[port]);
470 else
471 _ext_fn[port] = ((_ext[port] & _irq_mask[port]) == 0);
472 }
473 else
474 {
475 /* OR */
476 if (_irq_h_l[port])
477 _ext_fn[port] = ((_ext[port] & _irq_mask[port]) != 0);
478 else
479 _ext_fn[port] = ((_ext[port] & _irq_mask[port]) != _irq_mask[port]);
480 }
481
482 bool do_trigger_irq = (old == 0) && (_ext_fn[port] == 1);
483
484 DBG(2, form("KCemu/PIO/external",
485 "PIO: %04xh: [%c] _ext_fn: A/O = %d, H/L = %d, mask = 0x%02x, old = %02x, _ext_fn = %02x%s\n",
486 z80->getPC(), p, _irq_and_or[port], _irq_h_l[port], _irq_mask[port], old, _ext_fn[port],
487 do_trigger_irq ? " IRQ!" : ""));
488
489 if (do_trigger_irq)
490 trigger_irq(port);
491 }
492
493 void
set_A_EXT(byte_t mask,byte_t val)494 PIO::set_A_EXT(byte_t mask, byte_t val)
495 {
496 set_EXT(A, mask, val);
497 }
498
499 void
set_B_EXT(byte_t mask,byte_t val)500 PIO::set_B_EXT(byte_t mask, byte_t val)
501 {
502 set_EXT(B, mask, val);
503 }
504
505 void
trigger_irq(int port)506 PIO::trigger_irq(int port)
507 {
508 if (_irq_enable[port])
509 {
510 _strobe[port] = 1;
511 irq();
512 }
513 }
514
515 void
irqreq(void)516 PIO::irqreq(void)
517 {
518 DBG(2, form("KCemu/PIO/reti",
519 "PIO::irqreq(): %04xh\n",
520 z80->getPC()));
521 z80->set_irq_line(_z80_irq_mask);
522 }
523
524 word_t
irqack(void)525 PIO::irqack(void)
526 {
527 if (_strobe[A])
528 {
529 _strobe[A] = 0;
530 _irq_active[A] = 1;
531 z80->reset_irq_line(_z80_irq_mask);
532 return _irq_vector[A];
533 }
534
535 if (_irq_active[A])
536 return IRQ_NOT_ACK;
537
538 if (_strobe[B])
539 {
540 _strobe[B] = 0;
541 _irq_active[B] = 1;
542 z80->reset_irq_line(_z80_irq_mask);
543 return _irq_vector[B];
544 }
545
546 return IRQ_NOT_ACK;
547 }
548
549 void
reti(void)550 PIO::reti(void)
551 {
552 //cout.form("PIO: reti - A: %d, B: %d\n", _irq_active[a], _irq_active[B]);
553 if (_irq_active[A])
554 _irq_active[A] = 0;
555 if (_irq_active[B])
556 _irq_active[B] = 0;
557
558 if (_strobe[A])
559 {
560 trigger_irq(A);
561 return;
562 }
563
564 if (_strobe[B])
565 trigger_irq(B);
566 }
567
568 void
strobe_A(void)569 PIO::strobe_A(void)
570 {
571 //_strobe[A] = 1; this blocks the keyboard when the tape loader is active
572 trigger_irq(A);
573 }
574
575 void
strobe_B(void)576 PIO::strobe_B(void)
577 {
578 //_strobe[B] = 1;
579 trigger_irq(B);
580 }
581
582 int
ready_A(void)583 PIO::ready_A(void)
584 {
585 return _ready[A];
586 }
587
588 int
ready_B(void)589 PIO::ready_B(void)
590 {
591 return _ready[B];
592 }
593
594 void
register_callback_A_in(PIOCallbackInterface * cbi)595 PIO::register_callback_A_in(PIOCallbackInterface *cbi)
596 {
597 _cb_a_in = cbi;
598 }
599
600 void
register_callback_A_out(PIOCallbackInterface * cbi)601 PIO::register_callback_A_out(PIOCallbackInterface *cbi)
602 {
603 _cb_a_out = cbi;
604 }
605
606 void
register_callback_B_in(PIOCallbackInterface * cbi)607 PIO::register_callback_B_in(PIOCallbackInterface *cbi)
608 {
609 _cb_b_in = cbi;
610 }
611
612 void
register_callback_B_out(PIOCallbackInterface * cbi)613 PIO::register_callback_B_out(PIOCallbackInterface *cbi)
614 {
615 _cb_b_out = cbi;
616 }
617
618 void
info(void)619 PIO::info(void)
620 {
621 cout << " PIO:" << endl;
622 cout << " ---- port A port B" << endl;
623 cout << "\tmode: " << (int)_mode[A]
624 << " " << (int)_mode[B] << endl;
625 cout << "\tirq vector: " << hex << setfill('0') << setw(2)
626 << (int)_irq_vector[A]
627 << "h " << hex << setfill('0') << setw(2)
628 << (int)_irq_vector[B] << "h" << endl;
629 cout << "\tirq enabled: " << ((_irq[A])?"yes":"no ")
630 << " " << ((_irq[B])?"yes":"no ") << endl;
631 cout << "\tstrobe: " << (int)_strobe[A]
632 << " " << (int)_strobe[B] << endl;
633 cout << "\tready: " << (int)_ready[A]
634 << " " << (int)_ready[B] << endl;
635 cout << "\tvalue: " << hex << setfill('0') << setw(2)
636 << (int)_value[A]
637 << "h " << hex << setfill('0') << setw(2)
638 << (int)_value[B] << "h" << endl;
639 cout << "\text_mask: " << hex << setfill('0') << setw(2)
640 << (int)_bit_mode[A]
641 << "h " << hex << setfill('0') << setw(2)
642 << (int)_bit_mode[B] << "h" << endl;
643 cout << "\text: " << hex << setfill('0') << setw(2)
644 << (int)_ext[A]
645 << "h " << hex << setfill('0') << setw(2)
646 << (int)_ext[B] << "h" << endl << endl;
647 }
648