cc65/libsrc/lynx/ser/lynx-comlynx.s

393 lines
10 KiB
ArmAsm

;
; 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 and stop timer 4 (serial)
lda #$0C ; TXOPEN|RESETERR
sta SERCTL
lda #$00 ; Disable count and no reload
sta TIM4CTLA
; 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
ldy #SER_PARAMS::BAUDRATE
lda (ptr1),y
; Source period is 1 us
ldy #%00011000 ; ENABLE_RELOAD|ENABLE_COUNT|AUD_1
ldx #1
cmp #SER_BAUD_62500
beq setbaudrate
ldx #3
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 #68
cmp #SER_BAUD_1800
beq setbaudrate
ldx #103
cmp #SER_BAUD_1200
beq setbaudrate
ldx #207
cmp #SER_BAUD_600
beq setbaudrate
; Source period is 8 us
ldy #%00011011 ; ENABLE_RELOAD|ENABLE_COUNT|AUD_8
ldx #51
cmp #SER_BAUD_300
beq setbaudrate
lda #SER_ERR_BAUD_UNAVAIL
ldx #0 ; return value is char
rts
setbaudrate:
sty TIM4CTLA
stx TIM4BKUP
ldx #TXOPEN|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 #TXOPEN
stx contrl
bra checkhs
@L0:
ldx #PAREN|TXOPEN|PAREVEN
stx contrl
cmp #SER_PAR_EVEN
beq checkhs
ldx #PAREN|TXOPEN
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 #RXINTEN|RESETERR ; Turn on interrupts for receive
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 ; Check bit 7 of TxDone (TXINTEN)
bmi @L1 ; Was TXINTEN already set?
php
sei
lda contrl ; contrl does not include RXINTEN setting
ora #TXINTEN|RESETERR
sta SERCTL ; Allow TX-IRQ to hang RX-IRQ (no receive while transmitting)
sta TxDone
plp ; Restore processor and interrupt enable
@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:
lda 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 ; Read received data
lda contrl
and #PAREN ; Parity enabled implies SER_PAR_EVEN or SER_PAR_ODD
tay
ora #OVERRUN|FRAMERR|RXBRK
bit SERCTL ; Check presence of relevant error flags in SERCTL
beq @rx_irq ; No errors so far
tsb SerialStat ; Save error condition
bit #RXBRK ; Check for break signal
beq @noBreak
stz TxPtrIn ; Break received - drop buffers
stz TxPtrOut
stz RxPtrIn
stz RxPtrOut
@noBreak:
bra @exit0
@rx_irq:
tya
bne @2 ; Parity was enabled so no marker bit check needed
lda contrl
eor SERCTL ; Should match current parity bit
and #PARBIT ; Check for mark or space value
bne @exit0
@2:
txa
ldx RxPtrIn
sta RxBuffer,x
txa
inx
cpx RxPtrOut
beq @1
stx RxPtrIn
bra @IRQexit
@1:
sta RxPtrIn
lda #$80
tsb SerialStat
bra @exit0
@tx_irq:
ldx TxPtrOut ; Have all bytes been sent?
cpx TxPtrIn
beq @allSent
lda TxBuffer,x ; Send next byte
sta SERDAT
inc TxPtrOut
@exit1:
lda contrl
ora #TXINTEN|RESETERR
sta SERCTL
bra @IRQexit
@allSent:
lda SERCTL ; All bytes sent
bit #TXEMPTY
beq @exit1
bvs @exit1
stz TxDone
@exit0:
lda contrl
ora #RXINTEN|RESETERR ; Re-enable receive interrupt
sta SERCTL
@IRQexit:
lda #SERIAL_INTERRUPT
sta INTRST
clc
rts