p112: Improved ESCC, 16550A serial port support
authorWill Sowerbutts <will@sowerbutts.com>
Fri, 20 Feb 2015 21:33:43 +0000 (21:33 +0000)
committerWill Sowerbutts <will@sowerbutts.com>
Fri, 20 Feb 2015 21:36:05 +0000 (21:36 +0000)
Kernel/cpu-z180/z180.s
Kernel/platform-p112/config.h
Kernel/platform-p112/devices.c
Kernel/platform-p112/devtty.c
Kernel/platform-p112/devtty.h
Kernel/platform-p112/discard.c
Kernel/platform-p112/main.c
Kernel/platform-p112/p112.s

index edee00d..1f668c6 100644 (file)
@@ -550,8 +550,8 @@ _dofork:
         ; MUST arrange for this table to be 32-byte aligned
         ; linked immediately after commonmem.s
 interrupt_table:
-        .dw z180_irq_unused         ;     1    INT1 external interrupt - ? disconnected
-        .dw z180_irq_unused         ;     2    INT2 external interrupt - SD card socket event
+        .dw z180_irq1               ;     1    INT1 external interrupt - ? disconnected
+        .dw z180_irq2               ;     2    INT2 external interrupt - SD card socket event
         .dw z180_irq3               ;     3    Timer 0
         .dw z180_irq_unused         ;     4    Timer 1
         .dw z180_irq_unused         ;     5    DMA 0
@@ -575,15 +575,15 @@ z80_irq:
         xor a
         jr z180_irqgo
 
-z180_irq1:
-        push af
-        ld a, #1
-        jr z180_irqgo
-; 
-z180_irq2:
-        push af
-        ld a, #2
-        jr z180_irqgo
+z180_irq1:
+        push af
+        ld a, #1
+        jr z180_irqgo
+
+z180_irq2:
+        push af
+        ld a, #2
+        jr z180_irqgo
 
 z180_irq3:
         push af
index c8afad4..07789f6 100644 (file)
@@ -35,7 +35,7 @@
 #define BOOTDEVICENAMES "hd#,fd"
 
 /* Device parameters */
-#define NUM_DEV_TTY 4
+#define NUM_DEV_TTY 5
 
 #define TTYDEV   BOOT_TTY /* Device used by kernel for messages, panics */
 #define NBUFS    10       /* Number of block buffers */
index e814e81..1064308 100644 (file)
@@ -39,4 +39,5 @@ void device_init(void)
 {
     devide_init();
     ds1302_init();
+    tty_hw_init();
 }
index ecf0c49..928776d 100644 (file)
+#define _DEVTTY_PRIVATE
 #include <kernel.h>
+#include "config.h"
+#include <z180.h>
 #include <kdata.h>
 #include <printf.h>
 #include <stdbool.h>
 #include <tty.h>
 #include <devtty.h>
-#include "config.h"
-#include <z180.h>
+
+/* Will Sowerbutts 2015-02-15
+ *
+ * The P112 has so many serial ports! Here's a quick map:
+ *
+ * tty1 -- ESCC channel A (P4 header, RS232 levels, boot ROM uses this port)
+ * tty2 -- ESCC channel B (P14 header, TTL levels)
+ * tty3 -- ASCI channel 0 (P14 header, TTL levels)
+ * tty4 -- ASCI channel 1 (P14 header, TTL levels)
+ * tty5 -- SMC 16550 COM1 (P8 header, RS232 levels)
+ *
+ * We assume RTS/CTS flow control is in use.
+ *
+ * Note that the SMC has a second 16550 (COM2) but the pins are
+ * not presented on a header ... I guess five was enough!
+
+
+ * A note on interrupts:
+ *
+ * In normal operation we configure UART to generate an interrupt only when
+ * data has been received.
+ *
+ * When a transmitting process goes to sleep waiting on the UART (either
+ * because CTS is low or because it's time to reschedule) we enable interrupts
+ * on both CTS status change and transmitter ready. These are then disabled
+ * when we re-awaken the process. This has to be done with ints disabled
+ * otherwise there is a race to put the process to sleep before the interrupt
+ * to awaken it arrives.
+ */
+
+/* TODO:
+ *  - only do RTS/CTS flow control when configured to do so (how to test?)
+ *  - drive RTS signals, requires tty_inproc() to signal to us when to do so
+ *  - resurrect tty_carrier() code?
+ *  - implement RTS/CTS for ASCI (waiting on my making up a cable ...)
+ */
 
 char tbuf1[TTYSIZ];
 char tbuf2[TTYSIZ];
 char tbuf3[TTYSIZ];
 char tbuf4[TTYSIZ];
