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