Commit 1e229c21 authored by Claudiu Beznea's avatar Claudiu Beznea Committed by Stephen Boyd

clk: at91: clk-sam9x60-pll: add notifier for div part of PLL

SAM9X60's PLL which is also part of SAMA7G5 is composed of 2 parts:
one fractional part and one divider. On SAMA7G5 the CPU PLL could be
changed at run-time to implement DVFS. The hardware clock tree on
SAMA7G5 for CPU PLL is as follows:

                       +---- div1 ----------------> cpuck
                       |
FRAC PLL ---> DIV PLL -+-> prescaler ---> div0 ---> mck0

The div1 block is not implemented in Linux; on prescaler block it has
been discovered a bug on some scenarios and will be removed from Linux
in next commits. Thus, the final clock tree that will be used in Linux
will be as follows:

                       +-----------> cpuck
                       |
FRAC PLL ---> DIV PLL -+-> div0 ---> mck0

It has been proposed in [1] to not introduce a new CPUFreq driver but
to overload the proper clock drivers with proper operation such that
cpufreq-dt to be used. To accomplish this DIV PLL and div0 implement
clock notifiers which applies safe dividers before FRAC PLL is changed.
The current commit treats only the DIV PLL by adding a notifier that
sets a safe divider on PRE_RATE_CHANGE events. The safe divider is
provided by initialization clock code (sama7g5.c). The div0 is treated
in next commits (to keep the changes as clean as possible).

