diff options
-rw-r--r-- | drivers/net/wireless/ath/ath11k/ahb.c | 122 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath11k/ahb.h | 16 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath11k/core.c | 7 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath11k/hw.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath11k/pcic.c | 34 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath11k/pcic.h | 4 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath11k/wow.c | 8 |
7 files changed, 191 insertions, 1 deletions
diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 2bf709b0b12a..84bf679da9eb 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -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); diff --git a/drivers/net/wireless/ath/ath11k/ahb.h b/drivers/net/wireless/ath/ath11k/ahb.h index 58a945411c5b..415ddfd26654 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.h +++ b/drivers/net/wireless/ath/ath11k/ahb.h @@ -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) diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 54848b1efccb..d71043ae4fbf 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -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, }, }; diff --git a/drivers/net/wireless/ath/ath11k/hw.h b/drivers/net/wireless/ath/ath11k/hw.h index 90cf402fd5d9..f4a30835a5f6 100644 --- a/drivers/net/wireless/ath/ath11k/hw.h +++ b/drivers/net/wireless/ath/ath11k/hw.h @@ -217,6 +217,7 @@ struct ath11k_hw_params { bool tcl_ring_retry; u32 tx_ring_size; + bool smp2p_wow_exit; }; struct ath11k_hw_ops { diff --git a/drivers/net/wireless/ath/ath11k/pcic.c b/drivers/net/wireless/ath/ath11k/pcic.c index 74ed99af833b..3fa1958f8c82 100644 --- a/drivers/net/wireless/ath/ath11k/pcic.c +++ b/drivers/net/wireless/ath/ath11k/pcic.c @@ -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); diff --git a/drivers/net/wireless/ath/ath11k/pcic.h b/drivers/net/wireless/ath/ath11k/pcic.h index f3506611aa20..ac012e88bf6d 100644 --- a/drivers/net/wireless/ath/ath11k/pcic.h +++ b/drivers/net/wireless/ath/ath11k/pcic.h @@ -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 diff --git a/drivers/net/wireless/ath/ath11k/wow.c b/drivers/net/wireless/ath/ath11k/wow.c index b3e65cd13d83..0bf716fb0e70 100644 --- a/drivers/net/wireless/ath/ath11k/wow.c +++ b/drivers/net/wireless/ath/ath11k/wow.c @@ -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); |