Skip to content

Commit

Permalink
clk: stm32f4: support spread spectrum clock generation
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Dario Binacchi authored and Stephen Boyd committed Jan 15, 2025
1 parent a132837 commit 65b3516
Showing 1 changed file with 140 additions and 3 deletions.
143 changes: 140 additions & 3 deletions drivers/clk/clk-stm32f4.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,20 @@
#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 STM32F7_RCC_DCKCFGR2 0x90

#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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -512,13 +529,21 @@ 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;
u8 offset;
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)
Expand All @@ -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[] = {
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 65b3516

Please sign in to comment.