*--------------------------------------------------------------
*
*  DOS68DRV.ASM
*
*    This is an enhanced DOS-68 disk driver for the Smoke
*    Signal Broadcasting BFD-68(A) running in the SWTPC
*    6800 computer. It is placed in RAM at $BE00-$BFFF and
*    DOS68 is patched to call these routines instead of the
*    ROM routines. Compared to the I/O routines in the BFD-
*    68 ROM, this driver has the following improvements:
*
*	- 40% to 60% faster disk operations
*	- Support for fast stepping drives
*	- Honors head load timing selecting between drives
*	- Honors trim erase timing
*
*     The speed improvement was achieved by turning on the E
*     bit in read/write commands only when necessary. This,
*     in turn, allows a shorter interleave of 7 to be used
*     instead of 11. This means the FORMAT routine that is
*     part of DOS68 will create a disk that does NOT have
*     the speed improvement. Instead, format with PC2FLOP-7.
*
*     The step rate for seeking (as defined for the 1771) is
*     stored at $BE00. The default value is $03 which gives a
*     40ms step interval. A value of $00 can be placed here for
*     a 12ms step interval.
*
*    Ver     Date           Notes
*    ---   --------     ---------------------------------------
*    1.0   04/28/23	Original, Mike Douglas
*
*--------------------------------------------------------------

BFD68A	equ	$83F0		0 for 68, not 0 for 68A (from ROM)
DOSDRV	equ	$A07B		drive number as above
DOSTRK	equ	$A07C		track number as above
DOSSEC	equ	$A07D		sector number as above
DOSBUF	equ	$A07E		pointer to sector buffer

SWTBUG	equ	$E0E3		return to SWTBUG

* PIA Equates
*   The 1771 controller is accessed through a 6821 PIA. Values
*   are written to port A of the PIA to select the 1771 register
*   to read/write and then port B is used to transfer data bytes
*   to/from the 1771. The PIA connects directly to the negative
*   logic SS-50 data bus, so the values specifically for and
*   from the 6821 must be inverted. However, since the 1771 
*   also uses an inverted data bus, the values for and from
*   the 1771 do not need to be inverted.
*
*   In the PIA, CA1 is used to detect the rising edge of the INTRQ
*   line from the 1771. CA2 is used to detect the rising edge of
*   the DRQ handshake line from the 1771 when reading sector data.
*   CB2 is used as a negative going output strobe for data written
*   to the 1771. CB1 can be used to detect motor start if J15 is
*   installed.

PIABASE	equ	$9FFC		board controlled through a 6821 PIA

ADATA	equ	PIABASE+0	data register A
ADIR	equ	PIABASE+0	data direction register A
ACONTRL	equ	PIABASE+1	control register A

BDATA	equ	PIABASE+2	data register B
BDIR	equ	PIABASE+2	data direction register B
BCONTRL	equ	PIABASE+3	control register B

SELADIR	equ	$FF-$1A		value to access Port A dir reg
SELADAT	equ	$FF-$1E		value to access Port A data reg
SELBDIR	equ	$FF-$28		value to access Port B dir reg
SELBDAT	equ	$FF-$2C		value to access Port B data reg

* PIA Port A values to address the 1771. The values here are positive
*   true and are inverted before writing.
 
OUTCMD	equ	$40		write to 1771 command register
OUTTRK	equ	$41		write to 1771 track register
OUTSEC	equ	$42		write to 1771 sector register
OUTDAT	equ	$43		write to 1771 data register

INSTAT	equ	$00		read 1771 status register
INTRK	equ	$01		read 1771 track register
INSEC	equ	$02		read 1771 sector register
INDAT	equ	$C3		read 1771 data reg (w/DRQ handshake)

* 1771 disk controller equates

