/*
 * (C) Copyright 2007
 * Stefan Roese, DENX Software Engineering, sr@denx.de.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <command.h>
#include <malloc.h>
#include <environment.h>
#include <logbuff.h>
#include <post.h>

#include <asm/processor.h>
#include <asm/io.h>
#include <asm/gpio.h>

DECLARE_GLOBAL_DATA_PTR;

#define REBOOT_MAGIC	0x07081967
#define REBOOT_NOP	0x00000000
#define REBOOT_DO_POST	0x00000001

extern flash_info_t flash_info[CFG_MAX_FLASH_BANKS]; /* info for FLASH chips	*/
extern env_t *env_ptr;
extern uchar default_environment[];

ulong flash_get_size(ulong base, int banknum);
void env_crc_update(void);
int do_reset (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]);

static u32 start_time;

int board_early_init_f(void)
{
	mtdcr(uicsr, 0xFFFFFFFF);	/* clear all ints */
	mtdcr(uicer, 0x00000000);	/* disable all ints */
	mtdcr(uiccr, 0x00000000);
	mtdcr(uicpr, 0xFFFF7F00);	/* set int polarities */
	mtdcr(uictr, 0x00000000);	/* set int trigger levels */
	mtdcr(uicsr, 0xFFFFFFFF);	/* clear all ints */
	mtdcr(uicvcr, 0x00000001);	/* set vect base=0,INT0 highest priority */

	/*
	 * Configure CPC0_PCI to enable PerWE as output
	 */
	mtdcr(cpc0_pci, CPC0_PCI_SPE);

	return 0;
}

int misc_init_r(void)
{
	u32 pbcr;
	int size_val = 0;
	u32 post_magic;
	u32 post_val;

	post_magic = in_be32((void *)CFG_POST_MAGIC);
	post_val = in_be32((void *)CFG_POST_VAL);
	if ((post_magic == REBOOT_MAGIC) && (post_val == REBOOT_DO_POST)) {
		/*
		 * Set special bootline bootparameter to pass this POST boot
		 * mode to Linux to reset the username/password
		 */
		setenv("addmisc", "setenv bootargs \\${bootargs} factory_reset=yes");

		/*
		 * Normally don't run POST tests, only when enabled
		 * via the sw-reset button. So disable further tests
		 * upon next bootup here.
		 */
		out_be32((void *)CFG_POST_VAL, REBOOT_NOP);
	} else {
		/*
		 * Only run POST when initiated via the sw-reset button mechanism
		 */
		post_word_store(0);
	}

	/*
	 * Get current time
	 */
	start_time = get_timer(0);

	/*
	 * FLASH stuff...
	 */

	/* Re-do sizing to get full correct info */

	/* adjust flash start and offset */
	mfebc(pb0cr, pbcr);
	switch (gd->bd->bi_flashsize) {
	case 1 << 20:
		size_val = 0;
		break;
	case 2 << 20:
		size_val = 1;
		break;
	case 4 << 20:
		size_val = 2;
		break;
	case 8 << 20:
		size_val = 3;
		break;
	case 16 << 20:
		size_val = 4;
		break;
	case 32 << 20:
		size_val = 5;
		break;
	case 64 << 20:
		size_val = 6;
		break;
	case 128 << 20:
		size_val = 7;
		break;
	}
	pbcr = (pbcr & 0x0001ffff) | gd->bd->bi_flashstart | (size_val << 17);
	mtebc(pb0cr, pbcr);

	/*
	 * Re-check to get correct base address
	 */
	flash_get_size(gd->bd->bi_flashstart, 0);

	/* Monitor protection ON by default */
	(void)flash_protect(FLAG_PROTECT_SET,
			    -CFG_MONITOR_LEN,
			    0xffffffff,
			    &flash_info[0]);

	/* Env protection ON by default */
	(void)flash_protect(FLAG_PROTECT_SET,
			    CFG_ENV_ADDR_REDUND,
			    CFG_ENV_ADDR_REDUND + 2*CFG_ENV_SECT_SIZE - 1,
			    &flash_info[0]);

	return 0;
}

/*
 * Check Board Identity:
 */
int checkboard(void)
{
	char *s = getenv("serial#");

	puts("Board: Zeus-");

	if (in_be32((void *)GPIO0_IR) & GPIO_VAL(CFG_GPIO_ZEUS_PE))
		puts("PE");
	else
		puts("CE");

	puts(" of BulletEndPoint");

	if (s != NULL) {
		puts(", serial# ");
		puts(s);
	}
	putc('\n');

	/* both LED's off */
	gpio_write_bit(CFG_GPIO_LED_RED, 0);
	gpio_write_bit(CFG_GPIO_LED_GREEN, 0);
	udelay(10000);
	/* and on again */
	gpio_write_bit(CFG_GPIO_LED_RED, 1);
	gpio_write_bit(CFG_GPIO_LED_GREEN, 1);

	return (0);
}

