;--------------------------------------------------------------------------
;
;  FORMAT.ASM - Format utility for the FDC+3712
;     The FDC+3712 is the iCOM/Pertec FD3712 and its S-100 interface board
;     duplicated by the Altair FDC+ and 8" drives. The original FD3712
;     cannot format a disk, but the FDC+3712 provides a format capability
;     very similar to that of the FD3812.
;
;     This code is the FD3812 format program modified to only allow sel-
;     ection of the single sided, single density options and to pass data
;     to the controller in the 3712 manner vs the 3812 manner.
;
;     The original FD3812 program was disassembled by Patrick Linstruth.
;     This FDC+3712 update was made by Mike Douglas, 09/21.
;
;--------------------------------------------------------------------------

; Disk equates

NUMSEC	equ	26		;sectors per track
NUMTRK	equ	77		;number of tracks
MAXDRV	equ	4		;maximum drive number

; FD3812 controller commands

cSTATUS	equ	00h		;read status
cREAD	equ	03h		;read sector
cWRITE	equ	05h		;write sector
cRDCRC	equ	07h		;validate sector CRC
cSEEK	equ	09h		;seek to track
cCLRERR	equ	0Bh		;clear error flags
cRESTOR	equ	0Dh		;seek to track 0
cSETTRK	equ	11h		;set track to seek
cDRVSEC	equ	21h		;set unit and sector
cWRTBUF	equ	31h		;write buffer
cRDBUF	equ	40h		;read buffer
cSHIFT	equ	40h		;shift read buffer
cRESET	equ	81h		;reset the contorller
cLDCFG	equ	15h		;load configuration

; Controller status bits

sBUSY	equ	01h		;1=busy
sCRCERR	equ	08h		;1=CRC error
sWRTPRT	equ	10h		;1=write protected
sNOTRDY	equ	20h		;1=drive not ready
sDDM	equ	80h		;1=deleted data mark

; Interface board registers - The original board is at C0h/C1h, and the FDC+
;    can be patched to use that address, but it is not simple. For this
;    reason, the standard FDC+ address of 08/09 is used.

CMDOUT	equ	08h		;command output register
DATAIN	equ	08h		;data input from controller
DATAOUT	equ	09h		;data out to controller

; CP/M equates

WBOOT	equ	0		;CP/M warm boot
BDOS	equ	5		;BDOS jump address
CONIN	equ	1
CONOUT	equ	2

; Misc equates

CR	equ	0Dh
LF	equ	0Ah
BS	equ	08h
TAB	equ	09h

	org	00100h

	jmp	entry

	db	'FDC+3712 FORMAT V1.4.13'

entry	lxi	h,mTitle	;display title
	call	dispMsg	

getDrv	lxi	h,mDrive	;prompt for drive number
	call	dispMsg	
	call	getCh
	cpi	CR		;anything entered?
	jnz	setDrv		;yes
	mvi	a,'1'		;else, default default to drive 1

setDrv	sta	mDrvSD		;update messages with drive number
	sta	mDrvDD
	sui	'0'		;ASCII to decimal
	sta	drvNum		;save drive number in drvNum

	cpi	MAXDRV		;validate drive number
	jnc	getDrv

   if 0
getType	lxi	h,mType		;prompt for disk type (density)
	call	dispMsg	
	call	getCh
	cpi	CR		;anything entered?
	jnz	setType		;yes
	mvi	a,'1'		;else default to double density

setType	sui	'0'		;ASCII to decimal
	sta	dskType		;save in dskType
	cpi	2		;valid values are 0 and 1
	jnc	getType
   endif
   
	xra	a		;force single density
	sta	dskType

fmtPmpt lda	dskType		;0=SD,1=DD
	ora	a		;double density?
	lxi	h,mFmtSD
	jz	disPmpt		;no, display SD message
	lxi	h,mFmtDD	;else, DD message

