
INCLUDE "config_yaz180_private.inc"

SECTION rodata_common1_driver

PHASE   __COMMON_AREA_1_PHASE_DRIVER

;------------------------------------------------------------------------------
; start of common area 1 - RST functions
;------------------------------------------------------------------------------

EXTERN _shadowLock, _bankLockBase
EXTERN _bios_sp, _bank_sp

EXTERN invalidOpCode, newLine

;------------------------------------------------------------------------------

PUBLIC asm_z180_trap
PUBLIC _z180_trap_rst

.asm_z180_trap
    ld a,__IO_BASE_ADDRESS
    out0 (0x3f),a       ; set __IO_BASE_ADDRESS in ICR
    in0 a,(ITC)         ; get the trap & interrupt register
    xor a,$80           ; test and clear trap bit
    ret M               ; if no trap, then return

._z180_trap_rst         ; RST  0 - also handle an opcode trap
    pop hl              ; pop this function return
    ex (sp),hl          ; pop invalid opcode PC, stack return
    dec hl              ; back to first byte of opcode
    ld b,2              ; two bytes
    bit 6,a             ; check UFO bit for 3 byte opcode
    jr Z,trap_2         ; only two bytes
    dec hl              ; now HL points to first of three bytes
    inc b
.trap_2
    out0 (ITC),a        ; write cleared trap bit
    push hl             ; save opcode PC
    ld de,invalidOpCode
    call asm_pstring    ; asm_pstring modifies AF, DE, & HL
    pop hl              ; recover PC of invalid opcode
    ld d,h              ; copy PC
    ld e,l
    call asm_phexwd     ; print the address
    ld l,' '
    call asm_pchar      ; print space
.trap_loop
    ex de,hl            ; recover PC
    ld e,(hl)           ; get opcode
    inc hl              ; next PC
    ex de,hl            ; opcode to l
    call asm_phex       ; print
    ld l,' '
    call asm_pchar      ; print space
    djnz trap_loop      ; print all the opcode
    ld de,newLine
    call asm_pstring    ; print new line
    jp asm_delay

;------------------------------------------------------------------------------

PUBLIC _error_handler_rst

._error_handler_rst     ; RST  8
    pop hl              ; get the originating PC address
    push hl             ; put it back for the return
    ld e,(hl)           ; get error code in E
    dec hl
    call asm_phexwd     ; output originating RST address on serial port
    ld l,' '
    call asm_pchar      ; print space
    ex de,hl            ; get error code in L
    call asm_phex       ; output error code on serial port
    ld de,newLine
    call asm_pstring    ; print new line
    jp asm_delay

;------------------------------------------------------------------------------

PUBLIC _call_far_rst

._call_far_rst          ; RST 10
    ex af,af            ; all your register are belong to us
    exx

    ld hl,_shadowLock
.call_far_try_alt_lock
    sra (hl)            ; get alt register mutex
    jr C,call_far_try_alt_lock ; just keep trying

    pop hl              ; get the return PC address
    ld e,(hl)           ; get called address in DE
    inc hl
    ld d,(hl)
    inc hl
    ld c,(hl)           ; get called bank in C
    inc hl
    push hl             ; push the post call_far ret address back on stack

    in0 a,(BBR)         ; get the origin bank
    ld b,a              ; save origin BBR in B
    rrca                ; move the origin bank to low nibble
    rrca                ; we know BBR lower nibble is 0
    rrca
    rrca
    add a,c             ; create destination far address, from twos complement relative input
    and a,$0F           ; convert it to 4 bit address (not implicit here)

    ld h,_bankLockBase/$100 ; get the BANK Lock base address, page aligned
    ld l,a              ; make the reference into destination BANKnn Lock

    rlca                ; move the origin bank to high nibble
    rlca                ; we know BBR lower nibble is 0
    rlca
    rlca
    ld c,a              ; hold destination BBR in C

    xor a
    or (hl)             ; check bank is not cold
    jr Z,call_far_exit
    sra (hl)            ; now get the bank lock,
    jr C,call_far_exit  ; or exit if the bank is hot

                        ; OK we're going somewhere nice and warm,
                        ; now make the bank switch
    di
    ld (_bank_sp),sp    ; save the origin bank SP in Page0
    out0 (BBR),c        ; make the bank swap
    ei
    ld sp,(_bank_sp)    ; set up the new SP in new Page0

                        ; now prepare for our return
    push bc             ; push on the origin and destination bank

    ld hl,ret_far
    push hl             ; push ret_far function return address
    push de             ; push our destination address

.call_far_exit
    ld hl,_shadowLock   ; give alt register mutex
    ld (hl),$FE

    exx
    ex af,af            ; alt register are returned
    ret                 ; takes us out if there's error, or call onwards if success

.ret_far                ; we land back here once the call_far function returns
    push af             ; save flags
    push hl
    ld hl,_shadowLock
.ret_far_try_alt_lock
    sra (hl)            ; get alt register mutex
    jr C,ret_far_try_alt_lock ; just keep trying, can't give up
    pop hl
    pop af

    ex af,af            ; all your register are belong to us
    exx

    pop bc              ; get return (origin) BBR in B, and departing (destination) BBR in C

                        ; we left our origin bank lock unchanged, so it is still locked
    di
    ld (_bank_sp),sp    ; save the departing bank SP in Page0
    out0 (BBR),b        ; make the bank swap
    ei
    ld sp,(_bank_sp)    ; set up the originating SP in old Page0

    ld h,_bankLockBase/$100 ; get the BANK Lock Base, page aligned
    ld a,c              ; make the reference to destination BANKnn Lock
    rrca                ; move the origin bank to low nibble
    rrca                ; we know BBR lower nibble is 0
    rrca
    rrca
    ld l,a
    ld (hl),$FE         ; free the bank we are now departing

    ld hl,_shadowLock   ; give alt register mutex
    ld (hl),$FE

    exx                 ; alt registers are returned
    ex af,af
    ret

;------------------------------------------------------------------------------
; void jp_far(void *str, int8_t bank)
;
; str − This is a pointer to the destination address to which we will jump,
;       type-cast to a pointer of type void*.
; bank− This is the destination bank, relative to the current bank.
;
; This function returns a void.

; stack:
;   bank far
;   str high
;   str low
;   ret high
;   ret low

PUBLIC _jp_far, _jp_far_rst

._jp_far
    pop af              ; collect ret address
    pop hl              ; addr in HL
    pop de              ; bank in E
    push de
    push hl
    push af             ; put return address back, perhaps we'll return one day
                        ; this is the future top of _bios_sp

._jp_far_rst            ; RST 18
    push de             ; save the jump destination from EHL
    push hl

    ld hl,_shadowLock
.jp_far_try_alt_lock
    sra (hl)            ; get alt register mutex
    jr C,jp_far_try_alt_lock ; just keep trying

    ex af,af            ; all your register are belong to us
    exx

    pop de              ; get jump address in DE
    pop bc              ; get relative jump bank in C

    in0 a,(BBR)         ; get the origin bank
    rrca                ; move the origin bank to low nibble
    rrca                ; we know BBR lower nibble is 0
    rrca
    rrca
    ld b,a              ; save origin absolute bank in B
    add a,c             ; create destination far address, from twos complement relative input
    and a,$0F           ; convert it to 4 bit address (not implicit here)

    ld h,_bankLockBase/$100 ; get the BANK Lock base address, page aligned
    ld l,a              ; make reference into destination BANKnn Lock

    rlca                ; move the origin bank to high nibble
    rlca                ; we know BBR lower nibble is 0
    rlca
    rlca
    ld c,a              ; hold destination BBR in C

    xor a
    or (hl)             ; check bank is not cold
    jr Z,jp_far_exit

.jp_far_try_bank_lock
    sra (hl)            ; now get the bank lock,
    jr C,jp_far_try_bank_lock  ; keep trying if the bank is hot, we can't go back

                        ; OK we're going somewhere nice and warm,
                        ; now make the bank switch
    xor a
    and b               ; check whether we're jumping from bios
    jr Z,jp_far_from_bios

    di
    ld (_bank_sp),sp    ; save the origin bank SP in Page0
    out0 (BBR),c        ; make the bank swap
    ei
    ld sp,(_bank_sp)    ; set up the destination bank SP in new Page0

.jp_far_from_bios_ret
    push de             ; push our destination address for ret jp

    ld h,_bankLockBase/$100 ; get the BANK Lock Base, page aligned
    ld l,b              ; make reference to originating BANKnn Lock
    ld (hl),$FE         ; free the origin bank, we're not coming back

.jp_far_exit
    ld hl,_shadowLock   ; give alt register mutex
    ld (hl),$FE

    exx
    ex af,af            ; alt register are returned
    ret                 ; takes us out if there's error, or jp onwards if success

.jp_far_from_bios
    di
    ld (_bios_sp),sp    ; save the bios SP in COMMON AREA 1
    out0 (BBR),c        ; make the bank swap
    ei
    ld sp,(_bank_sp)    ; set up the destination bank SP in new Page0
    jr jp_far_from_bios_ret

;------------------------------------------------------------------------------

PUBLIC _system_rst

._system_rst            ; RST 20
    ex af,af            ; all your register are belong to us
    exx

    ld hl,_shadowLock
.system_try_alt_lock
    sra (hl)            ; get alt register mutex
    jr C,system_try_alt_lock ; just keep trying

    pop hl              ; get the return PC address
    ld e,(hl)           ; get called address in DE
    inc hl
    ld d,(hl)
    inc hl
    push hl             ; push the post system_rst return address back on stack

    in0 a,(CBAR)        ; get COMMON AREA 1 base address (know BANK base address is 0)
    cp d                ; check whether the called function can be reached in CA1
    jr C,system_local_exit ; success, so we stay local

    in0 b,(BBR)         ; get and save origin BBR in B
    ld c,0              ; hold absolute destination, BANK0, in C

    ld hl,_bankLockBase ; get the bank Lock Base, for BANK0
