summaryrefslogtreecommitdiffstats
path: root/drivers/clk/qcom/clk-alpha-pll.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/qcom/clk-alpha-pll.c')
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.c83
1 files changed, 79 insertions, 4 deletions
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
index 38ed1b83a094..2a4b0c9f5c4d 100644
--- a/drivers/clk/qcom/clk-alpha-pll.c
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -32,7 +32,10 @@
# define PLL_VOTE_FSM_ENA BIT(20)
# define PLL_FSM_ENA BIT(20)
# define PLL_VOTE_FSM_RESET BIT(21)
+# define PLL_UPDATE BIT(22)
+# define PLL_UPDATE_BYPASS BIT(23)
# define PLL_OFFLINE_ACK BIT(28)
+# define ALPHA_PLL_ACK_LATCH BIT(29)
# define PLL_ACTIVE_FLAG BIT(30)
# define PLL_LOCK_DET BIT(31)
@@ -130,6 +133,15 @@ static int wait_for_pll(struct clk_alpha_pll *pll, u32 mask, bool inverse,
#define wait_for_pll_offline(pll) \
wait_for_pll(pll, PLL_OFFLINE_ACK, 0, "offline")
+#define wait_for_pll_update(pll) \
+ wait_for_pll(pll, PLL_UPDATE, 1, "update")
+
+#define wait_for_pll_update_ack_set(pll) \
+ wait_for_pll(pll, ALPHA_PLL_ACK_LATCH, 0, "update_ack_set")
+
+#define wait_for_pll_update_ack_clear(pll) \
+ wait_for_pll(pll, ALPHA_PLL_ACK_LATCH, 1, "update_ack_clear")
+
void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap,
const struct alpha_pll_config *config)
{
@@ -402,8 +414,57 @@ clk_alpha_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
return alpha_pll_calc_rate(prate, l, a, alpha_width);
}
-static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long prate)
+static int clk_alpha_pll_update_latch(struct clk_alpha_pll *pll,
+ int (*is_enabled)(struct clk_hw *))
+{
+ int ret;
+ u32 mode;
+
+ if (!is_enabled(&pll->clkr.hw) ||
+ !(pll->flags & SUPPORTS_DYNAMIC_UPDATE))
+ return 0;
+
+ regmap_read(pll->clkr.regmap, PLL_MODE(pll), &mode);
+
+ /* Latch the input to the PLL */
+ regmap_update_bits(pll->clkr.regmap, PLL_MODE(pll), PLL_UPDATE,
+ PLL_UPDATE);
+
+ /* Wait for 2 reference cycle before checking ACK bit */
+ udelay(1);
+
+ /*
+ * PLL will latch the new L, Alpha and freq control word.
+ * PLL will respond by raising PLL_ACK_LATCH output when new programming
+ * has been latched in and PLL is being updated. When
+ * UPDATE_LOGIC_BYPASS bit is not set, PLL_UPDATE will be cleared
+ * automatically by hardware when PLL_ACK_LATCH is asserted by PLL.
+ */
+ if (mode & PLL_UPDATE_BYPASS) {
+ ret = wait_for_pll_update_ack_set(pll);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(pll->clkr.regmap, PLL_MODE(pll), PLL_UPDATE, 0);
+ } else {
+ ret = wait_for_pll_update(pll);
+ if (ret)
+ return ret;
+ }
+
+ ret = wait_for_pll_update_ack_clear(pll);
+ if (ret)
+ return ret;
+
+ /* Wait for PLL output to stabilize */
+ udelay(10);
+
+ return 0;
+}
+
+static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate,
+ int (*is_enabled)(struct clk_hw *))
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
const struct pll_vco *vco;
@@ -434,7 +495,21 @@ static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll),
PLL_ALPHA_EN, PLL_ALPHA_EN);
- return 0;
+ return clk_alpha_pll_update_latch(pll, is_enabled);
+}
+
+static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ return __clk_alpha_pll_set_rate(hw, rate, prate,
+ clk_alpha_pll_is_enabled);
+}
+
+static int clk_alpha_pll_hwfsm_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ return __clk_alpha_pll_set_rate(hw, rate, prate,
+ clk_alpha_pll_hwfsm_is_enabled);
}
static long clk_alpha_pll_round_rate(struct clk_hw *hw, unsigned long rate,
@@ -471,7 +546,7 @@ const struct clk_ops clk_alpha_pll_hwfsm_ops = {
.is_enabled = clk_alpha_pll_hwfsm_is_enabled,
.recalc_rate = clk_alpha_pll_recalc_rate,
.round_rate = clk_alpha_pll_round_rate,
- .set_rate = clk_alpha_pll_set_rate,
+ .set_rate = clk_alpha_pll_hwfsm_set_rate,
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_hwfsm_ops);