	title 'tarbell dma double density loader driver'

	; Copyright (c) Tarbell Electronics
	; Version 1.0 3-22-83 Edwin E. Smith

true	equ	-1
false	equ	0

;note - these routines are implied to be and must be in physical
;memory 00000h to 0ffffh and no translation is permitted
;ensure that bank 0 of the memory select table is contiguous


	; disk drive dispatching tables for linked bios.

	public	@dtbl
	public	fd0

	; variables containing parameters passed by bdos.

	extrn	@adrv,@rdrv
	extrn	@dma,@trk,@sect

	; utility routines in standard bios.

	extrn	?pmsg			; print message @<hl> up to 00.
	extrn	?cono			; console out.

	; port address equates.

stprat	equ	0	;0=3ms,1=6ms,2=15ms,3=20ms(stp rate)
hlab	equ	8	;0=no head load at beginning of seek
dual	equ	false	;true if using a persci drive
ercnt	equ	10	;error retry count
;
;
dmap	equ	0e0h		;dma port base address
wct0	equ	dmap+1		;dma word count register
cmnd	equ	dmap+8		;dma command register
dadr	equ	0f8h		;disk address
dcom	equ	dadr		;disk command port
dstat	equ	dadr		;disk status port
track	equ	dadr+1		;disk track register
sectp	equ	dadr+2		;disk sector register
ddata	equ	dadr+3		;disk data register
wait	equ	dadr+4		;in=wait for data-interrupt
ddsel	equ	dadr+4		;out=drive and density select
dmachk	equ	dadr+5		;in=non-wait status port
extdma	equ	dadr+5		;out=extended dma address
;
dmar	equ	01000000b	;dma read command
dmaw	equ	10000000b	;dma write command
fread	equ	10000000b	;floppy read command
fwrit	equ	10100000b	;floppy write command
fwritd	equ	10100100b	;floppy write with 15ms delay
rdid	equ	11000000b	;read id command
b128	equ	128		;sgl / dbl den. sect size
b512	equ	512		;deblock sector size
;

	; cp/m 3 disk definition macros.

	maclib cpm3

	; z80 macro library instruction definitions.

	maclib z80

	; drive table

@dtbl	dtbl	<fd0>

	; extended disk parameter headers (xpdhs).

	dw	0			;loaders don't write
	dw	fd$read
	dw	fd$login
	dw	fd$init
	db	0,0			; relative drive 0.
fd0:	dw	dbtrans			;deblocked translate table
	db	0,0,0,0,0,0,0,0,0	;bdos scratch area
	db	0			;media flag
	dw	dd512			;disk parameter block
	dw	cks0			;check vector
	dw	all0			;allocation vector
	dw	dirbcb			;directory bcb
	dw	dtabcb			;data bcb
	dw	0ffffh			;no hashing
	db	0			;hash bank
cks0	ds	44			;checksum storage
all0	ds	78			;allocation storage

	; disk parameter blocks

ss128:	dpb	128,26,77,1024,64,2		;single density single sided
						;128 bytes per sector

ds128:						;double density single sided
						;128 bytes per sector
	dw	51				;sectors per track
	db	4				;block shift factor
	db	15				;block mask
	db	0				;extent mask
	dw	237				;disk size-1
	dw	127				;directory size-1
	db	11000000b			;al0
	db	00000000b			;al1
	dw	32				;check size
	dw	2				;track offset
	db	0				;psh
	db	0				;phm



ds512:						;double density single sided
						;512 bytes per sector
	dw	64				;sectors per track
	db	4				;block shift factor
	db	15				;block mask
	db	0				;extent mask
	dw	299				;disk size-1
	dw	127				;directory size-1
	db	11000000b			;al0
	db	00000000b			;al1
	dw	32				;check size
	dw	2				;track offset
	db	2				;psh
	db	3				;phm

sd128:						;single density double sided
						;128 bytes per sector
	dw	26				;sectors per track
	db	4				;block shift factor
	db	15				;block mask
	db	0				;extent mask
	dw	242				;disk size-1
	dw	95				;directory size-1
	db	11000000b			;al0
	db	00000000b			;al1
	dw	24				;check size
	dw	2				;track offset
	db	0				;psh
	db	0				;phm

