// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * drivers/amlogic/media/di_multi/dolby_sys.c
 *
 * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 */

#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/semaphore.h>
#include <linux/workqueue.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
#include <linux/list.h>
#include <linux/of_reserved_mem.h>
#include <linux/of_irq.h>
#include <linux/uaccess.h>
#include <linux/of_fdt.h>
#include <linux/cma.h>
#include <linux/dma-map-ops.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include "dolby_sys.h"
#include "register.h"
#include <linux/amlogic/media/vfm/vframe.h>
#include <linux/amlogic/media/amdolbyvision/dolby_vision.h>
#define OP_BYPASS_CVM				0x01
#define OP_BYPASS_CSC				0x02
#define OP_CLKGATE_WHEN_LOAD_LUT	0x04

#define DI_FLAG_CHANGE_TC			0x000010

#define DIFLAG_FORCE_CVM				0x01
#define DIFLAG_BYPASS_CVM				0x02
#define DIFLAG_BYPASS_VPP				0x04
#define DIFLAG_USE_SINK_MIN_MAX		0x08
#define DIFLAG_CLKGATE_WHEN_LOAD_LUT	0x10
#define DIFLAG_BYPASS_CSC				0x200
struct di_dolby_hw_s {
	/* SWAP_CTRL2 */
	u32 vsize;
	u32 hsize;
	/* SWAP_CTRL1 */
	u32 vtotal;
	u32 htotal;
	/* SWAP_CTRL3 */
	u32 vwidth;
	u32 hwidth;
	/* SWAP_CTRL4 */
	u32 vpotch;
	u32 hpotch;
	/* SWAP_CTRL0 */
	bool bl_enable;
	bool el_enable;
	bool el_41_mode;
	u8 bl_lay_line_delay;
	u8 el_lay_line_delay;
	bool roundup_output; //add 0.5 for 8bit mode

	/* SWAP_CTRL5 */
	u8 bl_uv_mode;
	u8 el_uv_mode;

	u32 dm_count;
	u32 comp_count;
	u32 lut_count;
#ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION
	struct composer_reg_ipcore *comp_reg;
	struct dm_reg_ipcore1 *dm_reg;
	struct dm_lut_ipcore *dm_lut;

	struct composer_reg_ipcore *last_comp_reg;
	struct dm_reg_ipcore1 *last_dm_reg;
	struct dm_lut_ipcore *last_dm_lut;
#endif
};

enum disignal_format_e {
	FORMAT_INVALID = -1,
	FORMAT_DOVI = 0,
	FORMAT_HDR10 = 1,
	FORMAT_SDR = 2,
	FORMAT_DOVI_LL = 3,
	FORMAT_HLG = 4,
	FORMAT_HDR10PLUS = 5,
	FORMAT_SDR_2020 = 6,
	FORMAT_MVC = 7,
	FORMAT_CUVA = 8
};

struct di_dolby_dev_s {
	enum disignal_format_e src_fmt;

	bool bypass_csc;
	bool bypass_cvm;
	bool lut_endian;

	u8 next_tbl_id;
	u8 cur_tbl_id;

	//struct mutex di_mutex;
#ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION
	u32 update_flag[2];
	struct composer_reg_ipcore comp_reg[2];
	struct dm_reg_ipcore1 dm_reg[2];
	struct dm_lut_ipcore dm_lut[2];
#endif
	struct di_dolby_hw_s hw;
};

static struct di_dolby_dev_s *di_dolby;

void di_dolby_enable(bool enable)
{
	if (enable) {
		DIM_DI_WR_REG_BITS(DI_SC_TOP_CTRL, 1, 24, 1);//dolby enable
		DIM_DI_WR_REG_BITS(DI_SC_TOP_CTRL, 1, 19, 1);//dither enable
	} else {
		DIM_DI_WR_REG_BITS(DI_SC_TOP_CTRL, 0, 24, 1);//dolby enable
		DIM_DI_WR_REG_BITS(DI_SC_TOP_CTRL, 0, 19, 1);//dither enable
	}
}

#ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION
static int di_dolby_core_set(struct di_dolby_hw_s *hw,
			     bool lut_endian,
			     u32 op_flag,
			     u32 update_flag,
			     bool reset)
{
	u32 count;
	u32 bypass_flag = 0;
	int i;
	bool set_lut = false;
	u32 dm_count;
	u32 comp_count;
	u32 lut_count;
	u32 *dm_regs;
	u32 *comp_regs;
	u32 *lut_regs;
	u32 *last_dm;
	u32 *last_comp;

	if (!hw)
		return -1;

	dm_count = hw->dm_count;
	comp_count =  hw->comp_count;
	lut_count = hw->lut_count;
	dm_regs = (uint32_t *)hw->dm_reg;
	comp_regs = (uint32_t *)hw->comp_reg;
	lut_regs = (uint32_t *)hw->dm_lut;
	last_dm = (uint32_t *)hw->last_dm_reg;
	last_comp = (uint32_t *)hw->last_comp_reg;

	//reset = true;

	if (update_flag & DI_FLAG_CHANGE_TC)
		set_lut = true;

	DIM_DI_WR(AMDV_CORE1C_CLKGATE_CTRL, 0);
	DIM_DI_WR(AMDV_CORE1C_SWAP_CTRL1,
		  ((hw->hsize + hw->htotal) << 16) | (hw->vsize + hw->vtotal));
	DIM_DI_WR(AMDV_CORE1C_SWAP_CTRL3,
		  (hw->hwidth << 16) | hw->vwidth);
	DIM_DI_WR(AMDV_CORE1C_SWAP_CTRL4,
		  (hw->hpotch << 16) | hw->vpotch);
	DIM_DI_WR(AMDV_CORE1C_SWAP_CTRL2,
		  (hw->hsize << 16) | hw->vsize);

	DIM_DI_WR(AMDV_CORE1C_SWAP_CTRL5,
		  ((hw->el_uv_mode & 3) << 2) | ((hw->bl_uv_mode & 3) << 0));

	DIM_DI_WR(AMDV_CORE1C_DMA_CTRL, 0x0);
	DIM_DI_WR(AMDV_CORE1C_REG_START + 4, 4);
	DIM_DI_WR(AMDV_CORE1C_REG_START + 2, 1);

	if (!hw->el_enable)
		bypass_flag |= 1 << 3;
	if (op_flag & DIFLAG_BYPASS_CSC)
		bypass_flag |= 1 << 1;
	if (op_flag & DIFLAG_BYPASS_CVM)
		bypass_flag |= 1 << 2;
	if (hw->el_41_mode)
		bypass_flag |= 1 << 3;

	DIM_DI_WR(AMDV_CORE1C_REG_START + 1,
		  0x70 | bypass_flag); // bypass CVM and/or CSC
	DIM_DI_WR(AMDV_CORE1C_REG_START + 1,
		  0x70 | bypass_flag); // for delay

	if (dm_count == 0)
		count = 24;
	else
		count = dm_count;
	for (i = 0; i < count; i++)
		if (reset ||
		    dm_regs[i] != last_dm[i]) {
			DIM_DI_WR(AMDV_CORE1C_REG_START + 6 + i,
				  dm_regs[i]);
		}

	if (comp_count == 0)
		count = 173;
	else
		count = comp_count;
	for (i = 0; i < count; i++)
		if (reset ||
		    comp_regs[i] != last_comp[i]) {
			DIM_DI_WR(AMDV_CORE1C_REG_START + 6 + 44 + i,
				  comp_regs[i]);
		}
	/* metadata program done */
	DIM_DI_WR(AMDV_CORE1C_REG_START + 3, 1);

	if (lut_count == 0)
		count = 256 * 5;
	else
		count = lut_count;
	if (count && (set_lut || reset)) {
		if (op_flag & DIFLAG_CLKGATE_WHEN_LOAD_LUT) {
			DIM_DI_WR_REG_BITS(AMDV_CORE1C_CLKGATE_CTRL,
					   2, 2, 2);
		}
		DIM_DI_WR(AMDV_CORE1C_DMA_CTRL, 0x1401);
		if (lut_endian) {
			for (i = 0; i < count; i += 4) {
				DIM_DI_WR(AMDV_CORE1C_DMA_PORT,
					  lut_regs[i + 3]);
				DIM_DI_WR(AMDV_CORE1C_DMA_PORT,
					  lut_regs[i + 2]);
				DIM_DI_WR(AMDV_CORE1C_DMA_PORT,
					  lut_regs[i + 1]);
				DIM_DI_WR(AMDV_CORE1C_DMA_PORT,
					  lut_regs[i]);
			}
		} else {
			for (i = 0; i < count; i++)
				DIM_DI_WR(AMDV_CORE1C_DMA_PORT,
					  lut_regs[i]);
		}
		if (op_flag & DIFLAG_CLKGATE_WHEN_LOAD_LUT) {
			DIM_DI_WR_REG_BITS(AMDV_CORE1C_CLKGATE_CTRL,
					   0, 2, 2);
		}
	}

	DIM_DI_WR(DI_HDR_IN_HSIZE, hw->hsize);
	DIM_DI_WR(DI_HDR_IN_VSIZE, hw->vsize);

	DIM_DI_WR(AMDV_CORE1C_SWAP_CTRL0,
		  ((hw->roundup_output ? 1 : 0) << 12) |
		  ((hw->bl_lay_line_delay & 0xf) << 8) |
		  ((hw->el_lay_line_delay & 0xf) << 4) |
		  ((hw->el_41_mode ? 1 : 0) << 2) |
		  ((hw->el_enable ? 1 : 0) << 1) |
		  ((hw->bl_enable ? 1 : 0) << 0));
	return 0;
}
#endif

int di_dolby_do_setting(void /*struct di_dolby_dev_s *dev*/)
{
	u32 op_flag = 0;
#ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION
	u32 update_flag = 0;
	int tmp;
#endif

	struct di_dolby_dev_s *dev = di_dolby;

	if (!dev)
		return -1;

	//mutex_lock(&dev->di_mutex);
	if (dev->cur_tbl_id == 0xff) {
		//mutex_unlock(&dev->di_mutex);
		return -2;
	}

	if (dev->bypass_csc)
		op_flag |= DIFLAG_BYPASS_CSC;
	if (dev->bypass_cvm)
		op_flag |= DIFLAG_BYPASS_CVM;

#ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION
	dev->hw.dm_reg = &dev->dm_reg[dev->cur_tbl_id];
	dev->hw.comp_reg = &dev->comp_reg[dev->cur_tbl_id];
	dev->hw.dm_lut = &dev->dm_lut[dev->cur_tbl_id];
	update_flag = dev->update_flag[dev->cur_tbl_id];
	//mutex_unlock(&dev->di_mutex);

	tmp = di_dolby_core_set(&dev->hw, dev->lut_endian,
				op_flag, update_flag, true);
	if (tmp)
		return -3;
	dev->hw.last_dm_reg = dev->hw.dm_reg;
	dev->hw.last_comp_reg = dev->hw.comp_reg;
	dev->hw.last_dm_lut = dev->hw.dm_lut;
#endif
	return 0;
}

#ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION
int di_dolby_update_setting(struct dm_reg_ipcore1 *dm_reg,
			    struct composer_reg_ipcore *comp_reg,
			    struct dm_lut_ipcore *dm_lut,
			    u32 dm_count,
			    u32 comp_count,
			    u32 lut_count,
			    u32 update_flag,
			    u32 hsize,
			    u32 vsize)
{
	struct dm_reg_ipcore1 *new_dm_reg;
	struct composer_reg_ipcore *new_comp_reg;
	struct dm_lut_ipcore *new_dm_lut;

	if (!dm_reg || !comp_reg || !dm_lut || !dm_count || !comp_count ||
	    !lut_count)
		return -1;

	di_dolby->hw.hsize = hsize;
	di_dolby->hw.vsize = vsize;
	//mutex_lock(&di_dolby->di_mutex);
	if (di_dolby->next_tbl_id == 0xff)
		di_dolby->next_tbl_id = di_dolby->cur_tbl_id ^ 1;
	new_dm_reg = &di_dolby->dm_reg[di_dolby->next_tbl_id];
	new_comp_reg = &di_dolby->comp_reg[di_dolby->next_tbl_id];
	new_dm_lut = &di_dolby->dm_lut[di_dolby->next_tbl_id];
	di_dolby->update_flag[di_dolby->next_tbl_id] = update_flag;
	di_dolby->cur_tbl_id = di_dolby->next_tbl_id;
	di_dolby->next_tbl_id = di_dolby->next_tbl_id ^ 1;

	if (dm_count > di_dolby->hw.dm_count)
		dm_count = di_dolby->hw.dm_count;

	if (comp_count > di_dolby->hw.comp_count)
		comp_count = di_dolby->hw.comp_count;

	if (lut_count > di_dolby->hw.lut_count)
		lut_count = di_dolby->hw.lut_count;

	memcpy(new_dm_reg, dm_reg, sizeof(uint32_t) * dm_count);
	memcpy(new_comp_reg, comp_reg, sizeof(uint32_t) * comp_count);
	memcpy(new_dm_lut, dm_lut, sizeof(uint32_t) * lut_count);
	//mutex_unlock(&di_dolby->di_mutex);
	return 0;
}
#endif

void di_dolby_sw_init(void)
{
	di_dolby = vmalloc(sizeof(*di_dolby));
	if (!di_dolby) {
		//pr_info("di_dolby_sw_init fail\n");
		return;
	}
	memset(di_dolby, 0, sizeof(struct di_dolby_dev_s));

	//mutex_init(&di_dolby->di_mutex);

	di_dolby->hw.hsize = 1920;
	di_dolby->hw.vsize = 1080;

	di_dolby->hw.vtotal = 0x60; //0x40
	di_dolby->hw.htotal = 0x160; //0x80
	di_dolby->hw.vwidth = 0x8;
	di_dolby->hw.hwidth = 0x8;
	di_dolby->hw.vpotch = 0x4; //0x10
	di_dolby->hw.hpotch = 0x8;
	di_dolby->hw.bl_enable = true;
	di_dolby->hw.el_enable = false;
	di_dolby->hw.el_41_mode = true;
	di_dolby->hw.bl_lay_line_delay = 0;
	di_dolby->hw.el_lay_line_delay = 0;
	di_dolby->hw.roundup_output = true;
	di_dolby->hw.bl_uv_mode = 2;
	di_dolby->hw.el_uv_mode = 1;
	di_dolby->hw.dm_count = 27;
	di_dolby->hw.comp_count = 173;
	di_dolby->hw.lut_count = 256 * 5;

	di_dolby->bypass_cvm = false;
	di_dolby->bypass_csc = false;
	di_dolby->src_fmt = FORMAT_INVALID;
	di_dolby->lut_endian = true;
	di_dolby->cur_tbl_id = 1;
	di_dolby->next_tbl_id = 0xff;
}
