patch-2.1.88 linux/drivers/acorn/scsi/fas216.c

Next file: linux/drivers/acorn/scsi/fas216.h
Previous file: linux/drivers/acorn/scsi/ecoscsi.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.87/linux/drivers/acorn/scsi/fas216.c linux/drivers/acorn/scsi/fas216.c
@@ -0,0 +1,1575 @@
+/*
+ * linux/arch/arm/drivers/scsi/fas216.c
+ *
+ * Copyright (C) 1997 Russell King
+ *
+ * Based in information in qlogicfas.c by Tom Zerucha, Michael Griffith, and
+ * other sources.
+ *
+ * This is a generic driver.  To use it, have a look at cumana_2.c.  You
+ * should define your own structure that overlays FAS216_Info, eg:
+ * struct my_host_data {
+ *    FAS216_Info info;
+ *    ... my host specific data ...
+ * };
+ *
+ * Changelog:
+ *  30-08-1997	RMK	Created
+ *  14-09-1997	RMK	Started disconnect support
+ *  08-02-1998	RMK	Corrected real DMA support
+ *  15-02-1998	RMK	Started sync xfer support
+ */
+
+#include <linux/module.h>
+#include <linux/blk.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/proc_fs.h>
+#include <linux/unistd.h>
+#include <linux/stat.h>
+
+#include <asm/delay.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/ecard.h>
+
+#define FAS216_C
+
+#include "scsi.h"
+#include "hosts.h"
+#include "fas216.h"
+
+#define VER_MAJOR	0
+#define VER_MINOR	0
+#define VER_PATCH	2
+
+#undef NO_DISCONNECTS
+#undef DEBUG_CONNECT
+#undef DEBUG_BUSSERVICE
+#undef DEBUG_FUNCTIONDONE
+#undef DEBUG_MESSAGES
+
+static char *fas216_bus_phase (int stat)
+{
+	static char *phases[] = {
+		"DATA OUT", "DATA IN",
+		"COMMAND", "STATUS",
+		"MISC OUT", "MISC IN",
+		"MESG OUT", "MESG IN"
+	};
+
+	return phases[stat & STAT_BUSMASK];
+}
+
+static char fas216_target (FAS216_Info *info)
+{
+	if (info->SCpnt)
+		return '0' + info->SCpnt->target;
+	else
+		return 'H';
+}
+
+static void fas216_done (FAS216_Info *info, unsigned int result);
+
+/* Function: int fas216_clockrate (unsigned int clock)
+ * Purpose : calculate correct value to be written into clock conversion
+ *	     factor register.
+ * Params  : clock - clock speed in MHz
+ * Returns : CLKF_ value
+ */
+static int fas216_clockrate (int clock)
+{
+	if (clock <= 10 || clock > 40) {
+		printk(KERN_CRIT
+		       "fas216: invalid clock rate: check your driver!\n");
+		clock = -1;
+	} else
+		clock = ((clock - 1) / 5 + 1) & 7;
+
+	return clock;
+}
+
+/* Function: int fas216_syncperiod(FAS216_Info *info, int ns)
+ * Purpose : Calculate value to be loaded into the STP register
+ *           for a given period in ns
+ * Params  : info - state structure for interface connected to device
+ *         : ns   - period in ns (between subsequent bytes)
+ * Returns : Value suitable for REG_STP
+ */
+static int fas216_syncperiod(FAS216_Info *info, int ns)
+{
+	int value = (info->ifcfg.clockrate * ns) / 1000;
+
+	if (value < 4)
+		value = 4;
+	else if value > 35)
+		value = 35;
+
+	return value & 31;
+}
+
+/* Function: void fas216_updateptrs (FAS216_Info *info, int bytes_transferred)
+ * Purpose : update data pointers after transfer suspended/paused
+ * Params  : info              - interface's local pointer to update
+ *           bytes_transferred - number of bytes transferred
+ */
+static void
+fas216_updateptrs (FAS216_Info *info, int bytes_transferred)
+{
+	unsigned char *ptr = info->scsi.SCp.ptr;
+	unsigned int residual = info->scsi.SCp.this_residual;
+
+	info->SCpnt->request_bufflen -= bytes_transferred;
+
+	while (residual <= bytes_transferred && bytes_transferred) {
+		/* We have used up this buffer */
+		bytes_transferred -= residual;
+		if (info->scsi.SCp.buffers_residual) {
+			info->scsi.SCp.buffer++;
+			info->scsi.SCp.buffers_residual--;
+			ptr = (unsigned char *)info->scsi.SCp.buffer->address;
+			residual = info->scsi.SCp.buffer->length;
+		} else {
+			ptr = NULL;
+			residual = 0;
+		}
+	}
+
+	residual -= bytes_transferred;
+	ptr += bytes_transferred;
+
+	info->scsi.SCp.ptr = ptr;
+	info->scsi.SCp.this_residual = residual;
+}
+
+/* Function: void fas216_pio (FAS216_Info *info, fasdmadir_t direction)
+ * Purpose : transfer data off of/on to card using programmed IO
+ * Params  : info      - interface to transfer data to/from
+ *           direction - direction to transfer data (DMA_OUT/DMA_IN)
+ * Notes   : this is incredibly slow
+ */
+static void
+fas216_pio (FAS216_Info *info, fasdmadir_t direction)
+{
+	unsigned int length = info->scsi.SCp.this_residual;
+	char *ptr = info->scsi.SCp.ptr;
+
+	if (direction == DMA_OUT) {
+	    	while (length > 0) {
+			if ((inb(REG_CFIS(info)) & CFIS_CF) < 8) {
+				outb(*ptr++, REG_FF(info));
+				length -= 1;
+			} else if (inb(REG_STAT(info)) & STAT_INT)
+				break;
+		}
+	} else {
+	    	while (length > 0) {
+			if ((inb(REG_CFIS(info)) & CFIS_CF) != 0) {
+				*ptr++ = inb(REG_FF(info));
+				length -= 1;
+			} else if (inb(REG_STAT(info)) & STAT_INT)
+				break;
+		}
+	}
+
+	if (length == 0) {
+		if (info->scsi.SCp.buffers_residual) {
+			info->scsi.SCp.buffer++;
+			info->scsi.SCp.buffers_residual--;
+			ptr = (unsigned char *)info->scsi.SCp.buffer->address;
+			length = info->scsi.SCp.buffer->length;
+		} else {
+			ptr = NULL;
+			length = 0;
+		}
+	}
+
+	info->scsi.SCp.ptr = ptr;
+	info->scsi.SCp.this_residual = length;
+}
+
+/* Function: void fas216_starttransfer(FAS216_Info *info,
+ *				       fasdmadir_t direction)
+ * Purpose : Start a DMA/PIO transfer off of/on to card
+ * Params  : info      - interface from which device disconnected from
+ *           direction - transfer direction (DMA_OUT/DMA_IN)
+ */
+static void
+fas216_starttransfer(FAS216_Info *info, fasdmadir_t direction)
+{
+	fasdmatype_t dmatype;
+
+	info->scsi.phase = (direction == DMA_OUT) ?
+				PHASE_DATAOUT : PHASE_DATAIN;
+
+	if (info->dma.transfer_type == fasdma_real_block ||
+	    info->dma.transfer_type == fasdma_real_all) {
+		unsigned long total, residual;
+
+		if (info->dma.transfer_type == fasdma_real_block)
+			total = info->scsi.SCp.this_residual;
+		else
+			total = info->SCpnt->request_bufflen;
+
+		residual = (inb(REG_CFIS(info)) & CFIS_CF) +
+			    inb(REG_CTCL(info)) +
+			    (inb(REG_CTCM(info)) << 8) +
+			    (inb(REG_CTCH(info)) << 16);
+		fas216_updateptrs (info, total - residual);
+		info->dma.transfer_type = fasdma_none;
+	}
+
+	if (!info->scsi.SCp.ptr) {
+		printk ("scsi%d.%c: null buffer passed to "
+			"fas216_starttransfer\n", info->host->host_no,
+			fas216_target (info));
+		return;
+	}
+
+	dmatype = fasdma_none;
+	if (info->dma.setup)
+		dmatype = info->dma.setup(info->host, &info->scsi.SCp,
+					  direction);
+
+	info->dma.transfer_type = dmatype;
+
+	switch (dmatype) {
+	case fasdma_none:
+		outb(info->scsi.SCp.this_residual, REG_STCL(info));
+		outb(info->scsi.SCp.this_residual >> 8, REG_STCM(info));
+		outb(info->scsi.SCp.this_residual >> 16, REG_STCH(info));
+		outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info));
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		fas216_pio (info, direction);
+		break;
+
+	case fasdma_pseudo: {
+		int transferred;
+
+		outb(info->scsi.SCp.this_residual, REG_STCL(info));
+		outb(info->scsi.SCp.this_residual >> 8, REG_STCM(info));
+		outb(info->scsi.SCp.this_residual >> 16, REG_STCH(info));
+		outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info));
+		outb(CMD_TRANSFERINFO | CMD_WITHDMA, REG_CMD(info));
+
+		transferred =
+			info->dma.pseudo(info->host, &info->scsi.SCp,
+					 direction, info->SCpnt->transfersize);
+
+		fas216_updateptrs (info, transferred);
+		}
+		break;
+
+	case fasdma_real_block:
+		outb(info->scsi.SCp.this_residual, REG_STCL(info));
+		outb(info->scsi.SCp.this_residual >> 8, REG_STCM(info));
+		outb(info->scsi.SCp.this_residual >> 16, REG_STCH(info));
+		outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info));
+
+		outb(CMD_TRANSFERINFO | CMD_WITHDMA, REG_CMD(info));
+		break;
+
+	case fasdma_real_all:
+		outb(info->SCpnt->request_bufflen, REG_STCL(info));
+		outb(info->SCpnt->request_bufflen >> 8, REG_STCM(info));
+		outb(info->SCpnt->request_bufflen >> 16, REG_STCH(info));
+		outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info));
+
+		outb(CMD_TRANSFERINFO | CMD_WITHDMA, REG_CMD(info));
+		break;
+	}
+}
+
+/* Function: void fas216_stoptransfer (FAS216_Info *info)
+ * Purpose : Stop a DMA transfer onto / off of the card
+ * Params  : info      - interface from which device disconnected from
+ */
+static void
+fas216_stoptransfer (FAS216_Info *info)
+{
+	if (info->dma.transfer_type == fasdma_real_block ||
+	    info->dma.transfer_type == fasdma_real_all) {
+		unsigned long total, residual;
+
+		if (info->dma.stop)
+			info->dma.stop (info->host, &info->scsi.SCp);
+
+		if (info->dma.transfer_type == fasdma_real_block)
+			total = info->scsi.SCp.this_residual;
+		else
+			total = info->SCpnt->request_bufflen;
+
+		residual = (inb(REG_CFIS(info)) & CFIS_CF) +
+			    inb(REG_CTCL(info)) +
+			    (inb(REG_CTCM(info)) << 8) +
+			    (inb(REG_CTCH(info)) << 16);
+		fas216_updateptrs (info, total - residual);
+
+		info->dma.transfer_type = fasdma_none;
+	}
+}
+
+/* Function: void fas216_disconnected_intr (FAS216_Info *info)
+ * Purpose : handle device disconnection
+ * Params  : info - interface from which device disconnected from
+ */
+static void
+fas216_disconnect_intr (FAS216_Info *info)
+{
+#ifdef DEBUG_CONNECT
+	printk("scsi%d.%c: disconnect phase=%02X\n", info->host->host_no,
+		fas216_target (info), info->scsi.phase);
+#endif
+	msgqueue_flush (&info->scsi.msgs);
+
+	switch (info->scsi.phase) {
+	case PHASE_SELECTION:			/* while selecting - no target		*/
+		fas216_done (info, DID_NO_CONNECT);
+		break;
+
+	case PHASE_DISCONNECT:			/* message in - disconnecting		*/
+		outb(CMD_ENABLESEL, REG_CMD(info));
+		info->scsi.disconnectable = 1;
+		info->scsi.reconnected.tag = 0;
+		info->scsi.phase = PHASE_IDLE;
+		info->stats.disconnects += 1;
+		break;
+
+	case PHASE_DONE:			/* at end of command - complete		*/
+		fas216_done (info, DID_OK);
+		break;
+
+	case PHASE_AFTERMSGOUT:			/* message out - possible ABORT message	*/
+		if (info->scsi.last_message == ABORT) {
+			info->scsi.aborting = 0;
+			fas216_done (info, DID_ABORT);
+			break;
+		}
+
+	default:				/* huh?					*/
+		printk(KERN_ERR "scsi%d.%c: unexpected disconnect in phase %d\n",
+			info->host->host_no, fas216_target (info), info->scsi.phase);
+		fas216_stoptransfer(info);
+		fas216_done (info, DID_ERROR);
+		break;
+	}
+}
+
+/* Function: void fas216_reselected_intr (FAS216_Info *info)
+ * Purpose : Start reconnection of a device
+ * Params  : info - interface which was reselected
+ */
+static void
+fas216_reselected_intr (FAS216_Info *info)
+{
+    unsigned char target, identify_msg, ok;
+
+	if (info->scsi.phase == PHASE_SELECTION && info->SCpnt) {
+		Scsi_Cmnd *SCpnt = info->SCpnt;
+
+		info->origSCpnt = SCpnt;
+		info->SCpnt = NULL;
+
+		if (info->device[SCpnt->target].negstate == syncneg_sent)
+			info->device[SCpnt->target].negstate = syncneg_start;
+	}
+
+#ifdef DEBUG_CONNECT
+	printk("scsi%d.%c: reconnect phase=%02X\n", info->host->host_no,
+		fas216_target (info), info->scsi.phase);
+#endif
+
+    msgqueue_flush (&info->scsi.msgs);
+
+    if ((inb(REG_CFIS(info)) & CFIS_CF) != 2) {
+	printk (KERN_ERR "scsi%d.H: incorrect number of bytes after reselect\n",
+		info->host->host_no);
+	outb(CMD_SETATN, REG_CMD(info));
+	msgqueue_addmsg (&info->scsi.msgs, 1, MESSAGE_REJECT);
+	info->scsi.phase = PHASE_MSGOUT;
+	outb(CMD_MSGACCEPTED, REG_CMD(info));
+	return;
+    }
+
+    target = inb(REG_FF(info));
+    identify_msg = inb(REG_FF(info));
+
+    ok = 1;
+    if (!(target & (1 << info->host->this_id))) {
+	printk (KERN_ERR "scsi%d.H: invalid host id on reselect\n", info->host->host_no);
+	ok = 0;
+    }
+
+    if (!(identify_msg & 0x80)) {
+	printk (KERN_ERR "scsi%d.H: no IDENTIFY message on reselect, got msg %02X\n",
+		info->host->host_no, identify_msg);
+	ok = 0;
+    }
+
+    if (!ok) {
+	/*
+	 * Something went wrong - abort the command on
+	 * the target.  Should this be INITIATOR_ERROR ?
+	 */
+	outb(CMD_SETATN, REG_CMD(info));
+	msgqueue_addmsg (&info->scsi.msgs, 1, ABORT);
+	info->scsi.phase = PHASE_MSGOUT;
+	outb(CMD_MSGACCEPTED, REG_CMD(info));
+	return;
+    }
+
+    target &= ~(1 << info->host->this_id);
+    switch (target) {
+    case   1:  target = 0; break;
+    case   2:  target = 1; break;
+    case   4:  target = 2; break;
+    case   8:  target = 3; break;
+    case  16:  target = 4; break;
+    case  32:  target = 5; break;
+    case  64:  target = 6; break;
+    case 128:  target = 7; break;
+    default:   target = info->host->this_id; break;
+    }
+
+    identify_msg &= 7;
+    info->scsi.reconnected.target = target;
+    info->scsi.reconnected.lun    = identify_msg;
+    info->scsi.reconnected.tag    = 0;
+
+    ok = 0;
+    if (info->scsi.disconnectable && info->SCpnt &&
+	  info->SCpnt->target == target && info->SCpnt->lun == identify_msg)
+	ok = 1;
+
+    if (!ok && queue_probetgtlun (&info->queues.disconnected, target, identify_msg))
+	ok = 1;
+
+    if (ok) {
+	info->scsi.phase = PHASE_RECONNECTED;
+	outb(target, REG_SDID(info));
+    } else {
+	/*
+	 * Our command structure not found - abort the command on the target
+	 * Should this be INITIATOR_ERROR ?
+	 */
+	outb(CMD_SETATN, REG_CMD(info));
+	msgqueue_addmsg (&info->scsi.msgs, 1, ABORT);
+	info->scsi.phase = PHASE_MSGOUT;
+    }
+    outb(CMD_MSGACCEPTED, REG_CMD(info));
+}
+
+/* Function: void fas216_finish_reconnect (FAS216_Info *info)
+ * Purpose : finish reconnection sequence for device
+ * Params  : info - interface which caused function done interrupt
+ */
+static void
+fas216_finish_reconnect (FAS216_Info *info)
+{
+#ifdef DEBUG_CONNECT
+printk ("Connected: %1X %1X %02X, reconnected: %1X %1X %02X\n",
+	info->SCpnt->target, info->SCpnt->lun, info->SCpnt->tag,
+	info->scsi.reconnected.target, info->scsi.reconnected.lun,
+	info->scsi.reconnected.tag);
+#endif
+
+    if (info->scsi.disconnectable && info->SCpnt) {
+	info->scsi.disconnectable = 0;
+	if (info->SCpnt->target == info->scsi.reconnected.target &&
+	    info->SCpnt->lun    == info->scsi.reconnected.lun &&
+	    info->SCpnt->tag    == info->scsi.reconnected.tag) {
+#ifdef DEBUG_CONNECT
+	    printk ("scsi%d.%c: reconnected",
+		    info->host->host_no, fas216_target (info));
+#endif
+	} else {
+	    queue_add_cmd_tail (&info->queues.disconnected, info->SCpnt);
+#ifdef DEBUG_CONNECT
+	    printk ("scsi%d.%c: had to move command to disconnected queue\n",
+			info->host->host_no, fas216_target (info));
+#endif
+	    info->SCpnt = NULL;
+	}
+    }
+    if (!info->SCpnt) {
+	info->SCpnt = queue_remove_tgtluntag (&info->queues.disconnected,
+				info->scsi.reconnected.target,
+				info->scsi.reconnected.lun,
+				info->scsi.reconnected.tag);
+#ifdef DEBUG_CONNECT
+	printk ("scsi%d.%c: had to get command",
+		info->host->host_no, fas216_target (info));
+#endif
+    }
+    if (!info->SCpnt) {
+	outb(CMD_SETATN, REG_CMD(info));
+	msgqueue_addmsg (&info->scsi.msgs, 1, ABORT);
+	info->scsi.phase = PHASE_MSGOUT;
+	info->scsi.aborting = 1;
+    } else {
+	/*
+	 * Restore data pointer from SAVED data pointer
+	 */
+	info->scsi.SCp = info->SCpnt->SCp;
+#ifdef DEBUG_CONNECT
+	printk (", data pointers: [%p, %X]",
+		info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+#endif
+    }
+#ifdef DEBUG_CONNECT
+    printk ("\n");
+#endif
+}
+
+/* Function: void fas216_message (FAS216_Info *info)
+ * Purpose : handle a function done interrupt from FAS216 chip
+ * Params  : info - interface which caused function done interrupt
+ */
+static void fas216_message (FAS216_Info *info)
+{
+    unsigned char message[16];
+    unsigned int msglen = 1;
+
+    message[0] = inb(REG_FF(info));
+
+    if (message[0] == EXTENDED_MESSAGE) {
+	message[1] = inb(REG_FF(info));
+
+	for (msglen = 2; msglen < message[1]; msglen++)
+	    message[msglen] = inb(REG_FF(info));
+    }
+
+#ifdef DEBUG_MESSAGES
+    {
+	int i;
+
+	printk ("scsi%d.%c: message in: ",
+		info->host->host_no, fas216_target (info));
+	for (i = 0; i < msglen; i++)
+	    printk ("%02X ", message[i]);
+	printk ("\n");
+    }
+#endif
+    if (info->scsi.phase == PHASE_RECONNECTED) {
+	if (message[0] == SIMPLE_QUEUE_TAG)
+	    info->scsi.reconnected.tag = message[1];
+	fas216_finish_reconnect (info);
+	info->scsi.phase = PHASE_MSGIN;
+    }	
+
+    switch (message[0]) {
+    case COMMAND_COMPLETE:
+	printk ("fas216: command complete with no status in MESSAGE_IN?\n");
+	break;
+
+    case SAVE_POINTERS:
+	/*
+	 * Save current data pointer to SAVED data pointer
+	 */
+	info->SCpnt->SCp = info->scsi.SCp;
+#if defined (DEBUG_MESSAGES) || defined (DEBUG_CONNECT)
+	printk ("scsi%d.%c: save data pointers: [%p, %X]\n",
+		info->host->host_no, fas216_target (info),
+		info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+#endif
+	break;
+
+    case RESTORE_POINTERS:
+	/*
+	 * Restore current data pointer from SAVED data pointer
+	 */
+	info->scsi.SCp = info->SCpnt->SCp;
+#if defined (DEBUG_MESSAGES) || defined (DEBUG_CONNECT)
+	printk ("scsi%d.%c: restore data pointers: [%p, %X]\n",
+		info->host->host_no, fas216_target (info),
+		info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+#endif
+	break;
+
+    case DISCONNECT:
+	info->scsi.phase = PHASE_DISCONNECT;
+	break;
+
+    case MESSAGE_REJECT:
+	printk ("scsi%d.%c: reject, last message %04X\n",
+		info->host->host_no, fas216_target (info),
+		info->scsi.last_message);
+	break;
+
+    case SIMPLE_QUEUE_TAG:
+	/* handled above */
+	printk ("scsi%d.%c: reconnect queue tag %02X\n",
+		info->host->host_no, fas216_target (info),
+		message[1]);
+	break;
+
+    case EXTENDED_MESSAGE:
+	switch (message[2]) {
+	case EXTENDED_SDTR:	/* Sync transfer negociation request/reply */
+
+	case EXTENDED_WDTR:	/* Wide transfer negociation request/reply */
+		/* We don't do wide transfers - reject message */
+	default:
+		printk("scsi%d.%c: unrecognised extended message %02X, rejecting\n",
+			info->host->host_no, fas216_target (info),
+			message[2]);
+		msgqueue_flush (&info->scsi.msgs);
+		outb(CMD_SETATN, REG_CMD(info));
+		msgqueue_addmsg (&info->scsi.msgs, 1, MESSAGE_REJECT);
+		info->scsi.phase = PHASE_MSGOUT;
+		break;
+	}
+	break;
+
+    default:
+	printk ("scsi%d.%c: unrecognised message %02X, rejecting\n",
+		info->host->host_no, fas216_target (info),
+		message[0]);
+	msgqueue_flush (&info->scsi.msgs);
+	outb(CMD_SETATN, REG_CMD(info));
+	msgqueue_addmsg (&info->scsi.msgs, 1, MESSAGE_REJECT);
+	info->scsi.phase = PHASE_MSGOUT;
+	break;
+    }
+    outb(CMD_MSGACCEPTED, REG_CMD(info));
+}
+
+/* Function: void fas216_busservice_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr)
+ * Purpose : handle a bus service interrupt from FAS216 chip
+ * Params  : info - interface which caused bus service interrupt
+ *           stat - Status register contents
+ *           ssr  - SCSI Status register contents
+ */
+static void fas216_busservice_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr)
+{
+    int i;
+#ifdef DEBUG_BUSSERVICE
+    printk("scsi%d.%c: bus service: stat=%02X ssr=%02X phase=%02X\n",
+	   info->host->host_no, fas216_target(info), stat, ssr, info->scsi.phase);
+#endif
+    switch (ssr & IS_BITS) {
+    case IS_COMPLETE:			/* last action completed		*/
+	outb(CMD_NOP, REG_CMD(info));
+
+	switch (info->scsi.phase) {
+	case PHASE_SELECTION:		/* while selecting - selected target	*/
+	    switch (stat & STAT_BUSMASK) {
+	    case STAT_DATAOUT:	/* data out phase			*/
+		fas216_starttransfer (info, DMA_OUT);
+		break;
+
+	    case STAT_DATAIN:		/* data in phase			*/
+		fas216_starttransfer (info, DMA_IN);
+		break;
+
+	    case STAT_STATUS:		/* status phase				*/
+		info->scsi.phase = PHASE_STATUS;
+		outb(CMD_INITCMDCOMPLETE, REG_CMD(info));
+		break;
+
+	    case STAT_MESGIN:		/* message in phase			*/
+		info->scsi.phase = PHASE_MSGIN;
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		break;
+
+	    default:			/* other				*/
+		printk ("scsi%d.%c: bus phase %s after connect?\n",
+			info->host->host_no, fas216_target (info),
+			fas216_bus_phase (stat));
+		break;
+	    }
+	    break;
+
+	case PHASE_DATAIN:		/* while transfering data in		*/
+	    switch (stat & STAT_BUSMASK) {
+	    case STAT_DATAIN:		/* continue data in phase		*/
+		fas216_starttransfer (info, DMA_IN);
+		break;
+
+	    case STAT_STATUS:
+		fas216_stoptransfer(info);
+		info->scsi.phase = PHASE_STATUS;
+		outb(CMD_INITCMDCOMPLETE, REG_CMD(info));
+		break;
+
+	    case STAT_MESGIN:		/* message in phase			*/
+		fas216_stoptransfer(info);
+		info->scsi.phase = PHASE_MSGIN;
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		break;
+
+	    default:
+		printk ("scsi%d.%c: bus phase %s after data in?\n",
+			info->host->host_no, fas216_target (info),
+			fas216_bus_phase (stat));
+	    }
+	    break;
+
+	case PHASE_DATAOUT:		/* while transfering data out		*/
+	    switch (stat & STAT_BUSMASK) {
+	    case STAT_DATAOUT:
+		fas216_starttransfer (info, DMA_OUT);
+		break;
+
+	    case STAT_STATUS:
+		fas216_stoptransfer(info);
+		info->scsi.phase = PHASE_STATUS;
+		outb(CMD_FLUSHFIFO, REG_CMD(info));
+		outb(CMD_INITCMDCOMPLETE, REG_CMD(info));
+		break;
+
+	    case STAT_MESGIN:		/* message in phase			*/
+		fas216_stoptransfer(info);
+		info->scsi.phase = PHASE_MSGIN;
+		outb(CMD_FLUSHFIFO, REG_CMD(info));
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		break;
+
+	    default:
+		printk ("scsi%d.%c: bus phase %s after data out?\n",
+			info->host->host_no, fas216_target (info),
+			fas216_bus_phase (stat));
+	    }
+	    break;
+
+	case PHASE_RECONNECTED:		/* newly reconnected device		*/
+	    /*
+	     * Command reconnected - if MESGIN, get message - it may be
+	     * the tag.  If not, get command out of the disconnected queue
+	     */
+	    switch (stat & STAT_BUSMASK) {
+	    case STAT_MESGIN:
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		break;
+
+	    case STAT_STATUS:
+		fas216_finish_reconnect (info);
+		info->scsi.phase = PHASE_STATUS;
+		outb(CMD_INITCMDCOMPLETE, REG_CMD(info));
+		break;
+
+	    case STAT_DATAOUT:		/* data out phase			*/
+		fas216_finish_reconnect (info);
+		fas216_starttransfer (info, DMA_OUT);
+		break;
+
+	    case STAT_DATAIN:		/* data in phase			*/
+		fas216_finish_reconnect (info);
+		fas216_starttransfer (info, DMA_IN);
+		break;
+
+	    default:
+		printk ("scsi%d.%c: bus phase %s after reconnect?\n",
+			info->host->host_no, fas216_target (info),
+			fas216_bus_phase (stat));
+	    }
+	    break;
+
+	case PHASE_MSGIN:
+	    switch (stat & STAT_BUSMASK) {
+	    case STAT_MESGIN:
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		break;
+
+	    default:
+		printk ("scsi%d.%c: bus phase %s after message in?\n",
+			info->host->host_no, fas216_target (info),
+			fas216_bus_phase (stat));
+	    }
+	    break;
+
+	case PHASE_MSGOUT:
+	    if ((stat & STAT_BUSMASK) != STAT_MESGOUT) {
+		printk ("scsi%d.%c: didn't manage MESSAGE OUT phase\n",
+			info->host->host_no, fas216_target (info));
+	    } else {
+		unsigned int msglen;
+
+		msglen = msgqueue_msglength (&info->scsi.msgs);
+
+		outb(CMD_FLUSHFIFO, REG_CMD(info));
+
+		if (msglen == 0)
+		    outb(NOP, REG_FF(info));
+		else {
+		    char *msg;
+
+		    while ((msg = msgqueue_getnextmsg (&info->scsi.msgs, &msglen)) != NULL) {
+			for (i = 0; i < msglen; i++)
+			    outb(msg[i], REG_FF(info));
+		    }
+		}
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		info->scsi.phase = PHASE_AFTERMSGOUT;
+	    }
+	    break;
+
+	case PHASE_AFTERMSGOUT:
+	    switch (stat & STAT_BUSMASK) {
+	    case STAT_MESGIN:
+		info->scsi.phase = PHASE_MSGIN;
+		outb(CMD_TRANSFERINFO, REG_CMD(info));
+		break;
+
+	    default:
+		printk ("scsi%d.%c: bus phase %s after message out\n",
+			info->host->host_no, fas216_target (info),
+			fas216_bus_phase (stat));
+	    }
+	    break;
+
+	case PHASE_DISCONNECT:
+	    printk ("scsi%d.%c: disconnect message received, but bus service %s?\n",
+		    info->host->host_no, fas216_target (info),
+		    fas216_bus_phase (stat));
+	    outb(CMD_SETATN, REG_CMD(info));
+	    msgqueue_addmsg (&info->scsi.msgs, 1, ABORT);
+	    info->scsi.phase = PHASE_MSGOUT;
+	    info->scsi.aborting = 1;
+	    outb(CMD_TRANSFERINFO, REG_CMD(info));
+	    break;
+
+	default:
+	    printk ("scsi%d.%c: internal phase %d for bus service?"
+		    "  What do I do with this?\n",
+		    info->host->host_no, fas216_target (info),
+		    info->scsi.phase);
+	}
+	break;
+
+    default:
+	printk ("scsi%d.%c: bus service at step %d?\n",
+		info->host->host_no, fas216_target (info),
+		ssr & IS_BITS);
+    }
+}
+
+/* Function: void fas216_funcdone_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr)
+ * Purpose : handle a function done interrupt from FAS216 chip
+ * Params  : info - interface which caused function done interrupt
+ *           stat - Status register contents
+ *           ssr  - SCSI Status register contents
+ */
+static void fas216_funcdone_intr (FAS216_Info *info, unsigned int stat, unsigned int ssr)
+{
+    int status, message;
+#ifdef DEBUG_FUNCTIONDONE
+    printk("scsi%d.%c: function done: stat=%X ssr=%X phase=%02X\n",
+	   info->host->host_no, fas216_target(info), stat, ssr, info->scsi.phase);
+#endif
+    switch (info->scsi.phase) {
+    case PHASE_STATUS:			/* status phase - read status and msg	*/
+	status = inb(REG_FF(info));
+	message = inb(REG_FF(info));
+	info->scsi.SCp.Message = message;
+	info->scsi.SCp.Status = status;
+	info->scsi.phase = PHASE_DONE;
+	outb(CMD_MSGACCEPTED, REG_CMD(info));
+	break;
+
+    case PHASE_IDLE:			/* reselected?				*/
+    case PHASE_MSGIN:			/* message in phase			*/
+    case PHASE_RECONNECTED:		/* reconnected command			*/
+	if ((stat & STAT_BUSMASK) == STAT_MESGIN) {
+	    fas216_message (info);
+	    break;
+	}
+
+    default:
+	printk ("scsi%d.%c: internal phase %d for function done?"
+		"  What do I do with this?\n",
+		info->host->host_no, fas216_target (info),
+		info->scsi.phase);
+    }
+}
+
+/* Function: void fas216_intr (struct Scsi_Host *instance)
+ * Purpose : handle interrupts from the interface to progress a command
+ * Params  : instance - interface to service
+ */
+void fas216_intr (struct Scsi_Host *instance)
+{
+    FAS216_Info *info = (FAS216_Info *)instance->hostdata;
+    unsigned char isr, ssr, stat;
+
+    stat = inb(REG_STAT(info));
+    ssr = inb(REG_IS(info));
+    isr = inb(REG_INST(info));
+
+    if (isr & INST_BUSRESET)
+	printk ("scsi%d.H: fas216: bus reset detected\n", instance->host_no);
+    else if (isr & INST_ILLEGALCMD)
+	printk (KERN_CRIT "scsi%d.H: illegal command given\n", instance->host_no);
+    else if (isr & INST_DISCONNECT)
+	fas216_disconnect_intr (info);
+    else if (isr & INST_RESELECTED)		/* reselected			*/
+	fas216_reselected_intr (info);
+    else if (isr & INST_BUSSERVICE)		/* bus service request		*/
+	fas216_busservice_intr (info, stat, ssr);
+    else if (isr & INST_FUNCDONE)		/* function done		*/
+	fas216_funcdone_intr (info, stat, ssr);
+    else
+    	printk ("scsi%d.%c: unknown interrupt received:"
+		" phase %d isr %02X ssr %02X stat %02X\n",
+		instance->host_no, fas216_target (info),
+		info->scsi.phase, isr, ssr, stat);
+}
+
+/* Function: void fas216_kick (FAS216_Info *info)
+ * Purpose : kick a command to the interface - interface should be idle
+ * Params  : info - our host interface to kick
+ * Notes   : Interrupts are always disabled!
+ */
+static void fas216_kick (FAS216_Info *info)
+{
+	Scsi_Cmnd *SCpnt;
+	int i, msglen, from_queue = 0;
+
+	if (info->origSCpnt) {
+		SCpnt = info->origSCpnt;
+		info->origSCpnt = NULL;
+	} else
+		SCpnt = NULL;
+
+	/* retrieve next command */
+	if (!SCpnt) {
+		SCpnt = queue_remove_exclude(&info->queues.issue, info->busyluns);
+		from_queue = 1;
+	}
+
+	if (!SCpnt) /* no command pending - just exit */
+		return;
+
+	if (info->scsi.disconnectable && info->SCpnt) {
+		queue_add_cmd_tail (&info->queues.disconnected, info->SCpnt);
+		info->scsi.disconnectable = 0;
+		info->SCpnt = NULL;
+		printk("scsi%d.%c: moved command to disconnected queue\n",
+			info->host->host_no, fas216_target (info));
+	}
+
+	/*
+	 * tagged queuing - allocate a new tag to this command
+	 */
+	if (SCpnt->device->tagged_queue && SCpnt->cmnd[0] != REQUEST_SENSE) {
+		SCpnt->device->current_tag += 1;
+		if (SCpnt->device->current_tag == 0)
+		    SCpnt->device->current_tag = 1;
+		SCpnt->tag = SCpnt->device->current_tag;
+	}
+
+	/*
+	 * claim host busy
+	 */
+	info->scsi.phase = PHASE_SELECTION;
+	info->SCpnt = SCpnt;
+	info->scsi.SCp = SCpnt->SCp;
+	info->dma.transfer_type = fasdma_none;
+
+#ifdef DEBUG_CONNECT
+	printk("scsi%d.%c: starting cmd %02X",
+		info->host->host_no, '0' + SCpnt->target,
+		SCpnt->cmnd[0]);
+#endif
+
+	if (from_queue) {
+#ifdef SCSI2_TAG
+		if (SCpnt->device->tagged_queue && SCpnt->cmnd[0] != REQUEST_SENSE) {
+		    SCpnt->device->current_tag += 1;
+			if (SCpnt->device->current_tag == 0)
+			    SCpnt->device->current_tag = 1;
+				SCpnt->tag = SCpnt->device->current_tag;
+		} else
+#endif
+			set_bit(SCpnt->target * 8 + SCpnt->lun, info->busyluns);
+
+		info->stats.removes += 1;
+		switch (SCpnt->cmnd[0]) {
+		case WRITE_6:
+		case WRITE_10:
+		case WRITE_12:
+			info->stats.writes += 1;
+			break;
+		case READ_6:
+		case READ_10:
+		case READ_12:
+			info->stats.reads += 1;
+			break;
+		default:
+			info->stats.miscs += 1;
+			break;
+		}
+	}
+
+	/* build outgoing message bytes */
+	msgqueue_flush (&info->scsi.msgs);
+	if (info->device[SCpnt->target].disconnect_ok)
+		msgqueue_addmsg(&info->scsi.msgs, 1, IDENTIFY(1, SCpnt->lun));
+	else
+		msgqueue_addmsg(&info->scsi.msgs, 1, IDENTIFY(0, SCpnt->lun));
+
+	/* add tag message if required */
+	if (SCpnt->tag)
+		msgqueue_addmsg(&info->scsi.msgs, 2, SIMPLE_QUEUE_TAG, SCpnt->tag);
+
+	/* add synchronous negociation */
+	if (info->device[SCpnt->target].negstate == syncneg_start) {
+		info->device[SCpnt->target].negstate = syncneg_sent;
+		msgqueue_addmsg(&info->scsi.msgs, 5,
+				EXTENDED_MESSAGE, 3, EXTENDED_SDTR,
+				1000 / info->ifcfg.clockrate,
+				info->ifcfg.sync_max_depth);
+	}
+
+	/* following what the ESP driver says */
+	outb(0, REG_STCL(info));
+	outb(0, REG_STCM(info));
+	outb(0, REG_STCH(info));
+	outb(CMD_NOP | CMD_WITHDMA, REG_CMD(info));
+
+	/* flush FIFO */
+	outb(CMD_FLUSHFIFO, REG_CMD(info));
+
+	/* load bus-id and timeout */
+	outb(BUSID(SCpnt->target), REG_SDID(info));
+	outb(info->ifcfg.select_timeout, REG_STIM(info));
+
+	/* synchronous transfers */
+	outb(info->device[SCpnt->target].sof, REG_SOF(info));
+	outb(info->device[SCpnt->target].stp, REG_STP(info));
+
+	msglen = msgqueue_msglength (&info->scsi.msgs);
+
+	if (msglen == 1 || msglen == 3) {
+		char *msg;
+
+		/* load message bytes */
+		while ((msg = msgqueue_getnextmsg(&info->scsi.msgs, &msglen)) != NULL) {
+			for (i = 0; i < msglen; i++)
+				outb(msg[i], REG_FF(info));
+		}
+
+		/* load command */
+		for (i = 0; i < SCpnt->cmd_len; i++)
+			outb(SCpnt->cmnd[i], REG_FF(info));
+
+		if (msglen == 1)
+			outb(CMD_SELECTATN, REG_CMD(info));
+		else
+			outb(CMD_SELECTATN3, REG_CMD(info));
+	} else {
+		outb(CMD_SELECTATNSTOP, REG_CMD(info));
+	}
+
+#ifdef DEBUG_CONNECT
+	printk(", data pointers [%p, %X]\n",
+		info->scsi.SCp.ptr, info->scsi.SCp.this_residual);
+#endif
+	/* should now get either DISCONNECT or (FUNCTION DONE with BUS SERVICE) intr */
+}
+
+/* Function: void fas216_done (FAS216_Info *info, unsigned int result)
+ * Purpose : complete processing for command
+ * Params  : info   - interface that completed
+ *	     result - driver byte of result
+ */
+static void fas216_done (FAS216_Info *info, unsigned int result)
+{
+    Scsi_Cmnd *SCpnt = info->SCpnt;
+
+    if (info->scsi.aborting) {
+	printk ("scsi%d.%c: uncaught abort - returning DID_ABORT\n",
+		info->host->host_no, fas216_target (info));
+	result = DID_ABORT;
+	info->scsi.aborting = 0;
+    }
+
+    info->stats.fins += 1;
+
+    if (SCpnt) {
+    	info->scsi.phase = PHASE_IDLE;
+	info->SCpnt = NULL;
+
+	SCpnt->result = result << 16 | info->scsi.SCp.Message << 8 |
+				info->scsi.SCp.Status;
+
+	/*
+	 * In theory, this should not happen, but just in case it does.
+	 */
+	if (info->scsi.SCp.ptr && result == DID_OK) {
+	    switch (status_byte (SCpnt->result)) {
+	    case CHECK_CONDITION:
+	    case COMMAND_TERMINATED:
+	    case BUSY:
+	    case QUEUE_FULL:
+	    case RESERVATION_CONFLICT:
+		break;
+
+	    default:
+		printk (KERN_ERR "scsi%d.H: incomplete data transfer "
+			"detected: result=%08X command=",
+			info->host->host_no, SCpnt->result);
+		print_command (SCpnt->cmnd);
+	    }
+	}
+#ifdef DEBUG_CONNECT
+	printk ("scsi%d.%c: scsi command (%p) complete, result=%08X\n",
+		info->host->host_no, fas216_target (info),
+		SCpnt, SCpnt->result);
+#endif
+
+	if (!SCpnt->scsi_done)
+	    panic ("scsi%d.H: null scsi_done function in fas216_done", info->host->host_no);
+
+	clear_bit (SCpnt->target * 8 + SCpnt->lun, info->busyluns);
+
+	SCpnt->scsi_done (SCpnt);
+    } else
+	panic ("scsi%d.H: null command in fas216_done", info->host->host_no);
+
+    if (info->scsi.irq != NO_IRQ)
+	fas216_kick (info);
+}
+
+/* Function: int fas216_queue_command (Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
+ * Purpose : queue a command for adapter to process.
+ * Params  : SCpnt - Command to queue
+ *	     done  - done function to call once command is complete
+ * Returns : 0 - success, else error
+ */
+int fas216_queue_command (Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
+{
+	FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata;
+
+#ifdef DEBUG_CONNECT
+	printk("scsi%d.%c: received queuable command (%p) %02X\n",
+		SCpnt->host->host_no, '0' + SCpnt->target,
+		SCpnt, SCpnt->cmnd[0]);
+#endif
+
+	SCpnt->scsi_done = done;
+	SCpnt->host_scribble = NULL;
+	SCpnt->result = 0;
+	SCpnt->SCp.Message = 0;
+	SCpnt->SCp.Status = 0;
+
+	if (SCpnt->use_sg) {
+		unsigned long len = 0;
+		int buf;
+
+		SCpnt->SCp.buffer = (struct scatterlist *) SCpnt->buffer;
+		SCpnt->SCp.buffers_residual = SCpnt->use_sg - 1;
+		SCpnt->SCp.ptr = (char *) SCpnt->SCp.buffer->address;
+		SCpnt->SCp.this_residual = SCpnt->SCp.buffer->length;
+		/*
+		 * Calculate correct buffer length
+		 */
+		for (buf = 0; buf <= SCpnt->SCp.buffers_residual; buf++)
+			len += SCpnt->SCp.buffer[buf].length;
+		SCpnt->request_bufflen = len;
+	} else {
+		SCpnt->SCp.buffer = NULL;
+		SCpnt->SCp.buffers_residual = 0;
+		SCpnt->SCp.ptr = (unsigned char *)SCpnt->request_buffer;
+		SCpnt->SCp.this_residual = SCpnt->request_bufflen;
+	}
+
+	info->stats.queues += 1;
+	SCpnt->tag = 0;
+
+	if (info->scsi.irq != NO_IRQ) {
+		unsigned long flags;
+
+		/* add command into execute queue and let it complete under
+		 * the drivers interrupts.
+		 */
+		if (!queue_add_cmd_ordered (&info->queues.issue, SCpnt)) {
+			SCpnt->result = DID_ERROR << 16;
+			done (SCpnt);
+		}
+		save_flags_cli (flags);
+		if (!info->SCpnt || info->scsi.disconnectable)
+			fas216_kick (info);
+		restore_flags (flags);
+	} else {
+		/* no interrupts to rely on - we'll have to handle the
+		 * command ourselves.  For now, we give up.
+		 */
+		SCpnt->result = DID_ERROR << 16;
+		done (SCpnt);
+	}
+	return 0;
+}
+
+/* Function: void fas216_internal_done (Scsi_Cmnd *SCpnt)
+ * Purpose : trigger restart of a waiting thread in fas216_command
+ * Params  : SCpnt - Command to wake
+ */
+static void fas216_internal_done (Scsi_Cmnd *SCpnt)
+{
+	FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata;
+
+	info->internal_done = 1;
+}
+
+/* Function: int fas216_command (Scsi_Cmnd *SCpnt)
+ * Purpose : queue a command for adapter to process.
+ * Params  : SCpnt - Command to queue
+ * Returns : scsi result code
+ */
+int fas216_command (Scsi_Cmnd *SCpnt)
+{
+	FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata;
+	unsigned long flags;
+
+	info->internal_done = 0;
+	fas216_queue_command (SCpnt, fas216_internal_done);
+
+	/*
+	 * This wastes time, since we can't return until the command is
+	 * complete. We can't seep either since we may get re-entered!
+	 * However, we must re-enable interrupts, or else we'll be
+	 * waiting forever.
+	 */
+	save_flags (flags);
+	sti ();
+
+	while (!info->internal_done)
+		barrier ();
+
+	restore_flags (flags);
+
+	return SCpnt->result;
+}
+
+/* Prototype: void fas216_reportstatus(Scsi_Cmnd **SCpntp1,
+ *				       Scsi_Cmnd **SCpntp2, int result)
+ * Purpose  : pass a result to *SCpntp1, and check if *SCpntp1 = *SCpntp2
+ * Params   : SCpntp1 - pointer to command to return
+ *	      SCpntp2 - pointer to command to check
+ *	      result  - result to pass back to mid-level done function
+ * Returns  : *SCpntp2 = NULL if *SCpntp1 is the same command
+ *	      structure as *SCpntp2.
+ */
+static void fas216_reportstatus(Scsi_Cmnd **SCpntp1, Scsi_Cmnd **SCpntp2,
+				int result)
+{
+	Scsi_Cmnd *SCpnt = *SCpntp1;
+
+	if (SCpnt) {
+		*SCpntp1 = NULL;
+
+		SCpnt->result = result;
+		SCpnt->scsi_done (SCpnt);
+	}
+
+	if (SCpnt == *SCpntp2)
+		*SCpntp2 = NULL;
+}
+
+/* Function: int fas216_abort (Scsi_Cmnd *SCpnt)
+ * Purpose : abort a command if something horrible happens.
+ * Params  : SCpnt - Command that is believed to be causing a problem.
+ * Returns : one of SCSI_ABORT_ macros.
+ */
+int fas216_abort (Scsi_Cmnd *SCpnt)
+{
+	FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata;
+	int result = SCSI_ABORT_SNOOZE;
+
+	info->stats.aborts += 1;
+
+	printk(KERN_WARNING "scsi%d: fas216_abort: ", info->host->host_no);
+
+	do {
+		/* If command is waiting in the issue queue, then we can
+		 * simply remove the command and return abort status
+		 */
+		if (queue_removecmd (&info->queues.issue, SCpnt)) {
+			SCpnt->result = DID_ABORT << 16;
+			SCpnt->scsi_done (SCpnt);
+			printk ("command on issue queue");
+			result = SCSI_ABORT_SUCCESS;
+			break;
+		}
+
+		/* If the command is on the disconencted queue, we need to
+		 * reconnect to the device
+		 */
+		if (queue_cmdonqueue (&info->queues.disconnected, SCpnt))
+			printk ("command on disconnected queue");
+
+		/* If the command is connected, we need to flag that the
+		 * command needs to be aborted
+		 */
+		if (info->SCpnt == SCpnt)
+			printk ("command executing");
+
+		/* If the command is pending for execution, then again
+		 * this is simple - we remove it and report abort status
+		 */
+		if (info->origSCpnt == SCpnt) {
+			info->origSCpnt = NULL;
+			SCpnt->result = DID_ABORT << 16;
+			SCpnt->scsi_done (SCpnt);
+			printk ("command waiting for execution");
+			result = SCSI_ABORT_SUCCESS;
+			break;
+		}
+	} while (0);
+
+	printk ("\n");
+
+	return result;
+}
+
+/* Function: void fas216_reset_state(FAS216_Info *info)
+ * Purpose : Initialise driver internal state
+ * Params  : info - state to initialise
+ */
+static void fas216_reset_state(FAS216_Info *info)
+{
+	int i;
+
+	/*
+	 * Clear out all stale info in our state structure
+	 */
+	memset (info->busyluns, 0, sizeof (info->busyluns));
+	msgqueue_flush(&info->scsi.msgs);
+	info->scsi.reconnected.target = 0;
+	info->scsi.reconnected.lun = 0;
+	info->scsi.reconnected.tag = 0;
+	info->scsi.disconnectable = 0;
+	info->scsi.last_message = 0;
+	info->scsi.aborting = 0;
+	info->scsi.phase = PHASE_IDLE;
+
+	for (i = 0; i < 8; i++) {
+#ifndef NO_DISCONNECTS
+		info->device[i].disconnect_ok = 1;
+#else
+		info->device[i].disconnect_ok = 0;
+#endif
+		info->device[i].stp = fas216_syncperiod(info->ifcfg.asyncperiod);
+		info->device[i].sof = 0;
+#ifdef SCSI2SYNC
+		info->device[i].negstate = syncneg_start;
+#else
+		info->device[i].negstate = syncneg_complete;
+#endif
+	}
+}
+
+/* Function: void fas216_init_chip(FAS216_Info *info)
+ * Purpose : Initialise FAS216 state after reset
+ * Params  : info - state structure for interface
+ */
+static void fas216_init_chip(FAS216_Info *info)
+{
+	outb(fas216_clockrate(info->ifcfg.clockrate), REG_CLKF(info));
+	outb(info->scsi.cfg[0], REG_CNTL1(info));
+	outb(info->scsi.cfg[1], REG_CNTL2(info));
+	outb(info->scsi.cfg[2], REG_CNTL3(info));
+	outb(info->ifcfg.select_timeout, REG_STIM(info));
+	outb(0, REG_SOF(info));
+	outb(fas216_syncperiod(info->ifcfg.asyncperiod), REG_STP(info));
+	outb(info->scsi.cfg[0], REG_CNTL1(info));
+}
+
+/* Function: int fas216_reset (Scsi_Cmnd *SCpnt, unsigned int reset_flags)
+ * Purpose : resets the adapter if something horrible happens.
+ * Params  : SCpnt - Command that is believed to be causing a problem.
+ *	     reset_flags - flags indicating reset type that is believed
+ *           to be required.
+ * Returns : one of SCSI_RESET_ macros, or'd with the SCSI_RESET_*_RESET
+ *           macros.
+ */
+int fas216_reset (Scsi_Cmnd *SCpnt, unsigned int reset_flags)
+{
+	FAS216_Info *info = (FAS216_Info *)SCpnt->host->hostdata;
+	Scsi_Cmnd *SCptr;
+	int result = 0;
+
+	info->stats.resets += 1;
+
+	printk(KERN_WARNING "scsi%d: fas216_reset: ", info->host->host_no);
+
+	outb(info->scsi.cfg[3], REG_CNTL3(info));
+
+	fas216_stoptransfer(info);
+	fas216_reset_state(info);
+
+	if (reset_flags & SCSI_RESET_SUGGEST_HOST_RESET) {
+		outb(CMD_RESETCHIP, REG_CMD(info));
+		outb(CMD_NOP, REG_CMD(info));
+		result |= SCSI_RESET_HOST_RESET;
+	}
+
+	if (reset_flags & SCSI_RESET_SUGGEST_BUS_RESET) {
+	    	outb(CMD_RESETSCSI, REG_CMD(info));
+    		outb(CMD_NOP, REG_CMD(info));
+	    	result |= SCSI_RESET_BUS_RESET;
+	}
+
+	if (!(reset_flags &
+	      (SCSI_RESET_SUGGEST_BUS_RESET|SCSI_RESET_SUGGEST_HOST_RESET))) {
+		outb(CMD_RESETCHIP, REG_CMD(info));
+		outb(CMD_NOP, REG_CMD(info));
+		outb(CMD_RESETSCSI, REG_CMD(info));
+		result |= SCSI_RESET_HOST_RESET | SCSI_RESET_BUS_RESET;
+	}
+
+	if (result & SCSI_RESET_HOST_RESET)
+		fas216_init_chip(info);
+
+	/*
+	 * Signal all commands in progress have been reset
+	 */
+	fas216_reportstatus (&info->SCpnt, &SCpnt, DID_RESET << 16);
+
+	while ((SCptr = queue_remove (&info->queues.disconnected)) != NULL)
+		fas216_reportstatus (&SCptr, &SCpnt, DID_RESET << 16);
+
+	if (SCpnt) {
+		/*
+		 * Command not found on disconnected queue, nor currently
+		 * executing command - check pending commands
+		 */
+		if (info->origSCpnt == SCpnt)
+			info->origSCpnt = NULL;
+
+		queue_removecmd(&info->queues.issue, SCpnt);
+
+		SCpnt->result = DID_RESET << 16;
+		SCpnt->scsi_done (SCpnt);
+	}
+
+	printk ("\n");
+
+	return result | SCSI_RESET_SUCCESS;
+}
+
+/* Function: int fas216_init (struct Scsi_Host *instance)
+ * Purpose : initialise FAS/NCR/AMD SCSI ic.
+ * Params  : instance - a driver-specific filled-out structure
+ * Returns : 0 on success
+ */
+int fas216_init (struct Scsi_Host *instance)
+{
+	FAS216_Info *info = (FAS216_Info *)instance->hostdata;
+	unsigned long flags;
+	int target_jiffies;
+
+	info->host = instance;
+	info->scsi.cfg[0] = instance->this_id;
+	info->scsi.cfg[1] = CNTL2_ENF | CNTL2_S2FE;
+	info->scsi.cfg[2] = CNTL3_ADDIDCHK | CNTL3_G2CB | CNTL3_FASTSCSI | CNTL3_FASTCLK;
+	info->scsi.type = "unknown";
+	info->SCpnt = NULL;
+	fas216_reset_state(info);
+
+	memset (&info->stats, 0, sizeof (info->stats));
+
+	msgqueue_initialise (&info->scsi.msgs);
+
+	if (!queue_initialise (&info->queues.issue))
+		return 1;
+
+	if (!queue_initialise (&info->queues.disconnected)) {
+		queue_free (&info->queues.issue);
+		return 1;
+	}
+
+	outb(0, REG_CNTL3(info));
+	outb(CNTL2_S2FE, REG_CNTL2(info));
+
+	if ((inb(REG_CNTL2(info)) & (~0xe0)) != CNTL2_S2FE) {
+		info->scsi.type = "NCR53C90";
+	} else {
+		outb(0, REG_CNTL2(info));
+		outb(0, REG_CNTL3(info));
+		outb(5, REG_CNTL3(info));
+		if (inb(REG_CNTL3(info)) != 5) {
+			info->scsi.type = "NCR53C90A";
+		} else {
+			outb(0, REG_CNTL3(info));
+			info->scsi.type = "NCR53C9x";
+		}
+	}
+
+
+	outb(CNTL3_IDENABLE, REG_CNTL3(info));
+	outb(0, REG_CNTL3(info));
+
+	outb(CMD_RESETCHIP, REG_CMD(info));
+	outb(CMD_WITHDMA | CMD_NOP, REG_CMD(info));
+	outb(CNTL2_ENF, REG_CNTL2(info));
+	outb(CMD_RESETCHIP, REG_CMD(info));
+	switch (inb(REG1_ID(info))) {
+	case 12:
+		info->scsi.type = "Am53CF94";
+		break;
+	default:
+		break;
+	}
+
+	udelay (300);
+
+	/* now for the real initialisation */
+	fas216_init_chip(info);
+
+	outb(info->scsi.cfg[0] | CNTL1_DISR, REG_CNTL1(info));
+	outb(CMD_RESETSCSI, REG_CMD(info));
+
+	/* scsi standard says 250ms */
+	target_jiffies = jiffies + (25 * HZ) / 100;
+	save_flags (flags);
+	sti ();
+
+	while (jiffies < target_jiffies) barrier ();
+
+	restore_flags (flags);
+
+	outb(info->scsi.cfg[0], REG_CNTL1(info));
+	inb(REG_INST(info));
+
+	return 0;
+}
+
+/* Function: int fas216_release (struct Scsi_Host *instance)
+ * Purpose : release all resources and put everything to bed for
+ *           FAS/NCR/AMD SCSI ic.
+ * Params  : instance - a driver-specific filled-out structure
+ * Returns : 0 on success
+ */
+int fas216_release (struct Scsi_Host *instance)
+{
+	FAS216_Info *info = (FAS216_Info *)instance->hostdata;
+
+	outb(CMD_RESETCHIP, REG_CMD(info));
+	queue_free (&info->queues.disconnected);
+	queue_free (&info->queues.issue);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(fas216_init);
+EXPORT_SYMBOL(fas216_abort);
+EXPORT_SYMBOL(fas216_reset);
+EXPORT_SYMBOL(fas216_queue_command);
+EXPORT_SYMBOL(fas216_command);
+EXPORT_SYMBOL(fas216_intr);
+EXPORT_SYMBOL(fas216_release);
+
+#ifdef MODULE
+int init_module (void)
+{
+	return 0;
+}
+
+void cleanup_module (void)
+{
+}
+#endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov