/* 	Copyright (c) 1994 NeXT Computer, Inc.  All rights reserved. 
 *
 * AMD_ChipPrivate.m - methods used only by AMD_Chip module.
 *
 * HISTORY
 * 2 Nov 94    Doug Mitchell at NeXT
 *      Created. 
 */

#import "AMD_Chip.h"
#import "AMD_ChipPrivate.h"
#import "AMD_Private.h"
#import "AMD_x86.h"
#import "AMD_Regs.h"
#import "AMD_Types.h"
#import "AMD_ddm.h"
#import "bringup.h"
#import <driverkit/generalFuncs.h>
#import <kernserv/prototypes.h>


@implementation AMD_SCSI(ChipPrivate)

/*
 * Determine if SCSI interrupt is pending.
 */
- (sintPending_t)scsiInterruptPending
{
	unsigned char sstat;
	
	sstat = READ_REG(scsiStat);
	if (sstat & SS_INTERRUPT) {
		return SINT_DEVICE;
	}
	else {
		return SINT_NONE;
	}
}

/*
 * Methods invoked upon interrupt. One per legal scState. All assume that 
 * status and interrupt status have been captured in saveStatus and 
 * saveIntrStatus.
 */
 
/*
 * Disconnected - only legal event here is reselection.
 */
- (void)fsmDisconnected
{
	ddm_chip("fsmDisconnected\n", 1,2,3,4,5);
	ASSERT(activeCmd == NULL);
	if(saveIntrStatus & IS_RESELECTED) {
		/*
		 * We've been reselected. 
		 */
				
		unsigned char	selectByte;
		unsigned 	fifoDepth;
		unsigned char	msg;
	
		/* 
		 * Make sure there's a selection byte and an 
		 * identify message in the fifo.
		 */
		fifoDepth = READ_REG(currFifoState) & FS_FIFO_LEVEL_MASK;
		ddm_chip("reselect: fifoDepth %d\n", fifoDepth, 2,3,4,5);
		if(fifoDepth != 2) {
			ddm_err("reselection, fifoDepth %d\n", fifoDepth, 
				2,3,4,5);
			IOLog("AMD53C974: Bad FIFO count (%d) on Reselect\n",
				fifoDepth);
			[self hwAbort:SR_IOST_HW 
				reason:NULL];
			return;
			
		}
		
		/* 
		 * make sure target set his bit.
		 */
		if ((selectByte = READ_REG(scsiFifo) &~ (1 << hostId)) == 0) {
			ddm_err("fsmDisconnected: reselection failed"
				" - no target bit\n", 1,2,3,4,5);
			[self hwAbort:SR_IOST_BV 
				reason:"No target bit on Reselect"];
			return;
		}
		
		/* 
		 * figure out target from bit that's on.
		 */
		for (reselTarget = 0; 
		     (selectByte & 1) == 0; 
		     reselTarget++, selectByte>>=1) {
			continue;
		}
		
		/* 
		 * first message byte must be identify.
		 */
		msg = READ_REG(scsiFifo);
		if (saveStatus & SS_PARITYERROR) {
			ddm_err("fsmDisconnected: reselected parity error\n",
				1,2,3,4,5);
			[self hwAbort:SR_IOST_PARITY 
				reason:"Parity error on Reselect"];
			return;
		}
		if ((msg & MSG_IDENTIFYMASK) == 0) {
			ddm_err("fsmDisconnected: reselection failed - "
				"bad msg byte (0x%x)\n", msg, 2,3,4,5);
			[self hwAbort:SR_IOST_BV 
				reason:"Bad ID Message on Reselect"];
			return;
		}
		reselLun = msg & MSG_ID_LUNMASK;
		currMsgInCnt = 0;
		
		/*
		 * At this point, the chip is waiting for us to validate 
		 * the identify message. If cmd queueing is enabled
		 * for this target, the target is waiting to send a queue 
		 * tag message, so we have to tell the chip to 
		 * drop ACK before we proceed with the reselection.
		 *
		 * In case of sync mode, we need to load target context right
		 * now, before dropping ACK, because the target might go
		 * straight to a data in or data out as soon as ACK drops.
		 */
		[self targetContext:reselTarget];
		scState = SCS_ACCEPTINGMSG;
		reselPending = 1;
		WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
		WRITE_REG(scsiCmd, SCMD_MSG_ACCEPTED);
		ddm_chip("reselPending: target %d lun %d\n",
			reselTarget, reselLun, 3,4,5);
		 
	} else if(saveIntrStatus & IS_SCSIRESET) {
		/*
		 * TBD - for now ignore, we get one of these by resetting the
		 * chip. If an I/O is pending, it'll probably time out.
		 * Maybe we want to return SR_IOST_RESET on the pending 
		 * command...
		 */
		ddm_chip("fsmDisconnected: ignoring reset interrupt\n",
			1,2,3,4,5);
	} else {
		/* 
		 * I'm confused.... 
		 */
		[self hwAbort:SR_IOST_BV 
			reason:"bad interrupt while disconnected"];
	}
}


/*
 * One of three things can happen here - the selection could succeed (though
 * with possible imcomplete message out), it could time out, or we can be 
 * reselected.
 */
#define CATCH_SELECT_TO		1

