1 /*
2 * Copyright (c) 2013 Eugene Krasnikov <k.eugene.e@gmail.com>
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
11 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
19 #include <linux/module.h>
20 #include <linux/firmware.h>
21 #include <linux/platform_device.h>
22 #include "wcn36xx.h"
23
24 unsigned int wcn36xx_dbg_mask;
25 module_param_named(debug_mask, wcn36xx_dbg_mask, uint, 0644);
26 MODULE_PARM_DESC(debug_mask, "Debugging mask");
27
28 #define CHAN2G(_freq, _idx) { \
29 .band = IEEE80211_BAND_2GHZ, \
30 .center_freq = (_freq), \
31 .hw_value = (_idx), \
32 .max_power = 25, \
33 }
34
35 #define CHAN5G(_freq, _idx) { \
36 .band = IEEE80211_BAND_5GHZ, \
37 .center_freq = (_freq), \
38 .hw_value = (_idx), \
39 .max_power = 25, \
40 }
41
42 /* The wcn firmware expects channel values to matching
43 * their mnemonic values. So use these for .hw_value. */
44 static struct ieee80211_channel wcn_2ghz_channels[] = {
45 CHAN2G(2412, 1), /* Channel 1 */
46 CHAN2G(2417, 2), /* Channel 2 */
47 CHAN2G(2422, 3), /* Channel 3 */
48 CHAN2G(2427, 4), /* Channel 4 */
49 CHAN2G(2432, 5), /* Channel 5 */
50 CHAN2G(2437, 6), /* Channel 6 */
51 CHAN2G(2442, 7), /* Channel 7 */
52 CHAN2G(2447, 8), /* Channel 8 */
53 CHAN2G(2452, 9), /* Channel 9 */
54 CHAN2G(2457, 10), /* Channel 10 */
55 CHAN2G(2462, 11), /* Channel 11 */
56 CHAN2G(2467, 12), /* Channel 12 */
57 CHAN2G(2472, 13), /* Channel 13 */
58 CHAN2G(2484, 14) /* Channel 14 */
59
60 };
61
62 static struct ieee80211_channel wcn_5ghz_channels[] = {
63 CHAN5G(5180, 36),
64 CHAN5G(5200, 40),
65 CHAN5G(5220, 44),
66 CHAN5G(5240, 48),
67 CHAN5G(5260, 52),
68 CHAN5G(5280, 56),
69 CHAN5G(5300, 60),
70 CHAN5G(5320, 64),
71 CHAN5G(5500, 100),
72 CHAN5G(5520, 104),
73 CHAN5G(5540, 108),
74 CHAN5G(5560, 112),
75 CHAN5G(5580, 116),
76 CHAN5G(5600, 120),
77 CHAN5G(5620, 124),
78 CHAN5G(5640, 128),
79 CHAN5G(5660, 132),
80 CHAN5G(5700, 140),
81 CHAN5G(5745, 149),
82 CHAN5G(5765, 153),
83 CHAN5G(5785, 157),
84 CHAN5G(5805, 161),
85 CHAN5G(5825, 165)
86 };
87
88 #define RATE(_bitrate, _hw_rate, _flags) { \
89 .bitrate = (_bitrate), \
90 .flags = (_flags), \
91 .hw_value = (_hw_rate), \
92 .hw_value_short = (_hw_rate) \
93 }
94
95 static struct ieee80211_rate wcn_2ghz_rates[] = {
96 RATE(10, HW_RATE_INDEX_1MBPS, 0),
97 RATE(20, HW_RATE_INDEX_2MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
98 RATE(55, HW_RATE_INDEX_5_5MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
99 RATE(110, HW_RATE_INDEX_11MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
100 RATE(60, HW_RATE_INDEX_6MBPS, 0),
101 RATE(90, HW_RATE_INDEX_9MBPS, 0),
102 RATE(120, HW_RATE_INDEX_12MBPS, 0),
103 RATE(180, HW_RATE_INDEX_18MBPS, 0),
104 RATE(240, HW_RATE_INDEX_24MBPS, 0),
105 RATE(360, HW_RATE_INDEX_36MBPS, 0),
106 RATE(480, HW_RATE_INDEX_48MBPS, 0),
107 RATE(540, HW_RATE_INDEX_54MBPS, 0)
108 };
109
110 static struct ieee80211_rate wcn_5ghz_rates[] = {
111 RATE(60, HW_RATE_INDEX_6MBPS, 0),
112 RATE(90, HW_RATE_INDEX_9MBPS, 0),
113 RATE(120, HW_RATE_INDEX_12MBPS, 0),
114 RATE(180, HW_RATE_INDEX_18MBPS, 0),
115 RATE(240, HW_RATE_INDEX_24MBPS, 0),
116 RATE(360, HW_RATE_INDEX_36MBPS, 0),
117 RATE(480, HW_RATE_INDEX_48MBPS, 0),
118 RATE(540, HW_RATE_INDEX_54MBPS, 0)
119 };
120
121 static struct ieee80211_supported_band wcn_band_2ghz = {
122 .channels = wcn_2ghz_channels,
123 .n_channels = ARRAY_SIZE(wcn_2ghz_channels),
124 .bitrates = wcn_2ghz_rates,
125 .n_bitrates = ARRAY_SIZE(wcn_2ghz_rates),
126 .ht_cap = {
127 .cap = IEEE80211_HT_CAP_GRN_FLD |
128 IEEE80211_HT_CAP_SGI_20 |
129 IEEE80211_HT_CAP_DSSSCCK40 |
130 IEEE80211_HT_CAP_LSIG_TXOP_PROT |
131 IEEE80211_HT_CAP_SGI_40 |
132 IEEE80211_HT_CAP_SUP_WIDTH_20_40,
133 .ht_supported = true,
134 .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
135 .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
136 .mcs = {
137 .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
138 .rx_highest = cpu_to_le16(72),
139 .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
140 }
141 }
142 };
143
144 static struct ieee80211_supported_band wcn_band_5ghz = {
145 .channels = wcn_5ghz_channels,
146 .n_channels = ARRAY_SIZE(wcn_5ghz_channels),
147 .bitrates = wcn_5ghz_rates,
148 .n_bitrates = ARRAY_SIZE(wcn_5ghz_rates),
149 .ht_cap = {
150 .cap = IEEE80211_HT_CAP_GRN_FLD |
151 IEEE80211_HT_CAP_SGI_20 |
152 IEEE80211_HT_CAP_DSSSCCK40 |
153 IEEE80211_HT_CAP_LSIG_TXOP_PROT |
154 IEEE80211_HT_CAP_SGI_40 |
155 IEEE80211_HT_CAP_SUP_WIDTH_20_40,
156 .ht_supported = true,
157 .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
158 .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
159 .mcs = {
160 .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
161 .rx_highest = cpu_to_le16(150),
162 .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
163 }
164 }
165 };
166
167 #ifdef CONFIG_PM
168
169 static const struct wiphy_wowlan_support wowlan_support = {
170 .flags = WIPHY_WOWLAN_ANY
171 };
172
173 #endif
174
get_sta_index(struct ieee80211_vif * vif,struct wcn36xx_sta * sta_priv)175 static inline u8 get_sta_index(struct ieee80211_vif *vif,
176 struct wcn36xx_sta *sta_priv)
177 {
178 return NL80211_IFTYPE_STATION == vif->type ?
179 sta_priv->bss_sta_index :
180 sta_priv->sta_index;
181 }
182
183 static const char * const wcn36xx_caps_names[] = {
184 "MCC", /* 0 */
185 "P2P", /* 1 */
186 "DOT11AC", /* 2 */
187 "SLM_SESSIONIZATION", /* 3 */
188 "DOT11AC_OPMODE", /* 4 */
189 "SAP32STA", /* 5 */
190 "TDLS", /* 6 */
191 "P2P_GO_NOA_DECOUPLE_INIT_SCAN",/* 7 */
192 "WLANACTIVE_OFFLOAD", /* 8 */
193 "BEACON_OFFLOAD", /* 9 */
194 "SCAN_OFFLOAD", /* 10 */
195 "ROAM_OFFLOAD", /* 11 */
196 "BCN_MISS_OFFLOAD", /* 12 */
197 "STA_POWERSAVE", /* 13 */
198 "STA_ADVANCED_PWRSAVE", /* 14 */
199 "AP_UAPSD", /* 15 */
200 "AP_DFS", /* 16 */
201 "BLOCKACK", /* 17 */
202 "PHY_ERR", /* 18 */
203 "BCN_FILTER", /* 19 */
204 "RTT", /* 20 */
205 "RATECTRL", /* 21 */
206 "WOW" /* 22 */
207 };
208
wcn36xx_get_cap_name(enum place_holder_in_cap_bitmap x)209 static const char *wcn36xx_get_cap_name(enum place_holder_in_cap_bitmap x)
210 {
211 if (x >= ARRAY_SIZE(wcn36xx_caps_names))
212 return "UNKNOWN";
213 return wcn36xx_caps_names[x];
214 }
215
wcn36xx_feat_caps_info(struct wcn36xx * wcn)216 static void wcn36xx_feat_caps_info(struct wcn36xx *wcn)
217 {
218 int i;
219
220 for (i = 0; i < MAX_FEATURE_SUPPORTED; i++) {
221 if (get_feat_caps(wcn->fw_feat_caps, i))
222 wcn36xx_info("FW Cap %s\n", wcn36xx_get_cap_name(i));
223 }
224 }
225
wcn36xx_detect_chip_version(struct wcn36xx * wcn)226 static void wcn36xx_detect_chip_version(struct wcn36xx *wcn)
227 {
228 if (get_feat_caps(wcn->fw_feat_caps, DOT11AC)) {
229 wcn36xx_info("Chip is 3680\n");
230 wcn->chip_version = WCN36XX_CHIP_3680;
231 } else {
232 wcn36xx_info("Chip is 3660\n");
233 wcn->chip_version = WCN36XX_CHIP_3660;
234 }
235 }
236
wcn36xx_start(struct ieee80211_hw * hw)237 static int wcn36xx_start(struct ieee80211_hw *hw)
238 {
239 struct wcn36xx *wcn = hw->priv;
240 int ret;
241
242 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac start\n");
243
244 /* SMD initialization */
245 ret = wcn36xx_smd_open(wcn);
246 if (ret) {
247 wcn36xx_err("Failed to open smd channel: %d\n", ret);
248 goto out_err;
249 }
250
251 /* Allocate memory pools for Mgmt BD headers and Data BD headers */
252 ret = wcn36xx_dxe_allocate_mem_pools(wcn);
253 if (ret) {
254 wcn36xx_err("Failed to alloc DXE mempool: %d\n", ret);
255 goto out_smd_close;
256 }
257
258 ret = wcn36xx_dxe_alloc_ctl_blks(wcn);
259 if (ret) {
260 wcn36xx_err("Failed to alloc DXE ctl blocks: %d\n", ret);
261 goto out_free_dxe_pool;
262 }
263
264 wcn->hal_buf = kmalloc(WCN36XX_HAL_BUF_SIZE, GFP_KERNEL);
265 if (!wcn->hal_buf) {
266 wcn36xx_err("Failed to allocate smd buf\n");
267 ret = -ENOMEM;
268 goto out_free_dxe_ctl;
269 }
270
271 ret = wcn36xx_smd_load_nv(wcn);
272 if (ret) {
273 wcn36xx_err("Failed to push NV to chip\n");
274 goto out_free_smd_buf;
275 }
276
277 ret = wcn36xx_smd_start(wcn);
278 if (ret) {
279 wcn36xx_err("Failed to start chip\n");
280 goto out_free_smd_buf;
281 }
282
283 if (!wcn36xx_is_fw_version(wcn, 1, 2, 2, 24)) {
284 ret = wcn36xx_smd_feature_caps_exchange(wcn);
285 if (ret)
286 wcn36xx_warn("Exchange feature caps failed\n");
287 else
288 wcn36xx_feat_caps_info(wcn);
289 }
290
291 wcn36xx_detect_chip_version(wcn);
292
293 /* DMA channel initialization */
294 ret = wcn36xx_dxe_init(wcn);
295 if (ret) {
296 wcn36xx_err("DXE init failed\n");
297 goto out_smd_stop;
298 }
299
300 wcn36xx_debugfs_init(wcn);
301
302 INIT_LIST_HEAD(&wcn->vif_list);
303 spin_lock_init(&wcn->dxe_lock);
304
305 return 0;
306
307 out_smd_stop:
308 wcn36xx_smd_stop(wcn);
309 out_free_smd_buf:
310 kfree(wcn->hal_buf);
311 out_free_dxe_pool:
312 wcn36xx_dxe_free_mem_pools(wcn);
313 out_free_dxe_ctl:
314 wcn36xx_dxe_free_ctl_blks(wcn);
315 out_smd_close:
316 wcn36xx_smd_close(wcn);
317 out_err:
318 return ret;
319 }
320
wcn36xx_stop(struct ieee80211_hw * hw)321 static void wcn36xx_stop(struct ieee80211_hw *hw)
322 {
323 struct wcn36xx *wcn = hw->priv;
324
325 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac stop\n");
326
327 wcn36xx_debugfs_exit(wcn);
328 wcn36xx_smd_stop(wcn);
329 wcn36xx_dxe_deinit(wcn);
330 wcn36xx_smd_close(wcn);
331
332 wcn36xx_dxe_free_mem_pools(wcn);
333 wcn36xx_dxe_free_ctl_blks(wcn);
334
335 kfree(wcn->hal_buf);
336 }
337
wcn36xx_config(struct ieee80211_hw * hw,u32 changed)338 static int wcn36xx_config(struct ieee80211_hw *hw, u32 changed)
339 {
340 struct wcn36xx *wcn = hw->priv;
341 struct ieee80211_vif *vif = NULL;
342 struct wcn36xx_vif *tmp;
343
344 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac config changed 0x%08x\n", changed);
345
346 if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
347 int ch = WCN36XX_HW_CHANNEL(wcn);
348 wcn36xx_dbg(WCN36XX_DBG_MAC, "wcn36xx_config channel switch=%d\n",
349 ch);
350 list_for_each_entry(tmp, &wcn->vif_list, list) {
351 vif = container_of((void *)tmp,
352 struct ieee80211_vif,
353 drv_priv);
354 wcn36xx_smd_switch_channel(wcn, vif, ch);
355 }
356 }
357
358 return 0;
359 }
360
361 #define WCN36XX_SUPPORTED_FILTERS (0)
362
wcn36xx_configure_filter(struct ieee80211_hw * hw,unsigned int changed,unsigned int * total,u64 multicast)363 static void wcn36xx_configure_filter(struct ieee80211_hw *hw,
364 unsigned int changed,
365 unsigned int *total, u64 multicast)
366 {
367 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac configure filter\n");
368
369 *total &= WCN36XX_SUPPORTED_FILTERS;
370 }
371
wcn36xx_tx(struct ieee80211_hw * hw,struct ieee80211_tx_control * control,struct sk_buff * skb)372 static void wcn36xx_tx(struct ieee80211_hw *hw,
373 struct ieee80211_tx_control *control,
374 struct sk_buff *skb)
375 {
376 struct wcn36xx *wcn = hw->priv;
377 struct wcn36xx_sta *sta_priv = NULL;
378
379 if (control->sta)
380 sta_priv = (struct wcn36xx_sta *)control->sta->drv_priv;
381
382 if (wcn36xx_start_tx(wcn, sta_priv, skb))
383 ieee80211_free_txskb(wcn->hw, skb);
384 }
385
wcn36xx_set_key(struct ieee80211_hw * hw,enum set_key_cmd cmd,struct ieee80211_vif * vif,struct ieee80211_sta * sta,struct ieee80211_key_conf * key_conf)386 static int wcn36xx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
387 struct ieee80211_vif *vif,
388 struct ieee80211_sta *sta,
389 struct ieee80211_key_conf *key_conf)
390 {
391 struct wcn36xx *wcn = hw->priv;
392 struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
393 struct wcn36xx_sta *sta_priv = vif_priv->sta;
394 int ret = 0;
395 u8 key[WLAN_MAX_KEY_LEN];
396
397 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac80211 set key\n");
398 wcn36xx_dbg(WCN36XX_DBG_MAC, "Key: cmd=0x%x algo:0x%x, id:%d, len:%d flags 0x%x\n",
399 cmd, key_conf->cipher, key_conf->keyidx,
400 key_conf->keylen, key_conf->flags);
401 wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "KEY: ",
402 key_conf->key,
403 key_conf->keylen);
404
405 switch (key_conf->cipher) {
406 case WLAN_CIPHER_SUITE_WEP40:
407 vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP40;
408 break;
409 case WLAN_CIPHER_SUITE_WEP104:
410 vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP40;
411 break;
412 case WLAN_CIPHER_SUITE_CCMP:
413 vif_priv->encrypt_type = WCN36XX_HAL_ED_CCMP;
414 break;
415 case WLAN_CIPHER_SUITE_TKIP:
416 vif_priv->encrypt_type = WCN36XX_HAL_ED_TKIP;
417 break;
418 default:
419 wcn36xx_err("Unsupported key type 0x%x\n",
420 key_conf->cipher);
421 ret = -EOPNOTSUPP;
422 goto out;
423 }
424
425 switch (cmd) {
426 case SET_KEY:
427 if (WCN36XX_HAL_ED_TKIP == vif_priv->encrypt_type) {
428 /*
429 * Supplicant is sending key in the wrong order:
430 * Temporal Key (16 b) - TX MIC (8 b) - RX MIC (8 b)
431 * but HW expects it to be in the order as described in
432 * IEEE 802.11 spec (see chapter 11.7) like this:
433 * Temporal Key (16 b) - RX MIC (8 b) - TX MIC (8 b)
434 */
435 memcpy(key, key_conf->key, 16);
436 memcpy(key + 16, key_conf->key + 24, 8);
437 memcpy(key + 24, key_conf->key + 16, 8);
438 } else {
439 memcpy(key, key_conf->key, key_conf->keylen);
440 }
441
442 if (IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags) {
443 sta_priv->is_data_encrypted = true;
444 /* Reconfigure bss with encrypt_type */
445 if (NL80211_IFTYPE_STATION == vif->type)
446 wcn36xx_smd_config_bss(wcn,
447 vif,
448 sta,
449 sta->addr,
450 true);
451
452 wcn36xx_smd_set_stakey(wcn,
453 vif_priv->encrypt_type,
454 key_conf->keyidx,
455 key_conf->keylen,
456 key,
457 get_sta_index(vif, sta_priv));
458 } else {
459 wcn36xx_smd_set_bsskey(wcn,
460 vif_priv->encrypt_type,
461 key_conf->keyidx,
462 key_conf->keylen,
463 key);
464 if ((WLAN_CIPHER_SUITE_WEP40 == key_conf->cipher) ||
465 (WLAN_CIPHER_SUITE_WEP104 == key_conf->cipher)) {
466 sta_priv->is_data_encrypted = true;
467 wcn36xx_smd_set_stakey(wcn,
468 vif_priv->encrypt_type,
469 key_conf->keyidx,
470 key_conf->keylen,
471 key,
472 get_sta_index(vif, sta_priv));
473 }
474 }
475 break;
476 case DISABLE_KEY:
477 if (!(IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags)) {
478 wcn36xx_smd_remove_bsskey(wcn,
479 vif_priv->encrypt_type,
480 key_conf->keyidx);
481 } else {
482 sta_priv->is_data_encrypted = false;
483 /* do not remove key if disassociated */
484 if (sta_priv->aid)
485 wcn36xx_smd_remove_stakey(wcn,
486 vif_priv->encrypt_type,
487 key_conf->keyidx,
488 get_sta_index(vif, sta_priv));
489 }
490 break;
491 default:
492 wcn36xx_err("Unsupported key cmd 0x%x\n", cmd);
493 ret = -EOPNOTSUPP;
494 goto out;
495 }
496
497 out:
498 return ret;
499 }
500
wcn36xx_sw_scan_start(struct ieee80211_hw * hw,struct ieee80211_vif * vif,const u8 * mac_addr)501 static void wcn36xx_sw_scan_start(struct ieee80211_hw *hw,
502 struct ieee80211_vif *vif,
503 const u8 *mac_addr)
504 {
505 struct wcn36xx *wcn = hw->priv;
506
507 wcn36xx_smd_init_scan(wcn, HAL_SYS_MODE_SCAN);
508 wcn36xx_smd_start_scan(wcn);
509 }
510
wcn36xx_sw_scan_complete(struct ieee80211_hw * hw,struct ieee80211_vif * vif)511 static void wcn36xx_sw_scan_complete(struct ieee80211_hw *hw,
512 struct ieee80211_vif *vif)
513 {
514 struct wcn36xx *wcn = hw->priv;
515
516 wcn36xx_smd_end_scan(wcn);
517 wcn36xx_smd_finish_scan(wcn, HAL_SYS_MODE_SCAN);
518 }
519
wcn36xx_update_allowed_rates(struct ieee80211_sta * sta,enum ieee80211_band band)520 static void wcn36xx_update_allowed_rates(struct ieee80211_sta *sta,
521 enum ieee80211_band band)
522 {
523 int i, size;
524 u16 *rates_table;
525 struct wcn36xx_sta *sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
526 u32 rates = sta->supp_rates[band];
527
528 memset(&sta_priv->supported_rates, 0,
529 sizeof(sta_priv->supported_rates));
530 sta_priv->supported_rates.op_rate_mode = STA_11n;
531
532 size = ARRAY_SIZE(sta_priv->supported_rates.dsss_rates);
533 rates_table = sta_priv->supported_rates.dsss_rates;
534 if (band == IEEE80211_BAND_2GHZ) {
535 for (i = 0; i < size; i++) {
536 if (rates & 0x01) {
537 rates_table[i] = wcn_2ghz_rates[i].hw_value;
538 rates = rates >> 1;
539 }
540 }
541 }
542
543 size = ARRAY_SIZE(sta_priv->supported_rates.ofdm_rates);
544 rates_table = sta_priv->supported_rates.ofdm_rates;
545 for (i = 0; i < size; i++) {
546 if (rates & 0x01) {
547 rates_table[i] = wcn_5ghz_rates[i].hw_value;
548 rates = rates >> 1;
549 }
550 }
551
552 if (sta->ht_cap.ht_supported) {
553 BUILD_BUG_ON(sizeof(sta->ht_cap.mcs.rx_mask) >
554 sizeof(sta_priv->supported_rates.supported_mcs_set));
555 memcpy(sta_priv->supported_rates.supported_mcs_set,
556 sta->ht_cap.mcs.rx_mask,
557 sizeof(sta->ht_cap.mcs.rx_mask));
558 }
559 }
wcn36xx_set_default_rates(struct wcn36xx_hal_supported_rates * rates)560 void wcn36xx_set_default_rates(struct wcn36xx_hal_supported_rates *rates)
561 {
562 u16 ofdm_rates[WCN36XX_HAL_NUM_OFDM_RATES] = {
563 HW_RATE_INDEX_6MBPS,
564 HW_RATE_INDEX_9MBPS,
565 HW_RATE_INDEX_12MBPS,
566 HW_RATE_INDEX_18MBPS,
567 HW_RATE_INDEX_24MBPS,
568 HW_RATE_INDEX_36MBPS,
569 HW_RATE_INDEX_48MBPS,
570 HW_RATE_INDEX_54MBPS
571 };
572 u16 dsss_rates[WCN36XX_HAL_NUM_DSSS_RATES] = {
573 HW_RATE_INDEX_1MBPS,
574 HW_RATE_INDEX_2MBPS,
575 HW_RATE_INDEX_5_5MBPS,
576 HW_RATE_INDEX_11MBPS
577 };
578
579 rates->op_rate_mode = STA_11n;
580 memcpy(rates->dsss_rates, dsss_rates,
581 sizeof(*dsss_rates) * WCN36XX_HAL_NUM_DSSS_RATES);
582 memcpy(rates->ofdm_rates, ofdm_rates,
583 sizeof(*ofdm_rates) * WCN36XX_HAL_NUM_OFDM_RATES);
584 rates->supported_mcs_set[0] = 0xFF;
585 }
wcn36xx_bss_info_changed(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_bss_conf * bss_conf,u32 changed)586 static void wcn36xx_bss_info_changed(struct ieee80211_hw *hw,
587 struct ieee80211_vif *vif,
588 struct ieee80211_bss_conf *bss_conf,
589 u32 changed)
590 {
591 struct wcn36xx *wcn = hw->priv;
592 struct sk_buff *skb = NULL;
593 u16 tim_off, tim_len;
594 enum wcn36xx_hal_link_state link_state;
595 struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
596
597 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss info changed vif %p changed 0x%08x\n",
598 vif, changed);
599
600 if (changed & BSS_CHANGED_BEACON_INFO) {
601 wcn36xx_dbg(WCN36XX_DBG_MAC,
602 "mac bss changed dtim period %d\n",
603 bss_conf->dtim_period);
604
605 vif_priv->dtim_period = bss_conf->dtim_period;
606 }
607
608 if (changed & BSS_CHANGED_PS) {
609 wcn36xx_dbg(WCN36XX_DBG_MAC,
610 "mac bss PS set %d\n",
611 bss_conf->ps);
612 if (bss_conf->ps) {
613 wcn36xx_pmc_enter_bmps_state(wcn, vif);
614 } else {
615 wcn36xx_pmc_exit_bmps_state(wcn, vif);
616 }
617 }
618
619 if (changed & BSS_CHANGED_BSSID) {
620 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed_bssid %pM\n",
621 bss_conf->bssid);
622
623 if (!is_zero_ether_addr(bss_conf->bssid)) {
624 vif_priv->is_joining = true;
625 vif_priv->bss_index = 0xff;
626 wcn36xx_smd_join(wcn, bss_conf->bssid,
627 vif->addr, WCN36XX_HW_CHANNEL(wcn));
628 wcn36xx_smd_config_bss(wcn, vif, NULL,
629 bss_conf->bssid, false);
630 } else {
631 vif_priv->is_joining = false;
632 wcn36xx_smd_delete_bss(wcn, vif);
633 }
634 }
635
636 if (changed & BSS_CHANGED_SSID) {
637 wcn36xx_dbg(WCN36XX_DBG_MAC,
638 "mac bss changed ssid\n");
639 wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "ssid ",
640 bss_conf->ssid, bss_conf->ssid_len);
641
642 vif_priv->ssid.length = bss_conf->ssid_len;
643 memcpy(&vif_priv->ssid.ssid,
644 bss_conf->ssid,
645 bss_conf->ssid_len);
646 }
647
648 if (changed & BSS_CHANGED_ASSOC) {
649 vif_priv->is_joining = false;
650 if (bss_conf->assoc) {
651 struct ieee80211_sta *sta;
652 struct wcn36xx_sta *sta_priv;
653
654 wcn36xx_dbg(WCN36XX_DBG_MAC,
655 "mac assoc bss %pM vif %pM AID=%d\n",
656 bss_conf->bssid,
657 vif->addr,
658 bss_conf->aid);
659
660 rcu_read_lock();
661 sta = ieee80211_find_sta(vif, bss_conf->bssid);
662 if (!sta) {
663 wcn36xx_err("sta %pM is not found\n",
664 bss_conf->bssid);
665 rcu_read_unlock();
666 goto out;
667 }
668 sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
669
670 wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn));
671
672 wcn36xx_smd_set_link_st(wcn, bss_conf->bssid,
673 vif->addr,
674 WCN36XX_HAL_LINK_POSTASSOC_STATE);
675 wcn36xx_smd_config_bss(wcn, vif, sta,
676 bss_conf->bssid,
677 true);
678 sta_priv->aid = bss_conf->aid;
679 /*
680 * config_sta must be called from because this is the
681 * place where AID is available.
682 */
683 wcn36xx_smd_config_sta(wcn, vif, sta);
684 rcu_read_unlock();
685 } else {
686 wcn36xx_dbg(WCN36XX_DBG_MAC,
687 "disassociated bss %pM vif %pM AID=%d\n",
688 bss_conf->bssid,
689 vif->addr,
690 bss_conf->aid);
691 wcn36xx_smd_set_link_st(wcn,
692 bss_conf->bssid,
693 vif->addr,
694 WCN36XX_HAL_LINK_IDLE_STATE);
695 }
696 }
697
698 if (changed & BSS_CHANGED_AP_PROBE_RESP) {
699 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed ap probe resp\n");
700 skb = ieee80211_proberesp_get(hw, vif);
701 if (!skb) {
702 wcn36xx_err("failed to alloc probereq skb\n");
703 goto out;
704 }
705
706 wcn36xx_smd_update_proberesp_tmpl(wcn, vif, skb);
707 dev_kfree_skb(skb);
708 }
709
710 if (changed & BSS_CHANGED_BEACON_ENABLED ||
711 changed & BSS_CHANGED_BEACON) {
712 wcn36xx_dbg(WCN36XX_DBG_MAC,
713 "mac bss changed beacon enabled %d\n",
714 bss_conf->enable_beacon);
715
716 if (bss_conf->enable_beacon) {
717 vif_priv->dtim_period = bss_conf->dtim_period;
718 vif_priv->bss_index = 0xff;
719 wcn36xx_smd_config_bss(wcn, vif, NULL,
720 vif->addr, false);
721 skb = ieee80211_beacon_get_tim(hw, vif, &tim_off,
722 &tim_len);
723 if (!skb) {
724 wcn36xx_err("failed to alloc beacon skb\n");
725 goto out;
726 }
727 wcn36xx_smd_send_beacon(wcn, vif, skb, tim_off, 0);
728 dev_kfree_skb(skb);
729
730 if (vif->type == NL80211_IFTYPE_ADHOC ||
731 vif->type == NL80211_IFTYPE_MESH_POINT)
732 link_state = WCN36XX_HAL_LINK_IBSS_STATE;
733 else
734 link_state = WCN36XX_HAL_LINK_AP_STATE;
735
736 wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr,
737 link_state);
738 } else {
739 wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr,
740 WCN36XX_HAL_LINK_IDLE_STATE);
741 wcn36xx_smd_delete_bss(wcn, vif);
742 }
743 }
744 out:
745 return;
746 }
747
748 /* this is required when using IEEE80211_HW_HAS_RATE_CONTROL */
wcn36xx_set_rts_threshold(struct ieee80211_hw * hw,u32 value)749 static int wcn36xx_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
750 {
751 struct wcn36xx *wcn = hw->priv;
752 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac set RTS threshold %d\n", value);
753
754 wcn36xx_smd_update_cfg(wcn, WCN36XX_HAL_CFG_RTS_THRESHOLD, value);
755 return 0;
756 }
757
wcn36xx_remove_interface(struct ieee80211_hw * hw,struct ieee80211_vif * vif)758 static void wcn36xx_remove_interface(struct ieee80211_hw *hw,
759 struct ieee80211_vif *vif)
760 {
761 struct wcn36xx *wcn = hw->priv;
762 struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
763 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac remove interface vif %p\n", vif);
764
765 list_del(&vif_priv->list);
766 wcn36xx_smd_delete_sta_self(wcn, vif->addr);
767 }
768
wcn36xx_add_interface(struct ieee80211_hw * hw,struct ieee80211_vif * vif)769 static int wcn36xx_add_interface(struct ieee80211_hw *hw,
770 struct ieee80211_vif *vif)
771 {
772 struct wcn36xx *wcn = hw->priv;
773 struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
774
775 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac add interface vif %p type %d\n",
776 vif, vif->type);
777
778 if (!(NL80211_IFTYPE_STATION == vif->type ||
779 NL80211_IFTYPE_AP == vif->type ||
780 NL80211_IFTYPE_ADHOC == vif->type ||
781 NL80211_IFTYPE_MESH_POINT == vif->type)) {
782 wcn36xx_warn("Unsupported interface type requested: %d\n",
783 vif->type);
784 return -EOPNOTSUPP;
785 }
786
787 list_add(&vif_priv->list, &wcn->vif_list);
788 wcn36xx_smd_add_sta_self(wcn, vif);
789
790 return 0;
791 }
792
wcn36xx_sta_add(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_sta * sta)793 static int wcn36xx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
794 struct ieee80211_sta *sta)
795 {
796 struct wcn36xx *wcn = hw->priv;
797 struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
798 struct wcn36xx_sta *sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
799 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta add vif %p sta %pM\n",
800 vif, sta->addr);
801
802 spin_lock_init(&sta_priv->ampdu_lock);
803 vif_priv->sta = sta_priv;
804 sta_priv->vif = vif_priv;
805 /*
806 * For STA mode HW will be configured on BSS_CHANGED_ASSOC because
807 * at this stage AID is not available yet.
808 */
809 if (NL80211_IFTYPE_STATION != vif->type) {
810 wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn));
811 sta_priv->aid = sta->aid;
812 wcn36xx_smd_config_sta(wcn, vif, sta);
813 }
814 return 0;
815 }
816
wcn36xx_sta_remove(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_sta * sta)817 static int wcn36xx_sta_remove(struct ieee80211_hw *hw,
818 struct ieee80211_vif *vif,
819 struct ieee80211_sta *sta)
820 {
821 struct wcn36xx *wcn = hw->priv;
822 struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
823 struct wcn36xx_sta *sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
824
825 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta remove vif %p sta %pM index %d\n",
826 vif, sta->addr, sta_priv->sta_index);
827
828 wcn36xx_smd_delete_sta(wcn, sta_priv->sta_index);
829 vif_priv->sta = NULL;
830 sta_priv->vif = NULL;
831 return 0;
832 }
833
834 #ifdef CONFIG_PM
835
wcn36xx_suspend(struct ieee80211_hw * hw,struct cfg80211_wowlan * wow)836 static int wcn36xx_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wow)
837 {
838 struct wcn36xx *wcn = hw->priv;
839
840 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac suspend\n");
841
842 flush_workqueue(wcn->hal_ind_wq);
843 wcn36xx_smd_set_power_params(wcn, true);
844 return 0;
845 }
846
wcn36xx_resume(struct ieee80211_hw * hw)847 static int wcn36xx_resume(struct ieee80211_hw *hw)
848 {
849 struct wcn36xx *wcn = hw->priv;
850
851 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac resume\n");
852
853 flush_workqueue(wcn->hal_ind_wq);
854 wcn36xx_smd_set_power_params(wcn, false);
855 return 0;
856 }
857
858 #endif
859
wcn36xx_ampdu_action(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_ampdu_params * params)860 static int wcn36xx_ampdu_action(struct ieee80211_hw *hw,
861 struct ieee80211_vif *vif,
862 struct ieee80211_ampdu_params *params)
863 {
864 struct wcn36xx *wcn = hw->priv;
865 struct wcn36xx_sta *sta_priv = NULL;
866 struct ieee80211_sta *sta = params->sta;
867 enum ieee80211_ampdu_mlme_action action = params->action;
868 u16 tid = params->tid;
869 u16 *ssn = ¶ms->ssn;
870
871 wcn36xx_dbg(WCN36XX_DBG_MAC, "mac ampdu action action %d tid %d\n",
872 action, tid);
873
874 sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
875
876 switch (action) {
877 case IEEE80211_AMPDU_RX_START:
878 sta_priv->tid = tid;
879 wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 0,
880 get_sta_index(vif, sta_priv));
881 wcn36xx_smd_add_ba(wcn);
882 wcn36xx_smd_trigger_ba(wcn, get_sta_index(vif, sta_priv));
883 break;
884 case IEEE80211_AMPDU_RX_STOP:
885 wcn36xx_smd_del_ba(wcn, tid, get_sta_index(vif, sta_priv));
886 break;
887 case IEEE80211_AMPDU_TX_START:
888 spin_lock_bh(&sta_priv->ampdu_lock);
889 sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_START;
890 spin_unlock_bh(&sta_priv->ampdu_lock);
891
892 ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid);
893 break;
894 case IEEE80211_AMPDU_TX_OPERATIONAL:
895 spin_lock_bh(&sta_priv->ampdu_lock);
896 sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_OPERATIONAL;
897 spin_unlock_bh(&sta_priv->ampdu_lock);
898
899 wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 1,
900 get_sta_index(vif, sta_priv));
901 break;
902 case IEEE80211_AMPDU_TX_STOP_FLUSH:
903 case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
904 case IEEE80211_AMPDU_TX_STOP_CONT:
905 spin_lock_bh(&sta_priv->ampdu_lock);
906 sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_NONE;
907 spin_unlock_bh(&sta_priv->ampdu_lock);
908
909 ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
910 break;
911 default:
912 wcn36xx_err("Unknown AMPDU action\n");
913 }
914
915 return 0;
916 }
917
918 static const struct ieee80211_ops wcn36xx_ops = {
919 .start = wcn36xx_start,
920 .stop = wcn36xx_stop,
921 .add_interface = wcn36xx_add_interface,
922 .remove_interface = wcn36xx_remove_interface,
923 #ifdef CONFIG_PM
924 .suspend = wcn36xx_suspend,
925 .resume = wcn36xx_resume,
926 #endif
927 .config = wcn36xx_config,
928 .configure_filter = wcn36xx_configure_filter,
929 .tx = wcn36xx_tx,
930 .set_key = wcn36xx_set_key,
931 .sw_scan_start = wcn36xx_sw_scan_start,
932 .sw_scan_complete = wcn36xx_sw_scan_complete,
933 .bss_info_changed = wcn36xx_bss_info_changed,
934 .set_rts_threshold = wcn36xx_set_rts_threshold,
935 .sta_add = wcn36xx_sta_add,
936 .sta_remove = wcn36xx_sta_remove,
937 .ampdu_action = wcn36xx_ampdu_action,
938 };
939
wcn36xx_init_ieee80211(struct wcn36xx * wcn)940 static int wcn36xx_init_ieee80211(struct wcn36xx *wcn)
941 {
942 int ret = 0;
943
944 static const u32 cipher_suites[] = {
945 WLAN_CIPHER_SUITE_WEP40,
946 WLAN_CIPHER_SUITE_WEP104,
947 WLAN_CIPHER_SUITE_TKIP,
948 WLAN_CIPHER_SUITE_CCMP,
949 };
950
951 ieee80211_hw_set(wcn->hw, TIMING_BEACON_ONLY);
952 ieee80211_hw_set(wcn->hw, AMPDU_AGGREGATION);
953 ieee80211_hw_set(wcn->hw, CONNECTION_MONITOR);
954 ieee80211_hw_set(wcn->hw, SUPPORTS_PS);
955 ieee80211_hw_set(wcn->hw, SIGNAL_DBM);
956 ieee80211_hw_set(wcn->hw, HAS_RATE_CONTROL);
957
958 wcn->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
959 BIT(NL80211_IFTYPE_AP) |
960 BIT(NL80211_IFTYPE_ADHOC) |
961 BIT(NL80211_IFTYPE_MESH_POINT);
962
963 wcn->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &wcn_band_2ghz;
964 wcn->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &wcn_band_5ghz;
965
966 wcn->hw->wiphy->cipher_suites = cipher_suites;
967 wcn->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
968
969 wcn->hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
970
971 #ifdef CONFIG_PM
972 wcn->hw->wiphy->wowlan = &wowlan_support;
973 #endif
974
975 wcn->hw->max_listen_interval = 200;
976
977 wcn->hw->queues = 4;
978
979 SET_IEEE80211_DEV(wcn->hw, wcn->dev);
980
981 wcn->hw->sta_data_size = sizeof(struct wcn36xx_sta);
982 wcn->hw->vif_data_size = sizeof(struct wcn36xx_vif);
983
984 return ret;
985 }
986
wcn36xx_platform_get_resources(struct wcn36xx * wcn,struct platform_device * pdev)987 static int wcn36xx_platform_get_resources(struct wcn36xx *wcn,
988 struct platform_device *pdev)
989 {
990 struct resource *res;
991 /* Set TX IRQ */
992 res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
993 "wcnss_wlantx_irq");
994 if (!res) {
995 wcn36xx_err("failed to get tx_irq\n");
996 return -ENOENT;
997 }
998 wcn->tx_irq = res->start;
999
1000 /* Set RX IRQ */
1001 res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1002 "wcnss_wlanrx_irq");
1003 if (!res) {
1004 wcn36xx_err("failed to get rx_irq\n");
1005 return -ENOENT;
1006 }
1007 wcn->rx_irq = res->start;
1008
1009 /* Map the memory */
1010 res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1011 "wcnss_mmio");
1012 if (!res) {
1013 wcn36xx_err("failed to get mmio\n");
1014 return -ENOENT;
1015 }
1016 wcn->mmio = ioremap(res->start, resource_size(res));
1017 if (!wcn->mmio) {
1018 wcn36xx_err("failed to map io memory\n");
1019 return -ENOMEM;
1020 }
1021 return 0;
1022 }
1023
wcn36xx_probe(struct platform_device * pdev)1024 static int wcn36xx_probe(struct platform_device *pdev)
1025 {
1026 struct ieee80211_hw *hw;
1027 struct wcn36xx *wcn;
1028 int ret;
1029 u8 addr[ETH_ALEN];
1030
1031 wcn36xx_dbg(WCN36XX_DBG_MAC, "platform probe\n");
1032
1033 hw = ieee80211_alloc_hw(sizeof(struct wcn36xx), &wcn36xx_ops);
1034 if (!hw) {
1035 wcn36xx_err("failed to alloc hw\n");
1036 ret = -ENOMEM;
1037 goto out_err;
1038 }
1039 platform_set_drvdata(pdev, hw);
1040 wcn = hw->priv;
1041 wcn->hw = hw;
1042 wcn->dev = &pdev->dev;
1043 wcn->ctrl_ops = pdev->dev.platform_data;
1044
1045 mutex_init(&wcn->hal_mutex);
1046
1047 if (!wcn->ctrl_ops->get_hw_mac(addr)) {
1048 wcn36xx_info("mac address: %pM\n", addr);
1049 SET_IEEE80211_PERM_ADDR(wcn->hw, addr);
1050 }
1051
1052 ret = wcn36xx_platform_get_resources(wcn, pdev);
1053 if (ret)
1054 goto out_wq;
1055
1056 wcn36xx_init_ieee80211(wcn);
1057 ret = ieee80211_register_hw(wcn->hw);
1058 if (ret)
1059 goto out_unmap;
1060
1061 return 0;
1062
1063 out_unmap:
1064 iounmap(wcn->mmio);
1065 out_wq:
1066 ieee80211_free_hw(hw);
1067 out_err:
1068 return ret;
1069 }
wcn36xx_remove(struct platform_device * pdev)1070 static int wcn36xx_remove(struct platform_device *pdev)
1071 {
1072 struct ieee80211_hw *hw = platform_get_drvdata(pdev);
1073 struct wcn36xx *wcn = hw->priv;
1074 wcn36xx_dbg(WCN36XX_DBG_MAC, "platform remove\n");
1075
1076 release_firmware(wcn->nv);
1077 mutex_destroy(&wcn->hal_mutex);
1078
1079 ieee80211_unregister_hw(hw);
1080 iounmap(wcn->mmio);
1081 ieee80211_free_hw(hw);
1082
1083 return 0;
1084 }
1085 static const struct platform_device_id wcn36xx_platform_id_table[] = {
1086 {
1087 .name = "wcn36xx",
1088 .driver_data = 0
1089 },
1090 {}
1091 };
1092 MODULE_DEVICE_TABLE(platform, wcn36xx_platform_id_table);
1093
1094 static struct platform_driver wcn36xx_driver = {
1095 .probe = wcn36xx_probe,
1096 .remove = wcn36xx_remove,
1097 .driver = {
1098 .name = "wcn36xx",
1099 },
1100 .id_table = wcn36xx_platform_id_table,
1101 };
1102
wcn36xx_init(void)1103 static int __init wcn36xx_init(void)
1104 {
1105 platform_driver_register(&wcn36xx_driver);
1106 return 0;
1107 }
1108 module_init(wcn36xx_init);
1109
wcn36xx_exit(void)1110 static void __exit wcn36xx_exit(void)
1111 {
1112 platform_driver_unregister(&wcn36xx_driver);
1113 }
1114 module_exit(wcn36xx_exit);
1115
1116 MODULE_LICENSE("Dual BSD/GPL");
1117 MODULE_AUTHOR("Eugene Krasnikov k.eugene.e@gmail.com");
1118 MODULE_FIRMWARE(WLAN_NV_FILE);
1119