]> Pileus Git - ~andy/linux/blobdiff - drivers/net/wireless/iwlwifi/iwl-power.c
iwlwifi: Thermal Throttling Management - part 2
[~andy/linux] / drivers / net / wireless / iwlwifi / iwl-power.c
index d7fdb5825450917c669f15d684aaad9515f32a24..00937b3ee8ecbb3302a81d9a9b280d0fc80cbba9 100644 (file)
@@ -98,6 +98,45 @@ static const struct iwl_power_vec_entry range_2[IWL_POWER_NUM] = {
        {{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(4, 7, 10, 10, 0xFF)}, 0}
 };
 
+/* default Thermal Throttling transaction table
+ * Current state   |         Throttling Down               |  Throttling Up
+ *=============================================================================
+ *                 Condition Nxt State  Condition Nxt State Condition Nxt State
+ *-----------------------------------------------------------------------------
+ *     IWL_TI_0     T >= 115   CT_KILL  115>T>=105   TI_1      N/A      N/A
+ *     IWL_TI_1     T >= 115   CT_KILL  115>T>=110   TI_2     T<=95     TI_0
+ *     IWL_TI_2     T >= 115   CT_KILL                        T<=100    TI_1
+ *    IWL_CT_KILL      N/A       N/A       N/A        N/A     T<=95     TI_0
+ *=============================================================================
+ */
+static const struct iwl_tt_trans tt_range_0[IWL_TI_STATE_MAX - 1] = {
+       {IWL_TI_0, IWL_ABSOLUTE_ZERO, 104},
+       {IWL_TI_1, 105, CT_KILL_THRESHOLD},
+       {IWL_TI_CT_KILL, CT_KILL_THRESHOLD + 1, IWL_ABSOLUTE_MAX}
+};
+static const struct iwl_tt_trans tt_range_1[IWL_TI_STATE_MAX - 1] = {
+       {IWL_TI_0, IWL_ABSOLUTE_ZERO, 95},
+       {IWL_TI_2, 110, CT_KILL_THRESHOLD},
+       {IWL_TI_CT_KILL, CT_KILL_THRESHOLD + 1, IWL_ABSOLUTE_MAX}
+};
+static const struct iwl_tt_trans tt_range_2[IWL_TI_STATE_MAX - 1] = {
+       {IWL_TI_1, IWL_ABSOLUTE_ZERO, 100},
+       {IWL_TI_CT_KILL, CT_KILL_THRESHOLD + 1, IWL_ABSOLUTE_MAX},
+       {IWL_TI_CT_KILL, CT_KILL_THRESHOLD + 1, IWL_ABSOLUTE_MAX}
+};
+static const struct iwl_tt_trans tt_range_3[IWL_TI_STATE_MAX - 1] = {
+       {IWL_TI_0, IWL_ABSOLUTE_ZERO, CT_KILL_EXIT_THRESHOLD},
+       {IWL_TI_CT_KILL, CT_KILL_EXIT_THRESHOLD + 1, IWL_ABSOLUTE_MAX},
+       {IWL_TI_CT_KILL, CT_KILL_EXIT_THRESHOLD + 1, IWL_ABSOLUTE_MAX}
+};
+
+/* Advance Thermal Throttling default restriction table */
+static const struct iwl_tt_restriction restriction_range[IWL_TI_STATE_MAX] = {
+       {IWL_TX_MULTI, true, IWL_RX_MULTI},
+       {IWL_TX_SINGLE, true, IWL_RX_MULTI},
+       {IWL_TX_SINGLE, false, IWL_RX_SINGLE},
+       {IWL_TX_NONE, false, IWL_RX_NONE}
+};
 
 /* set card power command */
 static int iwl_set_power(struct iwl_priv *priv, void *cmd)
@@ -273,6 +312,42 @@ int iwl_power_set_user_mode(struct iwl_priv *priv, u16 mode)
 }
 EXPORT_SYMBOL(iwl_power_set_user_mode);
 
