All of the thermal_zone_device_register_with_trips() callers pass zero writable trip points masks to it, so drop the mask argument from that function and update all of its callers accordingly. This also removes the artificial trip points per zone limit of 32, related to using writable trip points masks. No intentional functional impact. Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
850 lines
21 KiB
C
850 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
|
/*
|
|
* Copyright (C) 2012-2014, 2019-2022 Intel Corporation
|
|
* Copyright (C) 2013-2014 Intel Mobile Communications GmbH
|
|
* Copyright (C) 2015-2016 Intel Deutschland GmbH
|
|
*/
|
|
#include <linux/sort.h>
|
|
|
|
#include "mvm.h"
|
|
|
|
#define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT HZ
|
|
|
|
void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm)
|
|
{
|
|
struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
|
|
u32 duration = tt->params.ct_kill_duration;
|
|
|
|
if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
|
|
return;
|
|
|
|
IWL_ERR(mvm, "Enter CT Kill\n");
|
|
iwl_mvm_set_hw_ctkill_state(mvm, true);
|
|
|
|
if (!iwl_mvm_is_tt_in_fw(mvm)) {
|
|
tt->throttle = false;
|
|
tt->dynamic_smps = false;
|
|
}
|
|
|
|
/* Don't schedule an exit work if we're in test mode, since
|
|
* the temperature will not change unless we manually set it
|
|
* again (or disable testing).
|
|
*/
|
|
if (!mvm->temperature_test)
|
|
schedule_delayed_work(&tt->ct_kill_exit,
|
|
round_jiffies_relative(duration * HZ));
|
|
}
|
|
|
|
static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm)
|
|
{
|
|
if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
|
|
return;
|
|
|
|
IWL_ERR(mvm, "Exit CT Kill\n");
|
|
iwl_mvm_set_hw_ctkill_state(mvm, false);
|
|
}
|
|
|
|
static void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp)
|
|
{
|
|
/* ignore the notification if we are in test mode */
|
|
if (mvm->temperature_test)
|
|
return;
|
|
|
|
if (mvm->temperature == temp)
|
|
return;
|
|
|
|
mvm->temperature = temp;
|
|
iwl_mvm_tt_handler(mvm);
|
|
}
|
|
|
|
static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm,
|
|
struct iwl_rx_packet *pkt)
|
|
{
|
|
struct iwl_dts_measurement_notif_v1 *notif_v1;
|
|
int len = iwl_rx_packet_payload_len(pkt);
|
|
int temp;
|
|
|
|
/* we can use notif_v1 only, because v2 only adds an additional
|
|
* parameter, which is not used in this function.
|
|
*/
|
|
if (WARN_ON_ONCE(len < sizeof(*notif_v1))) {
|
|
IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
notif_v1 = (void *)pkt->data;
|
|
|
|
temp = le32_to_cpu(notif_v1->temp);
|
|
|
|
/* shouldn't be negative, but since it's s32, make sure it isn't */
|
|
if (WARN_ON_ONCE(temp < 0))
|
|
temp = 0;
|
|
|
|
IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp);
|
|
|
|
return temp;
|
|
}
|
|
|
|
static bool iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data *notif_wait,
|
|
struct iwl_rx_packet *pkt, void *data)
|
|
{
|
|
struct iwl_mvm *mvm =
|
|
container_of(notif_wait, struct iwl_mvm, notif_wait);
|
|
int *temp = data;
|
|
int ret;
|
|
|
|
ret = iwl_mvm_temp_notif_parse(mvm, pkt);
|
|
if (ret < 0)
|
|
return true;
|
|
|
|
*temp = ret;
|
|
|
|
return true;
|
|
}
|
|
|
|
void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
|
|
{
|
|
struct iwl_rx_packet *pkt = rxb_addr(rxb);
|
|
struct iwl_dts_measurement_notif_v2 *notif_v2;
|
|
int len = iwl_rx_packet_payload_len(pkt);
|
|
int temp;
|
|
u32 ths_crossed;
|
|
|
|
/* the notification is handled synchronously in ctkill, so skip here */
|
|
if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
|
|
return;
|
|
|
|
temp = iwl_mvm_temp_notif_parse(mvm, pkt);
|
|
|
|
if (!iwl_mvm_is_tt_in_fw(mvm)) {
|
|
if (temp >= 0)
|
|
iwl_mvm_tt_temp_changed(mvm, temp);
|
|
return;
|
|
}
|
|
|
|
if (WARN_ON_ONCE(len < sizeof(*notif_v2))) {
|
|
IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
|
|
return;
|
|
}
|
|
|
|
notif_v2 = (void *)pkt->data;
|
|
ths_crossed = le32_to_cpu(notif_v2->threshold_idx);
|
|
|
|
/* 0xFF in ths_crossed means the notification is not related
|
|
* to a trip, so we can ignore it here.
|
|
*/
|
|
if (ths_crossed == 0xFF)
|
|
return;
|
|
|
|
IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n",
|
|
temp, ths_crossed);
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS))
|
|
return;
|
|
|
|
if (mvm->tz_device.tzone) {
|
|
struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device;
|
|
|
|
thermal_zone_device_update(tz_dev->tzone,
|
|
THERMAL_TRIP_VIOLATED);
|
|
}
|
|
#endif /* CONFIG_THERMAL */
|
|
}
|
|
|
|
void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
|
|
{
|
|
struct iwl_rx_packet *pkt = rxb_addr(rxb);
|
|
struct ct_kill_notif *notif;
|
|
|
|
notif = (struct ct_kill_notif *)pkt->data;
|
|
IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n",
|
|
notif->temperature);
|
|
if (iwl_fw_lookup_notif_ver(mvm->fw, PHY_OPS_GROUP,
|
|
CT_KILL_NOTIFICATION, 0) > 1)
|
|
IWL_DEBUG_TEMP(mvm,
|
|
"CT kill notification DTS bitmap = 0x%x, Scheme = %d\n",
|
|
notif->dts, notif->scheme);
|
|
|
|
iwl_mvm_enter_ctkill(mvm);
|
|
}
|
|
|
|
/*
|
|
* send the DTS_MEASUREMENT_TRIGGER command with or without waiting for a
|
|
* response. If we get a response then the measurement is stored in 'temp'
|
|
*/
|
|
static int iwl_mvm_send_temp_cmd(struct iwl_mvm *mvm, bool response, s32 *temp)
|
|
{
|
|
struct iwl_host_cmd cmd = {};
|
|
struct iwl_dts_measurement_cmd dts_cmd = {
|
|
.flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP),
|
|
};
|
|
struct iwl_ext_dts_measurement_cmd ext_cmd = {
|
|
.control_mode = cpu_to_le32(DTS_DIRECT_WITHOUT_MEASURE),
|
|
};
|
|
struct iwl_dts_measurement_resp *resp;
|
|
void *cmd_ptr;
|
|
int ret;
|
|
u32 cmd_flags = 0;
|
|
u16 len;
|
|
|
|
/* Check which command format is used (regular/extended) */
|
|
if (fw_has_capa(&mvm->fw->ucode_capa,
|
|
IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) {
|
|
len = sizeof(ext_cmd);
|
|
cmd_ptr = &ext_cmd;
|
|
} else {
|
|
len = sizeof(dts_cmd);
|
|
cmd_ptr = &dts_cmd;
|
|
}
|
|
/* The command version where we get a response is zero length */
|
|
if (response) {
|
|
cmd_flags = CMD_WANT_SKB;
|
|
len = 0;
|
|
}
|
|
|
|
cmd.id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE);
|
|
cmd.len[0] = len;
|
|
cmd.flags = cmd_flags;
|
|
cmd.data[0] = cmd_ptr;
|
|
|
|
IWL_DEBUG_TEMP(mvm,
|
|
"Sending temperature measurement command - %s response\n",
|
|
response ? "with" : "without");
|
|
ret = iwl_mvm_send_cmd(mvm, &cmd);
|
|
|
|
if (ret) {
|
|
IWL_ERR(mvm,
|
|
"Failed to send the temperature measurement command (err=%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
if (response) {
|
|
resp = (void *)cmd.resp_pkt->data;
|
|
*temp = le32_to_cpu(resp->temp);
|
|
IWL_DEBUG_TEMP(mvm,
|
|
"Got temperature measurement response: temp=%d\n",
|
|
*temp);
|
|
iwl_free_resp(&cmd);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp)
|
|
{
|
|
struct iwl_notification_wait wait_temp_notif;
|
|
static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP,
|
|
DTS_MEASUREMENT_NOTIF_WIDE) };
|
|
int ret;
|
|
u8 cmd_ver;
|
|
|
|
/*
|
|
* If command version is 1 we send the command and immediately get
|
|
* a response. For older versions we send the command and wait for a
|
|
* notification (no command TLV for previous versions).
|
|
*/
|
|
cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw,
|
|
WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE),
|
|
IWL_FW_CMD_VER_UNKNOWN);
|
|
if (cmd_ver == 1)
|
|
return iwl_mvm_send_temp_cmd(mvm, true, temp);
|
|
|
|
lockdep_assert_held(&mvm->mutex);
|
|
|
|
iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif,
|
|
temp_notif, ARRAY_SIZE(temp_notif),
|
|
iwl_mvm_temp_notif_wait, temp);
|
|
|
|
ret = iwl_mvm_send_temp_cmd(mvm, false, temp);
|
|
if (ret) {
|
|
iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif);
|
|
return ret;
|
|
}
|
|
|
|
ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif,
|
|
IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT);
|
|
if (ret)
|
|
IWL_WARN(mvm, "Getting the temperature timed out\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void check_exit_ctkill(struct work_struct *work)
|
|
{
|
|
struct iwl_mvm_tt_mgmt *tt;
|
|
struct iwl_mvm *mvm;
|
|
u32 duration;
|
|
s32 temp;
|
|
int ret;
|
|
|
|
tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work);
|
|
mvm = container_of(tt, struct iwl_mvm, thermal_throttle);
|
|
|
|
if (iwl_mvm_is_tt_in_fw(mvm)) {
|
|
iwl_mvm_exit_ctkill(mvm);
|
|
|
|
return;
|
|
}
|
|
|
|
duration = tt->params.ct_kill_duration;
|
|
|
|
flush_work(&mvm->roc_done_wk);
|
|
|
|
mutex_lock(&mvm->mutex);
|
|
|
|
if (__iwl_mvm_mac_start(mvm))
|
|
goto reschedule;
|
|
|
|
ret = iwl_mvm_get_temp(mvm, &temp);
|
|
|
|
__iwl_mvm_mac_stop(mvm);
|
|
|
|
if (ret)
|
|
goto reschedule;
|
|
|
|
IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp);
|
|
|
|
if (temp <= tt->params.ct_kill_exit) {
|
|
mutex_unlock(&mvm->mutex);
|
|
iwl_mvm_exit_ctkill(mvm);
|
|
return;
|
|
}
|
|
|
|
reschedule:
|
|
mutex_unlock(&mvm->mutex);
|
|
schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit,
|
|
round_jiffies(duration * HZ));
|
|
}
|
|
|
|
static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mvm *mvm = _data;
|
|
enum ieee80211_smps_mode smps_mode;
|
|
|
|
lockdep_assert_held(&mvm->mutex);
|
|
|
|
if (mvm->thermal_throttle.dynamic_smps)
|
|
smps_mode = IEEE80211_SMPS_DYNAMIC;
|
|
else
|
|
smps_mode = IEEE80211_SMPS_AUTOMATIC;
|
|
|
|
if (vif->type != NL80211_IFTYPE_STATION)
|
|
return;
|
|
|
|
iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode, 0);
|
|
}
|
|
|
|
static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable)
|
|
{
|
|
struct iwl_mvm_sta *mvmsta;
|
|
int i, err;
|
|
|
|
for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) {
|
|
mvmsta = iwl_mvm_sta_from_staid_protected(mvm, i);
|
|
if (!mvmsta)
|
|
continue;
|
|
|
|
if (enable == mvmsta->tt_tx_protection)
|
|
continue;
|
|
err = iwl_mvm_tx_protection(mvm, mvmsta, enable);
|
|
if (err) {
|
|
IWL_ERR(mvm, "Failed to %s Tx protection\n",
|
|
enable ? "enable" : "disable");
|
|
} else {
|
|
IWL_DEBUG_TEMP(mvm, "%s Tx protection\n",
|
|
enable ? "Enable" : "Disable");
|
|
mvmsta->tt_tx_protection = enable;
|
|
}
|
|
}
|
|
}
|
|
|
|
void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff)
|
|
{
|
|
struct iwl_host_cmd cmd = {
|
|
.id = REPLY_THERMAL_MNG_BACKOFF,
|
|
.len = { sizeof(u32), },
|
|
.data = { &backoff, },
|
|
};
|
|
|
|
backoff = max(backoff, mvm->thermal_throttle.min_backoff);
|
|
|
|
if (iwl_mvm_send_cmd(mvm, &cmd) == 0) {
|
|
IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n",
|
|
backoff);
|
|
mvm->thermal_throttle.tx_backoff = backoff;
|
|
} else {
|
|
IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n");
|
|
}
|
|
}
|
|
|
|
void iwl_mvm_tt_handler(struct iwl_mvm *mvm)
|
|
{
|
|
struct iwl_tt_params *params = &mvm->thermal_throttle.params;
|
|
struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
|
|
s32 temperature = mvm->temperature;
|
|
bool throttle_enable = false;
|
|
int i;
|
|
u32 tx_backoff;
|
|
|
|
IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature);
|
|
|
|
if (params->support_ct_kill && temperature >= params->ct_kill_entry) {
|
|
iwl_mvm_enter_ctkill(mvm);
|
|
return;
|
|
}
|
|
|
|
if (params->support_ct_kill &&
|
|
temperature <= params->ct_kill_exit) {
|
|
iwl_mvm_exit_ctkill(mvm);
|
|
return;
|
|
}
|
|
|
|
if (params->support_dynamic_smps) {
|
|
if (!tt->dynamic_smps &&
|
|
temperature >= params->dynamic_smps_entry) {
|
|
IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n");
|
|
tt->dynamic_smps = true;
|
|
ieee80211_iterate_active_interfaces_atomic(
|
|
mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
|
|
iwl_mvm_tt_smps_iterator, mvm);
|
|
throttle_enable = true;
|
|
} else if (tt->dynamic_smps &&
|
|
temperature <= params->dynamic_smps_exit) {
|
|
IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n");
|
|
tt->dynamic_smps = false;
|
|
ieee80211_iterate_active_interfaces_atomic(
|
|
mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
|
|
iwl_mvm_tt_smps_iterator, mvm);
|
|
}
|
|
}
|
|
|
|
if (params->support_tx_protection) {
|
|
if (temperature >= params->tx_protection_entry) {
|
|
iwl_mvm_tt_tx_protection(mvm, true);
|
|
throttle_enable = true;
|
|
} else if (temperature <= params->tx_protection_exit) {
|
|
iwl_mvm_tt_tx_protection(mvm, false);
|
|
}
|
|
}
|
|
|
|
if (params->support_tx_backoff) {
|
|
tx_backoff = tt->min_backoff;
|
|
for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) {
|
|
if (temperature < params->tx_backoff[i].temperature)
|
|
break;
|
|
tx_backoff = max(tt->min_backoff,
|
|
params->tx_backoff[i].backoff);
|
|
}
|
|
if (tx_backoff != tt->min_backoff)
|
|
throttle_enable = true;
|
|
if (tt->tx_backoff != tx_backoff)
|
|
iwl_mvm_tt_tx_backoff(mvm, tx_backoff);
|
|
}
|
|
|
|
if (!tt->throttle && throttle_enable) {
|
|
IWL_WARN(mvm,
|
|
"Due to high temperature thermal throttling initiated\n");
|
|
tt->throttle = true;
|
|
} else if (tt->throttle && !tt->dynamic_smps &&
|
|
tt->tx_backoff == tt->min_backoff &&
|
|
temperature <= params->tx_protection_exit) {
|
|
IWL_WARN(mvm,
|
|
"Temperature is back to normal thermal throttling stopped\n");
|
|
tt->throttle = false;
|
|
}
|
|
}
|
|
|
|
static const struct iwl_tt_params iwl_mvm_default_tt_params = {
|
|
.ct_kill_entry = 118,
|
|
.ct_kill_exit = 96,
|
|
.ct_kill_duration = 5,
|
|
.dynamic_smps_entry = 114,
|
|
.dynamic_smps_exit = 110,
|
|
.tx_protection_entry = 114,
|
|
.tx_protection_exit = 108,
|
|
.tx_backoff = {
|
|
{.temperature = 112, .backoff = 200},
|
|
{.temperature = 113, .backoff = 600},
|
|
{.temperature = 114, .backoff = 1200},
|
|
{.temperature = 115, .backoff = 2000},
|
|
{.temperature = 116, .backoff = 4000},
|
|
{.temperature = 117, .backoff = 10000},
|
|
},
|
|
.support_ct_kill = true,
|
|
.support_dynamic_smps = true,
|
|
.support_tx_protection = true,
|
|
.support_tx_backoff = true,
|
|
};
|
|
|
|
/* budget in mWatt */
|
|
static const u32 iwl_mvm_cdev_budgets[] = {
|
|
2400, /* cooling state 0 */
|
|
2000, /* cooling state 1 */
|
|
1800, /* cooling state 2 */
|
|
1600, /* cooling state 3 */
|
|
1400, /* cooling state 4 */
|
|
1200, /* cooling state 5 */
|
|
1000, /* cooling state 6 */
|
|
900, /* cooling state 7 */
|
|
800, /* cooling state 8 */
|
|
700, /* cooling state 9 */
|
|
650, /* cooling state 10 */
|
|
600, /* cooling state 11 */
|
|
550, /* cooling state 12 */
|
|
500, /* cooling state 13 */
|
|
450, /* cooling state 14 */
|
|
400, /* cooling state 15 */
|
|
350, /* cooling state 16 */
|
|
300, /* cooling state 17 */
|
|
250, /* cooling state 18 */
|
|
200, /* cooling state 19 */
|
|
150, /* cooling state 20 */
|
|
};
|
|
|
|
int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 state)
|
|
{
|
|
struct iwl_mvm_ctdp_cmd cmd = {
|
|
.operation = cpu_to_le32(op),
|
|
.budget = cpu_to_le32(iwl_mvm_cdev_budgets[state]),
|
|
.window_size = 0,
|
|
};
|
|
int ret;
|
|
u32 status;
|
|
|
|
lockdep_assert_held(&mvm->mutex);
|
|
|
|
status = 0;
|
|
ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP,
|
|
CTDP_CONFIG_CMD),
|
|
sizeof(cmd), &cmd, &status);
|
|
|
|
if (ret) {
|
|
IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
switch (op) {
|
|
case CTDP_CMD_OPERATION_START:
|
|
#ifdef CONFIG_THERMAL
|
|
mvm->cooling_dev.cur_state = state;
|
|
#endif /* CONFIG_THERMAL */
|
|
break;
|
|
case CTDP_CMD_OPERATION_REPORT:
|
|
IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status);
|
|
/* when the function is called with CTDP_CMD_OPERATION_REPORT
|
|
* option the function should return the average budget value
|
|
* that is received from the FW.
|
|
* The budget can't be less or equal to 0, so it's possible
|
|
* to distinguish between error values and budgets.
|
|
*/
|
|
return status;
|
|
case CTDP_CMD_OPERATION_STOP:
|
|
IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n");
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
static int compare_temps(const void *a, const void *b)
|
|
{
|
|
return ((s16)le16_to_cpu(*(__le16 *)a) -
|
|
(s16)le16_to_cpu(*(__le16 *)b));
|
|
}
|
|
|
|
struct iwl_trip_walk_data {
|
|
__le16 *thresholds;
|
|
int count;
|
|
};
|
|
|
|
static int iwl_trip_temp_cb(struct thermal_trip *trip, void *arg)
|
|
{
|
|
struct iwl_trip_walk_data *twd = arg;
|
|
|
|
if (trip->temperature == THERMAL_TEMP_INVALID)
|
|
return 0;
|
|
|
|
twd->thresholds[twd->count++] = cpu_to_le16((s16)(trip->temperature / 1000));
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm)
|
|
{
|
|
struct temp_report_ths_cmd cmd = {0};
|
|
int ret;
|
|
#ifdef CONFIG_THERMAL
|
|
struct iwl_trip_walk_data twd = { .thresholds = cmd.thresholds, .count = 0 };
|
|
|
|
lockdep_assert_held(&mvm->mutex);
|
|
|
|
if (!mvm->tz_device.tzone)
|
|
goto send;
|
|
|
|
/*
|
|
* The thermal core holds an array of temperature trips that are
|
|
* unsorted and uncompressed, the FW should get it compressed and
|
|
* sorted.
|
|
*/
|
|
|
|
/* compress trips to cmd array, remove uninitialized values*/
|
|
for_each_thermal_trip(mvm->tz_device.tzone, iwl_trip_temp_cb, &twd);
|
|
|
|
cmd.num_temps = cpu_to_le32(twd.count);
|
|
if (twd.count)
|
|
sort(cmd.thresholds, twd.count, sizeof(s16), compare_temps, NULL);
|
|
|
|
send:
|
|
#endif
|
|
ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP,
|
|
TEMP_REPORTING_THRESHOLDS_CMD),
|
|
0, sizeof(cmd), &cmd);
|
|
if (ret)
|
|
IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n",
|
|
ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device,
|
|
int *temperature)
|
|
{
|
|
struct iwl_mvm *mvm = thermal_zone_device_priv(device);
|
|
int ret;
|
|
int temp;
|
|
|
|
mutex_lock(&mvm->mutex);
|
|
|
|
if (!iwl_mvm_firmware_running(mvm) ||
|
|
mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
|
|
ret = -ENODATA;
|
|
goto out;
|
|
}
|
|
|
|
ret = iwl_mvm_get_temp(mvm, &temp);
|
|
if (ret)
|
|
goto out;
|
|
|
|
*temperature = temp * 1000;
|
|
|
|
out:
|
|
mutex_unlock(&mvm->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device,
|
|
int trip, int temp)
|
|
{
|
|
struct iwl_mvm *mvm = thermal_zone_device_priv(device);
|
|
int ret;
|
|
|
|
mutex_lock(&mvm->mutex);
|
|
|
|
if (!iwl_mvm_firmware_running(mvm) ||
|
|
mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if ((temp / 1000) > S16_MAX) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
ret = iwl_mvm_send_temp_report_ths_cmd(mvm);
|
|
out:
|
|
mutex_unlock(&mvm->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static struct thermal_zone_device_ops tzone_ops = {
|
|
.get_temp = iwl_mvm_tzone_get_temp,
|
|
.set_trip_temp = iwl_mvm_tzone_set_trip_temp,
|
|
};
|
|
|
|
static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm)
|
|
{
|
|
int i, ret;
|
|
char name[16];
|
|
static atomic_t counter = ATOMIC_INIT(0);
|
|
|
|
if (!iwl_mvm_is_tt_in_fw(mvm)) {
|
|
mvm->tz_device.tzone = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
|
|
|
|
sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF);
|
|
/*
|
|
* 0 is a valid temperature,
|
|
* so initialize the array with S16_MIN which invalid temperature
|
|
*/
|
|
for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++) {
|
|
mvm->tz_device.trips[i].temperature = THERMAL_TEMP_INVALID;
|
|
mvm->tz_device.trips[i].type = THERMAL_TRIP_PASSIVE;
|
|
mvm->tz_device.trips[i].flags = THERMAL_TRIP_FLAG_RW_TEMP;
|
|
}
|
|
mvm->tz_device.tzone = thermal_zone_device_register_with_trips(name,
|
|
mvm->tz_device.trips,
|
|
IWL_MAX_DTS_TRIPS,
|
|
mvm, &tzone_ops,
|
|
NULL, 0, 0);
|
|
if (IS_ERR(mvm->tz_device.tzone)) {
|
|
IWL_DEBUG_TEMP(mvm,
|
|
"Failed to register to thermal zone (err = %ld)\n",
|
|
PTR_ERR(mvm->tz_device.tzone));
|
|
mvm->tz_device.tzone = NULL;
|
|
return;
|
|
}
|
|
|
|
ret = thermal_zone_device_enable(mvm->tz_device.tzone);
|
|
if (ret) {
|
|
IWL_DEBUG_TEMP(mvm, "Failed to enable thermal zone\n");
|
|
thermal_zone_device_unregister(mvm->tz_device.tzone);
|
|
}
|
|
}
|
|
|
|
static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev,
|
|
unsigned long *state)
|
|
{
|
|
*state = ARRAY_SIZE(iwl_mvm_cdev_budgets) - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev,
|
|
unsigned long *state)
|
|
{
|
|
struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
|
|
|
|
*state = mvm->cooling_dev.cur_state;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev,
|
|
unsigned long new_state)
|
|
{
|
|
struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
|
|
int ret;
|
|
|
|
mutex_lock(&mvm->mutex);
|
|
|
|
if (!iwl_mvm_firmware_running(mvm) ||
|
|
mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
|
|
ret = -EIO;
|
|
goto unlock;
|
|
}
|
|
|
|
if (new_state >= ARRAY_SIZE(iwl_mvm_cdev_budgets)) {
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START,
|
|
new_state);
|
|
|
|
unlock:
|
|
mutex_unlock(&mvm->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static const struct thermal_cooling_device_ops tcooling_ops = {
|
|
.get_max_state = iwl_mvm_tcool_get_max_state,
|
|
.get_cur_state = iwl_mvm_tcool_get_cur_state,
|
|
.set_cur_state = iwl_mvm_tcool_set_cur_state,
|
|
};
|
|
|
|
static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm)
|
|
{
|
|
char name[] = "iwlwifi";
|
|
|
|
if (!iwl_mvm_is_ctdp_supported(mvm))
|
|
return;
|
|
|
|
BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
|
|
|
|
mvm->cooling_dev.cdev =
|
|
thermal_cooling_device_register(name,
|
|
mvm,
|
|
&tcooling_ops);
|
|
|
|
if (IS_ERR(mvm->cooling_dev.cdev)) {
|
|
IWL_DEBUG_TEMP(mvm,
|
|
"Failed to register to cooling device (err = %ld)\n",
|
|
PTR_ERR(mvm->cooling_dev.cdev));
|
|
mvm->cooling_dev.cdev = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm)
|
|
{
|
|
if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone)
|
|
return;
|
|
|
|
IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n");
|
|
if (mvm->tz_device.tzone) {
|
|
thermal_zone_device_unregister(mvm->tz_device.tzone);
|
|
mvm->tz_device.tzone = NULL;
|
|
}
|
|
}
|
|
|
|
static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm)
|
|
{
|
|
if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev)
|
|
return;
|
|
|
|
IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n");
|
|
if (mvm->cooling_dev.cdev) {
|
|
thermal_cooling_device_unregister(mvm->cooling_dev.cdev);
|
|
mvm->cooling_dev.cdev = NULL;
|
|
}
|
|
}
|
|
#endif /* CONFIG_THERMAL */
|
|
|
|
void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff)
|
|
{
|
|
struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
|
|
|
|
IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n");
|
|
|
|
if (mvm->cfg->thermal_params)
|
|
tt->params = *mvm->cfg->thermal_params;
|
|
else
|
|
tt->params = iwl_mvm_default_tt_params;
|
|
|
|
tt->throttle = false;
|
|
tt->dynamic_smps = false;
|
|
tt->min_backoff = min_backoff;
|
|
INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill);
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
iwl_mvm_cooling_device_register(mvm);
|
|
iwl_mvm_thermal_zone_register(mvm);
|
|
#endif
|
|
mvm->init_status |= IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
|
|
}
|
|
|
|
void iwl_mvm_thermal_exit(struct iwl_mvm *mvm)
|
|
{
|
|
if (!(mvm->init_status & IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE))
|
|
return;
|
|
|
|
cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit);
|
|
IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n");
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
iwl_mvm_cooling_device_unregister(mvm);
|
|
iwl_mvm_thermal_zone_unregister(mvm);
|
|
#endif
|
|
mvm->init_status &= ~IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
|
|
}
|