;===============================================================================
; FDU - FLOPPY DISK UTILITY PROGRAM FOR RETROBREW COMPUTERS
;===============================================================================
;
;	AUTHOR:	 WAYNE WARTHEN (DERIVED FROM FDCMON BY ANDREW LYNCH & DAN WERNER)
;_______________________________________________________________________________
;
; CHANGELOG:
;   2011-08-05: v1.0	INITIAL RELEASE
;   2011-08-25: v1.1	SUPPORT ZETA
;			SUPPORT INT/WAIT
;   2011-08-28: v1.2	FIX ZETA RESET LOGIC (from Sergey)
;			FIX ZETA MOTOR DISPLAY (from Sergey)
;			NEW ZETA DISK CHANGE DISPLAY
;   2011-09-01: V1.3	ADDED CONFIGURABLE CPU FREQUENCY FOR DELAYS
;   2011-09-05: V1.4	USE CPU FREQUENCY FOR POLLING TIMEOUT
;   2011-09-19: V1.5	IMPROVED USE OF FDC RESET ON ZETA
;			ADDED FDC RESET TO FDC CONTROL MENU
;   2011-09-24: V1.5a	MINOR CORRECTION TO POLLING ROUTINE TO AVOID OVERRUN
;   2011-10-07: V2.0	PRELIMINARY SUPPORT FOR DUAL IDE
;   2011-10-13: V2.1	PRELIMINARY SUPPORT FOR N8
;   2011-10-20: V2.2	INCLUDE BUSY & NDMA BITS IN RESULTS BYTE READY CHECKING
;   2011-10-22: V2.3	ADDED VERIFY FUNCTION
;   2011-11-25: V2.4	ADDED SUPPORT FOR DISKIO V3
;   2012-01-09: V2.5	WARM START CP/M AT TERMINATION TO LOGOUT ALL DRIVES
;   2012-01-09: V2.6	EXPERIMENTAL SUPPORT FOR 5 1/4" DRIVES
;   2012-04-05: V2.7	SUPPORT FOR 8" DRIVES
;   2012-04-06: V2.7a	FIXED MEDIA SELECTION MENU (DUPLICATE ENTRIES)
;   2012-04-08: V2.7b	HANDLE DENSITY SELECT PROPERLY
;   2012-05-22: V2.8	ADDED NEW MEDIA DEFINITIONS (5.25", 320K)
;   2012-06-01: V2.9	ADDED INTERLEAVE CAPABILITY IN FORMAT COMMAND
;   2012-06-05: V3.0	DOCUMENTATION CLEANUP
;   2012-07-01: V3.1	MODIFY HLT FOR 8" MEDIA (50ms PER YD-180 SPEC)
;   2013-06-17: V3.2	CLEANED UP THE SRT, HLT, AND HUT VALUES
;   2015-02-10: V3.3	ADDED ZETA SBC V2 SUPPORT (SERGEY KISELEV)
;   2015-03-25: V4.0	RENAMED APP FDTST --> FD
;   2017-09-02: V5.0	RENAMED APP TO FDU (FLOPPY DISK UTILITY)
;			DYNAMIC FDC SELECTION AT STARTUP
;			DYNAMIC CPU SPEED ADJUSTMENT
;   2017-12-16: V5.1	IMPROVED POLLING READ/WRITE PERFORMANCE
;   2018-01-08: V5.2	ADDED RCBUS SUPPORT FOR:
;			- SCOTT BAKER (SMB) SMC 9266 FDC
;			- SCOTT BAKER (SMB) WDC 37C65 FDC
;   2018-09-05: v5.3	ADDED SUPPORT FOR SMALLZ80
;			- USE EOT=R TO END R/W AFTER ONE SECTOR INSTEAD
;			  OF USING PULSE TC
;   2020-01-05: V5.4	ADDED SUPPORT FOR DYNO FDC
;   2020-04-29: v5.5	ADDED SUPPORT FOR ETCHED PIXELS FDC
;   2020-12-12: v5.6	UPDATED SMALLZ80 TO NEW I/O ADDRESSES
;   2021-03-24: v5.7    ADDED SOME SINGLE-SIDED FORMATS
;   2021-07-26: v5.8	ADDED SUPPORT FOR NHYODYNE (MBC) FDC
;   2023-12-10: v5.9	ADDED SUPPORT FOR DUODYNE (DUO) FDC
;			
;_______________________________________________________________________________
;
; BUILDING:
;   CAN BE BUILT WITH TASM LIKE THIS:
;      TASM -t80 -b -fFF FDU.ASM FDU.COM FDU.LST
;
; TODO:
;   1) CURRENT TRACK IS UPDATED EVEN IF SEEK FAILS! (DEFER, RECOVERS AUTOMATICALLY)
;   2) CLEAN UP UTILITY ROUTINES (DEFER, NOT IMPORTANT)
;   3) TIGHTEN UP THE TIMING LOOPS (DEFER, LOW PRIORITY, WORKING FINE AS IS)
;   4) MOVE STX EVALUATION TO DIO LEVEL? (NOT SURE..., LOW PRIORITY)
;   5) ALLOW <ESC> TO ABORT AT PROMPTS
;   6) ALLOW <ESC> TO ABORT OPERATION IN PROGRESS
;   7) MOVE CONTINUE PROMPT TO A SEPARATE ROUTINE
;
;_______________________________________________________________________________
;
;
FALSE		.EQU		0
TRUE		.EQU		~FALSE
;
; FDC ID
;
FDC_DIO		.EQU		0
FDC_DIO3	.EQU		1
FDC_ZETA	.EQU		2
FDC_ZETA2	.EQU		3
FDC_DIDE	.EQU		4
FDC_N8		.EQU		5
FDC_RCSMC	.EQU		6
FDC_RCWDC	.EQU		7
FDC_SMZ80	.EQU		8
FDC_DYNO	.EQU		9
FDC_EPFDC	.EQU		10
FDC_MBC		.EQU		11
FDC_DUO		.EQU		12
;
; FDC MODE
;
_DIO		.EQU		$01	; CUSTOM FOR DIO BOARD
_DIO3		.EQU		$02	; CUSTOM FOR DIO3 BOARD
_ZETA		.EQU		$04	; CUSTOM FOR ZETA
_RCSMC		.EQU		$08	; CUSTOM FOR RCBUS SMB SMC MODULE
_PCAT		.EQU		$10	; PC/AT MODE IN NEWER CONTROLLERS
;
;===============================================================================
; MAIN PROGRAM PROCEDURE
;===============================================================================
;
	.ORG	00100H

	; SAVE PREVIOUS STACK POINTER, AND SWITCH TO OUR STACK
	LD	(STACKSAV),SP
	LD	SP,STACK

	; GENERAL INITIALIZATION (BIOS & SPEED DETECTION)
	CALL	INIT
	JR	NZ,EXIT

	; SELECT FD CONTROLLER
	CALL	NEWLINE
	CALL	FDCSEL
	JR	NZ,EXIT

	; SETUP FOR DEFAULT MEDIA AND RUN THE MAIN MENU
	CALL	DIO_SETMEDIA
	LD	HL,MM_INFO
	CALL	RUNMENU

EXIT:
	LD	C,00H			; CP/M SYSTEM RESET (WARM START)
	CALL	0005H			; RETURN TO CP/M
		
	; CLEAN UP AND RETURN TO OS
	CALL	NEWLINE
	LD	SP,(STACKSAV)
	RET
	
	HALT				; SHOULD NEVER GET HERE

;	
;===============================================================================
; FD CONTROLLER SELECTION
;===============================================================================
;
INIT:
	; DISPLAY PROGRAM STARTUP BANNER
	CALL	NEWLINE
	LD	DE,STR_BANNER
	CALL	WRITESTR
	
	; UNA UBIOS DETECTION...
	LD	A,($FFFD)	; FIXED LOCATION OF UNA API VECTOR
	CP	$C3		; JP INSTRUCTION?
	JR	NZ,INIT1	; IF NOT, NOT UNA
	LD	HL,($FFFE)	; GET JP ADDRESS
	LD	A,(HL)		; GET BYTE AT TARGET ADDRESS
	CP	$FD		; FIRST BYTE OF UNA PUSH IX INSTRUCTION
	JR	NZ,INIT1	; IF NOT, NOT UNA
	INC	HL		; POINT TO NEXT BYTE
	LD	A,(HL)		; GET NEXT BYTE
	CP	$E5		; SECOND BYTE OF UNA PUSH IX INSTRUCTION
	JR	NZ,INIT1	; IF NOT, NOT UNA
	LD	DE,STR_UBIOS	; POINT TO UBIOS TAG
	CALL	WRITESTR	; AND DISPLAY IT

	; GET CPU SPEED FROM UBIOS
	LD	C,0F8H		; UNA BIOS GET PHI FUNCTION
	RST	08		; RETURNS SPEED IN HZ IN DE:HL
	LD	B,4		; DIVIDE MHZ IN DE:HL BY 100000H 
INIT0:
	SRL	D		; ... TO GET APPROX CPU SPEED IN
	RR	E		; ...MHZ.  THROW AWAY HL, AND
	DJNZ	INIT0		; ...RIGHT SHIFT DE BY 4.
	INC	E		; FIX UP FOR VALUE TRUNCATION
	LD	A,E		; PUT IN A
	LD	(CPUSPD),A	; SAVE IT
	JR	INIT3		; AND DONE

INIT1:
	; ROMWBW HBIOS DETECTION
	LD	HL,(0FFFEH)	; HL := ADR OR ROMWBW HBIOS IDENT
	LD	A,(HL)		; GET FIRST BYTE OF ROMWBW MARKER
	CP	'W'		; MATCH?
	JP	NZ,INIT2	; ABORT WITH INVALID CONFIG BLOCK
	INC	HL		; NEXT BYTE (MARKER BYTE 2)
	LD	A,(HL)		; LOAD IT
	CP	~'W'		; MATCH?
	JP	NZ,INIT2	; ABORT WITH INVALID CONFIG BLOCK
	LD	DE,STR_HBIOS	; POINT TO HBIOS TAG
	CALL	WRITESTR	; AND DISPLAY IT

	; GET CPU SPEED FROM HBIOS
	LD	B,0F8H		; HBIOS SYSGET FUNCTION 0XF8
	LD	C,0F0H		; CPUINFO SUBFUNCTION 0XF0
	RST	08		; DO IT, L := CPU SPEED IN MHZ
	LD	A,L		; MOVE IT TO A
	LD	(CPUSPD),A	; SAVE IT
	JR	INIT3		; AND DONE
	
INIT2:
	;; NO KNOWN BIOS DETECTED, BAIL OUT W/ ERROR
	;LD	DE,STR_BIOERR
	;CALL	WRITESTR
	;OR	0FFH
	;RET
	LD	A,20
	LD	(CPUSPD),A
	
INIT3:
	; COMPUTE CPU SCALER FOR DELAY LOOPS
	LD	A,(CPUSPD)
	CP	3			; TEST FOR <= 2 (SPECIAL HANDLING)
	JR	C,INIT4			; IF <= 2, SPECIAL PROCESSING
	SUB	2			; ADJUST AS REQUIRED BY DELAY FUNCTIONS
	JR	INIT5			; AND CONTINUE
INIT4:
	LD	A,1			; USE THE MIN VALUE OF 1
INIT5:
	LD	(CPUSCL),A		; UPDATE CPU SCALER VALUE

	; REMAINDER OF BANNER
	CALL	NEWLINE
	LD	DE,STR_BANNER2
	CALL	WRITESTR

	; INITIALIZATION DONE
	XOR	A
	RET
		
STR_BANNER	.DB	"Floppy Disk Utility (FDU) v5.9, 10-Dec-2023$"
STR_BANNER2	.DB	"Copyright (C) 2023, Wayne Warthen, GNU GPL v3","$"
STR_HBIOS	.DB	" [HBIOS]$"
STR_UBIOS	.DB	" [UBIOS]$"
;
STR_BIOERR	.DB	"\r\n\r\n*** Unknown BIOS ***\r\n$"
;
CPUSPD		.DB	20		; DEFAULT TO SAFE (HIGH) VALUE
CPUSCL		.DB	0		; CPU SPEED DELAY SCALER (COMPUTED)

;	
;===============================================================================
; FD CONTROLLER SELECTION
;===============================================================================
;
FDCSEL:
	; PROMPT
	LD	DE,FSS_MENU
	CALL	WRITESTR
;
FDCSEL1:
	CALL	GETKEYUC
	CP	'X'			; EXIT?
	JR	NZ,FDCSEL2		; IF NOT, CONTINUE
	OR	0FFH			; ELSE SET NZ FOR EXIT REQUEST
	RET				; AND RETURN
;	
FDCSEL2:
	SUB	'A'			; ASCII -> BINARY
	CP	FDCCNT			; TOO HIGH?
	JR	NC,FDCSEL1		; IF SO, TRY AGAIN
;
	; SAVE SELECTED FDC IDENTIFIER
	LD	(FDCID),A		; RECORD THE FDC ID
	RLCA				; TIMES 4
	RLCA				; ... FOR 4 BYTE ENTRIES
	LD	HL,FDCTBL		; POINT TO FDC INSTANCE TABLE
	CALL	ADDHLA			; OFFSET TO DESRIED ENTRY, A TRASHED
	LD	E,(HL)			; LOAD LABEL PTR INTO DE
	INC	HL			; ...
	LD	D,(HL)			; ...
	LD	(FDCLBL),DE		; SAVE LABEL PTR
	INC	HL			; BUMP TO CFG POINTER
	LD	E,(HL)			; LOAD CFG PTR INTO DE
	INC	HL			; ...
	LD	D,(HL)			; ...
	LD	(FDCCFG),DE		; SAVE CFG PTR
	LD	IY,(FDCCFG)		; AND INIT A WORKING COPY
	LD	A,(IY+CFG_MODE)		; GET MODE BITMAP BYTE
	LD	(FDCBM),A		; SAVE IT TO ACTIVE WORKING COPY
;
	LD	DE,(FDCLBL)		; GET LABEL POINTER
	CALL	WRITESTR		; AND DISPLAY IT
;
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
; TABLE OF FDC CONTROLLERS
;
FDCTBL:	;	LABEL		CONFIG DATA
	;	-----		-----------
	.DW	STR_DIO,	CFG_DIO
	.DW	STR_DIO3,	CFG_DIO3
	.DW	STR_ZETA,	CFG_ZETA
	.DW	STR_ZETA2,	CFG_ZETA2
	.DW	STR_DIDE,	CFG_DIDE
	.DW	STR_N8,		CFG_N8
	.DW	STR_RCSMC,	CFG_RCSMC
	.DW	STR_RCWDC,	CFG_RCWDC
	.DW	STR_SMZ80,	CFG_SMZ80
	.DW	STR_DYNO,	CFG_DYNO
	.DW	STR_EPFDC,	CFG_EPFDC
	.DW	STR_MBC,	CFG_MBC
	.DW	STR_DUO,	CFG_DUO
FDCCNT	.EQU	($-FDCTBL)/4	; FD CONTROLLER COUNT
;
; FDC LABEL STRINGS
;
STR_DIO		.TEXT	"DISKIO$"
STR_DIO3	.TEXT	"DISKIO3$"
STR_ZETA	.TEXT	"ZETA$"
STR_ZETA2	.TEXT	"ZETA2$"
STR_DIDE	.TEXT	"D-IDE$"
STR_N8		.TEXT	"N8$"
STR_RCSMC	.TEXT	"RC-SMC$"
STR_RCWDC	.TEXT	"RC-WDC$"
STR_SMZ80	.TEXT	"SMZ80$"
STR_DYNO	.TEXT	"DYNO$"
STR_EPFDC	.TEXT	"EPFDC$"
STR_MBC		.TEXT	"NHYODYNE$"
STR_DUO		.TEXT	"DUODYNE$"
;
; FDC CONFIGURATION BLOCKS
;
CFG_MSR		.EQU	0
CFG_DATA	.EQU	1
CFG_DIR		.EQU	2
CFG_DOR		.EQU	3
CFG_DCR		.EQU	4
CFG_DACK	.EQU	5
CFG_TC		.EQU	6
CFG_DMA		.EQU	7
CFG_MODE	.EQU	8
;
CFG_DIO:
	.DB	036H	; FDC MAIN STATUS REGISTER
	.DB	037H	; FDC DATA PORT
	.DB	038H	; DATA INPUT REGISTER
	.DB	03AH	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	0FFH	; DCR
	.DB	0FFH	; DACK
	.DB	0FFH	; TERMINAL COUNT (W/ DACK)
	.DB	03CH	; PSEUDO DMA DATA PORT
	.DB	_DIO	; MODE=
CFG_DIO3:
	.DB	036H	; FDC MAIN STATUS REGISTER
	.DB	037H	; FDC DATA PORT
	.DB	038H	; DATA INPUT REGISTER
	.DB	03AH	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	0FFH	; DCR
	.DB	0FFH	; DACK
	.DB	0FFH	; TERMINAL COUNT (W/ DACK)
	.DB	03CH	; PSEUDO DMA DATA PORT
	.DB	_DIO3	; MODE=
CFG_ZETA:
	.DB	036H	; FDC MAIN STATUS REGISTER
	.DB	037H	; FDC DATA PORT
	.DB	038H	; DATA INPUT REGISTER
	.DB	03AH	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	0FFH	; DCR
	.DB	0FFH	; DACK
	.DB	0FFH	; TERMINAL COUNT (W/ DACK)
	.DB	03CH	; PSEUDO DMA DATA PORT
	.DB	_ZETA	; MODE=
;
CFG_ZETA2:
	.DB	030H	; FDC MAIN STATUS REGISTER
	.DB	031H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	038H	; DIGITAL OUTPUT REGISTER
	.DB	028H	; CONFIGURATION CONTROL REGISTER
	.DB	0FFH	; DACK
	.DB	038H	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; NOT USED BY ZETA SBC V2
	.DB	_PCAT	; MODE=
;
CFG_DIDE:
	.DB	02AH	; FDC MAIN STATUS REGISTER
	.DB	02BH	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	02CH	; DOR
	.DB	02DH	; DCR
	.DB	03CH	; DACK
	.DB	03DH	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; NOT USED BY DIDE
	.DB	_PCAT	; MODE=
;
CFG_N8:
	.DB	08CH	; FDC MAIN STATUS REGISTER
	.DB	08DH	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	092H	; DOR
	.DB	091H	; DCR
	.DB	090H	; DACK
	.DB	093H	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; NOT USED BY N8
	.DB	_PCAT	; MODE=
;
CFG_RCSMC:
	.DB	050H	; FDC MAIN STATUS REGISTER
	.DB	051H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	058H	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	0FFH	; DCR
	.DB	0FFH	; DACK
	.DB	0FFH	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; PSEUDO DMA DATA PORT
	.DB	_RCSMC	; MODE=
;
CFG_RCWDC:
	.DB	050H	; FDC MAIN STATUS REGISTER
	.DB	051H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	058H	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	048H	; DCR
	.DB	0FFH	; DACK
	.DB	058H	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; PSEUDO DMA DATA PORT
	.DB	_PCAT	; MODE=
;
CFG_SMZ80:
	.DB	074H	; FDC MAIN STATUS REGISTER
	.DB	075H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	072H	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	077H	; DCR
	.DB	0FFH	; DACK
	.DB	0FFH	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; PSEUDO DMA DATA PORT
	.DB	_PCAT	; MODE=
;
CFG_DYNO:
	.DB	084H	; FDC MAIN STATUS REGISTER
	.DB	085H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	086H	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	087H	; DCR
	.DB	0FFH	; DACK
	.DB	086H	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; PSEUDO DMA DATA PORT
	.DB	_PCAT	; MODE=
;
CFG_EPFDC:
	.DB	048H	; FDC MAIN STATUS REGISTER
	.DB	049H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	04AH	; DIGITAL OUTPUT REGISTER (LATCH)
	.DB	04BH	; DCR
	.DB	0FFH	; DACK
	.DB	04CH	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; PSEUDO DMA DATA PORT
	.DB	_PCAT	; MODE=
;
CFG_MBC:
	.DB	030H	; FDC MAIN STATUS REGISTER
	.DB	031H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	036H	; DIGITAL OUTPUT REGISTER (WHEN WRITTEN)
	.DB	035H	; CONFIGURATION CONTROL REGISTER
	.DB	036H	; DACK (WHEN READ)
	.DB	037H	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; NOT USED
	.DB	_PCAT	; MODE=
