File Index
Files and directories
Sign in
FILE

clk-pllv4.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/imx/clk-pllv4.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Copyright (C) 2016 Freescale Semiconductor, Inc.
  4 * Copyright 2017~2018 NXP
  5 *
  6 * Author: Dong Aisheng <aisheng.dong@nxp.com>
  7 *
  8 */
  9
 10#include <linux/bits.h>
 11#include <linux/clk-provider.h>
 12#include <linux/err.h>
 13#include <linux/io.h>
 14#include <linux/iopoll.h>
 15#include <linux/slab.h>
 16
 17#include "clk.h"
 18
 19/* PLL Control Status Register (xPLLCSR) */
 20#define PLL_CSR_OFFSET		0x0
 21#define PLL_VLD			BIT(24)
 22#define PLL_EN			BIT(0)
 23
 24/* PLL Configuration Register (xPLLCFG) */
 25#define PLL_CFG_OFFSET		0x08
 26#define IMX8ULP_PLL_CFG_OFFSET	0x10
 27#define BP_PLL_MULT		16
 28#define BM_PLL_MULT		(0x7f << 16)
 29
 30/* PLL Numerator Register (xPLLNUM) */
 31#define PLL_NUM_OFFSET		0x10
 32#define IMX8ULP_PLL_NUM_OFFSET	0x1c
 33
 34/* PLL Denominator Register (xPLLDENOM) */
 35#define PLL_DENOM_OFFSET	0x14
 36#define IMX8ULP_PLL_DENOM_OFFSET	0x18
 37
 38#define MAX_MFD			0x3fffffff
 39#define DEFAULT_MFD		1000000
 40
 41struct clk_pllv4 {
 42	struct clk_hw	hw;
 43	void __iomem	*base;
 44	u32		cfg_offset;
 45	u32		num_offset;
 46	u32		denom_offset;
 47	bool		use_mult_range;
 48};
 49
 50/* Valid PLL MULT Table */
 51static const int pllv4_mult_table[] = {33, 27, 22, 20, 17, 16};
 52
 53/* Valid PLL MULT range, (max, min) */
 54static const int pllv4_mult_range[] = {54, 27};
 55
 56#define to_clk_pllv4(__hw) container_of(__hw, struct clk_pllv4, hw)
 57
 58#define LOCK_TIMEOUT_US		USEC_PER_MSEC
 59
 60static inline int clk_pllv4_wait_lock(struct clk_pllv4 *pll)
 61{
 62	u32 csr;
 63
 64	return readl_poll_timeout(pll->base  + PLL_CSR_OFFSET,
 65				  csr, csr & PLL_VLD, 0, LOCK_TIMEOUT_US);
 66}
 67
 68static int clk_pllv4_is_prepared(struct clk_hw *hw)
 69{
 70	struct clk_pllv4 *pll = to_clk_pllv4(hw);
 71
 72	if (readl_relaxed(pll->base) & PLL_EN)
 73		return 1;
 74
 75	return 0;
 76}
 77
 78static unsigned long clk_pllv4_recalc_rate(struct clk_hw *hw,
 79					   unsigned long parent_rate)
 80{
 81	struct clk_pllv4 *pll = to_clk_pllv4(hw);
 82	u32 mult, mfn, mfd;
 83	u64 temp64;
 84
 85	mult = readl_relaxed(pll->base + pll->cfg_offset);
 86	mult &= BM_PLL_MULT;
 87	mult >>= BP_PLL_MULT;
 88
 89	mfn = readl_relaxed(pll->base + pll->num_offset);
 90	mfd = readl_relaxed(pll->base + pll->denom_offset);
 91	temp64 = parent_rate;
 92	temp64 *= mfn;
 93	do_div(temp64, mfd);
 94
 95	return (parent_rate * mult) + (u32)temp64;
 96}
 97
 98static int clk_pllv4_determine_rate(struct clk_hw *hw,
 99				    struct clk_rate_request *req)
100{
101	struct clk_pllv4 *pll = to_clk_pllv4(hw);
102	unsigned long parent_rate = req->best_parent_rate;
103	unsigned long round_rate, i;
104	u32 mfn, mfd = DEFAULT_MFD;
105	bool found = false;
106	u64 temp64;
107	u32 mult;
108
109	if (pll->use_mult_range) {
110		temp64 = (u64) req->rate;
111		do_div(temp64, parent_rate);
112		mult = temp64;
113		if (mult >= pllv4_mult_range[1] &&
114		    mult <= pllv4_mult_range[0]) {
115			round_rate = parent_rate * mult;
116			found = true;
117		}
118	} else {
119		for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) {
120			round_rate = parent_rate * pllv4_mult_table[i];
121			if (req->rate >= round_rate) {
122				found = true;
123				break;
124			}
125		}
126	}
127
128	if (!found) {
129		pr_warn("%s: unable to round rate %lu, parent rate %lu\n",
130			clk_hw_get_name(hw), req->rate, parent_rate);
131		req->rate = 0;
132
133		return 0;
134	}
135
136	if (parent_rate <= MAX_MFD)
137		mfd = parent_rate;
138
139	temp64 = (u64)(req->rate - round_rate);
140	temp64 *= mfd;
141	do_div(temp64, parent_rate);
142	mfn = temp64;
143
144	/*
145	 * NOTE: The value of numerator must always be configured to be
146	 * less than the value of the denominator. If we can't get a proper
147	 * pair of mfn/mfd, we simply return the round_rate without using
148	 * the frac part.
149	 */
150	if (mfn >= mfd) {
151		req->rate = round_rate;
152
153		return 0;
154	}
155
156	temp64 = (u64)parent_rate;
157	temp64 *= mfn;
158	do_div(temp64, mfd);
159
160	req->rate = round_rate + (u32)temp64;
161
162	return 0;
163}
164
165static bool clk_pllv4_is_valid_mult(struct clk_pllv4 *pll, unsigned int mult)
166{
167	int i;
168
169	/* check if mult is in valid MULT table */
170	if (pll->use_mult_range) {
171		if (mult >= pllv4_mult_range[1] &&
172		    mult <= pllv4_mult_range[0])
173			return true;
174	} else {
175		for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) {
176			if (pllv4_mult_table[i] == mult)
177				return true;
178		}
179	}
180
181	return false;
182}
183
184static int clk_pllv4_set_rate(struct clk_hw *hw, unsigned long rate,
185			      unsigned long parent_rate)
186{
187	struct clk_pllv4 *pll = to_clk_pllv4(hw);
188	u32 val, mult, mfn, mfd = DEFAULT_MFD;
189	u64 temp64;
190
191	mult = rate / parent_rate;
192
193	if (!clk_pllv4_is_valid_mult(pll, mult))
194		return -EINVAL;
195
196	if (parent_rate <= MAX_MFD)
197		mfd = parent_rate;
198
199	temp64 = (u64)(rate - mult * parent_rate);
200	temp64 *= mfd;
201	do_div(temp64, parent_rate);
202	mfn = temp64;
203
204	val = readl_relaxed(pll->base + pll->cfg_offset);
205	val &= ~BM_PLL_MULT;
206	val |= mult << BP_PLL_MULT;
207	writel_relaxed(val, pll->base + pll->cfg_offset);
208
209	writel_relaxed(mfn, pll->base + pll->num_offset);
210	writel_relaxed(mfd, pll->base + pll->denom_offset);
211
212	return 0;
213}
214
215static int clk_pllv4_prepare(struct clk_hw *hw)
216{
217	u32 val;
218	struct clk_pllv4 *pll = to_clk_pllv4(hw);
219
220	val = readl_relaxed(pll->base);
221	val |= PLL_EN;
222	writel_relaxed(val, pll->base);
223
224	return clk_pllv4_wait_lock(pll);
225}
226
227static void clk_pllv4_unprepare(struct clk_hw *hw)
228{
229	u32 val;
230	struct clk_pllv4 *pll = to_clk_pllv4(hw);
231
232	val = readl_relaxed(pll->base);
233	val &= ~PLL_EN;
234	writel_relaxed(val, pll->base);
235}
236
237static const struct clk_ops clk_pllv4_ops = {
238	.recalc_rate	= clk_pllv4_recalc_rate,
239	.determine_rate = clk_pllv4_determine_rate,
240	.set_rate	= clk_pllv4_set_rate,
241	.prepare	= clk_pllv4_prepare,
242	.unprepare	= clk_pllv4_unprepare,
243	.is_prepared	= clk_pllv4_is_prepared,
244};
245
246struct clk_hw *imx_clk_hw_pllv4(enum imx_pllv4_type type, const char *name,
247		 const char *parent_name, void __iomem *base)
248{
249	struct clk_pllv4 *pll;
250	struct clk_hw *hw;
251	struct clk_init_data init;
252	int ret;
253
254	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
255	if (!pll)
256		return ERR_PTR(-ENOMEM);
257
258	pll->base = base;
259
260	if (type == IMX_PLLV4_IMX8ULP ||
261	    type == IMX_PLLV4_IMX8ULP_1GHZ) {
262		pll->cfg_offset = IMX8ULP_PLL_CFG_OFFSET;
263		pll->num_offset = IMX8ULP_PLL_NUM_OFFSET;
264		pll->denom_offset = IMX8ULP_PLL_DENOM_OFFSET;
265		if (type == IMX_PLLV4_IMX8ULP_1GHZ)
266			pll->use_mult_range = true;
267	} else {
268		pll->cfg_offset = PLL_CFG_OFFSET;
269		pll->num_offset = PLL_NUM_OFFSET;
270		pll->denom_offset = PLL_DENOM_OFFSET;
271	}
272
273	init.name = name;
274	init.ops = &clk_pllv4_ops;
275	init.parent_names = &parent_name;
276	init.num_parents = 1;
277	init.flags = CLK_SET_RATE_GATE;
278
279	pll->hw.init = &init;
280
281	hw = &pll->hw;
282	ret = clk_hw_register(NULL, hw);
283	if (ret) {
284		kfree(pll);
285		hw = ERR_PTR(ret);
286	}
287
288	return hw;
289}
290EXPORT_SYMBOL_GPL(imx_clk_hw_pllv4);
~/ Read-only file preview.