// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */
#include <linux/module.h>
#include <linux/component.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/of.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/completion.h>
#include <linux/limits.h>

#include "mtk_tv_drm_common.h"
#include "mtk_tv_drm_pqu_agent.h"
#include "mtk_tv_drm_log.h"

#include "pqu_msg.h"
#include "ext_command_client_api.h"
#include "ext_command_render_if.h"

//--------------------------------------------------------------------------------------------------
//  Local Defines
//--------------------------------------------------------------------------------------------------
#define MTK_DRM_MODEL MTK_DRM_MODEL_PQU_METADATA
#define ERR(fmt, args...) DRM_ERROR("[%s][%d] " fmt, __func__, __LINE__, ##args)
#define LOG(fmt, args...) DRM_INFO("[%s][%d] " fmt, __func__, __LINE__, ##args)

#define TIMEOUT_MS	(1000)

//--------------------------------------------------------------------------------------------------
//  Local Enum and Structures
//--------------------------------------------------------------------------------------------------
struct pqu_reply_data {
	struct list_head list;
	uint16_t agent_cmd;
	uint16_t agent_id;
	union {
		struct { // sync
			struct completion completion;
			struct pqu_render_agent_param *output;
		};
		struct { // async
			mtk_tv_pqu_render_reply_cb callback;
			void *priv_data;
		};
	};
};

struct pqu_agent_info {
	bool init;

	struct list_head reply_list;
	struct mutex reply_list_mutex;

	struct mutex agent_id_mutex;
	uint16_t agent_id;
};

//--------------------------------------------------------------------------------------------------
//  Local Variables
//--------------------------------------------------------------------------------------------------
static struct pqu_agent_info _pqu_agent_info;

//--------------------------------------------------------------------------------------------------
//  Local Functions
//--------------------------------------------------------------------------------------------------
static inline uint16_t _pqu_agent_get_reply_id(struct pqu_agent_info *agent)
{
	uint16_t id = 0;

	mutex_lock(&agent->agent_id_mutex);
	id = agent->agent_id;
	agent->agent_id = (agent->agent_id == U16_MAX) ? 0 : agent->agent_id + 1;
	mutex_unlock(&agent->agent_id_mutex);
	return id;
}

static inline int _pqu_agent_add_reply_list(struct pqu_agent_info *agent,
	struct pqu_reply_data *reply)
{
	mutex_lock(&agent->reply_list_mutex);
	list_add_tail(&reply->list, &agent->reply_list);
	mutex_unlock(&agent->reply_list_mutex);
	return 0;
}

static inline struct pqu_reply_data *_pqu_agent_search_reply_list(struct pqu_agent_info *agent,
	uint16_t agent_cmd, uint16_t agent_id)
{
	struct list_head *list, *tmp;
	struct pqu_reply_data *reply = NULL;

	mutex_lock(&agent->reply_list_mutex);
	if (list_empty(&agent->reply_list) == false) {
		list_for_each_safe(list, tmp, &agent->reply_list) {
			reply = list_entry(list, struct pqu_reply_data, list);
			LOG("  agent_cmd:0x%x, agent_id:%d", reply->agent_cmd, reply->agent_id);
			if (reply->agent_cmd == agent_cmd && reply->agent_id == agent_id) {
				list_del(&reply->list);
				break;
			}
			reply = NULL;
		}
	} else
		ERR("reply list is empty");
	mutex_unlock(&agent->reply_list_mutex);
	return reply;
}

static void mtk_tv_drm_pqu_agent_result_callback(
	int error_cause, pid_t thread_id, uint32_t user_param, void *reply_param)
{
	struct pqu_agent_info *agent = &_pqu_agent_info;
	struct pqu_render_agnet_info *info = (struct pqu_render_agnet_info *)reply_param;
	struct pqu_reply_data *reply = NULL;

	if (info == NULL) {
		ERR("reply_param is NULL");
		return;
	}

	LOG("agent_cmd:0x%x, agent_id:%d", info->agent_cmd, info->agent_id);

	reply = _pqu_agent_search_reply_list(agent, info->agent_cmd, info->agent_id);
	if (reply == NULL) {
		ERR("reply not found");
		return;
	}

	switch (PQU_RENDER_AGENT_CMD_GET_REPLY_TYPE(reply->agent_cmd)) {
	case PQU_RENDER_AGENT_CMD_REPLY_TYPE_SYNC:
		*reply->output = info->param;
		LOG("sync reply, agent_cmd:0x%x, agent_id:%d, output:0x%p",
			reply->agent_cmd, reply->agent_id, reply->output);
		complete_all(&reply->completion);
		break;
	case PQU_RENDER_AGENT_CMD_REPLY_TYPE_ASYNC:
		reply->callback(reply->priv_data, &info->param);
		LOG("async reply, agent_cmd:0x%x, agent_id:%d, priv:0x%p, cb:0x%p",
			reply->agent_cmd, reply->agent_id, reply->priv_data,
			reply->callback);
		FREE(reply);
		break;
	default:
		ERR("Unknown reply type: 0x%x", reply->agent_cmd);
		break;
	}
}

static inline int _pqu_agent_init(struct pqu_agent_info *agent)
{
	if (agent->init)
		return 0;

	// reply list
	mutex_init(&agent->reply_list_mutex);
	INIT_LIST_HEAD(&agent->reply_list);
	pqu_msg_register_notify_func(PQU_MSG_REPLY_RENDER_AGENT,
					mtk_tv_drm_pqu_agent_result_callback);

	// agent id
	mutex_init(&agent->agent_id_mutex);
	agent->agent_id = 0;

	agent->init = true;
	return 0;
}

