TITLE FLEXER vers. 3.04 by Martin Eberhard
	SUBTTL	Banner
;======================================================
;FLEXER: Floppy Drive Exercizer
;for CDOS 2.X and a Cromemco 4FDc, 16FDC or 64FDC
;
;For measuring and adjusting floppy drives, including:
; 8" and 5.25" (minidisk) drives
; Stepper-motor and voice-coil actuators
; 40-track minidisks and 80-track "HD" minidisks
; Single- and double-sided disks
; Single- and double-density drives
;
;=================
;Revision History
;1.01 - 1.06  27FEB2013 - 13MAR2013 M. Eberhard
; Created, basic functionality
;
;2.00 - 2.04 14MAR2013 - 13JUN2013  M.Eberhard
; Add read/write and buffer commands, etc.
; Add buffer and sector commands. Add ID command. Fix
;
;3.00 17 February 2016  M. Eberhard [NEVER RELEASED]
; Streamlined user interface, mainly by creating
; immediate commands
;
;3.01 31 January 2017 M. Eberhard
; Cleanup, debug
;
;3.02 21 February 2019 M. Eberhard
; Add "A" and "V" automatic commands to SK and SE
;
;3.03 14 June 2020  M. Eberhard
; Fix bug in GET01
;
;3.04  2 August 2020 M. Eberhard
; Wait for -Seek Complete to go high before waiting
; for it to go low during both seeks and restores.
; (for PerSci 299B drives). Change coding style so
; that registers a,b,c,d,e,h,l,sp,fa are lowercase.
; Tidy up comments a tad. Default drive tipe=8.
;
;3.05  30 January 2021  M. Eberhard
; Report "voicecoil" instead of the step rate when a
; voicecoil drive is selected. Fix timeout value in
; voicecoil seek.

VERSION	equ	0305h		;hex-encoded-decimal

;=====================================================
;Written to assemble (under CDOS) with Cromemco's ASMB
;assembler or cross-assemble (under CP/M) with SLR
;Systems's Z80ASM assembler (Release 1.32)
;
;Notes:
;
;This program uses software timing loops for seek and
;rotational time measurements, and assumes:
;1) a 4 MHz Z-80 with 0-wait state memory
;2) Accessing the FDC DSTAT register inserts 1 wait
;   state (which is imposed by the Z-80 itself)
;3) Accessing the FDC ASTAT register inserts 4 wait
;   states, including the one imposed by the Z-80.
;
;Do not change 'jp' instructions within timing loops
;to 'jr' instructions, as this will effect the timing.
;
;Because of the timing loops, interrupts are blocked
;while this program runs. There is an assembly option
;(INTIO) to block interrupt just during timing loops.
;If you set INTIO to be TRUE, then you must set the
;16FDC's interrrupt mask register correctly: mask the
;disk interrupts, and either mask or allow UART
;interrupts as your software requires. Note that this
;code has not been tested in an interrupt environment.
;
;This program requires about 16K of RAM, starting at
;100h. The last 10.5K of this RAM is a buffer that is
;only used during the Write Pattern function.
;
;The 4/16FDC unloads the disk head by de-selecting the
;drive. This means that any disk operation that
;requires the drive to be selected must also do so
;with its head loaded. For example, the Eject function
;applies to the selected disk. If the disk heads are
;unloaded and you use the EJ command, the head will
;load on the selected disk before the disk ejects.
;
;The WP (write pattern) command writes an entire track
;of the specified data pattern, without any sector
;formatting. A track like this is useful for tuning
;the drive's read channel. A disk that has been used
;for this purpose must be reformatted (using e.g. CDOS
;INIT) before it can be used by CDOS.
	EJECT
	SUBTTL System Equates
FALSE	equ	0
TRUE	equ	0FFH

;Program Equates

INTIO	equ	FALSE	;TRUE allows ints except during
			;timing loops. Make sure 16FDC
			;ints are set up right.
			;(TRUE is still untested.)

MAXTK8	equ	76	;default 8" disk max track #
MAXTKM	equ	39	;default minidisk max track #
SEEKTO	equ	5	;approx. seek timeout in seconds
DRTRYS	equ	3	;default read retry max

MAXBPS	equ	1024	;max bytes per sector
TBSIZE	equ	10500	;track buffer size, bytes

;ASCII Characters

CTRLC	equ	03H
BS	equ	08H
LF	equ	0AH
CR	equ	0DH
ESC	equ	1Bh
QUOTE	equ	27H	;single quote
DEL	equ	7FH

;CDOS 2.X Addresses

BASE	equ	0		;CDOS base addr
WBOOT	equ	BASE		;warm-boot to CDOS
CDOS	equ	BASE+5		;CDOS System Call
FCB	equ	BASE+5CH	;CDOS file ctrl blk
COMBUF	equ	BASE+80H	;disk & cmd line buff
USAREA	equ	BASE+100H	;User program

CBSIZE	equ	USAREA-COMBUF	;cmd line buff size

;CDOS 2.X System Call Functions

CCONIN	equ	80h	;Read Console to a, no echo
CCONIE	equ	1	;Read console to a, echo
CCONOT	equ	2	;Write E to Console
CPRINT	equ	9	;Print $-terminated line at de
CRDCON	equ	10	;Input buffered line at de
CCONST	equ	11	;Console input status

;16FDC Ports & Bits

ASTAT	equ	04H	;Aux stat input
ACTRL	equ	04H	;Auxiliary cmd output
DSTAT	equ	30H	;Status input
DCMMD	equ	30H	;cmd output
DTRCK	equ	31H	;Track I/O
DSCTR	equ	32H	;Sector I/O
DDATA	equ	33H	;Data I/O
DFLAG	equ	34H	;Flags input
DCTRL	equ	34H	;Control output
	EJECT
;WD1793 Commands (For DCMMD port)

RSTRCM	equ	00000000B ;(I)Restore
SEEKCM	equ	00010000B ;(I)Seek
STEPCM	equ	00100000B ;(I)Step
STPICM	equ	01000000B ;(I)Step In
STPOCM	equ	01100000B ;(I)Step Out
RSECCM	equ	10000000B ;(II)Read Record
WSECCM	equ	10100000B ;(II)Write Record
RADRCM	equ	11000000B ;(III)Read addr
RTRKCM	equ	11100000B ;(III)Read track
WTRKCM	equ	11110000B ;(III)Write track
FINTCM	equ	11010000B ;(IV)Force int

;WD1793 Command Options

RS03MS	equ	00000000B ;(I) 3 mS step rate
RS06MS	equ	00000001B ;(I) 6 mS step rate
RS10MS	equ	00000010B ;(I) 10 mS step rate
RS15MS	equ	00000011B ;(I) 15 mS step rate
VERTRK	equ	00000100B ;(I) Verify on dest track
HDLOAD	equ	00001000B ;(I) Head Load request
UPDTTR	equ	00010000B ;(I) Update track reg
A0DDM	equ	00000001B ;(III)Write deleted-data mark
F1SCF	equ	00000010B ;(II) Side compare flag
F2SSF	equ	00001000B ;(II) Side select flag
MULTCM	equ	00010000B ;(II) R/W Multiple
ED15MS	equ	00000100B ;(II & III) Settling Delay

;Composite WD1793 commands (Step rates added later)
;Note: Disk will always appear ready if not HDLOAD

DSTEPI	equ	STPICM+UPDTTR+HDLOAD	;step in
DSTEPO	equ	STPOCM+UPDTTR+HDLOAD	;step out
DSEEK	equ	SEEKCM+HDLOAD	;seek
WTRACK	equ	WTRKCM+ED15MS	;write track
DEFSTP	equ	RS10MS		;default step rate
GETID	equ	RADRCM+ED15MS	;Get track ID
RDSECT	equ	RSECCM+ED15MS	;read sector
WRSECT	equ	WSECCM+ED15MS	;write sector

;DCTRL output bits

DCDS0	equ	00000001B ;Drive Sel 0
DCDS1	equ	00000010B ;Drive Sel 1
DCDS2	equ	00000100B ;Drive Sel 2
DCDS3	equ	00001000B ;Drive Sel 3
DCSMSK	equ	11110000B ;Drive Sel mask
DCMAXI	equ	00010000B ;0=Mini, 1=Maxi
DCMOTO	equ	00100000B ;Motor On
DCMMSK	equ	11011111B ;Motor mask
DCDDEN	equ	01000000B ;Enable Double Density
DCAUTO	equ	10000000B ;Enable auto-wait

;ACTRL output bits (Active-low)

ACSID0	 equ	00000010B ;-Side select
ACCTRL	 equ	00000100B ;-Control Out
ACRSTR	 equ	00001000B ;-Restore
ACFSEK	 equ	00010000B ;-Fast Seek
ACDSO	 equ	00100000B ;-Drive Select Override
ACEJCT	 equ	01000000B ;-EJECT

;Composite ACTRL values

EJECT	equ	not[ACSID0+ACEJCT+ACDSO]
FASEEK	equ	not[ACSID0+ACFSEK+ACDSO]
DRESTR	equ	not[ACSID0+ACRSTR+ACDSO]
ACSSM	equ	not[ACSID0+ACDSO]

;WD1793 DSTAT input bits, after a command

SBUSY	equ	00000001B ;(All)Busy
SINDEX	equ	00000010B ;(I)Index
SDATRQ	equ	00000010B ;(II & III)Data Request
STRAK0	equ	00000100B ;(I)Track 0
SLOSTD	equ	00000100B ;(II & III)Lost Data
SCRCER	equ	00001000B ;(ALL)CRC Error
SRNFER	equ	00010000B ;(II & III)Record Not Found
SHEADL	equ	00100000B ;(I)Head Loaded
SWFALT	equ	00100000B ;(Writes)Write Fault
SRTYPE	equ	00100000B ;(Read rec) Record Type
SWPROT	equ	01000000B ;(All)Disk Write Protect
SNORDY	equ	10000000B ;(All)Not Ready

;DFLAG input bits

DFSEOJ  equ	00000001B ;Disk cmd complete
DFAWTO	equ	00000010B ;Auto-wait timeout
DFSMTO	equ	00000100B ;Motor timeout
DFSMON	equ	00001000B ;WD1793 requesting motors on
DSFhLD	equ	00100000B ;WD1793 requesting head load
DSFDRQ	equ	10000000B ;WD1793 is requesting data

;ASTAT bits

DASSIP	equ	01000000B ;Seek in progress
DASDRQ	equ	10000000B ;Data Request
	EJECT
	SUBTTL Main Loop
	ORG	USAREA

	if not INTIO
	di			;block ints always
	endif

;Clear RW, verify buffers using CDOS's stack

	ld	bc,MAXBPS*2	;clear 2 buffers
	ld	e,0
	call	FILBUF

;Create local stack, print banner

	ld	de,BANNER	;Sign-on msg

;Fix stack, Print message at de, and go to MAIN

PRMAIN:	ld	sp,STACK	;SP=local stack
	call	CRPRNT

;========================================
;Main loop: re-initialize several things,
;look for commands from user & dispatch
;========================================
MAIN:	call	REDCTL		;restore DCTL state
				;may spin the motor up
	if INTIO
	ei			;allow I/O ints
	endif

	ld	a,(DTYPE)	;cancel forced-step
	and	not FRCSTP
	ld	(DTYPE),a

	ld	a,(SIDENO)	;get side #
	or	ACSSM		;(re)set ACTRL
	out	ACTRL,a

MAINLP:	ld	de,PROMPT
	call	CRPRNT

	call	GETLIN		;get 0-terminated line
	jr	Z,MAINLP	;null line: try again

;Search for matching 2-chr command in CMDTAB

	ld	de,CMDTAB-1	;point to command table

	ld	c,(hl)		;1st command chr in C
	inc	hl		;2nd cmd chr at (hl)

