sc108: sync with CTC and tty work for RC2014
authorAlan Cox <alan@linux.intel.com>
Sun, 27 Jan 2019 18:27:28 +0000 (18:27 +0000)
committerAlan Cox <alan@linux.intel.com>
Sun, 27 Jan 2019 18:27:28 +0000 (18:27 +0000)
Kernel/platform-sc108/config.h
Kernel/platform-sc108/devices.c
Kernel/platform-sc108/devtty.c
Kernel/platform-sc108/devtty.h
Kernel/platform-sc108/discard.c
Kernel/platform-sc108/kernel.def
Kernel/platform-sc108/main.c
Kernel/platform-sc108/rc2014.h
Kernel/platform-sc108/sc108.s

index 349ffb4..4cbe93d 100644 (file)
@@ -63,7 +63,7 @@ extern unsigned int swap_dev;
 #define CONFIG_IDE
 
 /* Device parameters */
-#define NUM_DEV_TTY 2
+#define NUM_DEV_TTY 4
 
 /* UART0 as the console */
 #define BOOT_TTY (512 + 1)
index 1a52d3e..885f9d2 100644 (file)
@@ -21,7 +21,7 @@ struct devsw dev_tab[] =  /* The device driver switch table */
   /* 1: /dev/fd - Floppy disk block devices */
   {  no_open,      no_close,   no_rdwr,        no_rdwr,        no_ioctl},
   /* 2: /dev/tty -- serial ports */
-  {  tty_open,     tty_close,  tty_read,       tty_write,      tty_ioctl},
+  {  rctty_open,   tty_close,  tty_read,       tty_write,      tty_ioctl},
   /* 3: RAM disk */
   {  no_open,      no_close,   no_rdwr,        no_rdwr,        no_ioctl},
   /* 4: /dev/mem etc      System devices (one offs) */
index acf5ae5..d7c230b 100644 (file)
@@ -8,10 +8,18 @@
 
 static char tbuf1[TTYSIZ];
 static char tbuf2[TTYSIZ];
+static char tbuf3[TTYSIZ];
+static char tbuf4[TTYSIZ];
 
 static uint8_t sleeping;
 