disPmpt call	dispMsg		;display press return prompt
	call	getCh
	cpi	'B'		;if 'B'oot
	jz	cpmExit		;exit to CP/M

	cpi	CR
	jnz	getDrv		;if not CR, start over

	lxi	h,mFmting	;display "formatting" message
	call	dispMsg

	lda	dskType		;0=SD,1=DD
	push	psw		;save dskType
	ani	1		;bit 0 is density
	sta	trkDen		;0=SD,1=DD
	sta	dskDen		;0=SD,1=DD

	pop	psw		;a=dskType
	rar			;move double sided bit to bit 0
	ani	1
	sta	dsFlag		;save double sided flag

	lda	vfyOnly		;verify only if not FF and not 0
	inr	a
	jz	initAll
	dcr	a

initAll	sta	vfyOnly		;save verify flag

	mvi	a,cCLRERR	;clear controller errors
	call	doCmd		;  (not needed since reset next)
	mvi	a,cRESET	;reset controller
	call	doCmd

	lxi	h,sideNum	;initialize variables
	xra	a
	mov	m,a		;sideNum=0

	inx	h		;move hl->trkNum
	inx	h
	inx	h
	mov	m,a		;trkNum=0

	inx	h		;move hl->fmtMode
	inr	a
	mov	m,a		;fmtMode=1

	lda	vfyOnly		;if verify only
	ora	a		;jump to verify
	jnz	vfyTrk0

  if 0
	mvi	e,0		;send controller 256 byte of E5
	mvi	a,cWRTBUF
	out	CMDOUT
	mvi	a,0E5h

fillBuf	out	DATAOUT
	dcr	e
	jnz	fillBuf

	xra	a		;clear write buffer command
	out	CMDOUT
  endif
   
 	mvi	e,128		;send controller 128 bytes of E5
	mvi	a,0E5h
	out	DATAOUT	

fillBuf	mvi	a,cWRTBUF	;write byte to the controller buffer
	out	CMDOUT
	xra	a		;return bit 0 low
	out	CMDOUT	
	dcr	e		;decrement byte counter
	jnz	fillBuf

wrtTrk	call	drvSec		;select drive and sector
	call	setCfg		;set density and format config
	call	seekTrk		;seek to track
	call	drvSec		;select drive and sector

	mvi	a,cWRITE	;issue WRITE command
	call	doCmdCk	

	lxi	h,sideNum	;if side 1, switch to side 0
	mov	a,m
	ana	a
	jnz	wSide0

	lda	dsFlag		;double sided disk?
	ana	a
	jnz	wSide1		;yes, switch to side 1

wNxtTrk	lxi	h,trkNum	;increment track number
	inr	m
	mov	a,m
	cpi	NUMTRK
	jnz	wrtTrk		;loop to format all tracks

	jmp	wrtTrk0		;done, now update sec 0, track 0

wSide1	mvi	a,1		;hl->sideNum here (could be mvi m,1)
	mov	m,a		;set to side 1 
	jmp	wrtTrk		;format top side of current track

wSide0	xra	a		;hl->sideNum
	mov	m,a		;set to side 0
	jmp	wNxtTrk		;next track

vfyTrk0	xra	a
	sta	trkNum		;trkNum = 0
	sta	sideNum		;sideNum = 0
	sta	fmtMode		;fmtMode = 0

vfyTrk	call	drvSec		;select drive and sector
	call	setCfg		;set density and format config
	call	seekTrk		;seek track

vfyLoop	call	drvSec		;select drive and sector
	mvi	e,3		;retry 3 times

vfyRtry mvi	a,cRDCRC	;issue READ CRC command

	call	doCmd
	ani	sCRCERR		;CRC error?
	jz	vNxtSec		;no error, verify next sector

	call	dCRCErr		;handle CRC error
	call	clrErrs		;clear errors on controller
	dcr	e		;decrement retry count
	jnz	vfyRtry		;try again

	jmp	fmtDone		;give up

vNxtSec	lxi	h,secNum	;increment sector number
	inr	m
	mov	a,m
	cpi	NUMSEC+1	;all sectors verified?
	jnz	vfyLoop		;no, very next sector

	mvi	m,1		;secNum = 1
	lxi	h,sideNum	;just finished side 1?
	mov	a,m
	ana	a
	jnz	vSide0		;verify side 0 of next track

	lda	dsFlag		;double sided disk?
	mov	a,m
	ana	a
	jnz	vSide1		;yes, verify top side for current track

