/******************************************************************************
 *
 * Copyright(c) 2019 Realtek Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 *****************************************************************************/
#define _PHL_CUSTOM_C_
#include "../phl_headers.h"
#ifdef CONFIG_PHL_CUSTOM_FEATURE_VR
#include "phl_custom_vr.h"
#endif
#ifdef CONFIG_PHL_CUSTOM_FEATURE_FB
#include "phl_custom_fb.h"
#endif
#include "phl_custom_antenna.h"

#ifdef CONFIG_PHL_CUSTOM_FEATURE

enum custom_status {
	CUS_STAT_CMD_USED = BIT0,
	CUS_STAT_RPT_USED = BIT1,
	CUS_STAT_RPT_SENDING = BIT2,
};

struct phl_custom_ctx {
	struct phl_info_t *phl_info;
	u8 status; /* refer to enum custom_status*/
	u8 cmd_buf[sizeof(struct rtw_custom_decrpt)];
	u8 rpt_buf[sizeof(struct rtw_custom_decrpt)];
#ifdef CONFIG_PHL_CUSTOM_FEATURE_VR
	struct _custom_vr_ctx vr_ctx;
#endif
#ifdef CONFIG_PHL_CUSTOM_FEATURE_FB
	struct _custom_fb_ctx fb_ctx;
#endif
};

bool
_custom_rpt_notify_check(void* priv, struct phl_msg* msg)
{
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)priv;
	bool ret = true;
	if (!TEST_STATUS_FLAG(ctx->status, CUS_STAT_RPT_USED) ||
	    TEST_STATUS_FLAG(ctx->status, CUS_STAT_RPT_SENDING)) {
		ret = false;
	}
	return ret;
}

void
_custom_rpt_notify_complete(void* priv, struct phl_msg* msg)
{
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)priv;

	CLEAR_STATUS_FLAG(ctx->status, CUS_STAT_RPT_USED);
	CLEAR_STATUS_FLAG(ctx->status, CUS_STAT_RPT_SENDING);
}

void
_custom_rpt_notify_start(void* priv,
                         struct phl_msg* msg,
                         struct phl_msg_attribute* attr)
{
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)priv;
	struct rtw_custom_decrpt *evt_rpt = (struct rtw_custom_decrpt *)ctx->rpt_buf;

	SET_STATUS_FLAG(ctx->status, CUS_STAT_RPT_SENDING);

	SET_MSG_MDL_ID_FIELD(msg->msg_id, PHL_MDL_CUSTOM);
	SET_MSG_EVT_ID_FIELD(msg->msg_id, MSG_EVT_CUSTOM_CMD_DONE);

	msg->inbuf = (u8*) evt_rpt;
	msg->inlen = sizeof(struct rtw_custom_decrpt);

	attr->completion.completion = _custom_rpt_notify_complete;
	attr->completion.priv = ctx;
}

static void
_indicate_custome_evt_rpt(struct phl_custom_ctx *ctx)
{
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};

	if (!_custom_rpt_notify_check((void*)ctx, &msg))
		return;

	_custom_rpt_notify_start((void*)ctx, &msg, &attr);

	if (phl_msg_hub_send(ctx->phl_info,
	                     &attr, &msg) != RTW_PHL_STATUS_SUCCESS) {
		PHL_ERR("%s send msg fail\n", __func__);
		_custom_rpt_notify_complete((void*)ctx, &msg);
	}
}
enum rtw_phl_status
_phl_custom_prepare_default_fail_rpt(struct phl_custom_ctx *ctx,
                                     struct phl_msg* msg)
{
	struct rtw_custom_decrpt *cmd = (struct rtw_custom_decrpt *)(msg->inbuf);
	u8 ret = MDL_RET_FAIL;

	if (TEST_STATUS_FLAG(ctx->status, CUS_STAT_RPT_USED))
		return RTW_PHL_STATUS_RESOURCE;

	phl_custom_prepare_evt_rpt(ctx,
                                   cmd->evt_id,
                                   cmd->customer_id,
                                   &ret,
                                   1);
	return RTW_PHL_STATUS_SUCCESS;
}

