;============================================================
;                     Disk Identifier
;             for the CCS 2422 WD1793-based
;            multimode floppy disk controller
;============================================================
;This program's revision number
VERSION	equ	0105h	;high byte = major revision number
			;low byte=2-digit minor version
;============================================================
; Revision History
; 1.05 15 November 2021
;   Detect and report Quad-Density and High Density 5.25"
;   80-track drives
; 1.04  15 March 2021  M. Eberhard
;   Detect and report 3.5" drives (with 2 or 4MHZ CPU) on the
;   8" (50-pin) 2422 drive interface
; 1.03 21 February 2018  M. Eberhard
;   Add voicecoil check, tidy up report a bit
; 1.02 3 June 2017  M. Eberhard
;   Add write-protect check
; 1.01 2 June 2017  M. Eberhard
;   Add SPT check
; 1.00 27 May 2017  M. Eberhard
;   Created. This program is a bit of a hack, cobbled
;   together from CBIOS and Flexer routines mostly.
;============================================================
; Assembles with Digital Research's ASM
; Does not use any Z-80 instructions.
; Works with 2MHz or 4 MHz 808 or Z80 CPU
;============================================================
;============================================================
;Assembly Options
;============================================================
FALSE	equ	0
TRUE	equ	not FALSE
;============================================================
;Equates
;============================================================
MAXDRV	equ	3	;Drives A-D
RATRYS	equ	4	;Max 'read address' retries per density
MAXTK8	equ	76	;8" disk max track number
MAXTK5	equ	39	;5.25" minidisk max track number
;---------------------------------------------------------
;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
;------------
;CP/M Equates
;------------
WBOOT	equ	0000H		;BIOS warm boot
WBOOTA	equ	WBOOT+1		;BIOS address
CDISK	equ	0004H		;Current drive
COMBUF	equ	80h		;Input buffer used by CP/M
TPA	equ	100h		;transient program area

STACK	equ	COMBUF+80	;stack in the com buffer
;---------------------------------------
;CCS 2422 Floppy Disk Controller Equates
;---------------------------------------
BCTRL	equ	4		;Control port 2
BSTAT	equ	4		;status port 2
DSTAT	equ	30h		;disk status port
DCMMD	equ	DSTAT		;disk command port
DTRCK	equ	DSTAT+1		;track port
DSCTR	equ	DSTAT+2		;sector port
DDATA	equ	DSTAT+3		;data port
DFLAG	equ	DSTAT+4		;disk flag port
DCTRL	equ	DSTAT+4		;disk control port

;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
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 destination track
HDLOAD	equ	00001000b	;(I) head load
UPDTTR	equ	00010000b	;(I) update track reg
F1SCMP	equ	00000010b	;(II)enable side compare
F2SCS1	equ	00001000b	;(II)side compare, side 1
MULTCM	equ	00010000b	;(II) read/write multiple
ED15MS	equ	00000100b	;(II & III) settling delay

