Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* orinoco_pci.c |
2 | * | |
3 | * Driver for Prism II devices that have a direct PCI interface | |
4 | * (i.e., not in a Pcmcia or PLX bridge) | |
5 | * | |
6 | * Specifically here we're talking about the Linksys WMP11 | |
7 | * | |
8 | * Current maintainers (as of 29 September 2003) are: | |
9 | * Pavel Roskin <proski AT gnu.org> | |
10 | * and David Gibson <hermes AT gibson.dropbear.id.au> | |
11 | * | |
12 | * Some of this code is borrowed from orinoco_plx.c | |
13 | * Copyright (C) 2001 Daniel Barlow <dan AT telent.net> | |
14 | * Some of this code is "inspired" by linux-wlan-ng-0.1.10, but nothing | |
15 | * has been copied from it. linux-wlan-ng-0.1.10 is originally : | |
16 | * Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved. | |
17 | * This file originally written by: | |
18 | * Copyright (C) 2001 Jean Tourrilhes <jt AT hpl.hp.com> | |
19 | * And is now maintained by: | |
20 | * (C) Copyright David Gibson, IBM Corp. 2002-2003. | |
21 | * | |
22 | * The contents of this file are subject to the Mozilla Public License | |
23 | * Version 1.1 (the "License"); you may not use this file except in | |
24 | * compliance with the License. You may obtain a copy of the License | |
25 | * at http://www.mozilla.org/MPL/ | |
26 | * | |
27 | * Software distributed under the License is distributed on an "AS IS" | |
28 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See | |
29 | * the License for the specific language governing rights and | |
30 | * limitations under the License. | |
31 | * | |
32 | * Alternatively, the contents of this file may be used under the | |
33 | * terms of the GNU General Public License version 2 (the "GPL"), in | |
34 | * which case the provisions of the GPL are applicable instead of the | |
35 | * above. If you wish to allow the use of your version of this file | |
36 | * only under the terms of the GPL and not to allow others to use your | |
37 | * version of this file under the MPL, indicate your decision by | |
38 | * deleting the provisions above and replace them with the notice and | |
39 | * other provisions required by the GPL. If you do not delete the | |
40 | * provisions above, a recipient may use your version of this file | |
41 | * under either the MPL or the GPL. | |
42 | */ | |
43 | ||
44 | /* | |
45 | * Theory of operation... | |
46 | * ------------------- | |
47 | * Maybe you had a look in orinoco_plx. Well, this is totally different... | |
48 | * | |
49 | * The card contains only one PCI region, which contains all the usual | |
50 | * hermes registers. | |
51 | * | |
52 | * The driver will memory map this region in normal memory. Because | |
53 | * the hermes registers are mapped in normal memory and not in ISA I/O | |
54 | * post space, we can't use the usual inw/outw macros and we need to | |
55 | * use readw/writew. | |
56 | * This slight difference force us to compile our own version of | |
57 | * hermes.c with the register access macro changed. That's a bit | |
58 | * hackish but works fine. | |
59 | * | |
60 | * Note that the PCI region is pretty big (4K). That's much more than | |
61 | * the usual set of hermes register (0x0 -> 0x3E). I've got a strong | |
62 | * suspicion that the whole memory space of the adapter is in fact in | |
63 | * this region. Accessing directly the adapter memory instead of going | |
64 | * through the usual register would speed up significantely the | |
65 | * operations... | |
66 | * | |
67 | * Finally, the card looks like this : | |
68 | ----------------------- | |
69 | Bus 0, device 14, function 0: | |
70 | Network controller: PCI device 1260:3873 (Harris Semiconductor) (rev 1). | |
71 | IRQ 11. | |
72 | Master Capable. Latency=248. | |
73 | Prefetchable 32 bit memory at 0xffbcc000 [0xffbccfff]. | |
74 | ----------------------- | |
75 | 00:0e.0 Network controller: Harris Semiconductor: Unknown device 3873 (rev 01) | |
76 | Subsystem: Unknown device 1737:3874 | |
77 | Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- | |
78 | Status: Cap+ 66Mhz- UDF- FastB2B+ ParErr- DEVSEL=medium >TAbort- <TAbort- <MAbort- >SERR- <PERR- | |
79 | Latency: 248 set, cache line size 08 | |
80 | Interrupt: pin A routed to IRQ 11 | |
81 | Region 0: Memory at ffbcc000 (32-bit, prefetchable) [size=4K] | |
82 | Capabilities: [dc] Power Management version 2 | |
83 | Flags: PMEClk- AuxPwr- DSI- D1+ D2+ PME+ | |
84 | Status: D0 PME-Enable- DSel=0 DScale=0 PME- | |
85 | ----------------------- | |
86 | * | |
87 | * That's all.. | |
88 | * | |
89 | * Jean II | |
90 | */ | |
91 | ||
92 | #define DRIVER_NAME "orinoco_pci" | |
93 | #define PFX DRIVER_NAME ": " | |
94 | ||
95 | #include <linux/config.h> | |
96 | ||
97 | #include <linux/module.h> | |
98 | #include <linux/kernel.h> | |
99 | #include <linux/init.h> | |
100 | #include <linux/sched.h> | |
101 | #include <linux/ptrace.h> | |
102 | #include <linux/slab.h> | |
103 | #include <linux/string.h> | |
104 | #include <linux/timer.h> | |
105 | #include <linux/ioport.h> | |
106 | #include <linux/netdevice.h> | |
107 | #include <linux/if_arp.h> | |
108 | #include <linux/etherdevice.h> | |
109 | #include <linux/list.h> | |
110 | #include <linux/pci.h> | |
111 | #include <linux/fcntl.h> | |
112 | ||
113 | #include <asm/uaccess.h> | |
114 | #include <asm/io.h> | |
115 | #include <asm/system.h> | |
116 | ||
117 | #include "hermes.h" | |
118 | #include "orinoco.h" | |
119 | ||
120 | /* All the magic there is from wlan-ng */ | |
121 | /* Magic offset of the reset register of the PCI card */ | |
122 | #define HERMES_PCI_COR (0x26) | |
123 | /* Magic bitmask to reset the card */ | |
124 | #define HERMES_PCI_COR_MASK (0x0080) | |
125 | /* Magic timeouts for doing the reset. | |
126 | * Those times are straight from wlan-ng, and it is claimed that they | |
127 | * are necessary. Alan will kill me. Take your time and grab a coffee. */ | |
128 | #define HERMES_PCI_COR_ONT (250) /* ms */ | |
129 | #define HERMES_PCI_COR_OFFT (500) /* ms */ | |
130 | #define HERMES_PCI_COR_BUSYT (500) /* ms */ | |
131 | ||
132 | /* Orinoco PCI specific data */ | |
133 | struct orinoco_pci_card { | |
134 | void __iomem *pci_ioaddr; | |
135 | }; | |
136 | ||
137 | /* | |
138 | * Do a soft reset of the PCI card using the Configuration Option Register | |
139 | * We need this to get going... | |
140 | * This is the part of the code that is strongly inspired from wlan-ng | |
141 | * | |
142 | * Note : This code is done with irq enabled. This mean that many | |
143 | * interrupts will occur while we are there. This is why we use the | |
144 | * jiffies to regulate time instead of a straight mdelay(). Usually we | |
145 | * need only around 245 iteration of the loop to do 250 ms delay. | |
146 | * | |
147 | * Note bis : Don't try to access HERMES_CMD during the reset phase. | |
148 | * It just won't work ! | |
149 | */ | |
150 | static int | |
151 | orinoco_pci_cor_reset(struct orinoco_private *priv) | |
152 | { | |
153 | hermes_t *hw = &priv->hw; | |
154 | unsigned long timeout; | |
155 | u16 reg; | |
156 | ||
157 | /* Assert the reset until the card notice */ | |
158 | hermes_write_regn(hw, PCI_COR, HERMES_PCI_COR_MASK); | |
159 | mdelay(HERMES_PCI_COR_ONT); | |
160 | ||
161 | /* Give time for the card to recover from this hard effort */ | |
162 | hermes_write_regn(hw, PCI_COR, 0x0000); | |
163 | mdelay(HERMES_PCI_COR_OFFT); | |
164 | ||
165 | /* The card is ready when it's no longer busy */ | |
166 | timeout = jiffies + (HERMES_PCI_COR_BUSYT * HZ / 1000); | |
167 | reg = hermes_read_regn(hw, CMD); | |
168 | while (time_before(jiffies, timeout) && (reg & HERMES_CMD_BUSY)) { | |
169 | mdelay(1); | |
170 | reg = hermes_read_regn(hw, CMD); | |
171 | } | |
172 | ||
173 | /* Still busy? */ | |
174 | if (reg & HERMES_CMD_BUSY) { | |
175 | printk(KERN_ERR PFX "Busy timeout\n"); | |
176 | return -ETIMEDOUT; | |
177 | } | |
178 | ||
179 | return 0; | |
180 | } | |
181 | ||
182 | /* | |
183 | * Initialise a card. Mostly similar to PLX code. | |
184 | */ | |
185 | static int orinoco_pci_init_one(struct pci_dev *pdev, | |
186 | const struct pci_device_id *ent) | |
187 | { | |
188 | int err = 0; | |
189 | unsigned long pci_iorange; | |
190 | u16 __iomem *pci_ioaddr = NULL; | |
191 | unsigned long pci_iolen; | |
192 | struct orinoco_private *priv = NULL; | |
193 | struct orinoco_pci_card *card; | |
194 | struct net_device *dev = NULL; | |
195 | ||
196 | err = pci_enable_device(pdev); | |
197 | if (err) { | |
198 | printk(KERN_ERR PFX "Cannot enable PCI device\n"); | |
199 | return err; | |
200 | } | |
201 | ||
202 | err = pci_request_regions(pdev, DRIVER_NAME); | |
203 | if (err != 0) { | |
204 | printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); | |
205 | goto fail_resources; | |
206 | } | |
207 | ||
208 | /* Resource 0 is mapped to the hermes registers */ | |
209 | pci_iorange = pci_resource_start(pdev, 0); | |
210 | pci_iolen = pci_resource_len(pdev, 0); | |
211 | pci_ioaddr = ioremap(pci_iorange, pci_iolen); | |
212 | if (!pci_iorange) { | |
213 | printk(KERN_ERR PFX "Cannot remap hardware registers\n"); | |
214 | goto fail_map; | |
215 | } | |
216 | ||
217 | /* Allocate network device */ | |
218 | dev = alloc_orinocodev(sizeof(*card), orinoco_pci_cor_reset); | |
219 | if (! dev) { | |
220 | err = -ENOMEM; | |
221 | goto fail_alloc; | |
222 | } | |
223 | ||
224 | priv = netdev_priv(dev); | |
225 | card = priv->card; | |
226 | card->pci_ioaddr = pci_ioaddr; | |
227 | dev->mem_start = pci_iorange; | |
228 | dev->mem_end = pci_iorange + pci_iolen - 1; | |
229 | SET_MODULE_OWNER(dev); | |
230 | SET_NETDEV_DEV(dev, &pdev->dev); | |
231 | ||
232 | hermes_struct_init(&priv->hw, pci_ioaddr, HERMES_32BIT_REGSPACING); | |
233 | ||
234 | printk(KERN_DEBUG PFX "Detected device %s, mem:0x%lx-0x%lx, irq %d\n", | |
235 | pci_name(pdev), dev->mem_start, dev->mem_end, pdev->irq); | |
236 | ||
237 | err = request_irq(pdev->irq, orinoco_interrupt, SA_SHIRQ, | |
238 | dev->name, dev); | |
239 | if (err) { | |
240 | printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); | |
241 | err = -EBUSY; | |
242 | goto fail_irq; | |
243 | } | |
244 | dev->irq = pdev->irq; | |
245 | ||
246 | /* Perform a COR reset to start the card */ | |
247 | err = orinoco_pci_cor_reset(priv); | |
248 | if (err) { | |
249 | printk(KERN_ERR PFX "Initial reset failed\n"); | |
250 | goto fail; | |
251 | } | |
252 | ||
253 | err = register_netdev(dev); | |
254 | if (err) { | |
255 | printk(KERN_ERR PFX "Failed to register net device\n"); | |
256 | goto fail; | |
257 | } | |
258 | ||
259 | pci_set_drvdata(pdev, dev); | |
260 | ||
261 | return 0; | |
262 | ||
263 | fail: | |
264 | free_irq(pdev->irq, dev); | |
265 | ||
266 | fail_irq: | |
267 | pci_set_drvdata(pdev, NULL); | |
268 | free_orinocodev(dev); | |
269 | ||
270 | fail_alloc: | |
271 | iounmap(pci_ioaddr); | |
272 | ||
273 | fail_map: | |
274 | pci_release_regions(pdev); | |
275 | ||
276 | fail_resources: | |
277 | pci_disable_device(pdev); | |
278 | ||
279 | return err; | |
280 | } | |
281 | ||
282 | static void __devexit orinoco_pci_remove_one(struct pci_dev *pdev) | |
283 | { | |
284 | struct net_device *dev = pci_get_drvdata(pdev); | |
285 | struct orinoco_private *priv = netdev_priv(dev); | |
286 | struct orinoco_pci_card *card = priv->card; | |
287 | ||
288 | unregister_netdev(dev); | |
289 | free_irq(dev->irq, dev); | |
290 | pci_set_drvdata(pdev, NULL); | |
291 | free_orinocodev(dev); | |
292 | iounmap(card->pci_ioaddr); | |
293 | pci_release_regions(pdev); | |
294 | pci_disable_device(pdev); | |
295 | } | |
296 | ||
297 | static int orinoco_pci_suspend(struct pci_dev *pdev, u32 state) | |
298 | { | |
299 | struct net_device *dev = pci_get_drvdata(pdev); | |
300 | struct orinoco_private *priv = netdev_priv(dev); | |
301 | unsigned long flags; | |
302 | int err; | |
303 | ||
304 | printk(KERN_DEBUG "%s: Orinoco-PCI entering sleep mode (state=%d)\n", | |
305 | dev->name, state); | |
306 | ||
307 | err = orinoco_lock(priv, &flags); | |
308 | if (err) { | |
309 | printk(KERN_ERR "%s: hw_unavailable on orinoco_pci_suspend\n", | |
310 | dev->name); | |
311 | return err; | |
312 | } | |
313 | ||
314 | err = __orinoco_down(dev); | |
315 | if (err) | |
316 | printk(KERN_WARNING "%s: orinoco_pci_suspend(): Error %d downing interface\n", | |
317 | dev->name, err); | |
318 | ||
319 | netif_device_detach(dev); | |
320 | ||
321 | priv->hw_unavailable++; | |
322 | ||
323 | orinoco_unlock(priv, &flags); | |
324 | ||
325 | pci_save_state(pdev); | |
326 | pci_set_power_state(pdev, 3); | |
327 | ||
328 | return 0; | |
329 | } | |
330 | ||
331 | static int orinoco_pci_resume(struct pci_dev *pdev) | |
332 | { | |
333 | struct net_device *dev = pci_get_drvdata(pdev); | |
334 | struct orinoco_private *priv = netdev_priv(dev); | |
335 | unsigned long flags; | |
336 | int err; | |
337 | ||
338 | printk(KERN_DEBUG "%s: Orinoco-PCI waking up\n", dev->name); | |
339 | ||
340 | pci_set_power_state(pdev, 0); | |
341 | pci_restore_state(pdev); | |
342 | ||
343 | err = orinoco_reinit_firmware(dev); | |
344 | if (err) { | |
345 | printk(KERN_ERR "%s: Error %d re-initializing firmware on orinoco_pci_resume()\n", | |
346 | dev->name, err); | |
347 | return err; | |
348 | } | |
349 | ||
350 | spin_lock_irqsave(&priv->lock, flags); | |
351 | ||
352 | netif_device_attach(dev); | |
353 | ||
354 | priv->hw_unavailable--; | |
355 | ||
356 | if (priv->open && (! priv->hw_unavailable)) { | |
357 | err = __orinoco_up(dev); | |
358 | if (err) | |
359 | printk(KERN_ERR "%s: Error %d restarting card on orinoco_pci_resume()\n", | |
360 | dev->name, err); | |
361 | } | |
362 | ||
363 | spin_unlock_irqrestore(&priv->lock, flags); | |
364 | ||
365 | return 0; | |
366 | } | |
367 | ||
368 | static struct pci_device_id orinoco_pci_pci_id_table[] = { | |
369 | /* Intersil Prism 3 */ | |
370 | {0x1260, 0x3872, PCI_ANY_ID, PCI_ANY_ID,}, | |
371 | /* Intersil Prism 2.5 */ | |
372 | {0x1260, 0x3873, PCI_ANY_ID, PCI_ANY_ID,}, | |
373 | /* Samsung MagicLAN SWL-2210P */ | |
374 | {0x167d, 0xa000, PCI_ANY_ID, PCI_ANY_ID,}, | |
375 | {0,}, | |
376 | }; | |
377 | ||
378 | MODULE_DEVICE_TABLE(pci, orinoco_pci_pci_id_table); | |
379 | ||
380 | static struct pci_driver orinoco_pci_driver = { | |
381 | .name = DRIVER_NAME, | |
382 | .id_table = orinoco_pci_pci_id_table, | |
383 | .probe = orinoco_pci_init_one, | |
384 | .remove = __devexit_p(orinoco_pci_remove_one), | |
385 | .suspend = orinoco_pci_suspend, | |
386 | .resume = orinoco_pci_resume, | |
387 | }; | |
388 | ||
389 | static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION | |
390 | " (Pavel Roskin <proski@gnu.org>," | |
391 | " David Gibson <hermes@gibson.dropbear.id.au> &" | |
392 | " Jean Tourrilhes <jt@hpl.hp.com>)"; | |
393 | MODULE_AUTHOR("Pavel Roskin <proski@gnu.org> & David Gibson <hermes@gibson.dropbear.id.au>"); | |
394 | MODULE_DESCRIPTION("Driver for wireless LAN cards using direct PCI interface"); | |
395 | MODULE_LICENSE("Dual MPL/GPL"); | |
396 | ||
397 | static int __init orinoco_pci_init(void) | |
398 | { | |
399 | printk(KERN_DEBUG "%s\n", version); | |
400 | return pci_module_init(&orinoco_pci_driver); | |
401 | } | |
402 | ||
403 | static void __exit orinoco_pci_exit(void) | |
404 | { | |
405 | pci_unregister_driver(&orinoco_pci_driver); | |
406 | } | |
407 | ||
408 | module_init(orinoco_pci_init); | |
409 | module_exit(orinoco_pci_exit); | |
410 | ||
411 | /* | |
412 | * Local variables: | |
413 | * c-indent-level: 8 | |
414 | * c-basic-offset: 8 | |
415 | * tab-width: 8 | |
416 | * End: | |
417 | */ |