xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/thermal.c (revision fab183d632628381b466a41479489541ac0e29a0)
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3  * Copyright (C) 2024-2026 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 
iwl_mld_handle_ct_kill_notif(struct iwl_mld * mld,struct iwl_rx_packet * pkt)21 void 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, true);
32 
33 	wiphy_delayed_work_queue(mld->wiphy, &mld->ct_kill_exit_wk,
34 				 round_jiffies_relative(IWL_MLD_CT_KILL_DURATION));
35 }
36 
iwl_mld_exit_ctkill(struct wiphy * wiphy,struct wiphy_work * wk)37 static 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, false);
45 }
46 
iwl_mld_handle_temp_notif(struct iwl_mld * mld,struct iwl_rx_packet * pkt)47 void 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
iwl_mld_get_temp(struct iwl_mld * mld,s32 * temp)82 static 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);
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(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 
115 free_resp:
116 	iwl_free_resp(&cmd);
117 	return ret;
118 }
119 
compare_temps(const void * a,const void * b)120 static 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 
126 struct iwl_trip_walk_data {
127 	__le16 *thresholds;
128 	int count;
129 };
130 
iwl_trip_temp_iter(struct thermal_trip * trip,void * arg)131 static 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 
iwl_mld_config_temp_report_ths(struct iwl_mld * mld)144 int 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(mld->tzone, iwl_trip_temp_iter, &twd);
164 
165 	cmd.num_temps = cpu_to_le32(twd.count);
166 	if (twd.count)
167 		sort(cmd.thresholds, twd.count, sizeof(s16),
168 		     compare_temps, NULL);
169 
170 send:
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
iwl_mld_tzone_get_temp(struct thermal_zone_device * device,int * temperature)185 static int iwl_mld_tzone_get_temp(struct thermal_zone_device *device,
186 				  int *temperature)
187 {
188 	struct iwl_mld *mld = thermal_zone_device_priv(device);
189 	int temp;
190 	int ret = 0;
191 
192 	wiphy_lock(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);
203 	if (ret)
204 		goto unlock;
205 
206 	*temperature = temp * 1000;
207 unlock:
208 	wiphy_unlock(mld->wiphy);
209 	return ret;
210 }
211 
iwl_mld_tzone_set_trip_temp(struct thermal_zone_device * device,const struct thermal_trip * trip,int temp)212 static 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(device);
217 	int ret;
218 
219 	wiphy_lock(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);
232 unlock:
233 	wiphy_unlock(mld->wiphy);
234 	return ret;
235 }
236 
237 static  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 
iwl_mld_thermal_zone_register(struct iwl_mld * mld)242 static 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(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF);
258 	mld->tzone =
259 		thermal_zone_device_register_with_trips(name, trips,
260 							IWL_MAX_DTS_TRIPS,
261 							mld, &tzone_ops,
262 							NULL, 0, 0);
263 	if (IS_ERR(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(mld->tzone);
272 	if (ret) {
273 		IWL_DEBUG_TEMP(mld, "Failed to enable thermal zone\n");
274 		thermal_zone_device_unregister(mld->tzone);
275 		mld->tzone = NULL;
276 	}
277 }
278 
iwl_mld_config_ctdp(struct iwl_mld * mld,u32 state,enum iwl_ctdp_cmd_operation op)279 int iwl_mld_config_ctdp(struct iwl_mld *mld, u32 state,
280 			enum iwl_ctdp_cmd_operation op)
281 {
282 	struct iwl_ctdp_cmd cmd = {
283 		.operation = cpu_to_le32(op),
284 		.window_size = 0,
285 	};
286 	u32 budget;
287 	int ret;
288 
289 	lockdep_assert_wiphy(mld->wiphy);
290 
291 	/* Do a linear scale from IWL_MLD_MIN_CTDP_BUDGET_MW to the configured
292 	 * maximum in the predefined number of steps.
293 	 */
294 	budget = ((mld->power_budget_mw - IWL_MLD_MIN_CTDP_BUDGET_MW) *
295 		  (IWL_MLD_NUM_CTDP_STEPS - 1 - state)) /
296 		 (IWL_MLD_NUM_CTDP_STEPS - 1) +
297 		 IWL_MLD_MIN_CTDP_BUDGET_MW;
298 	cmd.budget = cpu_to_le32(budget);
299 
300 	ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(PHY_OPS_GROUP, CTDP_CONFIG_CMD),
301 				   &cmd);
302 
303 	if (ret) {
304 		IWL_ERR(mld, "cTDP command failed (err=%d)\n", ret);
305 		return ret;
306 	}
307 
308 	if (op == CTDP_CMD_OPERATION_START)
309 		mld->cooling_dev.cur_state = state;
310 
311 	return 0;
312 }
313 
iwl_mld_tcool_get_max_state(struct thermal_cooling_device * cdev,unsigned long * state)314 static int iwl_mld_tcool_get_max_state(struct thermal_cooling_device *cdev,
315 				       unsigned long *state)
316 {
317 	*state = IWL_MLD_NUM_CTDP_STEPS - 1;
318 
319 	return 0;
320 }
321 
iwl_mld_tcool_get_cur_state(struct thermal_cooling_device * cdev,unsigned long * state)322 static int iwl_mld_tcool_get_cur_state(struct thermal_cooling_device *cdev,
323 				       unsigned long *state)
324 {
325 	struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata);
326 
327 	*state = mld->cooling_dev.cur_state;
328 
329 	return 0;
330 }
331 
iwl_mld_tcool_set_cur_state(struct thermal_cooling_device * cdev,unsigned long new_state)332 static int iwl_mld_tcool_set_cur_state(struct thermal_cooling_device *cdev,
333 				       unsigned long new_state)
334 {
335 	struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata);
336 	int ret;
337 
338 	wiphy_lock(mld->wiphy);
339 
340 	if (!mld->fw_status.running) {
341 		ret = -EIO;
342 		goto unlock;
343 	}
344 
345 	if (new_state >= IWL_MLD_NUM_CTDP_STEPS) {
346 		ret = -EINVAL;
347 		goto unlock;
348 	}
349 
350 	ret = iwl_mld_config_ctdp(mld, new_state, CTDP_CMD_OPERATION_START);
351 
352 unlock:
353 	wiphy_unlock(mld->wiphy);
354 	return ret;
355 }
356 
357 static const struct thermal_cooling_device_ops tcooling_ops = {
358 	.get_max_state = iwl_mld_tcool_get_max_state,
359 	.get_cur_state = iwl_mld_tcool_get_cur_state,
360 	.set_cur_state = iwl_mld_tcool_set_cur_state,
361 };
362 
iwl_mld_cooling_device_register(struct iwl_mld * mld)363 static void iwl_mld_cooling_device_register(struct iwl_mld *mld)
364 {
365 	char name[] = "iwlwifi";
366 
367 	BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
368 
369 	mld->cooling_dev.cdev =
370 		thermal_cooling_device_register(name,
371 						mld,
372 						&tcooling_ops);
373 
374 	if (IS_ERR(mld->cooling_dev.cdev)) {
375 		IWL_DEBUG_TEMP(mld,
376 			       "Failed to register to cooling device (err = %ld)\n",
377 			       PTR_ERR(mld->cooling_dev.cdev));
378 		mld->cooling_dev.cdev = NULL;
379 		return;
380 	}
381 }
382 
iwl_mld_thermal_zone_unregister(struct iwl_mld * mld)383 static void iwl_mld_thermal_zone_unregister(struct iwl_mld *mld)
384 {
385 	if (!mld->tzone)
386 		return;
387 
388 	IWL_DEBUG_TEMP(mld, "Thermal zone device unregister\n");
389 	thermal_zone_device_unregister(mld->tzone);
390 	mld->tzone = NULL;
391 }
392 
iwl_mld_cooling_device_unregister(struct iwl_mld * mld)393 static void iwl_mld_cooling_device_unregister(struct iwl_mld *mld)
394 {
395 	if (!mld->cooling_dev.cdev)
396 		return;
397 
398 	IWL_DEBUG_TEMP(mld, "Cooling device unregister\n");
399 	if (mld->cooling_dev.cdev) {
400 		thermal_cooling_device_unregister(mld->cooling_dev.cdev);
401 		mld->cooling_dev.cdev = NULL;
402 	}
403 }
404 #endif /* CONFIG_THERMAL */
405 
iwl_mld_ctdp_get_max_budget(struct iwl_mld * mld)406 static u32 iwl_mld_ctdp_get_max_budget(struct iwl_mld *mld)
407 {
408 	u64 bios_power_budget = 0;
409 	u32 default_power_budget;
410 
411 	switch (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id)) {
412 	case IWL_CFG_RF_TYPE_GF:
413 		/* dual-radio devices have a higher budget */
414 		if (CSR_HW_RFID_IS_CDB(mld->trans->info.hw_rf_id))
415 			default_power_budget = 5200;
416 		else
417 			default_power_budget = 2880;
418 		break;
419 	case IWL_CFG_RF_TYPE_FM:
420 		default_power_budget = 3450;
421 		break;
422 	case IWL_CFG_RF_TYPE_WH:
423 	case IWL_CFG_RF_TYPE_PE:
424 	default:
425 		default_power_budget = 5550;
426 		break;
427 	}
428 
429 	iwl_bios_get_pwr_limit(&mld->fwrt, &bios_power_budget);
430 
431 	/* 32bit in UEFI, 16bit in ACPI; use BIOS value if it is in range */
432 	if (bios_power_budget &&
433 	    bios_power_budget != 0xffff && bios_power_budget != 0xffffffff &&
434 	    bios_power_budget >= IWL_MLD_MIN_CTDP_BUDGET_MW &&
435 	    bios_power_budget <= default_power_budget)
436 		return (u32)bios_power_budget;
437 
438 	return default_power_budget;
439 }
440 
iwl_mld_thermal_initialize(struct iwl_mld * mld)441 void iwl_mld_thermal_initialize(struct iwl_mld *mld)
442 {
443 	lockdep_assert_not_held(&mld->wiphy->mtx);
444 
445 	wiphy_delayed_work_init(&mld->ct_kill_exit_wk, iwl_mld_exit_ctkill);
446 
447 	mld->power_budget_mw = iwl_mld_ctdp_get_max_budget(mld);
448 	IWL_DEBUG_TEMP(mld, "cTDP power budget: %d mW\n", mld->power_budget_mw);
449 
450 #ifdef CONFIG_THERMAL
451 	iwl_mld_cooling_device_register(mld);
452 	iwl_mld_thermal_zone_register(mld);
453 #endif
454 }
455 
iwl_mld_thermal_exit(struct iwl_mld * mld)456 void iwl_mld_thermal_exit(struct iwl_mld *mld)
457 {
458 	wiphy_lock(mld->wiphy);
459 	wiphy_delayed_work_cancel(mld->wiphy, &mld->ct_kill_exit_wk);
460 	wiphy_unlock(mld->wiphy);
461 
462 #ifdef CONFIG_THERMAL
463 	iwl_mld_cooling_device_unregister(mld);
464 	iwl_mld_thermal_zone_unregister(mld);
465 #endif
466 }
467