/*
 * Commands related to AlliedWare+ release system
 *
 * Allied Telesis Labs limited
 * By Matthew Millman (matthew.millman@alliedtelesis.co.nz)
 * 
 * 
 */

#include <common.h>
#include <command.h>
#include <malloc.h>
#include <jffs2/jffs2.h>
#ifdef CONFIG_CMD_FAT
#include <fat.h>
#endif
#include <linux/list.h>
#include <linux/ctype.h>
#include <watchdog.h>
#include <version.h>
#include <asm/byteorder.h>
#ifdef CONFIG_MIPS
#include <asm/addrspace.h>
#endif
#include <epi3.h>
#include <atl_release.h>
#ifdef CONFIG_DIAGNOSTICS
#include <diag.h>
#endif
#ifdef CONFIG_BOOTMENU
#include <bootmenu.h>
#else
#define bootmenu_printf(...)
#endif
#ifdef CONFIG_LZMA
#include <lzma/LzmaTypes.h>
#include <lzma/LzmaDec.h>
#include <lzma/LzmaTools.h>
#endif /* CONFIG_LZMA */
#ifdef CONFIG_MACH_SBX81CFC
#include "../board/alliedtelesis/SBx81CFC/sbx81cfc.h"
#endif /* CONFIG_MACH_SBX81CFC */
#ifdef CONFIG_USB_STORAGE
#include <usb.h>
#endif /* CONFIG_USB_STORAGE */

/* enable/disable debugging messages */
#define	DEBUG
#undef	DEBUG

#ifdef  DEBUG
# define DEBUGF(fmt, args...)	printf(fmt ,##args)
#else
# define DEBUGF(fmt, args...)
#endif

#define MIPS_MAX_CACHE_SIZE	0x10000

# define CHUNKSZ (64 * 1024)
DECLARE_GLOBAL_DATA_PTR;

int bootfallback = 0;

int do_reset (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]);

extern int do_flerase (cmd_tbl_t *, int, int, char *[]);
extern int do_mem_cp  (cmd_tbl_t *, int, int, char *[]);
extern int do_protect (cmd_tbl_t *, int, int, char *[]);
extern int do_bootm (cmd_tbl_t *, int, int, char *[]);
extern int mtdparts_init(void);
extern struct part_info* jffs2_part_info(struct mtd_device *dev, unsigned int part_num);
extern struct mtd_device * get_mtd_current_dev(void);
extern u8 get_mtd_current_partnum(void);

extern int gunzip(void *, int, uchar *, unsigned long *);

static int boot_alliedware_package(ulong addr, char *release_filename);
#if defined(CONFIG_FIT)
static int boot_alliedware_fit(ulong addr, char *release_filename);
extern int do_bootm (cmd_tbl_t *, int, int, char *[]);
#endif

int install_from_mem(u32 addr)
{
	char buf[512];
	
	sprintf(buf, "prot off 0x%08X 0x%08X", CONFIG_UBOOT_FLASH_BASE, CONFIG_UBOOT_FLASH_BASE + CONFIG_UBOOT_SIZE - 1);
	if (run_command(buf, 0) < 0)
		return 0;
	
	sprintf(buf, "erase 0x%08X 0x%08X", CONFIG_UBOOT_FLASH_BASE, CONFIG_UBOOT_FLASH_BASE + CONFIG_UBOOT_SIZE - 1);
	if (run_command(buf, 0) < 0)
		return 0;
	
	sprintf(buf, "cp.b 0x%08X 0x%08X 0x%08X", addr, CONFIG_UBOOT_FLASH_BASE, CONFIG_UBOOT_SIZE);
	if (run_command(buf, 0) < 0)
		return 0;
	
	sprintf(buf, "prot on 0x%08X 0x%08X", CONFIG_UBOOT_FLASH_BASE, CONFIG_UBOOT_FLASH_BASE + CONFIG_UBOOT_SIZE - 1);
	if (run_command(buf, 0) < 0)
		return 0;
	
	return 1;
	
}

