/* * (c) 1996-1997 Andre Fachat * fachat@physik.tu-chemnitz.de * * This is (part of) an UART 16550A serial line driver * for the OS/A65 operating system. * * The 16550 is not really an easy chip, but it has * 16 byte input and output FIFO buffers, which allows * much higher interrupt latencies. * * routines defined here are: * * devini - initializes 16550A hardware * devirq - must be called in the interrupt * routine * setspeed - sets the speed of the transmission * (yr = speed index in the divisor * table) * txon/txoff - switches transmitting on/off * rxon/rxoff - switches receiving on/off * * routines needed are: * * GETC - read byte from application in IRQ * (via software FIFO) * PUTC - write byte to application from IRQ * (via software FIFO) * STRCMD - get status of software FIFO */ /* UART register definitions */ #define RXTX 0 /* DLAB=0 */ #define IER 1 /* DLAB=0 */ #define DLL 0 /* divisor latch low, DLAB=1 */ #define DLH 1 /* divisor latch high, DLAB=1 */ #define IIR 2 /* Irq Ident. Reg., read only */ #define FCR 2 /* FIFO Ctrl Reg., write only */ #define LCR 3 /* Line Ctrl Reg */ #define MCR 4 /* Modem Ctrl Reg */ #define LSR 5 /* Line Status Reg */ #define MSR 6 /* Modem Status Reg */ #define SCR 7 /* 'scratchpad', i.e. unused */ /* * UART is the (memory mapped) address of the UART * The E_* codes are distinct error codes. E_OK is 0, * the others are not. * "status" is a local variable */ /* * status: Bit 0 = 1= handshake enabled * 1 : 1= no ACIA found * 5 : 1= set RTS hi * 6 : 1= we are transmitting (DC_SW_TX) * 7 : 1= we are receiving (DC_SW_RX) */ #define DC_SW_RX %10000000 #define DC_SW_TX %01000000 /* * 16550 divisor values for BAUD rates ?, 50, 75, 110, * 134.5, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, * 7200, 9600, 19200 */ divisor .word -1, 2304, 1536, 1047, 857, 768, 384, .word 192, 96, 64, 48, 32, 24, 16, 12, 6 /***************************************************** * init UART * * This routine is according to the Serial FAQ by * Chris Blum, Release 18. The FAQ is posted regularly * to comp.sys.ibm.pc.hardware.comm and * comp.os.msdos.programmer and can be obtained by * ftp at ftp://ftp.phil.uni-sb.de/pub/people/chris/ * The_Serial_Port * * (These routines get called with the device number * in the x register. As this driver supports one * UART only, x is always 0 - that may change) */ devini lda #0 sta status ; check if there's something like an UART at ; all ldy UART+MCR lda #$10 sta UART+MCR lda UART+MSR and #$f0 bne nodev lda #$1f sta UART+MCR lda UART+MSR and #$f0 cmp #$f0 bne nodev sty UART+MCR ; check if it has a scratchpad register ; if not then it's plain 8250 ldy UART+SCR lda #%10101010 sta UART+SCR cmp UART+SCR bne dev8250 lsr sta UART+SCR cmp UART+SCR bne dev8250 sty UART+SCR ; now check the 16xxx versions lda #1 sta UART+FCR lda UART+IIR ldy #0 sty UART+FCR asl bcc dev16450 asl bcc dev16550 ; else dev16550A; currently only this one ; is supported ; ok, we detected a 16550A, i.e. a chip ; with working FIFO lda #%10000000 sta UART+LCR ldx #14*2 ; 9600 BAUD lda divisor,x sta UART+DLL lda divisor+1,x sta UART+DLH lda #%00000011 ; 8N1 sta UART+LCR lda #7 ; no FIFO enable and ; clear FIFOs, sta UART+FCR ; trigger at 1 byte lda #0 sta UART+IER ; polled mode (so far) sta UART+MCR ; reset DTR, RTS clc rts nodev ; no UART at all dev8250 ; no Scratchpad, no FIFO dev16450 ; scratchpad, no FIFO dev16550 ; FIFO bug lda status ora #2 sta status lda #E_NOTIMP sec rts /***************************************************** * UART interrupt routine * * The interrupt routines are called one after each * other for every device in this OS. * If an interrupt source has been removed, then * E_OK is returned, E_NOIRQ otherwise. So this * routine even gets called, when lower level * interrupts occur. This is due to the single level * interrupt structure of the 6502 (not counting the * NMI, which is not really of use in this OS) * * The routine is nasty due to several reasons: The * UART doesn't periodically generate interrupts, * when the transmitter is empty, as the ACIA does. * So it is possible to be able to send (i.e. put * characters in software send FIFO) but the interrupt * driven driver doesn't know about it. Therefore, the * Serial FAQ suggests to start the transmission * manually when data is written to the drivers * internal buffers. In this OS the buffers (software * FIFOs) are totally independent FIFOs (Streams) * written to and read with PUTC and GETC. * To avoid lockups, I therefore check if I can start * sending every time the interrupt routine is called, * even if the interrupt source is not the UART * itself. * * The FAQ suggests checking if data is to be sent * after all UART interrupt sources have been handled. * But "Start transmission by simply calling the tx * interrupt handler after you've written to the * tx fifo [software-FIFO] of the program..." (These * are things you can do with DOS.... ts,ts,ts) * doesn't work here. Instead we check it in every * interrupt call. * */ devirq .( lda UART+IIR ; UART IIR lsr bcc intr ; ok, found IRQ irqend lda #E_NOIRQ ; no irq source found jmp irqe ; not this one ;--------------------------------------------- intr and #%00000011 ; interrupt mask - makes four tay ; possible IRQs bne int_tx ;-------------- ; modem status interrupt lda UART+MSR ; de-locked by reading the MSR jmp checkint ; do it, even if this IRQ is ; not enabled... int_tx ;-------------- dey bne int_rx tx_loop ; transmitter empty interrupt jsr tx2 ; write data bytes to UART ; FIFO jmp checkint int_rx ;-------------- dey bne int_status rx_loop ; receiver interrupt jsr rx2 ; get one char and save lda UART+LSR lsr bcs rx_loop ; still data in UART FIFO? jmp checkint int_status ;-------------- lda UART+LSR ; line status interrupt, ; de-locked by reading MSR ;-------------- checkint ; still another IRQ pending? lda UART+IIR lsr bcc intr ; irq still pending jsr nobyt ; check if we are still ; allowed to rx (i.e. stream ; has not been closed from ; reader) ;-------------- irqok lda #E_OK ; irq source removed irqe pha ; we get here no matter if ; UART IRQ or not lda UART+LSR ; check if we are allowed to ; start sending manually and #$40 ; (THRE = LSR bit 6) beq nbyt ; no then end jsr tx2 ; otherwise fill FIFO jsr nobyt ; check if we are still ; allowed to receive (this ; ensures to check even if no ; traffic) nbyt pla rts ;--------------------------------------------- ; Receive a single byte rx2 .( lda UART+RXTX ; load data byte bit status ; are we receiving? bpl rx2end ; no, then end jsr PUTC ; and save in software FIFO bcc rx2end ; no error -> end cmp #E_SLWM ; stream below low water mark? bne test ; this happens most and is ; caught by nobyt anyway rx2end rts ; check stream (software-FIFO) status &nobyt bit status ; are we receiving? bpl rx2end ; no -> end jsr STRCMD ; get state of stream bcc rx2end ; ok -> end test cmp #E_NUL ; stream has been closed from ; reader? bne tstwater jmp rxoff ; yes, then shut receiver off tstwater cmp #E_SEMPTY ; stream is empty? beq wl ; ok, ensure that RTS is low tax lda status ; we want RTS hi? and #1 bne wh ; yes, then high txa cmp #E_SFULL ; stream full beq wh ; then RTS high cmp #E_SHWM ; stream above high water ; mark? bne twl ; no then branch wh ldx #0 jmp rtsoff twl cmp #E_SLWM ; stream below low water mark? bne rx2end ; no then end wl ldx #0 ; otherwise RTS low jmp rtson .) ;--------------------------------------------- ; Fill transmitter FIFO tx2 .( bit status ; are we transmitting? bvc txrt ; reading IIR should clear ; this line lda UART+MSR ; are CTS and DSR ok? and #%00110000 cmp #%00110000 ; cts or dsr inactive bne txrt ; yes then end ldy #15 ; number of byte txloop jsr GETC bcs test2 sta UART+RXTX ; send new data byte dey bne txloop ; fill up FIFO bcc txrt test2 cmp #E_EOF ; we got a stream End-Of-File? bne txrt jmp txoff ; yes, then shut transmitter ; off txrt rts .) .) /***************************************************** * support routines */ dtroff lda UART+MCR and #%11111110 sta UART+MCR lda #0 sta UART+IER rts dtron lda UART+MCR ora #%00000001 sta UART+MCR lda #3 sta UART+IER rts rtsoff lda UART+MCR and #%11111101 sta UART+MCR lda status,x ora #%00100000 sta status,x rts rtson lda UART+MCR ora #%00000010 sta UART+MCR lda status,x and #%11011111 sta status,x rts /***************************************************** * control handling * * This is rather OS specific, so maybe it's not * necessary here. One thing to mention is, that * rxoff and txoff just reset their bit in the status * byte. rxoff then sets RTS high (rtsoff). After * that, both check if transmitter or receiver are on. * If both are shut off, then DTR is set high * (dtroff). * * If the transmitter or receiver are enabled, both * set DTR low (dtron). The receiver also sets CTS * low (ctslo). */ rxon jsr rtson jsr dtron lda #DC_SW_RX bne o2a txon jsr dtron lda #DC_SW_TX o2a ora status sta status bne ok rxoff lda status and #DC_SW_RX beq devoff jsr rtsoff ; signal eof to software FIFO here lda status and #255-DC_SW_RX sta status jmp checkdtr txoff lda status and #DC_SW_TX beq devoff ; signal close to software FIFO here lda status and #255-DC_SW_TX sta status,x checkdtr and #DC_SW_TX+DC_SW_RX bne active jsr dtroff active jmp ok setspeed tya and #%00001111 asl tax beq ok lda UART+LCR ora #$80 sta UART+LCR lda divisor,x sta UART+DLL lda divisor+1,x sta UART+DLH lda UART+LCR and #$7f sta UART+LCR ok lda #E_OK .byt $2c devon lda #E_DON .byt $2c devoff lda #E_DOFF .byt $2c onotimp lda #E_NOTIMP cmp #1 rts