From 87c6bb6b8b481a755d6e9345d87299b79f283977 Mon Sep 17 00:00:00 2001 From: Deepak Katragadda Date: Mon, 21 Nov 2016 12:18:48 -0800 Subject: [PATCH] clk: qcom: clk-rcg2: Configure the RCGs to a safe frequency as needed In certain cases, an RCG might be prone to being enabled even though the overlying software thinks that it disabled the RCG. In order to avoid letting the RCG go into an invalid state, support parking it at a safe frequency during clk_disable() and deferring all the RCG configuration updates to be done during clk_enable(), if a scaling request comes in whilst the clock is disabled. Change-Id: I55f1d1d346182a2b480127c57d6659fc9a63331b Signed-off-by: Deepak Katragadda --- drivers/clk/qcom/clk-rcg.h | 2 + drivers/clk/qcom/clk-rcg2.c | 97 +++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h index accdac9fb964..58fbd07e6f15 100644 --- a/drivers/clk/qcom/clk-rcg.h +++ b/drivers/clk/qcom/clk-rcg.h @@ -159,6 +159,7 @@ extern const struct clk_ops clk_dyn_rcg_ops; * @parent_map: map from software's parent index to hardware's src_sel field * @freq_tbl: frequency table * @current_freq: last cached frequency when using branches with shared RCGs + * @enable_safe_config: When set, the RCG is parked at CXO when it's disabled * @clkr: regmap clock handle * @flags: set if RCG needs to be force enabled/disabled during * power sequence. @@ -173,6 +174,7 @@ struct clk_rcg2 { unsigned long current_freq; u32 new_index; u32 curr_index; + bool enable_safe_config; struct clk_regmap clkr; u8 flags; diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index 653722f9c4b0..c37716e8273d 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -49,6 +50,14 @@ #define N_REG 0xc #define D_REG 0x10 +static struct freq_tbl cxo_f = { + .freq = 19200000, + .src = 0, + .pre_div = 1, + .m = 0, + .n = 0, +}; + static int clk_rcg2_is_enabled(struct clk_hw *hw) { struct clk_rcg2 *rcg = to_clk_rcg2(hw); @@ -153,6 +162,17 @@ static int clk_rcg2_set_parent(struct clk_hw *hw, u8 index) return update_config(rcg); } +static void clk_rcg_clear_force_enable(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + + /* force disable RCG - clear CMD_ROOT_EN bit */ + regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, + CMD_ROOT_EN, 0); + /* Add a hardware mandate delay to disable the RCG */ + udelay(100); +} + /* * Calculate m/n:d rate * @@ -184,6 +204,12 @@ clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) struct clk_rcg2 *rcg = to_clk_rcg2(hw); u32 cfg, hid_div, m = 0, n = 0, mode = 0, mask; + if (rcg->enable_safe_config && !clk_hw_is_prepared(hw)) { + if (!rcg->current_freq) + rcg->current_freq = cxo_f.freq; + return rcg->current_freq; + } + regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &cfg); if (rcg->mnd_width) { @@ -425,14 +451,6 @@ static void disable_unprepare_rcg_srcs(struct clk_hw *hw, struct clk *curr, clk_unprepare(new); } -static struct freq_tbl cxo_f = { - .freq = 19200000, - .src = 0, - .pre_div = 1, - .m = 0, - .n = 0, -}; - static int clk_enable_disable_prepare_unprepare(struct clk_hw *hw, int cindex, int nindex, bool enable) { @@ -452,6 +470,7 @@ static int clk_rcg2_enable(struct clk_hw *hw) { int ret = 0; const struct freq_tbl *f; + unsigned long rate; struct clk_rcg2 *rcg = to_clk_rcg2(hw); if (rcg->flags & FORCE_ENABLE_RCGR) { @@ -477,9 +496,31 @@ static int clk_rcg2_enable(struct clk_hw *hw) clk_enable_disable_prepare_unprepare(hw, rcg->curr_index, rcg->new_index, false); + + return ret; } - return ret; + if (!rcg->enable_safe_config) + return 0; + + /* + * Switch from CXO to the stashed mux selection. Force enable and + * disable the RCG while configuring it to safeguard against any update + * signal coming from the downstream clock. The current parent has + * already been prepared and enabled at this point, and the CXO source + * is always on while APPS is online. Therefore, the RCG can safely be + * switched. + */ + rate = clk_get_rate(hw->clk); + f = qcom_find_freq(rcg->freq_tbl, rate); + if (!f) + return -EINVAL; + + clk_rcg_set_force_enable(hw); + clk_rcg2_configure(rcg, f); + clk_rcg_clear_force_enable(hw); + + return 0; } static void clk_rcg2_disable(struct clk_hw *hw) @@ -487,12 +528,31 @@ static void clk_rcg2_disable(struct clk_hw *hw) struct clk_rcg2 *rcg = to_clk_rcg2(hw); if (rcg->flags & FORCE_ENABLE_RCGR) { - /* force disable RCG - clear CMD_ROOT_EN bit */ - regmap_update_bits(rcg->clkr.regmap, - rcg->cmd_rcgr + CMD_REG, CMD_ROOT_EN, 0); - /* Add a delay to disable the RCG */ - udelay(100); + clk_rcg_clear_force_enable(hw); + return; } + + if (!rcg->enable_safe_config) + return; + /* + * Park the RCG at a safe configuration - sourced off the CXO. This is + * needed for 2 reasons: In the case of RCGs sourcing PSCBCs, due to a + * default HW behavior, the RCG will turn on when its corresponding + * GDSC is enabled. We might also have cases when the RCG might be left + * enabled without the overlying SW knowing about it. This results from + * hard to track cases of downstream clocks being left enabled. In both + * these cases, scaling the RCG will fail since it's enabled but with + * its sources cut off. + * + * Save mux select and switch to CXO. Force enable and disable the RCG + * while configuring it to safeguard against any update signal coming + * from the downstream clock. The current parent is still prepared and + * enabled at this point, and the CXO source is always on while APPS is + * online. Therefore, the RCG can safely be switched. + */ + clk_rcg_set_force_enable(hw); + clk_rcg2_configure(rcg, &cxo_f); + clk_rcg_clear_force_enable(hw); } @@ -506,6 +566,15 @@ static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate) if (rcg->flags & FORCE_ENABLE_RCGR) rcg->current_freq = clk_get_rate(hw->clk); + /* + * Return if the RCG is currently disabled. This configuration update + * will happen as part of the RCG enable sequence. + */ + if (rcg->enable_safe_config && !clk_hw_is_prepared(hw)) { + rcg->current_freq = rate; + return 0; + } + f = qcom_find_freq(rcg->freq_tbl, rate); if (!f) return -EINVAL;