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

#include <platform_def.h>

#include <common/bl_common.h>
#include <common/debug.h>
#include <drivers/marvell/ccu.h>
#include <drivers/marvell/mochi/ap_setup.h>
#include <drivers/marvell/mochi/cp110_setup.h>
#include <lib/mmio.h>

#include <armada_common.h>
#include <marvell_plat_priv.h> /* timer functionality */

#include "mss_scp_bootloader.h"

/* MSS windows configuration */
#define MSS_AEBR(base)			(base + 0x160)
#define MSS_AIBR(base)			(base + 0x164)
#define MSS_AEBR_MASK			0xFFF
#define MSS_AIBR_MASK			0xFFF

#define MSS_EXTERNAL_SPACE		0x50000000
#define MSS_EXTERNAL_ACCESS_BIT		28
#define MSS_EXTERNAL_ADDR_MASK		0xfffffff
#define MSS_INTERNAL_ACCESS_BIT		28

struct addr_map_win ccu_mem_map[] = {
	{MVEBU_CP_REGS_BASE(0), 0x4000000, IO_0_TID}
};

/* Since the scp_bl2 image can contain firmware for cp1 and cp0 coprocessors,
 * the access to cp0 and cp1 need to be provided. More precisely it is
 * required to:
 *  - get the information about device id which is stored in CP0 registers
 *    (to distinguish between cases where we have cp0 and cp1 or standalone cp0)
 *  - get the access to cp which is needed for loading fw for cp0/cp1
 *    coprocessors
 * This function configures ccu windows accordingly.
 *
 * Note: there is no need to restore previous ccu configuration, since in next
 * phase (BL31) the init_ccu will be called (via apn806_init/
 * bl31_plat_arch_setu) and therefore the ccu configuration will be overwritten.
 */
static int bl2_plat_mmap_init(void)
{
	int cfg_num, win_id, cfg_idx, cp;

	cfg_num =  ARRAY_SIZE(ccu_mem_map);

	/* CCU window-0 should not be counted - it's already used */
	if (cfg_num > (MVEBU_CCU_MAX_WINS - 1)) {
		ERROR("BL2: %s: trying to open too many windows\n", __func__);
		return -1;
	}

	/* Enable required CCU windows
	 * Do not touch CCU window 0,
	 * it's used for the internal registers access
	 */
	for (cfg_idx = 0, win_id = 1;
	     (win_id < MVEBU_CCU_MAX_WINS) && (cfg_idx < cfg_num); win_id++) {
		/* Skip already enabled CCU windows */
		if (ccu_is_win_enabled(MVEBU_AP0, win_id))
			continue;
		/* Enable required CCU windows */
		ccu_win_check(&ccu_mem_map[cfg_idx]);
		ccu_enable_win(MVEBU_AP0, &ccu_mem_map[cfg_idx], win_id);
		cfg_idx++;
	}

	/* Config address for each cp other than cp0 */
	for (cp = 1; cp < CP_COUNT; cp++)
		update_cp110_default_win(cp);

	/* There is need to configure IO_WIN windows again to overwrite
	 * temporary configuration done during update_cp110_default_win
	 */
	init_io_win(MVEBU_AP0);

	/* Open AMB bridge required for MG access */
	for (cp = 0; cp < CP_COUNT; cp++)
		cp110_amb_init(MVEBU_CP_REGS_BASE(cp));

	return 0;
}

/*****************************************************************************
 * Transfer SCP_BL2 from Trusted RAM using the SCP Download protocol.
 * Return 0 on success, -1 otherwise.
 *****************************************************************************
 */
int bl2_plat_handle_scp_bl2(image_info_t *scp_bl2_image_info)
{
	int ret;

	INFO("BL2: Initiating SCP_BL2 transfer to SCP\n");

	/* initialize time (for delay functionality) */
	plat_delay_timer_init();

	ret = bl2_plat_mmap_init();
	if (ret != 0)
		return ret;

	ret = scp_bootloader_transfer((void *)scp_bl2_image_info->image_base,
		scp_bl2_image_info->image_size);

	if (ret == 0)
		INFO("BL2: SCP_BL2 transferred to SCP\n");
	else
		ERROR("BL2: SCP_BL2 transfer failure\n");

	return ret;
}

uintptr_t bl2_plat_get_cp_mss_regs(int ap_idx, int cp_idx)
{
	return MVEBU_CP_REGS_BASE(cp_idx) + 0x280000;
}

uintptr_t bl2_plat_get_ap_mss_regs(int ap_idx)
{
	return MVEBU_REGS_BASE + 0x580000;
}

uint32_t bl2_plat_get_cp_count(int ap_idx)
{
	uint32_t revision = cp110_device_id_get(MVEBU_CP_REGS_BASE(0));
	/* A8040: two CPs.
	 * A7040: one CP.
	 */
	if (revision == MVEBU_80X0_DEV_ID ||
	    revision == MVEBU_80X0_CP115_DEV_ID)
		return 2;
	else if (revision == MVEBU_CN9130_DEV_ID)
		return CP_COUNT;
	else
		return 1;
}

uint32_t bl2_plat_get_ap_count(void)
{
	/* A8040 and A7040 have only one AP */
	return 1;
}

void bl2_plat_configure_mss_windows(uintptr_t mss_regs)
{
	/* set AXI External and Internal Address Bus extension */
	mmio_write_32(MSS_AEBR(mss_regs),
		      ((0x0 >> MSS_EXTERNAL_ACCESS_BIT) & MSS_AEBR_MASK));
	mmio_write_32(MSS_AIBR(mss_regs),
		      ((mss_regs >> MSS_INTERNAL_ACCESS_BIT) & MSS_AIBR_MASK));
}
