wifi: ath11k: Add WoW support for WCN6750
authorManikanta Pubbisetty <quic_mpubbise@quicinc.com>
Mon, 19 Sep 2022 12:47:14 +0000 (15:47 +0300)
committerKalle Valo <quic_kvalo@quicinc.com>
Mon, 19 Sep 2022 13:03:34 +0000 (16:03 +0300)
Add support for WoW on WCN6750 chipset.

Unlike other chips where WoW exit happens after sending WoW wakeup
WMI command, exit from WoW suspend in the case of WCN6750 happens
upon sending a WoW exit SMP2P (Shared memory point to point) message
to the firmware.

Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.1.0.1-00887-QCAMSLSWPLZ-1

Signed-off-by: Manikanta Pubbisetty <quic_mpubbise@quicinc.com>
Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com>
Link: https://lore.kernel.org/r/20220902112520.24804-3-quic_mpubbise@quicinc.com
drivers/net/wireless/ath/ath11k/ahb.c
drivers/net/wireless/ath/ath11k/ahb.h
drivers/net/wireless/ath/ath11k/core.c
drivers/net/wireless/ath/ath11k/hw.h
drivers/net/wireless/ath/ath11k/pcic.c
drivers/net/wireless/ath/ath11k/pcic.h
drivers/net/wireless/ath/ath11k/wow.c

index 2bf709b0b12a24635c71c93326cee187318bd87b..84bf679da9ebeb453c6c2e1bf8bff180509849df 100644 (file)
@@ -16,6 +16,8 @@
 #include "hif.h"
 #include <linux/remoteproc.h>
 #include "pcic.h"
