mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 21:39:35 -04:00
Add thermal cooling device support to control the temperature by throttling data transmission. Throttling is performed by suspending data TX queues according to a configured duty-cycle off percentage. The thermal cooling device allows users to configure the duty-cycle off percentage and operate the device with the selected value. User configuration updates a single duty-cycle off percentage, which is applied uniformly by the host and treated as only one temperature level. This value remains in effect until updated again by the user. All other thermal throttling parameters continue to use their default firmware provided values. Reject invalid duty-cycle off percentage values that fall outside the supported range. Register a cooling device to allow the thermal framework to query and set the current throttle state, report the maximum supported state, and keep the host state in sync with successful firmware updates. A throttle state of zero restores the default firmware thermal configuration. Command to set the duty-cycle off percent: echo 40 > /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/cur_state Command to read duty-cycle off percent: cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/cur_state Command to read the maximum duty-cycle off percent: cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/max_state Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com> Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com> Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com> Link: https://patch.msgid.link/20260413153840.1969931-6-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
305 lines
7.6 KiB
C
305 lines
7.6 KiB
C
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
/*
|
|
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
|
|
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/thermal.h>
|
|
#include "core.h"
|
|
#include "debug.h"
|
|
|
|
static const struct ath12k_wmi_tt_level_config_param
|
|
tt_level_configs[ATH12K_TT_CFG_IDX_MAX][ENHANCED_THERMAL_LEVELS] = {
|
|
[ATH12K_TT_CFG_IDX_IPA] = {
|
|
[0] = { .tmplwm = -100, .tmphwm = 115, .dcoffpercent = 0,
|
|
.pout_reduction_db = 0 },
|
|
[1] = { .tmplwm = 110, .tmphwm = 120, .dcoffpercent = 0,
|
|
.pout_reduction_db = 12 },
|
|
[2] = { .tmplwm = 115, .tmphwm = 125, .dcoffpercent = 50,
|
|
.pout_reduction_db = 12 },
|
|
[3] = { .tmplwm = 120, .tmphwm = 130, .dcoffpercent = 90,
|
|
.pout_reduction_db = 12 },
|
|
[4] = { .tmplwm = 125, .tmphwm = 130, .dcoffpercent = 100,
|
|
.pout_reduction_db = 12 },
|
|
},
|
|
[ATH12K_TT_CFG_IDX_XFEM] = {
|
|
[0] = { .tmplwm = -100, .tmphwm = 105, .dcoffpercent = 0,
|
|
.pout_reduction_db = 0 },
|
|
[1] = { .tmplwm = 100, .tmphwm = 110, .dcoffpercent = 0,
|
|
.pout_reduction_db = 0 },
|
|
[2] = { .tmplwm = 105, .tmphwm = 115, .dcoffpercent = 50,
|
|
.pout_reduction_db = 0 },
|
|
[3] = { .tmplwm = 110, .tmphwm = 120, .dcoffpercent = 90,
|
|
.pout_reduction_db = 0 },
|
|
[4] = { .tmplwm = 115, .tmphwm = 120, .dcoffpercent = 100,
|
|
.pout_reduction_db = 0 },
|
|
},
|
|
};
|
|
|
|
static enum ath12k_thermal_cfg_idx ath12k_thermal_cfg_index(struct ath12k *ar)
|
|
{
|
|
if (test_bit(WMI_TLV_SERVICE_IS_TARGET_IPA, ar->ab->wmi_ab.svc_map))
|
|
return ATH12K_TT_CFG_IDX_IPA;
|
|
|
|
return ATH12K_TT_CFG_IDX_XFEM;
|
|
}
|
|
|
|
int ath12k_thermal_throttling_config_default(struct ath12k *ar)
|
|
{
|
|
struct ath12k_wmi_thermal_mitigation_arg param = {};
|
|
int ret;
|
|
|
|
if (test_bit(WMI_TLV_SERVICE_THERM_THROT_5_LEVELS, ar->ab->wmi_ab.svc_map))
|
|
param.num_levels = ENHANCED_THERMAL_LEVELS;
|
|
else
|
|
param.num_levels = THERMAL_LEVELS;
|
|
|
|
param.levelconf = ar->thermal.tt_level_configs;
|
|
|
|
ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, ¶m);
|
|
if (ret)
|
|
ath12k_warn(ar->ab,
|
|
"failed to send thermal mitigation cmd for default config: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
void ath12k_thermal_init_configs(struct ath12k *ar)
|
|
{
|
|
enum ath12k_thermal_cfg_idx cfg_idx;
|
|
|
|
cfg_idx = ath12k_thermal_cfg_index(ar);
|
|
ar->thermal.tt_level_configs = &tt_level_configs[cfg_idx][0];
|
|
}
|
|
|
|
static int
|
|
ath12k_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev,
|
|
unsigned long *state)
|
|
{
|
|
*state = ATH12K_THERMAL_THROTTLE_MAX;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ath12k_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev,
|
|
unsigned long *state)
|
|
{
|
|
struct ath12k *ar = cdev->devdata;
|
|
|
|
mutex_lock(&ar->thermal.lock);
|
|
*state = ar->thermal.throttle_state;
|
|
mutex_unlock(&ar->thermal.lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state)
|
|
{
|
|
struct ath12k_wmi_thermal_mitigation_arg param = {};
|
|
struct ath12k_wmi_tt_level_config_param cfg = {};
|
|
int ret;
|
|
|
|
param.num_levels = 1;
|
|
cfg.dcoffpercent = throttle_state;
|
|
param.levelconf = &cfg;
|
|
|
|
ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, ¶m);
|
|
if (ret)
|
|
ath12k_warn(ar->ab, "failed to send thermal mitigation cmd: %d\n",
|
|
ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
ath12k_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev,
|
|
unsigned long throttle_state)
|
|
{
|
|
struct ath12k *ar = cdev->devdata;
|
|
|
|
if (throttle_state > ATH12K_THERMAL_THROTTLE_MAX)
|
|
return -EINVAL;
|
|
|
|
scoped_guard(mutex, &ar->thermal.lock) {
|
|
if (ar->thermal.throttle_state == throttle_state)
|
|
return 0;
|
|
ar->thermal.throttle_state = throttle_state;
|
|
}
|
|
|
|
if (throttle_state == 0)
|
|
return ath12k_thermal_throttling_config_default(ar);
|
|
|
|
return ath12k_thermal_set_throttling(ar, throttle_state);
|
|
}
|
|
|
|
static const struct thermal_cooling_device_ops ath12k_thermal_ops = {
|
|
.get_max_state = ath12k_thermal_get_max_throttle_state,
|
|
.get_cur_state = ath12k_thermal_get_cur_throttle_state,
|
|
.set_cur_state = ath12k_thermal_set_cur_throttle_state,
|
|
};
|
|
|
|
static ssize_t ath12k_thermal_temp_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct ath12k *ar = dev_get_drvdata(dev);
|
|
unsigned long time_left;
|
|
int ret, temperature;
|
|
|
|
guard(wiphy)(ath12k_ar_to_hw(ar)->wiphy);
|
|
|
|
if (ar->ah->state != ATH12K_HW_STATE_ON)
|
|
return -ENETDOWN;
|
|
|
|
reinit_completion(&ar->thermal.wmi_sync);
|
|
ret = ath12k_wmi_send_pdev_temperature_cmd(ar);
|
|
if (ret) {
|
|
ath12k_warn(ar->ab, "failed to read temperature %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags))
|
|
return -ESHUTDOWN;
|
|
|
|
time_left = wait_for_completion_timeout(&ar->thermal.wmi_sync,
|
|
ATH12K_THERMAL_SYNC_TIMEOUT_HZ);
|
|
if (!time_left) {
|
|
ath12k_warn(ar->ab, "failed to synchronize thermal read\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
spin_lock_bh(&ar->data_lock);
|
|
temperature = ar->thermal.temperature;
|
|
spin_unlock_bh(&ar->data_lock);
|
|
|
|
/* display in millidegree celsius */
|
|
return sysfs_emit(buf, "%d\n", temperature * 1000);
|
|
}
|
|
|
|
void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature)
|
|
{
|
|
spin_lock_bh(&ar->data_lock);
|
|
ar->thermal.temperature = temperature;
|
|
spin_unlock_bh(&ar->data_lock);
|
|
complete_all(&ar->thermal.wmi_sync);
|
|
}
|
|
|
|
static SENSOR_DEVICE_ATTR_RO(temp1_input, ath12k_thermal_temp, 0);
|
|
|
|
static struct attribute *ath12k_hwmon_attrs[] = {
|
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(ath12k_hwmon);
|
|
|
|
static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
|
|
{
|
|
char pdev_name[20];
|
|
struct ath12k *ar;
|
|
int ret;
|
|
|
|
ar = ab->pdevs[i].ar;
|
|
if (!ar)
|
|
return 0;
|
|
|
|
ar->thermal.cdev =
|
|
thermal_cooling_device_register("ath12k_thermal", ar,
|
|
&ath12k_thermal_ops);
|
|
if (IS_ERR(ar->thermal.cdev)) {
|
|
ret = PTR_ERR(ar->thermal.cdev);
|
|
ar->thermal.cdev = NULL;
|
|
ath12k_err(ar->ab, "failed to register cooling device: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u",
|
|
ar->hw_link_id);
|
|
|
|
ret = sysfs_create_link(&ar->ah->hw->wiphy->dev.kobj,
|
|
&ar->thermal.cdev->device.kobj, pdev_name);
|
|
if (ret) {
|
|
ath12k_err(ab, "failed to create cooling device symlink: %d\n",
|
|
ret);
|
|
goto unregister_cdev;
|
|
}
|
|
|
|
ar->thermal.hwmon_dev =
|
|
hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev,
|
|
"ath12k_hwmon", ar,
|
|
ath12k_hwmon_groups);
|
|
if (IS_ERR(ar->thermal.hwmon_dev)) {
|
|
ret = PTR_ERR(ar->thermal.hwmon_dev);
|
|
ar->thermal.hwmon_dev = NULL;
|
|
ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
|
|
ret);
|
|
goto remove_sysfs;
|
|
}
|
|
|
|
return 0;
|
|
|
|
remove_sysfs:
|
|
sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name);
|
|
unregister_cdev:
|
|
thermal_cooling_device_unregister(ar->thermal.cdev);
|
|
ar->thermal.cdev = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i)
|
|
{
|
|
char pdev_name[20];
|
|
struct ath12k *ar;
|
|
|
|
ar = ab->pdevs[i].ar;
|
|
if (!ar)
|
|
return;
|
|
|
|
hwmon_device_unregister(ar->thermal.hwmon_dev);
|
|
ar->thermal.hwmon_dev = NULL;
|
|
|
|
scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u",
|
|
ar->hw_link_id);
|
|
sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name);
|
|
|
|
thermal_cooling_device_unregister(ar->thermal.cdev);
|
|
ar->thermal.cdev = NULL;
|
|
}
|
|
|
|
int ath12k_thermal_register(struct ath12k_base *ab)
|
|
{
|
|
int i, ret;
|
|
|
|
if (!IS_REACHABLE(CONFIG_HWMON))
|
|
return 0;
|
|
|
|
for (i = 0; i < ab->num_radios; i++) {
|
|
ret = ath12k_thermal_setup_radio(ab, i);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
return 0;
|
|
out:
|
|
for (i--; i >= 0; i--)
|
|
ath12k_thermal_cleanup_radio(ab, i);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ath12k_thermal_unregister(struct ath12k_base *ab)
|
|
{
|
|
int i;
|
|
|
|
if (!IS_REACHABLE(CONFIG_HWMON))
|
|
return;
|
|
|
|
for (i = 0; i < ab->num_radios; i++)
|
|
ath12k_thermal_cleanup_radio(ab, i);
|
|
}
|