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