@@ -5,6 +5,7 @@
#define pr_fmt(fmt) "pll14xx: " fmt
+#include <dt-bindings/clock/clock.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk-provider.h>
@@ -22,6 +23,8 @@
#define GNRL_CTL 0x0
#define DIV_CTL0 0x4
#define DIV_CTL1 0x8
+#define SSCG_CTRL 0xc
+
#define LOCK_STATUS BIT(31)
#define LOCK_SEL_MASK BIT(29)
#define CLKE_MASK BIT(11)
@@ -33,6 +36,13 @@
#define KDIV_MASK GENMASK(15, 0)
#define KDIV_MIN SHRT_MIN
#define KDIV_MAX SHRT_MAX
+#define SSCG_ENABLE BIT(31)
+#define MFREQ_CTL_MASK GENMASK(19, 12)
+#define MRAT_CTL_MASK GENMASK(9, 4)
+#define SEL_PF_DOWN_SPREAD 0
+#define SEL_PF_UP_SPREAD 1
+#define SEL_PF_CENTER_SPREAD 2
+#define SEL_PF_MASK GENMASK(1, 0)
#define LOCK_TIMEOUT_US 10000
@@ -44,6 +54,7 @@ struct clk_pll14xx {
int rate_count;
s16 delta_k;
spinlock_t lock;
+ struct clk_spread_spectrum ss_conf;
};
#define to_clk_pll14xx(_hw) container_of(_hw, struct clk_pll14xx, hw)
@@ -366,6 +377,37 @@ static int clk_pll1416x_set_rate(struct clk_hw *hw, unsigned long drate,
return 0;
}
+static void __clk_pll1443x_set_spread_spectrum(struct clk_hw *hw,
+ unsigned long parent_rate,
+ unsigned int pdiv,
+ unsigned int mdiv)
+{
+ struct clk_pll14xx *pll = to_clk_pll14xx(hw);
+ struct clk_spread_spectrum *conf = &pll->ss_conf;
+ u32 sscg_ctrl, mfr, mrr, sel_pf;
+
+ if (conf->method == CLK_SSC_CENTER_SPREAD)
+ sel_pf = SEL_PF_CENTER_SPREAD;
+ else if (conf->method == CLK_SSC_UP_SPREAD)
+ sel_pf = SEL_PF_UP_SPREAD;
+ else if (conf->method == CLK_SSC_DOWN_SPREAD)
+ sel_pf = SEL_PF_DOWN_SPREAD;
+ else
+ return;
+
+ sscg_ctrl = readl_relaxed(pll->base + SSCG_CTRL);
+ sscg_ctrl &= ~(SSCG_ENABLE | MFREQ_CTL_MASK | MRAT_CTL_MASK | SEL_PF_MASK);
+
+ mfr = parent_rate / (conf->modfreq_hz * pdiv * BIT(5));
+ mrr = (conf->spread_bp * mdiv * BIT(6)) / (10000 * mfr);
+
+ sscg_ctrl |= SSCG_ENABLE | FIELD_PREP(MFREQ_CTL_MASK, mfr) |
+ FIELD_PREP(MRAT_CTL_MASK, mrr) |
+ FIELD_PREP(SEL_PF_MASK, sel_pf);
+
+ writel_relaxed(sscg_ctrl, pll->base + SSCG_CTRL);
+}
+
static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate,
unsigned long prate)
{
@@ -390,6 +432,9 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate,
writel_relaxed(FIELD_PREP(KDIV_MASK, rate.kdiv),
pll->base + DIV_CTL1);
+ __clk_pll1443x_set_spread_spectrum(hw, prate, rate.pdiv,
+ rate.mdiv);
+
spin_unlock_irqrestore(&pll->lock, flags);
return 0;
@@ -411,6 +456,8 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate,
writel_relaxed(FIELD_PREP(KDIV_MASK, rate.kdiv), pll->base + DIV_CTL1);
+ __clk_pll1443x_set_spread_spectrum(hw, prate, rate.pdiv, rate.mdiv);
+
spin_unlock_irqrestore(&pll->lock, flags);
/*
@@ -437,6 +484,16 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate,
return 0;
}
+static int clk_pll1443x_set_spread_spectrum(struct clk_hw *hw,
+ const struct clk_spread_spectrum *ss_conf)
+{
+ struct clk_pll14xx *pll = to_clk_pll14xx(hw);
+
+ memcpy(&pll->ss_conf, ss_conf, sizeof(pll->ss_conf));
+
+ return 0;
+}
+
static int clk_pll14xx_prepare(struct clk_hw *hw)
{
struct clk_pll14xx *pll = to_clk_pll14xx(hw);
@@ -509,6 +566,7 @@ static const struct clk_ops clk_pll1443x_ops = {
.recalc_rate = clk_pll14xx_recalc_rate,
.determine_rate = clk_pll1443x_determine_rate,
.set_rate = clk_pll1443x_set_rate,
+ .set_spread_spectrum = clk_pll1443x_set_spread_spectrum,
};
struct clk_hw *imx_dev_clk_hw_pll14xx(struct device *dev, const char *name,
Add support for spread spectrum clock (SSC) generation to the pll14xx driver. Tested on the video PLL of i.MX8MN and i.MX8MP based boards. Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com> --- Changes in v10: - Drop 'Reviewed-by' tag of Peng Fan. - Adapt the driver to the new infrastructure. Implement the set_spread_spectrum() and get the modulation parameters from struct clk_spread_spectrum. Changes in v9: - Add 'Reviewed-by' tag of Peng Fan. drivers/clk/imx/clk-pll14xx.c | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+)