RDERRS	equ	$1C		bit mask for read errors
VFYERRS	equ	$18		bit mask for verify errors
WRTERRS	equ	$5C		bit mask for write errors
HEADMSK	equ	$20		bit mask for head loaded
WRTPROT	equ	$40		bit mask for write protected

READCMD	equ	$88		read command (E bit off)
WRTCMD	equ	$A8		write command (E bit off)
RSTRCMD	equ	$08		restore to track 0
SEEKCMD	equ	$18		seek command (loads head)
HDUNLD	EQU	$10		seek command (unloads head)
FORCINT	equ	$D0		force interrupt (reset 1771)

* Data definitions

	org	$BE00
stepR8	fcb	3		step rate bits for 1771
eBit	fcb	0		current E-bit state
selBits	fcb	0		drive and side select bits
drvBits	fcb	$FF		drive select bit ($FF forces change)

* Duplicate of BFD-68(A) ROM jump table

	org	$BE20
	jmp	SWTBUG		cold start
	jmp	SWTBUG		warm start
	jmp	init		initialize
	jmp	read		read sector
	jmp	write		write sector
	jmp	SWTBUG		read track
	jmp	SWTBUG		write track
	jmp	SWTBUG		seek
	jmp	restor		restore to track 0
	jmp	SWTBUG
	jmp	SWTBUG

	org	$BE60
	jmp	SWTBUG		write 1771 command
	jmp	SWTBUG		wait for command to finish
	jmp	SWTBUG		read 1771 status
	jmp	SWTBUG		set sector in 1771
	jmp	setTrk		set track from DOSTRK to 1771
	jmp	SWTBUG		set seek track	
	jmp	getTrk		read track register into B
	jmp	SWTBUG		step in one track
	jmp	SWTBUG		step out one track
	jmp	SWTBUG		execute step command at $3B
	jmp	SWTBUG

*--------------------------------------------------------------
* init - Initialize selBits as required for the BFD-68
*   versus the BFD-68A. Bit 2 must be low for the BFD-68
*   (connected to chip select on the 1771) and must be
*   high on the BFD-68A (enables head load timer).
*--------------------------------------------------------------
init	ldaa	BFD68A		68 or 68A?
	beq	saveSel		68
	ldaa	#$04		else bit 2 must be on for 68A
saveSel	staa	selBits

	ldaa	#$FF		ensure new drive select occurs
	staa	drvBits
	rts

*--------------------------------------------------------------
* read - Read sector with track in DOSTRK, sector in DOSSEC,
*    and sector buffer DOSBUF
*--------------------------------------------------------------
read	jsr	seek		seek to track, set sector to read
	ldab	#READCMD	issue 1771 read sector command
	bsr	ioCmd

	jsr	setIn		set port B as input (reading)
	ldaa	#INDAT		set port A to read sector data
	oraa	selBits		include drive select bits
	coma			data must be inverted
	staa	ADATA

rdLoop	ldaa	ACONTRL		sector done (INTRQ)?
	bpl	rdDone		yes
	asla			new byte ready (DRQ)?
	bmi	rdLoop		no

	ldaa	BDATA		get byte
	staa	0,x		save in buffer
	inx
	ldaa	ADATA		clear DRQ handshake
	bra	rdLoop

rdDone	asla			need to read last byte?
	bmi	rdExit		no
	ldaa	BDATA		else, get last byte
	staa	0,x

rdExit	jsr	rdStat		exit with 1771 status in B
	bitb	#RDERRS		check read error bits
	cli
	rts

*--------------------------------------------------------------
* write - Write sector with track in DOSTRK, sector in DOSSEC,
*    and sector buffer DOSBUF
*--------------------------------------------------------------
write	bsr	seek		seek to track, set sector to write
	ldab	#WRTCMD		issue 1771 write command
	bsr	ioCmd
	ldaa	#OUTDAT		sector bytes go to data register
	jsr	out1771

