;
;==================================================================================================
;   FLOPPY DISK DRIVER
;==================================================================================================
;
; TODO:
;
;
; PORTS
;
#IF ((FDMODE == FDMODE_DIO) | (FDMODE == FDMODE_ZETA) | (FDMODE == FDMODE_DIO3))
FDC_MSR		.EQU	$36		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$37		; 8272 DATA PORT
FDC_DIR		.EQU	$38		; DATA INPUT REGISTER
FDC_DOR		.EQU	$3A		; DIGITAL OUTPUT REGISTER (LATCH)
FDC_DMA		.EQU	$3C		; PSEUDO DMA DATA PORT
  #DEFINE FDMODE_STR "DIO"
#ENDIF
#IF (FDMODE = FDMODE_ZETA2)
FDC_MSR		.EQU	$30		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$31		; 8272 DATA PORT
FDC_DOR		.EQU	$38		; DIGITAL OUTPUT REGISTER
FDC_DCR		.EQU	$28		; CONFIGURATION CONTROL REGISTER
FDC_TC		.EQU	$38		; TERMINAL COUNT (W/ DACK)
  #DEFINE FDMODE_STR "ZETA2"
#ENDIF
#IF (FDMODE == FDMODE_DIDE)
FDC_BID		.EQU	$20		; IO RANGE 20H-3FH
FDC_MSR		.EQU	$2A		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$2B		; 8272 DATA PORT
FDC_DOR		.EQU	$2C		; DOR
FDC_DCR		.EQU	$2D		; DCR
FDC_DACK	.EQU	$3C		; DACK
FDC_TC		.EQU	$3D		; TERMINAL COUNT (W/ DACK)
FDC_DMA		.EQU	$3C		; NOT USED BY DIDE
  #DEFINE FDMODE_STR "DIDE"
#ENDIF
#IF (FDMODE == FDMODE_N8)
FDC_MSR		.EQU	$8C		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$8D		; 8272 DATA PORT
FDC_DOR		.EQU	$92		; DOR
FDC_DCR		.EQU	$91		; DCR
FDC_DACK	.EQU	$90		; DACK
FDC_TC		.EQU	$93		; TERMINAL COUNT (W/ DACK)
FDC_DMA		.EQU	$3C		; NOT USED BY N8
  #DEFINE FDMODE_STR "N8"
#ENDIF
#IF (FDMODE == FDMODE_RCSMC)
FDC_MSR		.EQU	$50		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$51		; 8272 DATA PORT
FDC_DOR		.EQU	$58		; DIGITAL OUTPUT REGISTER (LATCH)
  #DEFINE FDMODE_STR "RCSMC"
#ENDIF
#IF (FDMODE == FDMODE_RCWDC)
FDC_MSR		.EQU	$50		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$51		; 8272 DATA PORT
FDC_DOR		.EQU	$58		; DIGITAL OUTPUT REGISTER
FDC_DCR		.EQU	$48		; CONFIGURATION CONTROL REGISTER
FDC_TC		.EQU	$58		; TERMINAL COUNT (W/ DACK)
  #DEFINE FDMODE_STR "RCWDC"
#ENDIF
#IF (FDMODE == FDMODE_DYNO)
FDC_BASE        .EQU    $84
FDC_MSR		.EQU	FDC_BASE + $00	; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	FDC_BASE + $01	; 8272 DATA PORT
FDC_DOR		.EQU	FDC_BASE + $02	; DIGITAL OUTPUT REGISTER
FDC_DCR		.EQU	FDC_BASE + $03	; CONFIGURATION CONTROL REGISTER
FDC_TC		.EQU	FDC_BASE + $02	; TERMINAL COUNT (W/ DACK)
  #DEFINE FDMODE_STR "DYNO"
#ENDIF
#IF (FDMODE == FDMODE_EPFDC)
FDC_MSR		.EQU	$48		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$49		; 8272 DATA PORT
FDC_DOR		.EQU	$4A		; DIGITAL OUTPUT REGISTER
FDC_DCR		.EQU	$4B		; CONFIGURATION CONTROL REGISTER
FDC_TC		.EQU	$4C		; TERMINAL COUNT (W/ DACK)
  #DEFINE FDMODE_STR "EPFDC"
#ENDIF
#IF (FDMODE == FDMODE_MBC)
FDC_MSR		.EQU	$30		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$31		; 8272 DATA PORT
FDC_DOR		.EQU	$36		; DIGITAL OUTPUT REGISTER
FDC_DCR		.EQU	$35		; CONFIGURATION CONTROL REGISTER
FDC_TC		.EQU	$37		; TERMINAL COUNT (W/ DACK)
  #DEFINE FDMODE_STR "MBC"
#ENDIF
#IF (FDMODE == FDMODE_DUO)
FDC_MSR		.EQU	$80		; 8272 MAIN STATUS REGISTER
FDC_DATA	.EQU	$81		; 8272 DATA PORT
FDC_DOR		.EQU	$86		; DIGITAL OUTPUT REGISTER
FDC_DCR		.EQU	$85		; CONFIGURATION CONTROL REGISTER
FDC_TC		.EQU	$87		; TERMINAL COUNT (W/ DACK)
  #DEFINE FDMODE_STR "DUO"
#ENDIF
;
;
; DISK OPERATIONS
;
DOP_READ	.EQU	0		; READ OPERATION
DOP_WRITE	.EQU	1		; WRITE OPERATION
DOP_FORMAT	.EQU	2		; FORMAT OPERATION
DOP_READID	.EQU	3		; READ ID OPERATION
;
; FDC RESULT CODES
;
FRC_OK		.EQU	0		; 00
FRC_NOTIMPL	.EQU	-01H		; FF
FRC_CMDERR	.EQU	-02H		; FE
FRC_ERROR	.EQU	-03H		; FD
FRC_ABORT	.EQU	-04H		; FC
FRC_BUFMAX	.EQU	-05H		; FB
FRC_ABTERM	.EQU	-08H		; F8
FRC_INVCMD	.EQU	-09H		; F7
FRC_DSKCHG	.EQU	-0AH		; F6
FRC_ENDCYL	.EQU	-0BH		; F5
FRC_DATAERR	.EQU	-0CH		; F4
FRC_OVERRUN	.EQU	-0DH		; F3
FRC_NODATA	.EQU	-0EH		; F2
FRC_NOTWRIT	.EQU	-0FH		; F1
FRC_MISADR	.EQU	-10H		; F0
FRC_TOFDCRDY	.EQU	-11H		; EF
FRC_TOSNDCMD	.EQU	-12H		; EE
FRC_TOGETRES	.EQU	-13H		; ED
FRC_TOEXEC	.EQU	-14H		; EC
FRC_TOSEEKWT	.EQU	-15H		; EB
;
; FD DEVICE CONFIGURATION
;
FD_DEVCNT	.EQU	FDCNT		; 2 DEVICES SUPPORTED
FD_CFGSIZ	.EQU	9		; SIZE OF CFG TBL ENTRIES
;
; PER DEVICE DATA OFFSETS
;					; OFFSET OF...
FD_DEV		.EQU	0		; DEVICE NUMBER (BYTE)
FD_STAT		.EQU	1		; LAST STATUS (BYTE)
FD_MEDTYP	.EQU	2		; MEDIA TYPE FDM... (BYTE)
FD_CURTRK	.EQU	3		; CURRENT TRACK (BYTE)
FD_HST		.EQU	4		; HOSTS SEEK POSITION
FD_HSTTRK	.EQU	FD_HST + 0	; HOST TRACK (WORD)
FD_HSTSEC	.EQU	FD_HST + 2	; HOST SECTOR (BYTE)
FD_HSTHD	.EQU	FD_HST + 3	; HOST HEAD (BYTE)
FD_TYPE		.EQU	8		; FLOPPY DSIK TYPE (BYTE)
;
FD_CFGTBL:
	; DEVICE 0, PRIMARY MASTER
	.DB	0			; DEVICE NUMBER
	.DB	0			; DEVICE STATUS
	.DB	$FF			; CURRENT MEDIA TYPE (INIT TO NONE)
	.DB	$FF			; CURRENT TRACK
	.DW	0			; HOST TRACK
	.DB	0			; HOST SECTOR
	.DB	0			; HOST HEAD
	.DB	FD0TYPE			; DRIVE TYPE
;
	DEVECHO	"FD: MODE="
	DEVECHO	FDMODE_STR
	DEVECHO	", IO="
	DEVECHO	FDC_MSR
	DEVECHO	", DRIVE 0"
	DEVECHO	", TYPE="
  #IF (FD0TYPE == FDT_NONE
	DEVECHO	"NONE"
  #ENDIF
  #IF (FD0TYPE == FDT_3DD
	DEVECHO	"3.5\" DD"
  #ENDIF
  #IF (FD0TYPE == FDT_3HD
	DEVECHO	"3.5\" HD"
  #ENDIF
  #IF (FD0TYPE == FDT_5DD
	DEVECHO	"5.25\" DD"
  #ENDIF
  #IF (FD0TYPE == FDT_5HD
	DEVECHO	"5.25\" HD"
  #ENDIF
  #IF (FD0TYPE == FDT_8
	DEVECHO	"8\" DD"
  #ENDIF
	DEVECHO	"\n"
;
#IF (FD_DEVCNT >= 2)
	; DEVICE 1, PRIMARY SLAVE
	.DB	1			; DRIVER DEVICE NUMBER
	.DB	0			; DEVICE STATUS
	.DB	$FF			; CURRENT MEDIA TYPE (INIT TO NONE)
	.DB	$FF			; CURRENT TRACK
	.DW	0			; HOST TRACK
	.DB	0			; HOST SECTOR
	.DB	0			; HOST HEAD
	.DB	FD1TYPE			; DRIVE TYPE
;
	DEVECHO	"FD: MODE="
	DEVECHO	FDMODE_STR
	DEVECHO	", IO="
	DEVECHO	FDC_MSR
	DEVECHO	", DRIVE 1"
	DEVECHO	", TYPE="
  #IF (FD1TYPE == FDT_NONE
	DEVECHO	"NONE"
  #ENDIF
  #IF (FD1TYPE == FDT_3DD
	DEVECHO	"3.5\" DD"
  #ENDIF
  #IF (FD1TYPE == FDT_3HD
	DEVECHO	"3.5\" HD"
  #ENDIF
  #IF (FD1TYPE == FDT_5DD
	DEVECHO	"5.25\" DD"
  #ENDIF
  #IF (FD1TYPE == FDT_5HD
	DEVECHO	"5.25\" HD"
  #ENDIF
  #IF (FD1TYPE == FDT_8
	DEVECHO	"8\" DD"
  #ENDIF
	DEVECHO	"\n"
#ENDIF
;
#IF ($ - FD_CFGTBL) != (FD_DEVCNT * FD_CFGSIZ)
	.ECHO	"*** INVALID FD CONFIG TABLE ***\n"
#ENDIF
;
	.DB	$FF			; END MARKER
;
#IF (FDTRACE > 0)
;
; FDC STATUS CODE STRINGS
;
FSS_OK		.TEXT	"OK$"
FSS_NOTIMPL	.TEXT	"NOT IMPLEMENTED$"
FSS_CMDERR	.TEXT	"COMMAND ERROR$"
FSS_ERROR	.TEXT	"ERROR$"
FSS_ABORT	.TEXT	"ABORT$"
FSS_BUFMAX	.TEXT	"BUFFER EXCEEDED$"
FSS_ABTERM	.TEXT	"ABNORMAL TERMINATION$"
FSS_INVCMD	.TEXT	"INVALID COMMAND$"
FSS_DSKCHG	.TEXT	"DISK CHANGE$"
FSS_ENDCYL	.TEXT	"END OF CYLINDER$"
FSS_DATAERR	.TEXT	"DATA ERROR$"
FSS_OVERRUN	.TEXT	"OVERRUN$"
FSS_NODATA	.TEXT	"NO DATA$"
FSS_NOTWRIT	.TEXT	"NOT WRITABLE$"
FSS_MISADR	.TEXT	"MISSING ADDRESS MARK$"
FSS_TOFDCRDY	.TEXT	"FDC READY TIMEOUT$"
FSS_TOSNDCMD	.TEXT	"SENDCMD TIMEOUT$"
FSS_TOGETRES	.TEXT	"GET RESULTS TIMEOUT$"
FSS_TOEXEC	.TEXT	"EXEC TIMEOUT$"
FSS_TOSEEKWT	.TEXT	"SEEK WAIT TIMEOUT$"
;
; FDC STATUS STRING TABLE
;
FSST:		.DB	FRC_OK \	.DW	FSS_OK
FSST_ENTSIZ	.EQU	$ - FSST
		.DB	FRC_NOTIMPL \	.DW	FSS_NOTIMPL
		.DB	FRC_CMDERR \	.DW	FSS_CMDERR
		.DB	FRC_ERROR \	.DW	FSS_ERROR
		.DB	FRC_ABORT \	.DW	FSS_ABORT
		.DB	FRC_BUFMAX \	.DW	FSS_BUFMAX
		.DB	FRC_ABTERM \	.DW	FSS_ABTERM
		.DB	FRC_INVCMD \	.DW	FSS_INVCMD
		.DB	FRC_DSKCHG \	.DW	FSS_DSKCHG
		.DB	FRC_ENDCYL \	.DW	FSS_ENDCYL
		.DB	FRC_DATAERR \	.DW	FSS_DATAERR
		.DB	FRC_OVERRUN \	.DW	FSS_OVERRUN
		.DB	FRC_NODATA \	.DW	FSS_NODATA
		.DB	FRC_NOTWRIT \	.DW	FSS_NOTWRIT
		.DB	FRC_MISADR \	.DW	FSS_MISADR
		.DB	FRC_TOFDCRDY \	.DW	FSS_TOFDCRDY
		.DB	FRC_TOSNDCMD \	.DW	FSS_TOSNDCMD
		.DB	FRC_TOGETRES \	.DW	FSS_TOGETRES
		.DB	FRC_TOEXEC \	.DW	FSS_TOEXEC
		.DB	FRC_TOSEEKWT \	.DW	FSS_TOSEEKWT
FSST_COUNT	.EQU	(($ - FSST) / FSST_ENTSIZ)	; # ENTRIES IN TABLE
#ENDIF
;
; FDC DRIVE TYPE MEDIA OPTIONS
;
; THIS TABLE LISTS THE TYPES OF MEDIA THAT SHOULD BE ATTEMPTED
; FOR EACH POSSIBLE DRIVE TYPE.  THE ENTRIES MATCH THE ORDER OF THE
; FDT_ VALUES DEFINED IN STD.ASM
;
FD_MEDIAMAP:
		.DW	FDMM_NONE	; NO DRIVE TYPE
		.DW	FDMM_3DD
		.DW	FDMM_3HD
		.DW	FDMM_5DD
		.DW	FDMM_5HD
		.DW	FDMM_8
;
FDMM_NONE	.DB	$FF
FDMM_3DD	.DB	FDM720, $FF
FDMM_3HD	.DB	FDM144, FDM720, $FF
FDMM_5DD	.DB	FDM360, $FF
FDMM_5HD	.DB	FDM120, FDM360, $FF
FDMM_8		.DB	FDM111, $FF
;
; FDC DRIVE TYPE ATTRIBUTES
;
; LOOKUP TABLE TO MAP THE DEVICE ATTRIBUTES BYTE RETURNED
; BY THE FD_DEVICE FUNCTION BASED ON THE DRIVE TYPE.
; THE ENTRIES MATCH THE ORDER OF THE
; FDT_ VALUES DEFINED IN STD.ASM
;
FD_DEVATTR:
		.DB	%11000000	; DRIVE TYPE NONE
		.DB	%11010101	; DRIVE TYPE 3.5 DD
		.DB	%11010110	; DRIVE TYPE 3.5 HD
		.DB	%11001101	; DRIVE TYPE 5.25 DD
		.DB	%11001110	; DRIVE TYPE 5.25 HD
		.DB	%11000101	; DRIVE TYPE 8
;
; FDC COMMANDS
;
CFD_READ	.EQU	00000110B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,DTL --> ST0,ST1,ST2,C,H,R,N
CFD_READDEL	.EQU	00001100B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,DTL --> ST0,ST1,ST2,C,H,R,N
CFD_WRITE	.EQU	00000101B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,DTL --> ST0,ST1,ST2,C,H,R,N
CFD_WRITEDEL	.EQU	00001001B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,DTL --> ST0,ST1,ST2,C,H,R,N
CFD_READTRK	.EQU	00000010B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,DTL --> ST0,ST1,ST2,C,H,R,N
CFD_READID	.EQU	00001010B	; CMD,HDS/DS --> ST0,ST1,ST2,C,H,R,N
CFD_FMTTRK	.EQU	00001101B	; CMD,HDS/DS,N,SC,GPL,D --> ST0,ST1,ST2,C,H,R,N
CFD_SCANEQ	.EQU	00010001B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,STP --> ST0,ST1,ST2,C,H,R,N
CFD_SCANLOEQ	.EQU	00011001B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,STP --> ST0,ST1,ST2,C,H,R,N
CFD_SCANHIEQ	.EQU	00011101B	; CMD,HDS/DS,C,H,R,N,EOT,GPL,STP --> ST0,ST1,ST2,C,H,R,N
CFD_RECAL	.EQU	00000111B	; CMD,DS --> <EMPTY>
CFD_SENSEINT	.EQU	00001000B	; CMD --> ST0,PCN
CFD_SPECIFY	.EQU	00000011B	; CMD,SRT/HUT,HLT/ND --> <EMPTY>
CFD_DRVSTAT	.EQU	00000100B	; CMD,HDS/DS --> ST3
CFD_SEEK	.EQU	00001111B	; CMD,HDS/DS --> <EMPTY>
CFD_VERSION	.EQU	00010000B	; CMD --> ST0
;
;
; Specify Command:
; +-----+-----+-----+-----+-----+-----+-----+-----+-----+
; |Byte |  7  |	 6  |  5  |  4	|  3  |	 2  |  1  |  0	|
; +-----+-----+-----+-----+-----+-----+-----+-----+-----+
; |  0	|  0  |	 0  |  0  |  0	|  0  |	 0  |  1  |  1	|
; |  1	| ----- STEP RATE ----- | -- HEAD UNLOAD TIME - |
; |  2	| ------------ HEAD LOAD TIME ----------- | NDM |
; +-----+-----+-----+-----+-----+-----+-----+-----+-----+
;
;
; Step Rate (milliseconds):		 Head Unload Time (milliseconds):	Head Load Time (milliseconds):
; +------+------+------+------+------+	 +------+------+------+------+------+	+------+------+------+------+------+
; |	 |	   BITRATE	     |	 |	|	  BITRATE	    |	|      |	 BITRATE	   |
; |  VAL | 1.0M | 500K | 300K | 250K |	 |  VAL | 1.0M | 500K | 300K | 250K |	|  VAL | 1.0M | 500K | 300K | 250K |
; +------+------+------+------+------+	 +------+------+------+------+------+	+------+------+------+------+------+
; |    0 |  8.0 | 16.0 | 26.7 | 32.0 |	 |    0 |  128 |  256 |	 426 |	512 |	|    0 |  128 |	 256 |	426 |  512 |
; |    1 |  7.5 | 15.0 | 25.0 | 30.0 |	 |    1 |    8 |   16 | 26.7 |	 32 |	|    1 |    1 |	   2 |	3.3 |	 4 |
; |    2 |  7.0 | 14.0 | 23.3 | 28.0 |	 |    2 |   16 |   32 | 53.3 |	 64 |	|    2 |    2 |	   4 |	6.7 |	 8 |
; |  ... |  ... |  ... |  ... |	 ... |	 |  ... |  ... |  ... |	 ... |	... |	|  ... |  ... |	 ... |	... |  ... |
; |   14 |  1.0 |  2.0 |  3.3 |	 4.0 |	 |   14 |  112 |  224 |	 373 |	448 |	|  126 |  126 |	 252 |	420 |  504 |
; |   15 |  0.5 |  1.0 |  1.7 |	 2.0 |	 |   15 |  120 |  240 |	 400 |	480 |	|  127 |  127 |	 254 |	423 |  508 |
; +------+------+------+------+------+	 +------+------+------+------+------+	+------+------+------+------+------+
;
; IBM PS/2 CALLS FOR:
;   STEP RATE: 3ms (6ms FOR ALL 41mm OR 720K DRIVES)
;   HEAD LOAD TIME: 15ms
;
; STATIC CONFIGURATION, NEVER CHANGES (PRIVATE)
;
FCD_MT		.EQU	000H		; MULTI-TRACK, WE DON'T USE, SET TO 0
FCD_MFM		.EQU	001H		; MFM, 0=FM, 1=MFM, WE USE MFM ALWAYS
FCD_SK		.EQU	000H		; SKIP MODE, WE DON'T USE, SET TO 0
FCD_N		.EQU	002H		; SECTOR SIZE, N=2 FOR 512 BYTES
FCD_DTL		.EQU	0FFH		; DATA LENGTH (WHEN N=0, SET TO FF OTHERWISE)
FCD_STP		.EQU	001H		; SECTOR SCAN TYPE, 1=CONTIG, 2=ALTERNATING
;
FCD_PC720	.DB	050H		; NUMBER OF CYLINDERS
		.DB	002H		; NUMBER OF HEADS
		.DB	009H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
		.DB	009H		; SECTOR COUNT
		.DW	200H		; SECTOR SIZE IN BYTES
		.DB	02AH		; GAP LENGTH (R/W)
		.DB	050H		; GAP LENGTH (FORMAT)
		.DB	(13 << 4) | 0	; SRT = 6ms, HUT = 512ms
		.DB	(4 << 1) | 1	; HLT = 16ms, ND = YES
		.DB	DOR_BR250	; DOR
		.DB	DCR_BR250	; DCR
		.IF	(($ - FCD_PC720) != FCD_LEN)
		.ECHO	"*** FCD_PC720 SIZE ERROR!!! ***\n"
		.ENDIF
;
FCD_PC144	.DB	050H		; NUMBER OF CYLINDERS
		.DB	002H		; NUMBER OF HEADS
		.DB	012H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
		.DB	012H		; SECTOR COUNT
		.DW	200H		; SECTOR SIZE IN BYTES
		.DB	01BH		; GAP LENGTH (R/W)
		.DB	06CH		; GAP LENGTH (FORMAT)
		.DB	(13 << 4) | 0	; SRT = 3ms, HUT = 256ms
		.DB	(8 << 1) | 1	; HLT = 16ms, ND = YES
		.DB	DOR_BR500	; DOR
		.DB	DCR_BR500	; DCR
		.IF	(($ - FCD_PC144) != FCD_LEN)
		.ECHO	"*** FCD_PC144 SIZE ERROR!!! ***\n"
		.ENDIF
;
FCD_PC360	.DB	028H		; NUMBER OF CYLINDERS
		.DB	002H		; NUMBER OF HEADS
		.DB	009H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
		.DB	009H		; SECTOR COUNT
		.DW	200H		; SECTOR SIZE IN BYTES
		.DB	02AH		; GAP LENGTH (R/W)
		.DB	050H		; GAP LENGTH (FORMAT)
		.DB	(13 << 4) | 0	; SRT = 6ms, HUT = 512ms
		.DB	(4 << 1) | 1	; HLT = 16ms, ND = YES
		.DB	DOR_BR250	; DOR
		.DB	DCR_BR250	; DCR
		.IF	(($ - FCD_PC360) != FCD_LEN)
		.ECHO	"*** FCD_PC360 SIZE ERROR!!! ***\n"
		.ENDIF
;
FCD_PC120	.DB	050H		; NUMBER OF CYLINDERS
		.DB	002H		; NUMBER OF HEADS
		.DB	00FH		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
		.DB	00FH		; SECTOR COUNT
		.DW	200H		; SECTOR SIZE IN BYTES
		.DB	01BH		; GAP LENGTH (R/W)
		.DB	054H		; GAP LENGTH (FORMAT)
		.DB	(10 << 4) | 0	; SRT = 6ms, HUT = 256ms
		.DB	(8 << 1) | 1	; HLT = 16ms, ND = YES
		.DB	DOR_BR500	; DOR
		.DB	DCR_BR500	; DCR
		.IF	(($ - FCD_PC120) != FCD_LEN)
		.ECHO	"*** FCD_PC120 SIZE ERROR!!! ***\n"
		.ENDIF
;
FCD_PC111	.DB	04DH		; NUMBER OF CYLINDERS
		.DB	002H		; NUMBER OF HEADS
		.DB	00FH		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
		.DB	00FH		; SECTOR COUNT
		.DW	200H		; SECTOR SIZE IN BYTES
		.DB	01BH		; GAP LENGTH (R/W)
		.DB	054H		; GAP LENGTH (FORMAT)
		.DB	(13 << 4) | 0	; SRT = 3ms, HUT = 256ms
		.DB	(25 << 1) | 1	; HLT = 50ms, ND = YES
		.DB	DOR_BR500	; DOR
		.DB	DCR_BR500	; DCR
		.IF	(($ - FCD_PC111) != FCD_LEN)
		.ECHO	"*** FCD_PC111 SIZE ERROR!!! ***\n"
		.ENDIF
