// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */
#include <linux/module.h>
#include <drm/mtk_tv_drm.h>
#include <linux/soc/mediatek/mtk-cmdq-ext.h>

#include "mtk_tv_drm_kms.h"
#include "mtk_tv_drm_log.h"
#include "mtk_tv_sm_gce.h"

#define GCE_TEST 1 // 0:disable 1:mickey 2:others
//--------------------------------------------------------------------------------------------------
//  Local Defines
//--------------------------------------------------------------------------------------------------
#define MTK_DRM_MODEL MTK_DRM_MODEL_SCRIPTMGT_ML
#define ERR(fmt, args...) pr_err("[%s][%d] " fmt, __func__, __LINE__, ##args)
#define WRN(fmt, args...) pr_warn("[%s][%d] " fmt, __func__, __LINE__, ##args)
#define LOG(fmt, args...) pr_info("[%s][%d] " fmt, __func__, __LINE__, ##args)

#define SM_GCE_INVALID_MEM_INDEX	(0xFF)
#define SM_GCE_MEM_FAIL_MAX		(120)

#define GCE_DISP_VIDEO_INDEX		(0) // index of 'mboxes' in drm dts
#define GCE_DISP_VIDEO_NAME		"ml_trig_disp" // index of 'gce-events' in drm dts

// Convert ML cmd to CODA
/***************************************************************
 *	mask[63:48] | bank[47:23] | offset[22:16] | data[15:0]
 ***************************************************************/
#define ML_CMD_TO_CODA_DATA(cmd)	((uint16_t)(cmd & 0xFFFF))
#define ML_CMD_TO_CODA_MASK(cmd)	((uint16_t)((cmd >> 48) & 0xFFFF))
#define ML_CMD_TO_CODA_ADDR(cmd)	({			\
	uint8_t __offset = (uint8_t)((cmd >> 16) & 0x7F);	\
	uint32_t __bank = (uint32_t)((cmd >> 23) & 0x1FFFF);	\
	(uint32_t)((__bank << 8) | __offset); })

// Convert FLD mask to bitwise mask
/*********************************************
 *	width[31:16] | shift[15:8] | ac[7:0]
 *********************************************/
#define FLD_MASK_TO_BIT_MASK(fld)	({			\
	uint16_t __shift = (fld & 0x0000FF00) >> 8;		\
	uint16_t __width = (fld & 0xFFFF0000) >> 16;		\
	(__width == 0 || (__shift + __width) > 16) ? 0 :	\
	(uint32_t)(0xFFFF >> (16 - __width)) << __shift; })

//--------------------------------------------------------------------------------------------------
//  Local Enum and Structures
//--------------------------------------------------------------------------------------------------
struct work_node {
	struct list_head list;
	uint16_t delay;
	uint16_t count;
	struct sm_reg regs[];
};

//--------------------------------------------------------------------------------------------------
//  Local Variables
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
//  Local Functions
//--------------------------------------------------------------------------------------------------
static inline int _sm_gce_add_regs(
	struct mtk_sm_gce_context *gce,
	uint16_t count,
	struct sm_reg *regs)
{
	struct mtk_sm_gce_pkg *pkg = &gce->pkgs[gce->mem_idx];
	uint16_t cmd_cnt = 0, i;
	uint32_t mask;
	int ret;

	if (gce->cmd_cnt + count > gce->pkg_cmd_cnt)
		return -ENOBUFS;

	for (i = 0; i < count; i++) {
		mask = FLD_MASK_TO_BIT_MASK(regs[i].mask);
		ret = cmdq_pkt_write(pkg->pkt, NULL, regs[i].addr, regs[i].data, mask);
#if (GCE_TEST == 2)
		drv_hwreg_common_writetwobytemask(regs[i].addr, regs[i].data, regs[i].mask);
#endif
		if (ret) {
			ERR("PKG write bank:0x%x, mask:0%x(0x%x), data:0x%x fail, ret=%d",
				regs[i].addr, regs[i].mask, mask, regs[i].data, ret);
			continue;
		}
		cmd_cnt++;
	}
	gce->cmd_cnt += cmd_cnt;
	return 0;
}

static inline int _sm_gce_add_regs_delay(
	struct mtk_sm_gce_context *gce,
	uint16_t count,
	struct sm_reg *regs,
	uint8_t vsync_num)
{
	struct work_node *work;
	uint32_t regs_size;

	regs_size = sizeof(struct sm_reg) * count;
	work = ZALLOC(sizeof(struct work_node) + regs_size);
	if (work == NULL)
		return -ENOMEM;

	work->count = count;
	work->delay = vsync_num;
	gce->list_cmd_cnt += count;
	memcpy(work->regs, regs, regs_size);
	list_add_tail(&work->list, &gce->work_list);
	return 0;
}

static inline int _sm_gce_add_cmds(
	struct mtk_sm_gce_context *gce,
	uint16_t count,
	void *start_addr)
{
	struct mtk_sm_gce_pkg *pkg = &gce->pkgs[gce->mem_idx];
	uint64_t *cmds = start_addr;
	uint32_t addr, mask, data;
	uint16_t cmd_cnt = 0;
	uint16_t i;
	int ret;

	if (gce->cmd_cnt + count > gce->pkg_cmd_cnt)
		return -ENOBUFS;

	for (i = 0; i < count; i++) {
		addr = ML_CMD_TO_CODA_ADDR(cmds[i]);
		mask = FLD_MASK_TO_BIT_MASK(ML_CMD_TO_CODA_MASK(cmds[i]));
		data = ML_CMD_TO_CODA_DATA(cmds[i]);
		ret = cmdq_pkt_write(pkg->pkt, NULL, addr, data, mask);
#if (GCE_TEST == 2)
		drv_hwreg_common_writetwobytemask(addr, data, ML_CMD_TO_CODA_MASK(cmds[i]));
#endif
		if (ret) {
			ERR("PKG write cmd 0x%llx(bank:0x%x/mask:0%x(0x%x)/data:0x%x) fail, ret=%d",
				cmds[i], addr, mask, data, ret);
			continue;
		}
		cmd_cnt++;
	}
	gce->cmd_cnt += cmd_cnt;
	return 0;
}

static inline int _sm_gce_add_cmds_delay(
	struct mtk_sm_gce_context *gce,
	uint16_t count,
	void *start_addr,
	uint8_t vsync_num)
{
	struct work_node *work;
	uint64_t *cmds = start_addr;
	uint32_t addr, mask, data, regs_size;
	uint16_t i;

	regs_size = sizeof(struct sm_reg) * count;
	work = ZALLOC(sizeof(struct work_node) + regs_size);
	if (work == NULL)
		return -ENOMEM;

	for (i = 0; i < count; i++) {
		addr = ML_CMD_TO_CODA_ADDR(cmds[i]);
		mask = ML_CMD_TO_CODA_MASK(cmds[i]);
		data = ML_CMD_TO_CODA_DATA(cmds[i]);
		work->regs[i].addr = addr;
		work->regs[i].mask = mask;
		work->regs[i].data = data;
	}
	work->count = count;
	work->delay = vsync_num;
	gce->list_cmd_cnt += count;
	list_add_tail(&work->list, &gce->work_list);
	return 0;
}

static inline int _sm_gce_reset(
	struct mtk_sm_gce_context *gce)
{
	struct list_head *node, *tmp;
	struct work_node *work;
	uint8_t i;
	int ret;

	// destroy gce
	gce->init = false;
	cmdq_mbox_disable(gce->client->chan);
	cmdq_mbox_stop(gce->client);
	cmdq_mbox_destroy(gce->client);
	gce->client = NULL;