wrtLoop	ldaa	ACONTRL		end of sector (INTRQ)?
	bpl	wrtDone		yes
	asla			new byte requested (DRQ)?
	bmi	wrtLoop		no

	ldaa	0,x		get next byte
	staa	BDATA		give to 1771
	inx
	ldaa	ADATA		clear DRQ handshake
	bra	wrtLoop

wrtDone	ldaa	#1000/6		delay 1ms for trim erase

trimDly	deca
	bne	trimDly

	jsr	rdStat		B=write status
	bitb	#WRTERRS	check write error bits
	cli
	rts

*--------------------------------------------------------------
* ioCmd - Issue the read or write command in B. Exits with
*   X->data buffer from DOSBUF
*--------------------------------------------------------------
ioCmd	ldx	DOSBUF		X->transfer buffer
	orab	eBit		include possible E-bit
	clr	eBit		assume not needed next time
	sei			disable interrupts
	ldaa	#OUTCMD		output a command to the 1771
	bra	out1771		do the output and return

*--------------------------------------------------------------
* seek - Seek to the track specified in DOSTRK and sector
*    sector from DOSSEC. A seek is issued even if the track
*    does not change as the seek is used to load the head.
*    DOS68 provides soide select in the $20 bit of the sector
*    number. Top and bottom side sector numbers are the same.
*--------------------------------------------------------------
seek	jsr	drvSel
	ldaa	#OUTSEC		write to 1771 sector register
	ldab	DOSSEC
	andb	#$1F
	bsr	out1771		output sector from B

	ldaa	BFD68A		which controller?
	beq	headChk		68 does not have side select

	ldaa	selBits		update side select status
	anda	#$F7		assume side 0 (bit 3)
	ldab	DOSSEC
	andb	#$20		side 0 or 1?
	beq	setSide		side 0
	oraa	#$08		else, set side 1
setSide	staa	selBits		save updated select bits

headChk	ldaa	#OUTCMD		write to 1771 command register
 	ldab	#FORCINT	issue "FORCE INTERRUPT" to get
	bsr	out1771		...type I status
	bsr	rdStat
	andb	#HEADMSK	is head loaded?
	bne	seek2		yes

	ldaa	#$04		else, set E-bit for next I/O
	staa	eBit		...to wait on head load
 
seek2	ldaa	#INTRK		get the track drive is on
	bsr	in1771		B=track drive is on
	ldaa	DOSTRK		A=track to seek
	anda	#$7F
	cba			already on correct track?
	beq	sameTrk		yes, E-bit not needed

	ldab	#$04		turn on E-bit to force 20ms
	stab	eBit		...for head settle time

sameTrk	tab			B=track to seek
	ldaa	#OUTDAT		write track to seek to data reg
	bsr	out1771

	ldab	#SEEKCMD	always issue seek to load the head
doSeek	ldaa	#OUTCMD		output a command to the 1771
	orab	stepR8		include step rate bits
	bsr	out1771
*				fall into waitCmd

*--------------------------------------------------------------
* waitCmd - Wait for INTRQ ("done") from the 1771
* rdStat - Return 1771 status register in B
* in1771 - Read a 1771 register into B. A contains the port
*    address code to select the desired 1771 register.
*--------------------------------------------------------------
waitCmd	ldaa	ACONTRL		wait for INTRQ from 1771	
	bmi	waitCmd

rdStat	ldaa	#INSTAT		read the 1771 status register

in1771	bsr	setIn		set port B as input
	oraa	selBits		include drive select bits
	coma			port A must be written inverted
	staa	ADATA		set register to read from
 	ldab	BDATA		read the selected register
	bsr	delay13		give 13us for INTRQ to de-assert
	ldaa	ADATA		clear INTRQ flag in PIA
	tstb			exit with zero status check
delay13	rts