;WD1793 step rates (note: 5.25" disk mode halves the clock rate):
;
;  value   5.25"      3.5" & 8"
; RS03MS    6 mS        3 MS
; RS06MS   12 mS        6 MS
; RS10MS   20 mS       10 MS
; RS15MS   30 mS       15 MS

STEP5	equ	RS10MS		;SA400L Minidisk rate=20 mS
				;(SA450 is the same)
STEP8	equ	RS10MS		;SA800 8" 1-sided rate=8 mS
STP8D	equ	RS03MS		;SA851 8" 2-sided rate=3 mS

;Composite WD1793 commands used in this program

CCRSTOR	equ	RSTRCM+HDLOAD+STEP5	;restore, load head
CCSEEK	equ	SEEKCM+HDLOAD+STEP5	;seek, load head
CCSTEPI	equ	STPICM+HDLOAD+STEP5	;step-in, load head
CCRDSEC	equ	RSECCM+F1SCMP		;read sector, verify side
CCWRSEC	equ	WSECCM+F1SCMP		;write sector, verify side
CCRDADD	equ	RADRCM+ED15MS		;read address, head-load delay

;Control register 1 (DCTRL) output bits

DCDS1	equ	00000001b	;drive select 1
DCDS2	equ	00000010b	;drive select 2
DCDS3	equ	00000100b	;drive select 3
DCDS4	equ	00001000b	;drive select 4
DCMAXI	equ	00010000b	;0=5.25" disk, 1=8" or 3.5" disk
DCMDMO	equ	00100000b	;Motor on
DCDDEN	equ	01000000b	;double density enable
DCAUTOW	equ	10000000b	;autowait

;Control register 2 (BCTRL) output bits

BCEJECT	equ	00000100b	;remote eject control (PerSci)
BCFSEEK	equ	00010000b	;fast seek: voicecoil drives
BCSIDE0	equ	01000000b	;0=side 1, 1=side 0
BCROMEN	equ	10000000b	;enable onboard ROM

;Composite BCTRL values used to select disk side
; Note: ROM is actually disabled via the page register
; Note: fast seek disabled VIA hardware jumper

SIDE0	equ	BCROMEN+BCFSEEK+BCSIDE0	;Needs to be <>0
SIDE1	equ	BCROMEN+BCFSEEK		;Needs to be <>0

;WD1793 status (DSTAT) after a command

SBUSY	equ	00000001b	;(All) busy
SCRCERR	equ	00001000b	;(All) CRC error
SWRPROT	equ	01000000b	;(All) write protect
SNOTRDY	equ	10000000b	;(All) drive not ready
SINDEX	equ	00000010b	;(I) index
STRACK0	equ	00000100b	;(I) track 0
SSEEKER	equ	00010000b	;(I) seek error
SHEADLD	equ	00100000b	;(I) head loaded
SDATRQ	equ	00000010b	;(II & III) data request state
SLOSTD	equ	00000100b	;(II & III) lost data error
SRNFER	equ	00010000b	;(II & III) record not found
SRCTYF	equ	00100000b	;(II & III) record type fault
SWFALT	equ	00100000b	;(II & III) Write fault

;Status register 1 (DFLAG) input bits

DFINTRQ	equ	00000001b	;WD1793 INTRQ signal, active high
DFDS1	equ	00000010b	;DS1 in control reg 1
DFDS2	equ	00000100b	;DS2 in control reg 1
DFDS3	equ	00001000b	;DS3 in control reg 1
DFDS4	equ	00010000b	;DS4 in control reg 1
DFSHLD	equ	00100000b	;WD1793 HLD signal, active high
DFAUTOB	equ	01000000b	;autoboot jumper, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;Status reg 2 (BSTAT) input bits

BTRK0	equ	00000001b	;when on track 0: 1=8" disk
BMINI	equ	00000010b	;-DCTRL reg bit: 1=Minidisk mode
BWPRT	equ	00000100b	;0=disk is write-protected
BSIDE0	equ	00001000b	;SIDE0 bit in control register 2
BINDEX	equ	00010000b	;index signal, active low
BDDEN	equ	00100000b	;DDEN signal in control register 1
BSIDED	equ	01000000b	;0=selected 8" disk is 2-sided
;--------------
;ASCII Equates
;--------------
CTRLC	equ	3
CR	equ	0Dh
LF	equ	0Ah
ESC	EQU	1BH
DQUOT	equ     22H          ;double-quote character
SQUOT	equ	27H	     ;single-quote character
BQUOT	equ     60H          ;back-quote character

;*****************
;Beginning of Code
;*****************
	org TPA

	lxi	sp,STACK	;local stack

	call	CILPRT
 db '================================',CR,LF
 db '      DISKID  version '
 db ((VERSION AND 0F00h)/256)+'0','.'
 db ((VERSION AND 0F0h)/16)+'0',(VERSION AND 0Fh) +'0',CR,LF
 db 'For the CCS 2422 disk controller',CR,LF
 db '       by Martin Eberhard',CR,LF
 db '===============================','='+80h

;----------------------------------------------------
;Get the drive letter followed by the optional colon.
;If no input then use the currently selected drive
;-----------------------------------------------------
	lxi	h,COMBUF	;Where CP/M put the command
	mov	a,m		;1st byte is the byte count
	ora	a		;No drive specified?
	lda	CDISK
	jz	GOTDRV		;then use current drive

	mov	a,m
	cpi	4		;space plus 2 characters
	jnc	CMDERR		;bogus input?

	inx	h		;space
	inx	h		;drive letter
	mov	b,m
	cpi	2		;no colon is ok too
	jz	GDRV0

	inx	h		;check for mandatory colon
	mov	a,m
	cpi	':'
	jnz	CMDERR

GDRV0:	mov	a,b
	ori	'a' - 'A'	;lowercase is ok too
	sui	'a'
	cpi	MAXDRV+1
	jnc	CMDERR

;a=0-3 for drive A-D

GOTDRV:	sta	DISKNO		;remember disk

;Give the user a chance to insert a disk

	mvi	b,0		;don't say CP/M
	call	GETRET

;Compute drive select bits and select the drive

	lda	DISKNO
	inr	a
	mov	b,a
	mvi	a,80h
SBLOOP:	rlc
	dcr	b
	jnz	SBLOOP

	ori	DCMDMO		;in case it's a 5.25" disk
	out	DCTRL		;select disk
	sta	BDCTRL		;remember DCTRL value

;Restore the drive to track 0

	call	RESTOR		;restore the disk, and
				;determine its size
				;selects side 0

;Step in past system tracks

	call	STEPIN
	call	STEPIN
	call	STEPIN

	call	GETID		;get BPS, SPT, etc.
	jnz	BADISK		;error reading track ID?

;--------------------------
;Figure out sidedness
; GETID made BDCTRL current
;--------------------------
	lda	BDCTRL
	ani	DCMAXI		;Z set if 5.25" disk
	jnz	MAXSID		;NZ: do 3.5" or 8" sidedness
;-----------------------------------------------------------
;5.25" disk Sidedness: Look at side 1 to see if this is a
;2-sided disk. There are 3 possibilities: 
;1) This is a 1-sided drive. In this case, "side 1" will be
;   readable (since the side signal is ignored), but the
;   side will identify as side 0. We return with a=FF.
;2) This is a 1-sided disk in a 2-sided drive. In this case,
;   we will (probably) read garbage from the side, and the
;   IDREAD will fail. We return with a=0 for 1-sided disk
;3) this is a 2-sided disk in a 2-sided drive. In this case,
;the IDREAD will be successful, and the side will be 1.
;-----------------------------------------------------------
	mvi	a,SIDE1		;select side 1 of new disk
	out	BCTRL

	call	IDREAD		;Try to get an ID from side 1
	jnz	MINSD1		;NZ: sidew 1 is not readable

	lda	IDSIDE		;side that IDREAD found (0 or 1)
	ora	a		;did we find side 1?
	jnz	GOTSID		;y:a=1 for 1-sided

	dcr	a		;n: this is a 1-sided drive
	jmp	GOTSID		;a=ff

