iwlwifi: mvm: add thermal throttling and CT kill
authorEytan Lifshitz <eytan.lifshitz@intel.com>
Sun, 19 May 2013 16:14:41 +0000 (19:14 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 29 May 2013 07:03:18 +0000 (09:03 +0200)
In order to avoid NIC destruction due to high temperature,
CT kill will power down the NIC.

To avoid this, thermal throttling will decrease throughput
to prevent the NIC from reaching the temperature at which
CT kill is performed.

Signed-off-by: Eytan Lifshitz <eytan.lifshitz@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
17 files changed:
drivers/net/wireless/iwlwifi/iwl-csr.h
drivers/net/wireless/iwlwifi/iwl-prph.h
drivers/net/wireless/iwlwifi/mvm/Makefile
drivers/net/wireless/iwlwifi/mvm/bt-coex.c
drivers/net/wireless/iwlwifi/mvm/fw-api.h
drivers/net/wireless/iwlwifi/mvm/fw.c
drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c
drivers/net/wireless/iwlwifi/mvm/mac80211.c
drivers/net/wireless/iwlwifi/mvm/mvm.h
drivers/net/wireless/iwlwifi/mvm/ops.c
drivers/net/wireless/iwlwifi/mvm/rs.c
drivers/net/wireless/iwlwifi/mvm/rs.h
drivers/net/wireless/iwlwifi/mvm/rx.c
drivers/net/wireless/iwlwifi/mvm/sta.c
drivers/net/wireless/iwlwifi/mvm/sta.h
drivers/net/wireless/iwlwifi/mvm/tt.c [new file with mode: 0644]
drivers/net/wireless/iwlwifi/mvm/utils.c

index 20e845d4da04304b0493b3d8c6fa3f29b333a427..a276af476e2d7ab64e859748c9b8fd48b84ebb80 100644 (file)
 #define IWL_HOST_INT_CALIB_TIMEOUT_DEF (0x10)
 #define IWL_HOST_INT_CALIB_TIMEOUT_MIN (0x0)
 
+/*****************************************************************************
+ *                        7000/3000 series SHR DTS addresses                 *
+ *****************************************************************************/
+
+/* Diode Results Register Structure: */
+enum dtd_diode_reg {
+       DTS_DIODE_REG_DIG_VAL                   = 0x000000FF, /* bits [7:0] */
+       DTS_DIODE_REG_VREF_LOW                  = 0x0000FF00, /* bits [15:8] */
+       DTS_DIODE_REG_VREF_HIGH                 = 0x00FF0000, /* bits [23:16] */
+       DTS_DIODE_REG_VREF_ID                   = 0x03000000, /* bits [25:24] */
+       DTS_DIODE_REG_PASS_ONCE                 = 0x80000000, /* bits [31:31] */
+       DTS_DIODE_REG_FLAGS_MSK                 = 0xFF000000, /* bits [31:24] */
+/* Those are the masks INSIDE the flags bit-field: */
+       DTS_DIODE_REG_FLAGS_VREFS_ID_POS        = 0,
+       DTS_DIODE_REG_FLAGS_VREFS_ID            = 0x00000003, /* bits [1:0] */
+       DTS_DIODE_REG_FLAGS_PASS_ONCE_POS       = 7,
+       DTS_DIODE_REG_FLAGS_PASS_ONCE           = 0x00000080, /* bits [7:7] */
+};
+
 #endif /* !__iwl_csr_h__ */
index 386f2a7c87cb731b39aec7dd03c53f03e5e17725..ff8cc75c189d4d842abf8611fb8c5e7c7a63bf38 100644 (file)
 /* Device system time */
 #define DEVICE_SYSTEM_TIME_REG 0xA0206C
 
+/*****************************************************************************
+ *                        7000/3000 series SHR DTS addresses                 *
+ *****************************************************************************/
+
+#define SHR_MISC_WFM_DTS_EN    (0x00a10024)
+#define DTSC_CFG_MODE          (0x00a10604)
+#define DTSC_VREF_AVG          (0x00a10648)
+#define DTSC_VREF5_AVG         (0x00a1064c)
+#define DTSC_CFG_MODE_PERIODIC (0x2)
+#define DTSC_PTAT_AVG          (0x00a10650)
+
+
 /**
  * Tx Scheduler
  *
index 2acc44b40986868437a92be40adcb30ecaa1a6fd..ff856e543ae85689549fbd1fcae64320a2ca67fa 100644 (file)
@@ -3,7 +3,7 @@ iwlmvm-y += fw.o mac80211.o nvm.o ops.o phy-ctxt.o mac-ctxt.o
 iwlmvm-y += utils.o rx.o tx.o binding.o quota.o sta.o
 iwlmvm-y += scan.o time-event.o rs.o
 iwlmvm-y += power.o bt-coex.o
-iwlmvm-y += led.o
+iwlmvm-y += led.o tt.o
 iwlmvm-$(CONFIG_IWLWIFI_DEBUGFS) += debugfs.o
 iwlmvm-$(CONFIG_PM_SLEEP) += d3.o
 
index 810bfa5f6de0a906058422be4a7d26d6329a5698..f03655f303aab4e87fe3bd8d4658939c23ee7838 100644 (file)
@@ -351,6 +351,7 @@ static void iwl_mvm_bt_notif_iterator(void *_data, u8 *mac,
        enum ieee80211_band band;
        int ave_rssi;
 
+       lockdep_assert_held(&mvm->mutex);
        if (vif->type != NL80211_IFTYPE_STATION)
                return;
 
@@ -365,7 +366,8 @@ static void iwl_mvm_bt_notif_iterator(void *_data, u8 *mac,
        smps_mode = IEEE80211_SMPS_AUTOMATIC;
 
        if (band != IEEE80211_BAND_2GHZ) {
-               ieee80211_request_smps(vif, smps_mode);
+               iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_BT_COEX,
+                                   smps_mode);
                return;
        }
 
@@ -380,7 +382,7 @@ static void iwl_mvm_bt_notif_iterator(void *_data, u8 *mac,
                       mvmvif->id,  data->notif->bt_status,
                       data->notif->bt_traffic_load, smps_mode);
 
-       ieee80211_request_smps(vif, smps_mode);
+       iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_BT_COEX, smps_mode);
 
        /* don't reduce the Tx power if in loose scheme */
        if (is_loose_coex())
index 733153c3b66c040cd2843163c95898f2b2ccf940..db6f47406ac2acb0cfd2996b3811995147a98a3d 100644 (file)
@@ -139,6 +139,9 @@ enum {
        /* Power */
        POWER_TABLE_CMD = 0x77,
 
+       /* Thermal Throttling*/
+       REPLY_THERMAL_MNG_BACKOFF = 0x7e,
+
        /* Scanning */
        SCAN_REQUEST_CMD = 0x80,
        SCAN_ABORT_CMD = 0x81,
@@ -977,4 +980,212 @@ struct iwl_mcast_filter_cmd {
        u8 addr_list[0];
 } __packed; /* MCAST_FILTERING_CMD_API_S_VER_1 */
 
+struct mvm_statistics_dbg {
+       __le32 burst_check;
+       __le32 burst_count;
+       __le32 wait_for_silence_timeout_cnt;
+       __le32 reserved[3];
+} __packed; /* STATISTICS_DEBUG_API_S_VER_2 */
+
+struct mvm_statistics_div {
+       __le32 tx_on_a;
+       __le32 tx_on_b;
+       __le32 exec_time;
+       __le32 probe_time;
+       __le32 rssi_ant;
+       __le32 reserved2;
+} __packed; /* STATISTICS_SLOW_DIV_API_S_VER_2 */
+
+struct mvm_statistics_general_common {
+       __le32 temperature;   /* radio temperature */
+       __le32 temperature_m; /* radio voltage */
+       struct mvm_statistics_dbg dbg;
+       __le32 sleep_time;
+       __le32 slots_out;
+       __le32 slots_idle;
+       __le32 ttl_timestamp;
+       struct mvm_statistics_div div;
+       __le32 rx_enable_counter;
+       /*
+        * num_of_sos_states:
+        *  count the number of times we have to re-tune
+        *  in order to get out of bad PHY status
+        */
+       __le32 num_of_sos_states;
+} __packed; /* STATISTICS_GENERAL_API_S_VER_5 */
+
+struct mvm_statistics_rx_non_phy {
+       __le32 bogus_cts;       /* CTS received when not expecting CTS */
+       __le32 bogus_ack;       /* ACK received when not expecting ACK */
+       __le32 non_bssid_frames;        /* number of frames with BSSID that
+                                        * doesn't belong to the STA BSSID */
+       __le32 filtered_frames; /* count frames that were dumped in the
+                                * filtering process */
+       __le32 non_channel_beacons;     /* beacons with our bss id but not on
+                                        * our serving channel */
+       __le32 channel_beacons; /* beacons with our bss id and in our
+                                * serving channel */
+       __le32 num_missed_bcon; /* number of missed beacons */
+       __le32 adc_rx_saturation_time;  /* count in 0.8us units the time the
+                                        * ADC was in saturation */
+       __le32 ina_detection_search_time;/* total time (in 0.8us) searched
+                                         * for INA */
+       __le32 beacon_silence_rssi_a;   /* RSSI silence after beacon frame */
+       __le32 beacon_silence_rssi_b;   /* RSSI silence after beacon frame */
+       __le32 beacon_silence_rssi_c;   /* RSSI silence after beacon frame */
+       __le32 interference_data_flag;  /* flag for interference data
+                                        * availability. 1 when data is
+                                        * available. */
+       __le32 channel_load;            /* counts RX Enable time in uSec */
+       __le32 dsp_false_alarms;        /* DSP false alarm (both OFDM
+                                        * and CCK) counter */
+       __le32 beacon_rssi_a;
+       __le32 beacon_rssi_b;
+       __le32 beacon_rssi_c;
+       __le32 beacon_energy_a;
+       __le32 beacon_energy_b;
+       __le32 beacon_energy_c;
+       __le32 num_bt_kills;
+       __le32 mac_id;
+       __le32 directed_data_mpdu;
+} __packed; /* STATISTICS_RX_NON_PHY_API_S_VER_3 */
+
+struct mvm_statistics_rx_phy {
+       __le32 ina_cnt;
+       __le32 fina_cnt;
+       __le32 plcp_err;
+       __le32 crc32_err;
+       __le32 overrun_err;
+       __le32 early_overrun_err;
+       __le32 crc32_good;
+       __le32 false_alarm_cnt;
+       __le32 fina_sync_err_cnt;
+       __le32 sfd_timeout;
+       __le32 fina_timeout;
+       __le32 unresponded_rts;
+       __le32 rxe_frame_limit_overrun;
+       __le32 sent_ack_cnt;
+       __le32 sent_cts_cnt;
+       __le32 sent_ba_rsp_cnt;
+       __le32 dsp_self_kill;
+       __le32 mh_format_err;
+       __le32 re_acq_main_rssi_sum;
+       __le32 reserved;
+} __packed; /* STATISTICS_RX_PHY_API_S_VER_2 */
+
+struct mvm_statistics_rx_ht_phy {
+       __le32 plcp_err;
+       __le32 overrun_err;
+       __le32 early_overrun_err;
+       __le32 crc32_good;
+       __le32 crc32_err;
+       __le32 mh_format_err;
+       __le32 agg_crc32_good;
+       __le32 agg_mpdu_cnt;
+       __le32 agg_cnt;
+       __le32 unsupport_mcs;
+} __packed;  /* STATISTICS_HT_RX_PHY_API_S_VER_1 */
+
+#define MAX_CHAINS 3
+
+struct mvm_statistics_tx_non_phy_agg {
+       __le32 ba_timeout;
+       __le32 ba_reschedule_frames;
+       __le32 scd_query_agg_frame_cnt;
+       __le32 scd_query_no_agg;
+       __le32 scd_query_agg;
+       __le32 scd_query_mismatch;
+       __le32 frame_not_ready;
+       __le32 underrun;
+       __le32 bt_prio_kill;
+       __le32 rx_ba_rsp_cnt;
+       __s8 txpower[MAX_CHAINS];
+       __s8 reserved;
+       __le32 reserved2;
+} __packed; /* STATISTICS_TX_NON_PHY_AGG_API_S_VER_1 */
+
+struct mvm_statistics_tx_channel_width {
+       __le32 ext_cca_narrow_ch20[1];
+       __le32 ext_cca_narrow_ch40[2];
+       __le32 ext_cca_narrow_ch80[3];
+       __le32 ext_cca_narrow_ch160[4];
+       __le32 last_tx_ch_width_indx;
+       __le32 rx_detected_per_ch_width[4];
+       __le32 success_per_ch_width[4];
+       __le32 fail_per_ch_width[4];
+}; /* STATISTICS_TX_CHANNEL_WIDTH_API_S_VER_1 */
+
+struct mvm_statistics_tx {
+       __le32 preamble_cnt;
+       __le32 rx_detected_cnt;
+       __le32 bt_prio_defer_cnt;
+       __le32 bt_prio_kill_cnt;
+       __le32 few_bytes_cnt;
+       __le32 cts_timeout;
+       __le32 ack_timeout;
+       __le32 expected_ack_cnt;
+       __le32 actual_ack_cnt;
+       __le32 dump_msdu_cnt;
+       __le32 burst_abort_next_frame_mismatch_cnt;
+       __le32 burst_abort_missing_next_frame_cnt;
+       __le32 cts_timeout_collision;
+       __le32 ack_or_ba_timeout_collision;
+       struct mvm_statistics_tx_non_phy_agg agg;
+       struct mvm_statistics_tx_channel_width channel_width;
+} __packed; /* STATISTICS_TX_API_S_VER_4 */
+
+
+struct mvm_statistics_bt_activity {
+       __le32 hi_priority_tx_req_cnt;
+       __le32 hi_priority_tx_denied_cnt;
+       __le32 lo_priority_tx_req_cnt;
+       __le32 lo_priority_tx_denied_cnt;
+       __le32 hi_priority_rx_req_cnt;
+       __le32 hi_priority_rx_denied_cnt;
+       __le32 lo_priority_rx_req_cnt;
+       __le32 lo_priority_rx_denied_cnt;
+} __packed;  /* STATISTICS_BT_ACTIVITY_API_S_VER_1 */
+
+struct mvm_statistics_general {
+       struct mvm_statistics_general_common common;
+       __le32 beacon_filtered;
+       __le32 missed_beacons;
+       __s8 beacon_filter_everage_energy;
+       __s8 beacon_filter_reason;
+       __s8 beacon_filter_current_energy;
+       __s8 beacon_filter_reserved;
+       __le32 beacon_filter_delta_time;
+       struct mvm_statistics_bt_activity bt_activity;
+} __packed; /* STATISTICS_GENERAL_API_S_VER_5 */
+
+struct mvm_statistics_rx {
+       struct mvm_statistics_rx_phy ofdm;
+       struct mvm_statistics_rx_phy cck;
+       struct mvm_statistics_rx_non_phy general;
+       struct mvm_statistics_rx_ht_phy ofdm_ht;
+} __packed; /* STATISTICS_RX_API_S_VER_3 */
+
+/*
+ * STATISTICS_NOTIFICATION = 0x9d (notification only, not a command)
+ *
+ * By default, uCode issues this notification after receiving a beacon
+ * while associated.  To disable this behavior, set DISABLE_NOTIF flag in the
+ * REPLY_STATISTICS_CMD 0x9c, above.
+ *
+ * Statistics counters continue to increment beacon after beacon, but are
+ * cleared when changing channels or when driver issues REPLY_STATISTICS_CMD
+ * 0x9c with CLEAR_STATS bit set (see above).
+ *
+ * uCode also issues this notification during scans.  uCode clears statistics
+ * appropriately so that each notification contains statistics for only the
+ * one channel that has just been scanned.
+ */
+
+struct iwl_notif_statistics { /* STATISTICS_NTFY_API_S_VER_8 */
+       __le32 flag;
+       struct mvm_statistics_rx rx;
+       struct mvm_statistics_tx tx;
+       struct mvm_statistics_general general;
+} __packed;
+
 #endif /* __fw_api_h__ */
index 20ee2812cc9b535b11d12c9bdcfca4fd4c8903ef..cd7c0032cc583073bd5acaa7ceebbd19457aa1c4 100644 (file)
@@ -330,7 +330,7 @@ int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm, bool read_nvm)
         * abort after reading the nvm in case RF Kill is on, we will complete
         * the init seq later when RF kill will switch to off
         */
-       if (test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status)) {
+       if (iwl_mvm_is_radio_killed(mvm)) {
                IWL_DEBUG_RF_KILL(mvm,
                                  "jump over all phy activities due to RF kill\n");
                iwl_remove_notification(&mvm->notif_wait, &calib_wait);
index b2cc3d98e0f7aa2e89b744b17cd706ed20268cc6..dc5f4ef50198765c232a48619a6fbe508e110843 100644 (file)
@@ -227,7 +227,7 @@ static int iwl_mvm_mac_ctxt_allocate_resources(struct iwl_mvm *mvm,
                .found_vif = false,
        };
        u32 ac;
-       int ret;
+       int ret, i;
 
        /*
         * Allocate a MAC ID and a TSF for this MAC, along with the queues
@@ -335,6 +335,9 @@ static int iwl_mvm_mac_ctxt_allocate_resources(struct iwl_mvm *mvm,
        mvmvif->bcast_sta.sta_id = IWL_MVM_STATION_COUNT;
        mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT;
 
+       for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++)
+               mvmvif->smps_requests[i] = IEEE80211_SMPS_AUTOMATIC;
+
        return 0;
 
 exit_fail:
index c9924c12e0fec5f0bf152595051cf4a733e134b0..c26f6b504460ef74320045f5280dc31339cb819d 100644 (file)
@@ -265,8 +265,8 @@ static void iwl_mvm_mac_tx(struct ieee80211_hw *hw,
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 
-       if (test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status)) {
-               IWL_DEBUG_DROP(mvm, "Dropping - RF KILL\n");
+       if (iwl_mvm_is_radio_killed(mvm)) {
+               IWL_DEBUG_DROP(mvm, "Dropping - RF/CT KILL\n");
                goto drop;
        }
 
index 4e87a321e107cbb9b4df17cf6eb3d694ea244907..a288552491b3d24fd514feb419e6135be51a678e 100644 (file)
@@ -150,6 +150,12 @@ enum iwl_power_scheme {
 
 #define IWL_CONN_MAX_LISTEN_INTERVAL   70
 
+enum iwl_mvm_smps_type_request {
+       IWL_MVM_SMPS_REQ_BT_COEX,
+       IWL_MVM_SMPS_REQ_TT,
+       NUM_IWL_MVM_SMPS_REQ,
+};
+
 /**
  * struct iwl_mvm_vif - data per Virtual Interface, it is a MAC context
  * @id: between 0 and 3
@@ -164,6 +170,8 @@ enum iwl_power_scheme {
  * @bcast_sta: station used for broadcast packets. Used by the following
  *  vifs: P2P_DEVICE, GO and AP.
  * @beacon_skb: the skb used to hold the AP/GO beacon template
+ * @smps_requests: the requests of of differents parts of the driver, regard
+       the desired smps mode.
  */
 struct iwl_mvm_vif {
        u16 id;
@@ -218,6 +226,8 @@ struct iwl_mvm_vif {
        struct dentry *dbgfs_slink;
        void *dbgfs_data;
 #endif
+
+       enum ieee80211_smps_mode smps_requests[NUM_IWL_MVM_SMPS_REQ];
 };
 
 static inline struct iwl_mvm_vif *
@@ -226,12 +236,6 @@ iwl_mvm_vif_from_mac80211(struct ieee80211_vif *vif)
        return (void *)vif->drv_priv;
 }
 
-enum iwl_mvm_status {
-       IWL_MVM_STATUS_HW_RFKILL,
-       IWL_MVM_STATUS_ROC_RUNNING,
-       IWL_MVM_STATUS_IN_HW_RESTART,
-};
-
 enum iwl_scan_status {
        IWL_MVM_SCAN_NONE,
        IWL_MVM_SCAN_OS,
@@ -249,6 +253,63 @@ struct iwl_nvm_section {
        const u8 *data;
 };
 
+/*
+ * Tx-backoff threshold
+ * @temperature: The threshold in Celsius
+ * @backoff: The tx-backoff in uSec
+ */
+struct iwl_tt_tx_backoff {
+       s32 temperature;
+       u32 backoff;
+};
+
+#define TT_TX_BACKOFF_SIZE 6
+
+/**
+ * struct iwl_tt_params - thermal throttling parameters
+ * @ct_kill_entry: CT Kill entry threshold
+ * @ct_kill_exit: CT Kill exit threshold
+ * @ct_kill_duration: The time  intervals (in uSec) in which the driver needs
+ *     to checks whether to exit CT Kill.
+ * @dynamic_smps_entry: Dynamic SMPS entry threshold
+ * @dynamic_smps_exit: Dynamic SMPS exit threshold
+ * @tx_protection_entry: TX protection entry threshold
+ * @tx_protection_exit: TX protection exit threshold
+ * @tx_backoff: Array of thresholds for tx-backoff , in ascending order.
+ * @support_ct_kill: Support CT Kill?
+ * @support_dynamic_smps: Support dynamic SMPS?
+ * @support_tx_protection: Support tx protection?
+ * @support_tx_backoff: Support tx-backoff?
+ */
+struct iwl_tt_params {
+       s32 ct_kill_entry;
+       s32 ct_kill_exit;
+       u32 ct_kill_duration;
+       s32 dynamic_smps_entry;
+       s32 dynamic_smps_exit;
+       s32 tx_protection_entry;
+       s32 tx_protection_exit;
+       struct iwl_tt_tx_backoff tx_backoff[TT_TX_BACKOFF_SIZE];
+       bool support_ct_kill;
+       bool support_dynamic_smps;
+       bool support_tx_protection;
+       bool support_tx_backoff;
+};
+
+/**
+ * struct iwl_mvm_tt_mgnt - Thermal Throttling Management structure
+ * @ct_kill_exit: worker to exit thermal kill
+ * @dynamic_smps: Is thermal throttling enabled dynamic_smps?
+ * @tx_backoff: The current thremal throttling tx backoff in uSec.
+ * @params: Parameters to configure the thermal throttling algorithm.
+ */
+struct iwl_mvm_tt_mgmt {
+       struct delayed_work ct_kill_exit;
+       bool dynamic_smps;
+       u32 tx_backoff;
+       const struct iwl_tt_params *params;
+};
+
 struct iwl_mvm {
        /* for logger access */
        struct device *dev;
@@ -356,6 +417,10 @@ struct iwl_mvm {
        /* BT-Coex */
        u8 bt_kill_msk;
        struct iwl_bt_coex_profile_notif last_bt_notif;
+
+       /* Thermal Throttling and CTkill */
+       struct iwl_mvm_tt_mgmt thermal_throttle;
+       s32 temperature;        /* Celsius */
 };
 
 /* Extract MVM priv from op_mode and _hw */
@@ -365,6 +430,19 @@ struct iwl_mvm {
 #define IWL_MAC80211_GET_MVM(_hw)                      \
        IWL_OP_MODE_GET_MVM((struct iwl_op_mode *)((_hw)->priv))
 
+enum iwl_mvm_status {
+       IWL_MVM_STATUS_HW_RFKILL,
+       IWL_MVM_STATUS_HW_CTKILL,
+       IWL_MVM_STATUS_ROC_RUNNING,
+       IWL_MVM_STATUS_IN_HW_RESTART,
+};
+
+static inline bool iwl_mvm_is_radio_killed(struct iwl_mvm *mvm)
+{
+       return test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status) ||
+              test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status);
+}
+
 extern const u8 iwl_mvm_ac_to_tx_fifo[];
 
 struct iwl_rate_info {
@@ -555,4 +633,15 @@ int iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm,
 int iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm,
                                  struct ieee80211_vif *vif);
 
+/* SMPS */
+void iwl_mvm_update_smps(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
+                               enum iwl_mvm_smps_type_request req_type,
+                               enum ieee80211_smps_mode smps_request);
+
+/* Thermal management and CT-kill */
+void iwl_mvm_tt_handler(struct iwl_mvm *mvm);
+void iwl_mvm_tt_initialize(struct iwl_mvm *mvm);
+void iwl_mvm_tt_exit(struct iwl_mvm *mvm);
+void iwl_mvm_set_hw_ctkill_state(struct iwl_mvm *mvm, bool state);
+
 #endif /* __IWL_MVM_H__ */
index 7998baca5a8b73707e873e87a514077be1883725..8431637e38f25eed2743ea5e4b5b3cb34c8d6df8 100644 (file)
@@ -222,6 +222,7 @@ static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = {
 
        RX_HANDLER(BT_PROFILE_NOTIFICATION, iwl_mvm_rx_bt_coex_notif, true),
        RX_HANDLER(BEACON_NOTIFICATION, iwl_mvm_rx_beacon_notif, false),
+       RX_HANDLER(STATISTICS_NOTIFICATION, iwl_mvm_rx_statistics, true),
 
        RX_HANDLER(RADIO_VERSION_NOTIFICATION, iwl_mvm_rx_radio_ver, false),
        RX_HANDLER(CARD_STATE_NOTIFICATION, iwl_mvm_rx_card_state_notif, false),
@@ -294,6 +295,7 @@ static const char *iwl_mvm_cmd_strings[REPLY_MAX] = {
        CMD(BT_CONFIG),
        CMD(MCAST_FILTER_CMD),
        CMD(REPLY_BEACON_FILTERING_CMD),
+       CMD(REPLY_THERMAL_MNG_BACKOFF),
 };
 #undef CMD
 
@@ -394,6 +396,8 @@ iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg,
        if (err)
                goto out_free;
 
+       iwl_mvm_tt_initialize(mvm);
+
        mutex_lock(&mvm->mutex);
        err = iwl_run_init_mvm_ucode(mvm, true);
        mutex_unlock(&mvm->mutex);
@@ -441,6 +445,8 @@ static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode)
 
        iwl_mvm_leds_exit(mvm);
 
+       iwl_mvm_tt_exit(mvm);
+
        ieee80211_unregister_hw(mvm->hw);
 
        kfree(mvm->scan_cmd);
@@ -595,6 +601,16 @@ static void iwl_mvm_wake_sw_queue(struct iwl_op_mode *op_mode, int queue)
        ieee80211_wake_queue(mvm->hw, mq);
 }
 
+void iwl_mvm_set_hw_ctkill_state(struct iwl_mvm *mvm, bool state)
+{
+       if (state)
+               set_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status);
+       else
+               clear_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status);
+
+       wiphy_rfkill_set_hw_state(mvm->hw->wiphy, iwl_mvm_is_radio_killed(mvm));
+}
+
 static void iwl_mvm_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state)
 {
        struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode);
@@ -604,7 +620,7 @@ static void iwl_mvm_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state)
        else
                clear_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status);
 
-       wiphy_rfkill_set_hw_state(mvm->hw->wiphy, state);
+       wiphy_rfkill_set_hw_state(mvm->hw->wiphy, iwl_mvm_is_radio_killed(mvm));
 }
 
 static void iwl_mvm_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb)
index 55334d542e263d1f4784e40d37ac9d48618e2eac..6a050c69e7d071b8ef33179fb6cad3efdcb2f645 100644 (file)
@@ -3080,3 +3080,29 @@ void iwl_mvm_rate_control_unregister(void)
 {
        ieee80211_rate_control_unregister(&rs_mvm_ops);
 }
+
+/**
+ * iwl_mvm_tx_protection - Gets LQ command, change it to enable/disable
+ * Tx protection, according to this rquest and previous requests,
+ * and send the LQ command.
+ * @lq: The LQ command
+ * @mvmsta: The station
+ * @enable: Enable Tx protection?
+ */
+int iwl_mvm_tx_protection(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq,
+                         struct iwl_mvm_sta *mvmsta, bool enable)
+{
+       lockdep_assert_held(&mvm->mutex);
+
+       if (enable) {
+               if (mvmsta->tx_protection == 0)
+                       lq->flags |= LQ_FLAG_SET_STA_TLC_RTS_MSK;
+               mvmsta->tx_protection++;
+       } else {
+               mvmsta->tx_protection--;
+               if (mvmsta->tx_protection == 0)
+                       lq->flags &= ~LQ_FLAG_SET_STA_TLC_RTS_MSK;
+       }
+
+       return iwl_mvm_send_lq_cmd(mvm, lq, CMD_ASYNC, false);
+}
index 219c6857cc0f3abe71390b58ebae41449a5535f5..f66155a57238e868890ea2041811fe291eed4d0d 100644 (file)
@@ -390,4 +390,9 @@ extern int iwl_mvm_rate_control_register(void);
  */
 extern void iwl_mvm_rate_control_unregister(void);
 
