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>
This commit is contained in:
Claudiu Beznea 2020-07-22 10:38:24 +03:00 committed by Stephen Boyd
parent 0416824edc
commit 43b1bb4a9b
3 changed files with 441 additions and 194 deletions

View File

@ -25,20 +25,31 @@
#define FCORE_MIN (600000000)
#define FCORE_MAX (1200000000)
#define PLL_MAX_ID 1
#define PLL_MAX_ID 7
struct sam9x60_pll {
struct clk_hw hw;
struct sam9x60_pll_core {
struct regmap *regmap;
spinlock_t *lock;
const struct clk_pll_characteristics *characteristics;
u32 frac;
const struct clk_pll_layout *layout;
struct clk_hw hw;
u8 id;
u8 div;
};
struct sam9x60_frac {
struct sam9x60_pll_core core;
u32 frac;
u16 mul;
};
#define to_sam9x60_pll(hw) container_of(hw, struct sam9x60_pll, hw)
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)
{
@ -49,43 +60,53 @@ static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
return !!(status & BIT(id));
}
static int sam9x60_pll_prepare(struct clk_hw *hw)
static bool sam9x60_frac_pll_ready(struct regmap *regmap, u8 id)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
struct regmap *regmap = pll->regmap;
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;
u8 div;
u16 mul;
u32 val, frac;
spin_lock_irqsave(pll->lock, flags);
regmap_write(regmap, AT91_PMC_PLL_UPDT, pll->id);
regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, 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_CTRL1, &val);
mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val);
frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val);
cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
if (sam9x60_pll_ready(regmap, pll->id) &&
(div == pll->div && mul == pll->mul && frac == pll->frac)) {
spin_unlock_irqrestore(pll->lock, flags);
return 0;
}
if (sam9x60_frac_pll_ready(regmap, core->id) &&
(cmul == frac->mul && cfrac == frac->frac))
goto unlock;
/* Recommended value for AT91_PMC_PLL_ACR */
if (pll->characteristics->upll)
/* 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,
FIELD_PREP(PMC_PLL_CTRL1_MUL_MSK, pll->mul) |
FIELD_PREP(PMC_PLL_CTRL1_FRACR_MSK, pll->frac));
(frac->mul << core->layout->mul_shift) |
(frac->frac << core->layout->frac_shift));
if (pll->characteristics->upll) {
if (core->characteristics->upll) {
/* Enable the UTMI internal bandgap */
val |= AT91_PMC_PLL_ACR_UTMIBG;
regmap_write(regmap, AT91_PMC_PLL_ACR, val);
@ -100,229 +121,409 @@ static int sam9x60_pll_prepare(struct clk_hw *hw)
}
regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_UPDATE, AT91_PMC_PLL_UPDT_UPDATE);
AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
AT91_PMC_PLL_UPDT_UPDATE | core->id);
regmap_write(regmap, AT91_PMC_PLL_CTRL0,
AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL |
AT91_PMC_PLL_CTRL0_ENPLLCK | pll->div);
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_UPDATE);
AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
AT91_PMC_PLL_UPDT_UPDATE | core->id);
while (!sam9x60_pll_ready(regmap, pll->id))
while (!sam9x60_pll_ready(regmap, core->id))
cpu_relax();
spin_unlock_irqrestore(pll->lock, flags);
unlock:
spin_unlock_irqrestore(core->lock, flags);
return 0;
}
static int sam9x60_pll_is_prepared(struct clk_hw *hw)
static void sam9x60_frac_pll_unprepare(struct clk_hw *hw)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
return sam9x60_pll_ready(pll->regmap, pll->id);
}
static void sam9x60_pll_unprepare(struct clk_hw *hw)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
struct regmap *regmap = core->regmap;
unsigned long flags;
spin_lock_irqsave(pll->lock, flags);
spin_lock_irqsave(core->lock, flags);
regmap_write(pll->regmap, AT91_PMC_PLL_UPDT, pll->id);
regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_update_bits(pll->regmap, AT91_PMC_PLL_CTRL0,
AT91_PMC_PLL_CTRL0_ENPLLCK, 0);
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, AT91_PMC_PLL_CTRL0_ENPLL, 0);
regmap_update_bits(pll->regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_UPDATE, AT91_PMC_PLL_UPDT_UPDATE);
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(pll->regmap, AT91_PMC_PLL_CTRL0,
AT91_PMC_PLL_CTRL0_ENPLL, 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);
if (pll->characteristics->upll)
regmap_update_bits(pll->regmap, AT91_PMC_PLL_ACR,
AT91_PMC_PLL_ACR_UTMIBG |
AT91_PMC_PLL_ACR_UTMIVR, 0);
regmap_update_bits(pll->regmap, AT91_PMC_PLL_UPDT,
AT91_PMC_PLL_UPDT_UPDATE, AT91_PMC_PLL_UPDT_UPDATE);
spin_unlock_irqrestore(pll->lock, flags);
spin_unlock_irqrestore(core->lock, flags);
}
static unsigned long sam9x60_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
static int sam9x60_frac_pll_is_prepared(struct clk_hw *hw)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
return DIV_ROUND_CLOSEST_ULL((parent_rate * (pll->mul + 1) +
((u64)parent_rate * pll->frac >> 22)), (pll->div + 1));
return sam9x60_pll_ready(core->regmap, core->id);
}
static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll,
static long sam9x60_frac_pll_compute_mul_frac(struct sam9x60_pll_core *core,
unsigned long rate,
unsigned long parent_rate,
bool update)
{
const struct clk_pll_characteristics *characteristics =
pll->characteristics;
unsigned long bestremainder = ULONG_MAX;
unsigned long maxdiv, mindiv, tmpdiv;
long bestrate = -ERANGE;
unsigned long bestdiv = 0;
unsigned long bestmul = 0;
unsigned long bestfrac = 0;
u64 fcore = 0;
struct sam9x60_frac *frac = to_sam9x60_frac(core);
unsigned long tmprate, remainder;
unsigned long nmul = 0;
unsigned long nfrac = 0;
if (rate < characteristics->output[0].min ||
rate > characteristics->output[0].max)
if (rate < FCORE_MIN || rate > FCORE_MAX)
return -ERANGE;
if (!pll->characteristics->upll) {
mindiv = parent_rate / rate;
if (mindiv < 2)
mindiv = 2;
maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX, rate);
if (maxdiv > PLL_DIV_MAX)
maxdiv = PLL_DIV_MAX;
} else {
mindiv = maxdiv = UPLL_DIV;
}
for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) {
unsigned long remainder;
unsigned long tmprate;
unsigned long tmpmul;
unsigned long tmpfrac = 0;
/*
* Calculate the multiplier associated with the current
* divider that provide the closest rate to the requested one.
*/
tmpmul = mult_frac(rate, tmpdiv, parent_rate);
tmprate = mult_frac(parent_rate, tmpmul, tmpdiv);
nmul = mult_frac(rate, 1, parent_rate);
tmprate = mult_frac(parent_rate, nmul, 1);
remainder = rate - tmprate;
if (remainder) {
tmpfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * tmpdiv * (1 << 22),
nfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * (1 << 22),
parent_rate);
tmprate += DIV_ROUND_CLOSEST_ULL((u64)tmpfrac * parent_rate,
tmpdiv * (1 << 22));
if (tmprate > rate)
remainder = tmprate - rate;
else
remainder = rate - tmprate;
tmprate += DIV_ROUND_CLOSEST_ULL((u64)nfrac * parent_rate,
(1 << 22));
}
fcore = parent_rate * (tmpmul + 1) +
((u64)parent_rate * tmpfrac >> 22);
if (fcore < FCORE_MIN || fcore > FCORE_MAX)
continue;
/*
* Compare the remainder with the best remainder found until
* now and elect a new best multiplier/divider pair if the
* current remainder is smaller than the best one.
*/
if (remainder < bestremainder) {
bestremainder = remainder;
bestdiv = tmpdiv;
bestmul = tmpmul;
bestrate = tmprate;
bestfrac = tmpfrac;
}
/* We've found a perfect match! */
if (!remainder)
break;
}
/* Check if bestrate is a valid output rate */
if (fcore < FCORE_MIN || fcore > FCORE_MAX ||
bestrate < characteristics->output[0].min ||
bestrate > characteristics->output[0].max)
/* Check if resulted rate is a valid. */
if (tmprate < FCORE_MIN || tmprate > FCORE_MAX)
return -ERANGE;
if (update) {
pll->div = bestdiv - 1;
pll->mul = bestmul - 1;
pll->frac = bestfrac;
frac->mul = nmul - 1;
frac->frac = nfrac;
}
return bestrate;
return tmprate;
}
static long sam9x60_pll_round_rate(struct clk_hw *hw, unsigned long rate,
static long sam9x60_frac_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
return sam9x60_pll_get_best_div_mul(pll, rate, *parent_rate, false);
return sam9x60_frac_pll_compute_mul_frac(core, rate, *parent_rate, false);
}
static int sam9x60_pll_set_rate(struct clk_hw *hw, unsigned long rate,
static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
return sam9x60_pll_get_best_div_mul(pll, rate, parent_rate, true);
return sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);
}
static const struct clk_ops pll_ops = {
.prepare = sam9x60_pll_prepare,
.unprepare = sam9x60_pll_unprepare,
.is_prepared = sam9x60_pll_is_prepared,
.recalc_rate = sam9x60_pll_recalc_rate,
.round_rate = sam9x60_pll_round_rate,
.set_rate = sam9x60_pll_set_rate,
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_pll(struct regmap *regmap, spinlock_t *lock,
const char *name, const char *parent_name, u8 id,
const struct clk_pll_characteristics *characteristics)
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_pll *pll;
struct sam9x60_frac *frac;
struct clk_hw *hw;
struct clk_init_data init;
unsigned int pllr;
unsigned long parent_rate, flags;
unsigned int val;
int ret;
if (id > PLL_MAX_ID)
if (id > PLL_MAX_ID || !lock || !parent_hw)
return ERR_PTR(-EINVAL);
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
frac = kzalloc(sizeof(*frac), GFP_KERNEL);
if (!frac)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &pll_ops;
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;
pll->id = id;
pll->hw.init = &init;
pll->characteristics = characteristics;
pll->regmap = regmap;
pll->lock = lock;
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;
regmap_write(regmap, AT91_PMC_PLL_UPDT, id);
regmap_read(regmap, AT91_PMC_PLL_CTRL0, &pllr);
pll->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, pllr);
regmap_read(regmap, AT91_PMC_PLL_CTRL1, &pllr);
pll->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, pllr);
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;
}
hw = &pll->hw;
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(pll);
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);
}