;Error in IDREAD implies a 1-sided disk in a 2-sided drive.
;Reset SIDE and the select table for side 0.

MINSD1:	mvi	a,SIDE0		;select side 0 again
	out	BCTRL

	call	IDREAD		;BSTAT is again current
				;d=found track number
				;e=BPS(0:3)
				;a=0 on success
	jnz	BADISK		;error reading side 0?

	jmp	GOTSID		;a=0 for 1-sided disk
;----------------------------------------------------
;3.5" and 8" Sidedness: Look at BSIDED bit in BSTAT
;----------------------------------------------------
MAXSID:	in	BSTAT		;2-sided 8"?
	cma
	ani	BSIDED		;0 means 1-sided disk
	jz	GOTSID

	mvi	a,1		;remember 2-sided
;----------------------------------------------------------
; Remember sidedness:
; 0 means 1-sided disk in a 1-sided drive
; 1 means 2-sided disk
; FF means 1-sided disk in a 2-sided drive
;----------------------------------------------------------
GOTSID:	sta	SIDED
;-----------------------------
;Report basic stuff about disk
;-----------------------------
	call	CILPRT
	db	LF,'Disk',' '+80h

	call	PDRIVE

	call	ILPRNT
	db	' is a',' '+80h

	lda	SIDED
	inr	a		;FF?
	jz	RPRT01		;y: 1-sided drive

	call	PADEC		;a=1 or 2

	call	ILPRNT
	db	'-sided',' '+80h

RPRT01:	in	BSTAT
	ani	BMINI		;1=5.25" disk
	jnz	RPRT07

;Some sort of high data rate drive detected

	lda	FLAG35		;3.5" detected?
	ora	a
	jz	RPRT02

	call	ILPRNT
	db	'3.','5'+80h
	jmp	RPRT06

;It's either an 8" or an HD 5.25" disk

RPRT02:	mvi	a,MAXTK8
	call	SEEKID
	jnz	RPRT03		;error?

	cpi	MAXTK8
	jz	RPRT04		;y: ok disk

;Error: can't ID last definitely-existing track
;on a high data rate drive (so we don't even know
;what size is the drive.)

RPRT03:	call	ILPRNT
	db	'high data rate',' '+80h
	jmp	RPRT08

RPRT04:	mvi	a,MAXTK8+1
	call	SEEKID
	jnz	RPRT05		;error implies 8"

;It's an 80-track HD 5.25" drive

	cpi	MAXTK8+1	;correct track?
	jnz	RPRT05		;not rightimplies 8"

	call	 ILPRNT
	db	'80-track High Density 5.2','5'+80h	
	jmp	RPRT06

;It's an 8" drive

RPRT05:	call	ILPRNT
	db	'77-track ','8'+80h

RPRT06:	call	ILPRNT
	db	'" dis','k'+80h

	lda	FLAGVC		;Voicecoil detected?
	ora	a
	jz	RPRT14

	call	ILPRNT
	db	' in a voicecoil driv','e'+80h

	jmp	RPRT14

;It's a low data rate 5.25" disk of some sort

RPRT07:	call	ILPRNT
	db	'5.25"',' '+80h

	mvi	a,34
	call	SEEKID
	jnz	RPRT08

	cpi	34		;correct track?
	jz	RPRT09

;Error: can't read the last definitely-existing track

RPRT08:	call	ILPRNT
	db	'disk with track problem','s'+80h
	jmp	RPRT14

;It's some kind of (ok) low-data rate disk

RPRT09:	mvi	a,MAXTK5
	call	SEEKID
	jnz	RPRT10		;ID error implies 35 track

	cpi	MAXTK5		;correct track?
	jz	RPRT11		;really found 39?

;It's a 35-track minidisk

