##+M#########################################################################
# Adaptec 274x/284x/294x device driver for Linux and FreeBSD.
#
# Copyright (c) 1994 John Aycock
#   The University of Calgary Department of Computer Science.
#
# Modifications/enhancements:
#   Copyright (c) 1994, 1995 Justin Gibbs. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING.  If not, write to
# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
# 
# FreeBSD, Twin, Wide, 2 command per target support, tagged queuing and other 
# optimizations provided by Justin T. Gibbs (gibbs@FreeBSD.org)
##-M#########################################################################

VERSION AIC7XXX_SEQ_VER "$Id: aic7xxx.seq,v 2.0 1995/08/02 05:28:42 deang Exp $"

SCBMASK		= 0x1f

SCSISEQ		= 0x00
ENRSELI		= 0x10
SXFRCTL0	= 0x01
SXFRCTL1	= 0x02
SCSISIGI	= 0x03
SCSISIGO	= 0x03
SCSIRATE	= 0x04
SCSIID		= 0x05
SCSIDATL	= 0x06
STCNT		= 0x08
STCNT+0		= 0x08
STCNT+1		= 0x09
STCNT+2		= 0x0a
CLRSINT0	= 0x0b
SSTAT0		= 0x0b
SELDO		= 0x40
SELDI		= 0x20
CLRSINT1	= 0x0c
SSTAT1		= 0x0c
SIMODE1		= 0x11
SCSIBUSL	= 0x12
SHADDR		= 0x14
SELID		= 0x19
SBLKCTL		= 0x1f
SEQCTL		= 0x60
A		= 0x64				# == ACCUM
SINDEX		= 0x65
DINDEX		= 0x66
ALLZEROS	= 0x6a
NONE		= 0x6a
SINDIR		= 0x6c
DINDIR		= 0x6d
FUNCTION1	= 0x6e
HADDR		= 0x88
HADDR+1		= 0x89
HADDR+2		= 0x8a
HADDR+3		= 0x8b
HCNT		= 0x8c
HCNT+0		= 0x8c
HCNT+1		= 0x8d
HCNT+2		= 0x8e
SCBPTR		= 0x90
INTSTAT		= 0x91
DFCNTRL		= 0x93
DFSTATUS	= 0x94
DFDAT		= 0x99
QINFIFO		= 0x9b
QINCNT		= 0x9c
QOUTFIFO	= 0x9d

SCSICONF_A	= 0x5a
SCSICONF_B	= 0x5b

#  The two reserved bytes at SCBARRAY+1[23] are expected to be set to
#  zero, and the reserved bit in SCBARRAY+0 is used as an internal flag
#  to indicate whether or not to reload scatter-gather parameters after
#  a disconnect.  We also use bits 6 & 7 to indicate whether or not to
#  initiate SDTR or WDTR repectively when starting this command.
#
SCBARRAY+0	= 0xa0

DISCONNECTED	= 0x04
NEEDDMA		= 0x08
SG_LOAD		= 0x10
TAG_ENB		= 0x20
NEEDSDTR	= 0x40
NEEDWDTR	= 0x80

SCBARRAY+1	= 0xa1
SCBARRAY+2	= 0xa2
SCBARRAY+3	= 0xa3
SCBARRAY+4	= 0xa4
SCBARRAY+5	= 0xa5
SCBARRAY+6	= 0xa6
SCBARRAY+7	= 0xa7
SCBARRAY+8	= 0xa8
SCBARRAY+9	= 0xa9
SCBARRAY+10	= 0xaa
SCBARRAY+11	= 0xab
SCBARRAY+12	= 0xac
SCBARRAY+13	= 0xad
SCBARRAY+14	= 0xae
SCBARRAY+15	= 0xaf
SCBARRAY+16	= 0xb0
SCBARRAY+17	= 0xb1
SCBARRAY+18	= 0xb2
SCBARRAY+19	= 0xb3
SCBARRAY+20	= 0xb4
SCBARRAY+21	= 0xb5
SCBARRAY+22	= 0xb6
SCBARRAY+23	= 0xb7
SCBARRAY+24	= 0xb8
SCBARRAY+25	= 0xb9
SCBARRAY+26	= 0xba
SCBARRAY+27	= 0xbb
SCBARRAY+28	= 0xbc
SCBARRAY+29	= 0xbd
SCBARRAY+30	= 0xbe

BAD_PHASE	= 0x01				# unknown scsi bus phase
CMDCMPLT	= 0x02				# Command Complete
SEND_REJECT	= 0x11				# sending a message reject
NO_IDENT	= 0x21				# no IDENTIFY after reconnect
NO_MATCH	= 0x31				# no cmd match for reconnect
MSG_SDTR	= 0x41				# SDTR message recieved
MSG_WDTR	= 0x51				# WDTR message recieved
MSG_REJECT	= 0x61				# Reject message recieved
BAD_STATUS	= 0x71				# Bad status from target
RESIDUAL	= 0x81				# Residual byte count != 0
ABORT_TAG	= 0x91				# Sent an ABORT_TAG message
AWAITING_MSG	= 0xa1				# Kernel requested to specify
						# a message to this target
						# (command was null), so tell
						# it that it can fill the
						# message buffer.


#  The host adapter card (at least the BIOS) uses 20-2f for SCSI
#  device information, 32-33 and 5a-5f as well. As it turns out, the
#  BIOS trashes 20-2f, writing the synchronous negotiation results
#  on top of the BIOS values, so we re-use those for our per-target
#  scratchspace (actually a value that can be copied directly into
#  SCSIRATE).  The kernel driver will enable synchronous negotiation
#  for all targets that have a value other than 0 in the lower four
#  bits of the target scratch space.  This should work irregardless of
#  whether the bios has been installed. NEEDWDTR and NEEDSDTR are the top
#  two bits of the SCB control byte.  The kernel driver will set these
#  when a WDTR or SDTR message should be sent to the target the SCB's 
#  command references.
#
#  REJBYTE contains the first byte of a MESSAGE IN message, so the driver 
#  can report an intelligible error if a message is rejected.
#
#  FLAGS's high bit is true if we are currently handling a reselect;
#  its next-highest bit is true ONLY IF we've seen an IDENTIFY message
#  from the reselecting target.  If we haven't had IDENTIFY, then we have
#  no idea what the lun is, and we can't select the right SCB register
#  bank, so force a kernel panic if the target attempts a data in/out or
#  command phase instead of corrupting something.  FLAGS also contains
#  configuration bits so that we can optimize for TWIN and WIDE controllers
#  as well as the MAX_OFFSET bit which we set when we want to negotiate for
#  maximum sync offset irregardless of what the per target scratch space says.
#
#  Note that SG_NEXT occupies four bytes.
#
SYNCNEG		= 0x20

REJBYTE		= 0x31
DISC_DSB_A	= 0x32
DISC_DSB_B	= 0x33

MSG_LEN		= 0x34
MSG_START+0	= 0x35
MSG_START+1	= 0x36
MSG_START+2	= 0x37
MSG_START+3	= 0x38
MSG_START+4	= 0x39
MSG_START+5	= 0x3a
-MSG_START+0	= 0xcb				# 2's complement of MSG_START+0

ARG_1		= 0x4a				# sdtr conversion args & return
BUS_16_BIT	= 0x01
RETURN_1	= 0x4a

SIGSTATE	= 0x4b				# value written to SCSISIGO

# Linux users should use 0xc (12) for SG_SIZEOF
#SG_SIZEOF	= 0x8 				# sizeof(struct ahc_dma)
SG_SIZEOF	= 0xc 				# sizeof(struct scatterlist)
# if AIC7XXX_USE_SG
SCB_SIZEOF	= 0x13				# sizeof SCB to DMA (19 bytes)
# else
#SCB_SIZEOF	= 0x1a				# sizeof SCB without SG
# endif

SG_NOLOAD	= 0x4c				# load SG pointer/length?
SG_COUNT	= 0x4d				# working value of SG count
SG_NEXT		= 0x4e				# working value of SG pointer
SG_NEXT+0	= 0x4e
SG_NEXT+1	= 0x4f
SG_NEXT+2	= 0x50
SG_NEXT+3	= 0x51

SCBCOUNT	= 0x52				# the actual number of SCBs
FLAGS		= 0x53				# Device configuration flags
TWIN_BUS	= 0x01
WIDE_BUS	= 0x02
MAX_OFFSET	= 0x08
ACTIVE_MSG	= 0x20
IDENTIFY_SEEN	= 0x40
RESELECTED	= 0x80

MAX_OFFSET_8BIT	= 0x0f
MAX_OFFSET_WIDE	= 0x08

ACTIVE_A	= 0x54
ACTIVE_B	= 0x55
SAVED_TCL	= 0x56				# Temporary storage for the 
						# target/channel/lun of a
						# reconnecting target

# After starting the selection hardware, we return to the "poll_for_work"
# loop so that we can check for reconnecting targets as well as for our
# selection to complete just in case the reselection wins bus arbitration.
# The problem with this is that we must keep track of the SCB that we've
# already pulled from the QINFIFO and started the selection on just in case
# the reselection wins so that we can retry the selection at a later time.
# This problem cannot be resolved by holding a single entry in scratch
# ram since a reconnecting target can request sense and this will create
# yet another SCB waiting for selection.  The solution used here is to 
# use byte 31 of the SCB as a psuedo-next pointer and to thread a list
# of SCBs that are awaiting selection.  Since 0 is a valid SCB offset, 
# SCB_LIST_NULL is 0x10 which is out of range.  The kernel driver must
# add an entry to this list everytime a request sense occurs.  The sequencer
# will automatically consume the entries.

WAITING_SCBH	= 0x57				# head of list of SCBs awaiting
						# selection
WAITING_SCBT	= 0x58				# tail of list of SCBs awaiting
						# selection
SCB_LIST_NULL	= 0x10


#  Poll QINCNT for work - the lower bits contain
#  the number of entries in the Queue In FIFO.
#
start:
	test	WAITING_SCBH,SCB_LIST_NULL jz start_waiting
poll_for_work:
	test	FLAGS,TWIN_BUS	jz start2	# Are we a twin channel device?
# For fairness, we check the other bus first, since we just finished a 
# transaction on the current channel.
	xor	SBLKCTL,0x08			# Toggle to the other bus
	test	SSTAT0,SELDI	jnz reselect
	test	SSTAT0,SELDO	jnz select
	xor	SBLKCTL,0x08			# Toggle to the original bus
start2:
	test	SSTAT0,SELDI	jnz reselect
	test	SSTAT0,SELDO	jnz select
	test	WAITING_SCBH,SCB_LIST_NULL jz start_waiting
	test	QINCNT,SCBMASK	jz poll_for_work

# We have at least one queued SCB now and we don't have any 
# SCBs in the list of SCBs awaiting selection.  Set the SCB
# pointer from the FIFO so we see the right bank of SCB 
# registers, then set SCSI options and set the initiator and
# target SCSI IDs.
#
	mov	SCBPTR,QINFIFO

# If the control byte of this SCB has the NEEDDMA flag set, we have
# yet to DMA it from host memory

test    SCBARRAY+0,NEEDDMA      jz test_busy    
	clr	HCNT+2
	clr	HCNT+1
	mvi	HCNT+0,SCB_SIZEOF

	mvi	DINDEX,HADDR      
	mvi	SCBARRAY+26     call bcopy_4
        
	mvi	DFCNTRL,0xd                     # HDMAEN|DIRECTION|FIFORESET

#  Wait for DMA from host memory to data FIFO to complete, then disable
#  DMA and wait for it to acknowledge that it's off.
#
	call	dma_finish

# Copy the SCB from the FIFO to  the SCBARRAY

	mvi	DINDEX, SCBARRAY+0
	call	bcopy_3_dfdat 
	call	bcopy_4_dfdat
	call	bcopy_4_dfdat
	call	bcopy_4_dfdat   
	call	bcopy_4_dfdat
# ifndef AIC7XXX_USE_SG
#	call	bcopy_3_dfdat
#	call	bcopy_4_dfdat
# endif

# See if there is not already an active SCB for this target.  This code
# locks out on a per target basis instead of target/lun.  Although this
# is not ideal for devices that have multiple luns active at the same
# time, it is faster than looping through all SCB's looking for active
# commands.  It may be benificial to make findscb a more general procedure
# to see if the added cost of the search is negligible.  This code also 
# assumes that the kernel driver will clear the active flags on board 
# initialization, board reset, and a target's SELTO.

