diff mbox series

[v4,06/11] clk: at91: clk-sam9x60-pll: allow runtime changes for pll

Message ID 1604655988-353-7-git-send-email-claudiu.beznea@microchip.com
State Superseded
Headers show
Series clk: at91: adapt for dvfs | expand

Commit Message

Claudiu Beznea Nov. 6, 2020, 9:46 a.m. UTC
Allow runtime frequency changes for PLLs registered with proper flags.
This is necessary for CPU PLL on SAMA7G5 which is used by DVFS.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-sam9x60-pll.c | 102 ++++++++++++++++++++++++++++++-------
 drivers/clk/at91/pmc.h             |   4 +-
 drivers/clk/at91/sam9x60.c         |  13 +++--
 drivers/clk/at91/sama7g5.c         |  48 ++++++++++-------
 4 files changed, 127 insertions(+), 40 deletions(-)

Comments

Stephen Boyd Nov. 14, 2020, 9:14 p.m. UTC | #1
Quoting Claudiu Beznea (2020-11-06 01:46:23)
> diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c

> index 78f458a7b2ef..6fe5d8530a0c 100644

> --- a/drivers/clk/at91/clk-sam9x60-pll.c

> +++ b/drivers/clk/at91/clk-sam9x60-pll.c

> @@ -225,8 +225,51 @@ static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate,

>                                      unsigned long parent_rate)

>  {

>         struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);

> +       struct sam9x60_frac *frac = to_sam9x60_frac(core);

> +       struct regmap *regmap = core->regmap;

> +       unsigned long irqflags, clkflags = clk_hw_get_flags(hw);

> +       unsigned int val, cfrac, cmul;

> +       long ret;

> +

> +       ret = sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);

> +       if (ret <= 0 || (clkflags & CLK_SET_RATE_GATE))


Is this function being called when the clk is enabled and it has the
CLK_SET_RATE_GATE flag set? I'm confused why this driver needs to check
this flag.
 
> +               return ret;

> +

> +       spin_lock_irqsave(core->lock, irqflags);

> +

> +       regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,

> +                          core->id);

> +       regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);

> +       cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;

> +       cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;

> +

> +       if (cmul == frac->mul && cfrac == frac->frac)

> +               goto unlock;

> +

> +       regmap_write(regmap, AT91_PMC_PLL_CTRL1,

> +                    (frac->mul << core->layout->mul_shift) |

> +                    (frac->frac << core->layout->frac_shift));

> +

> +       regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,

> +                          AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,

> +                          AT91_PMC_PLL_UPDT_UPDATE | core->id);

> +

> +       regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,

> +                          AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,

> +                          AT91_PMC_PLL_CTRL0_ENLOCK |

> +                          AT91_PMC_PLL_CTRL0_ENPLL);

> +

> +       regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,

> +                          AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,

> +                          AT91_PMC_PLL_UPDT_UPDATE | core->id);

>  

> -       return sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);

> +       while (!sam9x60_pll_ready(regmap, core->id))

> +               cpu_relax();

> +

> +unlock:

> +       spin_unlock_irqrestore(core->lock, irqflags);

> +

> +       return ret;

>  }

>  

