9406f79775
Signed-off-by: Stanislaw Gruszka <sgruszka@redhat.com>
165 lines
4.8 KiB
C
165 lines
4.8 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 il_power_vec_entry {
|
|
struct il_powertable_cmd cmd;
|
|
u8 no_dtim; /* number of skip dtim */
|
|
};
|
|
|
|
static void il_power_sleep_cam_cmd(struct il_priv *il,
|
|
struct il_powertable_cmd *cmd)
|
|
{
|
|
memset(cmd, 0, sizeof(*cmd));
|
|
|
|
if (il->power_data.pci_pm)
|
|
cmd->flags |= IL_POWER_PCI_PM_MSK;
|
|
|
|
D_POWER("Sleep command for CAM\n");
|
|
}
|
|
|
|
static int
|
|
il_set_power(struct il_priv *il, struct il_powertable_cmd *cmd)
|
|
{
|
|
D_POWER("Sending power/sleep command\n");
|
|
D_POWER("Flags value = 0x%08X\n", cmd->flags);
|
|
D_POWER("Tx timeout = %u\n",
|
|
le32_to_cpu(cmd->tx_data_timeout));
|
|
D_POWER("Rx timeout = %u\n",
|
|
le32_to_cpu(cmd->rx_data_timeout));
|
|
D_POWER(
|
|
"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 il_send_cmd_pdu(il, POWER_TABLE_CMD,
|
|
sizeof(struct il_powertable_cmd), cmd);
|
|
}
|
|
|
|
int
|
|
il_power_set_mode(struct il_priv *il, struct il_powertable_cmd *cmd,
|
|
bool force)
|
|
{
|
|
int ret;
|
|
bool update_chains;
|
|
|
|
lockdep_assert_held(&il->mutex);
|
|
|
|
/* Don't update the RX chain when chain noise calibration is running */
|
|
update_chains = il->chain_noise_data.state == IL_CHAIN_NOISE_DONE ||
|
|
il->chain_noise_data.state == IL_CHAIN_NOISE_ALIVE;
|
|
|
|
if (!memcmp(&il->power_data.sleep_cmd, cmd, sizeof(*cmd)) && !force)
|
|
return 0;
|
|
|
|
if (!il_is_ready_rf(il))
|
|
return -EIO;
|
|
|
|
/* scan complete use sleep_power_next, need to be updated */
|
|
memcpy(&il->power_data.sleep_cmd_next, cmd, sizeof(*cmd));
|
|
if (test_bit(STATUS_SCANNING, &il->status) && !force) {
|
|
D_INFO("Defer power set mode while scanning\n");
|
|
return 0;
|
|
}
|
|
|
|
if (cmd->flags & IL_POWER_DRIVER_ALLOW_SLEEP_MSK)
|
|
set_bit(STATUS_POWER_PMI, &il->status);
|
|
|
|
ret = il_set_power(il, cmd);
|
|
if (!ret) {
|
|
if (!(cmd->flags & IL_POWER_DRIVER_ALLOW_SLEEP_MSK))
|
|
clear_bit(STATUS_POWER_PMI, &il->status);
|
|
|
|
if (il->cfg->ops->lib->update_chain_flags && update_chains)
|
|
il->cfg->ops->lib->update_chain_flags(il);
|
|
else if (il->cfg->ops->lib->update_chain_flags)
|
|
D_POWER(
|
|
"Cannot update the power, chain noise "
|
|
"calibration running: %d\n",
|
|
il->chain_noise_data.state);
|
|
|
|
memcpy(&il->power_data.sleep_cmd, cmd, sizeof(*cmd));
|
|
} else
|
|
IL_ERR("set power fail, ret = %d", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int il_power_update_mode(struct il_priv *il, bool force)
|
|
{
|
|
struct il_powertable_cmd cmd;
|
|
|
|
il_power_sleep_cam_cmd(il, &cmd);
|
|
return il_power_set_mode(il, &cmd, force);
|
|
}
|
|
EXPORT_SYMBOL(il_power_update_mode);
|
|
|
|
/* initialize to default */
|
|
void il_power_initialize(struct il_priv *il)
|
|
{
|
|
u16 lctl = il_pcie_link_ctl(il);
|
|
|
|
il->power_data.pci_pm = !(lctl & PCI_CFG_LINK_CTRL_VAL_L0S_EN);
|
|
|
|
il->power_data.debug_sleep_level_override = -1;
|
|
|
|
memset(&il->power_data.sleep_cmd, 0,
|
|
sizeof(il->power_data.sleep_cmd));
|
|
}
|
|
EXPORT_SYMBOL(il_power_initialize);
|