#include <asm/io.h>
#include <common.h>
#include <image.h>
#include <libfdt.h>
#include <flashhal.h>
#include <asm/arch-en7523/spi_nand_flash.h>

#define DBG_BOOTARGS 0

#define PART_FDT_MAGIC 0xD00DFEED

struct tclinux_info {
	u32 kernel_off;
	u32 kernel_size;
	u32 rootfs_off;
	u32 rootfs_size;
	u32 tclinux_size;
};

int get_tclinux_arm(const void *fit, struct tclinux_info *info)
{
	int noffset, images_noffset;
	int len;
	const void *data;

	info->tclinux_size = fdt_totalsize(fit);

	images_noffset = fdt_path_offset(fit, "/images");
	if (images_noffset < 0) {
		printf("Can't find images parent node '%s' (%s)\n",
			  "/images", fdt_strerror(images_noffset));
		return -1;
	}

	noffset = fdt_subnode_offset(fit, images_noffset, "kernel@1");
	if (noffset < 0) {
		printf("Can't get node offset for image unit name: '%s' (%s)\n",
			  "kernel@1", fdt_strerror(noffset));
		return -1;
	}

	data = (void *)fdt_getprop(fit, noffset, "data", &len);
	if (data == NULL) {
		printf("get kernel data error:0x%08u", (u32)data);
		return -1;
	}

	info->kernel_off = (u32)data - (u32)fit;
	info->kernel_size = len;

#if DBG_BOOTARGS
	printf("=== tclinux_size:0x%08u ===\n", info->tclinux_size);
	printf("=== kernel_off:0x%08u ===\n", info->kernel_off);
	printf("=== kernel_size:0x%08u ===\n", info->kernel_size);
#endif

	noffset = fdt_subnode_offset(fit, images_noffset, "filesystem@1");
	if (noffset < 0) {
		printf("Can't get node offset for image unit name: '%s' (%s)\n",
			  "filesystem@1", fdt_strerror(noffset));
		return -1;
	}

	data = (void *)fdt_getprop(fit, noffset, "data", &len);
	if (data == NULL) {
		printf("get rootfs data error:0x%08u", (u32)data);
		return -1;
	}

	info->rootfs_off = (u32)data - (u32)fit;
	info->rootfs_size = len;
#if DBG_BOOTARGS
	printf("=== rootfs_off:0x%08u ===\n", info->rootfs_off);
	printf("=== rootfs_size:0x%08u ===\n", info->rootfs_size);
#endif
	return 0;
}

void set_tclinux_bootarg(unsigned long buf)
{
	struct tclinux_info info;
	char var[1024] = {0};
	char tclinux_size_str[16] = {0};
	char kernel_off_str[16] = {0};
	char kernel_size_str[16] = {0};
	char rootfs_off_str[16] = {0};
	char rootfs_size_str[16] = {0};
	char *ethaddr;
	#define TCLINUX_INFO_STR	("tclinux_info=")

    if (get_tclinux_arm((const void *) (buf), &info))
		return;

	sprintf(tclinux_size_str, "0x%x", info.tclinux_size);
	sprintf(kernel_off_str, "0x%x", info.kernel_off);
	sprintf(kernel_size_str, "0x%x", info.kernel_size);
	sprintf(rootfs_off_str, "0x%x", info.rootfs_off);
	sprintf(rootfs_size_str, "0x%x", info.rootfs_size);

	/* set image to kernel */
	strcat(var, TCLINUX_INFO_STR);
	strcat(var, tclinux_size_str);
	strcat(var, ",");
	strcat(var, kernel_off_str);
	strcat(var, ",");
	strcat(var, kernel_size_str);
	strcat(var, ",");
	strcat(var, rootfs_off_str);
	strcat(var, ",");
	strcat(var, rootfs_size_str);
	strcat(var, " ");

	/* set MAC address to kernel */
	parse_mi_config(var);

#if DBG_BOOTARGS
	printf("=== var:%s ===\n", var);
#endif

	setenv("bootargs", var);
}

static int flash_command(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	unsigned long			addr = 0;
	unsigned long			len = 0;
	unsigned long			buf = CONFIG_SYS_LOAD_ADDR;
	unsigned long			retlen = 0;
	unsigned long			img_size = 0;
	image_header_t			*img_hdr = (image_header_t *)buf;

	if (argc < 2)
		return CMD_RET_USAGE;

	if (!strncmp(argv[1], "init", 5))
	{
		printf("init: %d\n", flash_init(0));
	}
	else if (!strncmp(argv[1], "erase", 6))
	{	
		addr = simple_strtoul(argv[2], NULL, 16);	
		len  = simple_strtoul(argv[3], NULL, 16);

		printf("erase: addr=0x%lx, len=0x%lx\n", addr, len);
		flash_erase(addr, len);
	}
	else if (!strncmp(argv[1], "read", 5))
	{
		addr = simple_strtoul(argv[2], NULL, 16);	
		len  = simple_strtoul(argv[3], NULL, 16);
		if (argc == 5)
		{
			buf = simple_strtoul(argv[4], NULL, 16);
		}

		printf("read: src=0x%lx, len=0x%lx, dst=0x%lx\n", addr, len, buf);
		flash_read(addr, len, &retlen, (unsigned char *) buf);
	}
	else if (!strncmp(argv[1], "write", 6))
	{
		addr = simple_strtoul(argv[2], NULL, 16);	
		len  = simple_strtoul(argv[3], NULL, 16);
		if (argc == 5)
		{
			buf = simple_strtoul(argv[4], NULL, 16);
		}

		printf("write: src=0x%lx, len=0x%lx, dst=0x%lx\n", buf, len, addr);
		flash_write(addr, len, &retlen, (unsigned char *) buf);
	}
	else if (!strncmp(argv[1], "imgread", 6))
	{
		addr = simple_strtoul(argv[2], NULL, 16);	
		len  = simple_strtoul(argv[3], NULL, 16);
		if (argc == 5)
		{
			buf = simple_strtoul(argv[4], NULL, 16);
		}

		printf("read: src=0x%lx, len=0x%lx, dst=0x%lx\n", addr, len, buf);
		flash_read(addr, len, &retlen, (unsigned char *) buf);

		if (PART_FDT_MAGIC == (unsigned long) fdt_magic(buf))
		{
			/*
			* If img_size is large than 0, it specifies that user assigns a image size.
			* Or we read image size from image header.
			*/

			img_size = (unsigned long) fdt_totalsize(buf);
			printf("This is a FIT Image, img_size = 0x%lx\n", img_size);
		}
		else if (IH_MAGIC == uimage_to_cpu(img_hdr->ih_magic))
		{
			img_size = uimage_to_cpu(img_hdr->ih_size) + sizeof(image_header_t);
			printf("This is a uImage, img_size = 0x%lx\n", img_size);
		}

		if (img_size)
		{
			printf("read: src=0x%lx, len=0x%lx, dst=0x%lx\n", addr, img_size, buf);
			flash_read(addr, img_size, &retlen, (unsigned char *) buf);
			set_tclinux_bootarg(buf);
		}

	}
	else if (!strncmp(argv[1], "imgwrite", 6))
	{
		addr = simple_strtoul(argv[2], NULL, 16);	
		len  = simple_strtoul(argv[3], NULL, 16);
		if (argc == 5)
		{
			buf = simple_strtoul(argv[4], NULL, 16);
		}

		if (PART_FDT_MAGIC == (unsigned long) fdt_magic(buf))
		{
			/*
			* If img_size is large than 0, it specifies that user assigns a image size.
			* Or we read image size from image header.
			*/

			img_size = (unsigned long) fdt_totalsize(buf);
			printf("This is a FIT Image, img_size = 0x%lx\n", img_size);
		}
		else if (IH_MAGIC == uimage_to_cpu(img_hdr->ih_magic))
		{
			img_size = uimage_to_cpu(img_hdr->ih_size)+sizeof(image_header_t);
			printf("This is a uImage, img_size = 0x%lx\n", img_size);
		}

		if (img_size && (img_size != len))
		{
			printf("len(0x%lx) and img_size(0x%lx) is different\n", len, img_size);				
		}

		printf("erase: addr=0x%lx, len=0x%lx\n", addr, len);
		flash_erase(addr, len);

		printf("write: src=0x%lx, len=0x%lx, dst=0x%lx\n", buf, len, addr);
		flash_write(addr, len, &retlen, (unsigned char *) buf);
	}
	else if (!strncmp(argv[1], "upgrade", 6))
	{
		addr = simple_strtoul(argv[2], NULL, 16);
		len  = simple_strtoul(argv[3], NULL, 16);
		if (argc == 5)
		{
			buf = simple_strtoul(argv[4], NULL, 16);
		}

		flash_erase_non_block(addr, len);
		flash_write(addr, len, &retlen, (unsigned char *) buf);
	}
	else
		return CMD_RET_USAGE;
					     
	return 0;
}

U_BOOT_CMD(
		flash,   5,      0,      flash_command,
		"flash - flash command\n",
		"flash usage:\n"
		"	flash init\n"
		"	flash erase [addr] [len]\n"
		"	flash read [src] [len] *[dst]\n"
		"	flash write [dst] [len] *[src]"
);