static u32 detect_sdram_size(void)
{
	u32 val;
	u32 size;

	mfsdram(mem_mb0cf, val);
	size = (4 << 20) << ((val & 0x000e0000) >> 17);

	/*
	 * Check if 2nd bank is enabled too
	 */
	mfsdram(mem_mb1cf, val);
	if (val & 1)
		size += (4 << 20) << ((val & 0x000e0000) >> 17);

	return size;
}

long int initdram (int board_type)
{
	return detect_sdram_size();
}

#if defined(CFG_DRAM_TEST)
int testdram(void)
{
	unsigned long *mem = (unsigned long *)0;
	const unsigned long kend = (1024 / sizeof(unsigned long));
	unsigned long k, n;
	unsigned long msr;
	unsigned long total_kbytes;

	total_kbytes = detect_sdram_size();

	msr = mfmsr();
	mtmsr(msr & ~(MSR_EE));

	for (k = 0; k < total_kbytes ;
	     ++k, mem += (1024 / sizeof(unsigned long))) {
		if ((k & 1023) == 0) {
			printf("%3d MB\r", k / 1024);
		}

		memset(mem, 0xaaaaaaaa, 1024);
		for (n = 0; n < kend; ++n) {
			if (mem[n] != 0xaaaaaaaa) {
				printf("SDRAM test fails at: %08x\n",
				       (uint) & mem[n]);
				return 1;
			}
		}

		memset(mem, 0x55555555, 1024);
		for (n = 0; n < kend; ++n) {
			if (mem[n] != 0x55555555) {
				printf("SDRAM test fails at: %08x\n",
				       (uint) & mem[n]);
				return 1;
			}
		}
	}
	printf("SDRAM test passes\n");
	mtmsr(msr);

	return 0;
}
#endif

static int default_env_var(char *buf, char *var)
{
	char *ptr;
	char *val;

	/*
	 * Find env variable
	 */
	ptr = strstr(buf + 4, var);
	if (ptr == NULL) {
		printf("ERROR: %s not found!\n", var);
		return -1;
	}
	ptr += strlen(var) + 1;

	/*
	 * Now the ethaddr needs to be updated in the "normal"
	 * environment storage -> redundant flash.
	 */
	val = ptr;
	setenv(var, val);
	printf("Updated %s from eeprom to %s!\n", var, val);

	return 0;
}

static int restore_default(void)
{
	char *buf;
	char *buf_save;
	u32 crc;

	/*
	 * Unprotect and erase environment area
	 */
	flash_protect(FLAG_PROTECT_CLEAR,
		      CFG_ENV_ADDR_REDUND,
		      CFG_ENV_ADDR_REDUND + 2*CFG_ENV_SECT_SIZE - 1,
		      &flash_info[0]);

	flash_sect_erase(CFG_ENV_ADDR_REDUND,
			 CFG_ENV_ADDR_REDUND + 2*CFG_ENV_SECT_SIZE - 1);

	/*
	 * Now restore default environment from U-Boot image
	 * -> ipaddr, serverip...
	 */
	memset(env_ptr, 0, sizeof(env_t));
	memcpy(env_ptr->data, default_environment, ENV_SIZE);
#ifdef CFG_REDUNDAND_ENVIRONMENT
	env_ptr->flags = 0xFF;
#endif
	env_crc_update();
	gd->env_valid = 1;

	/*
	 * Read board specific values from I2C EEPROM
	 * and set env variables accordingly
	 * -> ethaddr, eth1addr, serial#
	 */
	buf = buf_save = malloc(FACTORY_RESET_ENV_SIZE);
	if (eeprom_read(FACTORY_RESET_I2C_EEPROM, FACTORY_RESET_ENV_OFFS,
			(u8 *)buf, FACTORY_RESET_ENV_SIZE)) {
		puts("\nError reading EEPROM!\n");
	} else {
		crc = crc32(0, (u8 *)(buf + 4), FACTORY_RESET_ENV_SIZE - 4);
		if (crc != *(u32 *)buf) {
			printf("ERROR: crc mismatch %08lx %08lx\n", crc, *(u32 *)buf);
			return -1;
		}

		default_env_var(buf, "ethaddr");
		buf += 8 + 18;
		default_env_var(buf, "eth1addr");
		buf += 9 + 18;
		default_env_var(buf, "serial#");
	}

	/*
	 * Finally save updated env variables back to flash
	 */
	saveenv();

	free(buf_save);

	return 0;
}