RPRT10:	call	ILPRNT
	db	'3','5'+80h
	jmp	RPRT13

;It's either a 40-track minidisk or an 80-track quad density

RPRT11:	mvi	a,MAXTK5+1
	call	SEEKID
	jnz	RPRT12		;ID error implies 40 track

	cpi	MAXTK5+1	;correct track?
	jnz	RPRT12		;really found the right track?

;It's an 80-track quad density

	call	ILPRNT
	db	'80-track Quad Density 5.25" dis','k'+80h
	jmp	RPRT14	

;It's a 40-track minidisk

RPRT12:	call	ILPRNT
	db	'4','0'+80h

RPRT13:	call	ILPRNT

	db	'-track minidis','k'+80h
	jmp	RPRT14

;roport sidedness for minidisks

	lda	SIDED		;1-sided drive
	inr	a
	jnz	RPRT08

	call	ILPRNT
	db	' in a 1-sided driv','e'+80h

RPRT14:	call	ILPRNT
	db	'.'+80h

;Report write-protect status

	call	CILPRT

	db	'  The disk is',' '+80h

	in	BSTAT
	ani	BWPRT		;0=Write protected
	jz	RPRT15

	call	ILPRNT
	db	'not',' '+80h

RPRT15:	call	ILPRNT
	db	'write-protected','.'+80h

;Report about the data tracks

	call	CILPRT
	db	'  Data tracks are',' '+80h
	call	DENSTY
	call	BPSSPT

;Examine and report about system tracks

	call	CILPRT
	db	'System tracks',':'+80h

;Seek track 0, and report

	mvi	b,0		;track number
	call	SYSTRK

;Seek track 1 and report

	mvi	b,1
	call	SYSTRK

;If it's a minidisk, then seek track 2 and report

	mvi	b,2
	lda	BDCTRL
	ani	DCMAXI
	cz	SYSTRK

;***Exit****
;Normal exit
;***********
	jmp	ABORT

;***Subroutine**************************
;Report the density of the current track
;***************************************
DENSTY:	in	BSTAT
	ani	BDDEN
	jz	DENS1

	call	ILPRNT
	db	'dou','b'+80h
	jmp	DENS2

DENS1:	call	ILPRNT
	db	'sin','g'+80h

DENS2:	call	ILPRNT
	db	'le densit','y'+80h
	ret

;***Subroutine****************************
;Seek a system track, and report its setup
;On Entry:
;  b=track number
;*****************************************
SYSTRK:	call	CILPRT
	db	'  Track',' '+80h

	mov	a,b
	push	b
	call	PADEC
	pop	b

	call	ILPRNT
	db	' is',' '+80h

	mov	a,b		;track to seek
	out	DDATA		;track to seek
	mvi	c,CCSEEK	;seek the desired track
	call	T1DCMD		;Seek is a Type I command

	call	GETID

	jnz	BADTRK

	call	DENSTY
	call	ILPRNT
	db	','+80h

;Fall into BPS

;***Subroutine*******************************
;Report the bytes/sector and sectors/track
;of the current track
;********************************************
BPSSPT:	call	ILPRNT
	db	' with',' '+80h
	lda	IDSSIZ
	lxi	h,64

BPS1:	dad	h
	dcr	a
	jp	BPS1

	call	PHLDEC
	call	ILPRNT
	db	' bytes/sector,',' '+80h

	lda	IDSPT
	call	PADEC
	call	ILPRNT
	db	' sectors/track','.'+80h

	ret

;***Subroutine***
;Report bad trak
;*****************
BADTRK:	call	ILPRNT
	db	'not readable','.'+80h
	ret

;***Subroutine********************************************
;Seek track and read its ID using WD1793 'read address'
;  command, and compute parameters for this track.
;  Search for the highest sector number on this track.
;On Entry:
;  a=track too seek
;  Disk is selected
;  BDCTRL = DCTRL value
;On Success Exit:
;  a=found track number
;  BDCTRL=DCTRL value updated with density
;  Density set for this track in DCTRL
;  Z set
;  Drive is selected
;  DTRCK matches the found track number
;  IDSAVE bytes are current for the current track
;  IDSPT has the highest sector found
;  interrupts are disabled
;On Error Exit:
;  Z cleared
;Trashes bc,de,hl
;*********************************************************
SEEKID:	out	DDATA		;track to seek
	mvi	c,CCSEEK	;seek the desired track
	call	T1DCMD		;Seek is a Type I command
				;trashes de, selects side 0

	mvi	b,2		;set up to read data 6 bytes
	lxi	h,SISAVE	;put ID read data here
	mvi	d,RADRCM	;Read address without head load delay

	call	RDPSEC		;On return, Z means good read
	rnz			;error on ID: Z cleared

;Set the track register according to what we found
	in	DSCTR		;track # from 'read address'
				;The 1793 put it in its
				;..sector reg
	out	DTRCK		;set track reg

;Normal exit with a=track, Z set
	ret			;Normal exit

