// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2017-2019 NXP
 */

#include <common.h>
#include <clk.h>
#include <cpu.h>
#include <dm.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <dm/uclass.h>
#include <errno.h>
#include <asm/arch/sci/sci.h>
#include <asm/arch/clock.h>
#include <power-domain.h>
#include <dm/device.h>
#include <dm/uclass-internal.h>
#include <thermal.h>
#include <elf.h>
#include <asm/arch/sid.h>
#include <asm/arch/sys_proto.h>
#include <asm/arch-imx/cpu.h>
#include <asm/armv8/cpu.h>
#include <asm/armv8/mmu.h>
#include <asm/setup.h>
#include <asm/mach-imx/boot_mode.h>
#include <asm/arch/video_common.h>
#include <linux/libfdt.h>
#include <fdt_support.h>
#include <fdtdec.h>
#include <generated/version_autogenerated.h>
#include <asm/mach-imx/imx_vservice.h>
#include <asm/arch/power-domain.h>
#include <spl.h>
#include <asm/arch/lpcg.h>

DECLARE_GLOBAL_DATA_PTR;

struct edma_ch_map {
	sc_rsrc_t ch_start_rsrc;
	u32 ch_start_regs;
	u32 ch_num;
	const char* node_path;
};

#define BT_PASSOVER_TAG	0x504F
struct pass_over_info_t *get_pass_over_info(void)
{
	struct pass_over_info_t *p =
		(struct pass_over_info_t *)PASS_OVER_INFO_ADDR;

	if (p->barker != BT_PASSOVER_TAG ||
	    p->len != sizeof(struct pass_over_info_t))
		return NULL;

	return p;
}

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_RECOVER_SPL_DATA_SECTION)
char __data_save_start[0] __attribute__((section(".__data_save_start")));
char __data_save_end[0] __attribute__((section(".__data_save_end")));

u32 cold_reboot_flag = 1;

static void save_restore_data(void)
{
	u32 data_size = __data_save_end - __data_save_start;

	if (cold_reboot_flag == 1) {
		/* Save data section to data_save section */
		memcpy(__data_save_start, __data_save_start - data_size, data_size);
	} else {
		/* Restore the data_save section to data section */
		memcpy(__data_save_start - data_size, __data_save_start, data_size);
	}
	cold_reboot_flag++;
}
#endif

int arch_cpu_init(void)
{
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_RECOVER_SPL_DATA_SECTION)
	save_restore_data();
#endif

	return 0;
}

int arch_cpu_init_dm(void)
{
	struct udevice *devp;
	int node, ret;

	node = fdt_node_offset_by_compatible(gd->fdt_blob, -1, "fsl,imx8-mu");

	ret = uclass_get_device_by_of_offset(UCLASS_MISC, node, &devp);
	if (ret) {
		printf("could not get scu %d\n", ret);
		return ret;
	}

	struct pass_over_info_t *pass_over;

	if (is_soc_rev(CHIP_REV_A)) {
		pass_over = get_pass_over_info();
		if (pass_over && pass_over->g_ap_mu == 0) {
			/*
			 * When ap_mu is 0, means the U-Boot booted
			 * from first container
			 */
			sc_misc_boot_status(-1, SC_MISC_BOOT_STATUS_SUCCESS);
		}
	}

#ifdef CONFIG_IMX_SMMU
	int sciErr;
	sciErr = sc_pm_set_resource_power_mode(-1, SC_R_SMMU,
				SC_PM_PW_MODE_ON);
	if (sciErr)
		return 0;
#endif

	return 0;
}

#ifdef CONFIG_IMX_BOOTAUX

#ifdef CONFIG_IMX8QM
int arch_auxiliary_core_up(u32 core_id, ulong boot_private_data)
{
	sc_rsrc_t core_rsrc, mu_rsrc;
	sc_faddr_t tcml_addr;
	u32 tcm_size = SZ_256K; /* TCML + TCMU */
	ulong addr;


	switch (core_id) {
	case 0:
		core_rsrc = SC_R_M4_0_PID0;
		tcml_addr = 0x34FE0000;
		mu_rsrc = SC_R_M4_0_MU_1A;
		break;
	case 1:
		core_rsrc = SC_R_M4_1_PID0;
		tcml_addr = 0x38FE0000;
		mu_rsrc = SC_R_M4_1_MU_1A;
		break;
	default:
		printf("Not support this core boot up, ID:%u\n", core_id);
		return -EINVAL;
	}

	addr = (sc_faddr_t)boot_private_data;

	if (addr >= tcml_addr && addr <= tcml_addr + tcm_size) {
		printf("Wrong image address 0x%lx, should not in TCML\n",
			addr);
		return -EINVAL;
	}

	printf("Power on M4 and MU\n");

	if (sc_pm_set_resource_power_mode(-1, core_rsrc, SC_PM_PW_MODE_ON) != SC_ERR_NONE)
		return -EIO;

	if (sc_pm_set_resource_power_mode(-1, mu_rsrc, SC_PM_PW_MODE_ON) != SC_ERR_NONE)
		return -EIO;

	printf("Copy M4 image from 0x%lx to TCML 0x%lx\n", addr, (ulong)tcml_addr);

	if (addr != tcml_addr)
		memcpy((void *)tcml_addr, (void *)addr, tcm_size);

	printf("Start M4 %u\n", core_id);
	if (sc_pm_cpu_start(-1, core_rsrc, true, tcml_addr) != SC_ERR_NONE)
		return -EIO;

	printf("bootaux complete\n");
	return 0;
}
#endif

#ifdef CONFIG_IMX8QXP
static unsigned long load_elf_image_shdr(unsigned long addr)
{
	Elf32_Ehdr *ehdr; /* Elf header structure pointer */
	Elf32_Shdr *shdr; /* Section header structure pointer */
	unsigned char *strtab = 0; /* String table pointer */
	unsigned char *image; /* Binary image pointer */
	int i; /* Loop counter */

	ehdr = (Elf32_Ehdr *)addr;

	/* Find the section header string table for output info */
	shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
			     (ehdr->e_shstrndx * sizeof(Elf32_Shdr)));

	if (shdr->sh_type == SHT_STRTAB)
		strtab = (unsigned char *)(addr + shdr->sh_offset);

	/* Load each appropriate section */
	for (i = 0; i < ehdr->e_shnum; ++i) {
		shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
				     (i * sizeof(Elf32_Shdr)));

		if (!(shdr->sh_flags & SHF_ALLOC) ||
		    shdr->sh_addr == 0 || shdr->sh_size == 0) {
			continue;
		}

		if (strtab) {
			debug("%sing %s @ 0x%08lx (%ld bytes)\n",
			      (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load",
			       &strtab[shdr->sh_name],
			       (unsigned long)shdr->sh_addr,
			       (long)shdr->sh_size);
		}

		if (shdr->sh_type == SHT_NOBITS) {
			memset((void *)(uintptr_t)shdr->sh_addr, 0,
			       shdr->sh_size);
		} else {
			image = (unsigned char *)addr + shdr->sh_offset;
			memcpy((void *)(uintptr_t)shdr->sh_addr,
			       (const void *)image, shdr->sh_size);
		}
		flush_cache(shdr->sh_addr, shdr->sh_size);
	}

	return ehdr->e_entry;
}

int arch_auxiliary_core_up(u32 core_id, ulong boot_private_data)
{
	sc_rsrc_t core_rsrc, mu_rsrc = -1;
	sc_faddr_t aux_core_ram;
	u32 size;
	ulong addr;

	switch (core_id) {
	case 0:
		core_rsrc = SC_R_M4_0_PID0;
		aux_core_ram = 0x34FE0000;
		mu_rsrc = SC_R_M4_0_MU_1A;
		size = SZ_256K;
		break;
	case 1:
		core_rsrc = SC_R_DSP;
		aux_core_ram = 0x596f8000;
		size = SZ_2K;
		break;
	default:
		printf("Not support this core boot up, ID:%u\n", core_id);
		return -EINVAL;
	}

	addr = (sc_faddr_t)boot_private_data;

	if (addr >= aux_core_ram && addr <= aux_core_ram + size) {
		printf("Wrong image address 0x%lx, should not in aux core ram\n",
			addr);
		return -EINVAL;
	}

	printf("Power on aux core %d\n", core_id);

	if (sc_pm_set_resource_power_mode(-1, core_rsrc, SC_PM_PW_MODE_ON) != SC_ERR_NONE)
		return -EIO;

	if (mu_rsrc != -1) {
		if (sc_pm_set_resource_power_mode(-1, mu_rsrc, SC_PM_PW_MODE_ON) != SC_ERR_NONE)
			return -EIO;
	}

	if (core_id == 1) {
		struct power_domain pd;

		if (sc_pm_clock_enable(-1, core_rsrc, SC_PM_CLK_PER, true, false) != SC_ERR_NONE) {
			printf("Error enable clock\n");
			return -EIO;
		}

		lpcg_all_clock_on(AUD_DSP_LPCG);

		if (!power_domain_lookup_name("audio_sai0", &pd)) {
			if (power_domain_on(&pd)) {
				printf("Error power on SAI0\n");
				return -EIO;
			}
		}

		if (!power_domain_lookup_name("audio_ocram", &pd)) {
			if (power_domain_on(&pd)) {
				printf("Error power on HIFI RAM\n");
				return -EIO;
			}
		}

		lpcg_all_clock_on(AUD_OCRAM_LPCG);
		lpcg_all_clock_on(AUD_SAI_0_LPCG);
	}

	printf("Copy image from 0x%lx to 0x%lx\n", addr, (ulong)aux_core_ram);
	if (core_id == 0) {
		/* M4 use bin file */
		memcpy((void *)aux_core_ram, (void *)addr, size);
	} else {
		/* HIFI use elf file */
		if (!valid_elf_image(addr))
			return -1;
		addr = load_elf_image_shdr(addr);
	}

	printf("Start %s\n", core_id == 0 ? "M4" : "HIFI");

	if (sc_pm_cpu_start(-1, core_rsrc, true, aux_core_ram) != SC_ERR_NONE)
		return -EIO;

	printf("bootaux complete\n");
	return 0;
}
#endif

int arch_auxiliary_core_check_up(u32 core_id)
{
	sc_rsrc_t core_rsrc;
	sc_pm_power_mode_t power_mode;

	switch (core_id) {
	case 0:
		core_rsrc = SC_R_M4_0_PID0;
		break;
#ifdef CONFIG_IMX8QM
	case 1:
		core_rsrc = SC_R_M4_1_PID0;
		break;
#endif
	default:
		printf("Not support this core, ID:%u\n", core_id);
		return 0;
	}

	if (sc_pm_get_resource_power_mode(-1, core_rsrc, &power_mode) != SC_ERR_NONE)
		return 0;

	if (power_mode != SC_PM_PW_MODE_OFF)
		return 1;

	return 0;
}
#endif

static bool check_owned_resource(sc_rsrc_t rsrc_id)
{
	bool owned;

	owned = sc_rm_is_resource_owned(-1, rsrc_id);

	return owned;
}

#ifdef CONFIG_IMX_SMMU
struct smmu_sid dev_sids[] = {
};

int imx8_config_smmu_sid(struct smmu_sid *dev_sids, int size)
{
	int i;
	int sciErr = 0;

	if ((dev_sids == NULL) || (size <= 0))
		return 0;

	for (i = 0; i < size; i++) {
		if (!check_owned_resource(dev_sids[i].rsrc)) {
			printf("%s rsrc[%d] not owned\n", __func__, dev_sids[i].rsrc);
			continue;
		}
		sciErr = sc_rm_set_master_sid(-1,
					      dev_sids[i].rsrc,
					      dev_sids[i].sid);
		if (sciErr) {
			printf("set master sid error\n");
			return sciErr;
		}
	}

	return 0;
}
#endif

void arch_preboot_os(void)
{
#if defined(CONFIG_VIDEO_IMXDPUV1)
	imxdpuv1_fb_disable();
#endif
#ifdef CONFIG_IMX_SMMU
	imx8_config_smmu_sid(dev_sids, ARRAY_SIZE(dev_sids));
#endif
}

#if defined(CONFIG_ARCH_MISC_INIT)
#define FSL_SIP_BUILDINFO		0xC2000003
#define FSL_SIP_BUILDINFO_GET_COMMITHASH	0x00
extern uint32_t _end_ofs;

static void set_buildinfo_to_env(uint32_t scfw, uint32_t secofw, char *mkimage, char *atf)
{
	if (!mkimage || !atf)
		return;

	env_set("commit_mkimage", mkimage);
	env_set("commit_atf", atf);
	env_set_hex("commit_scfw", (ulong)scfw);
	env_set_hex("commit_secofw", (ulong)secofw);
}

static void acquire_buildinfo(void)
{
	uint32_t sc_build = 0, sc_commit = 0;
	uint32_t seco_build = 0, seco_commit = 0;
	char *mkimage_commit, *temp;
	uint64_t atf_commit = 0;

	/* Get SCFW build and commit id */
	sc_misc_build_info(-1, &sc_build, &sc_commit);
	if (sc_build == 0) {
		debug("SCFW does not support build info\n");
		sc_commit = 0; /* Display 0 when the build info is not supported*/
	}

	/* Get SECO FW build and commit id */
	sc_seco_build_info(-1, &seco_build, &seco_commit);
	if (seco_build == 0) {
		debug("SECO FW does not support build info\n");
		seco_commit = 0; /* Display 0 when the build info is not supported*/
	}

	/* Get imx-mkimage commit id.
	 * The imx-mkimage puts the commit hash behind the end of u-boot.bin
	 */
	mkimage_commit = (char *)(ulong)(CONFIG_SYS_TEXT_BASE + _end_ofs + fdt_totalsize(gd->fdt_blob));
	temp = mkimage_commit + 8;
	*temp = '\0';

	if (strlen(mkimage_commit) == 0) {
		debug("IMX-MKIMAGE does not support build info\n");
		mkimage_commit = "0"; /* Display 0 */
	}

	/* Get ARM Trusted Firmware commit id */
	atf_commit = call_imx_sip(FSL_SIP_BUILDINFO, FSL_SIP_BUILDINFO_GET_COMMITHASH, 0, 0, 0);
	if (atf_commit == 0xffffffff) {
		debug("ATF does not support build info\n");
		atf_commit = 0x30; /* Display 0, 0 ascii is 0x30 */
	}

	/* Set all to env */
	set_buildinfo_to_env(sc_commit, seco_commit, mkimage_commit, (char *)&atf_commit);

	printf("\n BuildInfo: \n  - SCFW %08x, SECO-FW %08x, IMX-MKIMAGE %s, ATF %s\n  - %s \n\n",
		sc_commit, seco_commit, mkimage_commit, (char *)&atf_commit, U_BOOT_VERSION);
}

int arch_misc_init(void)
{
	acquire_buildinfo();

	return 0;
}
#endif

int print_bootinfo(void)
{
	enum boot_device bt_dev = get_boot_device();

	puts("Boot:  ");
	switch (bt_dev) {
	case SD1_BOOT:
		puts("SD0\n");
		break;
	case SD2_BOOT:
		puts("SD1\n");
		break;
	case SD3_BOOT:
		puts("SD2\n");
		break;
	case MMC1_BOOT:
		puts("MMC0\n");
		break;
	case MMC2_BOOT:
		puts("MMC1\n");
		break;
	case MMC3_BOOT:
		puts("MMC2\n");
		break;
	case FLEXSPI_BOOT:
		puts("FLEXSPI\n");
		break;
	case SATA_BOOT:
		puts("SATA\n");
		break;
	case NAND_BOOT:
		puts("NAND\n");
		break;
	case USB_BOOT:
		puts("USB\n");
		break;
	default:
		printf("Unknown device %u\n", bt_dev);
		break;
	}

	return 0;
}

enum boot_device get_boot_device(void)
{
	enum boot_device boot_dev = SD1_BOOT;

	sc_rsrc_t dev_rsrc;

	sc_misc_get_boot_dev(-1, &dev_rsrc);