int do_mfg_automation_tool(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	u32 ret;
	ret = 0;

	if (!argv[1])
		return 0;

#ifdef CONFIG_DIAGNOSTICS
	if (!strcmp(argv[1], "erase_flash"))
	{
#ifdef DIAG_FLASH
	        ret = diag_erase_all_flash(1, 0);
		goto done;
#endif /* DIAG_FLASH */
	}

#if defined(CONFIG_ATLAT10908) || defined(CONFIG_ATLAT9924TS)
	if (!strcmp(argv[1], "erase_flash_all"))
	{
#ifndef CONFIG_SYS_NO_FLASH
		ret = diag_erase_all_flash(1, 1);
#endif /* CONFIG_SYS_NO_FLASH */
		goto done;
	}
#endif /* defined(CONFIG_ATLAT10908) || defined(CONFIG_ATLAT9924TS) */
#endif /* CONFIG_DIAGNOSTICS */
	
#ifdef CONFIG_DIAG_EPI3
	if (!strcmp(argv[1], "erase_epi3"))
	{
#ifndef CONFIG_SYS_NO_FLASH
		ret = diag_epi3_erase_personality_area(1);
#endif /* CONFIG_SYS_NO_FLASH */
		goto done;
	}
#endif
	
#ifdef CONFIG_BOOTMENU
	if (!strcmp(argv[1], "erase_env"))
	{
		bootmenu_restore_default_settings(1);
		goto done;
	}
#endif
	
	if (!strcmp(argv[1], "copy_bootcode"))
	{
		u32 addr;
		
		if (!argv[2]) {
			printf("Error: missing parameter\n");
			return 1;
		}
		addr = simple_strtoul(argv[2], NULL, 16);
		
		ret = install_from_mem(addr);
		goto done;
	}
	
	printf("Error: token: %s not understood\n", argv[1]);
	return 1;
	
done:	
	printf("operation complete\n");
	return ret;
}

#ifdef CONFIG_MACH_SBX81CFC
int detect_bootp_trigger(struct part_info * part)
{
	//This file is created in userspace when an incompatible release is detected.
	if (jffs2_1pass_file_exists(part, ".try_bootp_boot")) {
		bootmenu_printf("Last booted release was incompatible with master, trying bootp.\n");
		try_bootp_boot();
		// if bootp works, we will never get here.
		bootmenu_printf("bootp failed, booting configured release.\n");
	}
	return 0;
}
#endif /* CONFIG_MACH_SBX81CFC */

/**
 * Special routine which is used for the bootup sequence of the
 * AlliedWare+ release system
 *
 * @param cmdtp command internal data
 * @param flag command flag
 * @param argc number of arguments supplied to the command
 * @param argv arguments list
 * @return 0 on success, 1 otherwise
 */

#ifndef CONFIG_SYS_NO_FLASH

