[PATCH 3/4] clk: meson: g12a: add notifiers to handle cpu clock change

Neil Armstrong narmstrong at baylibre.com
Tue Jul 30 08:14:53 PDT 2019


On 30/07/2019 10:37, Jerome Brunet wrote:
> On Mon 29 Jul 2019 at 15:16, Neil Armstrong <narmstrong at baylibre.com> wrote:
> 
>> In order to implement clock switching for the CLKID_CPU_CLK and
>> CLKID_CPUB_CLK, notifiers are added on specific points of the
>> clock tree :
>>
>> cpu_clk / cpub_clk
>> |   \- cpu_clk_dyn
>> |      |  \- cpu_clk_premux0
>> |      |        |- cpu_clk_postmux0
>> |      |        |    |- cpu_clk_dyn0_div
>> |      |        |    \- xtal/fclk_div2/fclk_div3
>> |      |        \- xtal/fclk_div2/fclk_div3
>> |      \- cpu_clk_premux1
>> |            |- cpu_clk_postmux1
>> |            |    |- cpu_clk_dyn1_div
>> |            |    \- xtal/fclk_div2/fclk_div3
>> |            \- xtal/fclk_div2/fclk_div3
>> \ sys_pll / sys1_pll
>>
>> This for each cluster, a single one for G12A, two for G12B.
>>
>> Each cpu_clk_premux1 tree is marked as read-only and CLK_SET_RATE_NO_REPARENT,
>> to be used as "parking" clock in a safe clock frequency.
>>
>> A notifier is added on each cpu_clk_premux0 to detech when CCF want to
>> change the frequency of the cpu_clk_dyn tree.
>> In this notifier, the cpu_clk_premux1 tree is configured to use the xtal
>> clock and then the cpu_clk_dyn is switch to cpu_clk_premux1 while CCF
>> updates the cpu_clk_premux0 tree.
>>
>> A notifier is added on each sys_pll/sys1_pll to detect when CCF wants to
>> change the PLL clock source of the cpu_clk.
>> In this notifier, the cpu_clk is switched to cpu_clk_dyn while CCF
>> updates the sys_pll/sys1_pll frequency.
>>
>> A third small notifier is added on each cpu_clk / cpub_clk and cpu_clk_dyn,
>> add a small delay at PRE_RATE_CHANGE/POST_RATE_CHANGE to let the other
>> notofiers change propagate before changing the cpu_clk_premux0 and sys_pll
>> clock trees.
>>
>> This notifier set permits switching the cpu_clk / cpub_clk without any
>> glitches and using a safe parking clock while switching between sub-GHz
>> clocks using the cpu_clk_dyn tree.
>>
>> This setup has been tested and validated on the Amlogic G12A and G12B
>> SoCs running the arm64 cpuburn at [1] and cycling between all the possible
>> cpufreq translations of each cluster and checking the final frequency using
>> the clock-measurer, script at [2].
>>
>> [1] https://github.com/ssvb/cpuburn-arm/blob/master/cpuburn-a53.S
>> [2] https://gist.github.com/superna9999/d4de964dbc0f84b7d527e1df2ddea25f

Signoff missing.... will fix in v2

