// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/component.h>
#include <drm/mtk_tv_drm.h>
#include "mtk_tv_drm_kms.h"
#include "mtk_tv_drm_log.h"
#include "mtk_tv_sm.h"
#include "mtk_tv_sm_ml.h"
#include "mtk_tv_sm_gce.h"

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

#define TO_U64(v)		((uint64_t)(long)(v))
#define TO_PTR(v)		((void *)(long)(v))

#define SM_CMD_SIZE		(8)
#define SM_DEFAULT_GCE		false

#if IS_ENABLED(CONFIG_MTK_CMDQ_MBOX_EXT)
#define SM_GCE_ENABLE		1
#else
#define SM_GCE_ENABLE		0
#endif

//--------------------------------------------------------------------------------------------------
//  Local Enum and Structures
//--------------------------------------------------------------------------------------------------
struct ml_info {
	bool init;
	bool gce_driver;
	bool support_gce;
	bool use_gce;
};

struct cmd_buf {
	void *alloc_addr;
	void *start_addr[];
};

//--------------------------------------------------------------------------------------------------
//  Local Variables
//--------------------------------------------------------------------------------------------------
static struct ml_info _sm_info;

//--------------------------------------------------------------------------------------------------
//  Local Functions
//--------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Global Functions
//-------------------------------------------------------------------------------------------------
int mtk_tv_sm_init(struct mtk_sm_context *sm,
	struct mtk_tv_kms_context *kms,
	enum sm_ml_sync ml_sync_id,
	enum sm_ml_unique_id unique_id,
	uint8_t mem_buf_cnt,
	uint16_t mem_cmd_cnt,
	struct mtk_sm_context *master)
{
	struct ml_info *info = &_sm_info;
	int ret = 0;

	if (!sm || !kms) {
		ERR("Invalid input");
		return -EINVAL;
	}

	if (unlikely(info->init == false)) {
		info->gce_driver = (bool)SM_GCE_ENABLE;
		info->support_gce = kms->hw_caps.support_gce;
		info->use_gce = (info->gce_driver && info->support_gce) ? SM_DEFAULT_GCE : false;
		info->init = true;
	}

	sm->unique_id = unique_id;
	sm->ml_sync_id = ml_sync_id;
	sm->mem_buf_cnt = mem_buf_cnt;
	sm->mem_cmd_cnt = mem_cmd_cnt;

	// ML
	ret = mtk_tv_sm_ml_init(&sm->ml, ml_sync_id, unique_id, mem_buf_cnt, mem_cmd_cnt);
	if (ret == -ENODEV && master != NULL) {
		WRN("0x%llx ML not support, dup to 0x%llx", sm->unique_id, master->unique_id);
		ret = mtk_tv_sm_ml_dup(&sm->ml, &master->ml);
	}
	if (ret)
		ERR("0x%llx init fail, ret=%d", sm->unique_id, ret);

	// GCE
#if SM_GCE_ENABLE
	ret = mtk_tv_sm_gce_init(&sm->gce, kms->dev, unique_id, mem_buf_cnt, mem_cmd_cnt);
	if (ret == -ENODEV && master != NULL) {
		WRN("0x%llx GCE not support, dup to 0x%llx", sm->unique_id, master->unique_id);
		ret = mtk_tv_sm_gce_dup(&sm->gce, &master->gce);
	}
	if (ret)
		ERR("0x%llx GCE init fail, ret=%d", sm->unique_id, ret);
#endif
	return 0;
}

int mtk_tv_sm_deinit(struct mtk_sm_context *sm)
{
	struct ml_info *info = &_sm_info;
	int ret = 0;

	if (!sm || !info->init) {
		ERR("Invalid input");
		return -EINVAL;
	}

	// ML
	ret = mtk_tv_sm_ml_deinit(&sm->ml);
	if (ret)
		ERR("0x%llx ML deinit fail, ret=%d", sm->unique_id, ret);

	// GCE
#if SM_GCE_ENABLE
	ret = mtk_tv_sm_gce_deinit(&sm->gce);
	if (ret)
		ERR("0x%llx GCE deinit fail, ret=%d", sm->unique_id, ret);
#endif
	return 0;
}

