// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
/*
 * Copyright (C) 2020, STMicroelectronics - All Rights Reserved
 */

#include <bootm.h>
#include <command.h>
#include <dfu.h>
#include <env.h>
#include <image.h>
#include <asm/arch/stm32prog.h>
#include <linux/printk.h>
#include "stm32prog.h"

struct stm32prog_data *stm32prog_data;

static void enable_vidconsole(void)
{
	char *stdname;
	char buf[64];

	stdname = env_get("stdout");
	if (!stdname || !strstr(stdname, "vidconsole")) {
		if (!stdname)
			snprintf(buf, sizeof(buf), "serial,vidconsole");
		else
			snprintf(buf, sizeof(buf), "%s,vidconsole", stdname);
		env_set("stdout", buf);
	}

	stdname = env_get("stderr");
	if (!stdname || !strstr(stdname, "vidconsole")) {
		if (!stdname)
			snprintf(buf, sizeof(buf), "serial,vidconsole");
		else
			snprintf(buf, sizeof(buf), "%s,vidconsole", stdname);
		env_set("stderr", buf);
	}
}

static int do_stm32prog(struct cmd_tbl *cmdtp, int flag, int argc,
			char * const argv[])
{
	ulong	addr, size;
	int dev, ret;
	enum stm32prog_link_t link = LINK_UNDEFINED;
	bool reset = false;
	struct image_header_s header;
	struct stm32prog_data *data;

	if (argc < 3 ||  argc > 5)
		return CMD_RET_USAGE;

	if (IS_ENABLED(CONFIG_CMD_STM32PROG_USB) && !strcmp(argv[1], "usb"))
		link = LINK_USB;
	else if (IS_ENABLED(CONFIG_CMD_STM32PROG_SERIAL) && !strcmp(argv[1], "serial"))
		link = LINK_SERIAL;

	if (link == LINK_UNDEFINED) {
		log_err("not supported link=%s\n", argv[1]);
		return CMD_RET_USAGE;
	}

	dev = (int)dectoul(argv[2], NULL);

	addr = CONFIG_SYS_LOAD_ADDR;
	size = 0;
	if (argc > 3) {
		addr = hextoul(argv[3], NULL);
		if (!addr)
			return CMD_RET_FAILURE;
	}
	if (argc > 4)
		size = hextoul(argv[4], NULL);

	/* check STM32IMAGE presence */
	if (size == 0) {
		stm32prog_header_check(addr, &header);
		if (header.type == HEADER_STM32IMAGE) {
			size = header.image_length + header.length;
		}
	}

	if (IS_ENABLED(CONFIG_VIDEO))
		enable_vidconsole();

	data = (struct stm32prog_data *)malloc(sizeof(*data));

	if (!data) {
		log_err("Alloc failed.");
		return CMD_RET_FAILURE;
	}
	stm32prog_data = data;

	ret = stm32prog_init(data, addr, size);
	if (ret)
		log_debug("Invalid or missing layout file at 0x%lx.\n", addr);

	/* prepare DFU for device read/write */
	ret = stm32prog_dfu_init(data);
	if (ret)
		goto cleanup;

	switch (link) {
	case LINK_SERIAL:
		ret = stm32prog_serial_init(data, dev);
		if (ret)
			goto cleanup;
		reset = stm32prog_serial_loop(data);
		break;
	case LINK_USB:
		reset = stm32prog_usb_loop(data, dev);
		break;
	default:
		goto cleanup;
	}

	stm32prog_clean(data);
	free(stm32prog_data);
	stm32prog_data = NULL;

	puts("Download done\n");

	if (data->uimage) {
		char boot_addr_start[20];
		char dtb_addr[20];
		char initrd_addr[40];
		char *fdt_arg, *initrd_arg;
		const void *uimage = (void *)data->uimage;
		const void *dtb = (void *)data->dtb;
		const void *initrd = (void *)data->initrd;
		struct bootm_info bmi;

		fdt_arg = dtb_addr;
		if (!dtb)
			fdt_arg = env_get("fdtcontroladdr");
		else
			snprintf(dtb_addr, sizeof(dtb_addr) - 1, "0x%p", dtb);

		snprintf(boot_addr_start, sizeof(boot_addr_start) - 1,
			 "0x%p", uimage);

		initrd_arg = NULL;
		if (initrd) {
			snprintf(initrd_addr, sizeof(initrd_addr) - 1,
				 "0x%p:0x%zx", initrd, data->initrd_size);
			initrd_arg = initrd_addr;
		}

		printf("Booting kernel at %s %s %s...\n\n\n", boot_addr_start,
		       initrd_arg ?: "-", fdt_arg);

		bootm_init(&bmi);
		bmi.addr_img = boot_addr_start;
		bmi.conf_ramdisk = initrd_arg;
		bmi.conf_fdt = fdt_arg;

		/* Try bootm for legacy and FIT format image */
		if (genimg_get_format(uimage) != IMAGE_FORMAT_INVALID)
			bootm_run(&bmi);
		else if (IS_ENABLED(CONFIG_CMD_BOOTZ))
			bootz_run(&bmi);
	}
	if (data->script)
		cmd_source_script(data->script, NULL, NULL);

	if (reset) {
		puts("Reset...\n");
		run_command("reset", 0);
	}

	return CMD_RET_SUCCESS;

cleanup:
	stm32prog_clean(data);
	free(stm32prog_data);
	stm32prog_data = NULL;

	return CMD_RET_FAILURE;
}

U_BOOT_CMD(stm32prog, 5, 0, do_stm32prog,
	   "start communication with tools STM32Cubeprogrammer",
	   "<link> <dev> [<addr>] [<size>]\n"
	   "  <link> = serial|usb\n"
	   "  <dev>  = device instance\n"
	   "  <addr> = address of flashlayout\n"
	   "  <size> = size of flashlayout (optional for image with STM32 header)\n"
);

bool stm32prog_get_fsbl_nor(void)
{
	if (stm32prog_data)
		return stm32prog_data->fsbl_nor_detected;

	return false;
}
