2008-04-22 06:41:51 +08:00
|
|
|
/******************************************************************************
|
|
|
|
*
|
2009-01-09 02:20:02 +08:00
|
|
|
* Copyright(c) 2007 - 2009 Intel Corporation. All rights reserved.
|
2008-04-22 06:41:51 +08:00
|
|
|
*
|
|
|
|
* 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:
|
2008-12-10 03:28:58 +08:00
|
|
|
* Intel Linux Wireless <ilw@linux.intel.com>
|
2008-04-22 06:41:51 +08:00
|
|
|
* Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
|
|
|
|
#include <net/mac80211.h>
|
|
|
|
|
|
|
|
#include "iwl-eeprom.h"
|
2008-04-25 02:55:38 +08:00
|
|
|
#include "iwl-dev.h"
|
2008-04-22 06:41:51 +08:00
|
|
|
#include "iwl-core.h"
|
iwlwifi: Thermal Throttling Management - Part 1
Part 1 of Thermal Throttling Management -
Thermal Throttling feature is used to put NIC into low power state when
driver detect the Radio temperature reach pre-defined threshold
Two Thermal Throttling Management Methods; this patch introduce the
Legacy Thermal Management:
IWL_TI_0: normal temperature, system power state
IWL_TI_1: high temperature detect, low power state
IWL_TI_2: higher temperature detected, lower power state
IWL_TI_CT_KILL: critical temperature detected, lowest power state
Once get into CT_KILL state, uCode go into sleep, driver will stop all
the active queues, then move to IWL_TI_CT_KILL state; also set up 5
seconds timer to toggle CSR flag, uCode wake up upon CSR flag change,
then measure the temperature.
If temperature is above CT_KILL exit threshold, uCode go backto sleep;
if temperature is below CT_KILL exit threshold, uCode send Card State
Notification response with appropriate CT_KILL status flag, and uCode
remain awake, Driver receive Card State Notification Response and update
the card temperature to the CT_KILL exit threshold.
Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-07-25 02:13:02 +08:00
|
|
|
#include "iwl-io.h"
|
2008-04-25 02:55:37 +08:00
|
|
|
#include "iwl-commands.h"
|
2008-04-22 06:41:51 +08:00
|
|
|
#include "iwl-debug.h"
|
|
|
|
#include "iwl-power.h"
|
|
|
|
|
|
|
|
/*
|
2009-05-09 04:44:38 +08:00
|
|
|
* Setting power level allow the card to go to sleep when not busy.
|
2008-04-22 06:41:51 +08:00
|
|
|
*
|
2009-05-09 04:44:38 +08:00
|
|
|
* The power level is set to INDEX_1 (the least deep state) by
|
|
|
|
* default, and will, in the future, be the deepest state unless
|
|
|
|
* otherwise required by pm_qos network latency requirements.
|
2008-04-22 06:41:51 +08:00
|
|
|
*
|
2009-05-09 04:44:38 +08:00
|
|
|
* Using INDEX_1 without pm_qos is ok because mac80211 will disable
|
|
|
|
* PS when even checking every beacon for the TIM bit would exceed
|
|
|
|
* the required latency.
|
2008-04-22 06:41:51 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#define IWL_POWER_RANGE_0_MAX (2)
|
|
|
|
#define IWL_POWER_RANGE_1_MAX (10)
|
|
|
|
|
|
|
|
|
2009-05-09 04:44:38 +08:00
|
|
|
#define NOSLP cpu_to_le16(0), 0, 0
|
|
|
|
#define SLP IWL_POWER_DRIVER_ALLOW_SLEEP_MSK, 0, 0
|
|
|
|
#define TU_TO_USEC 1024
|
|
|
|
#define SLP_TOUT(T) cpu_to_le32((T) * TU_TO_USEC)
|
|
|
|
#define SLP_VEC(X0, X1, X2, X3, X4) {cpu_to_le32(X0), \
|
|
|
|
cpu_to_le32(X1), \
|
|
|
|
cpu_to_le32(X2), \
|
|
|
|
cpu_to_le32(X3), \
|
|
|
|
cpu_to_le32(X4)}
|
2008-04-22 06:41:51 +08:00
|
|
|
/* default power management (not Tx power) table values */
|
2009-05-09 04:44:38 +08:00
|
|
|
/* for DTIM period 0 through IWL_POWER_RANGE_0_MAX */
|
|
|
|
static const struct iwl_power_vec_entry range_0[IWL_POWER_NUM] = {
|
2008-04-22 06:41:51 +08:00
|
|
|
{{NOSLP, SLP_TOUT(0), SLP_TOUT(0), SLP_VEC(0, 0, 0, 0, 0)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 2, 2, 2, 0xFF)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(1, 2, 2, 2, 0xFF)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 2, 2, 2, 0xFF)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 2, 4, 4, 0xFF)}, 1},
|
|
|
|
{{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(2, 2, 4, 6, 0xFF)}, 2}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2009-05-09 04:44:38 +08:00
|
|
|
/* for DTIM period IWL_POWER_RANGE_0_MAX + 1 through IWL_POWER_RANGE_1_MAX */
|
|
|
|
static const struct iwl_power_vec_entry range_1[IWL_POWER_NUM] = {
|
2008-04-22 06:41:51 +08:00
|
|
|
{{NOSLP, SLP_TOUT(0), SLP_TOUT(0), SLP_VEC(0, 0, 0, 0, 0)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 2, 3, 4, 4)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(1, 2, 3, 4, 7)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 4, 6, 7, 9)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 4, 6, 9, 10)}, 1},
|
|
|
|
{{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(2, 4, 7, 10, 10)}, 2}
|
|
|
|
};
|
|
|
|
|
2009-05-09 04:44:38 +08:00
|
|
|
/* for DTIM period > IWL_POWER_RANGE_1_MAX */
|
|
|
|
static const struct iwl_power_vec_entry range_2[IWL_POWER_NUM] = {
|
2008-04-22 06:41:51 +08:00
|
|
|
{{NOSLP, SLP_TOUT(0), SLP_TOUT(0), SLP_VEC(0, 0, 0, 0, 0)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 2, 3, 4, 0xFF)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(2, 4, 6, 7, 0xFF)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 7, 9, 9, 0xFF)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 7, 9, 9, 0xFF)}, 0},
|
|
|
|
{{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(4, 7, 10, 10, 0xFF)}, 0}
|
|
|
|
};
|
|
|
|
|
2009-01-28 06:27:58 +08:00
|
|
|
|
2008-07-18 13:52:57 +08:00
|
|
|
/* set card power command */
|
|
|
|
static int iwl_set_power(struct iwl_priv *priv, void *cmd)
|
|
|
|
{
|
iwlwifi: fix PS disable status race
iwlwifi internally needs to keep track of whether PS
is enabled in the firmware or not. To do this, it keeps
a bit in the status flags, called STATUS_POWER_PMI.
The code to set this bit looks as follows:
static int iwl_set_power(struct iwl_priv *priv, void *cmd)
{
return iwl_send_cmd_pdu_async(priv, POWER_TABLE_CMD,
sizeof(struct iwl_powertable_cmd),
cmd, NULL);
}
int iwl_power_update_mode(...)
{
[...]
if (final_mode != IWL_POWER_MODE_CAM)
set_bit(STATUS_POWER_PMI, &priv->status);
iwl_update_power_cmd(priv, &cmd, final_mode);
cmd.keep_alive_beacons = 0;
if (final_mode == IWL_POWER_INDEX_5)
cmd.flags |= IWL_POWER_FAST_PD;
ret = iwl_set_power(priv, &cmd);
if (final_mode == IWL_POWER_MODE_CAM)
clear_bit(STATUS_POWER_PMI, &priv->status);
else
set_bit(STATUS_POWER_PMI, &priv->status);
if (priv->cfg->ops->lib->update_chain_flags && update_chains)
priv->cfg->ops->lib->update_chain_flags(priv);
[...]
}
Now, this bit really needs to track what the _firmware_
thinks, not what the driver thinks. Therefore, there is
a race condition here -- the driver sets the bit before
it knows that the async command sent to the card in the
iwl_set_power function has been processed. As a result,
the call to update_chain_flags() may think that the card
has been woken up (PMI bit cleared) while in reality it
hasn't processed the async POWER_TABLE_CMD yet.
This leads to bugs -- any commands the update_chain_flags
function sends can get stuck and subsequent commands also
fail.
The fix is almost trivial: since there's no reason to send
an async command here (in fact, there almost never should
be since many mac80211 callbacks can sleep) just make the
function wait for the card to process the command and then
return and clear the PMI bit.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Acked-by: Mohamed Abbas <mohamed.abbas@intel.com>
Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-05-09 04:44:37 +08:00
|
|
|
return iwl_send_cmd_pdu(priv, POWER_TABLE_CMD,
|
|
|
|
sizeof(struct iwl_powertable_cmd), cmd);
|
2008-07-18 13:52:57 +08:00
|
|
|
}
|
2008-04-22 06:41:51 +08:00
|
|
|
|
|
|
|
/* initialize to default */
|
2009-01-20 07:30:21 +08:00
|
|
|
static void iwl_power_init_handle(struct iwl_priv *priv)
|
2008-04-22 06:41:51 +08:00
|
|
|
{
|
|
|
|
struct iwl_power_mgr *pow_data;
|
2009-05-09 04:44:38 +08:00
|
|
|
int size = sizeof(struct iwl_power_vec_entry) * IWL_POWER_NUM;
|
2008-09-03 11:26:49 +08:00
|
|
|
struct iwl_powertable_cmd *cmd;
|
|
|
|
int i;
|
2009-02-11 07:19:02 +08:00
|
|
|
u16 lctl;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
2009-01-28 06:27:56 +08:00
|
|
|
IWL_DEBUG_POWER(priv, "Initialize power \n");
|
2008-04-22 06:41:51 +08:00
|
|
|
|
2009-01-20 07:30:21 +08:00
|
|
|
pow_data = &priv->power_data;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
|
|
|
memset(pow_data, 0, sizeof(*pow_data));
|
|
|
|
|
|
|
|
memcpy(&pow_data->pwr_range_0[0], &range_0[0], size);
|
|
|
|
memcpy(&pow_data->pwr_range_1[0], &range_1[0], size);
|
|
|
|
memcpy(&pow_data->pwr_range_2[0], &range_2[0], size);
|
|
|
|
|
2009-02-11 07:19:02 +08:00
|
|
|
lctl = iwl_pcie_link_ctl(priv);
|
2008-04-22 06:41:51 +08:00
|
|
|
|
2009-01-28 06:27:56 +08:00
|
|
|
IWL_DEBUG_POWER(priv, "adjust power command flags\n");
|
2008-04-22 06:41:51 +08:00
|
|
|
|
2009-05-09 04:44:38 +08:00
|
|
|
for (i = 0; i < IWL_POWER_NUM; i++) {
|
2008-09-03 11:26:49 +08:00
|
|
|
cmd = &pow_data->pwr_range_0[i].cmd;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
2009-02-11 07:19:02 +08:00
|
|
|
if (lctl & PCI_CFG_LINK_CTRL_VAL_L0S_EN)
|
2008-09-03 11:26:49 +08:00
|
|
|
cmd->flags &= ~IWL_POWER_PCI_PM_MSK;
|
|
|
|
else
|
|
|
|
cmd->flags |= IWL_POWER_PCI_PM_MSK;
|
2008-04-22 06:41:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-24 14:48:56 +08:00
|
|
|
/* adjust power command according to DTIM period and power level*/
|
2009-01-20 07:30:21 +08:00
|
|
|
static int iwl_update_power_cmd(struct iwl_priv *priv,
|
|
|
|
struct iwl_powertable_cmd *cmd, u16 mode)
|
2008-04-22 06:41:51 +08:00
|
|
|
{
|
|
|
|
struct iwl_power_vec_entry *range;
|
|
|
|
struct iwl_power_mgr *pow_data;
|
2009-01-20 07:30:21 +08:00
|
|
|
int i;
|
|
|
|
u32 max_sleep = 0;
|
|
|
|
u8 period;
|
|
|
|
bool skip;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
|
|
|
if (mode > IWL_POWER_INDEX_5) {
|
2009-01-28 06:27:56 +08:00
|
|
|
IWL_DEBUG_POWER(priv, "Error invalid power mode \n");
|
2009-01-20 07:30:21 +08:00
|
|
|
return -EINVAL;
|
2008-04-22 06:41:51 +08:00
|
|
|
}
|
2009-01-20 07:30:21 +08:00
|
|
|
|
|
|
|
pow_data = &priv->power_data;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
|
|
|
if (pow_data->dtim_period <= IWL_POWER_RANGE_0_MAX)
|
|
|
|
range = &pow_data->pwr_range_0[0];
|
|
|
|
else if (pow_data->dtim_period <= IWL_POWER_RANGE_1_MAX)
|
|
|
|
range = &pow_data->pwr_range_1[0];
|
|
|
|
else
|
|
|
|
range = &pow_data->pwr_range_2[0];
|
|
|
|
|
|
|
|
period = pow_data->dtim_period;
|
2008-07-18 13:52:57 +08:00
|
|
|
memcpy(cmd, &range[mode].cmd, sizeof(struct iwl_powertable_cmd));
|
2008-04-22 06:41:51 +08:00
|
|
|
|
|
|
|
if (period == 0) {
|
|
|
|
period = 1;
|
2009-01-20 07:30:21 +08:00
|
|
|
skip = false;
|
2008-04-22 06:41:51 +08:00
|
|
|
} else {
|
2009-01-20 07:30:21 +08:00
|
|
|
skip = !!range[mode].no_dtim;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skip) {
|
2008-04-22 06:41:51 +08:00
|
|
|
__le32 slp_itrvl = cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1];
|
|
|
|
max_sleep = le32_to_cpu(slp_itrvl);
|
|
|
|
if (max_sleep == 0xFF)
|
|
|
|
max_sleep = period * (skip + 1);
|
|
|
|
else if (max_sleep > period)
|
|
|
|
max_sleep = (le32_to_cpu(slp_itrvl) / period) * period;
|
|
|
|
cmd->flags |= IWL_POWER_SLEEP_OVER_DTIM_MSK;
|
2009-01-20 07:30:21 +08:00
|
|
|
} else {
|
|
|
|
max_sleep = period;
|
|
|
|
cmd->flags &= ~IWL_POWER_SLEEP_OVER_DTIM_MSK;
|
2008-04-22 06:41:51 +08:00
|
|
|
}
|
|
|
|
|
2009-01-20 07:30:21 +08:00
|
|
|
for (i = 0; i < IWL_POWER_VEC_SIZE; i++)
|
2008-04-22 06:41:51 +08:00
|
|
|
if (le32_to_cpu(cmd->sleep_interval[i]) > max_sleep)
|
|
|
|
cmd->sleep_interval[i] = cpu_to_le32(max_sleep);
|
|
|
|
|
2009-01-28 06:27:56 +08:00
|
|
|
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",
|
2008-04-22 06:41:51 +08:00
|
|
|
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]));
|
|
|
|
|
2009-01-20 07:30:21 +08:00
|
|
|
return 0;
|
2008-04-22 06:41:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2008-09-03 11:26:56 +08:00
|
|
|
* compute the final power mode index
|
2008-04-22 06:41:51 +08:00
|
|
|
*/
|
2008-09-03 11:26:53 +08:00
|
|
|
int iwl_power_update_mode(struct iwl_priv *priv, bool force)
|
2008-04-22 06:41:51 +08:00
|
|
|
{
|
|
|
|
struct iwl_power_mgr *setting = &(priv->power_data);
|
|
|
|
int ret = 0;
|
iwlwifi: Thermal Throttling Management - Part 1
Part 1 of Thermal Throttling Management -
Thermal Throttling feature is used to put NIC into low power state when
driver detect the Radio temperature reach pre-defined threshold
Two Thermal Throttling Management Methods; this patch introduce the
Legacy Thermal Management:
IWL_TI_0: normal temperature, system power state
IWL_TI_1: high temperature detect, low power state
IWL_TI_2: higher temperature detected, lower power state
IWL_TI_CT_KILL: critical temperature detected, lowest power state
Once get into CT_KILL state, uCode go into sleep, driver will stop all
the active queues, then move to IWL_TI_CT_KILL state; also set up 5
seconds timer to toggle CSR flag, uCode wake up upon CSR flag change,
then measure the temperature.
If temperature is above CT_KILL exit threshold, uCode go backto sleep;
if temperature is below CT_KILL exit threshold, uCode send Card State
Notification response with appropriate CT_KILL status flag, and uCode
remain awake, Driver receive Card State Notification Response and update
the card temperature to the CT_KILL exit threshold.
Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-07-25 02:13:02 +08:00
|
|
|
struct iwl_tt_mgmt *tt = &priv->power_data.tt;
|
2008-04-22 06:41:51 +08:00
|
|
|
u16 uninitialized_var(final_mode);
|
2008-11-08 01:58:37 +08:00
|
|
|
bool update_chains;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
2008-09-03 11:26:53 +08:00
|
|
|
/* Don't update the RX chain when chain noise calibration is running */
|
2008-11-08 01:58:37 +08:00
|
|
|
update_chains = priv->chain_noise_data.state == IWL_CHAIN_NOISE_DONE ||
|
|
|
|
priv->chain_noise_data.state == IWL_CHAIN_NOISE_ALIVE;
|
2008-09-03 11:26:53 +08:00
|
|
|
|
2009-05-09 04:44:38 +08:00
|
|
|
final_mode = priv->power_data.user_power_setting;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
2009-05-09 04:44:38 +08:00
|
|
|
if (setting->power_disabled)
|
2008-04-22 06:41:51 +08:00
|
|
|
final_mode = IWL_POWER_MODE_CAM;
|
|
|
|
|
iwlwifi: Thermal Throttling Management - Part 1
Part 1 of Thermal Throttling Management -
Thermal Throttling feature is used to put NIC into low power state when
driver detect the Radio temperature reach pre-defined threshold
Two Thermal Throttling Management Methods; this patch introduce the
Legacy Thermal Management:
IWL_TI_0: normal temperature, system power state
IWL_TI_1: high temperature detect, low power state
IWL_TI_2: higher temperature detected, lower power state
IWL_TI_CT_KILL: critical temperature detected, lowest power state
Once get into CT_KILL state, uCode go into sleep, driver will stop all
the active queues, then move to IWL_TI_CT_KILL state; also set up 5
seconds timer to toggle CSR flag, uCode wake up upon CSR flag change,
then measure the temperature.
If temperature is above CT_KILL exit threshold, uCode go backto sleep;
if temperature is below CT_KILL exit threshold, uCode send Card State
Notification response with appropriate CT_KILL status flag, and uCode
remain awake, Driver receive Card State Notification Response and update
the card temperature to the CT_KILL exit threshold.
Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-07-25 02:13:02 +08:00
|
|
|
if (tt->state >= IWL_TI_1) {
|
|
|
|
/* TT power setting overwrite user & system power setting */
|
|
|
|
final_mode = tt->tt_power_mode;
|
|
|
|
}
|
2009-05-09 04:44:38 +08:00
|
|
|
if (iwl_is_ready_rf(priv) &&
|
2008-09-03 11:26:53 +08:00
|
|
|
((setting->power_mode != final_mode) || force)) {
|
2008-07-18 13:52:57 +08:00
|
|
|
struct iwl_powertable_cmd cmd;
|
2008-04-22 06:41:51 +08:00
|
|
|
|
|
|
|
if (final_mode != IWL_POWER_MODE_CAM)
|
|
|
|
set_bit(STATUS_POWER_PMI, &priv->status);
|
|
|
|
|
2009-01-20 07:30:21 +08:00
|
|
|
iwl_update_power_cmd(priv, &cmd, final_mode);
|
2008-04-22 06:41:51 +08:00
|
|
|
cmd.keep_alive_beacons = 0;
|
|
|
|
|
|
|
|
if (final_mode == IWL_POWER_INDEX_5)
|
|
|
|
cmd.flags |= IWL_POWER_FAST_PD;
|
|
|
|
|
2008-07-18 13:52:57 +08:00
|
|
|
ret = iwl_set_power(priv, &cmd);
|
2008-04-22 06:41:51 +08:00
|
|
|
|
|
|
|
if (final_mode == IWL_POWER_MODE_CAM)
|
|
|
|
clear_bit(STATUS_POWER_PMI, &priv->status);
|
|
|
|
|
2008-11-08 01:58:37 +08:00
|
|
|
if (priv->cfg->ops->lib->update_chain_flags && update_chains)
|
2008-04-22 06:41:51 +08:00
|
|
|
priv->cfg->ops->lib->update_chain_flags(priv);
|
2008-11-08 01:58:37 +08:00
|
|
|
else
|
2009-01-28 06:27:56 +08:00
|
|
|
IWL_DEBUG_POWER(priv, "Cannot update the power, chain noise "
|
2008-11-08 01:58:37 +08:00
|
|
|
"calibration running: %d\n",
|
|
|
|
priv->chain_noise_data.state);
|
2008-04-22 06:41:51 +08:00
|
|
|
if (!ret)
|
|
|
|
setting->power_mode = final_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_power_update_mode);
|
|
|
|
|
|
|
|
/* set user_power_setting */
|
|
|
|
int iwl_power_set_user_mode(struct iwl_priv *priv, u16 mode)
|
|
|
|
{
|
2009-05-09 04:44:38 +08:00
|
|
|
if (mode >= IWL_POWER_NUM)
|
2008-04-22 06:41:51 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
priv->power_data.user_power_setting = mode;
|
|
|
|
|
2008-09-03 11:26:53 +08:00
|
|
|
return iwl_power_update_mode(priv, 0);
|
2008-04-22 06:41:51 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_power_set_user_mode);
|
|
|
|
|
iwlwifi: Thermal Throttling Management - Part 1
Part 1 of Thermal Throttling Management -
Thermal Throttling feature is used to put NIC into low power state when
driver detect the Radio temperature reach pre-defined threshold
Two Thermal Throttling Management Methods; this patch introduce the
Legacy Thermal Management:
IWL_TI_0: normal temperature, system power state
IWL_TI_1: high temperature detect, low power state
IWL_TI_2: higher temperature detected, lower power state
IWL_TI_CT_KILL: critical temperature detected, lowest power state
Once get into CT_KILL state, uCode go into sleep, driver will stop all
the active queues, then move to IWL_TI_CT_KILL state; also set up 5
seconds timer to toggle CSR flag, uCode wake up upon CSR flag change,
then measure the temperature.
If temperature is above CT_KILL exit threshold, uCode go backto sleep;
if temperature is below CT_KILL exit threshold, uCode send Card State
Notification response with appropriate CT_KILL status flag, and uCode
remain awake, Driver receive Card State Notification Response and update
the card temperature to the CT_KILL exit threshold.
Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-07-25 02:13:02 +08:00
|
|
|
#define CT_KILL_EXIT_DURATION (5) /* 5 seconds duration */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* toggle the bit to wake up uCode and check the temperature
|
|
|
|
* if the temperature is below CT, uCode will stay awake and send card
|
|
|
|
* state notification with CT_KILL bit clear to inform Thermal Throttling
|
|
|
|
* Management to change state. Otherwise, uCode will go back to sleep
|
|
|
|
* without doing anything, driver should continue the 5 seconds timer
|
|
|
|
* to wake up uCode for temperature check until temperature drop below CT
|
|
|
|
*/
|
|
|
|
static void iwl_tt_check_exit_ct_kill(unsigned long data)
|
|
|
|
{
|
|
|
|
struct iwl_priv *priv = (struct iwl_priv *)data;
|
|
|
|
struct iwl_tt_mgmt *tt = &priv->power_data.tt;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (test_bit(STATUS_EXIT_PENDING, &priv->status))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (tt->state == IWL_TI_CT_KILL) {
|
|
|
|
if (priv->power_data.ct_kill_toggle) {
|
|
|
|
iwl_write32(priv, CSR_UCODE_DRV_GP1_CLR,
|
|
|
|
CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT);
|
|
|
|
priv->power_data.ct_kill_toggle = false;
|
|
|
|
} else {
|
|
|
|
iwl_write32(priv, CSR_UCODE_DRV_GP1_SET,
|
|
|
|
CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT);
|
|
|
|
priv->power_data.ct_kill_toggle = true;
|
|
|
|
}
|
|
|
|
iwl_read32(priv, CSR_UCODE_DRV_GP1);
|
|
|
|
spin_lock_irqsave(&priv->reg_lock, flags);
|
|
|
|
if (!iwl_grab_nic_access(priv))
|
|
|
|
iwl_release_nic_access(priv);
|
|
|
|
spin_unlock_irqrestore(&priv->reg_lock, flags);
|
|
|
|
|
|
|
|
/* Reschedule the ct_kill timer to occur in
|
|
|
|
* CT_KILL_EXIT_DURATION seconds to ensure we get a
|
|
|
|
* thermal update */
|
|
|
|
mod_timer(&priv->power_data.ct_kill_exit_tm, jiffies +
|
|
|
|
CT_KILL_EXIT_DURATION * HZ);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void iwl_perform_ct_kill_task(struct iwl_priv *priv,
|
|
|
|
bool stop)
|
|
|
|
{
|
|
|
|
if (stop) {
|
|
|
|
IWL_DEBUG_POWER(priv, "Stop all queues\n");
|
|
|
|
if (priv->mac80211_registered)
|
|
|
|
ieee80211_stop_queues(priv->hw);
|
|
|
|
IWL_DEBUG_POWER(priv,
|
|
|
|
"Schedule 5 seconds CT_KILL Timer\n");
|
|
|
|
mod_timer(&priv->power_data.ct_kill_exit_tm, jiffies +
|
|
|
|
CT_KILL_EXIT_DURATION * HZ);
|
|
|
|
} else {
|
|
|
|
IWL_DEBUG_POWER(priv, "Wake all queues\n");
|
|
|
|
if (priv->mac80211_registered)
|
|
|
|
ieee80211_wake_queues(priv->hw);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define IWL_MINIMAL_POWER_THRESHOLD (CT_KILL_THRESHOLD_LEGACY)
|
|
|
|
#define IWL_REDUCED_PERFORMANCE_THRESHOLD_2 (100)
|
|
|
|
#define IWL_REDUCED_PERFORMANCE_THRESHOLD_1 (90)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Legacy thermal throttling
|
|
|
|
* 1) Avoid NIC destruction due to high temperatures
|
|
|
|
* Chip will identify dangerously high temperatures that can
|
|
|
|
* harm the device and will power down
|
|
|
|
* 2) Avoid the NIC power down due to high temperature
|
|
|
|
* Throttle early enough to lower the power consumption before
|
|
|
|
* drastic steps are needed
|
|
|
|
*/
|
|
|
|
static void iwl_legacy_tt_handler(struct iwl_priv *priv, s32 temp)
|
|
|
|
{
|
|
|
|
struct iwl_tt_mgmt *tt = &priv->power_data.tt;
|
|
|
|
enum iwl_tt_state new_state;
|
|
|
|
struct iwl_power_mgr *setting = &priv->power_data;
|
|
|
|
|
|
|
|
#ifdef CONFIG_IWLWIFI_DEBUG
|
|
|
|
if ((tt->tt_previous_temp) &&
|
|
|
|
(temp > tt->tt_previous_temp) &&
|
|
|
|
((temp - tt->tt_previous_temp) >
|
|
|
|
IWL_TT_INCREASE_MARGIN)) {
|
|
|
|
IWL_DEBUG_POWER(priv,
|
|
|
|
"Temperature increase %d degree Celsius\n",
|
|
|
|
(temp - tt->tt_previous_temp));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/* in Celsius */
|
|
|
|
if (temp >= IWL_MINIMAL_POWER_THRESHOLD)
|
|
|
|
new_state = IWL_TI_CT_KILL;
|
|
|
|
else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_2)
|
|
|
|
new_state = IWL_TI_2;
|
|
|
|
else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_1)
|
|
|
|
new_state = IWL_TI_1;
|
|
|
|
else
|
|
|
|
new_state = IWL_TI_0;
|
|
|
|
|
|
|
|
#ifdef CONFIG_IWLWIFI_DEBUG
|
|
|
|
tt->tt_previous_temp = temp;
|
|
|
|
#endif
|
|
|
|
if (tt->state != new_state) {
|
|
|
|
if (tt->state == IWL_TI_0) {
|
|
|
|
tt->sys_power_mode = setting->power_mode;
|
|
|
|
IWL_DEBUG_POWER(priv, "current power mode: %u\n",
|
|
|
|
setting->power_mode);
|
|
|
|
}
|
|
|
|
switch (new_state) {
|
|
|
|
case IWL_TI_0:
|
|
|
|
/* when system ready to go back to IWL_TI_0 state
|
|
|
|
* using system power mode instead of TT power mode
|
|
|
|
* revert back to the orginal power mode which was saved
|
|
|
|
* before enter Thermal Throttling state
|
|
|
|
* update priv->power_data.user_power_setting to the
|
|
|
|
* required power mode to make sure
|
|
|
|
* iwl_power_update_mode() will update power correctly.
|
|
|
|
*/
|
|
|
|
priv->power_data.user_power_setting =
|
|
|
|
tt->sys_power_mode;
|
|
|
|
tt->tt_power_mode = tt->sys_power_mode;
|
|
|
|
break;
|
|
|
|
case IWL_TI_1:
|
|
|
|
tt->tt_power_mode = IWL_POWER_INDEX_3;
|
|
|
|
break;
|
|
|
|
case IWL_TI_2:
|
|
|
|
tt->tt_power_mode = IWL_POWER_INDEX_4;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
tt->tt_power_mode = IWL_POWER_INDEX_5;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (iwl_power_update_mode(priv, true)) {
|
|
|
|
/* TT state not updated
|
|
|
|
* try again during next temperature read
|
|
|
|
*/
|
|
|
|
IWL_ERR(priv, "Cannot update power mode, "
|
|
|
|
"TT state not updated\n");
|
|
|
|
} else {
|
|
|
|
if (new_state == IWL_TI_CT_KILL)
|
|
|
|
iwl_perform_ct_kill_task(priv, true);
|
|
|
|
else if (tt->state == IWL_TI_CT_KILL &&
|
|
|
|
new_state != IWL_TI_CT_KILL)
|
|
|
|
iwl_perform_ct_kill_task(priv, false);
|
|
|
|
tt->state = new_state;
|
|
|
|
IWL_DEBUG_POWER(priv, "Temperature state changed %u\n",
|
|
|
|
tt->state);
|
|
|
|
IWL_DEBUG_POWER(priv, "Power Index change to %u\n",
|
|
|
|
tt->tt_power_mode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Card State Notification indicated reach critical temperature
|
|
|
|
* if PSP not enable, no Thermal Throttling function will be performed
|
|
|
|
* just set the GP1 bit to acknowledge the event
|
|
|
|
* otherwise, go into IWL_TI_CT_KILL state
|
|
|
|
* since Card State Notification will not provide any temperature reading
|
|
|
|
* so just pass the CT_KILL temperature to iwl_legacy_tt_handler()
|
|
|
|
*/
|
|
|
|
void iwl_tt_enter_ct_kill(struct iwl_priv *priv)
|
|
|
|
{
|
|
|
|
struct iwl_tt_mgmt *tt = &priv->power_data.tt;
|
|
|
|
|
|
|
|
if (test_bit(STATUS_EXIT_PENDING, &priv->status))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (tt->state != IWL_TI_CT_KILL) {
|
|
|
|
IWL_ERR(priv, "Device reached critical temperature "
|
|
|
|
"- ucode going to sleep!\n");
|
|
|
|
iwl_legacy_tt_handler(priv, IWL_MINIMAL_POWER_THRESHOLD);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_tt_enter_ct_kill);
|
|
|
|
|
|
|
|
/* Card State Notification indicated out of critical temperature
|
|
|
|
* since Card State Notification will not provide any temperature reading
|
|
|
|
* so pass the IWL_REDUCED_PERFORMANCE_THRESHOLD_2 temperature
|
|
|
|
* to iwl_legacy_tt_handler() to get out of IWL_CT_KILL state
|
|
|
|
*/
|
|
|
|
void iwl_tt_exit_ct_kill(struct iwl_priv *priv)
|
|
|
|
{
|
|
|
|
struct iwl_tt_mgmt *tt = &priv->power_data.tt;
|
|
|
|
|
|
|
|
if (test_bit(STATUS_EXIT_PENDING, &priv->status))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* stop ct_kill_exit_tm timer */
|
|
|
|
del_timer_sync(&priv->power_data.ct_kill_exit_tm);
|
|
|
|
|
|
|
|
if (tt->state == IWL_TI_CT_KILL) {
|
|
|
|
IWL_ERR(priv,
|
|
|
|
"Device temperature below critical"
|
|
|
|
"- ucode awake!\n");
|
|
|
|
iwl_legacy_tt_handler(priv,
|
|
|
|
IWL_REDUCED_PERFORMANCE_THRESHOLD_2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_tt_exit_ct_kill);
|
|
|
|
|
|
|
|
void iwl_tt_handler(struct iwl_priv *priv)
|
|
|
|
{
|
|
|
|
s32 temp = priv->temperature; /* degrees CELSIUS except 4965 */
|
|
|
|
|
|
|
|
if (test_bit(STATUS_EXIT_PENDING, &priv->status))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ((priv->hw_rev & CSR_HW_REV_TYPE_MSK) == CSR_HW_REV_TYPE_4965)
|
|
|
|
temp = KELVIN_TO_CELSIUS(priv->temperature);
|
|
|
|
|
|
|
|
iwl_legacy_tt_handler(priv, temp);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_tt_handler);
|
|
|
|
|
|
|
|
/* Thermal throttling initialization
|
|
|
|
*/
|
|
|
|
void iwl_tt_initialize(struct iwl_priv *priv)
|
|
|
|
{
|
|
|
|
struct iwl_tt_mgmt *tt = &priv->power_data.tt;
|
|
|
|
struct iwl_power_mgr *setting = &priv->power_data;
|
|
|
|
|
|
|
|
IWL_DEBUG_POWER(priv, "Initialize Thermal Throttling \n");
|
|
|
|
|
|
|
|
memset(tt, 0, sizeof(struct iwl_tt_mgmt));
|
|
|
|
|
|
|
|
tt->state = IWL_TI_0;
|
|
|
|
tt->sys_power_mode = setting->power_mode;
|
|
|
|
tt->tt_power_mode = tt->sys_power_mode;
|
|
|
|
init_timer(&priv->power_data.ct_kill_exit_tm);
|
|
|
|
priv->power_data.ct_kill_exit_tm.data = (unsigned long)priv;
|
|
|
|
priv->power_data.ct_kill_exit_tm.function = iwl_tt_check_exit_ct_kill;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_tt_initialize);
|
|
|
|
|
|
|
|
/* cleanup thermal throttling management related memory and timer */
|
|
|
|
void iwl_tt_exit(struct iwl_priv *priv)
|
|
|
|
{
|
|
|
|
/* stop ct_kill_exit_tm timer if activated */
|
|
|
|
del_timer_sync(&priv->power_data.ct_kill_exit_tm);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_tt_exit);
|
|
|
|
|
2008-10-24 14:48:56 +08:00
|
|
|
/* initialize to default */
|
2008-04-22 06:41:51 +08:00
|
|
|
void iwl_power_initialize(struct iwl_priv *priv)
|
|
|
|
{
|
|
|
|
iwl_power_init_handle(priv);
|
2009-05-09 04:44:38 +08:00
|
|
|
priv->power_data.user_power_setting = IWL_POWER_INDEX_1;
|
|
|
|
/* default to disabled until mac80211 says otherwise */
|
|
|
|
priv->power_data.power_disabled = 1;
|
2008-04-22 06:41:51 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(iwl_power_initialize);
|