ath11k: implement suspend for QCA6390 PCI devices
authorCarl Huang <cjhuang@codeaurora.org>
Fri, 11 Dec 2020 17:35:50 +0000 (19:35 +0200)
committerKalle Valo <kvalo@codeaurora.org>
Sat, 12 Dec 2020 04:41:44 +0000 (06:41 +0200)
Now that all the needed pieces are in place implement suspend support QCA6390
PCI devices. All other devices will return -EOPNOTSUPP during suspend. The
suspend is implemented by switching the firmware to WoW mode during suspend, so
the firmware will be running on low power mode while host is in suspend.

At the moment we are not able to shutdown and fully power off the device due to
bugs in MHI subsystem, so WoW mode is a workaround for the time being.

During suspend we enable WoW mode, disable CE irq and DP irq, then put MHI to
suspend state.  During resume, driver resumes MHI firstly, then enables CE irq
and dp IRQ, and sends WoW wakeup command to firmware.

Tested-on: QCA6390 hw2.0 PCI WLAN.HST.1.0.1-01740-QCAHSTSWPLZ_V2_TO_X86-1

Signed-off-by: Carl Huang <cjhuang@codeaurora.org>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
Link: https://lore.kernel.org/r/1607708150-21066-11-git-send-email-kvalo@codeaurora.org
drivers/net/wireless/ath/ath11k/ce.c
drivers/net/wireless/ath/ath11k/ce.h
drivers/net/wireless/ath/ath11k/core.c
drivers/net/wireless/ath/ath11k/core.h
drivers/net/wireless/ath/ath11k/dp.c
drivers/net/wireless/ath/ath11k/dp.h
drivers/net/wireless/ath/ath11k/hw.h
drivers/net/wireless/ath/ath11k/pci.c

index 9d730f8ac81614e4dd02bc6936e9daeca1aa5f1c..987c650102720220034f750a9297e6091348fbe1 100644 (file)
@@ -195,7 +195,7 @@ static bool ath11k_ce_need_shadow_fix(int ce_id)
        return false;
 }
 
-static void ath11k_ce_stop_shadow_timers(struct ath11k_base *ab)
+void ath11k_ce_stop_shadow_timers(struct ath11k_base *ab)
 {
        int i;
 
index 269b599ac0b094506927e4b4efe4e77d9d3cc930..d6eeef919349c0de2c54fdbf4d37eb027030e007 100644 (file)
@@ -190,4 +190,6 @@ int ath11k_ce_map_service_to_pipe(struct ath11k_base *ab, u16 service_id,
 int ath11k_ce_attr_attach(struct ath11k_base *ab);
 void ath11k_ce_get_shadow_config(struct ath11k_base *ab,
                                 u32 **shadow_cfg, u32 *shadow_cfg_len);
+void ath11k_ce_stop_shadow_timers(struct ath11k_base *ab);
+
 #endif
index 451748fa8fff850970189c90eaecbce44ef7f18f..b97c38b9a270135c5e4f25d84ea781885bc918c1 100644 (file)
@@ -13,6 +13,7 @@
 #include "dp_rx.h"
 #include "debug.h"
 #include "hif.h"
+#include "wow.h"
 
 unsigned int ath11k_debug_mask;
 EXPORT_SYMBOL(ath11k_debug_mask);
@@ -66,6 +67,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_shadow_regs = false,
                .idle_ps = false,
                .cold_boot_calib = true,
+               .supports_suspend = false,
        },
        {
                .hw_rev = ATH11K_HW_IPQ6018_HW10,
@@ -103,6 +105,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_shadow_regs = false,
                .idle_ps = false,
                .cold_boot_calib = true,
+               .supports_suspend = false,
        },
        {
                .name = "qca6390 hw2.0",
@@ -139,9 +142,91 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_shadow_regs = true,
                .idle_ps = true,
                .cold_boot_calib = false,
+               .supports_suspend = true,
        },
 };
 
