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;