;++
;
; Floppy formatter for the TRS-80 Model II under CP/M.
;
; By John Wilson.
;
; Should be adaptable to other machines using WD 179X FDCs, and probably runs
; as-is on the Model 12.
;
; Includes support for 3.5"/5.25" disks, although making CP/M like them is your
; problem.
;
; Copyright (C) 1995 by John Wilson.  All rights reserved.  This program or
; extracts thereof may be freely distributed and/or modified, as long as proper
; credit is given to the author.
;
; 03/07/92	JMBW	Created.
; 01/31/95	JMBW	Prettified for release.
;
;--
bdos	equ	0005h
;
lf	equ	0Ah
cr	equ	0Dh
;
; Z-80 instructions that we use:
;
otir	equ	0B3EDh		;copy (HL) (len=B) to port C
;
	org	100h
start:	; start here
	lxi	sp,stack	;set up stack
	call	print
	db	'TRS-80 Model II disk formatter',cr,lf
	db	'By John Wilson',cr,lf,'$'
loop:	lxi	sp,stack	;reinit
	call	print		;display prompt
	db	'*$'
	xra	a		;load 0
	sta	kbbuf+1		;set # chars in buf
	mvi	a,lkbbuf	;length
	sta	kbbuf		;set max # chars
	lxi	d,kbbuf		;pt at buffer
	mvi	c,0Ah		;get string
	call	bdos
	mvi	e,lf		;echo lf
	mvi	c,2		;conout
	call	bdos
	lxi	h,kbbuf+1	;pt at length byte
	mov	e,m		;get length
	mvi	d,0
	dad	d		;pt at last char
	inx	h		;+1 to char after
	mvi	m,0		;mark end
	lxi	h,kbbuf+2	;pt at string
loop1:	lxi	d,cmdtab	;pt at commands
	call	keyw		;look up & dispatch
	jz	loop		;blank line, ignore
badkw:	; bad keyword, complain
	ldax	d		;get a char
	inx	d
	push	b		;save
	push	d
	mov	e,a		;copy
	mvi	c,2		;write
	call	bdos
	pop	d		;restore
	pop	b
	dcr	b		;done?
	jnz	badkw		;no
	call	print
	db	'?',cr,lf,'$'
	jmp	loop
next:	; handle next command on line
	mov	a,m		;get next char (they called CONFRM)
	cpi	','		;more?
	jnz	loop		;no
	inx	h		;yes, skip it
	jmp	loop1		;look up
;
cmdtab:	db	3,6,'1.44MB'	;set for standard 3.5" 1.44MB disk
	dw	def144
	db	1,6,'2.88MB'	;set for standard (?) 3.5" 2.88MB disk
	dw	def288
	db	1,5,'360KB'	;set for standard 5.25" 360KB disk
	dw	def360
	db	1,5,'720KB'	;set for standard 3.5/5.25" 720KB disk
	dw	def720
	db	2,3,'8DD'	;8" double density
	dw	def8dd
	db	2,3,'8SD'	;8" single density
	dw	def8sd
	db	2,3,'8XD'	;8" extended density
	dw	def8xd
	db	2,2,'A:'	;unit 0
	dw	unit0
	db	2,3,'B/S'	;bytes per sector
	dw	bytes
	db	2,2,'B:'	;unit 1
	dw	unit1
	db	2,85h,'BYTES'	;# of bytes per sector
	dw	bytes
	db	2,2,'C:'	;unit 2
	dw	unit2
	db	2,89h,'CYLINDERS' ;synonym for TRACKS (well not really)
	dw	trks
	db	2,2,'D:'	;unit 3
	dw	unit3
	db	2,2,'DD'	;double density
	dw	dd
	db	2,2,'DS'	;double sided
	dw	ds1
	db	1,3,'FDC'	;set FDC base address (E0 or A0, or ?)
	dw	fdc
	db	4,4,'FDC1'	;use FDC #1
	dw	fdc1
	db	4,4,'FDC2'	;use FDC #2
	dw	fdc2
	db	1,2,'GO'	;actually format
	dw	go
	db	1,4,'QUIT'	;quit to CP/M
	dw	0
	db	2,4,'RATE'	;set bit rate
	dw	rate
	db	2,3,'RPM'	;let us know drive speed
	dw	motrpm
	db	2,2,'SD'	;single density
	dw	sd
	db	2,7,'SECTORS'	;# of sectors
	dw	sectrs
	db	1,4,'SHOW'	;show parameters
	dw	show
	db	2,2,'SS'	;single sided
	dw	ss
	db	6,6,'TRACK0'	;track 0 SD or like rest of disk
	dw	trk0
	db	1,6,'TRACKS'	;# of tracks (actually cylinders)
	dw	trks
	dw	0
;+
;
; Set parameters for 1.44 MB 3.5" ufloppy.
;
;-
def144:	call	confrm		;check tail for junk
	mvi	a,2		;# sides
	sta	sides
	lda	select		;get select reg value
	ani	0Fh		;don't mess with drive
	ori	0C0h		;DD, side 0, 500kHz clock
	sta	select
	mvi	a,80		;# tracks
	sta	tracks
	mvi	a,18		;# sectors
	sta	secs
	mvi	a,2		;512 b/s
	sta	size
	mvi	a,1		;300 RPM
	sta	rpm
	xra	a		;track 0 same
	sta	trk0sd
	jmp	next
