#include <common.h>
#include <malloc.h>
#include <bootmenu.h>
#include <linux/ctype.h>
#include <asm/errno.h>
#include <asm/processor.h>
#include <version.h>
#include <epi3.h>
#include <idprom.h>
#ifdef CONFIG_SPI_FLASH
#include <spi_flash.h>
#endif
#if defined(CONFIG_MPC824X)
#include <mpc824x.h>
#endif
#if defined(CONFIG_MV78100)
#include <asm/arch/mv78100.h>
#endif
#ifdef CONFIG_DISCOVERY
#include <asm/arch/discovery.h>
#endif
#ifdef CONFIG_KIRKWOOD
#include <asm/arch/kirkwood.h>
#endif
#ifdef CONFIG_ARM
#include <asm/arch/cpu.h>
#include <asm/io.h>
#endif

#ifdef CONFIG_SPI_FLASH
#define CONFIG_BM_SPI_BUS	0
#define CONFIG_BM_SPI_CS	0
#define CONFIG_BM_SPI_MAX_HZ	1000000
#define CONFIG_BM_SPI_MODE	SPI_MODE_3
#endif

DECLARE_GLOBAL_DATA_PTR;

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

void bootmenu_baudrate(void)
{
	char	key;
	u8	quit;
	u8 	i;
	u8	count;
	
	int baudrate_table[] = CONFIG_SYS_BAUDRATE_TABLE;
	count = sizeof(baudrate_table) / sizeof(int);

	quit = 0;

	while (!quit)
	{

/*		Display the menu
 */
		serial_puts( "Select baud rate:\n\n"
				"  0. Return to previous menu\n"
				"  ------------------------------------------------------\n");
		for (i = 0; i < count; i++)
		{
#ifdef CONFIG_SYS_NS16550_CLK
			if (baudrate_table[i] > (CONFIG_SYS_NS16550_CLK / 16))
				break;
#else
#error Only NS16550 supported
#endif
			
			bootmenu_printf("  %1u. %u", i + 1, baudrate_table[i]);
			bootmenu_printf("\n");
		
		}
		
		serial_puts("\nEnter selection ==> " );

/*		Wait for a key to be pressed
 */
		key = serial_getc();

		if (isdigit(key))
			serial_putc(key);

		serial_putc('\n');
		serial_putc('\n');

/*		Action the selection
 */
		
		if (key == '0')
		{
			quit = 1;
		}
		if (key >= '1' && key <= '9')
		{
			u8 idx;
			idx = key - '0';
			char buf[512];
			
			idx--;

#ifdef CONFIG_SYS_NS16550_CLK
			if (baudrate_table[idx] <= (CONFIG_SYS_NS16550_CLK / 16))
#else
#error Only NS16550 supported
#endif
			{
			
				bootmenu_printf("  Change your terminal program baud rate to %u and press enter...\n", baudrate_table[idx]);
				bootmenu_printf("  If for some reason you are unable to do this, power cycle the device\n");
				bootmenu_printf("  and the existing baud rate will be restored.\n\n");
			
				if (idx < count)
				{
					sprintf(buf, "setenv baudrate %u", baudrate_table[idx]);
					run_command(buf, 0);
				}
			
				serial_puts( "\n  Use this baud rate by default? (Y/N) ==> " );

				key = serial_getc();

				if (isprint(key))
				{
					serial_putc( key );
				}

				serial_putc('\n');

				if ( key == 'y' || key == 'Y' )
				{
#if !defined(CONFIG_SYS_NO_FLASH) || defined (CONFIG_SPI_FLASH)
					bootmenu_printf("  Saving settings... ");
					saveenv();
					bootmenu_printf("Complete\n");
#endif
				}
				serial_putc('\n');

			quit = 1;
			}
		}
	}
}

