18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Mac80211 power management API for ST-Ericsson CW1200 drivers
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2011, ST-Ericsson
68c2ecf20Sopenharmony_ci * Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/if_ether.h>
118c2ecf20Sopenharmony_ci#include "cw1200.h"
128c2ecf20Sopenharmony_ci#include "pm.h"
138c2ecf20Sopenharmony_ci#include "sta.h"
148c2ecf20Sopenharmony_ci#include "bh.h"
158c2ecf20Sopenharmony_ci#include "hwbus.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define CW1200_BEACON_SKIPPING_MULTIPLIER 3
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistruct cw1200_udp_port_filter {
208c2ecf20Sopenharmony_ci	struct wsm_udp_port_filter_hdr hdr;
218c2ecf20Sopenharmony_ci	/* Up to 4 filters are allowed. */
228c2ecf20Sopenharmony_ci	struct wsm_udp_port_filter filters[WSM_MAX_FILTER_ELEMENTS];
238c2ecf20Sopenharmony_ci} __packed;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistruct cw1200_ether_type_filter {
268c2ecf20Sopenharmony_ci	struct wsm_ether_type_filter_hdr hdr;
278c2ecf20Sopenharmony_ci	/* Up to 4 filters are allowed. */
288c2ecf20Sopenharmony_ci	struct wsm_ether_type_filter filters[WSM_MAX_FILTER_ELEMENTS];
298c2ecf20Sopenharmony_ci} __packed;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic struct cw1200_udp_port_filter cw1200_udp_port_filter_on = {
328c2ecf20Sopenharmony_ci	.hdr.num = 2,
338c2ecf20Sopenharmony_ci	.filters = {
348c2ecf20Sopenharmony_ci		[0] = {
358c2ecf20Sopenharmony_ci			.action = WSM_FILTER_ACTION_FILTER_OUT,
368c2ecf20Sopenharmony_ci			.type = WSM_FILTER_PORT_TYPE_DST,
378c2ecf20Sopenharmony_ci			.port = __cpu_to_le16(67), /* DHCP Bootps */
388c2ecf20Sopenharmony_ci		},
398c2ecf20Sopenharmony_ci		[1] = {
408c2ecf20Sopenharmony_ci			.action = WSM_FILTER_ACTION_FILTER_OUT,
418c2ecf20Sopenharmony_ci			.type = WSM_FILTER_PORT_TYPE_DST,
428c2ecf20Sopenharmony_ci			.port = __cpu_to_le16(68), /* DHCP Bootpc */
438c2ecf20Sopenharmony_ci		},
448c2ecf20Sopenharmony_ci	}
458c2ecf20Sopenharmony_ci};
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic struct wsm_udp_port_filter_hdr cw1200_udp_port_filter_off = {
488c2ecf20Sopenharmony_ci	.num = 0,
498c2ecf20Sopenharmony_ci};
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci#ifndef ETH_P_WAPI
528c2ecf20Sopenharmony_ci#define ETH_P_WAPI     0x88B4
538c2ecf20Sopenharmony_ci#endif
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic struct cw1200_ether_type_filter cw1200_ether_type_filter_on = {
568c2ecf20Sopenharmony_ci	.hdr.num = 4,
578c2ecf20Sopenharmony_ci	.filters = {
588c2ecf20Sopenharmony_ci		[0] = {
598c2ecf20Sopenharmony_ci			.action = WSM_FILTER_ACTION_FILTER_IN,
608c2ecf20Sopenharmony_ci			.type = __cpu_to_le16(ETH_P_IP),
618c2ecf20Sopenharmony_ci		},
628c2ecf20Sopenharmony_ci		[1] = {
638c2ecf20Sopenharmony_ci			.action = WSM_FILTER_ACTION_FILTER_IN,
648c2ecf20Sopenharmony_ci			.type = __cpu_to_le16(ETH_P_PAE),
658c2ecf20Sopenharmony_ci		},
668c2ecf20Sopenharmony_ci		[2] = {
678c2ecf20Sopenharmony_ci			.action = WSM_FILTER_ACTION_FILTER_IN,
688c2ecf20Sopenharmony_ci			.type = __cpu_to_le16(ETH_P_WAPI),
698c2ecf20Sopenharmony_ci		},
708c2ecf20Sopenharmony_ci		[3] = {
718c2ecf20Sopenharmony_ci			.action = WSM_FILTER_ACTION_FILTER_IN,
728c2ecf20Sopenharmony_ci			.type = __cpu_to_le16(ETH_P_ARP),
738c2ecf20Sopenharmony_ci		},
748c2ecf20Sopenharmony_ci	},
758c2ecf20Sopenharmony_ci};
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic struct wsm_ether_type_filter_hdr cw1200_ether_type_filter_off = {
788c2ecf20Sopenharmony_ci	.num = 0,
798c2ecf20Sopenharmony_ci};
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci/* private */
828c2ecf20Sopenharmony_cistruct cw1200_suspend_state {
838c2ecf20Sopenharmony_ci	unsigned long bss_loss_tmo;
848c2ecf20Sopenharmony_ci	unsigned long join_tmo;
858c2ecf20Sopenharmony_ci	unsigned long direct_probe;
868c2ecf20Sopenharmony_ci	unsigned long link_id_gc;
878c2ecf20Sopenharmony_ci	bool beacon_skipping;
888c2ecf20Sopenharmony_ci	u8 prev_ps_mode;
898c2ecf20Sopenharmony_ci};
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic void cw1200_pm_stay_awake_tmo(struct timer_list *unused)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	/* XXX what's the point of this ? */
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ciint cw1200_pm_init(struct cw1200_pm_state *pm,
978c2ecf20Sopenharmony_ci		   struct cw1200_common *priv)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	spin_lock_init(&pm->lock);
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	timer_setup(&pm->stay_awake, cw1200_pm_stay_awake_tmo, 0);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	return 0;
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_civoid cw1200_pm_deinit(struct cw1200_pm_state *pm)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	del_timer_sync(&pm->stay_awake);
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_civoid cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
1128c2ecf20Sopenharmony_ci			  unsigned long tmo)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	long cur_tmo;
1158c2ecf20Sopenharmony_ci	spin_lock_bh(&pm->lock);
1168c2ecf20Sopenharmony_ci	cur_tmo = pm->stay_awake.expires - jiffies;
1178c2ecf20Sopenharmony_ci	if (!timer_pending(&pm->stay_awake) || cur_tmo < (long)tmo)
1188c2ecf20Sopenharmony_ci		mod_timer(&pm->stay_awake, jiffies + tmo);
1198c2ecf20Sopenharmony_ci	spin_unlock_bh(&pm->lock);
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic long cw1200_suspend_work(struct delayed_work *work)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	int ret = cancel_delayed_work(work);
1258c2ecf20Sopenharmony_ci	long tmo;
1268c2ecf20Sopenharmony_ci	if (ret > 0) {
1278c2ecf20Sopenharmony_ci		/* Timer is pending */
1288c2ecf20Sopenharmony_ci		tmo = work->timer.expires - jiffies;
1298c2ecf20Sopenharmony_ci		if (tmo < 0)
1308c2ecf20Sopenharmony_ci			tmo = 0;
1318c2ecf20Sopenharmony_ci	} else {
1328c2ecf20Sopenharmony_ci		tmo = -1;
1338c2ecf20Sopenharmony_ci	}
1348c2ecf20Sopenharmony_ci	return tmo;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic int cw1200_resume_work(struct cw1200_common *priv,
1388c2ecf20Sopenharmony_ci			       struct delayed_work *work,
1398c2ecf20Sopenharmony_ci			       unsigned long tmo)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	if ((long)tmo < 0)
1428c2ecf20Sopenharmony_ci		return 1;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	return queue_delayed_work(priv->workqueue, work, tmo);
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ciint cw1200_can_suspend(struct cw1200_common *priv)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	if (atomic_read(&priv->bh_rx)) {
1508c2ecf20Sopenharmony_ci		wiphy_dbg(priv->hw->wiphy, "Suspend interrupted.\n");
1518c2ecf20Sopenharmony_ci		return 0;
1528c2ecf20Sopenharmony_ci	}
1538c2ecf20Sopenharmony_ci	return 1;
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cw1200_can_suspend);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ciint cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct cw1200_common *priv = hw->priv;
1608c2ecf20Sopenharmony_ci	struct cw1200_pm_state *pm_state = &priv->pm_state;
1618c2ecf20Sopenharmony_ci	struct cw1200_suspend_state *state;
1628c2ecf20Sopenharmony_ci	int ret;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	spin_lock_bh(&pm_state->lock);
1658c2ecf20Sopenharmony_ci	ret = timer_pending(&pm_state->stay_awake);
1668c2ecf20Sopenharmony_ci	spin_unlock_bh(&pm_state->lock);
1678c2ecf20Sopenharmony_ci	if (ret)
1688c2ecf20Sopenharmony_ci		return -EAGAIN;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	/* Do not suspend when datapath is not idle */
1718c2ecf20Sopenharmony_ci	if (priv->tx_queue_stats.num_queued)
1728c2ecf20Sopenharmony_ci		return -EBUSY;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	/* Make sure there is no configuration requests in progress. */
1758c2ecf20Sopenharmony_ci	if (!mutex_trylock(&priv->conf_mutex))
1768c2ecf20Sopenharmony_ci		return -EBUSY;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	/* Ensure pending operations are done.
1798c2ecf20Sopenharmony_ci	 * Note also that wow_suspend must return in ~2.5sec, before
1808c2ecf20Sopenharmony_ci	 * watchdog is triggered.
1818c2ecf20Sopenharmony_ci	 */
1828c2ecf20Sopenharmony_ci	if (priv->channel_switch_in_progress)
1838c2ecf20Sopenharmony_ci		goto revert1;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	/* Do not suspend when join is pending */
1868c2ecf20Sopenharmony_ci	if (priv->join_pending)
1878c2ecf20Sopenharmony_ci		goto revert1;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	/* Do not suspend when scanning */
1908c2ecf20Sopenharmony_ci	if (down_trylock(&priv->scan.lock))
1918c2ecf20Sopenharmony_ci		goto revert1;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	/* Lock TX. */
1948c2ecf20Sopenharmony_ci	wsm_lock_tx_async(priv);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	/* Wait to avoid possible race with bh code.
1978c2ecf20Sopenharmony_ci	 * But do not wait too long...
1988c2ecf20Sopenharmony_ci	 */
1998c2ecf20Sopenharmony_ci	if (wait_event_timeout(priv->bh_evt_wq,
2008c2ecf20Sopenharmony_ci			       !priv->hw_bufs_used, HZ / 10) <= 0)
2018c2ecf20Sopenharmony_ci		goto revert2;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	/* Set UDP filter */
2048c2ecf20Sopenharmony_ci	wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_on.hdr);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	/* Set ethernet frame type filter */
2078c2ecf20Sopenharmony_ci	wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_on.hdr);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	/* Allocate state */
2108c2ecf20Sopenharmony_ci	state = kzalloc(sizeof(struct cw1200_suspend_state), GFP_KERNEL);
2118c2ecf20Sopenharmony_ci	if (!state)
2128c2ecf20Sopenharmony_ci		goto revert3;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	/* Change to legacy PS while going to suspend */
2158c2ecf20Sopenharmony_ci	if (!priv->vif->p2p &&
2168c2ecf20Sopenharmony_ci	    priv->join_status == CW1200_JOIN_STATUS_STA &&
2178c2ecf20Sopenharmony_ci	    priv->powersave_mode.mode != WSM_PSM_PS) {
2188c2ecf20Sopenharmony_ci		state->prev_ps_mode = priv->powersave_mode.mode;
2198c2ecf20Sopenharmony_ci		priv->powersave_mode.mode = WSM_PSM_PS;
2208c2ecf20Sopenharmony_ci		cw1200_set_pm(priv, &priv->powersave_mode);
2218c2ecf20Sopenharmony_ci		if (wait_event_interruptible_timeout(priv->ps_mode_switch_done,
2228c2ecf20Sopenharmony_ci						     !priv->ps_mode_switch_in_progress, 1*HZ) <= 0) {
2238c2ecf20Sopenharmony_ci			goto revert4;
2248c2ecf20Sopenharmony_ci		}
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	/* Store delayed work states. */
2288c2ecf20Sopenharmony_ci	state->bss_loss_tmo =
2298c2ecf20Sopenharmony_ci		cw1200_suspend_work(&priv->bss_loss_work);
2308c2ecf20Sopenharmony_ci	state->join_tmo =
2318c2ecf20Sopenharmony_ci		cw1200_suspend_work(&priv->join_timeout);
2328c2ecf20Sopenharmony_ci	state->direct_probe =
2338c2ecf20Sopenharmony_ci		cw1200_suspend_work(&priv->scan.probe_work);
2348c2ecf20Sopenharmony_ci	state->link_id_gc =
2358c2ecf20Sopenharmony_ci		cw1200_suspend_work(&priv->link_id_gc_work);
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	cancel_delayed_work_sync(&priv->clear_recent_scan_work);
2388c2ecf20Sopenharmony_ci	atomic_set(&priv->recent_scan, 0);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	/* Enable beacon skipping */
2418c2ecf20Sopenharmony_ci	if (priv->join_status == CW1200_JOIN_STATUS_STA &&
2428c2ecf20Sopenharmony_ci	    priv->join_dtim_period &&
2438c2ecf20Sopenharmony_ci	    !priv->has_multicast_subscription) {
2448c2ecf20Sopenharmony_ci		state->beacon_skipping = true;
2458c2ecf20Sopenharmony_ci		wsm_set_beacon_wakeup_period(priv,
2468c2ecf20Sopenharmony_ci					     priv->join_dtim_period,
2478c2ecf20Sopenharmony_ci					     CW1200_BEACON_SKIPPING_MULTIPLIER * priv->join_dtim_period);
2488c2ecf20Sopenharmony_ci	}
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	/* Stop serving thread */
2518c2ecf20Sopenharmony_ci	if (cw1200_bh_suspend(priv))
2528c2ecf20Sopenharmony_ci		goto revert5;
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	ret = timer_pending(&priv->mcast_timeout);
2558c2ecf20Sopenharmony_ci	if (ret)
2568c2ecf20Sopenharmony_ci		goto revert6;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	/* Store suspend state */
2598c2ecf20Sopenharmony_ci	pm_state->suspend_state = state;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	/* Enable IRQ wake */
2628c2ecf20Sopenharmony_ci	ret = priv->hwbus_ops->power_mgmt(priv->hwbus_priv, true);
2638c2ecf20Sopenharmony_ci	if (ret) {
2648c2ecf20Sopenharmony_ci		wiphy_err(priv->hw->wiphy,
2658c2ecf20Sopenharmony_ci			  "PM request failed: %d. WoW is disabled.\n", ret);
2668c2ecf20Sopenharmony_ci		cw1200_wow_resume(hw);
2678c2ecf20Sopenharmony_ci		return -EBUSY;
2688c2ecf20Sopenharmony_ci	}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	/* Force resume if event is coming from the device. */
2718c2ecf20Sopenharmony_ci	if (atomic_read(&priv->bh_rx)) {
2728c2ecf20Sopenharmony_ci		cw1200_wow_resume(hw);
2738c2ecf20Sopenharmony_ci		return -EAGAIN;
2748c2ecf20Sopenharmony_ci	}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	return 0;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_cirevert6:
2798c2ecf20Sopenharmony_ci	WARN_ON(cw1200_bh_resume(priv));
2808c2ecf20Sopenharmony_cirevert5:
2818c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->bss_loss_work,
2828c2ecf20Sopenharmony_ci			   state->bss_loss_tmo);
2838c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->join_timeout,
2848c2ecf20Sopenharmony_ci			   state->join_tmo);
2858c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->scan.probe_work,
2868c2ecf20Sopenharmony_ci			   state->direct_probe);
2878c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->link_id_gc_work,
2888c2ecf20Sopenharmony_ci			   state->link_id_gc);
2898c2ecf20Sopenharmony_cirevert4:
2908c2ecf20Sopenharmony_ci	kfree(state);
2918c2ecf20Sopenharmony_cirevert3:
2928c2ecf20Sopenharmony_ci	wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
2938c2ecf20Sopenharmony_ci	wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
2948c2ecf20Sopenharmony_cirevert2:
2958c2ecf20Sopenharmony_ci	wsm_unlock_tx(priv);
2968c2ecf20Sopenharmony_ci	up(&priv->scan.lock);
2978c2ecf20Sopenharmony_cirevert1:
2988c2ecf20Sopenharmony_ci	mutex_unlock(&priv->conf_mutex);
2998c2ecf20Sopenharmony_ci	return -EBUSY;
3008c2ecf20Sopenharmony_ci}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ciint cw1200_wow_resume(struct ieee80211_hw *hw)
3038c2ecf20Sopenharmony_ci{
3048c2ecf20Sopenharmony_ci	struct cw1200_common *priv = hw->priv;
3058c2ecf20Sopenharmony_ci	struct cw1200_pm_state *pm_state = &priv->pm_state;
3068c2ecf20Sopenharmony_ci	struct cw1200_suspend_state *state;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	state = pm_state->suspend_state;
3098c2ecf20Sopenharmony_ci	pm_state->suspend_state = NULL;
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	/* Disable IRQ wake */
3128c2ecf20Sopenharmony_ci	priv->hwbus_ops->power_mgmt(priv->hwbus_priv, false);
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	/* Scan.lock must be released before BH is resumed other way
3158c2ecf20Sopenharmony_ci	 * in case when BSS_LOST command arrived the processing of the
3168c2ecf20Sopenharmony_ci	 * command will be delayed.
3178c2ecf20Sopenharmony_ci	 */
3188c2ecf20Sopenharmony_ci	up(&priv->scan.lock);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	/* Resume BH thread */
3218c2ecf20Sopenharmony_ci	WARN_ON(cw1200_bh_resume(priv));
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	/* Restores previous PS mode */
3248c2ecf20Sopenharmony_ci	if (!priv->vif->p2p && priv->join_status == CW1200_JOIN_STATUS_STA) {
3258c2ecf20Sopenharmony_ci		priv->powersave_mode.mode = state->prev_ps_mode;
3268c2ecf20Sopenharmony_ci		cw1200_set_pm(priv, &priv->powersave_mode);
3278c2ecf20Sopenharmony_ci	}
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	if (state->beacon_skipping) {
3308c2ecf20Sopenharmony_ci		wsm_set_beacon_wakeup_period(priv, priv->beacon_int *
3318c2ecf20Sopenharmony_ci					     priv->join_dtim_period >
3328c2ecf20Sopenharmony_ci					     MAX_BEACON_SKIP_TIME_MS ? 1 :
3338c2ecf20Sopenharmony_ci					     priv->join_dtim_period, 0);
3348c2ecf20Sopenharmony_ci		state->beacon_skipping = false;
3358c2ecf20Sopenharmony_ci	}
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	/* Resume delayed work */
3388c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->bss_loss_work,
3398c2ecf20Sopenharmony_ci			   state->bss_loss_tmo);
3408c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->join_timeout,
3418c2ecf20Sopenharmony_ci			   state->join_tmo);
3428c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->scan.probe_work,
3438c2ecf20Sopenharmony_ci			   state->direct_probe);
3448c2ecf20Sopenharmony_ci	cw1200_resume_work(priv, &priv->link_id_gc_work,
3458c2ecf20Sopenharmony_ci			   state->link_id_gc);
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	/* Remove UDP port filter */
3488c2ecf20Sopenharmony_ci	wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	/* Remove ethernet frame type filter */
3518c2ecf20Sopenharmony_ci	wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	/* Unlock datapath */
3548c2ecf20Sopenharmony_ci	wsm_unlock_tx(priv);
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	/* Unlock configuration mutex */
3578c2ecf20Sopenharmony_ci	mutex_unlock(&priv->conf_mutex);
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	/* Free memory */
3608c2ecf20Sopenharmony_ci	kfree(state);
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	return 0;
3638c2ecf20Sopenharmony_ci}
364