Commit | Line | Data |
---|---|---|
8e84c258 EK |
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> | |
4bda7faf | 20 | #include <linux/firmware.h> |
8e84c258 | 21 | #include <linux/platform_device.h> |
05ddce49 BA |
22 | #include <linux/of_address.h> |
23 | #include <linux/of_device.h> | |
f303a931 | 24 | #include <linux/of_irq.h> |
5052de8d | 25 | #include <linux/rpmsg.h> |
f303a931 BA |
26 | #include <linux/soc/qcom/smem_state.h> |
27 | #include <linux/soc/qcom/wcnss_ctrl.h> | |
8e84c258 | 28 | #include "wcn36xx.h" |
87f825e6 | 29 | #include "testmode.h" |
8e84c258 EK |
30 | |
31 | unsigned int wcn36xx_dbg_mask; | |
32 | module_param_named(debug_mask, wcn36xx_dbg_mask, uint, 0644); | |
33 | MODULE_PARM_DESC(debug_mask, "Debugging mask"); | |
34 | ||
35 | #define CHAN2G(_freq, _idx) { \ | |
57fbcce3 | 36 | .band = NL80211_BAND_2GHZ, \ |
8e84c258 EK |
37 | .center_freq = (_freq), \ |
38 | .hw_value = (_idx), \ | |
39 | .max_power = 25, \ | |
40 | } | |
41 | ||
42 | #define CHAN5G(_freq, _idx) { \ | |
57fbcce3 | 43 | .band = NL80211_BAND_5GHZ, \ |
8e84c258 EK |
44 | .center_freq = (_freq), \ |
45 | .hw_value = (_idx), \ | |
46 | .max_power = 25, \ | |
47 | } | |
48 | ||
49 | /* The wcn firmware expects channel values to matching | |
50 | * their mnemonic values. So use these for .hw_value. */ | |
51 | static struct ieee80211_channel wcn_2ghz_channels[] = { | |
52 | CHAN2G(2412, 1), /* Channel 1 */ | |
53 | CHAN2G(2417, 2), /* Channel 2 */ | |
54 | CHAN2G(2422, 3), /* Channel 3 */ | |
55 | CHAN2G(2427, 4), /* Channel 4 */ | |
56 | CHAN2G(2432, 5), /* Channel 5 */ | |
57 | CHAN2G(2437, 6), /* Channel 6 */ | |
58 | CHAN2G(2442, 7), /* Channel 7 */ | |
59 | CHAN2G(2447, 8), /* Channel 8 */ | |
60 | CHAN2G(2452, 9), /* Channel 9 */ | |
61 | CHAN2G(2457, 10), /* Channel 10 */ | |
62 | CHAN2G(2462, 11), /* Channel 11 */ | |
63 | CHAN2G(2467, 12), /* Channel 12 */ | |
64 | CHAN2G(2472, 13), /* Channel 13 */ | |
65 | CHAN2G(2484, 14) /* Channel 14 */ | |
66 | ||
67 | }; | |
68 | ||
69 | static struct ieee80211_channel wcn_5ghz_channels[] = { | |
70 | CHAN5G(5180, 36), | |
71 | CHAN5G(5200, 40), | |
72 | CHAN5G(5220, 44), | |
73 | CHAN5G(5240, 48), | |
74 | CHAN5G(5260, 52), | |
75 | CHAN5G(5280, 56), | |
76 | CHAN5G(5300, 60), | |
77 | CHAN5G(5320, 64), | |
78 | CHAN5G(5500, 100), | |
79 | CHAN5G(5520, 104), | |
80 | CHAN5G(5540, 108), | |
81 | CHAN5G(5560, 112), | |
82 | CHAN5G(5580, 116), | |
83 | CHAN5G(5600, 120), | |
84 | CHAN5G(5620, 124), | |
85 | CHAN5G(5640, 128), | |
86 | CHAN5G(5660, 132), | |
87 | CHAN5G(5700, 140), | |
88 | CHAN5G(5745, 149), | |
89 | CHAN5G(5765, 153), | |
90 | CHAN5G(5785, 157), | |
91 | CHAN5G(5805, 161), | |
92 | CHAN5G(5825, 165) | |
93 | }; | |
94 | ||
95 | #define RATE(_bitrate, _hw_rate, _flags) { \ | |
96 | .bitrate = (_bitrate), \ | |
97 | .flags = (_flags), \ | |
98 | .hw_value = (_hw_rate), \ | |
99 | .hw_value_short = (_hw_rate) \ | |
100 | } | |
101 | ||
102 | static struct ieee80211_rate wcn_2ghz_rates[] = { | |
103 | RATE(10, HW_RATE_INDEX_1MBPS, 0), | |
104 | RATE(20, HW_RATE_INDEX_2MBPS, IEEE80211_RATE_SHORT_PREAMBLE), | |
105 | RATE(55, HW_RATE_INDEX_5_5MBPS, IEEE80211_RATE_SHORT_PREAMBLE), | |
106 | RATE(110, HW_RATE_INDEX_11MBPS, IEEE80211_RATE_SHORT_PREAMBLE), | |
107 | RATE(60, HW_RATE_INDEX_6MBPS, 0), | |
108 | RATE(90, HW_RATE_INDEX_9MBPS, 0), | |
109 | RATE(120, HW_RATE_INDEX_12MBPS, 0), | |
110 | RATE(180, HW_RATE_INDEX_18MBPS, 0), | |
111 | RATE(240, HW_RATE_INDEX_24MBPS, 0), | |
112 | RATE(360, HW_RATE_INDEX_36MBPS, 0), | |
113 | RATE(480, HW_RATE_INDEX_48MBPS, 0), | |
114 | RATE(540, HW_RATE_INDEX_54MBPS, 0) | |
115 | }; | |
116 | ||
117 | static struct ieee80211_rate wcn_5ghz_rates[] = { | |
118 | RATE(60, HW_RATE_INDEX_6MBPS, 0), | |
119 | RATE(90, HW_RATE_INDEX_9MBPS, 0), | |
120 | RATE(120, HW_RATE_INDEX_12MBPS, 0), | |
121 | RATE(180, HW_RATE_INDEX_18MBPS, 0), | |
122 | RATE(240, HW_RATE_INDEX_24MBPS, 0), | |
123 | RATE(360, HW_RATE_INDEX_36MBPS, 0), | |
124 | RATE(480, HW_RATE_INDEX_48MBPS, 0), | |
125 | RATE(540, HW_RATE_INDEX_54MBPS, 0) | |
126 | }; | |
127 | ||
128 | static struct ieee80211_supported_band wcn_band_2ghz = { | |
129 | .channels = wcn_2ghz_channels, | |
130 | .n_channels = ARRAY_SIZE(wcn_2ghz_channels), | |
131 | .bitrates = wcn_2ghz_rates, | |
132 | .n_bitrates = ARRAY_SIZE(wcn_2ghz_rates), | |
133 | .ht_cap = { | |
134 | .cap = IEEE80211_HT_CAP_GRN_FLD | | |
135 | IEEE80211_HT_CAP_SGI_20 | | |
136 | IEEE80211_HT_CAP_DSSSCCK40 | | |
137 | IEEE80211_HT_CAP_LSIG_TXOP_PROT, | |
138 | .ht_supported = true, | |
139 | .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, | |
140 | .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, | |
141 | .mcs = { | |
142 | .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, | |
143 | .rx_highest = cpu_to_le16(72), | |
144 | .tx_params = IEEE80211_HT_MCS_TX_DEFINED, | |
145 | } | |
146 | } | |
147 | }; | |
148 | ||
149 | static struct ieee80211_supported_band wcn_band_5ghz = { | |
150 | .channels = wcn_5ghz_channels, | |
151 | .n_channels = ARRAY_SIZE(wcn_5ghz_channels), | |
152 | .bitrates = wcn_5ghz_rates, | |
153 | .n_bitrates = ARRAY_SIZE(wcn_5ghz_rates), | |
154 | .ht_cap = { | |
155 | .cap = IEEE80211_HT_CAP_GRN_FLD | | |
156 | IEEE80211_HT_CAP_SGI_20 | | |
157 | IEEE80211_HT_CAP_DSSSCCK40 | | |
158 | IEEE80211_HT_CAP_LSIG_TXOP_PROT | | |
159 | IEEE80211_HT_CAP_SGI_40 | | |
160 | IEEE80211_HT_CAP_SUP_WIDTH_20_40, | |
161 | .ht_supported = true, | |
162 | .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, | |
163 | .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, | |
164 | .mcs = { | |
165 | .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, | |
166 | .rx_highest = cpu_to_le16(72), | |
167 | .tx_params = IEEE80211_HT_MCS_TX_DEFINED, | |
168 | } | |
169 | } | |
170 | }; | |
171 | ||
172 | #ifdef CONFIG_PM | |
173 | ||
174 | static const struct wiphy_wowlan_support wowlan_support = { | |
175 | .flags = WIPHY_WOWLAN_ANY | |
176 | }; | |
177 | ||
178 | #endif | |
179 | ||
180 | static inline u8 get_sta_index(struct ieee80211_vif *vif, | |
181 | struct wcn36xx_sta *sta_priv) | |
182 | { | |
183 | return NL80211_IFTYPE_STATION == vif->type ? | |
184 | sta_priv->bss_sta_index : | |
185 | sta_priv->sta_index; | |
186 | } | |
187 | ||
2be6636a PF |
188 | static const char * const wcn36xx_caps_names[] = { |
189 | "MCC", /* 0 */ | |
190 | "P2P", /* 1 */ | |
191 | "DOT11AC", /* 2 */ | |
192 | "SLM_SESSIONIZATION", /* 3 */ | |
193 | "DOT11AC_OPMODE", /* 4 */ | |
194 | "SAP32STA", /* 5 */ | |
195 | "TDLS", /* 6 */ | |
196 | "P2P_GO_NOA_DECOUPLE_INIT_SCAN",/* 7 */ | |
197 | "WLANACTIVE_OFFLOAD", /* 8 */ | |
198 | "BEACON_OFFLOAD", /* 9 */ | |
199 | "SCAN_OFFLOAD", /* 10 */ | |
200 | "ROAM_OFFLOAD", /* 11 */ | |
201 | "BCN_MISS_OFFLOAD", /* 12 */ | |
202 | "STA_POWERSAVE", /* 13 */ | |
203 | "STA_ADVANCED_PWRSAVE", /* 14 */ | |
204 | "AP_UAPSD", /* 15 */ | |
205 | "AP_DFS", /* 16 */ | |
206 | "BLOCKACK", /* 17 */ | |
207 | "PHY_ERR", /* 18 */ | |
208 | "BCN_FILTER", /* 19 */ | |
209 | "RTT", /* 20 */ | |
210 | "RATECTRL", /* 21 */ | |
ffc03c33 BA |
211 | "WOW", /* 22 */ |
212 | "WLAN_ROAM_SCAN_OFFLOAD", /* 23 */ | |
213 | "SPECULATIVE_PS_POLL", /* 24 */ | |
214 | "SCAN_SCH", /* 25 */ | |
215 | "IBSS_HEARTBEAT_OFFLOAD", /* 26 */ | |
216 | "WLAN_SCAN_OFFLOAD", /* 27 */ | |
217 | "WLAN_PERIODIC_TX_PTRN", /* 28 */ | |
218 | "ADVANCE_TDLS", /* 29 */ | |
219 | "BATCH_SCAN", /* 30 */ | |
220 | "FW_IN_TX_PATH", /* 31 */ | |
221 | "EXTENDED_NSOFFLOAD_SLOT", /* 32 */ | |
222 | "CH_SWITCH_V1", /* 33 */ | |
223 | "HT40_OBSS_SCAN", /* 34 */ | |
224 | "UPDATE_CHANNEL_LIST", /* 35 */ | |
225 | "WLAN_MCADDR_FLT", /* 36 */ | |
226 | "WLAN_CH144", /* 37 */ | |
227 | "NAN", /* 38 */ | |
228 | "TDLS_SCAN_COEXISTENCE", /* 39 */ | |
229 | "LINK_LAYER_STATS_MEAS", /* 40 */ | |
230 | "MU_MIMO", /* 41 */ | |
231 | "EXTENDED_SCAN", /* 42 */ | |
232 | "DYNAMIC_WMM_PS", /* 43 */ | |
233 | "MAC_SPOOFED_SCAN", /* 44 */ | |
234 | "BMU_ERROR_GENERIC_RECOVERY", /* 45 */ | |
235 | "DISA", /* 46 */ | |
236 | "FW_STATS", /* 47 */ | |
237 | "WPS_PRBRSP_TMPL", /* 48 */ | |
238 | "BCN_IE_FLT_DELTA", /* 49 */ | |
239 | "TDLS_OFF_CHANNEL", /* 51 */ | |
240 | "RTT3", /* 52 */ | |
241 | "MGMT_FRAME_LOGGING", /* 53 */ | |
242 | "ENHANCED_TXBD_COMPLETION", /* 54 */ | |
243 | "LOGGING_ENHANCEMENT", /* 55 */ | |
244 | "EXT_SCAN_ENHANCED", /* 56 */ | |
245 | "MEMORY_DUMP_SUPPORTED", /* 57 */ | |
246 | "PER_PKT_STATS_SUPPORTED", /* 58 */ | |
247 | "EXT_LL_STAT", /* 60 */ | |
248 | "WIFI_CONFIG", /* 61 */ | |
249 | "ANTENNA_DIVERSITY_SELECTION", /* 62 */ | |
2be6636a PF |
250 | }; |
251 | ||
252 | static const char *wcn36xx_get_cap_name(enum place_holder_in_cap_bitmap x) | |
253 | { | |
254 | if (x >= ARRAY_SIZE(wcn36xx_caps_names)) | |
255 | return "UNKNOWN"; | |
256 | return wcn36xx_caps_names[x]; | |
257 | } | |
258 | ||
259 | static void wcn36xx_feat_caps_info(struct wcn36xx *wcn) | |
260 | { | |
261 | int i; | |
262 | ||
263 | for (i = 0; i < MAX_FEATURE_SUPPORTED; i++) { | |
264 | if (get_feat_caps(wcn->fw_feat_caps, i)) | |
6b8a127b | 265 | wcn36xx_dbg(WCN36XX_DBG_MAC, "FW Cap %s\n", wcn36xx_get_cap_name(i)); |
2be6636a PF |
266 | } |
267 | } | |
268 | ||
8e84c258 EK |
269 | static int wcn36xx_start(struct ieee80211_hw *hw) |
270 | { | |
271 | struct wcn36xx *wcn = hw->priv; | |
272 | int ret; | |
273 | ||
274 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac start\n"); | |
275 | ||
276 | /* SMD initialization */ | |
277 | ret = wcn36xx_smd_open(wcn); | |
278 | if (ret) { | |
279 | wcn36xx_err("Failed to open smd channel: %d\n", ret); | |
280 | goto out_err; | |
281 | } | |
282 | ||
283 | /* Allocate memory pools for Mgmt BD headers and Data BD headers */ | |
284 | ret = wcn36xx_dxe_allocate_mem_pools(wcn); | |
285 | if (ret) { | |
286 | wcn36xx_err("Failed to alloc DXE mempool: %d\n", ret); | |
287 | goto out_smd_close; | |
288 | } | |
289 | ||
290 | ret = wcn36xx_dxe_alloc_ctl_blks(wcn); | |
291 | if (ret) { | |
292 | wcn36xx_err("Failed to alloc DXE ctl blocks: %d\n", ret); | |
293 | goto out_free_dxe_pool; | |
294 | } | |
295 | ||
296 | wcn->hal_buf = kmalloc(WCN36XX_HAL_BUF_SIZE, GFP_KERNEL); | |
297 | if (!wcn->hal_buf) { | |
298 | wcn36xx_err("Failed to allocate smd buf\n"); | |
299 | ret = -ENOMEM; | |
300 | goto out_free_dxe_ctl; | |
301 | } | |
302 | ||
303 | ret = wcn36xx_smd_load_nv(wcn); | |
304 | if (ret) { | |
305 | wcn36xx_err("Failed to push NV to chip\n"); | |
306 | goto out_free_smd_buf; | |
307 | } | |
308 | ||
309 | ret = wcn36xx_smd_start(wcn); | |
310 | if (ret) { | |
311 | wcn36xx_err("Failed to start chip\n"); | |
312 | goto out_free_smd_buf; | |
313 | } | |
314 | ||
f2ed5d24 PF |
315 | if (!wcn36xx_is_fw_version(wcn, 1, 2, 2, 24)) { |
316 | ret = wcn36xx_smd_feature_caps_exchange(wcn); | |
317 | if (ret) | |
318 | wcn36xx_warn("Exchange feature caps failed\n"); | |
319 | else | |
320 | wcn36xx_feat_caps_info(wcn); | |
321 | } | |
322 | ||
8e84c258 EK |
323 | /* DMA channel initialization */ |
324 | ret = wcn36xx_dxe_init(wcn); | |
325 | if (ret) { | |
326 | wcn36xx_err("DXE init failed\n"); | |
327 | goto out_smd_stop; | |
328 | } | |
329 | ||
330 | wcn36xx_debugfs_init(wcn); | |
331 | ||
8e84c258 | 332 | INIT_LIST_HEAD(&wcn->vif_list); |
4b12462a BC |
333 | spin_lock_init(&wcn->dxe_lock); |
334 | ||
8e84c258 EK |
335 | return 0; |
336 | ||
337 | out_smd_stop: | |
338 | wcn36xx_smd_stop(wcn); | |
339 | out_free_smd_buf: | |
340 | kfree(wcn->hal_buf); | |
8e84c258 EK |
341 | out_free_dxe_ctl: |
342 | wcn36xx_dxe_free_ctl_blks(wcn); | |
4aa2d31f CJ |
343 | out_free_dxe_pool: |
344 | wcn36xx_dxe_free_mem_pools(wcn); | |
8e84c258 EK |
345 | out_smd_close: |
346 | wcn36xx_smd_close(wcn); | |
347 | out_err: | |
348 | return ret; | |
349 | } | |
350 | ||
351 | static void wcn36xx_stop(struct ieee80211_hw *hw) | |
352 | { | |
353 | struct wcn36xx *wcn = hw->priv; | |
354 | ||
355 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac stop\n"); | |
356 | ||
80c764d3 DM |
357 | cancel_work_sync(&wcn->scan_work); |
358 | ||
359 | mutex_lock(&wcn->scan_lock); | |
360 | if (wcn->scan_req) { | |
361 | struct cfg80211_scan_info scan_info = { | |
362 | .aborted = true, | |
363 | }; | |
364 | ||
365 | ieee80211_scan_completed(wcn->hw, &scan_info); | |
366 | } | |
367 | wcn->scan_req = NULL; | |
368 | mutex_unlock(&wcn->scan_lock); | |
369 | ||
8e84c258 EK |
370 | wcn36xx_debugfs_exit(wcn); |
371 | wcn36xx_smd_stop(wcn); | |
372 | wcn36xx_dxe_deinit(wcn); | |
373 | wcn36xx_smd_close(wcn); | |
374 | ||
375 | wcn36xx_dxe_free_mem_pools(wcn); | |
376 | wcn36xx_dxe_free_ctl_blks(wcn); | |
377 | ||
378 | kfree(wcn->hal_buf); | |
379 | } | |
380 | ||
381 | static int wcn36xx_config(struct ieee80211_hw *hw, u32 changed) | |
382 | { | |
383 | struct wcn36xx *wcn = hw->priv; | |
384 | struct ieee80211_vif *vif = NULL; | |
385 | struct wcn36xx_vif *tmp; | |
386 | ||
387 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac config changed 0x%08x\n", changed); | |
388 | ||
39efc7cc BA |
389 | mutex_lock(&wcn->conf_mutex); |
390 | ||
8e84c258 EK |
391 | if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { |
392 | int ch = WCN36XX_HW_CHANNEL(wcn); | |
393 | wcn36xx_dbg(WCN36XX_DBG_MAC, "wcn36xx_config channel switch=%d\n", | |
394 | ch); | |
395 | list_for_each_entry(tmp, &wcn->vif_list, list) { | |
ce75877f | 396 | vif = wcn36xx_priv_to_vif(tmp); |
8e84c258 EK |
397 | wcn36xx_smd_switch_channel(wcn, vif, ch); |
398 | } | |
399 | } | |
400 | ||
0856655a LP |
401 | if (changed & IEEE80211_CONF_CHANGE_PS) { |
402 | list_for_each_entry(tmp, &wcn->vif_list, list) { | |
403 | vif = wcn36xx_priv_to_vif(tmp); | |
404 | if (hw->conf.flags & IEEE80211_CONF_PS) { | |
405 | if (vif->bss_conf.ps) /* ps allowed ? */ | |
406 | wcn36xx_pmc_enter_bmps_state(wcn, vif); | |
407 | } else { | |
408 | wcn36xx_pmc_exit_bmps_state(wcn, vif); | |
409 | } | |
410 | } | |
411 | } | |
412 | ||
39efc7cc BA |
413 | mutex_unlock(&wcn->conf_mutex); |
414 | ||
8e84c258 EK |
415 | return 0; |
416 | } | |
417 | ||
8e84c258 EK |
418 | static void wcn36xx_configure_filter(struct ieee80211_hw *hw, |
419 | unsigned int changed, | |
420 | unsigned int *total, u64 multicast) | |
421 | { | |
20a779ed PF |
422 | struct wcn36xx_hal_rcv_flt_mc_addr_list_type *fp; |
423 | struct wcn36xx *wcn = hw->priv; | |
424 | struct wcn36xx_vif *tmp; | |
425 | struct ieee80211_vif *vif = NULL; | |
426 | ||
8e84c258 EK |
427 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac configure filter\n"); |
428 | ||
39efc7cc BA |
429 | mutex_lock(&wcn->conf_mutex); |
430 | ||
20a779ed PF |
431 | *total &= FIF_ALLMULTI; |
432 | ||
433 | fp = (void *)(unsigned long)multicast; | |
434 | list_for_each_entry(tmp, &wcn->vif_list, list) { | |
435 | vif = wcn36xx_priv_to_vif(tmp); | |
436 | ||
437 | /* FW handles MC filtering only when connected as STA */ | |
438 | if (*total & FIF_ALLMULTI) | |
439 | wcn36xx_smd_set_mc_list(wcn, vif, NULL); | |
440 | else if (NL80211_IFTYPE_STATION == vif->type && tmp->sta_assoc) | |
441 | wcn36xx_smd_set_mc_list(wcn, vif, fp); | |
442 | } | |
39efc7cc BA |
443 | |
444 | mutex_unlock(&wcn->conf_mutex); | |
20a779ed PF |
445 | kfree(fp); |
446 | } | |
447 | ||
448 | static u64 wcn36xx_prepare_multicast(struct ieee80211_hw *hw, | |
449 | struct netdev_hw_addr_list *mc_list) | |
450 | { | |
451 | struct wcn36xx_hal_rcv_flt_mc_addr_list_type *fp; | |
452 | struct netdev_hw_addr *ha; | |
453 | ||
454 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac prepare multicast list\n"); | |
455 | fp = kzalloc(sizeof(*fp), GFP_ATOMIC); | |
456 | if (!fp) { | |
457 | wcn36xx_err("Out of memory setting filters.\n"); | |
458 | return 0; | |
459 | } | |
460 | ||
461 | fp->mc_addr_count = 0; | |
462 | /* update multicast filtering parameters */ | |
463 | if (netdev_hw_addr_list_count(mc_list) <= | |
464 | WCN36XX_HAL_MAX_NUM_MULTICAST_ADDRESS) { | |
465 | netdev_hw_addr_list_for_each(ha, mc_list) { | |
466 | memcpy(fp->mc_addr[fp->mc_addr_count], | |
467 | ha->addr, ETH_ALEN); | |
468 | fp->mc_addr_count++; | |
469 | } | |
470 | } | |
471 | ||
472 | return (u64)(unsigned long)fp; | |
8e84c258 EK |
473 | } |
474 | ||
475 | static void wcn36xx_tx(struct ieee80211_hw *hw, | |
476 | struct ieee80211_tx_control *control, | |
477 | struct sk_buff *skb) | |
478 | { | |
479 | struct wcn36xx *wcn = hw->priv; | |
480 | struct wcn36xx_sta *sta_priv = NULL; | |
481 | ||
482 | if (control->sta) | |
a92e4696 | 483 | sta_priv = wcn36xx_sta_to_priv(control->sta); |
8e84c258 EK |
484 | |
485 | if (wcn36xx_start_tx(wcn, sta_priv, skb)) | |
486 | ieee80211_free_txskb(wcn->hw, skb); | |
487 | } | |
488 | ||
489 | static int wcn36xx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, | |
490 | struct ieee80211_vif *vif, | |
491 | struct ieee80211_sta *sta, | |
492 | struct ieee80211_key_conf *key_conf) | |
493 | { | |
494 | struct wcn36xx *wcn = hw->priv; | |
ce75877f | 495 | struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif); |
216da128 | 496 | struct wcn36xx_sta *sta_priv = sta ? wcn36xx_sta_to_priv(sta) : NULL; |
8e84c258 EK |
497 | int ret = 0; |
498 | u8 key[WLAN_MAX_KEY_LEN]; | |
499 | ||
500 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac80211 set key\n"); | |
501 | wcn36xx_dbg(WCN36XX_DBG_MAC, "Key: cmd=0x%x algo:0x%x, id:%d, len:%d flags 0x%x\n", | |
502 | cmd, key_conf->cipher, key_conf->keyidx, | |
503 | key_conf->keylen, key_conf->flags); | |
504 | wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "KEY: ", | |
505 | key_conf->key, | |
506 | key_conf->keylen); | |
507 | ||
39efc7cc BA |
508 | mutex_lock(&wcn->conf_mutex); |
509 | ||
8e84c258 EK |
510 | switch (key_conf->cipher) { |
511 | case WLAN_CIPHER_SUITE_WEP40: | |
512 | vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP40; | |
513 | break; | |
514 | case WLAN_CIPHER_SUITE_WEP104: | |
10db60b9 | 515 | vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP104; |
8e84c258 EK |
516 | break; |
517 | case WLAN_CIPHER_SUITE_CCMP: | |
518 | vif_priv->encrypt_type = WCN36XX_HAL_ED_CCMP; | |
519 | break; | |
520 | case WLAN_CIPHER_SUITE_TKIP: | |
521 | vif_priv->encrypt_type = WCN36XX_HAL_ED_TKIP; | |
522 | break; | |
523 | default: | |
524 | wcn36xx_err("Unsupported key type 0x%x\n", | |
525 | key_conf->cipher); | |
526 | ret = -EOPNOTSUPP; | |
527 | goto out; | |
528 | } | |
529 | ||
530 | switch (cmd) { | |
531 | case SET_KEY: | |
532 | if (WCN36XX_HAL_ED_TKIP == vif_priv->encrypt_type) { | |
533 | /* | |
534 | * Supplicant is sending key in the wrong order: | |
535 | * Temporal Key (16 b) - TX MIC (8 b) - RX MIC (8 b) | |
536 | * but HW expects it to be in the order as described in | |
537 | * IEEE 802.11 spec (see chapter 11.7) like this: | |
538 | * Temporal Key (16 b) - RX MIC (8 b) - TX MIC (8 b) | |
539 | */ | |
540 | memcpy(key, key_conf->key, 16); | |
541 | memcpy(key + 16, key_conf->key + 24, 8); | |
542 | memcpy(key + 24, key_conf->key + 16, 8); | |
543 | } else { | |
544 | memcpy(key, key_conf->key, key_conf->keylen); | |
545 | } | |
546 | ||
547 | if (IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags) { | |
548 | sta_priv->is_data_encrypted = true; | |
549 | /* Reconfigure bss with encrypt_type */ | |
550 | if (NL80211_IFTYPE_STATION == vif->type) | |
551 | wcn36xx_smd_config_bss(wcn, | |
552 | vif, | |
553 | sta, | |
554 | sta->addr, | |
555 | true); | |
556 | ||
557 | wcn36xx_smd_set_stakey(wcn, | |
558 | vif_priv->encrypt_type, | |
559 | key_conf->keyidx, | |
560 | key_conf->keylen, | |
561 | key, | |
562 | get_sta_index(vif, sta_priv)); | |
563 | } else { | |
564 | wcn36xx_smd_set_bsskey(wcn, | |
565 | vif_priv->encrypt_type, | |
0fc8bb50 | 566 | vif_priv->bss_index, |
8e84c258 EK |
567 | key_conf->keyidx, |
568 | key_conf->keylen, | |
569 | key); | |
e3160542 | 570 | |
8e84c258 EK |
571 | if ((WLAN_CIPHER_SUITE_WEP40 == key_conf->cipher) || |
572 | (WLAN_CIPHER_SUITE_WEP104 == key_conf->cipher)) { | |
216da128 LP |
573 | list_for_each_entry(sta_priv, |
574 | &vif_priv->sta_list, list) { | |
575 | sta_priv->is_data_encrypted = true; | |
576 | wcn36xx_smd_set_stakey(wcn, | |
577 | vif_priv->encrypt_type, | |
578 | key_conf->keyidx, | |
579 | key_conf->keylen, | |
580 | key, | |
581 | get_sta_index(vif, sta_priv)); | |
582 | } | |
8e84c258 EK |
583 | } |
584 | } | |
585 | break; | |
586 | case DISABLE_KEY: | |
587 | if (!(IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags)) { | |
0fc8bb50 DM |
588 | if (vif_priv->bss_index != WCN36XX_HAL_BSS_INVALID_IDX) |
589 | wcn36xx_smd_remove_bsskey(wcn, | |
590 | vif_priv->encrypt_type, | |
591 | vif_priv->bss_index, | |
592 | key_conf->keyidx); | |
593 | ||
2716a8ac | 594 | vif_priv->encrypt_type = WCN36XX_HAL_ED_NONE; |
8e84c258 EK |
595 | } else { |
596 | sta_priv->is_data_encrypted = false; | |
597 | /* do not remove key if disassociated */ | |
598 | if (sta_priv->aid) | |
599 | wcn36xx_smd_remove_stakey(wcn, | |
600 | vif_priv->encrypt_type, | |
601 | key_conf->keyidx, | |
602 | get_sta_index(vif, sta_priv)); | |
603 | } | |
604 | break; | |
605 | default: | |
606 | wcn36xx_err("Unsupported key cmd 0x%x\n", cmd); | |
607 | ret = -EOPNOTSUPP; | |
608 | goto out; | |
8e84c258 EK |
609 | } |
610 | ||
611 | out: | |
39efc7cc BA |
612 | mutex_unlock(&wcn->conf_mutex); |
613 | ||
8e84c258 EK |
614 | return ret; |
615 | } | |
616 | ||
88603903 | 617 | static void wcn36xx_hw_scan_worker(struct work_struct *work) |
8e84c258 | 618 | { |
88603903 BA |
619 | struct wcn36xx *wcn = container_of(work, struct wcn36xx, scan_work); |
620 | struct cfg80211_scan_request *req = wcn->scan_req; | |
621 | u8 channels[WCN36XX_HAL_PNO_MAX_NETW_CHANNELS_EX]; | |
622 | struct cfg80211_scan_info scan_info = {}; | |
03c95dbe | 623 | bool aborted = false; |
88603903 BA |
624 | int i; |
625 | ||
626 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac80211 scan %d channels worker\n", req->n_channels); | |
627 | ||
628 | for (i = 0; i < req->n_channels; i++) | |
629 | channels[i] = req->channels[i]->hw_value; | |
630 | ||
631 | wcn36xx_smd_update_scan_params(wcn, channels, req->n_channels); | |
8e84c258 EK |
632 | |
633 | wcn36xx_smd_init_scan(wcn, HAL_SYS_MODE_SCAN); | |
88603903 | 634 | for (i = 0; i < req->n_channels; i++) { |
03c95dbe BA |
635 | mutex_lock(&wcn->scan_lock); |
636 | aborted = wcn->scan_aborted; | |
637 | mutex_unlock(&wcn->scan_lock); | |
638 | ||
639 | if (aborted) | |
640 | break; | |
641 | ||
88603903 BA |
642 | wcn->scan_freq = req->channels[i]->center_freq; |
643 | wcn->scan_band = req->channels[i]->band; | |
644 | ||
645 | wcn36xx_smd_start_scan(wcn, req->channels[i]->hw_value); | |
646 | msleep(30); | |
647 | wcn36xx_smd_end_scan(wcn, req->channels[i]->hw_value); | |
648 | ||
649 | wcn->scan_freq = 0; | |
650 | } | |
651 | wcn36xx_smd_finish_scan(wcn, HAL_SYS_MODE_SCAN); | |
652 | ||
03c95dbe | 653 | scan_info.aborted = aborted; |
88603903 BA |
654 | ieee80211_scan_completed(wcn->hw, &scan_info); |
655 | ||
656 | mutex_lock(&wcn->scan_lock); | |
657 | wcn->scan_req = NULL; | |
658 | mutex_unlock(&wcn->scan_lock); | |
8e84c258 EK |
659 | } |
660 | ||
88603903 BA |
661 | static int wcn36xx_hw_scan(struct ieee80211_hw *hw, |
662 | struct ieee80211_vif *vif, | |
663 | struct ieee80211_scan_request *hw_req) | |
8e84c258 EK |
664 | { |
665 | struct wcn36xx *wcn = hw->priv; | |
88603903 BA |
666 | mutex_lock(&wcn->scan_lock); |
667 | if (wcn->scan_req) { | |
668 | mutex_unlock(&wcn->scan_lock); | |
669 | return -EBUSY; | |
670 | } | |
03c95dbe BA |
671 | |
672 | wcn->scan_aborted = false; | |
88603903 | 673 | wcn->scan_req = &hw_req->req; |
2f3bef4b | 674 | |
88603903 BA |
675 | mutex_unlock(&wcn->scan_lock); |
676 | ||
2f3bef4b LP |
677 | if (!get_feat_caps(wcn->fw_feat_caps, SCAN_OFFLOAD)) { |
678 | /* legacy manual/sw scan */ | |
679 | schedule_work(&wcn->scan_work); | |
680 | return 0; | |
681 | } | |
88603903 | 682 | |
2f3bef4b | 683 | return wcn36xx_smd_start_hw_scan(wcn, vif, &hw_req->req); |
8e84c258 EK |
684 | } |
685 | ||
03c95dbe BA |
686 | static void wcn36xx_cancel_hw_scan(struct ieee80211_hw *hw, |
687 | struct ieee80211_vif *vif) | |
688 | { | |
689 | struct wcn36xx *wcn = hw->priv; | |
690 | ||
691 | mutex_lock(&wcn->scan_lock); | |
692 | wcn->scan_aborted = true; | |
693 | mutex_unlock(&wcn->scan_lock); | |
694 | ||
89722f57 DM |
695 | if (get_feat_caps(wcn->fw_feat_caps, SCAN_OFFLOAD)) { |
696 | /* ieee80211_scan_completed will be called on FW scan | |
697 | * indication */ | |
698 | wcn36xx_smd_stop_hw_scan(wcn); | |
699 | } else { | |
700 | struct cfg80211_scan_info scan_info = { | |
701 | .aborted = true, | |
702 | }; | |
9bfd05e3 | 703 | |
89722f57 DM |
704 | cancel_work_sync(&wcn->scan_work); |
705 | ieee80211_scan_completed(wcn->hw, &scan_info); | |
706 | } | |
03c95dbe BA |
707 | } |
708 | ||
8e84c258 | 709 | static void wcn36xx_update_allowed_rates(struct ieee80211_sta *sta, |
57fbcce3 | 710 | enum nl80211_band band) |
8e84c258 EK |
711 | { |
712 | int i, size; | |
713 | u16 *rates_table; | |
a92e4696 | 714 | struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(sta); |
8e84c258 EK |
715 | u32 rates = sta->supp_rates[band]; |
716 | ||
717 | memset(&sta_priv->supported_rates, 0, | |
718 | sizeof(sta_priv->supported_rates)); | |
719 | sta_priv->supported_rates.op_rate_mode = STA_11n; | |
720 | ||
721 | size = ARRAY_SIZE(sta_priv->supported_rates.dsss_rates); | |
722 | rates_table = sta_priv->supported_rates.dsss_rates; | |
57fbcce3 | 723 | if (band == NL80211_BAND_2GHZ) { |
8e84c258 EK |
724 | for (i = 0; i < size; i++) { |
725 | if (rates & 0x01) { | |
726 | rates_table[i] = wcn_2ghz_rates[i].hw_value; | |
727 | rates = rates >> 1; | |
728 | } | |
729 | } | |
730 | } | |
731 | ||
732 | size = ARRAY_SIZE(sta_priv->supported_rates.ofdm_rates); | |
733 | rates_table = sta_priv->supported_rates.ofdm_rates; | |
734 | for (i = 0; i < size; i++) { | |
735 | if (rates & 0x01) { | |
736 | rates_table[i] = wcn_5ghz_rates[i].hw_value; | |
737 | rates = rates >> 1; | |
738 | } | |
739 | } | |
740 | ||
741 | if (sta->ht_cap.ht_supported) { | |
742 | BUILD_BUG_ON(sizeof(sta->ht_cap.mcs.rx_mask) > | |
743 | sizeof(sta_priv->supported_rates.supported_mcs_set)); | |
744 | memcpy(sta_priv->supported_rates.supported_mcs_set, | |
745 | sta->ht_cap.mcs.rx_mask, | |
746 | sizeof(sta->ht_cap.mcs.rx_mask)); | |
747 | } | |
748 | } | |
749 | void wcn36xx_set_default_rates(struct wcn36xx_hal_supported_rates *rates) | |
750 | { | |
751 | u16 ofdm_rates[WCN36XX_HAL_NUM_OFDM_RATES] = { | |
752 | HW_RATE_INDEX_6MBPS, | |
753 | HW_RATE_INDEX_9MBPS, | |
754 | HW_RATE_INDEX_12MBPS, | |
755 | HW_RATE_INDEX_18MBPS, | |
756 | HW_RATE_INDEX_24MBPS, | |
757 | HW_RATE_INDEX_36MBPS, | |
758 | HW_RATE_INDEX_48MBPS, | |
759 | HW_RATE_INDEX_54MBPS | |
760 | }; | |
761 | u16 dsss_rates[WCN36XX_HAL_NUM_DSSS_RATES] = { | |
762 | HW_RATE_INDEX_1MBPS, | |
763 | HW_RATE_INDEX_2MBPS, | |
764 | HW_RATE_INDEX_5_5MBPS, | |
765 | HW_RATE_INDEX_11MBPS | |
766 | }; | |
767 | ||
768 | rates->op_rate_mode = STA_11n; | |
769 | memcpy(rates->dsss_rates, dsss_rates, | |
770 | sizeof(*dsss_rates) * WCN36XX_HAL_NUM_DSSS_RATES); | |
771 | memcpy(rates->ofdm_rates, ofdm_rates, | |
772 | sizeof(*ofdm_rates) * WCN36XX_HAL_NUM_OFDM_RATES); | |
773 | rates->supported_mcs_set[0] = 0xFF; | |
774 | } | |
775 | static void wcn36xx_bss_info_changed(struct ieee80211_hw *hw, | |
776 | struct ieee80211_vif *vif, | |
777 | struct ieee80211_bss_conf *bss_conf, | |
778 | u32 changed) | |
779 | { | |
780 | struct wcn36xx *wcn = hw->priv; | |
781 | struct sk_buff *skb = NULL; | |
782 | u16 tim_off, tim_len; | |
783 | enum wcn36xx_hal_link_state link_state; | |
ce75877f | 784 | struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif); |
8e84c258 EK |
785 | |
786 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss info changed vif %p changed 0x%08x\n", | |
787 | vif, changed); | |
788 | ||
39efc7cc BA |
789 | mutex_lock(&wcn->conf_mutex); |
790 | ||
8e84c258 EK |
791 | if (changed & BSS_CHANGED_BEACON_INFO) { |
792 | wcn36xx_dbg(WCN36XX_DBG_MAC, | |
793 | "mac bss changed dtim period %d\n", | |
794 | bss_conf->dtim_period); | |
795 | ||
796 | vif_priv->dtim_period = bss_conf->dtim_period; | |
797 | } | |
798 | ||
8e84c258 EK |
799 | if (changed & BSS_CHANGED_BSSID) { |
800 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed_bssid %pM\n", | |
801 | bss_conf->bssid); | |
802 | ||
803 | if (!is_zero_ether_addr(bss_conf->bssid)) { | |
804 | vif_priv->is_joining = true; | |
90023c03 | 805 | vif_priv->bss_index = WCN36XX_HAL_BSS_INVALID_IDX; |
2a46c829 DM |
806 | wcn36xx_smd_set_link_st(wcn, bss_conf->bssid, vif->addr, |
807 | WCN36XX_HAL_LINK_PREASSOC_STATE); | |
8e84c258 EK |
808 | wcn36xx_smd_join(wcn, bss_conf->bssid, |
809 | vif->addr, WCN36XX_HW_CHANNEL(wcn)); | |
810 | wcn36xx_smd_config_bss(wcn, vif, NULL, | |
811 | bss_conf->bssid, false); | |
812 | } else { | |
813 | vif_priv->is_joining = false; | |
814 | wcn36xx_smd_delete_bss(wcn, vif); | |
2a46c829 DM |
815 | wcn36xx_smd_set_link_st(wcn, bss_conf->bssid, vif->addr, |
816 | WCN36XX_HAL_LINK_IDLE_STATE); | |
2716a8ac | 817 | vif_priv->encrypt_type = WCN36XX_HAL_ED_NONE; |
8e84c258 EK |
818 | } |
819 | } | |
820 | ||
821 | if (changed & BSS_CHANGED_SSID) { | |
822 | wcn36xx_dbg(WCN36XX_DBG_MAC, | |
823 | "mac bss changed ssid\n"); | |
824 | wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "ssid ", | |
825 | bss_conf->ssid, bss_conf->ssid_len); | |
826 | ||
827 | vif_priv->ssid.length = bss_conf->ssid_len; | |
828 | memcpy(&vif_priv->ssid.ssid, | |
829 | bss_conf->ssid, | |
830 | bss_conf->ssid_len); | |
831 | } | |
832 | ||
833 | if (changed & BSS_CHANGED_ASSOC) { | |
834 | vif_priv->is_joining = false; | |
835 | if (bss_conf->assoc) { | |
836 | struct ieee80211_sta *sta; | |
837 | struct wcn36xx_sta *sta_priv; | |
838 | ||
839 | wcn36xx_dbg(WCN36XX_DBG_MAC, | |
840 | "mac assoc bss %pM vif %pM AID=%d\n", | |
841 | bss_conf->bssid, | |
842 | vif->addr, | |
843 | bss_conf->aid); | |
844 | ||
043ce546 | 845 | vif_priv->sta_assoc = true; |
39efc7cc BA |
846 | |
847 | /* | |
848 | * Holding conf_mutex ensures mutal exclusion with | |
849 | * wcn36xx_sta_remove() and as such ensures that sta | |
850 | * won't be freed while we're operating on it. As such | |
851 | * we do not need to hold the rcu_read_lock(). | |
852 | */ | |
8e84c258 EK |
853 | sta = ieee80211_find_sta(vif, bss_conf->bssid); |
854 | if (!sta) { | |
855 | wcn36xx_err("sta %pM is not found\n", | |
856 | bss_conf->bssid); | |
8e84c258 EK |
857 | goto out; |
858 | } | |
a92e4696 | 859 | sta_priv = wcn36xx_sta_to_priv(sta); |
8e84c258 EK |
860 | |
861 | wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn)); | |
862 | ||
863 | wcn36xx_smd_set_link_st(wcn, bss_conf->bssid, | |
864 | vif->addr, | |
865 | WCN36XX_HAL_LINK_POSTASSOC_STATE); | |
866 | wcn36xx_smd_config_bss(wcn, vif, sta, | |
867 | bss_conf->bssid, | |
868 | true); | |
869 | sta_priv->aid = bss_conf->aid; | |
870 | /* | |
871 | * config_sta must be called from because this is the | |
872 | * place where AID is available. | |
873 | */ | |
874 | wcn36xx_smd_config_sta(wcn, vif, sta); | |
8e84c258 EK |
875 | } else { |
876 | wcn36xx_dbg(WCN36XX_DBG_MAC, | |
877 | "disassociated bss %pM vif %pM AID=%d\n", | |
878 | bss_conf->bssid, | |
879 | vif->addr, | |
880 | bss_conf->aid); | |
043ce546 | 881 | vif_priv->sta_assoc = false; |
8e84c258 EK |
882 | wcn36xx_smd_set_link_st(wcn, |
883 | bss_conf->bssid, | |
884 | vif->addr, | |
885 | WCN36XX_HAL_LINK_IDLE_STATE); | |
886 | } | |
887 | } | |
888 | ||
889 | if (changed & BSS_CHANGED_AP_PROBE_RESP) { | |
890 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed ap probe resp\n"); | |
891 | skb = ieee80211_proberesp_get(hw, vif); | |
892 | if (!skb) { | |
893 | wcn36xx_err("failed to alloc probereq skb\n"); | |
894 | goto out; | |
895 | } | |
896 | ||
897 | wcn36xx_smd_update_proberesp_tmpl(wcn, vif, skb); | |
898 | dev_kfree_skb(skb); | |
899 | } | |
900 | ||
b3e3f871 CYY |
901 | if (changed & BSS_CHANGED_BEACON_ENABLED || |
902 | changed & BSS_CHANGED_BEACON) { | |
8e84c258 EK |
903 | wcn36xx_dbg(WCN36XX_DBG_MAC, |
904 | "mac bss changed beacon enabled %d\n", | |
905 | bss_conf->enable_beacon); | |
906 | ||
907 | if (bss_conf->enable_beacon) { | |
908628db | 908 | vif_priv->dtim_period = bss_conf->dtim_period; |
90023c03 | 909 | vif_priv->bss_index = WCN36XX_HAL_BSS_INVALID_IDX; |
8e84c258 EK |
910 | wcn36xx_smd_config_bss(wcn, vif, NULL, |
911 | vif->addr, false); | |
912 | skb = ieee80211_beacon_get_tim(hw, vif, &tim_off, | |
913 | &tim_len); | |
914 | if (!skb) { | |
915 | wcn36xx_err("failed to alloc beacon skb\n"); | |
916 | goto out; | |
917 | } | |
918 | wcn36xx_smd_send_beacon(wcn, vif, skb, tim_off, 0); | |
919 | dev_kfree_skb(skb); | |
920 | ||
921 | if (vif->type == NL80211_IFTYPE_ADHOC || | |
922 | vif->type == NL80211_IFTYPE_MESH_POINT) | |
923 | link_state = WCN36XX_HAL_LINK_IBSS_STATE; | |
924 | else | |
925 | link_state = WCN36XX_HAL_LINK_AP_STATE; | |
926 | ||
927 | wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr, | |
928 | link_state); | |
929 | } else { | |
5443918d | 930 | wcn36xx_smd_delete_bss(wcn, vif); |
8e84c258 EK |
931 | wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr, |
932 | WCN36XX_HAL_LINK_IDLE_STATE); | |
8e84c258 EK |
933 | } |
934 | } | |
935 | out: | |
39efc7cc BA |
936 | |
937 | mutex_unlock(&wcn->conf_mutex); | |
8e84c258 EK |
938 | } |
939 | ||
940 | /* this is required when using IEEE80211_HW_HAS_RATE_CONTROL */ | |
941 | static int wcn36xx_set_rts_threshold(struct ieee80211_hw *hw, u32 value) | |
942 | { | |
943 | struct wcn36xx *wcn = hw->priv; | |
944 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac set RTS threshold %d\n", value); | |
945 | ||
39efc7cc | 946 | mutex_lock(&wcn->conf_mutex); |
8e84c258 | 947 | wcn36xx_smd_update_cfg(wcn, WCN36XX_HAL_CFG_RTS_THRESHOLD, value); |
39efc7cc BA |
948 | mutex_unlock(&wcn->conf_mutex); |
949 | ||
8e84c258 EK |
950 | return 0; |
951 | } | |
952 | ||
953 | static void wcn36xx_remove_interface(struct ieee80211_hw *hw, | |
954 | struct ieee80211_vif *vif) | |
955 | { | |
956 | struct wcn36xx *wcn = hw->priv; | |
ce75877f | 957 | struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif); |
8e84c258 EK |
958 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac remove interface vif %p\n", vif); |
959 | ||
39efc7cc BA |
960 | mutex_lock(&wcn->conf_mutex); |
961 | ||
8e84c258 EK |
962 | list_del(&vif_priv->list); |
963 | wcn36xx_smd_delete_sta_self(wcn, vif->addr); | |
39efc7cc BA |
964 | |
965 | mutex_unlock(&wcn->conf_mutex); | |
8e84c258 EK |
966 | } |
967 | ||
968 | static int wcn36xx_add_interface(struct ieee80211_hw *hw, | |
969 | struct ieee80211_vif *vif) | |
970 | { | |
971 | struct wcn36xx *wcn = hw->priv; | |
ce75877f | 972 | struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif); |
8e84c258 EK |
973 | |
974 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac add interface vif %p type %d\n", | |
975 | vif, vif->type); | |
976 | ||
977 | if (!(NL80211_IFTYPE_STATION == vif->type || | |
978 | NL80211_IFTYPE_AP == vif->type || | |
979 | NL80211_IFTYPE_ADHOC == vif->type || | |
980 | NL80211_IFTYPE_MESH_POINT == vif->type)) { | |
981 | wcn36xx_warn("Unsupported interface type requested: %d\n", | |
982 | vif->type); | |
983 | return -EOPNOTSUPP; | |
984 | } | |
985 | ||
39efc7cc BA |
986 | mutex_lock(&wcn->conf_mutex); |
987 | ||
2edfcf2b | 988 | vif_priv->bss_index = WCN36XX_HAL_BSS_INVALID_IDX; |
e3160542 | 989 | INIT_LIST_HEAD(&vif_priv->sta_list); |
8e84c258 EK |
990 | list_add(&vif_priv->list, &wcn->vif_list); |
991 | wcn36xx_smd_add_sta_self(wcn, vif); | |
992 | ||
39efc7cc BA |
993 | mutex_unlock(&wcn->conf_mutex); |
994 | ||
8e84c258 EK |
995 | return 0; |
996 | } | |
997 | ||
998 | static int wcn36xx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, | |
999 | struct ieee80211_sta *sta) | |
1000 | { | |
1001 | struct wcn36xx *wcn = hw->priv; | |
ce75877f | 1002 | struct wcn36xx_vif *vif_priv = wcn36xx_vif_to_priv(vif); |
a92e4696 | 1003 | struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(sta); |
8e84c258 EK |
1004 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta add vif %p sta %pM\n", |
1005 | vif, sta->addr); | |
1006 | ||
39efc7cc BA |
1007 | mutex_lock(&wcn->conf_mutex); |
1008 | ||
e26dc173 | 1009 | spin_lock_init(&sta_priv->ampdu_lock); |
8e84c258 | 1010 | sta_priv->vif = vif_priv; |
e3160542 LP |
1011 | list_add(&sta_priv->list, &vif_priv->sta_list); |
1012 | ||
8e84c258 EK |
1013 | /* |
1014 | * For STA mode HW will be configured on BSS_CHANGED_ASSOC because | |
1015 | * at this stage AID is not available yet. | |
1016 | */ | |
1017 | if (NL80211_IFTYPE_STATION != vif->type) { | |
1018 | wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn)); | |
1019 | sta_priv->aid = sta->aid; | |
1020 | wcn36xx_smd_config_sta(wcn, vif, sta); | |
1021 | } | |
39efc7cc BA |
1022 | |
1023 | mutex_unlock(&wcn->conf_mutex); | |
1024 | ||
8e84c258 EK |
1025 | return 0; |
1026 | } | |
1027 | ||
1028 | static int wcn36xx_sta_remove(struct ieee80211_hw *hw, | |
1029 | struct ieee80211_vif *vif, | |
1030 | struct ieee80211_sta *sta) | |
1031 | { | |
1032 | struct wcn36xx *wcn = hw->priv; | |
a92e4696 | 1033 | struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(sta); |
8e84c258 EK |
1034 | |
1035 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta remove vif %p sta %pM index %d\n", | |
1036 | vif, sta->addr, sta_priv->sta_index); | |
1037 | ||
39efc7cc BA |
1038 | mutex_lock(&wcn->conf_mutex); |
1039 | ||
e3160542 | 1040 | list_del(&sta_priv->list); |
8e84c258 | 1041 | wcn36xx_smd_delete_sta(wcn, sta_priv->sta_index); |
8e84c258 | 1042 | sta_priv->vif = NULL; |
39efc7cc BA |
1043 | |
1044 | mutex_unlock(&wcn->conf_mutex); | |
1045 | ||
8e84c258 EK |
1046 | return 0; |
1047 | } | |
1048 | ||
1049 | #ifdef CONFIG_PM | |
1050 | ||
1051 | static int wcn36xx_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wow) | |
1052 | { | |
1053 | struct wcn36xx *wcn = hw->priv; | |
1054 | ||
1055 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac suspend\n"); | |
1056 | ||
1057 | flush_workqueue(wcn->hal_ind_wq); | |
1058 | wcn36xx_smd_set_power_params(wcn, true); | |
1059 | return 0; | |
1060 | } | |
1061 | ||
1062 | static int wcn36xx_resume(struct ieee80211_hw *hw) | |
1063 | { | |
1064 | struct wcn36xx *wcn = hw->priv; | |
1065 | ||
1066 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac resume\n"); | |
1067 | ||
1068 | flush_workqueue(wcn->hal_ind_wq); | |
1069 | wcn36xx_smd_set_power_params(wcn, false); | |
1070 | return 0; | |
1071 | } | |
1072 | ||
1073 | #endif | |
1074 | ||
1075 | static int wcn36xx_ampdu_action(struct ieee80211_hw *hw, | |
1076 | struct ieee80211_vif *vif, | |
50ea05ef | 1077 | struct ieee80211_ampdu_params *params) |
8e84c258 EK |
1078 | { |
1079 | struct wcn36xx *wcn = hw->priv; | |
a92e4696 | 1080 | struct wcn36xx_sta *sta_priv = wcn36xx_sta_to_priv(params->sta); |
50ea05ef SS |
1081 | struct ieee80211_sta *sta = params->sta; |
1082 | enum ieee80211_ampdu_mlme_action action = params->action; | |
1083 | u16 tid = params->tid; | |
1084 | u16 *ssn = ¶ms->ssn; | |
2ce113de | 1085 | int ret = 0; |
8e84c258 EK |
1086 | |
1087 | wcn36xx_dbg(WCN36XX_DBG_MAC, "mac ampdu action action %d tid %d\n", | |
1088 | action, tid); | |
1089 | ||
39efc7cc BA |
1090 | mutex_lock(&wcn->conf_mutex); |
1091 | ||
8e84c258 EK |
1092 | switch (action) { |
1093 | case IEEE80211_AMPDU_RX_START: | |
1094 | sta_priv->tid = tid; | |
1095 | wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 0, | |
1096 | get_sta_index(vif, sta_priv)); | |
1097 | wcn36xx_smd_add_ba(wcn); | |
1098 | wcn36xx_smd_trigger_ba(wcn, get_sta_index(vif, sta_priv)); | |
8e84c258 EK |
1099 | break; |
1100 | case IEEE80211_AMPDU_RX_STOP: | |
1101 | wcn36xx_smd_del_ba(wcn, tid, get_sta_index(vif, sta_priv)); | |
1102 | break; | |
1103 | case IEEE80211_AMPDU_TX_START: | |
e26dc173 BC |
1104 | spin_lock_bh(&sta_priv->ampdu_lock); |
1105 | sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_START; | |
1106 | spin_unlock_bh(&sta_priv->ampdu_lock); | |
1107 | ||
2ce113de | 1108 | ret = IEEE80211_AMPDU_TX_START_IMMEDIATE; |
8e84c258 EK |
1109 | break; |
1110 | case IEEE80211_AMPDU_TX_OPERATIONAL: | |
e26dc173 BC |
1111 | spin_lock_bh(&sta_priv->ampdu_lock); |
1112 | sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_OPERATIONAL; | |
1113 | spin_unlock_bh(&sta_priv->ampdu_lock); | |
1114 | ||
8e84c258 EK |
1115 | wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 1, |
1116 | get_sta_index(vif, sta_priv)); | |
1117 | break; | |
1118 | case IEEE80211_AMPDU_TX_STOP_FLUSH: | |
1119 | case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: | |
1120 | case IEEE80211_AMPDU_TX_STOP_CONT: | |
e26dc173 BC |
1121 | spin_lock_bh(&sta_priv->ampdu_lock); |
1122 | sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_NONE; | |
1123 | spin_unlock_bh(&sta_priv->ampdu_lock); | |
1124 | ||
8e84c258 EK |
1125 | ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); |
1126 | break; | |
1127 | default: | |
1128 | wcn36xx_err("Unknown AMPDU action\n"); | |
1129 | } | |
1130 | ||
39efc7cc BA |
1131 | mutex_unlock(&wcn->conf_mutex); |
1132 | ||
2ce113de | 1133 | return ret; |
8e84c258 EK |
1134 | } |
1135 | ||
1136 | static const struct ieee80211_ops wcn36xx_ops = { | |
1137 | .start = wcn36xx_start, | |
1138 | .stop = wcn36xx_stop, | |
1139 | .add_interface = wcn36xx_add_interface, | |
1140 | .remove_interface = wcn36xx_remove_interface, | |
1141 | #ifdef CONFIG_PM | |
1142 | .suspend = wcn36xx_suspend, | |
1143 | .resume = wcn36xx_resume, | |
1144 | #endif | |
1145 | .config = wcn36xx_config, | |
20a779ed | 1146 | .prepare_multicast = wcn36xx_prepare_multicast, |
8e84c258 EK |
1147 | .configure_filter = wcn36xx_configure_filter, |
1148 | .tx = wcn36xx_tx, | |
1149 | .set_key = wcn36xx_set_key, | |
88603903 | 1150 | .hw_scan = wcn36xx_hw_scan, |
03c95dbe | 1151 | .cancel_hw_scan = wcn36xx_cancel_hw_scan, |
8e84c258 EK |
1152 | .bss_info_changed = wcn36xx_bss_info_changed, |
1153 | .set_rts_threshold = wcn36xx_set_rts_threshold, | |
1154 | .sta_add = wcn36xx_sta_add, | |
1155 | .sta_remove = wcn36xx_sta_remove, | |
1156 | .ampdu_action = wcn36xx_ampdu_action, | |
87f825e6 EI |
1157 | |
1158 | CFG80211_TESTMODE_CMD(wcn36xx_tm_cmd) | |
8e84c258 EK |
1159 | }; |
1160 | ||
1161 | static int wcn36xx_init_ieee80211(struct wcn36xx *wcn) | |
1162 | { | |
8e84c258 EK |
1163 | static const u32 cipher_suites[] = { |
1164 | WLAN_CIPHER_SUITE_WEP40, | |
1165 | WLAN_CIPHER_SUITE_WEP104, | |
1166 | WLAN_CIPHER_SUITE_TKIP, | |
1167 | WLAN_CIPHER_SUITE_CCMP, | |
1168 | }; | |
1169 | ||
30686bf7 JB |
1170 | ieee80211_hw_set(wcn->hw, TIMING_BEACON_ONLY); |
1171 | ieee80211_hw_set(wcn->hw, AMPDU_AGGREGATION); | |
1172 | ieee80211_hw_set(wcn->hw, CONNECTION_MONITOR); | |
1173 | ieee80211_hw_set(wcn->hw, SUPPORTS_PS); | |
1174 | ieee80211_hw_set(wcn->hw, SIGNAL_DBM); | |
1175 | ieee80211_hw_set(wcn->hw, HAS_RATE_CONTROL); | |
88603903 | 1176 | ieee80211_hw_set(wcn->hw, SINGLE_SCAN_ON_ALL_BANDS); |
8e84c258 EK |
1177 | |
1178 | wcn->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | | |
1179 | BIT(NL80211_IFTYPE_AP) | | |
1180 | BIT(NL80211_IFTYPE_ADHOC) | | |
1181 | BIT(NL80211_IFTYPE_MESH_POINT); | |
1182 | ||
57fbcce3 | 1183 | wcn->hw->wiphy->bands[NL80211_BAND_2GHZ] = &wcn_band_2ghz; |
fd52bdae LP |
1184 | if (wcn->rf_id != RF_IRIS_WCN3620) |
1185 | wcn->hw->wiphy->bands[NL80211_BAND_5GHZ] = &wcn_band_5ghz; | |
8e84c258 | 1186 | |
88603903 BA |
1187 | wcn->hw->wiphy->max_scan_ssids = WCN36XX_MAX_SCAN_SSIDS; |
1188 | wcn->hw->wiphy->max_scan_ie_len = WCN36XX_MAX_SCAN_IE_LEN; | |
1189 | ||
8e84c258 EK |
1190 | wcn->hw->wiphy->cipher_suites = cipher_suites; |
1191 | wcn->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); | |
1192 | ||
8e84c258 EK |
1193 | #ifdef CONFIG_PM |
1194 | wcn->hw->wiphy->wowlan = &wowlan_support; | |
1195 | #endif | |
1196 | ||
1197 | wcn->hw->max_listen_interval = 200; | |
1198 | ||
1199 | wcn->hw->queues = 4; | |
1200 | ||
1201 | SET_IEEE80211_DEV(wcn->hw, wcn->dev); | |
1202 | ||
1203 | wcn->hw->sta_data_size = sizeof(struct wcn36xx_sta); | |
1204 | wcn->hw->vif_data_size = sizeof(struct wcn36xx_vif); | |
1205 | ||
ae44b502 AZ |
1206 | wiphy_ext_feature_set(wcn->hw->wiphy, |
1207 | NL80211_EXT_FEATURE_CQM_RSSI_LIST); | |
1208 | ||
f0eea277 | 1209 | return 0; |
8e84c258 EK |
1210 | } |
1211 | ||
1212 | static int wcn36xx_platform_get_resources(struct wcn36xx *wcn, | |
1213 | struct platform_device *pdev) | |
1214 | { | |
05ddce49 | 1215 | struct device_node *mmio_node; |
fd52bdae | 1216 | struct device_node *iris_node; |
8e84c258 | 1217 | struct resource *res; |
05ddce49 BA |
1218 | int index; |
1219 | int ret; | |
1220 | ||
8e84c258 | 1221 | /* Set TX IRQ */ |
f303a931 | 1222 | res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "tx"); |
8e84c258 EK |
1223 | if (!res) { |
1224 | wcn36xx_err("failed to get tx_irq\n"); | |
1225 | return -ENOENT; | |
1226 | } | |
1227 | wcn->tx_irq = res->start; | |
1228 | ||
1229 | /* Set RX IRQ */ | |
f303a931 | 1230 | res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "rx"); |
8e84c258 EK |
1231 | if (!res) { |
1232 | wcn36xx_err("failed to get rx_irq\n"); | |
1233 | return -ENOENT; | |
1234 | } | |
1235 | wcn->rx_irq = res->start; | |
1236 | ||
f303a931 BA |
1237 | /* Acquire SMSM tx enable handle */ |
1238 | wcn->tx_enable_state = qcom_smem_state_get(&pdev->dev, | |
1239 | "tx-enable", &wcn->tx_enable_state_bit); | |
1240 | if (IS_ERR(wcn->tx_enable_state)) { | |
1241 | wcn36xx_err("failed to get tx-enable state\n"); | |
1242 | return PTR_ERR(wcn->tx_enable_state); | |
1243 | } | |
1244 | ||
1245 | /* Acquire SMSM tx rings empty handle */ | |
1246 | wcn->tx_rings_empty_state = qcom_smem_state_get(&pdev->dev, | |
1247 | "tx-rings-empty", &wcn->tx_rings_empty_state_bit); | |
1248 | if (IS_ERR(wcn->tx_rings_empty_state)) { | |
1249 | wcn36xx_err("failed to get tx-rings-empty state\n"); | |
1250 | return PTR_ERR(wcn->tx_rings_empty_state); | |
1251 | } | |
1252 | ||
05ddce49 BA |
1253 | mmio_node = of_parse_phandle(pdev->dev.parent->of_node, "qcom,mmio", 0); |
1254 | if (!mmio_node) { | |
1255 | wcn36xx_err("failed to acquire qcom,mmio reference\n"); | |
1256 | return -EINVAL; | |
1257 | } | |
1258 | ||
6f10b4e1 BA |
1259 | wcn->is_pronto = !!of_device_is_compatible(mmio_node, "qcom,pronto"); |
1260 | ||
05ddce49 BA |
1261 | /* Map the CCU memory */ |
1262 | index = of_property_match_string(mmio_node, "reg-names", "ccu"); | |
1263 | wcn->ccu_base = of_iomap(mmio_node, index); | |
1264 | if (!wcn->ccu_base) { | |
1265 | wcn36xx_err("failed to map ccu memory\n"); | |
1266 | ret = -ENOMEM; | |
1267 | goto put_mmio_node; | |
8e84c258 | 1268 | } |
05ddce49 BA |
1269 | |
1270 | /* Map the DXE memory */ | |
1271 | index = of_property_match_string(mmio_node, "reg-names", "dxe"); | |
1272 | wcn->dxe_base = of_iomap(mmio_node, index); | |
1273 | if (!wcn->dxe_base) { | |
1274 | wcn36xx_err("failed to map dxe memory\n"); | |
1275 | ret = -ENOMEM; | |
1276 | goto unmap_ccu; | |
8e84c258 | 1277 | } |
05ddce49 | 1278 | |
fd52bdae | 1279 | /* External RF module */ |
1967c128 | 1280 | iris_node = of_get_child_by_name(mmio_node, "iris"); |
fd52bdae LP |
1281 | if (iris_node) { |
1282 | if (of_device_is_compatible(iris_node, "qcom,wcn3620")) | |
1283 | wcn->rf_id = RF_IRIS_WCN3620; | |
1284 | of_node_put(iris_node); | |
1285 | } | |
1286 | ||
05ddce49 | 1287 | of_node_put(mmio_node); |
8e84c258 | 1288 | return 0; |
05ddce49 BA |
1289 | |
1290 | unmap_ccu: | |
1291 | iounmap(wcn->ccu_base); | |
1292 | put_mmio_node: | |
1293 | of_node_put(mmio_node); | |
1294 | return ret; | |
8e84c258 EK |
1295 | } |
1296 | ||
1297 | static int wcn36xx_probe(struct platform_device *pdev) | |
1298 | { | |
1299 | struct ieee80211_hw *hw; | |
1300 | struct wcn36xx *wcn; | |
f303a931 | 1301 | void *wcnss; |
8e84c258 | 1302 | int ret; |
f303a931 | 1303 | const u8 *addr; |
8e84c258 EK |
1304 | |
1305 | wcn36xx_dbg(WCN36XX_DBG_MAC, "platform probe\n"); | |
1306 | ||
f303a931 BA |
1307 | wcnss = dev_get_drvdata(pdev->dev.parent); |
1308 | ||
8e84c258 EK |
1309 | hw = ieee80211_alloc_hw(sizeof(struct wcn36xx), &wcn36xx_ops); |
1310 | if (!hw) { | |
1311 | wcn36xx_err("failed to alloc hw\n"); | |
1312 | ret = -ENOMEM; | |
1313 | goto out_err; | |
1314 | } | |
1315 | platform_set_drvdata(pdev, hw); | |
1316 | wcn = hw->priv; | |
1317 | wcn->hw = hw; | |
1318 | wcn->dev = &pdev->dev; | |
6b8a127b | 1319 | wcn->first_boot = true; |
39efc7cc | 1320 | mutex_init(&wcn->conf_mutex); |
8e84c258 | 1321 | mutex_init(&wcn->hal_mutex); |
88603903 BA |
1322 | mutex_init(&wcn->scan_lock); |
1323 | ||
57e06e0e DM |
1324 | ret = dma_set_mask_and_coherent(wcn->dev, DMA_BIT_MASK(32)); |
1325 | if (ret < 0) { | |
1326 | wcn36xx_err("failed to set DMA mask: %d\n", ret); | |
1327 | goto out_wq; | |
1328 | } | |
1329 | ||
88603903 | 1330 | INIT_WORK(&wcn->scan_work, wcn36xx_hw_scan_worker); |
8e84c258 | 1331 | |
5052de8d | 1332 | wcn->smd_channel = qcom_wcnss_open_channel(wcnss, "WLAN_CTRL", wcn36xx_smd_rsp_process, hw); |
f303a931 BA |
1333 | if (IS_ERR(wcn->smd_channel)) { |
1334 | wcn36xx_err("failed to open WLAN_CTRL channel\n"); | |
1335 | ret = PTR_ERR(wcn->smd_channel); | |
1336 | goto out_wq; | |
1337 | } | |
1338 | ||
f303a931 BA |
1339 | addr = of_get_property(pdev->dev.of_node, "local-mac-address", &ret); |
1340 | if (addr && ret != ETH_ALEN) { | |
1341 | wcn36xx_err("invalid local-mac-address\n"); | |
1342 | ret = -EINVAL; | |
1343 | goto out_wq; | |
1344 | } else if (addr) { | |
8e84c258 EK |
1345 | wcn36xx_info("mac address: %pM\n", addr); |
1346 | SET_IEEE80211_PERM_ADDR(wcn->hw, addr); | |
1347 | } | |
1348 | ||
1349 | ret = wcn36xx_platform_get_resources(wcn, pdev); | |
1350 | if (ret) | |
1351 | goto out_wq; | |
1352 | ||
1353 | wcn36xx_init_ieee80211(wcn); | |
1354 | ret = ieee80211_register_hw(wcn->hw); | |
1355 | if (ret) | |
1356 | goto out_unmap; | |
1357 | ||
1358 | return 0; | |
1359 | ||
1360 | out_unmap: | |
05ddce49 BA |
1361 | iounmap(wcn->ccu_base); |
1362 | iounmap(wcn->dxe_base); | |
8e84c258 EK |
1363 | out_wq: |
1364 | ieee80211_free_hw(hw); | |
1365 | out_err: | |
1366 | return ret; | |
1367 | } | |
f303a931 | 1368 | |
8e84c258 EK |
1369 | static int wcn36xx_remove(struct platform_device *pdev) |
1370 | { | |
1371 | struct ieee80211_hw *hw = platform_get_drvdata(pdev); | |
1372 | struct wcn36xx *wcn = hw->priv; | |
1373 | wcn36xx_dbg(WCN36XX_DBG_MAC, "platform remove\n"); | |
1374 | ||
4bda7faf | 1375 | release_firmware(wcn->nv); |
8e84c258 EK |
1376 | |
1377 | ieee80211_unregister_hw(hw); | |
f303a931 BA |
1378 | |
1379 | qcom_smem_state_put(wcn->tx_enable_state); | |
1380 | qcom_smem_state_put(wcn->tx_rings_empty_state); | |
1381 | ||
efad8396 BA |
1382 | rpmsg_destroy_ept(wcn->smd_channel); |
1383 | ||
05ddce49 BA |
1384 | iounmap(wcn->dxe_base); |
1385 | iounmap(wcn->ccu_base); | |
d5362888 BA |
1386 | |
1387 | mutex_destroy(&wcn->hal_mutex); | |
8e84c258 EK |
1388 | ieee80211_free_hw(hw); |
1389 | ||
1390 | return 0; | |
1391 | } | |
f303a931 BA |
1392 | |
1393 | static const struct of_device_id wcn36xx_of_match[] = { | |
1394 | { .compatible = "qcom,wcnss-wlan" }, | |
8e84c258 EK |
1395 | {} |
1396 | }; | |
f303a931 | 1397 | MODULE_DEVICE_TABLE(of, wcn36xx_of_match); |
8e84c258 EK |
1398 | |
1399 | static struct platform_driver wcn36xx_driver = { | |
1400 | .probe = wcn36xx_probe, | |
1401 | .remove = wcn36xx_remove, | |
1402 | .driver = { | |
1403 | .name = "wcn36xx", | |
f303a931 | 1404 | .of_match_table = wcn36xx_of_match, |
8e84c258 | 1405 | }, |
8e84c258 EK |
1406 | }; |
1407 | ||
f303a931 | 1408 | module_platform_driver(wcn36xx_driver); |
8e84c258 EK |
1409 | |
1410 | MODULE_LICENSE("Dual BSD/GPL"); | |
1411 | MODULE_AUTHOR("Eugene Krasnikov k.eugene.e@gmail.com"); | |
1412 | MODULE_FIRMWARE(WLAN_NV_FILE); |