;***Subroutine********************************************
;Read current track ID using WD1793 'read address'
;  command, and compute parameters for this track.
;  Search for the highest sector number on this track.
;On Entry:
;  Disk is selected
;  BDCTRL = DCTRL value
;On Success Exit:
;  a=0
;  BDCTRL=DCTRL value updated with density
;  Density set for this track in DCTRL
;  Z set
;  Drive is selected
;  DTRCK matches the found track number
;  IDSAVE bytes are current for the current track
;  IDSPT has the highest sector found
;  interrupts are disabled
;On Error Exit:
;  Z cleared
;Trashes bc,de,hl
;*********************************************************
GETID:	xra	a		;No sector found yet

SPTLUP:	mov	c,a
	call	IDREAD		;Get ID
				;d=found track number
				;e=BPS(0:3)
				;density set in BDCTRL
	rnz			;error reading track ID?

	lda	IDSECT
	cmp	c		;smaller than the last one?
	jnc	SPTLUP		;n: keep looking

	mov	a,c		;SPT
	sta	IDSPT

	xra	a		;success
	ret

;***Subroutine********************************************
;Read current track ID using WD1793 'read address'
;  command, and compute parameters for this track.
;On Entry:
;  Disk is selected
;  BDCTRL = DCTRL value
;On Success Exit:
;  a=0
;  BDCTRL=DCTRL value updated with density
;  Density set for this track in DCTRL
;  Z set
;  Drive is selected
;  DTRCK matches the found track number
;  IDSAVE bytes are current for the current track
;  interrupts are disabled
;On Error Exit:
;  Z cleared
;Trashes b,de,hl
;*********************************************************
IDREAD:	mvi	a,RATRYS*2	;set up retry counter
				;(2x for density tries)
IDRTRY:	sta	RCOUNT
;---------------------------------------------------
;Set up and use RDPSEC to read the track ID into RAM
;at IDSAVE. Always include the head-load delay since
;this only gets called prior to a track read or
;write - almost always on a new track.
;loaded, to avoid an unnecessary head-load delay.
;---------------------------------------------------
	lxi	h,IDSAVE	;put 'read address' data here

	mvi	b,2		;set up to read data 6 bytes

	mvi	d,RADRCM	;Read address without head load delay

IDRD1:	call	RDPSEC		;On return, Z means good read

	jnz	IDRDER		;go deal with read error on ID
				;(may need to toggle density)
;----------------------------------------------------
;Set the track register according to what we found
;----------------------------------------------------
	in	DSCTR		;track # from 'read address'
				;The 1793 put it in its
				;..sector reg
	out	DTRCK		;set track reg
;---------------------------
;Normal exit with a=0, Z set
;---------------------------
	xra	a		;a=0, set Z: no error
	ret			;Normal exit
;----------------------------------------------------------
;Error during 'read address'. Toggle density and try again.
;----------------------------------------------------------
IDRDER:	lda	BDCTRL
	xri	DCDDEN		;toggle density in PRMTBL
	sta	BDCTRL

	lda	RCOUNT
	dcr	a		;bump retry counter
	rm			;too many tries: Z clear

	jmp	IDRTRY

;***Subroutine*******************************************
;Read sector ID
;Fast as possible: 30 cycles (15 uS) in-to-in.
;Too slow for a 2 MHz CPU.
;On Entry:
;  b=2 for read address command
;  d = WD1793 Read Address command
;  hl=DMA address
;  BDCTRL = DCTRL value without Auto-wait enabled
;On Exit:
; Z set and a=0 if success
; Z clear and a=error flags if error detected
;Trashes b,hl
;Stack depth: 0
;*******************************************************
RDPSEC:	lda	BDCTRL		;select, density, size
	ori	DCAUTOW		;enable auto-wait
	out	DCTRL

	mov	a,d		;read or ID sector cmd
	out	DCMMD		;issue WD1793 command

	jmp	RDPS2

RDPS1:	in	DDATA	;(11) 28 cycles in-to-in

	inx	h	;(6)
	mov	m,a	;(7)
	inx	h	;(6)
	in	DDATA	;(11) 30 cycles in-to-in

	mov	m,a	;(7)
	inx	h	;(6)
RDPS2:	in	DDATA	;(11) 24 cycles in-to-in

	mov	m,a	;(7)
	inx	h	;(6)
	dcr	b	;(4) set/clear Z for end of loop
	in	DDATA	;(11) 28 cycles in-to-in

	mov	m,a	;(7)
	JNZ	RDPS1	;(10)

;Check for all the possible errors during a sector read
;or a read address. (b=0 on entry)

EORW:	in	DFLAG		;Wait for interupt bit
	rrc			;1793 DFINTRQ flag set?
	jc	EORW1		;y: command complete

	dcr	b
	jnz	EORW		;n:keep waiting

;Controller failure: read/ID command did not complete

	jmp	EORW2		;drive not ready