static enum phl_mdl_ret_code _phl_custom_hdl_fail_evt(
    void *dispr, struct phl_custom_ctx *ctx, struct phl_msg *msg)
{
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	struct rtw_custom_decrpt *cmd = NULL;

	cmd = (struct rtw_custom_decrpt *)(msg->inbuf);
	switch (cmd->customer_id) {
#ifdef CONFIG_PHL_CUSTOM_FEATURE_VR
	case CUS_ID_VR:
		ret =
		    phl_custom_hdl_vr_fail_evt(dispr, ctx, &(ctx->vr_ctx), msg);
		break;
#endif
#ifdef CONFIG_PHL_CUSTOM_FEATURE_FB
	case CUS_ID_FB:
		ret =
		    phl_custom_hdl_fb_fail_evt(dispr, ctx, &(ctx->fb_ctx), msg);
		break;
#endif
	default:
		ret = MDL_RET_IGNORE;
		break;
	}
	return ret;
}

static enum phl_mdl_ret_code _phl_custom_hdl_internal_evt(
    void *dispr, struct phl_custom_ctx *ctx, struct phl_msg *msg)
{
	enum phl_mdl_ret_code ret = MDL_RET_FAIL;
	struct rtw_custom_decrpt *cmd = NULL;

	cmd = (struct rtw_custom_decrpt *)(msg->inbuf);
	switch (cmd->customer_id) {
#ifdef CONFIG_PHL_CUSTOM_FEATURE_VR
	case CUS_ID_VR:
		ret = phl_custom_hdl_vr_evt(dispr, ctx, &(ctx->vr_ctx), msg);
		break;
#endif
#ifdef CONFIG_PHL_CUSTOM_FEATURE_FB
	case CUS_ID_FB:
		ret = phl_custom_hdl_fb_evt(dispr, ctx, &(ctx->fb_ctx), msg);
		break;
#endif
	case CUS_ID_ANT_TOOL:
		ret = phl_custom_antenna_cmd_hdlr(dispr, ctx, msg);
		break;
	default:
		ret = MDL_RET_IGNORE;
		break;
	}
	return ret;
}
static enum phl_mdl_ret_code
_phl_custom_hdl_external_evt(void* dispr,
                             struct phl_custom_ctx* ctx,
                             struct phl_msg* msg)
{
	return MDL_RET_IGNORE;
}

void
_phl_custom_cmd_completion(void* priv, struct phl_msg* msg)
{
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)priv;
	CLEAR_STATUS_FLAG(ctx->status, CUS_STAT_CMD_USED);
}

enum phl_mdl_ret_code
_phl_custom_mdl_init(void* phl_info,
                     void* dispr,
                     void** priv)
{
	struct phl_info_t *phl = (struct phl_info_t *)phl_info;
	struct phl_custom_ctx* ctx = NULL;
	void *d = phl_to_drvpriv(phl);

	FUNCIN();
	if (priv == NULL)
		return MDL_RET_FAIL;

	(*priv) = NULL;
	ctx = (struct phl_custom_ctx *)_os_mem_alloc(d, sizeof(struct phl_custom_ctx));
	if (ctx == NULL) {
		PHL_ERR(" %s, alloc fail\n",__FUNCTION__);
		return MDL_RET_FAIL;
	}
	ctx->phl_info = phl_info;
	(*priv) = (void*)ctx;
	PHL_INFO(" %s, size phl_custom_ctx(%d)\n",
			__FUNCTION__, (int)sizeof(struct phl_custom_ctx));
	return MDL_RET_SUCCESS;
}
void
_phl_custom_mdl_deinit(void* dispr, void* priv)
{
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx*)priv;
	void *d = phl_to_drvpriv(ctx->phl_info);

	_os_mem_free(d, ctx, sizeof(struct phl_custom_ctx));
	PHL_INFO(" %s\n", __FUNCTION__);
}
enum phl_mdl_ret_code
_phl_custom_mdl_start(void* dispr, void* priv)
{
	enum phl_mdl_ret_code ret = MDL_RET_FAIL;

	FUNCIN();
	FUNCOUT();

	ret = MDL_RET_SUCCESS;
	return ret;
}
enum phl_mdl_ret_code
_phl_custom_mdl_stop(void* dispr, void* priv)
{
	enum phl_mdl_ret_code ret = MDL_RET_FAIL;

	FUNCIN();
	FUNCOUT();

	ret = MDL_RET_SUCCESS;
	return ret;
}
enum phl_mdl_ret_code
_phl_custom_mdl_msg_hdlr(void* dispr,
                         void* priv,
                         struct phl_msg* msg)
{
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx*)priv;

	FUNCIN();
	if (IS_MSG_FAIL(msg->msg_id)) {
		ret = _phl_custom_hdl_fail_evt(dispr, priv, msg);
		_indicate_custome_evt_rpt(ctx);
		return MDL_RET_IGNORE;
	}

	switch (MSG_MDL_ID_FIELD(msg->msg_id)) {
		case PHL_MDL_CUSTOM:
			ret = _phl_custom_hdl_internal_evt(dispr, priv, msg);
			break;
		default:
			ret = _phl_custom_hdl_external_evt(dispr, priv, msg);
			break;
	}
	_indicate_custome_evt_rpt(ctx);
	FUNCOUT();
	return ret;
}

