162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Qlogic FAS408 ISA card driver 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright 1994, Tom Zerucha. 562306a36Sopenharmony_ci * tz@execpc.com 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Redistributable under terms of the GNU General Public License 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * For the avoidance of doubt the "preferred form" of this code is one which 1062306a36Sopenharmony_ci * is in an open non patent encumbered format. Where cryptographic key signing 1162306a36Sopenharmony_ci * forms part of the process of creating an executable the information 1262306a36Sopenharmony_ci * including keys needed to generate an equivalently functional executable 1362306a36Sopenharmony_ci * are deemed to be part of the source code. 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Check qlogicfas408.c for more credits and info. 1662306a36Sopenharmony_ci */ 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <linux/module.h> 1962306a36Sopenharmony_ci#include <linux/blkdev.h> /* to get disk capacity */ 2062306a36Sopenharmony_ci#include <linux/kernel.h> 2162306a36Sopenharmony_ci#include <linux/string.h> 2262306a36Sopenharmony_ci#include <linux/init.h> 2362306a36Sopenharmony_ci#include <linux/interrupt.h> 2462306a36Sopenharmony_ci#include <linux/ioport.h> 2562306a36Sopenharmony_ci#include <linux/proc_fs.h> 2662306a36Sopenharmony_ci#include <linux/unistd.h> 2762306a36Sopenharmony_ci#include <linux/spinlock.h> 2862306a36Sopenharmony_ci#include <linux/stat.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#include <asm/io.h> 3162306a36Sopenharmony_ci#include <asm/irq.h> 3262306a36Sopenharmony_ci#include <asm/dma.h> 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#include <scsi/scsi.h> 3562306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h> 3662306a36Sopenharmony_ci#include <scsi/scsi_device.h> 3762306a36Sopenharmony_ci#include <scsi/scsi_eh.h> 3862306a36Sopenharmony_ci#include <scsi/scsi_host.h> 3962306a36Sopenharmony_ci#include <scsi/scsi_tcq.h> 4062306a36Sopenharmony_ci#include "qlogicfas408.h" 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/* Set the following to 2 to use normal interrupt (active high/totempole- 4362306a36Sopenharmony_ci * tristate), otherwise use 0 (REQUIRED FOR PCMCIA) for active low, open 4462306a36Sopenharmony_ci * drain 4562306a36Sopenharmony_ci */ 4662306a36Sopenharmony_ci#define INT_TYPE 2 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic char qlogicfas_name[] = "qlogicfas"; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* 5162306a36Sopenharmony_ci * Look for qlogic card and init if found 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic struct Scsi_Host *__qlogicfas_detect(struct scsi_host_template *host, 5562306a36Sopenharmony_ci int qbase, 5662306a36Sopenharmony_ci int qlirq) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci int qltyp; /* type of chip */ 5962306a36Sopenharmony_ci int qinitid; 6062306a36Sopenharmony_ci struct Scsi_Host *hreg; /* registered host structure */ 6162306a36Sopenharmony_ci struct qlogicfas408_priv *priv; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* Qlogic Cards only exist at 0x230 or 0x330 (the chip itself 6462306a36Sopenharmony_ci * decodes the address - I check 230 first since MIDI cards are 6562306a36Sopenharmony_ci * typically at 0x330 6662306a36Sopenharmony_ci * 6762306a36Sopenharmony_ci * Theoretically, two Qlogic cards can coexist in the same system. 6862306a36Sopenharmony_ci * This should work by simply using this as a loadable module for 6962306a36Sopenharmony_ci * the second card, but I haven't tested this. 7062306a36Sopenharmony_ci */ 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if (!qbase || qlirq == -1) 7362306a36Sopenharmony_ci goto err; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci if (!request_region(qbase, 0x10, qlogicfas_name)) { 7662306a36Sopenharmony_ci printk(KERN_INFO "%s: address %#x is busy\n", qlogicfas_name, 7762306a36Sopenharmony_ci qbase); 7862306a36Sopenharmony_ci goto err; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!qlogicfas408_detect(qbase, INT_TYPE)) { 8262306a36Sopenharmony_ci printk(KERN_WARNING "%s: probe failed for %#x\n", 8362306a36Sopenharmony_ci qlogicfas_name, 8462306a36Sopenharmony_ci qbase); 8562306a36Sopenharmony_ci goto err_release_mem; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci printk(KERN_INFO "%s: Using preset base address of %03x," 8962306a36Sopenharmony_ci " IRQ %d\n", qlogicfas_name, qbase, qlirq); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci qltyp = qlogicfas408_get_chip_type(qbase, INT_TYPE); 9262306a36Sopenharmony_ci qinitid = host->this_id; 9362306a36Sopenharmony_ci if (qinitid < 0) 9462306a36Sopenharmony_ci qinitid = 7; /* if no ID, use 7 */ 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci qlogicfas408_setup(qbase, qinitid, INT_TYPE); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci hreg = scsi_host_alloc(host, sizeof(struct qlogicfas408_priv)); 9962306a36Sopenharmony_ci if (!hreg) 10062306a36Sopenharmony_ci goto err_release_mem; 10162306a36Sopenharmony_ci priv = get_priv_by_host(hreg); 10262306a36Sopenharmony_ci hreg->io_port = qbase; 10362306a36Sopenharmony_ci hreg->n_io_port = 16; 10462306a36Sopenharmony_ci hreg->dma_channel = -1; 10562306a36Sopenharmony_ci if (qlirq != -1) 10662306a36Sopenharmony_ci hreg->irq = qlirq; 10762306a36Sopenharmony_ci priv->qbase = qbase; 10862306a36Sopenharmony_ci priv->qlirq = qlirq; 10962306a36Sopenharmony_ci priv->qinitid = qinitid; 11062306a36Sopenharmony_ci priv->shost = hreg; 11162306a36Sopenharmony_ci priv->int_type = INT_TYPE; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci sprintf(priv->qinfo, 11462306a36Sopenharmony_ci "Qlogicfas Driver version 0.46, chip %02X at %03X, IRQ %d, TPdma:%d", 11562306a36Sopenharmony_ci qltyp, qbase, qlirq, QL_TURBO_PDMA); 11662306a36Sopenharmony_ci host->name = qlogicfas_name; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci if (request_irq(qlirq, qlogicfas408_ihandl, 0, qlogicfas_name, hreg)) 11962306a36Sopenharmony_ci goto free_scsi_host; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci if (scsi_add_host(hreg, NULL)) 12262306a36Sopenharmony_ci goto free_interrupt; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci scsi_scan_host(hreg); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci return hreg; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cifree_interrupt: 12962306a36Sopenharmony_ci free_irq(qlirq, hreg); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cifree_scsi_host: 13262306a36Sopenharmony_ci scsi_host_put(hreg); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cierr_release_mem: 13562306a36Sopenharmony_ci release_region(qbase, 0x10); 13662306a36Sopenharmony_cierr: 13762306a36Sopenharmony_ci return NULL; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci#define MAX_QLOGICFAS 8 14162306a36Sopenharmony_cistatic struct qlogicfas408_priv *cards; 14262306a36Sopenharmony_cistatic int iobase[MAX_QLOGICFAS]; 14362306a36Sopenharmony_cistatic int irq[MAX_QLOGICFAS] = { [0 ... MAX_QLOGICFAS-1] = -1 }; 14462306a36Sopenharmony_cimodule_param_hw_array(iobase, int, ioport, NULL, 0); 14562306a36Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0); 14662306a36Sopenharmony_ciMODULE_PARM_DESC(iobase, "I/O address"); 14762306a36Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ"); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic int qlogicfas_detect(struct scsi_host_template *sht) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci struct Scsi_Host *shost; 15262306a36Sopenharmony_ci struct qlogicfas408_priv *priv; 15362306a36Sopenharmony_ci int num; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci for (num = 0; num < MAX_QLOGICFAS; num++) { 15662306a36Sopenharmony_ci shost = __qlogicfas_detect(sht, iobase[num], irq[num]); 15762306a36Sopenharmony_ci if (shost == NULL) { 15862306a36Sopenharmony_ci /* no more devices */ 15962306a36Sopenharmony_ci break; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci priv = get_priv_by_host(shost); 16262306a36Sopenharmony_ci priv->next = cards; 16362306a36Sopenharmony_ci cards = priv; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci return num; 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic int qlogicfas_release(struct Scsi_Host *shost) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci struct qlogicfas408_priv *priv = get_priv_by_host(shost); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci scsi_remove_host(shost); 17462306a36Sopenharmony_ci if (shost->irq) { 17562306a36Sopenharmony_ci qlogicfas408_disable_ints(priv); 17662306a36Sopenharmony_ci free_irq(shost->irq, shost); 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci if (shost->io_port && shost->n_io_port) 17962306a36Sopenharmony_ci release_region(shost->io_port, shost->n_io_port); 18062306a36Sopenharmony_ci scsi_host_put(shost); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci/* 18662306a36Sopenharmony_ci * The driver template is also needed for PCMCIA 18762306a36Sopenharmony_ci */ 18862306a36Sopenharmony_cistatic struct scsi_host_template qlogicfas_driver_template = { 18962306a36Sopenharmony_ci .module = THIS_MODULE, 19062306a36Sopenharmony_ci .name = qlogicfas_name, 19162306a36Sopenharmony_ci .proc_name = qlogicfas_name, 19262306a36Sopenharmony_ci .info = qlogicfas408_info, 19362306a36Sopenharmony_ci .queuecommand = qlogicfas408_queuecommand, 19462306a36Sopenharmony_ci .eh_abort_handler = qlogicfas408_abort, 19562306a36Sopenharmony_ci .eh_host_reset_handler = qlogicfas408_host_reset, 19662306a36Sopenharmony_ci .bios_param = qlogicfas408_biosparam, 19762306a36Sopenharmony_ci .can_queue = 1, 19862306a36Sopenharmony_ci .this_id = -1, 19962306a36Sopenharmony_ci .sg_tablesize = SG_ALL, 20062306a36Sopenharmony_ci .dma_boundary = PAGE_SIZE - 1, 20162306a36Sopenharmony_ci}; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic __init int qlogicfas_init(void) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci if (!qlogicfas_detect(&qlogicfas_driver_template)) { 20662306a36Sopenharmony_ci /* no cards found */ 20762306a36Sopenharmony_ci printk(KERN_INFO "%s: no cards were found, please specify " 20862306a36Sopenharmony_ci "I/O address and IRQ using iobase= and irq= " 20962306a36Sopenharmony_ci "options", qlogicfas_name); 21062306a36Sopenharmony_ci return -ENODEV; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci return 0; 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cistatic __exit void qlogicfas_exit(void) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci struct qlogicfas408_priv *priv; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci for (priv = cards; priv != NULL; priv = priv->next) 22162306a36Sopenharmony_ci qlogicfas_release(priv->shost); 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ciMODULE_AUTHOR("Tom Zerucha, Michael Griffith"); 22562306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for the Qlogic FAS408 based ISA card"); 22662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 22762306a36Sopenharmony_cimodule_init(qlogicfas_init); 22862306a36Sopenharmony_cimodule_exit(qlogicfas_exit); 22962306a36Sopenharmony_ci 230