]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath12k: add thermal cooling device support
authorMaharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
Mon, 13 Apr 2026 15:38:40 +0000 (21:08 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Thu, 30 Apr 2026 21:24:09 +0000 (14:24 -0700)
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>
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/thermal.c
drivers/net/wireless/ath/ath12k/thermal.h

index 99e6cc6d6d7faa4c3a03737bbaaaf972e0c39416..9ce759626f181abff1b1cd28fe63f5775b68af4b 100644 (file)
@@ -14806,6 +14806,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
        init_completion(&ar->completed_11d_scan);
        init_completion(&ar->regd_update_completed);
        init_completion(&ar->thermal.wmi_sync);
+       mutex_init(&ar->thermal.lock);
 
        ar->thermal.temperature = 0;
        ar->thermal.hwmon_dev = NULL;
index 6f70c11c109824a542d139bd41b96a22297caf46..97fc49c40ac150fb9bb6234e48581b7a1dce0ce7 100644 (file)
@@ -76,6 +76,73 @@ void ath12k_thermal_init_configs(struct ath12k *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, &param);
+       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)
@@ -132,6 +199,7 @@ 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;
 
@@ -139,6 +207,28 @@ static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
        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,
@@ -148,14 +238,22 @@ static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
                ar->thermal.hwmon_dev = NULL;
                ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
                           ret);
-               return 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;
@@ -164,6 +262,13 @@ static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i)
 
        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)
index 33231bb3683c0624a70d93298b3cc16462d6e2c2..30e7b0880e056daadc56412f98d87b3d57fa129b 100644 (file)
@@ -7,9 +7,12 @@
 #ifndef _ATH12K_THERMAL_
 #define _ATH12K_THERMAL_
 
+#include <linux/mutex.h>
+
 #define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ)
 
 #define ATH12K_THERMAL_DEFAULT_DUTY_CYCLE 100
+#define ATH12K_THERMAL_THROTTLE_MAX 100
 
 enum ath12k_thermal_cfg_idx {
        /* Internal Power Amplifier Device */
@@ -26,6 +29,10 @@ struct ath12k_thermal {
        int temperature;
        struct device *hwmon_dev;
        const struct ath12k_wmi_tt_level_config_param *tt_level_configs;
+       struct thermal_cooling_device *cdev;
+       /* Serialize thermal operations and hwmon reads */
+       struct mutex lock;
+       u32 throttle_state;
 };
 
 #if IS_REACHABLE(CONFIG_THERMAL)
@@ -34,6 +41,7 @@ void ath12k_thermal_unregister(struct ath12k_base *ab);
 void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature);
 int ath12k_thermal_throttling_config_default(struct ath12k *ar);
 void ath12k_thermal_init_configs(struct ath12k *ar);
+int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state);
 #else
 static inline int ath12k_thermal_register(struct ath12k_base *ab)
 {
@@ -57,5 +65,11 @@ static inline int ath12k_thermal_throttling_config_default(struct ath12k *ar)
 static inline void ath12k_thermal_init_configs(struct ath12k *ar)
 {
 }
+
+static inline int ath12k_thermal_set_throttling(struct ath12k *ar,
+                                               u32 throttle_state)
+{
+       return 0;
+}
 #endif
 #endif /* _ATH12K_THERMAL_ */