NXTCOM:	inc	de		;skip over address

	ld	a,(de)
	or	a		;test for table end
	jr	Z,CMDERR	;cmd not in table

	xor	c		;test first character
	ld	b,a		;temp save result
	inc	de
	ld	a,(de)		;2nd table character
	xor	(hl)		;test 2nd character
	or	b		;both characters match?

	inc	de		;point to addr low byte
	ld	a,(de)		;Low byte of address
	ld	b,a
	inc	de		;point to addr hi byte

	jr	NZ,NXTCOM	;NO match: keep looking

	ld	a,(de)		;high byte of address
	ld	d,a
	ld	e,b		;low byte of address

	inc	hl		;point past command

;de=command execution address
;hl points past command on command line

	call	SKIPSP		;Skip any trailing spaces	

;Command found. Dispatch to (de)
;with Z set if end of cmd line

	ld	bc,MAIN		;create return addr
	push	bc
	push	de		;execution addr
	ret			;go execute	

;-----------------------------------
;Bogus command return (resets stack)
;-----------------------------------
CMDERR:	ld	de,HUHST	;'Huh?'
	jr	PRMAIN		;print msg & go to main
	EJECT
	SUBTTL Command Execution Routines
;******===Command Routine==========
;* ID *  Display Track ID
;******
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==================================
FTKID:	jr	NZ,CMDERR	;no params allowed

	call	TRAKID		;get track id

;Display track ID info

	push	bc		;save SPT & retries
	push	hl		;save BPS

	ld	de,TKIDST	;heading
	call	CRPRNT

	ld	de,TRAKST	;print track
	call	CRPRNT
	ld	a,(COMBUF)	;track # from ID
	call	PDEC8

	ld	de,SIDEST	;print side
	call	CRPRNT
	ld	a,(COMBUF+1)	;side # from ID
	call	PDEC8

	call	PDENS		;density

	call	PCRLF
	pop	hl		;recover bytes/sector
	call	PDEC16		;print hl as dec	
	ld	de,BPSST	;Bytes per sector
	call	PRINTF

	call	PCRLF
	pop	bc		;SPT, retries fm TRAKID
	ld	a,b		;a=SPT
	ld	de,UNSUST	;unsupported
	or	a		;0 means unsupported
	jp	Z,PRMAIN

	call	PDEC8		;print a=SPT
	ld	de,SPTST	;sector/track
	jp	PRINTF			
	EJECT
;******===Command Routine==========
;* SR *  Read Sector
;******
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==================================
FREAD:	call	RWSTUP

	ld	de,SECBUF	;target address
	call	READS		;read the sector

	ld	de,RERRST	;read error?
	jp	NZ,RWFLT

	push	bc		;c=final status
	ld	de,SRRCST	;read required
	ld	c,b		;tries remaining
	call	PRTRYS		;print retries, if any

	call	PCRLF
	call	PDEC16
	ld	de,BYRDST	;bytes trans, rec typ
	call	PRINTF

	pop	bc		;c=final status
	ld	a,c
	ld	de,PREST	;present
	and	SRTYPE		;deleted data mark?
	jr	NZ,FREAD1
	ld	de,ADELST	;absent
FREAD1:	call	PRINTF

;Fall into CHKSID

;===Subroutine=========================
;Compare SIDENO to side found by TRAKID
;On Entry:
;  SIDENO=current side
;  (COMBUF+1)=TRAKID side #
;On Exit:
;  Z set if match
;  message printed, Z clear if not
;Trashes a,b,de
;======================================
CHKSID:	ld	a,(SIDENO)	;correct side?
	rrca			;make it 0/1
	xor	1		;0 for side 0
	ld	b,a
	ld	a,(COMBUF+1)	;ID side #
	cp	b		;mismatch?
	ret	Z		;N:done

	ld	de,SDMMST
	call	CRPRNT		;Y:report
	inc	b		;clear Z
	ret
	EJECT
;******===Command Routine==========
;* SW *  Write Sector
;******  with read-back verify
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==================================
FWRITE:	call	RWSTUP		;setup for write

	call	CHKSID		;side mismatch?
	ret	NZ		;Y:done

	push	hl		;save bytes/sect

	ld	de,ASKSST
	call	ASKYN		;ready?

	call	PREPD		;autowait, etc.
	ld	de,SECBUF	;source address

	ld	a,(WDDM)	;deleted data mark?
	or	WRSECT		;WD1793 command

	if INTIO
	di			;no ints during write
	endif

	out	DCMMD,a

;Write hl bytes to disk from memory at de

WRITDL:	in	a,DFLAG		;(11)autowait
	ld	a,(de)		;(7)
	out	DDATA,a		;(12)disk data
	inc	de		;(6)
	dec	hl		;(6)
	ld	a,h		;(4)
	or	l		;(4)
	jp	NZ,WRITDL	;(10)

	call	EOJ		;get final status

	ld	a,c		;Final status
	ld	de,PROTST
	and	SWPROT
	jr	NZ,FLTEXT

	ld	a,c
	ld	de,WFLTST	;write fault
	and	SRNFER+SCRCER+SLOSTD+SWFALT
	jr	NZ,RWFLT

;Report number of bytes written

	call	PCRLF
	pop	hl		;Bytes/Sect
	push	hl		;save for verify
	call	PDEC16		;number of bytes
	ld	de,BYWRST	;bytes written
	call	PRINTF

;Read back into verify buffer, to verify write

	pop	hl		;Bytes/Sect
	jr	WVERFY		;reuse verifycmd 
	EJECT
;******===Command Routine==========
;* SV *  Verify Sector
;******
;On Entry at FVERFY:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;Entry at WVERFY:
;  hl=byte count
;==================================
FVERFY:	call	RWSTUP		;setup for read
				;return hl=byte count

	call	CHKSID		;side mismatch?
	ret	NZ		;Y:done

WVERFY:	push	hl		;byte count for compare

	ld	de,VERBUF	;dest addr=verify buf
	call	READS		;read sector we wrote

	ld	de,RVERST	;read err during verify
	jr	NZ,FLTEXT	;...even after retries?

	ld	de,VRRCST	;verify read required
	ld	c,b		;c=tries remaining
	call	PRTRYS		;print retries, if any

;Compare read-back data to written data

	ld	de,VERBUF	;target address
	ld	hl,SECBUF	;source address
	pop	bc		;bc=bytes/sector
	push	bc		;save for report

VLOOP:	ld	a,(de)		;loop through bc bytes
	inc	de
	cpi
	jr	NZ,VFAIL
	jp	NV,VLOOP	;no jr instr. for NV
;--------------
;verify success
;--------------
	call	PCRLF
	pop	hl		;Bytes/Sect
	call	PDEC16		;number of bytes
	ld	de,BYVFYD	;bytes verified
	jp	PRINTF
;-----------
;verify fail
;-----------
VFAIL:	ld	de,VERRST	;verify error
	jr	FLTEXT

;-----------------
;read/Write  error
;de=error string
;-----------------
RWFLT:	and	SLOSTD+SCRCER+SWFALT
	jr	NZ,FLTEXT

	ld	de,RNFST	;record not found
FLTEXT:	jp	PRMAIN	
	
	EJECT
;******===Command Routine==========
;* VD *  Display the Verify Buffer
;******
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==================================
FVDISP:	ld	hl,VERBUF	;verify buffer addr
	jr	BDISP		;same as FBDISP	

;******===Command Routine==========
;* BD *  Buffer Display
;******
;On Entry at FBDISP:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;On Entry at BDISP:
;  hl=buffer address
;==================================
FBDISP:	ld	hl,SECBUF	;read/write buffer addr

BDISP:	jr	NZ,JCMDER	;no params allowed
	ld	de,0		;address count

FDISP1:	call	PADDR		;print address

	push	hl		;save for ASCII print
	push	de

FDISP2:	call	PSPACE

;Print 16 bytes in hex

	ld	a,(hl)		;data
	call	PRHEX
	inc	de		;next
	inc	hl

	ld	a,e
	and	0Fh		;new line every 16
	jr	NZ,FDISP2

;Print them again in ASCII

	call	PSPACE

	pop	de
	pop	hl

FDISP3:	ld	a,(hl)
	cp	' '		;non-printing?
	jr	C,FDISP4
	cp	DEL		;more non-printing
	jr	C,FDISP5
FDISP4:	ld	a,'.'

FDISP5:	call	PRINTA
	inc	de		;next
	inc	hl

	ld	a,e
	and	0Fh		;new line every 16
	jr	NZ,FDISP3


	ld	a,e
	or	a		;pause every 256
	jr	NZ,FDISP1

	ld	a,d
	cp	MAXBPS/256	;max bytes/sec
	ret	NC

	push	de
	ld	de,SCONST	;space to continue
	call	CRPRNT
	pop	de

FDISP6:	call	CHKCON		;Abort from user?
	cp	' '		;space to advance?
	jr	NZ,FDISP6	;n: keep waiting

	jr	FDISP1
	EJECT
;******===Command Routine==========
;* BF *  RW Buffer Fill
;******
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==================================
FFILL:	call	GETHEX		;get fill value in de
	ld	a,b		;overflow?
	cp	3		;max 2 digits
	jr	NC,JCMDER

	ld	a,(hl)		;any more chrs?
	or	a
	jr	NZ,JCMDER	;junk at end

	ld	bc,MAXBPS	;size of RW buffer

;Fall into FILBUF

;===Subroutine=================
;Fill SECBUF
;On Entry:
;  bc=number of bytes to fill
;  e=fill value
;Trashes a,bc,hl
;==============================
FILBUF:	ld	hl,SECBUF

FILLUP:	ld	(hl),e		;loop to fill buffer
	inc	hl
	dec	bc
	ld	a,b
	or	c
	jr	NZ,FILLUP

	ret

;-----------------
;Command error:
;make use of jr's
;-----------------
JCMDER:	jp	CMDERR
	EJECT
;******===Command Routine==========
;* BE *  RW Buffer Edit
;******
;allow user to edit buffer contents
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==================================
FEDIT:	call	GETHEX		;get address in de
	ld	a,b		;overflow?
	cp	5
	jr	NC,JCMDER

	ld	a,(hl)		;any more chrs?
	or	a
	jr	NZ,JCMDER

	ld	a,d
	cp	MAXBPS/256	;past buffer end?
	jr	NC,JCMDER

FEDIT0:	ld	hl,SECBUF
	add	hl,de		;hl points to buff addr

FEDIT1:	call	PADDR		;print address

FEDIT2:	call	PSPACE

	ld	a,(hl)		;data
	call	PRHEX

	ld	a,'.'		;separator
	call	PRINTA

	push	de
	push	hl

	call	GETLIN		;get user input
	jr	NC,FEDIT3	;Just did immediate cmd?

	pop	hl		;did immediate command
	pop	de		;..so reprint address
	jr	FEDIT1

FEDIT3:	call	GETHEX		;convert to hex
				;Z set if no input
	ld	a,e		;get low byte

	pop	hl
	pop	de

	jr	Z,FEDIT4	;no change?

	ld	(hl),a		;update w/ low byte

FEDIT4:	inc	hl
	INC	de

	ld	a,e		;need an address?
	and	07H		;every 8
	jr	NZ,FEDIT2

	ld	a,d		;end of sector?
	cp	MAXBPS/256
	jr	C,FEDIT1

	ld	de,0		;sector addr count
	jr	FEDIT0		;wrap to beginning
	EJECT
;******===Command Routine==========
;* SE *  Display Settings & Status
;******
;On Entry:
;  Z set if end of cmd line already
;==================================
FSET:	jr	NZ,JCMDER	;no parameters allowed	

	ld	de,FSETST
	call	CRPRNT

	call	PUNIT		;unit

	ld	a,(SDCTRL)	;any unit selected?
	and	0Fh
	ret	Z		;N:done

	call	PDTYP		;type, max track, rates

	call	PMOTOR		;motor state
	call	PSIDE		;side
	call	PTRACK		;current track
	call	PHLOAD		;head-load state

;Issue a seek command so that the WD1793 will send
;protect and index to DSTAT. Also select drive, since
;unloading the head is done by deselecting the drive.

	call	NULSEK		;returns DSTAT in C

