diff mbox series

[v4] clk: samsung: Prevent potential endless loop in the PLL set_rate ops

Message ID 20201110193226.20681-1-s.nawrocki@samsung.com
State New
Headers show
Series [v4] clk: samsung: Prevent potential endless loop in the PLL set_rate ops | expand

Commit Message

The PLL status polling loops in the set_rate callbacks of some PLLs
have no timeout detection and may become endless loops when something
goes wrong with the PLL.

For some PLLs there is already the ktime API based timeout detection,
but it will not work in all conditions when .set_rate gets called.
In particular, before the clocksource is initialized or when the
timekeeping is suspended.

This patch adds a common helper with the PLL status bit polling and
timeout detection. For conditions where the timekeeping API should not
be used a simple readl_relaxed/cpu_relax() busy loop is added with the
iterations limit derived from measurements of readl_relaxed() execution
time for various PLL types and Exynos SoCs variants.

Actual PLL lock time depends on the P divider value, the VCO frequency
and a constant PLL type specific LOCK_FACTOR and can be calculated as

 lock_time = Pdiv * LOCK_FACTOR / VCO_freq

For the ktime API use cases a common timeout value of 20 ms is applied
for all the PLLs with an assumption that maximum possible value of Pdiv
is 64, maximum possible LOCK_FACTOR value is 3000 and minimum VCO
frequency is 24 MHz.

Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
---
I'm not sure whether we actually need to implement precise timeouts,
likely the simple busy loop case would be enough. AFAIK the PLL
failures happen very rarely, mostly in early code development stage
for given platform.

Changes since v3:
 - dropped udelay() from the PLL status bit polling loop as it didn't
   work on arm64 at early boot time, before timekeeping was initialized,
 - use the timekeeping API in cases when it is already initialized and
   not suspended,
 - use samsung_pll_lock_wait() also in samsung_pll3xxx_enable() function,
   now all potential endless loops are removed.
---
 drivers/clk/samsung/clk-pll.c | 147 ++++++++++++++++++++----------------------
 1 file changed, 71 insertions(+), 76 deletions(-)

Comments

Krzysztof Kozlowski Nov. 13, 2020, 7:36 a.m. UTC | #1
On Tue, Nov 10, 2020 at 08:32:26PM +0100, Sylwester Nawrocki wrote:
> The PLL status polling loops in the set_rate callbacks of some PLLs

> have no timeout detection and may become endless loops when something

> goes wrong with the PLL.

> 

> For some PLLs there is already the ktime API based timeout detection,

> but it will not work in all conditions when .set_rate gets called.

> In particular, before the clocksource is initialized or when the

> timekeeping is suspended.

> 

> This patch adds a common helper with the PLL status bit polling and

> timeout detection. For conditions where the timekeeping API should not

> be used a simple readl_relaxed/cpu_relax() busy loop is added with the

> iterations limit derived from measurements of readl_relaxed() execution

> time for various PLL types and Exynos SoCs variants.

> 

> Actual PLL lock time depends on the P divider value, the VCO frequency

> and a constant PLL type specific LOCK_FACTOR and can be calculated as

> 

>  lock_time = Pdiv * LOCK_FACTOR / VCO_freq

> 

> For the ktime API use cases a common timeout value of 20 ms is applied

> for all the PLLs with an assumption that maximum possible value of Pdiv

> is 64, maximum possible LOCK_FACTOR value is 3000 and minimum VCO

> frequency is 24 MHz.

> 

> Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com>

> ---

> I'm not sure whether we actually need to implement precise timeouts,

> likely the simple busy loop case would be enough. AFAIK the PLL

> failures happen very rarely, mostly in early code development stage

> for given platform.

> 

> Changes since v3:

>  - dropped udelay() from the PLL status bit polling loop as it didn't

>    work on arm64 at early boot time, before timekeeping was initialized,

>  - use the timekeeping API in cases when it is already initialized and

>    not suspended,

>  - use samsung_pll_lock_wait() also in samsung_pll3xxx_enable() function,

>    now all potential endless loops are removed.

> ---

>  drivers/clk/samsung/clk-pll.c | 147 ++++++++++++++++++++----------------------

>  1 file changed, 71 insertions(+), 76 deletions(-)