>  static const struct clk_ops sam9x60_frac_pll_ops = {

> @@ -378,9 +421,39 @@ static int sam9x60_div_pll_set_rate(struct clk_hw *hw, unsigned long rate,

>  {

>         struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);

>         struct sam9x60_div *div = to_sam9x60_div(core);

> +       struct regmap *regmap = core->regmap;

> +       unsigned long irqflags, clkflags = clk_hw_get_flags(hw);

> +       unsigned int val, cdiv;

>  

>         div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1;

>  

> +       if (clkflags & CLK_SET_RATE_GATE)


Same comment.

> diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c

> index d685e22b2014..33faf7c6d9fb 100644

> --- a/drivers/clk/at91/sama7g5.c

> +++ b/drivers/clk/at91/sama7g5.c

> @@ -95,15 +95,15 @@ static const struct clk_pll_layout pll_layout_divio = {

>   * @p:         clock parent

>   * @l:         clock layout

>   * @t:         clock type

> - * @f:         true if clock is critical and cannot be disabled

> + * @f:         clock flags

>   * @eid:       export index in sama7g5->chws[] array

>   */

>  static const struct {

>         const char *n;

>         const char *p;

>         const struct clk_pll_layout *l;

> +       u32 f;


Why not unsigned long?

>         u8 t;

> -       u8 c;

>         u8 eid;

>  } sama7g5_plls[][PLL_ID_MAX] = {

>         [PLL_ID_CPU] = {

> @@ -111,13 +111,13 @@ static const struct {

>                   .p = "mainck",

>                   .l = &pll_layout_frac,

>                   .t = PLL_TYPE_FRAC,

> -                 .c = 1, },

> +                 .f = CLK_IS_CRITICAL, },

>  

>                 { .n = "cpupll_divpmcck",

>                   .p = "cpupll_fracck",

>                   .l = &pll_layout_divpmc,

>                   .t = PLL_TYPE_DIV,

> -                 .c = 1,

> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT,

>                   .eid = PMC_CPUPLL, },

>         },

>  

> @@ -126,13 +126,13 @@ static const struct {

>                   .p = "mainck",

>                   .l = &pll_layout_frac,

>                   .t = PLL_TYPE_FRAC,

> -                 .c = 1, },

> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },

>  

>                 { .n = "syspll_divpmcck",

>                   .p = "syspll_fracck",

>                   .l = &pll_layout_divpmc,

>                   .t = PLL_TYPE_DIV,

> -                 .c = 1,

> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,


Please indicate why clks are critical. Whenever the CLK_IS_CRITICAL flag
is used we should have a comment indicating why.

>                   .eid = PMC_SYSPLL, },

>         },

>
Claudiu Beznea Nov. 16, 2020, 11:24 a.m. UTC | #2
On 14.11.2020 23:14, Stephen Boyd wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe

> 

> Quoting Claudiu Beznea (2020-11-06 01:46:23)

>> diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c

>> index 78f458a7b2ef..6fe5d8530a0c 100644

>> --- a/drivers/clk/at91/clk-sam9x60-pll.c

>> +++ b/drivers/clk/at91/clk-sam9x60-pll.c

>> @@ -225,8 +225,51 @@ static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate,

>>                                      unsigned long parent_rate)

>>  {

>>         struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);

>> +       struct sam9x60_frac *frac = to_sam9x60_frac(core);

>> +       struct regmap *regmap = core->regmap;

>> +       unsigned long irqflags, clkflags = clk_hw_get_flags(hw);

>> +       unsigned int val, cfrac, cmul;

>> +       long ret;

>> +

>> +       ret = sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);

>> +       if (ret <= 0 || (clkflags & CLK_SET_RATE_GATE))

> 

> Is this function being called when the clk is enabled and it has the

> CLK_SET_RATE_GATE flag set?


Yes, this function could be called when CLK_SET_RATE_GATE is set.
On SAMA7G5 there are multiple PLL blocks of the same type. All these PLLs
are controlled by clk-sam9x60-pll.c driver. One of this PLL block fed the
CPU who's frequency could be changed at run time. At the same time there
are PLLs that fed hardware block not glitch free aware or that we don't
want to allow the rate change (this is the case of SAM9X60's CPU PLL, or
the DDR PLL on SAMA7G5).

I'm confused why this driver needs to check
> this flag.


Because we have multiple PLLs of the same type, some of them feed hardware
blocks that are glitch free aware of these PLLs' frequencies changes, some
feed hardware blocks that are not glitch free aware of PLLs' frequencies
changes or for some of them we don't want the frequency changes at all.

> 

>> +               return ret;

>> +

>> +       spin_lock_irqsave(core->lock, irqflags);

>> +

>> +       regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,

>> +                          core->id);

>> +       regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);

>> +       cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;

>> +       cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;

>> +

>> +       if (cmul == frac->mul && cfrac == frac->frac)

>> +               goto unlock;

>> +

>> +       regmap_write(regmap, AT91_PMC_PLL_CTRL1,

>> +                    (frac->mul << core->layout->mul_shift) |

>> +                    (frac->frac << core->layout->frac_shift));

>> +

>> +       regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,

>> +                          AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,

>> +                          AT91_PMC_PLL_UPDT_UPDATE | core->id);

>> +

>> +       regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,

>> +                          AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,

>> +                          AT91_PMC_PLL_CTRL0_ENLOCK |

