mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02: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_APB2ENR		0x44
 | 
				
			||||||
#define STM32F4_RCC_BDCR		0x70
 | 
					#define STM32F4_RCC_BDCR		0x70
 | 
				
			||||||
#define STM32F4_RCC_CSR			0x74
 | 
					#define STM32F4_RCC_CSR			0x74
 | 
				
			||||||
 | 
					#define STM32F4_RCC_SSCGR		0x80
 | 
				
			||||||
#define STM32F4_RCC_PLLI2SCFGR		0x84
 | 
					#define STM32F4_RCC_PLLI2SCFGR		0x84
 | 
				
			||||||
#define STM32F4_RCC_PLLSAICFGR		0x88
 | 
					#define STM32F4_RCC_PLLSAICFGR		0x88
 | 
				
			||||||
#define STM32F4_RCC_DCKCFGR		0x8c
 | 
					#define STM32F4_RCC_DCKCFGR		0x8c
 | 
				
			||||||
| 
						 | 
					@ -42,6 +43,12 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define STM32F4_RCC_PLLCFGR_N_MASK	GENMASK(14, 6)
 | 
					#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 NONE -1
 | 
				
			||||||
#define NO_IDX  NONE
 | 
					#define NO_IDX  NONE
 | 
				
			||||||
#define NO_MUX  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" },
 | 
						{ 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
 | 
					 * This bitmask tells us which bit offsets (0..192) on STM32F4[23]xxx
 | 
				
			||||||
 * have gate bits associated with them. Its combined hweight is 71.
 | 
					 * 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 }
 | 
						{ 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 {
 | 
					struct stm32f4_pll {
 | 
				
			||||||
	spinlock_t *lock;
 | 
						spinlock_t *lock;
 | 
				
			||||||
	struct	clk_gate gate;
 | 
						struct	clk_gate gate;
 | 
				
			||||||
| 
						 | 
					@ -519,6 +542,8 @@ struct stm32f4_pll {
 | 
				
			||||||
	u8 bit_rdy_idx;
 | 
						u8 bit_rdy_idx;
 | 
				
			||||||
	u8 status;
 | 
						u8 status;
 | 
				
			||||||
	u8 n_start;
 | 
						u8 n_start;
 | 
				
			||||||
 | 
						bool ssc_enable;
 | 
				
			||||||
 | 
						struct stm32f4_pll_ssc ssc_conf;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define to_stm32f4_pll(_gate) container_of(_gate, struct stm32f4_pll, gate)
 | 
					#define to_stm32f4_pll(_gate) container_of(_gate, struct stm32f4_pll, gate)
 | 
				
			||||||
| 
						 | 
					@ -541,6 +566,7 @@ struct stm32f4_vco_data {
 | 
				
			||||||
	u8 offset;
 | 
						u8 offset;
 | 
				
			||||||
	u8 bit_idx;
 | 
						u8 bit_idx;
 | 
				
			||||||
	u8 bit_rdy_idx;
 | 
						u8 bit_rdy_idx;
 | 
				
			||||||
 | 
						bool sscg;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct stm32f4_vco_data  vco_data[] = {
 | 
					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;
 | 
						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,
 | 
					static int stm32f4_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 | 
				
			||||||
				unsigned long parent_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);
 | 
						writel(val, base + pll->offset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (pll->ssc_enable)
 | 
				
			||||||
 | 
							stm32f4_pll_set_ssc(hw, parent_rate, n);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (pll_state)
 | 
						if (pll_state)
 | 
				
			||||||
		stm32f4_pll_enable(hw);
 | 
							stm32f4_pll_enable(hw);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -788,6 +843,84 @@ static struct clk_hw *clk_register_pll_div(const char *name,
 | 
				
			||||||
	return hw;
 | 
						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,
 | 
					static struct clk_hw *stm32f4_rcc_register_pll(const char *pllsrc,
 | 
				
			||||||
		const struct stm32f4_pll_data *data,  spinlock_t *lock)
 | 
							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 of_device_id *match;
 | 
				
			||||||
	const struct stm32f4_clk_data *data;
 | 
						const struct stm32f4_clk_data *data;
 | 
				
			||||||
	unsigned long pllm;
 | 
						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);
 | 
						base = of_iomap(np, 0);
 | 
				
			||||||
	if (!base) {
 | 
						if (!base) {
 | 
				
			||||||
| 
						 | 
					@ -1754,7 +1888,7 @@ static void __init stm32f4_rcc_init(struct device_node *np)
 | 
				
			||||||
	clk_hw_register_fixed_factor(NULL, "vco_in", pll_src,
 | 
						clk_hw_register_fixed_factor(NULL, "vco_in", pll_src,
 | 
				
			||||||
				     0, 1, pllm);
 | 
									     0, 1, pllm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	stm32f4_rcc_register_pll("vco_in", &data->pll_data[0],
 | 
						pll_vco_hw = stm32f4_rcc_register_pll("vco_in", &data->pll_data[0],
 | 
				
			||||||
					      &stm32f4_clk_lock);
 | 
										      &stm32f4_clk_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clks[PLL_VCO_I2S] = stm32f4_rcc_register_pll("vco_in",
 | 
						clks[PLL_VCO_I2S] = stm32f4_rcc_register_pll("vco_in",
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
						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;
 | 
						return;
 | 
				
			||||||
fail:
 | 
					fail:
 | 
				
			||||||
	kfree(clks);
 | 
						kfree(clks);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue