diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/net/wireless/orinoco_plx.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/net/wireless/orinoco_plx.c')
-rw-r--r-- | drivers/net/wireless/orinoco_plx.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/drivers/net/wireless/orinoco_plx.c b/drivers/net/wireless/orinoco_plx.c new file mode 100644 index 000000000000..7ab05b89fb3f --- /dev/null +++ b/drivers/net/wireless/orinoco_plx.c | |||
@@ -0,0 +1,419 @@ | |||
1 | /* orinoco_plx.c | ||
2 | * | ||
3 | * Driver for Prism II devices which would usually be driven by orinoco_cs, | ||
4 | * but are connected to the PCI bus by a PLX9052. | ||
5 | * | ||
6 | * Current maintainers (as of 29 September 2003) are: | ||
7 | * Pavel Roskin <proski AT gnu.org> | ||
8 | * and David Gibson <hermes AT gibson.dropbear.id.au> | ||
9 | * | ||
10 | * (C) Copyright David Gibson, IBM Corp. 2001-2003. | ||
11 | * Copyright (C) 2001 Daniel Barlow | ||
12 | * | ||
13 | * The contents of this file are subject to the Mozilla Public License | ||
14 | * Version 1.1 (the "License"); you may not use this file except in | ||
15 | * compliance with the License. You may obtain a copy of the License | ||
16 | * at http://www.mozilla.org/MPL/ | ||
17 | * | ||
18 | * Software distributed under the License is distributed on an "AS IS" | ||
19 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See | ||
20 | * the License for the specific language governing rights and | ||
21 | * limitations under the License. | ||
22 | * | ||
23 | * Alternatively, the contents of this file may be used under the | ||
24 | * terms of the GNU General Public License version 2 (the "GPL"), in | ||
25 | * which case the provisions of the GPL are applicable instead of the | ||
26 | * above. If you wish to allow the use of your version of this file | ||
27 | * only under the terms of the GPL and not to allow others to use your | ||
28 | * version of this file under the MPL, indicate your decision by | ||
29 | * deleting the provisions above and replace them with the notice and | ||
30 | * other provisions required by the GPL. If you do not delete the | ||
31 | * provisions above, a recipient may use your version of this file | ||
32 | * under either the MPL or the GPL. | ||
33 | |||
34 | * Caution: this is experimental and probably buggy. For success and | ||
35 | * failure reports for different cards and adaptors, see | ||
36 | * orinoco_plx_pci_id_table near the end of the file. If you have a | ||
37 | * card we don't have the PCI id for, and looks like it should work, | ||
38 | * drop me mail with the id and "it works"/"it doesn't work". | ||
39 | * | ||
40 | * Note: if everything gets detected fine but it doesn't actually send | ||
41 | * or receive packets, your first port of call should probably be to | ||
42 | * try newer firmware in the card. Especially if you're doing Ad-Hoc | ||
43 | * modes. | ||
44 | * | ||
45 | * The actual driving is done by orinoco.c, this is just resource | ||
46 | * allocation stuff. The explanation below is courtesy of Ryan Niemi | ||
47 | * on the linux-wlan-ng list at | ||
48 | * http://archives.neohapsis.com/archives/dev/linux-wlan/2001-q1/0026.html | ||
49 | * | ||
50 | * The PLX9052-based cards (WL11000 and several others) are a | ||
51 | * different beast than the usual PCMCIA-based PRISM2 configuration | ||
52 | * expected by wlan-ng. Here's the general details on how the WL11000 | ||
53 | * PCI adapter works: | ||
54 | * | ||
55 | * - Two PCI I/O address spaces, one 0x80 long which contains the | ||
56 | * PLX9052 registers, and one that's 0x40 long mapped to the PCMCIA | ||
57 | * slot I/O address space. | ||
58 | * | ||
59 | * - One PCI memory address space, mapped to the PCMCIA memory space | ||
60 | * (containing the CIS). | ||
61 | * | ||
62 | * After identifying the I/O and memory space, you can read through | ||
63 | * the memory space to confirm the CIS's device ID or manufacturer ID | ||
64 | * to make sure it's the expected card. qKeep in mind that the PCMCIA | ||
65 | * spec specifies the CIS as the lower 8 bits of each word read from | ||
66 | * the CIS, so to read the bytes of the CIS, read every other byte | ||
67 | * (0,2,4,...). Passing that test, you need to enable the I/O address | ||
68 | * space on the PCMCIA card via the PCMCIA COR register. This is the | ||
69 | * first byte following the CIS. In my case (which may not have any | ||
70 | * relation to what's on the PRISM2 cards), COR was at offset 0x800 | ||
71 | * within the PCI memory space. Write 0x41 to the COR register to | ||
72 | * enable I/O mode and to select level triggered interrupts. To | ||
73 | * confirm you actually succeeded, read the COR register back and make | ||
74 | * sure it actually got set to 0x41, incase you have an unexpected | ||
75 | * card inserted. | ||
76 | * | ||
77 | * Following that, you can treat the second PCI I/O address space (the | ||
78 | * one that's not 0x80 in length) as the PCMCIA I/O space. | ||
79 | * | ||
80 | * Note that in the Eumitcom's source for their drivers, they register | ||
81 | * the interrupt as edge triggered when registering it with the | ||
82 | * Windows kernel. I don't recall how to register edge triggered on | ||
83 | * Linux (if it can be done at all). But in some experimentation, I | ||
84 | * don't see much operational difference between using either | ||
85 | * interrupt mode. Don't mess with the interrupt mode in the COR | ||
86 | * register though, as the PLX9052 wants level triggers with the way | ||
87 | * the serial EEPROM configures it on the WL11000. | ||
88 | * | ||
89 | * There's some other little quirks related to timing that I bumped | ||
90 | * into, but I don't recall right now. Also, there's two variants of | ||
91 | * the WL11000 I've seen, revision A1 and T2. These seem to differ | ||
92 | * slightly in the timings configured in the wait-state generator in | ||
93 | * the PLX9052. There have also been some comments from Eumitcom that | ||
94 | * cards shouldn't be hot swapped, apparently due to risk of cooking | ||
95 | * the PLX9052. I'm unsure why they believe this, as I can't see | ||
96 | * anything in the design that would really cause a problem, except | ||
97 | * for crashing drivers not written to expect it. And having developed | ||
98 | * drivers for the WL11000, I'd say it's quite tricky to write code | ||
99 | * that will successfully deal with a hot unplug. Very odd things | ||
100 | * happen on the I/O side of things. But anyway, be warned. Despite | ||
101 | * that, I've hot-swapped a number of times during debugging and | ||
102 | * driver development for various reasons (stuck WAIT# line after the | ||
103 | * radio card's firmware locks up). | ||
104 | * | ||
105 | * Hope this is enough info for someone to add PLX9052 support to the | ||
106 | * wlan-ng card. In the case of the WL11000, the PCI ID's are | ||
107 | * 0x1639/0x0200, with matching subsystem ID's. Other PLX9052-based | ||
108 | * manufacturers other than Eumitcom (or on cards other than the | ||
109 | * WL11000) may have different PCI ID's. | ||
110 | * | ||
111 | * If anyone needs any more specific info, let me know. I haven't had | ||
112 | * time to implement support myself yet, and with the way things are | ||
113 | * going, might not have time for a while.. | ||
114 | */ | ||
115 | |||
116 | #define DRIVER_NAME "orinoco_plx" | ||
117 | #define PFX DRIVER_NAME ": " | ||
118 | |||
119 | #include <linux/config.h> | ||
120 | |||
121 | #include <linux/module.h> | ||
122 | #include <linux/kernel.h> | ||
123 | #include <linux/init.h> | ||
124 | #include <linux/sched.h> | ||
125 | #include <linux/ptrace.h> | ||
126 | #include <linux/slab.h> | ||
127 | #include <linux/string.h> | ||
128 | #include <linux/timer.h> | ||
129 | #include <linux/ioport.h> | ||
130 | #include <asm/uaccess.h> | ||
131 | #include <asm/io.h> | ||
132 | #include <asm/system.h> | ||
133 | #include <linux/netdevice.h> | ||
134 | #include <linux/if_arp.h> | ||
135 | #include <linux/etherdevice.h> | ||
136 | #include <linux/list.h> | ||
137 | #include <linux/pci.h> | ||
138 | #include <linux/fcntl.h> | ||
139 | |||
140 | #include <pcmcia/cisreg.h> | ||
141 | |||
142 | #include "hermes.h" | ||
143 | #include "orinoco.h" | ||
144 | |||
145 | #define COR_OFFSET (0x3e0) /* COR attribute offset of Prism2 PC card */ | ||
146 | #define COR_VALUE (COR_LEVEL_REQ | COR_FUNC_ENA) /* Enable PC card with interrupt in level trigger */ | ||
147 | #define COR_RESET (0x80) /* reset bit in the COR register */ | ||
148 | #define PLX_RESET_TIME (500) /* milliseconds */ | ||
149 | |||
150 | #define PLX_INTCSR 0x4c /* Interrupt Control & Status Register */ | ||
151 | #define PLX_INTCSR_INTEN (1<<6) /* Interrupt Enable bit */ | ||
152 | |||
153 | static const u8 cis_magic[] = { | ||
154 | 0x01, 0x03, 0x00, 0x00, 0xff, 0x17, 0x04, 0x67 | ||
155 | }; | ||
156 | |||
157 | /* Orinoco PLX specific data */ | ||
158 | struct orinoco_plx_card { | ||
159 | void __iomem *attr_mem; | ||
160 | }; | ||
161 | |||
162 | /* | ||
163 | * Do a soft reset of the card using the Configuration Option Register | ||
164 | */ | ||
165 | static int orinoco_plx_cor_reset(struct orinoco_private *priv) | ||
166 | { | ||
167 | hermes_t *hw = &priv->hw; | ||
168 | struct orinoco_plx_card *card = priv->card; | ||
169 | u8 __iomem *attr_mem = card->attr_mem; | ||
170 | unsigned long timeout; | ||
171 | u16 reg; | ||
172 | |||
173 | writeb(COR_VALUE | COR_RESET, attr_mem + COR_OFFSET); | ||
174 | mdelay(1); | ||
175 | |||
176 | writeb(COR_VALUE, attr_mem + COR_OFFSET); | ||
177 | mdelay(1); | ||
178 | |||
179 | /* Just in case, wait more until the card is no longer busy */ | ||
180 | timeout = jiffies + (PLX_RESET_TIME * HZ / 1000); | ||
181 | reg = hermes_read_regn(hw, CMD); | ||
182 | while (time_before(jiffies, timeout) && (reg & HERMES_CMD_BUSY)) { | ||
183 | mdelay(1); | ||
184 | reg = hermes_read_regn(hw, CMD); | ||
185 | } | ||
186 | |||
187 | /* Did we timeout ? */ | ||
188 | if (reg & HERMES_CMD_BUSY) { | ||
189 | printk(KERN_ERR PFX "Busy timeout\n"); | ||
190 | return -ETIMEDOUT; | ||
191 | } | ||
192 | |||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | |||
197 | static int orinoco_plx_init_one(struct pci_dev *pdev, | ||
198 | const struct pci_device_id *ent) | ||
199 | { | ||
200 | int err = 0; | ||
201 | u8 __iomem *attr_mem = NULL; | ||
202 | u32 csr_reg, plx_addr; | ||
203 | struct orinoco_private *priv = NULL; | ||
204 | struct orinoco_plx_card *card; | ||
205 | unsigned long pccard_ioaddr = 0; | ||
206 | unsigned long pccard_iolen = 0; | ||
207 | struct net_device *dev = NULL; | ||
208 | void __iomem *mem; | ||
209 | int i; | ||
210 | |||
211 | err = pci_enable_device(pdev); | ||
212 | if (err) { | ||
213 | printk(KERN_ERR PFX "Cannot enable PCI device\n"); | ||
214 | return err; | ||
215 | } | ||
216 | |||
217 | err = pci_request_regions(pdev, DRIVER_NAME); | ||
218 | if (err != 0) { | ||
219 | printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); | ||
220 | goto fail_resources; | ||
221 | } | ||
222 | |||
223 | /* Resource 1 is mapped to PLX-specific registers */ | ||
224 | plx_addr = pci_resource_start(pdev, 1); | ||
225 | |||
226 | /* Resource 2 is mapped to the PCMCIA attribute memory */ | ||
227 | attr_mem = ioremap(pci_resource_start(pdev, 2), | ||
228 | pci_resource_len(pdev, 2)); | ||
229 | if (!attr_mem) { | ||
230 | printk(KERN_ERR PFX "Cannot remap PCMCIA space\n"); | ||
231 | goto fail_map_attr; | ||
232 | } | ||
233 | |||
234 | /* Resource 3 is mapped to the PCMCIA I/O address space */ | ||
235 | pccard_ioaddr = pci_resource_start(pdev, 3); | ||
236 | pccard_iolen = pci_resource_len(pdev, 3); | ||
237 | |||
238 | mem = pci_iomap(pdev, 3, 0); | ||
239 | if (!mem) { | ||
240 | err = -ENOMEM; | ||
241 | goto fail_map_io; | ||
242 | } | ||
243 | |||
244 | /* Allocate network device */ | ||
245 | dev = alloc_orinocodev(sizeof(*card), orinoco_plx_cor_reset); | ||
246 | if (!dev) { | ||
247 | printk(KERN_ERR PFX "Cannot allocate network device\n"); | ||
248 | err = -ENOMEM; | ||
249 | goto fail_alloc; | ||
250 | } | ||
251 | |||
252 | priv = netdev_priv(dev); | ||
253 | card = priv->card; | ||
254 | card->attr_mem = attr_mem; | ||
255 | dev->base_addr = pccard_ioaddr; | ||
256 | SET_MODULE_OWNER(dev); | ||
257 | SET_NETDEV_DEV(dev, &pdev->dev); | ||
258 | |||
259 | hermes_struct_init(&priv->hw, mem, HERMES_16BIT_REGSPACING); | ||
260 | |||
261 | printk(KERN_DEBUG PFX "Detected Orinoco/Prism2 PLX device " | ||
262 | "at %s irq:%d, io addr:0x%lx\n", pci_name(pdev), pdev->irq, | ||
263 | pccard_ioaddr); | ||
264 | |||
265 | err = request_irq(pdev->irq, orinoco_interrupt, SA_SHIRQ, | ||
266 | dev->name, dev); | ||
267 | if (err) { | ||
268 | printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); | ||
269 | err = -EBUSY; | ||
270 | goto fail_irq; | ||
271 | } | ||
272 | dev->irq = pdev->irq; | ||
273 | |||
274 | /* bjoern: We need to tell the card to enable interrupts, in | ||
275 | case the serial eprom didn't do this already. See the | ||
276 | PLX9052 data book, p8-1 and 8-24 for reference. */ | ||
277 | csr_reg = inl(plx_addr + PLX_INTCSR); | ||
278 | if (!(csr_reg & PLX_INTCSR_INTEN)) { | ||
279 | csr_reg |= PLX_INTCSR_INTEN; | ||
280 | outl(csr_reg, plx_addr + PLX_INTCSR); | ||
281 | csr_reg = inl(plx_addr + PLX_INTCSR); | ||
282 | if (!(csr_reg & PLX_INTCSR_INTEN)) { | ||
283 | printk(KERN_ERR PFX "Cannot enable interrupts\n"); | ||
284 | goto fail; | ||
285 | } | ||
286 | } | ||
287 | |||
288 | err = orinoco_plx_cor_reset(priv); | ||
289 | if (err) { | ||
290 | printk(KERN_ERR PFX "Initial reset failed\n"); | ||
291 | goto fail; | ||
292 | } | ||
293 | |||
294 | printk(KERN_DEBUG PFX "CIS: "); | ||
295 | for (i = 0; i < 16; i++) { | ||
296 | printk("%02X:", readb(attr_mem + 2*i)); | ||
297 | } | ||
298 | printk("\n"); | ||
299 | |||
300 | /* Verify whether a supported PC card is present */ | ||
301 | /* FIXME: we probably need to be smarted about this */ | ||
302 | for (i = 0; i < sizeof(cis_magic); i++) { | ||
303 | if (cis_magic[i] != readb(attr_mem +2*i)) { | ||
304 | printk(KERN_ERR PFX "The CIS value of Prism2 PC " | ||
305 | "card is unexpected\n"); | ||
306 | err = -EIO; | ||
307 | goto fail; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | err = register_netdev(dev); | ||
312 | if (err) { | ||
313 | printk(KERN_ERR PFX "Cannot register network device\n"); | ||
314 | goto fail; | ||
315 | } | ||
316 | |||
317 | pci_set_drvdata(pdev, dev); | ||
318 | |||
319 | return 0; | ||
320 | |||
321 | fail: | ||
322 | free_irq(pdev->irq, dev); | ||
323 | |||
324 | fail_irq: | ||
325 | pci_set_drvdata(pdev, NULL); | ||
326 | free_orinocodev(dev); | ||
327 | |||
328 | fail_alloc: | ||
329 | pci_iounmap(pdev, mem); | ||
330 | |||
331 | fail_map_io: | ||
332 | iounmap(attr_mem); | ||
333 | |||
334 | fail_map_attr: | ||
335 | pci_release_regions(pdev); | ||
336 | |||
337 | fail_resources: | ||
338 | pci_disable_device(pdev); | ||
339 | |||
340 | return err; | ||
341 | } | ||
342 | |||
343 | static void __devexit orinoco_plx_remove_one(struct pci_dev *pdev) | ||
344 | { | ||
345 | struct net_device *dev = pci_get_drvdata(pdev); | ||
346 | struct orinoco_private *priv = netdev_priv(dev); | ||
347 | struct orinoco_plx_card *card = priv->card; | ||
348 | u8 __iomem *attr_mem = card->attr_mem; | ||
349 | |||
350 | BUG_ON(! dev); | ||
351 | |||
352 | unregister_netdev(dev); | ||
353 | free_irq(dev->irq, dev); | ||
354 | pci_set_drvdata(pdev, NULL); | ||
355 | free_orinocodev(dev); | ||
356 | pci_iounmap(pdev, priv->hw.iobase); | ||
357 | iounmap(attr_mem); | ||
358 | pci_release_regions(pdev); | ||
359 | pci_disable_device(pdev); | ||
360 | } | ||
361 | |||
362 | |||
363 | static struct pci_device_id orinoco_plx_pci_id_table[] = { | ||
364 | {0x111a, 0x1023, PCI_ANY_ID, PCI_ANY_ID,}, /* Siemens SpeedStream SS1023 */ | ||
365 | {0x1385, 0x4100, PCI_ANY_ID, PCI_ANY_ID,}, /* Netgear MA301 */ | ||
366 | {0x15e8, 0x0130, PCI_ANY_ID, PCI_ANY_ID,}, /* Correga - does this work? */ | ||
367 | {0x1638, 0x1100, PCI_ANY_ID, PCI_ANY_ID,}, /* SMC EZConnect SMC2602W, | ||
368 | Eumitcom PCI WL11000, | ||
369 | Addtron AWA-100 */ | ||
370 | {0x16ab, 0x1100, PCI_ANY_ID, PCI_ANY_ID,}, /* Global Sun Tech GL24110P */ | ||
371 | {0x16ab, 0x1101, PCI_ANY_ID, PCI_ANY_ID,}, /* Reported working, but unknown */ | ||
372 | {0x16ab, 0x1102, PCI_ANY_ID, PCI_ANY_ID,}, /* Linksys WDT11 */ | ||
373 | {0x16ec, 0x3685, PCI_ANY_ID, PCI_ANY_ID,}, /* USR 2415 */ | ||
374 | {0xec80, 0xec00, PCI_ANY_ID, PCI_ANY_ID,}, /* Belkin F5D6000 tested by | ||
375 | Brendan W. McAdams <rit AT jacked-in.org> */ | ||
376 | {0x10b7, 0x7770, PCI_ANY_ID, PCI_ANY_ID,}, /* 3Com AirConnect PCI tested by | ||
377 | Damien Persohn <damien AT persohn.net> */ | ||
378 | {0,}, | ||
379 | }; | ||
380 | |||
381 | MODULE_DEVICE_TABLE(pci, orinoco_plx_pci_id_table); | ||
382 | |||
383 | static struct pci_driver orinoco_plx_driver = { | ||
384 | .name = DRIVER_NAME, | ||
385 | .id_table = orinoco_plx_pci_id_table, | ||
386 | .probe = orinoco_plx_init_one, | ||
387 | .remove = __devexit_p(orinoco_plx_remove_one), | ||
388 | }; | ||
389 | |||
390 | static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION | ||
391 | " (Pavel Roskin <proski@gnu.org>," | ||
392 | " David Gibson <hermes@gibson.dropbear.id.au>," | ||
393 | " Daniel Barlow <dan@telent.net>)"; | ||
394 | MODULE_AUTHOR("Daniel Barlow <dan@telent.net>"); | ||
395 | MODULE_DESCRIPTION("Driver for wireless LAN cards using the PLX9052 PCI bridge"); | ||
396 | MODULE_LICENSE("Dual MPL/GPL"); | ||
397 | |||
398 | static int __init orinoco_plx_init(void) | ||
399 | { | ||
400 | printk(KERN_DEBUG "%s\n", version); | ||
401 | return pci_module_init(&orinoco_plx_driver); | ||
402 | } | ||
403 | |||
404 | static void __exit orinoco_plx_exit(void) | ||
405 | { | ||
406 | pci_unregister_driver(&orinoco_plx_driver); | ||
407 | ssleep(1); | ||
408 | } | ||
409 | |||
410 | module_init(orinoco_plx_init); | ||
411 | module_exit(orinoco_plx_exit); | ||
412 | |||
413 | /* | ||
414 | * Local variables: | ||
415 | * c-indent-level: 8 | ||
416 | * c-basic-offset: 8 | ||
417 | * tab-width: 8 | ||
418 | * End: | ||
419 | */ | ||