+bool iwl_ht_enabled(struct iwl_priv *priv)
+{
+       struct iwl_tt_mgmt *tt = &priv->power_data.tt;
+       struct iwl_tt_restriction *restriction;
+
+       if (!priv->power_data.adv_tt)
+               return true;
+       restriction = tt->restriction + tt->state;
+       return restriction->is_ht;
+}
+EXPORT_SYMBOL(iwl_ht_enabled);
+
+u8 iwl_tx_ant_restriction(struct iwl_priv *priv)
+{
+       struct iwl_tt_mgmt *tt = &priv->power_data.tt;
+       struct iwl_tt_restriction *restriction;
+
+       if (!priv->power_data.adv_tt)
+               return IWL_TX_MULTI;
+       restriction = tt->restriction + tt->state;
+       return restriction->tx_stream;
+}
+EXPORT_SYMBOL(iwl_tx_ant_restriction);
+
+u8 iwl_rx_ant_restriction(struct iwl_priv *priv)
+{
+       struct iwl_tt_mgmt *tt = &priv->power_data.tt;
+       struct iwl_tt_restriction *restriction;
+
+       if (!priv->power_data.adv_tt)
+               return IWL_RX_MULTI;
+       restriction = tt->restriction + tt->state;
+       return restriction->rx_stream;
+}
+EXPORT_SYMBOL(iwl_rx_ant_restriction);
+
 #define CT_KILL_EXIT_DURATION (5)      /* 5 seconds duration */
 
 /*
@@ -427,12 +502,147 @@ static void iwl_legacy_tt_handler(struct iwl_priv *priv, s32 temp)
        }
 }
 
+/*
+ * Advance 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
+ *     Actions include relaxing the power down sleep thresholds and
+ *     decreasing the number of TX streams
+ * 3) Avoid throughput performance impact as much as possible
+ *
+ *=============================================================================
+ *                 Condition Nxt State  Condition Nxt State Condition Nxt State
+ *-----------------------------------------------------------------------------
+ *     IWL_TI_0     T >= 115   CT_KILL  115>T>=105   TI_1      N/A      N/A
+ *     IWL_TI_1     T >= 115   CT_KILL  115>T>=110   TI_2     T<=95     TI_0
+ *     IWL_TI_2     T >= 115   CT_KILL                        T<=100    TI_1
+ *    IWL_CT_KILL      N/A       N/A       N/A        N/A     T<=95     TI_0
+ *=============================================================================
+ */
+static void iwl_advance_tt_handler(struct iwl_priv *priv, s32 temp)
+{
+       struct iwl_tt_mgmt *tt = &priv->power_data.tt;
+       int i;
+       bool changed = false;
+       enum iwl_tt_state old_state;
+       struct iwl_tt_trans *transaction;
+
+       old_state = tt->state;
+       for (i = 0; i < IWL_TI_STATE_MAX - 1; i++) {
+               /* based on the current TT state,
+                * find the curresponding transaction table
+                * each table has (IWL_TI_STATE_MAX - 1) entries
+                * tt->transaction + ((old_state * (IWL_TI_STATE_MAX - 1))
+                * will advance to the correct table.
+                * then based on the current temperature
+                * find the next state need to transaction to
+                * go through all the possible (IWL_TI_STATE_MAX - 1) entries
+                * in the current table to see if transaction is needed
+                */
+               transaction = tt->transaction +
+                       ((old_state * (IWL_TI_STATE_MAX - 1)) + i);
+               if (temp >= transaction->tt_low &&
+                   temp <= transaction->tt_high) {
+#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));
+                       }
+                       tt->tt_previous_temp = temp;
+#endif
+                       if (old_state !=
+                           transaction->next_state) {
+                               changed = true;
+                               tt->state =
+                                       transaction->next_state;
+                       }
+                       break;
+               }
+       }
+       if (changed) {
+               struct iwl_rxon_cmd *rxon = &priv->staging_rxon;
+               struct iwl_power_mgr *setting = &priv->power_data;
+
+               if (tt->state >= IWL_TI_1) {
+                       /* if switching from IWL_TI_0 to other TT state
+                        * save previous power setting in tt->sys_power_mode */
+                       if (old_state == IWL_TI_0)
+                               tt->sys_power_mode = setting->power_mode;
+                       /* force PI = IWL_POWER_INDEX_5 in the case of TI > 0 */
+                       tt->tt_power_mode = IWL_POWER_INDEX_5;
+                       if (!iwl_ht_enabled(priv))
+                               /* disable HT */
+                               rxon->flags &= ~(RXON_FLG_CHANNEL_MODE_MSK |
+                                       RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK |
+                                       RXON_FLG_FAT_PROT_MSK |
+                                       RXON_FLG_HT_PROT_MSK);
+                       else {
+                               /* check HT capability and set
+                                * according to the system HT capability
+                                * in case get disabled before */
+                               iwl_set_rxon_ht(priv, &priv->current_ht_config);
+                       }
+
+               } else {
+                       /* restore system power setting */
+                       /* the previous power mode was saved in
+                        * tt->sys_power_mode when system move into
+                        * Thermal Throttling state
+                        * set power_data.user_power_setting to the previous
+                        * system power mode to make sure power will get
+                        * updated correctly
+                        */
+                       priv->power_data.user_power_setting =
+                               tt->sys_power_mode;
+                       tt->tt_power_mode = tt->sys_power_mode;
+                       /* check HT capability and set
+                        * according to the system HT capability
+                        * in case get disabled before */
+                       iwl_set_rxon_ht(priv, &priv->current_ht_config);
+               }
+               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");
+                       tt->state = old_state;
+               } else {
+                       IWL_DEBUG_POWER(priv,
+                                       "Thermal Throttling to new state: %u\n",
+                                       tt->state);
+                       if (old_state != IWL_TI_CT_KILL &&
+                           tt->state == IWL_TI_CT_KILL) {
+                               IWL_DEBUG_POWER(priv, "Enter IWL_TI_CT_KILL\n");
+                               iwl_perform_ct_kill_task(priv, true);
+
+                       } else if (old_state == IWL_TI_CT_KILL &&
+                                 tt->state != IWL_TI_CT_KILL) {
+                               IWL_DEBUG_POWER(priv, "Exit IWL_TI_CT_KILL\n");
+                               iwl_perform_ct_kill_task(priv, false);
+                       }
+               }
+       }
+}
+
 /* 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
+ * for Legacy mode
  * so just pass the CT_KILL temperature to iwl_legacy_tt_handler()
+ * for advance mode
+ * pass CT_KILL_THRESHOLD+1 to make sure move into IWL_TI_CT_KILL state
  */
 void iwl_tt_enter_ct_kill(struct iwl_priv *priv)
 {
@@ -444,7 +654,12 @@ void iwl_tt_enter_ct_kill(struct iwl_priv *priv)
        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);
+               if (!priv->power_data.adv_tt)
+                       iwl_legacy_tt_handler(priv,
+                                             IWL_MINIMAL_POWER_THRESHOLD);
+               else
+                       iwl_advance_tt_handler(priv,
+                                              CT_KILL_THRESHOLD + 1);
        }
 }
 EXPORT_SYMBOL(iwl_tt_enter_ct_kill);
@@ -468,8 +683,11 @@ void iwl_tt_exit_ct_kill(struct iwl_priv *priv)
                IWL_ERR(priv,
                        "Device temperature below critical"
                        "- ucode awake!\n");
-               iwl_legacy_tt_handler(priv,
-                       IWL_REDUCED_PERFORMANCE_THRESHOLD_2);
+               if (!priv->power_data.adv_tt)
+                       iwl_legacy_tt_handler(priv,
+                                       IWL_REDUCED_PERFORMANCE_THRESHOLD_2);
+               else
+                       iwl_advance_tt_handler(priv, CT_KILL_EXIT_THRESHOLD);
        }
 }
 EXPORT_SYMBOL(iwl_tt_exit_ct_kill);
@@ -484,16 +702,24 @@ void iwl_tt_handler(struct iwl_priv *priv)
        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);
+       if (!priv->power_data.adv_tt)
+               iwl_legacy_tt_handler(priv, temp);
+       else
+               iwl_advance_tt_handler(priv, temp);
 }
 EXPORT_SYMBOL(iwl_tt_handler);
 
 /* Thermal throttling initialization
+ * For advance thermal throttling:
+ *     Initialize Thermal Index and temperature threshold table
+ *     Initialize thermal throttling restriction table
  */
 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;