+struct iwl_mvm_sta;
+
+int iwl_mvm_tx_protection(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq,
+                         struct iwl_mvm_sta *mvmsta, bool enable);
+
 #endif /* __rs__ */
index 4dfc21a3e83ed3a1ab127baf1a7864a2fdc4cc9d..e4930d5027d228ff4e411ec2e8c6289e1c4366d2 100644 (file)
@@ -363,3 +363,25 @@ int iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb,
                                        rxb, &rx_status);
        return 0;
 }
+
+/*
+ * iwl_mvm_rx_statistics - STATISTICS_NOTIFICATION handler
+ *
+ * TODO: This handler is implemented partially.
+ * It only gets the NIC's temperature.
+ */
+int iwl_mvm_rx_statistics(struct iwl_mvm *mvm,
+                         struct iwl_rx_cmd_buffer *rxb,
+                         struct iwl_device_cmd *cmd)
+{
+       struct iwl_rx_packet *pkt = rxb_addr(rxb);
+       struct iwl_notif_statistics *stats = (void *)&pkt->data;
+       struct mvm_statistics_general_common *common = &stats->general.common;
+
+       if (mvm->temperature != le32_to_cpu(common->temperature)) {
+               mvm->temperature = le32_to_cpu(common->temperature);
+               iwl_mvm_tt_handler(mvm);
+       }
+
+       return 0;
+}
index 5c664ed54400ed65c5232c9092c62dceaeadfdcd..2278858d5658f14a4e9b4d5fc6ed9001e78c3a1c 100644 (file)
@@ -64,6 +64,7 @@
 
 #include "mvm.h"
 #include "sta.h"
