diff mbox series

[ath-next,v2,5/6] wifi: ath11l: choose default PM policy for hibernation

Message ID 20250326-ath11k-bring-hibernation-back-v2-5-87fdc2d6428f@quicinc.com
State Superseded
Headers show
Series wifi: ath11k: bring hibernation support back | expand

Commit Message

Baochen Qiang March 26, 2025, 1:33 a.m. UTC
Now WoWLAN mode is chosen for those machines listed in the quirk table.
This works for suspend (S3) but breaks for hibernation (S4), because
WoWLAN mode requires WLAN power to be sustained, which is not the case
during hibernation. For hibernation, the default mode should be used.

Register a PM notifier with which kernel can notify us of the actual PM
operation: if system is going to suspend, the original PM policy is
honored; while if it is hibernation, overwrite it with default policy.

To summarize: for suspend (S3), WoWLAN mode is chosen for machines listed
in the quirk table, non-WoWLAN mode for others; for hibernation (S4),
non-WoWLAN mode is chosen for all.

Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30

Signed-off-by: Baochen Qiang <quic_bqiang@quicinc.com>
---
 drivers/net/wireless/ath/ath11k/core.c | 52 +++++++++++++++++++++++++++++++---
 drivers/net/wireless/ath/ath11k/core.h |  5 ++++
 drivers/net/wireless/ath/ath11k/pci.c  |  1 +
 3 files changed, 54 insertions(+), 4 deletions(-)
diff mbox series

Patch

diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
index e1c0eca773fc0243aa4d159dd8671f6a4084b29a..cde009c3bdaf1e7a4f64a10df61029ef83408cd0 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -1136,7 +1136,7 @@  int ath11k_core_suspend(struct ath11k_base *ab)
 	if (ret <= 0)
 		return ret;
 
-	if (ab->pm_policy == ATH11K_PM_WOW)
+	if (ab->actual_pm_policy == ATH11K_PM_WOW)
 		return ath11k_core_suspend_wow(ab);
 
 	return ath11k_core_suspend_default(ab);
@@ -1151,7 +1151,7 @@  int ath11k_core_suspend_late(struct ath11k_base *ab)
 	if (ret <= 0)
 		return ret;
 
-	if (ab->pm_policy == ATH11K_PM_WOW)
+	if (ab->actual_pm_policy == ATH11K_PM_WOW)
 		return 0;
 
 	ath11k_hif_irq_disable(ab);
@@ -1171,7 +1171,7 @@  int ath11k_core_resume_early(struct ath11k_base *ab)
 	if (ret <= 0)
 		return ret;
 
-	if (ab->pm_policy == ATH11K_PM_WOW)
+	if (ab->actual_pm_policy == ATH11K_PM_WOW)
 		return 0;
 
 	reinit_completion(&ab->restart_completed);
@@ -1240,7 +1240,7 @@  int ath11k_core_resume(struct ath11k_base *ab)
 	if (ret <= 0)
 		return ret;
 
-	if (ab->pm_policy == ATH11K_PM_WOW)
+	if (ab->actual_pm_policy == ATH11K_PM_WOW)
 		return ath11k_core_resume_wow(ab);
 
 	return ath11k_core_resume_default(ab);
@@ -2500,6 +2500,43 @@  int ath11k_core_pre_init(struct ath11k_base *ab)
 }
 EXPORT_SYMBOL(ath11k_core_pre_init);
 
+static int ath11k_core_pm_notify(struct notifier_block *nb,
+				 unsigned long action, void *nouse)
+{
+	struct ath11k_base *ab = container_of(nb, struct ath11k_base,
+					      pm_nb);
+
+	switch (action) {
+	case PM_SUSPEND_PREPARE:
+		ab->actual_pm_policy = ab->pm_policy;
+		break;
+	case PM_HIBERNATION_PREPARE:
+		ab->actual_pm_policy = ATH11K_PM_DEFAULT;
+		break;
+	default:
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static int ath11k_core_pm_notifier_register(struct ath11k_base *ab)
+{
+	ab->pm_nb.notifier_call = ath11k_core_pm_notify;
+	return register_pm_notifier(&ab->pm_nb);
+}
+
+void ath11k_core_pm_notifier_unregister(struct ath11k_base *ab)
+{
+	int ret;
+
+	ret = unregister_pm_notifier(&ab->pm_nb);
+	if (ret)
+		/* just warn here, there is nothing can be done in fail case */
+		ath11k_warn(ab, "failed to unregister PM notifier %d\n", ret);
+}
+EXPORT_SYMBOL(ath11k_core_pm_notifier_unregister);
+
 int ath11k_core_init(struct ath11k_base *ab)
 {
 	const struct dmi_system_id *dmi_id;
@@ -2513,6 +2550,12 @@  int ath11k_core_init(struct ath11k_base *ab)
 
 	ath11k_dbg(ab, ATH11K_DBG_BOOT, "pm policy %u\n", ab->pm_policy);
 
+	ret = ath11k_core_pm_notifier_register(ab);
+	if (ret) {
+		ath11k_err(ab, "failed to register PM notifier: %d\n", ret);
+		return ret;
+	}
+
 	ret = ath11k_core_soc_create(ab);
 	if (ret) {
 		ath11k_err(ab, "failed to create soc core: %d\n", ret);
@@ -2535,6 +2578,7 @@  void ath11k_core_deinit(struct ath11k_base *ab)
 	ath11k_hif_power_down(ab, false);
 	ath11k_mac_destroy(ab);
 	ath11k_core_soc_destroy(ab);
+	ath11k_core_pm_notifier_unregister(ab);
 }
 EXPORT_SYMBOL(ath11k_core_deinit);
 
diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
index 7a9354e0eadb58730028217960e5bbfe0056c2fb..339d4fca1ed5fbddd8f386589529f0febba98ecb 100644
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -16,6 +16,7 @@ 
 #include <linux/rhashtable.h>
 #include <linux/average.h>
 #include <linux/firmware.h>
+#include <linux/suspend.h>
 
 #include "qmi.h"
 #include "htc.h"
@@ -1066,6 +1067,8 @@  struct ath11k_base {
 #endif
 
 	enum ath11k_pm_policy pm_policy;
+	enum ath11k_pm_policy actual_pm_policy;
+	struct notifier_block pm_nb;
 
 	/* must be last */
 	u8 drv_priv[] __aligned(sizeof(void *));
@@ -1333,4 +1336,6 @@  static inline const char *ath11k_bus_str(enum ath11k_bus bus)
 	return "unknown";
 }
 
+void ath11k_core_pm_notifier_unregister(struct ath11k_base *ab);
+
 #endif /* _CORE_H_ */
diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
index 37f5ed7f74f2f6adb3a6b5e176ed8c0105d235c2..630df6b1d82dff357c8259475cc9b03e4580a1b9 100644
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -1164,6 +1164,7 @@  static void ath11k_pci_remove(struct pci_dev *pdev)
 		ath11k_pci_power_down(ab, false);
 		ath11k_debugfs_soc_destroy(ab);
 		ath11k_qmi_deinit_service(ab);
+		ath11k_core_pm_notifier_unregister(ab);
 		goto qmi_fail;
 	}