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