;--------------------------------------------------------------------------
;
;  This is the disk PROM for the FDC+3712. It is optimized for use with
;  the FDC+ implementation of the FD3712.
;
;  The original interface board had 1K of PROM at F000h and required
;  1K of RAM at F400h. This configuration is difficult to implement
;  without a PROM card that can enable just 1K of address space. This
;  PROM does not require the scratchpad RAM and the PROM itself has
;  been moved to F400h.
;
;  The FDC+3712 is much simpler to use if the I/O address of the 3712
;  registers start at 08h instead of the original C0h address. This
;  PROM uses the 08h address.
;
;	Ver	 Date		     Description
;	---   ----------   ------------------------------------------
;	1.0   09/11/2021   M Douglas, Original, based on disassembly
;			   of the original FD3712 PROM found on the
;			   S-100 interface board
;
;--------------------------------------------------------------------------
ROMBASE	equ	0F400h		;base address of PROM
RAMBASE	equ	40h		;reserved RAM for a ROM BIOS
STACK	equ	80h		;stack during cold boot		

; FD3712 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	41h		;shift read buffer
cRESET	equ	81h		;reset the controller
cLDCFG	equ	15h		;load configuration

; Controller status bits

sBUSY	equ	01h		;1=busy
sSKERR	equ	02h		;1=seek error
sCRCERR	equ	08h		;1=CRC error
sWRTPRT	equ	10h		;1=write protected
sNOTRDY	equ	20h		;1=drive not ready

; Interface board registers

CMDOUT	equ	08h		;command output register
DATAIN	equ	08h		;data input from controller
DATAOUT	equ	09h		;data out to controller

; CP/M equates

CCPLEN	equ	0800h		;CP/M 2.2 fixed
BDOSLEN	equ	0E00h		;CP/M 2.2 fixed
MODELOC	equ	33h		;offset to mode byte in BIOS
fWRTVFY	equ	40h		;verify write flag in mode byte

; Drive equates

NUMSEC	equ	26		;sectors per track
SECLEN	equ	128		;length of sector

;--------------------------------------------------------------------------
; RAM is on page zero in a range reserved by CP/M for use by a ROM BIOS
;--------------------------------------------------------------------------
	org	RAMBASE
drvNum	ds	1		;drive number from CP/M
trkNum	ds	1		;track number from CP/M
secNum	ds	1		;sector number from CP/M
dmaAddr	ds	2		;buffer address

drvTrk	ds	1		;track num the drive is known to be on
biosAdr	ds	2		;address of BIOS in RAM

;--------------------------------------------------------------------------
; Entry Vector Table
;--------------------------------------------------------------------------
	org	ROMBASE
	
	jmp	boot		;boot the disk
	jmp	cBoot		;after BIOS cold boot processing
	jmp	wBoot		;perform warm boot
	jmp	home		;disk home
	jmp	selDrv		;select drive
	jmp	setTrk		;set track number
	jmp	setSec		;set sector number
	jmp	setDma		;set I/O buffer address
	jmp	read		;read sector
	jmp	write		;write sector

;--------------------------------------------------------------------------
; boot - load 1st sector from track 0 into RAM at 80h and jump to it
;--------------------------------------------------------------------------
boot	lxi	sp,STACK	;init stack pointer

ldBoot	call	initAll		;init controller, drives, variables
	mvi	c,0		;load boot sector from track zero
	call	setTrk
	lxi	b,80h		;loading into RAM at 80h
	call	setDma
	call	read		;read the boot sector
	jnz	ldBoot		;error, try again

	jmp	80h		;jump to boot sector just loaded

;--------------------------------------------------------------------------
; cBoot - CP/M cold boot post-processing
;--------------------------------------------------------------------------
cBoot	shld	biosAdr		;save address provided for the BIOS
	return

;--------------------------------------------------------------------------
; wBoot - Reload CPM from disk up to, but not including the BIOS.
;--------------------------------------------------------------------------
wBoot	call	initAll		;init controller, drives, variables
	
	lhld	biosAdr		;compute load address for trk 0, sec 3
	lxi	d,-(CCPLEN+BDOSLEN)+SECLEN
	dad	d		;hl->CCP+128
	
	mvi	c,0		;load track 0 starting at sector 3
	call	setTrk
	mvi	a,3
	call	loadTrk

	lhld	biosAdr		;compute load address for trk 1, sec 1
	lxi	d,-(CCPLEN+BDOSLEN)+SECLEN*(NUMSEC-1)
	dad	d
	
	mvi	c,1		;load track 1 starting at sector 1
	call	setTrk
	mvi	a,1
				;fall in to loadTrk

