msm: clock: Move to per-clock voltage class support in the top-level driver

Generalize the voltage voting logic so that it lives in the
top-level clock.c driver. This has a couple of advantages:
 - Voltage voting is no longer restricted to clock-local drivers
   and need not be duplicated for different driver types.
 - Different clocks may specify requirements on different power rails
   by using different voltage_class implementations.
 - Fmax data (maximum frequency allowed at a given voltage level)
   is separated from the frequency tables and captured as a
   property of each clock, so that difference SoCs may share the
   same frequency tables even if their voltage requirements differ.
 - The per-clock lock can be managed entirely at the clock.c level

Change-Id: I2bbd16096c14cedefa2b7fdf77d9f20d842b1e4d
Signed-off-by: Matt Wagantall <mattw@codeaurora.org>
diff --git a/arch/arm/mach-msm/clock.c b/arch/arm/mach-msm/clock.c
index 58a64a3..de89382 100644
--- a/arch/arm/mach-msm/clock.c
+++ b/arch/arm/mach-msm/clock.c
@@ -24,13 +24,116 @@
 
 #include "clock.h"
 
+/* Find the voltage level required for a given rate. */
+static int find_vdd_level(struct clk *clk, unsigned long rate)
+{
+	int level;
+
+	for (level = 0; level < ARRAY_SIZE(clk->fmax); level++)
+		if (rate <= clk->fmax[level])
+			break;
+
+	if (level == ARRAY_SIZE(clk->fmax)) {
+		pr_err("Rate %lu for %s is greater than highest Fmax\n", rate,
+			clk->dbg_name);
+		return -EINVAL;
+	}
+
+	return level;
+}
+
+/* Update voltage level given the current votes. */
+static int update_vdd(struct clk_vdd_class *vdd_class)
+{
+	int level, rc;
+
+	for (level = ARRAY_SIZE(vdd_class->level_votes)-1; level > 0; level--)
+		if (vdd_class->level_votes[level])
+			break;
+
+	if (level == vdd_class->cur_level)
+		return 0;
+
+	rc = vdd_class->set_vdd(vdd_class, level);
+	if (!rc)
+		vdd_class->cur_level = level;
+
+	return rc;
+}
+
+/* Vote for a voltage level. */
+int vote_vdd_level(struct clk_vdd_class *vdd_class, int level)
+{
+	unsigned long flags;
+	int rc;
+
+	spin_lock_irqsave(&vdd_class->lock, flags);
+	vdd_class->level_votes[level]++;
+	rc = update_vdd(vdd_class);
+	if (rc)
+		vdd_class->level_votes[level]--;
+	spin_unlock_irqrestore(&vdd_class->lock, flags);
+
+	return rc;
+}
+
+/* Remove vote for a voltage level. */
+int unvote_vdd_level(struct clk_vdd_class *vdd_class, int level)
+{
+	unsigned long flags;
+	int rc = 0;
+
+	spin_lock_irqsave(&vdd_class->lock, flags);
+	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 = update_vdd(vdd_class);
+	if (rc)
+		vdd_class->level_votes[level]++;
+out:
+	spin_unlock_irqrestore(&vdd_class->lock, flags);
+	return rc;
+}
+
+/* Vote for a voltage level corresponding to a clock's rate. */
+static int vote_rate_vdd(struct clk *clk, unsigned long rate)
+{
+	int level;
+
+	if (!clk->vdd_class)
+		return 0;
+
+	level = find_vdd_level(clk, rate);
+	if (level < 0)
+		return level;
+
+	return vote_vdd_level(clk->vdd_class, level);
+}
+
+/* Remove vote for a voltage level corresponding to a clock's rate. */
+static void unvote_rate_vdd(struct clk *clk, unsigned long rate)
+{
+	int level;
+
+	if (!clk->vdd_class)
+		return;
+
+	level = find_vdd_level(clk, rate);
+	if (level < 0)
+		return;
+
+	unvote_vdd_level(clk->vdd_class, level);
+}
+
 /*
  * Standard clock functions defined in include/linux/clk.h
  */
 int clk_enable(struct clk *clk)
 {
 	int ret = 0;
-	unsigned long flags;
+	unsigned long flags, rate;
 	struct clk *parent;
 
 	if (!clk)
@@ -39,22 +142,22 @@
 	spin_lock_irqsave(&clk->lock, flags);
 	if (clk->count == 0) {
 		parent = clk_get_parent(clk);
+		rate = clk_get_rate(clk);
+
 		ret = clk_enable(parent);
 		if (ret)
-			goto out;
+			goto err_enable_parent;
 		ret = clk_enable(clk->depends);
-		if (ret) {
-			clk_disable(parent);
-			goto out;
-		}
+		if (ret)
+			goto err_enable_depends;
 
+		ret = vote_rate_vdd(clk, rate);
+		if (ret)
+			goto err_vote_vdd;
 		if (clk->ops->enable)
 			ret = clk->ops->enable(clk);
-		if (ret) {
-			clk_disable(clk->depends);
-			clk_disable(parent);
-			goto out;
-		}
+		if (ret)
+			goto err_enable_clock;
 	} else if (clk->flags & CLKFLAG_HANDOFF_RATE) {
 		/*
 		 * The clock was already enabled by handoff code so there is no
@@ -69,6 +172,16 @@
 out:
 	spin_unlock_irqrestore(&clk->lock, flags);
 
+	return 0;
+
+err_enable_clock:
+	unvote_rate_vdd(clk, rate);
+err_vote_vdd:
+	clk_disable(clk->depends);
+err_enable_depends:
+	clk_disable(parent);
+err_enable_parent:
+	spin_unlock_irqrestore(&clk->lock, flags);
 	return ret;
 }
 EXPORT_SYMBOL(clk_enable);
@@ -76,7 +189,6 @@
 void clk_disable(struct clk *clk)
 {
 	unsigned long flags;
-	struct clk *parent;
 
 	if (!clk)
 		return;
@@ -85,10 +197,13 @@
 	if (WARN(clk->count == 0, "%s is unbalanced", clk->dbg_name))
 		goto out;
 	if (clk->count == 1) {
+		struct clk *parent = clk_get_parent(clk);
+		unsigned long rate = clk_get_rate(clk);
+
 		if (clk->ops->disable)
 			clk->ops->disable(clk);
+		unvote_rate_vdd(clk, rate);
 		clk_disable(clk->depends);
-		parent = clk_get_parent(clk);
 		clk_disable(parent);
 	}
 	clk->count--;
@@ -117,10 +232,35 @@
 
 int clk_set_rate(struct clk *clk, unsigned long rate)
 {
+	unsigned long start_rate, flags;
+	int rc;
+
 	if (!clk->ops->set_rate)
 		return -ENOSYS;
 
-	return clk->ops->set_rate(clk, rate);
+	spin_lock_irqsave(&clk->lock, flags);
+	if (clk->count) {
+		start_rate = clk_get_rate(clk);
+		/* Enforce vdd requirements for target frequency. */
+		rc = vote_rate_vdd(clk, rate);
+		if (rc)
+			goto err_vote_vdd;
+		rc = clk->ops->set_rate(clk, rate);
+		if (rc)
+			goto err_set_rate;
+		/* Release vdd requirements for starting frequency. */
+		unvote_rate_vdd(clk, start_rate);
+	} else {
+		rc = clk->ops->set_rate(clk, rate);
+	}
+	spin_unlock_irqrestore(&clk->lock, flags);
+	return rc;
+
+err_set_rate:
+	unvote_rate_vdd(clk, rate);
+err_vote_vdd:
+	spin_unlock_irqrestore(&clk->lock, flags);
+	return rc;
 }
 EXPORT_SYMBOL(clk_set_rate);