xref: /linux/drivers/net/wireless/st/cw1200/pm.c (revision 664b0bae0b87f69bc9deb098f5e0158b9cf18e04)
1a910e4a9SSolomon Peachy /*
2a910e4a9SSolomon Peachy  * Mac80211 power management API for ST-Ericsson CW1200 drivers
3a910e4a9SSolomon Peachy  *
4a910e4a9SSolomon Peachy  * Copyright (c) 2011, ST-Ericsson
5a910e4a9SSolomon Peachy  * Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
6a910e4a9SSolomon Peachy  *
7a910e4a9SSolomon Peachy  * This program is free software; you can redistribute it and/or modify
8a910e4a9SSolomon Peachy  * it under the terms of the GNU General Public License version 2 as
9a910e4a9SSolomon Peachy  * published by the Free Software Foundation.
10a910e4a9SSolomon Peachy  */
11a910e4a9SSolomon Peachy 
12a910e4a9SSolomon Peachy #include <linux/module.h>
13a910e4a9SSolomon Peachy #include <linux/if_ether.h>
14a910e4a9SSolomon Peachy #include "cw1200.h"
15a910e4a9SSolomon Peachy #include "pm.h"
16a910e4a9SSolomon Peachy #include "sta.h"
17a910e4a9SSolomon Peachy #include "bh.h"
18911373ccSSolomon Peachy #include "hwbus.h"
19a910e4a9SSolomon Peachy 
20a910e4a9SSolomon Peachy #define CW1200_BEACON_SKIPPING_MULTIPLIER 3
21a910e4a9SSolomon Peachy 
22a910e4a9SSolomon Peachy struct cw1200_udp_port_filter {
23a910e4a9SSolomon Peachy 	struct wsm_udp_port_filter_hdr hdr;
24a910e4a9SSolomon Peachy 	/* Up to 4 filters are allowed. */
25a910e4a9SSolomon Peachy 	struct wsm_udp_port_filter filters[WSM_MAX_FILTER_ELEMENTS];
26a910e4a9SSolomon Peachy } __packed;
27a910e4a9SSolomon Peachy 
28a910e4a9SSolomon Peachy struct cw1200_ether_type_filter {
29a910e4a9SSolomon Peachy 	struct wsm_ether_type_filter_hdr hdr;
30a910e4a9SSolomon Peachy 	/* Up to 4 filters are allowed. */
31a910e4a9SSolomon Peachy 	struct wsm_ether_type_filter filters[WSM_MAX_FILTER_ELEMENTS];
32a910e4a9SSolomon Peachy } __packed;
33a910e4a9SSolomon Peachy 
34a910e4a9SSolomon Peachy static struct cw1200_udp_port_filter cw1200_udp_port_filter_on = {
35a910e4a9SSolomon Peachy 	.hdr.num = 2,
36a910e4a9SSolomon Peachy 	.filters = {
37a910e4a9SSolomon Peachy 		[0] = {
38a910e4a9SSolomon Peachy 			.action = WSM_FILTER_ACTION_FILTER_OUT,
39a910e4a9SSolomon Peachy 			.type = WSM_FILTER_PORT_TYPE_DST,
40a910e4a9SSolomon Peachy 			.port = __cpu_to_le16(67), /* DHCP Bootps */
41a910e4a9SSolomon Peachy 		},
42a910e4a9SSolomon Peachy 		[1] = {
43a910e4a9SSolomon Peachy 			.action = WSM_FILTER_ACTION_FILTER_OUT,
44a910e4a9SSolomon Peachy 			.type = WSM_FILTER_PORT_TYPE_DST,
45a910e4a9SSolomon Peachy 			.port = __cpu_to_le16(68), /* DHCP Bootpc */
46a910e4a9SSolomon Peachy 		},
47a910e4a9SSolomon Peachy 	}
48a910e4a9SSolomon Peachy };
49a910e4a9SSolomon Peachy 
50a910e4a9SSolomon Peachy static struct wsm_udp_port_filter_hdr cw1200_udp_port_filter_off = {
51a910e4a9SSolomon Peachy 	.num = 0,
52a910e4a9SSolomon Peachy };
53a910e4a9SSolomon Peachy 
54a910e4a9SSolomon Peachy #ifndef ETH_P_WAPI
55a910e4a9SSolomon Peachy #define ETH_P_WAPI     0x88B4
56a910e4a9SSolomon Peachy #endif
57a910e4a9SSolomon Peachy 
58a910e4a9SSolomon Peachy static struct cw1200_ether_type_filter cw1200_ether_type_filter_on = {
59a910e4a9SSolomon Peachy 	.hdr.num = 4,
60a910e4a9SSolomon Peachy 	.filters = {
61a910e4a9SSolomon Peachy 		[0] = {
62a910e4a9SSolomon Peachy 			.action = WSM_FILTER_ACTION_FILTER_IN,
63a910e4a9SSolomon Peachy 			.type = __cpu_to_le16(ETH_P_IP),
64a910e4a9SSolomon Peachy 		},
65a910e4a9SSolomon Peachy 		[1] = {
66a910e4a9SSolomon Peachy 			.action = WSM_FILTER_ACTION_FILTER_IN,
67a910e4a9SSolomon Peachy 			.type = __cpu_to_le16(ETH_P_PAE),
68a910e4a9SSolomon Peachy 		},
69a910e4a9SSolomon Peachy 		[2] = {
70a910e4a9SSolomon Peachy 			.action = WSM_FILTER_ACTION_FILTER_IN,
71a910e4a9SSolomon Peachy 			.type = __cpu_to_le16(ETH_P_WAPI),
72a910e4a9SSolomon Peachy 		},
73a910e4a9SSolomon Peachy 		[3] = {
74a910e4a9SSolomon Peachy 			.action = WSM_FILTER_ACTION_FILTER_IN,
75a910e4a9SSolomon Peachy 			.type = __cpu_to_le16(ETH_P_ARP),
76a910e4a9SSolomon Peachy 		},
77a910e4a9SSolomon Peachy 	},
78a910e4a9SSolomon Peachy };
79a910e4a9SSolomon Peachy 
80a910e4a9SSolomon Peachy static struct wsm_ether_type_filter_hdr cw1200_ether_type_filter_off = {
81a910e4a9SSolomon Peachy 	.num = 0,
82a910e4a9SSolomon Peachy };
83a910e4a9SSolomon Peachy 
84a910e4a9SSolomon Peachy /* private */
85a910e4a9SSolomon Peachy struct cw1200_suspend_state {
86a910e4a9SSolomon Peachy 	unsigned long bss_loss_tmo;
87a910e4a9SSolomon Peachy 	unsigned long join_tmo;
88a910e4a9SSolomon Peachy 	unsigned long direct_probe;
89a910e4a9SSolomon Peachy 	unsigned long link_id_gc;
90a910e4a9SSolomon Peachy 	bool beacon_skipping;
91a910e4a9SSolomon Peachy 	u8 prev_ps_mode;
92a910e4a9SSolomon Peachy };
93a910e4a9SSolomon Peachy 
94*d3e99b2dSKees Cook static void cw1200_pm_stay_awake_tmo(struct timer_list *unused)
95a910e4a9SSolomon Peachy {
96a910e4a9SSolomon Peachy 	/* XXX what's the point of this ? */
97a910e4a9SSolomon Peachy }
98a910e4a9SSolomon Peachy 
99a910e4a9SSolomon Peachy int cw1200_pm_init(struct cw1200_pm_state *pm,
100a910e4a9SSolomon Peachy 		   struct cw1200_common *priv)
101a910e4a9SSolomon Peachy {
102a910e4a9SSolomon Peachy 	spin_lock_init(&pm->lock);
103a910e4a9SSolomon Peachy 
104*d3e99b2dSKees Cook 	timer_setup(&pm->stay_awake, cw1200_pm_stay_awake_tmo, 0);
105a910e4a9SSolomon Peachy 
106a910e4a9SSolomon Peachy 	return 0;
107a910e4a9SSolomon Peachy }
108a910e4a9SSolomon Peachy 
109a910e4a9SSolomon Peachy void cw1200_pm_deinit(struct cw1200_pm_state *pm)
110a910e4a9SSolomon Peachy {
111a910e4a9SSolomon Peachy 	del_timer_sync(&pm->stay_awake);
112a910e4a9SSolomon Peachy }
113a910e4a9SSolomon Peachy 
114a910e4a9SSolomon Peachy void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
115a910e4a9SSolomon Peachy 			  unsigned long tmo)
116a910e4a9SSolomon Peachy {
117a910e4a9SSolomon Peachy 	long cur_tmo;
118a910e4a9SSolomon Peachy 	spin_lock_bh(&pm->lock);
119a910e4a9SSolomon Peachy 	cur_tmo = pm->stay_awake.expires - jiffies;
120a910e4a9SSolomon Peachy 	if (!timer_pending(&pm->stay_awake) || cur_tmo < (long)tmo)
121a910e4a9SSolomon Peachy 		mod_timer(&pm->stay_awake, jiffies + tmo);
122a910e4a9SSolomon Peachy 	spin_unlock_bh(&pm->lock);
123a910e4a9SSolomon Peachy }
124a910e4a9SSolomon Peachy 
125a910e4a9SSolomon Peachy static long cw1200_suspend_work(struct delayed_work *work)
126a910e4a9SSolomon Peachy {
127a910e4a9SSolomon Peachy 	int ret = cancel_delayed_work(work);
128a910e4a9SSolomon Peachy 	long tmo;
129a910e4a9SSolomon Peachy 	if (ret > 0) {
130a910e4a9SSolomon Peachy 		/* Timer is pending */
131a910e4a9SSolomon Peachy 		tmo = work->timer.expires - jiffies;
132a910e4a9SSolomon Peachy 		if (tmo < 0)
133a910e4a9SSolomon Peachy 			tmo = 0;
134a910e4a9SSolomon Peachy 	} else {
135a910e4a9SSolomon Peachy 		tmo = -1;
136a910e4a9SSolomon Peachy 	}
137a910e4a9SSolomon Peachy 	return tmo;
138a910e4a9SSolomon Peachy }
139a910e4a9SSolomon Peachy 
140a910e4a9SSolomon Peachy static int cw1200_resume_work(struct cw1200_common *priv,
141a910e4a9SSolomon Peachy 			       struct delayed_work *work,
142a910e4a9SSolomon Peachy 			       unsigned long tmo)
143a910e4a9SSolomon Peachy {
144a910e4a9SSolomon Peachy 	if ((long)tmo < 0)
145a910e4a9SSolomon Peachy 		return 1;
146a910e4a9SSolomon Peachy 
147a910e4a9SSolomon Peachy 	return queue_delayed_work(priv->workqueue, work, tmo);
148a910e4a9SSolomon Peachy }
149a910e4a9SSolomon Peachy 
150a910e4a9SSolomon Peachy int cw1200_can_suspend(struct cw1200_common *priv)
151a910e4a9SSolomon Peachy {
152a910e4a9SSolomon Peachy 	if (atomic_read(&priv->bh_rx)) {
153a910e4a9SSolomon Peachy 		wiphy_dbg(priv->hw->wiphy, "Suspend interrupted.\n");
154a910e4a9SSolomon Peachy 		return 0;
155a910e4a9SSolomon Peachy 	}
156a910e4a9SSolomon Peachy 	return 1;
157a910e4a9SSolomon Peachy }
158a910e4a9SSolomon Peachy EXPORT_SYMBOL_GPL(cw1200_can_suspend);
159a910e4a9SSolomon Peachy 
160a910e4a9SSolomon Peachy int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
161a910e4a9SSolomon Peachy {
162a910e4a9SSolomon Peachy 	struct cw1200_common *priv = hw->priv;
163a910e4a9SSolomon Peachy 	struct cw1200_pm_state *pm_state = &priv->pm_state;
164a910e4a9SSolomon Peachy 	struct cw1200_suspend_state *state;
165a910e4a9SSolomon Peachy 	int ret;
166a910e4a9SSolomon Peachy 
167a910e4a9SSolomon Peachy 	spin_lock_bh(&pm_state->lock);
168a910e4a9SSolomon Peachy 	ret = timer_pending(&pm_state->stay_awake);
169a910e4a9SSolomon Peachy 	spin_unlock_bh(&pm_state->lock);
170a910e4a9SSolomon Peachy 	if (ret)
171a910e4a9SSolomon Peachy 		return -EAGAIN;
172a910e4a9SSolomon Peachy 
173a910e4a9SSolomon Peachy 	/* Do not suspend when datapath is not idle */
174a910e4a9SSolomon Peachy 	if (priv->tx_queue_stats.num_queued)
175a910e4a9SSolomon Peachy 		return -EBUSY;
176a910e4a9SSolomon Peachy 
177a910e4a9SSolomon Peachy 	/* Make sure there is no configuration requests in progress. */
178a910e4a9SSolomon Peachy 	if (!mutex_trylock(&priv->conf_mutex))
179a910e4a9SSolomon Peachy 		return -EBUSY;
180a910e4a9SSolomon Peachy 
181a910e4a9SSolomon Peachy 	/* Ensure pending operations are done.
182a910e4a9SSolomon Peachy 	 * Note also that wow_suspend must return in ~2.5sec, before
183a910e4a9SSolomon Peachy 	 * watchdog is triggered.
184a910e4a9SSolomon Peachy 	 */
185a910e4a9SSolomon Peachy 	if (priv->channel_switch_in_progress)
186a910e4a9SSolomon Peachy 		goto revert1;
187a910e4a9SSolomon Peachy 
188a910e4a9SSolomon Peachy 	/* Do not suspend when join is pending */
189a910e4a9SSolomon Peachy 	if (priv->join_pending)
190a910e4a9SSolomon Peachy 		goto revert1;
191a910e4a9SSolomon Peachy 
192a910e4a9SSolomon Peachy 	/* Do not suspend when scanning */
193a910e4a9SSolomon Peachy 	if (down_trylock(&priv->scan.lock))
194a910e4a9SSolomon Peachy 		goto revert1;
195a910e4a9SSolomon Peachy 
196a910e4a9SSolomon Peachy 	/* Lock TX. */
197a910e4a9SSolomon Peachy 	wsm_lock_tx_async(priv);
198a910e4a9SSolomon Peachy 
199a910e4a9SSolomon Peachy 	/* Wait to avoid possible race with bh code.
200a910e4a9SSolomon Peachy 	 * But do not wait too long...
201a910e4a9SSolomon Peachy 	 */
202a910e4a9SSolomon Peachy 	if (wait_event_timeout(priv->bh_evt_wq,
203a910e4a9SSolomon Peachy 			       !priv->hw_bufs_used, HZ / 10) <= 0)
204a910e4a9SSolomon Peachy 		goto revert2;
205a910e4a9SSolomon Peachy 
206a910e4a9SSolomon Peachy 	/* Set UDP filter */
207a910e4a9SSolomon Peachy 	wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_on.hdr);
208a910e4a9SSolomon Peachy 
209a910e4a9SSolomon Peachy 	/* Set ethernet frame type filter */
210a910e4a9SSolomon Peachy 	wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_on.hdr);
211a910e4a9SSolomon Peachy 
212a910e4a9SSolomon Peachy 	/* Allocate state */
213a910e4a9SSolomon Peachy 	state = kzalloc(sizeof(struct cw1200_suspend_state), GFP_KERNEL);
214a910e4a9SSolomon Peachy 	if (!state)
215a910e4a9SSolomon Peachy 		goto revert3;
216a910e4a9SSolomon Peachy 
217a910e4a9SSolomon Peachy 	/* Change to legacy PS while going to suspend */
218a910e4a9SSolomon Peachy 	if (!priv->vif->p2p &&
219a910e4a9SSolomon Peachy 	    priv->join_status == CW1200_JOIN_STATUS_STA &&
220a910e4a9SSolomon Peachy 	    priv->powersave_mode.mode != WSM_PSM_PS) {
221a910e4a9SSolomon Peachy 		state->prev_ps_mode = priv->powersave_mode.mode;
222a910e4a9SSolomon Peachy 		priv->powersave_mode.mode = WSM_PSM_PS;
223a910e4a9SSolomon Peachy 		cw1200_set_pm(priv, &priv->powersave_mode);
224a910e4a9SSolomon Peachy 		if (wait_event_interruptible_timeout(priv->ps_mode_switch_done,
225a910e4a9SSolomon Peachy 						     !priv->ps_mode_switch_in_progress, 1*HZ) <= 0) {
22647acf6f5SChristian Engelmayer 			goto revert4;
227a910e4a9SSolomon Peachy 		}
228a910e4a9SSolomon Peachy 	}
229a910e4a9SSolomon Peachy 
230a910e4a9SSolomon Peachy 	/* Store delayed work states. */
231a910e4a9SSolomon Peachy 	state->bss_loss_tmo =
232a910e4a9SSolomon Peachy 		cw1200_suspend_work(&priv->bss_loss_work);
233a910e4a9SSolomon Peachy 	state->join_tmo =
234a910e4a9SSolomon Peachy 		cw1200_suspend_work(&priv->join_timeout);
235a910e4a9SSolomon Peachy 	state->direct_probe =
236a910e4a9SSolomon Peachy 		cw1200_suspend_work(&priv->scan.probe_work);
237a910e4a9SSolomon Peachy 	state->link_id_gc =
238a910e4a9SSolomon Peachy 		cw1200_suspend_work(&priv->link_id_gc_work);
239a910e4a9SSolomon Peachy 
240a910e4a9SSolomon Peachy 	cancel_delayed_work_sync(&priv->clear_recent_scan_work);
241a910e4a9SSolomon Peachy 	atomic_set(&priv->recent_scan, 0);
242a910e4a9SSolomon Peachy 
243a910e4a9SSolomon Peachy 	/* Enable beacon skipping */
244a910e4a9SSolomon Peachy 	if (priv->join_status == CW1200_JOIN_STATUS_STA &&
245a910e4a9SSolomon Peachy 	    priv->join_dtim_period &&
246a910e4a9SSolomon Peachy 	    !priv->has_multicast_subscription) {
247a910e4a9SSolomon Peachy 		state->beacon_skipping = true;
248a910e4a9SSolomon Peachy 		wsm_set_beacon_wakeup_period(priv,
249a910e4a9SSolomon Peachy 					     priv->join_dtim_period,
250a910e4a9SSolomon Peachy 					     CW1200_BEACON_SKIPPING_MULTIPLIER * priv->join_dtim_period);
251a910e4a9SSolomon Peachy 	}
252a910e4a9SSolomon Peachy 
253a910e4a9SSolomon Peachy 	/* Stop serving thread */
254a910e4a9SSolomon Peachy 	if (cw1200_bh_suspend(priv))
25547acf6f5SChristian Engelmayer 		goto revert5;
256a910e4a9SSolomon Peachy 
257a910e4a9SSolomon Peachy 	ret = timer_pending(&priv->mcast_timeout);
258a910e4a9SSolomon Peachy 	if (ret)
25947acf6f5SChristian Engelmayer 		goto revert6;
260a910e4a9SSolomon Peachy 
261a910e4a9SSolomon Peachy 	/* Store suspend state */
262a910e4a9SSolomon Peachy 	pm_state->suspend_state = state;
263a910e4a9SSolomon Peachy 
264a910e4a9SSolomon Peachy 	/* Enable IRQ wake */
265911373ccSSolomon Peachy 	ret = priv->hwbus_ops->power_mgmt(priv->hwbus_priv, true);
266a910e4a9SSolomon Peachy 	if (ret) {
267a910e4a9SSolomon Peachy 		wiphy_err(priv->hw->wiphy,
268a910e4a9SSolomon Peachy 			  "PM request failed: %d. WoW is disabled.\n", ret);
269a910e4a9SSolomon Peachy 		cw1200_wow_resume(hw);
270a910e4a9SSolomon Peachy 		return -EBUSY;
271a910e4a9SSolomon Peachy 	}
272a910e4a9SSolomon Peachy 
273a910e4a9SSolomon Peachy 	/* Force resume if event is coming from the device. */
274a910e4a9SSolomon Peachy 	if (atomic_read(&priv->bh_rx)) {
275a910e4a9SSolomon Peachy 		cw1200_wow_resume(hw);
276a910e4a9SSolomon Peachy 		return -EAGAIN;
277a910e4a9SSolomon Peachy 	}
278a910e4a9SSolomon Peachy 
279a910e4a9SSolomon Peachy 	return 0;
280a910e4a9SSolomon Peachy 
28147acf6f5SChristian Engelmayer revert6:
282a910e4a9SSolomon Peachy 	WARN_ON(cw1200_bh_resume(priv));
28347acf6f5SChristian Engelmayer revert5:
284a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->bss_loss_work,
285a910e4a9SSolomon Peachy 			   state->bss_loss_tmo);
286a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->join_timeout,
287a910e4a9SSolomon Peachy 			   state->join_tmo);
288a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->scan.probe_work,
289a910e4a9SSolomon Peachy 			   state->direct_probe);
290a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->link_id_gc_work,
291a910e4a9SSolomon Peachy 			   state->link_id_gc);
29247acf6f5SChristian Engelmayer revert4:
293a910e4a9SSolomon Peachy 	kfree(state);
294a910e4a9SSolomon Peachy revert3:
295a910e4a9SSolomon Peachy 	wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
296a910e4a9SSolomon Peachy 	wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
297a910e4a9SSolomon Peachy revert2:
298a910e4a9SSolomon Peachy 	wsm_unlock_tx(priv);
299a910e4a9SSolomon Peachy 	up(&priv->scan.lock);
300a910e4a9SSolomon Peachy revert1:
301a910e4a9SSolomon Peachy 	mutex_unlock(&priv->conf_mutex);
302a910e4a9SSolomon Peachy 	return -EBUSY;
303a910e4a9SSolomon Peachy }
304a910e4a9SSolomon Peachy 
305a910e4a9SSolomon Peachy int cw1200_wow_resume(struct ieee80211_hw *hw)
306a910e4a9SSolomon Peachy {
307a910e4a9SSolomon Peachy 	struct cw1200_common *priv = hw->priv;
308a910e4a9SSolomon Peachy 	struct cw1200_pm_state *pm_state = &priv->pm_state;
309a910e4a9SSolomon Peachy 	struct cw1200_suspend_state *state;
310a910e4a9SSolomon Peachy 
311a910e4a9SSolomon Peachy 	state = pm_state->suspend_state;
312a910e4a9SSolomon Peachy 	pm_state->suspend_state = NULL;
313a910e4a9SSolomon Peachy 
314a910e4a9SSolomon Peachy 	/* Disable IRQ wake */
315911373ccSSolomon Peachy 	priv->hwbus_ops->power_mgmt(priv->hwbus_priv, false);
316a910e4a9SSolomon Peachy 
317a910e4a9SSolomon Peachy 	/* Scan.lock must be released before BH is resumed other way
318a910e4a9SSolomon Peachy 	 * in case when BSS_LOST command arrived the processing of the
319a910e4a9SSolomon Peachy 	 * command will be delayed.
320a910e4a9SSolomon Peachy 	 */
321a910e4a9SSolomon Peachy 	up(&priv->scan.lock);
322a910e4a9SSolomon Peachy 
323a910e4a9SSolomon Peachy 	/* Resume BH thread */
324a910e4a9SSolomon Peachy 	WARN_ON(cw1200_bh_resume(priv));
325a910e4a9SSolomon Peachy 
326a910e4a9SSolomon Peachy 	/* Restores previous PS mode */
327a910e4a9SSolomon Peachy 	if (!priv->vif->p2p && priv->join_status == CW1200_JOIN_STATUS_STA) {
328a910e4a9SSolomon Peachy 		priv->powersave_mode.mode = state->prev_ps_mode;
329a910e4a9SSolomon Peachy 		cw1200_set_pm(priv, &priv->powersave_mode);
330a910e4a9SSolomon Peachy 	}
331a910e4a9SSolomon Peachy 
332a910e4a9SSolomon Peachy 	if (state->beacon_skipping) {
333a910e4a9SSolomon Peachy 		wsm_set_beacon_wakeup_period(priv, priv->beacon_int *
334a910e4a9SSolomon Peachy 					     priv->join_dtim_period >
335a910e4a9SSolomon Peachy 					     MAX_BEACON_SKIP_TIME_MS ? 1 :
336a910e4a9SSolomon Peachy 					     priv->join_dtim_period, 0);
337a910e4a9SSolomon Peachy 		state->beacon_skipping = false;
338a910e4a9SSolomon Peachy 	}
339a910e4a9SSolomon Peachy 
340a910e4a9SSolomon Peachy 	/* Resume delayed work */
341a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->bss_loss_work,
342a910e4a9SSolomon Peachy 			   state->bss_loss_tmo);
343a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->join_timeout,
344a910e4a9SSolomon Peachy 			   state->join_tmo);
345a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->scan.probe_work,
346a910e4a9SSolomon Peachy 			   state->direct_probe);
347a910e4a9SSolomon Peachy 	cw1200_resume_work(priv, &priv->link_id_gc_work,
348a910e4a9SSolomon Peachy 			   state->link_id_gc);
349a910e4a9SSolomon Peachy 
350a910e4a9SSolomon Peachy 	/* Remove UDP port filter */
351a910e4a9SSolomon Peachy 	wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
352a910e4a9SSolomon Peachy 
353a910e4a9SSolomon Peachy 	/* Remove ethernet frame type filter */
354a910e4a9SSolomon Peachy 	wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
355a910e4a9SSolomon Peachy 
356a910e4a9SSolomon Peachy 	/* Unlock datapath */
357a910e4a9SSolomon Peachy 	wsm_unlock_tx(priv);
358a910e4a9SSolomon Peachy 
359a910e4a9SSolomon Peachy 	/* Unlock configuration mutex */
360a910e4a9SSolomon Peachy 	mutex_unlock(&priv->conf_mutex);
361a910e4a9SSolomon Peachy 
362a910e4a9SSolomon Peachy 	/* Free memory */
363a910e4a9SSolomon Peachy 	kfree(state);
364a910e4a9SSolomon Peachy 
365a910e4a9SSolomon Peachy 	return 0;
366a910e4a9SSolomon Peachy }
367