View File

@ -54,8 +54,14 @@ struct clk_master_characteristics {
struct clk_pll_layout {
u32 pllr_mask;
u16 mul_mask;
u32 mul_mask;
u32 frac_mask;
u32 div_mask;
u32 endiv_mask;
u8 mul_shift;
u8 frac_shift;
u8 div_shift;
u8 endiv_shift;
};
extern const struct clk_pll_layout at91rm9200_pll_layout;
@ -181,9 +187,17 @@ at91_clk_register_plldiv(struct regmap *regmap, const char *name,
const char *parent_name);
struct clk_hw * __init
sam9x60_clk_register_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 struct clk_pll_characteristics *characteristics);
const struct clk_pll_characteristics *characteristics,
const struct clk_pll_layout *layout, bool critical);
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 clk_hw * __init
at91_clk_register_programmable(struct regmap *regmap, const char *name,

View File

@ -42,6 +42,20 @@ static const struct clk_pll_characteristics upll_characteristics = {
.upll = true,
};
static const struct clk_pll_layout pll_frac_layout = {
.mul_mask = GENMASK(31, 24),
.frac_mask = GENMASK(21, 0),
.mul_shift = 24,
.frac_shift = 0,
};
static const struct clk_pll_layout pll_div_layout = {
.div_mask = GENMASK(7, 0),
.endiv_mask = BIT(29),
.div_shift = 0,
.endiv_shift = 29,
};
static const struct clk_programmable_layout sam9x60_programmable_layout = {
.pres_mask = 0xff,
.pres_shift = 8,
@ -156,6 +170,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
const char *td_slck_name, *md_slck_name, *mainxtal_name;
struct pmc_data *sam9x60_pmc;
const char *parent_names[6];
struct clk_hw *main_osc_hw;
struct regmap *regmap;
struct clk_hw *hw;
int i;
@ -200,6 +215,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
bypass);
if (IS_ERR(hw))
goto err_free;
main_osc_hw = hw;
parent_names[0] = "main_rc_osc";
parent_names[1] = "main_osc";
@ -209,15 +225,31 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
sam9x60_pmc->chws[PMC_MAIN] = hw;
hw = sam9x60_clk_register_pll(regmap, &pmc_pll_lock, "pllack",
"mainck", 0, &plla_characteristics);
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);
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);
if (IS_ERR(hw))
goto err_free;
sam9x60_pmc->chws[PMC_PLLACK] = hw;
hw = sam9x60_clk_register_pll(regmap, &pmc_pll_lock, "upllck",
"main_osc", 1, &upll_characteristics);
hw = sam9x60_clk_register_frac_pll(regmap, &pmc_pll_lock, "upllck_fracck",
"main_osc", main_osc_hw, 1,
&upll_characteristics,
&pll_frac_layout, false);
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);
if (IS_ERR(hw))
goto err_free;
@ -225,7 +257,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
parent_names[0] = md_slck_name;
parent_names[1] = "mainck";
parent_names[2] = "pllack";
parent_names[2] = "pllack_divck";
hw = at91_clk_register_master(regmap, "masterck", 3, parent_names,
&sam9x60_master_layout,
&mck_characteristics);
@ -234,8 +266,8 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
sam9x60_pmc->chws[PMC_MCK] = hw;
parent_names[0] = "pllack";
parent_names[1] = "upllck";
parent_names[0] = "pllack_divck";
parent_names[1] = "upllck_divck";
parent_names[2] = "main_osc";
hw = sam9x60_clk_register_usb(regmap, "usbck", parent_names, 3);
if (IS_ERR(hw))
@ -245,8 +277,8 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
parent_names[1] = td_slck_name;
parent_names[2] = "mainck";
parent_names[3] = "masterck";
parent_names[4] = "pllack";
parent_names[5] = "upllck";
parent_names[4] = "pllack_divck";
parent_names[5] = "upllck_divck";
for (i = 0; i < 8; i++) {
char name[6];