dd128:						;double density double sided
						;128 bytes per sector
	dw	51				;sectors per track
	db	5				;block shift factor
	db	31				;block mask
	db	0				;extent mask
	dw	237				;disk size-1
	dw	95				;directory size-1
	db	11000000b			;al0
	db	00000000b			;al1
	dw	24				;check size
	dw	2				;track offset
	db	0				;psh
	db	0				;phm

dd512:						;double density double sided
						;512 bytes per sector
	dw	64				;sectors per track
	db	5				;block shift factor
	db	31				;block mask
	db	0				;extent mask
	dw	303				;disk size-1
	dw	169				;directory size-1
	db	11000000b			;al0
	db	00000000b			;al1
	dw	42				;check size
	dw	2				;track offset
	db	2				;psh
	db	3				;phm

trans	skew 	26,6,1		;generate translate table for single density
ddtrans	skew 	51,1,1		;generate translate table for double density
dbtrans	skew	16,1,1		;generate translate table for 512 x 16

;buffers and buffer control blocks

dirbcb	dw	0ffh		;initialize drive to ff
	ds	9		; 9 bytes used by bdos
	dw	buff1		;address of a 512 byte buffer

dtabcb	dw	0ffh		;initialize drive to ff
	ds	9		; 9 bytes used by bdos
	dw	buff2		;address of a 512 byte buffer

buff1	ds	512		;512 bytes of buffer space
buff2	ds	512		;512 bytes of buffer space

    ; disk i/o routines for standardized bios interface

; initialization entry point.

;		called for first time initialization.


fd$init:	; all initialization done by drive 0
	ret


fd$login:
		; this entry is called when a logical drive is about to
		; be logged into for the purpose of density determination.

		; it may adjust the parameters contained in the disk
		; parameter header pointed at by <de>

	push	d		;save pointer to dph
	call	gethf		;get hardware format pointer
	inx	h		;get disk pointer address
	lda	@rdrv		;get relative drive #
	add	a
	add	a
	add	a
	add	a
	mov	m,a		;clear disk select byte for this drive
	push	h		;save disk pointer address
	call	get$id		;get the id byte from @rdrv
	jnz	id$error	;return select error
	mov	e,a		;save id in E
	lda	@rdrv		;get drive #
	lxi	h,drive$ids	;point to drive id storage
	mov	c,a
	mvi	b,0		;generate offset
	dad	b		;add in offset
	mov	a,e		;get disk id back
	mov	m,a		;save for future reference
	lxi	h,disk$ids	;hl points to list of disk id's
	lxi	b,6		;six of them to check
	ccir			;check untill we find one
	lxi	h,dph$patches	;hl points to patch words for the dph
	dad	b		;and disk format word
	dad	b
	dad	b		;add b in 6 times for 6 bytes
	dad	b
	dad	b
	dad	b
; hl now points to selected format word
;
; 0 = unlogged
; 1 = single density
; 2 = double density (51 secs - 128 b)
; 3 = double density (16 secs - 512 b)
;
;example:
;for double density 512x16:
;
;	db	3 * 256 + 8
;
;3 = use 512 byte sector size (hl+1)
;8 = set bit 3 to a '1' for doub density (hl)
;
;for double density 512x16 doub sided:
;
;	db	3 * 256 + 48h
;
;3 = use deblock part of bios (hl+1)
;48h = set bit 3 '1' for doub den
;      set bit 6 '1' for doub sided (hl)
;
	mov	a,m		;get disk density mask
	inx	h		;point hl at disk format flag
	xthl			;get pointer to disk hardware byte
	ora	m		;or in density mask
	mov	m,a		;save
	sta	diskhs		;value for selected disk
	dcx	h		;point hl at format flag
	xthl			;get table pointer back
	mov	a,m		;get format type flag
	inx	h		;point hl at translate vector
	xchg			;save pointer in de
	pop	h		;get pointer to format byte
	mov	m,a		;save
	sta	diskhf		;value for selected disk
	xchg			;get table pointer back
	pop	d		;get address of selected dph
;move translate vector from table to dph
	lxi	b,2		;2 bytes to move
	ldir			;patch translate vector 
