diff options
author | Stephen Boyd <sboyd@codeaurora.org> | 2014-01-15 10:47:22 -0800 |
---|---|---|
committer | Mike Turquette <mturquette@linaro.org> | 2014-01-16 12:00:57 -0800 |
commit | 3fa2252b7a78a8057017471a28f47b306e95ee26 (patch) | |
tree | e3cf04e4099a9414b15d27f6428f7726f29d7a05 /drivers/clk | |
parent | d0d44dd4ac58bc547646a9d0e65b4648f97cb533 (diff) | |
download | linux-stable-3fa2252b7a78a8057017471a28f47b306e95ee26.tar.gz linux-stable-3fa2252b7a78a8057017471a28f47b306e95ee26.tar.bz2 linux-stable-3fa2252b7a78a8057017471a28f47b306e95ee26.zip |
clk: Add set_rate_and_parent() op
Some of Qualcomm's clocks can change their parent and rate at the
same time with a single register write. Add support for this
hardware to the common clock framework by adding a new
set_rate_and_parent() op. When the clock framework determines
that both the parent and the rate are going to change during
clk_set_rate() it will call the .set_rate_and_parent() op if
available and fall back to calling .set_parent() followed by
.set_rate() otherwise.
Reviewed-by: James Hogan <james.hogan@imgtec.com>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Mike Turquette <mturquette@linaro.org>
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/clk.c | 78 |
1 files changed, 59 insertions, 19 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index e3e03270b95e..2b38dc99063f 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -1218,10 +1218,9 @@ static void clk_reparent(struct clk *clk, struct clk *new_parent) clk->parent = new_parent; } -static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index) +static struct clk *__clk_set_parent_before(struct clk *clk, struct clk *parent) { unsigned long flags; - int ret = 0; struct clk *old_parent = clk->parent; /* @@ -1252,6 +1251,34 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index) clk_reparent(clk, parent); clk_enable_unlock(flags); + return old_parent; +} + +static void __clk_set_parent_after(struct clk *clk, struct clk *parent, + struct clk *old_parent) +{ + /* + * Finish the migration of prepare state and undo the changes done + * for preventing a race with clk_enable(). + */ + if (clk->prepare_count) { + clk_disable(clk); + clk_disable(old_parent); + __clk_unprepare(old_parent); + } + + /* update debugfs with new clk tree topology */ + clk_debug_reparent(clk, parent); +} + +static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index) +{ + unsigned long flags; + int ret = 0; + struct clk *old_parent; + + old_parent = __clk_set_parent_before(clk, parent); + /* change clock input source */ if (parent && clk->ops->set_parent) ret = clk->ops->set_parent(clk->hw, p_index); @@ -1269,18 +1296,8 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index) return ret; } - /* - * Finish the migration of prepare state and undo the changes done - * for preventing a race with clk_enable(). - */ - if (clk->prepare_count) { - clk_disable(clk); - clk_disable(old_parent); - __clk_unprepare(old_parent); - } + __clk_set_parent_after(clk, parent, old_parent); - /* update debugfs with new clk tree topology */ - clk_debug_reparent(clk, parent); return 0; } @@ -1465,17 +1482,32 @@ static void clk_change_rate(struct clk *clk) struct clk *child; unsigned long old_rate; unsigned long best_parent_rate = 0; + bool skip_set_rate = false; + struct clk *old_parent; old_rate = clk->rate; - /* set parent */ - if (clk->new_parent && clk->new_parent != clk->parent) - __clk_set_parent(clk, clk->new_parent, clk->new_parent_index); - - if (clk->parent) + if (clk->new_parent) + best_parent_rate = clk->new_parent->rate; + else if (clk->parent) best_parent_rate = clk->parent->rate; - if (clk->ops->set_rate) + if (clk->new_parent && clk->new_parent != clk->parent) { + old_parent = __clk_set_parent_before(clk, clk->new_parent); + + if (clk->ops->set_rate_and_parent) { + skip_set_rate = true; + clk->ops->set_rate_and_parent(clk->hw, clk->new_rate, + best_parent_rate, + clk->new_parent_index); + } else if (clk->ops->set_parent) { + clk->ops->set_parent(clk->hw, clk->new_parent_index); + } + + __clk_set_parent_after(clk, clk->new_parent, old_parent); + } + + if (!skip_set_rate && clk->ops->set_rate) clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate); if (clk->ops->recalc_rate) @@ -1770,6 +1802,14 @@ int __clk_init(struct device *dev, struct clk *clk) goto out; } + if (clk->ops->set_rate_and_parent && + !(clk->ops->set_parent && clk->ops->set_rate)) { + pr_warn("%s: %s must implement .set_parent & .set_rate\n", + __func__, clk->name); + ret = -EINVAL; + goto out; + } + /* throw a WARN if any entries in parent_names are NULL */ for (i = 0; i < clk->num_parents; i++) WARN(!clk->parent_names[i], |