Skip to content

Commit

Permalink
Add core serial IO implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
quinnyo committed May 9, 2024
1 parent 34b165c commit 0352ed2
Showing 1 changed file with 218 additions and 0 deletions.
218 changes: 218 additions & 0 deletions unbricked/serial-link/sio.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
INCLUDE "hardware.inc"

; ::::::::::::::::::::::::::::::::::::::
; :: ::
; :: ______. ::
; :: _ |````` || ::
; :: _/ \__@_ |[- - ]|| ::
; :: / `--<[|]= |[ m ]|| ::
; :: \ .______ | ```` || ::
; :: / !| `````| | + oo|| ::
; :: ( ||[ ^u^]| | .. #|| ::
; :: `-<[|]=|[ ]| `______// ::
; :: || ```` | ::
; :: || + oo| ::
; :: || .. #| ::
; :: !|______/ ::
; :: ::
; :: ::
; ::::::::::::::::::::::::::::::::::::::
;
; This concludes the physical portion of the tutorial.
; (I'm sure we can all agree that this was worth whatever it cost.)


; Duration of timeout period in ticks. (for externally clocked device)
DEF SIO_TIMEOUT_TICKS EQU 240
; Duration of 'catchup' delay period in ticks. (for internally clocked device)
DEF SIO_CATCHUP_TICKS EQU 1

DEF SIO_CONFIG_INTCLK EQU SCF_SOURCE
DEF SIO_CONFIG_DEFAULT EQU 0
EXPORT SIO_CONFIG_INTCLK

; SioStatus transfer state enum
RSRESET
DEF SIO_IDLE RB 1
DEF SIO_XFER_START RB 1
DEF SIO_XFER_STARTED RB 1
DEF SIO_XFER_COMPLETED RB 1
DEF SIO_XFER_FAILED RB 1
EXPORT SIO_IDLE, SIO_XFER_START, SIO_XFER_STARTED, SIO_XFER_COMPLETED, SIO_XFER_FAILED


SECTION "SioCore State", WRAM0
; Sio config flags
wSioConfig:: db
; Sio state machine current state
wSioState:: db
; Source address of next value to transmit
wSioTxPtr:: dw
; Destination address of next received value
wSioRxPtr:: dw
; Number of transfers to perform (bytes to transfer)
wSioCount:: db

; Timer state (as ticks remaining, expires at zero) for timeouts and delays.
wTimer: db


SECTION "SioCore Impl", ROM0
; Initialise/reset Sio to the ready to use 'IDLE' state.
; NOTE: Enables the serial interrupt.
; @mut: AF, [IE]
SioInit::
ld a, SIO_CONFIG_DEFAULT
ld [wSioConfig], a
ld a, SIO_IDLE
ld [wSioState], a
ld a, 0
ld [wTimer], a
ld a, $FF ; using FFFF as 'null'/unset
ld [wSioTxPtr + 0], a
ld [wSioTxPtr + 1], a
ld [wSioRxPtr + 0], a
ld [wSioRxPtr + 1], a
ld a, 0
ld [wSioCount], a

; enable serial interrupt
ldh a, [rIE]
or a, IEF_SERIAL
ldh [rIE], a
ret


; @mut: AF, HL
SioTick::
ld a, [wSioState]
cp a, SIO_XFER_START
jr z, process_queue
cp a, SIO_XFER_STARTED
jr z, .xfer_started
cp a, SIO_XFER_COMPLETED
jr z, process_queue
; treat anything else as failed/error and do nothing
ret
.xfer_started:
; update timeout on external clock
ldh a, [rSC]
and a, SCF_SOURCE
ret nz
ld a, [wTimer]
and a, a
ret z ; timer == 0, timeout disabled
dec a
ld [wTimer], a
jr z, SioAbortTransfer
ret
.process_queue:
; if SioCount > 0: start next transfer
ld a, [wSioCount]
and a, a
ret z
; if this device is the clock source (internal clock), do catchup delay
ldh a, [rSC]
bit SCB_SOURCE, a
jr z, .start_next
ld a, [wTimer]
and a, a
jr z, .start_next
dec a
ld [wTimer], a
ret
.start_next:
; read the Tx pointer (points to the next value to send)
ld hl, wSioTxPtr
ld a, [hl+]
ld h, [hl]
ld l, a
; set the clock source (do this first & separately from starting the transfer!)
ld a, [wSioConfig]
and a, SCF_SOURCE ; the sio config byte uses the same bit for the clock source
ldh [rSC], a
; load the value to send
ld a, [hl]
ldh [rSB], a
; start the transfer
ldh a, [rSC]
or a, SCF_START
ldh [rSC], a

; reset timeout (on externally clocked device)
bit SCB_SOURCE, a
jr nz, :+
ld a, SIO_TIMEOUT_TICKS
ld [wTimer], a
:
ld a, SIO_XFER_STARTED
ld [wSioState], a
ret


; Abort the ongoing transfer (if any) and enter the FAILED state.
; @mut: AF
SioAbortTransfer::
ld a, SIO_XFER_FAILED
ld [wSioState], a
ldh a, [rSC]
res SCB_START, a
ldh [rSC], a
ret


SioSerialInterruptHandler:
push af
push hl

; check that we were expecting a transfer
ld a, [wSioState]
cp a, SIO_XFER_STARTED
ret nz

; store the received value
ld hl, wSioRxPtr
ld a, [hl+]
ld h, [hl]
ld l, a
ldh a, [rSB]
ld [hl+], a
; store the updated Rx pointer
ld a, l
ld [wSioRxPtr + 0], a
ld a, h
ld [wSioRxPtr + 1], a

; update the Tx pointer
ld hl, wSioTxPtr
inc [hl] ; inc low byte
jr nz, :+
; inc high byte if overflow
inc hl
inc [hl]
:

; update transfer count
ld hl, wSioCount
dec [hl]

; set transfer state to 'completed'
ld a, SIO_XFER_COMPLETED
ld [wSioState], a

ldh a, [rSC]
and a, SCF_SOURCE
jr z, :+
; reset delay timer on internal clock
ld a, SIO_CATCHUP_TICKS
ld [wTimer], a
:

pop hl
pop af
reti


SECTION "Serial Interrupt", ROM0[$58]
SerialInterrupt:
jp SioSerialInterruptHandler

0 comments on commit 0352ed2

Please sign in to comment.