;--------------------------------------------------------------------------------------------------------
;-
;- Name:            rfs_bank4.asm
;- Created:         July 2019
;- Author(s):       Philip Smart
;- Description:     Sharp MZ series Rom Filing System.
;-                  This assembly language program is written to utilise the banked flashroms added with
;-                  the MZ-80A RFS hardware upgrade.
;-
;- Credits:         
;- Copyright:       (c) 2018-2026 Philip Smart <philip.smart@net2net.org>
;-
;- History:         July 2019 - Merged 2 utilities to create this compilation.
;-                  May 2020  - Bank switch changes with release of v2 pcb with coded latch. The coded
;-                              latch adds additional instruction overhead as the control latches share
;-                              the same address space as the Flash RAMS thus the extra hardware to
;-                              only enable the control registers if a fixed number of reads is made
;-                              into the upper 8 bytes which normally wouldnt occur. Caveat - ensure
;-                              that no loop instruction is ever placed into EFF8H - EFFFH.
;-                  Aug 2023  - Updates to make RFS run under the SFD700 Floppy Disk Interface board.
;-                              UROM remains the same, a 2K paged ROM, MROM is located at F000 when
;-                              RFS is built for the SFD700.
;-
;--------------------------------------------------------------------------------------------------------
;- This source file is free software: you can redistribute it and-or modify
;- it under the terms of the GNU General Public License as published
;- by the Free Software Foundation, either version 3 of the License, or
;- (at your option) any later version.
;-
;- This source file is distributed in the hope that it will be useful,
;- but WITHOUT ANY WARRANTY; without even the implied warranty of
;- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;- GNU General Public License for more details.
;-
;- You should have received a copy of the GNU General Public License
;- along with this program.  If not, see <http://www.gnu.org/licenses/>.
;--------------------------------------------------------------------------------------------------------

            IF BUILD_SFD700 = 1
             ORG    0E000H
             ALIGN  0E300H
             DB     "BANK4"
             ALIGN  UROMADDR
            ENDIF

            ;===========================================================
            ;
            ; USER ROM BANK 4 - CMT Controller utilities.
            ;
            ;===========================================================
            ORG     UROMADDR

            ;--------------------------------
            ; Common code spanning all banks.
            ;--------------------------------
            NOP
            HWSELROM2                                                         ; Select the first ROM page.
            ;
            ; No mans land... this should have switched to Bank 0 and at this point there is a jump to 00000H.
            JP      00000H                                                   ; This is for safety!!


            ;------------------------------------------------------------------------------------------
            ; Bank switching code, allows a call to code in another bank.
            ; This code is duplicated in each bank such that a bank switch doesnt affect logic flow.
            ;------------------------------------------------------------------------------------------            
            ALIGN_NOPS UROMBSTBL
            ;
BKSW4to0:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK0                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to1:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK1                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to2:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK2                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to3:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK3                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to4:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK4                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to5:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK5                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to6:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK6                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to7:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK7                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to8:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK8                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to9:   PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK9                                              ; Required bank to call.
            JR      BKSW4_0
BKSW4to10:  PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK10                                             ; Required bank to call.
            JR      BKSW4_0
BKSW4to11:  PUSH    AF
            LD      A, ROMBANK4                                              ; Calling bank (ie. us).
            PUSH    AF
            LD      A, ROMBANK11                                             ; Required bank to call.
            ;
BKSW4_0:    PUSH    HL                                                       ; Place function to call on stack
            LD      HL, BKSWRET4                                             ; Place bank switchers return address on stack.
            EX      (SP),HL
            LD      (TMPSTACKP),SP                                           ; Save the stack pointer as some old code corrupts it.
            BNKSWSEL
            JP      (HL)                                                     ; Jump to required function.
BKSWRET4:   POP     AF                                                       ; Get bank which called us.
            BNKSWSELRET
            POP     AF
            RET  


           ;-------------------------------------------------------------------------------
           ; START OF CMT CONTROLLER FUNCTIONALITY
           ;-------------------------------------------------------------------------------

            ; CMT Utility to Load a program from tape.
            ;
            ; Three entry points:
            ; LOADTAPE = Load the first program shifting to lo memory if required and execute.
            ; LOADTAPENX = Load the first program and return without executing.
            ; LOADTAPECP = Load the first program to address 0x1200 and return.
            ;
LOADTAPECP: LD      A,0FFH
            LD      (CMTAUTOEXEC),A
            JR      LOADTAPE2
LOADTAPENX: LD      A,0FFH
            JR      LOADTAPE1
LOADTAPE:   LD      A,000H
LOADTAPE1:  LD      (CMTAUTOEXEC),A
            XOR     A
LOADTAPE2:  LD      (CMTCOPY),A                                          ; Set cmt copy mode, 0xFF if we are copying.
            LD      A,0FFH                                               ; If called interbank, set a result code in memory to detect success.
            LD      (RESULT),A
            CALL    ?RDI
            JP      C,?ERX2
            LDDE    MSGLOAD                                              ; 'LOADING '
            LD      BC,NAME
            LD      HL,PRINTMSG
            CALL    BKSW4to6
            XOR     A
            LD      (CMTLOLOAD),A

            LD      HL,(DTADR)                                           ; Common code, store load address in case we shift or manipulate loading.
            LD      (DTADRSTORE),HL

            LD      A,(CMTCOPY)                                          ; If were copying we always load at 0x1200.
            OR      A
            JR      Z,LOADTAPE3
            LD      HL,01200H
            LD      (DTADR),HL

LOADTAPE3:  LD      HL,(DTADR)                                           ; If were loading and the load address is below 0x1200, shift it to 0x1200 to load then move into correct location.
            LD      A,H
            OR      L
            JR      NZ,LOADTAPE4
            LD      A,0FFh
            LD      (CMTLOLOAD),A
            LD      HL,01200h
            LD      (DTADR),HL
LOADTAPE4:  CALL    ?RDD
            JP      C,?ERX2
            LD      HL,(DTADRSTORE)                                      ; Restore the original load address into the CMT header.
            LD      (DTADR),HL
            LD      A,(CMTCOPY)
            OR      A
            JR      NZ,LOADTAPE6
LOADTAPE5:  LD      A,(CMTAUTOEXEC)                                      ; Get back the auto execute flag.
            OR      A
            JR      NZ,LOADTAPE6                                         ; Dont execute..
            LD      A,(CMTLOLOAD)
            CP      0FFh
            JR      Z,LOADTAPELM                                         ; Execute at low memory?
            LD      BC,00100h
            LD      HL,(EXADR)
            JP      (HL)
