/*
 * USB boot functions for Carambola2
 *
 * Copyright (C) 2014 Mantas Pucka <mantas@8devices.com>
 *
 * SPDX-License-Identifier:GPL-2.0
 */

#include <common.h>
#include <command.h>
#include <part.h>
#include <usb.h>
#include <fs.h>
#include <fat.h>

#define XMK_STR(x) #x
#define MK_STR(x) XMK_STR(x)


void usbboot_indicate_recovery(int type)
{
	// type=0 - before recovery
	// type=1 - after recovery
	int cnt = 4;  // how many times to blink

	if (type) cnt=2;
	while(cnt--){
		ar7240_gpio_led_switch(3, 1);
		mdelay(500);
		ar7240_gpio_led_switch(3, 0);
		mdelay(500);
	}
	if (!type) mdelay(500); //wait 0.5s after last blink before recovery
}

int usbboot_boot(char * boot_dev_part, char * boot_file_name)
{
	int fat_read_ret;
	char* bootm_argv[] = {"bootm", MK_STR(CFG_USB_BOOT_LOAD_ADDR)};

	fs_set_blk_dev ("usb", boot_dev_part, FS_TYPE_FAT);
	fat_read_ret = do_fat_read_at(boot_file_name,
								0,
								CFG_USB_BOOT_LOAD_ADDR,
								CFG_MAX_USB_BOOT_FILE_SIZE,
								0);
	if (fat_read_ret > 0){
		printf ("Booting image %s (%d bytes) from usb device %s \n",
			boot_file_name , fat_read_ret, boot_dev_part);
		do_bootm(NULL, 0 , 2, bootm_argv);
	}
	return -1; //Boot failed
}

int usbboot_recovery(char * boot_dev_part, char * boot_file_name)
{
	int fat_read_ret;
	char flash_cmd [128];
	char image_size[16];
	
	fs_set_blk_dev ("usb", boot_dev_part, FS_TYPE_FAT);
	fat_read_ret = do_fat_read_at(boot_file_name,
					0,
					CFG_USB_BOOT_LOAD_ADDR,
					CFG_MAX_USB_RECOVERY_FILE_SIZE,
					0);

	if (fat_read_ret > 0){
		usbboot_indicate_recovery(0);
		//TODO: check image??
		printf ("Flashing image %s (%d bytes) from usb device %s \n",
			boot_file_name , fat_read_ret, boot_dev_part);
		sprintf(image_size,"%x",fat_read_ret);

		// Example command
		//erase 0x9f050000 +3FA000; cp.b 0x80060000 0x9f050000 3FA000
		char* load_addr = MK_STR(CFG_USB_BOOT_LOAD_ADDR);
		sprintf(flash_cmd,"erase %s +%s; cp.b %s %s %s",
			  CFG_USB_RECOVERY_FW_START_IN_FLASH,
			  image_size,
			  load_addr,
			  CFG_USB_RECOVERY_FW_START_IN_FLASH,
			  image_size);
		printf ("\nFlashing image\n%s\n", flash_cmd);
		setenv("recovery_flash_cmd", flash_cmd);
		int  argc_flash=2;
		char* argv_flash[]={"run", "recovery_flash_cmd"};
		cmd_tbl_t *cmdtp=NULL;
		
		int flash_ret = do_run ( cmdtp,  0,  argc_flash, argv_flash);		
		printf ("\nFlashing sucsessful. Remove USB storage with recovery image.");
		usbboot_indicate_recovery(1);
		do_reset(NULL, 0, 0, NULL);
	}
	return -1; //Boot failed
}

int usbboot_scan(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int i;
	char part_id[8];
	block_dev_desc_t *stor_dev = NULL;
	disk_partition_t *info = NULL;
	
	char* boot_file_name; //Can include directories eg. "boot/vmlinux.uimage"
	char* recovery_file_name;
	char* boot_dev_part; 
	/* boot_dev_part format "<usb device>:<partition>", 
	 * eg. "0:1" - frst usb storage device's first partition
	 * */
	  
	boot_file_name = getenv ("bootfile");
	if (boot_file_name == NULL)
		boot_file_name = CFG_USB_BOOT_FILENAME;
	
	recovery_file_name = getenv ("recoveryfile");
	if (recovery_file_name == NULL)
		recovery_file_name = CFG_USB_RECOVERY_FILENAME;
	
	boot_dev_part = getenv ("bootdev");
	
	usb_stop();

	if (usb_init() < 0)
		return -1;
	/* try to recognize storage devices immediately */
	usb_stor_scan(0);

	int devno = 0;

	//if given device name just boot it
	if (boot_dev_part != NULL){
		usbboot_recovery(boot_dev_part, recovery_file_name);
		usbboot_boot(boot_dev_part, boot_file_name);
		return -2; //if returns, means boot failed
	}

	// scan all devices and partitions for boot image
	for (devno = 0; ; ++devno) {
		stor_dev = usb_stor_get_dev(devno);
		if (stor_dev == NULL)
			break;

		//try boot from full device (no partitions)
		sprintf(part_id,"%d:%d", devno, 0);
		usbboot_recovery(part_id, recovery_file_name);
		usbboot_boot(part_id, boot_file_name);

		//scan partitions
		if (stor_dev->part_type != PART_TYPE_DOS)
			continue;
		for (i=1; i<=CFG_USB_BOOT_MAX_PARTITIONS_SCAN; i++){
			printf("start %d size %d name %s\n", info->start, info->size, info->name);
			printf("SCANNING device %d\n",i);
			if (get_partition_info_dos(stor_dev, i, info) == 0){
				sprintf(part_id,"%d:%d", devno, i);
				usbboot_recovery(part_id, recovery_file_name);
				usbboot_boot(part_id, boot_file_name);
			}
		}
	}
	printf ("Boot or recovery image (%s or %s) not found in usb storage\n", boot_file_name, recovery_file_name);
	return -1;
}

void do_usb_boot (void){
	char* boot_mode;
	int s_force=0, s_gpio=0, s_never = 0;

	boot_mode = getenv ("usbboot");
	if (boot_mode != NULL){
		s_force = !(strcmp(boot_mode, "force"));
		s_gpio = !(strcmp(boot_mode, "gpio"));
		s_never = !(strcmp(boot_mode, "never"));
	}
	
	if ( (s_force && s_never == 0) || (boot_mode == NULL))
		s_gpio = 1;
	
	if (s_never){
		printf("USB boot is disabled in environment\n");
		return;
	}
	if (s_force){
		printf("USB boot is forced in environment\n");
		usbboot_scan(0,0,0,0);
	}
	if ( s_gpio ){
	debug("USB GPIO: %d\n", button_read(CFG_USB_BOOT_BUTTON_ID));
	if (button_read(CFG_USB_BOOT_BUTTON_ID)==1)
		usbboot_scan(0,0,0,0);
	}
}
	
U_BOOT_CMD(
	usb_boot_file,	5,	1,	usbboot_scan,
	"usb_boot_file - Automatic boot/recovery from file in USB drive\n",
	" "
);
