ath10k: move fw_features to struct ath10k_fw_file
[cascardo/linux.git] / drivers / net / wireless / ath / ath10k / wow.c
1 /*
2  * Copyright (c) 2015 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 "mac.h"
18
19 #include <net/mac80211.h>
20 #include "hif.h"
21 #include "core.h"
22 #include "debug.h"
23 #include "wmi.h"
24 #include "wmi-ops.h"
25
26 static const struct wiphy_wowlan_support ath10k_wowlan_support = {
27         .flags = WIPHY_WOWLAN_DISCONNECT |
28                  WIPHY_WOWLAN_MAGIC_PKT,
29         .pattern_min_len = WOW_MIN_PATTERN_SIZE,
30         .pattern_max_len = WOW_MAX_PATTERN_SIZE,
31         .max_pkt_offset = WOW_MAX_PKT_OFFSET,
32 };
33
34 static int ath10k_wow_vif_cleanup(struct ath10k_vif *arvif)
35 {
36         struct ath10k *ar = arvif->ar;
37         int i, ret;
38
39         for (i = 0; i < WOW_EVENT_MAX; i++) {
40                 ret = ath10k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 0);
41                 if (ret) {
42                         ath10k_warn(ar, "failed to issue wow wakeup for event %s on vdev %i: %d\n",
43                                     wow_wakeup_event(i), arvif->vdev_id, ret);
44                         return ret;
45                 }
46         }
47
48         for (i = 0; i < ar->wow.max_num_patterns; i++) {
49                 ret = ath10k_wmi_wow_del_pattern(ar, arvif->vdev_id, i);
50                 if (ret) {
51                         ath10k_warn(ar, "failed to delete wow pattern %d for vdev %i: %d\n",
52                                     i, arvif->vdev_id, ret);
53                         return ret;
54                 }
55         }
56
57         return 0;
58 }
59
60 static int ath10k_wow_cleanup(struct ath10k *ar)
61 {
62         struct ath10k_vif *arvif;
63         int ret;
64
65         lockdep_assert_held(&ar->conf_mutex);
66
67         list_for_each_entry(arvif, &ar->arvifs, list) {
68                 ret = ath10k_wow_vif_cleanup(arvif);
69                 if (ret) {
70                         ath10k_warn(ar, "failed to clean wow wakeups on vdev %i: %d\n",
71                                     arvif->vdev_id, ret);
72                         return ret;
73                 }
74         }
75
76         return 0;
77 }
78
79 static int ath10k_vif_wow_set_wakeups(struct ath10k_vif *arvif,
80                                       struct cfg80211_wowlan *wowlan)
81 {
82         int ret, i;
83         unsigned long wow_mask = 0;
84         struct ath10k *ar = arvif->ar;
85         const struct cfg80211_pkt_pattern *patterns = wowlan->patterns;
86         int pattern_id = 0;
87
88         /* Setup requested WOW features */
89         switch (arvif->vdev_type) {
90         case WMI_VDEV_TYPE_IBSS:
91                 __set_bit(WOW_BEACON_EVENT, &wow_mask);
92                  /* fall through */
93         case WMI_VDEV_TYPE_AP:
94                 __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask);
95                 __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask);
96                 __set_bit(WOW_PROBE_REQ_WPS_IE_EVENT, &wow_mask);
97                 __set_bit(WOW_AUTH_REQ_EVENT, &wow_mask);
98                 __set_bit(WOW_ASSOC_REQ_EVENT, &wow_mask);
99                 __set_bit(WOW_HTT_EVENT, &wow_mask);
100                 __set_bit(WOW_RA_MATCH_EVENT, &wow_mask);
101                 break;
102         case WMI_VDEV_TYPE_STA:
103                 if (wowlan->disconnect) {
104                         __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask);
105                         __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask);
106                         __set_bit(WOW_BMISS_EVENT, &wow_mask);
107                         __set_bit(WOW_CSA_IE_EVENT, &wow_mask);
108                 }
109
110                 if (wowlan->magic_pkt)
111                         __set_bit(WOW_MAGIC_PKT_RECVD_EVENT, &wow_mask);
112                 break;
113         default:
114                 break;
115         }
116
117         for (i = 0; i < wowlan->n_patterns; i++) {
118                 u8 bitmask[WOW_MAX_PATTERN_SIZE] = {};
119                 int j;
120
121                 if (patterns[i].pattern_len > WOW_MAX_PATTERN_SIZE)
122                         continue;
123
124                 /* convert bytemask to bitmask */
125                 for (j = 0; j < patterns[i].pattern_len; j++)
126                         if (patterns[i].mask[j / 8] & BIT(j % 8))
127                                 bitmask[j] = 0xff;
128
129                 ret = ath10k_wmi_wow_add_pattern(ar, arvif->vdev_id,
130                                                  pattern_id,
131                                                  patterns[i].pattern,
132                                                  bitmask,
133                                                  patterns[i].pattern_len,
134                                                  patterns[i].pkt_offset);
135                 if (ret) {
136                         ath10k_warn(ar, "failed to add pattern %i to vdev %i: %d\n",
137                                     pattern_id,
138                                     arvif->vdev_id, ret);
139                         return ret;
140                 }
141
142                 pattern_id++;
143                 __set_bit(WOW_PATTERN_MATCH_EVENT, &wow_mask);
144         }
145
146         for (i = 0; i < WOW_EVENT_MAX; i++) {
147                 if (!test_bit(i, &wow_mask))
148                         continue;
149                 ret = ath10k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 1);
150                 if (ret) {
151                         ath10k_warn(ar, "failed to enable wakeup event %s on vdev %i: %d\n",
152                                     wow_wakeup_event(i), arvif->vdev_id, ret);
153                         return ret;
154                 }
155         }
156
157         return 0;
158 }
159
160 static int ath10k_wow_set_wakeups(struct ath10k *ar,
161                                   struct cfg80211_wowlan *wowlan)
162 {
163         struct ath10k_vif *arvif;
164         int ret;
165
166         lockdep_assert_held(&ar->conf_mutex);
167
168         list_for_each_entry(arvif, &ar->arvifs, list) {
169                 ret = ath10k_vif_wow_set_wakeups(arvif, wowlan);
170                 if (ret) {
171                         ath10k_warn(ar, "failed to set wow wakeups on vdev %i: %d\n",
172                                     arvif->vdev_id, ret);
173                         return ret;
174                 }
175         }
176
177         return 0;
178 }
179
180 static int ath10k_wow_enable(struct ath10k *ar)
181 {
182         int ret;
183
184         lockdep_assert_held(&ar->conf_mutex);
185
186         reinit_completion(&ar->target_suspend);
187
188         ret = ath10k_wmi_wow_enable(ar);
189         if (ret) {
190                 ath10k_warn(ar, "failed to issue wow enable: %d\n", ret);
191                 return ret;
192         }
193
194         ret = wait_for_completion_timeout(&ar->target_suspend, 3 * HZ);
195         if (ret == 0) {
196                 ath10k_warn(ar, "timed out while waiting for suspend completion\n");
197                 return -ETIMEDOUT;
198         }
199
200         return 0;
201 }
202
203 static int ath10k_wow_wakeup(struct ath10k *ar)
204 {
205         int ret;
206
207         lockdep_assert_held(&ar->conf_mutex);
208
209         reinit_completion(&ar->wow.wakeup_completed);
210
211         ret = ath10k_wmi_wow_host_wakeup_ind(ar);
212         if (ret) {
213                 ath10k_warn(ar, "failed to send wow wakeup indication: %d\n",
214                             ret);
215                 return ret;
216         }
217
218         ret = wait_for_completion_timeout(&ar->wow.wakeup_completed, 3 * HZ);
219         if (ret == 0) {
220                 ath10k_warn(ar, "timed out while waiting for wow wakeup completion\n");
221                 return -ETIMEDOUT;
222         }
223
224         return 0;
225 }
226
227 int ath10k_wow_op_suspend(struct ieee80211_hw *hw,
228                           struct cfg80211_wowlan *wowlan)
229 {
230         struct ath10k *ar = hw->priv;
231         int ret;
232
233         mutex_lock(&ar->conf_mutex);
234
235         if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
236                               ar->running_fw->fw_file.fw_features))) {
237                 ret = 1;
238                 goto exit;
239         }
240
241         ret =  ath10k_wow_cleanup(ar);
242         if (ret) {
243                 ath10k_warn(ar, "failed to clear wow wakeup events: %d\n",
244                             ret);
245                 goto exit;
246         }
247
248         ret = ath10k_wow_set_wakeups(ar, wowlan);
249         if (ret) {
250                 ath10k_warn(ar, "failed to set wow wakeup events: %d\n",
251                             ret);
252                 goto cleanup;
253         }
254
255         ret = ath10k_wow_enable(ar);
256         if (ret) {
257                 ath10k_warn(ar, "failed to start wow: %d\n", ret);
258                 goto cleanup;
259         }
260
261         ret = ath10k_hif_suspend(ar);
262         if (ret) {
263                 ath10k_warn(ar, "failed to suspend hif: %d\n", ret);
264                 goto wakeup;
265         }
266
267         goto exit;
268
269 wakeup:
270         ath10k_wow_wakeup(ar);
271
272 cleanup:
273         ath10k_wow_cleanup(ar);
274
275 exit:
276         mutex_unlock(&ar->conf_mutex);
277         return ret ? 1 : 0;
278 }
279
280 int ath10k_wow_op_resume(struct ieee80211_hw *hw)
281 {
282         struct ath10k *ar = hw->priv;
283         int ret;
284
285         mutex_lock(&ar->conf_mutex);
286
287         if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
288                               ar->running_fw->fw_file.fw_features))) {
289                 ret = 1;
290                 goto exit;
291         }
292
293         ret = ath10k_hif_resume(ar);
294         if (ret) {
295                 ath10k_warn(ar, "failed to resume hif: %d\n", ret);
296                 goto exit;
297         }
298
299         ret = ath10k_wow_wakeup(ar);
300         if (ret)
301                 ath10k_warn(ar, "failed to wakeup from wow: %d\n", ret);
302
303 exit:
304         if (ret) {
305                 switch (ar->state) {
306                 case ATH10K_STATE_ON:
307                         ar->state = ATH10K_STATE_RESTARTING;
308                         ret = 1;
309                         break;
310                 case ATH10K_STATE_OFF:
311                 case ATH10K_STATE_RESTARTING:
312                 case ATH10K_STATE_RESTARTED:
313                 case ATH10K_STATE_UTF:
314                 case ATH10K_STATE_WEDGED:
315                         ath10k_warn(ar, "encountered unexpected device state %d on resume, cannot recover\n",
316                                     ar->state);
317                         ret = -EIO;
318                         break;
319                 }
320         }
321
322         mutex_unlock(&ar->conf_mutex);
323         return ret;
324 }
325
326 int ath10k_wow_init(struct ath10k *ar)
327 {
328         if (!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
329                       ar->running_fw->fw_file.fw_features))
330                 return 0;
331
332         if (WARN_ON(!test_bit(WMI_SERVICE_WOW, ar->wmi.svc_map)))
333                 return -EINVAL;
334
335         ar->wow.wowlan_support = ath10k_wowlan_support;
336         ar->wow.wowlan_support.n_patterns = ar->wow.max_num_patterns;
337         ar->hw->wiphy->wowlan = &ar->wow.wowlan_support;
338
339         return 0;
340 }