patch-2.4.7 linux/drivers/scsi/pcmcia/nsp_cs.c

Next file: linux/drivers/scsi/pcmcia/nsp_cs.h
Previous file: linux/drivers/scsi/pcmcia/Makefile
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.6/linux/drivers/scsi/pcmcia/nsp_cs.c linux/drivers/scsi/pcmcia/nsp_cs.c
@@ -0,0 +1,1714 @@
+/*======================================================================
+
+    NinjaSCSI-3 / NinjaSCSI-32Bi PCMCIA SCSI hostadapter card driver
+      By: YOKOTA Hiroshi <yokota@netlab.is.tsukuba.ac.jp>
+
+    Ver.2.0   Support 32bit PIO mode
+    Ver.1.1.2 Fix for scatter list buffer exceeds
+    Ver.1.1   Support scatter list
+    Ver.0.1   Initial version
+
+    This software may be used and distributed according to the terms of
+    the GNU General Public License.
+
+======================================================================*/
+
+/***********************************************************************
+    This driver is for these PCcards.
+
+	I-O DATA PCSC-F	 (Workbit NinjaSCSI-3)
+			"WBT", "NinjaSCSI-3", "R1.0"
+	I-O DATA CBSC-II (Workbit NinjaSCSI-32Bi in 16bit mode)
+			"IO DATA", "CBSC16	 ", "1"
+
+***********************************************************************/
+
+/* $Id: nsp_cs.c,v 1.28 2001/02/15 02:56:32 elca Exp $ */
+
+#ifdef NSP_KERNEL_2_2
+#include <pcmcia/config.h>
+#include <pcmcia/k_compat.h>
+#endif
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/tqueue.h>
+#include <linux/interrupt.h>
+#include <linux/major.h>
+#include <linux/blk.h>
+#include <linux/stat.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <../drivers/scsi/scsi.h>
+#include <../drivers/scsi/hosts.h>
+#include <../drivers/scsi/sd.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_ioctl.h>
+
+#include <pcmcia/version.h>
+#include <pcmcia/cs_types.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include "nsp_cs.h"
+
+MODULE_AUTHOR("YOKOTA Hiroshi <yokota@netlab.is.tsukuba.ac.jp>");
+MODULE_DESCRIPTION("WorkBit NinjaSCSI-3 / NinjaSCSI-32Bi(16bit) PCMCIA SCSI host adapter module");
+MODULE_SUPPORTED_DEVICE("sd,sr,sg,st");
+
+#ifdef PCMCIA_DEBUG
+static int pc_debug = PCMCIA_DEBUG;
+MODULE_PARM(pc_debug, "i");
+MODULE_PARM_DESC(pc_debug, "set debug level");
+static char *version = "$Id: nsp_cs.c,v 1.28 2001/02/15 02:56:32 elca Exp $";
+#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args)
+#else
+#define DEBUG(n, args...) /* */
+#endif
+
+#include "nsp_io.h"
+
+/*====================================================================*/
+
+typedef struct scsi_info_t {
+	dev_link_t             link;
+	struct Scsi_Host      *host;
+	int	               ndev;
+	dev_node_t             node[8];
+	int                    stop;
+	struct bus_operations *bus;
+} scsi_info_t;
+
+static void nsp_release(u_long arg);
+static int nsp_event(event_t event, int priority,
+		     event_callback_args_t *args);
+static dev_link_t *nsp_attach(void);
+static void nsp_detach(dev_link_t *);
+static int nsp_detect(Scsi_Host_Template * );
+static const char * nsp_info(struct Scsi_Host *);
+static int nsp_queuecommand(Scsi_Cmnd *, void (* done)(Scsi_Cmnd *));
+static int nsp_abort(Scsi_Cmnd *);
+static int nsp_reset(Scsi_Cmnd *, unsigned int);
+
+
+/*----------------------------------------------------------------*/
+
+#if (KERNEL_VERSION(2,4,0) > LINUX_VERSION_CODE)
+#define PROC_SCSI_NSP PROC_SCSI_IBMMCA /* bad hack... */
+static struct proc_dir_entry proc_scsi_nsp = {
+	PROC_SCSI_NSP, 6, "nsp_cs",
+	S_IFDIR | S_IRUGO | S_IXUGO, 2
+};
+#endif
+
+/*====================================================================*/
+/* Parameters that can be set with 'insmod' */
+
+static unsigned int irq_mask = 0xffff;
+MODULE_PARM(irq_mask, "i");
+MODULE_PARM_DESC(irq_mask, "IRQ mask bits");
+
+static int irq_list[4] = { -1 };
+MODULE_PARM(irq_list, "1-4i");
+MODULE_PARM_DESC(irq_list, "IRQ number list");
+
+/*----------------------------------------------------------------*/
+/* driver state info, local to driver */
+static char nspinfo[100];     /* description */
+
+/* /usr/src/linux/drivers/scsi/hosts.h */
+static Scsi_Host_Template driver_template = {
+/*	next:			NULL,*/
+/*	proc_dir:		NULL,*/
+/*	proc_info:		NULL,*/
+	name:			"WorkBit NinjaSCSI-3/32Bi",
+	detect:			nsp_detect,
+/*	release:		NULL,*/
+	info:			nsp_info,
+/*	command:		NULL,*/
+	queuecommand:		nsp_queuecommand,
+/*	eh_strategy_handler:	nsp_eh_strategy,*/
+/*	eh_abort_handler:	nsp_eh_abort,*/
+/*	eh_device_reset_handler: nsp_eh_device_reset,*/
+	eh_bus_reset_handler:	nsp_eh_bus_reset,
+/*	eh_host_reset_handler:	nsp_eh_host_reset,*/
+	abort:			nsp_abort,
+	reset:			nsp_reset,
+/*	slave_attach:		NULL,*/
+/*	bios_param:		nsp_biosparam,*/
+	can_queue:		1,
+	this_id:		-1,
+	sg_tablesize:		SG_ALL,
+	cmd_per_lun:		1,
+/*	present:		0,*/
+/*	unchecked_isa_dma:	0,*/
+	use_clustering:		DISABLE_CLUSTERING,
+	use_new_eh_code:	0,
+/*	emulated:		0,*/
+};
+
+static dev_link_t *dev_list = NULL;
+static dev_info_t dev_info  = {"nsp_cs"};
+
+static nsp_hw_data nsp_data;
+
+/***********************************************************/
+
+static int nsp_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
+{
+#ifdef PCMCIA_DEBUG
+	//unsigned int host_id = SCpnt->host->this_id;
+	//unsigned int base    = SCpnt->host->io_port;
+	unsigned char target = SCpnt->target;
+#endif
+
+	DEBUG(0, __FUNCTION__ "() SCpnt=0x%p target=%d lun=%d buff=0x%p bufflen=%d use_sg=%d\n",
+	      SCpnt, target, SCpnt->lun, SCpnt->request_buffer, SCpnt->request_bufflen, SCpnt->use_sg);
+	//DEBUG(0, " before CurrentSC=0x%p\n", nsp_data.CurrentSC);
+
+	if(nsp_data.CurrentSC != NULL) {
+		printk(KERN_DEBUG " " __FUNCTION__ "() CurrentSC!=NULL this can't be happen\n");
+		nsp_data.CurrentSC = NULL;
+		SCpnt->result	   = DID_BAD_TARGET << 16;
+		done(SCpnt);
+		return FAILED;
+	}
+
+#ifdef PCMCIA_DEBUG
+	show_command(SCpnt);
+#endif
+
+	SCpnt->scsi_done	= done;
+	nsp_data.CurrentSC	= SCpnt;
+	RESID		        = SCpnt->request_bufflen;
+
+	SCpnt->SCp.Status	= -1;
+	SCpnt->SCp.Message	= -1;
+	SCpnt->SCp.have_data_in = IO_UNKNOWN;
+	SCpnt->SCp.sent_command = 0;
+	SCpnt->SCp.phase	= PH_UNDETERMINED;
+
+	/* setup scratch area
+	   SCp.ptr		: buffer pointer
+	   SCp.this_residual	: buffer length
+	   SCp.buffer		: next buffer
+	   SCp.buffers_residual : left buffers in list
+	   SCp.phase		: current state of the command */
+	if (SCpnt->use_sg) {
+		SCpnt->SCp.buffer	    = (struct scatterlist *) SCpnt->request_buffer;
+		SCpnt->SCp.ptr		    = SCpnt->SCp.buffer->address;
+		SCpnt->SCp.this_residual    = SCpnt->SCp.buffer->length;
+		SCpnt->SCp.buffers_residual = SCpnt->use_sg - 1;
+	} else {
+		SCpnt->SCp.ptr		    = (char *) SCpnt->request_buffer;
+		SCpnt->SCp.this_residual    = SCpnt->request_bufflen;
+		SCpnt->SCp.buffer	    = NULL;
+		SCpnt->SCp.buffers_residual = 0;
+	}
+
+	if(nsphw_start_selection(SCpnt) == FALSE) {
+		DEBUG(0, " selection fail\n");
+		nsp_data.CurrentSC = NULL;
+		SCpnt->result	   = DID_NO_CONNECT << 16;
+		done(SCpnt);
+		return FAILED;
+	}
+
+
+	//DEBUG(0, __FUNCTION__ "() out\n");
+	return SUCCESS;
+}
+
+/*
+ * setup PIO FIFO transfer mode and enable/disable to data out
+ */
+static void nsp_setup_fifo(unsigned int base, int enabled)
+{
+	unsigned char transfer_mode;
+
+	//DEBUG(0, __FUNCTION__ "() enabled=%d\n", enabled);
+
+	if (enabled != FALSE) {
+		transfer_mode = TRANSFER_GO | BRAIND;
+	} else {
+		transfer_mode = 0;
+	}
+
+	transfer_mode |= nsp_data.TransferMode;
+
+	nsp_index_write(base, TRANSFERMODE, transfer_mode);
+}
+
+/*
+ * Initialize Ninja hardware
+ */
+static int nsphw_init(void)
+{
+	unsigned int base     = nsp_data.BaseAddress;
+	int	     i, j;
+	sync_data    tmp_sync = { SyncNegotiation: SYNC_NOT_YET,
+				  SyncPeriod:	   0,
+				  SyncOffset:	   0
+	};
+
+	//DEBUG(0, __FUNCTION__ "() in\n");
+
+	nsp_data.ScsiClockDiv = CLOCK_40M;
+	nsp_data.CurrentSC    = NULL;
+	nsp_data.FifoCount    = 0;
+	nsp_data.TransferMode = MODE_IO8;
+
+	/* setup sync data */
+	for ( i = 0; i < N_TARGET; i++ ) {
+		for ( j = 0; j < N_LUN; j++ ) {
+			nsp_data.Sync[i][j] = tmp_sync;
+		}
+	}
+
+	/* block all interrupts */
+	nsp_write(base,	      IRQCONTROL,   IRQCONTROL_ALLMASK);
+
+	/* setup SCSI interface */
+	nsp_write(base,	      IFSELECT,	    IF_IFSEL);
+
+	nsp_index_write(base, SCSIIRQMODE,  0);
+
+	nsp_index_write(base, TRANSFERMODE, MODE_IO8);
+	nsp_index_write(base, CLOCKDIV,	    nsp_data.ScsiClockDiv);
+
+	nsp_index_write(base, PARITYCTRL,   0);
+	nsp_index_write(base, POINTERCLR,   POINTER_CLEAR     |
+					    ACK_COUNTER_CLEAR |
+					    REQ_COUNTER_CLEAR |
+					    HOST_COUNTER_CLEAR);
+
+	/* setup fifo asic */
+	nsp_write(base,	      IFSELECT,	    IF_REGSEL);
+	nsp_index_write(base, TERMPWRCTRL,  0);
+	if ((nsp_index_read(base, OTHERCONTROL) & TPWR_SENSE) == 0) {
+		printk(KERN_INFO "nsp_cs: terminator power on\n");
+		nsp_index_write(base, TERMPWRCTRL, POWER_ON);
+	}
+
+	nsp_index_write(base, TIMERCOUNT,   0);
+	nsp_index_write(base, TIMERCOUNT,   0); /* requires 2 times!! */
+
+	nsp_index_write(base, SYNCREG,	    0);
+	nsp_index_write(base, ACKWIDTH,	    0);
+
+	/* enable interrupts and ack them */
+	nsp_index_write(base, SCSIIRQMODE,  SCSI_PHASE_CHANGE_EI |
+					    RESELECT_EI		 |
+					    SCSI_RESET_IRQ_EI	 );
+	nsp_write(base,	      IRQCONTROL,   IRQCONTROL_ALLCLEAR);
+
+	nsp_setup_fifo(base, FALSE);
+
+	return TRUE;
+}
+
+/*
+ * Start selection phase
+ */
+static unsigned int nsphw_start_selection(Scsi_Cmnd *SCpnt)
+{
+	unsigned int  host_id	 = SCpnt->host->this_id;
+	unsigned int  base	 = SCpnt->host->io_port;
+	unsigned char target	 = SCpnt->target;
+	int	      wait_count;
+	unsigned char phase, arbit;
+
+	//DEBUG(0, __FUNCTION__ "()in\n");
+
+
+	phase = nsp_index_read(base, SCSIBUSMON);
+	if(phase != BUSMON_BUS_FREE) {
+		//DEBUG(0, " bus busy\n");
+		return FALSE;
+	}
+
+	/* start arbitration */
+	//DEBUG(0, " start arbit\n");
+	SCpnt->SCp.phase = PH_ARBSTART;
+	nsp_index_write(base, SETARBIT, ARBIT_GO);
+
+	wait_count = jiffies + 10 * HZ;
+	do {
+		/* XXX: what a stupid chip! */
+		arbit = nsp_index_read(base, ARBITSTATUS);
+		//DEBUG(0, " arbit=%d, wait_count=%d\n", arbit, wait_count);
+		udelay(1); /* hold 1.2us */
+	} while((arbit & (ARBIT_WIN | ARBIT_FAIL)) == 0 &&
+		time_before(jiffies, wait_count));
+
+	if((arbit & ARBIT_WIN) == 0) {
+		//DEBUG(0, " arbit fail\n");
+		nsp_index_write(base, SETARBIT, ARBIT_FLAG_CLEAR);
+		return FALSE;
+	}
+
+	/* assert select line */
+	//DEBUG(0, " assert SEL line\n");
+	SCpnt->SCp.phase = PH_SELSTART;
+	udelay(3);
+	nsp_index_write(base, SCSIDATALATCH, (1 << host_id) | (1 << target));
+	nsp_index_write(base, SCSIBUSCTRL,   SCSI_SEL | SCSI_BSY | SCSI_ATN);
+	udelay(3);
+	nsp_index_write(base, SCSIBUSCTRL,   SCSI_SEL | SCSI_BSY | SCSI_DATAOUT_ENB | SCSI_ATN);
+	nsp_index_write(base, SETARBIT,	     ARBIT_FLAG_CLEAR);
+	udelay(3);
+	nsp_index_write(base, SCSIBUSCTRL,   SCSI_SEL | SCSI_DATAOUT_ENB | SCSI_ATN);
+
+	/* check selection timeout */
+	nsp_start_timer(SCpnt, 1000/51);
+	nsp_data.SelectionTimeOut = 1;
+
+	return TRUE;
+}
+
+struct nsp_sync_table {
+	unsigned int min_period;
+	unsigned int max_period;
+	unsigned int chip_period;
+	unsigned int ack_width;
+};
+
+static struct nsp_sync_table nsp_sync_table_40M[] = {
+	{0x0c,0x0c,0x1,0},	/* 20MB	 50ns*/
+	{0x19,0x19,0x3,1},	/* 10MB	 100ns*/ 
+	{0x1a,0x25,0x5,2},	/* 7.5MB 150ns*/ 
+	{0x26,0x32,0x7,3},	/* 5MB	 200ns*/
+	{0x0, 0,   0,  0}
+};
+
+static struct nsp_sync_table nsp_sync_table_20M[] = {
+	{0x19,0x19,0x1,0},	/* 10MB	 100ns*/ 
+	{0x1a,0x25,0x2,0},	/* 7.5MB 150ns*/ 
+	{0x26,0x32,0x3,1},	/* 5MB	 200ns*/
+	{0x0, 0,   0,  0}
+};
+
+/*
+ * setup synchronous data transfer mode
+ */
+static int nsp_msg(Scsi_Cmnd *SCpnt)
+{
+	unsigned char	       target = SCpnt->target;
+	unsigned char	       lun    = SCpnt->lun;
+	sync_data	      *sync   = &(nsp_data.Sync[target][lun]);
+	struct nsp_sync_table *sync_table;
+	unsigned int	       period, offset;
+	int		       i;
+
+
+	DEBUG(0, __FUNCTION__ "()\n");
+
+/**!**/
+
+	period = sync->SyncPeriod;
+	offset = sync->SyncOffset;
+
+	DEBUG(0, " period=0x%x, offset=0x%x\n", period, offset);
+
+	if (nsp_data.ScsiClockDiv == CLOCK_20M) {
+		sync_table = &nsp_sync_table_20M[0];
+	} else {
+		sync_table = &nsp_sync_table_40M[0];
+	}
+
+	for ( i = 0; sync_table->max_period != 0; i++, sync_table++) {
+		if ( period >= sync_table->min_period &&
+		     period <= sync_table->max_period	 ) {
+			break;
+		}
+	}
+
+	if (period != 0 && sync_table->max_period == 0) {
+		/*
+		 * No proper period/offset found
+		 */
+		DEBUG(0, " no proper period/offset\n");
+
+		sync->SyncPeriod   = 0;
+		sync->SyncOffset   = 0;
+		sync->SyncRegister = 0;
+		sync->AckWidth	   = 0;
+
+		return FALSE;
+	}
+
+	sync->SyncRegister = (sync_table->chip_period << SYNCREG_PERIOD_SHIFT) |
+			     (offset & SYNCREG_OFFSET_MASK);
+	sync->AckWidth	   = sync_table->ack_width;
+
+	DEBUG(0, " sync_reg=0x%x, ack_width=0x%x\n", sync->SyncRegister, sync->AckWidth);
+
+	return TRUE;
+}
+
+
+/*
+ * start ninja hardware timer
+ */
+static void nsp_start_timer(Scsi_Cmnd *SCpnt, int time)
+{
+	unsigned int base = SCpnt->host->io_port;
+
+	//DEBUG(0, __FUNCTION__ "() in SCpnt=0x%p, time=%d\n", SCpnt, time);
+	nsp_data.TimerCount = time;
+	nsp_index_write(base, TIMERCOUNT, time);
+}
+
+/*
+ * wait for bus phase change
+ */
+static int nsp_negate_signal(Scsi_Cmnd *SCpnt, unsigned char mask, char *str)
+{
+	unsigned int  base = SCpnt->host->io_port;
+	unsigned char reg;
+	int	      count, i = TRUE;
+
+	//DEBUG(0, __FUNCTION__ "()\n");
+
+	count = jiffies + HZ;
+
+	do {
+		reg = nsp_index_read(base, SCSIBUSMON);
+		if (reg == 0xff) {
+			break;
+		}
+	} while ((i = time_before(jiffies, count)) && (reg & mask) != 0);
+
+	if (!i) {
+		printk(KERN_DEBUG __FUNCTION__ " %s signal off timeut\n", str);
+	}
+
+	return 0;
+}
+
+/*
+ * expect Ninja Irq
+ */
+static int nsp_expect_signal(Scsi_Cmnd	   *SCpnt,
+			     unsigned char  current_phase,
+			     unsigned char  mask)
+{
+	unsigned int  base	 = SCpnt->host->io_port;
+	int	      wait_count;
+	unsigned char phase, i_src;
+
+	//DEBUG(0, __FUNCTION__ "() current_phase=0x%x, mask=0x%x\n", current_phase, mask);
+
+	wait_count = jiffies + HZ;
+	do {
+		phase = nsp_index_read(base, SCSIBUSMON);
+		if (phase == 0xff) {
+			//DEBUG(0, " ret -1\n");
+			return -1;
+		}
+		i_src = nsp_read(base, IRQSTATUS);
+		if (i_src & IRQSTATUS_SCSI) {
+			//DEBUG(0, " ret 0 found scsi signal\n");
+			return 0;
+		}
+		if ((phase & mask) != 0 && (phase & BUSMON_PHASE_MASK) == current_phase) {
+			//DEBUG(0, " ret 1 phase=0x%x\n", phase);
+			return 1;
+		}
+	} while(time_before(jiffies, wait_count));
+
+	//DEBUG(0, __FUNCTION__ " : " __FUNCTION__ " timeout\n");
+	return -1;
+}
+
+/*
+ * transfer SCSI message
+ */
+static int nsp_xfer(Scsi_Cmnd *SCpnt, int phase)
+{
+	unsigned int  base = SCpnt->host->io_port;
+	char	     *buf  = nsp_data.MsgBuffer;
+	int	      len  = MIN(MSGBUF_SIZE, nsp_data.MsgLen);
+	int	      ptr;
+	int	      ret;
+
+	//DEBUG(0, __FUNCTION__ "()\n");
+/**!**/
+	for (ptr = 0; len > 0; len --, ptr ++) {
+
+		ret = nsp_expect_signal(SCpnt, phase, BUSMON_REQ);
+		if (ret <= 0) {
+			DEBUG(0, " xfer quit\n");
+			return 0;
+		}
+
+		/* if last byte, negate ATN */
+		if (len == 1) {
+			nsp_index_write(base, SCSIBUSCTRL, AUTODIRECTION | ACKENB);
+		}
+
+		/* read & write message */
+		if (phase & BUSMON_IO) {
+			//DEBUG(0, " read msg\n");
+			buf[ptr] = nsp_index_read(base, SCSIDATAWITHACK);
+		} else {
+			//DEBUG(0, " write msg\n");
+			nsp_index_write(base, SCSIDATAWITHACK, buf[ptr]);
+		}
+		nsp_negate_signal(SCpnt, BUSMON_ACK, "xfer<ack>");
+
+	}
+	return len;
+}
+
+/*
+ * get extra SCSI data from fifo
+ */
+static int nsp_dataphase_bypass(Scsi_Cmnd *SCpnt)
+{
+	unsigned int base = SCpnt->host->io_port;
+	unsigned int count;
+
+	//DEBUG(0, __FUNCTION__ "()\n");
+
+	if (SCpnt->SCp.have_data_in != IO_IN) {
+		return 0;
+	}
+
+	count = nsp_fifo_count(SCpnt);
+	if (nsp_data.FifoCount == count) {
+		//DEBUG(0, " not use bypass quirk\n");
+		return 0;
+	}
+
+	/*
+	 * XXX: NSP_QUIRK
+	 * data phase skip only occures in case of SCSI_LOW_READ
+	 */
+	SCpnt->SCp.phase = PH_DATA;
+	nsp_pio_read(SCpnt);
+	nsp_setup_fifo(base, FALSE);
+
+	DEBUG(0, " use bypass quirk\n");
+	return 0;
+}
+
+/*
+ * accept reselection
+ */
+static int nsp_reselected(Scsi_Cmnd *SCpnt)
+{
+	unsigned int  base = SCpnt->host->io_port;
+	unsigned char reg;
+
+	//DEBUG(0, __FUNCTION__ "()\n");
+
+	nsp_negate_signal(SCpnt, BUSMON_SEL, "reselect<SEL>");
+
+	nsp_nexus(SCpnt);
+	reg = nsp_index_read(base, SCSIBUSCTRL) & ~(SCSI_BSY | SCSI_ATN);
+	nsp_index_write(base, SCSIBUSCTRL, reg);
+	nsp_index_write(base, SCSIBUSCTRL, reg | AUTODIRECTION | ACKENB);
+
+	return TRUE;
+}
+
+/*
+ * count how many data transferd
+ */
+static int nsp_fifo_count(Scsi_Cmnd *SCpnt)
+{
+	unsigned int base = SCpnt->host->io_port;
+	unsigned int count;
+	unsigned int l, m, h;
+
+	nsp_index_write(base, POINTERCLR, POINTER_CLEAR);
+
+        l = (unsigned int)nsp_read(base, DATAREG);
+        m = (unsigned int)nsp_read(base, DATAREG);
+        h = (unsigned int)nsp_read(base, DATAREG);
+
+	count = (h << 16) | (m << 8) | (l << 0);
+
+	//DEBUG(0, __FUNCTION__ "() =0x%x\n", count);
+
+	return count;
+}
+
+/* fifo size */
+#define RFIFO_CRIT 64
+#define WFIFO_CRIT 64
+
+/*
+ * read data in DATA IN phase
+ */
+static void nsp_pio_read(Scsi_Cmnd *SCpnt)
+{
+	unsigned int  base     = SCpnt->host->io_port;
+	int	      time_out, i;
+	int	      ocount, res;
+	unsigned char stat, fifo_stat;
+
+	ocount = nsp_data.FifoCount;
+
+	DEBUG(0, __FUNCTION__ "() in SCpnt=0x%p resid=%d ocount=%d ptr=0x%p this_residual=%d buffers=0x%p nbuf=%d\n", SCpnt, RESID, ocount, SCpnt->SCp.ptr, SCpnt->SCp.this_residual, SCpnt->SCp.buffer, SCpnt->SCp.buffers_residual);
+
+	time_out = jiffies + 10 * HZ;
+
+	while ((i = time_before(jiffies,time_out)) &&
+	       (SCpnt->SCp.this_residual > 0 || SCpnt->SCp.buffers_residual > 0 ) ) {
+
+		stat = nsp_index_read(base, SCSIBUSMON);
+		stat &= BUSMON_PHASE_MASK;
+
+		res = nsp_fifo_count(SCpnt) - ocount;
+
+		//DEBUG(0, " ptr=0x%p this=0x%x ocount=0x%x res=0x%x\n", SCpnt->SCp.ptr, SCpnt->SCp.this_residual, ocount, res);
+		if (res == 0) { /* if some data avilable ? */
+			if (stat == BUSPHASE_DATA_IN) { /* phase changed? */
+				//DEBUG(0, " wait for data this=%d\n", SCpnt->SCp.this_residual);
+				continue;
+			} else {
+				DEBUG(0, " phase changed stat=0x%x\n", stat);
+				break;
+			}
+		}
+
+		fifo_stat = nsp_read(base, FIFOSTATUS);
+		if ((fifo_stat & FIFOSTATUS_FULL_EMPTY) == 0 &&
+		    stat                                == BUSPHASE_DATA_IN) {
+			continue;
+		}
+
+		res = MIN(res, SCpnt->SCp.this_residual);
+
+		switch (nsp_data.TransferMode) {
+		case MODE_IO32:
+			res &= ~(BIT(1)|BIT(0)); /* align 4 */
+			nsp_fifo32_read(base, SCpnt->SCp.ptr, res >> 2);
+			break;
+		case MODE_IO8:
+			nsp_fifo8_read (base, SCpnt->SCp.ptr, res     );
+			break;
+		default:
+			DEBUG(0, "unknown read mode\n");
+			break;
+		}
+
+		RESID			 -= res;
+		SCpnt->SCp.ptr		 += res;
+		SCpnt->SCp.this_residual -= res;
+		ocount			 += res;
+		//DEBUG(0, " ptr=0x%p this_residual=0x%x ocount=0x%x\n", SCpnt->SCp.ptr, SCpnt->SCp.this_residual, ocount);
+
+		/* go to next scatter list if availavle */
+		if (SCpnt->SCp.this_residual	== 0 &&
+		    SCpnt->SCp.buffers_residual != 0 ) {
+			//DEBUG(0, " scatterlist next timeout=%d\n", time_out);
+			SCpnt->SCp.buffers_residual--;
+			SCpnt->SCp.buffer++;
+			SCpnt->SCp.ptr		 = SCpnt->SCp.buffer->address;
+			SCpnt->SCp.this_residual = SCpnt->SCp.buffer->length;
+		}
+
+		time_out = jiffies + 10 * HZ;
+	}
+
+	nsp_data.FifoCount = ocount;
+
+	if (!i) {
+		printk(KERN_DEBUG __FUNCTION__ "() pio read timeout resid=%d this_residual=%d buffers_residual=%d\n", RESID, SCpnt->SCp.this_residual, SCpnt->SCp.buffers_residual);
+	}
+	DEBUG(0, " read ocount=0x%x\n", ocount);
+}
+
+/*
+ * write data in DATA OUT phase
+ */
+static void nsp_pio_write(Scsi_Cmnd *SCpnt)
+{
+	unsigned int  base     = SCpnt->host->io_port;
+	int	      time_out, i;
+	unsigned int  ocount, res;
+	unsigned char stat;
+
+	ocount	 = nsp_data.FifoCount;
+
+	DEBUG(0, __FUNCTION__ "() in fifocount=%d ptr=0x%p this_residual=%d buffers=0x%p nbuf=%d resid=0x%x\n", nsp_data.FifoCount, SCpnt->SCp.ptr, SCpnt->SCp.this_residual, SCpnt->SCp.buffer, SCpnt->SCp.buffers_residual, SCpnt->resid);
+
+	time_out = jiffies + 10 * HZ;
+
+	while ((i = time_before(jiffies, time_out))                             &&
+	       (SCpnt->SCp.this_residual > 0 || SCpnt->SCp.buffers_residual > 0)) {
+		stat = nsp_index_read(base, SCSIBUSMON);
+		stat &= BUSMON_PHASE_MASK;
+		if (stat != BUSPHASE_DATA_OUT) {
+			DEBUG(0, " phase changed stat=0x%x\n", stat);
+			break;
+		}
+
+		res = ocount - nsp_fifo_count(SCpnt);
+		if (res > 0) { /* write all data? */
+			DEBUG(0, " wait for all data out. ocount=0x%x res=%d\n", ocount, res);
+			continue;
+		}
+
+		res = MIN(SCpnt->SCp.this_residual, WFIFO_CRIT);
+
+		//DEBUG(0, " ptr=0x%p this=0x%x res=0x%x\n", SCpnt->SCp.ptr, SCpnt->SCp.this_residual, res);
+		switch (nsp_data.TransferMode) {
+		case MODE_IO32:
+			res &= ~(BIT(1)|BIT(0)); /* align 4 */
+			nsp_fifo32_write(base, SCpnt->SCp.ptr, res >> 2);
+			break;
+		case MODE_IO8:
+			nsp_fifo8_write (base, SCpnt->SCp.ptr, res     );
+			break;
+		default:
+			DEBUG(0, "unknown write mode\n");
+			break;
+		}
+
+		RESID			 -= res;
+		SCpnt->SCp.ptr		 += res;
+		SCpnt->SCp.this_residual -= res;
+		ocount			 += res;
+
+		/* go to next scatter list if availavle */
+		if (SCpnt->SCp.this_residual	== 0 &&
+		    SCpnt->SCp.buffers_residual != 0 ) {
+			//DEBUG(0, " scatterlist next\n");
+			SCpnt->SCp.buffers_residual--;
+			SCpnt->SCp.buffer++;
+			SCpnt->SCp.ptr		 = SCpnt->SCp.buffer->address;
+			SCpnt->SCp.this_residual = SCpnt->SCp.buffer->length;
+		}
+
+		time_out = jiffies + 10 * HZ;
+	}
+
+	nsp_data.FifoCount = ocount;
+
+	if (!i) {
+		printk(KERN_DEBUG __FUNCTION__ "() pio write timeout resid=%d\n", RESID);
+	}
+	//DEBUG(0, " write ocount=%d\n", ocount);
+}
+
+#undef RFIFO_CRIT
+#undef WFIFO_CRIT
+
+/*
+ * setup synchronous/asynchronous data transfer mode
+ */
+static int nsp_nexus(Scsi_Cmnd *SCpnt)
+{
+	unsigned int  base   = SCpnt->host->io_port;
+	unsigned char target = SCpnt->target;
+	unsigned char lun    = SCpnt->lun;
+	sync_data *sync = &(nsp_data.Sync[target][lun]);
+
+	//DEBUG(0, __FUNCTION__ "() in SCpnt=0x%p\n", SCpnt);
+
+	/* setup synch transfer registers */
+	nsp_index_write(base, SYNCREG,	sync->SyncRegister);
+	nsp_index_write(base, ACKWIDTH, sync->AckWidth);
+
+	if (RESID % 4 != 0   ||
+	    RESID     <= 256 ) {
+		nsp_data.TransferMode = MODE_IO8;
+	} else {
+		nsp_data.TransferMode = MODE_IO32;
+	}
+
+	/* setup pdma fifo */
+	nsp_setup_fifo(base, TRUE);
+
+	/* clear ack counter */
+	nsp_data.FifoCount = 0;
+	nsp_index_write(base, POINTERCLR, POINTER_CLEAR	    |
+					  ACK_COUNTER_CLEAR |
+					  REQ_COUNTER_CLEAR |
+					  HOST_COUNTER_CLEAR);
+
+	return 0;
+}
+
+/*
+ * interrupt handler
+ */
+static void nspintr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned int   base;
+	unsigned char  i_src, irq_phase, phase;
+	Scsi_Cmnd     *tmpSC;
+	int	       ret, len;
+	unsigned char  data_reg, control_reg;
+
+
+	//DEBUG(0, __FUNCTION__ "(%d) CurrentSC=0x%p\n", irq, nsp_data.CurrentSC);
+
+	base = nsp_data.BaseAddress;
+	//DEBUG(0, " base=0x%x\n", base);
+
+	/*
+	 * interrupt check
+	 */
+	nsp_write(base, IRQCONTROL, IRQCONTROL_IRQDISABLE);
+	i_src = nsp_read(base, IRQSTATUS);
+	if (i_src == 0xff || (i_src & IRQSTATUS_MASK) == 0) {
+		nsp_write(base, IRQCONTROL, 0);
+		//DEBUG(0, " no irq\n");
+		return;
+	}
+
+	//DEBUG(0, " i_src=0x%x\n", i_src);
+
+	/* XXX: IMPORTANT
+	 * Do not read an irq_phase register if no scsi phase interrupt.
+	 * Unless, you should lose a scsi phase interrupt.
+	 */
+	phase = nsp_index_read(base, SCSIBUSMON);
+	if((i_src & IRQSTATUS_SCSI) != 0) {
+		irq_phase = nsp_index_read(base, IRQPHASESENCE);
+	} else {
+		irq_phase = 0;
+	}
+
+	//DEBUG(0, " irq_phase=0x%x\n", irq_phase);
+
+	/*
+	 * timer interrupt handler (scsi vs timer interrupts)
+	 */
+	//DEBUG(0, " timercount=%d\n", nsp_data.TimerCount);
+	if (nsp_data.TimerCount != 0) {
+		//DEBUG(0, " stop timer\n");
+		nsp_index_write(base, TIMERCOUNT, 0);
+		nsp_index_write(base, TIMERCOUNT, 0);
+		nsp_data.TimerCount = 0;
+	}
+
+	if ((i_src & IRQSTATUS_MASK) == IRQSTATUS_TIMER &&
+	    nsp_data.SelectionTimeOut == 0) {
+		//DEBUG(0, " timer start\n");
+		nsp_write(base, IRQCONTROL, IRQCONTROL_TIMER_CLEAR);
+		return;
+	}
+
+	nsp_write(base, IRQCONTROL, IRQCONTROL_TIMER_CLEAR | IRQCONTROL_FIFO_CLEAR);
+
+	if (nsp_data.CurrentSC == NULL) {
+		printk(KERN_DEBUG __FUNCTION__ " CurrentSC==NULL irq_status=0x%x phase=0x%x irq_phase=0x%x this can't be happen\n", i_src, phase, irq_phase);
+		return;
+	} else {
+		tmpSC = nsp_data.CurrentSC;
+	}
+
+	/*
+	 * parse hardware SCSI irq reasons register
+	 */
+	if ((i_src & IRQSTATUS_SCSI) != 0) {
+		if ((irq_phase & SCSI_RESET_IRQ) != 0) {
+			printk(KERN_DEBUG " " __FUNCTION__ "() bus reset (power off?)\n");
+
+			nsp_data.CurrentSC = NULL;
+			tmpSC->result	   = DID_RESET << 16;
+			tmpSC->scsi_done(tmpSC);
+			return;
+		}
+
+		if ((irq_phase & RESELECT_IRQ) != 0) {
+			DEBUG(0, " reselect\n");
+			nsp_write(base, IRQCONTROL, IRQCONTROL_RESELECT_CLEAR);
+			if (nsp_reselected(tmpSC) != FALSE) {
+				return;
+			}
+		}
+
+		if ((irq_phase & (PHASE_CHANGE_IRQ | LATCHED_BUS_FREE)) == 0) {
+			return; 
+		}
+	}
+
+	//show_phase(tmpSC);
+
+	switch(tmpSC->SCp.phase) {
+	case PH_SELSTART:
+		if ((phase & BUSMON_BSY) == 0) {
+			//DEBUG(0, " selection count=%d\n", nsp_data.SelectionTimeOut);
+			if (nsp_data.SelectionTimeOut >= NSP_SELTIMEOUT) {
+				DEBUG(0, " selection time out\n");
+				nsp_data.SelectionTimeOut = 0;
+				nsp_index_write(base, SCSIBUSCTRL, 0);
+
+				nsp_data.CurrentSC = NULL;
+				tmpSC->result	   = DID_NO_CONNECT << 16;
+				tmpSC->scsi_done(tmpSC);
+
+				return;
+			}
+			nsp_data.SelectionTimeOut += 1;
+			nsp_start_timer(tmpSC, 1000/51);
+			return;
+		}
+
+		/* attention assert */
+		//DEBUG(0, " attention assert\n");
+		nsp_data.SelectionTimeOut = 0;
+		tmpSC->SCp.phase	  = PH_SELECTED;
+		nsp_index_write(base, SCSIBUSCTRL, SCSI_ATN);
+		udelay(1);
+		nsp_index_write(base, SCSIBUSCTRL, SCSI_ATN | AUTODIRECTION | ACKENB);
+		return;
+
+		break;
+
+	case PH_RESELECT:
+		//DEBUG(0, " phase reselect\n");
+		if ((phase & BUSMON_PHASE_MASK) != BUSPHASE_MESSAGE_IN) {
+
+			nsp_data.CurrentSC = NULL;
+			tmpSC->result	   = DID_ABORT << 16;
+			tmpSC->scsi_done(tmpSC);
+			return;
+		}
+		/* fall thru */
+	default:
+		if (( i_src & (IRQSTATUS_SCSI | IRQSTATUS_FIFO)) == 0) {
+			return;
+		}
+		break;
+	}
+
+	/*
+	 * SCSI sequencer
+	 */
+	//DEBUG(0, " start scsi seq\n");
+
+	/* normal disconnect */
+	if ((irq_phase & LATCHED_BUS_FREE) != 0) {
+		//DEBUG(0, " normal disconnect i_src=0x%x, phase=0x%x, irq_phase=0x%x\n", i_src, phase, irq_phase);
+		if ((tmpSC->SCp.Message == 0)) {     /* all command complete and return status */
+			nsp_data.CurrentSC = NULL;
+			tmpSC->result = (DID_OK		    << 16) |
+					(tmpSC->SCp.Message <<	8) |
+					(tmpSC->SCp.Status  <<	0);
+			DEBUG(0, " command complete result=0x%x\n", tmpSC->result);
+			tmpSC->scsi_done(tmpSC);
+			return;
+		}
+
+		return;
+	}
+
+
+	/* check unexpected bus free state */
+	if (phase == 0) {
+		printk(KERN_DEBUG " " __FUNCTION__ " unexpected bus free. i_src=0x%x, phase=0x%x, irq_phase=0x%x\n", i_src, phase, irq_phase);
+
+		nsp_data.CurrentSC = NULL;
+		tmpSC->result	   = DID_ERROR << 16;
+		tmpSC->scsi_done(tmpSC);
+		return;
+	}
+
+	switch (phase & BUSMON_PHASE_MASK) {
+	case BUSPHASE_COMMAND:
+		DEBUG(0, " BUSPHASE_COMMAND\n");
+		if ((phase & BUSMON_REQ) == 0) {
+			DEBUG(0, " REQ == 0\n");
+			return;
+		}
+
+		tmpSC->SCp.phase = PH_COMMAND;
+
+		nsp_nexus(tmpSC);
+
+		/* write scsi command */
+		nsp_index_write(base, COMMANDCTRL, CLEAR_COMMAND_POINTER);
+		for (len = 0; len < COMMAND_SIZE(tmpSC->cmnd[0]); len++) {
+			nsp_index_write(base, COMMANDDATA, tmpSC->cmnd[len]);
+		}
+		nsp_index_write(base, COMMANDCTRL, CLEAR_COMMAND_POINTER | AUTO_COMMAND_GO);
+		break;
+
+	case BUSPHASE_DATA_OUT:
+		DEBUG(0, " BUSPHASE_DATA_OUT\n");
+
+		tmpSC->SCp.phase = PH_DATA;
+		tmpSC->SCp.have_data_in = IO_OUT;
+
+		nsp_pio_write(tmpSC);
+
+		break;
+
+	case BUSPHASE_DATA_IN:
+		DEBUG(0, " BUSPHASE_DATA_IN\n");
+
+		tmpSC->SCp.phase = PH_DATA;
+		tmpSC->SCp.have_data_in = IO_IN;
+
+		nsp_pio_read(tmpSC);
+
+		break;
+
+	case BUSPHASE_STATUS:
+		nsp_dataphase_bypass(tmpSC);
+		DEBUG(0, " BUSPHASE_STATUS\n");
+
+		tmpSC->SCp.phase = PH_STATUS;
+
+		tmpSC->SCp.Status = nsp_index_read(base, SCSIDATAWITHACK);
+		//DEBUG(0, " message=0x%x status=0x%x\n", tmpSC->SCp.Message, tmpSC->SCp.Status);
+
+		break;
+
+	case BUSPHASE_MESSAGE_OUT:
+		DEBUG(0, " BUSPHASE_MESSAGE_OUT\n");
+		if ((phase & BUSMON_REQ) == 0) {
+			goto timer_out;
+		}
+
+		tmpSC->SCp.phase = PH_MSG_OUT;
+
+		/*
+		 * XXX: NSP QUIRK
+		 * NSP invoke interrupts only in the case of scsi phase changes,
+		 * therefore we should poll the scsi phase here to catch 
+		 * the next "msg out" if exists (no scsi phase changes).
+		 */
+		ret = 16;
+		len = 0;
+
+		nsp_msg(tmpSC);
+
+		nsp_data.MsgBuffer[len] = IDENTIFY(TRUE, tmpSC->lun); len++;
+		nsp_data.MsgLen		= len;
+
+		do {
+			DEBUG(0, " msgout loop\n");
+
+			if (nsp_xfer(tmpSC, BUSPHASE_MESSAGE_OUT)) {
+				printk(KERN_DEBUG " " __FUNCTION__ " msgout: xfer short\n");
+			}
+
+			/* catch a next signal */
+			ret = nsp_expect_signal(tmpSC, BUSPHASE_MESSAGE_OUT, BUSMON_REQ);
+		} while (ret > 0 && len-- > 0);
+
+		break;
+
+	case BUSPHASE_MESSAGE_IN:
+		nsp_dataphase_bypass(tmpSC);
+		DEBUG(0, " BUSPHASE_MESSAGE_IN\n");
+		if ((phase & BUSMON_REQ) == 0) {
+			goto timer_out;
+		}
+
+		tmpSC->SCp.phase = PH_MSG_IN;
+
+		/*
+		 * XXX: NSP QUIRK
+		 * NSP invoke interrupts only in the case of scsi phase changes,
+		 * therefore we should poll the scsi phase here to catch 
+		 * the next "msg in" if exists (no scsi phase changes).
+		 */
+		ret = 16;
+		len = 0;
+
+		do {
+			//DEBUG(0, " msgin loop\n");
+			/* read data */
+			data_reg = nsp_index_read(base, SCSIDATAIN);
+
+			/* assert ACK */
+			control_reg = nsp_index_read(base, SCSIBUSCTRL);
+			control_reg |= SCSI_ACK;
+			nsp_index_write(base, SCSIBUSCTRL, control_reg);
+			nsp_negate_signal(tmpSC, BUSMON_REQ, "msgin<REQ>");
+
+			nsp_data.MsgBuffer[len] = data_reg; len++;
+			DEBUG(0, " msg=0x%x\n", data_reg);
+
+			/* deassert ACK */
+			control_reg =  nsp_index_read(base, SCSIBUSCTRL);
+			control_reg &= ~SCSI_ACK;
+			nsp_index_write(base, SCSIBUSCTRL, control_reg);
+
+			/* catch a next signal */
+			ret = nsp_expect_signal(tmpSC, BUSPHASE_MESSAGE_IN, BUSMON_REQ);
+		} while (ret > 0 && MSGBUF_SIZE > len);
+
+		nsp_data.MsgLen	   = len;
+		tmpSC->SCp.Message = nsp_data.MsgBuffer[len-1];
+
+		//DEBUG(0, " message=0x%x len=%d\n", tmpSC->SCp.Message, nsp_data.MsgLen);
+		break;
+
+	case BUSPHASE_SELECT:
+	default:
+		DEBUG(0, " BUSPHASE other\n");
+
+		break;
+	}
+
+	//DEBUG(0, __FUNCTION__ "() out\n");
+	return;	
+
+timer_out:
+	nsp_start_timer(tmpSC, 1000/102);
+	return;
+}
+
+#ifdef DBG_SHOWCOMMAND
+#include "nsp_debug.c"
+#endif	/* DBG_SHOWCOMMAND */
+
+/*====================================================================*/
+static void cs_error(client_handle_t handle, int func, int ret)
+{
+	error_info_t err = { func, ret };
+	CardServices(ReportError, handle, &err);
+}
+
+/*======================================================================
+    nsp_attach() creates an "instance" of the driver, allocating
+    local data structures for one device.  The device is registered
+    with Card Services.
+
+    The dev_link structure is initialized, but we don't actually
+    configure the card at this point -- we wait until we receive a
+    card insertion event.
+======================================================================*/
+static dev_link_t *nsp_attach(void)
+{
+	scsi_info_t  *info;
+	client_reg_t  client_reg;
+	dev_link_t   *link;
+	int	      ret, i;
+
+	DEBUG(0, __FUNCTION__ "()\n");
+
+	/* Create new SCSI device */
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) return NULL;
+	memset(info, 0, sizeof(*info));
+	link = &info->link;
+	link->priv = info;
+
+	/* Initialize the dev_link_t structure */
+	link->release.function	 = &nsp_release;
+	link->release.data	 = (u_long)link;
+
+	/* The io structure describes IO port mapping */
+	link->io.NumPorts1	 = 0x10;
+	link->io.Attributes1	 = IO_DATA_PATH_WIDTH_AUTO;
+	link->io.IOAddrLines	 = 10;	/* not used */
+
+	/* Interrupt setup */
+	link->irq.Attributes	 = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT;
+	link->irq.IRQInfo1	 = IRQ_INFO2_VALID    | IRQ_LEVEL_ID;
+	if (irq_list[0] == -1) {
+		link->irq.IRQInfo2 = irq_mask;
+	} else {
+		for (i = 0; i < 4; i++) {
+			link->irq.IRQInfo2 |= 1 << irq_list[i];
+		}
+	}
+	link->irq.Handler	 = &nspintr;
+
+	/* General socket configuration */
+	link->conf.Attributes	 = CONF_ENABLE_IRQ;
+	link->conf.Vcc		 = 50;
+	link->conf.IntType	 = INT_MEMORY_AND_IO;
+	link->conf.Present	 = PRESENT_OPTION;
+
+
+	/* Register with Card Services */
+	link->next               = dev_list;
+	dev_list                 = link;
+	client_reg.dev_info	 = &dev_info;
+	client_reg.Attributes	 = INFO_IO_CLIENT | INFO_CARD_SHARE;
+	client_reg.EventMask	 =
+		CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
+		CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET	|
+		CS_EVENT_PM_SUSPEND	| CS_EVENT_PM_RESUME	 ;
+	client_reg.event_handler = &nsp_event;
+	client_reg.Version	 = 0x0210;
+	client_reg.event_callback_args.client_data = link;
+	ret = CardServices(RegisterClient, &link->handle, &client_reg);
+	if (ret != CS_SUCCESS) {
+		cs_error(link->handle, RegisterClient, ret);
+		nsp_detach(link);
+		return NULL;
+	}
+
+	return link;
+} /* nsp_attach */
+
+
+/*======================================================================
+    This deletes a driver "instance".  The device is de-registered
+    with Card Services.	 If it has been released, all local data
+    structures are freed.  Otherwise, the structures will be freed
+    when the device is released.
+======================================================================*/
+static void nsp_detach(dev_link_t *link)
+{
+	dev_link_t **linkp;
+
+	DEBUG(0, __FUNCTION__ "(0x%p)\n", link);
+    
+	/* Locate device structure */
+	for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) {
+		if (*linkp == link) {
+			break;
+		}
+	}
+	if (*linkp == NULL) {
+		return;
+	}
+
+	del_timer(&link->release);
+	if (link->state & DEV_CONFIG) {
+		nsp_release((u_long)link);
+		if (link->state & DEV_STALE_CONFIG) {
+			link->state |= DEV_STALE_LINK;
+			return;
+		}
+	}
+
+	/* Break the link with Card Services */
+	if (link->handle) {
+		CardServices(DeregisterClient, link->handle);
+	}
+
+	/* Unlink device structure, free bits */
+	*linkp = link->next;
+	kfree(link->priv);
+
+} /* nsp_detach */
+
+
+/*======================================================================
+    nsp_config() is scheduled to run after a CARD_INSERTION event
+    is received, to configure the PCMCIA socket, and to make the
+    ethernet device available to the system.
+======================================================================*/
+#define CS_CHECK(fn, args...) \
+while ((last_ret=CardServices(last_fn=(fn),args))!=0) goto cs_failed
+#define CFG_CHECK(fn, args...) \
+if (CardServices(fn, args) != 0) goto next_entry
+/*====================================================================*/
+
+static void nsp_config(dev_link_t *link)
+{
+	client_handle_t	  handle = link->handle;
+	scsi_info_t	 *info	 = link->priv;
+	tuple_t		  tuple;
+	cisparse_t	  parse;
+	int		  i, last_ret, last_fn;
+	u_char		  tuple_data[64];
+	config_info_t	  conf;
+	Scsi_Device	 *dev;
+	dev_node_t	**tail, *node;
+	struct Scsi_Host *host;
+
+	DEBUG(0, __FUNCTION__ "() in\n");
+
+	tuple.DesiredTuple    = CISTPL_CONFIG;
+	tuple.Attributes      = 0;
+	tuple.TupleData	      = tuple_data;
+	tuple.TupleDataMax    = sizeof(tuple_data);
+	tuple.TupleOffset     = 0;
+	CS_CHECK(GetFirstTuple, handle, &tuple);
+	CS_CHECK(GetTupleData,	handle, &tuple);
+	CS_CHECK(ParseTuple,	handle, &tuple, &parse);
+	link->conf.ConfigBase = parse.config.base;
+	link->conf.Present    = parse.config.rmask[0];
+
+	/* Configure card */
+	driver_template.module = &__this_module;
+	link->state	      |= DEV_CONFIG;
+
+	/* Look up the current Vcc */
+	CS_CHECK(GetConfigurationInfo, handle, &conf);
+	link->conf.Vcc = conf.Vcc;
+
+	tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
+	CS_CHECK(GetFirstTuple, handle, &tuple);
+	while (1) {
+		CFG_CHECK(GetTupleData, handle, &tuple);
+		CFG_CHECK(ParseTuple,	handle, &tuple, &parse);
+		link->conf.ConfigIndex = parse.cftable_entry.index;
+		link->io.BasePort1     = parse.cftable_entry.io.win[0].base;
+		i = CardServices(RequestIO, handle, &link->io);
+		if (i == CS_SUCCESS) {
+			break;
+		}
+	next_entry:
+		DEBUG(0, __FUNCTION__ " next\n");
+		CS_CHECK(GetNextTuple, handle, &tuple);
+	}
+
+	CS_CHECK(RequestIRQ,	       handle, &link->irq);
+	CS_CHECK(RequestConfiguration, handle, &link->conf);
+
+	/* A bad hack... */
+	release_region(link->io.BasePort1, link->io.NumPorts1);
+
+	/* Set port and IRQ */
+	nsp_data.BaseAddress = link->io.BasePort1;
+	nsp_data.NumAddress  = link->io.NumPorts1;
+	nsp_data.IrqNumber   = link->irq.AssignedIRQ;
+
+	DEBUG(0, __FUNCTION__ " I/O[0x%x+0x%x] IRQ %d\n",
+	      nsp_data.BaseAddress, nsp_data.NumAddress, nsp_data.IrqNumber);
+
+	if(nsphw_init() == FALSE) {
+		goto cs_failed;
+	}
+
+	scsi_register_module(MODULE_SCSI_HA, &driver_template);
+
+	DEBUG(0, "GET_SCSI_INFO\n");
+	tail = &link->dev;
+	info->ndev = 0;
+	for (host = scsi_hostlist; host != NULL; host = host->next) {
+		if (host->hostt == &driver_template) {
+			for (dev = host->host_queue; dev != NULL; dev = dev->next) {
+				u_long arg[2], id;
+				kernel_scsi_ioctl(dev, SCSI_IOCTL_GET_IDLUN, arg);
+				id = (arg[0]&0x0f) + ((arg[0]>>4)&0xf0) +
+					((arg[0]>>8)&0xf00) + ((arg[0]>>12)&0xf000);
+				node = &info->node[info->ndev];
+				node->minor = 0;
+				switch (dev->type) {
+				case TYPE_TAPE:
+					node->major = SCSI_TAPE_MAJOR;
+					sprintf(node->dev_name, "st#%04lx", id);
+					break;
+				case TYPE_DISK:
+				case TYPE_MOD:
+					node->major = SCSI_DISK0_MAJOR;
+					sprintf(node->dev_name, "sd#%04lx", id);
+					break;
+				case TYPE_ROM:
+				case TYPE_WORM:
+					node->major = SCSI_CDROM_MAJOR;
+					sprintf(node->dev_name, "sr#%04lx", id);
+					break;
+				default:
+					node->major = SCSI_GENERIC_MAJOR;
+					sprintf(node->dev_name, "sg#%04lx", id);
+					break;
+				}
+				*tail = node; tail = &node->next;
+				info->ndev++;
+				info->host = dev->host;
+			}
+		}
+	}
+	*tail = NULL;
+	if (info->ndev == 0) {
+		printk(KERN_INFO "nsp_cs: no SCSI devices found\n");
+	}
+
+	/* Finally, report what we've done */
+	printk(KERN_INFO "nsp_cs: index 0x%02x: Vcc %d.%d",
+	       link->conf.ConfigIndex,
+	       link->conf.Vcc/10, link->conf.Vcc%10);
+	if (link->conf.Vpp1) {
+		printk(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10);
+	}
+	if (link->conf.Attributes & CONF_ENABLE_IRQ) {
+		printk(", irq %d", link->irq.AssignedIRQ);
+	}
+	if (link->io.NumPorts1) {
+		printk(", io 0x%04x-0x%04x", link->io.BasePort1,
+		       link->io.BasePort1+link->io.NumPorts1-1);
+	}
+	printk("\n");
+
+	link->state &= ~DEV_CONFIG_PENDING;
+	return;
+
+cs_failed:
+	cs_error(link->handle, last_fn, last_ret);
+	nsp_release((u_long)link);
+	return;
+
+} /* nsp_config */
+#undef CS_CHECK
+#undef CFG_CHECK
+
+/*======================================================================
+    After a card is removed, nsp_release() will unregister the net
+    device, and release the PCMCIA configuration.  If the device is
+    still open, this will be postponed until it is closed.
+======================================================================*/
+static void nsp_release(u_long arg)
+{
+	dev_link_t *link = (dev_link_t *)arg;
+
+	DEBUG(0, __FUNCTION__ "(0x%p)\n", link);
+
+	/*
+	 * If the device is currently in use, we won't release until it
+	 * is actually closed.
+	 */
+	if (link->open) {
+		DEBUG(1, "nsp_cs: release postponed, '%s' still open\n",
+		      link->dev->dev_name);
+		link->state |= DEV_STALE_CONFIG;
+		return;
+	}
+
+	/* Unlink the device chain */
+	scsi_unregister_module(MODULE_SCSI_HA, &driver_template);
+	link->dev = NULL;
+
+	if (link->win) {
+		CardServices(ReleaseWindow, link->win);
+	}
+	CardServices(ReleaseConfiguration,  link->handle);
+	if (link->io.NumPorts1) {
+		CardServices(ReleaseIO,     link->handle, &link->io);
+	}
+	if (link->irq.AssignedIRQ) {
+		CardServices(ReleaseIRQ,    link->handle, &link->irq);
+	}
+	link->state &= ~DEV_CONFIG;
+
+	if (link->state & DEV_STALE_LINK) {
+		nsp_detach(link);
+	}
+} /* nsp_release */
+
+/*======================================================================
+
+    The card status event handler.  Mostly, this schedules other
+    stuff to run after an event is received.  A CARD_REMOVAL event
+    also sets some flags to discourage the net drivers from trying
+    to talk to the card any more.
+
+    When a CARD_REMOVAL event is received, we immediately set a flag
+    to block future accesses to this device.  All the functions that
+    actually access the device should check this flag to make sure
+    the card is still present.
+    
+======================================================================*/
+static int nsp_event(event_t		    event,
+		     int		    priority,
+		     event_callback_args_t *args)
+{
+	dev_link_t  *link = args->client_data;
+	scsi_info_t *info = link->priv;
+
+	DEBUG(1, __FUNCTION__ "(0x%06x)\n", event);
+
+	switch (event) {
+	case CS_EVENT_CARD_REMOVAL:
+		DEBUG(0, " event: remove\n");
+		link->state &= ~DEV_PRESENT;
+		if (link->state & DEV_CONFIG) {
+			((scsi_info_t *)link->priv)->stop = 1;
+			mod_timer(&link->release, jiffies + HZ/20);
+		}
+		break;
+
+	case CS_EVENT_CARD_INSERTION:
+		DEBUG(0, " event: insert\n");
+		link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
+		info->bus    =  args->bus;
+		nsp_config(link);
+		break;
+
+	case CS_EVENT_PM_SUSPEND:
+		link->state |= DEV_SUSPEND;
+		/* Fall through... */
+	case CS_EVENT_RESET_PHYSICAL:
+		/* Mark the device as stopped, to block IO until later */
+		info->stop = 1;
+		if (link->state & DEV_CONFIG) {
+			CardServices(ReleaseConfiguration, link->handle);
+		}
+		break;
+
+	case CS_EVENT_PM_RESUME:
+		link->state &= ~DEV_SUSPEND;
+		/* Fall through... */
+	case CS_EVENT_CARD_RESET:
+		DEBUG(0, " event: reset\n");
+		if (link->state & DEV_CONFIG) {
+			Scsi_Cmnd tmp;
+
+			CardServices(RequestConfiguration, link->handle, &link->conf);
+			tmp.host = info->host;
+			nsp_reset(&tmp, 0);
+		}
+		info->stop = 0;
+		/* restart IO */
+		nsphw_init();
+		break;
+
+	default:
+		DEBUG(0, " event: unknown\n");
+		break;
+	}
+	DEBUG(0, __FUNCTION__ " end\n");
+	return 0;
+} /* nsp_event */
+
+/*----------------------------------------------------------------*/
+/* look for ninja3 card and init if found			  */
+/*----------------------------------------------------------------*/
+static int nsp_detect(Scsi_Host_Template *sht)
+{
+	struct Scsi_Host *host;	/* registered host structure */
+
+	DEBUG(0, __FUNCTION__ " this_id=%d\n", sht->this_id);
+
+#if (KERNEL_VERSION(2,3,99) > LINUX_VERSION_CODE)
+	sht->proc_dir	  = &proc_scsi_nsp; /* kernel 2.2 */
+#else
+	sht->proc_name	  = "nsp_cs";	    /* kernel 2.4 */
+#endif
+
+	sht->this_id	  = SCSI_INITIATOR_ID;
+
+	request_region(nsp_data.BaseAddress, nsp_data.NumAddress,"nsp_cs");
+	host		  = scsi_register(sht, 0);
+	host->io_port	  = nsp_data.BaseAddress;
+	host->unique_id	  = nsp_data.BaseAddress;
+	host->n_io_port	  = nsp_data.NumAddress;
+	host->irq	  = nsp_data.IrqNumber;
+	host->dma_channel = -1;
+
+	sprintf(nspinfo,
+/* Buffer size is 100 bytes */
+/*  0         1         2         3         4         5         6         7         8         9         0*/
+/*  01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890*/
+   "NinjaSCSI-3/32Bi Driver version 2.0, I/O 0x%04lx-0x%04lx IRQ %2d",
+		host->io_port, host->io_port + host->n_io_port,
+		host->irq);
+	sht->name	  = nspinfo;
+
+	DEBUG(0, __FUNCTION__ " end\n");
+
+	return 1; /* detect done. */
+}
+
+
+/*----------------------------------------------------------------*/
+/* return info string						  */
+/*----------------------------------------------------------------*/
+static const char *nsp_info(struct Scsi_Host *host)
+{
+	return nspinfo;
+}
+
+static int nsp_reset(Scsi_Cmnd *SCpnt, unsigned int why)
+{
+	DEBUG(0, __FUNCTION__ "() SCpnt=0x%p why=%d\n", SCpnt, why);
+
+	return nsp_eh_bus_reset(SCpnt);
+}
+
+static int nsp_abort(Scsi_Cmnd *SCpnt)
+{
+	DEBUG(0, __FUNCTION__ "() SCpnt=0x%p\n", SCpnt);
+	return FAILED;
+}
+
+/*static int nsp_eh_strategy(struct Scsi_Host *Shost)
+{
+}
+static int nsp_eh_abort(Scsi_Cmnd * cmd)
+{
+	DEBUG(0, __FUNCTION__"() SCpnt\n");
+}
+
+static nsp_eh_device_reset(Scsi_Cmnd *)
+{
+}
+*/
+static int nsp_eh_bus_reset(Scsi_Cmnd *SCpnt)
+{
+	unsigned int base = SCpnt->host->io_port;
+	int	     i;
+
+	DEBUG(0, __FUNCTION__ "() SCpnt=0x%p\n", SCpnt);
+
+	nsp_write(base, IRQCONTROL, IRQCONTROL_ALLMASK);
+
+	nsp_index_write(base, SCSIBUSCTRL, SCSI_RST);
+	mdelay(100); /* 100ms */
+	nsp_index_write(base, SCSIBUSCTRL, 0);
+	for(i = 0; i < 5; i++) {
+		(void) nsp_index_read(base, IRQPHASESENCE); /* dummy read */
+	}
+
+	nsp_write(base, IRQCONTROL, IRQCONTROL_ALLCLEAR);
+
+	return 0;
+}
+/*
+static nsp_eh_host_reset(Scsi_Cmnd *)
+{
+}*/
+
+/*======================================================================*
+ *	module entry point
+ *====================================================================*/
+static int __init nsp_init(void)
+{
+	servinfo_t serv;
+
+	DEBUG(0, __FUNCTION__ "() in\n");
+	DEBUG(0, "%s\n", version);
+	CardServices(GetCardServicesInfo, &serv);
+	if (serv.Revision != CS_RELEASE_CODE) {
+		printk(KERN_DEBUG "nsp_cs: Card Services release "
+		       "does not match!\n");
+		return -1;
+	}
+	register_pcmcia_driver(&dev_info, &nsp_attach, &nsp_detach);
+
+	DEBUG(0, __FUNCTION__ "() out\n");
+	return 0;
+}
+
+
+static void __exit nsp_cleanup(void)
+{
+	DEBUG(0, "nsp_cs: unloading\n");
+	unregister_pcmcia_driver(&dev_info);
+	while (dev_list != NULL) {
+		if (dev_list->state & DEV_CONFIG) {
+			nsp_release((u_long)dev_list);
+		}
+		nsp_detach(dev_list);
+	}
+}
+
+module_init(nsp_init);
+module_exit(nsp_cleanup);
+
+/*
+ *
+ *
+ */
+
+/* end */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)