- (void)fsmSelecting
{
	unsigned char fifoDepth;
	unsigned char phase;
	IOSCSIRequest *scsiReq = activeCmd->scsiReq;
	
	ddm_chip("fsmSelecting\n", 1,2,3,4,5);
	ASSERT(activeCmd != NULL);
	if (saveIntrStatus & IS_DISCONNECT) {
		/*
		 * selection timed-out. Abort this request.
		 */
		#if	CATCH_SELECT_TO
		/* DEBUG ONLY */
		if(scsiReq->cdb.cdb_opcode != C6OP_INQUIRY) {
			IOLog("Unexpected Select Timeout\n");
		}
		#endif	CATCH_SELECT_TO
		ddm_chip("***SELECTION TIMEOUT for target %d\n",
			activeCmd->scsiReq->target, 2,3,4,5);
		WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
		scState = SCS_DISCONNECTED;
		scsiReq->driverStatus = SR_IOST_SELTO;
		[self ioComplete:activeCmd];
		activeCmd = NULL;
	}
	else if(saveIntrStatus == (IS_SUCCESSFUL_OP|IS_SERVICE_REQ)) {

		ddm_chip("selection seqstep=%d\n", 
			saveSeqStep & INS_STATE_MASK, 2,3,4,5);
		
		switch (saveSeqStep & INS_STATE_MASK) {
		    case 0:	
		    	/*
			 * No message phase. If we really wanted one,
			 * this could be significant...
			 */
			if(activeCmd->queueTag != QUEUE_TAG_NONTAGGED) {
				/*
				 * This target can't do command queueing.
				 */
				[self disableMode:AM_CmdQueue];
			}	
			if(SDTR_State == SNS_HOST_INIT_NEEDED) {
				/*
				 * We were trying to do an SDTR.
				 */
				[self disableMode:AM_Sync];
				SDTR_State = SNS_NONE;
			}
			
			/*
			 * OK, let's try to continue following phase
			 * changes.
			 */
			scState = SCS_INITIATOR;
			break;
			
		    case 3:	/* didn't complete cmd phase, parity? */
		    case 4:	/* everything worked */
		    case 1:	/* everything worked, SCMD_SELECT_ATN_STOP
		    		 * case */
		    
		        /* 
			 * We're connected. Start following the target's phase
			 * changes.
			 *
			 * If we're trying to do sync negotiation,
			 * this is the place to do it. In that case, we
			 * sent a SCMD_SELECT_ATN_STOP command, and
			 * ATN is now asserted (and we're hopefully in
			 * msg out phase). We want to send 5 bytes. 
			 * Drop them into currMsgOut[] and prime the  
			 * msgOutState machine.
			 */
			if(SDTR_State == SNS_HOST_INIT_NEEDED) {
				[self createSDTR:currMsgOut inboundMsg:NULL];
				currMsgOutCnt = MSG_SDTR_LENGTH;
				msgOutState = MOS_WAITING;
			}
			scState = SCS_INITIATOR;
			break;
			
		    case 2:	
			/*
			 * Either no command phase, or imcomplete message
			 * transfer.
			 */
			fifoDepth = READ_REG(currFifoState) & 
				FS_FIFO_LEVEL_MASK;
			phase = saveStatus & SS_PHASEMASK;
			ddm_chip("INCOMPLETE SELECT; fifoDepth %d phase %s\n",
				fifoDepth, 
				IOFindNameForValue(phase, scsiPhaseValues),
				3,4,5);
			if(activeCmd->queueTag != QUEUE_TAG_NONTAGGED) {
				/*
				 * This target can't do command queueing.
				 */
				[self disableMode:AM_CmdQueue];
			}	
			
			/*
			 * Spec says ATN is asserted if all message bytes
			 * were not sent.
			 */
			if(fifoDepth > activeCmd->cdbLength) {
				WRITE_REG(scsiCmd, SCMD_CLR_ATN);
			}
			
			/*
			 * OK, let's try to continue following phase
			 * changes.
			 */
			scState = SCS_INITIATOR;
			break;

		    default:
			[self hwAbort:SR_IOST_HW 
				reason:"Selection sequence Error"];
			break;
		}
	}
	else if(saveIntrStatus & IS_RESELECTED) {
		/*
		 * We got reselected while trying to do a selection. 
		 * Enqueue this cmdBuf on the HEAD of pendingQ, then deal
		 * with the reselect. 
		 * Tricky case, we have to "deactivate" this command
		 * since this hwStart attempt failed.  
		 */
		queue_enter_first(&pendingQ, activeCmd, commandBuf *, link);
		[self deactivateCmd:activeCmd];
		ddm_chip("reselect while trying to select target %d\n",
			activeCmd->scsiReq->target, 2,3,4,5);
		activeCmd = NULL;
		scState = SCS_DISCONNECTED;
		
		/*
		 * Go deal with reselect.
		 */
		[self fsmDisconnected];
	}
	else {
		ddm_err("fsmSelecting: Bogus select/reselect interrupt\n", 
			1,2,3,4,5);
		[self hwAbort:SR_IOST_HW 
			reason: "Bogus select/reselect interrupt"];
		return;
	}
	return;
}

/*
 * This one is illegal.
 */
- (void)fsmInitiator
{
	ddm_chip("fsmInitiator\n", 1,2,3,4,5);
	[self hwAbort:SR_IOST_HW reason:"Interrupt as Initiator"];
}

/*
 * We just did a SCMD_INIT_CMD_CMPLT command, hopefully all that's left is
 * to drop ACK. Command Complete message is handled in fscAcceptingMsg.
 */