*--------------------------------------------------------------
* out1771 - Write to a 1771 register. Register A contains the
*  port address code and B contains the value to write.
*--------------------------------------------------------------
out1771	oraa	selBits		include drive select bits
	coma			port A must be written inverted
	staa	ADATA		set the 1771 register to write
	bsr	setOut		set port B for output
	stab	BDATA		output the passed data
	rts

*--------------------------------------------------------------
* setIn, setOut - Set port B direction
*--------------------------------------------------------------
setIn 	psha			preserve registers
	pshb
	ldab	#$FF		set as input (inverted)
	bra	setDir

setOut	psha			preserve registers
	pshb
	clrb			set as output (inverted)

setDir	ldaa	#SELBDIR	access data direction reg 
	staa	BCONTRL
	stab	BDIR		updated the direction
	ldaa	#SELBDAT	back to the data register
	staa	BCONTRL

	pulb			restore and exit
	pula
	rts

*--------------------------------------------------------------
* restor - Select drive and restore to track zero.
*--------------------------------------------------------------
restor	bsr	drvSel		select the drive
	ldab	#RSTRCMD	restore to track zero
	bra	doSeek		issue the seek type command

*--------------------------------------------------------------
* drvSel - Select the drive in DOSDRV
*    If a drive change occurs, the head is unloaded to ensure 
*    the head load timer restarts with the new drive selection.
*--------------------------------------------------------------
drvSel	ldaa	DOSDRV		A=drive to use
	cmpa	drvBits		different drive selected?
	beq	sameDrv		no

* For the BFD68, drive select is $08, $10, $20 for drive 0, 1, 2.
*    For the 68A, drive select is a two bit value in bits 5,4.

	staa	drvBits		save new select bits (68 style)
	tst	BFD68A		is controller 68 or 68A?
	beq	drv68		it is a 68
	anda	#$30		else 0-2 in bits 5,4
	
drv68	ldab	selBits		put new drive number into selBits
	andb	#$04		keep bit 2
	aba			combine with drive select			
	staa	selBits		save new drive select bits

* Drive change: Perform a null seek to unload the head which
*    restarts the head load timer.

	ldaa	#INTRK		read track register from 1771
	bsr	in1771		B=1771 track register
	ldaa	#OUTDAT		seeking to the same track
	bsr	out1771
	ldab	#HDUNLD		do null seek with head unload
	bsr	doSeek

sameDrv	rts

*--------------------------------------------------------------
* setTrk - Set the 1771 track register from DOSTRK
* getTrk - Return the 1771 track register in B
*--------------------------------------------------------------
setTrk	ldab	DOSTRK		B=track number to write
	andb	#$7F
	ldaa	#OUTTRK		write to 1771 track register
	bra	out1771		write track reg and exit

* getTrk - Return the 1771 track register in B

getTrk	ldaa	#INTRK		read track register from 1771
	bra	in1771		reat track reg ane exit

	ORG	$BFD0

* Patch references to the ROM to point to this driver instead

* DDE4  7E 8026	 JMP	$8026    SSB INITIALIZE PIA ROUTINE
* DD47  BD 8038	 JSR	$8038    SSB RESTORE
* DDA0  BD 8038	 JSR	$8038    SSB RESTORE
* DD30  CE 802C	 LDX	#$802C   SSB WRITE SECTOR ROUTINE
* DD35  CE 8029	 LDX	#$8029   SSB READ SECTOR ROUTINE 
* DD88  8C 8029	 CPX	#$8029   SSB READ SECTOR ROUTINE 
* DD51	BD 806C	 JSR	$806C    SSB Write track register
* DDB7  BD 8072	 JSR	$8072    SSB Read track register

patch	ldaa	#$BE
	staa	$DDE5
	staa	$DD48
	staa	$DDA1
	staa	$DD31
	staa	$DD36
	staa	$DD89
	staa	$DD52
	staa	$DDB8

	jsr	init		run the init routine
	jmp	$D283		jump to DOS warm start entry

	org	$A048		"G"O command address
	fdb	patch
	end
