blob: 65e3a20e81c735377c16c7f798bf9f0db451b597 [file] [log] [blame]
/*
* Copyright (c) 2017 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"
/**
* 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;
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 {
return QDF_STATUS_E_FAULT;
}
return QDF_STATUS_SUCCESS;
}
/**
* 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)
{
uint32_t listen_interval;
QDF_STATUS ret;
uint8_t vdev_id;
enum pmo_power_save_qpower_mode qpower_config;
enum tQDF_ADAPTER_MODE opmode = pmo_core_get_vdev_op_mode(vdev);
qpower_config = pmo_core_psoc_get_qpower_config(psoc);
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_info("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_info("Failed to Set Listen Interval vdevId %d",
vdev_id);
}
pmo_debug("Set Listen Interval vdevId %d Listen Intv %d",
vdev_id, listen_interval);
if (qpower_config) {
pmo_debug("disable Qpower in suspend mode!");
ret = pmo_tgt_send_vdev_sta_ps_param(vdev,
pmo_sta_ps_enable_qpower, 0);
if (QDF_IS_STATUS_ERROR(ret))
pmo_info("Failed to disable Qpower in suspend mode!");
}
ret = pmo_tgt_vdev_update_param_req(vdev,
pmo_vdev_param_dtim_policy,
pmo_normal_dtim);
if (QDF_IS_STATUS_ERROR(ret))
pmo_info("Failed to Set to Normal DTIM vdevId %d",
vdev_id);
/* Set it to Normal DTIM */
pmo_core_vdev_set_dtim_policy(vdev, pmo_normal_dtim);
pmo_debug("Set DTIM Policy to Normal Dtim vdevId %d", vdev_id);
}
}
/**
* 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;
bool alt_mdtim_enabled;
QDF_STATUS status;
/* 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);
qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock);
alt_mdtim_enabled = vdev_ctx->alt_modulated_dtim_enable;
qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);
if (!alt_mdtim_enabled)
pmo_core_set_vdev_suspend_dtim(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_set(enable_mask, sizeof(uint32_t) * BM_LEN, 0);
qdf_mem_set(disable_mask, sizeof(uint32_t) * BM_LEN, 0);
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 &&
(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);
pmo_core_enable_wakeup_event(psoc, vdev_id,
enable_mask);
enable_configured = true;
}
if (enable_configured)
pmo_core_enable_wakeup_event(psoc, vdev_id,
enable_mask);
if (disable_configured)
pmo_core_disable_wakeup_event(psoc, vdev_id,
disable_mask);
}
}
/**
* pmo_core_psoc_configure_suspend(): configure suspend req events
* @psoc: objmgr psoc
*
* 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)
{
struct pmo_psoc_priv_obj *psoc_ctx;
PMO_ENTER();
psoc_ctx = pmo_psoc_get_priv(psoc);
if (pmo_core_is_wow_applicable(psoc)) {
pmo_info("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);
}
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);
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)
{
enum pmo_power_save_qpower_mode qpower_config;
QDF_STATUS ret;
uint8_t vdev_id;
enum tQDF_ADAPTER_MODE opmode = pmo_core_get_vdev_op_mode(vdev);
uint32_t cfg_data_val = 0;
qpower_config = pmo_core_psoc_get_qpower_config(psoc);
vdev_id = pmo_vdev_get_id(vdev);
if ((PMO_VDEV_IN_STA_MODE(opmode)) &&
(pmo_core_vdev_get_dtim_policy(vdev) == pmo_normal_dtim)) {
/*
if (!mac) {
WMA_LOGE(FL("Failed to get mac context"));
return;
}
if ((wlan_cfg_get_int(mac, WNI_CFG_LISTEN_INTERVAL,
&cfg_data_val) != eSIR_SUCCESS)) {
pmo_err("Failed to get value for listen interval");
cfg_data_val = POWERSAVE_DEFAULT_LISTEN_INTERVAL;
}
*/
cfg_data_val = 1;
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);
ret = pmo_tgt_vdev_update_param_req(vdev,
pmo_vdev_param_dtim_policy,
pmo_stick_dtim);
if (QDF_IS_STATUS_ERROR(ret)) {
/* Set it back to Stick DTIM */
pmo_err("Failed to Set to Stick DTIM vdevId %d",
vdev_id);
}
pmo_core_vdev_set_dtim_policy(vdev, pmo_stick_dtim);
pmo_debug("Set DTIM Policy to Stick Dtim vdevId %d", vdev_id);
if (qpower_config) {
pmo_debug("enable Qpower in resume mode!");
ret = pmo_tgt_send_vdev_sta_ps_param(vdev,
pmo_sta_ps_enable_qpower, qpower_config);
if (QDF_IS_STATUS_ERROR(ret))
pmo_err("Failed to enable Qpower in resume");
}
}
}
/**
* 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;
bool alt_mdtim_enabled;
QDF_STATUS status;
/* 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);
qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock);
alt_mdtim_enabled = vdev_ctx->alt_modulated_dtim_enable;
qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);
if (!alt_mdtim_enabled)
pmo_core_set_vdev_resume_dtim(psoc, vdev, vdev_ctx);
pmo_vdev_put_ref(vdev);
}
}
/**
* 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 wlan_objmgr_psoc_objmgr *objmgr;
struct wlan_objmgr_vdev *vdev;
/* Iterate through VDEV list */
for (vdev_id = 0; vdev_id < WLAN_UMAC_PSOC_MAX_VDEVS; vdev_id++) {
wlan_psoc_obj_lock(psoc);
objmgr = &psoc->soc_objmgr;
if (!objmgr->wlan_vdev_list[vdev_id]) {
wlan_psoc_obj_unlock(psoc);
continue;
}
vdev = objmgr->wlan_vdev_list[vdev_id];
wlan_psoc_obj_unlock(psoc);
if (vdev) {
#if defined(QCA_LL_LEGACY_TX_FLOW_CONTROL) || defined(QCA_LL_TX_FLOW_CONTROL_V2)
/*
* When host resume, 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),
pmo_core_vdev_get_dp_handle(vdev),
0xffffffff);
if (psoc_ctx->pause_bitmap_notifier)
psoc_ctx->pause_bitmap_notifier(vdev_id,
0);
}
#endif /* QCA_LL_LEGACY_TX_FLOW_CONTROL */
}
}
}
/**
* pmo_core_psoc_configure_resume(): configure events after bus resume
* @psoc: objmgr psoc
*
* 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)
{
struct pmo_psoc_priv_obj *psoc_ctx;
PMO_ENTER();
psoc_ctx = pmo_psoc_get_priv(psoc);
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);
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;
}
status = pmo_tgt_psoc_send_wow_enable_req(psoc, &param);
if (status != QDF_STATUS_SUCCESS) {
pmo_err("Failed to enable wow in fw");
goto out;
}
pmo_tgt_update_target_suspend_flag(psoc, true);
if (qdf_wait_single_event(&psoc_ctx->wow.target_suspend,
PMO_TGT_SUSPEND_COMPLETE_TIMEOUT)
!= QDF_STATUS_SUCCESS) {
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);
status = QDF_STATUS_E_FAILURE;
QDF_BUG(0);
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));
/*
if (!cds_is_driver_recovering())
QDF_BUG(0);
else
pmo_err("SSR in progress, ignore no credit issue");
*/
}
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, &param);
if (status != QDF_STATUS_SUCCESS)
goto out;
pmo_tgt_update_target_suspend_flag(psoc, true);
if (qdf_wait_single_event(&psoc_ctx->wow.target_suspend,
PMO_TGT_SUSPEND_COMPLETE_TIMEOUT)
!= QDF_STATUS_SUCCESS) {
status = QDF_STATUS_E_TIMEOUT;
pmo_err("Failed to get ACK from firmware for pdev suspend");
pmo_tgt_update_target_suspend_flag(psoc, false);
/* wma_suspend_target_timeout(pmac->sme.enableSelfRecovery); */
}
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);
/* TODO - scan manager need to provide the below public api
if (wma_check_scan_in_progress(handle)) {
pmo_err("Scan in progress. Aborting suspend");
status = QDF_STATUS_E_NULL_VALUE;
goto out;
}
*/
wow_mode_selected = pmo_core_is_wow_enabled(psoc_ctx);
pmo_info("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;
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: %p, dp: %p, txrx: %p, htc: %p",
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;
if (hif_pre_runtime_suspend(hif_ctx))
goto runtime_failure;
status = cdp_runtime_suspend(dp_soc, txrx_pdev);
if (status != QDF_STATUS_SUCCESS)
goto runtime_failure;
if (htc_runtime_suspend(htc_ctx))
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);
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;
if (hif_runtime_suspend(hif_ctx))
goto pmo_bus_resume;
if (pld_cb && pld_cb())
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));
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: %p, dp: %p, txrx: %p, htc: %p",
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)
QDF_BUG(!pld_cb());
QDF_BUG(!hif_runtime_resume(hif_ctx));
status = pmo_core_psoc_bus_resume_req(psoc, QDF_RUNTIME_SUSPEND);
QDF_BUG(status == QDF_STATUS_SUCCESS);
status = pmo_core_psoc_configure_resume(psoc);
QDF_BUG(status == QDF_STATUS_SUCCESS);
status = pmo_tgt_psoc_set_runtime_pm_inprogress(psoc, false);
QDF_BUG(status == QDF_STATUS_SUCCESS);
QDF_BUG(!htc_runtime_resume(htc_ctx));
status = cdp_runtime_resume(dp_soc, txrx_pdev);
QDF_BUG(status == QDF_STATUS_SUCCESS);
hif_process_runtime_resume_success(hif_ctx);
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_single_event(&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_BUG(0);
} 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_BUG(0);
} 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_info("wow mode %d", wow_mode);
pmo_core_update_wow_initial_wake_up(psoc_ctx, false);
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);
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();
}