- (void)fsmCompleting
{		
	ddm_chip("fsmCompleting\n", 1,2,3,4,5);
	ASSERT(activeCmd != NULL);
	if(saveIntrStatus & IS_DISCONNECT) {
		ddm_err("unexpected completing disconnect\n",
			1,2,3,4,5);
		return;
	}
	if(saveIntrStatus & IS_SUCCESSFUL_OP) {
		/*
		 * Got both status and msg in fifo; Ack is still true.
		 */
		if((READ_REG(currFifoState) & FS_FIFO_LEVEL_MASK) != 2) {
			/*
			 * This is pretty bogus - we expect a status and 
			 * msg in the fifo. 
		   	 */
			[self hwAbort:SR_IOST_HW reason:"InitComplete fifo"
				" level"];
			return;
		}
		activeCmd->scsiReq->scsiStatus = READ_REG(scsiFifo);
		currMsgInCnt = 1;
		currMsgIn[0] = READ_REG(scsiFifo);
		ddm_chip("fsmCompleting: status 0x%x msg 0x%x\n",
			activeCmd->scsiReq->scsiStatus, 
			currMsgIn[0], 3,4,5);
		if (saveStatus & SS_PARITYERROR) {
			ddm_err("fsmCompleting: parity error on msg in\n",
				1,2,3,4,5);
			[self hwAbort:SR_IOST_PARITY 
				reason:"Parity error on message in"];
			return;
		}
		scState = SCS_ACCEPTINGMSG;
		WRITE_REG(scsiCmd, SCMD_MSG_ACCEPTED);
		return;
	} else {
		/*
		 * Must have just got a status byte only. This is kind of
		 * weird, but let's try to handle it.
		 */
		ddm_err("fsmCompleting: status only on complete\n", 
			1,2,3,4,5);
		if((READ_REG(currFifoState) & FS_FIFO_LEVEL_MASK) != 1) {
			[self hwAbort:SR_IOST_HW 
				reason:"Bad Fifo level on Cmd Complete"];
			return;
		}
		activeCmd->scsiReq->scsiStatus = READ_REG(scsiFifo);
		if(saveStatus & SS_PARITYERROR) {
			ddm_err("fsmCompleting: parity error on status\n",
				1,2,3,4,5);
			[self hwAbort:SR_IOST_PARITY 
				reason:"Parity Error on Cmd Complete"];
			return;
		}
		
		/*
		 * Back to watching phase changes. Why the target isn't in 
		 * message in we have yet to find out.
		 */
		scState = SCS_INITIATOR;
	}
}

/*
 * DMA Complete.
 */
- (void)fsmDMAing
{
	u_int bytesMoved;
	
	ddm_chip("fsmDMAing\n", 1,2,3,4,5);
	ASSERT(activeCmd != NULL);
	
	bytesMoved = [self dmaTerminate];
	if(bytesMoved > activeCmd->currentByteCount) {
		ddm_err("fsmDMAing: DMA transfer count exceeeded\n",
			1,2,3,4,5);
		ddm_err("  expected %d, moved %d\n", 
			activeCmd->currentByteCount, bytesMoved, 3,4,5);
		bytesMoved = activeCmd->currentByteCount;
	}
	((char *)activeCmd->currentPtr) += bytesMoved;
	activeCmd->currentByteCount     -= bytesMoved; 
	if(saveStatus & SS_PARITYERROR) {
		ddm_err("fsmDMAing: SCSI Data Parity Error\n", 1,2,3,4,5);
		[self hwAbort:SR_IOST_PARITY reason:"SCSI Data Parity Error"];
		return;
	}
	/*
	 * Back to watching phase changes.
         */
	scState = SCS_INITIATOR;
}

/*
 * Just completed the SCMD_TRANSFER_INFO operation for message in. ACK is
 * still true. Stash the current message byte in currMsgIn[] and proceed to
 * fsmAcceptingMsg after a SCMD_MSG_ACCEPTED.
 */