enum phl_mdl_ret_code
_phl_custom_feature_set_hdlr(void* dispr,
                         struct phl_custom_ctx *ctx,
                         struct phl_module_op_info* info)
{
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	struct rtw_custom_decrpt *cmd = NULL;

	cmd = (struct rtw_custom_decrpt *)info->inbuf;

	switch (cmd->customer_id) {
#ifdef CONFIG_PHL_CUSTOM_FEATURE_VR
		case CUS_ID_VR:
			ret = phl_custom_vr_feature_set_hdlr(dispr, ctx,
			                                     &(ctx->vr_ctx), cmd);
			break;
#endif
#ifdef CONFIG_PHL_CUSTOM_FEATURE_FB
		case CUS_ID_FB:
			ret = phl_custom_fb_feature_set_hdlr(dispr, ctx,
			                                     &(ctx->fb_ctx), cmd);
			break;
#endif
		default:
			ret = MDL_RET_IGNORE;
			break;
	}

	return ret;
}

static enum phl_mdl_ret_code
_phl_custom_mdl_set_info(void* dispr,
                         void* priv,
                         struct phl_module_op_info* info)
{
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)priv;
	void *d = phl_to_drvpriv(ctx->phl_info);
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};
	struct rtw_custom_decrpt *cmd = NULL;
	u8 idx = 0xff;

	phl_dispr_get_idx(dispr, &idx);
	FUNCIN();
	switch (info->op_code) {
		case BK_MODL_OP_INPUT_CMD:
			if (TEST_STATUS_FLAG(ctx->status, CUS_STAT_CMD_USED) ||
				info->inlen > sizeof(struct rtw_custom_decrpt)) {
				PHL_ERR("%s buf len err or used\n", __func__);
				break;
			}
			SET_STATUS_FLAG(ctx->status, CUS_STAT_CMD_USED);
			_os_mem_cpy(d, ctx->cmd_buf, info->inbuf, info->inlen);
			cmd = (struct rtw_custom_decrpt *)ctx->cmd_buf;
			SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_MDL_CUSTOM);
			SET_MSG_EVT_ID_FIELD(msg.msg_id, (u16)cmd->evt_id);
			msg.inbuf = ctx->cmd_buf;
			msg.inlen = info->inlen;
			attr.completion.priv = priv;
			attr.completion.completion = _phl_custom_cmd_completion;
			msg.band_idx = idx;
			if (phl_disp_eng_send_msg(ctx->phl_info,
			                          &msg,
			                          &attr,
			                          NULL) == RTW_PHL_STATUS_SUCCESS) {
				ret = MDL_RET_SUCCESS;
			} else {
				CLEAR_STATUS_FLAG(ctx->status, CUS_STAT_CMD_USED);
				PHL_ERR("%s send msg fail\n", __func__);
				ret = MDL_RET_FAIL;
			}
			break;
		case BK_MODL_OP_CUS_FEATURE_SET:
			ret = _phl_custom_feature_set_hdlr(dispr, ctx, info);
			break;
		default:
			break;
	}
	FUNCOUT();
	return ret;
}

