[RFC PATCH 09/34] msm: clock: Implement rate voting

From: David Brown
Date: Wed Nov 02 2011 - 14:43:57 EST


From: Stephen Boyd <sboyd@xxxxxxxxxxxxxx>

Some clocks have multiple consumers where each consumer requires
a minimum rate. Implement a sub driver to aggregate
clk_set_rate() calls from each consumer and call clk_set_rate()
on the parent clock when the minimum requested rate of all the
voters changes.

Signed-off-by: Stephen Boyd <sboyd@xxxxxxxxxxxxxx>
Signed-off-by: David Brown <davidb@xxxxxxxxxxxxxx>
---
arch/arm/mach-msm/Makefile | 1 +
arch/arm/mach-msm/clock-pcom.h | 2 +-
arch/arm/mach-msm/clock-voter.c | 187 +++++++++++++++++++++++++++++++++++++++
arch/arm/mach-msm/clock-voter.h | 41 +++++++++
arch/arm/mach-msm/clock.c | 18 ++---
arch/arm/mach-msm/clock.h | 9 ++
6 files changed, 245 insertions(+), 13 deletions(-)
create mode 100644 arch/arm/mach-msm/clock-voter.c
create mode 100644 arch/arm/mach-msm/clock-voter.h

diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index 4285dfd..5318553 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -1,5 +1,6 @@
obj-y += io.o idle.o timer.o
obj-y += clock.o
+obj-y += clock-voter.o
obj-$(CONFIG_DEBUG_FS) += clock-debug.o

obj-$(CONFIG_MSM_VIC) += irq-vic.o
diff --git a/arch/arm/mach-msm/clock-pcom.h b/arch/arm/mach-msm/clock-pcom.h
index 955c917..a5ac74d 100644
--- a/arch/arm/mach-msm/clock-pcom.h
+++ b/arch/arm/mach-msm/clock-pcom.h
@@ -145,7 +145,7 @@ static inline struct pcom_clk *to_pcom_clk(struct clk *clk)
.ops = &clk_ops_pcom, \
.flags = clk_flags, \
.dbg_name = #clk_id, \
- .lock = __SPIN_LOCK_UNLOCKED(clk_name.c), \
+ CLK_INIT(clk_name.c), \
}, \
}

