linux-stable/arch/arm/plat-omap/clock.c
Russell King 8b9dbc16d4 [ARM] omap: arrange for clock recalc methods to return the rate
linux-omap source commit 33d000c99ee393fe2042f93e8422f94976d276ce
introduces a way to "dry run" clock changes before they're committed.
However, this involves putting logic to handle this into each and
every recalc function, and unfortunately due to the caching, led to
some bugs.

Solve both of issues by making the recalc methods always return the
clock rate for the clock, which the caller decides what to do with.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2009-02-14 09:59:21 +00:00

453 lines
9.4 KiB
C

/*
* linux/arch/arm/plat-omap/clock.c
*
* Copyright (C) 2004 - 2008 Nokia corporation
* Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com>
*
* Modified for omap shared clock framework by Tony Lindgren <tony@atomide.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/cpufreq.h>
#include <linux/debugfs.h>
#include <linux/io.h>
#include <mach/clock.h>
static LIST_HEAD(clocks);
static DEFINE_MUTEX(clocks_mutex);
static DEFINE_SPINLOCK(clockfw_lock);
static struct clk_functions *arch_clock;
/*-------------------------------------------------------------------------
* Standard clock functions defined in include/linux/clk.h
*-------------------------------------------------------------------------*/
int clk_enable(struct clk *clk)
{
unsigned long flags;
int ret = 0;
if (clk == NULL || IS_ERR(clk))
return -EINVAL;
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_enable)
ret = arch_clock->clk_enable(clk);
spin_unlock_irqrestore(&clockfw_lock, flags);
return ret;
}
EXPORT_SYMBOL(clk_enable);
void clk_disable(struct clk *clk)
{
unsigned long flags;
if (clk == NULL || IS_ERR(clk))
return;
spin_lock_irqsave(&clockfw_lock, flags);
if (clk->usecount == 0) {
printk(KERN_ERR "Trying disable clock %s with 0 usecount\n",
clk->name);
WARN_ON(1);
goto out;
}
if (arch_clock->clk_disable)
arch_clock->clk_disable(clk);
out:
spin_unlock_irqrestore(&clockfw_lock, flags);
}
EXPORT_SYMBOL(clk_disable);
unsigned long clk_get_rate(struct clk *clk)
{
unsigned long flags;
unsigned long ret = 0;
if (clk == NULL || IS_ERR(clk))
return 0;
spin_lock_irqsave(&clockfw_lock, flags);
ret = clk->rate;
spin_unlock_irqrestore(&clockfw_lock, flags);
return ret;
}
EXPORT_SYMBOL(clk_get_rate);
/*-------------------------------------------------------------------------
* Optional clock functions defined in include/linux/clk.h
*-------------------------------------------------------------------------*/
long clk_round_rate(struct clk *clk, unsigned long rate)
{
unsigned long flags;
long ret = 0;
if (clk == NULL || IS_ERR(clk))
return ret;
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_round_rate)
ret = arch_clock->clk_round_rate(clk, rate);
spin_unlock_irqrestore(&clockfw_lock, flags);
return ret;
}
EXPORT_SYMBOL(clk_round_rate);
int clk_set_rate(struct clk *clk, unsigned long rate)
{
unsigned long flags;
int ret = -EINVAL;
if (clk == NULL || IS_ERR(clk))
return ret;
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_set_rate)
ret = arch_clock->clk_set_rate(clk, rate);
if (ret == 0) {
if (clk->recalc)
clk->rate = clk->recalc(clk);
propagate_rate(clk);
}
spin_unlock_irqrestore(&clockfw_lock, flags);
return ret;
}
EXPORT_SYMBOL(clk_set_rate);
int clk_set_parent(struct clk *clk, struct clk *parent)
{
unsigned long flags;
int ret = -EINVAL;
if (clk == NULL || IS_ERR(clk) || parent == NULL || IS_ERR(parent))
return ret;
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_set_parent)
ret = arch_clock->clk_set_parent(clk, parent);
if (ret == 0) {
if (clk->recalc)
clk->rate = clk->recalc(clk);
propagate_rate(clk);
}
spin_unlock_irqrestore(&clockfw_lock, flags);
return ret;
}
EXPORT_SYMBOL(clk_set_parent);
struct clk *clk_get_parent(struct clk *clk)
{
return clk->parent;
}
EXPORT_SYMBOL(clk_get_parent);
/*-------------------------------------------------------------------------
* OMAP specific clock functions shared between omap1 and omap2
*-------------------------------------------------------------------------*/
unsigned int __initdata mpurate;
/*
* By default we use the rate set by the bootloader.
* You can override this with mpurate= cmdline option.
*/
static int __init omap_clk_setup(char *str)
{
get_option(&str, &mpurate);
if (!mpurate)
return 1;
if (mpurate < 1000)
mpurate *= 1000000;
return 1;
}
__setup("mpurate=", omap_clk_setup);
/* Used for clocks that always have same value as the parent clock */
unsigned long followparent_recalc(struct clk *clk)
{
return clk->parent->rate;
}
void clk_reparent(struct clk *child, struct clk *parent)
{
list_del_init(&child->sibling);
if (parent)
list_add(&child->sibling, &parent->children);
child->parent = parent;
/* now do the debugfs renaming to reattach the child
to the proper parent */
}
/* Propagate rate to children */
void propagate_rate(struct clk * tclk)
{
struct clk *clkp;
list_for_each_entry(clkp, &tclk->children, sibling) {
if (clkp->recalc)
clkp->rate = clkp->recalc(clkp);
propagate_rate(clkp);
}
}
static LIST_HEAD(root_clks);
/**
* recalculate_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.
* Called at init.
*/
void recalculate_root_clocks(void)
{
struct clk *clkp;
list_for_each_entry(clkp, &root_clks, sibling) {
if (clkp->recalc)
clkp->rate = clkp->recalc(clkp);
propagate_rate(clkp);
}
}
void clk_init_one(struct clk *clk)
{
INIT_LIST_HEAD(&clk->children);
}
int clk_register(struct clk *clk)
{
if (clk == NULL || IS_ERR(clk))
return -EINVAL;
/*
* trap out already registered clocks
*/
if (clk->node.next || clk->node.prev)
return 0;
mutex_lock(&clocks_mutex);
if (clk->parent)
list_add(&clk->sibling, &clk->parent->children);
else
list_add(&clk->sibling, &root_clks);
list_add(&clk->node, &clocks);
if (clk->init)
clk->init(clk);
mutex_unlock(&clocks_mutex);
return 0;
}
EXPORT_SYMBOL(clk_register);
void clk_unregister(struct clk *clk)
{
if (clk == NULL || IS_ERR(clk))
return;
mutex_lock(&clocks_mutex);
list_del(&clk->sibling);
list_del(&clk->node);
mutex_unlock(&clocks_mutex);
}
EXPORT_SYMBOL(clk_unregister);
void clk_enable_init_clocks(void)
{
struct clk *clkp;
list_for_each_entry(clkp, &clocks, node) {
if (clkp->flags & ENABLE_ON_INIT)
clk_enable(clkp);
}
}
EXPORT_SYMBOL(clk_enable_init_clocks);
/*
* Low level helpers
*/
static int clkll_enable_null(struct clk *clk)
{
return 0;
}
static void clkll_disable_null(struct clk *clk)
{
}
const struct clkops clkops_null = {
.enable = clkll_enable_null,
.disable = clkll_disable_null,
};
#ifdef CONFIG_CPU_FREQ
void clk_init_cpufreq_table(struct cpufreq_frequency_table **table)
{
unsigned long flags;
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_init_cpufreq_table)
arch_clock->clk_init_cpufreq_table(table);
spin_unlock_irqrestore(&clockfw_lock, flags);
}
EXPORT_SYMBOL(clk_init_cpufreq_table);
#endif
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_OMAP_RESET_CLOCKS
/*
* Disable any unused clocks left on by the bootloader
*/
static int __init clk_disable_unused(void)
{
struct clk *ck;
unsigned long flags;
list_for_each_entry(ck, &clocks, node) {
if (ck->ops == &clkops_null)
continue;
if (ck->usecount > 0 || ck->enable_reg == 0)
continue;
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_disable_unused)
arch_clock->clk_disable_unused(ck);
spin_unlock_irqrestore(&clockfw_lock, flags);
}
return 0;
}
late_initcall(clk_disable_unused);
#endif
int __init clk_init(struct clk_functions * custom_clocks)
{
if (!custom_clocks) {
printk(KERN_ERR "No custom clock functions registered\n");
BUG();
}
arch_clock = custom_clocks;
return 0;
}
#if defined(CONFIG_PM_DEBUG) && defined(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, *child;
struct clk *pa = c->parent;
char s[255];
char *p = s;
p += sprintf(p, "%s", c->name);
if (c->id != 0)
sprintf(p, ":%d", c->id);
d = debugfs_create_dir(s, pa ? pa->dent : clk_debugfs_root);
if (!d)
return -ENOMEM;
c->dent = d;
d = debugfs_create_u8("usecount", S_IRUGO, c->dent, (u8 *)&c->usecount);
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:
d = c->dent;
list_for_each_entry(child, &d->d_subdirs, d_u.d_child)
debugfs_remove(child);
debugfs_remove(c->dent);
return err;
}
static int clk_debugfs_register(struct clk *c)
{
int err;
struct clk *pa = c->parent;
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(clk_debugfs_root); /* REVISIT: Cleanup correctly */
return err;
}
late_initcall(clk_debugfs_init);
#endif /* defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS) */