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