;+
;
; Set parameters for 2.88MB 3.5" ufloppy.  Well worth a try anyway.
;
;-
def288:	call	confrm		;confirmed?
	mvi	a,2		;DS
	sta	sides
	lda	select		;get select reg value
	ani	0Fh		;preserve drv sel
	ori	0E0h		;DD, side 0, 1MHz clock
	sta	select
	mvi	a,80		;# tracks
	sta	tracks
	mvi	a,36		;# sectors
	sta	secs
	mvi	a,2		;512 b/s
	sta	size
	mvi	a,1		;300 RPM
	sta	rpm
	xra	a		;track 0 same
	sta	trk0sd
	jmp	next
;+
;
; Set parameters for 5.25" 360KB minifloppy.
;
;-
def360:	call	confrm		;check tail for junk
	mvi	a,2		;# sides
	sta	sides
	lda	select		;get sel reg value
	ani	0Fh		;isolate drv sel
	ori	0D0h		;DD, side 0, 250kHz clock
	sta	select
	mvi	a,40		;# tracks
	sta	tracks
	mvi	a,9		;# sectors
	sta	secs
	mvi	a,2		;512 b/s
	sta	size
	mvi	a,1		;300 RPM
	sta	rpm
	xra	a		;track 0 same
	sta	trk0sd
	jmp	next
;+
;
; Set parameters for 720KB 3.5" or 5.25" mini/microfloppy.
;
;-
def720:	call	confrm		;check tail for junk
	mvi	a,2		;# sides
	sta	sides
	lda	select		;get select reg
	ani	0Fh		;save drv sel
	ori	0D0h		;DD, hd 0, 250kHz clock
	sta	select
	mvi	a,80		;# tracks
	sta	tracks
	mvi	a,9		;# sectors
	sta	secs
	mvi	a,2		;512 b/s
	sta	size
	mvi	a,1		;300 RPM
	sta	rpm
	xra	a		;track 0 same
	sta	trk0sd
	jmp	next
;+
;
; Set parms for 8" double density (26*256).
;
;-
def8dd:	call	confrm		;confirmed?
	lda	select		;get sel reg
	ani	0Fh		;save drv sel
	ori	0C0h		;DD, hd 0, 500kHz clock
	sta	select
	mvi	a,1		;256 b/s
	sta	size
sd8:	mvi	a,26		;# sectors
	sta	secs
xd8:	mvi	a,77		;# tracks
	sta	tracks
	xra	a		;360 RPM
	sta	rpm
	inr	a		;track 0 SD
	sta	trk0sd
	sta	sides		;1 side
	jmp	next
;+
;
; Set parms for 8" single density (26*128).
;
;-
def8sd:	call	confrm		;confirmed?
	lda	select		;get sel reg
	ani	0Fh		;save drv sel
	ori	40h		;SD, hd 0, 500kHz clock
	sta	select
	xra	a		;128 b/s
	sta	size
	jmp	sd8
;+
;
; Set parms for 8" extended density (8*1K).
;
;-
def8xd:	call	confrm		;confirmed
	lda	select		;get sel reg
	ani	0Fh		;save drv sel
	ori	0C0h		;DD, hd 0, 500kHz clock
	sta	select
	mvi	a,8		;8 sectors
	sta	secs
	mvi	a,3		;1024 b/s
	sta	size
	jmp	xd8
;+
;
; Set disk unit #'s.
;
;-
unit3:	add	a		;*8
unit2:	add	a		;*4
unit1:	add	a		;*2
unit0:	cma			;1's comp
	ani	0Fh		;isolate
	mov	b,a		;copy
	lda	select		;get select reg
	ani	0F0h		;isolate left half
	ora	b		;OR in new bits
	call	confrm		;check tail for junk
	sta	select
	jmp	next
;+
;
; Set number of bytes per sector.
;
;-
bytes:	lxi	d,bytes1	;list
	call	keyw		;look up keyword
	jmp	badkw		;bad keyword
bytes1:	db	2,3,'128'	;128 b/s
	dw	b128
	db	1,3,'256'	;256 b/s
	dw	b256
	db	1,3,'512'	;512 b/s
	dw	b512
	db	2,4,'1024'	;1024 b/s
	dw	b1024
	db	2,83h,'1KB'	;synonym for above
	dw	b1024
	dw	0
;
b1024:	inr	a		;4
b512:	inr	a		;3
b256:	inr	a		;2
b128:	dcr	a		;1; make 0-3
	call	confrm		;confirmed?
	sta	size		;save size flag
	jmp	next
;
dd:	; double density
	call	confrm		;confirmed?
	lda	select		;get sel reg
	ori	80h		;DD
	sta	select
	jmp	next
;
ds1:	; double sided
	call	confrm		;confirmed?
	mvi	a,2		;# sides
	sta	sides
	jmp	next
;+
;
; Set FDC base address (E0 or A0).
;
;-
fdc:	call	skip		;skip blanks
	jz	fdc1		;use default value
	call	hexin		;get hex #
	jz	fdcba1		;skip if help
	call	confrm		;confirmed?
	jmp	setprt
fdcba1:	call	print
	db	'Specify FDC base I/O address (default=E0)',cr,lf,'$'
	jmp	loop
;
fdc1:	; use FDC #1 (E0)
	call	confrm		;confirmed?
	mvi	a,0E0h		;base addr
	jmp	setprt		;set it
;
fdc2:	; use FDC #2 (A0)
	call	confrm		;confirmed?
	mvi	a,0A0h		;base addr
