1
0
mirror of https://github.com/cc65/cc65.git synced 2024-11-02 18:06:48 +00:00
cc65/libsrc/lynx/ser/lynx-comlynx.s
Oliver Schmidt 2c975d3642 Create static drivers directly from source files.
Up to now static drivers were created via co65 from dynamic drivers. However there was an issue with that approach:

The dynamic drivers are "o65 simple files" which obligates that they start with the 'code' segment. However dynamic drivers need to start with the module header - which is written to. For dynamic drivers this isn't more than a conceptual issue because they are always contain a 'data' segment and may therefore only be loaded into writable memory.

However when dynamic drivers are converted to static drivers using co65 then that issue becomes a real problem as then the 'code' segment may end up in non-writable memory - and thus writing to the module header fails.

Instead of changing the way dynamic drivers work I opted to rather make static driver creation totally independent from dynamic drivers. This allows to place the module header in the 'data' segment (see 'module.mac').
2014-06-04 23:50:18 +02:00

415 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 INSTALL
.addr UNINSTALL
.addr OPEN
.addr CLOSE
.addr GET
.addr PUT
.addr STATUS
.addr IOCTL
.addr 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
;----------------------------------------------------------------------------
; INSTALL: Is called after the driver is loaded into memory.
;
; Must return an SER_ERR_xx code in a/x.
INSTALL:
; Set up IRQ vector ?
;----------------------------------------------------------------------------
; UNINSTALL: Is called before the driver is removed from memory.
; No return code required (the driver is removed from memory on return).
;
UNINSTALL:
;----------------------------------------------------------------------------
; CLOSE: Close the port and disable interrupts. Called without parameters.
; Must return an SER_ERR_xx code in a/x.
CLOSE:
; Disable interrupts
; Done, return an error code
lda #<SER_ERR_OK
ldx #>SER_ERR_OK
rts
;----------------------------------------------------------------------------
; 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.
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 #>SER_ERR_BAUD_UNAVAIL
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
ldx #>SER_ERR_OK
rts
invparameter:
lda #<SER_ERR_INIT_FAILED
ldx #>SER_ERR_INIT_FAILED
rts
;----------------------------------------------------------------------------
; 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.
GET:
lda RxPtrIn
cmp RxPtrOut
bne GetByte
lda #<SER_ERR_NO_DATA
ldx #>SER_ERR_NO_DATA
rts
GetByte:
ldy RxPtrOut
lda RxBuffer,y
inc RxPtrOut
ldx #$00
sta (ptr1,x)
txa ; Return code = 0
rts
;----------------------------------------------------------------------------
; PUT: Output character in A.
; Must return an SER_ERR_xx code in a/x.
PUT:
tax
lda TxPtrIn
ina
cmp TxPtrOut
bne PutByte
lda #<SER_ERR_OVERFLOW
ldx #>SER_ERR_OVERFLOW
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
tax
rts
;----------------------------------------------------------------------------
; STATUS: Return the status in the variable pointed to by ptr1.
; Must return an SER_ERR_xx code in a/x.
STATUS:
ldy SerialStat
ldx #$00
sta (ptr1,x)
txa ; Return code = 0
rts
;----------------------------------------------------------------------------
; 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.
IOCTL:
lda #<SER_ERR_INV_IOCTL
ldx #>SER_ERR_INV_IOCTL
rts
;----------------------------------------------------------------------------
; 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.
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