>> ---
>>  drivers/clk/meson/g12a.c | 567 +++++++++++++++++++++++++++++++++++----
>>  1 file changed, 521 insertions(+), 46 deletions(-)
>>
>> diff --git a/drivers/clk/meson/g12a.c b/drivers/clk/meson/g12a.c
>> index e4957fd9f91f..23162310c7ee 100644
>> --- a/drivers/clk/meson/g12a.c
>> +++ b/drivers/clk/meson/g12a.c
>> @@ -14,6 +14,7 @@
>>  #include <linux/init.h>
>>  #include <linux/of_device.h>
>>  #include <linux/platform_device.h>
>> +#include <linux/clk.h>
>>  
>>  #include "clk-mpll.h"
>>  #include "clk-pll.h"
>> @@ -88,16 +89,9 @@ static struct clk_regmap g12a_fixed_pll = {
>>  	},
>>  };
>>  
>> -/*
>> - * Internal sys pll emulation configuration parameters
>> - */
>> -static const struct reg_sequence g12a_sys_init_regs[] = {
>> -	{ .reg = HHI_SYS_PLL_CNTL1,	.def = 0x00000000 },
>> -	{ .reg = HHI_SYS_PLL_CNTL2,	.def = 0x00000000 },
>> -	{ .reg = HHI_SYS_PLL_CNTL3,	.def = 0x48681c00 },
>> -	{ .reg = HHI_SYS_PLL_CNTL4,	.def = 0x88770290 },
>> -	{ .reg = HHI_SYS_PLL_CNTL5,	.def = 0x39272000 },
>> -	{ .reg = HHI_SYS_PLL_CNTL6,	.def = 0x56540000 },
>> +static const struct pll_mult_range g12a_sys_pll_mult_range = {
>> +	.min = 128,
>> +	.max = 250,
>>  };
> 
> The init sequence is removed, I suppose you were concerned about
> glitching the clock on startup ?
> 
> Without the init sequence, it will inherit whatever is left by the
> bootloader. We have seen in the past that this is not desirable.
> 
> I'm mostly concerned about CNTL3 to CNTL6. Should we apply the sequence
> on .set_rate() instead ? It should be safe then ?
> 

This PLL (and the SYS1_PLL) cannot be disabled (thus re-initialized) until
the cpu has been parked on a safe clock, and the default beahavior of the
current G12A & G12B BL2 is to setup this PLL and the SYS1_PLL to start with
the cores at 1,2GHz. So we can consider theses PLLs has been correctly
initialized, thus removing the init code and still be safe.

>>
> 
> [...]
> 
>>  
>> @@ -364,16 +366,50 @@ static struct clk_regmap g12a_cpu_clk_premux1 = {
>>  	},
>>  	.hw.init = &(struct clk_init_data){
>>  		.name = "cpu_clk_dyn1_sel",
>> -		.ops = &clk_regmap_mux_ro_ops,
>> +		.ops = &clk_regmap_mux_ops,
>>  		.parent_data = (const struct clk_parent_data []) {
>>  			{ .fw_name = "xtal", },
>>  			{ .hw = &g12a_fclk_div2.hw },
>>  			{ .hw = &g12a_fclk_div3.hw },
>>  		},
>>  		.num_parents = 3,
>> +		/* This sub-tree is used a parking clock */
>> +		.flags = CLK_SET_RATE_NO_REPARENT
>>  	},
>>  };
>>  
>> +#define SYS_CPU_DYN_ENABLE	BIT(26)
>> +
>> +/* This divider uses bit 26 to take change in account */
>> +static int g12a_cpu_clk_mux0_div_set_rate(struct clk_hw *hw, unsigned long rate,
>> +					  unsigned long parent_rate)
>> +{
>> +	struct clk_regmap *clk = to_clk_regmap(hw);
>> +	struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
>> +	unsigned int val;
>> +	int ret;
>> +
>> +	ret = divider_get_val(rate, parent_rate, div->table, div->width,
>> +			      div->flags);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	val = (unsigned int)ret << div->shift;
>> +
>> +	regmap_update_bits(clk->map, HHI_SYS_CPU_CLK_CNTL0,
>> +			   SYS_CPU_DYN_ENABLE, SYS_CPU_DYN_ENABLE);
>> +
>> +	return regmap_update_bits(clk->map, div->offset,
>> +				  clk_div_mask(div->width) << div->shift |
>> +				  SYS_CPU_DYN_ENABLE, val);
>> +};
>> +
>> +const struct clk_ops g12a_cpu_clk_mux0_div_ops = {
>> +	.recalc_rate = clk_regmap_div_recalc_rate,
>> +	.round_rate = clk_regmap_div_round_rate,
>> +	.set_rate = g12a_cpu_clk_mux0_div_set_rate,
>> +};
> 
> I would prefer if we could keep the clock drivers and clock controllers
> separated.
> 
> Could you move the above above in another file ?

Yup, done