;The Ready bit will always read high for minidisks, and
;will also always read high once the 1792 stops
;requesting head load. Therefore, the ready state of
;DCTRL is captured during NULSEK. 

	ld	a,(SDCTRL)	;no ready signal
	and	DCMAXI		;...on a minidisk
	ld	de,CRLFST	;no ready status message
	jr	Z,DS1

	ld	de,DRIVST	;Drive
	call	CRPRNT

	ld	de,RDYST	;ready
	ld	a,c
	and	SNORDY
	jr	Z,DS1
	ld	de,NRDYST	;not ready	

DS1:	push	af		;save ready status
	call	PRINTF	

	ld	de,DISKST	;Disk
	call	CRPRNT
	ld	de,UNPRST	;not write protected	
	in	a,DSTAT
	and	SWPROT
	jr	Z,DS2
	ld	de,PROTST	;write protected
DS2:	call	PRINTF	

	ld	de,TRK0ST	;track 0
	call	CRPRNT
	ld	de,DETST	;detected
	in	a,DSTAT
	and	STRAK0
	jr	NZ,DS3
	ld	de,NDETST	;not detected
DS3:	call	PRINTF

	pop	af		;ready status
	call	Z,IMEASR	;measure spindle if ready

	call	PMODE		;operating mode
	call	PVERB		;verbose mode
	jp	PRETRY		;Read retries

;The disk appears ready. Print status that depend on
;having a disk spinning in the drive

	EJECT
;******===Command Routine==============
;* A= * Select Automatic or Manual Mode
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  AUTOSK updated
;======================================
FSETA:	call	GET01		;0 or 1
	ld	(AUTOSK),a

;Fall into PMODE

;===Subroutine=======
;Print Operating Mode
;====================
PMODE:	ld	de,MODEST	;'Mode:'
	call	CRPRNT

	ld	a,(AUTOSK)
	ld	de,aUTOST	;'Automatic'
	or	a		;0 means auto
	jr	NZ,PMOD1
	ld	de,MANST	;'Manual'

PMOD1:	jp	PRINTF
	EJECT
;******===Command Routine==========
;* D= * Set Density
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  SDCTRL drive density bit updated
;==================================
FSETD:	call	GETASC		;get ASCII param

	ld	hl,SDCTRL
	cp	'S'
	jr	Z,SETSD

	cp	'D'
	jp	NZ,CMDERR	;illegal value?

	ld	a,(hl)
	or	DCDDEN		;double density
	jr	SETD1

SETSD:	ld	a,(hl)
	and	not DCDDEN	;single density

SETD1:	ld	(hl),a

;Fall into PDENS

;===Subroutine=========
;Print selected density
;======================
PDENS:	ld	a,(SDCTRL)
	ld	de,SNGLST	;'Single'
	and	DCDDEN		;what density?
	jr	Z,DENS1
	ld	de,dUBLST	;'Double'
DENS1:	call	CRPRNT

	ld	de,DENSST	;'density'
	jp	PRINTF		;print value & ret
	EJECT
;******===Command Routine====
;* M= * Set Max Track number
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  MAXTRK updated
;============================
FSETM:	ld	bc,99		;default & max
	call	GETDEC
	or	a		;0 not allowed
	jp	Z,CMDERR

	ld	(MAXTRK),a

;Fall into PMAXTK

;===Subroutine=========
;Print max track number
;======================
PMAXTK:	ld	de,MAXTST	;'Max Track:'
	call	CRPRNT

	ld	a,(MAXTRK)
	jp	PDEC8
	EJECT
;******===Command Routine====
;* N= * Set max Read Retries
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  RTRIES updated
;============================
FSETN:	ld	bc,99		;default & max
	call	GETDEC

	inc	a		;tries=retries+1
	ld	(RTRIES),a

;Fall into PRETRY

;===Subroutine============
;Print max allowed retries
;=========================
PRETRY:	ld	de,RETRST	;'Retries:'
	call	CRPRNT

	ld	a,(RTRIES)
	dec	a		;retries=tries-1
	jp	PDEC8
	EJECT
;******===Command Routine====
;* R= * Set Step-Rate
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  STPRAT updated
;============================
FSETR:	ld	bc,3		;default & max
	call	GETDEC
	ld	(STPRAT),a

;Fall into PSTPRT

;===Subroutine===========
;Print selected Step Rate
;========================
PSTPRT:	ld	de,STRST	;'Step Rate:'
	call	CRPRNT

	ld	a,(STPRAT)
	ld	hl,STPTAB	;look up actual rate

	ld	e,a
	ld	d,0
	add	hl,de

	ld	b,(hl)		;get step rate

	ld	a,(DTYPE)	;minidisk/HD mini?

	or	a		;voicecoil drive?
	jr	z,PSTR2	;y:	 say so

	cp	DTMD
	ld	a,b		;recover step rate
	jr	C,PSTR1
	rlca			;*2 for minidisks

PSTR1:	call	PDEC8
	ld	de,MSST		;mS
	jr	PSTR3

PSTR2:	ld	de,VCST

PSTR3:	jp	PRINTF		;return from there

	EJECT
;******===Command Routine====
;* S= * Set Disk Side
;******
;On Entry:
;  hl=addr of parameter value
;On exit:
;  SIDENO=00 for drive 1,
;  02 for drive 0 (for ACTRL)
;============================
FSETS:	call	GET01

	rlca			;bit in place for ACTRL
	xor	02		;reverse polarity

	ld	(SIDENO),a

	or	ACSSM		;now set side
	out	ACTRL,a

;Fall into PSIDE

;===Subroutine===========
;Print selected disk side
;Trashes a,bc,de
;========================
PSIDE:	ld	de,SIDEST	;'Side:'
	call	CRPRNT

	ld	a,(SIDENO)
	rrca			;bit in low position
	xor	1		;it was inverted
	jp	PDEC8
	EJECT
;******===Command Routine====
;* T= * Select Drive Type
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  DTYPE updated
;  SDCTRL updated
;  MAXTRK updated
;============================
FSETT:	call	GETASC		;get 1 ASCII param
	ld	de,MAXTK8*256	;e=type, d=max track#
	cp	'V'
	jr	Z,SETT1
	inc	e
	cp	'8'
	jr	Z,SETT1
	ld	de,MAXTKM*256+2
	cp	'5'
	jp	NZ,CMDERR

SETT1:	ld	a,d		;save max track #
	ld	(MAXTRK),a

	ld	hl,SDCTRL	;clear DCTRL maxi bit
	ld	a,(hl)
	and	not DCMAXI
	ld	(hl),a

	ld	a,e
	ld	(DTYPE),a

	cp	DTMD		;8" or voicecoil 8"?
	jr	NC,SETT2	;N: its mini
	
	ld	a,(hl)
	or	DCMAXI		;set SDCTRL maxi bit
	ld	(hl),a

SETT2:

;Fall into PDTYP

;===Subroutine=====================
;Print Drive Type, Max Track, Rates
;==================================
PDTYP:	ld	de,DTYPST	;'Disk type:' 
	call	CRPRNT

	ld	a,(DTYPE)
	or	a
	jr	NZ,PDTYP0

	ld	de,VCST		;'Voicecoil '
	call	PRINTF
	ld	a,1

PDTYP0:	ld	de,MAXIST	;'8"'
	dec	a
	jr	Z,PDTYP1

	ld	de,MINIST	;'Minidisk'

PDTYP1:	call	PRINTF
	call	PMAXTK		;max track
	call	PDENS		;density
	call	PDRATE		;data rate
	jp	PSTPRT		;...and step rate
	EJECT
;******===Command Routine==========
;* U= * Set Unit Number {A-D}
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  SDCTRL drive select bits updated
;  The drive has been restored to 0
;==================================
FSETU:	call	GETASC		;get ASCII param

	sub	'A'		;Adjust to 0
	cp	4		;max drive letter is D
	jp	NC,CMDERR	;illegal value

	ld	b,a		;make drive selectcmd 
	inc	b
	xor	a
	scf

SULOOP:	rla			;shift bit into place
	djnz	SULOOP

	ld	b,a
	ld	hl,SDCTRL
	ld	a,(hl)		;previous DCTRL value
	and	DCSMSK		;unit mask
	or	b		;new unit number
	ld	(hl),a		;new DCTRL value

	call	FRCST		;temp force stepping
	call	RESTOR		;select and restore

;Fall into PUNIT

;===Subroutine======
;Print selected unit
;===================
PUNIT:	ld	de,UNITST	;'Unit:'
	call	CRPRNT

;Fall into PRDRIV

;===Subroutine=================
;Print the current drive letter
;Trashes a,c,de
;==============================
PRDRIV:	ld	a,(SDCTRL)
	and	0Fh		;just get drive bits
	ld	de,NONEST
	jp	Z,PRINTF	;print none if none

	ld	c,'A'-1

PU1:	rrca			;loop to create A-D
	inc	c
	jr	NC,PU1

	ld	a,c		;a='A'-'D'
	jp	PRINTA
	EJECT
;******===Command Routine====
;* V= * Set Verbose Mode
;******
;On Entry:
;  hl=addr of parameter value
;On Exit:
;  VERBOS updated
;============================
FSETV:	call	GET01
	ld	(VERBOS),a

;Fall into PVERB

;===Subroutine=====
;Print Verbose Mode
;==================
PVERB:	ld	de,VERBST	;'Verbose'
	call	CRPRNT

	ld	a,(VERBOS)
	ld	de,OFFST	;'off'
	or	a		;0 means disabled
	jr	Z,PVERB1
	ld	de,ONST		;'on'

PVERB1:	jp	PRINTF
	EJECT
;******===Command Routine==========
;* RE * Restore to track 0
;******
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==================================
FRESTR:	jp	NZ,CMDERR	;No parameters allowed

	call	RESTOR
	jp	OPTRAK		;print track # and ret

;*****==============
;* ? *  Help Command
;*****==============
FHELP:	ld	de,HLPMSG
	jp	PRMAIN		;print msg & go to main
	EJECT
;******===Command Routine==================
;* EJ * Eject (PerSci) disk
;******
;Select the correct drive and side
;Send 1-sec eject pulse per Persci 277 spec
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;==========================================
FEJECT:	jp	NZ,CMDERR	;no parameters allowed

	ld	a,(SDCTRL)	;select the drive
	out	DCTRL,a		;maybe been motor-off

	ld	hl,SIDENO

	ld	a,(hl)		;select the side
	or	a,EJECT		;combine ejectcmd 
	out	ACTRL,a		;eject now

	call	STALL1		;stall for 1 sec

	ld	a,(hl)		;select the side
	or	a,ACSSM
	out	ACTRL,a		;end eject pulse

	ld	de,EJST		;Ejected
	jp	CRPRNT
	EJECT
;******===Command Routine======================
;* WP * Write Pattern <H1> [...<Hn>]
;******
;Write a complete track of the given pattern.
;There are no sectors - this is not a formatted
;track. This function is for tuning the read
;channel, etc.
;On Entry:
;  Z set if end of cmd line already
;  Unit, side, track, density are all set
;  hl=address of 1st chr aftercmd 
;==============================================
FWPAT:	jp	Z,CMDERR	;must be at least 1

;Get pattern values into COMBUF. Pattern values
;between F5 and FE are not allowed, as they will kick
;the WD1793 into one of its modes.

	ld	de,COMBUF	;reuse buffer
	ld	b,0FFH		;count goes 1 extra

GEPLUP: inc	b		;bump pattern count
	push	bc

	push	de
	call	GETHEX		;val in de, cnt in B
	ld	a,b		;too many digits?
	cp	3
	jp	NC,CMDERR	;Y:abort
	ld	a,e		;get low byte
	pop	de

	ld	(de),a		;save byte in list
	inc	de		;bump pointer

	push	de
	ld	de,BADPST	;can't be F5-FE
	inc	a		;must be below F6 now
	cp	0F6H
	jp	NC,PRMAIN
	pop	de

	ld	a,b		;hex digit count
	pop	bc		;recover pattern count

	or	a		;0 digits last time?
	jr	NZ,GEPLUP	;N:keep looking

