diff mbox series

[RFC,3/3] clk: imx: clk-fracn-gppll: Support dynamic rates

Message ID 20250210160012.783446-3-alexander.stein@ew.tq-group.com (mailing list archive)
State New
Headers show
Series [RFC,1/3] clk: imx: clk-fracn-gppll: Do not access num/denom register for integer PLL | expand

Commit Message

Alexander Stein Feb. 10, 2025, 4 p.m. UTC
The fracn gppll PLL so far only supports rates from a rate table passed
during initialization. Calculating PLL settings dynamically helps audio
applications to get their desired rates, so support for this is added
in this patch.

The strategy to get to the PLL setting for a rate is:

- The rate table is searched for suitable rates, so for standard rates the
  same settings are used as without this patch
- Then try to only adjust mfn, on fractional PLLs only, which specifies
  the fractional part of the PLL. This setting can be changed without
  glitches on the output and is therefore preferred
- As a last resort the best settings are calculated dynamically

Implementation is inspired by commit b09c68dc57c9d ("clk: imx: pll14xx:
Support dynamic rates")

Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
---
This is the first time I'm touching PLL code, I might be missing things
or not being aware of important aspects when it comes to PLL.
Thus this is a RFC

 drivers/clk/imx/clk-fracn-gppll.c | 203 ++++++++++++++++++++++++++----
 1 file changed, 181 insertions(+), 22 deletions(-)

Comments

Sascha Hauer Feb. 11, 2025, 7:47 a.m. UTC | #1
Hi Alexander,

On Mon, Feb 10, 2025 at 05:00:11PM +0100, Alexander Stein wrote:
> The fracn gppll PLL so far only supports rates from a rate table passed
> during initialization. Calculating PLL settings dynamically helps audio
> applications to get their desired rates, so support for this is added
> in this patch.
> 
> The strategy to get to the PLL setting for a rate is:
> 
> - The rate table is searched for suitable rates, so for standard rates the
>   same settings are used as without this patch
> - Then try to only adjust mfn, on fractional PLLs only, which specifies
>   the fractional part of the PLL. This setting can be changed without
>   glitches on the output and is therefore preferred

I wonder if this part is worth it. There might be cases in which a
glitch free switch is required, but without being able to enforce
a glitch free switch we can't rely on it.

Also this makes the result depend on the current PLL settings, so the
result is no longer reproducible. I.e. switching from a fari away
frequency to the desired frequency might yield in different settings
than switching from a nearby frequency to the desired frequency.

Finally I think the glitch free switch doesn't work currently, because
the PLL is fully disabled and re-enabled unconditionally in
clk_fracn_gppll_set_rate().

That said, glitch free switching would be great to have sometimes.

> - As a last resort the best settings are calculated dynamically
> 
> Implementation is inspired by commit b09c68dc57c9d ("clk: imx: pll14xx:
> Support dynamic rates")
> 
> Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
> ---

...

> +			if (pll->flags & CLK_FRACN_GPPLL_FRACN) {
> +				if (!dist) {
> +					/* Disable fractional part upon exact match */
> +					mfd = 1;
> +					mfn = 0;
> +				} else {
> +					mfd = 100;
> +					mfd = clamp(mfd, PLL_MFD_MIN, PLL_MFN_MAX);

With mfd = 100 this clamp looks like a no-op. Do we need this?

Sascha
Peng Fan Feb. 14, 2025, 4:45 a.m. UTC | #2
On Mon, Feb 10, 2025 at 05:00:11PM +0100, Alexander Stein wrote:
>The fracn gppll PLL so far only supports rates from a rate table passed
>during initialization. Calculating PLL settings dynamically helps audio
>applications to get their desired rates, so support for this is added
>in this patch.
>
>The strategy to get to the PLL setting for a rate is:
>
>- The rate table is searched for suitable rates, so for standard rates the
>  same settings are used as without this patch
>- Then try to only adjust mfn, on fractional PLLs only, which specifies
>  the fractional part of the PLL. This setting can be changed without
>  glitches on the output and is therefore preferred
>- As a last resort the best settings are calculated dynamically
>
>Implementation is inspired by commit b09c68dc57c9d ("clk: imx: pll14xx:
>Support dynamic rates")

Not a fan of this, we have seen issues that not able to calculate
out exact audio frequency with dynamic rates. But anyway
if your setup needs this feature, it is ok to add.

>
>Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
>---
>This is the first time I'm touching PLL code, I might be missing things
>or not being aware of important aspects when it comes to PLL.
>Thus this is a RFC
>
> drivers/clk/imx/clk-fracn-gppll.c | 203 ++++++++++++++++++++++++++----
> 1 file changed, 181 insertions(+), 22 deletions(-)
>
>diff --git a/drivers/clk/imx/clk-fracn-gppll.c b/drivers/clk/imx/clk-fracn-gppll.c
>index a7d57fbe93196..68c5b4a95efbe 100644
>--- a/drivers/clk/imx/clk-fracn-gppll.c
>+++ b/drivers/clk/imx/clk-fracn-gppll.c
>@@ -25,6 +25,12 @@
> 
> #define PLL_NUMERATOR		0x40
> #define PLL_MFN_MASK		GENMASK(31, 2)
>+#define PLL_MFI_MIN		0x2
>+#define PLL_MFI_MAX		0x1ff
>+#define PLL_MFN_MIN		0x0
>+#define PLL_MFN_MAX		0x3fffffff
>+#define PLL_MFD_MIN		0x1
>+#define PLL_MFD_MAX		0x3fffffff
> 
> #define PLL_DENOMINATOR		0x50
> #define PLL_MFD_MASK		GENMASK(29, 0)
>@@ -134,21 +140,6 @@ imx_get_pll_settings(struct clk_fracn_gppll *pll, unsigned long rate)
> 	return NULL;
> }
> 
>-static long clk_fracn_gppll_round_rate(struct clk_hw *hw, unsigned long rate,
>-				       unsigned long *prate)
>-{
>-	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
>-	const struct imx_fracn_gppll_rate_table *rate_table = pll->rate_table;
>-	int i;
>-
>-	/* Assuming rate_table is in descending order */
>-	for (i = 0; i < pll->rate_count; i++)
>-		if (rate >= rate_table[i].rate)
>-			return rate_table[i].rate;
>-
>-	/* return minimum supported value */
>-	return rate_table[pll->rate_count - 1].rate;
>-}
> 
> static long clk_fracn_gppll_calc_rate(struct clk_fracn_gppll *pll, u32 mfn,
> 				      u32 mfd, u32 mfi, u32 rdiv, u32 odiv,
>@@ -202,6 +193,174 @@ static long clk_fracn_gppll_calc_rate(struct clk_fracn_gppll *pll, u32 mfn,
> 	return (unsigned long)fvco;
> }
> 
>+static u32 clk_fracn_gppll_calc_mfi(int rdiv, unsigned long fvco,
>+				    unsigned long fref)
>+{
>+	u32 mfi;
>+
>+	/* fvco = fref / rdiv * mfi */
>+	mfi = DIV_ROUND_CLOSEST(fvco * rdiv, fref);
>+	return clamp_t(u32, mfi, PLL_MFI_MIN, PLL_MFI_MAX);
>+}
>+
>+static long clk_fracn_gppll_calc_mfn(int mfd, int mfi, int odiv, int rdiv,
>+				     unsigned long rate, unsigned long prate)
>+{
>+	unsigned long tmp;
>+	long mfn;
>+
>+	/* calc mfn = ((rate * odiv * rdiv / prate) - mfi) * mfd */
>+	tmp = rate * odiv * rdiv - (mfi * prate);
>+	mfn = DIV_ROUND_CLOSEST(tmp * mfd, prate);
>+	return clamp_t(long, mfn, PLL_MFN_MIN, PLL_MFN_MAX);
>+}
>+
>+static void clk_fracn_gppll_calc_settings(struct clk_fracn_gppll *pll, unsigned long rate,
>+					  unsigned long prate, struct imx_fracn_gppll_rate_table *t)
>+{
>+	u32 pll_denominator, pll_numerator, pll_div;
>+	u32 mfi, mfn, mfd, rdiv, odiv;
>+	long fout, rate_min, rate_max, dist, best = LONG_MAX;
>+	const struct imx_fracn_gppll_rate_table *tt;
>+
>+	/*
>+	 * PLL constrains:
>+	 *
>+	 * a) 1 <= MFN <= 0x3fffffff (fractional only)
>+	 * b) 1 <= MFD <= 0x3fffffff (fractional only)
>+	 * c) 2 <= MFI <= 0x1ff
>+	 * d) 1 <= RDIV <= 7
>+	 * e) 2 <= ODIV <= 255
>+	 * f) -2 <= MFN/MFD <= 2
>+	 * g) 20MHz <= (Fref / rdiv) <= 40MHz
>+	 * h) 2.5GHz <= Fvco <= 5Ghz
>+	 *
>+	 * Fvco = (Fref / rdiv) * (MFI + MFN / MFD)
>+	 * Fout = Fvco / odiv
>+	 */
>+
>+	/* First try if we can get the desired rate from one of the static entries */
>+	tt = imx_get_pll_settings(pll, rate);
>+	if (tt) {
>+		pr_debug("%s: in=%ld, want=%ld, Using PLL setting from table\n",
>+			 clk_hw_get_name(&pll->hw), prate, rate);
>+		t->rate = tt->rate;
>+		t->mfi = tt->mfi;
>+		t->mfn = tt->mfn;
>+		t->mfd = tt->mfd;
>+		t->rdiv = tt->rdiv;
>+		t->odiv = tt->odiv;
>+		return;
>+	}
>+
>+	/* glitch free MFN adjustment only for fractional PLL */
>+	if (pll->flags & CLK_FRACN_GPPLL_FRACN) {
>+		pll_numerator = readl_relaxed(pll->base + PLL_NUMERATOR);
>+
>+		pll_denominator = readl_relaxed(pll->base + PLL_DENOMINATOR);
>+		mfd = FIELD_GET(PLL_MFD_MASK, pll_denominator);
>+
>+		pll_div = readl_relaxed(pll->base + PLL_DIV);
>+		mfi = FIELD_GET(PLL_MFI_MASK, pll_div);
>+		rdiv = FIELD_GET(PLL_RDIV_MASK, pll_div);
>+		odiv = FIELD_GET(PLL_ODIV_MASK, pll_div);
>+
>+		/* Then see if we can get the desired rate by only adjusting MFN (glitch free) */
>+		rate_min = clk_fracn_gppll_calc_rate(pll, PLL_MFN_MIN, mfd, mfi, rdiv, odiv, prate);
>+		rate_max = clk_fracn_gppll_calc_rate(pll, PLL_MFN_MAX, mfd, mfi, rdiv, odiv, prate);
>+
>+		if (rate >= rate_min && rate <= rate_max) {
>+			mfn = clk_fracn_gppll_calc_mfn(mfd, mfi, odiv, rdiv, rate, prate);
>+			pr_debug("%s: in=%ld, want=%ld Only adjust mfn %ld -> %d\n",
>+				 clk_hw_get_name(&pll->hw), prate, rate,
>+				 FIELD_GET(PLL_MFN_MASK, pll_numerator), mfn);
>+			fout = clk_fracn_gppll_calc_rate(pll, mfn, mfd, mfi, rdiv, odiv, prate);
>+			t->rate = (unsigned int)fout;
>+			t->mfi = mfi;
>+			t->mfn = mfn;
>+			t->mfd = mfd;
>+			t->rdiv = rdiv;
>+			t->odiv = odiv;
>+			return;
>+		}
>+	}
>+
>+	/* Finally calculate best values */
>+	for (rdiv = 1; rdiv <= 7; rdiv++) {
>+		if ((prate / rdiv) < 20000000)
>+			continue;
>+		if ((prate / rdiv) > 40000000)
>+			continue;
>+
>+		for (odiv = 2; odiv <= 255; odiv++) {
>+			mfi = clk_fracn_gppll_calc_mfi(rdiv, rate * odiv, prate);
>+			mfd = 1;
>+			mfn = 0;
>+
>+			/* Try integer PLL part only first */
>+			fout = clk_fracn_gppll_calc_rate(pll, mfn, mfd, mfi, rdiv, odiv, prate);
>+			if (fout * odiv < 2500000000UL)
>+				continue;
>+			if (fout * odiv > 5000000000UL)
>+				continue;
>+
>+			if (pll->flags & CLK_FRACN_GPPLL_FRACN) {
>+				if (!dist) {
>+					/* Disable fractional part upon exact match */
>+					mfd = 1;
>+					mfn = 0;
>+				} else {
>+					mfd = 100;

why hardcode mfd to 100?

>+					mfd = clamp(mfd, PLL_MFD_MIN, PLL_MFN_MAX);
>+
>+					mfn = clk_fracn_gppll_calc_mfn(mfd, mfi, odiv, rdiv, rate, prate);
>+					if ((mfn / mfd) > 2)
>+						continue;
>+
>+					fout = clk_fracn_gppll_calc_rate(pll, mfn, mfd, mfi, rdiv, odiv, prate);
>+					if (fout * odiv < 2500000000)
>+						continue;
>+					if (fout * odiv > 5000000000)
>+						continue;
>+				}
>+			} else {
>+				mfd = 0;
>+				mfn = 0;
>+			}
>+
>+			/* best match */
>+			dist = abs((long)rate - (long)fout);
>+			if (dist < best) {
>+				best = dist;
>+				t->rate = (unsigned int)fout;
>+				t->mfi = mfi;
>+				t->mfn = mfn;
>+				t->mfd = mfd;
>+				t->rdiv = rdiv;
>+				t->odiv = odiv;
>+
>+				if (!dist)
>+					goto found;
>+			}
>+		}
>+	}
>+found:
>+	pr_debug("%s: in=%lu, want=%lu got=%u (mfi=%u mfn=%u mfd=%u rdiv=%u odiv=%u)\n",
>+		 clk_hw_get_name(&pll->hw), prate, rate, t->rate, t->mfi, t->mfn, t->mfd,
>+		 t->rdiv, t->odiv);
>+}
>+
>+static long clk_fracn_gppll_round_rate(struct clk_hw *hw, unsigned long rate,
>+				       unsigned long *prate)
>+{
>+	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
>+	struct imx_fracn_gppll_rate_table t;
>+
>+	clk_fracn_gppll_calc_settings(pll, rate, *prate, &t);
>+
>+	return t.rate;
>+}
>+
> static unsigned long clk_fracn_gppll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> {
> 	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
>@@ -242,11 +401,11 @@ static int clk_fracn_gppll_set_rate(struct clk_hw *hw, unsigned long drate,
> 				    unsigned long prate)
> {
> 	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
>-	const struct imx_fracn_gppll_rate_table *rate;
>+	struct imx_fracn_gppll_rate_table rate;
> 	u32 tmp, pll_div, ana_mfn;
> 	int ret;
> 
>-	rate = imx_get_pll_settings(pll, drate);
>+	clk_fracn_gppll_calc_settings(pll, drate, prate, &rate);
> 
> 	/* Hardware control select disable. PLL is control by register */
> 	tmp = readl_relaxed(pll->base + PLL_CTRL);
>@@ -266,13 +425,13 @@ static int clk_fracn_gppll_set_rate(struct clk_hw *hw, unsigned long drate,
> 	tmp &= ~CLKMUX_BYPASS;
> 	writel_relaxed(tmp, pll->base + PLL_CTRL);

As Sascha mentioned, set rate will first disable output, power down pll based
on current implementation. So this patch might not get tested.

Regards
Peng

> 
>-	pll_div = FIELD_PREP(PLL_RDIV_MASK, rate->rdiv) | rate->odiv |
>-		FIELD_PREP(PLL_MFI_MASK, rate->mfi);
>+	pll_div = FIELD_PREP(PLL_RDIV_MASK, rate.rdiv) | rate.odiv |
>+		FIELD_PREP(PLL_MFI_MASK, rate.mfi);
> 	writel_relaxed(pll_div, pll->base + PLL_DIV);
> 	readl(pll->base + PLL_DIV);
> 	if (pll->flags & CLK_FRACN_GPPLL_FRACN) {
>-		writel_relaxed(rate->mfd, pll->base + PLL_DENOMINATOR);
>-		writel_relaxed(FIELD_PREP(PLL_MFN_MASK, rate->mfn), pll->base + PLL_NUMERATOR);
>+		writel_relaxed(rate.mfd, pll->base + PLL_DENOMINATOR);
>+		writel_relaxed(FIELD_PREP(PLL_MFN_MASK, rate.mfn), pll->base + PLL_NUMERATOR);
> 		readl(pll->base + PLL_NUMERATOR);
> 	}
> 
>@@ -296,7 +455,7 @@ static int clk_fracn_gppll_set_rate(struct clk_hw *hw, unsigned long drate,
> 	ana_mfn = readl_relaxed(pll->base + PLL_STATUS);
> 	ana_mfn = FIELD_GET(PLL_MFN_MASK, ana_mfn);
> 
>-	WARN(ana_mfn != rate->mfn, "ana_mfn != rate->mfn\n");
>+	WARN(ana_mfn != rate.mfn, "ana_mfn != rate->mfn\n");
> 
> 	return 0;
> }
>-- 
>2.34.1
>
diff mbox series

Patch

diff --git a/drivers/clk/imx/clk-fracn-gppll.c b/drivers/clk/imx/clk-fracn-gppll.c
index a7d57fbe93196..68c5b4a95efbe 100644
--- a/drivers/clk/imx/clk-fracn-gppll.c
+++ b/drivers/clk/imx/clk-fracn-gppll.c
@@ -25,6 +25,12 @@ 
 
 #define PLL_NUMERATOR		0x40
 #define PLL_MFN_MASK		GENMASK(31, 2)
+#define PLL_MFI_MIN		0x2
+#define PLL_MFI_MAX		0x1ff
+#define PLL_MFN_MIN		0x0
+#define PLL_MFN_MAX		0x3fffffff
+#define PLL_MFD_MIN		0x1
+#define PLL_MFD_MAX		0x3fffffff
 
 #define PLL_DENOMINATOR		0x50
 #define PLL_MFD_MASK		GENMASK(29, 0)
@@ -134,21 +140,6 @@  imx_get_pll_settings(struct clk_fracn_gppll *pll, unsigned long rate)
 	return NULL;
 }
 
-static long clk_fracn_gppll_round_rate(struct clk_hw *hw, unsigned long rate,
-				       unsigned long *prate)
-{
-	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
-	const struct imx_fracn_gppll_rate_table *rate_table = pll->rate_table;
-	int i;
-
-	/* Assuming rate_table is in descending order */
-	for (i = 0; i < pll->rate_count; i++)
-		if (rate >= rate_table[i].rate)
-			return rate_table[i].rate;
-
-	/* return minimum supported value */
-	return rate_table[pll->rate_count - 1].rate;
-}
 
 static long clk_fracn_gppll_calc_rate(struct clk_fracn_gppll *pll, u32 mfn,
 				      u32 mfd, u32 mfi, u32 rdiv, u32 odiv,
@@ -202,6 +193,174 @@  static long clk_fracn_gppll_calc_rate(struct clk_fracn_gppll *pll, u32 mfn,
 	return (unsigned long)fvco;
 }
 
+static u32 clk_fracn_gppll_calc_mfi(int rdiv, unsigned long fvco,
+				    unsigned long fref)
+{
+	u32 mfi;
+
+	/* fvco = fref / rdiv * mfi */
+	mfi = DIV_ROUND_CLOSEST(fvco * rdiv, fref);
+	return clamp_t(u32, mfi, PLL_MFI_MIN, PLL_MFI_MAX);
+}
+
+static long clk_fracn_gppll_calc_mfn(int mfd, int mfi, int odiv, int rdiv,
+				     unsigned long rate, unsigned long prate)
+{
+	unsigned long tmp;
+	long mfn;
+
+	/* calc mfn = ((rate * odiv * rdiv / prate) - mfi) * mfd */
+	tmp = rate * odiv * rdiv - (mfi * prate);
+	mfn = DIV_ROUND_CLOSEST(tmp * mfd, prate);
+	return clamp_t(long, mfn, PLL_MFN_MIN, PLL_MFN_MAX);
+}
+
+static void clk_fracn_gppll_calc_settings(struct clk_fracn_gppll *pll, unsigned long rate,
+					  unsigned long prate, struct imx_fracn_gppll_rate_table *t)
+{
+	u32 pll_denominator, pll_numerator, pll_div;
+	u32 mfi, mfn, mfd, rdiv, odiv;
+	long fout, rate_min, rate_max, dist, best = LONG_MAX;
+	const struct imx_fracn_gppll_rate_table *tt;
+
+	/*
+	 * PLL constrains:
+	 *
+	 * a) 1 <= MFN <= 0x3fffffff (fractional only)
+	 * b) 1 <= MFD <= 0x3fffffff (fractional only)
+	 * c) 2 <= MFI <= 0x1ff
+	 * d) 1 <= RDIV <= 7
+	 * e) 2 <= ODIV <= 255
+	 * f) -2 <= MFN/MFD <= 2
+	 * g) 20MHz <= (Fref / rdiv) <= 40MHz
+	 * h) 2.5GHz <= Fvco <= 5Ghz
+	 *
+	 * Fvco = (Fref / rdiv) * (MFI + MFN / MFD)
+	 * Fout = Fvco / odiv
+	 */
+
+	/* First try if we can get the desired rate from one of the static entries */
+	tt = imx_get_pll_settings(pll, rate);
+	if (tt) {
+		pr_debug("%s: in=%ld, want=%ld, Using PLL setting from table\n",
+			 clk_hw_get_name(&pll->hw), prate, rate);
+		t->rate = tt->rate;
+		t->mfi = tt->mfi;
+		t->mfn = tt->mfn;
+		t->mfd = tt->mfd;
+		t->rdiv = tt->rdiv;
+		t->odiv = tt->odiv;
+		return;
+	}
+
+	/* glitch free MFN adjustment only for fractional PLL */
+	if (pll->flags & CLK_FRACN_GPPLL_FRACN) {
+		pll_numerator = readl_relaxed(pll->base + PLL_NUMERATOR);
+
+		pll_denominator = readl_relaxed(pll->base + PLL_DENOMINATOR);
+		mfd = FIELD_GET(PLL_MFD_MASK, pll_denominator);
+
+		pll_div = readl_relaxed(pll->base + PLL_DIV);
+		mfi = FIELD_GET(PLL_MFI_MASK, pll_div);
+		rdiv = FIELD_GET(PLL_RDIV_MASK, pll_div);
+		odiv = FIELD_GET(PLL_ODIV_MASK, pll_div);
+
+		/* Then see if we can get the desired rate by only adjusting MFN (glitch free) */
+		rate_min = clk_fracn_gppll_calc_rate(pll, PLL_MFN_MIN, mfd, mfi, rdiv, odiv, prate);
+		rate_max = clk_fracn_gppll_calc_rate(pll, PLL_MFN_MAX, mfd, mfi, rdiv, odiv, prate);
+
+		if (rate >= rate_min && rate <= rate_max) {
+			mfn = clk_fracn_gppll_calc_mfn(mfd, mfi, odiv, rdiv, rate, prate);
+			pr_debug("%s: in=%ld, want=%ld Only adjust mfn %ld -> %d\n",
+				 clk_hw_get_name(&pll->hw), prate, rate,
+				 FIELD_GET(PLL_MFN_MASK, pll_numerator), mfn);
+			fout = clk_fracn_gppll_calc_rate(pll, mfn, mfd, mfi, rdiv, odiv, prate);
+			t->rate = (unsigned int)fout;
+			t->mfi = mfi;
+			t->mfn = mfn;
+			t->mfd = mfd;
+			t->rdiv = rdiv;
+			t->odiv = odiv;
+			return;
+		}
+	}
+
+	/* Finally calculate best values */
+	for (rdiv = 1; rdiv <= 7; rdiv++) {
+		if ((prate / rdiv) < 20000000)
+			continue;
+		if ((prate / rdiv) > 40000000)
+			continue;
+
+		for (odiv = 2; odiv <= 255; odiv++) {
+			mfi = clk_fracn_gppll_calc_mfi(rdiv, rate * odiv, prate);
+			mfd = 1;
+			mfn = 0;
+
+			/* Try integer PLL part only first */
+			fout = clk_fracn_gppll_calc_rate(pll, mfn, mfd, mfi, rdiv, odiv, prate);
+			if (fout * odiv < 2500000000UL)
+				continue;
+			if (fout * odiv > 5000000000UL)
+				continue;
+
+			if (pll->flags & CLK_FRACN_GPPLL_FRACN) {
+				if (!dist) {
+					/* Disable fractional part upon exact match */
+					mfd = 1;
+					mfn = 0;
+				} else {
+					mfd = 100;
+					mfd = clamp(mfd, PLL_MFD_MIN, PLL_MFN_MAX);
+
+					mfn = clk_fracn_gppll_calc_mfn(mfd, mfi, odiv, rdiv, rate, prate);
+					if ((mfn / mfd) > 2)
+						continue;
+
+					fout = clk_fracn_gppll_calc_rate(pll, mfn, mfd, mfi, rdiv, odiv, prate);
+					if (fout * odiv < 2500000000)
+						continue;
+					if (fout * odiv > 5000000000)
+						continue;
+				}
+			} else {
+				mfd = 0;
+				mfn = 0;
+			}
+
+			/* best match */
+			dist = abs((long)rate - (long)fout);
+			if (dist < best) {
+				best = dist;
+				t->rate = (unsigned int)fout;
+				t->mfi = mfi;
+				t->mfn = mfn;
+				t->mfd = mfd;
+				t->rdiv = rdiv;
+				t->odiv = odiv;
+
+				if (!dist)
+					goto found;
+			}
+		}
+	}
+found:
+	pr_debug("%s: in=%lu, want=%lu got=%u (mfi=%u mfn=%u mfd=%u rdiv=%u odiv=%u)\n",
+		 clk_hw_get_name(&pll->hw), prate, rate, t->rate, t->mfi, t->mfn, t->mfd,
+		 t->rdiv, t->odiv);
+}
+
+static long clk_fracn_gppll_round_rate(struct clk_hw *hw, unsigned long rate,
+				       unsigned long *prate)
+{
+	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
+	struct imx_fracn_gppll_rate_table t;
+
+	clk_fracn_gppll_calc_settings(pll, rate, *prate, &t);
+
+	return t.rate;
+}
+
 static unsigned long clk_fracn_gppll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
 {
 	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
@@ -242,11 +401,11 @@  static int clk_fracn_gppll_set_rate(struct clk_hw *hw, unsigned long drate,
 				    unsigned long prate)
 {
 	struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw);
-	const struct imx_fracn_gppll_rate_table *rate;
+	struct imx_fracn_gppll_rate_table rate;
 	u32 tmp, pll_div, ana_mfn;
 	int ret;
 
-	rate = imx_get_pll_settings(pll, drate);
+	clk_fracn_gppll_calc_settings(pll, drate, prate, &rate);
 
 	/* Hardware control select disable. PLL is control by register */
 	tmp = readl_relaxed(pll->base + PLL_CTRL);
@@ -266,13 +425,13 @@  static int clk_fracn_gppll_set_rate(struct clk_hw *hw, unsigned long drate,
 	tmp &= ~CLKMUX_BYPASS;
 	writel_relaxed(tmp, pll->base + PLL_CTRL);
 
-	pll_div = FIELD_PREP(PLL_RDIV_MASK, rate->rdiv) | rate->odiv |
-		FIELD_PREP(PLL_MFI_MASK, rate->mfi);
+	pll_div = FIELD_PREP(PLL_RDIV_MASK, rate.rdiv) | rate.odiv |
+		FIELD_PREP(PLL_MFI_MASK, rate.mfi);
 	writel_relaxed(pll_div, pll->base + PLL_DIV);
 	readl(pll->base + PLL_DIV);
 	if (pll->flags & CLK_FRACN_GPPLL_FRACN) {
-		writel_relaxed(rate->mfd, pll->base + PLL_DENOMINATOR);
-		writel_relaxed(FIELD_PREP(PLL_MFN_MASK, rate->mfn), pll->base + PLL_NUMERATOR);
+		writel_relaxed(rate.mfd, pll->base + PLL_DENOMINATOR);
+		writel_relaxed(FIELD_PREP(PLL_MFN_MASK, rate.mfn), pll->base + PLL_NUMERATOR);
 		readl(pll->base + PLL_NUMERATOR);
 	}
 
@@ -296,7 +455,7 @@  static int clk_fracn_gppll_set_rate(struct clk_hw *hw, unsigned long drate,
 	ana_mfn = readl_relaxed(pll->base + PLL_STATUS);
 	ana_mfn = FIELD_GET(PLL_MFN_MASK, ana_mfn);
 
-	WARN(ana_mfn != rate->mfn, "ana_mfn != rate->mfn\n");
+	WARN(ana_mfn != rate.mfn, "ana_mfn != rate->mfn\n");
 
 	return 0;
 }