;--------------------------------------------------------------------------------------------------------
;-
;- Name:            rfs_bank1.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.
;-                  Mar 2023 -  Fixed Floppy bug where the driver letter entered wasnt recognised.
;-                  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     "BANK1"
            ALIGN  UROMADDR
           ENDIF

           ;============================================================
           ;
           ; USER ROM BANK 1 - Floppy Disk Controller functions.
           ;
           ;============================================================
           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
           ;
BKSW1to0:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK0                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to1:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK1                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to2:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK2                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to3:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK3                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to4:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK4                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to5:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK5                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to6:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK6                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to7:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK7                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to8:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK8                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to9:  PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK9                                              ; Required bank to call.
           JR       BKSW1_0
BKSW1to10: PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK10                                             ; Required bank to call.
           JR       BKSW1_0
BKSW1to11: PUSH     AF
           LD       A, ROMBANK1                                              ; Calling bank (ie. us).
           PUSH     AF
           LD       A, ROMBANK11                                             ; Required bank to call.
           ;
BKSW1_0:   PUSH     HL                                                       ; Place function to call on stack
           LD       HL, BKSWRET1                                             ; 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.
BKSWRET1:  POP      AF                                                       ; Get bank which called us.
           BNKSWSELRET
           POP      AF
           RET      

FDCCMD     EQU      01000H
MOTON      EQU      01001H
TRK0FD1    EQU      01002H
TRK0FD2    EQU      01003H
TRK0FD3    EQU      01004H
TRK0FD4    EQU      01005H
RETRIES    EQU      01006H
BPARA      EQU      01008H

           ;-------------------------------------------------------------------------------
           ; START OF FLOPPY DISK CONTROLLER FUNCTIONALITY
           ;-------------------------------------------------------------------------------

           ; Method to check if the floppy interface ROM is present and if it is, jump to its entry point.
           ;
FDCK:      CALL     FDCKROM                                                  ; Check to see if the Floppy ROM is present, exit if it isnt.
           CALL     Z,0F000h
           RET                                                               ; JP       CMDCMPEND
FDCKROM:   LD       A,(0F000h)
           OR       A
           RET

FLOPPY:    IF      BUILD_ROMDISK+BUILD_PICOZ80 = 1
            IF     BUILD_MZ80A = 1
             PUSH     DE                                                     ; Preserve pointer to input buffer.
             LD       DE,BPARA                                               ; Copy disk parameter block into RAM work area. (From)
             LD       HL,PRMBLK                                              ; (To)
             LD       BC,0000BH                                              ; 11 bytes of config data.
             LDIR                                                            ; BC=0, HL=F0E8, DE=1013
             POP      DE                                                     ; init 1001-1005, port $DC mit $00
             LD       A,(DE)                                                 ; If not at the end of the line, then process as the boot disk number.
             CP       00Dh                                                   ; 
             JR       NZ,GETBOOTDSK                                          ; 
             CALL     DSKINIT                                                ; Initialise disk and flags.
L000F:       LDDE     MSGBOOTDRV                                              ;
             LD       HL,PRINTMSG
             CALL     BKSW1to6
             LD       DE,011A3H                                              ; 
             CALL     GETL                                                   ; 
             LD       A,(DE)                                                 ; 
             CP       01BH                                                   ; BREAK pressed?
             JP       Z,ST1X                                                 ; 
             LD       HL,19                                                  ; Check input value is in range 1-4.
             ADD      HL,DE                                                  ; 
             LD       A,(HL)                                                 ; 
             CP       00DH                                                   ; 
             JR       Z,L003A                                                ; 
GETBOOTDSK:  CALL     HEX                                                    ; Convert number to binary
             JR       C,L000F                                                ; If illegal, loop back and re-prompt.
             DEC      A                                                      ; 
             CP       004H                                                   ; Check in range, if not loop back.
             JR       NC,L000F                                               ; 
             LD       (BPARA),A                                              ; Store in parameter block.
L003A:       LD       IX,BPARA                                               ; Point to drive number.,
             CALL     DSKREAD                                                ; Read sector 1 of trk 0
             LD       HL,0CE00H                                              ; Now compare the first 7 bytes of what was read to see if this is a bootable disk.
             LD       DE,DSKID                                               ; 
             LD       B,007H                                                 ; 
L0049:       LD       C,(HL)                                                 ; 
             LD       A,(DE)                                                 ; 
             CP       C                                                      ; 
             JP       NZ,L008C                                               ; If NZ then this is not a master disk, ie not bootable, so error exit with message.
             INC      HL                                                     ; 
             INC      DE                                                     ; 
             DJNZ     L0049                                                  ; 
             LDDE     MSGIPLLOAD                                              ;
             LD       HL,PRINTMSG
             CALL     BKSW1to6
             LD       DE,0CE07H                                              ; Program name stored at 8th byte in boot sector.
             LD       HL,PRTFN
             CALL     BKSW1to6
             LD       HL,(0CE16H)                                            ; Get the load address
             LD       (IX+005H),L                                            ; And store in parameter block at 100D/100E
             LD       (IX+006H),H                                            ; 
             INC      HL
             DEC      HL
             JR       NZ, NOTCPM                                             ; If load address is 0 then where loading CPM.
             ;LD       A,(MEMSW)                                              ; Page out ROM.