;fill track buffer with the pattern. This may over-
;fill by (COMBUF-4)/2 bytes, but buffer is big enough

	ld	a,b		;pattern count

	ld	bc,TBSIZE	;bytes to write
	ld	de,TRKBUF	;dest

FILLP:	push	af		;save pattern count
	ld	hl,COMBUF	;source

FILLP1:	ldi			;(hl)+:=(de)+,bc-
	dec	a
	jr	NZ,FILLP1

	ld	a,b		;done?
	rlca			;msb means underflow
	jr	C,FLDON

	pop	af		;pattern count
	jr	FILLP
FLDON:				;pattern count on stack

;Print the relevent settings, and confirm with user

	ld	de,WPATST	;write pattern
	call	CRPRNT
	call	PUNIT		;unit
	call	PTRACK		;current track
	call	PSIDE		;side
	call	PDENS		;density
	ld	de,PLENST	;pattern length
	call	CRPRNT
	pop	bc		;pattern count
	ld	a,b
	call	PDEC8

	ld	de,ASKWST	;warning...
	call	ASKYN		;ask Y or N
	
;Prepare to write the track with pattern

	call	PREPD		;get ready to write

	ld	b,DFSEOJ+DFAWTO	;test mask
	ld	sp,TRKBUF	;data pointer now

	ld	a,WTRACK	;kick off write

	if INTIO
	di			;no ints during write
	endif

	out	DCMMD,a

	jr	WTLENT

;b=DFLAG mask to check for done or timeout
;c=data port address
;SP points to track data in buffer. (SP gets
;   repaired at the end by PRMAIN.)
;Loop through the pattern, quickly writing to disk
;until the controller stops requesting data, due to
;finding the next index pulse. This may write one
;bytes after the WD1793 stops requesting data, which
;will be ignored by the WD1793.
;Worst-case out-to-out time is 16 uS, or 64 cycles
;This loop worst case out-to-out=38 cycles

WTLOOP:	in	a,DFLAG		;(11)autowait until DRQ
	out	(c),d		;(13)low byte

WTLENT:	pop	de		;(10)get 2 bytes

	in	a,DFLAG		;(11)autowait until DRQ
	out	(c),e		;(13)

	and	b		;(4) done or timeout?
	jr	Z,WTLOOP	;(10)

	call	EOJ		;Read final status

	ld	de,PROTST	;write protected
	ld	a,c
	and	SWPROT
	jr	NZ,WTDONE	;quit if protected

	ld	de,DONEST
	ld	a,c
	and	SWFALT+SLOSTD
	jr	Z,WTDONE	;no errors: done

	ld	de,WFLTST	;write fault
WTDONE:	jp	PRMAIN		;repairs stack too
	EJECT
;******===Command Routine==========
;* MO *  Motor Off/On
;******  toggle with speed check
;On Entry:
;  Z set if end of cmd line already
;On Exit:
;  SDCTRL is updated
;==================================
FMOTOR:	jp	NZ,CMDERR	;no value allowed
	ld	b,0		;no toggle the 1st time

	call	NULSEK		;expose INDEX

MOT2:	ld	a,(SDCTRL)	;get SDCTRL w/ motor
	xor	b		;space: toggle it
	call	DODCTL		;...now

	call	PMOTOR		;report motor state

MOT3:	call	IMEASR		;measure spindle motor
	call	CHKCON		;Abort from user?
	cp	' '		;space to advance?
	jr	NZ,MOT3		;n: keep waiting

	ld	b,DCMOTO	;toggle now
	jr	MOT2		;y: toggle motor
	EJECT
;******===Command Routine===================
;* ST * Step <trk1> [...<trkn>]
;******
;Step to or between given tracks.
;Same as the SKcmd ,except the WD1793 fast-
;seek mode is disabled.
;On Entry:
;  Z set if end of cmd line already
;  hl=address of 1st chr aftercmd 
;Note: head must be loaded to step, since
;drive is selected only when head is loaded.
;===========================================
FSTEP:	call	FRCST		;temp force stepping

;Fall into FSEEK
	EJECT
;******===Command Routine============================
;* SK * Seek <trk1> [...trkn]
;******
;Fast seek sequentially through the list of specified
;tracks, repeating the list until the user types 'Q'.
;If no list is specified, then use list from
;previous SK or ST command. If just one track is
;specified, then just seek that track and quit.
;On Entry:
;  Z set if end of cmd line already
;  DTYPE=0 for fast-seeks, <>0 for steps
;  hl=address of 1st chr aftercmd 
;Note: head must be loaded to step, since the drive
;is selected only when the head is loaded.
;====================================================

;Get complete list of target tracks, convert
;them to binary, and store them in PATBUF.

FSEEK:	ld	de,PATBUF	;pattern buffer
	push	de

	ld	a,(de)		;in case reuse pattern
	ld	c,a

	jr	Z,FSEEK2	;reuse prior pattern?

	ld	c,0FFh		;value cnt, 1 extra

GELUP:	inc	c		;inc C
	push	bc		;save count

	call	SKIPSP		;skip ' ' between vals

	ld	a,(MAXTRK)	;max value
	ld	c,a		;...for GETDEC
	call	GETDEC		;get a track #
				;Z set if done

	inc	de		;bump pointer
	ld	(de),a		;save in list
	pop	bc

	jr	NZ,GELUP	;keep looking

FSEEK2:	call	SELECT		;select specified unit

	pop	hl		;PATBUF
	ld	(hl),c		;pattern cnt in buffer
				;..first location

;If more than 1 track, then loop thru the list until
;the user types CTRLC or ESC. If just 1 track, then
;seek that track and quit. If 0 target tracks, then
;seek track 0 and quit.
;hl=PATBUF

SEKAGN:	ld	a,(hl)		;1st byte:pattern count
	cp	2		;0 or 1 tracks?
	jr	C,SEEK1		;Y: seek just 1 track

;Seek tracks listed in PATBUF

	ld	b,a		;pattern count
	push	hl		;PATBUF

SEKLUP:	inc	hl
	ld	a,(hl)		;get a track number

	push	hl
	push	bc

	call	SEEK		;Also prints seek time
				;..if verbose

;Check for user input between seeks

SUWAIT:	call	CHKCON		;user trying to quit?

;check for toggling automatic/manual mode or verbose
;mode

	ld	c,a		;save user input
	ld	hl,VERBOS	;point to flags

	cp	'V'		;verbose mode toggle?
	jr	NZ,SKNOTV

	ld	a,(hl)		;y: toggle verbose mode
	xor	1
	ld	(hl),a
SKNOTV:

	inc	hl		;point to AUTOSK
	cp	'A'		;automatic mode toggle?
	ld	a,(hl)		;a=automatic-mode flag

	jr	NZ,SKNOTA

	xor	1		;y: toggle auto mode
	ld	(hl),a		;..and put it back
SKNOTA:

	or	a		;manual or automatic?
	jr	NZ,SKNUI	;auto: all done with user

;Manual mode: check for any of the other immediate
;commands

	ld	a,c		;recover user input
	call	CKIMED		;immediate command?

	cp	' '		;space to advance?
	jr	NZ,SUWAIT	;n: keep waiting

;Next pattern entry

SKNUI:	pop	bc
	pop	hl
	djnz	SEKLUP		;loop thru B entries

;Restart pattern

	pop	hl		;PATBUF
	jr	SEKAGN		;restart sequence

;Just seek one track, report the track, and quit

SEEK1:	inc	hl
	ld	a,(hl)		;get track no.

;Fall into SEEK
	EJECT
	SUBTTL Subroutines
;===Subroutine==============================
;Seek target track & wait for done. Use fast
;(voice-coil) seek if possible. Print seek
;time if verbose mode enabled.
;Timeout after ~12 Sec
;On Entry
;  DTYPE is 0 for fast-seeking, NZ otherwise
;  a=target track
;  SIDENO=selected side
;  SDCTRL is valid
;Trashes a,bc, hl
;===========================================
SEEK:	out	DDATA,a		;target track to WD1793

	if	INTIO
	di			;no interrupts here
	endif

	ld	a,(SDCTRL)	;select the drive
	out	DCTRL,a		;may have been heads-up

	ld	a,(DTYPE)	;can we fast-seek?
	or	a		;0 means yes
	jr	NZ,SKSLO

	ld	a,(SIDENO)	;enable fast-seek mode
	or	FASEEK
	out	ACTRL,a
SKSLO:
	ld	a,(STPRAT)	;current step rate
	or	DSEEK		;combine w/ seekcmd 
	out	DCMMD,a		;kick off WD1793

	push	de		;(11)
	ld	de,2		;(10)2 for 20 uS bump

	ld	hl,4		;(10)account for 61 uS
	ld	c,h		;(4)upper counter byte

	ld	b,11		;(7) 61 uS out-to-in
				;..(56 uS min) as
S28LUP:	djnz	S28LUP		;(13*10+8) ..required
				;..by the WD1793)

;Wait for WD1793 to finish seek command. This loop
;takes exactly 80 cycles=20 uS per pass (assuming 1
;wait state to read DSTAT, due to the Z80 CPU), so
;de=2. This loop will timeout when c times out. c is
;in  units of 655360 uS=0.6536 seconds, or about 21/32
;sec the funny math for SEEKTO causes the result to be
;rounded up.

SEK0:	add	hl,de		;(11)Bump timer
	ld	a,c		;(4)
	adc	a,d		;(4)catch carry (d=0)
	ld	c,a		;(4)high byte done
	cp (SEEKTO*32+20)/21	;(7)timeout?
	jr	Z,SEKTO		;(7)Y: abort

	inc	de		;(6)stall
	dec	de		;(6)stall
	or	a		;(4)stall

	in	a,DSTAT		;(10+1W)wait for

	rrca			;(4)test SBUSY
	jr	C,SEK0		;(12/7)

;Done seeking if no voicecoil. 79 cycles in-to-in
;exactly.

	ld	a,(DTYPE)	;(13)fast-seeking?
	or	a		;(4)0 means yes
	jr	NZ,SEKDON	;(7)N:done

;Wait for voice-coil drive to indicate seek in
;progress, or timeout and fall into the SEK2 loop in
;1 mS. (PerSci 299B drives take as much as 600 uS to
;raise its -Seek Complete after it sees -Step.) This
;loop takes exactly 80 cycles=20 uS per pass (with 4
;wait states to read ASTAT), so de=2.
;Register b is used as a timeout for this loop.

	ld	b,50		;(7)1 mS timeout

SEK1:	add	hl,de		;(11)add 20 uS to timer
	ld	a,c		;(4)
	adc	a,d		;(4)catch carry (d=0)
	ld	c,a		;(4)high byte done

	in	a,ASTAT		;(10+4W)wait for SIP

	or	a		;(4)stall
	or	a		;(4)stall

	and	DASSIP		;(7)1=seek in progress
	jr	NZ,SEK2		;(7/12)

	or	a		;(4)stall
	or	a		;(4)stall

	djnz	SEK1		;(13/8)timeout?

;Wait for voice-coil drive to indicate seek complete.
;This loop takes exactly 80 cycles=20 uS per pass
;(with 4 wait states to read ASTAT), so de=2. This
;loop will timeout when c times out.

SEK2:	add	hl,de		;(11)add 20 uS to timer
	ld	a,c		;(4)
	adc	a,d		;(4)catch carry (d=0)
	ld	c,a		;(4)high byte done
	cp (SEEKTO*32+20)/21	;(7)timeout?
	jr	Z,SEKTO		;(7)Y: abort

	in	a,ASTAT		;(10+4W)wait SIP end

	ret	Z		;(5)stall (Z not set)
	ret	Z		;(5)stall

	and	DASSIP		;(7)1=seek in progress
	jr	NZ,SEK2		;(12)

;Done. c,hl=elapsed time in units of 10 uS.

