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);