*--------------------------------------------------------------
*
*  BFDDSK3.ASM
*
*    This is an enhanced FLEX 3 disk driver for the Smoke
*    Signal Broadcasting BFD-68(A) running in the SWTPC
*    6800 computer. Compared to the SWTPC MF-68 driver and
*    a driver using the disk routines in the BFD-68 ROM, 
*    this driver provides the following improvements:
*
*	- 40% to 60% faster disk operations
*	- Support for double sided drives (68A only)
*	- Support for fast stepping drives
*	- Honors head load timing selecting between drives
*	- Honors motor start-up time
*	- Honors side select timing
*	- 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 FLEX to work within its designed sector
*     interleave instead of slipping a full revolution with
*     every sector I/O.
*
*     The step rate for seeking (as defined for the 1771) is
*     stored at $BEB3. The default value is $03 which gives a
*     40ms step interval. A value of $00 can be placed here for
*     a 12ms step interval. The STEPFAST utility does this and
*     can be placed in the STARTUP.TXT file if desired.
*
*    Ver     Date           Notes
*    ---   --------     ---------------------------------------
*    1.0   04/11/23	Original, Mike Douglas
*
*--------------------------------------------------------------

BFD68A	equ	$83F0		0 for 68, not 0 for 68A (from ROM)

* 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)

* FLEX equates

PRCNT	equ	$AC34		FLAG NON ZERO IF SPOOLING

* Driver jump table

	org	$BE80
	jmp	read		read sector
	jmp	write		write sector
	jmp	verify		verify sector just written
	jmp	restor		restore to track 0
	jmp	drvSel		select drive
	jmp	check		check if drive ready
	jmp	check		quick check if drive ready
	jmp	init		cold start init
	jmp	exit0		warm start init
	jmp	exit0		seek to track

* Data definitions

eBit	fcb	0		current E-bit state
selBits	fcb	0		drive and side select bits
xTemp	fdb	0		temp X register storage
curDrv	fcb	4		current drive (4 signals invalid)
trkTbl	fcb	0,0,0,0,0	track for each drive plus guard

	org	$BEB3		step rate must be at $BEB3
stepR8	fcb	3		(can be patched externally)

	org	$BE10		safe start of driver space
*--------------------------------------------------------------
* read - Read sector with track in A, sector in B, X->buffer
*--------------------------------------------------------------
read	jsr	seek		seek to track, set sector to read
	ldab	#READCMD	issue 1771 read sector command
	jsr	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 A, sector in B, X->buffer
*--------------------------------------------------------------
write	jsr	seek		seek to track, set sector to write
	ldab	#WRTCMD		issue 1771 write command
	jsr	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

**************************************************************
* Note: Jump table and data area is in this space
**************************************************************

	org	stepR8+1	resume after jump table and data

*--------------------------------------------------------------
* ioCmd - Issue the read or write command in B. Swap out the
*    spooler if required.
*--------------------------------------------------------------
ioCmd	tst	PRCNT		are we spooling?
	beq	ioCmd1		no
	swi			yes, force task swap

ioCmd1	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 A, sector in B.
*    A seek is issued even if the track does not change as
*    the seek is used to load the head. The 1771 sector
*    register is set to the value in B. The side select
*    output is set based on the sector number. The side
*    select delay of 100us is inherent in the processing
*    here.
*--------------------------------------------------------------
seek	psha			preserve the track number
	ldaa	#OUTSEC		write to 1771 sector register
	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)
	cmpb	#10		side 0 or 1?
	bls	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
	pula			A=track to seek
	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
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

*--------------------------------------------------------------
* verify - Verify the sector just written. Interrupts are
*    not masked so a spooler swap is not required.
*--------------------------------------------------------------
verify	ldaa	#OUTCMD		output a command to the 1771
	ldab	#READCMD	read same sector to verify
	bsr	out1771
	bsr	waitCmd		wait for command to finish
	bitb	#VFYERRS	test for errors
	rts

*--------------------------------------------------------------
* restor - Select drive and restore to track zero.
*   Interrupts are not masked so a spooler swap is not
*   required.
*
*   Returns:
*     Zero false, carry clear if write protected
*     Zero false, carry set for invalid drive
*     Zero true, carry clear otherwise
*--------------------------------------------------------------
restor	stx	xTemp		must preserve X
	bsr	drvSel		select the drive
	ldx	xTemp		restore X
	bcs	restr2		select error

	ldab	#RSTRCMD	restore to track zero
	bsr	doSeek		issue the seek type command
	bitb	#WRTPROT	write protected?
	beq	restr1		no
	ldab	#$0B		else, code for write protected
restr1	clc
restr2	rts

*--------------------------------------------------------------
* drvSel - Select the drive in the FCB pointed to by X.
*    If a drive change occurs, the head is unloaded to ensure 
*    the head load timer restarts with the new drive selection.
*--------------------------------------------------------------
drvSel	bsr	check		check for valid drive
	bcs	selRts		error, exit

	cmpa	curDrv		different drive selected?
	beq	sameDrv		no

* Drive change: Perform a null seek to unload the head which
*    restarts the head load timer.

	psha			save new drive number for later
	ldaa	#INTRK		read track register from 1771
	bsr	in1771		B=1771 track register
	pshb			save current track for later
	ldaa	#OUTDAT		seeking to the same track
	bsr	out1771
	ldab	#HDUNLD		do null seek with head unload
	bsr	doSeek

* Save current track from 1771 to the track table. Load saved track
*    for new drive into the 1771
 
	ldx	#trkTbl		X->saved track table
	ldab	curDrv		B=current drive (before switch)
	stab	saveTrk+1	set save index into trkTbl

	pulb			B=old track number
	pula			A=new drive number
	staa	curDrv		set new current drive
	staa	loadTrk+1	set load index from trkTbl

saveTrk	stab	0,x		save current track into trkTbl
loadTrk	ldab	0,x		B=track for new drive
	ldaa	#OUTTRK		write to 1771 track register
	bsr	out1771

* Put drive select bits in proper position in selBits. 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

	ldaa	curDrv		A=new drive number

sameDrv	tst	BFD68A		is controller 68 or 68A?
	bne	drv68A		it is a 68A

	cmpa	#1		carry set if drive 0
	rola			bit 0 set if drive 0
	bra	drvBits		shift and save drive bits
	
drv68A	asla			put drive in bits 5,4 for 68A
drvBits	asla			
	asla			
	asla
	
	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
*				carry clear here (no error)
selRts	rts 

*--------------------------------------------------------------
* check - Check if the drive in the X->FCB is ready
*   Returns:
*     Zero true, carry clear if drive ready
*     Zero false, carry set if drive not ready
*     A=drive to select
*--------------------------------------------------------------
check	ldaa	3,x		get drive number from FCB
	cmpa	#2		allow 0-2
	bhi	chkBad		invalid drive
exit0	clrb			else, carry clear, zero true
	rts
	
chkBad	ldab	#$80		indicate not ready
	sec
	rts

*--------------------------------------------------------------
* 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	clra			selBits for BFD68
	tst	BFD68A		68 or 68A?
	beq	saveSel		68
	ldaa	#$04		else bit 2 must be on for 68A
saveSel	staa	selBits
	clrb			exit with zero status
	rts

*--------------------------------------------------------------
* Use "END" alone (not even a trailing comment) for FLEX 3 and
* append FLEX3CON.BIN last in the list of files when making
* FLEX3.SYS. If sources are assembled on a PC and uploaded as
* an S19 file, the $AD00 FLEX entry address must be specified
* when saving the last .BIN file that will be specified on the
* APPEND command line.
*--------------------------------------------------------------
	end