SEKDON:	pop	de

	ld	a,(SIDENO)	;disable fast-seek mode
	or	ACSSM
	out	ACTRL,a

	if	INTIO
	ei			;interrupts ok
	endif

;Print the current track number, and
;also the seek speed only if VERBOS<>0
;On Entry:
;   c,hl=seek time in 0.01 mS units
;   VERBOS <> 0 enables printing

	ld	a,(VERBOS)
	or	a
	ret	Z

	ld	de,SKTST	;seek time:
	call	CRPRNT
	call	PRNTMS		;print seek time

	jr	PTRACK

;Seek timeout. Print timeout message based on whether
;or not the drive type is set for voicecoil drive

SEKTO:	POP	de		;fix stack

	ld	de,VCST		;'voicecoil '
	ld	a,(DTYPE)	;was it a voicecoil sk?
	or	a
	call	Z,PRINTF

	ld	de,SKTOST
SEKTO1:	jp	PRMAIN

	EJECT
;===Subroutine=================
;Print the current track number
;Trashes all registers
;==============================
PTRACK:	ld	de,TRAKST	;Track:
	call	CRPRNT
	in	a,DTRCK		;get actual track #

;Fall into PDEC8

;===Subroutine====================
;Print a as decimal on the console
;Trashes all registers
;=================================
PDEC8:	ld	l,a
	ld	h,0

;fall into PDEC16

;===Subroutine==================================
;Entry at PRFRAC prints 2 digits (for after a .)
;Print hl as decimal on the console
;Trashes all registers
;===============================================
PDEC16:	ld	d,0	;Suppress leading 0's

	ld	bc,-10000
	call	DECDIG
	ld	bc,-1000
	call	DECDIG
	ld	bc,-100
	call	DECDIG

PRFRAC:	ld	bc,-10
	call	DECDIG

	ld	a,l	;last digit is simple
	jr	DECDG0	;with leading 0's
;---Local Subroutine--------------------------
;Divide hl by power of 10 in bc, and print
;the result, unless it's a leading 0.
;On Entry:
;  hl=Dividend
;  bc=divisor (a negative power of 10)
;  d=0 if all prior digits were 0
;On Exit:
;  Quotent is printed, unless it's a leading 0
;  hl=remainder
;  d=0 iff this and all prior digits are 0
;---------------------------------------------
DECDIG:	ld	a,-1	;will go 1 too many times
	push	de	;d=leading zero state

DIGLP:	ld	d,h	;de gets prev value
	ld	e,l
	inc	a
	add	hl,bc	;subtract power of 10
	jr	C,DIGLP

	ex	de,hl	;hl has remainder
	pop	de	;d=leading 0 state

	ld	e,a	;E has digit to print
	or	d	;leading 0 to suppress?
	ret	Z	;Y: digit is done

	ld	d,a	;save for next digit
 
	ld	a,e	;recover new digit

DECDG0:	add	'0'	;make ASCII
	jp	PRINTA	
	EJECT
;===Subroutine===================
;Get track ID
;On Exit:
;  (COMBUF)=track #
;  (COMBUF+1)=Side #
;  (COMBUF+2)=sector number found
;  (COMBUF+3)=Sector length (0-3)
;  b=calculated sectors/track
;  hl=calculated bytes/sector
;  DCDEN bit in SDCTRL set/reset
;   according to track density
;Rude abort if error reading ID
;Trashes c,de
;================================
TRAKID:	ld	a,(RTRIES)	;how many retries?
	rlca			;*2 for density
	ld	b,a		;retry count

TKID0:	call	PREPD		;set up for R/W

	ld	a,GETID
	ld	hl,6		;6-byte ID
	ld	de,COMBUF
	call	READD		;read the ID

	ld	hl,SDCTRL

	ld	a,c		;final status
	and	SRNFER+SCRCER+SLOSTD
	jr	Z,TKIDGD	;good read?

;retry on error with other density

	ld	a,(hl)
	xor	DCDDEN		;try other density
	ld	(hl),a	

	djnz	TKID0		;retry if b<>1

	ld	de,IDERST	;error reading ID
	jp	PRMAIN		;print exit

;Good read. Compute retries remaining in C

TKIDGD:	ld	a,b		;how many tries?
	inc	a		;cy clear from above
	rra			;account for density
	ld	c,a		;remaining retries

;Compute sectors/track & bytes/sector for ret
	
	ld	a,(COMBUF+3)	;BPS from ID
	ld	b,a		;b=BPS value

	ld	a,(hl)		;SDCTRL
	and	DCMAXI		;compute disk size offset

	rrca			;put DCMAXI into bit 2
	rrca
	add	b		;offset based on DCMAXI
	ld	e,a		;de becomes offset
	ld	d,0

	ld	a,(hl)		;SDCTRL
	and	DCDDEN		;offset on density
	jr	NZ,TKID1
	inc	de		;bump if SD

TKID1:	ld	hl,SPTTAB	;table beginning
	add	hl,de		;offset into table
	ld	a,(hl)		;a=SPT

	inc	b		;b=BPS (1-4)
	ld	hl,64		;loop goes 1 extra

TKBPSL:	add	hl,hl		;compute bytes/sector
	djnz	TKBPSL	

	ld	b,a		;b=SPT for return

	ld	de,IDRCST	;track ID required

;fall into PRTRYS

;===Subroutine=========================
;Print retries required
;to read track ID
;On entry:
;  c=number of remaining tries to read
;  de=Retry message
;Trashes a,de
;======================================
PRTRYS:	ld	a,(RTRIES)	;start value	
	sub	c		;a=# of retries
	ret	Z		;no print if no retries

	push	hl
	push	af		;save retry count
	call	CRPRNT		;retry message
	ld	de,REQST	;required
	call	PRINTF
	pop	af		;calculated retries
	push	bc
	call	PDEC8
	pop	bc
	pop	hl
	ld	de,RTRYST	;retries
	jp	PRINTF
	EJECT
;===Subroutine===========================
;Set up for read or write sector
;On entry:
;  hl points to next chr in command line
;On Exit:
;  disk ID is in COMBUF
;  correct density in SDCTRL
;  a=sector number
;  b=2nd cmd line param, if any
;  hl=# of bytes to transfer
;  WDDM=A0DDM iff 'D' followed sector #
;Abort if track mismatch
;Trashes bc,de
;========================================
RWSTUP:	ld	bc,99		;default, max
	call	GETDEC		;get sector number
	jp	Z,CMDERR	;error if no value

	push	af		;save desired sector

;Check if DDM is to be written

	call	GETASC
	jr	Z,RWSNOD	;no deleted data mark
	cp	'D'		;deleted data mark req?
RWCERR:	jp	NZ,CMDERR

	ld	a,A0DDM		;deleted data mark	
RWSNOD:	ld	(WDDM),a

	call	TRAKID		;get BPS, set density
				;this overwrites DSCTR

	ld	a,(COMBUF)	;Track # from ID
	ld	b,a
	in	a,DTRCK		;which track?
	ld	de,TKMMST	;track mismatch
	cp	b
	jp	NZ,PRMAIN	;abort if mismatch

	pop	af		;requested sector
	out	DSCTR,a		;set WD1793 sector

	ret
	EJECT
;===Subroutine=========================
;Prepare for read or write disk command
;Select drive, abort if none
;Turn on motor, wait for spin-up
;get and test drive ready, abort if not
;Set auto-wait bit
;flush disk buffer
;On Exit:
;  c=DDATA
;  drive is ready
;  Auto-wait is set
;Trashes a
;======================================
PREPD:	push	de
	ld	de,SDCTRL
	ld	a,(de)
	or	DCMOTO		;motor on
	ld	(de),a

	push	bc
	call	SELECT		;select the drive
	call	STALL1		;Spindle motor start
	pop	bc

	ld	de,CBSYST	;WD1793 busy
	in	a,DSTAT
	and	SBUSY
PDERR:	jp	NZ,PRMAIN	;quit if busy

	ld	de,NRDYST	;not ready
	in	a,DSTAT
	and	SNORDY
	jr	NZ,PDERR	;quit if not ready

	ld	c,DDATA		;C value for ret
	in	a,(c)		;flush WD1793

	ld	a,(SDCTRL)
	or	DCAUTO		;set up auto-wait
	out	DCTRL,a
	
	pop	de
	ret
	EJECT
;===Subroutine===========================
;Select the drive and Restore to track 0
;Use direct control for voicecoil drives,
;use WD1793 restore command otherwise.
;Trashes a,c,de,hl
;Leaves unit selected
;Rude exit if no unit selected
;========================================
RESTOR:	ld	a,(DTYPE)	;which kind of restore?
	or	a
	jr	NZ,SLORES	;0 for voicecoil mode

;Fast restore, using voice-coil drive's restore signal

	call	NULSEK		;so we can read track0
				;also SELECTs
	
	ld	a,(SIDENO)	;side # is in this reg
	or	a,DRESTR	;hardware restorecmd 
	out	ACTRL,a		;start restore pulse

;Direct restore does not use the WD1793 restore
;function, so clear the track register here.

	xor	a		;clear WD1793 track reg
	out	DTRCK,a

;wait for seek complete, timeout after 4.8 seconds.
;Make sure we see track 0 when the seek is complete.

	ld	hl,2162h	;l=1 mS timeout for
				;..REST0 loop, and
				;..h=4.8 Sec timeout
				;..for REST1 loop

;First Wait for the "seek in progress" signal from
;drive (slow on the PerSci 299B). If already on
;track 0 then we will timeout here after 1 mS and
;fall through the REST1 loop.

REST0:	in	a,ASTAT		;(11)wait for restore 
	and	DASSIP		;(7)1=seek in progress
	jr	NZ,REST1	;(7)

	dec	l		;(4)
	jr	NZ,REST0	;(12)
	;41 cycles=10.25 uS/pass

;Then wait for the restore to complete, with abort
;opportunity

REST1:	call	CHKCON		;(2240)user aborting?
	ld	de,RTOST	;(10)timeout message
	dec	hl		;(6)
	ld	a,h		;(4)
	or	a,l		;(4)
	jp	Z,PRMAIN	;(10)

	in	a,ASTAT		;(11)wait for finish
	and	DASSIP		;(7)1=seek in progress
	jr	NZ,REST1	;(11)
	;2303 cycles/pass=575.75 uS per pass

	ld	a,(SIDENO)	;get side # again
	or	ACSSM		;set side to end
	out	ACTRL,a		;...the restore pulse

	ld	de,TRK0ST	;Track 0 not detected
	in	a,DSTAT		;test for track 0
	and	STRAK0		;1=track 0 detected
	call	Z,CRPRNT	;message if no track 0

	ret

;WD1793 Restore for non-voice-coil drive

SLORES:	call	SELECT		;select the drive
	ld	a,RSTRCM+RS15MS ;restore slowly

;Fall into DSKCMD
	EJECT
;===Subroutine===========================
;Send command to WD1793 & wait 'till done
;Rude abort if user quits
;On Entry:
;  a=command
;  drive is selected
;On Exit:
;  c=Status after command
;Trashes a
;========================================
DSKCMD:	out	DCMMD,a		;WD1793 command

;Fall into EOJ

;===Subroutine==================
;Wait 'till WD1793 command done
;Rude abort if user quits
;On Entry:
;  a=command
;  drive is selected
;On Exit:
;  c=Status after command
;  interrupts enabled if INTIO
;  Z flag set
;Trashes a
;===============================
EOJ:
	if INTIO
	ei			;allow I/O ints
	endif

EOJLUP:	call	CHKCON		;user abort? (>56 uS)

	in	a,DSTAT		;wait for busy to end
	ld	c,a		;for ret
	and	SBUSY
	jr	NZ,eOJLUP

	ret
	EJECT
;===Subroutine============================
;Select the drive specified in SDCTRL, and
;test if any drive is actually selected.
;Print message and rudely exit if not.
;Trashes a,de
;=========================================
SELECT:	ld	a,(SDCTRL)	;select the drive
	out	DCTRL,a

	and	0Fh		;any drive selected?
	ld	de,CANTST	;no drive selected
	jp	Z,PRMAIN	;rude abort

	ret
