]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
clk: stm32f4: support spread spectrum clock generation
authorDario Binacchi <dario.binacchi@amarulasolutions.com>
Tue, 14 Jan 2025 18:19:49 +0000 (19:19 +0100)
committerStephen Boyd <sboyd@kernel.org>
Wed, 15 Jan 2025 23:17:05 +0000 (15:17 -0800)
Support spread spectrum clock generation for the main PLL, the only one
for which this functionality is available.

Tested on the STM32F469I-DISCO board.

Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com>
Link: https://lore.kernel.org/r/20250114182021.670435-5-dario.binacchi@amarulasolutions.com
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
drivers/clk/clk-stm32f4.c

index db1c56c8d54fe8d392cfb3b3f8906d1c0fd5436a..f476883bc93ba50debb8322648d3839d7c0dd16b 100644 (file)
@@ -35,6 +35,7 @@
 #define STM32F4_RCC_APB2ENR            0x44
 #define STM32F4_RCC_BDCR               0x70
 #define STM32F4_RCC_CSR                        0x74
+#define STM32F4_RCC_SSCGR              0x80
 #define STM32F4_RCC_PLLI2SCFGR         0x84
 #define STM32F4_RCC_PLLSAICFGR         0x88
 #define STM32F4_RCC_DCKCFGR            0x8c
 
 #define STM32F4_RCC_PLLCFGR_N_MASK     GENMASK(14, 6)
 
+#define STM32F4_RCC_SSCGR_SSCGEN       BIT(31)
+#define STM32F4_RCC_SSCGR_SPREADSEL    BIT(30)
+#define STM32F4_RCC_SSCGR_RESERVED_MASK        GENMASK(29, 28)
+#define STM32F4_RCC_SSCGR_INCSTEP_MASK GENMASK(27, 13)
+#define STM32F4_RCC_SSCGR_MODPER_MASK  GENMASK(12, 0)
+
 #define NONE -1
 #define NO_IDX  NONE
 #define NO_MUX  NONE
@@ -367,6 +374,16 @@ static const struct stm32f4_gate_data stm32f769_gates[] __initconst = {
        { STM32F4_RCC_APB2ENR, 30,      "mdio",         "apb2_div" },
 };
 
+enum stm32f4_pll_ssc_mod_type {
+       STM32F4_PLL_SSC_CENTER_SPREAD,
+       STM32F4_PLL_SSC_DOWN_SPREAD,
+};
+
+static const char * const stm32f4_ssc_mod_methods[] __initconst = {
+       [STM32F4_PLL_SSC_DOWN_SPREAD] = "down-spread",
+       [STM32F4_PLL_SSC_CENTER_SPREAD] = "center-spread",
+};
+
 /*
  * This bitmask tells us which bit offsets (0..192) on STM32F4[23]xxx
  * have gate bits associated with them. Its combined hweight is 71.
@@ -512,6 +529,12 @@ static const struct clk_div_table pll_divr_table[] = {
        { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 }, { 6, 6 }, { 7, 7 }, { 0 }
 };
 
+struct stm32f4_pll_ssc {
+       unsigned int mod_freq;
+       unsigned int mod_depth;
+       enum stm32f4_pll_ssc_mod_type mod_type;
+};
+
 struct stm32f4_pll {
        spinlock_t *lock;
        struct  clk_gate gate;
@@ -519,6 +542,8 @@ struct stm32f4_pll {
        u8 bit_rdy_idx;
        u8 status;
        u8 n_start;
+       bool ssc_enable;
+       struct stm32f4_pll_ssc ssc_conf;
 };
 
 #define to_stm32f4_pll(_gate) container_of(_gate, struct stm32f4_pll, gate)
@@ -541,6 +566,7 @@ struct stm32f4_vco_data {
        u8 offset;
        u8 bit_idx;
        u8 bit_rdy_idx;
+       bool sscg;
 };
 
 static const struct stm32f4_vco_data  vco_data[] = {
@@ -661,6 +687,32 @@ static long stm32f4_pll_round_rate(struct clk_hw *hw, unsigned long rate,
        return *prate * n;
 }
 
+static void stm32f4_pll_set_ssc(struct clk_hw *hw, unsigned long parent_rate,
+                               unsigned int ndiv)
+{
+       struct clk_gate *gate = to_clk_gate(hw);
+       struct stm32f4_pll *pll = to_stm32f4_pll(gate);
+       struct stm32f4_pll_ssc *ssc = &pll->ssc_conf;
+       u32 modeper, incstep;
+       u32 sscgr;
+
+       sscgr = readl(base + STM32F4_RCC_SSCGR);
+       /* reserved field must be kept at reset value */
+       sscgr &= STM32F4_RCC_SSCGR_RESERVED_MASK;
+
+       modeper = DIV_ROUND_CLOSEST(parent_rate, 4 * ssc->mod_freq);
+       incstep = DIV_ROUND_CLOSEST(((1 << 15) - 1) * ssc->mod_depth * ndiv,
+                                   5 * 10000 * modeper);
+       sscgr |= STM32F4_RCC_SSCGR_SSCGEN |
+               FIELD_PREP(STM32F4_RCC_SSCGR_INCSTEP_MASK, incstep) |
+               FIELD_PREP(STM32F4_RCC_SSCGR_MODPER_MASK, modeper);
+
+       if (ssc->mod_type)
+               sscgr |= STM32F4_RCC_SSCGR_SPREADSEL;
+
+       writel(sscgr, base + STM32F4_RCC_SSCGR);
+}
+
 static int stm32f4_pll_set_rate(struct clk_hw *hw, unsigned long rate,
                                unsigned long parent_rate)
 {
@@ -683,6 +735,9 @@ static int stm32f4_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 
        writel(val, base + pll->offset);
 
+       if (pll->ssc_enable)
+               stm32f4_pll_set_ssc(hw, parent_rate, n);
+
        if (pll_state)
                stm32f4_pll_enable(hw);
 
@@ -788,6 +843,84 @@ static struct clk_hw *clk_register_pll_div(const char *name,
        return hw;
 }
 
+static int __init stm32f4_pll_init_ssc(struct clk_hw *hw,
+                                      const struct stm32f4_pll_ssc *conf)
+{
+       struct clk_gate *gate = to_clk_gate(hw);
+       struct stm32f4_pll *pll = to_stm32f4_pll(gate);
+       struct clk_hw *parent;
+       unsigned long parent_rate;
+       int pll_state;
+       unsigned long n, val;
+
+       parent = clk_hw_get_parent(hw);
+       if (!parent) {
+               pr_err("%s: failed to get clock parent\n", __func__);
+               return -ENODEV;
+       }
+
+       parent_rate = clk_hw_get_rate(parent);
+
+       pll->ssc_enable = true;
+       memcpy(&pll->ssc_conf, conf, sizeof(pll->ssc_conf));
+
+       pll_state = stm32f4_pll_is_enabled(hw);
+
+       if (pll_state)
+               stm32f4_pll_disable(hw);
+
+       val = readl(base + pll->offset);
+       n = FIELD_GET(STM32F4_RCC_PLLCFGR_N_MASK, val);
+
+       pr_debug("%s: pll: %s, parent: %s, parent-rate: %lu, n: %lu\n",
+                __func__, clk_hw_get_name(hw), clk_hw_get_name(parent),
+                parent_rate, n);
+
+       stm32f4_pll_set_ssc(hw, parent_rate, n);
+
+       if (pll_state)
+               stm32f4_pll_enable(hw);
+
+       return 0;
+}
+
+static int __init stm32f4_pll_ssc_parse_dt(struct device_node *np,
+                                          struct stm32f4_pll_ssc *conf)
+{
+       int ret;
+       const char *s;
+
+       if (!conf)
+               return -EINVAL;
+
+       ret = of_property_read_u32(np, "st,ssc-modfreq-hz", &conf->mod_freq);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(np, "st,ssc-moddepth-permyriad",
+                                  &conf->mod_depth);
+       if (ret) {
+               pr_err("%pOF: missing st,ssc-moddepth-permyriad\n", np);
+               return ret;
+       }
+
+       ret = fwnode_property_match_property_string(of_fwnode_handle(np),
+                                                   "st,ssc-modmethod",
+                                                   stm32f4_ssc_mod_methods,
+                                                   ARRAY_SIZE(stm32f4_ssc_mod_methods));
+       if (ret < 0) {
+               pr_err("%pOF: failed to get st,ssc-modmethod\n", np);
+               return ret;
+       }
+
+       conf->mod_type = ret;
+
+       pr_debug("%pOF: SSCG settings: mod_freq: %d, mod_depth: %d mod_method: %s [%d]\n",
+                np, conf->mod_freq, conf->mod_depth, s, conf->mod_type);
+
+       return 0;
+}
+
 static struct clk_hw *stm32f4_rcc_register_pll(const char *pllsrc,
                const struct stm32f4_pll_data *data,  spinlock_t *lock)
 {
@@ -1695,7 +1828,8 @@ static void __init stm32f4_rcc_init(struct device_node *np)
        const struct of_device_id *match;
        const struct stm32f4_clk_data *data;
        unsigned long pllm;
-       struct clk_hw *pll_src_hw;
+       struct clk_hw *pll_src_hw, *pll_vco_hw;
+       struct stm32f4_pll_ssc ssc_conf;
 
        base = of_iomap(np, 0);
        if (!base) {
@@ -1754,8 +1888,8 @@ static void __init stm32f4_rcc_init(struct device_node *np)
        clk_hw_register_fixed_factor(NULL, "vco_in", pll_src,
                                     0, 1, pllm);
 
-       stm32f4_rcc_register_pll("vco_in", &data->pll_data[0],
-                       &stm32f4_clk_lock);
+       pll_vco_hw = stm32f4_rcc_register_pll("vco_in", &data->pll_data[0],
+                                             &stm32f4_clk_lock);
 
        clks[PLL_VCO_I2S] = stm32f4_rcc_register_pll("vco_in",
                        &data->pll_data[1], &stm32f4_clk_lock);
@@ -1900,6 +2034,9 @@ static void __init stm32f4_rcc_init(struct device_node *np)
 
        of_clk_add_hw_provider(np, stm32f4_rcc_lookup_clk, NULL);
 
+       if (!stm32f4_pll_ssc_parse_dt(np, &ssc_conf))
+               stm32f4_pll_init_ssc(pll_vco_hw, &ssc_conf);
+
        return;
 fail:
        kfree(clks);