;
; FCD LOOKUP TABLE (CALLED TO SET HL TO ADDRESS OF MEDIA DATA ABOVE)
; ENTRIES BELOW MUST MATCH COUNT AND VALUES OF FDMXXX IN STD.ASM
;
FCD_TBL:
		LD	HL,FCD_PC720 \ RET	; FDM720 = 0
		LD	HL,FCD_PC144 \ RET	; FDM144 = 1
		LD	HL,FCD_PC360 \ RET	; FDM360 = 2
		LD	HL,FCD_PC120 \ RET	; FDM120 = 3
		LD	HL,FCD_PC111 \ RET	; FDM111 = 4
;
; DOR BITS (3AH)
;
;	DISKIO			250KBPS		500KBPS
;	-------			-------		-------
;D7	/DC/RDY			1 (N/A)		1 (N/A)
;D6	/REDWC (DENSITY)	0 (DD)		1 (HD)
;D5	P0* (PRECOMP BIT 0)	1 \		0 \
;D4	P1* (PRECOMP BIT 1)	0 (125NS)	1 (125NS)
;D3	P2* (PRECOMP BIT 2)	0 /		0 /
;D2	MINI (BITRATE)		1 (250KBPS)	0 (500KBPS)
;D1	/MOTOR (ACTIVE LO)	1 (OFF)		1 (OFF)
;D0	TC (TERMINAL COUNT)	0 (OFF)		0 (OFF)
;
; *NOTE: FOR 9229 DATA SEPARATOR USED IN DISKIO, VALUE OF PRECOMP BITS CHANGES WITH MINI
; IF MINI=1 (250KBPS), USE 001 FOR 125NS PRECOMP, IF MINI=0, USE 010 FOR 125NS PRECOMP
;
#IF (FDMODE == FDMODE_DIO)
DOR_BR250	.EQU	10100100B	; 250KBPS W/ MOTOR ON
DOR_BR500	.EQU	11010000B	; 500KBPS W/ MOTOR ON
DOR_INIT	.EQU	11010010B	; INITIAL DEFAULT LATCH VALUE
#ENDIF
;
;	ZETA/DISKIO3		250KBPS		500KBPS
;	------------		-------		-------
;D7	/FDC_RST		1 (RUN)		1 (RUN)
;D6	DENSEL			1 (DD)		0 (HD)
;D5	P0 (PRECOMP BIT 0)	1 \		1 \
;D4	P1 (PRECOMP BIT 1)	0 (125NS)	0 (125NS)
;D3	P2 (PRECOMP BIT 2)	0 /		0 /
;D2	MINI (BITRATE)		1 (250KBPS)	0 (500KBPS)
;D1	MOTOR			0 (OFF)		0 (OFF)
;D0	TC			0 (OFF)		0 (OFF)
;
; MOTOR AND DENSITY SELECT ARE INVERTED ON ZETA/DISKIO3 VS. DIO
;
#IF ((FDMODE == FDMODE_ZETA) | (FDMODE == FDMODE_DIO3))
DOR_BR250	.EQU	11100110B	; 250KBPS W/ MOTOR ON
DOR_BR500	.EQU	10100010B	; 500KBPS W/ MOTOR ON
DOR_INIT	.EQU	10100000B	; INITIAL DEFAULT LATCH VALUE
#ENDIF
;
;	RCSMC			250KBPS		500KBPS
;	------------		-------		-------
;D7	/FDC_RST		1 (RUN)		1 (RUN)
;D6	DENSEL			0 (DD)		1 (HD)
;D5	P0 (PRECOMP BIT 0)	1 \		1 \
;D4	P1 (PRECOMP BIT 1)	0 (125NS)	0 (125NS)
;D3	P2 (PRECOMP BIT 2)	0 /		0 /
;D2	MOTORB			0 (OFF)		0 (OFF)
;D1	MOTORA			0 (OFF)		0 (OFF)
;D0	TC			0 (OFF)		0 (OFF)
;
; MOTOR INVERTED VS. DIO, DENSITY SELECT LIKE DIO
;
#IF (FDMODE == FDMODE_RCSMC)
; RCSMC HAS NO MINI (BITRATE) LATCH AT D2.  INSTEAD, D1 AND
; D2 PROVIDE INDEPENDENT MOTOR CONTROL FOR EACH DRIVE.
; MINI (BITRATE) IS A HARDWARE JUMPER (JP3)
; JP3: 1-2 IS DD (MINI HIGH) AND 2-3 IS HD (MINI LOW)
; JP3 *MUST* BE SET CORRECTLY FOR MEDIA USED
; THE CORRECT MOTOR BIT IS SET IN MOTOR ON, NEITHER SET HERE
DOR_BR250	.EQU	10100000B	; 250KBPS W/ MOTOR OFF!
DOR_BR500	.EQU	11100000B	; 500KBPS W/ MOTOR OFF!
DOR_INIT	.EQU	11100000B	; INITIAL DEFAULT LATCH VALUE
#ENDIF
;
; *** DIDE/N8/ZETA V2 ***
;
#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO) | (FDMODE == FDMODE_EPFDC) | (FDMODE == FDMODE_MBC) | (FDMODE == FDMODE_DUO))
DOR_INIT	.EQU	00001100B	; SOFT RESET INACTIVE, DMA ENABLED
DOR_BR250	.EQU	DOR_INIT
DOR_BR500	.EQU	DOR_INIT
#ENDIF
;
; DCR (ONLY APPLIES TO DIDE, N8, AND ZETA V2)
;
DCR_BR250	.EQU	01H		; 250KBPS
DCR_BR500	.EQU	00H		; 500KBPS
;
#IF (FDTRACE > 0)
;
; FDC COMMAND STRINGS
;
FCS_NOP		.DB	"NOP$"
FCS_READ	.DB	"READ$"
FCS_READDEL	.DB	"READDEL$"
FCS_WRITE	.DB	"WRITE$"
FCS_WRITEDEL	.DB	"WRITEDEL$"
FCS_READTRK	.DB	"READTRK$"
FCS_READID	.DB	"READID$"
FCS_FMTTRK	.DB	"FMTTRK$"
FCS_SCANEQ	.DB	"SCANEQ$"
FCS_SCANLOEQ	.DB	"SCANLOEQ$"
FCS_SCANHIEQ	.DB	"SCANHIEQ$"
FCS_RECAL	.DB	"RECAL$"
FCS_SENSEINT	.DB	"SENSEINT$"
FCS_SPECIFY	.DB	"SPECIFY$"
FCS_DRVSTAT	.DB	"DRVSTAT$"
FCS_SEEK	.DB	"SEEK$"
FCS_VERSION	.DB	"VER$"
;
; FDC COMMAND TABLE
;
FCT		.DB	CFD_READ \	.DW	FCS_READ
FCT_ENTSIZ	.EQU	$ - FCT
		.DB	CFD_READDEL \	.DW	FCS_READDEL
		.DB	CFD_WRITE \	.DW	FCS_WRITE
		.DB	CFD_WRITEDEL \	.DW	FCS_WRITEDEL
		.DB	CFD_READTRK \	.DW	FCS_READTRK
		.DB	CFD_READID \	.DW	FCS_READID
		.DB	CFD_FMTTRK \	.DW	FCS_FMTTRK
		.DB	CFD_SCANEQ \	.DW	FCS_SCANEQ
		.DB	CFD_SCANLOEQ \	.DW	FCS_SCANLOEQ
		.DB	CFD_SCANHIEQ \	.DW	FCS_SCANHIEQ
		.DB	CFD_RECAL \	.DW	FCS_RECAL
		.DB	CFD_SENSEINT \	.DW	FCS_SENSEINT
		.DB	CFD_SPECIFY \	.DW	FCS_SPECIFY
		.DB	CFD_DRVSTAT \	.DW	FCS_DRVSTAT
		.DB	CFD_SEEK \	.DW	FCS_SEEK
		.DB	CFD_VERSION \	.DW	FCS_VERSION
FCT_COUNT	.EQU	(($ - FCT) / FCT_ENTSIZ)	; # ENTRIES IN TABLE
#ENDIF
;
; DRIVER FUNCTION TABLE
;
FD_FNTBL:
	.DW	FD_STATUS
	.DW	FD_RESET
	.DW	FD_SEEK
	.DW	FD_READ
	.DW	FD_WRITE
	.DW	FD_VERIFY
	.DW	FD_FORMAT
	.DW	FD_DEVICE
	.DW	FD_MEDIA
	.DW	FD_DEFMED
	.DW	FD_CAP
	.DW	FD_GEOM
#IF (($ - FD_FNTBL) != (DIO_FNCNT * 2))
	.ECHO	"*** INVALID FD FUNCTION TABLE ***\n"
#ENDIF
;
FD_VERIFY:
FD_FORMAT:
FD_DEFMED:
	SYSCHKERR(ERR_NOTIMPL)		; INVALID SUB-FUNCTION
	RET
;
;
;
FD_DEVICE:
	LD	D,DIODEV_FD		; D := DEVICE TYPE
	LD	E,(IY+FD_DEV)		; E := PHYSICAL DEVICE NUMBER

	LD	A,(IY+FD_TYPE)		; DRIVE TYPE
	LD	HL,FD_DEVATTR		; DEVICE ATTR TABLE
	CALL	ADDHLA			; POINT TO ENTRY
	LD	C,(HL)			; GET IT
	LD	H,FDMODE		; H := MODE
	LD	L,FDC_MSR		; L := BASE I/O ADDRESS
	XOR	A			; SIGNAL SUCCESS
	RET
;
; FD_MEDIA
;
FD_MEDIA:
	LD	A,E			; GET FLAGS
	OR	A			; SET FLAGS
	JR	Z,FD_MEDIA4		; JUST REPORT CURRENT STATUS AND MEDIA

#IF (FDMAUTO)
	; SETUP TO READ TRK 0, HD 0, SEC 0
	LD	A,(IY+FD_DEV)		; GET DRIVE UNIT
	LD	(FCD_DS),A
	LD	A,0
	LD	(FCD_C),A
	LD	(FCD_H),A
	INC	A
	LD	(FCD_R),A
	LD	A,DOP_READID
	LD	(FCD_DOP),A
#ENDIF


	LD	A,(IY+FD_TYPE)		; GET DRIVE TYPE VALUE
	;CALL	PRTHEXBYTE		; *DEBUG*
	LD	HL,FD_MEDIAMAP		; POINT TO MEDIA MAP TABLE
	ADD	A,A			; TABLE IS WORD SIZED
	CALL	ADDHLA			; LOOKUP ENTRY
	LD	A,(HL)			; DEREFERENCE
	INC	HL
	LD	H,(HL)
	LD	L,A

#IF (FDMAUTO)
  #IF (FDTRACE < 3)
	; SUPPRESS TRACING FOR MEDIA TESTS
	LD	A,0
	LD	(FCD_TRACE),A
  #ENDIF

	LD	B,5			; 5 ATTEMPTS
FD_MEDIARETRY:
	PUSH	HL			; SAVE MEDIA MAP PTR
	CALL	FD_MEDIALOOP		; TRY IT
	POP	HL			; RECOVER MEDIA MAP PTR
	JR	Z,FD_MEDIA3		; CONTINUE ON SUCCESS
	DJNZ	FD_MEDIARETRY		; LOOP TILL DONE

FD_MEDIARETRY1:
	; NO JOY, RETURN WITH E=0 (NO MEDIA)
	LD	(IY+FD_MEDTYP),$FF	; SET MEDIA TYPE TO UNDEFINED
	LD	E,0			; NO MEDIA FLAG
	LD	A,ERR_NOMEDIA		; SIGNAL ERROR
	OR	A			; SET FLAGS
	RET

FD_MEDIALOOP:
	LD	A,(HL)			; LOAD NEXT MEDIA TYPE TO TRY
	CP	$FF			; END OF MEDIA TYPES TO TRY?
	JR	Z,FD_MEDIALOOP1		; NO MORE TO TRY, LOOP EXIT
	CALL	FD_TESTMEDIA		; TRY IT
	RET	Z			; RETURN ON SUCCESS
	INC	HL			; NEXT MEDIA TYPE TO TRY
	JR	FD_MEDIALOOP		; LOOP
FD_MEDIALOOP1:
	OR	$FF			; SIGNAL FAILURE
	RET				; RETURN

FD_TESTMEDIA:
	;CALL	PRTHEXBYTE		; *DEBUG*
	LD	(IY+FD_MEDTYP),A
	PUSH	HL
	PUSH	BC
	CALL	FD_START
	POP	BC
	POP	HL
	;CALL	PRTHEXBYTE		; *DEBUG*
	RET

FD_MEDIA3:

  #IF (FDTRACE < 3)
	; RESTORE TRACING FOR MEDIA TESTS
	LD	A,FDTRACE
	LD	(FCD_TRACE),A
  #ENDIF
#ELSE

	LD	A,(HL)
	LD	(IY+FD_MEDTYP),A

#ENDIF

FD_MEDIA4:

#IF (FDTRACE >= 3)
	LD	DE,FDSTR_SELECT
	CALL	WRITESTR
	;LD	BC,(FDDS_MEDIAADR)
	;CALL	PRTHEXWORD
#ENDIF

	; LOAD THE MEDIA BYTE
	;LD	HL,(FDDS_MEDIAADR)
	;LD	A,(HL)
	LD	A,(IY+FD_MEDTYP)	; GET CURRENT MEDIA TYPE
	ADD	A,MID_FD720		; ASSUMES MID_ VALUES ARE IN SAME ORDER AS FDM VALUES

#IF (FDTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
	LD	E,A			; MOVE MEDIA VALUE TO E
	XOR	A			; SIGNAL SUCCESS
	RET
;
;
;
FD_CAP:
	CALL	FD_GEOM		; GET GEOMETRY
	; HL=TRACKS, D=HEADS, E=SECTORS
	PUSH	HL			; SAVE TRACK COUNT
	LD	H,D			; HEADS COUNT TO H
	RES	7,H			; MAKE SURE LBA CAPABILITY BIT IS CLEARED
	CALL	MULT8			; HL := H * E FOR SECTORS / CYL
	POP	DE			; RECOVER TRACKS, E == TRACK COUNT
	LD	H,L			; MOVE WORKING COUNT L --> H
	CALL	MULT8			; HL := H * E FOR TOTAL SECTORS
	LD	DE,0			; HI WORD ALWAYS ZERO
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
;
;
FD_GEOM:
	LD	A,(IY+FD_MEDTYP)	; GET CURRENT MEDIA TYPE
	RLCA				; TABLE IS 4 BYTE ENTRIES
	RLCA				; A = A * 4
	LD	HL,FCD_TBL		; HL = START OF TABLE
	LD	D,0			; SET DE TO TABLE OFFSET
	LD	E,A
	ADD	HL,DE			; OFFSET BASED ON DESIRED MEDIA
	CALL	JPHL			; CALL THE TABLE ENTRY (SEE FCD_TBL)
	; HL NOW POINTS TO START OF DESIRED MEDIA INFO
	LD	A,(HL)			; GET TRACKS
	INC	HL			; POINT TO HEADS
	LD	D,(HL)			; GET HEADS
	SET	7,D			; SET LBA CAPABILITY BIT
	INC	HL			; POINT TO SECTORS
	LD	E,(HL)			; GET SECTORS
	LD	L,A			; L := TRACKS
	LD	H,0			; HI WORD OF TRACKS IS ALWAYS ZERO
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
; FD_INIT
;
FD_INIT:
	CALL	NEWLINE			; FORMATTING
	PRTS("FD: MODE=$")
;
#IF (FDMODE == FDMODE_DIO)
	PRTS("DIO$")
#ENDIF
;
#IF (FDMODE == FDMODE_ZETA)
	PRTS("ZETA$")
#ENDIF
;
#IF (FDMODE == FDMODE_DIO3)
	PRTS("DIO3$")
#ENDIF
;
#IF (FDMODE == FDMODE_ZETA2)
	PRTS("ZETA2$")
#ENDIF
;
#IF (FDMODE == FDMODE_DIDE)
	PRTS("DIDE$")
#ENDIF
;
#IF (FDMODE == FDMODE_N8)
	PRTS("N8$")
#ENDIF
;
#IF (FDMODE == FDMODE_RCSMC)
	PRTS("RCSMC$")
#ENDIF
;
#IF (FDMODE == FDMODE_RCWDC)
	PRTS("RCWDC$")
#ENDIF
;
#IF (FDMODE == FDMODE_DYNO)
	PRTS("DYNO$")
#ENDIF
;
#IF (FDMODE == FDMODE_EPFDC)
	PRTS("EPFDC$")
#ENDIF
;
#IF (FDMODE == FDMODE_MBC)
	PRTS("MBC$")
#ENDIF
;
#IF (FDMODE == FDMODE_DUO)
	PRTS("DUO$")
#ENDIF
;	
	PRTS(" IO=0x$")
	LD	A,FDC_MSR
	CALL	PRTHEXBYTE
	CALL	FD_DETECT		; CHECK FOR FDC
	JR	Z,FD_INIT00		; CONTINUE IF FOUND
	PRTS(" NOT PRESENT$")		; NOT ZERO, H/W NOT PRESENT
	OR	$FF			; SIGNAL FAILURE
	RET				; BAIL OUT
;	
FD_INIT00:	
	PRTS(" UNITS=$")
	LD	A, FD_DEVCNT + '0'
	CALL	COUT
;
; SETUP THE DISPATCH TABLE ENTRIES
;
	LD	B,FD_DEVCNT		; LOOP CONTROL
	LD	IY,FD_CFGTBL		; START OF CFG TABLE
FD_INIT0:
	PUSH	BC			; SAVE LOOP CONTROL
	LD	BC,FD_FNTBL		; BC := FUNC TABLE ADR
	PUSH	IY			; CFG ENTRY POINTER
	POP	DE			; COPY TO DE
	CALL	DIO_ADDENT		; ADD ENTRY, BC IS NOT DESTROYED
	CALL	FD_INITUNIT		; DO UNIT INITIALIZATION
	LD	BC,FD_CFGSIZ		; SIZE OF CFG ENTRY
	ADD	IY,BC			; BUMP IY TO NEXT ENTRY
	POP	BC			; RESTORE BC
	DJNZ	FD_INIT0		; LOOP AS NEEDED
;
	LD	A,FDTRACE
	LD	(FCD_TRACE),A
;
	LD	BC,0
	LD	(FCD_IDLECNT),BC
;
	LD	A,DOR_INIT
	LD	(FST_DOR),A
;
	CALL	FC_RESETFDC
	CALL	FD_CLRDSKCHG
;
	LD	A,TRUE
	LD	(FCD_FDCRDY),A
;
	RET
;
; HARDWARE PRESENCE DETECTION
;
; CURRENTLY JUST DETERMINES IF FDC EXISTS AT PRE-DEFINED ADDRESS,
; DOES NOT ATTEMPT TO DETERMINE THE ACTUAL VARIANT.
;
FD_DETECT:
	; BLINDLY RESET FDC (WHICH MAY OR MAY NOT EXIST)
	LD	A,DOR_INIT	; MAKE SURE INITIAL DOR VALUE IS SETUP
	LD	(FST_DOR),A	; AND PUT IN SHADOW REGISTER
	CALL	FC_RESETFDC	; RESET FDC
	
	IN_A_NN(FDC_MSR)	; READ MSR
	;CALL	PC_SPACE	; *DEBUG*
	;CALL	PRTHEXBYTE	; *DEBUG*
	
	CP	$D0		; SPECIAL CASE: DATA PENDING?
	JR	NZ,FD_DETECT1	; NOPE, MOVE ALONG
	IN_A_NN(FDC_DATA)	; SWALLOW THE PENDING DATA
	CALL	DLY32		; SETTLE
	IN_A_NN(FDC_MSR)	; ... AND REREAD THE STATUS
	;CALL	PC_SPACE	; *DEBUG*
	;CALL	PRTHEXBYTE	; *DEBUG*
;
FD_DETECT1:
	CP	$80		; WE EXPECT $80
	JR	Z,FD_DETECT2	; IF SO, CONTINUE CHECKING
;
	; WE HAVE SEEN AN FDC THAT NEEDS A SECOND READ TO GET
	; DESIRED VALUE, SO TRY ONE MORE TIME
	CALL	DLY32		; WAIT A BIT
	IN_A_NN(FDC_MSR)	; ... AND REREAD THE STATUS
	;CALL	PC_SPACE	; *DEBUG*
	;CALL	PRTHEXBYTE	; *DEBUG*
	CP	$80		; CHECK FOR CORRECT VALUE
	RET	NZ		; RETURN IF FAILED WITH ZF RESET
;
FD_DETECT2:
	; AS A FINAL TEST, WE REREAD THE MSR USING A DIFFERENT
	; OPCODE.  THIS SHOULD ELIMINATE ANY FALSE POSITIVES FROM
	; A FLOATING BUS.
	CALL	DLY32		; WAIT A BIT
	LD	C,FDC_MSR	; POINT TO MSR PORT
	EZ80_IO
	IN	A,(C)		; READ USING (C) FORM OF INPUT
	;CALL	PC_SPACE	; *DEBUG*
	;CALL	PRTHEXBYTE	; *DEBUG*
	CP	$80		; CHECK FOR CORRECT VALUE
	RET			; RETURN WITH ZF SET CORRECTLY
;
; UNIT INITIALIZATION
;
FD_INITUNIT:
	LD	(IY+FD_STAT),0		; CLEAR STATUS
	LD	(IY+FD_MEDTYP),$FF	; SET UNKNOWN MEDIA TYPE
	LD	(IY+FD_CURTRK),$FE	; SPECIAL VALUE FOR CURTRK
	RET
;
; FD_IDLE QUIESCES THE FLOPPY SUBSYSTEM (MOTOR OFF)
; AFTER BEING CALLED ENOUGH TIMES...
; SHOULD IT INVALIDATE THE BUFFER???
;
FD_IDLE:
	LD	BC,(FCD_IDLECNT)
	LD	A,B
	OR	C
	RET	Z			; COUNTER ALREADY FIRED

	DEC	BC			; DECREMENT COUNTER
	LD	(FCD_IDLECNT),BC	; SAVE IT
	LD	A,B
	OR	C
	RET	NZ			; STILL COUNTING DOWN, RETURN

	CALL	FC_MOTOROFF		; COUNTER JUST EXPIRED, SHUTDOWN MOTOR!
	RET
;
; FD_STATUS
;
FD_STATUS:
	LD	A,(IY+FD_CURTRK)	; A = CURRENT TRACK

	CP	0FFH			; IS CURRENT TRACK = $FF?
	JR	Z,FD_STATUS1		; IF SO, NOT READY

	XOR	A			; A = 0 = OK
	RET				; RETURN

FD_STATUS1:
	OR	A			; A ALREADY = $FF, JUST SET FLAGS
	RET
;
;
;
FD_RESET:
	XOR	A			; ALWAYS OK
	RET
;
; FD_CLRDSKCHG
;
FD_CLRDSKCHG:
	; PROCESS ANY PENDING DISK CHANGE NOTIFICATIONS
	LD	B,5
FD_CLRDSKCHG1:
	PUSH	BC
	CALL	FC_SENSEINT
	POP	BC
	LD	A,(FST_RC)
	CP	FRC_DSKCHG
	RET	NZ			; NO MORE DISK CHANGE NOTIFICATIONS
	DJNZ	FD_CLRDSKCHG1
;
; FD_WTSEEK
;
; WAIT FOR PENDING SEEK OPERATION TO COMPLETE BY POLLING SENSEINT
; AND WAITING FOR ABTERM OR OK.
;
FD_WTSEEK:
	LD	BC,1000H

FD_WTSEEKLOOP:
	PUSH	BC
	CALL	FC_SENSEINT
	POP	BC

	LD	A,(FST_RC)		; CHECK RC
	CP	FRC_ABTERM		; ABTERM = DONE/FAILED
	JR	Z,FD_RETRC
	CP	FRC_OK			; OK = DONE/SUCCESS
	JR	Z,FD_RETRC

	DEC	BC			; CHECK LOOP COUNTER IN BC
	LD	A,B			; "
	OR	C			; "
	JR	NZ,FD_WTSEEKLOOP	; LOOP UNTIL COUNTER EXHAUSTED

FD_RETRC:
	LD	A,(FST_RC)
	OR	A
	RET				; TIMEOUT/FAILED
