Commit | Line | Data |
---|---|---|
9ee718aa EL |
1 | /****************************************************************************** |
2 | * | |
3 | * This file is provided under a dual BSD/GPLv2 license. When using or | |
4 | * redistributing this file, you may do so under either license. | |
5 | * | |
6 | * GPL LICENSE SUMMARY | |
7 | * | |
51368bf7 | 8 | * Copyright(c) 2013 - 2014 Intel Corporation. All rights reserved. |
8b4139dc | 9 | * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH |
0a3b7119 | 10 | * Copyright(c) 2015 - 2016 Intel Deutschland GmbH |
9ee718aa EL |
11 | * |
12 | * This program is free software; you can redistribute it and/or modify | |
13 | * it under the terms of version 2 of the GNU General Public License as | |
14 | * published by the Free Software Foundation. | |
15 | * | |
16 | * This program is distributed in the hope that it will be useful, but | |
17 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
19 | * General Public License for more details. | |
20 | * | |
21 | * You should have received a copy of the GNU General Public License | |
22 | * along with this program; if not, write to the Free Software | |
23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, | |
24 | * USA | |
25 | * | |
26 | * The full GNU General Public License is included in this distribution | |
27 | * in the file called COPYING. | |
28 | * | |
29 | * Contact Information: | |
d01c5366 | 30 | * Intel Linux Wireless <linuxwifi@intel.com> |
9ee718aa EL |
31 | * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 |
32 | * | |
33 | * BSD LICENSE | |
34 | * | |
51368bf7 | 35 | * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved. |
8b4139dc | 36 | * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH |
0a3b7119 | 37 | * Copyright(c) 2015 - 2016 Intel Deutschland GmbH |
9ee718aa EL |
38 | * All rights reserved. |
39 | * | |
40 | * Redistribution and use in source and binary forms, with or without | |
41 | * modification, are permitted provided that the following conditions | |
42 | * are met: | |
43 | * | |
44 | * * Redistributions of source code must retain the above copyright | |
45 | * notice, this list of conditions and the following disclaimer. | |
46 | * * Redistributions in binary form must reproduce the above copyright | |
47 | * notice, this list of conditions and the following disclaimer in | |
48 | * the documentation and/or other materials provided with the | |
49 | * distribution. | |
50 | * * Neither the name Intel Corporation nor the names of its | |
51 | * contributors may be used to endorse or promote products derived | |
52 | * from this software without specific prior written permission. | |
53 | * | |
54 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
55 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
56 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
57 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
58 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
59 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
60 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
61 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
62 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
63 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
64 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
65 | * | |
66 | *****************************************************************************/ | |
67 | ||
c221daf2 CRI |
68 | #include <linux/sort.h> |
69 | ||
9ee718aa | 70 | #include "mvm.h" |
9ee718aa | 71 | |
a0a09243 | 72 | #define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT HZ |
9ee718aa | 73 | |
a0a09243 | 74 | static void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm) |
9ee718aa | 75 | { |
19789abb | 76 | struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; |
3444682a | 77 | u32 duration = tt->params.ct_kill_duration; |
9ee718aa | 78 | |
a0a09243 LC |
79 | if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) |
80 | return; | |
9ee718aa | 81 | |
a0a09243 LC |
82 | IWL_ERR(mvm, "Enter CT Kill\n"); |
83 | iwl_mvm_set_hw_ctkill_state(mvm, true); | |
9ee718aa | 84 | |
c221daf2 CRI |
85 | if (!iwl_mvm_is_tt_in_fw(mvm)) { |
86 | tt->throttle = false; | |
87 | tt->dynamic_smps = false; | |
88 | } | |
19789abb | 89 | |
a0a09243 LC |
90 | /* Don't schedule an exit work if we're in test mode, since |
91 | * the temperature will not change unless we manually set it | |
92 | * again (or disable testing). | |
93 | */ | |
94 | if (!mvm->temperature_test) | |
19789abb | 95 | schedule_delayed_work(&tt->ct_kill_exit, |
a0a09243 | 96 | round_jiffies_relative(duration * HZ)); |
9ee718aa EL |
97 | } |
98 | ||
a0a09243 | 99 | static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm) |
9ee718aa | 100 | { |
a0a09243 LC |
101 | if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) |
102 | return; | |
9ee718aa | 103 | |
a0a09243 LC |
104 | IWL_ERR(mvm, "Exit CT Kill\n"); |
105 | iwl_mvm_set_hw_ctkill_state(mvm, false); | |
9ee718aa EL |
106 | } |
107 | ||
fd1f7550 LC |
108 | void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp) |
109 | { | |
110 | /* ignore the notification if we are in test mode */ | |
111 | if (mvm->temperature_test) | |
112 | return; | |
113 | ||
114 | if (mvm->temperature == temp) | |
115 | return; | |
116 | ||
117 | mvm->temperature = temp; | |
118 | iwl_mvm_tt_handler(mvm); | |
119 | } | |
120 | ||
121 | static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm, | |
122 | struct iwl_rx_packet *pkt) | |
9ee718aa | 123 | { |
c221daf2 | 124 | struct iwl_dts_measurement_notif_v1 *notif_v1; |
a0a09243 | 125 | int len = iwl_rx_packet_payload_len(pkt); |
fd1f7550 | 126 | int temp; |
a0a09243 | 127 | |
c221daf2 CRI |
128 | /* we can use notif_v1 only, because v2 only adds an additional |
129 | * parameter, which is not used in this function. | |
130 | */ | |
131 | if (WARN_ON_ONCE(len < sizeof(*notif_v1))) { | |
a0a09243 | 132 | IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); |
fd1f7550 | 133 | return -EINVAL; |
a0a09243 | 134 | } |
9ee718aa | 135 | |
c221daf2 | 136 | notif_v1 = (void *)pkt->data; |
9ee718aa | 137 | |
c221daf2 | 138 | temp = le32_to_cpu(notif_v1->temp); |
9ee718aa | 139 | |
a0a09243 | 140 | /* shouldn't be negative, but since it's s32, make sure it isn't */ |
fd1f7550 LC |
141 | if (WARN_ON_ONCE(temp < 0)) |
142 | temp = 0; | |
143 | ||
144 | IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp); | |
145 | ||
146 | return temp; | |
147 | } | |
148 | ||
149 | static bool iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data *notif_wait, | |
150 | struct iwl_rx_packet *pkt, void *data) | |
151 | { | |
152 | struct iwl_mvm *mvm = | |
153 | container_of(notif_wait, struct iwl_mvm, notif_wait); | |
154 | int *temp = data; | |
155 | int ret; | |
156 | ||
157 | ret = iwl_mvm_temp_notif_parse(mvm, pkt); | |
158 | if (ret < 0) | |
159 | return true; | |
160 | ||
161 | *temp = ret; | |
9ee718aa | 162 | |
a0a09243 | 163 | return true; |
9ee718aa EL |
164 | } |
165 | ||
0416841d | 166 | void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) |
ea9af24d LC |
167 | { |
168 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | |
c221daf2 CRI |
169 | struct iwl_dts_measurement_notif_v2 *notif_v2; |
170 | int len = iwl_rx_packet_payload_len(pkt); | |
ea9af24d | 171 | int temp; |
c221daf2 | 172 | u32 ths_crossed; |
ea9af24d LC |
173 | |
174 | /* the notification is handled synchronously in ctkill, so skip here */ | |
175 | if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) | |
0416841d | 176 | return; |
ea9af24d LC |
177 | |
178 | temp = iwl_mvm_temp_notif_parse(mvm, pkt); | |
c221daf2 CRI |
179 | |
180 | if (!iwl_mvm_is_tt_in_fw(mvm)) { | |
181 | if (temp >= 0) | |
182 | iwl_mvm_tt_temp_changed(mvm, temp); | |
183 | return; | |
184 | } | |
185 | ||
186 | if (WARN_ON_ONCE(len < sizeof(*notif_v2))) { | |
187 | IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); | |
188 | return; | |
189 | } | |
190 | ||
191 | notif_v2 = (void *)pkt->data; | |
192 | ths_crossed = le32_to_cpu(notif_v2->threshold_idx); | |
193 | ||
194 | /* 0xFF in ths_crossed means the notification is not related | |
195 | * to a trip, so we can ignore it here. | |
196 | */ | |
197 | if (ths_crossed == 0xFF) | |
198 | return; | |
199 | ||
200 | IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n", | |
201 | temp, ths_crossed); | |
202 | ||
203 | #ifdef CONFIG_THERMAL | |
204 | if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS)) | |
0416841d | 205 | return; |
ea9af24d | 206 | |
c221daf2 CRI |
207 | /* |
208 | * We are now handling a temperature notification from the firmware | |
209 | * in ASYNC and hold the mutex. thermal_notify_framework will call | |
210 | * us back through get_temp() which ought to send a SYNC command to | |
211 | * the firmware and hence to take the mutex. | |
212 | * Avoid the deadlock by unlocking the mutex here. | |
213 | */ | |
91f66a3c EG |
214 | if (mvm->tz_device.tzone) { |
215 | struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device; | |
216 | ||
217 | mutex_unlock(&mvm->mutex); | |
218 | thermal_notify_framework(tz_dev->tzone, | |
219 | tz_dev->fw_trips_index[ths_crossed]); | |
220 | mutex_lock(&mvm->mutex); | |
221 | } | |
c221daf2 | 222 | #endif /* CONFIG_THERMAL */ |
ea9af24d LC |
223 | } |
224 | ||
0a3b7119 CRI |
225 | void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) |
226 | { | |
227 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | |
228 | struct ct_kill_notif *notif; | |
229 | int len = iwl_rx_packet_payload_len(pkt); | |
230 | ||
231 | if (WARN_ON_ONCE(len != sizeof(*notif))) { | |
232 | IWL_ERR(mvm, "Invalid CT_KILL_NOTIFICATION\n"); | |
233 | return; | |
234 | } | |
235 | ||
236 | notif = (struct ct_kill_notif *)pkt->data; | |
237 | IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n", | |
238 | notif->temperature); | |
239 | ||
240 | iwl_mvm_enter_ctkill(mvm); | |
241 | } | |
242 | ||
a0a09243 | 243 | static int iwl_mvm_get_temp_cmd(struct iwl_mvm *mvm) |
9ee718aa | 244 | { |
a0a09243 LC |
245 | struct iwl_dts_measurement_cmd cmd = { |
246 | .flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP), | |
9ee718aa | 247 | }; |
78efc702 AN |
248 | struct iwl_ext_dts_measurement_cmd extcmd = { |
249 | .control_mode = cpu_to_le32(DTS_AUTOMATIC), | |
250 | }; | |
09eef330 AE |
251 | u32 cmdid; |
252 | ||
253 | if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_WIDE_CMD_HDR)) | |
254 | cmdid = iwl_cmd_id(CMD_DTS_MEASUREMENT_TRIGGER_WIDE, | |
255 | PHY_OPS_GROUP, 0); | |
256 | else | |
257 | cmdid = CMD_DTS_MEASUREMENT_TRIGGER; | |
78efc702 AN |
258 | |
259 | if (!fw_has_capa(&mvm->fw->ucode_capa, | |
260 | IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) | |
261 | return iwl_mvm_send_cmd_pdu(mvm, cmdid, 0, sizeof(cmd), &cmd); | |
262 | ||
263 | return iwl_mvm_send_cmd_pdu(mvm, cmdid, 0, sizeof(extcmd), &extcmd); | |
9ee718aa EL |
264 | } |
265 | ||
7869318e | 266 | int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp) |
9ee718aa | 267 | { |
a0a09243 | 268 | struct iwl_notification_wait wait_temp_notif; |
09eef330 AE |
269 | static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP, |
270 | DTS_MEASUREMENT_NOTIF_WIDE) }; | |
7869318e | 271 | int ret; |
9ee718aa | 272 | |
09eef330 AE |
273 | if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_WIDE_CMD_HDR)) |
274 | temp_notif[0] = DTS_MEASUREMENT_NOTIFICATION; | |
275 | ||
a0a09243 | 276 | lockdep_assert_held(&mvm->mutex); |
b689fa79 | 277 | |
a0a09243 LC |
278 | iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif, |
279 | temp_notif, ARRAY_SIZE(temp_notif), | |
7869318e | 280 | iwl_mvm_temp_notif_wait, temp); |
efc36dbd | 281 | |
a0a09243 LC |
282 | ret = iwl_mvm_get_temp_cmd(mvm); |
283 | if (ret) { | |
284 | IWL_ERR(mvm, "Failed to get the temperature (err=%d)\n", ret); | |
285 | iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif); | |
286 | return ret; | |
287 | } | |
9ee718aa | 288 | |
a0a09243 LC |
289 | ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif, |
290 | IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT); | |
7869318e | 291 | if (ret) |
a0a09243 | 292 | IWL_ERR(mvm, "Getting the temperature timed out\n"); |
b689fa79 | 293 | |
7869318e | 294 | return ret; |
9ee718aa EL |
295 | } |
296 | ||
297 | static void check_exit_ctkill(struct work_struct *work) | |
298 | { | |
299 | struct iwl_mvm_tt_mgmt *tt; | |
300 | struct iwl_mvm *mvm; | |
301 | u32 duration; | |
302 | s32 temp; | |
7869318e | 303 | int ret; |
9ee718aa EL |
304 | |
305 | tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work); | |
306 | mvm = container_of(tt, struct iwl_mvm, thermal_throttle); | |
307 | ||
0a3b7119 CRI |
308 | if (iwl_mvm_is_tt_in_fw(mvm)) { |
309 | iwl_mvm_exit_ctkill(mvm); | |
310 | ||
311 | return; | |
312 | } | |
313 | ||
3444682a | 314 | duration = tt->params.ct_kill_duration; |
9ee718aa | 315 | |
a0a09243 LC |
316 | mutex_lock(&mvm->mutex); |
317 | ||
318 | if (__iwl_mvm_mac_start(mvm)) | |
319 | goto reschedule; | |
320 | ||
576eeee9 | 321 | /* make sure the device is available for direct read/writes */ |
a0a09243 LC |
322 | if (iwl_mvm_ref_sync(mvm, IWL_MVM_REF_CHECK_CTKILL)) { |
323 | __iwl_mvm_mac_stop(mvm); | |
576eeee9 | 324 | goto reschedule; |
a0a09243 | 325 | } |
576eeee9 | 326 | |
7869318e | 327 | ret = iwl_mvm_get_temp(mvm, &temp); |
9ee718aa | 328 | |
576eeee9 EP |
329 | iwl_mvm_unref(mvm, IWL_MVM_REF_CHECK_CTKILL); |
330 | ||
a0a09243 LC |
331 | __iwl_mvm_mac_stop(mvm); |
332 | ||
7869318e | 333 | if (ret) |
9ee718aa | 334 | goto reschedule; |
a0a09243 | 335 | |
9ee718aa EL |
336 | IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp); |
337 | ||
3444682a | 338 | if (temp <= tt->params.ct_kill_exit) { |
a0a09243 | 339 | mutex_unlock(&mvm->mutex); |
9ee718aa EL |
340 | iwl_mvm_exit_ctkill(mvm); |
341 | return; | |
342 | } | |
343 | ||
344 | reschedule: | |
a0a09243 | 345 | mutex_unlock(&mvm->mutex); |
9ee718aa EL |
346 | schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit, |
347 | round_jiffies(duration * HZ)); | |
348 | } | |
349 | ||
350 | static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac, | |
351 | struct ieee80211_vif *vif) | |
352 | { | |
353 | struct iwl_mvm *mvm = _data; | |
354 | enum ieee80211_smps_mode smps_mode; | |
355 | ||
356 | lockdep_assert_held(&mvm->mutex); | |
357 | ||
358 | if (mvm->thermal_throttle.dynamic_smps) | |
359 | smps_mode = IEEE80211_SMPS_DYNAMIC; | |
360 | else | |
361 | smps_mode = IEEE80211_SMPS_AUTOMATIC; | |
362 | ||
fded313e EL |
363 | if (vif->type != NL80211_IFTYPE_STATION) |
364 | return; | |
365 | ||
9ee718aa EL |
366 | iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode); |
367 | } | |
368 | ||
369 | static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable) | |
370 | { | |
371 | struct ieee80211_sta *sta; | |
372 | struct iwl_mvm_sta *mvmsta; | |
373 | int i, err; | |
374 | ||
375 | for (i = 0; i < IWL_MVM_STATION_COUNT; i++) { | |
376 | sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], | |
377 | lockdep_is_held(&mvm->mutex)); | |
378 | if (IS_ERR_OR_NULL(sta)) | |
379 | continue; | |
5b577a90 | 380 | mvmsta = iwl_mvm_sta_from_mac80211(sta); |
9ee718aa EL |
381 | if (enable == mvmsta->tt_tx_protection) |
382 | continue; | |
e126b5d9 | 383 | err = iwl_mvm_tx_protection(mvm, mvmsta, enable); |
9ee718aa EL |
384 | if (err) { |
385 | IWL_ERR(mvm, "Failed to %s Tx protection\n", | |
386 | enable ? "enable" : "disable"); | |
387 | } else { | |
388 | IWL_DEBUG_TEMP(mvm, "%s Tx protection\n", | |
389 | enable ? "Enable" : "Disable"); | |
390 | mvmsta->tt_tx_protection = enable; | |
391 | } | |
392 | } | |
393 | } | |
394 | ||
0c0e2c71 | 395 | void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff) |
9ee718aa EL |
396 | { |
397 | struct iwl_host_cmd cmd = { | |
398 | .id = REPLY_THERMAL_MNG_BACKOFF, | |
399 | .len = { sizeof(u32), }, | |
400 | .data = { &backoff, }, | |
9ee718aa EL |
401 | }; |
402 | ||
0c0e2c71 IY |
403 | backoff = max(backoff, mvm->thermal_throttle.min_backoff); |
404 | ||
9ee718aa EL |
405 | if (iwl_mvm_send_cmd(mvm, &cmd) == 0) { |
406 | IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n", | |
407 | backoff); | |
408 | mvm->thermal_throttle.tx_backoff = backoff; | |
409 | } else { | |
410 | IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n"); | |
411 | } | |
412 | } | |
413 | ||
414 | void iwl_mvm_tt_handler(struct iwl_mvm *mvm) | |
415 | { | |
3444682a | 416 | struct iwl_tt_params *params = &mvm->thermal_throttle.params; |
9ee718aa EL |
417 | struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; |
418 | s32 temperature = mvm->temperature; | |
dafe6c43 | 419 | bool throttle_enable = false; |
9ee718aa EL |
420 | int i; |
421 | u32 tx_backoff; | |
422 | ||
423 | IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature); | |
424 | ||
425 | if (params->support_ct_kill && temperature >= params->ct_kill_entry) { | |
426 | iwl_mvm_enter_ctkill(mvm); | |
427 | return; | |
428 | } | |
429 | ||
b689fa79 | 430 | if (params->support_ct_kill && |
3444682a | 431 | temperature <= params->ct_kill_exit) { |
b689fa79 LC |
432 | iwl_mvm_exit_ctkill(mvm); |
433 | return; | |
434 | } | |
435 | ||
9ee718aa EL |
436 | if (params->support_dynamic_smps) { |
437 | if (!tt->dynamic_smps && | |
438 | temperature >= params->dynamic_smps_entry) { | |
439 | IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n"); | |
440 | tt->dynamic_smps = true; | |
441 | ieee80211_iterate_active_interfaces_atomic( | |
442 | mvm->hw, IEEE80211_IFACE_ITER_NORMAL, | |
443 | iwl_mvm_tt_smps_iterator, mvm); | |
dafe6c43 | 444 | throttle_enable = true; |
9ee718aa EL |
445 | } else if (tt->dynamic_smps && |
446 | temperature <= params->dynamic_smps_exit) { | |
447 | IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n"); | |
448 | tt->dynamic_smps = false; | |
449 | ieee80211_iterate_active_interfaces_atomic( | |
450 | mvm->hw, IEEE80211_IFACE_ITER_NORMAL, | |
451 | iwl_mvm_tt_smps_iterator, mvm); | |
452 | } | |
453 | } | |
454 | ||
455 | if (params->support_tx_protection) { | |
dafe6c43 | 456 | if (temperature >= params->tx_protection_entry) { |
9ee718aa | 457 | iwl_mvm_tt_tx_protection(mvm, true); |
dafe6c43 | 458 | throttle_enable = true; |
459 | } else if (temperature <= params->tx_protection_exit) { | |
9ee718aa | 460 | iwl_mvm_tt_tx_protection(mvm, false); |
dafe6c43 | 461 | } |
9ee718aa EL |
462 | } |
463 | ||
464 | if (params->support_tx_backoff) { | |
1b8ebbd3 | 465 | tx_backoff = tt->min_backoff; |
9ee718aa EL |
466 | for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) { |
467 | if (temperature < params->tx_backoff[i].temperature) | |
468 | break; | |
1b8ebbd3 EL |
469 | tx_backoff = max(tt->min_backoff, |
470 | params->tx_backoff[i].backoff); | |
9ee718aa | 471 | } |
1b8ebbd3 | 472 | if (tx_backoff != tt->min_backoff) |
dafe6c43 | 473 | throttle_enable = true; |
9ee718aa EL |
474 | if (tt->tx_backoff != tx_backoff) |
475 | iwl_mvm_tt_tx_backoff(mvm, tx_backoff); | |
476 | } | |
dafe6c43 | 477 | |
478 | if (!tt->throttle && throttle_enable) { | |
479 | IWL_WARN(mvm, | |
480 | "Due to high temperature thermal throttling initiated\n"); | |
481 | tt->throttle = true; | |
19a04bdd EL |
482 | } else if (tt->throttle && !tt->dynamic_smps && |
483 | tt->tx_backoff == tt->min_backoff && | |
dafe6c43 | 484 | temperature <= params->tx_protection_exit) { |
485 | IWL_WARN(mvm, | |
486 | "Temperature is back to normal thermal throttling stopped\n"); | |
487 | tt->throttle = false; | |
488 | } | |
9ee718aa EL |
489 | } |
490 | ||
3444682a | 491 | static const struct iwl_tt_params iwl_mvm_default_tt_params = { |
9ee718aa EL |
492 | .ct_kill_entry = 118, |
493 | .ct_kill_exit = 96, | |
494 | .ct_kill_duration = 5, | |
495 | .dynamic_smps_entry = 114, | |
496 | .dynamic_smps_exit = 110, | |
497 | .tx_protection_entry = 114, | |
498 | .tx_protection_exit = 108, | |
499 | .tx_backoff = { | |
500 | {.temperature = 112, .backoff = 200}, | |
501 | {.temperature = 113, .backoff = 600}, | |
502 | {.temperature = 114, .backoff = 1200}, | |
503 | {.temperature = 115, .backoff = 2000}, | |
504 | {.temperature = 116, .backoff = 4000}, | |
505 | {.temperature = 117, .backoff = 10000}, | |
506 | }, | |
507 | .support_ct_kill = true, | |
508 | .support_dynamic_smps = true, | |
509 | .support_tx_protection = true, | |
510 | .support_tx_backoff = true, | |
511 | }; | |
512 | ||
00f481bd CRI |
513 | int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 budget) |
514 | { | |
515 | struct iwl_mvm_ctdp_cmd cmd = { | |
516 | .operation = cpu_to_le32(op), | |
517 | .budget = cpu_to_le32(budget), | |
518 | .window_size = 0, | |
519 | }; | |
520 | int ret; | |
521 | u32 status; | |
522 | ||
523 | lockdep_assert_held(&mvm->mutex); | |
524 | ||
525 | ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP, | |
526 | CTDP_CONFIG_CMD), | |
527 | sizeof(cmd), &cmd, &status); | |
528 | ||
529 | if (ret) { | |
530 | IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret); | |
531 | return ret; | |
532 | } | |
533 | ||
534 | switch (op) { | |
535 | case CTDP_CMD_OPERATION_START: | |
536 | #ifdef CONFIG_THERMAL | |
537 | mvm->cooling_dev.cur_state = budget; | |
538 | #endif /* CONFIG_THERMAL */ | |
539 | break; | |
540 | case CTDP_CMD_OPERATION_REPORT: | |
541 | IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status); | |
542 | /* when the function is called with CTDP_CMD_OPERATION_REPORT | |
543 | * option the function should return the average budget value | |
544 | * that is received from the FW. | |
545 | * The budget can't be less or equal to 0, so it's possible | |
546 | * to distinguish between error values and budgets. | |
547 | */ | |
548 | return status; | |
549 | case CTDP_CMD_OPERATION_STOP: | |
550 | IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n"); | |
551 | break; | |
552 | } | |
553 | ||
554 | return 0; | |
555 | } | |
556 | ||
c221daf2 CRI |
557 | #ifdef CONFIG_THERMAL |
558 | static int compare_temps(const void *a, const void *b) | |
559 | { | |
560 | return ((s16)le16_to_cpu(*(__le16 *)a) - | |
561 | (s16)le16_to_cpu(*(__le16 *)b)); | |
562 | } | |
563 | ||
564 | int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm) | |
565 | { | |
566 | struct temp_report_ths_cmd cmd = {0}; | |
567 | int ret, i, j, idx = 0; | |
568 | ||
569 | lockdep_assert_held(&mvm->mutex); | |
570 | ||
91f66a3c EG |
571 | if (!mvm->tz_device.tzone) |
572 | return -EINVAL; | |
573 | ||
c221daf2 CRI |
574 | /* The driver holds array of temperature trips that are unsorted |
575 | * and uncompressed, the FW should get it compressed and sorted | |
576 | */ | |
577 | ||
578 | /* compress temp_trips to cmd array, remove uninitialized values*/ | |
91f66a3c | 579 | for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) { |
c221daf2 CRI |
580 | if (mvm->tz_device.temp_trips[i] != S16_MIN) { |
581 | cmd.thresholds[idx++] = | |
582 | cpu_to_le16(mvm->tz_device.temp_trips[i]); | |
583 | } | |
91f66a3c | 584 | } |
c221daf2 CRI |
585 | cmd.num_temps = cpu_to_le32(idx); |
586 | ||
587 | if (!idx) | |
588 | goto send; | |
589 | ||
590 | /*sort cmd array*/ | |
591 | sort(cmd.thresholds, idx, sizeof(s16), compare_temps, NULL); | |
592 | ||
593 | /* we should save the indexes of trips because we sort | |
594 | * and compress the orginal array | |
595 | */ | |
596 | for (i = 0; i < idx; i++) { | |
597 | for (j = 0; j < IWL_MAX_DTS_TRIPS; j++) { | |
598 | if (le16_to_cpu(cmd.thresholds[i]) == | |
599 | mvm->tz_device.temp_trips[j]) | |
600 | mvm->tz_device.fw_trips_index[i] = j; | |
601 | } | |
602 | } | |
603 | ||
604 | send: | |
605 | ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP, | |
606 | TEMP_REPORTING_THRESHOLDS_CMD), | |
607 | 0, sizeof(cmd), &cmd); | |
608 | if (ret) | |
609 | IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n", | |
610 | ret); | |
611 | ||
612 | return ret; | |
613 | } | |
614 | ||
615 | static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device, | |
616 | int *temperature) | |
617 | { | |
618 | struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; | |
619 | int ret; | |
620 | int temp; | |
621 | ||
622 | mutex_lock(&mvm->mutex); | |
623 | ||
624 | if (!mvm->ucode_loaded || !(mvm->cur_ucode == IWL_UCODE_REGULAR)) { | |
625 | ret = -EIO; | |
626 | goto out; | |
627 | } | |
628 | ||
629 | ret = iwl_mvm_get_temp(mvm, &temp); | |
630 | if (ret) | |
631 | goto out; | |
632 | ||
633 | *temperature = temp * 1000; | |
634 | ||
635 | out: | |
636 | mutex_unlock(&mvm->mutex); | |
637 | return ret; | |
638 | } | |
639 | ||
640 | static int iwl_mvm_tzone_get_trip_temp(struct thermal_zone_device *device, | |
641 | int trip, int *temp) | |
642 | { | |
643 | struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; | |
644 | ||
645 | if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) | |
646 | return -EINVAL; | |
647 | ||
648 | *temp = mvm->tz_device.temp_trips[trip] * 1000; | |
649 | ||
650 | return 0; | |
651 | } | |
652 | ||
653 | static int iwl_mvm_tzone_get_trip_type(struct thermal_zone_device *device, | |
654 | int trip, enum thermal_trip_type *type) | |
655 | { | |
656 | if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) | |
657 | return -EINVAL; | |
658 | ||
659 | *type = THERMAL_TRIP_PASSIVE; | |
660 | ||
661 | return 0; | |
662 | } | |
663 | ||
664 | static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device, | |
665 | int trip, int temp) | |
666 | { | |
667 | struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; | |
668 | struct iwl_mvm_thermal_device *tzone; | |
669 | int i, ret; | |
670 | s16 temperature; | |
671 | ||
672 | mutex_lock(&mvm->mutex); | |
673 | ||
674 | if (!mvm->ucode_loaded || !(mvm->cur_ucode == IWL_UCODE_REGULAR)) { | |
675 | ret = -EIO; | |
676 | goto out; | |
677 | } | |
678 | ||
679 | if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) { | |
680 | ret = -EINVAL; | |
681 | goto out; | |
682 | } | |
683 | ||
684 | if ((temp / 1000) > S16_MAX) { | |
685 | ret = -EINVAL; | |
686 | goto out; | |
687 | } | |
688 | ||
689 | temperature = (s16)(temp / 1000); | |
690 | tzone = &mvm->tz_device; | |
691 | ||
692 | if (!tzone) { | |
693 | ret = -EIO; | |
694 | goto out; | |
695 | } | |
696 | ||
697 | /* no updates*/ | |
698 | if (tzone->temp_trips[trip] == temperature) { | |
699 | ret = 0; | |
700 | goto out; | |
701 | } | |
702 | ||
703 | /* already existing temperature */ | |
704 | for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) { | |
705 | if (tzone->temp_trips[i] == temperature) { | |
706 | ret = -EINVAL; | |
707 | goto out; | |
708 | } | |
709 | } | |
710 | ||
711 | tzone->temp_trips[trip] = temperature; | |
712 | ||
713 | ret = iwl_mvm_send_temp_report_ths_cmd(mvm); | |
714 | out: | |
715 | mutex_unlock(&mvm->mutex); | |
716 | return ret; | |
717 | } | |
718 | ||
719 | static struct thermal_zone_device_ops tzone_ops = { | |
720 | .get_temp = iwl_mvm_tzone_get_temp, | |
721 | .get_trip_temp = iwl_mvm_tzone_get_trip_temp, | |
722 | .get_trip_type = iwl_mvm_tzone_get_trip_type, | |
723 | .set_trip_temp = iwl_mvm_tzone_set_trip_temp, | |
724 | }; | |
725 | ||
726 | /* make all trips writable */ | |
727 | #define IWL_WRITABLE_TRIPS_MSK (BIT(IWL_MAX_DTS_TRIPS) - 1) | |
728 | ||
729 | static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm) | |
730 | { | |
731 | int i; | |
732 | char name[] = "iwlwifi"; | |
733 | ||
734 | if (!iwl_mvm_is_tt_in_fw(mvm)) { | |
735 | mvm->tz_device.tzone = NULL; | |
736 | ||
737 | return; | |
738 | } | |
739 | ||
740 | BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); | |
741 | ||
742 | mvm->tz_device.tzone = thermal_zone_device_register(name, | |
743 | IWL_MAX_DTS_TRIPS, | |
744 | IWL_WRITABLE_TRIPS_MSK, | |
745 | mvm, &tzone_ops, | |
746 | NULL, 0, 0); | |
747 | if (IS_ERR(mvm->tz_device.tzone)) { | |
748 | IWL_DEBUG_TEMP(mvm, | |
749 | "Failed to register to thermal zone (err = %ld)\n", | |
750 | PTR_ERR(mvm->tz_device.tzone)); | |
91f66a3c | 751 | mvm->tz_device.tzone = NULL; |
c221daf2 CRI |
752 | return; |
753 | } | |
754 | ||
755 | /* 0 is a valid temperature, | |
756 | * so initialize the array with S16_MIN which invalid temperature | |
757 | */ | |
758 | for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++) | |
759 | mvm->tz_device.temp_trips[i] = S16_MIN; | |
760 | } | |
761 | ||
5c89e7bc CRI |
762 | static const u32 iwl_mvm_cdev_budgets[] = { |
763 | 2000, /* cooling state 0 */ | |
764 | 1800, /* cooling state 1 */ | |
765 | 1600, /* cooling state 2 */ | |
766 | 1400, /* cooling state 3 */ | |
767 | 1200, /* cooling state 4 */ | |
768 | 1000, /* cooling state 5 */ | |
769 | 900, /* cooling state 6 */ | |
770 | 800, /* cooling state 7 */ | |
771 | 700, /* cooling state 8 */ | |
772 | 650, /* cooling state 9 */ | |
773 | 600, /* cooling state 10 */ | |
774 | 550, /* cooling state 11 */ | |
775 | 500, /* cooling state 12 */ | |
776 | 450, /* cooling state 13 */ | |
777 | 400, /* cooling state 14 */ | |
778 | 350, /* cooling state 15 */ | |
779 | 300, /* cooling state 16 */ | |
780 | 250, /* cooling state 17 */ | |
781 | 200, /* cooling state 18 */ | |
782 | 150, /* cooling state 19 */ | |
783 | }; | |
784 | ||
5c89e7bc CRI |
785 | static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev, |
786 | unsigned long *state) | |
787 | { | |
788 | *state = ARRAY_SIZE(iwl_mvm_cdev_budgets) - 1; | |
789 | ||
790 | return 0; | |
791 | } | |
792 | ||
793 | static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev, | |
794 | unsigned long *state) | |
795 | { | |
796 | struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); | |
797 | ||
798 | if (test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status)) | |
799 | return -EBUSY; | |
800 | ||
801 | *state = mvm->cooling_dev.cur_state; | |
802 | return 0; | |
803 | } | |
804 | ||
805 | static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev, | |
806 | unsigned long new_state) | |
807 | { | |
808 | struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); | |
809 | int ret; | |
810 | ||
811 | if (!mvm->ucode_loaded || !(mvm->cur_ucode == IWL_UCODE_REGULAR)) | |
812 | return -EIO; | |
813 | ||
814 | if (test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status)) | |
815 | return -EBUSY; | |
816 | ||
817 | mutex_lock(&mvm->mutex); | |
818 | ||
819 | if (new_state >= ARRAY_SIZE(iwl_mvm_cdev_budgets)) { | |
820 | ret = -EINVAL; | |
821 | goto unlock; | |
822 | } | |
823 | ||
824 | ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START, | |
825 | iwl_mvm_cdev_budgets[new_state]); | |
826 | ||
827 | unlock: | |
828 | mutex_unlock(&mvm->mutex); | |
829 | return ret; | |
830 | } | |
831 | ||
832 | static struct thermal_cooling_device_ops tcooling_ops = { | |
833 | .get_max_state = iwl_mvm_tcool_get_max_state, | |
834 | .get_cur_state = iwl_mvm_tcool_get_cur_state, | |
835 | .set_cur_state = iwl_mvm_tcool_set_cur_state, | |
836 | }; | |
837 | ||
91f66a3c | 838 | static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm) |
5c89e7bc CRI |
839 | { |
840 | char name[] = "iwlwifi"; | |
841 | ||
91f66a3c EG |
842 | if (!iwl_mvm_is_ctdp_supported(mvm)) |
843 | return; | |
5c89e7bc CRI |
844 | |
845 | BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); | |
846 | ||
847 | mvm->cooling_dev.cdev = | |
848 | thermal_cooling_device_register(name, | |
849 | mvm, | |
850 | &tcooling_ops); | |
851 | ||
852 | if (IS_ERR(mvm->cooling_dev.cdev)) { | |
853 | IWL_DEBUG_TEMP(mvm, | |
854 | "Failed to register to cooling device (err = %ld)\n", | |
855 | PTR_ERR(mvm->cooling_dev.cdev)); | |
91f66a3c EG |
856 | mvm->cooling_dev.cdev = NULL; |
857 | return; | |
5c89e7bc | 858 | } |
5c89e7bc CRI |
859 | } |
860 | ||
c221daf2 CRI |
861 | static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm) |
862 | { | |
91f66a3c | 863 | if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone) |
c221daf2 CRI |
864 | return; |
865 | ||
91f66a3c EG |
866 | IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n"); |
867 | thermal_zone_device_unregister(mvm->tz_device.tzone); | |
868 | mvm->tz_device.tzone = NULL; | |
c221daf2 | 869 | } |
5c89e7bc CRI |
870 | |
871 | static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm) | |
872 | { | |
91f66a3c | 873 | if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev) |
5c89e7bc CRI |
874 | return; |
875 | ||
91f66a3c EG |
876 | IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n"); |
877 | thermal_cooling_device_unregister(mvm->cooling_dev.cdev); | |
878 | mvm->cooling_dev.cdev = NULL; | |
5c89e7bc | 879 | } |
c221daf2 CRI |
880 | #endif /* CONFIG_THERMAL */ |
881 | ||
882 | void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff) | |
9ee718aa EL |
883 | { |
884 | struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; | |
885 | ||
886 | IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n"); | |
6be497f2 | 887 | |
3444682a CRI |
888 | if (mvm->cfg->thermal_params) |
889 | tt->params = *mvm->cfg->thermal_params; | |
6be497f2 | 890 | else |
3444682a | 891 | tt->params = iwl_mvm_default_tt_params; |
6be497f2 | 892 | |
dafe6c43 | 893 | tt->throttle = false; |
19789abb | 894 | tt->dynamic_smps = false; |
0c0e2c71 | 895 | tt->min_backoff = min_backoff; |
9ee718aa | 896 | INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill); |
c221daf2 CRI |
897 | |
898 | #ifdef CONFIG_THERMAL | |
5c89e7bc | 899 | iwl_mvm_cooling_device_register(mvm); |
c221daf2 CRI |
900 | iwl_mvm_thermal_zone_register(mvm); |
901 | #endif | |
9ee718aa EL |
902 | } |
903 | ||
c221daf2 | 904 | void iwl_mvm_thermal_exit(struct iwl_mvm *mvm) |
9ee718aa EL |
905 | { |
906 | cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit); | |
907 | IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n"); | |
c221daf2 CRI |
908 | |
909 | #ifdef CONFIG_THERMAL | |
5c89e7bc | 910 | iwl_mvm_cooling_device_unregister(mvm); |
c221daf2 CRI |
911 | iwl_mvm_thermal_zone_unregister(mvm); |
912 | #endif | |
9ee718aa | 913 | } |