162306a36Sopenharmony_ci/*----------------------------------------------------------------*/
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci   Qlogic linux driver - work in progress. No Warranty express or implied.
462306a36Sopenharmony_ci   Use at your own risk.  Support Tort Reform so you won't have to read all
562306a36Sopenharmony_ci   these silly disclaimers.
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci   Copyright 1994, Tom Zerucha.
862306a36Sopenharmony_ci   tz@execpc.com
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci   Additional Code, and much appreciated help by
1162306a36Sopenharmony_ci   Michael A. Griffith
1262306a36Sopenharmony_ci   grif@cs.ucr.edu
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci   Thanks to Eric Youngdale and Dave Hinds for loadable module and PCMCIA
1562306a36Sopenharmony_ci   help respectively, and for suffering through my foolishness during the
1662306a36Sopenharmony_ci   debugging process.
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci   Reference Qlogic FAS408 Technical Manual, 53408-510-00A, May 10, 1994
1962306a36Sopenharmony_ci   (you can reference it, but it is incomplete and inaccurate in places)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci   Version 0.46 1/30/97 - kernel 1.2.0+
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci   Functions as standalone, loadable, and PCMCIA driver, the latter from
2462306a36Sopenharmony_ci   Dave Hinds' PCMCIA package.
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci   Cleaned up 26/10/2002 by Alan Cox <alan@lxorguk.ukuu.org.uk> as part of the 2.5
2762306a36Sopenharmony_ci   SCSI driver cleanup and audit. This driver still needs work on the
2862306a36Sopenharmony_ci   following
2962306a36Sopenharmony_ci	-	Non terminating hardware waits
3062306a36Sopenharmony_ci	-	Some layering violations with its pcmcia stub
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci   Redistributable under terms of the GNU General Public License
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci   For the avoidance of doubt the "preferred form" of this code is one which
3562306a36Sopenharmony_ci   is in an open non patent encumbered format. Where cryptographic key signing
3662306a36Sopenharmony_ci   forms part of the process of creating an executable the information
3762306a36Sopenharmony_ci   including keys needed to generate an equivalently functional executable
3862306a36Sopenharmony_ci   are deemed to be part of the source code.
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci*/
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#include <linux/module.h>
4362306a36Sopenharmony_ci#include <linux/blkdev.h>		/* to get disk capacity */
4462306a36Sopenharmony_ci#include <linux/kernel.h>
4562306a36Sopenharmony_ci#include <linux/string.h>
4662306a36Sopenharmony_ci#include <linux/init.h>
4762306a36Sopenharmony_ci#include <linux/interrupt.h>
4862306a36Sopenharmony_ci#include <linux/ioport.h>
4962306a36Sopenharmony_ci#include <linux/proc_fs.h>
5062306a36Sopenharmony_ci#include <linux/unistd.h>
5162306a36Sopenharmony_ci#include <linux/spinlock.h>
5262306a36Sopenharmony_ci#include <linux/stat.h>
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci#include <asm/io.h>
5562306a36Sopenharmony_ci#include <asm/irq.h>
5662306a36Sopenharmony_ci#include <asm/dma.h>
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#include <scsi/scsi.h>
5962306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h>
6062306a36Sopenharmony_ci#include <scsi/scsi_device.h>
6162306a36Sopenharmony_ci#include <scsi/scsi_eh.h>
6262306a36Sopenharmony_ci#include <scsi/scsi_host.h>
6362306a36Sopenharmony_ci#include <scsi/scsi_tcq.h>
6462306a36Sopenharmony_ci#include "qlogicfas408.h"
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/*----------------------------------------------------------------*/
6762306a36Sopenharmony_cistatic int qlcfg5 = (XTALFREQ << 5);	/* 15625/512 */
6862306a36Sopenharmony_cistatic int qlcfg6 = SYNCXFRPD;
6962306a36Sopenharmony_cistatic int qlcfg7 = SYNCOFFST;
7062306a36Sopenharmony_cistatic int qlcfg8 = (SLOWCABLE << 7) | (QL_ENABLE_PARITY << 4);
7162306a36Sopenharmony_cistatic int qlcfg9 = ((XTALFREQ + 4) / 5);
7262306a36Sopenharmony_cistatic int qlcfgc = (FASTCLK << 3) | (FASTSCSI << 4);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/*----------------------------------------------------------------*/
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/*----------------------------------------------------------------*/
7762306a36Sopenharmony_ci/* local functions */
7862306a36Sopenharmony_ci/*----------------------------------------------------------------*/
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci/* error recovery - reset everything */
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic void ql_zap(struct qlogicfas408_priv *priv)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	int x;
8562306a36Sopenharmony_ci	int qbase = priv->qbase;
8662306a36Sopenharmony_ci	int int_type = priv->int_type;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	x = inb(qbase + 0xd);
8962306a36Sopenharmony_ci	REG0;
9062306a36Sopenharmony_ci	outb(3, qbase + 3);	/* reset SCSI */
9162306a36Sopenharmony_ci	outb(2, qbase + 3);	/* reset chip */
9262306a36Sopenharmony_ci	if (x & 0x80)
9362306a36Sopenharmony_ci		REG1;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci/*
9762306a36Sopenharmony_ci *	Do a pseudo-dma tranfer
9862306a36Sopenharmony_ci */
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int ql_pdma(struct qlogicfas408_priv *priv, int phase, char *request,
10162306a36Sopenharmony_ci		   int reqlen)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	int j;
10462306a36Sopenharmony_ci	int qbase = priv->qbase;
10562306a36Sopenharmony_ci	j = 0;
10662306a36Sopenharmony_ci	if (phase & 1) {	/* in */
10762306a36Sopenharmony_ci#if QL_TURBO_PDMA
10862306a36Sopenharmony_ci		rtrc(4)
10962306a36Sopenharmony_ci		/* empty fifo in large chunks */
11062306a36Sopenharmony_ci		if (reqlen >= 128 && (inb(qbase + 8) & 2)) {	/* full */
11162306a36Sopenharmony_ci			insl(qbase + 4, request, 32);
11262306a36Sopenharmony_ci			reqlen -= 128;
11362306a36Sopenharmony_ci			request += 128;
11462306a36Sopenharmony_ci		}
11562306a36Sopenharmony_ci		while (reqlen >= 84 && !(j & 0xc0))	/* 2/3 */
11662306a36Sopenharmony_ci			if ((j = inb(qbase + 8)) & 4)
11762306a36Sopenharmony_ci			{
11862306a36Sopenharmony_ci				insl(qbase + 4, request, 21);
11962306a36Sopenharmony_ci				reqlen -= 84;
12062306a36Sopenharmony_ci				request += 84;
12162306a36Sopenharmony_ci			}
12262306a36Sopenharmony_ci		if (reqlen >= 44 && (inb(qbase + 8) & 8)) {	/* 1/3 */
12362306a36Sopenharmony_ci			insl(qbase + 4, request, 11);
12462306a36Sopenharmony_ci			reqlen -= 44;
12562306a36Sopenharmony_ci			request += 44;
12662306a36Sopenharmony_ci		}
12762306a36Sopenharmony_ci#endif
12862306a36Sopenharmony_ci		/* until both empty and int (or until reclen is 0) */
12962306a36Sopenharmony_ci		rtrc(7)
13062306a36Sopenharmony_ci		j = 0;
13162306a36Sopenharmony_ci		while (reqlen && !((j & 0x10) && (j & 0xc0)))
13262306a36Sopenharmony_ci		{
13362306a36Sopenharmony_ci			/* while bytes to receive and not empty */
13462306a36Sopenharmony_ci			j &= 0xc0;
13562306a36Sopenharmony_ci			while (reqlen && !((j = inb(qbase + 8)) & 0x10))
13662306a36Sopenharmony_ci			{
13762306a36Sopenharmony_ci				*request++ = inb(qbase + 4);
13862306a36Sopenharmony_ci				reqlen--;
13962306a36Sopenharmony_ci			}
14062306a36Sopenharmony_ci			if (j & 0x10)
14162306a36Sopenharmony_ci				j = inb(qbase + 8);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci		}
14462306a36Sopenharmony_ci	} else {		/* out */
14562306a36Sopenharmony_ci#if QL_TURBO_PDMA
14662306a36Sopenharmony_ci		rtrc(4)
14762306a36Sopenharmony_ci		if (reqlen >= 128 && inb(qbase + 8) & 0x10) {	/* empty */
14862306a36Sopenharmony_ci			outsl(qbase + 4, request, 32);
14962306a36Sopenharmony_ci			reqlen -= 128;
15062306a36Sopenharmony_ci			request += 128;
15162306a36Sopenharmony_ci		}
15262306a36Sopenharmony_ci		while (reqlen >= 84 && !(j & 0xc0))	/* 1/3 */
15362306a36Sopenharmony_ci			if (!((j = inb(qbase + 8)) & 8)) {
15462306a36Sopenharmony_ci				outsl(qbase + 4, request, 21);
15562306a36Sopenharmony_ci				reqlen -= 84;
15662306a36Sopenharmony_ci				request += 84;
15762306a36Sopenharmony_ci			}
15862306a36Sopenharmony_ci		if (reqlen >= 40 && !(inb(qbase + 8) & 4)) {	/* 2/3 */
15962306a36Sopenharmony_ci			outsl(qbase + 4, request, 10);
16062306a36Sopenharmony_ci			reqlen -= 40;
16162306a36Sopenharmony_ci			request += 40;
16262306a36Sopenharmony_ci		}
16362306a36Sopenharmony_ci#endif
16462306a36Sopenharmony_ci		/* until full and int (or until reclen is 0) */
16562306a36Sopenharmony_ci		rtrc(7)
16662306a36Sopenharmony_ci		    j = 0;
16762306a36Sopenharmony_ci		while (reqlen && !((j & 2) && (j & 0xc0))) {
16862306a36Sopenharmony_ci			/* while bytes to send and not full */
16962306a36Sopenharmony_ci			while (reqlen && !((j = inb(qbase + 8)) & 2))
17062306a36Sopenharmony_ci			{
17162306a36Sopenharmony_ci				outb(*request++, qbase + 4);
17262306a36Sopenharmony_ci				reqlen--;
17362306a36Sopenharmony_ci			}
17462306a36Sopenharmony_ci			if (j & 2)
17562306a36Sopenharmony_ci				j = inb(qbase + 8);
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci	/* maybe return reqlen */
17962306a36Sopenharmony_ci	return inb(qbase + 8) & 0xc0;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci/*
18362306a36Sopenharmony_ci *	Wait for interrupt flag (polled - not real hardware interrupt)
18462306a36Sopenharmony_ci */
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic int ql_wai(struct qlogicfas408_priv *priv)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	int k;
18962306a36Sopenharmony_ci	int qbase = priv->qbase;
19062306a36Sopenharmony_ci	unsigned long i;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	k = 0;
19362306a36Sopenharmony_ci	i = jiffies + WATCHDOG;
19462306a36Sopenharmony_ci	while (time_before(jiffies, i) && !priv->qabort &&
19562306a36Sopenharmony_ci					!((k = inb(qbase + 4)) & 0xe0)) {
19662306a36Sopenharmony_ci		barrier();
19762306a36Sopenharmony_ci		cpu_relax();
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci	if (time_after_eq(jiffies, i))
20062306a36Sopenharmony_ci		return (DID_TIME_OUT);
20162306a36Sopenharmony_ci	if (priv->qabort)
20262306a36Sopenharmony_ci		return (priv->qabort == 1 ? DID_ABORT : DID_RESET);
20362306a36Sopenharmony_ci	if (k & 0x60)
20462306a36Sopenharmony_ci		ql_zap(priv);
20562306a36Sopenharmony_ci	if (k & 0x20)
20662306a36Sopenharmony_ci		return (DID_PARITY);
20762306a36Sopenharmony_ci	if (k & 0x40)
20862306a36Sopenharmony_ci		return (DID_ERROR);
20962306a36Sopenharmony_ci	return 0;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/*
21362306a36Sopenharmony_ci *	Initiate scsi command - queueing handler
21462306a36Sopenharmony_ci *	caller must hold host lock
21562306a36Sopenharmony_ci */
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic void ql_icmd(struct scsi_cmnd *cmd)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
22062306a36Sopenharmony_ci	int	qbase = priv->qbase;
22162306a36Sopenharmony_ci	int	int_type = priv->int_type;
22262306a36Sopenharmony_ci	unsigned int i;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	priv->qabort = 0;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	REG0;
22762306a36Sopenharmony_ci	/* clearing of interrupts and the fifo is needed */
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	inb(qbase + 5);		/* clear interrupts */
23062306a36Sopenharmony_ci	if (inb(qbase + 5))	/* if still interrupting */
23162306a36Sopenharmony_ci		outb(2, qbase + 3);	/* reset chip */
23262306a36Sopenharmony_ci	else if (inb(qbase + 7) & 0x1f)
23362306a36Sopenharmony_ci		outb(1, qbase + 3);	/* clear fifo */
23462306a36Sopenharmony_ci	while (inb(qbase + 5));	/* clear ints */
23562306a36Sopenharmony_ci	REG1;
23662306a36Sopenharmony_ci	outb(1, qbase + 8);	/* set for PIO pseudo DMA */
23762306a36Sopenharmony_ci	outb(0, qbase + 0xb);	/* disable ints */
23862306a36Sopenharmony_ci	inb(qbase + 8);		/* clear int bits */
23962306a36Sopenharmony_ci	REG0;
24062306a36Sopenharmony_ci	outb(0x40, qbase + 0xb);	/* enable features */
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	/* configurables */
24362306a36Sopenharmony_ci	outb(qlcfgc, qbase + 0xc);
24462306a36Sopenharmony_ci	/* config: no reset interrupt, (initiator) bus id */
24562306a36Sopenharmony_ci	outb(0x40 | qlcfg8 | priv->qinitid, qbase + 8);
24662306a36Sopenharmony_ci	outb(qlcfg7, qbase + 7);
24762306a36Sopenharmony_ci	outb(qlcfg6, qbase + 6);
24862306a36Sopenharmony_ci	outb(qlcfg5, qbase + 5);	/* select timer */
24962306a36Sopenharmony_ci	outb(qlcfg9 & 7, qbase + 9);	/* prescaler */
25062306a36Sopenharmony_ci/*	outb(0x99, qbase + 5);	*/
25162306a36Sopenharmony_ci	outb(scmd_id(cmd), qbase + 4);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	for (i = 0; i < cmd->cmd_len; i++)
25462306a36Sopenharmony_ci		outb(cmd->cmnd[i], qbase + 2);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	priv->qlcmd = cmd;
25762306a36Sopenharmony_ci	outb(0x41, qbase + 3);	/* select and send command */
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci/*
26162306a36Sopenharmony_ci *	Process scsi command - usually after interrupt
26262306a36Sopenharmony_ci */
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic void ql_pcmd(struct scsi_cmnd *cmd)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	unsigned int i, j;
26762306a36Sopenharmony_ci	unsigned long k;
26862306a36Sopenharmony_ci	unsigned int status;	/* scsi returned status */
26962306a36Sopenharmony_ci	unsigned int message;	/* scsi returned message */
27062306a36Sopenharmony_ci	unsigned int phase;	/* recorded scsi phase */
27162306a36Sopenharmony_ci	unsigned int reqlen;	/* total length of transfer */
27262306a36Sopenharmony_ci	char *buf;
27362306a36Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
27462306a36Sopenharmony_ci	int qbase = priv->qbase;
27562306a36Sopenharmony_ci	int int_type = priv->int_type;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	rtrc(1)
27862306a36Sopenharmony_ci	j = inb(qbase + 6);
27962306a36Sopenharmony_ci	i = inb(qbase + 5);
28062306a36Sopenharmony_ci	if (i == 0x20) {
28162306a36Sopenharmony_ci		set_host_byte(cmd, DID_NO_CONNECT);
28262306a36Sopenharmony_ci		return;
28362306a36Sopenharmony_ci	}
28462306a36Sopenharmony_ci	i |= inb(qbase + 5);	/* the 0x10 bit can be set after the 0x08 */
28562306a36Sopenharmony_ci	if (i != 0x18) {
28662306a36Sopenharmony_ci		printk(KERN_ERR "Ql:Bad Interrupt status:%02x\n", i);
28762306a36Sopenharmony_ci		ql_zap(priv);
28862306a36Sopenharmony_ci		set_host_byte(cmd, DID_BAD_INTR);
28962306a36Sopenharmony_ci		return;
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci	j &= 7;			/* j = inb( qbase + 7 ) >> 5; */
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	/* correct status is supposed to be step 4 */
29462306a36Sopenharmony_ci	/* it sometimes returns step 3 but with 0 bytes left to send */
29562306a36Sopenharmony_ci	/* We can try stuffing the FIFO with the max each time, but we will get a
29662306a36Sopenharmony_ci	   sequence of 3 if any bytes are left (but we do flush the FIFO anyway */
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	if (j != 3 && j != 4) {
29962306a36Sopenharmony_ci		printk(KERN_ERR "Ql:Bad sequence for command %d, int %02X, cmdleft = %d\n",
30062306a36Sopenharmony_ci		     j, i, inb(qbase + 7) & 0x1f);
30162306a36Sopenharmony_ci		ql_zap(priv);
30262306a36Sopenharmony_ci		set_host_byte(cmd, DID_ERROR);
30362306a36Sopenharmony_ci		return;
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	if (inb(qbase + 7) & 0x1f)	/* if some bytes in fifo */
30762306a36Sopenharmony_ci		outb(1, qbase + 3);	/* clear fifo */
30862306a36Sopenharmony_ci	/* note that request_bufflen is the total xfer size when sg is used */
30962306a36Sopenharmony_ci	reqlen = scsi_bufflen(cmd);
31062306a36Sopenharmony_ci	/* note that it won't work if transfers > 16M are requested */
31162306a36Sopenharmony_ci	if (reqlen && !((phase = inb(qbase + 4)) & 6)) {	/* data phase */
31262306a36Sopenharmony_ci		struct scatterlist *sg;
31362306a36Sopenharmony_ci		rtrc(2)
31462306a36Sopenharmony_ci		outb(reqlen, qbase);	/* low-mid xfer cnt */
31562306a36Sopenharmony_ci		outb(reqlen >> 8, qbase + 1);	/* low-mid xfer cnt */
31662306a36Sopenharmony_ci		outb(reqlen >> 16, qbase + 0xe);	/* high xfer cnt */
31762306a36Sopenharmony_ci		outb(0x90, qbase + 3);	/* command do xfer */
31862306a36Sopenharmony_ci		/* PIO pseudo DMA to buffer or sglist */
31962306a36Sopenharmony_ci		REG1;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci		scsi_for_each_sg(cmd, sg, scsi_sg_count(cmd), i) {
32262306a36Sopenharmony_ci			if (priv->qabort) {
32362306a36Sopenharmony_ci				REG0;
32462306a36Sopenharmony_ci				set_host_byte(cmd,
32562306a36Sopenharmony_ci					      priv->qabort == 1 ?
32662306a36Sopenharmony_ci					      DID_ABORT : DID_RESET);
32762306a36Sopenharmony_ci			}
32862306a36Sopenharmony_ci			buf = sg_virt(sg);
32962306a36Sopenharmony_ci			if (ql_pdma(priv, phase, buf, sg->length))
33062306a36Sopenharmony_ci				break;
33162306a36Sopenharmony_ci		}
33262306a36Sopenharmony_ci		REG0;
33362306a36Sopenharmony_ci		rtrc(2);
33462306a36Sopenharmony_ci		/*
33562306a36Sopenharmony_ci		 *	Wait for irq (split into second state of irq handler
33662306a36Sopenharmony_ci		 *	if this can take time)
33762306a36Sopenharmony_ci		 */
33862306a36Sopenharmony_ci		if ((k = ql_wai(priv))) {
33962306a36Sopenharmony_ci			set_host_byte(cmd, k);
34062306a36Sopenharmony_ci			return;
34162306a36Sopenharmony_ci		}
34262306a36Sopenharmony_ci		k = inb(qbase + 5);	/* should be 0x10, bus service */
34362306a36Sopenharmony_ci	}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/*
34662306a36Sopenharmony_ci	 *	Enter Status (and Message In) Phase
34762306a36Sopenharmony_ci	 */
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	k = jiffies + WATCHDOG;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	while (time_before(jiffies, k) && !priv->qabort &&
35262306a36Sopenharmony_ci						!(inb(qbase + 4) & 6))
35362306a36Sopenharmony_ci		cpu_relax();	/* wait for status phase */
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	if (time_after_eq(jiffies, k)) {
35662306a36Sopenharmony_ci		ql_zap(priv);
35762306a36Sopenharmony_ci		set_host_byte(cmd, DID_TIME_OUT);
35862306a36Sopenharmony_ci		return;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	/* FIXME: timeout ?? */
36262306a36Sopenharmony_ci	while (inb(qbase + 5))
36362306a36Sopenharmony_ci		cpu_relax();	/* clear pending ints */
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	if (priv->qabort) {
36662306a36Sopenharmony_ci		set_host_byte(cmd,
36762306a36Sopenharmony_ci			      priv->qabort == 1 ? DID_ABORT : DID_RESET);
36862306a36Sopenharmony_ci		return;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	outb(0x11, qbase + 3);	/* get status and message */
37262306a36Sopenharmony_ci	if ((k = ql_wai(priv))) {
37362306a36Sopenharmony_ci		set_host_byte(cmd, k);
37462306a36Sopenharmony_ci		return;
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci	i = inb(qbase + 5);	/* get chip irq stat */
37762306a36Sopenharmony_ci	j = inb(qbase + 7) & 0x1f;	/* and bytes rec'd */
37862306a36Sopenharmony_ci	status = inb(qbase + 2);
37962306a36Sopenharmony_ci	message = inb(qbase + 2);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	/*
38262306a36Sopenharmony_ci	 *	Should get function complete int if Status and message, else
38362306a36Sopenharmony_ci	 *	bus serv if only status
38462306a36Sopenharmony_ci	 */
38562306a36Sopenharmony_ci	if (!((i == 8 && j == 2) || (i == 0x10 && j == 1))) {
38662306a36Sopenharmony_ci		printk(KERN_ERR "Ql:Error during status phase, int=%02X, %d bytes recd\n", i, j);
38762306a36Sopenharmony_ci		set_host_byte(cmd, DID_ERROR);
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci	outb(0x12, qbase + 3);	/* done, disconnect */
39062306a36Sopenharmony_ci	rtrc(1);
39162306a36Sopenharmony_ci	if ((k = ql_wai(priv))) {
39262306a36Sopenharmony_ci		set_host_byte(cmd, k);
39362306a36Sopenharmony_ci		return;
39462306a36Sopenharmony_ci	}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	/*
39762306a36Sopenharmony_ci	 *	Should get bus service interrupt and disconnect interrupt
39862306a36Sopenharmony_ci	 */
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	i = inb(qbase + 5);	/* should be bus service */
40162306a36Sopenharmony_ci	while (!priv->qabort && ((i & 0x20) != 0x20)) {
40262306a36Sopenharmony_ci		barrier();
40362306a36Sopenharmony_ci		cpu_relax();
40462306a36Sopenharmony_ci		i |= inb(qbase + 5);
40562306a36Sopenharmony_ci	}
40662306a36Sopenharmony_ci	rtrc(0);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	if (priv->qabort) {
40962306a36Sopenharmony_ci		set_host_byte(cmd,
41062306a36Sopenharmony_ci			      priv->qabort == 1 ? DID_ABORT : DID_RESET);
41162306a36Sopenharmony_ci		return;
41262306a36Sopenharmony_ci	}
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	set_host_byte(cmd, DID_OK);
41562306a36Sopenharmony_ci	if (message != COMMAND_COMPLETE)
41662306a36Sopenharmony_ci		scsi_msg_to_host_byte(cmd, message);
41762306a36Sopenharmony_ci	set_status_byte(cmd, status);
41862306a36Sopenharmony_ci	return;
41962306a36Sopenharmony_ci}
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci/*
42262306a36Sopenharmony_ci *	Interrupt handler
42362306a36Sopenharmony_ci */
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_cistatic void ql_ihandl(void *dev_id)
42662306a36Sopenharmony_ci{
42762306a36Sopenharmony_ci	struct scsi_cmnd *icmd;
42862306a36Sopenharmony_ci	struct Scsi_Host *host = dev_id;
42962306a36Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_host(host);
43062306a36Sopenharmony_ci	int qbase = priv->qbase;
43162306a36Sopenharmony_ci	REG0;
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	if (!(inb(qbase + 4) & 0x80))	/* false alarm? */
43462306a36Sopenharmony_ci		return;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	if (priv->qlcmd == NULL) {	/* no command to process? */
43762306a36Sopenharmony_ci		int i;
43862306a36Sopenharmony_ci		i = 16;
43962306a36Sopenharmony_ci		while (i-- && inb(qbase + 5));	/* maybe also ql_zap() */
44062306a36Sopenharmony_ci		return;
44162306a36Sopenharmony_ci	}
44262306a36Sopenharmony_ci	icmd = priv->qlcmd;
44362306a36Sopenharmony_ci	ql_pcmd(icmd);
44462306a36Sopenharmony_ci	priv->qlcmd = NULL;
44562306a36Sopenharmony_ci	/*
44662306a36Sopenharmony_ci	 *	If result is CHECK CONDITION done calls qcommand to request
44762306a36Sopenharmony_ci	 *	sense
44862306a36Sopenharmony_ci	 */
44962306a36Sopenharmony_ci	scsi_done(icmd);
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ciirqreturn_t qlogicfas408_ihandl(int irq, void *dev_id)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	unsigned long flags;
45562306a36Sopenharmony_ci	struct Scsi_Host *host = dev_id;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	spin_lock_irqsave(host->host_lock, flags);
45862306a36Sopenharmony_ci	ql_ihandl(dev_id);
45962306a36Sopenharmony_ci	spin_unlock_irqrestore(host->host_lock, flags);
46062306a36Sopenharmony_ci	return IRQ_HANDLED;
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci/*
46462306a36Sopenharmony_ci *	Queued command
46562306a36Sopenharmony_ci */
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cistatic int qlogicfas408_queuecommand_lck(struct scsi_cmnd *cmd)
46862306a36Sopenharmony_ci{
46962306a36Sopenharmony_ci	void (*done)(struct scsi_cmnd *) = scsi_done;
47062306a36Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	set_host_byte(cmd, DID_OK);
47362306a36Sopenharmony_ci	set_status_byte(cmd, SAM_STAT_GOOD);
47462306a36Sopenharmony_ci	if (scmd_id(cmd) == priv->qinitid) {
47562306a36Sopenharmony_ci		set_host_byte(cmd, DID_BAD_TARGET);
47662306a36Sopenharmony_ci		done(cmd);
47762306a36Sopenharmony_ci		return 0;
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	/* wait for the last command's interrupt to finish */
48162306a36Sopenharmony_ci	while (priv->qlcmd != NULL) {
48262306a36Sopenharmony_ci		barrier();
48362306a36Sopenharmony_ci		cpu_relax();
48462306a36Sopenharmony_ci	}
48562306a36Sopenharmony_ci	ql_icmd(cmd);
48662306a36Sopenharmony_ci	return 0;
48762306a36Sopenharmony_ci}
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ciDEF_SCSI_QCMD(qlogicfas408_queuecommand)
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci/*
49262306a36Sopenharmony_ci *	Return bios parameters
49362306a36Sopenharmony_ci */
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ciint qlogicfas408_biosparam(struct scsi_device *disk, struct block_device *dev,
49662306a36Sopenharmony_ci			   sector_t capacity, int ip[])
49762306a36Sopenharmony_ci{
49862306a36Sopenharmony_ci/* This should mimic the DOS Qlogic driver's behavior exactly */
49962306a36Sopenharmony_ci	ip[0] = 0x40;
50062306a36Sopenharmony_ci	ip[1] = 0x20;
50162306a36Sopenharmony_ci	ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);
50262306a36Sopenharmony_ci	if (ip[2] > 1024) {
50362306a36Sopenharmony_ci		ip[0] = 0xff;
50462306a36Sopenharmony_ci		ip[1] = 0x3f;
50562306a36Sopenharmony_ci		ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);
50662306a36Sopenharmony_ci#if 0
50762306a36Sopenharmony_ci		if (ip[2] > 1023)
50862306a36Sopenharmony_ci			ip[2] = 1023;
50962306a36Sopenharmony_ci#endif
51062306a36Sopenharmony_ci	}
51162306a36Sopenharmony_ci	return 0;
51262306a36Sopenharmony_ci}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci/*
51562306a36Sopenharmony_ci *	Abort a command in progress
51662306a36Sopenharmony_ci */
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ciint qlogicfas408_abort(struct scsi_cmnd *cmd)
51962306a36Sopenharmony_ci{
52062306a36Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
52162306a36Sopenharmony_ci	priv->qabort = 1;
52262306a36Sopenharmony_ci	ql_zap(priv);
52362306a36Sopenharmony_ci	return SUCCESS;
52462306a36Sopenharmony_ci}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci/*
52762306a36Sopenharmony_ci *	Reset SCSI bus
52862306a36Sopenharmony_ci *	FIXME: This function is invoked with cmd = NULL directly by
52962306a36Sopenharmony_ci *	the PCMCIA qlogic_stub code. This wants fixing
53062306a36Sopenharmony_ci */
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ciint qlogicfas408_host_reset(struct scsi_cmnd *cmd)
53362306a36Sopenharmony_ci{
53462306a36Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
53562306a36Sopenharmony_ci	unsigned long flags;
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	priv->qabort = 2;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	spin_lock_irqsave(cmd->device->host->host_lock, flags);
54062306a36Sopenharmony_ci	ql_zap(priv);
54162306a36Sopenharmony_ci	spin_unlock_irqrestore(cmd->device->host->host_lock, flags);
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	return SUCCESS;
54462306a36Sopenharmony_ci}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci/*
54762306a36Sopenharmony_ci *	Return info string
54862306a36Sopenharmony_ci */
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ciconst char *qlogicfas408_info(struct Scsi_Host *host)
55162306a36Sopenharmony_ci{
55262306a36Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_host(host);
55362306a36Sopenharmony_ci	return priv->qinfo;
55462306a36Sopenharmony_ci}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci/*
55762306a36Sopenharmony_ci *	Get type of chip
55862306a36Sopenharmony_ci */
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ciint qlogicfas408_get_chip_type(int qbase, int int_type)
56162306a36Sopenharmony_ci{
56262306a36Sopenharmony_ci	REG1;
56362306a36Sopenharmony_ci	return inb(qbase + 0xe) & 0xf8;
56462306a36Sopenharmony_ci}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci/*
56762306a36Sopenharmony_ci *	Perform initialization tasks
56862306a36Sopenharmony_ci */
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_civoid qlogicfas408_setup(int qbase, int id, int int_type)
57162306a36Sopenharmony_ci{
57262306a36Sopenharmony_ci	outb(1, qbase + 8);	/* set for PIO pseudo DMA */
57362306a36Sopenharmony_ci	REG0;
57462306a36Sopenharmony_ci	outb(0x40 | qlcfg8 | id, qbase + 8);	/* (ini) bus id, disable scsi rst */
57562306a36Sopenharmony_ci	outb(qlcfg5, qbase + 5);	/* select timer */
57662306a36Sopenharmony_ci	outb(qlcfg9, qbase + 9);	/* prescaler */
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci#if QL_RESET_AT_START
57962306a36Sopenharmony_ci	outb(3, qbase + 3);
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	REG1;
58262306a36Sopenharmony_ci	/* FIXME: timeout */
58362306a36Sopenharmony_ci	while (inb(qbase + 0xf) & 4)
58462306a36Sopenharmony_ci		cpu_relax();
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	REG0;
58762306a36Sopenharmony_ci#endif
58862306a36Sopenharmony_ci}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci/*
59162306a36Sopenharmony_ci *	Checks if this is a QLogic FAS 408
59262306a36Sopenharmony_ci */
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ciint qlogicfas408_detect(int qbase, int int_type)
59562306a36Sopenharmony_ci{
59662306a36Sopenharmony_ci	REG1;
59762306a36Sopenharmony_ci	return (((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7) &&
59862306a36Sopenharmony_ci		((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7));
59962306a36Sopenharmony_ci}
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci/*
60262306a36Sopenharmony_ci *	Disable interrupts
60362306a36Sopenharmony_ci */
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_civoid qlogicfas408_disable_ints(struct qlogicfas408_priv *priv)
60662306a36Sopenharmony_ci{
60762306a36Sopenharmony_ci	int qbase = priv->qbase;
60862306a36Sopenharmony_ci	int int_type = priv->int_type;
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	REG1;
61162306a36Sopenharmony_ci	outb(0, qbase + 0xb);	/* disable ints */
61262306a36Sopenharmony_ci}
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci/*
61562306a36Sopenharmony_ci *	Init and exit functions
61662306a36Sopenharmony_ci */
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cistatic int __init qlogicfas408_init(void)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	return 0;
62162306a36Sopenharmony_ci}
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_cistatic void __exit qlogicfas408_exit(void)
62462306a36Sopenharmony_ci{
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci}
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ciMODULE_AUTHOR("Tom Zerucha, Michael Griffith");
62962306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for the Qlogic FAS SCSI controllers");
63062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
63162306a36Sopenharmony_cimodule_init(qlogicfas408_init);
63262306a36Sopenharmony_cimodule_exit(qlogicfas408_exit);
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_info);
63562306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_queuecommand);
63662306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_abort);
63762306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_host_reset);
63862306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_biosparam);
63962306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_ihandl);
64062306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_get_chip_type);
64162306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_setup);
64262306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_detect);
64362306a36Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_disable_ints);
64462306a36Sopenharmony_ci
645