// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2009-2012 Realtek Corporation.*/
#include "wifi.h"
#include "base.h"
#include "ps.h"
#include <linux/export.h>
#include "btcoexist/rtl_btc.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));
struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw));
/*<1> reset trx ring */
if (rtlhal->interface == INTF_PCI)
rtlpriv->intf_ops->reset_trx_ring(hw);
if (is_hal_stop(rtlhal))
rtl_dbg(rtlpriv, COMP_ERR, DBG_WARNING,
"Driver is already down!\n");
/*<2> Enable Adapter */
if (rtlpriv->cfg->ops->hw_init(hw))
return false;
rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RETRY_LIMIT,
&rtlmac->retry_long);
RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC);
rtlpriv->cfg->ops->switch_channel(hw);
rtlpriv->cfg->ops->set_channel_access(hw);
rtlpriv->cfg->ops->set_bw_mode(hw,
cfg80211_get_chandef_type(&hw->conf.chandef));
/*<3> Enable Interrupt */
rtlpriv->cfg->ops->enable_interrupt(hw);
/*<enable timer> */
rtl_watch_dog_timer_callback(&rtlpriv->works.watchdog_timer);
return true;
}
EXPORT_SYMBOL(rtl_ps_enable_nic);
bool rtl_ps_disable_nic(struct ieee80211_hw *hw)
{
struct rtl_priv *rtlpriv = rtl_priv(hw);
/*<1> Stop all timer */
rtl_deinit_deferred_work(hw, true);
/*<2> Disable Interrupt */
rtlpriv->cfg->ops->disable_interrupt(hw);
tasklet_kill(&rtlpriv->works.irq_tasklet);
/*<3> Disable Adapter */
rtlpriv->cfg->ops->hw_disable(hw);
return true;
}
EXPORT_SYMBOL(rtl_ps_disable_nic);
static bool rtl_ps_set_rf_state(struct ieee80211_hw *hw,
enum rf_pwrstate state_toset,
u32 changesource)
{
struct rtl_priv *rtlpriv = rtl_priv(hw);
struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw));
bool actionallowed = false;
u16 rfwait_cnt = 0;
/*Only one thread can change
*the RF state at one time, and others
*should wait to be executed.
*/
while (true) {
spin_lock(&rtlpriv->locks.rf_ps_lock);
if (ppsc->rfchange_inprogress) {
spin_unlock(&rtlpriv->locks.rf_ps_lock);
rtl_dbg(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(&rtlpriv->locks.rf_ps_lock);
break;
}
}
switch (state_toset) {
case ERFON:
ppsc->rfoff_reason &= (~changesource);
if ((changesource == RF_CHANGE_BY_HW) &&
(ppsc->hwradiooff)) {
ppsc->hwradiooff = false;
}
if (!ppsc->rfoff_reason) {
ppsc->rfoff_reason = 0;
actionallowed = true;
}
break;
case ERFOFF:
if ((changesource == RF_CHANGE_BY_HW) && !ppsc->hwradiooff) {
ppsc->hwradiooff = true;
}
ppsc->rfoff_reason |= changesource;
actionallowed = true;
break;
case ERFSLEEP:
ppsc->rfoff_reason |= changesource;
actionallowed = true;
break;
default:
pr_err("switch case %#x not processed\n", state_toset);
break;
}
if (actionallowed)
rtlpriv->cfg->ops->set_rf_power_state(hw, state_toset);
spin_lock(&rtlpriv->locks.rf_ps_lock);
ppsc->rfchange_inprogress = false;
spin_unlock(&rtlpriv->locks.rf_ps_lock);
return actionallowed;
}
static void _rtl_ps_inactive_ps(struct ieee80211_hw *hw)
{
struct