NOTCPM:      LD       HL,(0CE14H)                                            ; Get the size
             LD       (IX+003H),L                                            ; And store in parameter block at 100B/100C
             LD       (IX+004H),H                                            ; 
             LD       HL,(0CE1EH)                                            ; Get logical sector number
             LD       (IX+001H),L                                            ; And store in parameter block at 1009/100A
             LD       (IX+002H),H                                            ; 
             CALL     DSKREAD                                                ; Read the required data and store in memory.
             CALL     DSKINIT                                                ; Reset the disk ready for next operation.
             LD       HL,(0CE18H)                                            ; Get the execution address
             JP       (HL)                                                   ; And execute.

DSKLOADERR:  LDDE     MSGLOADERR                                              ; Loading error message
             JR       L008F                                                  ; (+003h)

L008C:       LDDE     MSGDSKNOTMST                                            ; This is not a boot/master disk message.
L008F:       LD       HL,PRINTMSG
             CALL     BKSW1to6
             LD       DE,ERRTONE                                             ; Play error tone.
             CALL     MELDY
             ;
             LD       SP,(TMPSTACKP)                                         ; Recover the correct stack pointer before exit.
             RET                                                             ; JP       SS

L0104:       LD       A,(MOTON)                                              ; motor on flag
             RRCA                                                            ; motor off?
             CALL     NC,DSKMOTORON                                          ; yes, set motor on and wait
             LD       A,(IX+000H)                                            ;drive no
             OR       084H                                                   ;
             OUT      (0DCH),A                                               ; Motor on for drive 0-3
             XOR      A                                                      ;
             LD       (FDCCMD),A                                             ; clr latest FDC command byte
             LD       HL,00000H                                              ;
L0119:       DEC      HL                                                     ;
             LD       A,H                                                    ;
             OR       L                                                      ;
             JP       Z,DSKERR                                               ; Reset and print message that this is not a bootable disk.
             IN       A,(0D8H)                                               ; Status register.
             CPL                                                             ;
             RLCA                                                            ;
             JR       C,L0119                                                ; Wait on motor off (bit 7)
             LD       C,(IX+000H)                                            ; Drive number
             LD       HL,TRK0FD1                                             ; 1 track 0 flag for each drive
             LD       B,000H                                                 ;
             ADD      HL,BC                                                  ; Compute related flag 1002/1003/1004/1005
             BIT      0,(HL)                                                 ;
             JR       NZ,L0137                                               ; 
             CALL     DSKSEEKTK0                                             ; Seek track 0.
             SET      0,(HL)                                                 ; Set bit 0 of trk 0 flag
L0137:       RET      
 
             ; Turn disk motor on.
DSKMOTORON:  LD       A,080H
             OUT      (0DCH),A                                               ; Motor on
             LD       B,010H                                                 ; 
L013E:       CALL     L02C7                                                  ; 
             DJNZ     L013E                                                  ; Wait until becomes ready.
             LD       A,001H                                                 ; Set motor on flag.
             LD       (MOTON),A                                              ; 
             RET      

L0149:       LD       A,01BH
             CALL     DSKCMD
             AND      099H
             RET      

             ; Initialise drive and reset flags, Set motor off
DSKINIT:     XOR      A                                                      
             OUT      (0DCH),A                                               ; Motor on/off
             LD       (TRK0FD1),A                                            ; Track 0 flag drive 1
             LD       (TRK0FD2),A                                            ; Track 0 flag drive 2
             LD       (TRK0FD3),A                                            ; Track 0 flag drive 3
             LD       (TRK0FD4),A                                            ; Track 0 flag drive 4
             LD       (MOTON),A                                              ; Motor on flag
             RET      

DSKSEEKTK0:  LD       A,00BH                                                 ; Restore command, seek track 0.
             CALL     DSKCMD                                                 ; Send command to FDC.
             AND      085H                                                   ; Process result.
             XOR      004H   
             RET      Z      
             JP       DSKERR

DSKCMD:      LD       (FDCCMD),A                                             ; Store latest FDC command.
             CPL                                                             ; Compliment it (FDC bit value is reversed).
             OUT      (0D8H),A                                               ; Send command to FDC.
             CALL     L017E                                                  ; Wait to become ready.
             IN       A,(0D8H)                                               ; Get status register.
             CPL                                                             ; Inverse (FDC is reverse bit logic).
             RET      

L017E:       PUSH     DE
             PUSH     HL
             CALL     L02C0
             LD       E,007H
L0185:       LD       HL,00000H
L0188:       DEC      HL
             LD       A,H
             OR       L
             JR       Z,L0196                                                ; (+009h)
             IN       A,(0D8H)
             CPL     
             RRCA    
             JR       C,L0188                                                ; (-00bh)
             POP      HL
             POP      DE
             RET     

L0196:       DEC      E
             JR       NZ,L0185                                               ; (-014h)
             JP       DSKERR

L019C:       PUSH     DE
             PUSH     HL
             CALL     L02C0
             LD       E,007H
L01A3:       LD       HL,00000H
L01A6:       DEC      HL
             LD       A,H
             OR       L
             JR       Z,L01B4                                                ; (+009h)
             IN       A,(0D8H)
             CPL     
             RRCA    
             JR       NC,L01A6                                               ; (-00bh)
             POP      HL
             POP      DE
             RET      

L01B4:       DEC      E
             JR       NZ,L01A3                                               ; (-014h)
             JP       DSKERR

             ; Read disk starting at the first logical sector in param block 1009/100A
             ; Continue reading for the given size 100B/100C and store in the location 
             ; Pointed to by the address stored in the parameter block. 100D/100E
DSKREAD:     CALL     L0220                                                  ; Compute logical sector-no to track-no & sector-no, retries=10
L01BD:       CALL     L0229                                                  ; Set current track & sector, get load address to HL
L01C0:       CALL     L0249                                                  ; Set side reg
             CALL     L0149                                                  ; Command 1b output (seek)
             JR       NZ,L0216                                               ; 
             CALL     L0259                                                  ; Set track & sector reg
             PUSH     IX                                                     ; Save 1008H
             LD       IX, 0F3FEH                                             ; As below. L03FE
             LD       IY,L01DF                                               ; Read sector into memory.
             ;DI      
             LD       A,094H                                                 ; Latest FDC command byte
             CALL     L028A
L01DB:       LD       B,000H
             JP       (IX)

             ; Get data from disk sector to staging area (CE00).
L01DF:       INI     
             LD       A,(DE)                                                 ; If not at the end of the line, then process as the boot disk number.
             JP       NZ, 0F3FEH                                             ; This is crucial, as the Z80 is running at 2MHz it is not fast enough so needs
                                                                             ; hardware acceleration in the form of a banked ROM, if disk not ready jumps to IX, if
                                                                             ; data ready, jumps to IY. L03FE
             POP      IX
             INC      (IX+008H)                                              ; Increment current sector number
             LD       A,(IX+008H)                                            ; Load current sector number
             PUSH     IX                                                     ; Save 1008H
             LD       IX, 0F3FEH                                             ; As above. L03FE
             CP       011H                                                   ; Sector 17? Need to loop to next track.
             JR       Z,L01FB                 
             DEC      D
             JR       NZ,L01DB                
             JR       L01FC                                                  ; (+001h)

L01FB:       DEC      D
L01FC:       CALL     L0294
             CALL     L02D2
             POP      IX
             IN       A,(0D8H)
             CPL      
             AND      0FFH
             JR       NZ,L0216                                               ; (+00bh)
             CALL     L0278
             JP       Z,L021B
             LD       A,(IX+007H)
             JR       L01C0                                                  ; (-056h)

L0216:       CALL     L026A
             JR       L01BD                                                  ; (-05eh)

L021B:       LD       A,080H
             OUT      (0DCH),A                                               ; Motor on
             RET     

L0220:       CALL     L02A3                                                  ; compute logical sector no to track no & sector no
             LD       A,00AH                                                 ; 10 retries
             LD       (RETRIES),A
             RET     

             ; Set current track & sector, get load address to HL
L0229:       CALL     L0104
             LD       D,(IX+004H)                                            ; Number of sectors to read
             LD       A,(IX+003H)                                            ; Bytes to read
             OR       A                                                      ; 0?
             JR       Z,L0236                                                ; Yes
             INC      D                                                      ; Number of sectors to read + 1
L0236:       LD       A,(IX+00AH)                                            ; Start sector number
             LD       (IX+008H),A                                            ; To current sector number
             LD       A,(IX+009H)                                            ; Start track number
             LD       (IX+007H),A                                            ; To current track number
             LD       L,(IX+005H)                                            ; Load address low byte
             LD       H,(IX+006H)                                            ; Load address high byte
             RET     

             ; Compute side/head.
L0249:       SRL      A                                                      ; Track number even?
             CPL                                                             ; 
             OUT      (0DBH),A                                               ; Output track no.
             JR       NC,L0254                                               ; Yes, even, set side/head 1
             LD       A,001H                                                 ; No, odd, set side/head 0
             JR       L0255                   

             ; Set side/head register.
L0254:       XOR      A                                                      ; Side 0
L0255:       CPL                                                             ; Side 1
             OUT      (0DDH),A                                               ; Side/head register.
             RET     

             ; Set track and sector register.
L0259:       LD       C,0DBH                  
             LD       A,(IX+007H)                                            ; Current track number
             SRL      A                       
             CPL                              
             OUT      (0D9H),A                                               ; Track reg
             LD       A,(IX+008H)                                            ; Current sector number
             CPL                             
             OUT      (0DAH),A                                               ; Sector reg
             RET                       

L026A:       LD       A,(RETRIES)
             DEC      A
             LD       (RETRIES),A
             JP       Z,DSKERR
             CALL     DSKSEEKTK0
             RET     

L0278:       LD       A,(IX+008H)
             CP       011H
             JR       NZ,L0287                                               ; (+008h)
             LD       A,001H
             LD       (IX+008H),A
             INC      (IX+007H)
L0287:       LD       A,D
             OR       A
             RET     

L028A:       LD       (FDCCMD),A
             CPL     
             OUT      (0D8H),A
             CALL     L019C
             RET      

L0294:       LD       A,0D8H
             CPL     
             OUT      (0D8H),A
             CALL     L017E
             RET     

DSKERR:      CALL     DSKINIT
             JP       DSKLOADERR

             ; Logical sector number to physical track and sector.
L02A3:       LD       B,000H
             LD       DE,00010H                                              ; No of sectors per trk (16)
             LD       L,(IX+001H)                                            ; Logical sector number
             LD       H,(IX+002H)                                            ; 2 bytes in length
             XOR      A
L02AF:       SBC      HL,DE                                                  ; Subtract 16 sectors/trk 
             JR       C,L02B6                                                ; Yes, negative value
             INC      B                                                      ; Count track
             JR       L02AF                                                  ; Loop
L02B6:       ADD      HL,DE                                                  ; Reset HL to the previous
             LD       H,B                                                    ; Track
             INC      L                                                      ; Correction +1
             LD       (IX+009H),H                                            ; Start track no
             LD       (IX+00AH),L                                            ; Start sector no
             RET     

L02C0:       PUSH     DE
             LD       DE,00007H
             JP       L02CB

L02C7:       PUSH     DE
             LD       DE,01013H
L02CB:       DEC      DE
             LD       A,E
             OR       D
             JR       NZ,L02CB                                               ; (-005h)
             POP      DE
             RET     

L02D2:       PUSH     AF
             LD       A,(0119CH)
             CP       0F0H
             JR       NZ,L02DB                                               ; (+001h)
             ;EI      
L02DB:       POP      AF
             RET     

             ;wait on bit 0 and bit 1 = 0 of state reg