	switch (dev_rsrc) {
	case SC_R_SDHC_0:
		boot_dev = MMC1_BOOT;
		break;
	case SC_R_SDHC_1:
		boot_dev = SD2_BOOT;
		break;
	case SC_R_SDHC_2:
		boot_dev = SD3_BOOT;
		break;
	case SC_R_NAND:
		boot_dev = NAND_BOOT;
		break;
	case SC_R_FSPI_0:
		boot_dev = FLEXSPI_BOOT;
		break;
	case SC_R_SATA_0:
		boot_dev = SATA_BOOT;
		break;
	case SC_R_USB_0:
	case SC_R_USB_1:
	case SC_R_USB_2:
		boot_dev = USB_BOOT;
		break;
	default:
		break;
	}

	return boot_dev;
}

bool is_usb_boot(void)
{
	return get_boot_device() == USB_BOOT;
}

#ifdef CONFIG_SERIAL_TAG
#define FUSE_UNIQUE_ID_WORD0 16
#define FUSE_UNIQUE_ID_WORD1 17
void get_board_serial(struct tag_serialnr *serialnr)
{
	sc_err_t err;
	uint32_t val1 = 0, val2 = 0;
	uint32_t word1, word2;

	word1 = FUSE_UNIQUE_ID_WORD0;
	word2 = FUSE_UNIQUE_ID_WORD1;

	err = sc_misc_otp_fuse_read(-1, word1, &val1);
	if (err != SC_ERR_NONE) {
		printf("%s fuse %d read error: %d\n", __func__,word1, err);
		return;
	}

	err = sc_misc_otp_fuse_read(-1, word2, &val2);
	if (err != SC_ERR_NONE) {
		printf("%s fuse %d read error: %d\n", __func__, word2, err);
		return;
	}
	serialnr->low = val1;
	serialnr->high = val2;
}
#endif /*CONFIG_SERIAL_TAG*/

__weak int board_mmc_get_env_dev(int devno)
{
	return devno;
}

int mmc_get_env_dev(void)
{
	sc_rsrc_t dev_rsrc;
	int devno;

	sc_misc_get_boot_dev(-1, &dev_rsrc);

	switch (dev_rsrc) {
	case SC_R_SDHC_0:
		devno = 0;
		break;
	case SC_R_SDHC_1:
		devno = 1;
		break;
	case SC_R_SDHC_2:
		devno = 2;
		break;
	default:
		/* If not boot from sd/mmc, use default value */
		return env_get_ulong("mmcdev", 10, CONFIG_SYS_MMC_ENV_DEV);
	}

	return board_mmc_get_env_dev(devno);
}

static bool check_owned_resources_in_pd_tree(void *blob, int nodeoff,
	unsigned int *unowned_rsrc)
{
	unsigned int rsrc_id;
	int phplen;
	const fdt32_t *php;

	/* Search the ancestors nodes in current SS power-domain tree,
	*   if all ancestors' resources are owned,  we can enable the node,
	*   otherwise any ancestor is not owned, we should disable the node.
	*/

	do {
		php = fdt_getprop(blob, nodeoff, "power-domains", &phplen);
		if (!php) {
			debug("   - ignoring no power-domains\n");
			break;
		}
		if (phplen != 4) {
			printf("ignoring %s power-domains of unexpected length %d\n",
					fdt_get_name(blob, nodeoff, NULL), phplen);
			break;
		}
		nodeoff = fdt_node_offset_by_phandle(blob, fdt32_to_cpu(*php));

		rsrc_id = fdtdec_get_uint(blob, nodeoff, "reg", 0);
		if (rsrc_id == SC_R_NONE) {
			debug("%s's power domain use SC_R_NONE\n",
				fdt_get_name(blob, nodeoff, NULL));
			break;
		}

		debug("power-domains node 0x%x, resource id %u\n", nodeoff, rsrc_id);

		if (!check_owned_resource(rsrc_id)) {
			if (unowned_rsrc != NULL)
				*unowned_rsrc = rsrc_id;
			return false;
		}
	} while (fdt_node_check_compatible(blob, nodeoff, "nxp,imx8-pd"));

	return true;
}

static int disable_fdt_node(void *blob, int nodeoffset)
{
	int rc, ret;
	const char *status = "disabled";

	do {
		rc = fdt_setprop(blob, nodeoffset, "status", status, strlen(status) + 1);
		if (rc) {
			if (rc == -FDT_ERR_NOSPACE) {
				ret = fdt_increase_size(blob, 512);
				if (ret)
					return ret;
			}
		}
	} while (rc == -FDT_ERR_NOSPACE);

	return rc;
}

static void fdt_edma_debug_int_array(u32 *array, int count, u32 stride)
{
#ifdef DEBUG
	int i;
	for (i = 0; i < count; i++) {
		printf("0x%x ", array[i]);
		if (i % stride == stride - 1)
			printf("\n");
	}

	printf("\n");
#endif
}

static void fdt_edma_debug_stringlist(const char *stringlist, int length)
{
#ifdef DEBUG
	int i = 0, len;
	while (i < length) {
		printf("%s\n", stringlist);

		len = strlen(stringlist) + 1;
		i += len;
		stringlist += len;
	}

	printf("\n");
#endif
}

static void fdt_edma_swap_int_array(u32 *array, int count)
{
	int i;
	for (i = 0; i < count; i++) {
		array[i] = cpu_to_fdt32(array[i]);
	}
}

static int fdt_edma_update_int_array(u32 *array, int count, u32 *new_array, u32 stride, int *remove_array, int remove_count)
{
	int i = 0, j, curr = 0, new_cnt = 0;

	do {
		if (remove_count && curr == remove_array[i]) {
			i++;
			remove_count--;
			array += stride;
		} else {
			for (j = 0; j< stride; j++) {
				*new_array = *array;
				new_array++;
				array++;
			}
			new_cnt+= j;
		}
		curr++;
	} while ((curr * stride) < count);

	return new_cnt;
}

static int fdt_edma_update_stringlist(const char *stringlist, int stringlist_count, char *newlist, int *remove_array, int remove_count)
{
	int i = 0, curr = 0, new_len = 0;
	int length;

	debug("fdt_edma_update_stringlist, remove_cnt %d\n", remove_count);

	do {
		if (remove_count && curr == remove_array[i]) {
			debug("remove %s at %d\n", stringlist, remove_array[i]);

			length = strlen(stringlist) + 1;
			stringlist += length;
			i++;
			remove_count--;
		} else {
			length = strlen(stringlist) + 1;
			strcpy(newlist, stringlist);

			debug("copy %s, %s, curr %d, len %d\n", newlist, stringlist, curr, length);

			stringlist += length;
			newlist += length;
			new_len += length;
		}
		curr++;
	} while (curr < stringlist_count);

	return new_len;
}

static int fdt_edma_get_channel_id(u32 *regs, int index, struct edma_ch_map *edma)
{
	u32 ch_reg = regs[(index << 2) + 1];
	u32 ch_reg_size = regs[(index << 2) + 3];
	int ch_id = (ch_reg - edma->ch_start_regs) / ch_reg_size;
	if (ch_id >= edma->ch_num)
		return -1;

	return ch_id;
}

static void update_fdt_edma_nodes(void *blob)
{
	struct edma_ch_map edma_qm[] = {
		{ SC_R_DMA_0_CH0, 0x5a200000, 32, "/dma-controller@5a1f0000"},
		{ SC_R_DMA_1_CH0, 0x5aa00000, 32, "/dma-controller@5a9f0000"},
		{ SC_R_DMA_2_CH0, 0x59200000, 5, "/dma-controller@591F0000"},
		{ SC_R_DMA_2_CH5, 0x59250000, 27, "/dma-controller@591F0000"},
		{ SC_R_DMA_3_CH0, 0x59a00000, 32, "/dma-controller@599F0000"},
	};

	struct edma_ch_map edma_qxp[] = {
		{ SC_R_DMA_0_CH0, 0x59200000, 32, "/dma-controller@591F0000"},
		{ SC_R_DMA_1_CH0, 0x59a00000, 32, "/dma-controller@599F0000"},
		{ SC_R_DMA_2_CH0, 0x5a200000, 5, "/dma-controller@5a1f0000"},
		{ SC_R_DMA_2_CH5, 0x5a250000, 27, "/dma-controller@5a1f0000"},
		{ SC_R_DMA_3_CH0, 0x5aa00000, 32, "/dma-controller@5a9f0000"},
	};

	u32 i, j, edma_size;
	int nodeoff, ret;
	struct edma_ch_map *edma_array;

	if (is_imx8qm()) {
		edma_array = edma_qm;
		edma_size = ARRAY_SIZE(edma_qm);
	} else {
		edma_array = edma_qxp;
		edma_size = ARRAY_SIZE(edma_qxp);
	}

	for (i = 0; i < edma_size; i++, edma_array++) {
		u32 regs[128];
		u32 interrupts[96];
		u32 dma_channels;
		int regs_count, interrupts_count, int_names_count;

		const char *list;
		int list_len, newlist_len;
		int remove[32];
		int remove_cnt = 0;
		char * newlist;

		nodeoff = fdt_path_offset(blob, edma_array->node_path);
		if (nodeoff < 0)
			continue; /* Not found, skip it */

		printf("%s, %d\n", edma_array->node_path, nodeoff);

		regs_count = fdtdec_get_int_array_count(blob, nodeoff, "reg", regs, 128);
		debug("regs_count %d\n", regs_count);
		if (regs_count < 0)
			continue;

		interrupts_count = fdtdec_get_int_array_count(blob, nodeoff, "interrupts", interrupts, 96);
		debug("interrupts_count %d\n", interrupts_count);
		if (interrupts_count < 0)
			continue;

		dma_channels = fdtdec_get_uint(blob, nodeoff, "dma-channels", 0);
		if (dma_channels == 0)
			continue;

		list = fdt_getprop(blob, nodeoff, "interrupt-names", &list_len);
		if (!list)
			continue;

		int_names_count = fdt_stringlist_count(blob, nodeoff, "interrupt-names");

		fdt_edma_debug_int_array(regs, regs_count, 4);
		fdt_edma_debug_int_array(interrupts, interrupts_count, 3);
		fdt_edma_debug_stringlist(list, list_len);

		for (j = 0; j < (regs_count >> 2); j++) {
			int ch_id = fdt_edma_get_channel_id(regs, j, edma_array);
			if (ch_id < 0)
				continue;

			if (!check_owned_resource(edma_array->ch_start_rsrc + ch_id)) {
				printf("remove edma items %d\n", j);

				dma_channels--;

				remove[remove_cnt] = j;
				remove_cnt++;
			}
		}

		if (remove_cnt > 0) {
			u32 new_regs[128];
			u32 new_interrupts[96];

			regs_count = fdt_edma_update_int_array(regs, regs_count, new_regs, 4, remove, remove_cnt);
			interrupts_count = fdt_edma_update_int_array(interrupts, interrupts_count, new_interrupts, 3, remove, remove_cnt);

			fdt_edma_debug_int_array(new_regs, regs_count, 4);
			fdt_edma_debug_int_array(new_interrupts, interrupts_count, 3);

			fdt_edma_swap_int_array(new_regs, regs_count);
			fdt_edma_swap_int_array(new_interrupts, interrupts_count);

			/* malloc a new string list */
			newlist = (char *)malloc(list_len);
			if (!newlist) {
				printf("malloc new string list failed, len=%d\n", list_len);
				continue;
			}

			newlist_len = fdt_edma_update_stringlist(list, int_names_count, newlist, remove, remove_cnt);
			fdt_edma_debug_stringlist(newlist, newlist_len);

			ret = fdt_setprop(blob, nodeoff, "reg", new_regs, regs_count * sizeof(u32));
			if (ret)
				printf("fdt_setprop regs error %d\n", ret);

			ret = fdt_setprop(blob, nodeoff, "interrupts", new_interrupts, interrupts_count * sizeof(u32));
			if (ret)
				printf("fdt_setprop interrupts error %d\n", ret);

			ret = fdt_setprop_u32(blob, nodeoff, "dma-channels", dma_channels);
			if (ret)
				printf("fdt_setprop_u32 dma-channels error %d\n", ret);

			ret = fdt_setprop(blob, nodeoff, "interrupt-names", newlist, newlist_len);
			if (ret)
				printf("fdt_setprop interrupt-names error %d\n", ret);

			free(newlist);
		}
	}
}

static void update_fdt_with_owned_resources(void *blob)
{
	/* Traverses the fdt nodes,
	  * check its power domain and use the resource id in the power domain
	  * for checking whether it is owned by current partition
	  */

	int offset = 0, next_off;
	int depth = 0, next_depth;
	unsigned int rsrc_id;
	int rc;

	for (offset = fdt_next_node(blob, offset, &depth); offset > 0;
		 offset = fdt_next_node(blob, offset, &depth)) {

		debug("Node name: %s, depth %d\n", fdt_get_name(blob, offset, NULL), depth);

		if (!fdtdec_get_is_enabled(blob, offset)) {
			debug("   - ignoring disabled device\n");
			continue;
		}

		if (!fdt_node_check_compatible(blob, offset, "nxp,imx8-pd")) {
			/* Skip to next depth=1 node*/
			next_off = offset;
			next_depth = depth;
			do {
				offset = next_off;
				depth = next_depth;
				next_off = fdt_next_node(blob, offset, &next_depth);
				if (next_off < 0 || next_depth < 1)
					break;

				debug("PD name: %s, offset %d, depth %d\n",
					fdt_get_name(blob, next_off, NULL), next_off, next_depth);
			} while (next_depth > 1);

			continue;
		}

		if (!check_owned_resources_in_pd_tree(blob, offset, &rsrc_id)) {
			/* If the resource is not owned, disable it in FDT */
			rc = disable_fdt_node(blob, offset);
			if (!rc)
				printf("Disable %s, resource id %u not owned\n",
					fdt_get_name(blob, offset, NULL), rsrc_id);
			else
				printf("Unable to disable %s, err=%s\n",
					fdt_get_name(blob, offset, NULL), fdt_strerror(rc));
		}

	}
}

static void update_fdt_with_owned_resources_v2(void *blob)
{
	int count, i, ret, rc;
	int offset = 0, depth = 0;
	struct fdtdec_phandle_args args;

	/* Check the new PD, if not find, continue with old PD tree */
	count = fdt_node_offset_by_compatible(blob, -1, "fsl,scu-pd");
	if (count < 0)
		return update_fdt_with_owned_resources(blob);

	debug("Update FDT V2\n");

	for (offset = fdt_next_node(blob, offset, &depth); offset > 0;
		 offset = fdt_next_node(blob, offset, &depth)) {

		debug("Node name: %s, depth %d\n", fdt_get_name(blob, offset, NULL), depth);

		if (!fdtdec_get_is_enabled(blob, offset)) {
			debug("   - ignoring disabled device\n");
			continue;
		}

		count = fdtdec_parse_phandle_with_args(blob,
						     offset,
						     "power-domains", "#power-domain-cells",
						     0, -1, NULL);
		if (count < 1) {
			debug("no power-domains found\n");
			continue;
		}

		debug("count %d\n", count);

		for (i = 0; i < count; i++) {
			ret = fdtdec_parse_phandle_with_args(blob,
						     offset,
						     "power-domains", "#power-domain-cells",
						     0, i, &args);

			if (ret) {
				debug("Error in parsing power-domains\n");
				continue;
			}

			debug("check resource id %u\n", args.args[0]);

			if (!check_owned_resource(args.args[0])) {
				/* If the resource is not owned, disable it in FDT */
				rc = disable_fdt_node(blob, offset);
				if (!rc)
					printf("Disable %s, resource id %u not owned\n",
						fdt_get_name(blob, offset, NULL), args.args[0]);
				else
					printf("Unable to disable %s, err=%s\n",
						fdt_get_name(blob, offset, NULL), fdt_strerror(rc));
			}

		}
	}
}

#ifdef CONFIG_IMX_SMMU
static int get_srsc_from_fdt_node_power_domain(void *blob, int device_offset, int index)
{
	const fdt32_t *prop;
	int pdnode_offset;
	int ret;
	struct fdtdec_phandle_args args;

	prop = fdt_getprop(blob, device_offset, "power-domains", NULL);
	if (!prop) {
		debug("node %s has no power-domains\n",
				fdt_get_name(blob, device_offset, NULL));
		return -ENOENT;
	}

	pdnode_offset = fdt_node_offset_by_phandle(blob, fdt32_to_cpu(*prop));
	if (pdnode_offset < 0) {
		pr_err("failed to fetch node %s power-domain",
				fdt_get_name(blob, device_offset, NULL));
		return pdnode_offset;
	}

	ret = fdtdec_get_uint(blob, pdnode_offset, "reg", -ENOENT);
	if (ret != -ENOENT)
		return ret;

	ret = fdtdec_parse_phandle_with_args(blob, device_offset,
					     "power-domains",
					     "#power-domain-cells", 0, index,
					     &args);
	if (ret)
		return ret;
	return args.args[0];
}

