1 | // SPDX-License-Identifier: ISC |
2 | /* |
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 | |
13 | static 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, check_stop: false); |
23 | } |
24 | mutex_unlock(lock: &wil->vif_mutex); |
25 | } |
26 | |
27 | static 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, check_stop: true); |
37 | } |
38 | mutex_unlock(lock: &wil->vif_mutex); |
39 | } |
40 | |
41 | static bool |
42 | wil_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 | |
73 | int 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, up: true, ok: false); |
95 | mutex_unlock(lock: &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(lock: &wil->vif_mutex); |
123 | goto out; |
124 | } |
125 | } |
126 | mutex_unlock(lock: &wil->vif_mutex); |
127 | |
128 | out: |
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 | |
138 | static int wil_resume_keep_radio_on(struct wil6210_priv *wil) |
139 | { |
140 | int rc = 0; |
141 | |
142 | /* wil_status_resuming will be cleared when getting |
143 | * WMI_TRAFFIC_RESUME_EVENTID |
144 | */ |
145 | set_bit(nr: wil_status_resuming, addr: wil->status); |
146 | clear_bit(nr: wil_status_suspended, addr: 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, kbps: 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 | |
173 | out: |
174 | if (rc) |
175 | set_bit(nr: wil_status_suspended, addr: wil->status); |
176 | return rc; |
177 | } |
178 | |
179 | static 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(sem: &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(nr: wil_status_suspending, addr: wil->status); |
197 | up_write(sem: &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, fmt: "rx vring is not empty -> NAPI\n" ); |
240 | napi_synchronize(n: &wil->napi_rx); |
241 | msleep(msecs: 20); |
242 | } |
243 | } |
244 | |
245 | /* In case of pending WMI events, reject the suspend |
246 | * and resume the device. |
247 | * This can happen if the device sent the WMI events before |
248 | * 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, kbps: 0); |
276 | |
277 | set_bit(nr: wil_status_suspended, addr: wil->status); |
278 | clear_bit(nr: wil_status_suspending, addr: wil->status); |
279 | |
280 | return rc; |
281 | |
282 | resume_after_fail: |
283 | set_bit(nr: wil_status_resuming, addr: wil->status); |
284 | clear_bit(nr: wil_status_suspending, addr: 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 | |
293 | reject_suspend: |
294 | clear_bit(nr: wil_status_suspending, addr: wil->status); |
295 | wil_pm_wake_connected_net_queues(wil); |
296 | return -EBUSY; |
297 | } |
298 | |
299 | static 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(sem: &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(nr: wil_status_suspending, addr: wil->status); |
316 | up_write(sem: &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, up: true, ok: false); |
321 | mutex_unlock(lock: &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(nr: wil_status_suspended, addr: wil->status); |
346 | |
347 | out: |
348 | clear_bit(nr: wil_status_suspending, addr: wil->status); |
349 | wil_dbg_pm(wil, "suspend radio off: %d\n" , rc); |
350 | |
351 | return rc; |
352 | } |
353 | |
354 | static 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 up |
362 | * During open(), IFF_UP set after actual device method |
363 | * invocation. This prevent recursive call to wil_up() |
364 | * wil_status_suspended will be cleared in wil_reset |
365 | */ |
366 | mutex_lock(&wil->vif_mutex); |
367 | active_ifaces = wil_has_active_ifaces(wil, up: true, ok: false); |
368 | mutex_unlock(lock: &wil->vif_mutex); |
369 | if (active_ifaces) |
370 | rc = wil_up(wil); |
371 | else |
372 | clear_bit(nr: wil_status_suspended, addr: wil->status); |
373 | |
374 | return rc; |
375 | } |
376 | |
377 | int 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 | else |
391 | 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 | |
399 | int 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 | else |
417 | rc = wil_resume_radio_off(wil); |
418 | |
419 | out: |
420 | wil_dbg_pm(wil, "resume: %s => %d\n" , is_runtime ? "runtime" : "system" , |
421 | rc); |
422 | return rc; |
423 | } |
424 | |
425 | void 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 | |
435 | void 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 | |
443 | int 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 | |
457 | void 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 | |