#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
        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,
        .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)
        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;
        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);
 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);
 
 
        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);
 
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .hw_rev = ATH11K_HW_IPQ6018_HW10,
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "qca6390 hw2.0",
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "qcn9074 hw1.0",
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "wcn6855 hw2.0",
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "wcn6855 hw2.1",
 
                .tcl_ring_retry = true,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+               .smp2p_wow_exit = false,
        },
        {
                .name = "wcn6750 hw1.0",
 
                .tcl_ring_retry = false,
                .tx_ring_size = DP_TCL_DATA_RING_SIZE_WCN6750,
+               .smp2p_wow_exit = true,
        },
 };
 
 
        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);