> 

> diff --git a/drivers/clk/samsung/clk-pll.c b/drivers/clk/samsung/clk-pll.c

> index ac70ad7..cefb57e 100644

> --- a/drivers/clk/samsung/clk-pll.c

> +++ b/drivers/clk/samsung/clk-pll.c

> @@ -8,14 +8,17 @@

>  

>  #include <linux/errno.h>

>  #include <linux/hrtimer.h>

> +#include <linux/iopoll.h>

>  #include <linux/delay.h>

>  #include <linux/slab.h>

> +#include <linux/timekeeping.h>

>  #include <linux/clk-provider.h>

>  #include <linux/io.h>

>  #include "clk.h"

>  #include "clk-pll.h"

>  

> -#define PLL_TIMEOUT_MS		10

> +#define PLL_TIMEOUT_US		20000U

> +#define PLL_TIMEOUT_LOOPS	1000000U

>  

>  struct samsung_clk_pll {

>  	struct clk_hw		hw;

> @@ -63,6 +66,53 @@ static long samsung_pll_round_rate(struct clk_hw *hw,

>  	return rate_table[i - 1].rate;

>  }

>  

> +static bool __early_timeout = true;


Drop the __ prefix and maybe use "pll_early_timeout".
This looks like __ro_after_init.

Best regards,
Krzysztof
diff mbox series

Patch

diff --git a/drivers/clk/samsung/clk-pll.c b/drivers/clk/samsung/clk-pll.c
index ac70ad7..cefb57e 100644
--- a/drivers/clk/samsung/clk-pll.c
+++ b/drivers/clk/samsung/clk-pll.c
@@ -8,14 +8,17 @@ 
 
 #include <linux/errno.h>
 #include <linux/hrtimer.h>
+#include <linux/iopoll.h>
 #include <linux/delay.h>
 #include <linux/slab.h>
+#include <linux/timekeeping.h>
 #include <linux/clk-provider.h>
 #include <linux/io.h>
 #include "clk.h"
 #include "clk-pll.h"
 
-#define PLL_TIMEOUT_MS		10
+#define PLL_TIMEOUT_US		20000U
+#define PLL_TIMEOUT_LOOPS	1000000U
 
 struct samsung_clk_pll {
 	struct clk_hw		hw;
@@ -63,6 +66,53 @@  static long samsung_pll_round_rate(struct clk_hw *hw,
 	return rate_table[i - 1].rate;
 }
 
+static bool __early_timeout = true;
+
+static int __init samsung_pll_disable_early_timeout(void)
+{
+	__early_timeout = false;
+	return 0;
+}
+arch_initcall(samsung_pll_disable_early_timeout);
+
+/* Wait until the PLL is locked */
+static int samsung_pll_lock_wait(struct samsung_clk_pll *pll,
+				 unsigned int reg_mask)
+{
+	int i, ret;
+	u32 val;
+
+	/*
+	 * This function might be called when the timekeeping API can't be used
+	 * to detect timeouts. One situation is when the clocksource is not yet
+	 * initialized, another when the timekeeping is suspended. udelay() also
+	 * cannot be used when the clocksource is not running on arm64, since
+	 * the current timer is used as cycle counter. So a simple busy loop
+	 * is used here in that special cases. The limit of iterations has been
+	 * derived from experimental measurements of various PLLs on multiple
+	 * Exynos SoC variants. Single register read time was usually in range
+	 * 0.4...1.5 us, never less than 0.4 us.
+	 */
+	if (__early_timeout || timekeeping_suspended) {
+		i = PLL_TIMEOUT_LOOPS;
+		while (i-- > 0) {
+			if (readl_relaxed(pll->con_reg) & reg_mask)
+				return 0;
+
+			cpu_relax();
+		}
+		ret = -ETIMEDOUT;
+	} else {
+		ret = readl_relaxed_poll_timeout_atomic(pll->con_reg, val,
+					val & reg_mask, 0, PLL_TIMEOUT_US);
+	}
+
+	if (ret < 0)
+		pr_err("Could not lock PLL %s\n", clk_hw_get_name(&pll->hw));
+
+	return ret;
+}
+
 static int samsung_pll3xxx_enable(struct clk_hw *hw)
 {
 	struct samsung_clk_pll *pll = to_clk_pll(hw);
@@ -72,13 +122,7 @@  static int samsung_pll3xxx_enable(struct clk_hw *hw)
 	tmp |= BIT(pll->enable_offs);
 	writel_relaxed(tmp, pll->con_reg);
 
-	/* wait lock time */
-	do {
-		cpu_relax();
-		tmp = readl_relaxed(pll->con_reg);
-	} while (!(tmp & BIT(pll->lock_offs)));
-
-	return 0;
+	return samsung_pll_lock_wait(pll, BIT(pll->lock_offs));
 }
 
 static void samsung_pll3xxx_disable(struct clk_hw *hw)
@@ -240,13 +284,10 @@  static int samsung_pll35xx_set_rate(struct clk_hw *hw, unsigned long drate,
 			(rate->sdiv << PLL35XX_SDIV_SHIFT);
 	writel_relaxed(tmp, pll->con_reg);
 
-	/* Wait until the PLL is locked if it is enabled. */
-	if (tmp & BIT(pll->enable_offs)) {
-		do {
-			cpu_relax();
-			tmp = readl_relaxed(pll->con_reg);
-		} while (!(tmp & BIT(pll->lock_offs)));
-	}
+	/* Wait for PLL lock if the PLL is enabled */
+	if (tmp & BIT(pll->enable_offs))
+		return samsung_pll_lock_wait(pll, BIT(pll->lock_offs));
+
 	return 0;
 }
 
@@ -318,7 +359,7 @@  static int samsung_pll36xx_set_rate(struct clk_hw *hw, unsigned long drate,
 					unsigned long parent_rate)
 {
 	struct samsung_clk_pll *pll = to_clk_pll(hw);
-	u32 tmp, pll_con0, pll_con1;
+	u32 pll_con0, pll_con1;
 	const struct samsung_pll_rate_table *rate;
 
 	rate = samsung_get_pll_settings(pll, drate);
@@ -356,13 +397,8 @@  static int samsung_pll36xx_set_rate(struct clk_hw *hw, unsigned long drate,
 	pll_con1 |= rate->kdiv << PLL36XX_KDIV_SHIFT;
 	writel_relaxed(pll_con1, pll->con_reg + 4);
 
-	/* wait_lock_time */
-	if (pll_con0 & BIT(pll->enable_offs)) {
-		do {
-			cpu_relax();
-			tmp = readl_relaxed(pll->con_reg);
-		} while (!(tmp & BIT(pll->lock_offs)));
-	}
+	if (pll_con0 & BIT(pll->enable_offs))
+		return samsung_pll_lock_wait(pll, BIT(pll->lock_offs));
 
 	return 0;
 }
@@ -437,7 +473,6 @@  static int samsung_pll45xx_set_rate(struct clk_hw *hw, unsigned long drate,
 	struct samsung_clk_pll *pll = to_clk_pll(hw);
 	const struct samsung_pll_rate_table *rate;
 	u32 con0, con1;
-	ktime_t start;
 
 	/* Get required rate settings from table */
 	rate = samsung_get_pll_settings(pll, drate);
@@ -488,21 +523,8 @@  static int samsung_pll45xx_set_rate(struct clk_hw *hw, unsigned long drate,
 	writel_relaxed(con1, pll->con_reg + 0x4);
 	writel_relaxed(con0, pll->con_reg);
 
-	/* Wait for locking. */
-	start = ktime_get();
-	while (!(readl_relaxed(pll->con_reg) & PLL45XX_LOCKED)) {
-		ktime_t delta = ktime_sub(ktime_get(), start);
-
-		if (ktime_to_ms(delta) > PLL_TIMEOUT_MS) {
-			pr_err("%s: could not lock PLL %s\n",
-					__func__, clk_hw_get_name(hw));
-			return -EFAULT;
-		}
-
-		cpu_relax();
-	}
-
-	return 0;
+	/* Wait for PLL lock */
+	return samsung_pll_lock_wait(pll, PLL45XX_LOCKED);
 }
 
 static const struct clk_ops samsung_pll45xx_clk_ops = {
@@ -588,7 +610,6 @@  static int samsung_pll46xx_set_rate(struct clk_hw *hw, unsigned long drate,
 	struct samsung_clk_pll *pll = to_clk_pll(hw);
 	const struct samsung_pll_rate_table *rate;
 	u32 con0, con1, lock;
-	ktime_t start;
 
 	/* Get required rate settings from table */
 	rate = samsung_get_pll_settings(pll, drate);
@@ -647,21 +668,8 @@  static int samsung_pll46xx_set_rate(struct clk_hw *hw, unsigned long drate,
 	writel_relaxed(con0, pll->con_reg);
 	writel_relaxed(con1, pll->con_reg + 0x4);
 
-	/* Wait for locking. */
-	start = ktime_get();
-	while (!(readl_relaxed(pll->con_reg) & PLL46XX_LOCKED)) {
-		ktime_t delta = ktime_sub(ktime_get(), start);
-
-		if (ktime_to_ms(delta) > PLL_TIMEOUT_MS) {
-			pr_err("%s: could not lock PLL %s\n",
-					__func__, clk_hw_get_name(hw));
-			return -EFAULT;
-		}
-
-		cpu_relax();
-	}
-
-	return 0;
+	/* Wait for PLL lock */
+	return samsung_pll_lock_wait(pll, PLL46XX_LOCKED);
 }
 
 static const struct clk_ops samsung_pll46xx_clk_ops = {
@@ -1035,14 +1043,9 @@  static int samsung_pll2550xx_set_rate(struct clk_hw *hw, unsigned long drate,
 			(rate->sdiv << PLL2550XX_S_SHIFT);
 	writel_relaxed(tmp, pll->con_reg);
 
-	/* wait_lock_time */
-	do {
-		cpu_relax();
-		tmp = readl_relaxed(pll->con_reg);
-	} while (!(tmp & (PLL2550XX_LOCK_STAT_MASK
-			<< PLL2550XX_LOCK_STAT_SHIFT)));
-
-	return 0;
+	/* Wait for PLL lock */
+	return samsung_pll_lock_wait(pll,
+			PLL2550XX_LOCK_STAT_MASK << PLL2550XX_LOCK_STAT_SHIFT);
 }
 
 static const struct clk_ops samsung_pll2550xx_clk_ops = {
@@ -1132,13 +1135,9 @@  static int samsung_pll2650x_set_rate(struct clk_hw *hw, unsigned long drate,
 	con1 |= ((rate->kdiv & PLL2650X_K_MASK) << PLL2650X_K_SHIFT);
 	writel_relaxed(con1, pll->con_reg + 4);
 
-	do {
-		cpu_relax();
-		con0 = readl_relaxed(pll->con_reg);
-	} while (!(con0 & (PLL2650X_LOCK_STAT_MASK
-			<< PLL2650X_LOCK_STAT_SHIFT)));
-
-	return 0;
+	/* Wait for PLL lock */
+	return samsung_pll_lock_wait(pll,
+			PLL2650X_LOCK_STAT_MASK << PLL2650X_LOCK_STAT_SHIFT);
 }
 
 static const struct clk_ops samsung_pll2650x_clk_ops = {
@@ -1196,7 +1195,7 @@  static int samsung_pll2650xx_set_rate(struct clk_hw *hw, unsigned long drate,
 					unsigned long parent_rate)
 {
 	struct samsung_clk_pll *pll = to_clk_pll(hw);
-	u32 tmp, pll_con0, pll_con2;
+	u32 pll_con0, pll_con2;
 	const struct samsung_pll_rate_table *rate;
 
 	rate = samsung_get_pll_settings(pll, drate);
@@ -1229,11 +1228,7 @@  static int samsung_pll2650xx_set_rate(struct clk_hw *hw, unsigned long drate,
 	writel_relaxed(pll_con0, pll->con_reg);
 	writel_relaxed(pll_con2, pll->con_reg + 8);
 
-	do {
-		tmp = readl_relaxed(pll->con_reg);
-	} while (!(tmp & (0x1 << PLL2650XX_PLL_LOCKTIME_SHIFT)));
-
-	return 0;
+	return samsung_pll_lock_wait(pll, 0x1 << PLL2650XX_PLL_LOCKTIME_SHIFT);
 }
 
 static const struct clk_ops samsung_pll2650xx_clk_ops = {