	// free work_node
	list_for_each_safe(node, tmp, &gce->work_list) {
		work = list_entry(node, struct work_node, list);
		list_del(node);
		FREE((void *)work);
	}

	// reset pkgs
	for (i = 0; i < gce->pkg_buf_cnt; i++) {
		gce->pkgs[i].use = false;
		if (gce->pkgs[i].pkt != NULL) {
			cmdq_pkt_destroy(gce->pkgs[i].pkt);
			gce->pkgs[i].pkt = NULL;
		}
	}

	// create gce client
	gce->client = cmdq_mbox_create(gce->dev, gce->mbox_index);
	if (IS_ERR_OR_NULL(gce->client)) {
		ret = IS_ERR_OR_NULL(gce->client);
		ERR("GCE create index(%d) fail, ret=%d", gce->mbox_index, ret);
		return ret;
	}
	cmdq_mbox_enable(gce->client->chan);

	// reset gce
	gce->mem_idx = SM_GCE_INVALID_MEM_INDEX;
	gce->cmd_cnt = 0;
	gce->mem_fail_cnt = 0;
	gce->list_cmd_cnt = 0;
	gce->init = true;
	return 0;
}

static void _sm_gce_flush_cb(struct cmdq_cb_data data)
{
	struct mtk_sm_gce_pkg *pkg = data.data;

	if (pkg == NULL) {
		ERR("PKG is NULL");
		return;
	}
	WRITE_ONCE(pkg->use, false);
	if (data.err)
		ERR("%s pkt=%d flush fail, ret=%d", pkg->gce->name, pkg->idx, data.err);
	else
		LOG("%s pkt=%d flush done", pkg->gce->name, pkg->idx);
}

//-------------------------------------------------------------------------------------------------
//  Global Functions
//-------------------------------------------------------------------------------------------------
int mtk_tv_sm_gce_init(struct mtk_sm_gce_context *gce,
	struct device *dev,
	enum sm_ml_unique_id unique_id,
	uint8_t buffer_cnt,
	uint16_t cmd_cnt)
{
	struct mtk_sm_gce_pkg *pkgs = NULL;
	uint32_t size;
	int ret = 0;
	uint8_t i;

	if (gce == NULL) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (gce->init == true) {
		ERR("GCE is inited");
		return 0;
	}
	memset(gce, 0, sizeof(struct mtk_sm_gce_context));
	gce->dev = dev;
	gce->pkg_buf_cnt = buffer_cnt;
	gce->pkg_cmd_cnt = cmd_cnt;
	gce->master_ctx = NULL;
	gce->mem_idx = SM_GCE_INVALID_MEM_INDEX;

	// get gce index
	switch (unique_id) {
	case E_SM_ML_UID_DISP_VIDEO:
		gce->name = "GCE_VDO";
		WRN("%s not support", gce->name);
		return -ENODEV;

	case E_SM_ML_UID_DISP_IRQ:
		gce->mbox_index = GCE_DISP_VIDEO_INDEX;
		gce->event_name = GCE_DISP_VIDEO_NAME;
		gce->name = "GCE_IRQ";
		break;

	case E_SM_ML_UID_VGS:
		gce->name = "GCE_VGS";
		WRN("%s not support", gce->name);
		return -ENODEV;

	default:
		ERR("Invalid GCE unique_id:0x%llx", unique_id);
		return -EINVAL;
	}

	// allocate pkgs
	size = buffer_cnt * sizeof(struct mtk_sm_gce_pkg);
	gce->pkgs = ZALLOC(size);
	if (gce->pkgs == NULL) {
		ERR("ZALLOC(%u) fail", size);
		return -ENOMEM;
	}
	for (i = 0; i < buffer_cnt; i++) {
		gce->pkgs[i].idx = i;
		gce->pkgs[i].gce = gce;
		gce->pkgs[i].use = false;
		gce->pkgs[i].pkt = NULL;
	}

	// get gce event
	ret = cmdq_dev_get_event(gce->dev, gce->event_name);
	if (ret < 0) {
		ERR("GCE get event(%s) fail, ret=%d", gce->event_name, ret);
		goto ERROR;
	}
	gce->event_index = ret;

	// create gce client
	gce->client = cmdq_mbox_create(gce->dev, gce->mbox_index);
	if (IS_ERR_OR_NULL(gce->client)) {
		ret = IS_ERR_OR_NULL(gce->client);
		ERR("GCE create index(%d) fail, ret=%d", gce->mbox_index, ret);
		goto ERROR;
	}
	cmdq_mbox_enable(gce->client->chan);

	INIT_LIST_HEAD(&gce->work_list);
	mutex_init(&gce->mutex);
	gce->init = true;

	LOG("%s: mbox_index=%d, event_name=%s, event_index=%d",
		gce->name, gce->mbox_index, gce->event_name, gce->event_index);
	return 0;

ERROR:
	// goto ERROR only after gce is allocated
	if (gce->client) {
		cmdq_mbox_destroy(gce->client);
		gce->client = NULL;
	}
	FREE(pkgs);
	return ret;
}

