/*
 * Copyright (c) 2019, Intel Corporation. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <arch.h>
#include <arch_helpers.h>
#include <assert.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
#include <platform_def.h>

#include "s10_clock_manager.h"
#include "s10_handoff.h"

static const CLOCK_SOURCE_CONFIG  clk_source = {
	/* clk_freq_of_eosc1 */
	(uint32_t) 25000000,
	/* clk_freq_of_f2h_free */
	(uint32_t) 460000000,
	/* clk_freq_of_cb_intosc_ls */
	(uint32_t) 50000000,
};

void wait_pll_lock(void)
{
	uint32_t data;

	do {
		data = mmio_read_32(ALT_CLKMGR + ALT_CLKMGR_STAT);
	} while ((ALT_CLKMGR_STAT_MAINPLLLOCKED(data) == 0) ||
			(ALT_CLKMGR_STAT_PERPLLLOCKED(data) == 0));
}

void wait_fsm(void)
{
	uint32_t data;

	do {
		data = mmio_read_32(ALT_CLKMGR + ALT_CLKMGR_STAT);
	} while (ALT_CLKMGR_STAT_BUSY(data) == ALT_CLKMGR_STAT_BUSY_E_BUSY);
}

void config_clkmgr_handoff(handoff *hoff_ptr)
{
	uint32_t m_div, refclk_div, mscnt, hscnt;

	/* Bypass all mainpllgrp's clocks */
	mmio_write_32(ALT_CLKMGR_MAINPLL +
			ALT_CLKMGR_MAINPLL_BYPASS,
			0x7);
	wait_fsm();
	/* Bypass all perpllgrp's clocks */
	mmio_write_32(ALT_CLKMGR_PERPLL +
			ALT_CLKMGR_PERPLL_BYPASS,
			0x7f);
	wait_fsm();

	/* Setup main PLL dividers */
	m_div = ALT_CLKMGR_MAINPLL_FDBCK_MDIV(hoff_ptr->main_pll_fdbck);
	refclk_div = ALT_CLKMGR_MAINPLL_PLLGLOB_REFCLKDIV(
			hoff_ptr->main_pll_pllglob);
	mscnt = 200 / ((6 + m_div) / refclk_div);
	hscnt = (m_div + 6) * mscnt / refclk_div - 9;

	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_PLLGLOB,
			hoff_ptr->main_pll_pllglob);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_FDBCK,
			hoff_ptr->main_pll_fdbck);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_VCOCALIB,
			ALT_CLKMGR_MAINPLL_VCOCALIB_HSCNT_SET(hscnt) |
			ALT_CLKMGR_MAINPLL_VCOCALIB_MSCNT_SET(mscnt));
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_PLLC0,
			hoff_ptr->main_pll_pllc0);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_PLLC1,
			hoff_ptr->main_pll_pllc1);

	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_NOCDIV,
			hoff_ptr->main_pll_nocdiv);

	/* Setup peripheral PLL dividers */
	m_div = ALT_CLKMGR_PERPLL_FDBCK_MDIV(hoff_ptr->per_pll_fdbck);
	refclk_div = ALT_CLKMGR_PERPLL_PLLGLOB_REFCLKDIV(
			hoff_ptr->per_pll_pllglob);
	mscnt = 200 / ((6 + m_div) / refclk_div);
	hscnt = (m_div + 6) * mscnt / refclk_div - 9;

	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_PLLGLOB,
			hoff_ptr->per_pll_pllglob);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_FDBCK,
			hoff_ptr->per_pll_fdbck);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_VCOCALIB,
			ALT_CLKMGR_PERPLL_VCOCALIB_HSCNT_SET(hscnt) |
			ALT_CLKMGR_PERPLL_VCOCALIB_MSCNT_SET(mscnt));
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_PLLC0,
			hoff_ptr->per_pll_pllc0);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_PLLC1,
			hoff_ptr->per_pll_pllc1);

	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_GPIODIV,
			ALT_CLKMGR_PERPLL_GPIODIV_GPIODBCLK_SET(
			hoff_ptr->per_pll_gpiodiv));
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_EMACCTL,
			hoff_ptr->per_pll_emacctl);


	/* Take both PLL out of reset and power up */
	mmio_setbits_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_PLLGLOB,
			ALT_CLKMGR_MAINPLL_PLLGLOB_PD_SET_MSK |
			ALT_CLKMGR_MAINPLL_PLLGLOB_RST_SET_MSK);
	mmio_setbits_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_PLLGLOB,
			ALT_CLKMGR_PERPLL_PLLGLOB_PD_SET_MSK |
			ALT_CLKMGR_PERPLL_PLLGLOB_RST_SET_MSK);

	wait_pll_lock();

	/* Dividers for C2 to C9 only init after PLLs are lock. */
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_MPUCLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_NOCCLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR2CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR3CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR4CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR5CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR6CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR7CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR8CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR9CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR2CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR3CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR4CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR5CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR6CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR7CLK, 0xff);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR8CLK, 0xff);

	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_MPUCLK,
			hoff_ptr->main_pll_mpuclk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_NOCCLK,
			hoff_ptr->main_pll_nocclk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR2CLK,
			hoff_ptr->main_pll_cntr2clk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR3CLK,
			hoff_ptr->main_pll_cntr3clk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR4CLK,
			hoff_ptr->main_pll_cntr4clk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR5CLK,
			hoff_ptr->main_pll_cntr5clk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR6CLK,
			hoff_ptr->main_pll_cntr6clk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR7CLK,
			hoff_ptr->main_pll_cntr7clk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR8CLK,
			hoff_ptr->main_pll_cntr8clk);
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_CNTR9CLK,
			hoff_ptr->main_pll_cntr9clk);

	/* Peripheral PLL Clock Source and Counters/Divider */
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR2CLK,
			hoff_ptr->per_pll_cntr2clk);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR3CLK,
			hoff_ptr->per_pll_cntr3clk);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR4CLK,
			hoff_ptr->per_pll_cntr4clk);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR5CLK,
			hoff_ptr->per_pll_cntr5clk);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR6CLK,
			hoff_ptr->per_pll_cntr6clk);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR7CLK,
			hoff_ptr->per_pll_cntr7clk);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR8CLK,
			hoff_ptr->per_pll_cntr8clk);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_CNTR9CLK,
			hoff_ptr->per_pll_cntr9clk);

	/* Take all PLLs out of bypass */
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_BYPASS, 0);
	wait_fsm();
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_BYPASS, 0);
	wait_fsm();

	/* Set safe mode/ out of boot mode */
	mmio_clrbits_32(ALT_CLKMGR + ALT_CLKMGR_CTRL,
		ALT_CLKMGR_CTRL_BOOTMODE_SET_MSK);
	wait_fsm();

	/* 10 Enable mainpllgrp's software-managed clock */
	mmio_write_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_EN,
			ALT_CLKMGR_MAINPLL_EN_RESET);
	mmio_write_32(ALT_CLKMGR_PERPLL + ALT_CLKMGR_PERPLL_EN,
			ALT_CLKMGR_PERPLL_EN_RESET);

	/* Clear loss lock  interrupt status register that */
	/* might be set during configuration */
	mmio_write_32(ALT_CLKMGR + ALT_CLKMGR_INTRCLR,
			ALT_CLKMGR_INTRCLR_MAINLOCKLOST_SET_MSK |
			ALT_CLKMGR_INTRCLR_PERLOCKLOST_SET_MSK);
}

int get_wdt_clk(handoff *hoff_ptr)
{
	int main_noc_base_clk, l3_main_free_clk, l4_sys_free_clk;
	int data32, mdiv, refclkdiv, ref_clk;

	data32 = mmio_read_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_PLLGLOB);

	switch (ALT_CLKMGR_MAINPLL_PLLGLOB_PSRC(data32)) {
	case ALT_CLKMGR_MAINPLL_PLLGLOB_PSRC_EOSC1:
		ref_clk = clk_source.clk_freq_of_eosc1;
		break;
	case ALT_CLKMGR_MAINPLL_PLLGLOB_PSRC_INTOSC:
		ref_clk = clk_source.clk_freq_of_cb_intosc_ls;
		break;
	case ALT_CLKMGR_MAINPLL_PLLGLOB_PSRC_F2S:
		ref_clk = clk_source.clk_freq_of_f2h_free;
		break;
	default:
		ref_clk = 0;
		assert(0);
		break;
	}

	refclkdiv = ALT_CLKMGR_MAINPLL_PLLGLOB_REFCLKDIV(data32);
	data32 = mmio_read_32(ALT_CLKMGR_MAINPLL + ALT_CLKMGR_MAINPLL_FDBCK);
	mdiv = ALT_CLKMGR_MAINPLL_FDBCK_MDIV(data32);
	ref_clk = (ref_clk / refclkdiv) * (6 + mdiv);

	main_noc_base_clk = ref_clk / (hoff_ptr->main_pll_pllc1 & 0xff);
	l3_main_free_clk = main_noc_base_clk / (hoff_ptr->main_pll_nocclk + 1);
	l4_sys_free_clk = l3_main_free_clk / 4;

	return l4_sys_free_clk;
}
