summaryrefslogtreecommitdiff
path: root/drivers/clk/zynqmp
diff options
context:
space:
mode:
authorRajan Vaja <rajan.vaja@xilinx.com>2019-12-04 22:35:58 -0800
committerStephen Boyd <sboyd@kernel.org>2020-01-23 13:25:37 -0800
commit4ebd92d2e228ceb33bafa273fd9cb2b16615cdd4 (patch)
tree5f326342b3ded35c7dda9c30118641219c157eeb /drivers/clk/zynqmp
parente942171bbb762977afaa1eb24a312c3bd56386a5 (diff)
downloadlinux-4ebd92d2e228ceb33bafa273fd9cb2b16615cdd4.tar.gz
clk: zynqmp: Fix divider calculation
zynqmp_clk_divider_round_rate() returns actual divider value after calculating from parent rate and desired rate, even though that rate is not supported by single divider of hardware. It is also possible that such divisor value can be achieved through 2 different dividers. As, Linux tries to set such divisor value(out of range) in single divider set divider is getting failed. Fix the same by computing best possible combination of two divisors which provides more accurate clock rate. Signed-off-by: Michal Simek <michal.simek@xilinx.com> Signed-off-by: Tejas Patel <tejas.patel@xilinx.com> Signed-off-by: Rajan Vaja <rajan.vaja@xilinx.com> Link: https://lkml.kernel.org/r/1575527759-26452-6-git-send-email-rajan.vaja@xilinx.com Signed-off-by: Stephen Boyd <sboyd@kernel.org>
Diffstat (limited to 'drivers/clk/zynqmp')
-rw-r--r--drivers/clk/zynqmp/divider.c46
1 files changed, 46 insertions, 0 deletions
diff --git a/drivers/clk/zynqmp/divider.c b/drivers/clk/zynqmp/divider.c
index da6903197fc7..c6af0c454997 100644
--- a/drivers/clk/zynqmp/divider.c
+++ b/drivers/clk/zynqmp/divider.c
@@ -89,6 +89,42 @@ static unsigned long zynqmp_clk_divider_recalc_rate(struct clk_hw *hw,
return DIV_ROUND_UP_ULL(parent_rate, value);
}
+static void zynqmp_get_divider2_val(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate,
+ struct zynqmp_clk_divider *divider,
+ int *bestdiv)
+{
+ int div1;
+ int div2;
+ long error = LONG_MAX;
+ struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+ struct zynqmp_clk_divider *pdivider = to_zynqmp_clk_divider(parent_hw);
+
+ if (!pdivider)
+ return;
+
+ *bestdiv = 1;
+ for (div1 = 1; div1 <= pdivider->max_div;) {
+ for (div2 = 1; div2 <= divider->max_div;) {
+ long new_error = ((parent_rate / div1) / div2) - rate;
+
+ if (abs(new_error) < abs(error)) {
+ *bestdiv = div2;
+ error = new_error;
+ }
+ if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
+ div2 = div2 << 1;
+ else
+ div2++;
+ }
+ if (pdivider->flags & CLK_DIVIDER_POWER_OF_TWO)
+ div1 = div1 << 1;
+ else
+ div1++;
+ }
+}
+
/**
* zynqmp_clk_divider_round_rate() - Round rate of divider clock
* @hw: handle between common and hardware-specific interfaces
@@ -126,6 +162,16 @@ static long zynqmp_clk_divider_round_rate(struct clk_hw *hw,
bestdiv = zynqmp_divider_get_val(*prate, rate);
+ /*
+ * In case of two divisors, compute best divider values and return
+ * divider2 value based on compute value. div1 will be automatically
+ * set to optimum based on required total divider value.
+ */
+ if (div_type == TYPE_DIV2 &&
+ (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
+ zynqmp_get_divider2_val(hw, rate, *prate, divider, &bestdiv);
+ }
+
if ((clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) && divider->is_frac)
bestdiv = rate % *prate ? 1 : bestdiv;
*prate = rate * bestdiv;