test_busy:
	test	SCBARRAY+0,0x20	jnz start_scb
	and	FUNCTION1,0x70,SCBARRAY+1
	mov	A,FUNCTION1
	test	SCBARRAY+1,0x88	jz test_a	# Id < 8 && A channel

	test	ACTIVE_B,A	jnz requeue
	or	ACTIVE_B,A	# Mark the current target as busy
	jmp	start_scb

# Place the currently active back on the queue for later processing
requeue:
	mov	QINFIFO, SCBPTR
	jmp	poll_for_work

# Pull the first entry off of the waiting for selection list
start_waiting:
	mov	SCBPTR,WAITING_SCBH
	jmp	start_scb

test_a:
	test	ACTIVE_A,A	jnz requeue
	or	ACTIVE_A,A	# Mark the current target as busy

start_scb:
	and	SINDEX,0xf7,SBLKCTL  #Clear the channel select bit
	and	A,0x08,SCBARRAY+1    #Get new channel bit
	or	SINDEX,A	     
	mov	SBLKCTL,SINDEX	# select channel
	mov	SCBARRAY+1	call initialize_scsiid

# Enable selection phase as an initiator, and do automatic ATN
# after the selection.  We do this now so that we can overlap the
# rest of our work to set up this target with the arbitration and
# selection bus phases.
#
start_selection:
	or	SCSISEQ,0x48			# ENSELO|ENAUTOATNO
	mov	WAITING_SCBH, SCBPTR
	clr	SG_NOLOAD
	and	FLAGS,0x3f	# !RESELECTING

#  As soon as we get a successful selection, the target should go
#  into the message out phase since we have ATN asserted.  Prepare
#  the message to send, locking out the device driver.  If the device
#  driver hasn't beaten us with an ABORT or RESET message, then tack
#  on an SDTR negotiation if required.
#
#  Messages are stored in scratch RAM starting with a flag byte (high bit
#  set means active message), one length byte, and then the message itself.
#

	test	SCBARRAY+11,0xff jnz identify	# 0 Length Command?

#  The kernel has sent us an SCB with no command attached.  This implies
#  that the kernel wants to send a message of some sort to this target,
#  so we interrupt the driver, allow it to fill the message buffer, and
#  then go back into the arbitration loop
	mvi     INTSTAT,AWAITING_MSG
	jmp     poll_for_work

identify:
	mov	SCBARRAY+1	call disconnect	# disconnect ok?

	and	SINDEX,0x7,SCBARRAY+1		# lun
	or	SINDEX,A			# return value from disconnect
	or	SINDEX,0x80	call mk_mesg	# IDENTIFY message

	mov	A,SINDEX
	test	SCBARRAY+0,0xe0	jz  !message	# WDTR, SDTR or TAG??
	cmp	MSG_START+0,A	jne !message	# did driver beat us?

# Tag Message if Tag enabled in SCB control block.  Use SCBPTR as the tag
# value

mk_tag:
	mvi	DINDEX, MSG_START+1
	test	SCBARRAY+0,TAG_ENB jz mk_tag_done
	and	A,0x23,SCBARRAY+0
	mov	DINDIR,A
	mov	DINDIR,SCBPTR

	add	MSG_LEN,-MSG_START+0,DINDEX	# update message length

mk_tag_done:

	mov	DINDEX	call mk_dtr	# build DTR message if needed

!message:
	jmp	poll_for_work

#  Reselection has been initiated by a target. Make a note that we've been
#  reselected, but haven't seen an IDENTIFY message from the target
#  yet.
#
reselect:
	mov	SELID		call initialize_scsiid
	and	FLAGS,0x3f			# reselected, no IDENTIFY	
	or	FLAGS,RESELECTED jmp select2

# After the selection, remove this SCB from the "waiting for selection"
# list.  This is achieved by simply moving our "next" pointer into
# WAITING_SCBH and setting our next pointer to null so that the next
# time this SCB is used, we don't get confused.
#
select:
	or	SCBARRAY+0,NEEDDMA
	mov	WAITING_SCBH,SCBARRAY+30
	mvi	SCBARRAY+30,SCB_LIST_NULL
select2:
	call	initialize_for_target
	mvi	SCSISEQ,ENRSELI
	mvi	CLRSINT0,0x60			# CLRSELDI|CLRSELDO
	mvi	CLRSINT1,0x8			# CLRBUSFREE

#  Main loop for information transfer phases.  If BSY is false, then
#  we have a bus free condition, expected or not.  Otherwise, wait
#  for the target to assert REQ before checking MSG, C/D and I/O
#  for the bus phase.
#
#  We can't simply look at the values of SCSISIGI here (if we want
#  to do synchronous data transfer), because the target won't assert
#  REQ if it's already sent us some data that we haven't acknowledged
#  yet.
#
ITloop:
	test	SSTAT1,0x8	jnz p_busfree	# BUSFREE
	test	SSTAT1,0x1	jz ITloop	# REQINIT

	and	A,0xe0,SCSISIGI			# CDI|IOI|MSGI

	cmp	ALLZEROS,A	je p_dataout
	cmp	A,0x40		je p_datain
	cmp	A,0x80		je p_command
	cmp	A,0xc0		je p_status
	cmp	A,0xa0		je p_mesgout
	cmp	A,0xe0		je p_mesgin

	mvi	INTSTAT,BAD_PHASE		# unknown - signal driver

p_dataout:
	mvi	0		call scsisig	# !CDO|!IOO|!MSGO
	call	assert
	call	sg_load

	mvi	DINDEX,HADDR
	mvi	SCBARRAY+19	call bcopy_4

#	mvi	DINDEX,HCNT	# implicit since HCNT is next to HADDR
	mvi	SCBARRAY+23	call bcopy_3

	mvi	DINDEX,STCNT
	mvi	SCBARRAY+23	call bcopy_3

# If we are the last SG block, don't set wideodd.
	test    SCBARRAY+18,0xff jnz p_dataout_wideodd
	mvi	0x3d		call dma	# SCSIEN|SDMAEN|HDMAEN|
						#   DIRECTION|FIFORESET
	jmp	p_dataout_rest

p_dataout_wideodd:
	mvi	0xbd		call dma	# WIDEODD|SCSIEN|SDMAEN|HDMAEN|
						#   DIRECTION|FIFORESET

