Message ID | 20240228022243.17762-4-quic_bqiang@quicinc.com (mailing list archive) |
---|---|
State | Superseded |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | wifi: ath11k: hibernation support | expand |
Context | Check | Description |
---|---|---|
netdev/tree_selection | success | Guessing tree name failed - patch did not apply |
On 2/27/2024 6:22 PM, Baochen Qiang wrote: > 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 <tiwai@suse.de> > Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com> > Signed-off-by: Baochen Qiang <quic_bqiang@quicinc.com> > --- > 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(-) ...snip... > +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 nit: s/signle/single/ > + * 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; > @@ -940,29 +990,19 @@ int ath11k_core_resume(struct ath11k_base *ab) > 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); these are disabled in suspend_late() do you need to enable in resume_early()? or are they expected to be enabled via ath11k_wow_op_resume()? and if that is the case, why isn't the disable in ath11k_wow_op_suspend() sufficient? can the disables in suspend_late() be removed? just concerned about the lack of symmetry here /jeff
On 2/28/2024 11:31 PM, Jeff Johnson wrote: > On 2/27/2024 6:22 PM, Baochen Qiang wrote: >> 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 <tiwai@suse.de> >> Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com> >> Signed-off-by: Baochen Qiang <quic_bqiang@quicinc.com> >> --- >> 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(-) > ...snip... >> +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 > > nit: s/signle/single/ > >> + * 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; >> @@ -940,29 +990,19 @@ int ath11k_core_resume(struct ath11k_base *ab) >> 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); > > these are disabled in suspend_late() > do you need to enable in resume_early()? > or are they expected to be enabled via ath11k_wow_op_resume()? > > and if that is the case, why isn't the disable in > ath11k_wow_op_suspend() sufficient? can the disables in suspend_late() > be removed? There are two user cases here: 1. if WoWLAN is enabled, IRQ enable/disable only happens in ath11k_wow_op_suspend()/resume(), ath11k_core_suspend()/late_suspend() and ath11k_core_resume()/early_resume() do nothing but return directly due to below check: if (!ar || ar->state != ATH11K_STATE_OFF) return 0; so this is symmetric and no issues here. 2. if WoWLAN is disabled, ath11k_wow_op_suspend()/resume() won't get called, see the check on 'local->wowlan' in __ieee80211_suspend(). Then IRQ is disabled in ath11k_core_suspend_late(). The reason why IRQ is not enabled in ath11k_core_resume()/early_resume() is because in this case we power down/up firmware, and during power up we go the reset path where IRQ would be enabled back in below calls: CE irqs: ath11k_core_qmi_firmware_ready() -> ath11k_core_start() -> ath11k_hif_start() -> ath11k_pci_start() -> ath11k_pcic_start() -> ath11k_pcic_ce_irqs_enable() DP irqs: ath11k_core_qmi_firmware_ready() -> ath11k_hif_irq_enable() > > just concerned about the lack of symmetry here Yes, also noticed it but didn't figure out a better way. > > /jeff
diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 7c0a23517949..60b4c2800a33 100644 --- 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 <linux/module.h> @@ -413,7 +413,7 @@ static int ath11k_ahb_power_up(struct ath11k_base *ab) 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 platform_device *pdev) 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; diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index c78bce19bd75..8533bf3174d2 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -894,12 +894,6 @@ int ath11k_core_suspend(struct ath11k_base *ab) 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", @@ -910,24 +904,80 @@ int ath11k_core_suspend(struct ath11k_base *ab) 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; @@ -940,29 +990,19 @@ int ath11k_core_resume(struct ath11k_base *ab) 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); @@ -2060,6 +2100,8 @@ static void ath11k_core_restart(struct work_struct *work) if (!ab->is_reset) ath11k_core_post_reconfigure_recovery(ab); + + complete(&ab->restart_completed); } static void ath11k_core_reset(struct work_struct *work) @@ -2129,7 +2171,7 @@ static void ath11k_core_reset(struct work_struct *work) 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"); @@ -2202,7 +2244,7 @@ void ath11k_core_deinit(struct ath11k_base *ab) 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); @@ -2255,6 +2297,7 @@ struct ath11k_base *ath11k_core_alloc(struct device *dev, size_t priv_size, 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; diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h index b3fb74a226fb..a20c29e3a227 100644 --- 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 @@ -1033,6 +1033,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; @@ -1232,8 +1234,10 @@ void ath11k_core_free_bdf(struct ath11k_base *ab, struct ath11k_board_data *bd); 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); diff --git a/drivers/net/wireless/ath/ath11k/hif.h b/drivers/net/wireless/ath/ath11k/hif.h index 877a4073fed6..c4c6cc09c7c1 100644 --- 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_disable(struct ath11k_base *ab) 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) diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c index 3de7fa6f88d0..c7ff9a9937aa 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -442,9 +442,17 @@ int ath11k_mhi_start(struct ath11k_pci *ab_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_keep_dev() + * workaround, otherwise ath11k_core_resume() will timeout + * during resume. + */ + if (is_suspend) + mhi_power_down_keep_dev(ab_pci->mhi_ctrl, true); + else + mhi_power_down(ab_pci->mhi_ctrl, true); + mhi_unprepare_after_power_down(ab_pci->mhi_ctrl); } diff --git a/drivers/net/wireless/ath/ath11k/mhi.h b/drivers/net/wireless/ath/ath11k/mhi.h index f81fba2644a4..2d567705e732 100644 --- 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 ath11k_base *ab); int ath11k_mhi_suspend(struct ath11k_pci *ar_pci); int ath11k_mhi_resume(struct ath11k_pci *ar_pci); - #endif diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c index be9d2c69cc41..8d63b84d1261 100644 --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -638,7 +638,7 @@ static int ath11k_pci_power_up(struct ath11k_base *ab) 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); @@ -649,7 +649,7 @@ static void ath11k_pci_power_down(struct ath11k_base *ab) 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); } @@ -970,7 +970,7 @@ static void ath11k_pci_remove(struct pci_dev *pdev) 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; @@ -998,7 +998,7 @@ static void ath11k_pci_shutdown(struct pci_dev *pdev) 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) @@ -1035,9 +1035,39 @@ static __maybe_unused int ath11k_pci_pm_resume(struct device *dev) 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", diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index 5006f81f779b..d4a243b64f6c 100644 --- 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(struct ath11k_base *ab) } /* 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;