>> +                          AT91_PMC_PLL_CTRL0_ENPLL);

>> +

>> +       regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,

>> +                          AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,

>> +                          AT91_PMC_PLL_UPDT_UPDATE | core->id);

>>

>> -       return sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);

>> +       while (!sam9x60_pll_ready(regmap, core->id))

>> +               cpu_relax();

>> +

>> +unlock:

>> +       spin_unlock_irqrestore(core->lock, irqflags);

>> +

>> +       return ret;

>>  }

>>

>>  static const struct clk_ops sam9x60_frac_pll_ops = {

>> @@ -378,9 +421,39 @@ static int sam9x60_div_pll_set_rate(struct clk_hw *hw, unsigned long rate,

>>  {

>>         struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);

>>         struct sam9x60_div *div = to_sam9x60_div(core);

>> +       struct regmap *regmap = core->regmap;

>> +       unsigned long irqflags, clkflags = clk_hw_get_flags(hw);

>> +       unsigned int val, cdiv;

>>

>>         div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1;

>>

>> +       if (clkflags & CLK_SET_RATE_GATE)

> 

> Same comment.

> 

>> diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c

>> index d685e22b2014..33faf7c6d9fb 100644

>> --- a/drivers/clk/at91/sama7g5.c

>> +++ b/drivers/clk/at91/sama7g5.c

>> @@ -95,15 +95,15 @@ static const struct clk_pll_layout pll_layout_divio = {

>>   * @p:         clock parent

>>   * @l:         clock layout

>>   * @t:         clock type

>> - * @f:         true if clock is critical and cannot be disabled

>> + * @f:         clock flags

>>   * @eid:       export index in sama7g5->chws[] array

>>   */

>>  static const struct {

>>         const char *n;

>>         const char *p;

>>         const struct clk_pll_layout *l;

>> +       u32 f;

> 

> Why not unsigned long?


I'll switch to unsigned long.

> 

>>         u8 t;

>> -       u8 c;

>>         u8 eid;

>>  } sama7g5_plls[][PLL_ID_MAX] = {

>>         [PLL_ID_CPU] = {

>> @@ -111,13 +111,13 @@ static const struct {

>>                   .p = "mainck",

>>                   .l = &pll_layout_frac,

>>                   .t = PLL_TYPE_FRAC,

>> -                 .c = 1, },

>> +                 .f = CLK_IS_CRITICAL, },

>>

>>                 { .n = "cpupll_divpmcck",

>>                   .p = "cpupll_fracck",

>>                   .l = &pll_layout_divpmc,

>>                   .t = PLL_TYPE_DIV,

>> -                 .c = 1,

>> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT,

>>                   .eid = PMC_CPUPLL, },

>>         },

>>

>> @@ -126,13 +126,13 @@ static const struct {

>>                   .p = "mainck",

>>                   .l = &pll_layout_frac,

>>                   .t = PLL_TYPE_FRAC,

>> -                 .c = 1, },

>> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },

>>

>>                 { .n = "syspll_divpmcck",

>>                   .p = "syspll_fracck",

>>                   .l = &pll_layout_divpmc,

>>                   .t = PLL_TYPE_DIV,

>> -                 .c = 1,

>> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,

> 

> Please indicate why clks are critical.


Sure! I'll do it in next version. I chose it like this because they are
feeding critical parts of the system like CPU or memory.

> Whenever the CLK_IS_CRITICAL flag

> is used we should have a comment indicating why.


I was not aware of this rule. I'll update the code accordingly.

Thank you,
Claudiu Beznea

> 

>>                   .eid = PMC_SYSPLL, },

>>         },

>>
Stephen Boyd Nov. 18, 2020, 1:49 a.m. UTC | #3
Quoting Claudiu.Beznea@microchip.com (2020-11-16 03:24:54)
> 

> 

> On 14.11.2020 23:14, Stephen Boyd wrote:

> > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe

> > 

> > Quoting Claudiu Beznea (2020-11-06 01:46:23)

> >> diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c

> >> index 78f458a7b2ef..6fe5d8530a0c 100644

> >> --- a/drivers/clk/at91/clk-sam9x60-pll.c

> >> +++ b/drivers/clk/at91/clk-sam9x60-pll.c

> >> @@ -225,8 +225,51 @@ static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate,

> >>                                      unsigned long parent_rate)

