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