LOADTAPELM: LD      A,(MEMSW)                                            ; Perform memory switch, mapping out ROM from $0000 to $C000
            LD      HL,01200h                                            ; Shift the program down to RAM at $0000
            LD      DE,00000h
            LD      BC,(SIZE)
            LDIR
            LD      BC,00100h
            LD      HL,(EXADR)                                           ; Fetch exec address and run.
            JP      (HL)
LOADTAPE6:  LDDE    MSGCMTDATA
            PUSH    HL                                                   ; Load address as parameter 2.
            LD      HL,(EXADR)
            PUSH    HL                                                   ; Execution address as parameter 1.
            LD      BC,(SIZE)                                            ; Size as BC parameter.
            LD      HL,PRINTMSG
            CALL    BKSW4to6
            POP     BC
            POP     BC                                                   ; Waste parameters.
            XOR     A                                                    ; Success.
            LD      (RESULT),A
            RET


            ; SA1510 Routine to write a tape header. Copied into the RFS and modified to merge better
            ; with the RFS interface.
            ;
CMTWRI:    ;DI      
            PUSH    DE
            PUSH    BC
            PUSH    HL
            LD      D,0D7H
            LD      E,0CCH
            LD      HL,IBUFE
            LD      BC,00080H
            CALL    CKSUM
            IF      BUILD_ROMDISK+BUILD_PICOZ80 = 1
              CALL  MOTOR80A 
            ENDIF
            IF      BUILD_SFD700 = 1
              IN    A,(SFD700_MODE)
              OR    A
              JR    Z,CMTWRI80A
              CALL  MOTOR700
              JR    CMTWRI0
CMTWRI80A:    CALL  MOTOR80A
            ENDIF
CMTWRI0:    JR      C,CMTWRI2                 
            LD      A,E
            CP      0CCH
            JR      NZ,CMTWRI1                
            PUSH    HL
            PUSH    DE
            PUSH    BC
            LDDE    MSGCMTWRITE
            LD      BC,NAME
            LD      HL,PRINTMSG
            CALL    BKSW4to6
            POP     BC
            POP     DE
            POP     HL
CMTWRI1:    CALL    GAP
            IF      BUILD_ROMDISK+BUILD_PICOZ80 = 1
              CALL  WTAPE80A
            ENDIF
            IF      BUILD_SFD700 = 1
              IN    A,(SFD700_MODE)
              OR    A
              JR    Z,CMTWRI80A2
              CALL  WTAPE700
              JR    CMTWRI2
CMTWRI80A2:   CALL  WTAPE80A
            ENDIF
CMTWRI2:    POP     HL
            POP     BC
            POP     DE
            CALL    MSTOP
            PUSH    AF
            LD      A,(TIMFG)
            CP      0F0H
            JR      NZ,CMTWRI3                
           ;EI      
CMTWRI3:    POP     AF
            RET     


            ; Method to save an application stored in memory to a cassette in the CMT. The start, size and execution address are either given in BUFER via the 
            ; command line and the a filename is prompted for and read, or alternatively all the data is passed into the function already set in the CMT header.
            ; The tape is then opened and the header + data are written out.
            ;
SAVECMT:    LD      A,0FFH                                               ; Set SDCOPY to indicate this is a copy command and not a command line save.
            JR      SAVEX1
            ;
            ; Normal entry point, the cmdline contains XXXXYYYYZZZZ where XXXX=start, YYYY=size, ZZZZ=exec addr. A filenname is prompted for and read.
            ; The data is stored in the CMT header prior to writing out the header and data..
            ;
SAVEX:      LD      HL,GETCMTPARM                                        ; Get the CMT parameters.
            CALL    BKSW4to3
            LD      A,C
            OR      A
            RET     NZ                                                   ; Exit if an error occurred.

            XOR     A
SAVEX1:     LD      (SDCOPY),A
            LD      A,0FFH
            LD      (RESULT),A                                           ; For interbank calls, pass result via a memory variable. Assume failure unless updated.
            LD      A,OBJCD                                              ; Set attribute: OBJ
            LD      (ATRB),A
            CALL    CMTWRI                                               ; Commence header write. Header doesnt need updating for header write.
?ERX1:      JP      C,?ERX2

            LD      A,(SDCOPY)
            OR      A
            JR      Z,SAVEX2
            LD      DE,(DTADR)
            LD      A,D                                                  ; If copying and address is below 1000H, then data is held at 1200H so update header for write.
            CP      001H
            JR      NC,SAVEX2
            LD      DE,01200H
            LD      (DTADR),DE
SAVEX2:     CALL    ?WRD                                                 ; data
            JR      C,?ERX1
            LDDE    MSGSAVEOK                                            ; 'OK!'
            LD      HL,PRINTMSG
            CALL    BKSW4to6
            LD      A,0                                                  ; Success.
            LD      (RESULT),A
            RET
?ERX2:      CP      002h
            JR      NZ,?ERX3
            LD      (RESULT),A                                           ; Set break key pressed code.
            RET     Z
?ERX3:      LDDE    MSGE1                                                ; 'CHECK SUM ER.'
            LD      HL,PRINTMSG
            CALL    BKSW4to6
            RET


            ; Method to verify that a tape write occurred free of error. After a write, the tape is read and compared with the memory that created it.
            ;
VRFYX:      CALL    ?VRFY
            JP      C,?ERX2
            LDDE    MSGOK                                                ; 'OK!'
            LD      HL,PRINTMSG
            CALL    BKSW4to6
            RET

            ; Method to toggle the audible key press sound, ie a beep when a key is pressed.
            ;
SGX:        LD      A,(SWRK)
            RRA
            CCF
            RLA
            LD      (SWRK),A
            RET

            ;-------------------------------------------------------------------------------
            ; END OF CMT CONTROLLER FUNCTIONALITY
            ;-------------------------------------------------------------------------------

           ;-------------------------------------------------------------------------------
           ; START OF MEMORY TEST FUNCTIONALITY
           ;-------------------------------------------------------------------------------

MEMTEST:    LD      B,240       ; Number of loops
LOOP:       LD      HL,MEMSTART ; Start of checked memory,
            LD      D,0CFh      ; End memory check CF00
LOOP1:      LD      A,000h
            CP      L
            JR      NZ,LOOP1b
            CALL    PRTHL       ; Print HL as 4digit hex.
            LD      A,0C4h      ; Move cursor left.
            LD      E,004h      ; 4 times.
LOOP1a:     CALL    DPCT
            DEC     E
            JR      NZ,LOOP1a
