]>
Commit | Line | Data |
---|---|---|
0f22aab8 LY |
1 | /** |
2 | * Airgo MIMO wireless driver | |
3 | * | |
4 | * Copyright (c) 2007 Li YanBo <dreamfly281@gmail.com> | |
5 | ||
6 | * Thanks for Jeff Williams <angelbane@gmail.com> do reverse engineer | |
7 | * works and published the SPECS at http://airgo.wdwconsulting.net/mymoin | |
8 | ||
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | */ | |
13 | ||
14 | #include <linux/init.h> | |
15 | #include <linux/etherdevice.h> | |
16 | #include <linux/pci.h> | |
17 | #include <linux/delay.h> | |
18 | ||
19 | #include "agnx.h" | |
20 | #include "debug.h" | |
21 | #include "xmit.h" | |
22 | #include "phy.h" | |
23 | ||
24 | MODULE_AUTHOR("Li YanBo <dreamfly281@gmail.com>"); | |
25 | MODULE_DESCRIPTION("Airgo MIMO PCI wireless driver"); | |
26 | MODULE_LICENSE("GPL"); | |
27 | ||
28 | static struct pci_device_id agnx_pci_id_tbl[] __devinitdata = { | |
29 | { PCI_DEVICE(0x17cb, 0x0001) }, /* Beklin F5d8010, Netgear WGM511 etc */ | |
30 | { PCI_DEVICE(0x17cb, 0x0002) }, /* Netgear Wpnt511 */ | |
31 | { 0 } | |
32 | }; | |
33 | ||
34 | MODULE_DEVICE_TABLE(pci, agnx_pci_id_tbl); | |
35 | ||
36 | ||
37 | static inline void agnx_interrupt_ack(struct agnx_priv *priv, u32 *reason) | |
38 | { | |
39 | void __iomem *ctl = priv->ctl; | |
40 | u32 reg; | |
41 | ||
f58e12e4 | 42 | if (*reason & AGNX_STAT_RX) { |
0f22aab8 LY |
43 | /* Mark complete RX */ |
44 | reg = ioread32(ctl + AGNX_CIR_RXCTL); | |
45 | reg |= 0x4; | |
46 | iowrite32(reg, ctl + AGNX_CIR_RXCTL); | |
47 | /* disable Rx interrupt */ | |
48 | } | |
f58e12e4 | 49 | if (*reason & AGNX_STAT_TX) { |
0f22aab8 LY |
50 | reg = ioread32(ctl + AGNX_CIR_TXDCTL); |
51 | if (reg & 0x4) { | |
52 | iowrite32(reg, ctl + AGNX_CIR_TXDCTL); | |
53 | *reason |= AGNX_STAT_TXD; | |
54 | } | |
f58e12e4 | 55 | reg = ioread32(ctl + AGNX_CIR_TXMCTL); |
0f22aab8 LY |
56 | if (reg & 0x4) { |
57 | iowrite32(reg, ctl + AGNX_CIR_TXMCTL); | |
58 | *reason |= AGNX_STAT_TXM; | |
59 | } | |
60 | } | |
f58e12e4 EA |
61 | /* if (*reason & AGNX_STAT_X) { |
62 | reg = ioread32(ctl + AGNX_INT_STAT); | |
63 | iowrite32(reg, ctl + AGNX_INT_STAT); | |
64 | /* FIXME reinit interrupt mask *\/ | |
65 | reg = 0xc390bf9 & ~IRQ_TX_BEACON; | |
66 | reg &= ~IRQ_TX_DISABLE; | |
67 | iowrite32(reg, ctl + AGNX_INT_MASK); | |
68 | iowrite32(0x800, ctl + AGNX_CIR_BLKCTL); | |
69 | } */ | |
0f22aab8 LY |
70 | } /* agnx_interrupt_ack */ |
71 | ||
72 | static irqreturn_t agnx_interrupt_handler(int irq, void *dev_id) | |
73 | { | |
74 | struct ieee80211_hw *dev = dev_id; | |
75 | struct agnx_priv *priv = dev->priv; | |
76 | void __iomem *ctl = priv->ctl; | |
77 | irqreturn_t ret = IRQ_NONE; | |
78 | u32 irq_reason; | |
79 | ||
80 | spin_lock(&priv->lock); | |
81 | ||
f58e12e4 | 82 | /* printk(KERN_ERR PFX "Get a interrupt %s\n", __func__); */ |
0f22aab8 LY |
83 | |
84 | if (priv->init_status != AGNX_START) | |
85 | goto out; | |
86 | ||
87 | /* FiXME Here has no lock, Is this will lead to race? */ | |
88 | irq_reason = ioread32(ctl + AGNX_CIR_BLKCTL); | |
89 | if (!(irq_reason & 0x7)) | |
90 | goto out; | |
91 | ||
92 | ret = IRQ_HANDLED; | |
93 | priv->irq_status = ioread32(ctl + AGNX_INT_STAT); | |
94 | ||
f58e12e4 | 95 | /* printk(PFX "Interrupt reason is 0x%x\n", irq_reason); */ |
0f22aab8 LY |
96 | /* Make sure the txm and txd flags don't conflict with other unknown |
97 | interrupt flag, maybe is not necessary */ | |
98 | irq_reason &= 0xF; | |
99 | ||
100 | disable_rx_interrupt(priv); | |
101 | /* TODO Make sure the card finished initialized */ | |
102 | agnx_interrupt_ack(priv, &irq_reason); | |
103 | ||
f58e12e4 | 104 | if (irq_reason & AGNX_STAT_RX) |
0f22aab8 | 105 | handle_rx_irq(priv); |
f58e12e4 | 106 | if (irq_reason & AGNX_STAT_TXD) |
0f22aab8 | 107 | handle_txd_irq(priv); |
f58e12e4 | 108 | if (irq_reason & AGNX_STAT_TXM) |
0f22aab8 | 109 | handle_txm_irq(priv); |
f58e12e4 | 110 | if (irq_reason & AGNX_STAT_X) |
0f22aab8 LY |
111 | handle_other_irq(priv); |
112 | ||
113 | enable_rx_interrupt(priv); | |
114 | out: | |
115 | spin_unlock(&priv->lock); | |
116 | return ret; | |
117 | } /* agnx_interrupt_handler */ | |
118 | ||
119 | ||
120 | /* FIXME */ | |
121 | static int agnx_tx(struct ieee80211_hw *dev, struct sk_buff *skb) | |
122 | { | |
123 | AGNX_TRACE; | |
124 | return _agnx_tx(dev->priv, skb); | |
125 | } /* agnx_tx */ | |
126 | ||
127 | ||
128 | static int agnx_get_mac_address(struct agnx_priv *priv) | |
129 | { | |
130 | void __iomem *ctl = priv->ctl; | |
131 | u32 reg; | |
132 | AGNX_TRACE; | |
133 | ||
134 | /* Attention! directly read the MAC or other date from EEPROM will | |
135 | lead to cardbus(WGM511) lock up when write to PM PLL register */ | |
136 | reg = agnx_read32(ctl, 0x3544); | |
137 | udelay(40); | |
138 | reg = agnx_read32(ctl, 0x354c); | |
139 | udelay(50); | |
140 | /* Get the mac address */ | |
141 | reg = agnx_read32(ctl, 0x3544); | |
142 | udelay(40); | |
143 | ||
144 | /* HACK */ | |
145 | reg = cpu_to_le32(reg); | |
146 | priv->mac_addr[0] = ((u8 *)®)[2]; | |
147 | priv->mac_addr[1] = ((u8 *)®)[3]; | |
148 | reg = agnx_read32(ctl, 0x3548); | |
149 | udelay(50); | |
150 | *((u32 *)(priv->mac_addr + 2)) = cpu_to_le32(reg); | |
151 | ||
152 | if (!is_valid_ether_addr(priv->mac_addr)) { | |
153 | DECLARE_MAC_BUF(mbuf); | |
154 | printk(KERN_WARNING PFX "read mac %s\n", print_mac(mbuf, priv->mac_addr)); | |
155 | printk(KERN_WARNING PFX "Invalid hwaddr! Using random hwaddr\n"); | |
156 | random_ether_addr(priv->mac_addr); | |
157 | } | |
158 | ||
159 | return 0; | |
160 | } /* agnx_get_mac_address */ | |
161 | ||
162 | static int agnx_alloc_rings(struct agnx_priv *priv) | |
163 | { | |
164 | unsigned int len; | |
165 | AGNX_TRACE; | |
166 | ||
167 | /* Allocate RX/TXM/TXD rings info */ | |
168 | priv->rx.size = AGNX_RX_RING_SIZE; | |
169 | priv->txm.size = AGNX_TXM_RING_SIZE; | |
170 | priv->txd.size = AGNX_TXD_RING_SIZE; | |
171 | ||
172 | len = priv->rx.size + priv->txm.size + priv->txd.size; | |
173 | ||
f58e12e4 | 174 | /* priv->rx.info = kzalloc(sizeof(struct agnx_info) * len, GFP_KERNEL); */ |
0f22aab8 LY |
175 | priv->rx.info = kzalloc(sizeof(struct agnx_info) * len, GFP_ATOMIC); |
176 | if (!priv->rx.info) | |
177 | return -ENOMEM; | |
178 | priv->txm.info = priv->rx.info + priv->rx.size; | |
179 | priv->txd.info = priv->txm.info + priv->txm.size; | |
180 | ||
181 | /* Allocate RX/TXM/TXD descriptors */ | |
182 | priv->rx.desc = pci_alloc_consistent(priv->pdev, sizeof(struct agnx_desc) * len, | |
183 | &priv->rx.dma); | |
184 | if (!priv->rx.desc) { | |
185 | kfree(priv->rx.info); | |
186 | return -ENOMEM; | |
187 | } | |
188 | ||
189 | priv->txm.desc = priv->rx.desc + priv->rx.size; | |
190 | priv->txm.dma = priv->rx.dma + sizeof(struct agnx_desc) * priv->rx.size; | |
191 | priv->txd.desc = priv->txm.desc + priv->txm.size; | |
192 | priv->txd.dma = priv->txm.dma + sizeof(struct agnx_desc) * priv->txm.size; | |
193 | ||
194 | return 0; | |
195 | } /* agnx_alloc_rings */ | |
196 | ||
197 | static void rings_free(struct agnx_priv *priv) | |
198 | { | |
199 | unsigned int len = priv->rx.size + priv->txm.size + priv->txd.size; | |
200 | unsigned long flags; | |
201 | AGNX_TRACE; | |
202 | ||
203 | spin_lock_irqsave(&priv->lock, flags); | |
204 | kfree(priv->rx.info); | |
205 | pci_free_consistent(priv->pdev, sizeof(struct agnx_desc) * len, | |
206 | priv->rx.desc, priv->rx.dma); | |
207 | spin_unlock_irqrestore(&priv->lock, flags); | |
208 | } | |
209 | ||
e543c241 | 210 | #if 0 |
0f22aab8 LY |
211 | static void agnx_periodic_work_handler(struct work_struct *work) |
212 | { | |
f58e12e4 EA |
213 | struct agnx_priv *priv = container_of(work, struct agnx_priv, periodic_work.work); |
214 | /* unsigned long flags; */ | |
0f22aab8 LY |
215 | unsigned long delay; |
216 | ||
217 | /* fixme: using mutex?? */ | |
f58e12e4 | 218 | /* spin_lock_irqsave(&priv->lock, flags); */ |
0f22aab8 LY |
219 | |
220 | /* TODO Recalibrate*/ | |
f58e12e4 EA |
221 | /* calibrate_oscillator(priv); */ |
222 | /* antenna_calibrate(priv); */ | |
223 | /* agnx_send_packet(priv, 997); / | |
0f22aab8 LY |
224 | /* FIXME */ |
225 | /* if (debug == 3) */ | |
226 | /* delay = msecs_to_jiffies(AGNX_PERIODIC_DELAY); */ | |
227 | /* else */ | |
228 | delay = msecs_to_jiffies(AGNX_PERIODIC_DELAY); | |
f58e12e4 | 229 | /* delay = round_jiffies(HZ * 15); */ |
0f22aab8 LY |
230 | |
231 | queue_delayed_work(priv->hw->workqueue, &priv->periodic_work, delay); | |
232 | ||
f58e12e4 | 233 | /* spin_unlock_irqrestore(&priv->lock, flags); */ |
0f22aab8 | 234 | } |
e543c241 | 235 | #endif |
0f22aab8 LY |
236 | |
237 | static int agnx_start(struct ieee80211_hw *dev) | |
238 | { | |
239 | struct agnx_priv *priv = dev->priv; | |
e543c241 | 240 | /* unsigned long delay; */ |
0f22aab8 LY |
241 | int err = 0; |
242 | AGNX_TRACE; | |
243 | ||
244 | err = agnx_alloc_rings(priv); | |
245 | if (err) { | |
246 | printk(KERN_ERR PFX "Can't alloc RX/TXM/TXD rings\n"); | |
247 | goto out; | |
248 | } | |
249 | err = request_irq(priv->pdev->irq, &agnx_interrupt_handler, | |
250 | IRQF_SHARED, "agnx_pci", dev); | |
251 | if (err) { | |
252 | printk(KERN_ERR PFX "Failed to register IRQ handler\n"); | |
253 | rings_free(priv); | |
254 | goto out; | |
255 | } | |
256 | ||
f58e12e4 | 257 | /* mdelay(500); */ |
0f22aab8 LY |
258 | |
259 | might_sleep(); | |
260 | agnx_hw_init(priv); | |
261 | ||
f58e12e4 | 262 | /* mdelay(500); */ |
0f22aab8 LY |
263 | might_sleep(); |
264 | ||
265 | priv->init_status = AGNX_START; | |
266 | /* INIT_DELAYED_WORK(&priv->periodic_work, agnx_periodic_work_handler); */ | |
267 | /* delay = msecs_to_jiffies(AGNX_PERIODIC_DELAY); */ | |
268 | /* queue_delayed_work(priv->hw->workqueue, &priv->periodic_work, delay); */ | |
269 | out: | |
270 | return err; | |
271 | } /* agnx_start */ | |
272 | ||
273 | static void agnx_stop(struct ieee80211_hw *dev) | |
274 | { | |
275 | struct agnx_priv *priv = dev->priv; | |
276 | AGNX_TRACE; | |
277 | ||
278 | priv->init_status = AGNX_STOP; | |
279 | /* make sure hardware will not generate irq */ | |
280 | agnx_hw_reset(priv); | |
281 | free_irq(priv->pdev->irq, dev); | |
f58e12e4 EA |
282 | flush_workqueue(priv->hw->workqueue); |
283 | /* cancel_delayed_work_sync(&priv->periodic_work); */ | |
0f22aab8 LY |
284 | unfill_rings(priv); |
285 | rings_free(priv); | |
286 | } | |
287 | ||
262d3870 | 288 | static int agnx_config(struct ieee80211_hw *dev, u32 changed) |
0f22aab8 LY |
289 | { |
290 | struct agnx_priv *priv = dev->priv; | |
262d3870 | 291 | struct ieee80211_conf *conf = &dev->conf; |
0f22aab8 LY |
292 | int channel = ieee80211_frequency_to_channel(conf->channel->center_freq); |
293 | AGNX_TRACE; | |
294 | ||
295 | spin_lock(&priv->lock); | |
296 | /* FIXME need priv lock? */ | |
297 | if (channel != priv->channel) { | |
298 | priv->channel = channel; | |
299 | agnx_set_channel(priv, priv->channel); | |
300 | } | |
301 | ||
302 | spin_unlock(&priv->lock); | |
303 | return 0; | |
304 | } | |
305 | ||
306 | static int agnx_config_interface(struct ieee80211_hw *dev, | |
307 | struct ieee80211_vif *vif, | |
308 | struct ieee80211_if_conf *conf) | |
309 | { | |
310 | struct agnx_priv *priv = dev->priv; | |
311 | void __iomem *ctl = priv->ctl; | |
312 | AGNX_TRACE; | |
313 | ||
314 | spin_lock(&priv->lock); | |
315 | ||
316 | if (memcmp(conf->bssid, priv->bssid, ETH_ALEN)) { | |
0f22aab8 LY |
317 | agnx_set_bssid(priv, conf->bssid); |
318 | memcpy(priv->bssid, conf->bssid, ETH_ALEN); | |
319 | hash_write(priv, conf->bssid, BSSID_STAID); | |
320 | sta_init(priv, BSSID_STAID); | |
321 | /* FIXME needed? */ | |
322 | sta_power_init(priv, BSSID_STAID); | |
323 | agnx_write32(ctl, AGNX_BM_MTSM, 0xff & ~0x1); | |
324 | } | |
0f22aab8 LY |
325 | spin_unlock(&priv->lock); |
326 | return 0; | |
327 | } /* agnx_config_interface */ | |
328 | ||
329 | ||
330 | static void agnx_configure_filter(struct ieee80211_hw *dev, | |
331 | unsigned int changed_flags, | |
332 | unsigned int *total_flags, | |
333 | int mc_count, struct dev_mc_list *mclist) | |
334 | { | |
335 | unsigned int new_flags = 0; | |
336 | ||
337 | *total_flags = new_flags; | |
338 | /* TODO */ | |
339 | } | |
340 | ||
341 | static int agnx_add_interface(struct ieee80211_hw *dev, | |
342 | struct ieee80211_if_init_conf *conf) | |
343 | { | |
344 | struct agnx_priv *priv = dev->priv; | |
345 | AGNX_TRACE; | |
346 | ||
347 | spin_lock(&priv->lock); | |
348 | /* FIXME */ | |
349 | if (priv->mode != NL80211_IFTYPE_MONITOR) | |
350 | return -EOPNOTSUPP; | |
351 | ||
352 | switch (conf->type) { | |
353 | case NL80211_IFTYPE_STATION: | |
354 | priv->mode = conf->type; | |
355 | break; | |
356 | default: | |
357 | return -EOPNOTSUPP; | |
358 | } | |
359 | ||
360 | spin_unlock(&priv->lock); | |
361 | ||
362 | return 0; | |
363 | } | |
364 | ||
365 | static void agnx_remove_interface(struct ieee80211_hw *dev, | |
366 | struct ieee80211_if_init_conf *conf) | |
367 | { | |
368 | struct agnx_priv *priv = dev->priv; | |
369 | AGNX_TRACE; | |
370 | ||
371 | /* TODO */ | |
372 | priv->mode = NL80211_IFTYPE_MONITOR; | |
373 | } | |
374 | ||
375 | static int agnx_get_stats(struct ieee80211_hw *dev, | |
376 | struct ieee80211_low_level_stats *stats) | |
377 | { | |
378 | struct agnx_priv *priv = dev->priv; | |
379 | AGNX_TRACE; | |
380 | spin_lock(&priv->lock); | |
381 | /* TODO !! */ | |
382 | memcpy(stats, &priv->stats, sizeof(*stats)); | |
383 | spin_unlock(&priv->lock); | |
384 | ||
385 | return 0; | |
386 | } | |
387 | ||
388 | static u64 agnx_get_tsft(struct ieee80211_hw *dev) | |
389 | { | |
390 | void __iomem *ctl = ((struct agnx_priv *)dev->priv)->ctl; | |
391 | u32 tsftl; | |
392 | u64 tsft; | |
393 | AGNX_TRACE; | |
394 | ||
395 | /* FIXME */ | |
396 | tsftl = ioread32(ctl + AGNX_TXM_TIMESTAMPLO); | |
397 | tsft = ioread32(ctl + AGNX_TXM_TIMESTAMPHI); | |
398 | tsft <<= 32; | |
399 | tsft |= tsftl; | |
400 | ||
401 | return tsft; | |
402 | } | |
403 | ||
404 | static int agnx_get_tx_stats(struct ieee80211_hw *dev, | |
405 | struct ieee80211_tx_queue_stats *stats) | |
406 | { | |
407 | struct agnx_priv *priv = dev->priv; | |
408 | AGNX_TRACE; | |
409 | ||
410 | /* FIXME now we just using txd queue, but should using txm queue too */ | |
411 | stats[0].len = (priv->txd.idx - priv->txd.idx_sent) / 2; | |
412 | stats[0].limit = priv->txd.size - 2; | |
413 | stats[0].count = priv->txd.idx / 2; | |
414 | ||
415 | return 0; | |
416 | } | |
417 | ||
418 | static struct ieee80211_ops agnx_ops = { | |
419 | .tx = agnx_tx, | |
420 | .start = agnx_start, | |
421 | .stop = agnx_stop, | |
422 | .add_interface = agnx_add_interface, | |
423 | .remove_interface = agnx_remove_interface, | |
424 | .config = agnx_config, | |
425 | .config_interface = agnx_config_interface, | |
f58e12e4 | 426 | .configure_filter = agnx_configure_filter, |
0f22aab8 LY |
427 | .get_stats = agnx_get_stats, |
428 | .get_tx_stats = agnx_get_tx_stats, | |
429 | .get_tsf = agnx_get_tsft | |
430 | }; | |
431 | ||
432 | static void __devexit agnx_pci_remove(struct pci_dev *pdev) | |
433 | { | |
434 | struct ieee80211_hw *dev = pci_get_drvdata(pdev); | |
6bf67672 | 435 | struct agnx_priv *priv; |
0f22aab8 LY |
436 | AGNX_TRACE; |
437 | ||
438 | if (!dev) | |
439 | return; | |
6bf67672 | 440 | priv = dev->priv; |
0f22aab8 LY |
441 | ieee80211_unregister_hw(dev); |
442 | pci_iounmap(pdev, priv->ctl); | |
443 | pci_iounmap(pdev, priv->data); | |
444 | pci_release_regions(pdev); | |
445 | pci_disable_device(pdev); | |
446 | ||
447 | ieee80211_free_hw(dev); | |
448 | } | |
449 | ||
450 | static int __devinit agnx_pci_probe(struct pci_dev *pdev, | |
451 | const struct pci_device_id *id) | |
452 | { | |
453 | struct ieee80211_hw *dev; | |
454 | struct agnx_priv *priv; | |
455 | u32 mem_addr0, mem_len0; | |
456 | u32 mem_addr1, mem_len1; | |
457 | int err; | |
458 | DECLARE_MAC_BUF(mac); | |
459 | ||
460 | err = pci_enable_device(pdev); | |
461 | if (err) { | |
462 | printk(KERN_ERR PFX "Can't enable new PCI device\n"); | |
463 | return err; | |
464 | } | |
465 | ||
466 | /* get pci resource */ | |
467 | mem_addr0 = pci_resource_start(pdev, 0); | |
468 | mem_len0 = pci_resource_len(pdev, 0); | |
469 | mem_addr1 = pci_resource_start(pdev, 1); | |
470 | mem_len1 = pci_resource_len(pdev, 1); | |
471 | printk(KERN_DEBUG PFX "Memaddr0 is %x, length is %x\n", mem_addr0, mem_len0); | |
472 | printk(KERN_DEBUG PFX "Memaddr1 is %x, length is %x\n", mem_addr1, mem_len1); | |
473 | ||
474 | err = pci_request_regions(pdev, "agnx-pci"); | |
475 | if (err) { | |
476 | printk(KERN_ERR PFX "Can't obtain PCI resource\n"); | |
477 | return err; | |
478 | } | |
479 | ||
284901a9 YH |
480 | if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) || |
481 | pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32))) { | |
0f22aab8 LY |
482 | printk(KERN_ERR PFX "No suitable DMA available\n"); |
483 | goto err_free_reg; | |
484 | } | |
485 | ||
486 | pci_set_master(pdev); | |
487 | printk(KERN_DEBUG PFX "pdev->irq is %d\n", pdev->irq); | |
488 | ||
489 | dev = ieee80211_alloc_hw(sizeof(*priv), &agnx_ops); | |
490 | if (!dev) { | |
491 | printk(KERN_ERR PFX "ieee80211 alloc failed\n"); | |
492 | err = -ENOMEM; | |
493 | goto err_free_reg; | |
494 | } | |
495 | /* init priv */ | |
496 | priv = dev->priv; | |
497 | memset(priv, 0, sizeof(*priv)); | |
498 | priv->mode = NL80211_IFTYPE_MONITOR; | |
499 | priv->pdev = pdev; | |
500 | priv->hw = dev; | |
501 | spin_lock_init(&priv->lock); | |
502 | priv->init_status = AGNX_UNINIT; | |
503 | ||
504 | /* Map mem #1 and #2 */ | |
505 | priv->ctl = pci_iomap(pdev, 0, mem_len0); | |
f58e12e4 | 506 | /* printk(KERN_DEBUG PFX"MEM1 mapped address is 0x%p\n", priv->ctl); */ |
0f22aab8 LY |
507 | if (!priv->ctl) { |
508 | printk(KERN_ERR PFX "Can't map device memory\n"); | |
509 | goto err_free_dev; | |
510 | } | |
511 | priv->data = pci_iomap(pdev, 1, mem_len1); | |
512 | printk(KERN_DEBUG PFX "MEM2 mapped address is 0x%p\n", priv->data); | |
513 | if (!priv->data) { | |
514 | printk(KERN_ERR PFX "Can't map device memory\n"); | |
515 | goto err_iounmap2; | |
516 | } | |
517 | ||
518 | pci_read_config_byte(pdev, PCI_REVISION_ID, &priv->revid); | |
519 | ||
520 | priv->band.channels = (struct ieee80211_channel *)agnx_channels; | |
521 | priv->band.n_channels = ARRAY_SIZE(agnx_channels); | |
522 | priv->band.bitrates = (struct ieee80211_rate *)agnx_rates_80211g; | |
523 | priv->band.n_bitrates = ARRAY_SIZE(agnx_rates_80211g); | |
524 | ||
525 | /* Init ieee802.11 dev */ | |
526 | SET_IEEE80211_DEV(dev, &pdev->dev); | |
527 | pci_set_drvdata(pdev, dev); | |
528 | dev->extra_tx_headroom = sizeof(struct agnx_hdr); | |
529 | ||
530 | /* FIXME It only include FCS in promious mode but not manage mode */ | |
531 | /* dev->flags = IEEE80211_HW_RX_INCLUDES_FCS; */ | |
532 | dev->channel_change_time = 5000; | |
533 | dev->max_signal = 100; | |
534 | /* FIXME */ | |
535 | dev->queues = 1; | |
536 | ||
537 | agnx_get_mac_address(priv); | |
538 | ||
539 | SET_IEEE80211_PERM_ADDR(dev, priv->mac_addr); | |
540 | ||
541 | /* /\* FIXME *\/ */ | |
542 | /* for (i = 1; i < NUM_DRIVE_MODES; i++) { */ | |
543 | /* err = ieee80211_register_hwmode(dev, &priv->modes[i]); */ | |
544 | /* if (err) { */ | |
545 | /* printk(KERN_ERR PFX "Can't register hwmode\n"); */ | |
546 | /* goto err_iounmap; */ | |
547 | /* } */ | |
548 | /* } */ | |
549 | ||
550 | priv->channel = 1; | |
551 | dev->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; | |
552 | ||
553 | err = ieee80211_register_hw(dev); | |
554 | if (err) { | |
555 | printk(KERN_ERR PFX "Can't register hardware\n"); | |
556 | goto err_iounmap; | |
557 | } | |
558 | ||
559 | agnx_hw_reset(priv); | |
560 | ||
561 | ||
562 | printk(PFX "%s: hwaddr %s, Rev 0x%02x\n", wiphy_name(dev->wiphy), | |
563 | print_mac(mac, dev->wiphy->perm_addr), priv->revid); | |
564 | return 0; | |
565 | ||
566 | err_iounmap: | |
567 | pci_iounmap(pdev, priv->data); | |
568 | ||
569 | err_iounmap2: | |
570 | pci_iounmap(pdev, priv->ctl); | |
571 | ||
572 | err_free_dev: | |
573 | pci_set_drvdata(pdev, NULL); | |
574 | ieee80211_free_hw(dev); | |
575 | ||
576 | err_free_reg: | |
577 | pci_release_regions(pdev); | |
578 | ||
579 | pci_disable_device(pdev); | |
580 | return err; | |
581 | } /* agnx_pci_probe*/ | |
582 | ||
583 | #ifdef CONFIG_PM | |
584 | ||
585 | static int agnx_pci_suspend(struct pci_dev *pdev, pm_message_t state) | |
586 | { | |
587 | struct ieee80211_hw *dev = pci_get_drvdata(pdev); | |
588 | AGNX_TRACE; | |
589 | ||
590 | ieee80211_stop_queues(dev); | |
591 | agnx_stop(dev); | |
592 | ||
593 | pci_save_state(pdev); | |
594 | pci_set_power_state(pdev, pci_choose_state(pdev, state)); | |
595 | return 0; | |
596 | } | |
597 | ||
598 | static int agnx_pci_resume(struct pci_dev *pdev) | |
599 | { | |
600 | struct ieee80211_hw *dev = pci_get_drvdata(pdev); | |
601 | AGNX_TRACE; | |
602 | ||
603 | pci_set_power_state(pdev, PCI_D0); | |
604 | pci_restore_state(pdev); | |
605 | ||
606 | agnx_start(dev); | |
607 | ieee80211_wake_queues(dev); | |
608 | ||
609 | return 0; | |
610 | } | |
611 | ||
612 | #else | |
613 | ||
614 | #define agnx_pci_suspend NULL | |
615 | #define agnx_pci_resume NULL | |
616 | ||
617 | #endif /* CONFIG_PM */ | |
618 | ||
619 | ||
620 | static struct pci_driver agnx_pci_driver = { | |
621 | .name = "agnx-pci", | |
622 | .id_table = agnx_pci_id_tbl, | |
623 | .probe = agnx_pci_probe, | |
624 | .remove = __devexit_p(agnx_pci_remove), | |
625 | .suspend = agnx_pci_suspend, | |
626 | .resume = agnx_pci_resume, | |
627 | }; | |
628 | ||
629 | static int __init agnx_pci_init(void) | |
630 | { | |
631 | AGNX_TRACE; | |
632 | return pci_register_driver(&agnx_pci_driver); | |
633 | } | |
634 | ||
635 | static void __exit agnx_pci_exit(void) | |
636 | { | |
637 | AGNX_TRACE; | |
638 | pci_unregister_driver(&agnx_pci_driver); | |
639 | } | |
640 | ||
641 | ||
642 | module_init(agnx_pci_init); | |
643 | module_exit(agnx_pci_exit); |