[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