int mtk_tv_sm_gce_dup(struct mtk_sm_gce_context *dst_gce, struct mtk_sm_gce_context *src_gce)
{
	if (!dst_gce || !src_gce) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (dst_gce->init == true) {
		ERR("dst has inited");
		return -EINVAL;
	}
	// if src_sm is also a slave, find the master
	while (src_gce->master_ctx != NULL) {
		src_gce = src_gce->master_ctx;
		LOG("Goto new src:%p", src_gce);
	}
	LOG("%s's master is %s", dst_gce->name, src_gce->name);
	if (src_gce->init == false) {
		ERR("src has not inited");
		return -EINVAL;
	}
	dst_gce->master_ctx = src_gce;
	dst_gce->init       = true;
	return 0;
}

int mtk_tv_sm_gce_deinit(struct mtk_sm_gce_context *gce)
{
	struct list_head *node, *tmp;
	struct work_node *work;
	uint8_t i;

	if (!gce) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (gce->init == false) {
		WRN("GCE is NOT inited");
		return 0;
	}
	gce->init = false;
	if (gce->master_ctx == NULL) {
		cmdq_mbox_disable(gce->client->chan);
		cmdq_mbox_stop(gce->client);
		cmdq_mbox_destroy(gce->client);
		for (i = 0; i < gce->pkg_buf_cnt; i++) {
			if (gce->pkgs[i].pkt != NULL)
				cmdq_pkt_destroy(gce->pkgs[i].pkt);
		}
		list_for_each_safe(node, tmp, &gce->work_list) {
			work = list_entry(node, struct work_node, list);
			list_del(node);
			FREE((void *)work);
		}
		mutex_destroy(&gce->mutex);
	}
	LOG("%s", gce->name);
	memset(gce, 0, sizeof(struct mtk_sm_gce_context));
	return 0;
}