[1] https://lore.kernel.org/lkml/20210105104426.4tmgc2l3vyicwedd@vireshk-i7/Signed-off-by: default avatarClaudiu Beznea <claudiu.beznea@microchip.com>
Link: https://lore.kernel.org/r/20211011112719.3951784-12-claudiu.beznea@microchip.comAcked-by: default avatarNicolas Ferre <nicolas.ferre@microchip.com>
Signed-off-by: default avatarStephen Boyd <sboyd@kernel.org>
parent 0ef99f82
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
#include <linux/bitfield.h> #include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clk-provider.h> #include <linux/clk-provider.h>
#include <linux/clkdev.h> #include <linux/clkdev.h>
#include <linux/clk/at91_pmc.h> #include <linux/clk/at91_pmc.h>
...@@ -47,12 +48,15 @@ struct sam9x60_div { ...@@ -47,12 +48,15 @@ struct sam9x60_div {
struct sam9x60_pll_core core; struct sam9x60_pll_core core;
struct at91_clk_pms pms; struct at91_clk_pms pms;
u8 div; u8 div;
u8 safe_div;
}; };
#define to_sam9x60_pll_core(hw) container_of(hw, struct sam9x60_pll_core, hw) #define to_sam9x60_pll_core(hw) container_of(hw, struct sam9x60_pll_core, hw)
#define to_sam9x60_frac(core) container_of(core, struct sam9x60_frac, core) #define to_sam9x60_frac(core) container_of(core, struct sam9x60_frac, core)
#define to_sam9x60_div(core) container_of(core, struct sam9x60_div, core) #define to_sam9x60_div(core) container_of(core, struct sam9x60_div, core)
static struct sam9x60_div *notifier_div;
static inline bool sam9x60_pll_ready(struct regmap *regmap, int id) static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
{ {
unsigned int status; unsigned int status;
...@@ -329,6 +333,26 @@ static const struct clk_ops sam9x60_frac_pll_ops_chg = { ...@@ -329,6 +333,26 @@ static const struct clk_ops sam9x60_frac_pll_ops_chg = {
.restore_context = sam9x60_frac_pll_restore_context, .restore_context = sam9x60_frac_pll_restore_context,
}; };
/* This function should be called with spinlock acquired. */
static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, u32 div,
bool enable)
{
struct regmap *regmap = core->regmap;
u32 ena_msk = enable ? core->layout->endiv_mask : 0;
u32 ena_val = enable ? (1 << core->layout->endiv_shift) : 0;
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
core->layout->div_mask | ena_msk,
(div << core->layout->div_shift) | ena_val);
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();
}
static int sam9x60_div_pll_set(struct sam9x60_pll_core *core) static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
{ {
struct sam9x60_div *div = to_sam9x60_div(core); struct sam9x60_div *div = to_sam9x60_div(core);
...@@ -346,17 +370,7 @@ static int sam9x60_div_pll_set(struct sam9x60_pll_core *core) ...@@ -346,17 +370,7 @@ static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
if (!!(val & core->layout->endiv_mask) && cdiv == div->div) if (!!(val & core->layout->endiv_mask) && cdiv == div->div)
goto unlock; goto unlock;
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, sam9x60_div_pll_set_div(core, div->div, 1);
core->layout->div_mask | core->layout->endiv_mask,
(div->div << core->layout->div_shift) |
(1 << core->layout->endiv_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: unlock:
spin_unlock_irqrestore(core->lock, flags); spin_unlock_irqrestore(core->lock, flags);
...@@ -502,16 +516,7 @@ static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate, ...@@ -502,16 +516,7 @@ static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
if (cdiv == div->div) if (cdiv == div->div)
goto unlock; goto unlock;
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, sam9x60_div_pll_set_div(core, div->div, 0);
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: unlock:
spin_unlock_irqrestore(core->lock, irqflags); spin_unlock_irqrestore(core->lock, irqflags);
...@@ -538,6 +543,48 @@ static void sam9x60_div_pll_restore_context(struct clk_hw *hw) ...@@ -538,6 +543,48 @@ static void sam9x60_div_pll_restore_context(struct clk_hw *hw)
sam9x60_div_pll_set(core); sam9x60_div_pll_set(core);
} }
static int sam9x60_div_pll_notifier_fn(struct notifier_block *notifier,
unsigned long code, void *data)
{
struct sam9x60_div *div = notifier_div;
struct sam9x60_pll_core core = div->core;
struct regmap *regmap = core.regmap;
unsigned long irqflags;
u32 val, cdiv;
int ret = NOTIFY_DONE;
if (code != PRE_RATE_CHANGE)
return ret;
/*
* We switch to safe divider to avoid overclocking of other domains
* feed by us while the frac PLL (our parent) is changed.
*/
div->div = div->safe_div;
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->safe_div)
goto unlock;
sam9x60_div_pll_set_div(&core, div->div, 0);
ret = NOTIFY_OK;
unlock:
spin_unlock_irqrestore(core.lock, irqflags);
return ret;
}
static struct notifier_block sam9x60_div_pll_notifier = {
.notifier_call = sam9x60_div_pll_notifier_fn,
};
static const struct clk_ops sam9x60_div_pll_ops = { static const struct clk_ops sam9x60_div_pll_ops = {
.prepare = sam9x60_div_pll_prepare, .prepare = sam9x60_div_pll_prepare,
.unprepare = sam9x60_div_pll_unprepare, .unprepare = sam9x60_div_pll_unprepare,
...@@ -647,7 +694,8 @@ struct clk_hw * __init ...@@ -647,7 +694,8 @@ struct clk_hw * __init
sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock, sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
const char *name, const char *parent_name, u8 id, const char *name, const char *parent_name, u8 id,
const struct clk_pll_characteristics *characteristics, const struct clk_pll_characteristics *characteristics,
const struct clk_pll_layout *layout, u32 flags) const struct clk_pll_layout *layout, u32 flags,
u32 safe_div)
{ {
struct sam9x60_div *div; struct sam9x60_div *div;
struct clk_hw *hw; struct clk_hw *hw;
...@@ -656,9 +704,13 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock, ...@@ -656,9 +704,13 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
unsigned int val; unsigned int val;
int ret; int ret;
if (id > PLL_MAX_ID || !lock) /* We only support one changeable PLL. */
if (id > PLL_MAX_ID || !lock || (safe_div && notifier_div))
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
if (safe_div >= PLL_DIV_MAX)
safe_div = PLL_DIV_MAX - 1;
div = kzalloc(sizeof(*div), GFP_KERNEL); div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div) if (!div)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -678,6 +730,7 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock, ...@@ -678,6 +730,7 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
div->core.layout = layout; div->core.layout = layout;
div->core.regmap = regmap; div->core.regmap = regmap;
div->core.lock = lock; div->core.lock = lock;
div->safe_div = safe_div;
spin_lock_irqsave(div->core.lock, irqflags); spin_lock_irqsave(div->core.lock, irqflags);
...@@ -693,6 +746,9 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock, ...@@ -693,6 +746,9 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
if (ret) { if (ret) {
kfree(div); kfree(div);
hw = ERR_PTR(ret); hw = ERR_PTR(ret);
} else if (div->safe_div) {
notifier_div = div;
clk_notifier_register(hw->clk, &sam9x60_div_pll_notifier);
} }
return hw; return hw;
......
...@@ -214,7 +214,8 @@ struct clk_hw * __init ...@@ -214,7 +214,8 @@ struct clk_hw * __init
sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock, sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
const char *name, const char *parent_name, u8 id, const char *name, const char *parent_name, u8 id,
const struct clk_pll_characteristics *characteristics, const struct clk_pll_characteristics *characteristics,
const struct clk_pll_layout *layout, u32 flags); const struct clk_pll_layout *layout, u32 flags,
u32 safe_div);
struct clk_hw * __init struct clk_hw * __init
sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock, sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
......
...@@ -242,7 +242,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) ...@@ -242,7 +242,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
* This feeds CPU. It should not * This feeds CPU. It should not
* be disabled. * be disabled.
*/ */
CLK_IS_CRITICAL | CLK_SET_RATE_GATE); CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0);
if (IS_ERR(hw)) if (IS_ERR(hw))
goto err_free; goto err_free;
...@@ -260,7 +260,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) ...@@ -260,7 +260,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
&pll_div_layout, &pll_div_layout,
CLK_SET_RATE_GATE | CLK_SET_RATE_GATE |
CLK_SET_PARENT_GATE | CLK_SET_PARENT_GATE |
CLK_SET_RATE_PARENT); CLK_SET_RATE_PARENT, 0);
if (IS_ERR(hw)) if (IS_ERR(hw))
goto err_free; goto err_free;
...@@ -279,7 +279,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) ...@@ -279,7 +279,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
hw = at91_clk_register_master_div(regmap, "masterck_div", hw = at91_clk_register_master_div(regmap, "masterck_div",
"masterck_pres", &sam9x60_master_layout, "masterck_pres", &sam9x60_master_layout,
&mck_characteristics, &mck_lock, &mck_characteristics, &mck_lock,
CLK_SET_RATE_GATE); CLK_SET_RATE_GATE, 0);
if (IS_ERR(hw)) if (IS_ERR(hw))
goto err_free; goto err_free;
......
...@@ -127,6 +127,8 @@ static const struct clk_pll_characteristics pll_characteristics = { ...@@ -127,6 +127,8 @@ static const struct clk_pll_characteristics pll_characteristics = {
* @t: clock type * @t: clock type
* @f: clock flags * @f: clock flags
* @eid: export index in sama7g5->chws[] array * @eid: export index in sama7g5->chws[] array
* @safe_div: intermediate divider need to be set on PRE_RATE_CHANGE
* notification
*/ */
static const struct { static const struct {
const char *n; const char *n;
...@@ -136,6 +138,7 @@ static const struct { ...@@ -136,6 +138,7 @@ static const struct {
unsigned long f; unsigned long f;
u8 t; u8 t;
u8 eid; u8 eid;
u8 safe_div;
} sama7g5_plls[][PLL_ID_MAX] = { } sama7g5_plls[][PLL_ID_MAX] = {
[PLL_ID_CPU] = { [PLL_ID_CPU] = {
{ .n = "cpupll_fracck", { .n = "cpupll_fracck",
...@@ -156,7 +159,12 @@ static const struct { ...@@ -156,7 +159,12 @@ static const struct {
.t = PLL_TYPE_DIV, .t = PLL_TYPE_DIV,
/* This feeds CPU. It should not be disabled. */ /* This feeds CPU. It should not be disabled. */
.f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT, .f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT,
.eid = PMC_CPUPLL, }, .eid = PMC_CPUPLL,
/*
* Safe div=15 should be safe even for switching b/w 1GHz and
* 90MHz (frac pll might go up to 1.2GHz).
*/
.safe_div = 15, },
}, },
[PLL_ID_SYS] = { [PLL_ID_SYS] = {
...@@ -967,7 +975,8 @@ static void __init sama7g5_pmc_setup(struct device_node *np) ...@@ -967,7 +975,8 @@ static void __init sama7g5_pmc_setup(struct device_node *np)
sama7g5_plls[i][j].p, i, sama7g5_plls[i][j].p, i,
sama7g5_plls[i][j].c, sama7g5_plls[i][j].c,
sama7g5_plls[i][j].l, sama7g5_plls[i][j].l,
sama7g5_plls[i][j].f); sama7g5_plls[i][j].f,
sama7g5_plls[i][j].safe_div);
break; break;
default: default:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment