From 23ee9c0f59dc2e837127f30c1eb5a8dbb83045fc Mon Sep 17 00:00:00 2001
From: Gianfranco Mariotti <gianfranco.mariotti@seco.com>
Date: Mon, 6 May 2024 17:03:56 +0200
Subject: [PATCH] [DRIVER] clk: imx: pll1443x: add PLL SSCG support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

In some situations, users might face EMI issues due to spikes around specific
frequencies: enabling Spread Spectrum Clocking is indicated to reduce the
electromagnetic peak around that specific frequency.

From the i.MX8MP SoC Reference Manual document, Section 5.1.8 "CCM Analog
Memory Map/Register Definition", the DRAM PLL, Audio PLL1, Audio PLL2 and
Video PLL1 have available the spread spectrum function.

The spread spectrum mode parameters of these PLLs can be calculated using
the formulas below:
* Modulation frequency: MF = Fin / p / mfr / (2^5) [Hz]
* Modulation rate: MR = mfr * mrr / m / (2^6) × 100 [%: percentage of the PLL output frequency]
* Where:
  * 0 <= mfr <= 255, 1 <= mrr <= 63, 0 <= mrr × mfr <= 512
  * Fin - PLL input clock frequency (typically, 24MHz = 24 * (10^6) Hz)
  * p = CCM_ANALOG_XXX_YYY_FDIV_CTL0[PLL_PRE_DIV] - reference frequency pre-divider value
  * m = CCM_ANALOG_XXX_YYY_FDIV_CTL0[PLL_MAIN_DIV] - PLL main divider value
  * mfr = CCM_ANALOG_XXX_YYY_SSCG_CTRL[PLL_MFREQ_CTL] - modulation frequency factor
  * mrr = CCM_ANALOG_XXX_YYY_SSCG_CTRL[PLL_MRAT_CTL] - modulation range factor

fsl,imx8mm-anatop optional properties:
 - anatop-<$1>,sscg-enable
   - type: boolean
   - <$1>: PLL clk name
   - description: enable SSCG
 - anatop-<$1>,mfr
   - type: u32
   - <$1>: PLL clk name
   - description: modulation frequency control value
 - anatop-<$1>,mrr
   - type: u32
   - <$1>: PLL clk name
   - description: modulation rate control value
 - anatop-<$1>,sel-pf
   - type: u32
   - <$1>: PLL clk name
   - description: modulation method control value

fsl,imx8mm-anatop dts example:
&anatop {
  anatop-video_pll1,sscg-enable;
  anatop-video_pll1,mfr = <12>;
  anatop-video_pll1,mrr = <14>;
  anatop-video_pll1,sel-pf = <2>;
};
---
 drivers/clk/imx/clk-pll14xx.c | 84 +++++++++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)

diff --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c
index 1303e2f5e0db89..a97d4b4b8ed70b 100644
--- a/drivers/clk/imx/clk-pll14xx.c
+++ b/drivers/clk/imx/clk-pll14xx.c
@@ -17,6 +17,7 @@
 #define GNRL_CTL	0x0
 #define FDIV_CTL0	0x4
 #define FDIV_CTL1	0x8
+#define SSCG_CTL	0xC
 #define LOCK_STATUS	BIT(31)
 #define LOCK_SEL_MASK	BIT(29)
 #define CLKE_MASK	BIT(11)
@@ -264,6 +265,12 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate,
 	struct clk_pll14xx *pll = to_clk_pll14xx(hw);
 	const struct imx_pll14xx_rate_table *rate;
 	u32 tmp, div_val;
+	u32 sscg_en, mfr, mrr, sel_pf, sscg_val;
+	u32 mfr_mrr, mf, mr_int, mr_frac;
+	struct device_node *np;
+	const char *clk_name;
+	char property[40];
+	u32 readbuf;
 	int ret;
 
 	rate = imx_get_pll_settings(pll, drate);
@@ -300,6 +307,83 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate,
 	writel_relaxed(div_val, pll->base + FDIV_CTL0);
 	writel_relaxed(rate->kdiv << KDIV_SHIFT, pll->base + FDIV_CTL1);
 
