| /* |
| * Copyright (c) 2017-2019 The Linux Foundation. All rights reserved. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /** |
| * DOC: Define API's for suspend / resume handling |
| */ |
| |
| #include "wlan_pmo_wow.h" |
| #include "wlan_pmo_tgt_api.h" |
| #include "wlan_pmo_main.h" |
| #include "wlan_pmo_obj_mgmt_public_struct.h" |
| #include "wlan_pmo_lphb.h" |
| #include "wlan_pmo_suspend_resume.h" |
| #include "cdp_txrx_ops.h" |
| #include "cdp_txrx_misc.h" |
| #include "cdp_txrx_flow_ctrl_legacy.h" |
| #include "hif.h" |
| #include "htc_api.h" |
| #include "wlan_pmo_obj_mgmt_api.h" |
| #include <wlan_scan_ucfg_api.h> |
| #include "cds_api.h" |
| #include "wlan_pmo_static_config.h" |
| #include "wlan_mlme_ucfg_api.h" |
| #include "cfg_mlme_sap.h" |
| #include "cfg_ucfg_api.h" |
| |
| /** |
| * pmo_core_get_vdev_dtim_period() - Get vdev dtim period |
| * @vdev: objmgr vdev handle |
| * |
| * Return: Vdev dtim period |
| */ |
| static uint8_t pmo_core_get_vdev_dtim_period(struct wlan_objmgr_vdev *vdev) |
| { |
| uint8_t dtim_period = 0; |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| struct wlan_objmgr_psoc *psoc; |
| QDF_STATUS ret = QDF_STATUS_E_FAILURE; |
| |
| psoc = pmo_vdev_get_psoc(vdev); |
| |
| pmo_psoc_with_ctx(psoc, psoc_ctx) { |
| if (psoc_ctx->get_dtim_period) |
| ret = psoc_ctx->get_dtim_period(pmo_vdev_get_id(vdev), |
| &dtim_period); |
| } |
| |
| if (QDF_IS_STATUS_ERROR(ret)) |
| pmo_err("Failed to get to dtim period for vdevId %d", |
| pmo_vdev_get_id(vdev)); |
| |
| return dtim_period; |
| } |
| |
| /** |
| * pmo_core_get_vdev_beacon_interval() - Get vdev beacon interval |
| * @vdev: objmgr vdev handle |
| * |
| * Return: Vdev beacon interval |
| */ |
| static uint16_t pmo_core_get_vdev_beacon_interval(struct wlan_objmgr_vdev *vdev) |
| { |
| uint16_t beacon_interval = 0; |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| struct wlan_objmgr_psoc *psoc; |
| QDF_STATUS ret = QDF_STATUS_E_FAILURE; |
| |
| psoc = pmo_vdev_get_psoc(vdev); |
| |
| pmo_psoc_with_ctx(psoc, psoc_ctx) { |
| if (psoc_ctx->get_beacon_interval) |
| ret = psoc_ctx->get_beacon_interval( |
| pmo_vdev_get_id(vdev), |
| &beacon_interval); |
| } |
| |
| if (QDF_IS_STATUS_ERROR(ret)) |
| pmo_err("Failed to get beacon interval for vdev id %d", |
| pmo_vdev_get_id(vdev)); |
| |
| return beacon_interval; |
| } |
| |
| /** |
| * pmo_core_calculate_listen_interval() - Calculate vdev listen interval |
| * @vdev: objmgr vdev handle |
| * @vdev_ctx: pmo vdev priv ctx |
| * @listen_interval: listen interval which is computed for vdev |
| * |
| * Return: QDF_STATUS |
| */ |
| static QDF_STATUS pmo_core_calculate_listen_interval( |
| struct wlan_objmgr_vdev *vdev, |
| struct pmo_vdev_priv_obj *vdev_ctx, |
| uint32_t *listen_interval) |
| { |
| uint32_t max_mod_dtim; |
| uint32_t beacon_interval_mod; |
| struct pmo_psoc_cfg *psoc_cfg = &vdev_ctx->pmo_psoc_ctx->psoc_cfg; |
| struct wlan_objmgr_psoc *psoc = wlan_vdev_get_psoc(vdev); |
| |
| if (psoc_cfg->sta_dynamic_dtim) { |
| *listen_interval = psoc_cfg->sta_dynamic_dtim; |
| } else if ((psoc_cfg->sta_mod_dtim) && |
| (psoc_cfg->sta_max_li_mod_dtim)) { |
| /* |
| * When the system is in suspend |
| * (maximum beacon will be at 1s == 10) |
| * If maxModulatedDTIM ((MAX_LI_VAL = 10) / AP_DTIM) |
| * equal or larger than MDTIM |
| * (configured in WCNSS_qcom_cfg.ini) |
| * Set LI to MDTIM * AP_DTIM |
| * If Dtim = 2 and Mdtim = 2 then LI is 4 |
| * Else |
| * Set LI to maxModulatedDTIM * AP_DTIM |
| */ |
| beacon_interval_mod = |
| pmo_core_get_vdev_beacon_interval(vdev) / 100; |
| if (beacon_interval_mod == 0) |
| beacon_interval_mod = 1; |
| |
| max_mod_dtim = psoc_cfg->sta_max_li_mod_dtim / |
| (pmo_core_get_vdev_dtim_period(vdev) |
| * beacon_interval_mod); |
| |
| if (max_mod_dtim <= 0) |
| max_mod_dtim = 1; |
| |
| if (max_mod_dtim >= psoc_cfg->sta_mod_dtim) { |
| *listen_interval = |
| (psoc_cfg->sta_mod_dtim * |
| pmo_core_get_vdev_dtim_period(vdev)); |
| } else { |
| *listen_interval = |
| (max_mod_dtim * |
| pmo_core_get_vdev_dtim_period(vdev)); |
| } |
| } else { |
| /* Get Listen Interval */ |
| if (QDF_IS_STATUS_ERROR(ucfg_mlme_get_listen_interval(psoc, |
| listen_interval))) { |
| pmo_err("Failed to get value for listen interval"); |
| *listen_interval = cfg_default(CFG_LISTEN_INTERVAL); |
| } |
| } |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static void pmo_configure_vdev_suspend_params( |
| struct wlan_objmgr_psoc *psoc, |
| struct wlan_objmgr_vdev *vdev, |
| struct pmo_vdev_priv_obj *vdev_ctx) |
| { |
| QDF_STATUS ret; |
| uint8_t vdev_id; |
| enum QDF_OPMODE opmode = pmo_core_get_vdev_op_mode(vdev); |
| struct pmo_psoc_cfg *psoc_cfg = &vdev_ctx->pmo_psoc_ctx->psoc_cfg; |
| uint8_t ito_repeat_count_value = 0; |
| uint32_t non_wow_inactivity_time, wow_inactivity_time; |
| |
| pmo_enter(); |
| |
| vdev_id = pmo_vdev_get_id(vdev); |
| if (!PMO_VDEV_IN_STA_MODE(opmode)) |
| return; |
| ret = pmo_tgt_send_vdev_sta_ps_param(vdev, |
| pmo_sta_ps_param_inactivity_time, |
| psoc_cfg->wow_data_inactivity_timeout); |
| if (QDF_IS_STATUS_ERROR(ret)) { |
| pmo_debug("Failed to Set wow inactivity timeout vdevId %d", |
| vdev_id); |
| } |
| |
| non_wow_inactivity_time = psoc_cfg->ps_data_inactivity_timeout; |
| wow_inactivity_time = psoc_cfg->wow_data_inactivity_timeout; |
| /* |
| * To keep ito repeat count same in wow mode as in non wow mode, |
| * modulating ito repeat count value. |
| */ |
| ito_repeat_count_value = (non_wow_inactivity_time / |
| wow_inactivity_time) * |
| psoc_cfg->ito_repeat_count; |
| if (ito_repeat_count_value) |
| ret = pmo_tgt_send_vdev_sta_ps_param(vdev, |
| pmo_sta_ps_param_ito_repeat_count, |
| psoc_cfg->wow_data_inactivity_timeout); |
| if (QDF_IS_STATUS_ERROR(ret)) { |
| pmo_err("Failed to Set ito repeat count vdevId %d", |
| vdev_id); |
| } |
| |
| pmo_exit(); |
| } |
| |
| static void pmo_configure_vdev_resume_params( |
| struct wlan_objmgr_psoc *psoc, |
| struct wlan_objmgr_vdev *vdev, |
| struct pmo_vdev_priv_obj *vdev_ctx) |
| { |
| QDF_STATUS ret; |
| uint8_t vdev_id; |
| enum QDF_OPMODE opmode = pmo_core_get_vdev_op_mode(vdev); |
| struct pmo_psoc_cfg *psoc_cfg = &vdev_ctx->pmo_psoc_ctx->psoc_cfg; |
| |
| pmo_enter(); |
| |
| vdev_id = pmo_vdev_get_id(vdev); |
| if (!PMO_VDEV_IN_STA_MODE(opmode)) |
| return; |
| ret = pmo_tgt_send_vdev_sta_ps_param(vdev, |
| pmo_sta_ps_param_inactivity_time, |
| psoc_cfg->ps_data_inactivity_timeout); |
| if (QDF_IS_STATUS_ERROR(ret)) { |
| pmo_debug("Failed to Set inactivity timeout vdevId %d", |
| vdev_id); |
| } |
| |
| pmo_exit(); |
| } |
| |
| /** |
| * pmo_core_set_vdev_suspend_dtim() - set suspend dtim parameters in fw |
| * @psoc: objmgr psoc handle |
| * @vdev: objmgr vdev handle |
| * @vdev_ctx: pmo vdev priv ctx |
| * |
| * Return: none |
| */ |
| static void pmo_core_set_vdev_suspend_dtim(struct wlan_objmgr_psoc *psoc, |
| struct wlan_objmgr_vdev *vdev, |
| struct pmo_vdev_priv_obj *vdev_ctx) |
| { |
| QDF_STATUS ret; |
| uint8_t vdev_id; |
| enum QDF_OPMODE opmode = pmo_core_get_vdev_op_mode(vdev); |
| uint32_t listen_interval = cfg_default(CFG_LISTEN_INTERVAL); |
| |
| vdev_id = pmo_vdev_get_id(vdev); |
| if (PMO_VDEV_IN_STA_MODE(opmode) && |
| pmo_core_get_vdev_dtim_period(vdev) != 0) { |
| /* calculate listen interval */ |
| ret = pmo_core_calculate_listen_interval(vdev, vdev_ctx, |
| &listen_interval); |
| if (ret != QDF_STATUS_SUCCESS) { |
| /* even it fails continue fwr will take default LI */ |
| pmo_debug("Fail to calculate listen interval"); |
| } |
| ret = pmo_tgt_vdev_update_param_req(vdev, |
| pmo_vdev_param_listen_interval, |
| listen_interval); |
| if (QDF_IS_STATUS_ERROR(ret)) { |
| /* even it fails continue fwr will take default LI */ |
| pmo_debug("Failed to Set Listen Interval vdevId %d", |
| vdev_id); |
| } |
| pmo_debug("Set Listen Interval vdevId %d Listen Intv %d", |
| vdev_id, listen_interval); |
| |
| pmo_core_vdev_set_restore_dtim(vdev, true); |
| } |
| } |
| |
| /* |
| * pmo_is_listen_interval_user_set() - Check if listen interval is configured |
| * by user or not |
| * @vdev_ctx: PMO vdev private object |
| * |
| * Return: true if listen interval is user configured else false |
| */ |
| static inline |
| bool pmo_is_listen_interval_user_set(struct pmo_vdev_priv_obj *vdev_ctx) |
| { |
| bool retval; |
| |
| qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock); |
| retval = vdev_ctx->dyn_modulated_dtim_enabled |
| || vdev_ctx->dyn_listen_interval; |
| qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock); |
| |
| return retval; |
| } |
| |
| /** |
| * pmo_core_set_suspend_dtim() - set suspend dtim |
| * @psoc: objmgr psoc handle |
| * |
| * Return: none |
| */ |
| static void pmo_core_set_suspend_dtim(struct wlan_objmgr_psoc *psoc) |
| { |
| uint8_t vdev_id; |
| struct wlan_objmgr_vdev *vdev; |
| struct pmo_vdev_priv_obj *vdev_ctx; |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| bool li_offload_support = false; |
| QDF_STATUS status; |
| |
| pmo_psoc_with_ctx(psoc, psoc_ctx) { |
| li_offload_support = psoc_ctx->caps.li_offload; |
| } |
| |
| if (li_offload_support) |
| pmo_debug("listen interval offload support is enabled"); |
| |
| /* Iterate through VDEV list */ |
| for (vdev_id = 0; vdev_id < WLAN_UMAC_PSOC_MAX_VDEVS; vdev_id++) { |
| vdev = pmo_psoc_get_vdev(psoc, vdev_id); |
| if (!vdev) |
| continue; |
| |
| status = pmo_vdev_get_ref(vdev); |
| if (QDF_IS_STATUS_ERROR(status)) |
| continue; |
| |
| vdev_ctx = pmo_vdev_get_priv(vdev); |
| if (!pmo_is_listen_interval_user_set(vdev_ctx) |
| && !li_offload_support) |
| pmo_core_set_vdev_suspend_dtim(psoc, vdev, vdev_ctx); |
| pmo_configure_vdev_suspend_params(psoc, vdev, vdev_ctx); |
| pmo_vdev_put_ref(vdev); |
| } |
| } |
| |
| /** |
| * pmo_core_update_wow_bus_suspend() - set wow bus suspend flag |
| * @psoc: objmgr psoc handle |
| * @psoc_ctx: pmo psoc priv ctx |
| * @val: true for enable else false |
| * Return: none |
| */ |
| static inline |
| void pmo_core_update_wow_bus_suspend(struct wlan_objmgr_psoc *psoc, |
| struct pmo_psoc_priv_obj *psoc_ctx, int val) |
| { |
| qdf_spin_lock_bh(&psoc_ctx->lock); |
| psoc_ctx->wow.is_wow_bus_suspended = val; |
| qdf_spin_unlock_bh(&psoc_ctx->lock); |
| pmo_tgt_psoc_update_wow_bus_suspend_state(psoc, val); |
| } |
| |
| /* Define for conciseness */ |
| #define BM_LEN PMO_WOW_MAX_EVENT_BM_LEN |
| #define EV_NLO WOW_NLO_SCAN_COMPLETE_EVENT |
| #define EV_PWR WOW_CHIP_POWER_FAILURE_DETECT_EVENT |
| |
| void pmo_core_configure_dynamic_wake_events(struct wlan_objmgr_psoc *psoc) |
| { |
| int vdev_id; |
| uint32_t adapter_type; |
| uint32_t enable_mask[BM_LEN]; |
| uint32_t disable_mask[BM_LEN]; |
| struct wlan_objmgr_vdev *vdev; |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| bool enable_configured; |
| bool disable_configured; |
| |
| /* Iterate through VDEV list */ |
| for (vdev_id = 0; vdev_id < WLAN_UMAC_PSOC_MAX_VDEVS; vdev_id++) { |
| |
| enable_configured = false; |
| disable_configured = false; |
| |
| qdf_mem_zero(enable_mask, sizeof(uint32_t) * BM_LEN); |
| qdf_mem_zero(disable_mask, sizeof(uint32_t) * BM_LEN); |
| |
| vdev = pmo_psoc_get_vdev(psoc, vdev_id); |
| if (!vdev) |
| continue; |
| |
| if (ucfg_scan_get_pno_in_progress(vdev)) { |
| if (ucfg_scan_get_pno_match(vdev)) { |
| pmo_set_wow_event_bitmap(EV_NLO, |
| BM_LEN, |
| enable_mask); |
| enable_configured = true; |
| } else { |
| pmo_set_wow_event_bitmap(EV_NLO, |
| BM_LEN, |
| disable_mask); |
| disable_configured = true; |
| } |
| } |
| |
| adapter_type = pmo_get_vdev_opmode(vdev); |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| |
| if (psoc_ctx->psoc_cfg.auto_power_save_fail_mode == |
| PMO_FW_TO_SEND_WOW_IND_ON_PWR_FAILURE && |
| (adapter_type == QDF_STA_MODE || |
| adapter_type == QDF_P2P_CLIENT_MODE)) { |
| if (psoc_ctx->is_device_in_low_pwr_mode && |
| psoc_ctx->is_device_in_low_pwr_mode(vdev_id)) { |
| pmo_set_wow_event_bitmap(EV_PWR, |
| BM_LEN, |
| enable_mask); |
| enable_configured = true; |
| } |
| } |
| |
| if (enable_configured) |
| pmo_tgt_enable_wow_wakeup_event(vdev, enable_mask); |
| if (disable_configured) |
| pmo_tgt_disable_wow_wakeup_event(vdev, disable_mask); |
| } |
| |
| } |
| |
| /** |
| * pmo_core_psoc_configure_suspend(): configure suspend req events |
| * @psoc: objmgr psoc |
| * @is_runtime_pm: indicate if it is used by runtime PM |
| * |
| * Responsibility of the caller to take the psoc reference. |
| * |
| * Return: QDF_STATUS_SUCCESS for success or error code |
| */ |
| static QDF_STATUS pmo_core_psoc_configure_suspend(struct wlan_objmgr_psoc *psoc, |
| bool is_runtime_pm) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| |
| pmo_enter(); |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| |
| if (pmo_core_is_wow_applicable(psoc)) { |
| pmo_debug("WOW Suspend"); |
| pmo_core_apply_lphb(psoc); |
| |
| pmo_core_configure_dynamic_wake_events(psoc); |
| pmo_core_update_wow_enable(psoc_ctx, true); |
| pmo_core_update_wow_enable_cmd_sent(psoc_ctx, false); |
| } |
| |
| /* |
| * For runtime PM, since system is awake, DTIM related commands |
| * do not have to be sent with WOW sequence. They can be sent |
| * through other paths which will just trigger a runtime resume. |
| */ |
| if (!is_runtime_pm) |
| pmo_core_set_suspend_dtim(psoc); |
| |
| /* |
| * To handle race between hif_pci_suspend and unpause/pause tx handler. |
| * This happens when host sending WMI_WOW_ENABLE_CMDID to FW and receive |
| * WMI_TX_PAUSE_EVENT with ACTON_UNPAUSE almost at same time. |
| */ |
| pmo_core_update_wow_bus_suspend(psoc, psoc_ctx, true); |
| |
| pmo_exit(); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| QDF_STATUS pmo_core_psoc_user_space_suspend_req(struct wlan_objmgr_psoc *psoc, |
| enum qdf_suspend_type type) |
| { |
| QDF_STATUS status; |
| |
| pmo_enter(); |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("pmo cannot get the reference out of psoc"); |
| goto out; |
| } |
| |
| /* Suspend all components before sending target suspend command */ |
| status = pmo_suspend_all_components(psoc, type); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Failed to suspend all component"); |
| goto dec_psoc_ref; |
| } |
| |
| status = pmo_core_psoc_configure_suspend(psoc, false); |
| if (status != QDF_STATUS_SUCCESS) |
| pmo_err("Failed to configure suspend"); |
| |
| dec_psoc_ref: |
| pmo_psoc_put_ref(psoc); |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| /** |
| * pmo_core_set_vdev_resume_dtim() - set resume dtim parameters in fw |
| * @psoc: objmgr psoc handle |
| * @vdev: objmgr vdev handle |
| * @vdev_ctx: pmo vdev priv ctx |
| * |
| * Return: none |
| */ |
| static void pmo_core_set_vdev_resume_dtim(struct wlan_objmgr_psoc *psoc, |
| struct wlan_objmgr_vdev *vdev, |
| struct pmo_vdev_priv_obj *vdev_ctx) |
| { |
| QDF_STATUS ret; |
| uint8_t vdev_id; |
| enum QDF_OPMODE opmode = pmo_core_get_vdev_op_mode(vdev); |
| int32_t cfg_data_val = 0; |
| |
| vdev_id = pmo_vdev_get_id(vdev); |
| if ((PMO_VDEV_IN_STA_MODE(opmode)) && |
| (pmo_core_vdev_get_restore_dtim(vdev))) { |
| /* Get Listen Interval */ |
| if (QDF_IS_STATUS_ERROR(ucfg_mlme_get_listen_interval(psoc, |
| &cfg_data_val))) { |
| pmo_err("Failed to get value for listen interval"); |
| cfg_data_val = cfg_default(CFG_LISTEN_INTERVAL); |
| } |
| |
| ret = pmo_tgt_vdev_update_param_req(vdev, |
| pmo_vdev_param_listen_interval, cfg_data_val); |
| if (QDF_IS_STATUS_ERROR(ret)) { |
| /* Even it fails continue Fw will take default LI */ |
| pmo_err("Failed to Set Listen Interval vdevId %d", |
| vdev_id); |
| } |
| pmo_debug("Set Listen Interval vdevId %d Listen Intv %d", |
| vdev_id, cfg_data_val); |
| pmo_core_vdev_set_restore_dtim(vdev, false); |
| } |
| } |
| |
| /** |
| * pmo_core_set_resume_dtim() - set resume time dtim |
| * @psoc: objmgr psoc handle |
| * |
| * Return: none |
| */ |
| static void pmo_core_set_resume_dtim(struct wlan_objmgr_psoc *psoc) |
| { |
| uint8_t vdev_id; |
| struct wlan_objmgr_vdev *vdev; |
| struct pmo_vdev_priv_obj *vdev_ctx; |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| bool li_offload_support = false; |
| QDF_STATUS status; |
| |
| pmo_psoc_with_ctx(psoc, psoc_ctx) { |
| li_offload_support = psoc_ctx->caps.li_offload; |
| } |
| |
| if (li_offload_support) |
| pmo_debug("listen interval offload support is enabled"); |
| |
| /* Iterate through VDEV list */ |
| for (vdev_id = 0; vdev_id < WLAN_UMAC_PSOC_MAX_VDEVS; vdev_id++) { |
| vdev = pmo_psoc_get_vdev(psoc, vdev_id); |
| if (!vdev) |
| continue; |
| |
| status = pmo_vdev_get_ref(vdev); |
| if (QDF_IS_STATUS_ERROR(status)) |
| continue; |
| |
| vdev_ctx = pmo_vdev_get_priv(vdev); |
| if (!pmo_is_listen_interval_user_set(vdev_ctx) |
| && !li_offload_support) |
| pmo_core_set_vdev_resume_dtim(psoc, vdev, vdev_ctx); |
| pmo_configure_vdev_resume_params(psoc, vdev, vdev_ctx); |
| pmo_vdev_put_ref(vdev); |
| } |
| } |
| |
| #if defined(QCA_LL_LEGACY_TX_FLOW_CONTROL) || defined(QCA_LL_TX_FLOW_CONTROL_V2) |
| /** |
| * pmo_unpause_vdev - unpause all vdev |
| * @psoc: objmgr psoc handle |
| * |
| * unpause all vdev aftter resume/coming out of wow mode |
| * |
| * Return: none |
| */ |
| static void pmo_unpause_all_vdev(struct wlan_objmgr_psoc *psoc, |
| struct pmo_psoc_priv_obj *psoc_ctx) |
| { |
| uint8_t vdev_id; |
| struct cdp_vdev *vdev_dp; |
| |
| /* Iterate through VDEV list */ |
| for (vdev_id = 0; vdev_id < WLAN_UMAC_PSOC_MAX_VDEVS; vdev_id++) { |
| vdev_dp = pmo_core_vdev_get_dp_handle(psoc_ctx, vdev_id); |
| if (!vdev_dp) |
| continue; |
| |
| /* When host resumes, by default unpause all active vdev */ |
| if (pmo_core_vdev_get_pause_bitmap(psoc_ctx, vdev_id)) { |
| cdp_fc_vdev_unpause(pmo_core_psoc_get_dp_handle(psoc), |
| vdev_dp, |
| 0xffffffff); |
| if (psoc_ctx->pause_bitmap_notifier) |
| psoc_ctx->pause_bitmap_notifier(vdev_id, 0); |
| } |
| } |
| } |
| #else |
| static inline void pmo_unpause_all_vdev(struct wlan_objmgr_psoc *psoc, |
| struct pmo_psoc_priv_obj *psoc_ctx) |
| { |
| } |
| #endif /* QCA_LL_LEGACY_TX_FLOW_CONTROL */ |
| |
| /** |
| * pmo_core_psoc_configure_resume(): configure events after bus resume |
| * @psoc: objmgr psoc |
| * @is_runtime_pm: indicate if it is used by runtime PM |
| * |
| * Responsibility of the caller to take the psoc reference. |
| * |
| * Return: QDF_STATUS_SUCCESS for success or error code |
| */ |
| static QDF_STATUS pmo_core_psoc_configure_resume(struct wlan_objmgr_psoc *psoc, |
| bool is_runtime_pm) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| |
| pmo_enter(); |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| |
| /* |
| * For runtime PM, since system is awake, DTIM related commands |
| * do not have to be sent with WOW sequence. They can be sent |
| * through other paths which will just trigger a runtime resume. |
| */ |
| if (!is_runtime_pm) |
| pmo_core_set_resume_dtim(psoc); |
| pmo_core_update_wow_bus_suspend(psoc, psoc_ctx, false); |
| pmo_unpause_all_vdev(psoc, psoc_ctx); |
| |
| pmo_exit(); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| QDF_STATUS pmo_core_psoc_user_space_resume_req(struct wlan_objmgr_psoc *psoc, |
| enum qdf_suspend_type type) |
| { |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| pmo_enter(); |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("pmo cannot get the reference out of psoc"); |
| goto out; |
| } |
| |
| /* Resume all components */ |
| status = pmo_resume_all_components(psoc, type); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Failed to resume all the components"); |
| goto dec_psoc_ref; |
| } |
| |
| status = pmo_core_psoc_configure_resume(psoc, false); |
| if (status != QDF_STATUS_SUCCESS) |
| pmo_err("Failed to configure resume"); |
| |
| dec_psoc_ref: |
| pmo_psoc_put_ref(psoc); |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| /** |
| * pmo_core_enable_wow_in_fw() - enable wow in fw |
| * @psoc: objmgr psoc handle |
| * @psoc_ctx: pmo psoc private ctx |
| * @wow_params: collection of wow enable override parameters |
| * |
| * Return: QDF status |
| */ |
| static QDF_STATUS |
| pmo_core_enable_wow_in_fw(struct wlan_objmgr_psoc *psoc, |
| struct pmo_psoc_priv_obj *psoc_ctx, |
| struct pmo_wow_enable_params *wow_params) |
| { |
| int host_credits, wmi_pending_cmds; |
| struct pmo_wow_cmd_params param = {0}; |
| QDF_STATUS status; |
| |
| pmo_enter(); |
| qdf_event_reset(&psoc_ctx->wow.target_suspend); |
| pmo_core_set_wow_nack(psoc_ctx, false); |
| host_credits = pmo_tgt_psoc_get_host_credits(psoc); |
| wmi_pending_cmds = pmo_tgt_psoc_get_pending_cmnds(psoc); |
| pmo_debug("Credits:%d; Pending_Cmds: %d", |
| host_credits, wmi_pending_cmds); |
| |
| param.enable = true; |
| if (wow_params->is_unit_test) |
| param.flags = WMI_WOW_FLAG_UNIT_TEST_ENABLE; |
| |
| switch (wow_params->interface_pause) { |
| default: |
| pmo_err("Invalid interface pause setting: %d", |
| wow_params->interface_pause); |
| /* intentional fall-through to default */ |
| case PMO_WOW_INTERFACE_PAUSE_DEFAULT: |
| param.can_suspend_link = |
| htc_can_suspend_link( |
| pmo_core_psoc_get_htc_handle(psoc)); |
| break; |
| case PMO_WOW_INTERFACE_PAUSE_ENABLE: |
| param.can_suspend_link = true; |
| break; |
| case PMO_WOW_INTERFACE_PAUSE_DISABLE: |
| param.can_suspend_link = false; |
| break; |
| } |
| |
| switch (wow_params->resume_trigger) { |
| default: |
| pmo_err("Invalid resume trigger setting: %d", |
| wow_params->resume_trigger); |
| /* intentional fall-through to default */ |
| case PMO_WOW_RESUME_TRIGGER_DEFAULT: |
| case PMO_WOW_RESUME_TRIGGER_GPIO: |
| /* |
| * GPIO is currently implicit. This means you can't actually |
| * force GPIO if a platform's default wake trigger is HTC wakeup |
| */ |
| break; |
| case PMO_WOW_RESUME_TRIGGER_HTC_WAKEUP: |
| param.flags |= WMI_WOW_FLAG_DO_HTC_WAKEUP; |
| break; |
| } |
| |
| if (psoc_ctx->psoc_cfg.d0_wow_supported && |
| !psoc_ctx->caps.unified_wow && |
| !param.can_suspend_link) { |
| psoc_ctx->wow.wow_state = pmo_wow_state_legacy_d0; |
| } else if (param.can_suspend_link) { |
| psoc_ctx->wow.wow_state = pmo_wow_state_unified_d3; |
| } else { |
| psoc_ctx->wow.wow_state = pmo_wow_state_unified_d0; |
| } |
| |
| if (qdf_is_drv_connected()) { |
| pmo_info("drv wow is enabled"); |
| param.flags |= WMI_WOW_FLAG_ENABLE_DRV_PCIE_L1SS_SLEEP; |
| } else { |
| pmo_info("non-drv wow is enabled"); |
| } |
| |
| status = pmo_tgt_psoc_send_wow_enable_req(psoc, ¶m); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Failed to enable wow in fw"); |
| goto out; |
| } |
| |
| pmo_tgt_update_target_suspend_flag(psoc, true); |
| |
| status = qdf_wait_for_event_completion(&psoc_ctx->wow.target_suspend, |
| PMO_TARGET_SUSPEND_TIMEOUT); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| pmo_err("Failed to receive WoW Enable Ack from FW"); |
| pmo_err("Credits:%d; Pending_Cmds: %d", |
| pmo_tgt_psoc_get_host_credits(psoc), |
| pmo_tgt_psoc_get_pending_cmnds(psoc)); |
| pmo_tgt_update_target_suspend_flag(psoc, false); |
| qdf_trigger_self_recovery(QDF_SUSPEND_TIMEOUT); |
| goto out; |
| } |
| |
| if (pmo_core_get_wow_nack(psoc_ctx)) { |
| pmo_err("FW not ready to WOW"); |
| pmo_tgt_update_target_suspend_flag(psoc, false); |
| status = QDF_STATUS_E_AGAIN; |
| goto out; |
| } |
| |
| host_credits = pmo_tgt_psoc_get_host_credits(psoc); |
| wmi_pending_cmds = pmo_tgt_psoc_get_pending_cmnds(psoc); |
| |
| if (host_credits < PMO_WOW_REQUIRED_CREDITS) { |
| pmo_err("No Credits after HTC ACK:%d, pending_cmds:%d," |
| "cannot resume back", host_credits, wmi_pending_cmds); |
| htc_dump_counter_info(pmo_core_psoc_get_htc_handle(psoc)); |
| qdf_trigger_self_recovery(QDF_SUSPEND_TIMEOUT); |
| } |
| pmo_debug("WOW enabled successfully in fw: credits:%d pending_cmds: %d", |
| host_credits, wmi_pending_cmds); |
| |
| pmo_core_update_wow_enable_cmd_sent(psoc_ctx, true); |
| |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| QDF_STATUS pmo_core_psoc_suspend_target(struct wlan_objmgr_psoc *psoc, |
| int disable_target_intr) |
| { |
| QDF_STATUS status; |
| struct pmo_suspend_params param; |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| |
| pmo_enter(); |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| |
| qdf_event_reset(&psoc_ctx->wow.target_suspend); |
| param.disable_target_intr = disable_target_intr; |
| status = pmo_tgt_psoc_send_supend_req(psoc, ¶m); |
| if (status != QDF_STATUS_SUCCESS) |
| goto out; |
| |
| pmo_tgt_update_target_suspend_flag(psoc, true); |
| |
| status = qdf_wait_for_event_completion(&psoc_ctx->wow.target_suspend, |
| PMO_TARGET_SUSPEND_TIMEOUT); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| pmo_err("Failed to get ACK from firmware for pdev suspend"); |
| pmo_tgt_update_target_suspend_flag(psoc, false); |
| qdf_trigger_self_recovery(QDF_SUSPEND_TIMEOUT); |
| } |
| |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| QDF_STATUS pmo_core_psoc_bus_suspend_req(struct wlan_objmgr_psoc *psoc, |
| enum qdf_suspend_type type, |
| struct pmo_wow_enable_params *wow_params) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| QDF_STATUS status; |
| bool wow_mode_selected = false; |
| |
| pmo_enter(); |
| if (!psoc) { |
| pmo_err("psoc is NULL"); |
| status = QDF_STATUS_E_NULL_VALUE; |
| goto out; |
| } |
| |
| if (!wow_params) { |
| pmo_err("wow_params is NULL"); |
| status = QDF_STATUS_E_NULL_VALUE; |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("pmo cannot get the reference out of psoc"); |
| goto out; |
| } |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| |
| wow_mode_selected = pmo_core_is_wow_enabled(psoc_ctx); |
| pmo_debug("wow mode selected %d", wow_mode_selected); |
| |
| if (wow_mode_selected) |
| status = pmo_core_enable_wow_in_fw(psoc, psoc_ctx, wow_params); |
| else |
| status = pmo_core_psoc_suspend_target(psoc, 0); |
| |
| pmo_psoc_put_ref(psoc); |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| #ifdef FEATURE_RUNTIME_PM |
| QDF_STATUS pmo_core_psoc_bus_runtime_suspend(struct wlan_objmgr_psoc *psoc, |
| pmo_pld_auto_suspend_cb pld_cb) |
| { |
| void *hif_ctx; |
| void *dp_soc; |
| void *txrx_pdev; |
| void *htc_ctx; |
| QDF_STATUS status; |
| int ret; |
| struct pmo_wow_enable_params wow_params = {0}; |
| |
| pmo_enter(); |
| |
| if (!psoc) { |
| pmo_err("psoc is NULL"); |
| status = QDF_STATUS_E_INVAL; |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("pmo cannot get the reference out of psoc"); |
| goto out; |
| } |
| |
| hif_ctx = pmo_core_psoc_get_hif_handle(psoc); |
| dp_soc = pmo_core_psoc_get_dp_handle(psoc); |
| txrx_pdev = pmo_core_psoc_get_txrx_handle(psoc); |
| htc_ctx = pmo_core_psoc_get_htc_handle(psoc); |
| if (!hif_ctx || !dp_soc || !txrx_pdev || !htc_ctx) { |
| pmo_err("Invalid hif: %pK, dp: %pK, txrx: %pK, htc: %pK", |
| hif_ctx, dp_soc, txrx_pdev, htc_ctx); |
| status = QDF_STATUS_E_INVAL; |
| goto dec_psoc_ref; |
| } |
| |
| wow_params.interface_pause = PMO_WOW_INTERFACE_PAUSE_ENABLE; |
| wow_params.resume_trigger = PMO_WOW_RESUME_TRIGGER_GPIO; |
| |
| ret = hif_pre_runtime_suspend(hif_ctx); |
| if (ret) { |
| status = qdf_status_from_os_return(ret); |
| goto runtime_failure; |
| } |
| |
| status = cdp_runtime_suspend(dp_soc, txrx_pdev); |
| if (status != QDF_STATUS_SUCCESS) |
| goto runtime_failure; |
| |
| ret = htc_runtime_suspend(htc_ctx); |
| if (ret) { |
| status = qdf_status_from_os_return(ret); |
| goto cdp_runtime_resume; |
| } |
| |
| status = pmo_tgt_psoc_set_runtime_pm_inprogress(psoc, true); |
| if (status != QDF_STATUS_SUCCESS) |
| goto resume_htc; |
| |
| status = pmo_core_psoc_configure_suspend(psoc, true); |
| if (status != QDF_STATUS_SUCCESS) |
| goto resume_htc; |
| |
| status = pmo_core_psoc_bus_suspend_req(psoc, QDF_RUNTIME_SUSPEND, |
| &wow_params); |
| if (status != QDF_STATUS_SUCCESS) |
| goto pmo_resume_configure; |
| |
| ret = hif_runtime_suspend(hif_ctx); |
| if (ret) { |
| status = qdf_status_from_os_return(ret); |
| goto pmo_bus_resume; |
| } |
| |
| if (pld_cb) { |
| ret = pld_cb(); |
| if (ret) { |
| status = qdf_status_from_os_return(ret); |
| goto resume_hif; |
| } |
| } |
| |
| hif_process_runtime_suspend_success(hif_ctx); |
| |
| goto dec_psoc_ref; |
| |
| resume_hif: |
| QDF_BUG(!hif_runtime_resume(hif_ctx)); |
| |
| pmo_bus_resume: |
| QDF_BUG(QDF_STATUS_SUCCESS == |
| pmo_core_psoc_bus_resume_req(psoc, QDF_RUNTIME_SUSPEND)); |
| |
| pmo_resume_configure: |
| QDF_BUG(QDF_STATUS_SUCCESS == |
| pmo_core_psoc_configure_resume(psoc, true)); |
| |
| resume_htc: |
| QDF_BUG(QDF_STATUS_SUCCESS == |
| pmo_tgt_psoc_set_runtime_pm_inprogress(psoc, false)); |
| |
| QDF_BUG(!htc_runtime_resume(htc_ctx)); |
| |
| cdp_runtime_resume: |
| QDF_BUG(QDF_STATUS_SUCCESS == |
| cdp_runtime_resume(dp_soc, txrx_pdev)); |
| |
| runtime_failure: |
| hif_process_runtime_suspend_failure(hif_ctx); |
| |
| dec_psoc_ref: |
| pmo_psoc_put_ref(psoc); |
| |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| QDF_STATUS pmo_core_psoc_bus_runtime_resume(struct wlan_objmgr_psoc *psoc, |
| pmo_pld_auto_resume_cb pld_cb) |
| { |
| void *hif_ctx; |
| void *dp_soc; |
| void *txrx_pdev; |
| void *htc_ctx; |
| QDF_STATUS status; |
| |
| pmo_enter(); |
| |
| if (!psoc) { |
| pmo_err("psoc is NULL"); |
| status = QDF_STATUS_E_INVAL; |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("pmo cannot get the reference out of psoc"); |
| goto out; |
| } |
| |
| hif_ctx = pmo_core_psoc_get_hif_handle(psoc); |
| dp_soc = pmo_core_psoc_get_dp_handle(psoc); |
| txrx_pdev = pmo_core_psoc_get_txrx_handle(psoc); |
| htc_ctx = pmo_core_psoc_get_htc_handle(psoc); |
| if (!hif_ctx || !dp_soc || !txrx_pdev || !htc_ctx) { |
| pmo_err("Invalid hif: %pK, dp: %pK, txrx: %pK, htc: %pK", |
| hif_ctx, dp_soc, txrx_pdev, htc_ctx); |
| status = QDF_STATUS_E_INVAL; |
| goto dec_psoc_ref; |
| } |
| |
| hif_pre_runtime_resume(hif_ctx); |
| |
| if (pld_cb) { |
| if (pld_cb()) { |
| status = QDF_STATUS_E_FAILURE; |
| goto fail; |
| } |
| } |
| |
| if (hif_runtime_resume(hif_ctx)) { |
| status = QDF_STATUS_E_FAILURE; |
| goto fail; |
| } |
| |
| status = pmo_core_psoc_bus_resume_req(psoc, QDF_RUNTIME_SUSPEND); |
| if (status != QDF_STATUS_SUCCESS) |
| goto fail; |
| |
| status = pmo_core_psoc_configure_resume(psoc, true); |
| if (status != QDF_STATUS_SUCCESS) |
| goto fail; |
| |
| status = pmo_tgt_psoc_set_runtime_pm_inprogress(psoc, false); |
| if (status != QDF_STATUS_SUCCESS) |
| goto fail; |
| |
| if (htc_runtime_resume(htc_ctx)) { |
| status = QDF_STATUS_E_FAILURE; |
| goto fail; |
| } |
| |
| status = cdp_runtime_resume(dp_soc, txrx_pdev); |
| if (status != QDF_STATUS_SUCCESS) |
| goto fail; |
| |
| hif_process_runtime_resume_success(hif_ctx); |
| |
| fail: |
| if (status != QDF_STATUS_SUCCESS) |
| qdf_trigger_self_recovery(QDF_RESUME_TIMEOUT); |
| |
| dec_psoc_ref: |
| pmo_psoc_put_ref(psoc); |
| |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| #endif |
| |
| /** |
| * pmo_core_psoc_send_host_wakeup_ind_to_fw() - send wakeup ind to fw |
| * @psoc: objmgr psoc handle |
| * @psoc_ctx: pmo psoc private context |
| * |
| * Sends host wakeup indication to FW. On receiving this indication, |
| * FW will come out of WOW. |
| * |
| * Return: QDF status |
| */ |
| static |
| QDF_STATUS pmo_core_psoc_send_host_wakeup_ind_to_fw( |
| struct wlan_objmgr_psoc *psoc, |
| struct pmo_psoc_priv_obj *psoc_ctx) |
| { |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| pmo_enter(); |
| qdf_event_reset(&psoc_ctx->wow.target_resume); |
| |
| status = pmo_tgt_psoc_send_host_wakeup_ind(psoc); |
| if (status) { |
| status = QDF_STATUS_E_FAILURE; |
| goto out; |
| } |
| pmo_debug("Host wakeup indication sent to fw"); |
| |
| status = qdf_wait_for_event_completion(&psoc_ctx->wow.target_resume, |
| PMO_RESUME_TIMEOUT); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Timeout waiting for resume event from FW"); |
| pmo_err("Pending commands %d credits %d", |
| pmo_tgt_psoc_get_pending_cmnds(psoc), |
| pmo_tgt_psoc_get_host_credits(psoc)); |
| qdf_trigger_self_recovery(QDF_RESUME_TIMEOUT); |
| } else { |
| pmo_debug("Host wakeup received"); |
| } |
| |
| if (status == QDF_STATUS_SUCCESS) |
| pmo_tgt_update_target_suspend_flag(psoc, false); |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| /** |
| * pmo_core_psoc_disable_wow_in_fw() - Disable wow in bus resume context. |
| * @psoc: objmgr psoc handle |
| * @psoc_ctx: pmo psoc private context |
| * |
| * Return: QDF_STATUS_SUCCESS for success or error code |
| */ |
| static |
| QDF_STATUS pmo_core_psoc_disable_wow_in_fw(struct wlan_objmgr_psoc *psoc, |
| struct pmo_psoc_priv_obj *psoc_ctx) |
| { |
| QDF_STATUS ret; |
| |
| pmo_enter(); |
| ret = pmo_core_psoc_send_host_wakeup_ind_to_fw(psoc, psoc_ctx); |
| if (ret != QDF_STATUS_SUCCESS) |
| goto out; |
| |
| pmo_core_update_wow_enable(psoc_ctx, false); |
| pmo_core_update_wow_enable_cmd_sent(psoc_ctx, false); |
| |
| /* To allow the tx pause/unpause events */ |
| pmo_core_update_wow_bus_suspend(psoc, psoc_ctx, false); |
| /* Unpause the vdev as we are resuming */ |
| pmo_unpause_all_vdev(psoc, psoc_ctx); |
| out: |
| pmo_exit(); |
| |
| return ret; |
| } |
| |
| /** |
| * pmo_core_psoc_resume_target() - resume target |
| * @psoc: objmgr psoc handle |
| * @psoc_ctx: pmo psoc private context |
| * |
| * Return: QDF_STATUS_SUCCESS for success or error code |
| */ |
| static |
| QDF_STATUS pmo_core_psoc_resume_target(struct wlan_objmgr_psoc *psoc, |
| struct pmo_psoc_priv_obj *psoc_ctx) |
| { |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| pmo_enter(); |
| qdf_event_reset(&psoc_ctx->wow.target_resume); |
| |
| status = pmo_tgt_psoc_send_target_resume_req(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| status = QDF_STATUS_E_FAILURE; |
| goto out; |
| } |
| |
| status = qdf_wait_single_event(&psoc_ctx->wow.target_resume, |
| PMO_RESUME_TIMEOUT); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_fatal("Timeout waiting for resume event from FW"); |
| pmo_fatal("Pending commands %d credits %d", |
| pmo_tgt_psoc_get_pending_cmnds(psoc), |
| pmo_tgt_psoc_get_host_credits(psoc)); |
| qdf_trigger_self_recovery(QDF_RESUME_TIMEOUT); |
| } else { |
| pmo_debug("Host wakeup received"); |
| } |
| |
| if (status == QDF_STATUS_SUCCESS) |
| pmo_tgt_update_target_suspend_flag(psoc, false); |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| QDF_STATUS pmo_core_psoc_bus_resume_req(struct wlan_objmgr_psoc *psoc, |
| enum qdf_suspend_type type) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| bool wow_mode; |
| QDF_STATUS status; |
| |
| pmo_enter(); |
| if (!psoc) { |
| pmo_err("psoc is null"); |
| status = QDF_STATUS_E_NULL_VALUE; |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("pmo cannot get the reference out of psoc"); |
| goto out; |
| } |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| wow_mode = pmo_core_is_wow_enabled(psoc_ctx); |
| pmo_debug("wow mode %d", wow_mode); |
| |
| pmo_core_update_wow_initial_wake_up(psoc_ctx, false); |
| |
| /* If target was not suspended, bail out */ |
| if (!pmo_tgt_is_target_suspended(psoc)) { |
| pmo_psoc_put_ref(psoc); |
| goto out; |
| } |
| |
| if (wow_mode) |
| status = pmo_core_psoc_disable_wow_in_fw(psoc, psoc_ctx); |
| else |
| status = pmo_core_psoc_resume_target(psoc, psoc_ctx); |
| |
| pmo_psoc_put_ref(psoc); |
| |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| void pmo_core_psoc_target_suspend_acknowledge(void *context, bool wow_nack) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| struct wlan_objmgr_psoc *psoc = (struct wlan_objmgr_psoc *)context; |
| QDF_STATUS status; |
| |
| pmo_enter(); |
| if (!psoc) { |
| pmo_err("psoc is null"); |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Failed to get psoc reference"); |
| goto out; |
| } |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| |
| pmo_core_set_wow_nack(psoc_ctx, wow_nack); |
| qdf_event_set(&psoc_ctx->wow.target_suspend); |
| if (wow_nack && !pmo_tgt_psoc_get_runtime_pm_in_progress(psoc)) { |
| qdf_wake_lock_timeout_acquire(&psoc_ctx->wow.wow_wake_lock, |
| PMO_WAKE_LOCK_TIMEOUT); |
| } |
| |
| pmo_psoc_put_ref(psoc); |
| out: |
| pmo_exit(); |
| } |
| |
| void pmo_core_psoc_wakeup_host_event_received(struct wlan_objmgr_psoc *psoc) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| |
| pmo_enter(); |
| if (!psoc) { |
| pmo_err("psoc is null"); |
| goto out; |
| } |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| psoc_ctx->wow.wow_state = pmo_wow_state_none; |
| qdf_event_set(&psoc_ctx->wow.target_resume); |
| out: |
| pmo_exit(); |
| } |
| |
| int pmo_core_psoc_is_target_wake_up_received(struct wlan_objmgr_psoc *psoc) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| int ret = 0; |
| QDF_STATUS status; |
| |
| if (!psoc) { |
| pmo_err("psoc is NULL"); |
| ret = -EAGAIN; |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Failed to get psoc reference"); |
| ret = -EAGAIN; |
| goto out; |
| } |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| if (pmo_core_get_wow_initial_wake_up(psoc_ctx)) { |
| pmo_err("Target initial wake up received try again"); |
| ret = -EAGAIN; |
| } |
| |
| pmo_psoc_put_ref(psoc); |
| out: |
| pmo_exit(); |
| |
| return ret; |
| } |
| |
| |
| int pmo_core_psoc_clear_target_wake_up(struct wlan_objmgr_psoc *psoc) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| int ret = 0; |
| QDF_STATUS status; |
| |
| if (!psoc) { |
| pmo_err("psoc is NULL"); |
| ret = -EAGAIN; |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Failed to get psoc reference"); |
| ret = -EAGAIN; |
| goto out; |
| } |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| pmo_core_update_wow_initial_wake_up(psoc_ctx, false); |
| |
| pmo_psoc_put_ref(psoc); |
| out: |
| pmo_exit(); |
| |
| return ret; |
| } |
| |
| void pmo_core_psoc_handle_initial_wake_up(void *cb_ctx) |
| { |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| struct wlan_objmgr_psoc *psoc = (struct wlan_objmgr_psoc *)cb_ctx; |
| QDF_STATUS status; |
| |
| pmo_enter(); |
| if (!psoc) { |
| pmo_err("cb ctx/psoc is null"); |
| goto out; |
| } |
| |
| status = pmo_psoc_get_ref(psoc); |
| if (status != QDF_STATUS_SUCCESS) { |
| pmo_err("Failed to get psoc reference"); |
| goto out; |
| } |
| |
| psoc_ctx = pmo_psoc_get_priv(psoc); |
| pmo_core_update_wow_initial_wake_up(psoc_ctx, true); |
| |
| pmo_psoc_put_ref(psoc); |
| |
| out: |
| pmo_exit(); |
| } |
| |
| QDF_STATUS pmo_core_config_listen_interval(struct wlan_objmgr_vdev *vdev, |
| uint32_t new_li) |
| { |
| QDF_STATUS status; |
| uint8_t vdev_id; |
| uint32_t listen_interval; |
| struct pmo_vdev_priv_obj *vdev_ctx; |
| struct pmo_psoc_priv_obj *psoc_ctx; |
| struct wlan_objmgr_psoc *psoc = wlan_vdev_get_psoc(vdev); |
| |
| pmo_enter(); |
| |
| status = pmo_vdev_get_ref(vdev); |
| if (QDF_IS_STATUS_ERROR(status)) |
| goto out; |
| |
| vdev_ctx = pmo_vdev_get_priv(vdev); |
| vdev_id = pmo_vdev_get_id(vdev); |
| |
| qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock); |
| if (vdev_ctx->dyn_listen_interval == new_li) { |
| qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock); |
| status = QDF_STATUS_SUCCESS; |
| pmo_debug("Listen Interval(%d) already set for vdev id %d", |
| new_li, vdev_id); |
| goto dec_ref; |
| } |
| |
| vdev_ctx->dyn_listen_interval = new_li; |
| qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock); |
| |
| listen_interval = new_li ? new_li : cfg_default(CFG_LISTEN_INTERVAL); |
| |
| if (!new_li) { |
| /* Configure default LI as we do on resume */ |
| pmo_psoc_with_ctx(pmo_vdev_get_psoc(vdev), psoc_ctx) { |
| if (QDF_IS_STATUS_ERROR( |
| ucfg_mlme_get_listen_interval(psoc, |
| &listen_interval))) { |
| pmo_err("Failed to get listen interval"); |
| listen_interval = |
| cfg_default(CFG_LISTEN_INTERVAL); |
| } |
| } |
| } |
| |
| pmo_debug("Set Listen Interval %d for vdevId %d", listen_interval, |
| vdev_id); |
| status = pmo_tgt_vdev_update_param_req(vdev, |
| pmo_vdev_param_listen_interval, |
| listen_interval); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| /* even it fails continue fwr will take default LI */ |
| pmo_err("Failed to Set Listen Interval"); |
| } |
| |
| /* Set it to Normal DTIM */ |
| status = pmo_tgt_vdev_update_param_req(vdev, |
| pmo_vdev_param_dtim_policy, |
| pmo_normal_dtim); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| pmo_err("Failed to set Normal DTIM for vdev id %d", vdev_id); |
| } else { |
| pmo_debug("Set DTIM Policy to Normal for vdev id %d", vdev_id); |
| pmo_core_vdev_set_restore_dtim(vdev, true); |
| } |
| |
| dec_ref: |
| pmo_vdev_put_ref(vdev); |
| out: |
| pmo_exit(); |
| |
| return status; |
| } |
| |
| QDF_STATUS pmo_core_config_modulated_dtim(struct wlan_objmgr_vdev *vdev, |
| uint32_t mod_dtim) |
| { |
| struct pmo_vdev_priv_obj *vdev_ctx; |
| struct pmo_psoc_cfg *psoc_cfg; |
| bool prev_dtim_enabled; |
| uint32_t listen_interval; |
| uint32_t beacon_interval_mod; |
| uint32_t max_mod_dtim; |
| QDF_STATUS status; |
| uint8_t vdev_id; |
| uint32_t max_dtim; |
| |
| pmo_enter(); |
| |
| status = pmo_vdev_get_ref(vdev); |
| if (status != QDF_STATUS_SUCCESS) |
| goto out; |
| |
| vdev_id = pmo_vdev_get_id(vdev); |
| vdev_ctx = pmo_vdev_get_priv(vdev); |
| psoc_cfg = &vdev_ctx->pmo_psoc_ctx->psoc_cfg; |
| |
| /* Calculate Maximum allowed modulated DTIM */ |
| beacon_interval_mod = |
| pmo_core_get_vdev_beacon_interval(vdev) / 100; |
| if (!beacon_interval_mod) |
| beacon_interval_mod = 1; |
| |
| max_dtim = (pmo_core_get_vdev_dtim_period(vdev) |
| * beacon_interval_mod); |
| |
| if (!max_dtim) { |
| pmo_err("Invalid dtim period"); |
| pmo_vdev_put_ref(vdev); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| max_mod_dtim = psoc_cfg->sta_max_li_mod_dtim / |
| max_dtim; |
| |
| if (!max_mod_dtim) |
| max_mod_dtim = 1; |
| |
| /* Calculate Listen Interval from provided mod DTIM */ |
| qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock); |
| vdev_ctx->dyn_modulated_dtim = mod_dtim; |
| prev_dtim_enabled = vdev_ctx->dyn_modulated_dtim_enabled; |
| vdev_ctx->dyn_modulated_dtim_enabled = mod_dtim != 1; |
| if (vdev_ctx->dyn_modulated_dtim > max_mod_dtim) { |
| listen_interval = max_mod_dtim * |
| pmo_core_get_vdev_dtim_period(vdev); |
| } else { |
| listen_interval = vdev_ctx->dyn_modulated_dtim * |
| pmo_core_get_vdev_dtim_period(vdev); |
| } |
| qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock); |
| |
| if (prev_dtim_enabled || mod_dtim != 1) { |
| status = pmo_tgt_vdev_update_param_req(vdev, |
| pmo_vdev_param_listen_interval, |
| listen_interval); |
| if (QDF_IS_STATUS_ERROR(status)) |
| /* even it fails continue fwr will take default LI */ |
| pmo_err("Failed to set Listen Interval for vdev id %d", |
| vdev_id); |
| else |
| pmo_debug("Set Listen Interval %d for vdev id %d", |
| listen_interval, vdev_id); |
| |
| status = pmo_tgt_vdev_update_param_req(vdev, |
| pmo_vdev_param_dtim_policy, |
| pmo_normal_dtim); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| pmo_err("Failed to set Normal DTIM for vdev id %d", |
| vdev_id); |
| } else { |
| pmo_debug("Set DTIM Policy to Normal for vdev id %d", |
| vdev_id); |
| pmo_core_vdev_set_restore_dtim(vdev, true); |
| } |
| } |
| |
| pmo_vdev_put_ref(vdev); |
| out: |
| pmo_exit(); |
| return status; |
| } |