Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Dong Aisheng 717 100.00% 1 100.00%
Total 717 1


// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2016 Freescale Semiconductor, Inc.
 * Copyright 2017~2018 NXP
 *
 * Author: Dong Aisheng <aisheng.dong@nxp.com>
 *
 */

#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/slab.h>

#include "clk.h"

/* PLL Control Status Register (xPLLCSR) */
#define PLL_CSR_OFFSET		0x0
#define PLL_VLD			BIT(24)
#define PLL_EN			BIT(0)

/* PLL Configuration Register (xPLLCFG) */
#define PLL_CFG_OFFSET		0x08
#define BP_PLL_MULT		16
#define BM_PLL_MULT		(0x7f << 16)

/* PLL Numerator Register (xPLLNUM) */
#define PLL_NUM_OFFSET		0x10

/* PLL Denominator Register (xPLLDENOM) */
#define PLL_DENOM_OFFSET	0x14

struct clk_pllv4 {
	struct clk_hw	hw;
	void __iomem	*base;
};

/* Valid PLL MULT Table */
static const int pllv4_mult_table[] = {33, 27, 22, 20, 17, 16};

#define to_clk_pllv4(__hw) container_of(__hw, struct clk_pllv4, hw)

#define LOCK_TIMEOUT_US		USEC_PER_MSEC

static inline int clk_pllv4_wait_lock(struct clk_pllv4 *pll)
{
	u32 csr;

	return readl_poll_timeout(pll->base  + PLL_CSR_OFFSET,
				  csr, csr & PLL_VLD, 0, LOCK_TIMEOUT_US);
}

static int clk_pllv4_is_enabled(struct clk_hw *hw)
{
	struct clk_pllv4 *pll = to_clk_pllv4(hw);

	if (readl_relaxed(pll->base) & PLL_EN)
		return 1;

	return 0;
}

static unsigned long clk_pllv4_recalc_rate(struct clk_hw *hw,
					   unsigned long parent_rate)
{
	struct clk_pllv4 *pll = to_clk_pllv4(hw);
	u32 div;

	div = readl_relaxed(pll->base + PLL_CFG_OFFSET);
	div &= BM_PLL_MULT;
	div >>= BP_PLL_MULT;

	return parent_rate * div;
}

static long clk_pllv4_round_rate(struct clk_hw *hw, unsigned long rate,
				 unsigned long *prate)
{
	unsigned long parent_rate = *prate;
	unsigned long round_rate, i;

	for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) {
		round_rate = parent_rate * pllv4_mult_table[i];
		if (rate >= round_rate)
			return round_rate;
	}

	return round_rate;
}

static bool clk_pllv4_is_valid_mult(unsigned int mult)
{
	int i;

	/* check if mult is in valid MULT table */
	for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) {
		if (pllv4_mult_table[i] == mult)
			return true;
	}

	return false;
}

static int clk_pllv4_set_rate(struct clk_hw *hw, unsigned long rate,
			      unsigned long parent_rate)
{
	struct clk_pllv4 *pll = to_clk_pllv4(hw);
	u32 val, mult;

	mult = rate / parent_rate;

	if (!clk_pllv4_is_valid_mult(mult))
		return -EINVAL;

	val = readl_relaxed(pll->base + PLL_CFG_OFFSET);
	val &= ~BM_PLL_MULT;
	val |= mult << BP_PLL_MULT;
	writel_relaxed(val, pll->base + PLL_CFG_OFFSET);

	return 0;
}

static int clk_pllv4_enable(struct clk_hw *hw)
{
	u32 val;
	struct clk_pllv4 *pll = to_clk_pllv4(hw);

	val = readl_relaxed(pll->base);
	val |= PLL_EN;
	writel_relaxed(val, pll->base);

	return clk_pllv4_wait_lock(pll);
}

static void clk_pllv4_disable(struct clk_hw *hw)
{
	u32 val;
	struct clk_pllv4 *pll = to_clk_pllv4(hw);

	val = readl_relaxed(pll->base);
	val &= ~PLL_EN;
	writel_relaxed(val, pll->base);
}

static const struct clk_ops clk_pllv4_ops = {
	.recalc_rate	= clk_pllv4_recalc_rate,
	.round_rate	= clk_pllv4_round_rate,
	.set_rate	= clk_pllv4_set_rate,
	.enable		= clk_pllv4_enable,
	.disable	= clk_pllv4_disable,
	.is_enabled	= clk_pllv4_is_enabled,
};

struct clk_hw *imx_clk_pllv4(const char *name, const char *parent_name,
			  void __iomem *base)
{
	struct clk_pllv4 *pll;
	struct clk_hw *hw;
	struct clk_init_data init;
	int ret;

	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
	if (!pll)
		return ERR_PTR(-ENOMEM);

	pll->base = base;

	init.name = name;
	init.ops = &clk_pllv4_ops;
	init.parent_names = &parent_name;
	init.num_parents = 1;
	init.flags = CLK_SET_RATE_GATE;

	pll->hw.init = &init;

	hw = &pll->hw;
	ret = clk_hw_register(NULL, hw);
	if (ret) {
		kfree(pll);
		hw = ERR_PTR(ret);
	}

	return hw;
}