| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 all | 
|  | 5 | these silly disclaimers. | 
|  | 6 |  | 
|  | 7 | Copyright 1994, Tom Zerucha. | 
|  | 8 | tz@execpc.com | 
|  | 9 |  | 
|  | 10 | Additional Code, and much appreciated help by | 
|  | 11 | Michael A. Griffith | 
|  | 12 | grif@cs.ucr.edu | 
|  | 13 |  | 
|  | 14 | Thanks to Eric Youngdale and Dave Hinds for loadable module and PCMCIA | 
|  | 15 | help respectively, and for suffering through my foolishness during the | 
|  | 16 | debugging process. | 
|  | 17 |  | 
|  | 18 | Reference Qlogic FAS408 Technical Manual, 53408-510-00A, May 10, 1994 | 
|  | 19 | (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 from | 
|  | 24 | Dave Hinds' PCMCIA package. | 
|  | 25 |  | 
| Alan Cox | fa195af | 2008-10-27 15:16:36 +0000 | [diff] [blame] | 26 | Cleaned up 26/10/2002 by Alan Cox <alan@lxorguk.ukuu.org.uk> as part of the 2.5 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 27 | SCSI driver cleanup and audit. This driver still needs work on the | 
|  | 28 | following | 
|  | 29 | -	Non terminating hardware waits | 
|  | 30 | -	Some layering violations with its pcmcia stub | 
|  | 31 |  | 
|  | 32 | Redistributable under terms of the GNU General Public License | 
|  | 33 |  | 
|  | 34 | For the avoidance of doubt the "preferred form" of this code is one which | 
|  | 35 | is in an open non patent encumbered format. Where cryptographic key signing | 
|  | 36 | forms part of the process of creating an executable the information | 
|  | 37 | including keys needed to generate an equivalently functional executable | 
|  | 38 | 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.h" | 
|  | 59 | #include <scsi/scsi_host.h> | 
|  | 60 | #include "qlogicfas408.h" | 
|  | 61 |  | 
|  | 62 | /*----------------------------------------------------------------*/ | 
|  | 63 | static int qlcfg5 = (XTALFREQ << 5);	/* 15625/512 */ | 
|  | 64 | static int qlcfg6 = SYNCXFRPD; | 
|  | 65 | static int qlcfg7 = SYNCOFFST; | 
|  | 66 | static int qlcfg8 = (SLOWCABLE << 7) | (QL_ENABLE_PARITY << 4); | 
|  | 67 | static int qlcfg9 = ((XTALFREQ + 4) / 5); | 
|  | 68 | static int qlcfgc = (FASTCLK << 3) | (FASTSCSI << 4); | 
|  | 69 |  | 
|  | 70 | /*----------------------------------------------------------------*/ | 
|  | 71 |  | 
|  | 72 | /*----------------------------------------------------------------*/ | 
|  | 73 | /* local functions */ | 
|  | 74 | /*----------------------------------------------------------------*/ | 
|  | 75 |  | 
|  | 76 | /* error recovery - reset everything */ | 
|  | 77 |  | 
|  | 78 | static void ql_zap(struct qlogicfas408_priv *priv) | 
|  | 79 | { | 
|  | 80 | int x; | 
|  | 81 | int qbase = priv->qbase; | 
|  | 82 | int int_type = priv->int_type; | 
|  | 83 |  | 
|  | 84 | x = inb(qbase + 0xd); | 
|  | 85 | REG0; | 
|  | 86 | outb(3, qbase + 3);	/* reset SCSI */ | 
|  | 87 | outb(2, qbase + 3);	/* reset chip */ | 
|  | 88 | if (x & 0x80) | 
|  | 89 | REG1; | 
|  | 90 | } | 
|  | 91 |  | 
|  | 92 | /* | 
|  | 93 | *	Do a pseudo-dma tranfer | 
|  | 94 | */ | 
|  | 95 |  | 
|  | 96 | static int ql_pdma(struct qlogicfas408_priv *priv, int phase, char *request, int reqlen) | 
|  | 97 | { | 
|  | 98 | int j; | 
|  | 99 | int qbase = priv->qbase; | 
|  | 100 | j = 0; | 
|  | 101 | if (phase & 1) {	/* in */ | 
|  | 102 | #if QL_TURBO_PDMA | 
|  | 103 | rtrc(4) | 
|  | 104 | /* empty fifo in large chunks */ | 
|  | 105 | if (reqlen >= 128 && (inb(qbase + 8) & 2)) {	/* full */ | 
|  | 106 | insl(qbase + 4, request, 32); | 
|  | 107 | reqlen -= 128; | 
|  | 108 | request += 128; | 
|  | 109 | } | 
|  | 110 | while (reqlen >= 84 && !(j & 0xc0))	/* 2/3 */ | 
|  | 111 | if ((j = inb(qbase + 8)) & 4) | 
|  | 112 | { | 
|  | 113 | insl(qbase + 4, request, 21); | 
|  | 114 | reqlen -= 84; | 
|  | 115 | request += 84; | 
|  | 116 | } | 
|  | 117 | if (reqlen >= 44 && (inb(qbase + 8) & 8)) {	/* 1/3 */ | 
|  | 118 | insl(qbase + 4, request, 11); | 
|  | 119 | reqlen -= 44; | 
|  | 120 | request += 44; | 
|  | 121 | } | 
|  | 122 | #endif | 
|  | 123 | /* until both empty and int (or until reclen is 0) */ | 
|  | 124 | rtrc(7) | 
|  | 125 | j = 0; | 
|  | 126 | while (reqlen && !((j & 0x10) && (j & 0xc0))) | 
|  | 127 | { | 
|  | 128 | /* while bytes to receive and not empty */ | 
|  | 129 | j &= 0xc0; | 
|  | 130 | while (reqlen && !((j = inb(qbase + 8)) & 0x10)) | 
|  | 131 | { | 
|  | 132 | *request++ = inb(qbase + 4); | 
|  | 133 | reqlen--; | 
|  | 134 | } | 
|  | 135 | if (j & 0x10) | 
|  | 136 | j = inb(qbase + 8); | 
|  | 137 |  | 
|  | 138 | } | 
|  | 139 | } else {		/* out */ | 
|  | 140 | #if QL_TURBO_PDMA | 
|  | 141 | rtrc(4) | 
|  | 142 | if (reqlen >= 128 && inb(qbase + 8) & 0x10) {	/* empty */ | 
|  | 143 | outsl(qbase + 4, request, 32); | 
|  | 144 | reqlen -= 128; | 
|  | 145 | request += 128; | 
|  | 146 | } | 
|  | 147 | while (reqlen >= 84 && !(j & 0xc0))	/* 1/3 */ | 
|  | 148 | if (!((j = inb(qbase + 8)) & 8)) { | 
|  | 149 | outsl(qbase + 4, request, 21); | 
|  | 150 | reqlen -= 84; | 
|  | 151 | request += 84; | 
|  | 152 | } | 
|  | 153 | if (reqlen >= 40 && !(inb(qbase + 8) & 4)) {	/* 2/3 */ | 
|  | 154 | outsl(qbase + 4, request, 10); | 
|  | 155 | reqlen -= 40; | 
|  | 156 | request += 40; | 
|  | 157 | } | 
|  | 158 | #endif | 
|  | 159 | /* until full and int (or until reclen is 0) */ | 
|  | 160 | rtrc(7) | 
|  | 161 | j = 0; | 
|  | 162 | while (reqlen && !((j & 2) && (j & 0xc0))) { | 
|  | 163 | /* while bytes to send and not full */ | 
|  | 164 | while (reqlen && !((j = inb(qbase + 8)) & 2)) | 
|  | 165 | { | 
|  | 166 | outb(*request++, qbase + 4); | 
|  | 167 | reqlen--; | 
|  | 168 | } | 
|  | 169 | if (j & 2) | 
|  | 170 | j = inb(qbase + 8); | 
|  | 171 | } | 
|  | 172 | } | 
|  | 173 | /* maybe return reqlen */ | 
|  | 174 | return inb(qbase + 8) & 0xc0; | 
|  | 175 | } | 
|  | 176 |  | 
|  | 177 | /* | 
|  | 178 | *	Wait for interrupt flag (polled - not real hardware interrupt) | 
|  | 179 | */ | 
|  | 180 |  | 
|  | 181 | static int ql_wai(struct qlogicfas408_priv *priv) | 
|  | 182 | { | 
|  | 183 | int k; | 
|  | 184 | int qbase = priv->qbase; | 
|  | 185 | unsigned long i; | 
|  | 186 |  | 
|  | 187 | k = 0; | 
|  | 188 | i = jiffies + WATCHDOG; | 
|  | 189 | while (time_before(jiffies, i) && !priv->qabort && | 
|  | 190 | !((k = inb(qbase + 4)) & 0xe0)) { | 
|  | 191 | barrier(); | 
|  | 192 | cpu_relax(); | 
|  | 193 | } | 
|  | 194 | if (time_after_eq(jiffies, i)) | 
|  | 195 | return (DID_TIME_OUT); | 
|  | 196 | if (priv->qabort) | 
|  | 197 | return (priv->qabort == 1 ? DID_ABORT : DID_RESET); | 
|  | 198 | if (k & 0x60) | 
|  | 199 | ql_zap(priv); | 
|  | 200 | if (k & 0x20) | 
|  | 201 | return (DID_PARITY); | 
|  | 202 | if (k & 0x40) | 
|  | 203 | return (DID_ERROR); | 
|  | 204 | return 0; | 
|  | 205 | } | 
|  | 206 |  | 
|  | 207 | /* | 
|  | 208 | *	Initiate scsi command - queueing handler | 
|  | 209 | *	caller must hold host lock | 
|  | 210 | */ | 
|  | 211 |  | 
| Henne | a24342b | 2006-10-03 21:31:14 +0200 | [diff] [blame] | 212 | static void ql_icmd(struct scsi_cmnd *cmd) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 213 | { | 
|  | 214 | struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd); | 
|  | 215 | int 	qbase = priv->qbase; | 
|  | 216 | int	int_type = priv->int_type; | 
|  | 217 | unsigned int i; | 
|  | 218 |  | 
|  | 219 | priv->qabort = 0; | 
|  | 220 |  | 
|  | 221 | REG0; | 
|  | 222 | /* clearing of interrupts and the fifo is needed */ | 
|  | 223 |  | 
|  | 224 | inb(qbase + 5);		/* clear interrupts */ | 
|  | 225 | if (inb(qbase + 5))	/* if still interrupting */ | 
|  | 226 | outb(2, qbase + 3);	/* reset chip */ | 
|  | 227 | else if (inb(qbase + 7) & 0x1f) | 
|  | 228 | outb(1, qbase + 3);	/* clear fifo */ | 
|  | 229 | while (inb(qbase + 5));	/* clear ints */ | 
|  | 230 | REG1; | 
|  | 231 | outb(1, qbase + 8);	/* set for PIO pseudo DMA */ | 
|  | 232 | outb(0, qbase + 0xb);	/* disable ints */ | 
|  | 233 | inb(qbase + 8);		/* clear int bits */ | 
|  | 234 | REG0; | 
|  | 235 | outb(0x40, qbase + 0xb);	/* enable features */ | 
|  | 236 |  | 
|  | 237 | /* configurables */ | 
|  | 238 | outb(qlcfgc, qbase + 0xc); | 
|  | 239 | /* config: no reset interrupt, (initiator) bus id */ | 
|  | 240 | outb(0x40 | qlcfg8 | priv->qinitid, qbase + 8); | 
|  | 241 | outb(qlcfg7, qbase + 7); | 
|  | 242 | outb(qlcfg6, qbase + 6); | 
|  | 243 | /**/ outb(qlcfg5, qbase + 5);	/* select timer */ | 
|  | 244 | outb(qlcfg9 & 7, qbase + 9);	/* prescaler */ | 
|  | 245 | /*	outb(0x99, qbase + 5);	*/ | 
| Jeff Garzik | 422c0d6 | 2005-10-24 18:05:09 -0400 | [diff] [blame] | 246 | outb(scmd_id(cmd), qbase + 4); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 247 |  | 
|  | 248 | for (i = 0; i < cmd->cmd_len; i++) | 
|  | 249 | outb(cmd->cmnd[i], qbase + 2); | 
|  | 250 |  | 
|  | 251 | priv->qlcmd = cmd; | 
|  | 252 | outb(0x41, qbase + 3);	/* select and send command */ | 
|  | 253 | } | 
|  | 254 |  | 
|  | 255 | /* | 
|  | 256 | *	Process scsi command - usually after interrupt | 
|  | 257 | */ | 
|  | 258 |  | 
| Henne | a24342b | 2006-10-03 21:31:14 +0200 | [diff] [blame] | 259 | static unsigned int ql_pcmd(struct scsi_cmnd *cmd) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 260 | { | 
|  | 261 | unsigned int i, j; | 
|  | 262 | unsigned long k; | 
|  | 263 | unsigned int result;	/* ultimate return result */ | 
|  | 264 | unsigned int status;	/* scsi returned status */ | 
|  | 265 | unsigned int message;	/* scsi returned message */ | 
|  | 266 | unsigned int phase;	/* recorded scsi phase */ | 
|  | 267 | unsigned int reqlen;	/* total length of transfer */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 268 | char *buf; | 
|  | 269 | struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd); | 
|  | 270 | int qbase = priv->qbase; | 
|  | 271 | int int_type = priv->int_type; | 
|  | 272 |  | 
|  | 273 | rtrc(1) | 
|  | 274 | j = inb(qbase + 6); | 
|  | 275 | i = inb(qbase + 5); | 
|  | 276 | if (i == 0x20) { | 
|  | 277 | return (DID_NO_CONNECT << 16); | 
|  | 278 | } | 
|  | 279 | i |= inb(qbase + 5);	/* the 0x10 bit can be set after the 0x08 */ | 
|  | 280 | if (i != 0x18) { | 
|  | 281 | printk(KERN_ERR "Ql:Bad Interrupt status:%02x\n", i); | 
|  | 282 | ql_zap(priv); | 
|  | 283 | return (DID_BAD_INTR << 16); | 
|  | 284 | } | 
|  | 285 | j &= 7;			/* j = inb( qbase + 7 ) >> 5; */ | 
|  | 286 |  | 
|  | 287 | /* correct status is supposed to be step 4 */ | 
|  | 288 | /* it sometimes returns step 3 but with 0 bytes left to send */ | 
|  | 289 | /* We can try stuffing the FIFO with the max each time, but we will get a | 
|  | 290 | sequence of 3 if any bytes are left (but we do flush the FIFO anyway */ | 
|  | 291 |  | 
|  | 292 | if (j != 3 && j != 4) { | 
|  | 293 | printk(KERN_ERR "Ql:Bad sequence for command %d, int %02X, cmdleft = %d\n", | 
|  | 294 | j, i, inb(qbase + 7) & 0x1f); | 
|  | 295 | ql_zap(priv); | 
|  | 296 | return (DID_ERROR << 16); | 
|  | 297 | } | 
|  | 298 | result = DID_OK; | 
|  | 299 | if (inb(qbase + 7) & 0x1f)	/* if some bytes in fifo */ | 
|  | 300 | outb(1, qbase + 3);	/* clear fifo */ | 
|  | 301 | /* note that request_bufflen is the total xfer size when sg is used */ | 
| FUJITA Tomonori | bc1ebfb | 2007-05-14 19:24:01 +0900 | [diff] [blame] | 302 | reqlen = scsi_bufflen(cmd); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 303 | /* note that it won't work if transfers > 16M are requested */ | 
|  | 304 | if (reqlen && !((phase = inb(qbase + 4)) & 6)) {	/* data phase */ | 
| FUJITA Tomonori | bc1ebfb | 2007-05-14 19:24:01 +0900 | [diff] [blame] | 305 | struct scatterlist *sg; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 306 | rtrc(2) | 
|  | 307 | outb(reqlen, qbase);	/* low-mid xfer cnt */ | 
|  | 308 | outb(reqlen >> 8, qbase + 1);	/* low-mid xfer cnt */ | 
|  | 309 | outb(reqlen >> 16, qbase + 0xe);	/* high xfer cnt */ | 
|  | 310 | outb(0x90, qbase + 3);	/* command do xfer */ | 
|  | 311 | /* PIO pseudo DMA to buffer or sglist */ | 
|  | 312 | REG1; | 
| FUJITA Tomonori | bc1ebfb | 2007-05-14 19:24:01 +0900 | [diff] [blame] | 313 |  | 
|  | 314 | scsi_for_each_sg(cmd, sg, scsi_sg_count(cmd), i) { | 
|  | 315 | if (priv->qabort) { | 
|  | 316 | REG0; | 
|  | 317 | return ((priv->qabort == 1 ? | 
|  | 318 | DID_ABORT : DID_RESET) << 16); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 319 | } | 
| Jens Axboe | 45711f1 | 2007-10-22 21:19:53 +0200 | [diff] [blame] | 320 | buf = sg_virt(sg); | 
| FUJITA Tomonori | bc1ebfb | 2007-05-14 19:24:01 +0900 | [diff] [blame] | 321 | if (ql_pdma(priv, phase, buf, sg->length)) | 
|  | 322 | break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 323 | } | 
|  | 324 | REG0; | 
|  | 325 | rtrc(2) | 
|  | 326 | /* | 
|  | 327 | *	Wait for irq (split into second state of irq handler | 
|  | 328 | *	if this can take time) | 
|  | 329 | */ | 
|  | 330 | if ((k = ql_wai(priv))) | 
|  | 331 | return (k << 16); | 
|  | 332 | k = inb(qbase + 5);	/* should be 0x10, bus service */ | 
|  | 333 | } | 
|  | 334 |  | 
|  | 335 | /* | 
|  | 336 | *	Enter Status (and Message In) Phase | 
|  | 337 | */ | 
|  | 338 |  | 
|  | 339 | k = jiffies + WATCHDOG; | 
|  | 340 |  | 
|  | 341 | while (time_before(jiffies, k) && !priv->qabort && | 
|  | 342 | !(inb(qbase + 4) & 6)) | 
|  | 343 | cpu_relax();	/* wait for status phase */ | 
|  | 344 |  | 
|  | 345 | if (time_after_eq(jiffies, k)) { | 
|  | 346 | ql_zap(priv); | 
|  | 347 | return (DID_TIME_OUT << 16); | 
|  | 348 | } | 
|  | 349 |  | 
|  | 350 | /* FIXME: timeout ?? */ | 
|  | 351 | while (inb(qbase + 5)) | 
|  | 352 | cpu_relax();	/* clear pending ints */ | 
|  | 353 |  | 
|  | 354 | if (priv->qabort) | 
|  | 355 | return ((priv->qabort == 1 ? DID_ABORT : DID_RESET) << 16); | 
|  | 356 |  | 
|  | 357 | outb(0x11, qbase + 3);	/* get status and message */ | 
|  | 358 | if ((k = ql_wai(priv))) | 
|  | 359 | return (k << 16); | 
|  | 360 | i = inb(qbase + 5);	/* get chip irq stat */ | 
|  | 361 | j = inb(qbase + 7) & 0x1f;	/* and bytes rec'd */ | 
|  | 362 | status = inb(qbase + 2); | 
|  | 363 | message = inb(qbase + 2); | 
|  | 364 |  | 
|  | 365 | /* | 
|  | 366 | *	Should get function complete int if Status and message, else | 
|  | 367 | *	bus serv if only status | 
|  | 368 | */ | 
|  | 369 | if (!((i == 8 && j == 2) || (i == 0x10 && j == 1))) { | 
|  | 370 | printk(KERN_ERR "Ql:Error during status phase, int=%02X, %d bytes recd\n", i, j); | 
|  | 371 | result = DID_ERROR; | 
|  | 372 | } | 
|  | 373 | outb(0x12, qbase + 3);	/* done, disconnect */ | 
|  | 374 | rtrc(1) | 
|  | 375 | if ((k = ql_wai(priv))) | 
|  | 376 | return (k << 16); | 
|  | 377 |  | 
|  | 378 | /* | 
|  | 379 | *	Should get bus service interrupt and disconnect interrupt | 
|  | 380 | */ | 
|  | 381 |  | 
|  | 382 | i = inb(qbase + 5);	/* should be bus service */ | 
|  | 383 | while (!priv->qabort && ((i & 0x20) != 0x20)) { | 
|  | 384 | barrier(); | 
|  | 385 | cpu_relax(); | 
|  | 386 | i |= inb(qbase + 5); | 
|  | 387 | } | 
|  | 388 | rtrc(0) | 
|  | 389 |  | 
|  | 390 | if (priv->qabort) | 
|  | 391 | return ((priv->qabort == 1 ? DID_ABORT : DID_RESET) << 16); | 
|  | 392 |  | 
|  | 393 | return (result << 16) | (message << 8) | (status & STATUS_MASK); | 
|  | 394 | } | 
|  | 395 |  | 
|  | 396 | /* | 
|  | 397 | *	Interrupt handler | 
|  | 398 | */ | 
|  | 399 |  | 
| Jeff Garzik | c7bec5a | 2006-10-06 15:00:58 -0400 | [diff] [blame] | 400 | static void ql_ihandl(void *dev_id) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 401 | { | 
| Henne | a24342b | 2006-10-03 21:31:14 +0200 | [diff] [blame] | 402 | struct scsi_cmnd *icmd; | 
| Jeff Garzik | c7bec5a | 2006-10-06 15:00:58 -0400 | [diff] [blame] | 403 | struct Scsi_Host *host = dev_id; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 404 | struct qlogicfas408_priv *priv = get_priv_by_host(host); | 
|  | 405 | int qbase = priv->qbase; | 
|  | 406 | REG0; | 
|  | 407 |  | 
|  | 408 | if (!(inb(qbase + 4) & 0x80))	/* false alarm? */ | 
|  | 409 | return; | 
|  | 410 |  | 
|  | 411 | if (priv->qlcmd == NULL) {	/* no command to process? */ | 
|  | 412 | int i; | 
|  | 413 | i = 16; | 
|  | 414 | while (i-- && inb(qbase + 5));	/* maybe also ql_zap() */ | 
|  | 415 | return; | 
|  | 416 | } | 
|  | 417 | icmd = priv->qlcmd; | 
|  | 418 | icmd->result = ql_pcmd(icmd); | 
|  | 419 | priv->qlcmd = NULL; | 
|  | 420 | /* | 
|  | 421 | *	If result is CHECK CONDITION done calls qcommand to request | 
|  | 422 | *	sense | 
|  | 423 | */ | 
|  | 424 | (icmd->scsi_done) (icmd); | 
|  | 425 | } | 
|  | 426 |  | 
| David Howells | 7d12e78 | 2006-10-05 14:55:46 +0100 | [diff] [blame] | 427 | irqreturn_t qlogicfas408_ihandl(int irq, void *dev_id) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 428 | { | 
|  | 429 | unsigned long flags; | 
|  | 430 | struct Scsi_Host *host = dev_id; | 
|  | 431 |  | 
|  | 432 | spin_lock_irqsave(host->host_lock, flags); | 
| Jeff Garzik | c7bec5a | 2006-10-06 15:00:58 -0400 | [diff] [blame] | 433 | ql_ihandl(dev_id); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 434 | spin_unlock_irqrestore(host->host_lock, flags); | 
|  | 435 | return IRQ_HANDLED; | 
|  | 436 | } | 
|  | 437 |  | 
|  | 438 | /* | 
|  | 439 | *	Queued command | 
|  | 440 | */ | 
|  | 441 |  | 
| Jeff Garzik | f281233 | 2010-11-16 02:10:29 -0500 | [diff] [blame] | 442 | static int qlogicfas408_queuecommand_lck(struct scsi_cmnd *cmd, | 
| Henne | a24342b | 2006-10-03 21:31:14 +0200 | [diff] [blame] | 443 | void (*done) (struct scsi_cmnd *)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 444 | { | 
|  | 445 | struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd); | 
| Jeff Garzik | 422c0d6 | 2005-10-24 18:05:09 -0400 | [diff] [blame] | 446 | if (scmd_id(cmd) == priv->qinitid) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 447 | cmd->result = DID_BAD_TARGET << 16; | 
|  | 448 | done(cmd); | 
|  | 449 | return 0; | 
|  | 450 | } | 
|  | 451 |  | 
|  | 452 | cmd->scsi_done = done; | 
|  | 453 | /* wait for the last command's interrupt to finish */ | 
|  | 454 | while (priv->qlcmd != NULL) { | 
|  | 455 | barrier(); | 
|  | 456 | cpu_relax(); | 
|  | 457 | } | 
|  | 458 | ql_icmd(cmd); | 
|  | 459 | return 0; | 
|  | 460 | } | 
|  | 461 |  | 
| Jeff Garzik | f281233 | 2010-11-16 02:10:29 -0500 | [diff] [blame] | 462 | DEF_SCSI_QCMD(qlogicfas408_queuecommand) | 
|  | 463 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 464 | /* | 
|  | 465 | *	Return bios parameters | 
|  | 466 | */ | 
|  | 467 |  | 
| Henne | a24342b | 2006-10-03 21:31:14 +0200 | [diff] [blame] | 468 | int qlogicfas408_biosparam(struct scsi_device *disk, struct block_device *dev, | 
|  | 469 | sector_t capacity, int ip[]) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 470 | { | 
|  | 471 | /* This should mimic the DOS Qlogic driver's behavior exactly */ | 
|  | 472 | ip[0] = 0x40; | 
|  | 473 | ip[1] = 0x20; | 
|  | 474 | ip[2] = (unsigned long) capacity / (ip[0] * ip[1]); | 
|  | 475 | if (ip[2] > 1024) { | 
|  | 476 | ip[0] = 0xff; | 
|  | 477 | ip[1] = 0x3f; | 
|  | 478 | ip[2] = (unsigned long) capacity / (ip[0] * ip[1]); | 
|  | 479 | #if 0 | 
|  | 480 | if (ip[2] > 1023) | 
|  | 481 | ip[2] = 1023; | 
|  | 482 | #endif | 
|  | 483 | } | 
|  | 484 | return 0; | 
|  | 485 | } | 
|  | 486 |  | 
|  | 487 | /* | 
|  | 488 | *	Abort a command in progress | 
|  | 489 | */ | 
|  | 490 |  | 
| Henne | a24342b | 2006-10-03 21:31:14 +0200 | [diff] [blame] | 491 | int qlogicfas408_abort(struct scsi_cmnd *cmd) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 492 | { | 
|  | 493 | struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd); | 
|  | 494 | priv->qabort = 1; | 
|  | 495 | ql_zap(priv); | 
|  | 496 | return SUCCESS; | 
|  | 497 | } | 
|  | 498 |  | 
|  | 499 | /* | 
|  | 500 | *	Reset SCSI bus | 
|  | 501 | *	FIXME: This function is invoked with cmd = NULL directly by | 
|  | 502 | *	the PCMCIA qlogic_stub code. This wants fixing | 
|  | 503 | */ | 
|  | 504 |  | 
| Henne | a24342b | 2006-10-03 21:31:14 +0200 | [diff] [blame] | 505 | int qlogicfas408_bus_reset(struct scsi_cmnd *cmd) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 506 | { | 
|  | 507 | struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd); | 
| Jeff Garzik | 68b3aa7 | 2005-05-28 07:56:31 -0400 | [diff] [blame] | 508 | unsigned long flags; | 
|  | 509 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 510 | priv->qabort = 2; | 
| Jeff Garzik | 68b3aa7 | 2005-05-28 07:56:31 -0400 | [diff] [blame] | 511 |  | 
|  | 512 | spin_lock_irqsave(cmd->device->host->host_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 513 | ql_zap(priv); | 
| Jeff Garzik | 68b3aa7 | 2005-05-28 07:56:31 -0400 | [diff] [blame] | 514 | spin_unlock_irqrestore(cmd->device->host->host_lock, flags); | 
|  | 515 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 516 | return SUCCESS; | 
|  | 517 | } | 
|  | 518 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 519 | /* | 
|  | 520 | *	Return info string | 
|  | 521 | */ | 
|  | 522 |  | 
|  | 523 | const char *qlogicfas408_info(struct Scsi_Host *host) | 
|  | 524 | { | 
|  | 525 | struct qlogicfas408_priv *priv = get_priv_by_host(host); | 
|  | 526 | return priv->qinfo; | 
|  | 527 | } | 
|  | 528 |  | 
|  | 529 | /* | 
|  | 530 | *	Get type of chip | 
|  | 531 | */ | 
|  | 532 |  | 
|  | 533 | int qlogicfas408_get_chip_type(int qbase, int int_type) | 
|  | 534 | { | 
|  | 535 | REG1; | 
|  | 536 | return inb(qbase + 0xe) & 0xf8; | 
|  | 537 | } | 
|  | 538 |  | 
|  | 539 | /* | 
|  | 540 | *	Perform initialization tasks | 
|  | 541 | */ | 
|  | 542 |  | 
|  | 543 | void qlogicfas408_setup(int qbase, int id, int int_type) | 
|  | 544 | { | 
|  | 545 | outb(1, qbase + 8);	/* set for PIO pseudo DMA */ | 
|  | 546 | REG0; | 
|  | 547 | outb(0x40 | qlcfg8 | id, qbase + 8);	/* (ini) bus id, disable scsi rst */ | 
|  | 548 | outb(qlcfg5, qbase + 5);	/* select timer */ | 
|  | 549 | outb(qlcfg9, qbase + 9);	/* prescaler */ | 
|  | 550 |  | 
|  | 551 | #if QL_RESET_AT_START | 
|  | 552 | outb(3, qbase + 3); | 
|  | 553 |  | 
|  | 554 | REG1; | 
|  | 555 | /* FIXME: timeout */ | 
|  | 556 | while (inb(qbase + 0xf) & 4) | 
|  | 557 | cpu_relax(); | 
|  | 558 |  | 
|  | 559 | REG0; | 
|  | 560 | #endif | 
|  | 561 | } | 
|  | 562 |  | 
|  | 563 | /* | 
|  | 564 | *	Checks if this is a QLogic FAS 408 | 
|  | 565 | */ | 
|  | 566 |  | 
|  | 567 | int qlogicfas408_detect(int qbase, int int_type) | 
|  | 568 | { | 
|  | 569 | REG1; | 
|  | 570 | return (((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7) && | 
|  | 571 | ((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7)); | 
|  | 572 | } | 
|  | 573 |  | 
|  | 574 | /* | 
|  | 575 | *	Disable interrupts | 
|  | 576 | */ | 
|  | 577 |  | 
|  | 578 | void qlogicfas408_disable_ints(struct qlogicfas408_priv *priv) | 
|  | 579 | { | 
|  | 580 | int qbase = priv->qbase; | 
|  | 581 | int int_type = priv->int_type; | 
|  | 582 |  | 
|  | 583 | REG1; | 
|  | 584 | outb(0, qbase + 0xb);	/* disable ints */ | 
|  | 585 | } | 
|  | 586 |  | 
|  | 587 | /* | 
|  | 588 | *	Init and exit functions | 
|  | 589 | */ | 
|  | 590 |  | 
|  | 591 | static int __init qlogicfas408_init(void) | 
|  | 592 | { | 
|  | 593 | return 0; | 
|  | 594 | } | 
|  | 595 |  | 
|  | 596 | static void __exit qlogicfas408_exit(void) | 
|  | 597 | { | 
|  | 598 |  | 
|  | 599 | } | 
|  | 600 |  | 
|  | 601 | MODULE_AUTHOR("Tom Zerucha, Michael Griffith"); | 
|  | 602 | MODULE_DESCRIPTION("Driver for the Qlogic FAS SCSI controllers"); | 
|  | 603 | MODULE_LICENSE("GPL"); | 
|  | 604 | module_init(qlogicfas408_init); | 
|  | 605 | module_exit(qlogicfas408_exit); | 
|  | 606 |  | 
|  | 607 | EXPORT_SYMBOL(qlogicfas408_info); | 
|  | 608 | EXPORT_SYMBOL(qlogicfas408_queuecommand); | 
|  | 609 | EXPORT_SYMBOL(qlogicfas408_abort); | 
|  | 610 | EXPORT_SYMBOL(qlogicfas408_bus_reset); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 611 | EXPORT_SYMBOL(qlogicfas408_biosparam); | 
|  | 612 | EXPORT_SYMBOL(qlogicfas408_ihandl); | 
|  | 613 | EXPORT_SYMBOL(qlogicfas408_get_chip_type); | 
|  | 614 | EXPORT_SYMBOL(qlogicfas408_setup); | 
|  | 615 | EXPORT_SYMBOL(qlogicfas408_detect); | 
|  | 616 | EXPORT_SYMBOL(qlogicfas408_disable_ints); | 
|  | 617 |  |