/*
 * 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: This file contains p2p north bound interface definitions
 */

#include <wmi_unified_api.h>
#include <wlan_objmgr_psoc_obj.h>
#include <scheduler_api.h>
#include "wlan_p2p_ucfg_api.h"
#include "wlan_p2p_public_struct.h"
#include "../../core/src/wlan_p2p_main.h"
#include "../../core/src/wlan_p2p_roc.h"
#include "../../core/src/wlan_p2p_off_chan_tx.h"

static inline struct wlan_lmac_if_p2p_tx_ops *
ucfg_p2p_psoc_get_tx_ops(struct wlan_objmgr_psoc *psoc)
{
	return &(psoc->soc_cb.tx_ops.p2p);
}

QDF_STATUS ucfg_p2p_init(void)
{
	return p2p_component_init();
}

QDF_STATUS ucfg_p2p_deinit(void)
{
	return p2p_component_deinit();
}

QDF_STATUS ucfg_p2p_psoc_open(struct wlan_objmgr_psoc *soc)
{
	return p2p_psoc_object_open(soc);
}

QDF_STATUS ucfg_p2p_psoc_close(struct wlan_objmgr_psoc *soc)
{
	return p2p_psoc_object_close(soc);
}

QDF_STATUS ucfg_p2p_psoc_start(struct wlan_objmgr_psoc *soc,
	struct p2p_start_param *req)
{
	return p2p_psoc_start(soc, req);
}

QDF_STATUS ucfg_p2p_psoc_stop(struct wlan_objmgr_psoc *soc)
{
	return p2p_psoc_stop(soc);
}

QDF_STATUS ucfg_p2p_roc_req(struct wlan_objmgr_psoc *soc,
	struct p2p_roc_req *roc_req, uint64_t *cookie)
{
	struct scheduler_msg msg = {0};
	struct p2p_soc_priv_obj *p2p_soc_obj;
	struct p2p_roc_context *roc_ctx;

	p2p_debug("soc:%p, vdev_id:%d, chan:%d, phy_mode:%d, duration:%d",
		soc, roc_req->vdev_id, roc_req->chan,
		roc_req->phy_mode, roc_req->duration);

