Commit 88fd03cf authored by Baochen Qiang's avatar Baochen Qiang Committed by Jeff Johnson
Browse files

wifi: ath11k: support non-WoWLAN mode suspend as well

Previously commit 166a490f ("wifi: ath11k: support hibernation") was
reverted due to [1], so currently we only support WoWLAN mode suspend.
This works well in scenarios where WLAN power is sustained during suspend,
however breaks in those where power is cut off.

This change basically brings the reverted commit back, but differs in that
we decide based on the PM policy to choose WoWLAN mode suspend or the
non-WoWLAN mode. As stated in the previous patch for now the PM policy is
determined based on machine models. That said we will choose WoWLAN mode
suspend if we are running on machines listed in ath11k_pm_quirk_table,
otherwise we choose the other one.

[1] https://bugzilla.kernel.org/show_bug.cgi?id=219196



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

Tested-by: default avatarMuhammad Usama Anjum <usama.anjum@collabora.com>
Tested-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarBaochen Qiang <quic_bqiang@quicinc.com>
Link: https://patch.msgid.link/20250328-ath11k-bring-hibernation-back-v3-4-23405ae23431@quicinc.com


Signed-off-by: default avatarJeff Johnson <jeff.johnson@oss.qualcomm.com>
parent 662cc5b9
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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);

@@ -1280,7 +1280,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;
+119 −4
Original line number Diff line number Diff line
@@ -1082,6 +1082,52 @@ static int ath11k_core_suspend_wow(struct ath11k_base *ab)
	return 0;
}

static int ath11k_core_suspend_default(struct ath11k_base *ab)
{
	int ret;

	ret = ath11k_dp_rx_pktlog_stop(ab, true);
	if (ret) {
		ath11k_warn(ab, "failed to stop dp rx (and timer) pktlog during suspend: %d\n",
			    ret);
		return ret;
	}

	/* So far only single_pdev_only devices can reach here,
	 * so it is valid to handle the first, and the only, pdev.
	 */
	ret = ath11k_mac_wait_tx_complete(ab->pdevs[0].ar);
	if (ret) {
		ath11k_warn(ab, "failed to wait tx complete: %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",
			    ret);
		return ret;
	}

	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;
}

int ath11k_core_suspend(struct ath11k_base *ab)
{
	int ret;
@@ -1090,10 +1136,73 @@ int ath11k_core_suspend(struct ath11k_base *ab)
	if (ret <= 0)
		return ret;

	if (ab->pm_policy == ATH11K_PM_WOW)
		return ath11k_core_suspend_wow(ab);

	return ath11k_core_suspend_default(ab);
}
EXPORT_SYMBOL(ath11k_core_suspend);

int ath11k_core_suspend_late(struct ath11k_base *ab)
{
	int ret;

	ret = ath11k_core_continue_suspend_resume(ab);
	if (ret <= 0)
		return ret;

	if (ab->pm_policy == ATH11K_PM_WOW)
		return 0;

	ath11k_hif_irq_disable(ab);
	ath11k_hif_ce_irq_disable(ab);

	ath11k_hif_power_down(ab, true);

	return 0;
}
EXPORT_SYMBOL(ath11k_core_suspend_late);

int ath11k_core_resume_early(struct ath11k_base *ab)
{
	int ret;

	ret = ath11k_core_continue_suspend_resume(ab);
	if (ret <= 0)
		return ret;

	if (ab->pm_policy == ATH11K_PM_WOW)
		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);

static int ath11k_core_resume_default(struct ath11k_base *ab)
{
	long time_left;
	int 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;
	}

	ret = ath11k_dp_rx_pktlog_start(ab);
	if (ret)
		ath11k_warn(ab, "failed to start rx pktlog during resume: %d\n",
			    ret);

	return ret;
}

static int ath11k_core_resume_wow(struct ath11k_base *ab)
{
	int ret;
@@ -1131,7 +1240,10 @@ int ath11k_core_resume(struct ath11k_base *ab)
	if (ret <= 0)
		return ret;

	if (ab->pm_policy == ATH11K_PM_WOW)
		return ath11k_core_resume_wow(ab);

	return ath11k_core_resume_default(ab);
}
EXPORT_SYMBOL(ath11k_core_resume);

@@ -2266,6 +2378,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)
@@ -2336,7 +2450,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");
@@ -2418,7 +2532,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);
}
@@ -2471,6 +2585,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;
+4 −0
Original line number Diff line number Diff line
@@ -1055,6 +1055,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;
@@ -1256,8 +1258,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);

+10 −4
Original line number Diff line number Diff line
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
 * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 * Copyright (c) 2022-2025 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,
@@ -68,12 +68,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)
+11 −3
Original line number Diff line number Diff line
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
 * Copyright (c) 2020 The Linux Foundation. All rights reserved.
 * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include <linux/msi.h>
@@ -454,9 +454,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)
{
	/* 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);
}

Loading