version	==	1
revision==	3
	.title	'FORMAT DISK PROGRAM - DSC3/DSC4'
	.pabs
	.phex
;----------
; Data and address definitions:
;
cr	=	0Dh	; <return>
lf	=	0Ah	; <linefeed>
onprecmp=	22	; turn on pre-compensation
maxprec	=	59	; turn off S/L bit
stepset	=	600h	; head step settle time = 10ms
RxRDY	=	0	; receiver ready bit
TxRDY	=	2	; transmitter ready bit
WBOOT	=	1	; for bios calls
;
; Port definitions:
;
DMA	=	38h
SIO1AC	=	2Ah	; SIO-1 channel A, control
SIO1AD	=	28h	; SIO-1 channel A, data -port 0
PIOAD	=	08h	; PIO channel A, data
PIOBD	=	09h	; PIO channel B, data
FLOP	=	18h	; Floppy DMA channel
FLOPSR	=	10h	; Floppy status register
FLOPDR	=	11h	; Floppy data register
SETMAP	=	03h	; Set memory map register
STOPFLOP=	03h	; Stop floppy controller
	
;---------
; Entry Points for ZDT
;
	.loc	100h
	lhld	WBOOT	; use interrupt vector in bios
	mvi	L,0	; where cold boot was
	mvi	M,DMAdone&0FFh ; low
	inx	H
	mvi	M,(DMAdone>8) & 0FFh ; then high
       	call	FORMAT	; format the disk(s)
	jmp	0	; warm boot
	.page
;----------
; Home selected disk drive to track 0
HOME:
	mvi	A,0	; current track now zero
	sta	curTRK
	lxi	H,homeFLOP+1 ; point to drive number
	jmpr	seek	; share code with SETTRK
;----------
; Select disk drive
SELDSK:
	lxi	H,DEVqst ; ask which disk
	call	PRTMSG
	call	CONIN	; get the answer
	cpi	03h	; check for cntrl-C
	rz
	mov	C,A
	call	CONOUT	; echo it
	sui	'0'
	jm	SELDSK
	cpi	8
	jp	SELDSK
	sta	curDSK
	ret
DEVqst:	.asciz	[cr][lf][lf]'Enter Disk Number (0-7): '
;----------
; Set track
SETTRK:
	lda	curTRK	; get current track
	lxi	H,seekFLOP+2 ; point to track number
	mov	M,A	; store into command
	dcx	H
;
; Seek a track (used by SETTRK and HOME)
seek:
	lda	curDSK	; get current disk
	mov	M,A	; store into command
	dcx	H
	call	onMOTOR	; turn on the drive motor
reseek: mov	D,H	; save ptr to seek command
	mov	E,L
	call	COMMAND	; seek
sense:	lxi	H,IsenseFLOP
	call	COMMAND	; sense interrupt status
	call	RESULT
	mov	H,D	; restore ptr to seek command
	mov	L,E
	lda	resbuf	; get first result byte
	bit	3,A	; check ready bit
	jrnz	reseek	; jump if not ready
	bit	5,A	; mask out top 2 bits
	jrz	sense	; loop until seek completed
	ani	0D0h
	cnz	IOERR
	lxi	B,stepset; delay 10 ms for step settle
;---------
; Delay loop
DELAY:	dcx	B
	mov	A,B
	ora	C
	jrnz	DELAY
	ret
	.page
;----------
; Format the entire disk single or double density
;
FORMAT:
	lxi	H,FORMstr ; print start-up message
	call	PRTMSG
..form:	call	SELDSK	; ask which disk
	rz		; return if cntrl-C
	call	ASKDEN	; ask what density
	call	WAITCR	; wait for return
	call	HOME	; recalibrate the disk drive
	mvi	D,0	; track 0 is single density
	call	TABLIN	; track 0 is linear
	call	FORM1TK	; format the first track
	
..1:	call	SETTRK	; seek to next track
	lda	curDEN	; rest of disk is specified
	mov	D,A	; density
	cpi	0	; single density is linear
	jrz	..2
	lda	curTRK	; so is track 1
	cpi	1
	jrz	..2
	push	D	; save density
	mvi	A,4	; set stagger for DD
	call	TABSTAG	; stagger the table
	pop	D
	jmpr	..3
..2:	call	TABLIN	; make table linear
..3:	call	FORM1TK ; format next track
	cpi	78	; all tracks formatted?
	jrnz	..1	; if not, loop
	lxi	H,FORMdon ; say 'done'
	call	PRTMSG
	jmpr	..form

FORMstr:.ascii	[cr][lf]'Floppy Disk Format Program '
	.byte	version+'0','.'
	.byte	revision/10+'0',revision@10+'0',0
FORMdon:.asciz	[cr][lf]'FORMAT COMPLETE'[cr][lf]

;----------
; Ask what density
ASKDEN:
	lxi	H,DENqst ; set up message
	call	PRTMSG
	call	CONIN	; get answer
	mov	C,A
	call	CONOUT	; echo it
	cpi	's'	; check for single
	jrz	..1
	cpi	'S'	; check for single
	jrnz	..2