;===Subroutine==============================
;Rewrite DCTL from SDCTRL and SHDLD
;On Exit:
;  DCTRL=SDCTRL if heads loaded
;  DCTRL=SDCTRL AND DCSMSK if heads unloaded
;Trashes a,c
;===========================================
REDCTL:	ld	a,(SDCTRL)

;Fall into DODCTL

;===Subroutine======================================
;Write to DCTl, taking into account head-load state,
;since heads are unloaded by deselecting the unit(s)
;On Entry:
;  a=new DCTRL value
;  SHDLD is valid
;On Exit:
;  SDCTRL=a
;  DCTRL=SDCTRL if heads loaded
;  DCTRL=SDCTRL AND DCSMSK if heads unloaded
;Trashes a,c
;===================================================
DODCTL:	ld	(SDCTRL),a	;save state with unit
	ld	c,a
	ld	a,(SHDLD)	;heads loaded?
	or	a		;load or unload?
	ld	a,c
	jr	NZ,DOD1		;load
	
	and	DCSMSK		;deselect to unload

DOD1:	out	DCTRL,a		;load/unload
	ret
	EJECT
;===Subroutine==================
;Read sector with retries
;On Entry:
;  de=destination address
;  hl=byte count
;  RETRIES=number of retries
;On Exit:
;  b=number of retries remaining
;  c=final status
;  Z set if success
;===============================
READS:	ld	a,(RTRIES)
	ld	b,a		;B counts tries

RDRTRY:	call	PREPD		;autowait, etc.
	push	hl		;save byte count
	push	de		;save dest address

	ld	a,RDSECT	;WD1793 command
	call	READD		;read the sector
	pop	de		;dest address
	pop	hl		;Bytes/Sect

	ld	a,c		;Final status
	and	SLOSTD+SCRCER+SRNFER
	ret	Z		;success return

	djnz	RDRTRY		;retry if we can

	or	a		;clear Z for error
	ret
;===Subroutine==========================
;Read hl bytes from disk to memory at de
;58 cycles per pass
;On Entry:
;  a=Disk Command (read or read ID)
;  de=target address
;  hl=byte count
;  Disk is already selected and setup
;On Exit:
;  c=final status
;Trashes a,de,hl
;=======================================
READD:
	if INTIO
	di			;no ints during read
	endif

	out	DCMMD,a		;WD1793 command

READDL:	in	a,DFLAG		;(11)autowait
	in	a,DDATA		;(11)disk data
	ld	(de),a		;(7)
	inc	de		;(6)
	dec	hl		;(6)
	ld	a,h		;(4)
	or	l		;(4)
	jp	NZ,READDL	;(10)

	jr	EOJ		;get final status
	EJECT
;===Subroutine======================================
;Seek the current track, so that index, track 0,etc.
;can be read, and so the spindle motor stays on
;On Exit:
;  c=status from command
;Trashes a,b,de
;===================================================
NULSEK:	call	SELECT		;select the drive
				;abort if none selected

	call	STALL1		;spindle motor start

	in	a,DTRCK
	out	DDATA,a
	ld	a,DSEEK
	jr	DSKCMD		;ret from there
;===Subroutine=====
;Stall for 1 second
;Trashes a,b,de
;==================
STALL1:	ld	de,1193
	ld	b,0

STALUP:	djnz	STALUP		;(13X256=3328)

	dec	de		;(6)
	ld	a,d		;(4)
	or	e		;(4)
	jr	NZ,STALUP	;(12)

	ret
	EJECT
;===Subroutine=======================================
;Measure spindle rotational time by timing the index
;pulses. This assumes a 4MHz Z80 with no wait states,
;and 1 wait state to read DSTAT. Timeout when looking
;for the first edge, to insure we are getting edges.
;
;If the motor is off or the drive is not ready, don't
;try to measure spindle speed.
;On Entry:
;  The drive is selected
;  NULSEK has been called, so that INDEX is visible
;On Exit:
;  Rotation time has been printed, unless the motor
;  is off or the drive is not ready
;  Timeout message printed if timeout
;  If motor is supposed to be on, it's been nudged
;     to restart motor-off timer
;  hl=revolution time in 0.01 mS units
;  hl=0 if motor off, drive not ready, or timeout
;Trashes a,c,de
;====================================================
IMEASR:	ld	hl,0

	ld	de,SDCTRL
	ld	a,(de)		;skip speed test
	and	DCMOTO		;...if motor is off
	ret	Z

	ld	a,(de)		;nudge motor to
	out	DCTRL,a		;...keep it spinning

	in	a,DSTAT		;skip speed test
	and	SNORDY		;...if drive not ready
	ret	NZ

	ld	de,MOTRST	;Spindle Motor:
	call	CRPRNT		;print message
	ld	c,SINDEX
	ld	de,TIMOST	;Timeout!

	if	INTIO
	di			;no ints
	endif

;Wait for a rising edge of INDEX

IMEAS1:	inc	hl		;(6)
	ld	a,h		;(4)
	or	l		;(4)
	jr	Z,IMTO		;(12/7)timeout?
	in	a,DSTAT		;(10+1W)wait low
	and	c		;(4)
	jr	NZ,IMEAS1	;(12)

IMEAS2:	inc	hl		;(6)
	ld	a,h		;(4)
	or	l		;(4)
	jr	Z,IMTO		;(12/7)timeout?
	in	a,DSTAT		;(10+1W)wait for
	and	c		;(4);... rising edge
	jr	Z,IMEAS2	;(12/7)

	ld	hl,-1		;(10)

;Measure the time to the next rising edge of INDEX. The
;following 2 loops each take exactly 40 cycles (10 uS)
;per pass, with 1 wait state to read DSTAT. It takes
;exactly 40 cycles from the 1st edge (above) to the
;next read of DSTAT, and 40 cycles between DSTAT reads
;between the 2 loops below.

IMEAS3:	inc	hl		;(6) bump timer
	in	a,DSTAT		;(10+1W)wait low
	ld	d,0		;(7) stall
	and	c		;(4) (clears C too)
	jr	NZ,IMEAS3	;(12/7)

	ret	C		;(5) stall (C never set)

IMEAS4:	inc	hl		;(6) bump timer
	in	a,DSTAT		;(10+1W)wait for edge
	ld	d,0		;(7) stall
	and	c		;(4)
	jr	Z,IMEAS4	;(12/7)

	if INTIO
	ei			;allow I/O ints
	endif

;hl has the time in 0.01 mS units

	ld	c,0		;msb is 0
	push	hl		;save result for ret
	call	PRNTMS		;print hl in mS
	pop	hl
	ld	de,REVST	;mS/rev

IMTO:
	if	INTIO
	ei			;ints ok
	endif
	jr	PRINTF	
	EJECT
;===Subroutine=======================
;Print c,hl in mS
;On Entry:
;  c,hl=value in 0.01 mS units
;On Exit:
;  'xxxx.xx mS' printed
;Trashes a,bc,de,hl
;====================================
;divide hl by 100 to get whole number mS portion
PRNTMS:	ld	a,c		;C has high digit
	ld	bc,-100
	ld	de,-1		;initiaize quotent

PMSLP:	inc	de
	add	hl,bc		;subtract 100
	jr	C,PMSLP
	add	b		;b=-1 here
	jr	C,PMSLP		;dec, test high digit

	ld	bc,100		;goes 1 too many times
	add	hl,bc		;hl now has remainder

	push	hl		;save remainder
	ex	de,hl		;whole # into hl
	call	PDEC16		;print whole # in de

;Print decimal point, then fractional mS portion
	ld	a,'.'
	call	PRINTA

	pop	hl		;recover remainder

	ld	d,1		;print leading 0's
	call	PRFRAC

	ld	de,MSST		;mS
	jr	PRINTF		
	EJECT
;===Subroutine===
;Print CR, LF
;Trashes a
;================
PCRLF:	push	de
	ld	de,CRLFST
	call	PRINTF
	pop	de
	ret
;===Subroutine=========================
;Print CR,LF,$-terminated message at de
;Trashes a,de
;======================================
CRPRNT:	call	PCRLF

;Fall into PRINTF

;===Subroutine===================
;Print $-terminated message at de
;Trashes a,de
;================================
PRINTF:	push	bc
	ld	c,CPRINT
	call	CDOS
	pop	bc
	ret
	EJECT
;===Subroutine========
;Print head-load state
;Trashes a,de
;=====================
PHLOAD:	ld	de,HEADST	;Head
	call	CRPRNT

	ld	a,(SHDLD)
	ld	de,LDST		;loaded
	or	a		;0 maens not loaded
	jr	NZ,PH1
	ld	de,ULDST	;unloaded

PH1:	jr	PRINTF

;===Subroutine====
;Print motor state
;=================
PMOTOR:	ld	de,MOTST	;Motor
	call	CRPRNT

	ld	de,ONST		;on
	ld	a,(SDCTRL)	;test state
	and	DCMOTO		;just the motor bit
	jr	NZ,PM1
	ld	de,OFFST	;off	

PM1:	jr	PRINTF
;===Subroutine===================
;Compute and report the data rate
;================================
PDRATE:	ld	de,DRATST	;data rate
	call	CRPRNT

	ld	hl,125		;SD minidisk kbps
	ld	a,(SDCTRL)	;DCMAXI:4 DCDDEN:6
	rlca			;DCMAXI:5 DCDDEN:7
	rlca			;DCMAXI:6 DCDDEN:C
	jr	NC,BR0
	add	hl,hl		;double density
BR0:	rlca			;DCMAXI:7
	rlca			;DCMAXI:C
	jr	NC,BR1
	add	hl,hl		;8"disk
BR1:	call	PDEC16		;hl=bit rate
	ld	de,KBPSST	;K-bits/sec
	jr	PRINTF	
	EJECT
;===Subroutine=========
;Print de as an address
;Trashes a,bc
;======================
PADDR:	call	PCRLF		;new line
	ld	a,d		;addr high byte
	call	PRHEX
	ld	a,e		;addr low byte
	call	PRHEX

	ld	a,':'
	jr	PRINTA
;===Subroutine==========
;Print a as 2 hex digits
;On Entry
;  a=value to print
;Trashes a,bc
;=======================
PRHEX:	ld	b,a		;save digit
	rrca			;hi nibble 1st
	rrca
	rrca
	rrca
	call	PHXNIB
	ld	a,b		;recover lo nibble

;fall into PHXNIB

;---Local subroutine---
;Print hex nibble
;On Entry:
;  Nibble in low 4 of a
;Trashes a,c
;----------------------
PHXNIB:	and	0Fh		;get low
	cp	0Ah		;A-F?
	jr	C,PHX1
	add	'A'-'9'-1
PHX1:	add	'0'

;fall into PRINTA

;===Subroutine=========
;Print a on the console
;Trashes a,c
;======================
PRINTA:	push	de
	ld	e,a
	ld	c,CCONOT
	call	CDOS
	pop	de
	ret

;===Subroutine===============
;Print a space on the console
;Trashes a,c
;============================
PSPACE:	ld	a,' '
	jr	PRINTA

	EJECT
;===Subroutine========================================
;Get a line of input from the user. Echo normal chrs,
;but not CR. Immediate commands (which must be the
;first chr on the line) are executed.
;On Exit:
;  0-terminated line is in COMBUF
;  hl=address of 1st non-blank chr in COMBUF
;  Z set if line has 0 characters (besides blanks)
;  Z and Cy set if immediate command was executed
;  Abort to PRMAIN if ^C or ESC
;Trashes a,bc,de
;=====================================================
GETLIN:	ld	hl,COMBUF
	push	hl
	ld	b,0		;chr count

;Backspace if possible

GLDEL:	ld	a,b		;can we back up?
	or	a
	jr	Z,GLOOP		;N: ignore

	ld	de,DELST	;BS,space,bS

GLBS:	call	PRINTF

	dec	b		;back up
	dec	hl
	