p_dataout_rest:
#  After a DMA finishes, save the final transfer pointer and count
#  back into the SCB, in case a device disconnects in the middle of
#  a transfer.  Use SHADDR and STCNT instead of HADDR and HCNT, since
#  it's a reflection of how many bytes were transferred on the SCSI
#  (as opposed to the host) bus.
#
	mvi	DINDEX,SCBARRAY+23
	mvi	STCNT		call bcopy_3

	mvi	DINDEX,SCBARRAY+19
	mvi	SHADDR		call bcopy_4

	call	sg_advance
	mov	SCBARRAY+18,SG_COUNT		# residual S/G count

	jmp	ITloop

p_datain:
	mvi	0x40		call scsisig	# !CDO|IOO|!MSGO
	call	assert
	call	sg_load

	mvi	DINDEX,HADDR
	mvi	SCBARRAY+19	call bcopy_4

#	mvi	DINDEX,HCNT	# implicit since HCNT is next to HADDR
	mvi	SCBARRAY+23	call bcopy_3

	mvi	DINDEX,STCNT
	mvi	SCBARRAY+23	call bcopy_3

# If we are the last SG block, don't set wideodd.
	test	SCBARRAY+18,0xff jnz p_datain_wideodd
	mvi	0x39		call dma	# SCSIEN|SDMAEN|HDMAEN|
						#   !DIRECTION|FIFORESET
	jmp	p_datain_rest
p_datain_wideodd:
	mvi	0xb9		call dma	# WIDEODD|SCSIEN|SDMAEN|HDMAEN|
						#   !DIRECTION|FIFORESET
p_datain_rest:
	mvi	DINDEX,SCBARRAY+23
	mvi	STCNT		call bcopy_3

	mvi	DINDEX,SCBARRAY+19
	mvi	SHADDR		call bcopy_4

	call	sg_advance
	mov	SCBARRAY+18,SG_COUNT		# residual S/G count

	jmp	ITloop

#  Command phase.  Set up the DMA registers and let 'er rip - the
#  two bytes after the SCB SCSI_cmd_length are zeroed by the driver,
#  so we can copy those three bytes directly into HCNT.
#
p_command:
	mvi	0x80		call scsisig	# CDO|!IOO|!MSGO
	call	assert

	mvi	DINDEX,HADDR
	mvi	SCBARRAY+7	call bcopy_4

#	mvi	DINDEX,HCNT	# implicit since HCNT is next to HADDR
	mvi	SCBARRAY+11	call bcopy_3

	mvi	DINDEX,STCNT
	mvi	SCBARRAY+11	call bcopy_3

	mvi	0x3d		call dma	# SCSIEN|SDMAEN|HDMAEN|
						#   DIRECTION|FIFORESET
	jmp	ITloop

#  Status phase.  Wait for the data byte to appear, then read it
#  and store it into the SCB.
#
p_status:
	mvi	0xc0		call scsisig	# CDO|IOO|!MSGO

	mvi	SCBARRAY+14	call inb_first
	jmp	p_mesgin_done

#  Message out phase.  If there is no active message, but the target
#  took us into this phase anyway, build a no-op message and send it.
#
p_mesgout:
	mvi	0xa0		call scsisig	# CDO|!IOO|MSGO
	mvi	0x8		call mk_mesg	# build NOP message

	clr     STCNT+2
	clr     STCNT+1

#  Set up automatic PIO transfer from MSG_START.  Bit 3 in
#  SXFRCTL0 (SPIOEN) is already on.
#
	mvi	SINDEX,MSG_START+0
	mov	DINDEX,MSG_LEN

#  When target asks for a byte, drop ATN if it's the last one in
#  the message.  Otherwise, keep going until the message is exhausted.
#  (We can't use outb for this since it wants the input in SINDEX.)
#
#  Keep an eye out for a phase change, in case the target issues
#  a MESSAGE REJECT.
#
p_mesgout2:
	test	SSTAT0,0x2	jz p_mesgout2	# SPIORDY
	test	SSTAT1,0x10	jnz p_mesgout6	# PHASEMIS

	cmp	DINDEX,1	jne p_mesgout3	# last byte?
	mvi	CLRSINT1,0x40			# CLRATNO - drop ATN

#  Write a byte to the SCSI bus.  The AIC-7770 refuses to automatically
#  send ACKs in automatic PIO or DMA mode unless you make sure that the
#  "expected" bus phase in SCSISIGO matches the actual bus phase.  This
#  behaviour is completely undocumented and caused me several days of
#  grief.
#
#  After plugging in different drives to test with and using a longer
#  SCSI cable, I found that I/O in Automatic PIO mode ceased to function,
#  especially when transferring >1 byte.  It seems to be much more stable
#  if STCNT is set to one before the transfer, and SDONE (in SSTAT0) is
#  polled for transfer completion - for both output _and_ input.  The
#  only theory I have is that SPIORDY doesn't drop right away when SCSIDATL
#  is accessed (like the documentation says it does), and that on a longer
#  cable run, the sequencer code was fast enough to loop back and see
#  an SPIORDY that hadn't dropped yet.
#
p_mesgout3:
	mvi	STCNT+0, 0x01	
	mov	SCSIDATL,SINDIR

p_mesgout4:
	test	SSTAT0,0x4	jz p_mesgout4	# SDONE
	dec	DINDEX
	test	DINDEX,0xff	jnz p_mesgout2

#  If the next bus phase after ATN drops is a message out, it means
#  that the target is requesting that the last message(s) be resent.
#
p_mesgout5:
	test	SSTAT1,0x8	jnz p_mesgout6	# BUSFREE
	test	SSTAT1,0x1	jz p_mesgout5	# REQINIT

	and	A,0xe0,SCSISIGI			# CDI|IOI|MSGI
	cmp	A,0xa0		jne p_mesgout6
	mvi	0x10		call scsisig	# ATNO - re-assert ATN

	jmp	ITloop

p_mesgout6:
	mvi	CLRSINT1,0x40			# CLRATNO - in case of PHASEMIS
	and	FLAGS,0xdf			# no active msg
	jmp	ITloop

#  Message in phase.  Bytes are read using Automatic PIO mode, but not
#  using inb.  This alleviates a race condition, namely that if ATN had
#  to be asserted under Automatic PIO mode, it had to beat the SCSI
#  circuitry sending an ACK to the target.  This showed up under heavy
#  loads and really confused things, since ABORT commands wouldn't be
#  seen by the drive after an IDENTIFY message in until it had changed
#  to a data I/O phase.
#
p_mesgin:
	mvi	0xe0		call scsisig	# CDO|IOO|MSGO
	mvi	A		call inb_first	# read the 1st message byte
	mvi	REJBYTE,A			# save it for the driver

	cmp	ALLZEROS,A	jne p_mesgin1

