@@ -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);
@@ -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_ */
@@ -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;
}
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(-)