;+
; Edit History
;
; Nov-25-91 ml.	The ACSI DMA channel will be used in the Sparrow's
;		SCSI.  (Argh!)  So, code is modified to talk to the
;		5380 through wdl and wdc.  This means Sparrow's SCSI
;		inherited all the restrictions of ACSI, except for
;		the opcode of a command (now we get all 8 bits.)
;		This is a merge of acsi.s and scsi.s of v5.00.
;
; Dec-10-91 ml.	Don't need to have extra long timeout in _rcvspscsi()
;		and _wrtspscsi() because extended read and write are 
;		not supported due to the 8 bit counter in the DMAC.
;
; Jan-27-92 ml.	Try optimizing it.
;		*NOTE*  Do NOT use 'move.l's to select the SCSI 
;			registers and write data to them.  It is
;			because 680x0 writes the high word first.
;			So, writing a long word to WDCWDL means
;			writing the data before pointing to the
;			correct register.  In the ACSI code, the
;			move.l's are writing data to the previous
;			address, and setting up the address for
;			the next access.  It was confusing, and it
;			is not done here.
;
; Jan-28-92 ml.	In order for AHDI to work at IPL 7, Timer C is used
;		instead of hz_200 for timeout purposes.
;
; Feb-12-92 ml.	Modified to use the "flush" bit and the extended counter.
;-


.include	"defs.h"
.include	"sysvar.h"
.include	"mfp.h"
.include	"acsi.h"
.include	"spscsi.h"
.include	"macros.h"
.include	"68030.s"

;+
; Command length
;-
NCMD	equ	6		; length of short command (in bytes)


.if	!DRIVER		    ; not to be included in driver
scxltmout:  dc.l    12001   ; SCSI extra long-timeout (>1 min)
slwsclto:   dc.l    5000    ; SCSI long-timeout (>25 S) for stunit()
slwscsto:   dc.l    42	    ; SCSI short-timeout (>205 mS) for stunit()
scltmout:   dc.l    201	    ; SCSI long-timeout (>1000 ms)
scstmout:   dc.l    101	    ; SCSI short-timeout (>500 ms)
rcaltm:	    dc.l    801	    ; time for drive recalibration (>4s)
.else	;!DRIVER
.extern	scxltmout, slwsclto, slwscsto, scltmout, scstmout, rcaltm
.endif	;!DRIVER

.extern	setadma, setacnt, _hdone
stat:	    dc.w    0		; status byte
flush:	    dc.b    0		; flush flag
.even

;+
; smplspscsi() - send a simple SCSI command (ie. no DMA involved)
;
;	d0.w = physical unit number
;	d1.l = transfer length (in bytes)
;	d2.w = command length (NCMD or LCMD)
;	a0.l = buffer address
;-
	.globl	_smplspscsi
_smplspscsi:
	st	flock
	andi.w	#7,d0			; mask off the flags to get unit num
	bsr	sblkspscsi		; send command block
	tst.w	d0			; successful?
	bmi.s	.0			; if not, go clean up
	bsr	w4spstat		; else wait for status
.0:	RSCSI	#SPSCSIREI,d1		; clear potential interrupt
	bra	_hdone			; go clean up



;+
; rcvspscsi() - send a SCSI command which receives data back.
;
; Passed:
;	d0.w = physical unit number
;	d1.l = transfer length (in bytes)
;	d2.w = command length (NCMD or LCMD)
;	a0.l = buffer address
;-
	.extern	_cachexst
	.globl	_rcvspscsi
_rcvspscsi:
	st	flock			; lock FIFO
	andi.w	#7,d0			; mask off the flags to get unit num
	movem.l	d1-d2/a0,-(sp)		; save buffer address and cmd length
	bsr	sblkspscsi		; send command block
	movem.l	(sp)+,d1-d2/a0		; restore buffer address and cmd len
	tst.w	d0			; successful?
	bmi	rspend			; if not successful, return

;+
; Feb-13-1992	Check whether FIFO needs to be flushed.
	sf	flush			; assume no need to flush
	move.l	d1,d0			; d0 = transfer length
	andi.l	#$0f,d0			; multiple of 16?
	beq.s	rdma0			; if so, no need to flush
	st	flush			; else, set flush flag
;-