LOOP1b:     INC     HL
            LD      A,H
            CP      D           ; Have we reached end of memory.
            JR      Z,LOOP3     ; Yes, exit.
            LD      A,(HL)      ; Read memory location under test, ie. 0.
            CPL                 ; Subtract, ie. FF - A, ie FF - 0 = FF.
            LD      (HL),A      ; Write it back, ie. FF.
            SUB     (HL)        ; Subtract written memory value from A, ie. should be 0.
            JR      NZ,LOOP2    ; Not zero, we have an error.
            LD      A,(HL)      ; Reread memory location, ie. FF
            CPL                 ; Subtract FF - FF
            LD      (HL),A      ; Write 0
            SUB     (HL)        ; Subtract 0
            JR      Z,LOOP1     ; Loop if the same, ie. 0
LOOP2:      LD      A,16h
            CALL    PRNT        ; Print A
            CALL    PRTHX       ; Print HL as 4 digit hex.
            CALL    PRNTS       ; Print space.
            XOR     A
            LD      (HL),A
            LD      A,(HL)      ; Get into A the failing bits.
            CALL    PRTHX       ; Print A as 2 digit hex.
            CALL    PRNTS       ; Print space.
            LD      A,0FFh      ; Repeat but first load FF into memory
            LD      (HL),A
            LD      A,(HL)
            CALL    PRTHX       ; Print A as 2 digit hex.
            NOP
            JR      LOOP4

LOOP3:      CALL    PRTHL
            LD      DE,OKCHECK
            CALL    MSG          ; Print check message in DE
            LD      A,B          ; Print loop count.
            CALL    PRTHX
            LD      DE,OKMSG
            CALL    MSG          ; Print ok message in DE
            CALL    NL
            DEC     B
            JR      NZ,LOOP
            LD      DE,DONEMSG
            CALL    MSG          ; Print check message in DE
            JP      ST1X

LOOP4:      LD      B,09h
            CALL    PRNTS        ; Print space.
            XOR     A            ; Zero A
            SCF                  ; Set Carry
LOOP5:      PUSH    AF           ; Store A and Flags
            LD      (HL),A       ; Store 0 to bad location.
            LD      A,(HL)       ; Read back
            CALL    PRTHX        ; Print A as 2 digit hex.
            CALL    PRNTS        ; Print space
            POP     AF           ; Get back A (ie. 0 + C)
            RLA                  ; Rotate left A. Bit LSB becomes Carry (ie. 1 first instance), Carry becomes MSB
            DJNZ    LOOP5        ; Loop if not zero, ie. print out all bit locations written and read to memory to locate bad bit.
            XOR     A            ; Zero A, clears flags.
            LD      A,80h
            LD      B,08h
LOOP6:      PUSH    AF           ; Repeat above but AND memory location with original A (ie. 80) 
            LD      C,A          ; Basically walk through all the bits to find which one is stuck.
            LD      (HL),A
            LD      A,(HL)
            AND     C
            NOP
            JR      Z,LOOP8      ; If zero then print out the bit number
            NOP
            NOP
            LD      A,C
            CPL
            LD      (HL),A
            LD      A,(HL)
            AND     C
            JR      NZ,LOOP8     ; As above, if the compliment doesnt yield zero, print out the bit number.
LOOP7:      POP     AF
            RRCA
            NOP
            DJNZ    LOOP6
            JP      ST1X

LOOP8:      CALL    LETNL        ; New line.
            LD      DE,BITMSG    ; BIT message
            CALL    MSG          ; Print message in DE
            LD      A,B
            DEC     A
            CALL    PRTHX        ; Print A as 2 digit hex, ie. BIT number.
            CALL    LETNL        ; New line
            LD      DE,BANKMSG   ; BANK message
            CALL    MSG          ; Print message in DE
            LD      A,H
            CP      50h          ; 'P'
            JR      NC,LOOP9     ; Work out bank number, 1, 2 or 3.
            LD      A,01h
            JR      LOOP11

LOOP9:      CP      90h
            JR      NC,LOOP10
            LD      A,02h
            JR      LOOP11

LOOP10:     LD      A,03h
LOOP11:     CALL    PRTHX        ; Print A as 2 digit hex, ie. BANK number.
            JR      LOOP7

DLY1S:      PUSH    AF
            PUSH    BC
            LD      C,10
L0324:      CALL    DLY12
            DEC     C
            JR      NZ,L0324
            POP     BC
            POP     AF
            RET
            
           ;-------------------------------------------------------------------------------
           ; END OF MEMORY TEST FUNCTIONALITY
           ;-------------------------------------------------------------------------------

           ;-------------------------------------------------------------------------------
           ; START OF TIMER TEST FUNCTIONALITY
           ;-------------------------------------------------------------------------------

            ; Test the 8253 Timer, configure it as per the monitor and display the read back values.
TIMERTST:   CALL    NL
            LD      DE,MSG_TIMERTST
            CALL    MSG
            CALL    NL
            LD      DE,MSG_TIMERVAL
            CALL    MSG
            LD      A,01h
            LD      DE,8000h
            CALL    TIMERTST1
NDE:        JP      NDE
            JP      ST1X
TIMERTST1: ;DI      
            PUSH    BC
            PUSH    DE
            PUSH    HL
            LD      (AMPM),A
            LD      A,0F0H
            LD      (TIMFG),A
ABCD:       LD      HL,0A8C0H
            XOR     A
            SBC     HL,DE
            PUSH    HL
            INC     HL
            EX      DE,HL

            LD      HL,CONTF    ; Control Register
            LD      (HL),0B0H   ; 10110000 Control Counter 2 10, Write 2 bytes 11, 000 Interrupt on Terminal Count, 0 16 bit binary
            LD      (HL),074H   ; 01110100 Control Counter 1 01, Write 2 bytes 11, 010 Rate Generator, 0 16 bit binary
            LD      (HL),030H   ; 00110100 Control Counter 1 01, Write 2 bytes 11, 010 interrupt on Terminal Count, 0 16 bit binary

            LD      HL,CONT2    ; Counter 2
            LD      (HL),E
            LD      (HL),D

            LD      HL,CONT1    ; Counter 1
            LD      (HL),00AH
            LD      (HL),000H

            LD      HL,CONT0    ; Counter 0
            LD      (HL),00CH
            LD      (HL),0C0H

;            LD      HL,CONT2    ; Counter 2
;            LD      C,(HL)
;            LD      A,(HL)
;            CP      D
;            JP      NZ,L0323H                
;            LD      A,C
;            CP      E
;            JP      Z,CDEF                
            ;