- (void)fsmGettingMsg
{
	BOOL	setAtn = NO;
	
	ASSERT((activeCmd != NULL) || reselPending);
	if(saveIntrStatus & IS_DISCONNECT) {
		ddm_chip("fsmGettingMsg: message In Disconnect\n", 1,2,3,4,5);
		/*
		 * This error is handled on return...
		 */
		return;
	}
	if((READ_REG(currFifoState) & FS_FIFO_LEVEL_MASK) != 1) {
		ddm_chip("Message In fifo error\n", 1,2,3,4,5);
		[self hwAbort:SR_IOST_HW reason:"Message In fifo error"];
		return;
	}

	currMsgIn[currMsgInCnt++] = READ_REG(scsiFifo);
	if(currMsgInCnt > AMD_MSG_SIZE) {
		[self hwAbort:SR_IOST_BV 
			reason:"Too Many Message bytes received"];
	}
	if(saveStatus & SS_PARITYERROR) {
		ddm_err("fsmGettingMsg: parity error on Message In\n", 
			1,2,3,4,5);
		[self hwAbort:SR_IOST_PARITY 
			reason:"parity error on Message In"];
		return;
	}
	ddm_chip("fsmGettingMsg: currMsgIn[%d] = 0x%x (%s)\n", currMsgInCnt-1,
		currMsgIn[currMsgInCnt-1],
		IOFindNameForValue(currMsgIn[currMsgInCnt-1], 
			scsiMsgValues),	4,5);
			
	/*
	 * Handle special cases. 
	 */
	 
	/*
	 * 1. If this is the last byte of an unsolicited sync negotiation, 
	 *    we have to assert ATN right now. The message is actually
	 *    fully parsed, and a response SDTR message created, in
	 *    fsmAcceptingMsg.
	 *
	 *    This parsing is pretty crude; if we come up with other special 
	 *    cases, we might rewrite this or come up with some state variables
	 *    to help us.
	 */
	if((currMsgInCnt >= MSG_SDTR_LENGTH) && (SDTR_State == SNS_NONE)) {
	
		int start = currMsgInCnt - MSG_SDTR_LENGTH;
		
		if((currMsgIn[start] == MSG_EXTENDED) &&
		   (currMsgIn[start+1] == (MSG_SDTR_LENGTH - 2)) &&
		   (currMsgIn[start+2] == MSG_SDTR)) {
		   	ddm_chip("UNSOLICITED SDTR IN; setting ATN\n",
				 1,2,3,4,5);
			WRITE_REG(scsiCmd, SCMD_SET_ATN);
			setAtn = YES;
			SDTR_State = SNS_TARGET_INIT;
		}
	}

	/*
	 * 2. If this was a message reject, it's possible that an extended
	 *    message out was prematurely aborted, with ATN still true.
	 *    Clear it so we don't do another (needless) message out.
	 *    Avoid this, of course, if we set ATN in this method for 
	 *    any reason.
	 */
	if((currMsgIn[currMsgInCnt - 1] == MSG_MSGREJECT) && 
	   (currMsgOutCnt > 1) &&
	   !setAtn) {
		ddm_chip("fsmGettingMsg: Message Reject; clearing ATN\n",
			1,2,3,4,5);
		WRITE_REG(scsiCmd, SCMD_CLR_ATN);
	}
	
	/*
	 * No need to clear FIFO; its depth was one on entry, and we read
	 * the byte. Note that clearing FIFO after the SCS_ACCEPTINGMSG
	 * might disturb possible sync data in transfer.
	 */
	WRITE_REG(scsiCmd, SCMD_MSG_ACCEPTED);
	scState = SCS_ACCEPTINGMSG;

}

/*
 * Just finished a message in; Ack is false. If phase is still
 * message in, we're in the midst of an extended message or additional
 * message bytes on reselect. Otherwise, message in is complete;
 * process currMsgIn[].
 */