#ifndef CONFIG_SYS_NO_FLASH
static int _erase_env(void)
{
	char buf[512];
	sprintf(buf, "prot off 0x%08X 0x%08X", atl_get_env_addr(), atl_get_env_addr() + CONFIG_ENV_SECT_SIZE - 1);
	if (run_command(buf, 0) < 0) {
		return -1;
	}

	sprintf(buf, "erase 0x%08X 0x%08X", atl_get_env_addr(), atl_get_env_addr() + CONFIG_ENV_SECT_SIZE - 1);
	if (run_command(buf, 0) < 0) {
		return -1;
	}

	return 0;
}
#elif defined(CONFIG_ENV_IS_IN_SPI_FLASH)
static int _erase_env(void)
{
	int ret;
	unsigned int max_hz;
	u32 saved_size, saved_offset;
	char *saved_buffer = NULL;
	static struct spi_flash *spi_flash = NULL;


	if (getenv("spi_clock"))
		max_hz = simple_strtoul(getenv("spi_clock"), NULL, 16);
	else
		max_hz = CONFIG_BM_SPI_MAX_HZ;

	spi_flash = spi_flash_probe(CONFIG_BM_SPI_BUS, CONFIG_BM_SPI_CS,
			max_hz, CONFIG_BM_SPI_MODE);

	if (!spi_flash) {
		printf("Error: SPI flash %d:%d not found\n",
			CONFIG_BM_SPI_BUS, CONFIG_BM_SPI_CS);
		ret = -ENODEV;
		goto done;
	}

	/* Save the extra data that might occupy part of the env sector */
	if (CONFIG_ENV_SECT_SIZE > CONFIG_ENV_SIZE) {
		saved_size = CONFIG_ENV_SECT_SIZE - CONFIG_ENV_SIZE;
		saved_offset = CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE;
		saved_buffer = malloc(saved_size);
		if (!saved_buffer) {
			ret = -ENOMEM;
			goto done;
		}
		ret = spi_flash_read(spi_flash, saved_offset, saved_size, saved_buffer);
		if (ret)
			goto done;
	}

	ret = spi_flash_erase(spi_flash, CONFIG_ENV_ADDR, CONFIG_ENV_SECT_SIZE);
	if (ret)
		goto done;

	/* Write back the extra data */
	if (CONFIG_ENV_SECT_SIZE > CONFIG_ENV_SIZE) {
		ret = spi_flash_write(spi_flash, saved_offset, saved_size, saved_buffer);
		if (ret)
			goto done;
	}

done:
	if (saved_buffer)
		free(saved_buffer);
	if (spi_flash)
		spi_flash_free(spi_flash);

	return 0;
}
#else
static int _erase_env(void)
{
	return -1;
}
#endif

void bootmenu_restore_default_settings(int doit)
{
	char		key;
	
	key = 0;

/*	Get confirmation from user
 */
	if (!doit)
	{
		serial_puts( "  WARNING: This option erases any settings that may have been configured by this menu\n\n"
			     "  Are you sure? (Y/N) ==> " );

		key = serial_getc();
	

	/*	Echo the user's response
 	*/
	
		if (isprint(key))
		{
			serial_putc( key );
		}
	}

/*	Erase the flash if the user confirmed wanting the erase
 */
	if ( key == 'y' || key == 'Y' || doit )
	{
		bootmenu_printf("\n\n");
		int baudrate;
		
		baudrate = simple_strtoul(getenv("baudrate"), NULL, 10);

		bootmenu_printf("  Restoring default settings... ");
		if(_erase_env()) {
			bootmenu_printf("Erase failed!\n\n");
			return;
		}
		bootmenu_printf("Complete\n");
		
		if (baudrate != CONFIG_BAUDRATE)
		{
			bootmenu_printf("  The device is rebooting with the default baud rate of %u baud.\n"
					"  Your terminal software is currently set to %u baud. Please change it now.\n", CONFIG_BAUDRATE, baudrate);
		}
		
		if (!doit) /* Don't automatically reset when this is automated */
		{
			bootmenu_printf("\n");
		
			do_reset(NULL, 0, 0, NULL);
		}

	}
	bootmenu_printf("\n");
}

int bootmenu_special()
{
	char	key;
	u8	quit;
	
	quit = 0;
	
	while (!quit)
	{

/*		Display the menu
 */
		serial_puts( "Special boot options menu:\n\n"
				"  0. Return to previous menu\n"
				"  ------------------------------------------------------\n"
				"  1. Skip startup script (Use system defaults)\n");
		serial_puts(
				"\nEnter selection ==> " );

/*		Wait for a key to be pressed
 */
		key = serial_getc();

/*		If it's a menu character then echo the character and spit out a couple 
 *		of blank lines
 */
		if (isdigit(key))
			serial_putc(key);

		serial_putc('\n');
		serial_putc('\n');

/*		Action the selection
 */
		switch ( key )
		{
			case '0':
				quit = 1;
				break;
			case '1':
				gd->flags |= GD_FLG_DEFAULT_CONFIG;
				bootmenu_printf("Option successfully set. Press 0 then 9 to boot with the default configuration\n"
				"Or boot the system from an alternate source.\n\n");
				break;
			default:	
				break;			/* Ignore any other character */
		}
	}
	
	return 0;
}

#ifdef CONFIG_PPC
static void bootmenu_cpuinfo(void)
{
	bd_t *bd = gd->bd;

#if defined(CONFIG_MPC85xx)
	uint svr = get_svr();
	
	switch (SVR_SOC_VER(svr))
	{
		case SVR_8535_E:
			bootmenu_printf("    Type ...............: MPC8535E\n");
			break;
		case SVR_8541_E:
			bootmenu_printf("    Type ...............: MPC8541E\n");
			break;
		case SVR_8555_E:
			bootmenu_printf("    Type ...............: MPC8555E\n");
			break;
		case SVR_8569_E:
			bootmenu_printf("    Type ...............: MPC8569E\n");
			break;
		default:
			bootmenu_printf("    Type ...............: Unknown\n");
			break;
	}
	
#elif defined(CONFIG_MPC824X)
	uint pvr = get_pvr();
	uint version = pvr >> 16;
	
	switch (version) {
		case CPU_TYPE_8240:
			bootmenu_printf("    Type ...............: MPC8240\n");
			break;

		case CPU_TYPE_8245:
			bootmenu_printf("    Type ...............: MPC8245\n");
			break;

		default:
			bootmenu_printf("    Type ...............: Unknown\n");
	}
#elif defined(CONFIG_MPC8260) || defined (CONFIG_MPC83xx)
	bootmenu_printf("    Type ...............: %s\n", CPU_ID_STR);
#endif
	bootmenu_printf("    Speed ..............: %u MHz\n", (bd->bi_intfreq  + 500000) / 1000000);
	bootmenu_printf("  Memory:\n");
	bootmenu_printf("    Total installed ....: %u MB\n", bd->bi_memsize / 1024 / 1024);	
	bootmenu_printf("    Speed ..............: %u MHz\n", (gd->mem_clk + 1000000) / 2000000);
}
#endif /* CONFIG_PPC */

#ifdef CONFIG_MIPS32
static void bootmenu_cpuinfo(void)
{
	bd_t *bd = gd->bd;

	bootmenu_printf("    Type ...............: MIPS32_4KC\n");
#if defined(CONFIG_ATRD_X300)
	bootmenu_printf("    Speed ..............: %u MHz\n", x300_get_cpuclk() / 1000 / 1000);
#elif defined(CONFIG_X200_GE)
	bootmenu_printf("    Speed ..............: %u MHz\n", (x200_ge_system_counter_freq() * 2) / 1000 / 1000);
#elif defined(CONFIG_X200_X400_FE)
	bootmenu_printf("    Speed ..............: %u MHz\n", (x200_x400_fe_system_counter_freq() * 2) / 1000 / 1000);
#endif
	bootmenu_printf("  Memory:\n");
	bootmenu_printf("    Total installed ....: %u MB\n", bd->bi_memsize / 1024 / 1024);
}
#endif /* CONFIG_MIPS32 */

#ifdef CONFIG_ARM
static void bootmenu_cpuinfo(void)
{
#if defined(CONFIG_KIRKWOOD)
	unsigned int devid = readl(KW_REG_DEVICE_ID);
	switch (devid & 0x03) {
	case 1:
		bootmenu_printf("    Type ...............: 88F6192_A0\n");
		break;
	case 2:
		bootmenu_printf("    Type ...............: 88F6281_A0\n");
		break;
	default:
		bootmenu_printf("    Type ...............: Unknown Kirkwood (%08x)\n", devid);
		break;
	}
#elif defined(CONFIG_DISCOVERY)
	unsigned int devrev;

	devrev = readl(DISCO_REG_DEVVEN_ID) & 0xffff0000;
	devrev |= readl(DISCO_REG_CLASSREV_ID) & 0x000000ff;

	switch (devrev) {
	case 0x78100001:
		bootmenu_printf("    Type ...............: MV78100_A0\n");
		break;
	case 0x78100002:
		bootmenu_printf("    Type ...............: MV78100_A1\n");
		break;
	default:
		bootmenu_printf("    Type ...............: Unknown Discovery (%08x)\n", devrev);
		break;
	}
#else
	bootmenu_printf("    Type ...............: Unknown ARM\n");
#endif
}
#endif /* CONFIG_ARM */


