aboutsummaryrefslogtreecommitdiff
path: root/drivers/clk
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.c192
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.h2
2 files changed, 189 insertions, 5 deletions
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
index 2a4b0c9f5c4d..6860f5b1db88 100644
--- a/drivers/clk/qcom/clk-alpha-pll.c
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -47,6 +47,7 @@
# define PLL_POST_DIV_SHIFT 8
# define PLL_POST_DIV_MASK 0xf
# define PLL_ALPHA_EN BIT(24)
+# define PLL_ALPHA_MODE BIT(25)
# define PLL_VCO_SHIFT 20
# define PLL_VCO_MASK 0x3
@@ -70,6 +71,16 @@ const u8 clk_alpha_pll_regs[][PLL_OFF_MAX_REGS] = {
[PLL_OFF_TEST_CTL_U] = 0x20,
[PLL_OFF_STATUS] = 0x24,
},
+ [CLK_ALPHA_PLL_TYPE_HUAYRA] = {
+ [PLL_OFF_L_VAL] = 0x04,
+ [PLL_OFF_ALPHA_VAL] = 0x08,
+ [PLL_OFF_USER_CTL] = 0x10,
+ [PLL_OFF_CONFIG_CTL] = 0x14,
+ [PLL_OFF_CONFIG_CTL_U] = 0x18,
+ [PLL_OFF_TEST_CTL] = 0x1c,
+ [PLL_OFF_TEST_CTL_U] = 0x20,
+ [PLL_OFF_STATUS] = 0x24,
+ },
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_regs);
@@ -81,6 +92,13 @@ EXPORT_SYMBOL_GPL(clk_alpha_pll_regs);
#define ALPHA_BITWIDTH 32U
#define ALPHA_SHIFT(w) min(w, ALPHA_BITWIDTH)
+#define PLL_HUAYRA_M_WIDTH 8
+#define PLL_HUAYRA_M_SHIFT 8
+#define PLL_HUAYRA_M_MASK 0xff
+#define PLL_HUAYRA_N_SHIFT 0
+#define PLL_HUAYRA_N_MASK 0xff
+#define PLL_HUAYRA_ALPHA_WIDTH 16
+
#define pll_alpha_width(p) \
((PLL_ALPHA_VAL_U(p) - PLL_ALPHA_VAL(p) == 4) ? \
ALPHA_REG_BITWIDTH : ALPHA_REG_16BIT_WIDTH)
@@ -473,7 +491,7 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
rate = alpha_pll_round_rate(rate, prate, &l, &a, alpha_width);
vco = alpha_pll_find_vco(pll, rate);
- if (!vco) {
+ if (pll->vco_table && !vco) {
pr_err("alpha pll not in a valid vco range\n");
return -EINVAL;
}
@@ -488,9 +506,11 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL(pll), a);
- regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll),
- PLL_VCO_MASK << PLL_VCO_SHIFT,
- vco->val << PLL_VCO_SHIFT);
+ if (vco) {
+ regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll),
+ PLL_VCO_MASK << PLL_VCO_SHIFT,
+ vco->val << PLL_VCO_SHIFT);
+ }
regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll),
PLL_ALPHA_EN, PLL_ALPHA_EN);
@@ -521,7 +541,7 @@ static long clk_alpha_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long min_freq, max_freq;
rate = alpha_pll_round_rate(rate, *prate, &l, &a, alpha_width);
- if (alpha_pll_find_vco(pll, rate))
+ if (!pll->vco_table || alpha_pll_find_vco(pll, rate))
return rate;
min_freq = pll->vco_table[0].min_freq;
@@ -530,6 +550,158 @@ static long clk_alpha_pll_round_rate(struct clk_hw *hw, unsigned long rate,
return clamp(rate, min_freq, max_freq);
}
+static unsigned long
+alpha_huayra_pll_calc_rate(u64 prate, u32 l, u32 a)
+{
+ /*
+ * a contains 16 bit alpha_val in two’s compliment number in the range
+ * of [-0.5, 0.5).
+ */
+ if (a >= BIT(PLL_HUAYRA_ALPHA_WIDTH - 1))
+ l -= 1;
+
+ return (prate * l) + (prate * a >> PLL_HUAYRA_ALPHA_WIDTH);
+}
+
+static unsigned long
+alpha_huayra_pll_round_rate(unsigned long rate, unsigned long prate,
+ u32 *l, u32 *a)
+{
+ u64 remainder;
+ u64 quotient;
+
+ quotient = rate;
+ remainder = do_div(quotient, prate);
+ *l = quotient;
+
+ if (!remainder) {
+ *a = 0;
+ return rate;
+ }
+
+ quotient = remainder << PLL_HUAYRA_ALPHA_WIDTH;
+ remainder = do_div(quotient, prate);
+
+ if (remainder)
+ quotient++;
+
+ /*
+ * alpha_val should be in two’s compliment number in the range
+ * of [-0.5, 0.5) so if quotient >= 0.5 then increment the l value
+ * since alpha value will be subtracted in this case.
+ */
+ if (quotient >= BIT(PLL_HUAYRA_ALPHA_WIDTH - 1))
+ *l += 1;
+
+ *a = quotient;
+ return alpha_huayra_pll_calc_rate(prate, *l, *a);
+}
+
+static unsigned long
+alpha_pll_huayra_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ u64 rate = parent_rate, tmp;
+ struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
+ u32 l, alpha = 0, ctl, alpha_m, alpha_n;
+
+ regmap_read(pll->clkr.regmap, PLL_L_VAL(pll), &l);
+ regmap_read(pll->clkr.regmap, PLL_USER_CTL(pll), &ctl);
+
+ if (ctl & PLL_ALPHA_EN) {
+ regmap_read(pll->clkr.regmap, PLL_ALPHA_VAL(pll), &alpha);
+ /*
+ * Depending upon alpha_mode, it can be treated as M/N value or
+ * as a two’s compliment number. When alpha_mode=1,
+ * pll_alpha_val<15:8>=M and pll_apla_val<7:0>=N
+ *
+ * Fout=FIN*(L+(M/N))
+ *
+ * M is a signed number (-128 to 127) and N is unsigned
+ * (0 to 255). M/N has to be within +/-0.5.
+ *
+ * When alpha_mode=0, it is a two’s compliment number in the
+ * range [-0.5, 0.5).
+ *
+ * Fout=FIN*(L+(alpha_val)/2^16)
+ *
+ * where alpha_val is two’s compliment number.
+ */
+ if (!(ctl & PLL_ALPHA_MODE))
+ return alpha_huayra_pll_calc_rate(rate, l, alpha);
+
+ alpha_m = alpha >> PLL_HUAYRA_M_SHIFT & PLL_HUAYRA_M_MASK;
+ alpha_n = alpha >> PLL_HUAYRA_N_SHIFT & PLL_HUAYRA_N_MASK;
+
+ rate *= l;
+ tmp = parent_rate;
+ if (alpha_m >= BIT(PLL_HUAYRA_M_WIDTH - 1)) {
+ alpha_m = BIT(PLL_HUAYRA_M_WIDTH) - alpha_m;
+ tmp *= alpha_m;
+ do_div(tmp, alpha_n);
+ rate -= tmp;
+ } else {
+ tmp *= alpha_m;
+ do_div(tmp, alpha_n);
+ rate += tmp;
+ }
+
+ return rate;
+ }
+
+ return alpha_huayra_pll_calc_rate(rate, l, alpha);
+}
+
+static int alpha_pll_huayra_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
+ u32 l, a, ctl, cur_alpha = 0;
+
+ rate = alpha_huayra_pll_round_rate(rate, prate, &l, &a);
+
+ regmap_read(pll->clkr.regmap, PLL_USER_CTL(pll), &ctl);
+
+ if (ctl & PLL_ALPHA_EN)
+ regmap_read(pll->clkr.regmap, PLL_ALPHA_VAL(pll), &cur_alpha);
+
+ /*
+ * Huayra PLL supports PLL dynamic programming. User can change L_VAL,
+ * without having to go through the power on sequence.
+ */
+ if (clk_alpha_pll_is_enabled(hw)) {
+ if (cur_alpha != a) {
+ pr_err("clock needs to be gated %s\n",
+ clk_hw_get_name(hw));
+ return -EBUSY;
+ }
+
+ regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l);
+ /* Ensure that the write above goes to detect L val change. */
+ mb();
+ return wait_for_pll_enable_lock(pll);
+ }
+
+ regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l);
+ regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL(pll), a);
+
+ if (a == 0)
+ regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll),
+ PLL_ALPHA_EN, 0x0);
+ else
+ regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll),
+ PLL_ALPHA_EN | PLL_ALPHA_MODE, PLL_ALPHA_EN);
+
+ return 0;
+}
+
+static long alpha_pll_huayra_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ u32 l, a;
+
+ return alpha_huayra_pll_round_rate(rate, *prate, &l, &a);
+}
+
const struct clk_ops clk_alpha_pll_ops = {
.enable = clk_alpha_pll_enable,
.disable = clk_alpha_pll_disable,
@@ -540,6 +712,16 @@ const struct clk_ops clk_alpha_pll_ops = {
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_ops);
+const struct clk_ops clk_alpha_pll_huayra_ops = {
+ .enable = clk_alpha_pll_enable,
+ .disable = clk_alpha_pll_disable,
+ .is_enabled = clk_alpha_pll_is_enabled,
+ .recalc_rate = alpha_pll_huayra_recalc_rate,
+ .round_rate = alpha_pll_huayra_round_rate,
+ .set_rate = alpha_pll_huayra_set_rate,
+};
+EXPORT_SYMBOL_GPL(clk_alpha_pll_huayra_ops);
+
const struct clk_ops clk_alpha_pll_hwfsm_ops = {
.enable = clk_alpha_pll_hwfsm_enable,
.disable = clk_alpha_pll_hwfsm_disable,
diff --git a/drivers/clk/qcom/clk-alpha-pll.h b/drivers/clk/qcom/clk-alpha-pll.h
index 82fef8f1c9af..667e9069d086 100644
--- a/drivers/clk/qcom/clk-alpha-pll.h
+++ b/drivers/clk/qcom/clk-alpha-pll.h
@@ -20,6 +20,7 @@
/* Alpha PLL types */
enum {
CLK_ALPHA_PLL_TYPE_DEFAULT,
+ CLK_ALPHA_PLL_TYPE_HUAYRA,
CLK_ALPHA_PLL_TYPE_MAX,
};
@@ -104,6 +105,7 @@ struct alpha_pll_config {
extern const struct clk_ops clk_alpha_pll_ops;
extern const struct clk_ops clk_alpha_pll_hwfsm_ops;
extern const struct clk_ops clk_alpha_pll_postdiv_ops;
+extern const struct clk_ops clk_alpha_pll_huayra_ops;
void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap,
const struct alpha_pll_config *config);