xref: /OK3568_Linux_fs/kernel/drivers/net/wireless/intel/iwlwifi/mvm/tt.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun /******************************************************************************
2*4882a593Smuzhiyun  *
3*4882a593Smuzhiyun  * This file is provided under a dual BSD/GPLv2 license.  When using or
4*4882a593Smuzhiyun  * redistributing this file, you may do so under either license.
5*4882a593Smuzhiyun  *
6*4882a593Smuzhiyun  * GPL LICENSE SUMMARY
7*4882a593Smuzhiyun  *
8*4882a593Smuzhiyun  * Copyright(c) 2013 - 2014, 2019 Intel Corporation. All rights reserved.
9*4882a593Smuzhiyun  * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH
10*4882a593Smuzhiyun  * Copyright(c) 2015 - 2016 Intel Deutschland GmbH
11*4882a593Smuzhiyun  * Copyright(c) 2019 - 2020 Intel Corporation
12*4882a593Smuzhiyun  *
13*4882a593Smuzhiyun  * This program is free software; you can redistribute it and/or modify
14*4882a593Smuzhiyun  * it under the terms of version 2 of the GNU General Public License as
15*4882a593Smuzhiyun  * published by the Free Software Foundation.
16*4882a593Smuzhiyun  *
17*4882a593Smuzhiyun  * This program is distributed in the hope that it will be useful, but
18*4882a593Smuzhiyun  * WITHOUT ANY WARRANTY; without even the implied warranty of
19*4882a593Smuzhiyun  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20*4882a593Smuzhiyun  * General Public License for more details.
21*4882a593Smuzhiyun  *
22*4882a593Smuzhiyun  * The full GNU General Public License is included in this distribution
23*4882a593Smuzhiyun  * in the file called COPYING.
24*4882a593Smuzhiyun  *
25*4882a593Smuzhiyun  * Contact Information:
26*4882a593Smuzhiyun  *  Intel Linux Wireless <linuxwifi@intel.com>
27*4882a593Smuzhiyun  * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
28*4882a593Smuzhiyun  *
29*4882a593Smuzhiyun  * BSD LICENSE
30*4882a593Smuzhiyun  *
31*4882a593Smuzhiyun  * Copyright(c) 2012 - 2014, 2019 Intel Corporation. All rights reserved.
32*4882a593Smuzhiyun  * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH
33*4882a593Smuzhiyun  * Copyright(c) 2015 - 2016 Intel Deutschland GmbH
34*4882a593Smuzhiyun  * Copyright(c) 2019 - 2020 Intel Corporation
35*4882a593Smuzhiyun  * All rights reserved.
36*4882a593Smuzhiyun  *
37*4882a593Smuzhiyun  * Redistribution and use in source and binary forms, with or without
38*4882a593Smuzhiyun  * modification, are permitted provided that the following conditions
39*4882a593Smuzhiyun  * are met:
40*4882a593Smuzhiyun  *
41*4882a593Smuzhiyun  *  * Redistributions of source code must retain the above copyright
42*4882a593Smuzhiyun  *    notice, this list of conditions and the following disclaimer.
43*4882a593Smuzhiyun  *  * Redistributions in binary form must reproduce the above copyright
44*4882a593Smuzhiyun  *    notice, this list of conditions and the following disclaimer in
45*4882a593Smuzhiyun  *    the documentation and/or other materials provided with the
46*4882a593Smuzhiyun  *    distribution.
47*4882a593Smuzhiyun  *  * Neither the name Intel Corporation nor the names of its
48*4882a593Smuzhiyun  *    contributors may be used to endorse or promote products derived
49*4882a593Smuzhiyun  *    from this software without specific prior written permission.
50*4882a593Smuzhiyun  *
51*4882a593Smuzhiyun  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
52*4882a593Smuzhiyun  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
53*4882a593Smuzhiyun  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
54*4882a593Smuzhiyun  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
55*4882a593Smuzhiyun  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
56*4882a593Smuzhiyun  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
57*4882a593Smuzhiyun  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
58*4882a593Smuzhiyun  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
59*4882a593Smuzhiyun  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
60*4882a593Smuzhiyun  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
61*4882a593Smuzhiyun  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62*4882a593Smuzhiyun  *
63*4882a593Smuzhiyun  *****************************************************************************/
64*4882a593Smuzhiyun 
65*4882a593Smuzhiyun #include <linux/sort.h>
66*4882a593Smuzhiyun 
67*4882a593Smuzhiyun #include "mvm.h"
68*4882a593Smuzhiyun 
69*4882a593Smuzhiyun #define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT	HZ
70*4882a593Smuzhiyun 
iwl_mvm_enter_ctkill(struct iwl_mvm * mvm)71*4882a593Smuzhiyun void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm)
72*4882a593Smuzhiyun {
73*4882a593Smuzhiyun 	struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
74*4882a593Smuzhiyun 	u32 duration = tt->params.ct_kill_duration;
75*4882a593Smuzhiyun 
76*4882a593Smuzhiyun 	if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
77*4882a593Smuzhiyun 		return;
78*4882a593Smuzhiyun 
79*4882a593Smuzhiyun 	IWL_ERR(mvm, "Enter CT Kill\n");
80*4882a593Smuzhiyun 	iwl_mvm_set_hw_ctkill_state(mvm, true);
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun 	if (!iwl_mvm_is_tt_in_fw(mvm)) {
83*4882a593Smuzhiyun 		tt->throttle = false;
84*4882a593Smuzhiyun 		tt->dynamic_smps = false;
85*4882a593Smuzhiyun 	}
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	/* Don't schedule an exit work if we're in test mode, since
88*4882a593Smuzhiyun 	 * the temperature will not change unless we manually set it
89*4882a593Smuzhiyun 	 * again (or disable testing).
90*4882a593Smuzhiyun 	 */
91*4882a593Smuzhiyun 	if (!mvm->temperature_test)
92*4882a593Smuzhiyun 		schedule_delayed_work(&tt->ct_kill_exit,
93*4882a593Smuzhiyun 				      round_jiffies_relative(duration * HZ));
94*4882a593Smuzhiyun }
95*4882a593Smuzhiyun 
iwl_mvm_exit_ctkill(struct iwl_mvm * mvm)96*4882a593Smuzhiyun static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm)
97*4882a593Smuzhiyun {
98*4882a593Smuzhiyun 	if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
99*4882a593Smuzhiyun 		return;
100*4882a593Smuzhiyun 
101*4882a593Smuzhiyun 	IWL_ERR(mvm, "Exit CT Kill\n");
102*4882a593Smuzhiyun 	iwl_mvm_set_hw_ctkill_state(mvm, false);
103*4882a593Smuzhiyun }
104*4882a593Smuzhiyun 
iwl_mvm_tt_temp_changed(struct iwl_mvm * mvm,u32 temp)105*4882a593Smuzhiyun void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp)
106*4882a593Smuzhiyun {
107*4882a593Smuzhiyun 	/* ignore the notification if we are in test mode */
108*4882a593Smuzhiyun 	if (mvm->temperature_test)
109*4882a593Smuzhiyun 		return;
110*4882a593Smuzhiyun 
111*4882a593Smuzhiyun 	if (mvm->temperature == temp)
112*4882a593Smuzhiyun 		return;
113*4882a593Smuzhiyun 
114*4882a593Smuzhiyun 	mvm->temperature = temp;
115*4882a593Smuzhiyun 	iwl_mvm_tt_handler(mvm);
116*4882a593Smuzhiyun }
117*4882a593Smuzhiyun 
iwl_mvm_temp_notif_parse(struct iwl_mvm * mvm,struct iwl_rx_packet * pkt)118*4882a593Smuzhiyun static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm,
119*4882a593Smuzhiyun 				    struct iwl_rx_packet *pkt)
120*4882a593Smuzhiyun {
121*4882a593Smuzhiyun 	struct iwl_dts_measurement_notif_v1 *notif_v1;
122*4882a593Smuzhiyun 	int len = iwl_rx_packet_payload_len(pkt);
123*4882a593Smuzhiyun 	int temp;
124*4882a593Smuzhiyun 
125*4882a593Smuzhiyun 	/* we can use notif_v1 only, because v2 only adds an additional
126*4882a593Smuzhiyun 	 * parameter, which is not used in this function.
127*4882a593Smuzhiyun 	*/
128*4882a593Smuzhiyun 	if (WARN_ON_ONCE(len < sizeof(*notif_v1))) {
129*4882a593Smuzhiyun 		IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
130*4882a593Smuzhiyun 		return -EINVAL;
131*4882a593Smuzhiyun 	}
132*4882a593Smuzhiyun 
133*4882a593Smuzhiyun 	notif_v1 = (void *)pkt->data;
134*4882a593Smuzhiyun 
135*4882a593Smuzhiyun 	temp = le32_to_cpu(notif_v1->temp);
136*4882a593Smuzhiyun 
137*4882a593Smuzhiyun 	/* shouldn't be negative, but since it's s32, make sure it isn't */
138*4882a593Smuzhiyun 	if (WARN_ON_ONCE(temp < 0))
139*4882a593Smuzhiyun 		temp = 0;
140*4882a593Smuzhiyun 
141*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp);
142*4882a593Smuzhiyun 
143*4882a593Smuzhiyun 	return temp;
144*4882a593Smuzhiyun }
145*4882a593Smuzhiyun 
iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data * notif_wait,struct iwl_rx_packet * pkt,void * data)146*4882a593Smuzhiyun static bool iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data *notif_wait,
147*4882a593Smuzhiyun 				    struct iwl_rx_packet *pkt, void *data)
148*4882a593Smuzhiyun {
149*4882a593Smuzhiyun 	struct iwl_mvm *mvm =
150*4882a593Smuzhiyun 		container_of(notif_wait, struct iwl_mvm, notif_wait);
151*4882a593Smuzhiyun 	int *temp = data;
152*4882a593Smuzhiyun 	int ret;
153*4882a593Smuzhiyun 
154*4882a593Smuzhiyun 	ret = iwl_mvm_temp_notif_parse(mvm, pkt);
155*4882a593Smuzhiyun 	if (ret < 0)
156*4882a593Smuzhiyun 		return true;
157*4882a593Smuzhiyun 
158*4882a593Smuzhiyun 	*temp = ret;
159*4882a593Smuzhiyun 
160*4882a593Smuzhiyun 	return true;
161*4882a593Smuzhiyun }
162*4882a593Smuzhiyun 
iwl_mvm_temp_notif(struct iwl_mvm * mvm,struct iwl_rx_cmd_buffer * rxb)163*4882a593Smuzhiyun void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
164*4882a593Smuzhiyun {
165*4882a593Smuzhiyun 	struct iwl_rx_packet *pkt = rxb_addr(rxb);
166*4882a593Smuzhiyun 	struct iwl_dts_measurement_notif_v2 *notif_v2;
167*4882a593Smuzhiyun 	int len = iwl_rx_packet_payload_len(pkt);
168*4882a593Smuzhiyun 	int temp;
169*4882a593Smuzhiyun 	u32 ths_crossed;
170*4882a593Smuzhiyun 
171*4882a593Smuzhiyun 	/* the notification is handled synchronously in ctkill, so skip here */
172*4882a593Smuzhiyun 	if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
173*4882a593Smuzhiyun 		return;
174*4882a593Smuzhiyun 
175*4882a593Smuzhiyun 	temp = iwl_mvm_temp_notif_parse(mvm, pkt);
176*4882a593Smuzhiyun 
177*4882a593Smuzhiyun 	if (!iwl_mvm_is_tt_in_fw(mvm)) {
178*4882a593Smuzhiyun 		if (temp >= 0)
179*4882a593Smuzhiyun 			iwl_mvm_tt_temp_changed(mvm, temp);
180*4882a593Smuzhiyun 		return;
181*4882a593Smuzhiyun 	}
182*4882a593Smuzhiyun 
183*4882a593Smuzhiyun 	if (WARN_ON_ONCE(len < sizeof(*notif_v2))) {
184*4882a593Smuzhiyun 		IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
185*4882a593Smuzhiyun 		return;
186*4882a593Smuzhiyun 	}
187*4882a593Smuzhiyun 
188*4882a593Smuzhiyun 	notif_v2 = (void *)pkt->data;
189*4882a593Smuzhiyun 	ths_crossed = le32_to_cpu(notif_v2->threshold_idx);
190*4882a593Smuzhiyun 
191*4882a593Smuzhiyun 	/* 0xFF in ths_crossed means the notification is not related
192*4882a593Smuzhiyun 	 * to a trip, so we can ignore it here.
193*4882a593Smuzhiyun 	 */
194*4882a593Smuzhiyun 	if (ths_crossed == 0xFF)
195*4882a593Smuzhiyun 		return;
196*4882a593Smuzhiyun 
197*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n",
198*4882a593Smuzhiyun 		       temp, ths_crossed);
199*4882a593Smuzhiyun 
200*4882a593Smuzhiyun #ifdef CONFIG_THERMAL
201*4882a593Smuzhiyun 	if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS))
202*4882a593Smuzhiyun 		return;
203*4882a593Smuzhiyun 
204*4882a593Smuzhiyun 	if (mvm->tz_device.tzone) {
205*4882a593Smuzhiyun 		struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device;
206*4882a593Smuzhiyun 
207*4882a593Smuzhiyun 		thermal_notify_framework(tz_dev->tzone,
208*4882a593Smuzhiyun 					 tz_dev->fw_trips_index[ths_crossed]);
209*4882a593Smuzhiyun 	}
210*4882a593Smuzhiyun #endif /* CONFIG_THERMAL */
211*4882a593Smuzhiyun }
212*4882a593Smuzhiyun 
iwl_mvm_ct_kill_notif(struct iwl_mvm * mvm,struct iwl_rx_cmd_buffer * rxb)213*4882a593Smuzhiyun void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
214*4882a593Smuzhiyun {
215*4882a593Smuzhiyun 	struct iwl_rx_packet *pkt = rxb_addr(rxb);
216*4882a593Smuzhiyun 	struct ct_kill_notif *notif;
217*4882a593Smuzhiyun 	int len = iwl_rx_packet_payload_len(pkt);
218*4882a593Smuzhiyun 
219*4882a593Smuzhiyun 	if (WARN_ON_ONCE(len != sizeof(*notif))) {
220*4882a593Smuzhiyun 		IWL_ERR(mvm, "Invalid CT_KILL_NOTIFICATION\n");
221*4882a593Smuzhiyun 		return;
222*4882a593Smuzhiyun 	}
223*4882a593Smuzhiyun 
224*4882a593Smuzhiyun 	notif = (struct ct_kill_notif *)pkt->data;
225*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n",
226*4882a593Smuzhiyun 		       notif->temperature);
227*4882a593Smuzhiyun 
228*4882a593Smuzhiyun 	iwl_mvm_enter_ctkill(mvm);
229*4882a593Smuzhiyun }
230*4882a593Smuzhiyun 
231*4882a593Smuzhiyun /*
232*4882a593Smuzhiyun  * send the DTS_MEASUREMENT_TRIGGER command with or without waiting for a
233*4882a593Smuzhiyun  * response. If we get a response then the measurement is stored in 'temp'
234*4882a593Smuzhiyun  */
iwl_mvm_send_temp_cmd(struct iwl_mvm * mvm,bool response,s32 * temp)235*4882a593Smuzhiyun static int iwl_mvm_send_temp_cmd(struct iwl_mvm *mvm, bool response, s32 *temp)
236*4882a593Smuzhiyun {
237*4882a593Smuzhiyun 	struct iwl_host_cmd cmd = {};
238*4882a593Smuzhiyun 	struct iwl_dts_measurement_cmd dts_cmd = {
239*4882a593Smuzhiyun 		.flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP),
240*4882a593Smuzhiyun 	};
241*4882a593Smuzhiyun 	struct iwl_ext_dts_measurement_cmd ext_cmd = {
242*4882a593Smuzhiyun 		.control_mode = cpu_to_le32(DTS_DIRECT_WITHOUT_MEASURE),
243*4882a593Smuzhiyun 	};
244*4882a593Smuzhiyun 	struct iwl_dts_measurement_resp *resp;
245*4882a593Smuzhiyun 	void *cmd_ptr;
246*4882a593Smuzhiyun 	int ret;
247*4882a593Smuzhiyun 	u32 cmd_flags = 0;
248*4882a593Smuzhiyun 	u16 len;
249*4882a593Smuzhiyun 
250*4882a593Smuzhiyun 	/* Check which command format is used (regular/extended) */
251*4882a593Smuzhiyun 	if (fw_has_capa(&mvm->fw->ucode_capa,
252*4882a593Smuzhiyun 			IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) {
253*4882a593Smuzhiyun 		len = sizeof(ext_cmd);
254*4882a593Smuzhiyun 		cmd_ptr = &ext_cmd;
255*4882a593Smuzhiyun 	} else {
256*4882a593Smuzhiyun 		len = sizeof(dts_cmd);
257*4882a593Smuzhiyun 		cmd_ptr = &dts_cmd;
258*4882a593Smuzhiyun 	}
259*4882a593Smuzhiyun 	/* The command version where we get a response is zero length */
260*4882a593Smuzhiyun 	if (response) {
261*4882a593Smuzhiyun 		cmd_flags = CMD_WANT_SKB;
262*4882a593Smuzhiyun 		len = 0;
263*4882a593Smuzhiyun 	}
264*4882a593Smuzhiyun 
265*4882a593Smuzhiyun 	cmd.id =  WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE);
266*4882a593Smuzhiyun 	cmd.len[0] = len;
267*4882a593Smuzhiyun 	cmd.flags = cmd_flags;
268*4882a593Smuzhiyun 	cmd.data[0] = cmd_ptr;
269*4882a593Smuzhiyun 
270*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm,
271*4882a593Smuzhiyun 		       "Sending temperature measurement command - %s response\n",
272*4882a593Smuzhiyun 		       response ? "with" : "without");
273*4882a593Smuzhiyun 	ret = iwl_mvm_send_cmd(mvm, &cmd);
274*4882a593Smuzhiyun 
275*4882a593Smuzhiyun 	if (ret) {
276*4882a593Smuzhiyun 		IWL_ERR(mvm,
277*4882a593Smuzhiyun 			"Failed to send the temperature measurement command (err=%d)\n",
278*4882a593Smuzhiyun 			ret);
279*4882a593Smuzhiyun 		return ret;
280*4882a593Smuzhiyun 	}
281*4882a593Smuzhiyun 
282*4882a593Smuzhiyun 	if (response) {
283*4882a593Smuzhiyun 		resp = (void *)cmd.resp_pkt->data;
284*4882a593Smuzhiyun 		*temp = le32_to_cpu(resp->temp);
285*4882a593Smuzhiyun 		IWL_DEBUG_TEMP(mvm,
286*4882a593Smuzhiyun 			       "Got temperature measurement response: temp=%d\n",
287*4882a593Smuzhiyun 			       *temp);
288*4882a593Smuzhiyun 		iwl_free_resp(&cmd);
289*4882a593Smuzhiyun 	}
290*4882a593Smuzhiyun 
291*4882a593Smuzhiyun 	return ret;
292*4882a593Smuzhiyun }
293*4882a593Smuzhiyun 
iwl_mvm_get_temp(struct iwl_mvm * mvm,s32 * temp)294*4882a593Smuzhiyun int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp)
295*4882a593Smuzhiyun {
296*4882a593Smuzhiyun 	struct iwl_notification_wait wait_temp_notif;
297*4882a593Smuzhiyun 	static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP,
298*4882a593Smuzhiyun 					    DTS_MEASUREMENT_NOTIF_WIDE) };
299*4882a593Smuzhiyun 	int ret;
300*4882a593Smuzhiyun 	u8 cmd_ver;
301*4882a593Smuzhiyun 
302*4882a593Smuzhiyun 	/*
303*4882a593Smuzhiyun 	 * If command version is 1 we send the command and immediately get
304*4882a593Smuzhiyun 	 * a response. For older versions we send the command and wait for a
305*4882a593Smuzhiyun 	 * notification (no command TLV for previous versions).
306*4882a593Smuzhiyun 	 */
307*4882a593Smuzhiyun 	cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_OPS_GROUP,
308*4882a593Smuzhiyun 					CMD_DTS_MEASUREMENT_TRIGGER_WIDE,
309*4882a593Smuzhiyun 					IWL_FW_CMD_VER_UNKNOWN);
310*4882a593Smuzhiyun 	if (cmd_ver == 1)
311*4882a593Smuzhiyun 		return iwl_mvm_send_temp_cmd(mvm, true, temp);
312*4882a593Smuzhiyun 
313*4882a593Smuzhiyun 	lockdep_assert_held(&mvm->mutex);
314*4882a593Smuzhiyun 
315*4882a593Smuzhiyun 	iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif,
316*4882a593Smuzhiyun 				   temp_notif, ARRAY_SIZE(temp_notif),
317*4882a593Smuzhiyun 				   iwl_mvm_temp_notif_wait, temp);
318*4882a593Smuzhiyun 
319*4882a593Smuzhiyun 	ret = iwl_mvm_send_temp_cmd(mvm, false, temp);
320*4882a593Smuzhiyun 	if (ret) {
321*4882a593Smuzhiyun 		iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif);
322*4882a593Smuzhiyun 		return ret;
323*4882a593Smuzhiyun 	}
324*4882a593Smuzhiyun 
325*4882a593Smuzhiyun 	ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif,
326*4882a593Smuzhiyun 				    IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT);
327*4882a593Smuzhiyun 	if (ret)
328*4882a593Smuzhiyun 		IWL_ERR(mvm, "Getting the temperature timed out\n");
329*4882a593Smuzhiyun 
330*4882a593Smuzhiyun 	return ret;
331*4882a593Smuzhiyun }
332*4882a593Smuzhiyun 
check_exit_ctkill(struct work_struct * work)333*4882a593Smuzhiyun static void check_exit_ctkill(struct work_struct *work)
334*4882a593Smuzhiyun {
335*4882a593Smuzhiyun 	struct iwl_mvm_tt_mgmt *tt;
336*4882a593Smuzhiyun 	struct iwl_mvm *mvm;
337*4882a593Smuzhiyun 	u32 duration;
338*4882a593Smuzhiyun 	s32 temp;
339*4882a593Smuzhiyun 	int ret;
340*4882a593Smuzhiyun 
341*4882a593Smuzhiyun 	tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work);
342*4882a593Smuzhiyun 	mvm = container_of(tt, struct iwl_mvm, thermal_throttle);
343*4882a593Smuzhiyun 
344*4882a593Smuzhiyun 	if (iwl_mvm_is_tt_in_fw(mvm)) {
345*4882a593Smuzhiyun 		iwl_mvm_exit_ctkill(mvm);
346*4882a593Smuzhiyun 
347*4882a593Smuzhiyun 		return;
348*4882a593Smuzhiyun 	}
349*4882a593Smuzhiyun 
350*4882a593Smuzhiyun 	duration = tt->params.ct_kill_duration;
351*4882a593Smuzhiyun 
352*4882a593Smuzhiyun 	flush_work(&mvm->roc_done_wk);
353*4882a593Smuzhiyun 
354*4882a593Smuzhiyun 	mutex_lock(&mvm->mutex);
355*4882a593Smuzhiyun 
356*4882a593Smuzhiyun 	if (__iwl_mvm_mac_start(mvm))
357*4882a593Smuzhiyun 		goto reschedule;
358*4882a593Smuzhiyun 
359*4882a593Smuzhiyun 	ret = iwl_mvm_get_temp(mvm, &temp);
360*4882a593Smuzhiyun 
361*4882a593Smuzhiyun 	__iwl_mvm_mac_stop(mvm);
362*4882a593Smuzhiyun 
363*4882a593Smuzhiyun 	if (ret)
364*4882a593Smuzhiyun 		goto reschedule;
365*4882a593Smuzhiyun 
366*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp);
367*4882a593Smuzhiyun 
368*4882a593Smuzhiyun 	if (temp <= tt->params.ct_kill_exit) {
369*4882a593Smuzhiyun 		mutex_unlock(&mvm->mutex);
370*4882a593Smuzhiyun 		iwl_mvm_exit_ctkill(mvm);
371*4882a593Smuzhiyun 		return;
372*4882a593Smuzhiyun 	}
373*4882a593Smuzhiyun 
374*4882a593Smuzhiyun reschedule:
375*4882a593Smuzhiyun 	mutex_unlock(&mvm->mutex);
376*4882a593Smuzhiyun 	schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit,
377*4882a593Smuzhiyun 			      round_jiffies(duration * HZ));
378*4882a593Smuzhiyun }
379*4882a593Smuzhiyun 
iwl_mvm_tt_smps_iterator(void * _data,u8 * mac,struct ieee80211_vif * vif)380*4882a593Smuzhiyun static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac,
381*4882a593Smuzhiyun 				     struct ieee80211_vif *vif)
382*4882a593Smuzhiyun {
383*4882a593Smuzhiyun 	struct iwl_mvm *mvm = _data;
384*4882a593Smuzhiyun 	enum ieee80211_smps_mode smps_mode;
385*4882a593Smuzhiyun 
386*4882a593Smuzhiyun 	lockdep_assert_held(&mvm->mutex);
387*4882a593Smuzhiyun 
388*4882a593Smuzhiyun 	if (mvm->thermal_throttle.dynamic_smps)
389*4882a593Smuzhiyun 		smps_mode = IEEE80211_SMPS_DYNAMIC;
390*4882a593Smuzhiyun 	else
391*4882a593Smuzhiyun 		smps_mode = IEEE80211_SMPS_AUTOMATIC;
392*4882a593Smuzhiyun 
393*4882a593Smuzhiyun 	if (vif->type != NL80211_IFTYPE_STATION)
394*4882a593Smuzhiyun 		return;
395*4882a593Smuzhiyun 
396*4882a593Smuzhiyun 	iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode);
397*4882a593Smuzhiyun }
398*4882a593Smuzhiyun 
iwl_mvm_tt_tx_protection(struct iwl_mvm * mvm,bool enable)399*4882a593Smuzhiyun static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable)
400*4882a593Smuzhiyun {
401*4882a593Smuzhiyun 	struct iwl_mvm_sta *mvmsta;
402*4882a593Smuzhiyun 	int i, err;
403*4882a593Smuzhiyun 
404*4882a593Smuzhiyun 	for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) {
405*4882a593Smuzhiyun 		mvmsta = iwl_mvm_sta_from_staid_protected(mvm, i);
406*4882a593Smuzhiyun 		if (!mvmsta)
407*4882a593Smuzhiyun 			continue;
408*4882a593Smuzhiyun 
409*4882a593Smuzhiyun 		if (enable == mvmsta->tt_tx_protection)
410*4882a593Smuzhiyun 			continue;
411*4882a593Smuzhiyun 		err = iwl_mvm_tx_protection(mvm, mvmsta, enable);
412*4882a593Smuzhiyun 		if (err) {
413*4882a593Smuzhiyun 			IWL_ERR(mvm, "Failed to %s Tx protection\n",
414*4882a593Smuzhiyun 				enable ? "enable" : "disable");
415*4882a593Smuzhiyun 		} else {
416*4882a593Smuzhiyun 			IWL_DEBUG_TEMP(mvm, "%s Tx protection\n",
417*4882a593Smuzhiyun 				       enable ? "Enable" : "Disable");
418*4882a593Smuzhiyun 			mvmsta->tt_tx_protection = enable;
419*4882a593Smuzhiyun 		}
420*4882a593Smuzhiyun 	}
421*4882a593Smuzhiyun }
422*4882a593Smuzhiyun 
iwl_mvm_tt_tx_backoff(struct iwl_mvm * mvm,u32 backoff)423*4882a593Smuzhiyun void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff)
424*4882a593Smuzhiyun {
425*4882a593Smuzhiyun 	struct iwl_host_cmd cmd = {
426*4882a593Smuzhiyun 		.id = REPLY_THERMAL_MNG_BACKOFF,
427*4882a593Smuzhiyun 		.len = { sizeof(u32), },
428*4882a593Smuzhiyun 		.data = { &backoff, },
429*4882a593Smuzhiyun 	};
430*4882a593Smuzhiyun 
431*4882a593Smuzhiyun 	backoff = max(backoff, mvm->thermal_throttle.min_backoff);
432*4882a593Smuzhiyun 
433*4882a593Smuzhiyun 	if (iwl_mvm_send_cmd(mvm, &cmd) == 0) {
434*4882a593Smuzhiyun 		IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n",
435*4882a593Smuzhiyun 			       backoff);
436*4882a593Smuzhiyun 		mvm->thermal_throttle.tx_backoff = backoff;
437*4882a593Smuzhiyun 	} else {
438*4882a593Smuzhiyun 		IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n");
439*4882a593Smuzhiyun 	}
440*4882a593Smuzhiyun }
441*4882a593Smuzhiyun 
iwl_mvm_tt_handler(struct iwl_mvm * mvm)442*4882a593Smuzhiyun void iwl_mvm_tt_handler(struct iwl_mvm *mvm)
443*4882a593Smuzhiyun {
444*4882a593Smuzhiyun 	struct iwl_tt_params *params = &mvm->thermal_throttle.params;
445*4882a593Smuzhiyun 	struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
446*4882a593Smuzhiyun 	s32 temperature = mvm->temperature;
447*4882a593Smuzhiyun 	bool throttle_enable = false;
448*4882a593Smuzhiyun 	int i;
449*4882a593Smuzhiyun 	u32 tx_backoff;
450*4882a593Smuzhiyun 
451*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature);
452*4882a593Smuzhiyun 
453*4882a593Smuzhiyun 	if (params->support_ct_kill && temperature >= params->ct_kill_entry) {
454*4882a593Smuzhiyun 		iwl_mvm_enter_ctkill(mvm);
455*4882a593Smuzhiyun 		return;
456*4882a593Smuzhiyun 	}
457*4882a593Smuzhiyun 
458*4882a593Smuzhiyun 	if (params->support_ct_kill &&
459*4882a593Smuzhiyun 	    temperature <= params->ct_kill_exit) {
460*4882a593Smuzhiyun 		iwl_mvm_exit_ctkill(mvm);
461*4882a593Smuzhiyun 		return;
462*4882a593Smuzhiyun 	}
463*4882a593Smuzhiyun 
464*4882a593Smuzhiyun 	if (params->support_dynamic_smps) {
465*4882a593Smuzhiyun 		if (!tt->dynamic_smps &&
466*4882a593Smuzhiyun 		    temperature >= params->dynamic_smps_entry) {
467*4882a593Smuzhiyun 			IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n");
468*4882a593Smuzhiyun 			tt->dynamic_smps = true;
469*4882a593Smuzhiyun 			ieee80211_iterate_active_interfaces_atomic(
470*4882a593Smuzhiyun 					mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
471*4882a593Smuzhiyun 					iwl_mvm_tt_smps_iterator, mvm);
472*4882a593Smuzhiyun 			throttle_enable = true;
473*4882a593Smuzhiyun 		} else if (tt->dynamic_smps &&
474*4882a593Smuzhiyun 			   temperature <= params->dynamic_smps_exit) {
475*4882a593Smuzhiyun 			IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n");
476*4882a593Smuzhiyun 			tt->dynamic_smps = false;
477*4882a593Smuzhiyun 			ieee80211_iterate_active_interfaces_atomic(
478*4882a593Smuzhiyun 					mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
479*4882a593Smuzhiyun 					iwl_mvm_tt_smps_iterator, mvm);
480*4882a593Smuzhiyun 		}
481*4882a593Smuzhiyun 	}
482*4882a593Smuzhiyun 
483*4882a593Smuzhiyun 	if (params->support_tx_protection) {
484*4882a593Smuzhiyun 		if (temperature >= params->tx_protection_entry) {
485*4882a593Smuzhiyun 			iwl_mvm_tt_tx_protection(mvm, true);
486*4882a593Smuzhiyun 			throttle_enable = true;
487*4882a593Smuzhiyun 		} else if (temperature <= params->tx_protection_exit) {
488*4882a593Smuzhiyun 			iwl_mvm_tt_tx_protection(mvm, false);
489*4882a593Smuzhiyun 		}
490*4882a593Smuzhiyun 	}
491*4882a593Smuzhiyun 
492*4882a593Smuzhiyun 	if (params->support_tx_backoff) {
493*4882a593Smuzhiyun 		tx_backoff = tt->min_backoff;
494*4882a593Smuzhiyun 		for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) {
495*4882a593Smuzhiyun 			if (temperature < params->tx_backoff[i].temperature)
496*4882a593Smuzhiyun 				break;
497*4882a593Smuzhiyun 			tx_backoff = max(tt->min_backoff,
498*4882a593Smuzhiyun 					 params->tx_backoff[i].backoff);
499*4882a593Smuzhiyun 		}
500*4882a593Smuzhiyun 		if (tx_backoff != tt->min_backoff)
501*4882a593Smuzhiyun 			throttle_enable = true;
502*4882a593Smuzhiyun 		if (tt->tx_backoff != tx_backoff)
503*4882a593Smuzhiyun 			iwl_mvm_tt_tx_backoff(mvm, tx_backoff);
504*4882a593Smuzhiyun 	}
505*4882a593Smuzhiyun 
506*4882a593Smuzhiyun 	if (!tt->throttle && throttle_enable) {
507*4882a593Smuzhiyun 		IWL_WARN(mvm,
508*4882a593Smuzhiyun 			 "Due to high temperature thermal throttling initiated\n");
509*4882a593Smuzhiyun 		tt->throttle = true;
510*4882a593Smuzhiyun 	} else if (tt->throttle && !tt->dynamic_smps &&
511*4882a593Smuzhiyun 		   tt->tx_backoff == tt->min_backoff &&
512*4882a593Smuzhiyun 		   temperature <= params->tx_protection_exit) {
513*4882a593Smuzhiyun 		IWL_WARN(mvm,
514*4882a593Smuzhiyun 			 "Temperature is back to normal thermal throttling stopped\n");
515*4882a593Smuzhiyun 		tt->throttle = false;
516*4882a593Smuzhiyun 	}
517*4882a593Smuzhiyun }
518*4882a593Smuzhiyun 
519*4882a593Smuzhiyun static const struct iwl_tt_params iwl_mvm_default_tt_params = {
520*4882a593Smuzhiyun 	.ct_kill_entry = 118,
521*4882a593Smuzhiyun 	.ct_kill_exit = 96,
522*4882a593Smuzhiyun 	.ct_kill_duration = 5,
523*4882a593Smuzhiyun 	.dynamic_smps_entry = 114,
524*4882a593Smuzhiyun 	.dynamic_smps_exit = 110,
525*4882a593Smuzhiyun 	.tx_protection_entry = 114,
526*4882a593Smuzhiyun 	.tx_protection_exit = 108,
527*4882a593Smuzhiyun 	.tx_backoff = {
528*4882a593Smuzhiyun 		{.temperature = 112, .backoff = 200},
529*4882a593Smuzhiyun 		{.temperature = 113, .backoff = 600},
530*4882a593Smuzhiyun 		{.temperature = 114, .backoff = 1200},
531*4882a593Smuzhiyun 		{.temperature = 115, .backoff = 2000},
532*4882a593Smuzhiyun 		{.temperature = 116, .backoff = 4000},
533*4882a593Smuzhiyun 		{.temperature = 117, .backoff = 10000},
534*4882a593Smuzhiyun 	},
535*4882a593Smuzhiyun 	.support_ct_kill = true,
536*4882a593Smuzhiyun 	.support_dynamic_smps = true,
537*4882a593Smuzhiyun 	.support_tx_protection = true,
538*4882a593Smuzhiyun 	.support_tx_backoff = true,
539*4882a593Smuzhiyun };
540*4882a593Smuzhiyun 
541*4882a593Smuzhiyun /* budget in mWatt */
542*4882a593Smuzhiyun static const u32 iwl_mvm_cdev_budgets[] = {
543*4882a593Smuzhiyun 	2400,	/* cooling state 0 */
544*4882a593Smuzhiyun 	2000,	/* cooling state 1 */
545*4882a593Smuzhiyun 	1800,	/* cooling state 2 */
546*4882a593Smuzhiyun 	1600,	/* cooling state 3 */
547*4882a593Smuzhiyun 	1400,	/* cooling state 4 */
548*4882a593Smuzhiyun 	1200,	/* cooling state 5 */
549*4882a593Smuzhiyun 	1000,	/* cooling state 6 */
550*4882a593Smuzhiyun 	900,	/* cooling state 7 */
551*4882a593Smuzhiyun 	800,	/* cooling state 8 */
552*4882a593Smuzhiyun 	700,	/* cooling state 9 */
553*4882a593Smuzhiyun 	650,	/* cooling state 10 */
554*4882a593Smuzhiyun 	600,	/* cooling state 11 */
555*4882a593Smuzhiyun 	550,	/* cooling state 12 */
556*4882a593Smuzhiyun 	500,	/* cooling state 13 */
557*4882a593Smuzhiyun 	450,	/* cooling state 14 */
558*4882a593Smuzhiyun 	400,	/* cooling state 15 */
559*4882a593Smuzhiyun 	350,	/* cooling state 16 */
560*4882a593Smuzhiyun 	300,	/* cooling state 17 */
561*4882a593Smuzhiyun 	250,	/* cooling state 18 */
562*4882a593Smuzhiyun 	200,	/* cooling state 19 */
563*4882a593Smuzhiyun 	150,	/* cooling state 20 */
564*4882a593Smuzhiyun };
565*4882a593Smuzhiyun 
iwl_mvm_ctdp_command(struct iwl_mvm * mvm,u32 op,u32 state)566*4882a593Smuzhiyun int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 state)
567*4882a593Smuzhiyun {
568*4882a593Smuzhiyun 	struct iwl_mvm_ctdp_cmd cmd = {
569*4882a593Smuzhiyun 		.operation = cpu_to_le32(op),
570*4882a593Smuzhiyun 		.budget = cpu_to_le32(iwl_mvm_cdev_budgets[state]),
571*4882a593Smuzhiyun 		.window_size = 0,
572*4882a593Smuzhiyun 	};
573*4882a593Smuzhiyun 	int ret;
574*4882a593Smuzhiyun 	u32 status;
575*4882a593Smuzhiyun 
576*4882a593Smuzhiyun 	lockdep_assert_held(&mvm->mutex);
577*4882a593Smuzhiyun 
578*4882a593Smuzhiyun 	status = 0;
579*4882a593Smuzhiyun 	ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP,
580*4882a593Smuzhiyun 						       CTDP_CONFIG_CMD),
581*4882a593Smuzhiyun 					  sizeof(cmd), &cmd, &status);
582*4882a593Smuzhiyun 
583*4882a593Smuzhiyun 	if (ret) {
584*4882a593Smuzhiyun 		IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret);
585*4882a593Smuzhiyun 		return ret;
586*4882a593Smuzhiyun 	}
587*4882a593Smuzhiyun 
588*4882a593Smuzhiyun 	switch (op) {
589*4882a593Smuzhiyun 	case CTDP_CMD_OPERATION_START:
590*4882a593Smuzhiyun #ifdef CONFIG_THERMAL
591*4882a593Smuzhiyun 		mvm->cooling_dev.cur_state = state;
592*4882a593Smuzhiyun #endif /* CONFIG_THERMAL */
593*4882a593Smuzhiyun 		break;
594*4882a593Smuzhiyun 	case CTDP_CMD_OPERATION_REPORT:
595*4882a593Smuzhiyun 		IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status);
596*4882a593Smuzhiyun 		/* when the function is called with CTDP_CMD_OPERATION_REPORT
597*4882a593Smuzhiyun 		 * option the function should return the average budget value
598*4882a593Smuzhiyun 		 * that is received from the FW.
599*4882a593Smuzhiyun 		 * The budget can't be less or equal to 0, so it's possible
600*4882a593Smuzhiyun 		 * to distinguish between error values and budgets.
601*4882a593Smuzhiyun 		 */
602*4882a593Smuzhiyun 		return status;
603*4882a593Smuzhiyun 	case CTDP_CMD_OPERATION_STOP:
604*4882a593Smuzhiyun 		IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n");
605*4882a593Smuzhiyun 		break;
606*4882a593Smuzhiyun 	}
607*4882a593Smuzhiyun 
608*4882a593Smuzhiyun 	return 0;
609*4882a593Smuzhiyun }
610*4882a593Smuzhiyun 
611*4882a593Smuzhiyun #ifdef CONFIG_THERMAL
compare_temps(const void * a,const void * b)612*4882a593Smuzhiyun static int compare_temps(const void *a, const void *b)
613*4882a593Smuzhiyun {
614*4882a593Smuzhiyun 	return ((s16)le16_to_cpu(*(__le16 *)a) -
615*4882a593Smuzhiyun 		(s16)le16_to_cpu(*(__le16 *)b));
616*4882a593Smuzhiyun }
617*4882a593Smuzhiyun #endif
618*4882a593Smuzhiyun 
iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm * mvm)619*4882a593Smuzhiyun int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm)
620*4882a593Smuzhiyun {
621*4882a593Smuzhiyun 	struct temp_report_ths_cmd cmd = {0};
622*4882a593Smuzhiyun 	int ret;
623*4882a593Smuzhiyun #ifdef CONFIG_THERMAL
624*4882a593Smuzhiyun 	int i, j, idx = 0;
625*4882a593Smuzhiyun 
626*4882a593Smuzhiyun 	lockdep_assert_held(&mvm->mutex);
627*4882a593Smuzhiyun 
628*4882a593Smuzhiyun 	if (!mvm->tz_device.tzone)
629*4882a593Smuzhiyun 		goto send;
630*4882a593Smuzhiyun 
631*4882a593Smuzhiyun 	/* The driver holds array of temperature trips that are unsorted
632*4882a593Smuzhiyun 	 * and uncompressed, the FW should get it compressed and sorted
633*4882a593Smuzhiyun 	 */
634*4882a593Smuzhiyun 
635*4882a593Smuzhiyun 	/* compress temp_trips to cmd array, remove uninitialized values*/
636*4882a593Smuzhiyun 	for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) {
637*4882a593Smuzhiyun 		if (mvm->tz_device.temp_trips[i] != S16_MIN) {
638*4882a593Smuzhiyun 			cmd.thresholds[idx++] =
639*4882a593Smuzhiyun 				cpu_to_le16(mvm->tz_device.temp_trips[i]);
640*4882a593Smuzhiyun 		}
641*4882a593Smuzhiyun 	}
642*4882a593Smuzhiyun 	cmd.num_temps = cpu_to_le32(idx);
643*4882a593Smuzhiyun 
644*4882a593Smuzhiyun 	if (!idx)
645*4882a593Smuzhiyun 		goto send;
646*4882a593Smuzhiyun 
647*4882a593Smuzhiyun 	/*sort cmd array*/
648*4882a593Smuzhiyun 	sort(cmd.thresholds, idx, sizeof(s16), compare_temps, NULL);
649*4882a593Smuzhiyun 
650*4882a593Smuzhiyun 	/* we should save the indexes of trips because we sort
651*4882a593Smuzhiyun 	 * and compress the orginal array
652*4882a593Smuzhiyun 	 */
653*4882a593Smuzhiyun 	for (i = 0; i < idx; i++) {
654*4882a593Smuzhiyun 		for (j = 0; j < IWL_MAX_DTS_TRIPS; j++) {
655*4882a593Smuzhiyun 			if (le16_to_cpu(cmd.thresholds[i]) ==
656*4882a593Smuzhiyun 				mvm->tz_device.temp_trips[j])
657*4882a593Smuzhiyun 				mvm->tz_device.fw_trips_index[i] = j;
658*4882a593Smuzhiyun 		}
659*4882a593Smuzhiyun 	}
660*4882a593Smuzhiyun 
661*4882a593Smuzhiyun send:
662*4882a593Smuzhiyun #endif
663*4882a593Smuzhiyun 	ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP,
664*4882a593Smuzhiyun 						TEMP_REPORTING_THRESHOLDS_CMD),
665*4882a593Smuzhiyun 				   0, sizeof(cmd), &cmd);
666*4882a593Smuzhiyun 	if (ret)
667*4882a593Smuzhiyun 		IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n",
668*4882a593Smuzhiyun 			ret);
669*4882a593Smuzhiyun 
670*4882a593Smuzhiyun 	return ret;
671*4882a593Smuzhiyun }
672*4882a593Smuzhiyun 
673*4882a593Smuzhiyun #ifdef CONFIG_THERMAL
iwl_mvm_tzone_get_temp(struct thermal_zone_device * device,int * temperature)674*4882a593Smuzhiyun static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device,
675*4882a593Smuzhiyun 				  int *temperature)
676*4882a593Smuzhiyun {
677*4882a593Smuzhiyun 	struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata;
678*4882a593Smuzhiyun 	int ret;
679*4882a593Smuzhiyun 	int temp;
680*4882a593Smuzhiyun 
681*4882a593Smuzhiyun 	mutex_lock(&mvm->mutex);
682*4882a593Smuzhiyun 
683*4882a593Smuzhiyun 	if (!iwl_mvm_firmware_running(mvm) ||
684*4882a593Smuzhiyun 	    mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
685*4882a593Smuzhiyun 		ret = -ENODATA;
686*4882a593Smuzhiyun 		goto out;
687*4882a593Smuzhiyun 	}
688*4882a593Smuzhiyun 
689*4882a593Smuzhiyun 	ret = iwl_mvm_get_temp(mvm, &temp);
690*4882a593Smuzhiyun 	if (ret)
691*4882a593Smuzhiyun 		goto out;
692*4882a593Smuzhiyun 
693*4882a593Smuzhiyun 	*temperature = temp * 1000;
694*4882a593Smuzhiyun 
695*4882a593Smuzhiyun out:
696*4882a593Smuzhiyun 	mutex_unlock(&mvm->mutex);
697*4882a593Smuzhiyun 	return ret;
698*4882a593Smuzhiyun }
699*4882a593Smuzhiyun 
iwl_mvm_tzone_get_trip_temp(struct thermal_zone_device * device,int trip,int * temp)700*4882a593Smuzhiyun static int iwl_mvm_tzone_get_trip_temp(struct thermal_zone_device *device,
701*4882a593Smuzhiyun 				       int trip, int *temp)
702*4882a593Smuzhiyun {
703*4882a593Smuzhiyun 	struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata;
704*4882a593Smuzhiyun 
705*4882a593Smuzhiyun 	if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS)
706*4882a593Smuzhiyun 		return -EINVAL;
707*4882a593Smuzhiyun 
708*4882a593Smuzhiyun 	*temp = mvm->tz_device.temp_trips[trip] * 1000;
709*4882a593Smuzhiyun 
710*4882a593Smuzhiyun 	return 0;
711*4882a593Smuzhiyun }
712*4882a593Smuzhiyun 
iwl_mvm_tzone_get_trip_type(struct thermal_zone_device * device,int trip,enum thermal_trip_type * type)713*4882a593Smuzhiyun static int iwl_mvm_tzone_get_trip_type(struct thermal_zone_device *device,
714*4882a593Smuzhiyun 				       int trip, enum thermal_trip_type *type)
715*4882a593Smuzhiyun {
716*4882a593Smuzhiyun 	if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS)
717*4882a593Smuzhiyun 		return -EINVAL;
718*4882a593Smuzhiyun 
719*4882a593Smuzhiyun 	*type = THERMAL_TRIP_PASSIVE;
720*4882a593Smuzhiyun 
721*4882a593Smuzhiyun 	return 0;
722*4882a593Smuzhiyun }
723*4882a593Smuzhiyun 
iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device * device,int trip,int temp)724*4882a593Smuzhiyun static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device,
725*4882a593Smuzhiyun 				       int trip, int temp)
726*4882a593Smuzhiyun {
727*4882a593Smuzhiyun 	struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata;
728*4882a593Smuzhiyun 	struct iwl_mvm_thermal_device *tzone;
729*4882a593Smuzhiyun 	int i, ret;
730*4882a593Smuzhiyun 	s16 temperature;
731*4882a593Smuzhiyun 
732*4882a593Smuzhiyun 	mutex_lock(&mvm->mutex);
733*4882a593Smuzhiyun 
734*4882a593Smuzhiyun 	if (!iwl_mvm_firmware_running(mvm) ||
735*4882a593Smuzhiyun 	    mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
736*4882a593Smuzhiyun 		ret = -EIO;
737*4882a593Smuzhiyun 		goto out;
738*4882a593Smuzhiyun 	}
739*4882a593Smuzhiyun 
740*4882a593Smuzhiyun 	if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) {
741*4882a593Smuzhiyun 		ret = -EINVAL;
742*4882a593Smuzhiyun 		goto out;
743*4882a593Smuzhiyun 	}
744*4882a593Smuzhiyun 
745*4882a593Smuzhiyun 	if ((temp / 1000) > S16_MAX) {
746*4882a593Smuzhiyun 		ret = -EINVAL;
747*4882a593Smuzhiyun 		goto out;
748*4882a593Smuzhiyun 	}
749*4882a593Smuzhiyun 
750*4882a593Smuzhiyun 	temperature = (s16)(temp / 1000);
751*4882a593Smuzhiyun 	tzone = &mvm->tz_device;
752*4882a593Smuzhiyun 
753*4882a593Smuzhiyun 	if (!tzone) {
754*4882a593Smuzhiyun 		ret = -EIO;
755*4882a593Smuzhiyun 		goto out;
756*4882a593Smuzhiyun 	}
757*4882a593Smuzhiyun 
758*4882a593Smuzhiyun 	/* no updates*/
759*4882a593Smuzhiyun 	if (tzone->temp_trips[trip] == temperature) {
760*4882a593Smuzhiyun 		ret = 0;
761*4882a593Smuzhiyun 		goto out;
762*4882a593Smuzhiyun 	}
763*4882a593Smuzhiyun 
764*4882a593Smuzhiyun 	/* already existing temperature */
765*4882a593Smuzhiyun 	for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) {
766*4882a593Smuzhiyun 		if (tzone->temp_trips[i] == temperature) {
767*4882a593Smuzhiyun 			ret = -EINVAL;
768*4882a593Smuzhiyun 			goto out;
769*4882a593Smuzhiyun 		}
770*4882a593Smuzhiyun 	}
771*4882a593Smuzhiyun 
772*4882a593Smuzhiyun 	tzone->temp_trips[trip] = temperature;
773*4882a593Smuzhiyun 
774*4882a593Smuzhiyun 	ret = iwl_mvm_send_temp_report_ths_cmd(mvm);
775*4882a593Smuzhiyun out:
776*4882a593Smuzhiyun 	mutex_unlock(&mvm->mutex);
777*4882a593Smuzhiyun 	return ret;
778*4882a593Smuzhiyun }
779*4882a593Smuzhiyun 
780*4882a593Smuzhiyun static  struct thermal_zone_device_ops tzone_ops = {
781*4882a593Smuzhiyun 	.get_temp = iwl_mvm_tzone_get_temp,
782*4882a593Smuzhiyun 	.get_trip_temp = iwl_mvm_tzone_get_trip_temp,
783*4882a593Smuzhiyun 	.get_trip_type = iwl_mvm_tzone_get_trip_type,
784*4882a593Smuzhiyun 	.set_trip_temp = iwl_mvm_tzone_set_trip_temp,
785*4882a593Smuzhiyun };
786*4882a593Smuzhiyun 
787*4882a593Smuzhiyun /* make all trips writable */
788*4882a593Smuzhiyun #define IWL_WRITABLE_TRIPS_MSK (BIT(IWL_MAX_DTS_TRIPS) - 1)
789*4882a593Smuzhiyun 
iwl_mvm_thermal_zone_register(struct iwl_mvm * mvm)790*4882a593Smuzhiyun static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm)
791*4882a593Smuzhiyun {
792*4882a593Smuzhiyun 	int i, ret;
793*4882a593Smuzhiyun 	char name[16];
794*4882a593Smuzhiyun 	static atomic_t counter = ATOMIC_INIT(0);
795*4882a593Smuzhiyun 
796*4882a593Smuzhiyun 	if (!iwl_mvm_is_tt_in_fw(mvm)) {
797*4882a593Smuzhiyun 		mvm->tz_device.tzone = NULL;
798*4882a593Smuzhiyun 
799*4882a593Smuzhiyun 		return;
800*4882a593Smuzhiyun 	}
801*4882a593Smuzhiyun 
802*4882a593Smuzhiyun 	BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
803*4882a593Smuzhiyun 
804*4882a593Smuzhiyun 	sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF);
805*4882a593Smuzhiyun 	mvm->tz_device.tzone = thermal_zone_device_register(name,
806*4882a593Smuzhiyun 							IWL_MAX_DTS_TRIPS,
807*4882a593Smuzhiyun 							IWL_WRITABLE_TRIPS_MSK,
808*4882a593Smuzhiyun 							mvm, &tzone_ops,
809*4882a593Smuzhiyun 							NULL, 0, 0);
810*4882a593Smuzhiyun 	if (IS_ERR(mvm->tz_device.tzone)) {
811*4882a593Smuzhiyun 		IWL_DEBUG_TEMP(mvm,
812*4882a593Smuzhiyun 			       "Failed to register to thermal zone (err = %ld)\n",
813*4882a593Smuzhiyun 			       PTR_ERR(mvm->tz_device.tzone));
814*4882a593Smuzhiyun 		mvm->tz_device.tzone = NULL;
815*4882a593Smuzhiyun 		return;
816*4882a593Smuzhiyun 	}
817*4882a593Smuzhiyun 
818*4882a593Smuzhiyun 	ret = thermal_zone_device_enable(mvm->tz_device.tzone);
819*4882a593Smuzhiyun 	if (ret) {
820*4882a593Smuzhiyun 		IWL_DEBUG_TEMP(mvm, "Failed to enable thermal zone\n");
821*4882a593Smuzhiyun 		thermal_zone_device_unregister(mvm->tz_device.tzone);
822*4882a593Smuzhiyun 		return;
823*4882a593Smuzhiyun 	}
824*4882a593Smuzhiyun 
825*4882a593Smuzhiyun 	/* 0 is a valid temperature,
826*4882a593Smuzhiyun 	 * so initialize the array with S16_MIN which invalid temperature
827*4882a593Smuzhiyun 	 */
828*4882a593Smuzhiyun 	for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++)
829*4882a593Smuzhiyun 		mvm->tz_device.temp_trips[i] = S16_MIN;
830*4882a593Smuzhiyun }
831*4882a593Smuzhiyun 
iwl_mvm_tcool_get_max_state(struct thermal_cooling_device * cdev,unsigned long * state)832*4882a593Smuzhiyun static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev,
833*4882a593Smuzhiyun 				       unsigned long *state)
834*4882a593Smuzhiyun {
835*4882a593Smuzhiyun 	*state = ARRAY_SIZE(iwl_mvm_cdev_budgets) - 1;
836*4882a593Smuzhiyun 
837*4882a593Smuzhiyun 	return 0;
838*4882a593Smuzhiyun }
839*4882a593Smuzhiyun 
iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device * cdev,unsigned long * state)840*4882a593Smuzhiyun static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev,
841*4882a593Smuzhiyun 				       unsigned long *state)
842*4882a593Smuzhiyun {
843*4882a593Smuzhiyun 	struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
844*4882a593Smuzhiyun 
845*4882a593Smuzhiyun 	*state = mvm->cooling_dev.cur_state;
846*4882a593Smuzhiyun 
847*4882a593Smuzhiyun 	return 0;
848*4882a593Smuzhiyun }
849*4882a593Smuzhiyun 
iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device * cdev,unsigned long new_state)850*4882a593Smuzhiyun static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev,
851*4882a593Smuzhiyun 				       unsigned long new_state)
852*4882a593Smuzhiyun {
853*4882a593Smuzhiyun 	struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
854*4882a593Smuzhiyun 	int ret;
855*4882a593Smuzhiyun 
856*4882a593Smuzhiyun 	mutex_lock(&mvm->mutex);
857*4882a593Smuzhiyun 
858*4882a593Smuzhiyun 	if (!iwl_mvm_firmware_running(mvm) ||
859*4882a593Smuzhiyun 	    mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
860*4882a593Smuzhiyun 		ret = -EIO;
861*4882a593Smuzhiyun 		goto unlock;
862*4882a593Smuzhiyun 	}
863*4882a593Smuzhiyun 
864*4882a593Smuzhiyun 	if (new_state >= ARRAY_SIZE(iwl_mvm_cdev_budgets)) {
865*4882a593Smuzhiyun 		ret = -EINVAL;
866*4882a593Smuzhiyun 		goto unlock;
867*4882a593Smuzhiyun 	}
868*4882a593Smuzhiyun 
869*4882a593Smuzhiyun 	ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START,
870*4882a593Smuzhiyun 				   new_state);
871*4882a593Smuzhiyun 
872*4882a593Smuzhiyun unlock:
873*4882a593Smuzhiyun 	mutex_unlock(&mvm->mutex);
874*4882a593Smuzhiyun 	return ret;
875*4882a593Smuzhiyun }
876*4882a593Smuzhiyun 
877*4882a593Smuzhiyun static const struct thermal_cooling_device_ops tcooling_ops = {
878*4882a593Smuzhiyun 	.get_max_state = iwl_mvm_tcool_get_max_state,
879*4882a593Smuzhiyun 	.get_cur_state = iwl_mvm_tcool_get_cur_state,
880*4882a593Smuzhiyun 	.set_cur_state = iwl_mvm_tcool_set_cur_state,
881*4882a593Smuzhiyun };
882*4882a593Smuzhiyun 
iwl_mvm_cooling_device_register(struct iwl_mvm * mvm)883*4882a593Smuzhiyun static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm)
884*4882a593Smuzhiyun {
885*4882a593Smuzhiyun 	char name[] = "iwlwifi";
886*4882a593Smuzhiyun 
887*4882a593Smuzhiyun 	if (!iwl_mvm_is_ctdp_supported(mvm))
888*4882a593Smuzhiyun 		return;
889*4882a593Smuzhiyun 
890*4882a593Smuzhiyun 	BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
891*4882a593Smuzhiyun 
892*4882a593Smuzhiyun 	mvm->cooling_dev.cdev =
893*4882a593Smuzhiyun 		thermal_cooling_device_register(name,
894*4882a593Smuzhiyun 						mvm,
895*4882a593Smuzhiyun 						&tcooling_ops);
896*4882a593Smuzhiyun 
897*4882a593Smuzhiyun 	if (IS_ERR(mvm->cooling_dev.cdev)) {
898*4882a593Smuzhiyun 		IWL_DEBUG_TEMP(mvm,
899*4882a593Smuzhiyun 			       "Failed to register to cooling device (err = %ld)\n",
900*4882a593Smuzhiyun 			       PTR_ERR(mvm->cooling_dev.cdev));
901*4882a593Smuzhiyun 		mvm->cooling_dev.cdev = NULL;
902*4882a593Smuzhiyun 		return;
903*4882a593Smuzhiyun 	}
904*4882a593Smuzhiyun }
905*4882a593Smuzhiyun 
iwl_mvm_thermal_zone_unregister(struct iwl_mvm * mvm)906*4882a593Smuzhiyun static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm)
907*4882a593Smuzhiyun {
908*4882a593Smuzhiyun 	if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone)
909*4882a593Smuzhiyun 		return;
910*4882a593Smuzhiyun 
911*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n");
912*4882a593Smuzhiyun 	if (mvm->tz_device.tzone) {
913*4882a593Smuzhiyun 		thermal_zone_device_unregister(mvm->tz_device.tzone);
914*4882a593Smuzhiyun 		mvm->tz_device.tzone = NULL;
915*4882a593Smuzhiyun 	}
916*4882a593Smuzhiyun }
917*4882a593Smuzhiyun 
iwl_mvm_cooling_device_unregister(struct iwl_mvm * mvm)918*4882a593Smuzhiyun static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm)
919*4882a593Smuzhiyun {
920*4882a593Smuzhiyun 	if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev)
921*4882a593Smuzhiyun 		return;
922*4882a593Smuzhiyun 
923*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n");
924*4882a593Smuzhiyun 	if (mvm->cooling_dev.cdev) {
925*4882a593Smuzhiyun 		thermal_cooling_device_unregister(mvm->cooling_dev.cdev);
926*4882a593Smuzhiyun 		mvm->cooling_dev.cdev = NULL;
927*4882a593Smuzhiyun 	}
928*4882a593Smuzhiyun }
929*4882a593Smuzhiyun #endif /* CONFIG_THERMAL */
930*4882a593Smuzhiyun 
iwl_mvm_thermal_initialize(struct iwl_mvm * mvm,u32 min_backoff)931*4882a593Smuzhiyun void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff)
932*4882a593Smuzhiyun {
933*4882a593Smuzhiyun 	struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
934*4882a593Smuzhiyun 
935*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n");
936*4882a593Smuzhiyun 
937*4882a593Smuzhiyun 	if (mvm->cfg->thermal_params)
938*4882a593Smuzhiyun 		tt->params = *mvm->cfg->thermal_params;
939*4882a593Smuzhiyun 	else
940*4882a593Smuzhiyun 		tt->params = iwl_mvm_default_tt_params;
941*4882a593Smuzhiyun 
942*4882a593Smuzhiyun 	tt->throttle = false;
943*4882a593Smuzhiyun 	tt->dynamic_smps = false;
944*4882a593Smuzhiyun 	tt->min_backoff = min_backoff;
945*4882a593Smuzhiyun 	INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill);
946*4882a593Smuzhiyun 
947*4882a593Smuzhiyun #ifdef CONFIG_THERMAL
948*4882a593Smuzhiyun 	iwl_mvm_cooling_device_register(mvm);
949*4882a593Smuzhiyun 	iwl_mvm_thermal_zone_register(mvm);
950*4882a593Smuzhiyun #endif
951*4882a593Smuzhiyun 	mvm->init_status |= IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
952*4882a593Smuzhiyun }
953*4882a593Smuzhiyun 
iwl_mvm_thermal_exit(struct iwl_mvm * mvm)954*4882a593Smuzhiyun void iwl_mvm_thermal_exit(struct iwl_mvm *mvm)
955*4882a593Smuzhiyun {
956*4882a593Smuzhiyun 	if (!(mvm->init_status & IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE))
957*4882a593Smuzhiyun 		return;
958*4882a593Smuzhiyun 
959*4882a593Smuzhiyun 	cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit);
960*4882a593Smuzhiyun 	IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n");
961*4882a593Smuzhiyun 
962*4882a593Smuzhiyun #ifdef CONFIG_THERMAL
963*4882a593Smuzhiyun 	iwl_mvm_cooling_device_unregister(mvm);
964*4882a593Smuzhiyun 	iwl_mvm_thermal_zone_unregister(mvm);
965*4882a593Smuzhiyun #endif
966*4882a593Smuzhiyun 	mvm->init_status &= ~IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
967*4882a593Smuzhiyun }
968