diff options
Diffstat (limited to 'drivers/net/wireless/rtlwifi/ps.c')
-rw-r--r-- | drivers/net/wireless/rtlwifi/ps.c | 493 |
1 files changed, 493 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..d2326c13449e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/ps.c | |||
@@ -0,0 +1,493 @@ | |||
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 | unsigned long flags; | ||
290 | |||
291 | spin_lock_irqsave(&rtlpriv->locks.ips_lock, flags); | ||
292 | |||
293 | if (ppsc->b_inactiveps) { | ||
294 | rtstate = ppsc->rfpwr_state; | ||
295 | |||
296 | if (rtstate != ERFON && | ||
297 | !ppsc->b_swrf_processing && | ||
298 | ppsc->rfoff_reason <= RF_CHANGE_BY_IPS) { | ||
299 | |||
300 | ppsc->inactive_pwrstate = ERFON; | ||
301 | ppsc->b_in_powersavemode = false; | ||
302 | |||
303 | _rtl_ps_inactive_ps(hw); | ||
304 | } | ||
305 | } | ||
306 | |||
307 | spin_unlock_irqrestore(&rtlpriv->locks.ips_lock, flags); | ||
308 | } | ||
309 | |||
310 | /*for FW LPS*/ | ||
311 | |||
312 | /* | ||
313 | *Determine if we can set Fw into PS mode | ||
314 | *in current condition.Return TRUE if it | ||
315 | *can enter PS mode. | ||
316 | */ | ||
317 | static bool rtl_get_fwlps_doze(struct ieee80211_hw *hw) | ||
318 | { | ||
319 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
320 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
321 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
322 | u32 ps_timediff; | ||
323 | |||
324 | ps_timediff = jiffies_to_msecs(jiffies - | ||
325 | ppsc->last_delaylps_stamp_jiffies); | ||
326 | |||
327 | if (ps_timediff < 2000) { | ||
328 | RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, | ||
329 | ("Delay enter Fw LPS for DHCP, ARP," | ||
330 | " or EAPOL exchanging state.\n")); | ||
331 | return false; | ||
332 | } | ||
333 | |||
334 | if (mac->link_state != MAC80211_LINKED) | ||
335 | return false; | ||
336 | |||
337 | if (mac->opmode == NL80211_IFTYPE_ADHOC) | ||
338 | return false; | ||
339 | |||
340 | return true; | ||
341 | } | ||
342 | |||
343 | /* Change current and default preamble mode.*/ | ||
344 | static void rtl_lps_set_psmode(struct ieee80211_hw *hw, u8 rt_psmode) | ||
345 | { | ||
346 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
347 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
348 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
349 | u8 rpwm_val, fw_pwrmode; | ||
350 | |||
351 | if (mac->opmode == NL80211_IFTYPE_ADHOC) | ||
352 | return; | ||
353 | |||
354 | if (mac->link_state != MAC80211_LINKED) | ||
355 | return; | ||
356 | |||
357 | if (ppsc->dot11_psmode == rt_psmode) | ||
358 | return; | ||
359 | |||
360 | /* Update power save mode configured. */ | ||
361 | ppsc->dot11_psmode = rt_psmode; | ||
362 | |||
363 | /* | ||
364 | *<FW control LPS> | ||
365 | *1. Enter PS mode | ||
366 | * Set RPWM to Fw to turn RF off and send H2C fw_pwrmode | ||
367 | * cmd to set Fw into PS mode. | ||
368 | *2. Leave PS mode | ||
369 | * Send H2C fw_pwrmode cmd to Fw to set Fw into Active | ||
370 | * mode and set RPWM to turn RF on. | ||
371 | */ | ||
372 | |||
373 | if ((ppsc->b_fwctrl_lps) && (ppsc->b_leisure_ps) && | ||
374 | ppsc->report_linked) { | ||
375 | bool b_fw_current_inps; | ||
376 | if (ppsc->dot11_psmode == EACTIVE) { | ||
377 | RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, | ||
378 | ("FW LPS leave ps_mode:%x\n", | ||
379 | FW_PS_ACTIVE_MODE)); | ||
380 | |||
381 | rpwm_val = 0x0C; /* RF on */ | ||
382 | fw_pwrmode = FW_PS_ACTIVE_MODE; | ||
383 | rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, | ||
384 | (u8 *) (&rpwm_val)); | ||
385 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
386 | HW_VAR_H2C_FW_PWRMODE, | ||
387 | (u8 *) (&fw_pwrmode)); | ||
388 | b_fw_current_inps = false; | ||
389 | |||
390 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
391 | HW_VAR_FW_PSMODE_STATUS, | ||
392 | (u8 *) (&b_fw_current_inps)); | ||
393 | |||
394 | } else { | ||
395 | if (rtl_get_fwlps_doze(hw)) { | ||
396 | RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, | ||
397 | ("FW LPS enter ps_mode:%x\n", | ||
398 | ppsc->fwctrl_psmode)); | ||
399 | |||
400 | rpwm_val = 0x02; /* RF off */ | ||
401 | b_fw_current_inps = true; | ||
402 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
403 | HW_VAR_FW_PSMODE_STATUS, | ||
404 | (u8 *) (&b_fw_current_inps)); | ||
405 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
406 | HW_VAR_H2C_FW_PWRMODE, | ||
407 | (u8 *) (&ppsc->fwctrl_psmode)); | ||
408 | |||
409 | rtlpriv->cfg->ops->set_hw_reg(hw, | ||
410 | HW_VAR_SET_RPWM, | ||
411 | (u8 *) (&rpwm_val)); | ||
412 | } else { | ||
413 | /* Reset the power save related parameters. */ | ||
414 | ppsc->dot11_psmode = EACTIVE; | ||
415 | } | ||
416 | } | ||
417 | } | ||
418 | } | ||
419 | |||
420 | /*Enter the leisure power save mode.*/ | ||
421 | void rtl_lps_enter(struct ieee80211_hw *hw) | ||
422 | { | ||
423 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
424 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
425 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
426 | unsigned long flag; | ||
427 | |||
428 | if (!(ppsc->b_fwctrl_lps && ppsc->b_leisure_ps)) | ||
429 | return; | ||
430 | |||
431 | if (rtlpriv->sec.being_setkey) | ||
432 | return; | ||
433 | |||
434 | if (rtlpriv->link_info.b_busytraffic) | ||
435 | return; | ||
436 | |||
437 | /*sleep after linked 10s, to let DHCP and 4-way handshake ok enough!! */ | ||
438 | if (mac->cnt_after_linked < 5) | ||
439 | return; | ||
440 | |||
441 | if (mac->opmode == NL80211_IFTYPE_ADHOC) | ||
442 | return; | ||
443 | |||
444 | if (mac->link_state != MAC80211_LINKED) | ||
445 | return; | ||
446 | |||
447 | spin_lock_irqsave(&rtlpriv->locks.lps_lock, flag); | ||
448 | |||
449 | if (ppsc->b_leisure_ps) { | ||
450 | /* Idle for a while if we connect to AP a while ago. */ | ||
451 | if (mac->cnt_after_linked >= 2) { | ||
452 | if (ppsc->dot11_psmode == EACTIVE) { | ||
453 | RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, | ||
454 | ("Enter 802.11 power save mode...\n")); | ||
455 | |||
456 | rtl_lps_set_psmode(hw, EAUTOPS); | ||
457 | } | ||
458 | } | ||
459 | } | ||
460 | spin_unlock_irqrestore(&rtlpriv->locks.lps_lock, flag); | ||
461 | } | ||
462 | |||
463 | /*Leave the leisure power save mode.*/ | ||
464 | void rtl_lps_leave(struct ieee80211_hw *hw) | ||
465 | { | ||
466 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
467 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
468 | struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); | ||
469 | unsigned long flag; | ||
470 | |||
471 | spin_lock_irqsave(&rtlpriv->locks.lps_lock, flag); | ||
472 | |||
473 | if (ppsc->b_fwctrl_lps && ppsc->b_leisure_ps) { | ||
474 | if (ppsc->dot11_psmode != EACTIVE) { | ||
475 | |||
476 | /*FIX ME */ | ||
477 | rtlpriv->cfg->ops->enable_interrupt(hw); | ||
478 | |||
479 | if (ppsc->reg_rfps_level & RT_RF_LPS_LEVEL_ASPM && | ||
480 | RT_IN_PS_LEVEL(ppsc, RT_RF_LPS_LEVEL_ASPM) && | ||
481 | rtlhal->interface == INTF_PCI) { | ||
482 | rtlpriv->intf_ops->disable_aspm(hw); | ||
483 | RT_CLEAR_PS_LEVEL(ppsc, RT_RF_LPS_LEVEL_ASPM); | ||
484 | } | ||
485 | |||
486 | RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, | ||
487 | ("Busy Traffic,Leave 802.11 power save..\n")); | ||
488 | |||
489 | rtl_lps_set_psmode(hw, EACTIVE); | ||
490 | } | ||
491 | } | ||
492 | spin_unlock_irqrestore(&rtlpriv->locks.lps_lock, flag); | ||
493 | } | ||