..1:	mvi	A,0	; set single density
	sta	curDEN		
	ret
..2:	cpi	'd'	; check for double
	jrz	..3
	cpi	'D'
	jrnz	ASKDEN	; invalid, so reask
..3:	mvi	A,1	; set double density
	sta	curDEN
	ret
DENqst:	.asciz	[cr][lf]'Single or Double density? '
;----------
; Wait for CR
WAITCR:
	lxi	H,WCRqst ; print a message
	call	PRTMSG
..1:	call	CONIN	; get an answer
	cpi	cr	; is it cr?
	jrnz	..1	; if not loop
	mov	C,A
	call	CONOUT	; echo itV
	ret
WCRqst:	.asciz	[cr][lf]'Type return to start.'
;----------
; Format curTRK and increment it.
;
; Build Sector Table (linear)
TABLIN:
	lxi	H,bufFLOP ; build sector table in buf
	lda	curTRK	; C should have curTRK
	mov	C,A
	mvi	A,1	; A has sector number

..1:	mov	M,C	; fill in TRACK
	inx	H
	push	PSW
	lda	curDSK
	rrc
	rrc
	ani	1	; compute head (ie, side)
	mov	M,A	; fill in HEAD (0 or 1)
	pop	PSW
	inx	H
	mov	M,A	; fill in SECTOR (counting)
	inx	H
	mov	M,D	; code for BYTES/SECTOR
	inx	H
	inr	A	; set to next sector
	cpi	27	; end of list?
	jrnz	..1	; no, then loop
	ret

;
; Build staggered sector table
TABSTAG:
	push	D	; save density
	lxi	H,bufFLOP ; zero out the output buffer	
	mvi	M,0
	lxi	D,bufFLOP+1
	lxi	B,255
	ldir

	lxi	H,bufFLOP ; build the sector table
	mov	B,A	; B has the stagger count
	lda	curTRK	; C has the track number
	mov	C,A
	pop	D	; restore density
	mvi	A,1	; first sector is 1

..1:	mov	M,C	; fill in TRACK
	inx	H
	push	PSW
	lda	curDSK
	rrc
	rrc
	ani	1
	mov	M,A	; fill in HEAD (0 or 1)
	pop	PSW
	inx	H	
	mov	M,A	; fill in SECTOR (counting)
	inx	H
	mov	M,D	; fill in BYTES/SECTOR
	inx	H

	cpi	26	; last sector?
	rz		; return if yes
	inr	A	; set next sector
	push	B	; save stagger count
..2:	inx	H	; increment HL by 4 for each
	inx	H	; 1 sector to stagger
	inx	H
	inx	H
	djnz	..2	; repeat for stagger count
	pop	B	; restore stagger counter
	push	PSW	; save A
..3:	mov	A,L	; check for HL overflow
	cpi	26*4
	jm	..4
	sbi	26*4	; if overflow subtract extra
	mov	L,A	; from HL
..4:	inx	H	; check if this sector already
	inx	H	; filled
	mov	A,M
	ora	A
	jrz	..5
	inx	H	; if so skip it, and try next
	inx	H	; next sector
	jmpr	..3
..5:	dcx	H	; restore HL
	dcx	H
	pop	PSW	; restore A
	jmpr	..1	; fill next sector

FORM1TK:
;
; Set up the DMA chip
	mvi	A,0	; multiplex Floppy to DMA
	out	PIOAD
	lxi	H,DMAfmt
	lxi	B,DMAfm$<8+DMA
	outir		; program the chip
;
; Set up the floppy format command
	lda	curDSK	; set up current disk
	sta	FORMdsk
	mov	A,D	; set up floppy density
	sta	FORMmod 
	cpi	0	; check for density
	jrnz	..2	; and fill in special fields
	mvi	A,0Dh	; set up for single density
	sta	FORMcom	; format command
	mvi	A,1Bh	; set gap size
	sta	FORMgap
	jmpr	..3
..2:	mvi	A,4Dh	; set up for double density
	sta	FORMcom	; format command
	mvi	A,36h	; set gap size
	sta	FORMgap
..3:
;
; Execute the floppy command
	lxi	H,FORMcom
	call	EXflop
;
; Increment to next track
	lda	curTRK
	inr	A
	sta	curTRK

	ret		; return
;
; Execute the floppy command
EXflop:	ei		; turn on interupts
	call	COMMAND
	call	RESULT
	lda	resbuf	; get first result byte
	ani	0C0h	; mask out top 2 bits
	cnz	IOERR	; jump if abnormal termination
	lda	resbuf+1 ; get second result byte
	ani	33h	; check for CRC error
	cnz	IOERR
	ret
;
; Handle the floppy-done interrupt from the DMA chip
DMAdone:push	PSW
	in	STOPFLOP; reset the floppy chip
	mvi	A,0C3h	; reset the DMA chip
	out	DMA
	pop	PSW
	ei
	reti