+#include "rs.h"
 
 static int iwl_mvm_find_free_sta_id(struct iwl_mvm *mvm)
 {
@@ -217,6 +218,8 @@ int iwl_mvm_add_sta(struct iwl_mvm *mvm,
                                                      mvmvif->color);
        mvm_sta->vif = vif;
        mvm_sta->max_agg_bufsize = LINK_QUAL_AGG_FRAME_LIMIT_DEF;
+       mvm_sta->tx_protection = 0;
+       mvm_sta->tt_tx_protection = false;
 
        /* HW restart, don't assume the memory has been zeroed */
        atomic_set(&mvm->pending_frames[sta_id], 0);
@@ -798,21 +801,23 @@ int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
                min(mvmsta->max_agg_bufsize, buf_size);
        mvmsta->lq_sta.lq.agg_frame_cnt_limit = mvmsta->max_agg_bufsize;
 
+       IWL_DEBUG_HT(mvm, "Tx aggregation enabled on ra = %pM tid = %d\n",
+                    sta->addr, tid);
+
        if (mvm->cfg->ht_params->use_rts_for_aggregation) {
                /*
                 * switch to RTS/CTS if it is the prefer protection
                 * method for HT traffic
+                * this function also sends the LQ command
                 */
-               mvmsta->lq_sta.lq.flags |= LQ_FLAG_SET_STA_TLC_RTS_MSK;
+               return iwl_mvm_tx_protection(mvm, &mvmsta->lq_sta.lq,
+                                            mvmsta, true);
                /*
                 * TODO: remove the TLC_RTS flag when we tear down the last
                 * AGG session (agg_tids_count in DVM)
                 */
        }
 
-       IWL_DEBUG_HT(mvm, "Tx aggregation enabled on ra = %pM tid = %d\n",
-                    sta->addr, tid);
-
        return iwl_mvm_send_lq_cmd(mvm, &mvmsta->lq_sta.lq, CMD_ASYNC, false);
 }
 
