msm: clock: Add hardware gating support to branch clocks

Add support for hardware clock gating on branch clocks. Do this
in a few steps:

 o At clock registration time, check to see if the branch is not
   in hardware gated mode and fix up the branch data structure
   as appropriate

 o When a clk_reset() is asserted take the branch out of
   hardware control so the reset can propagate if the clock
   is enabled

 o When a clk_reset() is deasserted put the branch back into
   hardware control

Add three new ops to the clk_ops structure to support the above.

	void (*enable_hwcg)(struct clk *clk);
	void (*disable_hwcg)(struct clk *clk);
	int (*in_hwcg_mode)(struct clk *clk);

{enable,disable}_hwcg() should enable and disable hardware clock
gating if possible. in_hwcg_mode() should return either a
non-zero value or a zero indicating whether the clock is
currently in hardware gating mode or not in hardware gating mode
respectively.

Use in_hwcg_mode() to detect when to skip the halt checks for a
branch and when to disable/enable hardware gating mode while
measuring clocks. This is particularly important for measurement
because we don't want to put a clock into hardware gating mode
accidentally after we measure the clock if the clock wasn't in
hardware gating mode to begin with.

Also expose a debugfs node indicating whether or not this clock
supports hardware gating. This should aid in debugging problems
due to hardware gating.

Change-Id: Id9b2d83adcc6dabe2544f48ef0b4d752d9a5c5c1
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
diff --git a/arch/arm/mach-msm/clock-local.c b/arch/arm/mach-msm/clock-local.c
index fb76f07..2a1c013 100644
--- a/arch/arm/mach-msm/clock-local.c
+++ b/arch/arm/mach-msm/clock-local.c
@@ -273,6 +273,14 @@
 	return invert ? !status_bit : status_bit;
 }
 
+int branch_in_hwcg_mode(const struct branch *b)
+{
+	if (!b->hwcg_mask)
+		return 0;
+
+	return !!(readl_relaxed(b->hwcg_reg) & b->hwcg_mask);
+}
+
 void __branch_clk_enable_reg(const struct branch *clk, const char *name)
 {
 	u32 reg_val;
@@ -291,6 +299,10 @@
 	 */
 	mb();
 
+	/* Skip checking halt bit if the clock is in hardware gated mode */
+	if (branch_in_hwcg_mode(clk))
+		return;
+
 	/* Wait for clock to enable before returning. */
 	if (clk->halt_check == DELAY)
 		udelay(HALT_CHECK_DELAY_US);
@@ -362,6 +374,10 @@
 	 */
 	mb();
 
+	/* Skip checking halt bit if the clock is in hardware gated mode */
+	if (branch_in_hwcg_mode(clk))
+		return reg_val;
+
 	/* Wait for clock to disable before continuing. */
 	if (clk->halt_check == DELAY || clk->halt_check == ENABLE_VOTED
 				     || clk->halt_check == HALT_VOTED)
@@ -584,12 +600,32 @@
 	return to_rcg_clk(clk)->current_freq->src_clk;
 }
 
+/* Disable hw clock gating if not set at boot */
+static void branch_handoff(struct branch *clk, struct clk *c)
+{
+	if (!branch_in_hwcg_mode(clk)) {
+		clk->hwcg_mask = 0;
+		c->flags &= ~CLKFLAG_HWCG;
+	} else {
+		c->flags |= CLKFLAG_HWCG;
+	}
+}
+
+int branch_clk_handoff(struct clk *c)
+{
+	struct branch_clk *clk = to_branch_clk(c);
+	branch_handoff(&clk->b, &clk->c);
+	return 0;
+}
+
 int rcg_clk_handoff(struct clk *c)
 {
 	struct rcg_clk *clk = to_rcg_clk(c);
 	uint32_t ctl_val, ns_val, md_val, ns_mask;
 	struct clk_freq_tbl *freq;
 
+	branch_handoff(&clk->b, &clk->c);
+
 	ctl_val = readl_relaxed(clk->b.ctl_reg);
 	if (!(ctl_val & clk->root_en_mask))
 		return 0;
@@ -859,35 +895,100 @@
 	return branch->enabled;
 }
 
