diff options
Diffstat (limited to 'drivers/net/wireless/ath/wil6210/pm.c')
| -rw-r--r-- | drivers/net/wireless/ath/wil6210/pm.c | 228 | 
1 files changed, 209 insertions, 19 deletions
| diff --git a/drivers/net/wireless/ath/wil6210/pm.c b/drivers/net/wireless/ath/wil6210/pm.c index 2ae4fe85cc8c..ce1f384e7f8e 100644 --- a/drivers/net/wireless/ath/wil6210/pm.c +++ b/drivers/net/wireless/ath/wil6210/pm.c @@ -15,6 +15,7 @@   */  #include "wil6210.h" +#include <linux/jiffies.h>  int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime)  { @@ -61,20 +62,170 @@ out:  	wil_dbg_pm(wil, "can_suspend: %s => %s (%d)\n",  		   is_runtime ? "runtime" : "system", rc ? "No" : "Yes", rc); +	if (rc) +		wil->suspend_stats.rejected_by_host++; +  	return rc;  } -int wil_suspend(struct wil6210_priv *wil, bool is_runtime) +static int wil_resume_keep_radio_on(struct wil6210_priv *wil)  {  	int rc = 0; -	struct net_device *ndev = wil_to_ndev(wil); -	wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system"); +	/* wil_status_resuming will be cleared when getting +	 * WMI_TRAFFIC_RESUME_EVENTID +	 */ +	set_bit(wil_status_resuming, wil->status); +	clear_bit(wil_status_suspended, wil->status); +	wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); +	wil_unmask_irq(wil); -	if (test_bit(wil_status_suspended, wil->status)) { -		wil_dbg_pm(wil, "trying to suspend while suspended\n"); -		return 0; +	wil6210_bus_request(wil, wil->bus_request_kbps_pre_suspend); + +	/* Send WMI resume request to the device */ +	rc = wmi_resume(wil); +	if (rc) { +		wil_err(wil, "device failed to resume (%d), resetting\n", rc); +		rc = wil_down(wil); +		if (rc) { +			wil_err(wil, "wil_down failed (%d)\n", rc); +			goto out; +		} +		rc = wil_up(wil); +		if (rc) { +			wil_err(wil, "wil_up failed (%d)\n", rc); +			goto out; +		} +	} + +	/* Wake all queues */ +	if (test_bit(wil_status_fwconnected, wil->status)) +		wil_update_net_queues_bh(wil, NULL, false); + +out: +	if (rc) +		set_bit(wil_status_suspended, wil->status); +	return rc; +} + +static int wil_suspend_keep_radio_on(struct wil6210_priv *wil) +{ +	int rc = 0; +	unsigned long start, data_comp_to; + +	wil_dbg_pm(wil, "suspend keep radio on\n"); + +	/* Prevent handling of new tx and wmi commands */ +	set_bit(wil_status_suspending, wil->status); +	wil_update_net_queues_bh(wil, NULL, true); + +	if (!wil_is_tx_idle(wil)) { +		wil_dbg_pm(wil, "Pending TX data, reject suspend\n"); +		wil->suspend_stats.rejected_by_host++; +		goto reject_suspend; +	} + +	if (!wil_is_rx_idle(wil)) { +		wil_dbg_pm(wil, "Pending RX data, reject suspend\n"); +		wil->suspend_stats.rejected_by_host++; +		goto reject_suspend; +	} + +	if (!wil_is_wmi_idle(wil)) { +		wil_dbg_pm(wil, "Pending WMI events, reject suspend\n"); +		wil->suspend_stats.rejected_by_host++; +		goto reject_suspend; +	} + +	/* Send WMI suspend request to the device */ +	rc = wmi_suspend(wil); +	if (rc) { +		wil_dbg_pm(wil, "wmi_suspend failed, reject suspend (%d)\n", +			   rc); +		goto reject_suspend; +	} + +	/* 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)) { +			if (time_after(jiffies, data_comp_to)) { +				if (wil_is_rx_idle(wil)) +					break; +				wil_err(wil, +					"TO waiting for idle RX, suspend failed\n"); +				wil->suspend_stats.failed_suspends++; +				goto resume_after_fail; +			} +			wil_dbg_ratelimited(wil, "rx vring is not empty -> NAPI\n"); +			napi_synchronize(&wil->napi_rx); +			msleep(20); +		} +	} + +	/* In case of pending WMI events, reject the suspend +	 * and resume the device. +	 * This can happen if the device sent the WMI events before +	 * approving the suspend. +	 */ +	if (!wil_is_wmi_idle(wil)) { +		wil_err(wil, "suspend failed due to pending WMI events\n"); +		wil->suspend_stats.failed_suspends++; +		goto resume_after_fail; +	} + +	wil_mask_irq(wil); + +	/* Disable device reset on PERST */ +	wil_s(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); + +	if (wil->platform_ops.suspend) { +		rc = wil->platform_ops.suspend(wil->platform_handle, true); +		if (rc) { +			wil_err(wil, "platform device failed to suspend (%d)\n", +				rc); +			wil->suspend_stats.failed_suspends++; +			wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); +			wil_unmask_irq(wil); +			goto resume_after_fail; +		} +	} + +	/* Save the current bus request to return to the same in resume */ +	wil->bus_request_kbps_pre_suspend = wil->bus_request_kbps; +	wil6210_bus_request(wil, 0); + +	set_bit(wil_status_suspended, wil->status); +	clear_bit(wil_status_suspending, wil->status); + +	return rc; + +resume_after_fail: +	set_bit(wil_status_resuming, wil->status); +	clear_bit(wil_status_suspending, wil->status); +	rc = wmi_resume(wil); +	/* 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);  	} +	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); +	return -EBUSY; +} + +static int wil_suspend_radio_off(struct wil6210_priv *wil) +{ +	int rc = 0; +	struct net_device *ndev = wil_to_ndev(wil); + +	wil_dbg_pm(wil, "suspend radio off\n");  	/* if netif up, hardware is alive, shut it down */  	if (ndev->flags & IFF_UP) { @@ -90,7 +241,7 @@ int wil_suspend(struct wil6210_priv *wil, bool is_runtime)  	wil_disable_irq(wil);  	if (wil->platform_ops.suspend) { -		rc = wil->platform_ops.suspend(wil->platform_handle); +		rc = wil->platform_ops.suspend(wil->platform_handle, false);  		if (rc) {  			wil_enable_irq(wil);  			goto out; @@ -100,6 +251,50 @@ int wil_suspend(struct wil6210_priv *wil, bool is_runtime)  	set_bit(wil_status_suspended, wil->status);  out: +	wil_dbg_pm(wil, "suspend radio off: %d\n", rc); + +	return rc; +} + +static int wil_resume_radio_off(struct wil6210_priv *wil) +{ +	int rc = 0; +	struct net_device *ndev = wil_to_ndev(wil); + +	wil_dbg_pm(wil, "Enabling PCIe IRQ\n"); +	wil_enable_irq(wil); +	/* if 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) +		rc = wil_up(wil); +	else +		clear_bit(wil_status_suspended, wil->status); + +	return rc; +} + +int wil_suspend(struct wil6210_priv *wil, bool is_runtime) +{ +	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"); + +	if (test_bit(wil_status_suspended, wil->status)) { +		wil_dbg_pm(wil, "trying to suspend while suspended\n"); +		return 0; +	} + +	if (!keep_radio_on) +		rc = wil_suspend_radio_off(wil); +	else +		rc = wil_suspend_keep_radio_on(wil); +  	wil_dbg_pm(wil, "suspend: %s => %d\n",  		   is_runtime ? "runtime" : "system", rc); @@ -110,29 +305,24 @@ int wil_resume(struct wil6210_priv *wil, bool is_runtime)  {  	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");  	if (wil->platform_ops.resume) { -		rc = wil->platform_ops.resume(wil->platform_handle); +		rc = wil->platform_ops.resume(wil->platform_handle, +					      keep_radio_on);  		if (rc) {  			wil_err(wil, "platform_ops.resume : %d\n", rc);  			goto out;  		}  	} -	wil_dbg_pm(wil, "Enabling PCIe IRQ\n"); -	wil_enable_irq(wil); - -	/* if 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) -		rc = wil_up(wil); +	if (keep_radio_on) +		rc = wil_resume_keep_radio_on(wil);  	else -		clear_bit(wil_status_suspended, wil->status); +		rc = wil_resume_radio_off(wil);  out:  	wil_dbg_pm(wil, "resume: %s => %d\n", | 