//-------------------------------------------------------------------------------------------------
//  Global Functions
//-------------------------------------------------------------------------------------------------
int mtk_tv_drm_pqu_agent_no_reply(
	enum pqu_render_agent_cmd agent_cmd,
	struct pqu_render_agent_param *input)
{
	struct pqu_agent_info *agent = &_pqu_agent_info;
	struct pqu_render_agnet_info info;
	int ret;

	ret = _pqu_agent_init(agent);
	if (ret < 0) {
		ERR("agent init fail, ret=%d", ret);
		return ret;
	}

	// to pqu
	memset(&info, 0, sizeof(struct pqu_render_agnet_info));
	info.agent_cmd = agent_cmd;
	info.agent_id = _pqu_agent_get_reply_id(agent);
	info.param = *input;
	LOG("agent_cmd:0x%x, agent_id:%d", agent_cmd, info.agent_id);

	/* pqu-rv55 */
	if (bPquEnable) {
		ret = pqu_render_agent_no_reply(&info);
		if (ret)
			ERR("pqu_render_agent_no_reply fail, ret=%d", ret);
		return ret;
	}

	/* pqu-lite */
	pqu_msg_send(PQU_MSG_SEND_RENDER_AGENT, &info);

	return 0;
}

int mtk_tv_drm_pqu_agent_sync_reply(
	enum pqu_render_agent_cmd agent_cmd,
	struct pqu_render_agent_param *input,
	struct pqu_render_agent_param *output)
{
	struct pqu_agent_info *agent = &_pqu_agent_info;
	struct pqu_render_agnet_info info_in;
	struct pqu_render_agnet_info info_out;
	struct pqu_reply_data *reply = NULL;
	int ret;

	ret = _pqu_agent_init(agent);
	if (ret < 0) {
		ERR("agent init fail, ret=%d", ret);
		return ret;
	}

	// to pqu
	agent_cmd = PQU_RENDER_AGENT_CMD_SET_REPLY_TYPE_SYNC(agent_cmd);
	memset(&info_in, 0, sizeof(struct pqu_render_agnet_info));
	info_in.agent_cmd = agent_cmd;
	info_in.agent_id = _pqu_agent_get_reply_id(agent);
	info_in.param = *input;
	LOG("agent_cmd:0x%x, agent_id:%d, output:0x%p", agent_cmd, info_in.agent_id, output);

	/* pqu-rv55 */
	if (bPquEnable) {
		memset(&info_out, 0, sizeof(struct pqu_render_agnet_info));
		ret = pqu_render_agent_sync_reply(&info_in, &info_out);
		if (ret)
			ERR("pqu_render_agent_sync_reply fail, ret=%d", ret);
		else
			*output = info_out.param;
		return ret;
	}

	/* pqu-lite */
	// reply date
	reply = ZALLOC(sizeof(struct pqu_reply_data));
	if (reply == NULL) {
		ERR("reply buffer fail");
		return -ENOMEM;
	}
	reply->agent_cmd = info_in.agent_cmd;
	reply->agent_id = info_in.agent_id;
	reply->output = output;
	init_completion(&reply->completion);
	_pqu_agent_add_reply_list(agent, reply);

	// send to pqu
	pqu_msg_send(PQU_MSG_SEND_RENDER_AGENT, &info_in);

	// block wait
	ret = wait_for_completion_interruptible_timeout(&reply->completion,
						msecs_to_jiffies(TIMEOUT_MS));
	if (ret <= 0) { // ret < 0 is errno, ret = 0 is timeout
		_pqu_agent_search_reply_list(agent, reply->agent_cmd, reply->agent_id);
		ERR("wait_for_completion fail, ret=%d", ret);
		ret = (ret) ? ret : -EBUSY;
	} else {
		LOG("wait_for_completion done, remain=%d", ret);
		ret = 0;
	}
	FREE(reply);
	return ret;
}

int mtk_tv_drm_pqu_agent_async_reply(
	enum pqu_render_agent_cmd agent_cmd,
	struct pqu_render_agent_param *input,
	mtk_tv_pqu_render_reply_cb callback,
	void *priv_data)
{
	struct pqu_agent_info *agent = &_pqu_agent_info;
	struct pqu_render_agnet_info info;
	struct callback_info cb = { .callback = mtk_tv_drm_pqu_agent_result_callback };
	struct pqu_reply_data *reply = NULL;
	int ret;


	ret = _pqu_agent_init(agent);
	if (ret < 0) {
		ERR("agent init fail, ret=%d", ret);
		return ret;
	}

	// to pqu
	agent_cmd = PQU_RENDER_AGENT_CMD_SET_REPLY_TYPE_ASYNC(agent_cmd);
	memset(&info, 0, sizeof(struct pqu_render_agnet_info));
	info.agent_cmd = agent_cmd;
	info.agent_id = _pqu_agent_get_reply_id(agent);
	info.param = *input;
	LOG("agent_cmd:0x%x, agent_id:%d, cb:0x%p, priv:0x%p",
		agent_cmd, info.agent_id, callback, priv_data);

	// reply date
	reply = ZALLOC(sizeof(struct pqu_reply_data));
	if (reply == NULL) {
		ERR("reply buffer fail");
		return -ENOMEM;
	}
	reply->agent_cmd = info.agent_cmd;
	reply->agent_id = info.agent_id;
	reply->callback = callback;
	reply->priv_data = priv_data;
	_pqu_agent_add_reply_list(agent, reply);

	/* pqu-rv55 */
	if (bPquEnable) {
		ret = pqu_render_agent_async_reply(&info, &cb);
		if (ret)
			ERR("pqu_render_agent_sync_reply fail, ret=%d", ret);
		return ret;
	}

	/* pqu-lite */
	// send to pqu
	pqu_msg_send(PQU_MSG_SEND_RENDER_AGENT, &info);

	return 0;
}
