diff --git a/patches.suse/wifi-ath11k-support-hibernation.patch b/patches.suse/wifi-ath11k-support-hibernation.patch new file mode 100644 index 0000000..81406aa --- /dev/null +++ b/patches.suse/wifi-ath11k-support-hibernation.patch @@ -0,0 +1,472 @@ +From: Baochen Qiang +Subject: [PATCH 3/3] wifi: ath11k: support hibernation +Date: Wed, 21 Feb 2024 11:00:26 +0800 +Message-id: <20240221030026.10553-4-quic_bqiang@quicinc.com> +Patch-mainline: Submitted, linux-wireless ML +References: bsc#1207948 + +Now that all infrastructure is in place and ath11k is fixed to handle all the +corner cases, power down the ath11k firmware during suspend and power it back +up during resume. This fixes the problem when using hibernation with ath11k PCI +devices. + +For suspend, two conditions needs to be satisfied: + 1. since MHI channel unprepare would be done in late suspend stage, + ath11k needs to get all QMI-dependent things done before that stage. + 2. and because unprepare MHI channels requires a working MHI stack, + ath11k is not allowed to call mhi_power_down() until that finishes. +So the original suspend callback is separated into two parts: the first part +handles all QMI-dependent things in suspend callback; while the second part +powers down MHI in suspend_late callback. This is valid because kernel calls +ath11k's suspend callback before all suspend_late callbacks, making the first +condition happy. And because MHI devices are children of ath11k device +(ab->dev), kernel guarantees that ath11k's suspend_late callback is called +after QRTR's suspend_late callback, this satisfies the second condition. + +Above analysis also applies to resume process. so the original resume +callback is separated into two parts: the first part powers up MHI stack +in resume_early callback, this guarantees MHI stack is working when +QRTR tries to prepare MHI channels (kernel calls QRTR's resume_early callback +after ath11k's resume_early callback, due to the child-father relationship); +the second part waits for the completion of restart, which won't fail now +since MHI channels are ready for use by QMI. + +Another notable change is in power down path, we tell mhi_power_down() to not +to destroy MHI devices, making it possible for QRTR to help unprepare/prepare +MHI channels, and finally get us rid of the probe-defer issue when resume. + +Also change related code due to interface changes. + +Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30 + +Tested-by: Takashi Iwai +Signed-off-by: Kalle Valo +Signed-off-by: Baochen Qiang +Acked-by: Takashi Iwai + +--- + drivers/net/wireless/ath/ath11k/ahb.c | 6 - + drivers/net/wireless/ath/ath11k/core.c | 105 +++++++++++++++++++++++---------- + drivers/net/wireless/ath/ath11k/core.h | 6 + + drivers/net/wireless/ath/ath11k/hif.h | 14 +++- + drivers/net/wireless/ath/ath11k/mhi.c | 12 +++ + drivers/net/wireless/ath/ath11k/mhi.h | 5 - + drivers/net/wireless/ath/ath11k/pci.c | 44 +++++++++++-- + drivers/net/wireless/ath/ath11k/qmi.c | 2 + 8 files changed, 142 insertions(+), 52 deletions(-) + +--- a/drivers/net/wireless/ath/ath11k/ahb.c ++++ b/drivers/net/wireless/ath/ath11k/ahb.c +@@ -1,7 +1,7 @@ + // SPDX-License-Identifier: BSD-3-Clause-Clear + /* + * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. +- * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. ++ * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + + #include +@@ -413,7 +413,7 @@ static int ath11k_ahb_power_up(struct at + return ret; + } + +-static void ath11k_ahb_power_down(struct ath11k_base *ab) ++static void ath11k_ahb_power_down(struct ath11k_base *ab, bool is_suspend) + { + struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab); + +@@ -1256,7 +1256,7 @@ static void ath11k_ahb_remove(struct pla + struct ath11k_base *ab = platform_get_drvdata(pdev); + + if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags)) { +- ath11k_ahb_power_down(ab); ++ ath11k_ahb_power_down(ab, false); + ath11k_debugfs_soc_destroy(ab); + ath11k_qmi_deinit_service(ab); + goto qmi_fail; +--- a/drivers/net/wireless/ath/ath11k/core.c ++++ b/drivers/net/wireless/ath/ath11k/core.c +@@ -800,12 +800,6 @@ int ath11k_core_suspend(struct ath11k_ba + return ret; + } + +- ret = ath11k_wow_enable(ab); +- if (ret) { +- ath11k_warn(ab, "failed to enable wow during suspend: %d\n", ret); +- return ret; +- } +- + ret = ath11k_dp_rx_pktlog_stop(ab, false); + if (ret) { + ath11k_warn(ab, "failed to stop dp rx pktlog during suspend: %d\n", +@@ -816,24 +810,80 @@ int ath11k_core_suspend(struct ath11k_ba + ath11k_ce_stop_shadow_timers(ab); + ath11k_dp_stop_shadow_timers(ab); + ++ /* PM framework skips suspend_late/resume_early callbacks ++ * if other devices report errors in their suspend callbacks. ++ * However ath11k_core_resume() would still be called because ++ * here we return success thus kernel put us on dpm_suspended_list. ++ * Since we won't go through a power down/up cycle, there is ++ * no chance to call complete(&ab->restart_completed) in ++ * ath11k_core_restart(), making ath11k_core_resume() timeout. ++ * So call it here to avoid this issue. This also works in case ++ * no error happens thus suspend_late/resume_early get called, ++ * because it will be reinitialized in ath11k_core_resume_early(). ++ */ ++ complete(&ab->restart_completed); ++ ++ return 0; ++} ++EXPORT_SYMBOL(ath11k_core_suspend); ++ ++int ath11k_core_suspend_late(struct ath11k_base *ab) ++{ ++ struct ath11k_pdev *pdev; ++ struct ath11k *ar; ++ ++ if (!ab->hw_params.supports_suspend) ++ return -EOPNOTSUPP; ++ ++ /* so far single_pdev_only chips have supports_suspend as true ++ * and only the first pdev is valid. ++ */ ++ pdev = ath11k_core_get_single_pdev(ab); ++ ar = pdev->ar; ++ if (!ar || ar->state != ATH11K_STATE_OFF) ++ return 0; ++ + ath11k_hif_irq_disable(ab); + ath11k_hif_ce_irq_disable(ab); + +- ret = ath11k_hif_suspend(ab); +- if (ret) { +- ath11k_warn(ab, "failed to suspend hif: %d\n", ret); +- return ret; +- } ++ ath11k_hif_power_down(ab, true); + + return 0; + } +-EXPORT_SYMBOL(ath11k_core_suspend); ++EXPORT_SYMBOL(ath11k_core_suspend_late); ++ ++int ath11k_core_resume_early(struct ath11k_base *ab) ++{ ++ int ret; ++ struct ath11k_pdev *pdev; ++ struct ath11k *ar; ++ ++ if (!ab->hw_params.supports_suspend) ++ return -EOPNOTSUPP; ++ ++ /* so far signle_pdev_only chips have supports_suspend as true ++ * and only the first pdev is valid. ++ */ ++ pdev = ath11k_core_get_single_pdev(ab); ++ ar = pdev->ar; ++ if (!ar || ar->state != ATH11K_STATE_OFF) ++ return 0; ++ ++ reinit_completion(&ab->restart_completed); ++ ret = ath11k_hif_power_up(ab); ++ if (ret) ++ ath11k_warn(ab, "failed to power up hif during resume: %d\n", ret); ++ ++ return ret; ++} ++EXPORT_SYMBOL(ath11k_core_resume_early); + + int ath11k_core_resume(struct ath11k_base *ab) + { + int ret; + struct ath11k_pdev *pdev; + struct ath11k *ar; ++ long time_left; + + if (!ab->hw_params.supports_suspend) + return -EOPNOTSUPP; +@@ -846,29 +896,19 @@ int ath11k_core_resume(struct ath11k_bas + if (!ar || ar->state != ATH11K_STATE_OFF) + return 0; + +- ret = ath11k_hif_resume(ab); +- if (ret) { +- ath11k_warn(ab, "failed to resume hif during resume: %d\n", ret); +- return ret; ++ time_left = wait_for_completion_timeout(&ab->restart_completed, ++ ATH11K_RESET_TIMEOUT_HZ); ++ if (time_left == 0) { ++ ath11k_warn(ab, "timeout while waiting for restart complete"); ++ return -ETIMEDOUT; + } + +- ath11k_hif_ce_irq_enable(ab); +- ath11k_hif_irq_enable(ab); +- + ret = ath11k_dp_rx_pktlog_start(ab); +- if (ret) { ++ if (ret) + ath11k_warn(ab, "failed to start rx pktlog during resume: %d\n", + ret); +- return ret; +- } +- +- ret = ath11k_wow_wakeup(ab); +- if (ret) { +- ath11k_warn(ab, "failed to wakeup wow during resume: %d\n", ret); +- return ret; +- } + +- return 0; ++ return ret; + } + EXPORT_SYMBOL(ath11k_core_resume); + +@@ -1966,6 +2006,8 @@ static void ath11k_core_restart(struct w + + if (!ab->is_reset) + ath11k_core_post_reconfigure_recovery(ab); ++ ++ complete(&ab->restart_completed); + } + + static void ath11k_core_reset(struct work_struct *work) +@@ -2035,7 +2077,7 @@ static void ath11k_core_reset(struct wor + ath11k_hif_irq_disable(ab); + ath11k_hif_ce_irq_disable(ab); + +- ath11k_hif_power_down(ab); ++ ath11k_hif_power_down(ab, false); + ath11k_hif_power_up(ab); + + ath11k_dbg(ab, ATH11K_DBG_BOOT, "reset started\n"); +@@ -2108,7 +2150,7 @@ void ath11k_core_deinit(struct ath11k_ba + + mutex_unlock(&ab->core_lock); + +- ath11k_hif_power_down(ab); ++ ath11k_hif_power_down(ab, false); + ath11k_mac_destroy(ab); + ath11k_core_soc_destroy(ab); + ath11k_fw_destroy(ab); +@@ -2161,6 +2203,7 @@ struct ath11k_base *ath11k_core_alloc(st + timer_setup(&ab->rx_replenish_retry, ath11k_ce_rx_replenish_retry, 0); + init_completion(&ab->htc_suspend); + init_completion(&ab->wow.wakeup_completed); ++ init_completion(&ab->restart_completed); + + ab->dev = dev; + ab->hif.bus = bus; +--- a/drivers/net/wireless/ath/ath11k/core.h ++++ b/drivers/net/wireless/ath/ath11k/core.h +@@ -1,7 +1,7 @@ + /* SPDX-License-Identifier: BSD-3-Clause-Clear */ + /* + * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. +- * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved. ++ * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + + #ifndef ATH11K_CORE_H +@@ -991,6 +991,8 @@ struct ath11k_base { + DECLARE_BITMAP(fw_features, ATH11K_FW_FEATURE_COUNT); + } fw; + ++ struct completion restart_completed; ++ + #ifdef CONFIG_NL80211_TESTMODE + struct { + u32 data_pos; +@@ -1190,8 +1192,10 @@ void ath11k_core_free_bdf(struct ath11k_ + int ath11k_core_check_dt(struct ath11k_base *ath11k); + int ath11k_core_check_smbios(struct ath11k_base *ab); + void ath11k_core_halt(struct ath11k *ar); ++int ath11k_core_resume_early(struct ath11k_base *ab); + int ath11k_core_resume(struct ath11k_base *ab); + int ath11k_core_suspend(struct ath11k_base *ab); ++int ath11k_core_suspend_late(struct ath11k_base *ab); + void ath11k_core_pre_reconfigure_recovery(struct ath11k_base *ab); + bool ath11k_core_coldboot_cal_support(struct ath11k_base *ab); + +--- a/drivers/net/wireless/ath/ath11k/hif.h ++++ b/drivers/net/wireless/ath/ath11k/hif.h +@@ -1,7 +1,7 @@ + /* SPDX-License-Identifier: BSD-3-Clause-Clear */ + /* + * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. +- * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. ++ * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + + #ifndef _HIF_H_ +@@ -18,7 +18,7 @@ struct ath11k_hif_ops { + int (*start)(struct ath11k_base *ab); + void (*stop)(struct ath11k_base *ab); + int (*power_up)(struct ath11k_base *ab); +- void (*power_down)(struct ath11k_base *ab); ++ void (*power_down)(struct ath11k_base *ab, bool is_suspend); + int (*suspend)(struct ath11k_base *ab); + int (*resume)(struct ath11k_base *ab); + int (*map_service_to_pipe)(struct ath11k_base *ab, u16 service_id, +@@ -67,12 +67,18 @@ static inline void ath11k_hif_irq_disabl + + static inline int ath11k_hif_power_up(struct ath11k_base *ab) + { ++ if (!ab->hif.ops->power_up) ++ return -EOPNOTSUPP; ++ + return ab->hif.ops->power_up(ab); + } + +-static inline void ath11k_hif_power_down(struct ath11k_base *ab) ++static inline void ath11k_hif_power_down(struct ath11k_base *ab, bool is_suspend) + { +- ab->hif.ops->power_down(ab); ++ if (!ab->hif.ops->power_down) ++ return; ++ ++ ab->hif.ops->power_down(ab, is_suspend); + } + + static inline int ath11k_hif_suspend(struct ath11k_base *ab) +--- a/drivers/net/wireless/ath/ath11k/mhi.c ++++ b/drivers/net/wireless/ath/ath11k/mhi.c +@@ -441,9 +441,17 @@ int ath11k_mhi_start(struct ath11k_pci * + return 0; + } + +-void ath11k_mhi_stop(struct ath11k_pci *ab_pci) ++void ath11k_mhi_stop(struct ath11k_pci *ab_pci, bool is_suspend) + { +- mhi_power_down(ab_pci->mhi_ctrl, true); ++ /* During suspend we need to use mhi_power_down_no_destroy() ++ * workaround, otherwise ath11k_core_resume() will timeout ++ * during resume. ++ */ ++ if (is_suspend) ++ mhi_power_down_no_destroy(ab_pci->mhi_ctrl, true); ++ else ++ mhi_power_down(ab_pci->mhi_ctrl, true); ++ + mhi_unprepare_after_power_down(ab_pci->mhi_ctrl); + } + +--- a/drivers/net/wireless/ath/ath11k/mhi.h ++++ b/drivers/net/wireless/ath/ath11k/mhi.h +@@ -1,7 +1,7 @@ + /* SPDX-License-Identifier: BSD-3-Clause-Clear */ + /* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. +- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. ++ * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + #ifndef _ATH11K_MHI_H + #define _ATH11K_MHI_H +@@ -18,7 +18,7 @@ + #define MHICTRL_RESET_MASK 0x2 + + int ath11k_mhi_start(struct ath11k_pci *ar_pci); +-void ath11k_mhi_stop(struct ath11k_pci *ar_pci); ++void ath11k_mhi_stop(struct ath11k_pci *ar_pci, bool is_suspend); + int ath11k_mhi_register(struct ath11k_pci *ar_pci); + void ath11k_mhi_unregister(struct ath11k_pci *ar_pci); + void ath11k_mhi_set_mhictrl_reset(struct ath11k_base *ab); +@@ -26,5 +26,4 @@ void ath11k_mhi_clear_vector(struct ath1 + + int ath11k_mhi_suspend(struct ath11k_pci *ar_pci); + int ath11k_mhi_resume(struct ath11k_pci *ar_pci); +- + #endif +--- a/drivers/net/wireless/ath/ath11k/pci.c ++++ b/drivers/net/wireless/ath/ath11k/pci.c +@@ -625,7 +625,7 @@ static int ath11k_pci_power_up(struct at + return 0; + } + +-static void ath11k_pci_power_down(struct ath11k_base *ab) ++static void ath11k_pci_power_down(struct ath11k_base *ab, bool is_suspend) + { + struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); + +@@ -636,7 +636,7 @@ static void ath11k_pci_power_down(struct + + ath11k_pci_msi_disable(ab_pci); + +- ath11k_mhi_stop(ab_pci); ++ ath11k_mhi_stop(ab_pci, is_suspend); + clear_bit(ATH11K_FLAG_DEVICE_INIT_DONE, &ab->dev_flags); + ath11k_pci_sw_reset(ab_pci->ab, false); + } +@@ -938,7 +938,7 @@ static void ath11k_pci_remove(struct pci + ath11k_pci_set_irq_affinity_hint(ab_pci, NULL); + + if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags)) { +- ath11k_pci_power_down(ab); ++ ath11k_pci_power_down(ab, false); + ath11k_debugfs_soc_destroy(ab); + ath11k_qmi_deinit_service(ab); + goto qmi_fail; +@@ -966,7 +966,7 @@ static void ath11k_pci_shutdown(struct p + struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); + + ath11k_pci_set_irq_affinity_hint(ab_pci, NULL); +- ath11k_pci_power_down(ab); ++ ath11k_pci_power_down(ab, false); + } + + static __maybe_unused int ath11k_pci_pm_suspend(struct device *dev) +@@ -1003,9 +1003,39 @@ static __maybe_unused int ath11k_pci_pm_ + return ret; + } + +-static SIMPLE_DEV_PM_OPS(ath11k_pci_pm_ops, +- ath11k_pci_pm_suspend, +- ath11k_pci_pm_resume); ++static __maybe_unused int ath11k_pci_pm_suspend_late(struct device *dev) ++{ ++ struct ath11k_base *ab = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = ath11k_core_suspend_late(ab); ++ if (ret) ++ ath11k_warn(ab, "failed to late suspend core: %d\n", ret); ++ ++ /* Similar to ath11k_pci_pm_suspend(), we return success here ++ * even error happens, to allow system suspend/hibernation survive. ++ */ ++ return 0; ++} ++ ++static __maybe_unused int ath11k_pci_pm_resume_early(struct device *dev) ++{ ++ struct ath11k_base *ab = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = ath11k_core_resume_early(ab); ++ if (ret) ++ ath11k_warn(ab, "failed to early resume core: %d\n", ret); ++ ++ return ret; ++} ++ ++static const struct dev_pm_ops __maybe_unused ath11k_pci_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(ath11k_pci_pm_suspend, ++ ath11k_pci_pm_resume) ++ SET_LATE_SYSTEM_SLEEP_PM_OPS(ath11k_pci_pm_suspend_late, ++ ath11k_pci_pm_resume_early) ++}; + + static struct pci_driver ath11k_pci_driver = { + .name = "ath11k_pci", +--- a/drivers/net/wireless/ath/ath11k/qmi.c ++++ b/drivers/net/wireless/ath/ath11k/qmi.c +@@ -2877,7 +2877,7 @@ int ath11k_qmi_fwreset_from_cold_boot(st + } + + /* reset the firmware */ +- ath11k_hif_power_down(ab); ++ ath11k_hif_power_down(ab, false); + ath11k_hif_power_up(ab); + ath11k_dbg(ab, ATH11K_DBG_QMI, "exit wait for cold boot done\n"); + return 0; diff --git a/series.conf b/series.conf index c87c0f1..aa6ce2b 100644 --- a/series.conf +++ b/series.conf @@ -197,6 +197,7 @@ patches.suse/wifi-ath11k-thermal-don-t-try-to-register-multiple-t.patch patches.suse/bus-mhi-host-add-mhi_power_down_no_destroy.patch patches.suse/net-qrtr-support-suspend-hibernation.patch + patches.suse/wifi-ath11k-support-hibernation.patch ######################################################## # USB