int do_awbootf(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	const char *releasedefs[] = {
		".release",
		".backup",
	};
	char *bootimg;
	char *filename = NULL;
	
	int size;
	int numreleasedefs = sizeof(releasedefs)/sizeof(releasedefs[0]);
	int have_release = 0;
	int i;
	int ret;
	
	struct part_info *part = NULL;
	char *release;
	char *releaseext;
	char *bootstrap;
	
	char *header;
	char *s;
	
	release = malloc(8192);
	header = malloc(8192);
	bootstrap = malloc(8192);

	releasedefs[0] = ".release";
	releasedefs[1] = ".backup";
	
	bootimg = NULL;
	releaseext = NULL;

	//bootstrapdef = ".bootstrap";
	
	if (argc == 2)
	{
		bootimg = argv[1];
		have_release = 1;
		numreleasedefs = 1;
	}


	load_addr = CONFIG_DIAGMEM_BASE;
	
	/* make sure we are in sync with env variables */
	ret = mtdparts_init();
	if (ret == 0)
	{
		bootmenu_printf("\nReading filesystem...\n");
		part = jffs2_part_info(get_mtd_current_dev(), get_mtd_current_partnum());
	}
	else
	{
		printf("mtdparts_init() fail\n");
	}

	if (part)
	{
#ifdef CONFIG_MACH_SBX81CFC
		/* Check for existence of bootp trigger file in flash */
		detect_bootp_trigger(part);
#endif /* CONFIG_MACH_SBX81CFC */
		for (i = bootfallback ? 1 : 0; i < numreleasedefs; i++)
		{
			if (!have_release)
			{
				/* Start out by reading which release to load */

				size = jffs2_1pass_load(release, part, releasedefs[i], ~(u32)0);

				if (size <= 0)
				{
					bootmenu_printf("Error: There is no %s release file set\n",
					                i ? "backup" : "primary");
					continue;
				}

				release[size] = 0;
				bootimg = strtok(release, "\n");
				if (!bootimg)
				{
					bootmenu_printf("Error: Missing return release file information in '%s'.\n"
					                "\tPlease recover the system using the Boot Menu\n",
					                releasedefs[i]);
					continue;
				}
			}

			/* Do some tests on the filename of the release, including whether
			or not this is a dynamic release */

			s = strrchr(bootimg, '.');
			releaseext = s + 1;

			if (!s || strcmp(releaseext, CONFIG_RELEASEFILE_EXT + 1))
			{
				bootmenu_printf("Error: Release filename is invalid "
				                "(should be <release>" CONFIG_RELEASEFILE_EXT ")\n");
				continue;
			}

			/* Figure out if the file is in flash: or card: */
			if ((s = strchr(bootimg, ':')))
			{
				filename = s + 1;
			}
			else
			{
				filename = bootimg;
			}
#if defined(CONFIG_MMC_SPI) && defined(CONFIG_CMD_FAT)
			if (filename != bootimg && strncmp(bootimg, "card:", 5) == 0)
			{
				/* Load 'filename' from card: */
				block_dev_desc_t *dev_desc = NULL;
				int part = 1;

				if ((dev_desc = get_dev("mmcspi", 0)) == NULL)
				{
					bootmenu_printf("Error: Invalid boot device\n");
					continue;
				}
				if (fat_register_device(dev_desc, part) != 0)
				{
					bootmenu_printf("Error: Unable to boot from card:\n");
					continue;
				}
				/* Do a dummy read of the header, to see if the file actually exists */
				size = file_fat_read(filename, header, sizeof(release_header));
				if (size <= 0)
				{
					bootmenu_printf("Error: Preferred release: card:%s does not exist\n",
					                filename);
					continue;
				}
				bootmenu_printf("Loading card:%s...\n", filename);
				size = file_fat_read (filename, (char *)load_addr, ~(u32)0);
				if (size <= 0)
				{
					bootmenu_printf("Error loading card:%s\n", filename);
					continue;
				}
			}
			else
#endif /* CONFIG_MMC_SPI && CONFIG_CMD_FAT */
#if defined(CONFIG_USB_STORAGE) && defined(CONFIG_CMD_FAT)
			if (filename != bootimg && strncmp(bootimg, "usb:", 4) == 0)
			{
				/* initialise usb by scanning for devices */
				usb_stor_scan(0);
				/* Load 'filename' from usb: */
				block_dev_desc_t *dev_desc = NULL;
				int part = 1;

				if ((dev_desc = get_dev("usb", 0)) == NULL)
				{
					bootmenu_printf("Error: Invalid boot device\n");
					continue;
				}
				if (fat_register_device(dev_desc, part) != 0)
				{
					bootmenu_printf("Error: Unable to boot from usb:\n");
					continue;
				}
				/* Do a dummy read of the header, to see if the file actually exists */
				size = file_fat_read(filename, header, sizeof(release_header));
				if (size <= 0)
				{
					bootmenu_printf("Error: Preferred release: usb:%s does not exist\n",
					                filename);
					continue;
				}
				bootmenu_printf("Loading usb:%s...\n", filename);
				size = file_fat_read (filename, (char *)load_addr, ~(u32)0);
				if (size <= 0)
				{
					bootmenu_printf("Error loading usb:%s\n", filename);
					continue;
				}
			}
			else
#endif /* CONFIG_USB_STORAGE && CONFIG_CMD_FAT */
			{
				/* Load 'filename' from flash: */
				/* Do a dummy read, to see if the file actually exists */
				size = jffs2_1pass_load(NULL, part, filename, ~(u32)0);
				if (size <= 0)
				{
					bootmenu_printf("Error: Preferred release: flash:%s does not exist\n",
					                filename);
					continue;
				}

				bootmenu_printf("Loading flash:%s...\n", filename);
				size = jffs2_1pass_load((char *)load_addr, part, filename, ~(u32)0);

				if (size <= 0)
				{
					bootmenu_printf("Error loading flash:%s\n", filename);
					continue;
				}
			}
			/* File has been loaded; try to boot it */
#if defined(CONFIG_FIT)
			/* Check for a FIT image */
			if (genimg_get_format ((void *)load_addr) == IMAGE_FORMAT_FIT &&
			    fit_check_format ((const void *)load_addr)) {
				boot_alliedware_fit (load_addr, filename);
				bootmenu_printf("Error: Primary release boot failed, trying secondary...\n\n");
			}
			else
#endif
			if (boot_alliedware_package(load_addr, filename) && (i == 0))
			{
				bootmenu_printf("Error: Primary release boot failed, trying secondary...\n\n");
			}
		} /* for (;;) */

		if (!have_release)
		{
			bootmenu_printf("Error: Boot failed. Please recover the system using the Boot Menu\n");
		}
	} /* if (part) */

	bootmenu_printf("Restarting...\n");
	do_reset(NULL, 0, 0, NULL);

	return 1;
}