- (void)fsmAcceptingMsg
{
	unsigned char 	phase = saveStatus & SS_PHASEMASK;
	unsigned 	index;
	perTargetData	*perTargetPtr;
	
	ddm_chip("fsmAcceptingMsg: phase %s\n", 
		IOFindNameForValue(phase, scsiPhaseValues), 2,3,4,5);
	if((phase == PHASE_MSGIN) && !(saveIntrStatus & IS_DISCONNECT)) {
	
		/*
		 * More message bytes to follow.
		 * We have to qualify with !IS_DISCONNECT to cover the 
		 * case of some targets (like the Exabyte tape drive)
		 * which bogusly keep CD, IO, and MSG asserted after
		 * they drop BSY upon command complete.
		 */
		WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
		WRITE_REG(scsiCmd, SCMD_TRANSFER_INFO);
		scState = SCS_GETTINGMSG; 
		return;
	}
	
	/*
	 * Message in complete. Handle message(s) in currMsgIn[].
	 */
	if(reselPending) {
	
		/*
		 * We're expecting either:
		 * -- no messages, or 
		 * -- queue tag and/or Save Pointers.
		 */
		unsigned char tag = QUEUE_TAG_NONTAGGED;
		
		ASSERT(activeCmd == NULL);
		for(index=0; index<currMsgInCnt; index++) {
		    switch(currMsgIn[index]) {
			case MSG_RESTOREPTRS:
			    ddm_chip("unnecessary restore pointers\n",
				    1,2,3,4,5);
			    continue;	/* to next message */
			    
			case MSG_SIMPLE_QUEUE_TAG:
			    if(index == (currMsgInCnt-1)) {
				[self hwAbort: SR_IOST_BV
				    reason:"Queue tag message, no tag"];
				return;
			    }
			    tag = currMsgIn[++index];
			    break;
			    
			/*
			 * handle others here...?
			 */
			default:
			    IOLog("AMD53C974: bad msg (0x%x) after identify\n",
				    currMsgIn[index]);
			    [self hwAbort: SR_IOST_BV reason:NULL];
			    return;
		    }
		}
			
		if([self reselect:reselTarget 
		        	lun:reselLun 
				queueTag:tag] == YES) {
			/*
			 * Found a disconnected commandBuf to reconnect.
			 *
			 * IDENTIFY msg implies restore ptrs.
			 */
			reselPending = 0;
			ASSERT(activeCmd != NULL);
			activeCmd->currentPtr = activeCmd->savedPtr;
			activeCmd->currentByteCount =  
				activeCmd->savedByteCount;
			scState = SCS_INITIATOR;
			return;
		}
		else {
			IOLog("AMD53C974: Illegal reselect (target %d lun "
				"%d tag %d)\n", reselTarget, reselLun, tag);
			[self hwAbort: SR_IOST_BV 
				reason:NULL];
			return;
		}
	}	/* reselect pending */
	
	/*
	 * Handle all other messages.
	 */
	ASSERT(activeCmd != NULL);
	perTargetPtr = &perTarget[activeCmd->scsiReq->target];
	for(index=0; index<currMsgInCnt; index++) {
	    switch(currMsgIn[index]) {
		case MSG_CMDCMPLT:
		    /*
		     * Bus really should be free; we came here from 
		     * fsmCompleting.
		     */
		    if(!(saveIntrStatus & IS_DISCONNECT)) {
			    ddm_err("fsmAcceptingMsg: Command Complete"
				    " but no Disconnect\n", 1,2,3,4,5);
			    [self hwAbort:SR_IOST_BV 
				    reason:"No Disconnect On Command"
					    " Complete"];
			    return;
		    }
		    
		    /*
		     * TA DA!
		     */
		    scState = SCS_DISCONNECTED;
		    activeCmd->scsiReq->driverStatus = SR_IOST_GOOD;
		    [self ioComplete:activeCmd];
		    activeCmd = NULL;
		    return;
		
		case MSG_DISCONNECT:
		    /*
		     * This could be in fsmGettingMsg, where we could 
		     * handle it gracefully by doing a MSGREJ, but this
		     * is such a bogus error that we'll just reset the
		     * offender.
		     */
		    if(!activeCmd->discEnable) {
			    ddm_chip("***Illegal Disconnect attempt\n",
				    1,2,3,4,5);
			    IOLog("AMD53C974: Illegal disconnect attempt"
				    " on target %d\n",
				    activeCmd->scsiReq->target);
			    [self hwAbort:SR_IOST_BV
				    reason:NULL];
			    return;
		    }
		    scState = SCS_DISCONNECTED;
		    [self disconnect];
		    return;

		case MSG_SAVEPTRS:
		    activeCmd->savedPtr = activeCmd->currentPtr;
		    activeCmd->savedByteCount = 
			    activeCmd->currentByteCount;
		    break;
		    
		case MSG_RESTOREPTRS:
		    activeCmd->currentPtr = activeCmd->savedPtr;
		    activeCmd->currentByteCount = 
			    activeCmd->savedByteCount;
		    break;
			    
		case MSG_MSGREJECT:
		    /*
		     * look at last message sent; may have to 
		     * disable sync or cmd queue mode for this target.
		     * This assumes that we don't send SDTR and queue tag
		     * in the same message.
		     */
		    ddm_chip("fsmAcceptingMsg: MESSAGE REJECT RECEIVED "
			    "from target %d\n", 
			    activeCmd->scsiReq->target, 2,3,4,5);
		    if(currMsgOutCnt == 0) {
			    /*
			     * Huh? We haven't sent a message recently...
			     */
			    [self hwAbort:SR_IOST_BV
				    reason:"Unexpected Message Reject"];
			    return;
		    }
		    switch(currMsgOut[0]) {
			case MSG_SIMPLE_QUEUE_TAG:	
			    [self disableMode:AM_CmdQueue];
			    break;
			case MSG_EXTENDED:
			    /*
			     * Only one we ever send is sync negotiation..
			     */
			    if(currMsgOut[2] == MSG_SDTR) {
			        [self disableMode:AM_Sync];
				SDTR_State = SNS_NONE;
				break;
			    }
			    else {
				[self hwAbort:SR_IOST_INT
				    reason:"Currupted Message Buffer"];
				return;
			    }
			default:
			    IOLog("AMD53C974: %s Message Rejected\n",
				    IOFindNameForValue(currMsgOut[0], 
				    scsiMsgValues));
			    /* oh well... */
			    break;
		    }
		    
		    /*
		     * In any case, we're definitely thru with the 
		     * outbound message buffer.
		     */
		    currMsgOutCnt = 0;
		    break;
		    
		case MSG_LNKCMDCMPLT:
		case MSG_LNKCMDCMPLTFLAG:
		    /*
		     * This should never happen, because hwStart trashes
		     * commands with the LINK bit on.
		     */
		    [self hwAbort:SR_IOST_BV reason:"Linked command"];
		    return;
		    
		case MSG_EXTENDED:
		    /*
		     * The only valid one is sync negotiation....
		     */
		    switch(currMsgIn[index+2]) {
			case MSG_SDTR:
			    if(currMsgIn[index+1] != (MSG_SDTR_LENGTH-2)) {
				[self hwAbort:SR_IOST_BV 
				    reason:"Bad Extended Msg Length"];
				return;
			    }
			    switch(SDTR_State) {
			        case SNS_HOST_INIT:
				    /* 
				     * Just completed SDTR that we initiated.
				     */
				    if([self parseSDTR:&currMsgIn[index]] 
				    		== NO) {
					[self hwAbort:SR_IOST_HW 
					    reason:"Bad SDTR Parameters"];
					return;
				    }
				   
				   /*
				    * Successful SDTR. 
				    */
				    ddm_chip("host-init SDTR COMPLETE\n", 
					1,2,3,4,5);
				    SDTR_State = SNS_NONE;
				    break;
				
				case SNS_TARGET_INIT:
				    /*
				     * Target-initiated negotiaited. This
				     * was detected in fsmGettingMsg, where
				     * we set ATN true.
				     * Cons up a response and prime the message 
				     * out state machine to send it.
				     */
				    [self createSDTR : currMsgOut
					    inboundMsg : &currMsgIn[index]];
				    currMsgOutCnt = MSG_SDTR_LENGTH;
				    msgOutState = MOS_WAITING;
				    break;
				   
				default:
				    IOPanic("AMD53C974: Bad SDTR_State");
			    }
			    
			    /*
			     * Skip over the rest of this message; index
			     * should point to the last byte of this message.
			     */
			    index += (MSG_SDTR_LENGTH - 1);
			    break;
    
			default:
			    IOLog("AMD53C974: Unexpected Extended Message "
				    "(0x%x) Received\n",
				    currMsgIn[index+2]);
			    [self hwAbort:SR_IOST_BV reason:NULL];
			    return;
		    }	
		    break;
		    		
		default:
		    /*
		     * all others are unacceptable. 
		     */
		    IOLog("AMD53C974: Illegal message (0x%x)\n", 
			    currMsgIn[index]);
		    [self messageOut:MSG_MSGREJECT];
	    } 
	} /* for index */
	
	/*
	 * Default case for 'break' from above switch - back to following 
	 * phase changes.
	 */
	scState = SCS_INITIATOR;
}

