;==============================================================
; FLEXER/CCS: Floppy Drive Exercizer
; for California Computer Systems CCS 2422 Floppy Disk
; Controller
;
; For measuring and adjusting floppy drives, including:
;  8" and 5.25" (minidisk) drives
;  Single- and double-sided disks
;  Single- and double-density drives
;  40-track minidisks (and 80-track "HD" minidisks)
;  Stepper-motor and voice-coil actuators
;
;==============================================================
; Revision History
;  Rev    Date       Author
; 1.00   26 Jan 2017  M. Eberhard
;  Ported from CDOS Flexer for the Cromemco controllers.
;  Voicecoil (PerSci) drive) untested.
; 1.01   29 June 20178  M. Eberhard
;  Get VoiceCoil mode working. Change register names to match
;  CCS documentation.
; 1.02  9 February 2018  M. Eberhard
;  Improve help screen. Report 8" diskette sidedness in SE.
; 1.03  14 Feb 2018  M. Eberhard
;  Add WC command, Fix a few bugs
; 1.04  15 May 2018  M. Eberhard
;  Detect if called by Memon/80, and use its I/O if so (MMTEST)
; 1.05  17 May 2018  M. Eberhard
;  Just undo 1.04 (This is pretty much the same as 1.03.)
; 1.06  4 June 2018  M. Eberhard
;  Fix side identification during sector read (Thanks Len B.)
; 1.07 4 June 2018  M. Eberhard
;  Restart from 1.03, repair sector read
;==============================================================
; Notes:
;
; This program uses software timing loops, and assumes either
; a 2 or 4 MHz 8080 or Z-80 with 0-wait states. Note that the
; M1 jumper on the CCS 2814 Z/80 CPU board must not be in the
; 'on' position for this program's timing loops to be accurate.
;
; Despite unrolled loops, etc., Flexer cannot reliably read or
; write double-density 8" disks when running at 2 MHz.
;
; This program requires a little more than 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 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 a CP/M FORMAT program) before it can be used by CP/M.
;
; See the long comment at the beginning of the SEEK subroutine
; for some detail about fast-seeking issues unique to the CCS
; 2422 disk controller.
;==============================================================
REVMAJ	equ	1		;Major revision level (1-9)
REVMIN	equ	07h		;Minor revision level (1-99)
				;each hex digit converts to ASCII

FALSE	equ	0
TRUE	equ	NOT FALSE

;Program Equates

MAXTK8	equ	76		;default 8" disk max track #
MAXTKM	equ	39		;default minidisk max track #
SEEKTO	equ	5		;approx. seek timeout (secs)
DRTRYS	equ	3		;default read retry max

LBSIZE	equ	79		;cmd line buff size
MAXBPS	equ	1024		;max bytes per sector
TBSIZE	equ	10500		;track buffer size

;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
;-------------
; CP/M EQUATES
;-------------
WBOOT	equ	0000H		;Jump to BIOS warm boot
				;(reloads CCP and BDOS)
WBOOTA	equ	WBOOT+1		;Address of BIOS
CDISK	equ	0004H		;Current drive

COMBUF	equ	0080H		;Cmd line parameters buffer
TPA	equ	100h		;transient programming area
;---------------------------------------------------------
;BIOS entry points, relative to the base address in WBOOTA
;---------------------------------------------------------
CONST	equ	06h		;Get console status in a
				;(0=not ready, ffh=ready)
CONIN	equ	09h		;Get console chr into a
CONOUT	equ	0Ch		;Write c to console output
;--------------------------------
;CCS 2422 Disk Controller Equates
;--------------------------------
;Port Addresses

CTRL2	equ	4		;Control Register 2
STAT2	equ	4		;Status Register 2
DSTAT	equ	30H		;WD1793 Status Register
DCMMD	equ	DSTAT		;WD1793 command Register
DTRCK	equ	DSTAT+1		;Track Register
DSCTR	equ	DSTAT+2		;Sector Register
DDATA	equ	DSTAT+3		;Data Register
STAT1	equ	DSTAT+4		;Status Register 1
CTRL1	equ	DSTAT+4		;Control Register 1

; WD1793 Commands

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 sector
WSECCM	equ	10100000b	;(II) Write sector
RADRCM	equ	11000000b	;(III) Read address
RTRKCM	equ	11100000b	;(III) Read track
WTRKCM	equ	11110000b	;(III) Write track
A0DDM	equ	00000001b 	;(III)Write deleted-data mark
FINTCM	equ	11010000b	;(IV) Force interrupt

;WD1793 Command Options (1st letter matches WD1793 data sheet)

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
UPDTTR	equ	00010000b	;(I) UPDATE TRACK REG
F1SCF	equ	00000010b	;(II) SIDE COMPARE FLAG
F2SSF	equ	00001000b	;(II) SIDE SELECT FLAG
MULTCM	equ	00010000b	;(II) READ/WRITE MULTIPLE
ED15MS	equ	00000100b	;(II & III) SETTLING DELAY

;WD1793 Step Rates, as needed by supported disk drives

;  Value   Mini   Maxi
; RS03MS   6 mS   3 mS
; RS06MS  12 mS   6 mS
; RS10MS  20 mS  10 mS
; RS15MS  30 mS  15 mS

STEP5	equ	RS10MS		;SA400L STEP RATE=20 MS
				;SA450 (2-SIDED) IS THE SAME
				;(MINIDISK MODE DOUBLES RS10MS)
STEP8	equ	RS10MS		;SA800 STEP RATE=8 MS
STP8D	equ	RS03MS		;SA851 2-SIDED STEP RATE=3 MS
RSMASK	equ	11111100b	;MASK TO REMOVE STEP RATE

DEFSTP	equ	RS10MS		;default step rate

;Composite WD1793 Commands

CCSTEPI	equ	STPICM+UPDTTR	;Step-in command w/o head load
CCSTEPO	equ	STPOCM+UPDTTR	;Step-out command w/o head load
CCSEEK	equ	SEEKCM+HDLOAD	;Seek command
CCSEEKV	equ	CCSEEK+VERTRK	;Seek and verify command
CCWTRAC	equ	WTRKCM+ED15MS	;Write track command
CCRDSEC	equ	RSECCM+F1SCF	;Read sector, check side
CCWRSEC	equ	WSECCM+ED15MS	;write sector
CCRDADD	equ	RADRCM+ED15MS	;Read address command
CCRSTOR	equ	RSTRCM+RS15MS	;Restore slowly

;Control Register 1 (CTRL1) Bit Definitions

DCDS1	equ	00000001b	;DRIVE SEL 1
DCDS2	equ	00000010b	;DRIVE SEL 2
DCDS3	equ	00000100b	;DRIVE SEL 3
DCDS4	equ	00001000b	;DRIVE SEL 4
DCMAXI	equ	00010000b	;0=MINI
DCMOTO	equ	00100000b	;MOTOR ON
DCDDEN	equ	01000000b	;DOUBLE DENS ENABLE
DCAUTO	equ	10000000b	;AUTOWAIT

DCDSMSK	equ	DCDS1+DCDS2+DCDS3+DCDS4	;DRIVE SELECT MASK

;Control Register 2 (CTRL2) Bit Definitions

BCEJECT	equ	00000100b	;Remote Eject (PerSci drives)
BCFSEKN	equ	00010000b	;-Fast Seek: Voice-coil drives
				;Note: CCS manual is wrong.
BCSIDE0	equ	01000000b	;0=SIDE 1, 1=SIDE 0
BCROMEN	equ	10000000b	;Enable onboard ROM

;Composite CTRL2 Values for Selecting Disk Sides
;Notes:
; ROM is actually disabled via the Page Register
; Fast Seek is disabled via a hardware jumper

SIDE0	equ	BCROMEN+BCSIDE0
SIDE1	equ	BCROMEN

;WD1793 Status Register (DSTAT) after a command

SBUSY	equ	00000001b	;(ALL) Busy
SCRCER	equ	00001000b	;(ALL) CRC error
SWPROT	equ	01000000b	;(ALL) Write protect
SNORDY	equ	10000000b	;(ALL) Drive not ready
SINDEX	equ	00000010b	;(I) Index
STRAK0	equ	00000100b	;(I) Track 0
SEEKER	equ	00010000b	;(I) Seek error
SHEADL	equ	00100000b	;(I) Hhead loaded
SWFALT	equ	00100000b	;(Write commands) Write fault
SDATRQ	equ	00000010b	;(II & III) Data Request State
SLOSTD	equ	00000100b	;(II & III) Lost data error
SRNFER	equ	00010000b	;(II & III) REex not found error
SRTYPE	equ	00100000b	;(II & III) Record type fault

;Status Register 1 (STAT1) Bit Definitions

DFSEOJ	equ	00000001b	;WD1793 INTRQ signal, active high
DFDS1	equ	00000010b	;DS1 state in CTRL1
DFDS2	equ	00000100b	;DS2 state in CTRL1
DFDS3	equ	00001000b	;DS3 state in CTRL1
DFDS4	equ	00010000b	;DS4 state in CTRL11
DFSHLD	equ	00100000b	;WD1793 head-load, active high
DFAUTOB	equ	01000000b	;Autoboot jumber, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;Status Register 2 (STAT2) Bit Definitions

BTRK0	equ	00000001b	;When on track 0: 1=maxi disk
BMINI	equ	00000010b	;-CTRL1 bit: 1=minidisk
BWPRT	equ	00000100b	;0=disk is write-protected
BSIDE0	equ	00001000b	;SIDE0 bit in CTRL2
BINDEX	equ	00010000b	;Index signal, Aactive low
BDDEN	equ	00100000b	;DDEN nit in CTRL1
BSIDED	equ	01000000b	;0=selected maxi disk is 2-sided
BVDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;*****************
;Beginning of code
;*****************
	ORG	TPA

;Clear RW Verify buffers using CP/M's stack

	lxi	b,MAXBPS*2	;clear 2 buffers
	mvi	e,0
	call	FILBUF

;Create local stack, print banner

	lxi	d,BANNER	;Sign-on msg

;Fix stack, Print message at de, and go to MAIN

PRMAIN:	lxi	sp,STACK	;local stack
	call	CRPRNT

;Fall into MAIN

;========================================
;Main loop: re-initialize several things,
;look for commands from user & dispatch
;========================================
MAIN:	call	RECTL1		;restore CTRL1 state
				;may spin the motor up

	lda	DTYPE		;cancel forced-step
	ani	FRCSTP XOR 0FFh
	sta	DTYPE

	lda	SCTRL2		;get side number & seek type
	out	CTRL2

MAINLP:	lxi	d,PROMPT
	call	CRPRNT

	call	GETLIN		;get 0-terminated line
	jz	MAINLP		;null line: try again

	lxi	d,CMDTAB
	call	FNDCOM		;get 1st 2 chrs as cmd
	jz	CMDERR		;command not in table

	call	SKIPSP		;Skip any trailing spaces	

;Command found. Dispatch to (de)
;with Z set if end of command line

	lxi	b,MAIN		;create return addr
	push	b
	push	d		;execution addr
	ret			;go execute

;******========================================
;* ID *  Display Track ID Command
;******
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FTKID:	jnz	CMDERR		;no params allowed

	call	TRAKID		;get track id

;Display track ID info

	push	b		;save SPT & retries
	push	h		;save BPS/4

	lxi	d,TKIDST	;heading
	call	CRPRNT

	lxi	d,TRAKST	;print track
	call	CRPRNT
	lda	COMBUF		;track number from ID
	call	PDEC8

	lxi	d,SIDEST	;print side
	call	CRPRNT
	lda	COMBUF+1	;side number from ID
	call	PDEC8

	call	PDENS		;density

	call	PCRLF
	pop	h		;recover BPS/4
	dad	h		;compute BPS
	dad	h
	call	PDEC16		;print hl as dec	
	lxi	d,BPSST		;Bytes per sector
	call	PRINTF

	call	PCRLF
	pop	b		;SPT & retries from TRAKID
	mov	a,b		;A=SPT
	lxi	d,UNSUST	;unsupported
	ora	a		;0 means unsupported
	jz	PRMAIN

	call	PDEC8		;print A=SPT
	lxi	d,SPTST		;sector/track
	jmp	PRINTF			

