Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / scsi / qlogicfas408.c
diff --git a/kernel/drivers/scsi/qlogicfas408.c b/kernel/drivers/scsi/qlogicfas408.c
new file mode 100644 (file)
index 0000000..c3a9151
--- /dev/null
@@ -0,0 +1,617 @@
+/*----------------------------------------------------------------*/
+/*
+   Qlogic linux driver - work in progress. No Warranty express or implied.
+   Use at your own risk.  Support Tort Reform so you won't have to read all
+   these silly disclaimers.
+
+   Copyright 1994, Tom Zerucha.   
+   tz@execpc.com
+   
+   Additional Code, and much appreciated help by
+   Michael A. Griffith
+   grif@cs.ucr.edu
+
+   Thanks to Eric Youngdale and Dave Hinds for loadable module and PCMCIA
+   help respectively, and for suffering through my foolishness during the
+   debugging process.
+
+   Reference Qlogic FAS408 Technical Manual, 53408-510-00A, May 10, 1994
+   (you can reference it, but it is incomplete and inaccurate in places)
+
+   Version 0.46 1/30/97 - kernel 1.2.0+
+
+   Functions as standalone, loadable, and PCMCIA driver, the latter from
+   Dave Hinds' PCMCIA package.
+   
+   Cleaned up 26/10/2002 by Alan Cox <alan@lxorguk.ukuu.org.uk> as part of the 2.5
+   SCSI driver cleanup and audit. This driver still needs work on the
+   following
+       -       Non terminating hardware waits
+       -       Some layering violations with its pcmcia stub
+
+   Redistributable under terms of the GNU General Public License
+
+   For the avoidance of doubt the "preferred form" of this code is one which
+   is in an open non patent encumbered format. Where cryptographic key signing
+   forms part of the process of creating an executable the information
+   including keys needed to generate an equivalently functional executable
+   are deemed to be part of the source code.
+
+*/
+
+#include <linux/module.h>
+#include <linux/blkdev.h>              /* to get disk capacity */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/proc_fs.h>
+#include <linux/unistd.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+
+#include "scsi.h"
+#include <scsi/scsi_host.h>
+#include "qlogicfas408.h"
+
+/*----------------------------------------------------------------*/
+static int qlcfg5 = (XTALFREQ << 5);   /* 15625/512 */
+static int qlcfg6 = SYNCXFRPD;
+static int qlcfg7 = SYNCOFFST;
+static int qlcfg8 = (SLOWCABLE << 7) | (QL_ENABLE_PARITY << 4);
+static int qlcfg9 = ((XTALFREQ + 4) / 5);
+static int qlcfgc = (FASTCLK << 3) | (FASTSCSI << 4);
+
+/*----------------------------------------------------------------*/
+
+/*----------------------------------------------------------------*/
+/* local functions */
+/*----------------------------------------------------------------*/
+
+/* error recovery - reset everything */
+
+static void ql_zap(struct qlogicfas408_priv *priv)
+{
+       int x;
+       int qbase = priv->qbase;
+       int int_type = priv->int_type;
+
+       x = inb(qbase + 0xd);
+       REG0;
+       outb(3, qbase + 3);     /* reset SCSI */
+       outb(2, qbase + 3);     /* reset chip */
+       if (x & 0x80)
+               REG1;
+}
+
+/*
+ *     Do a pseudo-dma tranfer
+ */
+static int ql_pdma(struct qlogicfas408_priv *priv, int phase, char *request, int reqlen)
+{
+       int j;
+       int qbase = priv->qbase;
+       j = 0;
+       if (phase & 1) {        /* in */
+#if QL_TURBO_PDMA
+               rtrc(4)
+               /* empty fifo in large chunks */
+               if (reqlen >= 128 && (inb(qbase + 8) & 2)) {    /* full */
+                       insl(qbase + 4, request, 32);
+                       reqlen -= 128;
+                       request += 128;
+               }
+               while (reqlen >= 84 && !(j & 0xc0))     /* 2/3 */
+                       if ((j = inb(qbase + 8)) & 4) 
+                       {
+                               insl(qbase + 4, request, 21);
+                               reqlen -= 84;
+                               request += 84;
+                       }
+               if (reqlen >= 44 && (inb(qbase + 8) & 8)) {     /* 1/3 */
+                       insl(qbase + 4, request, 11);
+                       reqlen -= 44;
+                       request += 44;
+               }
+#endif
+               /* until both empty and int (or until reclen is 0) */
+               rtrc(7)
+               j = 0;
+               while (reqlen && !((j & 0x10) && (j & 0xc0))) 
+               {
+                       /* while bytes to receive and not empty */
+                       j &= 0xc0;
+                       while (reqlen && !((j = inb(qbase + 8)) & 0x10)) 
+                       {
+                               *request++ = inb(qbase + 4);
+                               reqlen--;
+                       }
+                       if (j & 0x10)
+                               j = inb(qbase + 8);
+
+               }
+       } else {                /* out */
+#if QL_TURBO_PDMA
+               rtrc(4)
+                   if (reqlen >= 128 && inb(qbase + 8) & 0x10) {       /* empty */
+                       outsl(qbase + 4, request, 32);
+                       reqlen -= 128;
+                       request += 128;
+               }
+               while (reqlen >= 84 && !(j & 0xc0))     /* 1/3 */
+                       if (!((j = inb(qbase + 8)) & 8)) {
+                               outsl(qbase + 4, request, 21);
+                               reqlen -= 84;
+                               request += 84;
+                       }
+               if (reqlen >= 40 && !(inb(qbase + 8) & 4)) {    /* 2/3 */
+                       outsl(qbase + 4, request, 10);
+                       reqlen -= 40;
+                       request += 40;
+               }
+#endif
+               /* until full and int (or until reclen is 0) */
+               rtrc(7)
+                   j = 0;
+               while (reqlen && !((j & 2) && (j & 0xc0))) {
+                       /* while bytes to send and not full */
+                       while (reqlen && !((j = inb(qbase + 8)) & 2)) 
+                       {
+                               outb(*request++, qbase + 4);
+                               reqlen--;
+                       }
+                       if (j & 2)
+                               j = inb(qbase + 8);
+               }
+       }
+       /* maybe return reqlen */
+       return inb(qbase + 8) & 0xc0;
+}
+
+/*
+ *     Wait for interrupt flag (polled - not real hardware interrupt) 
+ */
+
+static int ql_wai(struct qlogicfas408_priv *priv)
+{
+       int k;
+       int qbase = priv->qbase;
+       unsigned long i;
+
+       k = 0;
+       i = jiffies + WATCHDOG;
+       while (time_before(jiffies, i) && !priv->qabort &&
+                                       !((k = inb(qbase + 4)) & 0xe0)) {
+               barrier();
+               cpu_relax();
+       }
+       if (time_after_eq(jiffies, i))
+               return (DID_TIME_OUT);
+       if (priv->qabort)
+               return (priv->qabort == 1 ? DID_ABORT : DID_RESET);
+       if (k & 0x60)
+               ql_zap(priv);
+       if (k & 0x20)
+               return (DID_PARITY);
+       if (k & 0x40)
+               return (DID_ERROR);
+       return 0;
+}
+
+/*
+ *     Initiate scsi command - queueing handler 
+ *     caller must hold host lock
+ */
+
+static void ql_icmd(struct scsi_cmnd *cmd)
+{
+       struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
+       int     qbase = priv->qbase;
+       int     int_type = priv->int_type;
+       unsigned int i;
+
+       priv->qabort = 0;
+
+       REG0;
+       /* clearing of interrupts and the fifo is needed */
+
+       inb(qbase + 5);         /* clear interrupts */
+       if (inb(qbase + 5))     /* if still interrupting */
+               outb(2, qbase + 3);     /* reset chip */
+       else if (inb(qbase + 7) & 0x1f)
+               outb(1, qbase + 3);     /* clear fifo */
+       while (inb(qbase + 5)); /* clear ints */
+       REG1;
+       outb(1, qbase + 8);     /* set for PIO pseudo DMA */
+       outb(0, qbase + 0xb);   /* disable ints */
+       inb(qbase + 8);         /* clear int bits */
+       REG0;
+       outb(0x40, qbase + 0xb);        /* enable features */
+
+       /* configurables */
+       outb(qlcfgc, qbase + 0xc);
+       /* config: no reset interrupt, (initiator) bus id */
+       outb(0x40 | qlcfg8 | priv->qinitid, qbase + 8);
+       outb(qlcfg7, qbase + 7);
+       outb(qlcfg6, qbase + 6);
+        /**/ outb(qlcfg5, qbase + 5);  /* select timer */
+       outb(qlcfg9 & 7, qbase + 9);    /* prescaler */
+/*     outb(0x99, qbase + 5);  */
+       outb(scmd_id(cmd), qbase + 4);
+
+       for (i = 0; i < cmd->cmd_len; i++)
+               outb(cmd->cmnd[i], qbase + 2);
+
+       priv->qlcmd = cmd;
+       outb(0x41, qbase + 3);  /* select and send command */
+}
+
+/*
+ *     Process scsi command - usually after interrupt 
+ */
+
+static unsigned int ql_pcmd(struct scsi_cmnd *cmd)
+{
+       unsigned int i, j;
+       unsigned long k;
+       unsigned int result;    /* ultimate return result */
+       unsigned int status;    /* scsi returned status */
+       unsigned int message;   /* scsi returned message */
+       unsigned int phase;     /* recorded scsi phase */
+       unsigned int reqlen;    /* total length of transfer */
+       char *buf;
+       struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
+       int qbase = priv->qbase;
+       int int_type = priv->int_type;
+
+       rtrc(1)
+       j = inb(qbase + 6);
+       i = inb(qbase + 5);
+       if (i == 0x20) {
+               return (DID_NO_CONNECT << 16);
+       }
+       i |= inb(qbase + 5);    /* the 0x10 bit can be set after the 0x08 */
+       if (i != 0x18) {
+               printk(KERN_ERR "Ql:Bad Interrupt status:%02x\n", i);
+               ql_zap(priv);
+               return (DID_BAD_INTR << 16);
+       }
+       j &= 7;                 /* j = inb( qbase + 7 ) >> 5; */
+
+       /* correct status is supposed to be step 4 */
+       /* it sometimes returns step 3 but with 0 bytes left to send */
+       /* We can try stuffing the FIFO with the max each time, but we will get a
+          sequence of 3 if any bytes are left (but we do flush the FIFO anyway */
+
+       if (j != 3 && j != 4) {
+               printk(KERN_ERR "Ql:Bad sequence for command %d, int %02X, cmdleft = %d\n",
+                    j, i, inb(qbase + 7) & 0x1f);
+               ql_zap(priv);
+               return (DID_ERROR << 16);
+       }
+       result = DID_OK;
+       if (inb(qbase + 7) & 0x1f)      /* if some bytes in fifo */
+               outb(1, qbase + 3);     /* clear fifo */
+       /* note that request_bufflen is the total xfer size when sg is used */
+       reqlen = scsi_bufflen(cmd);
+       /* note that it won't work if transfers > 16M are requested */
+       if (reqlen && !((phase = inb(qbase + 4)) & 6)) {        /* data phase */
+               struct scatterlist *sg;
+               rtrc(2)
+               outb(reqlen, qbase);    /* low-mid xfer cnt */
+               outb(reqlen >> 8, qbase + 1);   /* low-mid xfer cnt */
+               outb(reqlen >> 16, qbase + 0xe);        /* high xfer cnt */
+               outb(0x90, qbase + 3);  /* command do xfer */
+               /* PIO pseudo DMA to buffer or sglist */
+               REG1;
+
+               scsi_for_each_sg(cmd, sg, scsi_sg_count(cmd), i) {
+                       if (priv->qabort) {
+                               REG0;
+                               return ((priv->qabort == 1 ?
+                                        DID_ABORT : DID_RESET) << 16);
+                       }
+                       buf = sg_virt(sg);
+                       if (ql_pdma(priv, phase, buf, sg->length))
+                               break;
+               }
+               REG0;
+               rtrc(2)
+               /*
+                *      Wait for irq (split into second state of irq handler
+                *      if this can take time) 
+                */
+               if ((k = ql_wai(priv)))
+                       return (k << 16);
+               k = inb(qbase + 5);     /* should be 0x10, bus service */
+       }
+
+       /*
+        *      Enter Status (and Message In) Phase 
+        */
+        
+       k = jiffies + WATCHDOG;
+
+       while (time_before(jiffies, k) && !priv->qabort &&
+                                               !(inb(qbase + 4) & 6))
+               cpu_relax();    /* wait for status phase */
+
+       if (time_after_eq(jiffies, k)) {
+               ql_zap(priv);
+               return (DID_TIME_OUT << 16);
+       }
+
+       /* FIXME: timeout ?? */
+       while (inb(qbase + 5))
+               cpu_relax();    /* clear pending ints */
+
+       if (priv->qabort)
+               return ((priv->qabort == 1 ? DID_ABORT : DID_RESET) << 16);
+
+       outb(0x11, qbase + 3);  /* get status and message */
+       if ((k = ql_wai(priv)))
+               return (k << 16);
+       i = inb(qbase + 5);     /* get chip irq stat */
+       j = inb(qbase + 7) & 0x1f;      /* and bytes rec'd */
+       status = inb(qbase + 2);
+       message = inb(qbase + 2);
+
+       /*
+        *      Should get function complete int if Status and message, else 
+        *      bus serv if only status 
+        */
+       if (!((i == 8 && j == 2) || (i == 0x10 && j == 1))) {
+               printk(KERN_ERR "Ql:Error during status phase, int=%02X, %d bytes recd\n", i, j);
+               result = DID_ERROR;
+       }
+       outb(0x12, qbase + 3);  /* done, disconnect */
+       rtrc(1)
+       if ((k = ql_wai(priv)))
+               return (k << 16);
+
+       /*
+        *      Should get bus service interrupt and disconnect interrupt 
+        */
+        
+       i = inb(qbase + 5);     /* should be bus service */
+       while (!priv->qabort && ((i & 0x20) != 0x20)) {
+               barrier();
+               cpu_relax();
+               i |= inb(qbase + 5);
+       }
+       rtrc(0)
+
+       if (priv->qabort)
+               return ((priv->qabort == 1 ? DID_ABORT : DID_RESET) << 16);
+               
+       return (result << 16) | (message << 8) | (status & STATUS_MASK);
+}
+
+/*
+ *     Interrupt handler 
+ */
+
+static void ql_ihandl(void *dev_id)
+{
+       struct scsi_cmnd *icmd;
+       struct Scsi_Host *host = dev_id;
+       struct qlogicfas408_priv *priv = get_priv_by_host(host);
+       int qbase = priv->qbase;
+       REG0;
+
+       if (!(inb(qbase + 4) & 0x80))   /* false alarm? */
+               return;
+
+       if (priv->qlcmd == NULL) {      /* no command to process? */
+               int i;
+               i = 16;
+               while (i-- && inb(qbase + 5));  /* maybe also ql_zap() */
+               return;
+       }
+       icmd = priv->qlcmd;
+       icmd->result = ql_pcmd(icmd);
+       priv->qlcmd = NULL;
+       /*
+        *      If result is CHECK CONDITION done calls qcommand to request 
+        *      sense 
+        */
+       (icmd->scsi_done) (icmd);
+}
+
+irqreturn_t qlogicfas408_ihandl(int irq, void *dev_id)
+{
+       unsigned long flags;
+       struct Scsi_Host *host = dev_id;
+
+       spin_lock_irqsave(host->host_lock, flags);
+       ql_ihandl(dev_id);
+       spin_unlock_irqrestore(host->host_lock, flags);
+       return IRQ_HANDLED;
+}
+
+/*
+ *     Queued command
+ */
+
+static int qlogicfas408_queuecommand_lck(struct scsi_cmnd *cmd,
+                             void (*done) (struct scsi_cmnd *))
+{
+       struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
+       if (scmd_id(cmd) == priv->qinitid) {
+               cmd->result = DID_BAD_TARGET << 16;
+               done(cmd);
+               return 0;
+       }
+
+       cmd->scsi_done = done;
+       /* wait for the last command's interrupt to finish */
+       while (priv->qlcmd != NULL) {
+               barrier();
+               cpu_relax();
+       }
+       ql_icmd(cmd);
+       return 0;
+}
+
+DEF_SCSI_QCMD(qlogicfas408_queuecommand)
+
+/* 
+ *     Return bios parameters 
+ */
+
+int qlogicfas408_biosparam(struct scsi_device *disk, struct block_device *dev,
+                          sector_t capacity, int ip[])
+{
+/* This should mimic the DOS Qlogic driver's behavior exactly */
+       ip[0] = 0x40;
+       ip[1] = 0x20;
+       ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);
+       if (ip[2] > 1024) {
+               ip[0] = 0xff;
+               ip[1] = 0x3f;
+               ip[2] = (unsigned long) capacity / (ip[0] * ip[1]);
+#if 0
+               if (ip[2] > 1023)
+                       ip[2] = 1023;
+#endif
+       }
+       return 0;
+}
+
+/*
+ *     Abort a command in progress
+ */
+int qlogicfas408_abort(struct scsi_cmnd *cmd)
+{
+       struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
+       priv->qabort = 1;
+       ql_zap(priv);
+       return SUCCESS;
+}
+
+/* 
+ *     Reset SCSI bus
+ *     FIXME: This function is invoked with cmd = NULL directly by
+ *     the PCMCIA qlogic_stub code. This wants fixing
+ */
+
+int qlogicfas408_bus_reset(struct scsi_cmnd *cmd)
+{
+       struct qlogicfas408_priv *priv = get_priv_by_cmd(cmd);
+       unsigned long flags;
+
+       priv->qabort = 2;
+
+       spin_lock_irqsave(cmd->device->host->host_lock, flags);
+       ql_zap(priv);
+       spin_unlock_irqrestore(cmd->device->host->host_lock, flags);
+
+       return SUCCESS;
+}
+
+/*
+ *     Return info string
+ */
+
+const char *qlogicfas408_info(struct Scsi_Host *host)
+{
+       struct qlogicfas408_priv *priv = get_priv_by_host(host);
+       return priv->qinfo;
+}
+
+/*
+ *     Get type of chip
+ */
+
+int qlogicfas408_get_chip_type(int qbase, int int_type)
+{
+       REG1;
+       return inb(qbase + 0xe) & 0xf8;
+}
+
+/*
+ *     Perform initialization tasks
+ */
+
+void qlogicfas408_setup(int qbase, int id, int int_type)
+{
+       outb(1, qbase + 8);     /* set for PIO pseudo DMA */
+       REG0;
+       outb(0x40 | qlcfg8 | id, qbase + 8);    /* (ini) bus id, disable scsi rst */
+       outb(qlcfg5, qbase + 5);        /* select timer */
+       outb(qlcfg9, qbase + 9);        /* prescaler */
+
+#if QL_RESET_AT_START
+       outb(3, qbase + 3);
+
+       REG1;
+       /* FIXME: timeout */
+       while (inb(qbase + 0xf) & 4)
+               cpu_relax();
+
+       REG0;
+#endif
+}
+
+/*
+ *     Checks if this is a QLogic FAS 408
+ */
+
+int qlogicfas408_detect(int qbase, int int_type)
+{
+        REG1;
+       return (((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7) &&
+              ((inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7));           
+}
+
+/*
+ *     Disable interrupts
+ */
+
+void qlogicfas408_disable_ints(struct qlogicfas408_priv *priv)
+{
+       int qbase = priv->qbase;
+       int int_type = priv->int_type;
+
+       REG1;
+       outb(0, qbase + 0xb);   /* disable ints */
+}
+
+/*
+ *     Init and exit functions
+ */
+
+static int __init qlogicfas408_init(void)
+{
+       return 0;
+}
+
+static void __exit qlogicfas408_exit(void)
+{
+
+}
+
+MODULE_AUTHOR("Tom Zerucha, Michael Griffith");
+MODULE_DESCRIPTION("Driver for the Qlogic FAS SCSI controllers");
+MODULE_LICENSE("GPL");
+module_init(qlogicfas408_init);
+module_exit(qlogicfas408_exit);
+
+EXPORT_SYMBOL(qlogicfas408_info);
+EXPORT_SYMBOL(qlogicfas408_queuecommand);
+EXPORT_SYMBOL(qlogicfas408_abort);
+EXPORT_SYMBOL(qlogicfas408_bus_reset);
+EXPORT_SYMBOL(qlogicfas408_biosparam);
+EXPORT_SYMBOL(qlogicfas408_ihandl);
+EXPORT_SYMBOL(qlogicfas408_get_chip_type);
+EXPORT_SYMBOL(qlogicfas408_setup);
+EXPORT_SYMBOL(qlogicfas408_detect);
+EXPORT_SYMBOL(qlogicfas408_disable_ints);
+