1 /*
2 * mfp.cpp - MFP emulation
3 *
4 * Copyright (c) 2001-2005 Petr Stehlik of ARAnyM dev team (see AUTHORS)
5 *
6 * Copied almost bit-by-bit from STonC's mfp.c (thanks, Laurent!)
7 *
8 * This file is part of the ARAnyM project which builds a new and powerful
9 * TOS/FreeMiNT compatible virtual machine running on almost any hardware.
10 *
11 * ARAnyM is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * ARAnyM is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with ARAnyM; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26 #include "sysdeps.h"
27 #include "hardware.h"
28 #include "cpu_emulation.h"
29 #include "memory-uae.h"
30 #include "mfp.h"
31
32 #define DEBUG 0
33 #include "debug.h"
34
MFP_Timer(int value)35 MFP_Timer::MFP_Timer(int value)
36 {
37 name = 'A' + value;
38 reset();
39 }
40
reset()41 void MFP_Timer::reset()
42 {
43 control = start_data = current_data = 0;
44 state = false;
45 }
46
isRunning()47 bool MFP_Timer::isRunning()
48 {
49 return ((control & 0x0f) > 0);
50 }
51
setControl(uint8 value)52 void MFP_Timer::setControl(uint8 value)
53 {
54 control = value & 0x0f;
55 if (value & 0x10)
56 state = false;
57 // D(bug("Set MFP Timer%c control to $%x", name, value));
58 }
59
getControl()60 uint8 MFP_Timer::getControl()
61 {
62 return control | (state << 5);
63 }
64
setData(uint8 value)65 void MFP_Timer::setData(uint8 value)
66 {
67 // D(bug("Set MFP Timer%c data to %d", name, value));
68 start_data = value;
69 if (! isRunning())
70 current_data = value;
71 }
72
resetCounter()73 void MFP_Timer::resetCounter()
74 {
75 // D(bug("reset of Timer%c", name));
76 if (isRunning()) {
77 state = true;
78 current_data = start_data;
79 }
80 }
81
getData()82 uint8 MFP_Timer::getData()
83 {
84 // D(bug("get MFP Timer%c data = %d", name, current_data));
85
86 if (isRunning() && current_data > 2)
87 current_data--; // hack to overcome microseconds delays in TOS (e.g. at $E02570)
88
89 return current_data;
90 }
91
compute_timer_freq()92 int MFP_Timer::compute_timer_freq()
93 {
94 #define MFP_FREQ 2457600UL
95 int freq;
96 switch(control) {
97 case 1: freq = MFP_FREQ / 4; break;
98 case 2: freq = MFP_FREQ / 10; break;
99 case 3: freq = MFP_FREQ / 16; break;
100 case 4: freq = MFP_FREQ / 50; break;
101 case 5: freq = MFP_FREQ / 64; break;
102 case 6: freq = MFP_FREQ /100; break;
103 case 7: freq = MFP_FREQ /200; break;
104 default: freq = MFP_FREQ / 64; break; // TOS default
105 }
106 if (start_data)
107 freq = freq / start_data;
108 else
109 freq = freq / 192; // TOS default
110
111 return freq;
112 }
113
114 /*************************************************************************/
115
MFP(memptr addr,uint32 size)116 MFP::MFP(memptr addr, uint32 size) : BASE_IO(addr, size)
117 {
118 reset();
119 }
120
121 #define MFP_VR_VECTOR 0xF0
122 #define MFP_VR_SEI 0x08 /* Software end of interrupt,
123 need software cancelling in inservice ? */
124 #define MFP_VR_AEI 0x00 /* Automatic end of interrupt,
125 ??? */
reset()126 void MFP::reset()
127 {
128 input = 0xff;
129 GPIP_data = input;
130 vr = 0x0100;
131 active_edge = 0;
132 data_direction = 0;
133 irq_enable = 0;
134 irq_pending = 0;
135 irq_inservice = 0;
136 irq_mask = 0;
137 timerCounter = 0;
138
139 A.reset();
140 B.reset();
141 C.reset();
142 D.reset();
143 }
144
handleRead(memptr addr)145 uint8 MFP::handleRead(memptr addr)
146 {
147 addr -= getHWoffset();
148 if (addr > getHWsize())
149 return 0; // unhandled
150
151 uint8 value;
152 switch(addr) {
153 case 0x01: value = (GPIP_data & ~ 0x21) | getYAMAHA()->parallel->getBusy();
154 break;
155
156 case 0x03: value = active_edge;
157 break;
158
159 case 0x05: value = data_direction;
160 break;
161
162 case 0x07: value = irq_enable >> 8;
163 break;
164
165 case 0x09: value = irq_enable;
166 break;
167
168 case 0x0b: value = 0x20; //(irq_pending >> 8) | (tA->getControl() & 0x10); // finish
169 break;
170
171 case 0x0d: // D(bug("Read: TimerC IRQ %s pending", (irq_pending & 0x20) ? "" : "NOT"));
172 value = irq_pending;
173 break;
174
175 case 0xf: value = irq_inservice >> 8;
176 break;
177
178 case 0x11: // D(bug("Read: TimerC IRQ %s in-service", (irq_inservice & 0x20) ? "" : "NOT"));
179 value = irq_inservice;
180 break;
181
182 case 0x13: value = irq_mask >> 8;
183 break;
184
185 case 0x15: // D(bug("Read: TimerC IRQ %s masked", (irq_mask & 0x20) ? "" : "NOT"));
186 value = irq_mask;
187 break;
188
189 case 0x17: value = vr;
190 break;
191
192 case 0x19: value = A.getControl();
193 break;
194
195 case 0x1b: value = B.getControl();
196 break;
197
198 case 0x1d: value = (C.getControl() << 4) | D.getControl();
199 break;
200
201 case 0x1f: value = A.getData();
202 break;
203
204 case 0x21: value = B.getData();
205 break;
206
207 case 0x23: value = C.getData();
208 break;
209
210 case 0x25: value = D.getData();
211 break;
212
213 case 0x2d: value = 0x80; // for Linux/m68k
214 break;
215
216 default: value = 0;
217 };
218 D(bug("Reading MFP data from %04lx = %d ($%02x) at %06x", addr, value, value, showPC()));
219 return value;
220 }
221
set_active_edge(uint8 value)222 void MFP::set_active_edge(uint8 value)
223 {
224 static int map_gpip_to_ier[8] = {0, 1, 2, 3, 6, 7, 14, 15};
225 /* AER : 1=Rising (0->1), 0=Falling (1->0) */
226
227 /* [mfp.txt]
228 * The edge bit is simply one input to an exclusive-or
229 * gate, with the other input coming from the input buffer and the output going
230 * to a 1-0 transition detector. Thus, depending upon the state of the input,
231 * writing the AER can cause an interrupt-producing transition
232 */
233 /* this means : a = input xor aer, and interrupt when a 1->0.
234 * ____before____ ______after_____ ___result___
235 * input aer a input val a interrupt
236 * 0 0 0 0 1 1
237 * 1 0 1 1 1 0 yes
238 * 0 1 1 0 0 0 yes
239 * 1 1 0 1 0 1
240 */
241 int i, j;
242 for(j = 0, i = 1; j < 8; j++, i <<= 1) {
243 if( ((active_edge & i) != (value & i)) /* if AER changes */
244 && (! (data_direction & i)) /* for input lines */
245 && ((active_edge & i) != (input & i))) {
246 IRQ(map_gpip_to_ier[j], 1);
247 }
248 }
249
250 active_edge = value;
251 }
252
handleWrite(memptr addr,uint8 value)253 void MFP::handleWrite(memptr addr, uint8 value) {
254 addr -= getHWoffset();
255 if (addr > getHWsize())
256 return; // unhandled
257
258 D(bug("Writing MFP data to %04lx = %d ($%02x) at %06x", addr, value, value, showPC()));
259 switch(addr) {
260 case 0x01: //GPIP_data = value;
261 GPIP_data &= ~data_direction;
262 GPIP_data |= value & data_direction;
263 D(bug("New GPIP=$%x from PC=$%x", GPIP_data, showPC()));
264 break;
265
266 case 0x03: set_active_edge(value);
267 break;
268
269 case 0x05: data_direction = value;
270 GPIP_data &= data_direction;
271 GPIP_data |= input & (~data_direction);
272 D(bug("GPIP Data Direction set to %02x", value));
273 break;
274
275 case 0x07: irq_enable = (irq_enable & 0x00ff) | (value << 8);
276 // cancel any pending interrupts, but do not alter ISR
277 irq_pending &= irq_enable;
278 // mfp_check_timers_ier();
279 break;
280
281 case 0x09:
282 #ifdef DEBUG_IER
283 if ((irq_enable ^ value) & 0x20) {
284 D(bug("Write: TimerC IRQ %sabled", (value & 20) ? "en" : "dis"));
285 }
286 if ((irq_enable ^ value) & 0x40) {
287 D(bug("Write: IKBD IRQ %sabled", (value & 40) ? "en" : "dis"));
288 }
289 #endif /* DEBUG */
290 irq_enable = (irq_enable & 0xff00) | value;
291 // cancel any pending interrupts, but do not alter ISR
292 irq_pending &= irq_enable;
293 // mfp_check_timers_ier();
294 break;
295
296 /* [mfp.txt]
297 * IPRA and IPRB are also writeable and a pending interrupt can be cleared
298 * without going through the acknowledge sequence by writing a zero to the
299 * appropriate bit. This allows any one bit to be cleared, without altering any
300 * other bits, simply by writing all ones except for the bit position to be
301 * cleared on IPRA or IPRB.
302 */
303
304 case 0x0b: irq_pending &= (value << 8) | 0xff;
305 /* if no unmasked interrupt pending any more, there's no point in
306 * keeping F_MFP active
307 */
308 if ((irq_pending & irq_mask) == 0) {
309 TriggerMFP(false);
310 }
311 break;
312
313 case 0x0d:
314 #ifdef DEBUG_IPR
315 if ((irq_pending ^ value) & 0x20) {
316 D(bug("Write: TimerC IRQ %s pending", (value & 20) ? "" : "NOT"));
317 }
318 #endif /* DEBUG */
319 irq_pending &= 0xff00 | value;
320 if ((irq_pending & irq_mask) == 0) {
321 TriggerMFP(false);
322 }
323
324 break;
325
326 /* [mfp.txt]
327 * Only a zero may be written into any bit of ISRA and ISRB; thus the
328 * in-service may be cleared in software but cannot be set in software.
329 */
330 case 0xf: irq_inservice &= (value << 8) | 0xff;
331 if (irq_pending & irq_mask) {
332 // unmasked interrupt pending, signal it to cpu
333 TriggerMFP(true);
334 }
335 break;
336
337 case 0x11:
338 #ifdef DEBUG_ISR
339 if ((irq_inservice ^ value) & 0x20) {
340 D(bug("Write: TimerC IRQ %s in-service at %08x", (value & 20) ? "" : "NOT", showPC()));
341 }
342 #endif /* DEBUG */
343 irq_inservice &= 0xff00 | value;
344 if (irq_pending & irq_mask) {
345 // unmasked interrupt pending, signal it to cpu
346 TriggerMFP(true);
347 }
348 break;
349
350 case 0x13: irq_mask = (irq_mask & 0x00ff) | (value << 8);
351 TriggerMFP(irq_pending & irq_mask);
352 break;
353
354 case 0x15:
355 #ifdef DEBUG_IMR
356 if ((irq_mask ^ value) & 0x20) {
357 D(bug("Write: TimerC IRQ %s masked", (value & 20) ? "" : "NOT"));
358 }
359 #endif /* DEBUG */
360 irq_mask = (irq_mask & 0xff00) | value;
361 TriggerMFP(irq_pending & irq_mask);
362 break;
363
364 case 0x17: vr = value;
365 if (vr & MFP_VR_SEI) {
366 // software end-of-interrupt mode
367 }
368 else {
369 // automatic end-of-interrupt mode : reset inservice bits?
370 irq_inservice = 0;
371 // try to pass a vector
372 TriggerMFP(irq_pending & irq_mask);
373 }
374 break;
375
376 case 0x19: A.setControl(value);
377 break;
378
379 case 0x1b: B.setControl(value);
380 break;
381
382 case 0x1d: C.setControl(value >> 4);
383 D.setControl(value & 0x0f);
384 break;
385
386 case 0x1f: A.setData(value);
387 break;
388
389 case 0x21: B.setData(value);
390 break;
391
392 case 0x23: C.setData(value);
393 break;
394
395 case 0x25: D.setData(value);
396 break;
397
398 case 0x27:
399 case 0x29:
400 case 0x2b:
401 case 0x2d:
402 case 0x2f:
403 default:
404 break;
405 };
406 }
407
408 /*
409 * setGPIPbit sets input port bits (peripheral interrupt request)
410 */
setGPIPbit(int mask,int value)411 void MFP::setGPIPbit(int mask, int value)
412 {
413 static int map_gpip_to_ier[8] = {0, 1, 2, 3, 6, 7, 14, 15};
414 mask &= 0xff;
415
416 int oldGPIP = GPIP_data;
417 input &= ~mask;
418 input |= (value & mask);
419 /* if output port, no interrupt */
420 mask &= ~data_direction;
421
422 GPIP_data &= ~mask;
423 GPIP_data |= (value & mask);
424 GPIP_data &= 0xFF;
425 D(bug("setGPIPbit($%x, $%x): old=$%x, new=$%x", mask, value, oldGPIP, GPIP_data));
426 int i, j;
427 for(j = 0, i = 1; j < 8; j++, i <<= 1) {
428 if ((oldGPIP & i) != (GPIP_data & i)) {
429 D(bug("setGPIPbit: i=$%x, irq_enable=$%x, old=$%x, new=$%x", i, irq_enable, oldGPIP, GPIP_data));
430 if (active_edge & i) {
431 /* interrupt when going from 0 to 1 */
432 if (oldGPIP & i)
433 continue;
434 }
435 else {
436 /* interrupt when going from 1 to 0 */
437 if (GPIP_data & i)
438 continue;
439 }
440 D(bug("calling IRQ(%d)->%d", j, map_gpip_to_ier[j]));
441 IRQ(map_gpip_to_ier[j], 1);
442 }
443 }
444 }
445
446 /*
447 Ask for an interrupt. Depending on diverse factors, an
448 interrupt will eventually occur the next time
449 MFP:doInterrupt() is called.
450
451 0: IO port 0 : Printer Busy
452 1: IO port 1 : Printer Acknowledge (RS232 Carrier detect on ST)
453 2: IO port 2 : MIDI Interrupt (RS232 CTS on ST)
454 3: IO port 3 : DSP Interrupt (Blitter on ST)
455 4: timer D
456 5: timer C
457 6: IO port 4 : ACIA (IKBD/MIDI)
458 7: IO port 5 : FDC/SCSI/IDE
459 8: Timer B : Display Enable
460 9: Send error
461 10: Send buffer empty
462 11: Receive error
463 12: Receive buffer full
464 13: Timer A : DMA Sound Interrupt
465 14: IO port 6 : RS232 ring
466 15: IO port 7 : DMA Sound Interrupt (monochrome detect on ST)
467 */
468
IRQ(int int_level,int count)469 void MFP::IRQ(int int_level, int count)
470 {
471 int i = 1 << int_level;
472
473 if (int_level == 5) C.resetCounter(); // special hack for TimerC
474
475 /* interrupt enabled ? */
476 if( (irq_enable & i) == 0) {
477 // panicbug("interrupt %d not enabled", int_level);
478 return;
479 }
480
481 if (int_level == 5) timerCounter += count; // special hack for TimerC
482
483 /* same interrupt already pending */
484 if( (irq_pending & i) ) {
485 D(bug("same interrupt %d already pending", int_level));
486 return;
487 }
488
489
490 /* ok, we will request an interrupt to the mfp */
491 #if 0
492 if(int_level) {
493 panicbug( "mfp ask interrupt %d\n", int_level);
494 }
495 #endif
496 irq_pending |= i;
497 /* interrupt masked ? */
498 if( ! (irq_mask & i) ) {
499 /* irq_pending set but no irq : stop here */
500 D(bug("irq_pending set but no irq"));
501 return;
502 }
503 /* highest priority ? */
504 if(irq_inservice > i) {
505 /* no, do nothing (the mfp will check when the current
506 interrupt resumes, i.e. when irq_inservice is being cleared). */
507 D(bug("irq_inservice has higher priority"));
508 return;
509 }
510 /* say we want to interrupt the cpu */
511 TriggerMFP(true);
512 /* when UAE CPU finds this flag set, according to the current IPL,
513 the function MFP::doInterrupt() will be called. This function
514 will then decide what to do, and will treat the request that
515 has the highest priority at that time.
516 */
517 }
518
519 // return : vector number, or zero if no interrupt
doInterrupt()520 int MFP::doInterrupt()
521 {
522 int j, vector;
523 unsigned i;
524
525 /* what's happening here ? */
526 #if 0
527 panicbug( "starting mfp_do_interrupt\n");
528 panicbug( "ier %04x, ipr %04x, isr %04x, imr %04x\n",
529 irq_enable, irq_pending, irq_inservice, irq_mask);
530 #endif
531
532 /* any pending interrupts? */
533 for(j = 15, i = 0x8000; i; j--, i>>=1) {
534 if(irq_pending & i & irq_mask)
535 break;
536 }
537 if(i == 0) {
538 /* this shouldn't happen :-) */
539 panicbug( "mfp_do_interrupt called with no pending interrupt\n");
540 TriggerMFP(false);
541 return 0;
542 }
543 if(irq_inservice >= i) {
544 /* Still busy. We shouldn't come here. */
545
546 /* Running MagiC this happens all the time.
547 The panicbug log makes the whole console output unusable.
548 Thus it is commented out.
549
550 panicbug( "mfp_do_interrupt called when "
551 "another higher priority interrupt is running\n");
552 */
553
554 TriggerMFP(false);
555 return 0;
556 }
557
558 /* ok, do the interrupt, i.e. "pass the vector". */
559 vector = (vr & MFP_VR_VECTOR) + j;
560
561 if (j != 5 || --timerCounter <= 0) { // special hack for TimerC
562 irq_pending &= ~i;
563 TriggerMFP(false);
564 }
565
566 if(vr & MFP_VR_SEI) {
567 /* software mode of interrupt : irq_inservice will remain set until
568 explicitely cleared by writing on it */
569 irq_inservice |= i;
570 } else {
571 /* automatic mode of interrupt : irq_inservice automatically cleared
572 when the interrupt starts (which is now). In this case,
573 we must keep the flag raised if another unmasked
574 interrupt remains pending, since irq_inservice will not tell us
575 to re-raise the flag.
576 */
577 if((irq_pending & irq_mask)) {
578 /* if other unmasked interrupt pending, keep the flag */
579 TriggerMFP(true);
580 }
581 }
582
583 #if 0
584 panicbug( "MFP::doInterrupt : vector %d\n", vector);
585 #endif
586 return vector;
587 }
588
timerC_ms_ticks()589 int MFP::timerC_ms_ticks()
590 {
591 int freq = C.compute_timer_freq();
592
593 return 1000 / freq;
594 }
595