rdma0:	WSCSI	#0,#SPSCSIICR		; deassert the data bus
	WSCSI	#1,#SPSCSITCR		; set data in phase
	RSCSI	#SPSCSIREI,d0		; clear potential interrupt

.if	SPSCDMA				; Set up the DMAC for data transfer
;	cmpi.l	#512,d1			; transferring >= 512 bytes?
;	bcs	rpio			; if not, do programmed IO
;					; else set up DMA chip for DMA

	WSCSI	#2,#SPSCSIMR		; enable DMA mode
 	movea.l	#WDC,a1			; a1 = pointer to DMA chip
	bsr	setadma			; set DMA pointer
	move.w	#$190,XWDL(a1)	;WDL	; toggle DMA chip for "receive"
	RSTDELAY			; delay
	move.w	#$090,XWDL(a1)	;WDL
	RSTDELAY			; delay
	bsr	setacnt

rdma1:	btst.b	#STA,WDSR		; wait till it's safe to access
	bne.s	rdma1			;  the DMA channel

;	TMOUT	#2,d0			; delay before using DMA channel
;	CNTALL	d0

	WSCSI	#0,#SPSCSIDIR		; start the DMA receive
	move.w	#0,WDL

;+
; Dec-10-1991	ml.	Don't need to have extra long timeout because
;			extended read and write are not supported due
;			to the 8 bit counter in the DMAC.
;
; Feb-13-1992	ml.	Put code back in because DMAC has been modified
;			to have a 14-bit counter.
	moveq	#0,d0			; assume no extra timeout
	cmpi.w	#NCMD,d2		; is this a long command?
	beq.s	rdma2			; if not, just go on
	move.l	scxltmout,d0		; set up an extra long timeout
;-

rdma2:	bsr	w4spint			; wait for interrupts
	tst.w	d0			; successful?
	bne.s	rspend			; if error, returns

;+
; Feb-13-1992	ml.	Added code to use the flush bit on the DMAC.
;			Now, we can do DMA without sending a command
;			multiple times, to write everything from the 
;			FIFO to memory, for transfers of non-multiple
;			of 16 bytes.  Using this flush function requires
;			the buffer provided for the command to be as big 
;			as the closest multiple of 16 to receive all data.
;			
	tst.b	flush			; need to flush FIFO?
	beq.s	rdma3			; if not, proceed	
	move.w	#$0020,WDL		; else flush (Whoosh!)
;-
rdma3:	tst.b	_cachexst		; is there a cache to flush?
	beq.s	rstat			; if not, go get status
	move	sr,-(sp)		; go to IPL 7
	ori	#$700,sr		; no interrupts right now kudasai
	movecacrd0			; d0 = (cache control register)
	ori.w	#$808,d0		; dump both the D and I cache
	moved0cacr			; update cache control register
	move	(sp)+,sr		; restore interrupt state
	bra.s	rstat			; go read status 
.endif	;SPSCDMA

rpio:	movea.l	a0,a1			; a1 -> buffer to read into
rnxtb:	bsr	w4spdreq		; wait for REQ for data to come
	bmi.s	rspend			; if timed out, returns
	RSCSI	#SPSCSIDSR,d0		; still in data in phase?
	btst	#3,d0			
	beq	rstat			; if not, go get status
	RSCSI	#bSPSCSI,d0		; else read the next data byte
	move.b	d0,(a1)+	
	bsr	dospack
	bra.s	rnxtb			; do next byte
rstat:	bsr	w4spstat		; go get status byte
rspend:	RSCSI	#SPSCSIREI,d1		; clear potential interrupt
	bra	_hdone			; go clean up



;+
; wrtspscsi() - send a SCSI command which will write data to the target
;
; Passed:
;	d0.w = physical unit number
;	d1.l = transfer length (in bytes)
;	d2.w = command length (NCMD or LCMD)
;	a0.l = buffer address
;
; Comments: 
; 12/04/89 ml
;	Bus error occurs when doing a write to the disk that ends at top
; of memory.  The DMA counter is decremented when the bytes are written 
; from the ping pong buffers to the device, not when bytes are grapped 
; from RAM to the ping pong buffers.  Well, AFTER the last 8 bytes are 
; read into the ping pong buffers and BEFORE they are written to the 
; device, the chip will attempt to read the NEXT 8 bytes into the ping 
; pong buffers which results in a bus error because it will be reading 
; pass top of memory.  To get around this HARDWARE BUG, the code will 
; ALWAYS handshake the last 8 bytes over instead of DMAing them.
;
; 01/23/90 ml
;	A. Pratt said he's willing to move the screen down and sacrifice
; 16 bytes of memory.  So, code added on 12/04/89 is commented out.
;-
	.globl	_wrtspscsi