; loadTrk - Load the current track starting on the sector passed 
;   in C. The starting sector should be odd. Every other sector is
;   read (e.g., 3,5,7...25,2,4,6...24,26).

loadTrk	mov	c,a		;C=sector		

	lda	biosAdr+1	;A=MSB of BIOS address
	dcr	a
	cmp	h		;compare BIOS addr - load addr
	jc	ltSkip		;skip if load >= BIOS

	push	h		;preserve HL and BC
	push	b	
	call	setSec		;set sector
	
	mov	b,h		;set DMA address 
	mov	c,l
	call	setDma		;set address
	
	call	read		;read sector
	pop	b		;restore BC and HL
	pop	h
	jnz	wBoot		;start over if read error

ltSkip	lxi	d,2*SECLEN	;interleave of 2
	dad	d		;HL->address for next sector
	
	mvi	a,2		;interleave of 2
	add	c
	cpi	NUMSEC+1	;past last sector?
	jc	loadTrk		;no, read next sector

	sui	NUMSEC-1	;wrap around last sector
	lxi	d,-(SECLEN*(NUMSEC-1))
	dad	d		;compute address after wrap
	cpi	2		;first wrap?
	jz	loadTrk		;yes, keep going
	
	ret

;--------------------------------------------------------------------------
; home, setTrk, selDrv, setSec, setDma - standard BIOS entry points
;--------------------------------------------------------------------------
home	mvi	c,0		;set track to zero

setTrk	mov	a,c		;save track specified in C
	sta	trkNum
	ret        

selDrv	mov	a,c		;save drive number in drvNum
	sta	drvNum
	mvi	a,0FFh		;track num drive is on is not known
	sta	drvTrk
	ret

setSec	mov	a,c		;save sector specified in C
	sta	secNum
	ret

setDma	mov	h,b		;save buffer address specified in BC
	mov	l,c
	shld	dmaAddr
	ret

;--------------------------------------------------------------------------
; read - BIOS entry point. Read 128 byte CP/M sector from the drive,
;   track and sector specified in drvNum, trkNum, and secNum. Sector
;   is moved to the address specified in dmaAddr.
;--------------------------------------------------------------------------
read	call	selSeek		;select drive, seek track
	jnz	errExit		;error

	mvi	c,10		;C=retry counter

rdRtry	mvi	a,cREAD		;issue read command
	call	doCmd
	ani	(sNOTRDY+sCRCERR)  ;check read status
	jz	readXfr		;good read, go transfer data

	call	clrErrs		;clear controller errors 
	dcr	c		;decrement retry counter
	jnz	rdRtry		;and try again

	jmp	errExit		;give up, read failed

readXfr	lhld	dmaAddr		;HL->read destination
	mvi	c,SECLEN	;C=count of bytes to move

rdLoop	mvi	a,cRDBUF	;read and store byte from fifo
	out	CMDOUT
	in	DATAIN
	mov	m,a		

	mvi	a,cSHIFT	;shift fifo to next byte
	out	CMDOUT
	inx	h		;increment our pointer
	dcr	c		;repeat for all bytes
	jnz	rdLoop

	xra	a		;leave in examine status state
	out	CMDOUT
	ret

;--------------------------------------------------------------------------
; write - BIOS entry point. Write 128 byte CP/M sector to the drive,
;   track and sector specified in cpmDrv, cpmTrk, and cpmSec. Sector
;   data to be written is specified in dmaAddr. 
;--------------------------------------------------------------------------
write	call	selSeek		;select drive, seek to track
	jnz	errExit

	lhld	dmaAddr		;HL->user buffer
	mvi	c,SECLEN	;C=count of bytes to move