-uint8_t ser_type = 2;
+struct s_queue ttyinq[NUM_DEV_TTY + 1] = {     /* ttyinq[0] is never used */
+       {NULL, NULL, NULL, 0, 0, 0},
+       {tbuf1, tbuf1, tbuf1, TTYSIZ, 0, TTYSIZ / 2},
+       {tbuf2, tbuf2, tbuf2, TTYSIZ, 0, TTYSIZ / 2},
+       {tbuf3, tbuf3, tbuf3, TTYSIZ, 0, TTYSIZ / 2},
+       {tbuf4, tbuf2, tbuf2, TTYSIZ, 0, TTYSIZ / 2},
+};
 
 static tcflag_t uart0_mask[4] = {
        _ISYS,
@@ -32,13 +40,9 @@ static tcflag_t uart1_mask[4] = {
 tcflag_t *termios_mask[NUM_DEV_TTY + 1] = {
        NULL,
        uart0_mask,
-       uart1_mask
-};
-
-struct s_queue ttyinq[NUM_DEV_TTY + 1] = {     /* ttyinq[0] is never used */
-       {NULL, NULL, NULL, 0, 0, 0},
-       {tbuf1, tbuf1, tbuf1, TTYSIZ, 0, TTYSIZ / 2},
-       {tbuf2, tbuf2, tbuf2, TTYSIZ, 0, TTYSIZ / 2},
+       uart1_mask,
+       uart0_mask,
+       uart0_mask
 };
 
 uint8_t sio_r[] = {
@@ -47,13 +51,52 @@ uint8_t sio_r[] = {
        0x05, 0xEA
 };
 
+static uint16_t siobaud[] = {
+       0xC0,   /* 0 */
+       0,      /* 50 */
+       0,      /* 75 */
+       0,      /* 110 */
+       0,      /* 134 */
+       0,      /* 150 */
+       0xC0,   /* 300 */
+       0x60,   /* 600 */
+       0xC0,   /* 1200 */
+       0x60,   /* 2400 */
+       0x30,   /* 4800 */
+       0x18,   /* 9600 */
+       0x0C,   /* 19200 */
+       0x06,   /* 38400 */
+       0x04,   /* 57600 */
+       0x02    /* 115200 */
+};
+
 static void sio2_setup(uint8_t minor, uint8_t flags)
 {
        struct termios *t = &ttydata[minor].termios;
        uint8_t r;
+       uint8_t baud;
+
+       used(flags);
+
+       baud = t->c_cflag & CBAUD;
+       if (baud < B300)
+               baud = B300;
+
        /* Set bits per character */
        sio_r[1] = 0x01 | ((t->c_cflag & CSIZE) << 2);
+
        r = 0xC4;
+       if (ctc_present && minor == 3) {
+               CTC_CH1 = 0x55;
+               CTC_CH1 = siobaud[baud];
+               if (baud > B600)        /* Use x16 clock and CTC divider */
+                       r = 0x44;
+       } else
+               baud = B115200;
+
+       t->c_cflag &= CBAUD;
+       t->c_cflag |= baud;
+
        if (t->c_cflag & CSTOPB)
                r |= 0x08;
        if (t->c_cflag & PARENB)
@@ -66,13 +109,13 @@ static void sio2_setup(uint8_t minor, uint8_t flags)
 
 void tty_setup(uint8_t minor, uint8_t flags)
 {
-       if (ser_type == 1) {
+       if (sio_present || sio1_present) {
                sio2_setup(minor, flags);
                sio2_otir(SIO0_BASE + 2 * (minor - 1));
                /* We need to do CTS/RTS support and baud setting on channel 2
                   yet */
        }
-       if (ser_type == 2) {
+       if (acia_present) {
                struct termios *t = &ttydata[1].termios;
                uint8_t r = t->c_cflag & CSIZE;
                /* No CS5/CS6 CS7 must have parity enabled */
@@ -115,22 +158,21 @@ void tty_setup(uint8_t minor, uint8_t flags)
 int tty_carrier(uint8_t minor)
 {
         uint8_t c;
-       if (ser_type == 1) {
-               if (minor == 1) {
-                       SIOA_C = 0;
-                       c = SIOA_C;
-               } else {
-                       SIOB_C = 0;
-                       c = SIOB_C;
-               }
-               if (c & 0x8)
-                       return 1;
-               return 0;
-       } else  /* ACIA isn't wired for carrier on any board */
+        uint8_t port;
+
+        /* No carrier on ACIA */
+        if (sio_present == 0)
+               return 1;
+
+       port = SIO0_BASE + 2 * (minor - 1);
+       out(port, 0);
+       c = in(port);
+       if (c & 0x08)
                return 1;
+       return 0;
 }
 
-void tty_pollirq_sio(void)
+void tty_pollirq_sio0(void)
 {
        static uint8_t old_ca, old_cb;
        uint8_t ca, cb;
@@ -157,7 +199,7 @@ void tty_pollirq_sio(void)
                        SIOA_C = 2 << 5;
                /* Output pending */
                if ((ca & 4) && (sleeping & 2)) {
-                       tty_outproc(1);
+                       tty_outproc(2);
                        sleeping &= ~2;
                        SIOA_C = 5 << 3;        // reg 0 CMD 5 - reset transmit interrupt pending
                }
@@ -174,9 +216,9 @@ void tty_pollirq_sio(void)
                        tty_inproc(2, SIOB_D);
                        progress = 1;
                }
-               if ((cb & 4) && (sleeping & 4)) {
-                       tty_outproc(2);
-                       sleeping &= ~4;
+               if ((cb & 4) && (sleeping & 8)) {
+                       tty_outproc(3);
+                       sleeping &= ~8;
                        SIOB_C = 5 << 3;        // reg 0 CMD 5 - reset transmit interrupt pending
                }
                if ((cb ^ old_cb) & 8) {
@@ -188,14 +230,71 @@ void tty_pollirq_sio(void)
        } while(progress);
 }
 
+void tty_pollirq_sio1(void)
+{
+       static uint8_t old_ca, old_cb;
+       uint8_t ca, cb;
+       uint8_t progress;
+
+       /* Check for an interrupt */
+       SIOC_C = 0;
+       if (!(SIOC_C & 2))
+               return;
+
+       /* FIXME: need to process error/event interrupts as we can get
+          spurious characters or lines on an unused SIO floating */
+       do {
+               progress = 0;
+               SIOC_C = 0;             // read register 0
+               ca = SIOC_C;
+               /* Input pending */
+               if ((ca & 1) && !fullq(&ttyinq[3])) {
+                       progress = 1;
+                       tty_inproc(3, SIOC_D);
+               }
+               /* Break */
+               if (ca & 2)
+                       SIOC_C = 2 << 5;
+               /* Output pending */
+               if ((ca & 4) && (sleeping & 8)) {
+                       tty_outproc(3);
+                       sleeping &= ~8;
+                       SIOC_C = 5 << 3;        // reg 0 CMD 5 - reset transmit interrupt pending
+               }
+               /* Carrier changed */
+               if ((ca ^ old_ca) & 8) {
+                       if (ca & 8)
+                               tty_carrier_raise(3);
+                       else
+                               tty_carrier_drop(3);
+               }
+               SIOD_C = 0;             // read register 0
+               cb = SIOD_C;
+               if ((cb & 1) && !fullq(&ttyinq[4])) {
+                       tty_inproc(4, SIOD_D);
+                       progress = 1;
+               }
+               if ((cb & 4) && (sleeping & 16)) {
+                       tty_outproc(4);
+                       sleeping &= ~16;
+                       SIOD_C = 5 << 3;        // reg 0 CMD 5 - reset transmit interrupt pending
+               }
+               if ((cb ^ old_cb) & 8) {
+                       if (cb & 8)
+                               tty_carrier_raise(4);
+                       else
+                               tty_carrier_drop(4);
+               }
+       } while(progress);
+}
+
 void tty_pollirq_acia(void)
 {
        uint8_t ca;
 
        ca = ACIA_C;
        if (ca & 1) {
-               ca = ACIA_D;
-               tty_inproc(1, ca);
+               tty_inproc(1, ACIA_D);
        }
        if ((ca & 2) && sleeping) {
                tty_outproc(1);
@@ -203,25 +302,19 @@ void tty_pollirq_acia(void)
        }
 }
 
-static char hex[] = { "0123456789ABCDEF" };
-
 void tty_putc(uint8_t minor, unsigned char c)
 {
-       if (ser_type == 1) {
-               if (minor == 1) {
-                       SIOA_D = c;
-               } else if (minor == 2)
-                       SIOB_D = c;
-       } else if (minor == 1)
-               ACIA_D = c;
-       else if (minor = 3) {
-               /* FIXME: implement */
+       if (acia_present)
+               SIOA_D = c;
+       else {
+               uint8_t port = SIO0_BASE + 1 + 2 * (minor - 1);
+               out(port, c);
        }
 }
 
-/* We will need this for SIO once we implement flow control signals */
 void tty_sleeping(uint8_t minor)
 {
+       sleeping |= (1 << minor);
 }
 
 /* Be careful here. We need to peek at RR but we must be sure nobody else
@@ -232,40 +325,33 @@ void tty_sleeping(uint8_t minor)
 
    Need to review this we should be ok as the IRQ handler always leaves
    us pointing at RR0 */
-
 ttyready_t tty_writeready(uint8_t minor)
 {
        irqflags_t irq;
        uint8_t c;
-       if (ser_type == 1) {
-               irq = di();
-               if (minor == 1) {
-                       SIOA_C = 0;     /* read register 0 */
-                       c = SIOA_C;
-                       irqrestore(irq);
-                       if (c & 0x04)   /* THRE? */
-                               return TTY_READY_NOW;
-                       return TTY_READY_SOON;
-               } else if (minor == 2) {
-                       SIOB_C = 0;     /* read register 0 */
-                       c = SIOB_C;
-                       irqrestore(irq);
-                       if (c & 0x04)   /* THRE? */
-                               return TTY_READY_NOW;
-                       return TTY_READY_SOON;
-               }
-               irqrestore(irq);
-       } else if (ser_type == 2 && minor == 1) {
+       uint8_t port;
+
+       if (acia_present) {
                c = ACIA_C;
                if (c & 0x02)   /* THRE? */
                        return TTY_READY_NOW;
                return TTY_READY_SOON;
        }
-       return TTY_READY_NOW;
+
+       irq = di();
+       port = SIO0_BASE+ 2 * (minor - 1);
+       out(port, 0);
+       c = in(port);
+       irqrestore(irq);
+
+       if (c & 0x04)   /* THRE? */
+               return TTY_READY_NOW;
+       return TTY_READY_SOON;
 }
 
 void tty_data_consumed(uint8_t minor)
 {
+       used(minor);
 }
 
 /* kernel writes to system console -- never sleep! */
@@ -277,3 +363,20 @@ void kputchar(char c)
        while(tty_writeready(TTYDEV - 512) != TTY_READY_NOW);
        tty_putc(TTYDEV - 512, c);
 }
+
+int rctty_open(uint8_t minor, uint16_t flag)
+{
+       if (acia_present && minor != 1) {
+               udata.u_error = ENODEV;
+               return -1;
+       }
+       if ((minor == 1 || minor == 2) && !sio_present) {
+               udata.u_error = ENODEV;
+               return -1;
+       }
+       if ((minor == 3 || minor == 4) && !sio1_present) {
+               udata.u_error = ENODEV;
+               return -1;
+       }
+       return tty_open(minor, flag);
+}
index 518450e..6bc732c 100644 (file)
@@ -2,9 +2,9 @@
 #define __DEVTTY_DOT_H__
 
 void tty_putc(uint8_t minor, unsigned char c);
-void tty_pollirq_sio(void);
+void tty_pollirq_sio0(void);
+void tty_pollirq_sio1(void);
 void tty_pollirq_acia(void);
-
-extern uint8_t ser_type;
+int rctty_open(uint8_t minor, uint16_t flag);
 
 #endif
index 2a91275..b54093d 100644 (file)
@@ -5,10 +5,19 @@
 #include <ds1302.h>
 #include <devide.h>
 #include <blkdev.h>
+#include <rc2014.h>
 #include "config.h"
 
 void map_init(void)
 {
+       if (acia_present)
+               kputs("6850 ACIA detected at 0x80.\n");
+       if (sio_present)
+               kputs("Z80 SIO detected at 0x80.\n");
+       if (sio1_present)
+               kputs("Z80 SIO detected at 0x84.\n");
+       if (ctc_present)
+               kputs("Z80 CTC detected at 0x88.\n");
 }
 
 /*
index 37a5db6..20988a3 100644 (file)
@@ -17,12 +17,6 @@ CONSOLE_RATE         .equ    115200
 
 CPU_CLOCK_KHZ          .equ    7372
 
-; Base address of SIO/2 chip 0x80
-; For the Scott Baker SIO card adjust the order to match rc2014.h
-SIOA_C         .EQU    0x80
-SIOA_D         .EQU    SIOA_D+1
-SIOB_C         .EQU    SIOA_D+2
-SIOB_D         .EQU    SIOA_D+3
 ; Z80 CTC ports
 CTC_CH0                .equ    0x88    ; CTC channel 0 and interrupt vector
 CTC_CH1                .equ    0x89    ; CTC channel 1 (periodic interrupts)
index 6492518..2ac5003 100644 (file)
@@ -6,6 +6,7 @@
 #include <devfd.h>
 #include <rtc.h>
 #include <ds1302.h>
+#include <rc2014.h>
 
 extern unsigned char irqvector;
 struct blkbuf *bufpool_end = bufpool + NBUFS;  /* minimal for boot -- expanded after we're done with _DISCARD */
@@ -13,6 +14,10 @@ uint16_t swap_dev = 0xFFFF;
 uint16_t ramtop = 0xF000;
 uint8_t need_resched = 0;
 
+uint8_t acia_present;
+uint8_t ctc_present;
+uint8_t sio_present;
+uint8_t sio1_present;
 
 void platform_discard(void)
 {
@@ -27,15 +32,15 @@ void platform_discard(void)
        kprintf("Buffers available: %d\n", bufpool_end - bufpool);
 }
 
-static uint8_t idlect;
-
 void platform_idle(void)
 {
-       /* Check the clock. We try and reduce the impact of the clock on
-          latency by not doing it so often. 256 may be too small a divide
-          need t see what 1/10th sec looks like in poll loops */
-       if (!++idlect)
+       if (ctc_present)
+               __asm halt __endasm;
+       else {
+               irqflags_t irq = di();
                sync_clock();
+               irqrestore(irq);
+       }
 }
 
 uint8_t platform_param(unsigned char *p)
@@ -44,13 +49,32 @@ uint8_t platform_param(unsigned char *p)
        return 0;
 }
 
+static int16_t timerct;
+
+/* Call timer_interrupt at 10Hz */
+static void timer_tick(uint8_t n)
+{
+       timerct += n;
+       while (timerct >= 20) {
+               timer_interrupt();
+               timerct -= 20;
+       }
+}
+
 void platform_interrupt(void)
 {
-       if (ser_type == 1)
-               tty_pollirq_sio();
-       else
+       if (acia_present)
                tty_pollirq_acia();
-       return;
+       if (sio_present)
+               tty_pollirq_sio0();
+       if (sio1_present)
+               tty_pollirq_sio1();
+       if (ctc_present) {
+               uint8_t n = 255 - CTC_CH3;
+               CTC_CH3 = 0x47;
+               CTC_CH3 = 255;
+               timer_tick(n);
+       }
 }
 
 /*
@@ -93,24 +117,24 @@ static void sync_clock_read(void)
  */
 void sync_clock(void)
 {
-       irqflags_t irq = di();
-       int16_t tmp;
-       if (!re_enter++) {
-               sync_clock_read();
-               if (oldticks != 0xFF) {
-                       tmp = newticks - oldticks;
-                       if (tmp < 0)
-                               tmp += 60;
-                       tmp *= 10;
-                       while(tmp--) {
-                               timer_interrupt();
+       if (!ctc_present) {
+               irqflags_t irq = di();
+               int16_t tmp;
+               if (!re_enter++) {
+                       sync_clock_read();
+                       if (oldticks != 0xFF) {
+                               tmp = newticks - oldticks;
+                               if (tmp < 0)
+                                       tmp += 60;
+                               tmp *= 10;
+                               while(tmp--) {
+                                       timer_interrupt();
+                               }
                        }
                }
+               re_enter--;
+               irqrestore(irq);
        }
-       re_enter--;
-       if (re_enter > 1)
-               kputs("oops");
-       irqrestore(irq);
 }
 
 /*
index 21ee491..21b405d 100644 (file)
@@ -14,12 +14,28 @@ __sfr __at (SIO0_BASE + 1) SIOA_D;
 __sfr __at (SIO0_BASE + 2) SIOB_C;
 __sfr __at (SIO0_BASE + 3) SIOB_D;
 
+#define SIO1_BASE 0x84
+__sfr __at (SIO1_BASE + 0) SIOC_C;
+__sfr __at (SIO1_BASE + 1) SIOC_D;
+__sfr __at (SIO1_BASE + 2) SIOD_C;
+__sfr __at (SIO1_BASE + 3) SIOD_D;
+
 /* ACIA is at same address as SIO but we autodetect */
 
 #define ACIA_BASE 0x80
 __sfr __at (ACIA_BASE + 0) ACIA_C;
 __sfr __at (ACIA_BASE + 1) ACIA_D;
 
+__sfr __at 0x88 CTC_CH0;
+__sfr __at 0x89 CTC_CH1;
+__sfr __at 0x8A CTC_CH2;
+__sfr __at 0x8B CTC_CH3;
+
 extern void sio2_otir(uint8_t port) __z88dk_fastcall;
 
+extern uint8_t acia_present;
+extern uint8_t ctc_present;
+extern uint8_t sio_present;
+extern uint8_t sio1_present;
+
 #endif
index 33e65aa..ee6e9ab 100644 (file)
        .globl kstack_top
         .globl unix_syscall_entry
         .globl outcharhex
-       .globl _ser_type
+       .globl _acia_present
+       .globl _ctc_present
+       .globl _sio_present
+       .globl _sio1_present
 
        .globl s__COMMONMEM
        .globl l__COMMONMEM
         .include "../kernel.def"
 
 
+; Base address of SIO/2 chip 0x80
+
+SIOA_C         .EQU    0x80
+SIOA_D         .EQU    SIOA_C+1
+SIOB_C         .EQU    SIOA_C+2
+SIOB_D         .EQU    SIOA_C+3
+
+SIOC_C         .EQU    0x84
+SIOC_D         .EQU    SIOC_C+1
+SIOD_C         .EQU    SIOC_C+2
+SIOD_D         .EQU    SIOC_C+3
+
+ACIA_C          .EQU     0x80
+ACIA_D          .EQU     0x81
+ACIA_RESET      .EQU     0x03
+ACIA_RTS_HIGH_A      .EQU     0xD6   ; rts high, xmit interrupt disabled
+ACIA_RTS_LOW_A       .EQU     0x96   ; rts low, xmit interrupt disabled
+;ACIA_RTS_LOW_A       .EQU     0xB6   ; rts low, xmit interrupt enabled
+
+
 ;
 ; Buffers (we use asm to set this up as we need them in a special segment
 ; so we can recover the discard memory into the buffer pool
@@ -119,19 +142,44 @@ init_hardware:
        call ldir_to_user
 
        ; Play guess the serial port
-       ; This needs doing better. We might be fooled by floating flow
-       ; control lines as the SC104 does expose flow control. FIXME
-       in a,(SIOA_C)
-       and #0x2C
-       cp #0x2C
-       ; CTS and DCD should be high as they are not wired
-       jr nz, try_acia
+
+       ;
+       ; We are booted under ROMWBW, therefore use the same algorithm as
+       ; ROMWBW so if the probe fails we at least expect it to have failed
+       ; before we run.
+       ;
+       ; FIXME: see if we can cleanly ask ROMWBW for the device type
+       ;
+
+       ;
+       ; This could be the ACIA control port. If so we mash the settings
+       ; up but that is ok as we will port them back in the ACIA probe
+       ;
+
+       xor a
+       ld c,#SIOA_C
+       out (c),a                       ; RR0
+       in b,(c)                        ; Save RR0 value
+       inc a
+       out (c),a                       ; RR1
+       in a,(c)
+       cp b                            ; Same from both reads - not an SIO
+
+       jr z, try_acia
 
        ; Repeat the check on SIO B
-       in a,(SIOB_C)
-       and #0x2C
-       cp #0x2C
-       jr z, is_sio
+
+       xor a
+       ld c,#SIOB_C
+       out (c),a                       ; RR0
+       in b,(c)                        ; Save RR0 value
+       inc a
+       out (c),a                       ; RR1
+       in a,(c)
+       cp b                            ; Same from both reads - not an SIO
+
+       jr nz, is_sio
+
 try_acia:
        ;
        ;       Look for an ACIA
@@ -146,8 +194,8 @@ try_acia:
 
         ld a, #ACIA_RTS_LOW_A
         out (ACIA_C),a                         ; Initialise ACIA
-       ld a,#2
-       ld (_ser_type),a
+       ld a,#1
+       ld (_acia_present),a
        jp serial_up
 
        ;
@@ -156,14 +204,12 @@ try_acia:
        ; At least until RC2014 grows a nice keyboard/display card!
        ;
 not_acia_either:
-       xor a
-       ld (_ser_type),a
        jp serial_up
 ;
 ;      We have an SIO so do the required SIO hdance
 ;
-is_sio:        ld a,b
-       ld (_ser_type),a
+is_sio:        ld a,#1
+       ld (_sio_present),a
 
        ld hl,#sio_setup
        ld bc,#0xA00 + SIOA_C           ; 10 bytes to SIOA_C
@@ -172,9 +218,123 @@ is_sio:   ld a,b
        ld bc,#0x0A00 + SIOB_C          ; and to SIOB_C
        otir
 
+       xor a
+       ld c,#SIOC_C
+       out (c),a                       ; RR0
+       in b,(c)                        ; Save RR0 value
+       inc a
+       out (c),a                       ; RR1
+       in a,(c)
+       cp b                            ; Same from both reads - not an SIO
+
+       jr z, serial_up
+
+       ; Repeat the check on SIO B
+
+       xor a
+       ld c,#SIOD_C
+       out (c),a                       ; RR0
+       in b,(c)                        ; Save RR0 value
+       inc a
+       out (c),a                       ; RR1
+       in a,(c)
+       cp b                            ; Same from both reads - not an SIO
+
+       jr z, serial_up
+
+       ld a,#0x01
+       ld (_sio1_present),a
+
+       ld hl,#sio_setup
+       ld bc,#0xA00 + SIOC_C           ; 10 bytes to SIOC_C
+       otir
+       ld hl,#sio_setup
+       ld bc,#0x0A00 + SIOD_C          ; and to SIOD_C
+       otir
+
+
 serial_up:
-        im 1 ; set CPU interrupt mode
-        ret
+
+        ; ---------------------------------------------------------------------
+       ; Initialize CTC
+       ;
+       ; Need to do autodetect on this
+       ;
+       ; We must initialize all channels of the CTC. The documentation
+       ; states that the initial CTC state is undefined and we don't want
+       ; random interrupt surprises
+       ;
+       ; ---------------------------------------------------------------------
+
+       ;
+       ; Defense in depth - shut everything up first
+       ;
+
+       ld a,#0x43
+       out (CTC_CH0),a                 ; set CH0 mode
+       out (CTC_CH1),a                 ; set CH1 mode
+       out (CTC_CH2),a                 ; set CH2 mode
+       out (CTC_CH3),a                 ; set CH3 mode
+
+       ;
+       ; Probe for a CTC
+       ;
+
+       ld a,#0x47                      ; CTC 2 as counter
+       out (CTC_CH2),a
+       ld a,#0xAA                      ; Set a count
+       out (CTC_CH2),a
+       in a,(CTC_CH2)
+       cp #0xAA                        ; Should not have changed
+       jr nz, no_ctc
+
+       ld a,#0x07
+       out (CTC_CH2),a
+       ld a,#2
+       out (CTC_CH2),a
+
+       ; We are now counting down from 2 very fast, so should only see
+       ; those values on the bus
+
+       ld b,#0
+ctc_check:
+       in a,(CTC_CH2)
+       and #0xFC
+       jr nz, no_ctc
+       djnz ctc_check
+
+       ;
+       ; Looks like we have a CTC
+       ;
+
+have_ctc:
+       ld a,#1
+       ld (_ctc_present),a
+
+       ;
+       ; Set up timer for 200Hz
+       ;
+
+       ld a,#0xB5
+       out (CTC_CH2),a
+       ld a,#144
+       out (CTC_CH2),a ; 200 Hz
+
+       ;
+       ; Set up counter CH3 for official SIO (the SC110 sadly can't be
+       ; used this way).
+
+       ld a,#0x47
+       out (CTC_CH3),a
+       ld a,#255
+       out (CTC_CH3),a
+
+no_ctc:
+        ; Done CTC Stuff
+        ; ---------------------------------------------------------------------
+
+       im 1                            ; set Z80 CPU interrupt mode 1
+       ret
 
 RTS_LOW        .EQU    0xEA