[PATCH 3/3] clk: ti: clk-k3-pll: Add additional robustness steps to the PLL sequence

Manorit Chawdhry m-chawdhry at ti.com
Thu Nov 21 13:02:53 CET 2024


Based on the recommendation from HW team make modifications to
the sequence for more robustness.

- Unlock the PLL registers
- Enable external bypass
- Disable the PLL
- Program pllm and pllf
- Program Ref divider
- Enable other PLL controls like DSM_EN, DAC_EN,etc
- Enable calibration if available
- Enable PLL
- Wait for PLL lock and Calibration lock
- Remove external bypass

Re-write the full sequence from scratch as the previous sequence was way
off and keep it in a single commit for bisectability.

Signed-off-by: Manorit Chawdhry <m-chawdhry at ti.com>
---
 drivers/clk/ti/clk-k3-pll.c | 327 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 261 insertions(+), 66 deletions(-)

diff --git a/drivers/clk/ti/clk-k3-pll.c b/drivers/clk/ti/clk-k3-pll.c
index 7c18767236c1e6967a436ec163bd3b8d15833bc0..b775bd55faab98db3c1a7b6093b5e9ff402a5050 100644
--- a/drivers/clk/ti/clk-k3-pll.c
+++ b/drivers/clk/ti/clk-k3-pll.c
@@ -14,6 +14,7 @@
 #include <linux/clk-provider.h>
 #include "k3-clk.h"
 #include <linux/rational.h>
+#include <linux/delay.h>
 
 /* 16FFT register offsets */
 #define PLL_16FFT_CFG			0x08
@@ -29,10 +30,12 @@
 
 /* CAL STAT register bits */
 #define PLL_16FFT_CAL_STAT_CAL_LOCK	BIT(31)
+#define PLL_16FFT_CAL_STAT_CAL_LOCK_TIMEOUT (4350U * 100U)
 
 /* CFG register bits */
 #define PLL_16FFT_CFG_PLL_TYPE_SHIFT	(0)
 #define PLL_16FFT_CFG_PLL_TYPE_MASK	(0x3 << 0)
+#define PLL_16FFT_CFG_PLL_TYPE_FRAC2	0
 #define PLL_16FFT_CFG_PLL_TYPE_FRACF	1
 
 /* CAL CTRL register bits */
@@ -41,14 +44,21 @@
 #define PLL_16FFT_CAL_CTRL_CAL_BYP              BIT(15)
 #define PLL_16FFT_CAL_CTRL_CAL_CNT_SHIFT        16
 #define PLL_16FFT_CAL_CTRL_CAL_CNT_MASK         (0x7 << 16)
+#define PLL_16FFT_CAL_CTRL_CAL_IN_MASK          (0xFFFU)
 
 /* CTRL register bits */
 #define PLL_16FFT_CTRL_BYPASS_EN	BIT(31)
+#define PLL_16FFT_CTRL_BYP_ON_LOCKLOSS	BIT(16)
 #define PLL_16FFT_CTRL_PLL_EN		BIT(15)
+#define PLL_16FFT_CTRL_INTL_BYP_EN	BIT(8)
+#define PLL_16FFT_CTRL_CLK_4PH_EN	BIT(5)
+#define PLL_16FFT_CTRL_CLK_POSTDIV_EN	BIT(4)
 #define PLL_16FFT_CTRL_DSM_EN		BIT(1)
+#define PLL_16FFT_CTRL_DAC_EN		BIT(0)
 
 /* STAT register bits */
 #define PLL_16FFT_STAT_LOCK		BIT(0)
+#define PLL_16FFT_STAT_LOCK_TIMEOUT	(150U * 100U)
 
 /* FREQ_CTRL0 bits */
 #define PLL_16FFT_FREQ_CTRL0_FB_DIV_INT_MASK	0xfff
@@ -62,7 +72,6 @@
 /* FREQ_CTRL1 bits */
 #define PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_BITS	24
 #define PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_MASK	0xffffff
-#define PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_SHIFT	0
 
 /* KICK register magic values */
 #define PLL_KICK0_VALUE				0x68ef3490
