Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * (C) Copyright 2016
 * Texas Instruments, <www.ti.com>
 *
 * Ravi B <ravibabu@ti.com>
 */
#include <env.h>
#include <spl.h>
#include <linux/compiler.h>
#include <errno.h>
#include <watchdog.h>
#include <console.h>
#include <g_dnl.h>
#include <usb.h>
#include <dfu.h>
#include <linux/printk.h>
#include <pci_ep.h>
#include <dm/uclass.h>
#include <cpu_func.h>
#include <linux/io.h>

/*
 * Macros define size of magic word and boot phase string
 * in bytes.
 */
#define MAGIC_WORD_SIZE 4
#define BOOT_PHASE_STRING_SIZE 63

static int run_dfu(int usb_index, char *interface, char *devstring)
{
	int ret;

	ret = dfu_init_env_entities(interface, devstring);
	if (ret) {
		dfu_free_entities();
		goto exit;
	}

	run_usb_dnl_gadget(usb_index, "usb_dnl_dfu");
exit:
	dfu_free_entities();
	return ret;
}

#ifdef CONFIG_SPL_PCI_DFU
static int dfu_over_pcie(void)
{
	u32 offset, magic_word;
	volatile void *addr;
	struct udevice *dev;
	struct pci_bar bar;
	struct pci_ep_header hdr;
	uint fn = 0;
	int ret;
	char *bootphase;

	uclass_get_device_by_seq(UCLASS_PCI_EP, 0, &dev);
	if (!dev) {
		pr_err("Failed to get pci ep device\n");
		return -ENODEV;
	}

	hdr.deviceid = CONFIG_SPL_PCI_DFU_DEVICE_ID;
	hdr.vendorid = CONFIG_SPL_PCI_DFU_VENDOR_ID;
	hdr.baseclass_code = PCI_BASE_CLASS_MEMORY;
	hdr.subclass_code = PCI_CLASS_MEMORY_RAM;

	ret = pci_ep_write_header(dev, fn, &hdr);
	if (ret) {
		pr_err("Failed to write header: %d\n", ret);
		return ret;
	}

	bar.barno = BAR_0;
	bar.phys_addr = (dma_addr_t)CONFIG_SPL_PCI_DFU_SPL_LOAD_FIT_ADDRESS;
	bar.flags = PCI_BASE_ADDRESS_SPACE_MEMORY |
			  PCI_BASE_ADDRESS_MEM_TYPE_32 |
			  PCI_BASE_ADDRESS_MEM_PREFETCH;

	bar.size = CONFIG_SPL_PCI_DFU_BAR_SIZE;

	ret = pci_ep_set_bar(dev, fn, &bar);
	if (ret) {
		pr_err("Failed to set bar: %d\n", ret);
		return ret;
	}

	ret = pci_ep_start(dev);
	if (ret) {
		pr_err("Failed to start ep: %d\n", ret);
		return ret;
	}

	addr = (void *)CONFIG_SPL_PCI_DFU_SPL_LOAD_FIT_ADDRESS;
	offset = CONFIG_SPL_PCI_DFU_BAR_SIZE - MAGIC_WORD_SIZE;

	if (sizeof(CONFIG_SPL_PCI_DFU_BOOT_PHASE) > BOOT_PHASE_STRING_SIZE) {
		pr_err("Not copying boot phase. String too long\n");
	} else {
		bootphase = (char *)(addr + CONFIG_SPL_PCI_DFU_BAR_SIZE -
				  (BOOT_PHASE_STRING_SIZE + MAGIC_WORD_SIZE + 1));
		strlcpy(bootphase, CONFIG_SPL_PCI_DFU_BOOT_PHASE,
			sizeof(CONFIG_SPL_PCI_DFU_BOOT_PHASE) + 1);
	}

	addr = addr + offset;
	magic_word = CONFIG_SPL_PCI_DFU_MAGIC_WORD;
	(*(int *)addr) = 0;
	flush_dcache_all();
	for (;;) {
		if (*(int *)addr == magic_word)
			break;
		invalidate_dcache_all();
	}

	return 0;
}
#endif

int spl_dfu_cmd(int usbctrl, char *dfu_alt_info, char *interface, char *devstr)
{
	char *str_env;
	int ret;

#ifdef CONFIG_SPL_PCI_DFU
	if (spl_boot_device() == BOOT_DEVICE_PCIE)
		return dfu_over_pcie();
#endif

	/* set default environment */
	env_set_default(NULL, 0);
	str_env = env_get(dfu_alt_info);
	if (!str_env) {
		pr_err("\"%s\" env variable not defined!\n", dfu_alt_info);
		return -EINVAL;
	}

	ret = env_set("dfu_alt_info", str_env);
	if (ret) {
		pr_err("unable to set env variable \"dfu_alt_info\"!\n");
		return -EINVAL;
	}

	/* invoke dfu command */
	return run_dfu(usbctrl, interface, devstr);
}