msm: clock-local2: Introduce a static mux clock

MSM clock controllers contain static muxes that select
between clock sources without placing constraints on the
state of the sources. Introduce a clock and ops that provide
functionality to configure and control these static muxes.

Change-Id: Iaf0d6975b1a4c3db22923850196a0e603e5cb5a6
Signed-off-by: Vikram Mulukutla <markivx@codeaurora.org>
diff --git a/arch/arm/mach-msm/clock-local2.c b/arch/arm/mach-msm/clock-local2.c
index 0d1104e..f6c11b0 100644
--- a/arch/arm/mach-msm/clock-local2.c
+++ b/arch/arm/mach-msm/clock-local2.c
@@ -476,10 +476,7 @@
 	if (branch->max_div)
 		return branch->c.rate;
 
-	if (!branch->has_sibling)
-		return clk_get_rate(c->parent);
-
-	return 0;
+	return clk_get_rate(c->parent);
 }
 
 static int branch_clk_list_rate(struct clk *c, unsigned n)
@@ -812,6 +809,155 @@
 }
 
 
+#define ENABLE_REG(x)	(*(x)->base + (x)->enable_reg)
+#define SELECT_REG(x)	(*(x)->base + (x)->select_reg)
+
+/*
+ * mux clock functions
+ */
+static void mux_clk_halt_check(void)
+{
+	/* Ensure that the delay starts after the mux disable/enable. */
+	mb();
+	udelay(HALT_CHECK_DELAY_US);
+}
+
+static int mux_clk_enable(struct clk *c)
+{
+	unsigned long flags;
+	u32 regval;
+	struct mux_clk *mux = to_mux_clk(c);
+
+	spin_lock_irqsave(&local_clock_reg_lock, flags);
+	regval = readl_relaxed(ENABLE_REG(mux));
+	regval |= mux->enable_mask;
+	writel_relaxed(regval, ENABLE_REG(mux));
+	spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+	/* Wait for clock to enable before continuing. */
+	mux_clk_halt_check();
+
+	return 0;
+}
+
+static void mux_clk_disable(struct clk *c)
+{
+	unsigned long flags;
+	struct mux_clk *mux = to_mux_clk(c);
+	u32 regval;
+
+	spin_lock_irqsave(&local_clock_reg_lock, flags);
+	regval = readl_relaxed(ENABLE_REG(mux));
+	regval &= ~mux->enable_mask;
+	writel_relaxed(regval, ENABLE_REG(mux));
+	spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+
+	/* Wait for clock to disable before continuing. */
+	mux_clk_halt_check();
+}
+
+static int mux_source_switch(struct mux_clk *mux, struct mux_source *dest)
+{
+	unsigned long flags;
+	u32 regval;
+	int ret = 0;
+
+	ret = __clk_pre_reparent(&mux->c, dest->clk, &flags);
+	if (ret)
+		goto out;
+
+	regval = readl_relaxed(SELECT_REG(mux));
+	regval &= ~mux->select_mask;
+	regval |= dest->select_val;
+	writel_relaxed(regval, SELECT_REG(mux));
+
+	/* Make sure switch request goes through before proceeding. */
+	mb();
+
+	__clk_post_reparent(&mux->c, mux->c.parent, &flags);
+out:
+	return ret;
+}
+
+static int mux_clk_set_parent(struct clk *c, struct clk *parent)
+{
+	struct mux_clk *mux = to_mux_clk(c);
+	struct mux_source *dest = NULL;
+	int ret;
+
+	if (!mux->sources || !parent)
+		return -EPERM;
+
+	dest = mux->sources;
+
+	while (dest->clk) {
+		if (dest->clk == parent)
+			break;
+		dest++;
+	}
+
+	if (!dest->clk)
+		return -EPERM;
+
+	ret = mux_source_switch(mux, dest);
+	if (ret)
+		return ret;
+
+	mux->c.rate = clk_get_rate(dest->clk);
+
+	return 0;
+}
+
+static enum handoff mux_clk_handoff(struct clk *c)
+{
+	struct mux_clk *mux = to_mux_clk(c);
+	u32 mask = mux->enable_mask;
+	u32 regval = readl_relaxed(ENABLE_REG(mux));
+
+	c->rate = clk_get_rate(c->parent);
+
+	if (mask == (regval & mask))
+		return HANDOFF_ENABLED_CLK;
+
+	return HANDOFF_DISABLED_CLK;
+}
+
+static struct clk *mux_clk_get_parent(struct clk *c)
+{
+	struct mux_clk *mux = to_mux_clk(c);
+	struct mux_source *parent = NULL;
+	u32 regval = readl_relaxed(SELECT_REG(mux));
+
+	if (!mux->sources)
+		return ERR_PTR(-EPERM);
+
+	parent = mux->sources;
+
+	while (parent->clk) {
+		if ((regval & mux->select_mask) == parent->select_val)
+			return parent->clk;
+
+		parent++;
+	}
+
+	return ERR_PTR(-EPERM);
+}
+
+static int mux_clk_list_rate(struct clk *c, unsigned n)
+{
+	struct mux_clk *mux = to_mux_clk(c);
+	int i;
+
+	for (i = 0; i < n; i++)
+		if (!mux->sources[i].clk)
+			break;
+
+	if (!mux->sources[i].clk)
+		return -ENXIO;
+
+	return clk_get_rate(mux->sources[i].clk);
+}
+
 struct clk_ops clk_ops_empty;
 
 struct clk_ops clk_ops_rcg = {
@@ -875,3 +1021,14 @@
 	.reset = local_vote_clk_reset,
 	.handoff = local_vote_clk_handoff,
 };
+
+struct clk_ops clk_ops_mux = {
+	.enable = mux_clk_enable,
+	.disable = mux_clk_disable,
+	.set_parent = mux_clk_set_parent,
+	.get_parent = mux_clk_get_parent,
+	.handoff = mux_clk_handoff,
+	.list_rate = mux_clk_list_rate,
+};
+
+
diff --git a/arch/arm/mach-msm/clock-local2.h b/arch/arm/mach-msm/clock-local2.h
index 7ac7bd3..7882edb 100644
--- a/arch/arm/mach-msm/clock-local2.h
+++ b/arch/arm/mach-msm/clock-local2.h
@@ -157,6 +157,38 @@
 	return container_of(clk, struct measure_clk, c);
 }
 
+struct mux_source {
+	struct clk *const clk;
+	const u32 select_val;
+};
+
+/**
+ * struct mux_clk - branch clock
+ * @c: clk
+ * @enable_reg: register that contains the enable bit(s) for the mux
+ * @select_reg: register that contains the source selection bits for the mux
+ * @enable_mask: mask that enables the mux
+ * @select_mask: mask for the source selection bits
+ * @sources: list of mux sources
+ * @base: pointer to base address of ioremapped registers.
+ */
+struct mux_clk {
+	struct clk c;
+	const u32 enable_reg;
+	const u32 select_reg;
+	const u32 enable_mask;
+	const u32 select_mask;
+
+	struct mux_source *sources;
+
+	void *const __iomem *base;
+};
+
+static inline struct mux_clk *to_mux_clk(struct clk *clk)
+{
+	return container_of(clk, struct mux_clk, c);
+}
+
 /*
  * Generic set-rate implementations
  */
@@ -168,6 +200,7 @@
  */
 extern spinlock_t local_clock_reg_lock;
 
+extern struct clk_ops clk_ops_mux;
 extern struct clk_ops clk_ops_empty;
 extern struct clk_ops clk_ops_rcg;
 extern struct clk_ops clk_ops_rcg_mnd;