summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorStephen Boyd <sboyd@codeaurora.org>2016-05-16 11:05:16 +0530
committerTaniya Das <tdas@codeaurora.org>2016-11-03 09:16:56 +0530
commit2ac3d304b9acb8f1009cba6567f33fa285a5f6dc (patch)
treeb3f8dd6f0ebcaeca49c2587a0dedc31f8f274606 /drivers
parent8992f7dd08968333dcbeb70c5a0862970a0094cf (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.c2
-rw-r--r--drivers/clk/clk-divider.c2
-rw-r--r--drivers/clk/clk-fixed-factor.c2
-rw-r--r--drivers/clk/clk-fixed-rate.c2
-rw-r--r--drivers/clk/clk-fractional-divider.c2
-rw-r--r--drivers/clk/clk-gate.c2
-rw-r--r--drivers/clk/clk-mux.c2
-rw-r--r--drivers/clk/clk-pwm.c2
-rw-r--r--drivers/clk/clk.c268
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);