#endif

#if !defined(WATCHDOG_RESET)
#define WATCHDOG_RESET() 
#endif


unsigned long checksum32(void *data, unsigned long len)
{
	int ret = 0;
	
	while (len--)
	{
		ret += *(unsigned char *)data++;
		
		if ((ulong)data % CHUNKSZ == 0)
			WATCHDOG_RESET();
	}
	
	return ret;
}

ulong crc32_wdog(void *data, ulong len)
{
	ulong cdata;
	ulong edata;
	ulong csum;
	
	csum = 0;
	cdata = (ulong)data;
	edata = cdata + len;

	while (cdata < edata) 
	{
		ulong chunk = edata - cdata;

		if (chunk > CHUNKSZ)
			chunk = CHUNKSZ;
		
		csum = crc32(csum, (uchar *)cdata, chunk);
		cdata += chunk;

		WATCHDOG_RESET();
	}
	
	return csum;
}

int do_awbootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	u32 boot_addr;
	char release_filename[512];

	if (argc < 1)
	{
		boot_addr = load_addr;
	}
	else
	{
		boot_addr = simple_strtoul(argv[1], NULL, 16);
	}
	strncpy(release_filename, getenv("bootfile") ? getenv("bootfile") : "default" CONFIG_RELEASEFILE_EXT, sizeof(release_filename));
	
#if defined(CONFIG_FIT)
	/* Check for a FIT image */
	if (genimg_get_format ((void *)boot_addr) == IMAGE_FORMAT_FIT &&
	    fit_check_format ((const void *)boot_addr)) {
		boot_alliedware_fit (boot_addr, release_filename);
	} else
#endif

	boot_alliedware_package(boot_addr, release_filename);
	
	return 0;
}

static char *alliedware_bootargs(char *release_filename)
{
	char *kernel_args = (char *)malloc (512 + strlen (getenv ("relargs") ? getenv ("relargs") : ""));
	sprintf (kernel_args, "console=ttyS0,%s root=/dev/ram0 releasefile=%s bootversion=%s loglevel=%s extraflash=%08X",
		getenv ("baudrate"),
		release_filename,
		ATL_EXTRA_VERSION,
		getenv ("loglevel") ? getenv ("loglevel") : "1",
		atl_get_offset_from_base ());
	if (getenv ("mtdoops"))
		sprintf(kernel_args+strlen(kernel_args), " mtdoops.mtddev=%s", getenv ("mtdoops"));
	if (getenv ("mtdparts"))
		sprintf(kernel_args+strlen(kernel_args), " %s", getenv ("mtdparts"));
	if (gd->flags & GD_FLG_ONEOFFBOOT_FORCED)
		sprintf(kernel_args+strlen(kernel_args), " oneoffboot");
	if (gd->flags & GD_FLG_PASSWORD_RECOVERY)
		sprintf(kernel_args+strlen(kernel_args), " passwordrecovery");
	if (gd->flags & GD_FLG_DEFAULT_CONFIG)
		sprintf(kernel_args+strlen(kernel_args), " defaultconfig");
	if (getenv ("rtctime"))
		sprintf(kernel_args+strlen(kernel_args), " rtctime=%s", getenv ("rtctime"));
	if (getenv ("relargs"))
		sprintf(kernel_args+strlen(kernel_args), " %s", getenv ("relargs"));

	return kernel_args;
}

#if defined(CONFIG_FIT)
static int boot_alliedware_fit (ulong addr, char *release_filename)
{
	compatible_device compatible_devices[] = CONFIG_COMPATIBLE_DEVICES;
	char fit_argv_addr[32];
	char *fit_argv[3] = { "bootm", fit_argv_addr,  NULL };
	const char *board_name = NULL;
#ifdef CONFIG_DIAG_EPI3
	epi3Personality *epi3;
	int i;
#endif

	/* Construct and set kernel arguments */
	setenv ("bootargs", alliedware_bootargs(release_filename));

	/* Default to using the first compatible board name */
	board_name = compatible_devices[0].board_name;
#ifdef CONFIG_DIAG_EPI3
	/* Match the board name to personality */
	i = 0;
	epi3 = epi3_getcurrent();
	while (epi3 && compatible_devices[i].board_id) {
		if (epi3->epiBoardId == compatible_devices[i].board_id)
			board_name = compatible_devices[i].board_name;
			break;
		i++;
	}
#endif

	/* Use standard Boot from memory routines (FIT argument of address#config) */
	printf ("Booting image at %08x(%s) ...\n", (unsigned int)addr, board_name);
	sprintf (fit_argv_addr, "%x#%s", (unsigned int)addr, board_name);
	do_bootm (find_cmd ("bootm"), 0, 2, fit_argv);

	return 0;
}
#endif

#ifndef CONFIG_SYS_BOOTM_LEN
#define CONFIG_SYS_BOOTM_LEN	0x800000	/* use 8MByte as default max gunzip size */
#endif

static int boot_alliedware_package(ulong addr, char *release_filename)
{
	ulong	iflag;
#ifdef CONFIG_PPC
	ulong	sp;
#endif
	ulong	kernel_data;
	ulong	kernel_len;
	
	ulong	initrd_data;
	ulong	initrd_len;
	
	ulong	checksum;
	uint	unc_len = CONFIG_SYS_BOOTM_LEN;
	
	ulong tmp;
	
	release_header header;
	release_header *hdr = &header;
#if defined(CONFIG_PPC) || defined (CONFIG_MIPS)
#if defined(CONFIG_PPC)
	void	(*kernel)(bd_t *, ulong, ulong, ulong, ulong);
#elif defined(CONFIG_MIPS)	
	void	(*kernel)(bd_t *, ulong, ulong, ulong);
#endif
	bd_t	*kbd;
	char    *cmdline;
#elif defined(CONFIG_ARM)
	int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images);
	bootm_headers_t images;		/* pointers to os/initrd/fdt images */
#else
#error No arch defined!
#endif
	char 	*kernel_args;
	
	
	printf ("Booting image at %08lx ...\n", addr);

	/* Copy header so we can blank CRC field for re-calculation */
	memcpy(hdr, (char *)addr, sizeof(release_header));

	checksum = ntohl(hdr->header_crc32);
	hdr->header_crc32 = 0;
	
	
	if (ntohl(hdr->magic1) != MAGIC1
            || ntohl(hdr->magic2) != MAGIC2
            || ntohl(hdr->magic3) != MAGIC3
            || ntohl(hdr->magic4) != MAGIC4)
	{
		bootmenu_printf("Error: Release header corrupt.\n");
		return 1;
	}
	

	if ((tmp = crc32 (0, (void *)hdr, sizeof(release_header))) != checksum) 
	{
		bootmenu_printf("Error: Release header CRC check failed.\n");
		return 1;
	}

	if (ntohs(hdr->header_version) != 4)
	{
		bootmenu_printf("Error: The Bootloader version currently installed cannot boot this file.\n");
		return 1;
	}
	
	if (ntohs(hdr->intended_family) != CONFIG_THIS_DEVICE)
	{
		bootmenu_printf("Error: This release file is not intended for this device.\n");
		return 1;
	}
	