> 
>> +
> 
> [...]
> 
>>  
>> +/* This divider uses bit 26 to take change in account */
>> +static int g12b_cpub_clk_mux0_div_set_rate(struct clk_hw *hw,
>> +					   unsigned long rate,
>> +					   unsigned long parent_rate)
>> +{
>> +	struct clk_regmap *clk = to_clk_regmap(hw);
>> +	struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
>> +	unsigned int val;
>> +	int ret;
>> +
>> +	ret = divider_get_val(rate, parent_rate, div->table, div->width,
>> +			      div->flags);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	val = (unsigned int)ret << div->shift;
>> +
>> +	regmap_update_bits(clk->map, HHI_SYS_CPUB_CLK_CNTL,
> 
> Unless I missed something, this function is same as the g12a with the
> exception of the register address.
> 
> It seems this clock could have its own clock type and its own data
> structure to store the 'dyn enable' register parameter.

Yup done

> 
>> +			   SYS_CPU_DYN_ENABLE, SYS_CPU_DYN_ENABLE);
>> +
>> +	return regmap_update_bits(clk->map, div->offset,
>> +				  clk_div_mask(div->width) << div->shift |
>> +				  SYS_CPU_DYN_ENABLE, val);
>> +};
>> +
>> +static const struct clk_ops g12b_cpub_clk_mux0_div_ops = {
>> +	.recalc_rate = clk_regmap_div_recalc_rate,
>> +	.round_rate = clk_regmap_div_round_rate,
>> +	.set_rate = g12b_cpub_clk_mux0_div_set_rate,
>> +};
>> +
> 
> [...]
> 
>> +
>> +static int g12a_cpu_clk_postmux_notifier_cb(struct notifier_block *nb,
>> +					    unsigned long event, void *data)
>> +{
>> +	struct g12a_cpu_clk_postmux_nb_data *nb_data =
>> +		container_of(nb, struct g12a_cpu_clk_postmux_nb_data, nb);
>> +
>> +	switch (event) {
>> +	case PRE_RATE_CHANGE:
>> +		/*
>> +		 * This notifier means cpu_clk_postmux0 clock will be changed
>> +		 * to feed cpu_clk, this is the current path :
>> +		 * cpu_clk
>> +		 *    \- cpu_clk_dyn
>> +		 *          \- cpu_clk_postmux0
>> +		 *                \- cpu_clk_muxX_div
>> +		 *                      \- cpu_clk_premux0
>> +		 *				\- fclk_div3 or fclk_div2
>> +		 *		OR
>> +		 *                \- cpu_clk_premux0
>> +		 *			\- fclk_div3 or fclk_div2
>> +		 */
>> +
>> +		/* Setup cpu_clk_premux1 to xtal */
>> +		clk_hw_set_parent(nb_data->cpu_clk_premux1,
>> +				  nb_data->xtal);
>> +
>> +		/* Setup cpu_clk_postmux1 to bypass divider */
>> +		clk_hw_set_parent(nb_data->cpu_clk_postmux1,
>> +				  nb_data->cpu_clk_premux1);
>> +
>> +		/* Switch to parking clk on cpu_clk_postmux1 */
>> +		clk_hw_set_parent(nb_data->cpu_clk_dyn,
>> +				  nb_data->cpu_clk_postmux1);
>> +
>> +		/*
>> +		 * Now, cpu_clk is 24MHz in the current path :
>> +		 * cpu_clk
>> +		 *    \- cpu_clk_dyn
>> +		 *          \- cpu_clk_postmux1
>> +		 *                \- cpu_clk_premux1
>> +		 *                      \- xtal
>> +		 */
>> +
>> +		udelay(100);
> 
> Just curious about the this 100us delay. It seems fairly long, even at
> 24MHz. In your stress tests, have you tried shorter delays ? 10us maybe ?

Honestly no, I took the values from Amlogic implementation to be safe for
the first implementation, and I gave all my test scripts and utilities for
others to re-run them to eventually reduce these values to 10us in a second
time.

> 
>> +
>> +		return NOTIFY_OK;
>> +
>> +	case POST_RATE_CHANGE:
>> +		/*
>> +		 * The cpu_clk_postmux0 has ben updated, now switch back
>> +		 * cpu_clk_dyn to cpu_clk_postmux0 and take the changes
>> +		 * in account.
>> +		 */
>> +
>> +		/* Configure cpu_clk_dyn back to cpu_clk_postmux0 */
>> +		clk_hw_set_parent(nb_data->cpu_clk_dyn,
>> +				  nb_data->cpu_clk_postmux0);
>> +
>> +		/*
>> +		 * new path :
>> +		 * cpu_clk
>> +		 *    \- cpu_clk_dyn
>> +		 *          \- cpu_clk_postmux0
>> +		 *                \- cpu_clk_muxX_div
>> +		 *                      \- cpu_clk_premux0
>> +		 *				\- fclk_div3 or fclk_div2
>> +		 *		OR
>> +		 *                \- cpu_clk_premux0
>> +		 *			\- fclk_div3 or fclk_div2
>> +		 */
>> +
>> +		udelay(100);
>> +
>> +		return NOTIFY_OK;
>> +
>> +	default:
>> +		return NOTIFY_DONE;
>> +	}
>> +}
>> +

Thanks,
Neil



More information about the linux-amlogic mailing list