vNxtTrk	lxi	h,trkNum	;increment track number
	inr	m
	mov	a,m
	cpi	NUMTRK		;all tracks verified?
	jnz	vfyTrk		;no, verify next track

	jmp	fmtDone		;all done

vSide1	mvi	m,1		;verify side 1
	jmp	vfyTrk

vSide0	mvi	m,0		;verify side 0
	jmp	vNxtTrk	

dCRCErr	lxi	h,mCRC		;display CRC error
	call	dispMsg
	lhld	secNum		;sector number in hl
	mvi	h,0
	jmp	dSecTrk		;display sector number and track

; dispHL as decimal value

dispHL	push	b
	push	d
	push	h
	lxi	b,-10
	lxi	d,-1

dHLLoop dad	b
	inx	d
	jc	dHLLoop

	lxi	b,10
	dad	b
	xchg
	mov	a,h
	ora	l
	cnz	dispHL

	mov	a,e
	adi	'0'
	mov	e,a
	mvi	c,CONOUT
	mov	e,a
	call	BDOS
	pop	h
	pop	d
	pop	b
	ret

dSecTrk	call	dispHL		;display sector number in hl
	lxi	h,mTrack	;display " TRACK " message
	call	dispMsg
	lhld	trkNum		;display track number
	mvi	h,0
	call	dispHL
	ret
	
  if 0
wrtTrk0	mvi	e,127		;write 127 bytes of E5
	mvi	a,cWRTBUF
	out	CMDOUT
	mvi	a,0e5h

wTrk0Lp	out	DATAOUT
	dcr	e
	jnz	wTrk0Lp

	lda	dskType		;00=SD,01=DD
	rlc			;00=SD,02=DD
	out	DATAOUT		;write last byte as 00 or 02

	xra	a		;remove write command
	out	CMDOUT
  endif

wrtTrk0	mvi	e,127		;write 127 bytes of E5
	mvi	a,0E5h
	out	DATAOUT

wTrk0Lp	mvi	a,cWRTBUF	;write byte to the controller buffer
	out	CMDOUT
	xra	a		;return bit 0 low
	out	CMDOUT	
	dcr	e		;decrement byte counter
	jnz	wTrk0Lp

	lda	dskType		;00=SD,01=DD
	rlc			;00=SD,02=DD
	out	DATAOUT		;write last byte as 00 or 02
	mvi	a,cWRTBUF
	out	CMDOUT
	xra	a		;return bit 0 low
	out	CMDOUT	

	sta	trkNum		;trkNum = 0
	sta	trkDen		;trkDen = SD
	sta	fmtMode		;fmtMode = 0
	sta	sideNum		;sideNum = 0
	inr	a
	sta	secNum		;secNum = 1

	call	setCfg		;set format and density controller config
	call	drvSec		;select drive and sector
	mvi	a,cRESTOR	;seek to track 0
	call	doCmdCk

	mvi	e,5		;E=retry count

wTk0Sc1	mvi	a,cWRITE	;write sector 1 on track 0
	call	doCmd
	ani	sCRCERR+sWRTPRT+sNOTRDY
	jz	vfyTrk0		;verify if no errors

	call	clrErrs		;clear errors

	dcr	e		;decrement retry counter
	jnz	wTk0Sc1		;try again

	in	DATAIN		;get final status
	ani	sCRCERR		;mask CRC error
	jz	cmdErr		;if error other than CRC

	lxi	h,mCRCSys	;display CRC error message
	call	dispMsg

	jmp	fmtDone		;give up

seekTrk	lxi	h,trkNum	;hl->track number
	mov	a,m		;A=current track
	ana	a
	jnz	setTrk		;track > 0

	lda	sideNum	
	ana	a
	jz	setTrk0		;track 0, side 0, always SD

	lda	dskDen		;A=0 for SD, 1 for DD

setTrk0	sta	trkDen		;save in trkDen (not used anywhere)
	mvi	a,cRESTOR	;seek to track 0
	jmp	doCmdCk