;
CFG_DUO:
	.DB	080H	; FDC MAIN STATUS REGISTER
	.DB	081H	; FDC DATA PORT
	.DB	0FFH	; DATA INPUT REGISTER
	.DB	086H	; DIGITAL OUTPUT REGISTER (WHEN WRITTEN)
	.DB	085H	; CONFIGURATION CONTROL REGISTER
	.DB	086H	; DACK (WHEN READ)
	.DB	087H	; TERMINAL COUNT (W/ DACK)
	.DB	0FFH	; NOT USED
	.DB	_PCAT	; MODE=
;
FDCID	.DB	0			; FDC IDENTIFIER (0 INDEXED)
FDCBM	.DB	0			; FDC ID BITMAP
FDCLBL	.DW	0			; POINTER TO ACTIVE FDC LABEL STRING
FDCCFG	.DW	0			; POINTER TO ACTIVE CFG DATA
;
FSS_MENU:
	.TEXT	"\r\n"
	.TEXT	"SELECT FLOPPY DISK CONTROLLER:\r\n"
	.TEXT	"  (A) Disk IO ECB Board\r\n"
	.TEXT	"  (B) Disk IO 3 ECB Board\r\n"
	.TEXT	"  (C) Zeta SBC Onboard FDC\r\n"
	.TEXT	"  (D) Zeta 2 SBC Onboard FDC\r\n"
	.TEXT	"  (E) Dual IDE ECB Board\r\n"
	.TEXT	"  (F) N8 Onboard FDC\r\n"
	.TEXT	"  (G) RCBus SMC (SMB)\r\n"
	.TEXT	"  (H) RCBus WDC (SMB)\r\n"
	.TEXT	"  (I) SmallZ80 Expansion\r\n"
	.TEXT	"  (J) Dyno-Card FDC, D1030\r\n"
	.TEXT	"  (K) RCBus EPFDC\r\n"
	.TEXT	"  (L) Nhyodyne FDC\r\n"
	.TEXT	"  (M) Duodyne FDC\r\n"
	.TEXT	"  (X) Exit\r\n"
	.TEXT	"=== OPTION ===> $\r\n"
;	
;===============================================================================
; MAIN MENU
;===============================================================================
;
; MAIN MENU DATA
;
MM_TABLE	.DB	'S' \	 .DW	MMS_SETUP,	MM_SETUP
MM_ENTSIZ	.EQU	$ - MM_TABLE
		.DB	'R' \	 .DW	MMS_READ,	MM_READ
		.DB	'W' \	 .DW	MMS_WRITE,	MM_WRITE
		.DB	'F' \	 .DW	MMS_FORMAT,	MM_FORMAT
		.DB	'V' \	 .DW	MMS_VERIFY,	MM_VERIFY
		.DB	'I' \	 .DW	MMS_INITBUF,	MM_INITBUF
		.DB	'D' \	 .DW	MMS_DUMPBUF,	MM_DUMPBUF
		.DB	'C' \	 .DW	MMS_FDCMENU,	MM_FDCMENU
		.DB	'X' \	 .DW	MMS_EXIT,	0000H
MM_COUNT	.EQU	(($ - MM_TABLE) / MM_ENTSIZ)	; # ENTRIES IN TABLE
MM_INFO:	.DW	MM_DRAW
		.DW	MM_TABLE
		.DB	MM_ENTSIZ
		.DB	MM_COUNT
;		
; MAIN MENU DISPLAY STRINGS
;
STR_MAINMENU:
	.TEXT	"=======================<< FDU MAIN MENU >>======================\r\n"
;	.TEXT	"(S)ETUP: UNIT=XX  MEDIA=XXXXXXXXXXXXX  MODE=XXXXXXXXXX  TRACE=XX\r\n"
	.TEXT	"(S)ETUP: UNIT="
MV_UNIT	.TEXT		      "XX"
	.TEXT			"  MEDIA="
MV_MED	.TEXT				"XXXXXXXXXXXXX"
	.TEXT				             "  MODE="
MV_MODE	.TEXT					            "XXXXXXXXXX"
	.TEXT						              "  TRACE="
MV_TRC	.TEXT							              "XX"
	.TEXT								        "\r\n"
	.TEXT	"----------------------------------------------------------------\r\n"
	.TEXT	"(R)EAD		(W)RITE		(F)ORMAT	(V)ERIFY\r\n"	
	.TEXT	"(I)NIT BUFFER	(D)UMP BUFFER	FDC (C)MDS	E(X)IT\r\n"
	.TEXT	"=== OPTION ===> $\r\n"
;
MMS_SETUP:	.TEXT	"SETUP$"
MMS_READ:	.TEXT	"READ$"
MMS_WRITE:	.TEXT	"WRITE$"
MMS_FORMAT:	.TEXT	"FORMAT$"
MMS_VERIFY:	.TEXT	"VERIFY$"
MMS_INITBUF:	.TEXT	"INITIALIZE BUFFER$"
MMS_DUMPBUF:	.TEXT	"DUMP BUFFER$"
MMS_FDCMENU:	.TEXT	"FDC MENU$"
MMS_EXIT:	.TEXT	"EXIT$"
;
; MAIN MENU DRAW PROCEDURE
;
MM_DRAW:
	CALL	NEWLINE
	CALL	NEWLINE
	
		; UPDATE FDC LABEL
	LD	DE,STR_MAINMENU + 5
	LD	A,' '
	LD	(DE),A
	INC	DE
	LD	HL,(FDCLBL)
	CALL	COPYSTR
	LD	A,' '
	LD	(DE),A

		; UPDATE UNIT
	LD	DE,MV_UNIT
	LD	A,(DCD_UNIT)
	CALL	HEXSTRBYTE

		; UPDATE MEDIA
	LD	DE,(MDB_LABEL)
	LD	HL,MV_MED
	CALL	STRCPY
		
		; UPDATE MODE
	LD	A,(DCD_MD)
	RLA
	LD	E,A
	LD	D,0
	LD	HL,MDT
	ADD	HL,DE
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	EX	DE,HL
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	LD	HL,MV_MODE
	CALL	STRCPY		
		
		; UPDATE TRACE
	LD	DE,MV_TRC
	LD	A,(DCD_TRACE)
	CALL	HEXSTRBYTE		
		
		; DISPLAY THE MENU
	LD	DE,STR_MAINMENU
	CALL	WRITESTR

	RET
;	
; MAIN MENU FUNCTIONS
;
MM_SETUP:
	CALL	MM_GETSETUP
	CALL	DIO_SETMEDIA
	RET

MM_READ:
	LD	A,DOP_READ
	LD	(DCD_DOP),A
	CALL	MM_GETTGT
	CALL	MM_GETTGTPARMS
	CALL	MMOP_PROC
	RET

MM_WRITE:
	LD	A,DOP_WRITE
	LD	(DCD_DOP),A
	CALL	MM_GETTGT
	CALL	MM_GETTGTPARMS
	CALL	MMOP_PROC
	RET

MM_FORMAT:
	LD	A,DOP_FORMAT
	LD	(DCD_DOP),A
	CALL	MM_GETTGT
	CALL	MM_GETTGTPARMS
	CALL	MM_GETINTRLV
	CALL	MMOP_PROC
	RET

MM_VERIFY:
	LD	A,DOP_VERIFY
	LD	(DCD_DOP),A
	CALL	MM_GETTGT
	CALL	MM_GETTGTPARMS
	CALL	MM_SETVFYBUF
	CALL	MMOP_PROC
	RET

MM_INITBUF:
	CALL	PC_SPACE
	LD	DE,MMPS_IBOPT
	CALL	WRITESTR
MM_IBKEY:
	CALL	GETKEYUC
	CP	'P'		; PATTERN
	JP	Z,MM_IBPAT
	CP	'F'		; FILL
	JP	Z,MM_IBFILL
	JP	MM_IBKEY
MM_IBPAT:
	LD	DE,MMBS_PATTERN
	CALL	PC_SPACE
	CALL	WRITESTR
	CALL	MM_GETPATTERN
	LD	HL,BUFFER
	LD	DE,BUFSIZ
	LD	A,(DCD_PATTERN)
	LD	B,A
	CALL	PAT_BUFFER
	RET
MM_IBFILL:
	LD	DE,MMBS_FILL
	CALL	WRITESTR
	CALL	PC_SPACE
	CALL	MM_GETFILL
	LD	HL,BUFFER
	LD	DE,BUFSIZ
	LD	A,(DCD_FILL)
	LD	B,A
	CALL	FILL_BUFFER
	RET

MM_DUMPBUF:
	LD	HL,BUFFER		; SET ADDRESS TO DUMP
	LD	DE,BUFFER		; SET END ADDRESS
	INC	D			;
	INC	D			;
	CALL	DUMP_BUFFER		; DUMP BUFFER TO CONSOLE
	RET

MM_FDCMENU:
	CALL	DIO_INIT
	CALL	FDCMENU
	CALL	DIO_TERM
	RET
;
; MAIN MENU SUPPORT FUNCTIONS
;
MM_GETSETUP:
	LD	DE,MMP_UNIT
	LD	HL,DCD_UNIT
	LD	BC,0003H
	CALL	GETHEXBYTERNG
	
	CALL	MM_MEDIALIST
	LD	DE,MMP_MEDIA
	LD	HL,DCD_MT
	LD	B,0
	LD	A,MIT_ENTCNT
	DEC	A
	LD	C,A
	CALL	GETHEXBYTERNG
	
MM_GETMODE:
	CALL	MM_MODELIST
	LD	DE,MMP_MODE
	LD	HL,DCD_MD
	LD	B,0
	LD	A,MDT_ENTCNT
	DEC	A
	LD	C,A
	CALL	GETHEXBYTERNG
	
	; VALIDATE MODE AGAINST CURRENT FDC
	INC	A			; PREP VALUE FOR LOOP
	LD	B,A			; PUT IN LOOP COUNTER
	XOR	A			; CLEAR A
	SCF				; SET CF
MM_GETMODE1:
	RLA				; ROTATE BIT ONE POSITION
	DJNZ	MM_GETMODE1		; UNTIL BIT SET FOR MODE SPECIFIED
	PUSH	AF			; SAVE IT
	LD	A,(FDCID)		; A := FDC ID
	LD	HL,MD_MAP		; HL := START OF MODE MAP
	CALL	ADDHLA			; OFFSET TO CORRECT ENTRY FOR FDC
	POP	AF			; RECOVER MODE BIT VALUE
	AND	(HL)			; MASK WITH MODE MAP OF FDC
	JR	NZ,MM_TRACE		; NON-ZERO IS A MODE MATCH FOR FDC, CONTINUE
	LD	DE,MM_MODEERR		; INVALID MODE FOR FDC ERROR MESSAGE
	CALL	WRITESTR
	JR	MM_GETMODE

MM_TRACE:	
	LD	DE,MMP_TRACE
	LD	HL,DCD_TRACE
	LD	BC,0001H
	CALL	GETHEXBYTERNG

	RET
	
MM_GETFILL:
	LD	DE,MMP_FILL
	LD	HL,DCD_FILL
	CALL	GETHEXBYTE
	RET

MM_GETPATTERN:
	LD	DE,MMP_PATTERN
	LD	HL,DCD_PATTERN
	CALL	GETHEXBYTE
	RET

MM_GETTGT:
	CALL	PC_SPACE
	LD	A,(DCD_DOP)
	LD	DE,MMPS_TGTF
	CP	DOP_FORMAT
	JP	Z,MM_GETTGT0
	LD	DE,MMPS_TGTRW
MM_GETTGT0:
	CALL	WRITESTR
MM_GETTGT1:
	CALL	GETKEYUC

	LD	B,MMT_TRACK
	LD	DE,MMTS_TRACK
	CP	'T'
	JP	Z,MM_GETTGT3
	LD	B,MMT_DISK
	LD	DE,MMTS_DISK
	CP	'D'
	JP	Z,MM_GETTGT3
		; FORMAT CANNOT DO THE NEXT ONES (GETTGT2 CHECKS FOR THIS)
	LD	B,MMT_SECTOR
	LD	DE,MMTS_SECTOR
	CP	'S'
	JP	Z,MM_GETTGT2
	LD	B,MMT_RANDOM
	LD	DE,MMTS_RANDOM
	CP	'R'
	JP	Z,MM_GETTGT2
	JP	MM_GETTGT1
MM_GETTGT2:	; PREVENT FORMAT FROM USING SECTOR OR RANDOM FUNCTIONS
	LD	A,(DCD_DOP)
	CP	DOP_FORMAT
	JP	Z,MM_GETTGT1
MM_GETTGT3:
	LD	A,B
	LD	(MMD_TGT),A
	CALL	WRITESTR
	RET
	
MM_GETTGTPARMS:
	LD	A,(MMD_TGT)
	CP	MMT_DISK
	JP	Z,MM_GETTGTPARMS1
	CP	MMT_RANDOM
	JP	Z,MM_GETTGTPARMS1
	CALL	MM_GETTRACK
	CALL	MM_GETHEAD
	LD	A,(MMD_TGT)
	CP	MMT_TRACK
	JP	Z,MM_GETTGTPARMS1
	CALL	MM_GETSECTOR
	JP	MM_GETTGTPARMSX
MM_GETTGTPARMS1:
	LD	A,1	; FIX UP THE SECTOR VALUE TO BE 1 IF NOT SINGLE SECTOR MODE
	LD	(DCD_SECTOR),A
MM_GETTGTPARMSX:
	RET
	
MM_GETTRACK:
	LD	DE,MMP_TRACK
	LD	HL,DCD_TRACK
	LD	B,0
	LD	A,(MDB_NUMCYL)
	DEC	A
	LD	C,A
	CALL	GETHEXBYTERNG
	RET

MM_GETHEAD:
	LD	DE,MMP_HEAD
	LD	HL,DCD_HEAD
	LD	B,0
	LD	A,(MDB_NUMHD)
	DEC	A
	LD	C,A
	CALL	GETHEXBYTERNG
	RET

MM_GETSECTOR:
	LD	DE,MMP_SECTOR
	LD	HL,DCD_SECTOR
	LD	A,(MDB_SOT)
	LD	B,A
	LD	A,(MDB_EOT)
	LD	C,A
	CALL	GETHEXBYTERNG
	RET
	
MM_GETINTRLV:
	LD	DE,MMP_INTRLV
	LD	HL,DCD_INTRLV
	LD	B,1
	LD	A,(MDB_NUMSEC)
	LD	C,A
	CALL	GETHEXBYTERNG
	RET
	
MM_MEDIALIST:
	LD	HL,MIT
	LD	B,MIT_ENTCNT
	LD	C,0
MM_MEDIALISTLOOP:
	CALL	NEWLINE
	LD	A,C
	CALL	PRTHEXBYTE
	CALL	PC_COLON
	CALL	PC_SPACE
	PUSH	HL
	
	LD	A,(HL)		; HL = ENTRY VALUE
	INC	HL		; "
	LD	H,(HL)		; "
	LD	L,A		; "
	
	INC	HL
	INC	HL

	LD	A,(HL)		; HL = ENTRY VALUE
	INC	HL		; "
	LD	D,(HL)		; "
	LD	E,A		; "
	
	CALL	WRITESTR

	POP	HL
	INC	HL
	INC	HL
	INC	C
	DJNZ	MM_MEDIALISTLOOP
	RET

MM_MODELIST:
	LD	HL,MDT
	LD	B,MDT_ENTCNT
	LD	C,0
MM_MODELISTLOOP:
	CALL	NEWLINE
	LD	A,C
	CALL	PRTHEXBYTE
	CALL	PC_COLON
	CALL	PC_SPACE
	PUSH	HL
	
	LD	A,(HL)		; HL = ENTRY VALUE
	INC	HL		; "
	LD	H,(HL)		; "
	LD	L,A		; "
	
	INC	HL
	INC	HL

	LD	A,(HL)		; HL = ENTRY VALUE
	INC	HL		; "
	LD	D,(HL)		; "
	LD	E,A		; "
	
	CALL	WRITESTR

	POP	HL
	INC	HL
	INC	HL
	INC	C
	DJNZ	MM_MODELISTLOOP
	RET

MM_SETVFYBUF:
	LD	HL,BUFFER
	LD	DE,VFYBUF
	LD	BC,BUFSIZ
	LDIR
	RET
;	
; MAIN MENU PROMPTS
;
MMP_FILL	.TEXT	"FILL VALUE$"
MMP_PATTERN	.TEXT	"PATTERN START VALUE$"
MMP_UNIT	.TEXT	"UNIT$"
MMP_TRACE	.TEXT	"TRACE LEVEL$"
MMP_TRACK	.TEXT	"TRACK$"
MMP_HEAD	.TEXT	"HEAD$"
MMP_SECTOR	.TEXT	"SECTOR$"
MMP_MEDIA	.TEXT	"MEDIA$"
MMP_MODE	.TEXT	"MODE$"
MMP_INTRLV	.TEXT	"INTERLEAVE$"
;
; MAIN MENU TARGET
;
MMT_SECTOR	.EQU	'S'	; PERFORM OPERATION ON ONE SECTOR
MMT_TRACK	.EQU	'T'	; PERFORM OPERATION ON ONE TRACK
MMT_DISK	.EQU	'D'	; PERFORM OPERATION ON ENTIRE DISK
MMT_RANDOM	.EQU	'R'	; PERFORM OPERATION ON RANDOM SECTORS
;
; MAIN MENU TARGET STRINGS
;
MMTS_SECTOR	.TEXT	"SECTOR$"
MMTS_TRACK	.TEXT	"TRACK$"
MMTS_DISK	.TEXT	"DISK$"
MMTS_RANDOM	.TEXT	"RANDOM$"
;
; MAIN MENU DATA
;
MMD_TGT		.DB	MMT_SECTOR
;
; MAIN MENU PROMPT STRINGS
;
MMPS_TGTRW	.TEXT	"(S)ECTOR, (T)RACK, (D)ISK, (R)ANDOM ===> $"
MMPS_TGTF	.TEXT	"(T)RACK, (D)ISK ===> $"
MMPS_IBOPT	.TEXT	"(P)ATTERN, (F)ILL ===> $"
;
; MAIN MENU BUFFER OPTIONS
;
MMBS_PATTERN	.TEXT	"PATTERN$"
MMBS_FILL	.TEXT	"FILL$"
;
;
;
MM_MODEERR	.TEXT	"\r\n\r\n*** SELECTED MODE NOT SUPPORTED ON HARDWARE ***\r\n$"
;
;________________________________________________________________________________________________________________________________ 
;
; MAIN MENU OPERATIONS
;________________________________________________________________________________________________________________________________ 
;
MMOP_PROC:
	CALL	NEWLINE
	CALL	NEWLINE
	
	LD	A,FALSE
	LD	(DCD_ABORT),A
	
	CALL	DIO_INIT
	
	LD	A,(MMD_TGT)
	CP	MMT_DISK
	JP	Z,MMOP_PROCDISK
	CP	MMT_TRACK
	JP	Z,MMOP_PROCTRK
	CP	MMT_SECTOR
	JP	Z,MMOP_PROCSEC
	CP	MMT_RANDOM
	JP	Z,MMOP_PROCRND
	
	JP	MMOP_PROCX

MMOP_PROCDISK:
	CALL	MMOP_DISK
	JP	MMOP_PROCX
	
MMOP_PROCTRK:
	CALL	MMOP_TRACK
	JP	MMOP_PROCX
	
MMOP_PROCSEC:
	CALL	MMOP_SECTOR
	JP	MMOP_PROCX
	
MMOP_PROCRND:
	CALL	MMOP_RANDOM
	JP	MMOP_PROCX

MMOP_PROCX:
	CALL	DIO_TERM
	RET
	
MMOP_DISK:
	LD	A,0
	LD	(DCD_TRACK),A
	LD	(DCD_HEAD),A
MMOP_DISK1:
	LD	A,(DCD_ABORT)
	CP	TRUE
	JP	Z,MMOP_DISKX
	
	CALL	MMOP_TRACK

	LD	A,(MDB_NUMHD)
	LD	C,A
	LD	A,(DCD_HEAD)		; INC HEAD
	INC	A
	LD	(DCD_HEAD),A
	CP	C			; # OF HEADS
	JP	NZ,MMOP_DISK1		; LOOP IF LESS THAN # OF HEADS
	LD	A,0			; RESET HEAD
	LD	(DCD_HEAD),A		; AND FALL THROUGH TO INC TRACK

	LD	A,(MDB_NUMCYL)
	LD	C,A
	LD	A,(DCD_TRACK)		; INC TRACK
	INC	A
	LD	(DCD_TRACK),A
	CP	C			; # OF TRACKS
	JP	NZ,MMOP_DISK1