.system_try_bios_lock
    sra (hl)            ; now get the bios bank lock,
    jr C,system_try_bios_lock   ; keep trying if bios bank is hot

                        ; now make the bank switch
    di
    ld (_bank_sp),sp    ; save the origin bank SP in Page0
    out0 (BBR),c        ; make the bank swap, use C because out0 doesn't do immediate
    ei
    ld sp,(_bios_sp)    ; set up the bios SP

                        ; now prepare for our return
    push bc             ; push on the origin BBR in B
    inc sp

    ld hl,system_ret
    push hl             ; push system_ret return address

    push de             ; push our destination address

.system_exit
    exx                 ; alt register are returned
    ex af,af
                        ; we keep alt register mutex, as clib has to use alt registers

    ret                 ; takes us out if there's error, or call into yabios if success

.system_local_exit
    push de             ; push our destination address

    ld hl,_shadowLock   ; give alt register mutex
    ld (hl),$FE

    exx                 ; alt registers are returned
    ex af,af

    ret                 ; call into yabios CA1

.system_ret             ; we land back here once the yabios call is done
    exx                 ; we still have alt register mutex

    pop bc              ; get return (origin) bank in B
    inc sp
                        ; we left our origin bank locked
    di
    ld (_bios_sp),sp    ; save the departing bios SP
    out0 (BBR),b        ; make the bank swap
    ei
    ld sp,(_bank_sp)    ; set up the originating SP in old Page0

    ld hl,_bankLockBase ; get the bank Lock Base, for BANK0
    ld (hl),$FE         ; free the departing bios bank

    ld hl,_shadowLock   ; give alt register mutex
    ld (hl),$FE

    exx                 ; alt registers are returned
    ret                 ; ret (jp) to the address that we left on the stack

;------------------------------------------------------------------------------

PUBLIC _exit_far

._exit_far              ; use this to return to yabios from an exiting application
    ex af,af            ; all your register are belong to us
    exx

    ld hl,_shadowLock
.exit_far_try_alt_lock
    sra (hl)            ; get alt register mutex
    jr C,exit_far_try_alt_lock ; just keep trying, can't give up

    xor a               ; hold absolute destination, BANK0, in A
    in0 c,(BBR)         ; get and save origin BBR in C

    ld hl,_bankLockBase ; get the bank Lock Base, for BANK0
.exit_far_try_bios_lock
    sra (hl)            ; now get the bios bank lock,
    jr C,exit_far_try_bios_lock ; keep trying if bios bank is hot

    di
    ld (_bank_sp),sp   ; save the departing bank SP in Page0
    out0 (BBR),a       ; make the bank swap
    ei
    ld sp,(_bios_sp)   ; set up the arriving SP in bios

    ld h,_bankLockBase/$100 ; get the BANK Lock Base, page aligned
    ld a,c              ; make the reference to origin BANKnn Lock
    rrca                ; move the origin bank to low nibble
    rrca                ; we know BBR lower nibble is 0
    rrca
    rrca
    ld l,a
    ld (hl),$FE         ; free the bank we are now departing

    ld hl,_shadowLock   ; give alt register mutex
    ld (hl),$FE

    exx                 ; alt registers are returned
    ex af,af
    ret                 ; ret (jp) to the address that we left on the bios stack

;------------------------------------------------------------------------------

PUBLIC _am9511a_rst

._am9511a_rst           ; RST 28
    pop hl              ; get the return address
    inc hl
    ld c,(hl)           ; get APU command in C
    inc hl
    push hl             ; push the post return address back on stack
    jp asm_am9511a_cmd

;------------------------------------------------------------------------------

PUBLIC _user_rst

._user_rst              ; RST 30
    ret

;------------------------------------------------------------------------------
; start of common area 1 - system functions
;------------------------------------------------------------------------------

EXTERN _dmac0Lock

;------------------------------------------------------------------------------
; void *memcpy_far(far *str1, int8_t bank1, const void *str2, const int8_t bank2, size_t n)
;
; str1 − This is a pointer to the destination array where the content is to be
;       copied, type-cast to a pointer of type void*.
; bank1− This is the destination bank, relative to the current bank.
; str2 − This is a pointer to the source of data to be copied,
;       type-cast to a pointer of type const void*.
; bank2− This is the source bank, relative to the current bank.
; n    − This is the number of bytes to be copied.
;
; This function returns a void* to destination, which is str1, in HL.

; stack:
;   n high
;   n low
;   bank2 far
;   str2 high
;   str2 low
;   bank1 far
;   str1 high
;   str1 low
;   ret high
;   ret low

PUBLIC _memcpy_far

._memcpy_far
    ld hl,_dmac0Lock
    sra (hl)            ; take the DMAC0 lock
    jr C,_memcpy_far

    ld hl,9
    add hl,sp           ; pointing at nh bytes
    ld b,(hl)           ; b = nh
    dec hl
    ld c,(hl)           ; c = nl

    ld a,b              ; test for zero size, and exit
    or a,c
    jr Z,memcpy_far_error_exit  ; return on zero size

    out0 (BCR0H),b      ; set up the transfer size
    out0 (BCR0L),c
    push bc             ; save transfer size (again)

    dec hl              ; pointing at bank2 far

    in0 a,(BBR)         ; get the current bank
    rrca                ; move the current bank to low nibble
    rrca
    rrca
    rrca                ; save current bank in address format
    ld c,a              ; and put it in C

    add a,(hl)          ; create source relative far address, from twos complement input
    and a,$0F           ; convert it to 4 bit positive address (think this is implicit)
    ld b,a              ; hold source far address in B
    out0 (SAR0B),b      ; (SAR0B has only 4 bits)

    dec hl
    ld d,(hl)           ; get source high address in D
    dec hl
    ld e,(hl)           ; get source low address in E

    dec hl              ; pointing at bank1 far

    ld a,c              ; get current bank in address format
    add a,(hl)          ; create relative far destination address, from twos complement input
    and a,$0F           ; convert it to 4 bit positive address (think this is implicit)
    ld c,a              ; hold far destination address in C
    out0 (DAR0B),c      ; (DAR0B has only 4 bits)

    dec hl
    ld a,(hl)           ; get destination high address in a (h)
    dec hl
    ld l,(hl)           ; get destination low address l
    ld h,a              ; destination high address in h

    ld a,c
    cp b                ; check for bank dest < src
    ex de,hl            ; now source in hl, destination in de
    jr C,memcpy_far_left_right_early    ; if destination is lower bank, we do left right
                        ; otherwise we need to check further

    xor a               ; clear A and Carry
    sbc hl,de           ; check whether destination address < source address
    jr NC,memcpy_far_left_right  ; if so we can do left to right copy

.memcpy_far_right_left
    add hl,de           ; recover the source address
    pop bc              ; get the size of the copy
    dec bc
    add hl,bc           ; add in the copy size to the (source address -1)

    jr NC,memcpy_far_right_left_src_bank_no_overflow
    in0 a,(SAR0B)
    inc a               ; if copy origin flows into following bank
    out0 (SAR0B),a

.memcpy_far_right_left_src_bank_no_overflow
    ex de,hl            ; swap source to de
    add hl,bc           ; add in the copy size to the (destination address -1)
    jr NC,memcpy_far_right_left_dest_bank_no_overflow
    in0 a,(DAR0B)
    inc a               ; if copy destination flows into following bank
    out0 (DAR0B),a

.memcpy_far_right_left_dest_bank_no_overflow
    out0 (SAR0H),d
    out0 (SAR0L),e
    out0 (DAR0H),h
    out0 (DAR0L),l

    ld bc,+(DMODE_DM0|DMODE_SM0|DMODE_MMOD)*$100+DSTAT_DE0
    out0 (DMODE),b      ; DMODE_MMOD - memory-- to memory--, burst mode
    out0 (DSTAT),c      ; DSTAT_DE0 - enable DMA channel 0, no interrupt
                        ; in burst mode the Z180 CPU stops until the DMA completes

    in0 h,(DAR0H)       ; and now the destination address, post memcpy_far
    in0 l,(DAR0L)

.memcpy_far_error_exit
    ld a,$FE
    ld (_dmac0Lock),a   ; give DMAC0 free
    ret

.memcpy_far_left_right
    add hl,de           ; recover the source address, with no carry
.memcpy_far_left_right_early
    pop bc              ; get the size back, just to balance the stack
    out0 (SAR0H),h
    out0 (SAR0L),l
    out0 (DAR0H),d
    out0 (DAR0L),e

    ld bc,+(DMODE_MMOD)*$100+DSTAT_DE0
    out0 (DMODE),b      ; DMODE_MMOD - memory++ to memory++, burst mode
    out0 (DSTAT),c      ; DSTAT_DE0 - enable DMA channel 0, no interrupt
                        ; in burst mode the Z180 CPU stops until the DMA completes

    ex de,hl            ; and swap the destination address into hl to return

    jr memcpy_far_error_exit

;------------------------------------------------------------------------------
; void load_hex(uint8_t bankAbs)
;
; bankAbs − This is the absolute bank address (1 to 15), not BANK0.
;
; Type 2 Extended Segment Address (ESA), is equivalent to BBR data, and translates 1:1.
; Therefore BANK changes can be done by inputting the correct ESA data as 0xn000.
;
; This is a system function, and can only be called from BANK0.
;
; Reads the _bios_ioByte bit 0 to determine which serial interface to use.

PUBLIC _load_hex, _load_hex_fastcall

EXTERN _bios_ioByte

EXTERN initString, invalidTypeStr, badCheckSumStr, LoadOKStr

._load_hex
    pop af
    pop hl
    push hl
    push af

._load_hex_fastcall
    in0 a,(BBR)                 ; grab the current Bank Base Register
    and a                       ; check it is BANK0, so the user can't use this function
    ret NZ

    ld a,l                      ; get the destination bank, to convert to BBR
    and a,$0F                   ; be sure it is sane
    ret Z                       ; and not trying to write to BANK0

    rrca                        ; move the destination bank to high nibble
    rrca                        ; we know BBR lower nibble is 0
    rrca
    rrca
    out0 (BBR),a                ; set the BBR from BANK0, so no need for stack change

    ld a,(_bios_ioByte)         ; get I/O bit
    rrca                        ; put it in Carry
    jr NC,load_hex_get_lock1    ; ASCI1 TTY=0, ASCI0 CRT=1