;----------
; Handle an I/O error
;
; Print a message of the form 'I/O error nnnn', where
; 'nnnn' is the address in the BIOS where the error
; was detected.  Then return to the BDOS.
errmsg:	.asciz	[cr][lf]'I/O Error.'
IOERR:
	lxi	H,errmsg
	call	PRTMSG
	pop	H
	shld	locerr
	rst	6
locerr	=	bufFLOP-2
	.page
;----------
; Utility routines
;
; PRTMSG  - print a message
; COMMAND - send a command to the floppy controller
; RESULT  - get a result from the floppy controller
; WAITDR  - wait for floppy data register
; MOTORon - turn on the drive motor
;----------
; Print a message
;  Regs in:   HL = address of message (ended by null)
;  Regs out:  none
;  Destroyed: A, HL
PRTMSG:
	mov	A,M	; get a byte
	ora	A	; if null, then return
	rz
	mov	C,A	; put it where CONOUT wants it
	push	H	; save HL
	call	CONOUT	; output the byte
	pop	H	; restore HL
	inx	H	; point to next byte
	jmpr	PRTMSG
;----------
; Console input
CONIN:
	lhlä	WBOOT	; call the bios
	lxi	D,06h
	dad	D
	pchl
;----------
; Console output
CONOUT:
	lhld	WBOOT	; call the bios
	lxi	D,09h
	dad	D
	pchl
;----------
; Send a command to the floppy controller
;  Regs in:   HL = address of command string
;  Regs out:  none
;  Destroyed: A, HL
COMMAND:
	mov	A,M
	cpi	endcom	
	rz		; return if end-of-command
	call	WAITDR
	cc	IOERR	; direction is wrong
	mov	A,M
	out	FLOPDR	; send a byte to controller
	inx	H	; point to next byte
	jmpr	COMMAND
;----------
; Collect a result from the floppy controller
;  Regs in:   none
;  Regs out:  B = length of result string
;  Destroyed: A, HL
RESULT:
	lxi	H,resbuf
readDR:	call	WAITDR
	rnc
	mvi	A,0C3h
	out	DMA	; reset DMA chip
	in	FLOPDR	; get a result byte
	mov	M,A	; store it away
	inx	H
	jmpr	readDR
;----------
; Wait until floppy data register is ready
;  Regs in:   none
;  Regs out:  A = status register, shifted left by 1
WAITDR:
	in	FLOPSR
	rlc
	jrnc	WAITDR
	rlc
	ret
;----------
; onMOTOR - turn on a drive motor
;  Regs in:   A = drive number
;  Regs out:  none
;  Destroyed: A, B
onMOTOR:
	mov	B,A
	lda	curTRK	; set precomp
	cpi	onprecmp
	jrc	..1
	set	5,B	; bit 5 = pre-comp
	cpi	maxprec
	jrnc	..1
	set	4,B	; bit 4 = S/L
..1:	set	2,B	; bit 2 = head load
	mov	A,B
	out	PIOBD
	ret
	.page
;----------
; Current disk position information
curDSK:	.byte	0	; current physical drive
curTRK:	.byte	0	; current physical track
curSEC:	.byte	1	; current physical sector
curBYT:	.word	256	; current length of operation
curDMA:	.word	bufFLOP ; I/O buffer
curDEN:	.byte	1	; current density

;----------
; Floppy controller commands
endcom	=	0FFh	; command terminator
homeFLOP:
	.byte	7	; recalibrate command
	.byte	0	; curDSK
	.byte	endcom
seekFLOP:
	.byte	15	; seek command
	.byte	0	; curDSK
	.byte	0	; curTRK
	.byte	endcom
IsenseFLOP:
	.byte	8	; sense interrupt status
	.byte	endcom
readFLOP =	46h	; DOUBLE DENSITY
writeFLOP=	45h
resbuf: .blkb	7	; result buffer

;----------
; FORMAT command for Floppy Controller
FORMcom:
	.byte	4Dh	; format command (SD/DD)
FORMdsk:.byte	0	; curDSK
FORMmod:.byte	1	; 0=SD, 1=DD
	.byte	26	; 26 sectors/track
FORMgap:.byte	36h	; gap length
	.byte	0E5h	; fill byte
	.byte	endcom


;----------
; DMA commands for FORMAT command
DMAfmt:
	.byte	0C3h	; master reset
	.byte	0C7h	; reset port A
	.byte	0CBh	; reset port B
	.byte	079h	; read from memory
	.word	bufFLOP	; address
	.word	103	; size - 1
	.byte	14h	; port A inc, memory
	.byte	28h	; port B fixed, I/O
	.byte	95h	; byte mode
	.bytå	FLOÐ	» porô B
	.byte	12h	; interrupt at end of block
	.byte	00	; interrupt vector
	.byte	9Ah	; stop at end of block
	.byte	0CFh	; load starting address
	.byte	05	; write to I/O
	.byte	0CFh	; load starting address
	.byte	0ABh	; enable interrupts
	.byte	87h	; enable DMA
DMAfm$	=	.-DMAfmt
bufFLOP	=	3000h	; location of I/O buffer

.end
