162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci// Copyright (C) 2014 Broadcom Corporation
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/kernel.h>
562306a36Sopenharmony_ci#include <linux/slab.h>
662306a36Sopenharmony_ci#include <linux/err.h>
762306a36Sopenharmony_ci#include <linux/clk-provider.h>
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/of.h>
1062306a36Sopenharmony_ci#include <linux/clkdev.h>
1162306a36Sopenharmony_ci#include <linux/of_address.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "clk-iproc.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define IPROC_CLK_MAX_FREQ_POLICY                    0x3
1662306a36Sopenharmony_ci#define IPROC_CLK_POLICY_FREQ_OFFSET                 0x008
1762306a36Sopenharmony_ci#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT      8
1862306a36Sopenharmony_ci#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK       0x7
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define IPROC_CLK_PLLARMA_OFFSET                     0xc00
2162306a36Sopenharmony_ci#define IPROC_CLK_PLLARMA_LOCK_SHIFT                 28
2262306a36Sopenharmony_ci#define IPROC_CLK_PLLARMA_PDIV_SHIFT                 24
2362306a36Sopenharmony_ci#define IPROC_CLK_PLLARMA_PDIV_MASK                  0xf
2462306a36Sopenharmony_ci#define IPROC_CLK_PLLARMA_NDIV_INT_SHIFT             8
2562306a36Sopenharmony_ci#define IPROC_CLK_PLLARMA_NDIV_INT_MASK              0x3ff
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define IPROC_CLK_PLLARMB_OFFSET                     0xc04
2862306a36Sopenharmony_ci#define IPROC_CLK_PLLARMB_NDIV_FRAC_MASK             0xfffff
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define IPROC_CLK_PLLARMC_OFFSET                     0xc08
3162306a36Sopenharmony_ci#define IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT            8
3262306a36Sopenharmony_ci#define IPROC_CLK_PLLARMC_MDIV_MASK                  0xff
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define IPROC_CLK_PLLARMCTL5_OFFSET                  0xc20
3562306a36Sopenharmony_ci#define IPROC_CLK_PLLARMCTL5_H_MDIV_MASK             0xff
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define IPROC_CLK_PLLARM_OFFSET_OFFSET               0xc24
3862306a36Sopenharmony_ci#define IPROC_CLK_PLLARM_SW_CTL_SHIFT                29
3962306a36Sopenharmony_ci#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT       20
4062306a36Sopenharmony_ci#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK        0xff
4162306a36Sopenharmony_ci#define IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK       0xfffff
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define IPROC_CLK_ARM_DIV_OFFSET                     0xe00
4462306a36Sopenharmony_ci#define IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT  4
4562306a36Sopenharmony_ci#define IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK        0xf
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define IPROC_CLK_POLICY_DBG_OFFSET                  0xec0
4862306a36Sopenharmony_ci#define IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT          12
4962306a36Sopenharmony_ci#define IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK           0x7
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cienum iproc_arm_pll_fid {
5262306a36Sopenharmony_ci	ARM_PLL_FID_CRYSTAL_CLK   = 0,
5362306a36Sopenharmony_ci	ARM_PLL_FID_SYS_CLK       = 2,
5462306a36Sopenharmony_ci	ARM_PLL_FID_CH0_SLOW_CLK  = 6,
5562306a36Sopenharmony_ci	ARM_PLL_FID_CH1_FAST_CLK  = 7
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistruct iproc_arm_pll {
5962306a36Sopenharmony_ci	struct clk_hw hw;
6062306a36Sopenharmony_ci	void __iomem *base;
6162306a36Sopenharmony_ci	unsigned long rate;
6262306a36Sopenharmony_ci};
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci#define to_iproc_arm_pll(hw) container_of(hw, struct iproc_arm_pll, hw)
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic unsigned int __get_fid(struct iproc_arm_pll *pll)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	u32 val;
6962306a36Sopenharmony_ci	unsigned int policy, fid, active_fid;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	val = readl(pll->base + IPROC_CLK_ARM_DIV_OFFSET);
7262306a36Sopenharmony_ci	if (val & (1 << IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT))
7362306a36Sopenharmony_ci		policy = val & IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK;
7462306a36Sopenharmony_ci	else
7562306a36Sopenharmony_ci		policy = 0;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	/* something is seriously wrong */
7862306a36Sopenharmony_ci	BUG_ON(policy > IPROC_CLK_MAX_FREQ_POLICY);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	val = readl(pll->base + IPROC_CLK_POLICY_FREQ_OFFSET);
8162306a36Sopenharmony_ci	fid = (val >> (IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT * policy)) &
8262306a36Sopenharmony_ci		IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	val = readl(pll->base + IPROC_CLK_POLICY_DBG_OFFSET);
8562306a36Sopenharmony_ci	active_fid = IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK &
8662306a36Sopenharmony_ci		(val >> IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT);
8762306a36Sopenharmony_ci	if (fid != active_fid) {
8862306a36Sopenharmony_ci		pr_debug("%s: fid override %u->%u\n", __func__,	fid,
8962306a36Sopenharmony_ci				active_fid);
9062306a36Sopenharmony_ci		fid = active_fid;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	pr_debug("%s: active fid: %u\n", __func__, fid);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return fid;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/*
9962306a36Sopenharmony_ci * Determine the mdiv (post divider) based on the frequency ID being used.
10062306a36Sopenharmony_ci * There are 4 sources that can be used to derive the output clock rate:
10162306a36Sopenharmony_ci *    - 25 MHz Crystal
10262306a36Sopenharmony_ci *    - System clock
10362306a36Sopenharmony_ci *    - PLL channel 0 (slow clock)
10462306a36Sopenharmony_ci *    - PLL channel 1 (fast clock)
10562306a36Sopenharmony_ci */
10662306a36Sopenharmony_cistatic int __get_mdiv(struct iproc_arm_pll *pll)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	unsigned int fid;
10962306a36Sopenharmony_ci	int mdiv;
11062306a36Sopenharmony_ci	u32 val;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	fid = __get_fid(pll);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	switch (fid) {
11562306a36Sopenharmony_ci	case ARM_PLL_FID_CRYSTAL_CLK:
11662306a36Sopenharmony_ci	case ARM_PLL_FID_SYS_CLK:
11762306a36Sopenharmony_ci		mdiv = 1;
11862306a36Sopenharmony_ci		break;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	case ARM_PLL_FID_CH0_SLOW_CLK:
12162306a36Sopenharmony_ci		val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET);
12262306a36Sopenharmony_ci		mdiv = val & IPROC_CLK_PLLARMC_MDIV_MASK;
12362306a36Sopenharmony_ci		if (mdiv == 0)
12462306a36Sopenharmony_ci			mdiv = 256;
12562306a36Sopenharmony_ci		break;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	case ARM_PLL_FID_CH1_FAST_CLK:
12862306a36Sopenharmony_ci		val = readl(pll->base +	IPROC_CLK_PLLARMCTL5_OFFSET);
12962306a36Sopenharmony_ci		mdiv = val & IPROC_CLK_PLLARMCTL5_H_MDIV_MASK;
13062306a36Sopenharmony_ci		if (mdiv == 0)
13162306a36Sopenharmony_ci			mdiv = 256;
13262306a36Sopenharmony_ci		break;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	default:
13562306a36Sopenharmony_ci		mdiv = -EFAULT;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	return mdiv;
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic unsigned int __get_ndiv(struct iproc_arm_pll *pll)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	u32 val;
14462306a36Sopenharmony_ci	unsigned int ndiv_int, ndiv_frac, ndiv;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	val = readl(pll->base + IPROC_CLK_PLLARM_OFFSET_OFFSET);
14762306a36Sopenharmony_ci	if (val & (1 << IPROC_CLK_PLLARM_SW_CTL_SHIFT)) {
14862306a36Sopenharmony_ci		/*
14962306a36Sopenharmony_ci		 * offset mode is active. Read the ndiv from the PLLARM OFFSET
15062306a36Sopenharmony_ci		 * register
15162306a36Sopenharmony_ci		 */
15262306a36Sopenharmony_ci		ndiv_int = (val >> IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT) &
15362306a36Sopenharmony_ci			IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK;
15462306a36Sopenharmony_ci		if (ndiv_int == 0)
15562306a36Sopenharmony_ci			ndiv_int = 256;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci		ndiv_frac = val & IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK;
15862306a36Sopenharmony_ci	} else {
15962306a36Sopenharmony_ci		/* offset mode not active */
16062306a36Sopenharmony_ci		val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET);
16162306a36Sopenharmony_ci		ndiv_int = (val >> IPROC_CLK_PLLARMA_NDIV_INT_SHIFT) &
16262306a36Sopenharmony_ci			IPROC_CLK_PLLARMA_NDIV_INT_MASK;
16362306a36Sopenharmony_ci		if (ndiv_int == 0)
16462306a36Sopenharmony_ci			ndiv_int = 1024;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci		val = readl(pll->base + IPROC_CLK_PLLARMB_OFFSET);
16762306a36Sopenharmony_ci		ndiv_frac = val & IPROC_CLK_PLLARMB_NDIV_FRAC_MASK;
16862306a36Sopenharmony_ci	}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	ndiv = (ndiv_int << 20) | ndiv_frac;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	return ndiv;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci/*
17662306a36Sopenharmony_ci * The output frequency of the ARM PLL is calculated based on the ARM PLL
17762306a36Sopenharmony_ci * divider values:
17862306a36Sopenharmony_ci *   pdiv = ARM PLL pre-divider
17962306a36Sopenharmony_ci *   ndiv = ARM PLL multiplier
18062306a36Sopenharmony_ci *   mdiv = ARM PLL post divider
18162306a36Sopenharmony_ci *
18262306a36Sopenharmony_ci * The frequency is calculated by:
18362306a36Sopenharmony_ci *   ((ndiv * parent clock rate) / pdiv) / mdiv
18462306a36Sopenharmony_ci */
18562306a36Sopenharmony_cistatic unsigned long iproc_arm_pll_recalc_rate(struct clk_hw *hw,
18662306a36Sopenharmony_ci		unsigned long parent_rate)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct iproc_arm_pll *pll = to_iproc_arm_pll(hw);
18962306a36Sopenharmony_ci	u32 val;
19062306a36Sopenharmony_ci	int mdiv;
19162306a36Sopenharmony_ci	u64 ndiv;
19262306a36Sopenharmony_ci	unsigned int pdiv;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	/* in bypass mode, use parent rate */
19562306a36Sopenharmony_ci	val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET);
19662306a36Sopenharmony_ci	if (val & (1 << IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT)) {
19762306a36Sopenharmony_ci		pll->rate = parent_rate;
19862306a36Sopenharmony_ci		return pll->rate;
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	/* PLL needs to be locked */
20262306a36Sopenharmony_ci	val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET);
20362306a36Sopenharmony_ci	if (!(val & (1 << IPROC_CLK_PLLARMA_LOCK_SHIFT))) {
20462306a36Sopenharmony_ci		pll->rate = 0;
20562306a36Sopenharmony_ci		return 0;
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	pdiv = (val >> IPROC_CLK_PLLARMA_PDIV_SHIFT) &
20962306a36Sopenharmony_ci		IPROC_CLK_PLLARMA_PDIV_MASK;
21062306a36Sopenharmony_ci	if (pdiv == 0)
21162306a36Sopenharmony_ci		pdiv = 16;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	ndiv = __get_ndiv(pll);
21462306a36Sopenharmony_ci	mdiv = __get_mdiv(pll);
21562306a36Sopenharmony_ci	if (mdiv <= 0) {
21662306a36Sopenharmony_ci		pll->rate = 0;
21762306a36Sopenharmony_ci		return 0;
21862306a36Sopenharmony_ci	}
21962306a36Sopenharmony_ci	pll->rate = (ndiv * parent_rate) >> 20;
22062306a36Sopenharmony_ci	pll->rate = (pll->rate / pdiv) / mdiv;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	pr_debug("%s: ARM PLL rate: %lu. parent rate: %lu\n", __func__,
22362306a36Sopenharmony_ci		 pll->rate, parent_rate);
22462306a36Sopenharmony_ci	pr_debug("%s: ndiv_int: %u, pdiv: %u, mdiv: %d\n", __func__,
22562306a36Sopenharmony_ci		 (unsigned int)(ndiv >> 20), pdiv, mdiv);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	return pll->rate;
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic const struct clk_ops iproc_arm_pll_ops = {
23162306a36Sopenharmony_ci	.recalc_rate = iproc_arm_pll_recalc_rate,
23262306a36Sopenharmony_ci};
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_civoid __init iproc_armpll_setup(struct device_node *node)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	int ret;
23762306a36Sopenharmony_ci	struct iproc_arm_pll *pll;
23862306a36Sopenharmony_ci	struct clk_init_data init;
23962306a36Sopenharmony_ci	const char *parent_name;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
24262306a36Sopenharmony_ci	if (WARN_ON(!pll))
24362306a36Sopenharmony_ci		return;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	pll->base = of_iomap(node, 0);
24662306a36Sopenharmony_ci	if (WARN_ON(!pll->base))
24762306a36Sopenharmony_ci		goto err_free_pll;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	init.name = node->name;
25062306a36Sopenharmony_ci	init.ops = &iproc_arm_pll_ops;
25162306a36Sopenharmony_ci	init.flags = 0;
25262306a36Sopenharmony_ci	parent_name = of_clk_get_parent_name(node, 0);
25362306a36Sopenharmony_ci	init.parent_names = (parent_name ? &parent_name : NULL);
25462306a36Sopenharmony_ci	init.num_parents = (parent_name ? 1 : 0);
25562306a36Sopenharmony_ci	pll->hw.init = &init;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	ret = clk_hw_register(NULL, &pll->hw);
25862306a36Sopenharmony_ci	if (WARN_ON(ret))
25962306a36Sopenharmony_ci		goto err_iounmap;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &pll->hw);
26262306a36Sopenharmony_ci	if (WARN_ON(ret))
26362306a36Sopenharmony_ci		goto err_clk_unregister;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	return;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cierr_clk_unregister:
26862306a36Sopenharmony_ci	clk_hw_unregister(&pll->hw);
26962306a36Sopenharmony_cierr_iounmap:
27062306a36Sopenharmony_ci	iounmap(pll->base);
27162306a36Sopenharmony_cierr_free_pll:
27262306a36Sopenharmony_ci	kfree(pll);
27362306a36Sopenharmony_ci}
274