msm: clock-8x60: Allow hand-offs of already-running RCG clocks at boot

Inherit the rate of running rate-settable clocks at boot if the
same rate is supported by the Linux kernel.  These clocks will
still be disabled by the auto-off code in late-init if no driver
explicitly enables them before that.

This feature allows Linux drivers to keep a clock enabled all the
way through boot.  For glitch-free clocks, drivers can set the
rate of the clock to any supported value at boot and enable it
with no interruption of the clock signal.  Non-glitch-free clocks
will still experience some interruption, but this can be avoided
by calling clk_set_rate() with the same rate that was set by the
bootloader (which can be returned by calling clk_get_rate()).

Signed-off-by: Matt Wagantall <mattw@codeaurora.org>
diff --git a/arch/arm/mach-msm/clock-8x60.c b/arch/arm/mach-msm/clock-8x60.c
index de6f79c..44276d8 100644
--- a/arch/arm/mach-msm/clock-8x60.c
+++ b/arch/arm/mach-msm/clock-8x60.c
@@ -522,6 +522,7 @@
 	.enable = rcg_clk_enable,
 	.disable = rcg_clk_disable,
 	.auto_off = rcg_clk_auto_off,
+	.handoff = rcg_clk_handoff,
 	.set_rate = rcg_clk_set_rate,
 	.set_min_rate = rcg_clk_set_min_rate,
 	.get_rate = rcg_clk_get_rate,
diff --git a/arch/arm/mach-msm/clock-local.c b/arch/arm/mach-msm/clock-local.c
index 92132b2..6f29803 100644
--- a/arch/arm/mach-msm/clock-local.c
+++ b/arch/arm/mach-msm/clock-local.c
@@ -721,6 +721,46 @@
 	return to_rcg_clk(clk)->current_freq->src_clk;
 }
 
+void 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;
+
+	ctl_val = readl_relaxed(clk->b.ctl_reg);
+	if (!(ctl_val & clk->root_en_mask))
+		return;
+
+	if (clk->bank_masks) {
+		const struct bank_mask_info *bank_info;
+		if (!(ctl_val & clk->bank_masks->bank_sel_mask))
+			bank_info = &clk->bank_masks->bank0_mask;
+		else
+			bank_info = &clk->bank_masks->bank1_mask;
+
+		ns_mask = bank_info->ns_mask;
+		md_val = readl_relaxed(bank_info->md_reg);
+	} else {
+		ns_mask = clk->ns_mask;
+		md_val = clk->md_reg ? readl_relaxed(clk->md_reg) : 0;
+	}
+
+	ns_val = readl_relaxed(clk->ns_reg) & ns_mask;
+	for (freq = clk->freq_tbl; freq->freq_hz != FREQ_END; freq++) {
+		if ((freq->ns_val & ns_mask) == ns_val &&
+		    (freq->mnd_en_mask || freq->md_val == md_val)) {
+			pr_info("%s rate=%d\n", clk->c.dbg_name, freq->freq_hz);
+			break;
+		}
+	}
+	if (freq->freq_hz == FREQ_END)
+		return;
+
+	clk->current_freq = freq;
+	c->flags |= CLKFLAG_HANDOFF_RATE;
+	clk_enable(c);
+}
+
 static int pll_vote_clk_enable(struct clk *clk)
 {
 	u32 ena;
diff --git a/arch/arm/mach-msm/clock-local.h b/arch/arm/mach-msm/clock-local.h
index c38a5f2..2455fbf 100644
--- a/arch/arm/mach-msm/clock-local.h
+++ b/arch/arm/mach-msm/clock-local.h
@@ -152,6 +152,7 @@
 int rcg_clk_is_enabled(struct clk *clk);
 long rcg_clk_round_rate(struct clk *clk, unsigned rate);
 struct clk *rcg_clk_get_parent(struct clk *c);
+void rcg_clk_handoff(struct clk *c);
 
 /*
  * SYS_VDD voltage levels
diff --git a/arch/arm/mach-msm/clock.c b/arch/arm/mach-msm/clock.c
index c145240..a29e9cd 100644
--- a/arch/arm/mach-msm/clock.c
+++ b/arch/arm/mach-msm/clock.c
@@ -49,6 +49,15 @@
 			clk_disable(parent);
 			goto out;
 		}
+	} else if (clk->flags & CLKFLAG_HANDOFF_RATE) {
+		/*
+		 * The clock was already enabled by handoff code so there is no
+		 * need to enable it again here. Clearing the handoff flag will
+		 * prevent the lateinit handoff code from disabling the clock if
+		 * a client driver still has it enabled.
+		 */
+		clk->flags &= ~CLKFLAG_HANDOFF_RATE;
+		goto out;
 	}
 	clk->count++;
 out:
@@ -180,6 +189,8 @@
 		struct clk *clk = clock_tbl[n].clk;
 		struct clk *parent = clk_get_parent(clk);
 		clk_set_parent(clk, parent);
+		if (clk->ops->handoff)
+			clk->ops->handoff(clk);
 	}
 
 	clkdev_add_table(clock_tbl, num_clocks);
@@ -199,6 +210,7 @@
 	clock_debug_init(clk_init_data);
 	for (n = 0; n < clk_init_data->size; n++) {
 		struct clk *clk = clk_init_data->table[n].clk;
+		bool handoff = false;
 
 		clock_debug_add(clk);
 		if (!(clk->flags & CLKFLAG_SKIP_AUTO_OFF)) {
@@ -207,7 +219,17 @@
 				count++;
 				clk->ops->auto_off(clk);
 			}
+			if (clk->flags & CLKFLAG_HANDOFF_RATE) {
+				clk->flags &= ~CLKFLAG_HANDOFF_RATE;
+				handoff = true;
+			}
 			spin_unlock_irqrestore(&clk->lock, flags);
+			/*
+			 * Calling clk_disable() outside the lock is safe since
+			 * it doesn't need to be atomic with the flag change.
+			 */
+			if (handoff)
+				clk_disable(clk);
 		}
 	}
 	pr_info("clock_late_init() disabled %d unused clocks\n", count);
diff --git a/arch/arm/mach-msm/clock.h b/arch/arm/mach-msm/clock.h
index 5ed5a94..db49133 100644
--- a/arch/arm/mach-msm/clock.h
+++ b/arch/arm/mach-msm/clock.h
@@ -28,7 +28,7 @@
 #define CLKFLAG_NOINVERT		0x00000002
 #define CLKFLAG_NONEST			0x00000004
 #define CLKFLAG_NORESET			0x00000008
-
+#define CLKFLAG_HANDOFF_RATE		0x00000010
 #define CLKFLAG_SKIP_AUTO_OFF		0x00000200
 #define CLKFLAG_MIN			0x00000400
 #define CLKFLAG_MAX			0x00000800
@@ -37,6 +37,7 @@
 	int (*enable)(struct clk *clk);
 	void (*disable)(struct clk *clk);
 	void (*auto_off)(struct clk *clk);
+	void (*handoff)(struct clk *clk);
 	int (*reset)(struct clk *clk, enum clk_reset_action action);
 	int (*set_rate)(struct clk *clk, unsigned rate);
 	int (*set_min_rate)(struct clk *clk, unsigned rate);