#  We got a "command complete" message, so put the SCB pointer
#  into the Queue Out, and trigger a completion interrupt.
#  Check status for non zero return and interrupt driver if needed
#  This allows the driver to interpret errors only when they occur
#  instead of always uploading the scb.  If the status is SCSI_CHECK,
#  the driver will download a new scb requesting sense to replace
#  the old one, modify the "waiting for selection" SCB list and set 
#  RETURN_1 to 0x80.  If RETURN_1 is set to 0x80 the sequencer imediately
#  jumps to main loop where it will run down the waiting SCB list.
#  If the kernel driver does not wish to request sense, it need
#  only clear RETURN_1, and the command is allowed to complete.  We don't 
#  bother to post to the QOUTFIFO in the error case since it would require 
#  extra work in the kernel driver to ensure that the entry was removed 
#  before the command complete code tried processing it.

# First check for residuals
	test	SCBARRAY+15,0xff	jnz resid
	test	SCBARRAY+16,0xff	jnz resid
	test	SCBARRAY+17,0xff	jnz resid

check_status:
	test	SCBARRAY+14,0xff	jz status_ok	# 0 Status?
	mvi	INTSTAT,BAD_STATUS			# let driver know
	test	RETURN_1, 0x80	jz status_ok
	jmp	p_mesgin_done

status_ok:
#  First, mark this target as free.
	test	SCBARRAY+0,0x20	jnz complete		# Tagged command
	and	FUNCTION1,0x70,SCBARRAY+1
	mov	A,FUNCTION1
	test	SCBARRAY+1,0x88 jz clear_a
	xor	ACTIVE_B,A
	jmp	complete

clear_a:
	xor	ACTIVE_A,A

complete:
	mov	QOUTFIFO,SCBPTR
	mvi	INTSTAT,CMDCMPLT
	test    SCBARRAY+11,0xff jz start	# Immediate message complete
	jmp	p_mesgin_done

# If we have a residual count, interrupt and tell the host.  Other
# alternatives are to pause the sequencer on all command completes (yuck),
# dma the resid directly to the host (slick, but a ton of instructions), or
# have the sequencer pause itself when it encounters a non-zero resid 
# (unecessary pause just to flag the command -- yuck, but takes few instructions
# and since it shouldn't happen that often is good enough for our purposes).  

resid:
	mvi	INTSTAT,RESIDUAL
	jmp	check_status

#  Is it an extended message?  We only support the synchronous and wide data
#  transfer request messages, which will probably be in response to
#  WDTR or SDTR message outs from us.  If it's not SDTR or WDTR, reject it -
#  apparently this can be done after any message in byte, according
#  to the SCSI-2 spec.
#
p_mesgin1:
	cmp	A,1		jne p_mesgin2	# extended message code?
	
	mvi	ARG_1		call inb_next	# extended message length
	mvi	A		call inb_next	# extended message code

	cmp	A,1		je p_mesginSDTR	# Syncronous negotiation message
	cmp	A,3		je p_mesginWDTR # Wide negotiation message
	jmp	p_mesginN

p_mesginWDTR:
	cmp	ARG_1,2		jne p_mesginN	# extended mesg length = 2
	mvi	A		call inb_next	# Width of bus
	mvi	INTSTAT,MSG_WDTR		# let driver know
	test	RETURN_1,0x80	jz p_mesgin_done# Do we need to send WDTR?

# We didn't initiate the wide negotiation, so we must respond to the request
	and	RETURN_1,0x7f			# Clear the SEND_WDTR Flag
	or	FLAGS,ACTIVE_MSG
	mvi	DINDEX,MSG_START+0
	mvi	MSG_START+0	call mk_wdtr	# build WDTR message	
	or	SINDEX,0x10,SIGSTATE		# turn on ATNO
	call	scsisig
	jmp	p_mesgin_done

p_mesginSDTR:
	cmp	ARG_1,3		jne p_mesginN	# extended mesg length = 3
	mvi	ARG_1		call inb_next	# xfer period
	mvi	A		call inb_next	# REQ/ACK offset
	mvi	INTSTAT,MSG_SDTR		# call driver to convert

	test	RETURN_1,0xc0	jz p_mesgin_done# Do we need to mk_sdtr or rej?
	test	RETURN_1,0x40	jnz p_mesginN	# Requested SDTR too small - rej
	or	FLAGS,ACTIVE_MSG
	mvi	DINDEX, MSG_START+0
	mvi     MSG_START+0     call mk_sdtr
	or	SINDEX,0x10,SIGSTATE		# turn on ATNO
	call	scsisig
	jmp	p_mesgin_done

#  Is it a disconnect message?  Set a flag in the SCB to remind us
#  and await the bus going free.
#
p_mesgin2:
	cmp	A,4		jne p_mesgin3	# disconnect code?

	or	SCBARRAY+0,0x4			# set "disconnected" bit
	jmp	p_mesgin_done

#  Save data pointers message?  Copy working values into the SCB,
#  usually in preparation for a disconnect.
#
p_mesgin3:
	cmp	A,2		jne p_mesgin4	# save data pointers code?

	call	sg_ram2scb
	jmp	p_mesgin_done

#  Restore pointers message?  Data pointers are recopied from the
#  SCB anyway at the start of any DMA operation, so the only thing
#  to copy is the scatter-gather values.
#
p_mesgin4:
	cmp	A,3		jne p_mesgin5	# restore pointers code?

	call	sg_scb2ram
	jmp	p_mesgin_done

#  Identify message?  For a reconnecting target, this tells us the lun
#  that the reconnection is for - find the correct SCB and switch to it,
#  clearing the "disconnected" bit so we don't "find" it by accident later.
#
p_mesgin5:
	test	A,0x80		jz p_mesgin6	# identify message?

	test	A,0x78		jnz p_mesginN	# !DiscPriv|!LUNTAR|!Reserved

	and	A,0x07				# lun in lower three bits
	or      SAVED_TCL,A,SELID          
	and     SAVED_TCL,0xf7
	and     A,0x08,SBLKCTL			# B Channel??
	or      SAVED_TCL,A
	call	inb_last			# ACK
	mov	ALLZEROS	call findSCB    
