1/*======================================================================
2
3    A driver for the Qlogic SCSI card
4
5    qlogic_cs.c 1.83 2001/10/13 00:08:53
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
23    which 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/timer.h>
41#include <linux/ioport.h>
42#include <asm/io.h>
43#include <asm/byteorder.h>
44#include <scsi/scsi.h>
45#include <linux/major.h>
46#include <linux/blk.h>
47
48#include <../drivers/scsi/scsi.h>
49#include <../drivers/scsi/hosts.h>
50#include <scsi/scsi_ioctl.h>
51
52#include <../drivers/scsi/qlogicfas.h>
53
54#define qlogic_reset(h) qlogicfas_reset(h, 0)
55
56#include <pcmcia/version.h>
57#include <pcmcia/cs_types.h>
58#include <pcmcia/cs.h>
59#include <pcmcia/cistpl.h>
60#include <pcmcia/ds.h>
61#include <pcmcia/ciscode.h>
62
63extern void qlogicfas_preset(int port, int irq);
64
65/*====================================================================*/
66
67/* Module parameters */
68
69MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
70MODULE_DESCRIPTION("Qlogic PCMCIA SCSI driver");
71MODULE_LICENSE("Dual MPL/GPL");
72
73#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i")
74
75/* Bit map of interrupts to choose from */
76INT_MODULE_PARM(irq_mask, 0xdeb8);
77static int irq_list[4] = { -1 };
78MODULE_PARM(irq_list, "1-4i");
79
80#ifdef PCMCIA_DEBUG
81INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG);
82#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args)
83static char *version =
84"qlogic_cs.c 1.83 2001/10/13 00:08:53 (David Hinds)";
85#else
86#define DEBUG(n, args...)
87#endif
88
89/*====================================================================*/
90
91typedef struct scsi_info_t {
92    dev_link_t		link;
93    u_short		manf_id;
94    int			ndev;
95    dev_node_t		node[8];
96} scsi_info_t;
97
98static void qlogic_release(u_long arg);
99static int qlogic_event(event_t event, int priority,
100			event_callback_args_t *args);
101
102static dev_link_t *qlogic_attach(void);
103static void qlogic_detach(dev_link_t *);
104
105static Scsi_Host_Template driver_template = QLOGICFAS;
106
107static dev_link_t *dev_list = NULL;
108
109static dev_info_t dev_info = "qlogic_cs";
110
111/*====================================================================*/
112
113static void cs_error(client_handle_t handle, int func, int ret)
114{
115    error_info_t err = { func, ret };
116    CardServices(ReportError, handle, &err);
117}
118
119/*====================================================================*/
120
121static dev_link_t *qlogic_attach(void)
122{
123    scsi_info_t *info;
124    client_reg_t client_reg;
125    dev_link_t *link;
126    int i, ret;
127
128    DEBUG(0, "qlogic_attach()\n");
129
130    /* Create new SCSI device */
131    info = kmalloc(sizeof(*info), GFP_KERNEL);
132    if (!info) return NULL;
133    memset(info, 0, sizeof(*info));
134    link = &info->link; link->priv = info;
135    link->release.function = &qlogic_release;
136    link->release.data = (u_long)link;
137
138    link->io.NumPorts1 = 16;
139    link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO;
140    link->io.IOAddrLines = 10;
141    link->irq.Attributes = IRQ_TYPE_EXCLUSIVE;
142    link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID;
143    if (irq_list[0] == -1)
144	link->irq.IRQInfo2 = irq_mask;
145    else
146	for (i = 0; i < 4; i++)
147	    link->irq.IRQInfo2 |= 1 << irq_list[i];
148    link->conf.Attributes = CONF_ENABLE_IRQ;
149    link->conf.Vcc = 50;
150    link->conf.IntType = INT_MEMORY_AND_IO;
151    link->conf.Present = PRESENT_OPTION;
152
153    /* Register with Card Services */
154    link->next = dev_list;
155    dev_list = link;
156    client_reg.dev_info = &dev_info;
157    client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE;
158    client_reg.event_handler = &qlogic_event;
159    client_reg.EventMask =
160	CS_EVENT_RESET_REQUEST | CS_EVENT_CARD_RESET |
161	CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
162	CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
163    client_reg.Version = 0x0210;
164    client_reg.event_callback_args.client_data = link;
165    ret = CardServices(RegisterClient, &link->handle, &client_reg);
166    if (ret != 0) {
167	cs_error(link->handle, RegisterClient, ret);
168	qlogic_detach(link);
169	return NULL;
170    }
171
172    return link;
173} /* qlogic_attach */
174
175/*====================================================================*/
176
177static void qlogic_detach(dev_link_t *link)
178{
179    dev_link_t **linkp;
180
181    DEBUG(0, "qlogic_detach(0x%p)\n", link);
182
183    /* Locate device structure */
184    for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next)
185	if (*linkp == link) break;
186    if (*linkp == NULL)
187	return;
188
189    del_timer(&link->release);
190    if (link->state & DEV_CONFIG) {
191	qlogic_release((u_long)link);
192	if (link->state & DEV_STALE_CONFIG) {
193	    link->state |= DEV_STALE_LINK;
194	    return;
195	}
196    }
197
198    if (link->handle)
199	CardServices(DeregisterClient, link->handle);
200
201    /* Unlink device structure, free bits */
202    *linkp = link->next;
203    kfree(link->priv);
204
205} /* qlogic_detach */
206
207/*====================================================================*/
208
209#define CS_CHECK(fn, args...) \
210while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed
211
212#define CFG_CHECK(fn, args...) \
213if (CardServices(fn, args) != 0) goto next_entry
214
215static void qlogic_config(dev_link_t *link)
216{
217    client_handle_t handle = link->handle;
218    scsi_info_t *info = link->priv;
219    tuple_t tuple;
220    cisparse_t parse;
221    int i, last_ret, last_fn;
222    u_short tuple_data[32];
223    Scsi_Device *dev;
224    dev_node_t **tail, *node;
225    struct Scsi_Host *host;
226
227    DEBUG(0, "qlogic_config(0x%p)\n", link);
228
229    tuple.TupleData = (cisdata_t *)tuple_data;
230    tuple.TupleDataMax = 64;
231    tuple.TupleOffset = 0;
232    tuple.DesiredTuple = CISTPL_CONFIG;
233    CS_CHECK(GetFirstTuple, handle, &tuple);
234    CS_CHECK(GetTupleData, handle, &tuple);
235    CS_CHECK(ParseTuple, handle, &tuple, &parse);
236    link->conf.ConfigBase = parse.config.base;
237
238    tuple.DesiredTuple = CISTPL_MANFID;
239    if ((CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) &&
240	(CardServices(GetTupleData, handle, &tuple) == CS_SUCCESS))
241	info->manf_id = le16_to_cpu(tuple.TupleData[0]);
242
243    /* Configure card */
244    driver_template.module = &__this_module;
245    link->state |= DEV_CONFIG;
246
247    tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
248    CS_CHECK(GetFirstTuple, handle, &tuple);
249    while (1) {
250	CFG_CHECK(GetTupleData, handle, &tuple);
251	CFG_CHECK(ParseTuple, handle, &tuple, &parse);
252	link->conf.ConfigIndex = parse.cftable_entry.index;
253	link->io.BasePort1 = parse.cftable_entry.io.win[0].base;
254	link->io.NumPorts1 = parse.cftable_entry.io.win[0].len;
255	if (link->io.BasePort1 != 0) {
256	    i = CardServices(RequestIO, handle, &link->io);
257	    if (i == CS_SUCCESS) break;
258	}
259    next_entry:
260	CS_CHECK(GetNextTuple, handle, &tuple);
261    }
262
263    CS_CHECK(RequestIRQ, handle, &link->irq);
264    CS_CHECK(RequestConfiguration, handle, &link->conf);
265
266    if ((info->manf_id == MANFID_MACNICA) ||
267	(info->manf_id == MANFID_PIONEER) ||
268	(info->manf_id == 0x0098)) {
269	/* set ATAcmd */
270	outb(0xb4, link->io.BasePort1+0xd);
271	outb(0x24, link->io.BasePort1+0x9);
272	outb(0x04, link->io.BasePort1+0xd);
273    }
274
275    /* A bad hack... */
276    release_region(link->io.BasePort1, link->io.NumPorts1);
277
278    /* The KXL-810AN has a bigger IO port window */
279    if (link->io.NumPorts1 == 32)
280	qlogicfas_preset(link->io.BasePort1+16, link->irq.AssignedIRQ);
281    else
282	qlogicfas_preset(link->io.BasePort1, link->irq.AssignedIRQ);
283
284    scsi_register_module(MODULE_SCSI_HA, &driver_template);
285
286    tail = &link->dev;
287    info->ndev = 0;
288    for (host = scsi_hostlist; host; host = host->next)
289	if (host->hostt == &driver_template)
290	    for (dev = host->host_queue; dev; dev = dev->next) {
291	    u_long arg[2], id;
292	    kernel_scsi_ioctl(dev, SCSI_IOCTL_GET_IDLUN, arg);
293	    id = (arg[0]&0x0f) + ((arg[0]>>4)&0xf0) +
294		((arg[0]>>8)&0xf00) + ((arg[0]>>12)&0xf000);
295	    node = &info->node[info->ndev];
296	    node->minor = 0;
297	    switch (dev->type) {
298	    case TYPE_TAPE:
299		node->major = SCSI_TAPE_MAJOR;
300		sprintf(node->dev_name, "st#%04lx", id);
301		break;
302	    case TYPE_DISK:
303	    case TYPE_MOD:
304		node->major = SCSI_DISK0_MAJOR;
305		sprintf(node->dev_name, "sd#%04lx", id);
306		break;
307	    case TYPE_ROM:
308	    case TYPE_WORM:
309		node->major = SCSI_CDROM_MAJOR;
310		sprintf(node->dev_name, "sr#%04lx", id);
311		break;
312	    default:
313		node->major = SCSI_GENERIC_MAJOR;
314		sprintf(node->dev_name, "sg#%04lx", id);
315		break;
316	    }
317	    *tail = node; tail = &node->next;
318	    info->ndev++;
319	}
320    *tail = NULL;
321    if (info->ndev == 0)
322	printk(KERN_INFO "qlogic_cs: no SCSI devices found\n");
323
324    link->state &= ~DEV_CONFIG_PENDING;
325    return;
326
327cs_failed:
328    cs_error(link->handle, last_fn, last_ret);
329    qlogic_release((u_long)link);
330    return;
331
332} /* qlogic_config */
333
334/*====================================================================*/
335
336static void qlogic_release(u_long arg)
337{
338    dev_link_t *link = (dev_link_t *)arg;
339
340    DEBUG(0, "qlogic_release(0x%p)\n", link);
341
342    if (GET_USE_COUNT(&__this_module) != 0) {
343	DEBUG(0, "qlogic_cs: release postponed, device still open\n");
344	link->state |= DEV_STALE_CONFIG;
345	return;
346    }
347
348    scsi_unregister_module(MODULE_SCSI_HA, &driver_template);
349    link->dev = NULL;
350
351    CardServices(ReleaseConfiguration, link->handle);
352    CardServices(ReleaseIO, link->handle, &link->io);
353    CardServices(ReleaseIRQ, link->handle, &link->irq);
354
355    link->state &= ~DEV_CONFIG;
356    if (link->state & DEV_STALE_LINK)
357	qlogic_detach(link);
358
359} /* qlogic_release */
360
361/*====================================================================*/
362
363static int qlogic_event(event_t event, int priority,
364			event_callback_args_t *args)
365{
366    dev_link_t *link = args->client_data;
367
368    DEBUG(1, "qlogic_event(0x%06x)\n", event);
369
370    switch (event) {
371    case CS_EVENT_CARD_REMOVAL:
372	link->state &= ~DEV_PRESENT;
373	if (link->state & DEV_CONFIG)
374	    mod_timer(&link->release, jiffies + HZ/20);
375	break;
376    case CS_EVENT_CARD_INSERTION:
377	link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
378	qlogic_config(link);
379	break;
380    case CS_EVENT_PM_SUSPEND:
381	link->state |= DEV_SUSPEND;
382	/* Fall through... */
383    case CS_EVENT_RESET_PHYSICAL:
384	if (link->state & DEV_CONFIG)
385	    CardServices(ReleaseConfiguration, link->handle);
386	break;
387    case CS_EVENT_PM_RESUME:
388	link->state &= ~DEV_SUSPEND;
389	/* Fall through... */
390    case CS_EVENT_CARD_RESET:
391	if (link->state & DEV_CONFIG) {
392	    scsi_info_t *info = link->priv;
393	    CardServices(RequestConfiguration, link->handle, &link->conf);
394	    if ((info->manf_id == MANFID_MACNICA) ||
395		(info->manf_id == MANFID_PIONEER) ||
396		(info->manf_id == 0x0098)) {
397		outb( 0x80, link->io.BasePort1+0xd);
398		outb( 0x24, link->io.BasePort1+0x9);
399		outb( 0x04, link->io.BasePort1+0xd);
400	    }
401	    qlogic_reset(NULL);
402	}
403	break;
404    }
405    return 0;
406} /* qlogic_event */
407
408/*====================================================================*/
409
410static int __init init_qlogic_cs(void) {
411    servinfo_t serv;
412    DEBUG(0, "%s\n", version);
413    CardServices(GetCardServicesInfo, &serv);
414    if (serv.Revision != CS_RELEASE_CODE) {
415	printk(KERN_NOTICE "qlogic_cs: Card Services release "
416	       "does not match!\n");
417	return -1;
418    }
419    register_pccard_driver(&dev_info, &qlogic_attach, &qlogic_detach);
420    return 0;
421}
422
423static void __exit exit_qlogic_cs(void) {
424    DEBUG(0, "qlogic_cs: unloading\n");
425    unregister_pccard_driver(&dev_info);
426    while (dev_list != NULL)
427	qlogic_detach(dev_list);
428}
429
430module_init(init_qlogic_cs);
431module_exit(exit_qlogic_cs);
432