enum phl_mdl_ret_code
_phl_custom_feature_query_hdlr(void* dispr,
                         struct phl_custom_ctx *ctx,
                         struct phl_module_op_info* info)
{
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	struct rtw_custom_decrpt *cmd = NULL;

	cmd = (struct rtw_custom_decrpt *)info->outbuf;

	switch (cmd->customer_id) {
#ifdef CONFIG_PHL_CUSTOM_FEATURE_VR
		case CUS_ID_VR:
			ret = phl_custom_vr_feature_query_hdlr(dispr, ctx,
			                                     &(ctx->vr_ctx), cmd);
			break;
#endif
#ifdef CONFIG_PHL_CUSTOM_FEATURE_FB
		case CUS_ID_FB:
			ret = phl_custom_fb_feature_query_hdlr(dispr, ctx,
			                                     &(ctx->fb_ctx), cmd);
			break;
#endif
		default:
			ret = MDL_RET_IGNORE;
			break;
	}

	return ret;
}

enum phl_mdl_ret_code
_phl_custom_mdl_query_info(void* dispr,
                           void* priv,
                           struct phl_module_op_info* info)
{
	enum phl_mdl_ret_code ret = MDL_RET_IGNORE;
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)priv;
	void *d = phl_to_drvpriv(ctx->phl_info);
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};
	struct rtw_custom_decrpt *cmd = NULL;
	u8 idx = 0xff;

	FUNCIN();
	switch(info->op_code) {
		case BK_MODL_OP_INPUT_CMD:
			if (TEST_STATUS_FLAG(ctx->status, CUS_STAT_CMD_USED) ||
				info->inlen > sizeof(struct rtw_custom_decrpt)) {
				PHL_ERR("%s buf len err or used\n", __func__);
				break;
			}
			SET_STATUS_FLAG(ctx->status, CUS_STAT_CMD_USED);
			_os_mem_cpy(d, ctx->cmd_buf, info->inbuf, info->inlen);
			_os_mem_cpy(d, ctx->rpt_buf, info->outbuf, info->outlen);
			cmd = (struct rtw_custom_decrpt *)ctx->cmd_buf;
			SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_MDL_CUSTOM);
			SET_MSG_EVT_ID_FIELD(msg.msg_id, (u16)cmd->evt_id);
			msg.inbuf = ctx->cmd_buf;
			msg.inlen = info->inlen;
			msg.outbuf = ctx->rpt_buf;
			msg.outlen = info->outlen;
			attr.completion.priv = priv;
			attr.completion.completion = _phl_custom_cmd_completion;
			msg.band_idx = idx;
			if (phl_disp_eng_send_msg(ctx->phl_info,
			                          &msg,
			                          &attr,
			                          NULL) == RTW_PHL_STATUS_SUCCESS) {
				ret = MDL_RET_SUCCESS;
			} else {
				CLEAR_STATUS_FLAG(ctx->status, CUS_STAT_CMD_USED);
				PHL_ERR("%s send msg fail\n", __func__);
				ret = MDL_RET_FAIL;
			}
			break;
		case BK_MODL_OP_CUS_FEATURE_QUERY:
			_os_mem_cpy(d, ctx->rpt_buf, info->outbuf, info->outlen);
			ret = _phl_custom_feature_query_hdlr(dispr, ctx, info);
			break;
	default:
		break;
	}

	FUNCOUT();
	return ret;
}

enum rtw_phl_status
phl_register_custom_module(struct phl_info_t *phl_info, u8 band_idx)
{
	enum rtw_phl_status phl_status = RTW_PHL_STATUS_FAILURE;
	struct phl_bk_module_ops bk_ops = {0};

	bk_ops.init = _phl_custom_mdl_init;
	bk_ops.deinit = _phl_custom_mdl_deinit;
	bk_ops.start = _phl_custom_mdl_start;
	bk_ops.stop = _phl_custom_mdl_stop;
	bk_ops.msg_hdlr = _phl_custom_mdl_msg_hdlr;
	bk_ops.query_info = _phl_custom_mdl_query_info;
	bk_ops.set_info = _phl_custom_mdl_set_info;

	phl_status = phl_disp_eng_register_module(phl_info,
	                                          band_idx,
	                                          PHL_MDL_CUSTOM,
	                                          &bk_ops);
	if (RTW_PHL_STATUS_SUCCESS != phl_status)
		PHL_ERR("%s register Custom module in cmd disp failed\n", __func__);

	return phl_status;

}

