summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/clk/clk.c20
-rw-r--r--drivers/clk/qcom/Makefile2
-rw-r--r--drivers/clk/qcom/clk-voter.c133
-rw-r--r--drivers/clk/qcom/clk-voter.h50
-rw-r--r--include/linux/clk-provider.h3
5 files changed, 207 insertions, 1 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index ed763c7e98fc..1c625764133d 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -547,6 +547,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
* complex clock that may combine a mux with other operations.
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 7ee0294e9dc7..adebefd63e71 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -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
diff --git a/drivers/clk/qcom/clk-voter.c b/drivers/clk/qcom/clk-voter.c
new file mode 100644
index 000000000000..d3409b9e6b64
--- /dev/null
+++ b/drivers/clk/qcom/clk-voter.c
@@ -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,
+};
diff --git a/drivers/clk/qcom/clk-voter.h b/drivers/clk/qcom/clk-voter.h
new file mode 100644
index 000000000000..27092ae7d131
--- /dev/null
+++ b/drivers/clk/qcom/clk-voter.h
@@ -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
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 23026ba6ff25..0de2d0c780d7 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -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;