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> | |
1da177e4 LT |
96 | #include <linux/module.h> |
97 | #include <linux/kernel.h> | |
98 | #include <linux/init.h> | |
ef846bf0 | 99 | #include <linux/delay.h> |
1da177e4 | 100 | #include <linux/pci.h> |
1da177e4 | 101 | |
1da177e4 LT |
102 | #include "orinoco.h" |
103 | ||
104 | /* All the magic there is from wlan-ng */ | |
105 | /* Magic offset of the reset register of the PCI card */ | |
106 | #define HERMES_PCI_COR (0x26) | |
107 | /* Magic bitmask to reset the card */ | |
108 | #define HERMES_PCI_COR_MASK (0x0080) | |
109 | /* Magic timeouts for doing the reset. | |
110 | * Those times are straight from wlan-ng, and it is claimed that they | |
111 | * are necessary. Alan will kill me. Take your time and grab a coffee. */ | |
112 | #define HERMES_PCI_COR_ONT (250) /* ms */ | |
113 | #define HERMES_PCI_COR_OFFT (500) /* ms */ | |
114 | #define HERMES_PCI_COR_BUSYT (500) /* ms */ | |
115 | ||
116 | /* Orinoco PCI specific data */ | |
117 | struct orinoco_pci_card { | |
118 | void __iomem *pci_ioaddr; | |
119 | }; | |
120 | ||
121 | /* | |
122 | * Do a soft reset of the PCI card using the Configuration Option Register | |
123 | * We need this to get going... | |
124 | * This is the part of the code that is strongly inspired from wlan-ng | |
125 | * | |
126 | * Note : This code is done with irq enabled. This mean that many | |
127 | * interrupts will occur while we are there. This is why we use the | |
128 | * jiffies to regulate time instead of a straight mdelay(). Usually we | |
129 | * need only around 245 iteration of the loop to do 250 ms delay. | |
130 | * | |
131 | * Note bis : Don't try to access HERMES_CMD during the reset phase. | |
132 | * It just won't work ! | |
133 | */ | |
134 | static int | |
135 | orinoco_pci_cor_reset(struct orinoco_private *priv) | |
136 | { | |
137 | hermes_t *hw = &priv->hw; | |
138 | unsigned long timeout; | |
139 | u16 reg; | |
140 | ||
141 | /* Assert the reset until the card notice */ | |
142 | hermes_write_regn(hw, PCI_COR, HERMES_PCI_COR_MASK); | |
143 | mdelay(HERMES_PCI_COR_ONT); | |
144 | ||
145 | /* Give time for the card to recover from this hard effort */ | |
146 | hermes_write_regn(hw, PCI_COR, 0x0000); | |
147 | mdelay(HERMES_PCI_COR_OFFT); | |
148 | ||
149 | /* The card is ready when it's no longer busy */ | |
150 | timeout = jiffies + (HERMES_PCI_COR_BUSYT * HZ / 1000); | |
151 | reg = hermes_read_regn(hw, CMD); | |
152 | while (time_before(jiffies, timeout) && (reg & HERMES_CMD_BUSY)) { | |
153 | mdelay(1); | |
154 | reg = hermes_read_regn(hw, CMD); | |
155 | } | |
156 | ||
157 | /* Still busy? */ | |
158 | if (reg & HERMES_CMD_BUSY) { | |
159 | printk(KERN_ERR PFX "Busy timeout\n"); | |
160 | return -ETIMEDOUT; | |
161 | } | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
166 | /* | |
167 | * Initialise a card. Mostly similar to PLX code. | |
168 | */ | |
169 | static int orinoco_pci_init_one(struct pci_dev *pdev, | |
170 | const struct pci_device_id *ent) | |
171 | { | |
172 | int err = 0; | |
173 | unsigned long pci_iorange; | |
174 | u16 __iomem *pci_ioaddr = NULL; | |
175 | unsigned long pci_iolen; | |
176 | struct orinoco_private *priv = NULL; | |
177 | struct orinoco_pci_card *card; | |
178 | struct net_device *dev = NULL; | |
179 | ||
180 | err = pci_enable_device(pdev); | |
181 | if (err) { | |
182 | printk(KERN_ERR PFX "Cannot enable PCI device\n"); | |
183 | return err; | |
184 | } | |
185 | ||
186 | err = pci_request_regions(pdev, DRIVER_NAME); | |
187 | if (err != 0) { | |
188 | printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); | |
189 | goto fail_resources; | |
190 | } | |
191 | ||
192 | /* Resource 0 is mapped to the hermes registers */ | |
193 | pci_iorange = pci_resource_start(pdev, 0); | |
194 | pci_iolen = pci_resource_len(pdev, 0); | |
195 | pci_ioaddr = ioremap(pci_iorange, pci_iolen); | |
196 | if (!pci_iorange) { | |
197 | printk(KERN_ERR PFX "Cannot remap hardware registers\n"); | |
198 | goto fail_map; | |
199 | } | |
200 | ||
201 | /* Allocate network device */ | |
202 | dev = alloc_orinocodev(sizeof(*card), orinoco_pci_cor_reset); | |
203 | if (! dev) { | |
204 | err = -ENOMEM; | |
205 | goto fail_alloc; | |
206 | } | |
207 | ||
208 | priv = netdev_priv(dev); | |
209 | card = priv->card; | |
210 | card->pci_ioaddr = pci_ioaddr; | |
211 | dev->mem_start = pci_iorange; | |
212 | dev->mem_end = pci_iorange + pci_iolen - 1; | |
213 | SET_MODULE_OWNER(dev); | |
214 | SET_NETDEV_DEV(dev, &pdev->dev); | |
215 | ||
216 | hermes_struct_init(&priv->hw, pci_ioaddr, HERMES_32BIT_REGSPACING); | |
217 | ||
218 | printk(KERN_DEBUG PFX "Detected device %s, mem:0x%lx-0x%lx, irq %d\n", | |
219 | pci_name(pdev), dev->mem_start, dev->mem_end, pdev->irq); | |
220 | ||
221 | err = request_irq(pdev->irq, orinoco_interrupt, SA_SHIRQ, | |
222 | dev->name, dev); | |
223 | if (err) { | |
224 | printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); | |
225 | err = -EBUSY; | |
226 | goto fail_irq; | |
227 | } | |
228 | dev->irq = pdev->irq; | |
229 | ||
230 | /* Perform a COR reset to start the card */ | |
231 | err = orinoco_pci_cor_reset(priv); | |
232 | if (err) { | |
233 | printk(KERN_ERR PFX "Initial reset failed\n"); | |
234 | goto fail; | |
235 | } | |
236 | ||
237 | err = register_netdev(dev); | |
238 | if (err) { | |
239 | printk(KERN_ERR PFX "Failed to register net device\n"); | |
240 | goto fail; | |
241 | } | |
242 | ||
243 | pci_set_drvdata(pdev, dev); | |
244 | ||
245 | return 0; | |
246 | ||
247 | fail: | |
248 | free_irq(pdev->irq, dev); | |
249 | ||
250 | fail_irq: | |
251 | pci_set_drvdata(pdev, NULL); | |
252 | free_orinocodev(dev); | |
253 | ||
254 | fail_alloc: | |
255 | iounmap(pci_ioaddr); | |
256 | ||
257 | fail_map: | |
258 | pci_release_regions(pdev); | |
259 | ||
260 | fail_resources: | |
261 | pci_disable_device(pdev); | |
262 | ||
263 | return err; | |
264 | } | |
265 | ||
266 | static void __devexit orinoco_pci_remove_one(struct pci_dev *pdev) | |
267 | { | |
268 | struct net_device *dev = pci_get_drvdata(pdev); | |
269 | struct orinoco_private *priv = netdev_priv(dev); | |
270 | struct orinoco_pci_card *card = priv->card; | |
271 | ||
272 | unregister_netdev(dev); | |
273 | free_irq(dev->irq, dev); | |
274 | pci_set_drvdata(pdev, NULL); | |
275 | free_orinocodev(dev); | |
276 | iounmap(card->pci_ioaddr); | |
277 | pci_release_regions(pdev); | |
278 | pci_disable_device(pdev); | |
279 | } | |
280 | ||
05adc3b7 | 281 | static int orinoco_pci_suspend(struct pci_dev *pdev, pm_message_t state) |
1da177e4 LT |
282 | { |
283 | struct net_device *dev = pci_get_drvdata(pdev); | |
284 | struct orinoco_private *priv = netdev_priv(dev); | |
285 | unsigned long flags; | |
286 | int err; | |
287 | ||
1da177e4 LT |
288 | |
289 | err = orinoco_lock(priv, &flags); | |
290 | if (err) { | |
291 | printk(KERN_ERR "%s: hw_unavailable on orinoco_pci_suspend\n", | |
292 | dev->name); | |
293 | return err; | |
294 | } | |
295 | ||
296 | err = __orinoco_down(dev); | |
297 | if (err) | |
298 | printk(KERN_WARNING "%s: orinoco_pci_suspend(): Error %d downing interface\n", | |
299 | dev->name, err); | |
300 | ||
301 | netif_device_detach(dev); | |
302 | ||
303 | priv->hw_unavailable++; | |
304 | ||
305 | orinoco_unlock(priv, &flags); | |
306 | ||
307 | pci_save_state(pdev); | |
05adc3b7 | 308 | pci_set_power_state(pdev, PCI_D3hot); |
1da177e4 LT |
309 | |
310 | return 0; | |
311 | } | |
312 | ||
313 | static int orinoco_pci_resume(struct pci_dev *pdev) | |
314 | { | |
315 | struct net_device *dev = pci_get_drvdata(pdev); | |
316 | struct orinoco_private *priv = netdev_priv(dev); | |
317 | unsigned long flags; | |
318 | int err; | |
319 | ||
320 | printk(KERN_DEBUG "%s: Orinoco-PCI waking up\n", dev->name); | |
321 | ||
322 | pci_set_power_state(pdev, 0); | |
323 | pci_restore_state(pdev); | |
324 | ||
325 | err = orinoco_reinit_firmware(dev); | |
326 | if (err) { | |
327 | printk(KERN_ERR "%s: Error %d re-initializing firmware on orinoco_pci_resume()\n", | |
328 | dev->name, err); | |
329 | return err; | |
330 | } | |
331 | ||
332 | spin_lock_irqsave(&priv->lock, flags); | |
333 | ||
334 | netif_device_attach(dev); | |
335 | ||
336 | priv->hw_unavailable--; | |
337 | ||
338 | if (priv->open && (! priv->hw_unavailable)) { | |
339 | err = __orinoco_up(dev); | |
340 | if (err) | |
341 | printk(KERN_ERR "%s: Error %d restarting card on orinoco_pci_resume()\n", | |
342 | dev->name, err); | |
343 | } | |
344 | ||
345 | spin_unlock_irqrestore(&priv->lock, flags); | |
346 | ||
347 | return 0; | |
348 | } | |
349 | ||
350 | static struct pci_device_id orinoco_pci_pci_id_table[] = { | |
351 | /* Intersil Prism 3 */ | |
352 | {0x1260, 0x3872, PCI_ANY_ID, PCI_ANY_ID,}, | |
353 | /* Intersil Prism 2.5 */ | |
354 | {0x1260, 0x3873, PCI_ANY_ID, PCI_ANY_ID,}, | |
355 | /* Samsung MagicLAN SWL-2210P */ | |
356 | {0x167d, 0xa000, PCI_ANY_ID, PCI_ANY_ID,}, | |
357 | {0,}, | |
358 | }; | |
359 | ||
360 | MODULE_DEVICE_TABLE(pci, orinoco_pci_pci_id_table); | |
361 | ||
362 | static struct pci_driver orinoco_pci_driver = { | |
363 | .name = DRIVER_NAME, | |
364 | .id_table = orinoco_pci_pci_id_table, | |
365 | .probe = orinoco_pci_init_one, | |
366 | .remove = __devexit_p(orinoco_pci_remove_one), | |
367 | .suspend = orinoco_pci_suspend, | |
368 | .resume = orinoco_pci_resume, | |
369 | }; | |
370 | ||
371 | static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION | |
372 | " (Pavel Roskin <proski@gnu.org>," | |
373 | " David Gibson <hermes@gibson.dropbear.id.au> &" | |
374 | " Jean Tourrilhes <jt@hpl.hp.com>)"; | |
375 | MODULE_AUTHOR("Pavel Roskin <proski@gnu.org> & David Gibson <hermes@gibson.dropbear.id.au>"); | |
376 | MODULE_DESCRIPTION("Driver for wireless LAN cards using direct PCI interface"); | |
377 | MODULE_LICENSE("Dual MPL/GPL"); | |
378 | ||
379 | static int __init orinoco_pci_init(void) | |
380 | { | |
381 | printk(KERN_DEBUG "%s\n", version); | |
382 | return pci_module_init(&orinoco_pci_driver); | |
383 | } | |
384 | ||
385 | static void __exit orinoco_pci_exit(void) | |
386 | { | |
387 | pci_unregister_driver(&orinoco_pci_driver); | |
388 | } | |
389 | ||
390 | module_init(orinoco_pci_init); | |
391 | module_exit(orinoco_pci_exit); | |
392 | ||
393 | /* | |
394 | * Local variables: | |
395 | * c-indent-level: 8 | |
396 | * c-basic-offset: 8 | |
397 | * tab-width: 8 | |
398 | * End: | |
399 | */ |