setprt:	; set port in A
	sta	port		;(E0) save
	sta	cmdE0+1		;patch
	adi	4		;(E4)
	sta	abrE4a+1	;patch
	sta	abrE4b+1
	sta	abrE4c+1
	sta	abrE4d+1
	sta	cmdE4a+1
	sta	cmdE4b+1
	adi	2		;(E6)
	sta	fcyE6+1		;patch
	inr	a		;(E7)
	sta	abrE7+1		;patch
	sta	seek2+1
	sta	rport		;fix DMA commands
	sta	wport
	adi	8		;(EF)
	sta	goEFa+1		;patch
	sta	goEFb+1
	sta	goEFc+1
	sta	goEFd+1
	sta	errEF+1
	sta	fcyEFa+1
	sta	fcyEFb+1
	jmp	next
;+
;
; Set data rate (for DD mode).
;
; Radio Shack controllers all run at 500kHz (250kHz SD).
;
; The Revised Disk Controller has a jumper to run at 250kHz for 5.25"
; minifloppies, and can be trivially modified to run at 1MHz for 2.88MB
; microfloppies (naturally it isn't that simple though, oh well now I know).
;
; I modified mine (by adding one 74LS74, one 74LS153, and one 74LS174 -- you
; figure out where) to be able to select these speeds from software using the
; two don't-care bits in the select register (port EF).  These bits are latched
; on a second 74LS174 piggybacked on the one on the board, then clocked using
; the 74LS74 to ensure that speed changes occur only at 250kHz transitions
; (don't want glitches), and fed to the 74LS153 to choose which clock is fed to
; FDCLK (which appears on jumper pin Q).  The 74LS153 inputs are wired to
; select the following speeds:
;  EF<5:4>	speed
;  00h		500kHz	normal
;  10h		250kHz	b4=minifloppy select bit
;  20h		1MHz	b5=2.88MB select bit (doesn't work though)
;  30h		500kHz	normal again (not used by this program)
;
; 00 and 30 are both set to give the "normal" speed, since these are the values
; most likely to be written by programs which think they're don't-cares (00
; makes sense, but the other bits in the register are active-low so 30 would be
; convenient too).
;
; ANYWAY, so the upshot is if your controller is modified like mine then this
; command will actually change the speed, and if not then it will inform the
; program what speed you have set the jumpers for, and then write meaningless
; values to the don't-care bits in the select register.  Big deal.
;
;-
rate:	lxi	d,rate1		;table
	call	keyw		;look up
	jmp	badkw
rate1:	db	1,4,'1MHZ'	;20h in select reg
	dw	r1m
	db	1,6,'250KHZ'	;10h in select reg
	dw	r250k
	db	1,6,'500KHZ'	;0 in select reg
	dw	r500k
	dw	0
;
r1m:	adi	10h		;a=21h
r250k:	adi	10h		;a=11h
r500k:	ani	30h		;0, 10h, or 20h
	call	confrm		;confirmed?
	mov	b,a		;save
	lda	select		;get select reg
	ani	0CFh		;lose speed bits
	ora	b		;OR in new ones
	sta	select		;save it
	jmp	next		;later
;+
;
; Set RPM of drive motor.
;
; Actually don't set it, but tell us what it is for the purpose of computing
; the number of bytes per track (=7.5*BPS/RPM), which we may do some day for
; checking cute formats.
;
; 8" drives and 1.2MB 5.25" HD drives use 360 RPM.
; SD/DD 5.25" and all 3.5" drives use 300 RPM.
;
; Some weird 5.25" HD drives have a speed-select line to slow them down to 300
; RPM for handling DD floppies, but I don't feel motivated to modify my FDC to
; handle this line since such drives are rare and besides DD disks written on
; HD drives are very unreliable (tracking/hub-centering probs).
;
; The more usual case for DD disks on HD drives is to use a bit rate of 300kHz.
; This FDC mod wouldn't be as trivial as the *2 and /2 ones, so why bother?
;
;-
motrpm:	lxi	d,motor1	;table
	call	keyw
	jmp	badkw
motor1:	db	2,3,'300'
	dw	rpm300
	db	2,3,'360'
	dw	rpm360
	dw	0
;
rpm360:	xra	a		;a=0
rpm300:	call	confrm		;a=1; confirmed?
	sta	rpm		;save
	jmp	next
;
sd:	; single density
	call	confrm		;confirmed?
	lda	select		;get sel reg
	ani	7Fh		;clear DD bit
	sta	select
	jmp	next
;+
;
; Set # of sectors/track.
;
;-
sectrs:	call	decin		;get decimal #
	jz	sectr1
	call	confrm		;confirmed?
	sta	secs		;set # sectors
	jmp	next
sectr1:	call	print
	db	'Specify number of sectors per track (decimal)',cr,lf,'$'
	jmp	loop
;+
;
; Show setup.
;
;-
show:	call	confrm		;confirmed?
	push	h		;save cmd ptr
	call	params		;show parameters
	pop	h		;restore
	jmp	next
;
params:	; logical unit no.
	lda	select		;get select reg
	mvi	b,'A'		;load drive letter
	rar			;right 1
	jnc	drive1
	inr	b		;B
	rar
	jnc	drive1
	inr	b		;C
	rar
	jnc	drive1
	inr	b		;D
drive1:	mov	a,b		;copy
	sta	drive2
	call	print
	db	'Formatting on drive '
drive2:	db	'X:$'
	; controller
	lda	port		;get port #
	cpi	0E0h		;standard addr?
	jz	ctl2		;yeah, don't say anything
	cpi	0A0h		;revised controller w/J-K jumper?
	jnz	ctl1		;no
	call	print
	db	' (FDC2)$'
	jmp	ctl2
ctl1:	; nonstandard address
	call	print		;hope it's a standard controller!
	db	' (FDC at $'
	lda	port		;get port #
	call	hex		;display it
	mvi	e,')'		;)
	mvi	c,2
	call	bdos
ctl2:	; # sides
	lda	sides		;# sides
	dcr	a		;1 or 2?
	jnz	sides1
	call	print		;1
	db	' SS$'
	jmp	sides2
sides1:	call	print		;2
	db	' DS$'
sides2:	; density
	lda	select		;get flags
	ora	a		;single?
	jm	dens1		;no
	call	print		;single density
	db	'SD $'
	jmp	dens2
dens1:	call	print		;double density
	db	'DD $'
dens2:	; # of tracks
	lda	tracks		;get #
	call	decout
	call	print
	db	' tracks, $'
	; # of sectors
	lda	secs		;get #
	call	decout
	call	print
	db	' sectors, $'
	; sector size
	lda	size		;get sector size (0,1,2,3)
	add	a		;*2
	mov	e,a		;copy
	mvi	d,0
	lxi	h,bytsec	;pt at table
	dad	d		;index with hl
	mov	e,m		;get word
	inx	h
	mov	d,m
	mvi	c,09h		;print size
	call	bdos
	call	print
	db	' b/s, $'
	; bit rate
	lda	select		;get sel reg
	ani	30h		;isolate rate bits
	jz	brate1		;normal
	ani	20h		;2.88MB?
	jnz	brate2		;yes
	call	print
	db	'250kHz/$'
	jmp	brate3
brate1:	call	print
	db	'500kHz/$'
	jmp	brate3
brate2:	call	print
	db	'1MHz/$'
brate3:	; RPM
	lda	rpm		;get speed
	ora	a		;which?
	jz	rpm1
	call	print
	db	'300$'
	jmp	rpm2
rpm1:	call	print
	db	'360$'
rpm2:	call	print
	db	' RPM',cr,lf,'$'
	lda	trk0sd		;track 0 special?
	ora	a
	rz			;no
	call	print
	db	'SD track 0',cr,lf,'$'
	ret
bytsec:	dw	s128,s256,s512,s1024
s128:	db	'128$'
s256:	db	'256$'
s512:	db	'512$'
s1024:	db	'1024$'
;+
;
; Set whether track 0 is SD or not (bootable TRS-80 floppy).
;
; Track 0 has to be SD so that the data rate will be slow enough for programmed
; I/O.
;
;-
trk0:	lxi	d,trk0t
	call	keyw
	jmp	badkw
trk0t:	db	1,6,'NORMAL'
	dw	trk0n
	db	1,7,'SPECIAL'
	dw	trk0s
	dw	0
;
trk0n:	xra	a		;a=0
trk0s:	sta	trk0sd		;a=1, set flag
	jmp	next
;
ss:	; single sided
	call	confrm		;confirmed?
	sta	sides		;a already 1 from KEYW
	jmp	next
;+
;
; Get # tracks.
;
;-
trks:	call	decin		;get a #
	jz	trks1		;help
	call	confrm		;confirmed?
	sta	tracks		;save
	jmp	next
trks1:	call	print		;help msg
	db	'Specify number of cylinders (decimal)',cr,lf,'$'
	jmp	loop
;+
;
; Parameters have been set up, actually format the disk.
;
;-
go:	; init the drive
	lda	select		;get select bits
goEFa:	out	0EFh	;PATCH	;turn drive on
	call	recal		;recal the drive
	xra	a		;0
	sta	ctrk		;current track #
	; write single-density track 0 if needed
	lda	trk0sd		;track 0 is SD?
	ora	a
	jz	go1		;no
	lda	select		;get select bits
	ora	a		;SD already?
	jp	go1		;idiot
	ani	7Fh		;clear MFM bit
	sta	select
goEFb:	out	0EFh	;PATCH	;set FM mode
	mvi	a,26		;26 sectors
	sta	cscs
	xra	a		;128 b/s
	sta	csiz
	call	build		;build track
	call	fcyl		;format it
	lda	select		;get select
	ori	80h		;set MFM bit
	sta	select		;back to whatever it was
goEFc:	out	0EFh	;PATCH	;set MFM mode again
	mvi	a,1		;do track 1 next
	sta	ctrk
go1:	; the rest of the disk is normal
	lda	size		;set size
	sta	csiz
	lda	secs		;get # sectors
	sta	cscs
	call	build		;build a table for that
go2:	; format next cylinder
	call	fcyl		;yep
	lda	ctrk		;get track #
	inr	a		;+1
	sta	ctrk		;save
	mov	b,a		;copy
	lda	tracks		;get # tracks wanted
	cmp	b		;done?
	jnz	go2		;loop if not
	call	abort		;stop FDC
	mvi	a,0CFh		;turn drive motor off
goEFd:	out	0EFh
	lda	tracks		;get # tracks
	cpi	80		;did screen wrap?
	jz	go3		;yes
	call	print		;no, print crlf
	db	cr,lf,'$'
go3:	call	print
	db	'Format successful',cr,lf,'$'
	jmp	loop
;
err:	; error exit out of GO
	call	abort		;stop FDC
	mvi	a,0CFh		;turn drive motor off
errEF:	out	0EFh
	jmp	loop
;+
;
; Format a cylinder (both sides).
;
;-
fcyl:	; check for ^C or Esc
	mvi	e,0FFh		;flag=conditional read
	mvi	c,6		;yep
	call	bdos
	cpi	3		;^C?
	jz	fcyla
	cpi	1Bh		;Esc?
	jz	fcyla
	; init for side 0
	xra	a		;side 0
	sta	csid
	lda	select		;get select reg
fcyEFa:	out	0EFh	;PATCH	;select, side 0
	call	seek		;seek to this track
fcyl2:	; format this track
	call	fillin		;fill in data for this side
	mvi	b,5		;retry count
fcyl3:	push	b		;save
	call	abort		;abort whatever's happening
	push	psw		;save a
	call	dmaw		;set up DMA
	pop	psw		;get a
	ani	20h		;head loaded?
	mvi	a,0F4h		;[cmd=write track]
	jnz	fcyl4
	  ori	4		;set E if not (15 ms delay)
fcyl4:	call	cmd		;send command
	mov	c,a		;save a
	mvi	a,83h		;disable DMAC
	out	0F8h
	mov	a,c		;restore
	pop	b		;restore
	ani	0FDh		;check errors
	jz	fcyl5		;no prob
	dcr	b		;retry count exhausted?
	jnz	fcyl3
	call	print
	db	cr,lf,'? Write error',cr,lf,'$'
	jmp	err
fcyl5:	; verify the track
	mvi	a,1		;starting sector #
	sta	csec
fcyl6:	mvi	b,5		;retry count
fcyl7:	push	b		;save
	call	abort		;stop FDC
	push	psw
	call	dmar		;get DMAC ready to read
	lda	csec		;get current sector
fcyE6:	out	0E6h		;set it
	pop	psw
	ani	20h		;head loaded?
	mvi	a,80h		;[read sec, 1 rec, hd 0, 15ms dly, no hd chk]
	jnz	fcyl8
	  ori	4		;set 15ms delay if not
fcyl8:	call	cmd		;read the sector
	mov	c,a		;save result
	mvi	a,83h		;disable DMAC
	out	0F8h
	mov	a,c		;restore
	pop	b		;catch count
	ani	9Dh		;check errors
	jz	fcyl9
	dcr	b		;try -1
	jnz	fcyl7		;loop
	push	psw		;save a
	call	print
	db	cr,lf,'? Read error $'
	pop	psw		;catch
	call	hex		;display it
	call	print
	db	', sector $'
	lda	csec
	call	decout
	call	print
	db	cr,lf,'$'
	jmp	err
fcyl9:	lda	cscs		;get # sectors
	mov	c,a
	lda	csec		;get curr sector #
	cmp	c		;last?
	jz	fcyl10		;yes
	inr	a		;no, +1
	sta	csec
	jmp	fcyl6		;loop
fcyl10:	lda	sides		;get # sides
	dcr	a		;=1?
	jz	fcyl20		;skip if so
	lda	csid		;get side we just did
	ora	a		;done side 1 already?
	jnz	fcyl19		;yep
	inr	a		;=1
	sta	csid		;set side #
	lda	select		;get select reg
	ani	0BFh		;side 1
fcyEFb:	out	0EFh	;PATCH	;select it
	mvi	e,'.'		;+ for side 0
	mvi	c,2
	call	bdos
	jmp	fcyl2		;go
fcyl19:	; done (two-sided)
	mvi	e,8		;backspace
	mvi	c,2		;write
	call	bdos
fcyl20:	; done
	lda	ctrk		;get curr track #
	mvi	e,'0'-1		;digit
fcyl21:	inr	e		;+1
	sui	10		;-10
	jnc	fcyl21		;loop
	cpi	256-10		;was it a multiple of 10?
	jz	fcyl22		;yes
	  mvi	e,'*'		;print a star if not
fcyl22:	mvi	c,2		;write
	jmp	bdos		;and return
fcyla:	; abort
	call	print
	db	cr,lf,'? Aborted',cr,lf,'$'
	jmp	err
;+
;
; Abort FDC.
;
;-
abort:	mvi	a,0D0h		;abort (force interrupt)
abrE4a:	out	0E4h
	call	delay		;delay 16 usec
abrE7:	in	0E7h		;read data reg (clear DRQ)
abrE4b:	in	0E4h		;get status (clear INTRQ)
	mvi	a,0D0h		;abort again (why?)
abrE4c:	out	0E4h
	call	delay		;delay 16 usec
abrE4d:	in	0E4h		;get status
	ret
;
delay:	; >=16. usec delay
	push	b		;save
;	ld	b,5
;	djnz	$		;13 T-states each
	mvi	b,6		;loop count
dly1:	dcr	b		;(4)
	jnz	dly1		;(10)
	pop	b
	ret
;+
;
; Seek to track CTRK.
;
;-
seek:	mvi	b,2		;loop count
seek1:	call	abort		;abort whatever it's doing
	lda	ctrk		;get track #
seek2:	out	0E7h	;PATCH	;write to data reg
	mvi	a,1Ah		;seek, load hd, no vfy, rate=10ms
	call	cmd
	ani	91h		;isolate errors
	rz
	call	recal		;recalibrate
	dcr	b		;-1
	jnz	seek		;loop
	call	print
	db	cr,lf,'? Seek error',cr,lf,'$'
	jmp	err
;+
;
; Recalibrate drive (seek to track 0).
;
;-
recal:	call	abort		;abort FDC
	mvi	a,0Ah		;recal, load hd, no vfy, rate=10ms
	;jmp	cmd		;do it, return, lose errors
;;	ani	95h		;isolate errors
;;	cpi	04h		;TK00 should be set
;;	rz
;;; drop through
;+
;
; Write command in a to FDC, wait for completion, return status in a.
;
;-
cmd:
cmdE4a:	out	0E4h	;PATCH	;write to command reg
;;; Lifeboat delays here
cmdE0:	in	0E0h	;PATCH	;get status
	ani	1		;done?
	jz	cmdE0		;loop if not
cmdE4b:	in	0E4h	;PATCH	;get status
	ret
;+
;
; Set up DMA controller for reading (DMAR) or writing (DMAW)
; (no, these aren't RF08/RS08 6601/6603 instructions).
;
;-
dmar:	lxi	h,dmar1		;pt at table
	mvi	b,ldmar1	;length
	jmp	dmarw
dmaw:	lxi	h,dmaw1		;pt at table
	mvi	b,ldmaw1	;length
dmarw:	mvi	c,0F8h		;addr of DMAC control port (assume only 1)
	dw	otir		;set up
	ret
;+
;
; Parse keyword and look it up.
;
; hl	ptr to line
; de	ptr to keyword table
;
; Table entries:
;	db	min,max	;length of keyword to match
;	db	'string' ;(length=max)
;	dw	addr	;call here on match
; If high bit of MAX is set then that KW isn't shown on ?.
;
; Table ends with a 0 word.
;
; Routine is called with a=1.
;
;-
keyw:	call	skip		;skip blanks
	rz
	push	d		;save table addr
	call	getw		;get keyword
	dcx	h		;-1
	mov	a,m		;get last char
	inx	h
	xthl			;save ptr, get table
	cpi	'?'		;help?
	jz	kwhlp1
kw1:	mov	a,b		;get length
	cmp	m		;too short?
	inx	h
	jc	kw4		;yes
	mov	a,m		;get length of compare string
	inx	h
	ani	7Fh		;lose high bit
	jz	kw6		;end of table, split
	mov	c,a		;save
	cmp	b		;too long?
	jc	kw5		;yes
	; compare to string in table
	push	b		;save b, de
	push	d
kw2:	ldax	d		;get next char of ours
	cmp	m		;match?
	jnz	kw3
	inx	d		;skip
	inx	h
	dcr	c		;length-1
	dcr	b		;done?
	jnz	kw2		;loop
	; it's a match
	pop	d		;clear stack
	pop	d
	mov	e,c		;get remaining length (if abbrev.)
	mvi	d,0
	dad	d		;skip to call addr
	mov	e,m		;get it
	inx	h
	mov	d,m
	pop	h		;get input ptr
	pop	b		;(flush r.a.)
	push	d		;save addr
	mvi	a,1		;set a=1
	ret			;jump to routine
kw3:	; no match
	mov	a,c		;get # to go
	pop	d		;restore
	pop	b
	mov	c,a		;restore # to go
	jmp	kw5		;go skip (C already loaded)
kw4:	; our string is too short to match
	mov	c,m		;get length
	ani	7Fh		;lose "hidden" bit
	inx	h
kw5:	; too long, skip c chars & call addr
	mov	a,b		;save length
	mvi	b,0		;b=0
	dad	b		;skip to call addr
	mov	b,a		;(restore b)
	inx	h		;skip call addr
	inx	h
	jmp	kw1		;loop
kw6:	; not found
	pop	h		;restore
	dcr	a		;(was 0) clear Z flag
	ret
kwhlp1:	; give keyword help
	push	h
	call	print
	db	'A keyword, one of the following:',cr,lf,'$'
	pop	h
;;; what about d, b
	jmp	loop
;+
;
; Parse a word at (hl).
; Return ptr in de, len in b.
;
;-
getw:	mov	d,h		;de=hl
	mov	e,l
	mvi	b,0		;0
	mov	a,m		;get 1st char
getw1:	cpi	'a'		;lower case?
	jc	getw2
	cpi	'z'+1
	jnc	getw2
	ani	0DFh		;convert
	mov	m,a		;store
getw2:	inx	h		;+1
	inr	b		;+1
	mov	a,m		;get char
	cpi	','		;comma?
	rz
	cpi	' '+1		;printing char?
	jnc	getw1		;loop if not
	ret
;+
;
; Check for eol.
;
;-
confrm:	push	psw		;save a
	call	skip		;skip blanks
	jz	cfm1		;no prob
	cpi	','		;comma is OK too
	jz	cfm1
	call	print
	db	'? Not confirmed',cr,lf,'$'
	jmp	loop		;purge stack there
cfm1:	pop	psw		;restore a
	ret
;+
;
; Skip spaces & tabs at (HL).
; RZ if eol.
;
;-
skip1:	inx	h		;+1
skip:	mov	a,m		;get a char
	ora	a		;eol?
	rz
	cpi	' '+1		;blank or ctrl?
	jc	skip1		;yes
	ora	a		;Z=0
	ret
;+
;
; Print in-line string.
;
;-
print:	pop	d		;catch r.a.
	push	d		;save again
	mvi	c,09h		;print
	call	bdos
	pop	h		;restore
pr1:	mov	a,m		;get a char
	inx	h		;+1
	cpi	'$'		;end?
	jnz	pr1		;loop if not
	pchl			;return
;+
;
; Print hex digit in a.
;
;-
hex:	push	psw		;save
	rar			;right 4
	rar
	rar
	rar
	call	hex1		;do 1st dig
	pop	psw		;drop through for 2nd
hex1:	ani	0Fh		;isolate
	cpi	0Ah		;A-F?
	jc	hex2		;no
	  adi	'A'-'9'-1	;skip to letters
hex2:	adi	'0'		;convert
	mov	e,a		;copy
	mvi	c,2		;write
	jmp	bdos		;and return
;+
;
; Read decimal number into a.
; RZ if none or ?.
;
;-
decin:	call	skip		;skip to no.
	rz			;none
	call	getw		;get a word
	ldax	d		;get 1st char
	cpi	'?'		;help?
	rz			;yes
	push	h		;save
	mvi	l,0		;#=0
din1:	ldax	d		;get next digit
	inx	d		;+1
	sui	'0'		;convert to binary
	cpi	10		;valid?
	jnc	din2		;no
	mov	h,a		;save
	mov	a,l		;get low
	add	a		;*2
	add	a		;*4
	add	l		;*5
	add	a		;*10
	add	h		;+new digit
	mov	l,a		;save
	dcr	b		;done?
	jnz	din1		;loop if not
	mov	a,l		;get #
	pop	h		;restore
	dcr	b		;Z=0 (B=FF)
	ret
din2:	; invalid digit
	call	print
	db	'? Invalid decimal digit',cr,lf,'$'
	jmp	loop		;flush stack & restart
;+
;
; Read hex number into a.
; Same deal as above.
;
;-
hexin:	call	skip		;skip blanks
	rz
	call	getw		;get a word
	ldax	d		;get 1st char
	cpi	'?'		;help?
	rz
	push	h		;save
	mvi	l,0
hin1:	ldax	d		;get a char
	inx	d		;skip
	sui	'0'		;convert to binary
	cpi	10		;decimal digit?
	jc	hin3		;yes
	sui	'A'-'0'		;A-F?
	cpi	6
	jc	hin2		;yes
	sui	'a'-'A'		;a-f?
	cpi	6
	jnc	hin4		;no
hin2:	adi	10		;A-F => 10-15
hin3:	mov	h,a		;save
	mov	a,l		;get running total
	add	a		;*2
	add	a		;*4
	add	a		;*8
	add	a		;*16
	ora	h		;+ new digit
	mov	l,a		;save
	dcr	b		;-1
	jnz	hin1		;loop
	mov	a,l		;get #
	pop	h		;restore
	dcr	b		;Z=0 (B=FF)
	ret
hin4:	call	print
	db	'? Invalid hexadecimal digit',cr,lf,'$'
	jmp	loop
;+
;
; Print decimal number in a.
;
;-
decout:	; a scratch
	; b loop count
	; c BCD "bit"
	; e current output digit
	; h NZ if NZ dig has been written
	; l the number
	mvi	h,0		;no digits yet
	mov	l,a		;save
	lxi	b,200h+200	;shift count,,bit value
	mvi	e,'0'/4		;start new digit
dout1:	mov	a,l		;get #
	cmp	c		;will BCD "bit" fit?
	jc	dout2		;no
	inr	h		;(set non-zero flag)
	sub	c		;take the bit
dout2:	mov	l,a		;[save]
	mov	a,e		;[get digit]
	cmc			;flip C
	ral			;C bit into digit
	mov	e,a		;save
	mov	a,c		;get BCD bit
	rrc			;right 1
	mov	c,a		;copy
	dcr	b		;done this dig?
	jnz	dout1		;no
	push	b		;save
	push	h
	mvi	c,2		;write char
	mov	a,h		;get NZ flag
	ora	a		;any non-zero digs yet?
	cnz	bdos		;print this if so
	pop	h		;restore
	pop	b
	mvi	e,'0'/16	;starting value
	mvi	b,4		;bit count
	mov	a,c		;get BCD bit
	mvi	c,80		;new value
	cpi	50		;1st time, right?
	jz	dout1		;loop if so
	mov	a,l		;get remaining #
	ori	'0'		;convert
	mov	e,a		;save
	mvi	c,2		;print regardless of h
	jmp	bdos		;and return
;+
;
; Build track data to write.
;
;-
build:	lxi	d,wbuf		;pt at buffer
	lxi	h,sdtrk		;assume SD
	lda	select		;get select reg
	ora	a		;SD or DD?
	jp	bld1
	  lxi	h,ddtrk		;DD
bld1:	call	fildat		;do header
	lda	cscs		;get # sectors
	mov	b,a		;save
bld2:	; do next sector
	push	b		;save
	push	h
	call	fildat		;do header
	lxi	b,8001h		;assume length=128
	lda	csiz		;get sector size
	ora	a		;=0?
	jz	bld3
	lxi	b,1		;256?
	dcr	a
	jz	bld3
	inr	c		;512?
	dcr	a
	jz	bld3
	mvi	c,4		;1024
bld3:	mvi	a,0E5h		;data
bld4:	stax	d		;write
	inx	d
	dcr	b		;loop
	jnz	bld4
	dcr	c		;outer loop
	jnz	bld4
	call	fildat		;do trailer
	pop	h		;restore header
	pop	b
	dcr	b		;done?
	jnz	bld2		;do next if not
	lxi	h,-wbuf-12500-50 ;size we want (50 guard bytes)
	dad	d		;find -(# bytes to go)
	jc	bld99		;bad
	mvi	b,0FFh		;assume SD
	lda	select		;get select bits
	ora	a
	jp	bld5		;skip
	  mvi	b,4Eh		;DD
bld5:	mov	a,b		;copy
	stax	d		;store
	inx	d		;+1
	inx	h		;count
	mov	a,h		;done?
	ora	l
	jnz	bld5		;loop if not
	ret
bld99:	call	print
	db	cr,lf,'? Too many sectors',cr,lf,'$'
	jmp	err
;+
;
; Fill out data field.
;
; hl	ptr to table of (db count, db data) (count=0 marks end)
; de	ptr to buf to full
;
; Both updated on return.
;
;-
fildat:	mov	a,m		;get a byte
	inx	h		;+1
	ora	a		;end?
	rz
	mov	b,a		;length
	mov	a,m		;data
	inx	h		;+1
fdat1:	stax	d		;save
	inx	d
	dcr	b		;done?
	jnz	fdat1
	jmp	fildat		;loop
;+
;
; Fill in track, side, sector, length fields for this track.
;
;-
fillin:	lxi	h,128/2		;min sector length
	lda	csiz		;get size flag
	inr	a
fill1:	dad	h		;*2
	dcr	a		;count it up
	jnz	fill1
	lda	select		;get density flag
	ora	a		;which is it?
	jp	fill2		;single
	lxi	d,114-3		;114=size of header+trailer (116 incl CRC)
	dad	d		;add to sector length
	lxi	d,wbuf+146+16	;pt at first
	jmp	fill3
fill2:	lxi	d,58-3		;58=size of header+trailer (60 incl CRC)
	dad	d		;add to sector length
	lxi	d,wbuf+73+7	;pt at first
fill3:	; de=ptr to first header, hl=length of hdr+sec+trl -3
	xchg			;swap 'em
	lda	cscs		;get # sectors
	mov	b,a		;copy
	mvi	c,1		;starting sector #
fill4:	lda	ctrk		;track #
	mov	m,a
	inx	h		;+1
	lda	csid		;head #
	mov	m,a
	inx	h		;+2
	mov	m,c		;sector #
	inx	h		;+3
	lda	csiz		;sector length
	mov	m,a
	dad	d		;+length-3=begn of next
	inr	c		;bump sector #
;;; might want to handle interleaves other than 1
	dcr	b		;done?
	jnz	fill4		;loop if not
	ret
;
; SD track build table
; Data rate=250kHz
; 5208.33 bytes/track @ 360 RPM
; 6250 bytes/track @ 300 RPM
;
sdtrk:	db	40,0FFh
	db	6,0
	db	1,0FCh		;index mark
	db	26,0FFh
	db	0		;end of track header
	db	6,0
	db	1,0FEh		;ID AM
	db	4,0		;track, side, sector, length
	db	1,0F7h		;CRC
	db	11,0FFh
	db	6,0
	db	1,0FBh		;data AM
	db	0
	db	1,0F7h		;CRC
	db	27,0FFh
	db	0		;end of sector
;
; DD track build table
; Data rate=500kHz
; 10416.67 bytes/track @ 360 RPM
; 12500 bytes/track @ 300 RPM
;
ddtrk:	db	80,4Eh
	db	12,0
	db	3,0F6h		;(writes 0C2h)
	db	1,0FCh		;index mark
	db	50,4Eh
	db	0		;end of track header
	db	12,0
	db	3,0F5h		;(writes 0A1h)
	db	1,0FEh		;ID AM
	db	4,0		;track, side, sector, length
	db	1,0F7h		;CRC
	db	22,4Eh
	db	12,0
	db	3,0F5h		;(writes 0A1h)
	db	1,0FBh		;data AM
	db	0
	db	1,0F7h		;CRC
	db	54,4Eh
	db	0		;end of sector
;
; These tables flagrantly stolen from Lifeboat's FORMAT.COM program.
;
dmar1:	db	0C3h		;WR6 reset
	db	8Bh		;WR6 reset eob flag
	db	69h		;WR0 xfr B => A (db port, dw blklen follow)
rport:	db	0E7h		;read port
	dw	-1		;length -1 (we use FDC BUSY bit to finish)
	db	3Ch		;WR1 A=fixed I/O addr
	db	10h,8Dh		;WR2 B=inc'ing mem addr, WR4 byte, addr is..
	dw	rbuf		;read buf addr
	db	8Ah		;WR5 XFERRQ is active high (well..)
	db	0CFh		;WR6 load port B
	db	05h		;WR0 xfr A => B (just kidding before)
	db	0CFh		;WR6 load port A
	db	87h		;WR6 enable
ldmar1	equ	$-dmar1
;
dmaw1:	db	0C3h		;WR6 reset
	db	8Bh		;WR6 reset eob flag
	db	79h		;WR0 xfr A => B (dw addr,blklen follow)
	dw	wbuf		;write buf addr
	dw	-1		;length -1 (we use FDC BUSY bit to finish)
	db	14h		;WR1 A=inc'ing mem addr
	db	28h,85h		;WR2 B=fixed I/O addr, WR4 byte, addr is..
wport:	db	0E7h		;oh baby
	db	8Ah		;WR5 XFERRQ is active high (when DMAC sees it)
	db	0CFh		;WR6 load port B addr
	db	05h		;WR0 xfr A => B
	db	0CFh		;WR6 got it?  miserable sack of shit
	db	87h		;WR6 enable
ldmaw1	equ	$-dmaw1
;
port	db	0E0h		;base FDC I/O port
sides	db	1		;# sides
tracks	db	77		;# tracks
secs	db	26		;# sectors
size	db	1		;sector size (0=128, 1=256, 2=512, 3=1024)
select	db	0CEh		;select reg (EF) value bits as follows:
				;<7> 1=MFM (DD/HD), 0=FM (SD)
				;<6> 1=side 0, 0=side 1
				;<5> don't care (select 1MHz clock on mine)
				;<4> don't care (select 500kHz clock on mine)
				;<3:0> drive sel 3-0, active low (1 at a time)
rpm	db	0		;0=360 RPM (8" or 5.25" HD)
				;1=300 RPM (5.25" DD or 3.5")
trk0sd	db	0		;NZ => track 0 is SD with sector size /2
;
csid	db	0		;current side #
ctrk	db	0		;current track #
csec	db	0		;current sector #
csiz	db	0		;current sector size
cscs	db	0		;# of sectors/track
;
	ds	100h		;stack
stack:	; stack ends here
kbbuf:	ds	2+80+1		;2 lengths, buf, space for our 0
lkbbuf	equ	$-kbbuf-3
wbuf:	; track data are written here
rbuf	equ	$+26000		;max track is 25000 (2.88MB floppy, dream on)
	end
