]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: ath12k: add basic hwmon temperature reporting
authorMaharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
Mon, 23 Feb 2026 13:26:22 +0000 (18:56 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Thu, 26 Feb 2026 21:25:39 +0000 (13:25 -0800)
Add initial thermal support by wiring up a per-radio (pdev) hwmon temperature
sensor backed by the existing WMI pdev temperature command and event.
When userspace reads the sysfs file temp1_input, the driver sends
WMI_PDEV_GET_TEMPERATURE_CMDID (tag WMI_TAG_PDEV_GET_TEMPERATURE_CMD) and waits
for the corresponding WMI_PDEV_TEMPERATURE_EVENTID
(tag WMI_TAG_PDEV_TEMPERATURE_EVENT) to get the temperature and pdev_id.

Export the reported value in millidegrees Celsius as required by hwmon.
The temperature reported is per-radio (pdev). In a multi-radio wiphy under a
single phy, a separate hwmon device is created for each radio.

Sample command and output:
$ cat /sys/devices/pci0000:00/.../ieee80211/phyX/hwmonY/temp1_input
$ 50000

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

Co-developed-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>
Signed-off-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>
Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>
Link: https://patch.msgid.link/20260223132622.43464-1-maharaja.kennadyrajan@oss.qualcomm.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath12k/Makefile
drivers/net/wireless/ath/ath12k/core.c
drivers/net/wireless/ath/ath12k/core.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/thermal.c [new file with mode: 0644]
drivers/net/wireless/ath/ath12k/thermal.h [new file with mode: 0644]
drivers/net/wireless/ath/ath12k/wmi.c

index 3ba1236956cc84e9faf28b6cfa950930436abd43..3b39b2c3330761f1e13e9a55da1c1b9605957f7c 100644 (file)
@@ -32,6 +32,7 @@ ath12k-$(CONFIG_ATH12K_TRACING) += trace.o
 ath12k-$(CONFIG_PM) += wow.o
 ath12k-$(CONFIG_ATH12K_COREDUMP) += coredump.o
 ath12k-$(CONFIG_NL80211_TESTMODE) += testmode.o
+ath12k-$(CONFIG_THERMAL) += thermal.o
 
 # for tracing framework to find trace.h
 CFLAGS_trace.o := -I$(src)
index 9d6c50a94e64217dbe706f6de3e932dc663811bb..9dca1a0af73e39e4a492505e99ae68e6f8990e4e 100644 (file)
@@ -863,11 +863,22 @@ static int ath12k_core_pdev_create(struct ath12k_base *ab)
                return ret;
        }
 
+       ret = ath12k_thermal_register(ab);
+       if (ret) {
+               ath12k_err(ab, "could not register thermal device: %d\n", ret);
+               goto err_dp_pdev_free;
+       }
+
        return 0;
+
+err_dp_pdev_free:
+       ath12k_dp_pdev_free(ab);
+       return ret;
 }
 
 static void ath12k_core_pdev_destroy(struct ath12k_base *ab)
 {
+       ath12k_thermal_unregister(ab);
        ath12k_dp_pdev_free(ab);
 }
 
@@ -1361,6 +1372,7 @@ static int ath12k_core_reconfigure_on_crash(struct ath12k_base *ab)
 
        mutex_lock(&ab->core_lock);
        ath12k_link_sta_rhash_tbl_destroy(ab);
+       ath12k_thermal_unregister(ab);
        ath12k_dp_pdev_free(ab);
        ath12k_ce_cleanup_pipes(ab);
        ath12k_wmi_detach(ab);
@@ -1502,6 +1514,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab)
                        complete(&ar->vdev_delete_done);
                        complete(&ar->bss_survey_done);
                        complete_all(&ar->regd_update_completed);