#ifdef CONFIG_RELEASE_SPECIAL
	if (ntohl(hdr->release_special) < CONFIG_RELEASE_SPECIAL)
	{
		bootmenu_printf("Error: This Release cannot be booted due to incompatibility with this\n\tBootloader version.\n");
		return 1;
	}
#endif
#ifdef CONFIG_DIAG_EPI3
	if (!epi3_getcurrent())
	{
		bootmenu_printf("Error: This device does not currently have a valid personality,\n\tTherefore cannot be booted\n");
		do_reset(NULL, 0, 0, NULL);
	}
#endif

	
	kernel_data = addr + ntohl(hdr->kernel_offset);
	kernel_len  = ntohl(hdr->kernel_size);
	
	initrd_data = addr + ntohl(hdr->initrd_offset);
	initrd_len = ntohl(hdr->initrd_size);
	
	bootmenu_printf("Verifying release... ");
	
	switch (ntohs(hdr->check_type))
	{
		case CHECK_TYPE_NONE:
			bootmenu_printf("\nWarning: This release has been built with bootup verification disabled.\n");
			break;
		case CHECK_TYPE_CHECKSUM16:
			break;
		case CHECK_TYPE_CHECKSUM32:
			if (checksum32((void *)kernel_data, kernel_len) !=  ntohl(hdr->kernel_checksum))
			{
				bootmenu_printf ("\nError: Bad kernel data checksum.\n");
				return 1;
			}
			if (checksum32((void *)initrd_data, initrd_len) !=  ntohl(hdr->initrd_checksum))
			{
				bootmenu_printf ("\nError: Bad release data checksum.\n");
				return 1;
			}
			bootmenu_printf("OK\n");
			break;
		case CHECK_TYPE_CRC16:
			break;
		case CHECK_TYPE_CRC32:
			if (crc32_wdog((void *)kernel_data, kernel_len) != ntohl(hdr->kernel_checksum)) 
			{
				bootmenu_printf ("\nError: Bad kernel data CRC.\n");
				return 1;
			}
			if (crc32_wdog((void *)initrd_data, initrd_len) != ntohl(hdr->initrd_checksum)) 
			{
				bootmenu_printf ("\nError: Bad release data CRC.\n");
				return 1;
			}
			bootmenu_printf("OK\n");
			break;	
	}
	
	kernel_args = alliedware_bootargs(release_filename);
	sprintf(kernel_args+strlen(kernel_args), " ramdisk=%lu relhdr=%lu,%lu", initrd_len / 1024, addr, kernel_data + kernel_len);

	/*
	* We have reached the point of no return: we are going to
	* overwrite all exception vector code, so we cannot easily
	* recover from any failures any more...
	*/

	iflag = disable_interrupts();

	switch (hdr->kernel_compression_type) 
	{
		case COMPRESSION_TYPE_NONE:
			if (ntohl(hdr->kernel_load_addr) == addr)
			{
				break;
			} 
			else 
			{
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
				size_t l = kernel_len;
				void *to = (void *)ntohl(hdr->kernel_load_addr);
				void *from = (void *)kernel_data;

				printf ("Loading kernel");

				while (l > 0) {
					size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
					WATCHDOG_RESET();
					memmove (to, from, tail);
					to += tail;
					from += tail;
					l -= tail;
				}
#else	/* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
				memmove ((void *) ntohl(hdr->kernel_load_addr), (uchar *)kernel_data, kernel_len);
#endif	/* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
			}
			break;
		case COMPRESSION_TYPE_GZIP:
			printf ("Uncompressing kernel... ");
		
			if (gunzip ((void *)ntohl(hdr->kernel_load_addr), unc_len,
		    		(uchar *)kernel_data, &kernel_len) != 0) 
			{
				bootmenu_printf ("Error: Decompression of kernel failed.\n");
				goto fail;
			}
		    break;
#ifdef CONFIG_BZIP2
		case COMPRESSION_TYPE_BZIP2:
			printf ("Uncompressing kernel... ");
		/*
			* If we've got less than 4 MB of malloc() space,
			* use slower decompression algorithm which requires
			* at most 2300 KB of memory.
		*/
			i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->kernel_load_addr),
					&unc_len, (char *)kernel_data, kernel_len,
					CONFIG_SYS_MALLOC_LEN < (4096 * 1024), 0);
			if (i != BZ_OK) 
			{
				bootmenu_printf ("Error: Decompression of kernel failed.\n");
				goto fail;
			}
			break;