MMOP_DISKX:
	LD	A,0			; RESET TRACK TO A VALID VALUE
	LD	(DCD_TRACK),A
	RET
	
MMOP_TRACK:
	LD	A,(DCD_DOP)		; SPECIAL CASE, FORMAT IS TRACK-AT-A-TIME
	CP	DOP_FORMAT
	JP	Z,MMOP_TRACKFMT

	LD	A,(MDB_SOT)
	LD	(DCD_SECTOR),A
MMOP_TRACK1:
	LD	A,(DCD_ABORT)
	CP	TRUE
	JP	Z,MMOP_TRACKX
	
	CALL	DIO_RUN

	LD	A,(MDB_EOT)
	LD	C,A
	INC	C			; ONE MORE THAN EOT
	LD	A,(DCD_SECTOR)		; INC SECTOR
	INC	A
	LD	(DCD_SECTOR),A
	CP	C			; > MAX SECTOR?
	JP	NZ,MMOP_TRACK1
	JP	MMOP_TRACKX

MMOP_TRACKFMT:
	CALL	DIO_RUN
	JP	MMOP_TRACKX

MMOP_TRACKX:
	LD	A,(MDB_SOT)		; RESET SECTOR TO A VALID VALUE
	LD	(DCD_SECTOR),A
	RET
	
MMOP_SECTOR:
	CALL DIO_RUN
	RET
	
MMOP_RANDOM:
	LD	B,20H			; READ 20H SECTORS RANDOMLY
	
MMOP_RANDOM0:
	LD	A,(DCD_ABORT)
	CP	TRUE
	JP	Z,MMOP_RANDOMX
	
	PUSH	BC

MMOP_RANDOM1:	; GENERATE RANDOM TRACK
	LD	A,(MDB_NUMCYL)
	LD	C,A
	CALL	RNDBYTE
	AND	7FH			; USE 7 BITS FOR UP TO 128 TRACKS
	CP	C			; MAX TRACK IS 4F + 1 = 50H
	JP	P,MMOP_RANDOM1
	LD	(DCD_TRACK),A

MMOP_RANDOM2:	; GENERATE RANDOM HEAD
	;OR	A			
	;CALL	RNDBYTE
	;AND	01H			; JUST USE LOW ORDER BIT
	;LD	(DCD_HEAD),A
	LD	A,(MDB_NUMHD)
	LD	C,A
	CALL	RNDBYTE
	AND	01H			; USE 1 BIT FOR UP TO 2 HEADS
	CP	C
	JP	P,MMOP_RANDOM2
	LD	(DCD_HEAD),A
	
MMOP_RANDOM3:	; GENERATE RANDOM SECTOR
	LD	A,(MDB_EOT)
	LD	C,A
	INC	C			; ONE MORE THEN MAX SECTOR
	CALL	RNDBYTE
	AND	1FH			; USE 5 BITS FOR UP TO 32 SECTORS
	CP	C			; MAX SECTOR NUM IS 9 + 1 = 0AH
	JP	P,MMOP_RANDOM3
	CP	00H			; SECTOR NUM STARTS AT 1, DON'T ALLOW ZERO
	JP	Z,MMOP_RANDOM3
	LD	(DCD_SECTOR),A
	
	CALL	DIO_RUN

	POP	BC
	DJNZ	MMOP_RANDOM0

MMOP_RANDOMX:
	RET
	
;
;===============================================================================
; DISK INPUT/OUTPUT SERVICES, DEVICE INDEPENDENT (LOGICAL) ACCESS TO STORAGE
; TRANSLATES BETWEEN LOGICAL AND PHYSICAL DEVICE OPERATIONS
;===============================================================================
;
;	
STR_CONTINUE:
	.TEXT	"CONTINUE? (A)BORT, (R)ETRY, (I)GNORE ===> $"
;
DIO_SETMEDIA:
	; INITIZLIZE THE MEDIA DESCRIPTION BLOCK
	; FILL IN MDB BASED ON USER SELECTION
	LD	A,(DCD_MT)	; A = MEDIA ID (OFFSET IN MEDIA INDEX TABLE)
	ADD	A,A		; * 2 (2 BYTES PER ENTRY)
	LD	H,0		; MOVE IT TO HL
	LD	L,A		; "
	LD	DE,MIT		; DE = START OF MEDIA INDEX TABLE
	ADD	HL,DE		; ADD HL TO DE, HL NOW POINTS TO DESIRED ENTRY
	LD	A,(HL)		; HL = ENTRY VALUE
	INC	HL		; "
	LD	H,(HL)		; "
	LD	L,A		; "
	LD	DE,MDB		; HL = SOURCE, DE = DESTINATION
	LD	BC,MDB_LEN	; BC = BYTES TO COPY
	LDIR			; COPY ENTRY TO FCD

	LD	A,FALSE		; SET DRIVE READY TO FALSE!
	LD	(DCD_DSKRDY),A	; "
	RET
;	
DIO_INIT:
	; UPDATE DRIVE SELECTTION
	LD	A,(FCD_DS)	; GET THE CURRENT DRIVE SELECTION
	LD	B,A		; SAVE IN B
	LD	A,(DCD_UNIT)	; GET THE NEW DRIVE SELECTION
	CP	B		; CHANGED?
	; WE NEED TO SET DRIVE STATUS TO NOT READY IFF IT CHANGED
	JP	Z,DIO_INIT1	; DO NOT RESET DRIVE STATUS!
	LD	(FCD_DS),A	; UPDATE FCD_DS TO NEW VALUE
	LD	A,FALSE		; SET DRIVE READY TO FALSE!
	LD	(DCD_DSKRDY),A	; "

DIO_INIT1:
	; INITIALIZE TRACE SETTING
	LD	A,(DCD_TRACE)
	LD	(FCD_TRACE),A
	
	LD	HL,MDB_FCD	; HL = SOURCE
	LD	DE,FCD		; DE = DESTINATION
	LD	BC,FCD_LEN	; BC = BYTES TO COPY
	LDIR			; BYTES COPY FROM MDB TO FCD
	
	LD	A,0		; ASSUME DMA (NON-DMA = 0)
	LD	(FCD_ND),A
	LD	A,(DCD_MD)
	CP	MD_DRQWAIT
	JP	Z,DIO_INIT2	; YES, DMA NEEDED, DO IT

	LD	A,1		; SET TO NON-DMA (NDMA = 1)
	LD	(FCD_ND),A

DIO_INIT2:
	CALL	FC_INIT
	CALL	FC_MOTORON
	RET

DIO_TERM:
	CALL	FC_MOTOROFF
	RET
;
; DIO_CLRDSKCHG
;
DIO_CLRDSKCHG:
	; PROCESS ANY PENDING DISK CHANGE NOTIFICATIONS
	LD	B,5
DIO_CLRDSKCHG1:
	PUSH	BC
	CALL	FC_SENSEINT
	POP	BC
	CALL	DIO_CHKFC
	CP	FRC_DSKCHG
	RET	NZ
	DJNZ	DIO_CLRDSKCHG1
	RET
;
; DIO_WTSEEK
;	
; WAIT FOR PENDING SEEK OPERATION TO COMPLETE BY POLLING SENSEINT
; AND WAITING FOR ABTERM OR OK.
;
DIO_WTSEEK:
	LD	BC,1000H
;	LD	BC,20H			; *DEBUG*
;	LD	BC,1H			; *DEBUG*
DIO_WTSEEKLOOP:
	PUSH	BC
	CALL	FC_SENSEINT
	POP	BC
	
	LD	A,(FST_RC)		; CHECK RC
	CP	FRC_ABTERM		; ABTERM = DONE
	RET	Z
	CP	FRC_OK			; OK = DONE
	RET	Z
	
	DEC	BC			; CHECK LOOP COUNTER IN BC
	LD	A,B			; "
	OR	C			; "
	JP	NZ,DIO_WTSEEKLOOP	; LOOP UNTIL COUNTER EXHAUSTED

DIO_DRIVERESET:
	CALL	NEWLINE
	CALL	NEWLINE
	LD	DE,STR_DRIVERESET
	CALL	WRITESTR

	CALL	FC_RESETFDC
	
	CALL	DIO_CLRDSKCHG
	CALL	DIO_CHKFC
	CP	FRC_INVCMD		; INVALID COMMAND IS CORRECT RESPONSE HERE
	JP	NZ,DIO_NORESP

	; CONTINUE RESET SEQUENCE WITH 'SPECIFY' COMMAND
	CALL	FC_SPECIFY
	CALL	DIO_CHKFC
	JP	NZ,DIO_NORESP

	CALL	FC_RECAL
	CALL	DIO_CHKFC
	JP	NZ,DIO_NORESP

	; CAREFUL... FIRST RECAL MAY FAIL TO REACH TRACK 0
	; SO WE ALLOW FOR A SECOND TRY IN CASE OF A FAILURE
	CALL	DIO_WTSEEK
	CALL	DIO_CHKFC
	JP	Z,DIO_DRIVERESET1

	; SECOND TRY, ONLY IF NEEDED
	CALL	FC_RECAL
	CALL	DIO_CHKFC
	JP	NZ,DIO_NORESP

	CALL	DIO_WTSEEK
	CALL	DIO_CHKFC
	JP	NZ,DIO_NORESP
	JP	DIO_DRIVERESET1

DIO_NORESP:
	CALL	NEWLINE
	LD	DE,STR_NORESP
	CALL	WRITESTR
	RET
	
DIO_DRIVERESET1:
	LD	A,TRUE
	LD	(DCD_DSKRDY),A

	LD	A,0
	LD	(DCD_CURTRK),A
	
	RET
	
DIO_CHKFC:
	LD	A,(FST_RC)
	OR	A
	RET

DIO_RUN:
	LD	A,(DCD_DSKRDY)
	CP	TRUE
	JP	Z,DIO_RUN0

	CALL	DIO_DRIVERESET
	LD	A,(DCD_DSKRDY)
	CP	TRUE
	JP	NZ,DIO_RUNERR		; DRIVERESET FAILED!

DIO_RUN0:
	CALL	DIO_PROGRESS

	; COPY PARMS OVER
	LD	A,(DCD_UNIT)
	LD	(FCD_DS),A
	
	LD	A,(DCD_TRACE)
	LD	(FCD_TRACE),A

	LD	A,(DCD_TRACK)
	LD	(FCD_C),A
	
	LD	A,(DCD_HEAD)
	LD	(FCD_H),A
	
	LD	A,(DCD_SECTOR)
	LD	(FCD_R),A

	LD	A,(DCD_INTRLV)
	LD	(FCD_X),A

		; FIX: COMBINE WITH DCD_TRACK SETUP ABOVE?
	LD	A,(DCD_CURTRK)
	LD	B,A
	LD	A,(DCD_TRACK)
	CP	B
	JP	Z,DIO_RUN1	; SKIP SEEK IF POSSIBLE
	
		; SEEK AND WAIT FOR COMPLETE
	CALL	FC_SEEK
	CALL	DIO_CHKFC
	JP	NZ,DIO_RUNERR
	
	CALL	DIO_WTSEEK
	CALL	DIO_CHKFC
	JP	NZ,DIO_RUNERR
	
		; RECORD CURRENT TRACK
		; FIX: SHOULD NOT ASSUME SEEK REQUEST SUCCEEDED (FC_READID?)
	LD	A,(DCD_TRACK)
	LD	(DCD_CURTRK),A
	
DIO_RUN1:
	LD	A,(DCD_DOP)

	CP	DOP_READ
	JP	Z,DIO_RUNREAD
	
	CP	DOP_WRITE
	JP	Z,DIO_RUNWRITE
	
	CP	DOP_FORMAT
	JP	Z,DIO_RUNFORMAT
	
	CP	DOP_VERIFY
	JP	Z,DIO_RUNVERIFY
	
	JP	DIO_RUNX

DIO_RUNREAD:
	CALL	FC_READ
	JP	DIO_RUNCHK
		
DIO_RUNWRITE:
	CALL	FC_WRITE
	JP	DIO_RUNCHK
		
DIO_RUNFORMAT:
	CALL	FC_FMTTRK
	JP	DIO_RUNCHK

DIO_RUNVERIFY:
	CALL	FC_READ
	JP	DIO_RUNCHK
	
DIO_RUNCHK:
	LD	A,(FST_RC)
	OR	A
	CP	FRC_OK
	JP	NZ,DIO_RUNERR	; HANDLE I/O ERROR
	LD	A,(DCD_DOP)
	CP	DOP_VERIFY
	JP	NZ,DIO_RUNX	; NOT VERIFY, ALL DONE
	CALL	DIO_VERIFY
	LD	A,(FST_RC)
	OR	A
	CP	FRC_OK
	JP	NZ,DIO_RUNERR	; HANDLE VERIFY ERROR
	JP	DIO_RUNX
	
DIO_RUNERR:
	LD	A,FALSE
	LD	(DCD_DSKRDY),A
	CALL	FC_RESETFDC	; FIX...
	CALL	NEWLINE
	LD	DE,STR_CONTINUE	; ABORT/RETRY/IGNORE PROMPT
	CALL	WRITESTR
	
DIO_RUNERR1:
	CALL	GETKEYUC
	
	CP	'A'
	JP	Z,DIO_ABORT
	CP	'R'
	JP	Z,DIO_RETRY
	CP	'I'
	JP	Z,DIO_IGNORE

	JP	DIO_RUNERR1
	
DIO_ABORT:
	LD	DE,DIOCS_ABORT
	CALL	WRITESTR
	LD	A,TRUE
	LD	(DCD_ABORT),A
	JP	DIO_RUNX

DIO_RETRY:
	LD	DE,DIOCS_RETRY
	CALL	WRITESTR
	JP	DIO_RUN
	
DIO_IGNORE:
	LD	DE,DIOCS_IGNORE
	CALL	WRITESTR
	JP	DIO_RUNX
		
DIO_RUNX:
	RET
	
DIO_PROGRESS:
	LD	A,(NEWLINE_USED)
	OR	A
	JP	Z,DIO_PROGRESS1
	CALL	NEWLINE
	LD	A,0
	LD	(NEWLINE_USED),A

DIO_PROGRESS1:
	CALL	PC_CR
	LD	DE,STR_PROGRESS
	CALL	WRITESTR
	CALL	PC_COLON
	CALL	PC_SPACE
	LD	DE,DPL_TRACK
	CALL	WRITESTR
	CALL	PC_EQUAL
	LD	A,(DCD_TRACK)
	CALL	PRTHEXBYTE
	CALL	PC_SPACE
	LD	DE,DPL_HEAD
	CALL	WRITESTR
	CALL	PC_EQUAL
	LD	A,(DCD_HEAD)
	CALL	PRTHEXBYTE
	CALL	PC_SPACE
	LD	DE,DPL_SECTOR
	CALL	WRITESTR
	CALL	PC_EQUAL
	LD	A,(DCD_SECTOR)
	CALL	PRTHEXBYTE
	
	RET

DIO_VERIFY:
	LD	HL,BUFFER
	LD	DE,VFYBUF
	LD	BC,BUFSIZ
	
DIO_VERIFY1:
	LD	A,(DE)
	CP	(HL)
	JP	NZ,DIO_VERIFY2
	INC	DE
	INC	HL
	DEC	BC
	LD	A,B
	OR	C
	JP	NZ,DIO_VERIFY1
	RET

DIO_VERIFY2:
	PUSH	DE
	
	CALL	NEWLINE
	LD	DE,STR_MISMATCH
	CALL	WRITESTR
	POP	DE
	LD	A,D
	SUB	VFYBUF >> 8
	CALL	PRTHEXBYTE
	LD	A,E
	CALL	PRTHEXBYTE
	LD	A,FRC_MISMATCH
	LD	(FST_RC),A
	RET
;	
; CONTINUE PROMPT OPTION STRINGS
;
DIOCS_ABORT	.TEXT	"ABORT$"
DIOCS_RETRY	.TEXT	"RETRY$"
DIOCS_IGNORE	.TEXT	"IGNORE$"
;
; DISK PROGRESS LABELS
;
DPL_TRACK	.TEXT	"TRACK$"
DPL_HEAD	.TEXT	"HEAD$"
DPL_SECTOR	.TEXT	"SECTOR$"
;
; DISK OPERATIONS
;
DOP_READ	.EQU	0	; READ OPERATION
DOP_WRITE	.EQU	1	; WRITE OPERATION
DOP_FORMAT	.EQU	2	; FORMAT OPERATION
DOP_VERIFY	.EQU	3	; VERIFY OPERATION
;
; DEVICE CONTROL DATA
;
DCD_FILL	.DB	0E5H		; DEFAULT TO E5 (EMPTY DIRECTORY BYTE)
DCD_PATTERN	.DB	000H		; DEFAULT TO 00
DCD_TRACE	.DB	000H		; TRACE LEVEL
DCD_UNIT	.DB	000H		; DEFAULT UNIT = 0
DCD_SECTOR	.DB	001H		; DEFAULT SECTOR = 1
DCD_HEAD	.DB	000H		; DEFAULT HEAD = 0
DCD_TRACK	.DB	000H		; DEFAULT TRACK = 0
DCD_INTRLV	.DB	002H		; DEFAULT INTERLEAVE = 2
DCD_DOP		.DB	DOP_READ	; DEFAULT OP = READ
DCD_MT		.DB	MT_PC720	; DEFAULT FLOPPY DEVICE = PC720
DCD_MD		.DB	MD_POLL		; DEFAULT MODE = POLL
;
; DEVICE CONTROL DATA (PRIVATE)
;
DCD_DSKRDY	.DB	FALSE		; 0 = NOT RDY, 1 = RDY
DCD_ABORT	.DB	FALSE		; 0 = CONT, 1 = ABORT
DCD_CURTRK	.DB	0FFH		; CURRENT TRACK, FF = UNKNOWN
;
; MODES
;
MD_POLL		.EQU	0
MD_INT		.EQU	1
MD_INTFAST	.EQU	2
MD_INTWAIT	.EQU	3
MD_DRQWAIT	.EQU	4
;
; MODE MAPPING
; BIT IS SET FOR ALLOWED MODES PER FDC
;
MD_MAP:
		.DB	%00011111	; DIO	POLL,INT,INTFAST,INTWAIT,DRQWAIT
		.DB	%00000111	; DIO3	POLL,INT,INTFAST
		.DB	%00000111	; ZETA	POLL,INT,INTFAST
		.DB	%00000001	; ZETA2	POLL
		.DB	%00000001	; DIDE	POLL
		.DB	%00000001	; N8	POLL
		.DB	%00000001	; RCSMC	POLL
		.DB	%00000001	; RCWDC	POLL
		.DB	%00000001	; SMZ80	POLL
		.DB	%00000001	; DYNO	POLL
		.DB	%00000001	; EPFDC	POLL
		.DB	%00000001	; MBC	POLL
		.DB	%00000001	; DUO	POLL
;
; MEDIA DESCRIPTION BLOCK
;
MDB:
MDB_LABEL	.DW	000H		; ADDRESS OF MEDIA LABEL
MDB_DESC	.DW	000H		; ADDRESS OF MEDIA DESCRIPTION
MDB_NUMCYL	.DB	000H		; NUMBER OF CYLINDERS
MDB_NUMHD	.DB	000H		; NUMBER OF HEADS
MDB_NUMSEC	.DB	000H		; NUMBER OF SECTORS
MDB_SOT		.DB	000H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
MDB_FCD:	; FLOPPY CONFIGURATION DATA (PUBLIC) MANAGED AS A "BLOCK", MUST MATCH FCB BELOW
MDB_EOT					; END OF TRACK SECTOR (SAME AS SC SINCE SOT ALWAYS 1)
MDB_SC		.DB	000H		; SECTOR COUNT
MDB_SECSZ	.DW	000H		; SECTOR SIZE IN BYTES
MDB_GPL		.DB	000H		; GAP LENGTH (R/W)
MDB_GPLF	.DB	000H		; GAP LENGTH (FORMAT)
MDB_SRTHUT	.DB	000H		; STEP RATE, IBM PS/2 CALLS FOR 3ms, 0DH = 3ms SRT, HEAD UNLOAD TIME
MDB_HLT		.DB	000H		; HEAD LOAD TIME, IBM PS/2 CALLS FOR 15ms 08H = 16ms HUT
MDB_DORA	.DB	000H		; OPERATIONS REGISTER VALUE FOR MEDIA
MDB_DORB	.DB	000H		; OPERATIONS REGISTER VALUE FOR MEDIA
MDB_DORC	.DB	000H		; OPERATIONS REGISTER VALUE FOR MEDIA
MDB_DORD	.DB	000H		; OPERATIONS REGISTER VALUE FOR MEDIA
MDB_DCR		.DB	000H		; CONTROL REGISTER VALUE FOR MEDIA
MDB_LEN		.EQU	$ - MDB
;
MDT:		; MODE TABLE
		.DW	MTB_POLL
		.DW	MTB_INT
		.DW	MTB_INTFAST
		.DW	MTB_INTWAIT
		.DW	MTB_DRQWAIT