> >>  {

> >>         struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);

> >> +       struct sam9x60_frac *frac = to_sam9x60_frac(core);

> >> +       struct regmap *regmap = core->regmap;

> >> +       unsigned long irqflags, clkflags = clk_hw_get_flags(hw);

> >> +       unsigned int val, cfrac, cmul;

> >> +       long ret;

> >> +

> >> +       ret = sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);

> >> +       if (ret <= 0 || (clkflags & CLK_SET_RATE_GATE))

> > 

> > Is this function being called when the clk is enabled and it has the

> > CLK_SET_RATE_GATE flag set?

> 

> Yes, this function could be called when CLK_SET_RATE_GATE is set.

> On SAMA7G5 there are multiple PLL blocks of the same type. All these PLLs

> are controlled by clk-sam9x60-pll.c driver. One of this PLL block fed the

> CPU who's frequency could be changed at run time. At the same time there

> are PLLs that fed hardware block not glitch free aware or that we don't

> want to allow the rate change (this is the case of SAM9X60's CPU PLL, or

> the DDR PLL on SAMA7G5).

> 

> I'm confused why this driver needs to check

> > this flag.

> 

> Because we have multiple PLLs of the same type, some of them feed hardware

> blocks that are glitch free aware of these PLLs' frequencies changes, some

> feed hardware blocks that are not glitch free aware of PLLs' frequencies

> changes or for some of them we don't want the frequency changes at all.


Can we have different clk_ops for the different types of PLLs? It looks
like the flag is being used to overload this function to do different
things depending on how the flags are set. What happens if we decide to
change the semantics of this clk flag? How does it map to this driver?
Ideally this driver shouldn't need to worry about this flag, at least
not in this function, except to figure out if it should do something
different like not write the value to the hardware or something like
that.

The flag indicates to the clk framework that this clk should be gated
when clk_set_rate() is called on it. The driver should be able to figure
out that the clk is disabled by reading the hardware here and checking
the enable state, or it could just have different clk_ops for the
different type of PLL and do something different without checking the
flag. Either way, checking the flag looks wrong.

> >> -                 .c = 1,

> >> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,

> > 

> > Please indicate why clks are critical.

> 

> Sure! I'll do it in next version. I chose it like this because they are

> feeding critical parts of the system like CPU or memory.

> 

> > Whenever the CLK_IS_CRITICAL flag

> > is used we should have a comment indicating why.

> 

> I was not aware of this rule. I'll update the code accordingly.


Sorry. I should put a document comment next to the flag.
Claudiu Beznea Nov. 18, 2020, 8:58 a.m. UTC | #4
On 18.11.2020 03:49, Stephen Boyd wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe

> 

> Quoting Claudiu.Beznea@microchip.com (2020-11-16 03:24:54)

>>

>>

>> On 14.11.2020 23:14, Stephen Boyd wrote:

>>> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe

>>>

>>> Quoting Claudiu Beznea (2020-11-06 01:46:23)

>>>> diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c

>>>> index 78f458a7b2ef..6fe5d8530a0c 100644

>>>> --- a/drivers/clk/at91/clk-sam9x60-pll.c

>>>> +++ b/drivers/clk/at91/clk-sam9x60-pll.c

>>>> @@ -225,8 +225,51 @@ static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate,

>>>>                                      unsigned long parent_rate)

>>>>  {

>>>>         struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);

>>>> +       struct sam9x60_frac *frac = to_sam9x60_frac(core);

>>>> +       struct regmap *regmap = core->regmap;

>>>> +       unsigned long irqflags, clkflags = clk_hw_get_flags(hw);

>>>> +       unsigned int val, cfrac, cmul;

>>>> +       long ret;

>>>> +

>>>> +       ret = sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);

>>>> +       if (ret <= 0 || (clkflags & CLK_SET_RATE_GATE))

>>>

>>> Is this function being called when the clk is enabled and it has the

>>> CLK_SET_RATE_GATE flag set?

>>

>> Yes, this function could be called when CLK_SET_RATE_GATE is set.

>> On SAMA7G5 there are multiple PLL blocks of the same type. All these PLLs

>> are controlled by clk-sam9x60-pll.c driver. One of this PLL block fed the