GLOOP:	call	HCONIN		;Get uppercase chr
				;Keep head loaded

	cp	CR		;end of input?
	jr	Z,GLDON

	cp	BS		;any backspaces?
	jr	Z,GLDEL
	cp	DEL
	jr	Z,GLDEL
	
	ld	(hl),a		;save new chr

	ld	e,a		;echo character
	ld	c,CCONOT
	call	CDOS

	ld	a,b		;1st chr?
	inc	b		;count new chr
	or	a
	ld	a,(hl)		;recover chr

	inc	hl		;next slot in buffer


	call	Z,cKIMED	;y: immediate cmd?
				;Z set if so 

	jr	NZ,GLOOP	;n: look for another

	scf			;indicate immediate

	pop	hl		;fix stack
	ret

;TErminate complete input in COMBUF with a null

GLDON:	ld	(hl),0		;install null term.
	pop	hl		;point to COMBUF

;Fall into SKIPSP

;===Subroutine=======================
;Skip over spaces in cmd line
;On Entry:
;  hl=address in cmd line
;On Exit:
;  a=next chr on line
;  hl=address of first chr past space
;  Z set and a=0 if no params found
;====================================
SKIPSP:	dec	hl		;account for init inc

SSLOOP:	inc	hl		;loop to skip spaces

	ld	a,(hl)

	cp	' '
	jr	Z,SSLOOP

	or	a		;set or clr Z
	ret
	EJECT
;===Subroutine==========================
;Check for one of the immediate commands
;and execute it if so.
;On Entry:
;  a=character
;On Exit if immediate command:
;  a=0
;  Z set
;  Trashes c,de
;On Exit otherwise:
;  all preserved
;=======================================
CKIMED:	cp	'+'		;step in?
	jr	Z,STEPIN

	cp	'-'		;step out?
	jr	Z,STEPOT

	cp	'>'		;nudge inward?
	jr	Z,NUDGIN

	cp	'<'		;nudge outward?
	jr	Z,NUDGOT

	cp	'H'		;headload toggle?
	ret	NZ

;Fall into TOGHD

;===Subroutine===================
;Toggle the head-load state
;On Exit:
;  a=0
;  Z set
;Trashes c,de
;================================
TOGHD:	ld	a,(SHDLD)	;get prev state
	xor	1		;toggle it
	ld	(SHDLD),a	;remember state

	push	af		;Z means unloaded
	call	REDCTL		;write it to DCTL

;set/clear the WD1793 head load state using a null seek

	in	a,DTRCK		;seek track we're on
	out	DDATA,a

	pop	af		;Z set for unloaded
	ld	a,SEEKCM	;head unloaded
	jr	Z,TOGHD1	;head loaded?
	ld	a,DSEEK		;null seek, head loaded
TOGHD1:	out	DCMMD,a		;issue WD1793 command

	ld	a,(VERBOS)	;verbose?
	or	a
	call	NZ,PHLOAD	;report headload if so

	xor	a		;clear Z
	ret
	EJECT
;===Subroutine===================
;Step in one track, if possible
;On Entry:
;  VERBOS <> 0 enables printing
;On Exit:
;  a=0
;  Z set
;Trashes c,de
;================================
STEPIN:	ld	a,(MAXTRK)
	ld	c,a
	in	a,DTRCK		;where is the head now?
	cp	c		;max already?
	
	ld	c,DSTEPI	;step in (up track)
	call	C,STPSUB	;step cmd unless max

	jr	OPTRAK		;report track number,
				;..ret with Z set
;===Subroutine===================
;Step out one track, if possible
;On Entry:
;  VERBOS <> 0 enables printing
;On Exit:
;  a=1 if verbose, 0 if not
;  Z set
;Trashes c,de
;================================
STEPOT:	in	a,DTRCK		;where is the head now?
	or	a		;track 0 already?
	
	ld	c,DSTEPO	;step out (down track)
	call	NZ,STPSUB	;step cmd unless track0

;Fall into OPTRAK

;===Subroutine==============================
;Print the current track # only if VERBOS<>0
;On Entry:
;  VERBOS <> 0 enables printing
;On Exit:
;  Z set
;  a=0
;Trashes de
;===========================================
OPTRAK:	push	hl
	push	bc

	ld	a,(VERBOS)	;verbose?
	or	a
	call	NZ,PTRACK	;print track number,
				;..trashes regs

	pop	bc
	pop	hl

	xor	a		;for return
	ret

;===Subroutine==========================
;Nudge the head inward and then back out
;On Exit:
;  a=0
;  Z set
;  b and hl have been decremented
;Trashes c
;=======================================
NUDGIN:	ld	a,(MAXTRK)
	ld	c,a
	in	a,DTRCK		;where is the head now?
	cp	c		;max already?
	jr	NC,NIDONE	;y: then do nothing
	
	ld	c,DSTEPI	;step in (up track)
	call	STPSUB		;stepcmd 
	ld	c,DSTEPO	;step in (down track)
	call	STPSUB		;stepcmd 

NIDONE:	jr	OPTRAK		;recycle some code
;===Subroutine==========================
;Nudge the head outward and then back in
;On Exit:
;  a=0
;  Z set
;  b and hl have been decremented
;Trashes c
;=======================================
NUDGOT:	in	a,DTRCK		;where is the head now?
	or	a		;track 0 already?
	jr	Z,NODONE	;y: then do nothing
	
	ld	c,DSTEPO	;step in (down track)
	call	STPSUB		;stepcmd 
	ld	c,DSTEPI	;step in (up track)
	call	STPSUB		;stepcmd 

NODONE:	jr	OPTRAK		;recycle some code
;---Local Subroutine----
;Get the step rate and
;issue the step command 
;On Entry:
;  c=step command
;Trashes a,c
;-----------------------
STPSUB:	call	SELECT		;select the drive
	ld	a,(STPRAT)
	or	c
	jp	DSKCMD		;issue command, ret
	EJECT
;===Subroutine===================
;Ask user Y or N. return if Y,
;jump to PRMAIN for anything else

;On Entry:
;  de=initial string
;Trashes a,c,de
;================================
ASKYN:	call	CRPRNT
	call	PRDRIV		;print drive letter
	ld	de,ASKRST	;ready (Y/N)?
	call	CRPRNT

	ld	c,CCONIE	;input with echo
	call	CDOS		;get chr with echo
	ld	de,aBRTST	;Abort
	cp	'Y'
	jp	NZ,PRMAIN	;abort if not Y
	ret
;===Subroutine===========================
;Get 1 ASCII chr from the cmd buffer
;On Entry:
;  hl points to chr
;On Exit:
;  a=chr
;  Z set and a=0 if no chr
;  hl points to first chr of next param
;  Rude jump to GPERR if  2nd chr present
;Trashes c
;========================================
GETASC:	ld	a,(hl)		;get next chr
	or	a
	ret	Z		;no more

	push	af		;save chr and NZ
	inc	hl
	call	SKIPSP		;should be no more
	jr	NZ,GPERR	;too much crap

	pop	af		;recover chr and NZ
	ret
;===Subroutine================================
;Get a 0 or 1 from the cmd buffer
;On Entry:
;  hl points to first chr of a parameter
;On Exit:
;  a=b=value
;  Z set if value is 0
;  hl points to first chr of next param
;  Rude jump to CMDERR if:
;  {no chr, bogus chr, value>1, another param}
;Trashes c
;=============================================
GET01:	ld	c,1
	call	GETDEC
	jr	Z,GPERR		;error if no chr

	call	SKIPSP		;should be no more
	jr	NZ,GPERR	;too much crap

	ld	a,b		;get, retest value
	or	a
	ret			;with Z set
	EJECT
;===Subroutine================================
;Get decimal parameter
;On Entry:
;  b=default value
;  c=max allowed value
;  hl points to first chr of a parameter
;On Exit:
;  a=b=value, default if no more chrs
;  Z set if no parameters found
;  Rude jump to CMDERR if bogus chr or value>c
;=============================================
GETDEC:	call	GETDD		;get 1st digit into a
	ret	Z		;return w/ default in b

	ld	b,a		;save 1st digit
	call	GETDD
	jr	Z,GPDON		;ret w/ 1-digit input

	push	af		;save new digit

	ld	a,b		;compute b=b*10
	rlca			;*2
	rlca			;*4
	add	b		;*5
	rlca			;*10
	ld	b,a

	pop	af		;recover 1st digit
	add	b		;combine digits
	ld	b,a		;into B for ret

GPDON:	ld	a,(hl)		;any more digits?
	or	a		;end of line is ok
	jr	Z,GPDOK
	cp	' '		;separator is OK too
	jr	NZ,GPERR	;abort if any
GPDOK:

	ld	a,c		;max value from input
	cp	b		;legal value?
	jr	C,GPERR		;abort if not

	or	h		;clear Z

	ld	a,b		;result in a & b
	ret

;---Local Subroutine-----------------------------
;Get & test a decimal digit
;On Exit:
;  Z set if end of cmd line or no digit
;  hl points to next cmd line chr unless it's ' '
;  a=chr converted to binary, unless Z
;------------------------------------------------
GETDD:	call	GET1
	ret	Z

	sub	'0'		;de-ASCII
	cp	10		;carry means below 10
	ret	C

GPERR:	jp	CMDERR		;bogus: rude abort
	EJECT
;===Subroutine=========================
;Get a multi-digit hex value
;On Entry:
;  hl points to first hex digit
;On Exit:
;  Z set and a=b=de=0 if no value found
;  b=number of digits found
;  de=value
;  hl points to next input param
; Rude jump to CMDERR if bogus
;======================================
GETHEX:	ld	de,0		;initial value
	ld	b,d		;b counts digits

	call	HEX2BN		;get 1st digit
	ret	Z		;Z: no chrs

GHLUP:	inc	b		;digit count
	ex	de,hl		;shift prev digits left 
	add	hl,hl
	add	hl,hl
	add	hl,hl
	add	hl,hl
	ex	de,hl

	add	e		;combine new digit
	ld	e,a

	call	HEX2BN		;get another digit
	jr	NZ,GHLUP	;keep going until gone

	call	SKIPSP		;advance to next param

	or	1		;clear Z
	ret		
;---Local Subroutine-------------------
;Convert hex at (hl) to binary
;On Exit:
;  a=chr, if any
;  hl advanced if chr found
;  Z set if no chrs
;Rude abort if bad hex
;--------------------------------------
HEX2BN:	call	GET1		;get a character
	ret	Z		;none left?

	sub	'0'		;remove ASCII bias
	cp	10
	ret	C		;done if 0-9

	sub	9+('A'-'9')	;should be 0 to 5
	cp	6		;gap chr or too big?
	jr	NC,GPERR	;Y: bogus

	add	0Ah		;make it A-F
	ret
;===Subroutine======================
;Get 1 chr from command line
;On Exit:
;  Z set if none left
;  chr in a and advance hl otherwise
;===================================
GET1:	ld	a,(hl)		;any chrs?
	or	a
	ret	Z		;N:Z set

	cp	' '		;no digit?
	ret	Z
	inc	hl
	ret
	EJECT
;===Subroutine===================
;Set Force Step Mode bit in DTYPE
;All registers preserved
;================================
FRCST:	push	af
	push	hl
	ld	a,FRCSTP
	ld	hl,DTYPE
	or	(hl)
	ld	(hl),a

	pop	hl
	pop	af
	ret
	EJECT
;===Subroutine=================
;Check for user input
;TEst for ^C or ESC pressed
;Rude jump to PRMAIN if so
;On Exit:
;  Z set if no chr waiting
;  a=chr if not ^C or ESC
;Trashes c
;==============================
CHKCON:	ld	c,CCONST	;(7)console stat
	call	CDOS		;(2194)
	or	a		;(4)Z clear=chr waiting
	ret	Z		;(11)

;Fall into UCONIN

