// 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 <linux/dma-buf.h>
#include <drm/mtk_tv_drm.h>

#include "mtk_tv_drm_kms.h"
#include "mtk_tv_drm_crtc.h"
#include "mtk_tv_drm_metabuf.h"
#include "mtk_tv_drm_pqmap.h"
#include "mtk_tv_drm_autogen.h"
#include "mtk_tv_drm_log.h"
#include "mtk_tv_sm.h"
#include "hwreg_render_stub.h"

//--------------------------------------------------------------------------------------------------
//  Local Defines
//--------------------------------------------------------------------------------------------------
#define MTK_DRM_MODEL MTK_DRM_MODEL_PQMAP
#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 DWORK_DELAY_MS	100

//--------------------------------------------------------------------------------------------------
//  Local Enum and Structures
//--------------------------------------------------------------------------------------------------

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

//--------------------------------------------------------------------------------------------------
//  Local Functions
//--------------------------------------------------------------------------------------------------
static char _mtk_render_pqmap_pim_callback(
	int tag,
	const char *data,
	const size_t datalen,
	void *autogen)
{
	reg_func *regfunc = (reg_func *)data;
	uint32_t reg_idx;
	uint32_t reg_value;
	int ret = true;
	int i;

	if (!regfunc) {
		ERR("regfunc = NULL");
		return false;
	}
	switch (tag) {
	case EXTMEM_TAG_REGISTER_SETTINGS:
		LOG("NTS_HWREG num = %u", regfunc->padded_data.fn_num);
		for (i = 0; i < regfunc->padded_data.fn_num; ++i) {
			reg_idx = regfunc->fn_tbl[i].fn_idx;
			reg_value = regfunc->fn_tbl[i].fn_para;
			LOG("  NTS_HWREG[0x%x]:%u", reg_idx, reg_value);
			if (mtk_tv_drm_autogen_set_nts_hw_reg(autogen, reg_idx, reg_value)) {
				ERR("set NTS_HW reg(0x%x, 0x%x) error", reg_idx, reg_value);
				ret = false;
			}
		}
		break;

	case EXTMEM_TAG_VREGISTER_SETTINGS:
		LOG("VRREG num = %u", regfunc->padded_data.fn_num);
		for (i = 0; i < regfunc->padded_data.fn_num; ++i) {
			reg_idx = regfunc->fn_tbl[i].fn_idx;
			reg_value = regfunc->fn_tbl[i].fn_para;
			LOG("  SWREG[0x%x]:%u", reg_idx, reg_value);
			if (mtk_tv_drm_autogen_set_sw_reg(autogen, reg_idx, reg_value)) {
				ERR("set SW reg(0x%x, 0x%x) error", reg_idx, reg_value);
				ret = false;
			}
		}
		break;

	case EXTMEM_TAG_CUS_VREGISTER_SETTINGS:
		LOG("CUSREG num = %u", regfunc->padded_data.fn_num);
		for (i = 0; i < regfunc->padded_data.fn_num; ++i) {
			reg_idx = regfunc->fn_tbl[i].fn_idx;
			reg_value = regfunc->fn_tbl[i].fn_para;
			LOG("  CUSREG[0x%x]:%u", reg_idx, reg_value);
			if (mtk_tv_drm_autogen_set_cus_reg(autogen, reg_idx, reg_value)) {
				ERR("set CUS SW reg(0x%x, 0x%x) error", reg_idx, reg_value);
				ret = false;
			}
		}
		break;

	default:
		ret = false;
		break;
	}
	return ret;
}

static void _mtk_render_pqmap_dwork_callback(struct work_struct *work)
{
	struct delayed_work *dwork = NULL;
	struct mtk_pqmap_context *pqmap_ctx = NULL;

	LOG("start delayed work");
	dwork = container_of(work, struct delayed_work, work);
	pqmap_ctx = container_of(dwork, struct mtk_pqmap_context, dwork);
	mtk_tv_drm_pqmap_resume(pqmap_ctx);
}

//-------------------------------------------------------------------------------------------------
//  Global Functions
//-------------------------------------------------------------------------------------------------
int mtk_tv_drm_pqmap_init(
	struct mtk_pqmap_context *ctx,
	struct drm_mtk_tv_pqmap_param *param)
{
	struct mtk_pqmap_info *pqmap = NULL;
	struct mtk_tv_drm_metabuf metabuf = {0};
	uint8_t i;
	int ret = -EINVAL;

