diff options
author | Stephen Boyd <sboyd@codeaurora.org> | 2015-03-06 18:41:53 -0500 |
---|---|---|
committer | Stephen Boyd <sboyd@codeaurora.org> | 2015-03-12 15:20:30 -0400 |
commit | 9d3745d44a7faa7d24db7facb1949a1378162f3e (patch) | |
tree | d98f818f3c4c2011299f803e8b80aeda2f56c648 | |
parent | 65bd20046f15c9c465df7aa9b86491e56e9c0d25 (diff) |
clk: qcom: Properly change rates for ahbix clock
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>
-rw-r--r-- | drivers/clk/qcom/clk-rcg.c | 62 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-rcg.h | 1 | ||||
-rw-r--r-- | drivers/clk/qcom/lcc-ipq806x.c | 5 |
3 files changed, 65 insertions, 3 deletions
diff --git a/drivers/clk/qcom/clk-rcg.c b/drivers/clk/qcom/clk-rcg.c index 0039bd7d3965..466f30ca65c2 100644 --- 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(struct clk_hw *hw, unsigned long rate, | |||
495 | return __clk_rcg_set_rate(rcg, rcg->freq_tbl); | 495 | return __clk_rcg_set_rate(rcg, rcg->freq_tbl); |
496 | } | 496 | } |
497 | 497 | ||
498 | /* | ||
499 | * This type of clock has a glitch-free mux that switches between the output of | ||
500 | * the M/N counter and an always on clock source (XO). When clk_set_rate() is | ||
501 | * called we need to make sure that we don't switch to the M/N counter if it | ||
502 | * isn't clocking because the mux will get stuck and the clock will stop | ||
503 | * outputting a clock. This can happen if the framework isn't aware that this | ||
504 | * clock is on and so clk_set_rate() doesn't turn on the new parent. To fix | ||
505 | * this we switch the mux in the enable/disable ops and reprogram the M/N | ||
506 | * counter in the set_rate op. We also make sure to switch away from the M/N | ||
507 | * counter in set_rate if software thinks the clock is off. | ||
508 | */ | ||
509 | static int clk_rcg_lcc_set_rate(struct clk_hw *hw, unsigned long rate, | ||
510 | unsigned long parent_rate) | ||
511 | { | ||
512 | struct clk_rcg *rcg = to_clk_rcg(hw); | ||
513 | const struct freq_tbl *f; | ||
514 | int ret; | ||
515 | u32 gfm = BIT(10); | ||
516 | |||
517 | f = qcom_find_freq(rcg->freq_tbl, rate); | ||
518 | if (!f) | ||
519 | return -EINVAL; | ||
520 | |||
521 | /* Switch to XO to avoid glitches */ | ||
522 | regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, 0); | ||
523 | ret = __clk_rcg_set_rate(rcg, f); | ||
524 | /* Switch back to M/N if it's clocking */ | ||
525 | if (__clk_is_enabled(hw->clk)) | ||
526 | regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, gfm); | ||
527 | |||
528 | return ret; | ||
529 | } | ||
530 | |||
531 | static int clk_rcg_lcc_enable(struct clk_hw *hw) | ||
532 | { | ||
533 | struct clk_rcg *rcg = to_clk_rcg(hw); | ||
534 | u32 gfm = BIT(10); | ||
535 | |||
536 | /* Use M/N */ | ||
537 | return regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, gfm); | ||
538 | } | ||
539 | |||
540 | static void clk_rcg_lcc_disable(struct clk_hw *hw) | ||
541 | { | ||
542 | struct clk_rcg *rcg = to_clk_rcg(hw); | ||
543 | u32 gfm = BIT(10); | ||
544 | |||
545 | /* Use XO */ | ||
546 | regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, 0); | ||
547 | } | ||
548 | |||
498 | static int __clk_dyn_rcg_set_rate(struct clk_hw *hw, unsigned long rate) | 549 | static int __clk_dyn_rcg_set_rate(struct clk_hw *hw, unsigned long rate) |
499 | { | 550 | { |
500 | struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw); | 551 | struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw); |
@@ -543,6 +594,17 @@ const struct clk_ops clk_rcg_bypass_ops = { | |||
543 | }; | 594 | }; |
544 | EXPORT_SYMBOL_GPL(clk_rcg_bypass_ops); | 595 | EXPORT_SYMBOL_GPL(clk_rcg_bypass_ops); |
545 | 596 | ||
597 | const struct clk_ops clk_rcg_lcc_ops = { | ||
598 | .enable = clk_rcg_lcc_enable, | ||
599 | .disable = clk_rcg_lcc_disable, | ||
600 | .get_parent = clk_rcg_get_parent, | ||
601 | .set_parent = clk_rcg_set_parent, | ||
602 | .recalc_rate = clk_rcg_recalc_rate, | ||
603 | .determine_rate = clk_rcg_determine_rate, | ||
604 | .set_rate = clk_rcg_lcc_set_rate, | ||
605 | }; | ||
606 | EXPORT_SYMBOL_GPL(clk_rcg_lcc_ops); | ||
607 | |||
546 | const struct clk_ops clk_dyn_rcg_ops = { | 608 | const struct clk_ops clk_dyn_rcg_ops = { |
547 | .enable = clk_enable_regmap, | 609 | .enable = clk_enable_regmap, |
548 | .is_enabled = clk_is_enabled_regmap, | 610 | .is_enabled = clk_is_enabled_regmap, |
diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h index 687e41f91d7c..d09d06ba278e 100644 --- a/drivers/clk/qcom/clk-rcg.h +++ b/drivers/clk/qcom/clk-rcg.h | |||
@@ -96,6 +96,7 @@ struct clk_rcg { | |||
96 | 96 | ||
97 | extern const struct clk_ops clk_rcg_ops; | 97 | extern const struct clk_ops clk_rcg_ops; |
98 | extern const struct clk_ops clk_rcg_bypass_ops; | 98 | extern const struct clk_ops clk_rcg_bypass_ops; |
99 | extern const struct clk_ops clk_rcg_lcc_ops; | ||
99 | 100 | ||
100 | #define to_clk_rcg(_hw) container_of(to_clk_regmap(_hw), struct clk_rcg, clkr) | 101 | #define to_clk_rcg(_hw) container_of(to_clk_regmap(_hw), struct clk_rcg, clkr) |
101 | 102 | ||
diff --git a/drivers/clk/qcom/lcc-ipq806x.c b/drivers/clk/qcom/lcc-ipq806x.c index 121ffde25dc3..5e4a3dafcf63 100644 --- a/drivers/clk/qcom/lcc-ipq806x.c +++ b/drivers/clk/qcom/lcc-ipq806x.c | |||
@@ -386,13 +386,12 @@ static struct clk_rcg ahbix_clk = { | |||
386 | .freq_tbl = clk_tbl_ahbix, | 386 | .freq_tbl = clk_tbl_ahbix, |
387 | .clkr = { | 387 | .clkr = { |
388 | .enable_reg = 0x38, | 388 | .enable_reg = 0x38, |
389 | .enable_mask = BIT(10), /* toggle the gfmux to select mn/pxo */ | 389 | .enable_mask = BIT(11), |
390 | .hw.init = &(struct clk_init_data){ | 390 | .hw.init = &(struct clk_init_data){ |
391 | .name = "ahbix", | 391 | .name = "ahbix", |
392 | .parent_names = lcc_pxo_pll4, | 392 | .parent_names = lcc_pxo_pll4, |
393 | .num_parents = 2, | 393 | .num_parents = 2, |
394 | .ops = &clk_rcg_ops, | 394 | .ops = &clk_rcg_lcc_ops, |
395 | .flags = CLK_SET_RATE_GATE, | ||
396 | }, | 395 | }, |
397 | }, | 396 | }, |
398 | }; | 397 | }; |