+char tbuf5[TTYSIZ];
 
 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,   tbuf2,   tbuf2,   TTYSIZ,   0,   TTYSIZ/2 },
-    {   tbuf4,   tbuf2,   tbuf2,   TTYSIZ,   0,   TTYSIZ/2 },
+    {   tbuf3,   tbuf3,   tbuf3,   TTYSIZ,   0,   TTYSIZ/2 },
+    {   tbuf4,   tbuf4,   tbuf4,   TTYSIZ,   0,   TTYSIZ/2 },
+    {   tbuf5,   tbuf5,   tbuf5,   TTYSIZ,   0,   TTYSIZ/2 },
 };
 
+/* tty_hw_init() which sets up tty5 can be found in discard.c */
+
 void tty_setup(uint8_t minor)
 {
     minor;
 }
 
-/* For the moment */
 int tty_carrier(uint8_t minor)
 {
-    minor;
+#if 0   /* The code below works -- but if ESCC A has DCD low on boot
+           the system crashes, which is no fun. So we just lie and 
+           always report that it is high. A battle for another day. */
+    uint8_t c;
+
+    switch(minor){
+        /* ---- ESCC ports ------------------------- */
+        case 1:
+            c = ESCC_CTRL_A;
+escc_carriertest:
+            return (c & 8) ? 1 : 0; /* test DCD */
+        case 2:
+            c = ESCC_CTRL_B;
+            goto escc_carriertest;
+        /* ---- 16550 port ------------------------- */
+        case 5:
+            c = TTY_COM1_MSR;
+            return (c & 0x80) ? 1 : 0; /* test DCD */
+
+        /* ---- ASCI ports ------------------------- */
+            /* TODO: test DCD for ASCI */
+    }
+#endif
+    minor; /* unused */
     return 1;
 }
 
 void tty_pollirq_asci0(void)
 {
     while(ASCI_STAT0 & 0x80){
-        tty_inproc(1, ASCI_RDR0);
+        tty_inproc(3, ASCI_RDR0);
     }
 }
 
 void tty_pollirq_asci1(void)
 {
     while(ASCI_STAT1 & 0x80){
-        tty_inproc(2, ASCI_RDR1);
+        tty_inproc(4, ASCI_RDR1);
+    }
+}
+
+void tty_pollirq_com1(void)
+{
+    uint8_t iir, msr, lsr;
+
+    while(true){
+        iir = TTY_COM1_IIR;
+        lsr = TTY_COM1_LSR;
+        /* IIR bits
+         * 3 2 1 0
+         * -------
+         * x x x 1     no interrupt pending
+         * 0 1 1 0  6  LSR changed -- read the LSR
+         * 0 1 0 0  4  receive FIFO >= threshold
+         * 1 1 0 0  C  received data sat in FIFO for a while
+         * 0 0 1 0  2  transmit holding register empty
+         * 0 0 0 0  0  MSR changed -- read the MSR
+         */
+        switch(iir & 0x0F){
+            case 0x0: /* MSR changed */
+            case 0x2: /* transmit register empty */
+                /* TODO: with DCD implemented we need to spot DCD changes here also */
+                msr = TTY_COM1_MSR;
+                if((msr & 0x10) && (lsr & 0x20)){ /* CTS high, transmit reg empty */
+                    tty_outproc(5);
+                }
+                /* fall through */
+            case 0x6: /* LSR changed */
+                /* we already read the LSR register so int has cleared */
+                TTY_COM1_IER = 0x01; /* enable only receive interrupts */
+                break;
+            case 0x4: /* receive (FIFO >= threshold) */
+            case 0xC: /* receive (timeout waiting for FIFO to fill) */
+                while(lsr & 0x01){ /* Data ready */
+                    tty_inproc(5, TTY_COM1_RBR);
+                    lsr = TTY_COM1_LSR;
+                }
+                break;
+            default:
+                return;
+        }
     }
 }
 
 void tty_pollirq_escc(void)
 {
-    unsigned char rr3;
+    uint8_t rr3;
+
     ESCC_CTRL_A = 0x03; /* select read register 3 */
     rr3 = ESCC_CTRL_A;
 
     if(rr3 & 0x20){ /* channel A RX pending */
-        tty_inproc(1, ESCC_DATA_A);
+        while(ESCC_CTRL_A & 1)
+            tty_inproc(1, ESCC_DATA_A);
     }
+
     if(rr3 & 0x04){ /* channel B RX pending */
-        tty_inproc(2, ESCC_DATA_B);
+        while(ESCC_CTRL_B & 1)
+            tty_inproc(2, ESCC_DATA_B);
+    }
+
+    if(rr3 & 0x18){ /* channel A TX pending or ext/status */
+        if(rr3 & 0x10)
+            ESCC_CTRL_A = 0x28; /* reset transmit interrupt */
+        if(rr3 & 0x08)
+            ESCC_CTRL_A = 0x10; /* reset external/status interrupts */
+        /* disable further tx/status ints */
+        ESCC_CTRL_A = 0x01; /* select WR1 */
+        ESCC_CTRL_A = 0x10; /* receive interrupts only please */
+        ESCC_CTRL_A = 0x0F; /* select WR15 */
+        ESCC_CTRL_A = 0x01; /* disable CTS interrupts (otherwise it latches CTS in RR0) */
+        tty_outproc(1);
     }
-    if(rr3 & (~0x24)){
-        kputs("[escc hurts]");
+
+    if(rr3 & 0x03){ /* channel B TX pending or ext/status */
+        if(rr3 & 0x02)
+            ESCC_CTRL_B = 0x28; /* reset transmit interrupt */
+        if(rr3 & 0x01)
+            ESCC_CTRL_B = 0x10; /* reset external/status interrupts */
+        /* disable further tx/status ints */
+        ESCC_CTRL_B = 0x01; /* select WR1 */
+        ESCC_CTRL_B = 0x10; /* receive interrupts only please */
+        ESCC_CTRL_B = 0x0F; /* select WR15 */
+        ESCC_CTRL_B = 0x01; /* disable CTS interrupts (otherwise it latches CTS in RR0) */
+        tty_outproc(2);
     }
 
     ESCC_CTRL_A = 0x38; /* reset interrupt under service */
 }
 
+void tty_sleeping(uint8_t minor)
+{
+    /* enable tx/status ints so we can awaken the process */
+    switch(minor){
+        case 1:
+            ESCC_CTRL_A = 0x01; /* select WR1 */
+            ESCC_CTRL_A = 0x17; /* receive, transmit, ext/status interrupts */
+            ESCC_CTRL_A = 0x0F; /* select WR15 */
+            ESCC_CTRL_A = 0x21; /* enable CTS interrupts */
+            break;
+        case 2:
+            ESCC_CTRL_B = 0x01; /* select WR1 */
+            ESCC_CTRL_B = 0x17; /* receive, transmit, ext/status interrupts */
+            ESCC_CTRL_B = 0x0F; /* select WR15 */
+            ESCC_CTRL_B = 0x21; /* enable CTS interrupts */
+            break;
+        case 5:
+            TTY_COM1_IER = 0x0B; /* enable all but LSR interrupt */
+    }
+}
+
+ttyready_t tty_writeready(uint8_t minor)
+{
+    uint8_t c;
+
+    switch(minor){
+        default:
+            return TTY_READY_NOW;
+
+        /* ---- ESCC ports ------------------------- */
+        case 1:
+            c = ESCC_CTRL_A;
+escc_readytest:
+            if((c & 0x20) == 0) /* CTS not asserted? */
+                return TTY_READY_LATER;
+            else if(c & 0x04) /* Transmit empty? */
+                return TTY_READY_NOW;
+            else /* This could be made baud rate dependent */
+                return TTY_READY_SOON;
+
+        case 2:
+            c = ESCC_CTRL_B;
+            goto escc_readytest;
+
+        /* ---- ASCI ports ------------------------- */
+        case 3:
+            c = ASCI_STAT0;
+asci_readytest:
+            if(c & 2)
+                return TTY_READY_NOW;
+            return TTY_READY_SOON;
+
+        case 4:
+            c = ASCI_STAT1;
+            goto asci_readytest;
+
+        /* ---- 16550 port ------------------------- */
+        case 5:
+            c = TTY_COM1_MSR;
+            if((c & 0x10) == 0) /* CTS not asserted? */
+                return TTY_READY_LATER;
+            c = TTY_COM1_LSR;
+            if( c & 0x20 ) /* THRE? */
+                return TTY_READY_NOW;
+            return TTY_READY_SOON;
+    }
+}
+
 void tty_putc(uint8_t minor, unsigned char c)
 {
+    /* note that these all ignore CTS; tty_writeready checks it, but it does
+     * mean kernel writes to console ignore flow control.  This could be easily
+     * fixed, but then we could get stuck in here indefinitely if CTS drops
+     * between tty_writeready and tty_putc */
+
     switch(minor){
         case 1:
             while(!(ESCC_CTRL_A & 4));
@@ -84,15 +290,13 @@ void tty_putc(uint8_t minor, unsigned char c)
             while(!(ASCI_STAT1 & 2));
             ASCI_TDR1 = c;
             break;
+        case 5:
+            while(!(TTY_COM1_LSR & 0x20));
+            TTY_COM1_THR = c;
+            break;
     }
 }
 
-ttyready_t tty_writeready(uint8_t minor)
-{
-    minor;
-    return TTY_READY_NOW;
-}
-
 /* kernel writes to system console -- never sleep! */
 void kputchar(char c)
 {
index 9417ced..d72a7db 100644 (file)
@@ -1,7 +1,42 @@
 #ifndef __DEVTTY_DOT_H__
 #define __DEVTTY_DOT_H__
+
+void tty_hw_init(void);
 void tty_putc(uint8_t minor, unsigned char c);
 void tty_pollirq_escc(void);
 void tty_pollirq_asci0(void);
 void tty_pollirq_asci1(void);
+void tty_pollirq_com1(void);
+
+#ifdef _DEVTTY_PRIVATE
+
+/* it's our old friend the 16550AF */
+#define COM1_BASE        0x98
+#define COM1_RBR         (COM1_BASE + 0) /* DLAB=0, read only */
+#define COM1_THR         (COM1_BASE + 0) /* DLAB=0, write only */
+#define COM1_IER         (COM1_BASE + 1) /* DLAB=0 */
+#define COM1_IIR         (COM1_BASE + 2) /* read only */
+#define COM1_FCR         (COM1_BASE + 2) /* write only */
+#define COM1_LCR         (COM1_BASE + 3)
+#define COM1_MCR         (COM1_BASE + 4)
+#define COM1_LSR         (COM1_BASE + 5)
+#define COM1_MSR         (COM1_BASE + 6)
+#define COM1_SCR         (COM1_BASE + 7)
+#define COM1_DLL         (COM1_BASE + 0) /* DLAB=1 */
+#define COM1_DLM         (COM1_BASE + 1) /* DLAB=1 */
+
+__sfr __at COM1_RBR TTY_COM1_RBR;
+__sfr __at COM1_THR TTY_COM1_THR;
+__sfr __at COM1_IER TTY_COM1_IER;
+__sfr __at COM1_IIR TTY_COM1_IIR;
+__sfr __at COM1_FCR TTY_COM1_FCR;
+__sfr __at COM1_LCR TTY_COM1_LCR;
+__sfr __at COM1_MCR TTY_COM1_MCR;
+__sfr __at COM1_LSR TTY_COM1_LSR;
+__sfr __at COM1_MSR TTY_COM1_MSR;
+__sfr __at COM1_SCR TTY_COM1_SCR;
+__sfr __at COM1_DLL TTY_COM1_DLL;
+__sfr __at COM1_DLM TTY_COM1_DLM;
+
+#endif
 #endif
index e8eb7f1..865e72b 100644 (file)
@@ -1,9 +1,10 @@
+#define _DEVTTY_PRIVATE
 #include <kernel.h>
 #include <kdata.h>
 #include <printf.h>
 #include <devtty.h>
-#include <z180.h>
 #include "config.h"
+#include <z180.h>
 #ifdef CONFIG_P112_FLOPPY
 #include "devfd.h"
 #endif
@@ -28,3 +29,20 @@ void map_init(void)
     copy_and_map_process(&init_process->p_page);
     /* kernel bank udata (0x300 bytes) is never used again -- could be reused? */
 }
+
+/* called from devices.c to set up tty5 */
+void tty_hw_init(void)
+{
+    /* Note: setup for tty1, tty2 (ESCC) is found in p112.s */
+
+    /* setup for tty5 (16550) for 115200,8n1 */
+    /* 37C655GT feature: Out2 in MCR is the master UART interrupt enable */
+    TTY_COM1_MCR = 0x0B; /* disable loopback, set DTR and RTS, enable Out2 */
+    TTY_COM1_LCR = 0x80; /* enable DLAB to access divisor */
+    TTY_COM1_DLL = 0x01; /* program divisor for 115200bps */
+    TTY_COM1_DLM = 0x00;
+    TTY_COM1_LCR = 0x03; /* disable DLAB, program 8 bits, no parity */
+    TTY_COM1_FCR = 0x07; /* enable and clear FIFOs, interrupt threshold 1 byte */
+    TTY_COM1_IER = 0x01; /* enable only receive interrupts */
+}
+
index 72e5058..a34c8a5 100644 (file)
@@ -41,6 +41,9 @@ void platform_interrupt(void)
         case Z180_INT0:
             tty_pollirq_escc();
             return;
+        case Z180_INT2:
+            tty_pollirq_com1();
+            return;
         case Z180_INT_TIMER0:
             z180_timer_interrupt(); 
             return;
index da4aa97..3eb53b4 100644 (file)
@@ -50,7 +50,7 @@ init_hardware:
         ld hl, #(RAM_KB-64)        ; 64K for kernel
         ld (_procmem), hl
 
-        ; enable ASCI interrupts
+        ; configure ASCI UART
         ; in0 a, (ASCI_STAT0)
         ; or #0x08                ; enable ASCI0 receive interrupts
         ; out0 (ASCI_STAT0), a
@@ -64,10 +64,16 @@ init_hardware:
         ; and #0x7f               ; disable RDRF interrupt inhibit
         ; out0 (ASCI_ASEXT1), a
 
-        ; enable ESCC interrupts
-        ld bc, #0x0114 ; write register 1, 0x14: enable receive interrupts only
+        ; configure ESCC UART
+        ld bc, #0x0F01 ; write register 15, disable CTS status interrupts, expose WR7'
         call write_escc
-        ld bc, #0x0908 ; write register 9, 0x08: master interrupt enable
+        ld bc, #0x0760 ; write register 7', enable TX interrupt only when FIFO is empty, extended read mode
+        call write_escc
+        ; WRS: Note that WR3 "obey CTS pin" for transmit control is not useful
+        ; as it also uses the DCD pin for receiver enable. Shame.
+        ld bc, #0x0110 ; write register 1, enable receive interrupts
+        call write_escc
+        ld bc, #0x0908 ; write register 9, master interrupt enable
         call write_escc
 
         jp z180_init_hardware