int do_set_default(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	char *buf;
	char *buf_save;
	char str[32];
	u32 crc;
	char var[32];

	if (argc < 4) {
		puts("ERROR!\n");
		return -1;
	}

	buf = buf_save = malloc(FACTORY_RESET_ENV_SIZE);
	memset(buf, 0, FACTORY_RESET_ENV_SIZE);

	strcpy(var, "ethaddr");
	printf("Setting %s to %s\n", var, argv[1]);
	sprintf(str, "%s=%s", var, argv[1]);
	strcpy(buf + 4, str);
	buf += strlen(str) + 1;

	strcpy(var, "eth1addr");
	printf("Setting %s to %s\n", var, argv[2]);
	sprintf(str, "%s=%s", var, argv[2]);
	strcpy(buf + 4, str);
	buf += strlen(str) + 1;

	strcpy(var, "serial#");
	printf("Setting %s to %s\n", var, argv[3]);
	sprintf(str, "%s=%s", var, argv[3]);
	strcpy(buf + 4, str);

	crc = crc32(0, (u8 *)(buf_save + 4), FACTORY_RESET_ENV_SIZE - 4);
	*(u32 *)buf_save = crc;

	if (eeprom_write(FACTORY_RESET_I2C_EEPROM, FACTORY_RESET_ENV_OFFS,
			 (u8 *)buf_save, FACTORY_RESET_ENV_SIZE)) {
		puts("\nError writing EEPROM!\n");
		return -1;
	}

	free(buf_save);

	return 0;
}

U_BOOT_CMD(
	setdef,	4,	1,	do_set_default,
	"setdef  - write board-specific values to EEPROM (ethaddr...)\n",
	"ethaddr eth1addr serial#\n    - write board-specific values to EEPROM\n"
	);

static inline int sw_reset_pressed(void)
{
	return !(in_be32((void *)GPIO0_IR) & GPIO_VAL(CFG_GPIO_SW_RESET));
}

int do_chkreset(cmd_tbl_t* cmdtp, int flag, int argc, char* argv[])
{
	int delta;
	int count = 0;
	int post = 0;
	int factory_reset = 0;

	if (!sw_reset_pressed()) {
		printf("SW-Reset already high (Button released)\n");
		printf("-> No action taken!\n");
		return 0;
	}

	printf("Waiting for SW-Reset button to be released.");

	while (1) {
		delta = get_timer(start_time);
		if (!sw_reset_pressed())
			break;

		if ((delta > CFG_TIME_POST) && !post) {
			printf("\nWhen released now, POST tests will be started.");
			gpio_write_bit(CFG_GPIO_LED_GREEN, 0);
			post = 1;
		}

		if ((delta > CFG_TIME_FACTORY_RESET) && !factory_reset) {
			printf("\nWhen released now, factory default values"
			       " will be restored.");
			gpio_write_bit(CFG_GPIO_LED_RED, 0);
			factory_reset = 1;
		}

		udelay(1000);
		if (!(count++ % 1000))
			printf(".");
	}


	printf("\nSW-Reset Button released after %d milli-seconds!\n", delta);

	if (delta > CFG_TIME_FACTORY_RESET) {
		printf("Starting factory reset value restoration...\n");

		/*
		 * Restore default setting
		 */
		restore_default();

		/*
		 * Reset the board for default to become valid
		 */
		do_reset(NULL, 0, 0, NULL);

		return 0;
	}

	if (delta > CFG_TIME_POST) {
		printf("Starting POST configuration...\n");

		/*
		 * Enable POST upon next bootup
		 */
		out_be32((void *)CFG_POST_MAGIC, REBOOT_MAGIC);
		out_be32((void *)CFG_POST_VAL, REBOOT_DO_POST);
		post_bootmode_init();

		/*
		 * Reset the logbuffer for a clean start
		 */
		logbuff_reset();

		do_reset(NULL, 0, 0, NULL);

		return 0;
	}

	return 0;
}

U_BOOT_CMD (
	chkreset, 1, 1, do_chkreset,
	"chkreset- Check for status of SW-reset button and act accordingly\n",
	NULL
);

#if defined(CONFIG_POST)
/*
 * Returns 1 if keys pressed to start the power-on long-running tests
 * Called from board_init_f().
 */
int post_hotkeys_pressed(void)
{
	u32 post_magic;
	u32 post_val;

	post_magic = in_be32((void *)CFG_POST_MAGIC);
	post_val = in_be32((void *)CFG_POST_VAL);

	if ((post_magic == REBOOT_MAGIC) && (post_val == REBOOT_DO_POST))
		return 1;
	else
		return 0;
}
#endif /* CONFIG_POST */