obj-y += io.o
obj-y += irq.o legacy_irq.o
obj-y += clock.o
+obj-y += dvfs.o
obj-y += timer.o
obj-y += gpio.o
obj-y += pinmux.o
#include <linux/slab.h>
#include <linux/seq_file.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.
+ *
+ * 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.
+ *
+ * When taking dvfs_lock and clock_lock, dvfs_lock must be taken first.
+ */
static DEFINE_SPINLOCK(clock_lock);
+
+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;
+};
+
struct clk *tegra_get_clock_by_name(const char *name)
{
struct clk *c;
return ret;
}
-static void clk_recalculate_rate(struct clk *c)
+static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p)
{
u64 rate;
- if (!c->parent)
- return;
-
- rate = c->parent->rate;
+ rate = p->rate;
if (c->mul != 0 && c->div != 0) {
rate = rate * c->mul;
do_div(rate, c->div);
}
+ return rate;
+}
+
+static void clk_recalculate_rate(struct clk *c)
+{
+ unsigned long rate;
+
+ if (!c->parent)
+ return;
+
+ rate = clk_predict_rate_from_parent(c, c->parent);
+
if (rate > c->max_rate)
- pr_warn("clocks: Set clock %s to rate %llu, max is %lu\n",
+ pr_warn("clocks: Set clock %s to rate %lu, max is %lu\n",
c->name, rate, c->max_rate);
c->rate = rate;
INIT_LIST_HEAD(&c->children);
INIT_LIST_HEAD(&c->sibling);
+ INIT_LIST_HEAD(&c->dvfs);
if (c->ops && c->ops->init)
c->ops->init(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();
+
return ret;
}
EXPORT_SYMBOL(clk_enable);
{
unsigned long flags;
+ 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();
+ }
}
EXPORT_SYMBOL(clk_disable);
int clk_set_parent(struct clk *c, struct clk *parent)
{
- int ret;
+ int ret = 0;
unsigned long flags;
+ unsigned long new_rate = clk_predict_rate_from_parent(c, parent);
+
+
+ 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)
+ goto out;
+ }
+
spin_lock_irqsave(&clock_lock, flags);
ret = clk_set_parent_locked(c, parent);
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, new_rate);
+
+out:
+ if (clk_is_auto_dvfs(c))
+ unlock_dvfs();
+
return ret;
}
EXPORT_SYMBOL(clk_set_parent);
{
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)
int ret = 0;
unsigned long flags;
+ 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;
+ }
+
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);
tegra2_init_clocks();
}
+void __init tegra_clk_set_dvfs_rates(void)
+{
+ struct clk *c;
+ list_for_each_entry(c, &clocks, node) {
+ if (clk_is_auto_dvfs(c)) {
+ if (c->refcnt > 0)
+ tegra_dvfs_set_rate(c, c->rate);
+ else
+ tegra_dvfs_set_rate(c, 0);
+ } else if (clk_is_dvfs(c)) {
+ tegra_dvfs_set_rate(c, c->dvfs_rate);
+ }
+ }
+}
+
int __init tegra_disable_boot_clocks(void)
{
unsigned long flags;
-
struct clk *c;
+ lock_dvfs();
spin_lock_irqsave(&clock_lock, flags);
list_for_each_entry(c, &clocks, node) {
}
spin_unlock_irqrestore(&clock_lock, flags);
-
+ unlock_dvfs();
return 0;
}
late_initcall(tegra_disable_boot_clocks);
#ifdef CONFIG_DEBUG_FS
static struct dentry *clk_debugfs_root;
+static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level)
+{
+ seq_printf(s, "%*s %-*s%21s%d mV\n",
+ level * 3 + 1, "",
+ 30 - level * 3, d->reg_id,
+ "",
+ d->cur_millivolts);
+}
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};
!c->set ? '*' : ' ',
30 - level * 3, c->name,
state, c->refcnt, div, c->rate);
+
+ 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) {
clock_tree_show_one(s, child, level + 1);
}
if (!d)
goto err_out;
+ if (dvfs_debugfs_init(clk_debugfs_root))
+ goto err_out;
+
list_for_each_entry(c, &clocks, node) {
err = clk_debugfs_register(c);
if (err)
struct clk_ops *ops;
struct clk *parent;
struct clk_lookup lookup;
+ unsigned long dvfs_rate;
unsigned long rate;
+ unsigned long requested_rate;
unsigned long max_rate;
+ bool is_dvfs;
+ bool auto_dvfs;
u32 flags;
u32 refcnt;
const char *name;
/* Virtual cpu clock */
struct clk *main;
struct clk *backup;
+
+ struct list_head dvfs;
};
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);
#endif
--- /dev/null
+/*
+ *
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * Author:
+ * Colin Cross <ccross@google.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/list_sort.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/regulator/consumer.h>
+#include <asm/clkdev.h>
+#include <mach/clk.h>
+
+#include "board.h"
+#include "clock.h"
+#include "dvfs.h"
+
+struct dvfs_reg {
+ struct list_head node; /* node in dvfs_reg_list */
+ struct list_head dvfs; /* list head of attached dvfs clocks */
+ const char *reg_id;
+ struct regulator *reg;
+ int max_millivolts;
+ int millivolts;
+};
+
+static LIST_HEAD(dvfs_list);
+static LIST_HEAD(dvfs_debug_list);
+static LIST_HEAD(dvfs_reg_list);
+
+static DEFINE_MUTEX(dvfs_lock);
+
+void lock_dvfs(void)
+{
+ mutex_lock(&dvfs_lock);
+}
+
+void unlock_dvfs(void)
+{
+ mutex_unlock(&dvfs_lock);
+}
+
+static int dvfs_reg_set_voltage(struct dvfs_reg *dvfs_reg)
+{
+ int millivolts = 0;
+ struct dvfs *d;
+
+ list_for_each_entry(d, &dvfs_reg->dvfs, reg_node)
+ millivolts = max(d->cur_millivolts, millivolts);
+
+ if (millivolts == dvfs_reg->millivolts)
+ return 0;
+
+ dvfs_reg->millivolts = millivolts;
+
+ return regulator_set_voltage(dvfs_reg->reg,
+ millivolts * 1000, dvfs_reg->max_millivolts * 1000);
+}
+
+static int dvfs_reg_get_voltage(struct dvfs_reg *dvfs_reg)
+{
+ int ret = regulator_get_voltage(dvfs_reg->reg);
+
+ if (ret > 0)
+ return ret / 1000;
+
+ return ret;
+}
+
+static struct dvfs_reg *get_dvfs_reg(struct dvfs *d)
+{
+ struct dvfs_reg *dvfs_reg;
+ struct regulator *reg;
+
+ list_for_each_entry(dvfs_reg, &dvfs_reg_list, node)
+ if (!strcmp(d->reg_id, dvfs_reg->reg_id))
+ return dvfs_reg;
+
+ reg = regulator_get(NULL, d->reg_id);
+ if (IS_ERR(reg))
+ return NULL;
+
+ dvfs_reg = kzalloc(sizeof(struct dvfs_reg), GFP_KERNEL);
+ if (!dvfs_reg) {
+ pr_err("%s: Failed to allocate dvfs_reg\n", __func__);
+ regulator_put(reg);
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&dvfs_reg->dvfs);
+ dvfs_reg->reg = reg;
+ dvfs_reg->reg_id = kstrdup(d->reg_id, GFP_KERNEL);
+
+ list_add_tail(&dvfs_reg->node, &dvfs_reg_list);
+
+ return dvfs_reg;
+}
+
+static struct dvfs_reg *attach_dvfs_reg(struct dvfs *d)
+{
+ struct dvfs_reg *dvfs_reg;
+
+ dvfs_reg = get_dvfs_reg(d);
+ if (!dvfs_reg)
+ return NULL;
+
+ list_add_tail(&d->reg_node, &dvfs_reg->dvfs);
+ d->dvfs_reg = dvfs_reg;
+ if (d->max_millivolts > d->dvfs_reg->max_millivolts)
+ d->dvfs_reg->max_millivolts = d->max_millivolts;
+
+ d->cur_millivolts = dvfs_reg_get_voltage(d->dvfs_reg);
+
+ return dvfs_reg;
+}
+
+static int
+__tegra_dvfs_set_rate(struct clk *c, struct dvfs *d, unsigned long rate)
+{
+ int i = 0;
+ int ret;
+
+ if (d->freqs == NULL || d->millivolts == NULL)
+ return -ENODEV;
+
+ if (rate > d->freqs[d->num_freqs - 1]) {
+ pr_warn("tegra_dvfs: rate %lu too high for dvfs on %s\n", rate,
+ c->name);
+ return -EINVAL;
+ }
+
+ if (rate == 0) {
+ d->cur_millivolts = 0;
+ } else {
+ while (i < d->num_freqs && rate > d->freqs[i])
+ i++;
+
+ d->cur_millivolts = d->millivolts[i];
+ }
+
+ d->cur_rate = rate;
+
+ if (!d->dvfs_reg)
+ return 0;
+
+ ret = dvfs_reg_set_voltage(d->dvfs_reg);
+ if (ret)
+ pr_err("Failed to set regulator %s for clock %s to %d mV\n",
+ d->dvfs_reg->reg_id, c->name, d->cur_millivolts);
+
+ return ret;
+}
+
+int tegra_dvfs_set_rate(struct clk *c, unsigned long rate)
+{
+ struct dvfs *d;
+ int ret = 0;
+ bool freq_up;
+
+ c->dvfs_rate = rate;
+
+ freq_up = (c->refcnt == 0) || (rate > c->rate);
+
+ list_for_each_entry(d, &c->dvfs, node) {
+ if (d->higher == freq_up)
+ ret = __tegra_dvfs_set_rate(c, d, rate);
+ if (ret)
+ return ret;
+ }
+
+ list_for_each_entry(d, &c->dvfs, node) {
+ if (d->higher != freq_up)
+ ret = __tegra_dvfs_set_rate(c, d, rate);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(tegra_dvfs_set_rate);
+
+int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d)
+{
+ int i;
+ struct dvfs_reg *dvfs_reg;
+
+ dvfs_reg = attach_dvfs_reg(d);
+ if (!dvfs_reg) {
+ pr_err("Failed to get regulator %s for clock %s\n",
+ d->reg_id, c->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < MAX_DVFS_FREQS; i++) {
+ if (d->millivolts[i] == 0)
+ break;
+
+ d->freqs[i] *= d->freqs_mult;
+
+ /* If final frequencies are 0, pad with previous frequency */
+ if (d->freqs[i] == 0 && i > 1)
+ d->freqs[i] = d->freqs[i - 1];
+ }
+ d->num_freqs = i;
+
+ if (d->auto_dvfs)
+ c->auto_dvfs = true;
+
+ c->is_dvfs = true;
+ smp_wmb();
+
+ list_add_tail(&d->node, &c->dvfs);
+
+ list_add_tail(&d->debug_node, &dvfs_debug_list);
+
+ return 0;
+}
+
+int __init tegra_init_dvfs(void)
+{
+ lock_dvfs();
+ tegra2_init_dvfs();
+
+ tegra_clk_set_dvfs_rates();
+ unlock_dvfs();
+
+ return 0;
+}
+late_initcall(tegra_init_dvfs);
+
+#ifdef CONFIG_DEBUG_FS
+static int dvfs_tree_sort_cmp(void *p, struct list_head *a, struct list_head *b)
+{
+ struct dvfs *da = list_entry(a, struct dvfs, debug_node);
+ struct dvfs *db = list_entry(b, struct dvfs, debug_node);
+ int ret;
+
+ ret = strcmp(da->reg_id, db->reg_id);
+ if (ret != 0)
+ return ret;
+
+ if (da->cur_millivolts < db->cur_millivolts)
+ return 1;
+ if (da->cur_millivolts > db->cur_millivolts)
+ return -1;
+
+ return strcmp(da->clk_name, db->clk_name);
+}
+
+static int dvfs_tree_show(struct seq_file *s, void *data)
+{
+ struct dvfs *d;
+ const char *last_reg = "";
+
+ seq_printf(s, " clock rate mV\n");
+ seq_printf(s, "--------------------------------\n");
+
+ lock_dvfs();
+
+ list_sort(NULL, &dvfs_debug_list, dvfs_tree_sort_cmp);
+
+ list_for_each_entry(d, &dvfs_debug_list, debug_node) {
+ if (strcmp(last_reg, d->dvfs_reg->reg_id) != 0) {
+ last_reg = d->dvfs_reg->reg_id;
+ seq_printf(s, "%s %d mV:\n", d->dvfs_reg->reg_id,
+ d->dvfs_reg->millivolts);
+ }
+
+ seq_printf(s, " %-10s %-10lu %-4d mV\n", d->clk_name,
+ d->cur_rate, d->cur_millivolts);
+ }
+
+ unlock_dvfs();
+
+ return 0;
+}
+
+static int dvfs_tree_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dvfs_tree_show, inode->i_private);
+}
+
+static const struct file_operations dvfs_tree_fops = {
+ .open = dvfs_tree_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root)
+{
+ struct dentry *d;
+
+ d = debugfs_create_file("dvfs", S_IRUGO, clk_debugfs_root, NULL,
+ &dvfs_tree_fops);
+ if (!d)
+ return -ENOMEM;
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ *
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * Author:
+ * Colin Cross <ccross@google.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _TEGRA_DVFS_H_
+#define _TEGRA_DVFS_H_
+
+#define MAX_DVFS_FREQS 16
+
+struct clk;
+
+struct dvfs {
+ /* Used only by tegra2_clock.c */
+ const char *clk_name;
+ int process_id;
+ bool cpu;
+
+ /* Must be initialized before tegra_dvfs_init */
+ const char *reg_id;
+ int freqs_mult;
+ unsigned long freqs[MAX_DVFS_FREQS];
+ unsigned long millivolts[MAX_DVFS_FREQS];
+ bool auto_dvfs;
+ bool higher;
+
+ /* Filled in by tegra_dvfs_init */
+ int max_millivolts;
+ int num_freqs;
+ struct dvfs_reg *dvfs_reg;
+
+ int cur_millivolts;
+ unsigned long cur_rate;
+ struct list_head node;
+ struct list_head debug_node;
+ struct list_head reg_node;
+};
+
+void lock_dvfs(void);
+void unlock_dvfs(void);
+
+void tegra2_init_dvfs(void);
+int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d);
+int dvfs_debugfs_init(struct dentry *clk_debugfs_root);
+
+#endif
#ifndef __MACH_CLK_H
#define __MACH_CLK_H
+struct dvfs;
+
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);
+
#endif