; ; Serial driver for the Atari Lynx ComLynx port. ; ; Karri Kaksonen, 17.09.2009 ; .include "lynx.inc" .include "zeropage.inc" .include "ser-kernel.inc" .include "ser-error.inc" .macpack module ; ------------------------------------------------------------------------ ; Header. Includes jump table module_header _lynx_comlynx_ser ; Driver signature .byte $73, $65, $72 ; "ser" .byte SER_API_VERSION ; Serial API version number ; Library reference .addr $0000 ; Jump table .addr SER_INSTALL .addr SER_UNINSTALL .addr SER_OPEN .addr SER_CLOSE .addr SER_GET .addr SER_PUT .addr SER_STATUS .addr SER_IOCTL .addr SER_IRQ ;---------------------------------------------------------------------------- ; Global variables ; .bss TxBuffer: .res 256 RxBuffer: .res 256 RxPtrIn: .res 1 RxPtrOut: .res 1 TxPtrIn: .res 1 TxPtrOut: .res 1 contrl: .res 1 SerialStat: .res 1 TxDone: .res 1 .code ;---------------------------------------------------------------------------- ; SER_INSTALL: Is called after the driver is loaded into memory. ; ; Must return an SER_ERR_xx code in a/x. SER_INSTALL: ; Set up IRQ vector ? ;---------------------------------------------------------------------------- ; SER_UNINSTALL: Is called before the driver is removed from memory. ; No return code required (the driver is removed from memory on return). ; SER_UNINSTALL: ;---------------------------------------------------------------------------- ; SER_CLOSE: Close the port and disable interrupts. Called without parameters. ; Must return an SER_ERR_xx code in a/x. SER_CLOSE: ; Disable interrupts ; Done, return an error code lda #SER_ERR_OK .assert SER_ERR_OK = 0, error tax rts ;---------------------------------------------------------------------------- ; SER_OPEN: A pointer to a ser_params structure is passed in ptr1. ; ; The Lynx has only two correct serial data formats: ; 8 bits, parity mark, 1 stop bit ; 8 bits, parity space, 1 stop bit ; ; It also has two wrong formats; ; 8 bits, even parity, 1 stop bit ; 8 bits, odd parity, 1 stop bit ; ; Unfortunately the parity bit includes itself in the calculation making ; parity not compatible with the rest of the world. ; ; We can only specify a few baud rates. ; Lynx has two non-standard speeds 31250 and 62500 which are ; frequently used in games. ; ; The receiver will always read the parity and report parity errors. ; ; Must return an SER_ERR_xx code in a/x. SER_OPEN: stz RxPtrIn stz RxPtrOut stz TxPtrIn stz TxPtrOut ; clock = 8 * 15625 lda #%00011000 sta TIM4CTLA ldy #SER_PARAMS::BAUDRATE lda (ptr1),y ldx #1 cmp #SER_BAUD_62500 beq setbaudrate ldx #2 cmp #SER_BAUD_31250 beq setbaudrate ldx #12 cmp #SER_BAUD_9600 beq setbaudrate ldx #25 cmp #SER_BAUD_4800 beq setbaudrate ldx #51 cmp #SER_BAUD_2400 beq setbaudrate ldx #103 cmp #SER_BAUD_1200 beq setbaudrate ldx #207 cmp #SER_BAUD_600 beq setbaudrate ; clock = 6 * 15625 ldx #%00011010 stx TIM4CTLA ldx #12 cmp #SER_BAUD_7200 beq setbaudrate ldx #25 cmp #SER_BAUD_3600 beq setbaudrate ldx #207 stx TIM4BKUP ; clock = 4 * 15625 ldx #%00011100 cmp #SER_BAUD_300 beq setprescaler ; clock = 6 * 15625 ldx #%00011110 cmp #SER_BAUD_150 beq setprescaler ; clock = 1 * 15625 ldx #%00011111 stx TIM4CTLA cmp #SER_BAUD_75 beq baudsuccess ldx #141 cmp #SER_BAUD_110 beq setbaudrate ; clock = 2 * 15625 ldx #%00011010 stx TIM4CTLA ldx #68 cmp #SER_BAUD_1800 beq setbaudrate ; clock = 6 * 15625 ldx #%00011110 stx TIM4CTLA ldx #231 cmp #SER_BAUD_134_5 beq setbaudrate lda #SER_ERR_BAUD_UNAVAIL ldx #0 ; return value is char rts setprescaler: stx TIM4CTLA bra baudsuccess setbaudrate: stx TIM4BKUP baudsuccess: ldx #TxOpenColl|ParEven stx contrl ldy #SER_PARAMS::DATABITS ; Databits lda (ptr1),y cmp #SER_BITS_8 bne invparameter ldy #SER_PARAMS::STOPBITS ; Stopbits lda (ptr1),y cmp #SER_STOP_1 bne invparameter ldy #SER_PARAMS::PARITY ; Parity lda (ptr1),y cmp #SER_PAR_NONE beq invparameter cmp #SER_PAR_MARK beq checkhs cmp #SER_PAR_SPACE bne @L0 ldx #TxOpenColl stx contrl bra checkhs @L0: ldx #TxParEnable|TxOpenColl|ParEven stx contrl cmp #SER_PAR_EVEN beq checkhs ldx #TxParEnable|TxOpenColl stx contrl checkhs: ldx contrl stx SERCTL ldy #SER_PARAMS::HANDSHAKE ; Handshake lda (ptr1),y cmp #SER_HS_NONE bne invparameter lda SERDAT lda contrl ora #RxIntEnable|ResetErr sta SERCTL lda #SER_ERR_OK .assert SER_ERR_OK = 0, error tax rts invparameter: lda #SER_ERR_INIT_FAILED ldx #0 ; return value is char rts ;---------------------------------------------------------------------------- ; SER_GET: Will fetch a character from the receive buffer and store it into the ; variable pointed to by ptr1. If no data is available, SER_ERR_NO_DATA is ; returned. SER_GET: lda RxPtrIn cmp RxPtrOut bne GetByte lda #SER_ERR_NO_DATA ldx #0 ; return value is char rts GetByte: ldy RxPtrOut lda RxBuffer,y inc RxPtrOut ldx #$00 sta (ptr1,x) txa ; Return code = 0 rts ;---------------------------------------------------------------------------- ; SER_PUT: Output character in A. ; Must return an SER_ERR_xx code in a/x. SER_PUT: tax lda TxPtrIn ina cmp TxPtrOut bne PutByte lda #SER_ERR_OVERFLOW ldx #0 ; return value is char rts PutByte: ldy TxPtrIn txa sta TxBuffer,y inc TxPtrIn bit TxDone bmi @L1 php sei lda contrl ora #TxIntEnable|ResetErr sta SERCTL ; Allow TX-IRQ to hang RX-IRQ sta TxDone plp @L1: lda #SER_ERR_OK .assert SER_ERR_OK = 0, error tax rts ;---------------------------------------------------------------------------- ; SER_STATUS: Return the status in the variable pointed to by ptr1. ; Must return an SER_ERR_xx code in a/x. SER_STATUS: ldy SerialStat ldx #$00 sta (ptr1,x) txa ; Return code = 0 rts ;---------------------------------------------------------------------------- ; SER_IOCTL: Driver defined entry point. The wrapper will pass a pointer to ioctl ; specific data in ptr1, and the ioctl code in A. ; Must return an SER_ERR_xx code in a/x. SER_IOCTL: lda #SER_ERR_INV_IOCTL ldx #0 ; return value is char rts ;---------------------------------------------------------------------------- ; SER_IRQ: Called from the builtin runtime IRQ handler as a subroutine. All ; registers are already saved, no parameters are passed, but the carry flag ; is clear on entry. The routine must return with carry set if the interrupt ; was handled, otherwise with carry clear. ; ; Both the Tx and Rx interrupts are level sensitive instead of edge sensitive. ; Due to this bug you have to disable the interrupt before clearing it. SER_IRQ: lda INTSET ; Poll all pending interrupts and #SERIAL_INTERRUPT bne @L0 clc rts @L0: bit TxDone bmi @tx_irq ; Transmit in progress ldx SERDAT lda SERCTL and #RxParityErr|RxOverrun|RxFrameErr|RxBreak beq @rx_irq tsb SerialStat ; Save error condition bit #RxBreak beq @noBreak stz TxPtrIn ; Break received - drop buffers stz TxPtrOut stz RxPtrIn stz RxPtrOut @noBreak: lda contrl ora #RxIntEnable|ResetErr sta SERCTL lda #$10 sta INTRST bra @IRQexit @rx_irq: lda contrl ora #RxIntEnable|ResetErr sta SERCTL txa ldx RxPtrIn sta RxBuffer,x txa inx @cont0: cpx RxPtrOut beq @1 stx RxPtrIn lda #SERIAL_INTERRUPT sta INTRST bra @IRQexit @1: sta RxPtrIn lda #$80 tsb SerialStat @tx_irq: ldx TxPtrOut ; Has all bytes been sent? cpx TxPtrIn beq @allSent lda TxBuffer,x ; Send next byte sta SERDAT inc TxPtrOut @exit1: lda contrl ora #TxIntEnable|ResetErr sta SERCTL lda #SERIAL_INTERRUPT sta INTRST bra @IRQexit @allSent: lda SERCTL ; All bytes sent bit #TxEmpty beq @exit1 bvs @exit1 stz TxDone lda contrl ora #RxIntEnable|ResetErr sta SERCTL lda #SERIAL_INTERRUPT sta INTRST @IRQexit: clc rts