enum rtw_phl_status
phl_custom_prepare_evt_rpt(void *custom_ctx,
                           u32 evt_id,
                           u32 customer_id,
                           u8 *rpt,
                           u32 rpt_len)
{
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)custom_ctx;
	struct rtw_custom_decrpt *evt_rpt = (struct rtw_custom_decrpt *)ctx->rpt_buf;
	void *d = phl_to_drvpriv(ctx->phl_info);
	u8* val = evt_rpt->data;

	if ((rpt_len > MAX_DATA_SIZE) ||
	   TEST_STATUS_FLAG(ctx->status, CUS_STAT_RPT_USED))
		return RTW_PHL_STATUS_RESOURCE;

	SET_STATUS_FLAG(ctx->status, CUS_STAT_RPT_USED);
	evt_rpt->evt_id = evt_id;
	evt_rpt->customer_id = customer_id;
	evt_rpt->len = rpt_len;
	_os_mem_cpy(d, val, rpt, rpt_len);
	return RTW_PHL_STATUS_SUCCESS;
}

enum rtw_phl_status
rtw_phl_custom_prepare_set_info_evt(u32 evt_id,
                                    u32 customer_id,
                                    struct rtw_custom_decrpt *inbuf,
                                    struct phl_module_op_info *op_info,
                                    u32 data_len)
{
	if ((data_len > MAX_DATA_SIZE)) {
		PHL_ERR("%s data_len exceeds MAX_DATA_SIZE\n", __func__);
		return RTW_PHL_STATUS_RESOURCE;
	}

	inbuf->evt_id = evt_id;
	inbuf->customer_id = customer_id;
	inbuf->len = data_len;

	op_info->op_code = BK_MODL_OP_CUS_FEATURE_SET;
	op_info->inbuf = (u8*)inbuf;
	op_info->inlen = sizeof(struct rtw_custom_decrpt);

	return RTW_PHL_STATUS_SUCCESS;
}

enum rtw_phl_status
rtw_phl_custom_prepare_query_info_evt(u32 evt_id,
                                      u32 customer_id,
                                      struct rtw_custom_decrpt *inbuf,
                                      struct phl_module_op_info *op_info,
                                      u32 data_len)
{
	if ((data_len > MAX_DATA_SIZE)) {
		PHL_ERR("%s data_len exceeds MAX_DATA_SIZE\n", __func__);
		return RTW_PHL_STATUS_RESOURCE;
	}
	inbuf->evt_id = evt_id;
	inbuf->customer_id = customer_id;
	inbuf->len = data_len;

	op_info->op_code = BK_MODL_OP_CUS_FEATURE_QUERY;
	op_info->outbuf = (u8*)inbuf;
	op_info->outlen = sizeof(struct rtw_custom_decrpt);

	return RTW_PHL_STATUS_SUCCESS;
}

struct phl_info_t*
phl_custom_get_phl_info(void *custom_ctx)
{
	struct phl_custom_ctx *ctx = (struct phl_custom_ctx *)custom_ctx;

	return ctx->phl_info;
}

/**
 * phl_custom_init_role_link_cap
 * 1. role cap customization
 * input:
 * @phl_info: (struct phl_info_t *)
 * @hw_band:hw_band
 * @role_link_cap: (struct role_link_cap_t)
 * return: rtw_phl_status
 */
enum rtw_phl_status
phl_custom_init_role_link_cap(struct phl_info_t *phl_info,
                         u8 hw_band,
                         struct role_link_cap_t *role_link_cap)
{
	enum rtw_phl_status status = RTW_PHL_STATUS_SUCCESS;

#ifdef CONFIG_PHL_CUSTOM_FEATURE_FB
	status = phl_custom_fb_init_role_link_cap(phl_info, hw_band, role_link_cap);
#endif

	return status;
}
#endif
