[v4,03/11] clk: sunxi-ng: nkm: Improve determine rate when setting parent

Message ID 20230717-pll-mipi_set_rate_parent-v4-3-04acf1d39765@oltmanns.dev
State New
Headers
Series clk: sunxi-ng: Consider alternative parent rates when determining NKM clock rate |

Commit Message

Frank Oltmanns July 17, 2023, 1:34 p.m. UTC
  Make the SET_PARENT_RATE flag independent of the parents round_rate or
determine_rate implementation.

Currently, the algorithm for ccu_nkm_find_best_with_parent_adj simply
calculates the optimal parent rate as
    (1) parent = rate * m / (n * k)

Due to integer division (1) might return a parent rate that is too low.
So using this value for asking the parent for a rate it supports via
clk_hw_round_rate causes problems on
 a) parents that only support finding rates that are lower than the
    requested rate - which is the default for sunxi-ng ccu's.
 b) parents that incidentally also support the truncated rate.

In those cases ccu_nkm_determine_rate might return A' when A is
requested and A'' when rate A' is requested.

Prevent this by trying to find a parent rate so that
    (2) _rate = parent * n * k / m
matches the requested rate exactly, if possible.

Background:
===========
determine_rate may be called multiple times by the clk framework when
setting a clock's rate. But the clk framework expects that the values
determine_rate returns (i.e. the rate and parent_rate) are consistent
with previous calls.

Specifically, clock's have to ensure that if determine_rate is called
with requested rate A and the best rate it can find is A', it must also
return A' when called with requested rate A'.

Signed-off-by: Frank Oltmanns <frank@oltmanns.dev>
---
 drivers/clk/sunxi-ng/ccu_nkm.c | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)
  

Comments

Maxime Ripard July 17, 2023, 2:10 p.m. UTC | #1
Hi,

On Mon, Jul 17, 2023 at 03:34:27PM +0200, Frank Oltmanns wrote:
> Make the SET_PARENT_RATE flag independent of the parents round_rate or
> determine_rate implementation.
> 
> Currently, the algorithm for ccu_nkm_find_best_with_parent_adj simply
> calculates the optimal parent rate as
>     (1) parent = rate * m / (n * k)
> 
> Due to integer division (1) might return a parent rate that is too low.
> So using this value for asking the parent for a rate it supports via
> clk_hw_round_rate causes problems on
>  a) parents that only support finding rates that are lower than the
>     requested rate - which is the default for sunxi-ng ccu's.
>  b) parents that incidentally also support the truncated rate.
> 
> In those cases ccu_nkm_determine_rate might return A' when A is
> requested and A'' when rate A' is requested.
> 
> Prevent this by trying to find a parent rate so that
>     (2) _rate = parent * n * k / m
> matches the requested rate exactly, if possible.
> 
> Background:
> ===========
> determine_rate may be called multiple times by the clk framework when
> setting a clock's rate. But the clk framework expects that the values
> determine_rate returns (i.e. the rate and parent_rate) are consistent
> with previous calls.
> 
> Specifically, clock's have to ensure that if determine_rate is called
> with requested rate A and the best rate it can find is A', it must also
> return A' when called with requested rate A'.
> 
> Signed-off-by: Frank Oltmanns <frank@oltmanns.dev>

As stated in my earlier mail, I still disagree with that patch.

Maxime
  

Patch

diff --git a/drivers/clk/sunxi-ng/ccu_nkm.c b/drivers/clk/sunxi-ng/ccu_nkm.c
index 750e2b8da24b..793160bc2d47 100644
--- a/drivers/clk/sunxi-ng/ccu_nkm.c
+++ b/drivers/clk/sunxi-ng/ccu_nkm.c
@@ -17,6 +17,27 @@  struct _ccu_nkm {
 	unsigned long	m, min_m, max_m;
 };
 
+/*
+ * Calculate the optimal parent when determining the nkm clock's rate.
+ *
+ * This function is used when the nkm clock supports setting the parent rate and ensures that the
+ * determine_rate function returns consistent values. I.e., when determine rate is called for rate A
+ * and the best rate it can find is A', this function ensures that determine_rate will also return
+ * A' when A' is requested.
+ */
+static unsigned long ccu_nkm_optimal_parent_rate(unsigned long rate, unsigned long n,
+				 unsigned long k, unsigned long m)
+{
+	unsigned long _rate, parent;
+
+	parent = DIV_ROUND_UP(rate * m, n * k);
+
+	_rate = parent * n * k / m;
+	if (_rate > rate)
+		parent = rate * m / (n * k);
+	return parent;
+}
+
 static unsigned long ccu_nkm_find_best_with_parent_adj(struct clk_hw *phw, struct _ccu_nkm *nkm,
 						       unsigned long *parent, unsigned long rate)
 {
@@ -29,7 +50,8 @@  static unsigned long ccu_nkm_find_best_with_parent_adj(struct clk_hw *phw, struc
 			for (_m = nkm->min_m; _m <= nkm->max_m; _m++) {
 				unsigned long tmp_rate;
 
-				tmp_parent = clk_hw_round_rate(phw, rate * _m / (_n * _k));
+				tmp_parent = ccu_nkm_optimal_parent_rate(rate, _n, _k, _m);
+				tmp_parent = clk_hw_round_rate(phw, tmp_parent);
 
 				tmp_rate = tmp_parent * _n * _k / _m;
 				if (tmp_rate > rate)