mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-11 05:17:10 +09:00
ARM: tegra: clock: Redo clock locking
Give each clock its own lock, and remove all lock traversals from parent to child clocks to prevent AB-BA deadlocks. Signed-off-by: Colin Cross <ccross@android.com> Change-Id: I0afb7d1bca956439b1a4f17bbc6748aaec706b49
This commit is contained in:
@@ -18,119 +18,205 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/clkdev.h>
|
||||
|
||||
#include <mach/clk.h>
|
||||
|
||||
#include "board.h"
|
||||
#include "clock.h"
|
||||
#include "dvfs.h"
|
||||
|
||||
static LIST_HEAD(clocks);
|
||||
|
||||
/*
|
||||
* clock_lock must be held when:
|
||||
* Accessing any clock register non-atomically
|
||||
* or
|
||||
* Relying on any state of a clk struct not to change, unless clk_is_dvfs
|
||||
* returns true on that clk struct, and dvfs_lock is held instead.
|
||||
* Locking:
|
||||
*
|
||||
* Any function that changes the state of a clk struct must hold
|
||||
* the dvfs_lock if clk_is_auto_dvfs(clk) is true, and the clock_lock.
|
||||
* Each struct clk has a lock. Depending on the cansleep flag, that lock
|
||||
* may be a spinlock or a mutex. For most clocks, the spinlock is sufficient,
|
||||
* and using the spinlock allows the clock to be manipulated from an interrupt
|
||||
* or while holding a spinlock. Some clocks may need to adjust a regulator
|
||||
* in order to maintain the required voltage for a new frequency. Those
|
||||
* clocks set the cansleep flag, and take a mutex so that the regulator api
|
||||
* can be used while holding the lock.
|
||||
*
|
||||
* When taking dvfs_lock and clock_lock, dvfs_lock must be taken first.
|
||||
* To avoid AB-BA locking problems, locks must always be traversed from child
|
||||
* clock to parent clock. For example, when enabling a clock, the clock's lock
|
||||
* is taken, and then clk_enable is called on the parent, which take's the
|
||||
* parent clock's lock. There are two exceptions to this ordering:
|
||||
* 1. When setting a clock as cansleep, in which case the entire list of clocks
|
||||
* is traversed to set the children as cansleep as well. This must occur
|
||||
* during init, before any calls to clk_get, so no other clock locks can
|
||||
* get taken.
|
||||
* 2. When dumping the clock tree through debugfs. In this case, clk_lock_all
|
||||
* is called, which attemps to iterate through the entire list of clocks
|
||||
* and take every clock lock. If any call to clk_trylock fails, a locked
|
||||
* clocks are unlocked, and the process is retried. When all the locks
|
||||
* are held, the only clock operation that can be called is
|
||||
* clk_get_rate_all_locked.
|
||||
*
|
||||
* Within a single clock, no clock operation can call another clock operation
|
||||
* on itself, except for clk_get_rate_locked. Any clock operation can call
|
||||
* any other clock operation on any of it's possible parents.
|
||||
*
|
||||
* clk_set_cansleep is used to mark a clock as sleeping. It is called during
|
||||
* dvfs (Dynamic Voltage and Frequency Scaling) init on any clock that has a
|
||||
* dvfs requirement. It can only be called on clocks that are the sole parent
|
||||
* of all of their child clocks, meaning the child clock can not be reparented
|
||||
* onto a different, possibly non-sleeping, clock. This is inherently true
|
||||
* of all leaf clocks in the clock tree
|
||||
*
|
||||
* An additional lock, clock_list_lock, is used to protect the list of all
|
||||
* clocks.
|
||||
*
|
||||
* The clock operations must lock internally to protect against
|
||||
* read-modify-write on registers that are shared by multiple clocks
|
||||
*/
|
||||
static DEFINE_SPINLOCK(clock_lock);
|
||||
static DEFINE_MUTEX(clock_list_lock);
|
||||
static LIST_HEAD(clocks);
|
||||
|
||||
static inline bool clk_is_auto_dvfs(struct clk *c)
|
||||
{
|
||||
smp_rmb();
|
||||
return c->auto_dvfs;
|
||||
};
|
||||
}
|
||||
|
||||
static inline bool clk_is_dvfs(struct clk *c)
|
||||
{
|
||||
smp_rmb();
|
||||
return c->is_dvfs;
|
||||
};
|
||||
}
|
||||
|
||||
static inline bool clk_cansleep(struct clk *c)
|
||||
{
|
||||
return c->cansleep;
|
||||
}
|
||||
|
||||
#define clk_lock_save(c, flags) \
|
||||
do { \
|
||||
if (clk_cansleep(c)) { \
|
||||
flags = 0; \
|
||||
mutex_lock(&c->mutex); \
|
||||
} else { \
|
||||
spin_lock_irqsave(&c->spinlock, flags); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define clk_unlock_restore(c, flags) \
|
||||
do { \
|
||||
if (clk_cansleep(c)) \
|
||||
mutex_unlock(&c->mutex); \
|
||||
else \
|
||||
spin_unlock_irqrestore(&c->spinlock, flags); \
|
||||
} while (0)
|
||||
|
||||
static inline void clk_lock_init(struct clk *c)
|
||||
{
|
||||
mutex_init(&c->mutex);
|
||||
spin_lock_init(&c->spinlock);
|
||||
}
|
||||
|
||||
struct clk *tegra_get_clock_by_name(const char *name)
|
||||
{
|
||||
struct clk *c;
|
||||
struct clk *ret = NULL;
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
mutex_lock(&clock_list_lock);
|
||||
list_for_each_entry(c, &clocks, node) {
|
||||
if (strcmp(c->name, name) == 0) {
|
||||
ret = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
mutex_unlock(&clock_list_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Must be called with clk_lock(c) held */
|
||||
static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p)
|
||||
{
|
||||
u64 rate;
|
||||
|
||||
rate = p->rate;
|
||||
rate = clk_get_rate(p);
|
||||
|
||||
if (c->mul != 0 && c->div != 0) {
|
||||
rate = rate * c->mul;
|
||||
rate *= c->mul;
|
||||
do_div(rate, c->div);
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static void clk_recalculate_rate(struct clk *c)
|
||||
/* Must be called with clk_lock(c) held */
|
||||
unsigned long clk_get_rate_locked(struct clk *c)
|
||||
{
|
||||
unsigned long rate;
|
||||
|
||||
if (!c->parent)
|
||||
return;
|
||||
if (c->parent)
|
||||
rate = clk_predict_rate_from_parent(c, c->parent);
|
||||
else
|
||||
rate = c->rate;
|
||||
|
||||
rate = clk_predict_rate_from_parent(c, c->parent);
|
||||
return rate;
|
||||
}
|
||||
|
||||
if (rate > c->max_rate)
|
||||
pr_warn("clocks: Set clock %s to rate %lu, max is %lu\n",
|
||||
c->name, rate, c->max_rate);
|
||||
unsigned long clk_get_rate(struct clk *c)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned long rate;
|
||||
|
||||
c->rate = rate;
|
||||
clk_lock_save(c, flags);
|
||||
|
||||
rate = clk_get_rate_locked(c);
|
||||
|
||||
clk_unlock_restore(c, flags);
|
||||
|
||||
return rate;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_get_rate);
|
||||
|
||||
static void __clk_set_cansleep(struct clk *c)
|
||||
{
|
||||
struct clk *child;
|
||||
BUG_ON(mutex_is_locked(&c->mutex));
|
||||
BUG_ON(spin_is_locked(&c->spinlock));
|
||||
|
||||
list_for_each_entry(child, &clocks, node) {
|
||||
if (child->parent != c)
|
||||
continue;
|
||||
|
||||
WARN(child->ops && child->ops->set_parent,
|
||||
"can't make child clock %s of %s "
|
||||
"sleepable if it's parent could change",
|
||||
child->name, c->name);
|
||||
|
||||
__clk_set_cansleep(child);
|
||||
}
|
||||
|
||||
c->cansleep = true;
|
||||
}
|
||||
|
||||
/* Must be called before any clk_get calls */
|
||||
void clk_set_cansleep(struct clk *c)
|
||||
{
|
||||
|
||||
mutex_lock(&clock_list_lock);
|
||||
__clk_set_cansleep(c);
|
||||
mutex_unlock(&clock_list_lock);
|
||||
}
|
||||
|
||||
int clk_reparent(struct clk *c, struct clk *parent)
|
||||
{
|
||||
c->parent = parent;
|
||||
list_del(&c->sibling);
|
||||
list_add_tail(&c->sibling, &parent->children);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void propagate_rate(struct clk *c)
|
||||
{
|
||||
struct clk *clkp;
|
||||
|
||||
list_for_each_entry(clkp, &c->children, sibling) {
|
||||
clk_recalculate_rate(clkp);
|
||||
propagate_rate(clkp);
|
||||
}
|
||||
}
|
||||
|
||||
void clk_init(struct clk *c)
|
||||
{
|
||||
unsigned long flags;
|
||||
clk_lock_init(c);
|
||||
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
|
||||
INIT_LIST_HEAD(&c->children);
|
||||
INIT_LIST_HEAD(&c->sibling);
|
||||
INIT_LIST_HEAD(&c->dvfs);
|
||||
|
||||
if (c->ops && c->ops->init)
|
||||
@@ -145,71 +231,58 @@ void clk_init(struct clk *c)
|
||||
c->state = ON;
|
||||
}
|
||||
|
||||
clk_recalculate_rate(c);
|
||||
|
||||
mutex_lock(&clock_list_lock);
|
||||
list_add(&c->node, &clocks);
|
||||
|
||||
if (c->parent)
|
||||
list_add_tail(&c->sibling, &c->parent->children);
|
||||
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
mutex_unlock(&clock_list_lock);
|
||||
}
|
||||
|
||||
int clk_enable_locked(struct clk *c)
|
||||
int clk_enable(struct clk *c)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
|
||||
clk_lock_save(c, flags);
|
||||
|
||||
if (clk_is_auto_dvfs(c)) {
|
||||
ret = tegra_dvfs_set_rate(c, clk_get_rate_locked(c));
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (c->refcnt == 0) {
|
||||
if (c->parent) {
|
||||
ret = clk_enable_locked(c->parent);
|
||||
ret = clk_enable(c->parent);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (c->ops && c->ops->enable) {
|
||||
ret = c->ops->enable(c);
|
||||
if (ret) {
|
||||
if (c->parent)
|
||||
clk_disable_locked(c->parent);
|
||||
return ret;
|
||||
clk_disable(c->parent);
|
||||
goto out;
|
||||
}
|
||||
c->state = ON;
|
||||
c->set = true;
|
||||
}
|
||||
}
|
||||
c->refcnt++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int clk_enable(struct clk *c)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (clk_is_auto_dvfs(c)) {
|
||||
lock_dvfs();
|
||||
ret = tegra_dvfs_set_rate(c, c->rate);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
ret = clk_enable_locked(c);
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
|
||||
out:
|
||||
if (clk_is_auto_dvfs(c))
|
||||
unlock_dvfs();
|
||||
|
||||
clk_unlock_restore(c, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_enable);
|
||||
|
||||
void clk_disable_locked(struct clk *c)
|
||||
void clk_disable(struct clk *c)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
clk_lock_save(c, flags);
|
||||
|
||||
if (c->refcnt == 0) {
|
||||
WARN(1, "Attempting to disable clock %s with refcnt 0", c->name);
|
||||
clk_unlock_restore(c, flags);
|
||||
return;
|
||||
}
|
||||
if (c->refcnt == 1) {
|
||||
@@ -217,79 +290,53 @@ void clk_disable_locked(struct clk *c)
|
||||
c->ops->disable(c);
|
||||
|
||||
if (c->parent)
|
||||
clk_disable_locked(c->parent);
|
||||
clk_disable(c->parent);
|
||||
|
||||
c->state = OFF;
|
||||
}
|
||||
c->refcnt--;
|
||||
}
|
||||
|
||||
void clk_disable(struct clk *c)
|
||||
{
|
||||
unsigned long flags;
|
||||
if (clk_is_auto_dvfs(c) && c->refcnt == 0)
|
||||
tegra_dvfs_set_rate(c, 0);
|
||||
|
||||
if (clk_is_auto_dvfs(c))
|
||||
lock_dvfs();
|
||||
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
clk_disable_locked(c);
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
|
||||
if (clk_is_auto_dvfs(c)) {
|
||||
if (c->refcnt == 0)
|
||||
tegra_dvfs_set_rate(c, 0);
|
||||
unlock_dvfs();
|
||||
}
|
||||
clk_unlock_restore(c, flags);
|
||||
}
|
||||
EXPORT_SYMBOL(clk_disable);
|
||||
|
||||
int clk_set_parent_locked(struct clk *c, struct clk *parent)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!c->ops || !c->ops->set_parent)
|
||||
return -ENOSYS;
|
||||
|
||||
ret = c->ops->set_parent(c, parent);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_recalculate_rate(c);
|
||||
|
||||
propagate_rate(c);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int clk_set_parent(struct clk *c, struct clk *parent)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
unsigned long new_rate = clk_predict_rate_from_parent(c, parent);
|
||||
unsigned long new_rate;
|
||||
unsigned long old_rate;
|
||||
|
||||
clk_lock_save(c, flags);
|
||||
|
||||
if (clk_is_auto_dvfs(c)) {
|
||||
lock_dvfs();
|
||||
if (c->refcnt > 0 && (!c->parent || new_rate > c->rate))
|
||||
ret = tegra_dvfs_set_rate(c, new_rate);
|
||||
if (!ret)
|
||||
if (!c->ops || !c->ops->set_parent) {
|
||||
ret = -ENOSYS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
new_rate = clk_predict_rate_from_parent(c, parent);
|
||||
old_rate = clk_get_rate_locked(c);
|
||||
|
||||
if (clk_is_auto_dvfs(c) && c->refcnt > 0 &&
|
||||
(!c->parent || new_rate > old_rate)) {
|
||||
ret = tegra_dvfs_set_rate(c, new_rate);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
ret = clk_set_parent_locked(c, parent);
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
if (!ret)
|
||||
ret = c->ops->set_parent(c, parent);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (clk_is_auto_dvfs(c) && c->refcnt > 0)
|
||||
if (clk_is_auto_dvfs(c) && c->refcnt > 0 &&
|
||||
new_rate < old_rate)
|
||||
ret = tegra_dvfs_set_rate(c, new_rate);
|
||||
|
||||
out:
|
||||
if (clk_is_auto_dvfs(c))
|
||||
unlock_dvfs();
|
||||
|
||||
clk_unlock_restore(c, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_set_parent);
|
||||
@@ -300,86 +347,87 @@ struct clk *clk_get_parent(struct clk *c)
|
||||
}
|
||||
EXPORT_SYMBOL(clk_get_parent);
|
||||
|
||||
int clk_set_rate_locked(struct clk *c, unsigned long rate)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (rate == c->requested_rate)
|
||||
return 0;
|
||||
|
||||
if (rate > c->max_rate)
|
||||
rate = c->max_rate;
|
||||
|
||||
if (!c->ops || !c->ops->set_rate)
|
||||
return -ENOSYS;
|
||||
|
||||
c->requested_rate = rate;
|
||||
|
||||
ret = c->ops->set_rate(c, rate);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_recalculate_rate(c);
|
||||
|
||||
propagate_rate(c);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int clk_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
unsigned long old_rate;
|
||||
|
||||
if (clk_is_auto_dvfs(c)) {
|
||||
lock_dvfs();
|
||||
if (rate > c->rate && c->refcnt > 0)
|
||||
ret = tegra_dvfs_set_rate(c, rate);
|
||||
if (ret)
|
||||
goto out;
|
||||
clk_lock_save(c, flags);
|
||||
|
||||
if (!c->ops || !c->ops->set_rate) {
|
||||
ret = -ENOSYS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
ret = clk_set_rate_locked(c, rate);
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (clk_is_auto_dvfs(c) && c->refcnt > 0)
|
||||
ret = tegra_dvfs_set_rate(c, rate);
|
||||
|
||||
out:
|
||||
if (clk_is_auto_dvfs(c))
|
||||
unlock_dvfs();
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_set_rate);
|
||||
|
||||
unsigned long clk_get_rate(struct clk *c)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned long ret;
|
||||
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
|
||||
ret = c->rate;
|
||||
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_get_rate);
|
||||
|
||||
long clk_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
if (!c->ops || !c->ops->round_rate)
|
||||
return -ENOSYS;
|
||||
old_rate = clk_get_rate_locked(c);
|
||||
|
||||
if (rate > c->max_rate)
|
||||
rate = c->max_rate;
|
||||
|
||||
return c->ops->round_rate(c, rate);
|
||||
if (clk_is_auto_dvfs(c) && rate > old_rate && c->refcnt > 0) {
|
||||
ret = tegra_dvfs_set_rate(c, rate);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = c->ops->set_rate(c, rate);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (clk_is_auto_dvfs(c) && rate < old_rate && c->refcnt > 0)
|
||||
ret = tegra_dvfs_set_rate(c, rate);
|
||||
|
||||
out:
|
||||
clk_unlock_restore(c, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_set_rate);
|
||||
|
||||
/* Must be called with clocks lock and all indvidual clock locks held */
|
||||
unsigned long clk_get_rate_all_locked(struct clk *c)
|
||||
{
|
||||
u64 rate;
|
||||
int mul = 1;
|
||||
int div = 1;
|
||||
struct clk *p = c;
|
||||
|
||||
while (p) {
|
||||
c = p;
|
||||
if (c->mul != 0 && c->div != 0) {
|
||||
mul *= c->mul;
|
||||
div *= c->div;
|
||||
}
|
||||
p = c->parent;
|
||||
}
|
||||
|
||||
rate = c->rate;
|
||||
rate *= mul;
|
||||
do_div(rate, div);
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
long clk_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
unsigned long flags;
|
||||
long ret;
|
||||
|
||||
clk_lock_save(c, flags);
|
||||
|
||||
if (!c->ops || !c->ops->round_rate) {
|
||||
ret = -ENOSYS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rate > c->max_rate)
|
||||
rate = c->max_rate;
|
||||
|
||||
ret = c->ops->round_rate(c, rate);
|
||||
|
||||
out:
|
||||
clk_unlock_restore(c, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_round_rate);
|
||||
|
||||
@@ -459,32 +507,51 @@ EXPORT_SYMBOL(tegra_periph_reset_assert);
|
||||
void __init tegra_init_clock(void)
|
||||
{
|
||||
tegra2_init_clocks();
|
||||
tegra2_init_dvfs();
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate through all clocks, setting the dvfs rate to the current clock
|
||||
* rate on all auto dvfs clocks, and to the saved dvfs rate on all manual
|
||||
* dvfs clocks. Used to enable dvfs during late init, after the regulators
|
||||
* are available.
|
||||
*/
|
||||
void __init tegra_clk_set_dvfs_rates(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct clk *c;
|
||||
|
||||
mutex_lock(&clock_list_lock);
|
||||
|
||||
list_for_each_entry(c, &clocks, node) {
|
||||
clk_lock_save(c, flags);
|
||||
if (clk_is_auto_dvfs(c)) {
|
||||
if (c->refcnt > 0)
|
||||
tegra_dvfs_set_rate(c, c->rate);
|
||||
tegra_dvfs_set_rate(c, clk_get_rate_locked(c));
|
||||
else
|
||||
tegra_dvfs_set_rate(c, 0);
|
||||
} else if (clk_is_dvfs(c)) {
|
||||
tegra_dvfs_set_rate(c, c->dvfs_rate);
|
||||
}
|
||||
clk_unlock_restore(c, flags);
|
||||
}
|
||||
|
||||
mutex_unlock(&clock_list_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate through all clocks, disabling any for which the refcount is 0
|
||||
* but the clock init detected the bootloader left the clock on.
|
||||
*/
|
||||
int __init tegra_disable_boot_clocks(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct clk *c;
|
||||
|
||||
lock_dvfs();
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
mutex_lock(&clock_list_lock);
|
||||
|
||||
list_for_each_entry(c, &clocks, node) {
|
||||
clk_lock_save(c, flags);
|
||||
if (c->refcnt == 0 && c->state == ON &&
|
||||
c->ops && c->ops->disable) {
|
||||
pr_warning("Disabling clock %s left on by bootloader\n",
|
||||
@@ -492,15 +559,134 @@ int __init tegra_disable_boot_clocks(void)
|
||||
c->ops->disable(c);
|
||||
c->state = OFF;
|
||||
}
|
||||
clk_unlock_restore(c, flags);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
unlock_dvfs();
|
||||
mutex_unlock(&clock_list_lock);
|
||||
return 0;
|
||||
}
|
||||
late_initcall(tegra_disable_boot_clocks);
|
||||
|
||||
int __init tegra_late_init_clock(void)
|
||||
{
|
||||
tegra_disable_boot_clocks();
|
||||
tegra_clk_set_dvfs_rates();
|
||||
return 0;
|
||||
}
|
||||
late_initcall(tegra_late_init_clock);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
/*
|
||||
* Attempt to lock all the clocks that are marked cansleep
|
||||
* Must be called with irqs enabled
|
||||
*/
|
||||
static int __clk_lock_all_mutexes(void)
|
||||
{
|
||||
struct clk *c;
|
||||
|
||||
might_sleep();
|
||||
|
||||
list_for_each_entry(c, &clocks, node)
|
||||
if (clk_cansleep(c))
|
||||
if (!mutex_trylock(&c->mutex))
|
||||
goto unlock_mutexes;
|
||||
|
||||
return 0;
|
||||
|
||||
unlock_mutexes:
|
||||
list_for_each_entry_continue_reverse(c, &clocks, node)
|
||||
if (clk_cansleep(c))
|
||||
mutex_unlock(&c->mutex);
|
||||
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to lock all the clocks that are not marked cansleep
|
||||
* Must be called with irqs disabled
|
||||
*/
|
||||
static int __clk_lock_all_spinlocks(void)
|
||||
{
|
||||
struct clk *c;
|
||||
|
||||
list_for_each_entry(c, &clocks, node)
|
||||
if (!clk_cansleep(c))
|
||||
if (!spin_trylock(&c->spinlock))
|
||||
goto unlock_spinlocks;
|
||||
|
||||
return 0;
|
||||
|
||||
unlock_spinlocks:
|
||||
list_for_each_entry_continue_reverse(c, &clocks, node)
|
||||
if (!clk_cansleep(c))
|
||||
spin_unlock(&c->spinlock);
|
||||
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
static void __clk_unlock_all_mutexes(void)
|
||||
{
|
||||
struct clk *c;
|
||||
|
||||
list_for_each_entry_reverse(c, &clocks, node)
|
||||
if (clk_cansleep(c))
|
||||
mutex_unlock(&c->mutex);
|
||||
}
|
||||
|
||||
static void __clk_unlock_all_spinlocks(void)
|
||||
{
|
||||
struct clk *c;
|
||||
|
||||
list_for_each_entry_reverse(c, &clocks, node)
|
||||
if (!clk_cansleep(c))
|
||||
spin_unlock(&c->spinlock);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function retries until it can take all locks, and may take
|
||||
* an arbitrarily long time to complete.
|
||||
* Must be called with irqs enabled, returns with irqs disabled
|
||||
* Must be called with clock_list_lock held
|
||||
*/
|
||||
static void clk_lock_all(void)
|
||||
{
|
||||
int ret;
|
||||
retry:
|
||||
ret = __clk_lock_all_mutexes();
|
||||
if (ret)
|
||||
goto failed_mutexes;
|
||||
|
||||
local_irq_disable();
|
||||
|
||||
ret = __clk_lock_all_spinlocks();
|
||||
if (ret)
|
||||
goto failed_spinlocks;
|
||||
|
||||
/* All locks taken successfully, return */
|
||||
return;
|
||||
|
||||
failed_spinlocks:
|
||||
local_irq_enable();
|
||||
__clk_unlock_all_mutexes();
|
||||
failed_mutexes:
|
||||
msleep(1);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unlocks all clocks after a clk_lock_all
|
||||
* Must be called with irqs disabled, returns with irqs enabled
|
||||
* Must be called with clock_list_lock held
|
||||
*/
|
||||
static void clk_unlock_all(void)
|
||||
{
|
||||
__clk_unlock_all_spinlocks();
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
__clk_unlock_all_mutexes();
|
||||
}
|
||||
|
||||
static struct dentry *clk_debugfs_root;
|
||||
|
||||
static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level)
|
||||
@@ -515,7 +701,6 @@ static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level)
|
||||
static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level)
|
||||
{
|
||||
struct clk *child;
|
||||
struct clk *safe;
|
||||
struct dvfs *d;
|
||||
const char *state = "uninit";
|
||||
char div[8] = {0};
|
||||
@@ -547,12 +732,15 @@ static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level)
|
||||
c->rate > c->max_rate ? '!' : ' ',
|
||||
!c->set ? '*' : ' ',
|
||||
30 - level * 3, c->name,
|
||||
state, c->refcnt, div, c->rate);
|
||||
state, c->refcnt, div, clk_get_rate_all_locked(c));
|
||||
|
||||
list_for_each_entry(d, &c->dvfs, node)
|
||||
dvfs_show_one(s, d, level + 1);
|
||||
|
||||
list_for_each_entry_safe(child, safe, &c->children, sibling) {
|
||||
list_for_each_entry(child, &clocks, node) {
|
||||
if (child->parent != c)
|
||||
continue;
|
||||
|
||||
clock_tree_show_one(s, child, level + 1);
|
||||
}
|
||||
}
|
||||
@@ -560,14 +748,20 @@ static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level)
|
||||
static int clock_tree_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct clk *c;
|
||||
unsigned long flags;
|
||||
seq_printf(s, " clock state ref div rate\n");
|
||||
seq_printf(s, "--------------------------------------------------------------\n");
|
||||
spin_lock_irqsave(&clock_lock, flags);
|
||||
|
||||
mutex_lock(&clock_list_lock);
|
||||
|
||||
clk_lock_all();
|
||||
|
||||
list_for_each_entry(c, &clocks, node)
|
||||
if (c->parent == NULL)
|
||||
clock_tree_show_one(s, c, 0);
|
||||
spin_unlock_irqrestore(&clock_lock, flags);
|
||||
|
||||
clk_unlock_all();
|
||||
|
||||
mutex_unlock(&clock_list_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#define __MACH_TEGRA_CLOCK_H
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <asm/clkdev.h>
|
||||
|
||||
#define DIV_BUS (1 << 0)
|
||||
@@ -75,8 +77,6 @@ enum clk_state {
|
||||
struct clk {
|
||||
/* node for master clocks list */
|
||||
struct list_head node; /* node for list of all clocks */
|
||||
struct list_head children; /* list of children */
|
||||
struct list_head sibling; /* node for children */
|
||||
struct list_head dvfs; /* list of dvfs dependencies */
|
||||
struct clk_lookup lookup;
|
||||
|
||||
@@ -91,11 +91,11 @@ struct clk {
|
||||
unsigned long max_rate;
|
||||
bool is_dvfs;
|
||||
bool auto_dvfs;
|
||||
bool cansleep;
|
||||
u32 flags;
|
||||
const char *name;
|
||||
|
||||
u32 refcnt;
|
||||
unsigned long requested_rate;
|
||||
enum clk_state state;
|
||||
struct clk *parent;
|
||||
u32 div;
|
||||
@@ -137,8 +137,10 @@ struct clk {
|
||||
unsigned long rate;
|
||||
} shared_bus_user;
|
||||
} u;
|
||||
};
|
||||
|
||||
struct mutex mutex;
|
||||
spinlock_t spinlock;
|
||||
};
|
||||
|
||||
struct clk_duplicate {
|
||||
const char *name;
|
||||
@@ -158,12 +160,10 @@ void tegra2_periph_reset_assert(struct clk *c);
|
||||
void clk_init(struct clk *clk);
|
||||
struct clk *tegra_get_clock_by_name(const char *name);
|
||||
unsigned long clk_measure_input_freq(void);
|
||||
void clk_disable_locked(struct clk *c);
|
||||
int clk_enable_locked(struct clk *c);
|
||||
int clk_set_parent_locked(struct clk *c, struct clk *parent);
|
||||
int clk_set_rate_locked(struct clk *c, unsigned long rate);
|
||||
int clk_reparent(struct clk *c, struct clk *parent);
|
||||
void tegra_clk_init_from_table(struct tegra_clk_init_table *table);
|
||||
void tegra_clk_set_dvfs_rates(void);
|
||||
void clk_set_cansleep(struct clk *c);
|
||||
unsigned long clk_get_rate_locked(struct clk *c);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -26,5 +26,6 @@ void tegra_periph_reset_deassert(struct clk *c);
|
||||
void tegra_periph_reset_assert(struct clk *c);
|
||||
|
||||
int tegra_dvfs_set_rate(struct clk *c, unsigned long rate);
|
||||
unsigned long clk_get_rate_all_locked(struct clk *c);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <asm/clkdev.h>
|
||||
|
||||
@@ -337,12 +337,12 @@ static int tegra2_super_clk_set_parent(struct clk *c, struct clk *p)
|
||||
val |= sel->value << shift;
|
||||
|
||||
if (c->refcnt)
|
||||
clk_enable_locked(p);
|
||||
clk_enable(p);
|
||||
|
||||
clk_writel(val, c->reg);
|
||||
|
||||
if (c->refcnt && c->parent)
|
||||
clk_disable_locked(c->parent);
|
||||
clk_disable(c->parent);
|
||||
|
||||
clk_reparent(c, p);
|
||||
return 0;
|
||||
@@ -389,31 +389,31 @@ static int tegra2_cpu_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
* Take an extra reference to the main pll so it doesn't turn
|
||||
* off when we move the cpu off of it
|
||||
*/
|
||||
clk_enable_locked(c->u.cpu.main);
|
||||
clk_enable(c->u.cpu.main);
|
||||
|
||||
ret = clk_set_parent_locked(c->parent, c->u.cpu.backup);
|
||||
ret = clk_set_parent(c->parent, c->u.cpu.backup);
|
||||
if (ret) {
|
||||
pr_err("Failed to switch cpu to clock %s\n", c->u.cpu.backup->name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rate == c->u.cpu.backup->rate)
|
||||
if (rate == clk_get_rate(c->u.cpu.backup))
|
||||
goto out;
|
||||
|
||||
ret = clk_set_rate_locked(c->u.cpu.main, rate);
|
||||
ret = clk_set_rate(c->u.cpu.main, rate);
|
||||
if (ret) {
|
||||
pr_err("Failed to change cpu pll to %lu\n", rate);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = clk_set_parent_locked(c->parent, c->u.cpu.main);
|
||||
ret = clk_set_parent(c->parent, c->u.cpu.main);
|
||||
if (ret) {
|
||||
pr_err("Failed to switch cpu to clock %s\n", c->u.cpu.main->name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
clk_disable_locked(c->u.cpu.main);
|
||||
clk_disable(c->u.cpu.main);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ static void tegra2_bus_clk_disable(struct clk *c)
|
||||
static int tegra2_bus_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
u32 val = clk_readl(c->reg);
|
||||
unsigned long parent_rate = c->parent->rate;
|
||||
unsigned long parent_rate = clk_get_rate(c->parent);
|
||||
int i;
|
||||
for (i = 1; i <= 4; i++) {
|
||||
if (rate == parent_rate / i) {
|
||||
@@ -539,14 +539,15 @@ static void tegra2_blink_clk_disable(struct clk *c)
|
||||
|
||||
static int tegra2_blink_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
if (rate >= c->parent->rate) {
|
||||
unsigned long parent_rate = clk_get_rate(c->parent);
|
||||
if (rate >= parent_rate) {
|
||||
c->div = 1;
|
||||
pmc_writel(0, c->reg);
|
||||
} else {
|
||||
unsigned int on_off;
|
||||
u32 val;
|
||||
|
||||
on_off = DIV_ROUND_UP(c->parent->rate / 8, rate);
|
||||
on_off = DIV_ROUND_UP(parent_rate / 8, rate);
|
||||
c->div = on_off * 8;
|
||||
|
||||
val = (on_off & PMC_BLINK_TIMER_DATA_ON_MASK) <<
|
||||
@@ -632,7 +633,7 @@ static int tegra2_pll_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
|
||||
pr_debug("%s: %s %lu\n", __func__, c->name, rate);
|
||||
|
||||
input_rate = c->parent->rate;
|
||||
input_rate = clk_get_rate(c->parent);
|
||||
for (sel = c->u.pll.freq_table; sel->input_rate != 0; sel++) {
|
||||
if (sel->input_rate == input_rate && sel->output_rate == rate) {
|
||||
c->mul = sel->n;
|
||||
@@ -772,9 +773,11 @@ static int tegra2_pll_div_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
u32 val;
|
||||
u32 new_val;
|
||||
int divider_u71;
|
||||
unsigned long parent_rate = clk_get_rate(c->parent);
|
||||
|
||||
pr_debug("%s: %s %lu\n", __func__, c->name, rate);
|
||||
if (c->flags & DIV_U71) {
|
||||
divider_u71 = clk_div71_get_divider(c->parent->rate, rate);
|
||||
divider_u71 = clk_div71_get_divider(parent_rate, rate);
|
||||
if (divider_u71 >= 0) {
|
||||
val = clk_readl(c->reg);
|
||||
new_val = val >> c->reg_shift;
|
||||
@@ -792,7 +795,7 @@ static int tegra2_pll_div_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
return 0;
|
||||
}
|
||||
} else if (c->flags & DIV_2) {
|
||||
if (c->parent->rate == rate * 2)
|
||||
if (parent_rate == rate * 2)
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
@@ -801,15 +804,16 @@ static int tegra2_pll_div_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
static long tegra2_pll_div_clk_round_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
int divider;
|
||||
unsigned long parent_rate = clk_get_rate(c->parent);
|
||||
pr_debug("%s: %s %lu\n", __func__, c->name, rate);
|
||||
|
||||
if (c->flags & DIV_U71) {
|
||||
divider = clk_div71_get_divider(c->parent->rate, rate);
|
||||
divider = clk_div71_get_divider(parent_rate, rate);
|
||||
if (divider < 0)
|
||||
return divider;
|
||||
return c->parent->rate * 2 / (divider + 2);
|
||||
return parent_rate * 2 / (divider + 2);
|
||||
} else if (c->flags & DIV_2) {
|
||||
return c->parent->rate / 2;
|
||||
return parent_rate / 2;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -923,12 +927,12 @@ static int tegra2_periph_clk_set_parent(struct clk *c, struct clk *p)
|
||||
val |= (sel->value) << PERIPH_CLK_SOURCE_SHIFT;
|
||||
|
||||
if (c->refcnt)
|
||||
clk_enable_locked(p);
|
||||
clk_enable(p);
|
||||
|
||||
clk_writel(val, c->reg);
|
||||
|
||||
if (c->refcnt && c->parent)
|
||||
clk_disable_locked(c->parent);
|
||||
clk_disable(c->parent);
|
||||
|
||||
clk_reparent(c, p);
|
||||
return 0;
|
||||
@@ -942,9 +946,10 @@ static int tegra2_periph_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
u32 val;
|
||||
int divider;
|
||||
pr_debug("%s: %lu\n", __func__, rate);
|
||||
unsigned long parent_rate = clk_get_rate(c->parent);
|
||||
|
||||
if (c->flags & DIV_U71) {
|
||||
divider = clk_div71_get_divider(c->parent->rate, rate);
|
||||
divider = clk_div71_get_divider(parent_rate, rate);
|
||||
if (divider >= 0) {
|
||||
val = clk_readl(c->reg);
|
||||
val &= ~PERIPH_CLK_SOURCE_DIVU71_MASK;
|
||||
@@ -955,7 +960,7 @@ static int tegra2_periph_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
return 0;
|
||||
}
|
||||
} else if (c->flags & DIV_U16) {
|
||||
divider = clk_div16_get_divider(c->parent->rate, rate);
|
||||
divider = clk_div16_get_divider(parent_rate, rate);
|
||||
if (divider >= 0) {
|
||||
val = clk_readl(c->reg);
|
||||
val &= ~PERIPH_CLK_SOURCE_DIVU16_MASK;
|
||||
@@ -965,7 +970,7 @@ static int tegra2_periph_clk_set_rate(struct clk *c, unsigned long rate)
|
||||
c->mul = 1;
|
||||
return 0;
|
||||
}
|
||||
} else if (c->parent->rate <= rate) {
|
||||
} else if (parent_rate <= rate) {
|
||||
c->div = 1;
|
||||
c->mul = 1;
|
||||
return 0;
|
||||
@@ -977,19 +982,20 @@ static long tegra2_periph_clk_round_rate(struct clk *c,
|
||||
unsigned long rate)
|
||||
{
|
||||
int divider;
|
||||
unsigned long parent_rate = clk_get_rate(c->parent);
|
||||
pr_debug("%s: %s %lu\n", __func__, c->name, rate);
|
||||
|
||||
if (c->flags & DIV_U71) {
|
||||
divider = clk_div71_get_divider(c->parent->rate, rate);
|
||||
divider = clk_div71_get_divider(parent_rate, rate);
|
||||
if (divider < 0)
|
||||
return divider;
|
||||
|
||||
return c->parent->rate * 2 / (divider + 2);
|
||||
return parent_rate * 2 / (divider + 2);
|
||||
} else if (c->flags & DIV_U16) {
|
||||
divider = clk_div16_get_divider(c->parent->rate, rate);
|
||||
divider = clk_div16_get_divider(parent_rate, rate);
|
||||
if (divider < 0)
|
||||
return divider;
|
||||
return c->parent->rate / (divider + 1);
|
||||
return parent_rate / (divider + 1);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -1017,7 +1023,7 @@ static void tegra2_clk_double_init(struct clk *c)
|
||||
|
||||
static int tegra2_clk_double_set_rate(struct clk *c, unsigned long rate)
|
||||
{
|
||||
if (rate != 2 * c->parent->rate)
|
||||
if (rate != 2 * clk_get_rate(c->parent))
|
||||
return -EINVAL;
|
||||
c->mul = 2;
|
||||
c->div = 1;
|
||||
@@ -1068,12 +1074,12 @@ static int tegra2_audio_sync_clk_set_parent(struct clk *c, struct clk *p)
|
||||
val |= sel->value;
|
||||
|
||||
if (c->refcnt)
|
||||
clk_enable_locked(p);
|
||||
clk_enable(p);
|
||||
|
||||
clk_writel(val, c->reg);
|
||||
|
||||
if (c->refcnt && c->parent)
|
||||
clk_disable_locked(c->parent);
|
||||
clk_disable(c->parent);
|
||||
|
||||
clk_reparent(c, p);
|
||||
return 0;
|
||||
@@ -1134,12 +1140,13 @@ static void tegra_clk_shared_bus_update(struct clk *bus)
|
||||
struct clk *c;
|
||||
unsigned long rate = bus->u.shared_bus.min_rate;
|
||||
|
||||
list_for_each_entry(c, &bus->u.shared_bus.list, u.shared_bus_user.node)
|
||||
list_for_each_entry(c, &bus->u.shared_bus.list,
|
||||
u.shared_bus_user.node) {
|
||||
if (c->u.shared_bus_user.enabled)
|
||||
rate = max(c->u.shared_bus_user.rate, rate);
|
||||
}
|
||||
|
||||
if (rate != bus->rate)
|
||||
clk_set_rate_locked(bus, rate);
|
||||
clk_set_rate(bus, rate);
|
||||
};
|
||||
|
||||
static void tegra_clk_shared_bus_init(struct clk *c)
|
||||
|
||||
Reference in New Issue
Block a user