;******=========================================
;* SR *  Read Sector command
;******
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FREAD:	call	RWSTUP		;hl=BPS/4

	lxi	d,SECBUF		;target address
	call	READS		;read the sector

	lxi	d,RERRST	;read error?
	jnz	RWFLT

	push	b		;C= final status
	lxi	d,SRRCST	;read required
	mov	c,b		;tries remaining
	call	PRTRYS		;print retries, if any

	call	PCRLF

	dad	h		;compute BPS
	dad	h		;..from BPS/4
	call	PDEC16
	lxi	d,BYRDST	;bytes trans, rec typ
	call	PRINTF

	pop	b		;C=final status
	mov	a,c
	lxi	d,PREST		;present
	ani	SRTYPE		;deleted data mark?
	jnz	FREAD1
	lxi	d,ADELST	;absent
FREAD1:	call	PRINTF

;Fall into CHKSID

;===Subroutine=================================
;compare side in SCTRL2 to side found by TRAKID
;On Entry:
; (COMBUF+1)=TRAKID side number
;On Exit:
;  Z set if match
;  message printed, Z clear if not
;Trashes a,b,de
;==============================================
CHKSID:	lda	SCTRL2		;correct side?
	mvi	b,0
	ani	BCSIDE0		;active-low bit
	jnz	CKSID0
	inr	b
CKSID0:
	lda	COMBUF+1	;ID side number
	lxi	d,SDMMST
	cmp	b		;mismatch?
	rz			;no:done

	call	CRPRNT		;yes:report
	inr	b		;clear Z
	ret

;******=========================================
;* SW *  Write Sector command
;******  with read-back verify
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FWRITE:	call	RWSTUP		;setup for write & read

	call	CHKSID		;side mismatch?
	rnz			;yes:done

	push	h		;save bytes/sect

	lxi	d,ASKSST
	call	ASKYN		;ready?

	call	PREPD		;autowait, etc.
	lxi	d,SECBUF		;source address

	lda	WDDM		;deleted data mark?
	ori	CCWRSEC		;1793 command

	out	DCMMD

;Write l*4 bytes to disk from  memory at de. The CCS
;controller will autowait on output to DDATA
;30 Z80 cycles/27 8080 cycles worst case between OUTs
;(Z80 cycles in parens)[8080 cycles in brackets]

WRITDL:	ldax	d		;(7)[7]buffer byte 1
	out	DDATA		;(11)[10]disk byte 1

	inx	d		;(6)[5]point to byte 2
	ldax	d		;(7)[7]buffer byte 2
	dcr	l		;(4)[5]Z set if 0
	out	DDATA		;(11)[10]disk byte 2

	inx	d		;(6)[5]point to byte 3
	ldax	d		;(7)[7]buffer byte 3
	out	DDATA		;(11)[10]disk byte 3

	inx	d		;(6)[5]point to byte 4
	ldax	d		;(7)[7]buffer byte 4
	inx	d		;(6)[5]point to byte 1
	out	DDATA		;(11)[10]disk byte 4

	jnz	WRITDL		;(10)[10]more quads?

	call	EOJ		;get final status

	mov	a,c		;Final status
	lxi	d,PROTST
	ani	SWPROT
	jnz	FLTEXT

	mov	a,c
	lxi	d,WFLTST	;write fault
	ani	SRNFER+SCRCER+SLOSTD+SWFALT
	jnz	RWFLT

;Report number of bytes written

	call	PCRLF

	pop	h		;BPS/4
	push	h		;save for verify
	dad	h		;compute BPS
	dad	h
	call	PDEC16		;number of bytes

	lxi	d,BYWRST	; 'bytes written...'
	call	PRINTF

;Read back into verify buffer, to verify write

	pop	h		;Bytes/Sect
	jmp	WVERFY		;reuse verify command

;******=========================================
;* SV *  Verify Sector command
;******
;On Entry at FVERFY:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;Entry at WVERFY:
;  DSCTR register = Sector number
;  CTRL1 is set up
;   hl=number of bytes to transfer/4
;  WDDM = A0DDM iff 'D' followed sector number
;===============================================
FVERFY:	call	RWSTUP		;setup for sector read

	call	CHKSID		;side mismatch?
	rnz			;yes:done

WVERFY:	push	h		;save BPS/4 for compare
	lxi	d,VERBUF	;dest address = verify buf
	call	READS		;read the sector we wrote

	lxi	d,RVERST	;read error during verify
	jnz	FLTEXT		;..even after retries?

	lxi	d,VRRCST	;verify read required
	mov	c,b		;C=tries remaining
	call	PRTRYS		;print retries, if any

;Compare read-back data to written data

	pop	h		;BPS/4
	dad	h		;compute hl=BPS
	dad	h
	push	h		;save BPS for message

	xchg			;de=byte count
	lxi	b,VERBUF	;bc=target address
	lxi	h,SECBUF		;hl=source address

VLOOP:	ldax	b		;loop through de bytes
	cmp	m
	jnz	VFAIL

	inx	b
	inx	h
	dcx	d
	mov	a,d
	ora	e
	jnz	VLOOP
;--------------
;verify success
;--------------
	pop	h		;BPS
	call	PCRLF
	call	PDEC16		;number of bytes
	lxi	d,BYVFYD	;bytes verified
	jmp	PRINTF
;-----------
;verify fail
;-----------
VFAIL:	lxi	d,VERRST	;verify error
	jmp	FLTEXT
;-----------------
;read/Write  error
;de = error string
;-----------------
RWFLT:	ani	SLOSTD+SCRCER+SWFALT
	jnz	FLTEXT

	lxi	d,RNFST		;record not found
FLTEXT:	jmp	PRMAIN	
	
;******=========================================
;* VD *  Display the Verify Buffer Command
;******
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FVDISP:	lxi	h,VERBUF	;verify buffer address
	jmp	BDISP		;same as FBDISP	

;******=========================================
;* BD *  Buffer Display Command
;******
;On Entry at FBDISP:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;On Entry at BDISP:
;  hl=buffer address
;===============================================
FBDISP:	lxi	h,SECBUF		;read/write buffer addr

BDISP:	jnz	CMDERR		;no params allowed
	lxi	d,0		;address count

FDISP1:	call	PADDR		;print address

	push	h		;save for ASCII print
	push	d

FDISP2:	mvi	a,' '
	call	PRINTA

;Print 16 bytes in hex

	mov	a,m		;data
	call	PRHEX
	inx	d		;next
	inx	h

	mov	a,e
	ani	0Fh		;new line every 16
	jnz	FDISP2

;Print them again in ASCII

	mvi	a,' '
	call	PRINTA

	pop	d
	pop	h

FDISP3:	mov	a,m
	cpi	' '		;non-printing?
	jc	FDISP4
	cpi	DEL		;more non-printing
	jc	FDISP5
FDISP4:	mvi	a,'.'

FDISP5:	call	PRINTA
	inx	d		;next
	inx	h

	mov	a,e
	ani	0Fh		;new line every 16
	jnz	FDISP3


	mov	a,e
	ora	a		;pause every 256
	jnz	FDISP1

	mov	a,d
	cpi	MAXBPS/256	;max bytes/sec
	rnc

	push	d
	lxi	d,SCONST	;space to continue
	call	CRPRNT
	pop	d

FDISP6:	call	TABORT		;Abort from user?
	cpi	'Q'		;Q quits too
	jz	UCQUIT

	cpi	' '		;space to advance?
	jnz	FDISP6		;n: keep waiting

	jmp	FDISP1

;******=========================================
;* BF *  RW Buffer Fill Command
;******
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FFILL:	call	GETHEX		;get fill value in de
	mov	a,b		;too many digits?
	cpi	3		;max 2 digits
	jnc	CMDERR

	mov	a,m		;any more input?
	ora	a
	jnz	CMDERR		;junk at end

	lxi	b,MAXBPS	;size of RW buffer

;Fall into FILBUF

;===Subroutine=================
;Fill SECBUF
;On Entry:
;  bc = number of bytes to fill
;  e = fill value
;Trashes psw,bc,hl
;==============================
FILBUF:	lxi	h,SECBUF

FILLUP:	mov	m,e		;loop to fill buffer
	inx	h
	dcx	b
	mov	a,b
	ora	c
	jnz	FILLUP

	ret

;******=========================================
;* BE *  Buffer Edit Command
;******
;Allow user to edit buffer contents
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FEDIT:	call	GETHEX		;get address in de
	mov	a,b		;overflow?
	cpi	5
	jnc	CMDERR

	mov	a,m		;any more chrs?
	ora	a
	jnz	CMDERR

	mov	a,d
	cpi	MAXBPS/256	;past buffer end?
	jnc	CMDERR

FEDIT0:	lxi	h,SECBUF
	dad	d		;hl points to buff addr

FEDIT1:	call	PADDR		;print address

FEDIT2:	mvi	a,' '
	call	PRINTA

	mov	a,m		;data
	call	PRHEX

	mvi	a,'.'		;separator
	call	PRINTA

	push	h
	push	d

	call	GETLIN		;get user input
	mov	a,m		;Quit?
	cpi	'Q'		;Q quits us too
	jz	UCQUIT
	call	GETHEX		;convert to hex
				;Z set if no input
	mov	a,e		;get low byte

	pop	d
	pop	h

	jz	FEDIT3		;no change?

	mov	m,a		;update w/ low byte
FEDIT3:

	inx	h
	inx	d

	mov	a,e		;need an address?
	ani	07h		;every 8
	jnz	FEDIT2

	mov	a,d		;end of sector?
	cpi	MAXBPS/256
	jc	FEDIT1

	lxi	d,0		;sector addr count
	jmp	FEDIT0		;wrap to beginning

;******================================
;* SE *  Display Settings & Status
;******
;On Entry:
;  Z set if end of command line already
;======================================
FSET:	jnz	CMDERR		;no parameters allowed	

	lxi	d,FSETST
	call	CRPRNT

	call	PSPEED		;Z80 CPU speed
	call	PMODE		;operating mode
	call	PVERB		;verbose mode
	call	PRETRY		;Read retries
	call	PDTYP		;type, max trk, rates
	call	PUNIT		;unit

	lda	SCTRL1		;any unit selected?
	ani	0Fh
	rz			;no:done

	call	PSIDE		;side
	call	PTRACK		;current track
	call	PMOTOR		;motor state
	call	PHLOAD		;head-load state

;Issue a seek command so that the 1793 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
;CTRL1 is captured during NULSEK. 

	lda	SCTRL1		;no ready signal
	ani	DCMAXI		;...on a minidisk
	jz	DS2

	push	b		;save DSTAT
	lxi	d,DRIVST	;Drive
	call	CRPRNT
	pop	b		;recover DSTAT in C

	lxi	d,RDYST		;ready
	mov	a,c
	ani	SNORDY
	jz	DS1
	lxi	d,NRDYST	;not ready	
DS1:	push	psw		;save ready status
	call	PRINTF	
	pop	psw
	rnz			;skip if not ready

;The disk appears ready. Print status that depends on
;having a disk spinning in the drive

DS2:	lxi	d,TRK0ST	;track 0
	call	CRPRNT
	lxi	d,DETST		;detected
	in	DSTAT
	ani	STRAK0
	jnz	DS3
	lxi	d,NDETST	;not detected
ds3:	call	PRINTF

	lxi	d,DISKST	;Disk
	call	CRPRNT
	lxi	d,UNPRST	;not write protected	
	in	DSTAT
	ani	SWPROT
	jz	DS4
	lxi	d,PROTST	;write protected
DS4:	call	PRINTF

	lda	SCTRL1		;no more checks
	ani	DCMAXI		;...on a minidisk
	jz	IMEASR

;Report doskette sidedness
	call	PCRLF
	call	PSIDED

	lxi	d,DETST		;detected
	call	PRINTF		;finish message
	