>> CPU who's frequency could be changed at run time. At the same time there

>> are PLLs that fed hardware block not glitch free aware or that we don't

>> want to allow the rate change (this is the case of SAM9X60's CPU PLL, or

>> the DDR PLL on SAMA7G5).

>>

>> I'm confused why this driver needs to check

>>> this flag.

>>

>> Because we have multiple PLLs of the same type, some of them feed hardware

>> blocks that are glitch free aware of these PLLs' frequencies changes, some

>> feed hardware blocks that are not glitch free aware of PLLs' frequencies

>> changes or for some of them we don't want the frequency changes at all.

> 

> Can we have different clk_ops for the different types of PLLs?


Sure! I'll switch to this way.

Thank you for your feedback,
Claudiu Beznea

> It looks

> like the flag is being used to overload this function to do different

> things depending on how the flags are set. What happens if we decide to

> change the semantics of this clk flag? How does it map to this driver?

> Ideally this driver shouldn't need to worry about this flag, at least

> not in this function, except to figure out if it should do something

> different like not write the value to the hardware or something like

> that.

> 

> The flag indicates to the clk framework that this clk should be gated

> when clk_set_rate() is called on it. The driver should be able to figure

> out that the clk is disabled by reading the hardware here and checking

> the enable state, or it could just have different clk_ops for the

> different type of PLL and do something different without checking the

> flag. Either way, checking the flag looks wrong.

> 

>>>> -                 .c = 1,

>>>> +                 .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,

>>>

>>> Please indicate why clks are critical.

>>

>> Sure! I'll do it in next version. I chose it like this because they are

>> feeding critical parts of the system like CPU or memory.

>>

>>> Whenever the CLK_IS_CRITICAL flag

>>> is used we should have a comment indicating why.

>>

>> I was not aware of this rule. I'll update the code accordingly.

> 

> Sorry. I should put a document comment next to the flag.

>
diff mbox series

Patch

diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
index 78f458a7b2ef..6fe5d8530a0c 100644
--- a/drivers/clk/at91/clk-sam9x60-pll.c
+++ b/drivers/clk/at91/clk-sam9x60-pll.c
@@ -225,8 +225,51 @@  static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 				     unsigned long parent_rate)
 {
 	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+	struct sam9x60_frac *frac = to_sam9x60_frac(core);
+	struct regmap *regmap = core->regmap;
+	unsigned long irqflags, clkflags = clk_hw_get_flags(hw);
+	unsigned int val, cfrac, cmul;
+	long ret;
+
+	ret = sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);
+	if (ret <= 0 || (clkflags & CLK_SET_RATE_GATE))
+		return ret;
+
+	spin_lock_irqsave(core->lock, irqflags);
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+			   core->id);
+	regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);
+	cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
+	cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
+
+	if (cmul == frac->mul && cfrac == frac->frac)
+		goto unlock;
+
+	regmap_write(regmap, AT91_PMC_PLL_CTRL1,
+		     (frac->mul << core->layout->mul_shift) |
+		     (frac->frac << core->layout->frac_shift));
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
+			   AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,
+			   AT91_PMC_PLL_CTRL0_ENLOCK |
+			   AT91_PMC_PLL_CTRL0_ENPLL);
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
 
-	return sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);
+	while (!sam9x60_pll_ready(regmap, core->id))
+		cpu_relax();
+
+unlock:
+	spin_unlock_irqrestore(core->lock, irqflags);
+
+	return ret;
 }
 
 static const struct clk_ops sam9x60_frac_pll_ops = {
@@ -378,9 +421,39 @@  static int sam9x60_div_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 {
 	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
 	struct sam9x60_div *div = to_sam9x60_div(core);
+	struct regmap *regmap = core->regmap;
+	unsigned long irqflags, clkflags = clk_hw_get_flags(hw);
+	unsigned int val, cdiv;
 
 	div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1;
 
+	if (clkflags & CLK_SET_RATE_GATE)
+		return 0;
+
+	spin_lock_irqsave(core->lock, irqflags);
+	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+			   core->id);
+	regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
+	cdiv = (val & core->layout->div_mask) >> core->layout->div_shift;
+
+	/* Stop if nothing changed. */
+	if (cdiv == div->div)
+		goto unlock;
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
+			   core->layout->div_mask,
+			   (div->div << core->layout->div_shift));
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+	while (!sam9x60_pll_ready(regmap, core->id))
+		cpu_relax();
+
+unlock:
+	spin_unlock_irqrestore(core->lock, irqflags);
+
 	return 0;
 }
 
@@ -398,12 +471,12 @@  sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
 			      const char *name, const char *parent_name,
 			      struct clk_hw *parent_hw, u8 id,
 			      const struct clk_pll_characteristics *characteristics,
-			      const struct clk_pll_layout *layout, bool critical)
+			      const struct clk_pll_layout *layout, u32 flags)
 {
 	struct sam9x60_frac *frac;
 	struct clk_hw *hw;
 	struct clk_init_data init;
-	unsigned long parent_rate, flags;
+	unsigned long parent_rate, irqflags;
 	unsigned int val;
 	int ret;
 
@@ -418,9 +491,7 @@  sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
 	init.parent_names = &parent_name;
 	init.num_parents = 1;
 	init.ops = &sam9x60_frac_pll_ops;
-	init.flags = CLK_SET_RATE_GATE;
-	if (critical)
-		init.flags |= CLK_IS_CRITICAL;
+	init.flags = flags;
 
 	frac->core.id = id;
 	frac->core.hw.init = &init;
@@ -429,7 +500,7 @@  sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
 	frac->core.regmap = regmap;
 	frac->core.lock = lock;
 
-	spin_lock_irqsave(frac->core.lock, flags);
+	spin_lock_irqsave(frac->core.lock, irqflags);
 	if (sam9x60_pll_ready(regmap, id)) {
 		regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
 				   AT91_PMC_PLL_UPDT_ID_MSK, id);
@@ -457,7 +528,7 @@  sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
 			goto free;
 		}
 	}
-	spin_unlock_irqrestore(frac->core.lock, flags);
+	spin_unlock_irqrestore(frac->core.lock, irqflags);
 
 	hw = &frac->core.hw;
 	ret = clk_hw_register(NULL, hw);
@@ -469,7 +540,7 @@  sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
 	return hw;
 
 free:
-	spin_unlock_irqrestore(frac->core.lock, flags);
+	spin_unlock_irqrestore(frac->core.lock, irqflags);
 	kfree(frac);
 	return hw;
 }
@@ -478,12 +549,12 @@  struct clk_hw * __init
 sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 			     const char *name, const char *parent_name, u8 id,
 			     const struct clk_pll_characteristics *characteristics,
-			     const struct clk_pll_layout *layout, bool critical)
+			     const struct clk_pll_layout *layout, u32 flags)
 {
 	struct sam9x60_div *div;
 	struct clk_hw *hw;
 	struct clk_init_data init;
-	unsigned long flags;
+	unsigned long irqflags;
 	unsigned int val;
 	int ret;
 
@@ -498,10 +569,7 @@  sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 	init.parent_names = &parent_name;
 	init.num_parents = 1;
 	init.ops = &sam9x60_div_pll_ops;
-	init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
-		     CLK_SET_RATE_PARENT;
-	if (critical)
-		init.flags |= CLK_IS_CRITICAL;
+	init.flags = flags;
 
 	div->core.id = id;
 	div->core.hw.init = &init;
@@ -510,14 +578,14 @@  sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 	div->core.regmap = regmap;
 	div->core.lock = lock;
 
-	spin_lock_irqsave(div->core.lock, flags);
+	spin_lock_irqsave(div->core.lock, irqflags);
 
 	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
 			   AT91_PMC_PLL_UPDT_ID_MSK, id);
 	regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
 	div->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val);
 
-	spin_unlock_irqrestore(div->core.lock, flags);
+	spin_unlock_irqrestore(div->core.lock, irqflags);
 
 	hw = &div->core.hw;
 	ret = clk_hw_register(NULL, hw);
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 0a9364bde339..bedcd85ad750 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -190,14 +190,14 @@  struct clk_hw * __init
 sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 			     const char *name, const char *parent_name, u8 id,
 			     const struct clk_pll_characteristics *characteristics,
