/*
 * Copyright 2021,2025 Sony Corporation
 */

#include <common.h>
#include <asm/gpio.h>
#include <dts_parser.h>
#include <src/vbo_frame/vbo_frame.h>
#include <dm/uclass.h>
#include <dm/platdata.h>
#include <linux/errno.h>
#include <dm/lists.h>
#include <dm/device-internal.h>

#include "iniutility.h"
#include "mtk_gegop.h"
#include "MsTypes.h"
#include "apiGOP.h"
#include "MsOS.h"
#include "qhb_internal.h"
#include "system_impl.h"
#include "sony_ac_off_det.h"
#include "Sony_be_boot_params.h"
#include "sony_panel.h"
#include "be_boot_cmd.h"
#include "ldbe_boot_sequence.h"

//#define VERIFICATION_MODE// Use Prepare FWdata code.
//#define BE_BOOT_DEBUG_MODE

#define WIDTH	3840
#define HEIGHT	2160
#ifdef VERIFICATION_MODE
#define FIRST_FWDATA_WIDTH	64
#endif
#define FIRST_FWDATA_HEIGHT	243
#define FWDATA_HEIGHT		400//0x01000000 MAX line 1092
#define GPIO_ERROR_TIMEOUT_MS	5000 // 5000ms
#define GPIO_POLLING_INTERVAL_MS	10 // 10ms

#define CONFIG_BE_BOOT_DEBUG

#ifdef CONFIG_BE_BOOT_DEBUG
#define BE_BOOT_DBG(fmt, args...) printf(fmt, ##args)
#else
#define BE_BOOT_DBG(fmt, args...)
#endif

static struct udevice *dev;

#ifdef BE_BOOT_DEBUG_MODE
static unsigned long vxo_frame_block;
#endif


int check_GPIO_value_with_timeout(unsigned int io_num, unsigned int expect_val, unsigned int timeout_ms)
{
	unsigned int IO_check_counter = 0;
	unsigned int max_count = 500; // default 5000ms

	if (timeout_ms > 0)
		max_count = timeout_ms / GPIO_POLLING_INTERVAL_MS;

	BE_BOOT_DBG("%s: start\n", __func__);
	while (true) {
		if (gpio_get_value(io_num) == expect_val) {
			printf("%s: success\n", __func__);
			break;
		}
		IO_check_counter++;
		if (IO_check_counter >= max_count) {
			printf("%s: error (timeout: %dms)\n", __func__, timeout_ms);
			return -ETIME;// add error action
		}
		mdelay(GPIO_POLLING_INTERVAL_MS);
	}

	return 0;
}

int check_GPIO_value(unsigned int io_num, unsigned int expect_val)
{
	return check_GPIO_value_with_timeout(io_num, expect_val, GPIO_ERROR_TIMEOUT_MS);
}


int set_be_power_on(void)
{
	int ret = 0;
	struct be_boot_pwr_on be_boot_param;

	//parse_local_struct
	ret = get_be_power_on_param(dev, &be_boot_param);
	if (ret != 0) {
		printf("get_param_fail\n");
		return -1; //failure
	}

	BE_BOOT_DBG("%s start.\n", __func__);

	gpio_request(be_boot_param.pwr_rst_io, "BE_RST");

	gpio_direction_output(be_boot_param.pwr_rst_io, 1);
	mdelay(be_boot_param.stable_delay);

	return 0;

}

int set_be_power_on_pre(void)
{
	int ret = 0;
	struct be_boot_pwr_on be_boot_param;

	//parse_local_struct
	ret = get_be_power_on_param(dev, &be_boot_param);
	if (ret != 0) {
		printf("get_param_fail\n");
		return -1; //failure
	}

	BE_BOOT_DBG("%s start be_p_on1=%u be_p_on2=%u on_delay=%u\n", __func__, be_boot_param.pwr_1_io, be_boot_param.pwr_2_io, be_boot_param.on_delay);

	gpio_request(be_boot_param.pwr_1_io, "P_ON1");
	gpio_request(be_boot_param.pwr_2_io, "P_ON2");

	if (check_ac_off_detected()) {
		printf("Skip pwr_1_io\n");
		return 0;
	}
	gpio_direction_output(be_boot_param.pwr_1_io, 1);
	mdelay(be_boot_param.on_delay);

	if (check_ac_off_detected()) {
		printf("Skip pwr_2_io\n");
		return 0;
	}
	gpio_direction_output(be_boot_param.pwr_2_io, 1);
	mdelay(be_boot_param.on_delay);

	return 0;

}