+       int size = sizeof(struct iwl_tt_trans) * (IWL_TI_STATE_MAX - 1);
+       struct iwl_tt_trans *transaction;
 
        IWL_DEBUG_POWER(priv, "Initialize Thermal Throttling \n");
 
@@ -505,14 +731,65 @@ void iwl_tt_initialize(struct iwl_priv *priv)
        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;
+       switch (priv->hw_rev & CSR_HW_REV_TYPE_MSK) {
+       case CSR_HW_REV_TYPE_6x00:
+       case CSR_HW_REV_TYPE_6x50:
+               IWL_DEBUG_POWER(priv, "Advanced Thermal Throttling\n");
+               tt->restriction = kzalloc(sizeof(struct iwl_tt_restriction) *
+                                        IWL_TI_STATE_MAX, GFP_KERNEL);
+               tt->transaction = kzalloc(sizeof(struct iwl_tt_trans) *
+                       IWL_TI_STATE_MAX * (IWL_TI_STATE_MAX - 1),
+                       GFP_KERNEL);
+               if (!tt->restriction || !tt->transaction) {
+                       IWL_ERR(priv, "Fallback to Legacy Throttling\n");
+                       priv->power_data.adv_tt = false;
+                       kfree(tt->restriction);
+                       tt->restriction = NULL;
+                       kfree(tt->transaction);
+                       tt->transaction = NULL;
+               } else {
+                       transaction = tt->transaction +
+                               (IWL_TI_0 * (IWL_TI_STATE_MAX - 1));
+                       memcpy(transaction, &tt_range_0[0], size);
+                       transaction = tt->transaction +
+                               (IWL_TI_1 * (IWL_TI_STATE_MAX - 1));
+                       memcpy(transaction, &tt_range_1[0], size);
+                       transaction = tt->transaction +
+                               (IWL_TI_2 * (IWL_TI_STATE_MAX - 1));
+                       memcpy(transaction, &tt_range_2[0], size);
+                       transaction = tt->transaction +
+                               (IWL_TI_CT_KILL * (IWL_TI_STATE_MAX - 1));
+                       memcpy(transaction, &tt_range_3[0], size);
+                       size = sizeof(struct iwl_tt_restriction) *
+                               IWL_TI_STATE_MAX;
+                       memcpy(tt->restriction,
+                               &restriction_range[0], size);
+                       priv->power_data.adv_tt = true;
+               }
+               break;
+       default:
+               IWL_DEBUG_POWER(priv, "Legacy Thermal Throttling\n");
+               priv->power_data.adv_tt = false;
+               break;
+       }
 }
 EXPORT_SYMBOL(iwl_tt_initialize);
 
 /* cleanup thermal throttling management related memory and timer */
 void iwl_tt_exit(struct iwl_priv *priv)
 {
+       struct iwl_tt_mgmt *tt = &priv->power_data.tt;
+
        /* stop ct_kill_exit_tm timer if activated */
        del_timer_sync(&priv->power_data.ct_kill_exit_tm);
+
+       if (priv->power_data.adv_tt) {
+               /* free advance thermal throttling memory */
+               kfree(tt->restriction);
+               tt->restriction = NULL;
+               kfree(tt->transaction);
+               tt->transaction = NULL;
+       }
 }
 EXPORT_SYMBOL(iwl_tt_exit);