	if (ctx == NULL || param == NULL) {
		ERR("Invalid input, ctx: 0x%p, param: 0x%p", ctx, param);
		return -EINVAL;
	}
	mtk_tv_drm_pqmap_deinit(ctx);
	INIT_DELAYED_WORK(&ctx->dwork, _mtk_render_pqmap_dwork_callback);
	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i) {
		pqmap = &ctx->pqmap[i];
		LOG("qmap %d", i);
		LOG("  pim fd   = %d", param->pim_dma_fd[i]);
		LOG("  pim size = %d", param->pim_size[i]);
		drv_STUB("PQMAP_PIM_SIZE", param->pim_size[i]);
		drv_STUB("PQMAP_PIM_FD",   param->pim_dma_fd[i]);

		// check pim size
		if (param->pim_size[i] == 0) {
			LOG("do nothing, pim %d size = %u", i, param->pim_size[i]);
			pqmap->init = -ENOENT;
			continue;
		}

		// convert ION to metabuf
		memset(&metabuf, 0, sizeof(struct mtk_tv_drm_metabuf));
		if (mtk_tv_drm_metabuf_alloc_by_ion(&metabuf, param->pim_dma_fd[i])) {
			ERR("mtk_tv_drm_metabuf_alloc_by_ion(%d) fail", param->pim_dma_fd[i]);
			pqmap->init = -EINVAL;
			continue;
		}
		if (param->pim_size[i] > metabuf.size) {
			ERR("ION size error (pim %u > dma %u)", param->pim_size[i], metabuf.size);
			mtk_tv_drm_metabuf_free(&metabuf);
			pqmap->init = -EBADF;
			continue;
		}

		// store pim
		pqmap->pim_va = MALLOC(param->pim_size[i]);
		if (pqmap->pim_va == NULL) {
			ERR("kmalloc(%u) failed", param->pim_size[i]);
			mtk_tv_drm_metabuf_free(&metabuf);
			pqmap->init = -ENOMEM;
			continue;
		}
		memcpy(pqmap->pim_va, metabuf.addr, param->pim_size[i]);
		pqmap->pim_size = param->pim_size[i];
		mtk_tv_drm_metabuf_free(&metabuf);

		// init pqmap
		if (!pqmap_pim_init(&pqmap->pim_handle, pqmap->pim_va, pqmap->pim_size)) {
			ERR("render PIM init failed");
			FREE(pqmap->pim_va);
			pqmap->pim_va = NULL;
			pqmap->pim_size = 0;
			ret = -EPERM;
			continue;
		}

		pqmap->init = true;
		LOG("pqmap %d init done", i);
	}
	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i) {
		// if anyone is inited successfully
		if (ctx->pqmap[i].init == true)
			return 0;
	}
	return ret;
}

int mtk_tv_drm_pqmap_deinit(
	struct mtk_pqmap_context *ctx)
{
	struct mtk_pqmap_info *pqmap = NULL;
	uint8_t i;

	if (ctx == NULL) {
		ERR("Invalid input");
		return -EINVAL;
	}
	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i) {
		pqmap = &ctx->pqmap[i];
		if (pqmap->init != true)
			continue;
		if (pqmap->pim_handle)
			pqmap_pim_destroy(&pqmap->pim_handle);
		if (pqmap->pim_va != NULL)
			FREE(pqmap->pim_va);
	}
	memset(ctx, 0, sizeof(struct mtk_pqmap_context));
	LOG("pqmap deinit done");
	return 0;
}

int mtk_tv_drm_pqmap_suspend(
	struct mtk_pqmap_context *pqmap_ctx)
{
	if (pqmap_ctx == NULL) {
		ERR("Invalid input");
		return -EINVAL;
	}
	cancel_delayed_work(&pqmap_ctx->dwork);
	return 0;
}

int mtk_tv_drm_pqmap_resume(
	struct mtk_pqmap_context *pqmap_ctx)
{
	struct mtk_tv_kms_context *kms = NULL;
	struct mtk_sm_context *ml = NULL;
	uint32_t node_sum = 0;
	uint8_t i;
	int ret = 0;

	if (pqmap_ctx == NULL) {
		ERR("Invalid input");
		return -EINVAL;
	}
	kms = container_of(pqmap_ctx, struct mtk_tv_kms_context, pqmap_priv);
	ml = &kms->video_priv.disp_irq_ml;

	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i)
		node_sum += pqmap_ctx->last_nums[i];

	// restore last nodes
	if (node_sum == 0 || pqmap_ctx->last_metabuf.addr == NULL) {
		LOG("No need resume, total node = %d, dma addr = %p",
			node_sum, pqmap_ctx->last_metabuf.addr);
		return 0;
	}
	ret = mtk_tv_drm_pqmap_write_ml(pqmap_ctx, ml, (uint16_t *)pqmap_ctx->last_nums,
							pqmap_ctx->last_metabuf.addr);
	if (ret >= 0) {
		LOG("node_sum:%u, dma_fd:%d", node_sum, pqmap_ctx->last_metabuf.ion_info.ion_fd);
	} else if (ret == -EAGAIN) {
		LOG("schedule delayed work");
		schedule_delayed_work(&pqmap_ctx->dwork, msecs_to_jiffies(DWORK_DELAY_MS));
	} else {
		ERR("PQMAP fire fail, ret=%d", ret);
		return ret;
	}
	return 0;
}

int mtk_tv_drm_pqmap_set_nodes(
	struct mtk_pqmap_context *pqmap_ctx,
	struct drm_mtk_tv_pqmap_node_dmafd *nodes)
{
	struct mtk_tv_kms_context *kms = NULL;
	struct mtk_sm_context *ml = NULL;
	struct mtk_tv_drm_metabuf metabuf = {0};
	uint32_t node_sum = 0, node_size = 0;
	uint8_t i;
	int ret = 0;

	if (!pqmap_ctx || !nodes) {
		ERR("Invalid input");
		return -EINVAL;
	}
	kms = container_of(pqmap_ctx, struct mtk_tv_kms_context, pqmap_priv);
	ml = &kms->video_priv.disp_irq_ml;

	// covert dma_fd into metabuf
	memset(&metabuf, 0, sizeof(struct mtk_tv_drm_metabuf));
	ret = mtk_tv_drm_metabuf_alloc_by_ion(&metabuf, nodes->node_dma_fd);
	if (ret) {
		ERR("Convert DMA_FD %d fail, ret=%d", nodes->node_dma_fd, ret);
		return ret;
	}

	// check dma buffer size and expected size
	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i)
		node_sum += nodes->node_num[i];
	node_size = node_sum * sizeof(uint16_t);
	if (metabuf.size < node_size) {
		ERR("DMA size error, %u < %u", metabuf.size, node_size);
		mtk_tv_drm_metabuf_free(&metabuf);
		return -EINVAL;
	}

	// use pqmap to convert cmds and write into ml
	ret = mtk_tv_drm_pqmap_write_ml(pqmap_ctx, ml, (uint16_t *)nodes->node_num, metabuf.addr);
	if (ret) {
		ERR("PQMAP fire fail, ret=%d", ret);
		mtk_tv_drm_metabuf_free(&metabuf);
		return ret;
	}

	LOG("node_sum:%u, node_size:%u, dma_fd:%d", node_sum, node_size, metabuf.ion_info.ion_fd);

	// store last nodes
	if (pqmap_ctx->last_metabuf.addr != NULL) {
		memset(&pqmap_ctx->last_nums, 0, sizeof(uint16_t) * MTK_DRM_PQMAP_NUM_MAX);
		mtk_tv_drm_metabuf_free(&pqmap_ctx->last_metabuf);
		memset(&pqmap_ctx->last_metabuf, 0, sizeof(struct mtk_tv_drm_metabuf));
	}
	pqmap_ctx->last_metabuf = metabuf;
	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i)
		pqmap_ctx->last_nums[i] = nodes->node_num[i];
	return ret;
}

int mtk_tv_drm_pqmap_update(
	struct mtk_pqmap_context *pqmap_ctx,
	struct mtk_sm_context *ml_ctx,
	struct drm_mtk_tv_pqmap_node_array *node_array)
{
	if (!pqmap_ctx || !ml_ctx || !node_array) {
		ERR("Invalid input");
		return -EINVAL;
	}
	return mtk_tv_drm_pqmap_write_ml(pqmap_ctx, ml_ctx,
		node_array->qmap_num, node_array->node_array);
}