/*
 * Just completed the SCMD_TRANSFER_INFO operation for message out. 
 */
- (void)fsmSendingMsg
{
	ddm_chip("fsmSendingMsg\n", 1,2,3,4,5);
	ASSERT(activeCmd != NULL);
	scState = SCS_INITIATOR;
	if(SDTR_State == SNS_TARGET_INIT) {
		/*
		 * If the message we just sent was a SDTR, we've just 
		 * completed a target-initiated SDTR sequence. 
		 * Note this assumes that an outbound SDTR in this 
		 * situation is the only message in currMsgOut[].
		 * This will have to change if we send a queue tag and
		 * SDTR in the sqame message.
		 */
		if((currMsgOutCnt == MSG_SDTR_LENGTH) &&
		   (currMsgOut[0] == MSG_EXTENDED) &&
		   (currMsgOut[1] == (MSG_SDTR_LENGTH - 2)) &&
		   (currMsgOut[2] == MSG_SDTR)) {
			
		 	ddm_chip("fsmSendingMsg: target-init SDTR complete\n",
				1,2,3,4,5);
			if([self parseSDTR:currMsgOut] == NO) {
				/*
				 * Shouldn't fail; we generated this 
				 * message ourself...
				 */
				IOPanic("AMD53C974: SDTR Problem\n");
			}
			SDTR_State = SNS_NONE;
		}
	}
}


/*
 * Just completed the SCMD_TRANSFER_INFO operation for command.
 */
- (void)fsmSendingCmd
{
	ddm_chip("fsmSendingCmd\n", 1,2,3,4,5);
	ASSERT(activeCmd != NULL);
	scState = SCS_INITIATOR;
}

/*
 * Follow SCSI Phase change. Called while SCS_INITIATOR. 
 */
- (void)fsmPhaseChange
{
	int 		phase;
	char 		*cp;
	cdb_t 		*cdbp;
	int 		i;
	sc_status_t 	rtn;
	
	ddm_chip("fsmPhaseChange\n", 1,2,3,4,5);
	ASSERT(activeCmd != NULL);

	/*
	 * Advance msg out state machine -- SCSI spec says if
	 * we do a msg out phase and then see another phase
	 * we can assume msg was transmitted without error.
	 * However, if we're in msg in, we may have a message reject
	 * coming in, so we'll keep currMsgOut[] valid in that case.
	 *
	 * FIXME - one case which this would not cover is the queue tag
	 * message saved in currMsgOut[] during selection. We don't 
	 * go to MOS_SAWMSGOUT in that case. problem?
	 */
	phase = saveStatus & SS_PHASEMASK;
	if ((phase != PHASE_MSGOUT) &&
	    (phase != PHASE_MSGIN) &&
	    (msgOutState == MOS_SAWMSGOUT)) {
		msgOutState = MOS_NONE;
		currMsgOutCnt = 0;
	}

	/*
	 * If we just sent a host-initiated SDTR and the target went 
	 * to something other than phase in, we assume that the negotiation
	 * failed. This is in violation of the spec, but the Sony CDROM 
	 * does this.
	 */
	if((SDTR_State == SNS_HOST_INIT) && (phase != PHASE_MSGIN)) {
		ddm_chip("IMPLIED SNS_HOST_INIT Reject\n", 1,2,3,4,5);
   	        [self disableMode:AM_Sync];
		SDTR_State = SNS_NONE;
	}
	
	/* 
	 * make sure we start off with a clean slate.
	 *
	 * NO - this can disturb possible sync data in! We'll need to cover
	 * this in individual cases elsewhere...
	 */
	/* WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO); */
	ddm_chip("fsmPhaseChange:  phase = %s\n", 
		IOFindNameForValue(phase, scsiPhaseValues), 2,3,4,5);

	switch (phase) {

	    case PHASE_COMMAND:
	    	/*
		 * The normal case here is after a host-initiated SDTR
		 * sequence. 
		 */
		ddm_chip("fsmPhaseChange: command phase\n", 1,2,3,4,5);
		WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
		cdbp = &activeCmd->scsiReq->cdb;
		cp = (char *)cdbp;
		for(i=0; i<activeCmd->cdbLength; i++) {
			WRITE_REG(scsiFifo, *cp++);
		}

#if	0
		/*
		 * This causes extra bytes to sit around in the fifo
		 * if we go straight to data phase after this, and
		 * we can't clear the fifo at that time in case
		 * we're in sync data in...
		 */
		/*
		 * fill fifo to avoid spurious command phase for target
		 * chips that try to get max command length
		 */
		for (i = 12 - activeCmd->cdbLength; i > 0; i--)
			WRITE_REG(scsiFifo, 0);
#endif	0
		WRITE_REG(scsiCmd, SCMD_TRANSFER_INFO);
		scState = SCS_SENDINGCMD;
		break;

	    case PHASE_DATAOUT:	/* To Target from Initiator (write) */
		if(activeCmd->scsiReq->read) {
			[self hwAbort:SR_IOST_BV reason:"bad i/o direction"];
			break;
		}
		if(rtn = [self dmaStart]) {
			[self hwAbort:rtn 
				reason: IOFindNameForValue(rtn, 
					IOScStatusStrings)];
			break;
		}
		break;
		
	    case PHASE_DATAIN:	/* From Target to Initiator (read) */
		if(!activeCmd->scsiReq->read) {
			[self hwAbort:SR_IOST_BV reason:"bad i/o direction"];
			break;
		}
		if(rtn = [self dmaStart]) {
			[self hwAbort:rtn 
				reason: IOFindNameForValue(rtn, 
					IOScStatusStrings)];
			break;
		}
		break;
	
	    case PHASE_STATUS:	/* Status from Target to Initiator */
		/*
		 * fsmCompleting will collect the STATUS byte
		 * (and hopefully a MSG) from the fifo when this
		 * completes.
		 */
		scState = SCS_COMPLETING;
		currMsgInCnt = 0;
		WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
		WRITE_REG(scsiCmd, SCMD_INIT_CMD_CMPLT);
		break;
		
	    case PHASE_MSGIN:	/* Message from Target to Initiator */
		scState = SCS_GETTINGMSG;
		currMsgInCnt = 0;
		WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
		WRITE_REG(scsiCmd, SCMD_TRANSFER_INFO);
		break;
		
	    case PHASE_MSGOUT:	/* Message from Initiator to Target */
	        WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
		if(msgOutState == MOS_WAITING) {
			int i;
			
			ASSERT(currMsgOutCnt != 0);
			for(i=0; i<currMsgOutCnt; i++) {
				ddm_chip("msg out: writing 0x%x\n",
					currMsgOut[i], 2,3,4,5);
				WRITE_REG(scsiFifo, currMsgOut[i]);
			}
			msgOutState = MOS_SAWMSGOUT;
			if(SDTR_State == SNS_HOST_INIT_NEEDED) {
				/*
				 * sending SDTR message after select.
				 */
				ASSERT(currMsgOut[0] == MSG_EXTENDED);
				ASSERT(currMsgOut[2] == MSG_SDTR);
				ASSERT(currMsgOutCnt == MSG_SDTR_LENGTH);
				ddm_chip("going to SNS_HOST_INIT\n",
					1,2,3,4,5);
				SDTR_State = SNS_HOST_INIT;
			}
		} else {
			/*
			 * Target went to msg out and we don't have
			 * anything to send!  Just give it a nop.
			 */
			ddm_chip("msg out: sending MSG_NOP\n", 1,2,3,4,5);
			WRITE_REG(scsiFifo, MSG_NOP);
		}

		scState = SCS_SENDINGMSG;
		/* 
		 * ATN is automatically cleared when transfer info completes.
		 */
		WRITE_REG(scsiCmd, SCMD_TRANSFER_INFO);
		break;
		
	    default:
	    	[self hwAbort:SR_IOST_HW reason:"Bad SCSI phase"];
		break;
	}
}