;
; FD_FDCRESET
;
FD_FDCRESET:
	CALL	FC_RESETFDC

	CALL	FD_CLRDSKCHG

	LD	A,TRUE
	LD	(FCD_FDCRDY),A

	; MARK ALL DRIVES AS NEEDING RECALIBRATION
	; NOTE THAT IF THE VALUE IS CURRENTLY $FF,
	; WE NEED TO LEAVE IT ALONE, SO WE 'OR' IN THE
	; $FE TO AVOID THIS SCENARIO.

	PUSH	IY			; SAVE CURRENT IY
	LD	B,FD_DEVCNT		; LOOP CONTROL
	LD	DE,FD_CFGSIZ		; SIZE OF CFG ENTRY
	LD	IY,FD_CFGTBL		; START OF CFG TABLE
FD_FDCRESET1:
	LD	A,(IY+FD_CURTRK)	; GET CURRENT TRACK
	OR	$FE			; APPLY NEW VALUE
	LD	(IY+FD_CURTRK),A	; UPDATE TRACK
	ADD	IY,DE			; BUMP IY TO NEXT ENTRY
	DJNZ	FD_FDCRESET1		; LOOP AS NEEDED
	POP	IY			; RESTORE IY

	RET
;
; FD_DRIVERESET
;
; ATTEMPT TO FULLY RESET FLOPPY DRIVE, PRIMARILY RECALIBRATE
;
FD_DRIVERESET:
	CALL	FC_SPECIFY
	RET	NZ			; ERROR, BAIL OUT

	CALL	FC_RECAL
	RET	NZ			; ERROR, BAIL OUT

	; FIRST RECAL MAY FAIL TO REACH TRACK 0
	; SO WE TRY ONCE MORE IN CASE OF A FAILURE
	CALL	FD_WTSEEK
	RET	Z

	; SECOND TRY, ONLY IF NEEDED
	CALL	FC_RECAL
	RET	NZ			; ERROR, BAIL OUT

	CALL	FD_WTSEEK
	RET
