/* * * Copyright (C) 2010 Google, Inc. * * Author: * Colin Cross * * 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 #include #include #include #include #include #include #include #include #include #include "clock.h" #include "board.h" #include "fuse.h" static LIST_HEAD(clocks); static DEFINE_SPINLOCK(clock_lock); static DEFINE_MUTEX(dvfs_lock); static int clk_is_dvfs(struct clk *c) { return (c->dvfs != NULL); }; static int dvfs_set_rate(struct dvfs *d, unsigned long rate) { struct dvfs_table *t; if (d->table == NULL) return -ENODEV; for (t = d->table; t->rate != 0; t++) { if (rate <= t->rate) { if (!d->reg) return 0; return regulator_set_voltage(d->reg, t->millivolts * 1000, d->max_millivolts * 1000); } } return -EINVAL; } static void dvfs_init(struct clk *c) { int process_id; int i; struct dvfs_table *table; process_id = c->dvfs->cpu ? tegra_core_process_id() : tegra_cpu_process_id(); for (i = 0; i < c->dvfs->process_id_table_length; i++) if (process_id == c->dvfs->process_id_table[i].process_id) c->dvfs->table = c->dvfs->process_id_table[i].table; if (c->dvfs->table == NULL) { pr_err("Failed to find dvfs table for clock %s process %d\n", c->name, process_id); return; } c->dvfs->max_millivolts = 0; for (table = c->dvfs->table; table->rate != 0; table++) if (c->dvfs->max_millivolts < table->millivolts) c->dvfs->max_millivolts = table->millivolts; c->dvfs->reg = regulator_get(NULL, c->dvfs->reg_id); if (IS_ERR(c->dvfs->reg)) { pr_err("Failed to get regulator %s for clock %s\n", c->dvfs->reg_id, c->name); c->dvfs->reg = NULL; return; } if (c->refcnt > 0) dvfs_set_rate(c->dvfs, c->rate); } 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); list_for_each_entry(c, &clocks, node) { if (strcmp(c->name, name) == 0) { ret = c; break; } } spin_unlock_irqrestore(&clock_lock, flags); return ret; } static void clk_recalculate_rate(struct clk *c) { u64 rate; if (!c->parent) return; rate = c->parent->rate; if (c->mul != 0 && c->div != 0) { rate = rate * c->mul; do_div(rate, c->div); } if (rate > c->max_rate) pr_warn("clocks: Set clock %s to rate %llu, max is %lu\n", c->name, rate, c->max_rate); c->rate = rate; } int clk_reparent(struct clk *c, struct clk *parent) { pr_debug("%s: %s\n", __func__, c->name); 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; pr_debug("%s: %s\n", __func__, c->name); list_for_each_entry(clkp, &c->children, sibling) { pr_debug(" %s\n", clkp->name); clk_recalculate_rate(clkp); propagate_rate(clkp); } } void clk_init(struct clk *c) { unsigned long flags; pr_debug("%s: %s\n", __func__, c->name); spin_lock_irqsave(&clock_lock, flags); INIT_LIST_HEAD(&c->children); INIT_LIST_HEAD(&c->sibling); if (c->ops && c->ops->init) c->ops->init(c); clk_recalculate_rate(c); list_add(&c->node, &clocks); if (c->parent) list_add_tail(&c->sibling, &c->parent->children); spin_unlock_irqrestore(&clock_lock, flags); } int clk_enable_locked(struct clk *c) { int ret; pr_debug("%s: %s\n", __func__, c->name); if (c->refcnt == 0) { if (c->parent) { ret = clk_enable_locked(c->parent); if (ret) return ret; } if (c->ops && c->ops->enable) { ret = c->ops->enable(c); if (ret) { if (c->parent) clk_disable_locked(c->parent); return ret; } c->state = ON; #ifdef CONFIG_DEBUG_FS c->set = 1; #endif } } c->refcnt++; return 0; } int clk_enable_cansleep(struct clk *c) { int ret; unsigned long flags; mutex_lock(&dvfs_lock); if (clk_is_dvfs(c) && c->refcnt > 0) dvfs_set_rate(c->dvfs, c->rate); spin_lock_irqsave(&clock_lock, flags); ret = clk_enable_locked(c); spin_unlock_irqrestore(&clock_lock, flags); mutex_unlock(&dvfs_lock); return ret; } EXPORT_SYMBOL(clk_enable_cansleep); int clk_enable(struct clk *c) { int ret; unsigned long flags; if (clk_is_dvfs(c)) BUG(); spin_lock_irqsave(&clock_lock, flags); ret = clk_enable_locked(c); spin_unlock_irqrestore(&clock_lock, flags); return ret; } EXPORT_SYMBOL(clk_enable); void clk_disable_locked(struct clk *c) { pr_debug("%s: %s\n", __func__, c->name); if (c->refcnt == 0) { WARN(1, "Attempting to disable clock %s with refcnt 0", c->name); return; } if (c->refcnt == 1) { if (c->ops && c->ops->disable) c->ops->disable(c); if (c->parent) clk_disable_locked(c->parent); c->state = OFF; } c->refcnt--; } void clk_disable_cansleep(struct clk *c) { unsigned long flags; mutex_lock(&dvfs_lock); spin_lock_irqsave(&clock_lock, flags); clk_disable_locked(c); spin_unlock_irqrestore(&clock_lock, flags); if (clk_is_dvfs(c) && c->refcnt == 0) dvfs_set_rate(c->dvfs, c->rate); mutex_unlock(&dvfs_lock); } EXPORT_SYMBOL(clk_disable_cansleep); void clk_disable(struct clk *c) { unsigned long flags; if (clk_is_dvfs(c)) BUG(); spin_lock_irqsave(&clock_lock, flags); clk_disable_locked(c); spin_unlock_irqrestore(&clock_lock, flags); } EXPORT_SYMBOL(clk_disable); int clk_set_parent_locked(struct clk *c, struct clk *parent) { int ret; pr_debug("%s: %s\n", __func__, c->name); 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; unsigned long flags; spin_lock_irqsave(&clock_lock, flags); ret = clk_set_parent_locked(c, parent); spin_unlock_irqrestore(&clock_lock, flags); return ret; } EXPORT_SYMBOL(clk_set_parent); struct clk *clk_get_parent(struct clk *c) { return c->parent; } EXPORT_SYMBOL(clk_get_parent); int clk_set_rate_locked(struct clk *c, unsigned long rate) { int ret; if (rate > c->max_rate) rate = c->max_rate; if (!c->ops || !c->ops->set_rate) return -ENOSYS; ret = c->ops->set_rate(c, rate); if (ret) return ret; clk_recalculate_rate(c); propagate_rate(c); return 0; } int clk_set_rate_cansleep(struct clk *c, unsigned long rate) { int ret = 0; unsigned long flags; pr_debug("%s: %s\n", __func__, c->name); mutex_lock(&dvfs_lock); if (rate > c->rate) ret = dvfs_set_rate(c->dvfs, 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; ret = dvfs_set_rate(c->dvfs, rate); out: mutex_unlock(&dvfs_lock); return ret; } EXPORT_SYMBOL(clk_set_rate_cansleep); int clk_set_rate(struct clk *c, unsigned long rate) { int ret = 0; unsigned long flags; pr_debug("%s: %s\n", __func__, c->name); if (clk_is_dvfs(c)) BUG(); spin_lock_irqsave(&clock_lock, flags); ret = clk_set_rate_locked(c, rate); spin_unlock_irqrestore(&clock_lock, flags); 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); pr_debug("%s: %s\n", __func__, c->name); 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) { pr_debug("%s: %s\n", __func__, c->name); if (!c->ops || !c->ops->round_rate) return -ENOSYS; if (rate > c->max_rate) rate = c->max_rate; return c->ops->round_rate(c, rate); } EXPORT_SYMBOL(clk_round_rate); static int tegra_clk_init_one_from_table(struct tegra_clk_init_table *table) { struct clk *c; struct clk *p; int ret = 0; c = tegra_get_clock_by_name(table->name); if (!c) { pr_warning("Unable to initialize clock %s\n", table->name); return -ENODEV; } if (table->parent) { p = tegra_get_clock_by_name(table->parent); if (!p) { pr_warning("Unable to find parent %s of clock %s\n", table->parent, table->name); return -ENODEV; } if (c->parent != p) { ret = clk_set_parent(c, p); if (ret) { pr_warning("Unable to set parent %s of clock %s: %d\n", table->parent, table->name, ret); return -EINVAL; } } } if (table->rate && table->rate != clk_get_rate(c)) { ret = clk_set_rate(c, table->rate); if (ret) { pr_warning("Unable to set clock %s to rate %lu: %d\n", table->name, table->rate, ret); return -EINVAL; } } if (table->enabled) { ret = clk_enable(c); if (ret) { pr_warning("Unable to enable clock %s: %d\n", table->name, ret); return -EINVAL; } } return 0; } void tegra_clk_init_from_table(struct tegra_clk_init_table *table) { for (; table->name; table++) tegra_clk_init_one_from_table(table); } EXPORT_SYMBOL(tegra_clk_init_from_table); void tegra_periph_reset_deassert(struct clk *c) { tegra2_periph_reset_deassert(c); } EXPORT_SYMBOL(tegra_periph_reset_deassert); void tegra_periph_reset_assert(struct clk *c) { tegra2_periph_reset_assert(c); } EXPORT_SYMBOL(tegra_periph_reset_assert); void __init tegra_init_clock(void) { tegra2_init_clocks(); } int __init tegra_init_dvfs(void) { struct clk *c, *safe; mutex_lock(&dvfs_lock); list_for_each_entry_safe(c, safe, &clocks, node) if (c->dvfs) dvfs_init(c); mutex_unlock(&dvfs_lock); return 0; } late_initcall(tegra_init_dvfs); #ifdef CONFIG_DEBUG_FS static struct dentry *clk_debugfs_root; static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level) { struct clk *child; struct clk *safe; const char *state = "uninit"; char div[8] = {0}; if (c->state == ON) state = "on"; else if (c->state == OFF) state = "off"; if (c->mul != 0 && c->div != 0) { if (c->mul > c->div) { int mul = c->mul / c->div; int mul2 = (c->mul * 10 / c->div) % 10; int mul3 = (c->mul * 10) % c->div; if (mul2 == 0 && mul3 == 0) snprintf(div, sizeof(div), "x%d", mul); else if (mul3 == 0) snprintf(div, sizeof(div), "x%d.%d", mul, mul2); else snprintf(div, sizeof(div), "x%d.%d..", mul, mul2); } else { snprintf(div, sizeof(div), "%d%s", c->div / c->mul, (c->div % c->mul) ? ".5" : ""); } } seq_printf(s, "%*s%c%c%-*s %-6s %-3d %-8s %-10lu\n", level * 3 + 1, "", c->rate > c->max_rate ? '!' : ' ', !c->set ? '*' : ' ', 30 - level * 3, c->name, state, c->refcnt, div, c->rate); list_for_each_entry_safe(child, safe, &c->children, sibling) { clock_tree_show_one(s, child, level + 1); } } 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); list_for_each_entry(c, &clocks, node) if (c->parent == NULL) clock_tree_show_one(s, c, 0); spin_unlock_irqrestore(&clock_lock, flags); return 0; } static int clock_tree_open(struct inode *inode, struct file *file) { return single_open(file, clock_tree_show, inode->i_private); } static const struct file_operations clock_tree_fops = { .open = clock_tree_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int possible_parents_show(struct seq_file *s, void *data) { struct clk *c = s->private; int i; for (i = 0; c->inputs[i].input; i++) { char *first = (i == 0) ? "" : " "; seq_printf(s, "%s%s", first, c->inputs[i].input->name); } seq_printf(s, "\n"); return 0; } static int possible_parents_open(struct inode *inode, struct file *file) { return single_open(file, possible_parents_show, inode->i_private); } static const struct file_operations possible_parents_fops = { .open = possible_parents_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int clk_debugfs_register_one(struct clk *c) { struct dentry *d, *child, *child_tmp; d = debugfs_create_dir(c->name, clk_debugfs_root); if (!d) return -ENOMEM; c->dent = d; d = debugfs_create_u8("refcnt", S_IRUGO, c->dent, (u8 *)&c->refcnt); if (!d) goto err_out; d = debugfs_create_u32("rate", S_IRUGO, c->dent, (u32 *)&c->rate); if (!d) goto err_out; d = debugfs_create_x32("flags", S_IRUGO, c->dent, (u32 *)&c->flags); 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; } return 0; err_out: d = c->dent; list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) debugfs_remove(child); debugfs_remove(c->dent); return -ENOMEM; } 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 = -ENOMEM; d = debugfs_create_dir("clock", NULL); if (!d) return -ENOMEM; clk_debugfs_root = d; d = debugfs_create_file("clock_tree", S_IRUGO, clk_debugfs_root, NULL, &clock_tree_fops); if (!d) goto err_out; list_for_each_entry(c, &clocks, node) { err = clk_debugfs_register(c); if (err) goto err_out; } return 0; err_out: debugfs_remove_recursive(clk_debugfs_root); return err; } late_initcall(clk_debugfs_init); #endif