int mtk_tv_drm_pqmap_write_ml(
	struct mtk_pqmap_context *pqmap_ctx,
	struct mtk_sm_context *ml_ctx,
	uint16_t *node_num,
	uint16_t *node_buf)
{
	struct mtk_tv_kms_context *kms = NULL;
	struct mtk_autogen_context *autogen = NULL;

	struct mtk_pqmap_info *pqmap = NULL;
	void *ml_mem_start_addr = NULL;
	void *ml_mem_max_addr = NULL;
	uint32_t autogen_cmd_size = 0;
	uint32_t i, j, k = 0;
	int ret = 0;

	if (!pqmap_ctx || !ml_ctx || !node_num || !node_buf) {
		ERR("Invalid input");
		return -EINVAL;
	}
	kms = container_of(pqmap_ctx, struct mtk_tv_kms_context, pqmap_priv);
	autogen = &kms->autogen_priv;

	// get ml buffer from ML
	ret = mtk_tv_sm_get_mem_info(ml_ctx, &ml_mem_start_addr, &ml_mem_max_addr);
	if (ret < 0) {
		ERR("ML get memory info error");
		return ret;
	}

	// set ml buffer to Autogen
	ret = mtk_tv_drm_autogen_set_mem_info(autogen, ml_mem_start_addr, ml_mem_max_addr);
	if (ret < 0) {
		if (ret == -EAGAIN)
			LOG("Set ML memory info again");
		else
			ERR("Set ML memory info error");
		return ret;
	}

	// foreach pqmap
	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i) {
		pqmap = &pqmap_ctx->pqmap[i];

		// check does the pqmap be inited
		if (pqmap->init != true) {
			ERR("pqmap %d is not inited", i);
			continue;
		}

		// skip if the pqmap is no node
		LOG("pqmap %d: node_num %u", i, node_num[i]);
		if (node_num[i] == 0)
			continue;

		// write cmds to ml buffer by pqmap
		drv_STUB("PQMAP_TRIG_NUM", node_num[i]);
		for (j = 0; j < node_num[i]; ++j) {
			LOG("  node[%d] = 0x%x", j, node_buf[k]);
			pqmap_pim_load_setting(pqmap->pim_handle, node_buf[k++], 0,
				_mtk_render_pqmap_pim_callback, autogen);
		}
	}

	// gen cmd number of AutoGen written
	ret = mtk_tv_drm_autogen_get_cmd_size(autogen, &autogen_cmd_size);
	if (ret < 0) {
		ERR("Get Autogen command size error");
		ret = -EPERM;
		goto ERROR;
	}

	// write cmds to ML
	ret = mtk_tv_sm_set_mem_cmds(ml_ctx, ml_mem_start_addr, autogen_cmd_size);
	if (ret < 0) {
		ERR("Add ML command size error");
		ret = -EPERM;
	}
ERROR:
	mtk_tv_drm_autogen_release_mem_info(autogen);
	mtk_tv_sm_put_mem_info(ml_ctx, ml_mem_start_addr);
	return ret;
}

int mtk_tv_drm_pqmap_check_buffer(
	void *data,
	uint32_t size)
{
	struct drm_mtk_tv_pqmap_node_array *node_array = NULL;
	uint32_t expect_size = 0;
	uint32_t node_num = 0;
	uint32_t i;

	if (size < sizeof(struct drm_mtk_tv_pqmap_node_array)) {
		ERR("blob too small: size=%u, sturct=%zu",
			size, sizeof(struct drm_mtk_tv_pqmap_node_array));
		return -1;
	}
	node_array = (struct drm_mtk_tv_pqmap_node_array *)data;

	// sum of all qmap nodes
	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; ++i) {
		node_num += node_array->qmap_num[i];
		LOG("qmap %d num = %u", i, node_array->qmap_num[i]);
	}

	// expected buffer size
	expect_size = sizeof(struct drm_mtk_tv_pqmap_node_array);
	expect_size += sizeof(uint16_t) * node_array->node_num;

	// check condition
	LOG("node num     = %u", node_array->node_num);
	LOG("buffer size  = %u", size);
	LOG("expect size  = %u", expect_size);
	if (node_num != node_array->node_num || size != expect_size) {
		ERR("blob check error: size(%u != %u) or num(%u != %u)\n",
			size, expect_size, node_num, node_array->node_num);
		return -1;
	}
	return 0;
}

int mtk_tv_drm_pqmap_set_info_ioctl(
	struct drm_device *drm,
	void *data,
	struct drm_file *file_priv)
{
	struct drm_crtc *crtc = NULL;
	struct mtk_tv_drm_crtc *mtk_tv_crtc = NULL;
	struct mtk_drm_plane *mplane = NULL;
	struct mtk_tv_kms_context *kms = NULL;