_wrtspscsi:
	st	flock			; lock FIFO
	andi.w	#7,d0			; mask off the flags to get unit num

	movem.l	d1-d2/a0,-(sp)		; save beginning buffer address
	bsr	sblkspscsi		; send command block
	movem.l	(sp)+,d1-d2/a0		; a0 = where DMA ends
	tst.w	d0			; successful?
	bmi	wspend			; if not, go clean up

	WSCSI	#0,#SPSCSITCR		; set data out phase
	RSCSI	#SPSCSIREI,d0		; clear potential interrupt

.if	SPSCDMA
					; Set up the DMAC for data transfer
	cmpi.l	#512,d1			; transferring >= 512 bytes?
	bcs.s	wpio			; if not, do programmed IO
					; else do DMA
	WSCSI	#2,#SPSCSIMR		; enable DMA mode

	movea.l	#WDC,a1			; a1 = pointer to DMA chip

	bsr	setadma			; set DMA pointer
	move.w	#$90,XWDL(a1)	;WDL	; toggle DMA chip for "send"
	RSTDELAY			; delay 
	move.w	#$190,XWDL(a1)	;WDL
	RSTDELAY			; delay
	bsr	setacnt			; set DMA count

wdma0:	btst.b	#STA,WDSR		; wait till it's safe to access
	bne.s	wdma0			;  the DMA channel

;	TMOUT	#2,d0			; delay before using DMA channel
;	CNTALL	d0

	WSCSI	#0,#($100|SPSCSIDS)	; start the DMA send
	move.w	#$100,WDL

;+
; Dec-10-1991	ml.	Don't need to have extra long timeout because
;			extended read and write are not supported due
;			to the 8 bit counter in the DMAC.
;
	moveq	#0,d0			; assume no extra timeout
	cmpi.w	#NCMD,d2		; is this a long command?
	beq.s	wdma1			; if not, just go on
	move.l	scxltmout,d0		; set up an extra long timeout
;-

wdma1:	bsr	w4spint			; wait for interrupts
	tst.w	d0
	beq.s	wstat			; if ok, wait for status byte
	bra.s	wspend			; else return
.endif	;SPSCDMA
					; hand shake data over the bus
wpio:	movea.l	a0,a1			; a1 -> buffer to write from
wnxtb:	bsr	w4spdreq		; wait for REQ for data to come
	bmi.s	wspend			; if timed out, returns
	RSCSI	#SPSCSIDSR,d0
	btst	#3,d0			; still in data out phase?
	beq.s	wstat			; if not, go get status
	moveq	#0,d0
	move.b	(a1)+,d0		; write the next data byte
	WSCSI	d0,#bSPSCSI
	bsr	dospack
	bra.s	wnxtb			; do next byte
wstat:	bsr	w4spstat		; get status byte
wspend:	RSCSI	#SPSCSIREI,d1		; clear potential interrupt
	bra	_hdone			; go clean up



;+
; sblkspscsi() - set DMA pointer and count and send command block
;
; Passed:
;	d0.w = physical unit number
;	d1.l = transfer length (in bytes)
;	d2.w = command length (NCMD or LCMD)
;	a0.l = buffer address
;
; Returns:
;	d0.l =  0 if successful
;	d0.l = -1 if timeout
;-
	.extern	_cmdblk
sblkspscsi:
	movem.l	d1-d2/a0,-(sp)		; preserve d1, d2 and a0
	move.w	d0,-(sp)		; physical unit #
	bsr	selspscsi		; select the unit
	addq.l	#2,sp			; clean up stack
	movem.l	(sp)+,d1-d2/a0		; restore d1, d2 and a0
	tst.w	d0			; selection successful?
	bmi.s	sbspend			; if timed out, return
					; else proceed
	WSCSI	#2,#SPSCSITCR		; assert C/D
	WSCSI	#1,#SPSCSIICR		; assert data bus

	lea	_cmdblk,a1		; a1 -> command block
	subq.w	#1,d2			; dbra likes one less