L0323H:     PUSH    AF
            PUSH    BC
            PUSH    DE
            PUSH    HL
            ;
            LD      HL,CONTF    ; Control Register
            LD      (HL),080H
            LD      HL,CONT2    ; Counter 2
            LD      C,(HL)
            LD      A,(HL)
            CALL    PRTHX
            LD      A,C
            CALL    PRTHX
            ;
            CALL    PRNTS
            ;CALL    DLY1S
            ;
            LD      HL,CONTF    ; Control Register
            LD      (HL),040H
            LD      HL,CONT1    ; Counter 1
            LD      C,(HL)
            LD      A,(HL)
            CALL    PRTHX
            LD      A,C
            CALL    PRTHX
            ;
            CALL    PRNTS
            ;CALL    DLY1S
            ;
            LD      HL,CONTF    ; Control Register
            LD      (HL),000H
            LD      HL,CONT0    ; Counter 0
            LD      C,(HL)
            LD      A,(HL)
            CALL    PRTHX
            LD      A,C
            CALL    PRTHX
            ;
            ;CALL    DLY1S
            ;
            LD      A,0C4h      ; Move cursor left.
            LD      E,0Eh      ; 4 times.
L0330:      CALL    DPCT
            DEC     E
            JR      NZ,L0330
            ;
;            LD      C,20
;L0324:      CALL    DLY12
;            DEC     C
;            JR      NZ,L0324
            ;
            POP     HL
            POP     DE
            POP     BC
            POP     AF
            ;
            LD      HL,CONT2    ; Counter 2
            LD      C,(HL)
            LD      A,(HL)
            CP      D
            JP      NZ,L0323H                
            LD      A,C
            CP      E
            JP      NZ,L0323H                
            ;
            ;
            PUSH    AF
            PUSH    BC
            PUSH    DE
            PUSH    HL
            CALL    NL
            CALL    NL
            CALL    NL
            LD      DE,MSG_TIMERVAL2
            CALL    MSG
            POP     HL
            POP     DE
            POP     BC
            POP     AF

            ;
CDEF:       POP     DE
            LD      HL,CONT1
            LD      (HL),00CH
            LD      (HL),07BH
            INC     HL

L0336H:     PUSH    AF
            PUSH    BC
            PUSH    DE
            PUSH    HL
            ;
            LD      HL,CONTF    ; Control Register
            LD      (HL),080H
            LD      HL,CONT2    ; Counter 2
            LD      C,(HL)
            LD      A,(HL)
            CALL    PRTHX
            LD      A,C
            CALL    PRTHX
            ;
            CALL    PRNTS
            CALL    DLY1S
            ;
            LD      HL,CONTF    ; Control Register
            LD      (HL),040H
            LD      HL,CONT1    ; Counter 1
            LD      C,(HL)
            LD      A,(HL)
            CALL    PRTHX
            LD      A,C
            CALL    PRTHX
            ;
            CALL    PRNTS
            CALL    DLY1S
            ;
            LD      HL,CONTF    ; Control Register
            LD      (HL),000H
            LD      HL,CONT0    ; Counter 0
            LD      C,(HL)
            LD      A,(HL)
            CALL    PRTHX
            LD      A,C
            CALL    PRTHX
            ;
            CALL    DLY1S
            ;
            LD      A,0C4h      ; Move cursor left.
            LD      E,0Eh      ; 4 times.
L0340:      CALL    DPCT
            DEC     E
            JR      NZ,L0340
            ;
            POP     HL
            POP     DE
            POP     BC
            POP     AF

            LD      HL,CONT2    ; Counter 2
            LD      C,(HL)
            LD      A,(HL)
            CP      D
            JR      NZ,L0336H                
            LD      A,C
            CP      E
            JR      NZ,L0336H                
            CALL    NL
            LD      DE,MSG_TIMERVAL3
            CALL    MSG
            POP     HL
            POP     DE
            POP     BC
           ;EI      
            RET   
            ;-------------------------------------------------------------------------------
            ; END OF TIMER TEST FUNCTIONALITY
            ;-------------------------------------------------------------------------------
  
            ;--------------------------------------
            ;
            ; Message table
            ;
            ;--------------------------------------             
OKCHECK:    DB      ", CHECK: ", 0Dh
OKMSG:      DB      " OK.", 0Dh
DONEMSG:    DB      11h
            DB      "RAM TEST COMPLETE.", 0Dh
           
BITMSG:     DB      " BIT:  ", 0Dh
BANKMSG:    DB      " BANK: ", 0Dh
MSG_TIMERTST:
            DB      "8253 TIMER TEST", 0Dh, 00h
MSG_TIMERVAL:
            DB      "READ VALUE 1: ", 0Dh, 00h
MSG_TIMERVAL2:
            DB      "READ VALUE 2: ", 0Dh, 00h
MSG_TIMERVAL3:
            DB      "READ DONE.", 0Dh, 00h

            IF BUILD_PICOZ80 = 1
            ;-------------------------------------------------------------------------------
            ; QD BOOT / QL LIST - Quick Disk boot and directory listing (picoZ80 only).
            ;
            ; QD  - Load & execute first file from QD drive via SIO ports F4-F7H.
            ; QL  - List directory of QD disk.
            ;
            ; Works with both PHYSICAL QD drives (MZ-1E14) and VIRTUAL QD drives
            ; (picoZ80 QDDrive.c emulator) since both use the same Z80 SIO/2 protocol.
            ;
            ; Based on disassembly of the MZ-1E14 QD Interface ROM.
            ; SIO port mapping: F4=ChA Data, F6=ChA Ctrl, F7=ChB Ctrl
            ; Protocol: SIO sync mode with sync chars 16H 16H, address mark A5H,
            ;           block type + size(2) + data + CRC(3 trailing bytes).
            ;           Block type 00=header (64-byte MZF header), 01/05=data.
            ;-------------------------------------------------------------------------------

            ;---------------------------------------------------------------
            ; QD command entry: boot first file from QD.
            ;---------------------------------------------------------------
QDBOOT:     XOR     A                                                    ; 0 = boot (auto-execute)
            JR      QDGO

            ;---------------------------------------------------------------
            ; QL command entry: list QD directory.
            ;---------------------------------------------------------------
QDLIST:     LD      A,001H                                               ; 1 = list directory

