/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2012 Samsung Electronics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

/* Clock setup for SMDK5420 board based on EXYNOS5 */

#include <console/console.h>
#include <delay.h>
#include "clk.h"
#include "cpu.h"
#include "dp.h"
#include "dmc.h"
#include "setup.h"

void system_clock_init(void)
{
	struct exynos5420_clock *clk =
		(struct exynos5420_clock *)EXYNOS5420_CLOCK_BASE;
	struct exynos5_mct_regs *mct_regs =
		(struct exynos5_mct_regs *)EXYNOS5_MULTI_CORE_TIMER_BASE;
	u32 val;

	/* Turn on the MCT as early as possible. */
	mct_regs->g_tcon |= (1 << 8);

	/* PLL locktime */
	writel(APLL_LOCK_VAL, &clk->apll_lock);
	writel(MPLL_LOCK_VAL, &clk->mpll_lock);
	writel(BPLL_LOCK_VAL, &clk->bpll_lock);
	writel(CPLL_LOCK_VAL, &clk->cpll_lock);
	writel(DPLL_LOCK_VAL, &clk->dpll_lock);
	writel(EPLL_LOCK_VAL, &clk->epll_lock);
	writel(VPLL_LOCK_VAL, &clk->vpll_lock);
	writel(IPLL_LOCK_VAL, &clk->ipll_lock);
	writel(SPLL_LOCK_VAL, &clk->spll_lock);
	writel(KPLL_LOCK_VAL, &clk->kpll_lock);
        writel(RPLL_LOCK_VAL, &clk->rpll_lock);

	setbits_le32(&clk->clk_src_cpu, MUX_HPM_SEL_MASK);

	writel(0, &clk->clk_src_top6);

	writel(0, &clk->clk_src_cdrex);
	writel(SRC_KFC_HPM_SEL, &clk->clk_src_kfc);
	writel(HPM_RATIO,  &clk->clk_div_cpu1);
	writel(CLK_DIV_CPU0_VAL, &clk->clk_div_cpu0);

	/* switch A15 clock source to OSC clock before changing APLL */
	clrbits_le32(&clk->clk_src_cpu, APLL_FOUT);

	/* Set APLL */
	writel(APLL_CON1_VAL, &clk->apll_con1);
	val = set_pll(225, 3, 0);	/* FOUT=1800MHz */
	writel(val, &clk->apll_con0);
	while ((readl(&clk->apll_con0) & PLL_LOCKED) == 0)
		;

	/* now it is safe to switch to APLL */
	setbits_le32(&clk->clk_src_cpu, APLL_FOUT);

	writel(SRC_KFC_HPM_SEL, &clk->clk_src_kfc);
	writel(CLK_DIV_KFC_VAL, &clk->clk_div_kfc0);

	/* switch A7 clock source to OSC clock before changing KPLL */
	clrbits_le32(&clk->clk_src_kfc, KPLL_FOUT);

	/* Set KPLL*/
	writel(KPLL_CON1_VAL, &clk->kpll_con1);
	val = set_pll(0xc8, 0x2, 0x2);
	writel(val, &clk->kpll_con0);
	while ((readl(&clk->kpll_con0) & PLL_LOCKED) == 0)
		;

	/* now it is safe to switch to KPLL */
	setbits_le32(&clk->clk_src_kfc, KPLL_FOUT);

	/* Set MPLL */
	writel(MPLL_CON1_VAL, &clk->mpll_con1);
	val = set_pll(0xc8, 0x3, 0x1);
	writel(val, &clk->mpll_con0);
	while ((readl(&clk->mpll_con0) & PLL_LOCKED) == 0)
		;

	/* Set DPLL */
	writel(DPLL_CON1_VAL, &clk->dpll_con1);
	val = set_pll(0x190, 0x4, 0x2);
	writel(val, &clk->dpll_con0);
	while ((readl(&clk->dpll_con0) & PLL_LOCKED) == 0)
		;

	/* Set EPLL */
	writel(EPLL_CON2_VAL, &clk->epll_con2);
	writel(EPLL_CON1_VAL, &clk->epll_con1);
	val = set_pll(0x64, 0x2, 0x1);
	writel(val, &clk->epll_con0);
	while ((readl(&clk->epll_con0) & PLL_LOCKED) == 0)
		;

	/* Set CPLL */
	writel(CPLL_CON1_VAL, &clk->cpll_con1);
	val = set_pll(0xde, 0x4, 0x1);
	writel(val, &clk->cpll_con0);
	while ((readl(&clk->cpll_con0) & PLL_LOCKED) == 0)
		;

	/* Set IPLL */
	writel(IPLL_CON1_VAL, &clk->ipll_con1);
	val = set_pll(0xB9, 0x3, 0x2);
	writel(val, &clk->ipll_con0);
	while ((readl(&clk->ipll_con0) & PLL_LOCKED) == 0)
		;

	/* Set VPLL */
	writel(VPLL_CON1_VAL, &clk->vpll_con1);
	val = set_pll(0xd7, 0x3, 0x2);
	writel(val, &clk->vpll_con0);
	while ((readl(&clk->vpll_con0) & PLL_LOCKED) == 0)
		;

	/* Set BPLL */
	writel(BPLL_CON1_VAL, &clk->bpll_con1);
	val = set_pll(0xc8, 0x3, 0x1);
	writel(val, &clk->bpll_con0);
	while ((readl(&clk->bpll_con0) & PLL_LOCKED) == 0)
		;

	/* Set SPLL */
	writel(SPLL_CON1_VAL, &clk->spll_con1);
	val = set_pll(200, 0x3, 0x2);	/* 400MHz */
	writel(val, &clk->spll_con0);
	while ((readl(&clk->spll_con0) & PLL_LOCKED) == 0)
		;

        /* We use RPLL as the source for FIMD video stream clock */
	writel(RPLL_CON1_VAL, &clk->rpll_con1);
	writel(RPLL_CON2_VAL, &clk->rpll_con2);
	/* computed by gabe from first principles; u-boot is probably
	 * wrong again
	 */
	val = set_pll(0xa0, 0x3, 0x2);
	writel(val, &clk->rpll_con0);
	/* note: this is a meaningless exercise. The hardware lock
	 * detection does not work. So this just spins for some
	 * time and is done. NO indication of success should attach
	 * to this or any other spin on a con0 value.
	 */
	while ((readl(&clk->rpll_con0) & PLL_LOCKED) == 0)
		;

	writel(CLK_DIV_CDREX0_VAL, &clk->clk_div_cdrex0);
	writel(CLK_DIV_CDREX1_VAL, &clk->clk_div_cdrex1);

	writel(CLK_SRC_TOP0_VAL, &clk->clk_src_top0);
	writel(CLK_SRC_TOP1_VAL, &clk->clk_src_top1);
	writel(CLK_SRC_TOP2_VAL, &clk->clk_src_top2);
	writel(CLK_SRC_TOP7_VAL, &clk->clk_src_top7);

	writel(CLK_DIV_TOP0_VAL, &clk->clk_div_top0);
	writel(CLK_DIV_TOP1_VAL, &clk->clk_div_top1);
	writel(CLK_DIV_TOP2_VAL, &clk->clk_div_top2);

	writel(0, &clk->clk_src_top10);
	writel(0, &clk->clk_src_top11);
	writel(0, &clk->clk_src_top12);

	writel(CLK_SRC_TOP3_VAL, &clk->clk_src_top3);
	writel(CLK_SRC_TOP4_VAL, &clk->clk_src_top4);
	writel(CLK_SRC_TOP5_VAL, &clk->clk_src_top5);

	/* DISP1 BLK CLK SELECTION */
	writel(CLK_SRC_DISP1_0_VAL, &clk->clk_src_disp10);
	writel(CLK_DIV_DISP1_0_VAL, &clk->clk_div_disp10);

	/* AUDIO BLK */
	writel(AUDIO0_SEL_EPLL, &clk->clk_src_mau);
	writel(DIV_MAU_VAL, &clk->clk_div_mau);

	/* FSYS */
	writel(CLK_SRC_FSYS0_VAL, &clk->clk_src_fsys);
	writel(CLK_DIV_FSYS0_VAL, &clk->clk_div_fsys0);
	writel(CLK_DIV_FSYS1_VAL, &clk->clk_div_fsys1);
	writel(CLK_DIV_FSYS2_VAL, &clk->clk_div_fsys2);

	writel(CLK_SRC_ISP_VAL, &clk->clk_src_isp);
	writel(CLK_DIV_ISP0_VAL, &clk->clk_div_isp0);
	writel(CLK_DIV_ISP1_VAL, &clk->clk_div_isp1);

	writel(CLK_SRC_PERIC0_VAL, &clk->clk_src_peric0);
	writel(CLK_SRC_PERIC1_VAL, &clk->clk_src_peric1);

	writel(CLK_DIV_PERIC0_VAL, &clk->clk_div_peric0);
	writel(CLK_DIV_PERIC1_VAL, &clk->clk_div_peric1);
	writel(CLK_DIV_PERIC2_VAL, &clk->clk_div_peric2);
	writel(CLK_DIV_PERIC3_VAL, &clk->clk_div_peric3);
	writel(CLK_DIV_PERIC4_VAL, &clk->clk_div_peric4);

	writel(CLK_DIV2_RATIO, &clk->clkdiv2_ratio);
	writel(CLK_DIV4_RATIO, &clk->clkdiv4_ratio);
	writel(CLK_DIV_G2D, &clk->clk_div_g2d);

	writel(CLK_SRC_CPU_VAL, &clk->clk_src_cpu);
	writel(CLK_SRC_TOP6_VAL, &clk->clk_src_top6);
	writel(CLK_SRC_CDREX_VAL, &clk->clk_src_cdrex);
	writel(CLK_SRC_KFC_VAL, &clk->clk_src_kfc);
}

void clock_gate(void)
{
	/* Not implemented for now. */
}