setTrk	out	DATAOUT		;A=track to seek to
	mvi	a,cSETTRK	;set track on controller
	call	doCmd

	lda	dskDen		;get disk format
	sta	trkDen		;save in trkDen
	mvi	a,cSEEK		;seek to track

doCmdCk	call	doCmd		;issue command to controller
	ani	sWRTPRT+sNOTRDY	;write protect or not ready?
	jnz	cmdErr		;display error

	in	DATAIN		;return status in A
	ret

drvSec	lda	drvNum		;drive number in A
	ani	3		;0-3
	rrc			;move to bits 6,7
	rrc
	mov	b,a		;save in B

	lda	sideNum		;side select
	rrc			;move to bit 5
	rrc
	rrc
	ora	b		;combine with drive select
	mov	b,a

	lda	secNum		;sector number in A
	ani	01fh		;mask bits 0-4
	ora	b		;combine with drive and side selects

	out	DATAOUT		;store on controller
	mvi	a,cDRVSEC	;issue command to controller
	jmp	doCmdCk	

setCfg	lxi	h,trkNum	;hl->trkNum
	lda	sideNum		;side 0 and track 0?
	ora	m
	jz	loadCfg		;yes, config for single density

	lda	dskDen		;A 0=SD,1=DD

loadCfg	rrc			;move to bit 7
	mov	b,a		;save in B
	lda	fmtMode		;load fmtMode bit in A
	ora	b		;combine with density bit
	rrc			;move to bits 4,5
	rrc
	rrc

	out	DATAOUT		;load config on controller
	mvi	a,cLDCFG
	jmp	doCmdCk

cmdErr	in	DATAIN		;get error status
	ani	6		;mask drive number
	rrc
	ori	'0'		;convert to ASCII
	sta	mDrv		;store in message text
	lxi	h,mDrvNo	;display drive number
	call	dispMsg

	in	DATAIN		;get error status
	ani	sWRTPRT		;write protect error?
	jz	tNotRdy		;no, test for not ready
	lxi	h,mWrtPro	;display write protect error

errExit	call	dispMsg		;display error
	call	clrErrs		;clear errors on controller
	jmp	fmtDone		;done

tNotRdy	in	DATAIN		;get error status
	ani	sNOTRDY		;not ready?
	jz	tMedErr		;no, test for media error
	lxi	h,mNotRdy	;display not ready error
	jmp	errExit

tMedErr	in	DATAIN		;get error status
	ani	sDDM		;test for deleted data mark
	jz	pgmErr		;no, general program error
	lxi	h,mMedErr	;display media error
	jmp	errExit

pgmErr	in	DATAIN		;get error status
	mov	l,a		;save error in HL
	mvi	h,0
	call	dispHL		;display error value
	mvi	c,' '
	call	putCh
	lxi	h,mPgmErr	;display program error msg
	jmp	errExit

fmtDone	lxi	h,mMenu		;display format done menu
	call	dispMsg
	call	getCh		;get characer
	ani	07Fh		;mask off parity
	cpi	'B'		;boot back to CP/M
	jz	cpmExit

	cpi	'N'		;new parameters
	jz	getDrv

	cpi	'R'		;run again
	jz	fmtPmpt

	mvi	c,03fh		;display '?'
	call	putCh

	jmp	fmtDone		;prompt again

;--------------------------------------------------------------------------
; dispMsg - display message on console. HL=message length.
;   HL+1=start of message.
;--------------------------------------------------------------------------
dispMsg	mov	a,m		;A=length of message
	inx	h		;skip to first character

dispLp	mov	c,m		;C=character to print
	push	psw
	push	h
	call	putCh		;print character
	pop	h
	pop	psw
	dcr	a		;decrement counter
	inx	h		;next character
	jnz	dispLp

	ret

clrErrs	mvi	a,cCLRERR	;clear errors on controller
	jmp	outCmd

doCmd	call	outCmd		;issue command to controller

waitCmd	in	DATAIN		;wait for busy to clear
	ani	sBUSY
	jnz	waitCmd

	in	DATAIN		;return status in A
	ret