MDT_ENTCNT	.EQU	(($ - MDT) / 2)
;
MTB_POLL	.DW	MTL_POLL	; ADDRESS OF MODE LABEL
		.DW	MTS_POLL	; ADDRESS OF MODE DESCRIPTION
MTL_POLL	.TEXT	"POLL      $"
MTS_POLL	.TEXT	"POLLING (RECOMMENDED)$"
;
MTB_INT		.DW	MTL_INT		; ADDRESS OF MODE LABEL
		.DW	MTS_INT		; ADDRESS OF MODE DESCRIPTION
MTL_INT		.TEXT	"INT	   $"
MTS_INT		.TEXT	"INTERRUPT (!!! READ MANUAL !!!)$"
;
MTB_INTFAST	.DW	MTL_INTFAST	; ADDRESS OF MODE LABEL
		.DW	MTS_INTFAST	; ADDRESS OF MODE DESCRIPTION
MTL_INTFAST	.TEXT	"INT-FAST  $"
MTS_INTFAST	.TEXT	"FAST INTERRUPT (!!! READ MANUAL !!!)$"
;
MTB_INTWAIT	.DW	MTL_INTWAIT	; ADDRESS OF MODE LABEL
		.DW	MTS_INTWAIT	; ADDRESS OF MODE DESCRIPTION
MTL_INTWAIT	.TEXT	"INT/WAIT  $"
MTS_INTWAIT	.TEXT	"INT/WAIT (!!! READ MANUAL !!!)$"
;
MTB_DRQWAIT	.DW	MTL_DRQWAIT	; ADDRESS OF MODE LABEL
		.DW	MTS_DRQWAIT	; ADDRESS OF MODE DESCRIPTION
MTL_DRQWAIT	.TEXT	"DRQ/WAIT  $"
MTS_DRQWAIT	.TEXT	"DRQ/WAIT (!!! NOT YET IMPLEMENTED!!!)$"
;
; MEDIA TYPE INFORMATION
;
MT_PC720	.EQU	0
MT_PC144	.EQU	1
MT_PC320	.EQU	2
MT_PC360	.EQU	3
MT_PC120	.EQU	4
MT_PC111	.EQU	5
MT_PC160	.EQU	6
MT_PC180	.EQU	7
MT_PC320SS	.EQU	8
MT_PC360SS	.EQU	9
;
MIT:		; MEDIA INDEX TABLE
		.DW	MDB_PC720
		.DW	MDB_PC144
		.DW	MDB_PC320
		.DW	MDB_PC360
		.DW	MDB_PC120
		.DW	MDB_PC111
		.DW	MDB_PC160
		.DW	MDB_PC180
		.DW	MDB_PC320SS
		.DW	MDB_PC360SS
MIT_ENTCNT	.EQU	(($ - MIT) / 2)
;
; 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)
;
MDB_PC720	.DW	DTL_PC720	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC720	; ADDRESS OF MEDIA DESCRIPTION
		.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)
FCB_PC720	.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		; HLT = 16ms
		.DB	DORA_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR250	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR250	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC720) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC720	.TEXT	"720KB DS/DD  $"
DTS_PC720	.TEXT	"3.5\" 720KB - 9 SECTORS, 2 SIDES, 80 TRACKS, DOUBLE DENSITY$"
;
MDB_PC144	.DW	DTL_PC144	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC144	; ADDRESS OF MEDIA DESCRIPTION
		.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)
FCB_PC144	.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		; HLT = 16ms
		.DB	DORA_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR500	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR500	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC144) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC144	.TEXT	"1.44MB DS/HD $"
DTS_PC144	.TEXT	"3.5\" 1.44MB - 18 SECTORS, 2 SIDES, 80 TRACKS, HIGH DENSITY$"
;	
MDB_PC320	.DW	DTL_PC320	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC320	; ADDRESS OF MEDIA DESCRIPTION
		.DB	028H		; NUMBER OF CYLINDERS
		.DB	002H		; NUMBER OF HEADS
		.DB	008H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
FCB_PC320	.DB	008H		; 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		; HLT = 16ms
		.DB	DORA_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR250	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR250	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC320) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC320	.TEXT	"320KB DS/DD  $"
DTS_PC320	.TEXT	"5.25\" 320KB - 8 SECTORS, 2 SIDES, 40 TRACKS, DOUBLE DENSITY$"
;	
MDB_PC360	.DW	DTL_PC360	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC360	; ADDRESS OF MEDIA DESCRIPTION
		.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)
FCB_PC360	.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		; HLT = 16ms
		.DB	DORA_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR250	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR250	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC360) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC360	.TEXT	"360KB DS/DD  $"
DTS_PC360	.TEXT	"5.25\" 360KB - 9 SECTORS, 2 SIDES, 40 TRACKS, DOUBLE DENSITY$"
;	
MDB_PC120	.DW	DTL_PC120	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC120	; ADDRESS OF MEDIA DESCRIPTION
		.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)
FCB_PC120	.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		; HLT = 16ms
		.DB	DORA_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR500	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR500	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC120) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC120	.TEXT	"1.2MB DS/HD  $"
DTS_PC120	.TEXT	"5.25\" 1.2MB - 15 SECTORS, 2 SIDES, 80 TRACKS, HIGH DENSITY$"
;	
MDB_PC111	.DW	DTL_PC111	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC111	; ADDRESS OF MEDIA DESCRIPTION
		.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)
FCB_PC111	.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		; HLT = 50ms
		.DB	DORA_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR500	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR500	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR500	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC111) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC111	.TEXT	"1.11MB DS/DD $"
DTS_PC111	.TEXT	"8\" 1.11MB - 15 SECTORS, 2 SIDES, 77 TRACKS, DOUBLE DENSITY$"
;	
MDB_PC160	.DW	DTL_PC160	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC160	; ADDRESS OF MEDIA DESCRIPTION
		.DB	028H		; NUMBER OF CYLINDERS
		.DB	001H		; NUMBER OF HEADS
		.DB	008H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
FCB_PC160	.DB	008H		; 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		; HLT = 16ms
		.DB	DORA_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR250	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR250	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC160) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC160	.TEXT	"160KB SS/DD  $"
DTS_PC160	.TEXT	"5.25\" 160KB - 8 SECTORS, 1 SIDE, 40 TRACKS, DOUBLE DENSITY$"
;
MDB_PC180	.DW	DTL_PC180	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC180	; ADDRESS OF MEDIA DESCRIPTION
		.DB	028H		; NUMBER OF CYLINDERS
		.DB	001H		; NUMBER OF HEADS
		.DB	009H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
FCB_PC180	.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		; HLT = 16ms
		.DB	DORA_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR250	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR250	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC180) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC180	.TEXT	"180KB SS/DD  $"
DTS_PC180	.TEXT	"5.25\" 180KB - 9 SECTORS, 1 SIDE, 40 TRACKS, DOUBLE DENSITY$"
;
MDB_PC320SS	.DW	DTL_PC320SS	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC320SS	; ADDRESS OF MEDIA DESCRIPTION
		.DB	050H		; NUMBER OF CYLINDERS
		.DB	001H		; NUMBER OF HEADS
		.DB	008H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
FCB_PC320SS	.DB	008H		; 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		; HLT = 16ms
		.DB	DORA_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR250	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR250	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC320SS) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC320SS	.TEXT	"320KB SS/DD  $"
DTS_PC320SS	.TEXT	"5.25\" 320KB - 8 SECTORS, 1 SIDE, 80 TRACKS, DOUBLE DENSITY$"
;
MDB_PC360SS	.DW	DTL_PC360SS	; ADDRESS OF MEDIA LABEL
		.DW	DTS_PC360SS	; ADDRESS OF MEDIA DESCRIPTION
		.DB	050H		; NUMBER OF CYLINDERS
		.DB	001H		; NUMBER OF HEADS
		.DB	009H		; NUMBER OF SECTORS
		.DB	001H		; START OF TRACK (ID OF FIRST SECTOR, USUALLY 1)
FCB_PC360SS	.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		; HLT = 16ms
		.DB	DORA_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORB_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORC_BR250	; OPERATIONS REGISTER VALUE
		.DB	DORD_BR250	; OPERATIONS REGISTER VALUE
		.DB	DCR_BR250	; CONTROL REGISTER VALUE
		.IF	(($ - MDB_PC360SS) != MDB_LEN)
		.ECHO	"*** FCB SIZE ERROR!!! ***\n"
		.ENDIF
DTL_PC360SS	.TEXT	"360KB SS/DD  $"
DTS_PC360SS	.TEXT	"5.25\" 360KB - 9 SECTORS, 1 SIDE, 80 TRACKS, DOUBLE DENSITY$"
;
;===============================================================================
; FLOPPY DISK CONTROL MENU (DIRECT MENU INTERFACE TO FDC & RELATED HARDWARE)
;===============================================================================
;
; FDC MENU DATA
;
FM_TABLE	.DB	'R' \	 .DW	FMS_READ,		FM_READ
FM_ENTSIZ	.EQU	$ - FM_TABLE
		.DB	'D' \	 .DW	FMS_READDEL,		FM_READDEL
		.DB	'W' \	 .DW	FMS_WRITE,		FM_WRITE
		.DB	'E' \	 .DW	FMS_WRITEDEL,		FM_WRITEDEL
		.DB	'T' \	 .DW	FMS_READTRK,		FM_READTRK
		.DB	'I' \	 .DW	FMS_READID,		FM_READID
		.DB	'F' \	 .DW	FMS_FMTTRK,		FM_FMTTRK
		.DB	'Q' \	 .DW	FMS_SCANEQ,		FM_SCANEQ
		.DB	'L' \	 .DW	FMS_SCANLOEQ,		FM_SCANLOEQ
		.DB	'H' \	 .DW	FMS_SCANHIEQ,		FM_SCANHIEQ
		.DB	'C' \	 .DW	FMS_RECAL,		FM_RECAL
		.DB	'N' \	 .DW	FMS_SENSEINT,		FM_SENSEINT
		.DB	'P' \	 .DW	FMS_SPECIFY,		FM_SPECIFY
		.DB	'V' \	 .DW	FMS_DRVSTAT,		FM_DRVSTAT
		.DB	'S' \	 .DW	FMS_SEEK,		FM_SEEK
		.DB	'O' \	 .DW	FMS_VERSION,		FM_VERSION
		.DB	'U' \	 .DW	FMS_PULSETC,		FM_PULSETC
		.DB	'A' \	 .DW	FMS_DOR,		FM_DOR
		.DB	'M' \	 .DW	FMS_MOTOR,		FM_MOTOR
		.DB	'Z' \	 .DW	FMS_FDCRESET,		FM_FDCRESET
		.DB	'X' \	 .DW	FMS_EXIT,		0000H
FM_COUNT	.EQU	(($ - FM_TABLE) / FM_ENTSIZ)	; # ENTRIES IN TABLE
FM_INFO:	.DW	FM_DRAW
		.DW	FM_TABLE
		.DB	FM_ENTSIZ
		.DB	FM_COUNT
;		
; FDC COMMAND MENU STRINGS
;
STR_FDCMENU:
;	.TEXT	"===================<< FDU FDC COMMAND MENU >>======= [MSR=XX] ==\r\n"
	.TEXT	"===================<< FDU FDC COMMAND MENU >>======= [MSR="
MV_MSR	.TEXT								  "XX"
	.TEXT								    "] ==\r\n"
	.TEXT	"(R)EAD		 READ (D)EL	 (W)RITE	 WRITE D(E)L\r\n"
	.TEXT	"READ (T)RK	 READ (I)D	 (F)ORMAT	 SCAN E(Q)\r\n"
	.TEXT	"SCAN (L)O/EQ	 SCAN (H)I/EQ	 RE(C)AL	 SE(N)SE INT\r\n"
	.TEXT	"S(P)ECIFY	 DRI(V)E STAT	 (S)EEK		 VERSI(O)N\r\n"
;	.TEXT	"P(U)LSE TC	 L(A)TCH:XX	 (M)OTOR:XXX	 FDC RESET (Z)\r\n"
	.TEXT	"P(U)LSE TC	 L(A)TCH:"
MV_LAT	.TEXT				"XX"
	.TEXT				  "	 (M)OTOR:"
MV_MOT	.TEXT						"XXX"
	.TEXT						   "	 FDC RESET (Z)"
	.TEXT								     "\r\n"
	.TEXT	"E(X)IT\r\n"
	.TEXT	"=== OPTION ===> $"
;
FMS_NOP		.TEXT	"NOP$"
FMS_READ	.TEXT	"READ$"
FMS_READDEL	.TEXT	"READ DELETED$"
FMS_WRITE	.TEXT	"WRITE$"
FMS_WRITEDEL	.TEXT	"WRITE DELETED$"
FMS_READTRK	.TEXT	"READ TRACK$"
FMS_READID	.TEXT	"READ ID$"
FMS_FMTTRK	.TEXT	"FORMAT TRACK$"
FMS_SCANEQ	.TEXT	"SCAN EQUAL$"
FMS_SCANLOEQ	.TEXT	"SCAN LOW OR EQUAL$"
FMS_SCANHIEQ	.TEXT	"SCAN HIGH OR EQUAL$"
FMS_RECAL	.TEXT	"RECALIBRATE$"
FMS_SENSEINT	.TEXT	"SENSE INTERRUPT$"
FMS_SPECIFY	.TEXT	"SPECIFY$"
FMS_DRVSTAT	.TEXT	"DRIVE STATUS$"
FMS_SEEK	.TEXT	"SEEK$"
FMS_VERSION	.TEXT	"VERSION$"
FMS_PULSETC	.TEXT	"PULSE TC$"
FMS_DOR		.TEXT	"DOR$"
FMS_MOTOR	.TEXT	"MOTOR$"
FMS_FDCRESET	.TEXT	"FDC RESET$"
FMS_EXIT	.TEXT	"EXIT$"
;
; ENTRY POINT FOR FDCMENU
;
FDCMENU:
	LD	A,01H
	LD	(FCD_TRACE),A		; FORCE TRACING OF ALL FDC COMMANDS
	LD	HL,FM_INFO
	CALL	RUNMENU
	RET
;
; FDCMENU DRAW PROCEDURE
;
FM_DRAW:
	CALL	NEWLINE
	CALL	NEWLINE
	
		; UPDATE MOTOR STATUS
	LD	HL,FDCBM
	LD	A,(HL)
	AND	_DIO
	JR	NZ,FM_DRAW0A
	LD	A,(HL)
	AND	_ZETA | _DIO3
	JR	NZ,FM_DRAW0B
	LD	A,(HL)
	AND	_PCAT
	JR	NZ,FM_DRAW0C
	LD	A,(HL)
	AND	_RCSMC
	JR	NZ,FM_DRAW0D
	JR	FM_DRAW3
FM_DRAW0A:	; DIO
	LD	A,(FST_DOR)
	AND	00000010B
	XOR	00000010B
	JR	FM_DRAW1
FM_DRAW0B:	; ZETA, DIO3
	LD	A,(FST_DOR)
	AND	00000010B
	JR	FM_DRAW1
FM_DRAW0C:	; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO, EPFDC, MBC, DUO
	LD	A,(FST_DOR)
	AND	11110000B
	JR	FM_DRAW1
FM_DRAW0D:	; RCSMC
	LD	A,(FST_DOR)
	AND	00000110B
	JR	FM_DRAW1
FM_DRAW1:
	LD	DE,STR_ON
	JP	NZ,FM_DRAW2
	LD	DE,STR_OFF
FM_DRAW2:
	LD	HL,MV_MOT
	CALL	STRCPY
FM_DRAW3:

		; UPDATE MSR VALUE
	LD	DE,MV_MSR
	LD	C,(IY+CFG_MSR)
	IN	A,(C)
	CALL	HEXSTRBYTE
	
		; UPDATE FST_DOR VALUE
	LD	DE,MV_LAT
	LD	A,(FST_DOR)
	CALL	HEXSTRBYTE

		; DISPLAY THE MENU
	LD	DE,STR_FDCMENU
	CALL	WRITESTR

	RET
;
; FDCMENU FUNCTIONS
;
FM_READ:
	CALL	FM_GETTHS
	CALL	FC_READ
	RET
	
FM_READDEL:
	CALL	FM_GETTHS
	CALL	FC_READDEL
	RET
	
FM_WRITE:
	CALL	FM_GETTHS
	CALL	FC_WRITE
	RET
	
FM_WRITEDEL:
	CALL	FM_GETTHS
	CALL	FC_WRITEDEL
	RET
	
FM_READTRK:
	CALL	FM_GETTHS
	CALL	FC_READTRK
	RET
	
FM_READID:
	CALL	FM_GETHEAD
	CALL	FC_READID
	RET
	
FM_FMTTRK:
	CALL	FM_GETTRK
	CALL	FM_GETHEAD
	CALL	FC_FMTTRK
	RET
	
FM_SCANEQ:
	JP	FM_NOTIMPL		; NOT IMPLEMENTED!
	CALL	FM_GETTHS
	CALL	FC_SCANEQ
	RET
	
FM_SCANLOEQ:
	JP	FM_NOTIMPL		; NOT IMPLEMENTED!
	CALL	FM_GETTHS
	CALL	FC_SCANLOEQ
	RET
	
FM_SCANHIEQ:
	JP	FM_NOTIMPL		; NOT IMPLEMENTED!
	CALL	FM_GETTHS
	CALL	FC_SCANHIEQ
	RET
	
FM_RECAL:
	LD	A,0			; UPDATE CYLINDER FOR FUTURE CALLS
	LD	(FCD_C),A		; FIX: NOT IN THE RIGHT PLACE
	CALL	FC_RECAL
	RET
	
FM_SENSEINT:
	CALL	FC_SENSEINT
	RET
	
FM_SPECIFY:
	CALL	FC_SPECIFY
	RET
	
FM_DRVSTAT:
	CALL	FM_GETHEAD
	CALL	FC_DRVSTAT
	RET
	
FM_SEEK:
	CALL	FM_GETTRK
	CALL	FC_SEEK
	RET

FM_VERSION:
	CALL	FC_VERSION
	RET

FM_PULSETC:
	CALL	NEWLINE
	CALL	FC_PULSETC
	RET

FM_DOR:
	CALL	FM_GETDOR
	CALL	FC_SETDOR
	RET
	
FM_MOTOR:
		; TOGGLE MOTOR STATE
	LD	HL,FDCBM
	LD	A,(HL)
	AND	_DIO
	JR	NZ,FM_MOTOR0A
	LD	A,(HL)
	AND	_ZETA | _DIO3
	JR	NZ,FM_MOTOR0B
	LD	A,(HL)
	AND	_PCAT
	JR	NZ,FM_MOTOR0C
	LD	A,(HL)
	AND	_RCSMC
	JR	NZ,FM_MOTOR0D
	RET
FM_MOTOR0A:	; DIO
	LD	A,(FST_DOR)
	AND	00000010B
	XOR	00000010B
	JR	FM_MOTOR1
FM_MOTOR0B:	; ZETA, DIO3
	LD	A,(FST_DOR)
	AND	00000010B
	JR	FM_MOTOR1
FM_MOTOR0C:	; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO, EPFDC, MBC, DUO
	LD	A,(FST_DOR)
	AND	11110000B
	JR	FM_MOTOR1
FM_MOTOR0D:	; RCSMC
	LD	A,(FST_DOR)
	AND	00000110B
	JR	FM_MOTOR1
FM_MOTOR1:
	JP	Z,FC_MOTORON
	JP	FC_MOTOROFF