int mtk_tv_sm_get_mem_index(struct mtk_sm_context *sm)
{
	struct ml_info *info = &_sm_info;
	int ml_ret = 0;
#if SM_GCE_ENABLE
	int gce_ret = 0;
#endif

	if (!sm || !info->init) {
		ERR("Invalid input");
		return -EINVAL;
	}

	// ML
	ml_ret = mtk_tv_sm_ml_get_mem_index(&sm->ml);
	if (ml_ret)
		WRN("0x%llx get ML mem fail, ret=%d", sm->unique_id, ml_ret);

	// GCE
#if SM_GCE_ENABLE
	gce_ret = mtk_tv_sm_gce_get_mem_index(&sm->gce);
	if (gce_ret)
		ERR("0x%llx get GCE mem fail, ret=%d", sm->unique_id, gce_ret);

	return (info->use_gce) ? gce_ret : ml_ret;
#else
	return ml_ret;
#endif
}

int mtk_tv_sm_write_cmd(struct mtk_sm_context *sm, uint16_t count, struct sm_reg *regs)
{
	return mtk_tv_sm_write_cmd_delay(sm, count, regs, 0);
}

int mtk_tv_sm_write_cmd_delay(struct mtk_sm_context *sm,
	uint16_t count, struct sm_reg *regs, uint8_t vsync_num)
{
	struct ml_info *info = &_sm_info;
	int ret = 0;

	if (!sm || !regs || !info->init) {
		ERR("Invalid input");
		return -EINVAL;
	}

#if SM_GCE_ENABLE
	if (info->use_gce == false) {
#endif
		// ML
		ret = mtk_tv_sm_ml_write_cmd_delay(&sm->ml, count, regs, vsync_num);
		if (ret)
			ERR("0x%llx write ML cmd fail, ret=%d", sm->unique_id, ret);
#if SM_GCE_ENABLE
	} else {
		// GCE
		ret = mtk_tv_sm_gce_write_cmd_delay(&sm->gce, count, regs, vsync_num);
		if (ret)
			ERR("0x%llx write GCE cmd fail, ret=%d", sm->unique_id, ret);
	}
#endif
	return ret;
}

int mtk_tv_sm_fire(struct mtk_sm_context *sm)
{
	struct ml_info *info = &_sm_info;
	int ml_ret = 0;
#if SM_GCE_ENABLE
	int gce_ret = 0;
#endif

	if (!sm || !info->init) {
		ERR("Invalid input");
		return -EINVAL;
	}

	// ML
	ml_ret = mtk_tv_sm_ml_fire(&sm->ml);
	if (ml_ret < 0)
		ERR("0x%llx ML fire fail, ret=%d", sm->unique_id, ml_ret);

	// GCE
#if SM_GCE_ENABLE
	gce_ret = mtk_tv_sm_gce_fire(&sm->gce);
	if (gce_ret < 0)
		ERR("0x%llx GCE fire fail, ret=%d", sm->unique_id, gce_ret);

	return (info->use_gce) ? gce_ret : ml_ret;
#else
	return ml_ret;
#endif
}

int mtk_tv_sm_get_mem_info(struct mtk_sm_context *sm, void **start_addr, void **max_addr)
{
	struct cmd_buf *cmd_buf;
	uint32_t alloc_size;
	void *alloc_addr;
	void *align_addr;

	if (!sm || !start_addr || !max_addr) {
		ERR("Invalid input");
		return -EINVAL;
	}

	alloc_size = (sm->mem_cmd_cnt * SM_CMD_SIZE)
				+ sizeof(struct cmd_buf) /* reserve for struct cmd_buf */
				+ SM_CMD_SIZE;  /* reserve for align */
	alloc_addr = MALLOC(alloc_size);
	if (alloc_addr == NULL) {
		ERR("alloc %u bytes fail", alloc_size);
		return -ENOMEM;
	}
	align_addr = TO_PTR(ALIGN(TO_U64(alloc_addr), SM_CMD_SIZE))
			+ sizeof(struct cmd_buf); /* reserve for struct cmd_buf */
	cmd_buf = container_of(align_addr, struct cmd_buf, start_addr);
	cmd_buf->alloc_addr = alloc_addr;

	*start_addr = align_addr;
	*max_addr   = align_addr + (sm->mem_cmd_cnt * SM_CMD_SIZE);
	LOG("alloc %p, align %p", alloc_addr, align_addr);
	return 0;
}