wrtLoop	mov	a,m		;A=next byte to write
	out	DATAOUT		;give to controller
	
	mvi	a,cWRTBUF	;command to move byte to write buffer
	out	CMDOUT	
	xra	a		;return bit zero to zero
	out	CMDOUT
	
	inx	h		;move to next byte
	dcr	c
	jnz	wrtLoop

	mvi	c,10		;C=retry counter

wrtRtry	mvi	a,cWRITE	;do the write to disk
	call	doCmd
	ani	sNOTRDY		;error?
	jz	chkCrc		;no, go check CRC

	call	clrErrs		;reset errors
	jmp	errExit		;and give up

chkCrc	lhld	biosAdr		;compute location of mode byte
	lxi	d,MODELOC
	dad	d		;hl->mode byte
	mov	a,m		;A=mode byte
	ani	fWRTVFY		;test write verify bit
	rz			;not verifying

	mvi	a,cRDCRC	;read sector to check CRC
	call	doCmd
	ani	(sNOTRDY+sCRCERR)  ;any errors?
	rz			;no, good write

	call	clrErrs		;clear controller errore
	dcr	c		;decrement retry count
	jnz	wrtRtry		;and try again

errExit	mvi	a,1		;return with A and status <> 0	
	ora	a
	ret

;--------------------------------------------------------------------------
; selSeek - Select current drive, seek to current track, give
;    controller current sector to look for
;--------------------------------------------------------------------------
selSeek	xra	a		;clear 3812 configuration bits
	out	DATAOUT		;  in case 3812 used as 3712?
	mvi	a,cLDCFG	;issue the command
	call	outCmd

	call	selSec		;select drive, set sector
	call	seek		;seek to requested track
	ret

selSec	lda	drvNum		;get drive number 0-3
	ani	03h
	rrc			;put drive number in upper two bits
	rrc
	mov	c,a		;C=drive in upper two bits

	lda	secNum		;combine drive with sector number
	ora	c
	out	DATAOUT		;give to controller

	mvi	a,cDRVSEC	;issue set sector command
	call	outCmd
	ret

;--------------------------------------------------------------------------
; seek - Seek to curTrk.
;--------------------------------------------------------------------------
seek	mvi	c,2		;C=retry counter

seekLp	lda	trkNum		;A=requested track	
	lxi	h,drvTrk	;HL->track drive is on
	cmp	m		;on right track already?
	rz			;yes

	mov	m,a		;save track drive will be on
	lda	trkNum		;give new track # to controller
	out	DATAOUT
	mvi	a,cSETTRK	;and issue set track command
	call	outCmd

	mvi	a,cSEEK		;seek to the track specified
	call	doCmd
	ani	(sNOTRDY+sCRCERR)   ;seek error?
	rz			;no, exit

	call	clrErrs		;else, clear controller errors
	mvi	m,0FFh		;track not valid yet	
	dcr	c		;decrement retry counter
	jnz	seekLp

	call	reset0		;reset controller, seek track 0
	mvi	a,sSKERR	;return seek error
	ora	a		;force non-zero status
	ret

;--------------------------------------------------------------------------
; initAll - Initialize variables, reset controller, restore drive 0
;    to track 0
; reset0 - reset controller, restore current drive to track 0
;--------------------------------------------------------------------------
initAll	xra	a		;default to drive 0
	sta	drvNum
	inr	a		;default to sector 1
	sta	secNum

reset0	mvi	a,cRESET	;reset controller
	call	outCmd

	call	selSec		;select drive and sector
	mvi	a,0FFh		;current drive track not known
	sta	drvTrk
	mvi	a,cRESTOR	;issue restore to track 0
				;fall into doCmd

;--------------------------------------------------------------------------
; doCmd - Issue command in A to the controller, wait for completion,
;   and return status.
;--------------------------------------------------------------------------
doCmd	call	outCmd		;give command to the controller

waitCmd	in	DATAIN		;wait for not busy
	ani	sBUSY
	jnz	waitCmd

	in	DATAIN		;return status of the operation
	ret

;--------------------------------------------------------------------------
; clrErrs - Issue the clear errors command to the controller
; outCmd - Issue the command in A to the controller
;--------------------------------------------------------------------------
clrErrs	mvi	a,cCLRERR	;clear controller errors

outCmd	out	CMDOUT		;give command to the controller
	xra	a		;exit in examine status mode
	out	CMDOUT
	ret

	end
