File Index
Files and directories
Sign in
FILE

thermal.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/wireless/intel/iwlwifi/mld/thermal.c C
  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, 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
 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, 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);
 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
115free_resp:
116	iwl_free_resp(&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(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
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(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;
207unlock:
208	wiphy_unlock(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(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);
232unlock:
233	wiphy_unlock(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(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	}
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(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, new_state, CTDP_CMD_OPERATION_START);
350
351unlock:
352	wiphy_unlock(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(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(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(&mld->fwrt, &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(&mld->ct_kill_exit_wk, 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(mld->wiphy);
460	wiphy_delayed_work_cancel(mld->wiphy, &mld->ct_kill_exit_wk);
461	wiphy_unlock(mld->wiphy);
462
463#ifdef CONFIG_THERMAL
464	iwl_mld_cooling_device_unregister(mld);
465	iwl_mld_thermal_zone_unregister(mld);
466#endif
467}
~/ Read-only file preview.