xref: /openbmc/linux/drivers/net/wireless/ti/wlcore/main.c (revision a251c17a)
12b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
27b3115f2SLuciano Coelho /*
38f6ac537SLuciano Coelho  * This file is part of wlcore
47b3115f2SLuciano Coelho  *
57b3115f2SLuciano Coelho  * Copyright (C) 2008-2010 Nokia Corporation
68f6ac537SLuciano Coelho  * Copyright (C) 2011-2013 Texas Instruments Inc.
77b3115f2SLuciano Coelho  */
87b3115f2SLuciano Coelho 
97b3115f2SLuciano Coelho #include <linux/module.h>
107b3115f2SLuciano Coelho #include <linux/firmware.h>
117b3115f2SLuciano Coelho #include <linux/etherdevice.h>
127b3115f2SLuciano Coelho #include <linux/vmalloc.h>
137b3115f2SLuciano Coelho #include <linux/interrupt.h>
146f921fabSLuciano Coelho #include <linux/irq.h>
15fa2648a3STony Lindgren #include <linux/pm_runtime.h>
163c83dd57STony Lindgren #include <linux/pm_wakeirq.h>
177b3115f2SLuciano Coelho 
18c31be25aSLuciano Coelho #include "wlcore.h"
197b3115f2SLuciano Coelho #include "debug.h"
207b3115f2SLuciano Coelho #include "wl12xx_80211.h"
217b3115f2SLuciano Coelho #include "io.h"
227b3115f2SLuciano Coelho #include "tx.h"
237b3115f2SLuciano Coelho #include "ps.h"
247b3115f2SLuciano Coelho #include "init.h"
257b3115f2SLuciano Coelho #include "debugfs.h"
267b3115f2SLuciano Coelho #include "testmode.h"
27d8c5a48dSEliad Peller #include "vendor_cmd.h"
287b3115f2SLuciano Coelho #include "scan.h"
2953d67a50SArik Nemtsov #include "hw_ops.h"
3033cab57aSLuciano Coelho #include "sysfs.h"
317b3115f2SLuciano Coelho 
327b3115f2SLuciano Coelho #define WL1271_BOOT_RETRIES 3
33fa2648a3STony Lindgren #define WL1271_WAKEUP_TIMEOUT 500
347b3115f2SLuciano Coelho 
357b3115f2SLuciano Coelho static char *fwlog_param;
3693ac8488SIdo Reis static int fwlog_mem_blocks = -1;
377230341fSYair Shapira static int bug_on_recovery = -1;
387230341fSYair Shapira static int no_recovery     = -1;
397b3115f2SLuciano Coelho 
407b3115f2SLuciano Coelho static void __wl1271_op_remove_interface(struct wl1271 *wl,
417b3115f2SLuciano Coelho 					 struct ieee80211_vif *vif,
427b3115f2SLuciano Coelho 					 bool reset_tx_queues);
43c24ec83bSIdo Yariv static void wlcore_op_stop_locked(struct wl1271 *wl);
447b3115f2SLuciano Coelho static void wl1271_free_ap_keys(struct wl1271 *wl, struct wl12xx_vif *wlvif);
457b3115f2SLuciano Coelho 
wl12xx_set_authorized(struct wl1271 * wl,struct wl12xx_vif * wlvif)468f6ac537SLuciano Coelho static int wl12xx_set_authorized(struct wl1271 *wl, struct wl12xx_vif *wlvif)
477b3115f2SLuciano Coelho {
487b3115f2SLuciano Coelho 	int ret;
497b3115f2SLuciano Coelho 
507b3115f2SLuciano Coelho 	if (WARN_ON(wlvif->bss_type != BSS_TYPE_STA_BSS))
517b3115f2SLuciano Coelho 		return -EINVAL;
527b3115f2SLuciano Coelho 
537b3115f2SLuciano Coelho 	if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
547b3115f2SLuciano Coelho 		return 0;
557b3115f2SLuciano Coelho 
567b3115f2SLuciano Coelho 	if (test_and_set_bit(WLVIF_FLAG_STA_STATE_SENT, &wlvif->flags))
577b3115f2SLuciano Coelho 		return 0;
587b3115f2SLuciano Coelho 
59d50529c0SEliad Peller 	ret = wl12xx_cmd_set_peer_state(wl, wlvif, wlvif->sta.hlid);
607b3115f2SLuciano Coelho 	if (ret < 0)
617b3115f2SLuciano Coelho 		return ret;
627b3115f2SLuciano Coelho 
637b3115f2SLuciano Coelho 	wl1271_info("Association completed.");
647b3115f2SLuciano Coelho 	return 0;
657b3115f2SLuciano Coelho }
667b3115f2SLuciano Coelho 
wl1271_reg_notify(struct wiphy * wiphy,struct regulatory_request * request)670c0280bdSLuis R. Rodriguez static void wl1271_reg_notify(struct wiphy *wiphy,
687b3115f2SLuciano Coelho 			      struct regulatory_request *request)
697b3115f2SLuciano Coelho {
706b70e7ebSVictor Goldenshtein 	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
716b70e7ebSVictor Goldenshtein 	struct wl1271 *wl = hw->priv;
727b3115f2SLuciano Coelho 
731cd91b2cSGuy Mishol 	/* copy the current dfs region */
741cd91b2cSGuy Mishol 	if (request)
751cd91b2cSGuy Mishol 		wl->dfs_region = request->dfs_region;
761cd91b2cSGuy Mishol 
776b70e7ebSVictor Goldenshtein 	wlcore_regdomain_config(wl);
787b3115f2SLuciano Coelho }
797b3115f2SLuciano Coelho 
wl1271_set_rx_streaming(struct wl1271 * wl,struct wl12xx_vif * wlvif,bool enable)807b3115f2SLuciano Coelho static int wl1271_set_rx_streaming(struct wl1271 *wl, struct wl12xx_vif *wlvif,
817b3115f2SLuciano Coelho 				   bool enable)
827b3115f2SLuciano Coelho {
837b3115f2SLuciano Coelho 	int ret = 0;
847b3115f2SLuciano Coelho 
857b3115f2SLuciano Coelho 	/* we should hold wl->mutex */
867b3115f2SLuciano Coelho 	ret = wl1271_acx_ps_rx_streaming(wl, wlvif, enable);
877b3115f2SLuciano Coelho 	if (ret < 0)
887b3115f2SLuciano Coelho 		goto out;
897b3115f2SLuciano Coelho 
907b3115f2SLuciano Coelho 	if (enable)
917b3115f2SLuciano Coelho 		set_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags);
927b3115f2SLuciano Coelho 	else
937b3115f2SLuciano Coelho 		clear_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags);
947b3115f2SLuciano Coelho out:
957b3115f2SLuciano Coelho 	return ret;
967b3115f2SLuciano Coelho }
977b3115f2SLuciano Coelho 
987b3115f2SLuciano Coelho /*
997b3115f2SLuciano Coelho  * this function is being called when the rx_streaming interval
1007b3115f2SLuciano Coelho  * has beed changed or rx_streaming should be disabled
1017b3115f2SLuciano Coelho  */
wl1271_recalc_rx_streaming(struct wl1271 * wl,struct wl12xx_vif * wlvif)1027b3115f2SLuciano Coelho int wl1271_recalc_rx_streaming(struct wl1271 *wl, struct wl12xx_vif *wlvif)
1037b3115f2SLuciano Coelho {
1047b3115f2SLuciano Coelho 	int ret = 0;
1057b3115f2SLuciano Coelho 	int period = wl->conf.rx_streaming.interval;
1067b3115f2SLuciano Coelho 
1077b3115f2SLuciano Coelho 	/* don't reconfigure if rx_streaming is disabled */
1087b3115f2SLuciano Coelho 	if (!test_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags))
1097b3115f2SLuciano Coelho 		goto out;
1107b3115f2SLuciano Coelho 
1117b3115f2SLuciano Coelho 	/* reconfigure/disable according to new streaming_period */
1127b3115f2SLuciano Coelho 	if (period &&
1137b3115f2SLuciano Coelho 	    test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags) &&
1147b3115f2SLuciano Coelho 	    (wl->conf.rx_streaming.always ||
1157b3115f2SLuciano Coelho 	     test_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags)))
1167b3115f2SLuciano Coelho 		ret = wl1271_set_rx_streaming(wl, wlvif, true);
1177b3115f2SLuciano Coelho 	else {
1187b3115f2SLuciano Coelho 		ret = wl1271_set_rx_streaming(wl, wlvif, false);
1197b3115f2SLuciano Coelho 		/* don't cancel_work_sync since we might deadlock */
1207b3115f2SLuciano Coelho 		del_timer_sync(&wlvif->rx_streaming_timer);
1217b3115f2SLuciano Coelho 	}
1227b3115f2SLuciano Coelho out:
1237b3115f2SLuciano Coelho 	return ret;
1247b3115f2SLuciano Coelho }
1257b3115f2SLuciano Coelho 
wl1271_rx_streaming_enable_work(struct work_struct * work)1267b3115f2SLuciano Coelho static void wl1271_rx_streaming_enable_work(struct work_struct *work)
1277b3115f2SLuciano Coelho {
1287b3115f2SLuciano Coelho 	int ret;
1297b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = container_of(work, struct wl12xx_vif,
1307b3115f2SLuciano Coelho 						rx_streaming_enable_work);
1317b3115f2SLuciano Coelho 	struct wl1271 *wl = wlvif->wl;
1327b3115f2SLuciano Coelho 
1337b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
1347b3115f2SLuciano Coelho 
1357b3115f2SLuciano Coelho 	if (test_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags) ||
1367b3115f2SLuciano Coelho 	    !test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags) ||
1377b3115f2SLuciano Coelho 	    (!wl->conf.rx_streaming.always &&
1387b3115f2SLuciano Coelho 	     !test_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags)))
1397b3115f2SLuciano Coelho 		goto out;
1407b3115f2SLuciano Coelho 
1417b3115f2SLuciano Coelho 	if (!wl->conf.rx_streaming.interval)
1427b3115f2SLuciano Coelho 		goto out;
1437b3115f2SLuciano Coelho 
144ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
145ab589ac2SMinghao Chi 	if (ret < 0)
1467b3115f2SLuciano Coelho 		goto out;
1477b3115f2SLuciano Coelho 
1487b3115f2SLuciano Coelho 	ret = wl1271_set_rx_streaming(wl, wlvif, true);
1497b3115f2SLuciano Coelho 	if (ret < 0)
1507b3115f2SLuciano Coelho 		goto out_sleep;
1517b3115f2SLuciano Coelho 
1527b3115f2SLuciano Coelho 	/* stop it after some time of inactivity */
1537b3115f2SLuciano Coelho 	mod_timer(&wlvif->rx_streaming_timer,
1547b3115f2SLuciano Coelho 		  jiffies + msecs_to_jiffies(wl->conf.rx_streaming.duration));
1557b3115f2SLuciano Coelho 
1567b3115f2SLuciano Coelho out_sleep:
1579b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
1589b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
1597b3115f2SLuciano Coelho out:
1607b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
1617b3115f2SLuciano Coelho }
1627b3115f2SLuciano Coelho 
wl1271_rx_streaming_disable_work(struct work_struct * work)1637b3115f2SLuciano Coelho static void wl1271_rx_streaming_disable_work(struct work_struct *work)
1647b3115f2SLuciano Coelho {
1657b3115f2SLuciano Coelho 	int ret;
1667b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = container_of(work, struct wl12xx_vif,
1677b3115f2SLuciano Coelho 						rx_streaming_disable_work);
1687b3115f2SLuciano Coelho 	struct wl1271 *wl = wlvif->wl;
1697b3115f2SLuciano Coelho 
1707b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
1717b3115f2SLuciano Coelho 
1727b3115f2SLuciano Coelho 	if (!test_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags))
1737b3115f2SLuciano Coelho 		goto out;
1747b3115f2SLuciano Coelho 
175ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
176ab589ac2SMinghao Chi 	if (ret < 0)
1777b3115f2SLuciano Coelho 		goto out;
1787b3115f2SLuciano Coelho 
1797b3115f2SLuciano Coelho 	ret = wl1271_set_rx_streaming(wl, wlvif, false);
1807b3115f2SLuciano Coelho 	if (ret)
1817b3115f2SLuciano Coelho 		goto out_sleep;
1827b3115f2SLuciano Coelho 
1837b3115f2SLuciano Coelho out_sleep:
1849b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
1859b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
1867b3115f2SLuciano Coelho out:
1877b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
1887b3115f2SLuciano Coelho }
1897b3115f2SLuciano Coelho 
wl1271_rx_streaming_timer(struct timer_list * t)190e99e88a9SKees Cook static void wl1271_rx_streaming_timer(struct timer_list *t)
1917b3115f2SLuciano Coelho {
192e99e88a9SKees Cook 	struct wl12xx_vif *wlvif = from_timer(wlvif, t, rx_streaming_timer);
1937b3115f2SLuciano Coelho 	struct wl1271 *wl = wlvif->wl;
1947b3115f2SLuciano Coelho 	ieee80211_queue_work(wl->hw, &wlvif->rx_streaming_disable_work);
1957b3115f2SLuciano Coelho }
1967b3115f2SLuciano Coelho 
1977b3115f2SLuciano Coelho /* wl->mutex must be taken */
wl12xx_rearm_tx_watchdog_locked(struct wl1271 * wl)1987b3115f2SLuciano Coelho void wl12xx_rearm_tx_watchdog_locked(struct wl1271 *wl)
1997b3115f2SLuciano Coelho {
2007b3115f2SLuciano Coelho 	/* if the watchdog is not armed, don't do anything */
2017b3115f2SLuciano Coelho 	if (wl->tx_allocated_blocks == 0)
2027b3115f2SLuciano Coelho 		return;
2037b3115f2SLuciano Coelho 
2047b3115f2SLuciano Coelho 	cancel_delayed_work(&wl->tx_watchdog_work);
2057b3115f2SLuciano Coelho 	ieee80211_queue_delayed_work(wl->hw, &wl->tx_watchdog_work,
2067b3115f2SLuciano Coelho 		msecs_to_jiffies(wl->conf.tx.tx_watchdog_timeout));
2077b3115f2SLuciano Coelho }
2087b3115f2SLuciano Coelho 
wlcore_rc_update_work(struct work_struct * work)2097d3b29e5SEliad Peller static void wlcore_rc_update_work(struct work_struct *work)
2107d3b29e5SEliad Peller {
2117d3b29e5SEliad Peller 	int ret;
2127d3b29e5SEliad Peller 	struct wl12xx_vif *wlvif = container_of(work, struct wl12xx_vif,
2137d3b29e5SEliad Peller 						rc_update_work);
2147d3b29e5SEliad Peller 	struct wl1271 *wl = wlvif->wl;
215c0174ee2SMaital Hahn 	struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
2167d3b29e5SEliad Peller 
2177d3b29e5SEliad Peller 	mutex_lock(&wl->mutex);
2187d3b29e5SEliad Peller 
2197d3b29e5SEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON))
2207d3b29e5SEliad Peller 		goto out;
2217d3b29e5SEliad Peller 
222ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
223ab589ac2SMinghao Chi 	if (ret < 0)
2247d3b29e5SEliad Peller 		goto out;
2257d3b29e5SEliad Peller 
226c0174ee2SMaital Hahn 	if (ieee80211_vif_is_mesh(vif)) {
227c0174ee2SMaital Hahn 		ret = wl1271_acx_set_ht_capabilities(wl, &wlvif->rc_ht_cap,
228c0174ee2SMaital Hahn 						     true, wlvif->sta.hlid);
229c0174ee2SMaital Hahn 		if (ret < 0)
230c0174ee2SMaital Hahn 			goto out_sleep;
231c0174ee2SMaital Hahn 	} else {
2327d3b29e5SEliad Peller 		wlcore_hw_sta_rc_update(wl, wlvif);
233c0174ee2SMaital Hahn 	}
2347d3b29e5SEliad Peller 
235c0174ee2SMaital Hahn out_sleep:
2369b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
2379b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
2387d3b29e5SEliad Peller out:
2397d3b29e5SEliad Peller 	mutex_unlock(&wl->mutex);
2407d3b29e5SEliad Peller }
2417d3b29e5SEliad Peller 
wl12xx_tx_watchdog_work(struct work_struct * work)2427b3115f2SLuciano Coelho static void wl12xx_tx_watchdog_work(struct work_struct *work)
2437b3115f2SLuciano Coelho {
2447b3115f2SLuciano Coelho 	struct delayed_work *dwork;
2457b3115f2SLuciano Coelho 	struct wl1271 *wl;
2467b3115f2SLuciano Coelho 
24761383412SGeliang Tang 	dwork = to_delayed_work(work);
2487b3115f2SLuciano Coelho 	wl = container_of(dwork, struct wl1271, tx_watchdog_work);
2497b3115f2SLuciano Coelho 
2507b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
2517b3115f2SLuciano Coelho 
2524cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
2537b3115f2SLuciano Coelho 		goto out;
2547b3115f2SLuciano Coelho 
2557b3115f2SLuciano Coelho 	/* Tx went out in the meantime - everything is ok */
2567b3115f2SLuciano Coelho 	if (unlikely(wl->tx_allocated_blocks == 0))
2577b3115f2SLuciano Coelho 		goto out;
2587b3115f2SLuciano Coelho 
2597b3115f2SLuciano Coelho 	/*
2607b3115f2SLuciano Coelho 	 * if a ROC is in progress, we might not have any Tx for a long
2617b3115f2SLuciano Coelho 	 * time (e.g. pending Tx on the non-ROC channels)
2627b3115f2SLuciano Coelho 	 */
2637b3115f2SLuciano Coelho 	if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) {
2647b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to ROC",
2657b3115f2SLuciano Coelho 			     wl->conf.tx.tx_watchdog_timeout);
2667b3115f2SLuciano Coelho 		wl12xx_rearm_tx_watchdog_locked(wl);
2677b3115f2SLuciano Coelho 		goto out;
2687b3115f2SLuciano Coelho 	}
2697b3115f2SLuciano Coelho 
2707b3115f2SLuciano Coelho 	/*
2717b3115f2SLuciano Coelho 	 * if a scan is in progress, we might not have any Tx for a long
2727b3115f2SLuciano Coelho 	 * time
2737b3115f2SLuciano Coelho 	 */
2747b3115f2SLuciano Coelho 	if (wl->scan.state != WL1271_SCAN_STATE_IDLE) {
2757b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to scan",
2767b3115f2SLuciano Coelho 			     wl->conf.tx.tx_watchdog_timeout);
2777b3115f2SLuciano Coelho 		wl12xx_rearm_tx_watchdog_locked(wl);
2787b3115f2SLuciano Coelho 		goto out;
2797b3115f2SLuciano Coelho 	}
2807b3115f2SLuciano Coelho 
2817b3115f2SLuciano Coelho 	/*
2827b3115f2SLuciano Coelho 	* AP might cache a frame for a long time for a sleeping station,
2837b3115f2SLuciano Coelho 	* so rearm the timer if there's an AP interface with stations. If
2847b3115f2SLuciano Coelho 	* Tx is genuinely stuck we will most hopefully discover it when all
2857b3115f2SLuciano Coelho 	* stations are removed due to inactivity.
2867b3115f2SLuciano Coelho 	*/
2877b3115f2SLuciano Coelho 	if (wl->active_sta_count) {
2887b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms. AP has "
2897b3115f2SLuciano Coelho 			     " %d stations",
2907b3115f2SLuciano Coelho 			      wl->conf.tx.tx_watchdog_timeout,
2917b3115f2SLuciano Coelho 			      wl->active_sta_count);
2927b3115f2SLuciano Coelho 		wl12xx_rearm_tx_watchdog_locked(wl);
2937b3115f2SLuciano Coelho 		goto out;
2947b3115f2SLuciano Coelho 	}
2957b3115f2SLuciano Coelho 
2967b3115f2SLuciano Coelho 	wl1271_error("Tx stuck (in FW) for %d ms. Starting recovery",
2977b3115f2SLuciano Coelho 		     wl->conf.tx.tx_watchdog_timeout);
2987b3115f2SLuciano Coelho 	wl12xx_queue_recovery_work(wl);
2997b3115f2SLuciano Coelho 
3007b3115f2SLuciano Coelho out:
3017b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
3027b3115f2SLuciano Coelho }
3037b3115f2SLuciano Coelho 
wlcore_adjust_conf(struct wl1271 * wl)304e87288f0SLuciano Coelho static void wlcore_adjust_conf(struct wl1271 *wl)
3057b3115f2SLuciano Coelho {
30693ac8488SIdo Reis 
3077b3115f2SLuciano Coelho 	if (fwlog_param) {
3087b3115f2SLuciano Coelho 		if (!strcmp(fwlog_param, "continuous")) {
3097b3115f2SLuciano Coelho 			wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS;
3103719c17eSShahar Patury 			wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_HOST;
3117b3115f2SLuciano Coelho 		} else if (!strcmp(fwlog_param, "dbgpins")) {
3127b3115f2SLuciano Coelho 			wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS;
3137b3115f2SLuciano Coelho 			wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_DBG_PINS;
3147b3115f2SLuciano Coelho 		} else if (!strcmp(fwlog_param, "disable")) {
3157b3115f2SLuciano Coelho 			wl->conf.fwlog.mem_blocks = 0;
3167b3115f2SLuciano Coelho 			wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_NONE;
3177b3115f2SLuciano Coelho 		} else {
3187b3115f2SLuciano Coelho 			wl1271_error("Unknown fwlog parameter %s", fwlog_param);
3197b3115f2SLuciano Coelho 		}
3207b3115f2SLuciano Coelho 	}
3217230341fSYair Shapira 
3227230341fSYair Shapira 	if (bug_on_recovery != -1)
3237230341fSYair Shapira 		wl->conf.recovery.bug_on_recovery = (u8) bug_on_recovery;
3247230341fSYair Shapira 
3257230341fSYair Shapira 	if (no_recovery != -1)
3267230341fSYair Shapira 		wl->conf.recovery.no_recovery = (u8) no_recovery;
3277b3115f2SLuciano Coelho }
3287b3115f2SLuciano Coelho 
wl12xx_irq_ps_regulate_link(struct wl1271 * wl,struct wl12xx_vif * wlvif,u8 hlid,u8 tx_pkts)3297b3115f2SLuciano Coelho static void wl12xx_irq_ps_regulate_link(struct wl1271 *wl,
3307b3115f2SLuciano Coelho 					struct wl12xx_vif *wlvif,
3317b3115f2SLuciano Coelho 					u8 hlid, u8 tx_pkts)
3327b3115f2SLuciano Coelho {
33337c68ea6SArik Nemtsov 	bool fw_ps;
3347b3115f2SLuciano Coelho 
3355e74b3aaSEliad Peller 	fw_ps = test_bit(hlid, &wl->ap_fw_ps_map);
3367b3115f2SLuciano Coelho 
3377b3115f2SLuciano Coelho 	/*
3387b3115f2SLuciano Coelho 	 * Wake up from high level PS if the STA is asleep with too little
3397b3115f2SLuciano Coelho 	 * packets in FW or if the STA is awake.
3407b3115f2SLuciano Coelho 	 */
3417b3115f2SLuciano Coelho 	if (!fw_ps || tx_pkts < WL1271_PS_STA_MAX_PACKETS)
3427b3115f2SLuciano Coelho 		wl12xx_ps_link_end(wl, wlvif, hlid);
3437b3115f2SLuciano Coelho 
3447b3115f2SLuciano Coelho 	/*
3457b3115f2SLuciano Coelho 	 * Start high-level PS if the STA is asleep with enough blocks in FW.
3469a100968SArik Nemtsov 	 * Make an exception if this is the only connected link. In this
3479a100968SArik Nemtsov 	 * case FW-memory congestion is less of a problem.
34841ed1a78SEliad Peller 	 * Note that a single connected STA means 2*ap_count + 1 active links,
34941ed1a78SEliad Peller 	 * since we must account for the global and broadcast AP links
35041ed1a78SEliad Peller 	 * for each AP. The "fw_ps" check assures us the other link is a STA
35141ed1a78SEliad Peller 	 * connected to the AP. Otherwise the FW would not set the PSM bit.
3527b3115f2SLuciano Coelho 	 */
35341ed1a78SEliad Peller 	else if (wl->active_link_count > (wl->ap_count*2 + 1) && fw_ps &&
35437c68ea6SArik Nemtsov 		 tx_pkts >= WL1271_PS_STA_MAX_PACKETS)
3557b3115f2SLuciano Coelho 		wl12xx_ps_link_start(wl, wlvif, hlid, true);
3567b3115f2SLuciano Coelho }
3577b3115f2SLuciano Coelho 
wl12xx_irq_update_links_status(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct wl_fw_status * status)3587b3115f2SLuciano Coelho static void wl12xx_irq_update_links_status(struct wl1271 *wl,
3597b3115f2SLuciano Coelho 					   struct wl12xx_vif *wlvif,
36075fb4df7SEliad Peller 					   struct wl_fw_status *status)
3617b3115f2SLuciano Coelho {
3625e74b3aaSEliad Peller 	unsigned long cur_fw_ps_map;
3639ebcb232SArik Nemtsov 	u8 hlid;
3647b3115f2SLuciano Coelho 
36575fb4df7SEliad Peller 	cur_fw_ps_map = status->link_ps_bitmap;
3667b3115f2SLuciano Coelho 	if (wl->ap_fw_ps_map != cur_fw_ps_map) {
3677b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_PSM,
3685e74b3aaSEliad Peller 			     "link ps prev 0x%lx cur 0x%lx changed 0x%lx",
3697b3115f2SLuciano Coelho 			     wl->ap_fw_ps_map, cur_fw_ps_map,
3707b3115f2SLuciano Coelho 			     wl->ap_fw_ps_map ^ cur_fw_ps_map);
3717b3115f2SLuciano Coelho 
3727b3115f2SLuciano Coelho 		wl->ap_fw_ps_map = cur_fw_ps_map;
3737b3115f2SLuciano Coelho 	}
3747b3115f2SLuciano Coelho 
375da08fdfaSEliad Peller 	for_each_set_bit(hlid, wlvif->ap.sta_hlid_map, wl->num_links)
3767b3115f2SLuciano Coelho 		wl12xx_irq_ps_regulate_link(wl, wlvif, hlid,
3779ebcb232SArik Nemtsov 					    wl->links[hlid].allocated_pkts);
3787b3115f2SLuciano Coelho }
3797b3115f2SLuciano Coelho 
wlcore_fw_status(struct wl1271 * wl,struct wl_fw_status * status)38075fb4df7SEliad Peller static int wlcore_fw_status(struct wl1271 *wl, struct wl_fw_status *status)
3817b3115f2SLuciano Coelho {
3827b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
3837b3115f2SLuciano Coelho 	u32 old_tx_blk_count = wl->tx_blocks_available;
3847b3115f2SLuciano Coelho 	int avail, freed_blocks;
3857b3115f2SLuciano Coelho 	int i;
3868b7c0fc3SIdo Yariv 	int ret;
3879ebcb232SArik Nemtsov 	struct wl1271_link *lnk;
3886bac40a6SArik Nemtsov 
38975fb4df7SEliad Peller 	ret = wlcore_raw_read_data(wl, REG_RAW_FW_STATUS_ADDR,
39075fb4df7SEliad Peller 				   wl->raw_fw_status,
39175fb4df7SEliad Peller 				   wl->fw_status_len, false);
3928b7c0fc3SIdo Yariv 	if (ret < 0)
3938b7c0fc3SIdo Yariv 		return ret;
3947b3115f2SLuciano Coelho 
39575fb4df7SEliad Peller 	wlcore_hw_convert_fw_status(wl, wl->raw_fw_status, wl->fw_status);
39675fb4df7SEliad Peller 
3977b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_IRQ, "intr: 0x%x (fw_rx_counter = %d, "
3987b3115f2SLuciano Coelho 		     "drv_rx_counter = %d, tx_results_counter = %d)",
39975fb4df7SEliad Peller 		     status->intr,
40075fb4df7SEliad Peller 		     status->fw_rx_counter,
40175fb4df7SEliad Peller 		     status->drv_rx_counter,
40275fb4df7SEliad Peller 		     status->tx_results_counter);
4037b3115f2SLuciano Coelho 
4047b3115f2SLuciano Coelho 	for (i = 0; i < NUM_TX_QUEUES; i++) {
4057b3115f2SLuciano Coelho 		/* prevent wrap-around in freed-packets counter */
4067b3115f2SLuciano Coelho 		wl->tx_allocated_pkts[i] -=
40775fb4df7SEliad Peller 				(status->counters.tx_released_pkts[i] -
4087b3115f2SLuciano Coelho 				wl->tx_pkts_freed[i]) & 0xff;
4097b3115f2SLuciano Coelho 
41075fb4df7SEliad Peller 		wl->tx_pkts_freed[i] = status->counters.tx_released_pkts[i];
4117b3115f2SLuciano Coelho 	}
4127b3115f2SLuciano Coelho 
4139ebcb232SArik Nemtsov 
414da08fdfaSEliad Peller 	for_each_set_bit(i, wl->links_map, wl->num_links) {
41593d5d100SArik Nemtsov 		u8 diff;
4169ebcb232SArik Nemtsov 		lnk = &wl->links[i];
41793d5d100SArik Nemtsov 
4189ebcb232SArik Nemtsov 		/* prevent wrap-around in freed-packets counter */
41975fb4df7SEliad Peller 		diff = (status->counters.tx_lnk_free_pkts[i] -
4209ebcb232SArik Nemtsov 		       lnk->prev_freed_pkts) & 0xff;
4219ebcb232SArik Nemtsov 
42293d5d100SArik Nemtsov 		if (diff == 0)
42393d5d100SArik Nemtsov 			continue;
42493d5d100SArik Nemtsov 
42593d5d100SArik Nemtsov 		lnk->allocated_pkts -= diff;
42675fb4df7SEliad Peller 		lnk->prev_freed_pkts = status->counters.tx_lnk_free_pkts[i];
42793d5d100SArik Nemtsov 
42893d5d100SArik Nemtsov 		/* accumulate the prev_freed_pkts counter */
42993d5d100SArik Nemtsov 		lnk->total_freed_pkts += diff;
4309ebcb232SArik Nemtsov 	}
4319ebcb232SArik Nemtsov 
4327b3115f2SLuciano Coelho 	/* prevent wrap-around in total blocks counter */
43375fb4df7SEliad Peller 	if (likely(wl->tx_blocks_freed <= status->total_released_blks))
43475fb4df7SEliad Peller 		freed_blocks = status->total_released_blks -
4357b3115f2SLuciano Coelho 			       wl->tx_blocks_freed;
4367b3115f2SLuciano Coelho 	else
4377b3115f2SLuciano Coelho 		freed_blocks = 0x100000000LL - wl->tx_blocks_freed +
43875fb4df7SEliad Peller 			       status->total_released_blks;
4397b3115f2SLuciano Coelho 
44075fb4df7SEliad Peller 	wl->tx_blocks_freed = status->total_released_blks;
4417b3115f2SLuciano Coelho 
4427b3115f2SLuciano Coelho 	wl->tx_allocated_blocks -= freed_blocks;
4437b3115f2SLuciano Coelho 
4447b3115f2SLuciano Coelho 	/*
4457b3115f2SLuciano Coelho 	 * If the FW freed some blocks:
4467b3115f2SLuciano Coelho 	 * If we still have allocated blocks - re-arm the timer, Tx is
4477b3115f2SLuciano Coelho 	 * not stuck. Otherwise, cancel the timer (no Tx currently).
4487b3115f2SLuciano Coelho 	 */
4497b3115f2SLuciano Coelho 	if (freed_blocks) {
4507b3115f2SLuciano Coelho 		if (wl->tx_allocated_blocks)
4517b3115f2SLuciano Coelho 			wl12xx_rearm_tx_watchdog_locked(wl);
4527b3115f2SLuciano Coelho 		else
4537b3115f2SLuciano Coelho 			cancel_delayed_work(&wl->tx_watchdog_work);
4547b3115f2SLuciano Coelho 	}
4557b3115f2SLuciano Coelho 
45675fb4df7SEliad Peller 	avail = status->tx_total - wl->tx_allocated_blocks;
4577b3115f2SLuciano Coelho 
4587b3115f2SLuciano Coelho 	/*
4597b3115f2SLuciano Coelho 	 * The FW might change the total number of TX memblocks before
4607b3115f2SLuciano Coelho 	 * we get a notification about blocks being released. Thus, the
4617b3115f2SLuciano Coelho 	 * available blocks calculation might yield a temporary result
4627b3115f2SLuciano Coelho 	 * which is lower than the actual available blocks. Keeping in
4637b3115f2SLuciano Coelho 	 * mind that only blocks that were allocated can be moved from
4647b3115f2SLuciano Coelho 	 * TX to RX, tx_blocks_available should never decrease here.
4657b3115f2SLuciano Coelho 	 */
4667b3115f2SLuciano Coelho 	wl->tx_blocks_available = max((int)wl->tx_blocks_available,
4677b3115f2SLuciano Coelho 				      avail);
4687b3115f2SLuciano Coelho 
4697b3115f2SLuciano Coelho 	/* if more blocks are available now, tx work can be scheduled */
4707b3115f2SLuciano Coelho 	if (wl->tx_blocks_available > old_tx_blk_count)
4717b3115f2SLuciano Coelho 		clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags);
4727b3115f2SLuciano Coelho 
4737b3115f2SLuciano Coelho 	/* for AP update num of allocated TX blocks per link and ps status */
4747b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif_ap(wl, wlvif) {
47575fb4df7SEliad Peller 		wl12xx_irq_update_links_status(wl, wlvif, status);
4767b3115f2SLuciano Coelho 	}
4777b3115f2SLuciano Coelho 
4787b3115f2SLuciano Coelho 	/* update the host-chipset time offset */
4799285ec4cSJason A. Donenfeld 	wl->time_offset = (ktime_get_boottime_ns() >> 10) -
48075fb4df7SEliad Peller 		(s64)(status->fw_localtime);
4818b7c0fc3SIdo Yariv 
48275fb4df7SEliad Peller 	wl->fw_fast_lnk_map = status->link_fast_bitmap;
4830e810479SArik Nemtsov 
4848b7c0fc3SIdo Yariv 	return 0;
4857b3115f2SLuciano Coelho }
4867b3115f2SLuciano Coelho 
wl1271_flush_deferred_work(struct wl1271 * wl)4877b3115f2SLuciano Coelho static void wl1271_flush_deferred_work(struct wl1271 *wl)
4887b3115f2SLuciano Coelho {
4897b3115f2SLuciano Coelho 	struct sk_buff *skb;
4907b3115f2SLuciano Coelho 
4917b3115f2SLuciano Coelho 	/* Pass all received frames to the network stack */
4927b3115f2SLuciano Coelho 	while ((skb = skb_dequeue(&wl->deferred_rx_queue)))
4937b3115f2SLuciano Coelho 		ieee80211_rx_ni(wl->hw, skb);
4947b3115f2SLuciano Coelho 
4957b3115f2SLuciano Coelho 	/* Return sent skbs to the network stack */
4967b3115f2SLuciano Coelho 	while ((skb = skb_dequeue(&wl->deferred_tx_queue)))
4977b3115f2SLuciano Coelho 		ieee80211_tx_status_ni(wl->hw, skb);
4987b3115f2SLuciano Coelho }
4997b3115f2SLuciano Coelho 
wl1271_netstack_work(struct work_struct * work)5007b3115f2SLuciano Coelho static void wl1271_netstack_work(struct work_struct *work)
5017b3115f2SLuciano Coelho {
5027b3115f2SLuciano Coelho 	struct wl1271 *wl =
5037b3115f2SLuciano Coelho 		container_of(work, struct wl1271, netstack_work);
5047b3115f2SLuciano Coelho 
5057b3115f2SLuciano Coelho 	do {
5067b3115f2SLuciano Coelho 		wl1271_flush_deferred_work(wl);
5077b3115f2SLuciano Coelho 	} while (skb_queue_len(&wl->deferred_rx_queue));
5087b3115f2SLuciano Coelho }
5097b3115f2SLuciano Coelho 
5107b3115f2SLuciano Coelho #define WL1271_IRQ_MAX_LOOPS 256
5117b3115f2SLuciano Coelho 
wlcore_irq_locked(struct wl1271 * wl)512b5b45b3cSArik Nemtsov static int wlcore_irq_locked(struct wl1271 *wl)
5137b3115f2SLuciano Coelho {
514b5b45b3cSArik Nemtsov 	int ret = 0;
5157b3115f2SLuciano Coelho 	u32 intr;
5167b3115f2SLuciano Coelho 	int loopcount = WL1271_IRQ_MAX_LOOPS;
517f0325e38STony Lindgren 	bool run_tx_queue = true;
5187b3115f2SLuciano Coelho 	bool done = false;
5197b3115f2SLuciano Coelho 	unsigned int defer_count;
5207b3115f2SLuciano Coelho 	unsigned long flags;
5217b3115f2SLuciano Coelho 
5227b3115f2SLuciano Coelho 	/*
5237b3115f2SLuciano Coelho 	 * In case edge triggered interrupt must be used, we cannot iterate
5247b3115f2SLuciano Coelho 	 * more than once without introducing race conditions with the hardirq.
5257b3115f2SLuciano Coelho 	 */
5266f921fabSLuciano Coelho 	if (wl->irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING))
5277b3115f2SLuciano Coelho 		loopcount = 1;
5287b3115f2SLuciano Coelho 
5297b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_IRQ, "IRQ work");
5307b3115f2SLuciano Coelho 
5314cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
5327b3115f2SLuciano Coelho 		goto out;
5337b3115f2SLuciano Coelho 
534ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
535ab589ac2SMinghao Chi 	if (ret < 0)
5367b3115f2SLuciano Coelho 		goto out;
5377b3115f2SLuciano Coelho 
5387b3115f2SLuciano Coelho 	while (!done && loopcount--) {
5394e857c58SPeter Zijlstra 		smp_mb__after_atomic();
5407b3115f2SLuciano Coelho 
54175fb4df7SEliad Peller 		ret = wlcore_fw_status(wl, wl->fw_status);
542b5b45b3cSArik Nemtsov 		if (ret < 0)
543da74b693SDinghao Liu 			goto err_ret;
54453d67a50SArik Nemtsov 
54553d67a50SArik Nemtsov 		wlcore_hw_tx_immediate_compl(wl);
54653d67a50SArik Nemtsov 
54775fb4df7SEliad Peller 		intr = wl->fw_status->intr;
548f5755fe9SIdo Reis 		intr &= WLCORE_ALL_INTR_MASK;
5497b3115f2SLuciano Coelho 		if (!intr) {
5507b3115f2SLuciano Coelho 			done = true;
5517b3115f2SLuciano Coelho 			continue;
5527b3115f2SLuciano Coelho 		}
5537b3115f2SLuciano Coelho 
5547b3115f2SLuciano Coelho 		if (unlikely(intr & WL1271_ACX_INTR_WATCHDOG)) {
555f5755fe9SIdo Reis 			wl1271_error("HW watchdog interrupt received! starting recovery.");
556f5755fe9SIdo Reis 			wl->watchdog_recovery = true;
557b5b45b3cSArik Nemtsov 			ret = -EIO;
558f5755fe9SIdo Reis 
559f5755fe9SIdo Reis 			/* restarting the chip. ignore any other interrupt. */
560da74b693SDinghao Liu 			goto err_ret;
561f5755fe9SIdo Reis 		}
562f5755fe9SIdo Reis 
563f5755fe9SIdo Reis 		if (unlikely(intr & WL1271_ACX_SW_INTR_WATCHDOG)) {
564f5755fe9SIdo Reis 			wl1271_error("SW watchdog interrupt received! "
5657b3115f2SLuciano Coelho 				     "starting recovery.");
566afbe3718SYoni Divinsky 			wl->watchdog_recovery = true;
567b5b45b3cSArik Nemtsov 			ret = -EIO;
5687b3115f2SLuciano Coelho 
5697b3115f2SLuciano Coelho 			/* restarting the chip. ignore any other interrupt. */
570da74b693SDinghao Liu 			goto err_ret;
5717b3115f2SLuciano Coelho 		}
5727b3115f2SLuciano Coelho 
5737b3115f2SLuciano Coelho 		if (likely(intr & WL1271_ACX_INTR_DATA)) {
5747b3115f2SLuciano Coelho 			wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_DATA");
5757b3115f2SLuciano Coelho 
57675fb4df7SEliad Peller 			ret = wlcore_rx(wl, wl->fw_status);
577b5b45b3cSArik Nemtsov 			if (ret < 0)
578da74b693SDinghao Liu 				goto err_ret;
5797b3115f2SLuciano Coelho 
5807b3115f2SLuciano Coelho 			/* Check if any tx blocks were freed */
581f0325e38STony Lindgren 			if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags)) {
582f0325e38STony Lindgren 				if (spin_trylock_irqsave(&wl->wl_lock, flags)) {
583f0325e38STony Lindgren 					if (!wl1271_tx_total_queue_count(wl))
584f0325e38STony Lindgren 						run_tx_queue = false;
5857b3115f2SLuciano Coelho 					spin_unlock_irqrestore(&wl->wl_lock, flags);
586f0325e38STony Lindgren 				}
587f0325e38STony Lindgren 
5887b3115f2SLuciano Coelho 				/*
5897b3115f2SLuciano Coelho 				 * In order to avoid starvation of the TX path,
5907b3115f2SLuciano Coelho 				 * call the work function directly.
5917b3115f2SLuciano Coelho 				 */
592f0325e38STony Lindgren 				if (run_tx_queue) {
593eb96f841SIdo Yariv 					ret = wlcore_tx_work_locked(wl);
594b5b45b3cSArik Nemtsov 					if (ret < 0)
595da74b693SDinghao Liu 						goto err_ret;
596f0325e38STony Lindgren 				}
5977b3115f2SLuciano Coelho 			}
5987b3115f2SLuciano Coelho 
5997b3115f2SLuciano Coelho 			/* check for tx results */
600045b9b5fSIdo Yariv 			ret = wlcore_hw_tx_delayed_compl(wl);
601b5b45b3cSArik Nemtsov 			if (ret < 0)
602da74b693SDinghao Liu 				goto err_ret;
6037b3115f2SLuciano Coelho 
6047b3115f2SLuciano Coelho 			/* Make sure the deferred queues don't get too long */
6057b3115f2SLuciano Coelho 			defer_count = skb_queue_len(&wl->deferred_tx_queue) +
6067b3115f2SLuciano Coelho 				      skb_queue_len(&wl->deferred_rx_queue);
6077b3115f2SLuciano Coelho 			if (defer_count > WL1271_DEFERRED_QUEUE_LIMIT)
6087b3115f2SLuciano Coelho 				wl1271_flush_deferred_work(wl);
6097b3115f2SLuciano Coelho 		}
6107b3115f2SLuciano Coelho 
6117b3115f2SLuciano Coelho 		if (intr & WL1271_ACX_INTR_EVENT_A) {
6127b3115f2SLuciano Coelho 			wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_EVENT_A");
613045b9b5fSIdo Yariv 			ret = wl1271_event_handle(wl, 0);
614b5b45b3cSArik Nemtsov 			if (ret < 0)
615da74b693SDinghao Liu 				goto err_ret;
6167b3115f2SLuciano Coelho 		}
6177b3115f2SLuciano Coelho 
6187b3115f2SLuciano Coelho 		if (intr & WL1271_ACX_INTR_EVENT_B) {
6197b3115f2SLuciano Coelho 			wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_EVENT_B");
620045b9b5fSIdo Yariv 			ret = wl1271_event_handle(wl, 1);
621b5b45b3cSArik Nemtsov 			if (ret < 0)
622da74b693SDinghao Liu 				goto err_ret;
6237b3115f2SLuciano Coelho 		}
6247b3115f2SLuciano Coelho 
6257b3115f2SLuciano Coelho 		if (intr & WL1271_ACX_INTR_INIT_COMPLETE)
6267b3115f2SLuciano Coelho 			wl1271_debug(DEBUG_IRQ,
6277b3115f2SLuciano Coelho 				     "WL1271_ACX_INTR_INIT_COMPLETE");
6287b3115f2SLuciano Coelho 
6297b3115f2SLuciano Coelho 		if (intr & WL1271_ACX_INTR_HW_AVAILABLE)
6307b3115f2SLuciano Coelho 			wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_HW_AVAILABLE");
6317b3115f2SLuciano Coelho 	}
6327b3115f2SLuciano Coelho 
633da74b693SDinghao Liu err_ret:
6349b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
6359b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
6367b3115f2SLuciano Coelho 
6377b3115f2SLuciano Coelho out:
638b5b45b3cSArik Nemtsov 	return ret;
639b5b45b3cSArik Nemtsov }
640b5b45b3cSArik Nemtsov 
wlcore_irq(int irq,void * cookie)641b5b45b3cSArik Nemtsov static irqreturn_t wlcore_irq(int irq, void *cookie)
642b5b45b3cSArik Nemtsov {
643b5b45b3cSArik Nemtsov 	int ret;
644b5b45b3cSArik Nemtsov 	unsigned long flags;
645b5b45b3cSArik Nemtsov 	struct wl1271 *wl = cookie;
64635fba0f0STony Lindgren 	bool queue_tx_work = true;
647b5b45b3cSArik Nemtsov 
64897236a06SLuciano Coelho 	set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags);
649eb215c33STony Lindgren 
650eb215c33STony Lindgren 	/* complete the ELP completion */
651eb215c33STony Lindgren 	if (test_bit(WL1271_FLAG_IN_ELP, &wl->flags)) {
652eb215c33STony Lindgren 		spin_lock_irqsave(&wl->wl_lock, flags);
653eb215c33STony Lindgren 		if (wl->elp_compl)
65497236a06SLuciano Coelho 			complete(wl->elp_compl);
655eb215c33STony Lindgren 		spin_unlock_irqrestore(&wl->wl_lock, flags);
65697236a06SLuciano Coelho 	}
65797236a06SLuciano Coelho 
65897236a06SLuciano Coelho 	if (test_bit(WL1271_FLAG_SUSPENDED, &wl->flags)) {
65997236a06SLuciano Coelho 		/* don't enqueue a work right now. mark it as pending */
66097236a06SLuciano Coelho 		set_bit(WL1271_FLAG_PENDING_WORK, &wl->flags);
66197236a06SLuciano Coelho 		wl1271_debug(DEBUG_IRQ, "should not enqueue work");
662eb215c33STony Lindgren 		spin_lock_irqsave(&wl->wl_lock, flags);
66397236a06SLuciano Coelho 		disable_irq_nosync(wl->irq);
66497236a06SLuciano Coelho 		pm_wakeup_event(wl->dev, 0);
66597236a06SLuciano Coelho 		spin_unlock_irqrestore(&wl->wl_lock, flags);
6664633d30bSTony Lindgren 		goto out_handled;
66797236a06SLuciano Coelho 	}
66897236a06SLuciano Coelho 
669b5b45b3cSArik Nemtsov 	/* TX might be handled here, avoid redundant work */
670b5b45b3cSArik Nemtsov 	set_bit(WL1271_FLAG_TX_PENDING, &wl->flags);
671b5b45b3cSArik Nemtsov 	cancel_work_sync(&wl->tx_work);
672b5b45b3cSArik Nemtsov 
673b5b45b3cSArik Nemtsov 	mutex_lock(&wl->mutex);
674b5b45b3cSArik Nemtsov 
675b5b45b3cSArik Nemtsov 	ret = wlcore_irq_locked(wl);
676b5b45b3cSArik Nemtsov 	if (ret)
677b5b45b3cSArik Nemtsov 		wl12xx_queue_recovery_work(wl);
678b5b45b3cSArik Nemtsov 
67935fba0f0STony Lindgren 	/* In case TX was not handled in wlcore_irq_locked(), queue TX work */
6807b3115f2SLuciano Coelho 	clear_bit(WL1271_FLAG_TX_PENDING, &wl->flags);
68135fba0f0STony Lindgren 	if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags)) {
68235fba0f0STony Lindgren 		if (spin_trylock_irqsave(&wl->wl_lock, flags)) {
68335fba0f0STony Lindgren 			if (!wl1271_tx_total_queue_count(wl))
68435fba0f0STony Lindgren 				queue_tx_work = false;
6857b3115f2SLuciano Coelho 			spin_unlock_irqrestore(&wl->wl_lock, flags);
68635fba0f0STony Lindgren 		}
68735fba0f0STony Lindgren 		if (queue_tx_work)
68835fba0f0STony Lindgren 			ieee80211_queue_work(wl->hw, &wl->tx_work);
68935fba0f0STony Lindgren 	}
6907b3115f2SLuciano Coelho 
6917b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
6927b3115f2SLuciano Coelho 
6934633d30bSTony Lindgren out_handled:
6944633d30bSTony Lindgren 	clear_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags);
6954633d30bSTony Lindgren 
6967b3115f2SLuciano Coelho 	return IRQ_HANDLED;
6977b3115f2SLuciano Coelho }
6987b3115f2SLuciano Coelho 
6997b3115f2SLuciano Coelho struct vif_counter_data {
7007b3115f2SLuciano Coelho 	u8 counter;
7017b3115f2SLuciano Coelho 
7027b3115f2SLuciano Coelho 	struct ieee80211_vif *cur_vif;
7037b3115f2SLuciano Coelho 	bool cur_vif_running;
7047b3115f2SLuciano Coelho };
7057b3115f2SLuciano Coelho 
wl12xx_vif_count_iter(void * data,u8 * mac,struct ieee80211_vif * vif)7067b3115f2SLuciano Coelho static void wl12xx_vif_count_iter(void *data, u8 *mac,
7077b3115f2SLuciano Coelho 				  struct ieee80211_vif *vif)
7087b3115f2SLuciano Coelho {
7097b3115f2SLuciano Coelho 	struct vif_counter_data *counter = data;
7107b3115f2SLuciano Coelho 
7117b3115f2SLuciano Coelho 	counter->counter++;
7127b3115f2SLuciano Coelho 	if (counter->cur_vif == vif)
7137b3115f2SLuciano Coelho 		counter->cur_vif_running = true;
7147b3115f2SLuciano Coelho }
7157b3115f2SLuciano Coelho 
7167b3115f2SLuciano Coelho /* caller must not hold wl->mutex, as it might deadlock */
wl12xx_get_vif_count(struct ieee80211_hw * hw,struct ieee80211_vif * cur_vif,struct vif_counter_data * data)7177b3115f2SLuciano Coelho static void wl12xx_get_vif_count(struct ieee80211_hw *hw,
7187b3115f2SLuciano Coelho 			       struct ieee80211_vif *cur_vif,
7197b3115f2SLuciano Coelho 			       struct vif_counter_data *data)
7207b3115f2SLuciano Coelho {
7217b3115f2SLuciano Coelho 	memset(data, 0, sizeof(*data));
7227b3115f2SLuciano Coelho 	data->cur_vif = cur_vif;
7237b3115f2SLuciano Coelho 
7248b2c9824SJohannes Berg 	ieee80211_iterate_active_interfaces(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
7257b3115f2SLuciano Coelho 					    wl12xx_vif_count_iter, data);
7267b3115f2SLuciano Coelho }
7277b3115f2SLuciano Coelho 
wl12xx_fetch_firmware(struct wl1271 * wl,bool plt)7287b3115f2SLuciano Coelho static int wl12xx_fetch_firmware(struct wl1271 *wl, bool plt)
7297b3115f2SLuciano Coelho {
7307b3115f2SLuciano Coelho 	const struct firmware *fw;
7317b3115f2SLuciano Coelho 	const char *fw_name;
7327b3115f2SLuciano Coelho 	enum wl12xx_fw_type fw_type;
7337b3115f2SLuciano Coelho 	int ret;
7347b3115f2SLuciano Coelho 
7357b3115f2SLuciano Coelho 	if (plt) {
7367b3115f2SLuciano Coelho 		fw_type = WL12XX_FW_TYPE_PLT;
7376f7dd16cSLuciano Coelho 		fw_name = wl->plt_fw_name;
7387b3115f2SLuciano Coelho 	} else {
7397b3115f2SLuciano Coelho 		/*
7407b3115f2SLuciano Coelho 		 * we can't call wl12xx_get_vif_count() here because
7417b3115f2SLuciano Coelho 		 * wl->mutex is taken, so use the cached last_vif_count value
7427b3115f2SLuciano Coelho 		 */
7439b1a0a77SEliad Peller 		if (wl->last_vif_count > 1 && wl->mr_fw_name) {
7447b3115f2SLuciano Coelho 			fw_type = WL12XX_FW_TYPE_MULTI;
7456f7dd16cSLuciano Coelho 			fw_name = wl->mr_fw_name;
7467b3115f2SLuciano Coelho 		} else {
7477b3115f2SLuciano Coelho 			fw_type = WL12XX_FW_TYPE_NORMAL;
7486f7dd16cSLuciano Coelho 			fw_name = wl->sr_fw_name;
7497b3115f2SLuciano Coelho 		}
7507b3115f2SLuciano Coelho 	}
7517b3115f2SLuciano Coelho 
7527b3115f2SLuciano Coelho 	if (wl->fw_type == fw_type)
7537b3115f2SLuciano Coelho 		return 0;
7547b3115f2SLuciano Coelho 
7557b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_BOOT, "booting firmware %s", fw_name);
7567b3115f2SLuciano Coelho 
7577b3115f2SLuciano Coelho 	ret = request_firmware(&fw, fw_name, wl->dev);
7587b3115f2SLuciano Coelho 
7597b3115f2SLuciano Coelho 	if (ret < 0) {
7607b3115f2SLuciano Coelho 		wl1271_error("could not get firmware %s: %d", fw_name, ret);
7617b3115f2SLuciano Coelho 		return ret;
7627b3115f2SLuciano Coelho 	}
7637b3115f2SLuciano Coelho 
7647b3115f2SLuciano Coelho 	if (fw->size % 4) {
7657b3115f2SLuciano Coelho 		wl1271_error("firmware size is not multiple of 32 bits: %zu",
7667b3115f2SLuciano Coelho 			     fw->size);
7677b3115f2SLuciano Coelho 		ret = -EILSEQ;
7687b3115f2SLuciano Coelho 		goto out;
7697b3115f2SLuciano Coelho 	}
7707b3115f2SLuciano Coelho 
7717b3115f2SLuciano Coelho 	vfree(wl->fw);
7727b3115f2SLuciano Coelho 	wl->fw_type = WL12XX_FW_TYPE_NONE;
7737b3115f2SLuciano Coelho 	wl->fw_len = fw->size;
7747b3115f2SLuciano Coelho 	wl->fw = vmalloc(wl->fw_len);
7757b3115f2SLuciano Coelho 
7767b3115f2SLuciano Coelho 	if (!wl->fw) {
7777b3115f2SLuciano Coelho 		wl1271_error("could not allocate memory for the firmware");
7787b3115f2SLuciano Coelho 		ret = -ENOMEM;
7797b3115f2SLuciano Coelho 		goto out;
7807b3115f2SLuciano Coelho 	}
7817b3115f2SLuciano Coelho 
7827b3115f2SLuciano Coelho 	memcpy(wl->fw, fw->data, wl->fw_len);
7837b3115f2SLuciano Coelho 	ret = 0;
7847b3115f2SLuciano Coelho 	wl->fw_type = fw_type;
7857b3115f2SLuciano Coelho out:
7867b3115f2SLuciano Coelho 	release_firmware(fw);
7877b3115f2SLuciano Coelho 
7887b3115f2SLuciano Coelho 	return ret;
7897b3115f2SLuciano Coelho }
7907b3115f2SLuciano Coelho 
wl12xx_queue_recovery_work(struct wl1271 * wl)7917b3115f2SLuciano Coelho void wl12xx_queue_recovery_work(struct wl1271 *wl)
7927b3115f2SLuciano Coelho {
793b666bb7fSIdo Yariv 	/* Avoid a recursive recovery */
794792a58a8SIdo Yariv 	if (wl->state == WLCORE_STATE_ON) {
7951ede9500SArik Nemtsov 		WARN_ON(!test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY,
7961ede9500SArik Nemtsov 				  &wl->flags));
7971ede9500SArik Nemtsov 
7984cc53383SIdo Yariv 		wl->state = WLCORE_STATE_RESTARTING;
799792a58a8SIdo Yariv 		set_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags);
8007b3115f2SLuciano Coelho 		ieee80211_queue_work(wl->hw, &wl->recovery_work);
8017b3115f2SLuciano Coelho 	}
802b666bb7fSIdo Yariv }
8037b3115f2SLuciano Coelho 
wl12xx_copy_fwlog(struct wl1271 * wl,u8 * memblock,size_t maxlen)8047b3115f2SLuciano Coelho size_t wl12xx_copy_fwlog(struct wl1271 *wl, u8 *memblock, size_t maxlen)
8057b3115f2SLuciano Coelho {
806c83cb803SIgal Chernobelsky 	size_t len;
8077b3115f2SLuciano Coelho 
8087b3115f2SLuciano Coelho 	/* Make sure we have enough room */
809c8e49556SSilvan Jegen 	len = min_t(size_t, maxlen, PAGE_SIZE - wl->fwlog_size);
8107b3115f2SLuciano Coelho 
8117b3115f2SLuciano Coelho 	/* Fill the FW log file, consumed by the sysfs fwlog entry */
8127b3115f2SLuciano Coelho 	memcpy(wl->fwlog + wl->fwlog_size, memblock, len);
8137b3115f2SLuciano Coelho 	wl->fwlog_size += len;
8147b3115f2SLuciano Coelho 
8157b3115f2SLuciano Coelho 	return len;
8167b3115f2SLuciano Coelho }
8177b3115f2SLuciano Coelho 
wl12xx_read_fwlog_panic(struct wl1271 * wl)8187b3115f2SLuciano Coelho static void wl12xx_read_fwlog_panic(struct wl1271 *wl)
8197b3115f2SLuciano Coelho {
8203719c17eSShahar Patury 	u32 end_of_log = 0;
821fa2648a3STony Lindgren 	int error;
8227b3115f2SLuciano Coelho 
8233719c17eSShahar Patury 	if (wl->quirks & WLCORE_QUIRK_FWLOG_NOT_IMPLEMENTED)
8247b3115f2SLuciano Coelho 		return;
8257b3115f2SLuciano Coelho 
8267b3115f2SLuciano Coelho 	wl1271_info("Reading FW panic log");
8277b3115f2SLuciano Coelho 
8287b3115f2SLuciano Coelho 	/*
8297b3115f2SLuciano Coelho 	 * Make sure the chip is awake and the logger isn't active.
830847cbebdSEliad Peller 	 * Do not send a stop fwlog command if the fw is hanged or if
831847cbebdSEliad Peller 	 * dbgpins are used (due to some fw bug).
8327b3115f2SLuciano Coelho 	 */
833ab589ac2SMinghao Chi 	error = pm_runtime_resume_and_get(wl->dev);
834ab589ac2SMinghao Chi 	if (error < 0)
8353719c17eSShahar Patury 		return;
836847cbebdSEliad Peller 	if (!wl->watchdog_recovery &&
837847cbebdSEliad Peller 	    wl->conf.fwlog.output != WL12XX_FWLOG_OUTPUT_DBG_PINS)
8381e41213fSIgal Chernobelsky 		wl12xx_cmd_stop_fwlog(wl);
8397b3115f2SLuciano Coelho 
8407b3115f2SLuciano Coelho 	/* Traverse the memory blocks linked list */
8417b3115f2SLuciano Coelho 	do {
8423719c17eSShahar Patury 		end_of_log = wlcore_event_fw_logger(wl);
8433719c17eSShahar Patury 		if (end_of_log == 0) {
8443719c17eSShahar Patury 			msleep(100);
8453719c17eSShahar Patury 			end_of_log = wlcore_event_fw_logger(wl);
846c83cb803SIgal Chernobelsky 		}
8473719c17eSShahar Patury 	} while (end_of_log != 0);
8487b3115f2SLuciano Coelho }
8497b3115f2SLuciano Coelho 
wlcore_save_freed_pkts(struct wl1271 * wl,struct wl12xx_vif * wlvif,u8 hlid,struct ieee80211_sta * sta)85050d26aa3SEliad Peller static void wlcore_save_freed_pkts(struct wl1271 *wl, struct wl12xx_vif *wlvif,
85150d26aa3SEliad Peller 				   u8 hlid, struct ieee80211_sta *sta)
85250d26aa3SEliad Peller {
85350d26aa3SEliad Peller 	struct wl1271_station *wl_sta;
85430a00358SEliad Peller 	u32 sqn_recovery_padding = WL1271_TX_SQN_POST_RECOVERY_PADDING;
85550d26aa3SEliad Peller 
85650d26aa3SEliad Peller 	wl_sta = (void *)sta->drv_priv;
85750d26aa3SEliad Peller 	wl_sta->total_freed_pkts = wl->links[hlid].total_freed_pkts;
85850d26aa3SEliad Peller 
85950d26aa3SEliad Peller 	/*
86050d26aa3SEliad Peller 	 * increment the initial seq number on recovery to account for
86150d26aa3SEliad Peller 	 * transmitted packets that we haven't yet got in the FW status
86250d26aa3SEliad Peller 	 */
86330a00358SEliad Peller 	if (wlvif->encryption_type == KEY_GEM)
86430a00358SEliad Peller 		sqn_recovery_padding = WL1271_TX_SQN_POST_RECOVERY_PADDING_GEM;
86530a00358SEliad Peller 
86650d26aa3SEliad Peller 	if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags))
86730a00358SEliad Peller 		wl_sta->total_freed_pkts += sqn_recovery_padding;
86850d26aa3SEliad Peller }
86950d26aa3SEliad Peller 
wlcore_save_freed_pkts_addr(struct wl1271 * wl,struct wl12xx_vif * wlvif,u8 hlid,const u8 * addr)87050d26aa3SEliad Peller static void wlcore_save_freed_pkts_addr(struct wl1271 *wl,
87150d26aa3SEliad Peller 					struct wl12xx_vif *wlvif,
87250d26aa3SEliad Peller 					u8 hlid, const u8 *addr)
87350d26aa3SEliad Peller {
87450d26aa3SEliad Peller 	struct ieee80211_sta *sta;
87550d26aa3SEliad Peller 	struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
87650d26aa3SEliad Peller 
87750d26aa3SEliad Peller 	if (WARN_ON(hlid == WL12XX_INVALID_LINK_ID ||
87850d26aa3SEliad Peller 		    is_zero_ether_addr(addr)))
87950d26aa3SEliad Peller 		return;
88050d26aa3SEliad Peller 
88150d26aa3SEliad Peller 	rcu_read_lock();
88250d26aa3SEliad Peller 	sta = ieee80211_find_sta(vif, addr);
88350d26aa3SEliad Peller 	if (sta)
88450d26aa3SEliad Peller 		wlcore_save_freed_pkts(wl, wlvif, hlid, sta);
88550d26aa3SEliad Peller 	rcu_read_unlock();
88650d26aa3SEliad Peller }
88750d26aa3SEliad Peller 
wlcore_print_recovery(struct wl1271 * wl)8886134323fSIdo Yariv static void wlcore_print_recovery(struct wl1271 *wl)
8896134323fSIdo Yariv {
8906134323fSIdo Yariv 	u32 pc = 0;
8916134323fSIdo Yariv 	u32 hint_sts = 0;
8926134323fSIdo Yariv 	int ret;
8936134323fSIdo Yariv 
8946134323fSIdo Yariv 	wl1271_info("Hardware recovery in progress. FW ver: %s",
8956134323fSIdo Yariv 		    wl->chip.fw_ver_str);
8966134323fSIdo Yariv 
8976134323fSIdo Yariv 	/* change partitions momentarily so we can read the FW pc */
898b0f0ad39SIdo Yariv 	ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]);
899b0f0ad39SIdo Yariv 	if (ret < 0)
900b0f0ad39SIdo Yariv 		return;
9016134323fSIdo Yariv 
9026134323fSIdo Yariv 	ret = wlcore_read_reg(wl, REG_PC_ON_RECOVERY, &pc);
9036134323fSIdo Yariv 	if (ret < 0)
9046134323fSIdo Yariv 		return;
9056134323fSIdo Yariv 
9066134323fSIdo Yariv 	ret = wlcore_read_reg(wl, REG_INTERRUPT_NO_CLEAR, &hint_sts);
9076134323fSIdo Yariv 	if (ret < 0)
9086134323fSIdo Yariv 		return;
9096134323fSIdo Yariv 
910c108c905SLuciano Coelho 	wl1271_info("pc: 0x%x, hint_sts: 0x%08x count: %d",
911c108c905SLuciano Coelho 				pc, hint_sts, ++wl->recovery_count);
9126134323fSIdo Yariv 
9136134323fSIdo Yariv 	wlcore_set_partition(wl, &wl->ptable[PART_WORK]);
9146134323fSIdo Yariv }
9156134323fSIdo Yariv 
9166134323fSIdo Yariv 
wl1271_recovery_work(struct work_struct * work)9177b3115f2SLuciano Coelho static void wl1271_recovery_work(struct work_struct *work)
9187b3115f2SLuciano Coelho {
9197b3115f2SLuciano Coelho 	struct wl1271 *wl =
9207b3115f2SLuciano Coelho 		container_of(work, struct wl1271, recovery_work);
9217b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
9227b3115f2SLuciano Coelho 	struct ieee80211_vif *vif;
92302edf813STony Lindgren 	int error;
9247b3115f2SLuciano Coelho 
9257b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
9267b3115f2SLuciano Coelho 
9274cc53383SIdo Yariv 	if (wl->state == WLCORE_STATE_OFF || wl->plt)
9287b3115f2SLuciano Coelho 		goto out_unlock;
9297b3115f2SLuciano Coelho 
930ab589ac2SMinghao Chi 	error = pm_runtime_resume_and_get(wl->dev);
931ab589ac2SMinghao Chi 	if (error < 0)
93202edf813STony Lindgren 		wl1271_warning("Enable for recovery failed");
93302edf813STony Lindgren 	wlcore_disable_interrupts_nosync(wl);
93402edf813STony Lindgren 
935aafec111SArik Nemtsov 	if (!test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags)) {
9365cc14c04SBarak Bercovitz 		if (wl->conf.fwlog.output == WL12XX_FWLOG_OUTPUT_HOST)
9377b3115f2SLuciano Coelho 			wl12xx_read_fwlog_panic(wl);
9386134323fSIdo Yariv 		wlcore_print_recovery(wl);
939aafec111SArik Nemtsov 	}
9407b3115f2SLuciano Coelho 
9417230341fSYair Shapira 	BUG_ON(wl->conf.recovery.bug_on_recovery &&
9427b3115f2SLuciano Coelho 	       !test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags));
9437b3115f2SLuciano Coelho 
9444e651badSTony Lindgren 	clear_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags);
9454e651badSTony Lindgren 
9467230341fSYair Shapira 	if (wl->conf.recovery.no_recovery) {
94734785be5SArik Nemtsov 		wl1271_info("No recovery (chosen on module load). Fw will remain stuck.");
94834785be5SArik Nemtsov 		goto out_unlock;
94934785be5SArik Nemtsov 	}
95034785be5SArik Nemtsov 
9517b3115f2SLuciano Coelho 	/* Prevent spurious TX during FW restart */
95266396114SArik Nemtsov 	wlcore_stop_queues(wl, WLCORE_QUEUE_STOP_REASON_FW_RESTART);
9537b3115f2SLuciano Coelho 
9547b3115f2SLuciano Coelho 	/* reboot the chipset */
9557b3115f2SLuciano Coelho 	while (!list_empty(&wl->wlvif_list)) {
9567b3115f2SLuciano Coelho 		wlvif = list_first_entry(&wl->wlvif_list,
9577b3115f2SLuciano Coelho 				       struct wl12xx_vif, list);
9587b3115f2SLuciano Coelho 		vif = wl12xx_wlvif_to_vif(wlvif);
95950d26aa3SEliad Peller 
96050d26aa3SEliad Peller 		if (wlvif->bss_type == BSS_TYPE_STA_BSS &&
96150d26aa3SEliad Peller 		    test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) {
96250d26aa3SEliad Peller 			wlcore_save_freed_pkts_addr(wl, wlvif, wlvif->sta.hlid,
96350d26aa3SEliad Peller 						    vif->bss_conf.bssid);
96450d26aa3SEliad Peller 		}
96550d26aa3SEliad Peller 
9667b3115f2SLuciano Coelho 		__wl1271_op_remove_interface(wl, vif, false);
9677b3115f2SLuciano Coelho 	}
968c24ec83bSIdo Yariv 
969c24ec83bSIdo Yariv 	wlcore_op_stop_locked(wl);
9709b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
9719b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
9727b3115f2SLuciano Coelho 
9737b3115f2SLuciano Coelho 	ieee80211_restart_hw(wl->hw);
9747b3115f2SLuciano Coelho 
9757b3115f2SLuciano Coelho 	/*
9767b3115f2SLuciano Coelho 	 * Its safe to enable TX now - the queues are stopped after a request
9777b3115f2SLuciano Coelho 	 * to restart the HW.
9787b3115f2SLuciano Coelho 	 */
97966396114SArik Nemtsov 	wlcore_wake_queues(wl, WLCORE_QUEUE_STOP_REASON_FW_RESTART);
980c24ec83bSIdo Yariv 
9817b3115f2SLuciano Coelho out_unlock:
982afbe3718SYoni Divinsky 	wl->watchdog_recovery = false;
983b034fd6fSArik Nemtsov 	clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags);
9847b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
9857b3115f2SLuciano Coelho }
9867b3115f2SLuciano Coelho 
wlcore_fw_wakeup(struct wl1271 * wl)987b0f0ad39SIdo Yariv static int wlcore_fw_wakeup(struct wl1271 *wl)
9887b3115f2SLuciano Coelho {
989b0f0ad39SIdo Yariv 	return wlcore_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG, ELPCTRL_WAKE_UP);
9907b3115f2SLuciano Coelho }
9917b3115f2SLuciano Coelho 
wl1271_setup(struct wl1271 * wl)9927b3115f2SLuciano Coelho static int wl1271_setup(struct wl1271 *wl)
9937b3115f2SLuciano Coelho {
99475fb4df7SEliad Peller 	wl->raw_fw_status = kzalloc(wl->fw_status_len, GFP_KERNEL);
99575fb4df7SEliad Peller 	if (!wl->raw_fw_status)
99675fb4df7SEliad Peller 		goto err;
9977b3115f2SLuciano Coelho 
99875fb4df7SEliad Peller 	wl->fw_status = kzalloc(sizeof(*wl->fw_status), GFP_KERNEL);
99975fb4df7SEliad Peller 	if (!wl->fw_status)
100075fb4df7SEliad Peller 		goto err;
10010afd04e5SArik Nemtsov 
10025cbba2d4SVictor Goldenshtein 	wl->tx_res_if = kzalloc(sizeof(*wl->tx_res_if), GFP_KERNEL);
100375fb4df7SEliad Peller 	if (!wl->tx_res_if)
100475fb4df7SEliad Peller 		goto err;
10057b3115f2SLuciano Coelho 
10067b3115f2SLuciano Coelho 	return 0;
100775fb4df7SEliad Peller err:
100875fb4df7SEliad Peller 	kfree(wl->fw_status);
100975fb4df7SEliad Peller 	kfree(wl->raw_fw_status);
101075fb4df7SEliad Peller 	return -ENOMEM;
10117b3115f2SLuciano Coelho }
10127b3115f2SLuciano Coelho 
wl12xx_set_power_on(struct wl1271 * wl)10137b3115f2SLuciano Coelho static int wl12xx_set_power_on(struct wl1271 *wl)
10147b3115f2SLuciano Coelho {
10157b3115f2SLuciano Coelho 	int ret;
10167b3115f2SLuciano Coelho 
10177b3115f2SLuciano Coelho 	msleep(WL1271_PRE_POWER_ON_SLEEP);
10187b3115f2SLuciano Coelho 	ret = wl1271_power_on(wl);
10197b3115f2SLuciano Coelho 	if (ret < 0)
10207b3115f2SLuciano Coelho 		goto out;
10217b3115f2SLuciano Coelho 	msleep(WL1271_POWER_ON_SLEEP);
10227b3115f2SLuciano Coelho 	wl1271_io_reset(wl);
10237b3115f2SLuciano Coelho 	wl1271_io_init(wl);
10247b3115f2SLuciano Coelho 
1025b0f0ad39SIdo Yariv 	ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]);
1026b0f0ad39SIdo Yariv 	if (ret < 0)
1027b0f0ad39SIdo Yariv 		goto fail;
10287b3115f2SLuciano Coelho 
10297b3115f2SLuciano Coelho 	/* ELP module wake up */
1030b0f0ad39SIdo Yariv 	ret = wlcore_fw_wakeup(wl);
1031b0f0ad39SIdo Yariv 	if (ret < 0)
1032b0f0ad39SIdo Yariv 		goto fail;
10337b3115f2SLuciano Coelho 
10347b3115f2SLuciano Coelho out:
10357b3115f2SLuciano Coelho 	return ret;
1036b0f0ad39SIdo Yariv 
1037b0f0ad39SIdo Yariv fail:
1038b0f0ad39SIdo Yariv 	wl1271_power_off(wl);
1039b0f0ad39SIdo Yariv 	return ret;
10407b3115f2SLuciano Coelho }
10417b3115f2SLuciano Coelho 
wl12xx_chip_wakeup(struct wl1271 * wl,bool plt)10427b3115f2SLuciano Coelho static int wl12xx_chip_wakeup(struct wl1271 *wl, bool plt)
10437b3115f2SLuciano Coelho {
10447b3115f2SLuciano Coelho 	int ret = 0;
10457b3115f2SLuciano Coelho 
10467b3115f2SLuciano Coelho 	ret = wl12xx_set_power_on(wl);
10477b3115f2SLuciano Coelho 	if (ret < 0)
10487b3115f2SLuciano Coelho 		goto out;
10497b3115f2SLuciano Coelho 
10507b3115f2SLuciano Coelho 	/*
10517b3115f2SLuciano Coelho 	 * For wl127x based devices we could use the default block
10527b3115f2SLuciano Coelho 	 * size (512 bytes), but due to a bug in the sdio driver, we
10537b3115f2SLuciano Coelho 	 * need to set it explicitly after the chip is powered on.  To
10547b3115f2SLuciano Coelho 	 * simplify the code and since the performance impact is
10557b3115f2SLuciano Coelho 	 * negligible, we use the same block size for all different
10567b3115f2SLuciano Coelho 	 * chip types.
1057b5d6d9b2SLuciano Coelho 	 *
1058b5d6d9b2SLuciano Coelho 	 * Check if the bus supports blocksize alignment and, if it
1059b5d6d9b2SLuciano Coelho 	 * doesn't, make sure we don't have the quirk.
10607b3115f2SLuciano Coelho 	 */
1061b5d6d9b2SLuciano Coelho 	if (!wl1271_set_block_size(wl))
1062b5d6d9b2SLuciano Coelho 		wl->quirks &= ~WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN;
10637b3115f2SLuciano Coelho 
10646f7dd16cSLuciano Coelho 	/* TODO: make sure the lower driver has set things up correctly */
10657b3115f2SLuciano Coelho 
10667b3115f2SLuciano Coelho 	ret = wl1271_setup(wl);
10677b3115f2SLuciano Coelho 	if (ret < 0)
10687b3115f2SLuciano Coelho 		goto out;
10697b3115f2SLuciano Coelho 
10707b3115f2SLuciano Coelho 	ret = wl12xx_fetch_firmware(wl, plt);
1071ba2ffc96SZumeng Chen 	if (ret < 0) {
1072ba2ffc96SZumeng Chen 		kfree(wl->fw_status);
1073ba2ffc96SZumeng Chen 		kfree(wl->raw_fw_status);
1074ba2ffc96SZumeng Chen 		kfree(wl->tx_res_if);
1075ba2ffc96SZumeng Chen 	}
10767b3115f2SLuciano Coelho 
10777b3115f2SLuciano Coelho out:
10787b3115f2SLuciano Coelho 	return ret;
10797b3115f2SLuciano Coelho }
10807b3115f2SLuciano Coelho 
wl1271_plt_start(struct wl1271 * wl,const enum plt_mode plt_mode)10817019c80eSYair Shapira int wl1271_plt_start(struct wl1271 *wl, const enum plt_mode plt_mode)
10827b3115f2SLuciano Coelho {
10837b3115f2SLuciano Coelho 	int retries = WL1271_BOOT_RETRIES;
10847b3115f2SLuciano Coelho 	struct wiphy *wiphy = wl->hw->wiphy;
10857019c80eSYair Shapira 
10867019c80eSYair Shapira 	static const char* const PLT_MODE[] = {
10877019c80eSYair Shapira 		"PLT_OFF",
10887019c80eSYair Shapira 		"PLT_ON",
1089dd491ffbSYair Shapira 		"PLT_FEM_DETECT",
1090dd491ffbSYair Shapira 		"PLT_CHIP_AWAKE"
10917019c80eSYair Shapira 	};
10927019c80eSYair Shapira 
10937b3115f2SLuciano Coelho 	int ret;
10947b3115f2SLuciano Coelho 
10957b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
10967b3115f2SLuciano Coelho 
10977b3115f2SLuciano Coelho 	wl1271_notice("power up");
10987b3115f2SLuciano Coelho 
10994cc53383SIdo Yariv 	if (wl->state != WLCORE_STATE_OFF) {
11007b3115f2SLuciano Coelho 		wl1271_error("cannot go into PLT state because not "
11017b3115f2SLuciano Coelho 			     "in off state: %d", wl->state);
11027b3115f2SLuciano Coelho 		ret = -EBUSY;
11037b3115f2SLuciano Coelho 		goto out;
11047b3115f2SLuciano Coelho 	}
11057b3115f2SLuciano Coelho 
11067019c80eSYair Shapira 	/* Indicate to lower levels that we are now in PLT mode */
11077019c80eSYair Shapira 	wl->plt = true;
11087019c80eSYair Shapira 	wl->plt_mode = plt_mode;
11097019c80eSYair Shapira 
11107b3115f2SLuciano Coelho 	while (retries) {
11117b3115f2SLuciano Coelho 		retries--;
11127b3115f2SLuciano Coelho 		ret = wl12xx_chip_wakeup(wl, true);
11137b3115f2SLuciano Coelho 		if (ret < 0)
11147b3115f2SLuciano Coelho 			goto power_off;
11157b3115f2SLuciano Coelho 
1116dd491ffbSYair Shapira 		if (plt_mode != PLT_CHIP_AWAKE) {
1117c331b344SLuciano Coelho 			ret = wl->ops->plt_init(wl);
11187b3115f2SLuciano Coelho 			if (ret < 0)
11197b3115f2SLuciano Coelho 				goto power_off;
1120dd491ffbSYair Shapira 		}
11217b3115f2SLuciano Coelho 
11224cc53383SIdo Yariv 		wl->state = WLCORE_STATE_ON;
11237019c80eSYair Shapira 		wl1271_notice("firmware booted in PLT mode %s (%s)",
11247019c80eSYair Shapira 			      PLT_MODE[plt_mode],
11257b3115f2SLuciano Coelho 			      wl->chip.fw_ver_str);
11267b3115f2SLuciano Coelho 
11277b3115f2SLuciano Coelho 		/* update hw/fw version info in wiphy struct */
11287b3115f2SLuciano Coelho 		wiphy->hw_version = wl->chip.id;
11297b3115f2SLuciano Coelho 		strncpy(wiphy->fw_version, wl->chip.fw_ver_str,
11307b3115f2SLuciano Coelho 			sizeof(wiphy->fw_version));
11317b3115f2SLuciano Coelho 
11327b3115f2SLuciano Coelho 		goto out;
11337b3115f2SLuciano Coelho 
11347b3115f2SLuciano Coelho power_off:
11357b3115f2SLuciano Coelho 		wl1271_power_off(wl);
11367b3115f2SLuciano Coelho 	}
11377b3115f2SLuciano Coelho 
11387019c80eSYair Shapira 	wl->plt = false;
11397019c80eSYair Shapira 	wl->plt_mode = PLT_OFF;
11407019c80eSYair Shapira 
11417b3115f2SLuciano Coelho 	wl1271_error("firmware boot in PLT mode failed despite %d retries",
11427b3115f2SLuciano Coelho 		     WL1271_BOOT_RETRIES);
11437b3115f2SLuciano Coelho out:
11447b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
11457b3115f2SLuciano Coelho 
11467b3115f2SLuciano Coelho 	return ret;
11477b3115f2SLuciano Coelho }
11487b3115f2SLuciano Coelho 
wl1271_plt_stop(struct wl1271 * wl)11497b3115f2SLuciano Coelho int wl1271_plt_stop(struct wl1271 *wl)
11507b3115f2SLuciano Coelho {
11517b3115f2SLuciano Coelho 	int ret = 0;
11527b3115f2SLuciano Coelho 
11537b3115f2SLuciano Coelho 	wl1271_notice("power down");
11547b3115f2SLuciano Coelho 
11557b3115f2SLuciano Coelho 	/*
11567b3115f2SLuciano Coelho 	 * Interrupts must be disabled before setting the state to OFF.
11577b3115f2SLuciano Coelho 	 * Otherwise, the interrupt handler might be called and exit without
11587b3115f2SLuciano Coelho 	 * reading the interrupt status.
11597b3115f2SLuciano Coelho 	 */
1160dd5512ebSLuciano Coelho 	wlcore_disable_interrupts(wl);
11617b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
11627b3115f2SLuciano Coelho 	if (!wl->plt) {
11637b3115f2SLuciano Coelho 		mutex_unlock(&wl->mutex);
11647b3115f2SLuciano Coelho 
11657b3115f2SLuciano Coelho 		/*
11667b3115f2SLuciano Coelho 		 * This will not necessarily enable interrupts as interrupts
11677b3115f2SLuciano Coelho 		 * may have been disabled when op_stop was called. It will,
11687b3115f2SLuciano Coelho 		 * however, balance the above call to disable_interrupts().
11697b3115f2SLuciano Coelho 		 */
1170dd5512ebSLuciano Coelho 		wlcore_enable_interrupts(wl);
11717b3115f2SLuciano Coelho 
11727b3115f2SLuciano Coelho 		wl1271_error("cannot power down because not in PLT "
11737b3115f2SLuciano Coelho 			     "state: %d", wl->state);
11747b3115f2SLuciano Coelho 		ret = -EBUSY;
11757b3115f2SLuciano Coelho 		goto out;
11767b3115f2SLuciano Coelho 	}
11777b3115f2SLuciano Coelho 
11787b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
11797b3115f2SLuciano Coelho 
11807b3115f2SLuciano Coelho 	wl1271_flush_deferred_work(wl);
11817b3115f2SLuciano Coelho 	cancel_work_sync(&wl->netstack_work);
11827b3115f2SLuciano Coelho 	cancel_work_sync(&wl->recovery_work);
11837b3115f2SLuciano Coelho 	cancel_delayed_work_sync(&wl->tx_watchdog_work);
11847b3115f2SLuciano Coelho 
11857b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
11867b3115f2SLuciano Coelho 	wl1271_power_off(wl);
11877b3115f2SLuciano Coelho 	wl->flags = 0;
11882f18cf7cSArik Nemtsov 	wl->sleep_auth = WL1271_PSM_ILLEGAL;
11894cc53383SIdo Yariv 	wl->state = WLCORE_STATE_OFF;
11907b3115f2SLuciano Coelho 	wl->plt = false;
11917019c80eSYair Shapira 	wl->plt_mode = PLT_OFF;
11927b3115f2SLuciano Coelho 	wl->rx_counter = 0;
11937b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
11947b3115f2SLuciano Coelho 
11957b3115f2SLuciano Coelho out:
11967b3115f2SLuciano Coelho 	return ret;
11977b3115f2SLuciano Coelho }
11987b3115f2SLuciano Coelho 
wl1271_op_tx(struct ieee80211_hw * hw,struct ieee80211_tx_control * control,struct sk_buff * skb)119936323f81SThomas Huehn static void wl1271_op_tx(struct ieee80211_hw *hw,
120036323f81SThomas Huehn 			 struct ieee80211_tx_control *control,
120136323f81SThomas Huehn 			 struct sk_buff *skb)
12027b3115f2SLuciano Coelho {
12037b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
12047b3115f2SLuciano Coelho 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
12057b3115f2SLuciano Coelho 	struct ieee80211_vif *vif = info->control.vif;
12067b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = NULL;
12077b3115f2SLuciano Coelho 	unsigned long flags;
12087b3115f2SLuciano Coelho 	int q, mapping;
12097b3115f2SLuciano Coelho 	u8 hlid;
12107b3115f2SLuciano Coelho 
1211f4d02007SArik Nemtsov 	if (!vif) {
1212f4d02007SArik Nemtsov 		wl1271_debug(DEBUG_TX, "DROP skb with no vif");
1213f4d02007SArik Nemtsov 		ieee80211_free_txskb(hw, skb);
1214f4d02007SArik Nemtsov 		return;
1215f4d02007SArik Nemtsov 	}
12167b3115f2SLuciano Coelho 
1217f4d02007SArik Nemtsov 	wlvif = wl12xx_vif_to_data(vif);
12187b3115f2SLuciano Coelho 	mapping = skb_get_queue_mapping(skb);
12197b3115f2SLuciano Coelho 	q = wl1271_tx_get_queue(mapping);
12207b3115f2SLuciano Coelho 
122136323f81SThomas Huehn 	hlid = wl12xx_tx_get_hlid(wl, wlvif, skb, control->sta);
12227b3115f2SLuciano Coelho 
12237b3115f2SLuciano Coelho 	spin_lock_irqsave(&wl->wl_lock, flags);
12247b3115f2SLuciano Coelho 
122566396114SArik Nemtsov 	/*
122666396114SArik Nemtsov 	 * drop the packet if the link is invalid or the queue is stopped
122766396114SArik Nemtsov 	 * for any reason but watermark. Watermark is a "soft"-stop so we
122866396114SArik Nemtsov 	 * allow these packets through.
122966396114SArik Nemtsov 	 */
12307b3115f2SLuciano Coelho 	if (hlid == WL12XX_INVALID_LINK_ID ||
1231f4d02007SArik Nemtsov 	    (!test_bit(hlid, wlvif->links_map)) ||
1232d6037d22SArik Nemtsov 	     (wlcore_is_queue_stopped_locked(wl, wlvif, q) &&
1233d6037d22SArik Nemtsov 	      !wlcore_is_queue_stopped_by_reason_locked(wl, wlvif, q,
123466396114SArik Nemtsov 			WLCORE_QUEUE_STOP_REASON_WATERMARK))) {
12357b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_TX, "DROP skb hlid %d q %d", hlid, q);
12367b3115f2SLuciano Coelho 		ieee80211_free_txskb(hw, skb);
12377b3115f2SLuciano Coelho 		goto out;
12387b3115f2SLuciano Coelho 	}
12397b3115f2SLuciano Coelho 
12407b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_TX, "queue skb hlid %d q %d len %d",
12417b3115f2SLuciano Coelho 		     hlid, q, skb->len);
12427b3115f2SLuciano Coelho 	skb_queue_tail(&wl->links[hlid].tx_queue[q], skb);
12437b3115f2SLuciano Coelho 
12447b3115f2SLuciano Coelho 	wl->tx_queue_count[q]++;
12458591d424SArik Nemtsov 	wlvif->tx_queue_count[q]++;
12467b3115f2SLuciano Coelho 
12477b3115f2SLuciano Coelho 	/*
12487b3115f2SLuciano Coelho 	 * The workqueue is slow to process the tx_queue and we need stop
12497b3115f2SLuciano Coelho 	 * the queue here, otherwise the queue will get too long.
12507b3115f2SLuciano Coelho 	 */
12511c33db78SArik Nemtsov 	if (wlvif->tx_queue_count[q] >= WL1271_TX_QUEUE_HIGH_WATERMARK &&
1252d6037d22SArik Nemtsov 	    !wlcore_is_queue_stopped_by_reason_locked(wl, wlvif, q,
12538cdc44aaSArik Nemtsov 					WLCORE_QUEUE_STOP_REASON_WATERMARK)) {
12547b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_TX, "op_tx: stopping queues for q %d", q);
12551c33db78SArik Nemtsov 		wlcore_stop_queue_locked(wl, wlvif, q,
125666396114SArik Nemtsov 					 WLCORE_QUEUE_STOP_REASON_WATERMARK);
12577b3115f2SLuciano Coelho 	}
12587b3115f2SLuciano Coelho 
12597b3115f2SLuciano Coelho 	/*
12607b3115f2SLuciano Coelho 	 * The chip specific setup must run before the first TX packet -
12617b3115f2SLuciano Coelho 	 * before that, the tx_work will not be initialized!
12627b3115f2SLuciano Coelho 	 */
12637b3115f2SLuciano Coelho 
12647b3115f2SLuciano Coelho 	if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) &&
12657b3115f2SLuciano Coelho 	    !test_bit(WL1271_FLAG_TX_PENDING, &wl->flags))
12667b3115f2SLuciano Coelho 		ieee80211_queue_work(wl->hw, &wl->tx_work);
12677b3115f2SLuciano Coelho 
12687b3115f2SLuciano Coelho out:
12697b3115f2SLuciano Coelho 	spin_unlock_irqrestore(&wl->wl_lock, flags);
12707b3115f2SLuciano Coelho }
12717b3115f2SLuciano Coelho 
wl1271_tx_dummy_packet(struct wl1271 * wl)12727b3115f2SLuciano Coelho int wl1271_tx_dummy_packet(struct wl1271 *wl)
12737b3115f2SLuciano Coelho {
12747b3115f2SLuciano Coelho 	unsigned long flags;
12757b3115f2SLuciano Coelho 	int q;
12767b3115f2SLuciano Coelho 
12777b3115f2SLuciano Coelho 	/* no need to queue a new dummy packet if one is already pending */
12787b3115f2SLuciano Coelho 	if (test_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags))
12797b3115f2SLuciano Coelho 		return 0;
12807b3115f2SLuciano Coelho 
12817b3115f2SLuciano Coelho 	q = wl1271_tx_get_queue(skb_get_queue_mapping(wl->dummy_packet));
12827b3115f2SLuciano Coelho 
12837b3115f2SLuciano Coelho 	spin_lock_irqsave(&wl->wl_lock, flags);
12847b3115f2SLuciano Coelho 	set_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags);
12857b3115f2SLuciano Coelho 	wl->tx_queue_count[q]++;
12867b3115f2SLuciano Coelho 	spin_unlock_irqrestore(&wl->wl_lock, flags);
12877b3115f2SLuciano Coelho 
12887b3115f2SLuciano Coelho 	/* The FW is low on RX memory blocks, so send the dummy packet asap */
12897b3115f2SLuciano Coelho 	if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags))
1290eb96f841SIdo Yariv 		return wlcore_tx_work_locked(wl);
12917b3115f2SLuciano Coelho 
12927b3115f2SLuciano Coelho 	/*
12937b3115f2SLuciano Coelho 	 * If the FW TX is busy, TX work will be scheduled by the threaded
12947b3115f2SLuciano Coelho 	 * interrupt handler function
12957b3115f2SLuciano Coelho 	 */
12967b3115f2SLuciano Coelho 	return 0;
12977b3115f2SLuciano Coelho }
12987b3115f2SLuciano Coelho 
12997b3115f2SLuciano Coelho /*
13007b3115f2SLuciano Coelho  * The size of the dummy packet should be at least 1400 bytes. However, in
13017b3115f2SLuciano Coelho  * order to minimize the number of bus transactions, aligning it to 512 bytes
13027b3115f2SLuciano Coelho  * boundaries could be beneficial, performance wise
13037b3115f2SLuciano Coelho  */
13047b3115f2SLuciano Coelho #define TOTAL_TX_DUMMY_PACKET_SIZE (ALIGN(1400, 512))
13057b3115f2SLuciano Coelho 
wl12xx_alloc_dummy_packet(struct wl1271 * wl)13067b3115f2SLuciano Coelho static struct sk_buff *wl12xx_alloc_dummy_packet(struct wl1271 *wl)
13077b3115f2SLuciano Coelho {
13087b3115f2SLuciano Coelho 	struct sk_buff *skb;
13097b3115f2SLuciano Coelho 	struct ieee80211_hdr_3addr *hdr;
13107b3115f2SLuciano Coelho 	unsigned int dummy_packet_size;
13117b3115f2SLuciano Coelho 
13127b3115f2SLuciano Coelho 	dummy_packet_size = TOTAL_TX_DUMMY_PACKET_SIZE -
13137b3115f2SLuciano Coelho 			    sizeof(struct wl1271_tx_hw_descr) - sizeof(*hdr);
13147b3115f2SLuciano Coelho 
13157b3115f2SLuciano Coelho 	skb = dev_alloc_skb(TOTAL_TX_DUMMY_PACKET_SIZE);
13167b3115f2SLuciano Coelho 	if (!skb) {
13177b3115f2SLuciano Coelho 		wl1271_warning("Failed to allocate a dummy packet skb");
13187b3115f2SLuciano Coelho 		return NULL;
13197b3115f2SLuciano Coelho 	}
13207b3115f2SLuciano Coelho 
13217b3115f2SLuciano Coelho 	skb_reserve(skb, sizeof(struct wl1271_tx_hw_descr));
13227b3115f2SLuciano Coelho 
1323b080db58SJohannes Berg 	hdr = skb_put_zero(skb, sizeof(*hdr));
13247b3115f2SLuciano Coelho 	hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
13257b3115f2SLuciano Coelho 					 IEEE80211_STYPE_NULLFUNC |
13267b3115f2SLuciano Coelho 					 IEEE80211_FCTL_TODS);
13277b3115f2SLuciano Coelho 
1328b080db58SJohannes Berg 	skb_put_zero(skb, dummy_packet_size);
13297b3115f2SLuciano Coelho 
13307b3115f2SLuciano Coelho 	/* Dummy packets require the TID to be management */
13317b3115f2SLuciano Coelho 	skb->priority = WL1271_TID_MGMT;
13327b3115f2SLuciano Coelho 
13337b3115f2SLuciano Coelho 	/* Initialize all fields that might be used */
13347b3115f2SLuciano Coelho 	skb_set_queue_mapping(skb, 0);
13357b3115f2SLuciano Coelho 	memset(IEEE80211_SKB_CB(skb), 0, sizeof(struct ieee80211_tx_info));
13367b3115f2SLuciano Coelho 
13377b3115f2SLuciano Coelho 	return skb;
13387b3115f2SLuciano Coelho }
13397b3115f2SLuciano Coelho 
13407b3115f2SLuciano Coelho 
134122479972SLuciano Coelho static int
wl1271_validate_wowlan_pattern(struct cfg80211_pkt_pattern * p)134250ac6607SAmitkumar Karwar wl1271_validate_wowlan_pattern(struct cfg80211_pkt_pattern *p)
1343b95d7cefSEyal Shapira {
1344b95d7cefSEyal Shapira 	int num_fields = 0, in_field = 0, fields_size = 0;
1345b95d7cefSEyal Shapira 	int i, pattern_len = 0;
1346b95d7cefSEyal Shapira 
1347b95d7cefSEyal Shapira 	if (!p->mask) {
1348b95d7cefSEyal Shapira 		wl1271_warning("No mask in WoWLAN pattern");
1349b95d7cefSEyal Shapira 		return -EINVAL;
1350b95d7cefSEyal Shapira 	}
1351b95d7cefSEyal Shapira 
1352b95d7cefSEyal Shapira 	/*
1353b95d7cefSEyal Shapira 	 * The pattern is broken up into segments of bytes at different offsets
1354b95d7cefSEyal Shapira 	 * that need to be checked by the FW filter. Each segment is called
1355b95d7cefSEyal Shapira 	 * a field in the FW API. We verify that the total number of fields
1356b95d7cefSEyal Shapira 	 * required for this pattern won't exceed FW limits (8)
1357b95d7cefSEyal Shapira 	 * as well as the total fields buffer won't exceed the FW limit.
1358b95d7cefSEyal Shapira 	 * Note that if there's a pattern which crosses Ethernet/IP header
1359b95d7cefSEyal Shapira 	 * boundary a new field is required.
1360b95d7cefSEyal Shapira 	 */
1361b95d7cefSEyal Shapira 	for (i = 0; i < p->pattern_len; i++) {
1362b95d7cefSEyal Shapira 		if (test_bit(i, (unsigned long *)p->mask)) {
1363b95d7cefSEyal Shapira 			if (!in_field) {
1364b95d7cefSEyal Shapira 				in_field = 1;
1365b95d7cefSEyal Shapira 				pattern_len = 1;
1366b95d7cefSEyal Shapira 			} else {
1367b95d7cefSEyal Shapira 				if (i == WL1271_RX_FILTER_ETH_HEADER_SIZE) {
1368b95d7cefSEyal Shapira 					num_fields++;
1369b95d7cefSEyal Shapira 					fields_size += pattern_len +
1370b95d7cefSEyal Shapira 						RX_FILTER_FIELD_OVERHEAD;
1371b95d7cefSEyal Shapira 					pattern_len = 1;
1372b95d7cefSEyal Shapira 				} else
1373b95d7cefSEyal Shapira 					pattern_len++;
1374b95d7cefSEyal Shapira 			}
1375b95d7cefSEyal Shapira 		} else {
1376b95d7cefSEyal Shapira 			if (in_field) {
1377b95d7cefSEyal Shapira 				in_field = 0;
1378b95d7cefSEyal Shapira 				fields_size += pattern_len +
1379b95d7cefSEyal Shapira 					RX_FILTER_FIELD_OVERHEAD;
1380b95d7cefSEyal Shapira 				num_fields++;
1381b95d7cefSEyal Shapira 			}
1382b95d7cefSEyal Shapira 		}
1383b95d7cefSEyal Shapira 	}
1384b95d7cefSEyal Shapira 
1385b95d7cefSEyal Shapira 	if (in_field) {
1386b95d7cefSEyal Shapira 		fields_size += pattern_len + RX_FILTER_FIELD_OVERHEAD;
1387b95d7cefSEyal Shapira 		num_fields++;
1388b95d7cefSEyal Shapira 	}
1389b95d7cefSEyal Shapira 
1390b95d7cefSEyal Shapira 	if (num_fields > WL1271_RX_FILTER_MAX_FIELDS) {
1391b95d7cefSEyal Shapira 		wl1271_warning("RX Filter too complex. Too many segments");
1392b95d7cefSEyal Shapira 		return -EINVAL;
1393b95d7cefSEyal Shapira 	}
1394b95d7cefSEyal Shapira 
1395b95d7cefSEyal Shapira 	if (fields_size > WL1271_RX_FILTER_MAX_FIELDS_SIZE) {
1396b95d7cefSEyal Shapira 		wl1271_warning("RX filter pattern is too big");
1397b95d7cefSEyal Shapira 		return -E2BIG;
1398b95d7cefSEyal Shapira 	}
1399b95d7cefSEyal Shapira 
1400b95d7cefSEyal Shapira 	return 0;
1401b95d7cefSEyal Shapira }
1402b95d7cefSEyal Shapira 
wl1271_rx_filter_alloc(void)1403a6eab0c8SEyal Shapira struct wl12xx_rx_filter *wl1271_rx_filter_alloc(void)
1404a6eab0c8SEyal Shapira {
1405a6eab0c8SEyal Shapira 	return kzalloc(sizeof(struct wl12xx_rx_filter), GFP_KERNEL);
1406a6eab0c8SEyal Shapira }
1407a6eab0c8SEyal Shapira 
wl1271_rx_filter_free(struct wl12xx_rx_filter * filter)1408a6eab0c8SEyal Shapira void wl1271_rx_filter_free(struct wl12xx_rx_filter *filter)
1409a6eab0c8SEyal Shapira {
1410a6eab0c8SEyal Shapira 	int i;
1411a6eab0c8SEyal Shapira 
1412a6eab0c8SEyal Shapira 	if (filter == NULL)
1413a6eab0c8SEyal Shapira 		return;
1414a6eab0c8SEyal Shapira 
1415a6eab0c8SEyal Shapira 	for (i = 0; i < filter->num_fields; i++)
1416a6eab0c8SEyal Shapira 		kfree(filter->fields[i].pattern);
1417a6eab0c8SEyal Shapira 
1418a6eab0c8SEyal Shapira 	kfree(filter);
1419a6eab0c8SEyal Shapira }
1420a6eab0c8SEyal Shapira 
wl1271_rx_filter_alloc_field(struct wl12xx_rx_filter * filter,u16 offset,u8 flags,const u8 * pattern,u8 len)1421a6eab0c8SEyal Shapira int wl1271_rx_filter_alloc_field(struct wl12xx_rx_filter *filter,
1422a6eab0c8SEyal Shapira 				 u16 offset, u8 flags,
1423922bd80fSJohannes Berg 				 const u8 *pattern, u8 len)
1424a6eab0c8SEyal Shapira {
1425a6eab0c8SEyal Shapira 	struct wl12xx_rx_filter_field *field;
1426a6eab0c8SEyal Shapira 
1427a6eab0c8SEyal Shapira 	if (filter->num_fields == WL1271_RX_FILTER_MAX_FIELDS) {
1428a6eab0c8SEyal Shapira 		wl1271_warning("Max fields per RX filter. can't alloc another");
1429a6eab0c8SEyal Shapira 		return -EINVAL;
1430a6eab0c8SEyal Shapira 	}
1431a6eab0c8SEyal Shapira 
1432a6eab0c8SEyal Shapira 	field = &filter->fields[filter->num_fields];
1433a6eab0c8SEyal Shapira 
1434ab8c31ddSFuqian Huang 	field->pattern = kmemdup(pattern, len, GFP_KERNEL);
1435a6eab0c8SEyal Shapira 	if (!field->pattern) {
1436a6eab0c8SEyal Shapira 		wl1271_warning("Failed to allocate RX filter pattern");
1437a6eab0c8SEyal Shapira 		return -ENOMEM;
1438a6eab0c8SEyal Shapira 	}
1439a6eab0c8SEyal Shapira 
1440a6eab0c8SEyal Shapira 	filter->num_fields++;
1441a6eab0c8SEyal Shapira 
1442a6eab0c8SEyal Shapira 	field->offset = cpu_to_le16(offset);
1443a6eab0c8SEyal Shapira 	field->flags = flags;
1444a6eab0c8SEyal Shapira 	field->len = len;
1445a6eab0c8SEyal Shapira 
1446a6eab0c8SEyal Shapira 	return 0;
1447a6eab0c8SEyal Shapira }
1448a6eab0c8SEyal Shapira 
wl1271_rx_filter_get_fields_size(struct wl12xx_rx_filter * filter)1449a6eab0c8SEyal Shapira int wl1271_rx_filter_get_fields_size(struct wl12xx_rx_filter *filter)
1450a6eab0c8SEyal Shapira {
1451a6eab0c8SEyal Shapira 	int i, fields_size = 0;
1452a6eab0c8SEyal Shapira 
1453a6eab0c8SEyal Shapira 	for (i = 0; i < filter->num_fields; i++)
1454a6eab0c8SEyal Shapira 		fields_size += filter->fields[i].len +
1455a6eab0c8SEyal Shapira 			sizeof(struct wl12xx_rx_filter_field) -
1456a6eab0c8SEyal Shapira 			sizeof(u8 *);
1457a6eab0c8SEyal Shapira 
1458a6eab0c8SEyal Shapira 	return fields_size;
1459a6eab0c8SEyal Shapira }
1460a6eab0c8SEyal Shapira 
wl1271_rx_filter_flatten_fields(struct wl12xx_rx_filter * filter,u8 * buf)1461a6eab0c8SEyal Shapira void wl1271_rx_filter_flatten_fields(struct wl12xx_rx_filter *filter,
1462a6eab0c8SEyal Shapira 				    u8 *buf)
1463a6eab0c8SEyal Shapira {
1464a6eab0c8SEyal Shapira 	int i;
1465a6eab0c8SEyal Shapira 	struct wl12xx_rx_filter_field *field;
1466a6eab0c8SEyal Shapira 
1467a6eab0c8SEyal Shapira 	for (i = 0; i < filter->num_fields; i++) {
1468a6eab0c8SEyal Shapira 		field = (struct wl12xx_rx_filter_field *)buf;
1469a6eab0c8SEyal Shapira 
1470a6eab0c8SEyal Shapira 		field->offset = filter->fields[i].offset;
1471a6eab0c8SEyal Shapira 		field->flags = filter->fields[i].flags;
1472a6eab0c8SEyal Shapira 		field->len = filter->fields[i].len;
1473a6eab0c8SEyal Shapira 
1474a6eab0c8SEyal Shapira 		memcpy(&field->pattern, filter->fields[i].pattern, field->len);
1475a6eab0c8SEyal Shapira 		buf += sizeof(struct wl12xx_rx_filter_field) -
1476a6eab0c8SEyal Shapira 			sizeof(u8 *) + field->len;
1477a6eab0c8SEyal Shapira 	}
1478a6eab0c8SEyal Shapira }
1479a6eab0c8SEyal Shapira 
1480b95d7cefSEyal Shapira /*
1481b95d7cefSEyal Shapira  * Allocates an RX filter returned through f
1482b95d7cefSEyal Shapira  * which needs to be freed using rx_filter_free()
1483b95d7cefSEyal Shapira  */
148450ac6607SAmitkumar Karwar static int
wl1271_convert_wowlan_pattern_to_rx_filter(struct cfg80211_pkt_pattern * p,struct wl12xx_rx_filter ** f)148550ac6607SAmitkumar Karwar wl1271_convert_wowlan_pattern_to_rx_filter(struct cfg80211_pkt_pattern *p,
1486b95d7cefSEyal Shapira 					   struct wl12xx_rx_filter **f)
1487b95d7cefSEyal Shapira {
1488b95d7cefSEyal Shapira 	int i, j, ret = 0;
1489b95d7cefSEyal Shapira 	struct wl12xx_rx_filter *filter;
1490b95d7cefSEyal Shapira 	u16 offset;
1491b95d7cefSEyal Shapira 	u8 flags, len;
1492b95d7cefSEyal Shapira 
1493b95d7cefSEyal Shapira 	filter = wl1271_rx_filter_alloc();
1494b95d7cefSEyal Shapira 	if (!filter) {
1495b95d7cefSEyal Shapira 		wl1271_warning("Failed to alloc rx filter");
1496b95d7cefSEyal Shapira 		ret = -ENOMEM;
1497b95d7cefSEyal Shapira 		goto err;
1498b95d7cefSEyal Shapira 	}
1499b95d7cefSEyal Shapira 
1500b95d7cefSEyal Shapira 	i = 0;
1501b95d7cefSEyal Shapira 	while (i < p->pattern_len) {
1502b95d7cefSEyal Shapira 		if (!test_bit(i, (unsigned long *)p->mask)) {
1503b95d7cefSEyal Shapira 			i++;
1504b95d7cefSEyal Shapira 			continue;
1505b95d7cefSEyal Shapira 		}
1506b95d7cefSEyal Shapira 
1507b95d7cefSEyal Shapira 		for (j = i; j < p->pattern_len; j++) {
1508b95d7cefSEyal Shapira 			if (!test_bit(j, (unsigned long *)p->mask))
1509b95d7cefSEyal Shapira 				break;
1510b95d7cefSEyal Shapira 
1511b95d7cefSEyal Shapira 			if (i < WL1271_RX_FILTER_ETH_HEADER_SIZE &&
1512b95d7cefSEyal Shapira 			    j >= WL1271_RX_FILTER_ETH_HEADER_SIZE)
1513b95d7cefSEyal Shapira 				break;
1514b95d7cefSEyal Shapira 		}
1515b95d7cefSEyal Shapira 
1516b95d7cefSEyal Shapira 		if (i < WL1271_RX_FILTER_ETH_HEADER_SIZE) {
1517b95d7cefSEyal Shapira 			offset = i;
1518b95d7cefSEyal Shapira 			flags = WL1271_RX_FILTER_FLAG_ETHERNET_HEADER;
1519b95d7cefSEyal Shapira 		} else {
1520b95d7cefSEyal Shapira 			offset = i - WL1271_RX_FILTER_ETH_HEADER_SIZE;
1521b95d7cefSEyal Shapira 			flags = WL1271_RX_FILTER_FLAG_IP_HEADER;
1522b95d7cefSEyal Shapira 		}
1523b95d7cefSEyal Shapira 
1524b95d7cefSEyal Shapira 		len = j - i;
1525b95d7cefSEyal Shapira 
1526b95d7cefSEyal Shapira 		ret = wl1271_rx_filter_alloc_field(filter,
1527b95d7cefSEyal Shapira 						   offset,
1528b95d7cefSEyal Shapira 						   flags,
1529b95d7cefSEyal Shapira 						   &p->pattern[i], len);
1530b95d7cefSEyal Shapira 		if (ret)
1531b95d7cefSEyal Shapira 			goto err;
1532b95d7cefSEyal Shapira 
1533b95d7cefSEyal Shapira 		i = j;
1534b95d7cefSEyal Shapira 	}
1535b95d7cefSEyal Shapira 
1536b95d7cefSEyal Shapira 	filter->action = FILTER_SIGNAL;
1537b95d7cefSEyal Shapira 
1538b95d7cefSEyal Shapira 	*f = filter;
1539b95d7cefSEyal Shapira 	return 0;
1540b95d7cefSEyal Shapira 
1541b95d7cefSEyal Shapira err:
1542b95d7cefSEyal Shapira 	wl1271_rx_filter_free(filter);
1543b95d7cefSEyal Shapira 	*f = NULL;
1544b95d7cefSEyal Shapira 
1545b95d7cefSEyal Shapira 	return ret;
1546b95d7cefSEyal Shapira }
1547b95d7cefSEyal Shapira 
wl1271_configure_wowlan(struct wl1271 * wl,struct cfg80211_wowlan * wow)1548b95d7cefSEyal Shapira static int wl1271_configure_wowlan(struct wl1271 *wl,
1549b95d7cefSEyal Shapira 				   struct cfg80211_wowlan *wow)
1550b95d7cefSEyal Shapira {
1551b95d7cefSEyal Shapira 	int i, ret;
1552b95d7cefSEyal Shapira 
1553b95d7cefSEyal Shapira 	if (!wow || wow->any || !wow->n_patterns) {
1554c439a1caSArik Nemtsov 		ret = wl1271_acx_default_rx_filter_enable(wl, 0,
1555c439a1caSArik Nemtsov 							  FILTER_SIGNAL);
1556c439a1caSArik Nemtsov 		if (ret)
1557c439a1caSArik Nemtsov 			goto out;
1558c439a1caSArik Nemtsov 
1559c439a1caSArik Nemtsov 		ret = wl1271_rx_filter_clear_all(wl);
1560c439a1caSArik Nemtsov 		if (ret)
1561c439a1caSArik Nemtsov 			goto out;
1562c439a1caSArik Nemtsov 
1563b95d7cefSEyal Shapira 		return 0;
1564b95d7cefSEyal Shapira 	}
1565b95d7cefSEyal Shapira 
1566b95d7cefSEyal Shapira 	if (WARN_ON(wow->n_patterns > WL1271_MAX_RX_FILTERS))
1567b95d7cefSEyal Shapira 		return -EINVAL;
1568b95d7cefSEyal Shapira 
1569b95d7cefSEyal Shapira 	/* Validate all incoming patterns before clearing current FW state */
1570b95d7cefSEyal Shapira 	for (i = 0; i < wow->n_patterns; i++) {
1571b95d7cefSEyal Shapira 		ret = wl1271_validate_wowlan_pattern(&wow->patterns[i]);
1572b95d7cefSEyal Shapira 		if (ret) {
1573b95d7cefSEyal Shapira 			wl1271_warning("Bad wowlan pattern %d", i);
1574b95d7cefSEyal Shapira 			return ret;
1575b95d7cefSEyal Shapira 		}
1576b95d7cefSEyal Shapira 	}
1577b95d7cefSEyal Shapira 
1578c439a1caSArik Nemtsov 	ret = wl1271_acx_default_rx_filter_enable(wl, 0, FILTER_SIGNAL);
1579c439a1caSArik Nemtsov 	if (ret)
1580c439a1caSArik Nemtsov 		goto out;
1581c439a1caSArik Nemtsov 
1582c439a1caSArik Nemtsov 	ret = wl1271_rx_filter_clear_all(wl);
1583c439a1caSArik Nemtsov 	if (ret)
1584c439a1caSArik Nemtsov 		goto out;
1585b95d7cefSEyal Shapira 
1586b95d7cefSEyal Shapira 	/* Translate WoWLAN patterns into filters */
1587b95d7cefSEyal Shapira 	for (i = 0; i < wow->n_patterns; i++) {
158850ac6607SAmitkumar Karwar 		struct cfg80211_pkt_pattern *p;
1589b95d7cefSEyal Shapira 		struct wl12xx_rx_filter *filter = NULL;
1590b95d7cefSEyal Shapira 
1591b95d7cefSEyal Shapira 		p = &wow->patterns[i];
1592b95d7cefSEyal Shapira 
1593b95d7cefSEyal Shapira 		ret = wl1271_convert_wowlan_pattern_to_rx_filter(p, &filter);
1594b95d7cefSEyal Shapira 		if (ret) {
1595b95d7cefSEyal Shapira 			wl1271_warning("Failed to create an RX filter from "
1596b95d7cefSEyal Shapira 				       "wowlan pattern %d", i);
1597b95d7cefSEyal Shapira 			goto out;
1598b95d7cefSEyal Shapira 		}
1599b95d7cefSEyal Shapira 
1600b95d7cefSEyal Shapira 		ret = wl1271_rx_filter_enable(wl, i, 1, filter);
1601b95d7cefSEyal Shapira 
1602b95d7cefSEyal Shapira 		wl1271_rx_filter_free(filter);
1603b95d7cefSEyal Shapira 		if (ret)
1604b95d7cefSEyal Shapira 			goto out;
1605b95d7cefSEyal Shapira 	}
1606b95d7cefSEyal Shapira 
1607b95d7cefSEyal Shapira 	ret = wl1271_acx_default_rx_filter_enable(wl, 1, FILTER_DROP);
1608b95d7cefSEyal Shapira 
1609b95d7cefSEyal Shapira out:
1610b95d7cefSEyal Shapira 	return ret;
1611b95d7cefSEyal Shapira }
1612b95d7cefSEyal Shapira 
wl1271_configure_suspend_sta(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct cfg80211_wowlan * wow)16137b3115f2SLuciano Coelho static int wl1271_configure_suspend_sta(struct wl1271 *wl,
1614b95d7cefSEyal Shapira 					struct wl12xx_vif *wlvif,
1615b95d7cefSEyal Shapira 					struct cfg80211_wowlan *wow)
16167b3115f2SLuciano Coelho {
16177b3115f2SLuciano Coelho 	int ret = 0;
16187b3115f2SLuciano Coelho 
16197b3115f2SLuciano Coelho 	if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
16207b3115f2SLuciano Coelho 		goto out;
16217b3115f2SLuciano Coelho 
1622c439a1caSArik Nemtsov 	ret = wl1271_configure_wowlan(wl, wow);
1623c439a1caSArik Nemtsov 	if (ret < 0)
16246d5a748dSRam Amrani 		goto out;
1625c439a1caSArik Nemtsov 
162611bc97ebSEyal Shapira 	if ((wl->conf.conn.suspend_wake_up_event ==
162711bc97ebSEyal Shapira 	     wl->conf.conn.wake_up_event) &&
162811bc97ebSEyal Shapira 	    (wl->conf.conn.suspend_listen_interval ==
162911bc97ebSEyal Shapira 	     wl->conf.conn.listen_interval))
16306d5a748dSRam Amrani 		goto out;
163111bc97ebSEyal Shapira 
16327b3115f2SLuciano Coelho 	ret = wl1271_acx_wake_up_conditions(wl, wlvif,
16337b3115f2SLuciano Coelho 				    wl->conf.conn.suspend_wake_up_event,
16347b3115f2SLuciano Coelho 				    wl->conf.conn.suspend_listen_interval);
16357b3115f2SLuciano Coelho 
16367b3115f2SLuciano Coelho 	if (ret < 0)
16377b3115f2SLuciano Coelho 		wl1271_error("suspend: set wake up conditions failed: %d", ret);
16387b3115f2SLuciano Coelho out:
16397b3115f2SLuciano Coelho 	return ret;
16407b3115f2SLuciano Coelho 
16417b3115f2SLuciano Coelho }
16427b3115f2SLuciano Coelho 
wl1271_configure_suspend_ap(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct cfg80211_wowlan * wow)16437b3115f2SLuciano Coelho static int wl1271_configure_suspend_ap(struct wl1271 *wl,
1644b8714d1bSEliad Peller 					struct wl12xx_vif *wlvif,
1645b8714d1bSEliad Peller 					struct cfg80211_wowlan *wow)
16467b3115f2SLuciano Coelho {
16477b3115f2SLuciano Coelho 	int ret = 0;
16487b3115f2SLuciano Coelho 
16497b3115f2SLuciano Coelho 	if (!test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags))
16507b3115f2SLuciano Coelho 		goto out;
16517b3115f2SLuciano Coelho 
16527b3115f2SLuciano Coelho 	ret = wl1271_acx_beacon_filter_opt(wl, wlvif, true);
1653b8714d1bSEliad Peller 	if (ret < 0)
1654b8714d1bSEliad Peller 		goto out;
1655b8714d1bSEliad Peller 
1656b8714d1bSEliad Peller 	ret = wl1271_configure_wowlan(wl, wow);
1657b8714d1bSEliad Peller 	if (ret < 0)
1658b8714d1bSEliad Peller 		goto out;
16597b3115f2SLuciano Coelho 
16607b3115f2SLuciano Coelho out:
16617b3115f2SLuciano Coelho 	return ret;
16627b3115f2SLuciano Coelho 
16637b3115f2SLuciano Coelho }
16647b3115f2SLuciano Coelho 
wl1271_configure_suspend(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct cfg80211_wowlan * wow)16657b3115f2SLuciano Coelho static int wl1271_configure_suspend(struct wl1271 *wl,
1666b95d7cefSEyal Shapira 				    struct wl12xx_vif *wlvif,
1667b95d7cefSEyal Shapira 				    struct cfg80211_wowlan *wow)
16687b3115f2SLuciano Coelho {
16697b3115f2SLuciano Coelho 	if (wlvif->bss_type == BSS_TYPE_STA_BSS)
1670b95d7cefSEyal Shapira 		return wl1271_configure_suspend_sta(wl, wlvif, wow);
16717b3115f2SLuciano Coelho 	if (wlvif->bss_type == BSS_TYPE_AP_BSS)
1672b8714d1bSEliad Peller 		return wl1271_configure_suspend_ap(wl, wlvif, wow);
16737b3115f2SLuciano Coelho 	return 0;
16747b3115f2SLuciano Coelho }
16757b3115f2SLuciano Coelho 
wl1271_configure_resume(struct wl1271 * wl,struct wl12xx_vif * wlvif)16768f6ac537SLuciano Coelho static void wl1271_configure_resume(struct wl1271 *wl, struct wl12xx_vif *wlvif)
16777b3115f2SLuciano Coelho {
16787b3115f2SLuciano Coelho 	int ret = 0;
16797b3115f2SLuciano Coelho 	bool is_ap = wlvif->bss_type == BSS_TYPE_AP_BSS;
16807b3115f2SLuciano Coelho 	bool is_sta = wlvif->bss_type == BSS_TYPE_STA_BSS;
16817b3115f2SLuciano Coelho 
16827b3115f2SLuciano Coelho 	if ((!is_ap) && (!is_sta))
16837b3115f2SLuciano Coelho 		return;
16847b3115f2SLuciano Coelho 
1685b8714d1bSEliad Peller 	if ((is_sta && !test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) ||
1686b8714d1bSEliad Peller 	    (is_ap && !test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)))
1687d49524d3SEliad Peller 		return;
1688d49524d3SEliad Peller 
1689b95d7cefSEyal Shapira 	wl1271_configure_wowlan(wl, NULL);
1690b95d7cefSEyal Shapira 
1691b8714d1bSEliad Peller 	if (is_sta) {
169211bc97ebSEyal Shapira 		if ((wl->conf.conn.suspend_wake_up_event ==
169311bc97ebSEyal Shapira 		     wl->conf.conn.wake_up_event) &&
169411bc97ebSEyal Shapira 		    (wl->conf.conn.suspend_listen_interval ==
169511bc97ebSEyal Shapira 		     wl->conf.conn.listen_interval))
16966d5a748dSRam Amrani 			return;
169711bc97ebSEyal Shapira 
16987b3115f2SLuciano Coelho 		ret = wl1271_acx_wake_up_conditions(wl, wlvif,
16997b3115f2SLuciano Coelho 				    wl->conf.conn.wake_up_event,
17007b3115f2SLuciano Coelho 				    wl->conf.conn.listen_interval);
17017b3115f2SLuciano Coelho 
17027b3115f2SLuciano Coelho 		if (ret < 0)
17037b3115f2SLuciano Coelho 			wl1271_error("resume: wake up conditions failed: %d",
17047b3115f2SLuciano Coelho 				     ret);
17057b3115f2SLuciano Coelho 
17067b3115f2SLuciano Coelho 	} else if (is_ap) {
17077b3115f2SLuciano Coelho 		ret = wl1271_acx_beacon_filter_opt(wl, wlvif, false);
17087b3115f2SLuciano Coelho 	}
17097b3115f2SLuciano Coelho }
17107b3115f2SLuciano Coelho 
wl1271_op_suspend(struct ieee80211_hw * hw,struct cfg80211_wowlan * wow)17117de241f3SArnd Bergmann static int __maybe_unused wl1271_op_suspend(struct ieee80211_hw *hw,
17127b3115f2SLuciano Coelho 					    struct cfg80211_wowlan *wow)
17137b3115f2SLuciano Coelho {
17147b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
17157b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
171645aa7f07SEyal Reizer 	unsigned long flags;
17177b3115f2SLuciano Coelho 	int ret;
17187b3115f2SLuciano Coelho 
17197b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow);
1720b95d7cefSEyal Shapira 	WARN_ON(!wow);
17217b3115f2SLuciano Coelho 
172296caded8SArik Nemtsov 	/* we want to perform the recovery before suspending */
172396caded8SArik Nemtsov 	if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) {
172496caded8SArik Nemtsov 		wl1271_warning("postponing suspend to perform recovery");
172596caded8SArik Nemtsov 		return -EBUSY;
172696caded8SArik Nemtsov 	}
172796caded8SArik Nemtsov 
17287b3115f2SLuciano Coelho 	wl1271_tx_flush(wl);
17297b3115f2SLuciano Coelho 
17307b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
17316d5a748dSRam Amrani 
1732ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
1733bcb51441SDan Carpenter 	if (ret < 0) {
1734bcb51441SDan Carpenter 		mutex_unlock(&wl->mutex);
17356d5a748dSRam Amrani 		return ret;
1736bcb51441SDan Carpenter 	}
17376d5a748dSRam Amrani 
17387b3115f2SLuciano Coelho 	wl->wow_enabled = true;
17397b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif(wl, wlvif) {
17407845af35SEliad Peller 		if (wlcore_is_p2p_mgmt(wlvif))
17417845af35SEliad Peller 			continue;
17427845af35SEliad Peller 
1743b95d7cefSEyal Shapira 		ret = wl1271_configure_suspend(wl, wlvif, wow);
17447b3115f2SLuciano Coelho 		if (ret < 0) {
17453e69ed2bSDinghao Liu 			goto out_sleep;
17467b3115f2SLuciano Coelho 		}
17477b3115f2SLuciano Coelho 	}
17486d5a748dSRam Amrani 
17496d5a748dSRam Amrani 	/* disable fast link flow control notifications from FW */
17506d5a748dSRam Amrani 	ret = wlcore_hw_interrupt_notify(wl, false);
17516d5a748dSRam Amrani 	if (ret < 0)
17526d5a748dSRam Amrani 		goto out_sleep;
17536d5a748dSRam Amrani 
17546d5a748dSRam Amrani 	/* if filtering is enabled, configure the FW to drop all RX BA frames */
17556d5a748dSRam Amrani 	ret = wlcore_hw_rx_ba_filter(wl,
17566d5a748dSRam Amrani 				     !!wl->conf.conn.suspend_rx_ba_activity);
17576d5a748dSRam Amrani 	if (ret < 0)
17586d5a748dSRam Amrani 		goto out_sleep;
17596d5a748dSRam Amrani 
17606d5a748dSRam Amrani out_sleep:
1761fa2648a3STony Lindgren 	pm_runtime_put_noidle(wl->dev);
17627b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
17636d5a748dSRam Amrani 
17646d5a748dSRam Amrani 	if (ret < 0) {
17656d5a748dSRam Amrani 		wl1271_warning("couldn't prepare device to suspend");
17666d5a748dSRam Amrani 		return ret;
17676d5a748dSRam Amrani 	}
17686d5a748dSRam Amrani 
17697b3115f2SLuciano Coelho 	/* flush any remaining work */
17707b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "flushing remaining works");
17717b3115f2SLuciano Coelho 
17727b3115f2SLuciano Coelho 	flush_work(&wl->tx_work);
17737b3115f2SLuciano Coelho 
17749be86cf0SArik Nemtsov 	/*
17759be86cf0SArik Nemtsov 	 * Cancel the watchdog even if above tx_flush failed. We will detect
17769be86cf0SArik Nemtsov 	 * it on resume anyway.
17779be86cf0SArik Nemtsov 	 */
17789be86cf0SArik Nemtsov 	cancel_delayed_work(&wl->tx_watchdog_work);
17799be86cf0SArik Nemtsov 
178037bf241bSReizer, Eyal 	/*
178145aa7f07SEyal Reizer 	 * set suspended flag to avoid triggering a new threaded_irq
178245aa7f07SEyal Reizer 	 * work.
178337bf241bSReizer, Eyal 	 */
178445aa7f07SEyal Reizer 	spin_lock_irqsave(&wl->wl_lock, flags);
178545aa7f07SEyal Reizer 	set_bit(WL1271_FLAG_SUSPENDED, &wl->flags);
178645aa7f07SEyal Reizer 	spin_unlock_irqrestore(&wl->wl_lock, flags);
178737bf241bSReizer, Eyal 
178845aa7f07SEyal Reizer 	return pm_runtime_force_suspend(wl->dev);
17897b3115f2SLuciano Coelho }
17907b3115f2SLuciano Coelho 
wl1271_op_resume(struct ieee80211_hw * hw)17917de241f3SArnd Bergmann static int __maybe_unused wl1271_op_resume(struct ieee80211_hw *hw)
17927b3115f2SLuciano Coelho {
17937b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
17947b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
17957b3115f2SLuciano Coelho 	unsigned long flags;
1796ea0a3cf9SArik Nemtsov 	bool run_irq_work = false, pending_recovery;
1797725b8277SArik Nemtsov 	int ret;
17987b3115f2SLuciano Coelho 
17997b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 resume wow=%d",
18007b3115f2SLuciano Coelho 		     wl->wow_enabled);
18017b3115f2SLuciano Coelho 	WARN_ON(!wl->wow_enabled);
18027b3115f2SLuciano Coelho 
180345aa7f07SEyal Reizer 	ret = pm_runtime_force_resume(wl->dev);
180445aa7f07SEyal Reizer 	if (ret < 0) {
180545aa7f07SEyal Reizer 		wl1271_error("ELP wakeup failure!");
180645aa7f07SEyal Reizer 		goto out_sleep;
180745aa7f07SEyal Reizer 	}
180845aa7f07SEyal Reizer 
18097b3115f2SLuciano Coelho 	/*
18107b3115f2SLuciano Coelho 	 * re-enable irq_work enqueuing, and call irq_work directly if
18117b3115f2SLuciano Coelho 	 * there is a pending work.
18127b3115f2SLuciano Coelho 	 */
18137b3115f2SLuciano Coelho 	spin_lock_irqsave(&wl->wl_lock, flags);
18147b3115f2SLuciano Coelho 	clear_bit(WL1271_FLAG_SUSPENDED, &wl->flags);
18157b3115f2SLuciano Coelho 	if (test_and_clear_bit(WL1271_FLAG_PENDING_WORK, &wl->flags))
18167b3115f2SLuciano Coelho 		run_irq_work = true;
18177b3115f2SLuciano Coelho 	spin_unlock_irqrestore(&wl->wl_lock, flags);
18187b3115f2SLuciano Coelho 
1819725b8277SArik Nemtsov 	mutex_lock(&wl->mutex);
1820725b8277SArik Nemtsov 
1821ea0a3cf9SArik Nemtsov 	/* test the recovery flag before calling any SDIO functions */
1822ea0a3cf9SArik Nemtsov 	pending_recovery = test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS,
1823ea0a3cf9SArik Nemtsov 				    &wl->flags);
1824ea0a3cf9SArik Nemtsov 
18257b3115f2SLuciano Coelho 	if (run_irq_work) {
18267b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_MAC80211,
18277b3115f2SLuciano Coelho 			     "run postponed irq_work directly");
1828ea0a3cf9SArik Nemtsov 
1829ea0a3cf9SArik Nemtsov 		/* don't talk to the HW if recovery is pending */
1830725b8277SArik Nemtsov 		if (!pending_recovery) {
1831725b8277SArik Nemtsov 			ret = wlcore_irq_locked(wl);
1832725b8277SArik Nemtsov 			if (ret)
1833725b8277SArik Nemtsov 				wl12xx_queue_recovery_work(wl);
1834725b8277SArik Nemtsov 		}
1835ea0a3cf9SArik Nemtsov 
1836dd5512ebSLuciano Coelho 		wlcore_enable_interrupts(wl);
18377b3115f2SLuciano Coelho 	}
18387b3115f2SLuciano Coelho 
1839ea0a3cf9SArik Nemtsov 	if (pending_recovery) {
1840ea0a3cf9SArik Nemtsov 		wl1271_warning("queuing forgotten recovery on resume");
1841ea0a3cf9SArik Nemtsov 		ieee80211_queue_work(wl->hw, &wl->recovery_work);
18426d5a748dSRam Amrani 		goto out_sleep;
1843ea0a3cf9SArik Nemtsov 	}
1844ea0a3cf9SArik Nemtsov 
1845ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
1846ab589ac2SMinghao Chi 	if (ret < 0)
18476d5a748dSRam Amrani 		goto out;
18486d5a748dSRam Amrani 
18497b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif(wl, wlvif) {
18507845af35SEliad Peller 		if (wlcore_is_p2p_mgmt(wlvif))
18517845af35SEliad Peller 			continue;
18527845af35SEliad Peller 
18537b3115f2SLuciano Coelho 		wl1271_configure_resume(wl, wlvif);
18547b3115f2SLuciano Coelho 	}
1855ea0a3cf9SArik Nemtsov 
18566d5a748dSRam Amrani 	ret = wlcore_hw_interrupt_notify(wl, true);
18576d5a748dSRam Amrani 	if (ret < 0)
18586d5a748dSRam Amrani 		goto out_sleep;
18596d5a748dSRam Amrani 
18606d5a748dSRam Amrani 	/* if filtering is enabled, configure the FW to drop all RX BA frames */
18616d5a748dSRam Amrani 	ret = wlcore_hw_rx_ba_filter(wl, false);
18626d5a748dSRam Amrani 	if (ret < 0)
18636d5a748dSRam Amrani 		goto out_sleep;
18646d5a748dSRam Amrani 
18656d5a748dSRam Amrani out_sleep:
18669b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
18679b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
18686d5a748dSRam Amrani 
1869ea0a3cf9SArik Nemtsov out:
18707b3115f2SLuciano Coelho 	wl->wow_enabled = false;
18719be86cf0SArik Nemtsov 
18729be86cf0SArik Nemtsov 	/*
18739be86cf0SArik Nemtsov 	 * Set a flag to re-init the watchdog on the first Tx after resume.
18749be86cf0SArik Nemtsov 	 * That way we avoid possible conditions where Tx-complete interrupts
18759be86cf0SArik Nemtsov 	 * fail to arrive and we perform a spurious recovery.
18769be86cf0SArik Nemtsov 	 */
18779be86cf0SArik Nemtsov 	set_bit(WL1271_FLAG_REINIT_TX_WDOG, &wl->flags);
18787b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
18797b3115f2SLuciano Coelho 
18807b3115f2SLuciano Coelho 	return 0;
18817b3115f2SLuciano Coelho }
18827b3115f2SLuciano Coelho 
wl1271_op_start(struct ieee80211_hw * hw)18837b3115f2SLuciano Coelho static int wl1271_op_start(struct ieee80211_hw *hw)
18847b3115f2SLuciano Coelho {
18857b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 start");
18867b3115f2SLuciano Coelho 
18877b3115f2SLuciano Coelho 	/*
18887b3115f2SLuciano Coelho 	 * We have to delay the booting of the hardware because
18897b3115f2SLuciano Coelho 	 * we need to know the local MAC address before downloading and
18907b3115f2SLuciano Coelho 	 * initializing the firmware. The MAC address cannot be changed
18917b3115f2SLuciano Coelho 	 * after boot, and without the proper MAC address, the firmware
18927b3115f2SLuciano Coelho 	 * will not function properly.
18937b3115f2SLuciano Coelho 	 *
18947b3115f2SLuciano Coelho 	 * The MAC address is first known when the corresponding interface
18957b3115f2SLuciano Coelho 	 * is added. That is where we will initialize the hardware.
18967b3115f2SLuciano Coelho 	 */
18977b3115f2SLuciano Coelho 
18987b3115f2SLuciano Coelho 	return 0;
18997b3115f2SLuciano Coelho }
19007b3115f2SLuciano Coelho 
wlcore_op_stop_locked(struct wl1271 * wl)1901c24ec83bSIdo Yariv static void wlcore_op_stop_locked(struct wl1271 *wl)
19027b3115f2SLuciano Coelho {
19037b3115f2SLuciano Coelho 	int i;
19047b3115f2SLuciano Coelho 
19054cc53383SIdo Yariv 	if (wl->state == WLCORE_STATE_OFF) {
1906b666bb7fSIdo Yariv 		if (test_and_clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS,
1907b666bb7fSIdo Yariv 					&wl->flags))
1908b666bb7fSIdo Yariv 			wlcore_enable_interrupts(wl);
1909b666bb7fSIdo Yariv 
19107b3115f2SLuciano Coelho 		return;
19117b3115f2SLuciano Coelho 	}
19127b3115f2SLuciano Coelho 
19137b3115f2SLuciano Coelho 	/*
19147b3115f2SLuciano Coelho 	 * this must be before the cancel_work calls below, so that the work
19157b3115f2SLuciano Coelho 	 * functions don't perform further work.
19167b3115f2SLuciano Coelho 	 */
19174cc53383SIdo Yariv 	wl->state = WLCORE_STATE_OFF;
1918c24ec83bSIdo Yariv 
1919c24ec83bSIdo Yariv 	/*
1920c24ec83bSIdo Yariv 	 * Use the nosync variant to disable interrupts, so the mutex could be
1921c24ec83bSIdo Yariv 	 * held while doing so without deadlocking.
1922c24ec83bSIdo Yariv 	 */
1923c24ec83bSIdo Yariv 	wlcore_disable_interrupts_nosync(wl);
1924c24ec83bSIdo Yariv 
19257b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
19267b3115f2SLuciano Coelho 
1927c24ec83bSIdo Yariv 	wlcore_synchronize_interrupts(wl);
19286dbc5fc2SEliad Peller 	if (!test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags))
19296dbc5fc2SEliad Peller 		cancel_work_sync(&wl->recovery_work);
19307b3115f2SLuciano Coelho 	wl1271_flush_deferred_work(wl);
19317b3115f2SLuciano Coelho 	cancel_delayed_work_sync(&wl->scan_complete_work);
19327b3115f2SLuciano Coelho 	cancel_work_sync(&wl->netstack_work);
19337b3115f2SLuciano Coelho 	cancel_work_sync(&wl->tx_work);
19347b3115f2SLuciano Coelho 	cancel_delayed_work_sync(&wl->tx_watchdog_work);
19357b3115f2SLuciano Coelho 
19367b3115f2SLuciano Coelho 	/* let's notify MAC80211 about the remaining pending TX frames */
19377b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
1938d935e385SArik Nemtsov 	wl12xx_tx_reset(wl);
19397b3115f2SLuciano Coelho 
19407b3115f2SLuciano Coelho 	wl1271_power_off(wl);
1941b666bb7fSIdo Yariv 	/*
1942b666bb7fSIdo Yariv 	 * In case a recovery was scheduled, interrupts were disabled to avoid
1943b666bb7fSIdo Yariv 	 * an interrupt storm. Now that the power is down, it is safe to
1944b666bb7fSIdo Yariv 	 * re-enable interrupts to balance the disable depth
1945b666bb7fSIdo Yariv 	 */
1946b666bb7fSIdo Yariv 	if (test_and_clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags))
1947b666bb7fSIdo Yariv 		wlcore_enable_interrupts(wl);
19487b3115f2SLuciano Coelho 
194957fbcce3SJohannes Berg 	wl->band = NL80211_BAND_2GHZ;
19507b3115f2SLuciano Coelho 
19517b3115f2SLuciano Coelho 	wl->rx_counter = 0;
19527b3115f2SLuciano Coelho 	wl->power_level = WL1271_DEFAULT_POWER_LEVEL;
195383d08d3fSArik Nemtsov 	wl->channel_type = NL80211_CHAN_NO_HT;
19547b3115f2SLuciano Coelho 	wl->tx_blocks_available = 0;
19557b3115f2SLuciano Coelho 	wl->tx_allocated_blocks = 0;
19567b3115f2SLuciano Coelho 	wl->tx_results_count = 0;
19577b3115f2SLuciano Coelho 	wl->tx_packets_count = 0;
19587b3115f2SLuciano Coelho 	wl->time_offset = 0;
19597b3115f2SLuciano Coelho 	wl->ap_fw_ps_map = 0;
19607b3115f2SLuciano Coelho 	wl->ap_ps_map = 0;
19612f18cf7cSArik Nemtsov 	wl->sleep_auth = WL1271_PSM_ILLEGAL;
19627b3115f2SLuciano Coelho 	memset(wl->roles_map, 0, sizeof(wl->roles_map));
19637b3115f2SLuciano Coelho 	memset(wl->links_map, 0, sizeof(wl->links_map));
19647b3115f2SLuciano Coelho 	memset(wl->roc_map, 0, sizeof(wl->roc_map));
1965978cd3a0SEliad Peller 	memset(wl->session_ids, 0, sizeof(wl->session_ids));
196602d0727cSNadim Zubidat 	memset(wl->rx_filter_enabled, 0, sizeof(wl->rx_filter_enabled));
19677b3115f2SLuciano Coelho 	wl->active_sta_count = 0;
19689a100968SArik Nemtsov 	wl->active_link_count = 0;
19697b3115f2SLuciano Coelho 
19707b3115f2SLuciano Coelho 	/* The system link is always allocated */
19719ebcb232SArik Nemtsov 	wl->links[WL12XX_SYSTEM_HLID].allocated_pkts = 0;
19729ebcb232SArik Nemtsov 	wl->links[WL12XX_SYSTEM_HLID].prev_freed_pkts = 0;
19737b3115f2SLuciano Coelho 	__set_bit(WL12XX_SYSTEM_HLID, wl->links_map);
19747b3115f2SLuciano Coelho 
19757b3115f2SLuciano Coelho 	/*
19767b3115f2SLuciano Coelho 	 * this is performed after the cancel_work calls and the associated
19777b3115f2SLuciano Coelho 	 * mutex_lock, so that wl1271_op_add_interface does not accidentally
19787b3115f2SLuciano Coelho 	 * get executed before all these vars have been reset.
19797b3115f2SLuciano Coelho 	 */
19807b3115f2SLuciano Coelho 	wl->flags = 0;
19817b3115f2SLuciano Coelho 
19827b3115f2SLuciano Coelho 	wl->tx_blocks_freed = 0;
19837b3115f2SLuciano Coelho 
19847b3115f2SLuciano Coelho 	for (i = 0; i < NUM_TX_QUEUES; i++) {
19857b3115f2SLuciano Coelho 		wl->tx_pkts_freed[i] = 0;
19867b3115f2SLuciano Coelho 		wl->tx_allocated_pkts[i] = 0;
19877b3115f2SLuciano Coelho 	}
19887b3115f2SLuciano Coelho 
19897b3115f2SLuciano Coelho 	wl1271_debugfs_reset(wl);
19907b3115f2SLuciano Coelho 
199175fb4df7SEliad Peller 	kfree(wl->raw_fw_status);
199275fb4df7SEliad Peller 	wl->raw_fw_status = NULL;
199375fb4df7SEliad Peller 	kfree(wl->fw_status);
199475fb4df7SEliad Peller 	wl->fw_status = NULL;
19957b3115f2SLuciano Coelho 	kfree(wl->tx_res_if);
19967b3115f2SLuciano Coelho 	wl->tx_res_if = NULL;
19977b3115f2SLuciano Coelho 	kfree(wl->target_mem_map);
19987b3115f2SLuciano Coelho 	wl->target_mem_map = NULL;
19996b70e7ebSVictor Goldenshtein 
20006b70e7ebSVictor Goldenshtein 	/*
20016b70e7ebSVictor Goldenshtein 	 * FW channels must be re-calibrated after recovery,
20028d3c1fd8SEliad Peller 	 * save current Reg-Domain channel configuration and clear it.
20036b70e7ebSVictor Goldenshtein 	 */
20048d3c1fd8SEliad Peller 	memcpy(wl->reg_ch_conf_pending, wl->reg_ch_conf_last,
20058d3c1fd8SEliad Peller 	       sizeof(wl->reg_ch_conf_pending));
20066b70e7ebSVictor Goldenshtein 	memset(wl->reg_ch_conf_last, 0, sizeof(wl->reg_ch_conf_last));
2007c24ec83bSIdo Yariv }
2008c24ec83bSIdo Yariv 
wlcore_op_stop(struct ieee80211_hw * hw)2009c24ec83bSIdo Yariv static void wlcore_op_stop(struct ieee80211_hw *hw)
2010c24ec83bSIdo Yariv {
2011c24ec83bSIdo Yariv 	struct wl1271 *wl = hw->priv;
2012c24ec83bSIdo Yariv 
2013c24ec83bSIdo Yariv 	wl1271_debug(DEBUG_MAC80211, "mac80211 stop");
2014c24ec83bSIdo Yariv 
2015c24ec83bSIdo Yariv 	mutex_lock(&wl->mutex);
2016c24ec83bSIdo Yariv 
2017c24ec83bSIdo Yariv 	wlcore_op_stop_locked(wl);
20187b3115f2SLuciano Coelho 
20197b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
20207b3115f2SLuciano Coelho }
20217b3115f2SLuciano Coelho 
wlcore_channel_switch_work(struct work_struct * work)2022c50a2825SEliad Peller static void wlcore_channel_switch_work(struct work_struct *work)
2023c50a2825SEliad Peller {
2024c50a2825SEliad Peller 	struct delayed_work *dwork;
2025c50a2825SEliad Peller 	struct wl1271 *wl;
2026c50a2825SEliad Peller 	struct ieee80211_vif *vif;
2027c50a2825SEliad Peller 	struct wl12xx_vif *wlvif;
2028c50a2825SEliad Peller 	int ret;
2029c50a2825SEliad Peller 
203061383412SGeliang Tang 	dwork = to_delayed_work(work);
2031c50a2825SEliad Peller 	wlvif = container_of(dwork, struct wl12xx_vif, channel_switch_work);
2032c50a2825SEliad Peller 	wl = wlvif->wl;
2033c50a2825SEliad Peller 
2034c50a2825SEliad Peller 	wl1271_info("channel switch failed (role_id: %d).", wlvif->role_id);
2035c50a2825SEliad Peller 
2036c50a2825SEliad Peller 	mutex_lock(&wl->mutex);
2037c50a2825SEliad Peller 
2038c50a2825SEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON))
2039c50a2825SEliad Peller 		goto out;
2040c50a2825SEliad Peller 
2041c50a2825SEliad Peller 	/* check the channel switch is still ongoing */
2042c50a2825SEliad Peller 	if (!test_and_clear_bit(WLVIF_FLAG_CS_PROGRESS, &wlvif->flags))
2043c50a2825SEliad Peller 		goto out;
2044c50a2825SEliad Peller 
2045c50a2825SEliad Peller 	vif = wl12xx_wlvif_to_vif(wlvif);
2046c50a2825SEliad Peller 	ieee80211_chswitch_done(vif, false);
2047c50a2825SEliad Peller 
2048ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
2049ab589ac2SMinghao Chi 	if (ret < 0)
2050c50a2825SEliad Peller 		goto out;
2051c50a2825SEliad Peller 
2052c50a2825SEliad Peller 	wl12xx_cmd_stop_channel_switch(wl, wlvif);
2053c50a2825SEliad Peller 
20549b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
20559b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
2056c50a2825SEliad Peller out:
2057c50a2825SEliad Peller 	mutex_unlock(&wl->mutex);
2058c50a2825SEliad Peller }
2059c50a2825SEliad Peller 
wlcore_connection_loss_work(struct work_struct * work)2060c50a2825SEliad Peller static void wlcore_connection_loss_work(struct work_struct *work)
2061c50a2825SEliad Peller {
2062c50a2825SEliad Peller 	struct delayed_work *dwork;
2063c50a2825SEliad Peller 	struct wl1271 *wl;
2064c50a2825SEliad Peller 	struct ieee80211_vif *vif;
2065c50a2825SEliad Peller 	struct wl12xx_vif *wlvif;
2066c50a2825SEliad Peller 
206761383412SGeliang Tang 	dwork = to_delayed_work(work);
2068c50a2825SEliad Peller 	wlvif = container_of(dwork, struct wl12xx_vif, connection_loss_work);
2069c50a2825SEliad Peller 	wl = wlvif->wl;
2070c50a2825SEliad Peller 
2071c50a2825SEliad Peller 	wl1271_info("Connection loss work (role_id: %d).", wlvif->role_id);
2072c50a2825SEliad Peller 
2073c50a2825SEliad Peller 	mutex_lock(&wl->mutex);
2074c50a2825SEliad Peller 
2075c50a2825SEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON))
2076c50a2825SEliad Peller 		goto out;
2077c50a2825SEliad Peller 
2078c50a2825SEliad Peller 	/* Call mac80211 connection loss */
2079c50a2825SEliad Peller 	if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
2080c50a2825SEliad Peller 		goto out;
2081c50a2825SEliad Peller 
2082c50a2825SEliad Peller 	vif = wl12xx_wlvif_to_vif(wlvif);
2083c50a2825SEliad Peller 	ieee80211_connection_loss(vif);
2084c50a2825SEliad Peller out:
2085c50a2825SEliad Peller 	mutex_unlock(&wl->mutex);
2086c50a2825SEliad Peller }
2087c50a2825SEliad Peller 
wlcore_pending_auth_complete_work(struct work_struct * work)2088187e52ccSArik Nemtsov static void wlcore_pending_auth_complete_work(struct work_struct *work)
2089187e52ccSArik Nemtsov {
2090187e52ccSArik Nemtsov 	struct delayed_work *dwork;
2091187e52ccSArik Nemtsov 	struct wl1271 *wl;
2092187e52ccSArik Nemtsov 	struct wl12xx_vif *wlvif;
2093187e52ccSArik Nemtsov 	unsigned long time_spare;
2094187e52ccSArik Nemtsov 	int ret;
2095187e52ccSArik Nemtsov 
209661383412SGeliang Tang 	dwork = to_delayed_work(work);
2097187e52ccSArik Nemtsov 	wlvif = container_of(dwork, struct wl12xx_vif,
2098187e52ccSArik Nemtsov 			     pending_auth_complete_work);
2099187e52ccSArik Nemtsov 	wl = wlvif->wl;
2100187e52ccSArik Nemtsov 
2101187e52ccSArik Nemtsov 	mutex_lock(&wl->mutex);
2102187e52ccSArik Nemtsov 
2103187e52ccSArik Nemtsov 	if (unlikely(wl->state != WLCORE_STATE_ON))
2104187e52ccSArik Nemtsov 		goto out;
2105187e52ccSArik Nemtsov 
2106187e52ccSArik Nemtsov 	/*
2107187e52ccSArik Nemtsov 	 * Make sure a second really passed since the last auth reply. Maybe
2108187e52ccSArik Nemtsov 	 * a second auth reply arrived while we were stuck on the mutex.
2109187e52ccSArik Nemtsov 	 * Check for a little less than the timeout to protect from scheduler
2110187e52ccSArik Nemtsov 	 * irregularities.
2111187e52ccSArik Nemtsov 	 */
2112187e52ccSArik Nemtsov 	time_spare = jiffies +
2113187e52ccSArik Nemtsov 			msecs_to_jiffies(WLCORE_PEND_AUTH_ROC_TIMEOUT - 50);
2114187e52ccSArik Nemtsov 	if (!time_after(time_spare, wlvif->pending_auth_reply_time))
2115187e52ccSArik Nemtsov 		goto out;
2116187e52ccSArik Nemtsov 
2117ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
2118ab589ac2SMinghao Chi 	if (ret < 0)
2119187e52ccSArik Nemtsov 		goto out;
2120187e52ccSArik Nemtsov 
2121187e52ccSArik Nemtsov 	/* cancel the ROC if active */
2122187e52ccSArik Nemtsov 	wlcore_update_inconn_sta(wl, wlvif, NULL, false);
2123187e52ccSArik Nemtsov 
21249b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
21259b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
2126187e52ccSArik Nemtsov out:
2127187e52ccSArik Nemtsov 	mutex_unlock(&wl->mutex);
2128187e52ccSArik Nemtsov }
2129187e52ccSArik Nemtsov 
wl12xx_allocate_rate_policy(struct wl1271 * wl,u8 * idx)21307b3115f2SLuciano Coelho static int wl12xx_allocate_rate_policy(struct wl1271 *wl, u8 *idx)
21317b3115f2SLuciano Coelho {
21327b3115f2SLuciano Coelho 	u8 policy = find_first_zero_bit(wl->rate_policies_map,
21337b3115f2SLuciano Coelho 					WL12XX_MAX_RATE_POLICIES);
21347b3115f2SLuciano Coelho 	if (policy >= WL12XX_MAX_RATE_POLICIES)
21357b3115f2SLuciano Coelho 		return -EBUSY;
21367b3115f2SLuciano Coelho 
21377b3115f2SLuciano Coelho 	__set_bit(policy, wl->rate_policies_map);
21387b3115f2SLuciano Coelho 	*idx = policy;
21397b3115f2SLuciano Coelho 	return 0;
21407b3115f2SLuciano Coelho }
21417b3115f2SLuciano Coelho 
wl12xx_free_rate_policy(struct wl1271 * wl,u8 * idx)21427b3115f2SLuciano Coelho static void wl12xx_free_rate_policy(struct wl1271 *wl, u8 *idx)
21437b3115f2SLuciano Coelho {
21447b3115f2SLuciano Coelho 	if (WARN_ON(*idx >= WL12XX_MAX_RATE_POLICIES))
21457b3115f2SLuciano Coelho 		return;
21467b3115f2SLuciano Coelho 
21477b3115f2SLuciano Coelho 	__clear_bit(*idx, wl->rate_policies_map);
21487b3115f2SLuciano Coelho 	*idx = WL12XX_MAX_RATE_POLICIES;
21497b3115f2SLuciano Coelho }
21507b3115f2SLuciano Coelho 
wlcore_allocate_klv_template(struct wl1271 * wl,u8 * idx)2151001e39a8SEliad Peller static int wlcore_allocate_klv_template(struct wl1271 *wl, u8 *idx)
2152001e39a8SEliad Peller {
2153001e39a8SEliad Peller 	u8 policy = find_first_zero_bit(wl->klv_templates_map,
2154001e39a8SEliad Peller 					WLCORE_MAX_KLV_TEMPLATES);
2155001e39a8SEliad Peller 	if (policy >= WLCORE_MAX_KLV_TEMPLATES)
2156001e39a8SEliad Peller 		return -EBUSY;
2157001e39a8SEliad Peller 
2158001e39a8SEliad Peller 	__set_bit(policy, wl->klv_templates_map);
2159001e39a8SEliad Peller 	*idx = policy;
2160001e39a8SEliad Peller 	return 0;
2161001e39a8SEliad Peller }
2162001e39a8SEliad Peller 
wlcore_free_klv_template(struct wl1271 * wl,u8 * idx)2163001e39a8SEliad Peller static void wlcore_free_klv_template(struct wl1271 *wl, u8 *idx)
2164001e39a8SEliad Peller {
2165001e39a8SEliad Peller 	if (WARN_ON(*idx >= WLCORE_MAX_KLV_TEMPLATES))
2166001e39a8SEliad Peller 		return;
2167001e39a8SEliad Peller 
2168001e39a8SEliad Peller 	__clear_bit(*idx, wl->klv_templates_map);
2169001e39a8SEliad Peller 	*idx = WLCORE_MAX_KLV_TEMPLATES;
2170001e39a8SEliad Peller }
2171001e39a8SEliad Peller 
wl12xx_get_role_type(struct wl1271 * wl,struct wl12xx_vif * wlvif)21727b3115f2SLuciano Coelho static u8 wl12xx_get_role_type(struct wl1271 *wl, struct wl12xx_vif *wlvif)
21737b3115f2SLuciano Coelho {
2174c0174ee2SMaital Hahn 	struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
2175c0174ee2SMaital Hahn 
21767b3115f2SLuciano Coelho 	switch (wlvif->bss_type) {
21777b3115f2SLuciano Coelho 	case BSS_TYPE_AP_BSS:
21787b3115f2SLuciano Coelho 		if (wlvif->p2p)
21797b3115f2SLuciano Coelho 			return WL1271_ROLE_P2P_GO;
2180c0174ee2SMaital Hahn 		else if (ieee80211_vif_is_mesh(vif))
2181c0174ee2SMaital Hahn 			return WL1271_ROLE_MESH_POINT;
21827b3115f2SLuciano Coelho 		else
21837b3115f2SLuciano Coelho 			return WL1271_ROLE_AP;
21847b3115f2SLuciano Coelho 
21857b3115f2SLuciano Coelho 	case BSS_TYPE_STA_BSS:
21867b3115f2SLuciano Coelho 		if (wlvif->p2p)
21877b3115f2SLuciano Coelho 			return WL1271_ROLE_P2P_CL;
21887b3115f2SLuciano Coelho 		else
21897b3115f2SLuciano Coelho 			return WL1271_ROLE_STA;
21907b3115f2SLuciano Coelho 
21917b3115f2SLuciano Coelho 	case BSS_TYPE_IBSS:
21927b3115f2SLuciano Coelho 		return WL1271_ROLE_IBSS;
21937b3115f2SLuciano Coelho 
21947b3115f2SLuciano Coelho 	default:
21957b3115f2SLuciano Coelho 		wl1271_error("invalid bss_type: %d", wlvif->bss_type);
21967b3115f2SLuciano Coelho 	}
21977b3115f2SLuciano Coelho 	return WL12XX_INVALID_ROLE_TYPE;
21987b3115f2SLuciano Coelho }
21997b3115f2SLuciano Coelho 
wl12xx_init_vif_data(struct wl1271 * wl,struct ieee80211_vif * vif)22007b3115f2SLuciano Coelho static int wl12xx_init_vif_data(struct wl1271 *wl, struct ieee80211_vif *vif)
22017b3115f2SLuciano Coelho {
22027b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
22037b3115f2SLuciano Coelho 	int i;
22047b3115f2SLuciano Coelho 
22057b3115f2SLuciano Coelho 	/* clear everything but the persistent data */
22067b3115f2SLuciano Coelho 	memset(wlvif, 0, offsetof(struct wl12xx_vif, persistent));
22077b3115f2SLuciano Coelho 
22087b3115f2SLuciano Coelho 	switch (ieee80211_vif_type_p2p(vif)) {
22097b3115f2SLuciano Coelho 	case NL80211_IFTYPE_P2P_CLIENT:
22107b3115f2SLuciano Coelho 		wlvif->p2p = 1;
2211a821e385SGustavo A. R. Silva 		fallthrough;
22127b3115f2SLuciano Coelho 	case NL80211_IFTYPE_STATION:
22137845af35SEliad Peller 	case NL80211_IFTYPE_P2P_DEVICE:
22147b3115f2SLuciano Coelho 		wlvif->bss_type = BSS_TYPE_STA_BSS;
22157b3115f2SLuciano Coelho 		break;
22167b3115f2SLuciano Coelho 	case NL80211_IFTYPE_ADHOC:
22177b3115f2SLuciano Coelho 		wlvif->bss_type = BSS_TYPE_IBSS;
22187b3115f2SLuciano Coelho 		break;
22197b3115f2SLuciano Coelho 	case NL80211_IFTYPE_P2P_GO:
22207b3115f2SLuciano Coelho 		wlvif->p2p = 1;
2221a821e385SGustavo A. R. Silva 		fallthrough;
22227b3115f2SLuciano Coelho 	case NL80211_IFTYPE_AP:
2223c0174ee2SMaital Hahn 	case NL80211_IFTYPE_MESH_POINT:
22247b3115f2SLuciano Coelho 		wlvif->bss_type = BSS_TYPE_AP_BSS;
22257b3115f2SLuciano Coelho 		break;
22267b3115f2SLuciano Coelho 	default:
22277b3115f2SLuciano Coelho 		wlvif->bss_type = MAX_BSS_TYPE;
22287b3115f2SLuciano Coelho 		return -EOPNOTSUPP;
22297b3115f2SLuciano Coelho 	}
22307b3115f2SLuciano Coelho 
22317b3115f2SLuciano Coelho 	wlvif->role_id = WL12XX_INVALID_ROLE_ID;
22327b3115f2SLuciano Coelho 	wlvif->dev_role_id = WL12XX_INVALID_ROLE_ID;
22337b3115f2SLuciano Coelho 	wlvif->dev_hlid = WL12XX_INVALID_LINK_ID;
22347b3115f2SLuciano Coelho 
22357b3115f2SLuciano Coelho 	if (wlvif->bss_type == BSS_TYPE_STA_BSS ||
22367b3115f2SLuciano Coelho 	    wlvif->bss_type == BSS_TYPE_IBSS) {
22377b3115f2SLuciano Coelho 		/* init sta/ibss data */
22387b3115f2SLuciano Coelho 		wlvif->sta.hlid = WL12XX_INVALID_LINK_ID;
22397b3115f2SLuciano Coelho 		wl12xx_allocate_rate_policy(wl, &wlvif->sta.basic_rate_idx);
22407b3115f2SLuciano Coelho 		wl12xx_allocate_rate_policy(wl, &wlvif->sta.ap_rate_idx);
22417b3115f2SLuciano Coelho 		wl12xx_allocate_rate_policy(wl, &wlvif->sta.p2p_rate_idx);
2242001e39a8SEliad Peller 		wlcore_allocate_klv_template(wl, &wlvif->sta.klv_template_id);
224315e05bc0SLuciano Coelho 		wlvif->basic_rate_set = CONF_TX_RATE_MASK_BASIC;
224415e05bc0SLuciano Coelho 		wlvif->basic_rate = CONF_TX_RATE_MASK_BASIC;
224515e05bc0SLuciano Coelho 		wlvif->rate_set = CONF_TX_RATE_MASK_BASIC;
22467b3115f2SLuciano Coelho 	} else {
22477b3115f2SLuciano Coelho 		/* init ap data */
22487b3115f2SLuciano Coelho 		wlvif->ap.bcast_hlid = WL12XX_INVALID_LINK_ID;
22497b3115f2SLuciano Coelho 		wlvif->ap.global_hlid = WL12XX_INVALID_LINK_ID;
22507b3115f2SLuciano Coelho 		wl12xx_allocate_rate_policy(wl, &wlvif->ap.mgmt_rate_idx);
22517b3115f2SLuciano Coelho 		wl12xx_allocate_rate_policy(wl, &wlvif->ap.bcast_rate_idx);
22527b3115f2SLuciano Coelho 		for (i = 0; i < CONF_TX_MAX_AC_COUNT; i++)
22537b3115f2SLuciano Coelho 			wl12xx_allocate_rate_policy(wl,
22547b3115f2SLuciano Coelho 						&wlvif->ap.ucast_rate_idx[i]);
225542ec1f82SEliad Peller 		wlvif->basic_rate_set = CONF_TX_ENABLED_RATES;
225615e05bc0SLuciano Coelho 		/*
225715e05bc0SLuciano Coelho 		 * TODO: check if basic_rate shouldn't be
225815e05bc0SLuciano Coelho 		 * wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set);
225915e05bc0SLuciano Coelho 		 * instead (the same thing for STA above).
226015e05bc0SLuciano Coelho 		*/
226142ec1f82SEliad Peller 		wlvif->basic_rate = CONF_TX_ENABLED_RATES;
226215e05bc0SLuciano Coelho 		/* TODO: this seems to be used only for STA, check it */
226342ec1f82SEliad Peller 		wlvif->rate_set = CONF_TX_ENABLED_RATES;
22647b3115f2SLuciano Coelho 	}
22657b3115f2SLuciano Coelho 
226657fbcce3SJohannes Berg 	wlvif->bitrate_masks[NL80211_BAND_2GHZ] = wl->conf.tx.basic_rate;
226757fbcce3SJohannes Berg 	wlvif->bitrate_masks[NL80211_BAND_5GHZ] = wl->conf.tx.basic_rate_5;
22687b3115f2SLuciano Coelho 	wlvif->beacon_int = WL1271_DEFAULT_BEACON_INT;
22697b3115f2SLuciano Coelho 
22707b3115f2SLuciano Coelho 	/*
22717b3115f2SLuciano Coelho 	 * mac80211 configures some values globally, while we treat them
22727b3115f2SLuciano Coelho 	 * per-interface. thus, on init, we have to copy them from wl
22737b3115f2SLuciano Coelho 	 */
22747b3115f2SLuciano Coelho 	wlvif->band = wl->band;
22757b3115f2SLuciano Coelho 	wlvif->channel = wl->channel;
22767b3115f2SLuciano Coelho 	wlvif->power_level = wl->power_level;
227783d08d3fSArik Nemtsov 	wlvif->channel_type = wl->channel_type;
22787b3115f2SLuciano Coelho 
22797b3115f2SLuciano Coelho 	INIT_WORK(&wlvif->rx_streaming_enable_work,
22807b3115f2SLuciano Coelho 		  wl1271_rx_streaming_enable_work);
22817b3115f2SLuciano Coelho 	INIT_WORK(&wlvif->rx_streaming_disable_work,
22827b3115f2SLuciano Coelho 		  wl1271_rx_streaming_disable_work);
22837d3b29e5SEliad Peller 	INIT_WORK(&wlvif->rc_update_work, wlcore_rc_update_work);
2284c50a2825SEliad Peller 	INIT_DELAYED_WORK(&wlvif->channel_switch_work,
2285c50a2825SEliad Peller 			  wlcore_channel_switch_work);
2286c50a2825SEliad Peller 	INIT_DELAYED_WORK(&wlvif->connection_loss_work,
2287c50a2825SEliad Peller 			  wlcore_connection_loss_work);
2288187e52ccSArik Nemtsov 	INIT_DELAYED_WORK(&wlvif->pending_auth_complete_work,
2289187e52ccSArik Nemtsov 			  wlcore_pending_auth_complete_work);
22907b3115f2SLuciano Coelho 	INIT_LIST_HEAD(&wlvif->list);
22917b3115f2SLuciano Coelho 
2292e99e88a9SKees Cook 	timer_setup(&wlvif->rx_streaming_timer, wl1271_rx_streaming_timer, 0);
22937b3115f2SLuciano Coelho 	return 0;
22947b3115f2SLuciano Coelho }
22957b3115f2SLuciano Coelho 
wl12xx_init_fw(struct wl1271 * wl)22965dc283feSLuciano Coelho static int wl12xx_init_fw(struct wl1271 *wl)
22977b3115f2SLuciano Coelho {
22987b3115f2SLuciano Coelho 	int retries = WL1271_BOOT_RETRIES;
22997b3115f2SLuciano Coelho 	bool booted = false;
23007b3115f2SLuciano Coelho 	struct wiphy *wiphy = wl->hw->wiphy;
23017b3115f2SLuciano Coelho 	int ret;
23027b3115f2SLuciano Coelho 
23037b3115f2SLuciano Coelho 	while (retries) {
23047b3115f2SLuciano Coelho 		retries--;
23057b3115f2SLuciano Coelho 		ret = wl12xx_chip_wakeup(wl, false);
23067b3115f2SLuciano Coelho 		if (ret < 0)
23077b3115f2SLuciano Coelho 			goto power_off;
23087b3115f2SLuciano Coelho 
2309dd5512ebSLuciano Coelho 		ret = wl->ops->boot(wl);
23107b3115f2SLuciano Coelho 		if (ret < 0)
23117b3115f2SLuciano Coelho 			goto power_off;
23127b3115f2SLuciano Coelho 
23137b3115f2SLuciano Coelho 		ret = wl1271_hw_init(wl);
23147b3115f2SLuciano Coelho 		if (ret < 0)
23157b3115f2SLuciano Coelho 			goto irq_disable;
23167b3115f2SLuciano Coelho 
23177b3115f2SLuciano Coelho 		booted = true;
23187b3115f2SLuciano Coelho 		break;
23197b3115f2SLuciano Coelho 
23207b3115f2SLuciano Coelho irq_disable:
23217b3115f2SLuciano Coelho 		mutex_unlock(&wl->mutex);
23227b3115f2SLuciano Coelho 		/* Unlocking the mutex in the middle of handling is
23237b3115f2SLuciano Coelho 		   inherently unsafe. In this case we deem it safe to do,
23247b3115f2SLuciano Coelho 		   because we need to let any possibly pending IRQ out of
23254cc53383SIdo Yariv 		   the system (and while we are WLCORE_STATE_OFF the IRQ
23267b3115f2SLuciano Coelho 		   work function will not do anything.) Also, any other
23277b3115f2SLuciano Coelho 		   possible concurrent operations will fail due to the
23287b3115f2SLuciano Coelho 		   current state, hence the wl1271 struct should be safe. */
2329dd5512ebSLuciano Coelho 		wlcore_disable_interrupts(wl);
23307b3115f2SLuciano Coelho 		wl1271_flush_deferred_work(wl);
23317b3115f2SLuciano Coelho 		cancel_work_sync(&wl->netstack_work);
23327b3115f2SLuciano Coelho 		mutex_lock(&wl->mutex);
23337b3115f2SLuciano Coelho power_off:
23347b3115f2SLuciano Coelho 		wl1271_power_off(wl);
23357b3115f2SLuciano Coelho 	}
23367b3115f2SLuciano Coelho 
23377b3115f2SLuciano Coelho 	if (!booted) {
23387b3115f2SLuciano Coelho 		wl1271_error("firmware boot failed despite %d retries",
23397b3115f2SLuciano Coelho 			     WL1271_BOOT_RETRIES);
23407b3115f2SLuciano Coelho 		goto out;
23417b3115f2SLuciano Coelho 	}
23427b3115f2SLuciano Coelho 
23437b3115f2SLuciano Coelho 	wl1271_info("firmware booted (%s)", wl->chip.fw_ver_str);
23447b3115f2SLuciano Coelho 
23457b3115f2SLuciano Coelho 	/* update hw/fw version info in wiphy struct */
23467b3115f2SLuciano Coelho 	wiphy->hw_version = wl->chip.id;
23477b3115f2SLuciano Coelho 	strncpy(wiphy->fw_version, wl->chip.fw_ver_str,
23487b3115f2SLuciano Coelho 		sizeof(wiphy->fw_version));
23497b3115f2SLuciano Coelho 
23507b3115f2SLuciano Coelho 	/*
23517b3115f2SLuciano Coelho 	 * Now we know if 11a is supported (info from the NVS), so disable
23527b3115f2SLuciano Coelho 	 * 11a channels if not supported
23537b3115f2SLuciano Coelho 	 */
23547b3115f2SLuciano Coelho 	if (!wl->enable_11a)
235557fbcce3SJohannes Berg 		wiphy->bands[NL80211_BAND_5GHZ]->n_channels = 0;
23567b3115f2SLuciano Coelho 
23577b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "11a is %ssupported",
23587b3115f2SLuciano Coelho 		     wl->enable_11a ? "" : "not ");
23597b3115f2SLuciano Coelho 
23604cc53383SIdo Yariv 	wl->state = WLCORE_STATE_ON;
23617b3115f2SLuciano Coelho out:
23625dc283feSLuciano Coelho 	return ret;
23637b3115f2SLuciano Coelho }
23647b3115f2SLuciano Coelho 
wl12xx_dev_role_started(struct wl12xx_vif * wlvif)23657b3115f2SLuciano Coelho static bool wl12xx_dev_role_started(struct wl12xx_vif *wlvif)
23667b3115f2SLuciano Coelho {
23677b3115f2SLuciano Coelho 	return wlvif->dev_hlid != WL12XX_INVALID_LINK_ID;
23687b3115f2SLuciano Coelho }
23697b3115f2SLuciano Coelho 
23707b3115f2SLuciano Coelho /*
23717b3115f2SLuciano Coelho  * Check whether a fw switch (i.e. moving from one loaded
23727b3115f2SLuciano Coelho  * fw to another) is needed. This function is also responsible
23737b3115f2SLuciano Coelho  * for updating wl->last_vif_count, so it must be called before
23747b3115f2SLuciano Coelho  * loading a non-plt fw (so the correct fw (single-role/multi-role)
23757b3115f2SLuciano Coelho  * will be used).
23767b3115f2SLuciano Coelho  */
wl12xx_need_fw_change(struct wl1271 * wl,struct vif_counter_data vif_counter_data,bool add)23777b3115f2SLuciano Coelho static bool wl12xx_need_fw_change(struct wl1271 *wl,
23787b3115f2SLuciano Coelho 				  struct vif_counter_data vif_counter_data,
23797b3115f2SLuciano Coelho 				  bool add)
23807b3115f2SLuciano Coelho {
23817b3115f2SLuciano Coelho 	enum wl12xx_fw_type current_fw = wl->fw_type;
23827b3115f2SLuciano Coelho 	u8 vif_count = vif_counter_data.counter;
23837b3115f2SLuciano Coelho 
23847b3115f2SLuciano Coelho 	if (test_bit(WL1271_FLAG_VIF_CHANGE_IN_PROGRESS, &wl->flags))
23857b3115f2SLuciano Coelho 		return false;
23867b3115f2SLuciano Coelho 
23877b3115f2SLuciano Coelho 	/* increase the vif count if this is a new vif */
23887b3115f2SLuciano Coelho 	if (add && !vif_counter_data.cur_vif_running)
23897b3115f2SLuciano Coelho 		vif_count++;
23907b3115f2SLuciano Coelho 
23917b3115f2SLuciano Coelho 	wl->last_vif_count = vif_count;
23927b3115f2SLuciano Coelho 
23937b3115f2SLuciano Coelho 	/* no need for fw change if the device is OFF */
23944cc53383SIdo Yariv 	if (wl->state == WLCORE_STATE_OFF)
23957b3115f2SLuciano Coelho 		return false;
23967b3115f2SLuciano Coelho 
23979b1a0a77SEliad Peller 	/* no need for fw change if a single fw is used */
23989b1a0a77SEliad Peller 	if (!wl->mr_fw_name)
23999b1a0a77SEliad Peller 		return false;
24009b1a0a77SEliad Peller 
24017b3115f2SLuciano Coelho 	if (vif_count > 1 && current_fw == WL12XX_FW_TYPE_NORMAL)
24027b3115f2SLuciano Coelho 		return true;
24037b3115f2SLuciano Coelho 	if (vif_count <= 1 && current_fw == WL12XX_FW_TYPE_MULTI)
24047b3115f2SLuciano Coelho 		return true;
24057b3115f2SLuciano Coelho 
24067b3115f2SLuciano Coelho 	return false;
24077b3115f2SLuciano Coelho }
24087b3115f2SLuciano Coelho 
24097b3115f2SLuciano Coelho /*
24107b3115f2SLuciano Coelho  * Enter "forced psm". Make sure the sta is in psm against the ap,
24117b3115f2SLuciano Coelho  * to make the fw switch a bit more disconnection-persistent.
24127b3115f2SLuciano Coelho  */
wl12xx_force_active_psm(struct wl1271 * wl)24137b3115f2SLuciano Coelho static void wl12xx_force_active_psm(struct wl1271 *wl)
24147b3115f2SLuciano Coelho {
24157b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
24167b3115f2SLuciano Coelho 
24177b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif_sta(wl, wlvif) {
24187b3115f2SLuciano Coelho 		wl1271_ps_set_mode(wl, wlvif, STATION_POWER_SAVE_MODE);
24197b3115f2SLuciano Coelho 	}
24207b3115f2SLuciano Coelho }
24217b3115f2SLuciano Coelho 
24221c33db78SArik Nemtsov struct wlcore_hw_queue_iter_data {
24231c33db78SArik Nemtsov 	unsigned long hw_queue_map[BITS_TO_LONGS(WLCORE_NUM_MAC_ADDRESSES)];
24241c33db78SArik Nemtsov 	/* current vif */
24251c33db78SArik Nemtsov 	struct ieee80211_vif *vif;
24261c33db78SArik Nemtsov 	/* is the current vif among those iterated */
24271c33db78SArik Nemtsov 	bool cur_running;
24281c33db78SArik Nemtsov };
24291c33db78SArik Nemtsov 
wlcore_hw_queue_iter(void * data,u8 * mac,struct ieee80211_vif * vif)24301c33db78SArik Nemtsov static void wlcore_hw_queue_iter(void *data, u8 *mac,
24311c33db78SArik Nemtsov 				 struct ieee80211_vif *vif)
24321c33db78SArik Nemtsov {
24331c33db78SArik Nemtsov 	struct wlcore_hw_queue_iter_data *iter_data = data;
24341c33db78SArik Nemtsov 
24357845af35SEliad Peller 	if (vif->type == NL80211_IFTYPE_P2P_DEVICE ||
24367845af35SEliad Peller 	    WARN_ON_ONCE(vif->hw_queue[0] == IEEE80211_INVAL_HW_QUEUE))
24371c33db78SArik Nemtsov 		return;
24381c33db78SArik Nemtsov 
24391c33db78SArik Nemtsov 	if (iter_data->cur_running || vif == iter_data->vif) {
24401c33db78SArik Nemtsov 		iter_data->cur_running = true;
24411c33db78SArik Nemtsov 		return;
24421c33db78SArik Nemtsov 	}
24431c33db78SArik Nemtsov 
24441c33db78SArik Nemtsov 	__set_bit(vif->hw_queue[0] / NUM_TX_QUEUES, iter_data->hw_queue_map);
24451c33db78SArik Nemtsov }
24461c33db78SArik Nemtsov 
wlcore_allocate_hw_queue_base(struct wl1271 * wl,struct wl12xx_vif * wlvif)24471c33db78SArik Nemtsov static int wlcore_allocate_hw_queue_base(struct wl1271 *wl,
24481c33db78SArik Nemtsov 					 struct wl12xx_vif *wlvif)
24491c33db78SArik Nemtsov {
24501c33db78SArik Nemtsov 	struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
24511c33db78SArik Nemtsov 	struct wlcore_hw_queue_iter_data iter_data = {};
24521c33db78SArik Nemtsov 	int i, q_base;
24531c33db78SArik Nemtsov 
24547845af35SEliad Peller 	if (vif->type == NL80211_IFTYPE_P2P_DEVICE) {
24557845af35SEliad Peller 		vif->cab_queue = IEEE80211_INVAL_HW_QUEUE;
24567845af35SEliad Peller 		return 0;
24577845af35SEliad Peller 	}
24587845af35SEliad Peller 
24591c33db78SArik Nemtsov 	iter_data.vif = vif;
24601c33db78SArik Nemtsov 
24611c33db78SArik Nemtsov 	/* mark all bits taken by active interfaces */
24621c33db78SArik Nemtsov 	ieee80211_iterate_active_interfaces_atomic(wl->hw,
24631c33db78SArik Nemtsov 					IEEE80211_IFACE_ITER_RESUME_ALL,
24641c33db78SArik Nemtsov 					wlcore_hw_queue_iter, &iter_data);
24651c33db78SArik Nemtsov 
24661c33db78SArik Nemtsov 	/* the current vif is already running in mac80211 (resume/recovery) */
24671c33db78SArik Nemtsov 	if (iter_data.cur_running) {
24681c33db78SArik Nemtsov 		wlvif->hw_queue_base = vif->hw_queue[0];
24691c33db78SArik Nemtsov 		wl1271_debug(DEBUG_MAC80211,
24701c33db78SArik Nemtsov 			     "using pre-allocated hw queue base %d",
24711c33db78SArik Nemtsov 			     wlvif->hw_queue_base);
24721c33db78SArik Nemtsov 
24731c33db78SArik Nemtsov 		/* interface type might have changed type */
24741c33db78SArik Nemtsov 		goto adjust_cab_queue;
24751c33db78SArik Nemtsov 	}
24761c33db78SArik Nemtsov 
24771c33db78SArik Nemtsov 	q_base = find_first_zero_bit(iter_data.hw_queue_map,
24781c33db78SArik Nemtsov 				     WLCORE_NUM_MAC_ADDRESSES);
24791c33db78SArik Nemtsov 	if (q_base >= WLCORE_NUM_MAC_ADDRESSES)
24801c33db78SArik Nemtsov 		return -EBUSY;
24811c33db78SArik Nemtsov 
24821c33db78SArik Nemtsov 	wlvif->hw_queue_base = q_base * NUM_TX_QUEUES;
24831c33db78SArik Nemtsov 	wl1271_debug(DEBUG_MAC80211, "allocating hw queue base: %d",
24841c33db78SArik Nemtsov 		     wlvif->hw_queue_base);
24851c33db78SArik Nemtsov 
24861c33db78SArik Nemtsov 	for (i = 0; i < NUM_TX_QUEUES; i++) {
24871c33db78SArik Nemtsov 		wl->queue_stop_reasons[wlvif->hw_queue_base + i] = 0;
24881c33db78SArik Nemtsov 		/* register hw queues in mac80211 */
24891c33db78SArik Nemtsov 		vif->hw_queue[i] = wlvif->hw_queue_base + i;
24901c33db78SArik Nemtsov 	}
24911c33db78SArik Nemtsov 
24921c33db78SArik Nemtsov adjust_cab_queue:
24931c33db78SArik Nemtsov 	/* the last places are reserved for cab queues per interface */
24941c33db78SArik Nemtsov 	if (wlvif->bss_type == BSS_TYPE_AP_BSS)
24951c33db78SArik Nemtsov 		vif->cab_queue = NUM_TX_QUEUES * WLCORE_NUM_MAC_ADDRESSES +
24961c33db78SArik Nemtsov 				 wlvif->hw_queue_base / NUM_TX_QUEUES;
24971c33db78SArik Nemtsov 	else
24981c33db78SArik Nemtsov 		vif->cab_queue = IEEE80211_INVAL_HW_QUEUE;
24991c33db78SArik Nemtsov 
25001c33db78SArik Nemtsov 	return 0;
25011c33db78SArik Nemtsov }
25021c33db78SArik Nemtsov 
wl1271_op_add_interface(struct ieee80211_hw * hw,struct ieee80211_vif * vif)25037b3115f2SLuciano Coelho static int wl1271_op_add_interface(struct ieee80211_hw *hw,
25047b3115f2SLuciano Coelho 				   struct ieee80211_vif *vif)
25057b3115f2SLuciano Coelho {
25067b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
25077b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
25087b3115f2SLuciano Coelho 	struct vif_counter_data vif_count;
25097b3115f2SLuciano Coelho 	int ret = 0;
25107b3115f2SLuciano Coelho 	u8 role_type;
25117b3115f2SLuciano Coelho 
2512dd491ffbSYair Shapira 	if (wl->plt) {
2513dd491ffbSYair Shapira 		wl1271_error("Adding Interface not allowed while in PLT mode");
2514dd491ffbSYair Shapira 		return -EBUSY;
2515dd491ffbSYair Shapira 	}
2516dd491ffbSYair Shapira 
25177b3115f2SLuciano Coelho 	vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
2518848955ccSJohannes Berg 			     IEEE80211_VIF_SUPPORTS_UAPSD |
25197b3115f2SLuciano Coelho 			     IEEE80211_VIF_SUPPORTS_CQM_RSSI;
25207b3115f2SLuciano Coelho 
25217b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 add interface type %d mac %pM",
25227b3115f2SLuciano Coelho 		     ieee80211_vif_type_p2p(vif), vif->addr);
25237b3115f2SLuciano Coelho 
25247b3115f2SLuciano Coelho 	wl12xx_get_vif_count(hw, vif, &vif_count);
25257b3115f2SLuciano Coelho 
25267b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
25277b3115f2SLuciano Coelho 
25287b3115f2SLuciano Coelho 	/*
25297b3115f2SLuciano Coelho 	 * in some very corner case HW recovery scenarios its possible to
25307b3115f2SLuciano Coelho 	 * get here before __wl1271_op_remove_interface is complete, so
25317b3115f2SLuciano Coelho 	 * opt out if that is the case.
25327b3115f2SLuciano Coelho 	 */
25337b3115f2SLuciano Coelho 	if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags) ||
25347b3115f2SLuciano Coelho 	    test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)) {
25357b3115f2SLuciano Coelho 		ret = -EBUSY;
25367b3115f2SLuciano Coelho 		goto out;
25377b3115f2SLuciano Coelho 	}
25387b3115f2SLuciano Coelho 
25397b3115f2SLuciano Coelho 
25407b3115f2SLuciano Coelho 	ret = wl12xx_init_vif_data(wl, vif);
25417b3115f2SLuciano Coelho 	if (ret < 0)
25427b3115f2SLuciano Coelho 		goto out;
25437b3115f2SLuciano Coelho 
25447b3115f2SLuciano Coelho 	wlvif->wl = wl;
25457b3115f2SLuciano Coelho 	role_type = wl12xx_get_role_type(wl, wlvif);
25467b3115f2SLuciano Coelho 	if (role_type == WL12XX_INVALID_ROLE_TYPE) {
25477b3115f2SLuciano Coelho 		ret = -EINVAL;
25487b3115f2SLuciano Coelho 		goto out;
25497b3115f2SLuciano Coelho 	}
25507b3115f2SLuciano Coelho 
25511c33db78SArik Nemtsov 	ret = wlcore_allocate_hw_queue_base(wl, wlvif);
25521c33db78SArik Nemtsov 	if (ret < 0)
25531c33db78SArik Nemtsov 		goto out;
25541c33db78SArik Nemtsov 
25557b3115f2SLuciano Coelho 	/*
25567b3115f2SLuciano Coelho 	 * TODO: after the nvs issue will be solved, move this block
25577b3115f2SLuciano Coelho 	 * to start(), and make sure here the driver is ON.
25587b3115f2SLuciano Coelho 	 */
25594cc53383SIdo Yariv 	if (wl->state == WLCORE_STATE_OFF) {
25607b3115f2SLuciano Coelho 		/*
25617b3115f2SLuciano Coelho 		 * we still need this in order to configure the fw
25627b3115f2SLuciano Coelho 		 * while uploading the nvs
25637b3115f2SLuciano Coelho 		 */
25647b3115f2SLuciano Coelho 		memcpy(wl->addresses[0].addr, vif->addr, ETH_ALEN);
25657b3115f2SLuciano Coelho 
25665dc283feSLuciano Coelho 		ret = wl12xx_init_fw(wl);
25675dc283feSLuciano Coelho 		if (ret < 0)
25687b3115f2SLuciano Coelho 			goto out;
25697b3115f2SLuciano Coelho 	}
25707b3115f2SLuciano Coelho 
2571c40aad28STony Lindgren 	/*
2572c40aad28STony Lindgren 	 * Call runtime PM only after possible wl12xx_init_fw() above
2573c40aad28STony Lindgren 	 * is done. Otherwise we do not have interrupts enabled.
2574c40aad28STony Lindgren 	 */
2575ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
2576ab589ac2SMinghao Chi 	if (ret < 0)
2577c40aad28STony Lindgren 		goto out_unlock;
2578c40aad28STony Lindgren 
2579c40aad28STony Lindgren 	if (wl12xx_need_fw_change(wl, vif_count, true)) {
2580c40aad28STony Lindgren 		wl12xx_force_active_psm(wl);
2581c40aad28STony Lindgren 		set_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags);
2582c40aad28STony Lindgren 		mutex_unlock(&wl->mutex);
2583c40aad28STony Lindgren 		wl1271_recovery_work(&wl->recovery_work);
2584c40aad28STony Lindgren 		return 0;
2585c40aad28STony Lindgren 	}
2586c40aad28STony Lindgren 
25877845af35SEliad Peller 	if (!wlcore_is_p2p_mgmt(wlvif)) {
25887b3115f2SLuciano Coelho 		ret = wl12xx_cmd_role_enable(wl, vif->addr,
25897b3115f2SLuciano Coelho 					     role_type, &wlvif->role_id);
25907b3115f2SLuciano Coelho 		if (ret < 0)
25917b3115f2SLuciano Coelho 			goto out;
25927b3115f2SLuciano Coelho 
25937b3115f2SLuciano Coelho 		ret = wl1271_init_vif_specific(wl, vif);
25947b3115f2SLuciano Coelho 		if (ret < 0)
25957b3115f2SLuciano Coelho 			goto out;
25967b3115f2SLuciano Coelho 
25977845af35SEliad Peller 	} else {
25987845af35SEliad Peller 		ret = wl12xx_cmd_role_enable(wl, vif->addr, WL1271_ROLE_DEVICE,
25997845af35SEliad Peller 					     &wlvif->dev_role_id);
26007845af35SEliad Peller 		if (ret < 0)
26017845af35SEliad Peller 			goto out;
26027845af35SEliad Peller 
26037845af35SEliad Peller 		/* needed mainly for configuring rate policies */
26047845af35SEliad Peller 		ret = wl1271_sta_hw_init(wl, wlvif);
26057845af35SEliad Peller 		if (ret < 0)
26067845af35SEliad Peller 			goto out;
26077845af35SEliad Peller 	}
26087845af35SEliad Peller 
26097b3115f2SLuciano Coelho 	list_add(&wlvif->list, &wl->wlvif_list);
26107b3115f2SLuciano Coelho 	set_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags);
26117b3115f2SLuciano Coelho 
26127b3115f2SLuciano Coelho 	if (wlvif->bss_type == BSS_TYPE_AP_BSS)
26137b3115f2SLuciano Coelho 		wl->ap_count++;
26147b3115f2SLuciano Coelho 	else
26157b3115f2SLuciano Coelho 		wl->sta_count++;
26167b3115f2SLuciano Coelho out:
26179b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
26189b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
26197b3115f2SLuciano Coelho out_unlock:
26207b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
26217b3115f2SLuciano Coelho 
26227b3115f2SLuciano Coelho 	return ret;
26237b3115f2SLuciano Coelho }
26247b3115f2SLuciano Coelho 
__wl1271_op_remove_interface(struct wl1271 * wl,struct ieee80211_vif * vif,bool reset_tx_queues)26257b3115f2SLuciano Coelho static void __wl1271_op_remove_interface(struct wl1271 *wl,
26267b3115f2SLuciano Coelho 					 struct ieee80211_vif *vif,
26277b3115f2SLuciano Coelho 					 bool reset_tx_queues)
26287b3115f2SLuciano Coelho {
26297b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
26307b3115f2SLuciano Coelho 	int i, ret;
26312f18cf7cSArik Nemtsov 	bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS);
26327b3115f2SLuciano Coelho 
26337b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 remove interface");
26347b3115f2SLuciano Coelho 
26357b3115f2SLuciano Coelho 	if (!test_and_clear_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags))
26367b3115f2SLuciano Coelho 		return;
26377b3115f2SLuciano Coelho 
26387b3115f2SLuciano Coelho 	/* because of hardware recovery, we may get here twice */
26394cc53383SIdo Yariv 	if (wl->state == WLCORE_STATE_OFF)
26407b3115f2SLuciano Coelho 		return;
26417b3115f2SLuciano Coelho 
26427b3115f2SLuciano Coelho 	wl1271_info("down");
26437b3115f2SLuciano Coelho 
26447b3115f2SLuciano Coelho 	if (wl->scan.state != WL1271_SCAN_STATE_IDLE &&
2645c50a2825SEliad Peller 	    wl->scan_wlvif == wlvif) {
26467947d3e0SAvraham Stern 		struct cfg80211_scan_info info = {
26477947d3e0SAvraham Stern 			.aborted = true,
26487947d3e0SAvraham Stern 		};
26497947d3e0SAvraham Stern 
26507b3115f2SLuciano Coelho 		/*
26517b3115f2SLuciano Coelho 		 * Rearm the tx watchdog just before idling scan. This
26527b3115f2SLuciano Coelho 		 * prevents just-finished scans from triggering the watchdog
26537b3115f2SLuciano Coelho 		 */
26547b3115f2SLuciano Coelho 		wl12xx_rearm_tx_watchdog_locked(wl);
26557b3115f2SLuciano Coelho 
26567b3115f2SLuciano Coelho 		wl->scan.state = WL1271_SCAN_STATE_IDLE;
26577b3115f2SLuciano Coelho 		memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch));
2658c50a2825SEliad Peller 		wl->scan_wlvif = NULL;
26597b3115f2SLuciano Coelho 		wl->scan.req = NULL;
26607947d3e0SAvraham Stern 		ieee80211_scan_completed(wl->hw, &info);
26617b3115f2SLuciano Coelho 	}
26627b3115f2SLuciano Coelho 
26635a441f5fSBarak Bercovitz 	if (wl->sched_vif == wlvif)
266410199756SEliad Peller 		wl->sched_vif = NULL;
266510199756SEliad Peller 
26665d979f35SArik Nemtsov 	if (wl->roc_vif == vif) {
26675d979f35SArik Nemtsov 		wl->roc_vif = NULL;
26685d979f35SArik Nemtsov 		ieee80211_remain_on_channel_expired(wl->hw);
26695d979f35SArik Nemtsov 	}
26705d979f35SArik Nemtsov 
26717b3115f2SLuciano Coelho 	if (!test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) {
26727b3115f2SLuciano Coelho 		/* disable active roles */
2673ab589ac2SMinghao Chi 		ret = pm_runtime_resume_and_get(wl->dev);
2674ab589ac2SMinghao Chi 		if (ret < 0)
26757b3115f2SLuciano Coelho 			goto deinit;
26767b3115f2SLuciano Coelho 
26777b3115f2SLuciano Coelho 		if (wlvif->bss_type == BSS_TYPE_STA_BSS ||
26787b3115f2SLuciano Coelho 		    wlvif->bss_type == BSS_TYPE_IBSS) {
26797b3115f2SLuciano Coelho 			if (wl12xx_dev_role_started(wlvif))
26807b3115f2SLuciano Coelho 				wl12xx_stop_dev(wl, wlvif);
26817b3115f2SLuciano Coelho 		}
26827b3115f2SLuciano Coelho 
26837845af35SEliad Peller 		if (!wlcore_is_p2p_mgmt(wlvif)) {
26847b3115f2SLuciano Coelho 			ret = wl12xx_cmd_role_disable(wl, &wlvif->role_id);
268553df5271SDinghao Liu 			if (ret < 0) {
268653df5271SDinghao Liu 				pm_runtime_put_noidle(wl->dev);
26877b3115f2SLuciano Coelho 				goto deinit;
268853df5271SDinghao Liu 			}
26897845af35SEliad Peller 		} else {
26907845af35SEliad Peller 			ret = wl12xx_cmd_role_disable(wl, &wlvif->dev_role_id);
269153df5271SDinghao Liu 			if (ret < 0) {
269253df5271SDinghao Liu 				pm_runtime_put_noidle(wl->dev);
26937845af35SEliad Peller 				goto deinit;
26947845af35SEliad Peller 			}
269553df5271SDinghao Liu 		}
26967b3115f2SLuciano Coelho 
26979b71578dSTony Lindgren 		pm_runtime_mark_last_busy(wl->dev);
26989b71578dSTony Lindgren 		pm_runtime_put_autosuspend(wl->dev);
26997b3115f2SLuciano Coelho 	}
27007b3115f2SLuciano Coelho deinit:
27015a99610cSArik Nemtsov 	wl12xx_tx_reset_wlvif(wl, wlvif);
27025a99610cSArik Nemtsov 
27037b3115f2SLuciano Coelho 	/* clear all hlids (except system_hlid) */
27047b3115f2SLuciano Coelho 	wlvif->dev_hlid = WL12XX_INVALID_LINK_ID;
27057b3115f2SLuciano Coelho 
27067b3115f2SLuciano Coelho 	if (wlvif->bss_type == BSS_TYPE_STA_BSS ||
27077b3115f2SLuciano Coelho 	    wlvif->bss_type == BSS_TYPE_IBSS) {
27087b3115f2SLuciano Coelho 		wlvif->sta.hlid = WL12XX_INVALID_LINK_ID;
27097b3115f2SLuciano Coelho 		wl12xx_free_rate_policy(wl, &wlvif->sta.basic_rate_idx);
27107b3115f2SLuciano Coelho 		wl12xx_free_rate_policy(wl, &wlvif->sta.ap_rate_idx);
27117b3115f2SLuciano Coelho 		wl12xx_free_rate_policy(wl, &wlvif->sta.p2p_rate_idx);
2712001e39a8SEliad Peller 		wlcore_free_klv_template(wl, &wlvif->sta.klv_template_id);
27137b3115f2SLuciano Coelho 	} else {
27147b3115f2SLuciano Coelho 		wlvif->ap.bcast_hlid = WL12XX_INVALID_LINK_ID;
27157b3115f2SLuciano Coelho 		wlvif->ap.global_hlid = WL12XX_INVALID_LINK_ID;
27167b3115f2SLuciano Coelho 		wl12xx_free_rate_policy(wl, &wlvif->ap.mgmt_rate_idx);
27177b3115f2SLuciano Coelho 		wl12xx_free_rate_policy(wl, &wlvif->ap.bcast_rate_idx);
27187b3115f2SLuciano Coelho 		for (i = 0; i < CONF_TX_MAX_AC_COUNT; i++)
27197b3115f2SLuciano Coelho 			wl12xx_free_rate_policy(wl,
27207b3115f2SLuciano Coelho 						&wlvif->ap.ucast_rate_idx[i]);
27217b3115f2SLuciano Coelho 		wl1271_free_ap_keys(wl, wlvif);
27227b3115f2SLuciano Coelho 	}
27237b3115f2SLuciano Coelho 
27247b3115f2SLuciano Coelho 	dev_kfree_skb(wlvif->probereq);
27257b3115f2SLuciano Coelho 	wlvif->probereq = NULL;
27267b3115f2SLuciano Coelho 	if (wl->last_wlvif == wlvif)
27277b3115f2SLuciano Coelho 		wl->last_wlvif = NULL;
27287b3115f2SLuciano Coelho 	list_del(&wlvif->list);
27297b3115f2SLuciano Coelho 	memset(wlvif->ap.sta_hlid_map, 0, sizeof(wlvif->ap.sta_hlid_map));
27307b3115f2SLuciano Coelho 	wlvif->role_id = WL12XX_INVALID_ROLE_ID;
27317b3115f2SLuciano Coelho 	wlvif->dev_role_id = WL12XX_INVALID_ROLE_ID;
27327b3115f2SLuciano Coelho 
27332f18cf7cSArik Nemtsov 	if (is_ap)
27347b3115f2SLuciano Coelho 		wl->ap_count--;
27357b3115f2SLuciano Coelho 	else
27367b3115f2SLuciano Coelho 		wl->sta_count--;
27377b3115f2SLuciano Coelho 
273842066f9aSArik Nemtsov 	/*
273942066f9aSArik Nemtsov 	 * Last AP, have more stations. Configure sleep auth according to STA.
274042066f9aSArik Nemtsov 	 * Don't do thin on unintended recovery.
274142066f9aSArik Nemtsov 	 */
274242066f9aSArik Nemtsov 	if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags) &&
274342066f9aSArik Nemtsov 	    !test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags))
274442066f9aSArik Nemtsov 		goto unlock;
274542066f9aSArik Nemtsov 
274671e996beSEliad Peller 	if (wl->ap_count == 0 && is_ap) {
274771e996beSEliad Peller 		/* mask ap events */
274871e996beSEliad Peller 		wl->event_mask &= ~wl->ap_event_mask;
274971e996beSEliad Peller 		wl1271_event_unmask(wl);
275071e996beSEliad Peller 	}
275171e996beSEliad Peller 
27522f18cf7cSArik Nemtsov 	if (wl->ap_count == 0 && is_ap && wl->sta_count) {
27532f18cf7cSArik Nemtsov 		u8 sta_auth = wl->conf.conn.sta_sleep_auth;
27542f18cf7cSArik Nemtsov 		/* Configure for power according to debugfs */
27552f18cf7cSArik Nemtsov 		if (sta_auth != WL1271_PSM_ILLEGAL)
27562f18cf7cSArik Nemtsov 			wl1271_acx_sleep_auth(wl, sta_auth);
27572f18cf7cSArik Nemtsov 		/* Configure for ELP power saving */
27582f18cf7cSArik Nemtsov 		else
27592f18cf7cSArik Nemtsov 			wl1271_acx_sleep_auth(wl, WL1271_PSM_ELP);
27602f18cf7cSArik Nemtsov 	}
27612f18cf7cSArik Nemtsov 
276242066f9aSArik Nemtsov unlock:
27637b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
27647b3115f2SLuciano Coelho 
27657b3115f2SLuciano Coelho 	del_timer_sync(&wlvif->rx_streaming_timer);
27667b3115f2SLuciano Coelho 	cancel_work_sync(&wlvif->rx_streaming_enable_work);
27677b3115f2SLuciano Coelho 	cancel_work_sync(&wlvif->rx_streaming_disable_work);
27687d3b29e5SEliad Peller 	cancel_work_sync(&wlvif->rc_update_work);
2769c50a2825SEliad Peller 	cancel_delayed_work_sync(&wlvif->connection_loss_work);
2770c838478bSArik Nemtsov 	cancel_delayed_work_sync(&wlvif->channel_switch_work);
2771187e52ccSArik Nemtsov 	cancel_delayed_work_sync(&wlvif->pending_auth_complete_work);
27727b3115f2SLuciano Coelho 
27737b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
27747b3115f2SLuciano Coelho }
27757b3115f2SLuciano Coelho 
wl1271_op_remove_interface(struct ieee80211_hw * hw,struct ieee80211_vif * vif)27767b3115f2SLuciano Coelho static void wl1271_op_remove_interface(struct ieee80211_hw *hw,
27777b3115f2SLuciano Coelho 				       struct ieee80211_vif *vif)
27787b3115f2SLuciano Coelho {
27797b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
27807b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
27817b3115f2SLuciano Coelho 	struct wl12xx_vif *iter;
27827b3115f2SLuciano Coelho 	struct vif_counter_data vif_count;
27837b3115f2SLuciano Coelho 
27847b3115f2SLuciano Coelho 	wl12xx_get_vif_count(hw, vif, &vif_count);
27857b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
27867b3115f2SLuciano Coelho 
27874cc53383SIdo Yariv 	if (wl->state == WLCORE_STATE_OFF ||
27887b3115f2SLuciano Coelho 	    !test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags))
27897b3115f2SLuciano Coelho 		goto out;
27907b3115f2SLuciano Coelho 
27917b3115f2SLuciano Coelho 	/*
27927b3115f2SLuciano Coelho 	 * wl->vif can be null here if someone shuts down the interface
27937b3115f2SLuciano Coelho 	 * just when hardware recovery has been started.
27947b3115f2SLuciano Coelho 	 */
27957b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif(wl, iter) {
27967b3115f2SLuciano Coelho 		if (iter != wlvif)
27977b3115f2SLuciano Coelho 			continue;
27987b3115f2SLuciano Coelho 
27997b3115f2SLuciano Coelho 		__wl1271_op_remove_interface(wl, vif, true);
28007b3115f2SLuciano Coelho 		break;
28017b3115f2SLuciano Coelho 	}
28027b3115f2SLuciano Coelho 	WARN_ON(iter != wlvif);
28037b3115f2SLuciano Coelho 	if (wl12xx_need_fw_change(wl, vif_count, false)) {
28047b3115f2SLuciano Coelho 		wl12xx_force_active_psm(wl);
28057b3115f2SLuciano Coelho 		set_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags);
28067b3115f2SLuciano Coelho 		wl12xx_queue_recovery_work(wl);
28077b3115f2SLuciano Coelho 	}
28087b3115f2SLuciano Coelho out:
28097b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
28107b3115f2SLuciano Coelho }
28117b3115f2SLuciano Coelho 
wl12xx_op_change_interface(struct ieee80211_hw * hw,struct ieee80211_vif * vif,enum nl80211_iftype new_type,bool p2p)28127b3115f2SLuciano Coelho static int wl12xx_op_change_interface(struct ieee80211_hw *hw,
28137b3115f2SLuciano Coelho 				      struct ieee80211_vif *vif,
28147b3115f2SLuciano Coelho 				      enum nl80211_iftype new_type, bool p2p)
28157b3115f2SLuciano Coelho {
28167b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
28177b3115f2SLuciano Coelho 	int ret;
28187b3115f2SLuciano Coelho 
28197b3115f2SLuciano Coelho 	set_bit(WL1271_FLAG_VIF_CHANGE_IN_PROGRESS, &wl->flags);
28207b3115f2SLuciano Coelho 	wl1271_op_remove_interface(hw, vif);
28217b3115f2SLuciano Coelho 
28227b3115f2SLuciano Coelho 	vif->type = new_type;
28237b3115f2SLuciano Coelho 	vif->p2p = p2p;
28247b3115f2SLuciano Coelho 	ret = wl1271_op_add_interface(hw, vif);
28257b3115f2SLuciano Coelho 
28267b3115f2SLuciano Coelho 	clear_bit(WL1271_FLAG_VIF_CHANGE_IN_PROGRESS, &wl->flags);
28277b3115f2SLuciano Coelho 	return ret;
28287b3115f2SLuciano Coelho }
28297b3115f2SLuciano Coelho 
wlcore_join(struct wl1271 * wl,struct wl12xx_vif * wlvif)28303230f35eSEliad Peller static int wlcore_join(struct wl1271 *wl, struct wl12xx_vif *wlvif)
28317b3115f2SLuciano Coelho {
28327b3115f2SLuciano Coelho 	int ret;
28337b3115f2SLuciano Coelho 	bool is_ibss = (wlvif->bss_type == BSS_TYPE_IBSS);
28347b3115f2SLuciano Coelho 
28357b3115f2SLuciano Coelho 	/*
28367b3115f2SLuciano Coelho 	 * One of the side effects of the JOIN command is that is clears
28377b3115f2SLuciano Coelho 	 * WPA/WPA2 keys from the chipset. Performing a JOIN while associated
28387b3115f2SLuciano Coelho 	 * to a WPA/WPA2 access point will therefore kill the data-path.
28397b3115f2SLuciano Coelho 	 * Currently the only valid scenario for JOIN during association
28407b3115f2SLuciano Coelho 	 * is on roaming, in which case we will also be given new keys.
28417b3115f2SLuciano Coelho 	 * Keep the below message for now, unless it starts bothering
28427b3115f2SLuciano Coelho 	 * users who really like to roam a lot :)
28437b3115f2SLuciano Coelho 	 */
28447b3115f2SLuciano Coelho 	if (test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
28457b3115f2SLuciano Coelho 		wl1271_info("JOIN while associated.");
28467b3115f2SLuciano Coelho 
28477b3115f2SLuciano Coelho 	/* clear encryption type */
28487b3115f2SLuciano Coelho 	wlvif->encryption_type = KEY_NONE;
28497b3115f2SLuciano Coelho 
28507b3115f2SLuciano Coelho 	if (is_ibss)
28517b3115f2SLuciano Coelho 		ret = wl12xx_cmd_role_start_ibss(wl, wlvif);
2852cb88d01bSTony Lindgren 	else
28537b3115f2SLuciano Coelho 		ret = wl12xx_cmd_role_start_sta(wl, wlvif);
28547b3115f2SLuciano Coelho 
28553230f35eSEliad Peller 	return ret;
28563230f35eSEliad Peller }
28573230f35eSEliad Peller 
wl1271_ssid_set(struct wl12xx_vif * wlvif,struct sk_buff * skb,int offset)28583230f35eSEliad Peller static int wl1271_ssid_set(struct wl12xx_vif *wlvif, struct sk_buff *skb,
28593230f35eSEliad Peller 			    int offset)
28603230f35eSEliad Peller {
28613230f35eSEliad Peller 	u8 ssid_len;
28623230f35eSEliad Peller 	const u8 *ptr = cfg80211_find_ie(WLAN_EID_SSID, skb->data + offset,
28633230f35eSEliad Peller 					 skb->len - offset);
28643230f35eSEliad Peller 
28653230f35eSEliad Peller 	if (!ptr) {
28663230f35eSEliad Peller 		wl1271_error("No SSID in IEs!");
28673230f35eSEliad Peller 		return -ENOENT;
28683230f35eSEliad Peller 	}
28693230f35eSEliad Peller 
28703230f35eSEliad Peller 	ssid_len = ptr[1];
28713230f35eSEliad Peller 	if (ssid_len > IEEE80211_MAX_SSID_LEN) {
28723230f35eSEliad Peller 		wl1271_error("SSID is too long!");
28733230f35eSEliad Peller 		return -EINVAL;
28743230f35eSEliad Peller 	}
28753230f35eSEliad Peller 
28763230f35eSEliad Peller 	wlvif->ssid_len = ssid_len;
28773230f35eSEliad Peller 	memcpy(wlvif->ssid, ptr+2, ssid_len);
28783230f35eSEliad Peller 	return 0;
28793230f35eSEliad Peller }
28803230f35eSEliad Peller 
wlcore_set_ssid(struct wl1271 * wl,struct wl12xx_vif * wlvif)28813230f35eSEliad Peller static int wlcore_set_ssid(struct wl1271 *wl, struct wl12xx_vif *wlvif)
28823230f35eSEliad Peller {
28833230f35eSEliad Peller 	struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
28843230f35eSEliad Peller 	struct sk_buff *skb;
28853230f35eSEliad Peller 	int ieoffset;
28863230f35eSEliad Peller 
28873230f35eSEliad Peller 	/* we currently only support setting the ssid from the ap probe req */
28883230f35eSEliad Peller 	if (wlvif->bss_type != BSS_TYPE_STA_BSS)
28893230f35eSEliad Peller 		return -EINVAL;
28903230f35eSEliad Peller 
28913230f35eSEliad Peller 	skb = ieee80211_ap_probereq_get(wl->hw, vif);
28923230f35eSEliad Peller 	if (!skb)
28933230f35eSEliad Peller 		return -EINVAL;
28943230f35eSEliad Peller 
28953230f35eSEliad Peller 	ieoffset = offsetof(struct ieee80211_mgmt,
28963230f35eSEliad Peller 			    u.probe_req.variable);
28973230f35eSEliad Peller 	wl1271_ssid_set(wlvif, skb, ieoffset);
28983230f35eSEliad Peller 	dev_kfree_skb(skb);
28993230f35eSEliad Peller 
29003230f35eSEliad Peller 	return 0;
29013230f35eSEliad Peller }
29023230f35eSEliad Peller 
wlcore_set_assoc(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_bss_conf * bss_conf,u32 sta_rate_set)29033230f35eSEliad Peller static int wlcore_set_assoc(struct wl1271 *wl, struct wl12xx_vif *wlvif,
2904ec87011aSEliad Peller 			    struct ieee80211_bss_conf *bss_conf,
2905ec87011aSEliad Peller 			    u32 sta_rate_set)
29063230f35eSEliad Peller {
2907f276e20bSJohannes Berg 	struct ieee80211_vif *vif = container_of(bss_conf, struct ieee80211_vif,
2908f276e20bSJohannes Berg 						 bss_conf);
29093230f35eSEliad Peller 	int ieoffset;
29103230f35eSEliad Peller 	int ret;
29113230f35eSEliad Peller 
2912f276e20bSJohannes Berg 	wlvif->aid = vif->cfg.aid;
2913aaabee8bSLuciano Coelho 	wlvif->channel_type = cfg80211_get_chandef_type(&bss_conf->chandef);
29143230f35eSEliad Peller 	wlvif->beacon_int = bss_conf->beacon_int;
2915d50529c0SEliad Peller 	wlvif->wmm_enabled = bss_conf->qos;
29163230f35eSEliad Peller 
29173230f35eSEliad Peller 	set_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags);
29183230f35eSEliad Peller 
29193230f35eSEliad Peller 	/*
29203230f35eSEliad Peller 	 * with wl1271, we don't need to update the
29213230f35eSEliad Peller 	 * beacon_int and dtim_period, because the firmware
29223230f35eSEliad Peller 	 * updates it by itself when the first beacon is
29233230f35eSEliad Peller 	 * received after a join.
29243230f35eSEliad Peller 	 */
29253230f35eSEliad Peller 	ret = wl1271_cmd_build_ps_poll(wl, wlvif, wlvif->aid);
29263230f35eSEliad Peller 	if (ret < 0)
29273230f35eSEliad Peller 		return ret;
29283230f35eSEliad Peller 
29293230f35eSEliad Peller 	/*
29303230f35eSEliad Peller 	 * Get a template for hardware connection maintenance
29313230f35eSEliad Peller 	 */
29323230f35eSEliad Peller 	dev_kfree_skb(wlvif->probereq);
29333230f35eSEliad Peller 	wlvif->probereq = wl1271_cmd_build_ap_probe_req(wl,
29343230f35eSEliad Peller 							wlvif,
29353230f35eSEliad Peller 							NULL);
29363230f35eSEliad Peller 	ieoffset = offsetof(struct ieee80211_mgmt,
29373230f35eSEliad Peller 			    u.probe_req.variable);
29383230f35eSEliad Peller 	wl1271_ssid_set(wlvif, wlvif->probereq, ieoffset);
29393230f35eSEliad Peller 
29403230f35eSEliad Peller 	/* enable the connection monitoring feature */
29413230f35eSEliad Peller 	ret = wl1271_acx_conn_monit_params(wl, wlvif, true);
29423230f35eSEliad Peller 	if (ret < 0)
29433230f35eSEliad Peller 		return ret;
29447b3115f2SLuciano Coelho 
29457b3115f2SLuciano Coelho 	/*
29467b3115f2SLuciano Coelho 	 * The join command disable the keep-alive mode, shut down its process,
29477b3115f2SLuciano Coelho 	 * and also clear the template config, so we need to reset it all after
29487b3115f2SLuciano Coelho 	 * the join. The acx_aid starts the keep-alive process, and the order
29497b3115f2SLuciano Coelho 	 * of the commands below is relevant.
29507b3115f2SLuciano Coelho 	 */
29517b3115f2SLuciano Coelho 	ret = wl1271_acx_keep_alive_mode(wl, wlvif, true);
29527b3115f2SLuciano Coelho 	if (ret < 0)
29533230f35eSEliad Peller 		return ret;
29547b3115f2SLuciano Coelho 
29557b3115f2SLuciano Coelho 	ret = wl1271_acx_aid(wl, wlvif, wlvif->aid);
29567b3115f2SLuciano Coelho 	if (ret < 0)
29573230f35eSEliad Peller 		return ret;
29587b3115f2SLuciano Coelho 
29597b3115f2SLuciano Coelho 	ret = wl12xx_cmd_build_klv_null_data(wl, wlvif);
29607b3115f2SLuciano Coelho 	if (ret < 0)
29613230f35eSEliad Peller 		return ret;
29627b3115f2SLuciano Coelho 
29637b3115f2SLuciano Coelho 	ret = wl1271_acx_keep_alive_config(wl, wlvif,
2964001e39a8SEliad Peller 					   wlvif->sta.klv_template_id,
29657b3115f2SLuciano Coelho 					   ACX_KEEP_ALIVE_TPL_VALID);
29667b3115f2SLuciano Coelho 	if (ret < 0)
29673230f35eSEliad Peller 		return ret;
29687b3115f2SLuciano Coelho 
29696c7b5194SEliad Peller 	/*
29706c7b5194SEliad Peller 	 * The default fw psm configuration is AUTO, while mac80211 default
29716c7b5194SEliad Peller 	 * setting is off (ACTIVE), so sync the fw with the correct value.
29726c7b5194SEliad Peller 	 */
29736c7b5194SEliad Peller 	ret = wl1271_ps_set_mode(wl, wlvif, STATION_ACTIVE_MODE);
2974ec87011aSEliad Peller 	if (ret < 0)
2975ec87011aSEliad Peller 		return ret;
2976ec87011aSEliad Peller 
2977ec87011aSEliad Peller 	if (sta_rate_set) {
2978ec87011aSEliad Peller 		wlvif->rate_set =
2979ec87011aSEliad Peller 			wl1271_tx_enabled_rates_get(wl,
2980ec87011aSEliad Peller 						    sta_rate_set,
2981ec87011aSEliad Peller 						    wlvif->band);
2982ec87011aSEliad Peller 		ret = wl1271_acx_sta_rate_policies(wl, wlvif);
2983ec87011aSEliad Peller 		if (ret < 0)
29847b3115f2SLuciano Coelho 			return ret;
29857b3115f2SLuciano Coelho 	}
29867b3115f2SLuciano Coelho 
29877b3115f2SLuciano Coelho 	return ret;
29887b3115f2SLuciano Coelho }
29897b3115f2SLuciano Coelho 
wlcore_unset_assoc(struct wl1271 * wl,struct wl12xx_vif * wlvif)29903230f35eSEliad Peller static int wlcore_unset_assoc(struct wl1271 *wl, struct wl12xx_vif *wlvif)
29917b3115f2SLuciano Coelho {
29927b3115f2SLuciano Coelho 	int ret;
29933230f35eSEliad Peller 	bool sta = wlvif->bss_type == BSS_TYPE_STA_BSS;
29943230f35eSEliad Peller 
29953230f35eSEliad Peller 	/* make sure we are connected (sta) joined */
29963230f35eSEliad Peller 	if (sta &&
29973230f35eSEliad Peller 	    !test_and_clear_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
29983230f35eSEliad Peller 		return false;
29993230f35eSEliad Peller 
30003230f35eSEliad Peller 	/* make sure we are joined (ibss) */
30013230f35eSEliad Peller 	if (!sta &&
30023230f35eSEliad Peller 	    test_and_clear_bit(WLVIF_FLAG_IBSS_JOINED, &wlvif->flags))
30033230f35eSEliad Peller 		return false;
30043230f35eSEliad Peller 
30053230f35eSEliad Peller 	if (sta) {
30063230f35eSEliad Peller 		/* use defaults when not associated */
30073230f35eSEliad Peller 		wlvif->aid = 0;
30083230f35eSEliad Peller 
30093230f35eSEliad Peller 		/* free probe-request template */
30103230f35eSEliad Peller 		dev_kfree_skb(wlvif->probereq);
30113230f35eSEliad Peller 		wlvif->probereq = NULL;
30123230f35eSEliad Peller 
30133230f35eSEliad Peller 		/* disable connection monitor features */
30143230f35eSEliad Peller 		ret = wl1271_acx_conn_monit_params(wl, wlvif, false);
30153230f35eSEliad Peller 		if (ret < 0)
30163230f35eSEliad Peller 			return ret;
30173230f35eSEliad Peller 
30183230f35eSEliad Peller 		/* Disable the keep-alive feature */
30193230f35eSEliad Peller 		ret = wl1271_acx_keep_alive_mode(wl, wlvif, false);
30203230f35eSEliad Peller 		if (ret < 0)
30213230f35eSEliad Peller 			return ret;
3022d881fa2cSEliad Peller 
3023d881fa2cSEliad Peller 		/* disable beacon filtering */
3024d881fa2cSEliad Peller 		ret = wl1271_acx_beacon_filter_opt(wl, wlvif, false);
3025d881fa2cSEliad Peller 		if (ret < 0)
3026d881fa2cSEliad Peller 			return ret;
30273230f35eSEliad Peller 	}
30287b3115f2SLuciano Coelho 
30297b3115f2SLuciano Coelho 	if (test_and_clear_bit(WLVIF_FLAG_CS_PROGRESS, &wlvif->flags)) {
30307b3115f2SLuciano Coelho 		struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
30317b3115f2SLuciano Coelho 
3032fcab1890SEliad Peller 		wl12xx_cmd_stop_channel_switch(wl, wlvif);
30337b3115f2SLuciano Coelho 		ieee80211_chswitch_done(vif, false);
3034c50a2825SEliad Peller 		cancel_delayed_work(&wlvif->channel_switch_work);
30357b3115f2SLuciano Coelho 	}
30367b3115f2SLuciano Coelho 
30374137c17cSEliad Peller 	/* invalidate keep-alive template */
30384137c17cSEliad Peller 	wl1271_acx_keep_alive_config(wl, wlvif,
3039001e39a8SEliad Peller 				     wlvif->sta.klv_template_id,
30404137c17cSEliad Peller 				     ACX_KEEP_ALIVE_TPL_INVALID);
30414137c17cSEliad Peller 
30423230f35eSEliad Peller 	return 0;
30437b3115f2SLuciano Coelho }
30447b3115f2SLuciano Coelho 
wl1271_set_band_rate(struct wl1271 * wl,struct wl12xx_vif * wlvif)30457b3115f2SLuciano Coelho static void wl1271_set_band_rate(struct wl1271 *wl, struct wl12xx_vif *wlvif)
30467b3115f2SLuciano Coelho {
30477b3115f2SLuciano Coelho 	wlvif->basic_rate_set = wlvif->bitrate_masks[wlvif->band];
30487b3115f2SLuciano Coelho 	wlvif->rate_set = wlvif->basic_rate_set;
30497b3115f2SLuciano Coelho }
30507b3115f2SLuciano Coelho 
wl1271_sta_handle_idle(struct wl1271 * wl,struct wl12xx_vif * wlvif,bool idle)3051b0ed8a4dSArik Nemtsov static void wl1271_sta_handle_idle(struct wl1271 *wl, struct wl12xx_vif *wlvif,
3052b0ed8a4dSArik Nemtsov 				   bool idle)
3053b0ed8a4dSArik Nemtsov {
3054b0ed8a4dSArik Nemtsov 	bool cur_idle = !test_bit(WLVIF_FLAG_ACTIVE, &wlvif->flags);
3055b0ed8a4dSArik Nemtsov 
3056b0ed8a4dSArik Nemtsov 	if (idle == cur_idle)
3057b0ed8a4dSArik Nemtsov 		return;
3058b0ed8a4dSArik Nemtsov 
3059b0ed8a4dSArik Nemtsov 	if (idle) {
3060b0ed8a4dSArik Nemtsov 		clear_bit(WLVIF_FLAG_ACTIVE, &wlvif->flags);
3061b0ed8a4dSArik Nemtsov 	} else {
3062b0ed8a4dSArik Nemtsov 		/* The current firmware only supports sched_scan in idle */
3063b0ed8a4dSArik Nemtsov 		if (wl->sched_vif == wlvif)
3064b0ed8a4dSArik Nemtsov 			wl->ops->sched_scan_stop(wl, wlvif);
3065b0ed8a4dSArik Nemtsov 
3066b0ed8a4dSArik Nemtsov 		set_bit(WLVIF_FLAG_ACTIVE, &wlvif->flags);
3067b0ed8a4dSArik Nemtsov 	}
3068b0ed8a4dSArik Nemtsov }
3069b0ed8a4dSArik Nemtsov 
wl12xx_config_vif(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_conf * conf,u32 changed)30707b3115f2SLuciano Coelho static int wl12xx_config_vif(struct wl1271 *wl, struct wl12xx_vif *wlvif,
30717b3115f2SLuciano Coelho 			     struct ieee80211_conf *conf, u32 changed)
30727b3115f2SLuciano Coelho {
3073b6970ee5SEliad Peller 	int ret;
30747b3115f2SLuciano Coelho 
30757845af35SEliad Peller 	if (wlcore_is_p2p_mgmt(wlvif))
30767845af35SEliad Peller 		return 0;
30777845af35SEliad Peller 
30787b3115f2SLuciano Coelho 	if (conf->power_level != wlvif->power_level) {
30797b3115f2SLuciano Coelho 		ret = wl1271_acx_tx_power(wl, wlvif, conf->power_level);
30807b3115f2SLuciano Coelho 		if (ret < 0)
30817b3115f2SLuciano Coelho 			return ret;
30827b3115f2SLuciano Coelho 
30837b3115f2SLuciano Coelho 		wlvif->power_level = conf->power_level;
30847b3115f2SLuciano Coelho 	}
30857b3115f2SLuciano Coelho 
30867b3115f2SLuciano Coelho 	return 0;
30877b3115f2SLuciano Coelho }
30887b3115f2SLuciano Coelho 
wl1271_op_config(struct ieee80211_hw * hw,u32 changed)30897b3115f2SLuciano Coelho static int wl1271_op_config(struct ieee80211_hw *hw, u32 changed)
30907b3115f2SLuciano Coelho {
30917b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
30927b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
30937b3115f2SLuciano Coelho 	struct ieee80211_conf *conf = &hw->conf;
3094b6970ee5SEliad Peller 	int ret = 0;
30957b3115f2SLuciano Coelho 
3096b6970ee5SEliad Peller 	wl1271_debug(DEBUG_MAC80211, "mac80211 config psm %s power %d %s"
30977b3115f2SLuciano Coelho 		     " changed 0x%x",
30987b3115f2SLuciano Coelho 		     conf->flags & IEEE80211_CONF_PS ? "on" : "off",
30997b3115f2SLuciano Coelho 		     conf->power_level,
31007b3115f2SLuciano Coelho 		     conf->flags & IEEE80211_CONF_IDLE ? "idle" : "in use",
31017b3115f2SLuciano Coelho 			 changed);
31027b3115f2SLuciano Coelho 
31037b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
31047b3115f2SLuciano Coelho 
31057b3115f2SLuciano Coelho 	if (changed & IEEE80211_CONF_CHANGE_POWER)
31067b3115f2SLuciano Coelho 		wl->power_level = conf->power_level;
31077b3115f2SLuciano Coelho 
31084cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
31097b3115f2SLuciano Coelho 		goto out;
31107b3115f2SLuciano Coelho 
3111ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3112ab589ac2SMinghao Chi 	if (ret < 0)
31137b3115f2SLuciano Coelho 		goto out;
31147b3115f2SLuciano Coelho 
31157b3115f2SLuciano Coelho 	/* configure each interface */
31167b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif(wl, wlvif) {
31177b3115f2SLuciano Coelho 		ret = wl12xx_config_vif(wl, wlvif, conf, changed);
31187b3115f2SLuciano Coelho 		if (ret < 0)
31197b3115f2SLuciano Coelho 			goto out_sleep;
31207b3115f2SLuciano Coelho 	}
31217b3115f2SLuciano Coelho 
31227b3115f2SLuciano Coelho out_sleep:
31239b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
31249b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
31257b3115f2SLuciano Coelho 
31267b3115f2SLuciano Coelho out:
31277b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
31287b3115f2SLuciano Coelho 
31297b3115f2SLuciano Coelho 	return ret;
31307b3115f2SLuciano Coelho }
31317b3115f2SLuciano Coelho 
31327b3115f2SLuciano Coelho struct wl1271_filter_params {
31337b3115f2SLuciano Coelho 	bool enabled;
31347b3115f2SLuciano Coelho 	int mc_list_length;
31357b3115f2SLuciano Coelho 	u8 mc_list[ACX_MC_ADDRESS_GROUP_MAX][ETH_ALEN];
31367b3115f2SLuciano Coelho };
31377b3115f2SLuciano Coelho 
wl1271_op_prepare_multicast(struct ieee80211_hw * hw,struct netdev_hw_addr_list * mc_list)31387b3115f2SLuciano Coelho static u64 wl1271_op_prepare_multicast(struct ieee80211_hw *hw,
31397b3115f2SLuciano Coelho 				       struct netdev_hw_addr_list *mc_list)
31407b3115f2SLuciano Coelho {
31417b3115f2SLuciano Coelho 	struct wl1271_filter_params *fp;
31427b3115f2SLuciano Coelho 	struct netdev_hw_addr *ha;
31437b3115f2SLuciano Coelho 
31447b3115f2SLuciano Coelho 	fp = kzalloc(sizeof(*fp), GFP_ATOMIC);
31457b3115f2SLuciano Coelho 	if (!fp) {
31467b3115f2SLuciano Coelho 		wl1271_error("Out of memory setting filters.");
31477b3115f2SLuciano Coelho 		return 0;
31487b3115f2SLuciano Coelho 	}
31497b3115f2SLuciano Coelho 
31507b3115f2SLuciano Coelho 	/* update multicast filtering parameters */
31517b3115f2SLuciano Coelho 	fp->mc_list_length = 0;
31527b3115f2SLuciano Coelho 	if (netdev_hw_addr_list_count(mc_list) > ACX_MC_ADDRESS_GROUP_MAX) {
31537b3115f2SLuciano Coelho 		fp->enabled = false;
31547b3115f2SLuciano Coelho 	} else {
31557b3115f2SLuciano Coelho 		fp->enabled = true;
31567b3115f2SLuciano Coelho 		netdev_hw_addr_list_for_each(ha, mc_list) {
31577b3115f2SLuciano Coelho 			memcpy(fp->mc_list[fp->mc_list_length],
31587b3115f2SLuciano Coelho 					ha->addr, ETH_ALEN);
31597b3115f2SLuciano Coelho 			fp->mc_list_length++;
31607b3115f2SLuciano Coelho 		}
31617b3115f2SLuciano Coelho 	}
31627b3115f2SLuciano Coelho 
31637b3115f2SLuciano Coelho 	return (u64)(unsigned long)fp;
31647b3115f2SLuciano Coelho }
31657b3115f2SLuciano Coelho 
3166df140465SJohannes Berg #define WL1271_SUPPORTED_FILTERS (FIF_ALLMULTI | \
31677b3115f2SLuciano Coelho 				  FIF_FCSFAIL | \
31687b3115f2SLuciano Coelho 				  FIF_BCN_PRBRESP_PROMISC | \
31697b3115f2SLuciano Coelho 				  FIF_CONTROL | \
31707b3115f2SLuciano Coelho 				  FIF_OTHER_BSS)
31717b3115f2SLuciano Coelho 
wl1271_op_configure_filter(struct ieee80211_hw * hw,unsigned int changed,unsigned int * total,u64 multicast)31727b3115f2SLuciano Coelho static void wl1271_op_configure_filter(struct ieee80211_hw *hw,
31737b3115f2SLuciano Coelho 				       unsigned int changed,
31747b3115f2SLuciano Coelho 				       unsigned int *total, u64 multicast)
31757b3115f2SLuciano Coelho {
31767b3115f2SLuciano Coelho 	struct wl1271_filter_params *fp = (void *)(unsigned long)multicast;
31777b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
31787b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
31797b3115f2SLuciano Coelho 
31807b3115f2SLuciano Coelho 	int ret;
31817b3115f2SLuciano Coelho 
31827b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 configure filter changed %x"
31837b3115f2SLuciano Coelho 		     " total %x", changed, *total);
31847b3115f2SLuciano Coelho 
31857b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
31867b3115f2SLuciano Coelho 
31877b3115f2SLuciano Coelho 	*total &= WL1271_SUPPORTED_FILTERS;
31887b3115f2SLuciano Coelho 	changed &= WL1271_SUPPORTED_FILTERS;
31897b3115f2SLuciano Coelho 
31904cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
31917b3115f2SLuciano Coelho 		goto out;
31927b3115f2SLuciano Coelho 
3193ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3194ab589ac2SMinghao Chi 	if (ret < 0)
31957b3115f2SLuciano Coelho 		goto out;
31967b3115f2SLuciano Coelho 
31977b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif(wl, wlvif) {
31987845af35SEliad Peller 		if (wlcore_is_p2p_mgmt(wlvif))
31997845af35SEliad Peller 			continue;
32007845af35SEliad Peller 
32017b3115f2SLuciano Coelho 		if (wlvif->bss_type != BSS_TYPE_AP_BSS) {
32027b3115f2SLuciano Coelho 			if (*total & FIF_ALLMULTI)
32037b3115f2SLuciano Coelho 				ret = wl1271_acx_group_address_tbl(wl, wlvif,
32047b3115f2SLuciano Coelho 								   false,
32057b3115f2SLuciano Coelho 								   NULL, 0);
32067b3115f2SLuciano Coelho 			else if (fp)
32077b3115f2SLuciano Coelho 				ret = wl1271_acx_group_address_tbl(wl, wlvif,
32087b3115f2SLuciano Coelho 							fp->enabled,
32097b3115f2SLuciano Coelho 							fp->mc_list,
32107b3115f2SLuciano Coelho 							fp->mc_list_length);
32117b3115f2SLuciano Coelho 			if (ret < 0)
32127b3115f2SLuciano Coelho 				goto out_sleep;
32137b3115f2SLuciano Coelho 		}
32141f866532SIain Hunter 
32151f866532SIain Hunter 		/*
32161f866532SIain Hunter 		 * If interface in AP mode and created with allmulticast then disable
32171f866532SIain Hunter 		 * the firmware filters so that all multicast packets are passed
32181f866532SIain Hunter 		 * This is mandatory for MDNS based discovery protocols
32191f866532SIain Hunter 		 */
32201f866532SIain Hunter 		if (wlvif->bss_type == BSS_TYPE_AP_BSS) {
32211f866532SIain Hunter 			if (*total & FIF_ALLMULTI) {
32221f866532SIain Hunter 				ret = wl1271_acx_group_address_tbl(wl, wlvif,
32231f866532SIain Hunter 							false,
32241f866532SIain Hunter 							NULL, 0);
32251f866532SIain Hunter 				if (ret < 0)
32261f866532SIain Hunter 					goto out_sleep;
32271f866532SIain Hunter 			}
32281f866532SIain Hunter 		}
32297b3115f2SLuciano Coelho 	}
32307b3115f2SLuciano Coelho 
32317b3115f2SLuciano Coelho 	/*
32327b3115f2SLuciano Coelho 	 * the fw doesn't provide an api to configure the filters. instead,
32337b3115f2SLuciano Coelho 	 * the filters configuration is based on the active roles / ROC
32347b3115f2SLuciano Coelho 	 * state.
32357b3115f2SLuciano Coelho 	 */
32367b3115f2SLuciano Coelho 
32377b3115f2SLuciano Coelho out_sleep:
32389b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
32399b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
32407b3115f2SLuciano Coelho 
32417b3115f2SLuciano Coelho out:
32427b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
32437b3115f2SLuciano Coelho 	kfree(fp);
32447b3115f2SLuciano Coelho }
32457b3115f2SLuciano Coelho 
wl1271_record_ap_key(struct wl1271 * wl,struct wl12xx_vif * wlvif,u8 id,u8 key_type,u8 key_size,const u8 * key,u8 hlid,u32 tx_seq_32,u16 tx_seq_16,bool is_pairwise)32467b3115f2SLuciano Coelho static int wl1271_record_ap_key(struct wl1271 *wl, struct wl12xx_vif *wlvif,
32477b3115f2SLuciano Coelho 				u8 id, u8 key_type, u8 key_size,
32487b3115f2SLuciano Coelho 				const u8 *key, u8 hlid, u32 tx_seq_32,
3249cf33a772SMaital Hahn 				u16 tx_seq_16, bool is_pairwise)
32507b3115f2SLuciano Coelho {
32517b3115f2SLuciano Coelho 	struct wl1271_ap_key *ap_key;
32527b3115f2SLuciano Coelho 	int i;
32537b3115f2SLuciano Coelho 
32547b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_CRYPT, "record ap key id %d", (int)id);
32557b3115f2SLuciano Coelho 
32567b3115f2SLuciano Coelho 	if (key_size > MAX_KEY_SIZE)
32577b3115f2SLuciano Coelho 		return -EINVAL;
32587b3115f2SLuciano Coelho 
32597b3115f2SLuciano Coelho 	/*
32607b3115f2SLuciano Coelho 	 * Find next free entry in ap_keys. Also check we are not replacing
32617b3115f2SLuciano Coelho 	 * an existing key.
32627b3115f2SLuciano Coelho 	 */
32637b3115f2SLuciano Coelho 	for (i = 0; i < MAX_NUM_KEYS; i++) {
32647b3115f2SLuciano Coelho 		if (wlvif->ap.recorded_keys[i] == NULL)
32657b3115f2SLuciano Coelho 			break;
32667b3115f2SLuciano Coelho 
32677b3115f2SLuciano Coelho 		if (wlvif->ap.recorded_keys[i]->id == id) {
32687b3115f2SLuciano Coelho 			wl1271_warning("trying to record key replacement");
32697b3115f2SLuciano Coelho 			return -EINVAL;
32707b3115f2SLuciano Coelho 		}
32717b3115f2SLuciano Coelho 	}
32727b3115f2SLuciano Coelho 
32737b3115f2SLuciano Coelho 	if (i == MAX_NUM_KEYS)
32747b3115f2SLuciano Coelho 		return -EBUSY;
32757b3115f2SLuciano Coelho 
32767b3115f2SLuciano Coelho 	ap_key = kzalloc(sizeof(*ap_key), GFP_KERNEL);
32777b3115f2SLuciano Coelho 	if (!ap_key)
32787b3115f2SLuciano Coelho 		return -ENOMEM;
32797b3115f2SLuciano Coelho 
32807b3115f2SLuciano Coelho 	ap_key->id = id;
32817b3115f2SLuciano Coelho 	ap_key->key_type = key_type;
32827b3115f2SLuciano Coelho 	ap_key->key_size = key_size;
32837b3115f2SLuciano Coelho 	memcpy(ap_key->key, key, key_size);
32847b3115f2SLuciano Coelho 	ap_key->hlid = hlid;
32857b3115f2SLuciano Coelho 	ap_key->tx_seq_32 = tx_seq_32;
32867b3115f2SLuciano Coelho 	ap_key->tx_seq_16 = tx_seq_16;
3287cf33a772SMaital Hahn 	ap_key->is_pairwise = is_pairwise;
32887b3115f2SLuciano Coelho 
32897b3115f2SLuciano Coelho 	wlvif->ap.recorded_keys[i] = ap_key;
32907b3115f2SLuciano Coelho 	return 0;
32917b3115f2SLuciano Coelho }
32927b3115f2SLuciano Coelho 
wl1271_free_ap_keys(struct wl1271 * wl,struct wl12xx_vif * wlvif)32937b3115f2SLuciano Coelho static void wl1271_free_ap_keys(struct wl1271 *wl, struct wl12xx_vif *wlvif)
32947b3115f2SLuciano Coelho {
32957b3115f2SLuciano Coelho 	int i;
32967b3115f2SLuciano Coelho 
32977b3115f2SLuciano Coelho 	for (i = 0; i < MAX_NUM_KEYS; i++) {
32987b3115f2SLuciano Coelho 		kfree(wlvif->ap.recorded_keys[i]);
32997b3115f2SLuciano Coelho 		wlvif->ap.recorded_keys[i] = NULL;
33007b3115f2SLuciano Coelho 	}
33017b3115f2SLuciano Coelho }
33027b3115f2SLuciano Coelho 
wl1271_ap_init_hwenc(struct wl1271 * wl,struct wl12xx_vif * wlvif)33037b3115f2SLuciano Coelho static int wl1271_ap_init_hwenc(struct wl1271 *wl, struct wl12xx_vif *wlvif)
33047b3115f2SLuciano Coelho {
33057b3115f2SLuciano Coelho 	int i, ret = 0;
33067b3115f2SLuciano Coelho 	struct wl1271_ap_key *key;
33077b3115f2SLuciano Coelho 	bool wep_key_added = false;
33087b3115f2SLuciano Coelho 
33097b3115f2SLuciano Coelho 	for (i = 0; i < MAX_NUM_KEYS; i++) {
33107b3115f2SLuciano Coelho 		u8 hlid;
33117b3115f2SLuciano Coelho 		if (wlvif->ap.recorded_keys[i] == NULL)
33127b3115f2SLuciano Coelho 			break;
33137b3115f2SLuciano Coelho 
33147b3115f2SLuciano Coelho 		key = wlvif->ap.recorded_keys[i];
33157b3115f2SLuciano Coelho 		hlid = key->hlid;
33167b3115f2SLuciano Coelho 		if (hlid == WL12XX_INVALID_LINK_ID)
33177b3115f2SLuciano Coelho 			hlid = wlvif->ap.bcast_hlid;
33187b3115f2SLuciano Coelho 
33197b3115f2SLuciano Coelho 		ret = wl1271_cmd_set_ap_key(wl, wlvif, KEY_ADD_OR_REPLACE,
33207b3115f2SLuciano Coelho 					    key->id, key->key_type,
33217b3115f2SLuciano Coelho 					    key->key_size, key->key,
33227b3115f2SLuciano Coelho 					    hlid, key->tx_seq_32,
3323cf33a772SMaital Hahn 					    key->tx_seq_16, key->is_pairwise);
33247b3115f2SLuciano Coelho 		if (ret < 0)
33257b3115f2SLuciano Coelho 			goto out;
33267b3115f2SLuciano Coelho 
33277b3115f2SLuciano Coelho 		if (key->key_type == KEY_WEP)
33287b3115f2SLuciano Coelho 			wep_key_added = true;
33297b3115f2SLuciano Coelho 	}
33307b3115f2SLuciano Coelho 
33317b3115f2SLuciano Coelho 	if (wep_key_added) {
33327b3115f2SLuciano Coelho 		ret = wl12xx_cmd_set_default_wep_key(wl, wlvif->default_key,
33337b3115f2SLuciano Coelho 						     wlvif->ap.bcast_hlid);
33347b3115f2SLuciano Coelho 		if (ret < 0)
33357b3115f2SLuciano Coelho 			goto out;
33367b3115f2SLuciano Coelho 	}
33377b3115f2SLuciano Coelho 
33387b3115f2SLuciano Coelho out:
33397b3115f2SLuciano Coelho 	wl1271_free_ap_keys(wl, wlvif);
33407b3115f2SLuciano Coelho 	return ret;
33417b3115f2SLuciano Coelho }
33427b3115f2SLuciano Coelho 
wl1271_set_key(struct wl1271 * wl,struct wl12xx_vif * wlvif,u16 action,u8 id,u8 key_type,u8 key_size,const u8 * key,u32 tx_seq_32,u16 tx_seq_16,struct ieee80211_sta * sta,bool is_pairwise)33437b3115f2SLuciano Coelho static int wl1271_set_key(struct wl1271 *wl, struct wl12xx_vif *wlvif,
33447b3115f2SLuciano Coelho 		       u16 action, u8 id, u8 key_type,
33457b3115f2SLuciano Coelho 		       u8 key_size, const u8 *key, u32 tx_seq_32,
3346cf33a772SMaital Hahn 		       u16 tx_seq_16, struct ieee80211_sta *sta,
3347cf33a772SMaital Hahn 		       bool is_pairwise)
33487b3115f2SLuciano Coelho {
33497b3115f2SLuciano Coelho 	int ret;
33507b3115f2SLuciano Coelho 	bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS);
33517b3115f2SLuciano Coelho 
33527b3115f2SLuciano Coelho 	if (is_ap) {
33537b3115f2SLuciano Coelho 		struct wl1271_station *wl_sta;
33547b3115f2SLuciano Coelho 		u8 hlid;
33557b3115f2SLuciano Coelho 
33567b3115f2SLuciano Coelho 		if (sta) {
33577b3115f2SLuciano Coelho 			wl_sta = (struct wl1271_station *)sta->drv_priv;
33587b3115f2SLuciano Coelho 			hlid = wl_sta->hlid;
33597b3115f2SLuciano Coelho 		} else {
33607b3115f2SLuciano Coelho 			hlid = wlvif->ap.bcast_hlid;
33617b3115f2SLuciano Coelho 		}
33627b3115f2SLuciano Coelho 
33637b3115f2SLuciano Coelho 		if (!test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) {
33647b3115f2SLuciano Coelho 			/*
33657b3115f2SLuciano Coelho 			 * We do not support removing keys after AP shutdown.
33667b3115f2SLuciano Coelho 			 * Pretend we do to make mac80211 happy.
33677b3115f2SLuciano Coelho 			 */
33687b3115f2SLuciano Coelho 			if (action != KEY_ADD_OR_REPLACE)
33697b3115f2SLuciano Coelho 				return 0;
33707b3115f2SLuciano Coelho 
33717b3115f2SLuciano Coelho 			ret = wl1271_record_ap_key(wl, wlvif, id,
33727b3115f2SLuciano Coelho 					     key_type, key_size,
33737b3115f2SLuciano Coelho 					     key, hlid, tx_seq_32,
3374cf33a772SMaital Hahn 					     tx_seq_16, is_pairwise);
33757b3115f2SLuciano Coelho 		} else {
33767b3115f2SLuciano Coelho 			ret = wl1271_cmd_set_ap_key(wl, wlvif, action,
33777b3115f2SLuciano Coelho 					     id, key_type, key_size,
33787b3115f2SLuciano Coelho 					     key, hlid, tx_seq_32,
3379cf33a772SMaital Hahn 					     tx_seq_16, is_pairwise);
33807b3115f2SLuciano Coelho 		}
33817b3115f2SLuciano Coelho 
33827b3115f2SLuciano Coelho 		if (ret < 0)
33837b3115f2SLuciano Coelho 			return ret;
33847b3115f2SLuciano Coelho 	} else {
33857b3115f2SLuciano Coelho 		const u8 *addr;
33867b3115f2SLuciano Coelho 		static const u8 bcast_addr[ETH_ALEN] = {
33877b3115f2SLuciano Coelho 			0xff, 0xff, 0xff, 0xff, 0xff, 0xff
33887b3115f2SLuciano Coelho 		};
33897b3115f2SLuciano Coelho 
33907b3115f2SLuciano Coelho 		addr = sta ? sta->addr : bcast_addr;
33917b3115f2SLuciano Coelho 
33927b3115f2SLuciano Coelho 		if (is_zero_ether_addr(addr)) {
33937b3115f2SLuciano Coelho 			/* We dont support TX only encryption */
33947b3115f2SLuciano Coelho 			return -EOPNOTSUPP;
33957b3115f2SLuciano Coelho 		}
33967b3115f2SLuciano Coelho 
33977b3115f2SLuciano Coelho 		/* The wl1271 does not allow to remove unicast keys - they
33987b3115f2SLuciano Coelho 		   will be cleared automatically on next CMD_JOIN. Ignore the
33997b3115f2SLuciano Coelho 		   request silently, as we dont want the mac80211 to emit
34007b3115f2SLuciano Coelho 		   an error message. */
34017b3115f2SLuciano Coelho 		if (action == KEY_REMOVE && !is_broadcast_ether_addr(addr))
34027b3115f2SLuciano Coelho 			return 0;
34037b3115f2SLuciano Coelho 
34047b3115f2SLuciano Coelho 		/* don't remove key if hlid was already deleted */
34057b3115f2SLuciano Coelho 		if (action == KEY_REMOVE &&
34067b3115f2SLuciano Coelho 		    wlvif->sta.hlid == WL12XX_INVALID_LINK_ID)
34077b3115f2SLuciano Coelho 			return 0;
34087b3115f2SLuciano Coelho 
34097b3115f2SLuciano Coelho 		ret = wl1271_cmd_set_sta_key(wl, wlvif, action,
34107b3115f2SLuciano Coelho 					     id, key_type, key_size,
34117b3115f2SLuciano Coelho 					     key, addr, tx_seq_32,
34127b3115f2SLuciano Coelho 					     tx_seq_16);
34137b3115f2SLuciano Coelho 		if (ret < 0)
34147b3115f2SLuciano Coelho 			return ret;
34157b3115f2SLuciano Coelho 
34167b3115f2SLuciano Coelho 	}
34177b3115f2SLuciano Coelho 
34187b3115f2SLuciano Coelho 	return 0;
34197b3115f2SLuciano Coelho }
34207b3115f2SLuciano Coelho 
wlcore_op_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)3421a1c597f2SArik Nemtsov static int wlcore_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
34227b3115f2SLuciano Coelho 			     struct ieee80211_vif *vif,
34237b3115f2SLuciano Coelho 			     struct ieee80211_sta *sta,
34247b3115f2SLuciano Coelho 			     struct ieee80211_key_conf *key_conf)
34257b3115f2SLuciano Coelho {
34267b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
3427af390f4dSEliad Peller 	int ret;
3428af390f4dSEliad Peller 	bool might_change_spare =
3429af390f4dSEliad Peller 		key_conf->cipher == WL1271_CIPHER_SUITE_GEM ||
3430af390f4dSEliad Peller 		key_conf->cipher == WLAN_CIPHER_SUITE_TKIP;
3431a1c597f2SArik Nemtsov 
3432af390f4dSEliad Peller 	if (might_change_spare) {
3433af390f4dSEliad Peller 		/*
3434af390f4dSEliad Peller 		 * stop the queues and flush to ensure the next packets are
3435af390f4dSEliad Peller 		 * in sync with FW spare block accounting
3436af390f4dSEliad Peller 		 */
3437af390f4dSEliad Peller 		wlcore_stop_queues(wl, WLCORE_QUEUE_STOP_REASON_SPARE_BLK);
3438af390f4dSEliad Peller 		wl1271_tx_flush(wl);
3439af390f4dSEliad Peller 	}
3440af390f4dSEliad Peller 
3441af390f4dSEliad Peller 	mutex_lock(&wl->mutex);
3442af390f4dSEliad Peller 
3443af390f4dSEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
3444af390f4dSEliad Peller 		ret = -EAGAIN;
3445af390f4dSEliad Peller 		goto out_wake_queues;
3446af390f4dSEliad Peller 	}
3447af390f4dSEliad Peller 
3448ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3449ab589ac2SMinghao Chi 	if (ret < 0)
3450af390f4dSEliad Peller 		goto out_wake_queues;
3451af390f4dSEliad Peller 
3452af390f4dSEliad Peller 	ret = wlcore_hw_set_key(wl, cmd, vif, sta, key_conf);
3453af390f4dSEliad Peller 
34549b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
34559b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
3456af390f4dSEliad Peller 
3457af390f4dSEliad Peller out_wake_queues:
3458af390f4dSEliad Peller 	if (might_change_spare)
3459af390f4dSEliad Peller 		wlcore_wake_queues(wl, WLCORE_QUEUE_STOP_REASON_SPARE_BLK);
3460af390f4dSEliad Peller 
3461af390f4dSEliad Peller 	mutex_unlock(&wl->mutex);
3462af390f4dSEliad Peller 
3463af390f4dSEliad Peller 	return ret;
3464a1c597f2SArik Nemtsov }
3465a1c597f2SArik Nemtsov 
wlcore_set_key(struct wl1271 * wl,enum set_key_cmd cmd,struct ieee80211_vif * vif,struct ieee80211_sta * sta,struct ieee80211_key_conf * key_conf)3466a1c597f2SArik Nemtsov int wlcore_set_key(struct wl1271 *wl, enum set_key_cmd cmd,
3467a1c597f2SArik Nemtsov 		   struct ieee80211_vif *vif,
3468a1c597f2SArik Nemtsov 		   struct ieee80211_sta *sta,
3469a1c597f2SArik Nemtsov 		   struct ieee80211_key_conf *key_conf)
3470a1c597f2SArik Nemtsov {
34717b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
34727b3115f2SLuciano Coelho 	int ret;
34737b3115f2SLuciano Coelho 	u32 tx_seq_32 = 0;
34747b3115f2SLuciano Coelho 	u16 tx_seq_16 = 0;
34757b3115f2SLuciano Coelho 	u8 key_type;
347693d5d100SArik Nemtsov 	u8 hlid;
3477cf33a772SMaital Hahn 	bool is_pairwise;
34787b3115f2SLuciano Coelho 
34797b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 set key");
34807b3115f2SLuciano Coelho 
34817b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_CRYPT, "CMD: 0x%x sta: %p", cmd, sta);
34827b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_CRYPT, "Key: algo:0x%x, id:%d, len:%d flags 0x%x",
34837b3115f2SLuciano Coelho 		     key_conf->cipher, key_conf->keyidx,
34847b3115f2SLuciano Coelho 		     key_conf->keylen, key_conf->flags);
34857b3115f2SLuciano Coelho 	wl1271_dump(DEBUG_CRYPT, "KEY: ", key_conf->key, key_conf->keylen);
34867b3115f2SLuciano Coelho 
348793d5d100SArik Nemtsov 	if (wlvif->bss_type == BSS_TYPE_AP_BSS)
348893d5d100SArik Nemtsov 		if (sta) {
348993d5d100SArik Nemtsov 			struct wl1271_station *wl_sta = (void *)sta->drv_priv;
349093d5d100SArik Nemtsov 			hlid = wl_sta->hlid;
349193d5d100SArik Nemtsov 		} else {
349293d5d100SArik Nemtsov 			hlid = wlvif->ap.bcast_hlid;
349393d5d100SArik Nemtsov 		}
349493d5d100SArik Nemtsov 	else
349593d5d100SArik Nemtsov 		hlid = wlvif->sta.hlid;
349693d5d100SArik Nemtsov 
349793d5d100SArik Nemtsov 	if (hlid != WL12XX_INVALID_LINK_ID) {
349893d5d100SArik Nemtsov 		u64 tx_seq = wl->links[hlid].total_freed_pkts;
349993d5d100SArik Nemtsov 		tx_seq_32 = WL1271_TX_SECURITY_HI32(tx_seq);
350093d5d100SArik Nemtsov 		tx_seq_16 = WL1271_TX_SECURITY_LO16(tx_seq);
350193d5d100SArik Nemtsov 	}
350293d5d100SArik Nemtsov 
35037b3115f2SLuciano Coelho 	switch (key_conf->cipher) {
35047b3115f2SLuciano Coelho 	case WLAN_CIPHER_SUITE_WEP40:
35057b3115f2SLuciano Coelho 	case WLAN_CIPHER_SUITE_WEP104:
35067b3115f2SLuciano Coelho 		key_type = KEY_WEP;
35077b3115f2SLuciano Coelho 
35087b3115f2SLuciano Coelho 		key_conf->hw_key_idx = key_conf->keyidx;
35097b3115f2SLuciano Coelho 		break;
35107b3115f2SLuciano Coelho 	case WLAN_CIPHER_SUITE_TKIP:
35117b3115f2SLuciano Coelho 		key_type = KEY_TKIP;
35127b3115f2SLuciano Coelho 		key_conf->hw_key_idx = key_conf->keyidx;
35137b3115f2SLuciano Coelho 		break;
35147b3115f2SLuciano Coelho 	case WLAN_CIPHER_SUITE_CCMP:
35157b3115f2SLuciano Coelho 		key_type = KEY_AES;
35167b3115f2SLuciano Coelho 		key_conf->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
35177b3115f2SLuciano Coelho 		break;
35187b3115f2SLuciano Coelho 	case WL1271_CIPHER_SUITE_GEM:
35197b3115f2SLuciano Coelho 		key_type = KEY_GEM;
35207b3115f2SLuciano Coelho 		break;
35217b3115f2SLuciano Coelho 	default:
35227b3115f2SLuciano Coelho 		wl1271_error("Unknown key algo 0x%x", key_conf->cipher);
35237b3115f2SLuciano Coelho 
3524af390f4dSEliad Peller 		return -EOPNOTSUPP;
35257b3115f2SLuciano Coelho 	}
35267b3115f2SLuciano Coelho 
3527cf33a772SMaital Hahn 	is_pairwise = key_conf->flags & IEEE80211_KEY_FLAG_PAIRWISE;
3528cf33a772SMaital Hahn 
35297b3115f2SLuciano Coelho 	switch (cmd) {
35307b3115f2SLuciano Coelho 	case SET_KEY:
35317b3115f2SLuciano Coelho 		ret = wl1271_set_key(wl, wlvif, KEY_ADD_OR_REPLACE,
35327b3115f2SLuciano Coelho 				 key_conf->keyidx, key_type,
35337b3115f2SLuciano Coelho 				 key_conf->keylen, key_conf->key,
3534cf33a772SMaital Hahn 				 tx_seq_32, tx_seq_16, sta, is_pairwise);
35357b3115f2SLuciano Coelho 		if (ret < 0) {
35367b3115f2SLuciano Coelho 			wl1271_error("Could not add or replace key");
3537af390f4dSEliad Peller 			return ret;
35387b3115f2SLuciano Coelho 		}
35397b3115f2SLuciano Coelho 
35407b3115f2SLuciano Coelho 		/*
35417b3115f2SLuciano Coelho 		 * reconfiguring arp response if the unicast (or common)
35427b3115f2SLuciano Coelho 		 * encryption key type was changed
35437b3115f2SLuciano Coelho 		 */
35447b3115f2SLuciano Coelho 		if (wlvif->bss_type == BSS_TYPE_STA_BSS &&
35457b3115f2SLuciano Coelho 		    (sta || key_type == KEY_WEP) &&
35467b3115f2SLuciano Coelho 		    wlvif->encryption_type != key_type) {
35477b3115f2SLuciano Coelho 			wlvif->encryption_type = key_type;
35487b3115f2SLuciano Coelho 			ret = wl1271_cmd_build_arp_rsp(wl, wlvif);
35497b3115f2SLuciano Coelho 			if (ret < 0) {
35507b3115f2SLuciano Coelho 				wl1271_warning("build arp rsp failed: %d", ret);
3551af390f4dSEliad Peller 				return ret;
35527b3115f2SLuciano Coelho 			}
35537b3115f2SLuciano Coelho 		}
35547b3115f2SLuciano Coelho 		break;
35557b3115f2SLuciano Coelho 
35567b3115f2SLuciano Coelho 	case DISABLE_KEY:
35577b3115f2SLuciano Coelho 		ret = wl1271_set_key(wl, wlvif, KEY_REMOVE,
35587b3115f2SLuciano Coelho 				     key_conf->keyidx, key_type,
35597b3115f2SLuciano Coelho 				     key_conf->keylen, key_conf->key,
3560cf33a772SMaital Hahn 				     0, 0, sta, is_pairwise);
35617b3115f2SLuciano Coelho 		if (ret < 0) {
35627b3115f2SLuciano Coelho 			wl1271_error("Could not remove key");
3563af390f4dSEliad Peller 			return ret;
35647b3115f2SLuciano Coelho 		}
35657b3115f2SLuciano Coelho 		break;
35667b3115f2SLuciano Coelho 
35677b3115f2SLuciano Coelho 	default:
35687b3115f2SLuciano Coelho 		wl1271_error("Unsupported key cmd 0x%x", cmd);
3569af390f4dSEliad Peller 		return -EOPNOTSUPP;
35707b3115f2SLuciano Coelho 	}
35717b3115f2SLuciano Coelho 
35727b3115f2SLuciano Coelho 	return ret;
35737b3115f2SLuciano Coelho }
3574a1c597f2SArik Nemtsov EXPORT_SYMBOL_GPL(wlcore_set_key);
35757b3115f2SLuciano Coelho 
wl1271_op_set_default_key_idx(struct ieee80211_hw * hw,struct ieee80211_vif * vif,int key_idx)3576ba1e6eb9SYoni Divinsky static void wl1271_op_set_default_key_idx(struct ieee80211_hw *hw,
3577ba1e6eb9SYoni Divinsky 					  struct ieee80211_vif *vif,
3578ba1e6eb9SYoni Divinsky 					  int key_idx)
3579ba1e6eb9SYoni Divinsky {
3580ba1e6eb9SYoni Divinsky 	struct wl1271 *wl = hw->priv;
3581ba1e6eb9SYoni Divinsky 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
3582ba1e6eb9SYoni Divinsky 	int ret;
3583ba1e6eb9SYoni Divinsky 
3584ba1e6eb9SYoni Divinsky 	wl1271_debug(DEBUG_MAC80211, "mac80211 set default key idx %d",
3585ba1e6eb9SYoni Divinsky 		     key_idx);
3586ba1e6eb9SYoni Divinsky 
3587bf4e5f1aSEliad Peller 	/* we don't handle unsetting of default key */
3588bf4e5f1aSEliad Peller 	if (key_idx == -1)
3589bf4e5f1aSEliad Peller 		return;
3590bf4e5f1aSEliad Peller 
3591ba1e6eb9SYoni Divinsky 	mutex_lock(&wl->mutex);
3592ba1e6eb9SYoni Divinsky 
3593ba1e6eb9SYoni Divinsky 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
3594ba1e6eb9SYoni Divinsky 		ret = -EAGAIN;
3595ba1e6eb9SYoni Divinsky 		goto out_unlock;
3596ba1e6eb9SYoni Divinsky 	}
3597ba1e6eb9SYoni Divinsky 
3598ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3599ab589ac2SMinghao Chi 	if (ret < 0)
3600ba1e6eb9SYoni Divinsky 		goto out_unlock;
3601ba1e6eb9SYoni Divinsky 
3602ba1e6eb9SYoni Divinsky 	wlvif->default_key = key_idx;
3603ba1e6eb9SYoni Divinsky 
3604ba1e6eb9SYoni Divinsky 	/* the default WEP key needs to be configured at least once */
3605ba1e6eb9SYoni Divinsky 	if (wlvif->encryption_type == KEY_WEP) {
3606ba1e6eb9SYoni Divinsky 		ret = wl12xx_cmd_set_default_wep_key(wl,
3607ba1e6eb9SYoni Divinsky 				key_idx,
3608ba1e6eb9SYoni Divinsky 				wlvif->sta.hlid);
3609ba1e6eb9SYoni Divinsky 		if (ret < 0)
3610ba1e6eb9SYoni Divinsky 			goto out_sleep;
3611ba1e6eb9SYoni Divinsky 	}
3612ba1e6eb9SYoni Divinsky 
3613ba1e6eb9SYoni Divinsky out_sleep:
36149b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
36159b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
3616ba1e6eb9SYoni Divinsky 
3617ba1e6eb9SYoni Divinsky out_unlock:
3618ba1e6eb9SYoni Divinsky 	mutex_unlock(&wl->mutex);
3619ba1e6eb9SYoni Divinsky }
3620ba1e6eb9SYoni Divinsky 
wlcore_regdomain_config(struct wl1271 * wl)36216b70e7ebSVictor Goldenshtein void wlcore_regdomain_config(struct wl1271 *wl)
36226b70e7ebSVictor Goldenshtein {
36236b70e7ebSVictor Goldenshtein 	int ret;
36246b70e7ebSVictor Goldenshtein 
36256b70e7ebSVictor Goldenshtein 	if (!(wl->quirks & WLCORE_QUIRK_REGDOMAIN_CONF))
36266b70e7ebSVictor Goldenshtein 		return;
36276b70e7ebSVictor Goldenshtein 
36286b70e7ebSVictor Goldenshtein 	mutex_lock(&wl->mutex);
362975592be5SArik Nemtsov 
363075592be5SArik Nemtsov 	if (unlikely(wl->state != WLCORE_STATE_ON))
363175592be5SArik Nemtsov 		goto out;
363275592be5SArik Nemtsov 
3633ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3634ab589ac2SMinghao Chi 	if (ret < 0)
36356b70e7ebSVictor Goldenshtein 		goto out;
36366b70e7ebSVictor Goldenshtein 
36376b70e7ebSVictor Goldenshtein 	ret = wlcore_cmd_regdomain_config_locked(wl);
36386b70e7ebSVictor Goldenshtein 	if (ret < 0) {
36396b70e7ebSVictor Goldenshtein 		wl12xx_queue_recovery_work(wl);
36406b70e7ebSVictor Goldenshtein 		goto out;
36416b70e7ebSVictor Goldenshtein 	}
36426b70e7ebSVictor Goldenshtein 
36439b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
36449b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
36456b70e7ebSVictor Goldenshtein out:
36466b70e7ebSVictor Goldenshtein 	mutex_unlock(&wl->mutex);
36476b70e7ebSVictor Goldenshtein }
36486b70e7ebSVictor Goldenshtein 
wl1271_op_hw_scan(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_scan_request * hw_req)36497b3115f2SLuciano Coelho static int wl1271_op_hw_scan(struct ieee80211_hw *hw,
36507b3115f2SLuciano Coelho 			     struct ieee80211_vif *vif,
3651c56ef672SDavid Spinadel 			     struct ieee80211_scan_request *hw_req)
36527b3115f2SLuciano Coelho {
3653c56ef672SDavid Spinadel 	struct cfg80211_scan_request *req = &hw_req->req;
36547b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
36557b3115f2SLuciano Coelho 	int ret;
36567b3115f2SLuciano Coelho 	u8 *ssid = NULL;
36577b3115f2SLuciano Coelho 	size_t len = 0;
36587b3115f2SLuciano Coelho 
36597b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 hw scan");
36607b3115f2SLuciano Coelho 
36617b3115f2SLuciano Coelho 	if (req->n_ssids) {
36627b3115f2SLuciano Coelho 		ssid = req->ssids[0].ssid;
36637b3115f2SLuciano Coelho 		len = req->ssids[0].ssid_len;
36647b3115f2SLuciano Coelho 	}
36657b3115f2SLuciano Coelho 
36667b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
36677b3115f2SLuciano Coelho 
36684cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
36697b3115f2SLuciano Coelho 		/*
36707b3115f2SLuciano Coelho 		 * We cannot return -EBUSY here because cfg80211 will expect
36717b3115f2SLuciano Coelho 		 * a call to ieee80211_scan_completed if we do - in this case
36727b3115f2SLuciano Coelho 		 * there won't be any call.
36737b3115f2SLuciano Coelho 		 */
36747b3115f2SLuciano Coelho 		ret = -EAGAIN;
36757b3115f2SLuciano Coelho 		goto out;
36767b3115f2SLuciano Coelho 	}
36777b3115f2SLuciano Coelho 
3678ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3679ab589ac2SMinghao Chi 	if (ret < 0)
36807b3115f2SLuciano Coelho 		goto out;
36817b3115f2SLuciano Coelho 
36827b3115f2SLuciano Coelho 	/* fail if there is any role in ROC */
36837b3115f2SLuciano Coelho 	if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) {
36847b3115f2SLuciano Coelho 		/* don't allow scanning right now */
36857b3115f2SLuciano Coelho 		ret = -EBUSY;
36867b3115f2SLuciano Coelho 		goto out_sleep;
36877b3115f2SLuciano Coelho 	}
36887b3115f2SLuciano Coelho 
368978e28062SEliad Peller 	ret = wlcore_scan(hw->priv, vif, ssid, len, req);
36907b3115f2SLuciano Coelho out_sleep:
36919b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
36929b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
36937b3115f2SLuciano Coelho out:
36947b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
36957b3115f2SLuciano Coelho 
36967b3115f2SLuciano Coelho 	return ret;
36977b3115f2SLuciano Coelho }
36987b3115f2SLuciano Coelho 
wl1271_op_cancel_hw_scan(struct ieee80211_hw * hw,struct ieee80211_vif * vif)36997b3115f2SLuciano Coelho static void wl1271_op_cancel_hw_scan(struct ieee80211_hw *hw,
37007b3115f2SLuciano Coelho 				     struct ieee80211_vif *vif)
37017b3115f2SLuciano Coelho {
37027b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
370378e28062SEliad Peller 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
37047947d3e0SAvraham Stern 	struct cfg80211_scan_info info = {
37057947d3e0SAvraham Stern 		.aborted = true,
37067947d3e0SAvraham Stern 	};
37077b3115f2SLuciano Coelho 	int ret;
37087b3115f2SLuciano Coelho 
37097b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 cancel hw scan");
37107b3115f2SLuciano Coelho 
37117b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
37127b3115f2SLuciano Coelho 
37134cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
37147b3115f2SLuciano Coelho 		goto out;
37157b3115f2SLuciano Coelho 
37167b3115f2SLuciano Coelho 	if (wl->scan.state == WL1271_SCAN_STATE_IDLE)
37177b3115f2SLuciano Coelho 		goto out;
37187b3115f2SLuciano Coelho 
3719ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3720ab589ac2SMinghao Chi 	if (ret < 0)
37217b3115f2SLuciano Coelho 		goto out;
37227b3115f2SLuciano Coelho 
37237b3115f2SLuciano Coelho 	if (wl->scan.state != WL1271_SCAN_STATE_DONE) {
372478e28062SEliad Peller 		ret = wl->ops->scan_stop(wl, wlvif);
37257b3115f2SLuciano Coelho 		if (ret < 0)
37267b3115f2SLuciano Coelho 			goto out_sleep;
37277b3115f2SLuciano Coelho 	}
37287b3115f2SLuciano Coelho 
37297b3115f2SLuciano Coelho 	/*
37307b3115f2SLuciano Coelho 	 * Rearm the tx watchdog just before idling scan. This
37317b3115f2SLuciano Coelho 	 * prevents just-finished scans from triggering the watchdog
37327b3115f2SLuciano Coelho 	 */
37337b3115f2SLuciano Coelho 	wl12xx_rearm_tx_watchdog_locked(wl);
37347b3115f2SLuciano Coelho 
37357b3115f2SLuciano Coelho 	wl->scan.state = WL1271_SCAN_STATE_IDLE;
37367b3115f2SLuciano Coelho 	memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch));
3737c50a2825SEliad Peller 	wl->scan_wlvif = NULL;
37387b3115f2SLuciano Coelho 	wl->scan.req = NULL;
37397947d3e0SAvraham Stern 	ieee80211_scan_completed(wl->hw, &info);
37407b3115f2SLuciano Coelho 
37417b3115f2SLuciano Coelho out_sleep:
37429b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
37439b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
37447b3115f2SLuciano Coelho out:
37457b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
37467b3115f2SLuciano Coelho 
37477b3115f2SLuciano Coelho 	cancel_delayed_work_sync(&wl->scan_complete_work);
37487b3115f2SLuciano Coelho }
37497b3115f2SLuciano Coelho 
wl1271_op_sched_scan_start(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct cfg80211_sched_scan_request * req,struct ieee80211_scan_ies * ies)37507b3115f2SLuciano Coelho static int wl1271_op_sched_scan_start(struct ieee80211_hw *hw,
37517b3115f2SLuciano Coelho 				      struct ieee80211_vif *vif,
37527b3115f2SLuciano Coelho 				      struct cfg80211_sched_scan_request *req,
3753633e2713SDavid Spinadel 				      struct ieee80211_scan_ies *ies)
37547b3115f2SLuciano Coelho {
37557b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
37567b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
37577b3115f2SLuciano Coelho 	int ret;
37587b3115f2SLuciano Coelho 
37597b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_start");
37607b3115f2SLuciano Coelho 
37617b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
37627b3115f2SLuciano Coelho 
37634cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
37647b3115f2SLuciano Coelho 		ret = -EAGAIN;
37657b3115f2SLuciano Coelho 		goto out;
37667b3115f2SLuciano Coelho 	}
37677b3115f2SLuciano Coelho 
3768ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3769ab589ac2SMinghao Chi 	if (ret < 0)
37707b3115f2SLuciano Coelho 		goto out;
37717b3115f2SLuciano Coelho 
377278e28062SEliad Peller 	ret = wl->ops->sched_scan_start(wl, wlvif, req, ies);
37737b3115f2SLuciano Coelho 	if (ret < 0)
37747b3115f2SLuciano Coelho 		goto out_sleep;
37757b3115f2SLuciano Coelho 
377610199756SEliad Peller 	wl->sched_vif = wlvif;
37777b3115f2SLuciano Coelho 
37787b3115f2SLuciano Coelho out_sleep:
37799b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
37809b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
37817b3115f2SLuciano Coelho out:
37827b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
37837b3115f2SLuciano Coelho 	return ret;
37847b3115f2SLuciano Coelho }
37857b3115f2SLuciano Coelho 
wl1271_op_sched_scan_stop(struct ieee80211_hw * hw,struct ieee80211_vif * vif)378637e3308cSJohannes Berg static int wl1271_op_sched_scan_stop(struct ieee80211_hw *hw,
37877b3115f2SLuciano Coelho 				     struct ieee80211_vif *vif)
37887b3115f2SLuciano Coelho {
37897b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
379078f85f50SYoni Divinsky 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
37917b3115f2SLuciano Coelho 	int ret;
37927b3115f2SLuciano Coelho 
37937b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_stop");
37947b3115f2SLuciano Coelho 
37957b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
37967b3115f2SLuciano Coelho 
37974cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
37987b3115f2SLuciano Coelho 		goto out;
37997b3115f2SLuciano Coelho 
3800ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3801ab589ac2SMinghao Chi 	if (ret < 0)
38027b3115f2SLuciano Coelho 		goto out;
38037b3115f2SLuciano Coelho 
380478e28062SEliad Peller 	wl->ops->sched_scan_stop(wl, wlvif);
38057b3115f2SLuciano Coelho 
38069b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
38079b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
38087b3115f2SLuciano Coelho out:
38097b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
381037e3308cSJohannes Berg 
381137e3308cSJohannes Berg 	return 0;
38127b3115f2SLuciano Coelho }
38137b3115f2SLuciano Coelho 
wl1271_op_set_frag_threshold(struct ieee80211_hw * hw,u32 value)38147b3115f2SLuciano Coelho static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value)
38157b3115f2SLuciano Coelho {
38167b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
38177b3115f2SLuciano Coelho 	int ret = 0;
38187b3115f2SLuciano Coelho 
38197b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
38207b3115f2SLuciano Coelho 
38214cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
38227b3115f2SLuciano Coelho 		ret = -EAGAIN;
38237b3115f2SLuciano Coelho 		goto out;
38247b3115f2SLuciano Coelho 	}
38257b3115f2SLuciano Coelho 
3826ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3827ab589ac2SMinghao Chi 	if (ret < 0)
38287b3115f2SLuciano Coelho 		goto out;
38297b3115f2SLuciano Coelho 
38307b3115f2SLuciano Coelho 	ret = wl1271_acx_frag_threshold(wl, value);
38317b3115f2SLuciano Coelho 	if (ret < 0)
38327b3115f2SLuciano Coelho 		wl1271_warning("wl1271_op_set_frag_threshold failed: %d", ret);
38337b3115f2SLuciano Coelho 
38349b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
38359b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
38367b3115f2SLuciano Coelho 
38377b3115f2SLuciano Coelho out:
38387b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
38397b3115f2SLuciano Coelho 
38407b3115f2SLuciano Coelho 	return ret;
38417b3115f2SLuciano Coelho }
38427b3115f2SLuciano Coelho 
wl1271_op_set_rts_threshold(struct ieee80211_hw * hw,u32 value)38437b3115f2SLuciano Coelho static int wl1271_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
38447b3115f2SLuciano Coelho {
38457b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
38467b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif;
38477b3115f2SLuciano Coelho 	int ret = 0;
38487b3115f2SLuciano Coelho 
38497b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
38507b3115f2SLuciano Coelho 
38514cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
38527b3115f2SLuciano Coelho 		ret = -EAGAIN;
38537b3115f2SLuciano Coelho 		goto out;
38547b3115f2SLuciano Coelho 	}
38557b3115f2SLuciano Coelho 
3856ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
3857ab589ac2SMinghao Chi 	if (ret < 0)
38587b3115f2SLuciano Coelho 		goto out;
38597b3115f2SLuciano Coelho 
38607b3115f2SLuciano Coelho 	wl12xx_for_each_wlvif(wl, wlvif) {
38617b3115f2SLuciano Coelho 		ret = wl1271_acx_rts_threshold(wl, wlvif, value);
38627b3115f2SLuciano Coelho 		if (ret < 0)
38637b3115f2SLuciano Coelho 			wl1271_warning("set rts threshold failed: %d", ret);
38647b3115f2SLuciano Coelho 	}
38659b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
38669b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
38677b3115f2SLuciano Coelho 
38687b3115f2SLuciano Coelho out:
38697b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
38707b3115f2SLuciano Coelho 
38717b3115f2SLuciano Coelho 	return ret;
38727b3115f2SLuciano Coelho }
38737b3115f2SLuciano Coelho 
wl12xx_remove_ie(struct sk_buff * skb,u8 eid,int ieoffset)38747b3115f2SLuciano Coelho static void wl12xx_remove_ie(struct sk_buff *skb, u8 eid, int ieoffset)
38757b3115f2SLuciano Coelho {
38767b3115f2SLuciano Coelho 	int len;
38777b3115f2SLuciano Coelho 	const u8 *next, *end = skb->data + skb->len;
38787b3115f2SLuciano Coelho 	u8 *ie = (u8 *)cfg80211_find_ie(eid, skb->data + ieoffset,
38797b3115f2SLuciano Coelho 					skb->len - ieoffset);
38807b3115f2SLuciano Coelho 	if (!ie)
38817b3115f2SLuciano Coelho 		return;
38827b3115f2SLuciano Coelho 	len = ie[1] + 2;
38837b3115f2SLuciano Coelho 	next = ie + len;
38847b3115f2SLuciano Coelho 	memmove(ie, next, end - next);
38857b3115f2SLuciano Coelho 	skb_trim(skb, skb->len - len);
38867b3115f2SLuciano Coelho }
38877b3115f2SLuciano Coelho 
wl12xx_remove_vendor_ie(struct sk_buff * skb,unsigned int oui,u8 oui_type,int ieoffset)38887b3115f2SLuciano Coelho static void wl12xx_remove_vendor_ie(struct sk_buff *skb,
38897b3115f2SLuciano Coelho 					    unsigned int oui, u8 oui_type,
38907b3115f2SLuciano Coelho 					    int ieoffset)
38917b3115f2SLuciano Coelho {
38927b3115f2SLuciano Coelho 	int len;
38937b3115f2SLuciano Coelho 	const u8 *next, *end = skb->data + skb->len;
38947b3115f2SLuciano Coelho 	u8 *ie = (u8 *)cfg80211_find_vendor_ie(oui, oui_type,
38957b3115f2SLuciano Coelho 					       skb->data + ieoffset,
38967b3115f2SLuciano Coelho 					       skb->len - ieoffset);
38977b3115f2SLuciano Coelho 	if (!ie)
38987b3115f2SLuciano Coelho 		return;
38997b3115f2SLuciano Coelho 	len = ie[1] + 2;
39007b3115f2SLuciano Coelho 	next = ie + len;
39017b3115f2SLuciano Coelho 	memmove(ie, next, end - next);
39027b3115f2SLuciano Coelho 	skb_trim(skb, skb->len - len);
39037b3115f2SLuciano Coelho }
39047b3115f2SLuciano Coelho 
wl1271_ap_set_probe_resp_tmpl(struct wl1271 * wl,u32 rates,struct ieee80211_vif * vif)39057b3115f2SLuciano Coelho static int wl1271_ap_set_probe_resp_tmpl(struct wl1271 *wl, u32 rates,
39067b3115f2SLuciano Coelho 					 struct ieee80211_vif *vif)
39077b3115f2SLuciano Coelho {
39087b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
39097b3115f2SLuciano Coelho 	struct sk_buff *skb;
39107b3115f2SLuciano Coelho 	int ret;
39117b3115f2SLuciano Coelho 
39127b3115f2SLuciano Coelho 	skb = ieee80211_proberesp_get(wl->hw, vif);
39137b3115f2SLuciano Coelho 	if (!skb)
39147b3115f2SLuciano Coelho 		return -EOPNOTSUPP;
39157b3115f2SLuciano Coelho 
39167b3115f2SLuciano Coelho 	ret = wl1271_cmd_template_set(wl, wlvif->role_id,
39177b3115f2SLuciano Coelho 				      CMD_TEMPL_AP_PROBE_RESPONSE,
39187b3115f2SLuciano Coelho 				      skb->data,
39197b3115f2SLuciano Coelho 				      skb->len, 0,
39207b3115f2SLuciano Coelho 				      rates);
39217b3115f2SLuciano Coelho 	dev_kfree_skb(skb);
392262c2e579SLuciano Coelho 
392362c2e579SLuciano Coelho 	if (ret < 0)
392462c2e579SLuciano Coelho 		goto out;
392562c2e579SLuciano Coelho 
392662c2e579SLuciano Coelho 	wl1271_debug(DEBUG_AP, "probe response updated");
392762c2e579SLuciano Coelho 	set_bit(WLVIF_FLAG_AP_PROBE_RESP_SET, &wlvif->flags);
392862c2e579SLuciano Coelho 
392962c2e579SLuciano Coelho out:
39307b3115f2SLuciano Coelho 	return ret;
39317b3115f2SLuciano Coelho }
39327b3115f2SLuciano Coelho 
wl1271_ap_set_probe_resp_tmpl_legacy(struct wl1271 * wl,struct ieee80211_vif * vif,u8 * probe_rsp_data,size_t probe_rsp_len,u32 rates)39337b3115f2SLuciano Coelho static int wl1271_ap_set_probe_resp_tmpl_legacy(struct wl1271 *wl,
39347b3115f2SLuciano Coelho 					     struct ieee80211_vif *vif,
39357b3115f2SLuciano Coelho 					     u8 *probe_rsp_data,
39367b3115f2SLuciano Coelho 					     size_t probe_rsp_len,
39377b3115f2SLuciano Coelho 					     u32 rates)
39387b3115f2SLuciano Coelho {
39397b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
39407b3115f2SLuciano Coelho 	u8 probe_rsp_templ[WL1271_CMD_TEMPL_MAX_SIZE];
39417b3115f2SLuciano Coelho 	int ssid_ie_offset, ie_offset, templ_len;
39427b3115f2SLuciano Coelho 	const u8 *ptr;
39437b3115f2SLuciano Coelho 
39447b3115f2SLuciano Coelho 	/* no need to change probe response if the SSID is set correctly */
39457b3115f2SLuciano Coelho 	if (wlvif->ssid_len > 0)
39467b3115f2SLuciano Coelho 		return wl1271_cmd_template_set(wl, wlvif->role_id,
39477b3115f2SLuciano Coelho 					       CMD_TEMPL_AP_PROBE_RESPONSE,
39487b3115f2SLuciano Coelho 					       probe_rsp_data,
39497b3115f2SLuciano Coelho 					       probe_rsp_len, 0,
39507b3115f2SLuciano Coelho 					       rates);
39517b3115f2SLuciano Coelho 
3952f276e20bSJohannes Berg 	if (probe_rsp_len + vif->cfg.ssid_len > WL1271_CMD_TEMPL_MAX_SIZE) {
39537b3115f2SLuciano Coelho 		wl1271_error("probe_rsp template too big");
39547b3115f2SLuciano Coelho 		return -EINVAL;
39557b3115f2SLuciano Coelho 	}
39567b3115f2SLuciano Coelho 
39577b3115f2SLuciano Coelho 	/* start searching from IE offset */
39587b3115f2SLuciano Coelho 	ie_offset = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
39597b3115f2SLuciano Coelho 
39607b3115f2SLuciano Coelho 	ptr = cfg80211_find_ie(WLAN_EID_SSID, probe_rsp_data + ie_offset,
39617b3115f2SLuciano Coelho 			       probe_rsp_len - ie_offset);
39627b3115f2SLuciano Coelho 	if (!ptr) {
39637b3115f2SLuciano Coelho 		wl1271_error("No SSID in beacon!");
39647b3115f2SLuciano Coelho 		return -EINVAL;
39657b3115f2SLuciano Coelho 	}
39667b3115f2SLuciano Coelho 
39677b3115f2SLuciano Coelho 	ssid_ie_offset = ptr - probe_rsp_data;
39687b3115f2SLuciano Coelho 	ptr += (ptr[1] + 2);
39697b3115f2SLuciano Coelho 
39707b3115f2SLuciano Coelho 	memcpy(probe_rsp_templ, probe_rsp_data, ssid_ie_offset);
39717b3115f2SLuciano Coelho 
39727b3115f2SLuciano Coelho 	/* insert SSID from bss_conf */
39737b3115f2SLuciano Coelho 	probe_rsp_templ[ssid_ie_offset] = WLAN_EID_SSID;
3974f276e20bSJohannes Berg 	probe_rsp_templ[ssid_ie_offset + 1] = vif->cfg.ssid_len;
39757b3115f2SLuciano Coelho 	memcpy(probe_rsp_templ + ssid_ie_offset + 2,
3976f276e20bSJohannes Berg 	       vif->cfg.ssid, vif->cfg.ssid_len);
3977f276e20bSJohannes Berg 	templ_len = ssid_ie_offset + 2 + vif->cfg.ssid_len;
39787b3115f2SLuciano Coelho 
3979f276e20bSJohannes Berg 	memcpy(probe_rsp_templ + ssid_ie_offset + 2 + vif->cfg.ssid_len,
39807b3115f2SLuciano Coelho 	       ptr, probe_rsp_len - (ptr - probe_rsp_data));
39817b3115f2SLuciano Coelho 	templ_len += probe_rsp_len - (ptr - probe_rsp_data);
39827b3115f2SLuciano Coelho 
39837b3115f2SLuciano Coelho 	return wl1271_cmd_template_set(wl, wlvif->role_id,
39847b3115f2SLuciano Coelho 				       CMD_TEMPL_AP_PROBE_RESPONSE,
39857b3115f2SLuciano Coelho 				       probe_rsp_templ,
39867b3115f2SLuciano Coelho 				       templ_len, 0,
39877b3115f2SLuciano Coelho 				       rates);
39887b3115f2SLuciano Coelho }
39897b3115f2SLuciano Coelho 
wl1271_bss_erp_info_changed(struct wl1271 * wl,struct ieee80211_vif * vif,struct ieee80211_bss_conf * bss_conf,u32 changed)39907b3115f2SLuciano Coelho static int wl1271_bss_erp_info_changed(struct wl1271 *wl,
39917b3115f2SLuciano Coelho 				       struct ieee80211_vif *vif,
39927b3115f2SLuciano Coelho 				       struct ieee80211_bss_conf *bss_conf,
39937b3115f2SLuciano Coelho 				       u32 changed)
39947b3115f2SLuciano Coelho {
39957b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
39967b3115f2SLuciano Coelho 	int ret = 0;
39977b3115f2SLuciano Coelho 
39987b3115f2SLuciano Coelho 	if (changed & BSS_CHANGED_ERP_SLOT) {
39997b3115f2SLuciano Coelho 		if (bss_conf->use_short_slot)
40007b3115f2SLuciano Coelho 			ret = wl1271_acx_slot(wl, wlvif, SLOT_TIME_SHORT);
40017b3115f2SLuciano Coelho 		else
40027b3115f2SLuciano Coelho 			ret = wl1271_acx_slot(wl, wlvif, SLOT_TIME_LONG);
40037b3115f2SLuciano Coelho 		if (ret < 0) {
40047b3115f2SLuciano Coelho 			wl1271_warning("Set slot time failed %d", ret);
40057b3115f2SLuciano Coelho 			goto out;
40067b3115f2SLuciano Coelho 		}
40077b3115f2SLuciano Coelho 	}
40087b3115f2SLuciano Coelho 
40097b3115f2SLuciano Coelho 	if (changed & BSS_CHANGED_ERP_PREAMBLE) {
40107b3115f2SLuciano Coelho 		if (bss_conf->use_short_preamble)
40117b3115f2SLuciano Coelho 			wl1271_acx_set_preamble(wl, wlvif, ACX_PREAMBLE_SHORT);
40127b3115f2SLuciano Coelho 		else
40137b3115f2SLuciano Coelho 			wl1271_acx_set_preamble(wl, wlvif, ACX_PREAMBLE_LONG);
40147b3115f2SLuciano Coelho 	}
40157b3115f2SLuciano Coelho 
40167b3115f2SLuciano Coelho 	if (changed & BSS_CHANGED_ERP_CTS_PROT) {
40177b3115f2SLuciano Coelho 		if (bss_conf->use_cts_prot)
40187b3115f2SLuciano Coelho 			ret = wl1271_acx_cts_protect(wl, wlvif,
40197b3115f2SLuciano Coelho 						     CTSPROTECT_ENABLE);
40207b3115f2SLuciano Coelho 		else
40217b3115f2SLuciano Coelho 			ret = wl1271_acx_cts_protect(wl, wlvif,
40227b3115f2SLuciano Coelho 						     CTSPROTECT_DISABLE);
40237b3115f2SLuciano Coelho 		if (ret < 0) {
40247b3115f2SLuciano Coelho 			wl1271_warning("Set ctsprotect failed %d", ret);
40257b3115f2SLuciano Coelho 			goto out;
40267b3115f2SLuciano Coelho 		}
40277b3115f2SLuciano Coelho 	}
40287b3115f2SLuciano Coelho 
40297b3115f2SLuciano Coelho out:
40307b3115f2SLuciano Coelho 	return ret;
40317b3115f2SLuciano Coelho }
40327b3115f2SLuciano Coelho 
wlcore_set_beacon_template(struct wl1271 * wl,struct ieee80211_vif * vif,bool is_ap)403362c2e579SLuciano Coelho static int wlcore_set_beacon_template(struct wl1271 *wl,
40347b3115f2SLuciano Coelho 				      struct ieee80211_vif *vif,
403562c2e579SLuciano Coelho 				      bool is_ap)
40367b3115f2SLuciano Coelho {
40377b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
40387b3115f2SLuciano Coelho 	struct ieee80211_hdr *hdr;
40397b3115f2SLuciano Coelho 	u32 min_rate;
404062c2e579SLuciano Coelho 	int ret;
40418f6ac537SLuciano Coelho 	int ieoffset = offsetof(struct ieee80211_mgmt, u.beacon.variable);
40426e8912a5SShaul Triebitz 	struct sk_buff *beacon = ieee80211_beacon_get(wl->hw, vif, 0);
40437b3115f2SLuciano Coelho 	u16 tmpl_id;
40447b3115f2SLuciano Coelho 
40457b3115f2SLuciano Coelho 	if (!beacon) {
40467b3115f2SLuciano Coelho 		ret = -EINVAL;
40477b3115f2SLuciano Coelho 		goto out;
40487b3115f2SLuciano Coelho 	}
40497b3115f2SLuciano Coelho 
40507b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MASTER, "beacon updated");
40517b3115f2SLuciano Coelho 
40523230f35eSEliad Peller 	ret = wl1271_ssid_set(wlvif, beacon, ieoffset);
40537b3115f2SLuciano Coelho 	if (ret < 0) {
40547b3115f2SLuciano Coelho 		dev_kfree_skb(beacon);
40557b3115f2SLuciano Coelho 		goto out;
40567b3115f2SLuciano Coelho 	}
40577b3115f2SLuciano Coelho 	min_rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set);
40587b3115f2SLuciano Coelho 	tmpl_id = is_ap ? CMD_TEMPL_AP_BEACON :
40597b3115f2SLuciano Coelho 		CMD_TEMPL_BEACON;
40607b3115f2SLuciano Coelho 	ret = wl1271_cmd_template_set(wl, wlvif->role_id, tmpl_id,
40617b3115f2SLuciano Coelho 				      beacon->data,
40627b3115f2SLuciano Coelho 				      beacon->len, 0,
40637b3115f2SLuciano Coelho 				      min_rate);
40647b3115f2SLuciano Coelho 	if (ret < 0) {
40657b3115f2SLuciano Coelho 		dev_kfree_skb(beacon);
40667b3115f2SLuciano Coelho 		goto out;
40677b3115f2SLuciano Coelho 	}
40687b3115f2SLuciano Coelho 
4069d50529c0SEliad Peller 	wlvif->wmm_enabled =
4070d50529c0SEliad Peller 		cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT,
4071d50529c0SEliad Peller 					WLAN_OUI_TYPE_MICROSOFT_WMM,
4072d50529c0SEliad Peller 					beacon->data + ieoffset,
4073d50529c0SEliad Peller 					beacon->len - ieoffset);
4074d50529c0SEliad Peller 
40757b3115f2SLuciano Coelho 	/*
40767b3115f2SLuciano Coelho 	 * In case we already have a probe-resp beacon set explicitly
40777b3115f2SLuciano Coelho 	 * by usermode, don't use the beacon data.
40787b3115f2SLuciano Coelho 	 */
40797b3115f2SLuciano Coelho 	if (test_bit(WLVIF_FLAG_AP_PROBE_RESP_SET, &wlvif->flags))
40807b3115f2SLuciano Coelho 		goto end_bcn;
40817b3115f2SLuciano Coelho 
40827b3115f2SLuciano Coelho 	/* remove TIM ie from probe response */
40837b3115f2SLuciano Coelho 	wl12xx_remove_ie(beacon, WLAN_EID_TIM, ieoffset);
40847b3115f2SLuciano Coelho 
40857b3115f2SLuciano Coelho 	/*
40867b3115f2SLuciano Coelho 	 * remove p2p ie from probe response.
40877b3115f2SLuciano Coelho 	 * the fw reponds to probe requests that don't include
40887b3115f2SLuciano Coelho 	 * the p2p ie. probe requests with p2p ie will be passed,
40897b3115f2SLuciano Coelho 	 * and will be responded by the supplicant (the spec
40907b3115f2SLuciano Coelho 	 * forbids including the p2p ie when responding to probe
40917b3115f2SLuciano Coelho 	 * requests that didn't include it).
40927b3115f2SLuciano Coelho 	 */
40937b3115f2SLuciano Coelho 	wl12xx_remove_vendor_ie(beacon, WLAN_OUI_WFA,
40947b3115f2SLuciano Coelho 				WLAN_OUI_TYPE_WFA_P2P, ieoffset);
40957b3115f2SLuciano Coelho 
40967b3115f2SLuciano Coelho 	hdr = (struct ieee80211_hdr *) beacon->data;
40977b3115f2SLuciano Coelho 	hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
40987b3115f2SLuciano Coelho 					 IEEE80211_STYPE_PROBE_RESP);
40997b3115f2SLuciano Coelho 	if (is_ap)
41007b3115f2SLuciano Coelho 		ret = wl1271_ap_set_probe_resp_tmpl_legacy(wl, vif,
41017b3115f2SLuciano Coelho 							   beacon->data,
41027b3115f2SLuciano Coelho 							   beacon->len,
41037b3115f2SLuciano Coelho 							   min_rate);
41047b3115f2SLuciano Coelho 	else
41057b3115f2SLuciano Coelho 		ret = wl1271_cmd_template_set(wl, wlvif->role_id,
41067b3115f2SLuciano Coelho 					      CMD_TEMPL_PROBE_RESPONSE,
41077b3115f2SLuciano Coelho 					      beacon->data,
41087b3115f2SLuciano Coelho 					      beacon->len, 0,
41097b3115f2SLuciano Coelho 					      min_rate);
41107b3115f2SLuciano Coelho end_bcn:
41117b3115f2SLuciano Coelho 	dev_kfree_skb(beacon);
41127b3115f2SLuciano Coelho 	if (ret < 0)
41137b3115f2SLuciano Coelho 		goto out;
411462c2e579SLuciano Coelho 
411562c2e579SLuciano Coelho out:
411662c2e579SLuciano Coelho 	return ret;
411762c2e579SLuciano Coelho }
411862c2e579SLuciano Coelho 
wl1271_bss_beacon_info_changed(struct wl1271 * wl,struct ieee80211_vif * vif,struct ieee80211_bss_conf * bss_conf,u32 changed)411962c2e579SLuciano Coelho static int wl1271_bss_beacon_info_changed(struct wl1271 *wl,
412062c2e579SLuciano Coelho 					  struct ieee80211_vif *vif,
412162c2e579SLuciano Coelho 					  struct ieee80211_bss_conf *bss_conf,
412262c2e579SLuciano Coelho 					  u32 changed)
412362c2e579SLuciano Coelho {
412462c2e579SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
412562c2e579SLuciano Coelho 	bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS);
412662c2e579SLuciano Coelho 	int ret = 0;
412762c2e579SLuciano Coelho 
412848af2eb0SLuciano Coelho 	if (changed & BSS_CHANGED_BEACON_INT) {
412962c2e579SLuciano Coelho 		wl1271_debug(DEBUG_MASTER, "beacon interval updated: %d",
413062c2e579SLuciano Coelho 			bss_conf->beacon_int);
413162c2e579SLuciano Coelho 
413262c2e579SLuciano Coelho 		wlvif->beacon_int = bss_conf->beacon_int;
413362c2e579SLuciano Coelho 	}
413462c2e579SLuciano Coelho 
413562c2e579SLuciano Coelho 	if ((changed & BSS_CHANGED_AP_PROBE_RESP) && is_ap) {
413662c2e579SLuciano Coelho 		u32 rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set);
413762c2e579SLuciano Coelho 
413862c2e579SLuciano Coelho 		wl1271_ap_set_probe_resp_tmpl(wl, rate, vif);
413962c2e579SLuciano Coelho 	}
414062c2e579SLuciano Coelho 
414148af2eb0SLuciano Coelho 	if (changed & BSS_CHANGED_BEACON) {
414262c2e579SLuciano Coelho 		ret = wlcore_set_beacon_template(wl, vif, is_ap);
414362c2e579SLuciano Coelho 		if (ret < 0)
414462c2e579SLuciano Coelho 			goto out;
41457b3115f2SLuciano Coelho 
4146830513abSEliad Peller 		if (test_and_clear_bit(WLVIF_FLAG_BEACON_DISABLED,
4147830513abSEliad Peller 				       &wlvif->flags)) {
4148830513abSEliad Peller 			ret = wlcore_hw_dfs_master_restart(wl, wlvif);
4149830513abSEliad Peller 			if (ret < 0)
4150830513abSEliad Peller 				goto out;
4151830513abSEliad Peller 		}
4152830513abSEliad Peller 	}
41537b3115f2SLuciano Coelho out:
41547b3115f2SLuciano Coelho 	if (ret != 0)
41557b3115f2SLuciano Coelho 		wl1271_error("beacon info change failed: %d", ret);
41567b3115f2SLuciano Coelho 	return ret;
41577b3115f2SLuciano Coelho }
41587b3115f2SLuciano Coelho 
41597b3115f2SLuciano Coelho /* AP mode changes */
wl1271_bss_info_changed_ap(struct wl1271 * wl,struct ieee80211_vif * vif,struct ieee80211_bss_conf * bss_conf,u32 changed)41607b3115f2SLuciano Coelho static void wl1271_bss_info_changed_ap(struct wl1271 *wl,
41617b3115f2SLuciano Coelho 				       struct ieee80211_vif *vif,
41627b3115f2SLuciano Coelho 				       struct ieee80211_bss_conf *bss_conf,
41637b3115f2SLuciano Coelho 				       u32 changed)
41647b3115f2SLuciano Coelho {
41657b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
41667b3115f2SLuciano Coelho 	int ret = 0;
41677b3115f2SLuciano Coelho 
4168b6970ee5SEliad Peller 	if (changed & BSS_CHANGED_BASIC_RATES) {
41697b3115f2SLuciano Coelho 		u32 rates = bss_conf->basic_rates;
41707b3115f2SLuciano Coelho 
41717b3115f2SLuciano Coelho 		wlvif->basic_rate_set = wl1271_tx_enabled_rates_get(wl, rates,
41727b3115f2SLuciano Coelho 								 wlvif->band);
41737b3115f2SLuciano Coelho 		wlvif->basic_rate = wl1271_tx_min_rate_get(wl,
41747b3115f2SLuciano Coelho 							wlvif->basic_rate_set);
41757b3115f2SLuciano Coelho 
41767b3115f2SLuciano Coelho 		ret = wl1271_init_ap_rates(wl, wlvif);
41777b3115f2SLuciano Coelho 		if (ret < 0) {
41787b3115f2SLuciano Coelho 			wl1271_error("AP rate policy change failed %d", ret);
41797b3115f2SLuciano Coelho 			goto out;
41807b3115f2SLuciano Coelho 		}
41817b3115f2SLuciano Coelho 
41827b3115f2SLuciano Coelho 		ret = wl1271_ap_init_templates(wl, vif);
41837b3115f2SLuciano Coelho 		if (ret < 0)
41847b3115f2SLuciano Coelho 			goto out;
418562c2e579SLuciano Coelho 
4186c0174ee2SMaital Hahn 		/* No need to set probe resp template for mesh */
4187c0174ee2SMaital Hahn 		if (!ieee80211_vif_is_mesh(vif)) {
4188c0174ee2SMaital Hahn 			ret = wl1271_ap_set_probe_resp_tmpl(wl,
4189c0174ee2SMaital Hahn 							    wlvif->basic_rate,
4190c0174ee2SMaital Hahn 							    vif);
419162c2e579SLuciano Coelho 			if (ret < 0)
419262c2e579SLuciano Coelho 				goto out;
4193c0174ee2SMaital Hahn 		}
419462c2e579SLuciano Coelho 
419562c2e579SLuciano Coelho 		ret = wlcore_set_beacon_template(wl, vif, true);
419662c2e579SLuciano Coelho 		if (ret < 0)
419762c2e579SLuciano Coelho 			goto out;
41987b3115f2SLuciano Coelho 	}
41997b3115f2SLuciano Coelho 
42007b3115f2SLuciano Coelho 	ret = wl1271_bss_beacon_info_changed(wl, vif, bss_conf, changed);
42017b3115f2SLuciano Coelho 	if (ret < 0)
42027b3115f2SLuciano Coelho 		goto out;
42037b3115f2SLuciano Coelho 
420448af2eb0SLuciano Coelho 	if (changed & BSS_CHANGED_BEACON_ENABLED) {
42057b3115f2SLuciano Coelho 		if (bss_conf->enable_beacon) {
42067b3115f2SLuciano Coelho 			if (!test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) {
42077b3115f2SLuciano Coelho 				ret = wl12xx_cmd_role_start_ap(wl, wlvif);
42087b3115f2SLuciano Coelho 				if (ret < 0)
42097b3115f2SLuciano Coelho 					goto out;
42107b3115f2SLuciano Coelho 
42117b3115f2SLuciano Coelho 				ret = wl1271_ap_init_hwenc(wl, wlvif);
42127b3115f2SLuciano Coelho 				if (ret < 0)
42137b3115f2SLuciano Coelho 					goto out;
42147b3115f2SLuciano Coelho 
42157b3115f2SLuciano Coelho 				set_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags);
42167b3115f2SLuciano Coelho 				wl1271_debug(DEBUG_AP, "started AP");
42177b3115f2SLuciano Coelho 			}
42187b3115f2SLuciano Coelho 		} else {
42197b3115f2SLuciano Coelho 			if (test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) {
4220187e52ccSArik Nemtsov 				/*
4221187e52ccSArik Nemtsov 				 * AP might be in ROC in case we have just
4222187e52ccSArik Nemtsov 				 * sent auth reply. handle it.
4223187e52ccSArik Nemtsov 				 */
4224187e52ccSArik Nemtsov 				if (test_bit(wlvif->role_id, wl->roc_map))
4225187e52ccSArik Nemtsov 					wl12xx_croc(wl, wlvif->role_id);
4226187e52ccSArik Nemtsov 
42277b3115f2SLuciano Coelho 				ret = wl12xx_cmd_role_stop_ap(wl, wlvif);
42287b3115f2SLuciano Coelho 				if (ret < 0)
42297b3115f2SLuciano Coelho 					goto out;
42307b3115f2SLuciano Coelho 
42317b3115f2SLuciano Coelho 				clear_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags);
42327b3115f2SLuciano Coelho 				clear_bit(WLVIF_FLAG_AP_PROBE_RESP_SET,
42337b3115f2SLuciano Coelho 					  &wlvif->flags);
42347b3115f2SLuciano Coelho 				wl1271_debug(DEBUG_AP, "stopped AP");
42357b3115f2SLuciano Coelho 			}
42367b3115f2SLuciano Coelho 		}
42377b3115f2SLuciano Coelho 	}
42387b3115f2SLuciano Coelho 
42397b3115f2SLuciano Coelho 	ret = wl1271_bss_erp_info_changed(wl, vif, bss_conf, changed);
42407b3115f2SLuciano Coelho 	if (ret < 0)
42417b3115f2SLuciano Coelho 		goto out;
42427b3115f2SLuciano Coelho 
42437b3115f2SLuciano Coelho 	/* Handle HT information change */
42447b3115f2SLuciano Coelho 	if ((changed & BSS_CHANGED_HT) &&
42454bf88530SJohannes Berg 	    (bss_conf->chandef.width != NL80211_CHAN_WIDTH_20_NOHT)) {
42467b3115f2SLuciano Coelho 		ret = wl1271_acx_set_ht_information(wl, wlvif,
42477b3115f2SLuciano Coelho 					bss_conf->ht_operation_mode);
42487b3115f2SLuciano Coelho 		if (ret < 0) {
42497b3115f2SLuciano Coelho 			wl1271_warning("Set ht information failed %d", ret);
42507b3115f2SLuciano Coelho 			goto out;
42517b3115f2SLuciano Coelho 		}
42527b3115f2SLuciano Coelho 	}
42537b3115f2SLuciano Coelho 
42547b3115f2SLuciano Coelho out:
42557b3115f2SLuciano Coelho 	return;
42567b3115f2SLuciano Coelho }
42577b3115f2SLuciano Coelho 
wlcore_set_bssid(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_vif * vif,u32 sta_rate_set)42583230f35eSEliad Peller static int wlcore_set_bssid(struct wl1271 *wl, struct wl12xx_vif *wlvif,
4259f276e20bSJohannes Berg 			    struct ieee80211_vif *vif, u32 sta_rate_set)
42603230f35eSEliad Peller {
4261f276e20bSJohannes Berg 	struct ieee80211_bss_conf *bss_conf = &vif->bss_conf;
42623230f35eSEliad Peller 	u32 rates;
42633230f35eSEliad Peller 	int ret;
42643230f35eSEliad Peller 
42653230f35eSEliad Peller 	wl1271_debug(DEBUG_MAC80211,
42663230f35eSEliad Peller 	     "changed_bssid: %pM, aid: %d, bcn_int: %d, brates: 0x%x sta_rate_set: 0x%x",
4267f276e20bSJohannes Berg 	     bss_conf->bssid, vif->cfg.aid,
42683230f35eSEliad Peller 	     bss_conf->beacon_int,
42693230f35eSEliad Peller 	     bss_conf->basic_rates, sta_rate_set);
42703230f35eSEliad Peller 
42713230f35eSEliad Peller 	wlvif->beacon_int = bss_conf->beacon_int;
42723230f35eSEliad Peller 	rates = bss_conf->basic_rates;
42733230f35eSEliad Peller 	wlvif->basic_rate_set =
42743230f35eSEliad Peller 		wl1271_tx_enabled_rates_get(wl, rates,
42753230f35eSEliad Peller 					    wlvif->band);
42763230f35eSEliad Peller 	wlvif->basic_rate =
42773230f35eSEliad Peller 		wl1271_tx_min_rate_get(wl,
42783230f35eSEliad Peller 				       wlvif->basic_rate_set);
42793230f35eSEliad Peller 
42803230f35eSEliad Peller 	if (sta_rate_set)
42813230f35eSEliad Peller 		wlvif->rate_set =
42823230f35eSEliad Peller 			wl1271_tx_enabled_rates_get(wl,
42833230f35eSEliad Peller 						sta_rate_set,
42843230f35eSEliad Peller 						wlvif->band);
42853230f35eSEliad Peller 
42863230f35eSEliad Peller 	/* we only support sched_scan while not connected */
428710199756SEliad Peller 	if (wl->sched_vif == wlvif)
428878e28062SEliad Peller 		wl->ops->sched_scan_stop(wl, wlvif);
42893230f35eSEliad Peller 
42903230f35eSEliad Peller 	ret = wl1271_acx_sta_rate_policies(wl, wlvif);
42913230f35eSEliad Peller 	if (ret < 0)
42923230f35eSEliad Peller 		return ret;
42933230f35eSEliad Peller 
42943230f35eSEliad Peller 	ret = wl12xx_cmd_build_null_data(wl, wlvif);
42953230f35eSEliad Peller 	if (ret < 0)
42963230f35eSEliad Peller 		return ret;
42973230f35eSEliad Peller 
42983230f35eSEliad Peller 	ret = wl1271_build_qos_null_data(wl, wl12xx_wlvif_to_vif(wlvif));
42993230f35eSEliad Peller 	if (ret < 0)
43003230f35eSEliad Peller 		return ret;
43013230f35eSEliad Peller 
43023230f35eSEliad Peller 	wlcore_set_ssid(wl, wlvif);
43033230f35eSEliad Peller 
43043230f35eSEliad Peller 	set_bit(WLVIF_FLAG_IN_USE, &wlvif->flags);
43053230f35eSEliad Peller 
43063230f35eSEliad Peller 	return 0;
43073230f35eSEliad Peller }
43083230f35eSEliad Peller 
wlcore_clear_bssid(struct wl1271 * wl,struct wl12xx_vif * wlvif)43093230f35eSEliad Peller static int wlcore_clear_bssid(struct wl1271 *wl, struct wl12xx_vif *wlvif)
43103230f35eSEliad Peller {
43113230f35eSEliad Peller 	int ret;
43123230f35eSEliad Peller 
43133230f35eSEliad Peller 	/* revert back to minimum rates for the current band */
43143230f35eSEliad Peller 	wl1271_set_band_rate(wl, wlvif);
43153230f35eSEliad Peller 	wlvif->basic_rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set);
43163230f35eSEliad Peller 
43173230f35eSEliad Peller 	ret = wl1271_acx_sta_rate_policies(wl, wlvif);
43183230f35eSEliad Peller 	if (ret < 0)
43193230f35eSEliad Peller 		return ret;
43203230f35eSEliad Peller 
43213230f35eSEliad Peller 	if (wlvif->bss_type == BSS_TYPE_STA_BSS &&
43223230f35eSEliad Peller 	    test_bit(WLVIF_FLAG_IN_USE, &wlvif->flags)) {
43233230f35eSEliad Peller 		ret = wl12xx_cmd_role_stop_sta(wl, wlvif);
43243230f35eSEliad Peller 		if (ret < 0)
43253230f35eSEliad Peller 			return ret;
43263230f35eSEliad Peller 	}
43273230f35eSEliad Peller 
43283230f35eSEliad Peller 	clear_bit(WLVIF_FLAG_IN_USE, &wlvif->flags);
43293230f35eSEliad Peller 	return 0;
43303230f35eSEliad Peller }
43317b3115f2SLuciano Coelho /* STA/IBSS mode changes */
wl1271_bss_info_changed_sta(struct wl1271 * wl,struct ieee80211_vif * vif,struct ieee80211_bss_conf * bss_conf,u32 changed)43327b3115f2SLuciano Coelho static void wl1271_bss_info_changed_sta(struct wl1271 *wl,
43337b3115f2SLuciano Coelho 					struct ieee80211_vif *vif,
43347b3115f2SLuciano Coelho 					struct ieee80211_bss_conf *bss_conf,
43357b3115f2SLuciano Coelho 					u32 changed)
43367b3115f2SLuciano Coelho {
43377b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
43383230f35eSEliad Peller 	bool do_join = false;
43397b3115f2SLuciano Coelho 	bool is_ibss = (wlvif->bss_type == BSS_TYPE_IBSS);
43407b3115f2SLuciano Coelho 	bool ibss_joined = false;
43417b3115f2SLuciano Coelho 	u32 sta_rate_set = 0;
43427b3115f2SLuciano Coelho 	int ret;
43437b3115f2SLuciano Coelho 	struct ieee80211_sta *sta;
43447b3115f2SLuciano Coelho 	bool sta_exists = false;
43457b3115f2SLuciano Coelho 	struct ieee80211_sta_ht_cap sta_ht_cap;
43467b3115f2SLuciano Coelho 
43477b3115f2SLuciano Coelho 	if (is_ibss) {
43487b3115f2SLuciano Coelho 		ret = wl1271_bss_beacon_info_changed(wl, vif, bss_conf,
43497b3115f2SLuciano Coelho 						     changed);
43507b3115f2SLuciano Coelho 		if (ret < 0)
43517b3115f2SLuciano Coelho 			goto out;
43527b3115f2SLuciano Coelho 	}
43537b3115f2SLuciano Coelho 
43547b3115f2SLuciano Coelho 	if (changed & BSS_CHANGED_IBSS) {
4355f276e20bSJohannes Berg 		if (vif->cfg.ibss_joined) {
43567b3115f2SLuciano Coelho 			set_bit(WLVIF_FLAG_IBSS_JOINED, &wlvif->flags);
43577b3115f2SLuciano Coelho 			ibss_joined = true;
43587b3115f2SLuciano Coelho 		} else {
43593230f35eSEliad Peller 			wlcore_unset_assoc(wl, wlvif);
43603230f35eSEliad Peller 			wl12xx_cmd_role_stop_sta(wl, wlvif);
43617b3115f2SLuciano Coelho 		}
43627b3115f2SLuciano Coelho 	}
43637b3115f2SLuciano Coelho 
43647b3115f2SLuciano Coelho 	if ((changed & BSS_CHANGED_BEACON_INT) && ibss_joined)
43657b3115f2SLuciano Coelho 		do_join = true;
43667b3115f2SLuciano Coelho 
43677b3115f2SLuciano Coelho 	/* Need to update the SSID (for filtering etc) */
43687b3115f2SLuciano Coelho 	if ((changed & BSS_CHANGED_BEACON) && ibss_joined)
43697b3115f2SLuciano Coelho 		do_join = true;
43707b3115f2SLuciano Coelho 
43717b3115f2SLuciano Coelho 	if ((changed & BSS_CHANGED_BEACON_ENABLED) && ibss_joined) {
43727b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_ADHOC, "ad-hoc beaconing: %s",
43737b3115f2SLuciano Coelho 			     bss_conf->enable_beacon ? "enabled" : "disabled");
43747b3115f2SLuciano Coelho 
43757b3115f2SLuciano Coelho 		do_join = true;
43767b3115f2SLuciano Coelho 	}
43777b3115f2SLuciano Coelho 
4378b0ed8a4dSArik Nemtsov 	if (changed & BSS_CHANGED_IDLE && !is_ibss)
4379f276e20bSJohannes Berg 		wl1271_sta_handle_idle(wl, wlvif, vif->cfg.idle);
4380b0ed8a4dSArik Nemtsov 
438148af2eb0SLuciano Coelho 	if (changed & BSS_CHANGED_CQM) {
43827b3115f2SLuciano Coelho 		bool enable = false;
43837b3115f2SLuciano Coelho 		if (bss_conf->cqm_rssi_thold)
43847b3115f2SLuciano Coelho 			enable = true;
43857b3115f2SLuciano Coelho 		ret = wl1271_acx_rssi_snr_trigger(wl, wlvif, enable,
43867b3115f2SLuciano Coelho 						  bss_conf->cqm_rssi_thold,
43877b3115f2SLuciano Coelho 						  bss_conf->cqm_rssi_hyst);
43887b3115f2SLuciano Coelho 		if (ret < 0)
43897b3115f2SLuciano Coelho 			goto out;
43907b3115f2SLuciano Coelho 		wlvif->rssi_thold = bss_conf->cqm_rssi_thold;
43917b3115f2SLuciano Coelho 	}
43927b3115f2SLuciano Coelho 
4393ec87011aSEliad Peller 	if (changed & (BSS_CHANGED_BSSID | BSS_CHANGED_HT |
4394ec87011aSEliad Peller 		       BSS_CHANGED_ASSOC)) {
43957b3115f2SLuciano Coelho 		rcu_read_lock();
43967b3115f2SLuciano Coelho 		sta = ieee80211_find_sta(vif, bss_conf->bssid);
4397ef08d028SLuciano Coelho 		if (sta) {
4398046d2e7cSSriram R 			u8 *rx_mask = sta->deflink.ht_cap.mcs.rx_mask;
43997b3115f2SLuciano Coelho 
44007b3115f2SLuciano Coelho 			/* save the supp_rates of the ap */
4401046d2e7cSSriram R 			sta_rate_set = sta->deflink.supp_rates[wlvif->band];
4402046d2e7cSSriram R 			if (sta->deflink.ht_cap.ht_supported)
44037b3115f2SLuciano Coelho 				sta_rate_set |=
4404ef08d028SLuciano Coelho 					(rx_mask[0] << HW_HT_RATES_OFFSET) |
4405ef08d028SLuciano Coelho 					(rx_mask[1] << HW_MIMO_RATES_OFFSET);
4406046d2e7cSSriram R 			sta_ht_cap = sta->deflink.ht_cap;
44077b3115f2SLuciano Coelho 			sta_exists = true;
4408ef08d028SLuciano Coelho 		}
44097b3115f2SLuciano Coelho 
44107b3115f2SLuciano Coelho 		rcu_read_unlock();
44117b3115f2SLuciano Coelho 	}
44127b3115f2SLuciano Coelho 
44133230f35eSEliad Peller 	if (changed & BSS_CHANGED_BSSID) {
44143230f35eSEliad Peller 		if (!is_zero_ether_addr(bss_conf->bssid)) {
4415f276e20bSJohannes Berg 			ret = wlcore_set_bssid(wl, wlvif, vif,
44163230f35eSEliad Peller 					       sta_rate_set);
44173230f35eSEliad Peller 			if (ret < 0)
44183230f35eSEliad Peller 				goto out;
44193230f35eSEliad Peller 
44203230f35eSEliad Peller 			/* Need to update the BSSID (for filtering etc) */
44217b3115f2SLuciano Coelho 			do_join = true;
44227b3115f2SLuciano Coelho 		} else {
44233230f35eSEliad Peller 			ret = wlcore_clear_bssid(wl, wlvif);
44247b3115f2SLuciano Coelho 			if (ret < 0)
44257b3115f2SLuciano Coelho 				goto out;
44267b3115f2SLuciano Coelho 		}
44277b3115f2SLuciano Coelho 	}
44287b3115f2SLuciano Coelho 
44297b3115f2SLuciano Coelho 	if (changed & BSS_CHANGED_IBSS) {
44307b3115f2SLuciano Coelho 		wl1271_debug(DEBUG_ADHOC, "ibss_joined: %d",
4431f276e20bSJohannes Berg 			     vif->cfg.ibss_joined);
44327b3115f2SLuciano Coelho 
4433f276e20bSJohannes Berg 		if (vif->cfg.ibss_joined) {
44347b3115f2SLuciano Coelho 			u32 rates = bss_conf->basic_rates;
44357b3115f2SLuciano Coelho 			wlvif->basic_rate_set =
44367b3115f2SLuciano Coelho 				wl1271_tx_enabled_rates_get(wl, rates,
44377b3115f2SLuciano Coelho 							    wlvif->band);
44387b3115f2SLuciano Coelho 			wlvif->basic_rate =
44397b3115f2SLuciano Coelho 				wl1271_tx_min_rate_get(wl,
44407b3115f2SLuciano Coelho 						       wlvif->basic_rate_set);
44417b3115f2SLuciano Coelho 
44427b3115f2SLuciano Coelho 			/* by default, use 11b + OFDM rates */
44437b3115f2SLuciano Coelho 			wlvif->rate_set = CONF_TX_IBSS_DEFAULT_RATES;
44447b3115f2SLuciano Coelho 			ret = wl1271_acx_sta_rate_policies(wl, wlvif);
44457b3115f2SLuciano Coelho 			if (ret < 0)
44467b3115f2SLuciano Coelho 				goto out;
44477b3115f2SLuciano Coelho 		}
44487b3115f2SLuciano Coelho 	}
44497b3115f2SLuciano Coelho 
4450d881fa2cSEliad Peller 	if ((changed & BSS_CHANGED_BEACON_INFO) && bss_conf->dtim_period) {
4451d881fa2cSEliad Peller 		/* enable beacon filtering */
4452d881fa2cSEliad Peller 		ret = wl1271_acx_beacon_filter_opt(wl, wlvif, true);
4453d881fa2cSEliad Peller 		if (ret < 0)
4454d881fa2cSEliad Peller 			goto out;
4455d881fa2cSEliad Peller 	}
4456d881fa2cSEliad Peller 
44577b3115f2SLuciano Coelho 	ret = wl1271_bss_erp_info_changed(wl, vif, bss_conf, changed);
44587b3115f2SLuciano Coelho 	if (ret < 0)
44597b3115f2SLuciano Coelho 		goto out;
44607b3115f2SLuciano Coelho 
44617b3115f2SLuciano Coelho 	if (do_join) {
44623230f35eSEliad Peller 		ret = wlcore_join(wl, wlvif);
44637b3115f2SLuciano Coelho 		if (ret < 0) {
44647b3115f2SLuciano Coelho 			wl1271_warning("cmd join failed %d", ret);
44657b3115f2SLuciano Coelho 			goto out;
44667b3115f2SLuciano Coelho 		}
44673230f35eSEliad Peller 	}
44687b3115f2SLuciano Coelho 
44693230f35eSEliad Peller 	if (changed & BSS_CHANGED_ASSOC) {
4470f276e20bSJohannes Berg 		if (vif->cfg.assoc) {
4471ec87011aSEliad Peller 			ret = wlcore_set_assoc(wl, wlvif, bss_conf,
4472ec87011aSEliad Peller 					       sta_rate_set);
44737b3115f2SLuciano Coelho 			if (ret < 0)
44747b3115f2SLuciano Coelho 				goto out;
44757b3115f2SLuciano Coelho 
44767b3115f2SLuciano Coelho 			if (test_bit(WLVIF_FLAG_STA_AUTHORIZED, &wlvif->flags))
44777b3115f2SLuciano Coelho 				wl12xx_set_authorized(wl, wlvif);
44783230f35eSEliad Peller 		} else {
44793230f35eSEliad Peller 			wlcore_unset_assoc(wl, wlvif);
44807b3115f2SLuciano Coelho 		}
44817b3115f2SLuciano Coelho 	}
44827b3115f2SLuciano Coelho 
4483518b680aSEliad Peller 	if (changed & BSS_CHANGED_PS) {
4484a3b8008dSJohannes Berg 		if (vif->cfg.ps &&
4485518b680aSEliad Peller 		    test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags) &&
4486518b680aSEliad Peller 		    !test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags)) {
4487518b680aSEliad Peller 			int ps_mode;
4488518b680aSEliad Peller 			char *ps_mode_str;
4489518b680aSEliad Peller 
4490518b680aSEliad Peller 			if (wl->conf.conn.forced_ps) {
4491518b680aSEliad Peller 				ps_mode = STATION_POWER_SAVE_MODE;
4492518b680aSEliad Peller 				ps_mode_str = "forced";
4493518b680aSEliad Peller 			} else {
4494518b680aSEliad Peller 				ps_mode = STATION_AUTO_PS_MODE;
4495518b680aSEliad Peller 				ps_mode_str = "auto";
4496518b680aSEliad Peller 			}
4497518b680aSEliad Peller 
4498518b680aSEliad Peller 			wl1271_debug(DEBUG_PSM, "%s ps enabled", ps_mode_str);
4499518b680aSEliad Peller 
4500518b680aSEliad Peller 			ret = wl1271_ps_set_mode(wl, wlvif, ps_mode);
45017b3115f2SLuciano Coelho 			if (ret < 0)
4502518b680aSEliad Peller 				wl1271_warning("enter %s ps failed %d",
4503518b680aSEliad Peller 					       ps_mode_str, ret);
4504a3b8008dSJohannes Berg 		} else if (!vif->cfg.ps &&
4505518b680aSEliad Peller 			   test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags)) {
4506518b680aSEliad Peller 			wl1271_debug(DEBUG_PSM, "auto ps disabled");
4507518b680aSEliad Peller 
4508518b680aSEliad Peller 			ret = wl1271_ps_set_mode(wl, wlvif,
4509518b680aSEliad Peller 						 STATION_ACTIVE_MODE);
4510518b680aSEliad Peller 			if (ret < 0)
4511518b680aSEliad Peller 				wl1271_warning("exit auto ps failed %d", ret);
45127b3115f2SLuciano Coelho 		}
45137b3115f2SLuciano Coelho 	}
45147b3115f2SLuciano Coelho 
45157b3115f2SLuciano Coelho 	/* Handle new association with HT. Do this after join. */
45166f0b1bb2SEliad Peller 	if (sta_exists) {
451758321b29SEliad Peller 		bool enabled =
4518aaabee8bSLuciano Coelho 			bss_conf->chandef.width != NL80211_CHAN_WIDTH_20_NOHT;
451958321b29SEliad Peller 
4520530abe19SEliad Peller 		ret = wlcore_hw_set_peer_cap(wl,
45217b3115f2SLuciano Coelho 					     &sta_ht_cap,
452258321b29SEliad Peller 					     enabled,
4523530abe19SEliad Peller 					     wlvif->rate_set,
45247b3115f2SLuciano Coelho 					     wlvif->sta.hlid);
45257b3115f2SLuciano Coelho 		if (ret < 0) {
452658321b29SEliad Peller 			wl1271_warning("Set ht cap failed %d", ret);
45277b3115f2SLuciano Coelho 			goto out;
452858321b29SEliad Peller 
45297b3115f2SLuciano Coelho 		}
45307b3115f2SLuciano Coelho 
453158321b29SEliad Peller 		if (enabled) {
45327b3115f2SLuciano Coelho 			ret = wl1271_acx_set_ht_information(wl, wlvif,
45337b3115f2SLuciano Coelho 						bss_conf->ht_operation_mode);
45347b3115f2SLuciano Coelho 			if (ret < 0) {
453558321b29SEliad Peller 				wl1271_warning("Set ht information failed %d",
453658321b29SEliad Peller 					       ret);
45377b3115f2SLuciano Coelho 				goto out;
45387b3115f2SLuciano Coelho 			}
45397b3115f2SLuciano Coelho 		}
454058321b29SEliad Peller 	}
45417b3115f2SLuciano Coelho 
45427b3115f2SLuciano Coelho 	/* Handle arp filtering. Done after join. */
45437b3115f2SLuciano Coelho 	if ((changed & BSS_CHANGED_ARP_FILTER) ||
45447b3115f2SLuciano Coelho 	    (!is_ibss && (changed & BSS_CHANGED_QOS))) {
4545f276e20bSJohannes Berg 		__be32 addr = vif->cfg.arp_addr_list[0];
45467b3115f2SLuciano Coelho 		wlvif->sta.qos = bss_conf->qos;
45477b3115f2SLuciano Coelho 		WARN_ON(wlvif->bss_type != BSS_TYPE_STA_BSS);
45487b3115f2SLuciano Coelho 
4549f276e20bSJohannes Berg 		if (vif->cfg.arp_addr_cnt == 1 && vif->cfg.assoc) {
45507b3115f2SLuciano Coelho 			wlvif->ip_addr = addr;
45517b3115f2SLuciano Coelho 			/*
45527b3115f2SLuciano Coelho 			 * The template should have been configured only upon
45537b3115f2SLuciano Coelho 			 * association. however, it seems that the correct ip
45547b3115f2SLuciano Coelho 			 * isn't being set (when sending), so we have to
45557b3115f2SLuciano Coelho 			 * reconfigure the template upon every ip change.
45567b3115f2SLuciano Coelho 			 */
45577b3115f2SLuciano Coelho 			ret = wl1271_cmd_build_arp_rsp(wl, wlvif);
45587b3115f2SLuciano Coelho 			if (ret < 0) {
45597b3115f2SLuciano Coelho 				wl1271_warning("build arp rsp failed: %d", ret);
45607b3115f2SLuciano Coelho 				goto out;
45617b3115f2SLuciano Coelho 			}
45627b3115f2SLuciano Coelho 
45637b3115f2SLuciano Coelho 			ret = wl1271_acx_arp_ip_filter(wl, wlvif,
45647b3115f2SLuciano Coelho 				(ACX_ARP_FILTER_ARP_FILTERING |
45657b3115f2SLuciano Coelho 				 ACX_ARP_FILTER_AUTO_ARP),
45667b3115f2SLuciano Coelho 				addr);
45677b3115f2SLuciano Coelho 		} else {
45687b3115f2SLuciano Coelho 			wlvif->ip_addr = 0;
45697b3115f2SLuciano Coelho 			ret = wl1271_acx_arp_ip_filter(wl, wlvif, 0, addr);
45707b3115f2SLuciano Coelho 		}
45717b3115f2SLuciano Coelho 
45727b3115f2SLuciano Coelho 		if (ret < 0)
45737b3115f2SLuciano Coelho 			goto out;
45747b3115f2SLuciano Coelho 	}
45757b3115f2SLuciano Coelho 
45767b3115f2SLuciano Coelho out:
45777b3115f2SLuciano Coelho 	return;
45787b3115f2SLuciano Coelho }
45797b3115f2SLuciano Coelho 
wl1271_op_bss_info_changed(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_bss_conf * bss_conf,u64 changed)45807b3115f2SLuciano Coelho static void wl1271_op_bss_info_changed(struct ieee80211_hw *hw,
45817b3115f2SLuciano Coelho 				       struct ieee80211_vif *vif,
45827b3115f2SLuciano Coelho 				       struct ieee80211_bss_conf *bss_conf,
45837b7090b4SJohannes Berg 				       u64 changed)
45847b3115f2SLuciano Coelho {
45857b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
45867b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
45877b3115f2SLuciano Coelho 	bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS);
45887b3115f2SLuciano Coelho 	int ret;
45897b3115f2SLuciano Coelho 
4590d3f5a1b5SEliad Peller 	wl1271_debug(DEBUG_MAC80211, "mac80211 bss info role %d changed 0x%x",
4591d3f5a1b5SEliad Peller 		     wlvif->role_id, (int)changed);
45927b3115f2SLuciano Coelho 
45936b8bf5bcSArik Nemtsov 	/*
45946b8bf5bcSArik Nemtsov 	 * make sure to cancel pending disconnections if our association
45956b8bf5bcSArik Nemtsov 	 * state changed
45966b8bf5bcSArik Nemtsov 	 */
45976b8bf5bcSArik Nemtsov 	if (!is_ap && (changed & BSS_CHANGED_ASSOC))
4598c50a2825SEliad Peller 		cancel_delayed_work_sync(&wlvif->connection_loss_work);
45996b8bf5bcSArik Nemtsov 
4600b515d83aSEliad Peller 	if (is_ap && (changed & BSS_CHANGED_BEACON_ENABLED) &&
4601b515d83aSEliad Peller 	    !bss_conf->enable_beacon)
4602b515d83aSEliad Peller 		wl1271_tx_flush(wl);
4603b515d83aSEliad Peller 
46047b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
46057b3115f2SLuciano Coelho 
46064cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
46077b3115f2SLuciano Coelho 		goto out;
46087b3115f2SLuciano Coelho 
46097b3115f2SLuciano Coelho 	if (unlikely(!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)))
46107b3115f2SLuciano Coelho 		goto out;
46117b3115f2SLuciano Coelho 
4612ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
4613ab589ac2SMinghao Chi 	if (ret < 0)
46147b3115f2SLuciano Coelho 		goto out;
46157b3115f2SLuciano Coelho 
4616b30d49b2SAlex Gal 	if ((changed & BSS_CHANGED_TXPOWER) &&
4617b30d49b2SAlex Gal 	    bss_conf->txpower != wlvif->power_level) {
4618b30d49b2SAlex Gal 
4619b30d49b2SAlex Gal 		ret = wl1271_acx_tx_power(wl, wlvif, bss_conf->txpower);
4620b30d49b2SAlex Gal 		if (ret < 0)
4621b30d49b2SAlex Gal 			goto out;
4622b30d49b2SAlex Gal 
4623b30d49b2SAlex Gal 		wlvif->power_level = bss_conf->txpower;
4624b30d49b2SAlex Gal 	}
4625b30d49b2SAlex Gal 
46267b3115f2SLuciano Coelho 	if (is_ap)
46277b3115f2SLuciano Coelho 		wl1271_bss_info_changed_ap(wl, vif, bss_conf, changed);
46287b3115f2SLuciano Coelho 	else
46297b3115f2SLuciano Coelho 		wl1271_bss_info_changed_sta(wl, vif, bss_conf, changed);
46307b3115f2SLuciano Coelho 
46319b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
46329b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
46337b3115f2SLuciano Coelho 
46347b3115f2SLuciano Coelho out:
46357b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
46367b3115f2SLuciano Coelho }
46377b3115f2SLuciano Coelho 
wlcore_op_add_chanctx(struct ieee80211_hw * hw,struct ieee80211_chanctx_conf * ctx)4638b6970ee5SEliad Peller static int wlcore_op_add_chanctx(struct ieee80211_hw *hw,
4639b6970ee5SEliad Peller 				 struct ieee80211_chanctx_conf *ctx)
4640b6970ee5SEliad Peller {
4641b6970ee5SEliad Peller 	wl1271_debug(DEBUG_MAC80211, "mac80211 add chanctx %d (type %d)",
4642aaabee8bSLuciano Coelho 		     ieee80211_frequency_to_channel(ctx->def.chan->center_freq),
4643aaabee8bSLuciano Coelho 		     cfg80211_get_chandef_type(&ctx->def));
4644b6970ee5SEliad Peller 	return 0;
4645b6970ee5SEliad Peller }
4646b6970ee5SEliad Peller 
wlcore_op_remove_chanctx(struct ieee80211_hw * hw,struct ieee80211_chanctx_conf * ctx)4647b6970ee5SEliad Peller static void wlcore_op_remove_chanctx(struct ieee80211_hw *hw,
4648b6970ee5SEliad Peller 				     struct ieee80211_chanctx_conf *ctx)
4649b6970ee5SEliad Peller {
4650b6970ee5SEliad Peller 	wl1271_debug(DEBUG_MAC80211, "mac80211 remove chanctx %d (type %d)",
4651aaabee8bSLuciano Coelho 		     ieee80211_frequency_to_channel(ctx->def.chan->center_freq),
4652aaabee8bSLuciano Coelho 		     cfg80211_get_chandef_type(&ctx->def));
4653b6970ee5SEliad Peller }
4654b6970ee5SEliad Peller 
wlcore_op_change_chanctx(struct ieee80211_hw * hw,struct ieee80211_chanctx_conf * ctx,u32 changed)4655b6970ee5SEliad Peller static void wlcore_op_change_chanctx(struct ieee80211_hw *hw,
4656b6970ee5SEliad Peller 				     struct ieee80211_chanctx_conf *ctx,
4657b6970ee5SEliad Peller 				     u32 changed)
4658b6970ee5SEliad Peller {
4659750e9d15SEliad Peller 	struct wl1271 *wl = hw->priv;
4660750e9d15SEliad Peller 	struct wl12xx_vif *wlvif;
4661750e9d15SEliad Peller 	int ret;
4662750e9d15SEliad Peller 	int channel = ieee80211_frequency_to_channel(
4663750e9d15SEliad Peller 		ctx->def.chan->center_freq);
4664750e9d15SEliad Peller 
4665b6970ee5SEliad Peller 	wl1271_debug(DEBUG_MAC80211,
4666b6970ee5SEliad Peller 		     "mac80211 change chanctx %d (type %d) changed 0x%x",
4667750e9d15SEliad Peller 		     channel, cfg80211_get_chandef_type(&ctx->def), changed);
4668750e9d15SEliad Peller 
4669750e9d15SEliad Peller 	mutex_lock(&wl->mutex);
4670750e9d15SEliad Peller 
4671ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
4672ab589ac2SMinghao Chi 	if (ret < 0)
4673750e9d15SEliad Peller 		goto out;
4674750e9d15SEliad Peller 
4675750e9d15SEliad Peller 	wl12xx_for_each_wlvif(wl, wlvif) {
4676750e9d15SEliad Peller 		struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
4677750e9d15SEliad Peller 
4678750e9d15SEliad Peller 		rcu_read_lock();
4679d0a9123eSJohannes Berg 		if (rcu_access_pointer(vif->bss_conf.chanctx_conf) != ctx) {
4680750e9d15SEliad Peller 			rcu_read_unlock();
4681750e9d15SEliad Peller 			continue;
4682750e9d15SEliad Peller 		}
4683750e9d15SEliad Peller 		rcu_read_unlock();
4684750e9d15SEliad Peller 
4685750e9d15SEliad Peller 		/* start radar if needed */
4686750e9d15SEliad Peller 		if (changed & IEEE80211_CHANCTX_CHANGE_RADAR &&
4687750e9d15SEliad Peller 		    wlvif->bss_type == BSS_TYPE_AP_BSS &&
4688750e9d15SEliad Peller 		    ctx->radar_enabled && !wlvif->radar_enabled &&
4689750e9d15SEliad Peller 		    ctx->def.chan->dfs_state == NL80211_DFS_USABLE) {
4690750e9d15SEliad Peller 			wl1271_debug(DEBUG_MAC80211, "Start radar detection");
4691750e9d15SEliad Peller 			wlcore_hw_set_cac(wl, wlvif, true);
4692750e9d15SEliad Peller 			wlvif->radar_enabled = true;
4693750e9d15SEliad Peller 		}
4694750e9d15SEliad Peller 	}
4695750e9d15SEliad Peller 
46969b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
46979b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
4698750e9d15SEliad Peller out:
4699750e9d15SEliad Peller 	mutex_unlock(&wl->mutex);
4700b6970ee5SEliad Peller }
4701b6970ee5SEliad Peller 
wlcore_op_assign_vif_chanctx(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf,struct ieee80211_chanctx_conf * ctx)4702b6970ee5SEliad Peller static int wlcore_op_assign_vif_chanctx(struct ieee80211_hw *hw,
4703b6970ee5SEliad Peller 					struct ieee80211_vif *vif,
4704727eff4dSGregory Greenman 					struct ieee80211_bss_conf *link_conf,
4705b6970ee5SEliad Peller 					struct ieee80211_chanctx_conf *ctx)
4706b6970ee5SEliad Peller {
4707b6970ee5SEliad Peller 	struct wl1271 *wl = hw->priv;
4708b6970ee5SEliad Peller 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
4709b6970ee5SEliad Peller 	int channel = ieee80211_frequency_to_channel(
4710aaabee8bSLuciano Coelho 		ctx->def.chan->center_freq);
4711750e9d15SEliad Peller 	int ret = -EINVAL;
4712b6970ee5SEliad Peller 
4713b6970ee5SEliad Peller 	wl1271_debug(DEBUG_MAC80211,
4714750e9d15SEliad Peller 		     "mac80211 assign chanctx (role %d) %d (type %d) (radar %d dfs_state %d)",
4715750e9d15SEliad Peller 		     wlvif->role_id, channel,
4716750e9d15SEliad Peller 		     cfg80211_get_chandef_type(&ctx->def),
4717750e9d15SEliad Peller 		     ctx->radar_enabled, ctx->def.chan->dfs_state);
4718b6970ee5SEliad Peller 
4719b6970ee5SEliad Peller 	mutex_lock(&wl->mutex);
4720b6970ee5SEliad Peller 
4721750e9d15SEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON))
4722750e9d15SEliad Peller 		goto out;
4723750e9d15SEliad Peller 
4724750e9d15SEliad Peller 	if (unlikely(!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)))
4725750e9d15SEliad Peller 		goto out;
4726750e9d15SEliad Peller 
4727ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
4728ab589ac2SMinghao Chi 	if (ret < 0)
4729750e9d15SEliad Peller 		goto out;
4730750e9d15SEliad Peller 
4731aaabee8bSLuciano Coelho 	wlvif->band = ctx->def.chan->band;
4732b6970ee5SEliad Peller 	wlvif->channel = channel;
4733aaabee8bSLuciano Coelho 	wlvif->channel_type = cfg80211_get_chandef_type(&ctx->def);
4734b6970ee5SEliad Peller 
4735b6970ee5SEliad Peller 	/* update default rates according to the band */
4736b6970ee5SEliad Peller 	wl1271_set_band_rate(wl, wlvif);
4737b6970ee5SEliad Peller 
4738750e9d15SEliad Peller 	if (ctx->radar_enabled &&
4739750e9d15SEliad Peller 	    ctx->def.chan->dfs_state == NL80211_DFS_USABLE) {
4740750e9d15SEliad Peller 		wl1271_debug(DEBUG_MAC80211, "Start radar detection");
4741750e9d15SEliad Peller 		wlcore_hw_set_cac(wl, wlvif, true);
4742750e9d15SEliad Peller 		wlvif->radar_enabled = true;
4743750e9d15SEliad Peller 	}
4744750e9d15SEliad Peller 
47459b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
47469b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
4747750e9d15SEliad Peller out:
4748b6970ee5SEliad Peller 	mutex_unlock(&wl->mutex);
4749b6970ee5SEliad Peller 
4750b6970ee5SEliad Peller 	return 0;
4751b6970ee5SEliad Peller }
4752b6970ee5SEliad Peller 
wlcore_op_unassign_vif_chanctx(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf,struct ieee80211_chanctx_conf * ctx)4753b6970ee5SEliad Peller static void wlcore_op_unassign_vif_chanctx(struct ieee80211_hw *hw,
4754b6970ee5SEliad Peller 					   struct ieee80211_vif *vif,
4755727eff4dSGregory Greenman 					   struct ieee80211_bss_conf *link_conf,
4756b6970ee5SEliad Peller 					   struct ieee80211_chanctx_conf *ctx)
4757b6970ee5SEliad Peller {
4758b6970ee5SEliad Peller 	struct wl1271 *wl = hw->priv;
4759b6970ee5SEliad Peller 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
4760750e9d15SEliad Peller 	int ret;
4761b6970ee5SEliad Peller 
4762b6970ee5SEliad Peller 	wl1271_debug(DEBUG_MAC80211,
4763b6970ee5SEliad Peller 		     "mac80211 unassign chanctx (role %d) %d (type %d)",
4764b6970ee5SEliad Peller 		     wlvif->role_id,
4765aaabee8bSLuciano Coelho 		     ieee80211_frequency_to_channel(ctx->def.chan->center_freq),
4766aaabee8bSLuciano Coelho 		     cfg80211_get_chandef_type(&ctx->def));
4767b6970ee5SEliad Peller 
4768b6970ee5SEliad Peller 	wl1271_tx_flush(wl);
4769750e9d15SEliad Peller 
4770750e9d15SEliad Peller 	mutex_lock(&wl->mutex);
4771750e9d15SEliad Peller 
4772750e9d15SEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON))
4773750e9d15SEliad Peller 		goto out;
4774750e9d15SEliad Peller 
4775750e9d15SEliad Peller 	if (unlikely(!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)))
4776750e9d15SEliad Peller 		goto out;
4777750e9d15SEliad Peller 
4778ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
4779ab589ac2SMinghao Chi 	if (ret < 0)
4780750e9d15SEliad Peller 		goto out;
4781750e9d15SEliad Peller 
4782750e9d15SEliad Peller 	if (wlvif->radar_enabled) {
4783750e9d15SEliad Peller 		wl1271_debug(DEBUG_MAC80211, "Stop radar detection");
4784750e9d15SEliad Peller 		wlcore_hw_set_cac(wl, wlvif, false);
4785750e9d15SEliad Peller 		wlvif->radar_enabled = false;
4786750e9d15SEliad Peller 	}
4787750e9d15SEliad Peller 
47889b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
47899b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
4790750e9d15SEliad Peller out:
4791750e9d15SEliad Peller 	mutex_unlock(&wl->mutex);
4792750e9d15SEliad Peller }
4793750e9d15SEliad Peller 
__wlcore_switch_vif_chan(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_chanctx_conf * new_ctx)4794750e9d15SEliad Peller static int __wlcore_switch_vif_chan(struct wl1271 *wl,
4795750e9d15SEliad Peller 				    struct wl12xx_vif *wlvif,
4796750e9d15SEliad Peller 				    struct ieee80211_chanctx_conf *new_ctx)
4797750e9d15SEliad Peller {
4798750e9d15SEliad Peller 	int channel = ieee80211_frequency_to_channel(
4799750e9d15SEliad Peller 		new_ctx->def.chan->center_freq);
4800750e9d15SEliad Peller 
4801750e9d15SEliad Peller 	wl1271_debug(DEBUG_MAC80211,
4802750e9d15SEliad Peller 		     "switch vif (role %d) %d -> %d chan_type: %d",
4803750e9d15SEliad Peller 		     wlvif->role_id, wlvif->channel, channel,
4804750e9d15SEliad Peller 		     cfg80211_get_chandef_type(&new_ctx->def));
4805750e9d15SEliad Peller 
4806750e9d15SEliad Peller 	if (WARN_ON_ONCE(wlvif->bss_type != BSS_TYPE_AP_BSS))
4807750e9d15SEliad Peller 		return 0;
4808750e9d15SEliad Peller 
4809830513abSEliad Peller 	WARN_ON(!test_bit(WLVIF_FLAG_BEACON_DISABLED, &wlvif->flags));
4810830513abSEliad Peller 
4811750e9d15SEliad Peller 	if (wlvif->radar_enabled) {
4812750e9d15SEliad Peller 		wl1271_debug(DEBUG_MAC80211, "Stop radar detection");
4813750e9d15SEliad Peller 		wlcore_hw_set_cac(wl, wlvif, false);
4814750e9d15SEliad Peller 		wlvif->radar_enabled = false;
4815750e9d15SEliad Peller 	}
4816750e9d15SEliad Peller 
4817750e9d15SEliad Peller 	wlvif->band = new_ctx->def.chan->band;
4818750e9d15SEliad Peller 	wlvif->channel = channel;
4819750e9d15SEliad Peller 	wlvif->channel_type = cfg80211_get_chandef_type(&new_ctx->def);
4820750e9d15SEliad Peller 
4821750e9d15SEliad Peller 	/* start radar if needed */
4822750e9d15SEliad Peller 	if (new_ctx->radar_enabled) {
4823750e9d15SEliad Peller 		wl1271_debug(DEBUG_MAC80211, "Start radar detection");
4824750e9d15SEliad Peller 		wlcore_hw_set_cac(wl, wlvif, true);
4825750e9d15SEliad Peller 		wlvif->radar_enabled = true;
4826750e9d15SEliad Peller 	}
4827750e9d15SEliad Peller 
4828750e9d15SEliad Peller 	return 0;
4829750e9d15SEliad Peller }
4830750e9d15SEliad Peller 
4831750e9d15SEliad Peller static int
wlcore_op_switch_vif_chanctx(struct ieee80211_hw * hw,struct ieee80211_vif_chanctx_switch * vifs,int n_vifs,enum ieee80211_chanctx_switch_mode mode)4832750e9d15SEliad Peller wlcore_op_switch_vif_chanctx(struct ieee80211_hw *hw,
4833750e9d15SEliad Peller 			     struct ieee80211_vif_chanctx_switch *vifs,
4834750e9d15SEliad Peller 			     int n_vifs,
4835750e9d15SEliad Peller 			     enum ieee80211_chanctx_switch_mode mode)
4836750e9d15SEliad Peller {
4837750e9d15SEliad Peller 	struct wl1271 *wl = hw->priv;
4838750e9d15SEliad Peller 	int i, ret;
4839750e9d15SEliad Peller 
4840750e9d15SEliad Peller 	wl1271_debug(DEBUG_MAC80211,
4841750e9d15SEliad Peller 		     "mac80211 switch chanctx n_vifs %d mode %d",
4842750e9d15SEliad Peller 		     n_vifs, mode);
4843750e9d15SEliad Peller 
4844750e9d15SEliad Peller 	mutex_lock(&wl->mutex);
4845750e9d15SEliad Peller 
4846ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
4847ab589ac2SMinghao Chi 	if (ret < 0)
4848750e9d15SEliad Peller 		goto out;
4849750e9d15SEliad Peller 
4850750e9d15SEliad Peller 	for (i = 0; i < n_vifs; i++) {
4851750e9d15SEliad Peller 		struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vifs[i].vif);
4852750e9d15SEliad Peller 
4853750e9d15SEliad Peller 		ret = __wlcore_switch_vif_chan(wl, wlvif, vifs[i].new_ctx);
4854750e9d15SEliad Peller 		if (ret)
4855750e9d15SEliad Peller 			goto out_sleep;
4856750e9d15SEliad Peller 	}
4857750e9d15SEliad Peller out_sleep:
48589b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
48599b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
4860750e9d15SEliad Peller out:
4861750e9d15SEliad Peller 	mutex_unlock(&wl->mutex);
4862750e9d15SEliad Peller 
4863750e9d15SEliad Peller 	return 0;
4864b6970ee5SEliad Peller }
4865b6970ee5SEliad Peller 
wl1271_op_conf_tx(struct ieee80211_hw * hw,struct ieee80211_vif * vif,unsigned int link_id,u16 queue,const struct ieee80211_tx_queue_params * params)48667b3115f2SLuciano Coelho static int wl1271_op_conf_tx(struct ieee80211_hw *hw,
4867b3e2130bSJohannes Berg 			     struct ieee80211_vif *vif,
4868b3e2130bSJohannes Berg 			     unsigned int link_id, u16 queue,
48697b3115f2SLuciano Coelho 			     const struct ieee80211_tx_queue_params *params)
48707b3115f2SLuciano Coelho {
48717b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
48727b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
48737b3115f2SLuciano Coelho 	u8 ps_scheme;
48747b3115f2SLuciano Coelho 	int ret = 0;
48757b3115f2SLuciano Coelho 
48767845af35SEliad Peller 	if (wlcore_is_p2p_mgmt(wlvif))
48777845af35SEliad Peller 		return 0;
48787845af35SEliad Peller 
48797b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
48807b3115f2SLuciano Coelho 
48817b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 conf tx %d", queue);
48827b3115f2SLuciano Coelho 
48837b3115f2SLuciano Coelho 	if (params->uapsd)
48847b3115f2SLuciano Coelho 		ps_scheme = CONF_PS_SCHEME_UPSD_TRIGGER;
48857b3115f2SLuciano Coelho 	else
48867b3115f2SLuciano Coelho 		ps_scheme = CONF_PS_SCHEME_LEGACY;
48877b3115f2SLuciano Coelho 
48887b3115f2SLuciano Coelho 	if (!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags))
48897b3115f2SLuciano Coelho 		goto out;
48907b3115f2SLuciano Coelho 
4891ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
4892ab589ac2SMinghao Chi 	if (ret < 0)
48937b3115f2SLuciano Coelho 		goto out;
48947b3115f2SLuciano Coelho 
48957b3115f2SLuciano Coelho 	/*
48967b3115f2SLuciano Coelho 	 * the txop is confed in units of 32us by the mac80211,
48977b3115f2SLuciano Coelho 	 * we need us
48987b3115f2SLuciano Coelho 	 */
48997b3115f2SLuciano Coelho 	ret = wl1271_acx_ac_cfg(wl, wlvif, wl1271_tx_get_queue(queue),
49007b3115f2SLuciano Coelho 				params->cw_min, params->cw_max,
49017b3115f2SLuciano Coelho 				params->aifs, params->txop << 5);
49027b3115f2SLuciano Coelho 	if (ret < 0)
49037b3115f2SLuciano Coelho 		goto out_sleep;
49047b3115f2SLuciano Coelho 
49057b3115f2SLuciano Coelho 	ret = wl1271_acx_tid_cfg(wl, wlvif, wl1271_tx_get_queue(queue),
49067b3115f2SLuciano Coelho 				 CONF_CHANNEL_TYPE_EDCF,
49077b3115f2SLuciano Coelho 				 wl1271_tx_get_queue(queue),
49087b3115f2SLuciano Coelho 				 ps_scheme, CONF_ACK_POLICY_LEGACY,
49097b3115f2SLuciano Coelho 				 0, 0);
49107b3115f2SLuciano Coelho 
49117b3115f2SLuciano Coelho out_sleep:
49129b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
49139b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
49147b3115f2SLuciano Coelho 
49157b3115f2SLuciano Coelho out:
49167b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
49177b3115f2SLuciano Coelho 
49187b3115f2SLuciano Coelho 	return ret;
49197b3115f2SLuciano Coelho }
49207b3115f2SLuciano Coelho 
wl1271_op_get_tsf(struct ieee80211_hw * hw,struct ieee80211_vif * vif)49217b3115f2SLuciano Coelho static u64 wl1271_op_get_tsf(struct ieee80211_hw *hw,
49227b3115f2SLuciano Coelho 			     struct ieee80211_vif *vif)
49237b3115f2SLuciano Coelho {
49247b3115f2SLuciano Coelho 
49257b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
49267b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
49277b3115f2SLuciano Coelho 	u64 mactime = ULLONG_MAX;
49287b3115f2SLuciano Coelho 	int ret;
49297b3115f2SLuciano Coelho 
49307b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 get tsf");
49317b3115f2SLuciano Coelho 
49327b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
49337b3115f2SLuciano Coelho 
49344cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
49357b3115f2SLuciano Coelho 		goto out;
49367b3115f2SLuciano Coelho 
4937ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
4938ab589ac2SMinghao Chi 	if (ret < 0)
49397b3115f2SLuciano Coelho 		goto out;
49407b3115f2SLuciano Coelho 
49417b3115f2SLuciano Coelho 	ret = wl12xx_acx_tsf_info(wl, wlvif, &mactime);
49427b3115f2SLuciano Coelho 	if (ret < 0)
49437b3115f2SLuciano Coelho 		goto out_sleep;
49447b3115f2SLuciano Coelho 
49457b3115f2SLuciano Coelho out_sleep:
49469b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
49479b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
49487b3115f2SLuciano Coelho 
49497b3115f2SLuciano Coelho out:
49507b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
49517b3115f2SLuciano Coelho 	return mactime;
49527b3115f2SLuciano Coelho }
49537b3115f2SLuciano Coelho 
wl1271_op_get_survey(struct ieee80211_hw * hw,int idx,struct survey_info * survey)49547b3115f2SLuciano Coelho static int wl1271_op_get_survey(struct ieee80211_hw *hw, int idx,
49557b3115f2SLuciano Coelho 				struct survey_info *survey)
49567b3115f2SLuciano Coelho {
49577b3115f2SLuciano Coelho 	struct ieee80211_conf *conf = &hw->conf;
49587b3115f2SLuciano Coelho 
49597b3115f2SLuciano Coelho 	if (idx != 0)
49607b3115f2SLuciano Coelho 		return -ENOENT;
49617b3115f2SLuciano Coelho 
4962675a0b04SKarl Beldan 	survey->channel = conf->chandef.chan;
4963add779a0SYoni Divinsky 	survey->filled = 0;
49647b3115f2SLuciano Coelho 	return 0;
49657b3115f2SLuciano Coelho }
49667b3115f2SLuciano Coelho 
wl1271_allocate_sta(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_sta * sta)49677b3115f2SLuciano Coelho static int wl1271_allocate_sta(struct wl1271 *wl,
49687b3115f2SLuciano Coelho 			     struct wl12xx_vif *wlvif,
49697b3115f2SLuciano Coelho 			     struct ieee80211_sta *sta)
49707b3115f2SLuciano Coelho {
49717b3115f2SLuciano Coelho 	struct wl1271_station *wl_sta;
49727b3115f2SLuciano Coelho 	int ret;
49737b3115f2SLuciano Coelho 
49747b3115f2SLuciano Coelho 
497532f0fd5bSEliad Peller 	if (wl->active_sta_count >= wl->max_ap_stations) {
49767b3115f2SLuciano Coelho 		wl1271_warning("could not allocate HLID - too much stations");
49777b3115f2SLuciano Coelho 		return -EBUSY;
49787b3115f2SLuciano Coelho 	}
49797b3115f2SLuciano Coelho 
49807b3115f2SLuciano Coelho 	wl_sta = (struct wl1271_station *)sta->drv_priv;
49817b3115f2SLuciano Coelho 	ret = wl12xx_allocate_link(wl, wlvif, &wl_sta->hlid);
49827b3115f2SLuciano Coelho 	if (ret < 0) {
49837b3115f2SLuciano Coelho 		wl1271_warning("could not allocate HLID - too many links");
49847b3115f2SLuciano Coelho 		return -EBUSY;
49857b3115f2SLuciano Coelho 	}
49867b3115f2SLuciano Coelho 
49870e752df6SArik Nemtsov 	/* use the previous security seq, if this is a recovery/resume */
49880e752df6SArik Nemtsov 	wl->links[wl_sta->hlid].total_freed_pkts = wl_sta->total_freed_pkts;
49890e752df6SArik Nemtsov 
49907b3115f2SLuciano Coelho 	set_bit(wl_sta->hlid, wlvif->ap.sta_hlid_map);
49917b3115f2SLuciano Coelho 	memcpy(wl->links[wl_sta->hlid].addr, sta->addr, ETH_ALEN);
49927b3115f2SLuciano Coelho 	wl->active_sta_count++;
49937b3115f2SLuciano Coelho 	return 0;
49947b3115f2SLuciano Coelho }
49957b3115f2SLuciano Coelho 
wl1271_free_sta(struct wl1271 * wl,struct wl12xx_vif * wlvif,u8 hlid)49967b3115f2SLuciano Coelho void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid)
49977b3115f2SLuciano Coelho {
49987b3115f2SLuciano Coelho 	if (!test_bit(hlid, wlvif->ap.sta_hlid_map))
49997b3115f2SLuciano Coelho 		return;
50007b3115f2SLuciano Coelho 
50017b3115f2SLuciano Coelho 	clear_bit(hlid, wlvif->ap.sta_hlid_map);
50027b3115f2SLuciano Coelho 	__clear_bit(hlid, &wl->ap_ps_map);
50035e74b3aaSEliad Peller 	__clear_bit(hlid, &wl->ap_fw_ps_map);
50040e752df6SArik Nemtsov 
50050e752df6SArik Nemtsov 	/*
50060e752df6SArik Nemtsov 	 * save the last used PN in the private part of iee80211_sta,
50070e752df6SArik Nemtsov 	 * in case of recovery/suspend
50080e752df6SArik Nemtsov 	 */
500950d26aa3SEliad Peller 	wlcore_save_freed_pkts_addr(wl, wlvif, hlid, wl->links[hlid].addr);
50100e752df6SArik Nemtsov 
50117b3115f2SLuciano Coelho 	wl12xx_free_link(wl, wlvif, &hlid);
50127b3115f2SLuciano Coelho 	wl->active_sta_count--;
50137b3115f2SLuciano Coelho 
50147b3115f2SLuciano Coelho 	/*
50157b3115f2SLuciano Coelho 	 * rearm the tx watchdog when the last STA is freed - give the FW a
50167b3115f2SLuciano Coelho 	 * chance to return STA-buffered packets before complaining.
50177b3115f2SLuciano Coelho 	 */
50187b3115f2SLuciano Coelho 	if (wl->active_sta_count == 0)
50197b3115f2SLuciano Coelho 		wl12xx_rearm_tx_watchdog_locked(wl);
50207b3115f2SLuciano Coelho }
50217b3115f2SLuciano Coelho 
wl12xx_sta_add(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_sta * sta)50227b3115f2SLuciano Coelho static int wl12xx_sta_add(struct wl1271 *wl,
50237b3115f2SLuciano Coelho 			  struct wl12xx_vif *wlvif,
50247b3115f2SLuciano Coelho 			  struct ieee80211_sta *sta)
50257b3115f2SLuciano Coelho {
50267b3115f2SLuciano Coelho 	struct wl1271_station *wl_sta;
50277b3115f2SLuciano Coelho 	int ret = 0;
50287b3115f2SLuciano Coelho 	u8 hlid;
50297b3115f2SLuciano Coelho 
50307b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 add sta %d", (int)sta->aid);
50317b3115f2SLuciano Coelho 
50327b3115f2SLuciano Coelho 	ret = wl1271_allocate_sta(wl, wlvif, sta);
50337b3115f2SLuciano Coelho 	if (ret < 0)
50347b3115f2SLuciano Coelho 		return ret;
50357b3115f2SLuciano Coelho 
50367b3115f2SLuciano Coelho 	wl_sta = (struct wl1271_station *)sta->drv_priv;
50377b3115f2SLuciano Coelho 	hlid = wl_sta->hlid;
50387b3115f2SLuciano Coelho 
50397b3115f2SLuciano Coelho 	ret = wl12xx_cmd_add_peer(wl, wlvif, sta, hlid);
50407b3115f2SLuciano Coelho 	if (ret < 0)
50417b3115f2SLuciano Coelho 		wl1271_free_sta(wl, wlvif, hlid);
50427b3115f2SLuciano Coelho 
50437b3115f2SLuciano Coelho 	return ret;
50447b3115f2SLuciano Coelho }
50457b3115f2SLuciano Coelho 
wl12xx_sta_remove(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_sta * sta)50467b3115f2SLuciano Coelho static int wl12xx_sta_remove(struct wl1271 *wl,
50477b3115f2SLuciano Coelho 			     struct wl12xx_vif *wlvif,
50487b3115f2SLuciano Coelho 			     struct ieee80211_sta *sta)
50497b3115f2SLuciano Coelho {
50507b3115f2SLuciano Coelho 	struct wl1271_station *wl_sta;
50517b3115f2SLuciano Coelho 	int ret = 0, id;
50527b3115f2SLuciano Coelho 
50537b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 remove sta %d", (int)sta->aid);
50547b3115f2SLuciano Coelho 
50557b3115f2SLuciano Coelho 	wl_sta = (struct wl1271_station *)sta->drv_priv;
50567b3115f2SLuciano Coelho 	id = wl_sta->hlid;
50577b3115f2SLuciano Coelho 	if (WARN_ON(!test_bit(id, wlvif->ap.sta_hlid_map)))
50587b3115f2SLuciano Coelho 		return -EINVAL;
50597b3115f2SLuciano Coelho 
5060028e7243SEliad Peller 	ret = wl12xx_cmd_remove_peer(wl, wlvif, wl_sta->hlid);
50617b3115f2SLuciano Coelho 	if (ret < 0)
50627b3115f2SLuciano Coelho 		return ret;
50637b3115f2SLuciano Coelho 
50647b3115f2SLuciano Coelho 	wl1271_free_sta(wl, wlvif, wl_sta->hlid);
50657b3115f2SLuciano Coelho 	return ret;
50667b3115f2SLuciano Coelho }
50677b3115f2SLuciano Coelho 
wlcore_roc_if_possible(struct wl1271 * wl,struct wl12xx_vif * wlvif)5068426001a6SEliad Peller static void wlcore_roc_if_possible(struct wl1271 *wl,
5069426001a6SEliad Peller 				   struct wl12xx_vif *wlvif)
5070426001a6SEliad Peller {
5071426001a6SEliad Peller 	if (find_first_bit(wl->roc_map,
5072426001a6SEliad Peller 			   WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES)
5073426001a6SEliad Peller 		return;
5074426001a6SEliad Peller 
5075426001a6SEliad Peller 	if (WARN_ON(wlvif->role_id == WL12XX_INVALID_ROLE_ID))
5076426001a6SEliad Peller 		return;
5077426001a6SEliad Peller 
5078426001a6SEliad Peller 	wl12xx_roc(wl, wlvif, wlvif->role_id, wlvif->band, wlvif->channel);
5079426001a6SEliad Peller }
5080426001a6SEliad Peller 
5081187e52ccSArik Nemtsov /*
5082187e52ccSArik Nemtsov  * when wl_sta is NULL, we treat this call as if coming from a
5083187e52ccSArik Nemtsov  * pending auth reply.
5084187e52ccSArik Nemtsov  * wl->mutex must be taken and the FW must be awake when the call
5085187e52ccSArik Nemtsov  * takes place.
5086187e52ccSArik Nemtsov  */
wlcore_update_inconn_sta(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct wl1271_station * wl_sta,bool in_conn)5087187e52ccSArik Nemtsov void wlcore_update_inconn_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif,
5088187e52ccSArik Nemtsov 			      struct wl1271_station *wl_sta, bool in_conn)
5089426001a6SEliad Peller {
5090187e52ccSArik Nemtsov 	if (in_conn) {
5091187e52ccSArik Nemtsov 		if (WARN_ON(wl_sta && wl_sta->in_connection))
5092426001a6SEliad Peller 			return;
5093426001a6SEliad Peller 
5094187e52ccSArik Nemtsov 		if (!wlvif->ap_pending_auth_reply &&
5095187e52ccSArik Nemtsov 		    !wlvif->inconn_count)
5096187e52ccSArik Nemtsov 			wlcore_roc_if_possible(wl, wlvif);
5097187e52ccSArik Nemtsov 
5098187e52ccSArik Nemtsov 		if (wl_sta) {
5099187e52ccSArik Nemtsov 			wl_sta->in_connection = true;
5100187e52ccSArik Nemtsov 			wlvif->inconn_count++;
5101187e52ccSArik Nemtsov 		} else {
5102187e52ccSArik Nemtsov 			wlvif->ap_pending_auth_reply = true;
5103187e52ccSArik Nemtsov 		}
5104187e52ccSArik Nemtsov 	} else {
5105187e52ccSArik Nemtsov 		if (wl_sta && !wl_sta->in_connection)
5106187e52ccSArik Nemtsov 			return;
5107187e52ccSArik Nemtsov 
5108187e52ccSArik Nemtsov 		if (WARN_ON(!wl_sta && !wlvif->ap_pending_auth_reply))
5109187e52ccSArik Nemtsov 			return;
5110187e52ccSArik Nemtsov 
5111187e52ccSArik Nemtsov 		if (WARN_ON(wl_sta && !wlvif->inconn_count))
5112187e52ccSArik Nemtsov 			return;
5113187e52ccSArik Nemtsov 
5114187e52ccSArik Nemtsov 		if (wl_sta) {
5115426001a6SEliad Peller 			wl_sta->in_connection = false;
5116426001a6SEliad Peller 			wlvif->inconn_count--;
5117187e52ccSArik Nemtsov 		} else {
5118187e52ccSArik Nemtsov 			wlvif->ap_pending_auth_reply = false;
5119187e52ccSArik Nemtsov 		}
5120426001a6SEliad Peller 
5121187e52ccSArik Nemtsov 		if (!wlvif->inconn_count && !wlvif->ap_pending_auth_reply &&
5122187e52ccSArik Nemtsov 		    test_bit(wlvif->role_id, wl->roc_map))
5123426001a6SEliad Peller 			wl12xx_croc(wl, wlvif->role_id);
5124426001a6SEliad Peller 	}
5125426001a6SEliad Peller }
5126426001a6SEliad Peller 
wl12xx_update_sta_state(struct wl1271 * wl,struct wl12xx_vif * wlvif,struct ieee80211_sta * sta,enum ieee80211_sta_state old_state,enum ieee80211_sta_state new_state)51277b3115f2SLuciano Coelho static int wl12xx_update_sta_state(struct wl1271 *wl,
51287b3115f2SLuciano Coelho 				   struct wl12xx_vif *wlvif,
51297b3115f2SLuciano Coelho 				   struct ieee80211_sta *sta,
51307b3115f2SLuciano Coelho 				   enum ieee80211_sta_state old_state,
51317b3115f2SLuciano Coelho 				   enum ieee80211_sta_state new_state)
51327b3115f2SLuciano Coelho {
51337b3115f2SLuciano Coelho 	struct wl1271_station *wl_sta;
51347b3115f2SLuciano Coelho 	bool is_ap = wlvif->bss_type == BSS_TYPE_AP_BSS;
51357b3115f2SLuciano Coelho 	bool is_sta = wlvif->bss_type == BSS_TYPE_STA_BSS;
51367b3115f2SLuciano Coelho 	int ret;
51377b3115f2SLuciano Coelho 
51387b3115f2SLuciano Coelho 	wl_sta = (struct wl1271_station *)sta->drv_priv;
51397b3115f2SLuciano Coelho 
51407b3115f2SLuciano Coelho 	/* Add station (AP mode) */
51417b3115f2SLuciano Coelho 	if (is_ap &&
51427b3115f2SLuciano Coelho 	    old_state == IEEE80211_STA_NOTEXIST &&
514329936266SEliad Peller 	    new_state == IEEE80211_STA_NONE) {
514429936266SEliad Peller 		ret = wl12xx_sta_add(wl, wlvif, sta);
514529936266SEliad Peller 		if (ret)
514629936266SEliad Peller 			return ret;
5147426001a6SEliad Peller 
5148426001a6SEliad Peller 		wlcore_update_inconn_sta(wl, wlvif, wl_sta, true);
514929936266SEliad Peller 	}
51507b3115f2SLuciano Coelho 
51517b3115f2SLuciano Coelho 	/* Remove station (AP mode) */
51527b3115f2SLuciano Coelho 	if (is_ap &&
51537b3115f2SLuciano Coelho 	    old_state == IEEE80211_STA_NONE &&
51547b3115f2SLuciano Coelho 	    new_state == IEEE80211_STA_NOTEXIST) {
51557b3115f2SLuciano Coelho 		/* must not fail */
51567b3115f2SLuciano Coelho 		wl12xx_sta_remove(wl, wlvif, sta);
5157426001a6SEliad Peller 
5158426001a6SEliad Peller 		wlcore_update_inconn_sta(wl, wlvif, wl_sta, false);
51597b3115f2SLuciano Coelho 	}
51607b3115f2SLuciano Coelho 
51617b3115f2SLuciano Coelho 	/* Authorize station (AP mode) */
51627b3115f2SLuciano Coelho 	if (is_ap &&
51637b3115f2SLuciano Coelho 	    new_state == IEEE80211_STA_AUTHORIZED) {
51642fec3d27SArik Nemtsov 		ret = wl12xx_cmd_set_peer_state(wl, wlvif, wl_sta->hlid);
51657b3115f2SLuciano Coelho 		if (ret < 0)
51667b3115f2SLuciano Coelho 			return ret;
51677b3115f2SLuciano Coelho 
5168535633a5SGuy Mishol 		/* reconfigure rates */
5169535633a5SGuy Mishol 		ret = wl12xx_cmd_add_peer(wl, wlvif, sta, wl_sta->hlid);
5170535633a5SGuy Mishol 		if (ret < 0)
5171535633a5SGuy Mishol 			return ret;
5172535633a5SGuy Mishol 
5173046d2e7cSSriram R 		ret = wl1271_acx_set_ht_capabilities(wl, &sta->deflink.ht_cap,
5174046d2e7cSSriram R 						     true,
51752fec3d27SArik Nemtsov 						     wl_sta->hlid);
517629936266SEliad Peller 		if (ret)
51777b3115f2SLuciano Coelho 			return ret;
5178426001a6SEliad Peller 
5179426001a6SEliad Peller 		wlcore_update_inconn_sta(wl, wlvif, wl_sta, false);
51807b3115f2SLuciano Coelho 	}
51817b3115f2SLuciano Coelho 
51827b3115f2SLuciano Coelho 	/* Authorize station */
51837b3115f2SLuciano Coelho 	if (is_sta &&
51847b3115f2SLuciano Coelho 	    new_state == IEEE80211_STA_AUTHORIZED) {
51857b3115f2SLuciano Coelho 		set_bit(WLVIF_FLAG_STA_AUTHORIZED, &wlvif->flags);
518629936266SEliad Peller 		ret = wl12xx_set_authorized(wl, wlvif);
518729936266SEliad Peller 		if (ret)
518829936266SEliad Peller 			return ret;
51897b3115f2SLuciano Coelho 	}
51907b3115f2SLuciano Coelho 
51917b3115f2SLuciano Coelho 	if (is_sta &&
51927b3115f2SLuciano Coelho 	    old_state == IEEE80211_STA_AUTHORIZED &&
51937b3115f2SLuciano Coelho 	    new_state == IEEE80211_STA_ASSOC) {
51947b3115f2SLuciano Coelho 		clear_bit(WLVIF_FLAG_STA_AUTHORIZED, &wlvif->flags);
51953230f35eSEliad Peller 		clear_bit(WLVIF_FLAG_STA_STATE_SENT, &wlvif->flags);
51967b3115f2SLuciano Coelho 	}
51977b3115f2SLuciano Coelho 
519850d26aa3SEliad Peller 	/* save seq number on disassoc (suspend) */
519950d26aa3SEliad Peller 	if (is_sta &&
520050d26aa3SEliad Peller 	    old_state == IEEE80211_STA_ASSOC &&
520150d26aa3SEliad Peller 	    new_state == IEEE80211_STA_AUTH) {
520250d26aa3SEliad Peller 		wlcore_save_freed_pkts(wl, wlvif, wlvif->sta.hlid, sta);
520350d26aa3SEliad Peller 		wlvif->total_freed_pkts = 0;
520450d26aa3SEliad Peller 	}
520550d26aa3SEliad Peller 
520650d26aa3SEliad Peller 	/* restore seq number on assoc (resume) */
520750d26aa3SEliad Peller 	if (is_sta &&
520850d26aa3SEliad Peller 	    old_state == IEEE80211_STA_AUTH &&
520950d26aa3SEliad Peller 	    new_state == IEEE80211_STA_ASSOC) {
521050d26aa3SEliad Peller 		wlvif->total_freed_pkts = wl_sta->total_freed_pkts;
521150d26aa3SEliad Peller 	}
521250d26aa3SEliad Peller 
521329936266SEliad Peller 	/* clear ROCs on failure or authorization */
521429936266SEliad Peller 	if (is_sta &&
521529936266SEliad Peller 	    (new_state == IEEE80211_STA_AUTHORIZED ||
521629936266SEliad Peller 	     new_state == IEEE80211_STA_NOTEXIST)) {
521729936266SEliad Peller 		if (test_bit(wlvif->role_id, wl->roc_map))
521829936266SEliad Peller 			wl12xx_croc(wl, wlvif->role_id);
521929936266SEliad Peller 	}
522029936266SEliad Peller 
522129936266SEliad Peller 	if (is_sta &&
522229936266SEliad Peller 	    old_state == IEEE80211_STA_NOTEXIST &&
522329936266SEliad Peller 	    new_state == IEEE80211_STA_NONE) {
522429936266SEliad Peller 		if (find_first_bit(wl->roc_map,
522529936266SEliad Peller 				   WL12XX_MAX_ROLES) >= WL12XX_MAX_ROLES) {
522629936266SEliad Peller 			WARN_ON(wlvif->role_id == WL12XX_INVALID_ROLE_ID);
522729936266SEliad Peller 			wl12xx_roc(wl, wlvif, wlvif->role_id,
522829936266SEliad Peller 				   wlvif->band, wlvif->channel);
522929936266SEliad Peller 		}
523029936266SEliad Peller 	}
52317b3115f2SLuciano Coelho 	return 0;
52327b3115f2SLuciano Coelho }
52337b3115f2SLuciano Coelho 
wl12xx_op_sta_state(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_sta * sta,enum ieee80211_sta_state old_state,enum ieee80211_sta_state new_state)52347b3115f2SLuciano Coelho static int wl12xx_op_sta_state(struct ieee80211_hw *hw,
52357b3115f2SLuciano Coelho 			       struct ieee80211_vif *vif,
52367b3115f2SLuciano Coelho 			       struct ieee80211_sta *sta,
52377b3115f2SLuciano Coelho 			       enum ieee80211_sta_state old_state,
52387b3115f2SLuciano Coelho 			       enum ieee80211_sta_state new_state)
52397b3115f2SLuciano Coelho {
52407b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
52417b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
52427b3115f2SLuciano Coelho 	int ret;
52437b3115f2SLuciano Coelho 
52447b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 sta %d state=%d->%d",
52457b3115f2SLuciano Coelho 		     sta->aid, old_state, new_state);
52467b3115f2SLuciano Coelho 
52477b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
52487b3115f2SLuciano Coelho 
52494cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
52507b3115f2SLuciano Coelho 		ret = -EBUSY;
52517b3115f2SLuciano Coelho 		goto out;
52527b3115f2SLuciano Coelho 	}
52537b3115f2SLuciano Coelho 
5254ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
5255ab589ac2SMinghao Chi 	if (ret < 0)
52567b3115f2SLuciano Coelho 		goto out;
52577b3115f2SLuciano Coelho 
52587b3115f2SLuciano Coelho 	ret = wl12xx_update_sta_state(wl, wlvif, sta, old_state, new_state);
52597b3115f2SLuciano Coelho 
52609b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
52619b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
52627b3115f2SLuciano Coelho out:
52637b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
52647b3115f2SLuciano Coelho 	if (new_state < old_state)
52657b3115f2SLuciano Coelho 		return 0;
52667b3115f2SLuciano Coelho 	return ret;
52677b3115f2SLuciano Coelho }
52687b3115f2SLuciano Coelho 
wl1271_op_ampdu_action(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_ampdu_params * params)52697b3115f2SLuciano Coelho static int wl1271_op_ampdu_action(struct ieee80211_hw *hw,
52707b3115f2SLuciano Coelho 				  struct ieee80211_vif *vif,
527150ea05efSSara Sharon 				  struct ieee80211_ampdu_params *params)
52727b3115f2SLuciano Coelho {
52737b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
52747b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
52757b3115f2SLuciano Coelho 	int ret;
52767b3115f2SLuciano Coelho 	u8 hlid, *ba_bitmap;
527750ea05efSSara Sharon 	struct ieee80211_sta *sta = params->sta;
527850ea05efSSara Sharon 	enum ieee80211_ampdu_mlme_action action = params->action;
527950ea05efSSara Sharon 	u16 tid = params->tid;
528050ea05efSSara Sharon 	u16 *ssn = &params->ssn;
52817b3115f2SLuciano Coelho 
52827b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 ampdu action %d tid %d", action,
52837b3115f2SLuciano Coelho 		     tid);
52847b3115f2SLuciano Coelho 
52857b3115f2SLuciano Coelho 	/* sanity check - the fields in FW are only 8bits wide */
52867b3115f2SLuciano Coelho 	if (WARN_ON(tid > 0xFF))
52877b3115f2SLuciano Coelho 		return -ENOTSUPP;
52887b3115f2SLuciano Coelho 
52897b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
52907b3115f2SLuciano Coelho 
52914cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
52927b3115f2SLuciano Coelho 		ret = -EAGAIN;
52937b3115f2SLuciano Coelho 		goto out;
52947b3115f2SLuciano Coelho 	}
52957b3115f2SLuciano Coelho 
52967b3115f2SLuciano Coelho 	if (wlvif->bss_type == BSS_TYPE_STA_BSS) {
52977b3115f2SLuciano Coelho 		hlid = wlvif->sta.hlid;
52987b3115f2SLuciano Coelho 	} else if (wlvif->bss_type == BSS_TYPE_AP_BSS) {
52997b3115f2SLuciano Coelho 		struct wl1271_station *wl_sta;
53007b3115f2SLuciano Coelho 
53017b3115f2SLuciano Coelho 		wl_sta = (struct wl1271_station *)sta->drv_priv;
53027b3115f2SLuciano Coelho 		hlid = wl_sta->hlid;
53037b3115f2SLuciano Coelho 	} else {
53047b3115f2SLuciano Coelho 		ret = -EINVAL;
53057b3115f2SLuciano Coelho 		goto out;
53067b3115f2SLuciano Coelho 	}
53077b3115f2SLuciano Coelho 
53089ae5d8d4SArik Nemtsov 	ba_bitmap = &wl->links[hlid].ba_bitmap;
53099ae5d8d4SArik Nemtsov 
5310ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
5311ab589ac2SMinghao Chi 	if (ret < 0)
53127b3115f2SLuciano Coelho 		goto out;
53137b3115f2SLuciano Coelho 
53147b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 ampdu: Rx tid %d action %d",
53157b3115f2SLuciano Coelho 		     tid, action);
53167b3115f2SLuciano Coelho 
53177b3115f2SLuciano Coelho 	switch (action) {
53187b3115f2SLuciano Coelho 	case IEEE80211_AMPDU_RX_START:
53197b3115f2SLuciano Coelho 		if (!wlvif->ba_support || !wlvif->ba_allowed) {
53207b3115f2SLuciano Coelho 			ret = -ENOTSUPP;
53217b3115f2SLuciano Coelho 			break;
53227b3115f2SLuciano Coelho 		}
53237b3115f2SLuciano Coelho 
5324d21553f8SIgal Chernobelsky 		if (wl->ba_rx_session_count >= wl->ba_rx_session_count_max) {
53257b3115f2SLuciano Coelho 			ret = -EBUSY;
532607ceefa3STony Lindgren 			wl1271_debug(DEBUG_RX, "exceeded max RX BA sessions");
53277b3115f2SLuciano Coelho 			break;
53287b3115f2SLuciano Coelho 		}
53297b3115f2SLuciano Coelho 
53307b3115f2SLuciano Coelho 		if (*ba_bitmap & BIT(tid)) {
53317b3115f2SLuciano Coelho 			ret = -EINVAL;
53327b3115f2SLuciano Coelho 			wl1271_error("cannot enable RX BA session on active "
53337b3115f2SLuciano Coelho 				     "tid: %d", tid);
53347b3115f2SLuciano Coelho 			break;
53357b3115f2SLuciano Coelho 		}
53367b3115f2SLuciano Coelho 
53377b3115f2SLuciano Coelho 		ret = wl12xx_acx_set_ba_receiver_session(wl, tid, *ssn, true,
533842c7372aSMaxim Altshul 				hlid,
533942c7372aSMaxim Altshul 				params->buf_size);
534042c7372aSMaxim Altshul 
53417b3115f2SLuciano Coelho 		if (!ret) {
53427b3115f2SLuciano Coelho 			*ba_bitmap |= BIT(tid);
53437b3115f2SLuciano Coelho 			wl->ba_rx_session_count++;
53447b3115f2SLuciano Coelho 		}
53457b3115f2SLuciano Coelho 		break;
53467b3115f2SLuciano Coelho 
53477b3115f2SLuciano Coelho 	case IEEE80211_AMPDU_RX_STOP:
53487b3115f2SLuciano Coelho 		if (!(*ba_bitmap & BIT(tid))) {
5349c954910bSArik Nemtsov 			/*
5350c954910bSArik Nemtsov 			 * this happens on reconfig - so only output a debug
5351c954910bSArik Nemtsov 			 * message for now, and don't fail the function.
5352c954910bSArik Nemtsov 			 */
5353c954910bSArik Nemtsov 			wl1271_debug(DEBUG_MAC80211,
5354c954910bSArik Nemtsov 				     "no active RX BA session on tid: %d",
53557b3115f2SLuciano Coelho 				     tid);
5356c954910bSArik Nemtsov 			ret = 0;
53577b3115f2SLuciano Coelho 			break;
53587b3115f2SLuciano Coelho 		}
53597b3115f2SLuciano Coelho 
53607b3115f2SLuciano Coelho 		ret = wl12xx_acx_set_ba_receiver_session(wl, tid, 0, false,
536142c7372aSMaxim Altshul 							 hlid, 0);
53627b3115f2SLuciano Coelho 		if (!ret) {
53637b3115f2SLuciano Coelho 			*ba_bitmap &= ~BIT(tid);
53647b3115f2SLuciano Coelho 			wl->ba_rx_session_count--;
53657b3115f2SLuciano Coelho 		}
53667b3115f2SLuciano Coelho 		break;
53677b3115f2SLuciano Coelho 
53687b3115f2SLuciano Coelho 	/*
53697b3115f2SLuciano Coelho 	 * The BA initiator session management in FW independently.
53707b3115f2SLuciano Coelho 	 * Falling break here on purpose for all TX APDU commands.
53717b3115f2SLuciano Coelho 	 */
53727b3115f2SLuciano Coelho 	case IEEE80211_AMPDU_TX_START:
537318b559d5SJohannes Berg 	case IEEE80211_AMPDU_TX_STOP_CONT:
537418b559d5SJohannes Berg 	case IEEE80211_AMPDU_TX_STOP_FLUSH:
537518b559d5SJohannes Berg 	case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
53767b3115f2SLuciano Coelho 	case IEEE80211_AMPDU_TX_OPERATIONAL:
53777b3115f2SLuciano Coelho 		ret = -EINVAL;
53787b3115f2SLuciano Coelho 		break;
53797b3115f2SLuciano Coelho 
53807b3115f2SLuciano Coelho 	default:
53817b3115f2SLuciano Coelho 		wl1271_error("Incorrect ampdu action id=%x\n", action);
53827b3115f2SLuciano Coelho 		ret = -EINVAL;
53837b3115f2SLuciano Coelho 	}
53847b3115f2SLuciano Coelho 
53859b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
53869b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
53877b3115f2SLuciano Coelho 
53887b3115f2SLuciano Coelho out:
53897b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
53907b3115f2SLuciano Coelho 
53917b3115f2SLuciano Coelho 	return ret;
53927b3115f2SLuciano Coelho }
53937b3115f2SLuciano Coelho 
wl12xx_set_bitrate_mask(struct ieee80211_hw * hw,struct ieee80211_vif * vif,const struct cfg80211_bitrate_mask * mask)53947b3115f2SLuciano Coelho static int wl12xx_set_bitrate_mask(struct ieee80211_hw *hw,
53957b3115f2SLuciano Coelho 				   struct ieee80211_vif *vif,
53967b3115f2SLuciano Coelho 				   const struct cfg80211_bitrate_mask *mask)
53977b3115f2SLuciano Coelho {
53987b3115f2SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
53997b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
54007b3115f2SLuciano Coelho 	int i, ret = 0;
54017b3115f2SLuciano Coelho 
54027b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 set_bitrate_mask 0x%x 0x%x",
54037b3115f2SLuciano Coelho 		mask->control[NL80211_BAND_2GHZ].legacy,
54047b3115f2SLuciano Coelho 		mask->control[NL80211_BAND_5GHZ].legacy);
54057b3115f2SLuciano Coelho 
54067b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
54077b3115f2SLuciano Coelho 
5408091185d6SArik Nemtsov 	for (i = 0; i < WLCORE_NUM_BANDS; i++)
54097b3115f2SLuciano Coelho 		wlvif->bitrate_masks[i] =
54107b3115f2SLuciano Coelho 			wl1271_tx_enabled_rates_get(wl,
54117b3115f2SLuciano Coelho 						    mask->control[i].legacy,
54127b3115f2SLuciano Coelho 						    i);
54137b3115f2SLuciano Coelho 
54144cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
54157b3115f2SLuciano Coelho 		goto out;
54167b3115f2SLuciano Coelho 
54177b3115f2SLuciano Coelho 	if (wlvif->bss_type == BSS_TYPE_STA_BSS &&
54187b3115f2SLuciano Coelho 	    !test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) {
54197b3115f2SLuciano Coelho 
5420ab589ac2SMinghao Chi 		ret = pm_runtime_resume_and_get(wl->dev);
5421ab589ac2SMinghao Chi 		if (ret < 0)
54227b3115f2SLuciano Coelho 			goto out;
54237b3115f2SLuciano Coelho 
54247b3115f2SLuciano Coelho 		wl1271_set_band_rate(wl, wlvif);
54257b3115f2SLuciano Coelho 		wlvif->basic_rate =
54267b3115f2SLuciano Coelho 			wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set);
54277b3115f2SLuciano Coelho 		ret = wl1271_acx_sta_rate_policies(wl, wlvif);
54287b3115f2SLuciano Coelho 
54299b71578dSTony Lindgren 		pm_runtime_mark_last_busy(wl->dev);
54309b71578dSTony Lindgren 		pm_runtime_put_autosuspend(wl->dev);
54317b3115f2SLuciano Coelho 	}
54327b3115f2SLuciano Coelho out:
54337b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
54347b3115f2SLuciano Coelho 
54357b3115f2SLuciano Coelho 	return ret;
54367b3115f2SLuciano Coelho }
54377b3115f2SLuciano Coelho 
wl12xx_op_channel_switch(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_channel_switch * ch_switch)54387b3115f2SLuciano Coelho static void wl12xx_op_channel_switch(struct ieee80211_hw *hw,
54390f791eb4SLuciano Coelho 				     struct ieee80211_vif *vif,
54407b3115f2SLuciano Coelho 				     struct ieee80211_channel_switch *ch_switch)
54417b3115f2SLuciano Coelho {
54427b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
54430f791eb4SLuciano Coelho 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
54447b3115f2SLuciano Coelho 	int ret;
54457b3115f2SLuciano Coelho 
54467b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_MAC80211, "mac80211 channel switch");
54477b3115f2SLuciano Coelho 
54487b3115f2SLuciano Coelho 	wl1271_tx_flush(wl);
54497b3115f2SLuciano Coelho 
54507b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
54517b3115f2SLuciano Coelho 
54524cc53383SIdo Yariv 	if (unlikely(wl->state == WLCORE_STATE_OFF)) {
54530f791eb4SLuciano Coelho 		if (test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
54547b3115f2SLuciano Coelho 			ieee80211_chswitch_done(vif, false);
54557b3115f2SLuciano Coelho 		goto out;
54564cc53383SIdo Yariv 	} else if (unlikely(wl->state != WLCORE_STATE_ON)) {
54574cc53383SIdo Yariv 		goto out;
54587b3115f2SLuciano Coelho 	}
54597b3115f2SLuciano Coelho 
5460ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
5461ab589ac2SMinghao Chi 	if (ret < 0)
54627b3115f2SLuciano Coelho 		goto out;
54637b3115f2SLuciano Coelho 
54647b3115f2SLuciano Coelho 	/* TODO: change mac80211 to pass vif as param */
54657b3115f2SLuciano Coelho 
54660f791eb4SLuciano Coelho 	if (test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) {
54670f791eb4SLuciano Coelho 		unsigned long delay_usec;
5468e6562869SArik Nemtsov 
5469fcab1890SEliad Peller 		ret = wl->ops->channel_switch(wl, wlvif, ch_switch);
5470c50a2825SEliad Peller 		if (ret)
5471c50a2825SEliad Peller 			goto out_sleep;
5472c50a2825SEliad Peller 
54737b3115f2SLuciano Coelho 		set_bit(WLVIF_FLAG_CS_PROGRESS, &wlvif->flags);
5474c50a2825SEliad Peller 
5475c50a2825SEliad Peller 		/* indicate failure 5 seconds after channel switch time */
5476c50a2825SEliad Peller 		delay_usec = ieee80211_tu_to_usec(wlvif->beacon_int) *
5477c50a2825SEliad Peller 			ch_switch->count;
5478c50a2825SEliad Peller 		ieee80211_queue_delayed_work(hw, &wlvif->channel_switch_work,
5479c50a2825SEliad Peller 					     usecs_to_jiffies(delay_usec) +
5480c50a2825SEliad Peller 					     msecs_to_jiffies(5000));
54817b3115f2SLuciano Coelho 	}
54827b3115f2SLuciano Coelho 
5483c50a2825SEliad Peller out_sleep:
54849b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
54859b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
54867b3115f2SLuciano Coelho 
54877b3115f2SLuciano Coelho out:
54887b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
54897b3115f2SLuciano Coelho }
54907b3115f2SLuciano Coelho 
wlcore_get_beacon_ie(struct wl1271 * wl,struct wl12xx_vif * wlvif,u8 eid)5491534719f4SEliad Peller static const void *wlcore_get_beacon_ie(struct wl1271 *wl,
5492534719f4SEliad Peller 					struct wl12xx_vif *wlvif,
5493534719f4SEliad Peller 					u8 eid)
5494534719f4SEliad Peller {
5495534719f4SEliad Peller 	int ieoffset = offsetof(struct ieee80211_mgmt, u.beacon.variable);
5496534719f4SEliad Peller 	struct sk_buff *beacon =
54976e8912a5SShaul Triebitz 		ieee80211_beacon_get(wl->hw, wl12xx_wlvif_to_vif(wlvif), 0);
5498534719f4SEliad Peller 
5499534719f4SEliad Peller 	if (!beacon)
5500534719f4SEliad Peller 		return NULL;
5501534719f4SEliad Peller 
5502534719f4SEliad Peller 	return cfg80211_find_ie(eid,
5503534719f4SEliad Peller 				beacon->data + ieoffset,
5504534719f4SEliad Peller 				beacon->len - ieoffset);
5505534719f4SEliad Peller }
5506534719f4SEliad Peller 
wlcore_get_csa_count(struct wl1271 * wl,struct wl12xx_vif * wlvif,u8 * csa_count)5507534719f4SEliad Peller static int wlcore_get_csa_count(struct wl1271 *wl, struct wl12xx_vif *wlvif,
5508534719f4SEliad Peller 				u8 *csa_count)
5509534719f4SEliad Peller {
5510534719f4SEliad Peller 	const u8 *ie;
5511534719f4SEliad Peller 	const struct ieee80211_channel_sw_ie *ie_csa;
5512534719f4SEliad Peller 
5513534719f4SEliad Peller 	ie = wlcore_get_beacon_ie(wl, wlvif, WLAN_EID_CHANNEL_SWITCH);
5514534719f4SEliad Peller 	if (!ie)
5515534719f4SEliad Peller 		return -EINVAL;
5516534719f4SEliad Peller 
5517534719f4SEliad Peller 	ie_csa = (struct ieee80211_channel_sw_ie *)&ie[2];
5518534719f4SEliad Peller 	*csa_count = ie_csa->count;
5519534719f4SEliad Peller 
5520534719f4SEliad Peller 	return 0;
5521534719f4SEliad Peller }
5522534719f4SEliad Peller 
wlcore_op_channel_switch_beacon(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct cfg80211_chan_def * chandef)5523534719f4SEliad Peller static void wlcore_op_channel_switch_beacon(struct ieee80211_hw *hw,
5524534719f4SEliad Peller 					    struct ieee80211_vif *vif,
5525534719f4SEliad Peller 					    struct cfg80211_chan_def *chandef)
5526534719f4SEliad Peller {
5527534719f4SEliad Peller 	struct wl1271 *wl = hw->priv;
5528534719f4SEliad Peller 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
5529534719f4SEliad Peller 	struct ieee80211_channel_switch ch_switch = {
5530534719f4SEliad Peller 		.block_tx = true,
5531534719f4SEliad Peller 		.chandef = *chandef,
5532534719f4SEliad Peller 	};
5533534719f4SEliad Peller 	int ret;
5534534719f4SEliad Peller 
5535534719f4SEliad Peller 	wl1271_debug(DEBUG_MAC80211,
5536534719f4SEliad Peller 		     "mac80211 channel switch beacon (role %d)",
5537534719f4SEliad Peller 		     wlvif->role_id);
5538534719f4SEliad Peller 
5539534719f4SEliad Peller 	ret = wlcore_get_csa_count(wl, wlvif, &ch_switch.count);
5540534719f4SEliad Peller 	if (ret < 0) {
5541534719f4SEliad Peller 		wl1271_error("error getting beacon (for CSA counter)");
5542534719f4SEliad Peller 		return;
5543534719f4SEliad Peller 	}
5544534719f4SEliad Peller 
5545534719f4SEliad Peller 	mutex_lock(&wl->mutex);
5546534719f4SEliad Peller 
5547534719f4SEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
5548534719f4SEliad Peller 		ret = -EBUSY;
5549534719f4SEliad Peller 		goto out;
5550534719f4SEliad Peller 	}
5551534719f4SEliad Peller 
5552ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
5553ab589ac2SMinghao Chi 	if (ret < 0)
5554534719f4SEliad Peller 		goto out;
5555534719f4SEliad Peller 
5556534719f4SEliad Peller 	ret = wl->ops->channel_switch(wl, wlvif, &ch_switch);
5557534719f4SEliad Peller 	if (ret)
5558534719f4SEliad Peller 		goto out_sleep;
5559534719f4SEliad Peller 
5560534719f4SEliad Peller 	set_bit(WLVIF_FLAG_CS_PROGRESS, &wlvif->flags);
5561534719f4SEliad Peller 
5562534719f4SEliad Peller out_sleep:
55639b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
55649b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
5565534719f4SEliad Peller out:
5566534719f4SEliad Peller 	mutex_unlock(&wl->mutex);
5567534719f4SEliad Peller }
5568534719f4SEliad Peller 
wlcore_op_flush(struct ieee80211_hw * hw,struct ieee80211_vif * vif,u32 queues,bool drop)556977be2c54SEmmanuel Grumbach static void wlcore_op_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
557077be2c54SEmmanuel Grumbach 			    u32 queues, bool drop)
5571d8ae5a25SEliad Peller {
5572d8ae5a25SEliad Peller 	struct wl1271 *wl = hw->priv;
5573d8ae5a25SEliad Peller 
5574d8ae5a25SEliad Peller 	wl1271_tx_flush(wl);
5575d8ae5a25SEliad Peller }
5576d8ae5a25SEliad Peller 
wlcore_op_remain_on_channel(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_channel * chan,int duration,enum ieee80211_roc_type type)5577dabf37dbSEliad Peller static int wlcore_op_remain_on_channel(struct ieee80211_hw *hw,
5578dabf37dbSEliad Peller 				       struct ieee80211_vif *vif,
5579dabf37dbSEliad Peller 				       struct ieee80211_channel *chan,
5580d339d5caSIlan Peer 				       int duration,
5581d339d5caSIlan Peer 				       enum ieee80211_roc_type type)
5582dabf37dbSEliad Peller {
5583dabf37dbSEliad Peller 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
5584dabf37dbSEliad Peller 	struct wl1271 *wl = hw->priv;
558587cba169SEliad Peller 	int channel, active_roc, ret = 0;
5586dabf37dbSEliad Peller 
5587dabf37dbSEliad Peller 	channel = ieee80211_frequency_to_channel(chan->center_freq);
5588dabf37dbSEliad Peller 
5589dabf37dbSEliad Peller 	wl1271_debug(DEBUG_MAC80211, "mac80211 roc %d (%d)",
5590dabf37dbSEliad Peller 		     channel, wlvif->role_id);
5591dabf37dbSEliad Peller 
5592dabf37dbSEliad Peller 	mutex_lock(&wl->mutex);
5593dabf37dbSEliad Peller 
5594dabf37dbSEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON))
5595dabf37dbSEliad Peller 		goto out;
5596dabf37dbSEliad Peller 
5597dabf37dbSEliad Peller 	/* return EBUSY if we can't ROC right now */
559887cba169SEliad Peller 	active_roc = find_first_bit(wl->roc_map, WL12XX_MAX_ROLES);
559987cba169SEliad Peller 	if (wl->roc_vif || active_roc < WL12XX_MAX_ROLES) {
560087cba169SEliad Peller 		wl1271_warning("active roc on role %d", active_roc);
5601dabf37dbSEliad Peller 		ret = -EBUSY;
5602dabf37dbSEliad Peller 		goto out;
5603dabf37dbSEliad Peller 	}
5604dabf37dbSEliad Peller 
5605ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
5606ab589ac2SMinghao Chi 	if (ret < 0)
5607dabf37dbSEliad Peller 		goto out;
5608dabf37dbSEliad Peller 
5609dabf37dbSEliad Peller 	ret = wl12xx_start_dev(wl, wlvif, chan->band, channel);
5610dabf37dbSEliad Peller 	if (ret < 0)
5611dabf37dbSEliad Peller 		goto out_sleep;
5612dabf37dbSEliad Peller 
5613dabf37dbSEliad Peller 	wl->roc_vif = vif;
5614dabf37dbSEliad Peller 	ieee80211_queue_delayed_work(hw, &wl->roc_complete_work,
5615dabf37dbSEliad Peller 				     msecs_to_jiffies(duration));
5616dabf37dbSEliad Peller out_sleep:
56179b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
56189b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
5619dabf37dbSEliad Peller out:
5620dabf37dbSEliad Peller 	mutex_unlock(&wl->mutex);
5621dabf37dbSEliad Peller 	return ret;
5622dabf37dbSEliad Peller }
5623dabf37dbSEliad Peller 
__wlcore_roc_completed(struct wl1271 * wl)5624dabf37dbSEliad Peller static int __wlcore_roc_completed(struct wl1271 *wl)
5625dabf37dbSEliad Peller {
5626dabf37dbSEliad Peller 	struct wl12xx_vif *wlvif;
5627dabf37dbSEliad Peller 	int ret;
5628dabf37dbSEliad Peller 
5629dabf37dbSEliad Peller 	/* already completed */
5630dabf37dbSEliad Peller 	if (unlikely(!wl->roc_vif))
5631dabf37dbSEliad Peller 		return 0;
5632dabf37dbSEliad Peller 
5633dabf37dbSEliad Peller 	wlvif = wl12xx_vif_to_data(wl->roc_vif);
5634dabf37dbSEliad Peller 
5635dabf37dbSEliad Peller 	if (!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags))
5636dabf37dbSEliad Peller 		return -EBUSY;
5637dabf37dbSEliad Peller 
5638dabf37dbSEliad Peller 	ret = wl12xx_stop_dev(wl, wlvif);
5639dabf37dbSEliad Peller 	if (ret < 0)
5640dabf37dbSEliad Peller 		return ret;
5641dabf37dbSEliad Peller 
5642dabf37dbSEliad Peller 	wl->roc_vif = NULL;
5643dabf37dbSEliad Peller 
5644dabf37dbSEliad Peller 	return 0;
5645dabf37dbSEliad Peller }
5646dabf37dbSEliad Peller 
wlcore_roc_completed(struct wl1271 * wl)5647dabf37dbSEliad Peller static int wlcore_roc_completed(struct wl1271 *wl)
5648dabf37dbSEliad Peller {
5649dabf37dbSEliad Peller 	int ret;
5650dabf37dbSEliad Peller 
5651dabf37dbSEliad Peller 	wl1271_debug(DEBUG_MAC80211, "roc complete");
5652dabf37dbSEliad Peller 
5653dabf37dbSEliad Peller 	mutex_lock(&wl->mutex);
5654dabf37dbSEliad Peller 
5655dabf37dbSEliad Peller 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
5656dabf37dbSEliad Peller 		ret = -EBUSY;
5657dabf37dbSEliad Peller 		goto out;
5658dabf37dbSEliad Peller 	}
5659dabf37dbSEliad Peller 
5660ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
5661ab589ac2SMinghao Chi 	if (ret < 0)
5662dabf37dbSEliad Peller 		goto out;
5663dabf37dbSEliad Peller 
5664dabf37dbSEliad Peller 	ret = __wlcore_roc_completed(wl);
5665dabf37dbSEliad Peller 
56669b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
56679b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
5668dabf37dbSEliad Peller out:
5669dabf37dbSEliad Peller 	mutex_unlock(&wl->mutex);
5670dabf37dbSEliad Peller 
5671dabf37dbSEliad Peller 	return ret;
5672dabf37dbSEliad Peller }
5673dabf37dbSEliad Peller 
wlcore_roc_complete_work(struct work_struct * work)5674dabf37dbSEliad Peller static void wlcore_roc_complete_work(struct work_struct *work)
5675dabf37dbSEliad Peller {
5676dabf37dbSEliad Peller 	struct delayed_work *dwork;
5677dabf37dbSEliad Peller 	struct wl1271 *wl;
5678dabf37dbSEliad Peller 	int ret;
5679dabf37dbSEliad Peller 
568061383412SGeliang Tang 	dwork = to_delayed_work(work);
5681dabf37dbSEliad Peller 	wl = container_of(dwork, struct wl1271, roc_complete_work);
5682dabf37dbSEliad Peller 
5683dabf37dbSEliad Peller 	ret = wlcore_roc_completed(wl);
5684dabf37dbSEliad Peller 	if (!ret)
5685dabf37dbSEliad Peller 		ieee80211_remain_on_channel_expired(wl->hw);
5686dabf37dbSEliad Peller }
5687dabf37dbSEliad Peller 
wlcore_op_cancel_remain_on_channel(struct ieee80211_hw * hw,struct ieee80211_vif * vif)56885db4c4b9SEmmanuel Grumbach static int wlcore_op_cancel_remain_on_channel(struct ieee80211_hw *hw,
56895db4c4b9SEmmanuel Grumbach 					      struct ieee80211_vif *vif)
5690dabf37dbSEliad Peller {
5691dabf37dbSEliad Peller 	struct wl1271 *wl = hw->priv;
5692dabf37dbSEliad Peller 
5693dabf37dbSEliad Peller 	wl1271_debug(DEBUG_MAC80211, "mac80211 croc");
5694dabf37dbSEliad Peller 
5695dabf37dbSEliad Peller 	/* TODO: per-vif */
5696dabf37dbSEliad Peller 	wl1271_tx_flush(wl);
5697dabf37dbSEliad Peller 
5698dabf37dbSEliad Peller 	/*
5699dabf37dbSEliad Peller 	 * we can't just flush_work here, because it might deadlock
5700dabf37dbSEliad Peller 	 * (as we might get called from the same workqueue)
5701dabf37dbSEliad Peller 	 */
5702dabf37dbSEliad Peller 	cancel_delayed_work_sync(&wl->roc_complete_work);
5703dabf37dbSEliad Peller 	wlcore_roc_completed(wl);
5704dabf37dbSEliad Peller 
5705dabf37dbSEliad Peller 	return 0;
5706dabf37dbSEliad Peller }
5707dabf37dbSEliad Peller 
wlcore_op_sta_rc_update(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_sta * sta,u32 changed)57085f9b6777SArik Nemtsov static void wlcore_op_sta_rc_update(struct ieee80211_hw *hw,
57095f9b6777SArik Nemtsov 				    struct ieee80211_vif *vif,
57105f9b6777SArik Nemtsov 				    struct ieee80211_sta *sta,
57115f9b6777SArik Nemtsov 				    u32 changed)
57125f9b6777SArik Nemtsov {
57135f9b6777SArik Nemtsov 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
57145f9b6777SArik Nemtsov 
57157d3b29e5SEliad Peller 	wl1271_debug(DEBUG_MAC80211, "mac80211 sta_rc_update");
57167d3b29e5SEliad Peller 
57177d3b29e5SEliad Peller 	if (!(changed & IEEE80211_RC_BW_CHANGED))
57187d3b29e5SEliad Peller 		return;
57197d3b29e5SEliad Peller 
57207d3b29e5SEliad Peller 	/* this callback is atomic, so schedule a new work */
5721046d2e7cSSriram R 	wlvif->rc_update_bw = sta->deflink.bandwidth;
5722046d2e7cSSriram R 	memcpy(&wlvif->rc_ht_cap, &sta->deflink.ht_cap,
5723046d2e7cSSriram R 	       sizeof(sta->deflink.ht_cap));
57247d3b29e5SEliad Peller 	ieee80211_queue_work(hw, &wlvif->rc_update_work);
57255f9b6777SArik Nemtsov }
57265f9b6777SArik Nemtsov 
wlcore_op_sta_statistics(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_sta * sta,struct station_info * sinfo)57272b9a7e1bSJohannes Berg static void wlcore_op_sta_statistics(struct ieee80211_hw *hw,
57280a9ffac0SNadim Zubidat 				     struct ieee80211_vif *vif,
57290a9ffac0SNadim Zubidat 				     struct ieee80211_sta *sta,
57302b9a7e1bSJohannes Berg 				     struct station_info *sinfo)
57310a9ffac0SNadim Zubidat {
57320a9ffac0SNadim Zubidat 	struct wl1271 *wl = hw->priv;
57330a9ffac0SNadim Zubidat 	struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
57342b9a7e1bSJohannes Berg 	s8 rssi_dbm;
57352b9a7e1bSJohannes Berg 	int ret;
57360a9ffac0SNadim Zubidat 
57370a9ffac0SNadim Zubidat 	wl1271_debug(DEBUG_MAC80211, "mac80211 get_rssi");
57380a9ffac0SNadim Zubidat 
57390a9ffac0SNadim Zubidat 	mutex_lock(&wl->mutex);
57400a9ffac0SNadim Zubidat 
57410a9ffac0SNadim Zubidat 	if (unlikely(wl->state != WLCORE_STATE_ON))
57420a9ffac0SNadim Zubidat 		goto out;
57430a9ffac0SNadim Zubidat 
5744ab589ac2SMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
5745ab589ac2SMinghao Chi 	if (ret < 0)
57460a9ffac0SNadim Zubidat 		goto out_sleep;
57470a9ffac0SNadim Zubidat 
57482b9a7e1bSJohannes Berg 	ret = wlcore_acx_average_rssi(wl, wlvif, &rssi_dbm);
57490a9ffac0SNadim Zubidat 	if (ret < 0)
57500a9ffac0SNadim Zubidat 		goto out_sleep;
57510a9ffac0SNadim Zubidat 
575222d0d2faSOmer Efrat 	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL);
57532b9a7e1bSJohannes Berg 	sinfo->signal = rssi_dbm;
57542b9a7e1bSJohannes Berg 
57550a9ffac0SNadim Zubidat out_sleep:
57569b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
57579b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
57580a9ffac0SNadim Zubidat 
57590a9ffac0SNadim Zubidat out:
57600a9ffac0SNadim Zubidat 	mutex_unlock(&wl->mutex);
57610a9ffac0SNadim Zubidat }
57620a9ffac0SNadim Zubidat 
wlcore_op_get_expected_throughput(struct ieee80211_hw * hw,struct ieee80211_sta * sta)57632439ca04SMaxim Altshul static u32 wlcore_op_get_expected_throughput(struct ieee80211_hw *hw,
57642439ca04SMaxim Altshul 					     struct ieee80211_sta *sta)
57655f6d4ca3SMaxim Altshul {
57665f6d4ca3SMaxim Altshul 	struct wl1271_station *wl_sta = (struct wl1271_station *)sta->drv_priv;
57672439ca04SMaxim Altshul 	struct wl1271 *wl = hw->priv;
57685f6d4ca3SMaxim Altshul 	u8 hlid = wl_sta->hlid;
57695f6d4ca3SMaxim Altshul 
57705f6d4ca3SMaxim Altshul 	/* return in units of Kbps */
57715f6d4ca3SMaxim Altshul 	return (wl->links[hlid].fw_rate_mbps * 1000);
57725f6d4ca3SMaxim Altshul }
57735f6d4ca3SMaxim Altshul 
wl1271_tx_frames_pending(struct ieee80211_hw * hw)57747b3115f2SLuciano Coelho static bool wl1271_tx_frames_pending(struct ieee80211_hw *hw)
57757b3115f2SLuciano Coelho {
57767b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
57777b3115f2SLuciano Coelho 	bool ret = false;
57787b3115f2SLuciano Coelho 
57797b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
57807b3115f2SLuciano Coelho 
57814cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON))
57827b3115f2SLuciano Coelho 		goto out;
57837b3115f2SLuciano Coelho 
57847b3115f2SLuciano Coelho 	/* packets are considered pending if in the TX queue or the FW */
57857b3115f2SLuciano Coelho 	ret = (wl1271_tx_total_queue_count(wl) > 0) || (wl->tx_frames_cnt > 0);
57867b3115f2SLuciano Coelho out:
57877b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
57887b3115f2SLuciano Coelho 
57897b3115f2SLuciano Coelho 	return ret;
57907b3115f2SLuciano Coelho }
57917b3115f2SLuciano Coelho 
57927b3115f2SLuciano Coelho /* can't be const, mac80211 writes to this */
57937b3115f2SLuciano Coelho static struct ieee80211_rate wl1271_rates[] = {
57947b3115f2SLuciano Coelho 	{ .bitrate = 10,
57957b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_1MBPS,
57967b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_1MBPS, },
57977b3115f2SLuciano Coelho 	{ .bitrate = 20,
57987b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_2MBPS,
57997b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_2MBPS,
58007b3115f2SLuciano Coelho 	  .flags = IEEE80211_RATE_SHORT_PREAMBLE },
58017b3115f2SLuciano Coelho 	{ .bitrate = 55,
58027b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_5_5MBPS,
58037b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_5_5MBPS,
58047b3115f2SLuciano Coelho 	  .flags = IEEE80211_RATE_SHORT_PREAMBLE },
58057b3115f2SLuciano Coelho 	{ .bitrate = 110,
58067b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_11MBPS,
58077b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_11MBPS,
58087b3115f2SLuciano Coelho 	  .flags = IEEE80211_RATE_SHORT_PREAMBLE },
58097b3115f2SLuciano Coelho 	{ .bitrate = 60,
58107b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_6MBPS,
58117b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_6MBPS, },
58127b3115f2SLuciano Coelho 	{ .bitrate = 90,
58137b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_9MBPS,
58147b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_9MBPS, },
58157b3115f2SLuciano Coelho 	{ .bitrate = 120,
58167b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_12MBPS,
58177b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_12MBPS, },
58187b3115f2SLuciano Coelho 	{ .bitrate = 180,
58197b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_18MBPS,
58207b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_18MBPS, },
58217b3115f2SLuciano Coelho 	{ .bitrate = 240,
58227b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_24MBPS,
58237b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_24MBPS, },
58247b3115f2SLuciano Coelho 	{ .bitrate = 360,
58257b3115f2SLuciano Coelho 	 .hw_value = CONF_HW_BIT_RATE_36MBPS,
58267b3115f2SLuciano Coelho 	 .hw_value_short = CONF_HW_BIT_RATE_36MBPS, },
58277b3115f2SLuciano Coelho 	{ .bitrate = 480,
58287b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_48MBPS,
58297b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_48MBPS, },
58307b3115f2SLuciano Coelho 	{ .bitrate = 540,
58317b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_54MBPS,
58327b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_54MBPS, },
58337b3115f2SLuciano Coelho };
58347b3115f2SLuciano Coelho 
58357b3115f2SLuciano Coelho /* can't be const, mac80211 writes to this */
58367b3115f2SLuciano Coelho static struct ieee80211_channel wl1271_channels[] = {
5837583f8164SVictor Goldenshtein 	{ .hw_value = 1, .center_freq = 2412, .max_power = WLCORE_MAX_TXPWR },
5838583f8164SVictor Goldenshtein 	{ .hw_value = 2, .center_freq = 2417, .max_power = WLCORE_MAX_TXPWR },
5839583f8164SVictor Goldenshtein 	{ .hw_value = 3, .center_freq = 2422, .max_power = WLCORE_MAX_TXPWR },
5840583f8164SVictor Goldenshtein 	{ .hw_value = 4, .center_freq = 2427, .max_power = WLCORE_MAX_TXPWR },
5841583f8164SVictor Goldenshtein 	{ .hw_value = 5, .center_freq = 2432, .max_power = WLCORE_MAX_TXPWR },
5842583f8164SVictor Goldenshtein 	{ .hw_value = 6, .center_freq = 2437, .max_power = WLCORE_MAX_TXPWR },
5843583f8164SVictor Goldenshtein 	{ .hw_value = 7, .center_freq = 2442, .max_power = WLCORE_MAX_TXPWR },
5844583f8164SVictor Goldenshtein 	{ .hw_value = 8, .center_freq = 2447, .max_power = WLCORE_MAX_TXPWR },
5845583f8164SVictor Goldenshtein 	{ .hw_value = 9, .center_freq = 2452, .max_power = WLCORE_MAX_TXPWR },
5846583f8164SVictor Goldenshtein 	{ .hw_value = 10, .center_freq = 2457, .max_power = WLCORE_MAX_TXPWR },
5847583f8164SVictor Goldenshtein 	{ .hw_value = 11, .center_freq = 2462, .max_power = WLCORE_MAX_TXPWR },
5848583f8164SVictor Goldenshtein 	{ .hw_value = 12, .center_freq = 2467, .max_power = WLCORE_MAX_TXPWR },
5849583f8164SVictor Goldenshtein 	{ .hw_value = 13, .center_freq = 2472, .max_power = WLCORE_MAX_TXPWR },
5850583f8164SVictor Goldenshtein 	{ .hw_value = 14, .center_freq = 2484, .max_power = WLCORE_MAX_TXPWR },
58517b3115f2SLuciano Coelho };
58527b3115f2SLuciano Coelho 
58537b3115f2SLuciano Coelho /* can't be const, mac80211 writes to this */
58547b3115f2SLuciano Coelho static struct ieee80211_supported_band wl1271_band_2ghz = {
58557b3115f2SLuciano Coelho 	.channels = wl1271_channels,
58567b3115f2SLuciano Coelho 	.n_channels = ARRAY_SIZE(wl1271_channels),
58577b3115f2SLuciano Coelho 	.bitrates = wl1271_rates,
58587b3115f2SLuciano Coelho 	.n_bitrates = ARRAY_SIZE(wl1271_rates),
58597b3115f2SLuciano Coelho };
58607b3115f2SLuciano Coelho 
58617b3115f2SLuciano Coelho /* 5 GHz data rates for WL1273 */
58627b3115f2SLuciano Coelho static struct ieee80211_rate wl1271_rates_5ghz[] = {
58637b3115f2SLuciano Coelho 	{ .bitrate = 60,
58647b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_6MBPS,
58657b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_6MBPS, },
58667b3115f2SLuciano Coelho 	{ .bitrate = 90,
58677b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_9MBPS,
58687b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_9MBPS, },
58697b3115f2SLuciano Coelho 	{ .bitrate = 120,
58707b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_12MBPS,
58717b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_12MBPS, },
58727b3115f2SLuciano Coelho 	{ .bitrate = 180,
58737b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_18MBPS,
58747b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_18MBPS, },
58757b3115f2SLuciano Coelho 	{ .bitrate = 240,
58767b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_24MBPS,
58777b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_24MBPS, },
58787b3115f2SLuciano Coelho 	{ .bitrate = 360,
58797b3115f2SLuciano Coelho 	 .hw_value = CONF_HW_BIT_RATE_36MBPS,
58807b3115f2SLuciano Coelho 	 .hw_value_short = CONF_HW_BIT_RATE_36MBPS, },
58817b3115f2SLuciano Coelho 	{ .bitrate = 480,
58827b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_48MBPS,
58837b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_48MBPS, },
58847b3115f2SLuciano Coelho 	{ .bitrate = 540,
58857b3115f2SLuciano Coelho 	  .hw_value = CONF_HW_BIT_RATE_54MBPS,
58867b3115f2SLuciano Coelho 	  .hw_value_short = CONF_HW_BIT_RATE_54MBPS, },
58877b3115f2SLuciano Coelho };
58887b3115f2SLuciano Coelho 
58897b3115f2SLuciano Coelho /* 5 GHz band channels for WL1273 */
58907b3115f2SLuciano Coelho static struct ieee80211_channel wl1271_channels_5ghz[] = {
5891583f8164SVictor Goldenshtein 	{ .hw_value = 8, .center_freq = 5040, .max_power = WLCORE_MAX_TXPWR },
5892583f8164SVictor Goldenshtein 	{ .hw_value = 12, .center_freq = 5060, .max_power = WLCORE_MAX_TXPWR },
5893583f8164SVictor Goldenshtein 	{ .hw_value = 16, .center_freq = 5080, .max_power = WLCORE_MAX_TXPWR },
5894583f8164SVictor Goldenshtein 	{ .hw_value = 34, .center_freq = 5170, .max_power = WLCORE_MAX_TXPWR },
5895583f8164SVictor Goldenshtein 	{ .hw_value = 36, .center_freq = 5180, .max_power = WLCORE_MAX_TXPWR },
5896583f8164SVictor Goldenshtein 	{ .hw_value = 38, .center_freq = 5190, .max_power = WLCORE_MAX_TXPWR },
5897583f8164SVictor Goldenshtein 	{ .hw_value = 40, .center_freq = 5200, .max_power = WLCORE_MAX_TXPWR },
5898583f8164SVictor Goldenshtein 	{ .hw_value = 42, .center_freq = 5210, .max_power = WLCORE_MAX_TXPWR },
5899583f8164SVictor Goldenshtein 	{ .hw_value = 44, .center_freq = 5220, .max_power = WLCORE_MAX_TXPWR },
5900583f8164SVictor Goldenshtein 	{ .hw_value = 46, .center_freq = 5230, .max_power = WLCORE_MAX_TXPWR },
5901583f8164SVictor Goldenshtein 	{ .hw_value = 48, .center_freq = 5240, .max_power = WLCORE_MAX_TXPWR },
5902583f8164SVictor Goldenshtein 	{ .hw_value = 52, .center_freq = 5260, .max_power = WLCORE_MAX_TXPWR },
5903583f8164SVictor Goldenshtein 	{ .hw_value = 56, .center_freq = 5280, .max_power = WLCORE_MAX_TXPWR },
5904583f8164SVictor Goldenshtein 	{ .hw_value = 60, .center_freq = 5300, .max_power = WLCORE_MAX_TXPWR },
5905583f8164SVictor Goldenshtein 	{ .hw_value = 64, .center_freq = 5320, .max_power = WLCORE_MAX_TXPWR },
5906583f8164SVictor Goldenshtein 	{ .hw_value = 100, .center_freq = 5500, .max_power = WLCORE_MAX_TXPWR },
5907583f8164SVictor Goldenshtein 	{ .hw_value = 104, .center_freq = 5520, .max_power = WLCORE_MAX_TXPWR },
5908583f8164SVictor Goldenshtein 	{ .hw_value = 108, .center_freq = 5540, .max_power = WLCORE_MAX_TXPWR },
5909583f8164SVictor Goldenshtein 	{ .hw_value = 112, .center_freq = 5560, .max_power = WLCORE_MAX_TXPWR },
5910583f8164SVictor Goldenshtein 	{ .hw_value = 116, .center_freq = 5580, .max_power = WLCORE_MAX_TXPWR },
5911583f8164SVictor Goldenshtein 	{ .hw_value = 120, .center_freq = 5600, .max_power = WLCORE_MAX_TXPWR },
5912583f8164SVictor Goldenshtein 	{ .hw_value = 124, .center_freq = 5620, .max_power = WLCORE_MAX_TXPWR },
5913583f8164SVictor Goldenshtein 	{ .hw_value = 128, .center_freq = 5640, .max_power = WLCORE_MAX_TXPWR },
5914583f8164SVictor Goldenshtein 	{ .hw_value = 132, .center_freq = 5660, .max_power = WLCORE_MAX_TXPWR },
5915583f8164SVictor Goldenshtein 	{ .hw_value = 136, .center_freq = 5680, .max_power = WLCORE_MAX_TXPWR },
5916583f8164SVictor Goldenshtein 	{ .hw_value = 140, .center_freq = 5700, .max_power = WLCORE_MAX_TXPWR },
5917583f8164SVictor Goldenshtein 	{ .hw_value = 149, .center_freq = 5745, .max_power = WLCORE_MAX_TXPWR },
5918583f8164SVictor Goldenshtein 	{ .hw_value = 153, .center_freq = 5765, .max_power = WLCORE_MAX_TXPWR },
5919583f8164SVictor Goldenshtein 	{ .hw_value = 157, .center_freq = 5785, .max_power = WLCORE_MAX_TXPWR },
5920583f8164SVictor Goldenshtein 	{ .hw_value = 161, .center_freq = 5805, .max_power = WLCORE_MAX_TXPWR },
5921583f8164SVictor Goldenshtein 	{ .hw_value = 165, .center_freq = 5825, .max_power = WLCORE_MAX_TXPWR },
59227b3115f2SLuciano Coelho };
59237b3115f2SLuciano Coelho 
59247b3115f2SLuciano Coelho static struct ieee80211_supported_band wl1271_band_5ghz = {
59257b3115f2SLuciano Coelho 	.channels = wl1271_channels_5ghz,
59267b3115f2SLuciano Coelho 	.n_channels = ARRAY_SIZE(wl1271_channels_5ghz),
59277b3115f2SLuciano Coelho 	.bitrates = wl1271_rates_5ghz,
59287b3115f2SLuciano Coelho 	.n_bitrates = ARRAY_SIZE(wl1271_rates_5ghz),
59297b3115f2SLuciano Coelho };
59307b3115f2SLuciano Coelho 
59317b3115f2SLuciano Coelho static const struct ieee80211_ops wl1271_ops = {
59327b3115f2SLuciano Coelho 	.start = wl1271_op_start,
5933c24ec83bSIdo Yariv 	.stop = wlcore_op_stop,
59347b3115f2SLuciano Coelho 	.add_interface = wl1271_op_add_interface,
59357b3115f2SLuciano Coelho 	.remove_interface = wl1271_op_remove_interface,
59367b3115f2SLuciano Coelho 	.change_interface = wl12xx_op_change_interface,
59377b3115f2SLuciano Coelho #ifdef CONFIG_PM
59387b3115f2SLuciano Coelho 	.suspend = wl1271_op_suspend,
59397b3115f2SLuciano Coelho 	.resume = wl1271_op_resume,
59407b3115f2SLuciano Coelho #endif
59417b3115f2SLuciano Coelho 	.config = wl1271_op_config,
59427b3115f2SLuciano Coelho 	.prepare_multicast = wl1271_op_prepare_multicast,
59437b3115f2SLuciano Coelho 	.configure_filter = wl1271_op_configure_filter,
59447b3115f2SLuciano Coelho 	.tx = wl1271_op_tx,
5945a1c597f2SArik Nemtsov 	.wake_tx_queue = ieee80211_handle_wake_tx_queue,
59467b3115f2SLuciano Coelho 	.set_key = wlcore_op_set_key,
59477b3115f2SLuciano Coelho 	.hw_scan = wl1271_op_hw_scan,
59487b3115f2SLuciano Coelho 	.cancel_hw_scan = wl1271_op_cancel_hw_scan,
59497b3115f2SLuciano Coelho 	.sched_scan_start = wl1271_op_sched_scan_start,
59507b3115f2SLuciano Coelho 	.sched_scan_stop = wl1271_op_sched_scan_stop,
59517b3115f2SLuciano Coelho 	.bss_info_changed = wl1271_op_bss_info_changed,
59527b3115f2SLuciano Coelho 	.set_frag_threshold = wl1271_op_set_frag_threshold,
59537b3115f2SLuciano Coelho 	.set_rts_threshold = wl1271_op_set_rts_threshold,
59547b3115f2SLuciano Coelho 	.conf_tx = wl1271_op_conf_tx,
59557b3115f2SLuciano Coelho 	.get_tsf = wl1271_op_get_tsf,
59567b3115f2SLuciano Coelho 	.get_survey = wl1271_op_get_survey,
59577b3115f2SLuciano Coelho 	.sta_state = wl12xx_op_sta_state,
59587b3115f2SLuciano Coelho 	.ampdu_action = wl1271_op_ampdu_action,
59597b3115f2SLuciano Coelho 	.tx_frames_pending = wl1271_tx_frames_pending,
5960ba1e6eb9SYoni Divinsky 	.set_bitrate_mask = wl12xx_set_bitrate_mask,
59617b3115f2SLuciano Coelho 	.set_default_unicast_key = wl1271_op_set_default_key_idx,
5962534719f4SEliad Peller 	.channel_switch = wl12xx_op_channel_switch,
5963d8ae5a25SEliad Peller 	.channel_switch_beacon = wlcore_op_channel_switch_beacon,
5964dabf37dbSEliad Peller 	.flush = wlcore_op_flush,
5965dabf37dbSEliad Peller 	.remain_on_channel = wlcore_op_remain_on_channel,
5966b6970ee5SEliad Peller 	.cancel_remain_on_channel = wlcore_op_cancel_remain_on_channel,
5967b6970ee5SEliad Peller 	.add_chanctx = wlcore_op_add_chanctx,
5968b6970ee5SEliad Peller 	.remove_chanctx = wlcore_op_remove_chanctx,
5969b6970ee5SEliad Peller 	.change_chanctx = wlcore_op_change_chanctx,
5970b6970ee5SEliad Peller 	.assign_vif_chanctx = wlcore_op_assign_vif_chanctx,
5971750e9d15SEliad Peller 	.unassign_vif_chanctx = wlcore_op_unassign_vif_chanctx,
59725f9b6777SArik Nemtsov 	.switch_vif_chanctx = wlcore_op_switch_vif_chanctx,
59732b9a7e1bSJohannes Berg 	.sta_rc_update = wlcore_op_sta_rc_update,
59745f6d4ca3SMaxim Altshul 	.sta_statistics = wlcore_op_sta_statistics,
59757b3115f2SLuciano Coelho 	.get_expected_throughput = wlcore_op_get_expected_throughput,
59767b3115f2SLuciano Coelho 	CFG80211_TESTMODE_CMD(wl1271_tm_cmd)
59777b3115f2SLuciano Coelho };
59787b3115f2SLuciano Coelho 
597957fbcce3SJohannes Berg 
wlcore_rate_to_idx(struct wl1271 * wl,u8 rate,enum nl80211_band band)59807b3115f2SLuciano Coelho u8 wlcore_rate_to_idx(struct wl1271 *wl, u8 rate, enum nl80211_band band)
59817b3115f2SLuciano Coelho {
59827b3115f2SLuciano Coelho 	u8 idx;
598343a8bc5aSArik Nemtsov 
59847b3115f2SLuciano Coelho 	BUG_ON(band >= 2);
598543a8bc5aSArik Nemtsov 
59867b3115f2SLuciano Coelho 	if (unlikely(rate >= wl->hw_tx_rate_tbl_size)) {
59877b3115f2SLuciano Coelho 		wl1271_error("Illegal RX rate from HW: %d", rate);
59887b3115f2SLuciano Coelho 		return 0;
59897b3115f2SLuciano Coelho 	}
599043a8bc5aSArik Nemtsov 
59917b3115f2SLuciano Coelho 	idx = wl->band_rate_to_idx[band][rate];
59927b3115f2SLuciano Coelho 	if (unlikely(idx == CONF_HW_RXTX_RATE_UNSUPPORTED)) {
59937b3115f2SLuciano Coelho 		wl1271_error("Unsupported RX rate from HW: %d", rate);
59947b3115f2SLuciano Coelho 		return 0;
59957b3115f2SLuciano Coelho 	}
59967b3115f2SLuciano Coelho 
59977b3115f2SLuciano Coelho 	return idx;
59987b3115f2SLuciano Coelho }
5999f4afbed9SArik Nemtsov 
wl12xx_derive_mac_addresses(struct wl1271 * wl,u32 oui,u32 nic)60007b3115f2SLuciano Coelho static void wl12xx_derive_mac_addresses(struct wl1271 *wl, u32 oui, u32 nic)
60017b3115f2SLuciano Coelho {
60027b3115f2SLuciano Coelho 	int i;
6003f4afbed9SArik Nemtsov 
6004f4afbed9SArik Nemtsov 	wl1271_debug(DEBUG_PROBE, "base address: oui %06x nic %06x",
60057b3115f2SLuciano Coelho 		     oui, nic);
6006f4afbed9SArik Nemtsov 
60077b3115f2SLuciano Coelho 	if (nic + WLCORE_NUM_MAC_ADDRESSES - wl->num_mac_addr > 0xffffff)
60087b3115f2SLuciano Coelho 		wl1271_warning("NIC part of the MAC address wraps around!");
6009f4afbed9SArik Nemtsov 
60107b3115f2SLuciano Coelho 	for (i = 0; i < wl->num_mac_addr; i++) {
60117b3115f2SLuciano Coelho 		wl->addresses[i].addr[0] = (u8)(oui >> 16);
60127b3115f2SLuciano Coelho 		wl->addresses[i].addr[1] = (u8)(oui >> 8);
60137b3115f2SLuciano Coelho 		wl->addresses[i].addr[2] = (u8) oui;
60147b3115f2SLuciano Coelho 		wl->addresses[i].addr[3] = (u8)(nic >> 16);
60157b3115f2SLuciano Coelho 		wl->addresses[i].addr[4] = (u8)(nic >> 8);
60167b3115f2SLuciano Coelho 		wl->addresses[i].addr[5] = (u8) nic;
60177b3115f2SLuciano Coelho 		nic++;
60187b3115f2SLuciano Coelho 	}
6019f4afbed9SArik Nemtsov 
6020f4afbed9SArik Nemtsov 	/* we may be one address short at the most */
6021f4afbed9SArik Nemtsov 	WARN_ON(wl->num_mac_addr + 1 < WLCORE_NUM_MAC_ADDRESSES);
6022f4afbed9SArik Nemtsov 
6023f4afbed9SArik Nemtsov 	/*
6024f4afbed9SArik Nemtsov 	 * turn on the LAA bit in the first address and use it as
6025f4afbed9SArik Nemtsov 	 * the last address.
6026f4afbed9SArik Nemtsov 	 */
6027f4afbed9SArik Nemtsov 	if (wl->num_mac_addr < WLCORE_NUM_MAC_ADDRESSES) {
6028f4afbed9SArik Nemtsov 		int idx = WLCORE_NUM_MAC_ADDRESSES - 1;
6029f4afbed9SArik Nemtsov 		memcpy(&wl->addresses[idx], &wl->addresses[0],
6030f4afbed9SArik Nemtsov 		       sizeof(wl->addresses[0]));
603171a301bbSEliad Peller 		/* LAA bit */
6032f4afbed9SArik Nemtsov 		wl->addresses[idx].addr[0] |= BIT(1);
6033f4afbed9SArik Nemtsov 	}
6034f4afbed9SArik Nemtsov 
60357b3115f2SLuciano Coelho 	wl->hw->wiphy->n_addresses = WLCORE_NUM_MAC_ADDRESSES;
60367b3115f2SLuciano Coelho 	wl->hw->wiphy->addresses = wl->addresses;
60377b3115f2SLuciano Coelho }
60387b3115f2SLuciano Coelho 
wl12xx_get_hw_info(struct wl1271 * wl)60397b3115f2SLuciano Coelho static int wl12xx_get_hw_info(struct wl1271 *wl)
60407b3115f2SLuciano Coelho {
60417b3115f2SLuciano Coelho 	int ret;
60426134323fSIdo Yariv 
60436134323fSIdo Yariv 	ret = wlcore_read_reg(wl, REG_CHIP_ID_B, &wl->chip.id);
60446134323fSIdo Yariv 	if (ret < 0)
60457b3115f2SLuciano Coelho 		goto out;
604600782136SLuciano Coelho 
604700782136SLuciano Coelho 	wl->fuse_oui_addr = 0;
604800782136SLuciano Coelho 	wl->fuse_nic_addr = 0;
60496134323fSIdo Yariv 
60506134323fSIdo Yariv 	ret = wl->ops->get_pg_ver(wl, &wl->hw_pg_ver);
60516134323fSIdo Yariv 	if (ret < 0)
60527b3115f2SLuciano Coelho 		goto out;
605330d9b4a5SLuciano Coelho 
60546134323fSIdo Yariv 	if (wl->ops->get_mac)
60557b3115f2SLuciano Coelho 		ret = wl->ops->get_mac(wl);
60567b3115f2SLuciano Coelho 
60577b3115f2SLuciano Coelho out:
60587b3115f2SLuciano Coelho 	return ret;
60597b3115f2SLuciano Coelho }
60607b3115f2SLuciano Coelho 
wl1271_register_hw(struct wl1271 * wl)60617b3115f2SLuciano Coelho static int wl1271_register_hw(struct wl1271 *wl)
60627b3115f2SLuciano Coelho {
60637b3115f2SLuciano Coelho 	int ret;
6064d382b9c0SReizer, Eyal 	u32 oui_addr = 0, nic_addr = 0;
6065d382b9c0SReizer, Eyal 	struct platform_device *pdev = wl->pdev;
60667b3115f2SLuciano Coelho 	struct wlcore_platdev_data *pdev_data = dev_get_platdata(&pdev->dev);
60677b3115f2SLuciano Coelho 
60687b3115f2SLuciano Coelho 	if (wl->mac80211_registered)
60697b3115f2SLuciano Coelho 		return 0;
60706f8d6b20SIdo Yariv 
60717b3115f2SLuciano Coelho 	if (wl->nvs_len >= 12) {
60727b3115f2SLuciano Coelho 		/* NOTE: The wl->nvs->nvs element must be first, in
60737b3115f2SLuciano Coelho 		 * order to simplify the casting, we assume it is at
60747b3115f2SLuciano Coelho 		 * the beginning of the wl->nvs structure.
60757b3115f2SLuciano Coelho 		 */
60767b3115f2SLuciano Coelho 		u8 *nvs_ptr = (u8 *)wl->nvs;
60777b3115f2SLuciano Coelho 
60787b3115f2SLuciano Coelho 		oui_addr =
60797b3115f2SLuciano Coelho 			(nvs_ptr[11] << 16) + (nvs_ptr[10] << 8) + nvs_ptr[6];
60807b3115f2SLuciano Coelho 		nic_addr =
60817b3115f2SLuciano Coelho 			(nvs_ptr[5] << 16) + (nvs_ptr[4] << 8) + nvs_ptr[3];
60827b3115f2SLuciano Coelho 	}
60837b3115f2SLuciano Coelho 
60847b3115f2SLuciano Coelho 	/* if the MAC address is zeroed in the NVS derive from fuse */
60857b3115f2SLuciano Coelho 	if (oui_addr == 0 && nic_addr == 0) {
60867b3115f2SLuciano Coelho 		oui_addr = wl->fuse_oui_addr;
60877b3115f2SLuciano Coelho 		/* fuse has the BD_ADDR, the WLAN addresses are the next two */
60887b3115f2SLuciano Coelho 		nic_addr = wl->fuse_nic_addr + 1;
60897b3115f2SLuciano Coelho 	}
6090d382b9c0SReizer, Eyal 
609118dc5a4bSH. Nikolaus Schaller 	if (oui_addr == 0xdeadbe && nic_addr == 0xef0000) {
6092d382b9c0SReizer, Eyal 		wl1271_warning("Detected unconfigured mac address in nvs, derive from fuse instead.");
609318dc5a4bSH. Nikolaus Schaller 		if (!strcmp(pdev_data->family->name, "wl18xx")) {
6094d382b9c0SReizer, Eyal 			wl1271_warning("This default nvs file can be removed from the file system");
609518dc5a4bSH. Nikolaus Schaller 		} else {
609618dc5a4bSH. Nikolaus Schaller 			wl1271_warning("Your device performance is not optimized.");
6097d382b9c0SReizer, Eyal 			wl1271_warning("Please use the calibrator tool to configure your device.");
6098d382b9c0SReizer, Eyal 		}
6099d382b9c0SReizer, Eyal 
610018dc5a4bSH. Nikolaus Schaller 		if (wl->fuse_oui_addr == 0 && wl->fuse_nic_addr == 0) {
6101d382b9c0SReizer, Eyal 			wl1271_warning("Fuse mac address is zero. using random mac");
6102d382b9c0SReizer, Eyal 			/* Use TI oui and a random nic */
6103*a251c17aSJason A. Donenfeld 			oui_addr = WLCORE_TI_OUI_ADDRESS;
6104d382b9c0SReizer, Eyal 			nic_addr = get_random_u32();
6105d382b9c0SReizer, Eyal 		} else {
6106d382b9c0SReizer, Eyal 			oui_addr = wl->fuse_oui_addr;
6107d382b9c0SReizer, Eyal 			/* fuse has the BD_ADDR, the WLAN addresses are the next two */
6108d382b9c0SReizer, Eyal 			nic_addr = wl->fuse_nic_addr + 1;
6109d382b9c0SReizer, Eyal 		}
6110d382b9c0SReizer, Eyal 	}
6111f4afbed9SArik Nemtsov 
61127b3115f2SLuciano Coelho 	wl12xx_derive_mac_addresses(wl, oui_addr, nic_addr);
61137b3115f2SLuciano Coelho 
61147b3115f2SLuciano Coelho 	ret = ieee80211_register_hw(wl->hw);
61157b3115f2SLuciano Coelho 	if (ret < 0) {
61167b3115f2SLuciano Coelho 		wl1271_error("unable to register mac80211 hw: %d", ret);
61177b3115f2SLuciano Coelho 		goto out;
61187b3115f2SLuciano Coelho 	}
61197b3115f2SLuciano Coelho 
61207b3115f2SLuciano Coelho 	wl->mac80211_registered = true;
61217b3115f2SLuciano Coelho 
61227b3115f2SLuciano Coelho 	wl1271_debugfs_init(wl);
61237b3115f2SLuciano Coelho 
61247b3115f2SLuciano Coelho 	wl1271_notice("loaded");
61257b3115f2SLuciano Coelho 
61267b3115f2SLuciano Coelho out:
61277b3115f2SLuciano Coelho 	return ret;
61287b3115f2SLuciano Coelho }
61297b3115f2SLuciano Coelho 
wl1271_unregister_hw(struct wl1271 * wl)61307b3115f2SLuciano Coelho static void wl1271_unregister_hw(struct wl1271 *wl)
61317b3115f2SLuciano Coelho {
61327b3115f2SLuciano Coelho 	if (wl->plt)
61337b3115f2SLuciano Coelho 		wl1271_plt_stop(wl);
61347b3115f2SLuciano Coelho 
61357b3115f2SLuciano Coelho 	ieee80211_unregister_hw(wl->hw);
61367b3115f2SLuciano Coelho 	wl->mac80211_registered = false;
61377b3115f2SLuciano Coelho 
61387b3115f2SLuciano Coelho }
61397b3115f2SLuciano Coelho 
wl1271_init_ieee80211(struct wl1271 * wl)61407b3115f2SLuciano Coelho static int wl1271_init_ieee80211(struct wl1271 *wl)
6141583f8164SVictor Goldenshtein {
61427b3115f2SLuciano Coelho 	int i;
61437b3115f2SLuciano Coelho 	static const u32 cipher_suites[] = {
61447b3115f2SLuciano Coelho 		WLAN_CIPHER_SUITE_WEP40,
61457b3115f2SLuciano Coelho 		WLAN_CIPHER_SUITE_WEP104,
61467b3115f2SLuciano Coelho 		WLAN_CIPHER_SUITE_TKIP,
61477b3115f2SLuciano Coelho 		WLAN_CIPHER_SUITE_CCMP,
61487b3115f2SLuciano Coelho 		WL1271_CIPHER_SUITE_GEM,
61497b3115f2SLuciano Coelho 	};
61502c0133a4SArik Nemtsov 
61512c0133a4SArik Nemtsov 	/* The tx descriptor buffer */
61522c0133a4SArik Nemtsov 	wl->hw->extra_tx_headroom = sizeof(struct wl1271_tx_hw_descr);
61532c0133a4SArik Nemtsov 
61542c0133a4SArik Nemtsov 	if (wl->quirks & WLCORE_QUIRK_TKIP_HEADER_SPACE)
61557b3115f2SLuciano Coelho 		wl->hw->extra_tx_headroom += WL1271_EXTRA_SPACE_TKIP;
61567b3115f2SLuciano Coelho 
61577b3115f2SLuciano Coelho 	/* unit us */
61587b3115f2SLuciano Coelho 	/* FIXME: find a proper value */
61597b3115f2SLuciano Coelho 	wl->hw->max_listen_interval = wl->conf.conn.max_listen_interval;
616030686bf7SJohannes Berg 
616130686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, SUPPORT_FAST_XMIT);
6162cf33a772SMaital Hahn 	ieee80211_hw_set(wl->hw, CHANCTX_STA_CSA);
616330686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, SUPPORTS_PER_STA_GTK);
616430686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, QUEUE_CONTROL);
616530686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, TX_AMPDU_SETUP_IN_HW);
616630686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, AMPDU_AGGREGATION);
616730686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, AP_LINK_PS);
616830686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, SPECTRUM_MGMT);
616930686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, REPORTS_TX_ACK_STATUS);
617030686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, CONNECTION_MONITOR);
617130686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, HAS_RATE_CONTROL);
617230686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, SUPPORTS_DYNAMIC_PS);
617330686bf7SJohannes Berg 	ieee80211_hw_set(wl->hw, SIGNAL_DBM);
6174f3fe4e93SSara Sharon 	ieee80211_hw_set(wl->hw, SUPPORTS_PS);
61757b3115f2SLuciano Coelho 	ieee80211_hw_set(wl->hw, SUPPORTS_TX_FRAG);
61767b3115f2SLuciano Coelho 
61777b3115f2SLuciano Coelho 	wl->hw->wiphy->cipher_suites = cipher_suites;
61787b3115f2SLuciano Coelho 	wl->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
61797b3115f2SLuciano Coelho 
61807845af35SEliad Peller 	wl->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
61817845af35SEliad Peller 					 BIT(NL80211_IFTYPE_AP) |
61827845af35SEliad Peller 					 BIT(NL80211_IFTYPE_P2P_DEVICE) |
6183c0174ee2SMaital Hahn 					 BIT(NL80211_IFTYPE_P2P_CLIENT) |
6184c0174ee2SMaital Hahn #ifdef CONFIG_MAC80211_MESH
6185c0174ee2SMaital Hahn 					 BIT(NL80211_IFTYPE_MESH_POINT) |
61867845af35SEliad Peller #endif
6187c0174ee2SMaital Hahn 					 BIT(NL80211_IFTYPE_P2P_GO);
61887b3115f2SLuciano Coelho 
61897b3115f2SLuciano Coelho 	wl->hw->wiphy->max_scan_ssids = 1;
61907b3115f2SLuciano Coelho 	wl->hw->wiphy->max_sched_scan_ssids = 16;
61917b3115f2SLuciano Coelho 	wl->hw->wiphy->max_match_sets = 16;
61927b3115f2SLuciano Coelho 	/*
61937b3115f2SLuciano Coelho 	 * Maximum length of elements in scanning probe request templates
61947b3115f2SLuciano Coelho 	 * should be the maximum length possible for a template, without
61957b3115f2SLuciano Coelho 	 * the IEEE80211 header of the template
61967b3115f2SLuciano Coelho 	 */
61977b3115f2SLuciano Coelho 	wl->hw->wiphy->max_scan_ie_len = WL1271_CMD_TEMPL_MAX_SIZE -
61987b3115f2SLuciano Coelho 			sizeof(struct ieee80211_header);
6199ca986ad9SArend Van Spriel 
62007b3115f2SLuciano Coelho 	wl->hw->wiphy->max_sched_scan_reqs = 1;
62017b3115f2SLuciano Coelho 	wl->hw->wiphy->max_sched_scan_ie_len = WL1271_CMD_TEMPL_MAX_SIZE -
62027b3115f2SLuciano Coelho 		sizeof(struct ieee80211_header);
6203fbddf587SEliad Peller 
6204dabf37dbSEliad Peller 	wl->hw->wiphy->max_remain_on_channel_duration = 30000;
6205916ef361SLuciano Coelho 
62061fb90260SJohannes Berg 	wl->hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD |
6207cf33a772SMaital Hahn 				WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL |
6208e2e57291SJohannes Berg 				WIPHY_FLAG_HAS_CHANNEL_SWITCH |
62097b3115f2SLuciano Coelho 				WIPHY_FLAG_IBSS_RSN;
621087016f5eSJames Minor 
621187016f5eSJames Minor 	wl->hw->wiphy->features |= NL80211_FEATURE_AP_SCAN;
62127b3115f2SLuciano Coelho 
62137b3115f2SLuciano Coelho 	/* make sure all our channels fit in the scanned_ch bitmask */
62147b3115f2SLuciano Coelho 	BUILD_BUG_ON(ARRAY_SIZE(wl1271_channels) +
62157b3115f2SLuciano Coelho 		     ARRAY_SIZE(wl1271_channels_5ghz) >
62167b3115f2SLuciano Coelho 		     WL1271_MAX_CHANNELS);
6217583f8164SVictor Goldenshtein 	/*
6218583f8164SVictor Goldenshtein 	* clear channel flags from the previous usage
6219583f8164SVictor Goldenshtein 	* and restore max_power & max_antenna_gain values.
6220583f8164SVictor Goldenshtein 	*/
6221583f8164SVictor Goldenshtein 	for (i = 0; i < ARRAY_SIZE(wl1271_channels); i++) {
6222583f8164SVictor Goldenshtein 		wl1271_band_2ghz.channels[i].flags = 0;
6223583f8164SVictor Goldenshtein 		wl1271_band_2ghz.channels[i].max_power = WLCORE_MAX_TXPWR;
6224583f8164SVictor Goldenshtein 		wl1271_band_2ghz.channels[i].max_antenna_gain = 0;
6225583f8164SVictor Goldenshtein 	}
6226583f8164SVictor Goldenshtein 
6227583f8164SVictor Goldenshtein 	for (i = 0; i < ARRAY_SIZE(wl1271_channels_5ghz); i++) {
6228583f8164SVictor Goldenshtein 		wl1271_band_5ghz.channels[i].flags = 0;
6229583f8164SVictor Goldenshtein 		wl1271_band_5ghz.channels[i].max_power = WLCORE_MAX_TXPWR;
6230583f8164SVictor Goldenshtein 		wl1271_band_5ghz.channels[i].max_antenna_gain = 0;
6231583f8164SVictor Goldenshtein 	}
6232583f8164SVictor Goldenshtein 
62337b3115f2SLuciano Coelho 	/*
62347b3115f2SLuciano Coelho 	 * We keep local copies of the band structs because we need to
62357b3115f2SLuciano Coelho 	 * modify them on a per-device basis.
623657fbcce3SJohannes Berg 	 */
62377b3115f2SLuciano Coelho 	memcpy(&wl->bands[NL80211_BAND_2GHZ], &wl1271_band_2ghz,
623857fbcce3SJohannes Berg 	       sizeof(wl1271_band_2ghz));
623957fbcce3SJohannes Berg 	memcpy(&wl->bands[NL80211_BAND_2GHZ].ht_cap,
6240bfb92ca1SEliad Peller 	       &wl->ht_cap[NL80211_BAND_2GHZ],
624157fbcce3SJohannes Berg 	       sizeof(*wl->ht_cap));
62427b3115f2SLuciano Coelho 	memcpy(&wl->bands[NL80211_BAND_5GHZ], &wl1271_band_5ghz,
624357fbcce3SJohannes Berg 	       sizeof(wl1271_band_5ghz));
624457fbcce3SJohannes Berg 	memcpy(&wl->bands[NL80211_BAND_5GHZ].ht_cap,
6245bfb92ca1SEliad Peller 	       &wl->ht_cap[NL80211_BAND_5GHZ],
62467b3115f2SLuciano Coelho 	       sizeof(*wl->ht_cap));
624757fbcce3SJohannes Berg 
624857fbcce3SJohannes Berg 	wl->hw->wiphy->bands[NL80211_BAND_2GHZ] =
624957fbcce3SJohannes Berg 		&wl->bands[NL80211_BAND_2GHZ];
625057fbcce3SJohannes Berg 	wl->hw->wiphy->bands[NL80211_BAND_5GHZ] =
62517b3115f2SLuciano Coelho 		&wl->bands[NL80211_BAND_5GHZ];
62521c33db78SArik Nemtsov 
62531c33db78SArik Nemtsov 	/*
62541c33db78SArik Nemtsov 	 * allow 4 queues per mac address we support +
62551c33db78SArik Nemtsov 	 * 1 cab queue per mac + one global offchannel Tx queue
62561c33db78SArik Nemtsov 	 */
62571c33db78SArik Nemtsov 	wl->hw->queues = (NUM_TX_QUEUES + 1) * WLCORE_NUM_MAC_ADDRESSES + 1;
62581c33db78SArik Nemtsov 
62591c33db78SArik Nemtsov 	/* the last queue is the offchannel queue */
62607b3115f2SLuciano Coelho 	wl->hw->offchannel_tx_hw_queue = wl->hw->queues - 1;
62617b3115f2SLuciano Coelho 	wl->hw->max_rates = 1;
62627b3115f2SLuciano Coelho 
62637b3115f2SLuciano Coelho 	wl->hw->wiphy->reg_notifier = wl1271_reg_notify;
62647b3115f2SLuciano Coelho 
62657b3115f2SLuciano Coelho 	/* the FW answers probe-requests in AP-mode */
62667b3115f2SLuciano Coelho 	wl->hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
62677b3115f2SLuciano Coelho 	wl->hw->wiphy->probe_resp_offload =
62687b3115f2SLuciano Coelho 		NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
62697b3115f2SLuciano Coelho 		NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 |
62707b3115f2SLuciano Coelho 		NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P;
6271bcab320bSEliad Peller 
6272abf0b249SEliad Peller 	/* allowed interface combinations */
6273abf0b249SEliad Peller 	wl->hw->wiphy->iface_combinations = wl->iface_combinations;
6274bcab320bSEliad Peller 	wl->hw->wiphy->n_iface_combinations = wl->n_iface_combinations;
6275d8c5a48dSEliad Peller 
6276d8c5a48dSEliad Peller 	/* register vendor commands */
6277d8c5a48dSEliad Peller 	wlcore_set_vendor_commands(wl->hw->wiphy);
62787b3115f2SLuciano Coelho 
62797b3115f2SLuciano Coelho 	SET_IEEE80211_DEV(wl->hw, wl->dev);
62807b3115f2SLuciano Coelho 
62817b3115f2SLuciano Coelho 	wl->hw->sta_data_size = sizeof(struct wl1271_station);
62827b3115f2SLuciano Coelho 	wl->hw->vif_data_size = sizeof(struct wl12xx_vif);
6283ba421f8fSArik Nemtsov 
62847b3115f2SLuciano Coelho 	wl->hw->max_rx_aggregation_subframes = wl->conf.ht.rx_ba_win_size;
62857b3115f2SLuciano Coelho 
62867b3115f2SLuciano Coelho 	return 0;
62877b3115f2SLuciano Coelho }
6288c50a2825SEliad Peller 
wlcore_alloc_hw(size_t priv_size,u32 aggr_buf_size,u32 mbox_size)6289c50a2825SEliad Peller struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size,
62907b3115f2SLuciano Coelho 				     u32 mbox_size)
62917b3115f2SLuciano Coelho {
62927b3115f2SLuciano Coelho 	struct ieee80211_hw *hw;
62937b3115f2SLuciano Coelho 	struct wl1271 *wl;
62947b3115f2SLuciano Coelho 	int i, j, ret;
62957b3115f2SLuciano Coelho 	unsigned int order;
62967b3115f2SLuciano Coelho 
62977b3115f2SLuciano Coelho 	hw = ieee80211_alloc_hw(sizeof(*wl), &wl1271_ops);
62987b3115f2SLuciano Coelho 	if (!hw) {
62997b3115f2SLuciano Coelho 		wl1271_error("could not alloc ieee80211_hw");
63007b3115f2SLuciano Coelho 		ret = -ENOMEM;
63017b3115f2SLuciano Coelho 		goto err_hw_alloc;
63027b3115f2SLuciano Coelho 	}
63037b3115f2SLuciano Coelho 
63047b3115f2SLuciano Coelho 	wl = hw->priv;
63057b3115f2SLuciano Coelho 	memset(wl, 0, sizeof(*wl));
630696e0c683SArik Nemtsov 
630796e0c683SArik Nemtsov 	wl->priv = kzalloc(priv_size, GFP_KERNEL);
630896e0c683SArik Nemtsov 	if (!wl->priv) {
630996e0c683SArik Nemtsov 		wl1271_error("could not alloc wl priv");
631096e0c683SArik Nemtsov 		ret = -ENOMEM;
631196e0c683SArik Nemtsov 		goto err_priv_alloc;
631296e0c683SArik Nemtsov 	}
63137b3115f2SLuciano Coelho 
63147b3115f2SLuciano Coelho 	INIT_LIST_HEAD(&wl->wlvif_list);
63157b3115f2SLuciano Coelho 
63167b3115f2SLuciano Coelho 	wl->hw = hw;
6317da08fdfaSEliad Peller 
6318da08fdfaSEliad Peller 	/*
6319da08fdfaSEliad Peller 	 * wl->num_links is not configured yet, so just use WLCORE_MAX_LINKS.
6320da08fdfaSEliad Peller 	 * we don't allocate any additional resource here, so that's fine.
63217b3115f2SLuciano Coelho 	 */
6322da08fdfaSEliad Peller 	for (i = 0; i < NUM_TX_QUEUES; i++)
63237b3115f2SLuciano Coelho 		for (j = 0; j < WLCORE_MAX_LINKS; j++)
63247b3115f2SLuciano Coelho 			skb_queue_head_init(&wl->links[j].tx_queue[i]);
63257b3115f2SLuciano Coelho 
63267b3115f2SLuciano Coelho 	skb_queue_head_init(&wl->deferred_rx_queue);
63277b3115f2SLuciano Coelho 	skb_queue_head_init(&wl->deferred_tx_queue);
63287b3115f2SLuciano Coelho 
63297b3115f2SLuciano Coelho 	INIT_WORK(&wl->netstack_work, wl1271_netstack_work);
63307b3115f2SLuciano Coelho 	INIT_WORK(&wl->tx_work, wl1271_tx_work);
63317b3115f2SLuciano Coelho 	INIT_WORK(&wl->recovery_work, wl1271_recovery_work);
6332dabf37dbSEliad Peller 	INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work);
63337b3115f2SLuciano Coelho 	INIT_DELAYED_WORK(&wl->roc_complete_work, wlcore_roc_complete_work);
63347b3115f2SLuciano Coelho 	INIT_DELAYED_WORK(&wl->tx_watchdog_work, wl12xx_tx_watchdog_work);
63357b3115f2SLuciano Coelho 
63367b3115f2SLuciano Coelho 	wl->freezable_wq = create_freezable_workqueue("wl12xx_wq");
63377b3115f2SLuciano Coelho 	if (!wl->freezable_wq) {
63387b3115f2SLuciano Coelho 		ret = -ENOMEM;
63397b3115f2SLuciano Coelho 		goto err_hw;
63407b3115f2SLuciano Coelho 	}
63418f6ac537SLuciano Coelho 
63427b3115f2SLuciano Coelho 	wl->channel = 0;
63437b3115f2SLuciano Coelho 	wl->rx_counter = 0;
634457fbcce3SJohannes Berg 	wl->power_level = WL1271_DEFAULT_POWER_LEVEL;
634583d08d3fSArik Nemtsov 	wl->band = NL80211_BAND_2GHZ;
63467b3115f2SLuciano Coelho 	wl->channel_type = NL80211_CHAN_NO_HT;
63477b3115f2SLuciano Coelho 	wl->flags = 0;
634866340e5bSArik Nemtsov 	wl->sg_enabled = true;
6349c108c905SLuciano Coelho 	wl->sleep_auth = WL1271_PSM_ILLEGAL;
63507b3115f2SLuciano Coelho 	wl->recovery_count = 0;
63517b3115f2SLuciano Coelho 	wl->hw_pg_ver = -1;
63527b3115f2SLuciano Coelho 	wl->ap_ps_map = 0;
63537b3115f2SLuciano Coelho 	wl->ap_fw_ps_map = 0;
63547b3115f2SLuciano Coelho 	wl->quirks = 0;
63557b3115f2SLuciano Coelho 	wl->system_hlid = WL12XX_SYSTEM_HLID;
63569a100968SArik Nemtsov 	wl->active_sta_count = 0;
63577b3115f2SLuciano Coelho 	wl->active_link_count = 0;
63587b3115f2SLuciano Coelho 	wl->fwlog_size = 0;
63597b3115f2SLuciano Coelho 
63607b3115f2SLuciano Coelho 	/* The system link is always allocated */
63617b3115f2SLuciano Coelho 	__set_bit(WL12XX_SYSTEM_HLID, wl->links_map);
63627b3115f2SLuciano Coelho 
636372b0624fSArik Nemtsov 	memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map));
63647b3115f2SLuciano Coelho 	for (i = 0; i < wl->num_tx_desc; i++)
63657b3115f2SLuciano Coelho 		wl->tx_frames[i] = NULL;
63667b3115f2SLuciano Coelho 
63677b3115f2SLuciano Coelho 	spin_lock_init(&wl->wl_lock);
63684cc53383SIdo Yariv 
63697b3115f2SLuciano Coelho 	wl->state = WLCORE_STATE_OFF;
63707b3115f2SLuciano Coelho 	wl->fw_type = WL12XX_FW_TYPE_NONE;
63712c38849fSArik Nemtsov 	mutex_init(&wl->mutex);
63726f8d6b20SIdo Yariv 	mutex_init(&wl->flush_mutex);
63737b3115f2SLuciano Coelho 	init_completion(&wl->nvs_loading_complete);
637426a309c7SIgal Chernobelsky 
63757b3115f2SLuciano Coelho 	order = get_order(aggr_buf_size);
63767b3115f2SLuciano Coelho 	wl->aggr_buf = (u8 *)__get_free_pages(GFP_KERNEL, order);
63777b3115f2SLuciano Coelho 	if (!wl->aggr_buf) {
63787b3115f2SLuciano Coelho 		ret = -ENOMEM;
63797b3115f2SLuciano Coelho 		goto err_wq;
638026a309c7SIgal Chernobelsky 	}
63817b3115f2SLuciano Coelho 	wl->aggr_buf_size = aggr_buf_size;
63827b3115f2SLuciano Coelho 
63837b3115f2SLuciano Coelho 	wl->dummy_packet = wl12xx_alloc_dummy_packet(wl);
63847b3115f2SLuciano Coelho 	if (!wl->dummy_packet) {
63857b3115f2SLuciano Coelho 		ret = -ENOMEM;
63867b3115f2SLuciano Coelho 		goto err_aggr;
63877b3115f2SLuciano Coelho 	}
63887b3115f2SLuciano Coelho 
63897b3115f2SLuciano Coelho 	/* Allocate one page for the FW log */
63907b3115f2SLuciano Coelho 	wl->fwlog = (u8 *)get_zeroed_page(GFP_KERNEL);
63917b3115f2SLuciano Coelho 	if (!wl->fwlog) {
63927b3115f2SLuciano Coelho 		ret = -ENOMEM;
63937b3115f2SLuciano Coelho 		goto err_dummy_packet;
63947b3115f2SLuciano Coelho 	}
6395c50a2825SEliad Peller 
6396c50a2825SEliad Peller 	wl->mbox_size = mbox_size;
63977b3115f2SLuciano Coelho 	wl->mbox = kmalloc(wl->mbox_size, GFP_KERNEL | GFP_DMA);
63987b3115f2SLuciano Coelho 	if (!wl->mbox) {
63997b3115f2SLuciano Coelho 		ret = -ENOMEM;
64007b3115f2SLuciano Coelho 		goto err_fwlog;
64017b3115f2SLuciano Coelho 	}
64022e07d028SIdo Yariv 
64032e07d028SIdo Yariv 	wl->buffer_32 = kmalloc(sizeof(*wl->buffer_32), GFP_KERNEL);
64042e07d028SIdo Yariv 	if (!wl->buffer_32) {
64052e07d028SIdo Yariv 		ret = -ENOMEM;
64062e07d028SIdo Yariv 		goto err_mbox;
64072e07d028SIdo Yariv 	}
64087b3115f2SLuciano Coelho 
64097b3115f2SLuciano Coelho 	return hw;
64102e07d028SIdo Yariv 
64112e07d028SIdo Yariv err_mbox:
64122e07d028SIdo Yariv 	kfree(wl->mbox);
64137b3115f2SLuciano Coelho 
64147b3115f2SLuciano Coelho err_fwlog:
64157b3115f2SLuciano Coelho 	free_page((unsigned long)wl->fwlog);
64167b3115f2SLuciano Coelho 
64177b3115f2SLuciano Coelho err_dummy_packet:
64187b3115f2SLuciano Coelho 	dev_kfree_skb(wl->dummy_packet);
64197b3115f2SLuciano Coelho 
64207b3115f2SLuciano Coelho err_aggr:
64217b3115f2SLuciano Coelho 	free_pages((unsigned long)wl->aggr_buf, order);
64227b3115f2SLuciano Coelho 
64237b3115f2SLuciano Coelho err_wq:
64247b3115f2SLuciano Coelho 	destroy_workqueue(wl->freezable_wq);
64257b3115f2SLuciano Coelho 
64267b3115f2SLuciano Coelho err_hw:
642796e0c683SArik Nemtsov 	wl1271_debugfs_exit(wl);
642896e0c683SArik Nemtsov 	kfree(wl->priv);
642996e0c683SArik Nemtsov 
64307b3115f2SLuciano Coelho err_priv_alloc:
64317b3115f2SLuciano Coelho 	ieee80211_free_hw(hw);
64327b3115f2SLuciano Coelho 
64337b3115f2SLuciano Coelho err_hw_alloc:
64347b3115f2SLuciano Coelho 
64357b3115f2SLuciano Coelho 	return ERR_PTR(ret);
6436ffeb501cSLuciano Coelho }
64377b3115f2SLuciano Coelho EXPORT_SYMBOL_GPL(wlcore_alloc_hw);
6438ffeb501cSLuciano Coelho 
wlcore_free_hw(struct wl1271 * wl)64397b3115f2SLuciano Coelho int wlcore_free_hw(struct wl1271 *wl)
64407b3115f2SLuciano Coelho {
64417b3115f2SLuciano Coelho 	/* Unblock any fwlog readers */
64427b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
64437b3115f2SLuciano Coelho 	wl->fwlog_size = -1;
64447b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
644533cab57aSLuciano Coelho 
64467b3115f2SLuciano Coelho 	wlcore_sysfs_free(wl);
64472e07d028SIdo Yariv 
6448a8e27820SEliad Peller 	kfree(wl->buffer_32);
64497b3115f2SLuciano Coelho 	kfree(wl->mbox);
64507b3115f2SLuciano Coelho 	free_page((unsigned long)wl->fwlog);
645126a309c7SIgal Chernobelsky 	dev_kfree_skb(wl->dummy_packet);
64527b3115f2SLuciano Coelho 	free_pages((unsigned long)wl->aggr_buf, get_order(wl->aggr_buf_size));
64537b3115f2SLuciano Coelho 
64547b3115f2SLuciano Coelho 	wl1271_debugfs_exit(wl);
64557b3115f2SLuciano Coelho 
64567b3115f2SLuciano Coelho 	vfree(wl->fw);
64577b3115f2SLuciano Coelho 	wl->fw = NULL;
64587b3115f2SLuciano Coelho 	wl->fw_type = WL12XX_FW_TYPE_NONE;
64597b3115f2SLuciano Coelho 	kfree(wl->nvs);
64607b3115f2SLuciano Coelho 	wl->nvs = NULL;
646175fb4df7SEliad Peller 
646275fb4df7SEliad Peller 	kfree(wl->raw_fw_status);
64637b3115f2SLuciano Coelho 	kfree(wl->fw_status);
64647b3115f2SLuciano Coelho 	kfree(wl->tx_res_if);
64657b3115f2SLuciano Coelho 	destroy_workqueue(wl->freezable_wq);
646696e0c683SArik Nemtsov 
64677b3115f2SLuciano Coelho 	kfree(wl->priv);
64687b3115f2SLuciano Coelho 	ieee80211_free_hw(wl->hw);
64697b3115f2SLuciano Coelho 
64707b3115f2SLuciano Coelho 	return 0;
6471ffeb501cSLuciano Coelho }
64727b3115f2SLuciano Coelho EXPORT_SYMBOL_GPL(wlcore_free_hw);
6473964dc9e2SJohannes Berg 
6474964dc9e2SJohannes Berg #ifdef CONFIG_PM
6475964dc9e2SJohannes Berg static const struct wiphy_wowlan_support wlcore_wowlan_support = {
6476964dc9e2SJohannes Berg 	.flags = WIPHY_WOWLAN_ANY,
6477964dc9e2SJohannes Berg 	.n_patterns = WL1271_MAX_RX_FILTERS,
6478964dc9e2SJohannes Berg 	.pattern_min_len = 1,
6479964dc9e2SJohannes Berg 	.pattern_max_len = WL1271_RX_FILTER_MAX_PATTERN_SIZE,
6480964dc9e2SJohannes Berg };
6481964dc9e2SJohannes Berg #endif
6482f2cede49SArik Nemtsov 
wlcore_hardirq(int irq,void * cookie)6483f2cede49SArik Nemtsov static irqreturn_t wlcore_hardirq(int irq, void *cookie)
6484f2cede49SArik Nemtsov {
6485f2cede49SArik Nemtsov 	return IRQ_WAKE_THREAD;
6486f2cede49SArik Nemtsov }
64876f8d6b20SIdo Yariv 
wlcore_nvs_cb(const struct firmware * fw,void * context)64887b3115f2SLuciano Coelho static void wlcore_nvs_cb(const struct firmware *fw, void *context)
64896f8d6b20SIdo Yariv {
64906f8d6b20SIdo Yariv 	struct wl1271 *wl = context;
649190650625SJingoo Han 	struct platform_device *pdev = wl->pdev;
64926f921fabSLuciano Coelho 	struct wlcore_platdev_data *pdev_data = dev_get_platdata(&pdev->dev);
64936f921fabSLuciano Coelho 	struct resource *res;
6494ffeb501cSLuciano Coelho 
6495f2cede49SArik Nemtsov 	int ret;
64967b3115f2SLuciano Coelho 	irq_handler_t hardirq_fn = NULL;
64976f8d6b20SIdo Yariv 
64986f8d6b20SIdo Yariv 	if (fw) {
64996f8d6b20SIdo Yariv 		wl->nvs = kmemdup(fw->data, fw->size, GFP_KERNEL);
65006f8d6b20SIdo Yariv 		if (!wl->nvs) {
65013992eb2bSIdo Yariv 			wl1271_error("Could not allocate nvs data");
6502c31be25aSLuciano Coelho 			goto out;
65036f8d6b20SIdo Yariv 		}
65043e1ac932STony Lindgren 		wl->nvs_len = fw->size;
65056f8d6b20SIdo Yariv 	} else if (pdev_data->family->nvs_name) {
65063e1ac932STony Lindgren 		wl1271_debug(DEBUG_BOOT, "Could not get nvs file %s",
65073e1ac932STony Lindgren 			     pdev_data->family->nvs_name);
65083e1ac932STony Lindgren 		wl->nvs = NULL;
65093e1ac932STony Lindgren 		wl->nvs_len = 0;
65106f8d6b20SIdo Yariv 	} else {
65116f8d6b20SIdo Yariv 		wl->nvs = NULL;
65126f8d6b20SIdo Yariv 		wl->nvs_len = 0;
65133992eb2bSIdo Yariv 	}
65143992eb2bSIdo Yariv 
65153992eb2bSIdo Yariv 	ret = wl->ops->setup(wl);
65166f8d6b20SIdo Yariv 	if (ret < 0)
65173992eb2bSIdo Yariv 		goto out_free_nvs;
651872b0624fSArik Nemtsov 
651972b0624fSArik Nemtsov 	BUG_ON(wl->num_tx_desc > WLCORE_MAX_TX_DESCRIPTORS);
6520e87288f0SLuciano Coelho 
6521e87288f0SLuciano Coelho 	/* adjust some runtime configuration parameters */
6522e87288f0SLuciano Coelho 	wlcore_adjust_conf(wl);
65236f921fabSLuciano Coelho 
65246f921fabSLuciano Coelho 	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
65256f921fabSLuciano Coelho 	if (!res) {
65266f921fabSLuciano Coelho 		wl1271_error("Could not get IRQ resource");
6527f2cede49SArik Nemtsov 		goto out_free_nvs;
65287b3115f2SLuciano Coelho 	}
65296f921fabSLuciano Coelho 
65306f921fabSLuciano Coelho 	wl->irq = res->start;
65316f921fabSLuciano Coelho 	wl->irq_flags = res->flags & IRQF_TRIGGER_MASK;
65326f921fabSLuciano Coelho 	wl->if_ops = pdev_data->if_ops;
65336f921fabSLuciano Coelho 
65346f921fabSLuciano Coelho 	if (wl->irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING))
65356f921fabSLuciano Coelho 		hardirq_fn = wlcore_hardirq;
65366f921fabSLuciano Coelho 	else
65376f921fabSLuciano Coelho 		wl->irq_flags |= IRQF_ONESHOT;
6538bd763482SEyal Reizer 
6539bd763482SEyal Reizer 	ret = wl12xx_set_power_on(wl);
6540bd763482SEyal Reizer 	if (ret < 0)
6541bd763482SEyal Reizer 		goto out_free_nvs;
6542bd763482SEyal Reizer 
6543bd763482SEyal Reizer 	ret = wl12xx_get_hw_info(wl);
6544bd763482SEyal Reizer 	if (ret < 0) {
6545bd763482SEyal Reizer 		wl1271_error("couldn't get hw info");
6546bd763482SEyal Reizer 		wl1271_power_off(wl);
6547bd763482SEyal Reizer 		goto out_free_nvs;
6548bd763482SEyal Reizer 	}
6549f2cede49SArik Nemtsov 
65506f921fabSLuciano Coelho 	ret = request_threaded_irq(wl->irq, hardirq_fn, wlcore_irq,
65517b3115f2SLuciano Coelho 				   wl->irq_flags, pdev->name, wl);
6552bd763482SEyal Reizer 	if (ret < 0) {
6553bd763482SEyal Reizer 		wl1271_error("interrupt configuration failed");
65546f8d6b20SIdo Yariv 		wl1271_power_off(wl);
65557b3115f2SLuciano Coelho 		goto out_free_nvs;
65567b3115f2SLuciano Coelho 	}
6557dfb89c56SJohannes Berg 
65583c83dd57STony Lindgren #ifdef CONFIG_PM
65593c83dd57STony Lindgren 	device_init_wakeup(wl->dev, true);
65607b3115f2SLuciano Coelho 
65617b3115f2SLuciano Coelho 	ret = enable_irq_wake(wl->irq);
65627b3115f2SLuciano Coelho 	if (!ret) {
656383c3a7d4SEliad Peller 		wl->irq_wake_enabled = true;
6564964dc9e2SJohannes Berg 		if (pdev_data->pwr_in_suspend)
65657b3115f2SLuciano Coelho 			wl->hw->wiphy->wowlan = &wlcore_wowlan_support;
65663c83dd57STony Lindgren 	}
65673c83dd57STony Lindgren 
65683c83dd57STony Lindgren 	res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
65693c83dd57STony Lindgren 	if (res) {
65703c83dd57STony Lindgren 		wl->wakeirq = res->start;
65713c83dd57STony Lindgren 		wl->wakeirq_flags = res->flags & IRQF_TRIGGER_MASK;
65723c83dd57STony Lindgren 		ret = dev_pm_set_dedicated_wake_irq(wl->dev, wl->wakeirq);
65733c83dd57STony Lindgren 		if (ret)
65743c83dd57STony Lindgren 			wl->wakeirq = -ENODEV;
65753c83dd57STony Lindgren 	} else {
65763c83dd57STony Lindgren 		wl->wakeirq = -ENODEV;
6577dfb89c56SJohannes Berg 	}
65787b3115f2SLuciano Coelho #endif
6579bd763482SEyal Reizer 	disable_irq(wl->irq);
65804afc37a0SLuciano Coelho 	wl1271_power_off(wl);
65814afc37a0SLuciano Coelho 
65824afc37a0SLuciano Coelho 	ret = wl->ops->identify_chip(wl);
65838b425e62SLuciano Coelho 	if (ret < 0)
65844afc37a0SLuciano Coelho 		goto out_irq;
65857b3115f2SLuciano Coelho 
65867b3115f2SLuciano Coelho 	ret = wl1271_init_ieee80211(wl);
65877b3115f2SLuciano Coelho 	if (ret)
65887b3115f2SLuciano Coelho 		goto out_irq;
65897b3115f2SLuciano Coelho 
65907b3115f2SLuciano Coelho 	ret = wl1271_register_hw(wl);
65917b3115f2SLuciano Coelho 	if (ret)
65927b3115f2SLuciano Coelho 		goto out_irq;
659333cab57aSLuciano Coelho 
659433cab57aSLuciano Coelho 	ret = wlcore_sysfs_init(wl);
65958b425e62SLuciano Coelho 	if (ret)
65967b3115f2SLuciano Coelho 		goto out_unreg;
65976f8d6b20SIdo Yariv 
6598ffeb501cSLuciano Coelho 	wl->initialized = true;
65997b3115f2SLuciano Coelho 	goto out;
66008b425e62SLuciano Coelho 
66018b425e62SLuciano Coelho out_unreg:
66028b425e62SLuciano Coelho 	wl1271_unregister_hw(wl);
66037b3115f2SLuciano Coelho 
66043c83dd57STony Lindgren out_irq:
66053c83dd57STony Lindgren 	if (wl->wakeirq >= 0)
66063c83dd57STony Lindgren 		dev_pm_clear_wake_irq(wl->dev);
66077b3115f2SLuciano Coelho 	device_init_wakeup(wl->dev, false);
66087b3115f2SLuciano Coelho 	free_irq(wl->irq, wl);
66096f8d6b20SIdo Yariv 
66106f8d6b20SIdo Yariv out_free_nvs:
66116f8d6b20SIdo Yariv 	kfree(wl->nvs);
66127b3115f2SLuciano Coelho 
66136f8d6b20SIdo Yariv out:
66146f8d6b20SIdo Yariv 	release_firmware(fw);
66156f8d6b20SIdo Yariv 	complete_all(&wl->nvs_loading_complete);
66166f8d6b20SIdo Yariv }
6617fa2648a3STony Lindgren 
wlcore_runtime_suspend(struct device * dev)6618fa2648a3STony Lindgren static int __maybe_unused wlcore_runtime_suspend(struct device *dev)
6619fa2648a3STony Lindgren {
6620fa2648a3STony Lindgren 	struct wl1271 *wl = dev_get_drvdata(dev);
6621fa2648a3STony Lindgren 	struct wl12xx_vif *wlvif;
6622fa2648a3STony Lindgren 	int error;
6623fa2648a3STony Lindgren 
6624fa2648a3STony Lindgren 	/* We do not enter elp sleep in PLT mode */
6625fa2648a3STony Lindgren 	if (wl->plt)
6626fa2648a3STony Lindgren 		return 0;
6627fa2648a3STony Lindgren 
6628fa2648a3STony Lindgren 	/* Nothing to do if no ELP mode requested */
6629fa2648a3STony Lindgren 	if (wl->sleep_auth != WL1271_PSM_ELP)
6630fa2648a3STony Lindgren 		return 0;
6631fa2648a3STony Lindgren 
6632fa2648a3STony Lindgren 	wl12xx_for_each_wlvif(wl, wlvif) {
6633fa2648a3STony Lindgren 		if (!test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags) &&
6634fa2648a3STony Lindgren 		    test_bit(WLVIF_FLAG_IN_USE, &wlvif->flags))
6635fa2648a3STony Lindgren 			return -EBUSY;
6636fa2648a3STony Lindgren 	}
6637fa2648a3STony Lindgren 
6638fa2648a3STony Lindgren 	wl1271_debug(DEBUG_PSM, "chip to elp");
6639fa2648a3STony Lindgren 	error = wlcore_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG, ELPCTRL_SLEEP);
6640fa2648a3STony Lindgren 	if (error < 0) {
6641fa2648a3STony Lindgren 		wl12xx_queue_recovery_work(wl);
6642fa2648a3STony Lindgren 
6643fa2648a3STony Lindgren 		return error;
6644fa2648a3STony Lindgren 	}
6645fa2648a3STony Lindgren 
6646fa2648a3STony Lindgren 	set_bit(WL1271_FLAG_IN_ELP, &wl->flags);
6647fa2648a3STony Lindgren 
6648fa2648a3STony Lindgren 	return 0;
6649fa2648a3STony Lindgren }
6650fa2648a3STony Lindgren 
wlcore_runtime_resume(struct device * dev)6651fa2648a3STony Lindgren static int __maybe_unused wlcore_runtime_resume(struct device *dev)
6652fa2648a3STony Lindgren {
6653fa2648a3STony Lindgren 	struct wl1271 *wl = dev_get_drvdata(dev);
6654fa2648a3STony Lindgren 	DECLARE_COMPLETION_ONSTACK(compl);
6655fa2648a3STony Lindgren 	unsigned long flags;
6656fa2648a3STony Lindgren 	int ret;
66574e651badSTony Lindgren 	unsigned long start_time = jiffies;
6658fa2648a3STony Lindgren 	bool recovery = false;
6659fa2648a3STony Lindgren 
6660fa2648a3STony Lindgren 	/* Nothing to do if no ELP mode requested */
6661fa2648a3STony Lindgren 	if (!test_bit(WL1271_FLAG_IN_ELP, &wl->flags))
6662fa2648a3STony Lindgren 		return 0;
6663fa2648a3STony Lindgren 
6664fa2648a3STony Lindgren 	wl1271_debug(DEBUG_PSM, "waking up chip from elp");
6665fa2648a3STony Lindgren 
6666fa2648a3STony Lindgren 	spin_lock_irqsave(&wl->wl_lock, flags);
6667fa2648a3STony Lindgren 	wl->elp_compl = &compl;
6668fa2648a3STony Lindgren 	spin_unlock_irqrestore(&wl->wl_lock, flags);
6669fa2648a3STony Lindgren 
6670fa2648a3STony Lindgren 	ret = wlcore_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG, ELPCTRL_WAKE_UP);
66714e651badSTony Lindgren 	if (ret < 0) {
6672eb215c33STony Lindgren 		recovery = true;
6673fa2648a3STony Lindgren 	} else if (!test_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags)) {
6674fa2648a3STony Lindgren 		ret = wait_for_completion_timeout(&compl,
6675fa2648a3STony Lindgren 			msecs_to_jiffies(WL1271_WAKEUP_TIMEOUT));
66764e651badSTony Lindgren 		if (ret == 0) {
66774e651badSTony Lindgren 			wl1271_warning("ELP wakeup timeout!");
6678fa2648a3STony Lindgren 			recovery = true;
6679fa2648a3STony Lindgren 		}
6680fa2648a3STony Lindgren 	}
6681fa2648a3STony Lindgren 
6682fa2648a3STony Lindgren 	spin_lock_irqsave(&wl->wl_lock, flags);
6683fa2648a3STony Lindgren 	wl->elp_compl = NULL;
6684eb215c33STony Lindgren 	spin_unlock_irqrestore(&wl->wl_lock, flags);
66854e651badSTony Lindgren 	clear_bit(WL1271_FLAG_IN_ELP, &wl->flags);
66864e651badSTony Lindgren 
66874e651badSTony Lindgren 	if (recovery) {
66884e651badSTony Lindgren 		set_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags);
6689eb215c33STony Lindgren 		wl12xx_queue_recovery_work(wl);
6690eb215c33STony Lindgren 	} else {
6691eb215c33STony Lindgren 		wl1271_debug(DEBUG_PSM, "wakeup time: %u ms",
66924e651badSTony Lindgren 			     jiffies_to_msecs(jiffies - start_time));
66934e651badSTony Lindgren 	}
6694eb215c33STony Lindgren 
6695fa2648a3STony Lindgren 	return 0;
6696fa2648a3STony Lindgren }
6697fa2648a3STony Lindgren 
6698fa2648a3STony Lindgren static const struct dev_pm_ops wlcore_pm_ops = {
6699fa2648a3STony Lindgren 	SET_RUNTIME_PM_OPS(wlcore_runtime_suspend,
6700fa2648a3STony Lindgren 			   wlcore_runtime_resume,
6701fa2648a3STony Lindgren 			   NULL)
6702fa2648a3STony Lindgren };
6703b74324d1SBill Pemberton 
wlcore_probe(struct wl1271 * wl,struct platform_device * pdev)67046f8d6b20SIdo Yariv int wlcore_probe(struct wl1271 *wl, struct platform_device *pdev)
67053e1ac932STony Lindgren {
67063e1ac932STony Lindgren 	struct wlcore_platdev_data *pdev_data = dev_get_platdata(&pdev->dev);
67073e1ac932STony Lindgren 	const char *nvs_name;
67086f8d6b20SIdo Yariv 	int ret = 0;
67093e1ac932STony Lindgren 
67106f8d6b20SIdo Yariv 	if (!wl->ops || !wl->ptable || !pdev_data)
67116f8d6b20SIdo Yariv 		return -EINVAL;
67126f8d6b20SIdo Yariv 
67136f8d6b20SIdo Yariv 	wl->dev = &pdev->dev;
67146f8d6b20SIdo Yariv 	wl->pdev = pdev;
67156f8d6b20SIdo Yariv 	platform_set_drvdata(pdev, wl);
67163e1ac932STony Lindgren 
67173e1ac932STony Lindgren 	if (pdev_data->family && pdev_data->family->nvs_name) {
67180733d839SShawn Guo 		nvs_name = pdev_data->family->nvs_name;
67193e1ac932STony Lindgren 		ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
67206f8d6b20SIdo Yariv 					      nvs_name, &pdev->dev, GFP_KERNEL,
67216f8d6b20SIdo Yariv 					      wl, wlcore_nvs_cb);
67223e1ac932STony Lindgren 		if (ret < 0) {
67233e1ac932STony Lindgren 			wl1271_error("request_firmware_nowait failed for %s: %d",
67246f8d6b20SIdo Yariv 				     nvs_name, ret);
67256f8d6b20SIdo Yariv 			complete_all(&wl->nvs_loading_complete);
67263e1ac932STony Lindgren 		}
67273e1ac932STony Lindgren 	} else {
67283e1ac932STony Lindgren 		wlcore_nvs_cb(NULL, wl);
67296f8d6b20SIdo Yariv 	}
6730fa2648a3STony Lindgren 
67319b71578dSTony Lindgren 	wl->dev->driver->pm = &wlcore_pm_ops;
67329b71578dSTony Lindgren 	pm_runtime_set_autosuspend_delay(wl->dev, 50);
6733fa2648a3STony Lindgren 	pm_runtime_use_autosuspend(wl->dev);
6734fa2648a3STony Lindgren 	pm_runtime_enable(wl->dev);
67357b3115f2SLuciano Coelho 
67367b3115f2SLuciano Coelho 	return ret;
6737b2ba99ffSLuciano Coelho }
67387b3115f2SLuciano Coelho EXPORT_SYMBOL_GPL(wlcore_probe);
6739b74324d1SBill Pemberton 
wlcore_remove(struct platform_device * pdev)67407b3115f2SLuciano Coelho int wlcore_remove(struct platform_device *pdev)
67413e1ac932STony Lindgren {
67427b3115f2SLuciano Coelho 	struct wlcore_platdev_data *pdev_data = dev_get_platdata(&pdev->dev);
6743fa2648a3STony Lindgren 	struct wl1271 *wl = platform_get_drvdata(pdev);
6744fa2648a3STony Lindgren 	int error;
6745fa2648a3STony Lindgren 
6746fa2648a3STony Lindgren 	error = pm_runtime_get_sync(wl->dev);
6747fa2648a3STony Lindgren 	if (error < 0)
6748fa2648a3STony Lindgren 		dev_warn(wl->dev, "PM runtime failed: %i\n", error);
6749fa2648a3STony Lindgren 
67507b3115f2SLuciano Coelho 	wl->dev->driver->pm = NULL;
67513e1ac932STony Lindgren 
67526f8d6b20SIdo Yariv 	if (pdev_data->family && pdev_data->family->nvs_name)
67536f8d6b20SIdo Yariv 		wait_for_completion(&wl->nvs_loading_complete);
67546f8d6b20SIdo Yariv 	if (!wl->initialized)
67556f8d6b20SIdo Yariv 		return 0;
67563c83dd57STony Lindgren 
67573c83dd57STony Lindgren 	if (wl->wakeirq >= 0) {
67583c83dd57STony Lindgren 		dev_pm_clear_wake_irq(wl->dev);
67597b3115f2SLuciano Coelho 		wl->wakeirq = -ENODEV;
67603c83dd57STony Lindgren 	}
67613c83dd57STony Lindgren 
67623c83dd57STony Lindgren 	device_init_wakeup(wl->dev, false);
67633c83dd57STony Lindgren 
67643c83dd57STony Lindgren 	if (wl->irq_wake_enabled)
67653c83dd57STony Lindgren 		disable_irq_wake(wl->irq);
67667b3115f2SLuciano Coelho 
6767fa2648a3STony Lindgren 	wl1271_unregister_hw(wl);
6768fa2648a3STony Lindgren 
67699b71578dSTony Lindgren 	pm_runtime_put_sync(wl->dev);
6770fa2648a3STony Lindgren 	pm_runtime_dont_use_autosuspend(wl->dev);
6771fa2648a3STony Lindgren 	pm_runtime_disable(wl->dev);
67727b3115f2SLuciano Coelho 
6773ffeb501cSLuciano Coelho 	free_irq(wl->irq, wl);
67747b3115f2SLuciano Coelho 	wlcore_free_hw(wl);
67757b3115f2SLuciano Coelho 
67767b3115f2SLuciano Coelho 	return 0;
6777b2ba99ffSLuciano Coelho }
67787b3115f2SLuciano Coelho EXPORT_SYMBOL_GPL(wlcore_remove);
67797b3115f2SLuciano Coelho 
67807b3115f2SLuciano Coelho u32 wl12xx_debug_level = DEBUG_NONE;
67812ef00c53SJoe Perches EXPORT_SYMBOL_GPL(wl12xx_debug_level);
67827b3115f2SLuciano Coelho module_param_named(debug_level, wl12xx_debug_level, uint, 0600);
67837b3115f2SLuciano Coelho MODULE_PARM_DESC(debug_level, "wl12xx debugging level");
67847b3115f2SLuciano Coelho 
67857b3115f2SLuciano Coelho module_param_named(fwlog, fwlog_param, charp, 0);
67863719c17eSShahar Patury MODULE_PARM_DESC(fwlog,
67877b3115f2SLuciano Coelho 		 "FW logger options: continuous, dbgpins or disable");
67882ef00c53SJoe Perches 
678993ac8488SIdo Reis module_param(fwlog_mem_blocks, int, 0600);
679093ac8488SIdo Reis MODULE_PARM_DESC(fwlog_mem_blocks, "fwlog mem_blocks");
67912ef00c53SJoe Perches 
67927b3115f2SLuciano Coelho module_param(bug_on_recovery, int, 0600);
67937b3115f2SLuciano Coelho MODULE_PARM_DESC(bug_on_recovery, "BUG() on fw recovery");
67942ef00c53SJoe Perches 
679534785be5SArik Nemtsov module_param(no_recovery, int, 0600);
679634785be5SArik Nemtsov MODULE_PARM_DESC(no_recovery, "Prevent HW recovery. FW will remain stuck.");
67977b3115f2SLuciano Coelho 
67987b3115f2SLuciano Coelho MODULE_LICENSE("GPL");
67997b3115f2SLuciano Coelho MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>");
6800 MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>");
6801