wil6210: add support for PCIe D3hot in system suspend

In order to preserve the connection in suspend/resume flow,
wil6210 host allows going to PCIe D3hot state in suspend,
instead of performing a full wil6210 device reset. This
requires the platform ability to initiate wakeup in case of
RX data. To check that, a new platform API is added.
In addition, add cfg80211 suspend/resume callbacks
implementation.

Signed-off-by: Maya Erez <qca_merez@qca.qualcomm.com>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
This commit is contained in:
Maya Erez
2017-06-16 10:38:04 +03:00
committed by Kalle Valo
parent 5b49ee9f13
commit fe9ee51e6a
11 changed files with 604 additions and 50 deletions

View File

@@ -37,6 +37,8 @@ module_param(led_id, byte, 0444);
MODULE_PARM_DESC(led_id,
" 60G device led enablement. Set the led ID (0-2) to enable");
#define WIL_WAIT_FOR_SUSPEND_RESUME_COMP 200
/**
* WMI event receiving - theory of operations
*
@@ -233,6 +235,16 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len)
return -EAGAIN;
}
/* Allow sending only suspend / resume commands during susepnd flow */
if ((test_bit(wil_status_suspending, wil->status) ||
test_bit(wil_status_suspended, wil->status) ||
test_bit(wil_status_resuming, wil->status)) &&
((cmdid != WMI_TRAFFIC_SUSPEND_CMDID) &&
(cmdid != WMI_TRAFFIC_RESUME_CMDID))) {
wil_err(wil, "WMI: reject send_command during suspend\n");
return -EINVAL;
}
if (!head) {
wil_err(wil, "WMI head is garbage: 0x%08x\n", r->head);
return -EINVAL;
@@ -862,6 +874,11 @@ void wmi_recv_cmd(struct wil6210_priv *wil)
return;
}
if (test_bit(wil_status_suspended, wil->status)) {
wil_err(wil, "suspended. cannot handle WMI event\n");
return;
}
for (n = 0;; n++) {
u16 len;
bool q;
@@ -914,6 +931,15 @@ void wmi_recv_cmd(struct wil6210_priv *wil)
struct wmi_cmd_hdr *wmi = &evt->event.wmi;
u16 id = le16_to_cpu(wmi->command_id);
u32 tstamp = le32_to_cpu(wmi->fw_timestamp);
if (test_bit(wil_status_resuming, wil->status)) {
if (id == WMI_TRAFFIC_RESUME_EVENTID)
clear_bit(wil_status_resuming,
wil->status);
else
wil_err(wil,
"WMI evt %d while resuming\n",
id);
}
spin_lock_irqsave(&wil->wmi_ev_lock, flags);
if (wil->reply_id && wil->reply_id == id) {
if (wil->reply_buf) {
@@ -921,6 +947,11 @@ void wmi_recv_cmd(struct wil6210_priv *wil)
min(len, wil->reply_size));
immed_reply = true;
}
if (id == WMI_TRAFFIC_SUSPEND_EVENTID) {
wil_dbg_wmi(wil,
"set suspend_resp_rcvd\n");
wil->suspend_resp_rcvd = true;
}
}
spin_unlock_irqrestore(&wil->wmi_ev_lock, flags);
@@ -1762,6 +1793,85 @@ void wmi_event_flush(struct wil6210_priv *wil)
spin_unlock_irqrestore(&wil->wmi_ev_lock, flags);
}
int wmi_suspend(struct wil6210_priv *wil)
{
int rc;
struct wmi_traffic_suspend_cmd cmd = {
.wakeup_trigger = wil->wakeup_trigger,
};
struct {
struct wmi_cmd_hdr wmi;
struct wmi_traffic_suspend_event evt;
} __packed reply;
u32 suspend_to = WIL_WAIT_FOR_SUSPEND_RESUME_COMP;
wil->suspend_resp_rcvd = false;
wil->suspend_resp_comp = false;
reply.evt.status = WMI_TRAFFIC_SUSPEND_REJECTED;
rc = wmi_call(wil, WMI_TRAFFIC_SUSPEND_CMDID, &cmd, sizeof(cmd),
WMI_TRAFFIC_SUSPEND_EVENTID, &reply, sizeof(reply),
suspend_to);
if (rc) {
wil_err(wil, "wmi_call for suspend req failed, rc=%d\n", rc);
if (rc == -ETIME)
/* wmi_call TO */
wil->suspend_stats.rejected_by_device++;
else
wil->suspend_stats.rejected_by_host++;
goto out;
}
wil_dbg_wmi(wil, "waiting for suspend_response_completed\n");
rc = wait_event_interruptible_timeout(wil->wq,
wil->suspend_resp_comp,
msecs_to_jiffies(suspend_to));
if (rc == 0) {
wil_err(wil, "TO waiting for suspend_response_completed\n");
if (wil->suspend_resp_rcvd)
/* Device responded but we TO due to another reason */
wil->suspend_stats.rejected_by_host++;
else
wil->suspend_stats.rejected_by_device++;
rc = -EBUSY;
goto out;
}
wil_dbg_wmi(wil, "suspend_response_completed rcvd\n");
if (reply.evt.status == WMI_TRAFFIC_SUSPEND_REJECTED) {
wil_dbg_pm(wil, "device rejected the suspend\n");
wil->suspend_stats.rejected_by_device++;
}
rc = reply.evt.status;
out:
wil->suspend_resp_rcvd = false;
wil->suspend_resp_comp = false;
return rc;
}
int wmi_resume(struct wil6210_priv *wil)
{
int rc;
struct {
struct wmi_cmd_hdr wmi;
struct wmi_traffic_resume_event evt;
} __packed reply;
reply.evt.status = WMI_TRAFFIC_RESUME_FAILED;
rc = wmi_call(wil, WMI_TRAFFIC_RESUME_CMDID, NULL, 0,
WMI_TRAFFIC_RESUME_EVENTID, &reply, sizeof(reply),
WIL_WAIT_FOR_SUSPEND_RESUME_COMP);
if (rc)
return rc;
return reply.evt.status;
}
static bool wmi_evt_call_handler(struct wil6210_priv *wil, int id,
void *d, int len)
{
@@ -1851,3 +1961,36 @@ void wmi_event_worker(struct work_struct *work)
}
wil_dbg_wmi(wil, "event_worker: Finished\n");
}
bool wil_is_wmi_idle(struct wil6210_priv *wil)
{
ulong flags;
struct wil6210_mbox_ring *r = &wil->mbox_ctl.rx;
bool rc = false;
spin_lock_irqsave(&wil->wmi_ev_lock, flags);
/* Check if there are pending WMI events in the events queue */
if (!list_empty(&wil->pending_wmi_ev)) {
wil_dbg_pm(wil, "Pending WMI events in queue\n");
goto out;
}
/* Check if there is a pending WMI call */
if (wil->reply_id) {
wil_dbg_pm(wil, "Pending WMI call\n");
goto out;
}
/* Check if there are pending RX events in mbox */
r->head = wil_r(wil, RGF_MBOX +
offsetof(struct wil6210_mbox_ctl, rx.head));
if (r->tail != r->head)
wil_dbg_pm(wil, "Pending WMI mbox events\n");
else
rc = true;
out:
spin_unlock_irqrestore(&wil->wmi_ev_lock, flags);
return rc;
}