/*
 * Set up to send single-byte message. 
 */ 
- (void) messageOut : (u_char)msg
{
	ddm_chip("messageOut (0x%x)\n", msg, 2,3,4,5);
	currMsgOut[0] = msg;
	currMsgOutCnt = 1;
	msgOutState = MOS_WAITING;
	WRITE_REG(scsiCmd, SCMD_SET_ATN);
}


/*
 * Load syncPeriod, syncOffset for activeCmd per perTarget values.
 */
- (void)targetContext : (unsigned) target
{
	perTargetData *perTargetPtr;
	
	perTargetPtr = &perTarget[target];
	if(!syncModeEnable || 
	   perTargetPtr->syncDisable ||
	   (perTargetPtr->syncXferOffset == 0)) {
		/*
		 * Easy case, async.
		 */
		WRITE_REG(syncOffset, 0);
		ddm_chip("targetContext(%d): ASYNC\n", target, 2,3,4,5);
		#ifdef	DEBUG
		syncOffsetShadow = 0;
		#endif	DEBUG
	}
	else {
		unsigned char periodReg;
		unsigned char offsetReg;
		
		periodReg = nsPeriodToSyncPeriodReg(
			perTargetPtr->syncXferPeriod,
			fastModeEnable, scsiClockRate);
		WRITE_REG(syncPeriod, periodReg);
		
		/*
		 * FIXME - might eventually want to use non-default
		 * values for RAD and RAA...
		 */
		offsetReg = (perTargetPtr->syncXferOffset |
			SOR_RAD_DEFAULT | SOR_RAA_DEFAULT);
		WRITE_REG(syncOffset, offsetReg);
		ddm_chip("targetContext(%d): period 0x%x offset %d\n",
			target, periodReg, offsetReg, 4,5);
		#ifdef	DEBUG
		syncOffsetShadow = offsetReg;
		syncPeriodShadow = periodReg;
		#endif	DEBUG
	}
}

/*
 * Parse and validate 5-byte SDTR message. If valid, save in perTarget 
 * and in hardware. Returns YES if valid, else NO.
 * 
 * Specified message buffer could be from either currMsgIn[] or 
 * currMsgOut[].
 */
