/*
 * Copyright (C) 2018 Marvell International Ltd.
 *
 * SPDX-License-Identifier:     BSD-3-Clause
 * https://spdx.org/licenses
 */

#include <assert.h>

#include <platform_def.h>

#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/delay_timer.h>
#include <mg_conf_cm3/mg_conf_cm3.h>
#include <lib/mmio.h>

#include <plat_pm_trace.h>
#include <mss_scp_bootloader.h>
#include <mss_ipc_drv.h>
#include <mss_mem.h>
#include <mss_defs.h>
#include <mss_scp_bl2_format.h>

#define MSS_DMA_TIMEOUT			1000
#define MSS_EXTERNAL_SPACE		0x50000000
#define MSS_EXTERNAL_ADDR_MASK		0xfffffff
#define MSS_INTERNAL_SPACE		0x40000000
#define MSS_INTERNAL_ADDR_MASK		0x00ffffff

#define DMA_SIZE			128

#define MSS_HANDSHAKE_TIMEOUT		50

static int mss_check_image_ready(volatile struct mss_pm_ctrl_block *mss_pm_crtl)
{
	int timeout = MSS_HANDSHAKE_TIMEOUT;

	/* Wait for SCP to signal it's ready */
	while ((mss_pm_crtl->handshake != MSS_ACKNOWLEDGMENT) &&
						(timeout-- > 0))
		mdelay(1);

	if (mss_pm_crtl->handshake != MSS_ACKNOWLEDGMENT)
		return -1;

	mss_pm_crtl->handshake = HOST_ACKNOWLEDGMENT;

	return 0;
}

static int mss_iram_dma_load(uint32_t src_addr, uint32_t size,
			     uintptr_t mss_regs)
{
	uint32_t i, loop_num, timeout;

	/* load image to MSS RAM using DMA */
	loop_num = (size / DMA_SIZE) + !!(size % DMA_SIZE);
	for (i = 0; i < loop_num; i++) {
		/* write source address */
		mmio_write_32(MSS_DMA_SRCBR(mss_regs),
			      src_addr + (i * DMA_SIZE));
		/* write destination address */
		mmio_write_32(MSS_DMA_DSTBR(mss_regs), (i * DMA_SIZE));
		/* make sure DMA data is ready before triggering it */
		dsb();
		/* set the DMA control register */
		mmio_write_32(MSS_DMA_CTRLR(mss_regs),
			      ((MSS_DMA_CTRLR_REQ_SET <<
				MSS_DMA_CTRLR_REQ_OFFSET) |
			      (DMA_SIZE << MSS_DMA_CTRLR_SIZE_OFFSET)));
		/* Poll DMA_ACK at MSS_DMACTLR until it is ready */
		timeout = MSS_DMA_TIMEOUT;
		while (timeout > 0U) {
			if (((mmio_read_32(MSS_DMA_CTRLR(mss_regs)) >>
					  MSS_DMA_CTRLR_ACK_OFFSET) &
					  MSS_DMA_CTRLR_ACK_MASK)
					  == MSS_DMA_CTRLR_ACK_READY) {
				break;
			}
			udelay(50);
			timeout--;
		}
		if (timeout == 0) {
			ERROR("\nMSS DMA failed (timeout)\n");
			return 1;
		}
	}
	return 0;
}