EORW1:	in	DSTAT		;disk status
	ani	SNOTRDY+SRNFER+SCRCERR+SLOSTD+SRCTYF
	rp

EORW2:	jmp	DNRERR		;drive not ready?

;***Subroutine*********************************************
;Restore selected disk. Determine whether or not it is an
;8" disk. If so, then set up DCTRL & BDCTRL for an 8"
;1-sided disk.
;
;Note that some drives will report 'not ready' if you
;select them with side 1 when a 1-sided disk is in.
;On Entry:
;  BDCTRL=DCTRL value with select bits correct,
;    set up for 5.25" disk
;On Exit:
;  Abort if track 0 not found (error)
;  BDCTRL=DCTRL value updated for drive type
;  BCTRL is written for side 0
;  FLAGVC set if voice-coil 8" drive detected
;  FLAG25 set if 3.5" drive detected
;Trashes bc,de
;**********************************************************
RESTOR:
;--------------------------------------------------------
;Step in one track on the disk specified by BUFDSK first,
;since some disk drives can over-step past track 0
;--------------------------------------------------------
	call	STEPIN		;trashes de, select side 0

;-------------------------------------------------
;actually restore the disk specified by BUFDSK now
;-------------------------------------------------
	mvi	c,CCRSTOR
	call	T1DCMD		;Restore is a type I command
				;trashes de, select side 0
				;return disk status

	ani	STRACK0		;on track 0?
	jz	TK0ERR		;n: error exit
