;
;	File:		ADBDriver.aii
;
;	Contains:	IOP code for IOP Based ADB driver
;
;	Written by:	Gary G. Davidian 10-Jul-88
;
;	Copyright:	© 1988-1990 by Apple Computer, Inc., all rights reserved.
;
;	Change History (most recent first):
;
;		 <2>	  1/2/90	GGD		NEEDED FOR ZONE-5: General source cleanup, no code changes.
;	   <1.5>	11/19/89	GGD		NEEDED FOR ZONE-5 Added conditional assembly option
;									'ForceWorstCase' to cause ADB to use more processor time, to
;									stress test the worst case paths of the SWIM Driver. Added
;									facility to control auto-polling on a device address basis so
;									that the ADB Manager can select to only poll addresses that have
;									device table entries.
;	   <1.4>	 11/2/89	GGD		NEEDED FOR ZONE-5 Changed the ADB Polling rate from 13.5ms to
;									11.0ms to be closer to a floppy disk sector time, and to be
;									consistent with the Mac II ADB Micro poll rate.
;	   <1.3>	 9/21/89	GGD		Added ADBActivity indicator so that the disk driver can give ADB
;									more priority if there was recent activity. Corrected timing
;									calculation in Attn pulse (off by 5µs).
;	   <1.2>	  7/8/89	CCH		No changes. Testing in-file comments for ".aii" suffix files.
;	   <1.1>	 6/15/89	GGD		Updated to use equates for the latest rev of the IOP chip,
;									re-formated tab stops in source.
;	   <1.0>	 11/9/88	CCH		Adding to EASE.
;				 7/10/88	GGD		Created today.
;
;	To Do:
;
				eject
				title	'IOP ADB Driver'
; File: ADBDriver.aii  -	IOP code for IOP Based ADB driver.
;
;_______________________________________________________________________
;
; Copyright © Apple Computer, Inc. 1988-1989
;
;	  written by Gary G. Davidian 10-Jul-88
;
;  This module implements the IOP side of the IOP based Apple Desktop Bus
;  Driver.	It performs the low level ADB transactions as well as automatic
;  polling of the last device, and automatic service request polling (in
;  Most Recently Used order).  The Host CPU will only be interrupted, with
;  a single interrupt per transaction, when there is some device input to
;  be processed.
;
;  This driver co-exists on the IOP that also has the SWIM Driver, and the
;  SWIM chip is used to drive and receive the ADB bus.
;
; NOTE: This code was optimized for space, and also contains many cycle
;		counted timing dependent routines.	This may detract from its
;		readability at times, but keep in mind that this code replaces a
;		considerable amount of hardware and saves $$$ (profit sharing).
;
; MODIFICATION HISTORY:
;
;  <10Jul88>	GGD Created today.
;
;_______________________________________________________________________

				machine m65C02
				codechk off
				datachk off
				longa	off
				longi	off
				print	nomdir,nogen
				print	off
				include 'IOPDefs.aii'
				include 'SWIMDefs.aii'
				print	on

hasAltADB		equ		1					; include alternate SWIM based ADB also
ForceWorstCase	equ		0					; don't simulate worst case data length		<1.5>

;		Polling rate definitions

;  These two time constants are used to control auto polling,  and service request
;  polling.  Explicit commands are serviced immediatly, which may mean that if they
;  are issued at a very high frequency, they may utilize more than 50% of the bus
;  bandwith, and may prevent the processors in the devices from having time to
;  service their device  (this is the way that it is done on other machines).
;  During normal AutoPolling, this situation will not occur.  But it can occur during
;  Service Request polling.
;
;  AutoPollRate is the MINIMUM time from the START of a command to the START of
;  the next command when auto polling.
;
;  SRQPollDelay is the MINIMUM time from the END of a command to the START of
;  the next command.  This is used when polling for a device that issued a service
;  request.

AutoPollRate	equ 	(11000*TMPB1second)/1000000 	; 11.000 MS auto poll interval		<1.4>
SRQPollDelay	equ 	(00500*TMPB1second)/1000000 	;  0.500 MS delay before SRQ polls


;		ADB transaction related definitions

NumADBAddrs 	equ 	16					; 16 addresses, 0..15

ADBCmdMask		equ 	$0F 				; mask for command/reg portion of ADB command
ADBAddrMask 	equ 	$F0 				; mask for address portion of ADB command

ResetCmd		equ 	$00 				; ADB Bus Reset command
TalkR0Cmd		equ 	$0C 				; ADB command Talk Reg 0

MinDataLen		equ 	2					; Data length can be 0, 2..8 bytes
MaxDataLen		equ 	8					; Data length can be 0, 2..8 bytes


;		Message Format

ADBMsg			record	0,increment 		; format CPU <-> IOP messages
Flags			ds.b	1					; modifier/status flags (see below)
DataCount		ds.b	1					; number of bytes of ADBData (0..8) (cmd not included)
ADBCmd			ds.b	1					; ADB command to send / command that replied
ADBData 		ds.b	MaxDataLen			; ADB Data to be sent with command, or data received
				ds.b	1					; input data buffer (needs 1 extra byte)
				endr

;		ADBMsg.Flags bit definitions

ExplicitCmd 	equ 	%10000000			; message contains an explicit commmand
AutoPollEnable	equ 	%01000000			; auto polling and srq polling enabled
SetPollEnables	equ		%00100000			; Update auto-enable bit mask from ADBData field
ServiceRequest	equ 	%00000100			; service request detected
TimedOut		equ 	%00000010			; addressed device did not return data


;		Low level message passing state and data buffer assignments

ADBXmtMsgState	equ 	XMTMsg3State
ADBRcvMsgState	equ 	RCVMsg3State

ADBXmtMsgData	equ 	XMTMsg3Data
ADBRcvMsgData	equ 	RCVMsg3Data


;		Page zero variables

				seg 	'zdata'
ADBVarsZ		record
				export	ADBActivity			; export activity status for disk driver
ADBActivity		ds.b	1					; status of last 8 transactions
ADBFlags		ds.b	1					; status information
ByteCount		ds.b	1					; number of bytes to send / bytes received
PollingTMPB 	ds.b	1					; index of Polling Timer Param Block
MRUAddr 		ds.b	1					; ADB address most recently received from
LRUAddr 		ds.b	1					; ADB address least recently received from
AutoPollAddr	ds.b	1					; ADB address to auto poll
MsgPtr			ds.w	1					; pointer to xmt/rcv message buffer
Temp			ds.b	1					; general purpose temporary
SRQcount		ds.b	1					; number of devices searched during SRQ poll
				endr


;		Other Driver variables

				seg 	'data'
ADBVars 		record
XmtCmdAndData	ds.b	1					; command byte (must precede SendBuffer)
				ds.b	MaxDataLen			; output data buffer
MRUList 		ds.b	NumADBAddrs 		; list of ADB addrs in MRU order
DisableFlags	ds.b	NumADBAddrs 		; if <> 0 then don't auto-poll this address
				endr
ReceiveBuffer	equ 	ADBXmtMsgData+ADBMsg.ADBData	 ; rcv directly into msg buffer



				seg 	'code'
ADBDrvr 		proc	export

				export	HandleRCVMsg3		; ADB driver requests use MSG 3
				export	HandleXMTMsg3		; ADB driver requests use MSG 3
				export	InitADBDrvr 		; ADB driver initialization
				import	GetTMPB
				import	CancelTMPBTimer
				import	StartTMPBTimer
				import	WaitTMPBDone
				import	CheckTMPBdone
				import	TMPBtimeH
				import	TMPBtimeL

				with	ADBVarsZ,ADBVars	; globals used throughout driver
				title	'IOP ADB Driver - Initialization'

;_______________________________________________________________________
;
;  Routine: 	jsr 	InitADBDrvr
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, z, c
;  Calls:		GetTMPB
;  Called by:	SWIMIOP driver initialization
;
;  Function:	Initializes the IOP based ADB Driver variables.
;
;_______________________________________________________________________

InitADBDrvr
			if hasAltADB then
				jsr		CheckForAltADB		; patch in SWIM based ADB if present
			endif
				jsr 	GetTMPB 			; get a timer parameter block pointer
				sty 	<PollingTMPB		; save the TMPB pointer

*				stz 	<ADBFlags			; initialize the status flags

				assert	(ADBXmtMsgData**$FF)=(ADBRcvMsgData**$FF)
				lda 	#<(ADBXmtMsgData\
						+ADBMsg.ADBCmd)		; get low byte of msg pointer
				sta 	<MsgPtr 			; init low byte of message pointer

;		Initialize the MRU information

*				stz 	<MRUAddr			; first entry is default MRU
*				stz 	<AutoPollAddr		; first entry is default Poll device
				ldx 	#NumADBAddrs-1		; index/counter
				stx 	<LRUAddr			; last entry is default LRU
@MRUInitLoop	txa 						; prepare to store	count
				sta 	MRUList-1,x 		; MRUList[x] := x+1
				dex 						; point to prev list
				bne 	@MRUInitLoop		; init the entire list
*				stz 	MRUList\
						+NumADBAddrs-1		; last entry points to first
				rts
				title	'IOP ADB Driver - Host Message Request Handling'

;_______________________________________________________________________
;
;  Routine: 	jsr 	HandleRCVMsg3
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		CopyMsg, ADBAction (branched to)
;  Called by:	IOPKernel.HandleHSTInt
;
;  Function:	Handles receiving new messages from the host, by copying
;				the request information, and notifying the host that the
;				message request has been accepted.	ADBAction is then
;				called to actually process the command.
;
;_______________________________________________________________________

HandleRCVMsg3	ldx 	#>ADBRcvMsgData		; get high byte of message pointer
				ldy 	ADBRcvMsgData\
						+ADBMsg.DataCount	; get the xmt byte count
				lda 	ADBRcvMsgData\
						+ADBMsg.Flags		; get the new flags
				jsr 	CopyMsg 			; copy the message
				lda 	#MessageCompleted	; message completed state
				sta 	ADBRcvMsgState		; indicate that it is done
				lda 	#MsgCompletedINT	; get the interrupt bit
				sta 	HostControl 		; interrupt the main CPU
				bra 	ADBAction			; go run a command



;_______________________________________________________________________
;
;  Routine: 	jsr 	HandleXMTMsg3
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		CopyMsg, ADBAction (branched to)
;  Called by:	IOPKernel.HandleHSTInt
;
;  Function:	Handles receiving messages replies from the host, by copying
;				the request information, and updating the message state to
;				indicate that the message reply has been accepted.
;				ADBAction is then called to actually process the command.
;
;_______________________________________________________________________

HandleXMTMsg3	ldx 	#>ADBXmtMsgData		; get high byte of message pointer
				ldy 	ADBXmtMsgData\
						+ADBMsg.DataCount	; get the xmt byte count
				lda 	ADBXmtMsgData\
						+ADBMsg.Flags		; get the new flags
				jsr 	CopyMsg 			; copy the message
				assert	Idle=0
*				lda 	#Idle				; Indicate that response was received
				stz 	ADBXmtMsgState		; set the new message state
				bra 	ADBAction			; go run a command
				title	'IOP ADB Driver - Polling Request Handling'

;_______________________________________________________________________
;
;  Routine: 	jmp 	StartSRQPoll
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, n, z, c
;  Calls:		CancelPollTimer, StartPolling (branched to)
;  Called by:	ADBAction
;
;  Function:	Handles service request polling, by setting up a Talk R0
;				command for the next device in MRU (Most Recently Used) order.
;				It will delay for a short time before issuing the request
;				so that devices on the ADB bus, and the IOP itself will not be
;				saturated by this polling.
;
;_______________________________________________________________________

StartSRQPoll	ldx 	<AutoPollAddr		; get address of last device polled
@disabled		lda 	MRUList,x			; get the next address to poll for service request
				tax							; save the poll address
				inc		<SRQcount			; update the SRQ poll count
				lda		<SRQcount			; get the updated count
				cpa		#NumADBAddrs		; see if one pass through table completed
				bge		@updatePollAddr		; if all enabled addrs searched, search disabled ones
				bit		DisableFlags,x		; see if this address is enabled
				bmi		@disabled			; if disabled, skip it the first time
@updatePollAddr	stx 	<AutoPollAddr		; change poll device to next most recently used
				jsr 	CancelPollTimer 	; cancel the autopoll timer
				lda 	#<SRQPollDelay		; get low byte of poll delay
* (fall into)	bra 	StartPolling		; poll the next device after a short delay


;_______________________________________________________________________
;
;  Routine: 	jsr 	StartPolling
;  Inputs:		A - low byte of poll timer delay (high byte assumed zero)
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		ADBAction (falls into)
;  Called by:	ADBAction, StartSRQPoll (branched to)
;
;  Function:	Starts running the polling timer, and returns.
;
;				When the timer goes off, ADBAction will be called.
;
;_______________________________________________________________________

StartPolling	ldy 	<PollingTMPB		; get the TMPB index
				sta 	TMPBtimeL,y 		; store low into the TMPB
				assert	(SRQPollDelay>>8)=(AutoPollRate>>8)
				lda 	#>AutoPollRate		; get high byte of poll rate
				sta 	TMPBtimeH,y 		; store high into the TMPB
				jsr 	StartTMPBTimer		; start the timer
				ldy 	<PollingTMPB		; get the TMPB index
				jsr 	WaitTMPBdone		; wait for it to time out, return to caller

;				Timer interrupt resumes us here
* (fall into)	bra 	ADBAction			; when timer goes off, perform some ADB Action
				title	'IOP ADB Driver - Perform ADB Action'

;_______________________________________________________________________
;
;  Routine: 	jmp 	ADBAction
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		StartPolling, CheckTMPBdone, ADBRequest, StartSRQPoll (branched to)
;  Called by:	HandleRCVMsg3, HandleXMTMsg3, StartPolling (falls into)
;
;  Function:	Handles ADB request processing, by deciding what if anything
;				should be done.  While the IOP to Host transmit message is in
;				use, no ADB action will be performed, since there is no way
;				to notify the Host that it has completed.  If an Explicit
;				request is pending, it will be sent, otherwise, auto/SRQ
;				polling will be done if enabled.
;
;_______________________________________________________________________

ADBAction		lda 	ADBXmtMsgState		; get the xmt message state
				assert	Idle=0
				bne 	@Done				; if xmt msg in use, wait until its free

				ldy 	<PollingTMPB		; get the TMPB index
				jsr 	CheckTMPBdone		; see if auto poll timer timed out
				bne 	@Done				; if not, wait until auto poll interval
				lda 	#<AutoPollRate		; get low byte of auto poll rate
				jsr 	StartPolling		; setup timer to wake us up later

;		Figure out what to do.

				assert	ExplicitCmd=$80 	; n bit
				assert	AutoPollEnable=$40	; v bit
				bit 	<ADBFlags			; test explicit and auto-poll bits
				bmi 	@SendIt 			; if explicit command, go send it
				bvc 	@Done				; if implicit, and no auto polling, do nothing

;		If implicit, and polling enabled, create the auto-poll command

				lda 	<AutoPollAddr		; get the device address to auto poll
				asl 	a					; shift left 4 bits to position device address
				asl 	a
				asl 	a
				asl 	a
				ora 	#TalkR0Cmd			; make it a talk R0 command
				sta 	XmtCmdAndData		; update the command
				stz 	<ByteCount			; no data to send, now send the command

@SendIt 		jsr 	ADBRequest			; perform the ADB transaction

;		Check the result of the transaction

				lsr		<ADBActivity		; shift in zero, assume no activity

				lda 	<ADBFlags			; get flags
				sta 	ADBXmtMsgData\
						+ADBMsg.Flags		; return the new flags
				and 	#ExplicitCmd\
						+AutoPollEnable\
						+TimedOut			; isolate just the flags that we want to check

				cpa 	#AutoPollEnable\
						+TimedOut			; test for implicit, auto, no reply
				beq 	@CheckSRQ			; if so, don't notify host, keep on polling

;		If explicit command, or reply received, Send ADB transaction result back to host

				lda		#$80				; mask to set activity indication
				tsb		<ADBActivity		; indicate explicit or reply received activity

				lda 	<ByteCount			; get the number of bytes received
				sta 	ADBXmtMsgData\
						+ADBMsg.DataCount	; get the xmt byte count
				assert	(Idle+1)=NewMessageSent
				inc 	ADBXmtMsgState		; change message state Idle -> NewMessageSent
				lda 	#NewMsgSentINT		; prepare to interrupt the host
				sta 	HostControl 		; interrupt the host

				lda 	#ExplicitCmd		; prepare to clear explicit bit
				trb 	<ADBFlags			; indicate no longer explicit
				bne 	@Done				; if was explicit, don't check SRQ
				stz		<SRQcount			; if reply received, reset the SRQ count

;		Check for service request, and if so, start SRQ polling


@CheckSRQ		lda 	#ServiceRequest		; prepare to test for service request
				bit 	<ADBFlags			; see if service request was set
				bne 	StartSRQPoll		; if so, start/continue SRQ polling, and return
@Done			rts 						; otherwise, request is complete
				title	'IOP ADB Driver - Utility Routines'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CopyMsg
;  Inputs:		A - new flags from ADBMsg.Flags
;				X - page number of message buffer
;				Y - data count from ADBMsg.DataCount
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		CancelPollTimer (falls into)
;  Called by:	HandleRCVMsg3, HandleXMTMsg3
;
;  Function:	If an explicit command is not already accepted, it will
;				copy the flags, byte count, command and data, from the
;				message whose page number was passed in,  to prepare for
;				starting an ADB transaction.  If the new command is an
;				explicit command, the poll timer is canceled, so that
;				the command can run immediatly.
;
;_______________________________________________________________________

CopyMsg 		bit 	<ADBFlags			; see if already have an explicit cmd
				assert	ExplicitCmd=$80		; n bit
				bmi 	@CopyDone			; if explicit pending, don't overwrite it
				stx 	<MsgPtr+1			; setup high byte of message pointer
				sta 	<ADBFlags			; set new flags
				stz 	<ByteCount			; send count is zero when implicit
				and		#ExplicitCmd\
						+SetPollEnables		; check for explicit / SetPollEnables
				assert	ExplicitCmd=$80		; n bit
				bmi 	@Explicit			; if explicit then copy cmd and data
				beq		@CopyDone			; if not SetPollEnables, just return

;	Process a SetPollEnables request (ignored if ExplicitCmd is also set)
				trb 	<ADBFlags			; reset the SetPollEnables flag
				ldy		#2					; 2 bytes of data (16 bit mask)
				lda		(MsgPtr),y			; get low byte of mask
				sta		<Temp				; save it
				dey							; point to high byte
				lda		(MsgPtr),y			; get high byte of mask
				ldx		#NumADBAddrs-1		; loop counter, index into DisableFlags
@EnablesLoop	rol		<Temp				; shift the low byte
				rol		a					; shift the high byte (highest bit into carry)
				stz		DisableFlags,x		; assume polling enabled
				bcs		@NextEnable			; if enable bit set, DisableFlags := $00
				dec		DisableFlags,x		; if enable bit clear, DisableFlags := $FF
@NextEnable		dex							; update loop counter and index
				bpl		@EnablesLoop		; loop through all device addresses

@CopyDone		rts 						; all done

@Explicit		cpy 	#MaxDataLen+1		; see if count is reasonable
				blt 	@CopyStart			; if in range start copying
				ldy 	#MaxDataLen 		; otherwise, peg it to the Max
@CopyStart		sty 	<ByteCount			; init the send count
@CopyLoop		lda 	(MsgPtr),y			; get a byte from the message
				sta 	XmtCmdAndData,y 	; copy it into the send buffer
				dey 						; dec index/count
				bpl 	@CopyLoop			; copy the command and data
* (fall into)	bra 	CancelPollTimer 	; cancel the poll timer and return



;_______________________________________________________________________
;
;  Routine: 	jsr 	CancelPollTimer
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		CancelTMPBTimer
;  Called by:	StartSRQPoll, CopyMsg (falls into)
;
;  Function:	Cancels to Auto Polling interval timer.
;
;_______________________________________________________________________

CancelPollTimer ldy 	<PollingTMPB		; get the TMPB index
				jmp 	CancelTMPBTimer		; cancel the timer and return
				title	'IOP ADB Driver - Bus Timing'

;_______________________________________________________________________
;		Apple Desktop Bus timing definitions from ADB spec #062-0267 (Rev E)
;		Clock counts based upon IOP Clock = 15.6672 MHZ / 8 = 1.9584 MHZ
;_______________________________________________________________________

;symbol 		clocks	 true clocks	time	meaning
Tcyc	equ 	 196	;  195.840		100µs	bit cell time
T0		equ 	 127	;  127.296		 65µs	"0" low time
T1		equ 	  69	;	68.544		 35µs	"1" low time
Tattn	equ 	1567	; 1566.720		800µs	attention signal
Tsynch	equ 	 127	;  127.296		 65µs	synch pulse width
Tlt 	equ 	 392	;  391.680		200µs	stop to start time
Tstop	equ 	 137	;  137.088		 70µs	stop pulse width
Tsrq	equ 	 588	;  587.520		300µs	service request
*Tres	equ 	5875	; 5875.200		  3ms	reset signal (3ms min)

;		Additionally we define the following
Tres	equ 	7834	; 7833.600		  4ms	reset signal (40*Tcyc in Rev C)
*TsrqMin equ	Tsrq-Tsrq*30/100 ;		210µs	Minimum SRQ width is Tsrq - 30%
TsrqMin equ 	Tsrq-Tsrq*40/100 ;		180µs	Minimum SRQ width is Tsrq - 40% (mouse violates spec)
TsrqMax equ 	Tsrq*130/100	 ;		390µs	Maximum SRQ width is Tsrq + 30%
TltMax	equ 	Tlt*130/100 	 ;		260µs	Maximum stop to start time is Tlt + 30%
T0Max	equ 	Tcyc*130/100*65/100*105/100 ;	88.725µs Max time between data bit transitions
Tlow	equ 	T1		;	68.544		 35µs	initial low phase of bit cell
Tdata	equ 	T0-T1	;	58.752		 30µs	middle low or high phase of bit cell
Thigh	equ 	Tcyc-T0 ;	68.544		 35µs	final high phase of bit cell

		assert	Tcyc=(T0+T1)
		assert	Tcyc=(Tlow+Tdata+Thigh)

;		The ADB bus is normally high, a transaction is sent as follows.

;		Attention signal starts the transaction
;High			Bus is high when not in use
;Low	Tattn	Attention signal
;High	Tsynch	synch pulse

;		The 8 bits of the command are sent, MSB first, LSB last.
;Low	Tlow	low phase of data bit
;L/H	Tdata	low if zero, high if one.  Data phase of bit
;High	Thigh	high phase of data bit

;		Stop bit follows command
;Low	Tstop	command stop bit

;		Service requests from other devices are indicated here by pulling bus low.
;High	Tlt 	stop bit to start bit

;		data if any is as follows (otherwise bus is left high)

;Low	Tlow	beginning of start bit for data
;High	Tdata	data phase of start bit
;High	Thigh	high phase of start bit

;		2..8 bytes are sent, MSB first, LSB last.
;Low	Tlow	low phase of data bit
;L/H	Tdata	low if zero, high if one.  Data phase of bit
;High	Thigh	high phase of data bit

;		Stop bit follows command
;Low	Tstop	command stop bit
;High			bus left high at end of transaction
				title	'IOP ADB Driver - Bus Manipulation'

;_______________________________________________________________________
;  Macros and constants used to read and write the ADB Bus.  By using
;  these definitions, this code can be easily modified if the polarity,
;  bit position, or register addresss ever change.	This would allow easy
;  migration to an IOP that had direct access to the ADB bus, instead of
;  accessing it through the SWIM chip.
;_______________________________________________________________________

;		ADB output bit

			if hasAltADB then
rAltADBoutReg	equ 	rSetup				; SWIM register for ADB output state
wAltADBoutReg	equ 	wSetup				; SWIM register for ADB output state
AltADBoutMask	equ 	Select35			; bit mask for output bit
			endif