#ifdef VERIFICATION_MODE //Prepaer FWdata code. If use it, need FW data file "1-1.frm" ~ "2-2.frm".
int create_frame_buffer(unsigned int frame_number, unsigned long long *dump_buffer_vaddr, unsigned int buffer_size)
{
	unsigned int tmp_dot_swap;
	unsigned int dot_num, line_num;
	unsigned int width_set;
	unsigned int *buf;
	unsigned int *dump_buf;
	unsigned int *fw_data;
	unsigned long file_size;
	unsigned long fw_data_paddr;
	char *size_cmd = NULL;
	char *load_cmd = NULL;
	const char fr_path[3][128] = {"1-1.frm", "2-1.frm", "2-2.frm"};

// Get frame data file size.
	size_cmd = malloc(128);
	memset(size_cmd, 0, 128);
	snprintf(size_cmd, 128, "ext4size mmc 0:8 %s", fr_path[frame_number]);
	if (run_command(size_cmd, 0)) {
		printf("size_get error.\n");
		free(size_cmd);
		return -1;
	}
	file_size = env_get_hex("filesize", 0);
	free(size_cmd);

// load frame data.
	fw_data = malloc(file_size);
	memset(fw_data, 0, file_size);
	//sony_fs.c define VA2PA(addr)(addr > CONFIG_SYS_MIU0_NON_CACHE)?(addr - CONFIG_SYS_MIU0_NON_CACHE):(addr-CONFIG_SYS_MIU0_CACHE)
	fw_data_paddr = ((unsigned long)fw_data > 0x60000000)?((unsigned long)fw_data - 0x60000000):((unsigned long)fw_data - 0x20000000);

	load_cmd = malloc(128);
	memset(load_cmd, 0, 128);
	snprintf(load_cmd, 128, "ext4load mmc 0:8 %lx %s", fw_data_paddr, fr_path[frame_number]);
	if (run_command(load_cmd, 0)) {
		printf("load_command error.\n");
		free(fw_data);
		free(load_cmd);
		return -1;
	}
	memcpy(fw_data, (void *)fw_data_paddr, file_size);
	free(load_cmd);

// 1st frame data to 64dot left side justified.
	if (frame_number == 0) {
		buf = malloc(buffer_size);
		memset(buf, 0, buffer_size);

		width_set = FIRST_FWDATA_WIDTH;
		for (line_num = 0; line_num < FIRST_FWDATA_HEIGHT; line_num++) {
			if (line_num == (FIRST_FWDATA_HEIGHT - 1))
				width_set = 44;//last fwdata is 243line 44dot

			for (dot_num = 0; dot_num < width_set; dot_num++)
				buf[line_num * WIDTH + dot_num] = fw_data[line_num * FIRST_FWDATA_WIDTH + dot_num];

		}
	}
// Swap B and G for BE
	dump_buf = malloc(buffer_size);
	memset(dump_buf, 0, buffer_size);

	if (frame_number == 0) {
		width_set = FIRST_FWDATA_WIDTH;
		for (line_num = 0; line_num < FIRST_FWDATA_HEIGHT; line_num++) {
			if (line_num == (FIRST_FWDATA_HEIGHT - 1))
				width_set = 44;//last fwdata is 243line 44dot

			for (dot_num = 0; dot_num < width_set; dot_num++) {
				tmp_dot_swap = buf[line_num * WIDTH + dot_num];
				dump_buf[line_num * WIDTH + dot_num] = (unsigned int)(((tmp_dot_swap & 0x000000FF) << 8) | ((tmp_dot_swap & 0x0000FF00) >> 8) | (tmp_dot_swap & 0xFFFF0000));
			}
		}
	} else {
		for (dot_num = 0; dot_num < (buffer_size / 4); dot_num++) {
			tmp_dot_swap = fw_data[dot_num];
			dump_buf[dot_num] = (unsigned int)(((tmp_dot_swap & 0x000000FF) << 8) | ((tmp_dot_swap & 0x0000FF00) >> 8) | (tmp_dot_swap & 0xFFFF0000));
		}
	}

//copy to VAdder
	memcpy(dump_buffer_vaddr, dump_buf, buffer_size);
	flush_dcache_range((unsigned long)dump_buffer_vaddr, (unsigned long)(dump_buffer_vaddr+buffer_size));

	free(fw_data);
	free(dump_buf);
	if (frame_number == 0)
		free(buf);

	return 0; //success
}
#endif


