diff options
author | Stephen Boyd <sboyd@codeaurora.org> | 2016-05-16 11:05:16 +0530 |
---|---|---|
committer | Taniya Das <tdas@codeaurora.org> | 2016-11-03 09:16:56 +0530 |
commit | 2ac3d304b9acb8f1009cba6567f33fa285a5f6dc (patch) | |
tree | b3f8dd6f0ebcaeca49c2587a0dedc31f8f274606 /drivers | |
parent | 8992f7dd08968333dcbeb70c5a0862970a0094cf (diff) |
clk: Add support to vote to regulator framework from clk framework
Add vdd_class support which would help vote/unvote for any voltage rail
for the clock frequency to the regulator framework. A clock client request
for a clock frequency would look for the corresponding voltage vote and
would be send the request to regulator framework.
Change-Id: I5b1229091fcb7b3887b54735b9663fd31a35db21
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Taniya Das <tdas@codeaurora.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/clk/clk-composite.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk-divider.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk-fixed-factor.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk-fixed-rate.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk-fractional-divider.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk-gate.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk-mux.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk-pwm.c | 2 | ||||
-rw-r--r-- | drivers/clk/clk.c | 268 |
9 files changed, 275 insertions, 9 deletions
diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 4735de0660cc..8db56919e367 100644 --- a/drivers/clk/clk-composite.c +++ b/drivers/clk/clk-composite.c @@ -194,7 +194,7 @@ struct clk *clk_register_composite(struct device *dev, const char *name, unsigned long flags) { struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_composite *composite; struct clk_ops *clk_composite_ops; diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index bbf206e3da0d..0c83ffc22dd2 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -436,7 +436,7 @@ static struct clk *_register_divider(struct device *dev, const char *name, { struct clk_divider *div; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) { if (width + shift > 16) { diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c index 83de57aeceea..57fbc94764ff 100644 --- a/drivers/clk/clk-fixed-factor.c +++ b/drivers/clk/clk-fixed-factor.c @@ -75,7 +75,7 @@ struct clk *clk_register_fixed_factor(struct device *dev, const char *name, unsigned int mult, unsigned int div) { struct clk_fixed_factor *fix; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk *clk; fix = kmalloc(sizeof(*fix), GFP_KERNEL); diff --git a/drivers/clk/clk-fixed-rate.c b/drivers/clk/clk-fixed-rate.c index f85ec8d1711f..2ca7d5a8826f 100644 --- a/drivers/clk/clk-fixed-rate.c +++ b/drivers/clk/clk-fixed-rate.c @@ -62,7 +62,7 @@ struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev, { struct clk_fixed_rate *fixed; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; /* allocate fixed-rate clock */ fixed = kzalloc(sizeof(*fixed), GFP_KERNEL); diff --git a/drivers/clk/clk-fractional-divider.c b/drivers/clk/clk-fractional-divider.c index 5c4955e33f7a..f50892a74b60 100644 --- a/drivers/clk/clk-fractional-divider.c +++ b/drivers/clk/clk-fractional-divider.c @@ -124,7 +124,7 @@ struct clk *clk_register_fractional_divider(struct device *dev, u8 clk_divider_flags, spinlock_t *lock) { struct clk_fractional_divider *fd; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk *clk; fd = kzalloc(sizeof(*fd), GFP_KERNEL); diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c index de0b322f5f58..eeb142534016 100644 --- a/drivers/clk/clk-gate.c +++ b/drivers/clk/clk-gate.c @@ -129,7 +129,7 @@ struct clk *clk_register_gate(struct device *dev, const char *name, { struct clk_gate *gate; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; if (clk_gate_flags & CLK_GATE_HIWORD_MASK) { if (bit_idx > 15) { diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c index 7129c86a79db..21cb9fc0e4c4 100644 --- a/drivers/clk/clk-mux.c +++ b/drivers/clk/clk-mux.c @@ -124,7 +124,7 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name, { struct clk_mux *mux; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; u8 width = 0; if (clk_mux_flags & CLK_MUX_HIWORD_MASK) { diff --git a/drivers/clk/clk-pwm.c b/drivers/clk/clk-pwm.c index 328fcfcefd8c..63505a323a08 100644 --- a/drivers/clk/clk-pwm.c +++ b/drivers/clk/clk-pwm.c @@ -56,7 +56,7 @@ static const struct clk_ops clk_pwm_ops = { static int clk_pwm_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_pwm *clk_pwm; struct pwm_device *pwm; const char *clk_name; 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); |