static int mss_image_load(uint32_t src_addr, uint32_t size,
			  uintptr_t mss_regs, uintptr_t sram)
{
	uint32_t chunks = 1; /* !sram case */
	uint32_t chunk_num;
	int ret;

	/* Check if the img size is not bigger than ID-RAM size of MSS CM3 */
	if (size > MSS_IDRAM_SIZE) {
		ERROR("image is too big to fit into MSS CM3 memory\n");
		return 1;
	}

	/* The CPx MSS DMA cannot access DRAM directly in secure boot mode
	 * Copy the MSS FW image to MSS SRAM by the CPU first, then run
	 * MSS DMA for SRAM to IRAM copy
	 */
	if (sram != 0) {
		chunks = size / MSS_SRAM_SIZE + !!(size % MSS_SRAM_SIZE);
	}

	NOTICE("%s Loading MSS FW from addr. 0x%x Size 0x%x to MSS at 0x%lx\n",
	       sram == 0 ? "" : "SECURELY", src_addr, size, mss_regs);
	for (chunk_num = 0; chunk_num < chunks; chunk_num++) {
		size_t chunk_size = size;
		uint32_t img_src = MSS_EXTERNAL_SPACE | /* no SRAM */
				   (src_addr & MSS_EXTERNAL_ADDR_MASK);

		if (sram != 0) {
			uintptr_t chunk_source =
				  src_addr + MSS_SRAM_SIZE * chunk_num;

			if (chunk_num != (size / MSS_SRAM_SIZE)) {
				chunk_size = MSS_SRAM_SIZE;
			} else {
				chunk_size =  size % MSS_SRAM_SIZE;
			}

			if (chunk_size == 0) {
				break;
			}

			VERBOSE("Chunk %d -> SRAM 0x%lx from 0x%lx SZ 0x%lx\n",
				chunk_num, sram, chunk_source, chunk_size);
			memcpy((void *)sram, (void *)chunk_source, chunk_size);
			dsb();
			img_src = MSS_INTERNAL_SPACE |
				  (sram & MSS_INTERNAL_ADDR_MASK);
		}

		ret = mss_iram_dma_load(img_src, chunk_size, mss_regs);
		if (ret != 0) {
			ERROR("MSS FW chunk %d load failed\n", chunk_num);
			return ret;
		}
	}

	bl2_plat_configure_mss_windows(mss_regs);

	if (sram != 0) {
		/* Wipe the MSS SRAM after using it as copy buffer */
		memset((void *)sram, 0, MSS_SRAM_SIZE);
		NOTICE("CP MSS startup is postponed\n");
		/* FW loaded, but CPU startup postponed until final CP setup */
		mmio_write_32(sram, MSS_FW_READY_MAGIC);
		dsb();
	} else {
		/* Release M3 from reset */
		mmio_write_32(MSS_M3_RSTCR(mss_regs),
			      (MSS_M3_RSTCR_RST_OFF <<
			       MSS_M3_RSTCR_RST_OFFSET));
	}

	NOTICE("Done\n");

	return 0;
}

/* Load image to MSS AP and do PM related initialization
 * Note that this routine is different than other CM3 loading routines, because
 * firmware for AP is dedicated for PM and therefore some additional PM
 * initialization is required
 */
static int mss_ap_load_image(uintptr_t single_img,
			     uint32_t image_size, uint32_t ap_idx)
{
	volatile struct mss_pm_ctrl_block *mss_pm_crtl;
	int ret;

	/* TODO: add PM Control Info from platform */
	mss_pm_crtl = (struct mss_pm_ctrl_block *)MSS_SRAM_PM_CONTROL_BASE;
	mss_pm_crtl->ipc_version                = MV_PM_FW_IPC_VERSION;
	mss_pm_crtl->num_of_clusters            = PLAT_MARVELL_CLUSTER_COUNT;
	mss_pm_crtl->num_of_cores_per_cluster   =
						PLAT_MARVELL_CLUSTER_CORE_COUNT;
	mss_pm_crtl->num_of_cores               = PLAT_MARVELL_CLUSTER_COUNT *
						PLAT_MARVELL_CLUSTER_CORE_COUNT;
	mss_pm_crtl->pm_trace_ctrl_base_address = AP_MSS_ATF_CORE_CTRL_BASE;
	mss_pm_crtl->pm_trace_info_base_address = AP_MSS_ATF_CORE_INFO_BASE;
	mss_pm_crtl->pm_trace_info_core_size    = AP_MSS_ATF_CORE_INFO_SIZE;
	VERBOSE("MSS Control Block = 0x%x\n", MSS_SRAM_PM_CONTROL_BASE);
	VERBOSE("mss_pm_crtl->ipc_version                = 0x%x\n",
		mss_pm_crtl->ipc_version);
	VERBOSE("mss_pm_crtl->num_of_cores               = 0x%x\n",
		mss_pm_crtl->num_of_cores);
	VERBOSE("mss_pm_crtl->num_of_clusters            = 0x%x\n",
		mss_pm_crtl->num_of_clusters);
	VERBOSE("mss_pm_crtl->num_of_cores_per_cluster   = 0x%x\n",
		mss_pm_crtl->num_of_cores_per_cluster);
	VERBOSE("mss_pm_crtl->pm_trace_ctrl_base_address = 0x%x\n",
		mss_pm_crtl->pm_trace_ctrl_base_address);
	VERBOSE("mss_pm_crtl->pm_trace_info_base_address = 0x%x\n",
		mss_pm_crtl->pm_trace_info_base_address);
	VERBOSE("mss_pm_crtl->pm_trace_info_core_size    = 0x%x\n",
		mss_pm_crtl->pm_trace_info_core_size);

	/* TODO: add checksum to image */
	VERBOSE("Send info about the SCP_BL2 image to be transferred to SCP\n");

	ret = mss_image_load(single_img, image_size,
			     bl2_plat_get_ap_mss_regs(ap_idx), 0);
	if (ret != 0) {
		ERROR("SCP Image load failed\n");
		return -1;
	}

	/* check that the image was loaded successfully */
	ret = mss_check_image_ready(mss_pm_crtl);
	if (ret != 0)
		NOTICE("SCP Image doesn't contain PM firmware\n");

	return 0;
}

/* Load CM3 image (single_img) to CM3 pointed by cm3_type */
static int load_img_to_cm3(enum cm3_t cm3_type,
			   uintptr_t single_img, uint32_t image_size)
{
	int ret, ap_idx, cp_index;
	uint32_t ap_count = bl2_plat_get_ap_count();

	switch (cm3_type) {
	case MSS_AP:
		for (ap_idx = 0; ap_idx < ap_count; ap_idx++) {
			NOTICE("Load image to AP%d MSS\n", ap_idx);
			ret = mss_ap_load_image(single_img, image_size, ap_idx);
			if (ret != 0)
				return ret;
		}
		break;
	case MSS_CP0:
	case MSS_CP1:
	case MSS_CP2:
	case MSS_CP3:
		/* MSS_AP = 0
		 * MSS_CP1 = 1
		 * .
		 * .
		 * MSS_CP3 = 4
		 * Actual CP index is MSS_CPX - 1
		 */
		cp_index = cm3_type - 1;
		for (ap_idx = 0; ap_idx < ap_count; ap_idx++) {
			/* Check if we should load this image
			 * according to number of CPs
			 */
			if (bl2_plat_get_cp_count(ap_idx) <= cp_index) {
				NOTICE("Skipping MSS CP%d related image\n",
				       cp_index);
				break;
			}

			NOTICE("Load image to CP%d MSS AP%d\n",
			       cp_index, ap_idx);
			ret = mss_image_load(single_img, image_size,
					     bl2_plat_get_cp_mss_regs(
						     ap_idx, cp_index),
					     bl2_plat_get_cp_mss_sram(
						     ap_idx, cp_index));
			if (ret != 0) {
				ERROR("SCP Image load failed\n");
				return -1;
			}
		}
		break;
	case MG_CP0:
	case MG_CP1:
	case MG_CP2:
		cp_index = cm3_type - MG_CP0;
		if (bl2_plat_get_cp_count(0) <= cp_index) {
			NOTICE("Skipping MG CP%d related image\n",
			       cp_index);
			break;
		}
		NOTICE("Load image to CP%d MG\n", cp_index);
		ret = mg_image_load(single_img, image_size, cp_index);
		if (ret != 0) {
			ERROR("SCP Image load failed\n");
			return -1;
		}
		break;
	default:
		ERROR("SCP_BL2 wrong img format (cm3_type=%d)\n", cm3_type);
		break;
	}

	return 0;
}

/* The Armada 8K has 5 service CPUs and Armada 7K has 3. Therefore it was
 * required to provide a method for loading firmware to all of the service CPUs.
 * To achieve that, the scp_bl2 image in fact is file containing up to 5
 * concatenated firmwares and this routine splits concatenated image into single
 * images dedicated for appropriate service CPU and then load them.
 */
static int split_and_load_bl2_image(void *image)
{
	file_header_t *file_hdr;
	img_header_t *img_hdr;
	uintptr_t single_img;
	int i;

	file_hdr = (file_header_t *)image;

	if (file_hdr->magic != FILE_MAGIC) {
		ERROR("SCP_BL2 wrong img format\n");
		return -1;
	}

	if (file_hdr->nr_of_imgs > MAX_NR_OF_FILES) {
		ERROR("SCP_BL2 concatenated image contains too many images\n");
		return -1;
	}

	img_hdr = (img_header_t *)((uintptr_t)image + sizeof(file_header_t));
	single_img = (uintptr_t)image + sizeof(file_header_t) +
				    sizeof(img_header_t) * file_hdr->nr_of_imgs;

	NOTICE("SCP_BL2 contains %d concatenated images\n",
							  file_hdr->nr_of_imgs);
	for (i = 0; i < file_hdr->nr_of_imgs; i++) {

		/* Before loading make sanity check on header */
		if (img_hdr->version != HEADER_VERSION) {
			ERROR("Wrong header, img corrupted exiting\n");
			return -1;
		}

		load_img_to_cm3(img_hdr->type, single_img, img_hdr->length);

		/* Prepare offsets for next run */
		single_img += img_hdr->length;
		img_hdr++;
	}

	return 0;
}

int scp_bootloader_transfer(void *image, unsigned int image_size)
{
#ifdef SCP_BL2_BASE
	assert((uintptr_t) image == SCP_BL2_BASE);
#endif

	VERBOSE("Concatenated img size %d\n", image_size);

	if (image_size == 0) {
		ERROR("SCP_BL2 image size can't be 0 (current size = 0x%x)\n",
								    image_size);
		return -1;
	}

	if (split_and_load_bl2_image(image))
		return -1;

	return 0;
}