int enable_load_framedata(MS_U8 GOPIdx)
{
	GOP_InitInfo stGopInit;
	int resolution_width = 0, resolution_height = 0, hsync_start = 0;

	memset(&stGopInit, 0, sizeof(GOP_InitInfo));
	mtk_gop_get_resolution(&resolution_width, &resolution_height, &hsync_start);
	stGopInit.u16PanelWidth = (unsigned short)(resolution_width & 0xFFFF);
	stGopInit.u16PanelHeight = (unsigned short)(resolution_height & 0xFFFF);
	stGopInit.u16PanelHStr = (unsigned short)(hsync_start & 0xFFFF);

	MApi_GOP_InitByGOP(&stGopInit, GOPIdx);
	MApi_GOP_GWIN_UpdateRegOnceEx2(GOPIdx, true, false);
	MApi_GOP_GWIN_SetGOPDst(GOPIdx, E_GOP_DST_FRC);
	MApi_GOP_GWIN_Set_STRETCHWIN(GOPIdx, E_GOP_DST_FRC, 0, 0, WIDTH, HEIGHT);
	MApi_GOP_GWIN_SetBlending(GOPIdx, true, 0xff);
	MApi_GOP_SetByPassMode(GOPIdx, true);

	return 0; //success
}


int transfer_framedata(MS_U8 GOPIdx, MS_U64 phy_addr)
{
	MS_U8 fbid;

	fbid = MApi_GOP_GWIN_GetFreeFBID();
	printf("@@@ %s fbid:%d @@@\n", __func__, fbid);
	MApi_GOP_GWIN_CreateFBbyStaticAddr(fbid, 0, 0, WIDTH, HEIGHT, E_MS_FMT_ARGB8888, phy_addr);
	MApi_GOP_GWIN_MapFB2Win(fbid, GOPIdx);
	MApi_GOP_GWIN_Enable(GOPIdx, true);
	MApi_GOP_GWIN_UpdateRegOnceEx2(GOPIdx, false, false);
	mdelay(16);

	return 0; //success
}


int disable_load_framedata(MS_U8 GOPIdx)
{
	MApi_GOP_GWIN_UpdateRegOnceEx2(GOPIdx, true, false);
	MApi_GOP_SetByPassMode(GOPIdx, false);
	MApi_GOP_GWIN_Enable(GOPIdx, false);
	MApi_GOP_GWIN_UpdateRegOnceEx2(GOPIdx, false, false);

	return 0; //success
}


int get_logo_buf_addr(unsigned long long *phy_addr)
{
	int ret;
	struct dts_mmap mmap;

	if (phy_addr == NULL) {
		printf("Error: phy_addr is Null.\n");
		return -1;
	}

	ret = parse_dt("/mmap_info/MI_BOOTLOGO_BUF", mmap_dt_parser, (void *)&mmap, "reg");
	if (ret < 0 || mmap.address == 0) {
		printf("Error: Get GOP buffer from DTS mmap failure, use default value in DTS GOP buffer\n");
		ret = parse_dt("/mediatek-drm-tv-kms", integer_dt_parser, (void *)phy_addr, "GRAPHIC_BOOTLOGO_BUF_ADDR");
		if (ret < 0 || *phy_addr == 0) {
			printf("Error: Get GOP buffer from DTS GOP failure, Please check u-boot dts\n");
			return -1; //failure
		} else if (*phy_addr > 0xFFFFFFFF) {
			printf("Error: GE/GOP driver not support > 4G mmap address\n");
			return -1; //failure
		}
	} else {
		if (mmap.address > 0xFFFFFFFF) {
			printf("Error: GE/GOP driver not support > 4G mmap address\n");
			return -1; //failure
		}
		BE_BOOT_DBG("debug:mmap.address %llx\n", mmap.address);
		*phy_addr = mmap.address;
	}

	return 0; //success
}


int do_be_boot(void)
{
	MS_U8 GOPIdx = 0;

	unsigned int total_frame_num;
	unsigned int frame_num;
	unsigned int buf_size;
	unsigned int vb1_req_io;
	unsigned long long *dump_buf_vaddr;
	unsigned long long phy_addr = 0x0;

	int vx1req;
	int toggle_check_ret;
	int logo_buf_ret;
	int frame_num_ret;

	logo_buf_ret = get_logo_buf_addr(&phy_addr);
	if (logo_buf_ret != 0) {
		printf("get LOGO_BUF false\n");
		return -1; //failure
	}

//enable load fw data GOP init
	enable_load_framedata(GOPIdx);

//get vb1_req GPIO
	vb1_req_io = get_be_vb1req_io(dev);
	if (vb1_req_io == 0) {
		printf("get_vb1_req_param_fail\n");
		goto error;//failure
	}

// check_GPIO
	gpio_request(vb1_req_io, "VB1_REQ");
	vx1req = gpio_get_value(vb1_req_io);
	BE_BOOT_DBG("Vx1reqPin: %d\n", vx1req);//add error action

//start frame loop
	frame_num_ret = get_beboot_vb1_frame_num();//PQ_function
	if (frame_num_ret < 0) {
		printf("get_beboot_vb1_frame_num fail\n");
		goto error;//failure
	} else {
		total_frame_num = (unsigned int)frame_num_ret;
	}

	for (frame_num = 0; frame_num < total_frame_num; frame_num++) {
#ifdef VERIFICATION_MODE //Prepare FWdata code. If use it, need FW data file "1-1.frm" ~ "2-2.frm".

		if (frame_num == 0)
			buf_size = WIDTH * FIRST_FWDATA_HEIGHT * 4;// 1st FWfile data height 243
		else
			buf_size = WIDTH * FWDATA_HEIGHT * 4;

		dump_buf_vaddr = (unsigned long long *)phyical_addr_to_virtual_addr(phy_addr);
		if (create_frame_buffer(frame_num, dump_buf_vaddr, buf_size) == 0) {
			printf("create_frame_buffer() success.\n");
		} else {
			printf("create_frame_buffer() fail.\n");
			goto error;
		}

#else
		buf_size = 0x01000000;
		dump_buf_vaddr = (unsigned long long *)phyical_addr_to_virtual_addr(phy_addr);
		printf("draw_beboot_vb1_frame() start.\n");
		if (draw_beboot_vb1_frame(frame_num, dump_buf_vaddr, buf_size) == 0) { //PQ_function
			printf("draw_beboot_vb1_frame() success.\n");
		} else {
			printf("draw_beboot_vb1_frame() fail.\n");
			//printf("Production FWdata is preparing now. So transfer alternative data as fill with 0.\n");
			//memset(dump_buf_vaddr, 0, buf_size);
			goto error;
		}

		flush_dcache_range((unsigned long)dump_buf_vaddr, (unsigned long)(dump_buf_vaddr+buf_size));

		printf("VboBoot flip start(framenum %d)\n", frame_num);

#endif
	// Transfer
		transfer_framedata(GOPIdx, (MS_U64)phy_addr);


		//gpio_get_value() vx1req
		if (frame_num % 2 == 0)
			toggle_check_ret = check_GPIO_value(vb1_req_io, 0);
		else
			toggle_check_ret = check_GPIO_value(vb1_req_io, 1);

		if (toggle_check_ret != 0) {
			printf("toggle error.\n");//add error action
			goto error;
		}

		printf("vbo req change(framenum %d)\n", frame_num);

#ifdef BE_BOOT_DEBUG_MODE
		if (vxo_frame_block == frame_num + 1) {
			printf("VXO block!\n");
			while (1)
				;
		}
#endif

	}

//disable :erase the sent image
	disable_load_framedata(GOPIdx);
	return 0; //success

error:
	disable_load_framedata(GOPIdx);
	return -1; //failure

}


