| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /*====================================================================== | 
 | 2 |  | 
 | 3 |     A driver for the Qlogic SCSI card | 
 | 4 |  | 
 | 5 |     qlogic_cs.c 1.79 2000/06/12 21:27:26 | 
 | 6 |  | 
 | 7 |     The contents of this file are subject to the Mozilla Public | 
 | 8 |     License Version 1.1 (the "License"); you may not use this file | 
 | 9 |     except in compliance with the License. You may obtain a copy of | 
 | 10 |     the License at http://www.mozilla.org/MPL/ | 
 | 11 |  | 
 | 12 |     Software distributed under the License is distributed on an "AS | 
 | 13 |     IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or | 
 | 14 |     implied. See the License for the specific language governing | 
 | 15 |     rights and limitations under the License. | 
 | 16 |  | 
 | 17 |     The initial developer of the original code is David A. Hinds | 
 | 18 |     <dahinds@users.sourceforge.net>.  Portions created by David A. Hinds | 
 | 19 |     are Copyright (C) 1999 David A. Hinds.  All Rights Reserved. | 
 | 20 |  | 
 | 21 |     Alternatively, the contents of this file may be used under the | 
 | 22 |     terms of the GNU General Public License version 2 (the "GPL"), in which | 
 | 23 |     case the provisions of the GPL are applicable instead of the | 
 | 24 |     above.  If you wish to allow the use of your version of this file | 
 | 25 |     only under the terms of the GPL and not to allow others to use | 
 | 26 |     your version of this file under the MPL, indicate your decision | 
 | 27 |     by deleting the provisions above and replace them with the notice | 
 | 28 |     and other provisions required by the GPL.  If you do not delete | 
 | 29 |     the provisions above, a recipient may use your version of this | 
 | 30 |     file under either the MPL or the GPL. | 
 | 31 |      | 
 | 32 | ======================================================================*/ | 
 | 33 |  | 
 | 34 | #include <linux/module.h> | 
 | 35 | #include <linux/init.h> | 
 | 36 | #include <linux/kernel.h> | 
 | 37 | #include <linux/sched.h> | 
 | 38 | #include <linux/slab.h> | 
 | 39 | #include <linux/string.h> | 
 | 40 | #include <linux/ioport.h> | 
 | 41 | #include <asm/io.h> | 
 | 42 | #include <scsi/scsi.h> | 
 | 43 | #include <linux/major.h> | 
 | 44 | #include <linux/blkdev.h> | 
 | 45 | #include <scsi/scsi_ioctl.h> | 
 | 46 | #include <linux/interrupt.h> | 
 | 47 |  | 
 | 48 | #include "scsi.h" | 
 | 49 | #include <scsi/scsi_host.h> | 
 | 50 | #include "../qlogicfas408.h" | 
 | 51 |  | 
 | 52 | #include <pcmcia/version.h> | 
 | 53 | #include <pcmcia/cs_types.h> | 
 | 54 | #include <pcmcia/cs.h> | 
 | 55 | #include <pcmcia/cistpl.h> | 
 | 56 | #include <pcmcia/ds.h> | 
 | 57 | #include <pcmcia/ciscode.h> | 
 | 58 |  | 
 | 59 | /* Set the following to 2 to use normal interrupt (active high/totempole- | 
 | 60 |  * tristate), otherwise use 0 (REQUIRED FOR PCMCIA) for active low, open | 
 | 61 |  * drain | 
 | 62 |  */ | 
 | 63 | #define INT_TYPE	0 | 
 | 64 |  | 
 | 65 | static char qlogic_name[] = "qlogic_cs"; | 
 | 66 |  | 
 | 67 | #ifdef PCMCIA_DEBUG | 
 | 68 | static int pc_debug = PCMCIA_DEBUG; | 
 | 69 | module_param(pc_debug, int, 0644); | 
 | 70 | #define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) | 
 | 71 | static char *version = "qlogic_cs.c 1.79-ac 2002/10/26 (David Hinds)"; | 
 | 72 | #else | 
 | 73 | #define DEBUG(n, args...) | 
 | 74 | #endif | 
 | 75 |  | 
 | 76 | static Scsi_Host_Template qlogicfas_driver_template = { | 
 | 77 | 	.module			= THIS_MODULE, | 
 | 78 | 	.name			= qlogic_name, | 
 | 79 | 	.proc_name		= qlogic_name, | 
 | 80 | 	.info			= qlogicfas408_info, | 
 | 81 | 	.queuecommand		= qlogicfas408_queuecommand, | 
 | 82 | 	.eh_abort_handler	= qlogicfas408_abort, | 
 | 83 | 	.eh_bus_reset_handler	= qlogicfas408_bus_reset, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 84 | 	.bios_param		= qlogicfas408_biosparam, | 
 | 85 | 	.can_queue		= 1, | 
 | 86 | 	.this_id		= -1, | 
 | 87 | 	.sg_tablesize		= SG_ALL, | 
 | 88 | 	.cmd_per_lun		= 1, | 
 | 89 | 	.use_clustering		= DISABLE_CLUSTERING, | 
 | 90 | }; | 
 | 91 |  | 
 | 92 | /*====================================================================*/ | 
 | 93 |  | 
 | 94 | typedef struct scsi_info_t { | 
 | 95 | 	dev_link_t link; | 
 | 96 | 	dev_node_t node; | 
 | 97 | 	struct Scsi_Host *host; | 
 | 98 | 	unsigned short manf_id; | 
 | 99 | } scsi_info_t; | 
 | 100 |  | 
 | 101 | static void qlogic_release(dev_link_t *link); | 
 | 102 | static int qlogic_event(event_t event, int priority, event_callback_args_t * args); | 
 | 103 |  | 
 | 104 | static dev_link_t *qlogic_attach(void); | 
 | 105 | static void qlogic_detach(dev_link_t *); | 
 | 106 |  | 
 | 107 |  | 
 | 108 | static dev_link_t *dev_list = NULL; | 
 | 109 |  | 
 | 110 | static dev_info_t dev_info = "qlogic_cs"; | 
 | 111 |  | 
 | 112 | static struct Scsi_Host *qlogic_detect(Scsi_Host_Template *host, | 
 | 113 | 				dev_link_t *link, int qbase, int qlirq) | 
 | 114 | { | 
 | 115 | 	int qltyp;		/* type of chip */ | 
 | 116 | 	int qinitid; | 
 | 117 | 	struct Scsi_Host *shost;	/* registered host structure */ | 
 | 118 | 	struct qlogicfas408_priv *priv; | 
 | 119 |  | 
 | 120 | 	qltyp = qlogicfas408_get_chip_type(qbase, INT_TYPE); | 
 | 121 | 	qinitid = host->this_id; | 
 | 122 | 	if (qinitid < 0) | 
 | 123 | 		qinitid = 7;	/* if no ID, use 7 */ | 
 | 124 |  | 
 | 125 | 	qlogicfas408_setup(qbase, qinitid, INT_TYPE); | 
 | 126 |  | 
 | 127 | 	host->name = qlogic_name; | 
 | 128 | 	shost = scsi_host_alloc(host, sizeof(struct qlogicfas408_priv)); | 
 | 129 | 	if (!shost) | 
 | 130 | 		goto err; | 
 | 131 | 	shost->io_port = qbase; | 
 | 132 | 	shost->n_io_port = 16; | 
 | 133 | 	shost->dma_channel = -1; | 
 | 134 | 	if (qlirq != -1) | 
 | 135 | 		shost->irq = qlirq; | 
 | 136 |  | 
 | 137 | 	priv = get_priv_by_host(shost); | 
 | 138 | 	priv->qlirq = qlirq; | 
 | 139 | 	priv->qbase = qbase; | 
 | 140 | 	priv->qinitid = qinitid; | 
 | 141 | 	priv->shost = shost; | 
 | 142 | 	priv->int_type = INT_TYPE;					 | 
 | 143 |  | 
 | 144 | 	if (request_irq(qlirq, qlogicfas408_ihandl, 0, qlogic_name, shost)) | 
 | 145 | 		goto free_scsi_host; | 
 | 146 |  | 
 | 147 | 	sprintf(priv->qinfo, | 
 | 148 | 		"Qlogicfas Driver version 0.46, chip %02X at %03X, IRQ %d, TPdma:%d", | 
 | 149 | 		qltyp, qbase, qlirq, QL_TURBO_PDMA); | 
 | 150 |  | 
 | 151 | 	if (scsi_add_host(shost, NULL)) | 
 | 152 | 		goto free_interrupt; | 
 | 153 |  | 
 | 154 | 	scsi_scan_host(shost); | 
 | 155 |  | 
 | 156 | 	return shost; | 
 | 157 |  | 
 | 158 | free_interrupt: | 
 | 159 | 	free_irq(qlirq, shost); | 
 | 160 |  | 
 | 161 | free_scsi_host: | 
 | 162 | 	scsi_host_put(shost); | 
 | 163 | 	 | 
 | 164 | err: | 
 | 165 | 	return NULL; | 
 | 166 | } | 
 | 167 | static dev_link_t *qlogic_attach(void) | 
 | 168 | { | 
 | 169 | 	scsi_info_t *info; | 
 | 170 | 	client_reg_t client_reg; | 
 | 171 | 	dev_link_t *link; | 
 | 172 | 	int ret; | 
 | 173 |  | 
 | 174 | 	DEBUG(0, "qlogic_attach()\n"); | 
 | 175 |  | 
 | 176 | 	/* Create new SCSI device */ | 
 | 177 | 	info = kmalloc(sizeof(*info), GFP_KERNEL); | 
 | 178 | 	if (!info) | 
 | 179 | 		return NULL; | 
 | 180 | 	memset(info, 0, sizeof(*info)); | 
 | 181 | 	link = &info->link; | 
 | 182 | 	link->priv = info; | 
 | 183 | 	link->io.NumPorts1 = 16; | 
 | 184 | 	link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; | 
 | 185 | 	link->io.IOAddrLines = 10; | 
 | 186 | 	link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; | 
 | 187 | 	link->irq.IRQInfo1 = IRQ_LEVEL_ID; | 
 | 188 | 	link->conf.Attributes = CONF_ENABLE_IRQ; | 
 | 189 | 	link->conf.Vcc = 50; | 
 | 190 | 	link->conf.IntType = INT_MEMORY_AND_IO; | 
 | 191 | 	link->conf.Present = PRESENT_OPTION; | 
 | 192 |  | 
 | 193 | 	/* Register with Card Services */ | 
 | 194 | 	link->next = dev_list; | 
 | 195 | 	dev_list = link; | 
 | 196 | 	client_reg.dev_info = &dev_info; | 
 | 197 | 	client_reg.event_handler = &qlogic_event; | 
 | 198 | 	client_reg.EventMask = CS_EVENT_RESET_REQUEST | CS_EVENT_CARD_RESET | CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; | 
 | 199 | 	client_reg.Version = 0x0210; | 
 | 200 | 	client_reg.event_callback_args.client_data = link; | 
 | 201 | 	ret = pcmcia_register_client(&link->handle, &client_reg); | 
 | 202 | 	if (ret != 0) { | 
 | 203 | 		cs_error(link->handle, RegisterClient, ret); | 
 | 204 | 		qlogic_detach(link); | 
 | 205 | 		return NULL; | 
 | 206 | 	} | 
 | 207 |  | 
 | 208 | 	return link; | 
 | 209 | }				/* qlogic_attach */ | 
 | 210 |  | 
 | 211 | /*====================================================================*/ | 
 | 212 |  | 
 | 213 | static void qlogic_detach(dev_link_t * link) | 
 | 214 | { | 
 | 215 | 	dev_link_t **linkp; | 
 | 216 |  | 
 | 217 | 	DEBUG(0, "qlogic_detach(0x%p)\n", link); | 
 | 218 |  | 
 | 219 | 	/* Locate device structure */ | 
 | 220 | 	for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) | 
 | 221 | 		if (*linkp == link) | 
 | 222 | 			break; | 
 | 223 | 	if (*linkp == NULL) | 
 | 224 | 		return; | 
 | 225 |  | 
 | 226 | 	if (link->state & DEV_CONFIG) | 
 | 227 | 		qlogic_release(link); | 
 | 228 |  | 
 | 229 | 	if (link->handle) | 
 | 230 | 		pcmcia_deregister_client(link->handle); | 
 | 231 |  | 
 | 232 | 	/* Unlink device structure, free bits */ | 
 | 233 | 	*linkp = link->next; | 
 | 234 | 	kfree(link->priv); | 
 | 235 |  | 
 | 236 | }				/* qlogic_detach */ | 
 | 237 |  | 
 | 238 | /*====================================================================*/ | 
 | 239 |  | 
 | 240 | #define CS_CHECK(fn, ret) \ | 
 | 241 | do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) | 
 | 242 |  | 
 | 243 | static void qlogic_config(dev_link_t * link) | 
 | 244 | { | 
 | 245 | 	client_handle_t handle = link->handle; | 
 | 246 | 	scsi_info_t *info = link->priv; | 
 | 247 | 	tuple_t tuple; | 
 | 248 | 	cisparse_t parse; | 
 | 249 | 	int i, last_ret, last_fn; | 
 | 250 | 	unsigned short tuple_data[32]; | 
 | 251 | 	struct Scsi_Host *host; | 
 | 252 |  | 
 | 253 | 	DEBUG(0, "qlogic_config(0x%p)\n", link); | 
 | 254 |  | 
 | 255 | 	tuple.TupleData = (cisdata_t *) tuple_data; | 
 | 256 | 	tuple.TupleDataMax = 64; | 
 | 257 | 	tuple.TupleOffset = 0; | 
 | 258 | 	tuple.DesiredTuple = CISTPL_CONFIG; | 
 | 259 | 	CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); | 
 | 260 | 	CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); | 
 | 261 | 	CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); | 
 | 262 | 	link->conf.ConfigBase = parse.config.base; | 
 | 263 |  | 
 | 264 | 	tuple.DesiredTuple = CISTPL_MANFID; | 
 | 265 | 	if ((pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) && (pcmcia_get_tuple_data(handle, &tuple) == CS_SUCCESS)) | 
 | 266 | 		info->manf_id = le16_to_cpu(tuple.TupleData[0]); | 
 | 267 |  | 
 | 268 | 	/* Configure card */ | 
 | 269 | 	link->state |= DEV_CONFIG; | 
 | 270 |  | 
 | 271 | 	tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; | 
 | 272 | 	CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); | 
 | 273 | 	while (1) { | 
 | 274 | 		if (pcmcia_get_tuple_data(handle, &tuple) != 0 || | 
 | 275 | 				pcmcia_parse_tuple(handle, &tuple, &parse) != 0) | 
 | 276 | 			goto next_entry; | 
 | 277 | 		link->conf.ConfigIndex = parse.cftable_entry.index; | 
 | 278 | 		link->io.BasePort1 = parse.cftable_entry.io.win[0].base; | 
 | 279 | 		link->io.NumPorts1 = parse.cftable_entry.io.win[0].len; | 
 | 280 | 		if (link->io.BasePort1 != 0) { | 
 | 281 | 			i = pcmcia_request_io(handle, &link->io); | 
 | 282 | 			if (i == CS_SUCCESS) | 
 | 283 | 				break; | 
 | 284 | 		} | 
 | 285 | 	      next_entry: | 
 | 286 | 		CS_CHECK(GetNextTuple, pcmcia_get_next_tuple(handle, &tuple)); | 
 | 287 | 	} | 
 | 288 |  | 
 | 289 | 	CS_CHECK(RequestIRQ, pcmcia_request_irq(handle, &link->irq)); | 
 | 290 | 	CS_CHECK(RequestConfiguration, pcmcia_request_configuration(handle, &link->conf)); | 
 | 291 |  | 
 | 292 | 	if ((info->manf_id == MANFID_MACNICA) || (info->manf_id == MANFID_PIONEER) || (info->manf_id == 0x0098)) { | 
 | 293 | 		/* set ATAcmd */ | 
 | 294 | 		outb(0xb4, link->io.BasePort1 + 0xd); | 
 | 295 | 		outb(0x24, link->io.BasePort1 + 0x9); | 
 | 296 | 		outb(0x04, link->io.BasePort1 + 0xd); | 
 | 297 | 	} | 
 | 298 |  | 
 | 299 | 	/* The KXL-810AN has a bigger IO port window */ | 
 | 300 | 	if (link->io.NumPorts1 == 32) | 
 | 301 | 		host = qlogic_detect(&qlogicfas_driver_template, link, | 
 | 302 | 			link->io.BasePort1 + 16, link->irq.AssignedIRQ); | 
 | 303 | 	else | 
 | 304 | 		host = qlogic_detect(&qlogicfas_driver_template, link, | 
 | 305 | 			link->io.BasePort1, link->irq.AssignedIRQ); | 
 | 306 | 	 | 
 | 307 | 	if (!host) { | 
 | 308 | 		printk(KERN_INFO "%s: no SCSI devices found\n", qlogic_name); | 
 | 309 | 		goto out; | 
 | 310 | 	} | 
 | 311 |  | 
 | 312 | 	sprintf(info->node.dev_name, "scsi%d", host->host_no); | 
 | 313 | 	link->dev = &info->node; | 
 | 314 | 	info->host = host; | 
 | 315 |  | 
 | 316 | out: | 
 | 317 | 	link->state &= ~DEV_CONFIG_PENDING; | 
 | 318 | 	return; | 
 | 319 |  | 
 | 320 | cs_failed: | 
 | 321 | 	cs_error(link->handle, last_fn, last_ret); | 
 | 322 | 	link->dev = NULL; | 
 | 323 | 	pcmcia_release_configuration(link->handle); | 
 | 324 | 	pcmcia_release_io(link->handle, &link->io); | 
 | 325 | 	pcmcia_release_irq(link->handle, &link->irq); | 
 | 326 | 	link->state &= ~DEV_CONFIG; | 
 | 327 | 	return; | 
 | 328 |  | 
 | 329 | }				/* qlogic_config */ | 
 | 330 |  | 
 | 331 | /*====================================================================*/ | 
 | 332 |  | 
 | 333 | static void qlogic_release(dev_link_t *link) | 
 | 334 | { | 
 | 335 | 	scsi_info_t *info = link->priv; | 
 | 336 |  | 
 | 337 | 	DEBUG(0, "qlogic_release(0x%p)\n", link); | 
 | 338 |  | 
 | 339 | 	scsi_remove_host(info->host); | 
 | 340 | 	link->dev = NULL; | 
 | 341 |  | 
 | 342 | 	free_irq(link->irq.AssignedIRQ, info->host); | 
 | 343 |  | 
 | 344 | 	pcmcia_release_configuration(link->handle); | 
 | 345 | 	pcmcia_release_io(link->handle, &link->io); | 
 | 346 | 	pcmcia_release_irq(link->handle, &link->irq); | 
 | 347 |  | 
 | 348 | 	scsi_host_put(info->host); | 
 | 349 |  | 
 | 350 | 	link->state &= ~DEV_CONFIG; | 
 | 351 | } | 
 | 352 |  | 
 | 353 | /*====================================================================*/ | 
 | 354 |  | 
 | 355 | static int qlogic_event(event_t event, int priority, event_callback_args_t * args) | 
 | 356 | { | 
 | 357 | 	dev_link_t *link = args->client_data; | 
 | 358 |  | 
 | 359 | 	DEBUG(1, "qlogic_event(0x%06x)\n", event); | 
 | 360 |  | 
 | 361 | 	switch (event) { | 
 | 362 | 	case CS_EVENT_CARD_REMOVAL: | 
 | 363 | 		link->state &= ~DEV_PRESENT; | 
 | 364 | 		if (link->state & DEV_CONFIG) | 
 | 365 | 			qlogic_release(link); | 
 | 366 | 		break; | 
 | 367 | 	case CS_EVENT_CARD_INSERTION: | 
 | 368 | 		link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; | 
 | 369 | 		qlogic_config(link); | 
 | 370 | 		break; | 
 | 371 | 	case CS_EVENT_PM_SUSPEND: | 
 | 372 | 		link->state |= DEV_SUSPEND; | 
 | 373 | 		/* Fall through... */ | 
 | 374 | 	case CS_EVENT_RESET_PHYSICAL: | 
 | 375 | 		if (link->state & DEV_CONFIG) | 
 | 376 | 			pcmcia_release_configuration(link->handle); | 
 | 377 | 		break; | 
 | 378 | 	case CS_EVENT_PM_RESUME: | 
 | 379 | 		link->state &= ~DEV_SUSPEND; | 
 | 380 | 		/* Fall through... */ | 
 | 381 | 	case CS_EVENT_CARD_RESET: | 
 | 382 | 		if (link->state & DEV_CONFIG) { | 
 | 383 | 			scsi_info_t *info = link->priv; | 
 | 384 | 			pcmcia_request_configuration(link->handle, &link->conf); | 
 | 385 | 			if ((info->manf_id == MANFID_MACNICA) || (info->manf_id == MANFID_PIONEER) || (info->manf_id == 0x0098)) { | 
 | 386 | 				outb(0x80, link->io.BasePort1 + 0xd); | 
 | 387 | 				outb(0x24, link->io.BasePort1 + 0x9); | 
 | 388 | 				outb(0x04, link->io.BasePort1 + 0xd); | 
 | 389 | 			} | 
 | 390 | 			/* Ugggglllyyyy!!! */ | 
 | 391 | 			qlogicfas408_bus_reset(NULL); | 
 | 392 | 		} | 
 | 393 | 		break; | 
 | 394 | 	} | 
 | 395 | 	return 0; | 
 | 396 | }				/* qlogic_event */ | 
 | 397 |  | 
 | 398 |  | 
 | 399 | static struct pcmcia_driver qlogic_cs_driver = { | 
 | 400 | 	.owner		= THIS_MODULE, | 
 | 401 | 	.drv		= { | 
 | 402 | 	.name		= "qlogic_cs", | 
 | 403 | 	}, | 
 | 404 | 	.attach		= qlogic_attach, | 
 | 405 | 	.detach		= qlogic_detach, | 
 | 406 | }; | 
 | 407 |  | 
 | 408 | static int __init init_qlogic_cs(void) | 
 | 409 | { | 
 | 410 | 	return pcmcia_register_driver(&qlogic_cs_driver); | 
 | 411 | } | 
 | 412 |  | 
 | 413 | static void __exit exit_qlogic_cs(void) | 
 | 414 | { | 
 | 415 | 	pcmcia_unregister_driver(&qlogic_cs_driver); | 
 | 416 | 	BUG_ON(dev_list != NULL); | 
 | 417 | } | 
 | 418 |  | 
 | 419 | MODULE_AUTHOR("Tom Zerucha, Michael Griffith"); | 
 | 420 | MODULE_DESCRIPTION("Driver for the PCMCIA Qlogic FAS SCSI controllers"); | 
 | 421 | MODULE_LICENSE("GPL"); | 
 | 422 | module_init(init_qlogic_cs); | 
 | 423 | module_exit(exit_qlogic_cs); |