mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
clk: stm32f4: support spread spectrum clock generation
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>
This commit is contained in:
parent
a1328374d8
commit
65b3516dbe
1 changed files with 140 additions and 3 deletions
|
@ -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
|
||||
|
@ -42,6 +43,12 @@
|
|||
|
||||
#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);
|
||||
|
|
Loading…
Add table
Reference in a new issue