mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-25 05:34:00 +08:00
8c0236fc46
Clock framework for SPEAr is based upon clkdev framework for ARM Reviewed-by: Linus Walleij <linux.walleij@stericsson.com> Signed-off-by: Viresh Kumar <viresh.kumar@st.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
436 lines
10 KiB
C
436 lines
10 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/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/spinlock.h>
|
|
#include <mach/misc_regs.h>
|
|
#include <plat/clock.h>
|
|
|
|
static DEFINE_SPINLOCK(clocks_lock);
|
|
static LIST_HEAD(root_clks);
|
|
|
|
static void propagate_rate(struct list_head *);
|
|
|
|
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,
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
|
|
if (!clk || IS_ERR(clk))
|
|
return -EFAULT;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
if (clk->usage_count == 0) {
|
|
if (clk->ops && clk->ops->enable)
|
|
ret = clk->ops->enable(clk);
|
|
}
|
|
clk->usage_count++;
|
|
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;
|
|
|
|
if (!clk || IS_ERR(clk))
|
|
return;
|
|
|
|
WARN_ON(clk->usage_count == 0);
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
clk->usage_count--;
|
|
if (clk->usage_count == 0) {
|
|
if (clk->ops && clk->ops->disable)
|
|
clk->ops->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 || IS_ERR(clk) || !parent || IS_ERR(parent))
|
|
return -EFAULT;
|
|
if (clk->usage_count)
|
|
return -EBUSY;
|
|
if (!clk->pclk_sel)
|
|
return -EPERM;
|
|
if (clk->pclk == parent)
|
|
return 0;
|
|
|
|
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_mask << clk->pclk_sel_shift;
|
|
writel(val, clk->pclk_sel->pclk_sel_reg);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
/* reflect parent change in software */
|
|
clk->recalc(clk);
|
|
propagate_rate(&clk->children);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clk_set_parent);
|
|
|
|
/* registers clock in platform clock framework */
|
|
void clk_register(struct clk_lookup *cl)
|
|
{
|
|
struct clk *clk = cl->clk;
|
|
unsigned long flags;
|
|
|
|
if (!clk || IS_ERR(clk))
|
|
return;
|
|
|
|
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);
|
|
/* add clocks with only one parent to parent's children list */
|
|
} else if (clk->pclk && !clk->pclk_sel) {
|
|
list_add(&clk->sibling, &clk->pclk->children);
|
|
} else {
|
|
/* add clocks with > 1 parent to 1st parent's children list */
|
|
list_add(&clk->sibling,
|
|
&clk->pclk_sel->pclk_info[0].pclk->children);
|
|
}
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
/* add clock to arm clockdev framework */
|
|
clkdev_add(cl);
|
|
}
|
|
|
|
/**
|
|
* propagate_rate - recalculate and propagate all clocks in list head
|
|
*
|
|
* Recalculates all root clocks in list head, which if the clock's .recalc is
|
|
* set correctly, should also propagate their rates.
|
|
*/
|
|
static void propagate_rate(struct list_head *lhead)
|
|
{
|
|
struct clk *clkp, *_temp;
|
|
|
|
list_for_each_entry_safe(clkp, _temp, lhead, sibling) {
|
|
if (clkp->recalc)
|
|
clkp->recalc(clkp);
|
|
propagate_rate(&clkp->children);
|
|
}
|
|
}
|
|
|
|
/* returns current programmed clocks clock info structure */
|
|
static struct pclk_info *pclk_info_get(struct clk *clk)
|
|
{
|
|
unsigned int mask, i;
|
|
unsigned long flags;
|
|
struct pclk_info *info = NULL;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
mask = (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_mask == mask)
|
|
info = &clk->pclk_sel->pclk_info[i];
|
|
}
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
return info;
|
|
}
|
|
|
|
/*
|
|
* Set pclk as cclk's parent and add clock sibling node to current parents
|
|
* children list
|
|
*/
|
|
static void change_parent(struct clk *cclk, struct clk *pclk)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
list_del(&cclk->sibling);
|
|
list_add(&cclk->sibling, &pclk->children);
|
|
|
|
cclk->pclk = pclk;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
*/
|
|
void pll1_clk_recalc(struct clk *clk)
|
|
{
|
|
struct pll_clk_config *config = clk->private_data;
|
|
unsigned int num = 2, den = 0, val, mode = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
mode = (readl(config->mode_reg) >> PLL_MODE_SHIFT) &
|
|
PLL_MODE_MASK;
|
|
|
|
val = readl(config->cfg_reg);
|
|
/* calculate denominator */
|
|
den = (val >> PLL_DIV_P_SHIFT) & PLL_DIV_P_MASK;
|
|
den = 1 << den;
|
|
den *= (val >> PLL_DIV_N_SHIFT) & PLL_DIV_N_MASK;
|
|
|
|
/* calculate numerator & denominator */
|
|
if (!mode) {
|
|
/* Normal mode */
|
|
num *= (val >> PLL_NORM_FDBK_M_SHIFT) & PLL_NORM_FDBK_M_MASK;
|
|
} else {
|
|
/* Dithered mode */
|
|
num *= (val >> PLL_DITH_FDBK_M_SHIFT) & PLL_DITH_FDBK_M_MASK;
|
|
den *= 256;
|
|
}
|
|
|
|
clk->rate = (((clk->pclk->rate/10000) * num) / den) * 10000;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
|
|
/* calculates current programmed rate of ahb or apb bus */
|
|
void bus_clk_recalc(struct clk *clk)
|
|
{
|
|
struct bus_clk_config *config = clk->private_data;
|
|
unsigned int div;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
div = ((readl(config->reg) >> config->shift) & config->mask) + 1;
|
|
clk->rate = (unsigned long)clk->pclk->rate / div;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
void aux_clk_recalc(struct clk *clk)
|
|
{
|
|
struct aux_clk_config *config = clk->private_data;
|
|
struct pclk_info *pclk_info = NULL;
|
|
unsigned int num = 1, den = 1, val, eqn;
|
|
unsigned long flags;
|
|
|
|
/* get current programmed parent */
|
|
pclk_info = pclk_info_get(clk);
|
|
if (!pclk_info) {
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
clk->pclk = NULL;
|
|
clk->rate = 0;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
return;
|
|
}
|
|
|
|
change_parent(clk, pclk_info->pclk);
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
if (pclk_info->scalable) {
|
|
val = readl(config->synth_reg);
|
|
|
|
eqn = (val >> AUX_EQ_SEL_SHIFT) & AUX_EQ_SEL_MASK;
|
|
if (eqn == AUX_EQ1_SEL)
|
|
den *= 2;
|
|
|
|
/* calculate numerator */
|
|
num = (val >> AUX_XSCALE_SHIFT) & AUX_XSCALE_MASK;
|
|
|
|
/* calculate denominator */
|
|
den *= (val >> AUX_YSCALE_SHIFT) & AUX_YSCALE_MASK;
|
|
val = (((clk->pclk->rate/10000) * num) / den) * 10000;
|
|
} else
|
|
val = clk->pclk->rate;
|
|
|
|
clk->rate = val;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* calculates current programmed rate of gpt synthesizers
|
|
* Fout from synthesizer can be given from below equations:
|
|
* Fout= Fin/((2 ^ (N+1)) * (M+1))
|
|
*/
|
|
void gpt_clk_recalc(struct clk *clk)
|
|
{
|
|
struct aux_clk_config *config = clk->private_data;
|
|
struct pclk_info *pclk_info = NULL;
|
|
unsigned int div = 1, val;
|
|
unsigned long flags;
|
|
|
|
pclk_info = pclk_info_get(clk);
|
|
if (!pclk_info) {
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
clk->pclk = NULL;
|
|
clk->rate = 0;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
return;
|
|
}
|
|
|
|
change_parent(clk, pclk_info->pclk);
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
if (pclk_info->scalable) {
|
|
val = readl(config->synth_reg);
|
|
div += (val >> GPT_MSCALE_SHIFT) & GPT_MSCALE_MASK;
|
|
div *= 1 << (((val >> GPT_NSCALE_SHIFT) & GPT_NSCALE_MASK) + 1);
|
|
}
|
|
|
|
clk->rate = (unsigned long)clk->pclk->rate / div;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* Used for clocks that always have same value as the parent clock divided by a
|
|
* fixed divisor
|
|
*/
|
|
void follow_parent(struct clk *clk)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
clk->rate = clk->pclk->rate;
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
propagate_rate(&root_clks);
|
|
}
|