From dfb93869e00b32ead065ca556f9a5a44f15dc945 Mon Sep 17 00:00:00 2001 From: Will Sowerbutts Date: Fri, 20 Feb 2015 21:33:43 +0000 Subject: [PATCH] p112: Improved ESCC, 16550A serial port support --- Kernel/cpu-z180/z180.s | 22 +-- Kernel/platform-p112/config.h | 2 +- Kernel/platform-p112/devices.c | 1 + Kernel/platform-p112/devtty.c | 242 ++++++++++++++++++++++++++++++--- Kernel/platform-p112/devtty.h | 35 +++++ Kernel/platform-p112/discard.c | 20 ++- Kernel/platform-p112/main.c | 3 + Kernel/platform-p112/p112.s | 14 +- 8 files changed, 303 insertions(+), 36 deletions(-) diff --git a/Kernel/cpu-z180/z180.s b/Kernel/cpu-z180/z180.s index edee00d2..1f668c6e 100644 --- a/Kernel/cpu-z180/z180.s +++ b/Kernel/cpu-z180/z180.s @@ -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 diff --git a/Kernel/platform-p112/config.h b/Kernel/platform-p112/config.h index c8afad4e..07789f6f 100644 --- a/Kernel/platform-p112/config.h +++ b/Kernel/platform-p112/config.h @@ -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 */ diff --git a/Kernel/platform-p112/devices.c b/Kernel/platform-p112/devices.c index e814e816..10643085 100644 --- a/Kernel/platform-p112/devices.c +++ b/Kernel/platform-p112/devices.c @@ -39,4 +39,5 @@ void device_init(void) { devide_init(); ds1302_init(); + tty_hw_init(); } diff --git a/Kernel/platform-p112/devtty.c b/Kernel/platform-p112/devtty.c index ecf0c492..928776d2 100644 --- a/Kernel/platform-p112/devtty.c +++ b/Kernel/platform-p112/devtty.c @@ -1,72 +1,278 @@ +#define _DEVTTY_PRIVATE #include +#include "config.h" +#include #include #include #include #include #include -#include "config.h" -#include + +/* 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) { diff --git a/Kernel/platform-p112/devtty.h b/Kernel/platform-p112/devtty.h index 9417ced8..d72a7dbb 100644 --- a/Kernel/platform-p112/devtty.h +++ b/Kernel/platform-p112/devtty.h @@ -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 diff --git a/Kernel/platform-p112/discard.c b/Kernel/platform-p112/discard.c index e8eb7f16..865e72bf 100644 --- a/Kernel/platform-p112/discard.c +++ b/Kernel/platform-p112/discard.c @@ -1,9 +1,10 @@ +#define _DEVTTY_PRIVATE #include #include #include #include -#include #include "config.h" +#include #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 */ +} + diff --git a/Kernel/platform-p112/main.c b/Kernel/platform-p112/main.c index 72e5058a..a34c8a5d 100644 --- a/Kernel/platform-p112/main.c +++ b/Kernel/platform-p112/main.c @@ -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; diff --git a/Kernel/platform-p112/p112.s b/Kernel/platform-p112/p112.s index da4aa976..3eb53b4f 100644 --- a/Kernel/platform-p112/p112.s +++ b/Kernel/platform-p112/p112.s @@ -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 -- 2.34.1