petwifi/driver.a65
2022-09-04 10:02:27 +02:00

558 lines
11 KiB
Text

; 6502 mode
.p02
; load useful register/memory locations
.include "pet.inc"
; for some reason, the PIA registers are missing in pet.inc...
PIA1 := $E810
PIA1_PA := PIA1+$0 ; PORT A or DDR A: Data Direction Register A
PIA1_CRA := PIA1+$1 ; CRA: Control Register A
PIA1_PB := PIA1+$2 ; PORT B or DDR B: Data Direction Register B
PIA1_CRB := PIA1+$3 ; CRB: Control Register B
PIA2 := $E820
PIA2_PA := PIA2+$0
PIA2_CRA := PIA2+$1
PIA2_PB := PIA2+$2
PIA2_CRB := PIA2+$3
; 1MHz phase2 clock
PHI2_CLOCK = 1000000
; don't set the baud rate too high, or other operations will be starved
BAUD_RATE = 600
; parameters and return values can reside in an unused area of the zero page
.zeropage
; available bytes to read or write
.export rs_available
rs_available: .byte 0
; single byte transfer from input / to output fifo
.export rs_data
rs_data: .byte 0
; status of operation
rs_status_ok = 0
rs_status_read_buffer_empty = 1
rs_status_write_buffer_full = 2
rs_status_device_not_initialized = 3
rs_status_device_already_initialized = 4
.export rs_status
rs_status: .byte 0
; TODO add a state variable that can be monitored by the BASIC WAIT command
; this is for the load address, so we can generate PRG files.
; works as long as the code segment comes right after these two bytes,
; i.e. as long as the LOADADDR segment resides at $load_address - 2
.segment "LOADADDR"
.export LOADADDR
LOADADDR:
.word *+2
; entry points
.code
; these are convenience entry points right at the beginning of the page,
; to reduce dependency on code size.
; relocatable code would be perfect, but that's a lot more work.
.export rs_install
rs_install:
jmp install
.export rs_uninstall
rs_uninstall:
jmp uninstall
.export rs_read
rs_read:
jmp read
.export rs_write
rs_write:
jmp write
; driver state data
.bss
; 1=driver initialized
initialized: .byte 0
; saved IRQ vector
oldvector: .word 0
; state of the CA1 input: bit2=0 low level, bit2=1 high level
ca1state: .byte 0
; output buffer
; this is 16bit, because we need to process start and stop bits as well
outbuf: .word 0
; current shifted bit (if 0, no data is being transferred)
outshift: .byte 0
; output queue (16 bytes)
; this is a push from the front - pop from the tail fifo
outqlen: .byte 0
outq: .res 16, 0
; input buffer
inbuf: .word 0
; current shifted bit (if 0, no data is being transferred)
inshift: .byte 0
; input queue (16 bytes)
; this is a push from the tail - pop from the front fifo
inqlen: .byte 0
inq: .res 16, 0
; main code follows
.code
; driver installation, must be called once to set up IRQs, etc.
install:
; FIXME this doesn't work if we're loading from ROM
lda #1
sta initialized
; check if we're already initialized
lda initialized
beq @hwinit
; signal error
lda #rs_status_device_already_initialized
sta rs_status
; and return
rts
@hwinit:
; disable interrupts, so we're not disturbed
sei
; save the previous handler
lda IRQVec
sta oldvector
lda IRQVec+1
sta oldvector+1
; assign our custom interrupt handler to the IRQ vector
lda #<irqhandler
sta IRQVec
lda #>irqhandler
sta IRQVec+1
; VIA timer 2 cannot be enabled and disabled on the fly, so we'll
; configure it here and arm it later
; one-shot mode
lda VIA_CR
and #%11011111
sta VIA_CR
; enable interrupt
; interrupt enable is done by setting bit7 and the desired interrupt
; bit in IER to 1 (no need to read-modify-write)
lda #%10100000
sta VIA_IER
; set up the PIA1 and VIA to process I/O
; TXD: VIA PB3, output
lda VIA_DDRB
ora #%00001000
sta VIA_DDRB
; RXD: PIA1 CA1, input, interrupt
lda PIA1_CRA
; we start with the negative transition (RS232 start bit: H->L)
and #%11111101
; IRQ enable
ora #%00000001
sta PIA1_CRA
; CTS: PIA1 CB2, output (optional)
; RTS: PIA1 PA4, input (optional)
; clear some of the state variables
; the buffers are controlled by other variables anyway and don't need clearing
lda #0
sta ca1state
sta outshift
sta outqlen
sta inshift
sta inqlen
; we're ready
lda #1
sta initialized
; and ok
lda #rs_status_ok
sta rs_status
; fire away
cli
; return
rts
; driver uninstallation, must be called once to set up IRQs, etc.
.export uninstall
uninstall:
; check if we're already initialized
lda initialized
bne @hwuninit
; signal error
lda #rs_status_device_not_initialized
sta rs_status
; and return
rts
@hwuninit:
; disable interrupts, so we're not disturbed
sei
; restore the previous handler
lda oldvector
ldx oldvector+1
sta IRQVec
stx IRQVec+1
; disable all interrupts
; RXD: PIA1 CA1
lda PIA1_CRA
; IRQ disable
and #%11111110
sta PIA1_CRA
; and the VIA timer 2
; clearing is done by setting bit 7 to 0 and the desired interrupt to 1
lda #%00100000
sta VIA_IER
; we leave the I/O pins and the state alone
; but we'll go back to uninitalized state
lda #0
sta initialized
; and ok
lda #rs_status_ok
sta rs_status
; the rest of the system will probably want interrupts enabled
cli
; return
rts
; write one byte to FIFO
.export write
write:
; check if we're already initialized
lda initialized
bne @dowrite
; signal error
lda #rs_status_device_not_initialized
sta rs_status
; and return
rts
@dowrite:
; disable interrupts while we write into the buffer
sei
; test if there's space in the buffer first
lda #16
cmp outqlen
beq @wrnodata
; move data up first
ldy outqlen
; check if we need to move data at all
bne @wrbufend
; move all existing elements up, starting from the top
@wrbufloop:
lda outq-1,y
sta outq,y
dey
bne @wrbufloop
@wrbufend:
; write data to head
lda rs_data
sta outq
; update queue length
ldy outqlen
iny
sty outqlen
; also store to return value
sty rs_available
; all right
lda #rs_status_ok
sta rs_status
; an unconditional branch would be useful...
clc
bcc @wrdone
@wrnodata:
; return error
lda #rs_status_write_buffer_full
sta rs_status
@wrdone:
; and re-enable
cli
; return
rts
; read one byte from FIFO
.export read
read:
; check if we're already initialized
lda initialized
bne @doread
; signal error
lda #rs_status_device_not_initialized
sta rs_status
; and return
rts
@doread:
; disable interrupts while we read the buffer
sei
; test if we have any data first
lda inqlen
beq @rdnodata
; return data from head
lda inq
sta rs_data
; update the queue length
ldy inqlen
dey
sty inqlen
; also store to return value
sty rs_available
; check if we need to move any data
bne @rdbufend
; move all other data elements down
ldx #0
@rdbufloop:
lda inq+1,x
sta inq,x
inx
dey
bne @rdbufloop
@rdbufend:
; all right
lda #rs_status_ok
sta rs_status
; an unconditional branch would be useful...
clc
bcc @rddone
@rdnodata:
; return error
lda #rs_status_read_buffer_empty
sta rs_status
@rddone:
; and re-enable
cli
; return
rts
; start the timer
starttimer:
; arm VIA timer 2 by latching the counter
period = PHI2_CLOCK/BAUD_RATE
lda #<period
sta VIA_T1CL
lda #>period
sta VIA_T1CH
; return
rts
; stop the timer
@stoptimer:
; VIA timer 1 disabled
lda VIA_CR
and #%10111111
sta VIA_CR
; return
rts
; IRQ handler
.interruptor irqhandler
irqhandler:
; disable interrupts
sei
; clear decimal flag to avoid unexpected behavior
cld
; save registers
pha
txa
pha
tya
pha
; test for the interrupts we're expecting
@handleflip:
; read PIA1 CRA, interrupt flag is automatically cleared
lda PIA1_CRA
; save CRA for later, so we don't need to fetch it again
tax
; bit 7 is the CA1 interrupt flag - fired?
and #%10000000
; nope, skip flip handling
beq @handletimer
; handle CA1 interrupt
; fetch saved CRA
txa
; save current value for later use:
; bit 2 is contains the active value of CA1, which is what we want to know
sta ca1state
; invert transition (H->L <-> L->H)
eor #%00000010
; mask out interrupt flag - we don't want another IRQ right away
and #%01111111
sta PIA1_CRA
; check if we're already in the middle of a reception
lda inshift
; yes, don't do anything
bne @handletimer
; nope, initiate reception
lda #10
sta inshift
@handletimer:
; VIA timer 2 fired?
lda VIA_IFR
and #%00100000
; zero, no interrupt
beq @irqreturn
; timer 2 fired: rearm (we're in one-shot mode!), and clear the interrupt flag
; FIXME this should be done earlier, for more accurate timing
; FIXME we could skip writing the low byte here, writing the high byte is enough
jsr starttimer
@loadout:
; do we have more bits to send?
lda outshift
; yes, skip loading next byte
bne @shiftout
; load next byte from FIFO
; do we have more bytes?
lda outqlen
; nope, skip ahead to input processing
beq @shiftin
; load length
ldx outqlen
; decrement, also gives the index
dex
; load next byte from fifo
lda outq,x
; store new queue length
stx outqlen
; prepare shift register
; layout: 000000E7 6543210S
; bit0 = start bit = 0
; bit1..7 = data0..6
clc
rol
sta outbuf
; bit8 = data7
; bit9 = stop bit = 1
lda #%00000001
rol
sta outbuf+1
; we're shifting 10 bits out
lda #10
sta outshift
@shiftout:
; make sure overflow flag is clear, so we can do unconditional branches
clv
; load shift register bit 0
lda outbuf
and #%00000001
; mark or blank?
beq @shiftoutblank
; set output high
lda #%00001000
ora VIA_PB
bvc @shiftoutstore
@shiftoutblank:
; set output low
lda #%11110111
and VIA_PB
@shiftoutstore:
sta VIA_PB
; shift the next bit in
; 0 -> high byte -> carry
lsr outbuf+1
; carry -> low byte
ror outbuf
; decrement counter
dec outshift
@shiftin:
; FIXME we should check the start and stop bits, to synchronize RS232 transmissions
; check first if a reception is in progress
lda inshift
; nope, skip input processing
beq @irqreturn
; for unconditional branch
clv
; pick up the current CA1 state
lda ca1state
and #%00000100
; mark or blank?
beq @shiftinblank
; shift in high bit
sec
bvc @shiftinstore
@shiftinblank:
; shift in low bit
clc
@shiftinstore:
; rotate carry bit into buffer
rol inbuf
rol inbuf+1
; decrement counter
dec inshift
@storein:
; did we complete a transmission?
bne @irqreturn
; check if we have space available in the buffer
lda inqlen
cmp #16
; nope, drop this byte
bcc @irqreturn
; yes, store it
tax
lda inbuf+1
lsr
lda inbuf
ror
sta inq, x
; and update the buffer length
dex
stx inqlen
@irqreturn:
; restore registers
pla
tay
pla
tax
pla
; we don't need to re-enable interrupts here,
; this will happen automatically when flags are restored later
;cli
; jump to previous handler
; we don't need to return or restore flags, this will be done by the chained interrupt handler
jmp (oldvector)