sbs0:	bsr	w4spreq			; wait for REQ to come
	bmi.s	sbspend			; if timed out, returns
	move.b	(a1)+,d0		; d0.b = byte to be sent
	WSCSI	d0,#SPSCSIDB		; write a byte out to data bus
	bsr	dospack			; assert ACK
	dbra	d2,sbs0			; until whole command block is sent
	moveq	#0,d0			; all operations successful
sbspend: rts				; heading home


;+
; BOOLEAN selspscsi(SCSIUnit) 
; WORD SCSIUnit;
;-
selspscsi:
	TMOUT	scstmout,d1		; set up a short timeout
	move.w	#SPSCSICR,WDL
.0:	move.w	WDC,d0
	btst	#6,d0			; STILL busy from last time?
	beq.s	.1			; if not, it's available
	CNTDN1	d1,.0			; else if not timed-out, wait longer
	bra	.3			; else, return error

.1:	WSCSI	#0,#SPSCSITCR		; data out phase
	WSCSI	#0,#SPSCSIISR		; no interrupt from selection
	WSCSI	#$0c,#SPSCSIICR		; assert BSY and SEL
; set dest SCSI IDs
	clr.w	d0
	move.w	4(sp),d1		; get the SCSI unit desired
	bset	d1,d0			; set the appropriate bit
	WSCSI	d0,#SPSCSIODR		; (real code would set ours too)

;	WSCSI	#$0d,#SPSCSIICR		; assert BUSY, SEL and data bus
	WSCSI	#$05,#SPSCSIICR		; assert SEL and data bus
	RSCSI	#SPSCSIMR,d0		; read Mode Register
	andi.b	#$fe,d0			; clear arbitrate bit
	move.w	d0,WDC
	RSCSI	#SPSCSIICR,d0		; read Initiator Command Register
	andi.b	#$f7,d0			; clear BUSY
	move.w	d0,WDC
	nop				; 2 deskew delays
	nop

	TMOUT	scstmout,d1		; set up timeout counter
	move.w	#SPSCSICR,WDL
.2:	move.w	WDC,d0			; wait for bus to be busy
	btst	#6,d0
	bne.s	.4

	CNTDN1	d1,.2			; if not timed-out, wait some more

.3:	moveq	#-1,d0			; time out
	bra.s	.5
	
.4:	clr.w	d0			; selection successful
.5:	WSCSI	#0,#SPSCSIICR		; clear SEL and data bus assertion
	rts


;+
; VOID resetspscsi();
;
; Feb-14-1992	ml.	Added code to reset DMAC before talking through
;			it to reset the SCSI bus.  To protect DMAC from
;			locking up, check if it's safe to access it
;			before doing any of the above.
;-
	.globl	_resetspscsi
	.extern	setscstmout
	.extern	setscltmout
_resetspscsi:
rst0:	btst.b	#STA,WDSR	; wait till it's safe to access
	bne.s	rst0		;  the DMA channel

	move.w	#$190,WDL	; reset DMA chip
	RSTDELAY		; delay
	move.w	#$090,WDL
	RSTDELAY
				; reset SCSI bus
	WSCSI	#$80,#SPSCSIICR	; assert RST
;	TMOUT	scstmout,d1	; wait (at least) 250 ms
;	CNTALL	d1
	bsr	setscstmout	; wait (at least) 250 ms
.0:	cmp.l	(a0),d1
	bhi.s	.0
;	move.w	#0,WDC
	WSCSI	#0,#SPSCSIICR	; clear RST
;	TMOUT	scltmout,d1	; wait (at least) 1000 ms
;	CNTALL	d1
	bsr	setscltmout	; wait (at least) 1000 ms
.1:	cmp.l	(a0),d1
	bhi.s	.1
	rts


;+
; w4spint - wait for interrupts from 5380 or DMAC during DMA tranfers
;
; Returns:
;	d0.l = returned status or timeout error
;
; Comments:
;	When 5380 is interrupted, it indicates a change of data to
; status phase (i.e., DMA is done), or ...
;	When DMAC is interrupted, it indicates either DMA count is
; zero, or there is an internal bus error.
;-
w4spint:
	TMOUT	rcaltm,d1		; set timeout (incl. recalibration)
	add.l	d0,d1