;move dpb vector to dph
	xchg			;swap dph pointer to hl
	lxi	b,10		;offset to dpb
	dad	b		;add offset to hl
	xchg			;swap in table address again
	lxi	b,2		;2 bytes to move
	ldir			;patch dpb vector
;now we can return to root module to finish select
	ret
;
;id read error couldn't read first sector on disk
;
id$error:
	pop	h		;discard disk pointer
	pop	h		;discard dph pointer
	pop	d		;get-save return address
	pop	h		;discard dph pointer (saved by root)
	lxi	h,0		;indicate select error
	push	h		;push 0 on stack for root
	push	d		;restore return address
	ret			;return select error		
;
; read the id byte from the first sector of @rdrv
; return it in A with zero flag set if read error
;
get$id:
	lda	@rdrv		;get relative drive #
	add	a
	add	a
	add	a
	add	a
	out	ddsel		;select the drive
	call	rdydrv		;check for drive ready
	mvi	a,stprat+hlab	;restore drive
	call	execc1		;restore to track 0
	mvi	a,1
	out	sectp		;address sector 1
	lxi	b,fread shl 8 or wct0
	lxi	d,dmar shl 8 or (b128-1)
	lxi	h,buff2		;use local buffer for read
	call	dmarw		;do the read
	ani	9dh		;check for errors
	lda	buff2+7eh	;get disk format id byte
	ret
;
disk$ids	;table of six disk id bytes
	db	0deh		;double density 16 secs of 512?
	db	0dch		;512 x 16 doub sided?
	db	0dfh		;128 x 51 doub sided?
	db	0ddh		;double density 51 secs?
	db	0e7h		;doub sided single density
	db	0e5h		;single sided single density
;
dph$patches	;table of patch bytes for the disk parameter headers
		;also the hardware setting byte and the format byte

	dw	1*256 + 0	;format flag and density mask
	dw	trans		;single density translate table
	dw	ss128		;single density disk parameter block

	dw	1*256 + 40h	;=single density double sided
	dw	trans		;single density translate table
	dw	sd128		;single density double sided dpb

	dw	2*256 + 8	;=double density single sided
	dw	ddtrans		;double density translate table
	dw	ds128		;double density single sided dpb

	dw	2*256 + 48h	;=double density double sided
	dw	ddtrans		;double density translate table
	dw	dd128		;double density double sided dpb

	dw	3*256 + 48h	;=deblocked double sided
	dw	dbtrans		;512 x 16 translate table
	dw	dd512		;deblocked double sided dpb

	dw	3*256 + 8	;=deblocked single sided
	dw	dbtrans		;512 x 16 translate table
	dw	ds512		;deblocked single sided dpb
;
;end of disk format table
;
;

gethf:				;return hardware format for selected drive
	lda	@rdrv		;get relative drive code
	add	a		;double for indexing
	mov	c,a
	mvi	b,0		;generate offset
	lxi	h,dsklog	;point to disk log
	dad	b		;add in offset
	mov	a,m		;get format
	sta	diskhf		;save for future reference
	inx	h		;point at select byte
	mov	a,m		;get it
	sta	diskhs		;save for future reference
	dcx	h		;point at format byte for @rdrv
	ret

; disk read entry point.

		; this entry is called with the following arguments:

			; relative drive number in @rdrv (8 bits)
			; absolute drive number in @adrv (8 bits)
			; disk transfer address in @dma (16 bits)
			; disk transfer bank	in @dbnk (8 bits)
			; disk track address	in @trk (16 bits)
			; disk sector address	in @sect (16 bits)
			; pointer to xdph in <de>

		; it transfers the appropriate data, perform retries
		; if necessary, then return an error code in <a>

fd$read:			;read - read a sector
	call	ckdrv		;check to see if new drive
	call	gethf		;get hardware format
	lda	diskhs		;get disk density
	mov	b,a		;put density in reg b
	lda	@trk
	call	seekls		;seek @rdrv to @trk
	lda	seeker		;get seek error flag
	ora	a		;test for seek error
	jnz	comext		;abort read if seek error
	lded	@dma		;get dma address
