18c2ecf20Sopenharmony_ci/*----------------------------------------------------------------*/
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci   Qlogic linux driver - work in progress. No Warranty express or implied.
48c2ecf20Sopenharmony_ci   Use at your own risk.  Support Tort Reform so you won't have to read all
58c2ecf20Sopenharmony_ci   these silly disclaimers.
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci   Copyright 1994, Tom Zerucha.
88c2ecf20Sopenharmony_ci   tz@execpc.com
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci   Additional Code, and much appreciated help by
118c2ecf20Sopenharmony_ci   Michael A. Griffith
128c2ecf20Sopenharmony_ci   grif@cs.ucr.edu
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci   Thanks to Eric Youngdale and Dave Hinds for loadable module and PCMCIA
158c2ecf20Sopenharmony_ci   help respectively, and for suffering through my foolishness during the
168c2ecf20Sopenharmony_ci   debugging process.
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci   Reference Qlogic FAS408 Technical Manual, 53408-510-00A, May 10, 1994
198c2ecf20Sopenharmony_ci   (you can reference it, but it is incomplete and inaccurate in places)
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci   Version 0.46 1/30/97 - kernel 1.2.0+
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci   Functions as standalone, loadable, and PCMCIA driver, the latter from
248c2ecf20Sopenharmony_ci   Dave Hinds' PCMCIA package.
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci   Cleaned up 26/10/2002 by Alan Cox <alan@lxorguk.ukuu.org.uk> as part of the 2.5
278c2ecf20Sopenharmony_ci   SCSI driver cleanup and audit. This driver still needs work on the
288c2ecf20Sopenharmony_ci   following
298c2ecf20Sopenharmony_ci   	-	Non terminating hardware waits
308c2ecf20Sopenharmony_ci   	-	Some layering violations with its pcmcia stub
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci   Redistributable under terms of the GNU General Public License
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci   For the avoidance of doubt the "preferred form" of this code is one which
358c2ecf20Sopenharmony_ci   is in an open non patent encumbered format. Where cryptographic key signing
368c2ecf20Sopenharmony_ci   forms part of the process of creating an executable the information
378c2ecf20Sopenharmony_ci   including keys needed to generate an equivalently functional executable
388c2ecf20Sopenharmony_ci   are deemed to be part of the source code.
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci*/
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#include <linux/module.h>
438c2ecf20Sopenharmony_ci#include <linux/blkdev.h>		/* to get disk capacity */
448c2ecf20Sopenharmony_ci#include <linux/kernel.h>
458c2ecf20Sopenharmony_ci#include <linux/string.h>
468c2ecf20Sopenharmony_ci#include <linux/init.h>
478c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
488c2ecf20Sopenharmony_ci#include <linux/ioport.h>
498c2ecf20Sopenharmony_ci#include <linux/proc_fs.h>
508c2ecf20Sopenharmony_ci#include <linux/unistd.h>
518c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
528c2ecf20Sopenharmony_ci#include <linux/stat.h>
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci#include <asm/io.h>
558c2ecf20Sopenharmony_ci#include <asm/irq.h>
568c2ecf20Sopenharmony_ci#include <asm/dma.h>
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci#include "scsi.h"
598c2ecf20Sopenharmony_ci#include <scsi/scsi_host.h>
608c2ecf20Sopenharmony_ci#include "qlogicfas408.h"
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci/*----------------------------------------------------------------*/
638c2ecf20Sopenharmony_cistatic int qlcfg5 = (XTALFREQ << 5);	/* 15625/512 */
648c2ecf20Sopenharmony_cistatic int qlcfg6 = SYNCXFRPD;
658c2ecf20Sopenharmony_cistatic int qlcfg7 = SYNCOFFST;
668c2ecf20Sopenharmony_cistatic int qlcfg8 = (SLOWCABLE << 7) | (QL_ENABLE_PARITY << 4);
678c2ecf20Sopenharmony_cistatic int qlcfg9 = ((XTALFREQ + 4) / 5);
688c2ecf20Sopenharmony_cistatic int qlcfgc = (FASTCLK << 3) | (FASTSCSI << 4);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci/*----------------------------------------------------------------*/
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci/*----------------------------------------------------------------*/
738c2ecf20Sopenharmony_ci/* local functions */
748c2ecf20Sopenharmony_ci/*----------------------------------------------------------------*/
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci/* error recovery - reset everything */
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic void ql_zap(struct qlogicfas408_priv *priv)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	int x;
818c2ecf20Sopenharmony_ci	int qbase = priv->qbase;
828c2ecf20Sopenharmony_ci	int int_type = priv->int_type;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	x = inb(qbase + 0xd);
858c2ecf20Sopenharmony_ci	REG0;
868c2ecf20Sopenharmony_ci	outb(3, qbase + 3);	/* reset SCSI */
878c2ecf20Sopenharmony_ci	outb(2, qbase + 3);	/* reset chip */
888c2ecf20Sopenharmony_ci	if (x & 0x80)
898c2ecf20Sopenharmony_ci		REG1;
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci/*
938c2ecf20Sopenharmony_ci *	Do a pseudo-dma tranfer
948c2ecf20Sopenharmony_ci */
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_cistatic int ql_pdma(struct qlogicfas408_priv *priv, int phase, char *request, int reqlen)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	int j;
998c2ecf20Sopenharmony_ci	int qbase = priv->qbase;
1008c2ecf20Sopenharmony_ci	j = 0;
1018c2ecf20Sopenharmony_ci	if (phase & 1) {	/* in */
1028c2ecf20Sopenharmony_ci#if QL_TURBO_PDMA
1038c2ecf20Sopenharmony_ci		rtrc(4)
1048c2ecf20Sopenharmony_ci		/* empty fifo in large chunks */
1058c2ecf20Sopenharmony_ci		if (reqlen >= 128 && (inb(qbase + 8) & 2)) {	/* full */
1068c2ecf20Sopenharmony_ci			insl(qbase + 4, request, 32);
1078c2ecf20Sopenharmony_ci			reqlen -= 128;
1088c2ecf20Sopenharmony_ci			request += 128;
1098c2ecf20Sopenharmony_ci		}
1108c2ecf20Sopenharmony_ci		while (reqlen >= 84 && !(j & 0xc0))	/* 2/3 */
1118c2ecf20Sopenharmony_ci			if ((j = inb(qbase + 8)) & 4)
1128c2ecf20Sopenharmony_ci			{
1138c2ecf20Sopenharmony_ci				insl(qbase + 4, request, 21);
1148c2ecf20Sopenharmony_ci				reqlen -= 84;
1158c2ecf20Sopenharmony_ci				request += 84;
1168c2ecf20Sopenharmony_ci			}
1178c2ecf20Sopenharmony_ci		if (reqlen >= 44 && (inb(qbase + 8) & 8)) {	/* 1/3 */
1188c2ecf20Sopenharmony_ci			insl(qbase + 4, request, 11);
1198c2ecf20Sopenharmony_ci			reqlen -= 44;
1208c2ecf20Sopenharmony_ci			request += 44;
1218c2ecf20Sopenharmony_ci		}
1228c2ecf20Sopenharmony_ci#endif
1238c2ecf20Sopenharmony_ci		/* until both empty and int (or until reclen is 0) */
1248c2ecf20Sopenharmony_ci		rtrc(7)
1258c2ecf20Sopenharmony_ci		j = 0;
1268c2ecf20Sopenharmony_ci		while (reqlen && !((j & 0x10) && (j & 0xc0)))
1278c2ecf20Sopenharmony_ci		{
1288c2ecf20Sopenharmony_ci			/* while bytes to receive and not empty */
1298c2ecf20Sopenharmony_ci			j &= 0xc0;
1308c2ecf20Sopenharmony_ci			while (reqlen && !((j = inb(qbase + 8)) & 0x10))
1318c2ecf20Sopenharmony_ci			{
1328c2ecf20Sopenharmony_ci				*request++ = inb(qbase + 4);
1338c2ecf20Sopenharmony_ci				reqlen--;
1348c2ecf20Sopenharmony_ci			}
1358c2ecf20Sopenharmony_ci			if (j & 0x10)
1368c2ecf20Sopenharmony_ci				j = inb(qbase + 8);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci		}
1398c2ecf20Sopenharmony_ci	} else {		/* out */
1408c2ecf20Sopenharmony_ci#if QL_TURBO_PDMA
1418c2ecf20Sopenharmony_ci		rtrc(4)
1428c2ecf20Sopenharmony_ci		if (reqlen >= 128 && inb(qbase + 8) & 0x10) {	/* empty */
1438c2ecf20Sopenharmony_ci			outsl(qbase + 4, request, 32);
1448c2ecf20Sopenharmony_ci			reqlen -= 128;
1458c2ecf20Sopenharmony_ci			request += 128;
1468c2ecf20Sopenharmony_ci		}
1478c2ecf20Sopenharmony_ci		while (reqlen >= 84 && !(j & 0xc0))	/* 1/3 */
1488c2ecf20Sopenharmony_ci			if (!((j = inb(qbase + 8)) & 8)) {
1498c2ecf20Sopenharmony_ci				outsl(qbase + 4, request, 21);
1508c2ecf20Sopenharmony_ci				reqlen -= 84;
1518c2ecf20Sopenharmony_ci				request += 84;
1528c2ecf20Sopenharmony_ci			}
1538c2ecf20Sopenharmony_ci		if (reqlen >= 40 && !(inb(qbase + 8) & 4)) {	/* 2/3 */
1548c2ecf20Sopenharmony_ci			outsl(qbase + 4, request, 10);
1558c2ecf20Sopenharmony_ci			reqlen -= 40;
1568c2ecf20Sopenharmony_ci			request += 40;
1578c2ecf20Sopenharmony_ci		}
1588c2ecf20Sopenharmony_ci#endif
1598c2ecf20Sopenharmony_ci		/* until full and int (or until reclen is 0) */
1608c2ecf20Sopenharmony_ci		rtrc(7)
1618c2ecf20Sopenharmony_ci		    j = 0;
1628c2ecf20Sopenharmony_ci		while (reqlen && !((j & 2) && (j & 0xc0))) {
1638c2ecf20Sopenharmony_ci			/* while bytes to send and not full */
1648c2ecf20Sopenharmony_ci			while (reqlen && !((j = inb(qbase + 8)) & 2))
1658c2ecf20Sopenharmony_ci			{
1668c2ecf20Sopenharmony_ci				outb(*request++, qbase + 4);
1678c2ecf20Sopenharmony_ci				reqlen--;
1688c2ecf20Sopenharmony_ci			}
1698c2ecf20Sopenharmony_ci			if (j & 2)
1708c2ecf20Sopenharmony_ci				j = inb(qbase + 8);
1718c2ecf20Sopenharmony_ci		}
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci	/* maybe return reqlen */
1748c2ecf20Sopenharmony_ci	return inb(qbase + 8) & 0xc0;
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci/*
1788c2ecf20Sopenharmony_ci *	Wait for interrupt flag (polled - not real hardware interrupt)
1798c2ecf20Sopenharmony_ci */
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_cistatic int ql_wai(struct qlogicfas408_priv *priv)
1828c2ecf20Sopenharmony_ci{
1838c2ecf20Sopenharmony_ci	int k;
1848c2ecf20Sopenharmony_ci	int qbase = priv->qbase;
1858c2ecf20Sopenharmony_ci	unsigned long i;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	k = 0;
1888c2ecf20Sopenharmony_ci	i = jiffies + WATCHDOG;
1898c2ecf20Sopenharmony_ci	while (time_before(jiffies, i) && !priv->qabort &&
1908c2ecf20Sopenharmony_ci					!((k = inb(qbase + 4)) & 0xe0)) {
1918c2ecf20Sopenharmony_ci		barrier();
1928c2ecf20Sopenharmony_ci		cpu_relax();
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci	if (time_after_eq(jiffies, i))
1958c2ecf20Sopenharmony_ci		return (DID_TIME_OUT);
1968c2ecf20Sopenharmony_ci	if (priv->qabort)
1978c2ecf20Sopenharmony_ci		return (priv->qabort == 1 ? DID_ABORT : DID_RESET);
1988c2ecf20Sopenharmony_ci	if (k & 0x60)
1998c2ecf20Sopenharmony_ci		ql_zap(priv);
2008c2ecf20Sopenharmony_ci	if (k & 0x20)
2018c2ecf20Sopenharmony_ci		return (DID_PARITY);
2028c2ecf20Sopenharmony_ci	if (k & 0x40)
2038c2ecf20Sopenharmony_ci		return (DID_ERROR);
2048c2ecf20Sopenharmony_ci	return 0;
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci/*
2088c2ecf20Sopenharmony_ci *	Initiate scsi command - queueing handler
2098c2ecf20Sopenharmony_ci *	caller must hold host lock
2108c2ecf20Sopenharmony_ci */
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic void ql_icmd(struct scsi_cmnd *cmd)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
2158c2ecf20Sopenharmony_ci	int 	qbase = priv->qbase;
2168c2ecf20Sopenharmony_ci	int	int_type = priv->int_type;
2178c2ecf20Sopenharmony_ci	unsigned int i;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	priv->qabort = 0;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	REG0;
2228c2ecf20Sopenharmony_ci	/* clearing of interrupts and the fifo is needed */
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	inb(qbase + 5);		/* clear interrupts */
2258c2ecf20Sopenharmony_ci	if (inb(qbase + 5))	/* if still interrupting */
2268c2ecf20Sopenharmony_ci		outb(2, qbase + 3);	/* reset chip */
2278c2ecf20Sopenharmony_ci	else if (inb(qbase + 7) & 0x1f)
2288c2ecf20Sopenharmony_ci		outb(1, qbase + 3);	/* clear fifo */
2298c2ecf20Sopenharmony_ci	while (inb(qbase + 5));	/* clear ints */
2308c2ecf20Sopenharmony_ci	REG1;
2318c2ecf20Sopenharmony_ci	outb(1, qbase + 8);	/* set for PIO pseudo DMA */
2328c2ecf20Sopenharmony_ci	outb(0, qbase + 0xb);	/* disable ints */
2338c2ecf20Sopenharmony_ci	inb(qbase + 8);		/* clear int bits */
2348c2ecf20Sopenharmony_ci	REG0;
2358c2ecf20Sopenharmony_ci	outb(0x40, qbase + 0xb);	/* enable features */
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	/* configurables */
2388c2ecf20Sopenharmony_ci	outb(qlcfgc, qbase + 0xc);
2398c2ecf20Sopenharmony_ci	/* config: no reset interrupt, (initiator) bus id */
2408c2ecf20Sopenharmony_ci	outb(0x40 | qlcfg8 | priv->qinitid, qbase + 8);
2418c2ecf20Sopenharmony_ci	outb(qlcfg7, qbase + 7);
2428c2ecf20Sopenharmony_ci	outb(qlcfg6, qbase + 6);
2438c2ecf20Sopenharmony_ci	outb(qlcfg5, qbase + 5);	/* select timer */
2448c2ecf20Sopenharmony_ci	outb(qlcfg9 & 7, qbase + 9);	/* prescaler */
2458c2ecf20Sopenharmony_ci/*	outb(0x99, qbase + 5);	*/
2468c2ecf20Sopenharmony_ci	outb(scmd_id(cmd), qbase + 4);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	for (i = 0; i < cmd->cmd_len; i++)
2498c2ecf20Sopenharmony_ci		outb(cmd->cmnd[i], qbase + 2);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	priv->qlcmd = cmd;
2528c2ecf20Sopenharmony_ci	outb(0x41, qbase + 3);	/* select and send command */
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci/*
2568c2ecf20Sopenharmony_ci *	Process scsi command - usually after interrupt
2578c2ecf20Sopenharmony_ci */
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_cistatic unsigned int ql_pcmd(struct scsi_cmnd *cmd)
2608c2ecf20Sopenharmony_ci{
2618c2ecf20Sopenharmony_ci	unsigned int i, j;
2628c2ecf20Sopenharmony_ci	unsigned long k;
2638c2ecf20Sopenharmony_ci	unsigned int result;	/* ultimate return result */
2648c2ecf20Sopenharmony_ci	unsigned int status;	/* scsi returned status */
2658c2ecf20Sopenharmony_ci	unsigned int message;	/* scsi returned message */
2668c2ecf20Sopenharmony_ci	unsigned int phase;	/* recorded scsi phase */
2678c2ecf20Sopenharmony_ci	unsigned int reqlen;	/* total length of transfer */
2688c2ecf20Sopenharmony_ci	char *buf;
2698c2ecf20Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
2708c2ecf20Sopenharmony_ci	int qbase = priv->qbase;
2718c2ecf20Sopenharmony_ci	int int_type = priv->int_type;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	rtrc(1)
2748c2ecf20Sopenharmony_ci	j = inb(qbase + 6);
2758c2ecf20Sopenharmony_ci	i = inb(qbase + 5);
2768c2ecf20Sopenharmony_ci	if (i == 0x20) {
2778c2ecf20Sopenharmony_ci		return (DID_NO_CONNECT << 16);
2788c2ecf20Sopenharmony_ci	}
2798c2ecf20Sopenharmony_ci	i |= inb(qbase + 5);	/* the 0x10 bit can be set after the 0x08 */
2808c2ecf20Sopenharmony_ci	if (i != 0x18) {
2818c2ecf20Sopenharmony_ci		printk(KERN_ERR "Ql:Bad Interrupt status:%02x\n", i);
2828c2ecf20Sopenharmony_ci		ql_zap(priv);
2838c2ecf20Sopenharmony_ci		return (DID_BAD_INTR << 16);
2848c2ecf20Sopenharmony_ci	}
2858c2ecf20Sopenharmony_ci	j &= 7;			/* j = inb( qbase + 7 ) >> 5; */
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	/* correct status is supposed to be step 4 */
2888c2ecf20Sopenharmony_ci	/* it sometimes returns step 3 but with 0 bytes left to send */
2898c2ecf20Sopenharmony_ci	/* We can try stuffing the FIFO with the max each time, but we will get a
2908c2ecf20Sopenharmony_ci	   sequence of 3 if any bytes are left (but we do flush the FIFO anyway */
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	if (j != 3 && j != 4) {
2938c2ecf20Sopenharmony_ci		printk(KERN_ERR "Ql:Bad sequence for command %d, int %02X, cmdleft = %d\n",
2948c2ecf20Sopenharmony_ci		     j, i, inb(qbase + 7) & 0x1f);
2958c2ecf20Sopenharmony_ci		ql_zap(priv);
2968c2ecf20Sopenharmony_ci		return (DID_ERROR << 16);
2978c2ecf20Sopenharmony_ci	}
2988c2ecf20Sopenharmony_ci	result = DID_OK;
2998c2ecf20Sopenharmony_ci	if (inb(qbase + 7) & 0x1f)	/* if some bytes in fifo */
3008c2ecf20Sopenharmony_ci		outb(1, qbase + 3);	/* clear fifo */
3018c2ecf20Sopenharmony_ci	/* note that request_bufflen is the total xfer size when sg is used */
3028c2ecf20Sopenharmony_ci	reqlen = scsi_bufflen(cmd);
3038c2ecf20Sopenharmony_ci	/* note that it won't work if transfers > 16M are requested */
3048c2ecf20Sopenharmony_ci	if (reqlen && !((phase = inb(qbase + 4)) & 6)) {	/* data phase */
3058c2ecf20Sopenharmony_ci		struct scatterlist *sg;
3068c2ecf20Sopenharmony_ci		rtrc(2)
3078c2ecf20Sopenharmony_ci		outb(reqlen, qbase);	/* low-mid xfer cnt */
3088c2ecf20Sopenharmony_ci		outb(reqlen >> 8, qbase + 1);	/* low-mid xfer cnt */
3098c2ecf20Sopenharmony_ci		outb(reqlen >> 16, qbase + 0xe);	/* high xfer cnt */
3108c2ecf20Sopenharmony_ci		outb(0x90, qbase + 3);	/* command do xfer */
3118c2ecf20Sopenharmony_ci		/* PIO pseudo DMA to buffer or sglist */
3128c2ecf20Sopenharmony_ci		REG1;
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci		scsi_for_each_sg(cmd, sg, scsi_sg_count(cmd), i) {
3158c2ecf20Sopenharmony_ci			if (priv->qabort) {
3168c2ecf20Sopenharmony_ci				REG0;
3178c2ecf20Sopenharmony_ci				return ((priv->qabort == 1 ?
3188c2ecf20Sopenharmony_ci					 DID_ABORT : DID_RESET) << 16);
3198c2ecf20Sopenharmony_ci			}
3208c2ecf20Sopenharmony_ci			buf = sg_virt(sg);
3218c2ecf20Sopenharmony_ci			if (ql_pdma(priv, phase, buf, sg->length))
3228c2ecf20Sopenharmony_ci				break;
3238c2ecf20Sopenharmony_ci		}
3248c2ecf20Sopenharmony_ci		REG0;
3258c2ecf20Sopenharmony_ci		rtrc(2)
3268c2ecf20Sopenharmony_ci		/*
3278c2ecf20Sopenharmony_ci		 *	Wait for irq (split into second state of irq handler
3288c2ecf20Sopenharmony_ci		 *	if this can take time)
3298c2ecf20Sopenharmony_ci		 */
3308c2ecf20Sopenharmony_ci		if ((k = ql_wai(priv)))
3318c2ecf20Sopenharmony_ci			return (k << 16);
3328c2ecf20Sopenharmony_ci		k = inb(qbase + 5);	/* should be 0x10, bus service */
3338c2ecf20Sopenharmony_ci	}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	/*
3368c2ecf20Sopenharmony_ci	 *	Enter Status (and Message In) Phase
3378c2ecf20Sopenharmony_ci	 */
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	k = jiffies + WATCHDOG;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	while (time_before(jiffies, k) && !priv->qabort &&
3428c2ecf20Sopenharmony_ci						!(inb(qbase + 4) & 6))
3438c2ecf20Sopenharmony_ci		cpu_relax();	/* wait for status phase */
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	if (time_after_eq(jiffies, k)) {
3468c2ecf20Sopenharmony_ci		ql_zap(priv);
3478c2ecf20Sopenharmony_ci		return (DID_TIME_OUT << 16);
3488c2ecf20Sopenharmony_ci	}
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	/* FIXME: timeout ?? */
3518c2ecf20Sopenharmony_ci	while (inb(qbase + 5))
3528c2ecf20Sopenharmony_ci		cpu_relax();	/* clear pending ints */
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	if (priv->qabort)
3558c2ecf20Sopenharmony_ci		return ((priv->qabort == 1 ? DID_ABORT : DID_RESET) << 16);
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	outb(0x11, qbase + 3);	/* get status and message */
3588c2ecf20Sopenharmony_ci	if ((k = ql_wai(priv)))
3598c2ecf20Sopenharmony_ci		return (k << 16);
3608c2ecf20Sopenharmony_ci	i = inb(qbase + 5);	/* get chip irq stat */
3618c2ecf20Sopenharmony_ci	j = inb(qbase + 7) & 0x1f;	/* and bytes rec'd */
3628c2ecf20Sopenharmony_ci	status = inb(qbase + 2);
3638c2ecf20Sopenharmony_ci	message = inb(qbase + 2);
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	/*
3668c2ecf20Sopenharmony_ci	 *	Should get function complete int if Status and message, else
3678c2ecf20Sopenharmony_ci	 *	bus serv if only status
3688c2ecf20Sopenharmony_ci	 */
3698c2ecf20Sopenharmony_ci	if (!((i == 8 && j == 2) || (i == 0x10 && j == 1))) {
3708c2ecf20Sopenharmony_ci		printk(KERN_ERR "Ql:Error during status phase, int=%02X, %d bytes recd\n", i, j);
3718c2ecf20Sopenharmony_ci		result = DID_ERROR;
3728c2ecf20Sopenharmony_ci	}
3738c2ecf20Sopenharmony_ci	outb(0x12, qbase + 3);	/* done, disconnect */
3748c2ecf20Sopenharmony_ci	rtrc(1)
3758c2ecf20Sopenharmony_ci	if ((k = ql_wai(priv)))
3768c2ecf20Sopenharmony_ci		return (k << 16);
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	/*
3798c2ecf20Sopenharmony_ci	 *	Should get bus service interrupt and disconnect interrupt
3808c2ecf20Sopenharmony_ci	 */
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	i = inb(qbase + 5);	/* should be bus service */
3838c2ecf20Sopenharmony_ci	while (!priv->qabort && ((i & 0x20) != 0x20)) {
3848c2ecf20Sopenharmony_ci		barrier();
3858c2ecf20Sopenharmony_ci		cpu_relax();
3868c2ecf20Sopenharmony_ci		i |= inb(qbase + 5);
3878c2ecf20Sopenharmony_ci	}
3888c2ecf20Sopenharmony_ci	rtrc(0)
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	if (priv->qabort)
3918c2ecf20Sopenharmony_ci		return ((priv->qabort == 1 ? DID_ABORT : DID_RESET) << 16);
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	return (result << 16) | (message << 8) | (status & STATUS_MASK);
3948c2ecf20Sopenharmony_ci}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci/*
3978c2ecf20Sopenharmony_ci *	Interrupt handler
3988c2ecf20Sopenharmony_ci */
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_cistatic void ql_ihandl(void *dev_id)
4018c2ecf20Sopenharmony_ci{
4028c2ecf20Sopenharmony_ci	struct scsi_cmnd *icmd;
4038c2ecf20Sopenharmony_ci	struct Scsi_Host *host = dev_id;
4048c2ecf20Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_host(host);
4058c2ecf20Sopenharmony_ci	int qbase = priv->qbase;
4068c2ecf20Sopenharmony_ci	REG0;
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	if (!(inb(qbase + 4) & 0x80))	/* false alarm? */
4098c2ecf20Sopenharmony_ci		return;
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	if (priv->qlcmd == NULL) {	/* no command to process? */
4128c2ecf20Sopenharmony_ci		int i;
4138c2ecf20Sopenharmony_ci		i = 16;
4148c2ecf20Sopenharmony_ci		while (i-- && inb(qbase + 5));	/* maybe also ql_zap() */
4158c2ecf20Sopenharmony_ci		return;
4168c2ecf20Sopenharmony_ci	}
4178c2ecf20Sopenharmony_ci	icmd = priv->qlcmd;
4188c2ecf20Sopenharmony_ci	icmd->result = ql_pcmd(icmd);
4198c2ecf20Sopenharmony_ci	priv->qlcmd = NULL;
4208c2ecf20Sopenharmony_ci	/*
4218c2ecf20Sopenharmony_ci	 *	If result is CHECK CONDITION done calls qcommand to request
4228c2ecf20Sopenharmony_ci	 *	sense
4238c2ecf20Sopenharmony_ci	 */
4248c2ecf20Sopenharmony_ci	(icmd->scsi_done) (icmd);
4258c2ecf20Sopenharmony_ci}
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ciirqreturn_t qlogicfas408_ihandl(int irq, void *dev_id)
4288c2ecf20Sopenharmony_ci{
4298c2ecf20Sopenharmony_ci	unsigned long flags;
4308c2ecf20Sopenharmony_ci	struct Scsi_Host *host = dev_id;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	spin_lock_irqsave(host->host_lock, flags);
4338c2ecf20Sopenharmony_ci	ql_ihandl(dev_id);
4348c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(host->host_lock, flags);
4358c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
4368c2ecf20Sopenharmony_ci}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci/*
4398c2ecf20Sopenharmony_ci *	Queued command
4408c2ecf20Sopenharmony_ci */
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_cistatic int qlogicfas408_queuecommand_lck(struct scsi_cmnd *cmd,
4438c2ecf20Sopenharmony_ci			      void (*done) (struct scsi_cmnd *))
4448c2ecf20Sopenharmony_ci{
4458c2ecf20Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
4468c2ecf20Sopenharmony_ci	if (scmd_id(cmd) == priv->qinitid) {
4478c2ecf20Sopenharmony_ci		cmd->result = DID_BAD_TARGET << 16;
4488c2ecf20Sopenharmony_ci		done(cmd);
4498c2ecf20Sopenharmony_ci		return 0;
4508c2ecf20Sopenharmony_ci	}
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	cmd->scsi_done = done;
4538c2ecf20Sopenharmony_ci	/* wait for the last command's interrupt to finish */
4548c2ecf20Sopenharmony_ci	while (priv->qlcmd != NULL) {
4558c2ecf20Sopenharmony_ci		barrier();
4568c2ecf20Sopenharmony_ci		cpu_relax();
4578c2ecf20Sopenharmony_ci	}
4588c2ecf20Sopenharmony_ci	ql_icmd(cmd);
4598c2ecf20Sopenharmony_ci	return 0;
4608c2ecf20Sopenharmony_ci}
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ciDEF_SCSI_QCMD(qlogicfas408_queuecommand)
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci/*
4658c2ecf20Sopenharmony_ci *	Return bios parameters
4668c2ecf20Sopenharmony_ci */
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ciint qlogicfas408_biosparam(struct scsi_device *disk, struct block_device *dev,
4698c2ecf20Sopenharmony_ci			   sector_t capacity, int ip[])
4708c2ecf20Sopenharmony_ci{
4718c2ecf20Sopenharmony_ci/* This should mimic the DOS Qlogic driver's behavior exactly */
4728c2ecf20Sopenharmony_ci	ip[0] = 0x40;
4738c2ecf20Sopenharmony_ci	ip[1] = 0x20;
4748c2ecf20Sopenharmony_ci	ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);
4758c2ecf20Sopenharmony_ci	if (ip[2] > 1024) {
4768c2ecf20Sopenharmony_ci		ip[0] = 0xff;
4778c2ecf20Sopenharmony_ci		ip[1] = 0x3f;
4788c2ecf20Sopenharmony_ci		ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);
4798c2ecf20Sopenharmony_ci#if 0
4808c2ecf20Sopenharmony_ci		if (ip[2] > 1023)
4818c2ecf20Sopenharmony_ci			ip[2] = 1023;
4828c2ecf20Sopenharmony_ci#endif
4838c2ecf20Sopenharmony_ci	}
4848c2ecf20Sopenharmony_ci	return 0;
4858c2ecf20Sopenharmony_ci}
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci/*
4888c2ecf20Sopenharmony_ci *	Abort a command in progress
4898c2ecf20Sopenharmony_ci */
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ciint qlogicfas408_abort(struct scsi_cmnd *cmd)
4928c2ecf20Sopenharmony_ci{
4938c2ecf20Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
4948c2ecf20Sopenharmony_ci	priv->qabort = 1;
4958c2ecf20Sopenharmony_ci	ql_zap(priv);
4968c2ecf20Sopenharmony_ci	return SUCCESS;
4978c2ecf20Sopenharmony_ci}
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci/*
5008c2ecf20Sopenharmony_ci *	Reset SCSI bus
5018c2ecf20Sopenharmony_ci *	FIXME: This function is invoked with cmd = NULL directly by
5028c2ecf20Sopenharmony_ci *	the PCMCIA qlogic_stub code. This wants fixing
5038c2ecf20Sopenharmony_ci */
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ciint qlogicfas408_host_reset(struct scsi_cmnd *cmd)
5068c2ecf20Sopenharmony_ci{
5078c2ecf20Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
5088c2ecf20Sopenharmony_ci	unsigned long flags;
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci	priv->qabort = 2;
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_ci	spin_lock_irqsave(cmd->device->host->host_lock, flags);
5138c2ecf20Sopenharmony_ci	ql_zap(priv);
5148c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(cmd->device->host->host_lock, flags);
5158c2ecf20Sopenharmony_ci
5168c2ecf20Sopenharmony_ci	return SUCCESS;
5178c2ecf20Sopenharmony_ci}
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci/*
5208c2ecf20Sopenharmony_ci *	Return info string
5218c2ecf20Sopenharmony_ci */
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ciconst char *qlogicfas408_info(struct Scsi_Host *host)
5248c2ecf20Sopenharmony_ci{
5258c2ecf20Sopenharmony_ci	struct qlogicfas408_priv *priv = get_priv_by_host(host);
5268c2ecf20Sopenharmony_ci	return priv->qinfo;
5278c2ecf20Sopenharmony_ci}
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci/*
5308c2ecf20Sopenharmony_ci *	Get type of chip
5318c2ecf20Sopenharmony_ci */
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ciint qlogicfas408_get_chip_type(int qbase, int int_type)
5348c2ecf20Sopenharmony_ci{
5358c2ecf20Sopenharmony_ci	REG1;
5368c2ecf20Sopenharmony_ci	return inb(qbase + 0xe) & 0xf8;
5378c2ecf20Sopenharmony_ci}
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci/*
5408c2ecf20Sopenharmony_ci *	Perform initialization tasks
5418c2ecf20Sopenharmony_ci */
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_civoid qlogicfas408_setup(int qbase, int id, int int_type)
5448c2ecf20Sopenharmony_ci{
5458c2ecf20Sopenharmony_ci	outb(1, qbase + 8);	/* set for PIO pseudo DMA */
5468c2ecf20Sopenharmony_ci	REG0;
5478c2ecf20Sopenharmony_ci	outb(0x40 | qlcfg8 | id, qbase + 8);	/* (ini) bus id, disable scsi rst */
5488c2ecf20Sopenharmony_ci	outb(qlcfg5, qbase + 5);	/* select timer */
5498c2ecf20Sopenharmony_ci	outb(qlcfg9, qbase + 9);	/* prescaler */
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci#if QL_RESET_AT_START
5528c2ecf20Sopenharmony_ci	outb(3, qbase + 3);
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_ci	REG1;
5558c2ecf20Sopenharmony_ci	/* FIXME: timeout */
5568c2ecf20Sopenharmony_ci	while (inb(qbase + 0xf) & 4)
5578c2ecf20Sopenharmony_ci		cpu_relax();
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_ci	REG0;
5608c2ecf20Sopenharmony_ci#endif
5618c2ecf20Sopenharmony_ci}
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci/*
5648c2ecf20Sopenharmony_ci *	Checks if this is a QLogic FAS 408
5658c2ecf20Sopenharmony_ci */
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_ciint qlogicfas408_detect(int qbase, int int_type)
5688c2ecf20Sopenharmony_ci{
5698c2ecf20Sopenharmony_ci        REG1;
5708c2ecf20Sopenharmony_ci	return (((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7) &&
5718c2ecf20Sopenharmony_ci	       ((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7));
5728c2ecf20Sopenharmony_ci}
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci/*
5758c2ecf20Sopenharmony_ci *	Disable interrupts
5768c2ecf20Sopenharmony_ci */
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_civoid qlogicfas408_disable_ints(struct qlogicfas408_priv *priv)
5798c2ecf20Sopenharmony_ci{
5808c2ecf20Sopenharmony_ci	int qbase = priv->qbase;
5818c2ecf20Sopenharmony_ci	int int_type = priv->int_type;
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ci	REG1;
5848c2ecf20Sopenharmony_ci	outb(0, qbase + 0xb);	/* disable ints */
5858c2ecf20Sopenharmony_ci}
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci/*
5888c2ecf20Sopenharmony_ci *	Init and exit functions
5898c2ecf20Sopenharmony_ci */
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_cistatic int __init qlogicfas408_init(void)
5928c2ecf20Sopenharmony_ci{
5938c2ecf20Sopenharmony_ci	return 0;
5948c2ecf20Sopenharmony_ci}
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_cistatic void __exit qlogicfas408_exit(void)
5978c2ecf20Sopenharmony_ci{
5988c2ecf20Sopenharmony_ci
5998c2ecf20Sopenharmony_ci}
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ciMODULE_AUTHOR("Tom Zerucha, Michael Griffith");
6028c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Driver for the Qlogic FAS SCSI controllers");
6038c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
6048c2ecf20Sopenharmony_cimodule_init(qlogicfas408_init);
6058c2ecf20Sopenharmony_cimodule_exit(qlogicfas408_exit);
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_info);
6088c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_queuecommand);
6098c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_abort);
6108c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_host_reset);
6118c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_biosparam);
6128c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_ihandl);
6138c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_get_chip_type);
6148c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_setup);
6158c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_detect);
6168c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qlogicfas408_disable_ints);
6178c2ecf20Sopenharmony_ci
618