1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2/*
3 * Copyright (C) 2024-2025 Intel Corporation
4 */
5#ifdef CONFIG_THERMAL
6#include <linux/sort.h>
7#include <linux/thermal.h>
8#endif
9
10#include "fw/api/phy.h"
11
12#include "thermal.h"
13#include "mld.h"
14#include "hcmd.h"
15
16#define IWL_MLD_NUM_CTDP_STEPS 20
17#define IWL_MLD_MIN_CTDP_BUDGET_MW 150
18
19#define IWL_MLD_CT_KILL_DURATION (5 * HZ)
20
21void iwl_mld_handle_ct_kill_notif(struct iwl_mld *mld,
22 struct iwl_rx_packet *pkt)
23{
24 const struct ct_kill_notif *notif = (const void *)pkt->data;
25
26 IWL_ERR(mld,
27 "CT Kill notification: temp = %d, DTS = 0x%x, Scheme 0x%x - Enter CT Kill\n",
28 le16_to_cpu(notif->temperature), notif->dts,
29 notif->scheme);
30
31 iwl_mld_set_ctkill(mld, state: true);
32
33 wiphy_delayed_work_queue(wiphy: mld->wiphy, dwork: &mld->ct_kill_exit_wk,
34 delay: round_jiffies_relative(IWL_MLD_CT_KILL_DURATION));
35}
36
37static void iwl_mld_exit_ctkill(struct wiphy *wiphy, struct wiphy_work *wk)
38{
39 struct iwl_mld *mld;
40
41 mld = container_of(wk, struct iwl_mld, ct_kill_exit_wk.work);
42
43 IWL_ERR(mld, "Exit CT Kill\n");
44 iwl_mld_set_ctkill(mld, state: false);
45}
46
47void iwl_mld_handle_temp_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt)
48{
49 const struct iwl_dts_measurement_notif *notif =
50 (const void *)pkt->data;
51 int temp;
52 u32 ths_crossed;
53
54 temp = le32_to_cpu(notif->temp);
55
56 /* shouldn't be negative, but since it's s32, make sure it isn't */
57 if (IWL_FW_CHECK(mld, temp < 0, "negative temperature %d\n", temp))
58 return;
59
60 ths_crossed = le32_to_cpu(notif->threshold_idx);
61
62 /* 0xFF in ths_crossed means the notification is not related
63 * to a trip, so we can ignore it here.
64 */
65 if (ths_crossed == 0xFF)
66 return;
67
68 IWL_DEBUG_TEMP(mld, "Temp = %d Threshold crossed = %d\n",
69 temp, ths_crossed);
70
71 if (IWL_FW_CHECK(mld, ths_crossed >= IWL_MAX_DTS_TRIPS,
72 "bad threshold: %d\n", ths_crossed))
73 return;
74
75#ifdef CONFIG_THERMAL
76 if (mld->tzone)
77 thermal_zone_device_update(mld->tzone, THERMAL_TRIP_VIOLATED);
78#endif /* CONFIG_THERMAL */
79}
80
81#ifdef CONFIG_THERMAL
82static int iwl_mld_get_temp(struct iwl_mld *mld, s32 *temp)
83{
84 struct iwl_host_cmd cmd = {
85 .id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE),
86 .flags = CMD_WANT_SKB,
87 };
88 const struct iwl_dts_measurement_resp *resp;
89 int ret;
90
91 lockdep_assert_wiphy(mld->wiphy);
92
93 ret = iwl_mld_send_cmd(mld, cmd: &cmd);
94 if (ret) {
95 IWL_ERR(mld,
96 "Failed to send the temperature measurement command (err=%d)\n",
97 ret);
98 return ret;
99 }
100
101 if (iwl_rx_packet_payload_len(pkt: cmd.resp_pkt) < sizeof(*resp)) {
102 IWL_ERR(mld,
103 "Failed to get a valid response to DTS measurement\n");
104 ret = -EIO;
105 goto free_resp;
106 }
107
108 resp = (const void *)cmd.resp_pkt->data;
109 *temp = le32_to_cpu(resp->temp);
110
111 IWL_DEBUG_TEMP(mld,
112 "Got temperature measurement response: temp=%d\n",
113 *temp);
114
115free_resp:
116 iwl_free_resp(cmd: &cmd);
117 return ret;
118}
119
120static int compare_temps(const void *a, const void *b)
121{
122 return ((s16)le16_to_cpu(*(const __le16 *)a) -
123 (s16)le16_to_cpu(*(const __le16 *)b));
124}
125
126struct iwl_trip_walk_data {
127 __le16 *thresholds;
128 int count;
129};
130
131static int iwl_trip_temp_iter(struct thermal_trip *trip, void *arg)
132{
133 struct iwl_trip_walk_data *twd = arg;
134
135 if (trip->temperature == THERMAL_TEMP_INVALID)
136 return 0;
137
138 twd->thresholds[twd->count++] =
139 cpu_to_le16((s16)(trip->temperature / 1000));
140 return 0;
141}
142#endif
143
144int iwl_mld_config_temp_report_ths(struct iwl_mld *mld)
145{
146 struct temp_report_ths_cmd cmd = {0};
147 int ret;
148#ifdef CONFIG_THERMAL
149 struct iwl_trip_walk_data twd = {
150 .thresholds = cmd.thresholds,
151 .count = 0
152 };
153
154 if (!mld->tzone)
155 goto send;
156
157 /* The thermal core holds an array of temperature trips that are
158 * unsorted and uncompressed, the FW should get it compressed and
159 * sorted.
160 */
161
162 /* compress trips to cmd array, remove uninitialized values*/
163 for_each_thermal_trip(tz: mld->tzone, cb: iwl_trip_temp_iter, data: &twd);
164
165 cmd.num_temps = cpu_to_le32(twd.count);
166 if (twd.count)
167 sort(base: cmd.thresholds, num: twd.count, size: sizeof(s16),
168 cmp_func: compare_temps, NULL);
169
170send:
171#endif
172 lockdep_assert_wiphy(mld->wiphy);
173
174 ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(PHY_OPS_GROUP,
175 TEMP_REPORTING_THRESHOLDS_CMD),
176 &cmd);
177 if (ret)
178 IWL_ERR(mld, "TEMP_REPORT_THS_CMD command failed (err=%d)\n",
179 ret);
180
181 return ret;
182}
183
184#ifdef CONFIG_THERMAL
185static int iwl_mld_tzone_get_temp(struct thermal_zone_device *device,
186 int *temperature)
187{
188 struct iwl_mld *mld = thermal_zone_device_priv(tzd: device);
189 int temp;
190 int ret = 0;
191
192 wiphy_lock(wiphy: mld->wiphy);
193
194 if (!mld->fw_status.running) {
195 /* Tell the core that there is no valid temperature value to
196 * return, but it need not worry about this.
197 */
198 *temperature = THERMAL_TEMP_INVALID;
199 goto unlock;
200 }
201
202 ret = iwl_mld_get_temp(mld, temp: &temp);
203 if (ret)
204 goto unlock;
205
206 *temperature = temp * 1000;
207unlock:
208 wiphy_unlock(wiphy: mld->wiphy);
209 return ret;
210}
211
212static int iwl_mld_tzone_set_trip_temp(struct thermal_zone_device *device,
213 const struct thermal_trip *trip,
214 int temp)
215{
216 struct iwl_mld *mld = thermal_zone_device_priv(tzd: device);
217 int ret;
218
219 wiphy_lock(wiphy: mld->wiphy);
220
221 if (!mld->fw_status.running) {
222 ret = -EIO;
223 goto unlock;
224 }
225
226 if ((temp / 1000) > S16_MAX) {
227 ret = -EINVAL;
228 goto unlock;
229 }
230
231 ret = iwl_mld_config_temp_report_ths(mld);
232unlock:
233 wiphy_unlock(wiphy: mld->wiphy);
234 return ret;
235}
236
237static struct thermal_zone_device_ops tzone_ops = {
238 .get_temp = iwl_mld_tzone_get_temp,
239 .set_trip_temp = iwl_mld_tzone_set_trip_temp,
240};
241
242static void iwl_mld_thermal_zone_register(struct iwl_mld *mld)
243{
244 int ret;
245 char name[16];
246 static atomic_t counter = ATOMIC_INIT(0);
247 struct thermal_trip trips[IWL_MAX_DTS_TRIPS] = {
248 [0 ... IWL_MAX_DTS_TRIPS - 1] = {
249 .temperature = THERMAL_TEMP_INVALID,
250 .type = THERMAL_TRIP_PASSIVE,
251 .flags = THERMAL_TRIP_FLAG_RW_TEMP,
252 },
253 };
254
255 BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
256
257 sprintf(buf: name, fmt: "iwlwifi_%u", atomic_inc_return(v: &counter) & 0xFF);
258 mld->tzone =
259 thermal_zone_device_register_with_trips(type: name, trips,
260 IWL_MAX_DTS_TRIPS,
261 devdata: mld, ops: &tzone_ops,
262 NULL, passive_delay: 0, polling_delay: 0);
263 if (IS_ERR(ptr: mld->tzone)) {
264 IWL_DEBUG_TEMP(mld,
265 "Failed to register to thermal zone (err = %ld)\n",
266 PTR_ERR(mld->tzone));
267 mld->tzone = NULL;
268 return;
269 }
270
271 ret = thermal_zone_device_enable(tz: mld->tzone);
272 if (ret) {
273 IWL_DEBUG_TEMP(mld, "Failed to enable thermal zone\n");
274 thermal_zone_device_unregister(tz: mld->tzone);
275 }
276}
277
278int iwl_mld_config_ctdp(struct iwl_mld *mld, u32 state,
279 enum iwl_ctdp_cmd_operation op)
280{
281 struct iwl_ctdp_cmd cmd = {
282 .operation = cpu_to_le32(op),
283 .window_size = 0,
284 };
285 u32 budget;
286 int ret;
287
288 lockdep_assert_wiphy(mld->wiphy);
289
290 /* Do a linear scale from IWL_MLD_MIN_CTDP_BUDGET_MW to the configured
291 * maximum in the predefined number of steps.
292 */
293 budget = ((mld->power_budget_mw - IWL_MLD_MIN_CTDP_BUDGET_MW) *
294 (IWL_MLD_NUM_CTDP_STEPS - 1 - state)) /
295 (IWL_MLD_NUM_CTDP_STEPS - 1) +
296 IWL_MLD_MIN_CTDP_BUDGET_MW;
297 cmd.budget = cpu_to_le32(budget);
298
299 ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(PHY_OPS_GROUP, CTDP_CONFIG_CMD),
300 &cmd);
301
302 if (ret) {
303 IWL_ERR(mld, "cTDP command failed (err=%d)\n", ret);
304 return ret;
305 }
306
307 if (op == CTDP_CMD_OPERATION_START)
308 mld->cooling_dev.cur_state = state;
309
310 return 0;
311}
312
313static int iwl_mld_tcool_get_max_state(struct thermal_cooling_device *cdev,
314 unsigned long *state)
315{
316 *state = IWL_MLD_NUM_CTDP_STEPS - 1;
317
318 return 0;
319}
320
321static int iwl_mld_tcool_get_cur_state(struct thermal_cooling_device *cdev,
322 unsigned long *state)
323{
324 struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata);
325
326 *state = mld->cooling_dev.cur_state;
327
328 return 0;
329}
330
331static int iwl_mld_tcool_set_cur_state(struct thermal_cooling_device *cdev,
332 unsigned long new_state)
333{
334 struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata);
335 int ret;
336
337 wiphy_lock(wiphy: mld->wiphy);
338
339 if (!mld->fw_status.running) {
340 ret = -EIO;
341 goto unlock;
342 }
343
344 if (new_state >= IWL_MLD_NUM_CTDP_STEPS) {
345 ret = -EINVAL;
346 goto unlock;
347 }
348
349 ret = iwl_mld_config_ctdp(mld, state: new_state, op: CTDP_CMD_OPERATION_START);
350
351unlock:
352 wiphy_unlock(wiphy: mld->wiphy);
353 return ret;
354}
355
356static const struct thermal_cooling_device_ops tcooling_ops = {
357 .get_max_state = iwl_mld_tcool_get_max_state,
358 .get_cur_state = iwl_mld_tcool_get_cur_state,
359 .set_cur_state = iwl_mld_tcool_set_cur_state,
360};
361
362static void iwl_mld_cooling_device_register(struct iwl_mld *mld)
363{
364 char name[] = "iwlwifi";
365
366 BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
367
368 mld->cooling_dev.cdev =
369 thermal_cooling_device_register(name,
370 mld,
371 &tcooling_ops);
372
373 if (IS_ERR(ptr: mld->cooling_dev.cdev)) {
374 IWL_DEBUG_TEMP(mld,
375 "Failed to register to cooling device (err = %ld)\n",
376 PTR_ERR(mld->cooling_dev.cdev));
377 mld->cooling_dev.cdev = NULL;
378 return;
379 }
380}
381
382static void iwl_mld_thermal_zone_unregister(struct iwl_mld *mld)
383{
384 if (!mld->tzone)
385 return;
386
387 IWL_DEBUG_TEMP(mld, "Thermal zone device unregister\n");
388 if (mld->tzone) {
389 thermal_zone_device_unregister(tz: mld->tzone);
390 mld->tzone = NULL;
391 }
392}
393
394static void iwl_mld_cooling_device_unregister(struct iwl_mld *mld)
395{
396 if (!mld->cooling_dev.cdev)
397 return;
398
399 IWL_DEBUG_TEMP(mld, "Cooling device unregister\n");
400 if (mld->cooling_dev.cdev) {
401 thermal_cooling_device_unregister(mld->cooling_dev.cdev);
402 mld->cooling_dev.cdev = NULL;
403 }
404}
405#endif /* CONFIG_THERMAL */
406
407static u32 iwl_mld_ctdp_get_max_budget(struct iwl_mld *mld)
408{
409 u64 bios_power_budget = 0;
410 u32 default_power_budget;
411
412 switch (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id)) {
413 case IWL_CFG_RF_TYPE_GF:
414 /* dual-radio devices have a higher budget */
415 if (CSR_HW_RFID_IS_CDB(mld->trans->info.hw_rf_id))
416 default_power_budget = 5200;
417 else
418 default_power_budget = 2880;
419 break;
420 case IWL_CFG_RF_TYPE_FM:
421 default_power_budget = 3450;
422 break;
423 case IWL_CFG_RF_TYPE_WH:
424 case IWL_CFG_RF_TYPE_PE:
425 default:
426 default_power_budget = 5550;
427 break;
428 }
429
430 iwl_bios_get_pwr_limit(fwrt: &mld->fwrt, dflt_pwr_limit: &bios_power_budget);
431
432 /* 32bit in UEFI, 16bit in ACPI; use BIOS value if it is in range */
433 if (bios_power_budget &&
434 bios_power_budget != 0xffff && bios_power_budget != 0xffffffff &&
435 bios_power_budget >= IWL_MLD_MIN_CTDP_BUDGET_MW &&
436 bios_power_budget <= default_power_budget)
437 return (u32)bios_power_budget;
438
439 return default_power_budget;
440}
441
442void iwl_mld_thermal_initialize(struct iwl_mld *mld)
443{
444 lockdep_assert_not_held(&mld->wiphy->mtx);
445
446 wiphy_delayed_work_init(dwork: &mld->ct_kill_exit_wk, func: iwl_mld_exit_ctkill);
447
448 mld->power_budget_mw = iwl_mld_ctdp_get_max_budget(mld);
449 IWL_DEBUG_TEMP(mld, "cTDP power budget: %d mW\n", mld->power_budget_mw);
450
451#ifdef CONFIG_THERMAL
452 iwl_mld_cooling_device_register(mld);
453 iwl_mld_thermal_zone_register(mld);
454#endif
455}
456
457void iwl_mld_thermal_exit(struct iwl_mld *mld)
458{
459 wiphy_lock(wiphy: mld->wiphy);
460 wiphy_delayed_work_cancel(wiphy: mld->wiphy, dwork: &mld->ct_kill_exit_wk);
461 wiphy_unlock(wiphy: mld->wiphy);
462
463#ifdef CONFIG_THERMAL
464 iwl_mld_cooling_device_unregister(mld);
465 iwl_mld_thermal_zone_unregister(mld);
466#endif
467}
468

source code of linux/drivers/net/wireless/intel/iwlwifi/mld/thermal.c