#endif /* CONFIG_BZIP2 */
#ifdef CONFIG_LZMA
		case COMPRESSION_TYPE_LZMA:
			printf ("Uncompressing kernel... ");
			/*
			 * If we've got less than 4 MB of malloc() space,
			 * use slower decompression algorithm which requires
			 * at most 2300 KB of memory.
			 */
			int i = lzmaBuffToBuffDecompress ((unsigned char*)ntohl(hdr->kernel_load_addr),
					&unc_len, (unsigned char *)kernel_data, kernel_len);
			if (i != SZ_OK)
			{
				bootmenu_printf ("Error: Decompression of kernel failed.\n");
				goto fail;
			}
			break;
#endif /* CONFIG_LZMA */
		default:
			if (iflag)
				enable_interrupts();
			bootmenu_printf("Error: Unimplemented compression type %d.\n", hdr->kernel_compression_type);
			goto fail;
	}
	
	bootmenu_printf("Booting...\n");
	
	/*
	* Booting a (Linux) kernel image
	*
	* Allocate space for command line and board info - the
	* address should be as high as possible within the reach of
	* the kernel (see CONFIG_SYS_BOOTMAPSZ settings), but in unused
	* memory, which means far enough below the current stack
	* pointer.
	*/

#if defined(CONFIG_PPC) || defined (CONFIG_MIPS)

#if defined(CONFIG_PPC)
	asm( "mr %0,1": "=r"(sp) : );

	sp -= 2048;		/* just to be sure */
	if (sp > CONFIG_SYS_BOOTMAPSZ)
		sp = CONFIG_SYS_BOOTMAPSZ;
	sp &= ~0xF;


	cmdline = (char *)((sp - CONFIG_SYS_BARGSIZE) & ~0xF);
	kbd = (bd_t *)(((ulong)cmdline - sizeof(bd_t)) & ~0xF);
#elif defined (CONFIG_MIPS)
	cmdline = (char *)(gd->bd->bi_boot_params);
	kbd = (bd_t *)((ulong)cmdline - sizeof(bd_t));
#endif
	strcpy (cmdline, kernel_args);

	*kbd = *(gd->bd);

#if defined(CONFIG_SYS_INIT_RAM_LOCK) && !defined(CONFIG_E500) && !defined(CONFIG_E300)
	unlock_ram_in_cache();
#endif
	
#if defined(CONFIG_PPC)
	kernel = (void (*)(bd_t *, ulong, ulong, ulong, ulong)) ntohl(hdr->kernel_entry_point);
	(*kernel) (kbd, addr, initrd_data + initrd_len, (u32)cmdline, (u32)cmdline + strlen(cmdline));
#elif defined (CONFIG_MIPS)
	kernel = (void (*)(bd_t *, ulong, ulong, ulong)) ntohl(hdr->kernel_entry_point);
	flush_cache(hdr->kernel_load_addr, kernel_len);
	(*kernel) (kbd, addr, initrd_data + initrd_len, (u32)cmdline);
#endif

#elif defined(CONFIG_ARM)

	setenv("bootargs", kernel_args);
	images.ep = ntohl(hdr->kernel_entry_point);
	images.rd_start = initrd_data;
	images.rd_end = initrd_data + initrd_len;
	do_bootm_linux(0, 0, NULL, &images);

#else
#error No arch defined!
#endif

	bootmenu_printf("Error\n");
fail:
	bootmenu_printf("Restarting...\n");
	do_reset(NULL, 0, 0, NULL);
	
	return 1;
}