diff --git a/arch/arm/mach-msm/clock-voter.c b/arch/arm/mach-msm/clock-voter.c
new file mode 100644
index 0000000..6da4384
--- /dev/null
+++ b/arch/arm/mach-msm/clock-voter.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2010-2011, Code Aurora Forum. 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/err.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+
+#include "clock.h"
+#include "clock-voter.h"
+
+static DEFINE_SPINLOCK(voter_clk_lock);
+
+/* Aggregate the rate of clocks that are currently on. */
+static unsigned voter_clk_aggregate_rate(const struct clk *parent)
+{
+ struct clk *clk;
+ unsigned rate = 0;
+
+ list_for_each_entry(clk, &parent->children, siblings) {
+ struct clk_voter *v = to_clk_voter(clk);
+ if (v->enabled)
+ rate = max(v->rate, rate);
+ }
+ return rate;
+}
+
+static int voter_clk_set_rate(struct clk *clk, unsigned rate)
+{
+ int ret = 0;
+ unsigned long flags;
+ struct clk *clkp;
+ struct clk_voter *clkh, *v = to_clk_voter(clk);
+ unsigned cur_rate, new_rate, other_rate = 0;
+
+ spin_lock_irqsave(&voter_clk_lock, flags);
+
+ if (v->enabled) {
+ struct clk *parent = v->parent;
+
+ /*
+ * Get the aggregate rate without this clock's vote and update
+ * if the new rate is different than the current rate
+ */
+ list_for_each_entry(clkp, &parent->children, siblings) {
+ clkh = to_clk_voter(clkp);
+ if (clkh->enabled && clkh != v)
+ other_rate = max(clkh->rate, other_rate);
+ }
+
+ cur_rate = max(other_rate, v->rate);
+ new_rate = max(other_rate, rate);
+
+ if (new_rate != cur_rate) {
+ ret = clk_set_min_rate(parent, new_rate);
+ if (ret)
+ goto unlock;
+ }
+ }
+ v->rate = rate;
+unlock:
+ spin_unlock_irqrestore(&voter_clk_lock, flags);
+
+ return ret;
+}
+
+static int voter_clk_enable(struct clk *clk)
+{
+ int ret;
+ unsigned long flags;
+ unsigned cur_rate;
+ struct clk *parent;
+ struct clk_voter *v = to_clk_voter(clk);
+
+ spin_lock_irqsave(&voter_clk_lock, flags);
+ parent = v->parent;
+
+ /*
+ * Increase the rate if this clock is voting for a higher rate
+ * than the current rate.
+ */
+ cur_rate = voter_clk_aggregate_rate(parent);
+ if (v->rate > cur_rate) {
+ ret = clk_set_min_rate(parent, v->rate);
+ if (ret)
+ goto out;
+ }
+ v->enabled = true;
+out:
+ spin_unlock_irqrestore(&voter_clk_lock, flags);
+
+ return ret;
+}
+
+static void voter_clk_disable(struct clk *clk)
+{
+ unsigned long flags;
+ struct clk *parent;
+ struct clk_voter *v = to_clk_voter(clk);
+ unsigned cur_rate, new_rate;
+
+ spin_lock_irqsave(&voter_clk_lock, flags);
+ parent = v->parent;
+
+ /*
+ * Decrease the rate if this clock was the only one voting for
+ * the highest rate.
+ */
+ v->enabled = false;
+ new_rate = voter_clk_aggregate_rate(parent);
+ cur_rate = max(new_rate, v->rate);
+
+ if (new_rate < cur_rate)
+ clk_set_min_rate(parent, new_rate);
+
+ spin_unlock_irqrestore(&voter_clk_lock, flags);
+}
+
+static unsigned voter_clk_get_rate(struct clk *clk)
+{
+ unsigned rate;
+ unsigned long flags;
+ struct clk_voter *v = to_clk_voter(clk);
+
+ spin_lock_irqsave(&voter_clk_lock, flags);
+ rate = v->rate;
+ spin_unlock_irqrestore(&voter_clk_lock, flags);
+
+ return rate;
+}
+
+static unsigned voter_clk_is_enabled(struct clk *clk)
+{
+ struct clk_voter *v = to_clk_voter(clk);
+ return v->enabled;
+}
+
+static long voter_clk_round_rate(struct clk *clk, unsigned rate)
+{
+ struct clk_voter *v = to_clk_voter(clk);
+ return clk_round_rate(v->parent, rate);
+}
+
+static int voter_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&voter_clk_lock, flags);
+ if (list_empty(&clk->siblings))
+ list_add(&clk->siblings, &parent->children);
+ spin_unlock_irqrestore(&voter_clk_lock, flags);
+
+ return 0;
+}
+
+static struct clk *voter_clk_get_parent(struct clk *clk)
+{
+ struct clk_voter *v = to_clk_voter(clk);
+ return v->parent;
+}
+
+static bool voter_clk_is_local(struct clk *clk)
+{
+ return true;
+}
+
+struct clk_ops clk_ops_voter = {
+ .enable = voter_clk_enable,
+ .disable = voter_clk_disable,
+ .set_rate = voter_clk_set_rate,
+ .set_min_rate = voter_clk_set_rate,
+ .get_rate = voter_clk_get_rate,
+ .is_enabled = voter_clk_is_enabled,
+ .round_rate = voter_clk_round_rate,
+ .set_parent = voter_clk_set_parent,
+ .get_parent = voter_clk_get_parent,
+ .is_local = voter_clk_is_local,
+};
diff --git a/arch/arm/mach-msm/clock-voter.h b/arch/arm/mach-msm/clock-voter.h
new file mode 100644
index 0000000..170ba67
--- /dev/null
+++ b/arch/arm/mach-msm/clock-voter.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. 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 __ARCH_ARM_MACH_MSM_CLOCK_VOTER_H
+#define __ARCH_ARM_MACH_MSM_CLOCK_VOTER_H
+
+struct clk_ops;
+extern struct clk_ops clk_ops_voter;
+
+struct clk_voter {
+ bool enabled;
+ unsigned rate;
+ struct clk *parent;
+ struct clk c;
+};
+
+static inline struct clk_voter *to_clk_voter(struct clk *clk)
+{
+ return container_of(clk, struct clk_voter, c);
+}
+
+#define DEFINE_CLK_VOTER(clk_name, _parent) \
+ struct clk_voter clk_name = { \
+ .parent = _parent, \
+ .c = { \
+ .dbg_name = #clk_name, \
+ .ops = &clk_ops_voter, \
+ CLK_INIT(clk_name.c), \
+ }, \
+ }
+
+#endif
diff --git a/arch/arm/mach-msm/clock.c b/arch/arm/mach-msm/clock.c
index 026bdc0..ad55eaa 100644
--- a/arch/arm/mach-msm/clock.c
+++ b/arch/arm/mach-msm/clock.c
@@ -154,12 +154,6 @@ int clk_set_flags(struct clk *clk, unsigned long flags)
}
EXPORT_SYMBOL(clk_set_flags);

-/* EBI1 is the only shared clock that several clients want to vote on as of
- * this commit. If this changes in the future, then it might be better to
- * make clk_min_rate handle the voting or make ebi1_clk_set_min_rate more
- * generic to support different clocks.
- */
-static struct clk *ebi1_clk;
static struct clk_lookup *msm_clocks;
static unsigned msm_num_clocks;

@@ -167,13 +161,13 @@ void __init msm_clock_init(struct clk_lookup *clock_tbl, size_t num_clocks)
{
unsigned n;

- clkdev_add_table(clock_tbl, num_clocks);
-
- for (n = 0; n < num_clocks; n++)
- spin_lock_init(&clock_tbl[n].clk->lock);
+ for (n = 0; n < num_clocks; n++) {
+ struct clk *clk = clock_tbl[n].clk;
+ struct clk *parent = clk_get_parent(clk);
+ clk_set_parent(clk, parent);
+ }

- ebi1_clk = clk_get(NULL, "ebi1_clk");
- BUG_ON(ebi1_clk == NULL);
+ clkdev_add_table(clock_tbl, num_clocks);

msm_clocks = clock_tbl;
msm_num_clocks = num_clocks;
diff --git a/arch/arm/mach-msm/clock.h b/arch/arm/mach-msm/clock.h
index 6a7cbca..6aecd95 100644
--- a/arch/arm/mach-msm/clock.h
+++ b/arch/arm/mach-msm/clock.h
@@ -55,9 +55,18 @@ struct clk {
uint32_t flags;
struct clk_ops *ops;
const char *dbg_name;
+
+ struct list_head children;
+ struct list_head siblings;
+
spinlock_t lock;
};

+#define CLK_INIT(name) \
+ .lock = __SPIN_LOCK_UNLOCKED((name).lock), \
+ .children = LIST_HEAD_INIT((name).children), \
+ .siblings = LIST_HEAD_INIT((name).siblings)
+
#define OFF CLKFLAG_AUTO_OFF
#define CLK_MIN CLKFLAG_MIN
#define CLK_MAX CLKFLAG_MAX
--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/