rADBoutReg		equ		TimerDPLLctl		; SWIM register for ADB output state
wADBoutReg		equ		TimerDPLLctl		; SWIM register for ADB output state
ADBoutMask		equ		GPOUT0				; bit mask for output bit

;		macros for modifing state of ADBoutReg when it is the Accumulator

				macro
				ADBoutLow					; change state to cause bus to go Low
				ora		#ADBoutMask			; output bit is active low
				endm

				macro
				ADBoutHigh					; change state to cause bus to go High
				and		#≈ADBoutMask		; output bit is active low
				endm

;		ADB input bit

			if hasAltADB then
AltADBinReg		equ 	rHandshake			; SWIM register for ADB input state
AltADBinMask	equ 	DriveSense			; bit mask for input bit
			endif
ADBinReg		equ		TimerDPLLctl		; SWIM register for ADB input state
ADBinMask		equ		GPIN0				; bit mask for input bit

;		macros for branching on the state of ADBinReg after a BIT instruction

				macro
				brADBlo &Target 			; branch if bus is low
				beq 	&Target 			; input bit is active high
				endm

				macro
				brADBhi &Target 			; branch if bus is high
				bne 	&Target 			; input bit is active high
				endm
				title	'IOP ADB Driver - Set Bus / Set Bus After'

;_______________________________________________________________________
;
;  Routine: 	jsr 	SetBus
;  Inputs:		c - Value to set the ADB bus to.
;  Outputs: 	z - setup for brADBlo/brADBhi testing
;  Destroys:	A, n, v, z
;  Calls:		none
;  Called by:	ADBRequest
;
;  Function:	Sets the ADB bus output to the state specified by the
;				carry bit.	Carry reset means bus low, Carry set means
;				bus high.  After setting the bus, the bus input is read,
;				and tested, ready for branching with brADBlo/brADBhi.
;				This routine returns to the caller 12 clocks after the
;				bus is set.
;
;				The Macro SetBusAfter, is provided to allow an exact number
;				of clocks to be counted before setting the bus.
;				The acceptable clock counts are 27, 29..1301, 1303.
;
;_______________________________________________________________________


				macro
				SetBusAfter 	&Count		; set bus to carry after "count" clocks
				CallWaiting 	&Count-22,SetBusPlus
				endm

				macro
				CallWaiting 	&Count,&Routine
				lcla	&q,&r
&q				seta	&eval(&Count)/5
&r				seta	&eval(&Count)//5
				if		&r=1 then
&q				seta	&q-1
&r				seta	&r+5
				endif
				if		&q<1 then
				aerror	'Wait value too small'
				elseif	&q>255 then
				aerror	'Wait value too large'
				else
				lda 	#&q
				jsr 	&Routine&r
				endif
				endm

			if hasAltADB then
PatchBase
			endif
SetBusPlus6 	nop 						; add 6 clocks before loop
SetBusPlus4 	nop 						; add 4 clocks before loop
SetBusPlus2 	nop 						; add 2 clocks before loop
*				lda 	#1					; ( 2)
*				jsr 	SetBusHighPlus0		; ( 8)
SetBusPlus0 	dea 						; (10) decrement loop counter
SetBusPlus3 	bne 	SetBusPlus0 		; (12) loop until A=0

SetBus			lda 	rADBoutReg			; (16) read output register
				bcs 	@SetHigh			; (18) (19)
				ADBoutLow					; (20) modify to set bus low
				bra 	@SetBusOutput		; (23)

@SetHigh		ADBoutHigh					; (21) modify to set bus high
				nop 						; (23)

@SetBusOutput	sta 	wADBoutReg			; (27) set the bus high or low
				lda 	#ADBinMask			; ( 2) prepare check bus fighting
				bit 	ADBinReg			; ( 6) test the bus
				rts 						; (12) all done
			if hasAltADB then

				gbla	&FixrADBoutReg1,&FixOutLowOp1,&FixOutLowData1,&FixOutHighOp1
				gbla	&FixOutHighData1,&FixwADBoutReg1,&FixADBinMask1,&FixADBinReg1
&FixrADBoutReg1	seta	SetBus+1-PatchBase
&FixOutLowOp1	seta	SetBus+5-PatchBase
&FixOutLowData1	seta	SetBus+6-PatchBase
&FixOutHighOp1	seta	@SetHigh+0-PatchBase
&FixOutHighData1 seta	@SetHigh+1-PatchBase
&FixwADBoutReg1	seta	@SetBusOutput+1-PatchBase
&FixADBinMask1	seta	@SetBusOutput+4-PatchBase
&FixADBinReg1	seta	@SetBusOutput+6-PatchBase
			endif
				title	'IOP ADB Driver - ADB Request Transaction'

;_______________________________________________________________________
;
;  Routine: 	jsr 	ADBrequest
;  Inputs:		XmtCmdAndData - Command byte, followed by data, if any
;				ByteCount - number of data bytes to send (0..8)
;  Outputs: 	ReceiveBuffer - data received from device
;				ByteCount - number of data bytes received (0..8)
;				ADBFlags.ServiceRequest - set if SRQ detected, else 0
;				ADBFlags.TimedOut - set if no reply data received, else 0
;  Destroys:	A, X, Y, n, v, z. c
;  Calls:		SetBus, SetBusAfter, SendBytes, @ReceiveReply (branched to)
;  Called by:	ADBAction
;
;  Function:	Performs the low level ADB transmission, sending attention,
;				sync, command byte, and stop bit.  Detects and records
;				service requests after command byte's stop bit.  Data is
;				sent if ByteCount is non-zero, preceded by a start bit,
;				and followed by a stop bit.
;
;_______________________________________________________________________

ADBrequest
		if ForceWorstCase then				; simulate worst case data length	<1.5>
				ldy		#10*8				; 10 byte times @ 8 bits per byte