FM_FDCRESET:
	CALL	NEWLINE
	LD	A,(FDCID)
	CP	FDC_DIO			; RESET NOT POSSIBLE ON DIO
	JP	NZ,FC_RESETFDC
	LD	DE,FCS_NORES
	JP	WRITESTR

FM_NOTIMPL:
	CALL	PC_SPACE		; NOT IMPLEMENTED
	LD	DE,STR_NOTIMPL
	CALL	WRITESTR
	RET

FM_GETTHS:
	CALL	FM_GETTRK
	CALL	FM_GETHEAD
	CALL	FM_GETSEC
	RET
	
FM_GETTRK:
	LD	DE,FCPP_TRK
	LD	HL,FCD_C
	CALL	GETHEXBYTE
	RET

FM_GETHEAD:
	LD	DE,FCPP_HEAD
	LD	HL,FCD_H
	CALL	GETHEXBYTE
	RET

FM_GETSEC:
	LD	DE,FCPP_SEC
	LD	HL,FCD_R
	CALL	GETHEXBYTE
	RET

FM_GETDOR:
	LD	DE,FCPP_DOR
	LD	HL,FST_DOR
	CALL	GETHEXBYTE
	RET
;
FCS_NORES	.TEXT	"\r\n*** RESET NOT SUPORTED BY HARDWARE ***$"
;
;===============================================================================
; FLOPPY DISK CONTROL SERVICES (PHYSICAL DEVICE CONTROL FOR FDC HARDWARE)
;===============================================================================
;
; 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
FRC_MISMATCH	.EQU	-16H		; EA
;
; 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
;
; FDC COMMAND PHASE
;
FCP_CMD		.DB	000H	; INPUT: COMMAND CODE
FCP_BUFLEN	.DB	00H
FCP_BUF		.FILL	10H
FCP_BUFSIZ	.EQU	$-FCP_BUF
FCP_XFRCNT	.DW	00H	; BYTES TRANSFERRED DURING COMMAND PHASE	
;
; FDC EXECUTION PHASE
;
FXP_XR		.DW	00H	; INPUT: ADDRESS OF EXECUTION ROUTINE TO INVOKE
FXP_TO		.DB	00H	; TIMEOUT COUNTDOWN TIMER USED IN SOME VARIATIONS
FXP_ITER	.DB	00H	; LOOP ITERATION COUNTER (MUST IMMEDIATELY FOLLOW FXP_TO)
FXP_A		.DB	00H	; LAST VALUE OF REG A RECORDED DURING EXECUTION
FXP_BC		.DW	00H	; LAST VALUE OF REG BC RECORDED DURING EXECUTION
FXP_DE		.DW	00H	; LAST VALUE OF REG DE RECORDED DURING EXECUTION
FXP_HL		.DW	00H	; LAST VALUE OF REG HL RECORDED DURING EXECUTION
FXP_BUFLEN	.DB	00H
FXP_BUF		.FILL	50H	; USED FOR CERTAIN EXEC ROUTINES (FORMAT TRACK)
FXP_BUFSIZ	.EQU	$-FXP_BUF
;
; FDC STATUS
;
FST_RC		.DB	00H
FST_MSR		.DB	00H
FST_DOR		.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
		.FILL	10H		; ALLOWS EXTRA CHARACTERS TO BE RETREIEVED
FRB_SIZ		.EQU	$-FRB
;
; FDC COMMANDS
;
CMD_READ	.EQU	00000110B	; ST0,ST1,ST2,C,H,R,N
CMD_READDEL	.EQU	00001100B	; ST0,ST1,ST2,C,H,R,N
CMD_WRITE	.EQU	00000101B	; ST0,ST1,ST2,C,H,R,N
CMD_WRITEDEL	.EQU	00001001B	; ST0,ST1,ST2,C,H,R,N
CMD_READTRK	.EQU	00000010B	; ST0,ST1,ST2,C,H,R,N
CMD_READID	.EQU	00001010B	; ST0,ST1,ST2,C,H,R,N
CMD_FMTTRK	.EQU	00001101B	; ST0,ST1,ST2,C,H,R,N
CMD_SCANEQ	.EQU	00010001B	; ST0,ST1,ST2,C,H,R,N
CMD_SCANLOEQ	.EQU	00011001B	; ST0,ST1,ST2,C,H,R,N
CMD_SCANHIEQ	.EQU	00011101B	; ST0,ST1,ST2,C,H,R,N
CMD_RECAL	.EQU	00000111B	; <EMPTY>
CMD_SENSEINT	.EQU	00001000B	; ST0,PCN
CMD_SPECIFY	.EQU	00000011B	; <EMPTY>
CMD_DRVSTAT	.EQU	00000100B	; ST3
CMD_SEEK	.EQU	00001111B	; <EMPTY>
CMD_VERSION	.EQU	00010000B	; ST0
;
; FDC COMMAND DATA
;
FCD:		; FLOPPY CONFIGURATION DATA (PUBLIC) MANAGED AS A "BLOCK", SEE FCB BELOW
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_HLT		.DB	000H		; HEAD LOAD TIME, IBM PS/2 CALLS FOR 15ms 08H = 16ms HUT
FCD_DORA	.DB	000H		; DEFAULT DOR VALUE FOR MEDIA
FCD_DORB	.DB	000H		; DEFAULT DOR VALUE FOR MEDIA
FCD_DORC	.DB	000H		; DEFAULT DOR VALUE FOR MEDIA
FCD_DORD	.DB	000H		; DEFAULT DOR VALUE FOR MEDIA
FCD_DCR		.DB	000H		; DOR VALUE FOR MEDIA
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		; FORMAT DATA FILL BYTE
FCD_X		.DB	002H		; FORMAT INTERLEAVE FACTOR (1...N)
FCD_ND		.DB	000H		; DMA, 0=DMA, 1=NON-DMA
		; STATIC CONFIGURATION, NEVER CHANGES (PRIVATE)
FCD_SOT		.DB	001H		; STARTING SECTOR NUMBER OF TRACK
FCD_MT		.DB	000H		; MULTI-TRACK, WE DON'T USE, SET TO 0
FCD_MFM		.DB	001H		; MFM, 0=FM, 1=MFM, WE USE MFM ALWAYS
FCD_SK		.DB	000H		; SKIP MODE, WE DON'T USE, SET TO 0
FCD_N		.DB	002H		; SECTOR SIZE, N=2 FOR 512 BYTES
FCD_DTL		.DB	0FFH		; DATA LENGTH (WHEN N=0, SET TO FF OTHERWISE)
FCD_STP		.DB	001H		; SECTOR SCAN TYPE, 1=CONTIG, 2=ALTERNATING
		; CONTROL STUFF (PUBLIC)
FCD_TRACE	.DB	00H		; TRACE LEVEL
;
;
; FDC CMD PARM PROMPTS
;
FCPP_TRK	.TEXT	"TRACK$"
FCPP_HEAD	.TEXT	"HEAD$"
FCPP_SEC	.TEXT	"SECTOR$"
FCPP_DOR	.TEXT	"DOR$"
;
; FDC EXECUTION ROUTINE JUMP TABLE
;
FXRJ_NOP:	JP	FXR_NOP
FXRJ_READ:	JP	FXR_READ
FXRJ_READDEL:	JP	FXR_READDEL
FXRJ_WRITE:	JP	FXR_WRITE
FXRJ_WRITEDEL:	JP	FXR_WRITEDEL
FXRJ_READTRK:	JP	FXR_READTRK
FXRJ_READID:	JP	FXR_READID
FXRJ_FMTTRK:	JP	FXR_FMTTRK
FXRJ_SCANEQ:	JP	FXR_SCANEQ
FXRJ_SCANLOEQ:	JP	FXR_SCANLOEQ
FXRJ_SCANHIEQ:	JP	FXR_SCANHIEQ
FXRJ_RECAL:	JP	FXR_RECAL
FXRJ_SENSEINT:	JP	FXR_SENSEINT
FXRJ_SPECIFY:	JP	FXR_SPECIFY
FXRJ_DRVSTAT:	JP	FXR_DRVSTAT
FXRJ_SEEK:	JP	FXR_SEEK
FXRJ_VERSION	JP	FXR_VERSION
;
; FDC COMMAND STRINGS
;
FCS_NOP:		.TEXT	"NOP$"
FCS_READ:		.TEXT	"READ$"
FCS_READDEL:		.TEXT	"READ DELETED$"
FCS_WRITE:		.TEXT	"WRITE$"
FCS_WRITEDEL:		.TEXT	"WRITE DELETED$"
FCS_READTRK:		.TEXT	"READ TRACK$"
FCS_READID:		.TEXT	"READ ID$"
FCS_FMTTRK:		.TEXT	"FORMAT TRACK$"
FCS_SCANEQ:		.TEXT	"SCAN EQUAL$"
FCS_SCANLOEQ:		.TEXT	"SCAN LOW OR EQUAL$"
FCS_SCANHIEQ:		.TEXT	"SCAN HIGH OR EQUAL$"
FCS_RECAL:		.TEXT	"RECALIBRATE$"
FCS_SENSEINT:		.TEXT	"SENSE INTERRUPT$"
FCS_SPECIFY:		.TEXT	"SPECIFY$"
FCS_DRVSTAT:		.TEXT	"DRIVE STATUS$"
FCS_SEEK:		.TEXT	"SEEK$"
FCS_VERSION:		.TEXT	"VERSION$"
;
; FDC COMMAND TABLE
;
FCT		.DB	CMD_READ \	.DW	FCS_READ,	FXRJ_READ
FCT_ENTSIZ	.EQU	$ - FCT
		.DB	CMD_READDEL \	.DW	FCS_READDEL,	FXRJ_READDEL
		.DB	CMD_WRITE \	.DW	FCS_WRITE,	FXRJ_WRITE
		.DB	CMD_WRITEDEL \	.DW	FCS_WRITEDEL,	FXRJ_WRITEDEL
		.DB	CMD_READTRK \	.DW	FCS_READTRK,	FXRJ_READTRK
		.DB	CMD_READID \	.DW	FCS_READID,	FXRJ_READID
		.DB	CMD_FMTTRK \	.DW	FCS_FMTTRK,	FXRJ_FMTTRK
		.DB	CMD_SCANEQ \	.DW	FCS_SCANEQ,	FXRJ_SCANEQ
		.DB	CMD_SCANLOEQ \	.DW	FCS_SCANLOEQ,	FXRJ_SCANLOEQ
		.DB	CMD_SCANHIEQ \	.DW	FCS_SCANHIEQ,	FXRJ_SCANHIEQ
		.DB	CMD_RECAL \	.DW	FCS_RECAL,	FXRJ_RECAL
		.DB	CMD_SENSEINT \	.DW	FCS_SENSEINT,	FXRJ_SENSEINT
		.DB	CMD_SPECIFY \	.DW	FCS_SPECIFY,	FXRJ_SPECIFY
		.DB	CMD_DRVSTAT \	.DW	FCS_DRVSTAT,	FXRJ_DRVSTAT
		.DB	CMD_SEEK \	.DW	FCS_SEEK,	FXRJ_SEEK
		.DB	CMD_VERSION \	.DW	FCS_VERSION,	FXRJ_VERSION
FCT_COUNT	.EQU	(($ - FCT) / FCT_ENTSIZ)	; # ENTRIES IN TABLE
;
; ENTRY POINTS FOR FDC COMMANDS
;
FC_READ:
	LD	A,CMD_READ
	LD	B,0FFH			; MT & MFM & SK & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_READ
	LD	(FXP_XR),HL
	CALL	FC_SETUPIO
	CALL	FC_CMDPROC
	RET

FC_READDEL:
	LD	A,CMD_READDEL
	LD	B,0FFH			; MT & MFM & SK & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_READDEL
	LD	(FXP_XR),HL
	CALL	FC_SETUPIO
	CALL	FC_CMDPROC
	RET

FC_WRITE:
	LD	A,CMD_WRITE
	LD	B,0DFH			; MT & MFM & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_WRITE
	LD	(FXP_XR),HL
	CALL	FC_SETUPIO
	CALL	FC_CMDPROC
	RET

FC_WRITEDEL:
	LD	A,CMD_WRITEDEL
	LD	B,0DFH			; MT & MFM & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_WRITEDEL
	LD	(FXP_XR),HL
	CALL	FC_SETUPIO
	CALL	FC_CMDPROC
	RET

FC_READTRK:
	LD	A,CMD_READTRK
	LD	B,07FH			; MFM & SK & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_READTRK
	LD	(FXP_XR),HL
	CALL	FC_SETUPIO
	CALL	FC_CMDPROC
	RET

FC_READID:
	LD	A,CMD_READID
	LD	B,05FH			; MFM & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_READID
	LD	(FXP_XR),HL
	CALL	FC_SETUPCMD
	CALL	FC_CMDPROC
	RET
	
FC_FMTTRK:
	LD	A,CMD_FMTTRK
	LD	B,05FH			; MFM & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_FMTTRK
	LD	(FXP_XR),HL
	CALL	FC_SETUPFMT
	CALL	FC_CMDPROC
	RET
	
FC_SCANEQ:
	LD	A,CMD_SCANEQ
	LD	B,0FFH			; MT & MFM & SK & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_SCANEQ
	LD	(FXP_XR),HL
	CALL	FC_SETUPSCAN
	CALL	FC_CMDPROC
	RET

FC_SCANLOEQ:
	LD	A,CMD_SCANLOEQ
	LD	B,0FFH			; MT & MFM & SK & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_SCANLOEQ
	LD	(FXP_XR),HL
	CALL	FC_SETUPSCAN
	CALL	FC_CMDPROC
	RET

FC_SCANHIEQ:
	LD	A,CMD_SCANHIEQ
	LD	B,0FFH			; MT & MFM & SK & CMD BITS
	LD	(FCP_CMD),A
	LD	HL,FXR_SCANHIEQ
	LD	(FXP_XR),HL
	CALL	FC_SETUPSCAN
	CALL	FC_CMDPROC
	RET

FC_RECAL:
	LD	A,CMD_RECAL
	LD	B,01FH			; CMD BITS ONLY
	LD	(FCP_CMD),A
	LD	HL,FXR_RECAL
	LD	(FXP_XR),HL
	CALL	FC_SETUPSEEK		; SPECIALIZATION OF SEEK
	LD	A,2			; GENERIC COMMAND, BUT JUST FIRST COMMAND CODE
	LD	(FCP_BUFLEN),A
	CALL	FC_CMDPROC
	RET

FC_SENSEINT:
	LD	A,CMD_SENSEINT
	LD	B,01FH			; CMD BITS ONLY
	LD	(FCP_CMD),A
	LD	HL,FXR_SENSEINT
	LD	(FXP_XR),HL
	CALL	FC_SETUPCMD
	LD	A,1			; GENERIC COMMAND, BUT JUST FIRST COMMAND CODE
	LD	(FCP_BUFLEN),A
	CALL	FC_CMDPROC
	RET

FC_SPECIFY:
	LD	A,CMD_SPECIFY
	LD	B,01FH			; CMD BITS ONLY
	LD	(FCP_CMD),A
	LD	HL,FXR_SPECIFY
	LD	(FXP_XR),HL
	CALL	FC_SETUPSPECIFY
	CALL	FC_CMDPROC
	RET

FC_DRVSTAT:
	LD	A,CMD_DRVSTAT
	LD	B,01FH			; CMD BITS ONLY
	LD	(FCP_CMD),A
	LD	HL,FXR_DRVSTAT
	LD	(FXP_XR),HL
	CALL	FC_SETUPCMD
	CALL	FC_CMDPROC
	RET

FC_SEEK:
	LD	A,CMD_SEEK
	LD	B,01FH			; CMD BITS ONLY
	LD	(FCP_CMD),A
	LD	HL,FXR_SEEK
	LD	(FXP_XR),HL
	CALL	FC_SETUPSEEK
	CALL	FC_CMDPROC
	RET
	
FC_VERSION:
	LD	A,CMD_VERSION
	LD	B,01FH			; CMD BITS ONLY
	LD	(FCP_CMD),A
	LD	HL,FXR_VERSION
	LD	(FXP_XR),HL
	CALL	FC_SETUPCMD
	LD	A,1			; GENERIC COMMAND, BUT JUST FIRST COMMAND CODE
	LD	(FCP_BUFLEN),A
	CALL	FC_CMDPROC
	RET
;
; HELPER FUNCTIONS TO SETUP CMDBUF
;
FC_SETUPCMD:
	PUSH	BC			; B CONTAINS BIT MASK TO USE FOR FIRST BYTE
					; SO THAT WE CAN MASK OFF BITS THAT ARE NOT
					; USED FOR CERTAIN COMMANDS
	LD	DE,FCP_BUF

	AND	01FH			; REMOVE ANY EXTRANEOUS BITS FROM COMMAND BYTE
	LD	C,A			; SAVE THE COMMAND
	LD	A,(FCD_MT)		; GET MT BIT
	AND	01H			; MASK TO REMOVE IRRELEVANT BITS FOR SAFETY
	RLA				; MAKE ROOM FOR MFM
	LD	B,A			; SAVE WHAT WE HAVE SO FAR IN B
	LD	A,(FCD_MFM)		; GET MFM BIT
	AND	01H			; MASK TO REMOVE IRRELEVANT BITS FOR SAFETY
	OR	B			; COMBINE WITH SAVED
	RLA				; MAKE ROOM FOR SK
	LD	B,A			; SAVE WHAT WE HAVE SO FAR IN B
	LD	A,(FCD_SK)		; GET SK BIT
	AND	01H			; MASK TO REMOVE IRRELEVANT BITS FOR SAFETY
	OR	B			; COMBINE WITH SAVED
	RLA				; MAKE ROOM FOR THE COMMAND BITS
	RLA
	RLA
	RLA
	RLA
	LD	B,C			; RECOVER THE COMMAND VALUE
	OR	B			; COMBINE WITH SAVED
	POP	BC			; GET THE BIT MASK FOR FIRST BYTE
	AND	B			; APPLY IT
	LD	(DE),A			; SAVE THE BYTE
	INC	DE

	LD	A,(FCD_H)		; START WITH HDS	
	AND	01H			; MASK TO REMOVE IRRELEVANT BITS FOR SAFETY
	RLA				; MAKE ROOM FOR DS BITS
	RLA				; 
	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			; THIS IS A 2 BYTE COMMAND STRING
	LD	(FCP_BUFLEN),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
	
	; V5.3, USE EOT=R TO R/W ONLY ONE SECTOR
	;LD	A,(FCD_EOT)
	LD	A,(FCD_R)
	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_BUFLEN),A

	RET
	
FC_SETUPFMT:
	CALL	FC_SETUPCMD

	LD	A,(FCD_N)
	LD	(DE),A
	INC	DE
	
	LD	A,(FCD_SC)
	LD	(DE),A
	INC	DE
	
	LD	A,(FCD_GPLF)
	LD	(DE),A
	INC	DE
	
	LD	A,(FCD_D)
	LD	(DE),A
	INC	DE
	
	LD	A,6
	LD	(FCP_BUFLEN),A

; SETUP FORMAT BUFFER WITH SECTOR ID INFO FOR ENTIRE TRACK - (C,H,S,N) FOR EACH SECTOR
; INTERLEAVE AS REQUESTED
;
; B = CURRENT INTERLEAVE PASS STARTING SECTOR ID
; C = CURRENT SECTOR ID
; H = INTERLEAVE STEP
; L = LAST SECTOR ID + 1
; DE = BUFFER POINTER
;

	PUSH	DE
	PUSH	HL

	; COMPUTE INTERLEAVE STEP
	LD	H,-1
	LD	A,(FCD_X)
	LD	B,A
	LD	A,(FCD_SC)
LOOP:
	INC	H
	SUB	B
	JR	NC,LOOP

	LD	DE,FXP_BUF	; DE POINTS TO START OF BUFFER

	LD	A,(FCD_SOT)	; GET FIRST SECTOR ID
	LD	B,A		; B = FIRST SECTOR ID FOR CURRENT INTERLEAVE PASS
	LD	C,A		; C = SECTOR ID

	LD	A,(FCD_SC)	; NUM SECTORS TO SET UP
	ADD	A,C
	LD	L,A		; L = LAST SECTOR ID + 1
	