.load_hex_get_lock0
    ld hl,asci0RxLock
    sra (hl)                    ; take the ASCI0 Rx lock
    ret C                       ; return if we can't get lock
    call asm_asci0_flush_Rx_di  ; flush the ASCI0 Rx buffer
    jr load_hex_print_init

.load_hex_get_lock1
    ld hl,asci1RxLock
    sra (hl)                    ; take the ASCI1 Rx lock
    ret C                       ; return if we can't get lock
    call asm_asci1_flush_Rx_di  ; flush the ASCI1 Rx buffer

.load_hex_print_init
    ld de,initString
        call asm_pstring        ; asm_pstring modifies AF, DE, & HL

.load_hex_wait_colon
    call asm_rchar              ; Rx byte in L (A = char received too)
    cp CHAR_CTRL_C              ; check for CTRL_C
    jr Z,load_hex_done          ; exit done
    cp ':'                      ; wait for ':'
    jr NZ,load_hex_wait_colon
    ld c,0                      ; reset C to compute checksum
    call load_hex_read_byte     ; read byte count
    ld b,a                      ; store it in b
    call load_hex_read_byte     ; read upper byte of address
    ld d,a                      ; store in d
    call load_hex_read_byte     ; read lower byte of address
    ld e,a                      ; store in e
    call load_hex_read_byte     ; read record type
    cp 02                       ; check if record type is 02 (ESA)
    jr Z,load_hex_esa_data
    cp 01                       ; check if record type is 01 (end of file)
    jr Z,load_hex_end_load
    cp 00                       ; check if record type is 00 (data)
    jr NZ,load_hex_inval_type   ; if not, error
.load_hex_read_data
    call load_hex_read_byte
    ld (de),a                   ; write the byte at the RAM address
    inc de
    djnz load_hex_read_data     ; if b non zero, loop to get more data
.load_hex_read_chksum
    call load_hex_read_byte     ; read checksum, but we don't need to keep it
    ld a,c                      ; lower byte of c checksum should be 0
    or a
    jr NZ,load_hex_bad_chk      ; non zero, we have an issue
    ld l,'#'                    ; "#" per line loaded
    call asm_pchar              ; Print it
    jr load_hex_wait_colon

.load_hex_esa_data
    call load_hex_read_byte     ; get high byte of ESA
    and a,$F0                   ; be sure it is sane
    jr Z,load_hex_esa_data_bank0    ; and we're not trying to overwrite yabios RAM
    out0 (BBR),a                ; write it to the BBR
.load_hex_esa_data_bank0
    call load_hex_read_byte     ; get low byte of ESA, abandon it, but calc checksum
    jr load_hex_read_chksum     ; calculate checksum

.load_hex_end_load
    call load_hex_read_byte     ; read checksum, but we don't need to keep it
    ld a,c                      ; lower byte of c checksum should be 0
    or a
    jr NZ,load_hex_bad_chk      ; non zero, we have an issue

.load_hex_done
    ld de,LoadOKStr

.load_hex_exit
    xor a
    out0 (BBR),a                ; get our originating BANK0 BBR back, write it to the BBR

    call asm_pstring            ; asm_pstring modifies AF, DE, & HL

    ld a,(_bios_ioByte)         ; get I/O bit
    rrca                        ; put it in Carry
    jr NC,load_hex_unlock1      ; ASCI1 TTY=0, ASCI0 CRT=1

.load_hex_unlock0
    ld hl,asci0RxLock           ; give up the ASCI0 Rx mutex
    ld (hl),$FE
    ret

.load_hex_unlock1
    ld hl,asci1RxLock           ; give up the ASCI1 Rx mutex
    ld (hl),$FE
    ret

.load_hex_inval_type
    ld de,invalidTypeStr
    jr load_hex_exit

.load_hex_bad_chk
    ld de,badCheckSumStr
    jr load_hex_exit

.load_hex_read_byte             ; returns byte in A, checksum in C
    call asm_rhex
    ld l,a                      ; put assembled byte into L
    add a,c                     ; add the byte read to C (for checksum)
    ld c,a
    ld a,l
    ret                         ; return the byte read in L (A = char received too)

;------------------------------------------------------------------------------
; int8_t bank_get_rel(uint8_t bankAbs)
;
; bankAbs − This is the absolute bank address
;
; Returns the relative bank address

PUBLIC _bank_get_rel, _bank_get_rel_fastcall

._bank_get_rel
    pop af
    pop hl
    push hl
    push af

._bank_get_rel_fastcall
    in0 a,(BBR)         ; get the current bank
    rrca                ; move the current bank to low nibble
    rrca
    rrca
    rrca
    ld h,a

    ld a,l
    and a,$0F           ; limit input to 0 to 15 bank

    sub a,h             ; create source relative far address, from absolute input
    ld l,a

    ret

;------------------------------------------------------------------------------
; uint8_t bank_get_abs(int8_t bankRel)
;
; bankRel − This is the relative bank address (-128 to +127)
;
; Returns the capped absolute bank address (0 to 15)

PUBLIC _bank_get_abs, _bank_get_abs_fastcall

._bank_get_abs
    pop af
    pop hl
    push hl
    push af

._bank_get_abs_fastcall
    in0 a,(BBR)         ; get the current bank
    rrca                ; move the current bank to low nibble
    rrca
    rrca
    rrca

    add a,l             ; create source relative far address, from twos complement input
    and a,$0F           ; convert it to 4 bit positive address
    ld l,a

    ret

;------------------------------------------------------------------------------
; void lock_get(uint8_t * mutex)
;
; mutex − This is a pointer to a simple mutex semaphore

PUBLIC _lock_get, _lock_get_fastcall

._lock_get
    pop af
    pop hl
    push hl
    push af

._lock_get_fastcall
    sra (hl)
    jr C,_lock_get_fastcall
    ret

;------------------------------------------------------------------------------
; uint8_t lock_try(uint8_t * mutex)
;
; mutex − This is a pointer to a simple mutex semaphore
;
; This function returns 1 if it got the lock, 0 otherwise

PUBLIC _lock_try, _lock_try_fastcall

._lock_try
    pop af
    pop hl
    push hl
    push af

._lock_try_fastcall
    sra (hl)
    ld l,0
    jr C,lock_got_not
    inc l
.lock_got_not
    ret

;------------------------------------------------------------------------------
; void lock_give(uint8_t * mutex)
;
; mutex − This is a pointer to a simple mutex semaphore

PUBLIC _lock_give, _lock_give_fastcall

._lock_give
    pop af
    pop hl
    push hl
    push af

._lock_give_fastcall
    ld (hl),$FE
    ret

;------------------------------------------------------------------------------
; start of common area 1 driver - system_time functions
;------------------------------------------------------------------------------

PUBLIC asm_system_tick

EXTERN  __system_time_fraction, __system_time

.asm_system_tick
    push af
    push hl

    in0 a,(TCR)                 ; to clear the PRT0 interrupt, read the TCR
    in0 a,(TMDR0L)              ; followed by the TMDR0

    ld hl,__system_time_fraction
    inc (hl)
    jr Z,system_tick_update     ; at 0 we're at 1 second count, interrupted 256 times

.system_tick_exit
    pop hl
    pop af
    ei                          ; interrupts were enabled, or we wouldn't have been here
    ret

.system_tick_update
    ld hl,_bios_stack_canary    ; check that the bios stack has not collided with code
    ld a,(hl)                   ; grab first byte $AA
    rrca                        ; rotate it to $55
    inc hl                      ; point to second byte $55
    xor (hl)                    ; if correct, zero result
    jr NZ,system_tick_error     ; if not, then error

    ld hl,__system_time
    inc (hl)
    jr NZ,system_tick_exit
    inc hl
    inc (hl)
    jr NZ,system_tick_exit
    inc hl
    inc (hl)
    jr NZ,system_tick_exit
    inc hl
    inc (hl)
    jr system_tick_exit

.system_tick_error
    rst 8                       ; Okay, Houston, we've had a problem!
    defb '@'                    ; Stack Error Code 0x40 '@'
    jr system_tick_exit

;------------------------------------------------------------------------------
; start of common area 1 driver - am9511a functions
;------------------------------------------------------------------------------

EXTERN APUCMDBuf, APUCMDInPtr, APUCMDOutPtr, APUCMDBufUsed
EXTERN APUPTRBuf, APUPTRInPtr, APUPTROutPtr, APUPTRBufUsed
EXTERN APUStatus, APUError, APULock

;------------------------------------------------------------------------------
; Interrupt Service Routine for the Am9511A-1
;
; Initially called once the required operand pointers and commands are loaded.
;
; Following calls generated by END signal whenever a single APU command is completed.
; Sends a new command (with operands if needed) to the APU.
;
; On interrupt exit APUStatus contains either
; __IO_APU_STATUS_BUSY = 1, and rest of APUStatus bits are invalid
; __IO_APU_STATUS_BUSY = 0, idle, and the status bits resulting from the final COMMAND


PUBLIC asm_am9511a_isr

.asm_am9511a_isr
    push af                 ; store AF, etc, so we don't clobber them
    push bc
    push de
    push hl