+#include <linux/soc/qcom/smem.h>
+#include <linux/soc/qcom/smem_state.h>
 
 static const struct of_device_id ath11k_ahb_of_match[] = {
        /* TODO: Should we change the compatible string to something similar
@@ -687,6 +689,84 @@ static int ath11k_ahb_map_service_to_pipe(struct ath11k_base *ab, u16 service_id
        return 0;
 }
 
+static int ath11k_ahb_hif_suspend(struct ath11k_base *ab)
+{
+       struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
+       u32 wake_irq;
+       u32 value = 0;
+       int ret;
+
+       if (!device_may_wakeup(ab->dev))
+               return -EPERM;
+
+       wake_irq = ab->irq_num[ATH11K_PCI_IRQ_CE0_OFFSET + ATH11K_PCI_CE_WAKE_IRQ];
+
+       ret = enable_irq_wake(wake_irq);
+       if (ret) {
+               ath11k_err(ab, "failed to enable wakeup irq :%d\n", ret);
+               return ret;
+       }
+
+       value = u32_encode_bits(ab_ahb->smp2p_info.seq_no++,
+                               ATH11K_AHB_SMP2P_SMEM_SEQ_NO);
+       value |= u32_encode_bits(ATH11K_AHB_POWER_SAVE_ENTER,
+                                ATH11K_AHB_SMP2P_SMEM_MSG);
+
+       ret = qcom_smem_state_update_bits(ab_ahb->smp2p_info.smem_state,
+                                         ATH11K_AHB_SMP2P_SMEM_VALUE_MASK, value);
+       if (ret) {
+               ath11k_err(ab, "failed to send smp2p power save enter cmd :%d\n", ret);
+               return ret;
+       }
+
+       ath11k_dbg(ab, ATH11K_DBG_AHB, "ahb device suspended\n");
+
+       return ret;
+}
+
+static int ath11k_ahb_hif_resume(struct ath11k_base *ab)
+{
+       struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
+       u32 wake_irq;
+       u32 value = 0;
+       int ret;
+
+       if (!device_may_wakeup(ab->dev))
+               return -EPERM;
+
+       wake_irq = ab->irq_num[ATH11K_PCI_IRQ_CE0_OFFSET + ATH11K_PCI_CE_WAKE_IRQ];
+
+       ret = disable_irq_wake(wake_irq);
+       if (ret) {
+               ath11k_err(ab, "failed to disable wakeup irq: %d\n", ret);
+               return ret;
+       }
+
+       reinit_completion(&ab->wow.wakeup_completed);
+
+       value = u32_encode_bits(ab_ahb->smp2p_info.seq_no++,
+                               ATH11K_AHB_SMP2P_SMEM_SEQ_NO);
+       value |= u32_encode_bits(ATH11K_AHB_POWER_SAVE_EXIT,
+                                ATH11K_AHB_SMP2P_SMEM_MSG);
+
+       ret = qcom_smem_state_update_bits(ab_ahb->smp2p_info.smem_state,
+                                         ATH11K_AHB_SMP2P_SMEM_VALUE_MASK, value);
+       if (ret) {
+               ath11k_err(ab, "failed to send smp2p power save enter cmd :%d\n", ret);
+               return ret;
+       }
+
+       ret = wait_for_completion_timeout(&ab->wow.wakeup_completed, 3 * HZ);
+       if (ret == 0) {
+               ath11k_warn(ab, "timed out while waiting for wow wakeup completion\n");
+               return -ETIMEDOUT;
+       }
+
+       ath11k_dbg(ab, ATH11K_DBG_AHB, "ahb device resumed\n");
+
+       return 0;
+}
+
 static const struct ath11k_hif_ops ath11k_ahb_hif_ops_ipq8074 = {
        .start = ath11k_ahb_start,
        .stop = ath11k_ahb_stop,
@@ -713,6 +793,10 @@ static const struct ath11k_hif_ops ath11k_ahb_hif_ops_wcn6750 = {
        .map_service_to_pipe = ath11k_pcic_map_service_to_pipe,
        .power_down = ath11k_ahb_power_down,
        .power_up = ath11k_ahb_power_up,
+       .suspend = ath11k_ahb_hif_suspend,
+       .resume = ath11k_ahb_hif_resume,
+       .ce_irq_enable = ath11k_pci_enable_ce_irqs_except_wake_irq,
+       .ce_irq_disable = ath11k_pci_disable_ce_irqs_except_wake_irq,
 };
 
 static int ath11k_core_get_rproc(struct ath11k_base *ab)
@@ -787,6 +871,34 @@ static int ath11k_ahb_setup_msi_resources(struct ath11k_base *ab)
        return 0;
 }
 
+static int ath11k_ahb_setup_smp2p_handle(struct ath11k_base *ab)
+{
+       struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
+
+       if (!ab->hw_params.smp2p_wow_exit)
+               return 0;
+
+       ab_ahb->smp2p_info.smem_state = qcom_smem_state_get(ab->dev, "wlan-smp2p-out",
+                                                           &ab_ahb->smp2p_info.smem_bit);
+       if (IS_ERR(ab_ahb->smp2p_info.smem_state)) {
+               ath11k_err(ab, "failed to fetch smem state: %ld\n",
+                          PTR_ERR(ab_ahb->smp2p_info.smem_state));
+               return PTR_ERR(ab_ahb->smp2p_info.smem_state);
+       }
+
+       return 0;
+}
+
+static void ath11k_ahb_release_smp2p_handle(struct ath11k_base *ab)
+{
+       struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
+
+       if (!ab->hw_params.smp2p_wow_exit)
+               return;
+
+       qcom_smem_state_put(ab_ahb->smp2p_info.smem_state);
+}
+
 static int ath11k_ahb_setup_resources(struct ath11k_base *ab)
 {
        struct platform_device *pdev = ab->pdev;
@@ -1042,10 +1154,14 @@ static int ath11k_ahb_probe(struct platform_device *pdev)
        if (ret)
                goto err_core_free;
 
-       ret = ath11k_hal_srng_init(ab);
+       ret = ath11k_ahb_setup_smp2p_handle(ab);
        if (ret)
                goto err_fw_deinit;
 
+       ret = ath11k_hal_srng_init(ab);
+       if (ret)
+               goto err_release_smp2p_handle;
+
        ret = ath11k_ce_alloc_pipes(ab);
        if (ret) {
                ath11k_err(ab, "failed to allocate ce pipes: %d\n", ret);
@@ -1082,6 +1198,9 @@ err_ce_free:
 err_hal_srng_deinit:
        ath11k_hal_srng_deinit(ab);
 
+err_release_smp2p_handle:
+       ath11k_ahb_release_smp2p_handle(ab);
+
 err_fw_deinit:
        ath11k_ahb_fw_resource_deinit(ab);
 
@@ -1114,6 +1233,7 @@ static void ath11k_ahb_free_resources(struct ath11k_base *ab)
 
        ath11k_ahb_free_irq(ab);
        ath11k_hal_srng_deinit(ab);
+       ath11k_ahb_release_smp2p_handle(ab);
        ath11k_ahb_fw_resource_deinit(ab);
        ath11k_ce_free_pipes(ab);
        ath11k_core_free(ab);
index 58a945411c5b3cc6ff555f4db35bf43b890ec8c4..415ddfd2665497bb80e772b7ffce1c71f1fd8763 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: BSD-3-Clause-Clear */
 /*
  * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
  */
 #ifndef ATH11K_AHB_H
 #define ATH11K_AHB_H
@@ -8,6 +9,16 @@
 #include "core.h"
 
 #define ATH11K_AHB_RECOVERY_TIMEOUT (3 * HZ)
+
+#define ATH11K_AHB_SMP2P_SMEM_MSG              GENMASK(15, 0)
+#define ATH11K_AHB_SMP2P_SMEM_SEQ_NO           GENMASK(31, 16)
+#define ATH11K_AHB_SMP2P_SMEM_VALUE_MASK       0xFFFFFFFF
+
+enum ath11k_ahb_smp2p_msg_id {
+       ATH11K_AHB_POWER_SAVE_ENTER = 1,
+       ATH11K_AHB_POWER_SAVE_EXIT,
+};
+
 struct ath11k_base;
 
 struct ath11k_ahb {
@@ -21,6 +32,11 @@ struct ath11k_ahb {
                u32 ce_size;
                bool use_tz;
        } fw;
+       struct {
+               unsigned short seq_no;
+               unsigned int smem_bit;
+               struct qcom_smem_state *smem_state;
+       } smp2p_info;
 };
 
 static inline struct ath11k_ahb *ath11k_ahb_priv(struct ath11k_base *ab)
index 54848b1efccb2a985b2adbd65039cbfdc0d1423f..d71043ae4fbf33ffcb1c6db096f66782860a4653 100644 (file)
@@ -113,6 +113,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .hw_rev = ATH11K_HW_IPQ6018_HW10,
@@ -191,6 +192,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "qca6390 hw2.0",
@@ -271,6 +273,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "qcn9074 hw1.0",
@@ -348,6 +351,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "wcn6855 hw2.0",
@@ -428,6 +432,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "wcn6855 hw2.1",
@@ -507,6 +512,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "wcn6750 hw1.0",
@@ -583,6 +589,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 
                .tcl_ring_retry = false,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE_WCN6750,
+               .smp2p_wow_exit = true,
        },
 };
 
