mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 05:11:48 +00:00
12520c438f
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
1006 lines
23 KiB
C
1006 lines
23 KiB
C
/*
|
|
* arch/arm/plat-spear/clock.c
|
|
*
|
|
* Clock framework for SPEAr platform
|
|
*
|
|
* Copyright (C) 2009 ST Microelectronics
|
|
* Viresh Kumar<viresh.kumar@st.com>
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <linux/bug.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/spinlock.h>
|
|
#include <plat/clock.h>
|
|
|
|
static DEFINE_SPINLOCK(clocks_lock);
|
|
static LIST_HEAD(root_clks);
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static LIST_HEAD(clocks);
|
|
#endif
|
|
|
|
static void propagate_rate(struct clk *, int on_init);
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int clk_debugfs_reparent(struct clk *);
|
|
#endif
|
|
|
|
static int generic_clk_enable(struct clk *clk)
|
|
{
|
|
unsigned int val;
|
|
|
|
if (!clk->en_reg)
|
|
return -EFAULT;
|
|
|
|
val = readl(clk->en_reg);
|
|
if (unlikely(clk->flags & RESET_TO_ENABLE))
|
|
val &= ~(1 << clk->en_reg_bit);
|
|
else
|
|
val |= 1 << clk->en_reg_bit;
|
|
|
|
writel(val, clk->en_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void generic_clk_disable(struct clk *clk)
|
|
{
|
|
unsigned int val;
|
|
|
|
if (!clk->en_reg)
|
|
return;
|
|
|
|
val = readl(clk->en_reg);
|
|
if (unlikely(clk->flags & RESET_TO_ENABLE))
|
|
val |= 1 << clk->en_reg_bit;
|
|
else
|
|
val &= ~(1 << clk->en_reg_bit);
|
|
|
|
writel(val, clk->en_reg);
|
|
}
|
|
|
|
/* generic clk ops */
|
|
static struct clkops generic_clkops = {
|
|
.enable = generic_clk_enable,
|
|
.disable = generic_clk_disable,
|
|
};
|
|
|
|
/* returns current programmed clocks clock info structure */
|
|
static struct pclk_info *pclk_info_get(struct clk *clk)
|
|
{
|
|
unsigned int val, i;
|
|
struct pclk_info *info = NULL;
|
|
|
|
val = (readl(clk->pclk_sel->pclk_sel_reg) >> clk->pclk_sel_shift)
|
|
& clk->pclk_sel->pclk_sel_mask;
|
|
|
|
for (i = 0; i < clk->pclk_sel->pclk_count; i++) {
|
|
if (clk->pclk_sel->pclk_info[i].pclk_val == val)
|
|
info = &clk->pclk_sel->pclk_info[i];
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/*
|
|
* Set Update pclk, and pclk_info of clk and add clock sibling node to current
|
|
* parents children list
|
|
*/
|
|
static void clk_reparent(struct clk *clk, struct pclk_info *pclk_info)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
list_del(&clk->sibling);
|
|
list_add(&clk->sibling, &pclk_info->pclk->children);
|
|
|
|
clk->pclk = pclk_info->pclk;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
clk_debugfs_reparent(clk);
|
|
#endif
|
|
}
|
|
|
|
static void do_clk_disable(struct clk *clk)
|
|
{
|
|
if (!clk)
|
|
return;
|
|
|
|
if (!clk->usage_count) {
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
|
|
clk->usage_count--;
|
|
|
|
if (clk->usage_count == 0) {
|
|
/*
|
|
* Surely, there are no active childrens or direct users
|
|
* of this clock
|
|
*/
|
|
if (clk->pclk)
|
|
do_clk_disable(clk->pclk);
|
|
|
|
if (clk->ops && clk->ops->disable)
|
|
clk->ops->disable(clk);
|
|
}
|
|
}
|
|
|
|
static int do_clk_enable(struct clk *clk)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!clk)
|
|
return -EFAULT;
|
|
|
|
if (clk->usage_count == 0) {
|
|
if (clk->pclk) {
|
|
ret = do_clk_enable(clk->pclk);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
if (clk->ops && clk->ops->enable) {
|
|
ret = clk->ops->enable(clk);
|
|
if (ret) {
|
|
if (clk->pclk)
|
|
do_clk_disable(clk->pclk);
|
|
goto err;
|
|
}
|
|
}
|
|
/*
|
|
* Since the clock is going to be used for the first
|
|
* time please reclac
|
|
*/
|
|
if (clk->recalc) {
|
|
ret = clk->recalc(clk);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
}
|
|
clk->usage_count++;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* clk_enable - inform the system when the clock source should be running.
|
|
* @clk: clock source
|
|
*
|
|
* If the clock can not be enabled/disabled, this should return success.
|
|
*
|
|
* Returns success (0) or negative errno.
|
|
*/
|
|
int clk_enable(struct clk *clk)
|
|
{
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
ret = do_clk_enable(clk);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(clk_enable);
|
|
|
|
/*
|
|
* clk_disable - inform the system when the clock source is no longer required.
|
|
* @clk: clock source
|
|
*
|
|
* Inform the system that a clock source is no longer required by
|
|
* a driver and may be shut down.
|
|
*
|
|
* Implementation detail: if the clock source is shared between
|
|
* multiple drivers, clk_enable() calls must be balanced by the
|
|
* same number of clk_disable() calls for the clock source to be
|
|
* disabled.
|
|
*/
|
|
void clk_disable(struct clk *clk)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
do_clk_disable(clk);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(clk_disable);
|
|
|
|
/**
|
|
* clk_get_rate - obtain the current clock rate (in Hz) for a clock source.
|
|
* This is only valid once the clock source has been enabled.
|
|
* @clk: clock source
|
|
*/
|
|
unsigned long clk_get_rate(struct clk *clk)
|
|
{
|
|
unsigned long flags, rate;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
rate = clk->rate;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
return rate;
|
|
}
|
|
EXPORT_SYMBOL(clk_get_rate);
|
|
|
|
/**
|
|
* clk_set_parent - set the parent clock source for this clock
|
|
* @clk: clock source
|
|
* @parent: parent clock source
|
|
*
|
|
* Returns success (0) or negative errno.
|
|
*/
|
|
int clk_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
int i, found = 0, val = 0;
|
|
unsigned long flags;
|
|
|
|
if (!clk || !parent)
|
|
return -EFAULT;
|
|
if (clk->pclk == parent)
|
|
return 0;
|
|
if (!clk->pclk_sel)
|
|
return -EPERM;
|
|
|
|
/* check if requested parent is in clk parent list */
|
|
for (i = 0; i < clk->pclk_sel->pclk_count; i++) {
|
|
if (clk->pclk_sel->pclk_info[i].pclk == parent) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
/* reflect parent change in hardware */
|
|
val = readl(clk->pclk_sel->pclk_sel_reg);
|
|
val &= ~(clk->pclk_sel->pclk_sel_mask << clk->pclk_sel_shift);
|
|
val |= clk->pclk_sel->pclk_info[i].pclk_val << clk->pclk_sel_shift;
|
|
writel(val, clk->pclk_sel->pclk_sel_reg);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
/* reflect parent change in software */
|
|
clk_reparent(clk, &clk->pclk_sel->pclk_info[i]);
|
|
|
|
propagate_rate(clk, 0);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clk_set_parent);
|
|
|
|
/**
|
|
* clk_set_rate - set the clock rate for a clock source
|
|
* @clk: clock source
|
|
* @rate: desired clock rate in Hz
|
|
*
|
|
* Returns success (0) or negative errno.
|
|
*/
|
|
int clk_set_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
unsigned long flags;
|
|
int ret = -EINVAL;
|
|
|
|
if (!clk || !rate)
|
|
return -EFAULT;
|
|
|
|
if (clk->set_rate) {
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
ret = clk->set_rate(clk, rate);
|
|
if (!ret)
|
|
/* if successful -> propagate */
|
|
propagate_rate(clk, 0);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
} else if (clk->pclk) {
|
|
u32 mult = clk->div_factor ? clk->div_factor : 1;
|
|
ret = clk_set_rate(clk->pclk, mult * rate);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(clk_set_rate);
|
|
|
|
/* registers clock in platform clock framework */
|
|
void clk_register(struct clk_lookup *cl)
|
|
{
|
|
struct clk *clk;
|
|
unsigned long flags;
|
|
|
|
if (!cl || !cl->clk)
|
|
return;
|
|
clk = cl->clk;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
|
|
INIT_LIST_HEAD(&clk->children);
|
|
if (clk->flags & ALWAYS_ENABLED)
|
|
clk->ops = NULL;
|
|
else if (!clk->ops)
|
|
clk->ops = &generic_clkops;
|
|
|
|
/* root clock don't have any parents */
|
|
if (!clk->pclk && !clk->pclk_sel) {
|
|
list_add(&clk->sibling, &root_clks);
|
|
} else if (clk->pclk && !clk->pclk_sel) {
|
|
/* add clocks with only one parent to parent's children list */
|
|
list_add(&clk->sibling, &clk->pclk->children);
|
|
} else {
|
|
/* clocks with more than one parent */
|
|
struct pclk_info *pclk_info;
|
|
|
|
pclk_info = pclk_info_get(clk);
|
|
if (!pclk_info) {
|
|
pr_err("CLKDEV: invalid pclk info of clk with"
|
|
" %s dev_id and %s con_id\n",
|
|
cl->dev_id, cl->con_id);
|
|
} else {
|
|
clk->pclk = pclk_info->pclk;
|
|
list_add(&clk->sibling, &pclk_info->pclk->children);
|
|
}
|
|
}
|
|
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
/* debugfs specific */
|
|
#ifdef CONFIG_DEBUG_FS
|
|
list_add(&clk->node, &clocks);
|
|
clk->cl = cl;
|
|
#endif
|
|
|
|
/* add clock to arm clockdev framework */
|
|
clkdev_add(cl);
|
|
}
|
|
|
|
/**
|
|
* propagate_rate - recalculate and propagate all clocks to children
|
|
* @pclk: parent clock required to be propogated
|
|
* @on_init: flag for enabling clocks which are ENABLED_ON_INIT.
|
|
*
|
|
* Recalculates all children clocks
|
|
*/
|
|
void propagate_rate(struct clk *pclk, int on_init)
|
|
{
|
|
struct clk *clk, *_temp;
|
|
int ret = 0;
|
|
|
|
list_for_each_entry_safe(clk, _temp, &pclk->children, sibling) {
|
|
if (clk->recalc) {
|
|
ret = clk->recalc(clk);
|
|
/*
|
|
* recalc will return error if clk out is not programmed
|
|
* In this case configure default rate.
|
|
*/
|
|
if (ret && clk->set_rate)
|
|
clk->set_rate(clk, 0);
|
|
}
|
|
propagate_rate(clk, on_init);
|
|
|
|
if (!on_init)
|
|
continue;
|
|
|
|
/* Enable clks enabled on init, in software view */
|
|
if (clk->flags & ENABLED_ON_INIT)
|
|
do_clk_enable(clk);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* round_rate_index - return closest programmable rate index in rate_config tbl
|
|
* @clk: ptr to clock structure
|
|
* @drate: desired rate
|
|
* @rate: final rate will be returned in this variable only.
|
|
*
|
|
* Finds index in rate_config for highest clk rate which is less than
|
|
* requested rate. If there is no clk rate lesser than requested rate then
|
|
* -EINVAL is returned. This routine assumes that rate_config is written
|
|
* in incrementing order of clk rates.
|
|
* If drate passed is zero then default rate is programmed.
|
|
*/
|
|
static int
|
|
round_rate_index(struct clk *clk, unsigned long drate, unsigned long *rate)
|
|
{
|
|
unsigned long tmp = 0, prev_rate = 0;
|
|
int index;
|
|
|
|
if (!clk->calc_rate)
|
|
return -EFAULT;
|
|
|
|
if (!drate)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* This loops ends on two conditions:
|
|
* - as soon as clk is found with rate greater than requested rate.
|
|
* - if all clks in rate_config are smaller than requested rate.
|
|
*/
|
|
for (index = 0; index < clk->rate_config.count; index++) {
|
|
prev_rate = tmp;
|
|
tmp = clk->calc_rate(clk, index);
|
|
if (drate < tmp) {
|
|
index--;
|
|
break;
|
|
}
|
|
}
|
|
/* return if can't find suitable clock */
|
|
if (index < 0) {
|
|
index = -EINVAL;
|
|
*rate = 0;
|
|
} else if (index == clk->rate_config.count) {
|
|
/* program with highest clk rate possible */
|
|
index = clk->rate_config.count - 1;
|
|
*rate = tmp;
|
|
} else
|
|
*rate = prev_rate;
|
|
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* clk_round_rate - adjust a rate to the exact rate a clock can provide
|
|
* @clk: clock source
|
|
* @rate: desired clock rate in Hz
|
|
*
|
|
* Returns rounded clock rate in Hz, or negative errno.
|
|
*/
|
|
long clk_round_rate(struct clk *clk, unsigned long drate)
|
|
{
|
|
long rate = 0;
|
|
int index;
|
|
|
|
/*
|
|
* propagate call to parent who supports calc_rate. Similar approach is
|
|
* used in clk_set_rate.
|
|
*/
|
|
if (!clk->calc_rate) {
|
|
u32 mult;
|
|
if (!clk->pclk)
|
|
return clk->rate;
|
|
|
|
mult = clk->div_factor ? clk->div_factor : 1;
|
|
return clk_round_rate(clk->pclk, mult * drate) / mult;
|
|
}
|
|
|
|
index = round_rate_index(clk, drate, &rate);
|
|
if (index >= 0)
|
|
return rate;
|
|
else
|
|
return index;
|
|
}
|
|
EXPORT_SYMBOL(clk_round_rate);
|
|
|
|
/*All below functions are called with lock held */
|
|
|
|
/*
|
|
* Calculates pll clk rate for specific value of mode, m, n and p
|
|
*
|
|
* In normal mode
|
|
* rate = (2 * M[15:8] * Fin)/(N * 2^P)
|
|
*
|
|
* In Dithered mode
|
|
* rate = (2 * M[15:0] * Fin)/(256 * N * 2^P)
|
|
*/
|
|
unsigned long pll_calc_rate(struct clk *clk, int index)
|
|
{
|
|
unsigned long rate = clk->pclk->rate;
|
|
struct pll_rate_tbl *tbls = clk->rate_config.tbls;
|
|
unsigned int mode;
|
|
|
|
mode = tbls[index].mode ? 256 : 1;
|
|
return (((2 * rate / 10000) * tbls[index].m) /
|
|
(mode * tbls[index].n * (1 << tbls[index].p))) * 10000;
|
|
}
|
|
|
|
/*
|
|
* calculates current programmed rate of pll1
|
|
*
|
|
* In normal mode
|
|
* rate = (2 * M[15:8] * Fin)/(N * 2^P)
|
|
*
|
|
* In Dithered mode
|
|
* rate = (2 * M[15:0] * Fin)/(256 * N * 2^P)
|
|
*/
|
|
int pll_clk_recalc(struct clk *clk)
|
|
{
|
|
struct pll_clk_config *config = clk->private_data;
|
|
unsigned int num = 2, den = 0, val, mode = 0;
|
|
|
|
mode = (readl(config->mode_reg) >> config->masks->mode_shift) &
|
|
config->masks->mode_mask;
|
|
|
|
val = readl(config->cfg_reg);
|
|
/* calculate denominator */
|
|
den = (val >> config->masks->div_p_shift) & config->masks->div_p_mask;
|
|
den = 1 << den;
|
|
den *= (val >> config->masks->div_n_shift) & config->masks->div_n_mask;
|
|
|
|
/* calculate numerator & denominator */
|
|
if (!mode) {
|
|
/* Normal mode */
|
|
num *= (val >> config->masks->norm_fdbk_m_shift) &
|
|
config->masks->norm_fdbk_m_mask;
|
|
} else {
|
|
/* Dithered mode */
|
|
num *= (val >> config->masks->dith_fdbk_m_shift) &
|
|
config->masks->dith_fdbk_m_mask;
|
|
den *= 256;
|
|
}
|
|
|
|
if (!den)
|
|
return -EINVAL;
|
|
|
|
clk->rate = (((clk->pclk->rate/10000) * num) / den) * 10000;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Configures new clock rate of pll
|
|
*/
|
|
int pll_clk_set_rate(struct clk *clk, unsigned long desired_rate)
|
|
{
|
|
struct pll_rate_tbl *tbls = clk->rate_config.tbls;
|
|
struct pll_clk_config *config = clk->private_data;
|
|
unsigned long val, rate;
|
|
int i;
|
|
|
|
i = round_rate_index(clk, desired_rate, &rate);
|
|
if (i < 0)
|
|
return i;
|
|
|
|
val = readl(config->mode_reg) &
|
|
~(config->masks->mode_mask << config->masks->mode_shift);
|
|
val |= (tbls[i].mode & config->masks->mode_mask) <<
|
|
config->masks->mode_shift;
|
|
writel(val, config->mode_reg);
|
|
|
|
val = readl(config->cfg_reg) &
|
|
~(config->masks->div_p_mask << config->masks->div_p_shift);
|
|
val |= (tbls[i].p & config->masks->div_p_mask) <<
|
|
config->masks->div_p_shift;
|
|
val &= ~(config->masks->div_n_mask << config->masks->div_n_shift);
|
|
val |= (tbls[i].n & config->masks->div_n_mask) <<
|
|
config->masks->div_n_shift;
|
|
val &= ~(config->masks->dith_fdbk_m_mask <<
|
|
config->masks->dith_fdbk_m_shift);
|
|
if (tbls[i].mode)
|
|
val |= (tbls[i].m & config->masks->dith_fdbk_m_mask) <<
|
|
config->masks->dith_fdbk_m_shift;
|
|
else
|
|
val |= (tbls[i].m & config->masks->norm_fdbk_m_mask) <<
|
|
config->masks->norm_fdbk_m_shift;
|
|
|
|
writel(val, config->cfg_reg);
|
|
|
|
clk->rate = rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculates ahb, apb clk rate for specific value of div
|
|
*/
|
|
unsigned long bus_calc_rate(struct clk *clk, int index)
|
|
{
|
|
unsigned long rate = clk->pclk->rate;
|
|
struct bus_rate_tbl *tbls = clk->rate_config.tbls;
|
|
|
|
return rate / (tbls[index].div + 1);
|
|
}
|
|
|
|
/* calculates current programmed rate of ahb or apb bus */
|
|
int bus_clk_recalc(struct clk *clk)
|
|
{
|
|
struct bus_clk_config *config = clk->private_data;
|
|
unsigned int div;
|
|
|
|
div = ((readl(config->reg) >> config->masks->shift) &
|
|
config->masks->mask) + 1;
|
|
|
|
if (!div)
|
|
return -EINVAL;
|
|
|
|
clk->rate = (unsigned long)clk->pclk->rate / div;
|
|
return 0;
|
|
}
|
|
|
|
/* Configures new clock rate of AHB OR APB bus */
|
|
int bus_clk_set_rate(struct clk *clk, unsigned long desired_rate)
|
|
{
|
|
struct bus_rate_tbl *tbls = clk->rate_config.tbls;
|
|
struct bus_clk_config *config = clk->private_data;
|
|
unsigned long val, rate;
|
|
int i;
|
|
|
|
i = round_rate_index(clk, desired_rate, &rate);
|
|
if (i < 0)
|
|
return i;
|
|
|
|
val = readl(config->reg) &
|
|
~(config->masks->mask << config->masks->shift);
|
|
val |= (tbls[i].div & config->masks->mask) << config->masks->shift;
|
|
writel(val, config->reg);
|
|
|
|
clk->rate = rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* gives rate for different values of eq, x and y
|
|
*
|
|
* Fout from synthesizer can be given from two equations:
|
|
* Fout1 = (Fin * X/Y)/2 EQ1
|
|
* Fout2 = Fin * X/Y EQ2
|
|
*/
|
|
unsigned long aux_calc_rate(struct clk *clk, int index)
|
|
{
|
|
unsigned long rate = clk->pclk->rate;
|
|
struct aux_rate_tbl *tbls = clk->rate_config.tbls;
|
|
u8 eq = tbls[index].eq ? 1 : 2;
|
|
|
|
return (((rate/10000) * tbls[index].xscale) /
|
|
(tbls[index].yscale * eq)) * 10000;
|
|
}
|
|
|
|
/*
|
|
* calculates current programmed rate of auxiliary synthesizers
|
|
* used by: UART, FIRDA
|
|
*
|
|
* Fout from synthesizer can be given from two equations:
|
|
* Fout1 = (Fin * X/Y)/2
|
|
* Fout2 = Fin * X/Y
|
|
*
|
|
* Selection of eqn 1 or 2 is programmed in register
|
|
*/
|
|
int aux_clk_recalc(struct clk *clk)
|
|
{
|
|
struct aux_clk_config *config = clk->private_data;
|
|
unsigned int num = 1, den = 1, val, eqn;
|
|
|
|
val = readl(config->synth_reg);
|
|
|
|
eqn = (val >> config->masks->eq_sel_shift) &
|
|
config->masks->eq_sel_mask;
|
|
if (eqn == config->masks->eq1_mask)
|
|
den *= 2;
|
|
|
|
/* calculate numerator */
|
|
num = (val >> config->masks->xscale_sel_shift) &
|
|
config->masks->xscale_sel_mask;
|
|
|
|
/* calculate denominator */
|
|
den *= (val >> config->masks->yscale_sel_shift) &
|
|
config->masks->yscale_sel_mask;
|
|
|
|
if (!den)
|
|
return -EINVAL;
|
|
|
|
clk->rate = (((clk->pclk->rate/10000) * num) / den) * 10000;
|
|
return 0;
|
|
}
|
|
|
|
/* Configures new clock rate of auxiliary synthesizers used by: UART, FIRDA*/
|
|
int aux_clk_set_rate(struct clk *clk, unsigned long desired_rate)
|
|
{
|
|
struct aux_rate_tbl *tbls = clk->rate_config.tbls;
|
|
struct aux_clk_config *config = clk->private_data;
|
|
unsigned long val, rate;
|
|
int i;
|
|
|
|
i = round_rate_index(clk, desired_rate, &rate);
|
|
if (i < 0)
|
|
return i;
|
|
|
|
val = readl(config->synth_reg) &
|
|
~(config->masks->eq_sel_mask << config->masks->eq_sel_shift);
|
|
val |= (tbls[i].eq & config->masks->eq_sel_mask) <<
|
|
config->masks->eq_sel_shift;
|
|
val &= ~(config->masks->xscale_sel_mask <<
|
|
config->masks->xscale_sel_shift);
|
|
val |= (tbls[i].xscale & config->masks->xscale_sel_mask) <<
|
|
config->masks->xscale_sel_shift;
|
|
val &= ~(config->masks->yscale_sel_mask <<
|
|
config->masks->yscale_sel_shift);
|
|
val |= (tbls[i].yscale & config->masks->yscale_sel_mask) <<
|
|
config->masks->yscale_sel_shift;
|
|
writel(val, config->synth_reg);
|
|
|
|
clk->rate = rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculates gpt clk rate for different values of mscale and nscale
|
|
*
|
|
* Fout= Fin/((2 ^ (N+1)) * (M+1))
|
|
*/
|
|
unsigned long gpt_calc_rate(struct clk *clk, int index)
|
|
{
|
|
unsigned long rate = clk->pclk->rate;
|
|
struct gpt_rate_tbl *tbls = clk->rate_config.tbls;
|
|
|
|
return rate / ((1 << (tbls[index].nscale + 1)) *
|
|
(tbls[index].mscale + 1));
|
|
}
|
|
|
|
/*
|
|
* calculates current programmed rate of gpt synthesizers
|
|
* Fout from synthesizer can be given from below equations:
|
|
* Fout= Fin/((2 ^ (N+1)) * (M+1))
|
|
*/
|
|
int gpt_clk_recalc(struct clk *clk)
|
|
{
|
|
struct gpt_clk_config *config = clk->private_data;
|
|
unsigned int div = 1, val;
|
|
|
|
val = readl(config->synth_reg);
|
|
div += (val >> config->masks->mscale_sel_shift) &
|
|
config->masks->mscale_sel_mask;
|
|
div *= 1 << (((val >> config->masks->nscale_sel_shift) &
|
|
config->masks->nscale_sel_mask) + 1);
|
|
|
|
if (!div)
|
|
return -EINVAL;
|
|
|
|
clk->rate = (unsigned long)clk->pclk->rate / div;
|
|
return 0;
|
|
}
|
|
|
|
/* Configures new clock rate of gptiliary synthesizers used by: UART, FIRDA*/
|
|
int gpt_clk_set_rate(struct clk *clk, unsigned long desired_rate)
|
|
{
|
|
struct gpt_rate_tbl *tbls = clk->rate_config.tbls;
|
|
struct gpt_clk_config *config = clk->private_data;
|
|
unsigned long val, rate;
|
|
int i;
|
|
|
|
i = round_rate_index(clk, desired_rate, &rate);
|
|
if (i < 0)
|
|
return i;
|
|
|
|
val = readl(config->synth_reg) & ~(config->masks->mscale_sel_mask <<
|
|
config->masks->mscale_sel_shift);
|
|
val |= (tbls[i].mscale & config->masks->mscale_sel_mask) <<
|
|
config->masks->mscale_sel_shift;
|
|
val &= ~(config->masks->nscale_sel_mask <<
|
|
config->masks->nscale_sel_shift);
|
|
val |= (tbls[i].nscale & config->masks->nscale_sel_mask) <<
|
|
config->masks->nscale_sel_shift;
|
|
writel(val, config->synth_reg);
|
|
|
|
clk->rate = rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculates clcd clk rate for different values of div
|
|
*
|
|
* Fout from synthesizer can be given from below equation:
|
|
* Fout= Fin/2*div (division factor)
|
|
* div is 17 bits:-
|
|
* 0-13 (fractional part)
|
|
* 14-16 (integer part)
|
|
* To calculate Fout we left shift val by 14 bits and divide Fin by
|
|
* complete div (including fractional part) and then right shift the
|
|
* result by 14 places.
|
|
*/
|
|
unsigned long clcd_calc_rate(struct clk *clk, int index)
|
|
{
|
|
unsigned long rate = clk->pclk->rate;
|
|
struct clcd_rate_tbl *tbls = clk->rate_config.tbls;
|
|
|
|
rate /= 1000;
|
|
rate <<= 12;
|
|
rate /= (2 * tbls[index].div);
|
|
rate >>= 12;
|
|
rate *= 1000;
|
|
|
|
return rate;
|
|
}
|
|
|
|
/*
|
|
* calculates current programmed rate of clcd synthesizer
|
|
* Fout from synthesizer can be given from below equation:
|
|
* Fout= Fin/2*div (division factor)
|
|
* div is 17 bits:-
|
|
* 0-13 (fractional part)
|
|
* 14-16 (integer part)
|
|
* To calculate Fout we left shift val by 14 bits and divide Fin by
|
|
* complete div (including fractional part) and then right shift the
|
|
* result by 14 places.
|
|
*/
|
|
int clcd_clk_recalc(struct clk *clk)
|
|
{
|
|
struct clcd_clk_config *config = clk->private_data;
|
|
unsigned int div = 1;
|
|
unsigned long prate;
|
|
unsigned int val;
|
|
|
|
val = readl(config->synth_reg);
|
|
div = (val >> config->masks->div_factor_shift) &
|
|
config->masks->div_factor_mask;
|
|
|
|
if (!div)
|
|
return -EINVAL;
|
|
|
|
prate = clk->pclk->rate / 1000; /* first level division, make it KHz */
|
|
|
|
clk->rate = (((unsigned long)prate << 12) / (2 * div)) >> 12;
|
|
clk->rate *= 1000;
|
|
return 0;
|
|
}
|
|
|
|
/* Configures new clock rate of auxiliary synthesizers used by: UART, FIRDA*/
|
|
int clcd_clk_set_rate(struct clk *clk, unsigned long desired_rate)
|
|
{
|
|
struct clcd_rate_tbl *tbls = clk->rate_config.tbls;
|
|
struct clcd_clk_config *config = clk->private_data;
|
|
unsigned long val, rate;
|
|
int i;
|
|
|
|
i = round_rate_index(clk, desired_rate, &rate);
|
|
if (i < 0)
|
|
return i;
|
|
|
|
val = readl(config->synth_reg) & ~(config->masks->div_factor_mask <<
|
|
config->masks->div_factor_shift);
|
|
val |= (tbls[i].div & config->masks->div_factor_mask) <<
|
|
config->masks->div_factor_shift;
|
|
writel(val, config->synth_reg);
|
|
|
|
clk->rate = rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Used for clocks that always have value as the parent clock divided by a
|
|
* fixed divisor
|
|
*/
|
|
int follow_parent(struct clk *clk)
|
|
{
|
|
unsigned int div_factor = (clk->div_factor < 1) ? 1 : clk->div_factor;
|
|
|
|
clk->rate = clk->pclk->rate/div_factor;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* recalc_root_clocks - recalculate and propagate all root clocks
|
|
*
|
|
* Recalculates all root clocks (clocks with no parent), which if the
|
|
* clock's .recalc is set correctly, should also propagate their rates.
|
|
*/
|
|
void recalc_root_clocks(void)
|
|
{
|
|
struct clk *pclk;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
list_for_each_entry(pclk, &root_clks, sibling) {
|
|
if (pclk->recalc) {
|
|
ret = pclk->recalc(pclk);
|
|
/*
|
|
* recalc will return error if clk out is not programmed
|
|
* In this case configure default clock.
|
|
*/
|
|
if (ret && pclk->set_rate)
|
|
pclk->set_rate(pclk, 0);
|
|
}
|
|
propagate_rate(pclk, 1);
|
|
/* Enable clks enabled on init, in software view */
|
|
if (pclk->flags & ENABLED_ON_INIT)
|
|
do_clk_enable(pclk);
|
|
}
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
|
|
void __init clk_init(void)
|
|
{
|
|
recalc_root_clocks();
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
/*
|
|
* debugfs support to trace clock tree hierarchy and attributes
|
|
*/
|
|
static struct dentry *clk_debugfs_root;
|
|
static int clk_debugfs_register_one(struct clk *c)
|
|
{
|
|
int err;
|
|
struct dentry *d;
|
|
struct clk *pa = c->pclk;
|
|
char s[255];
|
|
char *p = s;
|
|
|
|
if (c) {
|
|
if (c->cl->con_id)
|
|
p += sprintf(p, "%s", c->cl->con_id);
|
|
if (c->cl->dev_id)
|
|
p += sprintf(p, "%s", c->cl->dev_id);
|
|
}
|
|
d = debugfs_create_dir(s, pa ? pa->dent : clk_debugfs_root);
|
|
if (!d)
|
|
return -ENOMEM;
|
|
c->dent = d;
|
|
|
|
d = debugfs_create_u32("usage_count", S_IRUGO, c->dent,
|
|
(u32 *)&c->usage_count);
|
|
if (!d) {
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
d = debugfs_create_u32("rate", S_IRUGO, c->dent, (u32 *)&c->rate);
|
|
if (!d) {
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
d = debugfs_create_x32("flags", S_IRUGO, c->dent, (u32 *)&c->flags);
|
|
if (!d) {
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
return 0;
|
|
|
|
err_out:
|
|
debugfs_remove_recursive(c->dent);
|
|
return err;
|
|
}
|
|
|
|
static int clk_debugfs_register(struct clk *c)
|
|
{
|
|
int err;
|
|
struct clk *pa = c->pclk;
|
|
|
|
if (pa && !pa->dent) {
|
|
err = clk_debugfs_register(pa);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
if (!c->dent) {
|
|
err = clk_debugfs_register_one(c);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __init clk_debugfs_init(void)
|
|
{
|
|
struct clk *c;
|
|
struct dentry *d;
|
|
int err;
|
|
|
|
d = debugfs_create_dir("clock", NULL);
|
|
if (!d)
|
|
return -ENOMEM;
|
|
clk_debugfs_root = d;
|
|
|
|
list_for_each_entry(c, &clocks, node) {
|
|
err = clk_debugfs_register(c);
|
|
if (err)
|
|
goto err_out;
|
|
}
|
|
return 0;
|
|
err_out:
|
|
debugfs_remove_recursive(clk_debugfs_root);
|
|
return err;
|
|
}
|
|
late_initcall(clk_debugfs_init);
|
|
|
|
static int clk_debugfs_reparent(struct clk *c)
|
|
{
|
|
debugfs_remove(c->dent);
|
|
return clk_debugfs_register_one(c);
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|