int mtk_tv_sm_gce_get_mem_index(struct mtk_sm_gce_context *gce)
{
	struct list_head *node, *tmp;
	struct work_node *work;
	uint8_t i, mem_idx;
	int ret = 0;

	if (!gce) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (gce->init == false) {
		ERR("GCE is NOT inited");
		return -EINVAL;
	}
	if (gce->master_ctx != NULL) {
		LOG("%s not master", gce->name);
		return 0;
	}
	mutex_lock(&gce->mutex);

	if (gce->mem_idx == SM_GCE_INVALID_MEM_INDEX) {
		// get memory index
		mem_idx = SM_GCE_INVALID_MEM_INDEX;
		for (i = 0; i < gce->pkg_buf_cnt; i++) {
			if (gce->pkgs[i].use == true) // the pkg is firing
				continue;

			if (gce->pkgs[i].pkt != NULL) { // the pkg is fired, destroy it
				cmdq_pkt_destroy(gce->pkgs[i].pkt);
				gce->pkgs[i].pkt = NULL;
			}
			if (mem_idx == SM_GCE_INVALID_MEM_INDEX) {
				gce->pkgs[i].pkt = cmdq_pkt_create(gce->client);
				if (IS_ERR_OR_NULL(gce->pkgs[i].pkt)) {
					ERR("%s create pkt%d fail, ret=%d", i, gce->name,
									PTR_ERR(gce->pkgs[i].pkt));
					gce->pkgs[i].pkt = NULL;
				} else {
					gce->pkgs[i].use = true;
					mem_idx = i;
				}
			}
		}
		if (mem_idx == SM_GCE_INVALID_MEM_INDEX) {
			gce->mem_fail_cnt++;
			WRN("%s find mem fail, fail_cnt:%d", gce->name, gce->mem_fail_cnt);
			if (gce->mem_fail_cnt >= SM_GCE_MEM_FAIL_MAX) {
				ret = _sm_gce_reset(gce);
				WRN("%s reset, ret=%d", gce->name, ret);
			}
			ret = -EINVAL;
			goto EXIT;
		}
		gce->mem_fail_cnt = 0;
		gce->mem_idx = mem_idx;

		// Clear GCE event and wait
		cmdq_pkt_clear_event(gce->pkgs[mem_idx].pkt, gce->event_index);
		cmdq_pkt_wfe(gce->pkgs[mem_idx].pkt, gce->event_index);

		LOG("%s get mem_idx=%d", gce->name, gce->mem_idx);
	}
	// check work list
	list_for_each_safe(node, tmp, &gce->work_list) {
		work = list_entry(node, struct work_node, list);
		if (work->delay > 0) {
			work->delay--;
			continue;
		}
		ret = _sm_gce_add_cmds(gce,  work->count, work->regs);
		if (ret == 0) {
			LOG("%s mem_idx %u, add %u", gce->name, gce->mem_idx, work->count);
			gce->list_cmd_cnt -= work->count;
			list_del(node);
			FREE((void *)work);
		} else {
			ERR("%s mem_idx:%u, add %u fail, ret=%d",
				gce->name, gce->mem_idx, work->count, ret);
			break;
		}
	}
EXIT:
	mutex_unlock(&gce->mutex);
	return ret;
}

int mtk_tv_sm_gce_write_cmd_delay(struct mtk_sm_gce_context *gce,
	uint16_t count, struct sm_reg *regs, uint8_t vsync_num)
{
	int ret = -EAGAIN;

	if (!gce || !regs) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (gce->init == false) {
		ERR("GCE is NOT inited");
		return -EINVAL;
	}
	if (count == 0) {
		// LOG("%s no command", gce->name);
		return 0;
	}
	if (gce->master_ctx != NULL) {
		LOG("%s use master %s", gce->name, gce->master_ctx->name);
		gce = gce->master_ctx;
	}
	mutex_lock(&gce->mutex);
	if (vsync_num == 0 && gce->mem_idx != SM_GCE_INVALID_MEM_INDEX) {
		ret = _sm_gce_add_regs(gce, count, regs);
		if (!ret)
			LOG("%s mem_idx %u, add %u", gce->name, gce->mem_idx, count);
		else
			WRN("%s mem_idx %u, add %u fail, ret=%d",
				gce->name, gce->mem_idx, count, ret);
	}
	if (ret != 0) {
		ret = _sm_gce_add_regs_delay(gce, count, regs, vsync_num);
		if (!ret)
			LOG("%s no mem_idx, add %u, delay %u", gce->name, count, vsync_num);
		else
			ERR("%s no mem_idx, add %u, delay %u fail, ret=%d",
				gce->name, count, vsync_num, ret);
	}
	mutex_unlock(&gce->mutex);
	return ret;
}