QDGO:       LD      (RESULT),A                                           ; Store mode: 0=boot, 1=list

            ; --- Initialize SIO and start QD motor ---
            CALL    QDINIT_STATE                                         ; Zero out QD work area
            CALL    QDSTART_MOTOR                                        ; SIO init + motor on + wait DCD

            LD      A,(RESULT)
            OR      A
            JP      NZ,QDGO_LIST                                         ; Mode 1 = directory list

            ; --- QD BOOT: Read first header, then first data block ---
            CALL    QDREAD_HDR                                           ; Read first header block into MZF area
            JP      C,QDERR_EXIT                                         ; Error reading header

            ; Print "Loading <filename>"
            LDDE    MSGLOAD+1                                            ; Skip leading CR
            LD      BC,NAME
            LD      HL,PRINTMSG
            CALL    BKSW4to6

            ; Read the data block.
            ; QD header field convention (differs from CMT):
            ;   DTADR = data size limit, EXADR = load address.
            ; If EXADR=0 and COMNT=0, relocate: load to 1200H, copy to 0000H after.
            LD      HL,(EXADR)
            LD      A,H
            OR      L
            JR      NZ,QDBOOT1                                           ; EXADR non-zero, use as load addr
            LD      HL,(COMNT)
            LD      A,H
            OR      L
            JR      NZ,QDBOOT1A                                          ; COMNT non-zero, load to EXADR (=0)
            ; Both zero: load to 1200H, will relocate to 0000H after
            LD      A,0FFH
            LD      (QDRELOC),A
            LD      HL,01200H
            JR      QDBOOT1
QDBOOT1A:   LD      HL,(EXADR)                                           ; Re-read EXADR (matches MZ-1E14 ROM)
QDBOOT1:    LD      (QDLOADADDR),HL
            LD      HL,(DTADR)
            LD      (QDBLKSIZE),HL                                       ; Max bytes to read (QD: DTADR=size)

            ; Read data block using QD SVC (mode 3, sub-mode 1 = data block)
            LD      HL,00003H
            LD      (QDCMD),HL                                           ; QDCMD=3 (read block), QDSUBCMD=0 (low byte)
            LD      A,001H
            LD      (QDSUBCMD),A                                         ; Sub-mode 1 = data block (bit 0 = 1)
            CALL    QDSVC_MAIN                                           ; Read data block with retry
            JP      C,QDERR_EXIT                                         ; Error reading data

            ; Motor off
            CALL    QDMOTOR_OFF

            ; Display loaded file info (QD: DTADR=size, EXADR=load addr)
            LDDE    MSGCMTDATA
            LD      HL,(QDLOADADDR)
            PUSH    HL                                                   ; Load address for 0FEH
            PUSH    HL                                                   ; Exec address for 0FFH (same for QD)
            LD      BC,(DTADR)                                           ; Size for 0FBH (QD: DTADR=size)
            LD      HL,PRINTMSG
            CALL    BKSW4to6
            POP     BC
            POP     BC

            ; Check if OBJ type for auto-execution
            LD      A,(ATRB)
            CP      OBJCD
            RET     NZ                                                   ; Not OBJ, just return

            ; Check relocation flag
            LD      A,(QDRELOC)
            CP      0FFH
            JR      Z,QDBOOT_RELOC

            ; Execute from load address (EXADR in QD convention)
            LD      BC,00300H                                            ; B=3 → $QD default device + auto-run
            LD      HL,(QDLOADADDR)
            JP      (HL)

QDBOOT_RELOC:
            ; Relocate from 1200H to 0000H and execute
            OUT     (MMIO0),A                                            ; Page out ROM at E800H
            LD      HL,01200H
            LD      DE,00000H
            LD      BC,(DTADR)                                           ; QD: DTADR = data size
            LDIR
            LD      BC,00300H                                            ; B=3 → $QD default device + auto-run
            JP      00000H

            ; --- QD DIRECTORY LIST ---
QDGO_LIST:  CALL    QDREAD_DIR                                           ; Read all headers, display directory
            ; Motor off (already done inside QDREAD_DIR on error/completion)

QDERR_EXIT: ; Common error/return handler — motor off
            PUSH    AF                                                   ; Save error code
            CALL    QDMOTOR_OFF
            POP     AF
            RET     NC                                                   ; No error, just return

            ; Display error message based on error code in A
            LDDE    MSGQDERR                                             ; Default: "QD: Read error"
            CP      028H
            JR      Z,QDERR_SHOW                                         ; End of tape (file not found)
            CP      032H
            JR      NZ,QDERR_SHOW2
            LDDE    MSGQDERR                                             ; Not ready
            JR      QDERR_SHOW
QDERR_SHOW2:
            CP      036H
            JR      NZ,QDERR_SHOW
            LDDE    MSGQDFMT                                             ; Unformat / bad format
QDERR_SHOW: LD      HL,PRINTMSG
            CALL    BKSW4to6
            RET

            ;---------------------------------------------------------------
            ; QDINIT_STATE: Initialize QD work area variables to zero.
            ;---------------------------------------------------------------
QDINIT_STATE:
            XOR     A
            LD      (QDMOTOR),A
            LD      (QDWORK1),A
            LD      (QDWORK2),A
            LD      (QDADDRMARK),A
            LD      (QDEXPMARK),A
            LD      (QDRELOC),A
            RET

            ;---------------------------------------------------------------
            ; QDSTART_MOTOR: Initialize SIO, turn motor on, wait for DCD.
            ; Combines QDSIO_INIT + QDINIT_FULL from MZ-1E14 ROM.
            ; Returns: CF=1 on error (not ready).
            ;---------------------------------------------------------------
QDSTART_MOTOR:
            ; Write SIO init table to Channel A control (OTIR)
            LD      HL,QDSIO_INITTBL
            LD      B,011                                                ; 11 bytes in init table
            LD      C,SIOA_CTRL
            OTIR                                                         ; Output init table to ChA ctrl

            ; Motor on: ChB WR5 = 80H (RTS = motor on)
            LD      A,005H
            LD      (QDMOTOR),A                                          ; Motor status = on
            OUT     (SIOB_CTRL),A                                        ; ChB: register pointer = WR5
            LD      A,080H
            OUT     (SIOB_CTRL),A                                        ; ChB WR5 = 80H (motor on)

            ; Wait for DCD on Channel A
QDSM_WDCD: LD      A,010H
            OUT     (SIOA_CTRL),A                                        ; ChA: reset ext/status
            IN      A,(SIOA_CTRL)                                        ; Read ChA RR0
            AND     008H                                                 ; DCD? (bit 3)
            JR      NZ,QDSM_HAVEDCD
            ; Check break key
            CALL    BRKEY
            JR      NZ,QDSM_WDCD                                        ; Keep waiting unless break
            ; Break pressed or no DCD
            SCF
            RET