;===Subroutine===========================
;Get console chr and convert to uppercase
;Abort command if ^C or ESC
;On Exit:
;  a=chr if not ^C or ESC
;  Z cleared
;Trashes c
;========================================
UCONIN:	ld	c,CCONIN	;console input
	call	CDOS		;wait for console chr

	sub	'a'		;convert lowercase
	cp	'z'+1
	jr	NC,UCIN1
	sub	'a'-'A'		;..to uppercase
UCIN1:	add	'a'

	cp	CTRLC		;Aborting command?
	jr	Z,UCQUIT
	
	cp	ESC		;ESC aborts too
	ret	NZ

UCQUIT:	ld	de,CRLFST
	jp	PRMAIN
;===Subroutine========================
;Get console chr, convert to uppercase
;Keep head loaded while we wait
;Abort command if ^C or ESC
;On Exit:
;  a=chr if not ^C or ESC
;=====================================
HCONIN:	ld	a,(SHDLD)	;current head-load state
	or	a		;head loaded?
	jr	Z,UCONIN	;n: just wait for the chr	

;Issue null seek commands while we wait for the user
;to restart the head-load timer, to keep the head
;loaded

HCILUP:	call	CHKCON		;user input?
	ret	NZ		;y: return with chr

	in	a,DSTAT		;drive busy?
	and	SBUSY
	jr	NZ,hCILUP	;y: no need to kick it

	in	a,DTRCK		;seek track we're on
	out	DDATA,a		;(a null seek)

	ld	a,DSEEK		;null seek, head loaded
	out	DCMMD,a		;issue WD1793 command

	jr	HCILUP		;keep looking	
	SUBTTL Command TAble
;=============
;Command TAble
;=============
CMDTAB:	db	'?',0
	dw	FHELP		;Help
	db	'BD'
	dw	FBDISP		;Display Buffer
	db	'BE'
	dw	FEDIT		;Edit buffer
	db	'BF'
	dw	FFILL		;Fill buffer
	db	'EJ'
	dw	FEJECT		;Eject
	db	'ID'
	dw	FTKID		;Track ID
	db	'MO'
	dw	FMOTOR		;Motor
	db	'QU'
	dw	WBOOT		;Quit
	db	'RE'
	dw	FRESTR		;Restore
	db	'SE'
	dw	FSET		;Settings
	db	'SK'
	dw	FSEEK		;Seek
	db	'ST'
	dw	FSTEP		;Step
	db	'SR'
	dw	FREAD		;Read sector
	db	'SW'
	dw	FWRITE		;Write sector
	db	'SV'
	dw	FVERFY		;Verify sector
	db	'VD'
	dw	FVDISP		;Display verify buffer
	db	'WP'
	dw	FWPAT		;Write pattern

	db	'A='
	dw	FSETA		;Auto mode
	db	'D='
	dw	FSETD		;Density
	db	'M='
	dw	FSETM		;Max track
	db	'N='
	dw	FSETN		;Read Retries
	db	'R='
	dw	FSETR		;Step rate
	db	'S='
	dw	FSETS		;Side
	db	'T='
	dw	FSETT		;Drive type
	db	'U='
	dw	FSETU		;Unit
	db	'V='
	dw	FSETV		;Verbose mode

	db	0		;TAble end
	EJECT
;=================================================
;Sector per Track TAbles
;Each line is 5 bytes long, with SPT for single-
;density {--,128,256,512,1024} and double density
;{128,256,512,1024,--} bytes/sector. Note that the
;lines overlap. 0 means unsupported configuration
;=================================================
SPTTAB:	db	0,18,10,5	;minidisks
	db	0,26,16,8,0	;8" disks
;==========================
;Step rate table
;Correspond to WD1793 rates
;==========================
STPTAB:	db	3		;00
	db	6		;01
	db	10		;10
	db	15		;11
	EJECT
	SUBTTL STRINGS
;====================
;$-terminated strings
;====================
BANNER:	db '===================================',CR,LF
	db '=  FLEXER Floppy Drive Exerciser  =',CR,LF
	db '=  For Cromemco Disk Controllers  =',CR,LF
	db '=  Version '
	db (VERSION AND 0F00h)/256+'0','.'
	db (VERSION AND 0F0h)/16+'0',(VERSION AND 0Fh)+'0'
	db '   by M. Eberhard  =',CR,LF
	db '===================================',CR,LF
	db CR,LF
	db 'Type ? for help'	;fall into CRLFST

CRLFST:	db	CR,LF,'$'

PROMPT:	db	'%$'
DELST:	db	BS,' '		;fall into BSST
BSST:	db	BS,'$'
HUHST:	db	'Huh?$'
EJST:	db	'Ejected$'
FSETST:	db	CR,LF,'==Current Settings & State==$'
MAXTST:	db	'Max '		;fall into TRAKST
TRAKST:	db	'Track: $'
UNITST:	db	'Unit: $'
SIDEST:	db	'Side: $'
MODEST:	db	'Mode: $'
AUTOST:	db	'Automatic$'
MANST:	db	'Manual$'
VERBST:	db	'Verbose Mode: $'
MOTST:	db	'Motor $'
ONST:	db	'on$'
OFFST:	db	'off$'
HEADST:	db	'Head $'
ULDST:	db	'un'		;fall into LDST
LDST:	db	'loaded$'
DRIVST:	db	'Drive $'
NRDYST:	db	'not '		;fall into RDTST
RDYST:	db	'ready$'
DISKST:	db	'Diskette $'
UNPRST:	db	'not '		;fall into PROTST
PROTST:	db	'write protected$'
MOTRST:	db	'Spindle Motor: $'
MSST:	db	' mS$'
REVST:	db	'/rev$'
CANTST:	db	'Can',QUOTE,'t. ' ;fall into NONEST
NONEST:	db	'No unit selected$'
SKTOST:	db	'Seek '		;fall into TIMOST
TIMOST:	db	'Timeout!$'
SKTST:	db	'Seek Time: $'
STRST:	db	'Step Rate: $'
DTYPST:	db	'Drive Type: $'
VCST:	db	'Voicecoil $'
MAXIST:	db	'8"$'
MINIST:	db	'Minidisk$'
TRK0ST:	db	'Track 0 $'
NDETST:	db	'not '		;fall into DETST
DETST:	db	'detected$'
SNGLST:	db	'Single$'
DUBLST:	db	'Double$'
DENSST:	db	' density$'
ASKSST:	db	'Write from buffer to unit $' 
ASKWST:	db	'Insert unprotected disk in unit $'
ASKRST:	db	'Ready (Y/N)? $' 
BADPST:	db	'Pattern bytes may not be between F5 and FE$'
ABRTST:	db	'Abort$'
WPATST:	db CR,LF,'==Write Pattern to Disk Track==',CR,LF
	db CR,LF,'WARNING: This destroys formatting and data!$'
	db CR,LF
DONEST:	db	'Done$'
PLENST:	db	'Pattern length: $'
SCONST:	db	'  -Space to continue, ESC to Quit-$'
BPSST:	db	' bytes/sector$'
TKIDST:	db	CR,LF,'==Track ID==$'
CBSYST:	db	'Error: WD1793 busy$'
BYRDST:	db	' bytes read into buffer',CR,LF
	db	'Deleted data mark $'
PREST:	db	'present$'
ADELST:	db	'absent$'
RVERST:	db	'Verify '	;fall into RERRST
RERRST:	db	'Read Error$'
VERRST:	db	'Verify Error$'
WFLTST:	db	'Write Fault$'
IDERST:	db	'Error reading track ID$'
RNFST:	db	'Sector not found$'
TKMMST:	db	'Track mismatch$'
SDMMST:	db	'Side mismatch$'
BYWRST:	db	' bytes written to disk$'
BYVFYD: db	' bytes verified$'
UNSUST:	db	'Unsupported'	;fall into SPTST
SPTST:	db	' Sectors/Track$'
RETRST:	db	'Max read retries: $'
REQST:	db	' required $'
IDRCST:	db	'Track ID$'
SRRCST:	db	'Sector read$'
VRRCST:	db	'Verify read-back$'
RTRYST:	db	' retries$'
DRATST:	db	'Data rate: $'
KBPSST:	db	'K bits/second$'
RTOST:	db	'Restore Timeout!$'

HLPMSG:
;   12345678901234567890123456789012345678901234567890123456789012345678901234567890
 db '                            ==FLEXER Commands=='
 db CR,LF
 db 'BD         Display read/write buffer      ?         Print Help screen'
 db CR,LF
 db 'BE <A>     Edit read/write buffer         A={0/1}   Auto mode {Off/On}'
 db CR,LF
 db 'BF <H>     Fill read/write buffer         D={S/D}   {Single/double} density'
 db CR,LF
 db 'EJ         Eject diskette                 M={1-99}  Max track number'
 db CR,LF
 db 'ID         Read current track ID          N={0-99}  Max read retries'
 db CR,LF
 db 'MO         Toggle/measure spindle motor   R={0-3}   Step rate'
 db CR,LF
 db 'QU         Quit to CDOS                   S={0/1}   Disk side'
 db CR,LF
 db 'RE         Restore to track 0             T={5/8/V} {5.25"/8"/Voicecoil} drive'
 db CR,LF
 db 'SE         Display settings & status      U={A-D}   Unit'
 db CR,LF
 db 'SK <T1> [...<Tn>] Fast voicecoil-seek     V={0/1}   Verbose mode {Off/On}'
 db CR,LF
 db 'SR <S>     Read sector into R/W buffer   IMMEDIATE COMMANDS'
 db CR,LF
 db 'ST <T1> [...<Tn>] Track-by-track seek      H   Toggle head load'
 db CR,LF
 db 'SV <S>     Verify sector = R/W buffer      +   Step in one track'
 db CR,LF
 db 'SW <S> [D] Write R/W buffer to sector      -   Step out one track'
 db CR,LF
 db '           [D sets deleted-data mark]      >   Nudge head inward'
 db CR,LF
 db 'VD         Display verify buffer           <   Nudge head outward'
 db CR,LF
 db 'WP <H1> [...<Hn>] Write pattern to track'
 db CR,LF
 db 'A toggles automatic mode and V toggles verbose mode during SK and ST.'
 db CR,LF
 db 'SK or ST with no arguments will use the previous track list.'
 db CR,LF
 db 'Use space bar to toggle motor or go to next track, use ^C or ESC to quit.'
 db CR,LF
 db 'For BE command, enter Hex value to change, CR to skip, ^C or ESC to quit.'
 db '$'
	EJECT
	SUBTTL RAM Variables
;=========================
;Initialized RAM variables
;=========================
VERBOS:	db	1		;01h for verbose display mode
AUTOSK:	db	0		;01h for automatic seek/step
				;AUTOSK must follow VERBOS

DTYPE:	db	DT8		;Drive Type
DTVC	  equ	  0		  ;Voicecoil (must be 0)
DT8	  equ	  1		  ;8" disk
DTMD	  equ	  2		  ;minidisk
DTHDMD	  equ	  3		  ;high-density minidisk
FRCSTP	  equ	  80H		  ;force step mode

SIDENO:	db	ACSID0		;side number, for ACTRL
SHDLD:	db	0		;1 for head loaded
SDCTRL:	db	DCMAXI+DCMOTO	;Current DCTRL value
STPRAT:	db	DEFSTP		;Step rate
MAXTRK:	db	MAXTK8		;max track number
WDDM:	db	0		;request write deleted data mark
RTRIES: db	DRTRYS+1	;Max allowed Read tries (retries+1)
;===================
;Track Patern Buffer
;===================
PATBUF:	db	0,0		;first 2 bytes initialized
	ds	CBSIZE/2-4
;===========
;Local stack
;===========
	ds	16
STACK:				;top of stack
;==================================
;Track buffer, used for track write
;(Overwrites the sector buffer and
;the verify buffer.)
;==================================
TRKBUF:
;==================================
;Sector buffer for reads and writes
;==================================
SECBUF:	ds	MAXBPS		;biggest possible sector
;====================
;Sector verify buffer
;====================
VERBUF:	ds	MAXBPS		;biggest possible sector

RAMEND	equ	$
	END