;
; -read logical sector-
;de is dma address
;
readls:	xchg			;put dma address in hl
	call	rdydrv		;check for drive ready
;
	mvi	a,ercnt		;retry count
rrtry:	sta	rtcnt		;save it
	lxi	b,fread shl 8 or wct0
	lda	diskhf		;check if 512 byte sectors
	cpi	3		;if = 3, 512 bytes
	lxi	d,dmar shl 8 or (b512-1)
	jrnc	rset
	lxi	d,dmar shl 8 or (b128-1)
rset:	push	h		;save dma address
	call	dmarw		;use common read/write
	pop	h
	ani	9dh		;check for errors during read
	rz			;return to cp/m if no read errors
	lda	rtcnt		;get retry count
	dcr	a		;decrement it
	jrnz	rrtry		;retry again
comext:	call	check$id	;see if density has changed
	mvi	a,0ffh		;return media change flag
	rnz			;to cp*m
	mvi	a,1		; return to cp/m error code
	ora	a		;set flags
	ret
;
; we have a disk error - check if disk format has been changed
;
check$id
	lda	@rdrv		;get the reletive drive #
	mov	c,a
	mvi	b,0		;generate offset
	lxi	h,drive$ids	;point to drive id bytes
	dad	b		;add in offset
	push	h		;save pointer
	call	get$id		;get the id byte of @rdrv
	pop	h		;get pointer back
	cmp	m		;test disk with previous
	ret
;
; check for new drive access
;
ckdrv:	lda	olddrv		;get old drive #
	mov	b,a
	lda	@rdrv		;get relative drive #
	cmp	b		;same ?
	rz			;return if no new drive
	sta	olddrv		;save new drive #
	mvi	a,0ffh
	sta	chngfl		;set change flag
	ret
;
;check to see if selected drive is ready
;routine is also good for slim line drives.
;
rdydrv:	call	statck		;check for drive ready
	rz			;return if ready
	mvi	b,50		;50 * 16ms = 800 ms
rdydv1:	call	dlylop
	call	statck		;check drive status
	rz			;return if ready
	djnz	rdydv1		;loop for 800 ms for slim line drives
	exx
	lda	@adrv
	adi	'a'		;ascii adjust
	sta	drv		;save for message
	lxi	h,ready		;point to ready message
	call	?pmsg		;print it
	exx
drvrdy:	call	statck
	jrnz	drvrdy		;loop till ready
	ret
;
;selected drive is ready now
;
statck:	in	dstat		;get drive status
	bit	7,a		;check ready bit
	ret
;
dlylop:	mvi	a,16		;16 ms count value
dlylp1:	mvi	c,210		;1 ms time constant
wexitl:	nop
	dcr	c
	jnz	wexitl
	dcr	a
	jnz	dlylp1
	ret
;
;logical sector seek
;
;a=track, b=hardware and density for fdc
;d=sector (for future read)
;
seekls:	push	psw		;save track
	xra	a
	sta	seeker		;reset seek error flag
	mvi	a,10		;retrys
	sta	seekrty		;save it
	push	b		;save density
	lda	@rdrv		;get relative drive #
	if	dual
	ani	0eh		;strip off lsb
	endif
;
;compute index into track table
;
	mov	c,a		;put disk number into reg c.
	mvi	b,0
	lxi	h,trktab	;point to start of trk table
	dad	b		;get correct drive byte
	pop	b
	mov	a,m		;get old track value
	out	track		;force into 1793 track reg.
	pop	psw		;restore new track value
	cpi	1
	mov	c,a		;save track in reg c
	mov	a,b		;get copy of drive and density
	jrnc	seekok		;we are > than track 1?
	ani	30h		;clear density & side bit
	jmpr	seek2		;going to trk < 1
;
seekok:	bit	6,b		;doing doub sided?
	jrz	seek1		;< > 1, doing single sided
	rrcr	c		;divide track by 2
	jrc	seekk1		;side bit set,doing odd trk
	res	6,b		;reset side bit, doing even trk