;look for a voicecoil drive

	lda	SCTRL1		;turn on DD bit
	ori	DCDDEN		;..to shorten WD1793 delays
	out	CTRL1

	lxi	d,VDST
	call	CRPRNT

	in	DTRCK		;current track
	out	DDATA		;set up for a null seek

	lda	SHDLD		;head load state
	ora	a		;head loaded?
	jz	DS5		;N: test the other way


	mvi	a,SEEKCM	;null seek, unload head
	call	DSKCMD

	mvi	a,CCSEEK	;(7)Null seek, head loaded
	call	DSKCMD

	mov	a,c		;DSTAT value into a
	jmp	DS6

DS5:	mvi	a,CCSEEK	;(7)Null seek, head loaded
	call	DSKCMD

	mov	d,c		;remember DSTAT value
	mvi	a,SEEKCM	;null seek, unload head
	call	DSKCMD
	mov	a,d

DS6:	lxi	d,DETST		;detected
	ani	SHEADL		;seek complete driven?
	jnz	DS7		;head will appear loaded

	lxi	d,NDETST	;not detected

DS7:	lda	SCTRL1		;put DD bit back
	out	CTRL1

	call	PRINTF		;finish message

	jmp	IMEASR		;go measure spindle speed

;******=========================
;* A= * Select Automatic Mode
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
; OPMODE updated
;===============================
FSETA:	call	GET01		;0 or 1
	xri	1		;so 1 means manual
	sta	OPMODE

;Fall into PMODE

;===Subroutine=======
;Print Operating Mode
;====================
PMODE:	lxi	d,MODEST	;Mode:
	call	CRPRNT

	lda	OPMODE
	lxi	d,AUTOST	;Automatic
	ani	OPMANM		;0 means auto
	jz	PMOD1
	lxi	d,MANST		;Manual

PMOD1:	jmp	PRINTF

;******============================
;* D= * Set Density
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
;  SCTRL1 drive density bit updated
;==================================
FSETD:	call	GETASC		;get ASCII param

	lxi	h,SCTRL1
	cpi	'S'
	jz	SETSD

	cpi	'D'
	jnz	CMDERR		;illegal value?

	mov	a,m
	ori	DCDDEN		;double density
	jmp	SETD1

SETSD:	mov	a,m
	ani	DCDDEN XOR 0FFh	;single density

SETD1:	mov	m,a

;Fall into PDENS

;===Subroutine=========
;Print selected density
;======================
PDENS:	lda	SCTRL1
	lxi	d,SNGLST	;Single
	ani	DCDDEN		;what density?
	jz	DENS1
	lxi	d,DUBLST	;Double
DENS1:	call	CRPRNT

	lxi	d,DENSST	;density
	jmp	PRINTF		;print value & ret

;******=========================
;* M= * Set Max Track Number
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
;  MAXTRK updated
;===============================
FSETM:	lxi	b,99		;default & max
	call	GETDEC
	mov	a,b
	ora	a		;0 not allowed
	jz	CMDERR

	sta	MAXTRK

;Fall into PMAXTK

;===Subroutine=================
;Print current max track number
;==============================
PMAXTK:	lxi	d,MAXTST	;Max Track:
	call	CRPRNT

	lda	MAXTRK
	jmp	PDEC8

;******===========================
;* N= * Set number of Read Retries
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
;  RTRIES updated
;=================================
FSETN:	lxi	b,99		;default & max
	call	GETDEC

	inr	a		;tries=retries+1
	sta	RTRIES

;Fall into PRETRY

;===Subroutine===========
;Print selected Step Rate
;========================
PRETRY:	lxi	d,RETRST	;Retries:
	call	CRPRNT

	lda	RTRIES
	dcr	a		;retries=tries-1
	jmp	PDEC8

;******=========================
;* R= * Set Step Rate
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
;  STPRAT updated
;===============================
FSETR:	lxi	b,3		;default & max
	call	GETDEC
	sta	STPRAT

;Fall into PSTPRT

;===Subroutine===========
;Print selected Step Rate
;========================
PSTPRT:	lxi	d,STRST		;Step Rate:
	call	CRPRNT

	lda	STPRAT
	lxi	h,STPTAB	;look up actual rate

	mov	e,a
	mvi	d,0
	dad	d

	mov	b,m		;get step rate

	in	STAT2		;set up for Minidisk?
	ani	BMINI		;NZ means minidisk
	mov	a,b
	jz	PSTR1
	rlc			;*2 for minidisks
PSTR1:

	call	PDEC8
	lxi	d,MSST
	jmp	PRINTF		;mS

;******==============================
;* S= * Set Disk Side
;******
;On Entry:
;  hl=address of parameter value
;On exit:
;  SCTRL2 BSIDE0 bit =0 for drive 1,
;  1 for drive 0 (for CTRL2)
;  Side set in CTRL2
;====================================
FSETS:	call	GET01
	mvi	b,BCSIDE0	;assume side 0
	dcr	a		;was it a 1?
	jnz	FSETS1
	mov	b,a		;then it's side 1

FSETS1:	lda	SCTRL2		;combine w/ other SCTRL2 bits
	ani	BCSIDE0 xor 0FFh
	ora	b
	out	CTRL2		;set side
	sta	SCTRL2		;remember side value
	
;Fall into PSIDE

;===Subroutine======
;Print selected side
;Trashes psw,bc,de
;===================
PSIDE:	lxi	d,SIDEST	;Side:
	call	CRPRNT

	lda	SCTRL2		;active-low bit
	ani	BCSIDE0
	mvi	a,'0'
	jnz	PSIDE0
	inr	a		;"1"

PSIDE0:	jmp	PRINTA	

;******=====================================
;* T= * Select Drive Type
;****** (Note: The controller indicates
;       if a disk is a Minidisk or not.)
;On Entry:
;  hl=address of parameter value
;    (hl)='S' for Stepper motor disk
;    (hl)='V' for 8" Voicecoil (PerSci) disk
;On Exit:
; SCTRL2 updated
; DTYPE updated
; SCTRL1 updated
; MAXTRK updated
;===========================================
FSETT:	call	GETASC		;get 1 ASCII param
	lxi	h,0		;h=SCTRL2 value, l=type
	cpi	'V'
	jz	SETT1

	lxi	h,BCFSEKN*256+1	;slow seek
	cpi	'S'
	jz	SETT1
	jnz	CMDERR
SETT1:

	mov	a,l
	sta	DTYPE		;drive type
	lda	SCTRL2
	ani	BCFSEKN xor 0FFh
	ora	h
	sta	SCTRL2
	

;Fall into PDTYP

;===Subroutine=====================
; rint Drive Type, Max Track, Rates
;==================================
PDTYP:	lxi	d,DTYPST	;Disk type: 
	call	CRPRNT

	lda	DTYPE
	lxi	d,VCST		;Voicecoil
	ani	7Fh		;msb is force bit
	push	psw		;Z set if voicecoil
	jz	PDTYP1
	lxi	d,STPRST	;stepper
PDTYP1:	call	PRINTF

	lxi	d,MAXIST
	in	STAT2		;what kind of drive?
	ani	BMINI		;0 means 8"
	jz	PDTYP2
	lxi	d,MINIST
PDTYP2:	call	PRINTF

	pop	psw		;Z set if voicecoil
	cnz	PSTPRT		;and step rate

	call	PMAXTK		;max track
	call	PDENS		;density
	jmp	PDRATE		;data rate

;******============================
;* U= * Set Unit Number {A-D}
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
;  SCTRL1 drive select bits updated
;  The drive has been restored to 0
;==================================
FSETU:	call	GETASC		;get ASCII param

	sui	'A'		;Adjust to 0
	cpi	4		;max drive letter is D
	jnc	CMDERR		;illegal value

	mov	b,a		;make drive select command
	mvi	a,80h		;start bit out at top

SULOOP:	rlc			;shift bit into place
	dcr	b
	jp	SULOOP

	mov	b,a		;b has drive bits
	lxi	h,SCTRL1
	mov	a,m		;previous CTRL1 value
	ani	DCDSMSK xor 0FFh ;remove prior unit number
	ora	b		;insert new unit number
	mov	m,a		;new CTRL1 value

	call	PCRLF		;for announcement below		

	call	FRCST		;temp force stepping
	call	RESTOR		;select and restore

;Now that we are on track 0, we can detect mini versus 8" disk,
;update the DCMAXI bit in the CTRL1 register, and print what
;we found. For 8" disks, we can detect and tell sidedness too.

	lxi	d,MINIST	;'Minidisk'
	in	STAT2
	ani	BTRK0		;0 means minidisk
	mov	b,a		;disk size bit for SCTRL1
	cz	PRINTF		;returns with Z set

;It's an 8" disk. Check & report its sidedness

	cnz	PSIDED		;also sets b=DCMAXI

	lda	SCTRL1		;update controller
	ani	DCMAXI XOR 0FFh	;clear bit
	ora	b		;put new bit in place
	sta	SCTRL1

;Finish announcing what sort of drive we found

	lxi	d,DETST		;' detected'
	call	PRINTF

;Fall into PUNIT

;===Subroutine======
;Print selected unit
;===================
PUNIT:	lxi	d,UNITST	;Unit:
	call	CRPRNT

;Fall into PRDRIV

;===Subroutine=================
;Print the current drive letter
;Trashes psw,c,de
;==============================
PRDRIV:	lda	SCTRL1
	ani	DCDSMSK		;just get drive bits
	lxi	d,NONEST
	jz	PRINTF		;print none if none

	mvi	c,'A'-1

PU1:	inr	c		;loop to create A-D
	rrc
	jnc	PU1

	mov	a,c		;A='A'-'D'
	jmp	PRINTA

;******=========================
;* V= * Set Verbose Mode
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
;  VERBMO updated
;===============================
FSETV:	call	GET01
	sta	VERBMO

;Fall into PVERB

;===Subroutine=====
;Print Verbose Mode
;==================
PVERB:	lxi	d,VERBST	;Verbose
	call	CRPRNT

	lda	VERBMO
	lxi	d,OFFST		;off
	ora	a		;0 means disabled
	jz	PVERB1
	lxi	d,ONST		;on

PVERB1:	jmp	PRINTF

;******===========================
;* Z= * Set Z80 Speed {2/4}
;******
;On Entry:
;  hl=address of parameter value
;On Exit:
;  ZSPEED= 0 or 2 for 2MHz or 4MHz
;=================================
FSETZ:	lxi	b,4*256+4	;default & max
	call	GETDEC
	sui	2		;should be 0 or 2
	jc	CMDERR		;was it 0 or 1?
	cpi	1		;was it 3?
	jz	CMDERR

	sta	ZSPEED

;Fall into PSPEED

;===Subroutine===
;Print Z80 Speed
;================
PSPEED:	lxi	d,ZSST		;Z80 speed
	call	CRPRNT

	lda	ZSPEED
	adi	'2'
	call	PRINTA
	
	lxi	d,MHZST		;mhz
	jmp	PRINTF

;******=========================================
;* RE * Restore to track 0
;******
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FRESTR:	jnz	CMDERR		;huh?

	call	RESTOR
	jmp	OPTRAK		;print track no. and ret

;*****==============
;* ? *  Help Command
;*****==============
FHELP:	lxi	d,HLPMSG
	jmp	PRMAIN

;===================================
;Bogus command return (resets stack)
;===================================
CMDERR:	lxi	d,HUHST		;Huh?
	jmp	PRMAIN		;print msg & go to main

;******=========================================
;* EJ * Eject disk
;******
;Select the correct drive and side, and then
;Send 1-sec eject pulse per PerSci 277 spec
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FEJECT:	jnz	CMDERR		;huh?

	lda	SCTRL1		;select the drive
	out	CTRL1		;maybe been motor-off

	lxi	h,SCTRL2
	mov	a,m		;select the side
	ori	BCEJECT		;combine eject command
	out	CTRL2		;eject now

	call	STALL1		;stall for 1 sec

	mov	a,m		;select the side
	out	CTRL2		;end eject pulse

	lxi	d,EJST		;Ejected
	jmp	CRPRNT

