| From 9d3745d44a7faa7d24db7facb1949a1378162f3e Mon Sep 17 00:00:00 2001 |
| From: Stephen Boyd <sboyd@codeaurora.org> |
| Date: Fri, 6 Mar 2015 15:41:53 -0800 |
| Subject: clk: qcom: Properly change rates for ahbix clock |
| |
| From: Stephen Boyd <sboyd@codeaurora.org> |
| |
| commit 9d3745d44a7faa7d24db7facb1949a1378162f3e upstream. |
| |
| The ahbix clock can never be turned off in practice. To change the |
| rates we need to switch the mux off the M/N counter to an always on |
| source (XO), reprogram the M/N counter to get the rate we want and |
| finally switch back to the M/N counter. Add a new ops structure |
| for this type of clock so that we can set the rate properly. |
| |
| Fixes: c99e515a92e9 "clk: qcom: Add IPQ806X LPASS clock controller (LCC) driver" |
| Tested-by: Kenneth Westfield <kwestfie@codeaurora.org> |
| Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/clk/qcom/clk-rcg.c | 62 +++++++++++++++++++++++++++++++++++++++++ |
| drivers/clk/qcom/clk-rcg.h | 1 |
| drivers/clk/qcom/lcc-ipq806x.c | 5 +-- |
| 3 files changed, 65 insertions(+), 3 deletions(-) |
| |
| --- a/drivers/clk/qcom/clk-rcg.c |
| +++ b/drivers/clk/qcom/clk-rcg.c |
| @@ -495,6 +495,57 @@ static int clk_rcg_bypass_set_rate(struc |
| return __clk_rcg_set_rate(rcg, rcg->freq_tbl); |
| } |
| |
| +/* |
| + * This type of clock has a glitch-free mux that switches between the output of |
| + * the M/N counter and an always on clock source (XO). When clk_set_rate() is |
| + * called we need to make sure that we don't switch to the M/N counter if it |
| + * isn't clocking because the mux will get stuck and the clock will stop |
| + * outputting a clock. This can happen if the framework isn't aware that this |
| + * clock is on and so clk_set_rate() doesn't turn on the new parent. To fix |
| + * this we switch the mux in the enable/disable ops and reprogram the M/N |
| + * counter in the set_rate op. We also make sure to switch away from the M/N |
| + * counter in set_rate if software thinks the clock is off. |
| + */ |
| +static int clk_rcg_lcc_set_rate(struct clk_hw *hw, unsigned long rate, |
| + unsigned long parent_rate) |
| +{ |
| + struct clk_rcg *rcg = to_clk_rcg(hw); |
| + const struct freq_tbl *f; |
| + int ret; |
| + u32 gfm = BIT(10); |
| + |
| + f = qcom_find_freq(rcg->freq_tbl, rate); |
| + if (!f) |
| + return -EINVAL; |
| + |
| + /* Switch to XO to avoid glitches */ |
| + regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, 0); |
| + ret = __clk_rcg_set_rate(rcg, f); |
| + /* Switch back to M/N if it's clocking */ |
| + if (__clk_is_enabled(hw->clk)) |
| + regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, gfm); |
| + |
| + return ret; |
| +} |
| + |
| +static int clk_rcg_lcc_enable(struct clk_hw *hw) |
| +{ |
| + struct clk_rcg *rcg = to_clk_rcg(hw); |
| + u32 gfm = BIT(10); |
| + |
| + /* Use M/N */ |
| + return regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, gfm); |
| +} |
| + |
| +static void clk_rcg_lcc_disable(struct clk_hw *hw) |
| +{ |
| + struct clk_rcg *rcg = to_clk_rcg(hw); |
| + u32 gfm = BIT(10); |
| + |
| + /* Use XO */ |
| + regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, 0); |
| +} |
| + |
| static int __clk_dyn_rcg_set_rate(struct clk_hw *hw, unsigned long rate) |
| { |
| struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw); |
| @@ -543,6 +594,17 @@ const struct clk_ops clk_rcg_bypass_ops |
| }; |
| EXPORT_SYMBOL_GPL(clk_rcg_bypass_ops); |
| |
| +const struct clk_ops clk_rcg_lcc_ops = { |
| + .enable = clk_rcg_lcc_enable, |
| + .disable = clk_rcg_lcc_disable, |
| + .get_parent = clk_rcg_get_parent, |
| + .set_parent = clk_rcg_set_parent, |
| + .recalc_rate = clk_rcg_recalc_rate, |
| + .determine_rate = clk_rcg_determine_rate, |
| + .set_rate = clk_rcg_lcc_set_rate, |
| +}; |
| +EXPORT_SYMBOL_GPL(clk_rcg_lcc_ops); |
| + |
| const struct clk_ops clk_dyn_rcg_ops = { |
| .enable = clk_enable_regmap, |
| .is_enabled = clk_is_enabled_regmap, |
| --- a/drivers/clk/qcom/clk-rcg.h |
| +++ b/drivers/clk/qcom/clk-rcg.h |
| @@ -96,6 +96,7 @@ struct clk_rcg { |
| |
| extern const struct clk_ops clk_rcg_ops; |
| extern const struct clk_ops clk_rcg_bypass_ops; |
| +extern const struct clk_ops clk_rcg_lcc_ops; |
| |
| #define to_clk_rcg(_hw) container_of(to_clk_regmap(_hw), struct clk_rcg, clkr) |
| |
| --- a/drivers/clk/qcom/lcc-ipq806x.c |
| +++ b/drivers/clk/qcom/lcc-ipq806x.c |
| @@ -386,13 +386,12 @@ static struct clk_rcg ahbix_clk = { |
| .freq_tbl = clk_tbl_ahbix, |
| .clkr = { |
| .enable_reg = 0x38, |
| - .enable_mask = BIT(10), /* toggle the gfmux to select mn/pxo */ |
| + .enable_mask = BIT(11), |
| .hw.init = &(struct clk_init_data){ |
| .name = "ahbix", |
| .parent_names = lcc_pxo_pll4, |
| .num_parents = 2, |
| - .ops = &clk_rcg_ops, |
| - .flags = CLK_SET_RATE_GATE, |
| + .ops = &clk_rcg_lcc_ops, |
| }, |
| }, |
| }; |