- (BOOL)parseSDTR    : (unsigned char *)sdtrMessage
{
	unsigned nsPeriod;
	unsigned char fastClock;
	unsigned minPeriod;
	perTargetData *perTargetPtr;
	
	ASSERT(activeCmd != NULL);
	perTargetPtr = &perTarget[activeCmd->scsiReq->target];
	
	if(sdtrMessage[0] != MSG_EXTENDED) {
		goto Bad;
	}
	if(sdtrMessage[1] != (MSG_SDTR_LENGTH - 2)) {
		goto Bad;
	}
	if(sdtrMessage[2] != MSG_SDTR) {
		goto Bad;
	}
	
	/*
	 * period
	 */
	nsPeriod = SDTR_TO_NS_PERIOD(sdtrMessage[3]);
	fastClock = READ_REG(control3) & CR3_FAST_CLOCK;
	if(fastClock && fastModeEnable) {
		minPeriod = MIN_PERIOD_FASTCLK_FASTSCSI;
	}
	else {
		minPeriod = MIN_PERIOD_NORM;
	}
	if(nsPeriod < minPeriod) {
		goto Bad;
	}
	perTargetPtr->syncXferPeriod = nsPeriod;
	
	/*
	 * Offset
	 */
	if(sdtrMessage[4] > AMD_MAX_SYNC_OFFSET) {
		goto Bad;
	}
	perTargetPtr->syncXferOffset = sdtrMessage[4];
	
	/*
	 * Success.
	 */
	perTargetPtr->syncDisable = 0;
	perTargetPtr->syncNegotNeeded = 0;
	[self targetContext:activeCmd->scsiReq->target];
	
	ddm_chip("parseSDTR SUCCESS: %02x %02x %02x %02x %02x\n",
		sdtrMessage[0], sdtrMessage[1], sdtrMessage[2], 
		sdtrMessage[3], sdtrMessage[4]);
	ddm_chip("   period %d offset %d\n", perTargetPtr->syncXferPeriod,
		perTargetPtr->syncXferOffset, 3,4,5);
	return YES;
	
Bad:
	ddm_chip("parseSDTR FAIL: 02%x %02x %02x %02x %02x\n",
		sdtrMessage[0], sdtrMessage[1], sdtrMessage[2], 
		sdtrMessage[3], sdtrMessage[4]);
	return NO;
}

/*
 * Cons up a SDTR message appropriate for both our hardware and a possible
 * target-generated SDTR message. If inboundMsg is NULL, we just use
 * the parameters we want.
 */
- (void)createSDTR		: (unsigned char *)outboundMsg	// required
		     inboundMsg : (unsigned char *)inboundMsg
{
	unsigned 	desiredNsPeriod;
	unsigned 	inboundNsPeriod;
	unsigned 	offset = AMD_MAX_SYNC_OFFSET;
	unsigned char 	fastClock;
	
	outboundMsg[0] = MSG_EXTENDED;
	outboundMsg[1] = MSG_SDTR_LENGTH - 2;
	outboundMsg[2] = MSG_SDTR;
	
	/*
	 * period
	 */
	fastClock = READ_REG(control3) & CR3_FAST_CLOCK;
	if(fastClock && fastModeEnable) {
		desiredNsPeriod = MIN_PERIOD_FASTCLK_FASTSCSI;
	}
	else {
		desiredNsPeriod = MIN_PERIOD_NORM;
	}
	if(inboundMsg) {
		inboundNsPeriod = SDTR_TO_NS_PERIOD(inboundMsg[3]);
	}
	else {
		inboundNsPeriod = desiredNsPeriod;
	}
	if(inboundNsPeriod > desiredNsPeriod) {
		/*
		 * Target is slower than us
		 */
		desiredNsPeriod = inboundNsPeriod;
	}
	outboundMsg[3] = NS_PERIOD_TO_SDTR(desiredNsPeriod);
	
	/*
	 * Offset
	 */
	if(inboundMsg) {
		offset = inboundMsg[4];
		if(offset > AMD_MAX_SYNC_OFFSET) {
			/* 
			 * target's buffer smaller than ours
			 */
			offset = AMD_MAX_SYNC_OFFSET;
		}
	}
	outboundMsg[4] = offset;
	
	ddm_chip("createSDTR: %02x %02x %02x %02x %02x\n",
		outboundMsg[0], outboundMsg[1], outboundMsg[2], 
		outboundMsg[3], outboundMsg[4]);
	ddm_chip("   period %d   offset %d\n", desiredNsPeriod, offset, 3,4,5);
}

/*
 * Disable specified mode for activeCmd's target. If mode is currently 
 * enabled, we'll log a message to the console.
 */
- (void)disableMode : (AMD_Mode)mode
{
	int target;
	perTargetData *perTargetPtr;
	const char *modeStr = NULL;
	
	ASSERT(activeCmd != NULL);
	target = activeCmd->scsiReq->target;
	perTargetPtr = &perTarget[target];
	switch(mode) {
	    case AM_Sync:
	    	if(perTargetPtr->syncDisable == 0) {
			perTargetPtr->syncDisable = 1;
			modeStr = "Synchronous Transfer Mode";
		}
		[self targetContext:activeCmd->scsiReq->target];
		break;
	    case AM_CmdQueue:
	        if(perTargetPtr->cmdQueueDisable == 0) {
			perTargetPtr->cmdQueueDisable = 1;
			modeStr = "Command Queueing";
		}
		break;
	}
	ddm_chip("DISABLING %s for target %d\n", modeStr, target, 3,4,5);
	if(modeStr) {
		IOLog("AMD53C974: DISABLING %s for target %d\n", 
			modeStr, target);
	}
}

@end	/* AMD_SCSI(ChipPrivate) */