.am9511a_isr_entry
    ld a,(APUCMDBufUsed)    ; check whether we have a command to do
    or a                    ; zero?
    jr Z,am9511a_isr_end    ; if so then clean up and END

    ld bc,__IO_APU_STATUS   ; the address of the APU status port in BC
    in a,(c)                ; read the APU
    and __IO_APU_STATUS_ERROR   ; any errors?
    call NZ,am9511a_isr_error   ; then capture the error in APUError

    ld hl,(APUCMDOutPtr)    ; get the pointer to place where we pop the COMMAND
    ld a,(hl)               ; get the COMMAND byte
    ld (APUStatus),a        ; save the COMMAND (in APUStatus byte)

    inc l                   ; move the COMMAND pointer low byte along, 0xFF rollover
    ld (APUCMDOutPtr),hl    ; write where the next byte should be popped

    ld hl,APUCMDBufUsed
    dec (hl)                ; atomically decrement COMMAND count remaining

    and $F0                 ; mask only most significant nibble of COMMAND

    cp __IO_APU_OP_REM      ; check whether it is OPERAND removal COMMAND
    jr Z,am9511a_isr_op_rem ; remove an OPERAND

    cp __IO_APU_OP_ENT      ; check whether it is OPERAND entry COMMAND
    jr Z,am9511a_isr_op_ent ; load an OPERAND

    xor a                   ; set PHI = crystal x 1/2 = 9.216MHz
    out0 (CMR),a            ; CPU Clock Multiplier Reg (CMR)
    out0 (CCR),a            ; CPU Control Reg (CCR)
                            ; Am9511A-1 needs TWCS 30ns. This provides 41.7ns.

    ld a,(APUStatus)        ; recover the COMMAND from status byte
    ld bc,__IO_APU_CONTROL  ; the address of the APU control port in BC
    out (c),a               ; load the COMMAND, and do it

    ld a,CMR_X2             ; set PHI = crystal x 2 = 36.864MHz
    out0 (CMR),a            ; CPU Clock Multiplier Reg (CMR)
    out0 (CCR),a            ; CPU Control Reg (CCR) CCR_XTAL_X2 = CMR_X2

    ld hl,APUStatus         ; set APUStatus to busy
    ld (hl),__IO_APU_STATUS_BUSY

.am9511a_isr_exit
    pop hl                  ; recover HL, etc
    pop de
    pop bc
    pop af
    ei                      ; interrupts were enabled, or we wouldn't have been here
    ret                     ; no Z80 interrupt chaining