outCmd	out	CMDOUT		;issue command to controller

	xra	a		;issue status command
	out	CMDOUT
	ret

;--------------------------------------------------------------------------
; putCh - display character C on console.
;--------------------------------------------------------------------------
putCh	push	b
	push	d
	push	h
	mov	e,c
	mvi	c,CONOUT
	call	BDOS
	pop	h
	pop	d
	pop	b
	ret

;--------------------------------------------------------------------------
; getCh - get character from console, return in A.
;--------------------------------------------------------------------------
getCh	push	b
	push	d
	push	h
	mvi	c,CONIN
	call	BDOS
	pop	h
	pop	d
	pop	b
	ret

	mvi	a,'1'

getCR	mov	c,a
	push	b
	call	getCh
	pop	b
	cpi	CR
	jnz	getCR
	ret

cpmExit	lxi	h,mReboot	;display load CP/M disk message
	call	dispMsg
	call	getCh		;wait for CR
	cpi	CR
	jnz	cpmExit

	jmp	WBOOT		;CP/M warm boot

mTitle	db	mDrive-$-1
	db	CR,LF
	db	'FORMAT PROGRAM VERSION 1.4.13',CR,LF
	db	'FOR FDC+3712 DISK SYSTEM',CR,LF

mDrive	db	mType-$-1
	db	CR,LF
	db	'DRIVE NO. (0-3) ? '
	
mType	db	mInter-$-1
	db	CR,LF
	db	'DISKETTE TYPE (0=SD, 1=DD) ? '
   
mInter	db	mNotRdy-$-1
	db	CR,LF
	db	'INTERLACE = '
   
mNotRdy	db	mWrtPro-$-1
	db	'NOT READY',CR,LF

mWrtPro	db	mCRC-$-1
	db	'WRITE PROTECTED',CR,LF

mCRC	db	mTrack-$-1
	db	CR,LF
	db	'CRC ERROR SECTOR '

mTrack	db	mMenu-$-1
	db	' TRACK '

mMenu	db	mPgmErr-$-1
	db	CR,LF
	db	'WARM BOOT, RUN AGAIN OR NEW PARAMETERS (B, R OR N) ? '

mPgmErr	db	mMedErr-$-1
	db	'PROGRAM PROBLEM '

mMedErr	db	mCRCSys-$-1
	db	'MEDIA ERROR',CR,LF

mCRCSys	db	mDrvNo-$-1
	db	CR,LF
	db	'CRC ERROR ON SYSTEM SECTOR',CR,LF

mDrvNo	db	mFmtSD-$-1
	db	CR,LF
	db	'DRIVE '
mDrv	db	'0'
	db	' '

mFmtSD	db	mFmtDD-$-1
	db	CR,LF
	db	'PRESS "RETURN" TO FORMAT SINGLE DENSITY '
	db	'IN DRIVE '
mDrvSD	db	'A'
	db	CR,LF
	db	'  OR "B" TO REBOOT, "N" FOR NEW PARAMETERS - '

mFmtDD	db	mFmting-$-1
	db	CR,LF
	db	'PRESS "RETURN" TO FORMAT DOUBLE DENSITY '
	db	'IN DRIVE '
mDrvDD	db	'A'
	db	CR,LF
	db	'  OR "B" TO REBOOT, "N" FOR NEW PARAMETERS - '

mFmting	db	mReboot-$-1
	db	CR,LF
	db	'....DISK FORMATTING '

mReboot	db	sideNum-$-1
	db	CR,LF
	db	'TO REBOOT: INSERT CP/M SYSTEM DISK AND PRESS '
	db	'"RETURN" '

sideNum	db	0		;side number
secNum	db	1		;sector number
trkDen	db	0		;density of last track
trkNum	db	0		;track number
fmtMode	db	0		;format mode
dsFlag	db	0		;double sided flag
dskDen	db	0		;disk density 0=SD,1=DD
drvNum	db	0		;drive number
dskType	db	0		;disk type bit 0=SD, 1=DD, +2=DS
	db	1		;069f
vfyOnly	db	0		;verify only, do not format
	end
