linux_dsm_epyc7002/drivers/clk/at91/clk-sam9x60-pll.c
Claudiu Beznea 43b1bb4a9b clk: at91: clk-sam9x60-pll: re-factor to support plls with multiple outputs
Some of the SAMA7G5 PLLs support multiple outputs (e.g. AUDIO PLL).
For these, split the PLL clock in two: fractional clock and
divider clock. In case PLLs supports multiple outputs (since these
outputs are dividers (with different settings) sharing the same
fractional part), it will register one fractional clock and multiple
divider clocks (dividers sharing the fractional clock).

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Link: https://lore.kernel.org/r/1595403506-8209-17-git-send-email-claudiu.beznea@microchip.com
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2020-07-24 02:19:08 -07:00

533 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Microchip Technology Inc.
*
*/
#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk/at91_pmc.h>
#include <linux/of.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include "pmc.h"
#define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0)
#define PMC_PLL_CTRL1_MUL_MSK GENMASK(31, 24)
#define PMC_PLL_CTRL1_FRACR_MSK GENMASK(21, 0)
#define PLL_DIV_MAX (FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1)
#define UPLL_DIV 2
#define PLL_MUL_MAX (FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, UINT_MAX) + 1)
#define FCORE_MIN (600000000)
#define FCORE_MAX (1200000000)
#define PLL_MAX_ID 7
struct sam9x60_pll_core {
struct regmap *regmap;
spinlock_t *lock;
const struct clk_pll_characteristics *characteristics;
const struct clk_pll_layout *layout;
struct clk_hw hw;
u8 id;
};
struct sam9x60_frac {
struct sam9x60_pll_core core;
u32 frac;
u16 mul;
};
struct sam9x60_div {
struct sam9x60_pll_core core;
u8 div;
};
#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_div(core) container_of(core, struct sam9x60_div, core)
static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
{
unsigned int status;
regmap_read(regmap, AT91_PMC_PLL_ISR0, &status);
return !!(status & BIT(id));
}
static bool sam9x60_frac_pll_ready(struct regmap *regmap, u8 id)
{
return sam9x60_pll_ready(regmap, id);
}
static unsigned long sam9x60_frac_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
struct sam9x60_frac *frac = to_sam9x60_frac(core);
return (parent_rate * (frac->mul + 1) +
((u64)parent_rate * frac->frac >> 22));
}
static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
struct sam9x60_frac *frac = to_sam9x60_frac(core);
struct regmap *regmap = core->regmap;
unsigned int val, cfrac, cmul;
unsigned long flags;
spin_lock_irqsave(core->lock, flags);
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 (sam9x60_frac_pll_ready(regmap, core->id) &&
(cmul == frac->mul && cfrac == frac->frac))
goto unlock;
/* Recommended value for PMC_PLL_ACR */
if (core->characteristics->upll)
val = AT91_PMC_PLL_ACR_DEFAULT_UPLL;
else
val = AT91_PMC_PLL_ACR_DEFAULT_PLLA;
regmap_write(regmap, AT91_PMC_PLL_ACR, val);
regmap_write(regmap, AT91_PMC_PLL_CTRL1,
(frac->mul << core->layout->mul_shift) |
(frac->frac << core->layout->frac_shift));
if (core->characteristics->upll) {
/* Enable the UTMI internal bandgap */
val |= AT91_PMC_PLL_ACR_UTMIBG;
regmap_write(regmap, AT91_PMC_PLL_ACR, val);
udelay(10);
/* Enable the UTMI internal regulator */
val |= AT91_PMC_PLL_ACR_UTMIVR;
regmap_write(regmap, AT91_PMC_PLL_ACR, val);
udelay(10);
}
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);
while (!sam9x60_pll_ready(regmap, core->id))
cpu_relax();
unlock:
spin_unlock_irqrestore(core->lock, flags);
return 0;
}
static void sam9x60_frac_pll_unprepare(struct clk_hw *hw)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
struct regmap *regmap = core->regmap;
unsigned long flags;
spin_lock_irqsave(core->lock, flags);
regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, AT91_PMC_PLL_CTRL0_ENPLL, 0);
if (core->characteristics->upll)
regmap_update_bits(regmap, AT91_PMC_PLL_ACR,
AT91_PMC_PLL_ACR_UTMIBG | AT91_PMC_PLL_ACR_UTMIVR, 0);
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);
spin_unlock_irqrestore(core->lock, flags);
}
static int sam9x60_frac_pll_is_prepared(struct clk_hw *hw)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
return sam9x60_pll_ready(core->regmap, core->id);
}
static long sam9x60_frac_pll_compute_mul_frac(struct sam9x60_pll_core *core,
unsigned long rate,
unsigned long parent_rate,
bool update)
{
struct sam9x60_frac *frac = to_sam9x60_frac(core);
unsigned long tmprate, remainder;
unsigned long nmul = 0;
unsigned long nfrac = 0;
if (rate < FCORE_MIN || rate > FCORE_MAX)
return -ERANGE;
/*
* Calculate the multiplier associated with the current
* divider that provide the closest rate to the requested one.
*/
nmul = mult_frac(rate, 1, parent_rate);
tmprate = mult_frac(parent_rate, nmul, 1);
remainder = rate - tmprate;
if (remainder) {
nfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * (1 << 22),
parent_rate);
tmprate += DIV_ROUND_CLOSEST_ULL((u64)nfrac * parent_rate,
(1 << 22));
}
/* Check if resulted rate is a valid. */
if (tmprate < FCORE_MIN || tmprate > FCORE_MAX)
return -ERANGE;
if (update) {
frac->mul = nmul - 1;
frac->frac = nfrac;
}
return tmprate;
}
static long sam9x60_frac_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
return sam9x60_frac_pll_compute_mul_frac(core, rate, *parent_rate, false);
}
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);
return sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);
}
static const struct clk_ops sam9x60_frac_pll_ops = {
.prepare = sam9x60_frac_pll_prepare,
.unprepare = sam9x60_frac_pll_unprepare,
.is_prepared = sam9x60_frac_pll_is_prepared,
.recalc_rate = sam9x60_frac_pll_recalc_rate,
.round_rate = sam9x60_frac_pll_round_rate,
.set_rate = sam9x60_frac_pll_set_rate,
};
static int sam9x60_div_pll_prepare(struct clk_hw *hw)
{
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 flags;
unsigned int val, cdiv;
spin_lock_irqsave(core->lock, flags);
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 enabled an nothing changed. */
if (!!(val & core->layout->endiv_mask) && cdiv == div->div)
goto unlock;
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
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:
spin_unlock_irqrestore(core->lock, flags);
return 0;
}
static void sam9x60_div_pll_unprepare(struct clk_hw *hw)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
struct regmap *regmap = core->regmap;
unsigned long flags;
spin_lock_irqsave(core->lock, flags);
regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
core->layout->endiv_mask, 0);
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);
spin_unlock_irqrestore(core->lock, flags);
}
static int sam9x60_div_pll_is_prepared(struct clk_hw *hw)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
struct regmap *regmap = core->regmap;
unsigned long flags;
unsigned int val;
spin_lock_irqsave(core->lock, flags);
regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
spin_unlock_irqrestore(core->lock, flags);
return !!(val & core->layout->endiv_mask);
}
static unsigned long sam9x60_div_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
struct sam9x60_div *div = to_sam9x60_div(core);
return DIV_ROUND_CLOSEST_ULL(parent_rate, (div->div + 1));
}
static long sam9x60_div_pll_compute_div(struct sam9x60_pll_core *core,
unsigned long *parent_rate,
unsigned long rate)
{
const struct clk_pll_characteristics *characteristics =
core->characteristics;
struct clk_hw *parent = clk_hw_get_parent(&core->hw);
unsigned long tmp_rate, tmp_parent_rate, tmp_diff;
long best_diff = -1, best_rate = -EINVAL;
u32 divid, best_div;
if (!rate)
return 0;
if (rate < characteristics->output[0].min ||
rate > characteristics->output[0].max)
return -ERANGE;
for (divid = 1; divid < core->layout->div_mask; divid++) {
tmp_parent_rate = clk_hw_round_rate(parent, rate * divid);
if (!tmp_parent_rate)
continue;
tmp_rate = DIV_ROUND_CLOSEST_ULL(tmp_parent_rate, divid);
tmp_diff = abs(rate - tmp_rate);
if (best_diff < 0 || best_diff > tmp_diff) {
*parent_rate = tmp_parent_rate;
best_rate = tmp_rate;
best_diff = tmp_diff;
best_div = divid;
}
if (!best_diff)
break;
}
if (best_rate < characteristics->output[0].min ||
best_rate > characteristics->output[0].max)
return -ERANGE;
return best_rate;
}
static long sam9x60_div_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
return sam9x60_div_pll_compute_div(core, parent_rate, rate);
}
static int sam9x60_div_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_div *div = to_sam9x60_div(core);
div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1;
return 0;
}
static const struct clk_ops sam9x60_div_pll_ops = {
.prepare = sam9x60_div_pll_prepare,
.unprepare = sam9x60_div_pll_unprepare,
.is_prepared = sam9x60_div_pll_is_prepared,
.recalc_rate = sam9x60_div_pll_recalc_rate,
.round_rate = sam9x60_div_pll_round_rate,
.set_rate = sam9x60_div_pll_set_rate,
};
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)
{
struct sam9x60_frac *frac;
struct clk_hw *hw;
struct clk_init_data init;
unsigned long parent_rate, flags;
unsigned int val;
int ret;
if (id > PLL_MAX_ID || !lock || !parent_hw)
return ERR_PTR(-EINVAL);
frac = kzalloc(sizeof(*frac), GFP_KERNEL);
if (!frac)
return ERR_PTR(-ENOMEM);
init.name = name;
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;
frac->core.id = id;
frac->core.hw.init = &init;
frac->core.characteristics = characteristics;
frac->core.layout = layout;
frac->core.regmap = regmap;
frac->core.lock = lock;
spin_lock_irqsave(frac->core.lock, flags);
if (sam9x60_pll_ready(regmap, id)) {
regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_ID_MSK, id);
regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);
frac->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val);
frac->frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val);
} else {
/*
* This means the PLL is not setup by bootloaders. In this
* case we need to set the minimum rate for it. Otherwise
* a clock child of this PLL may be enabled before setting
* its rate leading to enabling this PLL with unsupported
* rate. This will lead to PLL not being locked at all.
*/
parent_rate = clk_hw_get_rate(parent_hw);
if (!parent_rate) {
hw = ERR_PTR(-EINVAL);
goto free;
}
ret = sam9x60_frac_pll_compute_mul_frac(&frac->core, FCORE_MIN,
parent_rate, true);
if (ret <= 0) {
hw = ERR_PTR(ret);
goto free;
}
}
spin_unlock_irqrestore(frac->core.lock, flags);
hw = &frac->core.hw;
ret = clk_hw_register(NULL, hw);
if (ret) {
kfree(frac);
hw = ERR_PTR(ret);
}
return hw;
free:
spin_unlock_irqrestore(frac->core.lock, flags);
kfree(frac);
return hw;
}
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)
{
struct sam9x60_div *div;
struct clk_hw *hw;
struct clk_init_data init;
unsigned long flags;
unsigned int val;
int ret;
if (id > PLL_MAX_ID || !lock)
return ERR_PTR(-EINVAL);
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
init.name = name;
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;
div->core.id = id;
div->core.hw.init = &init;
div->core.characteristics = characteristics;
div->core.layout = layout;
div->core.regmap = regmap;
div->core.lock = lock;
spin_lock_irqsave(div->core.lock, flags);
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);
hw = &div->core.hw;
ret = clk_hw_register(NULL, hw);
if (ret) {
kfree(div);
hw = ERR_PTR(ret);
}
return hw;
}