QDSM_HAVEDCD:
            ; Wait for sync detection on Channel B (tape motion detected)
            LD      A,010H
            OUT     (SIOB_CTRL),A                                        ; ChB: reset ext/status
            IN      A,(SIOB_CTRL)                                        ; Read ChB RR0
            AND     008H                                                 ; DCD on ChB?
            JR      Z,QDSM_WDCD                                         ; Not yet, keep waiting

            ; Short delay for tape to stabilize
            LD      BC,000E9H
            CALL    QDDELAY

            ; Count total blocks on disk by reading FNBLK
            CALL    QDHUNT_SYNC                                          ; Enter hunt, wait sync, read type
            ; A = block type from FNBLK (should be the block count encoded)
            ; Actually the FNBLK has: sync + A5 + <block_count> + CRC
            ; After hunt, first byte read is the address mark (A5 consumed by hunt)
            ; then block type (= block count).
            ; For FNBLK the 'type' byte IS the total block count.
            LD      (QDTOTBLKS),A
            INC     A
            LD      (QDREMBLKS),A
            ; Read trailing CRC bytes
            CALL    QDREAD_TRAILING
            ; Set first-read flag
            LD      HL,QDFLAGS
            SET     3,(HL)
            XOR     A
            LD      (QDEXPMARK),A                                        ; Reset expected address mark
            OR      A                                                    ; Clear carry = success
            RET

            ;---------------------------------------------------------------
            ; QDREAD_HDR: Read first header block from QD into MZF header area.
            ; Populates: ATRB, NAME, SIZE, DTADR, EXADR from the 64-byte header.
            ; Returns: CF=1 on error.
            ;---------------------------------------------------------------
QDREAD_HDR:
            LD      HL,00003H
            LD      (QDCMD),HL                                           ; Mode 3 = read block
            XOR     A
            LD      (QDSUBCMD),A                                         ; Sub-mode 0 = header block (bit 0 = 0)
            LD      HL,ATRB                                              ; Load address = MZF header area
            LD      (QDLOADADDR),HL
            LD      HL,00040H                                            ; Max 64 bytes (MZF header)
            LD      (QDBLKSIZE),HL
            JP      QDSVC_MAIN                                           ; Read with retry, returns CF

            ;---------------------------------------------------------------
            ; QDREAD_DIR: Read all header blocks and display directory.
            ; Returns: CF=1 on error.
            ;---------------------------------------------------------------
QDREAD_DIR:
            ; Read headers into directory buffer at QDIRBUF (18 bytes per entry)
            LD      B,000H                                               ; File counter
            LD      HL,QDIRBUF                                           ; Directory buffer start

QDRD_LOOP:  LD      (QDLOADADDR),HL
            PUSH    HL
            PUSH    BC
            LD      HL,00003H
            LD      (QDCMD),HL                                           ; Mode 3 = read block
            XOR     A
            LD      (QDSUBCMD),A                                         ; Header block
            LD      HL,00040H
            LD      (QDBLKSIZE),HL                                       ; 64 bytes max
            CALL    QDSVC_MAIN                                           ; Read header
            POP     BC
            POP     HL
            JR      C,QDRD_DONE                                          ; Error or end of tape (28H)

            INC     B                                                    ; Count this file
            LD      DE,00012H                                            ; Next entry offset (18 bytes)
            ADD     HL,DE
            JR      QDRD_LOOP

QDRD_DONE:  ; Motor off
            PUSH    BC
            CALL    QDMOTOR_OFF
            POP     BC

            ; Check if we found any files
            XOR     A
            CP      B
            JR      NC,QDRD_EMPTY                                        ; No files found

            ; Display directory listing
            CALL    NL
            LD      HL,QDIRBUF
QDRD_SHOW:  ; Print filename from header at (HL)+1 (offset 1 = NAME in MZF header)
            PUSH    HL
            PUSH    BC
            INC     HL                                                   ; Point to NAME field
            EX      DE,HL
            ; Print the filename (17 chars max)
            LD      B,FNSIZE
QDRD_PNAME: LD      A,(DE)
            OR      A
            JR      Z,QDRD_PEND
            CP      CR
            JR      Z,QDRD_PEND
            CALL    PRNT                                                 ; Print one character
            INC     DE
            DJNZ    QDRD_PNAME
QDRD_PEND:  CALL    NL                                                   ; Newline after each entry
            POP     BC
            POP     HL
            ; Advance to next entry
            LD      DE,00012H
            ADD     HL,DE
            DJNZ    QDRD_SHOW

            OR      A                                                    ; Clear carry = success
            RET

QDRD_EMPTY: ; No files on disk
            OR      A
            RET

            ;---------------------------------------------------------------
            ; QDSVC_MAIN: QD block read service with retry (5 retries).
            ; Uses QDCMD/QDSUBCMD/QDLOADADDR/QDBLKSIZE.
            ; Returns: CF=1 on error, A=error code.
            ;---------------------------------------------------------------
QDSVC_MAIN: LD      A,005H                                               ; 5 retries
            LD      (QDRETRY),A
QDSVC_RETRY:
            DI
            CALL    QDREAD_DISPATCH                                      ; Attempt read
            EI
            RET     NC                                                   ; Success
            PUSH    AF
            CP      028H                                                 ; End of tape?
            JR      Z,QDSVC_RET                                          ; Yes — not retryable
            CALL    QDMOTOR_OFF
            POP     AF
            PUSH    AF
            CP      029H                                                 ; CRC/overrun error?
            JR      NZ,QDSVC_RET                                         ; No — not retryable
            ; Retry on error 29H
            LD      HL,QDRETRY
            DEC     (HL)
            JR      Z,QDSVC_EXHAUST
            POP     AF
            ; Re-init for retry
            CALL    QDSTART_MOTOR
            JR      NC,QDSVC_RETRY
            ; Motor start failed on retry — return error
            SCF
            RET
QDSVC_EXHAUST:
            XOR     A
            LD      (QDADDRMARK),A                                       ; Reset address mark counter
QDSVC_RET:  POP     AF
            RET

            ;---------------------------------------------------------------
            ; QDREAD_DISPATCH: Read dispatcher based on QDCMD.
            ; Mode 3 = read block.
            ; Returns: CF=1 on error, A=error code.
            ;---------------------------------------------------------------
QDREAD_DISPATCH:
            LD      (QDSAVESP),SP                                        ; Save SP for error recovery
            LD      A,(QDCMD)
            CP      003H
            JR      Z,QDREAD_BLOCK
            ; Other modes not needed for RFS QD/QL
            RET

            ;---------------------------------------------------------------
            ; QDREAD_BLOCK: Read one block from QD.
            ; Expects QDSUBCMD bit 0: 0=header, 1=data.
            ; Expects QDLOADADDR, QDBLKSIZE set.
            ; Returns: CF=1 on error.
            ;---------------------------------------------------------------