FC_SETUPFMT1:
	; CYLINDER
	LD	A,(FCD_C)
	LD	(DE),A
	INC	DE

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

	; SECTOR ID
	LD	A,C
	LD	(DE),A
	INC	DE

	; SECTOR SIZE
	LD	A,(FCD_N)
	LD	(DE),A
	INC	DE
	
	; INC SECTOR ID BY INTERLEAVE STEP
	LD	A,H
	ADD	A,C
	LD	C,A
	
	; LOOP IF WE HAVE NOT GOTTEN PAST LAST SECTOR ID
	CP	L
	JP	M,FC_SETUPFMT1
	
	; SETUP FOR NEXT INTERLEAVE PASS
	INC	B
	LD	C,B
	
	; LOOP IF WE ARE NOT DONE
	LD	A,H
	CP	C
	JP	P,FC_SETUPFMT1
	
	; DONE, FINALIZE BUFFER
	LD	A,(FCD_SC)	; GET SECTOR COUNT
	RLCA			; MULTIPLY BY 4
	RLCA	
	LD	(FXP_BUFLEN),A	; STORE AS BUFFER LENGTH
	
	POP	HL
	POP	DE
	RET
	
FC_SETUPSCAN:
	CALL	FC_SETUPIO	; START WITH GENERIC IO CMD
	
	LD	DE,FCP_BUF+8	; REPLACE DTL WITH STP
	LD	A,(FCD_STP)
	LD	(DE),A

	RET
		
FC_SETUPSEEK:
	CALL	FC_SETUPCMD	; START WITH GENERIC IO CMD
	
	LD	A,(FCD_C)
	LD	(DE),A
	INC	DE
	
	LD	DE,FCP_BUF	; REMOVE EXTRANEOUS BITS FROM CC0
	LD	A,(DE)
	AND	0FH
	LD	(DE),A
	
	LD	A,3
	LD	(FCP_BUFLEN),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_HLT)
	AND	07FH
	RLA
	LD	B,A
	LD	A,(FCD_ND)
	AND	01H
	OR	B
	LD	(DE),A			; SAVE THE BYTE
	INC	DE
	
	LD	A,3
	LD	(FCP_BUFLEN),A

	RET
;
; MAIN FDC COMMAND PROCESSOR
;
FC_CMDPROC:
	CALL	FOP
	CALL	FC_PRTRESULTS
	RET
;
; FDC SEQUENCE INITIALIZATION
;
FC_INIT:
	LD	HL,FDCBM
	LD	A,(HL)
	AND	_DIO
	JR	NZ,FC_INIT1
	LD	A,(HL)
	AND	_ZETA | _DIO3
	JR	NZ,FC_INIT2
	LD	A,(HL)
	AND	_PCAT
	JR	NZ,FC_INIT3
	LD	A,(HL)
	AND	_RCSMC
	JR	NZ,FC_INIT4
	RET
FC_INIT1:	; DIO
	LD	A,(FCD_DORA)
	JR	FC_INIT5
FC_INIT2:	; ZETA, DIO3
	LD	A,(FCD_DORB)
	JR	FC_INIT5
FC_INIT3:	; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO, EPFDC, MBC, DUO
	LD	A,(FCD_DORC)
	JR	FC_INIT5
FC_INIT4:	; WDSMC
	LD	A,(FCD_DORD)
	JR	FC_INIT5

FC_INIT5:
	LD	(FST_DOR),A
	CALL	FC_SETDOR
	RET
;
; SET FST_DOR
;
FC_SETDOR:
	PUSH	AF
	LD	A,(FST_DOR)
	LD	C,(IY+CFG_DOR)
	OUT	(C),A
	POP	AF
	RET
;
; RESET FDC BY PULSING BIT 7 OF DOR LOW
; NOTE: DIO HARDWARE HAS NO MECHANISM TO PULSE RESET VIA SOFTWARE
;
FC_RESETFDC:
	LD	C,(IY+CFG_DOR)
	LD	HL,FDCBM
	LD	A,(HL)
	AND	_ZETA | _DIO3 | _RCSMC
	JR	NZ,FC_RESETFDC1
	LD	A,(HL)
	AND	_PCAT
	JR	NZ,FC_RESETFDC2
	RET
FC_RESETFDC1:	; ZETA, DIO3, RCSMC
	LD	A,(FST_DOR)
	PUSH	AF
	RES	7,A
	OUT	(C),A
	CALL	DELAY
	POP	AF
	OUT	(C),A
	JR	FC_RESETFDC3
FC_RESETFDC2:	; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO, EPFDC, MBC, DUO
	LD	A,0
	OUT	(C),A
	LD	A,(FST_DOR)
	OUT	(C),A
	JR	FC_RESETFDC3
FC_RESETFDC3:
	LD	DE,156			; DELAY: 16us * 156 = 2.5ms
	CALL	VDELAY

	RET
;
; PULSE TERMCT TO TERMINATE ANY ACTIVE EXECUTION PHASE
;
FC_PULSETC:
	; V5.3, USE EOT=R TO R/W ONLY ONE SECTOR
	;LD	A,(FDCBM)
	;AND	_PCAT
	;JR	NZ,FC_PULSETC1
	;; NOT DIDE, N8, ZETA2, RCSMC, SMZ80
	;LD	C,(IY+CFG_DOR)
	;LD	A,(FST_DOR)
	;SET	0,A
	;OUT	(C),A
	;RES	0,A
	;OUT	(C),A
	;JR	FC_PULSETC2
;FC_PULSETC1:	; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO, EPFDC, MBC, DUO
	;LD	C,(IY+CFG_TC)
	;IN	A,(C)
	;JR	FC_PULSETC2
;FC_PULSETC2:
	RET
;
; SET FST_DOR FOR MOTOR CONTROL ON
;
FC_MOTORON:
	LD	HL,FDCBM
	LD	A,(HL)
	AND	_DIO
	JR	NZ,FC_MOTORON1
	LD	A,(HL)
	AND	_ZETA | _DIO3
	JR	NZ,FC_MOTORON2
	LD	A,(HL)
	AND	_PCAT
	JR	NZ,FC_MOTORON3
	LD	A,(HL)
	AND	_RCSMC
	JR	NZ,FC_MOTORON4
	RET
FC_MOTORON1:	; DIO
	LD	HL,FST_DOR		; POINT TO FDC_DOR
	RES	1,(HL)			; SET MOTOR ON
	JR	FC_MOTORON5
FC_MOTORON2:	; ZETA, DIO3
	LD	HL,FST_DOR		; POINT TO FDC_DOR
	SET	1,(HL)
	JR	FC_MOTORON5
FC_MOTORON3:	; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO, EPFDC, MBC, DUO
	LD	HL,FST_DOR		; POINT TO FDC_DOR
	LD	A,(HL)			; START WITH CURRENT DOR
	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_MOTORON3A:
	RLA				; SHIFT LEFT
	DJNZ	FC_MOTORON3A		; DS TIMES
	OR	C			; COMBINE WITH SAVED
	LD	(HL),A			; COMMIT THE NEW VALUE TO FST_DOR
	JR	FC_MOTORON5
FC_MOTORON4:	; RCSMC
	LD	A,(FCD_DS)		; GET CURRENT DS
	LD	C,00000010B		; ASSUME MOTORA (BIT 1)
	OR	A			; TEST FOR DS 0
	JR	Z,FC_MOTORON4A		; IF SO, CONTINUE W/ MOTORA
	LD	C,00000100B		; OTHERWISE, MOTORB (BIT 2)
FC_MOTORON4A:
	LD	A,(FST_DOR)		; GET CURRENT DOR VALUE
	OR	C			; APPLY NEW MOTOR BIT
	LD	(FST_DOR),A		; COMMIT NEW VALUE
	JR	FC_MOTORON5
FC_MOTORON5:
	CALL	FC_SETDOR		; OUTPUT TO CONTROLLER
	CALL	LDELAY			; WAIT 1/2 SEC ON MOTOR START FOR SPIN-UP
	LD	A,(FDCBM)
	AND	_PCAT
	RET	Z
	LD	A,(FCD_DCR)
	LD	C,(IY+CFG_DCR)
	OUT	(C),A
	RET
;
; SET FST_DOR FOR MOTOR CONTROL OFF
;
FC_MOTOROFF:
	LD	HL,FDCBM
	LD	A,(HL)
	AND	_DIO
	JR	NZ,FC_MOTOROFF1
	LD	A,(HL)
	AND	_ZETA | _DIO3
	JR	NZ,FC_MOTOROFF2
	LD	A,(HL)
	AND	_PCAT
	JR	NZ,FC_MOTOROFF3
	LD	A,(HL)
	AND	_RCSMC
	JR	NZ,FC_MOTOROFF4
	RET
FC_MOTOROFF1:	; DIO
	LD	HL,FST_DOR		; POINT TO FDC_DOR
	SET	1,(HL)			; SET MOTOR OFF
	JR	FC_MOTOROFF5
FC_MOTOROFF2:	; ZETA, DIO3
	LD	HL,FST_DOR		; POINT TO FDC_DOR
	RES	1,(HL)
	JR	FC_MOTOROFF5
FC_MOTOROFF3:	; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO, EPFDC, MBC, DUO
	LD	HL,FST_DOR		; POINT TO FDC_DOR
	LD	A,DORC_INIT
	LD	(HL),A
	JR	FC_MOTOROFF5
FC_MOTOROFF4:	; RCSMC
	LD	HL,FST_DOR		; POINT TO FDC_DOR
	RES	1,(HL)			; CLEAR MOTORA
	RES	2,(HL)			; CLEAR MOTORB
	JR	FC_MOTOROFF5

FC_MOTOROFF5:
	CALL	FC_SETDOR		; OUTPUT TO CONTROLLER
	RET
;
;===============================================================================
; FDC OPERATIONS
;===============================================================================
;
FOP:
;
; INITIALIZATION
;
	LD	A,0
	LD	(FRB_LEN),A
	
	LD	A,FRC_OK
	LD	(FST_RC),A

	LD	B,0		; B IS LOOP COUNTER
	LD	C,(IY+CFG_MSR)	; SET C TO MSR PORT
FOP_CLR1:
	CALL	DELAY		; FDC MAY TAKE UP TO 12us TO UPDATE MSR
	IN	A,(C)		; GET STATUS
	LD	(FST_MSR),A	; SAVE IT FOR POTENTIAL LATER DIAGNOSIS
	AND	0C0H		; ISOLATE HIGH NIBBLE, RQM/DIO/EXM/CB
	CP	0C0H		; LOOKING FOR RQM=1, DIO=1, BYTES PENDING
	JP	NZ,FOP_CMD1	; NO BYTES PENDING, GO TO NEXT PHASE
	INC	C		; SWITCH TO DATA PORT
	IN	A,(C)		; GET THE PENDING BYTE AND DISCARD
	DEC	C		; SWITCH BACK TO MSR PORT
	DJNZ	FOP_CLR1	; KEEP CHECKING TILL COUNTER EXHAUSTER
	JP	FOP_TOFDCRDY	; OTHERWISE, TIMEOUT
;
; SEND COMMAND
;
FOP_CMD1:
	LD	HL,FCP_BUF
	LD	A,(FCP_BUFLEN)
	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
	; TYPICAL MSR TRANSITIONS: <ENTRY> 80 <AFTER FIRST BYTE> 10 90 <AFTER SECOND/SUBSEQUENT BYTES> 90
	LD	C,(IY+CFG_MSR)	; SET C TO MSR PORT
	IN	A,(C)		; READ MAIN STATUS REGISTER
	LD	(FST_MSR),A	; SAVE IT FOR POTENTIAL LATER DIAGNOSIS
	AND	0C0H		; ISOLATE RQM/DIO
	CP	080H		; LOOKING FOR RQM=1, DIO=0 (FDC READY FOR A BYTE)
	JP	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
	LD	C,(IY+CFG_DATA)	; SET C TO DATA PORT
	OUT	(C),A		; PUSH IT TO FDC
	INC	HL		; INCREMENT POINTER FOR NEXT TIME
	DEC	D		; DECREMENT NUM BYTES LEFT TO SEND
	JP	NZ,FOP_CMD2	; DO NEXT BYTE

;
; EXECUTION PHASE
;
FOP_X1:
	LD	HL,(FXP_XR)	; LOAD THE EXECUTION ROUTINE ADDRESS
	CALL	JPHL		; CALL INDIRECTLY VIA HL
	
	LD	A,(FST_RC)	; CURRENT RESULT CODE
	;CALL	PC_SPACE
	;CALL	PRTHEXBYTE
	CP	FRC_OK		; ERROR?
	RET	NZ		; IF SO, ALL DONE
	
;	JP	FOP_RES		; CONTINUE WITH GETRESULTS
	
;
; RESULTS PHASE
;
FOP_RES:
	LD	B,0			; B = BYTES RECEIVED
	LD	HL,FRB_LEN		; POINT TO BUFFER LENGTH
	LD	(HL),B			; UPDATE NUMBER OF BYTES RECEIVED
	LD	HL,FRB			; POINT TO RECEIVE BUFFER

FOP_RES0:
	LD	DE,0			; DE IS LOOP COUNTER
	LD	C,(IY+CFG_MSR)		; SET C TO MSR PORT

FOP_RES1:
	CALL	DELAY			; FDC MAY TAKE UP TO 12us TO UPDATE MSR
	IN	A,(C)			; READ MAIN STATUS REGISTER
	;CALL	PC_SPACE
	;CALL	PRTHEXBYTE
	LD	(FST_MSR),A		; SAVE IT FOR POTENTIAL LATER DIAGNOSIS
	AND	$F0			; REMOVE SEEK BITS
	CP	$D0			; LOOKING FOR RQM=1, DIO=1, BUSY=1 (FDC BYTE PENDING)
	JR	Z,FOP_RES2		; GOOD, GO TO RECEIVE BYTE
	CP	$80			; DONE?
	JR	Z,FOP_EVAL		; IF SO, GO TO EVAL
	DEC	DE
	LD	A,D
	OR	E
	JP	NZ,FOP_RES1
	JP	FOP_TOGETRES		; OTHERWISE TIMEOUT ERROR

FOP_RES2:	; PROCESS NEXT PENDING BYTE
	LD	A,FRB_SIZ		; GET BUF SIZE
	CP	B			; REACHED MAX?
	JP	Z,FOP_BUFMAX		; HANDLE BUF MAX/EXIT
	INC	C			; SWITCH TO DATA PORT
	IN	A,(C)			; GET THE BYTE
	DEC	C			; SWITCH TO MSR PORT
	LD	(HL),A			; SAVE VALUE
	INC	HL			; INCREMENT BUF POS
	INC	B			; INCREMENT BYTES RECEIVED
	PUSH	HL			; SAVE HL
	LD	HL,FRB_LEN		; POINT TO BUFFER LENGTH
	LD	(HL),B			; UPDATE NUMBER OF BYTES RECEIVED
	POP	HL			; RESTORE HL
	JR	FOP_RES0		; CONTINUE READ LOOP

;
; EXIT POINTS
;
FOP_NOTIMPL:
	LD	A,FRC_NOTIMPL
	JP	FOP_ERR

FOP_CMDERR:
	LD	A,FRC_CMDERR
	JP	FOP_ERR

FOP_ERROR:
	LD	A,FRC_ERROR
	JP	FOP_ERR

FOP_ABORT:
	LD	A,FRC_ABORT
	JP	FOP_ERR

FOP_BUFMAX:
	LD	A,FRC_BUFMAX
	JP	FOP_ERR

FOP_TOFDCRDY:
	LD	A,FRC_TOFDCRDY
	JP	FOP_ERR

FOP_TOSNDCMD:
	LD	A,FRC_TOSNDCMD
	JP	FOP_ERR

FOP_TOGETRES:
	LD	A,FRC_TOGETRES
	JP	FOP_ERR

FOP_TOEXEC:
	LD	A,FRC_TOEXEC
	JP	FOP_ERR

FOP_ERR:
	LD	(FST_RC),A
	
FOP_EVAL:
	LD	A,(FCP_CMD)
	; DRVSTAT IS WEIRD, HAS ONLY ST3, NOTHING TO EVAL
	CP	CMD_DRVSTAT
	JP	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	CMD_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	A,FRC_ABTERM
	JP	FOP_SETFST

FOP_INVCMD:	
	LD	A,FRC_INVCMD
	JP	FOP_SETFST

FOP_DSKCHG:
	LD	A,FRC_DSKCHG
	JP	FOP_SETFST
	
FOP_EVALST1:
	LD	A,(FRB_ST1)
	BIT	7,A
	JP	NZ,FOP_ENDCYL
	BIT	5,A
	JP	NZ,FOP_DATAERR
	BIT	4,A
	JP	NZ,FOP_OVERRUN
	BIT	2,A
	JP	NZ,FOP_NODATA
	BIT	1,A
	JP	NZ,FOP_NOTWRIT
	BIT	0,A
	JP	NZ,FOP_MISADR
	JP	FOP_EXIT

FOP_ENDCYL:
	; V5.3, USE EOT=R TO R/W ONLY ONE SECTOR
	;LD	A,FRC_ENDCYL
	;JP	FOP_SETFST
	JP	FOP_EXIT
	
FOP_DATAERR:
	LD	A,FRC_DATAERR
	JP	FOP_SETFST
	
FOP_OVERRUN:
	LD	A,FRC_OVERRUN
	JP	FOP_SETFST
	
FOP_NODATA:
	LD	A,FRC_NODATA
	JP	FOP_SETFST
	
FOP_NOTWRIT:
	LD	A,FRC_NOTWRIT
	JP	FOP_SETFST
	
FOP_MISADR:
	LD	A,FRC_MISADR
	JP	FOP_SETFST
	
FOP_SETFST:
	LD	(FST_RC),A

FOP_EXIT:
	RET

;
; EXECUTION ROUTINES
;
FXR_READID:
	JP	FXRX

FXR_READ:
	LD	HL,BUFFER		; POINT TO SECTOR BUFFER START
	LD	DE,(FCD_SECSZ)

	LD	A,(DCD_MD)		; FIX: SHOULD NOT BE USING DCD HERE
	CP	MD_POLL
	JP	Z,FXRR
	CP	MD_INT
	JP	Z,IFXRR
	CP	MD_INTFAST
	JP	Z,FFXRR
	CP	MD_INTWAIT
	JP	Z,WFXRR
	CP	MD_DRQWAIT
	JP	Z,WFXRR
	JP	FXR_NOTIMPL
	
FXR_WRITE:
	LD	HL,BUFFER		; POINT TO SECTOR BUFFER START
	LD	DE,(FCD_SECSZ)
	
	LD	A,(DCD_MD)		; FIX: SHOULD NOT BE USING DCD HERE
	CP	MD_POLL
	JP	Z,FXRW
	CP	MD_INT
	JP	Z,IFXRW
	CP	MD_INTFAST
	JP	Z,FFXRW
	CP	MD_INTWAIT
	JP	Z,WFXRW
	CP	MD_DRQWAIT
	JP	Z,WFXRW
	JP	FXR_NOTIMPL

FXR_FMTTRK:
	LD	HL,FXP_BUF		; POINT TO BUFFER START
	LD	D,0
	LD	A,(FXP_BUFLEN)		; GET BYTE COUNT TO WRITE
	LD	E,A

	LD	A,(DCD_MD)		; FIX: SHOULD NOT BE USING DCD HERE
	CP	MD_POLL
	JP	Z,FXRW
	CP	MD_INT
	JP	Z,IFXRW
	CP	MD_INTFAST
	JP	Z,IFXRW			; CAN'T USE FFXRW BECAUSE IT IS NOT 512 BYTES
	CP	MD_INTWAIT
	JP	Z,WFXRW
	CP	MD_DRQWAIT			
	JP	Z,WFXRW
	JP	FXR_NOTIMPL

FXR_NOTIMPL:	
FXR_READDEL:
FXR_WRITEDEL:
FXR_READTRK:
FXR_SCANEQ:
FXR_SCANLOEQ:
FXR_SCANHIEQ:
	LD	A,FRC_NOTIMPL
	LD	(FST_RC),A
	; FALL THROUGH TO RET BELOW
FXR_NOP:
FXR_RECAL:
FXR_SENSEINT:
FXR_SPECIFY:
FXR_DRVSTAT:
FXR_SEEK:
FXR_VERSION:
	RET
;
; NULL EXECUTION, NO DATA TO READ/WRITE (USED BY READID)
;
FXRX:
	LD	DE,1000H		; DE IS LOOP COUNTER, 4096 ITERATIONS OF 25ms
	LD	C,(IY+CFG_MSR)		; SET C TO MSR PORT
