#include <common.h>
#include <linux/mtd/mtd.h>
#include <command.h>
#include <watchdog.h>
#include <malloc.h>
#include <asm/byteorder.h>
#include <net.h>
#include <mapmem.h>
#include <nand.h>

int get_data_length_from_nand(ulong offset, ulong *data_length)
{
	const image_header_t *hdr;
	nand_info_t *nand;
	size_t read_len;
	ulong addr;
	int ret;

	read_len = sizeof(image_header_t);
	addr = CONFIG_SYS_LOAD_ADDR;
	nand = nand_info[0];
	printf("nand read header offset is %lu\n", offset);
	ret = nand_read_skip_bad(nand, offset, &read_len,
		NULL, nand->size, (u_char *)addr);

	hdr = (image_header_t *)addr;

	*data_length = htonl(hdr->ih_size);
	printf("data length is %lx\n", *data_length);

	return ret;
}

int copy_imagedata_to_ram(ulong offset, ulong ram_addr, ulong data_length)
{
	nand_info_t *nand;
	u32 read_len;
	int ret;

	nand = nand_info[0];
	read_len = data_length + 64;
#if IMAGE_ENABLE_FIT
	read_len = data_length + 0x2000;
#endif
	printf("nand read data from offset 0x%lx to 0x%lx",  offset, ram_addr);
	printf("length is 0x%x,limit is 0x%llx\n", read_len, nand->size);

	ret = nand_read_skip_bad(nand, offset, &read_len,
		NULL, nand->size, (u_char *)ram_addr);

	return ret;
}

int copy_word_data_to_ram(ulong offset, ulong ram_addr)
{
	nand_info_t *nand;
	size_t read_len;
	int ret;
	ulong offset_tmp;
	u_char *tmp_data = (u_char *)malloc(sizeof(u_char)*2048);

	nand = nand_info[0];
	offset_tmp = offset;
	read_len = 4 + (offset_tmp & 0x7ff);
	offset_tmp = offset_tmp & 0xfffff800;
	/*printf("nand read data offset is %lu, 0x%x, 0x%lu\n",
	 *offset, read_len, ram_addr);
	 */
	ret = nand_read_skip_bad(nand, offset_tmp,
			&read_len, NULL, nand->size, tmp_data);
	memcpy((ulong *)ram_addr, (ulong *)(tmp_data + (offset & 0x7ff)), 4);

	free(tmp_data);
	tmp_data = NULL;
	return ret;
}

int update_image_to_nand(ulong offset, size_t length, unsigned char *buf)
{
	nand_info_t *nand;
	nand_erase_options_t opts;
	int ret;

	memset(&opts, 0, sizeof(opts));
	/*printf("offset is %x, len is %x, buf is 0x%x\n",
	 * offset, length, buf);
	 */

	nand = nand_info[0];
	opts.offset = offset;
	/*
	 *if (length%nand->erasesize)
	 *	opts.length = (length + nand->erasesize)
	 *	& (~(nand->erasesize -1));
	 *else
	 */
	opts.length = length;
	/*erase size does not include bad block size*/
	opts.spread = 1;
	/*	printf("erase offset is 0x%x, len is 0x%x\n",
	 *	opts.offset, opts.length);
	 */
	ret = nand_erase_opts(nand, &opts);
	ret = nand_write_skip_bad(nand, offset,  &length,
		NULL, nand->size, buf, 0);

	return ret;
}

int for_update_image_to_nand(ulong offset, size_t length, unsigned char *buf,
			size_t erase_length)
{
	nand_info_t *nand;
	nand_erase_options_t opts;
	int ret;

	memset(&opts, 0, sizeof(opts));
	/*printf("offset is %x, len is %x, buf is 0x%x\n",
	 * offset, length, buf);
	 */

	nand = nand_info[0];
	opts.offset = offset;
	opts.length = erase_length;
	/*erase size does not include bad block size*/
	opts.spread = 1;
	/*	printf("erase offset is 0x%x, len is 0x%x\n",
	 *	opts.offset, opts.length);
	 */
	ret = nand_erase_opts(nand, &opts);
	ret = nand_write_skip_bad(nand, offset,  &length,
		NULL, nand->size, buf, 0);

	return ret;
}

static int do_cmp(u32 addr1, u32 addr2, u32 len)
{
	ulong count, ngood, bytes;
	int	size;
	int     rcode = 0;
	const void *buf1, *buf2, *base;

	/* Check for size specification.*/

	size = 1;

	/*addr1 = 0x81800000;*/

	/*addr2 = 0x82000000;*/

	count = len;

	bytes = size * count;
	base = buf1 = map_sysmem(addr1, bytes);
	buf2 = map_sysmem(addr2, bytes);

	for (ngood = 0; ngood < count; ++ngood) {
		ulong word1, word2;

		if (size == 4) {
			word1 = *(ulong *)buf1;
			word2 = *(ulong *)buf2;
		} else if (size == 2) {
			word1 = *(ushort *)buf1;
			word2 = *(ushort *)buf2;
		} else {
			word1 = *(u_char *)buf1;
			word2 = *(u_char *)buf2;
		}
		if (word1 != word2) {
			ulong offset = buf1 - base;

			printf("at 0x%08lx (%#0*lx) != at 0x%08lx (%#0*lx)\n",
				(ulong)(addr1 + offset), size, word1,
				(ulong)(addr2 + offset), size, word2);
			rcode = 1;
			break;
		}

		buf1 += size;
		buf2 += size;

		/* reset watchdog from time to time */
		if ((ngood % (64 << 10)) == 0)
			WATCHDOG_RESET();
	}

	unmap_sysmem(buf1);
	unmap_sysmem(buf2);

/*	printf("Total of %ld %s(s) were the same\n", ngood, type);*/
	return rcode;
}


int nand_test(void)
{
	u32 len;
	u32 *buf;
	int ret;
	nand_info_t *nand;
	nand_erase_options_t opts;
	u32 ram_addr = 0x81800000;
	printf("enter nand test function\n");

	net_loop(TFTPGET);

	len = getenv_hex("filesize", 0);
	buf = (u32 *)0x82000000;
	printf("file len is %x\n", len);

	memset(&opts, 0, sizeof(opts));
/*	printf("offset is %x, len is %x, buf is 0x%x\n",
 *	offset, length, buf);
 */

	nand = nand_info[0];
	opts.offset = 0x120000;
/*
 *	if (length%nand->erasesize)
 *		opts.length = (length + nand->erasesize)
 *		& (~(nand->erasesize -1));
 *	else
 */
		opts.length = len;

/*	printf("erase offset is 0x%x, len is 0x%x\n",
 *	opts.offset, opts.length);
 */
	ret = nand_erase_opts(nand, &opts);
	ret = nand_write_skip_bad(nand, opts.offset,  &len,
			NULL, nand->size, (u_char *)buf, 0);

	printf("nand read start..., len is %x\n", len);
	ret = nand_read_skip_bad(nand, opts.offset, &len,
			NULL, nand->size, (u_char *)ram_addr);

	printf("start compare\n");
	ret = do_cmp(ram_addr, (u32)buf, len);

/*	printf("result is 0x%x\n", ret);*/

	if (ret == 0) {
		printf("compare success\n");
/*		_machine_restart();*/
	}
	return 0;
}