;******=========================================
;* WC * Write Pattern <H1> [...<Hn>]
;******
;Continuously write a complete track of the given
;pattern. There are no sectors - this is not a
;formatted track. This function is for tuning the
;write channel, etc.
;On Entry:
;  Z set if end of command line already
;  Unit, side, track, density are all set
;  hl=address of 1st character after the command
;===============================================
FWCPAT:	jz	CMDERR		;must be at least 1
	mvi	a,1		;continuous

	jmp	DOPAT	

;******=========================================
;* 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 command line already
;  Unit, side, track, density are all set
;  hl=address of 1st character after the command
;===============================================
FWPAT:	jz	CMDERR		;must be at least 1
	xra	a		;just once

;Fall into DOPAT

;===============================================
;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:
;  a=0 for one pass, nz for continuous
;  Unit, side, track, density are all set
;  hl=address of 1st character after the command
;===============================================
DOPAT:	push	psw		;remember continuousness

;Get pattern values into COMBUF. Pattern values betwen
;F5 and FE are not allowed, as they will kick the 1793
;into one of its modes.

	lxi	d,COMBUF	;reuse buffer
	mvi	b,0FFh		;count goes 1 extra

GEPLUP: inr	b		;bump pattern count
	push	b

	push	d
	call	GETHEX		;val in de, cnt in b

	mov	a,b		;too many digits?
	cpi	3
	jnc	CMDERR		;yes:abort
	mov	a,e		;get low byte
	pop	d

	stax	d		;save byte in list
	inx	d		;bump pointer

	push	d
	lxi	d,BADPST	;can't be F5-FE
	inr	a		;must be below F6 now
	cpi	0F6h
	jnc	PRMAIN
	pop	d

	mov	a,b		;hex digit count
	pop	b		;recover pattern count

	ora	a		;0 digits last time?
	jnz	GEPLUP		;no:keep looking

;fill track buffer with the pattern. This may overfill by
;as much as (COMBUF-4)/2 bytes, but buffer is big enough

	mov	a,b		;pattern count

	lxi	b,TBSIZE	;bytes to write
	lxi	h,TRKBUF	;dest

FILLP:	push	psw		;save pattern count
	lxi	d,COMBUF	;source

FILLP1:	push	psw		;pattern count
	ldax	d
	mov	m,a
	pop	psw		;pattern count
	inx	h
	inx	d
	dcx	b

	dcr	a		;next pattern byte
	jnz	FILLP1

	mov	a,b		;done?
	ora	c		;msb means underflow
	jz	FLDON

	pop	psw		;pattern count
	jmp	FILLP

FLDON:				;pat. count on stack

;Print the relevent settings, and confirm with user

	lxi	d,WPATST	;write pattern
	call	CRPRNT
	call	PUNIT		;unit
	call	PTRACK		;current track
	call	PSIDE		;side
	call	PDENS		;density
	lxi	d,PLENST	;pattern length
	call	CRPRNT
	pop	psw		;pattern count
	call	PDEC8

	lxi	d,ASKWST	;warning...
	call	ASKYN		;ask Y or N
	
;Prepare to write the track with pattern

	pop	psw		;continouos?
	mov	c,a

	lxi	d,QTOQST
	ora	a		;message if so
	cnz	CRPRNT

WTAGIN:	call	PREPD		;get ready to write

	mvi	b,DFSEOJ	;test mask
	lxi	sp,TRKBUF	;sp is our data pointer now

	mvi	a,CCWTRAC	;kick off 1793 write
	out	DCMMD

	jmp	WTLENT

; b = STAT1 mask to check for command done
; c = 0 if one pass, nz if continuous
; 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 and asserting its interrupt). This may write up
;to 3 bytes after the 1793 stops requesting data, which will
;be ignored by the 1793.

;Spec: Worst-case out-to-out time is 16 uS, or 32 2 MHz cycles
;This loop worst case out-to-out= 29 Z80 cycles/29 8080 cycles
;(Z80 cycles in parens)[8080 cycles in brackets]

WTLOOP:	mov	a,h		;(4)[5] write byte 4
	out	DDATA		;(11)[10]

WTLENT:	pop	d		;(10)[10]get bytes 1 & 2

	mov	a,e		;(4)[5] write byte 1
	out	DDATA		;(11)[10]

	pop	h		;(10)[10]get bytes 3 & 4

	mov	a,d		;(4)[5] write byte 2
	out	DDATA		;(11)[10]

	in	STAT1		;(11)[10]DRQ ?
	ana	b		;(4)[4] done?

	mov	a,l		;(4)[5] write byte 3
	out	DDATA		;(11)[10]

	jz	WTLOOP		;(10)[10]

;Done writing. Clean up and report.

	lxi	sp,STACK	;need a stack

;wait for completion, check for abort

WTEOJ:	call	TABORT		;abort?
	cpi	'Q'		;Q quits too
	jz	WTOK

	in	DSTAT		;wait for busy to end
	mov	b,a		;for remember
	ani	SBUSY
	jnz	WTEOJ

	lxi	d,PROTST	;write protected
	mov	a,b
	ani	SWPROT
	jnz	WTDONE		;quit if protected

	lxi	d,WFLTST	;write fault
	mov	a,b
	ani	SWFALT+SLOSTD
	jnz	WTDONE		;error: done

	ora	c		;Z means one pass
	jnz	WTAGIN		;Keep going?

WTOK:	lxi	d,DONEST	;no errors
WTDONE:	jmp	PRMAIN		;repairs stack too

;******================================
;* MO *  Motor Off/On 
;******  toggle with speed check
;On Entry:
;  Z set if end of command line already
;On Exit:
;  SCTRL1 is updated
;======================================
FMOTOR:	jnz	CMDERR		;no value: toggle

;------------------------------------------------------
;Toggle the motor on/off each time a space is typed.
;When the motor is on and the unit is ready, measure &
;print the spindle speed. Quit when the user types ESC.
;------------------------------------------------------
	call	NULSEK		;expose INDEX
	mvi	b,0		;don't toggle the 1st time

MOT2:	lda	SCTRL1		;get SCTRL1 w/ motor
	xra	b		;space: toggle it
	call	DOCTL1		;...now

	call	PMOTOR		;report motor state

MOT3:	call	IMEASR		;measure spindle motor
	call	TABORT		;Abort from user?

	cpi	'Q'		;Q quits too
	jz	UCQUIT

	cpi	' '		;space to advance?
	jnz	MOT3		;n: keep waiting

	mvi	b,DCMOTO	;motor bit
	jmp	MOT2		;y: toggle motor

;******=========================================
;* ST * Step <trk1> [...<trkn>]
;******
;Step to or between given tracks.
;Same as the SK command,except the 1793 fast-
;seek mode is disabled.
;Note: head must be loaded to step, since
;head loads only when drive is selected.
;On Entry:
;  Z set if end of command line already
;  hl=address of 1st character after the command
;===============================================
FSTEP:	call	SELECT		;select specified unit

	push	h
	call	FRCST		;temp force stepping
	pop	h

	mov	a,m		;end of command line?
	ora	a		;set Z if so

;Fall into FSEEK

;******===============================================
;* SK * Seek <trk1> [...trkn]
;******
;Seek sequentially through the list of specified
;tracks, repeating the list until the user types ESC.
;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.
;Allow immediate commands (+,-,>,<,A,H) during seeks.
;On Entry:
;  Z set if end of command line already
;  DTYPE = 0 for fast-seeks, <>0 for steps
;  hl=address of 1st character after the command
;=====================================================

;Get complete list of target tracks, convert
;them to binary, and store them in PATBUF.

FSEEK:	jz	FSEEK2		;reuse prior pattern?

	lxi	d,PATBUF+1	;pattern buffer
	mvi	c,0FFh		;value cnt, goes 1 extra

GELUP:	inr	c
	push	b		;save count

	lda	MAXTRK		;max value
	mov	c,a		;...for GETDEC
	call	GETDEC		;get a track number
				;Z set if done
	stax	d		;save in list
	pop	b		;count

	inx	d		;bump pointer
	jnz	GELUP		;keep looking

	mov	a,c		;save pattern count in buffer
	sta	PATBUF		;..first location

FSEEK2:	call	SELECT		;select specified unit

;If more than 1 track, then loop through the list until
;quit by ESC. If just 1 track, then seek that track and
;quit. If 0 target tracks, then seek track 0 and quit.

SEKAGN:	lxi	h,PATBUF	;point to track list
	mov	a,m		;first byte is pattern count
	cpi	2		;0 or 1 tracks?
	jc	SEEK1		;yes: seek just 1 track

;Seek tracks listed in PATBUF
	mov	b,m		;first byte is pattern count

SEKLUP:	inx	h
	mov	a,m		;get track number

	push	h
	push	b

	call	SEEK		;returns time in c,hl
	call	OPSTTK		;print sk speed & track

;Wait for user input only if in manual mode. Allow
;immediate commands while waiting. 'A' toggles
;between automatic and manual modes.

SUWAIT:	call	TABORT		;user trying to quit?

	cpi	'Q'		;Q quits too
	jz	UCQUIT

	cpi	'H'		;user toggling head load?
	cz	TOGHD		;returns a=0

	mov	c,a		;save user input

	lxi	h,OPMODE

	cpi	'A'		;toggle auto/manual mode?
	jnz	SUWA1
	mvi	a,OPMANM	;toggle OPMODE
	xra	m
	mov	m,a
SUWA1:
	mov	a,m		;OPMODE
	ani	OPMANM		;0 means auto
	jz	SKNUI		;y: all done with user

SUWA2:
	mov	a,c		;recover user input
	cpi	' '		;space to advance?
	push	psw		;save result

	call	CKIMED		;check for immediate command
				;trashes lots of regs
	
	lda	SHDLD		;head loaded?
	ora	a
	jz	SUWA4		;n:no need to nudge it

	in	DTRCK		;get current track
	out	DDATA

	mvi	a,CCSEEK	;y: Keep head loaded
	out	DCMMD		;issue WD1793 command

SUWA3:	in	STAT1		;wait 'till done
	ani	DFSEOJ		;done?
	jz	SUWA3

SUWA4:	pop	psw		;Z set if user typed space
	jnz	SUWAIT		;n: keep waiting

SKNUI:	pop	b
	pop	h
	dcr	b
	jnz	SEKLUP		;loop thru b patterns

	jmp	SEKAGN		;restart sequence

; Just seek one track, report the track, and quit

SEEK1:	inx	h
	mov	a,m		;get track number

	call	SEEK
	jmp	OPSTTK

;===Subroutine=====================================================
;seek target track, and measure time until the seek to completes
;Timeout after ~12 Sec
;On Entry
;  a=target track
;  STPRAT=step rate for non-PerSci drives
;  SCTRL1 is valid, with head-load bit set
;  SCTRL2=selected side & seek type
;  DTYPE=0 for PerSci-type drives (that can fast-seek)
;  DTYPE<>0 for Shugart-type drives (that can't fast-seek)
;  The WD1793's track register is current
;On Exit:
;  c,hl=seek time in 0.01 mS units
;  The head is loaded if the seek is a voicecoil seek
;Trashes psw,bc
;
;Note: The CCS2422 controller cannot read the Seek Complete signal
;from the PerSci disk drive directly. The Seek Complete signal is
;only connected to the reset input of the HLT 1-shot (U3b).
;Normally, this 1-shot is triggered by the leading edge of HLD,
;which in turn is controlled by the "h" option in the WD1793's
;Type I commands, and controls the loading of the disk drive heads.
;When the 1-shot times out (after about 35 mS), it sets the HLT
;input, which terminates a Type I command. However, when Fast Seek
;mode is enabled (by pulling the WD1793's -TEST input low,
;controlled by BCFSEKN in CTRL2), then HLT is the WD1793's Seek
;Complete input. Western Digital suggests driving the HLT input
;with the logical AND of HLD and SEEK COMPLETE (which would work
;only in a system that has only voice-coil drives).
;
;The way CCS built the 2422, the WD2793 will see a bogus Seek-
;Complete response for longer seeks, since the 1-shot will time out
;after about 35 mS, while the longest seek will be around 100 mS.
;This means software must compensate. The only way software can
;observe the state of the Seek Complete signal after the WD1793
;thinks the seek is complete is by retriggering the HLT 1-shot,
;and then looking at the HEAD LOADED signal in the WD1793 status
;register. (This bit is the logical AND of the HLD and HLT
;signals.) Retriggering the 1-shot requires writing a null-seek
;command (to the track the head is already on) with the "h" bit
;low, and then again with the "h" bit high, thus producing a
;rising edge to the 1-shot, and causing it to retrigger. (Note that
;Western Digital's WD79x application note from November, 1980
;specifies a 28 mS (worst case) delay from writing to the command
;register and reading from the status regiater.) If the Seek
;Complete signal from the disk drive is in fact active, then the
;1-shot will not trigger, and the HEAD LOADED bit will be true
;immediately. If Seek Complete is still inactive, then the HEAD-
;LOADED bit will be false for a while after the null-seek command.
;
;We put the WD2793 in Double Density mode here, to reduce the
;time from when a command is written to the command register and
;when the status register may be read (per the restriction in the
;Western Digital 179X Application Notes, dated November, 1980).
;In 8" and Double Density modes, the minimum time is 14 uS, or
;56 cycles of a 4 MHz Z-80 CPU.
;
;Numbers below in (parenthesis) are Z-80 cycle counts for the
;instructions. Numbers in [square brackets] are 8080 cycle counts.
;The timing loops are deliberately written to use the same total
;number of cycles for both CPU types.
;==================================================================
SEEK:	out	DDATA		;write target track to WD1793

	lda	SCTRL1		;select the drive
	ori	DCDDEN		;double-density mode for faster
	out	CTRL1		;..response from status reg

	lda	SCTRL2		;enable fast-seek mode if needed
	out	CTRL2		;and select side

;Set up constants and the timer

	push	d
	lxi	d,1		;16-bit 1 for DAD'ing

	lxi	h,2		;c,hl = 00,0002h
	mov	c,h		;upper counter byte=0

;Set the step rate per the user setting for Shugart-type drives,
;or set it to the max rate and select the WD1793's fast-seek
;mode for PerSci-type drives. (Remember that fast-seek mode on
;the WD1793 cranks the step rates up.) Note the FRCSTP bit in
;DTYPE will force a voicecoil drive to step. If the seek will
;be a voicecoil seek, then force the head loaded. Otherwise,
;set the head load according to SHDLD.

	lda	DTYPE		;can we fast-seek?
	ora	a		;0 means yes
	mvi	a,HDLOAD	;head loaded for voicecoil
	jz	SKSLO

	lda	STPRAT		;current step rate
SKSLO:	ori	SEEKCM		;combine step rate with Seek
	mov	b,a

	lda	SHDLD		;and combine with head load
	ora	b		;Combined seek command
	out	DCMMD		;kick off WD1793 seek

;Wait for 1793 to finish seek command. This loop takes
;exactly 80 Z80 or 8080 cycles per pass. This loop will
;timeout when c reaches its max.

SEK0:	xra	a		;(4)[4]
	dad	d		;(11)[10] de=1
	adc	c		;(4)[4] catch carry
	mov	c,a		;(4)[5]

	cpi	SEEKTO*4/5	;(7)[7]timeout?
	jz	SEKTO		;(10)[10]yes: abort

	mov	a,c		;(4)[5]stall
	xra	a		;(4)[4]stall
	xra	a		;(4)[4]stall

	in	STAT1		;(11)[10]wait 'till done
	ani	DFSEOJ		;(7)[7]done?
	jz	SEK0		;(10)[10]

;Done seeking if no voicecoil.
;160 cycles in-to-in exactly. (This is why we started the
;24-bit counter with 00,0002h instead of just 0.)

	lda	DTYPE		;(13)[13]fast-seeking?
	ora	a		;(4)[4]0 means yes
	jnz	SEKDON		;(10)[10]no:done

	xra	a		;(4)[4]stall
	call	SEKRET		;(27)[27]stall
	call	SEKRET		;(27)[27]stall

;Wait for the WD1793 HLT input to go high, which either
;means the HLT 1-shot expired or the drive asserted
;Seek Complete.	This loop takes exactly 80 cycles per
;pass. This loop will timeout when c reaches its max.

SEK1:	mov	a,d		;(4)[5] d=0
	dad	d		;(11)[10] hl=hl+1 (de=1)
	adc	c		;(4)[4] catch carry
	mov	c,a		;(4)[5]

	cpi	SEEKTO*4/5	;(7)[7]timeout?
	jz	SEKTO		;(10)[10]yes: abort

	mvi	a,0		;(7)[7]stall

	in	STAT1		;(11)[10]wait 'till done
	ani	DFSEOJ		;(7)[7]done?

	mvi	a,SEEKCM	;(7)[7]with HLD cleared
	jz	SEK1		;(10)[10]

;Wait for Seek Complete, by nudging the HLT 1-shot (with a pulse
;that's long enough for the 1793 to complete, but short enough
;that the head doesn't actually unload), and then looking at the
;HEAD LOADED bit. We are in 8", DD mode, so we must wait at least
;14 uS (56 cycles) between writing to DCMMD and reading from
;DSTAT. This loop will timeout when c reaches its max. This loop
;takes exactly 160 cycles per pass, and the HLD signal will be
;low for 25 cycles per pass (6.25 uS at 4MHz).
;Note: This forces the head to be loaded during seeks.

SEK2:	out	DCMMD		;(11)[10]nudge HLT 1-shot
	mvi	a,0		;(7)[7]stall
	mvi	b,CCSEEK	;(7)[7]sets HLD
	mov	a,b		;(4)[5]CCSEEK
	out	DCMMD		;(11)[10]un-nudge HLT 1-shot

	mvi	a,0		;(7)[7]stall
	call	SEKRET		;(27)[27]stall

;add 2 to 24-bit timer in c,hl (160 uS per pass here)

	mvi	e,2		;(7)[7]
	mov	a,d		;(4)[5] a,de=00,0002h
	dad	d		;(11)[10]  hl=hl+2 (de=2)
	adc	c		;(4)[4] catch carry
	mov	c,a		;(4)[5]

	cpi	SEEKTO*4/5	;(7)[7]timeout?
	jz	SEKTO		;(10)[10]yes: abort

	mvi	e,SEEKCM	;(7)[7]set up to clear HLD

	in	DSTAT 		;(11)[10]
	ani	SHEADL		;(7)[7]look at SEEK COMPLETE
	mov	a,e		;(4)[5]
	jz	SEK2		;(10)[10]

;Convert 24-bit value in c,hl into units of 10 uS, including
;compensating for CPU speed (as set by the user). Note that
;the above timing loops will measure correctly for either a
;Z-80 or an 8080 CPU.

SEKDON:	dad	h		;adjust to 10 uS/count
	mov	a,c
	ral			;msd too
	mov	c,a

;double result if 2 MHz CPU

	lda	ZSPEED
	ora	a
	jnz	SEK3

	dad	h
	mov	a,c
	ral
	mov	c,a
SEK3:

;Clean up and return from subroutine

	lda	SCTRL1		;Put density back how it was
	out	CTRL1		;and select side

	pop	d

SEKRET:	ret			;(10)[10]normal return
				;(Also used for delay)

;Seek timeout. Print timeout message based on whether or not
;the drive type is set for voicecoil drive, and then abort

SEKTO:	lxi	d,VSTOST
	lda	DTYPE		;was it a voicecoil seek?
	ora	a
	jz	SEKTO1		;y: say so

	lxi	d,SKTOST

SEKTO1:	jmp	PRMAIN		;abort: PRMAIN fixes stack

;===Subroutine==================
;Get track ID
;On Exit:
; (COMBUF)=track number
; (COMBUF+1)=Side number
; (COMBUF+2)=sector number found
; (COMBUF+3)=Sector length (0-3)
; b=calculated sectors/track
; l=calculated bytes/sector/4
; h=0
; DCDEN bit in SCTRL1 set/reset
;    according to track density
; Rude abort if error reading ID
;Trashes psw,c,de
;===============================
TRAKID:	lda	RTRIES		;how many retries?
	rlc			;*2 for density
	mov	b,a		;retry count

TKID0:	call	PREPD		;set up for R/W

	mvi	a,CCRDADD
	lxi	h,6		;6-byte ID
	lxi	d,COMBUF
	call	READD		;read the ID

	lxi	h,SCTRL1

	mov	a,c		;final status
	ani	SRNFER+SCRCER+SLOSTD
	jz	TKIDGD		;good read?

;retry on error with other density

	mov	a,m
	xri	DCDDEN		;try other density
	mov	m,a	

	dcr	b
	jnz	TKID0		;retry if b<>1

	lxi	d,IDERST	;error reading ID
	jmp	PRMAIN		;print exit

;Good read. Compute retries remaining in C

TKIDGD:	mov	a,b		;how many tries?
	inr	a		;cy clear from above
	rar			;account for density
	mov	c,a		;remaining retries

; Compute sectors/track
	
	lda	COMBUF+3	;BPS from ID
	mov	b,a		;B=BPS value

	mov	a,m		;SCTRL1
	ani	DCMAXI		;compute disk size offset

	rrc			;put DCMAXI into bit 2
	rrc
	add	b		;offset based on DCMAXI
	mov	e,a		;de becomes offset
	mvi	d,0

	mov	a,m		;SCTRL1
	ani	DCDDEN		;offset based on density
	jnz	TKID1
	inx	d		;bump if SD

TKID1:	lxi	h,SPTTAB	;table beginning
	dad	d		;offset into table
	mov	h,m		;h=SPT

;compute hl= BPS/4
;b=bps (0-3)

	mvi	a,16

TKBPSL:	add	a
	dcr	b
	jp	TKBPSL

	mov	l,a		;l=BPS/4
	mov	b,h		;b=SPT
	mvi	h,0		;hl=BPS/4

	lxi	d,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 psw,c,de
;=======================================
PRTRYS:	lda	RTRIES		;start value	
	sub	c		;a=number of retries
	rz			;don't print if no retries

	push	h
	push	b
	push	psw		;save retry count
	call	CRPRNT		;retry message
	lxi	d,REQST		;required
	call	PRINTF

	pop	psw		;calculated retries
	call	PDEC8
	pop	b
	pop	h
	lxi	d,RTRYST	;retries
	jmp	PRINTF

;===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 SCTRL1
;  a=sector number
;  b=2nd command line param, if any
;  hl=number of bytes to transfer/4
;  WDDM = A0DDM iff 'D' followed sector number
; Abort if track mismatch
;Trashes c,de
;============================================= 
RWSTUP:	lxi	b,99		;default, max
	call	GETDEC		;get sector number
	jz	CMDERR		;error if no value

	push	psw		;save desired sector

;Check if DDM is to be written

	call	GETASC
	jz	RWSNOD		;no deleted data mark
	cpi	'D'		;deleted data mark req?
	jnz	CMDERR

	mvi	a,A0DDM		;deleted data mark
	
RWSNOD:	sta	WDDM

	call	TRAKID		;get hl=BPS/4, set density
				;this overwrites DSCTR

	lda	COMBUF		;Track number from ID
	mov	b,a
	in	DTRCK		;which track?
	lxi	d,TKMMST	;track mismatch
	cmp	b
	jnz	PRMAIN		;abort if mismatch

	pop	psw		;requested sector
	out	DSCTR		;set 1793 sector

	ret

;===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:
; drive is ready
; Auto-wait is set
;Trashes psw
;======================================
PREPD:	push	h
	push	d
	lxi	h,SCTRL1
	mov	a,m
	ori	DCMOTO		;motor on
	mov	m,a

	push	b
	call	SELECT		;select the drive
	call	STALL1		;Spindle motor start
	pop	b

	lxi	d,CBSYST	;1793 busy
	in	DSTAT
	ani	SBUSY
PDERR:	jnz	PRMAIN		;quit if busy

	lxi	d,NRDYST	;not ready
	in	DSTAT
	ani	SNORDY
	jnz	PDERR		;quit if not ready

	in	DDATA		;flush 1793

	mov	a,m		;SCTRL1 value
	ori	DCAUTO		;set up auto-wait
	out	CTRL1
	
	pop	d
	pop	h
	ret

;===Subroutine===========================
;Select the drive and Restore to track 0
;Always use 1793 restore command, since
;the CCS 2422 does not support the PerSci
;voicecoil restore signal.
;On Entry:
;  SHDLD is valid
;On Exit:
;  Leaves unit selected
;  Rude exit if no unit selected
;Trashes psw,c,de,hl
;========================================
RESTOR:	call	SELECT		;select the drive
	lda	SHDLD		;don't change head load state
	ori	CCRSTOR		;restore slowly

;Fall into DSKCMD

;===Subroutine=========================
;Send command to 1793 & 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		;issue WD1793 command

;Fall into EOJ

;===Subroutine==================
;Wait untill WD1793 command done
;Rude abort if user quits
;On Exit:
;  c=Status after command
;  Z flag set
;Trashes a
;===============================
EOJ:	call	TABORT		;user abort? (>56 uS)

	in	DSTAT		;wait for busy to end
	mov	c,a		;for ret
	ani	SBUSY
	jnz	EOJ

	ret

;===Subroutine============================
;Select the drive specified in SCTRL1, and
;test if any drive is actually selected.
;Print message and rudely exit if not.
;Trashes psw,de
;=========================================
SELECT:	lda	SCTRL1		;select the drive
	out	CTRL1

	ani	DCDSMSK		;any drive selected?
	lxi	d,CANTST	;no drive selected
	jz	PRMAIN		;rude abort

	ret

;===Subroutine==================================
;Rewrite CTRL1 from SCTRL1 and SHDLD
;On Exit:
; CTRL1=SCTRL1 if heads loaded
; CTRL1=SCTRL1 AND not DCDSMSK if heads unloaded
;Trashes psw,c
;===============================================
RECTL1:	lda	SCTRL1

;Fall into DOCTL1

;===Subroutine=======================================
;Write to CTRL1, taking into account head-load state,
;since minidisk heads are only unloaded by
;deselecting the unit(s)
;On Entry:
;  a=new SCTRL1 value
;  SHDLD is valid
;  DTYPE is valid
;On Exit:
; SCTRL1=updated
; CTRL1=SCTRL1 if heads loaded or 8" disk
; CTRL1=SCTRL1 AND DCDSMSK if minidisk heads unloaded
;Trashes psw,c
;====================================================
DOCTL1:	sta	SCTRL1		;save state with unit
	mov	c,a
	lda	SHDLD		;heads loaded?
	ora	a		;load or unload?
	jnz	DOD1		;loaded: jump

	in	STAT2		;Minidisk?
	ani	BMINI		;Y: no headload pin
	jz	DOD1		;8" disk: just unload

	mov	a,c	
	ani	DCDSMSK xor 0FFh ;deselect to unload
	mov	c,a

DOD1:	mov	a,c
	out	CTRL1		;load/unload
	ret

;===Subroutine==================
;Read sector with retries
;On Entry:
;  de=destination address
;  l=byte count/4
;  h=0
;  (RETRIES)=number of retries
;On Exit:
;  b=number of retries remaining
;  c=final status
;  Z set if success
;===============================
READS:	lda	RTRIES
	mov	b,a		;B counts tries

RDRTRY:	call	PREPD		;autowait, etc.
	push	h		;save byte count
	push	d		;save dest address

	lda	SCTRL2		;Get control register value
	ani	BCSIDE0		;just the (inverted) side bit
	rrc
	rrc
	rrc
	xri	CCRDSEC+F2SSF	;1793 command, inverted side bit

	call	READD		;read the sector
	pop	d		;dest address
	pop	h		;l=bps/4

	mov	a,c		;Final status
	ani	SLOSTD+SCRCER+SRNFER
	rz			;success return

	dcr	b
	jnz	RDRTRY		;retry if we can

	ora	a		;clear Z for error
	ret

;===Subroutine========================================
;Read l*4 bytes from disk to memory at de
;30 z80 cycles/27 8080 cycles between IN's, worst case
;On Entry:
;  a=Disk Command (read or read ID)
;  de=target address
;  l=byte count/4
;  Disk is already selected and setup
;On Exit:
;  c=final status
;Trashes psw,de,hl
;(Z80 cycles in parens)[8080 cycles in brackets]
;=====================================================
READD:	dcx	d		;undo initial inx d
	out	DCMMD		;WD1793 command

READDL:	in	DDATA		;(11)[10]disk byte 1

	inx	d		;(6)[5]buffer byte 1
	stax	d		;(7)[7]save byte 1
	inx	d		;(6)[5]buffer byte 2
	in	DDATA		;(11)[10]disk byte 2

	stax	d		;(7)[7]save byte 2
	inx	d		;(6)[5]buffer byte 3
	dcr	l		;(4)[5]set Z when 0
	in	DDATA		;(11)[10]disk byte 3

	stax	d		;(7)[7]save byte 3
	inx	d		;(6)[5]buffer byte 4
	in	DDATA		;(11)[10]disk byte 4

	stax	d		;(7)[7]save byte 4
	jnz	READDL		;(10)[10]more quads?

	jmp	EOJ		;get final status

;===Subroutine==================
;Seek the current track, so that
;index,track 0, etc. can be read
;On Entry:
;  SHDLD is valid
;On Exit:
;  c = status from command
;Trashes psw,b,de
;===============================
NULSEK:	call	SELECT		;select the drive
				;abort if none selected

	call	STALL1		;spindle motor start time

	in	DTRCK		;set up for null seek
	out	DDATA

	lda	SHDLD		;don't change head
	ori	SEEKCM

	jmp	DSKCMD		;ret from there

;===Subroutine===================
;Stall for 1 second
;Same timing for 8080 and Z80 CPU
;Trashes psw,de
;================================
STALL1:	lxi	d,456		;2MHz cycles

	lda	ZSPEED
	ora	a
	jz	STALUP

	xchg			;double time for 4 MHz
	dad	h
	xchg

STALUP:	xra	a		;(4)[4]

STALP1:	sui	1		;(7)[7] 17*256=4352 cycles
	jnz	STALP1		;(10)[10]

	dcx	d		;(6)[5]
	mov	a,d		;(4)[5]
	ora	e		;(4)[4]
	jnz	STALUP		;(10)[10] (4+4352+24=4380 cycles/pass)

	ret

;===Subroutine=======================================
;Measure spindle motor speed by looking at 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 psw,b,de
;====================================================
IMEASR:	lxi	d,SCTRL1
	ldax	d		;skip speed test
	ani	DCMOTO		;...if motor is off
	rz

	ldax	d		;nudge motor to
	out	CTRL1		;...keep it spinning

	in	DSTAT		;skip speed test
	ani	SNORDY		;...if drive not ready
	rnz

	lxi	d,MOTRST	;'Spindle Motor: '
	call	CRPRNT		;print message

;Wait for INDEX to be low (The STAT2 signal is inverted)

	lxi	d,0		;timeout timer
	lxi	h,-1		;measurement timer
	mvi	c,BINDEX	;index bit mask

IMEAS1:	inx	d		;(6)[5]
	mov	a,d		;(4)[5]
	ora	e		;(4)[4]
	jz	IMTO		;(10)[10]timeout?

	in	STAT2		;(11)[10]wait for inactive
	ana	c		;(4)[4]
	jz	IMEAS1		;(10)[10]

;Wait for the next rising edge of INDEX

IMEAS2:	inx	d		;(6)[5]
	mov	a,d		;(4)[5]
	ora	e		;(4)[4]
	jz	IMTO		;(10)[10] timeout?

	in	STAT2		;(11)]10] wait for
	ana	c		;(4)[4];... rising edge
	jnz	IMEAS2		;(10)[10]

	mov	a,h		;(4)[5] stall
	rc			;(5)[5] stall (Carry never set)

;Measure the time to the next leading edge of INDEX. The
;following 2 loops each take exactly 40 Z80 cycles (10 uS at 4
;MHz) per pass. It takes 39 Z80 cycles from the 1st edge (above)
;to the next read of DSTAT, and 40 Z80 cycles between DSTAT
;reads between the 2 loops below. The loop takes only 39 8080
;cycles, so the meaurement will read 2.5% high when running on
;an 8080 CPU. (We could fix this with an 8080/Z80 test, and then
;two seperate measurement loops for the two CPU types...)

IMEAS3:	inx	h		;(6)[5] bump timer
	in	STAT2		;(11)[10] wait for + edge
	ana	c		;(4)[4]
	mov	a,h		;(4)[5] stall
	rc			;(5)[5] stall (Carry never set)
	jz	IMEAS3		;(10)[10]

IMEAS4:	inx	h		;(6)[5] bump timer
	in	STAT2		;(11)[10] wait for + edge
	ana	c		;(4)[4]
	mov	a,h		;(4)[5] stall
	rc			;(5)[5] stall (Carry never set)
	jnz	IMEAS4		;(10)[10]

;The above measurement assumes a 4 MHz Z80 CPU. If the Z80
;is set for 2 MHz, then we double the measured number.

	lda	ZSPEED		;0 means 2 MHz
	ora	a
	jnz	IMEAS5

	dad	h		;16-bit multiply by 2
IMEAS5:

;hl has the time in 0.01 mS units. which will be << 655.36 mS,
;so we can clear the high byte of the 24-bit number c,hl.

	mvi	c,0		;msb is 0
	push	h		;save result for ret
	call	PRNTMS		;print hl in mS
	pop	h
	lxi	d,REVST		;'/rev'
	jmp	PRINTF

;Timeout waiting for 1st index pulse

IMTO:	lxi	d,TIMOST	;Timeout!
	jmp	PRINTF	

;===Subroutine==========================================
;Print 24-bit value in mS
;On Entry:
;  [c,hl]=24-bit value, in 0.01 mS units
;On Exit:
;  'ABCDE.FG mS' printed (with leading zeros suppressed)
;Trashes psw,c,de,hl
;=======================================================
; divide c,hl by 100 to get whole number mS portion
PRNTMS:	mov	a,c		;C has high digit
	lxi	b,-100
	lxi	d,-1		;initiaize quotent

PMSLP:	inx	d
	dad	b		;subtract 100
	jc	PMSLP
	sui	1		;dec high digit
	jnc	PMSLP

	lxi	b,100		;goes 1 too many times
	dad	b		;hl now has remainder

	push	h		;save remainder
	xchg			;whole number into hl
	call	PDEC16		;print whole number in de

;Print decimal point, then fractional mS portion
	mvi	a,'.'
	call	PRINTA

	pop	h		;recover remainder

	mvi	d,1		;print leading 0's
	call	PRFRAC

	lxi	d,MSST		;mS
	jmp	PRINTF		

;===Subroutine===
;Print CR,LF
;Trashes psw,c
;================
PCRLF:	push	d
	lxi	d,CRLFST
	call	PRINTF
	pop	d
	ret

;===Subroutine=========================
;Print CR,LF,$-terminated message at de
;Trashes psw,c,de
;======================================
CRPRNT:	call	PCRLF

;Fall into PRINTF

;===Subroutine===================
;Print $-terminated message at de
;On Exit:
;   Z set
;Trashes de
;================================
PRINTF:	ldax	d
	cpi	'$'
	rz

	call	PRINTA
	inx	d
	jmp	PRINTF

;===Subroutine========
;Print head-load state
;Trashes psw,de
;=====================
PHLOAD:	lxi	d,HEADST	;'Head '
	call	CRPRNT

	lda	SHDLD
	lxi	d,LDST		;'loaded'
	ora	a		;0 maens not loaded
	jnz	PH1
	lxi	d,ULDST		;'unloaded'

PH1:	jmp	PRINTF

;===Subroutine====
;Print motor state
;=================
PMOTOR:	lxi	d,MOTST		;'Motor '
	call	CRPRNT

	lxi	d,ONST		;'on'
	lda	SCTRL1		;test state
	ani	DCMOTO		;just the motor bit
	jnz	PM1
	lxi	d,OFFST		;'off'	

PM1:	jmp	PRINTF

;===Subroutine===================
;Compute and report the data rate
;================================
PDRATE:	lxi	d,DRATST	;data rate
	call	CRPRNT

	lxi	h,125		;SD minidisk kbps
	lda	SCTRL1		;DCMAXI:4 DCDDEN:6
	rlc			;DCMAXI:5 DCDDEN:7
	rlc			;DCMAXI:6 DCDDEN:C
	jnc	BR0
	dad	h		;double density
