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