/*
* 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