+                       complete_all(&ar->thermal.wmi_sync);
 
                        wake_up(&ar->dp.tx_empty_waitq);
                        idr_for_each(&ar->txmgmt_idr,
index 990934ec92fcad1fede77e88aa5a0efe2cf73ee0..760c76d6f0f4a778f1f85610edb6ba4dcd3fe57c 100644 (file)
@@ -36,6 +36,7 @@
 #include "coredump.h"
 #include "cmn_defs.h"
 #include "dp_cmn.h"
+#include "thermal.h"
 
 #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
 
@@ -757,6 +758,8 @@ struct ath12k {
 
        s8 max_allowed_tx_power;
        struct ath12k_pdev_rssi_offsets rssi_info;
+
+       struct ath12k_thermal thermal;
 };
 
 struct ath12k_hw {
index 68431a0e128eadda03e7ca70d04ed1ffcbef611c..b9091f3f723fd5eb4e3c2e1afd9cc83f7f79fea4 100644 (file)
@@ -14792,6 +14792,10 @@ static void ath12k_mac_setup(struct ath12k *ar)
        init_completion(&ar->mlo_setup_done);
        init_completion(&ar->completed_11d_scan);
        init_completion(&ar->regd_update_completed);
+       init_completion(&ar->thermal.wmi_sync);
+
+       ar->thermal.temperature = 0;
+       ar->thermal.hwmon_dev = NULL;
 
        INIT_DELAYED_WORK(&ar->scan.timeout, ath12k_scan_timeout_work);
        wiphy_work_init(&ar->scan.vdev_clean_wk, ath12k_scan_vdev_clean_work);
diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c
new file mode 100644 (file)
index 0000000..a764d21
--- /dev/null
@@ -0,0 +1,124 @@
+// 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 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);
+
+int ath12k_thermal_register(struct ath12k_base *ab)
+{
+       struct ath12k *ar;
+       int i, j, ret;
+
+       if (!IS_REACHABLE(CONFIG_HWMON))
+               return 0;
+
+       for (i = 0; i < ab->num_radios; i++) {
+               ar = ab->pdevs[i].ar;
+               if (!ar)
+                       continue;
+
+               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);
+                       for (j = i - 1; j >= 0; j--) {
+                               ar = ab->pdevs[j].ar;
+                               if (!ar)
+                                       continue;
+
+                               hwmon_device_unregister(ar->thermal.hwmon_dev);
+                               ar->thermal.hwmon_dev = NULL;
+                       }
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+void ath12k_thermal_unregister(struct ath12k_base *ab)
+{
+       struct ath12k *ar;
+       int i;
+
+       if (!IS_REACHABLE(CONFIG_HWMON))
+               return;
+
+       for (i = 0; i < ab->num_radios; i++) {
+               ar = ab->pdevs[i].ar;
+               if (!ar)
+                       continue;
+
+               if (ar->thermal.hwmon_dev) {
+                       hwmon_device_unregister(ar->thermal.hwmon_dev);
+                       ar->thermal.hwmon_dev = NULL;
+               }
+       }
+}
diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h
new file mode 100644 (file)
index 0000000..9d84056
--- /dev/null
@@ -0,0 +1,40 @@
+/* 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.
+ */
+
+#ifndef _ATH12K_THERMAL_
+#define _ATH12K_THERMAL_
+
+#define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ)
+
+struct ath12k_thermal {
+       struct completion wmi_sync;
+
+       /* temperature value in Celsius degree protected by data_lock. */
+       int temperature;
+       struct device *hwmon_dev;
+};
+
+#if IS_REACHABLE(CONFIG_THERMAL)
+int ath12k_thermal_register(struct ath12k_base *ab);
+void ath12k_thermal_unregister(struct ath12k_base *ab);
+void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature);
+#else
+static inline int ath12k_thermal_register(struct ath12k_base *ab)
+{
+       return 0;
+}
+
+static inline void ath12k_thermal_unregister(struct ath12k_base *ab)
+{
+}
+
+static inline void ath12k_thermal_event_temperature(struct ath12k *ar,
+                                                   int temperature)
+{
+}
+
+#endif
+#endif /* _ATH12K_THERMAL_ */
index 7617fc3a2479cbe3dd64e0d95841e5afee80b627..69f2dbcfca639099d22cac79b756c9dab77a4300 100644 (file)
@@ -6778,31 +6778,6 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
        return 0;
 }
 
-static int
-ath12k_pull_pdev_temp_ev(struct ath12k_base *ab, struct sk_buff *skb,
-                        const struct wmi_pdev_temperature_event *ev)
-{
-       const void **tb;
-       int ret;
-
-       tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
-       if (IS_ERR(tb)) {
-               ret = PTR_ERR(tb);
-               ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
-               return ret;
-       }
-
-       ev = tb[WMI_TAG_PDEV_TEMPERATURE_EVENT];
-       if (!ev) {
-               ath12k_warn(ab, "failed to fetch pdev temp ev");
-               kfree(tb);
-               return -EPROTO;
-       }
-
-       kfree(tb);
-       return 0;
-}
-
 static void ath12k_wmi_op_ep_tx_credits(struct ath12k_base *ab)
 {
        /* try to send pending beacons first. they take priority */
@@ -8811,25 +8786,45 @@ static void
 ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
                                  struct sk_buff *skb)
 {
+       const struct wmi_pdev_temperature_event *ev;
        struct ath12k *ar;
-       struct wmi_pdev_temperature_event ev = {};
+       const void **tb;
+       int temp;
+       u32 pdev_id;
+
+       tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+       if (IS_ERR(tb)) {
+               ath12k_warn(ab, "failed to parse tlv: %ld\n", PTR_ERR(tb));
+               return;
+       }
 
-       if (ath12k_pull_pdev_temp_ev(ab, skb, &ev) != 0) {
-               ath12k_warn(ab, "failed to extract pdev temperature event");
+       ev = tb[WMI_TAG_PDEV_TEMPERATURE_EVENT];
+       if (!ev) {
+               ath12k_warn(ab, "failed to fetch pdev temp ev\n");
+               kfree(tb);
                return;
        }
 
+       temp = a_sle32_to_cpu(ev->temp);
+       pdev_id = le32_to_cpu(ev->pdev_id);
+
+       kfree(tb);
+
        ath12k_dbg(ab, ATH12K_DBG_WMI,
-                  "pdev temperature ev temp %d pdev_id %d\n", ev.temp, ev.pdev_id);
+                  "pdev temperature ev temp %d pdev_id %u\n",
+                  temp, pdev_id);
 
        rcu_read_lock();
 
-       ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(ev.pdev_id));
+       ar = ath12k_mac_get_ar_by_pdev_id(ab, pdev_id);
        if (!ar) {
-               ath12k_warn(ab, "invalid pdev id in pdev temperature ev %d", ev.pdev_id);
+               ath12k_warn(ab, "invalid pdev id %u in pdev temperature ev\n",
+                           pdev_id);
                goto exit;
        }
 
+       ath12k_thermal_event_temperature(ar, temp);
+
 exit:
        rcu_read_unlock();
 }