/*
 * Copyright 2014 Broadcom Corporation.
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <asm/io.h>
#include <asm/iproc-common/armpll.h>
#include <asm/iproc-common/sysmap.h>

#define NELEMS(x)	(sizeof(x) / sizeof(x[0]))

struct armpll_parameters {
	unsigned int mode;
	unsigned int ndiv_int;
	unsigned int ndiv_frac;
	unsigned int pdiv;
	unsigned int freqid;
};

struct armpll_parameters armpll_clk_tab[] = {
	{   25, 64,      1, 1, 0},
	{  100, 64,      1, 1, 2},
	{  400, 64,      1, 1, 6},
	{  448, 71, 713050, 1, 6},
	{  500, 80,      1, 1, 6},
	{  560, 89, 629145, 1, 6},
	{  600, 96,      1, 1, 6},
	{  800, 64,      1, 1, 7},
	{  896, 71, 713050, 1, 7},
	{ 1000, 80,      1, 1, 7},
	{ 1100, 88,      1, 1, 7},
	{ 1120, 89, 629145, 1, 7},
	{ 1200, 96,      1, 1, 7},
};

uint32_t armpll_config(uint32_t clkmhz)
{
	uint32_t freqid;
	uint32_t ndiv_frac;
	uint32_t pll;
	uint32_t status = 1;
	uint32_t timeout_countdown;
	int i;

	for (i = 0; i < NELEMS(armpll_clk_tab); i++) {
		if (armpll_clk_tab[i].mode == clkmhz) {
			status = 0;
			break;
		}
	}

	if (status) {
		printf("Error: Clock configuration not supported\n");
		goto armpll_config_done;
	}

	/* Enable write access */
	writel(IPROC_REG_WRITE_ACCESS, IHOST_PROC_CLK_WR_ACCESS);

	if (clkmhz == 25)
		freqid = 0;
	else
		freqid = 2;

	/* Bypass ARM clock and run on sysclk */
	writel(1 << IHOST_PROC_CLK_POLICY_FREQ__PRIV_ACCESS_MODE |
	       freqid << IHOST_PROC_CLK_POLICY_FREQ__POLICY3_FREQ_R |
	       freqid << IHOST_PROC_CLK_POLICY_FREQ__POLICY2_FREQ_R |
	       freqid << IHOST_PROC_CLK_POLICY_FREQ__POLICY1_FREQ_R |
	       freqid << IHOST_PROC_CLK_POLICY_FREQ__POLICY0_FREQ_R,
	       IHOST_PROC_CLK_POLICY_FREQ);

	writel(1 << IHOST_PROC_CLK_POLICY_CTL__GO |
	       1 << IHOST_PROC_CLK_POLICY_CTL__GO_AC,
	       IHOST_PROC_CLK_POLICY_CTL);

	/* Poll CCU until operation complete */
	timeout_countdown = 0x100000;
	while (readl(IHOST_PROC_CLK_POLICY_CTL) &
	       (1 << IHOST_PROC_CLK_POLICY_CTL__GO)) {
		timeout_countdown--;
		if (timeout_countdown == 0) {
			printf("CCU polling timedout\n");
			status = 1;
			goto armpll_config_done;
		}
	}

	if (clkmhz == 25 || clkmhz == 100) {
		status = 0;
		goto armpll_config_done;
	}

	/* Now it is safe to program the PLL */
	pll = readl(IHOST_PROC_CLK_PLLARMB);
	pll &= ~((1 << IHOST_PROC_CLK_PLLARMB__PLLARM_NDIV_FRAC_WIDTH) - 1);
	ndiv_frac =
		((1 << IHOST_PROC_CLK_PLLARMB__PLLARM_NDIV_FRAC_WIDTH) - 1) &
		 (armpll_clk_tab[i].ndiv_frac <<
		 IHOST_PROC_CLK_PLLARMB__PLLARM_NDIV_FRAC_R);
	pll |= ndiv_frac;
	writel(pll, IHOST_PROC_CLK_PLLARMB);

	writel(1 << IHOST_PROC_CLK_PLLARMA__PLLARM_LOCK |
	       armpll_clk_tab[i].ndiv_int <<
			IHOST_PROC_CLK_PLLARMA__PLLARM_NDIV_INT_R |
	       armpll_clk_tab[i].pdiv <<
			IHOST_PROC_CLK_PLLARMA__PLLARM_PDIV_R |
	       1 << IHOST_PROC_CLK_PLLARMA__PLLARM_SOFT_RESETB,
	       IHOST_PROC_CLK_PLLARMA);

	/* Poll ARM PLL Lock until operation complete */
	timeout_countdown = 0x100000;
	while (readl(IHOST_PROC_CLK_PLLARMA) &
	       (1 << IHOST_PROC_CLK_PLLARMA__PLLARM_LOCK)) {
		timeout_countdown--;
		if (timeout_countdown == 0) {
			printf("ARM PLL lock failed\n");
			status = 1;
			goto armpll_config_done;
		}
	}

	pll = readl(IHOST_PROC_CLK_PLLARMA);
	pll |= (1 << IHOST_PROC_CLK_PLLARMA__PLLARM_SOFT_POST_RESETB);
	writel(pll, IHOST_PROC_CLK_PLLARMA);

	/* Set the policy */
	writel(1 << IHOST_PROC_CLK_POLICY_FREQ__PRIV_ACCESS_MODE |
	       armpll_clk_tab[i].freqid <<
			IHOST_PROC_CLK_POLICY_FREQ__POLICY3_FREQ_R |
	       armpll_clk_tab[i].freqid <<
			IHOST_PROC_CLK_POLICY_FREQ__POLICY2_FREQ_R |
	       armpll_clk_tab[i].freqid <<
			IHOST_PROC_CLK_POLICY_FREQ__POLICY1_FREQ_R |
	       armpll_clk_tab[i+4].freqid <<
			IHOST_PROC_CLK_POLICY_FREQ__POLICY0_FREQ_R,
	       IHOST_PROC_CLK_POLICY_FREQ);

	writel(IPROC_CLKCT_HDELAY_SW_EN, IHOST_PROC_CLK_CORE0_CLKGATE);
	writel(IPROC_CLKCT_HDELAY_SW_EN, IHOST_PROC_CLK_CORE1_CLKGATE);
	writel(IPROC_CLKCT_HDELAY_SW_EN, IHOST_PROC_CLK_ARM_SWITCH_CLKGATE);
	writel(IPROC_CLKCT_HDELAY_SW_EN, IHOST_PROC_CLK_ARM_PERIPH_CLKGATE);
	writel(IPROC_CLKCT_HDELAY_SW_EN, IHOST_PROC_CLK_APB0_CLKGATE);

	writel(1 << IHOST_PROC_CLK_POLICY_CTL__GO |
	       1 << IHOST_PROC_CLK_POLICY_CTL__GO_AC,
	       IHOST_PROC_CLK_POLICY_CTL);

	/* Poll CCU until operation complete */
	timeout_countdown = 0x100000;
	while (readl(IHOST_PROC_CLK_POLICY_CTL) &
	       (1 << IHOST_PROC_CLK_POLICY_CTL__GO)) {
		timeout_countdown--;
		if (timeout_countdown == 0) {
			printf("CCU polling failed\n");
			status = 1;
			goto armpll_config_done;
		}
	}

	status = 0;
armpll_config_done:
	/* Disable access to PLL registers */
	writel(0, IHOST_PROC_CLK_WR_ACCESS);

	return status;
}