BR0:	rlc			;DCMAXI:7
	rlc			;DCMAXI:C
	jnc	BR1
	dad	h		;8"disk
BR1:	call	PDEC16		;hl=bit rate
	lxi	d,KBPSST	;K-bits/sec
	jmp	PRINTF	

;===Subroutine=========
;Print de as an address
;Trashes psw,bc
;======================
PADDR:	call	PCRLF		;new line
	mov	a,d		;addr high byte
	call	PRHEX
	mov	a,e		;addr low byte
	call	PRHEX

	mvi	a,':'
	jmp	PRINTA

;===Subroutine==========
;Print a as 2 hex digits
;On Entry
;  a = value to print
;Trashes psw,bc
;=======================
PRHEX:	mov	b,a		;save digit
	rrc			;hi nibble 1st
	rrc
	rrc
	rrc
	call	PHXNIB
	mov	a,b		;recover lo nibble

;fall into PHXNIB

;---Local Subroutine----
;Print hex nibble
;On Entry:
;  Nibble in low 4 of a
;Trashes psw,c
;-----------------------
PHXNIB:	ani	0Fh		;get low
	cpi	0Ah		;A-F?
	jc	PHX1
	adi	'A'-'9'-1
PHX1:	adi	'0'

;fall into PRINTA

;===Subroutine==========
;Print a on console.
;On Entry:
;   a=chr to print
;All registers preserved
;=======================
PRINTA:	push	psw
	push	b
	push	d
	push	h

	mov	c,a		;BIOS wants chr in c
	mvi	a,CONOUT

	call	DOBIOS

	pop	h
	pop	d
	pop	b
	pop	psw
	ret

;===Subroutine====================
;Go to a BIOS driver directly
;On Entry:
;  a=BIOS address offset
;On Exit:
;  All registers as BIOS left them
;=================================
DOBIOS:	lhld	WBOOTA		;BIOS base address
	mov	l,a		;offset the address

	pchl			;go to BIOS routine

;===Subroutine========================================
;Get a line of input from the user. Echo normal
;characters, but not CR. Immediate commands (which
;must be the first chr on the line) are executed, and
;the cursor backed up over the immediate command chr.
;On Exit:
;  0-terminated line is in COMBUF
;  hl = address of 1st non-blank chr in COMBUF
;  Z set if line is 0 characters (besides blanks)
;  Abort to PRMAIN if ^C or ESC
;Trashes a,bc,de
;=====================================================
GETLIN:	lxi	h,COMBUF
	push	h
	mvi	b,0		;chr count

;Backspace if possible

GLDEL:	mov	a,b		;can we back up?
	ora	a
	jz	GLOOP		;no: ignore

	lxi	d,DELST		;BS,space,BS

GLBS:	call	PRINTF

	dcr	b		;back up
	dcx	h
	
GLOOP:	call	HCONIN		;Get uppercase chr
				;and keep head loaded

	cpi	CR		;end of input?
	jz	GLDON

	cpi	BS		;any backspaces?
	jz	GLDEL
	cpi	DEL		;of either sort...
	jz	GLDEL
	
	mov	m,a		;save new chr
	call	PRINTA		;echo character

	inx	h		;next slot in buffer
	inr	b		;count new chr

	mov	c,b		;1st chr on line?
	dcr	c

	cz	CKIMED		;y: check for immediate command
				;Z set if immediate command

	jnz	GLOOP		;not immediate?

;Terminate complete input in COMBUF with a null

GLDON:	mvi	m,0		;install null termination
	pop	h		;point to COMBUF

;Fall into SKIPSP

;===Subroutine======================
;Skip over spaces in command line
;On Entry:
; hl=address in command 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:	dcx	h		;account for initial inc

SSLOOP:	inx	h		;loop to skip spaces

	mov	a,m

	cpi	' '
	jz	SSLOOP

	ora	a		;set or clr Z
	ret

;===Subroutine==========================
;Check for one of the immediate commands
;and execute it if so.
;On Entry:
;  a=character
;On Exit if immediate command:
;  a=0
;  hl and b have been decremented
;  Trashes c,de
;On Exit otherwise:
;  all preserved
;=======================================
CKIMED:	cpi	'+'		;step in?
	jz	STEPIN

	cpi	'-'		;step out?
	jz	STEPOT

	cpi	'>'		;nudge inward?
	jz	NUDGIN

	cpi	'<'		;nudge outward?
	jz	NUDGOT

	cpi	'H'		;headload toggle?
	rnz

;Fall into TOGHD

;===Subroutine===================
;Toggle the head-load state
;On Exit:
;  SHDLD = SHDLD xor HDLOAD
;  a=0
;  Z set
;  hl and b have been decremented
; Trashes c,de
;================================
TOGHD:	lda	SHDLD		;get prev state
	xri	HDLOAD		;toggle it
	sta	SHDLD		;remember state

	push	psw		;Z set for unloaded
	call	RECTL1		;write it to CTRL1

;set/clear the WD1793 head load state using a null seek

	in	DTRCK		;seek the track we're on
	out	DDATA
	pop	psw		;Z set for unloaded

	mvi	a,SEEKCM	;null seek, head unloaded
	jz	TOGHD1		;head loaded?
	mvi	a,CCSEEK	;null seek, head loaded
TOGHD1:	out	DCMMD		;issue WD1793 command

	call	PHLOAD		;report head-load state

	jmp	IMDONE		;recycle some code

;===Subroutine=================
;Step in one track, if possible
;On Entry:
;  VERBMO<>0 enables printing
;On Exit:
;  a=0
;  Z set
;Trashes a,c,de,hl
;==============================
STEPIN:	lda	MAXTRK
	mov	c,a
	in	DTRCK		;where is the head now?
	cmp	c		;max already?
	
	mvi	c,CCSTEPI	;step in (up track)
	cc	STPSUB		;step command unless max

	jmp	OPTRAK		;report track # & ret, Z set

;===Subroutine==================
;Step out one track, if possible
;On Entry:
;  VERBMO<>0 enables printing
;On Exit:
;  a=0
;  Z set
;Trashes a,c,de,hl
;===============================
STEPOT:	in	DTRCK		;where is the head now?
	ora	a		;track 0 already?
	
	mvi	c,CCSTEPO	;step out (down track)
	cnz	STPSUB		;step command unless track0

;Fall into OPTRAK to report track number & return, Z set

;===Subroutine===================================
;Print the current track number only if VERBMO<>0
;On Entry:
;  VERBMO<>0 enables printing
;On Exit:
;  a=0
;  b & hl decremented
;  Z set
;Trashes de
;================================================
OPTRAK:	lda	VERBMO		;verbose?
	ora	a
	jz	OPDONE		;n:done

	push	h
	push	b
	call	PTRACK		;print track no., trash regs
	pop	b
	pop	h
OPDONE:	

;Fall into IMDONE


IMDONE:	dcx	h		;back up input
	dcr	b		;..for return
	xra	a
	ret

;===Subroutine==========================
;Nudge the head inward and then back out
;On Exit:
;  a=0
;  Z set
;Trashes c
;=======================================
NUDGIN:	lda	MAXTRK
	mov	c,a
	in	DTRCK		;where is the head now?
	cmp	c		;max already?
	jnc	NIDONE		;y: then do nothing
	
	mvi	c,CCSTEPI	;step in (up track)
	call	STPSUB		;step command
	mvi	c,CCSTEPO	;step in (down track)
	call	STPSUB		;step command

NIDONE:	jmp	IMDONE		;recycle some code

;===Subroutine==========================
;Nudge the head outward and then back in
;On Exit:
;  a=0
;  Z set
; Trashes c
;=======================================
NUDGOT:	in	DTRCK		;where is the head now?
	ora	a		;track 0 already?
	jz	NODONE		;y: then do nothing
	
	mvi	c,CCSTEPO	;step in (down track)
	call	STPSUB		;step command
	mvi	c,CCSTEPI	;step in (up track)
	call	STPSUB		;step command

NODONE:	jmp	IMDONE		;recycle some code

;---Local Subroutine-------------------------
;Get the step rate and issue the step
;command. Heads must be loaded to step.
;On Entry:
;  c = CCSTEPI or CCSTEPO
;  SHDLD is valid
;Trashes psw,c
;--------------------------------------------
STPSUB:	lda	STPRAT		;current step rate
	ora	c
	mov	c,a
	lda	SHDLD		;combine with head load
	ora	c
	jmp	DSKCMD		;step

;===Subroutine========
;Ask user Y or N
;On Entry:
;  de = initial string
;Trashes psw,de
;=====================
ASKYN:	call	CRPRNT
	call	PRDRIV		;print drive letter
	lxi	d,ASKRST	;ready (Y/N)?

	call	CRPRNT
	call	UCONIN		;get response
	call	PRINTA		;echo

	lxi	d,ABRTST	;Abort
	cpi	'Y'
	jnz	PRMAIN		;abort if not Y
	ret

;===Subroutine=====================
;Print the sidedness of the current
;disk, assuming an 8" disk
;On Exit:
;  b=DCMAXI
;Trashes psw,de
;==================================
PSIDED:	in	STAT2
	ani	BSIDED		;0 means 2-sided
	mvi	a,'1'
	jnz	SETU0
	inr	a		;'2'
SETU0:	call	PRINTA

	lxi	d,SIDDST	;'-sided 8"'
	call	PRINTF

	mvi	b,DCMAXI	;for exit

	lxi	d,DISKST	;' diskette'
	jmp	PRINTF

;===Subroutine=====================
;Print the current track number and
;the seek speed only if VERBMO<>0
;On Entry:
;  c,hl=seek time in 0.01 mS units
;  VERBMO <> 0 enables printing
;Trashes all registers
;==================================
OPSTTK:	lda	VERBMO
	ora	a
	rz

	push	b		;save msd
	lxi	d,SKTST		;seek time:
	call	CRPRNT
	pop	b
	call	PRNTMS		;print seek time

;Fall into PTRACK

;===Subroutine=================
;Print the current track number
;Trashes all registers
;==============================
PTRACK:	lxi	d,TRAKST	;Track:
	call	CRPRNT
	in	DTRCK		;get actual track number

;Fall into PDEC8

;===Subroutine====================
;Print a as decimal on the console
;Trashes all registers
;=================================
PDEC8:	mov	l,a
	mvi	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:	mvi	d,0	;Suppress leading 0's

	lxi	b,-10000
	call	DECDIG
	lxi	b,-1000
	call	DECDIG
	lxi	b,-100
	call	DECDIG
PRFRAC:	lxi	b,-10
	call	DECDIG

	mov	a,l	;last digit is simple
	jmp	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:	mvi	a,0FFh	;-1: will go 1 too many times
	push	d	;leading zero state

DIGLP:	mov	d,h	;de gets prev value
	mov	e,l
	inr	a
	dad	b	;subtract power of 10
	jc	DIGLP

	xchg		;hl has remainder
	pop	d	;leading 0 state

	mov	e,a	;E has digit to print
	ora	d	;leading 0 to suppress?
	rz		;yes: digit is done

	mov	d,a	;save for next digit
 
	mov	a,e	;make ASCII
DECDG0:	adi	'0'
	jmp	PRINTA	

;===Subroutine===========================
;Get 1 ASCII chr from the command buffer
;On Entry:
;  hl points to character
;On Exit:
;  a=character
;  Z set and a=0 if no character present
;  hl points to first chr of next param
;  Rude jump to GPERR if  2nd chr present
;Trashes c
;========================================
GETASC:	call	SKIPSP
	mov	a,m		;get next chr
	ora	a
	rz			;no more

	push	psw		;save chr and NZ
	inx	h
	call	SKIPSP		;should be no more
	jnz	GPERR		;too much crap

	pop	psw		;recover chr and NZ
	ret