QDREAD_BLOCK:
            ; Ensure motor is on
            LD      A,(QDMOTOR)
            OR      A
            CALL    Z,QDSTART_MOTOR
            RET     C

            ; Find matching block
            CALL    QDREAD_FINDBLK
            RET     C                                                    ; Error finding block

            ; Read block size (2 bytes: lo, hi)
            CALL    QDREAD_BYTE
            LD      C,A                                                  ; Size low
            CALL    QDREAD_BYTE
            LD      B,A                                                  ; Size high

            ; Check block fits in buffer
            LD      HL,(QDBLKSIZE)
            SBC     HL,BC
            JP      C,QDERR_29_ENTRY                                     ; Too big

            ; Read data bytes into QDLOADADDR
            LD      HL,(QDLOADADDR)
QDRD_DATA:  CALL    QDREAD_BYTE
            LD      (HL),A
            INC     HL
            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,QDRD_DATA

            ; Read trailing CRC bytes
            CALL    QDREAD_TRAILING

            ; If data block (odd sub-mode), we're done
            LD      A,(QDSUBCMD)
            BIT     0,A
            JR      NZ,QDRD_DONE2
            ; Header block — keep motor running for next read
            OR      A                                                    ; Clear carry
            RET
QDRD_DONE2: OR      A                                                    ; Clear carry
            RET

            ;---------------------------------------------------------------
            ; QDREAD_FINDBLK: Scan blocks to find matching type.
            ; QDSUBCMD bit 0 determines: 0=header, 1=data.
            ; Returns: CF=1 on error (28H=end of tape).
            ;---------------------------------------------------------------
QDREAD_FINDBLK:
            LD      HL,QDREMBLKS
            DEC     (HL)
            JR      Z,QDRD_END                                           ; No more blocks

            ; Enter hunt mode, sync, read block type
            CALL    QDHUNT_SYNC
            LD      C,A                                                  ; Save block type

            ; Check address mark sequence
            LD      A,(QDADDRMARK)
            LD      HL,QDEXPMARK
            CP      (HL)
            JR      NZ,QDRD_SKIP                                         ; Not expected mark, skip

            INC     A
            LD      (QDADDRMARK),A                                       ; Increment address mark
            LD      (HL),A

            ; Check if block type matches what we want
            LD      A,(QDSUBCMD)
            XOR     C                                                    ; Compare type bits
            RRA                                                          ; Bit 0 to carry
            RET     NC                                                   ; Match found! (same parity)

            ; Wrong block type — skip its data
QDRD_SKIPDATA:
            CALL    QDREAD_BYTE                                          ; Size low
            LD      C,A
            CALL    QDREAD_BYTE                                          ; Size high
            LD      B,A
QDRD_SKIPLOOP:
            CALL    QDREAD_BYTE                                          ; Discard data byte
            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,QDRD_SKIPLOOP
            CALL    QDREAD_TRAILING                                      ; Read CRC
            JR      QDREAD_FINDBLK                                       ; Try next block

QDRD_SKIP:  INC     (HL)                                                 ; Adjust expected mark
            JR      QDRD_SKIPDATA                                        ; Skip this block

QDRD_END:   LD      A,028H                                               ; Error: end of tape
            SCF
            RET

            ;---------------------------------------------------------------
            ; QDHUNT_SYNC: Enter hunt mode, wait for sync, read block type.
            ; Returns: A = block type byte.
            ; On error: jumps to QDERR_xx via QDSAVESP.
            ;---------------------------------------------------------------
QDHUNT_SYNC:
            ; Re-init SIO for hunt (same as EF18 in MZ-1E14)
            LD      A,058H                                               ; WR0: Channel reset + reset IUS
            LD      B,011                                                ; 11 bytes
            LD      HL,QDSIO_INITTBL
            ; Write register + OTIR: OUT (C),A then OTIR
            LD      C,SIOA_CTRL
            OUT     (C),A                                                ; Write WR0 command
            ; Motor on via ChB
            LD      A,005H
            OUT     (SIOB_CTRL),A                                        ; ChB: register pointer = WR5
            LD      A,080H
            OUT     (SIOB_CTRL),A                                        ; ChB WR5 = 80H (motor on)
            OTIR                                                         ; Output init table to ChA ctrl

            ; Delay: longer on first read, shorter on subsequent
            LD      HL,QDFLAGS
            BIT     3,(HL)
            LD      BC,00003H                                            ; Short delay
            JR      Z,QDHS_DLY
            RES     3,(HL)                                               ; Clear first-read flag
            LD      BC,000A0H                                            ; Long delay
QDHS_DLY:   CALL    QDDELAY

            ; Set up hunt mode on Channel A
            LD      A,005H
            OUT     (SIOB_CTRL),A                                        ; ChB: WR5
            LD      A,082H
            OUT     (SIOB_CTRL),A                                        ; ChB WR5 = 82H (RTS + hunt enable?)

            LD      A,003H
            OUT     (SIOA_CTRL),A                                        ; ChA: register = WR3
            LD      A,0D3H                                               ; Rx enable, hunt, auto enables, 8-bit
            OUT     (SIOA_CTRL),A                                        ; ChA WR3 = D3H (hunt mode ON)

            ; Wait for sync detected (RR0 bit 4 = 0 means sync found)
            LD      BC,02CC0H                                            ; Timeout count
QDHS_WSYNC: LD      A,010H
            OUT     (SIOA_CTRL),A                                        ; ChA: reset ext/status
            IN      A,(SIOA_CTRL)                                        ; Read ChA RR0
            AND     010H                                                 ; Sync/hunt bit (bit 4)
            JR      Z,QDHS_FOUND                                         ; Sync found! (bit cleared)
            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,QDHS_WSYNC
            JP      QDERR_36_ENTRY                                       ; Timeout: unformat error

QDHS_FOUND: ; Sync detected — clear hunt mode
            LD      A,003H
            OUT     (SIOA_CTRL),A                                        ; ChA: WR3
            LD      A,0C3H                                               ; Rx enable, NO hunt, auto enables, 8-bit
            OUT     (SIOA_CTRL),A                                        ; ChA WR3 = C3H

            ; Wait for first Rx char available
            LD      B,09FH                                               ; Timeout
QDHS_WRX:   LD      A,010H
            OUT     (SIOA_CTRL),A                                        ; Reset ext/status
            IN      A,(SIOA_CTRL)                                        ; RR0
            AND     001H                                                 ; Rx char available?
            JR      NZ,QDHS_READY
            DEC     B
            JR      NZ,QDHS_WRX
            JP      QDERR_36_ENTRY                                       ; Timeout

