* Author:
* Colin Cross <ccross@google.com>
*
+ * Copyright (C) 2010-2011 NVIDIA Corporation
+ *
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
#include <linux/slab.h>
#include <linux/clk/tegra.h>
#include <linux/uaccess.h>
+#include <trace/events/power.h>
#include "board.h"
#include "clock.h"
/* Global data of Tegra CPU CAR ops */
struct tegra_cpu_car_ops *tegra_cpu_car_ops;
+#define DISABLE_BOOT_CLOCKS 1
+
/*
* Locking:
*
* 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 and clk_set_rate_locked. Any
- * clock operation can call any other clock operation on any of it's possible
- * parents.
+ * on itself, except for clk_xxx_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
+ * dvfs requirement, and propagated to all possible children of sleeping clock.
*
* An additional mutex, clock_list_lock, is used to protect the list of all
* clocks.
return ret;
}
+static void clk_stats_update(struct clk *c)
+{
+ u64 cur_jiffies = get_jiffies_64();
+
+ if (c->refcnt) {
+ c->stats.time_on = c->stats.time_on +
+ (jiffies64_to_cputime64(cur_jiffies) -
+ (c->stats.last_update));
+ }
+
+ c->stats.last_update = cur_jiffies;
+}
+
/* Must be called with clk_lock(c) held */
static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p)
{
rate = clk_get_rate(p);
- if (c->ops && c->ops->recalculate_rate)
- c->ops->recalculate_rate(c);
-
if (c->mul != 0 && c->div != 0) {
rate *= c->mul;
rate += c->div - 1; /* round up */
return rate;
}
-static unsigned long clk_get_max_rate(struct clk *c)
+unsigned long clk_get_max_rate(struct clk *c)
{
- if (c->ops && c->ops->get_max_rate)
- return c->ops->get_max_rate(c);
- else
return c->max_rate;
}
+unsigned long clk_get_min_rate(struct clk *c)
+{
+ return c->min_rate;
+}
+
/* Must be called with clk_lock(c) held */
unsigned long clk_get_rate_locked(struct clk *c)
{
static void __clk_set_cansleep(struct clk *c)
{
struct clk *child;
+ int i;
BUG_ON(mutex_is_locked(&c->mutex));
BUG_ON(spin_is_locked(&c->spinlock));
+ /* Make sure that all possible descendants of sleeping clock are
+ marked as sleeping (to eliminate "sleeping parent - non-sleeping
+ child" relationship */
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);
+ bool possible_parent = (child->parent == c);
+
+ if (!possible_parent && child->inputs) {
+ for (i = 0; child->inputs[i].input; i++) {
+ if (child->inputs[i].input == c) {
+ possible_parent = true;
+ break;
+ }
+ }
+ }
- __clk_set_cansleep(child);
+ if (possible_parent)
+ __clk_set_cansleep(child);
}
c->cansleep = true;
else
c->state = ON;
}
+ c->stats.last_update = get_jiffies_64();
mutex_lock(&clock_list_lock);
list_add(&c->node, &clocks);
mutex_unlock(&clock_list_lock);
}
-int clk_enable(struct clk *c)
+static int clk_enable_locked(struct clk *c)
{
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;
+ return ret;
}
if (c->refcnt == 0) {
if (c->parent) {
ret = clk_enable(c->parent);
if (ret)
- goto out;
+ return ret;
}
if (c->ops && c->ops->enable) {
ret = c->ops->enable(c);
+ trace_clock_enable(c->name, 1, 0);
if (ret) {
if (c->parent)
clk_disable(c->parent);
- goto out;
+ return ret;
}
c->state = ON;
c->set = true;
}
+ clk_stats_update(c);
}
c->refcnt++;
-out:
- clk_unlock_restore(c, &flags);
+
return ret;
}
-EXPORT_SYMBOL(clk_enable);
-void clk_disable(struct clk *c)
+
+int clk_enable(struct clk *c)
{
+ int ret = 0;
unsigned long flags;
clk_lock_save(c, &flags);
+ ret = clk_enable_locked(c);
+ clk_unlock_restore(c, &flags);
+ return ret;
+}
+EXPORT_SYMBOL(clk_enable);
+static void clk_disable_locked(struct clk *c)
+{
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) {
- if (c->ops && c->ops->disable)
+ if (c->ops && c->ops->disable) {
+ trace_clock_disable(c->name, 0, 0);
c->ops->disable(c);
-
+ }
if (c->parent)
clk_disable(c->parent);
c->state = OFF;
+ clk_stats_update(c);
}
c->refcnt--;
if (clk_is_auto_dvfs(c) && c->refcnt == 0)
tegra_dvfs_set_rate(c, 0);
+}
+
+void clk_disable(struct clk *c)
+{
+ unsigned long flags;
+ clk_lock_save(c, &flags);
+ clk_disable_locked(c);
clk_unlock_restore(c, &flags);
}
EXPORT_SYMBOL(clk_disable);
+static int clk_rate_change_notify(struct clk *c, unsigned long rate)
+{
+ if (!c->rate_change_nh)
+ return -ENOSYS;
+ return raw_notifier_call_chain(c->rate_change_nh, rate, NULL);
+}
+
int clk_set_parent(struct clk *c, struct clk *parent)
{
int ret = 0;
unsigned long flags;
unsigned long new_rate;
unsigned long old_rate;
+ bool disable = false;
clk_lock_save(c, &flags);
#endif
}
+ /* The new clock control register setting does not take effect if
+ * clock is disabled. Later, when the clock is enabled it would run
+ * for several cycles on the old parent, which may hang h/w if the
+ * parent is already disabled. To guarantee h/w switch to the new
+ * setting enable clock while setting parent.
+ */
+ if ((c->refcnt == 0) && (c->flags & MUX)) {
+ pr_debug("Setting parent of clock %s with refcnt 0\n", c->name);
+ disable = true;
+ ret = clk_enable_locked(c);
+ if (ret)
+ goto out;
+ }
+
if (clk_is_auto_dvfs(c) && c->refcnt > 0 &&
(!c->parent || new_rate > old_rate)) {
ret = tegra_dvfs_set_rate(c, new_rate);
new_rate < old_rate)
ret = tegra_dvfs_set_rate(c, new_rate);
+ if (new_rate != old_rate)
+ clk_rate_change_notify(c, new_rate);
+
out:
+ if (disable)
+ clk_disable_locked(c);
clk_unlock_restore(c, &flags);
return ret;
}
int ret = 0;
unsigned long old_rate, max_rate;
long new_rate;
+ bool disable = false;
old_rate = clk_get_rate_locked(c);
rate = new_rate;
}
+ /* The new clock control register setting does not take effect if
+ * clock is disabled. Later, when the clock is enabled it would run
+ * for several cycles on the old rate, which may over-clock module
+ * at given voltage. To guarantee h/w switch to the new setting
+ * enable clock while setting rate.
+ */
+ if ((c->refcnt == 0) && (c->flags & (DIV_U71 | DIV_U16)) &&
+ clk_is_auto_dvfs(c)) {
+ pr_debug("Setting rate of clock %s with refcnt 0\n", c->name);
+ disable = true;
+ ret = clk_enable_locked(c);
+ if (ret)
+ goto out;
+ }
+
if (clk_is_auto_dvfs(c) && rate > old_rate && c->refcnt > 0) {
ret = tegra_dvfs_set_rate(c, rate);
if (ret)
- return ret;
+ goto out;
}
+ trace_clock_set_rate(c->name, rate, 0);
ret = c->ops->set_rate(c, rate);
if (ret)
- return ret;
+ goto out;
if (clk_is_auto_dvfs(c) && rate < old_rate && c->refcnt > 0)
ret = tegra_dvfs_set_rate(c, rate);
+ if (rate != old_rate)
+ clk_rate_change_notify(c, rate);
+
+out:
+ if (disable)
+ clk_disable_locked(c);
return ret;
}
while (p) {
c = p;
- if (c->ops && c->ops->recalculate_rate)
- c->ops->recalculate_rate(c);
if (c->mul != 0 && c->div != 0) {
mul *= c->mul;
div *= c->div;
}
EXPORT_SYMBOL(clk_round_rate);
+static int tegra_clk_clip_rate_for_parent(struct clk *c, struct clk *p)
+{
+ unsigned long flags, max_rate, old_rate, new_rate;
+
+ clk_lock_save(c, &flags);
+
+ max_rate = clk_get_max_rate(c);
+ new_rate = clk_predict_rate_from_parent(c, p);
+ old_rate = clk_get_rate_locked(c);
+
+ clk_unlock_restore(c, &flags);
+
+ if (new_rate > max_rate) {
+ u64 rate = max_rate;
+ rate *= old_rate;
+ do_div(rate, new_rate);
+
+ return clk_set_rate(c, (unsigned long)rate);
+ }
+ return 0;
+}
+
static int tegra_clk_init_one_from_table(struct tegra_clk_init_table *table)
{
struct clk *c;
}
if (c->parent != p) {
+ ret = tegra_clk_clip_rate_for_parent(c, p);
+ if (ret) {
+ pr_warning("Unable to clip rate for parent %s"
+ " of clock %s: %d\n",
+ table->parent, table->name, ret);
+ return -EINVAL;
+ }
+
ret = clk_set_parent(c, p);
if (ret) {
pr_warning("Unable to set parent %s of clock %s: %d\n",
return ret;
}
+int tegra_is_clk_enabled(struct clk *c)
+{
+ return c->refcnt;
+}
+EXPORT_SYMBOL(tegra_is_clk_enabled);
+
+/* dvfs initialization may lower default maximum rate */
+void __init tegra_init_max_rate(struct clk *c, unsigned long max_rate)
+{
+ struct clk *shared_bus_user;
+
+ if (c->max_rate <= max_rate)
+ return;
+
+ pr_warning("Lowering %s maximum rate from %lu to %lu\n",
+ c->name, c->max_rate, max_rate);
+
+ c->max_rate = max_rate;
+ list_for_each_entry(shared_bus_user,
+ &c->shared_bus_list, u.shared_bus_user.node) {
+ shared_bus_user->u.shared_bus_user.rate = max_rate;
+ shared_bus_user->max_rate = max_rate;
+ }
+}
+
+void __init tegra_common_init_clock(void)
+{
+ int ret;
+ struct clk *cpu;
+ struct clk *twd;
+
+ /* The twd clock is a detached child of the CPU complex clock.
+ Force an update of the twd clock after DVFS as updated the
+ CPU clock rate. */
+ cpu = tegra_get_clock_by_name("cpu");
+ twd = tegra_get_clock_by_name("twd");
+ ret = clk_set_rate(twd, clk_get_rate(cpu));
+ if (ret)
+ pr_err("Failed to set twd clock rate: %d\n", ret);
+ else
+ pr_debug("TWD clock rate: %ld\n", clk_get_rate(twd));
+}
+
static bool tegra_keep_boot_clocks = false;
static int __init tegra_keep_boot_clocks_setup(char *__unused)
{
}
__setup("tegra_keep_boot_clocks", tegra_keep_boot_clocks_setup);
+/*
+ * Bootloader may not match kernel restrictions on CPU clock sources.
+ * Make sure CPU clock is sourced from either main or backup parent.
+ */
+static int tegra_sync_cpu_clock(void)
+{
+ int ret;
+ unsigned long rate;
+ struct clk *c = tegra_get_clock_by_name("cpu");
+
+ BUG_ON(!c);
+ rate = clk_get_rate(c);
+ ret = clk_set_rate(c, rate);
+ if (ret)
+ pr_err("%s: Failed to sync CPU at rate %lu\n", __func__, rate);
+ else
+ pr_info("CPU rate: %lu MHz\n", clk_get_rate(c) / 1000000);
+ return ret;
+}
+late_initcall(tegra_sync_cpu_clock);
+
/*
* Iterate through all clocks, disabling any for which the refcount is 0
* but the clock init detected the bootloader left the clock on.
*/
static int __init tegra_init_disable_boot_clocks(void)
{
+#if DISABLE_BOOT_CLOCKS
unsigned long flags;
struct clk *c;
}
mutex_unlock(&clock_list_lock);
+#endif
return 0;
}
late_initcall(tegra_init_disable_boot_clocks);
+int tegra_register_clk_rate_notifier(struct clk *c, struct notifier_block *nb)
+{
+ int ret;
+ unsigned long flags;
+
+ if (!c->rate_change_nh)
+ return -ENOSYS;
+
+ clk_lock_save(c, &flags);
+ ret = raw_notifier_chain_register(c->rate_change_nh, nb);
+ clk_unlock_restore(c, &flags);
+ return ret;
+}
+
+void tegra_unregister_clk_rate_notifier(
+ struct clk *c, struct notifier_block *nb)
+{
+ unsigned long flags;
+
+ if (!c->rate_change_nh)
+ return;
+
+ clk_lock_save(c, &flags);
+ raw_notifier_chain_unregister(c->rate_change_nh, nb);
+ clk_unlock_restore(c, &flags);
+}
+
#ifdef CONFIG_DEBUG_FS
/*
}
}
- seq_printf(s, "%*s%c%c%-*s %-6s %-3d %-8s %-10lu\n",
+ seq_printf(s, "%*s%c%c%-*s%c %-6s %-3d %-8s %-10lu",
level * 3 + 1, "",
rate > max_rate ? '!' : ' ',
!c->set ? '*' : ' ',
30 - level * 3, c->name,
+ c->cansleep ? '$' : ' ',
state, c->refcnt, div, rate);
+ if (c->parent && !list_empty(&c->parent->shared_bus_list))
+ seq_printf(s, " (%lu)", c->u.shared_bus_user.rate);
+ seq_printf(s, "\n");
if (c->dvfs)
dvfs_show_one(s, c->dvfs, level + 1);
static int clock_tree_show(struct seq_file *s, void *data)
{
struct clk *c;
- seq_printf(s, " clock state ref div rate\n");
- seq_printf(s, "--------------------------------------------------------------\n");
+ seq_printf(s, " clock state ref div rate (shared rate)\n");
+ seq_printf(s, "------------------------------------------------------------------------------\n");
mutex_lock(&clock_list_lock);
.release = single_release,
};
+static void syncevent_one(struct clk *c)
+{
+ struct clk *child;
+
+ if (c->state == ON)
+ trace_clock_enable(c->name, 1, smp_processor_id());
+ else
+ trace_clock_disable(c->name, 0, smp_processor_id());
+
+ trace_clock_set_rate(c->name, clk_get_rate_all_locked(c),
+ smp_processor_id());
+
+ list_for_each_entry(child, &clocks, node) {
+ if (child->parent != c)
+ continue;
+
+ syncevent_one(child);
+ }
+}
+
+static int syncevent_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct clk *c;
+ char buffer[40];
+ int buf_size;
+
+ memset(buffer, 0, sizeof(buffer));
+ buf_size = min(count, (sizeof(buffer)-1));
+
+ if (copy_from_user(buffer, user_buf, buf_size))
+ return -EFAULT;
+
+ if (!strnicmp("all", buffer, 3)) {
+ mutex_lock(&clock_list_lock);
+
+ clk_lock_all();
+
+ list_for_each_entry(c, &clocks, node) {
+ if (c->parent == NULL)
+ syncevent_one(c);
+ }
+
+ clk_unlock_all();
+
+ mutex_unlock(&clock_list_lock);
+ }
+
+ return count;
+}
+
+static const struct file_operations syncevent_fops = {
+ .write = syncevent_write,
+};
+
static int possible_parents_show(struct seq_file *s, void *data)
{
struct clk *c = s->private;
#ifdef CONFIG_TEGRA_CLOCK_DEBUG_WRITE
-static const mode_t parent_rate_mode = S_IRUGO | S_IWUGO;
+static const mode_t parent_rate_mode = S_IRUGO | S_IWUSR;
static ssize_t parent_write(struct file *file,
const char __user *userbuf, size_t count, loff_t *ppos)
DEFINE_SIMPLE_ATTRIBUTE(state_fops, state_get, NULL, "%llu\n");
#endif
+static int time_on_get(void *data, u64 *val)
+{
+ unsigned long flags;
+ struct clk *c = (struct clk *)data;
+
+ clk_lock_save(c, &flags);
+ clk_stats_update(c);
+ *val = cputime64_to_clock_t(c->stats.time_on);
+ clk_unlock_restore(c, &flags);
+
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(time_on_fops, time_on_get, NULL, "%llu\n");
+
static int clk_debugfs_register_one(struct clk *c)
{
struct dentry *d;
if (!d)
goto err_out;
+ d = debugfs_create_file(
+ "time_on", S_IRUGO, c->dent, c, &time_on_fops);
+ if (!d)
+ goto err_out;
+
if (c->inputs) {
d = debugfs_create_file("possible_parents", S_IRUGO, c->dent,
c, &possible_parents_fops);
if (!d)
goto err_out;
+ d = debugfs_create_file("syncevents", S_IWUGO, clk_debugfs_root, NULL,
+ &syncevent_fops);
+
if (dvfs_debugfs_init(clk_debugfs_root))
goto err_out;