setup_SCB:
	and	SCBARRAY+0,0xfb			# clear disconnect bit in SCB
	or	FLAGS,IDENTIFY_SEEN		# make note of IDENTIFY

	call	sg_scb2ram			# implied restore pointers
						#   required on reselect
	jmp	ITloop
get_tag:
	mvi	A		call inb_first
	cmp	A,0x20  	jne return	# Simple Tag message?
	mvi	A		call inb_next
	call			inb_last
	test	A,0xf0		jnz abort_tag	# Tag in range?
	mov	SCBPTR,A
	mov	A,SAVED_TCL
	cmp	SCBARRAY+1,A		jne abort_tag
	test	SCBARRAY+0,TAG_ENB	jz  abort_tag
	ret
abort_tag:
	or	SINDEX,0x10,SIGSTATE		# turn on ATNO
	call	scsisig
	mvi	INTSTAT,ABORT_TAG 		# let driver know
	mvi	0xd		call mk_mesg	# ABORT TAG message
	ret

#  Message reject?  Let the kernel driver handle this.  If we have an 
#  outstanding WDTR or SDTR negotiation, assume that it's a response from 
#  the target selecting 8bit or asynchronous transfer, otherwise just ignore 
#  it since we have no clue what it pertains to.
#
p_mesgin6:
	cmp	A,7		jne p_mesgin7	# message reject code?

	mvi	INTSTAT, MSG_REJECT
	jmp	p_mesgin_done

#  [ ADD MORE MESSAGE HANDLING HERE ]
#
p_mesgin7:

#  We have no idea what this message in is, and there's no way
#  to pass it up to the kernel, so we issue a message reject and
#  hope for the best.  Since we're now using manual PIO mode to
#  read in the message, there should no longer be a race condition
#  present when we assert ATN.  In any case, rejection should be a
#  rare occurrence - signal the driver when it happens.
#
p_mesginN:
	or	SINDEX,0x10,SIGSTATE		# turn on ATNO
	call	scsisig
	mvi	INTSTAT,SEND_REJECT		# let driver know

	mvi	0x7		call mk_mesg	# MESSAGE REJECT message

p_mesgin_done:
	call	inb_last			# ack & turn auto PIO back on
	jmp	ITloop


#  Bus free phase.  It might be useful to interrupt the device
#  driver if we aren't expecting this.  For now, make sure that
#  ATN isn't being asserted and look for a new command.
#
p_busfree:
	mvi	CLRSINT1,0x40			# CLRATNO
	clr	SIGSTATE

#  if this is an immediate command, perform a psuedo command complete to
#  notify the driver.
	test	SCBARRAY+11,0xff	jz status_ok
	jmp	start

#  Instead of a generic bcopy routine that requires an argument, we unroll
#  the two cases that are actually used, and call them explicitly.  This
#  not only reduces the overhead of doing a bcopy by 2/3rds, but ends up
#  saving space in the program since you don't have to put the argument 
#  into the accumulator before the call.  Both functions expect DINDEX to
#  contain the destination address and SINDEX to contain the source 
#  address.
bcopy_3:
	mov	DINDIR,SINDIR
	mov	DINDIR,SINDIR
	mov	DINDIR,SINDIR	ret

bcopy_4:
	mov	DINDIR,SINDIR
	mov	DINDIR,SINDIR
	mov	DINDIR,SINDIR
	mov	DINDIR,SINDIR	ret
	
bcopy_3_dfdat:
	mov	DINDIR,DFDAT
	mov	DINDIR,DFDAT
	mov	DINDIR,DFDAT	ret

bcopy_4_dfdat:
	mov	DINDIR,DFDAT
	mov	DINDIR,DFDAT
	mov	DINDIR,DFDAT
	mov	DINDIR,DFDAT	ret

#  Locking the driver out, build a one-byte message passed in SINDEX
#  if there is no active message already.  SINDEX is returned intact.
#
mk_mesg:
	mvi	SEQCTL,0x50			# PAUSEDIS|FASTMODE
	test	FLAGS,ACTIVE_MSG jnz mk_mesg1	# active message?

	or	FLAGS,ACTIVE_MSG		# if not, there is now
	mvi	MSG_LEN,1			# length = 1
	mov	MSG_START+0,SINDEX		# 1-byte message

mk_mesg1:
	mvi	SEQCTL,0x10	ret		# !PAUSEDIS|FASTMODE

#  Carefully read data in Automatic PIO mode.  I first tried this using
#  Manual PIO mode, but it gave me continual underrun errors, probably
#  indicating that I did something wrong, but I feel more secure leaving
#  Automatic PIO on all the time.
#
#  According to Adaptec's documentation, an ACK is not sent on input from
#  the target until SCSIDATL is read from.  So we wait until SCSIDATL is
#  latched (the usual way), then read the data byte directly off the bus
#  using SCSIBUSL.  When we have pulled the ATN line, or we just want to
#  acknowledge the byte, then we do a dummy read from SCISDATL.  The SCSI
#  spec guarantees that the target will hold the data byte on the bus until
#  we send our ACK.
#
#  The assumption here is that these are called in a particular sequence,
#  and that REQ is already set when inb_first is called.  inb_{first,next}
#  use the same calling convention as inb.
#
inb_first:
	clr	STCNT+2
	clr	STCNT+1
	mov	DINDEX,SINDEX
	mov	DINDIR,SCSIBUSL	ret		# read byte directly from bus

inb_next:
	mov	DINDEX,SINDEX			# save SINDEX

        mvi     STCNT+0,1			# xfer one byte
	mov	NONE,SCSIDATL			# dummy read from latch to ACK
inb_next1:
	test	SSTAT0,0x4	jz inb_next1	# SDONE
inb_next2:
	test	SSTAT0,0x2	jz inb_next2	# SPIORDY - wait for next byte
	mov	DINDIR,SCSIBUSL	ret		# read byte directly from bus

inb_last:
	mvi	STCNT+0,1			# ACK with dummy read
	mov	NONE,SCSIDATL
inb_last1:
	test	SSTAT0,0x4	jz inb_last1	# wait for completion
	ret

