Commit | Line | Data |
---|---|---|
8e99ea8d JB |
1 | // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
2 | /* | |
1a3e7039 | 3 | * Copyright (C) 2012-2014, 2019-2022 Intel Corporation |
8e99ea8d JB |
4 | * Copyright (C) 2013-2014 Intel Mobile Communications GmbH |
5 | * Copyright (C) 2015-2016 Intel Deutschland GmbH | |
6 | */ | |
c221daf2 CRI |
7 | #include <linux/sort.h> |
8 | ||
9ee718aa | 9 | #include "mvm.h" |
9ee718aa | 10 | |
a0a09243 | 11 | #define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT HZ |
9ee718aa | 12 | |
61d8c626 | 13 | void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm) |
9ee718aa | 14 | { |
19789abb | 15 | struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; |
3444682a | 16 | u32 duration = tt->params.ct_kill_duration; |
9ee718aa | 17 | |
a0a09243 LC |
18 | if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) |
19 | return; | |
9ee718aa | 20 | |
a0a09243 LC |
21 | IWL_ERR(mvm, "Enter CT Kill\n"); |
22 | iwl_mvm_set_hw_ctkill_state(mvm, true); | |
9ee718aa | 23 | |
c221daf2 CRI |
24 | if (!iwl_mvm_is_tt_in_fw(mvm)) { |
25 | tt->throttle = false; | |
26 | tt->dynamic_smps = false; | |
27 | } | |
19789abb | 28 | |
a0a09243 LC |
29 | /* Don't schedule an exit work if we're in test mode, since |
30 | * the temperature will not change unless we manually set it | |
31 | * again (or disable testing). | |
32 | */ | |
33 | if (!mvm->temperature_test) | |
19789abb | 34 | schedule_delayed_work(&tt->ct_kill_exit, |
a0a09243 | 35 | round_jiffies_relative(duration * HZ)); |
9ee718aa EL |
36 | } |
37 | ||
a0a09243 | 38 | static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm) |
9ee718aa | 39 | { |
a0a09243 LC |
40 | if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) |
41 | return; | |
9ee718aa | 42 | |
a0a09243 LC |
43 | IWL_ERR(mvm, "Exit CT Kill\n"); |
44 | iwl_mvm_set_hw_ctkill_state(mvm, false); | |
9ee718aa EL |
45 | } |
46 | ||
d8367b12 | 47 | static void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp) |
fd1f7550 LC |
48 | { |
49 | /* ignore the notification if we are in test mode */ | |
50 | if (mvm->temperature_test) | |
51 | return; | |
52 | ||
53 | if (mvm->temperature == temp) | |
54 | return; | |
55 | ||
56 | mvm->temperature = temp; | |
57 | iwl_mvm_tt_handler(mvm); | |
58 | } | |
59 | ||
60 | static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm, | |
61 | struct iwl_rx_packet *pkt) | |
9ee718aa | 62 | { |
c221daf2 | 63 | struct iwl_dts_measurement_notif_v1 *notif_v1; |
a0a09243 | 64 | int len = iwl_rx_packet_payload_len(pkt); |
fd1f7550 | 65 | int temp; |
a0a09243 | 66 | |
c221daf2 CRI |
67 | /* we can use notif_v1 only, because v2 only adds an additional |
68 | * parameter, which is not used in this function. | |
69 | */ | |
70 | if (WARN_ON_ONCE(len < sizeof(*notif_v1))) { | |
a0a09243 | 71 | IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); |
fd1f7550 | 72 | return -EINVAL; |
a0a09243 | 73 | } |
9ee718aa | 74 | |
c221daf2 | 75 | notif_v1 = (void *)pkt->data; |
9ee718aa | 76 | |
c221daf2 | 77 | temp = le32_to_cpu(notif_v1->temp); |
9ee718aa | 78 | |
a0a09243 | 79 | /* shouldn't be negative, but since it's s32, make sure it isn't */ |
fd1f7550 LC |
80 | if (WARN_ON_ONCE(temp < 0)) |
81 | temp = 0; | |
82 | ||
83 | IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp); | |
84 | ||
85 | return temp; | |
86 | } | |
87 | ||
88 | static bool iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data *notif_wait, | |
89 | struct iwl_rx_packet *pkt, void *data) | |
90 | { | |
91 | struct iwl_mvm *mvm = | |
92 | container_of(notif_wait, struct iwl_mvm, notif_wait); | |
93 | int *temp = data; | |
94 | int ret; | |
95 | ||
96 | ret = iwl_mvm_temp_notif_parse(mvm, pkt); | |
97 | if (ret < 0) | |
98 | return true; | |
99 | ||
100 | *temp = ret; | |
9ee718aa | 101 | |
a0a09243 | 102 | return true; |
9ee718aa EL |
103 | } |
104 | ||
0416841d | 105 | void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) |
ea9af24d LC |
106 | { |
107 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | |
c221daf2 CRI |
108 | struct iwl_dts_measurement_notif_v2 *notif_v2; |
109 | int len = iwl_rx_packet_payload_len(pkt); | |
ea9af24d | 110 | int temp; |
c221daf2 | 111 | u32 ths_crossed; |
ea9af24d LC |
112 | |
113 | /* the notification is handled synchronously in ctkill, so skip here */ | |
114 | if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) | |
0416841d | 115 | return; |
ea9af24d LC |
116 | |
117 | temp = iwl_mvm_temp_notif_parse(mvm, pkt); | |
c221daf2 CRI |
118 | |
119 | if (!iwl_mvm_is_tt_in_fw(mvm)) { | |
120 | if (temp >= 0) | |
121 | iwl_mvm_tt_temp_changed(mvm, temp); | |
122 | return; | |
123 | } | |
124 | ||
125 | if (WARN_ON_ONCE(len < sizeof(*notif_v2))) { | |
126 | IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); | |
127 | return; | |
128 | } | |
129 | ||
130 | notif_v2 = (void *)pkt->data; | |
131 | ths_crossed = le32_to_cpu(notif_v2->threshold_idx); | |
132 | ||
133 | /* 0xFF in ths_crossed means the notification is not related | |
134 | * to a trip, so we can ignore it here. | |
135 | */ | |
136 | if (ths_crossed == 0xFF) | |
137 | return; | |
138 | ||
139 | IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n", | |
140 | temp, ths_crossed); | |
141 | ||
142 | #ifdef CONFIG_THERMAL | |
143 | if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS)) | |
0416841d | 144 | return; |
ea9af24d | 145 | |
91f66a3c EG |
146 | if (mvm->tz_device.tzone) { |
147 | struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device; | |
148 | ||
93effd83 TG |
149 | thermal_zone_device_update(tz_dev->tzone, |
150 | THERMAL_TRIP_VIOLATED); | |
91f66a3c | 151 | } |
c221daf2 | 152 | #endif /* CONFIG_THERMAL */ |
ea9af24d LC |
153 | } |
154 | ||
0a3b7119 CRI |
155 | void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) |
156 | { | |
157 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | |
158 | struct ct_kill_notif *notif; | |
0a3b7119 CRI |
159 | |
160 | notif = (struct ct_kill_notif *)pkt->data; | |
161 | IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n", | |
162 | notif->temperature); | |
5c7fd9dc MK |
163 | if (iwl_fw_lookup_notif_ver(mvm->fw, PHY_OPS_GROUP, |
164 | CT_KILL_NOTIFICATION, 0) > 1) | |
165 | IWL_DEBUG_TEMP(mvm, | |
166 | "CT kill notification DTS bitmap = 0x%x, Scheme = %d\n", | |
167 | notif->dts, notif->scheme); | |
0a3b7119 CRI |
168 | |
169 | iwl_mvm_enter_ctkill(mvm); | |
170 | } | |
171 | ||
762c523f GA |
172 | /* |
173 | * send the DTS_MEASUREMENT_TRIGGER command with or without waiting for a | |
174 | * response. If we get a response then the measurement is stored in 'temp' | |
175 | */ | |
176 | static int iwl_mvm_send_temp_cmd(struct iwl_mvm *mvm, bool response, s32 *temp) | |
9ee718aa | 177 | { |
762c523f GA |
178 | struct iwl_host_cmd cmd = {}; |
179 | struct iwl_dts_measurement_cmd dts_cmd = { | |
a0a09243 | 180 | .flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP), |
9ee718aa | 181 | }; |
762c523f | 182 | struct iwl_ext_dts_measurement_cmd ext_cmd = { |
6bd5fa33 | 183 | .control_mode = cpu_to_le32(DTS_DIRECT_WITHOUT_MEASURE), |
78efc702 | 184 | }; |
762c523f GA |
185 | struct iwl_dts_measurement_resp *resp; |
186 | void *cmd_ptr; | |
187 | int ret; | |
188 | u32 cmd_flags = 0; | |
189 | u16 len; | |
190 | ||
191 | /* Check which command format is used (regular/extended) */ | |
192 | if (fw_has_capa(&mvm->fw->ucode_capa, | |
193 | IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) { | |
194 | len = sizeof(ext_cmd); | |
195 | cmd_ptr = &ext_cmd; | |
196 | } else { | |
197 | len = sizeof(dts_cmd); | |
198 | cmd_ptr = &dts_cmd; | |
199 | } | |
200 | /* The command version where we get a response is zero length */ | |
201 | if (response) { | |
202 | cmd_flags = CMD_WANT_SKB; | |
203 | len = 0; | |
204 | } | |
09eef330 | 205 | |
762c523f GA |
206 | cmd.id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE); |
207 | cmd.len[0] = len; | |
208 | cmd.flags = cmd_flags; | |
209 | cmd.data[0] = cmd_ptr; | |
210 | ||
211 | IWL_DEBUG_TEMP(mvm, | |
212 | "Sending temperature measurement command - %s response\n", | |
213 | response ? "with" : "without"); | |
214 | ret = iwl_mvm_send_cmd(mvm, &cmd); | |
215 | ||
216 | if (ret) { | |
217 | IWL_ERR(mvm, | |
218 | "Failed to send the temperature measurement command (err=%d)\n", | |
219 | ret); | |
220 | return ret; | |
221 | } | |
78efc702 | 222 | |
762c523f GA |
223 | if (response) { |
224 | resp = (void *)cmd.resp_pkt->data; | |
225 | *temp = le32_to_cpu(resp->temp); | |
226 | IWL_DEBUG_TEMP(mvm, | |
227 | "Got temperature measurement response: temp=%d\n", | |
228 | *temp); | |
229 | iwl_free_resp(&cmd); | |
230 | } | |
78efc702 | 231 | |
762c523f | 232 | return ret; |
9ee718aa EL |
233 | } |
234 | ||
7869318e | 235 | int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp) |
9ee718aa | 236 | { |
a0a09243 | 237 | struct iwl_notification_wait wait_temp_notif; |
09eef330 AE |
238 | static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP, |
239 | DTS_MEASUREMENT_NOTIF_WIDE) }; | |
7869318e | 240 | int ret; |
762c523f GA |
241 | u8 cmd_ver; |
242 | ||
243 | /* | |
244 | * If command version is 1 we send the command and immediately get | |
245 | * a response. For older versions we send the command and wait for a | |
246 | * notification (no command TLV for previous versions). | |
247 | */ | |
971cbe50 JB |
248 | cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, |
249 | WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE), | |
762c523f GA |
250 | IWL_FW_CMD_VER_UNKNOWN); |
251 | if (cmd_ver == 1) | |
252 | return iwl_mvm_send_temp_cmd(mvm, true, temp); | |
9ee718aa | 253 | |
a0a09243 | 254 | lockdep_assert_held(&mvm->mutex); |
b689fa79 | 255 | |
a0a09243 LC |
256 | iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif, |
257 | temp_notif, ARRAY_SIZE(temp_notif), | |
7869318e | 258 | iwl_mvm_temp_notif_wait, temp); |
efc36dbd | 259 | |
762c523f | 260 | ret = iwl_mvm_send_temp_cmd(mvm, false, temp); |
a0a09243 | 261 | if (ret) { |
a0a09243 LC |
262 | iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif); |
263 | return ret; | |
264 | } | |
9ee718aa | 265 | |
a0a09243 LC |
266 | ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif, |
267 | IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT); | |
7869318e | 268 | if (ret) |
c61734a6 | 269 | IWL_WARN(mvm, "Getting the temperature timed out\n"); |
b689fa79 | 270 | |
7869318e | 271 | return ret; |
9ee718aa EL |
272 | } |
273 | ||
274 | static void check_exit_ctkill(struct work_struct *work) | |
275 | { | |
276 | struct iwl_mvm_tt_mgmt *tt; | |
277 | struct iwl_mvm *mvm; | |
278 | u32 duration; | |
279 | s32 temp; | |
7869318e | 280 | int ret; |
9ee718aa EL |
281 | |
282 | tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work); | |
283 | mvm = container_of(tt, struct iwl_mvm, thermal_throttle); | |
284 | ||
0a3b7119 CRI |
285 | if (iwl_mvm_is_tt_in_fw(mvm)) { |
286 | iwl_mvm_exit_ctkill(mvm); | |
287 | ||
288 | return; | |
289 | } | |
290 | ||
3444682a | 291 | duration = tt->params.ct_kill_duration; |
9ee718aa | 292 | |
f9084775 NE |
293 | flush_work(&mvm->roc_done_wk); |
294 | ||
a0a09243 LC |
295 | mutex_lock(&mvm->mutex); |
296 | ||
297 | if (__iwl_mvm_mac_start(mvm)) | |
298 | goto reschedule; | |
299 | ||
7869318e | 300 | ret = iwl_mvm_get_temp(mvm, &temp); |
9ee718aa | 301 | |
a0a09243 LC |
302 | __iwl_mvm_mac_stop(mvm); |
303 | ||
7869318e | 304 | if (ret) |
9ee718aa | 305 | goto reschedule; |
a0a09243 | 306 | |
9ee718aa EL |
307 | IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp); |
308 | ||
3444682a | 309 | if (temp <= tt->params.ct_kill_exit) { |
a0a09243 | 310 | mutex_unlock(&mvm->mutex); |
9ee718aa EL |
311 | iwl_mvm_exit_ctkill(mvm); |
312 | return; | |
313 | } | |
314 | ||
315 | reschedule: | |
a0a09243 | 316 | mutex_unlock(&mvm->mutex); |
9ee718aa EL |
317 | schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit, |
318 | round_jiffies(duration * HZ)); | |
319 | } | |
320 | ||
321 | static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac, | |
322 | struct ieee80211_vif *vif) | |
323 | { | |
324 | struct iwl_mvm *mvm = _data; | |
325 | enum ieee80211_smps_mode smps_mode; | |
326 | ||
327 | lockdep_assert_held(&mvm->mutex); | |
328 | ||
329 | if (mvm->thermal_throttle.dynamic_smps) | |
330 | smps_mode = IEEE80211_SMPS_DYNAMIC; | |
331 | else | |
332 | smps_mode = IEEE80211_SMPS_AUTOMATIC; | |
333 | ||
fded313e EL |
334 | if (vif->type != NL80211_IFTYPE_STATION) |
335 | return; | |
336 | ||
1a3e7039 | 337 | iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode, 0); |
9ee718aa EL |
338 | } |
339 | ||
340 | static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable) | |
341 | { | |
9ee718aa EL |
342 | struct iwl_mvm_sta *mvmsta; |
343 | int i, err; | |
344 | ||
be9ae34e | 345 | for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { |
13303c0f SS |
346 | mvmsta = iwl_mvm_sta_from_staid_protected(mvm, i); |
347 | if (!mvmsta) | |
9ee718aa | 348 | continue; |
13303c0f | 349 | |
9ee718aa EL |
350 | if (enable == mvmsta->tt_tx_protection) |
351 | continue; | |
e126b5d9 | 352 | err = iwl_mvm_tx_protection(mvm, mvmsta, enable); |
9ee718aa EL |
353 | if (err) { |
354 | IWL_ERR(mvm, "Failed to %s Tx protection\n", | |
355 | enable ? "enable" : "disable"); | |
356 | } else { | |
357 | IWL_DEBUG_TEMP(mvm, "%s Tx protection\n", | |
358 | enable ? "Enable" : "Disable"); | |
359 | mvmsta->tt_tx_protection = enable; | |
360 | } | |
361 | } | |
362 | } | |
363 | ||
0c0e2c71 | 364 | void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff) |
9ee718aa EL |
365 | { |
366 | struct iwl_host_cmd cmd = { | |
367 | .id = REPLY_THERMAL_MNG_BACKOFF, | |
368 | .len = { sizeof(u32), }, | |
369 | .data = { &backoff, }, | |
9ee718aa EL |
370 | }; |
371 | ||
0c0e2c71 IY |
372 | backoff = max(backoff, mvm->thermal_throttle.min_backoff); |
373 | ||
9ee718aa EL |
374 | if (iwl_mvm_send_cmd(mvm, &cmd) == 0) { |
375 | IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n", | |
376 | backoff); | |
377 | mvm->thermal_throttle.tx_backoff = backoff; | |
378 | } else { | |
379 | IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n"); | |
380 | } | |
381 | } | |
382 | ||
383 | void iwl_mvm_tt_handler(struct iwl_mvm *mvm) | |
384 | { | |
3444682a | 385 | struct iwl_tt_params *params = &mvm->thermal_throttle.params; |
9ee718aa EL |
386 | struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; |
387 | s32 temperature = mvm->temperature; | |
dafe6c43 | 388 | bool throttle_enable = false; |
9ee718aa EL |
389 | int i; |
390 | u32 tx_backoff; | |
391 | ||
392 | IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature); | |
393 | ||
394 | if (params->support_ct_kill && temperature >= params->ct_kill_entry) { | |
395 | iwl_mvm_enter_ctkill(mvm); | |
396 | return; | |
397 | } | |
398 | ||
b689fa79 | 399 | if (params->support_ct_kill && |
3444682a | 400 | temperature <= params->ct_kill_exit) { |
b689fa79 LC |
401 | iwl_mvm_exit_ctkill(mvm); |
402 | return; | |
403 | } | |
404 | ||
9ee718aa EL |
405 | if (params->support_dynamic_smps) { |
406 | if (!tt->dynamic_smps && | |
407 | temperature >= params->dynamic_smps_entry) { | |
408 | IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n"); | |
409 | tt->dynamic_smps = true; | |
410 | ieee80211_iterate_active_interfaces_atomic( | |
411 | mvm->hw, IEEE80211_IFACE_ITER_NORMAL, | |
412 | iwl_mvm_tt_smps_iterator, mvm); | |
dafe6c43 | 413 | throttle_enable = true; |
9ee718aa EL |
414 | } else if (tt->dynamic_smps && |
415 | temperature <= params->dynamic_smps_exit) { | |
416 | IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n"); | |
417 | tt->dynamic_smps = false; | |
418 | ieee80211_iterate_active_interfaces_atomic( | |
419 | mvm->hw, IEEE80211_IFACE_ITER_NORMAL, | |
420 | iwl_mvm_tt_smps_iterator, mvm); | |
421 | } | |
422 | } | |
423 | ||
424 | if (params->support_tx_protection) { | |
dafe6c43 | 425 | if (temperature >= params->tx_protection_entry) { |
9ee718aa | 426 | iwl_mvm_tt_tx_protection(mvm, true); |
dafe6c43 | 427 | throttle_enable = true; |
428 | } else if (temperature <= params->tx_protection_exit) { | |
9ee718aa | 429 | iwl_mvm_tt_tx_protection(mvm, false); |
dafe6c43 | 430 | } |
9ee718aa EL |
431 | } |
432 | ||
433 | if (params->support_tx_backoff) { | |
1b8ebbd3 | 434 | tx_backoff = tt->min_backoff; |
9ee718aa EL |
435 | for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) { |
436 | if (temperature < params->tx_backoff[i].temperature) | |
437 | break; | |
1b8ebbd3 EL |
438 | tx_backoff = max(tt->min_backoff, |
439 | params->tx_backoff[i].backoff); | |
9ee718aa | 440 | } |
1b8ebbd3 | 441 | if (tx_backoff != tt->min_backoff) |
dafe6c43 | 442 | throttle_enable = true; |
9ee718aa EL |
443 | if (tt->tx_backoff != tx_backoff) |
444 | iwl_mvm_tt_tx_backoff(mvm, tx_backoff); | |
445 | } | |
dafe6c43 | 446 | |
447 | if (!tt->throttle && throttle_enable) { | |
448 | IWL_WARN(mvm, | |
449 | "Due to high temperature thermal throttling initiated\n"); | |
450 | tt->throttle = true; | |
19a04bdd EL |
451 | } else if (tt->throttle && !tt->dynamic_smps && |
452 | tt->tx_backoff == tt->min_backoff && | |
dafe6c43 | 453 | temperature <= params->tx_protection_exit) { |
454 | IWL_WARN(mvm, | |
455 | "Temperature is back to normal thermal throttling stopped\n"); | |
456 | tt->throttle = false; | |
457 | } | |
9ee718aa EL |
458 | } |
459 | ||
3444682a | 460 | static const struct iwl_tt_params iwl_mvm_default_tt_params = { |
9ee718aa EL |
461 | .ct_kill_entry = 118, |
462 | .ct_kill_exit = 96, | |
463 | .ct_kill_duration = 5, | |
464 | .dynamic_smps_entry = 114, | |
465 | .dynamic_smps_exit = 110, | |
466 | .tx_protection_entry = 114, | |
467 | .tx_protection_exit = 108, | |
468 | .tx_backoff = { | |
469 | {.temperature = 112, .backoff = 200}, | |
470 | {.temperature = 113, .backoff = 600}, | |
471 | {.temperature = 114, .backoff = 1200}, | |
472 | {.temperature = 115, .backoff = 2000}, | |
473 | {.temperature = 116, .backoff = 4000}, | |
474 | {.temperature = 117, .backoff = 10000}, | |
475 | }, | |
476 | .support_ct_kill = true, | |
477 | .support_dynamic_smps = true, | |
478 | .support_tx_protection = true, | |
479 | .support_tx_backoff = true, | |
480 | }; | |
481 | ||
b358993b CRI |
482 | /* budget in mWatt */ |
483 | static const u32 iwl_mvm_cdev_budgets[] = { | |
220089c7 MG |
484 | 2400, /* cooling state 0 */ |
485 | 2000, /* cooling state 1 */ | |
486 | 1800, /* cooling state 2 */ | |
487 | 1600, /* cooling state 3 */ | |
488 | 1400, /* cooling state 4 */ | |
489 | 1200, /* cooling state 5 */ | |
490 | 1000, /* cooling state 6 */ | |
491 | 900, /* cooling state 7 */ | |
492 | 800, /* cooling state 8 */ | |
493 | 700, /* cooling state 9 */ | |
494 | 650, /* cooling state 10 */ | |
495 | 600, /* cooling state 11 */ | |
496 | 550, /* cooling state 12 */ | |
497 | 500, /* cooling state 13 */ | |
498 | 450, /* cooling state 14 */ | |
499 | 400, /* cooling state 15 */ | |
500 | 350, /* cooling state 16 */ | |
501 | 300, /* cooling state 17 */ | |
502 | 250, /* cooling state 18 */ | |
503 | 200, /* cooling state 19 */ | |
504 | 150, /* cooling state 20 */ | |
b358993b CRI |
505 | }; |
506 | ||
507 | int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 state) | |
00f481bd CRI |
508 | { |
509 | struct iwl_mvm_ctdp_cmd cmd = { | |
510 | .operation = cpu_to_le32(op), | |
b358993b | 511 | .budget = cpu_to_le32(iwl_mvm_cdev_budgets[state]), |
00f481bd CRI |
512 | .window_size = 0, |
513 | }; | |
514 | int ret; | |
515 | u32 status; | |
516 | ||
517 | lockdep_assert_held(&mvm->mutex); | |
518 | ||
d460f1fb | 519 | status = 0; |
00f481bd CRI |
520 | ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP, |
521 | CTDP_CONFIG_CMD), | |
522 | sizeof(cmd), &cmd, &status); | |
523 | ||
524 | if (ret) { | |
525 | IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret); | |
526 | return ret; | |
527 | } | |
528 | ||
529 | switch (op) { | |
530 | case CTDP_CMD_OPERATION_START: | |
531 | #ifdef CONFIG_THERMAL | |
b358993b | 532 | mvm->cooling_dev.cur_state = state; |
00f481bd CRI |
533 | #endif /* CONFIG_THERMAL */ |
534 | break; | |
535 | case CTDP_CMD_OPERATION_REPORT: | |
536 | IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status); | |
537 | /* when the function is called with CTDP_CMD_OPERATION_REPORT | |
538 | * option the function should return the average budget value | |
539 | * that is received from the FW. | |
540 | * The budget can't be less or equal to 0, so it's possible | |
541 | * to distinguish between error values and budgets. | |
542 | */ | |
543 | return status; | |
544 | case CTDP_CMD_OPERATION_STOP: | |
545 | IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n"); | |
546 | break; | |
547 | } | |
548 | ||
549 | return 0; | |
550 | } | |
551 | ||
c221daf2 CRI |
552 | #ifdef CONFIG_THERMAL |
553 | static int compare_temps(const void *a, const void *b) | |
554 | { | |
555 | return ((s16)le16_to_cpu(*(__le16 *)a) - | |
556 | (s16)le16_to_cpu(*(__le16 *)b)); | |
557 | } | |
2d88b2cf | 558 | #endif |
c221daf2 CRI |
559 | |
560 | int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm) | |
561 | { | |
562 | struct temp_report_ths_cmd cmd = {0}; | |
2d88b2cf JB |
563 | int ret; |
564 | #ifdef CONFIG_THERMAL | |
565 | int i, j, idx = 0; | |
c221daf2 CRI |
566 | |
567 | lockdep_assert_held(&mvm->mutex); | |
568 | ||
91f66a3c | 569 | if (!mvm->tz_device.tzone) |
2d88b2cf | 570 | goto send; |
91f66a3c | 571 | |
c221daf2 CRI |
572 | /* The driver holds array of temperature trips that are unsorted |
573 | * and uncompressed, the FW should get it compressed and sorted | |
574 | */ | |
575 | ||
3d2f20ad | 576 | /* compress trips to cmd array, remove uninitialized values*/ |
91f66a3c | 577 | for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) { |
3d2f20ad | 578 | if (mvm->tz_device.trips[i].temperature != INT_MIN) { |
c221daf2 | 579 | cmd.thresholds[idx++] = |
3d2f20ad | 580 | cpu_to_le16((s16)(mvm->tz_device.trips[i].temperature / 1000)); |
c221daf2 | 581 | } |
91f66a3c | 582 | } |
c221daf2 CRI |
583 | cmd.num_temps = cpu_to_le32(idx); |
584 | ||
585 | if (!idx) | |
586 | goto send; | |
587 | ||
588 | /*sort cmd array*/ | |
589 | sort(cmd.thresholds, idx, sizeof(s16), compare_temps, NULL); | |
590 | ||
591 | /* we should save the indexes of trips because we sort | |
592 | * and compress the orginal array | |
593 | */ | |
594 | for (i = 0; i < idx; i++) { | |
595 | for (j = 0; j < IWL_MAX_DTS_TRIPS; j++) { | |
3d2f20ad DL |
596 | if ((int)(le16_to_cpu(cmd.thresholds[i]) * 1000) == |
597 | mvm->tz_device.trips[j].temperature) | |
c221daf2 CRI |
598 | mvm->tz_device.fw_trips_index[i] = j; |
599 | } | |
600 | } | |
601 | ||
602 | send: | |
2d88b2cf | 603 | #endif |
c221daf2 CRI |
604 | ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP, |
605 | TEMP_REPORTING_THRESHOLDS_CMD), | |
606 | 0, sizeof(cmd), &cmd); | |
607 | if (ret) | |
608 | IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n", | |
609 | ret); | |
610 | ||
611 | return ret; | |
612 | } | |
613 | ||
2d88b2cf | 614 | #ifdef CONFIG_THERMAL |
c221daf2 CRI |
615 | static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device, |
616 | int *temperature) | |
617 | { | |
3d4e1bad | 618 | struct iwl_mvm *mvm = thermal_zone_device_priv(device); |
c221daf2 CRI |
619 | int ret; |
620 | int temp; | |
621 | ||
622 | mutex_lock(&mvm->mutex); | |
623 | ||
aab6930d | 624 | if (!iwl_mvm_firmware_running(mvm) || |
702e975d | 625 | mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { |
1442a9a9 | 626 | ret = -ENODATA; |
c221daf2 CRI |
627 | goto out; |
628 | } | |
629 | ||
630 | ret = iwl_mvm_get_temp(mvm, &temp); | |
631 | if (ret) | |
632 | goto out; | |
633 | ||
634 | *temperature = temp * 1000; | |
635 | ||
636 | out: | |
637 | mutex_unlock(&mvm->mutex); | |
638 | return ret; | |
639 | } | |
640 | ||
c221daf2 CRI |
641 | static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device, |
642 | int trip, int temp) | |
643 | { | |
3d4e1bad | 644 | struct iwl_mvm *mvm = thermal_zone_device_priv(device); |
3d2f20ad | 645 | int ret; |
c221daf2 CRI |
646 | |
647 | mutex_lock(&mvm->mutex); | |
648 | ||
aab6930d | 649 | if (!iwl_mvm_firmware_running(mvm) || |
702e975d | 650 | mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { |
c221daf2 CRI |
651 | ret = -EIO; |
652 | goto out; | |
653 | } | |
654 | ||
c221daf2 CRI |
655 | if ((temp / 1000) > S16_MAX) { |
656 | ret = -EINVAL; | |
657 | goto out; | |
658 | } | |
659 | ||
c221daf2 CRI |
660 | ret = iwl_mvm_send_temp_report_ths_cmd(mvm); |
661 | out: | |
662 | mutex_unlock(&mvm->mutex); | |
663 | return ret; | |
664 | } | |
665 | ||
666 | static struct thermal_zone_device_ops tzone_ops = { | |
667 | .get_temp = iwl_mvm_tzone_get_temp, | |
c221daf2 CRI |
668 | .set_trip_temp = iwl_mvm_tzone_set_trip_temp, |
669 | }; | |
670 | ||
671 | /* make all trips writable */ | |
672 | #define IWL_WRITABLE_TRIPS_MSK (BIT(IWL_MAX_DTS_TRIPS) - 1) | |
673 | ||
674 | static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm) | |
675 | { | |
bbcf90c0 | 676 | int i, ret; |
baa6cf84 AO |
677 | char name[16]; |
678 | static atomic_t counter = ATOMIC_INIT(0); | |
c221daf2 CRI |
679 | |
680 | if (!iwl_mvm_is_tt_in_fw(mvm)) { | |
681 | mvm->tz_device.tzone = NULL; | |
682 | ||
683 | return; | |
684 | } | |
685 | ||
686 | BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); | |
687 | ||
baa6cf84 | 688 | sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF); |
3d2f20ad DL |
689 | mvm->tz_device.tzone = thermal_zone_device_register_with_trips(name, |
690 | mvm->tz_device.trips, | |
c221daf2 CRI |
691 | IWL_MAX_DTS_TRIPS, |
692 | IWL_WRITABLE_TRIPS_MSK, | |
693 | mvm, &tzone_ops, | |
694 | NULL, 0, 0); | |
695 | if (IS_ERR(mvm->tz_device.tzone)) { | |
696 | IWL_DEBUG_TEMP(mvm, | |
697 | "Failed to register to thermal zone (err = %ld)\n", | |
698 | PTR_ERR(mvm->tz_device.tzone)); | |
91f66a3c | 699 | mvm->tz_device.tzone = NULL; |
c221daf2 CRI |
700 | return; |
701 | } | |
702 | ||
bbcf90c0 AP |
703 | ret = thermal_zone_device_enable(mvm->tz_device.tzone); |
704 | if (ret) { | |
705 | IWL_DEBUG_TEMP(mvm, "Failed to enable thermal zone\n"); | |
706 | thermal_zone_device_unregister(mvm->tz_device.tzone); | |
707 | return; | |
708 | } | |
709 | ||
c221daf2 CRI |
710 | /* 0 is a valid temperature, |
711 | * so initialize the array with S16_MIN which invalid temperature | |
712 | */ | |
3d2f20ad DL |
713 | for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++) { |
714 | mvm->tz_device.trips[i].temperature = INT_MIN; | |
715 | mvm->tz_device.trips[i].type = THERMAL_TRIP_PASSIVE; | |
716 | } | |
c221daf2 CRI |
717 | } |
718 | ||
5c89e7bc CRI |
719 | static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev, |
720 | unsigned long *state) | |
721 | { | |
722 | *state = ARRAY_SIZE(iwl_mvm_cdev_budgets) - 1; | |
723 | ||
724 | return 0; | |
725 | } | |
726 | ||
727 | static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev, | |
728 | unsigned long *state) | |
729 | { | |
730 | struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); | |
731 | ||
5c89e7bc | 732 | *state = mvm->cooling_dev.cur_state; |
b358993b | 733 | |
5c89e7bc CRI |
734 | return 0; |
735 | } | |
736 | ||
737 | static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev, | |
738 | unsigned long new_state) | |
739 | { | |
740 | struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); | |
741 | int ret; | |
742 | ||
5c89e7bc CRI |
743 | mutex_lock(&mvm->mutex); |
744 | ||
aab6930d | 745 | if (!iwl_mvm_firmware_running(mvm) || |
702e975d | 746 | mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { |
d9954405 JB |
747 | ret = -EIO; |
748 | goto unlock; | |
749 | } | |
750 | ||
5c89e7bc CRI |
751 | if (new_state >= ARRAY_SIZE(iwl_mvm_cdev_budgets)) { |
752 | ret = -EINVAL; | |
753 | goto unlock; | |
754 | } | |
755 | ||
756 | ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START, | |
b358993b | 757 | new_state); |
5c89e7bc CRI |
758 | |
759 | unlock: | |
760 | mutex_unlock(&mvm->mutex); | |
761 | return ret; | |
762 | } | |
763 | ||
b1a1efc5 | 764 | static const struct thermal_cooling_device_ops tcooling_ops = { |
5c89e7bc CRI |
765 | .get_max_state = iwl_mvm_tcool_get_max_state, |
766 | .get_cur_state = iwl_mvm_tcool_get_cur_state, | |
767 | .set_cur_state = iwl_mvm_tcool_set_cur_state, | |
768 | }; | |
769 | ||
91f66a3c | 770 | static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm) |
5c89e7bc CRI |
771 | { |
772 | char name[] = "iwlwifi"; | |
773 | ||
91f66a3c EG |
774 | if (!iwl_mvm_is_ctdp_supported(mvm)) |
775 | return; | |
5c89e7bc CRI |
776 | |
777 | BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); | |
778 | ||
779 | mvm->cooling_dev.cdev = | |
780 | thermal_cooling_device_register(name, | |
781 | mvm, | |
782 | &tcooling_ops); | |
783 | ||
784 | if (IS_ERR(mvm->cooling_dev.cdev)) { | |
785 | IWL_DEBUG_TEMP(mvm, | |
786 | "Failed to register to cooling device (err = %ld)\n", | |
787 | PTR_ERR(mvm->cooling_dev.cdev)); | |
91f66a3c EG |
788 | mvm->cooling_dev.cdev = NULL; |
789 | return; | |
5c89e7bc | 790 | } |
5c89e7bc CRI |
791 | } |
792 | ||
c221daf2 CRI |
793 | static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm) |
794 | { | |
91f66a3c | 795 | if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone) |
c221daf2 CRI |
796 | return; |
797 | ||
91f66a3c | 798 | IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n"); |
92549cdc JA |
799 | if (mvm->tz_device.tzone) { |
800 | thermal_zone_device_unregister(mvm->tz_device.tzone); | |
801 | mvm->tz_device.tzone = NULL; | |
802 | } | |
c221daf2 | 803 | } |
5c89e7bc CRI |
804 | |
805 | static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm) | |
806 | { | |
91f66a3c | 807 | if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev) |
5c89e7bc CRI |
808 | return; |
809 | ||
91f66a3c | 810 | IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n"); |
92549cdc JA |
811 | if (mvm->cooling_dev.cdev) { |
812 | thermal_cooling_device_unregister(mvm->cooling_dev.cdev); | |
813 | mvm->cooling_dev.cdev = NULL; | |
814 | } | |
5c89e7bc | 815 | } |
c221daf2 CRI |
816 | #endif /* CONFIG_THERMAL */ |
817 | ||
818 | void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff) | |
9ee718aa EL |
819 | { |
820 | struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; | |
821 | ||
822 | IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n"); | |
6be497f2 | 823 | |
3444682a CRI |
824 | if (mvm->cfg->thermal_params) |
825 | tt->params = *mvm->cfg->thermal_params; | |
6be497f2 | 826 | else |
3444682a | 827 | tt->params = iwl_mvm_default_tt_params; |
6be497f2 | 828 | |
dafe6c43 | 829 | tt->throttle = false; |
19789abb | 830 | tt->dynamic_smps = false; |
0c0e2c71 | 831 | tt->min_backoff = min_backoff; |
9ee718aa | 832 | INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill); |
c221daf2 CRI |
833 | |
834 | #ifdef CONFIG_THERMAL | |
5c89e7bc | 835 | iwl_mvm_cooling_device_register(mvm); |
c221daf2 CRI |
836 | iwl_mvm_thermal_zone_register(mvm); |
837 | #endif | |
de8ba41b | 838 | mvm->init_status |= IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE; |
9ee718aa EL |
839 | } |
840 | ||
c221daf2 | 841 | void iwl_mvm_thermal_exit(struct iwl_mvm *mvm) |
9ee718aa | 842 | { |
de8ba41b LK |
843 | if (!(mvm->init_status & IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE)) |
844 | return; | |
845 | ||
9ee718aa EL |
846 | cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit); |
847 | IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n"); | |
c221daf2 CRI |
848 | |
849 | #ifdef CONFIG_THERMAL | |
5c89e7bc | 850 | iwl_mvm_cooling_device_unregister(mvm); |
c221daf2 CRI |
851 | iwl_mvm_thermal_zone_unregister(mvm); |
852 | #endif | |
de8ba41b | 853 | mvm->init_status &= ~IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE; |
9ee718aa | 854 | } |