|
@@ -36,6 +36,7 @@
|
|
#include "iwl-eeprom.h"
|
|
#include "iwl-eeprom.h"
|
|
#include "iwl-dev.h"
|
|
#include "iwl-dev.h"
|
|
#include "iwl-core.h"
|
|
#include "iwl-core.h"
|
|
|
|
+#include "iwl-io.h"
|
|
#include "iwl-commands.h"
|
|
#include "iwl-commands.h"
|
|
#include "iwl-debug.h"
|
|
#include "iwl-debug.h"
|
|
#include "iwl-power.h"
|
|
#include "iwl-power.h"
|
|
@@ -211,6 +212,7 @@ int iwl_power_update_mode(struct iwl_priv *priv, bool force)
|
|
{
|
|
{
|
|
struct iwl_power_mgr *setting = &(priv->power_data);
|
|
struct iwl_power_mgr *setting = &(priv->power_data);
|
|
int ret = 0;
|
|
int ret = 0;
|
|
|
|
+ struct iwl_tt_mgmt *tt = &priv->power_data.tt;
|
|
u16 uninitialized_var(final_mode);
|
|
u16 uninitialized_var(final_mode);
|
|
bool update_chains;
|
|
bool update_chains;
|
|
|
|
|
|
@@ -223,6 +225,10 @@ int iwl_power_update_mode(struct iwl_priv *priv, bool force)
|
|
if (setting->power_disabled)
|
|
if (setting->power_disabled)
|
|
final_mode = IWL_POWER_MODE_CAM;
|
|
final_mode = IWL_POWER_MODE_CAM;
|
|
|
|
|
|
|
|
+ if (tt->state >= IWL_TI_1) {
|
|
|
|
+ /* TT power setting overwrite user & system power setting */
|
|
|
|
+ final_mode = tt->tt_power_mode;
|
|
|
|
+ }
|
|
if (iwl_is_ready_rf(priv) &&
|
|
if (iwl_is_ready_rf(priv) &&
|
|
((setting->power_mode != final_mode) || force)) {
|
|
((setting->power_mode != final_mode) || force)) {
|
|
struct iwl_powertable_cmd cmd;
|
|
struct iwl_powertable_cmd cmd;
|
|
@@ -267,6 +273,249 @@ int iwl_power_set_user_mode(struct iwl_priv *priv, u16 mode)
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(iwl_power_set_user_mode);
|
|
EXPORT_SYMBOL(iwl_power_set_user_mode);
|
|
|
|
|
|
|
|
+#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);
|
|
|
|
+
|
|
/* initialize to default */
|
|
/* initialize to default */
|
|
void iwl_power_initialize(struct iwl_priv *priv)
|
|
void iwl_power_initialize(struct iwl_priv *priv)
|
|
{
|
|
{
|