blob: aedcd47e445bc89a26c223b61d1a696bcbd91eb3 [file] [log] [blame]
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/spinlock.h>
#include <linux/clk-provider.h>
#include "p7clk-provider.h"
#include "clock.h"
#define to_p7clk_pll(_hw) container_of(_hw, struct clk_p7_pll, hw)
/**
* p7_prepare_pll() - Prepare PLL
* @clk_hw: a @clk_hw in a @clk_p7_pll
*/
static int p7_prepare_pll(struct clk_hw* clk_hw)
{
struct clk_p7_pll const* const pll = to_p7clk_pll(clk_hw);
unsigned long const msk = BIT(pll->lock_bit_idx);
unsigned long const addr = pll->pll_conf_reg;
union p7_pll_reg curr = { .word = readl(addr) };
int err = 0;
pr_debug("p7_prepare_pll %p %x\n", clk_hw, curr.word);
if (curr.fields.reset || /* powered on ? */
! (readl(pll->lock_reg) & msk)) { /* locked ? */
curr.fields.reset = 0;
p7_write_pll_conf(addr, curr.word);
if (curr.fields.bypass)
return err;
err = p7_activate_pll(pll->lock_reg, msk);
if (err) {
/*
* The PLL is supposed to be locked when they are not
* reset and not bypassed, but we may have to update
* the config bits to restart the lock sequence. Toggle
* the last bit of config field to do so.
*/
curr.fields.cfg ^= 1;
p7_write_pll_conf(addr, curr.word);
curr.fields.cfg ^= 1;
p7_write_pll_conf(addr, curr.word);
err = p7_activate_pll(pll->lock_reg, msk);
}
}
return err;
}
/**
* p7_unprepare_pll() - Unprepare PLL
* @clk_hw: a @clk_hw in a @clk_p7_pll
*/
static void p7_unprepare_pll(struct clk_hw* clk_hw)
{
struct clk_p7_pll const* const pll = to_p7clk_pll(clk_hw);
unsigned long const addr = pll->pll_conf_reg;
union p7_pll_reg curr = { .word = readl(addr) };
pr_debug("p7_unprepare_pll %p\n", clk_hw);
/* power off */
curr.fields.reset = 1;
writel(curr.word, addr);
}
/**
* p7_enable_pll() - Enable PLL
* @clk_hw: a @clk_hw in a @clk_p7_pll
*
* If the PLL is not gateable, no-op.
*/
static int p7_enable_pll(struct clk_hw *clk_hw)
{
struct clk_p7_pll const* const pll = to_p7clk_pll(clk_hw);
unsigned long const addr = pll->pll_conf_reg;
union p7_pll_reg pll_conf;
unsigned long flags = 0;
pr_debug("p7_enable_pll %p\n", clk_hw);
if (! pll->gateable)
goto ret;
if (pll->lock)
spin_lock_irqsave(pll->lock, flags);
pll_conf.word = readl(addr);
if (pll_conf.fields.enable)
goto unlock;
pll_conf.fields.enable = 1;
writel(pll_conf.word, addr);
unlock:
if (pll->lock)
spin_unlock_irqrestore(pll->lock, flags);
ret:
return 0;
}
/**
* p7_disable_pll() - Disable PLL
* @clk_hw: a @clk_hw in a @clk_p7_pll
*/
static void p7_disable_pll(struct clk_hw *clk_hw)
{
struct clk_p7_pll const* const pll = to_p7clk_pll(clk_hw);
unsigned long const addr = pll->pll_conf_reg;
union p7_pll_reg pll_conf;
unsigned long flags = 0;
pr_debug("p7_disable_pll %p\n", clk_hw);
if (! pll->gateable)
return;
if (pll->lock)
spin_lock_irqsave(pll->lock, flags);
pll_conf.word = readl(addr);
if (! pll_conf.fields.enable)
goto ret;
pll_conf.fields.enable = 0;
writel(pll_conf.word, addr);
ret:
if (pll->lock)
spin_unlock_irqrestore(pll->lock, flags);
}
/**
* p7_get_pll_rate() - Return master / one level pll rate in HZ.
* @clk_hw: a @clk_hw in a @clk_p7_pll
* @parent_rate: rate of the parent clock
*/
static unsigned long p7_get_pll_rate(struct clk_hw* clk_hw,
unsigned long parent_rate)
{
struct clk_p7_pll* const pll = to_p7clk_pll(clk_hw);
unsigned long rate;
union p7_pll_reg curr;
curr.word = readl(pll->pll_conf_reg);
rate = p7_pll_rate(curr, parent_rate);
pr_debug("p7_get_pll_rate %p rate=%lu (word=%x)\n", clk_hw, rate, curr.word);
return rate;
}
/**
* p7_set_pll_rate() - Set master / one level pll frequency in HZ.
* @clk_hw: a @clk_hw in a @clk_p7_pll
* @rate: rate to configure in HZ.
* @parent_rate: rate of the parent clock
*
* Warning: frequency passed in argument will likely be rounded to a possible
* PLL operation point.
*/
static int p7_set_pll_rate(struct clk_hw* clk_hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_p7_pll *pll = to_p7clk_pll(clk_hw);
unsigned long const addr = pll->pll_conf_reg;
union p7_pll_reg curr, new;
int idx;
unsigned long flags = 0;
int err = 0;
pr_debug("p7_set_pll_rate %p rate=%lu parent=%lu\n",
clk_hw, rate, parent_rate);
idx = p7_get_clkcfg(pll->cfg_idx, rate, pll->cfg_nr);
if (idx < 0)
return idx;
new.word = pll->pll_cfg[idx];
if (pll->lock)
spin_lock_irqsave(pll->lock, flags);
curr.word = readl(addr);
if (curr.fields.cfg == new.fields.cfg &&
curr.fields.bypass == new.fields.bypass)
goto unlock;
curr.fields.cfg = new.fields.cfg;
curr.fields.bypass = new.fields.bypass;
p7_write_pll_conf(addr, curr.word);
if (pll->lock)
spin_unlock_irqrestore(pll->lock, flags);
/*
* If the PLL is reset, the lock bit will never be set to 1. So
* we avoid locking if this operation doesn't make sense.
*/
if ((!curr.fields.reset) && (!curr.fields.bypass))
err = p7_activate_pll(pll->lock_reg, BIT(pll->lock_bit_idx));
return err;
unlock:
if (pll->lock)
spin_unlock_irqrestore(pll->lock, flags);
return err;
}
/**
* p7_set_pll_rate() - Set master / one level pll frequency in HZ.
* @clk_hw: a @clk_hw in a @clk_p7_pll
* @rate: rate to configure in HZ.
* @parent_rate: pointer to the rate of the parent clock
*/
static long p7_round_pll_rate(struct clk_hw* clk_hw, unsigned long rate,
unsigned long* parent_rate)
{
struct clk_p7_pll* const pll = to_p7clk_pll(clk_hw);
int const idx = p7_get_clkcfg(pll->cfg_idx,
rate,
pll->cfg_nr);
pr_debug("p7_round_pll_rate %p rate=%lu parent=%lu : idx=%d\n",
clk_hw, rate, *parent_rate, idx);
if (idx < 0)
return 0;
return (long) pll->cfg_idx[idx];
}
static void p7_pll_init(struct clk_hw *clk_hw)
{
struct clk_p7_pll* const pll = to_p7clk_pll(clk_hw);
unsigned long const addr = pll->pll_conf_reg;
union p7_pll_reg curr;
unsigned long flags = 0;
if (!pll->gateable)
return;
if (pll->lock)
spin_lock_irqsave(pll->lock, flags);
curr.word = readl(addr);
if (curr.fields.enable) {
curr.fields.enable = 0;
writel(curr.word, addr);
}
if (pll->lock)
spin_unlock_irqrestore(pll->lock, flags);
}
struct clk_ops const p7_clk_pll_ops = {
.prepare = &p7_prepare_pll,
.unprepare = &p7_unprepare_pll,
.enable = &p7_enable_pll,
.disable = &p7_disable_pll,
.recalc_rate = &p7_get_pll_rate,
.set_rate = &p7_set_pll_rate,
.round_rate = &p7_round_pll_rate,
.init = &p7_pll_init,
};
struct clk_ops const p7_clk_fixed_pll_ops = {
.prepare = &p7_prepare_pll,
.unprepare = &p7_unprepare_pll,
.enable = &p7_enable_pll,
.disable = &p7_disable_pll,
.recalc_rate = &p7_get_pll_rate,
.init = &p7_pll_init,
};
struct clk *clk_register_pll(struct device *dev, const char *name,
const char *parent_name, const bool gateable, const unsigned long flags,
const unsigned long lock_reg, const u8 lock_bit_idx,
const unsigned long pll_reg, unsigned long const * const cfg_idx,
unsigned long const * const pll_cfg, const size_t cfg_nr,
spinlock_t *lock)
{
struct clk_p7_pll *pll;
struct clk *clk;
struct clk_init_data init;
pll = kzalloc(sizeof(struct clk_p7_pll), GFP_KERNEL);
if (!pll) {
pr_err("%s: could not allocate clk pll\n", __func__);
clk = ERR_PTR(-ENOMEM);
goto err;
}
init.name = name;
if (cfg_nr)
init.ops = &p7_clk_pll_ops;
else
init.ops = &p7_clk_fixed_pll_ops;
init.flags = flags;
init.parent_names = &parent_name;
init.num_parents = 1;
pll->gateable = gateable;
pll->lock_reg = lock_reg;
pll->lock_bit_idx = lock_bit_idx;
pll->pll_conf_reg = pll_reg;
pll->cfg_idx = cfg_nr ? kmemdup(cfg_idx, cfg_nr * sizeof(unsigned long), GFP_KERNEL) : NULL;
pll->pll_cfg = cfg_nr ? kmemdup(pll_cfg, cfg_nr * sizeof(unsigned long), GFP_KERNEL) : NULL;
pll->cfg_nr = cfg_nr;
pll->lock = lock;
pll->hw.init = &init;
if (cfg_nr && ((!pll->cfg_idx) || (!pll->pll_cfg))) {
pr_err("%s: could not allocate memory for pll info\n", __func__);
clk = ERR_PTR(-ENOMEM);
goto free_pll_content;
}
clk = clk_register(dev, &pll->hw);
pr_debug("clk_register_pll %p for %s\n", &pll->hw, name);
if (! IS_ERR(clk))
return clk;
free_pll_content:
if (pll->cfg_idx)
kfree(pll->cfg_idx);
if (pll->pll_cfg)
kfree(pll->pll_cfg);
kfree(pll);
err:
return clk;
}
EXPORT_SYMBOL(clk_register_pll);