xref: /openbmc/linux/drivers/net/wireless/ath/wil6210/pm.c (revision 68198dca)
1 /*
2  * Copyright (c) 2014,2017 Qualcomm Atheros, Inc.
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "wil6210.h"
18 #include <linux/jiffies.h>
19 
20 int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime)
21 {
22 	int rc = 0;
23 	struct wireless_dev *wdev = wil->wdev;
24 	struct net_device *ndev = wil_to_ndev(wil);
25 
26 	wil_dbg_pm(wil, "can_suspend: %s\n", is_runtime ? "runtime" : "system");
27 
28 	if (!(ndev->flags & IFF_UP)) {
29 		/* can always sleep when down */
30 		wil_dbg_pm(wil, "Interface is down\n");
31 		goto out;
32 	}
33 	if (test_bit(wil_status_resetting, wil->status)) {
34 		wil_dbg_pm(wil, "Delay suspend when resetting\n");
35 		rc = -EBUSY;
36 		goto out;
37 	}
38 	if (wil->recovery_state != fw_recovery_idle) {
39 		wil_dbg_pm(wil, "Delay suspend during recovery\n");
40 		rc = -EBUSY;
41 		goto out;
42 	}
43 
44 	/* interface is running */
45 	switch (wdev->iftype) {
46 	case NL80211_IFTYPE_MONITOR:
47 	case NL80211_IFTYPE_STATION:
48 	case NL80211_IFTYPE_P2P_CLIENT:
49 		if (test_bit(wil_status_fwconnecting, wil->status)) {
50 			wil_dbg_pm(wil, "Delay suspend when connecting\n");
51 			rc = -EBUSY;
52 			goto out;
53 		}
54 		break;
55 	/* AP-like interface - can't suspend */
56 	default:
57 		wil_dbg_pm(wil, "AP-like interface\n");
58 		rc = -EBUSY;
59 		break;
60 	}
61 
62 out:
63 	wil_dbg_pm(wil, "can_suspend: %s => %s (%d)\n",
64 		   is_runtime ? "runtime" : "system", rc ? "No" : "Yes", rc);
65 
66 	if (rc)
67 		wil->suspend_stats.rejected_by_host++;
68 
69 	return rc;
70 }
71 
72 static int wil_resume_keep_radio_on(struct wil6210_priv *wil)
73 {
74 	int rc = 0;
75 
76 	/* wil_status_resuming will be cleared when getting
77 	 * WMI_TRAFFIC_RESUME_EVENTID
78 	 */
79 	set_bit(wil_status_resuming, wil->status);
80 	clear_bit(wil_status_suspended, wil->status);
81 	wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
82 	wil_unmask_irq(wil);
83 
84 	wil6210_bus_request(wil, wil->bus_request_kbps_pre_suspend);
85 
86 	/* Send WMI resume request to the device */
87 	rc = wmi_resume(wil);
88 	if (rc) {
89 		wil_err(wil, "device failed to resume (%d)\n", rc);
90 		if (no_fw_recovery)
91 			goto out;
92 		rc = wil_down(wil);
93 		if (rc) {
94 			wil_err(wil, "wil_down failed (%d)\n", rc);
95 			goto out;
96 		}
97 		rc = wil_up(wil);
98 		if (rc) {
99 			wil_err(wil, "wil_up failed (%d)\n", rc);
100 			goto out;
101 		}
102 	}
103 
104 	/* Wake all queues */
105 	if (test_bit(wil_status_fwconnected, wil->status))
106 		wil_update_net_queues_bh(wil, NULL, false);
107 
108 out:
109 	if (rc)
110 		set_bit(wil_status_suspended, wil->status);
111 	return rc;
112 }
113 
114 static int wil_suspend_keep_radio_on(struct wil6210_priv *wil)
115 {
116 	int rc = 0;
117 	unsigned long start, data_comp_to;
118 
119 	wil_dbg_pm(wil, "suspend keep radio on\n");
120 
121 	/* Prevent handling of new tx and wmi commands */
122 	set_bit(wil_status_suspending, wil->status);
123 	wil_update_net_queues_bh(wil, NULL, true);
124 
125 	if (!wil_is_tx_idle(wil)) {
126 		wil_dbg_pm(wil, "Pending TX data, reject suspend\n");
127 		wil->suspend_stats.rejected_by_host++;
128 		goto reject_suspend;
129 	}
130 
131 	if (!wil_is_rx_idle(wil)) {
132 		wil_dbg_pm(wil, "Pending RX data, reject suspend\n");
133 		wil->suspend_stats.rejected_by_host++;
134 		goto reject_suspend;
135 	}
136 
137 	if (!wil_is_wmi_idle(wil)) {
138 		wil_dbg_pm(wil, "Pending WMI events, reject suspend\n");
139 		wil->suspend_stats.rejected_by_host++;
140 		goto reject_suspend;
141 	}
142 
143 	/* Send WMI suspend request to the device */
144 	rc = wmi_suspend(wil);
145 	if (rc) {
146 		wil_dbg_pm(wil, "wmi_suspend failed, reject suspend (%d)\n",
147 			   rc);
148 		goto reject_suspend;
149 	}
150 
151 	/* Wait for completion of the pending RX packets */
152 	start = jiffies;
153 	data_comp_to = jiffies + msecs_to_jiffies(WIL_DATA_COMPLETION_TO_MS);
154 	if (test_bit(wil_status_napi_en, wil->status)) {
155 		while (!wil_is_rx_idle(wil)) {
156 			if (time_after(jiffies, data_comp_to)) {
157 				if (wil_is_rx_idle(wil))
158 					break;
159 				wil_err(wil,
160 					"TO waiting for idle RX, suspend failed\n");
161 				wil->suspend_stats.failed_suspends++;
162 				goto resume_after_fail;
163 			}
164 			wil_dbg_ratelimited(wil, "rx vring is not empty -> NAPI\n");
165 			napi_synchronize(&wil->napi_rx);
166 			msleep(20);
167 		}
168 	}
169 
170 	/* In case of pending WMI events, reject the suspend
171 	 * and resume the device.
172 	 * This can happen if the device sent the WMI events before
173 	 * approving the suspend.
174 	 */
175 	if (!wil_is_wmi_idle(wil)) {
176 		wil_err(wil, "suspend failed due to pending WMI events\n");
177 		wil->suspend_stats.failed_suspends++;
178 		goto resume_after_fail;
179 	}
180 
181 	wil_mask_irq(wil);
182 
183 	/* Disable device reset on PERST */
184 	wil_s(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
185 
186 	if (wil->platform_ops.suspend) {
187 		rc = wil->platform_ops.suspend(wil->platform_handle, true);
188 		if (rc) {
189 			wil_err(wil, "platform device failed to suspend (%d)\n",
190 				rc);
191 			wil->suspend_stats.failed_suspends++;
192 			wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
193 			wil_unmask_irq(wil);
194 			goto resume_after_fail;
195 		}
196 	}
197 
198 	/* Save the current bus request to return to the same in resume */
199 	wil->bus_request_kbps_pre_suspend = wil->bus_request_kbps;
200 	wil6210_bus_request(wil, 0);
201 
202 	set_bit(wil_status_suspended, wil->status);
203 	clear_bit(wil_status_suspending, wil->status);
204 
205 	return rc;
206 
207 resume_after_fail:
208 	set_bit(wil_status_resuming, wil->status);
209 	clear_bit(wil_status_suspending, wil->status);
210 	rc = wmi_resume(wil);
211 	/* if resume succeeded, reject the suspend */
212 	if (!rc) {
213 		rc = -EBUSY;
214 		if (test_bit(wil_status_fwconnected, wil->status))
215 			wil_update_net_queues_bh(wil, NULL, false);
216 	}
217 	return rc;
218 
219 reject_suspend:
220 	clear_bit(wil_status_suspending, wil->status);
221 	if (test_bit(wil_status_fwconnected, wil->status))
222 		wil_update_net_queues_bh(wil, NULL, false);
223 	return -EBUSY;
224 }
225 
226 static int wil_suspend_radio_off(struct wil6210_priv *wil)
227 {
228 	int rc = 0;
229 	struct net_device *ndev = wil_to_ndev(wil);
230 
231 	wil_dbg_pm(wil, "suspend radio off\n");
232 
233 	/* if netif up, hardware is alive, shut it down */
234 	if (ndev->flags & IFF_UP) {
235 		rc = wil_down(wil);
236 		if (rc) {
237 			wil_err(wil, "wil_down : %d\n", rc);
238 			goto out;
239 		}
240 	}
241 
242 	/* Disable PCIe IRQ to prevent sporadic IRQs when PCIe is suspending */
243 	wil_dbg_pm(wil, "Disabling PCIe IRQ before suspending\n");
244 	wil_disable_irq(wil);
245 
246 	if (wil->platform_ops.suspend) {
247 		rc = wil->platform_ops.suspend(wil->platform_handle, false);
248 		if (rc) {
249 			wil_enable_irq(wil);
250 			goto out;
251 		}
252 	}
253 
254 	set_bit(wil_status_suspended, wil->status);
255 
256 out:
257 	wil_dbg_pm(wil, "suspend radio off: %d\n", rc);
258 
259 	return rc;
260 }
261 
262 static int wil_resume_radio_off(struct wil6210_priv *wil)
263 {
264 	int rc = 0;
265 	struct net_device *ndev = wil_to_ndev(wil);
266 
267 	wil_dbg_pm(wil, "Enabling PCIe IRQ\n");
268 	wil_enable_irq(wil);
269 	/* if netif up, bring hardware up
270 	 * During open(), IFF_UP set after actual device method
271 	 * invocation. This prevent recursive call to wil_up()
272 	 * wil_status_suspended will be cleared in wil_reset
273 	 */
274 	if (ndev->flags & IFF_UP)
275 		rc = wil_up(wil);
276 	else
277 		clear_bit(wil_status_suspended, wil->status);
278 
279 	return rc;
280 }
281 
282 int wil_suspend(struct wil6210_priv *wil, bool is_runtime)
283 {
284 	int rc = 0;
285 	struct net_device *ndev = wil_to_ndev(wil);
286 	bool keep_radio_on = ndev->flags & IFF_UP &&
287 			     wil->keep_radio_on_during_sleep;
288 
289 	wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system");
290 
291 	if (test_bit(wil_status_suspended, wil->status)) {
292 		wil_dbg_pm(wil, "trying to suspend while suspended\n");
293 		return 0;
294 	}
295 
296 	if (!keep_radio_on)
297 		rc = wil_suspend_radio_off(wil);
298 	else
299 		rc = wil_suspend_keep_radio_on(wil);
300 
301 	wil_dbg_pm(wil, "suspend: %s => %d\n",
302 		   is_runtime ? "runtime" : "system", rc);
303 
304 	if (!rc)
305 		wil->suspend_stats.suspend_start_time = ktime_get();
306 
307 	return rc;
308 }
309 
310 int wil_resume(struct wil6210_priv *wil, bool is_runtime)
311 {
312 	int rc = 0;
313 	struct net_device *ndev = wil_to_ndev(wil);
314 	bool keep_radio_on = ndev->flags & IFF_UP &&
315 			     wil->keep_radio_on_during_sleep;
316 	unsigned long long suspend_time_usec = 0;
317 
318 	wil_dbg_pm(wil, "resume: %s\n", is_runtime ? "runtime" : "system");
319 
320 	if (wil->platform_ops.resume) {
321 		rc = wil->platform_ops.resume(wil->platform_handle,
322 					      keep_radio_on);
323 		if (rc) {
324 			wil_err(wil, "platform_ops.resume : %d\n", rc);
325 			goto out;
326 		}
327 	}
328 
329 	if (keep_radio_on)
330 		rc = wil_resume_keep_radio_on(wil);
331 	else
332 		rc = wil_resume_radio_off(wil);
333 
334 	if (rc)
335 		goto out;
336 
337 	suspend_time_usec =
338 		ktime_to_us(ktime_sub(ktime_get(),
339 				      wil->suspend_stats.suspend_start_time));
340 	wil->suspend_stats.total_suspend_time += suspend_time_usec;
341 	if (suspend_time_usec < wil->suspend_stats.min_suspend_time)
342 		wil->suspend_stats.min_suspend_time = suspend_time_usec;
343 	if (suspend_time_usec > wil->suspend_stats.max_suspend_time)
344 		wil->suspend_stats.max_suspend_time = suspend_time_usec;
345 
346 out:
347 	wil_dbg_pm(wil, "resume: %s => %d, suspend time %lld usec\n",
348 		   is_runtime ? "runtime" : "system", rc, suspend_time_usec);
349 	return rc;
350 }
351