/*
* 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);

	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();
}