index a4ddce77aaaefa1eff7599ea275a9663b6cee648..3efa0a0cc987eee536757477dd9fd7ac46dd7cf0 100644 (file)
@@ -275,6 +275,8 @@ struct iwl_mvm_tid_data {
  * @lock: lock to protect the whole struct. Since %tid_data is access from Tx
  * and from Tx response flow, it needs a spinlock.
  * @tid_data: per tid data. Look at %iwl_mvm_tid_data.
+ * @tx_protection: reference counter for controlling the Tx protection.
+ * @tt_tx_protection: is thermal throttling enable Tx protection?
  *
  * When mac80211 creates a station it reserves some space (hw->sta_data_size)
  * in the structure for use by driver. This structure is placed in that
@@ -296,6 +298,10 @@ struct iwl_mvm_sta {
 #ifdef CONFIG_PM_SLEEP
        u16 last_seq_ctl;
 #endif
+
+       /* Temporary, until the new TLC will control the Tx protection */
+       s8 tx_protection;
+       bool tt_tx_protection;
 };
 
 /**
diff --git a/drivers/net/wireless/iwlwifi/mvm/tt.c b/drivers/net/wireless/iwlwifi/mvm/tt.c
new file mode 100644 (file)
index 0000000..4665fc0
--- /dev/null
@@ -0,0 +1,509 @@
+/******************************************************************************
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2013 Intel Corporation. All rights reserved.
+ *
+ * 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 COPYING.
+ *
+ * Contact Information:
+ *  Intel Linux Wireless <ilw@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name Intel Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+
+#include "mvm.h"
+#include "iwl-config.h"
+#include "iwl-io.h"
+#include "iwl-csr.h"
+#include "iwl-prph.h"
+
+#define OTP_DTS_DIODE_DEVIATION 96 /*in words*/
+/* VBG - Voltage Band Gap error data (temperature offset) */
+#define OTP_WP_DTS_VBG                 (OTP_DTS_DIODE_DEVIATION + 2)
+#define MEAS_VBG_MIN_VAL               2300
+#define MEAS_VBG_MAX_VAL               3000
+#define MEAS_VBG_DEFAULT_VAL           2700
+#define DTS_DIODE_VALID(flags)         (flags & DTS_DIODE_REG_FLAGS_PASS_ONCE)
+#define MIN_TEMPERATURE                        0
+#define MAX_TEMPERATURE                        125
+#define TEMPERATURE_ERROR              (MAX_TEMPERATURE + 1)
+#define PTAT_DIGITAL_VALUE_MIN_VALUE   0
+#define PTAT_DIGITAL_VALUE_MAX_VALUE   0xFF
+#define DTS_VREFS_NUM                  5
+static inline u32 DTS_DIODE_GET_VREFS_ID(u32 flags)
+{
+       return (flags & DTS_DIODE_REG_FLAGS_VREFS_ID) >>
+                                       DTS_DIODE_REG_FLAGS_VREFS_ID_POS;
+}
+
+#define CALC_VREFS_MIN_DIFF    43
+#define CALC_VREFS_MAX_DIFF    51
+#define CALC_LUT_SIZE          (1 + CALC_VREFS_MAX_DIFF - CALC_VREFS_MIN_DIFF)
+#define CALC_LUT_INDEX_OFFSET  CALC_VREFS_MIN_DIFF
+#define CALC_TEMPERATURE_RESULT_SHIFT_OFFSET   23
+
+/*
+ * @digital_value: The diode's digital-value sampled (temperature/voltage)
+ * @vref_low: The lower voltage-reference (the vref just below the diode's
+ *     sampled digital-value)
+ * @vref_high: The higher voltage-reference (the vref just above the diode's
+ *     sampled digital-value)
+ * @flags: bits[1:0]: The ID of the Vrefs pair (lowVref,highVref)
+ *     bits[6:2]: Reserved.
+ *     bits[7:7]: Indicates completion of at least 1 successful sample
+ *     since last DTS reset.
+ */
+struct iwl_mvm_dts_diode_bits {
+       u8 digital_value;
+       u8 vref_low;
+       u8 vref_high;
+       u8 flags;
+} __packed;
+
+union dts_diode_results {
+       u32 reg_value;
+       struct iwl_mvm_dts_diode_bits bits;
+} __packed;
+
+static s16 iwl_mvm_dts_get_volt_band_gap(struct iwl_mvm *mvm)
+{
+       struct iwl_nvm_section calib_sec;
+       const __le16 *calib;
+       u16 vbg;
+
+       /* TODO: move parsing to NVM code */
+       calib_sec = mvm->nvm_sections[NVM_SECTION_TYPE_CALIBRATION];
+       calib = (__le16 *)calib_sec.data;
+
+       vbg = le16_to_cpu(calib[OTP_WP_DTS_VBG]);
+
+       if (vbg < MEAS_VBG_MIN_VAL || vbg > MEAS_VBG_MAX_VAL)
+               vbg = MEAS_VBG_DEFAULT_VAL;
+
+       return vbg;
+}
+
+static u16 iwl_mvm_dts_get_ptat_deviation_offset(struct iwl_mvm *mvm)
+{
+       const u8 *calib;
+       u8 ptat, pa1, pa2, median;
+
+       /* TODO: move parsing to NVM code */
+       calib = mvm->nvm_sections[NVM_SECTION_TYPE_CALIBRATION].data;
+       ptat = calib[OTP_DTS_DIODE_DEVIATION];
+       pa1 = calib[OTP_DTS_DIODE_DEVIATION + 1];
+       pa2 = calib[OTP_DTS_DIODE_DEVIATION + 2];
+
+       /* get the median: */
+       if (ptat > pa1) {
+               if (ptat > pa2)
+                       median = (pa1 > pa2) ? pa1 : pa2;
+               else
+                       median = ptat;
+       } else {
+               if (pa1 > pa2)
+                       median = (ptat > pa2) ? ptat : pa2;
+               else
+                       median = pa1;
+       }
+
+       return ptat - median;
+}
+
+static u8 iwl_mvm_dts_calibrate_ptat_deviation(struct iwl_mvm *mvm, u8 value)
+{
+       /* Calibrate the PTAT digital value, based on PTAT deviation data: */
+       s16 new_val = value - iwl_mvm_dts_get_ptat_deviation_offset(mvm);
+
+       if (new_val > PTAT_DIGITAL_VALUE_MAX_VALUE)
+               new_val = PTAT_DIGITAL_VALUE_MAX_VALUE;
+       else if (new_val < PTAT_DIGITAL_VALUE_MIN_VALUE)
+               new_val = PTAT_DIGITAL_VALUE_MIN_VALUE;
+
+       return new_val;
+}
+
+static bool dts_get_adjacent_vrefs(struct iwl_mvm *mvm,
+                                  union dts_diode_results *avg_ptat)
+{
+       u8 vrefs_results[DTS_VREFS_NUM];
+       u8 low_vref_index = 0, flags;
+       u32 reg;
+
+       reg = iwl_read_prph(mvm->trans, DTSC_VREF_AVG);
+       memcpy(vrefs_results, &reg, sizeof(reg));
+       reg = iwl_read_prph(mvm->trans, DTSC_VREF5_AVG);
+       vrefs_results[4] = reg & 0xff;
+
+       if (avg_ptat->bits.digital_value < vrefs_results[0] ||
+           avg_ptat->bits.digital_value > vrefs_results[4])
+               return false;
+
+       if (avg_ptat->bits.digital_value > vrefs_results[3])
+               low_vref_index = 3;
+       else if (avg_ptat->bits.digital_value > vrefs_results[2])
+               low_vref_index = 2;
+       else if (avg_ptat->bits.digital_value > vrefs_results[1])
+               low_vref_index = 1;
+
+       avg_ptat->bits.vref_low  = vrefs_results[low_vref_index];
+       avg_ptat->bits.vref_high = vrefs_results[low_vref_index + 1];
+       flags = avg_ptat->bits.flags;
+       avg_ptat->bits.flags =
+               (flags & ~DTS_DIODE_REG_FLAGS_VREFS_ID) |
+               (low_vref_index & DTS_DIODE_REG_FLAGS_VREFS_ID);
+       return true;
+}
+
+/*
+ * return true it the results are valid, and false otherwise.
+ */
+static bool dts_read_ptat_avg_results(struct iwl_mvm *mvm,
+                                     union dts_diode_results *avg_ptat)
+{
+       u32 reg;
+       u8 tmp;
+
+       /* fill the diode value and pass_once with avg-reg results */
+       reg = iwl_read_prph(mvm->trans, DTSC_PTAT_AVG);
+       reg &= DTS_DIODE_REG_DIG_VAL | DTS_DIODE_REG_PASS_ONCE;
+       avg_ptat->reg_value = reg;
+
+       /* calibrate the PTAT digital value */
+       tmp = avg_ptat->bits.digital_value;
+       tmp = iwl_mvm_dts_calibrate_ptat_deviation(mvm, tmp);
+       avg_ptat->bits.digital_value = tmp;
+
+       /*
+        * fill vrefs fields, based on the avgVrefs results
+        * and the diode value
+        */
+       return dts_get_adjacent_vrefs(mvm, avg_ptat) &&
+               DTS_DIODE_VALID(avg_ptat->bits.flags);
+}
+
+static s32 calculate_nic_temperature(union dts_diode_results avg_ptat,
+                                    u16 volt_band_gap)
+{
+       u32 tmp_result;
+       u8 vrefs_diff;
+       /*
+        * For temperature calculation (at the end, shift right by 23)
+        * LUT[(D2-D1)] = ROUND{ 2^23 / ((D2-D1)*9*10) }
+        * (D2-D1) ==   43    44    45    46    47    48    49    50    51
+        */
+       static const u16 calc_lut[CALC_LUT_SIZE] = {
+               2168, 2118, 2071, 2026, 1983, 1942, 1902, 1864, 1828,
+       };
+
+       /*
+        * The diff between the high and low voltage-references is assumed
+        * to be strictly be in range of [60,68]
+        */
+       vrefs_diff = avg_ptat.bits.vref_high - avg_ptat.bits.vref_low;
+
+       if (vrefs_diff < CALC_VREFS_MIN_DIFF ||
+           vrefs_diff > CALC_VREFS_MAX_DIFF)
+               return TEMPERATURE_ERROR;
+
+       /* calculate the result: */
+       tmp_result =
+               vrefs_diff * (DTS_DIODE_GET_VREFS_ID(avg_ptat.bits.flags) + 9);
+       tmp_result += avg_ptat.bits.digital_value;
+       tmp_result -= avg_ptat.bits.vref_high;
+
+       /* multiply by the LUT value (based on the diff) */
+       tmp_result *= calc_lut[vrefs_diff - CALC_LUT_INDEX_OFFSET];
+
+       /*
+        * Get the BandGap (the voltage refereces source) error data
+        * (temperature offset)
+        */
+       tmp_result *= volt_band_gap;
+
+       /*
+        * here, tmp_result value can be up to 32-bits. We want to right-shift
+        * it *without* sign-extend.
+        */
+       tmp_result = tmp_result >> CALC_TEMPERATURE_RESULT_SHIFT_OFFSET;
+
+       /*
+        * at this point, tmp_result should be in the range:
+        * 200 <= tmp_result <= 365
+        */
+       return (s16)tmp_result - 240;
+}
+
+static s32 check_nic_temperature(struct iwl_mvm *mvm)
+{
+       u16 volt_band_gap;
+       union dts_diode_results avg_ptat;
+
+       volt_band_gap = iwl_mvm_dts_get_volt_band_gap(mvm);
+
+       /* disable DTS */
+       iwl_write_prph(mvm->trans, SHR_MISC_WFM_DTS_EN, 0);
+
+       /* SV initialization */
+       iwl_write_prph(mvm->trans, SHR_MISC_WFM_DTS_EN, 1);
+       iwl_write_prph(mvm->trans, DTSC_CFG_MODE,
+                      DTSC_CFG_MODE_PERIODIC);
+
+       /* wait for results */
+       msleep(100);
+       if (!dts_read_ptat_avg_results(mvm, &avg_ptat))
+               return TEMPERATURE_ERROR;
+
+       /* disable DTS */
+       iwl_write_prph(mvm->trans, SHR_MISC_WFM_DTS_EN, 0);
+
+       return calculate_nic_temperature(avg_ptat, volt_band_gap);
+}
+
+static void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm)
+{
+       u32 duration = mvm->thermal_throttle.params->ct_kill_duration;
+
+       IWL_ERR(mvm, "Enter CT Kill\n");
+       iwl_mvm_set_hw_ctkill_state(mvm, true);
+       schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit,
+                             round_jiffies_relative(duration * HZ));
+}
+
+static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm)
+{
+       IWL_ERR(mvm, "Exit CT Kill\n");
+       iwl_mvm_set_hw_ctkill_state(mvm, false);
+}
+
+static void check_exit_ctkill(struct work_struct *work)
+{
+       struct iwl_mvm_tt_mgmt *tt;
+       struct iwl_mvm *mvm;
+       u32 duration;
+       s32 temp;
+
+       tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work);
+       mvm = container_of(tt, struct iwl_mvm, thermal_throttle);
+
+       duration = tt->params->ct_kill_duration;
+
+       iwl_trans_start_hw(mvm->trans);
+       temp = check_nic_temperature(mvm);
+       iwl_trans_stop_hw(mvm->trans, false);
+
+       if (temp < MIN_TEMPERATURE || temp > MAX_TEMPERATURE) {
+               IWL_DEBUG_TEMP(mvm, "Failed to measure NIC temperature\n");
+               goto reschedule;
+       }
+       IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp);
+
+       if (temp <= tt->params->ct_kill_exit) {
+               iwl_mvm_exit_ctkill(mvm);
+               return;
+       }
+
+reschedule:
+       schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit,
+                             round_jiffies(duration * HZ));
+}
+
+static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac,
+                                    struct ieee80211_vif *vif)
+{
+       struct iwl_mvm *mvm = _data;
+       enum ieee80211_smps_mode smps_mode;
+
+       lockdep_assert_held(&mvm->mutex);
+
+       if (mvm->thermal_throttle.dynamic_smps)
+               smps_mode = IEEE80211_SMPS_DYNAMIC;
+       else
+               smps_mode = IEEE80211_SMPS_AUTOMATIC;
+
+       iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode);
+}
+
+static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable)
+{
+       struct ieee80211_sta *sta;
+       struct iwl_mvm_sta *mvmsta;
+       int i, err;
+
+       for (i = 0; i < IWL_MVM_STATION_COUNT; i++) {
+               sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i],
+                                               lockdep_is_held(&mvm->mutex));
+               if (IS_ERR_OR_NULL(sta))
+                       continue;
+               mvmsta = (void *)sta->drv_priv;
+               if (enable == mvmsta->tt_tx_protection)
+                       continue;
+               err = iwl_mvm_tx_protection(mvm, &mvmsta->lq_sta.lq,
+                                           mvmsta, enable);
+               if (err) {
+                       IWL_ERR(mvm, "Failed to %s Tx protection\n",
+                               enable ? "enable" : "disable");
+               } else {
+                       IWL_DEBUG_TEMP(mvm, "%s Tx protection\n",
+                                      enable ? "Enable" : "Disable");
+                       mvmsta->tt_tx_protection = enable;
+               }
+       }
+}
+
+static void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff)
+{
+       struct iwl_host_cmd cmd = {
+               .id = REPLY_THERMAL_MNG_BACKOFF,
+               .len = { sizeof(u32), },
+               .data = { &backoff, },
+               .flags = CMD_SYNC,
+       };
+
+       if (iwl_mvm_send_cmd(mvm, &cmd) == 0) {
+               IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n",
+                              backoff);
+               mvm->thermal_throttle.tx_backoff = backoff;
+       } else {
+               IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n");
+       }
+}
+
+void iwl_mvm_tt_handler(struct iwl_mvm *mvm)
+{
+       const struct iwl_tt_params *params = mvm->thermal_throttle.params;
+       struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
+       s32 temperature = mvm->temperature;
+       int i;
+       u32 tx_backoff;
+
+       IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature);
+
+       if (params->support_ct_kill && temperature >= params->ct_kill_entry) {
+               iwl_mvm_enter_ctkill(mvm);
+               return;
+       }
+
+       if (params->support_dynamic_smps) {
+               if (!tt->dynamic_smps &&
+                   temperature >= params->dynamic_smps_entry) {
+                       IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n");
+                       tt->dynamic_smps = true;
+                       ieee80211_iterate_active_interfaces_atomic(
+                                       mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
+                                       iwl_mvm_tt_smps_iterator, mvm);
+               } else if (tt->dynamic_smps &&
+                          temperature <= params->dynamic_smps_exit) {
+                       IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n");
+                       tt->dynamic_smps = false;
+                       ieee80211_iterate_active_interfaces_atomic(
+                                       mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
+                                       iwl_mvm_tt_smps_iterator, mvm);
+               }
+       }
+
+       if (params->support_tx_protection) {
+               if (temperature >= params->tx_protection_entry)
+                       iwl_mvm_tt_tx_protection(mvm, true);
+               else if (temperature <= params->tx_protection_exit)
+                       iwl_mvm_tt_tx_protection(mvm, false);
+       }
+
+       if (params->support_tx_backoff) {
+               tx_backoff = 0;
+               for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) {
+                       if (temperature < params->tx_backoff[i].temperature)
+                               break;
+                       tx_backoff = params->tx_backoff[i].backoff;
+               }
+               if (tt->tx_backoff != tx_backoff)
+                       iwl_mvm_tt_tx_backoff(mvm, tx_backoff);
+       }
+}
+
+static const struct iwl_tt_params iwl7000_tt_params = {
+       .ct_kill_entry = 118,
+       .ct_kill_exit = 96,
+       .ct_kill_duration = 5,
+       .dynamic_smps_entry = 114,
+       .dynamic_smps_exit = 110,
+       .tx_protection_entry = 114,
+       .tx_protection_exit = 108,
+       .tx_backoff = {
+               {.temperature = 112, .backoff = 200},
+               {.temperature = 113, .backoff = 600},
+               {.temperature = 114, .backoff = 1200},
+               {.temperature = 115, .backoff = 2000},
+               {.temperature = 116, .backoff = 4000},
+               {.temperature = 117, .backoff = 10000},
+       },
+       .support_ct_kill = true,
+       .support_dynamic_smps = true,
+       .support_tx_protection = true,
+       .support_tx_backoff = true,
+};
+
+void iwl_mvm_tt_initialize(struct iwl_mvm *mvm)
+{
+       struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
+
+       IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n");
+       tt->params = &iwl7000_tt_params;
+       INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill);
+}
+
+void iwl_mvm_tt_exit(struct iwl_mvm *mvm)
+{
+       cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit);
+       IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n");
+}
index 687b34e387ac325b0b5b5a3cf869625d31341ad4..c9b44ab4af07ac05dc173813c95e80429101765a 100644 (file)
@@ -471,3 +471,34 @@ int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq,
 
        return iwl_mvm_send_cmd(mvm, &cmd);
 }
+
+/**
+ * iwl_mvm_update_smps - Get a requst to change the SMPS mode
+ * @req_type: The part of the driver who call for a change.
+ * @smps_requests: The request to change the SMPS mode.
+ *
+ * Get a requst to change the SMPS mode,
+ * and change it according to all other requests in the driver.
+ */
+void iwl_mvm_update_smps(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
+                        enum iwl_mvm_smps_type_request req_type,
+                        enum ieee80211_smps_mode smps_request)
+{
+       struct iwl_mvm_vif *mvmvif;
+       enum ieee80211_smps_mode smps_mode = IEEE80211_SMPS_AUTOMATIC;
+       int i;
+
+       lockdep_assert_held(&mvm->mutex);
+       mvmvif = iwl_mvm_vif_from_mac80211(vif);
+       mvmvif->smps_requests[req_type] = smps_request;
+       for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) {
+               if (mvmvif->smps_requests[i] == IEEE80211_SMPS_STATIC) {
+                       smps_mode = IEEE80211_SMPS_STATIC;
+                       break;
+               }
+               if (mvmvif->smps_requests[i] == IEEE80211_SMPS_DYNAMIC)
+                       smps_mode = IEEE80211_SMPS_DYNAMIC;
+       }
+
+       ieee80211_request_smps(vif, smps_mode);
+}