-			     const struct clk_pll_layout *layout, bool critical);
+			     const struct clk_pll_layout *layout, u32 flags);
 
 struct clk_hw * __init
 sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
 			      const char *name, const char *parent_name,
 			      struct clk_hw *parent_hw, u8 id,
 			      const struct clk_pll_characteristics *characteristics,
-			      const struct clk_pll_layout *layout, bool critical);
+			      const struct clk_pll_layout *layout, u32 flags);
 
 struct clk_hw * __init
 at91_clk_register_programmable(struct regmap *regmap, const char *name,
diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c
index 3c4c95603595..e5094c69e606 100644
--- a/drivers/clk/at91/sam9x60.c
+++ b/drivers/clk/at91/sam9x60.c
@@ -228,13 +228,15 @@  static void __init sam9x60_pmc_setup(struct device_node *np)
 	hw = sam9x60_clk_register_frac_pll(regmap, &pmc_pll_lock, "pllack_fracck",
 					   "mainck", sam9x60_pmc->chws[PMC_MAIN],
 					   0, &plla_characteristics,
-					   &pll_frac_layout, true);
+					   &pll_frac_layout,
+					   CLK_IS_CRITICAL | CLK_SET_RATE_GATE);
 	if (IS_ERR(hw))
 		goto err_free;
 
 	hw = sam9x60_clk_register_div_pll(regmap, &pmc_pll_lock, "pllack_divck",
 					  "pllack_fracck", 0, &plla_characteristics,
-					  &pll_div_layout, true);
+					  &pll_div_layout,
+					  CLK_IS_CRITICAL | CLK_SET_RATE_GATE);
 	if (IS_ERR(hw))
 		goto err_free;
 
@@ -243,13 +245,16 @@  static void __init sam9x60_pmc_setup(struct device_node *np)
 	hw = sam9x60_clk_register_frac_pll(regmap, &pmc_pll_lock, "upllck_fracck",
 					   "main_osc", main_osc_hw, 1,
 					   &upll_characteristics,
-					   &pll_frac_layout, false);
+					   &pll_frac_layout, CLK_SET_RATE_GATE);
 	if (IS_ERR(hw))
 		goto err_free;
 
 	hw = sam9x60_clk_register_div_pll(regmap, &pmc_pll_lock, "upllck_divck",
 					  "upllck_fracck", 1, &upll_characteristics,
-					  &pll_div_layout, false);
+					  &pll_div_layout,
+					  CLK_SET_RATE_GATE |
+					  CLK_SET_PARENT_GATE |
+					  CLK_SET_RATE_PARENT);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
index d685e22b2014..33faf7c6d9fb 100644
--- a/drivers/clk/at91/sama7g5.c
+++ b/drivers/clk/at91/sama7g5.c
@@ -95,15 +95,15 @@  static const struct clk_pll_layout pll_layout_divio = {
  * @p:		clock parent
  * @l:		clock layout
  * @t:		clock type
- * @f:		true if clock is critical and cannot be disabled
+ * @f:		clock flags
  * @eid:	export index in sama7g5->chws[] array
  */
 static const struct {
 	const char *n;
 	const char *p;
 	const struct clk_pll_layout *l;
+	u32 f;
 	u8 t;
-	u8 c;
 	u8 eid;
 } sama7g5_plls[][PLL_ID_MAX] = {
 	[PLL_ID_CPU] = {
@@ -111,13 +111,13 @@  static const struct {
 		  .p = "mainck",
 		  .l = &pll_layout_frac,
 		  .t = PLL_TYPE_FRAC,
-		  .c = 1, },
+		  .f = CLK_IS_CRITICAL, },
 
 		{ .n = "cpupll_divpmcck",
 		  .p = "cpupll_fracck",
 		  .l = &pll_layout_divpmc,
 		  .t = PLL_TYPE_DIV,
-		  .c = 1,
+		  .f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT,
 		  .eid = PMC_CPUPLL, },
 	},
 
@@ -126,13 +126,13 @@  static const struct {
 		  .p = "mainck",
 		  .l = &pll_layout_frac,
 		  .t = PLL_TYPE_FRAC,
-		  .c = 1, },
+		  .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },
 
 		{ .n = "syspll_divpmcck",
 		  .p = "syspll_fracck",
 		  .l = &pll_layout_divpmc,
 		  .t = PLL_TYPE_DIV,
-		  .c = 1,
+		  .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,
 		  .eid = PMC_SYSPLL, },
 	},
 