FXRX1:
	CALL	DELAY
	IN	A,(C)			; GET MSR
	;AND	0E0H			; ISOLATE RQM/DIO/EXM
	CP	$D0			; WE WANT RQM=1,DIO=1,EXM=0 (READY TO READ A BYTE W/ EXEC INACTIVE)
	JP	Z,FXR_END		; GOT IT, EXIT CLEAN
	DEC	DE			; DECREMENT COUNTER (16 BIT)
	LD	A,D			; CHECK FOR ZERO
	OR	E			; "
	JR	NZ,FXRX1		; NOT ZERO YET, KEEP CHECKING
	JP	FXR_TO			; OTHERWISE, TIMEOUT ERROR
	RET

;
; READ DATA
;
FXRR:	LD	A,E			; LOW BYTE OF COUNT TO ACCUM
	OR	A			; TEST FOR ZERO
	JR	Z,FXRR1			; IF ZERO, DO NOT ADJUST ITERATIONS
	INC	D			; OTHERWISE, ITERATIONS IS ONE MORE THAN HIGH BYTE
FXRR1:	LD	B,E			; LOW BYTE OF COUNT TO B
	LD	A,D			; HIGH BYTE OF COUNT
	LD	(FXP_ITER),A		; ... IS ITERATION COUNT
	LD	A,(CPUSPD)		; CPU SPEED SCALER
	LD	(FXP_TO),A		; SAVE IT AS OUTER LOOP TIMEOUT
	LD	IX,FXP_TO		; (IX) IS FXP_TO, (IX+1) IS FCP_ITER
	LD	C,(IY+CFG_DATA)		; SET C TO DATA PORT
	LD	DE,0
	DI				; DISABLE INTERRUPTS TO AVOID OVERRUN
FXRR2:	DEC	C			; [04] SET C TO MSR PORT
FXRR3:	LD	DE,0			; [10] TIMEOUT COUNTER
FXRR4:	IN	A,(C)			; [12] GET MSR
	CP	$F0			; [07] BYTE READY?
	JR	Z,FXRR5			; [12/7] GET IT
	;LD	(FST_MSR),A		; [13] *DEBUG* SAVE MSR FOR LATER
	DEC	E			; [04] DEC LSB OF INNER TIMEOUT COUNTER
	JR	NZ,FXRR4		; [12/7] LOOP IF LSB IS NOT EXHAUSTED
	CP	$D0			; [07] IF RQM=1, DIO=1, EXM=0, CB=1, EXECUTION ABORTED
	JP	Z,FXR_ABORT		; [10] IF NOT SET, EXECUTION ABORTED
	DEC	D			; [04] DEC MSB OF INNER TIMEOUT COUNTER
	JR	NZ,FXRR4		; [12/7] LOOP IF MSB IS NOT EXHAUSTED
	DEC	(IX)			; [23] DECREMENT OUTER LOOP COUNTER
	JR	NZ,FXRR3		; [12/7] LOOP IF NOT EXHAUSTED
	JP	FXR_TO			; [10] OTHERWISE, HANDLE TIMEOUT
FXRR5:	INC	C			; [04] POINT TO DATA PORT
	INI				; [16] (HL) := (C), HL++, B--
	JR	NZ,FXRR2		; [12/7] LOOP
	DEC	(IX+1)			; [23] DECREMENT ITERATION COUNT
	JR	NZ,FXRR2		; [12] IF MORE ITERATIONS, LOOP
	JP	FXR_END			; [10] ELSE DONE

;
; INT READ DATA - SAFE VERSION
; HANDLES FDC ERRORS, BUT NO TIMEOUT
;
	; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
	.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
		.FILL	(($ & 0FF00H) + 40H) - $
		; .ORG	(($ & 0FF00H) + 40H)
	.ENDIF
;
IFXRR:	
	DI
	LD	C,(IY+CFG_MSR)	; SET C TO MSR PORT
IFXRR2:
	EI
IFXRRX	.EQU	$ - IFXRR
	HALT
	IN	A,(C)
	BIT	5,A
	JP	Z,FXR_ABORT
	INC	C		; SWITCH C TO DATA PORT
	INI
	DEC	C		; SWITCH C BACK TO MSR PORT
	DEC	DE
	LD	A,E
	OR	D
	JP	NZ,IFXRR2
	JP	FXR_END
;
; INT READ DATA - FAST VERSION
; FIXED SECTOR SIZE OF 512 BYTES
; HANGS ON FDC ERRORS, NO TIMEOUT
;
	; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
	.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
		.FILL	(($ & 0FF00H) + 40H) - $
	  ; .ORG	(($ & 0FF00H) + 40H)
	.ENDIF
;
FFXRR:
	DI
	LD	C,(IY+CFG_DATA)	; SET C TO DATA PORT FOR INI
FFXRR2	EI	
	HALT
FFXRRX1	.EQU	$ - FFXRR
	INI
	JP	NZ,FFXRR2
FFXRR3	EI	
	HALT	
FFXRRX2	.EQU	$ - FFXRR
	INI
	JP	NZ,FFXRR3
	JP	FXR_END
;
; WAIT READ DATA
; HANGS ON FDC ERRORS, NO TIMEOUT
;
WFXRR:
	DI
	LD	C,(IY+CFG_DMA)
WFXRR2:	
	INI			; GET PENDING BYTE
	DEC	DE		; DECREMENT BYTE COUNT
	LD	A,D	
	OR	E	
	JP	NZ,WFXRR2	; IF NOT ZERO, REPEAT LOOP
	JP	FXR_END		; CLEAN EXIT

;
; WRITE DATA
;
FXRW:	LD	A,E			; LOW BYTE OF COUNT TO ACCUM
	OR	A			; TEST FOR ZERO
	JR	Z,FXRW1			; IF ZERO, DO NOT ADJUST ITERATIONS
	INC	D			; OTHERWISE, ITERATIONS IS ONE MORE THAN HIGH BYTE
FXRW1:	LD	B,E			; LOW BYTE OF COUNT TO B
	LD	A,D			; HIGH BYTE OF COUNT
	LD	(FXP_ITER),A		; ... IS ITERATION COUNT
	LD	A,(CPUSPD)		; CPU SPEED SCALER
	LD	(FXP_TO),A		; SAVE IT AS OUTER LOOP TIMEOUT
	LD	IX,FXP_TO		; (IX) IS FXP_TO, (IX+1) IS FCP_ITER
	LD	C,(IY+CFG_DATA)		; SET C TO DATA PORT
	LD	DE,0
	DI				; DISABLE INTERRUPTS TO AVOID OVERRUN
FXRW2:	DEC	C			; [04] SET C TO MSR PORT
FXRW3:	LD	DE,0			; [10] TIMEOUT COUNTER
FXRW4:	IN	A,(C)			; [12] GET MSR
	CP	$B0			; [07] BYTE READY?
	JR	Z,FXRW5			; [12/7] GET IT
	;LD	(FST_MSR),A		; [13] *DEBUG* SAVE MSR FOR LATER
	DEC	E			; [04] DEC LSB OF INNER TIMEOUT COUNTER
	JR	NZ,FXRW4		; [12/7] LOOP IF LSB IS NOT EXHAUSTED
	CP	$D0			; [07] IF RQM=1, DIO=1, EXM=0, CB=1, EXECUTION ABORTED
	JP	Z,FXR_ABORT		; [10] IF NOT SET, EXECUTION ABORTED
	DEC	D			; [04] DEC MSB OF INNER TIMEOUT COUNTER
	JR	NZ,FXRW4		; [12/7] LOOP IF MSB IS NOT EXHAUSTED
	DEC	(IX)			; [23] DECREMENT OUTER LOOP COUNTER
	JR	NZ,FXRW3		; [12/7] LOOP IF NOT EXHAUSTED
	JP	FXR_TO			; [10] OTHERWISE, HANDLE TIMEOUT
FXRW5:	INC	C			; [04] POINT TO DATA PORT
	OUTI				; [16] (C) := (HL), HL++, B--
	JR	NZ,FXRW2		; [12/7] LOOP
	DEC	(IX+1)			; [23] DECREMENT ITERATION COUNT
	JR	NZ,FXRW2		; [12] IF MORE ITERATIONS, GO DO IT
	JP	FXR_END			; [10] ELSE DONE



;
; INT WRITE DATA - SAFE VERSION
; HANDLES FDC ERRORS, BUT NO TIMEOUT
;	
	; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
	.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
		.FILL	(($ & 0FF00H) + 40H) - $
	  ; .ORG	(($ & 0FF00H) + 40H)
	.ENDIF
;
IFXRW:	
	DI
	LD	C,(IY+CFG_MSR)	; SET C TO MSR PORT
IFXRW2:	EI	
	HALT
	IN	A,(C)
	BIT	5,A
	JP	Z,FXR_ABORT
	INC	C		; SWITCH TO DATA PORT
	OUTI
	DEC	C		; SWITCH BACK TO MSR PORT
	DEC	DE
	LD	A,E
	OR	D
	JP	NZ,IFXRW2
	JP	FXR_END
;
; INT WRITE DATA - FAST VERSION
; FIXED SECTOR SIZE OF 512 BYTES
; HANGS ON FDC ERRORS, NO TIMEOUT
;
	; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
	.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
		.FILL	(($ & 0FF00H) + 40H) - $
	  ; .ORG	(($ & 0FF00H) + 40H)
	.ENDIF
;
FFXRW:	
	DI
	LD	C,(IY+CFG_DATA)	; SET C TO DATA PORT
FFXRW2	EI	
	HALT	
	OUTI
	JP	NZ,FFXRW2
FFXRW3	EI	
	HALT	
	OUTI
	JP	NZ,FFXRW3
	JP	FXR_END
;
; WAIT WRITE DATA
; HANGS ON FDC ERRORS, NO TIMEOUT
;
WFXRW:
	DI
	LD	C,(IY+CFG_DMA)
WFXRW2:
	OUTI				; WRITE IT			16ts
	DEC	DE			; DECREMENT BYTE COUNT		 6ts
	LD	A,D			;				 4ts
	OR	E			;				 4ts
	JP	NZ,WFXRW2		; IF NOT ZERO, REPEAT LOOP	10ts = 40
	JP	FXR_END			; CLEAN EXIT

;
; COMMON COMPLETION CODE FOR ALL EXECUTION ROUTINES
;

FXR_TO:
	; SAVE CURRENT MSR VALUE
	;LD	C,(IY+CFG_MSR)		; SET C TO MSR PORT
	;IN	A,(C)
	LD	(FST_MSR),A
	; SET ERROR AND GET OUT W/O PULSING TC
	LD	A,FRC_TOEXEC
	LD	(FST_RC),A
	JR	FXR_END2

FXR_ABORT:
	; SAVE CURRENT MSR VALUE
	;LD	C,(IY+CFG_MSR)		; SET C TO MSR PORT
	;IN	A,(C)
	LD	(FST_MSR),A
	; LET RESULTS PHASE HANDLE ERROR, DO NOT PULSE TC, COMMAND ALREADY TERMINATED
	;LD	A,FRC_ABORT
	;LD	(FST_RC),A
	JR	FXR_END2

FXR_END:
	; SAVE CURRENT MSR VALUE
	;LD	C,(IY+CFG_MSR)		; SET C TO MSR PORT
	;IN	A,(C)
	LD	(FST_MSR),A
	; DO NOT PULSE TC AT END OF FORMAT OR READID, THOSE COMMANDS SELF-TERMINATE
	;LD	A,(FCP_CMD)
	;CP	CMD_FMTTRK
	;JR	Z,FXR_END2
	;CP	CMD_READID
	;JR	Z,FXR_END2
	
	CALL	FC_PULSETC
	
FXR_END2:
#IF 0
	LD	(FXP_A),A
	LD	(FXP_BC),BC
	LD	(FXP_DE),DE
	LD	(FXP_HL),HL

	CALL	FXR_DUMP
#ENDIF
	
	EI				; I/O FINISHED, INTS BACK ON
	RET

;
;===============================================================================
; 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
	JP	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
	JP	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
	JP	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
	JP	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
	JP	NZ,FCPR2
	
	; 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	CMD_SENSEINT
	JP	NZ,FCPR2
	
	LD	A,(FST_RC)
	CP	FRC_INVCMD
	JP	Z,FCPR_EXIT
	CP	FRC_DSKCHG
	JP	Z,FCPR_EXIT
	CP	FRC_ABTERM
	JP	Z,FCPR_EXIT
	JP	FCPR_EXIT
	
FCPR2:
	CALL	NEWLINE

	CALL	FC_PRTCMD
	CALL	PC_COLON
	
	LD	A,(FCP_BUFLEN)
	LD	DE,FCP_BUF
	CALL	PRTHEXBUF

	LD	DE,STR_ARROW
	CALL	WRITESTR

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

	LD	A,(FDCBM)
	AND	_ZETA | _DIO3
	JR	Z,FCPR3
	LD	DE,STR_DSKCHG
	CALL	WRITESTR
	LD	C,(IY+CFG_DIR)
	IN	A,(C)
	AND	01H
	CALL	PRTHEXBYTE

FCPR3:
	LD	A,(FST_RC)
	CALL	FC_PRTFST

FCPR_EXIT:
	RET
;
; DUMP EXECUTION INFO
;
FXR_DUMP:
	CALL	NEWLINE
	LD	DE,STR_OP
	CALL	WRITESTR
	CALL	PC_COLON
	LD	DE,STR_MSR
	CALL	WRITESTR
	LD	A,(FST_MSR)
	CALL	PRTHEXBYTE
	
	LD	DE,STR_A
	CALL	WRITESTR
	LD	A,(FXP_A)
	CALL	PRTHEXBYTE
	
	LD	DE,STR_BC
	CALL	WRITESTR
	LD	BC,(FXP_BC)
	LD	A,B
	CALL	PRTHEXBYTE
	LD	A,C
	CALL	PRTHEXBYTE

	LD	DE,STR_DE
	CALL	WRITESTR
	LD	BC,(FXP_DE)
	LD	A,B
	CALL	PRTHEXBYTE
	LD	A,C
	CALL	PRTHEXBYTE

	LD	DE,STR_HL
	CALL	WRITESTR
	LD	BC,(FXP_HL)
	LD	A,B
	CALL	PRTHEXBYTE
	LD	A,C
	CALL	PRTHEXBYTE

	LD	DE,STR_ITER
	CALL	WRITESTR
	LD	A,(FXP_ITER)
	CALL	PRTHEXBYTE

	LD	DE,STR_TO
	CALL	WRITESTR
	LD	A,(FXP_TO)
	CALL	PRTHEXBYTE

	RET
;
; 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
;
DORA_BR250	.EQU	10100110B	; 250KBPS
DORA_BR500	.EQU	11010010B	; 500KBPS
;
DORA_INIT	.EQU	DORA_BR250
;
;	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
;
DORB_BR250	.EQU	11100100B	; 250KBPS
DORB_BR500	.EQU	10100000B	; 500KBPS
;
DORB_INIT	.EQU	DORB_BR250
;
; *** DIDE/N8/ZETA2/RCWDC/SMZ80/DYNO/EPFDC/MBC/DUO ***
;
DORC_INIT	.EQU	00001100B	; SOFT RESET INACTIVE, DMA ENABLED
;
DORC_BR250	.EQU	DORC_INIT
DORC_BR500	.EQU	DORC_INIT
;
; *** RCSMC ***
;
DORD_BR250	.EQU	10100000B	; 250KBPS
DORD_BR500	.EQU	11100000B	; 500KBPS
;
DORD_INIT	.EQU	DORB_BR250
;
; DCR (ONLY APPLIES TO DIDE, N8, AND ZETA2)
;
DCR_BR250	.EQU	01H		; 250KBPS
DCR_BR500	.EQU	00H		; 500KBPS
;
;===============================================================================
; GENERAL UTILITY ROUTINES
;===============================================================================
;
; INITIALIZE BUFFER WITH FILLER BYTE
;   HL = ADDRESS OF BUFFER
;   DE = SIZE OF BUFFER
;   B = FILLER BYTE VALUE
;
FILL_BUFFER:
	LD	A,B
	LD	(HL),A
	INC	HL
	DEC	DE
	LD	A,D
	OR	E
	JP	NZ,FILL_BUFFER
	RET
;
; INITIALIZE BUFFER WITH PATTERN
;   HL = ADDRESS OF BUFFER
;   DE = SIZE OF BUFFER
;   B = STARTING BYTE VALUE
;
PAT_BUFFER:
	LD	A,B
	LD	(HL),A
	INC	HL
	DEC	DE
	INC	B
	LD	A,D
	OR	E
	JP	NZ,PAT_BUFFER
	RET
;
; PRINT A BLOCK OF MEMORY NICELY FORMATTED
;
DUMP_BUFFER:
	CALL	NEWLINE			;	
BLKRD:
	CALL	PHL			; PRINT START LOCATION
	LD	C,16			; SET FOR 16 LOCS
	PUSH	HL			; SAVE STARTING HL
NXTONE:
	LD	A,(HL)			; GET BYTE
	CALL	PRTHEXBYTE		; PRINT IT
	CALL	PC_SPACE		;
UPDH:	
	INC	HL			; POINT NEXT
	DEC	C			; DEC. LOC COUNT
	JR	NZ,NXTONE		; IF LINE NOT DONE
					; NOW PRINT 'DECODED' DATA TO RIGHT OF DUMP
PCRLF:
	CALL	PC_SPACE		; SPACE IT
	LD	C,16			; SET FOR 16 CHARS
	POP	HL			; GET BACK START
PCRLF0:
	LD	A,(HL)			; GET BYTE
	AND	060H			; SEE IF A 'DOT'
	LD	A,(HL)			; O.K. TO GET
	JR	NZ,PDOT			;
DOT:
	LD	A,2EH			; LOAD A DOT	
PDOT:
	CALL	COUT			; PRINT IT
	INC	HL			; 
	LD	A,D			;
	CP	H			;
	JR	NZ,UPDH1		;
	LD	A,E			;
	CP	L			;
	JP	Z,DUMP_END		;
;
;IF BLOCK NOT DUMPED, DO NEXT CHARACTER OR LINE
UPDH1:
	DEC	C			; DEC. CHAR COUNT
	JR	NZ,PCRLF0		; DO NEXT
CONTD:
	CALL	NEWLINE			;
	JP	BLKRD			;

DUMP_END:	
	RET				;
;
; UTILITY PROCS TO PRINT SINGLE CHARACTERS
;
PC_SPACE:
	PUSH	AF			; Store AF
	LD	A,' '			; LOAD A "SPACE"
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_HYPHEN:
	PUSH	AF			; Store AF
	LD	A,'-'			; LOAD A COLON
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_COLON:
	PUSH	AF			; Store AF
	LD	A,':'			; LOAD A COLON
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_EQUAL:
	PUSH	AF			; Store AF
	LD	A,'='			; LOAD A COLON
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_CR:
	PUSH	AF			; Store AF
	LD	A,CHR_CR		; LOAD A <CR>
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_LF:
	PUSH	AF			; Store AF
	LD	A,CHR_LF		; LOAD A <LF>
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_LBKT:
	PUSH	AF			; Store AF
	LD	A,'['			; LOAD A COLON
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_RBKT:
	PUSH	AF			; Store AF
	LD	A,']'			; LOAD A COLON
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_LPAREN:
	PUSH	AF			; Store AF
	LD	A,'('			; LOAD A COLON
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE


PC_RPAREN:
	PUSH	AF			; Store AF
	LD	A,')'			; LOAD A COLON
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

PC_BS:
	PUSH	AF			; Store AF
	LD	A,CHR_BS		; LOAD A <BS>
	CALL	COUT			; SCREEN IT
	POP	AF			; RESTORE AF
	RET				; DONE

NEWLINE_USED	.DB	1
NEWLINE:
	CALL	PC_CR
	CALL	PC_LF
	LD	A,1
	PUSH	AF
	LD	(NEWLINE_USED),A
	POP	AF			; RESTORE AF
	RET				; DONE
	
COPYSTR:
	LD	A,(HL)
	CP	'$'
	RET	Z
	LDI
	JR	COPYSTR
	
;	
;__COUT_________________________________________________________________________________________________________________________ 
;
;	PRINT CONTENTS OF A 
;________________________________________________________________________________________________________________________________
;
COUT:
	PUSH	BC			;
	PUSH	AF			;
	PUSH	HL			;
	PUSH	DE			;

	LD	C,2			; BDOS FUNC: CONSOLE WRITE CHAR
	LD	E,A			; CHARACTER TO E
	CALL	$0005			; CALL BDOS
	
	POP	DE			;
	POP	HL			;
	POP	AF			;
	POP	BC			;
	RET				; DONE