@@ -80,63 +89,194 @@ struct ti_pll_clk {
 
 #define to_clk_pll(_clk) container_of(_clk, struct ti_pll_clk, clk)
 
-static int ti_pll_wait_for_lock(struct clk *clk)
+static int ti_pll_clk_disable(struct clk *clk)
 {
 	struct ti_pll_clk *pll = to_clk_pll(clk);
+	u32 ctrl;
+
+	ctrl = readl(pll->base + PLL_16FFT_CTRL);
+
+	if ((ctrl & PLL_16FFT_CTRL_PLL_EN)) {
+		ctrl &= ~PLL_16FFT_CTRL_PLL_EN;
+		writel(ctrl, pll->base + PLL_16FFT_CTRL);
+
+		/* wait 1us */
+		udelay(1);
+	}
+
+	return 0;
+}
+
+static int ti_pll_clk_enable(struct clk *clk)
+{
+	struct ti_pll_clk *pll = to_clk_pll(clk);
+	u32 ctrl;
+
+	ctrl = readl(pll->base + PLL_16FFT_CTRL);
+	ctrl |= PLL_16FFT_CTRL_PLL_EN;
+	writel(ctrl, pll->base + PLL_16FFT_CTRL);
+
+	/* Wait 1us */
+	udelay(1);
+
+	return 0;
+}
+
+static bool clk_pll_16fft_check_lock(const struct ti_pll_clk *pll)
+{
 	u32 stat;
+
+	stat = readl(pll->base + PLL_16FFT_STAT);
+	return (stat & PLL_16FFT_STAT_LOCK);
+}
+
+static bool clk_pll_16fft_check_cal_lock(const struct ti_pll_clk *pll)
+{
+	u32 stat;
+
+	stat = readl(pll->base + PLL_16FFT_CAL_STAT);
+	return (stat & PLL_16FFT_CAL_STAT_CAL_LOCK);
+}
+
+static void clk_pll_16fft_cal_int(const struct ti_pll_clk *pll)
+{
+	u32 cal;
+
+	cal = readl(pll->base + PLL_16FFT_CAL_CTRL);
+
+	/* Enable fast cal mode */
+	cal |= PLL_16FFT_CAL_CTRL_FAST_CAL;
+
+	/* Disable calibration bypass */
+	cal &= ~PLL_16FFT_CAL_CTRL_CAL_BYP;
+
+	/* Set CALCNT to 2 */
+	cal &= ~PLL_16FFT_CAL_CTRL_CAL_CNT_MASK;
+	cal |= 2 << PLL_16FFT_CAL_CTRL_CAL_CNT_SHIFT;
+
+	/* Set CAL_IN to 0 */
+	cal &= ~PLL_16FFT_CAL_CTRL_CAL_IN_MASK;
+
+	/* Note this register does not readback the written value. */
+	writel(cal, pll->base + PLL_16FFT_CAL_CTRL);
+
+	/* Wait 1us before enabling the CAL_EN field */
+	udelay(1);
+
+	cal = readl(pll->base + PLL_16FFT_CAL_CTRL);
+
+	/* Enable calibration for FRACF */
+	cal |= PLL_16FFT_CAL_CTRL_CAL_EN;
+
+	/* Note this register does not readback the written value. */
+	writel(cal, pll->base + PLL_16FFT_CAL_CTRL);
+}
+
+static void clk_pll_16fft_disable_cal(const struct ti_pll_clk *pll)
+{
+	u32 cal, stat;
+
+	cal = readl(pll->base + PLL_16FFT_CAL_CTRL);
+	cal &= ~PLL_16FFT_CAL_CTRL_CAL_EN;
+	/* Note this register does not readback the written value. */
+	writel(cal, pll->base + PLL_16FFT_CAL_CTRL);
+	do {
+		stat = readl(pll->base + PLL_16FFT_CAL_STAT);
+	} while (stat & PLL_16FFT_CAL_STAT_CAL_LOCK);
+}
+
+static int ti_pll_wait_for_lock(struct clk *clk)
+{
+	struct ti_pll_clk *pll = to_clk_pll(clk);
 	u32 cfg;
 	u32 cal;
 	u32 freq_ctrl1;
-	int i;
+	unsigned int i;
 	u32 pllfm;
 	u32 pll_type;
-	int success;
+	u32 cal_en = 0;
+	bool success;
 
-	for (i = 0; i < 100000; i++) {
-		stat = readl(pll->base + PLL_16FFT_STAT);
-		if (stat & PLL_16FFT_STAT_LOCK) {
-			success = 1;
+	/*
+	 * Minimum VCO input freq is 5MHz, and the longest a lock should
+	 * be consider to be timed out after 750 cycles. Be conservative
+	 * and assume each loop takes 10 cycles and we run at a
+	 * max of 1GHz. That gives 15000 loop cycles. We may end up waiting
+	 * longer than necessary for timeout, but that should be ok.
+	 */
+	success = false;
+	for (i = 0; i < PLL_16FFT_STAT_LOCK_TIMEOUT; i++) {
+		if (clk_pll_16fft_check_lock(pll)) {
+			success = true;
 			break;
 		}
 	}
 
-	/* Enable calibration if not in fractional mode of the FRACF PLL */
+	/* Disable calibration in the fractional mode of the FRACF PLL based on data
+	 * from silicon and simulation data.
+	 */
 	freq_ctrl1 = readl(pll->base + PLL_16FFT_FREQ_CTRL1);
 	pllfm = freq_ctrl1 & PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_MASK;
-	pllfm >>= PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_SHIFT;
+
 	cfg = readl(pll->base + PLL_16FFT_CFG);
 	pll_type = (cfg & PLL_16FFT_CFG_PLL_TYPE_MASK) >> PLL_16FFT_CFG_PLL_TYPE_SHIFT;
 
-	if (success && pll_type == PLL_16FFT_CFG_PLL_TYPE_FRACF && pllfm == 0) {
+	if (success && pll_type == PLL_16FFT_CFG_PLL_TYPE_FRACF) {
 		cal = readl(pll->base + PLL_16FFT_CAL_CTRL);
+		cal_en = (cal & PLL_16FFT_CAL_CTRL_CAL_EN);
+	}
 
-		/* Enable calibration for FRACF */
-		cal |= PLL_16FFT_CAL_CTRL_CAL_EN;
+	if (success && pll_type == PLL_16FFT_CFG_PLL_TYPE_FRACF &&
+	    pllfm == 0 && cal_en == 1) {
+		/*
+		 * Wait for calibration lock.
+		 *
+		 * Lock should occur within:
+		 *
+		 *	170 * 2^(5+CALCNT) / PFD
+		 *      21760 / PFD
+		 *
+		 * CALCNT = 2, PFD = 5-50MHz. This gives a range of 0.435mS to
+		 * 4.35mS depending on PFD frequency.
+		 *
+		 * Be conservative and assume each loop takes 10 cycles and we run at a
+		 * max of 1GHz. That gives 435000 loop cycles. We may end up waiting
+		 * longer than necessary for timeout, but that should be ok.
+		 *
+		 * The recommend timeout for CALLOCK to go high is 4.35 ms
+		 */
+		success = false;
+		for (i = 0; i < PLL_16FFT_CAL_STAT_CAL_LOCK_TIMEOUT; i++) {
+			if (clk_pll_16fft_check_cal_lock(pll)) {
+				success = true;
+				break;
+			}
+		}
 
-		/* Enable fast cal mode */
-		cal |= PLL_16FFT_CAL_CTRL_FAST_CAL;
+		/* In case of cal lock failure, operate without calibration */
+		if (!success) {
+			debug("Failure for calibration, falling back without calibration\n");
 
-		/* Disable calibration bypass */
-		cal &= ~PLL_16FFT_CAL_CTRL_CAL_BYP;
+			/* Disable PLL */
+			ti_pll_clk_disable(clk);
 
-		/* Set CALCNT to 2 */
-		cal &= ~PLL_16FFT_CAL_CTRL_CAL_CNT_MASK;
-		cal |= 2 << PLL_16FFT_CAL_CTRL_CAL_CNT_SHIFT;
+			/* Disable Calibration */
+			clk_pll_16fft_disable_cal(pll);
 
-		/* Note this register does not readback the written value. */
-		writel(cal, pll->base + PLL_16FFT_CAL_CTRL);
+			/* Enable PLL */
+			ti_pll_clk_enable(clk);
 
-		success = 0;
-		for (i = 0; i < 100000; i++) {
-			stat = readl(pll->base + PLL_16FFT_CAL_STAT);
-			if (stat & PLL_16FFT_CAL_STAT_CAL_LOCK) {
-				success = 1;
-				break;
+			/* Wait for PLL Lock */
+			for (i = 0; i < PLL_16FFT_STAT_LOCK_TIMEOUT; i++) {
+				if (clk_pll_16fft_check_lock(pll)) {
+					success = true;
+					break;
+				}
 			}
 		}
 	}
 
-	if (success == 0) {
+	if (!success) {
 		printf("%s: pll (%s) failed to lock\n", __func__,
 		       clk->dev->name);
 		return -EBUSY;
@@ -180,6 +320,30 @@ static ulong ti_pll_clk_get_rate(struct clk *clk)
 	return current_freq;
 }
 
+static bool ti_pll_clk_is_bypass(struct ti_pll_clk *pll)
+{
+	u32 ctrl;
+	bool ret;
+
+	ctrl = readl(pll->base + PLL_16FFT_CTRL);
+	ret = (ctrl & PLL_16FFT_CTRL_BYPASS_EN) != 0;
+
+	return ret;
+}
+
+static void ti_pll_clk_bypass(struct ti_pll_clk *pll, bool bypass)
+{
+	u32 ctrl;
+
+	ctrl = readl(pll->base + PLL_16FFT_CTRL);
+	if (bypass)
+		ctrl |= PLL_16FFT_CTRL_BYPASS_EN;
+	else
+		ctrl &= ~PLL_16FFT_CTRL_BYPASS_EN;
+
+	writel(ctrl, pll->base + PLL_16FFT_CTRL);
+}
+
 static ulong ti_pll_clk_set_rate(struct clk *clk, ulong rate)
 {
 	struct ti_pll_clk *pll = to_clk_pll(clk);
@@ -187,9 +351,13 @@ static ulong ti_pll_clk_set_rate(struct clk *clk, ulong rate)
 	u64 parent_freq = clk_get_parent_rate(clk);
 	int ret;
 	u32 ctrl;
+	u32 cfg;
+	u32 pll_type;
 	unsigned long pllm;
 	u32 pllfm = 0;
 	unsigned long plld;
+	u32 freq_ctrl0;
+	u32 freq_ctrl1;
 	u32 div_ctrl;
 	u32 rem;
 	int shift;
@@ -212,16 +380,22 @@ static ulong ti_pll_clk_set_rate(struct clk *clk, ulong rate)
 				break;
 		}
 
-	/* Put PLL to bypass mode */
-	ctrl = readl(pll->base + PLL_16FFT_CTRL);
-	ctrl |= PLL_16FFT_CTRL_BYPASS_EN;
-	writel(ctrl, pll->base + PLL_16FFT_CTRL);
+	if (!ti_pll_clk_is_bypass(pll)) {
+		/* Put the PLL into bypass */
+		ti_pll_clk_bypass(pll, true);
+	}
+
+	/* Disable the PLL */
+	ti_pll_clk_disable(clk);
 
 	if (rate == parent_freq) {
 		debug("%s: put %s to bypass\n", __func__, clk->dev->name);
 		return rate;
 	}
 
+	cfg = readl(pll->base + PLL_16FFT_CFG);
+	pll_type = (cfg & PLL_16FFT_CFG_PLL_TYPE_MASK) >> PLL_16FFT_CFG_PLL_TYPE_SHIFT;
+
 	debug("%s: pre-frac-calc: rate=%u, parent_freq=%u, plld=%u, pllm=%u\n",
 	      __func__, (u32)rate, (u32)parent_freq, (u32)plld, (u32)pllm);
 
@@ -237,31 +411,75 @@ static ulong ti_pll_clk_set_rate(struct clk *clk, ulong rate)
 		plld = 1;
 	}
 
-	if (pllfm)
-		ctrl |= PLL_16FFT_CTRL_DSM_EN;
-	else
-		ctrl &= ~PLL_16FFT_CTRL_DSM_EN;
+	/* Program the new rate */
+	freq_ctrl0 = readl(pll->base + PLL_16FFT_FREQ_CTRL0);
+	freq_ctrl1 = readl(pll->base + PLL_16FFT_FREQ_CTRL1);
+	div_ctrl = readl(pll->base + PLL_16FFT_DIV_CTRL);
 
-	writel(pllm, pll->base + PLL_16FFT_FREQ_CTRL0);
-	writel(pllfm, pll->base + PLL_16FFT_FREQ_CTRL1);
+	freq_ctrl0 &= ~PLL_16FFT_FREQ_CTRL0_FB_DIV_INT_MASK;
+	freq_ctrl0 |= pllm;
+
+	freq_ctrl1 &= ~PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_MASK;
+	freq_ctrl1 |= pllfm;
 
 	/*
 	 * div_ctrl register contains other divider values, so rmw
 	 * only plld and leave existing values alone
 	 */
-	div_ctrl = readl(pll->base + PLL_16FFT_DIV_CTRL);
 	div_ctrl &= ~PLL_16FFT_DIV_CTRL_REF_DIV_MASK;
 	div_ctrl |= plld;
-	writel(div_ctrl, pll->base + PLL_16FFT_DIV_CTRL);
 
-	ctrl &= ~PLL_16FFT_CTRL_BYPASS_EN;
-	ctrl |= PLL_16FFT_CTRL_PLL_EN;
+	/* Make sure we have fractional support if required */
+	ctrl = readl(pll->base + PLL_16FFT_CTRL);
+
+	/* Don't use internal bypass,it is not glitch free. Always prefer glitchless bypass */
+	ctrl &= ~(PLL_16FFT_CTRL_INTL_BYP_EN | PLL_16FFT_CTRL_CLK_4PH_EN);
+
+	/* Always enable output if PLL,  Always bypass if we lose lock */
+	ctrl |= (PLL_16FFT_CTRL_CLK_POSTDIV_EN | PLL_16FFT_CTRL_BYP_ON_LOCKLOSS);
+
+	/* Enable fractional support if required */
+	if (pll_type == PLL_16FFT_CFG_PLL_TYPE_FRACF) {
+		if (pllfm != 0)
+			ctrl |= (PLL_16FFT_CTRL_DSM_EN | PLL_16FFT_CTRL_DAC_EN);
+		else
+			ctrl &= ~(PLL_16FFT_CTRL_DSM_EN | PLL_16FFT_CTRL_DAC_EN);
+	}
+
+	/* Enable Fractional by default for PLL_16FFT_CFG_PLL_TYPE_FRAC2 */
+	if (pll_type == PLL_16FFT_CFG_PLL_TYPE_FRAC2)
+		ctrl |= (PLL_16FFT_CTRL_DSM_EN | PLL_16FFT_CTRL_DAC_EN);
+
+	writel(freq_ctrl0, pll->base + PLL_16FFT_FREQ_CTRL0);
+	writel(freq_ctrl1, pll->base + PLL_16FFT_FREQ_CTRL1);
+	writel(div_ctrl, pll->base + PLL_16FFT_DIV_CTRL);
 	writel(ctrl, pll->base + PLL_16FFT_CTRL);
 
+	/* Configure PLL calibration*/
+	if (pll_type == PLL_16FFT_CFG_PLL_TYPE_FRACF) {
+		if (pllfm != 0) {
+			/* Disable Calibration in Fractional mode */
+			clk_pll_16fft_disable_cal(pll);
+		} else {
+			/* Enable Calibration in Integer mode */
+			clk_pll_16fft_cal_int(pll);
+		}
+	}
+
+	/*
+	 * Wait at least 1 ref cycle before enabling PLL.
+	 * Minimum VCO input frequency is 5MHz, therefore maximum
+	 * wait time for 1 ref clock is 0.2us.
+	 */
+	udelay(1);
+	ti_pll_clk_enable(clk);
+
 	ret = ti_pll_wait_for_lock(clk);
 	if (ret)
 		return ret;
 
+	ti_pll_clk_bypass(pll, false);
+
 	debug("%s: pllm=%u, plld=%u, pllfm=%u, parent_freq=%u\n",
 	      __func__, (u32)pllm, (u32)plld, (u32)pllfm, (u32)parent_freq);
 
@@ -279,30 +497,7 @@ static ulong ti_pll_clk_set_rate(struct clk *clk, ulong rate)
 	return current_freq;
 }
 
-static int ti_pll_clk_enable(struct clk *clk)
-{
-	struct ti_pll_clk *pll = to_clk_pll(clk);
-	u32 ctrl;
 
-	ctrl = readl(pll->base + PLL_16FFT_CTRL);
-	ctrl &= ~PLL_16FFT_CTRL_BYPASS_EN;
-	ctrl |= PLL_16FFT_CTRL_PLL_EN;
-	writel(ctrl, pll->base + PLL_16FFT_CTRL);
-
-	return ti_pll_wait_for_lock(clk);
-}
-
-static int ti_pll_clk_disable(struct clk *clk)
-{
-	struct ti_pll_clk *pll = to_clk_pll(clk);
-	u32 ctrl;
-
-	ctrl = readl(pll->base + PLL_16FFT_CTRL);
-	ctrl |= PLL_16FFT_CTRL_BYPASS_EN;
-	writel(ctrl, pll->base + PLL_16FFT_CTRL);
-
-	return 0;
-}
 
 static const struct clk_ops ti_pll_clk_ops = {
 	.get_rate = ti_pll_clk_get_rate,

-- 
2.46.0



More information about the U-Boot mailing list