brintos

brintos / linux-shallow public Read only

0
0
Text · 14.9 KiB · 3e065d5 Raw
645 lines · c
1/*----------------------------------------------------------------*/2/*3   Qlogic linux driver - work in progress. No Warranty express or implied.4   Use at your own risk.  Support Tort Reform so you won't have to read all5   these silly disclaimers.6 7   Copyright 1994, Tom Zerucha.8   tz@execpc.com9 10   Additional Code, and much appreciated help by11   Michael A. Griffith12   grif@cs.ucr.edu13 14   Thanks to Eric Youngdale and Dave Hinds for loadable module and PCMCIA15   help respectively, and for suffering through my foolishness during the16   debugging process.17 18   Reference Qlogic FAS408 Technical Manual, 53408-510-00A, May 10, 199419   (you can reference it, but it is incomplete and inaccurate in places)20 21   Version 0.46 1/30/97 - kernel 1.2.0+22 23   Functions as standalone, loadable, and PCMCIA driver, the latter from24   Dave Hinds' PCMCIA package.25 26   Cleaned up 26/10/2002 by Alan Cox <alan@lxorguk.ukuu.org.uk> as part of the 2.527   SCSI driver cleanup and audit. This driver still needs work on the28   following29	-	Non terminating hardware waits30	-	Some layering violations with its pcmcia stub31 32   Redistributable under terms of the GNU General Public License33 34   For the avoidance of doubt the "preferred form" of this code is one which35   is in an open non patent encumbered format. Where cryptographic key signing36   forms part of the process of creating an executable the information37   including keys needed to generate an equivalently functional executable38   are deemed to be part of the source code.39 40*/41 42#include <linux/module.h>43#include <linux/blkdev.h>		/* to get disk capacity */44#include <linux/kernel.h>45#include <linux/string.h>46#include <linux/init.h>47#include <linux/interrupt.h>48#include <linux/ioport.h>49#include <linux/proc_fs.h>50#include <linux/unistd.h>51#include <linux/spinlock.h>52#include <linux/stat.h>53 54#include <asm/io.h>55#include <asm/irq.h>56#include <asm/dma.h>57 58#include <scsi/scsi.h>59#include <scsi/scsi_cmnd.h>60#include <scsi/scsi_device.h>61#include <scsi/scsi_eh.h>62#include <scsi/scsi_host.h>63#include <scsi/scsi_tcq.h>64#include "qlogicfas408.h"65 66/*----------------------------------------------------------------*/67static int qlcfg5 = (XTALFREQ << 5);	/* 15625/512 */68static int qlcfg6 = SYNCXFRPD;69static int qlcfg7 = SYNCOFFST;70static int qlcfg8 = (SLOWCABLE << 7) | (QL_ENABLE_PARITY << 4);71static int qlcfg9 = ((XTALFREQ + 4) / 5);72static int qlcfgc = (FASTCLK << 3) | (FASTSCSI << 4);73 74/*----------------------------------------------------------------*/75 76/*----------------------------------------------------------------*/77/* local functions */78/*----------------------------------------------------------------*/79 80/* error recovery - reset everything */81 82static void ql_zap(struct qlogicfas408_priv *priv)83{84	int x;85	int qbase = priv->qbase;86	int int_type = priv->int_type;87 88	x = inb(qbase + 0xd);89	REG0;90	outb(3, qbase + 3);	/* reset SCSI */91	outb(2, qbase + 3);	/* reset chip */92	if (x & 0x80)93		REG1;94}95 96/*97 *	Do a pseudo-dma tranfer98 */99 100static int ql_pdma(struct qlogicfas408_priv *priv, int phase, char *request,101		   int reqlen)102{103	int j;104	int qbase = priv->qbase;105	j = 0;106	if (phase & 1) {	/* in */107#if QL_TURBO_PDMA108		rtrc(4)109		/* empty fifo in large chunks */110		if (reqlen >= 128 && (inb(qbase + 8) & 2)) {	/* full */111			insl(qbase + 4, request, 32);112			reqlen -= 128;113			request += 128;114		}115		while (reqlen >= 84 && !(j & 0xc0))	/* 2/3 */116			if ((j = inb(qbase + 8)) & 4)117			{118				insl(qbase + 4, request, 21);119				reqlen -= 84;120				request += 84;121			}122		if (reqlen >= 44 && (inb(qbase + 8) & 8)) {	/* 1/3 */123			insl(qbase + 4, request, 11);124			reqlen -= 44;125			request += 44;126		}127#endif128		/* until both empty and int (or until reclen is 0) */129		rtrc(7)130		j = 0;131		while (reqlen && !((j & 0x10) && (j & 0xc0)))132		{133			/* while bytes to receive and not empty */134			j &= 0xc0;135			while (reqlen && !((j = inb(qbase + 8)) & 0x10))136			{137				*request++ = inb(qbase + 4);138				reqlen--;139			}140			if (j & 0x10)141				j = inb(qbase + 8);142 143		}144	} else {		/* out */145#if QL_TURBO_PDMA146		rtrc(4)147		if (reqlen >= 128 && inb(qbase + 8) & 0x10) {	/* empty */148			outsl(qbase + 4, request, 32);149			reqlen -= 128;150			request += 128;151		}152		while (reqlen >= 84 && !(j & 0xc0))	/* 1/3 */153			if (!((j = inb(qbase + 8)) & 8)) {154				outsl(qbase + 4, request, 21);155				reqlen -= 84;156				request += 84;157			}158		if (reqlen >= 40 && !(inb(qbase + 8) & 4)) {	/* 2/3 */159			outsl(qbase + 4, request, 10);160			reqlen -= 40;161			request += 40;162		}163#endif164		/* until full and int (or until reclen is 0) */165		rtrc(7)166		    j = 0;167		while (reqlen && !((j & 2) && (j & 0xc0))) {168			/* while bytes to send and not full */169			while (reqlen && !((j = inb(qbase + 8)) & 2))170			{171				outb(*request++, qbase + 4);172				reqlen--;173			}174			if (j & 2)175				j = inb(qbase + 8);176		}177	}178	/* maybe return reqlen */179	return inb(qbase + 8) & 0xc0;180}181 182/*183 *	Wait for interrupt flag (polled - not real hardware interrupt)184 */185 186static int ql_wai(struct qlogicfas408_priv *priv)187{188	int k;189	int qbase = priv->qbase;190	unsigned long i;191 192	k = 0;193	i = jiffies + WATCHDOG;194	while (time_before(jiffies, i) && !priv->qabort &&195					!((k = inb(qbase + 4)) & 0xe0)) {196		barrier();197		cpu_relax();198	}199	if (time_after_eq(jiffies, i))200		return (DID_TIME_OUT);201	if (priv->qabort)202		return (priv->qabort == 1 ? DID_ABORT : DID_RESET);203	if (k & 0x60)204		ql_zap(priv);205	if (k & 0x20)206		return (DID_PARITY);207	if (k & 0x40)208		return (DID_ERROR);209	return 0;210}211 212/*213 *	Initiate scsi command - queueing handler214 *	caller must hold host lock215 */216 217static void ql_icmd(struct scsi_cmnd *cmd)218{219	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);220	int	qbase = priv->qbase;221	int	int_type = priv->int_type;222	unsigned int i;223 224	priv->qabort = 0;225 226	REG0;227	/* clearing of interrupts and the fifo is needed */228 229	inb(qbase + 5);		/* clear interrupts */230	if (inb(qbase + 5))	/* if still interrupting */231		outb(2, qbase + 3);	/* reset chip */232	else if (inb(qbase + 7) & 0x1f)233		outb(1, qbase + 3);	/* clear fifo */234	while (inb(qbase + 5));	/* clear ints */235	REG1;236	outb(1, qbase + 8);	/* set for PIO pseudo DMA */237	outb(0, qbase + 0xb);	/* disable ints */238	inb(qbase + 8);		/* clear int bits */239	REG0;240	outb(0x40, qbase + 0xb);	/* enable features */241 242	/* configurables */243	outb(qlcfgc, qbase + 0xc);244	/* config: no reset interrupt, (initiator) bus id */245	outb(0x40 | qlcfg8 | priv->qinitid, qbase + 8);246	outb(qlcfg7, qbase + 7);247	outb(qlcfg6, qbase + 6);248	outb(qlcfg5, qbase + 5);	/* select timer */249	outb(qlcfg9 & 7, qbase + 9);	/* prescaler */250/*	outb(0x99, qbase + 5);	*/251	outb(scmd_id(cmd), qbase + 4);252 253	for (i = 0; i < cmd->cmd_len; i++)254		outb(cmd->cmnd[i], qbase + 2);255 256	priv->qlcmd = cmd;257	outb(0x41, qbase + 3);	/* select and send command */258}259 260/*261 *	Process scsi command - usually after interrupt262 */263 264static void ql_pcmd(struct scsi_cmnd *cmd)265{266	unsigned int i, j;267	unsigned long k;268	unsigned int status;	/* scsi returned status */269	unsigned int message;	/* scsi returned message */270	unsigned int phase;	/* recorded scsi phase */271	unsigned int reqlen;	/* total length of transfer */272	char *buf;273	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);274	int qbase = priv->qbase;275	int int_type = priv->int_type;276 277	rtrc(1)278	j = inb(qbase + 6);279	i = inb(qbase + 5);280	if (i == 0x20) {281		set_host_byte(cmd, DID_NO_CONNECT);282		return;283	}284	i |= inb(qbase + 5);	/* the 0x10 bit can be set after the 0x08 */285	if (i != 0x18) {286		printk(KERN_ERR "Ql:Bad Interrupt status:%02x\n", i);287		ql_zap(priv);288		set_host_byte(cmd, DID_BAD_INTR);289		return;290	}291	j &= 7;			/* j = inb( qbase + 7 ) >> 5; */292 293	/* correct status is supposed to be step 4 */294	/* it sometimes returns step 3 but with 0 bytes left to send */295	/* We can try stuffing the FIFO with the max each time, but we will get a296	   sequence of 3 if any bytes are left (but we do flush the FIFO anyway */297 298	if (j != 3 && j != 4) {299		printk(KERN_ERR "Ql:Bad sequence for command %d, int %02X, cmdleft = %d\n",300		     j, i, inb(qbase + 7) & 0x1f);301		ql_zap(priv);302		set_host_byte(cmd, DID_ERROR);303		return;304	}305 306	if (inb(qbase + 7) & 0x1f)	/* if some bytes in fifo */307		outb(1, qbase + 3);	/* clear fifo */308	/* note that request_bufflen is the total xfer size when sg is used */309	reqlen = scsi_bufflen(cmd);310	/* note that it won't work if transfers > 16M are requested */311	if (reqlen && !((phase = inb(qbase + 4)) & 6)) {	/* data phase */312		struct scatterlist *sg;313		rtrc(2)314		outb(reqlen, qbase);	/* low-mid xfer cnt */315		outb(reqlen >> 8, qbase + 1);	/* low-mid xfer cnt */316		outb(reqlen >> 16, qbase + 0xe);	/* high xfer cnt */317		outb(0x90, qbase + 3);	/* command do xfer */318		/* PIO pseudo DMA to buffer or sglist */319		REG1;320 321		scsi_for_each_sg(cmd, sg, scsi_sg_count(cmd), i) {322			if (priv->qabort) {323				REG0;324				set_host_byte(cmd,325					      priv->qabort == 1 ?326					      DID_ABORT : DID_RESET);327			}328			buf = sg_virt(sg);329			if (ql_pdma(priv, phase, buf, sg->length))330				break;331		}332		REG0;333		rtrc(2);334		/*335		 *	Wait for irq (split into second state of irq handler336		 *	if this can take time)337		 */338		if ((k = ql_wai(priv))) {339			set_host_byte(cmd, k);340			return;341		}342		k = inb(qbase + 5);	/* should be 0x10, bus service */343	}344 345	/*346	 *	Enter Status (and Message In) Phase347	 */348 349	k = jiffies + WATCHDOG;350 351	while (time_before(jiffies, k) && !priv->qabort &&352						!(inb(qbase + 4) & 6))353		cpu_relax();	/* wait for status phase */354 355	if (time_after_eq(jiffies, k)) {356		ql_zap(priv);357		set_host_byte(cmd, DID_TIME_OUT);358		return;359	}360 361	/* FIXME: timeout ?? */362	while (inb(qbase + 5))363		cpu_relax();	/* clear pending ints */364 365	if (priv->qabort) {366		set_host_byte(cmd,367			      priv->qabort == 1 ? DID_ABORT : DID_RESET);368		return;369	}370 371	outb(0x11, qbase + 3);	/* get status and message */372	if ((k = ql_wai(priv))) {373		set_host_byte(cmd, k);374		return;375	}376	i = inb(qbase + 5);	/* get chip irq stat */377	j = inb(qbase + 7) & 0x1f;	/* and bytes rec'd */378	status = inb(qbase + 2);379	message = inb(qbase + 2);380 381	/*382	 *	Should get function complete int if Status and message, else383	 *	bus serv if only status384	 */385	if (!((i == 8 && j == 2) || (i == 0x10 && j == 1))) {386		printk(KERN_ERR "Ql:Error during status phase, int=%02X, %d bytes recd\n", i, j);387		set_host_byte(cmd, DID_ERROR);388	}389	outb(0x12, qbase + 3);	/* done, disconnect */390	rtrc(1);391	if ((k = ql_wai(priv))) {392		set_host_byte(cmd, k);393		return;394	}395 396	/*397	 *	Should get bus service interrupt and disconnect interrupt398	 */399 400	i = inb(qbase + 5);	/* should be bus service */401	while (!priv->qabort && ((i & 0x20) != 0x20)) {402		barrier();403		cpu_relax();404		i |= inb(qbase + 5);405	}406	rtrc(0);407 408	if (priv->qabort) {409		set_host_byte(cmd,410			      priv->qabort == 1 ? DID_ABORT : DID_RESET);411		return;412	}413 414	set_host_byte(cmd, DID_OK);415	if (message != COMMAND_COMPLETE)416		scsi_msg_to_host_byte(cmd, message);417	set_status_byte(cmd, status);418	return;419}420 421/*422 *	Interrupt handler423 */424 425static void ql_ihandl(void *dev_id)426{427	struct scsi_cmnd *icmd;428	struct Scsi_Host *host = dev_id;429	struct qlogicfas408_priv *priv = get_priv_by_host(host);430	int qbase = priv->qbase;431	REG0;432 433	if (!(inb(qbase + 4) & 0x80))	/* false alarm? */434		return;435 436	if (priv->qlcmd == NULL) {	/* no command to process? */437		int i;438		i = 16;439		while (i-- && inb(qbase + 5));	/* maybe also ql_zap() */440		return;441	}442	icmd = priv->qlcmd;443	ql_pcmd(icmd);444	priv->qlcmd = NULL;445	/*446	 *	If result is CHECK CONDITION done calls qcommand to request447	 *	sense448	 */449	scsi_done(icmd);450}451 452irqreturn_t qlogicfas408_ihandl(int irq, void *dev_id)453{454	unsigned long flags;455	struct Scsi_Host *host = dev_id;456 457	spin_lock_irqsave(host->host_lock, flags);458	ql_ihandl(dev_id);459	spin_unlock_irqrestore(host->host_lock, flags);460	return IRQ_HANDLED;461}462 463/*464 *	Queued command465 */466 467static int qlogicfas408_queuecommand_lck(struct scsi_cmnd *cmd)468{469	void (*done)(struct scsi_cmnd *) = scsi_done;470	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);471 472	set_host_byte(cmd, DID_OK);473	set_status_byte(cmd, SAM_STAT_GOOD);474	if (scmd_id(cmd) == priv->qinitid) {475		set_host_byte(cmd, DID_BAD_TARGET);476		done(cmd);477		return 0;478	}479 480	/* wait for the last command's interrupt to finish */481	while (priv->qlcmd != NULL) {482		barrier();483		cpu_relax();484	}485	ql_icmd(cmd);486	return 0;487}488 489DEF_SCSI_QCMD(qlogicfas408_queuecommand)490 491/*492 *	Return bios parameters493 */494 495int qlogicfas408_biosparam(struct scsi_device *disk, struct block_device *dev,496			   sector_t capacity, int ip[])497{498/* This should mimic the DOS Qlogic driver's behavior exactly */499	ip[0] = 0x40;500	ip[1] = 0x20;501	ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);502	if (ip[2] > 1024) {503		ip[0] = 0xff;504		ip[1] = 0x3f;505		ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);506#if 0507		if (ip[2] > 1023)508			ip[2] = 1023;509#endif510	}511	return 0;512}513 514/*515 *	Abort a command in progress516 */517 518int qlogicfas408_abort(struct scsi_cmnd *cmd)519{520	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);521	priv->qabort = 1;522	ql_zap(priv);523	return SUCCESS;524}525 526/*527 *	Reset SCSI bus528 *	FIXME: This function is invoked with cmd = NULL directly by529 *	the PCMCIA qlogic_stub code. This wants fixing530 */531 532int qlogicfas408_host_reset(struct scsi_cmnd *cmd)533{534	struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);535	unsigned long flags;536 537	priv->qabort = 2;538 539	spin_lock_irqsave(cmd->device->host->host_lock, flags);540	ql_zap(priv);541	spin_unlock_irqrestore(cmd->device->host->host_lock, flags);542 543	return SUCCESS;544}545 546/*547 *	Return info string548 */549 550const char *qlogicfas408_info(struct Scsi_Host *host)551{552	struct qlogicfas408_priv *priv = get_priv_by_host(host);553	return priv->qinfo;554}555 556/*557 *	Get type of chip558 */559 560int qlogicfas408_get_chip_type(int qbase, int int_type)561{562	REG1;563	return inb(qbase + 0xe) & 0xf8;564}565 566/*567 *	Perform initialization tasks568 */569 570void qlogicfas408_setup(int qbase, int id, int int_type)571{572	outb(1, qbase + 8);	/* set for PIO pseudo DMA */573	REG0;574	outb(0x40 | qlcfg8 | id, qbase + 8);	/* (ini) bus id, disable scsi rst */575	outb(qlcfg5, qbase + 5);	/* select timer */576	outb(qlcfg9, qbase + 9);	/* prescaler */577 578#if QL_RESET_AT_START579	outb(3, qbase + 3);580 581	REG1;582	/* FIXME: timeout */583	while (inb(qbase + 0xf) & 4)584		cpu_relax();585 586	REG0;587#endif588}589 590/*591 *	Checks if this is a QLogic FAS 408592 */593 594int qlogicfas408_detect(int qbase, int int_type)595{596	REG1;597	return (((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7) &&598		((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7));599}600 601/*602 *	Disable interrupts603 */604 605void qlogicfas408_disable_ints(struct qlogicfas408_priv *priv)606{607	int qbase = priv->qbase;608	int int_type = priv->int_type;609 610	REG1;611	outb(0, qbase + 0xb);	/* disable ints */612}613 614/*615 *	Init and exit functions616 */617 618static int __init qlogicfas408_init(void)619{620	return 0;621}622 623static void __exit qlogicfas408_exit(void)624{625 626}627 628MODULE_AUTHOR("Tom Zerucha, Michael Griffith");629MODULE_DESCRIPTION("Driver for the Qlogic FAS SCSI controllers");630MODULE_LICENSE("GPL");631module_init(qlogicfas408_init);632module_exit(qlogicfas408_exit);633 634EXPORT_SYMBOL(qlogicfas408_info);635EXPORT_SYMBOL(qlogicfas408_queuecommand);636EXPORT_SYMBOL(qlogicfas408_abort);637EXPORT_SYMBOL(qlogicfas408_host_reset);638EXPORT_SYMBOL(qlogicfas408_biosparam);639EXPORT_SYMBOL(qlogicfas408_ihandl);640EXPORT_SYMBOL(qlogicfas408_get_chip_type);641EXPORT_SYMBOL(qlogicfas408_setup);642EXPORT_SYMBOL(qlogicfas408_detect);643EXPORT_SYMBOL(qlogicfas408_disable_ints);644 645