File Index
Files and directories
Sign in
FILE

clk-pll.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/meson/clk-pll.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Copyright (c) 2015 Endless Mobile, Inc.
  4 * Author: Carlo Caione <carlo@endlessm.com>
  5 *
  6 * Copyright (c) 2018 Baylibre, SAS.
  7 * Author: Jerome Brunet <jbrunet@baylibre.com>
  8 */
  9
 10/*
 11 * In the most basic form, a Meson PLL is composed as follows:
 12 *
 13 *                     PLL
 14 *        +--------------------------------+
 15 *        |                                |
 16 *        |             +--+               |
 17 *  in >>-----[ /N ]--->|  |      +-----+  |
 18 *        |             |  |------| DCO |---->> out
 19 *        |  +--------->|  |      +--v--+  |
 20 *        |  |          +--+         |     |
 21 *        |  |                       |     |
 22 *        |  +--[ *(M + (F/Fmax) ]<--+     |
 23 *        |                                |
 24 *        +--------------------------------+
 25 *
 26 * out = in * (m + frac / frac_max) / n
 27 */
 28
 29#include <linux/clk-provider.h>
 30#include <linux/delay.h>
 31#include <linux/err.h>
 32#include <linux/io.h>
 33#include <linux/math64.h>
 34#include <linux/module.h>
 35
 36#include "clk-regmap.h"
 37#include "clk-pll.h"
 38
 39static inline struct meson_clk_pll_data *
 40meson_clk_pll_data(struct clk_regmap *clk)
 41{
 42	return (struct meson_clk_pll_data *)clk->data;
 43}
 44
 45static int __pll_round_closest_mult(struct meson_clk_pll_data *pll)
 46{
 47	if ((pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) &&
 48	    !MESON_PARM_APPLICABLE(&pll->frac))
 49		return 1;
 50
 51	return 0;
 52}
 53
 54static unsigned long __pll_params_to_rate(unsigned long parent_rate,
 55					  unsigned int m, unsigned int n,
 56					  unsigned int frac,
 57					  struct meson_clk_pll_data *pll)
 58{
 59	u64 rate = (u64)parent_rate * m;
 60	unsigned int frac_max = pll->frac_max ? pll->frac_max :
 61						(1 << pll->frac.width);
 62
 63	if (frac && MESON_PARM_APPLICABLE(&pll->frac)) {
 64		u64 frac_rate = (u64)parent_rate * frac;
 65
 66		rate += DIV_ROUND_UP_ULL(frac_rate, frac_max);
 67	}
 68
 69	return DIV_ROUND_UP_ULL(rate, n);
 70}
 71
 72static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
 73						unsigned long parent_rate)
 74{
 75	struct clk_regmap *clk = to_clk_regmap(hw);
 76	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
 77	unsigned int m, n, frac;
 78
 79	n = meson_parm_read(clk->map, &pll->n);
 80
 81	/*
 82	 * On some HW, N is set to zero on init. This value is invalid as
 83	 * it would result in a division by zero. The rate can't be
 84	 * calculated in this case
 85	 */
 86	if (n == 0)
 87		return 0;
 88
 89	m = meson_parm_read(clk->map, &pll->m);
 90
 91	frac = MESON_PARM_APPLICABLE(&pll->frac) ?
 92		meson_parm_read(clk->map, &pll->frac) :
 93		0;
 94
 95	return __pll_params_to_rate(parent_rate, m, n, frac, pll);
 96}
 97
 98static unsigned int __pll_params_with_frac(unsigned long rate,
 99					   unsigned long parent_rate,
100					   unsigned int m,
101					   unsigned int n,
102					   struct meson_clk_pll_data *pll)
103{
104	unsigned int frac_max = pll->frac_max ? pll->frac_max :
105						(1 << pll->frac.width);
106	u64 val = (u64)rate * n;
107
108	/* Bail out if we are already over the requested rate */
109	if (rate < parent_rate * m / n)
110		return 0;
111
112	if (pll->flags & CLK_MESON_PLL_ROUND_CLOSEST)
113		val = DIV_ROUND_CLOSEST_ULL(val * frac_max, parent_rate);
114	else
115		val = div_u64(val * frac_max, parent_rate);
116
117	val -= m * frac_max;
118
119	return min((unsigned int)val, (frac_max - 1));
120}
121
122static bool meson_clk_pll_is_better(unsigned long rate,
123				    unsigned long best,
124				    unsigned long now,
125				    struct meson_clk_pll_data *pll)
126{
127	if (__pll_round_closest_mult(pll)) {
128		/* Round Closest */
129		if (abs(now - rate) < abs(best - rate))
130			return true;
131	} else {
132		/* Round down */
133		if (now <= rate && best < now)
134			return true;
135	}
136
137	return false;
138}
139
140static int meson_clk_get_pll_table_index(unsigned int index,
141					 unsigned int *m,
142					 unsigned int *n,
143					 struct meson_clk_pll_data *pll)
144{
145	if (!pll->table[index].n)
146		return -EINVAL;
147
148	*m = pll->table[index].m;
149	*n = pll->table[index].n;
150
151	return 0;
152}
153
154static unsigned int meson_clk_get_pll_range_m(unsigned long rate,
155					      unsigned long parent_rate,
156					      unsigned int n,
157					      struct meson_clk_pll_data *pll)
158{
159	u64 val = (u64)rate * n;
160
161	if (__pll_round_closest_mult(pll))
162		return DIV_ROUND_CLOSEST_ULL(val, parent_rate);
163
164	return div_u64(val,  parent_rate);
165}
166
167static int meson_clk_get_pll_range_index(unsigned long rate,
168					 unsigned long parent_rate,
169					 unsigned int index,
170					 unsigned int *m,
171					 unsigned int *n,
172					 struct meson_clk_pll_data *pll)
173{
174	*n = index + 1;
175
176	/* Check the predivider range */
177	if (*n >= (1 << pll->n.width))
178		return -EINVAL;
179
180	if (*n == 1) {
181		/* Get the boundaries out the way */
182		if (rate <= pll->range->min * parent_rate) {
183			*m = pll->range->min;
184			return -ENODATA;
185		} else if (rate >= pll->range->max * parent_rate) {
186			*m = pll->range->max;
187			return -ENODATA;
188		}
189	}
190
191	*m = meson_clk_get_pll_range_m(rate, parent_rate, *n, pll);
192
193	/* the pre-divider gives a multiplier too big - stop */
194	if (*m >= (1 << pll->m.width))
195		return -EINVAL;
196
197	return 0;
198}
199
200static int meson_clk_get_pll_get_index(unsigned long rate,
201				       unsigned long parent_rate,
202				       unsigned int index,
203				       unsigned int *m,
204				       unsigned int *n,
205				       struct meson_clk_pll_data *pll)
206{
207	if (pll->range)
208		return meson_clk_get_pll_range_index(rate, parent_rate,
209						     index, m, n, pll);
210	else if (pll->table)
211		return meson_clk_get_pll_table_index(index, m, n, pll);
212
213	return -EINVAL;
214}
215
216static int meson_clk_get_pll_settings(unsigned long rate,
217				      unsigned long parent_rate,
218				      unsigned int *best_m,
219				      unsigned int *best_n,
220				      struct meson_clk_pll_data *pll)
221{
222	unsigned long best = 0, now = 0;
223	unsigned int i, m, n;
224	int ret;
225
226	for (i = 0, ret = 0; !ret; i++) {
227		ret = meson_clk_get_pll_get_index(rate, parent_rate,
228						  i, &m, &n, pll);
229		if (ret == -EINVAL)
230			break;
231
232		now = __pll_params_to_rate(parent_rate, m, n, 0, pll);
233		if (meson_clk_pll_is_better(rate, best, now, pll)) {
234			best = now;
235			*best_m = m;
236			*best_n = n;
237
238			if (now == rate)
239				break;
240		}
241	}
242
243	return best ? 0 : -EINVAL;
244}
245
246static int meson_clk_pll_determine_rate(struct clk_hw *hw,
247					struct clk_rate_request *req)
248{
249	struct clk_regmap *clk = to_clk_regmap(hw);
250	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
251	unsigned int m, n, frac;
252	unsigned long round;
253	int ret;
254
255	ret = meson_clk_get_pll_settings(req->rate, req->best_parent_rate,
256					 &m, &n, pll);
257	if (ret)
258		return ret;
259
260	round = __pll_params_to_rate(req->best_parent_rate, m, n, 0, pll);
261
262	if (!MESON_PARM_APPLICABLE(&pll->frac) || req->rate == round) {
263		req->rate = round;
264		return 0;
265	}
266
267	/*
268	 * The rate provided by the setting is not an exact match, let's
269	 * try to improve the result using the fractional parameter
270	 */
271	frac = __pll_params_with_frac(req->rate, req->best_parent_rate, m, n, pll);
272	req->rate = __pll_params_to_rate(req->best_parent_rate, m, n, frac, pll);
273
274	return 0;
275}
276
277static int meson_clk_pll_wait_lock(struct clk_hw *hw)
278{
279	struct clk_regmap *clk = to_clk_regmap(hw);
280	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
281	int delay = 5000;
282
283	do {
284		/* Is the clock locked now ? Time out after 100ms. */
285		if (meson_parm_read(clk->map, &pll->l))
286			return 0;
287
288		udelay(20);
289	} while (--delay);
290
291	return -ETIMEDOUT;
292}
293
294static int meson_clk_pll_is_enabled(struct clk_hw *hw)
295{
296	struct clk_regmap *clk = to_clk_regmap(hw);
297	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
298
299	if (MESON_PARM_APPLICABLE(&pll->rst) &&
300	    meson_parm_read(clk->map, &pll->rst))
301		return 0;
302
303	if (!meson_parm_read(clk->map, &pll->en) ||
304	    !meson_parm_read(clk->map, &pll->l))
305		return 0;
306
307	return 1;
308}
309
310static int meson_clk_pll_init(struct clk_hw *hw)
311{
312	struct clk_regmap *clk = to_clk_regmap(hw);
313	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
314	int ret;
315
316	ret = clk_regmap_init(hw);
317	if (ret)
318		return ret;
319
320	/*
321	 * Keep the clock running, which was already initialized and enabled
322	 * from the bootloader stage, to avoid any glitches.
323	 */
324	if ((pll->flags & CLK_MESON_PLL_NOINIT_ENABLED) &&
325	    meson_clk_pll_is_enabled(hw))
326		return 0;
327
328	if (pll->init_count) {
329		if (MESON_PARM_APPLICABLE(&pll->rst))
330			meson_parm_write(clk->map, &pll->rst, 1);
331
332		regmap_multi_reg_write(clk->map, pll->init_regs,
333				       pll->init_count);
334
335		if (MESON_PARM_APPLICABLE(&pll->rst))
336			meson_parm_write(clk->map, &pll->rst, 0);
337	}
338
339	return 0;
340}
341
342static int meson_clk_pcie_pll_enable(struct clk_hw *hw)
343{
344	int retries = 10;
345
346	do {
347		meson_clk_pll_init(hw);
348		if (!meson_clk_pll_wait_lock(hw))
349			return 0;
350		pr_info("Retry enabling PCIe PLL clock\n");
351	} while (--retries);
352
353	return -EIO;
354}
355
356static int meson_clk_pll_enable(struct clk_hw *hw)
357{
358	struct clk_regmap *clk = to_clk_regmap(hw);
359	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
360
361	/* do nothing if the PLL is already enabled */
362	if (clk_hw_is_enabled(hw))
363		return 0;
364
365	/* Make sure the pll is in reset */
366	if (MESON_PARM_APPLICABLE(&pll->rst))
367		meson_parm_write(clk->map, &pll->rst, 1);
368
369	/* Enable the pll */
370	meson_parm_write(clk->map, &pll->en, 1);
371
372	/* Take the pll out reset */
373	if (MESON_PARM_APPLICABLE(&pll->rst))
374		meson_parm_write(clk->map, &pll->rst, 0);
375
376	/*
377	 * Compared with the previous SoCs, self-adaption current module
378	 * is newly added for A1, keep the new power-on sequence to enable the
379	 * PLL. The sequence is:
380	 * 1. enable the pll, delay for 10us
381	 * 2. enable the pll self-adaption current module, delay for 40us
382	 * 3. enable the lock detect module
383	 */
384	if (MESON_PARM_APPLICABLE(&pll->current_en)) {
385		udelay(10);
386		meson_parm_write(clk->map, &pll->current_en, 1);
387		udelay(40);
388	}
389
390	if (MESON_PARM_APPLICABLE(&pll->l_detect)) {
391		meson_parm_write(clk->map, &pll->l_detect, 1);
392		meson_parm_write(clk->map, &pll->l_detect, 0);
393	}
394
395	if (meson_clk_pll_wait_lock(hw))
396		return -EIO;
397
398	return 0;
399}
400
401static void meson_clk_pll_disable(struct clk_hw *hw)
402{
403	struct clk_regmap *clk = to_clk_regmap(hw);
404	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
405
406	/* Put the pll is in reset */
407	if (MESON_PARM_APPLICABLE(&pll->rst))
408		meson_parm_write(clk->map, &pll->rst, 1);
409
410	/* Disable the pll */
411	meson_parm_write(clk->map, &pll->en, 0);
412
413	/* Disable PLL internal self-adaption current module */
414	if (MESON_PARM_APPLICABLE(&pll->current_en))
415		meson_parm_write(clk->map, &pll->current_en, 0);
416}
417
418static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
419				  unsigned long parent_rate)
420{
421	struct clk_regmap *clk = to_clk_regmap(hw);
422	struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
423	unsigned int enabled, m, n, frac = 0;
424	unsigned long old_rate;
425	int ret;
426
427	if (parent_rate == 0 || rate == 0)
428		return -EINVAL;
429
430	old_rate = clk_hw_get_rate(hw);
431
432	ret = meson_clk_get_pll_settings(rate, parent_rate, &m, &n, pll);
433	if (ret)
434		return ret;
435
436	enabled = meson_parm_read(clk->map, &pll->en);
437	if (enabled)
438		meson_clk_pll_disable(hw);
439
440	meson_parm_write(clk->map, &pll->n, n);
441	meson_parm_write(clk->map, &pll->m, m);
442
443	if (MESON_PARM_APPLICABLE(&pll->frac)) {
444		frac = __pll_params_with_frac(rate, parent_rate, m, n, pll);
445		meson_parm_write(clk->map, &pll->frac, frac);
446	}
447
448	/* If the pll is stopped, bail out now */
449	if (!enabled)
450		return 0;
451
452	ret = meson_clk_pll_enable(hw);
453	if (ret) {
454		pr_warn("%s: pll %s didn't lock, trying to set old rate %lu\n",
455			__func__, clk_hw_get_name(hw), old_rate);
456		/*
457		 * FIXME: Do we really need/want this HACK ?
458		 * It looks unsafe. what happens if the clock gets into a
459		 * broken state and we can't lock back on the old_rate ? Looks
460		 * like an infinite recursion is possible
461		 */
462		meson_clk_pll_set_rate(hw, old_rate, parent_rate);
463	}
464
465	return ret;
466}
467
468/*
469 * The Meson G12A PCIE PLL is fined tuned to deliver a very precise
470 * 100MHz reference clock for the PCIe Analog PHY, and thus requires
471 * a strict register sequence to enable the PLL.
472 * To simplify, re-use the _init() op to enable the PLL and keep
473 * the other ops except set_rate since the rate is fixed.
474 */
475const struct clk_ops meson_clk_pcie_pll_ops = {
476	.init		= clk_regmap_init,
477	.recalc_rate	= meson_clk_pll_recalc_rate,
478	.determine_rate	= meson_clk_pll_determine_rate,
479	.is_enabled	= meson_clk_pll_is_enabled,
480	.enable		= meson_clk_pcie_pll_enable,
481	.disable	= meson_clk_pll_disable
482};
483EXPORT_SYMBOL_NS_GPL(meson_clk_pcie_pll_ops, "CLK_MESON");
484
485const struct clk_ops meson_clk_pll_ops = {
486	.init		= meson_clk_pll_init,
487	.recalc_rate	= meson_clk_pll_recalc_rate,
488	.determine_rate	= meson_clk_pll_determine_rate,
489	.set_rate	= meson_clk_pll_set_rate,
490	.is_enabled	= meson_clk_pll_is_enabled,
491	.enable		= meson_clk_pll_enable,
492	.disable	= meson_clk_pll_disable
493};
494EXPORT_SYMBOL_NS_GPL(meson_clk_pll_ops, "CLK_MESON");
495
496const struct clk_ops meson_clk_pll_ro_ops = {
497	.init		= clk_regmap_init,
498	.recalc_rate	= meson_clk_pll_recalc_rate,
499	.is_enabled	= meson_clk_pll_is_enabled,
500};
501EXPORT_SYMBOL_NS_GPL(meson_clk_pll_ro_ops, "CLK_MESON");
502
503MODULE_DESCRIPTION("Amlogic PLL driver");
504MODULE_AUTHOR("Carlo Caione <carlo@endlessm.com>");
505MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
506MODULE_LICENSE("GPL");
507MODULE_IMPORT_NS("CLK_MESON");
~/ Read-only file preview.