;
;__PHL_________________________________________________________________________________________________________________________ 
;
;	PRINT THE HL REG ON THE SERIAL PORT
;________________________________________________________________________________________________________________________________
;
PHL:
	LD	A,H			; GET HI BYTE
	CALL	PRTHEXBYTE		; DO HEX OUT ROUTINE
	LD	A,L			; GET LOW BYTE
	CALL	PRTHEXBYTE		; HEX IT
	CALL	PC_SPACE			; 
	RET				; DONE	
;
; GET A LINE BUFFER WITH 2 HEX CHARS, HL=ADDRESS OF LINE BUFFER
; EXIT WITH C = NUMBER OF CHARS, C=0 MEANS NOTHING ENTERED
;
GETLNHEX:
	LD	C,0		; C = CHAR COUNT
GLH_LOOP:
	CALL	GETKEYUC
	OR	A
	JP	Z,GLH_LOOP
	CP	CHR_CR
	JP	Z,GLH_CHK
	CP	CHR_BS
	JP	Z,GLH_BS
	CP	'0'
	JP	M,GLH_LOOP
	CP	'9' + 1
	JP	M,GLH_APPEND
	CP	'A'
	JP	M,GLH_LOOP
	CP	'F' + 1
	JP	M,GLH_APPEND
	JP	GLH_LOOP
GLH_BS:
	LD	A,C
	OR	A
	JP	Z,GLH_LOOP
	CALL	PC_BS
	CALL	PC_SPACE
	CALL	PC_BS
	DEC	C
	DEC	HL
	JP	GLH_LOOP
GLH_APPEND:
	LD	B,A
	LD	A,C
	CP	2
	JP	P,GLH_LOOP
	LD	A,B
	CALL	COUT
	LD	(HL),A
	INC	C
	INC	HL
	JP	GLH_LOOP
GLH_CHK:
	LD	A,C
	CP	2
	JP	Z,GLH_EXIT
	CP	0
	JP	Z,GLH_EXIT
	JP	GLH_LOOP
GLH_EXIT:
	RET
;
;__HEXIN__________________________________________________________________________________________________________________________ 
;
;	GET ONE BYTE OF HEX DATA FROM BUFFER IN HL, RETURN IN A
;________________________________________________________________________________________________________________________________
;
HEXIN:
	PUSH	BC			; SAVE BC REGS.
	CALL	NIBL			; DO A NIBBLE
	RLC	A			; MOVE FIRST BYTE UPPER NIBBLE	
	RLC	A			; 
	RLC	A			; 
	RLC	A			; 
	LD	B,A			; SAVE ROTATED BYTE
	CALL	NIBL			; DO NEXT NIBBLE
	ADD	A,B			; COMBINE NIBBLES IN ACC.
	POP	BC			; RESTORE BC
	RET				; DONE	
NIBL:
	LD	A,(HL)			; GET K.B. DATA
	INC	HL			; INC KB POINTER
	CP	40H			; TEST FOR ALPHA
	JR	NC,ALPH			;
	AND	0FH			; GET THE BITS
	RET				;
ALPH:
	AND	0FH			; GET THE BITS
	ADD	A,09H			; MAKE IT HEX A-F
	RET				;
;
; COPY A $ TERMINATED STRING FROM ADDRESS IN DE TO ADDRESS IN HL
;  DE = ADDRESS OF SOURCE STRING
;  LH = ADDRESS OF TARGET LOCATION
;
STRCPY:
	LD	A,(DE)
	CP	'$'
	JP	Z,STRCPYX
	LD	(HL),A
	INC	HL
	INC	DE
	JP	STRCPY

STRCPYX:
	RET
;
; GET A HEX BYTE VALUE FROM THE USER
; WILL UPDATE STORED VALUE, EMPTY RESPONSE LEAVES VALUE ALONE
;  DE = ADDRESS OF PROMPT STRING
;  HL = ADDRESS OF VALUE
;  BC = HI/BO ALLOWABLE RANGE OF VALID VALUES
;
GHB_PROMPTP	.DW	0
GHB_RANGE	.DW	0
GHB_VALUEP	.DW	0
GHB_CLEAR	.DB	"  \b\b$"

GETHEXBYTE:
	LD	BC,000FFH
GETHEXBYTERNG:
	LD	(GHB_PROMPTP),DE
	LD	(GHB_RANGE),BC
	LD	(GHB_VALUEP),HL
	CALL	NEWLINE
GHB_LOOP:
	CALL	PC_CR
	LD	DE,STR_ENTER
	CALL	WRITESTR
	CALL	PC_SPACE
	LD	DE,(GHB_PROMPTP)
	CALL	WRITESTR
	CALL	PC_SPACE
	LD	BC,(GHB_RANGE)
	CALL	PC_LBKT
	LD	A,B
	CALL	PRTHEXBYTE
	CALL	PC_HYPHEN
	LD	A,C
	CALL	PRTHEXBYTE
	CALL	PC_RBKT
	CALL	PC_SPACE
	CALL	PC_LPAREN
	LD	HL,(GHB_VALUEP)
	LD	A,(HL)
	CALL	PRTHEXBYTE
	CALL	PC_RPAREN
	CALL	PC_COLON
	CALL	PC_SPACE
	LD	DE,GHB_CLEAR
	CALL	WRITESTR
	LD	HL,KEYBUF
	CALL	GETLNHEX
	LD	A,C
	CP	0			; RETAIN CURRENT VALUE
	JP	Z,GHB_EXIT
	CP	2			; INPUT LOOKS OK, UPDATE IT
	JP	Z,GHB_CHK
	JP	GHB_LOOP		; ANYTHING ELSE, BAD INPUT, DO OVER
GHB_CHK:
	LD	HL,KEYBUF
	CALL	HEXIN
	LD	BC,(GHB_RANGE)
	CP	B
	JP	C,GHB_LOOP
	CP	C
	JP	Z,GHB_OK
	JP	NC,GHB_LOOP
GHB_OK:
	LD	HL,(GHB_VALUEP)
	LD	(HL),A
GHB_EXIT:
	RET

;
; PRINT THE HEX BYTE VALUE IN A
;
HEXSTRBUF	.TEXT	"XX$"
;
PRTHEXBYTE:
	PUSH	AF
	PUSH	DE
	LD	DE,HEXSTRBUF
	CALL	HEXSTRBYTE
	LD	A,'$'
	LD	(DE),A
	LD	DE,HEXSTRBUF
	CALL	WRITESTR
	POP	DE
	POP	AF
	RET
;
; PRINT THE HEX WORD VALUE IN BC
;
PRTHEXWORD:
	PUSH	AF
	LD	A,B
	CALL	PRTHEXBYTE
	LD	A,C
	CALL	PRTHEXBYTE
	POP	AF
	RET
;
; CONVERT VALUE IN A TO A 2 CHARACTER HEX STRING AT DE
;
HEXCHR	.TEXT	"0123456789ABCDEF"
;
HEXSTRBYTE:
	PUSH	BC
	PUSH	HL
	PUSH	AF
	LD	BC,0
	RRA
	RRA
	RRA
	RRA
	AND	0FH
	LD	C,A
	LD	HL,HEXCHR
	ADD	HL,BC
	LD	A,(HL)
	LD	(DE),A
	INC	DE
	POP	AF
	PUSH	AF
	LD	BC,0
	AND	0FH
	LD	C,A
	LD	HL,HEXCHR
	ADD	HL,BC
	LD	A,(HL)
	LD	(DE),A
	INC	DE
	POP	AF
	POP	HL
	POP	BC
	RET
;
; CONVERT VALUE IN BC TO A 4 CHARACTER HEX STRING AT DE
;
HEXSTRWORD:
	LD	A,B
	CALL	HEXSTRBYTE
	LD	A,C
	CALL	HEXSTRBYTE
	RET

;
; PRINT A BYTE BUFFER IN HEX POINTED TO BY DE
; REGISTER A HAS SIZE OF BUFFER
;
PRTHEXBUF:
	CP	0		; EMPTY BUFFER?
	JP	Z,PRTHEXBUF2
	
	LD	B,A
PRTHEXBUF1:
	CALL	PC_SPACE
	LD	A,(DE)
	CALL	PRTHEXBYTE
	INC	DE
	DJNZ	PRTHEXBUF1
	JP	PRTHEXBUFX
	
PRTHEXBUF2:
	CALL	PC_SPACE
	LD	DE,STR_EMPTY
	CALL	WRITESTR

PRTHEXBUFX:
	RET
;
;	JP TO ADDRESS IN HL IN HEX POINTED TO BY DE
;	  MOSTLY USEFUL TO PERFORM AN INDIRECT CALL LIKE:
;	  LD	HL,xxxx
;	  CALL	JPHL
;
JPHL	JP	(HL)
;
; GENERATE A RANDOM BYTE
;
; RETURNS PSEUDO RANDOM 8 BIT NUMBER IN A. ONLY AFFECTS A.
; (SEED) IS THE BYTE FROM WHICH THE NUMBER IS GENERATED AND MUST BE	
; INITIALIZED TO A NON ZERO VALUE OR THIS FUNCTION WILL ALWAYS RETURN
; ZERO.
;
RB_SEED		.DB	1		; RNDBYTE SEED (MUST NOT BE ZERO)
;
RNDBYTE:
	LD	A,(RB_SEED)	; GET SEED
	AND	0B8H		; MASK NON-FEEDBACK BITS
	SCF			; SET CARRY
	JP	PO,RB_NC	; SKIP CLEAR IF ODD
	CCF			; COMPLEMENT CARRY (CLEAR IT)
RB_NC	LD	A,(RB_SEED)	; GET SEED BACK
	RLA			; ROTATE CARRY INTO BYTE
	LD	(RB_SEED),A	; SAVE BACK FOR NEXT
	RET			; DONE
;
; ADD HL,A
;
;   A REGISTER IS DESTROYED!
;
ADDHLA:
	ADD	A,L
	LD	L,A
	RET	NC
	INC	H
	RET
;
; OUTPUT A '$' TERMINATED STRING
;
WRITESTR:
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
	LD	C,09H
	CALL	0005H
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	RET
;
; READ A KEY, RETURN VALUE IN A
;
GETKEY:
	PUSH	BC
	PUSH	DE
	PUSH	HL
	LD	C,06H
	LD	E,0FFH
	CALL	0005H
	POP	HL
	POP	DE
	POP	BC
	
	CP	03
	JP	Z,EXIT
	
	RET

GETKEYUC:
	CALL	GETKEY
	CP	'a'
	JP	M,GETKEYUC_EXIT
	CP	'z' + 1
	JP	M,GETKEYUC_FIX
	JP	GETKEYUC_EXIT
GETKEYUC_FIX:
	AND	11011111B
GETKEYUC_EXIT:
	RET
;
; DELAY 16US (CPU SPEED COMPENSATED) INCUDING CALL/RET INVOCATION
; REGISTER A AND FLAGS DESTROYED
; NO COMPENSATION FOR Z180 MEMORY WAIT STATES
; THERE IS AN OVERHEAD OF 3TS PER INVOCATION
;   IMPACT OF OVERHEAD DIMINISHES AS CPU SPEED INCREASES
;
; CPU SCALER (CPUSCL) = (CPUHMZ - 2) FOR 16US + 3TS DELAY
;   NOTE: CPUSCL MUST BE >= 1!
;
; EXAMPLE: 8MHZ CPU (DELAY GOAL IS 16US)
;   LOOP = ((6 * 16) - 5) = 91TS
;   TOTAL COST = (91 + 40) = 131TS
;   ACTUAL DELAY = (131 / 8) = 16.375US
;
	; --- TOTAL COST = (LOOP COST + 40) TS -----------------+
DELAY:				; 17TS (FROM INVOKING CALL)	|
	LD	A,(CPUSCL)	; 13TS				|
;								|
DELAY1:				;				|
	; --- LOOP = ((CPUSCL * 16) - 5) TS ------------+	|
	DEC	A		; 4TS			|	|
#IFDEF CPU_Z180			;			|	|
	OR	A		; +4TS FOR Z180		|	|
#ENDIF				;			|	|
	JR	NZ,DELAY1	; 12TS (NZ) / 7TS (Z)	|	|
	; ----------------------------------------------+	|
;								|
	RET			; 10TS (RETURN)			|
	;-------------------------------------------------------+
;
; DELAY 16US * DE (CPU SPEED COMPENSATED)
; REGISTER DE, A, AND FLAGS DESTROYED
; NO COMPENSATION FOR Z180 MEMORY WAIT STATES
; THERE IS A 27TS OVERHEAD FOR CALL/RET PER INVOCATION
;   IMPACT OF OVERHEAD DIMINISHES AS DE AND/OR CPU SPEED INCREASES
;
; CPU SCALER (CPUSCL) = (CPUHMZ - 2) FOR 16US OUTER LOOP COST
;   NOTE: CPUSCL MUST BE > 0!
;
; EXAMPLE: 8MHZ CPU, DE=6250 (DELAY GOAL IS .1 SEC OR 100,000US)
;   INNER LOOP = ((16 * 6) - 5) = 91TS
;   OUTER LOOP = ((91 + 37) * 6250) = 800,000TS
;   ACTUAL DELAY = ((800,000 + 27) / 8) = 100,003US
;
	; --- TOTAL COST = (OUTER LOOP + 27) TS ------------------------+
VDELAY:				; 17TS (FROM INVOKING CALL)		|
;									|
	; --- OUTER LOOP = ((INNER LOOP + 37) * DE) TS ---------+	|
	LD	A,(CPUSCL)	; 13TS				|	|
;								|	|
VDELAY1:			;				|	|
	; --- INNER LOOP = ((CPUSCL * 16) - 5) TS ------+	|	|
#IFDEF CPU_Z180			;			|	|	|
	OR	A		; +4TS FOR Z180		|	|	|
#ENDIF				;			|	|	|
	DEC	A		; 4TS			|	|	|
	JR	NZ,VDELAY1	; 12TS (NZ) / 7TS (Z)	|	|	|
	; ----------------------------------------------+	|	|
;								|	|
	DEC	DE		; 6TS				|	|
#IFDEF CPU_Z180			;				|	|
	OR	A		; +4TS FOR Z180			|	|
#ENDIF				;				|	|
	LD	A,D		; 4TS				|	|
	OR	E		; 4TS				|	|
	JP	NZ,VDELAY	; 10TS				|	|
	;-------------------------------------------------------+	|
;									|
	RET			; 10TS (FINAL RETURN)			|
	;---------------------------------------------------------------+
;
; DELAY ABOUT 0.5 SECONDS
; 500000US / 16US = 31250
;
LDELAY:
	PUSH	AF
	PUSH	DE
	LD	DE,31250
	CALL	VDELAY
	POP	DE
	POP	AF
	RET

;
; HANDLE USER INPUT FOR A MENU BASED ON TABLE OF MENU DATA.  DISPATCH MENU FUNCTIONS.
; ON INPUT, HL=ADDRESS OF MENU TABLE, B=COUNT OF MENU ENTRIES
;
		.module	MenuInfo
MenuInfo	; TRANSIENT STORAGE FOR CURRENT MENU
_DrawProc	.dw	0	; ADDRESS OF MENU DRAW ROUTINE
_TableAdr	.dw	0	; ADDRESS OF MENU TABLE DATA
_EntryInfo	.dw	0	; ENTRY COUNT / ENTRY SIZE
_Size		.equ	$ - MenuInfo
;
RUNMENU:
	push	hl		; save address of menu info data

_Run:
	pop	hl		; restore/resave menu init address
	push	hl

	ld	de,MenuInfo
	ld	bc,_Size
	ldir

	ld	hl,(_DrawProc)
	call	JPHL		; call menu draw routine

_GetKey:
	call	GETKEYUC	; GET USER KEYPRESS
	ld	hl,(_TableAdr)
	ld	bc,(_EntryInfo)	; B=COUNT, C=ENTRY SIZE
	ld	d,0		; put entry size in de
	ld	e,c		; "

	; a=key pressed, hl=menu table address, b=entry count, de=entry size
_Loop:
	ld	c,(hl)
	cp	c
	jp	z,_Match	; found code

	add	hl,de		; point to next entry
	djnz	_Loop		; check next entry till count is zero
	jp	_GetKey		; keep trying

_Match:
	inc	hl		; load string
	ld	e,(hl)
	inc	hl
	ld	d,(hl)
	call	WRITESTR	; display it
	
	inc	hl		; load code address
	ld	e,(hl)
	inc	hl
	ld	d,(hl)
	
	ld	a,d		; check for zero
	or	e
	jp	z,_Exit		; zero means exit
	
	ex	de,hl
	call	JPHL		; indirect call to menu function
	jp	_Run

_Exit:
	pop	hl
	ret
;
; CONTROL CHARACTERS
;
CHR_CR		.EQU	0DH
CHR_LF		.EQU	0AH
CHR_BS		.EQU	08H
CHR_ESC		.EQU	1BH
;
STR_DRIVERESET	.TEXT	"RESET DRIVE...$"
STR_EXECUTE	.TEXT	"EXECUTION$"
STR_OP		.TEXT	"OPERATION$"
STR_FORMAT	.TEXT	"FORMAT$"
STR_SENDCMD	.TEXT	"SEND COMMAND$"
STR_GETRESULTS	.TEXT	"GET RESULTS$"
STR_SEEKWAIT	.TEXT	"SEEK WAIT$"
STR_DOR		.TEXT	"SET DOR$"
STR_PROGRESS	.TEXT	"PROGRESS$"
STR_MISMATCH	.TEXT	"DATA MISMATCH AT $"
STR_RESET	.TEXT	"FDCRESET...$"
STR_NOTIMPL	.TEXT	"*** NOT IMPLEMENTED ***$"
STR_NORESP	.TEXT	"*** DRIVE NOT RESPONDING ***$"
STR_EOD		.TEXT	"<EOD>$"
STR_EMPTY	.TEXT	"<EMPTY>$"
STR_TIMEOUT	.TEXT	"<TIMEOUT>$"
STR_ARROW	.TEXT	" -->$"
STR_ENTER	.TEXT	"ENTER$"
STR_ON		.TEXT	"ON $"
STR_OFF		.TEXT	"OFF$"
STR_DRV720	.TEXT	"720KB $"
STR_DRV144	.TEXT	"1.44MB$"
STR_MODEPOLL	.TEXT	"POLLING   $"
STR_MODEINT	.TEXT	"INTERRUPT $"
STR_MODEDMA	.TEXT	"DMA	   $"
STR_CC0		.TEXT	" CC0=$"
STR_CC1		.TEXT	" CC1=$"
STR_CC2		.TEXT	" CC2=$"
STR_N		.TEXT	" N=$"
STR_SC		.TEXT	" SC=$"
STR_GPL		.TEXT	" GPL=$"
STR_D		.TEXT	" D=$"
STR_ST0		.TEXT	" ST0=$"
STR_ST1		.TEXT	" ST1=$"
STR_ST2		.TEXT	" ST2=$"
STR_CYL		.TEXT	" CYL=$"
STR_UNIT	.TEXT	" UNIT=$"
STR_HEAD	.TEXT	" HD=$"
STR_REC		.TEXT	" SEC=$"
STR_NUM		.TEXT	" NUM=$"
STR_DENS	.TEXT	" DENS=$"
STR_EOTSEC	.TEXT	" EOTSEC=$"
STR_GAP		.TEXT	" GAP=$"
STR_DTL		.TEXT	" DTL=$"
STR_SN		.TEXT	" SN=$"
STR_NCN		.TEXT	" NCN=$"
STR_PCN		.TEXT	" PCN=$"
STR_MSR		.TEXT	" MSR=$"
STR_A		.TEXT	" A=$"
STR_BC		.TEXT	" BC=$"
STR_DE		.TEXT	" DE=$"
STR_HL		.TEXT	" HL=$"
STR_TO		.TEXT	" TO=$"
STR_ITER	.TEXT	" ITER=$"
STR_DSKCHG	.TEXT	" DC=$"
;
KEYBUFLEN	.EQU	80
KEYBUF		.FILL	KEYBUFLEN,' '
;
STACKSAV	.DW	0
STACKSIZ	.EQU	40H		; WE ARE A STACK PIG
		.FILL	STACKSIZ,0
STACK		.EQU	$
;
BUFFER		.EQU	4000H
VFYBUF		.EQU	5000H
BUFSIZ		.EQU	0200H
		.END
