<sect1>RS232 device drivers<p>
-No serial drivers are currently available for the Lynx.
+<descrip>
+ The ComLynx port has Tx and Rx wired together. Every byte is sent
+ to all connected Lynxes. Only one Lynx can send at a time. There is no
+ protocol created for communication. You are on your own.
+
+ If the Lynx returns framing error then it is likely that another Lynx is
+ sending data at the same time.
+
+ The Lynx can also send a break and receive a break. The Lynx break is
+ recognized if the bit is down for 24 bit cycles or more.
+
+ To send a break you just set the break bit. The length of the break depends
+ on how long this bit is down.
+
+ The driver supports the baudrates:
+ <itemize>
+ <item>62500
+ <item>31250
+ <item>9600
+ <item>7200
+ <item>4800
+ <item>3600
+ <item>2400
+ <item>1800
+ <item>1200
+ <item>600
+ <item>300
+ <item>150
+ <item>134.5
+ <item>110
+ <item>75
+ </itemize>
+ The parity bit supports MARK and SPACE. It also supports EVEN and ODD parity
+ but the parity bit is included in the calculation. Most of us don't want it
+ this way. But there is nothing we can do about it. Just don't use EVEN or ODD
+ when communicating to other equipment than the Lynx.
+
+ There is always only one stop bit. And the data length is always 8 bits.
+
+ We have no handshaking available. Even software handshake is impossible
+ as ComLynx has only one wire for the data.
+
+ Both transmit and receive are interrupt driven. The driver reserves a fixed
+ area $200-$2ff for the transmit ring buffer and $300-$3ff for the receive
+ ring buffer. This area can not be used at startup for anything as the Lynx
+ ROM needs this area for decryption purposes.
+</descrip><p>
<sect>Limitations<p>
--- /dev/null
+;
+; 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"
+
+; ------------------------------------------------------------------------
+; Header. Includes jump table
+
+ .segment "JUMPTABLE"
+
+ ; Driver signature
+ .byte $73, $65, $72 ; "ser"
+ .byte SER_API_VERSION ; Serial API version number
+
+ ; Jump table.
+ .addr INSTALL
+ .addr UNINSTALL
+ .addr OPEN
+ .addr CLOSE
+ .addr GET
+ .addr PUT
+ .addr STATUS
+ .addr IOCTL
+ .addr IRQ
+
+;----------------------------------------------------------------------------
+; Global variables
+;
+; The ring buffers will be at the fixed place
+; Tx buffer $200 - $2ff. Rx buffer $300 - $3ff.
+; This memory area can usually not be used for anything as the encryption
+; stuff needs it. But for this purpose it fits perfectly.
+
+ .bss
+
+TxBuffer = $0200
+RxBuffer = $0300
+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
+