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