--- /dev/null
+;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+; D-X Designs Pty Ltd P112 Floppy disk Routines
+; Copyright (C) 1998 by Harold F. Bower
+;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+; 2015-01-17 Will Sowerbutts: Ported to sdas/Fuzix from UZI-180
+; 2017-01-21 Will Sowerbutts: Improvements for reliable operation at 6MHz
+;
+; Reworked a bit for the FDC9266 and latch on the RBC/N8VEM hardware
+
+ .module devfd_hw
+
+CPU_Z180 .equ Z80_TYPE-2
+.ifeq CPU_Z180
+ .z180
+.endif
+
+ ; imported symbols
+ .globl map_kernel
+ .globl map_process_always
+ .globl _devfd_dtbl
+ .globl _platform_idle
+
+ ; exported sybols
+ .globl _devfd_init
+ .globl _devfd_read
+ .globl _devfd_write
+ .globl _devfd_track
+ .globl _devfd_sector
+ .globl _devfd_error
+ .globl _devfd_buffer
+ .globl _devfd_userbuf
+ .globl _fd_tick
+
+ .include "../platform/kernel.def"
+ .include "../kernel.def"
+
+
+;------------------------------------------------------------------------------
+ .area _CODE
+
+; FDC_MSR - Main Status Register (Read)
+; 7 6 5 4 3 2 1 0 (Write)
+;
+; 7 6 5 4 3 2 1 0 (Read)
+; | | | | +-+-+-+-- Drives Seeking (0=B0 Set, 1=B1 Set,.. 3=B3 Set)
+; | | | +---------- 1 = Command In Progress, 0 = Command Ended
+; | | +------------ 1 = Non-DMA Execution, 0 = DMA Execution
+; | +-------------- 1 = Read, 0 = Write
+; +---------------- 1 = Request for Master, 0 = Internal Execution
+;
+; FDC_DATA - Data/Command Register (Read/Write)
+; (Byte Writes/Reads)
+;
+; FDC_DOR (write) - Latch
+; 7 6 5 4 3 2 1 0
+; | | | | | | | +------- TC
+; | | | | | | +---------- 1 = Motor on
+; | | | | | +------------ 1 = 3.5/5.25" disk 2 = 8" (or high density)
+; | | +-+-+-------------- P2-P0 (Precomp - set 125ns 100)
+; | +-------------------- 1 = high density 0 = low density
+; +---------------------- 1 = active, 0 = in reset
+;
+; FDC_DOR (read)
+; 7 6 5 4 3 2 1 0
+; | | | | | | | +-------- DC
+; +-+-+-+-+-+-+---------- 0
+;
+;-------------------------------------------------------------
+MONTIM .equ 250 ; Motor On time (Seconds * TICKSPERSEC)
+
+; Offsets into _devfd_dtbl
+oFLG .equ 0 ; logged: 0 = Not Logged, 1 = Drive Logged
+oPRM1 .equ 1 ; cbyte0: Step Rate (B7-4), HUT (3-0)
+oPRM2 .equ 2 ; cbyte1: Hd Load in 4mS steps (0=infinite)
+oGAP3 .equ 3 ; gap3: Gap 3 Length for Read
+oSPT .equ 4 ; spt: Sectors-per-Track
+oSEC1 .equ 5 ; sector1: First Sector Number
+oFMT .equ 6 ; format: Bit-mapped Format byte
+oSPIN .equ 7 ; spinup: Spinup delay (1/20-secs)
+oTRK .equ 8 ; curtrk: Current Head Position (Track)
+oNCYL .equ 9 ; ncyl: Number of cylinders
+TBLSIZ .equ 10 ; sizeof() entry in _devfd_dtbl
+
+;-------------------------------------------------------------
+; Determine if the controller exists and a drive is attached
+; fdInit (int minor);
+; Enter: Drive Minor # is on Stack
+; Exit : HL = 0 if All is Ok, Non-Zero if Error
+
+_devfd_init:
+ XOR A
+ LD (motim),A ; Mark Motors as initially OFF
+ LD (hd),A ; and initially Head #0
+
+ LD A, #0x20 ; increase delay time for init
+ LD (dlyCnt),A
+
+ POP HL ; Return Addr
+ POP BC ; minor (in C)
+ PUSH BC ; Keep on Stack for Exit
+ PUSH HL
+ PUSH IY ; Must be saved for the C caller
+ LD A,C
+ LD (drive),A ; Save Desired Device
+ CP #4 ; Legal?
+ JR NC,NoDrv ; ..Exit if Error
+ CALL ActivA ; Else force Reset (B2=0)
+ LD B,#0
+indel1: DJNZ indel1 ; (settle)
+ CALL Activ8 ; then bring out of Reset
+indel2: DJNZ indel2 ; (settle, B already =0)
+ IN A,(FDC_MSR)
+ CP #0x80 ; Do we have correct Ready Status?
+ JR NZ,NoDrv ; ..exit Error if Not
+
+ LD A,(drive)
+ CALL GetPrm ; Pt to this drive's table entry
+ PUSH HL
+ POP IY
+ LD oFLG(IY), #0 ; Ensure drive is Unlogged
+ CALL Spec ; Set Controller Params
+ JR C,NoDrv ; ..Error if TimeOut, Else..
+ CALL Recal ; Recalibrate (home) Drive
+ JR NZ,NoDrv ; ..Error if it failed
+ LD oFLG(IY), #1 ; Mark drive as active
+ LD HL,#0 ; Load Ok Status
+ POP IY
+ RET
+
+NoDrv: LD HL,#0xFFFF ; Set Error Status
+ POP IY
+ RET
+
+;-------------------------------------------------------------
+; This routine Reads/Writes data from buffer trying up to 15 times
+; before giving up. If an error occurs after the next-to-last
+; try, the heads are homed to force a re-seek.
+;
+; Enter: Drive Minor # is on Stack. Entry Point sets Read/Write Flag
+; Exit : A = 0, Zero Set if Ok, A <> 0, Zero Reset if Errors
+; (also returns H = 0 and L set to A for compatibilty with C code)
+; Uses : AF,HL
+
+_devfd_read:
+ LD A,#1
+ .db 0x21 ;..Trash HL, fall thru..
+_devfd_write:
+ LD A,#0 ; has to be two bytes -- do not optimise to xor a!
+ LD (rdOp),A
+
+ POP HL ; Return Addr
+ POP BC ; minor (->C)
+ PUSH BC ; Keep on Stack for Exit
+ PUSH HL
+
+ PUSH IY
+
+ LD A,C
+ LD (drive),A ; Save Desired Device
+
+ CALL Setup ; Set up subsystem
+
+ LD A,#15 ; Get the maximum retry count
+Rwf1: LD (rwRtry),A
+ LD D,#0xFF ; (Verify needed)
+ CALL SEEK ; Try to seek to the desired track
+ JR NZ,Rwf2 ; ..jump if No Good
+
+ LD A,(rdOp)
+ OR A ; Read operation?
+ LD A,#0x05 ; Load DP8473 Write Command
+ JR Z,SWrite ; No, must be Write
+ INC A ; (A=06H) Load DP8473 Read Command
+SWrite: OR #0x40 ; Set MFM Mode Bit
+ PUSH BC ; Save Regs
+ LD C,A ; Save
+ LD B,#9 ; Read/Write Comnds are 9 bytes
+
+ LD A,(eot) ; Get Last Sctr #
+ PUSH AF ; (save for Exit)
+ LD A,(sect) ; Get Desired Sector #
+ LD (eot),A ; make last to Read only one Sector
+
+ ld hl,(_devfd_buffer)
+ CALL FdCmd ; Execute Read/Write
+
+ POP AF ; Restore Last Sctr #
+ LD (eot),A ; to Comnd Blk
+
+ LD A,(st1) ; Get Status Reg 1
+ AND #0x34 ; Return Any Error Bits
+ POP BC ; Restore Regs
+ LD (_devfd_error),A ; (store Error bits)
+ JR Z,FhdrX ; ..jump to return if No Errors
+
+Rwf2: LD A,(rwRtry) ; Get retry count
+ CP #2 ; Are we on Next to last try?
+ CALL Z,Recal ; Return to Track 0 if so
+ LD A,(rwRtry) ; and re-fetch try count
+ DEC A ; Do we have more retries left?
+ JR NZ,Rwf1 ; ..jump to try again if more tries remain
+
+ OR #0xFF ; Else show Error
+FhdrX: LD L,A
+ LD H,#0
+ POP IY
+ RET ; and Exit
+
+;-------------------------------------------------------------
+; SPEC - Do a Specify Command, setting Step Rate and Head
+; Load/Unload Time. Settings require tailoring to Drive.
+;
+; Enter: IY -> Drive Table entry for current drive
+; Exit : Nothing
+; Uses : AF,BC
+
+Spec: CALL WRdyT ; Wait for RQM (hope DIO is Low!), Disable Ints
+ RET C ; ..Error if Timed Out
+ LD A,#0x03 ; Do an FDC Specify Command
+ OUT (FDC_DATA),A
+
+ CALL WRdyT
+ RET C ; ..Error if Timed Out
+ LD A,oPRM1(IY) ; first Rate Byte (Step Rate, HUT)
+ OUT (FDC_DATA),A
+
+ CALL WRdyT
+ RET C ; ..Error if Timed Out
+ LD A,oPRM2(IY) ; Get Head Load Time
+ ADD A,A ; Shift value left (doubles count)
+ INC A ; Set LSB for Non-DMA Operation
+ OUT (FDC_DATA),A
+ XOR A ; Return Ok Flag
+ RET
+
+;-------------------------------------------------------------
+; RECAL Recalibrate Current "drive" (moves heads to track 0).
+; Enter : IY -> Current Drive Table Entry
+; Variable "drive" set to desired floppy unit
+; Return: A = 0 if Ok, NZ if Error. Flags reflect A
+; Uses : AF All other Registers Preserved/Not Affected
+;
+; NOTE: BC Must be preserved by this routine.
+
+Recal: LD A,(hd) ; Get head #
+ ADD A,A
+ ADD A,A ; Shift to B3
+ PUSH HL ; (preserve regs)
+ LD HL,#drive
+ OR (HL) ; add Drive bits
+ POP HL ; (restore regs)
+
+ LD (hdr),A ; in Command Block
+ LD A,#3 ; Give this 3 chances to Home
+Recal1: LD (retrys),A
+ PUSH BC ; Save needed regs
+ PUSH HL
+ LD BC,#(2*256+7) ; (2-byte Recalibrate Comnd = 07H)
+ CALL FdCmd ; execute Recalibrate
+ CALL FdcDn ; Clear Pending Ints, Wait for Seek Complete
+ POP HL ; (restore regs)
+ POP BC
+ AND #0x10 ; Homed? (B4=1 if No Trk0 found)
+ JR Z,RecOk ; ..jump to Store if Ok
+ LD A,(retrys)
+ DEC A ; Any trys left?
+ JR NZ,Recal1 ; ..loop if So
+ DEC A ; Else set Error Flag (0-->FF)
+ RET
+
+RecOk: XOR A ; Get a Zero (track / flag)
+ LD oTRK(IY),A ; Set in Table
+ RET ; and return
+
+;-------------------------------------------------------------
+; READID - Read the first Valid Address Mark on a track.
+;
+; Enter : "hdr" byte set in Command Blk
+; Return: A = 0 if Ok, NZ if Error. Flags reflect A
+; Uses : AF All other Registers Preserved/Not Affected
+
+ReadID: LD A,#0x4a ; Load ReadID Command + MFM Mode byte
+ PUSH BC ; Save regs
+ LD B,#2 ; two bytes in ReadID Command
+ LD C,A ; move Command to C
+ CALL FdCmd ; Activate DP8473 FDC
+
+ LD A,(st1) ; Get Status Reg 1
+ AND #0x25 ; Return Any Error Bits
+ POP BC ; Restore regs
+ RET ; ..and quit
+
+;-------------------------------------------------------------
+; SETUP - Set parameters necessary to Read/Write from Active Drive
+; Enter: Variable "drive" set to current Drive (0..3)
+; Variables _devfd_track and _devfd_sector set to desired block address
+; Exit : IY -> Drive's Table entry
+; Uses : AF,BC,HL
+
+Setup: LD A,(drive)
+ PUSH AF
+ CALL GetPrm ; Pt to Current Drive's Table
+ PUSH HL
+ POP IY
+ POP BC
+ LD A,(active) ; Get current Activation Byte
+ AND #0x80 ; keep reset bit
+ OR #0x20 ; 125ns precomp
+ PUSH AF ; Save the work so far
+; CALL Activ8 ; save new byte and activate FDC
+ LD A,(_devfd_track) ; Get Host Track #
+ SRL A ; Div by 2 (LSB to Carry)
+ LD (trk),A ; Physical Track # to Comnd Blk
+ LD A,#0
+ ADC A,A ; LSB becomes Head #
+ LD (hd),A ; save in Comnd Blk
+ ADD A,A
+ ADD A,A ; Shift to B3
+ LD HL,#drive
+ OR (HL) ; add Drive bits
+ LD (hdr),A ; Save in Comnd Blk
+ LD A,oGAP3(IY)
+ LD (gpl),A ; Set Gap3 Length
+ LD A,oSPT(IY)
+ LD (eot),A ; Final Sector # on Trk
+ LD A,oFMT(IY)
+ AND #3 ; B0/1 of Format byte is Sector Size
+ LD (rsz),A ; save in Comnd Blk
+ LD A,#0xFF
+ LD (dtl),A ; Set Data Length code
+ LD A,(_devfd_sector)
+ ADD A,oSEC1(IY) ; Offset Sector # (base 0) by 1st Sector #
+ LD (sect),A ; set in Comnd Blk
+ XOR A ; (Preset Hi 500 kbps, 3.5 & 5.25" Rate)
+ BIT 7,oFMT(IY) ; Hi (500 kbps) Speed?
+ JR NZ,StSiz0 ; ..jump if Hi-Density/Speed to Set if Yes
+ LD A,oFMT(IY)
+ AND #0x0c ; Else Get Drive size
+ CP #0x08 ; 5.25"?
+ LD A,#0x44 ; (Prepare for 250 kbps)
+ JR NZ,StSiz0 ; ..jump if Not 5.25" w/Rate Set
+ BIT 4,oFMT(IY) ; Hi-Density capable drive?
+ LD A,#0x44 ; (Prepare for 250 kbps)
+ JR Z,StSiz0 ; ..jump if No
+ LD A,#0x44 ; Else set to 300 kbps (@360 rpm = 250kbps)
+ ; Mini, high density.
+StSiz0:
+ POP BC ; Get bits back
+ OR B ; Merge in the rates
+ CALL Activ8 ; Set them all
+
+ LD D,A ; preserve Rate bits
+.ifeq CPU_Z180
+ IN0 A,(0x1F) ; Read Z80182 CPU Cntrl Reg (B7=1 if Hi Speed)
+ RLA ; Speed to Bit Carry..Turbo?
+ LD A,#(CPU_CLOCK_KHZ/1000) ; (Get Processor Rate in MHz)
+ JR C,StSiz1 ; ..jump if Turbo for longer delay
+.else
+ LD A,#(CPU_CLOCK_KHZ/1000) ; (Get Processor Rate in MHz)
+.endif
+ SRL A ; Divide rate by 2
+StSiz1: BIT 2,D ; 500 kb/s (Hi-Speed) Rate (D=0)?
+ JR NZ,StSiz2 ; ..jump if Not
+ LD A,#1 ; Else minimum delay for "High-Speed"
+StSiz2: LD (dlyCnt),A ; save delay count
+ RET
+
+;-------------------------------------------------------------
+; SEEK - Set the Track for disk operations and seek to it.
+;
+; Enter : A = Desired Track Number
+; D = Verify flag (0=No, FF=Yes)
+; Return: A = 0, Zero Flag Set (Z) if Ok, A <> 0 Zero Clear (NZ) if Error
+; Uses : AF All other Registers Preserved/Not Affected
+
+SEEK: PUSH HL ; Save Regs used here
+ PUSH DE
+ PUSH BC
+
+ LD A,(trk) ; Get Track #
+ CP oTRK(IY) ; Is desired Track same as last logged?
+ LD oTRK(IY),A ; (set as if we made it there)
+ JR NZ,SEEKNV ; ..jump if Not Same
+ INC D ; Else Set to No Verify (FF->0)
+SEEKNV: LD A,#4 ; Get the maximum Retry Count
+SEEK1: LD (retrys),A ; save remaining Retry Count
+ LD BC,#(3*256+0x0F); (3-byte Seek Command = 0FH)
+ CALL FdCmd ; Execute the Seek
+ CALL FdcDn ; Clear Pending Int, wait for Seek Complete
+
+ AND #0xE0
+ CP #0x20
+ JR NZ,SEEK2 ;;
+
+ AND #0x40 ; Set NZ if Abnormal Termination
+
+ LD B,A ;; Save Seek Status
+ LD A,(trk) ;; Check track #
+ CP C ;; Same track?
+ JR NZ,SEEK2 ;; Jump to Retry if NOT
+ LD A,B ;; Restore Seek Status
+
+ INC D ; Are we Verifying (FF -> 0)?
+ CALL Z,ReadID ; Read next ID Mark if So
+ DEC D ; (Correct for Test, 0 -> FF)
+
+ OR A ; Set Status (Seek Status if No ReadID)
+ JR Z,SEEKX ; ..exit if Ok
+
+SEEK2: LD A,(retrys) ; Else get trys remaining
+ DEC A ; Any left (80-track could need two)?
+ JR NZ,SEEK1 ; ..loop to try again if More
+ DEC A ; Else set Error Flag (0->FF)
+
+SEEKX: POP BC ; Restore Regs
+ POP DE
+ POP HL
+ RET
+
+
+;-------------------------------------------------------------
+; Check for Proper Termination of Seek/Recalibrate Actions by
+; executing a Check Interrupt Command returning ST0 in A.
+; Enter: None. Used after Seek/Recalibrate Commands
+; Exit : A = ST0 Result Byte, C = PCN result byte
+; Uses : AF and C. All other registers preserved/unused
+
+FdcDn: PUSH HL ; Don't alter regs
+FdcDn0: CALL WRdy1
+ LD A,#8 ; Sense Interrupt Status Comnd
+ OUT (FDC_DATA),A
+ CALL WRdy1
+ IN A,(FDC_DATA) ; Get first Result Byte (ST0)
+ LD L,A
+ CP #0x80 ; Invalid Command?
+ JR Z,FdcDn0 ; ..jump to exit if So
+ CALL WRdy1
+ IN A,(FDC_DATA) ; Read Second Result Byte (Trk #)
+ LD C,A ; ..into C
+ LD A,L
+ BIT 5,A ; Command Complete?
+ JR Z,FdcDn0 ; ..loop if Not
+ POP HL
+ RET
+
+;-------------------------------------------------------------
+; MOTOR CONTROL. This routine performs final selection of
+; the drive control latch and determines if the Motors are
+; already spinning. If they are off, then the Motors are
+; activated and the spinup delay time in tenths-of-seconds
+; is performed before returning.
+;
+; Enter : None
+; Return: None
+; Uses : HL. Remaining Registers Preserved/Not Affected
+
+Motor: PUSH AF ; Save Regs
+ PUSH BC
+ LD A,#MONTIM ; Get motor timeout
+ LD (motim),A ; Reset the countdown timer
+ LD A,(active) ; Load current DOR contents
+ BIT 1,A ; Check motor on ?
+ JR NZ,MotorX ; If so no wait
+ OR #2 ; Motor on
+ CALL Activ8 ; Send to FDC DOR, update active
+ ; TODO this is a busy loop -- we should set a timer and yield
+ LD A,(drive) ; Get Current drive
+ CALL GetPrm ; Pt to Param table
+ LD BC,#oSPIN
+ ADD HL,BC ; offset to Spinup Delay
+ LD A,(HL) ; Get value
+ LD (mtm),A ; to GP Counter
+ EI ; Ensure Ints are ABSOLUTELY Active..
+;
+; FIXME: this is wrong on two levels
+; #1 We shouldn't rely upon an IRQ (we can busy wait too)
+; #2 The timers are set in 1/20ths but it's not clear everyone is
+; using 1/20ths for the IRQ call (See p112)
+;
+MotoLp: PUSH DE
+ CALL _platform_idle
+ POP DE
+ LD A,(mtm) ; ..otherwise, loop never times out!
+ OR A ; Up to Speed?
+ JR NZ,MotoLp ; ..loop if Not
+ DI ; No Ints now..
+MotorX: POP BC
+ POP AF ; Restore Reg
+ RET
+
+;-------------------------------------------------------------
+; Wait for FDC RQM to become Ready with Timeout indicator.
+; Timeout Length is arbitrary and depends on CPU Clock Rate.
+
+WRdyT: ;DI ; No Ints while we are doing I/O
+ LD BC,#30000 ; << Arbitrary >>
+WRdyT0: DEC BC
+ LD A,B
+ OR C ; Timed Out?
+ SCF ; (set Error Flag in case)
+ RET Z ; ..return Error Flag if Yes
+ IN A,(FDC_MSR) ; Read Status Reg
+ AND #0x80 ; Interrupt Present (also kill Carry)?
+ RET NZ ; ..return Ok if Yes
+ JR WRdyT0 ; ..else loop to try again
+
+;-------------------------------------------------------------
+; Return Pointer to Parameters of selected Drive
+; Enter: A = Drive (0..3)
+; Exit : HL -> Parameter entry of drive
+; Uses : AF,HL
+
+GetPrm: PUSH DE
+ LD DE,#TBLSIZ ; Entry Size
+ LD HL,#_devfd_dtbl ; Init to table start
+ INC A
+GetPr0: DEC A ; End?
+ JR Z,GetPrX ; ..quit if Yes, Ptr set
+ ADD HL,DE ; Else step to next
+ JR GetPr0 ; ..loop til found
+
+GetPrX: POP DE
+ RET
+
+;-------------------------------------------------------------
+; This routine called at each Clock Interrupt. It is used
+; to provide any necessary timer/timeout functions.
+; Enter: None.
+; Exit : HL -> mtm byte variable
+; AF - destroyed
+; Uses : AF,HL
+
+_fd_tick:
+ LD HL,#motim ; Point to FDC Motor-On timer
+ LD A,(HL)
+ OR A ; Already Timed out?
+ JR Z,TDone ; ..jump if Yes
+ DEC (HL) ; Else count down
+ CALL Z,MotOff ; stop motors if timed out
+TDone: INC HL ; Advance ptr to watchdog/spinup timer (mtm)
+ LD A,(HL)
+ OR A ; Timed out?
+ RET Z ; ..quit if Yes
+ DEC (HL) ; Else count down
+ RET ; exit
+
+;-------------------------------------------------------------
+; Motor Off routine. Force Off to delay on next select
+; Enter: None. (Motoff)
+; A = FDC Device Control Reg bits (Activ8/ActivA)
+; Exit : A = Current FDC_DOR Register / "active" byte settings
+; Uses : AF
+
+MotOff: XOR A
+ LD (motim),A ; Ensure Motors Marked as OFF
+ LD A,(active) ; Get current settings
+ AND #0x7D ; strip out motor bit
+ OR #0x80 ; out of reset
+Activ8:
+ActivA: LD (active),A ; save
+ OUT (FDC_DOR),A ; and Command!
+ RET
+
+;-------------------------------------------------------------
+; FDCMD - Send Command to 9266 FDC
+; Enter: B = # of Bytes in Command, C = Command Byte
+; HL -> Buffer for Read/Write Data (If Needed)
+; Exit : AF = Status byte
+; Uses : AF. All other registers preserved/unused
+
+FdCmd: PUSH HL ; Save regs (for Exit)
+ PUSH BC
+ PUSH DE
+
+ ; rewrite FdCmdXfer code so data flows in the correct direction
+ LD A,(rdOp)
+ OR A
+ LD A,#0xA2 ; Load second byte of INI opcode (doesn't update flags)
+ JR NZ,FdCiUpd ; ... if read, skip increment
+ INC A ; ... if write, A=0xA3, second byte of OUTI opcode
+FdCiUpd:LD (FdCiR1+1),A ; update second byte of INI/OUTI instruction
+
+ ; is the buffer in user memory?
+ LD A,(_devfd_userbuf)
+ LD D,A ; store userbuf flag in D
+
+ ; prepare the drive
+ CALL Motor ; Ensure motors are On
+
+ CALL FdCmdXfer ; Do the data transfer (using code in _COMMONMEM)
+
+ LD HL,#st0 ; Point to Status Result area
+IsGo: CALL WRdy
+ BIT 4,A ; End of Status/Result?
+ JR Z,FdcXit ; ..exit if So
+ BIT 6,A ; Another byte Ready?
+ JR Z,FdcXit ; ..exit if Not
+ INI ; Else Read Result/Status Byte
+ JR IsGo ; ..loop for next
+FdcXit:
+ POP DE ; Restore Regs
+ POP BC
+ POP HL
+ RET
+
+;------------------------------------------------------------
+; COMMON MEMORY
+;------------------------------------------------------------
+ .area _COMMONMEM
+
+; inner section of FdCmd routine, has to touch buffers etc
+FdCmdXfer:
+ BIT 0,D ; Buffer in user memory?
+ CALL NZ, map_process_always
+
+ ; send the command (length is in B, command is in C)
+ PUSH HL ; save pointer for possible Transfer
+ LD HL,#comnd ; Point to Command Block
+ LD (HL),C ; command passed in C
+ LD C,#FDC_DATA ; FDC Data Port
+OtLoop: CALL WRdy ; Wait for RQM (hoping DIO is Low) (No Ints)
+ OUTI ; Output Command bytes to FDC
+ JR NZ,OtLoop ; ..loop til all bytes sent
+ POP HL ; Restore Possible Transfer Addr
+ JR FdCiR2 ; start sampling MSR
+
+; transfer loop
+;
+;
+FdCiR1: INI ; *** THIS INSTRUCTION IS MODIFIED IN PLACE to INI/OUTI
+ ; INI = ED A2
+ ; OUTI = ED A3
+FdCiR2: IN A,(FDC_MSR) ; Read Main Status Register
+ ADD A
+ JR NC, FdCiR2 ; loop until interrupt requested
+ ADD A ; are we still in the Execution Phase?
+ JP M, FdCiR1 ; if so, next byte!
+
+; tidy up and return
+FdCmdXferDone:
+ BIT 0,D ; Buffer in user memory?
+ RET Z ; done if not
+ JP map_kernel ; else remap kernel and return
+
+;-------------------------------------------------------------
+; Wait for FDC RQM to become Ready, return DIO status in
+; Zero Flag. Pause before reading status port (~12 mS
+; specified, some assumed in code).
+
+WRdy:
+WRdy1: LD A,(dlyCnt) ; Get delay count
+WRdy0: DEC A ; count down
+ JR NZ,WRdy0 ; for ~6 uS Delay
+
+WRdyL: IN A,(FDC_MSR) ; Read Main Status Register
+ BIT 7,A ; Interrupt Present?
+ RET NZ ; Return if So
+ JR WRdyL ; Else Loop
+
+dlyCnt: .db (CPU_CLOCK_KHZ/1000) ; Delay to avoid over-sampling status register
+
+; FDC command staging area
+comnd: .ds 1 ; Storage for Command in execution
+hdr: .ds 1 ; Head (B2), Drive (B0,1)
+trk: .ds 1 ; Track (t)
+hd: .ds 1 ; Head # (h)
+sect: .ds 1 ; Physical Sector Number
+rsz: .ds 1 ; Bytes/Sector (n)
+eot: .ds 1 ; End-of-Track Sect #
+gpl: .ds 1 ; Gap Length
+dtl: .ds 1 ; Data Length
+
+;------------------------------------------------------------
+; DATA MEMORY
+;------------------------------------------------------------
+ .area _DATA
+
+drive: .ds 1 ; (minor) Currently Selected Drive
+active: .ds 1 ; Current bits written to FDC_DOR
+
+_devfd_sector: .ds 1
+_devfd_track: .ds 1 ; LSB used as Head # in DS formats
+_devfd_error: .ds 1
+_devfd_buffer: .ds 2
+_devfd_userbuf: .ds 1
+
+; DISK Subsystem Variable Storage
+; FDC Operation Result Storage Area
+st0: .ds 1 ; Status Byte 0
+st1: .ds 1 ; Status Byte 1 (can also be PCN)
+ .ds 1 ; ST2 - Status Byte 2
+ .ds 1 ; RC - Track #
+ .ds 1 ; RH - Head # (0/1)
+ .ds 1 ; RR - Sector #
+ .ds 1 ; RN - Sector Size
+
+; -->>> NOTE: Do NOT move these next two variables out of sequence !!! <<<--
+motim: .ds 1 ; Motor On Time Counter <<<--
+mtm: .ds 1 ; Floppy Spinup Time down-counter <<<--
+
+rdOp: .ds 1 ; Read/write flag
+retrys: .ds 1 ; Number of times to try Opns
+rwRtry: .ds 1 ; Number of read/write tries
+DRVSPD: .ds 1 ; Drive Speed
+DRVSIZ: .ds 1 ; Drive Size