@delay			sec							; leave bus high
				setbusafter	(Tcyc*130/100)	; worst case bit time
				dey							; update bits remaining count
				bne		@delay				; loop through all bits
		endif								;									<1.5>
				clc 						; prepare to set bus low for attention pulse
				jsr 	SetBus				; (  12) attn is low for Tattn
				lda 	<ADBFlags			; (  15) Setup default result flags
				and 	#≈ServiceRequest	; (  17) Assume no service request
				ora 	#TimedOut			; (  19) Assume timeout (no receive data)
				sta 	<ADBFlags			; (  22)

;		initialize the receive buffer while attention is being sent.

				ldx 	#MaxDataLen 		; (  24) buffer index
				lda 	#$01				; (  26) initialize receive buffer to $01 for shift loop
@RcvBufInitLoop sta 	ReceiveBuffer,x 	; (  31) ( 101)
				dex 						; (  33) ( 103)
				bne 	@RcvBufInitLoop 	; (  36) ( 105)
				stz 	ReceiveBuffer		; ( 109) first byte is zero, to allow for start bit

;		check for specially handled reset command

				lda 	XmtCmdAndData		; ( 113) Get the command byte
				sta 	ADBXmtMsgData\
						+ADBMsg.ADBCmd 		; ( 117) return the command
				and 	#ADBCmdMask 		; ( 119) isolate the command
				assert	ResetCmd=$00
*				eor 	#ResetCmd			;		 test for reset command
				beq 	@SendReset			; ( 121) ( 122) if reset command, send it

;		kill some extra time because Tattn is too long for SetBusAfter range.

*				ldx 	#0					;		 X is still zero from above
@AttnLowWait	dex 						; ( 123) (1398)
				bne 	@AttnLowWait		; ( 126) (1400)

;		Send Sync before command byte

				ldy 	#1					; (1402) byte count is 1, index is 0. ADBcmd
				assert	Tsynch=(Tcyc-T1)	; sync is same as high phase of a 1 bit (start bit)
				SetBusAfter Tattn-Tlow-1402	; (Tattn-Tlow) (12) beginning of low phase of start bit
				jsr 	SendBytes			; (Tlow) (18) send start bit, ADBcmd, and Stop bit
				bcc 	@BusFite			; (20) report error if fighting (before stop bit)
				brADBhi @NoSRQ				; (22) (23)  if no fight then no SRQ

;		An SRQ is defined as TsrqMin .. TsrqMax of low from the beginning of the stop
;		bit, which was Tstop ago. So the min would be TsrqMin - Tstop.
;		And the max would be TsrqMax - Tstop.

				lda 	#ADBinMask			; (24) prepare to read the bus
				ldx 	#(TsrqMin\			; get min srq poll time
						-Tstop\ 			; Tsrq is defined from start of stop bit
						-26\				; which started its high phase 26 clocks ago
						+10)/11 			; (26) Round up, Load min loop counter
@ChkSRQmin		bit 	ADBinReg			; ( 4) test to see if SRQ went away
				brADBhi @NoSRQ				; ( 6) Branch if SRQ too short
				dex 						; ( 8) decrement timeout count
				bne 	@ChkSRQmin			; (11) loop 

;		if the SRQ was low long enough, make sure it goes back high soon enough

				ldx 	#(TsrqMax\			; get remaining max srq poll time
						-(TsrqMin+2)\		; adjust for min srq time already waited
						+10)/11+1			; (TsrqMin+2) Load max loop counter (1 extra due to early dec)
@ChkSRQmax		dex 						; ( 2) decrement timeout count
				beq 	@BusFite			; ( 4) if timed out, SRQ too long
				bit 	ADBinReg			; ( 8) test to see if SRQ is done
				brADBlo @ChkSRQmax			; (11) loop until it goes high or times out

				lda 	#ServiceRequest		; Flag valid SRQ detected
				tsb 	<ADBFlags			; update status

;		After the stop bit (and possibly SRQ) we wait 200µs (391 clocks) before
;		the start bit that indicates the beginning of the data bytes.

@NoSRQ			ldx 	#1					; (25) initialize buffer index
				ldy 	<ByteCount			; (28) get send byte count
				beq 	@ReceiveReply		; (30) (31) if no data to send, look for a reply

;		Send Start bit before data bytes
				clc 						; (32) prepare to set bus low
				SetBusAfter Tlt-32			; (Tlt) (12) begin start bit before sending data
				jsr 	SendBytes			; send the transmit buffer
* (fall into)	brADBhi @Done				; all done if no bus fight

@BusFite
@DataError
@TimeOut
@Done			stz 	<ByteCount			; indicate no bytes in reply
				rts 						; Done
			if hasAltADB then

				gbla	&FixADBinMask2,&FixADBinReg2,&FixADBinReg3
&FixADBinMask2	seta	@ChkSRQmin-3-PatchBase
&FixADBinReg2	seta	@ChkSRQmin+1-PatchBase
&FixADBinReg3	seta	@ChkSRQmax+4-PatchBase
			endif


;_______________________________________________________________________
;
;		Reset is a special case command, the command byte is not sent out
;		on the bus, instead, the bus is held low for Tres.
;
;_______________________________________________________________________

@ResetLoopCount equ 	(Tres-(132-15)+1299)/1300
@ResetWait		equ 	(Tres-(132-15))/@ResetLoopCount-(4+15)

@SendReset
*				ldx 	#0					; X is still zero from above
@ResetLoop		inx 						; ( 2) increment loop count
				cpx 	#@ResetLoopCount	; ( 4) carry := 1 if limit reached, otherwise 0.
				SetBusAfter @ResetWait		; (12) either leave the bus zero, or set it high
				bcc 	@ResetLoop			; (15) loop until last time
				rts 						; all done
				title	'IOP ADB Driver - Receive Reply'

;_______________________________________________________________________
;
;  Routine: 	jsr 	@ReceiveReply
;  Inputs:		ByteCount - initialized to zero
;				ReceiveBuffer[0] - initialized to zero
;				ReceiveBuffer[1..8] - initialized to $01
;  Outputs: 	ReceiveBuffer - data received from device
;				ByteCount - number of data bytes received (0..8)
;				ADBFlags.TimedOut - set if no reply data received, else 0
;  Destroys:	A, X, Y, n, v, z. c
;  Calls:		@UpdateMRU (falls into)
;  Called by:	ADBRequest
;
;  Function:	Performs the low level ADB data reception, sending attention,
;				sync, command byte, and stop bit.  Detects and records
;				service requests after command byte's stop bit.
;
;_______________________________________________________________________

@ReceiveReply	lda 	#ADBinMask			; ( 33) get the bit mask for testing the bus
;		start bit should happen by TltMax after stop bit
				ldx 	#-((T0Max\			; ( 35) setup low counter/timeout
						-0\ 				;		no extra time to reomve
						+10)/11+1)			;		Round up, adjust for early inc
				ldy 	#(TltMax\			;		setup start bit timeout
						-(37-7)\			;		Adjust for time before/after
						+10)/11 			; ( 37) round up
@StartSearch	bit 	ADBinReg			; ( 4) sample the bus
				brADBlo @LowLoop			; ( 6) loop until it goes low
				dey 						; ( 8) check for timeout
				bne 	@StartSearch		; (11) loop until start bit or timeout
				rts 						;	   no reply data if start bit timeout


@GetNextBit 	beq 	@DataError			; (20) if Shift Data is zero, start bit was zero
				ldx 	#-((T0Max\			; (22) setup low counter/timeout
						-22\				;		adjust for cycles already consumed
						+10)/11+1)			;		Round up, adjust for early inc

;		count number of samples when bus is low

@LowLoop		inx 						; ( 2) update low count/timeout
				beq 	@TimeOut			; ( 4) if low too long
				bit 	ADBinReg			; ( 8) sample the bus
				brADBlo @LowLoop			; (11) loop until it goes high

;		count number of samples when bus is high

				ldy 	#-((T0Max\			; ( 2) setup high counter/timeout
						-2\ 				;		adjust for cycles already consumed
						+10)/11+1)			;		Round up, adjust for early inc
@HighLoop		iny 						; ( 2) update high count/timeout
				beq 	@CheckEnd			; ( 4) if high too long (maybe end of data)
				bit 	ADBinReg			; ( 8) sample the bus
				brADBhi @HighLoop			; (11) loop until it goes low

;		data bit is based on the higher counter

				stx 	<Temp				; ( 3) setup for compare
				cpy 	<Temp				; ( 6) carry := 1 if high >= low
				ldx 	<ByteCount			; ( 9) get count/buffer index
				rol 	ReceiveBuffer,x		; (15) shift the bit in
				bcc 	@GetNextBit 		; (17) (18) loop until entire byte is received

				cpx 	#MaxDataLen 		; (19) see if end of buffer reached
				bge 	@DataError			; (21) report error if too many bytes received
				inc 	<ByteCount			; (26) update count/buffer index
				ldx 	#-((T0Max\			; (28) setup timeout counter
						-31\				;		adjust for cycles already consumed
						+10)/11+1)			;		Round up, adjust for early inc
				bra 	@LowLoop			; (31) get next bit

@CheckEnd		ldx 	<ByteCount			; get count/buffer index
				dec 	ReceiveBuffer,x 	; test for Shift Data = $01 (no bits shifted in)
				bne 	@TimeOut			; if partial byte received, then this is a timeout
				cpx 	#MinDataLen 		; see if the 2 byte minimum was reached
				blt 	@DataError			; if not, then the report the error

				lda 	#TimedOut			; indicate that there is a valid reply
				trb 	<ADBFlags			; update the flags
* (fall into)	bra 	@UpdateMRU			; mark the device as most recent
			if hasAltADB then

				gbla	&FixADBinMask3,&FixADBinReg4,&FixADBinReg5,&FixADBinReg6
&FixADBinMask3	seta	@ReceiveReply+1-PatchBase
&FixADBinReg4	seta	@StartSearch+1-PatchBase
&FixADBinReg5	seta	@LowLoop+4-PatchBase
&FixADBinReg6	seta	@HighLoop+4-PatchBase
			endif



;_______________________________________________________________________
;
;  Routine: 	jsr 	@UpdateMRU
;  Inputs:		XmtCmdAndData - command whose device is to be made MRU
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z. c
;  Calls:		none
;  Called by:	@ReceiveReply (falls into)
;
;  Function:	Updates the MRU chain by making the specified device the
;				most recently used, by removing it from the chain, and
;				inserting it before the prior MRU entry, and marking it
;				as the head of the MRU chain.
;
;				When a Service Request is pending, don't update the MRU,
;				because it is about to change again, and we don't want
;				the MRU list to change during SRQ polling because if more
;				that two devices were sending constant data, only the first
;				two would get serviced.
;
;_______________________________________________________________________


@UpdateMRU		lda 	#ServiceRequest		; prepare to test service request
				bit 	<ADBFlags			; see if it is set
				bne 	@MRUok				; don't update MRU if SRQ pending

				lda 	XmtCmdAndData		; get the last command sent
				lsr 	a					; shift right 4 bits to get device address
				lsr 	a
				lsr 	a
				lsr 	a
				cpa 	<MRUAddr			; see if already MRU
				beq 	@MRUok				; if so, no need to change anything

				tax 						; load last address into index reg
				bit		DisableFlags,x		; see if polling is disabled for this address
				bmi		@MRUok				; if disabled, don't update the AutoPollAddr

				ldy 	#-1 				; loop counter/index
@MRUloop		iny 						; inc counter/index
				cpa 	MRUList,y			; look for entry that points to new MRU
				bne 	@MRUloop			; loop until it is found

				cpa 	<LRUAddr			; see if LRU is becoming MRU
				bne 	@DoUpdate			; if not, do normal stuff
				sty 	<LRUAddr			; new LRU is pointer to old LRU
				bra 	@MRUdone			; all done

@DoUpdate		lda 	MRUList,x			; get successor of new MRU
				sta 	MRUList,y			; remove new MRU entry from chain
				ldy 	<LRUAddr			; get pointer to LRU entry
				txa 						;
				sta 	MRUList,y			; LRU entry points to new MRU entry
				lda 	<MRUAddr			; get pointer to old MRU entry
				sta 	MRUList,x			; new MRU entry points to old MRU entry
@MRUdone		stx 	<MRUAddr			; mark new entry as MRU
				stx 	<AutoPollAddr		; mark new entry as auto poll device

@MRUok			rts 						; return, with valid reply
				title	'IOP ADB Driver - Send Bytes'

;_______________________________________________________________________
;
;  Routine: 	jsr 	SendBytes
;  Inputs:		X - index to first byte to send from XmtCmdAndData
;				Y - number of bytes to send
;  Outputs: 	c - 0 means send aborted due to bus fighting
;					1 means send was successfull
;				z - setup for brADBlo/brADBhi testing
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SetBusAfter, brADBlo
;  Called by:	ADBRequest
;
;  Function:	Sends a start bit, the requested number of bytes, and a
;				stop bit.  The send will be aborted, and indicated in
;				carry bit, if a bus fight occurs on any bit other than the
;				stop bit.  The fight status of the stop bit is returned in
;				the z bit.	It is assumed the SetBusAfter was called to
;				set the bus low to begin the start bit right before calling
;				SendBytes.
;
;				This routine returns to the caller 18 clocks after setting
;				the stop bit high.
;
;_______________________________________________________________________

SendBytes

;		Send Start bit before data bytes

				stz 	<Temp				; (21) setup shift data for 1 bit (start bit)
				sec 						; (23) start bit is a one

;		Send the data bytes

@NextBit		SetBusAfter Tlow-23 		; (Tlow) (12) wait for end of low phase
				bra 	@SendLoop			; (15) join common code (waste 3 clocks)

@SendLoop		sec 						; (17)
				SetBusAfter Tdata-17		; (Tdata) (12) set the bus high for Thigh
				clc 						; (14) prepare to set bus low / flag fight error
				brADBlo @Error				; (16) report error if fighting
				SetBusAfter Thigh-16		; (Thigh) (12) set the bus low for Tlow
				bit 	<Temp				; (15) just waste 3 clocks
				asl 	<Temp				; (20) Shift the next bit into carry
				bne 	@NextBit			; (22) (23) loop if not final bit

;		drop 1 clock every byte to adjust for 195.84 vs 196.00
;				missing clock				; (23)
				lda 	XmtCmdAndData,x		; (27) get the next data byte
				sec 						; (29) shift in a one, so non-zero until done
				rol 	a					; (31) Shift first bit into carry, 1 into low bit
				sta 	<Temp				; (34) setup shift data
				inx 						; (36) update the buffer index
				dey 						; (38) update byte count
				bmi 	@SendStop			; (40) (41) send the entire buffer
				SetBusAfter Tlow-40 		; (Tlow) (12) wait for end of low phase
				bra 	@SendLoop			; (15) join common code

;		Send Stop bit after data bytes
@SendStop		sec 						; ( 43)
				SetBusAfter Tstop-43		; (Tstop) (12) stop bit is low for Tstop
@Error			rts 						; (18) then leave bus high
			if hasAltADB then
				title	'IOP ADB Driver - CheckForAltADB'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CheckForAltADB
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, z, c
;  Calls:		none
;  Called by:	InitADBDrvr
;
;  Function:	If running on an old IOP chip, patch in the SWIM support.
;
;_______________________________________________________________________

CheckForAltADB	ldx		#-1					; PatchTable index, pre-decremented
				lda		#ADBoutMask			; get the output bit mask
				tsb		wADBoutReg			; set the output bit
				bit		rADBoutReg			; see if the bit got set
				beq		@patchStart			; if clear, running on an old IOP chip
				rts							; if set, new IOP, code is correct

;	If this is an old IOP chip, we will patch our code to access the SWIM chip
;	registers instead.

@addrLoop		sta		PatchBase,y			; change the code to use the new byte
@addrStart		inx							; advance to next entry in the PatchTable
				ldy		PatchTable,x		; get the code offset to be changed
				bne		@addrLoop			; loop until end of list

@patchStart		inx							; advance to next entry in the PatchTable
				lda		PatchTable,x		; get the new code byte to be used
				bne		@addrStart			; loop until end of list
				rts							; code now patched for old IOP


FixrADBoutReg1	equ		&FixrADBoutReg1
FixwADBoutReg1	equ		&FixwADBoutReg1
FixOutLowOp1	equ		&FixOutLowOp1
FixOutLowData1	equ		&FixOutLowData1
FixOutHighOp1	equ		&FixOutHighOp1
FixOutHighData1	equ		&FixOutHighData1
FixADBinMask1	equ		&FixADBinMask1
FixADBinMask2	equ		&FixADBinMask2
FixADBinMask3	equ		&FixADBinMask3
FixADBinReg1	equ		&FixADBinReg1
FixADBinReg2	equ		&FixADBinReg2
FixADBinReg3	equ		&FixADBinReg3
FixADBinReg4	equ		&FixADBinReg4
FixADBinReg5	equ		&FixADBinReg5
FixADBinReg6	equ		&FixADBinReg6

PatchTable
				dc.b	rAltADBoutReg**$FF	; change reads to the ADBoutReg
				dc.b	FixrADBoutReg1
				dc.b	0

				dc.b	wAltADBoutReg**$FF	; change writes to the ADBoutReg
				dc.b	FixwADBoutReg1
				dc.b	0

				dc.b	$29					; change the 'ora' to an 'and'
				dc.b	FixOutLowOp1
				dc.b	0

				dc.b	≈AltADBoutMask		; active high output data
				dc.b	FixOutLowData1
				dc.b	0

				dc.b	$09					; change the 'and' to an 'ora'
				dc.b	FixOutHighOp1
				dc.b	0

				dc.b	AltADBoutMask		; active high output data
				dc.b	FixOutHighData1
				dc.b	0

				dc.b	AltADBinMask		; change loads of the ADBinMask
				dc.b	FixADBinMask1
				dc.b	FixADBinMask2
				dc.b	FixADBinMask3
				dc.b	0

				dc.b	AltADBinReg**$FF	; change reads to the ADBinReg
				dc.b	FixADBinReg1
				dc.b	FixADBinReg2
				dc.b	FixADBinReg3
				dc.b	FixADBinReg4
				dc.b	FixADBinReg5
				dc.b	FixADBinReg6
				dc.b	0

				dc.b	0					; end of table
			endif
				endwith
				endproc

				end