index 90cf402fd5d9029c788d099d7d5a0fbf886c8291..f4a30835a5f68d4e2b5f340808e30b55105d0e6d 100644 (file)
@@ -217,6 +217,7 @@ struct ath11k_hw_params {
 
        bool tcl_ring_retry;
        u32 tx_ring_size;
+       bool smp2p_wow_exit;
 };
 
 struct ath11k_hw_ops {
index 74ed99af833b1eaf74405e2d583e50222eddbec6..3fa1958f8c8238b82417c70bf237b27d154746c4 100644 (file)
@@ -777,3 +777,37 @@ int ath11k_pcic_register_pci_ops(struct ath11k_base *ab,
        return 0;
 }
 EXPORT_SYMBOL(ath11k_pcic_register_pci_ops);
+
+void ath11k_pci_enable_ce_irqs_except_wake_irq(struct ath11k_base *ab)
+{
+       int i;
+
+       for (i = 0; i < ab->hw_params.ce_count; i++) {
+               if (ath11k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR ||
+                   i == ATH11K_PCI_CE_WAKE_IRQ)
+                       continue;
+               ath11k_pcic_ce_irq_enable(ab, i);
+       }
+}
+EXPORT_SYMBOL(ath11k_pci_enable_ce_irqs_except_wake_irq);
+
+void ath11k_pci_disable_ce_irqs_except_wake_irq(struct ath11k_base *ab)
+{
+       int i;
+       int irq_idx;
+       struct ath11k_ce_pipe *ce_pipe;
+
+       for (i = 0; i < ab->hw_params.ce_count; i++) {
+               ce_pipe = &ab->ce.ce_pipe[i];
+               irq_idx = ATH11K_PCI_IRQ_CE0_OFFSET + i;
+
+               if (ath11k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR ||
+                   i == ATH11K_PCI_CE_WAKE_IRQ)
+                       continue;
+
+               disable_irq_nosync(ab->irq_num[irq_idx]);
+               synchronize_irq(ab->irq_num[irq_idx]);
+               tasklet_kill(&ce_pipe->intr_tq);
+       }
+}
+EXPORT_SYMBOL(ath11k_pci_disable_ce_irqs_except_wake_irq);
index f3506611aa20adcb72b3d5a7a748d626a92072a7..ac012e88bf6d4f65be3116196f010719787135eb 100644 (file)
@@ -12,6 +12,8 @@
 #define ATH11K_PCI_IRQ_CE0_OFFSET      3
 #define ATH11K_PCI_IRQ_DP_OFFSET       14
 
+#define ATH11K_PCI_CE_WAKE_IRQ 2
+
 #define ATH11K_PCI_WINDOW_ENABLE_BIT           0x40000000
 #define ATH11K_PCI_WINDOW_REG_ADDRESS          0x310c
 #define ATH11K_PCI_WINDOW_VALUE_MASK           GENMASK(24, 19)
@@ -46,5 +48,7 @@ int ath11k_pcic_init_msi_config(struct ath11k_base *ab);
 int ath11k_pcic_register_pci_ops(struct ath11k_base *ab,
                                 const struct ath11k_pci_ops *pci_ops);
 int ath11k_pcic_read(struct ath11k_base *ab, void *buf, u32 start, u32 end);
+void ath11k_pci_enable_ce_irqs_except_wake_irq(struct ath11k_base *ab);
+void ath11k_pci_disable_ce_irqs_except_wake_irq(struct ath11k_base *ab);
 
 #endif
index b3e65cd13d8349cab54413abcf3dc6cd44a36e7e..0bf716fb0e705efd38327a63f22e63062ea2c746 100644 (file)
@@ -1,6 +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.
  */
 
 #include <linux/delay.h>
@@ -67,6 +68,13 @@ int ath11k_wow_wakeup(struct ath11k_base *ab)
        struct ath11k *ar = ath11k_ab_to_ar(ab, 0);
        int ret;
 
+       /* In the case of WCN6750, WoW wakeup is done
+        * by sending SMP2P power save exit message
+        * to the target processor.
+        */
+       if (ab->hw_params.smp2p_wow_exit)
+               return 0;
+
        reinit_completion(&ab->wow.wakeup_completed);
 
        ret = ath11k_wmi_wow_host_wakeup_ind(ar);