diff options
Diffstat (limited to 'drivers/net/wireless/ath/wil6210/pm.c')
| -rw-r--r-- | drivers/net/wireless/ath/wil6210/pm.c | 247 |
1 files changed, 189 insertions, 58 deletions
diff --git a/drivers/net/wireless/ath/wil6210/pm.c b/drivers/net/wireless/ath/wil6210/pm.c index ce1f384e7f8e..c866cfd144c7 100644 --- a/drivers/net/wireless/ath/wil6210/pm.c +++ b/drivers/net/wireless/ath/wil6210/pm.c @@ -1,30 +1,100 @@ +// SPDX-License-Identifier: ISC /* * Copyright (c) 2014,2017 Qualcomm Atheros, Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. */ #include "wil6210.h" #include <linux/jiffies.h> +#include <linux/pm_runtime.h> + +#define WIL6210_AUTOSUSPEND_DELAY_MS (1000) + +static void wil_pm_wake_connected_net_queues(struct wil6210_priv *wil) +{ + int i; + + mutex_lock(&wil->vif_mutex); + for (i = 0; i < GET_MAX_VIFS(wil); i++) { + struct wil6210_vif *vif = wil->vifs[i]; + + if (vif && test_bit(wil_vif_fwconnected, vif->status)) + wil_update_net_queues_bh(wil, vif, NULL, false); + } + mutex_unlock(&wil->vif_mutex); +} + +static void wil_pm_stop_all_net_queues(struct wil6210_priv *wil) +{ + int i; + + mutex_lock(&wil->vif_mutex); + for (i = 0; i < GET_MAX_VIFS(wil); i++) { + struct wil6210_vif *vif = wil->vifs[i]; + + if (vif) + wil_update_net_queues_bh(wil, vif, NULL, true); + } + mutex_unlock(&wil->vif_mutex); +} + +static bool +wil_can_suspend_vif(struct wil6210_priv *wil, struct wil6210_vif *vif, + bool is_runtime) +{ + struct wireless_dev *wdev = vif_to_wdev(vif); + + switch (wdev->iftype) { + case NL80211_IFTYPE_MONITOR: + wil_dbg_pm(wil, "Sniffer\n"); + return false; + + /* for STA-like interface, don't runtime suspend */ + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + if (test_bit(wil_vif_fwconnecting, vif->status)) { + wil_dbg_pm(wil, "Delay suspend when connecting\n"); + return false; + } + if (is_runtime) { + wil_dbg_pm(wil, "STA-like interface\n"); + return false; + } + break; + /* AP-like interface - can't suspend */ + default: + wil_dbg_pm(wil, "AP-like interface\n"); + return false; + } + + return true; +} int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime) { - int rc = 0; - struct wireless_dev *wdev = wil->wdev; + int rc = 0, i; + bool wmi_only = test_bit(WMI_FW_CAPABILITY_WMI_ONLY, + wil->fw_capabilities); + bool active_ifaces; wil_dbg_pm(wil, "can_suspend: %s\n", is_runtime ? "runtime" : "system"); - if (!netif_running(wil_to_ndev(wil))) { + if (wmi_only || debug_fw) { + wil_dbg_pm(wil, "Deny any suspend - %s mode\n", + wmi_only ? "wmi_only" : "debug_fw"); + rc = -EBUSY; + goto out; + } + if (is_runtime && !wil->platform_ops.suspend) { + rc = -EBUSY; + goto out; + } + + mutex_lock(&wil->vif_mutex); + active_ifaces = wil_has_active_ifaces(wil, true, false); + mutex_unlock(&wil->vif_mutex); + + if (!active_ifaces) { /* can always sleep when down */ wil_dbg_pm(wil, "Interface is down\n"); goto out; @@ -41,22 +111,19 @@ int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime) } /* interface is running */ - switch (wdev->iftype) { - case NL80211_IFTYPE_MONITOR: - case NL80211_IFTYPE_STATION: - case NL80211_IFTYPE_P2P_CLIENT: - if (test_bit(wil_status_fwconnecting, wil->status)) { - wil_dbg_pm(wil, "Delay suspend when connecting\n"); + mutex_lock(&wil->vif_mutex); + for (i = 0; i < GET_MAX_VIFS(wil); i++) { + struct wil6210_vif *vif = wil->vifs[i]; + + if (!vif) + continue; + if (!wil_can_suspend_vif(wil, vif, is_runtime)) { rc = -EBUSY; + mutex_unlock(&wil->vif_mutex); goto out; } - break; - /* AP-like interface - can't suspend */ - default: - wil_dbg_pm(wil, "AP-like interface\n"); - rc = -EBUSY; - break; } + mutex_unlock(&wil->vif_mutex); out: wil_dbg_pm(wil, "can_suspend: %s => %s (%d)\n", @@ -85,7 +152,9 @@ static int wil_resume_keep_radio_on(struct wil6210_priv *wil) /* Send WMI resume request to the device */ rc = wmi_resume(wil); if (rc) { - wil_err(wil, "device failed to resume (%d), resetting\n", rc); + wil_err(wil, "device failed to resume (%d)\n", rc); + if (no_fw_recovery) + goto out; rc = wil_down(wil); if (rc) { wil_err(wil, "wil_down failed (%d)\n", rc); @@ -99,8 +168,7 @@ static int wil_resume_keep_radio_on(struct wil6210_priv *wil) } /* Wake all queues */ - if (test_bit(wil_status_fwconnected, wil->status)) - wil_update_net_queues_bh(wil, NULL, false); + wil_pm_wake_connected_net_queues(wil); out: if (rc) @@ -111,13 +179,24 @@ out: static int wil_suspend_keep_radio_on(struct wil6210_priv *wil) { int rc = 0; - unsigned long start, data_comp_to; + unsigned long data_comp_to; wil_dbg_pm(wil, "suspend keep radio on\n"); /* Prevent handling of new tx and wmi commands */ + rc = down_write_trylock(&wil->mem_lock); + if (!rc) { + wil_err(wil, + "device is busy. down_write_trylock failed, returned (0x%x)\n", + rc); + wil->suspend_stats.rejected_by_host++; + return -EBUSY; + } + set_bit(wil_status_suspending, wil->status); - wil_update_net_queues_bh(wil, NULL, true); + up_write(&wil->mem_lock); + + wil_pm_stop_all_net_queues(wil); if (!wil_is_tx_idle(wil)) { wil_dbg_pm(wil, "Pending TX data, reject suspend\n"); @@ -125,7 +204,7 @@ static int wil_suspend_keep_radio_on(struct wil6210_priv *wil) goto reject_suspend; } - if (!wil_is_rx_idle(wil)) { + if (!wil->txrx_ops.is_rx_idle(wil)) { wil_dbg_pm(wil, "Pending RX data, reject suspend\n"); wil->suspend_stats.rejected_by_host++; goto reject_suspend; @@ -146,16 +225,15 @@ static int wil_suspend_keep_radio_on(struct wil6210_priv *wil) } /* Wait for completion of the pending RX packets */ - start = jiffies; data_comp_to = jiffies + msecs_to_jiffies(WIL_DATA_COMPLETION_TO_MS); if (test_bit(wil_status_napi_en, wil->status)) { - while (!wil_is_rx_idle(wil)) { + while (!wil->txrx_ops.is_rx_idle(wil)) { if (time_after(jiffies, data_comp_to)) { - if (wil_is_rx_idle(wil)) + if (wil->txrx_ops.is_rx_idle(wil)) break; wil_err(wil, "TO waiting for idle RX, suspend failed\n"); - wil->suspend_stats.failed_suspends++; + wil->suspend_stats.r_on.failed_suspends++; goto resume_after_fail; } wil_dbg_ratelimited(wil, "rx vring is not empty -> NAPI\n"); @@ -171,7 +249,7 @@ static int wil_suspend_keep_radio_on(struct wil6210_priv *wil) */ if (!wil_is_wmi_idle(wil)) { wil_err(wil, "suspend failed due to pending WMI events\n"); - wil->suspend_stats.failed_suspends++; + wil->suspend_stats.r_on.failed_suspends++; goto resume_after_fail; } @@ -185,7 +263,7 @@ static int wil_suspend_keep_radio_on(struct wil6210_priv *wil) if (rc) { wil_err(wil, "platform device failed to suspend (%d)\n", rc); - wil->suspend_stats.failed_suspends++; + wil->suspend_stats.r_on.failed_suspends++; wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); wil_unmask_irq(wil); goto resume_after_fail; @@ -208,30 +286,45 @@ resume_after_fail: /* if resume succeeded, reject the suspend */ if (!rc) { rc = -EBUSY; - if (test_bit(wil_status_fwconnected, wil->status)) - wil_update_net_queues_bh(wil, NULL, false); + wil_pm_wake_connected_net_queues(wil); } return rc; reject_suspend: clear_bit(wil_status_suspending, wil->status); - if (test_bit(wil_status_fwconnected, wil->status)) - wil_update_net_queues_bh(wil, NULL, false); + wil_pm_wake_connected_net_queues(wil); return -EBUSY; } static int wil_suspend_radio_off(struct wil6210_priv *wil) { int rc = 0; - struct net_device *ndev = wil_to_ndev(wil); + bool active_ifaces; wil_dbg_pm(wil, "suspend radio off\n"); + rc = down_write_trylock(&wil->mem_lock); + if (!rc) { + wil_err(wil, + "device is busy. down_write_trylock failed, returned (0x%x)\n", + rc); + wil->suspend_stats.rejected_by_host++; + return -EBUSY; + } + + set_bit(wil_status_suspending, wil->status); + up_write(&wil->mem_lock); + /* if netif up, hardware is alive, shut it down */ - if (ndev->flags & IFF_UP) { + mutex_lock(&wil->vif_mutex); + active_ifaces = wil_has_active_ifaces(wil, true, false); + mutex_unlock(&wil->vif_mutex); + + if (active_ifaces) { rc = wil_down(wil); if (rc) { wil_err(wil, "wil_down : %d\n", rc); + wil->suspend_stats.r_off.failed_suspends++; goto out; } } @@ -244,6 +337,7 @@ static int wil_suspend_radio_off(struct wil6210_priv *wil) rc = wil->platform_ops.suspend(wil->platform_handle, false); if (rc) { wil_enable_irq(wil); + wil->suspend_stats.r_off.failed_suspends++; goto out; } } @@ -251,6 +345,7 @@ static int wil_suspend_radio_off(struct wil6210_priv *wil) set_bit(wil_status_suspended, wil->status); out: + clear_bit(wil_status_suspending, wil->status); wil_dbg_pm(wil, "suspend radio off: %d\n", rc); return rc; @@ -259,16 +354,19 @@ out: static int wil_resume_radio_off(struct wil6210_priv *wil) { int rc = 0; - struct net_device *ndev = wil_to_ndev(wil); + bool active_ifaces; wil_dbg_pm(wil, "Enabling PCIe IRQ\n"); wil_enable_irq(wil); - /* if netif up, bring hardware up + /* if any netif up, bring hardware up * During open(), IFF_UP set after actual device method * invocation. This prevent recursive call to wil_up() * wil_status_suspended will be cleared in wil_reset */ - if (ndev->flags & IFF_UP) + mutex_lock(&wil->vif_mutex); + active_ifaces = wil_has_active_ifaces(wil, true, false); + mutex_unlock(&wil->vif_mutex); + if (active_ifaces) rc = wil_up(wil); else clear_bit(wil_status_suspended, wil->status); @@ -276,12 +374,9 @@ static int wil_resume_radio_off(struct wil6210_priv *wil) return rc; } -int wil_suspend(struct wil6210_priv *wil, bool is_runtime) +int wil_suspend(struct wil6210_priv *wil, bool is_runtime, bool keep_radio_on) { int rc = 0; - struct net_device *ndev = wil_to_ndev(wil); - bool keep_radio_on = ndev->flags & IFF_UP && - wil->keep_radio_on_during_sleep; wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system"); @@ -301,12 +396,9 @@ int wil_suspend(struct wil6210_priv *wil, bool is_runtime) return rc; } -int wil_resume(struct wil6210_priv *wil, bool is_runtime) +int wil_resume(struct wil6210_priv *wil, bool is_runtime, bool keep_radio_on) { int rc = 0; - struct net_device *ndev = wil_to_ndev(wil); - bool keep_radio_on = ndev->flags & IFF_UP && - wil->keep_radio_on_during_sleep; wil_dbg_pm(wil, "resume: %s\n", is_runtime ? "runtime" : "system"); @@ -325,7 +417,46 @@ int wil_resume(struct wil6210_priv *wil, bool is_runtime) rc = wil_resume_radio_off(wil); out: - wil_dbg_pm(wil, "resume: %s => %d\n", - is_runtime ? "runtime" : "system", rc); + wil_dbg_pm(wil, "resume: %s => %d\n", is_runtime ? "runtime" : "system", + rc); return rc; } + +void wil_pm_runtime_allow(struct wil6210_priv *wil) +{ + struct device *dev = wil_to_dev(wil); + + pm_runtime_put_noidle(dev); + pm_runtime_set_autosuspend_delay(dev, WIL6210_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_allow(dev); +} + +void wil_pm_runtime_forbid(struct wil6210_priv *wil) +{ + struct device *dev = wil_to_dev(wil); + + pm_runtime_forbid(dev); + pm_runtime_get_noresume(dev); +} + +int wil_pm_runtime_get(struct wil6210_priv *wil) +{ + int rc; + struct device *dev = wil_to_dev(wil); + + rc = pm_runtime_resume_and_get(dev); + if (rc < 0) { + wil_err(wil, "pm_runtime_resume_and_get() failed, rc = %d\n", rc); + return rc; + } + + return 0; +} + +void wil_pm_runtime_put(struct wil6210_priv *wil) +{ + struct device *dev = wil_to_dev(wil); + + pm_runtime_put_autosuspend(dev); +} |
