]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blobdiff - releases/4.14.111/clk-fractional-divider-check-parent-rate-only-if-fla.patch
Linux 4.14.111
[thirdparty/kernel/stable-queue.git] / releases / 4.14.111 / clk-fractional-divider-check-parent-rate-only-if-fla.patch
diff --git a/releases/4.14.111/clk-fractional-divider-check-parent-rate-only-if-fla.patch b/releases/4.14.111/clk-fractional-divider-check-parent-rate-only-if-fla.patch
new file mode 100644 (file)
index 0000000..2749dd0
--- /dev/null
@@ -0,0 +1,104 @@
+From 987810fdd237206e07c8df6a183a0f874a0cec63 Mon Sep 17 00:00:00 2001
+From: Katsuhiro Suzuki <katsuhiro@katsuster.net>
+Date: Mon, 11 Feb 2019 00:38:06 +0900
+Subject: clk: fractional-divider: check parent rate only if flag is set
+
+[ Upstream commit d13501a2bedfbea0983cc868d3f1dc692627f60d ]
+
+Custom approximation of fractional-divider may not need parent clock
+rate checking. For example Rockchip SoCs work fine using grand parent
+clock rate even if target rate is greater than parent.
+
+This patch checks parent clock rate only if CLK_SET_RATE_PARENT flag
+is set.
+
+For detailed example, clock tree of Rockchip I2S audio hardware.
+  - Clock rate of CPLL is 1.2GHz, GPLL is 491.52MHz.
+  - i2s1_div is integer divider can divide N (N is 1~128).
+    Input clock is CPLL or GPLL. Initial divider value is N = 1.
+    Ex) PLL = CPLL, N = 10, i2s1_div output rate is
+      CPLL / 10 = 1.2GHz / 10 = 120MHz
+  - i2s1_frac is fractional divider can divide input to x/y, x and
+    y are 16bit integer.
+
+CPLL --> | selector | ---> i2s1_div -+--> | selector | --> I2S1 MCLK
+GPLL --> |          | ,--------------'    |          |
+                      `--> i2s1_frac ---> |          |
+
+Clock mux system try to choose suitable one from i2s1_div and
+i2s1_frac for master clock (MCLK) of I2S1.
+
+Bad scenario as follows:
+  - Try to set MCLK to 8.192MHz (32kHz audio replay)
+    Candidate setting is
+    - i2s1_div: GPLL / 60 = 8.192MHz
+    i2s1_div candidate is exactly same as target clock rate, so mux
+    choose this clock source. i2s1_div output rate is changed
+    491.52MHz -> 8.192MHz
+
+  - After that try to set to 11.2896MHz (44.1kHz audio replay)
+    Candidate settings are
+    - i2s1_div : CPLL / 107 = 11.214945MHz
+    - i2s1_frac: i2s1_div   = 8.192MHz
+      This is because clk_fd_round_rate() thinks target rate
+      (11.2896MHz) is higher than parent rate (i2s1_div = 8.192MHz)
+      and returns parent clock rate.
+
+Above is current upstreamed behavior. Clock mux system choose
+i2s1_div, but this clock rate is not acceptable for I2S driver, so
+users cannot replay audio.
+
+Expected behavior is:
+  - Try to set master clock to 11.2896MHz (44.1kHz audio replay)
+    Candidate settings are
+    - i2s1_div : CPLL / 107          = 11.214945MHz
+    - i2s1_frac: i2s1_div * 147/6400 = 11.2896MHz
+                 Change i2s1_div to GPLL / 1 = 491.52MHz at same
+                 time.
+
+If apply this commit, clk_fd_round_rate() calls custom approximate
+function of Rockchip even if target rate is higher than parent.
+Custom function changes both grand parent (i2s1_div) and parent
+(i2s_frac) settings at same time. Clock mux system can choose
+i2s1_frac and audio works fine.
+
+Signed-off-by: Katsuhiro Suzuki <katsuhiro@katsuster.net>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+[sboyd@kernel.org: Make function into a macro instead]
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/clk/clk-fractional-divider.c | 2 +-
+ include/linux/clk-provider.h         | 3 +++
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/clk/clk-fractional-divider.c b/drivers/clk/clk-fractional-divider.c
+index fdf625fb10fa..083daa293280 100644
+--- a/drivers/clk/clk-fractional-divider.c
++++ b/drivers/clk/clk-fractional-divider.c
+@@ -77,7 +77,7 @@ static long clk_fd_round_rate(struct clk_hw *hw, unsigned long rate,
+       unsigned long m, n;
+       u64 ret;
+-      if (!rate || rate >= *parent_rate)
++      if (!rate || (!clk_hw_can_set_rate_parent(hw) && rate >= *parent_rate))
+               return *parent_rate;
+       if (fd->approximation)
+diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
+index 2f4e79fe7b86..3eb3376f1cc8 100644
+--- a/include/linux/clk-provider.h
++++ b/include/linux/clk-provider.h
+@@ -743,6 +743,9 @@ unsigned int __clk_get_enable_count(struct clk *clk);
+ unsigned long clk_hw_get_rate(const struct clk_hw *hw);
+ unsigned long __clk_get_flags(struct clk *clk);
+ unsigned long clk_hw_get_flags(const struct clk_hw *hw);
++#define clk_hw_can_set_rate_parent(hw) \
++      (clk_hw_get_flags((hw)) & CLK_SET_RATE_PARENT)
++
+ bool clk_hw_is_prepared(const struct clk_hw *hw);
+ bool clk_hw_is_enabled(const struct clk_hw *hw);
+ bool __clk_is_enabled(struct clk *clk);
+-- 
+2.19.1
+