+	/* Enable SSCG */
+	np = of_find_compatible_node(NULL, NULL, "fsl,imx8mm-anatop");
+	if (np) {
+		clk_name = clk_hw_get_name(hw);
+
+		sprintf(property, "anatop-%s,sscg-enable", clk_name);
+		if (of_property_read_bool(np, property)) {
+			sscg_en = 1;
+			mfr, mrr, sel_pf = -1;
+
+			/* 0 <= mfr <= 255, 1 <= mrr <= 63, 0 <= mrr * mfr <= 512 */
+			sprintf(property, "anatop-%s,mfr", clk_name);
+			if (!of_property_read_u32(np, property, &readbuf)) {
+				mfr = readbuf;
+				if (!(mfr >= 0 && mfr <= 255)) {
+					sscg_en = 0;
+					pr_err("%s: SSCG not enabled for pll clk %s: mfr %d is out of range\n",
+							__func__, clk_name, mfr);
+				}
+			} else {
+				sscg_en = 0;
+				pr_err("%s: SSCG not enabled for pll clk %s: unable to read mfr\n",
+						__func__, clk_name);
+			}
+			sprintf(property, "anatop-%s,mrr", clk_name);
+			if (!of_property_read_u32(np, property, &readbuf)) {
+				mrr = readbuf;
+				if (!(mrr >= 1 && mrr <= 63)) {
+					sscg_en = 0;
+					pr_err("%s: SSCG not enabled for pll clk %s: mrr %d is out of range\n",
+							__func__, clk_name, mrr);
+				}
+				mfr_mrr = mfr * mrr;
+				if (!(mfr_mrr >= 0 && mfr_mrr <= 512)) {
+					sscg_en = 0;
+					pr_err("%s: SSCG not enabled for pll clk %s: mfr * mrr %d is out of range\n",
+							__func__, clk_name, mfr_mrr);
+				}
+			} else {
+				sscg_en = 0;
+				pr_err("%s: SSCG not enabled for pll clk %s: unable to read mrr\n",
+						__func__, clk_name);
+			}
+			/* sel_pf >= 0 && sel_pf <= 2 */
+			sprintf(property, "anatop-%s,sel-pf", clk_name);
+			if (!of_property_read_u32(np, property, &readbuf)) {
+				sel_pf = readbuf;
+				if (!(sel_pf >= 0 && sel_pf <= 2)) {
+					sscg_en = 0;
+					pr_err("%s: SSCG not enabled for pll clk %s: sel-pf %d is out of range\n",
+							__func__, clk_name, sel_pf);
+				}
+			} else {
+				sscg_en = 0;
+				pr_err("%s: SSCG not enabled for pll clk %s: unable to read sel-pf\n",
+						__func__, clk_name);
+			}
+
+			if (sscg_en) {
+				sscg_val = readl_relaxed(pll->base + SSCG_CTL);
+				sscg_val |= 1 << 31;   //SSCG_EN
+				sscg_val |= mfr << 12; //PLL_MFREQ_CTL
+				sscg_val |= mrr << 4;  //PLL_MRAT_CTL
+				sscg_val |= sel_pf;    //SEL_PF
+				writel_relaxed(sscg_val, pll->base + SSCG_CTL);
+
+				/* MF = Fin / p / mfr / (2^5) [Hz] */
+				mf = prate / (rate->pdiv * mfr * 32);
+				/* MR = mfr * mrr / m / (2^6) * 100 [%] */
+				mr_int = mfr_mrr * 1000 / (rate->mdiv * 64);
+				mr_frac = do_div(mr_int, 10);
+				pr_info("%s: SSCG enabled for pll clk %s: MF: %dHz, MR: %d.%d%%\n",
+						__func__, clk_name, mf, mr_int, mr_frac);
+			}
+		}
+	}
+
 	/*
 	 * According to SPEC, t3 - t2 need to be greater than
 	 * 1us and 1/FREF, respectively.
-- 
GitLab