diff options
Diffstat (limited to 'drivers/sh/clk/core.c')
| -rw-r--r-- | drivers/sh/clk/core.c | 313 |
1 files changed, 159 insertions, 154 deletions
diff --git a/drivers/sh/clk/core.c b/drivers/sh/clk/core.c index fd0d1b98901..be56b22ca94 100644 --- a/drivers/sh/clk/core.c +++ b/drivers/sh/clk/core.c @@ -21,12 +21,10 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/list.h> -#include <linux/kobject.h> -#include <linux/sysdev.h> +#include <linux/syscore_ops.h> #include <linux/seq_file.h> #include <linux/err.h> #include <linux/io.h> -#include <linux/debugfs.h> #include <linux/cpufreq.h> #include <linux/clk.h> #include <linux/sh_clk.h> @@ -35,6 +33,9 @@ static LIST_HEAD(clock_list); static DEFINE_SPINLOCK(clock_lock); static DEFINE_MUTEX(clock_list_sem); +/* clock disable operations are not passed on to hardware during boot */ +static int allow_disable; + void clk_rate_table_build(struct clk *clk, struct cpufreq_frequency_table *freq_table, int nr_freqs, @@ -62,12 +63,12 @@ void clk_rate_table_build(struct clk *clk, else freq = clk->parent->rate * mult / div; - freq_table[i].index = i; + freq_table[i].driver_data = i; freq_table[i].frequency = freq; } /* Termination entry */ - freq_table[i].index = i; + freq_table[i].driver_data = i; freq_table[i].frequency = CPUFREQ_TABLE_END; } @@ -90,8 +91,8 @@ struct clk_rate_round_data { static long clk_rate_round_helper(struct clk_rate_round_data *rounder) { unsigned long rate_error, rate_error_prev = ~0UL; - unsigned long rate_best_fit = rounder->rate; unsigned long highest, lowest, freq; + long rate_best_fit = -ENOENT; int i; highest = 0; @@ -146,7 +147,7 @@ long clk_rate_table_round(struct clk *clk, }; if (clk->nr_freqs < 1) - return 0; + return -ENOSYS; return clk_rate_round_helper(&table_round); } @@ -171,21 +172,35 @@ long clk_rate_div_range_round(struct clk *clk, unsigned int div_min, return clk_rate_round_helper(&div_range_round); } +static long clk_rate_mult_range_iter(unsigned int pos, + struct clk_rate_round_data *rounder) +{ + return clk_get_rate(rounder->arg) * pos; +} + +long clk_rate_mult_range_round(struct clk *clk, unsigned int mult_min, + unsigned int mult_max, unsigned long rate) +{ + struct clk_rate_round_data mult_range_round = { + .min = mult_min, + .max = mult_max, + .func = clk_rate_mult_range_iter, + .arg = clk_get_parent(clk), + .rate = rate, + }; + + return clk_rate_round_helper(&mult_range_round); +} + int clk_rate_table_find(struct clk *clk, struct cpufreq_frequency_table *freq_table, unsigned long rate) { - int i; - - for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { - unsigned long freq = freq_table[i].frequency; - - if (freq == CPUFREQ_ENTRY_INVALID) - continue; + struct cpufreq_frequency_table *pos; - if (freq == rate) - return i; - } + cpufreq_for_each_valid_entry(pos, freq_table) + if (pos->frequency == rate) + return pos - freq_table; return -ENOENT; } @@ -203,9 +218,6 @@ int clk_reparent(struct clk *child, struct clk *parent) list_add(&child->sibling, &parent->children); child->parent = parent; - /* now do the debugfs renaming to reattach the child - to the proper parent */ - return 0; } @@ -229,7 +241,7 @@ static void __clk_disable(struct clk *clk) return; if (!(--clk->usecount)) { - if (likely(clk->ops && clk->ops->disable)) + if (likely(allow_disable && clk->ops && clk->ops->disable)) clk->ops->disable(clk); if (likely(clk->parent)) __clk_disable(clk->parent); @@ -337,7 +349,7 @@ static int clk_establish_mapping(struct clk *clk) */ if (!clk->parent) { clk->mapping = &dummy_mapping; - return 0; + goto out; } /* @@ -366,6 +378,9 @@ static int clk_establish_mapping(struct clk *clk) } clk->mapping = mapping; +out: + clk->mapped_reg = clk->mapping->base; + clk->mapped_reg += (phys_addr_t)clk->enable_reg - clk->mapping->phys; return 0; } @@ -384,17 +399,19 @@ static void clk_teardown_mapping(struct clk *clk) /* Nothing to do */ if (mapping == &dummy_mapping) - return; + goto out; kref_put(&mapping->ref, clk_destroy_mapping); clk->mapping = NULL; +out: + clk->mapped_reg = NULL; } int clk_register(struct clk *clk) { int ret; - if (clk == NULL || IS_ERR(clk)) + if (IS_ERR_OR_NULL(clk)) return -EINVAL; /* @@ -418,8 +435,11 @@ int clk_register(struct clk *clk) list_add(&clk->sibling, &root_clks); list_add(&clk->node, &clock_list); + +#ifdef CONFIG_SH_CLK_CPG_LEGACY if (clk->ops && clk->ops->init) clk->ops->init(clk); +#endif out_unlock: mutex_unlock(&clock_list_sem); @@ -455,19 +475,13 @@ EXPORT_SYMBOL_GPL(clk_get_rate); int clk_set_rate(struct clk *clk, unsigned long rate) { - return clk_set_rate_ex(clk, rate, 0); -} -EXPORT_SYMBOL_GPL(clk_set_rate); - -int clk_set_rate_ex(struct clk *clk, unsigned long rate, int algo_id) -{ int ret = -EOPNOTSUPP; unsigned long flags; spin_lock_irqsave(&clock_lock, flags); if (likely(clk->ops && clk->ops->set_rate)) { - ret = clk->ops->set_rate(clk, rate, algo_id); + ret = clk->ops->set_rate(clk, rate); if (ret != 0) goto out_unlock; } else { @@ -485,7 +499,7 @@ out_unlock: return ret; } -EXPORT_SYMBOL_GPL(clk_set_rate_ex); +EXPORT_SYMBOL_GPL(clk_set_rate); int clk_set_parent(struct clk *clk, struct clk *parent) { @@ -541,154 +555,145 @@ long clk_round_rate(struct clk *clk, unsigned long rate) } EXPORT_SYMBOL_GPL(clk_round_rate); -#ifdef CONFIG_PM -static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state) +long clk_round_parent(struct clk *clk, unsigned long target, + unsigned long *best_freq, unsigned long *parent_freq, + unsigned int div_min, unsigned int div_max) { - static pm_message_t prev_state; - struct clk *clkp; + struct cpufreq_frequency_table *freq, *best = NULL; + unsigned long error = ULONG_MAX, freq_high, freq_low, div; + struct clk *parent = clk_get_parent(clk); - switch (state.event) { - case PM_EVENT_ON: - /* Resumeing from hibernation */ - if (prev_state.event != PM_EVENT_FREEZE) - break; + if (!parent) { + *parent_freq = 0; + *best_freq = clk_round_rate(clk, target); + return abs(target - *best_freq); + } - list_for_each_entry(clkp, &clock_list, node) { - if (likely(clkp->ops)) { - unsigned long rate = clkp->rate; - - if (likely(clkp->ops->set_parent)) - clkp->ops->set_parent(clkp, - clkp->parent); - if (likely(clkp->ops->set_rate)) - clkp->ops->set_rate(clkp, - rate, NO_CHANGE); - else if (likely(clkp->ops->recalc)) - clkp->rate = clkp->ops->recalc(clkp); + cpufreq_for_each_valid_entry(freq, parent->freq_table) { + if (unlikely(freq->frequency / target <= div_min - 1)) { + unsigned long freq_max; + + freq_max = (freq->frequency + div_min / 2) / div_min; + if (error > target - freq_max) { + error = target - freq_max; + best = freq; + if (best_freq) + *best_freq = freq_max; } + + pr_debug("too low freq %u, error %lu\n", freq->frequency, + target - freq_max); + + if (!error) + break; + + continue; } - break; - case PM_EVENT_FREEZE: - break; - case PM_EVENT_SUSPEND: - break; - } - prev_state = state; - return 0; -} + if (unlikely(freq->frequency / target >= div_max)) { + unsigned long freq_min; -static int clks_sysdev_resume(struct sys_device *dev) -{ - return clks_sysdev_suspend(dev, PMSG_ON); -} + freq_min = (freq->frequency + div_max / 2) / div_max; + if (error > freq_min - target) { + error = freq_min - target; + best = freq; + if (best_freq) + *best_freq = freq_min; + } -static struct sysdev_class clks_sysdev_class = { - .name = "clks", -}; + pr_debug("too high freq %u, error %lu\n", freq->frequency, + freq_min - target); -static struct sysdev_driver clks_sysdev_driver = { - .suspend = clks_sysdev_suspend, - .resume = clks_sysdev_resume, -}; + if (!error) + break; -static struct sys_device clks_sysdev_dev = { - .cls = &clks_sysdev_class, -}; + continue; + } -static int __init clk_sysdev_init(void) -{ - sysdev_class_register(&clks_sysdev_class); - sysdev_driver_register(&clks_sysdev_class, &clks_sysdev_driver); - sysdev_register(&clks_sysdev_dev); + div = freq->frequency / target; + freq_high = freq->frequency / div; + freq_low = freq->frequency / (div + 1); - return 0; -} -subsys_initcall(clk_sysdev_init); -#endif + if (freq_high - target < error) { + error = freq_high - target; + best = freq; + if (best_freq) + *best_freq = freq_high; + } -/* - * 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, *child_tmp; - struct clk *pa = c->parent; - char s[255]; - char *p = s; - - p += sprintf(p, "%p", c); - d = debugfs_create_dir(s, pa ? pa->dentry : clk_debugfs_root); - if (!d) - return -ENOMEM; - c->dentry = d; - - d = debugfs_create_u8("usecount", S_IRUGO, c->dentry, (u8 *)&c->usecount); - if (!d) { - err = -ENOMEM; - goto err_out; - } - d = debugfs_create_u32("rate", S_IRUGO, c->dentry, (u32 *)&c->rate); - if (!d) { - err = -ENOMEM; - goto err_out; - } - d = debugfs_create_x32("flags", S_IRUGO, c->dentry, (u32 *)&c->flags); - if (!d) { - err = -ENOMEM; - goto err_out; + if (target - freq_low < error) { + error = target - freq_low; + best = freq; + if (best_freq) + *best_freq = freq_low; + } + + pr_debug("%u / %lu = %lu, / %lu = %lu, best %lu, parent %u\n", + freq->frequency, div, freq_high, div + 1, freq_low, + *best_freq, best->frequency); + + if (!error) + break; } - return 0; -err_out: - d = c->dentry; - list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) - debugfs_remove(child); - debugfs_remove(c->dentry); - return err; + if (parent_freq) + *parent_freq = best->frequency; + + return error; } +EXPORT_SYMBOL_GPL(clk_round_parent); -static int clk_debugfs_register(struct clk *c) +#ifdef CONFIG_PM +static void clks_core_resume(void) { - int err; - struct clk *pa = c->parent; + struct clk *clkp; - if (pa && !pa->dentry) { - err = clk_debugfs_register(pa); - if (err) - return err; + list_for_each_entry(clkp, &clock_list, node) { + if (likely(clkp->usecount && clkp->ops)) { + unsigned long rate = clkp->rate; + + if (likely(clkp->ops->set_parent)) + clkp->ops->set_parent(clkp, + clkp->parent); + if (likely(clkp->ops->set_rate)) + clkp->ops->set_rate(clkp, rate); + else if (likely(clkp->ops->recalc)) + clkp->rate = clkp->ops->recalc(clkp); + } } +} + +static struct syscore_ops clks_syscore_ops = { + .resume = clks_core_resume, +}; + +static int __init clk_syscore_init(void) +{ + register_syscore_ops(&clks_syscore_ops); - if (!c->dentry) { - err = clk_debugfs_register_one(c); - if (err) - return err; - } return 0; } +subsys_initcall(clk_syscore_init); +#endif -static int __init clk_debugfs_init(void) +static int __init clk_late_init(void) { - struct clk *c; - struct dentry *d; - int err; + unsigned long flags; + struct clk *clk; - d = debugfs_create_dir("clock", NULL); - if (!d) - return -ENOMEM; - clk_debugfs_root = d; + /* disable all clocks with zero use count */ + mutex_lock(&clock_list_sem); + spin_lock_irqsave(&clock_lock, flags); - list_for_each_entry(c, &clock_list, node) { - err = clk_debugfs_register(c); - if (err) - goto err_out; - } + list_for_each_entry(clk, &clock_list, node) + if (!clk->usecount && clk->ops && clk->ops->disable) + clk->ops->disable(clk); + + /* from now on allow clock disable operations */ + allow_disable = 1; + + spin_unlock_irqrestore(&clock_lock, flags); + mutex_unlock(&clock_list_sem); return 0; -err_out: - debugfs_remove_recursive(clk_debugfs_root); - return err; } -late_initcall(clk_debugfs_init); +late_initcall(clk_late_init); |