.0:	btst	#5,GPIP			; interrupt?
	beq.s	.1			; active LOW
	CNTDN1	d1,.0			; if there's more time, keep waiting
					; else timed-out
	bsr	_resetspscsi		; reset the SCSI bus
	moveq	#-1,d0			; return timeout
	bra.s	w4spiend

.1:	moveq	#0,d0			; DMA is successful
	RSCSI	#SPSCSIREI,d1		; clear potential interrupt
	WSCSI	#0,#SPSCSIMR		; disable DMA mode
	WSCSI	#0,#SPSCSIICR		; make sure data bus is not asserted
w4spiend:
	rts


;+
; w4spstat - wait for status byte and message byte.
;
; Returns:
;	d0.l = returned status or timeout error
;-
w4spstat:
	WSCSI	#3,#SPSCSITCR		; status in phase
	RSCSI	#SPSCSIREI,d0		; clear potential interrupt

	bsr	w4spreq			; wait for status byte
	bmi.s	w4spend			; if timed-out, returns
gspstat:
	RSCSI	#SPSCSIDB,d0		; get the status byte
	andi.w	#$ff,d0
	move.w	d0,stat			; save the status byte
	bsr	dospack			; signal that status byte is here

.1:	bsr	w4spreq			; wait for message byte
	bmi.s	w4spend			; if timed-out, returns

	RSCSI	#SPSCSIDB,d0		; get and ignore message byte
	bsr	dospack			; signal that message byte is here
	move.w	stat,d0			; recall the status byte
w4spend:
	rts				; return


;+
; w4spdreq() - wait for REQ to come during hand shake of data bytes
; w4spreq() - wait for REQ to come during hand shake of non-data bytes
;
; Returns:
;	 0 - if successful
;	-1 - times out
;-
w4spdreq:
	TMOUT	rcaltm,d1		; add time for recalibration
	bra.s	w4req0
w4spreq:
	TMOUT	scstmout,d1		; set up timeout counter
w4req0:	move.w	#SPSCSICR,WDL
.1:	move.w	WDC,d0			; read Control Register
	btst	#5,d0			; waiting for REQ to come
	bne.s	.2			; if REQ comes, done
	CNTDN1	d1,.1			; if there is more time, keep waiting
	moveq	#-1,d0			; else, returns timed out
	bra.s	w4sprend
.2:	moveq	#0,d0			; returns successful
w4sprend:
	rts


;+
; dospack() - assert ACK
;
; Returns:
;	 0 - always successful
;-
dospack:	
	RSCSI	#SPSCSIICR,d0		; read Initiator Command Register
	ori.b	#$11,d0			; assert ACK (and data bus)
	move.w	d0,WDC
	andi.b	#$ef,d0			; clear ACK
	move.w	d0,WDC
	moveq	#0,d0
	rts


.if	!DRIVER				; not to be included in driver

;+
; fmtspscsi() - format a SCSI unit
;
;	d0.w = physical unit number
;	d1.l = transfer length (in bytes)
;	d2.w = command length (NCMD or LCMD)
;	a0.l = buffer address
;-
	.globl	_fmtspscsi
_fmtspscsi:
	st	flock			; lock FIFO
	andi.w	#7,d0			; mask off the flags to get unit num
	bsr	sblkspscsi		; send command block
	bpl.s	.0			; if successful, wait for status byte
	rts				; else return
.0:	WSCSI	#3,#SPSCSITCR		; status in phase
	RSCSI	#SPSCSIREI,d0		; clear potential interrupt
.1:	RSCSI	#SPSCSICR,d0		; read Control Register
	btst	#5,d0			; wait forever for REQ to come


.if	DEBUG

	bne.s	.3			; For debugging purpose - 
	move.l	#_hz_200,a0		;    add a delay in this "tight" 
	moveq	#2,d1			;    loop to slow down SCSI chip 
	add.l	(a0),d1			;    accessing.
.2:	cmp.l	(a0),d1
	bhi.s	.2
	bra.s	.1

.else	;DEBUG

	beq.s	.1

.endif	;DEBUG

.3:	bsr	gspstat			; when REQ comes, get status byte
	RSCSI	#SPSCSIREI,d1		; clear potential interrupt
	bra	_hdone			; go clean up

.endif	;!DRIVER