-int branch_reset(struct branch *clk, enum clk_reset_action action)
+static void branch_enable_hwcg(struct branch *b)
+{
+	unsigned long flags;
+	u32 reg_val;
+
+	spin_lock_irqsave(&local_clock_reg_lock, flags);
+	reg_val = readl_relaxed(b->hwcg_reg);
+	reg_val |= b->hwcg_mask;
+	writel_relaxed(reg_val, b->hwcg_reg);
+	spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+static void branch_disable_hwcg(struct branch *b)
+{
+	unsigned long flags;
+	u32 reg_val;
+
+	spin_lock_irqsave(&local_clock_reg_lock, flags);
+	reg_val = readl_relaxed(b->hwcg_reg);
+	reg_val &= ~b->hwcg_mask;
+	writel_relaxed(reg_val, b->hwcg_reg);
+	spin_unlock_irqrestore(&local_clock_reg_lock, flags);
+}
+
+void branch_clk_enable_hwcg(struct clk *clk)
+{
+	struct branch_clk *branch = to_branch_clk(clk);
+	branch_enable_hwcg(&branch->b);
+}
+
+void branch_clk_disable_hwcg(struct clk *clk)
+{
+	struct branch_clk *branch = to_branch_clk(clk);
+	branch_disable_hwcg(&branch->b);
+}
+
+int branch_clk_in_hwcg_mode(struct clk *c)
+{
+	struct branch_clk *clk = to_branch_clk(c);
+	return branch_in_hwcg_mode(&clk->b);
+}
+
+void rcg_clk_enable_hwcg(struct clk *clk)
+{
+	struct rcg_clk *rcg = to_rcg_clk(clk);
+	branch_enable_hwcg(&rcg->b);
+}
+
+void rcg_clk_disable_hwcg(struct clk *clk)
+{
+	struct rcg_clk *rcg = to_rcg_clk(clk);
+	branch_disable_hwcg(&rcg->b);
+}
+
+int rcg_clk_in_hwcg_mode(struct clk *c)
+{
+	struct rcg_clk *clk = to_rcg_clk(c);
+	return branch_in_hwcg_mode(&clk->b);
+}
+
+int branch_reset(struct branch *b, enum clk_reset_action action)
 {
 	int ret = 0;
 	u32 reg_val;
 	unsigned long flags;
 
-	if (!clk->reset_reg)
+	if (!b->reset_reg)
 		return -EPERM;
 
-	spin_lock_irqsave(&local_clock_reg_lock, flags);
+	/* Disable hw gating when asserting a reset */
+	if (b->hwcg_mask && action == CLK_RESET_ASSERT)
+		branch_disable_hwcg(b);
 
-	reg_val = readl_relaxed(clk->reset_reg);
+	spin_lock_irqsave(&local_clock_reg_lock, flags);
+	/* Assert/Deassert reset */
+	reg_val = readl_relaxed(b->reset_reg);
 	switch (action) {
 	case CLK_RESET_ASSERT:
-		reg_val |= clk->reset_mask;
+		reg_val |= b->reset_mask;
 		break;
 	case CLK_RESET_DEASSERT:
-		reg_val &= ~(clk->reset_mask);
+		reg_val &= ~b->reset_mask;
 		break;
 	default:
 		ret = -EINVAL;
 	}
-	writel_relaxed(reg_val, clk->reset_reg);
-
+	writel_relaxed(reg_val, b->reset_reg);
 	spin_unlock_irqrestore(&local_clock_reg_lock, flags);
 
+	/* Enable hw gating when deasserting a reset */
+	if (b->hwcg_mask && action == CLK_RESET_DEASSERT)
+		branch_enable_hwcg(b);
 	/* Make sure write is issued before returning. */
 	mb();
-
 	return ret;
 }
 
@@ -972,6 +1073,8 @@
 	struct cdiv_clk *clk = to_cdiv_clk(c);
 	u32 reg_val;
 
+	branch_handoff(&clk->b, &clk->c);
+
 	reg_val = readl_relaxed(clk->ns_reg);
 	if (reg_val & clk->ext_mask) {
 		clk->cur_div = 0;
@@ -983,9 +1086,30 @@
 	return 0;
 }
 
+static void cdiv_clk_enable_hwcg(struct clk *c)
+{
+	struct cdiv_clk *clk = to_cdiv_clk(c);
+	branch_enable_hwcg(&clk->b);
+}
+
+static void cdiv_clk_disable_hwcg(struct clk *c)
+{
+	struct cdiv_clk *clk = to_cdiv_clk(c);
+	branch_disable_hwcg(&clk->b);
+}
+
+static int cdiv_clk_in_hwcg_mode(struct clk *c)
+{
+	struct cdiv_clk *clk = to_cdiv_clk(c);
+	return branch_in_hwcg_mode(&clk->b);
+}
+
 struct clk_ops clk_ops_cdiv = {
 	.enable = cdiv_clk_enable,
 	.disable = cdiv_clk_disable,
+	.in_hwcg_mode = cdiv_clk_in_hwcg_mode,
+	.enable_hwcg = cdiv_clk_enable_hwcg,
+	.disable_hwcg = cdiv_clk_disable_hwcg,
 	.auto_off = cdiv_clk_disable,
 	.handoff = cdiv_clk_handoff,
 	.set_rate = cdiv_clk_set_rate,