diff options
Diffstat (limited to 'drivers/net/wireless/rtlwifi/ps.c')
-rw-r--r-- | drivers/net/wireless/rtlwifi/ps.c | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/drivers/net/wireless/rtlwifi/ps.c b/drivers/net/wireless/rtlwifi/ps.c new file mode 100644 index 000000000000..fd77cd508f50 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/ps.c | |||
@@ -0,0 +1,492 @@ | |||
1 | /****************************************************************************** | ||
2 | * | ||
3 | * Copyright(c) 2009-2010 Realtek Corporation. | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of version 2 of the GNU General Public License as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program; if not, write to the Free Software Foundation, Inc., | ||
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA | ||
17 | * | ||
18 | * The full GNU General Public License is included in this distribution in the | ||
19 | * file called LICENSE. | ||
20 | * | ||
21 | * Contact Information: | ||
22 | * wlanfae <wlanfae@realtek.com> | ||
23 | * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, | ||
24 | * Hsinchu 300, Taiwan. | ||
25 | * | ||
26 | * Larry Finger <Larry.Finger@lwfinger.net> | ||
27 | * | ||
28 | *****************************************************************************/ | ||
29 | |||
30 | #include "wifi.h" | ||
31 | #include "base.h" | ||
32 | #include "ps.h" | ||
33 | |||
34 | bool rtl_ps_enable_nic(struct ieee80211_hw *hw) | ||
35 | { | ||
36 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
37 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
38 | struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); | ||
39 | bool init_status = true; | ||
40 | |||
41 | /*<1> reset trx ring */ | ||
42 | if (rtlhal->interface == INTF_PCI) | ||
43 | rtlpriv->intf_ops->reset_trx_ring(hw); | ||
44 | |||
45 | if (is_hal_stop(rtlhal)) | ||
46 | RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, | ||
47 | ("Driver is already down!\n")); | ||
48 | |||
49 | /*<2> Enable Adapter */ | ||
50 | rtlpriv->cfg->ops->hw_init(hw); | ||
51 | RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); | ||
52 | /*init_status = false; */ | ||
53 | |||
54 | /*<3> Enable Interrupt */ | ||
55 | rtlpriv->cfg->ops->enable_interrupt(hw); | ||
56 | |||
57 | /*<enable timer> */ | ||
58 | rtl_watch_dog_timer_callback((unsigned long)hw); | ||
59 | |||
60 | return init_status; | ||
61 | } | ||
62 | EXPORT_SYMBOL(rtl_ps_enable_nic); | ||
63 | |||
64 | bool rtl_ps_disable_nic(struct ieee80211_hw *hw) | ||
65 | { | ||
66 | bool status = true; | ||
67 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
68 | |||
69 | /*<1> Stop all timer */ | ||
70 | rtl_deinit_deferred_work(hw); | ||
71 | |||
72 | /*<2> Disable Interrupt */ | ||
73 | rtlpriv->cfg->ops->disable_interrupt(hw); | ||
74 | |||
75 | /*<3> Disable Adapter */ | ||
76 | rtlpriv->cfg->ops->hw_disable(hw); | ||
77 | |||
78 | return status; | ||
79 | } | ||
80 | EXPORT_SYMBOL(rtl_ps_disable_nic); | ||
81 | |||
82 | bool rtl_ps_set_rf_state(struct ieee80211_hw *hw, | ||
83 | enum rf_pwrstate state_toset, | ||
84 | u32 changesource, bool protect_or_not) | ||
85 | { | ||
86 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
87 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
88 | enum rf_pwrstate rtstate; | ||
89 | bool b_actionallowed = false; | ||
90 | u16 rfwait_cnt = 0; | ||
91 | unsigned long flag; | ||
92 | |||
93 | /*protect_or_not = true; */ | ||
94 | |||
95 | if (protect_or_not) | ||
96 | goto no_protect; | ||
97 | |||
98 | /* | ||
99 | *Only one thread can change | ||
100 | *the RF state at one time, and others | ||
101 | *should wait to be executed. | ||
102 | */ | ||
103 | while (true) { | ||
104 | spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); | ||
105 | if (ppsc->rfchange_inprogress) { | ||
106 | spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, | ||
107 | flag); | ||
108 | |||
109 | RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, | ||
110 | ("RF Change in progress!" | ||
111 | "Wait to set..state_toset(%d).\n", | ||
112 | state_toset)); | ||
113 | |||
114 | /* Set RF after the previous action is done. */ | ||
115 | while (ppsc->rfchange_inprogress) { | ||
116 | rfwait_cnt++; | ||
117 | mdelay(1); | ||
118 | |||
119 | /* | ||
120 | *Wait too long, return false to avoid | ||
121 | *to be stuck here. | ||
122 | */ | ||
123 | if (rfwait_cnt > 100) | ||
124 | return false; | ||
125 | } | ||
126 | } else { | ||
127 | ppsc->rfchange_inprogress = true; | ||
128 | spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, | ||
129 | flag); | ||
130 | break; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | no_protect: | ||
135 | rtstate = ppsc->rfpwr_state; | ||
136 | |||
137 | switch (state_toset) { | ||
138 | case ERFON: | ||
139 | ppsc->rfoff_reason &= (~changesource); | ||
140 | |||
141 | if ((changesource == RF_CHANGE_BY_HW) && | ||
142 | (ppsc->b_hwradiooff == true)) { | ||
143 | ppsc->b_hwradiooff = false; | ||
144 | } | ||
145 | |||
146 | if (!ppsc->rfoff_reason) { | ||
147 | ppsc->rfoff_reason = 0; | ||
148 | b_actionallowed = true; | ||
149 | } | ||
150 | |||
151 | break; | ||
152 | |||
153 | case ERFOFF: | ||
154 | |||
155 | if ((changesource == RF_CHANGE_BY_HW) | ||
156 | && (ppsc->b_hwradiooff == false)) { | ||
157 | ppsc->b_hwradiooff = true; | ||
158 | } | ||
159 | |||
160 | ppsc->rfoff_reason |= changesource; | ||
161 | b_actionallowed = true; | ||
162 | break; | ||
163 | |||
164 | case ERFSLEEP: | ||
165 | ppsc->rfoff_reason |= changesource; | ||
166 | b_actionallowed = true; | ||
167 | break; | ||
168 | |||
169 | default: | ||
170 | RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, | ||
171 | ("switch case not process\n")); | ||
172 | break; | ||
173 | } | ||
174 | |||
175 | if (b_actionallowed) | ||
176 | rtlpriv->cfg->ops->set_rf_power_state(hw, state_toset); | ||
177 | |||
178 | if (!protect_or_not) { | ||
179 | spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); | ||
180 | ppsc->rfchange_inprogress = false; | ||
181 | spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); | ||
182 | } | ||
183 | |||
184 | return b_actionallowed; | ||
185 | } | ||
186 | EXPORT_SYMBOL(rtl_ps_set_rf_state); | ||
187 | |||
188 | static void _rtl_ps_inactive_ps(struct ieee80211_hw *hw) | ||
189 | { | ||
190 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
191 | struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); | ||
192 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
193 | |||
194 | ppsc->b_swrf_processing = true; | ||
195 | |||
196 | if (ppsc->inactive_pwrstate == ERFON && rtlhal->interface == INTF_PCI) { | ||
197 | if ((ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) && | ||
198 | RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM) && | ||
199 | rtlhal->interface == INTF_PCI) { | ||
200 | rtlpriv->intf_ops->disable_aspm(hw); | ||
201 | RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM); | ||
202 | } | ||
203 | } | ||
204 | |||
205 | rtl_ps_set_rf_state(hw, ppsc->inactive_pwrstate, | ||
206 | RF_CHANGE_BY_IPS, false); | ||
207 | |||
208 | if (ppsc->inactive_pwrstate == ERFOFF && | ||
209 | rtlhal->interface == INTF_PCI) { | ||
210 | if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) { | ||
211 | rtlpriv->intf_ops->enable_aspm(hw); | ||
212 | RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM); | ||
213 | } | ||
214 | } | ||
215 | |||
216 | ppsc->b_swrf_processing = false; | ||
217 | } | ||
218 | |||
219 | void rtl_ips_nic_off_wq_callback(void *data) | ||
220 | { | ||
221 | struct rtl_works *rtlworks = | ||
222 | container_of_dwork_rtl(data, struct rtl_works, ips_nic_off_wq); | ||
223 | struct ieee80211_hw *hw = rtlworks->hw; | ||
224 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
225 | struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); | ||
226 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
227 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
228 | enum rf_pwrstate rtstate; | ||
229 | |||
230 | if (mac->opmode != NL80211_IFTYPE_STATION) { | ||
231 | RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, | ||
232 | ("not station return\n")); | ||
233 | return; | ||
234 | } | ||
235 | |||
236 | if (is_hal_stop(rtlhal)) | ||
237 | return; | ||
238 | |||
239 | if (rtlpriv->sec.being_setkey) | ||
240 | return; | ||
241 | |||
242 | if (ppsc->b_inactiveps) { | ||
243 | rtstate = ppsc->rfpwr_state; | ||
244 | |||
245 | /* | ||
246 | *Do not enter IPS in the following conditions: | ||
247 | *(1) RF is already OFF or Sleep | ||
248 | *(2) b_swrf_processing (indicates the IPS is still under going) | ||
249 | *(3) Connectted (only disconnected can trigger IPS) | ||
250 | *(4) IBSS (send Beacon) | ||
251 | *(5) AP mode (send Beacon) | ||
252 | *(6) monitor mode (rcv packet) | ||
253 | */ | ||
254 | |||
255 | if (rtstate == ERFON && | ||
256 | !ppsc->b_swrf_processing && | ||
257 | (mac->link_state == MAC80211_NOLINK) && | ||
258 | !mac->act_scanning) { | ||
259 | RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, | ||
260 | ("IPSEnter(): Turn off RF.\n")); | ||
261 | |||
262 | ppsc->inactive_pwrstate = ERFOFF; | ||
263 | ppsc->b_in_powersavemode = true; | ||
264 | |||
265 | /*rtl_pci_reset_trx_ring(hw); */ | ||
266 | _rtl_ps_inactive_ps(hw); | ||
267 | } | ||
268 | } | ||
269 | } | ||
270 | |||
271 | void rtl_ips_nic_off(struct ieee80211_hw *hw) | ||
272 | { | ||
273 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
274 | |||
275 | /* | ||
276 | *because when link with ap, mac80211 will ask us | ||
277 | *to disable nic quickly after scan before linking, | ||
278 | *this will cause link failed, so we delay 100ms here | ||
279 | */ | ||
280 | queue_delayed_work(rtlpriv->works.rtl_wq, | ||
281 | &rtlpriv->works.ips_nic_off_wq, MSECS(100)); | ||
282 | } | ||
283 | |||
284 | void rtl_ips_nic_on(struct ieee80211_hw *hw) | ||
285 | { | ||
286 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
287 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
288 | enum rf_pwrstate rtstate; | ||
289 | |||
290 | down(&rtlpriv->locks.ips_sem); | ||
291 | |||
292 | if (ppsc->b_inactiveps) { | ||
293 | rtstate = ppsc->rfpwr_state; | ||
294 | |||
295 | if (rtstate != ERFON && | ||
296 | !ppsc->b_swrf_processing && | ||
297 | ppsc->rfoff_reason <= RF_CHANGE_BY_IPS) { | ||
298 | |||
299 | ppsc->inactive_pwrstate = ERFON; | ||
300 | ppsc->b_in_powersavemode = false; | ||
301 | |||
302 | _rtl_ps_inactive_ps(hw); | ||
303 | } | ||
304 | } | ||
305 | |||
306 | up(&rtlpriv->locks.ips_sem); | ||
307 | } | ||
308 | |||
309 | /*for FW LPS*/ | ||
310 | |||
311 | /* | ||
312 | *Determine if we can set Fw into PS mode | ||
313 | *in current condition.Return TRUE if it | ||
314 | *can enter PS mode. | ||
315 | */ | ||
316 | static bool rtl_get_fwlps_doze(struct ieee80211_hw *hw) | ||
317 | { | ||
318 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
319 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
320 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
321 | u32 ps_timediff; | ||
322 | |||
323 | ps_timediff = jiffies_to_msecs(jiffies - | ||
324 | ppsc->last_delaylps_stamp_jiffies); | ||
325 | |||
326 | if (ps_timediff < 2000) { | ||
327 | RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, | ||
328 | ("Delay enter Fw LPS for DHCP, ARP," | ||
329 | " or EAPOL exchanging state.\n")); | ||
330 | return false; | ||
331 | } | ||
332 | |||
333 | if (mac->link_state != MAC80211_LINKED) | ||
334 | return false; | ||
335 | |||
336 | if (mac->opmode == NL80211_IFTYPE_ADHOC) | ||
337 | return false; | ||
338 | |||
339 | return true; | ||
340 | } | ||
341 | |||
342 | /* Change current and default preamble mode.*/ | ||
343 | static void rtl_lps_set_psmode(struct ieee80211_hw *hw, u8 rt_psmode) | ||
344 | { | ||
345 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
346 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
347 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
348 | u8 rpwm_val, fw_pwrmode; | ||
349 | |||
350 | if (mac->opmode == NL80211_IFTYPE_ADHOC) | ||
351 | return; | ||
352 | |||
353 | if (mac->link_state != MAC80211_LINKED) | ||
354 | return; | ||
355 | |||
356 | if (ppsc->dot11_psmode == rt_psmode) | ||
357 | return; | ||
358 | |||
359 | /* Update power save mode configured. */ | ||
360 | ppsc->dot11_psmode = rt_psmode; | ||
361 | |||
362 | /* | ||
363 | *<FW control LPS> | ||
364 | *1. Enter PS mode | ||
365 | * Set RPWM to Fw to turn RF off and send H2C fw_pwrmode | ||
366 | * cmd to set Fw into PS mode. | ||
367 | *2. Leave PS mode | ||
368 | * Send H2C fw_pwrmode cmd to Fw to set Fw into Active | ||
369 | * mode and set RPWM to turn RF on. | ||
370 | */ | ||
371 | |||
372 | if ((ppsc->b_fwctrl_lps) && (ppsc->b_leisure_ps) && | ||
373 | ppsc->report_linked) { | ||
374 | bool b_fw_current_inps; | ||
375 | if (ppsc->dot11_psmode == EACTIVE) { | ||
376 | RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, | ||
377 | ("FW LPS leave ps_mode:%x\n", | ||
378 | FW_PS_ACTIVE_MODE)); | ||
379 | |||
380 | rpwm_val = 0x0C; /* RF on */ | ||
381 | fw_pwrmode = FW_PS_ACTIVE_MODE; | ||
382 | rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, | ||
383 | (u8 *) (&rpwm_val)); | ||
384 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
385 | HW_VAR_H2C_FW_PWRMODE, | ||
386 | (u8 *) (&fw_pwrmode)); | ||
387 | b_fw_current_inps = false; | ||
388 | |||
389 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
390 | HW_VAR_FW_PSMODE_STATUS, | ||
391 | (u8 *) (&b_fw_current_inps)); | ||
392 | |||
393 | } else { | ||
394 | if (rtl_get_fwlps_doze(hw)) { | ||
395 | RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, | ||
396 | ("FW LPS enter ps_mode:%x\n", | ||
397 | ppsc->fwctrl_psmode)); | ||
398 | |||
399 | rpwm_val = 0x02; /* RF off */ | ||
400 | b_fw_current_inps = true; | ||
401 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
402 | HW_VAR_FW_PSMODE_STATUS, | ||
403 | (u8 *) (&b_fw_current_inps)); | ||
404 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
405 | HW_VAR_H2C_FW_PWRMODE, | ||
406 | (u8 *) (&ppsc->fwctrl_psmode)); | ||
407 | |||
408 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
409 | HW_VAR_SET_RPWM, | ||
410 | (u8 *) (&rpwm_val)); | ||
411 | } else { | ||
412 | /* Reset the power save related parameters. */ | ||
413 | ppsc->dot11_psmode = EACTIVE; | ||
414 | } | ||
415 | } | ||
416 | } | ||
417 | } | ||
418 | |||
419 | /*Enter the leisure power save mode.*/ | ||
420 | void rtl_lps_enter(struct ieee80211_hw *hw) | ||
421 | { | ||
422 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
423 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
424 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
425 | unsigned long flag; | ||
426 | |||
427 | if (!(ppsc->b_fwctrl_lps && ppsc->b_leisure_ps)) | ||
428 | return; | ||
429 | |||
430 | if (rtlpriv->sec.being_setkey) | ||
431 | return; | ||
432 | |||
433 | if (rtlpriv->link_info.b_busytraffic) | ||
434 | return; | ||
435 | |||
436 | /*sleep after linked 10s, to let DHCP and 4-way handshake ok enough!! */ | ||
437 | if (mac->cnt_after_linked < 5) | ||
438 | return; | ||
439 | |||
440 | if (mac->opmode == NL80211_IFTYPE_ADHOC) | ||
441 | return; | ||
442 | |||
443 | if (mac->link_state != MAC80211_LINKED) | ||
444 | return; | ||
445 | |||
446 | spin_lock_irqsave(&rtlpriv->locks.lps_lock, flag); | ||
447 | |||
448 | if (ppsc->b_leisure_ps) { | ||
449 | /* Idle for a while if we connect to AP a while ago. */ | ||
450 | if (mac->cnt_after_linked >= 2) { | ||
451 | if (ppsc->dot11_psmode == EACTIVE) { | ||
452 | RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, | ||
453 | ("Enter 802.11 power save mode...\n")); | ||
454 | |||
455 | rtl_lps_set_psmode(hw, EAUTOPS); | ||
456 | } | ||
457 | } | ||
458 | } | ||
459 | spin_unlock_irqrestore(&rtlpriv->locks.lps_lock, flag); | ||
460 | } | ||
461 | |||
462 | /*Leave the leisure power save mode.*/ | ||
463 | void rtl_lps_leave(struct ieee80211_hw *hw) | ||
464 | { | ||
465 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
466 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
467 | struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); | ||
468 | unsigned long flag; | ||
469 | |||
470 | spin_lock_irqsave(&rtlpriv->locks.lps_lock, flag); | ||
471 | |||
472 | if (ppsc->b_fwctrl_lps && ppsc->b_leisure_ps) { | ||
473 | if (ppsc->dot11_psmode != EACTIVE) { | ||
474 | |||
475 | /*FIX ME */ | ||
476 | rtlpriv->cfg->ops->enable_interrupt(hw); | ||
477 | |||
478 | if (ppsc->reg_rfps_level & RT_RF_LPS_LEVEL_ASPM && | ||
479 | RT_IN_PS_LEVEL(ppsc, RT_RF_LPS_LEVEL_ASPM) && | ||
480 | rtlhal->interface == INTF_PCI) { | ||
481 | rtlpriv->intf_ops->disable_aspm(hw); | ||
482 | RT_CLEAR_PS_LEVEL(ppsc, RT_RF_LPS_LEVEL_ASPM); | ||
483 | } | ||
484 | |||
485 | RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, | ||
486 | ("Busy Traffic,Leave 802.11 power save..\n")); | ||
487 | |||
488 | rtl_lps_set_psmode(hw, EACTIVE); | ||
489 | } | ||
490 | } | ||
491 | spin_unlock_irqrestore(&rtlpriv->locks.lps_lock, flag); | ||
492 | } | ||