xref: /openbmc/linux/drivers/net/wireless/intel/iwlwifi/mvm/tt.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
18e99ea8dSJohannes Berg // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
28e99ea8dSJohannes Berg /*
3*1a3e7039SGregory Greenman  * Copyright (C) 2012-2014, 2019-2022 Intel Corporation
48e99ea8dSJohannes Berg  * Copyright (C) 2013-2014 Intel Mobile Communications GmbH
58e99ea8dSJohannes Berg  * Copyright (C) 2015-2016 Intel Deutschland GmbH
68e99ea8dSJohannes Berg  */
7c221daf2SChaya Rachel Ivgi #include <linux/sort.h>
8c221daf2SChaya Rachel Ivgi 
9e705c121SKalle Valo #include "mvm.h"
10e705c121SKalle Valo 
11e705c121SKalle Valo #define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT	HZ
12e705c121SKalle Valo 
iwl_mvm_enter_ctkill(struct iwl_mvm * mvm)1361d8c626SChaya Rachel Ivgi void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm)
14e705c121SKalle Valo {
15e705c121SKalle Valo 	struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
16e705c121SKalle Valo 	u32 duration = tt->params.ct_kill_duration;
17e705c121SKalle Valo 
18e705c121SKalle Valo 	if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
19e705c121SKalle Valo 		return;
20e705c121SKalle Valo 
21e705c121SKalle Valo 	IWL_ERR(mvm, "Enter CT Kill\n");
22e705c121SKalle Valo 	iwl_mvm_set_hw_ctkill_state(mvm, true);
23e705c121SKalle Valo 
24c221daf2SChaya Rachel Ivgi 	if (!iwl_mvm_is_tt_in_fw(mvm)) {
25e705c121SKalle Valo 		tt->throttle = false;
26e705c121SKalle Valo 		tt->dynamic_smps = false;
27c221daf2SChaya Rachel Ivgi 	}
28e705c121SKalle Valo 
29e705c121SKalle Valo 	/* Don't schedule an exit work if we're in test mode, since
30e705c121SKalle Valo 	 * the temperature will not change unless we manually set it
31e705c121SKalle Valo 	 * again (or disable testing).
32e705c121SKalle Valo 	 */
33e705c121SKalle Valo 	if (!mvm->temperature_test)
34e705c121SKalle Valo 		schedule_delayed_work(&tt->ct_kill_exit,
35e705c121SKalle Valo 				      round_jiffies_relative(duration * HZ));
36e705c121SKalle Valo }
37e705c121SKalle Valo 
iwl_mvm_exit_ctkill(struct iwl_mvm * mvm)38e705c121SKalle Valo static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm)
39e705c121SKalle Valo {
40e705c121SKalle Valo 	if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
41e705c121SKalle Valo 		return;
42e705c121SKalle Valo 
43e705c121SKalle Valo 	IWL_ERR(mvm, "Exit CT Kill\n");
44e705c121SKalle Valo 	iwl_mvm_set_hw_ctkill_state(mvm, false);
45e705c121SKalle Valo }
46e705c121SKalle Valo 
iwl_mvm_tt_temp_changed(struct iwl_mvm * mvm,u32 temp)47d8367b12SJohannes Berg static void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp)
48e705c121SKalle Valo {
49e705c121SKalle Valo 	/* ignore the notification if we are in test mode */
50e705c121SKalle Valo 	if (mvm->temperature_test)
51e705c121SKalle Valo 		return;
52e705c121SKalle Valo 
53e705c121SKalle Valo 	if (mvm->temperature == temp)
54e705c121SKalle Valo 		return;
55e705c121SKalle Valo 
56e705c121SKalle Valo 	mvm->temperature = temp;
57e705c121SKalle Valo 	iwl_mvm_tt_handler(mvm);
58e705c121SKalle Valo }
59e705c121SKalle Valo 
iwl_mvm_temp_notif_parse(struct iwl_mvm * mvm,struct iwl_rx_packet * pkt)60e705c121SKalle Valo static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm,
61e705c121SKalle Valo 				    struct iwl_rx_packet *pkt)
62e705c121SKalle Valo {
63c221daf2SChaya Rachel Ivgi 	struct iwl_dts_measurement_notif_v1 *notif_v1;
64e705c121SKalle Valo 	int len = iwl_rx_packet_payload_len(pkt);
65e705c121SKalle Valo 	int temp;
66e705c121SKalle Valo 
67c221daf2SChaya Rachel Ivgi 	/* we can use notif_v1 only, because v2 only adds an additional
68c221daf2SChaya Rachel Ivgi 	 * parameter, which is not used in this function.
69c221daf2SChaya Rachel Ivgi 	*/
70c221daf2SChaya Rachel Ivgi 	if (WARN_ON_ONCE(len < sizeof(*notif_v1))) {
71e705c121SKalle Valo 		IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
72e705c121SKalle Valo 		return -EINVAL;
73e705c121SKalle Valo 	}
74e705c121SKalle Valo 
75c221daf2SChaya Rachel Ivgi 	notif_v1 = (void *)pkt->data;
76e705c121SKalle Valo 
77c221daf2SChaya Rachel Ivgi 	temp = le32_to_cpu(notif_v1->temp);
78e705c121SKalle Valo 
79e705c121SKalle Valo 	/* shouldn't be negative, but since it's s32, make sure it isn't */
80e705c121SKalle Valo 	if (WARN_ON_ONCE(temp < 0))
81e705c121SKalle Valo 		temp = 0;
82e705c121SKalle Valo 
83e705c121SKalle Valo 	IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp);
84e705c121SKalle Valo 
85e705c121SKalle Valo 	return temp;
86e705c121SKalle Valo }
87e705c121SKalle Valo 
iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data * notif_wait,struct iwl_rx_packet * pkt,void * data)88e705c121SKalle Valo static bool iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data *notif_wait,
89e705c121SKalle Valo 				    struct iwl_rx_packet *pkt, void *data)
90e705c121SKalle Valo {
91e705c121SKalle Valo 	struct iwl_mvm *mvm =
92e705c121SKalle Valo 		container_of(notif_wait, struct iwl_mvm, notif_wait);
93e705c121SKalle Valo 	int *temp = data;
94e705c121SKalle Valo 	int ret;
95e705c121SKalle Valo 
96e705c121SKalle Valo 	ret = iwl_mvm_temp_notif_parse(mvm, pkt);
97e705c121SKalle Valo 	if (ret < 0)
98e705c121SKalle Valo 		return true;
99e705c121SKalle Valo 
100e705c121SKalle Valo 	*temp = ret;
101e705c121SKalle Valo 
102e705c121SKalle Valo 	return true;
103e705c121SKalle Valo }
104e705c121SKalle Valo 
iwl_mvm_temp_notif(struct iwl_mvm * mvm,struct iwl_rx_cmd_buffer * rxb)105e705c121SKalle Valo void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
106e705c121SKalle Valo {
107e705c121SKalle Valo 	struct iwl_rx_packet *pkt = rxb_addr(rxb);
108c221daf2SChaya Rachel Ivgi 	struct iwl_dts_measurement_notif_v2 *notif_v2;
109c221daf2SChaya Rachel Ivgi 	int len = iwl_rx_packet_payload_len(pkt);
110e705c121SKalle Valo 	int temp;
111c221daf2SChaya Rachel Ivgi 	u32 ths_crossed;
112e705c121SKalle Valo 
113e705c121SKalle Valo 	/* the notification is handled synchronously in ctkill, so skip here */
114e705c121SKalle Valo 	if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
115e705c121SKalle Valo 		return;
116e705c121SKalle Valo 
117e705c121SKalle Valo 	temp = iwl_mvm_temp_notif_parse(mvm, pkt);
118c221daf2SChaya Rachel Ivgi 
119c221daf2SChaya Rachel Ivgi 	if (!iwl_mvm_is_tt_in_fw(mvm)) {
120c221daf2SChaya Rachel Ivgi 		if (temp >= 0)
121c221daf2SChaya Rachel Ivgi 			iwl_mvm_tt_temp_changed(mvm, temp);
122c221daf2SChaya Rachel Ivgi 		return;
123c221daf2SChaya Rachel Ivgi 	}
124c221daf2SChaya Rachel Ivgi 
125c221daf2SChaya Rachel Ivgi 	if (WARN_ON_ONCE(len < sizeof(*notif_v2))) {
126c221daf2SChaya Rachel Ivgi 		IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
127c221daf2SChaya Rachel Ivgi 		return;
128c221daf2SChaya Rachel Ivgi 	}
129c221daf2SChaya Rachel Ivgi 
130c221daf2SChaya Rachel Ivgi 	notif_v2 = (void *)pkt->data;
131c221daf2SChaya Rachel Ivgi 	ths_crossed = le32_to_cpu(notif_v2->threshold_idx);
132c221daf2SChaya Rachel Ivgi 
133c221daf2SChaya Rachel Ivgi 	/* 0xFF in ths_crossed means the notification is not related
134c221daf2SChaya Rachel Ivgi 	 * to a trip, so we can ignore it here.
135c221daf2SChaya Rachel Ivgi 	 */
136c221daf2SChaya Rachel Ivgi 	if (ths_crossed == 0xFF)
137e705c121SKalle Valo 		return;
138e705c121SKalle Valo 
139c221daf2SChaya Rachel Ivgi 	IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n",
140c221daf2SChaya Rachel Ivgi 		       temp, ths_crossed);
141c221daf2SChaya Rachel Ivgi 
142c221daf2SChaya Rachel Ivgi #ifdef CONFIG_THERMAL
143c221daf2SChaya Rachel Ivgi 	if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS))
144c221daf2SChaya Rachel Ivgi 		return;
145c221daf2SChaya Rachel Ivgi 
14691f66a3cSEmmanuel Grumbach 	if (mvm->tz_device.tzone) {
14791f66a3cSEmmanuel Grumbach 		struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device;
14891f66a3cSEmmanuel Grumbach 
14993effd83SThara Gopinath 		thermal_zone_device_update(tz_dev->tzone,
15093effd83SThara Gopinath 					   THERMAL_TRIP_VIOLATED);
15191f66a3cSEmmanuel Grumbach 	}
152c221daf2SChaya Rachel Ivgi #endif /* CONFIG_THERMAL */
153e705c121SKalle Valo }
154e705c121SKalle Valo 
iwl_mvm_ct_kill_notif(struct iwl_mvm * mvm,struct iwl_rx_cmd_buffer * rxb)1550a3b7119SChaya Rachel Ivgi void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
1560a3b7119SChaya Rachel Ivgi {
1570a3b7119SChaya Rachel Ivgi 	struct iwl_rx_packet *pkt = rxb_addr(rxb);
1580a3b7119SChaya Rachel Ivgi 	struct ct_kill_notif *notif;
1590a3b7119SChaya Rachel Ivgi 
1600a3b7119SChaya Rachel Ivgi 	notif = (struct ct_kill_notif *)pkt->data;
1610a3b7119SChaya Rachel Ivgi 	IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n",
1620a3b7119SChaya Rachel Ivgi 		       notif->temperature);
1635c7fd9dcSMiri Korenblit 	if (iwl_fw_lookup_notif_ver(mvm->fw, PHY_OPS_GROUP,
1645c7fd9dcSMiri Korenblit 				    CT_KILL_NOTIFICATION, 0) > 1)
1655c7fd9dcSMiri Korenblit 		IWL_DEBUG_TEMP(mvm,
1665c7fd9dcSMiri Korenblit 			       "CT kill notification DTS bitmap = 0x%x, Scheme = %d\n",
1675c7fd9dcSMiri Korenblit 			       notif->dts, notif->scheme);
1680a3b7119SChaya Rachel Ivgi 
1690a3b7119SChaya Rachel Ivgi 	iwl_mvm_enter_ctkill(mvm);
1700a3b7119SChaya Rachel Ivgi }
1710a3b7119SChaya Rachel Ivgi 
172762c523fSGil Adam /*
173762c523fSGil Adam  * send the DTS_MEASUREMENT_TRIGGER command with or without waiting for a
174762c523fSGil Adam  * response. If we get a response then the measurement is stored in 'temp'
175762c523fSGil Adam  */
iwl_mvm_send_temp_cmd(struct iwl_mvm * mvm,bool response,s32 * temp)176762c523fSGil Adam static int iwl_mvm_send_temp_cmd(struct iwl_mvm *mvm, bool response, s32 *temp)
177e705c121SKalle Valo {
178762c523fSGil Adam 	struct iwl_host_cmd cmd = {};
179762c523fSGil Adam 	struct iwl_dts_measurement_cmd dts_cmd = {
180e705c121SKalle Valo 		.flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP),
181e705c121SKalle Valo 	};
182762c523fSGil Adam 	struct iwl_ext_dts_measurement_cmd ext_cmd = {
1836bd5fa33SGolan Ben Ami 		.control_mode = cpu_to_le32(DTS_DIRECT_WITHOUT_MEASURE),
184e705c121SKalle Valo 	};
185762c523fSGil Adam 	struct iwl_dts_measurement_resp *resp;
186762c523fSGil Adam 	void *cmd_ptr;
187762c523fSGil Adam 	int ret;
188762c523fSGil Adam 	u32 cmd_flags = 0;
189762c523fSGil Adam 	u16 len;
190e705c121SKalle Valo 
191762c523fSGil Adam 	/* Check which command format is used (regular/extended) */
192762c523fSGil Adam 	if (fw_has_capa(&mvm->fw->ucode_capa,
193762c523fSGil Adam 			IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) {
194762c523fSGil Adam 		len = sizeof(ext_cmd);
195762c523fSGil Adam 		cmd_ptr = &ext_cmd;
196762c523fSGil Adam 	} else {
197762c523fSGil Adam 		len = sizeof(dts_cmd);
198762c523fSGil Adam 		cmd_ptr = &dts_cmd;
199762c523fSGil Adam 	}
200762c523fSGil Adam 	/* The command version where we get a response is zero length */
201762c523fSGil Adam 	if (response) {
202762c523fSGil Adam 		cmd_flags = CMD_WANT_SKB;
203762c523fSGil Adam 		len = 0;
204762c523fSGil Adam 	}
205e705c121SKalle Valo 
206762c523fSGil Adam 	cmd.id =  WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE);
207762c523fSGil Adam 	cmd.len[0] = len;
208762c523fSGil Adam 	cmd.flags = cmd_flags;
209762c523fSGil Adam 	cmd.data[0] = cmd_ptr;
210e705c121SKalle Valo 
211762c523fSGil Adam 	IWL_DEBUG_TEMP(mvm,
212762c523fSGil Adam 		       "Sending temperature measurement command - %s response\n",
213762c523fSGil Adam 		       response ? "with" : "without");
214762c523fSGil Adam 	ret = iwl_mvm_send_cmd(mvm, &cmd);
215762c523fSGil Adam 
216762c523fSGil Adam 	if (ret) {
217762c523fSGil Adam 		IWL_ERR(mvm,
218762c523fSGil Adam 			"Failed to send the temperature measurement command (err=%d)\n",
219762c523fSGil Adam 			ret);
220762c523fSGil Adam 		return ret;
221762c523fSGil Adam 	}
222762c523fSGil Adam 
223762c523fSGil Adam 	if (response) {
224762c523fSGil Adam 		resp = (void *)cmd.resp_pkt->data;
225762c523fSGil Adam 		*temp = le32_to_cpu(resp->temp);
226762c523fSGil Adam 		IWL_DEBUG_TEMP(mvm,
227762c523fSGil Adam 			       "Got temperature measurement response: temp=%d\n",
228762c523fSGil Adam 			       *temp);
229762c523fSGil Adam 		iwl_free_resp(&cmd);
230762c523fSGil Adam 	}
231762c523fSGil Adam 
232762c523fSGil Adam 	return ret;
233e705c121SKalle Valo }
234e705c121SKalle Valo 
iwl_mvm_get_temp(struct iwl_mvm * mvm,s32 * temp)2357869318eSChaya Rachel Ivgi int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp)
236e705c121SKalle Valo {
237e705c121SKalle Valo 	struct iwl_notification_wait wait_temp_notif;
238e705c121SKalle Valo 	static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP,
239e705c121SKalle Valo 					    DTS_MEASUREMENT_NOTIF_WIDE) };
2407869318eSChaya Rachel Ivgi 	int ret;
241762c523fSGil Adam 	u8 cmd_ver;
242762c523fSGil Adam 
243762c523fSGil Adam 	/*
244762c523fSGil Adam 	 * If command version is 1 we send the command and immediately get
245762c523fSGil Adam 	 * a response. For older versions we send the command and wait for a
246762c523fSGil Adam 	 * notification (no command TLV for previous versions).
247762c523fSGil Adam 	 */
248971cbe50SJohannes Berg 	cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw,
249971cbe50SJohannes Berg 					WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE),
250762c523fSGil Adam 					IWL_FW_CMD_VER_UNKNOWN);
251762c523fSGil Adam 	if (cmd_ver == 1)
252762c523fSGil Adam 		return iwl_mvm_send_temp_cmd(mvm, true, temp);
253e705c121SKalle Valo 
254e705c121SKalle Valo 	lockdep_assert_held(&mvm->mutex);
255e705c121SKalle Valo 
256e705c121SKalle Valo 	iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif,
257e705c121SKalle Valo 				   temp_notif, ARRAY_SIZE(temp_notif),
2587869318eSChaya Rachel Ivgi 				   iwl_mvm_temp_notif_wait, temp);
259e705c121SKalle Valo 
260762c523fSGil Adam 	ret = iwl_mvm_send_temp_cmd(mvm, false, temp);
261e705c121SKalle Valo 	if (ret) {
262e705c121SKalle Valo 		iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif);
263e705c121SKalle Valo 		return ret;
264e705c121SKalle Valo 	}
265e705c121SKalle Valo 
266e705c121SKalle Valo 	ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif,
267e705c121SKalle Valo 				    IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT);
2687869318eSChaya Rachel Ivgi 	if (ret)
269c61734a6SGolan Ben Ami 		IWL_WARN(mvm, "Getting the temperature timed out\n");
270e705c121SKalle Valo 
2717869318eSChaya Rachel Ivgi 	return ret;
272e705c121SKalle Valo }
273e705c121SKalle Valo 
check_exit_ctkill(struct work_struct * work)274e705c121SKalle Valo static void check_exit_ctkill(struct work_struct *work)
275e705c121SKalle Valo {
276e705c121SKalle Valo 	struct iwl_mvm_tt_mgmt *tt;
277e705c121SKalle Valo 	struct iwl_mvm *mvm;
278e705c121SKalle Valo 	u32 duration;
279e705c121SKalle Valo 	s32 temp;
2807869318eSChaya Rachel Ivgi 	int ret;
281e705c121SKalle Valo 
282e705c121SKalle Valo 	tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work);
283e705c121SKalle Valo 	mvm = container_of(tt, struct iwl_mvm, thermal_throttle);
284e705c121SKalle Valo 
2850a3b7119SChaya Rachel Ivgi 	if (iwl_mvm_is_tt_in_fw(mvm)) {
2860a3b7119SChaya Rachel Ivgi 		iwl_mvm_exit_ctkill(mvm);
2870a3b7119SChaya Rachel Ivgi 
2880a3b7119SChaya Rachel Ivgi 		return;
2890a3b7119SChaya Rachel Ivgi 	}
2900a3b7119SChaya Rachel Ivgi 
291e705c121SKalle Valo 	duration = tt->params.ct_kill_duration;
292e705c121SKalle Valo 
293f9084775SNathan Errera 	flush_work(&mvm->roc_done_wk);
294f9084775SNathan Errera 
295e705c121SKalle Valo 	mutex_lock(&mvm->mutex);
296e705c121SKalle Valo 
297e705c121SKalle Valo 	if (__iwl_mvm_mac_start(mvm))
298e705c121SKalle Valo 		goto reschedule;
299e705c121SKalle Valo 
3007869318eSChaya Rachel Ivgi 	ret = iwl_mvm_get_temp(mvm, &temp);
301e705c121SKalle Valo 
302e705c121SKalle Valo 	__iwl_mvm_mac_stop(mvm);
303e705c121SKalle Valo 
3047869318eSChaya Rachel Ivgi 	if (ret)
305e705c121SKalle Valo 		goto reschedule;
306e705c121SKalle Valo 
307e705c121SKalle Valo 	IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp);
308e705c121SKalle Valo 
309e705c121SKalle Valo 	if (temp <= tt->params.ct_kill_exit) {
310e705c121SKalle Valo 		mutex_unlock(&mvm->mutex);
311e705c121SKalle Valo 		iwl_mvm_exit_ctkill(mvm);
312e705c121SKalle Valo 		return;
313e705c121SKalle Valo 	}
314e705c121SKalle Valo 
315e705c121SKalle Valo reschedule:
316e705c121SKalle Valo 	mutex_unlock(&mvm->mutex);
317e705c121SKalle Valo 	schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit,
318e705c121SKalle Valo 			      round_jiffies(duration * HZ));
319e705c121SKalle Valo }
320e705c121SKalle Valo 
iwl_mvm_tt_smps_iterator(void * _data,u8 * mac,struct ieee80211_vif * vif)321e705c121SKalle Valo static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac,
322e705c121SKalle Valo 				     struct ieee80211_vif *vif)
323e705c121SKalle Valo {
324e705c121SKalle Valo 	struct iwl_mvm *mvm = _data;
325e705c121SKalle Valo 	enum ieee80211_smps_mode smps_mode;
326e705c121SKalle Valo 
327e705c121SKalle Valo 	lockdep_assert_held(&mvm->mutex);
328e705c121SKalle Valo 
329e705c121SKalle Valo 	if (mvm->thermal_throttle.dynamic_smps)
330e705c121SKalle Valo 		smps_mode = IEEE80211_SMPS_DYNAMIC;
331e705c121SKalle Valo 	else
332e705c121SKalle Valo 		smps_mode = IEEE80211_SMPS_AUTOMATIC;
333e705c121SKalle Valo 
334e705c121SKalle Valo 	if (vif->type != NL80211_IFTYPE_STATION)
335e705c121SKalle Valo 		return;
336e705c121SKalle Valo 
337*1a3e7039SGregory Greenman 	iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode, 0);
338e705c121SKalle Valo }
339e705c121SKalle Valo 
iwl_mvm_tt_tx_protection(struct iwl_mvm * mvm,bool enable)340e705c121SKalle Valo static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable)
341e705c121SKalle Valo {
342e705c121SKalle Valo 	struct iwl_mvm_sta *mvmsta;
343e705c121SKalle Valo 	int i, err;
344e705c121SKalle Valo 
345be9ae34eSNathan Errera 	for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) {
34613303c0fSSara Sharon 		mvmsta = iwl_mvm_sta_from_staid_protected(mvm, i);
34713303c0fSSara Sharon 		if (!mvmsta)
348e705c121SKalle Valo 			continue;
34913303c0fSSara Sharon 
350e705c121SKalle Valo 		if (enable == mvmsta->tt_tx_protection)
351e705c121SKalle Valo 			continue;
352e705c121SKalle Valo 		err = iwl_mvm_tx_protection(mvm, mvmsta, enable);
353e705c121SKalle Valo 		if (err) {
354e705c121SKalle Valo 			IWL_ERR(mvm, "Failed to %s Tx protection\n",
355e705c121SKalle Valo 				enable ? "enable" : "disable");
356e705c121SKalle Valo 		} else {
357e705c121SKalle Valo 			IWL_DEBUG_TEMP(mvm, "%s Tx protection\n",
358e705c121SKalle Valo 				       enable ? "Enable" : "Disable");
359e705c121SKalle Valo 			mvmsta->tt_tx_protection = enable;
360e705c121SKalle Valo 		}
361e705c121SKalle Valo 	}
362e705c121SKalle Valo }
363e705c121SKalle Valo 
iwl_mvm_tt_tx_backoff(struct iwl_mvm * mvm,u32 backoff)364e705c121SKalle Valo void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff)
365e705c121SKalle Valo {
366e705c121SKalle Valo 	struct iwl_host_cmd cmd = {
367e705c121SKalle Valo 		.id = REPLY_THERMAL_MNG_BACKOFF,
368e705c121SKalle Valo 		.len = { sizeof(u32), },
369e705c121SKalle Valo 		.data = { &backoff, },
370e705c121SKalle Valo 	};
371e705c121SKalle Valo 
372e705c121SKalle Valo 	backoff = max(backoff, mvm->thermal_throttle.min_backoff);
373e705c121SKalle Valo 
374e705c121SKalle Valo 	if (iwl_mvm_send_cmd(mvm, &cmd) == 0) {
375e705c121SKalle Valo 		IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n",
376e705c121SKalle Valo 			       backoff);
377e705c121SKalle Valo 		mvm->thermal_throttle.tx_backoff = backoff;
378e705c121SKalle Valo 	} else {
379e705c121SKalle Valo 		IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n");
380e705c121SKalle Valo 	}
381e705c121SKalle Valo }
382e705c121SKalle Valo 
iwl_mvm_tt_handler(struct iwl_mvm * mvm)383e705c121SKalle Valo void iwl_mvm_tt_handler(struct iwl_mvm *mvm)
384e705c121SKalle Valo {
385e705c121SKalle Valo 	struct iwl_tt_params *params = &mvm->thermal_throttle.params;
386e705c121SKalle Valo 	struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
387e705c121SKalle Valo 	s32 temperature = mvm->temperature;
388e705c121SKalle Valo 	bool throttle_enable = false;
389e705c121SKalle Valo 	int i;
390e705c121SKalle Valo 	u32 tx_backoff;
391e705c121SKalle Valo 
392e705c121SKalle Valo 	IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature);
393e705c121SKalle Valo 
394e705c121SKalle Valo 	if (params->support_ct_kill && temperature >= params->ct_kill_entry) {
395e705c121SKalle Valo 		iwl_mvm_enter_ctkill(mvm);
396e705c121SKalle Valo 		return;
397e705c121SKalle Valo 	}
398e705c121SKalle Valo 
399e705c121SKalle Valo 	if (params->support_ct_kill &&
400e705c121SKalle Valo 	    temperature <= params->ct_kill_exit) {
401e705c121SKalle Valo 		iwl_mvm_exit_ctkill(mvm);
402e705c121SKalle Valo 		return;
403e705c121SKalle Valo 	}
404e705c121SKalle Valo 
405e705c121SKalle Valo 	if (params->support_dynamic_smps) {
406e705c121SKalle Valo 		if (!tt->dynamic_smps &&
407e705c121SKalle Valo 		    temperature >= params->dynamic_smps_entry) {
408e705c121SKalle Valo 			IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n");
409e705c121SKalle Valo 			tt->dynamic_smps = true;
410e705c121SKalle Valo 			ieee80211_iterate_active_interfaces_atomic(
411e705c121SKalle Valo 					mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
412e705c121SKalle Valo 					iwl_mvm_tt_smps_iterator, mvm);
413e705c121SKalle Valo 			throttle_enable = true;
414e705c121SKalle Valo 		} else if (tt->dynamic_smps &&
415e705c121SKalle Valo 			   temperature <= params->dynamic_smps_exit) {
416e705c121SKalle Valo 			IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n");
417e705c121SKalle Valo 			tt->dynamic_smps = false;
418e705c121SKalle Valo 			ieee80211_iterate_active_interfaces_atomic(
419e705c121SKalle Valo 					mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
420e705c121SKalle Valo 					iwl_mvm_tt_smps_iterator, mvm);
421e705c121SKalle Valo 		}
422e705c121SKalle Valo 	}
423e705c121SKalle Valo 
424e705c121SKalle Valo 	if (params->support_tx_protection) {
425e705c121SKalle Valo 		if (temperature >= params->tx_protection_entry) {
426e705c121SKalle Valo 			iwl_mvm_tt_tx_protection(mvm, true);
427e705c121SKalle Valo 			throttle_enable = true;
428e705c121SKalle Valo 		} else if (temperature <= params->tx_protection_exit) {
429e705c121SKalle Valo 			iwl_mvm_tt_tx_protection(mvm, false);
430e705c121SKalle Valo 		}
431e705c121SKalle Valo 	}
432e705c121SKalle Valo 
433e705c121SKalle Valo 	if (params->support_tx_backoff) {
434e705c121SKalle Valo 		tx_backoff = tt->min_backoff;
435e705c121SKalle Valo 		for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) {
436e705c121SKalle Valo 			if (temperature < params->tx_backoff[i].temperature)
437e705c121SKalle Valo 				break;
438e705c121SKalle Valo 			tx_backoff = max(tt->min_backoff,
439e705c121SKalle Valo 					 params->tx_backoff[i].backoff);
440e705c121SKalle Valo 		}
441e705c121SKalle Valo 		if (tx_backoff != tt->min_backoff)
442e705c121SKalle Valo 			throttle_enable = true;
443e705c121SKalle Valo 		if (tt->tx_backoff != tx_backoff)
444e705c121SKalle Valo 			iwl_mvm_tt_tx_backoff(mvm, tx_backoff);
445e705c121SKalle Valo 	}
446e705c121SKalle Valo 
447e705c121SKalle Valo 	if (!tt->throttle && throttle_enable) {
448e705c121SKalle Valo 		IWL_WARN(mvm,
449e705c121SKalle Valo 			 "Due to high temperature thermal throttling initiated\n");
450e705c121SKalle Valo 		tt->throttle = true;
451e705c121SKalle Valo 	} else if (tt->throttle && !tt->dynamic_smps &&
452e705c121SKalle Valo 		   tt->tx_backoff == tt->min_backoff &&
453e705c121SKalle Valo 		   temperature <= params->tx_protection_exit) {
454e705c121SKalle Valo 		IWL_WARN(mvm,
455e705c121SKalle Valo 			 "Temperature is back to normal thermal throttling stopped\n");
456e705c121SKalle Valo 		tt->throttle = false;
457e705c121SKalle Valo 	}
458e705c121SKalle Valo }
459e705c121SKalle Valo 
460e705c121SKalle Valo static const struct iwl_tt_params iwl_mvm_default_tt_params = {
461e705c121SKalle Valo 	.ct_kill_entry = 118,
462e705c121SKalle Valo 	.ct_kill_exit = 96,
463e705c121SKalle Valo 	.ct_kill_duration = 5,
464e705c121SKalle Valo 	.dynamic_smps_entry = 114,
465e705c121SKalle Valo 	.dynamic_smps_exit = 110,
466e705c121SKalle Valo 	.tx_protection_entry = 114,
467e705c121SKalle Valo 	.tx_protection_exit = 108,
468e705c121SKalle Valo 	.tx_backoff = {
469e705c121SKalle Valo 		{.temperature = 112, .backoff = 200},
470e705c121SKalle Valo 		{.temperature = 113, .backoff = 600},
471e705c121SKalle Valo 		{.temperature = 114, .backoff = 1200},
472e705c121SKalle Valo 		{.temperature = 115, .backoff = 2000},
473e705c121SKalle Valo 		{.temperature = 116, .backoff = 4000},
474e705c121SKalle Valo 		{.temperature = 117, .backoff = 10000},
475e705c121SKalle Valo 	},
476e705c121SKalle Valo 	.support_ct_kill = true,
477e705c121SKalle Valo 	.support_dynamic_smps = true,
478e705c121SKalle Valo 	.support_tx_protection = true,
479e705c121SKalle Valo 	.support_tx_backoff = true,
480e705c121SKalle Valo };
481e705c121SKalle Valo 
482b358993bSChaya Rachel Ivgi /* budget in mWatt */
483b358993bSChaya Rachel Ivgi static const u32 iwl_mvm_cdev_budgets[] = {
484220089c7SMordechay Goodstein 	2400,	/* cooling state 0 */
485220089c7SMordechay Goodstein 	2000,	/* cooling state 1 */
486220089c7SMordechay Goodstein 	1800,	/* cooling state 2 */
487220089c7SMordechay Goodstein 	1600,	/* cooling state 3 */
488220089c7SMordechay Goodstein 	1400,	/* cooling state 4 */
489220089c7SMordechay Goodstein 	1200,	/* cooling state 5 */
490220089c7SMordechay Goodstein 	1000,	/* cooling state 6 */
491220089c7SMordechay Goodstein 	900,	/* cooling state 7 */
492220089c7SMordechay Goodstein 	800,	/* cooling state 8 */
493220089c7SMordechay Goodstein 	700,	/* cooling state 9 */
494220089c7SMordechay Goodstein 	650,	/* cooling state 10 */
495220089c7SMordechay Goodstein 	600,	/* cooling state 11 */
496220089c7SMordechay Goodstein 	550,	/* cooling state 12 */
497220089c7SMordechay Goodstein 	500,	/* cooling state 13 */
498220089c7SMordechay Goodstein 	450,	/* cooling state 14 */
499220089c7SMordechay Goodstein 	400,	/* cooling state 15 */
500220089c7SMordechay Goodstein 	350,	/* cooling state 16 */
501220089c7SMordechay Goodstein 	300,	/* cooling state 17 */
502220089c7SMordechay Goodstein 	250,	/* cooling state 18 */
503220089c7SMordechay Goodstein 	200,	/* cooling state 19 */
504220089c7SMordechay Goodstein 	150,	/* cooling state 20 */
505b358993bSChaya Rachel Ivgi };
506b358993bSChaya Rachel Ivgi 
iwl_mvm_ctdp_command(struct iwl_mvm * mvm,u32 op,u32 state)507b358993bSChaya Rachel Ivgi int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 state)
50800f481bdSChaya Rachel Ivgi {
50900f481bdSChaya Rachel Ivgi 	struct iwl_mvm_ctdp_cmd cmd = {
51000f481bdSChaya Rachel Ivgi 		.operation = cpu_to_le32(op),
511b358993bSChaya Rachel Ivgi 		.budget = cpu_to_le32(iwl_mvm_cdev_budgets[state]),
51200f481bdSChaya Rachel Ivgi 		.window_size = 0,
51300f481bdSChaya Rachel Ivgi 	};
51400f481bdSChaya Rachel Ivgi 	int ret;
51500f481bdSChaya Rachel Ivgi 	u32 status;
51600f481bdSChaya Rachel Ivgi 
51700f481bdSChaya Rachel Ivgi 	lockdep_assert_held(&mvm->mutex);
51800f481bdSChaya Rachel Ivgi 
519d460f1fbSLuca Coelho 	status = 0;
52000f481bdSChaya Rachel Ivgi 	ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP,
52100f481bdSChaya Rachel Ivgi 						       CTDP_CONFIG_CMD),
52200f481bdSChaya Rachel Ivgi 					  sizeof(cmd), &cmd, &status);
52300f481bdSChaya Rachel Ivgi 
52400f481bdSChaya Rachel Ivgi 	if (ret) {
52500f481bdSChaya Rachel Ivgi 		IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret);
52600f481bdSChaya Rachel Ivgi 		return ret;
52700f481bdSChaya Rachel Ivgi 	}
52800f481bdSChaya Rachel Ivgi 
52900f481bdSChaya Rachel Ivgi 	switch (op) {
53000f481bdSChaya Rachel Ivgi 	case CTDP_CMD_OPERATION_START:
53100f481bdSChaya Rachel Ivgi #ifdef CONFIG_THERMAL
532b358993bSChaya Rachel Ivgi 		mvm->cooling_dev.cur_state = state;
53300f481bdSChaya Rachel Ivgi #endif /* CONFIG_THERMAL */
53400f481bdSChaya Rachel Ivgi 		break;
53500f481bdSChaya Rachel Ivgi 	case CTDP_CMD_OPERATION_REPORT:
53600f481bdSChaya Rachel Ivgi 		IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status);
53700f481bdSChaya Rachel Ivgi 		/* when the function is called with CTDP_CMD_OPERATION_REPORT
53800f481bdSChaya Rachel Ivgi 		 * option the function should return the average budget value
53900f481bdSChaya Rachel Ivgi 		 * that is received from the FW.
54000f481bdSChaya Rachel Ivgi 		 * The budget can't be less or equal to 0, so it's possible
54100f481bdSChaya Rachel Ivgi 		 * to distinguish between error values and budgets.
54200f481bdSChaya Rachel Ivgi 		 */
54300f481bdSChaya Rachel Ivgi 		return status;
54400f481bdSChaya Rachel Ivgi 	case CTDP_CMD_OPERATION_STOP:
54500f481bdSChaya Rachel Ivgi 		IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n");
54600f481bdSChaya Rachel Ivgi 		break;
54700f481bdSChaya Rachel Ivgi 	}
54800f481bdSChaya Rachel Ivgi 
54900f481bdSChaya Rachel Ivgi 	return 0;
55000f481bdSChaya Rachel Ivgi }
55100f481bdSChaya Rachel Ivgi 
552c221daf2SChaya Rachel Ivgi #ifdef CONFIG_THERMAL
compare_temps(const void * a,const void * b)553c221daf2SChaya Rachel Ivgi static int compare_temps(const void *a, const void *b)
554c221daf2SChaya Rachel Ivgi {
555c221daf2SChaya Rachel Ivgi 	return ((s16)le16_to_cpu(*(__le16 *)a) -
556c221daf2SChaya Rachel Ivgi 		(s16)le16_to_cpu(*(__le16 *)b));
557c221daf2SChaya Rachel Ivgi }
5582d88b2cfSJohannes Berg #endif
559c221daf2SChaya Rachel Ivgi 
iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm * mvm)560c221daf2SChaya Rachel Ivgi int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm)
561c221daf2SChaya Rachel Ivgi {
562c221daf2SChaya Rachel Ivgi 	struct temp_report_ths_cmd cmd = {0};
5632d88b2cfSJohannes Berg 	int ret;
5642d88b2cfSJohannes Berg #ifdef CONFIG_THERMAL
5652d88b2cfSJohannes Berg 	int i, j, idx = 0;
566c221daf2SChaya Rachel Ivgi 
567c221daf2SChaya Rachel Ivgi 	lockdep_assert_held(&mvm->mutex);
568c221daf2SChaya Rachel Ivgi 
56991f66a3cSEmmanuel Grumbach 	if (!mvm->tz_device.tzone)
5702d88b2cfSJohannes Berg 		goto send;
57191f66a3cSEmmanuel Grumbach 
572c221daf2SChaya Rachel Ivgi 	/* The driver holds array of temperature trips that are unsorted
573c221daf2SChaya Rachel Ivgi 	 * and uncompressed, the FW should get it compressed and sorted
574c221daf2SChaya Rachel Ivgi 	 */
575c221daf2SChaya Rachel Ivgi 
5763d2f20adSDaniel Lezcano 	/* compress trips to cmd array, remove uninitialized values*/
57791f66a3cSEmmanuel Grumbach 	for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) {
5783d2f20adSDaniel Lezcano 		if (mvm->tz_device.trips[i].temperature != INT_MIN) {
579c221daf2SChaya Rachel Ivgi 			cmd.thresholds[idx++] =
5803d2f20adSDaniel Lezcano 				cpu_to_le16((s16)(mvm->tz_device.trips[i].temperature / 1000));
581c221daf2SChaya Rachel Ivgi 		}
58291f66a3cSEmmanuel Grumbach 	}
583c221daf2SChaya Rachel Ivgi 	cmd.num_temps = cpu_to_le32(idx);
584c221daf2SChaya Rachel Ivgi 
585c221daf2SChaya Rachel Ivgi 	if (!idx)
586c221daf2SChaya Rachel Ivgi 		goto send;
587c221daf2SChaya Rachel Ivgi 
588c221daf2SChaya Rachel Ivgi 	/*sort cmd array*/
589c221daf2SChaya Rachel Ivgi 	sort(cmd.thresholds, idx, sizeof(s16), compare_temps, NULL);
590c221daf2SChaya Rachel Ivgi 
591c221daf2SChaya Rachel Ivgi 	/* we should save the indexes of trips because we sort
592c221daf2SChaya Rachel Ivgi 	 * and compress the orginal array
593c221daf2SChaya Rachel Ivgi 	 */
594c221daf2SChaya Rachel Ivgi 	for (i = 0; i < idx; i++) {
595c221daf2SChaya Rachel Ivgi 		for (j = 0; j < IWL_MAX_DTS_TRIPS; j++) {
5963d2f20adSDaniel Lezcano 			if ((int)(le16_to_cpu(cmd.thresholds[i]) * 1000) ==
5973d2f20adSDaniel Lezcano 			    mvm->tz_device.trips[j].temperature)
598c221daf2SChaya Rachel Ivgi 				mvm->tz_device.fw_trips_index[i] = j;
599c221daf2SChaya Rachel Ivgi 		}
600c221daf2SChaya Rachel Ivgi 	}
601c221daf2SChaya Rachel Ivgi 
602c221daf2SChaya Rachel Ivgi send:
6032d88b2cfSJohannes Berg #endif
604c221daf2SChaya Rachel Ivgi 	ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP,
605c221daf2SChaya Rachel Ivgi 						TEMP_REPORTING_THRESHOLDS_CMD),
606c221daf2SChaya Rachel Ivgi 				   0, sizeof(cmd), &cmd);
607c221daf2SChaya Rachel Ivgi 	if (ret)
608c221daf2SChaya Rachel Ivgi 		IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n",
609c221daf2SChaya Rachel Ivgi 			ret);
610c221daf2SChaya Rachel Ivgi 
611c221daf2SChaya Rachel Ivgi 	return ret;
612c221daf2SChaya Rachel Ivgi }
613c221daf2SChaya Rachel Ivgi 
6142d88b2cfSJohannes Berg #ifdef CONFIG_THERMAL
iwl_mvm_tzone_get_temp(struct thermal_zone_device * device,int * temperature)615c221daf2SChaya Rachel Ivgi static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device,
616c221daf2SChaya Rachel Ivgi 				  int *temperature)
617c221daf2SChaya Rachel Ivgi {
6183d4e1badSDaniel Lezcano 	struct iwl_mvm *mvm = thermal_zone_device_priv(device);
619c221daf2SChaya Rachel Ivgi 	int ret;
620c221daf2SChaya Rachel Ivgi 	int temp;
621c221daf2SChaya Rachel Ivgi 
622c221daf2SChaya Rachel Ivgi 	mutex_lock(&mvm->mutex);
623c221daf2SChaya Rachel Ivgi 
624aab6930dSJohannes Berg 	if (!iwl_mvm_firmware_running(mvm) ||
625702e975dSJohannes Berg 	    mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
6261442a9a9SLuca Coelho 		ret = -ENODATA;
627c221daf2SChaya Rachel Ivgi 		goto out;
628c221daf2SChaya Rachel Ivgi 	}
629c221daf2SChaya Rachel Ivgi 
630c221daf2SChaya Rachel Ivgi 	ret = iwl_mvm_get_temp(mvm, &temp);
631c221daf2SChaya Rachel Ivgi 	if (ret)
632c221daf2SChaya Rachel Ivgi 		goto out;
633c221daf2SChaya Rachel Ivgi 
634c221daf2SChaya Rachel Ivgi 	*temperature = temp * 1000;
635c221daf2SChaya Rachel Ivgi 
636c221daf2SChaya Rachel Ivgi out:
637c221daf2SChaya Rachel Ivgi 	mutex_unlock(&mvm->mutex);
638c221daf2SChaya Rachel Ivgi 	return ret;
639c221daf2SChaya Rachel Ivgi }
640c221daf2SChaya Rachel Ivgi 
iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device * device,int trip,int temp)641c221daf2SChaya Rachel Ivgi static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device,
642c221daf2SChaya Rachel Ivgi 				       int trip, int temp)
643c221daf2SChaya Rachel Ivgi {
6443d4e1badSDaniel Lezcano 	struct iwl_mvm *mvm = thermal_zone_device_priv(device);
645c221daf2SChaya Rachel Ivgi 	struct iwl_mvm_thermal_device *tzone;
6463d2f20adSDaniel Lezcano 	int ret;
647c221daf2SChaya Rachel Ivgi 
648c221daf2SChaya Rachel Ivgi 	mutex_lock(&mvm->mutex);
649c221daf2SChaya Rachel Ivgi 
650aab6930dSJohannes Berg 	if (!iwl_mvm_firmware_running(mvm) ||
651702e975dSJohannes Berg 	    mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
652c221daf2SChaya Rachel Ivgi 		ret = -EIO;
653c221daf2SChaya Rachel Ivgi 		goto out;
654c221daf2SChaya Rachel Ivgi 	}
655c221daf2SChaya Rachel Ivgi 
656c221daf2SChaya Rachel Ivgi 	if ((temp / 1000) > S16_MAX) {
657c221daf2SChaya Rachel Ivgi 		ret = -EINVAL;
658c221daf2SChaya Rachel Ivgi 		goto out;
659c221daf2SChaya Rachel Ivgi 	}
660c221daf2SChaya Rachel Ivgi 
661c221daf2SChaya Rachel Ivgi 	tzone = &mvm->tz_device;
662c221daf2SChaya Rachel Ivgi 	if (!tzone) {
663c221daf2SChaya Rachel Ivgi 		ret = -EIO;
664c221daf2SChaya Rachel Ivgi 		goto out;
665c221daf2SChaya Rachel Ivgi 	}
666c221daf2SChaya Rachel Ivgi 
667c221daf2SChaya Rachel Ivgi 	ret = iwl_mvm_send_temp_report_ths_cmd(mvm);
668c221daf2SChaya Rachel Ivgi out:
669c221daf2SChaya Rachel Ivgi 	mutex_unlock(&mvm->mutex);
670c221daf2SChaya Rachel Ivgi 	return ret;
671c221daf2SChaya Rachel Ivgi }
672c221daf2SChaya Rachel Ivgi 
673c221daf2SChaya Rachel Ivgi static  struct thermal_zone_device_ops tzone_ops = {
674c221daf2SChaya Rachel Ivgi 	.get_temp = iwl_mvm_tzone_get_temp,
675c221daf2SChaya Rachel Ivgi 	.set_trip_temp = iwl_mvm_tzone_set_trip_temp,
676c221daf2SChaya Rachel Ivgi };
677c221daf2SChaya Rachel Ivgi 
678c221daf2SChaya Rachel Ivgi /* make all trips writable */
679c221daf2SChaya Rachel Ivgi #define IWL_WRITABLE_TRIPS_MSK (BIT(IWL_MAX_DTS_TRIPS) - 1)
680c221daf2SChaya Rachel Ivgi 
iwl_mvm_thermal_zone_register(struct iwl_mvm * mvm)681c221daf2SChaya Rachel Ivgi static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm)
682c221daf2SChaya Rachel Ivgi {
683bbcf90c0SAndrzej Pietrasiewicz 	int i, ret;
684baa6cf84SAndrei Otcheretianski 	char name[16];
685baa6cf84SAndrei Otcheretianski 	static atomic_t counter = ATOMIC_INIT(0);
686c221daf2SChaya Rachel Ivgi 
687c221daf2SChaya Rachel Ivgi 	if (!iwl_mvm_is_tt_in_fw(mvm)) {
688c221daf2SChaya Rachel Ivgi 		mvm->tz_device.tzone = NULL;
689c221daf2SChaya Rachel Ivgi 
690c221daf2SChaya Rachel Ivgi 		return;
691c221daf2SChaya Rachel Ivgi 	}
692c221daf2SChaya Rachel Ivgi 
693c221daf2SChaya Rachel Ivgi 	BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
694c221daf2SChaya Rachel Ivgi 
695baa6cf84SAndrei Otcheretianski 	sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF);
6963d2f20adSDaniel Lezcano 	mvm->tz_device.tzone = thermal_zone_device_register_with_trips(name,
6973d2f20adSDaniel Lezcano 							mvm->tz_device.trips,
698c221daf2SChaya Rachel Ivgi 							IWL_MAX_DTS_TRIPS,
699c221daf2SChaya Rachel Ivgi 							IWL_WRITABLE_TRIPS_MSK,
700c221daf2SChaya Rachel Ivgi 							mvm, &tzone_ops,
701c221daf2SChaya Rachel Ivgi 							NULL, 0, 0);
702c221daf2SChaya Rachel Ivgi 	if (IS_ERR(mvm->tz_device.tzone)) {
703c221daf2SChaya Rachel Ivgi 		IWL_DEBUG_TEMP(mvm,
704c221daf2SChaya Rachel Ivgi 			       "Failed to register to thermal zone (err = %ld)\n",
705c221daf2SChaya Rachel Ivgi 			       PTR_ERR(mvm->tz_device.tzone));
70691f66a3cSEmmanuel Grumbach 		mvm->tz_device.tzone = NULL;
707c221daf2SChaya Rachel Ivgi 		return;
708c221daf2SChaya Rachel Ivgi 	}
709c221daf2SChaya Rachel Ivgi 
710bbcf90c0SAndrzej Pietrasiewicz 	ret = thermal_zone_device_enable(mvm->tz_device.tzone);
711bbcf90c0SAndrzej Pietrasiewicz 	if (ret) {
712bbcf90c0SAndrzej Pietrasiewicz 		IWL_DEBUG_TEMP(mvm, "Failed to enable thermal zone\n");
713bbcf90c0SAndrzej Pietrasiewicz 		thermal_zone_device_unregister(mvm->tz_device.tzone);
714bbcf90c0SAndrzej Pietrasiewicz 		return;
715bbcf90c0SAndrzej Pietrasiewicz 	}
716bbcf90c0SAndrzej Pietrasiewicz 
717c221daf2SChaya Rachel Ivgi 	/* 0 is a valid temperature,
718c221daf2SChaya Rachel Ivgi 	 * so initialize the array with S16_MIN which invalid temperature
719c221daf2SChaya Rachel Ivgi 	 */
7203d2f20adSDaniel Lezcano 	for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++) {
7213d2f20adSDaniel Lezcano 		mvm->tz_device.trips[i].temperature = INT_MIN;
7223d2f20adSDaniel Lezcano 		mvm->tz_device.trips[i].type = THERMAL_TRIP_PASSIVE;
7233d2f20adSDaniel Lezcano 	}
724c221daf2SChaya Rachel Ivgi }
725c221daf2SChaya Rachel Ivgi 
iwl_mvm_tcool_get_max_state(struct thermal_cooling_device * cdev,unsigned long * state)7265c89e7bcSChaya Rachel Ivgi static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev,
7275c89e7bcSChaya Rachel Ivgi 				       unsigned long *state)
7285c89e7bcSChaya Rachel Ivgi {
7295c89e7bcSChaya Rachel Ivgi 	*state = ARRAY_SIZE(iwl_mvm_cdev_budgets) - 1;
7305c89e7bcSChaya Rachel Ivgi 
7315c89e7bcSChaya Rachel Ivgi 	return 0;
7325c89e7bcSChaya Rachel Ivgi }
7335c89e7bcSChaya Rachel Ivgi 
iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device * cdev,unsigned long * state)7345c89e7bcSChaya Rachel Ivgi static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev,
7355c89e7bcSChaya Rachel Ivgi 				       unsigned long *state)
7365c89e7bcSChaya Rachel Ivgi {
7375c89e7bcSChaya Rachel Ivgi 	struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
7385c89e7bcSChaya Rachel Ivgi 
7395c89e7bcSChaya Rachel Ivgi 	*state = mvm->cooling_dev.cur_state;
740b358993bSChaya Rachel Ivgi 
7415c89e7bcSChaya Rachel Ivgi 	return 0;
7425c89e7bcSChaya Rachel Ivgi }
7435c89e7bcSChaya Rachel Ivgi 
iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device * cdev,unsigned long new_state)7445c89e7bcSChaya Rachel Ivgi static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev,
7455c89e7bcSChaya Rachel Ivgi 				       unsigned long new_state)
7465c89e7bcSChaya Rachel Ivgi {
7475c89e7bcSChaya Rachel Ivgi 	struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
7485c89e7bcSChaya Rachel Ivgi 	int ret;
7495c89e7bcSChaya Rachel Ivgi 
7505c89e7bcSChaya Rachel Ivgi 	mutex_lock(&mvm->mutex);
7515c89e7bcSChaya Rachel Ivgi 
752aab6930dSJohannes Berg 	if (!iwl_mvm_firmware_running(mvm) ||
753702e975dSJohannes Berg 	    mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
754d9954405SJohannes Berg 		ret = -EIO;
755d9954405SJohannes Berg 		goto unlock;
756d9954405SJohannes Berg 	}
757d9954405SJohannes Berg 
7585c89e7bcSChaya Rachel Ivgi 	if (new_state >= ARRAY_SIZE(iwl_mvm_cdev_budgets)) {
7595c89e7bcSChaya Rachel Ivgi 		ret = -EINVAL;
7605c89e7bcSChaya Rachel Ivgi 		goto unlock;
7615c89e7bcSChaya Rachel Ivgi 	}
7625c89e7bcSChaya Rachel Ivgi 
7635c89e7bcSChaya Rachel Ivgi 	ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START,
764b358993bSChaya Rachel Ivgi 				   new_state);
7655c89e7bcSChaya Rachel Ivgi 
7665c89e7bcSChaya Rachel Ivgi unlock:
7675c89e7bcSChaya Rachel Ivgi 	mutex_unlock(&mvm->mutex);
7685c89e7bcSChaya Rachel Ivgi 	return ret;
7695c89e7bcSChaya Rachel Ivgi }
7705c89e7bcSChaya Rachel Ivgi 
771b1a1efc5SBhumika Goyal static const struct thermal_cooling_device_ops tcooling_ops = {
7725c89e7bcSChaya Rachel Ivgi 	.get_max_state = iwl_mvm_tcool_get_max_state,
7735c89e7bcSChaya Rachel Ivgi 	.get_cur_state = iwl_mvm_tcool_get_cur_state,
7745c89e7bcSChaya Rachel Ivgi 	.set_cur_state = iwl_mvm_tcool_set_cur_state,
7755c89e7bcSChaya Rachel Ivgi };
7765c89e7bcSChaya Rachel Ivgi 
iwl_mvm_cooling_device_register(struct iwl_mvm * mvm)77791f66a3cSEmmanuel Grumbach static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm)
7785c89e7bcSChaya Rachel Ivgi {
7795c89e7bcSChaya Rachel Ivgi 	char name[] = "iwlwifi";
7805c89e7bcSChaya Rachel Ivgi 
78191f66a3cSEmmanuel Grumbach 	if (!iwl_mvm_is_ctdp_supported(mvm))
78291f66a3cSEmmanuel Grumbach 		return;
7835c89e7bcSChaya Rachel Ivgi 
7845c89e7bcSChaya Rachel Ivgi 	BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
7855c89e7bcSChaya Rachel Ivgi 
7865c89e7bcSChaya Rachel Ivgi 	mvm->cooling_dev.cdev =
7875c89e7bcSChaya Rachel Ivgi 		thermal_cooling_device_register(name,
7885c89e7bcSChaya Rachel Ivgi 						mvm,
7895c89e7bcSChaya Rachel Ivgi 						&tcooling_ops);
7905c89e7bcSChaya Rachel Ivgi 
7915c89e7bcSChaya Rachel Ivgi 	if (IS_ERR(mvm->cooling_dev.cdev)) {
7925c89e7bcSChaya Rachel Ivgi 		IWL_DEBUG_TEMP(mvm,
7935c89e7bcSChaya Rachel Ivgi 			       "Failed to register to cooling device (err = %ld)\n",
7945c89e7bcSChaya Rachel Ivgi 			       PTR_ERR(mvm->cooling_dev.cdev));
79591f66a3cSEmmanuel Grumbach 		mvm->cooling_dev.cdev = NULL;
79691f66a3cSEmmanuel Grumbach 		return;
7975c89e7bcSChaya Rachel Ivgi 	}
7985c89e7bcSChaya Rachel Ivgi }
7995c89e7bcSChaya Rachel Ivgi 
iwl_mvm_thermal_zone_unregister(struct iwl_mvm * mvm)800c221daf2SChaya Rachel Ivgi static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm)
801c221daf2SChaya Rachel Ivgi {
80291f66a3cSEmmanuel Grumbach 	if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone)
803c221daf2SChaya Rachel Ivgi 		return;
804c221daf2SChaya Rachel Ivgi 
805c221daf2SChaya Rachel Ivgi 	IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n");
80692549cdcSJens Axboe 	if (mvm->tz_device.tzone) {
807c221daf2SChaya Rachel Ivgi 		thermal_zone_device_unregister(mvm->tz_device.tzone);
808c221daf2SChaya Rachel Ivgi 		mvm->tz_device.tzone = NULL;
809c221daf2SChaya Rachel Ivgi 	}
81092549cdcSJens Axboe }
8115c89e7bcSChaya Rachel Ivgi 
iwl_mvm_cooling_device_unregister(struct iwl_mvm * mvm)8125c89e7bcSChaya Rachel Ivgi static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm)
8135c89e7bcSChaya Rachel Ivgi {
81491f66a3cSEmmanuel Grumbach 	if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev)
8155c89e7bcSChaya Rachel Ivgi 		return;
8165c89e7bcSChaya Rachel Ivgi 
8175c89e7bcSChaya Rachel Ivgi 	IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n");
81892549cdcSJens Axboe 	if (mvm->cooling_dev.cdev) {
8195c89e7bcSChaya Rachel Ivgi 		thermal_cooling_device_unregister(mvm->cooling_dev.cdev);
8205c89e7bcSChaya Rachel Ivgi 		mvm->cooling_dev.cdev = NULL;
8215c89e7bcSChaya Rachel Ivgi 	}
82292549cdcSJens Axboe }
823c221daf2SChaya Rachel Ivgi #endif /* CONFIG_THERMAL */
824c221daf2SChaya Rachel Ivgi 
iwl_mvm_thermal_initialize(struct iwl_mvm * mvm,u32 min_backoff)825c221daf2SChaya Rachel Ivgi void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff)
826e705c121SKalle Valo {
827e705c121SKalle Valo 	struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
828e705c121SKalle Valo 
829e705c121SKalle Valo 	IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n");
830e705c121SKalle Valo 
831e705c121SKalle Valo 	if (mvm->cfg->thermal_params)
832e705c121SKalle Valo 		tt->params = *mvm->cfg->thermal_params;
833e705c121SKalle Valo 	else
834e705c121SKalle Valo 		tt->params = iwl_mvm_default_tt_params;
835e705c121SKalle Valo 
836e705c121SKalle Valo 	tt->throttle = false;
837e705c121SKalle Valo 	tt->dynamic_smps = false;
838e705c121SKalle Valo 	tt->min_backoff = min_backoff;
839e705c121SKalle Valo 	INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill);
840c221daf2SChaya Rachel Ivgi 
841c221daf2SChaya Rachel Ivgi #ifdef CONFIG_THERMAL
8425c89e7bcSChaya Rachel Ivgi 	iwl_mvm_cooling_device_register(mvm);
843c221daf2SChaya Rachel Ivgi 	iwl_mvm_thermal_zone_register(mvm);
844c221daf2SChaya Rachel Ivgi #endif
845de8ba41bSLiad Kaufman 	mvm->init_status |= IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
846e705c121SKalle Valo }
847e705c121SKalle Valo 
iwl_mvm_thermal_exit(struct iwl_mvm * mvm)848c221daf2SChaya Rachel Ivgi void iwl_mvm_thermal_exit(struct iwl_mvm *mvm)
849e705c121SKalle Valo {
850de8ba41bSLiad Kaufman 	if (!(mvm->init_status & IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE))
851de8ba41bSLiad Kaufman 		return;
852de8ba41bSLiad Kaufman 
853e705c121SKalle Valo 	cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit);
854e705c121SKalle Valo 	IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n");
855c221daf2SChaya Rachel Ivgi 
856c221daf2SChaya Rachel Ivgi #ifdef CONFIG_THERMAL
8575c89e7bcSChaya Rachel Ivgi 	iwl_mvm_cooling_device_unregister(mvm);
858c221daf2SChaya Rachel Ivgi 	iwl_mvm_thermal_zone_unregister(mvm);
859c221daf2SChaya Rachel Ivgi #endif
860de8ba41bSLiad Kaufman 	mvm->init_status &= ~IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
861e705c121SKalle Valo }
862