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