;
;
;
FD_SEEK:
	; DE:HL CONTAINS EITHER LBA OR CHS
	BIT	7,D			; TEST LBA BIT
	JR	Z,FD_SEEK9		; IF NOT LBA, JUST SAVE INCOMING VALUE
	; NEED TO CONVERT LBA IN DE:HL TO CHS
	; NOTE: FLOPPY LBA WILL NEVER EXCEED 16 BITS, SO WE IGNORE DE ENTIRELY
	PUSH	HL			; SAVE HL
	CALL	FD_GEOM			; E := SPT, D := HDS
	POP	HL			; RESTORE HL
	RET	NZ			; BAIL OUT ON ERROR
	RES	7,D			; MAKE SURE LBA BIT IS CLEARED
	LD	(FD_CURGEOM),DE		; SAVE AS FD_CURSPT & FD_CURHDS
	LD	A,(FD_CURSPT)		; A := SECTORS PER TRACK
	LD	D,0			; DE := SPT
	LD	E,A
	CALL	DIV16			; DIVIDE, REMAINDER (SECTOR #) IN HL
	PUSH	HL			; SAVE SECTOR #
	PUSH	BC			; CYLINDERS AND HEADS BACK TO HL
	POP	HL
	LD	A,(FD_CURHDS)		; A := HEADS PER CYLINDER
	LD	D,0			; DE : = HEADS PER CYLINDER
	LD	E,A
	CALL	DIV16			; DIVIDE, BC := TRACK, REMAINDER (HEAD #) IN HL
	PUSH	HL			; SAVE HEAD #
	PUSH	BC			; COPY TRACK # TO HL
	POP	HL
	POP	BC			; RECOVER HEAD #
	LD	D,C			; HEAD # TO D
	POP	BC			; RECOVER SECTOR #
	LD	E,C			; SECTOR # TO E
FD_SEEK9:	; NOT LBA, JUST SAVE THE CHS VALUE IN CFG ENTRY
	PUSH	HL			; SAVE INCOMING HL TO (SP)
	LD	A,FD_HST		; A := HST OFFSET IN CFG ENTRY
	CALL	LDHLIYA			; HL := HST VALUE ADR
	EX	(SP),HL			; RESTORE INCOMING HL, HST ADR TO (SP)
	POP	BC			; HST ADR TO BC
	CALL	ST32			; SAVE HST IN CFG ENTRY
	XOR	A			; SIGNAL SUCCESS
	RET
;
FD_READ:
	CALL	HB_DSKREAD		; HOOK HBIOS DISK READ SUPERVISOR
	LD	(FD_DSKBUF),HL		; SAVE DISK BUFFER ADDRESS
	LD	A,DOP_READ
	JR	FD_RW
;
FD_WRITE:
	CALL	HB_DSKWRITE		; HOOK HBIOS DISK WRITE SUPERVISOR
	LD	(FD_DSKBUF),HL		; SAVE DISK BUFFER ADDRESS
	LD	A,DOP_WRITE
	JR	FD_RW
;
FD_RW:
	LD	(FCD_DOP),A		; SAVE REQUESTED DISK OPERATION
	LD	A,E			; BLOCK COUNT TO A
	OR	A			; SET FLAGS
	RET	Z			; ZERO SECTOR I/O, RETURN W/ E=0 & A=0
	LD	B,A			; INIT SECTOR DOWNCOUNTER
	LD	C,0			; INIT SECTOR READ/WRITE COUNT

	PUSH	BC			; SAVE COUNTERS
	CALL	FD_GEOM			; E := SPT, D := HDS
	POP	BC			; RESTORE COUNTERS
	JR	NZ,FD_RW4		; BAIL OUT ON ERROR
	RES	7,D			; MAKE SURE LBA BIT IS CLEARED
	LD	(FD_CURGEOM),DE		; SAVE AS FD_CURSPT & FD_CURHDS

FD_RW1:
	PUSH	BC			; SAVE COUNTERS
	CALL	FD_RUN			; PERFORM SECTOR READ/WRITE
	JR	NZ,FD_RW3		; IF ERROR, SKIP INCREMENT

	; INCREMENT SECTOR AND CHECK FOR TRACK OVERFLOW
	LD	A,FD_HSTSEC		; HST SECTOR OFFSET IN CFG
	CALL	LDHLIYA			; HL := ADR OF HST SECTOR
	INC	(HL)			; INCREMENT SECTOR
	LD	A,(FD_CURSPT)		; A := SECTORS PER TRACK
	CP	(HL)			; COMPARE SPT TO CURRENT SECTOR
	JR	NZ,FD_RW2		; IF NO OVERFLOW, DONE

	; RESET SECTOR, INCREMENT HEAD, AND CHECK FOR CYLINDER OVERFLOW
	LD	(HL),0			; RESET SECTOR TO ZERO
	INC	HL			; POINT TO HST HEAD
	INC	(HL)			; INCREMENT HEAD
	LD	A,(FD_CURHDS)		; A : = HEADS
	CP	(HL)			; COMPARE HEADS TO HST HEAD
	JR	NZ,FD_RW2		; IF NO OVERFLOW, DONE

	; RESET HEAD AND INCREMENT TRACK
	LD	(HL),0			; RESET HEAD TO ZERO
	DEC	HL			; POINT TO HST TRACK
	DEC	HL			; ...
	DEC	HL			; ...
	INC	(HL)			; INCREMENT HST TRACK LSB

FD_RW2:
	LD	HL,FD_DSKBUF+1		; POINT TO MSB OF BUFFER ADR
	INC	(HL)			; BUMP DMA BY
	INC	(HL)			; ... 512 BYTES
	XOR	A			; SIGNAL SUCCESS
FD_RW3:
	POP	BC			; RECOVER COUNTERS
	JR	NZ,FD_RW4		; IF ERROR, BAIL OUT
	INC	C			; BUMP COUNT OF SECTORS READ
	DJNZ	FD_RW1			; LOOP AS NEEDED
FD_RW4:
	LD	E,C			; SECTOR READ COUNT TO E
	LD	HL,(FD_DSKBUF)		; CURRENT DMA TO HL
	OR	A			; SET FLAGS BASED ON RETURN CODE
	RET				; AND RETURN, A HAS RETURN CODE

;
FD_RUN:
	; UPDATE DRIVE SELECTION
	LD	A,(IY+FD_DEV)		; GET UNIT
	LD	(FCD_DS),A		; UPDATE FCD_DS TO NEW VALUE

	; MAP HSTTRK TO FCD_H, FCD_C
	LD	A,(IY+FD_HSTTRK)	; GET TRACK VALUE (CYLINDER IS MORE ACCURATE)
	LD	(FCD_C),A		; ... AND MOVE IT TO CYL PARM
	LD	A,(IY+FD_HSTHD)		; GET HEAD VALUE
	LD	(FCD_H),A		; ... AND MOVE IT TO HEAD PARM

	; MAP HSTSEC TO FCD_R
	LD	A,(IY+FD_HSTSEC)
	INC	A			; SWITCH FROM ZERO BASED TO ONE BASED
	LD	(FCD_R),A

	; SET RETRY COUNTER
	LD	B,5
FD_RETRY:
	PUSH	BC
	CALL	FD_START
	POP	BC

	LD	A,(FST_RC)		; CHECK RESULT
	OR	A
	RET	Z			; SUCCESS

	DJNZ	FD_RETRY		; RETRY TILL COUNTER EXHAUSTED

#IF (FDTRACE == 1)
	CALL	FC_PRTRESULTS
#ENDIF

	; RETURN APPROPRIATE HBIOS ERROR
	LD	A,(FST_RC)
	CP	FRC_NOTWRIT
	LD	A,ERR_READONLY		; ASSUME WRITE PROTECTED ERROR
	JR	Z,FD_RETRY1
	LD	A,ERR_IO		; OTHERWISE I/O ERROR
FD_RETRY1:
	OR	A			; SET FLAGS
	RET				; AND GIVE UP
;
;
;
FD_START:
;
;;;#IF (DSKYENABLE)
;;;#IF (DSKYDSKACT)
	LD	A,4
	CALL	LDHLIYA
	CALL	HB_DSKACTCHS		; SHOW ACTIVITY
;;;#ENDIF
;;;#ENDIF
;
	LD	A,(FCD_FDCRDY)
	CP	TRUE
	CALL	NZ,FD_FDCRESET

	; COPY MEDIA CONFIG INTO FCD
	; THIS IS HERE TO ACCOMMODATE DIFFERENT MEDIA
	; IN DIFFERENT FLOPPY UNITS.
	LD	A,(IY+FD_MEDTYP)	; A = MEDIA BYTE
	RLCA				; TABLE IS 4 BYTE ENTRIES
	RLCA				; A = A * 4
	LD	HL,FCD_TBL		; HL = START OF TABLE
	LD	D,0			; SET DE TO TABLE OFFSET
	LD	E,A
	ADD	HL,DE			; OFFSET BASED ON DESIRED MEDIA
	CALL	JPHL			; CALL THE TABLE ENTRY (SEE FCD_TBL)
	LD	DE,FCD			; DE = DESTINATION
	LD	BC,FCD_LEN		; BC = BYTES TO COPY
	LDIR				; BYTES COPY FROM MDB TO FCD

	CALL	FC_MOTORON		; INCLUDES LATCH SETUP

	LD	A,(IY+FD_CURTRK)
	CP	0FEH			; FF = DRIVE NEEDS TO BE RESET
	JR	C,FD_RUN0		; NO RESET NEEDED, BYPASS

	CALL	FD_DRIVERESET
	JR	NZ,FD_RUNERR

	LD	(IY+FD_CURTRK),0	; ZERO CUR TRACK POS

FD_RUN0:
	; COMPARE CURRENT TRACK WITH REQUESTED TRACK TO SEE IF SEEK NEEDED
	LD	A,(FCD_C)
	CP	(IY+FD_CURTRK)
	JR	Z,FD_RUN1		; FDDS_TRKADR == FCD_C, SKIP SEEK

	; INITIATE SEEK TO NEW TRACK
	CALL	FC_SEEK
	JR	NZ,FD_RUNERR

	; WAIT FOR SEEK TO COMPLETE
	CALL	FD_WTSEEK
	JR	NZ,FD_RUNERR

	; RECORD NEW CURRENT TRACK
	LD	A,(FCD_C)
	LD	(IY+FD_CURTRK),A

FD_RUN1:
	; GET THE REQUESTED OPERATION
	LD	A,(FCD_DOP)

	; SETUP RETURN ADDRESS
	LD	HL,FD_RUNCHK
	PUSH	HL

	; DISPATCH TO FUNCTION
	CP	DOP_READ
	JR	Z,FC_READ
	CP	DOP_WRITE
	JR	Z,FC_WRITE
	CP	DOP_READID
	JR	Z,FC_READID
	SYSCHKERR(ERR_NOFUNC)
	RET

FD_RUNCHK:

FD_RUNEXIT:
	LD	A,(FST_RC)
	OR	A
	RET	Z

FD_RUNERR:
	; INDICATE THAT A CONTROLLER RESET IS DESIRED
	LD	A,FALSE
	LD	(FCD_FDCRDY),A

	; FLAG DRIVE IN ERROR STATUS BY SETTING TRKADR == FF
	LD	(IY+FD_CURTRK),$FF

	JP	FD_RETRC

;
;===============================================================================
; FLOPPY DISK CONTROL SERVICES (PHYSICAL DEVICE CONTROL FOR FDC HARDWARE)
;===============================================================================
;
; ENTRY POINTS FOR FDC COMMANDS
;
FC_READ:
	LD	A,CFD_READ | 11100000B
	CALL	FC_SETUPIO
	JP	FOP

FC_WRITE:
	LD	A,CFD_WRITE | 11000000B
	CALL	FC_SETUPIO
	JP	FOP

FC_READID:
	LD	A,CFD_READID | 01000000B
	CALL	FC_SETUPCMD
	JP	FOP

FC_RECAL:
	LD	A,CFD_RECAL | 00000000B
	CALL	FC_SETUPCMD
	JP	FOP		; FIX: DO WE NEED TO REMOVE HDS BITS FROM SECOND BYTE?

FC_SENSEINT:
	LD	A,CFD_SENSEINT | 00000000B
	CALL	FC_SETUPCMD
	LD	A,1		; GENERIC COMMAND, BUT JUST FIRST COMMAND CODE
	LD	(FCP_LEN),A
	JP	FOP

FC_SPECIFY:
	LD	A,CFD_SPECIFY | 00000000B
	CALL	FC_SETUPSPECIFY
	JP	FOP

FC_DRVSTAT:
	LD	A,CFD_DRVSTAT | 00000000B
	CALL	FC_SETUPCMD
	JP	FOP

FC_SEEK:
	LD	A,CFD_SEEK | 00000000B
	CALL	FC_SETUPSEEK
	JP	FOP
;
; HELPER FUNCTIONS TO SETUP CMDBUF
;
FC_SETUPCMD:
	; TRICKY...  THE INCOMING BYTE IN A MUST CONTAIN THE COMMAND CODE ITSELF
	; IN THE LOW 5 BITS PLUS IT MUST SET WHICH OF THE DESIRED BITS IT WANTS
	; IN THE HIGH 3 BITS.  WE 'AND' THIS WITH THE TEMPATE BITS TO PRODUCE
	; THE CORRECT FINAL COMMAND BYTE
	LD	DE,FCP_BUF
	AND	5FH			; MT=0, MFM=1, SK=0, CMD=11111
	LD	(DE),A			; SAVE THE BYTE
	AND	00011111B		; ISOLATE JUST THE COMMAND BITS
	LD	(FCP_CMD),A		; SAVE IT FOR LATER
	INC	DE

	LD	A,(FCD_H)		; START WITH HDS
	AND	01H			; MASK TO REMOVE IRRELEVANT BITS FOR SAFETY
	RLCA				; MAKE ROOM FOR DS BITS
	RLCA				;
	LD	B,A			; SAVE WHAT WE HAVE SO FAR IN B
	LD	A,(FCD_DS)		; GET DS VALUE
	AND	03H			; MASK TO REMOVE IRRELEVANT BITS FOR SAFETY
	OR	B			; COMBINE WITH SAVED
	LD	(DE),A			; SAVE THE BYTE
	INC	DE

	LD	A,2			; LENGTH IS 2 BYTES AT THIS POINT
	LD	(FCP_LEN),A

	RET

FC_SETUPIO:
	CALL	FC_SETUPCMD

	LD	A,(FCD_C)
	LD	(DE),A
	INC	DE

	LD	A,(FCD_H)
	LD	(DE),A
	INC	DE

	LD	A,(FCD_R)
	LD	(DE),A
	INC	DE

	LD	A,FCD_N
	LD	(DE),A
	INC	DE

	;LD	A,(FCD_EOT)
	LD	A,(FCD_R)		; READ ONLY ONE SECTOR
	LD	(DE),A
	INC	DE

	LD	A,(FCD_GPL)
	LD	(DE),A
	INC	DE

	LD	A,FCD_DTL
	LD	(DE),A
	INC	DE

	LD	A,9
	LD	(FCP_LEN),A

	RET

FC_SETUPSEEK:
	CALL	FC_SETUPCMD		; START WITH GENERIC IO CMD

	LD	A,(FCD_C)
	LD	(DE),A
	INC	DE

	LD	A,3
	LD	(FCP_LEN),A

	RET

FC_SETUPSPECIFY:
	CALL	FC_SETUPCMD
	DEC	DE			; BACKUP 1 BYTE, WE ONLY WANT FIRST BYTE

	LD	A,(FCD_SRTHUT)
	LD	(DE),A			; SAVE THE BYTE
	INC	DE

	LD	A,(FCD_HLTND)
	LD	(DE),A			; SAVE THE BYTE
	INC	DE

	LD	A,3
	LD	(FCP_LEN),A

	RET
;
; SET FST_DOR
;
FC_SETDOR:
	LD	(FST_DOR),A
	OUT_NN_A(FDC_DOR)
#IF (FDTRACE >= 3)
	CALL	NEWLINE
	LD	DE,FDSTR_DOR
	CALL	WRITESTR
	LD	DE,FDSTR_ARROW
	CALL	WRITESTR
	CALL	PC_SPACE
	LD	A,(FST_DOR)
	CALL	PRTHEXBYTE
#ENDIF
	RET
;
; SET FST_DCR
;
#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO) | (FDMODE == FDMODE_EPFDC) | (FDMODE == FDMODE_MBC) | (FDMODE == FDMODE_DUO))
;
FC_SETDCR
	LD	(FST_DCR),A
	OUT_NN_A(FDC_DCR)
#IF (FDTRACE >= 3)
	CALL	NEWLINE
	LD	DE,FDSTR_DCR
	CALL	WRITESTR
	LD	DE,FDSTR_ARROW
	CALL	WRITESTR
	CALL	PC_SPACE
	LD	A,(FST_DCR)
	CALL	PRTHEXBYTE
#ENDIF
	RET
;
#ENDIF
;
; RESET FDC BY PULSING BIT 7 OF LATCH LOW
;
FC_RESETFDC:
#IF (FDTRACE >= 3)
	LD	DE,FDSTR_RESETFDC
	CALL	WRITESTR
#ENDIF
	LD	A,(FST_DOR)
	PUSH	AF

#IF ((FDMODE == FDMODE_ZETA) | (FDMODE == FDMODE_DIO3) | (FDMODE == FDMODE_RCSMC))
	RES	7,A
#ENDIF
#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO) | (FDMODE == FDMODE_EPFDC) | (FDMODE == FDMODE_MBC) | (FDMODE == FDMODE_DUO))
	LD	A,0
#ENDIF
	CALL	FC_SETDOR
	CALL	DELAY
	POP	AF
	CALL	FC_SETDOR

	LD	DE,150			; DELAY: 16us * 150 = 2.4ms
	CALL	VDELAY
	RET
;
; PULSE TERMCT TO TERMINATE ANY ACTIVE EXECUTION PHASE
;
FC_PULSETC:
; PULSING TC NO LONGER REQUIRED BECAUSE WE ONLY READ A SINGLE SECTOR
;
;#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO) | (FDMODE == FDMODE_EPFDC) | (FDMODE == FDMODE_MBC) | (FDMODE == FDMODE_DUO))
;	IN	A,(FDC_TC)
;#ELSE
;	LD	A,(FST_DOR)
;	SET	0,A
;	OUT	(FDC_DOR),A
;	RES	0,A
;	OUT	(FDC_DOR),A
;#ENDIF
	RET
;
; SET FST_DOR FOR MOTOR CONTROL ON
;
FC_MOTORON:
;	LD	BC,300H
;	LD	BC,50H
;	LD	(FCD_IDLECNT),BC

	LD	A,(CB_CPUMHZ)
	RLCA
	LD	(FCD_IDLECNT),A

#IF (FDTRACE >= 3)
	LD	DE,FDSTR_MOTON
	CALL	WRITESTR
#ENDIF
#IF ((FDMODE == FDMODE_DIO) | (FDMODE == FDMODE_ZETA) | (FDMODE == FDMODE_DIO3))
	LD	A,(FST_DOR)
	PUSH	AF

	LD	A,(FCD_DOR)		; GET NEW LATCH VALUE (W/ MOTOR ON)
	CALL	FC_SETDOR		; AND IMPLEMENT IT

	POP	AF
#IF ((FDMODE == FDMODE_ZETA) | (FDMODE == FDMODE_DIO3))
	XOR	00000010B		; MOTOR BIT INVERTED ON ZETA
#ENDIF
	BIT	1,A			; SET FLAGS SET BASED ON CURRENT MOTOR BIT
	RET	Z			; MOTOR WAS PREVIOUSLY ON, WE ARE DONE
#ENDIF
#IF (FDMODE == FDMODE_RCSMC)
	LD	A,(FCD_DS)		; GET DRIVE SELECTED (0 OR 1)
	LD	C,%00000010		; MASK FOR MOTORA ON (BIT 1 IS MOTORA)
	OR	A			; SET FLAGS BASED ON FCD_DS
	JR	Z,FC_MOTORON1		; IF FCD_DS == 0, MOTORA IS CORRECT
	LD	C,%00000100		; ELSE MASK FOR MOTORB ON (BIT 2 IS MOTORB)
FC_MOTORON1:
	LD	A,(FST_DOR)		; GET CURRENT DOR VALUE
	PUSH	AF			; SAVE IT
	LD	A,(FCD_DOR)		; GET NEW DOR VALUE (W/O MOTOR BITS SET)
	OR	C			; ADD THE MOTOR BITS
	CALL	FC_SETDOR		; AND IMPLEMENT NEW VALUE
	POP	AF			; RECOVER PREVIOUS DOR VALUE
	AND	%00000110		; ISOLATE PREVIOUS MOTOR BITS
	CP	C			; COMPARE TO NEW MOTOR BITS
	RET	Z			; SKIP DELAY, MOTOR WAS ALREADY ON
#ENDIF
#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO) | (FDMODE == FDMODE_EPFDC) | (FDMODE == FDMODE_MBC) | (FDMODE == FDMODE_DUO))
	; SETUP DCR FOR DIDE HARDWARE
	LD	A,(FCD_DCR)		; GET NEW DCR VALUE
	CALL	FC_SETDCR		; AND IMPLEMENT IT

	LD	HL,FST_DOR		; POINT TO FDC_DOR
	LD	A,(HL)			; START WITH CURRENT DOR
	PUSH	AF
	AND	11111100B		; GET RID OF ANY ACTIVE DS BITS
	LD	C,A			; SAVE IT FOR NOW
	LD	A,(FCD_DS)		; NOW GET CURRENT DS
	LD	B,A			; PUT IN B FOR LATER
	OR	C			; COMBINE WITH SAVED DOR
	LD	C,A			; RE-SAVE IT
	INC	B			; SET UP B AS LOOP COUNTER (DS + 1)
	LD	A,00001000B		; STARTING BIT PATTERN FOR MOTOR
FC_MOTORON1:
	RLA				; SHIFT LEFT
	DJNZ	FC_MOTORON1		; DS TIMES
	OR	C			; COMBINE WITH SAVED
	LD	(HL),A			; COMMIT THE NEW VALUE TO FST_DOR
	CALL	FC_SETDOR		; OUTPUT TO CONTROLLER

	LD	C,A
	POP	AF
	CP	C
	RET	Z			; MOTOR WAS PREVIOUSLY ON
#ENDIF

#IF (FDTRACE >= 3)
	LD	DE,FDSTR_MOTDELAY
	CALL	WRITESTR
#ENDIF
	CALL	LDELAY			; DELAY FOR MOTOR SPINUP IF NOT PREVIOUSLY ON
	RET
;
; SET FST_DOR FOR MOTOR CONTROL OFF
;
FC_MOTOROFF:
	LD	A,(FCD_FDCRDY)
	CP	TRUE
	CALL	NZ,FD_FDCRESET

	LD	A,DOR_INIT
	CALL	FC_SETDOR		; OUTPUT TO CONTROLLER

#IF (FDTRACE >= 3)
	LD	DE,FDSTR_MOTOFF
	CALL	WRITESTR
#ENDIF
	RET
;
;===============================================================================
; FDC OPERATIONS
;===============================================================================
;
FOP:
;
; INITIALIZATION
;
	LD	A,0
	LD	(FRB_LEN),A

	LD	A,FRC_OK
	LD	(FST_RC),A
;
; CLEAR FDC, DISCARD ANY PENDING BYTES (GARBAGE?)
;
	LD	B,0			; B IS LOOP COUNTER
FOP_CLR1:
	CALL	DELAY			; FDC MAY TAKE UP TO 12us TO UPDATE MSR
	IN_A_NN(FDC_MSR)		; GET STATUS
	AND	0C0H			; ISOLATE HIGH NIBBLE, RQM/DIO/NDM/CB
	CP	0C0H			; LOOKING FOR RQM=1, DIO=1, BYTES PENDING
	JR	NZ,FOP_CMD1		; NO BYTES PENDING, GO TO NEXT PHASE
	IN_A_NN(FDC_DATA)		; GET THE PENDING BYTE AND DISCARD
	DJNZ	FOP_CLR1
	JP	FOP_TOFDCRDY		; OTHERWISE, TIMEOUT
;
; SEND COMMAND
;
FOP_CMD1:
	LD	HL,FCP_BUF
	LD	A,(FCP_LEN)
	LD	D,A			; D = CMD BYTES TO SEND

FOP_CMD2:	; START OF LOOP TO SEND NEXT BYTE
	LD	B,0			; B IS LOOP COUNTER

FOP_CMD4:	; START OF STATUS LOOP, WAIT FOR FDC TO BE READY FOR BYTE
	CALL	DELAY			; FDC MAY TAKE UP TO 12us TO UPDATE MSR
	IN_A_NN(FDC_MSR)		; READ MAIN STATUS REGISTER
	AND	0C0H			; ISOLATE RQM/DIO
	CP	080H			; LOOKING FOR RQM=1, DIO=0 (FDC READY FOR A BYTE)
	JR	Z,FOP_CMD6		; GOOD, GO TO SEND BYTE
	CP	0C0H			; HMMMM... RQM=1 & DIO=1, FDC WANTS TO SEND US DATA, UNEXPECTED
	JP	Z,FOP_RES		; GO IMMEDIATELY TO RESULTS???
	DJNZ	FOP_CMD4		; LOOP TILL COUNTER EXHAUSTED
	JP	FOP_TOSNDCMD		; COUNTER EXHAUSTED, TIMEOUT / EXIT

FOP_CMD6:	; SEND NEXT BYTE
	LD	A,(HL)			; POINT TO NEXT BYTE TO SEND
	OUT_NN_A(FDC_DATA)		; PUSH IT TO FDC
	INC	HL			; INCREMENT POINTER FOR NEXT TIME
	DEC	D			; DECREMENT NUM BYTES LEFT TO SEND
	JR	NZ,FOP_CMD2		; DO NEXT BYTE
;
; EXECUTION PHASE
;
FOP_X1:
	LD	A,(FCP_CMD)

	CP	CFD_READ
	JR	Z,FXR_READ
	CP	CFD_WRITE
	JR	Z,FXR_WRITE
	CP	CFD_READID
	JR	Z,FXR_NULL
	JP	FOP_RES
;;
;; EXECUTION ROUTINES
;;
;
; NULL EXECUTION, NO DATA TO READ/WRITE (USED BY SPECIFY, READID, ETC.)
;
; DO NOTHING, BUT WAIT FOR EXEC B IT TO CLEAR FDC READY.
; LOOP NEEDS TO ALLOW FOR 2 FULL ROTATIONS OF THE DISK
; WHICH IS 400ms AT 300RPM
;
FXR_NULL:
	LD	BC,$7000		; LOOP COUNTER, $7000 * 16us = ~485ms
FXR_NULL1:
	CALL	DELAY			; FDC MAY TAKE UP TO 12us TO UPDATE MSR
	IN_A_NN(FDC_MSR)		; GET MSR
	AND	0E0H			; ISOLATE RQM/DIO/NDM
	CP	0C0H			; WE WANT RQM=1,DIO=1,NDM=0 (READY TO READ A BYTE W/ EXEC INACTIVE)
	JP	Z,FOP_RES		; EXEC DONE, EXIT CLEAN W/O PULSING TC
	;JP	Z,FXR_NULL2		; *DEBUG*
	DEC	BC			; DECREMENT COUNTER (16 BIT)
	LD	A,B			; CHECK FOR ZERO
	OR	C			; "
	JR	NZ,FXR_NULL1		; NOT ZERO YET, KEEP CHECKING
	JP	FOP_TOEXEC		; TIMEOUT EXIT
;FXR_NULL2:	; *DEBUG*
;	CALL	PRTHEXBYTE
;	CALL	PRTHEXWORD
;	JP	FOP_RES
;
; READ DATA
;
FXR_READ:
	HB_DI				; TIME CRITICAL , INTERRUPTS WILL CAUSE I/O ERRS
	LD	HL,(FD_DSKBUF)		; POINT TO SECTOR BUFFER START
	LD	DE,(FCD_SECSZ)
	; TIMEOUT COUNTER IS CPU MHZ / 4 (MAKING SURE IT IS AT LEAST 1)
;	LD	A,(CPUMHZ + 3) / 4
	LD	A,(CB_CPUMHZ)		; GET CPU SPEED IN MHZ
	ADD	A,3			; ROUND UP
	SRL	A			; SHIFT RIGHT TWICE
	;SRL	A			; ... TO DIVIDE BY 4
	;INC	A			; MAKE SURE RESULT IS AT LEAST 1
	LD	(FCD_TO),A		; INIT TIMEOUT COUNTER
FXRR1	LD	C,0			; OUTER LOOP TIMEOUT COUNTER
FXRR2:	LD	B,0			; SETUP FOR 256 ITERATIONS
FXRR3:	IN_A_NN(FDC_MSR)		; GET MSR
	CP	0F0H			; WE WANT RQM=1,DIO=1,NDM=1,BUSY=1 (READY TO RECEIVE A BYTE W/ EXEC ACTIVE)
	JR	Z,FXRR4			; GOT IT, DO BYTE READ
	DJNZ	FXRR3			; NOT READY, LOOP IF COUNTER NOT ZERO
	JR	FXRR5			; COUNTER ZERO, GO TO OUTER LOOP LOGIC

FXRR4:	IN_A_NN(FDC_DATA)		; GET PENDING BYTE
	LD	(HL),A			; STORE IT IN BUFFER
	INC	HL			; INCREMENT THE BUFFER POINTER
	DEC	DE			; DECREMENT BYTE COUNT
	LD	A,D
	OR	E
	JR	NZ,FXRR2		; IF NOT ZERO, REPEAT LOOP
	JR	FXR_END			; CLEAN EXIT

FXRR5:					; OUTER LOOP, REALLY ONLY HAPPENS WHEN WAITING FOR FIRST BYTE OR ABORTED
	AND	0E0H			; ISOLATE RQM/DIO/NDM
	CP	0C0H			; IF RQM=1, DIO=1, NDM=0 (EXECUTION ABORTED)
	JR	Z,FXR_ABORT		; BAIL OUT TO ERR ROUTINE, FIX: GO TO SPECIFIC ROUTINE FOR THIS???
	DEC	C
	JR	NZ,FXRR2		; IF NOT ZERO, LOOP SOME MORE
	LD	A,(FCD_TO)
	DEC	A
	LD	(FCD_TO),A
	JR	NZ,FXRR1
	JR	FXR_TO			; OTHERWISE, TIMEOUT ERROR
;
; WRITE DATA
;
FXR_WRITE:
	HB_DI				; TIME CRITICAL , INTERRUPTS WILL CAUSE I/O ERRS
	LD	HL,(FD_DSKBUF)		; POINT TO SECTOR BUFFER START
	LD	DE,(FCD_SECSZ)
	; TIMEOUT COUNTER IS CPU MHZ / 4 (MAKING SURE IT IS AT LEAST 1)
;	LD	A,(CPUMHZ + 3) / 4
	LD	A,(CB_CPUMHZ)		; GET CPU SPEED IN MHZ
	ADD	A,3			; ROUND UP
	SRL	A			; SHIFT RIGHT TWICE
	;SRL	A			; ... TO DIVIDE BY 4
	;INC	A			; MAKE SURE RESULT IS AT LEAST 1
	LD	(FCD_TO),A
FXRW1	LD	C,0			; OUTER LOOP TIMEOUT COUNTER
FXRW2:	LD	B,0			; SETUP FOR 256 ITERATIONS
FXRW3:	IN_A_NN(FDC_MSR)		; GET MSR
	CP	0B0H			; WE WANT RQM=1,DIO=0,NDM=1,BUSY=1 (READY TO SEND A BYTE W/ EXEC ACTIVE)
	JR	Z,FXRW4			; GOT IT, DO BYTE WRITE
	DJNZ	FXRW3			; NOT READY, LOOP IF COUNTER NOT ZERO
	JR	FXRW5			; COUNTER ZERO, GO TO OUTER LOOP LOGIC
FXRW4:	LD	A,(HL)			; GET NEXT BYTE TO WRITE
	OUT_NN_A(FDC_DATA)		; WRITE IT
	INC	HL			; INCREMENT THE BUFFER POINTER
	DEC	DE			; DECREMENT LOOP COUNTER
	LD	A,D
	OR	E
	JR	NZ,FXRW2		; IF NOT ZERO, REPEAT LOOP
	JR	FXR_END			; CLEAN EXIT
FXRW5:					; OUTER LOOP, REALLY ONLY HAPPENS WHEN WAITING FOR FIRST BYTE OR ABORTED
	AND	0E0H			; ISOLATE RQM/DIO/NDM
	CP	0C0H			; IF RQM=1, DIO=1, NDM=0 (EXECUTION ABORTED)
	JR	Z,FXR_ABORT		; BAIL OUT TO ERR ROUTINE
	DEC	C
	JR	NZ,FXRW2		; IF NOT ZERO, LOOP SOME MORE
	LD	A,(FCD_TO)
	DEC	A
	LD	(FCD_TO),A
	JR	NZ,FXRW1
	JR	FXR_TO			; OTHERWISE, TIMEOUT ERROR
;
; COMMON COMPLETION CODE FOR ALL EXECUTION ROUTINES
;
FXR_TO:		; TIMEOUT
	HB_EI				; INTERRUPTS OK AGAIN
	JP	FOP_TOEXEC		; EXEC TIMEOUT
;
FXR_ABORT:	; EXECUTION ABORTED
	HB_EI				; INTERRUPTS OK AGAIN
	JR	FOP_RES			; GET RESULTS, NO NEED TO PULSE TC
;
FXR_END:	; EXECUTION COMPLETED NORMALLY
	CALL	FC_PULSETC		; PULSE TC TO END EXECUTION
	HB_EI				; INTERRUPTS OK AGAIN
	JR	FOP_RES			; GET RSEULTS
;
; RESULTS PHASE
;
FOP_RES:
	LD	HL,FRB			; POINT TO RECEIVE BUFFER

FOP_RES0:
	LD	BC,$7000		; LOOP COUNTER, $7000 * 16us = ~458ms

FOP_RES1:
	CALL	DELAY			; FDC MAY TAKE UP TO 12us TO UPDATE MSR
	IN_A_NN(FDC_MSR)		; READ MAIN STATUS REGISTER
	AND	0F0H			; ISOLATE RQM/DIO/EXEC/BUSY
	CP	0D0H			; LOOKING FOR RQM/DIO/BUSY
	JR	Z,FOP_RES2		; GOOD, GO TO RECEIVE BYTE
	CP	080H			; CHECK FOR RQM=1, DIO=0 (NOTHING LEFT)
	JR	Z,FOP_EVAL		; IF NOTHING LEFT, ALL DONE, GO TO EOD/EXIT
	DEC	BC			; DECREMENT COUNTER (16 BIT)
	LD	A,B			; CHECK FOR ZERO
	OR	C			; ""
	JR	NZ,FOP_RES1		; LOOP TILL COUNTER EXHAUSTED
	;IN	A,(FDC_MSR)		; *DEBUG*
	;CALL	PRTHEXBYTE		; *DEBUG*
	JR	FOP_TOGETRES		; OTHERWISE TIMEOUT ERROR

FOP_RES2:	; PROCESS NEXT PENDING BYTE
	LD	A,FRB_SIZ		; GET BUF SIZE
	CP	D			; REACHED MAX?
	JR	Z,FOP_BUFMAX		; HANDLE BUF MAX/EXIT
	IN_A_NN(FDC_DATA)		; GET THE BYTE
	LD	(HL),A			; SAVE VALUE
	INC	HL			; INCREMENT BUF POS
	INC	D			; INCREMENT BYTES RECEIVED
	PUSH	HL
	LD	HL,FRB_LEN		; POINT TO BUFFER LENGTH
	LD	(HL),D			; UPDATE NUMBER OF BYTES RECEIVED
	POP	HL
	JR	FOP_RES0		; CONTINUE READ LOOP
;
; EXIT POINTS
;
FOP_NOTIMPL:
	LD	A,FRC_NOTIMPL
	JR	FOP_ERR

FOP_CMDERR:
	LD	A,FRC_CMDERR
	JR	FOP_ERR

FOP_ERROR:
	LD	A,FRC_ERROR
	JR	FOP_ERR

FOP_ABORT:
	LD	A,FRC_ABORT
	JR	FOP_ERR

FOP_BUFMAX:
	LD	A,FRC_BUFMAX
	JR	FOP_ERR

FOP_TOFDCRDY:
	LD	A,FRC_TOFDCRDY
	JR	FOP_ERR

FOP_TOSNDCMD:
	LD	A,FRC_TOSNDCMD
	JR	FOP_ERR

FOP_TOGETRES:
	LD	A,FRC_TOGETRES
	JR	FOP_ERR

FOP_TOEXEC:
	LD	A,FRC_TOEXEC
	JR	FOP_ERR

FOP_ERR:
	LD	(FST_RC),A

FOP_EVAL:
	LD	A,(FCP_CMD)
	; DRVSTAT IS WEIRD, HAS ONLY ST3, NOTHING TO EVAL
	CP	CFD_DRVSTAT
	JR	Z,FOP_EXIT
	; DO WE HAVE ST0?
	LD	A,(FRB_LEN)
	CP	1
	JP	M,FOP_EXIT

FOP_EVALST0:
	LD	A,(FRB_ST0)
	AND	11000000B
	CP	01000000B		; ABTERM
	JR	Z,FOP_ABTERM
	CP	10000000B		; INVCMD
	JR	Z,FOP_INVCMD
	CP	11000000B		; DSKCHG
	JR	Z,FOP_DSKCHG
	JR	FOP_EXIT

FOP_ABTERM:
	; SENSEINT DOES NOT USE ST1
	LD	A,(FCP_CMD)
	CP	CFD_SENSEINT
	JR	Z,FOP_ABTERM1
	; DO WE HAVE ST1?
	LD	A,(FRB_LEN)
	CP	2
	JP	M,FOP_ABTERM1
	JR	FOP_EVALST1
FOP_ABTERM1:	; NO FURTHER DATA, SET FST TO ABTERM
	LD	C,FRC_ABTERM
	JR	FOP_SETFST

FOP_INVCMD:
	LD	C,FRC_INVCMD
	JR	FOP_SETFST

FOP_DSKCHG:
	LD	C,FRC_DSKCHG
	JR	FOP_SETFST

FOP_EVALST1:
	LD	A,(FRB_ST1)

	LD	C,FRC_ENDCYL
	BIT	7,A
	; THI IS NORMAL BECAUSE WE NOW READ ONLY A SINGLE SECTOR
	;JR	NZ,FOP_SETFST
	JR	NZ,FOP_EXIT

	LD	C,FRC_DATAERR
	BIT	5,A
	JR	NZ,FOP_SETFST

	LD	C,FRC_OVERRUN
	BIT	4,A
	JR	NZ,FOP_SETFST

	LD	C,FRC_NODATA
	BIT	2,A
	JR	NZ,FOP_SETFST

	LD	C,FRC_NOTWRIT
	BIT	1,A
	JR	NZ,FOP_SETFST

	LD	C,FRC_MISADR
	BIT	0,A
	JR	NZ,FOP_SETFST

	JR	FOP_EXIT

FOP_SETFST:
	LD	A,C
	LD	(FST_RC),A

FOP_EXIT:
#IF (FDTRACE >= 2)
	CALL	FC_PRTRESULTS
#ENDIF
	JP	FD_RETRC

#IF (FDTRACE > 0)
;
;===============================================================================
; COMMAND PROCESSING STATUS DISPLAY
;===============================================================================
;
; PRINT STATUS
;
FC_PRTFST:
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
	LD	A,(FST_RC)		; A GETS FST_RC
	LD	B,FSST_COUNT		; B GETS TABLE ENTRY COUNT
	LD	HL,FSST
	LD	DE,FSST_ENTSIZ		; TABLE ENTRY LENGTH

FC_PRTFST0:	; START OF LOOP
	LD	C,(HL)
	CP	C
	JR	Z,FC_PRTFST1		; FOUND CODE

	ADD	HL,DE			; POINT TO NEXT ENTRY
	DJNZ	FC_PRTFST0		; CHECK NEXT ENTRY TILL COUNT IS ZERO

	; NO MATCHING ENTRY, PRINT THE HEX VALUE
	CALL	PC_SPACE
	CALL	PC_LBKT
	CALL	PRTHEXBYTE
	CALL	PC_RBKT
	JR	FC_PRTFSTX

FC_PRTFST1:	; ENTRY FOUND, PRINT IT
	CALL	PC_SPACE
	INC	HL
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	CALL	PC_LBKT
	CALL	WRITESTR
	CALL	PC_RBKT


FC_PRTFSTX:
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	RET
;
; PRINT COMMAND
;
FC_PRTCMD:
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
	LD	A,(FCP_CMD)		; A GETS THE COMMAND CODE
	LD	B,FCT_COUNT		; B GETS TABLE ENTRY COUNT
	LD	HL,FCT
	LD	DE,FCT_ENTSIZ		; TABLE ENTRY LENGTH

FCPC_LOOP:	; START OF LOOP
	LD	C,(HL)
	CP	C
	JR	Z,FCPC_MATCH		; FOUND CODE

	ADD	HL,DE			; POINT TO NEXT ENTRY
	DJNZ	FCPC_LOOP		; CHECK NEXT ENTRY TILL COUNT IS ZERO

		; NO MATCHING ENTRY, PRINT THE HEX VALUE
	CALL	PC_SPACE
	CALL	PC_LBKT
	CALL	PRTHEXBYTE
	CALL	PC_RBKT
	JR	FCPC_EXIT

FCPC_MATCH:	; ENTRY FOUND, PRINT IT
	INC	HL
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	CALL	WRITESTR

FCPC_EXIT:
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	RET
;
; PRINT RESULTS
;
FC_PRTRESULTS:
	; IF TRACE IS SET, FORCE PRINT RESULTS
	LD	A,(FCD_TRACE)
	OR	A
	RET	Z			; IF TRACE = 0, BE SILENT!

	CP	3			; IS TRACE >= 3 ?
	JR	NC,FCPR2		; YES, SO FORCE PRINT EVERYTHING!

	; IF RC=OK, GET OUT, NOTHING TO PRINT
	LD	A,(FST_RC)
	CP	FRC_OK
	RET	Z

	; SPECIAL CASE, DON'T PRINT IF SENSEINT & INVCMD/DSK CHG/ABTERM
	LD	A,(FCP_CMD)
	CP	CFD_SENSEINT
	JR	NZ,FCPR2

	LD	A,(FST_RC)
	CP	FRC_INVCMD
	JR	Z,FCPR_EXIT
	CP	FRC_DSKCHG
	JR	Z,FCPR_EXIT
	CP	FRC_ABTERM
	JR	Z,FCPR_EXIT
	JR	FCPR_EXIT

FCPR2:
	CALL	NEWLINE

	LD	DE,FDSTR_FD
	CALL	WRITESTR
	CALL	PC_COLON
	CALL	PC_SPACE

	CALL	FC_PRTCMD

	LD	A,(FCP_LEN)
	LD	DE,FCP_BUF
	CALL	PRTHEXBUF

	LD	DE,FDSTR_ARROW
	CALL	WRITESTR

	LD	A,(FRB_LEN)
	LD	DE,FRB
	CALL	PRTHEXBUF

	LD	A,(FST_RC)
	CALL	FC_PRTFST

FCPR_EXIT:
	RET
;
; STRING CONSTANTS
;
FDSTR_ARROW	.TEXT	" -->$"
FDSTR_NORESP	.TEXT	"DRIVE NOT RESPONDING$"
FDSTR_FD	.TEXT	"FD$"
#IF (FDTRACE >= 3)
FDSTR_MOTON	.TEXT	"\r\nMOTOR ON$"
FDSTR_MOTOFF	.TEXT	"\r\nMOTOR OFF$"
FDSTR_MOTDELAY	.TEXT	"\r\nMOTOR DELAY$"
FDSTR_DOR	.TEXT	"DOR$"
FDSTR_DCR	.TEXT	"DCR$"
FDSTR_RESETFDC	.TEXT	"\r\nRESET FDC$"
FDSTR_SELECT	.TEXT	"\r\nSELECT: $"
#ENDIF		; (FDTRACE >= 3)
#ENDIF		; (FDTRACE > 0)
;
;
;==================================================================================================
;   FLOPPY DISK DRIVER - DATA
;==================================================================================================
;
; FDC COMMAND PHASE
;
FCP_CMD		.DB	000H
FCP_LEN		.DB	00H
FCP_BUF:
FCP_CMDX	.DB	0
FCP_HDSDS	.DB	0
FCP_C		.DB	0
FCP_H		.DB	0
FCP_R		.DB	0
FCP_N		.DB	0
FCP_EOT		.DB	0
FCP_GPL		.DB	0
FCP_DTL		.DB	0
FCP_BUFSIZ	.EQU	$-FCP_BUF
;
; FDC STATUS
;
FST_RC		.DB	00H
FST_DOR		.DB	00H
FST_DCR		.DB	00H
;
; FDC RESULTS BUFFER
;
FRB_LEN		.DB	00H
FRB
FRB_ST0
FRB_ST3		.DB	0
FRB_ST1
FRB_PCN		.DB	0
FRB_ST2		.DB	0
FRB_C		.DB	0
FRB_H		.DB	0
FRB_R		.DB	0
FRB_N		.DB	0
FRB_SIZ		.EQU	$-FRB
;
; FDC COMMAND DATA
;
FCD:		; FLOPPY CONFIGURATION DATA (PUBLIC) MANAGED AS A "BLOCK"
FCD_NUMCYL	.DB	000H		; NUMBER OF CYLINDERS
FCD_NUMHD	.DB	000H		; NUMBER OF HEADS
FCD_NUMSEC	.DB	000H		; NUMBER OF SECTORS
FCD_SOT		.DB	000H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
FCD_EOT					; END OF TRACK SECTOR (SAME AS SC SINCE SOT ALWAYS 1)
FCD_SC		.DB	000H		; SECTOR COUNT
FCD_SECSZ	.DW	000H		; SECTOR SIZE IN BYTES
FCD_GPL		.DB	000H		; GAP LENGTH (R/W)
FCD_GPLF	.DB	000H		; GAP LENGTH (FORMAT)
FCD_SRTHUT	.DB	000H		; STEP RATE, IBM PS/2 CALLS FOR 3ms, 0DH = 3ms SRT, HEAD UNLOAD TIME
FCD_HLTND	.DB	000H		; HEAD LOAD TIME, IBM PS/2 CALLS FOR 15ms 08H = 16ms HUT
FCD_DOR		.DB	000H		; DOR VALUE
FCD_DCR		.DB	000H		; DCR VALUE
FCD_LEN		.EQU	$ - FCD
		; DYNAMICALLY MANAGED (PUBLIC)
FCD_DS		.DB	001H		; DRIVE SELECT (UNIT NUMBER 0-3)
FCD_C		.DB	000H		; CYLINDER
FCD_H		.DB	000H		; HEAD
FCD_R		.DB	001H		; RECORD
FCD_D		.DB	0E5H		; DATA FILL BYTE
		; STATUS MANAGEMENT
FCD_DOP		.DB	0FFH		; CURRENT OPERATION (SEE DOP_...)
FCD_IDLECNT	.DW	0		; IDLE COUNT
FCD_TRACE	.DB	0		; TRACE LEVEL
FCD_TO		.DB	0		; TIMEOUT COUNTDOWN TIMER
FCD_FDCRDY	.DB	0		; FALSE MEANS FDC RESET NEEDED
;
; GENERAL WORKING STORAGE
;
FD_DSKBUF	.DW	0
FD_CURGEOM	.EQU	$		; TWO BYTES BELOW
FD_CURSPT	.DB	0		; CURRENT SECTORS PER TRACK
FD_CURHDS	.DB	0		; CURRENT HEADS