int init_params(void)
{
	int ret, offset;
	char node_name[32] = {0};

	if (dev != NULL) { // if already initialized then do nothing
		return 0;
	}

//dt_overlay_check
	parse_dt("/odm/sony_be_boot", string_dt_parser, (void *)&node_name, "compatible");
	BE_BOOT_DBG("node_name:%s\n", node_name);

//dt_bind
	offset = fdt_node_offset_by_compatible(gd->fdt_blob, -1, "sony,be_boot");
	ret = lists_bind_fdt(gd->dm_root, offset_to_ofnode(offset), &dev, false);
	if (ret != 0)
		printf("lists_bind_fdt_fail\n");

	ret = device_bind_by_name(gd->dm_root, false, &be_boot_info, &dev);
	if (ret != 0)
		printf("device_bind_by_name_fail\n");


//do_device
	ret = uclass_get_device_by_name(UCLASS_NOP, "sony_be_boot", &dev);
	if (ret != 0) {
		printf("uclass_get_device_by_name_fail\n");
		return -1; //failure
	}

	return 0; //success
}

int be_boot_sequence(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int ret;
	unsigned int boot_up_io;

	printf("start BE boot\n");

#ifdef BE_BOOT_DEBUG_MODE

	if (argc < 2)
		vxo_frame_block = 0;
	else
		vxo_frame_block = kstrtoul(argv[1], NULL, 0);

	printf("vxo_frame_block = %ld\n", vxo_frame_block);

#endif

//Check BE
	ret = paneldet_getBEType();
	if (ret == BEDET_INVALID) {
		printf("paneldet_getBEType fail\n");
		return -1; //failure
	} else if (ret == BEDET_NONE) {
		printf("Be_boot_sequence: end the sequence because FE ONLY\n");
		return 0; //success
	} else if (ret == BEDET_LDBE) {
		ret = ldbe_boot(dev);
		return ret; // -1:failure, 0:success
	}

	if (is_panel_self_run()) {
		printf("panel_self_run mode. skip BE_boot\n");
		return 0;
	}

	printf("start vboBoot\n");

//Use DeviceDriver
	ret = init_params();
	if (ret != 0) {
		printf("get_be_boot_param_by_driver fail\n");//add error action
		return -1; //failure
	}


	BE_BOOT_DBG("Be_boot_sequence: judge do be_boot. Now,always doing.\n");// add judge do_BEboot action

//BE_power_ctrl
	set_be_power_on();

	boot_up_io = get_be_bootup_io(dev);
	if (boot_up_io == 0) {
		printf("get_boot_up_param_fail\n");
		return -1; //failure
	}
	gpio_request(boot_up_io, "BOOTUP_NOTE");
	ret = check_GPIO_value(boot_up_io, 1);
	if (ret != 0) {
		printf("Be_boot_sequence:BE_power_on fail.\n");//add error action
		return -1; //failure
	}

	printf("BE power ON\n");

//FW_transfer_ctrl
	ret = do_be_boot();
	if (ret != 0) {
		printf("Be_boot_sequence:do_be_boot fail.\n");//add error action
		return -1; //failure
	}

	printf("end vboBoot\n");

	return 0; //success
}

int do_be_power_set(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	init_params();
	if (is_panel_self_run()) {
		if (check_be_type_match(BEDET_LDBE)) {
			printf("##### %s: Panel_self_run mode with LDBE. Skip BE power on...\n", __func__);
			return 0;
		}
	}
	printf("%s: set BE power on\n", __func__);
	set_be_power_on_pre();

	return 0;
}


#ifdef BE_BOOT_DEBUG_MODE
U_BOOT_CMD(be_boot, 2, 1, be_boot_sequence,
#else
U_BOOT_CMD(be_boot, 1, 1, be_boot_sequence,
#endif
		"be vxo boot\n",
		"be vxo boot\n"
);

U_BOOT_CMD(be_power_set, 1, 1, do_be_power_set,
		"be power on\n",
		"be power on\n"
);