#  DMA data transfer.  HADDR and HCNT must be loaded first, and
#  SINDEX should contain the value to load DFCNTRL with - 0x3d for
#  host->scsi, or 0x39 for scsi->host.  The SCSI channel is cleared
#  during initialization.
#
dma:
	mov	DFCNTRL,SINDEX
dma1:
dma2:
	test	SSTAT0,0x1	jnz dma3	# DMADONE
	test	SSTAT1,0x10	jz dma1		# PHASEMIS, ie. underrun

#  We will be "done" DMAing when the transfer count goes to zero, or
#  the target changes the phase (in light of this, it makes sense that
#  the DMA circuitry doesn't ACK when PHASEMIS is active).  If we are
#  doing a SCSI->Host transfer, the data FIFO should be flushed auto-
#  magically on STCNT=0 or a phase change, so just wait for FIFO empty
#  status.
#
dma3:
	test	SINDEX,0x4	jnz dma5	# DIRECTION
dma4:
	test	DFSTATUS,0x1	jz dma4		# !FIFOEMP

#  Now shut the DMA enables off, and copy STCNT (ie. the underrun
#  amount, if any) to the SCB registers; SG_COUNT will get copied to
#  the SCB's residual S/G count field after sg_advance is called.  Make
#  sure that the DMA enables are actually off first lest we get an ILLSADDR.
#
dma5:
	clr	DFCNTRL				# disable DMA
dma6:
	test	DFCNTRL,0x38	jnz dma6	# SCSIENACK|SDMAENACK|HDMAENACK

	mvi	DINDEX,SCBARRAY+15
	mvi	STCNT		call bcopy_3

	ret

dma_finish:
	test	DFSTATUS,0x8	jz dma_finish	# HDONE

	clr	DFCNTRL				# disable DMA
dma_finish2:
	test	DFCNTRL,0x8	jnz dma_finish2	# HDMAENACK
	ret

#  Common SCSI initialization for selection and reselection.  Expects
#  the target SCSI ID to be in the upper four bits of SINDEX, and A's
#  contents are stomped on return.
#
initialize_scsiid:
	and	SINDEX,0xf0		# Get target ID
	and	A,0x0f,SCSIID
	or	SINDEX,A
	mov	SCSIID,SINDEX ret

initialize_for_target:
#  Turn on Automatic PIO mode now, before we expect to see a REQ
#  from the target.  It shouldn't hurt anything to leave it on.  Set
#  CLRCHN here before the target has entered a data transfer mode -
#  with synchronous SCSI, if you do it later, you blow away some
#  data in the SCSI FIFO that the target has already sent to you.
#
	clr	SIGSTATE 

	mvi	SXFRCTL0,0x8a			# DFON|SPIOEN|CLRCHN

#  Initialize scatter-gather pointers by setting up the working copy
#  in scratch RAM.
#
	call	sg_scb2ram

#  Initialize SCSIRATE with the appropriate value for this target.
#
	call	ndx_dtr
	mov	SCSIRATE,SINDIR	ret

#  Assert that if we've been reselected, then we've seen an IDENTIFY
#  message.
#
assert:
	test	FLAGS,RESELECTED	jz return	# reselected?
	test	FLAGS,IDENTIFY_SEEN	jnz return	# seen IDENTIFY?

	mvi	INTSTAT,NO_IDENT 	ret	# no - cause a kernel panic

#  Find out if disconnection is ok from the information the BIOS has left
#  us.  The tcl from SCBARRAY+1 should be in SINDEX; A will
#  contain either 0x40 (disconnection ok) or 0x00 (disconnection not ok)
#  on exit.
#
#  To allow for wide or twin busses, we check the upper bit of the target ID
#  and the channel ID and look at the appropriate disconnect register. 
#
disconnect:
	and	FUNCTION1,0x70,SINDEX		# strip off extra just in case
	mov	A,FUNCTION1
	test	SINDEX, 0x88	jz disconnect_a

	test	DISC_DSB_B,A	jz disconnect1	# bit nonzero if DISabled
	clr	A		ret

disconnect_a:
	test	DISC_DSB_A,A	jz disconnect1	# bit nonzero if DISabled
	clr	A		ret

disconnect1:
	mvi	A,0x40		ret

#  Locate the SCB matching the target ID/channel/lun in SAVED_TCL and switch 
#  the SCB to it.  Have the kernel print a warning message if it can't be 
#  found, and generate an ABORT message to the target.  SINDEX should be
#  cleared on call.
#
findSCB:
	mov	A,SAVED_TCL
	mov	SCBPTR,SINDEX			# switch to new SCB
	cmp	SCBARRAY+1,A	jne findSCB1	# target ID/channel/lun match?
	test	SCBARRAY+0,0x4	jz findSCB1	# should be disconnected
	test	SCBARRAY+0,TAG_ENB jnz get_tag
	ret

findSCB1:
	inc	SINDEX
	mov	A,SCBCOUNT
	cmp	SINDEX,A	jne findSCB

	mvi	INTSTAT,NO_MATCH		# not found - signal kernel
	mvi	0x6		call mk_mesg	# ABORT message

	or	SINDEX,0x10,SIGSTATE		# assert ATNO
	call	scsisig
	ret

#  Make a working copy of the scatter-gather parameters in the SCB.
#
sg_scb2ram:
	mov	SG_COUNT,SCBARRAY+2

	mvi	DINDEX,SG_NEXT
	mvi	SCBARRAY+3	call bcopy_4

	mvi	SG_NOLOAD,0x80
	test	SCBARRAY+0,0x10	jnz return	# don't reload s/g?
	clr	SG_NOLOAD	 ret

#  Copying RAM values back to SCB, for Save Data Pointers message.
#
sg_ram2scb:
	mov	SCBARRAY+2,SG_COUNT

	mvi	DINDEX,SCBARRAY+3
	mvi	SG_NEXT		call bcopy_4

	and	SCBARRAY+0,0xef,SCBARRAY+0
	test	SG_NOLOAD,0x80	jz return	# reload s/g?
	or	SCBARRAY+0,SG_LOAD	 ret

#  Load a struct scatter if needed and set up the data address and
#  length.  If the working value of the SG count is nonzero, then
#  we need to load a new set of values.
#
#  This, like the above DMA, assumes a little-endian host data storage.
#
sg_load:
	test	SG_COUNT,0xff	jz return	# SG being used?
	test	SG_NOLOAD,0x80	jnz return	# don't reload s/g?

	clr	HCNT+2
	clr	HCNT+1
	mvi	HCNT+0,SG_SIZEOF

	mvi	DINDEX,HADDR
	mvi	SG_NEXT		call bcopy_4

	mvi	DFCNTRL,0xd			# HDMAEN|DIRECTION|FIFORESET