int mtk_tv_sm_gce_fire(struct mtk_sm_gce_context *gce)
{
	struct mtk_sm_gce_pkg *pkg;
	int ret = 0;

	if (!gce) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (gce->init == false) {
		ERR("GCE is NOT inited");
		return -EINVAL;
	}
	if (gce->master_ctx != NULL) {
		LOG("%s not master", gce->name);
		return 0;
	}
	mutex_lock(&gce->mutex);

	if (gce->cmd_cnt == 0) {
		LOG("%s no command", gce->name);
		goto EXIT;
	}
	if (gce->mem_idx == SM_GCE_INVALID_MEM_INDEX) {
		ERR("%s has invalid memory index %d", gce->name, gce->mem_idx);
		ret = -EINVAL;
		goto EXIT;
	}
	pkg = &gce->pkgs[gce->mem_idx];

#if GCE_TEST
	cmdq_pkt_dump_buf(pkg->pkt, 0);
#endif
#if (GCE_TEST == 2)
	_sm_gce_flush_cb((struct cmdq_cb_data){ .data = pkg, .err = 0 });
	ret = 0;
#else
	ret = cmdq_pkt_flush_async(pkg->pkt, _sm_gce_flush_cb, pkg);
	if (ret < 0) {
		ERR("%s flush fail, mem_idx=%d cmd_cnt=%d, ret = %d",
			gce->name, gce->mem_idx, gce->cmd_cnt, ret);
		goto EXIT;
	}
#endif
	LOG("%s mem_idx=%d cmd_cnt=%d", gce->name, gce->mem_idx, gce->cmd_cnt);
	gce->mem_idx  = SM_GCE_INVALID_MEM_INDEX;
	gce->cmd_cnt  = 0;
EXIT:
	mutex_unlock(&gce->mutex);
	return ret;
}

int mtk_tv_sm_gce_set_mem_cmds(struct mtk_sm_gce_context *gce, void *start_addr, uint16_t cmd_cnt)
{
	int ret = -EAGAIN;

	if (!gce || !start_addr) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (gce->init == false) {
		ERR("SM has not inited");
		return -EINVAL;
	}
	if (cmd_cnt == 0) {
		// LOG("%s no command", gce->name);
		return 0;
	}
	if (gce->master_ctx != NULL) {
		LOG("%s use master %s", gce->name, gce->master_ctx->name);
		gce = gce->master_ctx;
	}
	mutex_lock(&gce->mutex);
	if (gce->mem_idx != SM_GCE_INVALID_MEM_INDEX) {
		ret = _sm_gce_add_cmds(gce, cmd_cnt, start_addr);
		if (!ret)
			LOG("%s mem_idx %u, add %u", gce->name, gce->mem_idx, cmd_cnt);
		else
			WRN("%s mem_idx %u, add %u fail, ret=%d",
				gce->name, gce->mem_idx, cmd_cnt, ret);
	}
	if (ret != 0) {
		ret = _sm_gce_add_cmds_delay(gce, cmd_cnt, start_addr, 0);
		if (!ret)
			LOG("%s no mem_idx, store %u", gce->name, cmd_cnt);
		else
			ERR("%s no mem_idx, store %u, fail, ret=%d",
				gce->name, cmd_cnt, ret);
	}
	mutex_unlock(&gce->mutex);
	return ret;
}

bool mtk_tv_sm_gce_is_mem_ready(struct mtk_sm_gce_context *gce)
{
	bool ret;

	if (!gce) {
		ERR("Invalid input");
		return -ENXIO;
	}
	if (gce->init == false) {
		ERR("GCE is NOT inited");
		return -EINVAL;
	}
	if (gce->master_ctx != NULL) {
		LOG("%s use master %s", gce->name, gce->master_ctx->name);
		gce = gce->master_ctx;
	}
	mutex_lock(&gce->mutex);
	ret = (gce->mem_idx != SM_GCE_INVALID_MEM_INDEX);
	mutex_unlock(&gce->mutex);
	return ret;
}
