mirror of https://gitee.com/openkylin/linux.git
166 lines
5.1 KiB
C
166 lines
5.1 KiB
C
/******************************************************************************
|
|
*
|
|
* Copyright(c) 2007 - 2011 Intel Corporation. All rights reserved.
|
|
*
|
|
* Portions of this file are derived from the ipw3945 project, as well
|
|
* as portions of the ieee80211 subsystem header files.
|
|
*
|
|
* 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:
|
|
* Intel Linux Wireless <ilw@linux.intel.com>
|
|
* Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
|
|
*****************************************************************************/
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
|
|
#include <net/mac80211.h>
|
|
|
|
#include "iwl-eeprom.h"
|
|
#include "iwl-dev.h"
|
|
#include "iwl-core.h"
|
|
#include "iwl-io.h"
|
|
#include "iwl-commands.h"
|
|
#include "iwl-debug.h"
|
|
#include "iwl-power.h"
|
|
|
|
/*
|
|
* Setting power level allows the card to go to sleep when not busy.
|
|
*
|
|
* We calculate a sleep command based on the required latency, which
|
|
* we get from mac80211. In order to handle thermal throttling, we can
|
|
* also use pre-defined power levels.
|
|
*/
|
|
|
|
/*
|
|
* This defines the old power levels. They are still used by default
|
|
* (level 1) and for thermal throttle (levels 3 through 5)
|
|
*/
|
|
|
|
struct iwl_power_vec_entry {
|
|
struct iwl_powertable_cmd cmd;
|
|
u8 no_dtim; /* number of skip dtim */
|
|
};
|
|
|
|
static void iwl_legacy_power_sleep_cam_cmd(struct iwl_priv *priv,
|
|
struct iwl_powertable_cmd *cmd)
|
|
{
|
|
memset(cmd, 0, sizeof(*cmd));
|
|
|
|
if (priv->power_data.pci_pm)
|
|
cmd->flags |= IWL_POWER_PCI_PM_MSK;
|
|
|
|
IWL_DEBUG_POWER(priv, "Sleep command for CAM\n");
|
|
}
|
|
|
|
static int
|
|
iwl_legacy_set_power(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd)
|
|
{
|
|
IWL_DEBUG_POWER(priv, "Sending power/sleep command\n");
|
|
IWL_DEBUG_POWER(priv, "Flags value = 0x%08X\n", cmd->flags);
|
|
IWL_DEBUG_POWER(priv, "Tx timeout = %u\n",
|
|
le32_to_cpu(cmd->tx_data_timeout));
|
|
IWL_DEBUG_POWER(priv, "Rx timeout = %u\n",
|
|
le32_to_cpu(cmd->rx_data_timeout));
|
|
IWL_DEBUG_POWER(priv,
|
|
"Sleep interval vector = { %d , %d , %d , %d , %d }\n",
|
|
le32_to_cpu(cmd->sleep_interval[0]),
|
|
le32_to_cpu(cmd->sleep_interval[1]),
|
|
le32_to_cpu(cmd->sleep_interval[2]),
|
|
le32_to_cpu(cmd->sleep_interval[3]),
|
|
le32_to_cpu(cmd->sleep_interval[4]));
|
|
|
|
return iwl_legacy_send_cmd_pdu(priv, POWER_TABLE_CMD,
|
|
sizeof(struct iwl_powertable_cmd), cmd);
|
|
}
|
|
|
|
int
|
|
iwl_legacy_power_set_mode(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd,
|
|
bool force)
|
|
{
|
|
int ret;
|
|
bool update_chains;
|
|
|
|
lockdep_assert_held(&priv->mutex);
|
|
|
|
/* Don't update the RX chain when chain noise calibration is running */
|
|
update_chains = priv->chain_noise_data.state == IWL_CHAIN_NOISE_DONE ||
|
|
priv->chain_noise_data.state == IWL_CHAIN_NOISE_ALIVE;
|
|
|
|
if (!memcmp(&priv->power_data.sleep_cmd, cmd, sizeof(*cmd)) && !force)
|
|
return 0;
|
|
|
|
if (!iwl_legacy_is_ready_rf(priv))
|
|
return -EIO;
|
|
|
|
/* scan complete use sleep_power_next, need to be updated */
|
|
memcpy(&priv->power_data.sleep_cmd_next, cmd, sizeof(*cmd));
|
|
if (test_bit(STATUS_SCANNING, &priv->status) && !force) {
|
|
IWL_DEBUG_INFO(priv, "Defer power set mode while scanning\n");
|
|
return 0;
|
|
}
|
|
|
|
if (cmd->flags & IWL_POWER_DRIVER_ALLOW_SLEEP_MSK)
|
|
set_bit(STATUS_POWER_PMI, &priv->status);
|
|
|
|
ret = iwl_legacy_set_power(priv, cmd);
|
|
if (!ret) {
|
|
if (!(cmd->flags & IWL_POWER_DRIVER_ALLOW_SLEEP_MSK))
|
|
clear_bit(STATUS_POWER_PMI, &priv->status);
|
|
|
|
if (priv->cfg->ops->lib->update_chain_flags && update_chains)
|
|
priv->cfg->ops->lib->update_chain_flags(priv);
|
|
else if (priv->cfg->ops->lib->update_chain_flags)
|
|
IWL_DEBUG_POWER(priv,
|
|
"Cannot update the power, chain noise "
|
|
"calibration running: %d\n",
|
|
priv->chain_noise_data.state);
|
|
|
|
memcpy(&priv->power_data.sleep_cmd, cmd, sizeof(*cmd));
|
|
} else
|
|
IWL_ERR(priv, "set power fail, ret = %d", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int iwl_legacy_power_update_mode(struct iwl_priv *priv, bool force)
|
|
{
|
|
struct iwl_powertable_cmd cmd;
|
|
|
|
iwl_legacy_power_sleep_cam_cmd(priv, &cmd);
|
|
return iwl_legacy_power_set_mode(priv, &cmd, force);
|
|
}
|
|
EXPORT_SYMBOL(iwl_legacy_power_update_mode);
|
|
|
|
/* initialize to default */
|
|
void iwl_legacy_power_initialize(struct iwl_priv *priv)
|
|
{
|
|
u16 lctl = iwl_legacy_pcie_link_ctl(priv);
|
|
|
|
priv->power_data.pci_pm = !(lctl & PCI_CFG_LINK_CTRL_VAL_L0S_EN);
|
|
|
|
priv->power_data.debug_sleep_level_override = -1;
|
|
|
|
memset(&priv->power_data.sleep_cmd, 0,
|
|
sizeof(priv->power_data.sleep_cmd));
|
|
}
|
|
EXPORT_SYMBOL(iwl_legacy_power_initialize);
|