;---------------------------------------------------
;Test for a 5.25" disk while on track 0. Set up this
;drive for 8" or 3.5" drive if a 5.25" disk is not
;detected. (PRMTBL was initialized for 5.25" disks.)
;---------------------------------------------------
	in	BSTAT		;read BSTAT while on track 0
	ani	BTRK0		;1 if 8" disk while on track 0
	rz			;5.25": already set up

;Set up for faster data rate

	lda	BDCTRL
	ori	DCMAXI		;It's an 8" disk
	sta	BDCTRL

;---------------------------------------------------------
;This is either an 8" drive of some sort, or it is a 3.5"
;drive. 8" drives spin at 360 RPM, while 3.5" drives spin
;at 300 RPM. Measure the index pulse rate to determine
;which size drive we have. (accuracy is not important.)
;Wait for a leading edge, then time to the next edge.
;Note that the CPU may be running at either 2 or 4 MHz.
;---------------------------------------------------------
	lxi	b,3*256+0	;b=loop counter
				;c=inverter

RTLUP1:	mvi	d,0		;timer

;Time the index. 56 cycles per pass:
;First find a rising edge, then time
;to the next falling edge
	
RTLUP2:	inx	d		;(6)Bump timer
	mov	a,d		;(4)
	ora	e		;(4)Timeout?
	jz	INDTO		;(10)y: error

	in	BSTAT		;(11)
	xra	c		;(4)maybe invert

	ani	BINDEX		;(7)active-low signal
	jnz	RTLUP2		;(10)

	mov	a,c		;look for the other BINDEX
	cma			;..state next pass
	mov	c,a	

	dcr	b
	jnz	RTLUP1

;b=0, c=FF
;de=index time in units of 56 CPU cycles.
;Decide whether this disk is spinning at 300 RPM
;(3.5" drive) or at 360 RPM (8" drive).  Remember
;that the CPU may be running at 2 MHz or 4 MHz.
;
;Nominal values:
;         2 MHz CPU        4 MHz CPU
; RPM    cycles   de      cycles   de
; 300    400000  7142     800000  14286
; 360    333333  5952     666666  11905
;
;If de > 9728 (2600H) then assume the CPU is running
;at 4 MHz, and divide result by 2. Next, assume the
;disk is spinning at 300 RPM if de>6656 (1A00H)

	mov	a,d		;2MHz or 4 MHz CPU?
	cpi	26h		;carry set if 2 MHz
	jc	MHZ2
	rar			;divide Measurement by 2
MHZ2:
	cpi	1Ah		;carry set if 360 RPM (8")

	mov	a,c		;(c=FF)
	adc	b		;(b=0) a=FF iff 3.5"
	sta	FLAG35

	rnz

;-----------------------------------------------------------
;Test to see of SEEK COMPLETE signal (from the drive) is
;true. SEEK COMPLETE will clear the HLT flip-flop, which
;would otherwise keep the SHEADLD bit (in the WD1793 status
;register) low for about 35 mS after the rising edge of HLD.
;-----------------------------------------------------------
	in	DTRCK		;set up for null seek
	out	DDATA

	mvi	a,SEEKCM	;head not loaded
	call	DT1CMD		;unload, wait completion

	mvi	a,SEEKCM+HDLOAD	;create rising edge on HLD
	call	DT1CMD		;load, wait completion

	ani	SHEADLD		;test HLT bit
	sta	FLAGVC		;NZ means voicecoil drive

	ret

;***Subroutine*********************************
;Step in one track and wait for completion
;On Entry:
;  Disk is selected in DCTRL
;On Exit:
;  Side 0 is selected in BCTRL
;  a=disk status: 0 if not busy and not track 0
;  Z set if a=0
;  Abort if drive not ready
;Trashes c,de
;**********************************************
STEPIN:	mvi	c,CCSTEPI

;Fall into T1DCMD

;***Subroutine*********************************************
;Issue Type I command to the WD1793 and wait for completion
;On Entry:
;  c=DCMMD value
;  Disk is selected in DCTRL
;On Exit:
;  Side 0 is selected in BCTRL
;  a=disk status: 0 if not busy and not on track 0
;  Z set if a=0
;  Abort if drive not ready
;Trashes c,de
;*********************************************************
T1DCMD:	mvi	a,SIDE0		;select side 0, in case a	
	out	BCTRL		;..1-sided disk is installed

	call	WATRDY		;wait for drive to be ready
				;trashes de

	mov	a,c		;disk command

;Fall into DT1CMD

;***Subroutine*************************************************
;Issue a Type 1 command (Seek, Step, or Restore) to the WD1793,
;and then wait for the WD1793 to complete, with timeout.
;On Entry:
;  a=complete WD1793 command
;  BCTRL is set up with side and fast seek (as needed)
;On Exit:
;  WD1793 command has been executed and completed
;  a=disk status bits: Busy, Track 0, Head Load
;Rude abort to WBOOT if the drive is not ready or timeout
;Trashes c,de
;113 cycles fastest path
;**************************************************************
DT1CMD:	out	DCMMD		;(11)issue command to WD1793

;Wait for command to complete, with 2.5 second timeout
;(longer than the worst-case seek on a 5.25" drive)

	lxi	d,902		;(10)(2.5 second timeout)

;Inner loop: 2.7 mS (a little random on the first pass)

DT1DC1:	dcr	c		;(4)
	jnz	DT1DC2		;(10)

	dcx	d		;(6)
	mov	a,d		;(4)
	ora	e		;(4) clears carry too
	jz	DT1DC3		;(10)Timeout?

DT1DC2:	in	DFLAG		;(11)wait for completion
	rar			;(4)WD1793 INTRQ?

	jnc	DT1DC1		;(10)n: keep waiting

;Read and mask the disk controller status. Abort with
;'drive not ready' if the drive is not ready.

	in	DSTAT		;(11)WD1793 status
	ani	SNOTRDY+STRACK0+SBUSY+SHEADLD	;(7)ready?
	rp			;(10)done unless drive not ready

;Abort: the drive is not ready

DT1DC3:	jmp	DNRERR		;error abort

;***Subroutine*******************************************
;Wait for up to 675 mS (at least 3 index-pulse times) for
;the drive to be ready, reboot if timeout.
;On Exit:
;  BCTRL written
;Trashes psw, de
;********************************************************
WATRDY:	lxi	d,60000		;45 x 60,000/4= 675 mS

WRDY1:	in	DSTAT		;(11)disk status, clear INTRQ
	ral			;(4)Drive ready?
	rnc			;(5)y:done

	dcx	d		;(6)
	mov	a,d		;(4)
	ora	e		;(4)timeout?
	jnz	WRDY1		;(10)n: keep waiting

;Fall into DNRERR

;***Error Abort*****
;Drive not ready
;Report error, abort
;*******************
DNRERR:	call	CILPRT
	db	'Drive',' '+80h

	call	PDRIVE

	call	ILPRNT
	db	' not read','y'+80h

	jmp	ABORT

;***Error Exit******
;Bogus input
;*******************
CMDERR:	call	CILPRT
	db	'Huh?',CR,LF
	db	'Usage: DISKID <drive>',':'+80h

	jmp	WBOOT

;***Error Exit******
;Disk error
;*******************
BADISK:	call	CILPRT
	db	'Disk erro','r'+80h

	jmp	ABORT

;***Error Abort*****
;Track 0 not found
;Report error, abort
;*******************
TK0ERR:	call	CILPRT
	db	'Track 0 failur','e'+80h

	jmp	ABORT

;***Error Abort*****
;No index pulses
;Report error, abort
;*******************
INDTO:	call	CILPRT
	db	'Index failur','e'+80h

	jmp	ABORT

;***Subroutine****************************************
; Get CR or ^C from user
; On Entry:
;   DISKNO=drive number (0-3)
;   b<>0 if aborting
; Trashes a
;*****************************************************
GETRET:	xra	a		;deselect drive
	out	DCTRL		;..to deselect the drive

	call	CILPRT
	db	CR,LF,'Put',' '+80h

	mov	a,b		;Say CP/M?
	ora	a
	jz	GTRT1

	call	ILPRNT
	db	'CP/M',' '+80h

GTRT1:	call	ILPRNT
	db	'disk in drive',' '+80h

	call	PDRIVE

	call	ILPRNT
	db	' and press Return',':'+80h

	mvi	a,CONIN
	call	GOBIOS

	cpi	CR
	rz

	dcr	b
	inr	b		;Aborting already?
	jnz	GETRET

	cpi	CTRLC
	jnz	GETRET		;Keep trying

;Fall into ABORT

;***Exit***************************************
;;Wait for user to insert CP/M disk, then WBOOT
;**********************************************
ABORT:	xra	a		;Boot from A
	sta	DISKNO
	mvi	b,1		;aborting
	call	GETRET

	jmp	WBOOT

;***Subroutine****************************************
; Print drive letter
; On Entry:
;   DISKNO=drive number (0-3)
; Trashes a
;*****************************************************
PDRIVE:	lda	DISKNO
	adi	'A'
	jmp	PRINTA

;***Subroutine****************************************
; Print CR, LF, then In-line Message
;  The call to CILPRT is followed by a message string.
;  The last message chr has its msb set.
; On Exit:
;   Z cleared
; Trashes a
;*****************************************************
CILPRT:	call	ILPRNT
	db	CR,LF+80H

; fall into ILPRNT

;***Subroutine****************************************
; Print In-line Message
;  The call to ILPRNT is followed by a message string.
;  The last message chr has its msb set.
; On Exit:
;   Z cleared
; Trashes a
;*****************************************************
ILPRNT:	xthl			;Save hl, get msg addr
	push	b

IPLOOP:	mov	a,m
	ani	7Fh		;strip end marker
	call	PRINTA		;print byte
	mov	a,m		;end?
	inx	h		;Next byte
	ora	a		;msb set?
	jp	IPLOOP		;Do all bytes of msg

	pop	b
	xthl			;Restore hl,
				;..get return address
	ret

;=====Subroutine===================
; Print a as decimal on the console
; Trashes all registers
;==================================
PADEC:	mov	l,a
	mvi	h,0

;fall into PHLDEC

;=====Subroutine================================
;Print hl as decimal on the console
;Trashes all registers
;===============================================
PHLDEC:	mvi	d,0	;Suppress leading 0's

	lxi	b,-10000
	call	DECDIG
	lxi	b,-1000
	call	DECDIG
	lxi	b,-100
	call	DECDIG
	lxi	b,-10
	call	DECDIG

	mov	a,l	;last digit is simple
	jmp	DECDG0	;with leading 0's

;------------------------------------------------
; Local subroutine to divide hl by power of 10 in
; bc and print 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'

;Fall into PRINTA

;***Subroutine*******************
; Print character in a on console
; trashes psw,c
;********************************
PRINTA:	mov	c,a		;value to c for PRINTC

; fall into PRINTC

;***Subroutine*******************
;Print character in c on console
;trashes psw
;********************************
PRINTC:	mvi	a,CONOUT

; fall into GOBIOS

;***Subroutine**********************
;Go call a BIOS driver directly
;On Entry:
;  c=value for BIOS routine, if any
;  a = BIOS call address offset
;On Return:
;  psw as BIOS left it
;***********************************
GOBIOS:	push	h
	push	d
	push	b

	call	DOBIOS

	pop	b
	pop	d
	pop	h
	ret			;done

;***Subroutine**********************
;Go call a BIOS driver directly
;On Entry:
;  c=value for BIOS routine, if any
;  a = BIOS call address offset
;On Return:
;  all regs as BIOS left them
;***********************************
DOBIOS:	lhld	WBOOTA		;get BIOS base address
	mov	l,a		;a has jump vector

	pchl			;'call' BIOS routine

;---------------------------
;Initialized RAM Variables
;---------------------------
FLAG35:	db	0		;NZ: 3.5" drive detected
FLAGVC:	db	0		;NZ: voicecoil drive detected
DISKNO:	db	0		;user-selected disk (0-3)
BDCTRL:	db	0		;DCTRL value
SIDED:	db	0		;sided-ness
IDSPT:	db	0		;found sectors/track
RCOUNT:	db	0		;retry counter

;-----------------------------------------------
;Sector ID area
;The WD1793 Read Address command reads 6 bytes
;from the header of the next sector encountered.
;IDREAD writes these 6 bytes here.
;-----------------------------------------------
IDSAVE:
IDTRAC:	ds	1	;track number
IDSIDE:	ds	1	;side number
IDSECT:	ds	1	;sector address
IDSSIZ:	ds	1	;BPS(0:3)
IDCRC1:	ds	1	;CRC 1
IDCRC2:	ds	1	;CRC 2

;-----------------------------------------------
;Sector ID area
;The WD1793 Read Address command reads 6 bytes
;from the header of the next sector encountered.
;SEEKID writes these 6 bytes here.
;-----------------------------------------------
SISAVE:
SITRAC:	ds	1	;track number
SISIDE:	ds	1	;side number
SISECT:	ds	1	;sector address
SISSIZ:	ds	1	;BPS(0:3)
SICRC1:	ds	1	;CRC 1
SICRC2:	ds	1	;CRC 2





	end