int mtk_tv_sm_put_mem_info(struct mtk_sm_context *sm, void *start_addr)
{
	struct cmd_buf *cmd_buf;

	cmd_buf = container_of(start_addr, struct cmd_buf, start_addr);
	LOG("alloc %p, align %p", cmd_buf->alloc_addr, start_addr);
	FREE(cmd_buf->alloc_addr);
	return 0;
}

int mtk_tv_sm_set_mem_cmds(struct mtk_sm_context *sm, void *start_addr, uint16_t cmd_cnt)
{
	struct ml_info *info = &_sm_info;
	int ret = 0;

	if (!sm || !start_addr || !info->init) {
		ERR("Invalid input");
		return -EINVAL;
	}

#if SM_GCE_ENABLE
	if (info->use_gce == false) {
#endif
		// ML
		ret = mtk_tv_sm_ml_set_mem_cmds(&sm->ml, start_addr, cmd_cnt);
		if (ret)
			ERR("0x%llx write ML cmd fail, ret=%d", sm->unique_id, ret);
#if SM_GCE_ENABLE
	} else {
		// GCE
		ret |= mtk_tv_sm_gce_set_mem_cmds(&sm->gce, start_addr, cmd_cnt);
		if (ret)
			ERR("0x%llx write GCE cmd fail, ret=%d", sm->unique_id, ret);
	}
#endif
	return ret;
}

bool mtk_tv_sm_is_mem_ready(struct mtk_sm_context *sm)
{
	struct ml_info *info = &_sm_info;

	if (!sm || !info->init) {
		ERR("Invalid input");
		return false;
	}
#if SM_GCE_ENABLE
	return (info->use_gce == false) ? mtk_tv_sm_ml_is_mem_ready(&sm->ml) :
						mtk_tv_sm_gce_is_mem_ready(&sm->gce);
#else
	return mtk_tv_sm_ml_is_mem_ready(&sm->ml);
#endif
}

int mtk_tv_sm_write_cmd_in_ds(
	uint64_t ml_va, uint64_t max_addr, uint16_t count, struct sm_reg *regs)
{
	struct sm_ml_direct_write_mem_info sm_mem_info;
	enum sm_return_type sm_ret;

	memset(&sm_mem_info, 0, sizeof(struct sm_ml_direct_write_mem_info));
	sm_mem_info.reg            = (struct sm_reg *)regs;
	sm_mem_info.cmd_cnt        = count;
	sm_mem_info.va_address     = ml_va;
	sm_mem_info.va_max_address = max_addr;
	sm_mem_info.add_32_support = FALSE;
	sm_ret = sm_ml_write_cmd(&sm_mem_info);
	if (sm_ret != E_SM_RET_OK) {
		ERR("sm_ml_write_cmd error, ret=%d", sm_ret);
		return -ENOEXEC;
	}
	return 0;
}

ssize_t mtk_tv_sm_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct ml_info *info = &_sm_info;
	int len = 0;

	if (!info->init)
		return SYSFS_PRINT(len, buf, "sm info doesn't init");

	SYSFS_PRINT(len, buf, "sm_init    :%d", info->init);
	SYSFS_PRINT(len, buf, "gce_driver :%d", info->gce_driver);
	SYSFS_PRINT(len, buf, "support_gce:%d", info->support_gce);
	SYSFS_PRINT(len, buf, "use_gce    :%d", info->use_gce);
	return len;
}

ssize_t mtk_tv_sm_store(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	struct ml_info *info = &_sm_info;
	uint64_t val = 0;

	if (info->init != true) {
		pr_crit("SM is not init");
		goto EXIT;
	}
	if (info->gce_driver != true) {
		pr_crit("GCE driver is disable");
		goto EXIT;
	}
	if (info->support_gce != true) {
		pr_crit("SM not support GCE");
		goto EXIT;
	}
	if (mtk_render_common_parser_cmd_int(buf, "gce", &val) == 0) {
		pr_crit("SM is using %s", (val) ? "GCE" : "ML");
		info->use_gce = (bool)val;
	}
EXIT:
	return count;
}