static int config_smmu_resource_sid(int rsrc, int sid)
{
	int err;

	if (!check_owned_resource(rsrc)) {
		printf("%s rsrc[%d] not owned\n", __func__, rsrc);
		return -1;
	}
	err = sc_rm_set_master_sid(-1, rsrc, sid);
	debug("set_master_sid rsrc=%d sid=0x%x err=%d\n", rsrc, sid, err);
	if (err != SC_ERR_NONE) {
		pr_err("fail set_master_sid rsrc=%d sid=0x%x err=%d\n", rsrc, sid, err);
		return -EINVAL;
	}

	return 0;
}

static int config_smmu_fdt_device_sid(void *blob, int device_offset, int sid)
{
	int rsrc;
	int proplen;
	int i, count;
	const fdt32_t *prop;
	const char *name = fdt_get_name(blob, device_offset, NULL);

	prop = fdt_getprop(blob, device_offset, "fsl,sc_rsrc_id", &proplen);
	if (prop) {
		int i;

		debug("configure node %s sid 0x%x for %d resources\n",
				name, sid, (int)(proplen / sizeof(fdt32_t)));
		for (i = 0; i < proplen / sizeof(fdt32_t); ++i) {
			config_smmu_resource_sid(fdt32_to_cpu(prop[i]), sid);
		}

		return 0;
	}

	count = fdtdec_parse_phandle_with_args(blob, device_offset,
					       "power-domains",
					       "#power-domain-cells",
					       0, -1, NULL);
	for (i = 0; i < count; i++) {
		rsrc = get_srsc_from_fdt_node_power_domain(blob, device_offset, i);
		debug("configure node %s sid 0x%x rsrc=%d\n", name, sid, rsrc);
		if (rsrc < 0) {
			debug("failed to determine SC_R_* for node %s\n", name);
			return rsrc;
		}

		config_smmu_resource_sid(rsrc, sid);
	}

	return 0;
}

/* assign master sid based on iommu properties in fdt */
static int config_smmu_fdt(void *blob)
{
	int offset, proplen, i;
	const fdt32_t *prop;
	const char *name;

	/* Legacy smmu bindings, still used by xen. */
	offset = fdt_node_offset_by_compatible(blob, 0, "arm,mmu-500");
	if (offset > 0 && (prop = fdt_getprop(blob, offset, "mmu-masters", &proplen)))
	{
		debug("found legacy mmu-masters property\n");

		for (i = 0; i < proplen / 8; ++i) {
			uint32_t phandle = fdt32_to_cpu(prop[2 * i]);
			int sid = fdt32_to_cpu(prop[2 * i + 1]);
			int device_offset;

			device_offset = fdt_node_offset_by_phandle(blob, phandle);
			if (device_offset < 0) {
				pr_err("Failed to fetch device reference from mmu_masters: %d", device_offset);
				continue;
			}
			config_smmu_fdt_device_sid(blob, device_offset, sid);
		}

		/* Ignore new bindings if old bindings found, just like linux. */
		return 0;
	}

	/* Generic smmu bindings */
	offset = 0;
	while ((offset = fdt_next_node(blob, offset, NULL)) > 0)
	{
		name = fdt_get_name(blob, offset, NULL);
		prop = fdt_getprop(blob, offset, "iommus", &proplen);
		if (!prop)
			continue;
		debug("node %s iommus proplen %d\n", name, proplen);

		if (proplen == 12) {
			int sid = fdt32_to_cpu(prop[1]);
			config_smmu_fdt_device_sid(blob, offset, sid);
		} else if (proplen != 4) {
			debug("node %s ignore unexpected iommus proplen=%d\n", name, proplen);
		}
	}

	return 0;
}
#endif

#ifdef CONFIG_OF_SYSTEM_SETUP
static int ft_add_optee_node(void *fdt, bd_t *bd)
{
	const char *path, *subpath;
	int offs;

	/*
	 * No TEE space allocated indicating no TEE running, so no
	 * need to add optee node in dts
	 */
	if (!rom_pointer[1])
		return 0;

	offs = fdt_increase_size(fdt, 512);
	if (offs) {
		printf("No Space for dtb\n");
		return 1;
	}

	path = "/firmware";
	offs = fdt_path_offset(fdt, path);
	if (offs < 0) {
		path = "/";
		offs = fdt_path_offset(fdt, path);

		if (offs < 0) {
			printf("Could not find root node.\n");
			return 1;
		}

		subpath = "firmware";
		offs = fdt_add_subnode(fdt, offs, subpath);
		if (offs < 0) {
			printf("Could not create %s node.\n", subpath);
		}
	}

	subpath = "optee";
	offs = fdt_add_subnode(fdt, offs, subpath);
	if (offs < 0) {
		printf("Could not create %s node.\n", subpath);
	}

	fdt_setprop_string(fdt, offs, "compatible", "linaro,optee-tz");
	fdt_setprop_string(fdt, offs, "method", "smc");

	return 0;
}

int ft_system_setup(void *blob, bd_t *bd)
{
#if (CONFIG_BOOTAUX_RESERVED_MEM_SIZE != 0x00)
	int off;
	off = fdt_add_mem_rsv(blob, CONFIG_BOOTAUX_RESERVED_MEM_BASE,
				      CONFIG_BOOTAUX_RESERVED_MEM_SIZE);
		if (off < 0)
			printf("Failed  to reserve memory for bootaux: %s\n",
			       fdt_strerror(off));
#endif

#ifndef CONFIG_SKIP_RESOURCE_CHECKING
	update_fdt_with_owned_resources_v2(blob);
#endif

	update_fdt_edma_nodes(blob);
#ifdef CONFIG_IMX_SMMU
	config_smmu_fdt(blob);
#endif

	ft_add_optee_node(blob, bd);
	return 0;
}
#endif

#define MEMSTART_ALIGNMENT  SZ_2M /* Align the memory start with 2MB */

static sc_faddr_t reserve_optee_shm(sc_faddr_t addr_start)
{
	/* OPTEE has a share memory at its top address,
	 * ATF assigns the share memory to non-secure os partition for share with kernel
	 * We should not add this share memory to DDR bank, as this memory is dedicated for
	 * optee, optee driver will memremap it and can't be used by system malloc.
	 */

	sc_faddr_t optee_start = rom_pointer[0];
	sc_faddr_t optee_size = rom_pointer[1];

	if (optee_size && optee_start <= addr_start &&
		addr_start < optee_start + optee_size) {
		debug("optee 0x%llx 0x%llx, addr_start 0x%llx\n",
			optee_start, optee_size, addr_start);
		return optee_start + optee_size;
	}

	return addr_start;
}

static int get_owned_memreg(sc_rm_mr_t mr, sc_faddr_t *addr_start,
			    sc_faddr_t *addr_end)
{
	sc_faddr_t start, end;
	int ret;
	bool owned;

	owned = sc_rm_is_memreg_owned(-1, mr);
	if (owned) {
		ret = sc_rm_get_memreg_info(-1, mr, &start, &end);
		if (ret) {
			printf("Memreg get info failed, %d\n", ret);
			return -EINVAL;
		}
		debug("0x%llx -- 0x%llx\n", start, end);
		*addr_start = reserve_optee_shm(start);
		*addr_end = end;

		return 0;
	}

	return -EINVAL;
}

phys_size_t get_effective_memsize(void)
{
	sc_rm_mr_t mr;
	sc_faddr_t start, end, end1, start_aligned;
	int err;

	end1 = (sc_faddr_t)PHYS_SDRAM_1 + PHYS_SDRAM_1_SIZE;

	for (mr = 0; mr < 64; mr++) {
		err = get_owned_memreg(mr, &start, &end);
		if (!err) {
			start_aligned = roundup(start, MEMSTART_ALIGNMENT);
			/* Too small memory region, not use it */
			if (start_aligned > end)
				continue;

			/* Find the memory region runs the U-Boot */
			if (start >= PHYS_SDRAM_1 && start <= end1 &&
			    (start <= CONFIG_SYS_TEXT_BASE &&
			    end >= CONFIG_SYS_TEXT_BASE)) {
				if ((end + 1) <= ((sc_faddr_t)PHYS_SDRAM_1 +
				    PHYS_SDRAM_1_SIZE))
					return (end - PHYS_SDRAM_1 + 1);
				else
					return PHYS_SDRAM_1_SIZE;
			}
		}
	}

	return PHYS_SDRAM_1_SIZE;
}

int dram_init(void)
{
	sc_rm_mr_t mr;
	sc_faddr_t start, end, end1, end2;
	int err;

	end1 = (sc_faddr_t)PHYS_SDRAM_1 + PHYS_SDRAM_1_SIZE;
	end2 = (sc_faddr_t)PHYS_SDRAM_2 + PHYS_SDRAM_2_SIZE;
	for (mr = 0; mr < 64; mr++) {
		err = get_owned_memreg(mr, &start, &end);
		if (!err) {
			start = roundup(start, MEMSTART_ALIGNMENT);
			/* Too small memory region, not use it */
			if (start > end)
				continue;

			if (start >= PHYS_SDRAM_1 && start <= end1) {
				if ((end + 1) <= end1)
					gd->ram_size += end - start + 1;
				else
					gd->ram_size += end1 - start;
			} else if (start >= PHYS_SDRAM_2 && start <= end2) {
				if ((end + 1) <= end2)
					gd->ram_size += end - start + 1;
				else
					gd->ram_size += end2 - start;
			}
		}
	}

	/* If error, set to the default value */
	if (!gd->ram_size) {
		gd->ram_size = PHYS_SDRAM_1_SIZE;
		gd->ram_size += PHYS_SDRAM_2_SIZE;
	}
	return 0;
}

static void dram_bank_sort(int current_bank)
{
	phys_addr_t start;
	phys_size_t size;

	while (current_bank > 0) {
		if (gd->bd->bi_dram[current_bank - 1].start >
		    gd->bd->bi_dram[current_bank].start) {
			start = gd->bd->bi_dram[current_bank - 1].start;
			size = gd->bd->bi_dram[current_bank - 1].size;

			gd->bd->bi_dram[current_bank - 1].start =
				gd->bd->bi_dram[current_bank].start;
			gd->bd->bi_dram[current_bank - 1].size =
				gd->bd->bi_dram[current_bank].size;

			gd->bd->bi_dram[current_bank].start = start;
			gd->bd->bi_dram[current_bank].size = size;
		}
		current_bank--;
	}
}

int dram_init_banksize(void)
{
	sc_rm_mr_t mr;
	sc_faddr_t start, end, end1, end2;
	int i = 0;
	int err;

	end1 = (sc_faddr_t)PHYS_SDRAM_1 + PHYS_SDRAM_1_SIZE;
	end2 = (sc_faddr_t)PHYS_SDRAM_2 + PHYS_SDRAM_2_SIZE;

	for (mr = 0; mr < 64 && i < CONFIG_NR_DRAM_BANKS; mr++) {
		err = get_owned_memreg(mr, &start, &end);
		if (!err) {
			start = roundup(start, MEMSTART_ALIGNMENT);
			if (start > end) /* Small memory region, no use it */
				continue;

			if (start >= PHYS_SDRAM_1 && start <= end1) {
				gd->bd->bi_dram[i].start = start;

				if ((end + 1) <= end1)
					gd->bd->bi_dram[i].size =
						end - start + 1;
				else
					gd->bd->bi_dram[i].size = end1 - start;

				dram_bank_sort(i);
				i++;
			} else if (start >= PHYS_SDRAM_2 && start <= end2) {
				gd->bd->bi_dram[i].start = start;

				if ((end + 1) <= end2)
					gd->bd->bi_dram[i].size =
						end - start + 1;
				else
					gd->bd->bi_dram[i].size = end2 - start;

				dram_bank_sort(i);
				i++;
			}
		}
	}

	/* If error, set to the default value */
	if (!i) {
		gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
		gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
		gd->bd->bi_dram[1].start = PHYS_SDRAM_2;
		gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
	}

	return 0;
}

static u64 get_block_attrs(sc_faddr_t addr_start)
{
	u64 attr = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) | PTE_BLOCK_NON_SHARE |
		PTE_BLOCK_PXN | PTE_BLOCK_UXN;

	if ((addr_start >= PHYS_SDRAM_1 &&
	     addr_start <= ((sc_faddr_t)PHYS_SDRAM_1 + PHYS_SDRAM_1_SIZE)) ||
	    (addr_start >= PHYS_SDRAM_2 &&
	     addr_start <= ((sc_faddr_t)PHYS_SDRAM_2 + PHYS_SDRAM_2_SIZE)))
#ifdef CONFIG_IMX_TRUSTY_OS
		return (PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_INNER_SHARE);
#else
		return (PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_OUTER_SHARE);
#endif

	return attr;
}

static u64 get_block_size(sc_faddr_t addr_start, sc_faddr_t addr_end)
{
	sc_faddr_t end1, end2;

	end1 = (sc_faddr_t)PHYS_SDRAM_1 + PHYS_SDRAM_1_SIZE;
	end2 = (sc_faddr_t)PHYS_SDRAM_2 + PHYS_SDRAM_2_SIZE;

	if (addr_start >= PHYS_SDRAM_1 && addr_start <= end1) {
		if ((addr_end + 1) > end1)
			return end1 - addr_start;
	} else if (addr_start >= PHYS_SDRAM_2 && addr_start <= end2) {
		if ((addr_end + 1) > end2)
			return end2 - addr_start;
	}

	return (addr_end - addr_start + 1);
}

#define MAX_PTE_ENTRIES 512
#define MAX_MEM_MAP_REGIONS 16

static struct mm_region imx8_mem_map[MAX_MEM_MAP_REGIONS];
struct mm_region *mem_map = imx8_mem_map;

void enable_caches(void)
{
	sc_rm_mr_t mr;
	sc_faddr_t start, end;
	int err, i;

	/* Create map for registers access from 0x1c000000 to 0x80000000*/
	imx8_mem_map[0].virt = 0x1c000000UL;
	imx8_mem_map[0].phys = 0x1c000000UL;
	imx8_mem_map[0].size = 0x64000000UL;
	imx8_mem_map[0].attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
			 PTE_BLOCK_NON_SHARE | PTE_BLOCK_PXN | PTE_BLOCK_UXN;

	i = 1;

#ifdef CONFIG_IMX_VSERVICE_SHARED_BUFFER
	imx8_mem_map[i].virt = CONFIG_IMX_VSERVICE_SHARED_BUFFER;
	imx8_mem_map[i].phys = CONFIG_IMX_VSERVICE_SHARED_BUFFER;
	imx8_mem_map[i].size = CONFIG_IMX_VSERVICE_SHARED_BUFFER_SIZE;
	imx8_mem_map[i].attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
			 PTE_BLOCK_NON_SHARE | PTE_BLOCK_PXN | PTE_BLOCK_UXN;
	i++;
#endif

	for (mr = 0; mr < 64 && i < MAX_MEM_MAP_REGIONS; mr++) {
		err = get_owned_memreg(mr, &start, &end);
		if (!err) {
			imx8_mem_map[i].virt = start;
			imx8_mem_map[i].phys = start;
			imx8_mem_map[i].size = get_block_size(start, end);
			imx8_mem_map[i].attrs = get_block_attrs(start);
			i++;
		}
	}

	if (i < MAX_MEM_MAP_REGIONS) {
		imx8_mem_map[i].size = 0;
		imx8_mem_map[i].attrs = 0;
	} else {
		puts("Error, need more MEM MAP REGIONS reserved\n");
		icache_enable();
		return;
	}

	for (i = 0; i < MAX_MEM_MAP_REGIONS; i++) {
		debug("[%d] vir = 0x%llx phys = 0x%llx size = 0x%llx attrs = 0x%llx\n",
		      i, imx8_mem_map[i].virt, imx8_mem_map[i].phys,
		      imx8_mem_map[i].size, imx8_mem_map[i].attrs);
	}

	icache_enable();
	dcache_enable();
}

#ifndef CONFIG_SYS_DCACHE_OFF
u64 get_page_table_size(void)
{
	u64 one_pt = MAX_PTE_ENTRIES * sizeof(u64);
	u64 size = 0;

	/*
	 * For each memory region, the max table size:
	 * 2 level 3 tables + 2 level 2 tables + 1 level 1 table
	 */
	size = (2 + 2 + 1) * one_pt * MAX_MEM_MAP_REGIONS + one_pt;

	/*
	 * We need to duplicate our page table once to have an emergency pt to
	 * resort to when splitting page tables later on
	 */
	size *= 2;

	/*
	 * We may need to split page tables later on if dcache settings change,
	 * so reserve up to 4 (random pick) page tables for that.
	 */
	size += one_pt * 4;

	return size;
}
#endif

#if defined(CONFIG_IMX8QM)
#define FUSE_MAC0_WORD0 452
#define FUSE_MAC0_WORD1 453
#define FUSE_MAC1_WORD0 454
#define FUSE_MAC1_WORD1 455
#elif defined(CONFIG_IMX8QXP)
#define FUSE_MAC0_WORD0 708
#define FUSE_MAC0_WORD1 709
#define FUSE_MAC1_WORD0 710
#define FUSE_MAC1_WORD1 711
#endif

void imx_get_mac_from_fuse(int dev_id, unsigned char *mac)
{
	u32 word[2], val[2] = {};
	int i, ret;

	if (dev_id == 0) {
		word[0] = FUSE_MAC0_WORD0;
		word[1] = FUSE_MAC0_WORD1;
	} else {
		word[0] = FUSE_MAC1_WORD0;
		word[1] = FUSE_MAC1_WORD1;
	}

	for (i = 0; i < 2; i++) {
		ret = sc_misc_otp_fuse_read(-1, word[i], &val[i]);
		if (ret < 0)
			goto err;
	}

	mac[0] = val[0];
	mac[1] = val[0] >> 8;
	mac[2] = val[0] >> 16;
	mac[3] = val[0] >> 24;
	mac[4] = val[1];
	mac[5] = val[1] >> 8;

	debug("%s: MAC%d: %02x.%02x.%02x.%02x.%02x.%02x\n",
	      __func__, dev_id, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
	return;
err:
	printf("%s: fuse %d, err: %d\n", __func__, word[i], ret);
}

u32 get_cpu_rev(void)
{
	u32 id = 0, rev = 0;
	int ret;

	ret = sc_misc_get_control(-1, SC_R_SYSTEM, SC_C_ID, &id);
	if (ret)
		return 0;

	rev = (id >> 5)  & 0xf;
	id = (id & 0x1f) + MXC_SOC_IMX8;  /* Dummy ID for chip */

	return (id << 12) | rev;
}

void disconnect_from_pc(void)
{
	int ret;
	struct power_domain pd;

	if (!power_domain_lookup_name("conn_usb0", &pd)) {
		ret = power_domain_on(&pd);
		if (ret) {
			printf("conn_usb0 Power up failed! (error = %d)\n", ret);
			return;
		}

		writel(0x0, USB_BASE_ADDR + 0x140);

		ret = power_domain_off(&pd);
		if (ret) {
			printf("conn_usb0 Power off failed! (error = %d)\n", ret);
			return;
		}
	} else {
		printf("conn_usb0 finding failed!\n");
		return;
	}
}

#if CONFIG_IS_ENABLED(CPU)
struct cpu_imx_platdata {
	const char *name;
	const char *rev;
	const char *type;
	u32 cpurev;
	u32 freq_mhz;
	u32 mpidr;
};

const char *get_imx8_type(u32 imxtype)
{
	switch (imxtype) {
	case MXC_CPU_IMX8QM:
		return "QM";	/* i.MX8 Quad MAX */
	case MXC_CPU_IMX8QXP:
	case MXC_CPU_IMX8QXP_A0:
		return "QXP";
	default:
		return "??";
	}
}

const char *get_imx8_rev(u32 rev)
{
	switch (rev) {
	case CHIP_REV_A:
		return "A";
	case CHIP_REV_B:
		return "B";
	case CHIP_REV_C:
		return "C";
	default:
		return "?";
	}
}

const char *get_core_name(struct udevice *dev)
{
	const void *blob = gd->fdt_blob;
	int node = dev_of_offset(dev);

	if (!fdt_node_check_compatible(blob, node, "arm,cortex-a35"))
		return "A35";
	else if (!fdt_node_check_compatible(blob, node, "arm,cortex-a53"))
		return "A53";
	else if (!fdt_node_check_compatible(blob, node, "arm,cortex-a72"))
		return "A72";
	else
		return "?";
}

int cpu_imx_get_desc(struct udevice *dev, char *buf, int size)
{
	struct cpu_imx_platdata *plat = dev_get_platdata(dev);
	int len;

	if (size < 100)
		return -ENOSPC;

	len = snprintf(buf, size, "NXP i.MX8%s Rev%s %s at %u MHz",
		 plat->type, plat->rev, plat->name, plat->freq_mhz);

#if defined(CONFIG_IMX_SC_THERMAL)
	struct udevice *thermal_dev;
	int cpu_tmp, ret;

	if (!strcmp(plat->name, "A72"))
		ret = uclass_get_device_by_name(UCLASS_THERMAL, "cpu-thermal1", &thermal_dev);
	else
		ret = uclass_get_device_by_name(UCLASS_THERMAL, "cpu-thermal0", &thermal_dev);

	if (!ret) {
		ret = thermal_get_temp(thermal_dev, &cpu_tmp);

		if (!ret)
			len += snprintf(buf + len, size," at %dC", cpu_tmp);
		else
			len += snprintf(buf + len, size," - invalid sensor data");
	} else {
		len += snprintf(buf + len, size, " - invalid sensor device");
	}
#endif

	len += snprintf(buf + len, size, "\n");

	return 0;
}

static int cpu_imx_get_info(struct udevice *dev, struct cpu_info *info)
{
	struct cpu_imx_platdata *plat = dev_get_platdata(dev);

	info->cpu_freq = plat->freq_mhz * 1000;
	info->features = BIT(CPU_FEAT_L1_CACHE) | BIT(CPU_FEAT_MMU);
	return 0;
}

static int cpu_imx_get_count(struct udevice *dev)
{
	if (is_imx8qxp())
		return 4;
	else
		return 6;
}

static int cpu_imx_get_vendor(struct udevice *dev,  char *buf, int size)
{
	snprintf(buf, size, "NXP");
	return 0;
}

static bool cpu_imx_is_current(struct udevice *dev)
{
	struct cpu_imx_platdata *plat = dev_get_platdata(dev);

	if (plat->mpidr == (read_mpidr() & 0xffff))
		return true;

	return false;
}

static const struct cpu_ops cpu_imx8_ops = {
	.get_desc	= cpu_imx_get_desc,
	.get_info	= cpu_imx_get_info,
	.get_count	= cpu_imx_get_count,
	.get_vendor	= cpu_imx_get_vendor,
	.is_current_cpu = cpu_imx_is_current,
};

static const struct udevice_id cpu_imx8_ids[] = {
	{ .compatible = "arm,cortex-a35" },
	{ .compatible = "arm,cortex-a53" },
	{ .compatible = "arm,cortex-a72" },
	{ }
};

static int imx8_cpu_probe(struct udevice *dev)
{
	struct cpu_imx_platdata *plat = dev_get_platdata(dev);
	struct clk cpu_clk;
	u32 cpurev;
	int ret;

	cpurev = get_cpu_rev();
	plat->cpurev = cpurev;
	plat->name = get_core_name(dev);
	plat->rev = get_imx8_rev(cpurev & 0xFFF);
	plat->type = get_imx8_type((cpurev & 0xFF000) >> 12);

	plat->mpidr = dev_read_addr(dev);
	if (plat->mpidr == FDT_ADDR_T_NONE) {
		printf("%s: Failed to get CPU reg property\n", __func__);
		return -EINVAL;
	}

	ret = clk_get_by_index(dev, 0, &cpu_clk);
	if (ret) {
		debug("%s: Failed to get CPU clk: %d\n", __func__, ret);
		return 0;
	}

	plat->freq_mhz = clk_get_rate(&cpu_clk) / 1000000;
	return 0;
}

U_BOOT_DRIVER(cpu_imx8_drv) = {
	.name		= "imx8x_cpu",
	.id		= UCLASS_CPU,
	.of_match	= cpu_imx8_ids,
	.ops		= &cpu_imx8_ops,
	.probe		= imx8_cpu_probe,
	.platdata_auto_alloc_size = sizeof(struct cpu_imx_platdata),
	.flags		= DM_FLAG_PRE_RELOC,
};
#endif

static bool check_device_power_off(struct udevice *dev,
	const char* permanent_on_devices[], int size)
{
	int i;

	for (i = 0; i < size; i++) {
		if (!strcmp(dev->name, permanent_on_devices[i]))
			return false;
	}

	return true;
}

void power_off_pd_devices(const char* permanent_on_devices[], int size)
{
	struct udevice *dev;
	struct power_domain pd;

	for (uclass_find_first_device(UCLASS_POWER_DOMAIN, &dev); dev;
		uclass_find_next_device(&dev)) {

		if (device_active(dev)) {
			/* Power off active pd devices except the permanent power on devices */
			if (check_device_power_off(dev, permanent_on_devices, size)) {
				pd.dev = dev;
				power_domain_off(&pd);
			}
		}
	}
}

bool check_owned_udevice(struct udevice *dev)
{
	int ret;
	sc_rsrc_t resource_id;
	struct ofnode_phandle_args args;

	/* Get the resource id from its power-domain */
	ret = dev_read_phandle_with_args(dev, "power-domains",
					 "#power-domain-cells", 0, 0, &args);
	if (ret) {
		printf("no power-domains found\n");
		return false;
	}

	/* Get the owner partition for resource*/
	resource_id = (sc_rsrc_t)ofnode_read_u32_default(args.node, "reg", SC_R_NONE);
	if (resource_id == SC_R_NONE) {
		printf("Can't find the resource id for udev %s\n", dev->name);
		return false;
	}

	debug("udev %s, resource id %d\n", dev->name, resource_id);

	return check_owned_resource(resource_id);
}

bool check_m4_parts_boot(void)
{
	sc_rm_pt_t m4_parts[2];
	int err;

	err = sc_rm_get_resource_owner(-1, SC_R_M4_0_PID0, &m4_parts[0]);
	if (err != SC_ERR_NONE) {
		printf("%s get resource [%d] owner error: %d\n", __func__, SC_R_M4_0_PID0, err);
		return false;
	}

	if (sc_pm_is_partition_started(-1, m4_parts[0]))
		return true;

	if (is_imx8qm()) {
		err = sc_rm_get_resource_owner(-1, SC_R_M4_1_PID0, &m4_parts[1]);
		if (err != SC_ERR_NONE) {
			printf("%s get resource [%d] owner error: %d\n", __func__, SC_R_M4_1_PID0, err);
			return false;
		}

		if (sc_pm_is_partition_started(-1, m4_parts[1]))
			return true;
	}

	return false;
}

#ifdef CONFIG_IMX_VSERVICE
struct udevice * board_imx_vservice_find_mu(struct udevice *dev)
{
	int ret;
	const char *m4_mu_name[2] = {
		"mu@5d230000",
		"mu@5d240000"
	};
	struct udevice *m4_mu[2];
	sc_rm_pt_t m4_parts[2];
	int err;
	struct ofnode_phandle_args args;
	sc_rsrc_t resource_id;
	sc_rm_pt_t resource_part;

	/* Get the resource id from its power-domain */
	ret = dev_read_phandle_with_args(dev, "power-domains",
					 "#power-domain-cells", 0, 0, &args);
	if (ret) {
		printf("Can't find the power-domains property for udev %s\n", dev->name);
		return NULL;
	}

	/* Get the owner partition for resource*/
	resource_id = (sc_rsrc_t)ofnode_read_u32_default(args.node, "reg", SC_R_NONE);
	if (resource_id == SC_R_NONE) {
		printf("Can't find the resource id for udev %s\n", dev->name);
		return NULL;
	}

	err = sc_rm_get_resource_owner(-1, resource_id, &resource_part);
	if (err != SC_ERR_NONE) {
		printf("%s get resource [%d] owner error: %d\n", __func__, resource_id, err);
		return NULL;
	}

	debug("udev %s, resource id %d, resource part %d\n", dev->name, resource_id, resource_part);

	/* MU8 for communication between M4_0 and u-boot, MU9 for M4_1 and u-boot */
	err = sc_rm_get_resource_owner(-1, SC_R_M4_0_PID0, &m4_parts[0]);
	if (err != SC_ERR_NONE) {
		printf("%s get resource [%d] owner error: %d\n", __func__, SC_R_M4_0_PID0, err);
		return NULL;
	}

	ret = uclass_find_device_by_name(UCLASS_MISC,  m4_mu_name[0], &m4_mu[0]);
	if (!ret) {
		/* If the i2c is in m4_0 partition, return the mu8 */
		if (resource_part == m4_parts[0])
			return m4_mu[0];
	}

	if (is_imx8qm()) {
		err = sc_rm_get_resource_owner(-1, SC_R_M4_1_PID0, &m4_parts[1]);
		if (err != SC_ERR_NONE) {
			printf("%s get resource [%d] owner error: %d\n", __func__, SC_R_M4_1_PID0, err);
			return NULL;
		}

		ret = uclass_find_device_by_name(UCLASS_MISC,  m4_mu_name[1], &m4_mu[1]);
		if (!ret) {
			/* If the i2c is in m4_1 partition, return the mu9 */
			if (resource_part == m4_parts[1])
				return m4_mu[1];
		}
	}

	return NULL;
}

void * board_imx_vservice_get_buffer(struct imx_vservice_channel *node, u32 size)
{
	const char *m4_mu_name[2] = {
		"mu@5d230000",
		"mu@5d240000"
	};

	/* Each MU ownes 1M buffer */
	if (size <= 0x100000) {
		if (!strcmp(node->mu_dev->name, m4_mu_name[0]))
			return (void * )CONFIG_IMX_VSERVICE_SHARED_BUFFER;
		else if (!strcmp(node->mu_dev->name, m4_mu_name[1]))
			return (void * )(CONFIG_IMX_VSERVICE_SHARED_BUFFER + 0x100000);
		else
			return NULL;
	}

	return NULL;
}
#endif

/* imx8qxp i2c1 has lots of devices may used by both M4 and A core
*   If A core partition does not own the resource, we will start
*   virtual i2c driver. Otherwise use local i2c driver.
*/
int board_imx_virt_i2c_bind(struct udevice *dev)
{
	if (check_owned_udevice(dev))
		return -ENODEV;

	return 0;
}

int board_imx_lpi2c_bind(struct udevice *dev)
{
	if (check_owned_udevice(dev))
		return 0;

	return -ENODEV;
}

void board_boot_order(u32 *spl_boot_list)
{
	spl_boot_list[0] = spl_boot_device();

	if (spl_boot_list[0] == BOOT_DEVICE_SPI) {
		/* Check whether we own the flexspi0, if not, use NOR boot */
		if (!check_owned_resource(SC_R_FSPI_0))
			spl_boot_list[0] = BOOT_DEVICE_NOR;
	}
}

#ifdef CONFIG_USB_PORT_AUTO
int board_usb_gadget_port_auto(void)
{
	int ret;
	u32 usb2_data;
	struct power_domain pd;
	struct power_domain phy_pd;

	if (!power_domain_lookup_name("conn_usb0", &pd)) {
		ret = power_domain_on(&pd);
		if (ret) {
			printf("conn_usb0 Power up failed!\n");
			return ret;
		}

		if (!power_domain_lookup_name("conn_usb0_phy", &phy_pd)) {
			ret = power_domain_on(&phy_pd);
			if (ret) {
				printf("conn_usb0_phy Power up failed!\n");
				return ret;
			}
		} else {
			return -1;
		}

		enable_usboh3_clk(1);
		usb2_data = readl(USB_BASE_ADDR + 0x154);

		ret = power_domain_off(&phy_pd);
		if (ret) {
			printf("conn_usb0_phy Power off failed!\n");
			return ret;
		}
		ret = power_domain_off(&pd);
		if (ret) {
			printf("conn_usb0 Power off failed!\n");
			return ret;
		}

		if (!usb2_data)
			return 1;
		else
			return 0;
	}
	return -1;
}
#endif