	if (!soc) {
		p2p_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	p2p_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(soc,
			WLAN_UMAC_COMP_P2P);
	if (!p2p_soc_obj) {
		p2p_err("P2P soc object is NULL");
		return QDF_STATUS_E_FAILURE;
	}

	roc_ctx = qdf_mem_malloc(sizeof(*roc_ctx));
	if (!roc_ctx) {
		p2p_err("failed to allocate p2p roc context");
		return QDF_STATUS_E_NOMEM;
	}

	*cookie = (uintptr_t)roc_ctx;
	roc_ctx->p2p_soc_obj = p2p_soc_obj;
	roc_ctx->vdev_id = roc_req->vdev_id;
	roc_ctx->chan = roc_req->chan;
	roc_ctx->phy_mode = roc_req->phy_mode;
	roc_ctx->duration = roc_req->duration;
	roc_ctx->roc_state = ROC_STATE_IDLE;
	roc_ctx->roc_type = USER_REQUESTED;
	msg.type = P2P_ROC_REQ;
	msg.bodyptr = roc_ctx;
	msg.callback = p2p_process_cmd;
	scheduler_post_msg(QDF_MODULE_ID_OS_IF, &msg);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS ucfg_p2p_roc_cancel_req(struct wlan_objmgr_psoc *soc,
	uint64_t cookie)
{
	struct scheduler_msg msg = {0};
	struct p2p_soc_priv_obj *p2p_soc_obj;
	struct cancel_roc_context *cancel_roc;

	p2p_debug("soc:%p, cookie:0x%llx", soc, cookie);

	if (!soc) {
		p2p_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	p2p_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(soc,
			WLAN_UMAC_COMP_P2P);
	if (!p2p_soc_obj) {
		p2p_err("p2p soc context is NULL");
		return QDF_STATUS_E_FAILURE;
	}

	cancel_roc = qdf_mem_malloc(sizeof(*cancel_roc));
	if (!cancel_roc) {
		p2p_err("failed to allocate cancel p2p roc");
		return QDF_STATUS_E_NOMEM;
	}

	cancel_roc->p2p_soc_obj = p2p_soc_obj;
	cancel_roc->cookie = cookie;
	msg.type = P2P_CANCEL_ROC_REQ;
	msg.bodyptr = cancel_roc;
	msg.callback = p2p_process_cmd;
	scheduler_post_msg(QDF_MODULE_ID_OS_IF, &msg);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS ucfg_p2p_mgmt_tx(struct wlan_objmgr_psoc *soc,
	struct p2p_mgmt_tx *mgmt_frm, uint64_t *cookie)
{
	struct scheduler_msg msg = {0};
	struct p2p_soc_priv_obj *p2p_soc_obj;
	struct  tx_action_context *tx_action;

	p2p_debug("soc:%p, vdev_id:%d, chan:%d, wait:%d, buf_len:%d, cck:%d, no ack:%d, off chan:%d",
		soc, mgmt_frm->vdev_id, mgmt_frm->chan,
		mgmt_frm->wait, mgmt_frm->len, mgmt_frm->no_cck,
		mgmt_frm->dont_wait_for_ack, mgmt_frm->off_chan);

	if (!soc) {
		p2p_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	p2p_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(soc,
			WLAN_UMAC_COMP_P2P);
	if (!p2p_soc_obj) {
		p2p_err("P2P soc context is NULL");
		return QDF_STATUS_E_FAILURE;
	}

	tx_action = qdf_mem_malloc(sizeof(*tx_action));
	if (!tx_action) {
		p2p_err("Failed to allocate tx action context");
		return QDF_STATUS_E_NOMEM;
	}

	/* return cookie just for ota ack frames */
	if (mgmt_frm->dont_wait_for_ack)
		*cookie = 0;
	else
		*cookie = (uintptr_t)tx_action;

	tx_action->p2p_soc_obj = p2p_soc_obj;
	tx_action->vdev_id = mgmt_frm->vdev_id;
	tx_action->chan = mgmt_frm->chan;
	tx_action->duration = mgmt_frm->wait;
	tx_action->buf_len = mgmt_frm->len;
	tx_action->no_cck = mgmt_frm->no_cck;
	tx_action->no_ack = mgmt_frm->dont_wait_for_ack;
	tx_action->off_chan = mgmt_frm->off_chan;
	tx_action->buf = qdf_mem_malloc(tx_action->buf_len);
	if (!(tx_action->buf)) {
		p2p_err("Failed to allocate buffer for action frame");
		qdf_mem_free(tx_action);
		return QDF_STATUS_E_NOMEM;
	}
	qdf_mem_copy(tx_action->buf, mgmt_frm->buf, tx_action->buf_len);
	msg.type = P2P_MGMT_TX;
	msg.bodyptr = tx_action;
	msg.callback = p2p_process_cmd;
	scheduler_post_msg(QDF_MODULE_ID_OS_IF, &msg);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS ucfg_p2p_mgmt_tx_cancel(struct wlan_objmgr_psoc *soc,
	uint64_t cookie)
{
	struct scheduler_msg msg = {0};
	struct p2p_soc_priv_obj *p2p_soc_obj;
	struct cancel_roc_context *cancel_tx;

	p2p_debug("soc:%p, cookie:0x%llx", soc, cookie);

	if (!soc) {
		p2p_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	p2p_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(soc,
			WLAN_UMAC_COMP_P2P);
	if (!p2p_soc_obj) {
		p2p_err("p2p soc context is NULL");
		return QDF_STATUS_E_FAILURE;
	}

	cancel_tx = qdf_mem_malloc(sizeof(*cancel_tx));
	if (!cancel_tx) {
		p2p_err("Failed to allocate cancel p2p roc");
		return QDF_STATUS_E_NOMEM;
	}

	cancel_tx->p2p_soc_obj = p2p_soc_obj;
	cancel_tx->cookie = cookie;
	msg.type = P2P_MGMT_TX_CANCEL;
	msg.bodyptr = cancel_tx;
	msg.callback = p2p_process_cmd;
	scheduler_post_msg(QDF_MODULE_ID_OS_IF, &msg);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS ucfg_p2p_set_ps(struct wlan_objmgr_psoc *soc,
	struct p2p_ps_config *ps_config)
{
	struct wlan_lmac_if_p2p_tx_ops *p2p_ops;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;

	p2p_debug("soc:%p, vdev_id:%d, opp_ps:%d, ct_window:%d, count:%d, duration:%d, duration:%d, ps_selection:%d",
		soc, ps_config->vdev_id, ps_config->opp_ps,
		ps_config->ct_window, ps_config->count,
		ps_config->duration, ps_config->single_noa_duration,
		ps_config->ps_selection);

	if (!soc) {
		p2p_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	p2p_ops = ucfg_p2p_psoc_get_tx_ops(soc);
	if (p2p_ops->set_ps) {
		status = p2p_ops->set_ps(soc, ps_config);
		p2p_debug("p2p set ps, status:%d", status);
	}

	return status;
}

QDF_STATUS ucfg_p2p_lo_start(struct wlan_objmgr_psoc *soc,
	struct p2p_lo_start *p2p_lo_start)
{
	struct wlan_lmac_if_p2p_tx_ops *p2p_ops;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;

	p2p_debug("soc:%p, vdev_id:%d, ctl_flags:%d, freq:%d, period:%d, interval:%d, count:%d, dev_types_len:%d, probe_resp_len:%d, device_types:%p, probe_resp_tmplt:%p",
		soc, p2p_lo_start->vdev_id, p2p_lo_start->ctl_flags,
		p2p_lo_start->freq, p2p_lo_start->period,
		p2p_lo_start->interval, p2p_lo_start->count,
		p2p_lo_start->dev_types_len, p2p_lo_start->probe_resp_len,
		p2p_lo_start->device_types, p2p_lo_start->probe_resp_tmplt);

	if (!soc) {
		p2p_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	p2p_ops = ucfg_p2p_psoc_get_tx_ops(soc);
	if (p2p_ops->lo_start) {
		status = p2p_ops->lo_start(soc, p2p_lo_start);
		p2p_debug("p2p lo start, status:%d", status);
	}

	return status;
}

QDF_STATUS ucfg_p2p_lo_stop(struct wlan_objmgr_psoc *soc,
	uint32_t vdev_id)
{
	struct wlan_lmac_if_p2p_tx_ops *p2p_ops;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;

	p2p_debug("soc:%p, vdev_id:%d", soc, vdev_id);

	if (!soc) {
		p2p_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	p2p_ops = ucfg_p2p_psoc_get_tx_ops(soc);
	if (p2p_ops->lo_stop) {
		status = p2p_ops->lo_stop(soc, vdev_id);
		p2p_debug("p2p lo stop, status:%d", status);
	}

	return status;
}
