/*
 * Copyright 2024 Sony Corporation
 */

#include <rtos_switch.h>
#include <asm/gpio.h>
#include <autodet.h>
#include <dtbo.h>
#include <debug_impl.h>
#include <dm.h>
#include <linux/libfdt.h>
#include <mtk_dtbo.h>
#include <sony_fs.h>

#define ENV_DEBUG_RTOS_LOG				"dbgRTOSLOG"
#define STR_DEBUG_PMU					"PMU"
#define PMU_LOG_DTBO_NAME_FOR_KERNEL			"Sony_det_RS232C_M6L"
#define PMU_LOG_DTBO_NAME_FOR_PMU			"pmu_Sony_pmu_log_enable"
#define SONY_DT_MAP					"sony/autodet/sony_dt_map.txt"
#define MAX_CM4_DTBO_PATH_LENGTH			4096
#define ENV_STR_SIZE					8

static uint32_t dt_map_idx;
static struct sony_dt_map_t *dt_map_info;

static int allocate_memory(void)
{
	if (dt_map_info == NULL) {
		// allocate memory for dt_map_info
		dt_map_info = (struct sony_dt_map_t *)malloc(sizeof(struct sony_dt_map_t) * MAX_DT_MAP_ITEMS);
		assert(dt_map_info != NULL);
		memset(dt_map_info, 0x00, sizeof(struct sony_dt_map_t) * MAX_DT_MAP_ITEMS);
	}
	return 0;
}

static void free_memory(void)
{
	if (dt_map_info != NULL) {
		// free memory
		free(dt_map_info);
		dt_map_info = NULL;
		dt_map_idx = 0;
	}
}

static int parse_dt_map_data(const char *buf)
{
	char *find = NULL;
	char readline[128] = { 0 };
	int len = 0;

	while (*buf != 0x00) {
		if (dt_map_idx == MAX_DT_MAP_ITEMS) {
			UBOOT_ERROR("Error: out of space in dt_map_info.\n");
			return -1;
		}
		// skip line starting with "["
		if (*buf == 0x5b) {
			buf = strchr(buf, 0x0a);
			++buf;
		}
		// search 'LF'(0x0a)
		find = strchr(buf, 0x0a);
		if (find != NULL) {
			len = find - buf;
			if (len > sizeof(readline)) {
				len = sizeof(readline);
				UBOOT_ERROR("buffer size over.\n");
			}
			memset(readline, 0x00, sizeof(readline));
			memcpy(readline, buf, len);
			buf += len;
		}
		// search '='(0x3d)
		find = strchr(readline, 0x3d);
		if (find != NULL) {
			memcpy(dt_map_info[dt_map_idx].device_name, readline, find - readline);
			++find;
			dt_map_info[dt_map_idx].dtb_entry = strtoul(find, NULL, 10);
		}
		if (dt_map_info[dt_map_idx].device_name[0] != 0x00)
			dt_map_idx++;
		buf++;
	}
	return 0;
}

static int sony_dtb_overlay(const char *device_name)
{
	int i;
	int ret;

	for (i = 0; i < dt_map_idx; i++) {
		if (strncmp(dt_map_info[i].device_name, device_name, MAX_DEVICE_NAME_LENGTH) == 0) {
			ret = dtb_overlay(dt_map_info[i].dtb_entry);
			if (ret != 0) {
				UBOOT_ERROR("dtb_overlay %d: %s failed.\n", dt_map_info[i].dtb_entry, dt_map_info[i].device_name);
				return -1;
			}
			add_dtbo_idx(dt_map_info[i].dtb_entry);
			printf("dtb_overlay %d: %s done.\n", dt_map_info[i].dtb_entry, dt_map_info[i].device_name);

			return 0;
		}
	}
	printf("dtb_overlay: %s not found.\n", device_name);

	return -1;
}


static int open_and_parser(void)
{
	char *buf = NULL;
	int size = -1, ret = 0;

	size = ext4_f_size(BOOTDATA_PART_NAME, SONY_DT_MAP);
	if (size == -1) {
		UBOOT_ERROR("file size error.\n");
		return -1;
	}

	buf = malloc(size + 1);
	if (buf == NULL) {
		UBOOT_ERROR("malloc error\n");
		return -1;
	}

	memset(buf, 0, size + 1);
	if (ext4_f_load(BOOTDATA_PART_NAME, SONY_DT_MAP, buf) > 0)
		ret = parse_dt_map_data((const char *)buf);

	free(buf);
	return ret;
}

static int sony_dtbo_ovarlay_for_kernel_dt(void)
{
	int ret = 0;

	// allocate memory
	allocate_memory();

	// open device map file
	ret = open_and_parser();
	if (ret != 0) {
		UBOOT_ERROR("device map file open error. ret=%d\n", ret);
		free_memory();
		return -1;
	}

	// execute dtb overlay
	sony_dtb_overlay(PMU_LOG_DTBO_NAME_FOR_KERNEL);

	// free memory
	free_memory();

	return 0;
}

static int sony_dtbo_ovarlay_for_pmu_dt(void)
{
	int offset = -1, length = 0, fullpath_length = 0, ret = -1;
	unsigned long dtb_addr = 0;
	char *dtbo_path = NULL, *new_dtbo_path = NULL;
	char fullpath[MAX_VALUE_LENGTH] = { 0 };

	snprintf(fullpath, sizeof(fullpath) - 1, "/pmu/%s.dtbo", PMU_LOG_DTBO_NAME_FOR_PMU);
	fullpath_length = strnlen(fullpath, MAX_VALUE_LENGTH);

	get_uboot_dtb_addr(&dtb_addr);
	offset = fdt_node_offset_by_compatible((const void *)dtb_addr, -1, SONY_CM4_COMPATIBLE);
	if (offset < 0) {
		UBOOT_ERROR("%s node doesn't exist.\n", SONY_CM4_COMPATIBLE);
		return -1;
	}

	dtbo_path = (char *)fdt_getprop((const void *)dtb_addr, offset, SONY_CM4_DTBO_PATH, &length);
	//length + fullpath_length + null space
	if (length + fullpath_length + 1 > MAX_CM4_DTBO_PATH_LENGTH) {
		UBOOT_ERROR("buffer size error.\n");
		return -1;
	}

	new_dtbo_path = malloc(MAX_CM4_DTBO_PATH_LENGTH);
	if (!new_dtbo_path) {
		UBOOT_ERROR("malloc error.\n");
		return -1;
	}
	memset(new_dtbo_path, 0, MAX_CM4_DTBO_PATH_LENGTH);
	memcpy(new_dtbo_path, dtbo_path, length);
	memcpy(new_dtbo_path + length, fullpath, fullpath_length);

	length += fullpath_length + 1;
	ret = fdt_setprop((void *)dtb_addr, offset, SONY_CM4_DTBO_PATH, (const void *)new_dtbo_path, length);
	if (ret < 0) {
		UBOOT_ERROR("%s setprop error.\n", SONY_CM4_DTBO_PATH);
		free(new_dtbo_path);
		return -1;
	}
	free(new_dtbo_path);

	return 0;
}

int sony_dtbo_ovarlay_for_rtos_log(void)
{
	char *envp = NULL;

	envp = env_get(ENV_DEBUG_RTOS_LOG);
	if (!envp)
		return 0;

	if (strncmp(envp, STR_DEBUG_PMU, ENV_STR_SIZE) == 0) {
		if (is_uboot_dtb()) {
			UBOOT_INFO("[RTOS LOG SWITCH] dtb overlay for PMU dt.\n");
			sony_dtbo_ovarlay_for_pmu_dt();
		} else {
			UBOOT_INFO("[RTOS LOG SWITCH] dtb overlay for Kernel dt.\n");
			sony_dtbo_ovarlay_for_kernel_dt();
		}
	}

	return 0;
}