;===Subroutine================================
;Get a 0 or 1 from the command buffer
;On Entry:
;  hl points to first character 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:	mvi	c,1
	call	GETDEC
	jz	GPERR		;error if no chr

	call	SKIPSP		;should be no more
	jnz	GPERR		;too much crap

	ora	b		;get, retest value
	ret			;with Z set

;===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 characters
;  hl points to first character of next param
;  Z set if no parameters found
;  Rude jump to CMDERR if bogus chr or value>c
;=============================================
GETDEC:	call	SKIPSP		;spaces beteeen values
	call	GETDD		;get 1st digit into a
	rz			;return w/ default in b

	mov	b,a		;save 1st digit
	call	GETDD
	jz	GPDON		;ret w/ 1-digit input

	push	psw		;save new digit

	mov	a,b		;compute b=b*10
	rlc			;*2
	rlc			;*4
	add	b		;*5
	rlc			;*10
	mov	b,a

	pop	psw		;recover 1st digit
	add	b		;combine digits
	mov	b,a		;into b for ret

GPDON:	call	GET1		;any more digits?
	jnz	GPERR		;abort if any

	mov	a,c		;max value from input
	cmp	b		;legal value?
	jc	GPERR		;abort if not

	ori	1		;clear Z

	mov	a,b		;result in a
	ret			;& in b
;---Local Subroutine-----------------------------------
;Get and test a decimal digit
;On Exit:
;  Z set if end of command line or no digit
;  hl points to next command line chr unless it's a ' '
;  a=chr converted to binary, unless Z
;------------------------------------------------------
GETDD:	call	GET1
	rz

	sui	'0'		;de-ASCII
	cpi	10		;carry means below 10
	rc

GPERR:	jmp	CMDERR		;bogus: rude abort

;===Subroutine=========================
;Get a multidigit 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 hex
;===============================-======
GETHEX:	call	SKIPSP		;spaces beteeen values

	lxi	d,0		;initial value
	mov	b,d		;b counts digits

	call	HEX2BN		;get 1st digit
	rz			;Z: no chrs

GHLUP:	inr	b		;digit count
	xchg			;shift prev digits left 4
	dad	h
	dad	h
	dad	h
	dad	h
	xchg

	add	e		;combine new digit
	mov	e,a

	call	HEX2BN		;get another digit
	jnz	GHLUP		;keep going until gone

	ori	1		;clear Z
	ret		
;---Local Subroutine-------------------
;Convert hex (hl) to binary. rude abort
;if bad hex. Z set if no character.
;--------------------------------------
HEX2BN:	call	GET1
	rz

	sui	'0'		;un-ASCII
	cpi	10		;0-9?
	rc			;y: done

	sui	9+('A'-'9')	;should now be 0-5
	cpi	6		;gap chr or too big?
	jnc	GPERR		;yes: bogus

	adi	0Ah		;make it 0A-0F
	ret

;===Subroutine========================
;Get 1 character from the command line
;On Exit:
;  Z if none left
;  chr in a and advance hl otherwise
;=====================================
GET1:	mov	a,m		;any chrs?
	ora	a
	rz			;no:Z set

	cpi	' '		;no digit?
	rz
	inx	h
	ret

;===Subroutine===================
;Set Force Step Mode bit in DTYPE
;On Return:
;  a = 0
;  Z set
;Trashes hl
;================================
FRCST:	mvi	a,FRCSTP
	lxi	h,DTYPE

FORCE:	ora	m
	mov	m,a

	xra	a	;for return
	ret

;===Subroutine==================
;Test for ^C or ESC pressed
;Rude jump to PRMAIN if so
;On Exit:
;  a=0 & Z set if no chr waiting
;  a=character if not ^C or ESC
;Trashes c
;===============================
TABORT:	push	h
	push	d
	push	b

	mvi	A,CONST		;address offset
	call	DOBIOS		;Returns status in a

	pop	b
	pop	d
	pop	h
	ora	a		;Z clear=chr waiting
	rz

;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
;========================================
UCONIN:	push	h
	push	d
	push	b

UCONI1:	mvi	A,CONIN		;Address offset
	call	DOBIOS		;Returns chr in a
	pop	b
	pop	d
	pop	h

	sui	'a'		;convert lowercase
	cpi	'z'+1
	jnc	UCIN1
	sui	'a'-'A'		;..to uppercase
UCIN1:	adi	'a'

	cpi	CTRLC		;Aborting command?
	jz	UCQUIT
	
	cpi	ESC		;ESC aborts too
	rnz

UCQUIT:	lxi	d,CRLFST
	jmp	PRMAIN

;===Subroutine=============================
;Get a console character and convert it to
;uppercase. Keep head loaded while we wait.
;Abort command if ^C or ESC
;On Exit:
;  a=chr if not ^C or ESC
;==========================================
HCONIN:	lda	SHDLD		;current head-load state
	ora	a		;head loaded?
	jz	UCONIN		;n: just wait for the chr	

	push	h
	push	d
	push	b

;Issue (null) seek commands while we wait for the user
;to restart the WD1793's head-load timer

HCILUP:	mvi	A,CONST		;console status
	call	DOBIOS		;Returns status in a
	ora	a		;anything there?
	jnz	UCONI1		;y: go get the character

	in	DSTAT		;drive busy?
	ani	SBUSY
	jnz	HCILUP		;no need to kick it

	in	DTRCK		;seek the track we're on
	out	DDATA
	mvi	a,CCSEEK	;null seek, head loaded
	out	DCMMD		;issue WD1793 command

	jmp	HCILUP		;keep looking	

;===Subroutine============================
;Search command table for match
;On Entry:
;  hl=address of command 1st byte
;  de=command table address
;On Exit:
;  de=addr of pointer to execution routine
;  hl=address of next chr on command line
;Trashes psw
;
;-->>ENTRY IS AT FNDCOM<<--
;=========================================
NCOM:	inx	d		;next entry
NCOM2:	inx	d
	inx	d
	pop	h		;Recover original addr
;------------
; Entry Point
;------------
FNDCOM:	ldax	d
	ora	a		;table end?
	rz			;Not found: command error

	push	h		;Save initial addr
	cmp	m		;Test 1st chr
	inx	d
	jnz	NCOM

	inx	h
	ldax	d
	inx	d
	cmp	m		;Now test 2nd chr
	jnz	NCOM2		;no match: keep looking

	xchg			;pointer to addr in hl
	mov	e,m
	inx	h
	mov	d,m

;a=second chr of command
	pop	h		;recover pointer to command
	inx	h		;point hl to next chr
	ora	a		;1-chr command? (?)
	jz	NCOM3		;yes:don't overdo hl
	inx	h

NCOM3:	inr	a		;success: clear Z
	ret

;=============
;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		;set
	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	'WC'
	dw	FWCPAT		;write pattern continuous
	db	'WP'
	dw	FWPAT		;write pattern

	db	'A='
	dw	FSETA		;auto mode
	db	'D='
	dw	FSETD		;density
	db	'M='
	dw	FSETM		;Max track number
	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	'Z='
	dw	FSETZ		;Z80 Speed

	db	0		;Table End

;=================================================
;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 1793 rates
;========================
STPTAB:	db	3		;00
	db	6		;01
	db	10		;10
	db	15		;11

;====================
;$-terminated strings
;====================
BANNER:
 db '====================================================',CR,LF
 db '=                    FLEXER/CCS                    =',CR,LF
 db '=    CP/M  Floppy Disk Drive Exerciser for the     =',CR,LF
 db '= California Computer Systems 2422 Disk Controller =',CR,LF
 db '=    Version '
 db REVMAJ+'0','.',(REVMIN/16)+'0',(REVMIN and 0Fh)+'0'
 db '           by Martin 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$'
DRVST0:	db	' $'		;fall into DRIBST
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$'
VSTOST:	db	'Voicecoil '	;fall into SKTOST
SKTOST:	db	'Seek '		;fall into TIMOST
TIMOST:	db	'Timeout!$'
SKTST:	db	'Seek Time: $'
STRST:	db	'Step Rate: $'
DTYPST:	db	'Drive Type: $'
VCST:	db	'Voicecoil $'
STPRST:	db	'Stepper $'
ZSST:	db	'Z-80 Speed: $'
MHZST:	db	' MHz$'
SIDDST:	db	'-sided '	;fall into MAXIST
MAXIST:	db	'8" $'
MINIST:	db	'Minidisk $'
VDST:	db	'Voicecoil drive $'
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: 1793 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!$'
QTOQST:	db	'Type Q to quit$'

HLPMSG:
;    12345678901234567890123456789012345678901234567890123456789012345678901234567890
 db '                 ==FLEXER/CCS Floppy Exerciser Commands=='
 db CR,LF
 db 'BD         Display Read/Write Buffer    '
 db '  ?        Print Help screen'
 db CR,LF
 db 'BE <A>     Edit R/W Buffer              '
 db '  A={0/1}  Auto Mode {Off/On}'
 db CR,LF
 db 'BF <H>     Fill R/W Buffer              '
 db '  D={S/D}  {Single/Double} Density'
 db CR,LF
 db 'EJ         Eject Diskette               '
 db '  M={1-99} Max Track Number'
 db CR,LF
 db 'ID         Read Track ID                '
 db '  N={0-99} Max Read Retries'
 db CR,LF
 db 'MO         Toggle/Measure Motor *       '
 db '  R={0-3}  Step Rate'
 db CR,LF
 db 'QU         Quit to CP/M                 '
 db '  S={0/1}  Select Side'
 db CR,LF
 db 'RE         Restore to Track 0           '
 db '  T={S/V}  Type {Stepper/Voicecoil}'
 db CR,LF
 db 'SE         Display current settings     '
 db '  U={A-D}  Select Unit'
 db CR,LF
 db 'SK <T1> [...<Tn>] Voicecoil-Seek        '
 db '  V={0/1}  Verbose Mode {Off/On}'
 db CR,LF
 db 'SR <S>     Read Sector to R/W Buffer    '
 db '  Z={2/4}  Z-80 Speed {2/4} MHz'
 db CR,LF
 db 'ST <T1> [...<Tn>] Step-Seek             '
 db 'IMMEDIATE COMMANDS'
 db CR,LF
 db 'SV <S>     Verify Sector = R/W Buffer   '
 db '  H   Toggle head load *'
 db CR,LF
 db 'SW <S> [D] Write R/W Buffer to Sector   '
 db '  +   Step in one track'
 db CR,LF
 db '            [D sets deleted-data mark]  '
 db '  -   Step out one track'
 db CR,LF
 db 'VD         Display last Verify Data     '
 db '  >   Nudge head inward'
 db CR,LF
 db 'WP <H1> [...<Hn>] Write track pattern   '
 db '  <   Nudge head outward'
 db CR,LF
 db 'WC <H1> [...<Hn>] Continuous Write track'
 db CR,LF
 db '^C or ESC to quit BE, MO, SK and SE commands.'
 db ' (Q also quits MO, SK and SE.)'
 db CR,LF
 db 'For BE command, enter Hex to change or CR to skip'
 db ' each buffer entry.)'
 db CR,LF
 db 'Space toggles motor for MO command '
 db 'and goes to next track for SE and SK.'
 db CR,LF
 db '* Drive may be set up for motor always on'
 db ' and/or head loaded when selected.' 
 db '$'
;=========================
;Initialized RAM variables
;=========================
OPMODE:	db	OPMANM		;op mode - automatic or manual
OPMANM	  equ	  01H		  ;manual mode

DTYPE:	db	1		;0 for Voicecoil seeking
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

VERBMO:	db	1		;verbose display mode
ZSPEED:	db	2		;0=2 MHz, 2=4MHz Z80 CPU
SHDLD:	db	0		;HDLOAD for head loaded
SCTRL1:	db	DCMAXI+DCMOTO	;Current CTRL1 value
SCTRL2:	db	BCSIDE0+BCFSEKN	;current CTRL2 value
STPRAT:	db	DEFSTP		;Step rate
MAXTRK:	db	MAXTK8		;max track number
WDDM:	db	0		;write deleted data mark
RTRIES: db	DRTRYS+1	;Reead tries (retries+1)
;===================
;Track Patern Buffer
;===================
PATBUF:	db	0,0		;first 2 bytes initialized
	ds	LBSIZE/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