seekk1:	res	7,c		;clear msb of track value
seek1:	mov	a,b		;get drive select code
seek2:	out	ddsel		;select disk and density
	push	b
	call	rdydrv		;check for drive ready
	pop	b
	call	dlysel		;delay on selects
	lda	@sect		;get sector number
	out	sectp		;tell the controoller chip
	in	track		;read 1793 track register
	cmp	c		;is track the same?
	rz			;not necessary to seek
	mov	a,c		;not same, get new track number
	out	ddata		;seek to track with head load
;
;execute seek command
;
	mvi	a,14h+stprat+hlab	;seek command
execc1:	out	dcom		;output disk command
	call	sloop		;use common polling
	ani	90h		;mask out junk bits
	jrz	savtrk		;save track for next time
;
;we have a seek error,
;save primary registers
;and execute a read id.
;
ridcom:	exx			;save regs
	lxi	h,buff2		;use local buffer area for read id

	lxi	b,rdid shl 8 or wct0
	lxi	d,04006h	;dma read, length
	call	dmarw		;read the id marks

	ani	9dh		;any errors in reading?
	jrz	denok		;no, density is correct.
	exx			;yes, incorrect density
	mov	a,b		;get density and drive byte
	res	3,a		;set single density for retry
	out	ddsel		;on read id.
	jmpr	ridcom		;switch registers and retry.
;
denok:	in	sectp		;get track byte
	out	track		;place value into trk reg.
	lda	seekrty		;get retry count
	dcr	a
	sta	seekrty
	exx			;restore registers
	jrnz	seek1		;reseek to track
	mvi	a,0ffh
	sta	seeker		;set seek error flag
	ret
;
;delay on new drive select
;
dlysel:	lda	chngfl		;get drive change flag
	ora	a		;was there a change to new drv?
	rz			;return if not, else
	xra	a		;reset the drv chng flag
	sta	chngfl
;
;drives are different, so get the present track
;number and seek to it to unload the head.
;
	in	track		;get the present track number
	out	ddata		;place into data register
	mvi	a,10h + stprat	;get seek command
	out	dcom		;send seek command
	jmp	sloop		;wait till done
;
savtrk:	lda	@rdrv		;get relative drive code
	if	dual
	ani	0eh		;strip off lsb
	endif
	mov	c,a
	mvi	b,0		;generate offset
	lxi	h,trktab	;point to track table
	dad	b		;add offset
	in	track		;get 1793 track
	mov	m,a		;save in table
	ret
;
;dma read / write routine
;
dmarw:	xra	a		;clear accum
	out	cmnd		;clear dma chip
	mvi	a,0d0h		;force intrp cmnd (1793)
	out	dcom
	outp	e
	outp	d		;read/write with length
	dcr	c		;reg -> adr0 port now
	outp	l
	outp	h		;address for dma set up
	mvi	a,41h		;request chan 0 operation
	out	cmnd		;tell dma chip
	call	hdld		;check head load bit
	ora	b		;add in command for floppy
	out	dcom		;send it to floppy
;
;general purpose wait routine
;
sloop:	in	dmachk		;check bit 7
	rlc			;and rotate it left
	jrc	sloop		;and loop if carry
	xra	a		;get a zero
	out	cmnd		;clear dma chip
	in	dstat		;get status of disk
	ret			; and return it to caller
;
;check for head load bit on in 1793
;
hdld:	in	dstat		;get status from 1793
	bit	5,a		;check head load bit
	mvi	a,4		;get head load set bit
	rz			;return if not loaded
	xra	a		;get a zero
	ret			;head is load already
;
;data tables
;
ready:	db	0dh,0ah,7,'drive '
drv:	db	' : not ready ',0

dsklog:	dw	0000h		;disk 0 log flag/den
	dw	1000h		;disk 1 "
	dw	2000h		;disk 2 "
	dw	3000h		;disk 3 "

diskhf:	ds	1		;disk hardware format (1, 2, 3)
diskhs:	ds	1		;disk/density setting = hardware
trktab:	ds	4		;track table for 4 drives
drive$ids
	ds	4		;id bytes of 4 drives
;
rtcnt:	ds	1		;disk retry counter
seekrty:ds	1		;seek retry counter
seeker:	ds	1		;seek error flag
chngfl:	ds	1		;non-0 if new drive
olddrv:	ds	1		;old relative drive #
	end