QDHS_READY: ; Set address mark check mode
            LD      A,003H
            OUT     (SIOA_CTRL),A                                        ; ChA: WR3
            LD      A,0C9H                                               ; Rx enable, addr mark check, 8-bit
            OUT     (SIOA_CTRL),A

            ; Read and discard address mark byte (A5H)
            CALL    QDREAD_BYTE
            ; Read block type byte
            JP      QDREAD_BYTE                                          ; Returns A = block type

            ;---------------------------------------------------------------
            ; QDREAD_BYTE: Read one byte from QD via SIO Channel A.
            ; Returns: A = data byte read from port F4H.
            ; On error: jumps to QDERR_xx via QDSAVESP.
            ;---------------------------------------------------------------
QDREAD_BYTE:
            PUSH    BC
            LD      BC,08000H                                            ; Timeout: ~32768 iterations
QDRD_BPOLL: LD      A,010H
            OUT     (SIOA_CTRL),A                                        ; Reset ext/status
            IN      A,(SIOA_CTRL)                                        ; Read RR0
            AND     008H                                                 ; DCD? (bit 3)
            JR      Z,QDRD_BDCD                                          ; No DCD: not ready

            IN      A,(SIOA_CTRL)                                        ; Read RR0 again
            RLCA                                                         ; Bit 7 (break/abort) to carry
            JR      C,QDRD_BBRK                                          ; Break detected

            RRCA                                                         ; Restore
            RRCA                                                         ; Bit 0 → carry (Rx available)
            JR      C,QDRD_BOK                                            ; Data ready!

            DEC     BC                                                   ; Timeout counter
            LD      A,B
            OR      C
            JR      NZ,QDRD_BPOLL                                        ; Keep polling
            ; Timeout — treat as end-of-tape
            POP     BC
            JP      QDERR_28_ENTRY                                       ; Error 28H: end of tape
QDRD_BDCD:  POP     BC
            JP      QDERR_32_ENTRY                                       ; Error 32H: no DCD
QDRD_BBRK:  POP     BC
            JP      QDERR_29_ENTRY                                       ; Error 29H: break/CRC
QDRD_BOK:   POP     BC
            IN      A,(SIOA_DATA)                                        ; Read data byte from F4H
            OR      A                                                    ; Set flags (clear carry)
            RET

            ;---------------------------------------------------------------
            ; QDREAD_TRAILING: Read 3 trailing bytes (CRC), check for overrun.
            ; Returns: CF=1 on overrun error.
            ;---------------------------------------------------------------
QDREAD_TRAILING:
            LD      B,003H
QDRT_LOOP:  CALL    QDREAD_BYTE
            DJNZ    QDRT_LOOP

            ; Wait for Rx char to appear (checking for buffer clear)
QDRT_WAIT:  IN      A,(SIOA_CTRL)
            RRCA                                                         ; Bit 0 (Rx avail) to carry
            JR      NC,QDRT_WAIT

            ; Check for Rx overrun in RR1
            LD      A,001H
            OUT     (SIOA_CTRL),A                                        ; Register pointer = RR1
            IN      A,(SIOA_CTRL)                                        ; Read RR1
            AND     040H                                                 ; Rx overrun? (bit 6)
            JR      NZ,QDERR_29_ENTRY                                    ; Overrun error
            OR      A                                                    ; Clear carry
            RET

            ;---------------------------------------------------------------
            ; QDMOTOR_OFF: Turn off QD motor via SIO.
            ;---------------------------------------------------------------
QDMOTOR_OFF:
            PUSH    AF
            ; Channel A WR5: RTS off
            LD      A,005H
            OUT     (SIOA_CTRL),A                                        ; ChA: register = WR5
            LD      A,060H
            OUT     (SIOA_CTRL),A                                        ; ChA WR5 = 60H (DTR on, RTS off)
            ; Channel B WR5: motor off
            LD      A,005H
            OUT     (SIOB_CTRL),A                                        ; ChB: register = WR5
            XOR     A
            LD      (QDMOTOR),A                                          ; Motor status = off
            OUT     (SIOB_CTRL),A                                        ; ChB WR5 = 00H
            POP     AF
            RET

            ;---------------------------------------------------------------
            ; QDDELAY: Delay loop. BC = outer count, inner = 86H iterations.
            ;---------------------------------------------------------------
QDDELAY:    PUSH    AF
QDDELAY_O:  LD      A,086H
QDDELAY_I:  DEC     A
            JR      NZ,QDDELAY_I
            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,QDDELAY_O
            POP     AF
            RET

            ;---------------------------------------------------------------
            ; Error entry points: restore SP and return with carry set.
            ; These are jumped to from deep in the read routines.
            ;---------------------------------------------------------------
QDERR_28_ENTRY:                                                          ; End of tape / read timeout
            LD      A,028H
            JR      QDERR_RETURN
QDERR_29_ENTRY:                                                          ; CRC/overrun/break error
            LD      A,029H
            JR      QDERR_RETURN
QDERR_32_ENTRY:                                                          ; Not ready (no DCD)
            LD      A,032H
            JR      QDERR_RETURN
QDERR_36_ENTRY:                                                          ; Unformat (sync timeout)
            LD      A,036H
QDERR_RETURN:
            LD      SP,(QDSAVESP)                                        ; Restore SP
            SCF                                                          ; Set carry = error
            RET

            ;---------------------------------------------------------------
            ; SIO init table: 11 bytes written to Channel A control port.
            ; Configures: channel reset, x1 clock sync mode, sync chars 16H.
            ;---------------------------------------------------------------
QDSIO_INITTBL:
            DB      058H                                                 ; WR0: Channel reset + reset IUS
            DB      004H                                                 ; WR0: register pointer = WR4
            DB      010H                                                 ; WR4: x1 clock, 8-bit sync mode
            DB      005H                                                 ; WR0: register pointer = WR5
            DB      004H                                                 ; WR5: CRC-16
            DB      003H                                                 ; WR0: register pointer = WR3
            DB      0D0H                                                 ; WR3: Rx 8-bit, hunt mode, auto enables
            DB      006H                                                 ; WR0: register pointer = WR6
            DB      016H                                                 ; WR6: sync char 1 = 16H
            DB      007H                                                 ; WR0: register pointer = WR7
            DB      016H                                                 ; WR7: sync char 2 = 16H

            ENDIF

            ;--------------------------------------
            ;
            ; Message table - Refer to bank 6 for
            ;                 all messages.
            ;
            ;--------------------------------------

            ; RomDisk, top 8 bytes are used by the control registers when enabled so dont use the space.
            IF      BUILD_ROMDISK+BUILD_PICOZ80 = 1
              ALIGN 0EFF8h
              ORG   0EFF8h
              DB    0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh
            ENDIF

            IF      BUILD_SFD700 = 1
              ALIGN 0F000H
            ENDIF
