[PATCH] hostap update
[linux-2.6] / drivers / net / wireless / hostap / hostap_plx.c
1 #define PRISM2_PLX
2
3 /* Host AP driver's support for PC Cards on PCI adapters using PLX9052 is
4  * based on:
5  * - Host AP driver patch from james@madingley.org
6  * - linux-wlan-ng driver, Copyright (C) AbsoluteValue Systems, Inc.
7  */
8
9
10 #include <linux/config.h>
11 #include <linux/version.h>
12 #include <linux/module.h>
13 #include <linux/init.h>
14 #include <linux/if.h>
15 #include <linux/skbuff.h>
16 #include <linux/netdevice.h>
17 #include <linux/workqueue.h>
18 #include <linux/wireless.h>
19 #include <net/iw_handler.h>
20
21 #include <linux/ioport.h>
22 #include <linux/pci.h>
23 #include <asm/io.h>
24
25 #include "hostap_wlan.h"
26
27
28 static char *version = PRISM2_VERSION " (Jouni Malinen <jkmaline@cc.hut.fi>)";
29 static char *dev_info = "hostap_plx";
30
31
32 MODULE_AUTHOR("Jouni Malinen");
33 MODULE_DESCRIPTION("Support for Intersil Prism2-based 802.11 wireless LAN "
34                    "cards (PLX).");
35 MODULE_SUPPORTED_DEVICE("Intersil Prism2-based WLAN cards (PLX)");
36 MODULE_LICENSE("GPL");
37 MODULE_VERSION(PRISM2_VERSION);
38
39
40 static int ignore_cis;
41 module_param(ignore_cis, int, 0444);
42 MODULE_PARM_DESC(ignore_cis, "Do not verify manfid information in CIS");
43
44
45 #define PLX_MIN_ATTR_LEN 512    /* at least 2 x 256 is needed for CIS */
46 #define COR_SRESET       0x80
47 #define COR_LEVLREQ      0x40
48 #define COR_ENABLE_FUNC  0x01
49 /* PCI Configuration Registers */
50 #define PLX_PCIIPR       0x3d   /* PCI Interrupt Pin */
51 /* Local Configuration Registers */
52 #define PLX_INTCSR       0x4c   /* Interrupt Control/Status Register */
53 #define PLX_INTCSR_PCI_INTEN BIT(6) /* PCI Interrupt Enable */
54 #define PLX_CNTRL        0x50
55 #define PLX_CNTRL_SERIAL_EEPROM_PRESENT BIT(28)
56
57
58 #define PLXDEV(vendor,dev,str) { vendor, dev, PCI_ANY_ID, PCI_ANY_ID }
59
60 static struct pci_device_id prism2_plx_id_table[] __devinitdata = {
61         PLXDEV(0x10b7, 0x7770, "3Com AirConnect PCI 777A"),
62         PLXDEV(0x111a, 0x1023, "Siemens SpeedStream SS1023"),
63         PLXDEV(0x126c, 0x8030, "Nortel emobility"),
64         PLXDEV(0x1385, 0x4100, "Netgear MA301"),
65         PLXDEV(0x15e8, 0x0130, "National Datacomm NCP130 (PLX9052)"),
66         PLXDEV(0x15e8, 0x0131, "National Datacomm NCP130 (TMD7160)"),
67         PLXDEV(0x1638, 0x1100, "Eumitcom WL11000"),
68         PLXDEV(0x16ab, 0x1101, "Global Sun Tech GL24110P (?)"),
69         PLXDEV(0x16ab, 0x1102, "Linksys WPC11 with WDT11"),
70         PLXDEV(0x16ab, 0x1103, "Longshine 8031"),
71         PLXDEV(0x16ec, 0x3685, "US Robotics USR2415"),
72         PLXDEV(0xec80, 0xec00, "Belkin F5D6000"),
73         { 0 }
74 };
75
76
77 /* Array of known Prism2/2.5 PC Card manufactured ids. If your card's manfid
78  * is not listed here, you will need to add it here to get the driver
79  * initialized. */
80 static struct prism2_plx_manfid {
81         u16 manfid1, manfid2;
82 } prism2_plx_known_manfids[] = {
83         { 0x000b, 0x7110 } /* D-Link DWL-650 Rev. P1 */,
84         { 0x000b, 0x7300 } /* Philips 802.11b WLAN PCMCIA */,
85         { 0x0101, 0x0777 } /* 3Com AirConnect PCI 777A */,
86         { 0x0126, 0x8000 } /* Proxim RangeLAN */,
87         { 0x0138, 0x0002 } /* Compaq WL100 */,
88         { 0x0156, 0x0002 } /* Intersil Prism II Ref. Design (and others) */,
89         { 0x026f, 0x030b } /* Buffalo WLI-CF-S11G */,
90         { 0x0274, 0x1612 } /* Linksys WPC11 Ver 2.5 */,
91         { 0x0274, 0x1613 } /* Linksys WPC11 Ver 3 */,
92         { 0x028a, 0x0002 } /* D-Link DRC-650 */,
93         { 0x0250, 0x0002 } /* Samsung SWL2000-N */,
94         { 0xc250, 0x0002 } /* EMTAC A2424i */,
95         { 0xd601, 0x0002 } /* Z-Com XI300 */,
96         { 0xd601, 0x0005 } /* Zcomax XI-325H 200mW */,
97         { 0, 0}
98 };
99
100
101 #ifdef PRISM2_IO_DEBUG
102
103 static inline void hfa384x_outb_debug(struct net_device *dev, int a, u8 v)
104 {
105         struct hostap_interface *iface;
106         local_info_t *local;
107         unsigned long flags;
108
109         iface = netdev_priv(dev);
110         local = iface->local;
111
112         spin_lock_irqsave(&local->lock, flags);
113         prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_OUTB, a, v);
114         outb(v, dev->base_addr + a);
115         spin_unlock_irqrestore(&local->lock, flags);
116 }
117
118 static inline u8 hfa384x_inb_debug(struct net_device *dev, int a)
119 {
120         struct hostap_interface *iface;
121         local_info_t *local;
122         unsigned long flags;
123         u8 v;
124
125         iface = netdev_priv(dev);
126         local = iface->local;
127
128         spin_lock_irqsave(&local->lock, flags);
129         v = inb(dev->base_addr + a);
130         prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_INB, a, v);
131         spin_unlock_irqrestore(&local->lock, flags);
132         return v;
133 }
134
135 static inline void hfa384x_outw_debug(struct net_device *dev, int a, u16 v)
136 {
137         struct hostap_interface *iface;
138         local_info_t *local;
139         unsigned long flags;
140
141         iface = netdev_priv(dev);
142         local = iface->local;
143
144         spin_lock_irqsave(&local->lock, flags);
145         prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_OUTW, a, v);
146         outw(v, dev->base_addr + a);
147         spin_unlock_irqrestore(&local->lock, flags);
148 }
149
150 static inline u16 hfa384x_inw_debug(struct net_device *dev, int a)
151 {
152         struct hostap_interface *iface;
153         local_info_t *local;
154         unsigned long flags;
155         u16 v;
156
157         iface = netdev_priv(dev);
158         local = iface->local;
159
160         spin_lock_irqsave(&local->lock, flags);
161         v = inw(dev->base_addr + a);
162         prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_INW, a, v);
163         spin_unlock_irqrestore(&local->lock, flags);
164         return v;
165 }
166
167 static inline void hfa384x_outsw_debug(struct net_device *dev, int a,
168                                        u8 *buf, int wc)
169 {
170         struct hostap_interface *iface;
171         local_info_t *local;
172         unsigned long flags;
173
174         iface = netdev_priv(dev);
175         local = iface->local;
176
177         spin_lock_irqsave(&local->lock, flags);
178         prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_OUTSW, a, wc);
179         outsw(dev->base_addr + a, buf, wc);
180         spin_unlock_irqrestore(&local->lock, flags);
181 }
182
183 static inline void hfa384x_insw_debug(struct net_device *dev, int a,
184                                       u8 *buf, int wc)
185 {
186         struct hostap_interface *iface;
187         local_info_t *local;
188         unsigned long flags;
189
190         iface = netdev_priv(dev);
191         local = iface->local;
192
193         spin_lock_irqsave(&local->lock, flags);
194         prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_INSW, a, wc);
195         insw(dev->base_addr + a, buf, wc);
196         spin_unlock_irqrestore(&local->lock, flags);
197 }
198
199 #define HFA384X_OUTB(v,a) hfa384x_outb_debug(dev, (a), (v))
200 #define HFA384X_INB(a) hfa384x_inb_debug(dev, (a))
201 #define HFA384X_OUTW(v,a) hfa384x_outw_debug(dev, (a), (v))
202 #define HFA384X_INW(a) hfa384x_inw_debug(dev, (a))
203 #define HFA384X_OUTSW(a, buf, wc) hfa384x_outsw_debug(dev, (a), (buf), (wc))
204 #define HFA384X_INSW(a, buf, wc) hfa384x_insw_debug(dev, (a), (buf), (wc))
205
206 #else /* PRISM2_IO_DEBUG */
207
208 #define HFA384X_OUTB(v,a) outb((v), dev->base_addr + (a))
209 #define HFA384X_INB(a) inb(dev->base_addr + (a))
210 #define HFA384X_OUTW(v,a) outw((v), dev->base_addr + (a))
211 #define HFA384X_INW(a) inw(dev->base_addr + (a))
212 #define HFA384X_INSW(a, buf, wc) insw(dev->base_addr + (a), buf, wc)
213 #define HFA384X_OUTSW(a, buf, wc) outsw(dev->base_addr + (a), buf, wc)
214
215 #endif /* PRISM2_IO_DEBUG */
216
217
218 static int hfa384x_from_bap(struct net_device *dev, u16 bap, void *buf,
219                             int len)
220 {
221         u16 d_off;
222         u16 *pos;
223
224         d_off = (bap == 1) ? HFA384X_DATA1_OFF : HFA384X_DATA0_OFF;
225         pos = (u16 *) buf;
226
227         if (len / 2)
228                 HFA384X_INSW(d_off, buf, len / 2);
229         pos += len / 2;
230
231         if (len & 1)
232                 *((char *) pos) = HFA384X_INB(d_off);
233
234         return 0;
235 }
236
237
238 static int hfa384x_to_bap(struct net_device *dev, u16 bap, void *buf, int len)
239 {
240         u16 d_off;
241         u16 *pos;
242
243         d_off = (bap == 1) ? HFA384X_DATA1_OFF : HFA384X_DATA0_OFF;
244         pos = (u16 *) buf;
245
246         if (len / 2)
247                 HFA384X_OUTSW(d_off, buf, len / 2);
248         pos += len / 2;
249
250         if (len & 1)
251                 HFA384X_OUTB(*((char *) pos), d_off);
252
253         return 0;
254 }
255
256
257 /* FIX: This might change at some point.. */
258 #include "hostap_hw.c"
259
260
261 static void prism2_plx_cor_sreset(local_info_t *local)
262 {
263         unsigned char corsave;
264
265         printk(KERN_DEBUG "%s: Doing reset via direct COR access.\n",
266                dev_info);
267
268         /* Set sreset bit of COR and clear it after hold time */
269
270         if (local->attr_mem == NULL) {
271                 /* TMD7160 - COR at card's first I/O addr */
272                 corsave = inb(local->cor_offset);
273                 outb(corsave | COR_SRESET, local->cor_offset);
274                 mdelay(2);
275                 outb(corsave & ~COR_SRESET, local->cor_offset);
276                 mdelay(2);
277         } else {
278                 /* PLX9052 */
279                 corsave = readb(local->attr_mem + local->cor_offset);
280                 writeb(corsave | COR_SRESET,
281                        local->attr_mem + local->cor_offset);
282                 mdelay(2);
283                 writeb(corsave & ~COR_SRESET,
284                        local->attr_mem + local->cor_offset);
285                 mdelay(2);
286         }
287 }
288
289
290 static void prism2_plx_genesis_reset(local_info_t *local, int hcr)
291 {
292         unsigned char corsave;
293
294         if (local->attr_mem == NULL) {
295                 /* TMD7160 - COR at card's first I/O addr */
296                 corsave = inb(local->cor_offset);
297                 outb(corsave | COR_SRESET, local->cor_offset);
298                 mdelay(10);
299                 outb(hcr, local->cor_offset + 2);
300                 mdelay(10);
301                 outb(corsave & ~COR_SRESET, local->cor_offset);
302                 mdelay(10);
303         } else {
304                 /* PLX9052 */
305                 corsave = readb(local->attr_mem + local->cor_offset);
306                 writeb(corsave | COR_SRESET,
307                        local->attr_mem + local->cor_offset);
308                 mdelay(10);
309                 writeb(hcr, local->attr_mem + local->cor_offset + 2);
310                 mdelay(10);
311                 writeb(corsave & ~COR_SRESET,
312                        local->attr_mem + local->cor_offset);
313                 mdelay(10);
314         }
315 }
316
317
318 static struct prism2_helper_functions prism2_plx_funcs =
319 {
320         .card_present   = NULL,
321         .cor_sreset     = prism2_plx_cor_sreset,
322         .dev_open       = NULL,
323         .dev_close      = NULL,
324         .genesis_reset  = prism2_plx_genesis_reset,
325         .hw_type        = HOSTAP_HW_PLX,
326 };
327
328
329 static int prism2_plx_check_cis(void __iomem *attr_mem, int attr_len,
330                                 unsigned int *cor_offset,
331                                 unsigned int *cor_index)
332 {
333 #define CISTPL_CONFIG 0x1A
334 #define CISTPL_MANFID 0x20
335 #define CISTPL_END 0xFF
336 #define CIS_MAX_LEN 256
337         u8 *cis;
338         int i, pos;
339         unsigned int rmsz, rasz, manfid1, manfid2;
340         struct prism2_plx_manfid *manfid;
341
342         cis = kmalloc(CIS_MAX_LEN, GFP_KERNEL);
343         if (cis == NULL)
344                 return -ENOMEM;
345
346         /* read CIS; it is in even offsets in the beginning of attr_mem */
347         for (i = 0; i < CIS_MAX_LEN; i++)
348                 cis[i] = readb(attr_mem + 2 * i);
349         printk(KERN_DEBUG "%s: CIS: %02x %02x %02x %02x %02x %02x ...\n",
350                dev_info, cis[0], cis[1], cis[2], cis[3], cis[4], cis[5]);
351
352         /* set reasonable defaults for Prism2 cards just in case CIS parsing
353          * fails */
354         *cor_offset = 0x3e0;
355         *cor_index = 0x01;
356         manfid1 = manfid2 = 0;
357
358         pos = 0;
359         while (pos < CIS_MAX_LEN - 1 && cis[pos] != CISTPL_END) {
360                 if (pos + cis[pos + 1] >= CIS_MAX_LEN)
361                         goto cis_error;
362
363                 switch (cis[pos]) {
364                 case CISTPL_CONFIG:
365                         if (cis[pos + 1] < 1)
366                                 goto cis_error;
367                         rmsz = (cis[pos + 2] & 0x3c) >> 2;
368                         rasz = cis[pos + 2] & 0x03;
369                         if (4 + rasz + rmsz > cis[pos + 1])
370                                 goto cis_error;
371                         *cor_index = cis[pos + 3] & 0x3F;
372                         *cor_offset = 0;
373                         for (i = 0; i <= rasz; i++)
374                                 *cor_offset += cis[pos + 4 + i] << (8 * i);
375                         printk(KERN_DEBUG "%s: cor_index=0x%x "
376                                "cor_offset=0x%x\n", dev_info,
377                                *cor_index, *cor_offset);
378                         if (*cor_offset > attr_len) {
379                                 printk(KERN_ERR "%s: COR offset not within "
380                                        "attr_mem\n", dev_info);
381                                 kfree(cis);
382                                 return -1;
383                         }
384                         break;
385
386                 case CISTPL_MANFID:
387                         if (cis[pos + 1] < 4)
388                                 goto cis_error;
389                         manfid1 = cis[pos + 2] + (cis[pos + 3] << 8);
390                         manfid2 = cis[pos + 4] + (cis[pos + 5] << 8);
391                         printk(KERN_DEBUG "%s: manfid=0x%04x, 0x%04x\n",
392                                dev_info, manfid1, manfid2);
393                         break;
394                 }
395
396                 pos += cis[pos + 1] + 2;
397         }
398
399         if (pos >= CIS_MAX_LEN || cis[pos] != CISTPL_END)
400                 goto cis_error;
401
402         for (manfid = prism2_plx_known_manfids; manfid->manfid1 != 0; manfid++)
403                 if (manfid1 == manfid->manfid1 && manfid2 == manfid->manfid2) {
404                         kfree(cis);
405                         return 0;
406                 }
407
408         printk(KERN_INFO "%s: unknown manfid 0x%04x, 0x%04x - assuming this is"
409                " not supported card\n", dev_info, manfid1, manfid2);
410         goto fail;
411
412  cis_error:
413         printk(KERN_WARNING "%s: invalid CIS data\n", dev_info);
414
415  fail:
416         kfree(cis);
417         if (ignore_cis) {
418                 printk(KERN_INFO "%s: ignore_cis parameter set - ignoring "
419                        "errors during CIS verification\n", dev_info);
420                 return 0;
421         }
422         return -1;
423 }
424
425
426 static int prism2_plx_probe(struct pci_dev *pdev,
427                             const struct pci_device_id *id)
428 {
429         unsigned int pccard_ioaddr, plx_ioaddr;
430         unsigned long pccard_attr_mem;
431         unsigned int pccard_attr_len;
432         void __iomem *attr_mem = NULL;
433         unsigned int cor_offset, cor_index;
434         u32 reg;
435         local_info_t *local = NULL;
436         struct net_device *dev = NULL;
437         struct hostap_interface *iface;
438         static int cards_found /* = 0 */;
439         int irq_registered = 0;
440         int tmd7160;
441
442         if (pci_enable_device(pdev))
443                 return -EIO;
444
445         /* National Datacomm NCP130 based on TMD7160, not PLX9052. */
446         tmd7160 = (pdev->vendor == 0x15e8) && (pdev->device == 0x0131);
447
448         plx_ioaddr = pci_resource_start(pdev, 1);
449         pccard_ioaddr = pci_resource_start(pdev, tmd7160 ? 2 : 3);
450
451         if (tmd7160) {
452                 /* TMD7160 */
453                 attr_mem = NULL; /* no access to PC Card attribute memory */
454
455                 printk(KERN_INFO "TMD7160 PCI/PCMCIA adapter: io=0x%x, "
456                        "irq=%d, pccard_io=0x%x\n",
457                        plx_ioaddr, pdev->irq, pccard_ioaddr);
458
459                 cor_offset = plx_ioaddr;
460                 cor_index = 0x04;
461
462                 outb(cor_index | COR_LEVLREQ | COR_ENABLE_FUNC, plx_ioaddr);
463                 mdelay(1);
464                 reg = inb(plx_ioaddr);
465                 if (reg != (cor_index | COR_LEVLREQ | COR_ENABLE_FUNC)) {
466                         printk(KERN_ERR "%s: Error setting COR (expected="
467                                "0x%02x, was=0x%02x)\n", dev_info,
468                                cor_index | COR_LEVLREQ | COR_ENABLE_FUNC, reg);
469                         goto fail;
470                 }
471         } else {
472                 /* PLX9052 */
473                 pccard_attr_mem = pci_resource_start(pdev, 2);
474                 pccard_attr_len = pci_resource_len(pdev, 2);
475                 if (pccard_attr_len < PLX_MIN_ATTR_LEN)
476                         goto fail;
477
478
479                 attr_mem = ioremap(pccard_attr_mem, pccard_attr_len);
480                 if (attr_mem == NULL) {
481                         printk(KERN_ERR "%s: cannot remap attr_mem\n",
482                                dev_info);
483                         goto fail;
484                 }
485
486                 printk(KERN_INFO "PLX9052 PCI/PCMCIA adapter: "
487                        "mem=0x%lx, plx_io=0x%x, irq=%d, pccard_io=0x%x\n",
488                        pccard_attr_mem, plx_ioaddr, pdev->irq, pccard_ioaddr);
489
490                 if (prism2_plx_check_cis(attr_mem, pccard_attr_len,
491                                          &cor_offset, &cor_index)) {
492                         printk(KERN_INFO "Unknown PC Card CIS - not a "
493                                "Prism2/2.5 card?\n");
494                         goto fail;
495                 }
496
497                 printk(KERN_DEBUG "Prism2/2.5 PC Card detected in PLX9052 "
498                        "adapter\n");
499
500                 /* Write COR to enable PC Card */
501                 writeb(cor_index | COR_LEVLREQ | COR_ENABLE_FUNC,
502                        attr_mem + cor_offset);
503
504                 /* Enable PCI interrupts if they are not already enabled */
505                 reg = inl(plx_ioaddr + PLX_INTCSR);
506                 printk(KERN_DEBUG "PLX_INTCSR=0x%x\n", reg);
507                 if (!(reg & PLX_INTCSR_PCI_INTEN)) {
508                         outl(reg | PLX_INTCSR_PCI_INTEN,
509                              plx_ioaddr + PLX_INTCSR);
510                         if (!(inl(plx_ioaddr + PLX_INTCSR) &
511                               PLX_INTCSR_PCI_INTEN)) {
512                                 printk(KERN_WARNING "%s: Could not enable "
513                                        "Local Interrupts\n", dev_info);
514                                 goto fail;
515                         }
516                 }
517
518                 reg = inl(plx_ioaddr + PLX_CNTRL);
519                 printk(KERN_DEBUG "PLX_CNTRL=0x%x (Serial EEPROM "
520                        "present=%d)\n",
521                        reg, (reg & PLX_CNTRL_SERIAL_EEPROM_PRESENT) != 0);
522                 /* should set PLX_PCIIPR to 0x01 (INTA#) if Serial EEPROM is
523                  * not present; but are there really such cards in use(?) */
524         }
525
526         dev = prism2_init_local_data(&prism2_plx_funcs, cards_found,
527                                      &pdev->dev);
528         if (dev == NULL)
529                 goto fail;
530         iface = netdev_priv(dev);
531         local = iface->local;
532         cards_found++;
533
534         dev->irq = pdev->irq;
535         dev->base_addr = pccard_ioaddr;
536         local->attr_mem = attr_mem;
537         local->cor_offset = cor_offset;
538
539         pci_set_drvdata(pdev, dev);
540
541         if (request_irq(dev->irq, prism2_interrupt, SA_SHIRQ, dev->name,
542                         dev)) {
543                 printk(KERN_WARNING "%s: request_irq failed\n", dev->name);
544                 goto fail;
545         } else
546                 irq_registered = 1;
547
548         if (prism2_hw_config(dev, 1)) {
549                 printk(KERN_DEBUG "%s: hardware initialization failed\n",
550                        dev_info);
551                 goto fail;
552         }
553
554         return hostap_hw_ready(dev);
555
556  fail:
557         prism2_free_local_data(dev);
558
559         if (irq_registered && dev)
560                 free_irq(dev->irq, dev);
561
562         if (attr_mem)
563                 iounmap(attr_mem);
564
565         pci_disable_device(pdev);
566
567         return -ENODEV;
568 }
569
570
571 static void prism2_plx_remove(struct pci_dev *pdev)
572 {
573         struct net_device *dev;
574         struct hostap_interface *iface;
575
576         dev = pci_get_drvdata(pdev);
577         iface = netdev_priv(dev);
578
579         /* Reset the hardware, and ensure interrupts are disabled. */
580         prism2_plx_cor_sreset(iface->local);
581         hfa384x_disable_interrupts(dev);
582
583         if (iface->local->attr_mem)
584                 iounmap(iface->local->attr_mem);
585         if (dev->irq)
586                 free_irq(dev->irq, dev);
587
588         prism2_free_local_data(dev);
589         pci_disable_device(pdev);
590 }
591
592
593 MODULE_DEVICE_TABLE(pci, prism2_plx_id_table);
594
595 static struct pci_driver prism2_plx_drv_id = {
596         .name           = "prism2_plx",
597         .id_table       = prism2_plx_id_table,
598         .probe          = prism2_plx_probe,
599         .remove         = prism2_plx_remove,
600         .suspend        = NULL,
601         .resume         = NULL,
602         .enable_wake    = NULL
603 };
604
605
606 static int __init init_prism2_plx(void)
607 {
608         printk(KERN_INFO "%s: %s\n", dev_info, version);
609
610         return pci_register_driver(&prism2_plx_drv_id);
611 }
612
613
614 static void __exit exit_prism2_plx(void)
615 {
616         pci_unregister_driver(&prism2_plx_drv_id);
617         printk(KERN_INFO "%s: Driver unloaded\n", dev_info);
618 }
619
620
621 module_init(init_prism2_plx);
622 module_exit(exit_prism2_plx);