@@ -141,55 +141,66 @@  static const struct {
 		  .p = "mainck",
 		  .l = &pll_layout_frac,
 		  .t = PLL_TYPE_FRAC,
-		  .c = 1, },
+		  .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },
 
 		{ .n = "ddrpll_divpmcck",
 		  .p = "ddrpll_fracck",
 		  .l = &pll_layout_divpmc,
 		  .t = PLL_TYPE_DIV,
-		  .c = 1, },
+		  .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },
 	},
 
 	[PLL_ID_IMG] = {
 		{ .n = "imgpll_fracck",
 		  .p = "mainck",
 		  .l = &pll_layout_frac,
-		  .t = PLL_TYPE_FRAC, },
+		  .t = PLL_TYPE_FRAC,
+		  .f = CLK_SET_RATE_GATE, },
 
 		{ .n = "imgpll_divpmcck",
 		  .p = "imgpll_fracck",
 		  .l = &pll_layout_divpmc,
-		  .t = PLL_TYPE_DIV, },
+		  .t = PLL_TYPE_DIV,
+		  .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+		       CLK_SET_RATE_PARENT, },
 	},
 
 	[PLL_ID_BAUD] = {
 		{ .n = "baudpll_fracck",
 		  .p = "mainck",
 		  .l = &pll_layout_frac,
-		  .t = PLL_TYPE_FRAC, },
+		  .t = PLL_TYPE_FRAC,
+		  .f = CLK_SET_RATE_GATE, },
 
 		{ .n = "baudpll_divpmcck",
 		  .p = "baudpll_fracck",
 		  .l = &pll_layout_divpmc,
-		  .t = PLL_TYPE_DIV, },
+		  .t = PLL_TYPE_DIV,
+		  .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+		       CLK_SET_RATE_PARENT, },
 	},
 
 	[PLL_ID_AUDIO] = {
 		{ .n = "audiopll_fracck",
 		  .p = "main_xtal",
 		  .l = &pll_layout_frac,
-		  .t = PLL_TYPE_FRAC, },
+		  .t = PLL_TYPE_FRAC,
+		  .f = CLK_SET_RATE_GATE, },
 
 		{ .n = "audiopll_divpmcck",
 		  .p = "audiopll_fracck",
 		  .l = &pll_layout_divpmc,
 		  .t = PLL_TYPE_DIV,
+		  .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+		       CLK_SET_RATE_PARENT,
 		  .eid = PMC_AUDIOPMCPLL, },
 
 		{ .n = "audiopll_diviock",
 		  .p = "audiopll_fracck",
 		  .l = &pll_layout_divio,
 		  .t = PLL_TYPE_DIV,
+		  .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+		       CLK_SET_RATE_PARENT,
 		  .eid = PMC_AUDIOIOPLL, },
 	},
 
@@ -197,12 +208,15 @@  static const struct {
 		{ .n = "ethpll_fracck",
 		  .p = "main_xtal",
 		  .l = &pll_layout_frac,
-		  .t = PLL_TYPE_FRAC, },
+		  .t = PLL_TYPE_FRAC,
+		  .f = CLK_SET_RATE_GATE, },
 
 		{ .n = "ethpll_divpmcck",
 		  .p = "ethpll_fracck",
 		  .l = &pll_layout_divpmc,
-		  .t = PLL_TYPE_DIV, },
+		  .t = PLL_TYPE_DIV,
+		  .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+		       CLK_SET_RATE_PARENT, },
 	},
 };
 
@@ -890,7 +904,7 @@  static void __init sama7g5_pmc_setup(struct device_node *np)
 					sama7g5_plls[i][j].p, parent_hw, i,
 					&pll_characteristics,
 					sama7g5_plls[i][j].l,
-					sama7g5_plls[i][j].c);
+					sama7g5_plls[i][j].f);
 				break;
 
 			case PLL_TYPE_DIV:
@@ -899,7 +913,7 @@  static void __init sama7g5_pmc_setup(struct device_node *np)
 					sama7g5_plls[i][j].p, i,
 					&pll_characteristics,
 					sama7g5_plls[i][j].l,
-					sama7g5_plls[i][j].c);
+					sama7g5_plls[i][j].f);
 				break;
 
 			default: