Merge "clk: qcom: clk-voter: Add support for voter clocks"

This commit is contained in:
Linux Build Service Account 2016-10-15 23:19:41 -07:00 committed by Gerrit - the friendly Code Review server
commit 4f983fd1aa
5 changed files with 207 additions and 1 deletions

View file

@ -546,6 +546,26 @@ void clk_hw_set_rate_range(struct clk_hw *hw, unsigned long min_rate,
}
EXPORT_SYMBOL_GPL(clk_hw_set_rate_range);
/*
* Aggregate the rate of all child nodes which are enabled and exclude the
* child node which requests for clk_aggregate_rate.
*/
unsigned long clk_aggregate_rate(struct clk_hw *hw,
const struct clk_core *parent)
{
struct clk_core *child;
unsigned long aggre_rate = 0;
hlist_for_each_entry(child, &parent->children, child_node) {
if (child->enable_count &&
strcmp(child->name, hw->init->name))
aggre_rate = max(child->rate, aggre_rate);
}
return aggre_rate;
}
EXPORT_SYMBOL_GPL(clk_aggregate_rate);
/*
* Helper for finding best parent to provide a given frequency. This can be used
* directly as a determine_rate callback (e.g. for a mux), or from a more

View file

@ -11,7 +11,7 @@ clk-qcom-y += clk-regmap-divider.o
clk-qcom-y += clk-regmap-mux.o
clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
clk-qcom-y += clk-hfpll.o
clk-qcom-y += reset.o
clk-qcom-y += reset.o clk-voter.o
clk-qcom-y += clk-dummy.o
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o gdsc-regulator.o

View file

@ -0,0 +1,133 @@
/*
* 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 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include "clk-voter.h"
static int voter_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
int ret = 0;
struct clk_voter *v = to_clk_voter(hw);
unsigned long cur_rate, new_rate, other_rate = 0;
if (v->is_branch)
return ret;
if (v->enabled) {
struct clk_hw *parent = clk_hw_get_parent(hw);
/*
* Get the aggregate rate without this clock's vote and update
* if the new rate is different than the current rate.
*/
other_rate = clk_aggregate_rate(hw, parent->core);
cur_rate = max(other_rate, clk_get_rate(hw->clk));
new_rate = max(other_rate, rate);
if (new_rate != cur_rate) {
ret = clk_set_rate(parent->clk, new_rate);
if (ret)
return ret;
}
}
v->rate = rate;
return ret;
}
static int voter_clk_prepare(struct clk_hw *hw)
{
int ret = 0;
unsigned long cur_rate;
struct clk_hw *parent;
struct clk_voter *v = to_clk_voter(hw);
parent = clk_hw_get_parent(hw);
if (v->is_branch) {
v->enabled = true;
return ret;
}
/*
* Increase the rate if this clock is voting for a higher rate
* than the current rate.
*/
cur_rate = clk_aggregate_rate(hw, parent->core);
if (v->rate > cur_rate) {
ret = clk_set_rate(parent->clk, v->rate);
if (ret)
return ret;
}
v->enabled = true;
return ret;
}
static void voter_clk_unprepare(struct clk_hw *hw)
{
unsigned long cur_rate, new_rate;
struct clk_hw *parent;
struct clk_voter *v = to_clk_voter(hw);
parent = clk_hw_get_parent(hw);
/*
* Decrease the rate if this clock was the only one voting for
* the highest rate.
*/
v->enabled = false;
if (v->is_branch)
return;
new_rate = clk_aggregate_rate(hw, parent->core);
cur_rate = max(new_rate, v->rate);
if (new_rate < cur_rate)
clk_set_rate(parent->clk, new_rate);
}
static int voter_clk_is_enabled(struct clk_hw *hw)
{
struct clk_voter *v = to_clk_voter(hw);
return v->enabled;
}
static long voter_clk_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
return clk_hw_round_rate(clk_hw_get_parent(hw), rate);
}
static unsigned long voter_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_voter *v = to_clk_voter(hw);
return v->rate;
}
struct clk_ops clk_ops_voter = {
.prepare = voter_clk_prepare,
.unprepare = voter_clk_unprepare,
.set_rate = voter_clk_set_rate,
.is_enabled = voter_clk_is_enabled,
.round_rate = voter_clk_round_rate,
.recalc_rate = voter_clk_recalc_rate,
};

View file

@ -0,0 +1,50 @@
/*
* 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 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __QCOM_CLK_VOTER_H__
#define __QCOM_CLK_VOTER_H__
#include <linux/clk-provider.h>
#include <linux/platform_device.h>
struct clk_voter {
int is_branch;
bool enabled;
struct clk_hw hw;
unsigned long rate;
};
extern struct clk_ops clk_ops_voter;
#define to_clk_voter(_hw) container_of(_hw, struct clk_voter, hw)
#define __DEFINE_CLK_VOTER(clk_name, _parent_name, _default_rate, _is_branch) \
struct clk_voter clk_name = { \
.is_branch = (_is_branch), \
.rate = _default_rate, \
.hw.init = &(struct clk_init_data){ \
.ops = &clk_ops_voter, \
.name = #clk_name, \
.parent_names = (const char *[]){ #_parent_name }, \
.num_parents = 1, \
}, \
}
#define DEFINE_CLK_VOTER(clk_name, _parent_name, _default_rate) \
__DEFINE_CLK_VOTER(clk_name, _parent_name, _default_rate, 0)
#define DEFINE_CLK_BRANCH_VOTER(clk_name, _parent_name) \
__DEFINE_CLK_VOTER(clk_name, _parent_name, 1000, 1)
#endif

View file

@ -669,6 +669,9 @@ void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent);
void clk_hw_set_rate_range(struct clk_hw *hw, unsigned long min_rate,
unsigned long max_rate);
unsigned long clk_aggregate_rate(struct clk_hw *hw,
const struct clk_core *parent);
static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src)
{
dst->clk = src->clk;