#  Wait for DMA from host memory to data FIFO to complete, then disable
#  DMA and wait for it to acknowledge that it's off.
#

	call	dma_finish

#  Copy data from FIFO into SCB data pointer and data count.  This assumes
#  that the struct scatterlist has this structure (this and sizeof(struct
#  scatterlist) == 12 are asserted in aic7xxx.c):
#
#	struct scatterlist {
#		char *address;		/* four bytes, little-endian order */
#		...			/* four bytes, ignored */
#		unsigned short length;	/* two bytes, little-endian order */
#	}
#

# Not in FreeBSD.  the scatter list entry is only 8 bytes.
# 
# struct ahc_dma_seg {
#       physaddr addr;                  /* four bytes, little-endian order */
#       long    len;                    /* four bytes, little endian order */   
# };
#

	mvi	DINDEX, SCBARRAY+19
	call	bcopy_4_dfdat

# For Linux, we must throw away four bytes since there is a 32bit gap
# in the middle of a struct scatterlist
	mov	NONE,DFDAT
	mov	NONE,DFDAT
	mov	NONE,DFDAT
	mov	NONE,DFDAT

	call	bcopy_3_dfdat		#Only support 24 bit length.
	ret

#  Advance the scatter-gather pointers only IF NEEDED.  If SG is enabled,
#  and the SCSI transfer count is zero (note that this should be called
#  right after a DMA finishes), then move the working copies of the SG
#  pointer/length along.  If the SCSI transfer count is not zero, then
#  presumably the target is disconnecting - do not reload the SG values
#  next time.
#
sg_advance:
	test	SG_COUNT,0xff	jz return	# s/g enabled?

	test	STCNT+0,0xff	jnz sg_advance1	# SCSI transfer count nonzero?
	test	STCNT+1,0xff	jnz sg_advance1
	test	STCNT+2,0xff	jnz sg_advance1

	clr	SG_NOLOAD			# reload s/g next time
	dec	SG_COUNT			# one less segment to go

	clr	A				# add sizeof(struct scatter)
	add	SG_NEXT+0,SG_SIZEOF,SG_NEXT+0
	adc	SG_NEXT+1,A,SG_NEXT+1
	adc	SG_NEXT+2,A,SG_NEXT+2
	adc	SG_NEXT+3,A,SG_NEXT+3	ret

sg_advance1:
	mvi	SG_NOLOAD,0x80	ret		# don't reload s/g next time

#  Add the array base SYNCNEG to the target offset (the target address
#  is in SCSIID), and return the result in SINDEX.  The accumulator
#  contains the 3->8 decoding of the target ID on return.
#
ndx_dtr:
	shr	A,SCSIID,4
	test	SBLKCTL,0x08	jz ndx_dtr_2
	or	A,0x08		# Channel B entries add 8
ndx_dtr_2:
	add	SINDEX,SYNCNEG,A

	and	FUNCTION1,0x70,SCSIID		# 3-bit target address decode
	mov	A,FUNCTION1	ret

#  If we need to negotiate transfer parameters, build the WDTR or SDTR message
#  starting at the address passed in SINDEX.  DINDEX is modified on return.
#  The SCSI-II spec requires that Wide negotiation occur first and you can
#  only negotiat one or the other at a time otherwise in the event of a message
#  reject, you wouldn't be able to tell which message was the culpret.
#
mk_dtr:
	test	SCBARRAY+0,0xc0 jz return	# NEEDWDTR|NEEDSDTR
	test	SCBARRAY+0,NEEDWDTR jnz  mk_wdtr_16bit
	or	FLAGS, MAX_OFFSET	# Force an offset of 15 or 8 if WIDE

mk_sdtr:
	mvi	DINDIR,1			# extended message
	mvi	DINDIR,3			# extended message length = 3
	mvi	DINDIR,1			# SDTR code
	call	sdtr_to_rate
	mov	DINDIR,RETURN_1			# REQ/ACK transfer period
	test	FLAGS, MAX_OFFSET jnz mk_sdtr_max_offset
	and	DINDIR,0x0f,SINDIR		# Sync Offset

mk_sdtr_done:
	add	MSG_LEN,-MSG_START+0,DINDEX ret	# update message length

mk_sdtr_max_offset:
# We're initiating sync negotiation, so request the max offset we can (15 or 8)
	xor	FLAGS, MAX_OFFSET
	test	SCSIRATE, 0x80	jnz wmax_offset	# Talking to a WIDE device?
	mvi	DINDIR, MAX_OFFSET_8BIT
	jmp	mk_sdtr_done

wmax_offset:
	mvi	DINDIR, MAX_OFFSET_WIDE
	jmp	mk_sdtr_done

mk_wdtr_16bit:
	mvi	ARG_1,BUS_16_BIT
mk_wdtr:
	mvi	DINDIR,1			# extended message
	mvi	DINDIR,2			# extended message length = 2
	mvi	DINDIR,3			# WDTR code
	mov	DINDIR,ARG_1			# bus width

	add	MSG_LEN,-MSG_START+0,DINDEX ret	# update message length
	
#  Set SCSI bus control signal state.  This also saves the last-written
#  value into a location where the higher-level driver can read it - if
#  it has to send an ABORT or RESET message, then it needs to know this
#  so it can assert ATN without upsetting SCSISIGO.  The new value is
#  expected in SINDEX.  Change the actual state last to avoid contention
#  from the driver.
#
scsisig:
	mov	SIGSTATE,SINDEX
	mov	SCSISIGO,SINDEX	ret

sdtr_to_rate:
	call	ndx_dtr				# index scratch space for target
	shr	A,SINDIR,0x4
	dec	SINDEX				#Preserve SINDEX
	and	A,0x7
	clr	RETURN_1
sdtr_to_rate_loop:
	test	A,0x0f	jz sdtr_to_rate_done
	add	RETURN_1,0x18
	dec	A	
	jmp	sdtr_to_rate_loop
sdtr_to_rate_done:
	shr	RETURN_1,0x2
	add	RETURN_1,0x18	ret

return:
	ret