.am9511a_isr_end            ; we've finished a COMMAND sentence
    ld bc,__IO_APU_STATUS   ; the address of the APU status port in BC
    in a,(c)                ; read the APU
    tst __IO_APU_STATUS_BUSY; test the STATUS byte is valid (i.e. we're not busy)
    jr NZ,am9511a_isr_end
    ld (APUStatus),a        ; update status byte
    jr am9511a_isr_exit     ; we're done here

.am9511a_isr_op_ent
    ld hl,(APUPTROutPtr)    ; get the pointer to where we pop OPERAND PTR
    ld e,(hl)               ; read the OPERAND PTR low byte from the APUPTROutPtr
    inc l                   ; move the POINTER low byte along, 0xFF rollover
    ld d,(hl)               ; read the OPERAND PTR high byte from the APUPTROutPtr
    inc l
    ld b,(hl)               ; read the BBR of OPERAND PTR to the APUPTRInPtr
    inc l
    ld (APUPTROutPtr),hl    ; write where the next POINTER should be read

    ld hl,APUPTRBufUsed     ; decrement of POINTER count remaining
    dec (hl)
    dec (hl)
    dec (hl)

    ex de,hl                ; move the base address of the OPERAND to HL

    in0 e,(BBR)             ; keep current BBR in E
    out0 (BBR),b            ; make the bank swap to B

    xor a                   ; set PHI = crystal x 1/2 = 9.216MHz
    out0 (CMR),a            ; CPU Clock Multiplier Reg (CMR)
    out0 (CCR),a            ; CPU Control Reg (CCR)
                            ; Am9511A-1 needs TWCS 30ns. This provides 41.7ns.

    ld bc,__IO_APU_DATA+$0300 ; the address of the APU data port in BC
    outi                    ; output 16 bit OPERAND to APU

    ex (sp),hl              ; delay for 38 cycles (5us) TWI 1.280us
    ex (sp),hl
    outi                    ; output 16 bit OPERAND to APU

    ld a,(APUStatus)        ; recover the COMMAND (stored in APUStatus byte)
    cp __IO_APU_OP_ENT16    ; is it a 2 byte OPERAND
    jr Z,am9511a_isr_op_ent16 ; yes, then skip over 32bit stuff

    ex (sp),hl              ; delay for 38 cycles (5us) TWI 1.280us
    ex (sp),hl
    outi                    ; output last two bytes of 32 bit OPERAND

    ex (sp),hl              ; delay for 38 cycles (5us) TWI 1.280us
    ex (sp),hl
    outi

.am9511a_isr_op_ent16
    ld a,CMR_X2             ; set PHI = crystal x 2 = 36.864MHz
    out0 (CMR),a            ; CPU Clock Multiplier Reg (CMR)
    out0 (CCR),a            ; CPU Control Reg (CCR) CCR_XTAL_X2 = CMR_X2

    out0 (BBR),e            ; make the bank swap back
    jp am9511a_isr_entry    ; go back to get another COMMAND

.am9511a_isr_op_rem         ; REMINDER operands removed BIG ENDIAN !!!
    ld hl,(APUPTROutPtr)    ; get the pointer to where we pop OPERAND PTR
    ld e,(hl)               ; read the OPERAND PTR low byte from the APUPTROutPtr
    inc l                   ; move the POINTER low byte along, 0xFF rollover
    ld d,(hl)               ; read the OPERAND PTR high byte from the APUPTROutPtr
    inc l
    ld b,(hl)               ; read the BBR of OPERAND PTR to the APUPTRInPtr
    inc l
    ld (APUPTROutPtr),hl    ; write where the next POINTER should be read

    ld hl,APUPTRBufUsed    ; decrement of OPERAND POINTER count remaining
    dec (hl)
    dec (hl)
    dec (hl)

    ex de,hl                ; move the base address of the OPERAND to HL

    in0 e,(BBR)             ; keep current BBR in E
    out0 (BBR),b            ; make the bank swap to B

    ld bc,__IO_APU_DATA+$0300 ; the address of the APU data port in BC

    inc hl                  ; reverse the OPERAND bytes to load

    ld a,(APUStatus)        ; recover the COMMAND (stored in APUStatus byte)
    cp __IO_APU_OP_REM16    ; is it a 2 byte OPERAND
    jr Z,am9511a_isr_op_rem16   ; yes, then skip over 32bit stuff

    inc hl                  ; increment two more bytes for 32bit OPERAND
    inc hl

    ind                     ; get the higher two bytes of 32bit OPERAND
    ind

.am9511a_isr_op_rem16
    ind                     ; get 16 bit OPERAND
    ind

    out0 (BBR),e            ; make the bank swap back
    jp am9511a_isr_entry    ; go back to get another COMMAND

.am9511a_isr_error          ; we've an error to notify in A
    ld hl,APUError          ; collect any previous errors
    or (hl)                 ; and we add any new error types
    ld (hl),a               ; set the APUError status
    ret

;------------------------------------------------------------------------------
;       Initialises the APU buffers
;


PUBLIC asm_am9511a_reset

.asm_am9511a_reset
    push af
    push bc
    push de
    push hl

    ld hl,APUCMDBuf         ; Initialise COMMAND Buffer
    ld (APUCMDInPtr),hl
    ld (APUCMDOutPtr),hl

    ld hl,APUPTRBuf         ; Initialise OPERAND POINTER Buffer
    ld (APUPTRInPtr),hl
    ld (APUPTROutPtr),hl

    xor a                   ; clear A register to 0

    ld (APUCMDBufUsed),a    ; 0 both Buffer counts
    ld (APUPTRBufUsed),a

    ld (APUCMDBuf),a        ; clear COMMAND Buffer
    ld hl,APUCMDBuf
    ld d,h
    ld e,l
    inc de
    ld bc,__APU_CMD_SIZE-1
    ldir

    ld (APUPTRBuf),a        ; clear OPERAND POINTER Buffer
    ld hl,APUPTRBuf
    ld d,h
    ld e,l
    inc de
    ld bc,__APU_PTR_SIZE-1
    ldir

    ld (APUStatus),a        ; set APU status to idle (NOP)
    ld (APUError),a         ; clear APU errors

.am9511a_reset_loop
    ld bc,__IO_APU_STATUS   ; the address of the APU status port in bc
    in a,(c)                ; read the APU
    and __IO_APU_STATUS_BUSY    ; busy?
    jr NZ,am9511a_reset_loop

    in0 a,(ITC)
    or a,ITC_ITE0           ; enable INT0 for APU
    out0 (ITC),a

    ld hl,APULock           ; load the mutex lock address
    ld (hl),$FE             ; give mutex lock

    pop hl
    pop de
    pop bc
    pop af
    ret

;------------------------------------------------------------------------------
;       Confirms whether the APU is idle.
;       Loop until it returns ready.
;       Operand Entry and Removal takes little time,
;       and we'll be interrupted for Command entry.
;       Use after the first _apu_init call.
;
;       L = contents of (APUStatus || APUError)
;       SCF if no errors (aggregation of any errors found)
;
;       APUError is zeroed on return
;       Uses AF, HL

PUBLIC asm_am9511a_chk_idle

.asm_am9511a_chk_idle
    ld a,(APUStatus)        ; get the status of the APU (but don't disturb APU)
    tst __IO_APU_STATUS_BUSY; check busy bit is set,
    jr NZ,asm_am9511a_chk_idle  ; so we wait

    ld hl,APUError
    or (hl)                 ; collect the aggregated errors, with APUStatus
    tst __IO_APU_STATUS_ERROR   ; any errors?
    ld (hl),0               ; clear any aggregated errors in APUError
    ld h,0
    ld l,a
    ret NZ                  ; return with no carry if errors
    scf                     ; set carry flag
    ret                     ; return with (APUStatus || APUError) with carry set if no errors

;------------------------------------------------------------------------------
;       APU_OPP
;
;       OPERAND BANK in B
;       POINTER to OPERAND in DE
;       APU COMMAND in C

PUBLIC asm_am9511a_opp

; cmd - This is the APU command to load either 16 bit or 32 bit operand. uint8 (C)
; bank- This is the operand bank, relative to the current bank. int8 (B)
; addr- This is a pointer to operand, type-cast to a pointer of type void* (DE)

.asm_am9511a_opp
    ld a,(APUCMDBufUsed)    ; Get the number of bytes in the COMMAND buffer
    cp __APU_CMD_SIZE-1     ; check whether there is space in the buffer
    ret NC                  ; COMMAND buffer full, so exit

    ld a,(APUPTRBufUsed)    ; Get the number of bytes in the OPERAND PTR buffer
    cp __APU_PTR_SIZE-4     ; check whether there is space for a 3 byte OPERAND PTR
    ret NC                  ; OPERAND POINTER buffer full, so exit

    in0 a,(BBR)             ; get the origin (this) bank
    rrca                    ; move the origin bank to low nibble
    rrca                    ; we know BBR lower nibble is 0
    rrca
    rrca
    add a,b                 ; create destination far address, from twos complement relative input
    and a,$0F               ; convert it to 4 bit address (not implicit here)

    rlca                    ; move the origin bank to high nibble
    rlca                    ; we know BBR lower nibble is 0
    rlca
    rlca
    ld b,a                  ; hold destination BBR in B

    di
    ld hl,(APUCMDInPtr)     ; get the pointer to where we poke
    ld (hl),c               ; write the COMMAND byte to the APUCMDInPtr
    inc l                   ; move the COMMAND pointer low byte along, 0xFF rollover
    ld (APUCMDInPtr),hl     ; write where the next byte should be poked

    ld hl,APUCMDBufUsed
    inc (hl)                ; atomic increment of COMMAND count


    ld hl,(APUPTRInPtr)     ; get the pointer to where we poke
    ld (hl),e               ; write the low byte of OPERAND PTR to the APUPTRInPtr
    inc l                   ; move the POINTER low byte along, 0xFF rollover
    ld (hl),d               ; write the high byte of OPERAND PTR to the APUPTRInPtr
    inc l
    ld (hl),b               ; write the BBR of OPERAND PTR to the APUPTRInPtr
    inc l
    ld (APUPTRInPtr),hl     ; write where the next POINTER should be poked

    ld hl,APUPTRBufUsed
    inc (hl)                ; increment of OPERAND PTR count
    inc (hl)
    inc (hl)

    ei
    ret                     ; just complete, and exit

;------------------------------------------------------------------------------
;       APU_CMD
;
;       APU COMMAND in C

PUBLIC asm_am9511a_cmd

.asm_am9511a_cmd
    ld a,(APUCMDBufUsed)        ; Get the number of bytes in the COMMAND buffer
    cp __APU_CMD_SIZE-1         ; check whether there is space in the buffer
    ret NC                      ; COMMAND buffer full, so exit

    ld hl,APUCMDBufUsed
    di
    inc (hl)                    ; atomic increment of COMMAND count
    ld hl,(APUCMDInPtr)         ; get the pointer to where we poke
    ei
    ld (hl),c                   ; write the COMMAND byte to the APUCMDInPtr

    inc l                       ; move the COMMAND pointer low byte along, 0xFF rollover
    ld (APUCMDInPtr),hl         ; write where the next byte should be poked
    ret

;------------------------------------------------------------------------------
; start of common area 1 driver - asci0 functions
;------------------------------------------------------------------------------

EXTERN asci0RxCount, asci0RxIn, asci0RxOut, asci0RxBuffer, asci0RxLock
EXTERN asci0TxCount, asci0TxIn, asci0TxOut, asci0TxBuffer, asci0TxLock
EXTERN asci0RxBuffer
EXTERN asciTxBuffer

PUBLIC asm_asci0_interrupt


.asm_asci0_interrupt
    push af
    push hl
                                ; start doing the Rx stuff
    in0 a,(STAT0)               ; load the ASCI0 status register
    tst STAT0_RDRF              ; test whether we have received on ASCI0
    jr Z,asm_asci0_tx_check     ; if not, go check for bytes to transmit

.asm_asci0_rx_get
    in0 l,(RDR0)                ; move Rx byte from the ASCI0 RDR to l

    and STAT0_OVRN|STAT0_PE|STAT0_FE    ; test whether we have error on ASCI0
    jr NZ,asm_asci0_rx_error    ; drop this byte, clear error, and get the next byte

    ld a,(asci0RxCount)         ; get the number of bytes in the Rx buffer
    cp __ASCI0_RX_SIZE-1        ; check whether there is space in the buffer
    jr NC,asm_asci0_rx_check    ; buffer full, check whether we need to drain H/W FIFO

    ld a,l                      ; get Rx byte from l
    ld hl,(asci0RxIn)           ; get the pointer to where we poke
    ld (hl),a                   ; write the Rx byte to the asci0RxIn target

    inc l                       ; move the Rx pointer low byte along, 0xFF rollover
    ld (asci0RxIn),hl           ; write where the next byte should be poked

    ld hl,asci0RxCount
    inc (hl)                    ; atomically increment Rx buffer count

    ld a,(asci0RxCount)         ; get the current Rx count
    cp __ASCI0_RX_FULLISH       ; compare the count with the preferred full size
    jr NZ,asm_asci0_rx_check    ; leave the /RTS low, and check for Rx/Tx possibility

    in0 a,(CNTLA0)              ; get current value of control byte A
    or CNTLA0_RTS0              ; assert /RTS
    out0 (CNTLA0),a             ; write it back

.asm_asci0_rx_check             ; Z8S180 has 4 byte Rx H/W FIFO
    in0 a,(STAT0)               ; load the ASCI0 status register
    tst STAT0_RDRF              ; test whether we have received on ASCI0
    jr NZ,asm_asci0_rx_get      ; if still more bytes in H/W FIFO, get them

.asm_asci0_tx_check             ; now start doing the Tx stuff
    and STAT0_TDRE              ; test whether we can transmit on ASCI0
    jr Z,asm_asci0_tx_end       ; if not, then end

    ld a,(asci0TxCount)         ; get the number of bytes in the Tx buffer
    or a                        ; check whether it is zero
    jr Z,asm_asci0_tx_tie0_clear; if the count is zero, then disable the Tx Interrupt

    ld hl,(asci0TxOut)          ; get the pointer to place where we pop the Tx byte
    ld a,(hl)                   ; get the Tx byte
    out0 (TDR0),a               ; output the Tx byte to the ASCI0

    inc l                       ; move the Tx pointer low byte along, 0xFF rollover
    inc l
    ld (asci0TxOut),hl          ; write where the next byte should be popped

    ld hl,asci0TxCount
    dec (hl)                    ; atomically decrement current Tx count
    jr NZ,asm_asci0_tx_end      ; if we've more Tx bytes to send, we're done for now

.asm_asci0_tx_tie0_clear
    in0 a,(STAT0)               ; get the ASCI0 status register
    and ~STAT0_TIE              ; mask out (disable) the Tx Interrupt
    out0 (STAT0),a              ; set the ASCI0 status register

.asm_asci0_tx_end
    pop hl
    pop af
    ei
    ret

.asm_asci0_rx_error
    in0 a,(CNTLA0)              ; get the CNTRLA0 register
    and ~CNTLA0_EFR             ; to clear the error flag, EFR, to 0
    out0 (CNTLA0),a             ; and write it back
    jr asm_asci0_rx_check

PUBLIC asm_asci0_init

.asm_asci0_init
    ; initialise the ASCI0
                                ; load the default ASCI CRT configuration
                                ; BAUD = 115200 8n2
                                ; receive enabled
                                ; transmit enabled
                                ; receive interrupt enabled
                                ; transmit interrupt disabled

    ld a,CNTLA0_RE|CNTLA0_TE|CNTLA0_MODE_8N2
    out0 (CNTLA0),a             ; output to the ASCI0 control A reg

                                ; PHI / PS / SS / DR = BAUD Rate
                                ; PHI = 36.864MHz
                                ; BAUD = 115200 = 36864000 / 10 / 2 / 16
                                ; PS 0, SS_DIV_2, DR 0
    ld a,CNTLB0_SS_DIV_2
    out0    (CNTLB0),a          ; output to the ASCI0 control B reg

    ld a,STAT0_RIE              ; receive interrupt enabled
    out0 (STAT0),a              ; output to the ASCI0 status reg

    ld hl,asci0TxLock           ; load the mutex lock address
    ld (hl),$FE                 ; give mutex lock
    ld hl,asci0RxLock           ; load the mutex lock address
    ld (hl),$FE                 ; give mutex lock

    ret

PUBLIC asm_asci0_flush_Rx_di

.asm_asci0_flush_Rx_di
    push af
    push hl

    di                          ; di
    call asm_asci0_flush_Rx
    ei                          ; ei

    pop hl
    pop af
    ret

PUBLIC asm_asci0_flush_Rx

.asm_asci0_flush_Rx
    xor a
    ld (asci0RxCount),a         ; reset the Rx counter (set 0)
    ld hl,asci0RxBuffer         ; load Rx buffer pointer home
    ld (asci0RxIn),hl
    ld (asci0RxOut),hl
    ret

PUBLIC asm_asci0_flush_Tx_di

.asm_asci0_flush_Tx_di
    push af
    push hl

    di                          ; di
    call asm_asci0_flush_Tx
    ei                          ; ei

    pop hl
    pop af
    ret

PUBLIC asm_asci0_flush_Tx

.asm_asci0_flush_Tx
    xor a
    ld (asci0TxCount),a         ; reset the Tx counter (set 0)
    ld hl,asciTxBuffer          ; load Tx buffer pointer home
    ld (asci0TxIn),hl
    ld (asci0TxOut),hl
    ret

PUBLIC asm_asci0_reset

.asm_asci0_reset
    ; interrupts should be disabled
    call asm_asci0_init
    call asm_asci0_flush_Rx
    call asm_asci0_flush_Tx
    ret

PUBLIC asm_asci0_getc

.asm_asci0_getc

    ; exit     : l = char received (a = char received too)
    ;            carry reset if Rx buffer is empty
    ;
    ; modifies : af, l

    ld a,(asci0RxCount)         ; get the number of bytes in the Rx buffer
    or a                        ; see if there are zero bytes available
    ret Z                       ; if the count is zero, then return

    push hl
    cp __ASCI0_RX_EMPTYISH      ; compare the count with the preferred empty size
    jr NZ,asm_asci0_getc_cont   ; if the buffer not emptyish, don't change the RTS

    in0 a,(CNTLA0)              ; get current value of control byte A
    and ~CNTLA0_RTS0            ; clear /RTS
    out0 (CNTLA0),a             ; write it back

.asm_asci0_getc_cont
    ld hl,(asci0RxOut)          ; get the pointer to place where we pop the Rx byte
    ld a,(hl)                   ; get the Rx byte

    inc l                       ; move the Rx pointer low byte along, 0xFF rollover
    ld (asci0RxOut),hl          ; write where the next byte should be popped

    ld hl,asci0RxCount
    dec (hl)                    ; atomically decrement Rx count

    pop hl

    ld l,a                      ; put the byte in l
    scf                         ; indicate char received
    ret

PUBLIC asm_asci0_peekc

.asm_asci0_peekc

    ld a,(asci0RxCount)         ; get the number of bytes in the Rx buffer
    ld l,a                      ; and put it in l
    or a                        ; see if there are zero bytes available
    ret Z                       ; if the count is zero, then return

    ld hl,(asci0RxOut)          ; get the pointer to place where we pop the Rx byte
    ld a,(hl)                   ; get the Rx byte
    ld l,a                      ; and put it in l
    ret

PUBLIC asm_asci0_pollc

.asm_asci0_pollc

    ; exit     : l = number of characters in Rx buffer
    ;            carry reset if Rx buffer is empty
    ;
    ; modifies : af, l

    ld a,(asci0RxCount)         ; load the Rx bytes in buffer
    ld l,a                      ; load result

    or a                        ; check whether there are non-zero count
    ret Z                       ; return if zero count

    scf                         ; set carry to indicate char received
    ret

PUBLIC asm_asci0_putc

.asm_asci0_putc
    ; enter    : l = char to output
    ; exit     : l = 1 if Tx buffer is full
    ;            carry reset
    ; modifies : af, hl
    ld a,(asci0TxCount)         ; get the number of bytes in the Tx buffer
    or a                        ; check whether the buffer is empty
    jr NZ,asci0_put_buffer_tx   ; buffer not empty, so abandon immediate Tx

    di
    in0 a,(STAT0)               ; get the ASCI0 status register
    and STAT0_TDRE              ; test whether we can transmit on ASCI0
    jr Z,asci0_put_buffer_tx_ei ; if not, so abandon immediate Tx
    out0 (TDR0),l               ; output the Tx byte to the ASCI0

    ld l,0                      ; indicate Tx buffer was not full
    ei
    ret                         ; and just complete

.asci0_put_buffer_tx_ei
    ei
.asci0_put_buffer_tx
    ld a,(asci0TxCount)         ; Get the number of bytes in the Tx buffer
    cp __ASCI0_TX_SIZE-1        ; check whether there is space in the buffer
    jr NC,asci0_put_buffer_tx   ; buffer full, so keep trying

    ld a,l                      ; Tx byte
    ld hl,asci0TxCount
    di
    inc (hl)                    ; atomic increment of Tx count
    ld hl,(asci0TxIn)           ; get the pointer to where we poke
    ei
    ld (hl),a                   ; write the Tx byte to the asci0TxIn

    inc l                       ; move the Tx pointer low byte along, 0xFF rollover
    inc l
    ld (asci0TxIn),hl           ; write where the next byte should be poked

    ld l,0                      ; indicate Tx buffer was not full

    in0 a,(STAT0)               ; load the ASCI0 status register
    and STAT0_TIE               ; test whether ASCI0 interrupt is set
    ret NZ                      ; if so then just return

    di                          ; critical section begin
    in0 a,(STAT0)               ; get the ASCI status register again
    or STAT0_TIE                ; mask in (enable) the Tx Interrupt
    out0 (STAT0),a              ; set the ASCI status register
    ei                          ; critical section end
    ret

;------------------------------------------------------------------------------
; start of common area 1 driver - asci1 functions
;------------------------------------------------------------------------------

EXTERN asci1RxCount, asci1RxIn, asci1RxOut, asci1RxBuffer, asci1RxLock
EXTERN asci1TxCount, asci1TxIn, asci1TxOut, asci1TxBuffer, asci1TxLock
EXTERN asci1RxBuffer
EXTERN asciTxBuffer


PUBLIC asm_asci1_interrupt

.asm_asci1_interrupt
    push af
    push hl
                                ; start doing the Rx stuff
    in0 a,(STAT1)               ; load the ASCI1 status register
    tst STAT1_RDRF              ; test whether we have received on ASCI1
    jr Z,asm_asci1_tx_check     ; if not, go check for bytes to transmit

.asm_asci1_rx_get
    in0 l,(RDR1)                ; move Rx byte from the ASCI1 RDR to l

    and STAT1_OVRN|STAT1_PE|STAT1_FE    ; test whether we have error on ASCI1
    jr NZ,asm_asci1_rx_error    ; drop this byte, clear error, and get the next byte

    ld a,(asci1RxCount)         ; get the number of bytes in the Rx buffer
    cp __ASCI1_RX_SIZE-1        ; check whether there is space in the buffer
    jr NC,asm_asci1_rx_check    ; buffer full, check whether we need to drain H/W FIFO

    ld a,l                      ; get Rx byte from l
    ld hl,(asci1RxIn)           ; get the pointer to where we poke
    ld (hl),a                   ; write the Rx byte to the asci1RxIn target

    inc l                       ; move the Rx pointer low byte along, 0xFF rollover
    ld (asci1RxIn),hl           ; write where the next byte should be poked

    ld hl,asci1RxCount
    inc (hl)                    ; atomically increment Rx buffer count

.asm_asci1_rx_check             ; Z8S180 has 4 byte Rx H/W FIFO
    in0 a,(STAT1)               ; load the ASCI1 status register
    tst STAT1_RDRF              ; test whether we have received on ASCI1
    jr NZ,asm_asci1_rx_get      ; if still more bytes in H/W FIFO, get them

.asm_asci1_tx_check             ; now start doing the Tx stuff
    and STAT1_TDRE              ; test whether we can transmit on ASCI1
    jr Z,asm_asci1_tx_end       ; if not, then end

    ld a,(asci1TxCount)         ; get the number of bytes in the Tx buffer
    or a                        ; check whether it is zero
    jr Z,asm_asci1_tx_tie1_clear; if the count is zero, then disable the Tx Interrupt

    ld hl,(asci1TxOut)          ; get the pointer to place where we pop the Tx byte
    ld a,(hl)                   ; get the Tx byte
    out0 (TDR1),a               ; output the Tx byte to the ASCI1

    inc l                       ; move the Tx pointer low byte along, 0xFF rollover
    inc l
    ld (asci1TxOut),hl          ; write where the next byte should be popped

    ld hl,asci1TxCount
    dec (hl)                    ; atomically decrement current Tx count
    jr NZ,asm_asci1_tx_end      ; if we've more Tx bytes to send, we're done for now

.asm_asci1_tx_tie1_clear
    in0 a,(STAT1)               ; get the ASCI1 status register
    and ~STAT1_TIE              ; mask out (disable) the Tx Interrupt
    out0 (STAT1),a              ; set the ASCI1 status register

.asm_asci1_tx_end
    pop hl
    pop af
    ei
    ret

.asm_asci1_rx_error
    in0 a,(CNTLA1)              ; get the CNTRLA1 register
    and ~CNTLA1_EFR             ; to clear the error flag, EFR, to 0
    out0 (CNTLA1),a             ; and write it back
    jr asm_asci1_rx_check


PUBLIC asm_asci1_init

.asm_asci1_init
    ; initialise the ASCI1
                                ; load the default ASCI TTY configuration
                                ; BAUD = 9600 8n2
                                ; receive enabled
                                ; transmit enabled
                                ; receive interrupt enabled
                                ; transmit interrupt disabled

    ld a,CNTLA1_RE|CNTLA1_TE|CNTLA1_MODE_8N2
    out0 (CNTLA1),a             ; output to the ASCI1 control A reg

                                ; PHI / PS / SS / DR = BAUD Rate
                                ; PHI = 36.864MHz
                                ; BAUD = 9600 = 36864000 / 30 / 2 / 64
                                ; PS 1, SS_DIV_2, DR 1
    ld a,CNTLB1_PS|CNTLB1_DR|CNTLB1_SS_DIV_2
    out0    (CNTLB1),a          ; output to the ASCI1 control B reg

    ld a,STAT1_RIE              ; receive interrupt enabled
    out0 (STAT1),a              ; output to the ASCI1 status reg

    ld hl,asci1TxLock           ; load the mutex lock address
    ld (hl),$FE                 ; give mutex lock
    ld hl,asci1RxLock           ; load the mutex lock address
    ld (hl),$FE                 ; give mutex lock

    ret

PUBLIC asm_asci1_flush_Rx_di

.asm_asci1_flush_Rx_di
    push af
    push hl

    di                          ; di
    call asm_asci1_flush_Rx
    ei                          ; ei

    pop hl
    pop af
    ret

PUBLIC asm_asci1_flush_Rx

.asm_asci1_flush_Rx
    xor a
    ld (asci1RxCount),a         ; reset the Rx counter (set 0)
    ld hl,asci1RxBuffer         ; load Rx buffer pointer home
    ld (asci1RxIn),hl
    ld (asci1RxOut),hl
    ret

PUBLIC asm_asci1_flush_Tx_di

.asm_asci1_flush_Tx_di
    push af
    push hl

    di                          ; di
    call asm_asci1_flush_Tx
    ei                          ; ei

    pop hl
    pop af
    ret

PUBLIC asm_asci1_flush_Tx

.asm_asci1_flush_Tx

    xor a
    ld (asci1TxCount),a         ; reset the Tx counter (set 0)
    ld hl,asciTxBuffer+1        ; load Tx buffer pointer home
    ld (asci1TxIn),hl
    ld (asci1TxOut),hl
    ret

PUBLIC asm_asci1_reset

.asm_asci1_reset
    ; interrupts should be disabled
    call asm_asci1_init
    call asm_asci1_flush_Rx
    call asm_asci1_flush_Tx
    ret

PUBLIC asm_asci1_getc

.asm_asci1_getc

    ; exit     : l = char received (a = char received too)
    ;            carry reset if Rx buffer is empty
    ;
    ; modifies : af, l

    ld a,(asci1RxCount)         ; get the number of bytes in the Rx buffer
    or a                        ; see if there are zero bytes available
    ret Z                       ; if the count is zero, then return

    push hl

    ld hl,(asci1RxOut)          ; get the pointer to place where we pop the Rx byte
    ld a,(hl)                   ; get the Rx byte

    inc l                       ; move the Rx pointer low byte along, 0xFF rollover
    ld (asci1RxOut),hl          ; write where the next byte should be popped

    ld hl,asci1RxCount
    dec (hl)                    ; atomically decrement Rx count

    pop hl

    ld l,a                      ; put the byte in l
    scf                         ; indicate char received
    ret

PUBLIC asm_asci1_peekc

.asm_asci1_peekc

    ld a,(asci1RxCount)         ; get the number of bytes in the Rx buffer
    ld l,a                      ; and put it in l
    or a                        ; see if there are zero bytes available
    ret Z                       ; if the count is zero, then return

    ld hl,(asci1RxOut)          ; get the pointer to place where we pop the Rx byte
    ld a,(hl)                   ; get the Rx byte
    ld l,a                      ; and put it in l
    ret

PUBLIC asm_asci1_pollc

.asm_asci1_pollc

    ; exit     : l = number of characters in Rx buffer
    ;            carry reset if Rx buffer is empty
    ;
    ; modifies : af, l

    ld a,(asci1RxCount)         ; load the Rx bytes in buffer
    ld l,a                      ; load result

    or a                        ; check whether there are non-zero count
    ret Z                       ; return if zero count

    scf                         ; set carry to indicate char received
    ret

PUBLIC asm_asci1_putc

.asm_asci1_putc
    ; enter    : l = char to output
    ; exit     : l = 1 if Tx buffer is full
    ;            carry reset
    ; modifies : af, hl
    ld a,(asci1TxCount)         ; get the number of bytes in the Tx buffer
    or a                        ; check whether the buffer is empty
    jr NZ,asci1_put_buffer_tx   ; buffer not empty, so abandon immediate Tx

    di
    in0 a,(STAT1)               ; get the ASCI1 status register
    and STAT1_TDRE              ; test whether we can transmit on ASCI1
    jr Z,asci1_put_buffer_tx_ei ; if not, so abandon immediate Tx
    out0 (TDR1),l               ; output the Tx byte to the ASCI1

    ld l,0                      ; indicate Tx buffer was not full
    ei
    ret                         ; and just complete

.asci1_put_buffer_tx_ei
    ei
.asci1_put_buffer_tx
    ld a,(asci1TxCount)         ; Get the number of bytes in the Tx buffer
    cp __ASCI1_TX_SIZE-1        ; check whether there is space in the buffer
    jr NC,asci1_put_buffer_tx   ; buffer full, so keep trying

    ld a,l                      ; Tx byte
    ld hl,asci1TxCount
    di
    inc (hl)                    ; atomic increment of Tx count
    ld hl,(asci1TxIn)           ; get the pointer to where we poke
    ei
    ld (hl),a                   ; write the Tx byte to the asci1TxIn

    inc l                       ; move the Tx pointer low byte along, 0xFF rollover
    inc l
    ld (asci1TxIn),hl           ; write where the next byte should be poked

    ld l,0                      ; indicate Tx buffer was not full

    in0 a,(STAT1)               ; load the ASCI1 status register
    and STAT1_TIE               ; test whether ASCI1 interrupt is set
    ret NZ                      ; if so then just return

    di                          ; critical section begin
    in0 a,(STAT1)               ; get the ASCI status register again
    or STAT1_TIE                ; mask in (enable) the Tx Interrupt
    out0 (STAT1),a              ; set the ASCI status register
    ei                          ; critical section end
    ret

;------------------------------------------------------------------------------
; start of common area 1 driver - 8255 functions
;------------------------------------------------------------------------------

PUBLIC ide_read_byte,  ide_write_byte
PUBLIC ide_read_block, ide_write_block

    ;Do a read bus cycle to the drive, using the 8255.
    ;input A = ide register address
    ;output A = lower byte read from IDE drive

.ide_read_byte
    push bc
    push de
    ld d,a                  ;copy address to D
    ld bc,__IO_PIO_IDE_CTL
    out (c),a               ;drive address onto control lines
    or __IO_PIO_IDE_RD_LINE
    out (c),a               ;and assert read pin
    ld bc,__IO_PIO_IDE_LSB
    in e,(c)                ;read the lower byte
    ld bc,__IO_PIO_IDE_CTL
    out (c),d               ;deassert read pin
    xor a
    out (c),a               ;deassert all control pins
    ld a,e
    pop de
    pop bc
    ret

    ;Read a block of 512 bytes (one sector) from the drive
    ;16 bit data register and store it in memory at (HL++)
    ;Uses BC, DE

.ide_read_block
    ld bc,__IO_PIO_IDE_CTL
    ld d,__IO_PIO_IDE_DATA
    out (c),d               ;drive address onto control lines
    ld e,$0                 ;keep iterative count in e

IF (__IO_PIO_IDE_CTL = __IO_PIO_IDE_MSB+1) & (__IO_PIO_IDE_MSB = __IO_PIO_IDE_LSB+1)
.ide_rdblk2
    ld d,__IO_PIO_IDE_DATA|__IO_PIO_IDE_RD_LINE
    out (c),d               ;and assert read pin
    ld bc,__IO_PIO_IDE_LSB+$0200 ;drive lower lines with lsb,plus ini b offset
    ini                     ;read the lower byte (HL++)
    inc c                   ;drive upper lines with msb
    ini                     ;read the upper byte (HL++)
    inc c                   ;drive control port
    ld d,__IO_PIO_IDE_DATA
    out (c),d               ;deassert read pin
    dec e                   ;keep iterative count in e
    jr NZ,ide_rdblk2

ELSE
.ide_rdblk2
    ld d,__IO_PIO_IDE_DATA|__IO_PIO_IDE_RD_LINE
    out (c),d               ;and assert read pin
    ld bc,__IO_PIO_IDE_LSB  ;drive lower lines with lsb
    ini                     ;read the lower byte (HL++)
    ld bc,__IO_PIO_IDE_MSB  ;drive upper lines with msb
    ini                     ;read the upper byte (HL++)
    ld bc,__IO_PIO_IDE_CTL
    ld d,__IO_PIO_IDE_DATA
    out (c),d               ;deassert read pin
    dec e                   ;keep iterative count in e
    jr NZ,ide_rdblk2

ENDIF
;   ld bc,__IO_PIO_IDE_CTL  ;remembering what's in bc
;   ld e,$0
    out (c),e               ;deassert all control pins
    ret

    ;Do a write bus cycle to the drive, via the 8255
    ;input A = ide register address
    ;input E = lsb to write to IDE drive

.ide_write_byte
    push bc
    push de
    ld d,a                  ;copy address to D
    ld bc,__IO_PIO_IDE_CONFIG
    ld a,__IO_PIO_IDE_WR
    out (c),a               ;config 8255 chip, write mode
    ld bc,__IO_PIO_IDE_CTL
    ld a,d
    out (c),a               ;drive address onto control lines
    or __IO_PIO_IDE_WR_LINE
    out (c),a               ;and assert write pin
    ld bc,__IO_PIO_IDE_LSB
    out (c),e               ;drive lower lines with lsb
    ld bc,__IO_PIO_IDE_CTL
    out (c),d               ;deassert write pin
    xor a
    out (c),a               ;deassert all control pins
    ld bc,__IO_PIO_IDE_CONFIG
    ld a,__IO_PIO_IDE_RD
    out (c),a               ;config 8255 chip, read mode
    pop de
    pop bc
    ret

    ;Write a block of 512 bytes (one sector) from (HL++) to
    ;the drive 16 bit data register
    ;Uses BC, DE

.ide_write_block
    ld bc,__IO_PIO_IDE_CONFIG
    ld d,__IO_PIO_IDE_WR
    out (c),d               ;config 8255 chip, write mode
    ld bc,__IO_PIO_IDE_CTL
    ld d,__IO_PIO_IDE_DATA
    out (c),d               ;drive address onto control lines
    ld e,$0                 ;keep iterative count in e

IF (__IO_PIO_IDE_CTL = __IO_PIO_IDE_MSB+1) & (__IO_PIO_IDE_MSB = __IO_PIO_IDE_LSB+1)
.ide_wrblk2
    ld d,__IO_PIO_IDE_DATA|__IO_PIO_IDE_WR_LINE
    out (c),d               ;and assert write pin
    ld bc,__IO_PIO_IDE_LSB+$0200    ;drive lower lines with lsb, plus outi b offset
    outi                    ;write the lower byte (HL++)
    inc c                   ;drive upper lines with msb
    outi                    ;write the upper byte (HL++)
    inc c                   ;drive control port
    ld d,__IO_PIO_IDE_DATA
    out (c),d               ;deassert write pin
    dec e                   ;keep iterative count in e
    jr NZ,ide_wrblk2

ELSE
.ide_wrblk2
    ld d,__IO_PIO_IDE_DATA|__IO_PIO_IDE_WR_LINE
    out (c),d               ;and assert write pin
    ld bc,__IO_PIO_IDE_LSB  ;drive lower lines with lsb
    outi                    ;write the lower byte (HL++)
    ld bc,__IO_PIO_IDE_MSB  ;drive upper lines with msb
    outi                    ;write the upper byte (HL++)
    ld bc,__IO_PIO_IDE_CTL
    ld d,__IO_PIO_IDE_DATA
    out (c),d               ;deassert write pin
    dec e                   ;keep iterative count in e
    jr NZ,ide_wrblk2

ENDIF
;   ld bc,__IO_PIO_IDE_CTL  ;remembering what's in bc
;   ld e,$0
    out (c),e               ;deassert all control pins
    ld bc,__IO_PIO_IDE_CONFIG
    ld d,__IO_PIO_IDE_RD
    out (c),d               ;config 8255 chip, read mode
    ret

;------------------------------------------------------------------------------
; start of common area 1 driver - IDE functions
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
; IDE internal subroutines
;
; These routines talk to the drive, using the low level I/O.
; Normally a program should not call these directly.
;------------------------------------------------------------------------------

PUBLIC ide_setup_lba
PUBLIC ide_wait_ready, ide_wait_drq, ide_test_error

; set up the drive LBA registers
; LBA is contained in BCDE registers

.ide_setup_lba
    push hl
    ld a,__IO_PIO_IDE_LBA0
    call ide_write_byte     ;set LBA0 0:7
    ld e,d
    ld a,__IO_PIO_IDE_LBA1
    call ide_write_byte     ;set LBA1 8:15
    ld e,c
    ld a,__IO_PIO_IDE_LBA2
    call ide_write_byte     ;set LBA2 16:23
    ld a,b
    and 00001111b           ;lowest 4 bits used only
    or  11100000b           ;to enable LBA address mode, Master only
    ld e,a
    ld a,__IO_PIO_IDE_LBA3
    call ide_write_byte     ;set LBA3 24:27 + bits 5:7=111
    pop hl
    ret

; How to poll (waiting for the drive to be ready to transfer data):
; Read the Regular Status port until bit 7 (BSY, value = 0x80) clears,
; and bit 3 (DRQ, value = 0x08) sets.
; Or until bit 0 (ERR, value = 0x01) or bit 5 (DFE, value = 0x20) sets.
; If neither error bit is set, the device is ready right then.

; Carry is set on wait success.

.ide_wait_ready
    push af
.ide_wait_ready2
    ld a,__IO_PIO_IDE_ALT_STATUS    ;get IDE alt status register
    call ide_read_byte
    tst 00100001b           ;test for ERR or DFE
    jr NZ,ide_wait_error
    and 11000000b           ;mask off BuSY and RDY bits
    xor 01000000b           ;wait for RDY to be set and BuSY to be clear
    jr NZ,ide_wait_ready2
    pop af
    scf                     ;set carry flag on success
    ret

.ide_wait_error
    pop af
    or a                    ;clear carry flag on failure
    ret

; Wait for the drive to be ready to transfer data.
; Returns the drive's status in A

; Carry is set on wait success.

.ide_wait_drq
    push af
.ide_wait_drq2
    ld a,__IO_PIO_IDE_ALT_STATUS    ;get IDE alt status register
    call ide_read_byte
    tst 00100001b           ;test for ERR or DFE
    jr NZ,ide_wait_error
    and 10001000b           ;mask off BuSY and DRQ bits
    xor 00001000b           ;wait for DRQ to be set and BuSY to be clear
    jr NZ,ide_wait_drq2
    pop af
    scf                     ;set carry flag on success
    ret

; load the IDE status register and if there is an error noted,
; then load the IDE error register to provide details.

; Carry is set on no error.

.ide_test_error
    push af
    ld a,__IO_PIO_IDE_ALT_STATUS    ;select status register
    call ide_read_byte      ;get status in A
    bit 0,a                 ;test ERR bit
    jr Z,ide_test_success
    bit 5,a
    jr NZ,ide_test2         ;test write error bit

    ld a,__IO_PIO_IDE_ERROR ;select error register
    call ide_read_byte      ;get error register in a
.ide_test2
    inc sp                  ;pop old af
    inc sp
    or a                    ;make carry flag zero = error!
    ret                     ;if a = 0, ide write busy timed out

.ide_test_success
    pop af
    scf                     ;set carry flag on success
    ret

;------------------------------------------------------------------------------
; Routines that talk with the IDE drive, these should not be called by
; the main program.

; read a sector
; LBA specified by the 4 bytes in BCDE
; the address of the buffer to fill is in HL
; HL is left incremented by 512 bytes

; return carry on success, no carry for an error

PUBLIC ide_read_sector

.ide_read_sector
    push af
    push bc
    push de
    call ide_wait_ready     ;make sure drive is ready
    jr NC,ide_x_sector_error
    call ide_setup_lba      ;tell it which sector we want in BCDE
    ld e,$1
    ld a,__IO_PIO_IDE_SEC_CNT
    call ide_write_byte     ;set sector count to 1
    ld e,__IDE_CMD_READ
    ld a,__IO_PIO_IDE_COMMAND
    call ide_write_byte     ;ask the drive to read it
    call ide_wait_ready     ;make sure drive is ready to proceed
    jr NC,ide_x_sector_error
    call ide_wait_drq       ;wait until it's got the data
    jr NC,ide_x_sector_error
    call ide_read_block     ;grab the data into (HL++)

.ide_x_sector_ok
    pop de
    pop bc
    pop af
    scf                     ;carry = 1 on return = operation ok
    ret

.ide_x_sector_error
    pop de
    pop bc
    pop af
    jr ide_test_error       ;carry = 0 on return = operation failed

;------------------------------------------------------------------------------
; Routines that talk with the IDE drive, these should not be called by
; the main program.

; write a sector
; specified by the 4 bytes in BCDE
; the address of the origin buffer is in HL
; HL is left incremented by 512 bytes

; return carry on success, no carry for an error

PUBLIC ide_write_sector

.ide_write_sector
    push af
    push bc
    push de
    call ide_wait_ready     ;make sure drive is ready
    jr NC,ide_x_sector_error
    call ide_setup_lba      ;tell it which sector we want in BCDE
    ld e,$1
    ld a,__IO_PIO_IDE_SEC_CNT
    call ide_write_byte     ;set sector count to 1
    ld e,__IDE_CMD_WRITE
    ld a,__IO_PIO_IDE_COMMAND
    call ide_write_byte     ;instruct drive to write a sector
    call ide_wait_ready     ;make sure drive is ready to proceed
    jr NC,ide_x_sector_error
    call ide_wait_drq       ;wait until it wants the data
    jr NC,ide_x_sector_error
    call ide_write_block    ;send the data to the drive from (HL++)
    call ide_wait_ready
    jr NC,ide_x_sector_error
;   ld e,__IDE_CMD_CACHE_FLUSH
;   ld a,__IO_PIO_IDE_COMMAND
;   call ide_write_byte     ;tell drive to flush its hardware cache
;   call ide_wait_ready     ;wait until the write is complete
;   jr NC,ide_x_sector_error
    jr ide_x_sector_ok     ;carry = 1 on return = operation ok

;==============================================================================
;       DEBUGGING SUBROUTINES
;

PUBLIC asm_phexwd, asm_phex
PUBLIC asm_pchar, asm_pstring

PUBLIC asm_rhex, asm_rchar

PUBLIC asm_delay

EXTERN _bios_ioByte

;==============================================================================
;       OUTPUT SUBROUTINES
;

    ; print string from location in DE to asci0/1, modifies AF, DE, & HL
.asm_pstring
    ld a,(de)           ; Get character from DE address
    or a                ; Is it $00 ?
    ret Z               ; Then return on terminator
    ld l,a
    call asm_pchar      ; Print it
    inc de              ; Point to next character
    jr asm_pstring      ; Continue until $00

    ; print contents of HL as 16 bit number in ASCII HEX, modifies AF & HL
.asm_phexwd
    push hl
    ld l,h              ; high byte to L
    call asm_phex
    pop hl              ; recover HL, for low byte in L
    call asm_phex
    ret

    ; print contents of L as 8 bit number in ASCII HEX to asci0/1, modifies AF & HL
.asm_phex
    ld a,l              ; asm_asci0_putc / asm_asci1_putc modifies AF, HL
    push af
    rrca
    rrca
    rrca
    rrca
    call  asm_phex_conv
    pop af
.asm_phex_conv
    and  $0F
    add  a,$90
    daa
    adc  a,$40
    daa
    ld l,a

    ; print contents of L to asci0/1, modifies AF & HL
.asm_pchar
    ld a,(_bios_ioByte)     ; get I/O bit
    rrca                    ; put it in Carry
    jp NC,asm_asci1_putc    ; TTY=0 (asci1)
    jp asm_asci0_putc       ; CRT=1 (asci0)

;==============================================================================
;       INPUT SUBROUTINES
;

.asm_rhex                   ; Returns byte in A, modifies HL
    call asm_rhex_nibble    ; read the first nibble
    rlca                    ; shift it left by 4 bits
    rlca
    rlca
    rlca
    ld h,a                  ; temporarily store the first nibble in H
    call asm_rhex_nibble    ; get the second (low) nibble
    or h                    ; assemble two nibbles into one byte in A
    ret                     ; return the byte read in A

.asm_rhex_nibble
    call asm_rchar          ; Rx byte in L (A = byte Rx too) SCF if char read
    jr NC,asm_rhex_nibble   ; keep trying if no characters
    sub '0'
    cp 10
    ret C                   ; if A<10 just return
    sub 7                   ; else subtract 'A'-'0' (17) and add 10
    ret

.asm_rchar                  ; Rx byte in L (A = byte Rx too) SCF if char read
    ld a,(_bios_ioByte)     ; get I/O bit
    rrca                    ; put it in Carry
    jp NC,asm_asci1_getc    ; TTY=0 (asci1)
    jp asm_asci0_getc       ; CRT=1 (asci0)

;==============================================================================
;       DELAY SUBROUTINES
;

.asm_delay                  ; delay to allow printing before returning
    push bc
    ld bc,$0000
.asm_delay_loop
    ex (sp),hl              ; 19 cycles
    ex (sp),hl              ; 19 cycles
    djnz asm_delay_loop     ; 10 cycles
    dec c
    jr NZ,asm_delay_loop
    pop bc
    ret

;==============================================================================
;       BIOS STACK CANARY
;

._bios_stack_canary
    defb $AA
    defb $55

PUBLIC _common1_phase_end
._common1_phase_end         ; just to make sure there's enough space

DEPHASE

