diff options
Diffstat (limited to 'drivers/clk/clk.c')
| -rw-r--r-- | drivers/clk/clk.c | 268 |
1 files changed, 267 insertions, 1 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 97a604755053..1eb6e32e0d51 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -1,6 +1,7 @@ /* * Copyright (C) 2010-2011 Canonical Ltd <jeremy.kerr@canonical.com> * Copyright (C) 2011-2012 Linaro Ltd <mturquette@linaro.org> + * Copyright (c) 2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -23,6 +24,7 @@ #include <linux/init.h> #include <linux/sched.h> #include <linux/clkdev.h> +#include <linux/regulator/consumer.h> #include "clk.h" @@ -41,6 +43,13 @@ static HLIST_HEAD(clk_root_list); static HLIST_HEAD(clk_orphan_list); static LIST_HEAD(clk_notifier_list); +struct clk_handoff_vdd { + struct list_head list; + struct clk_vdd_class *vdd_class; +}; + +static LIST_HEAD(clk_handoff_vdd_list); + /*** private data structures ***/ struct clk_core { @@ -75,6 +84,9 @@ struct clk_core { struct hlist_node debug_node; #endif struct kref ref; + struct clk_vdd_class *vdd_class; + unsigned long *rate_max; + int num_rate_max; }; #define CREATE_TRACE_POINTS @@ -243,9 +255,12 @@ static int __init clk_ignore_unused_setup(char *__unused) } __setup("clk_ignore_unused", clk_ignore_unused_setup); +static int clk_unvote_vdd_level(struct clk_vdd_class *vdd_class, int level); + static int clk_disable_unused(void) { struct clk_core *core; + struct clk_handoff_vdd *v, *v_temp; if (clk_ignore_unused) { pr_warn("clk: Not disabling unused clocks\n"); @@ -266,6 +281,13 @@ static int clk_disable_unused(void) hlist_for_each_entry(core, &clk_orphan_list, child_node) clk_unprepare_unused_subtree(core); + list_for_each_entry_safe(v, v_temp, &clk_handoff_vdd_list, list) { + clk_unvote_vdd_level(v->vdd_class, + v->vdd_class->num_levels - 1); + list_del(&v->list); + kfree(v); + }; + clk_prepare_unlock(); return 0; @@ -585,6 +607,212 @@ int __clk_mux_determine_rate_closest(struct clk_hw *hw, } EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest); +/* + * Find the voltage level required for a given clock rate. + */ +static int clk_find_vdd_level(struct clk_core *clk, unsigned long rate) +{ + int level; + + for (level = 0; level < clk->num_rate_max; level++) + if (rate <= clk->rate_max[level]) + break; + + if (level == clk->num_rate_max) { + pr_err("Rate %lu for %s is greater than highest Fmax\n", rate, + clk->name); + return -EINVAL; + } + + return level; +} + +/* + * Update voltage level given the current votes. + */ +static int clk_update_vdd(struct clk_vdd_class *vdd_class) +{ + int level, rc = 0, i, ignore; + struct regulator **r = vdd_class->regulator; + int *uv = vdd_class->vdd_uv; + int n_reg = vdd_class->num_regulators; + int cur_lvl = vdd_class->cur_level; + int max_lvl = vdd_class->num_levels - 1; + int cur_base = cur_lvl * n_reg; + int new_base; + + /* aggregate votes */ + for (level = max_lvl; level > 0; level--) + if (vdd_class->level_votes[level]) + break; + + if (level == cur_lvl) + return 0; + + max_lvl = max_lvl * n_reg; + new_base = level * n_reg; + + for (i = 0; i < vdd_class->num_regulators; i++) { + pr_debug("Set Voltage level Min %d, Max %d\n", uv[new_base + i], + uv[max_lvl + i]); + rc = regulator_set_voltage(r[i], uv[new_base + i], + uv[max_lvl + i]); + if (rc) + goto set_voltage_fail; + + if (cur_lvl == 0 || cur_lvl == vdd_class->num_levels) + rc = regulator_enable(r[i]); + else if (level == 0) + rc = regulator_disable(r[i]); + if (rc) + goto enable_disable_fail; + } + + if (vdd_class->set_vdd && !vdd_class->num_regulators) + rc = vdd_class->set_vdd(vdd_class, level); + + if (!rc) + vdd_class->cur_level = level; + + return rc; + +enable_disable_fail: + regulator_set_voltage(r[i], uv[cur_base + i], uv[max_lvl + i]); + +set_voltage_fail: + for (i--; i >= 0; i--) { + regulator_set_voltage(r[i], uv[cur_base + i], uv[max_lvl + i]); + if (cur_lvl == 0 || cur_lvl == vdd_class->num_levels) + regulator_disable(r[i]); + else if (level == 0) + ignore = regulator_enable(r[i]); + } + + return rc; +} + +/* + * Vote for a voltage level. + */ +static int clk_vote_vdd_level(struct clk_vdd_class *vdd_class, int level) +{ + int rc = 0; + + if (level >= vdd_class->num_levels) + return -EINVAL; + + mutex_lock(&vdd_class->lock); + + vdd_class->level_votes[level]++; + + rc = clk_update_vdd(vdd_class); + if (rc) + vdd_class->level_votes[level]--; + + mutex_unlock(&vdd_class->lock); + + return rc; +} + +/* + * Remove vote for a voltage level. + */ +static int clk_unvote_vdd_level(struct clk_vdd_class *vdd_class, int level) +{ + int rc = 0; + + if (level >= vdd_class->num_levels) + return -EINVAL; + + mutex_lock(&vdd_class->lock); + + if (WARN(!vdd_class->level_votes[level], + "Reference counts are incorrect for %s level %d\n", + vdd_class->class_name, level)) + goto out; + + vdd_class->level_votes[level]--; + + rc = clk_update_vdd(vdd_class); + if (rc) + vdd_class->level_votes[level]++; + +out: + mutex_unlock(&vdd_class->lock); + return rc; +} + +/* + * Vote for a voltage level corresponding to a clock's rate. + */ +static int clk_vote_rate_vdd(struct clk_core *core, unsigned long rate) +{ + int level; + + if (!core->vdd_class) + return 0; + + level = clk_find_vdd_level(core, rate); + if (level < 0) + return level; + + return clk_vote_vdd_level(core->vdd_class, level); +} + +/* + * Remove vote for a voltage level corresponding to a clock's rate. + */ +static void clk_unvote_rate_vdd(struct clk_core *core, unsigned long rate) +{ + int level; + + if (!core->vdd_class) + return; + + level = clk_find_vdd_level(core, rate); + if (level < 0) + return; + + clk_unvote_vdd_level(core->vdd_class, level); +} + +static bool clk_is_rate_level_valid(struct clk_core *core, unsigned long rate) +{ + int level; + + if (!core->vdd_class) + return true; + + level = clk_find_vdd_level(core, rate); + + return level >= 0; +} + +static int clk_vdd_class_init(struct clk_vdd_class *vdd) +{ + struct clk_handoff_vdd *v; + + list_for_each_entry(v, &clk_handoff_vdd_list, list) { + if (v->vdd_class == vdd) + return 0; + } + + pr_debug("voting for vdd_class %s\n", vdd->class_name); + + if (clk_vote_vdd_level(vdd, vdd->num_levels - 1)) + pr_err("failed to vote for %s\n", vdd->class_name); + + v = kmalloc(sizeof(*v), GFP_KERNEL); + if (!v) + return -ENOMEM; + + v->vdd_class = vdd; + + list_add_tail(&v->list, &clk_handoff_vdd_list); + + return 0; +} + /*** clk api ***/ static void clk_core_unprepare(struct clk_core *core) @@ -608,6 +836,9 @@ static void clk_core_unprepare(struct clk_core *core) core->ops->unprepare(core->hw); trace_clk_unprepare_complete(core); + + clk_unvote_rate_vdd(core, core->rate); + clk_core_unprepare(core->parent); } @@ -649,12 +880,19 @@ static int clk_core_prepare(struct clk_core *core) trace_clk_prepare(core); + ret = clk_vote_rate_vdd(core, core->rate); + if (ret) { + clk_core_unprepare(core->parent); + return ret; + } + if (core->ops->prepare) ret = core->ops->prepare(core->hw); trace_clk_prepare_complete(core); if (ret) { + clk_unvote_rate_vdd(core, core->rate); clk_core_unprepare(core->parent); return ret; } @@ -1401,6 +1639,9 @@ static struct clk_core *clk_calc_new_rates(struct clk_core *core, top = clk_calc_new_rates(parent, best_parent_rate); out: + if (!clk_is_rate_level_valid(core, rate)) + return NULL; + clk_calc_subtree(core, new_rate, parent, p_index); return top; @@ -1485,15 +1726,26 @@ static int clk_change_rate(struct clk_core *core) trace_clk_set_rate(core, core->new_rate); + /* Enforce vdd requirements for new frequency. */ + if (core->prepare_count) { + rc = clk_vote_rate_vdd(core, core->new_rate); + if (rc) + goto out; + } + if (!skip_set_rate && core->ops->set_rate) { rc = core->ops->set_rate(core->hw, core->new_rate, best_parent_rate); if (rc) - goto out; + goto err_set_rate; } trace_clk_set_rate_complete(core, core->new_rate); + /* Release vdd requirements for old frequency. */ + if (core->prepare_count) + clk_unvote_rate_vdd(core, old_rate); + core->rate = clk_recalc(core, best_parent_rate); if (core->notifier_count && old_rate != core->rate) @@ -1519,6 +1771,9 @@ static int clk_change_rate(struct clk_core *core) return rc; +err_set_rate: + if (core->prepare_count) + clk_unvote_rate_vdd(core, core->new_rate); out: trace_clk_set_rate_complete(core, core->new_rate); @@ -2597,8 +2852,19 @@ struct clk *clk_register(struct device *dev, struct clk_hw *hw) core->num_parents = hw->init->num_parents; core->min_rate = 0; core->max_rate = ULONG_MAX; + core->vdd_class = hw->init->vdd_class; + core->rate_max = hw->init->rate_max; + core->num_rate_max = hw->init->num_rate_max; hw->core = core; + if (core->vdd_class) { + ret = clk_vdd_class_init(core->vdd_class); + if (ret) { + pr_err("Failed to initialize vdd class\n"); + goto fail_parent_names; + } + } + /* allocate local copy in case parent_names is __initdata */ core->parent_names = kcalloc(core->num_parents, sizeof(char *), GFP_KERNEL); |