#ifdef	CONFIG_DIAG_EPI3
static void bootmenu_epi3info(void)
{
	int temp;
	int i;
	epi3Personality *personality=epi3_getcurrent();
	compatible_device	compatible_devices[] = CONFIG_COMPATIBLE_DEVICES;

	if (personality)
	{
		bootmenu_printf("  Serial ...............: %s\n", personality->epiSerial);
		bootmenu_printf("  Manufacture date .....: %02u/%04u\n", personality->epiMonth + 1, personality->epiYear + 2000);
		i = 0;
		do
		{
			if (personality->epiBoardId == compatible_devices[i].board_id)
			{
				bootmenu_printf("  Product ..............: %u\n", personality->epiBoardId);
				bootmenu_printf("  Product name .........: %s\n", compatible_devices[i].board_name);
				break;
			}		
			i++;
			
			if (!compatible_devices[i].board_id)
			{
				bootmenu_printf("  Product ..............: %u (Not compatible with this software)\n", personality->epiBoardId);
				break;
			}
		
		} while (compatible_devices[i].board_id);
		
		
		if (personality->epiPcbRev & 0x8000)
		{
			if ((personality->epiPcbRev & ~0x8000) == 0)
				temp = 0;
			else
				temp = (u16)((personality->epiPcbRev & ~0x8000) + 16);
		}
		else
			temp = (u16)(personality->epiPcbRev + 1);
		
		if (personality->epiPcbRev & 0x8000)
		{
			u16 temp;
			
			temp = personality->epiPcbRev - 0x8000;
			
			if (temp < num_proto_names)
			{
				bootmenu_printf("  Revision .............: %s\n", ppr3_proto_names[temp]);
			}
			else
			{
				bootmenu_printf("  Revision .............: (too high)\n");
			}
		}
		else
		{	
			u16 temp;
			
			temp = personality->epiPcbRev;
			
			if (temp < num_rev_names)
			{
				bootmenu_printf("  Revision .............: %s\n", ppr3_rev_names[temp]);
			}
			else
			{
				bootmenu_printf("  Revision .............: %u (too high)\n", temp);
			}	
		}
		
		bootmenu_printf("  First MAC address ....: %pM\n",
				personality->epiMacAddress);
	}
	else
	{
		bootmenu_printf("  Error: No personality present\n");
	}
}
#endif /* CONFIG_DIAG_EPI3 */

#ifdef CONFIG_DIAG_IDPROM
static void bootmenu_idprominfo(void)
{
	struct idprom *id = idprom_get();
	uchar mac[6];

	if (id) {
		idprom_parse_enetaddr(id, mac);
		bootmenu_printf("  Serial ...............: %s\n", id->sn);
		bootmenu_printf("  Manufacture date .....: %s\n", id->bd);
		bootmenu_printf("  Model Number .........: %s\n", id->mn);
		bootmenu_printf("  Software Model Number.: %s\n", id->smn);
		bootmenu_printf("  Part Number...........: %s\n", id->pn);
		bootmenu_printf("  Revision .............: %s\n", id->hv);
		bootmenu_printf("  MAC address ..........: %pM\n", mac);
		bootmenu_printf("  Number of MACs........: %s\n", id->nmac);
	} else {
		bootmenu_printf("  Error: No personality present\n");
	}
}
#endif

int bootmenu_sysinfo(void)
{
	bd_t * 			bd;
	int 			i;
	compatible_device	compatible_devices[] = CONFIG_COMPATIBLE_DEVICES;

	i = 0;
	bd = gd->bd;

	bootmenu_printf("System information:\n\n");
	bootmenu_printf("  System (Detected values)\n");
	bootmenu_printf("  ------------------------------------------------------\n");
	bootmenu_printf("  CPU:\n");
	bootmenu_cpuinfo();
	bootmenu_printf("  Flash:\n");
	bootmenu_printf("    Total installed ....: %u MB\n", bd->bi_flashsize / 1024 / 1024);
	bootmenu_printf("  ------------------------------------------------------\n\n");

	bootmenu_printf("  Product information\n");
	bootmenu_printf("  ------------------------------------------------------\n");
	bootmenu_printf("  Compatible software ..: %u (r%u)\n", CONFIG_THIS_DEVICE, CONFIG_THIS_DEVICE);

	if (getenv("slot_id")) {
		bootmenu_printf("  Current slot .........: %s\n", getenv("slot_id"));
	}

#ifdef CONFIG_DIAG_EPI3
	bootmenu_epi3info();
#endif
#ifdef CONFIG_DIAG_IDPROM
	bootmenu_idprominfo();
#endif
	bootmenu_printf("  ------------------------------------------------------\n\n");
	
	bootmenu_printf("  Boot software information\n");
	bootmenu_printf("  ------------------------------------------------------\n");
	bootmenu_printf("  Version ..............: %s\n", ATL_EXTRA_VERSION);
	bootmenu_printf("  Build time ...........: %s - %s\n", __DATE__, __TIME__);
	bootmenu_printf("  Built by .............: %s\n", ATL_USERNAME);
	bootmenu_printf("  Build host ...........: %s\n", ATL_HOSTNAME);
	bootmenu_printf("  Build key ............: %s\n", ATL_BUILDKEY);
	bootmenu_printf("  Compatible products:\n");
	i = 0;
	do
	{
		bootmenu_printf("    %u ................: %s\n", compatible_devices[i].board_id, compatible_devices[i].board_name);
		i++;
		
	} while (compatible_devices[i].board_id);
	
	bootmenu_printf("  ------------------------------------------------------\n\n");
	
	bootmenu_printf("Press any key to continue... ");
	serial_getc();
	bootmenu_printf("\n\n");
	
	return 0;
}