+int ath11k_core_suspend(struct ath11k_base *ab)
+{
+       int ret;
+
+       if (!ab->hw_params.supports_suspend)
+               return -EOPNOTSUPP;
+
+       /* TODO: there can frames in queues so for now add delay as a hack.
+        * Need to implement to handle and remove this delay.
+        */
+       msleep(500);
+
+       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;
+       }
+
+       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",
+                           ret);
+               return ret;
+       }
+
+       ath11k_ce_stop_shadow_timers(ab);
+       ath11k_dp_stop_shadow_timers(ab);
+
+       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;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(ath11k_core_suspend);
+
+int ath11k_core_resume(struct ath11k_base *ab)
+{
+       int ret;
+
+       if (!ab->hw_params.supports_suspend)
+               return -EOPNOTSUPP;
+
+       ret = ath11k_hif_resume(ab);
+       if (ret) {
+               ath11k_warn(ab, "failed to resume hif during resume: %d\n", ret);
+               return ret;
+       }
+
+       ath11k_hif_ce_irq_enable(ab);
+       ath11k_hif_irq_enable(ab);
+
+       ret = ath11k_dp_rx_pktlog_start(ab);
+       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;
+}
+EXPORT_SYMBOL(ath11k_core_resume);
+
 int ath11k_core_check_dt(struct ath11k_base *ab)
 {
        size_t max_len = sizeof(ab->qmi.target.bdf_ext);
index ba8f2222169d39a214d4366693b4975555e84644..799bf3de11177519313860877d4602db97f6729d 100644 (file)
@@ -897,6 +897,8 @@ void ath11k_core_free_bdf(struct ath11k_base *ab, struct ath11k_board_data *bd);
 int ath11k_core_check_dt(struct ath11k_base *ath11k);
 
 void ath11k_core_halt(struct ath11k *ar);
+int ath11k_core_resume(struct ath11k_base *ab);
+int ath11k_core_suspend(struct ath11k_base *ab);
 
 const struct firmware *ath11k_core_firmware_request(struct ath11k_base *ab,
                                                    const char *filename);
index f977056ae5e8d7a213a247ffd2dc38f8c9e0c175..04f6c4e0658b7a2eb4f78476cd2a31d08384c0d1 100644 (file)
@@ -304,7 +304,7 @@ int ath11k_dp_srng_setup(struct ath11k_base *ab, struct dp_srng *ring,
        return 0;
 }
 
-static void ath11k_dp_stop_shadow_timers(struct ath11k_base *ab)
+void ath11k_dp_stop_shadow_timers(struct ath11k_base *ab)
 {
        int i;
 
index 1d9e2d6de3aec84823f062d4bb6d5d685b78a34e..ee768ccce46e1e8e6c59ffda58124872ece379a6 100644 (file)
@@ -1641,5 +1641,6 @@ void ath11k_dp_shadow_stop_timer(struct ath11k_base *ab,
 void ath11k_dp_shadow_init_timer(struct ath11k_base *ab,
                                 struct ath11k_hp_update_timer *update_timer,
                                 u32 interval, u32 ring_id);
+void ath11k_dp_stop_shadow_timers(struct ath11k_base *ab);
 
 #endif
index f33a458a465f1be045c014940baf3e581af79b72..8af0034fdb05e840e945de13a3d6c75a536a4bf6 100644 (file)
@@ -156,6 +156,7 @@ struct ath11k_hw_params {
        bool supports_shadow_regs;
        bool idle_ps;
        bool cold_boot_calib;
+       bool supports_suspend;
 };
 
 struct ath11k_hw_ops {
index e720ac6354fcb41bdf52bd89fe79ed72deb46129..857647aa57c8a7a66c41eaafa430667d062bc88e 100644 (file)
@@ -1204,12 +1204,43 @@ static void ath11k_pci_shutdown(struct pci_dev *pdev)
        ath11k_pci_power_down(ab);
 }
 
+static __maybe_unused int ath11k_pci_pm_suspend(struct device *dev)
+{
+       struct ath11k_base *ab = dev_get_drvdata(dev);
+       int ret;
+
+       ret = ath11k_core_suspend(ab);
+       if (ret)
+               ath11k_warn(ab, "failed to suspend core: %d\n", ret);
+
+       return ret;
+}
+
+static __maybe_unused int ath11k_pci_pm_resume(struct device *dev)
+{
+       struct ath11k_base *ab = dev_get_drvdata(dev);
+       int ret;
+
+       ret = ath11k_core_resume(ab);
+       if (ret)
+               ath11k_warn(ab, "failed to resume core: %d\n", ret);
+
+       return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(ath11k_pci_pm_ops,
+                        ath11k_pci_pm_suspend,
+                        ath11k_pci_pm_resume);
+
 static struct pci_driver ath11k_pci_driver = {
        .name = "ath11k_pci",
        .id_table = ath11k_pci_id_table,
        .probe = ath11k_pci_probe,
        .remove = ath11k_pci_remove,
        .shutdown = ath11k_pci_shutdown,
+#ifdef CONFIG_PM
+       .driver.pm = &ath11k_pci_pm_ops,
+#endif
 };
 
 static int ath11k_pci_init(void)