#if defined(CONFIG_ATLAT10908) || defined(CONFIG_ATLAT9924TS)

int do_i2c_fdr (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	u_char divider;
	u8 *i2c_ptr = ((u8 *)(CONFIG_SYS_CCSRBAR + 0x3000));

	if (argc < 2) {
		divider = *(volatile u8 *)&i2c_ptr[4];
		printf ("Divider is set to %02x\n", divider);
	} 
	else {
		divider = simple_strtoul(argv[1], NULL, 16);
		*(volatile u8 *)&i2c_ptr[4] = divider;
	}
	
	return 0;
}

U_BOOT_CMD(
	ifdr,   2,      1,      do_i2c_fdr,
	"i2c fdr divider",
	"divider\n\t- i2c fdr value\nno arguments to show current value"
	);

#endif

int cmd_get_delay_modifier(char *arg)
{
	int len = strlen(arg);
	if (len > 5 && arg[len-2] == '.') {
		switch(arg[len-1]) {
			case 's':
				return 0;
			case 'm':
				return 1;			
			case 'u':
				return 2;
			default:
				return -1;
		}
	}
	return 0;
}


#ifdef CONFIG_MIPS
int do_reloc (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	u32 loc;
	
	if (argc < 2) {
		printf("TEXT_BASE in ROM (initial address) : 0x%08X\n", (unsigned int)TEXT_BASE);
		printf("Relocation offset                  : 0x%08X\n", (unsigned int)gd->reloc_off);
		printf("TEXT_BASE in RAM (final address)   : 0x%08X\n", (unsigned int)(TEXT_BASE + gd->reloc_off));
	}
	
	else {
		if (argc == 3) {
			if (*argv[1] == 'f' || *argv[1] == 'F') {
				loc = simple_strtoul(argv[2], NULL, 16);
				printf("ROM location: %08x\n", (unsigned int)(loc - gd->reloc_off));
			}
			else if (*argv[1] == 'r' || *argv[1] == 'R') {
				loc = simple_strtoul(argv[2], NULL, 16);
				printf("RAM location: %08x\n", (unsigned int)(loc + gd->reloc_off));
			}
			else {
				printf("Invalid operation\n");
			}
		}
		else {
			printf("Not enough parameters\n");
		}
	}
	return 0;
}
#endif

int do_delay (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	int modifier;
	int amount;
	int i;
	int j;
	
	modifier = cmd_get_delay_modifier(argv[0]);
	
	amount = simple_strtoul(argv[1], NULL, 10);
	
	switch (modifier)
	{
		case 0:
			for (i = 0; i < amount; i++) {
				for (j = 0; j < 1000; j++) {		
					udelay(1000);
				}
			}
			break;
		case 1:
			for (i = 0; i < amount; i++) {
				udelay(1000);
			}
			break;
		case 2:
			udelay(amount);
			break;
	}
	
	return 0;
}

U_BOOT_CMD(
	delay,   2,      2,    do_delay,
	"delay for N (decimal) microseconds/milliseconds/seconds (not calibrated)",
	"[.u .m .s] N\n"
	"\tDelay for N (decimal) microseconds/milliseconds/seconds (not calibrated)"
	);


#ifdef CONFIG_MIPS
U_BOOT_CMD(
	reloc,   3,      1,    do_reloc,
	"print U-Boot relocation information",
	""
	);
#endif


#ifndef CONFIG_SYS_NO_FLASH

U_BOOT_CMD(
	awbootf,	2,	1,	do_awbootf,
	"Start the AlliedWare+ bootup sequence from flash",
	""
	);

#endif

U_BOOT_CMD(
	awbootm,	2,	2,	do_awbootm,
	"Start the AlliedWare+ bootup sequence from a location in memory",
	"[address]"
	);

U_BOOT_CMD(
	mat, 3, 2, do_mfg_automation_tool,
	"<Manufacturing Automation Tool entry **DO NOT USE**>",
	"\n"
	"\terase_flash\n"
	"\terase_epi3\n"
	"\terase_env\n"
	"\tcopy_bootcode"
	);