L0300:       IN      A,(0D8H)	                                 	         ; State reg
             RRCA    
             JR      C,L0300	                                             ; Wait on not busy
             RRCA    
             JR      C,L0300	                                             ; Wait on data reg ready
             JP      (IY)	                                                 ; to f1df
            ENDIF

            IF     BUILD_MZ700 = 1
            ;
            ;-------------------------------------------------------------------------------
            ; MZ-700 FLOPPY DISK CONTROLLER - Based on MZ-1E05 ROM code.
            ;
            ; The MZ-700 FDC uses the same WD1773 controller as the MZ-80A but with an
            ; inverted data bus (all values CPL'd before OUT, after IN). The read loop
            ; uses a simple INI transfer rather than the MZ-80A's hardware-assisted JP(IX)/JP(IY).
            ;
            ; Disk geometry: 70 tracks (0-69), 2 heads, 16 sectors/track (1-16), 256 bytes/sector.
            ;
            ; Work area in RAM:
            ;   BPARA (1008H): 11-byte parameter block (drive, sector, size, addr, track, sector)
            ;   FDCCMD (1000H): Last FDC command byte
            ;   MOTON  (1001H): Motor on flag
            ;   TRK0FD1-4 (1002-1005H): Track 0 calibrated flags per drive
            ;   RETRIES (1006H): Retry counter
            ;-------------------------------------------------------------------------------

            ; Method to boot from MZ-700 floppy disk.
            ;
             PUSH    DE                                                  ; Preserve pointer to input buffer.
             LD      DE,BPARA                                            ; Copy disk parameter block into RAM work area.
             LD      HL,PRMBLK
             LD      BC,11
             LDIR
             POP     DE
             LD      A,(DE)                                              ; Check if drive number given on command line.
             CP      00DH
             JR      NZ,FD7BOOT
             CALL    FD7INIT                                             ; Initialise disk and flags.
FD7PRMPT:    LDDE    MSGBOOTDRV
             LD      HL,PRINTMSG
             CALL    BKSW1to6
             LD      DE,011A3H
             CALL    GETL
             LD      A,(DE)
             CP      01BH                                                ; BREAK pressed?
             JP      Z,ST1X
             LD      HL,19
             ADD     HL,DE
             LD      A,(HL)
             CP      00DH
             JR      Z,FD7READ1
FD7BOOT:     CALL    HEX                                                 ; Convert number to binary.
             JR      C,FD7PRMPT
             DEC     A
             CP      4
             JR      NC,FD7PRMPT
             LD      (BPARA),A                                           ; Store drive number.
             ;
             ; Read boot sector (sector 0, track 0).
FD7READ1:    LD      IX,BPARA
             CALL    FD7READ                                             ; Read boot sector into 0xCF00.
             ;
             ; Verify boot disk - check for "2IPLPRO" signature.
             LD      HL,0CF00H                                           ; Boot sector loaded here.
             LD      DE,DSKID
             LD      B,7
FD7CHK:      LD      C,(HL)
             LD      A,(DE)
             CP      C
             JP      NZ,FD7NOTMST                                       ; Not a master disk.
             INC     HL
             INC     DE
             DJNZ    FD7CHK
             ;
             ; Valid boot disk - print loading message and program name.
             LDDE    MSGIPLLOAD
             LD      HL,PRINTMSG
             CALL    BKSW1to6
             LD      DE,0CF07H                                           ; Program name in boot sector.
             LD      HL,PRTFN
             CALL    BKSW1to6
             ;
             ; Extract load parameters from boot sector.
             LD      HL,(0CF16H)                                         ; Load/target address.
             LD      A,H
             OR      L
             JR      NZ,FD7NORM                                          ; Non-zero = normal file.
             LD      HL,(0CF18H)                                         ; Check exec address too.
             LD      A,H
             OR      L
             JR      Z,FD7RELO                                           ; Both zero = needs relocation (loads into ROM space).
FD7NORM:     LD      HL,(0CF18H)                                         ; Normal: use load address from boot sector.
             LD      (IX+5),L
             LD      (IX+6),H
             XOR     A
             JR      FD7LOAD
             ;
FD7RELO:     LD      A,0FFH                                              ; Target is 0x0000 (ROM area), load to temp buffer.
             LD      HL,01200H
             LD      (IX+5),L
             LD      (IX+6),H
FD7LOAD:     LD      (CMTLOLOAD),A                                       ; Save relocation flag.
             LD      HL,(0CF14H)                                         ; Byte size.
             LD      (IX+3),L
             LD      (IX+4),H
             LD      HL,(0CF1EH)                                         ; Start logical sector.
             LD      (IX+1),L
             LD      (IX+2),H
             ;
             CALL    FD7READ                                             ; Read the program data.
             CALL    FD7INIT                                             ; Motor off, reset.
             ;
             ; If relocation needed, page out ROM and copy to real address.
             LD      A,(CMTLOLOAD)
             CP      0FFH
             JR      NZ,FD7EXEC
             OUT     (0E0H),A                                            ; Page out ROM, enable lower DRAM.
             LD      HL,01200H                                           ; Source.
             LD      DE,(0CF16H)                                         ; Target address.
             LD      BC,(0CF14H)                                         ; Byte count.
             LDIR
FD7EXEC:     LD      BC,00200H                                           ; Default return code.
             LD      HL,(0CF18H)                                         ; Execution address.
             JP      (HL)
             ;
             ; Error handlers.
FD7NOTMST:   CALL    FD7INIT
             LDDE    MSGDSKNOTMST
             JR      FD7ERR
FD7LOADERR:  LDDE    MSGLOADERR
FD7ERR:      LD      HL,PRINTMSG
             CALL    BKSW1to6
             LD      DE,ERRTONE
             CALL    MELDY
             LD      SP,(TMPSTACKP)                                      ; Recover stack pointer.
             RET

            ;-------------------------------------------------------------------------------
            ; MZ-700 FDC LOW-LEVEL ROUTINES
            ;-------------------------------------------------------------------------------

            ; FDC I/O presence check: write/read test on track register.
FD7CC:       LD      A,0A5H
             LD      B,A
             CPL
             OUT     (FDC_TRACK),A
             CALL    FD7DLY80U
             IN      A,(FDC_TRACK)
             CPL
             CP      B
             RET

            ; Initialise disk: motor off, clear all flags.
FD7INIT:     PUSH    AF
             CALL    FD7DLY1M
             XOR     A
             OUT     (FDC_DRIVE),A                                       ; Motor off.
             LD      (TRK0FD1),A
             LD      (TRK0FD2),A
             LD      (TRK0FD3),A
             LD      (TRK0FD4),A
             LD      (MOTON),A
             POP     AF
             RET

            ; Motor on: enable motor and wait for spinup.
FD7MOTON:    LD      A,080H
             OUT     (FDC_DRIVE),A
             LD      B,16
FD7MTD1:     CALL    FD7DLY60M
             DJNZ    FD7MTD1
             LD      A,1
             LD      (MOTON),A
             RET

            ; Ready check: ensure motor is on, drive selected, seek track 0 if needed.
FD7READY:    LD      A,(MOTON)
             RRCA
             CALL    NC,FD7MOTON
             LD      A,(IX+0)                                            ; Drive number.
             OR      084H
             OUT     (FDC_DRIVE),A                                       ; Drive select + motor on.
             XOR     A
             LD      (FDCCMD),A
             CALL    FD7DLY60M
             LD      HL,0
FD7RDY0:     DEC     HL
             LD      A,H
             OR      L
             JP      Z,FD7DSKERR                                        ; Timeout.
             IN      A,(FDC_CMD)
             CPL
             RLCA
             JR      C,FD7RDY0                                          ; Wait for not-busy.
             LD      C,(IX+0)
             LD      HL,TRK0FD1
             LD      B,0
             ADD     HL,BC
             BIT     0,(HL)
             JR      NZ,FD7RDY2                                         ; Already calibrated.
             CALL    FD7RCLB                                             ; Seek track 0.
             SET     0,(HL)
FD7RDY2:     RET

            ; Seek to track in track register.
FD7SEEK:     LD      A,01BH                                              ; Seek command.
             CALL    FD7CMD1
             AND     099H
             RET

            ; Restore (seek track 0).
FD7RCLB:     LD      A,00BH                                              ; Restore command.
             CALL    FD7CMD1
             AND     085H
             XOR     004H
             RET     Z
             JP      FD7DSKERR

            ; Send FDC command and wait for completion (type I commands).
FD7CMD1:     LD      (FDCCMD),A
             CPL
             OUT     (FDC_CMD),A
             CALL    FD7BSYON                                            ; Wait for busy.
             CALL    FD7DLY60M
             IN      A,(FDC_CMD)
             CPL
             RET

            ; Send FDC command and wait for not-busy (type II/III commands).
FD7CMD2:     LD      (FDCCMD),A
             CPL
             OUT     (FDC_CMD),A
             CALL    FD7BSYOFF
             RET

            ; Wait for FDC to become busy (command accepted).
FD7BSYON:    PUSH    DE
             PUSH    HL
             CALL    FD7DLY80U
             LD      E,7
FD7BSY1:     LD      HL,0
FD7BSY2:     DEC     HL
             LD      A,H
             OR      L
             JR      Z,FD7BSY3
             IN      A,(FDC_CMD)
             RRCA
             JR      NC,FD7BSY2                                          ; Wait until busy bit set.
             POP     HL
             POP     DE
             RET
FD7BSY3:     DEC     E
             JR      NZ,FD7BSY1
             POP     HL
             POP     DE
             JP      FD7DSKERR

            ; Wait for FDC to become not-busy (command complete).
FD7BSYOFF:   PUSH    DE
             PUSH    HL
             CALL    FD7DLY80U
             LD      E,7
FD7BSF1:     LD      HL,0
FD7BSF2:     DEC     HL
             LD      A,H
             OR      L
             JR      Z,FD7BSF3
             IN      A,(FDC_CMD)
             RRCA
             JR      C,FD7BSF2                                           ; Wait until busy bit clear.
             POP     HL
             POP     DE
             RET
FD7BSF3:     DEC     E
             JR      NZ,FD7BSF1
             POP     HL
             POP     DE
             JP      FD7DSKERR

            ; Sequential read from disk.
            ; IX = parameter block (BPARA).
FD7READ:     CALL    FD7CNVRT                                            ; Convert logical sector to track/sector.
             CALL    FD7PRST1                                            ; Setup params, HL = load address.
FD7RE8:      CALL    FD7SIDST                                            ; Set side/head.
             CALL    FD7SEEK
             JP      NZ,FD7DSKERR
             CALL    FD7PRST2                                            ; Set track & sector registers.
             DI
             LD      A,094H                                              ; Read sector (multiple records).
             CALL    FD7CMD2
FD7RE6:      LD      B,0                                                 ; 256 bytes per sector.
FD7RE4:      IN      A,(FDC_CMD)
             RRCA
             JR      C,FD7RE3                                            ; Busy dropped = done.
             RRCA
             JR      C,FD7RE4                                            ; DRQ not set, keep waiting.
             INI                                                         ; Read byte: (HL) = IN(C), B--, HL++.
             JR      NZ,FD7RE4
             ;
             INC     (IX+8)                                              ; Next sector.
             LD      A,(IX+8)
             CP      17                                                  ; Past last sector on track?
             JR      Z,FD7RESEC
             DEC     D                                                   ; Decrement sector count.
             JR      NZ,FD7RE6                                           ; More sectors to read.
             JR      FD7REDONE
FD7RESEC:    DEC     D
FD7REDONE:   LD      A,0D8H                                              ; Force interrupt.
             CPL
             OUT     (FDC_CMD),A
             CALL    FD7BSYON
FD7RE3:      EI
             IN      A,(FDC_CMD)
             CPL
             AND     0FFH
             JR      NZ,FD7DSKERR                                       ; Read error.
             CALL    FD7ADJ                                              ; Adjust sector/track.
             JR      Z,FD7REND                                           ; All sectors read.
             LD      A,(IX+7)                                            ; Next track.
             JR      FD7RE8
FD7REND:     LD      A,080H
             OUT     (FDC_DRIVE),A                                       ; Keep motor on.
             RET

            ; Parameter setup: ready check, set sector count and start positions.
FD7PRST1:    CALL    FD7READY
             LD      D,(IX+4)                                            ; Sector count (high byte of size = pages).
             LD      A,(IX+3)                                            ; Low byte of size.
             OR      A
             JR      Z,FD7PRS1A
             INC     D                                                   ; Round up if partial sector.
FD7PRS1A:    LD      A,(IX+10)                                           ; Start sector -> current.
             LD      (IX+8),A
             LD      A,(IX+9)                                            ; Start track -> current.
             LD      (IX+7),A
             LD      L,(IX+5)                                            ; Load address.
             LD      H,(IX+6)
             RET

            ; Set side/head based on track number.
FD7SIDST:    SRL     A                                                   ; Track/2, carry = side.
             CPL
             OUT     (FDC_DATA),A                                        ; Track to data register for seek.
             JR      NC,FD7SIDE0
             LD      A,1                                                 ; Side 1.
             JR      FD7SIDES
FD7SIDE0:    XOR     A                                                   ; Side 0.
FD7SIDES:    CPL
             OUT     (FDC_SIDE),A
             RET

            ; Set track and sector registers.
FD7PRST2:    LD      C,FDC_DATA                                          ; Data port for INI instruction.
             LD      A,(IX+7)                                            ; Current track.
             SRL     A
             CPL
             OUT     (FDC_TRACK),A
             LD      A,(IX+8)                                            ; Current sector.
             CPL
             OUT     (FDC_SECTOR),A
             RET

            ; Adjust sector/track after reading.
FD7ADJ:      LD      A,(IX+8)
             CP      17                                                  ; Past last sector?
             JR      NZ,FD7ADJ1
             LD      A,1                                                 ; Wrap to sector 1.
             LD      (IX+8),A
             INC     (IX+7)                                              ; Next track.
FD7ADJ1:     LD      A,D
             OR      A                                                   ; Z if all sectors read.
             RET

            ; Convert logical sector number to physical track and sector.
FD7CNVRT:    LD      B,0                                                 ; Track counter.
             LD      DE,16                                               ; 16 sectors per track.
             LD      L,(IX+1)                                            ; Logical sector number.
             LD      H,(IX+2)
             XOR     A
FD7CNV0:     SBC     HL,DE
             JR      C,FD7CNV1
             INC     B
             JR      FD7CNV0
FD7CNV1:     ADD     HL,DE
             LD      H,B                                                 ; Track.
             INC     L                                                   ; Sector 1-16 (not 0-15).
             LD      (IX+9),H                                            ; Start track.
             LD      (IX+10),L                                           ; Start sector.
             RET

            ; Disk error handler.
FD7DSKERR:   CALL    FD7INIT
             JP      FD7LOADERR

            ; Time delays.
FD7DLY80U:   PUSH    DE
             LD      DE,15
             JR      FD7DLYT
FD7DLY1M:    PUSH    DE
             LD      DE,160
             JR      FD7DLYT
FD7DLY60M:   PUSH    DE
             LD      DE,8230
FD7DLYT:     DEC     DE
             LD      A,E
             OR      D
             JR      NZ,FD7DLYT
             POP     DE
             RET

            ;-------------------------------------------------------------------------------
            ; MZ-700 FLOPPY DIRECTORY
            ;
            ; Lists all files on a floppy disk. Default drive 1, or FD1..FD4.
            ;
            ; Disk layout:
            ;   Sector 0:              IPL boot header ("3IPLPRO" signature)
            ;   Sectors 1..(N-1):      Directory slots (one MZF header per sector)
            ;   Sectors N..end:        File data
            ;
            ; N = start data sector from boot header at +1EH.
            ; Each directory slot is one 256-byte sector containing an MZF header:
            ;   +00: file type (00=empty/deleted, 01=OBJ, 02=BTX, etc)
            ;   +01: filename (17 bytes)
            ;   +12: byte size (16-bit LE)
            ;   +14: load address (16-bit LE)
            ;   +16: exec address (16-bit LE)
            ;
            ; All slots are scanned; empty slots (type=00) are skipped.
            ;
            ; Input:  DE = pointer to optional drive number on command line.
            ;-------------------------------------------------------------------------------

FDDIR:       ; Parse drive number (default = drive 1 = index 0).
             LD      A,(DE)
             CP      00DH
             JR      Z,FDDIR_DEF
             CALL    HEX
             JR      C,FDDIR_DEF
             DEC     A                                                   ; 1-based to 0-based.
             CP      4
             JR      C,FDDIR_GO
FDDIR_DEF:   XOR     A
             ;
FDDIR_GO:    LD      (BPARA),A                                           ; Store drive number.
             ADD     A,'1'
             LD      C,A
             PUSH    BC
             LDDE    MSGFDDIR
             LD      HL,PRINTMSG
             CALL    BKSW1to6
             POP     BC
             ;
             ; Setup parameter block: read sector 0, 256 bytes to CF00H.
             LD      A,(BPARA)
             PUSH    AF
             LD      DE,BPARA
             LD      HL,PRMBLK
             PUSH    BC
             LD      BC,11
             LDIR
             POP     BC
             POP     AF
             LD      (BPARA),A
             ;
             CALL    FD7CC
             JP      NZ,FDDIR_END                                        ; No FDC.
             ;
             ; Read boot sector (sector 0 = IPL) and verify signature.
             LD      IX,BPARA
             CALL    FD7READ
             LD      HL,0CF00H
             LD      DE,DSKID
             LD      B,7
FDDIR_IPL:   LD      A,(DE)
             CP      (HL)
             JP      NZ,FDDIR_END                                        ; Not a valid disk.
             INC     HL
             INC     DE
             DJNZ    FDDIR_IPL
             ; Print IPL boot entry: name at +07, size +14, load +16, exec +18.
             LD      DE,0CF07H
             LD      HL,PRTFN
             CALL    BKSW1to6
             LD      HL,(0CF16H)
             PUSH    HL
             LD      HL,(0CF18H)
             PUSH    HL
             LD      BC,(0CF14H)
             LDDE    MSGFDINFO
             LD      HL,PRINTMSG
             CALL    BKSW1to6
             POP     BC
             POP     BC
             ;
             ; Scan directory: 32-byte entries, 8 per sector.
             ; The MZ-700 disk has: side 1 = IPL (sector 0), side 0 = directory (sector 16+).
             ; Boot sector offset +1EH has the start data sector — directory spans
             ; from sector 16 to (startDataSec - 1). First sector entry 0 is FAT header.
             ;
             ; Get number of directory sectors from IPL offset +1EH.
             LD      A,(0CF1EH)                                          ; Start data sector (low byte).
             SUB     16                                                  ; Directory sectors = startDataSec - 16.
             JR      Z,FDDIR_END                                         ; No directory sectors.
             JR      C,FDDIR_END
             LD      C,A                                                 ; C = number of directory sectors.
             LD      A,16                                                ; First directory sector.
             ;
FDDIR_SEC:   ; Read next directory sector.
             PUSH    AF                                                  ; Save current sector number.
             PUSH    BC                                                  ; Save remaining sector count.
             LD      (IX+1),A
             LD      (IX+2),0
             CALL    FD7READ
             ;
             ; First sector (16): skip entry 0 (FAT header), scan entries 1-7.
             ; Subsequent sectors: scan all 8 entries.
             POP     BC
             POP     AF
             PUSH    AF                                                  ; Keep sector number on stack.
             PUSH    BC
             CP      16
             JR      NZ,FDDIR_AL8                                        ; Not first sector → all 8 entries.
             LD      HL,0CF20H                                           ; Skip entry 0 (FAT header).
             LD      B,7                                                 ; 7 entries in first sector.
             JR      FDDIR_ENT
FDDIR_AL8:   LD      HL,0CF00H                                           ; Start from entry 0.
             LD      B,8                                                 ; 8 entries per sector.
             ;
FDDIR_ENT:   ; Check type byte at (HL). 00 = empty/deleted, FF = unused → skip.
             LD      A,(HL)
             OR      A
             JR      Z,FDDIR_NXE
             CP      0FFH
             JR      Z,FDDIR_NXE
             ;
             ; Valid entry: print filename and load/exec/size.
             LD      (TMPADR),HL                                         ; Save entry base.
             PUSH    HL
             PUSH    BC
             INC     HL                                                  ; +01 = filename.
             EX      DE,HL
             LD      HL,PRTFN
             CALL    BKSW1to6
             ; Read size from base+12H, load from base+14H, exec from base+16H.
             LD      HL,(TMPADR)
             LD      DE,012H
             ADD     HL,DE
             LD      C,(HL)
             INC     HL
             LD      B,(HL)                                              ; BC = size.
             INC     HL                                                  ; +14H = load addr.
             LD      E,(HL)
             INC     HL
             LD      D,(HL)
             PUSH    DE                                                  ; Stack: load addr.
             INC     HL                                                  ; +16H = exec addr.
             LD      E,(HL)
             INC     HL
             LD      D,(HL)
             PUSH    DE                                                  ; Stack: exec addr.
             LDDE    MSGFDINFO
             LD      HL,PRINTMSG
             CALL    BKSW1to6
             POP     BC                                                  ; Clean exec.
             POP     BC                                                  ; Clean load.
             POP     BC                                                  ; Restore entry counter.
             POP     HL                                                  ; Restore entry pointer.
             ;
FDDIR_NXE:   LD      DE,32                                              ; Next 32-byte entry.
             ADD     HL,DE
             DJNZ    FDDIR_ENT
             ;
             ; Move to next directory sector.
             POP     BC                                                  ; B unused, C = remaining sectors.
             POP     AF                                                  ; A = current sector number.
             INC     A                                                   ; Next sector.
             DEC     C
             JR      NZ,FDDIR_SEC                                        ; More directory sectors.
             ;
FDDIR_END:   CALL    FD7INIT
             RET

            ENDIF
           ENDIF

           ;-------------------------------------------------------------------------------
           ; END OF FLOPPY DISK CONTROLLER FUNCTIONALITY
           ;-------------------------------------------------------------------------------

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

           ; Error tone.
ERRTONE:   DB       "A0", 0D7H, "ARA", 0D7H, "AR", 00DH

           ; Boot disk identifier - 002H prefix for MZ-80A, 003H prefix for MZ-700.
DSKID:     IF       BUILD_MZ80A = 1
             DB     002H, "IPLPRO"
           ELSE
             DB     003H, "IPLPRO"
           ENDIF

           ; Parameter block: drive 0, sector 0, 256 bytes, load address differs.
           ; MZ-80A loads boot sector to CE00H, MZ-700 loads to CF00H.
PRMBLK:    IF       BUILD_MZ80A = 1
             DB     000H, 000H, 000H, 000H, 001H, 000H, 0CEH, 000H, 000H, 000H, 000H
           ELSE
             DB     000H, 000H, 000H, 000H, 001H, 000H, 0CFH, 000H, 000H, 000H, 000H
           ENDIF

           IF       BUILD_MZ80A = 1
           ; MZ-80A hardware-assisted read: JP (IY) must be at a fixed address (EBFDh/F3FEh).
           ALIGN    0EBFDh
           DB       0FFh
L03FE:     JP       (IY)
           ENDIF

            ; 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
