| /****************************************************************************** |
| * |
| * Copyright(c) 2009-2010 Realtek Corporation. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA |
| * |
| * The full GNU General Public License is included in this distribution in the |
| * file called LICENSE. |
| * |
| * Contact Information: |
| * wlanfae <wlanfae@realtek.com> |
| * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, |
| * Hsinchu 300, Taiwan. |
| * |
| * Larry Finger <Larry.Finger@lwfinger.net> |
| * |
| *****************************************************************************/ |
| |
| #include "wifi.h" |
| #include "base.h" |
| #include "ps.h" |
| |
| bool rtl_ps_enable_nic(struct ieee80211_hw *hw) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); |
| bool init_status = true; |
| |
| /*<1> reset trx ring */ |
| if (rtlhal->interface == INTF_PCI) |
| rtlpriv->intf_ops->reset_trx_ring(hw); |
| |
| if (is_hal_stop(rtlhal)) |
| RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, |
| ("Driver is already down!\n")); |
| |
| /*<2> Enable Adapter */ |
| rtlpriv->cfg->ops->hw_init(hw); |
| RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); |
| /*init_status = false; */ |
| |
| /*<3> Enable Interrupt */ |
| rtlpriv->cfg->ops->enable_interrupt(hw); |
| |
| /*<enable timer> */ |
| rtl_watch_dog_timer_callback((unsigned long)hw); |
| |
| return init_status; |
| } |
| EXPORT_SYMBOL(rtl_ps_enable_nic); |
| |
| bool rtl_ps_disable_nic(struct ieee80211_hw *hw) |
| { |
| bool status = true; |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| |
| /*<1> Stop all timer */ |
| rtl_deinit_deferred_work(hw); |
| |
| /*<2> Disable Interrupt */ |
| rtlpriv->cfg->ops->disable_interrupt(hw); |
| |
| /*<3> Disable Adapter */ |
| rtlpriv->cfg->ops->hw_disable(hw); |
| |
| return status; |
| } |
| EXPORT_SYMBOL(rtl_ps_disable_nic); |
| |
| bool rtl_ps_set_rf_state(struct ieee80211_hw *hw, |
| enum rf_pwrstate state_toset, |
| u32 changesource, bool protect_or_not) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| enum rf_pwrstate rtstate; |
| bool actionallowed = false; |
| u16 rfwait_cnt = 0; |
| unsigned long flag; |
| |
| /*protect_or_not = true; */ |
| |
| if (protect_or_not) |
| goto no_protect; |
| |
| /* |
| *Only one thread can change |
| *the RF state at one time, and others |
| *should wait to be executed. |
| */ |
| while (true) { |
| spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); |
| if (ppsc->rfchange_inprogress) { |
| spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, |
| flag); |
| |
| RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, |
| ("RF Change in progress!" |
| "Wait to set..state_toset(%d).\n", |
| state_toset)); |
| |
| /* Set RF after the previous action is done. */ |
| while (ppsc->rfchange_inprogress) { |
| rfwait_cnt++; |
| mdelay(1); |
| |
| /* |
| *Wait too long, return false to avoid |
| *to be stuck here. |
| */ |
| if (rfwait_cnt > 100) |
| return false; |
| } |
| } else { |
| ppsc->rfchange_inprogress = true; |
| spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, |
| flag); |
| break; |
| } |
| } |
| |
| no_protect: |
| rtstate = ppsc->rfpwr_state; |
| |
| switch (state_toset) { |
| case ERFON: |
| ppsc->rfoff_reason &= (~changesource); |
| |
| if ((changesource == RF_CHANGE_BY_HW) && |
| (ppsc->hwradiooff == true)) { |
| ppsc->hwradiooff = false; |
| } |
| |
| if (!ppsc->rfoff_reason) { |
| ppsc->rfoff_reason = 0; |
| actionallowed = true; |
| } |
| |
| break; |
| |
| case ERFOFF: |
| |
| if ((changesource == RF_CHANGE_BY_HW) |
| && (ppsc->hwradiooff == false)) { |
| ppsc->hwradiooff = true; |
| } |
| |
| ppsc->rfoff_reason |= changesource; |
| actionallowed = true; |
| break; |
| |
| case ERFSLEEP: |
| ppsc->rfoff_reason |= changesource; |
| actionallowed = true; |
| break; |
| |
| default: |
| RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, |
| ("switch case not process\n")); |
| break; |
| } |
| |
| if (actionallowed) |
| rtlpriv->cfg->ops->set_rf_power_state(hw, state_toset); |
| |
| if (!protect_or_not) { |
| spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); |
| ppsc->rfchange_inprogress = false; |
| spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); |
| } |
| |
| return actionallowed; |
| } |
| EXPORT_SYMBOL(rtl_ps_set_rf_state); |
| |
| static void _rtl_ps_inactive_ps(struct ieee80211_hw *hw) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| |
| ppsc->swrf_processing = true; |
| |
| if (ppsc->inactive_pwrstate == ERFON && rtlhal->interface == INTF_PCI) { |
| if ((ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) && |
| RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM) && |
| rtlhal->interface == INTF_PCI) { |
| rtlpriv->intf_ops->disable_aspm(hw); |
| RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM); |
| } |
| } |
| |
| rtl_ps_set_rf_state(hw, ppsc->inactive_pwrstate, |
| RF_CHANGE_BY_IPS, false); |
| |
| if (ppsc->inactive_pwrstate == ERFOFF && |
| rtlhal->interface == INTF_PCI) { |
| if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) { |
| rtlpriv->intf_ops->enable_aspm(hw); |
| RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM); |
| } |
| } |
| |
| ppsc->swrf_processing = false; |
| } |
| |
| void rtl_ips_nic_off_wq_callback(void *data) |
| { |
| struct rtl_works *rtlworks = |
| container_of_dwork_rtl(data, struct rtl_works, ips_nic_off_wq); |
| struct ieee80211_hw *hw = rtlworks->hw; |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); |
| struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| enum rf_pwrstate rtstate; |
| |
| if (mac->opmode != NL80211_IFTYPE_STATION) { |
| RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, |
| ("not station return\n")); |
| return; |
| } |
| |
| if (is_hal_stop(rtlhal)) |
| return; |
| |
| if (rtlpriv->sec.being_setkey) |
| return; |
| |
| if (ppsc->inactiveps) { |
| rtstate = ppsc->rfpwr_state; |
| |
| /* |
| *Do not enter IPS in the following conditions: |
| *(1) RF is already OFF or Sleep |
| *(2) swrf_processing (indicates the IPS is still under going) |
| *(3) Connectted (only disconnected can trigger IPS) |
| *(4) IBSS (send Beacon) |
| *(5) AP mode (send Beacon) |
| *(6) monitor mode (rcv packet) |
| */ |
| |
| if (rtstate == ERFON && |
| !ppsc->swrf_processing && |
| (mac->link_state == MAC80211_NOLINK) && |
| !mac->act_scanning) { |
| RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, |
| ("IPSEnter(): Turn off RF.\n")); |
| |
| ppsc->inactive_pwrstate = ERFOFF; |
| ppsc->in_powersavemode = true; |
| |
| /*rtl_pci_reset_trx_ring(hw); */ |
| _rtl_ps_inactive_ps(hw); |
| } |
| } |
| } |
| |
| void rtl_ips_nic_off(struct ieee80211_hw *hw) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| |
| /* |
| *because when link with ap, mac80211 will ask us |
| *to disable nic quickly after scan before linking, |
| *this will cause link failed, so we delay 100ms here |
| */ |
| queue_delayed_work(rtlpriv->works.rtl_wq, |
| &rtlpriv->works.ips_nic_off_wq, MSECS(100)); |
| } |
| |
| void rtl_ips_nic_on(struct ieee80211_hw *hw) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| enum rf_pwrstate rtstate; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&rtlpriv->locks.ips_lock, flags); |
| |
| if (ppsc->inactiveps) { |
| rtstate = ppsc->rfpwr_state; |
| |
| if (rtstate != ERFON && |
| !ppsc->swrf_processing && |
| ppsc->rfoff_reason <= RF_CHANGE_BY_IPS) { |
| |
| ppsc->inactive_pwrstate = ERFON; |
| ppsc->in_powersavemode = false; |
| |
| _rtl_ps_inactive_ps(hw); |
| } |
| } |
| |
| spin_unlock_irqrestore(&rtlpriv->locks.ips_lock, flags); |
| } |
| |
| /*for FW LPS*/ |
| |
| /* |
| *Determine if we can set Fw into PS mode |
| *in current condition.Return TRUE if it |
| *can enter PS mode. |
| */ |
| static bool rtl_get_fwlps_doze(struct ieee80211_hw *hw) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| u32 ps_timediff; |
| |
| ps_timediff = jiffies_to_msecs(jiffies - |
| ppsc->last_delaylps_stamp_jiffies); |
| |
| if (ps_timediff < 2000) { |
| RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, |
| ("Delay enter Fw LPS for DHCP, ARP," |
| " or EAPOL exchanging state.\n")); |
| return false; |
| } |
| |
| if (mac->link_state != MAC80211_LINKED) |
| return false; |
| |
| if (mac->opmode == NL80211_IFTYPE_ADHOC) |
| return false; |
| |
| return true; |
| } |
| |
| /* Change current and default preamble mode.*/ |
| static void rtl_lps_set_psmode(struct ieee80211_hw *hw, u8 rt_psmode) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| u8 rpwm_val, fw_pwrmode; |
| |
| if (mac->opmode == NL80211_IFTYPE_ADHOC) |
| return; |
| |
| if (mac->link_state != MAC80211_LINKED) |
| return; |
| |
| if (ppsc->dot11_psmode == rt_psmode) |
| return; |
| |
| /* Update power save mode configured. */ |
| ppsc->dot11_psmode = rt_psmode; |
| |
| /* |
| *<FW control LPS> |
| *1. Enter PS mode |
| * Set RPWM to Fw to turn RF off and send H2C fw_pwrmode |
| * cmd to set Fw into PS mode. |
| *2. Leave PS mode |
| * Send H2C fw_pwrmode cmd to Fw to set Fw into Active |
| * mode and set RPWM to turn RF on. |
| */ |
| |
| if ((ppsc->fwctrl_lps) && (ppsc->leisure_ps) && |
| ppsc->report_linked) { |
| bool fw_current_inps; |
| if (ppsc->dot11_psmode == EACTIVE) { |
| RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, |
| ("FW LPS leave ps_mode:%x\n", |
| FW_PS_ACTIVE_MODE)); |
| |
| rpwm_val = 0x0C; /* RF on */ |
| fw_pwrmode = FW_PS_ACTIVE_MODE; |
| rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, |
| (u8 *) (&rpwm_val)); |
| rtlpriv->cfg->ops->set_hw_reg(hw, |
| HW_VAR_H2C_FW_PWRMODE, |
| (u8 *) (&fw_pwrmode)); |
| fw_current_inps = false; |
| |
| rtlpriv->cfg->ops->set_hw_reg(hw, |
| HW_VAR_FW_PSMODE_STATUS, |
| (u8 *) (&fw_current_inps)); |
| |
| } else { |
| if (rtl_get_fwlps_doze(hw)) { |
| RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, |
| ("FW LPS enter ps_mode:%x\n", |
| ppsc->fwctrl_psmode)); |
| |
| rpwm_val = 0x02; /* RF off */ |
| fw_current_inps = true; |
| rtlpriv->cfg->ops->set_hw_reg(hw, |
| HW_VAR_FW_PSMODE_STATUS, |
| (u8 *) (&fw_current_inps)); |
| rtlpriv->cfg->ops->set_hw_reg(hw, |
| HW_VAR_H2C_FW_PWRMODE, |
| (u8 *) (&ppsc->fwctrl_psmode)); |
| |
| rtlpriv->cfg->ops->set_hw_reg(hw, |
| HW_VAR_SET_RPWM, |
| (u8 *) (&rpwm_val)); |
| } else { |
| /* Reset the power save related parameters. */ |
| ppsc->dot11_psmode = EACTIVE; |
| } |
| } |
| } |
| } |
| |
| /*Enter the leisure power save mode.*/ |
| void rtl_lps_enter(struct ieee80211_hw *hw) |
| { |
| struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| unsigned long flag; |
| |
| if (!(ppsc->fwctrl_lps && ppsc->leisure_ps)) |
| return; |
| |
| if (rtlpriv->sec.being_setkey) |
| return; |
| |
| if (rtlpriv->link_info.busytraffic) |
| return; |
| |
| /*sleep after linked 10s, to let DHCP and 4-way handshake ok enough!! */ |
| if (mac->cnt_after_linked < 5) |
| return; |
| |
| if (mac->opmode == NL80211_IFTYPE_ADHOC) |
| return; |
| |
| if (mac->link_state != MAC80211_LINKED) |
| return; |
| |
| spin_lock_irqsave(&rtlpriv->locks.lps_lock, flag); |
| |
| if (ppsc->leisure_ps) { |
| /* Idle for a while if we connect to AP a while ago. */ |
| if (mac->cnt_after_linked >= 2) { |
| if (ppsc->dot11_psmode == EACTIVE) { |
| RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, |
| ("Enter 802.11 power save mode...\n")); |
| |
| rtl_lps_set_psmode(hw, EAUTOPS); |
| } |
| } |
| } |
| spin_unlock_irqrestore(&rtlpriv->locks.lps_lock, flag); |
| } |
| |
| /*Leave the leisure power save mode.*/ |
| void rtl_lps_leave(struct ieee80211_hw *hw) |
| { |
| struct rtl_priv *rtlpriv = rtl_priv(hw); |
| struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); |
| struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); |
| unsigned long flag; |
| |
| spin_lock_irqsave(&rtlpriv->locks.lps_lock, flag); |
| |
| if (ppsc->fwctrl_lps && ppsc->leisure_ps) { |
| if (ppsc->dot11_psmode != EACTIVE) { |
| |
| /*FIX ME */ |
| rtlpriv->cfg->ops->enable_interrupt(hw); |
| |
| if (ppsc->reg_rfps_level & RT_RF_LPS_LEVEL_ASPM && |
| RT_IN_PS_LEVEL(ppsc, RT_RF_LPS_LEVEL_ASPM) && |
| rtlhal->interface == INTF_PCI) { |
| rtlpriv->intf_ops->disable_aspm(hw); |
| RT_CLEAR_PS_LEVEL(ppsc, RT_RF_LPS_LEVEL_ASPM); |
| } |
| |
| RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, |
| ("Busy Traffic,Leave 802.11 power save..\n")); |
| |
| rtl_lps_set_psmode(hw, EACTIVE); |
| } |
| } |
| spin_unlock_irqrestore(&rtlpriv->locks.lps_lock, flag); |
| } |