464 lines · c
1// SPDX-License-Identifier: ISC2/*3 * Copyright (c) 2014,2017 Qualcomm Atheros, Inc.4 * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.5 */6 7#include "wil6210.h"8#include <linux/jiffies.h>9#include <linux/pm_runtime.h>10 11#define WIL6210_AUTOSUSPEND_DELAY_MS (1000)12 13static void wil_pm_wake_connected_net_queues(struct wil6210_priv *wil)14{15 int i;16 17 mutex_lock(&wil->vif_mutex);18 for (i = 0; i < GET_MAX_VIFS(wil); i++) {19 struct wil6210_vif *vif = wil->vifs[i];20 21 if (vif && test_bit(wil_vif_fwconnected, vif->status))22 wil_update_net_queues_bh(wil, vif, NULL, false);23 }24 mutex_unlock(&wil->vif_mutex);25}26 27static void wil_pm_stop_all_net_queues(struct wil6210_priv *wil)28{29 int i;30 31 mutex_lock(&wil->vif_mutex);32 for (i = 0; i < GET_MAX_VIFS(wil); i++) {33 struct wil6210_vif *vif = wil->vifs[i];34 35 if (vif)36 wil_update_net_queues_bh(wil, vif, NULL, true);37 }38 mutex_unlock(&wil->vif_mutex);39}40 41static bool42wil_can_suspend_vif(struct wil6210_priv *wil, struct wil6210_vif *vif,43 bool is_runtime)44{45 struct wireless_dev *wdev = vif_to_wdev(vif);46 47 switch (wdev->iftype) {48 case NL80211_IFTYPE_MONITOR:49 wil_dbg_pm(wil, "Sniffer\n");50 return false;51 52 /* for STA-like interface, don't runtime suspend */53 case NL80211_IFTYPE_STATION:54 case NL80211_IFTYPE_P2P_CLIENT:55 if (test_bit(wil_vif_fwconnecting, vif->status)) {56 wil_dbg_pm(wil, "Delay suspend when connecting\n");57 return false;58 }59 if (is_runtime) {60 wil_dbg_pm(wil, "STA-like interface\n");61 return false;62 }63 break;64 /* AP-like interface - can't suspend */65 default:66 wil_dbg_pm(wil, "AP-like interface\n");67 return false;68 }69 70 return true;71}72 73int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime)74{75 int rc = 0, i;76 bool wmi_only = test_bit(WMI_FW_CAPABILITY_WMI_ONLY,77 wil->fw_capabilities);78 bool active_ifaces;79 80 wil_dbg_pm(wil, "can_suspend: %s\n", is_runtime ? "runtime" : "system");81 82 if (wmi_only || debug_fw) {83 wil_dbg_pm(wil, "Deny any suspend - %s mode\n",84 wmi_only ? "wmi_only" : "debug_fw");85 rc = -EBUSY;86 goto out;87 }88 if (is_runtime && !wil->platform_ops.suspend) {89 rc = -EBUSY;90 goto out;91 }92 93 mutex_lock(&wil->vif_mutex);94 active_ifaces = wil_has_active_ifaces(wil, true, false);95 mutex_unlock(&wil->vif_mutex);96 97 if (!active_ifaces) {98 /* can always sleep when down */99 wil_dbg_pm(wil, "Interface is down\n");100 goto out;101 }102 if (test_bit(wil_status_resetting, wil->status)) {103 wil_dbg_pm(wil, "Delay suspend when resetting\n");104 rc = -EBUSY;105 goto out;106 }107 if (wil->recovery_state != fw_recovery_idle) {108 wil_dbg_pm(wil, "Delay suspend during recovery\n");109 rc = -EBUSY;110 goto out;111 }112 113 /* interface is running */114 mutex_lock(&wil->vif_mutex);115 for (i = 0; i < GET_MAX_VIFS(wil); i++) {116 struct wil6210_vif *vif = wil->vifs[i];117 118 if (!vif)119 continue;120 if (!wil_can_suspend_vif(wil, vif, is_runtime)) {121 rc = -EBUSY;122 mutex_unlock(&wil->vif_mutex);123 goto out;124 }125 }126 mutex_unlock(&wil->vif_mutex);127 128out:129 wil_dbg_pm(wil, "can_suspend: %s => %s (%d)\n",130 is_runtime ? "runtime" : "system", rc ? "No" : "Yes", rc);131 132 if (rc)133 wil->suspend_stats.rejected_by_host++;134 135 return rc;136}137 138static int wil_resume_keep_radio_on(struct wil6210_priv *wil)139{140 int rc = 0;141 142 /* wil_status_resuming will be cleared when getting143 * WMI_TRAFFIC_RESUME_EVENTID144 */145 set_bit(wil_status_resuming, wil->status);146 clear_bit(wil_status_suspended, wil->status);147 wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);148 wil_unmask_irq(wil);149 150 wil6210_bus_request(wil, wil->bus_request_kbps_pre_suspend);151 152 /* Send WMI resume request to the device */153 rc = wmi_resume(wil);154 if (rc) {155 wil_err(wil, "device failed to resume (%d)\n", rc);156 if (no_fw_recovery)157 goto out;158 rc = wil_down(wil);159 if (rc) {160 wil_err(wil, "wil_down failed (%d)\n", rc);161 goto out;162 }163 rc = wil_up(wil);164 if (rc) {165 wil_err(wil, "wil_up failed (%d)\n", rc);166 goto out;167 }168 }169 170 /* Wake all queues */171 wil_pm_wake_connected_net_queues(wil);172 173out:174 if (rc)175 set_bit(wil_status_suspended, wil->status);176 return rc;177}178 179static int wil_suspend_keep_radio_on(struct wil6210_priv *wil)180{181 int rc = 0;182 unsigned long data_comp_to;183 184 wil_dbg_pm(wil, "suspend keep radio on\n");185 186 /* Prevent handling of new tx and wmi commands */187 rc = down_write_trylock(&wil->mem_lock);188 if (!rc) {189 wil_err(wil,190 "device is busy. down_write_trylock failed, returned (0x%x)\n",191 rc);192 wil->suspend_stats.rejected_by_host++;193 return -EBUSY;194 }195 196 set_bit(wil_status_suspending, wil->status);197 up_write(&wil->mem_lock);198 199 wil_pm_stop_all_net_queues(wil);200 201 if (!wil_is_tx_idle(wil)) {202 wil_dbg_pm(wil, "Pending TX data, reject suspend\n");203 wil->suspend_stats.rejected_by_host++;204 goto reject_suspend;205 }206 207 if (!wil->txrx_ops.is_rx_idle(wil)) {208 wil_dbg_pm(wil, "Pending RX data, reject suspend\n");209 wil->suspend_stats.rejected_by_host++;210 goto reject_suspend;211 }212 213 if (!wil_is_wmi_idle(wil)) {214 wil_dbg_pm(wil, "Pending WMI events, reject suspend\n");215 wil->suspend_stats.rejected_by_host++;216 goto reject_suspend;217 }218 219 /* Send WMI suspend request to the device */220 rc = wmi_suspend(wil);221 if (rc) {222 wil_dbg_pm(wil, "wmi_suspend failed, reject suspend (%d)\n",223 rc);224 goto reject_suspend;225 }226 227 /* Wait for completion of the pending RX packets */228 data_comp_to = jiffies + msecs_to_jiffies(WIL_DATA_COMPLETION_TO_MS);229 if (test_bit(wil_status_napi_en, wil->status)) {230 while (!wil->txrx_ops.is_rx_idle(wil)) {231 if (time_after(jiffies, data_comp_to)) {232 if (wil->txrx_ops.is_rx_idle(wil))233 break;234 wil_err(wil,235 "TO waiting for idle RX, suspend failed\n");236 wil->suspend_stats.r_on.failed_suspends++;237 goto resume_after_fail;238 }239 wil_dbg_ratelimited(wil, "rx vring is not empty -> NAPI\n");240 napi_synchronize(&wil->napi_rx);241 msleep(20);242 }243 }244 245 /* In case of pending WMI events, reject the suspend246 * and resume the device.247 * This can happen if the device sent the WMI events before248 * approving the suspend.249 */250 if (!wil_is_wmi_idle(wil)) {251 wil_err(wil, "suspend failed due to pending WMI events\n");252 wil->suspend_stats.r_on.failed_suspends++;253 goto resume_after_fail;254 }255 256 wil_mask_irq(wil);257 258 /* Disable device reset on PERST */259 wil_s(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);260 261 if (wil->platform_ops.suspend) {262 rc = wil->platform_ops.suspend(wil->platform_handle, true);263 if (rc) {264 wil_err(wil, "platform device failed to suspend (%d)\n",265 rc);266 wil->suspend_stats.r_on.failed_suspends++;267 wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);268 wil_unmask_irq(wil);269 goto resume_after_fail;270 }271 }272 273 /* Save the current bus request to return to the same in resume */274 wil->bus_request_kbps_pre_suspend = wil->bus_request_kbps;275 wil6210_bus_request(wil, 0);276 277 set_bit(wil_status_suspended, wil->status);278 clear_bit(wil_status_suspending, wil->status);279 280 return rc;281 282resume_after_fail:283 set_bit(wil_status_resuming, wil->status);284 clear_bit(wil_status_suspending, wil->status);285 rc = wmi_resume(wil);286 /* if resume succeeded, reject the suspend */287 if (!rc) {288 rc = -EBUSY;289 wil_pm_wake_connected_net_queues(wil);290 }291 return rc;292 293reject_suspend:294 clear_bit(wil_status_suspending, wil->status);295 wil_pm_wake_connected_net_queues(wil);296 return -EBUSY;297}298 299static int wil_suspend_radio_off(struct wil6210_priv *wil)300{301 int rc = 0;302 bool active_ifaces;303 304 wil_dbg_pm(wil, "suspend radio off\n");305 306 rc = down_write_trylock(&wil->mem_lock);307 if (!rc) {308 wil_err(wil,309 "device is busy. down_write_trylock failed, returned (0x%x)\n",310 rc);311 wil->suspend_stats.rejected_by_host++;312 return -EBUSY;313 }314 315 set_bit(wil_status_suspending, wil->status);316 up_write(&wil->mem_lock);317 318 /* if netif up, hardware is alive, shut it down */319 mutex_lock(&wil->vif_mutex);320 active_ifaces = wil_has_active_ifaces(wil, true, false);321 mutex_unlock(&wil->vif_mutex);322 323 if (active_ifaces) {324 rc = wil_down(wil);325 if (rc) {326 wil_err(wil, "wil_down : %d\n", rc);327 wil->suspend_stats.r_off.failed_suspends++;328 goto out;329 }330 }331 332 /* Disable PCIe IRQ to prevent sporadic IRQs when PCIe is suspending */333 wil_dbg_pm(wil, "Disabling PCIe IRQ before suspending\n");334 wil_disable_irq(wil);335 336 if (wil->platform_ops.suspend) {337 rc = wil->platform_ops.suspend(wil->platform_handle, false);338 if (rc) {339 wil_enable_irq(wil);340 wil->suspend_stats.r_off.failed_suspends++;341 goto out;342 }343 }344 345 set_bit(wil_status_suspended, wil->status);346 347out:348 clear_bit(wil_status_suspending, wil->status);349 wil_dbg_pm(wil, "suspend radio off: %d\n", rc);350 351 return rc;352}353 354static int wil_resume_radio_off(struct wil6210_priv *wil)355{356 int rc = 0;357 bool active_ifaces;358 359 wil_dbg_pm(wil, "Enabling PCIe IRQ\n");360 wil_enable_irq(wil);361 /* if any netif up, bring hardware up362 * During open(), IFF_UP set after actual device method363 * invocation. This prevent recursive call to wil_up()364 * wil_status_suspended will be cleared in wil_reset365 */366 mutex_lock(&wil->vif_mutex);367 active_ifaces = wil_has_active_ifaces(wil, true, false);368 mutex_unlock(&wil->vif_mutex);369 if (active_ifaces)370 rc = wil_up(wil);371 else372 clear_bit(wil_status_suspended, wil->status);373 374 return rc;375}376 377int wil_suspend(struct wil6210_priv *wil, bool is_runtime, bool keep_radio_on)378{379 int rc = 0;380 381 wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system");382 383 if (test_bit(wil_status_suspended, wil->status)) {384 wil_dbg_pm(wil, "trying to suspend while suspended\n");385 return 0;386 }387 388 if (!keep_radio_on)389 rc = wil_suspend_radio_off(wil);390 else391 rc = wil_suspend_keep_radio_on(wil);392 393 wil_dbg_pm(wil, "suspend: %s => %d\n",394 is_runtime ? "runtime" : "system", rc);395 396 return rc;397}398 399int wil_resume(struct wil6210_priv *wil, bool is_runtime, bool keep_radio_on)400{401 int rc = 0;402 403 wil_dbg_pm(wil, "resume: %s\n", is_runtime ? "runtime" : "system");404 405 if (wil->platform_ops.resume) {406 rc = wil->platform_ops.resume(wil->platform_handle,407 keep_radio_on);408 if (rc) {409 wil_err(wil, "platform_ops.resume : %d\n", rc);410 goto out;411 }412 }413 414 if (keep_radio_on)415 rc = wil_resume_keep_radio_on(wil);416 else417 rc = wil_resume_radio_off(wil);418 419out:420 wil_dbg_pm(wil, "resume: %s => %d\n", is_runtime ? "runtime" : "system",421 rc);422 return rc;423}424 425void wil_pm_runtime_allow(struct wil6210_priv *wil)426{427 struct device *dev = wil_to_dev(wil);428 429 pm_runtime_put_noidle(dev);430 pm_runtime_set_autosuspend_delay(dev, WIL6210_AUTOSUSPEND_DELAY_MS);431 pm_runtime_use_autosuspend(dev);432 pm_runtime_allow(dev);433}434 435void wil_pm_runtime_forbid(struct wil6210_priv *wil)436{437 struct device *dev = wil_to_dev(wil);438 439 pm_runtime_forbid(dev);440 pm_runtime_get_noresume(dev);441}442 443int wil_pm_runtime_get(struct wil6210_priv *wil)444{445 int rc;446 struct device *dev = wil_to_dev(wil);447 448 rc = pm_runtime_resume_and_get(dev);449 if (rc < 0) {450 wil_err(wil, "pm_runtime_resume_and_get() failed, rc = %d\n", rc);451 return rc;452 }453 454 return 0;455}456 457void wil_pm_runtime_put(struct wil6210_priv *wil)458{459 struct device *dev = wil_to_dev(wil);460 461 pm_runtime_mark_last_busy(dev);462 pm_runtime_put_autosuspend(dev);463}464