	// get kms
	drm_for_each_crtc(crtc, drm) {
		mtk_tv_crtc = to_mtk_tv_crtc(crtc);
		mplane = mtk_tv_crtc->plane[MTK_DRM_PLANE_TYPE_VIDEO];
		kms = (struct mtk_tv_kms_context *)mplane->crtc_private;
		break;
	}
	return mtk_tv_drm_pqmap_init(&kms->pqmap_priv, (struct drm_mtk_tv_pqmap_param *)data);
}

int mtk_tv_drm_pqmap_set_nodes_ioctl(
	struct drm_device *drm,
	void *data,
	struct drm_file *file_priv)
{
	struct drm_crtc *crtc = NULL;
	struct mtk_tv_drm_crtc *mtk_tv_crtc = NULL;
	struct mtk_drm_plane *mplane = NULL;
	struct mtk_tv_kms_context *kms = NULL;

	// get kms
	drm_for_each_crtc(crtc, drm) {
		mtk_tv_crtc = to_mtk_tv_crtc(crtc);
		mplane = mtk_tv_crtc->plane[MTK_DRM_PLANE_TYPE_VIDEO];
		kms = (struct mtk_tv_kms_context *)mplane->crtc_private;
		break;
	}
	return mtk_tv_drm_pqmap_set_nodes(&kms->pqmap_priv,
		(struct drm_mtk_tv_pqmap_node_dmafd *)data);
}

ssize_t render_qmap_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct mtk_tv_kms_context *kms;
	struct mtk_pqmap_context *qmap_ctx;
	struct mtk_pqmap_info *qmap;
	struct pqmap_version version;
	uint32_t i, j, k = 0;
	uint16_t *nodes;
	int len = 0;

	if (!dev || !buf) {
		ERR("Invalid inputs");
		return -EINVAL;
	}
	kms = dev_get_drvdata(dev);
	qmap_ctx = &kms->pqmap_priv;
	nodes = qmap_ctx->last_metabuf.addr;

	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; i++) {
		qmap = &qmap_ctx->pqmap[i];

		// basic info
		SYSFS_PRINT(len, buf, "=== QMAP %d ===", i);
		SYSFS_PRINT(len, buf, "pim init :%d", qmap->init);
		SYSFS_PRINT(len, buf, "pim size :%d", qmap->pim_size);

		// total
		memset(&version, 0, sizeof(struct pqmap_version));
		pqmap_pim_get_version(qmap->pim_handle, &version);
		SYSFS_PRINT(len, buf, "total ver:0x%x.0x%x.0x%x",
			version.u16MajorNumber, version.u16MinorNumber, version.u32Revision);

		// hwreg
		memset(&version, 0, sizeof(struct pqmap_version));
		pqmap_pim_get_hwreg_version(qmap->pim_handle, &version);
		SYSFS_PRINT(len, buf, "hwreg ver:0x%x.0x%x.0x%x",
			version.u16MajorNumber, version.u16MinorNumber, version.u32Revision);

		// swreg
		memset(&version, 0, sizeof(struct pqmap_version));
		pqmap_pim_get_swreg_version(qmap->pim_handle, &version);
		SYSFS_PRINT(len, buf, "swreg ver:0x%x.0x%x.0x%x",
			version.u16MajorNumber, version.u16MinorNumber, version.u32Revision);

		// last input
		SYSFS_PRINT(len, buf, "last input num:%d", qmap_ctx->last_nums[i]);
		SYSFS_PRCONT(len, buf, "last input nodes:");
		for (j = 0; j < qmap_ctx->last_nums[i]; j++)
			SYSFS_PRCONT(len, buf, "0x%x ", nodes[k++]);
		SYSFS_PRCONT(len, buf, "\n\n");
	}
	return len;
}

ssize_t render_qmap_store(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	struct mtk_tv_kms_context *kms;
	struct mtk_pqmap_info *qmap;
	long val = 0;
	uint8_t i;
	int err;

	if (!dev || !buf) {
		ERR("Invalid inputs");
		return -EINVAL;
	}
	kms = dev_get_drvdata(dev);

	err = kstrtol(buf, 0, &val);
	if (err)
		return err;

	for (i = 0; i < MTK_DRM_PQMAP_NUM_MAX; i++) {
		qmap = &kms->pqmap_priv.pqmap[i];
		if (qmap->pim_handle != NULL)
			qmap->init = (int)val;
	}
	return count;
}
