diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c index 43f7a77201c4..360b0ac30cce 100644 --- a/drivers/clk/qcom/clk-alpha-pll.c +++ b/drivers/clk/qcom/clk-alpha-pll.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "clk-alpha-pll.h" #include "clk-debug.h" @@ -23,6 +24,7 @@ # define PLL_LOCK_COUNT_MASK 0x3f # define PLL_BIAS_COUNT_SHIFT 14 # define PLL_BIAS_COUNT_MASK 0x3f +#define PLL_LATCH_INTERFACE BIT(11) # define PLL_VOTE_FSM_ENA BIT(20) # define PLL_FSM_ENA BIT(20) # define PLL_VOTE_FSM_RESET BIT(21) @@ -291,12 +293,15 @@ static int wait_for_pll(struct clk_alpha_pll *pll, u32 mask, bool inverse, u32 val; int count; int ret; + u64 time; const char *name = clk_hw_get_name(&pll->clkr.hw); ret = regmap_read(pll->clkr.regmap, PLL_MODE(pll), &val); if (ret) return ret; + time = sched_clock(); + for (count = 100; count > 0; count--) { ret = regmap_read(pll->clkr.regmap, PLL_MODE(pll), &val); if (ret) @@ -308,6 +313,9 @@ static int wait_for_pll(struct clk_alpha_pll *pll, u32 mask, bool inverse, udelay(1); } + time = sched_clock() - time; + + pr_err("PLL lock bit detection total wait time: %lld ns\n", time); WARN(1, "%s failed to %s!\n", name, action); return -ETIMEDOUT; @@ -337,14 +345,21 @@ static int wait_for_pll(struct clk_alpha_pll *pll, u32 mask, bool inverse, #define wait_for_pll_update_ack_clear(pll) \ wait_for_pll(pll, ALPHA_PLL_ACK_LATCH, 1, "update_ack_clear") +#define wait_for_pll_latch_ack(pll) \ + wait_for_pll(pll, ALPHA_PLL_ACK_LATCH, 0, "latch ack") + void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap, const struct alpha_pll_config *config) { u32 val, mask; - regmap_write(regmap, PLL_L_VAL(pll), config->l); - regmap_write(regmap, PLL_ALPHA_VAL(pll), config->alpha); - regmap_write(regmap, PLL_CONFIG_CTL(pll), config->config_ctl_val); + if (config->l) + regmap_write(regmap, PLL_L_VAL(pll), config->l); + if (config->alpha) + regmap_write(regmap, PLL_ALPHA_VAL(pll), config->alpha); + if (config->config_ctl_val) + regmap_write(regmap, PLL_CONFIG_CTL(pll), + config->config_ctl_val); if (pll_has_64bit_config(pll)) regmap_write(regmap, PLL_CONFIG_CTL_U(pll), @@ -353,25 +368,60 @@ void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap, if (pll_alpha_width(pll) > 32) regmap_write(regmap, PLL_ALPHA_VAL_U(pll), config->alpha_hi); - val = config->main_output_mask; - val |= config->aux_output_mask; - val |= config->aux2_output_mask; - val |= config->early_output_mask; - val |= config->pre_div_val; - val |= config->post_div_val; - val |= config->vco_val; - val |= config->alpha_en_mask; - val |= config->alpha_mode_mask; + if (config->main_output_mask || config->aux_output_mask || + config->aux2_output_mask || config->early_output_mask || + config->pre_div_val || config->vco_val || + config->alpha_en_mask) { + val = config->main_output_mask; + val |= config->aux_output_mask; + val |= config->aux2_output_mask; + val |= config->early_output_mask; + val |= config->pre_div_val; + val |= config->vco_val; + val |= config->alpha_en_mask; - mask = config->main_output_mask; - mask |= config->aux_output_mask; - mask |= config->aux2_output_mask; - mask |= config->early_output_mask; - mask |= config->pre_div_mask; - mask |= config->post_div_mask; - mask |= config->vco_mask; + mask = config->main_output_mask; + mask |= config->aux_output_mask; + mask |= config->aux2_output_mask; + mask |= config->early_output_mask; + mask |= config->pre_div_mask; + mask |= config->vco_mask; + mask |= config->alpha_en_mask; - regmap_update_bits(regmap, PLL_USER_CTL(pll), mask, val); + regmap_update_bits(regmap, PLL_USER_CTL(pll), mask, val); + } + + if (config->post_div_mask) { + mask = config->post_div_mask; + val = config->post_div_val; + regmap_update_bits(regmap, PLL_USER_CTL(pll), mask, val); + } + + /* Do not bypass the latch interface */ + if (pll->flags & SUPPORTS_SLEW) + regmap_update_bits(regmap, PLL_USER_CTL_U(pll), + PLL_LATCH_INTERFACE, (u32)~PLL_LATCH_INTERFACE); + + if (pll->flags & SUPPORTS_DYNAMIC_UPDATE) { + regmap_update_bits(regmap, PLL_MODE(pll), + PLL_UPDATE_BYPASS, + PLL_UPDATE_BYPASS); + } + + if (config->test_ctl_mask) { + mask = config->test_ctl_mask; + val = config->test_ctl_val; + regmap_update_bits(regmap, PLL_TEST_CTL(pll), mask, val); + } + + if (config->test_ctl_hi_mask) { + mask = config->test_ctl_hi_mask; + val = config->test_ctl_hi_val; + regmap_update_bits(regmap, PLL_TEST_CTL_U(pll), mask, val); + } + if (pll->flags & SUPPORTS_DYNAMIC_UPDATE) + regmap_update_bits(regmap, PLL_MODE(pll), PLL_UPDATE_BYPASS, + PLL_UPDATE_BYPASS); if (pll->flags & SUPPORTS_FSM_MODE) qcom_pll_set_fsm_mode(regmap, PLL_MODE(pll), 6, 0); @@ -610,6 +660,48 @@ 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_dynamic_update(struct clk_alpha_pll *pll) +{ + int ret; + + /* 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); + + ret = wait_for_pll_latch_ack(pll); + if (ret) + return ret; + + /* Return latch input to 0 */ + regmap_update_bits(pll->clkr.regmap, PLL_MODE(pll), + PLL_UPDATE, (u32)~PLL_UPDATE); + + ret = wait_for_pll_enable_lock(pll); + if (ret) + return ret; + + return 0; +} + +static const struct pll_vco_data + *find_vco_data(const struct pll_vco_data *data, + unsigned long rate, size_t size) +{ + int i; + + if (!data) + return NULL; + + for (i = 0; i < size; i++) { + if (rate == data[i].freq) + return &data[i]; + } + + return &data[i - 1]; +} static int __clk_alpha_pll_update_latch(struct clk_alpha_pll *pll) { @@ -644,7 +736,10 @@ static int __clk_alpha_pll_update_latch(struct clk_alpha_pll *pll) return ret; } - ret = wait_for_pll_update_ack_clear(pll); + if (pll->flags & SUPPORTS_DYNAMIC_UPDATE) + ret = wait_for_pll_enable_lock(pll); + else + ret = wait_for_pll_update_ack_clear(pll); if (ret) return ret; @@ -669,17 +764,36 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, int (*is_enabled)(struct clk_hw *)) { struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + struct clk_alpha_pll_postdiv *pll_postdiv = + to_clk_alpha_pll_postdiv(hw); const struct pll_vco *vco; + const struct pll_vco_data *data; u32 l, alpha_width = pll_alpha_width(pll); u64 a; + unsigned long rrate; + + rrate = alpha_pll_round_rate(rate, prate, &l, &a, alpha_width); + if (rrate != rate) { + pr_err("alpha_pll: Call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } - rate = alpha_pll_round_rate(rate, prate, &l, &a, alpha_width); vco = alpha_pll_find_vco(pll, rate); if (pll->vco_table && !vco) { pr_err("alpha pll not in a valid vco range\n"); return -EINVAL; } + /* + * For PLLs that do not support dynamic programming (dynamic_update + * is not set), ensure PLL is off before changing rate. For + * optimization reasons, assume no downstream clock is actively + * using it. + */ + if (is_enabled(&pll->clkr.hw) && + !(pll->flags & SUPPORTS_DYNAMIC_UPDATE)) + hw->init->ops->disable(hw); + regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l); if (alpha_width > ALPHA_BITWIDTH) @@ -696,9 +810,31 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, vco->val << PLL_VCO_SHIFT); } + data = find_vco_data(pll->vco_data, rate, pll->num_vco_data); + if (data) { + if (data->freq == rate) + regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), + PLL_POST_DIV_MASK(pll_postdiv) + << PLL_POST_DIV_SHIFT, + data->post_div_val << PLL_POST_DIV_SHIFT); + else + regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), + PLL_POST_DIV_MASK(pll_postdiv) + << PLL_POST_DIV_SHIFT, + 0x0 << PLL_VCO_SHIFT); + } + regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), PLL_ALPHA_EN, PLL_ALPHA_EN); + if (is_enabled(&pll->clkr.hw) && + (pll->flags & SUPPORTS_DYNAMIC_UPDATE)) + clk_alpha_pll_dynamic_update(pll); + + if (is_enabled(&pll->clkr.hw) && + !(pll->flags & SUPPORTS_DYNAMIC_UPDATE)) + hw->init->ops->enable(hw); + return clk_alpha_pll_update_latch(pll, is_enabled); } @@ -724,6 +860,9 @@ static long clk_alpha_pll_round_rate(struct clk_hw *hw, unsigned long rate, u64 a; unsigned long min_freq, max_freq; + if (rate < pll->min_supported_freq) + return pll->min_supported_freq; + rate = alpha_pll_round_rate(rate, *prate, &l, &a, alpha_width); if (!pll->vco_table || alpha_pll_find_vco(pll, rate)) return rate; @@ -1602,13 +1741,17 @@ static void clk_alpha_pll_list_registers(struct seq_file *f, struct clk_hw *hw) struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); int size, i, val; - static struct clk_register_data data[] = { - {"PLL_MODE", 0x0}, - {"PLL_L_VAL", 0x4}, - {"PLL_ALPHA_VAL", 0x8}, - {"PLL_ALPHA_VAL_U", 0xC}, - {"PLL_USER_CTL", 0x10}, - {"PLL_CONFIG_CTL", 0x18}, + static struct clk_register_data data[] = { + {"PLL_MODE", PLL_OFF_MODE}, + {"PLL_L_VAL", PLL_OFF_L_VAL}, + {"PLL_ALPHA_VAL", PLL_OFF_ALPHA_VAL}, + {"PLL_ALPHA_VAL_U", PLL_OFF_ALPHA_VAL_U}, + {"PLL_TEST_CTL", PLL_OFF_TEST_CTL}, + {"PLL_TEST_CTL_U", PLL_OFF_TEST_CTL_U}, + {"PLL_USER_CTL", PLL_OFF_USER_CTL}, + {"PLL_USER_CTL_U", PLL_OFF_USER_CTL_U}, + {"PLL_CONFIG_CTL", PLL_OFF_CONFIG_CTL}, + {"PLL_STATUS", PLL_OFF_STATUS}, }; static struct clk_register_data data1[] = { @@ -1618,12 +1761,13 @@ static void clk_alpha_pll_list_registers(struct seq_file *f, struct clk_hw *hw) size = ARRAY_SIZE(data); for (i = 0; i < size; i++) { - regmap_read(pll->clkr.regmap, pll->offset + data[i].offset, - &val); + regmap_read(pll->clkr.regmap, + pll->offset + pll->regs[data[i].offset], &val); seq_printf(f, "%20s: 0x%.8x\n", data[i].name, val); } - regmap_read(pll->clkr.regmap, pll->offset + data[0].offset, &val); + regmap_read(pll->clkr.regmap, pll->offset + + pll->regs[data[0].offset], &val); if (val & PLL_FSM_ENA) { regmap_read(pll->clkr.regmap, pll->clkr.enable_reg + @@ -4110,3 +4254,200 @@ const struct clk_ops clk_agera_pll_ops = { #endif }; EXPORT_SYMBOL(clk_agera_pll_ops); + +static int clk_alpha_pll_slew_update(struct clk_alpha_pll *pll) +{ + int ret = 0; + u32 val; + + regmap_update_bits(pll->clkr.regmap, PLL_MODE(pll), + PLL_UPDATE, PLL_UPDATE); + regmap_read(pll->clkr.regmap, PLL_MODE(pll), &val); + + ret = wait_for_pll_update(pll); + if (ret) + return ret; + /* + * HPG mandates a wait of at least 570ns before polling the LOCK + * detect bit. Have a delay of 1us just to be safe. + */ + mb(); + udelay(1); + + ret = wait_for_pll_enable_lock(pll); + + return ret; +} + +static int clk_alpha_pll_slew_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + unsigned long freq_hz; + const struct pll_vco *curr_vco, *vco; + u32 l, alpha_width = pll_alpha_width(pll); + u64 a; + + freq_hz = alpha_pll_round_rate(rate, parent_rate, &l, &a, alpha_width); + if (freq_hz != rate) { + pr_err("alpha_pll: Call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + curr_vco = alpha_pll_find_vco(pll, clk_hw_get_rate(hw)); + if (!curr_vco) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + + vco = alpha_pll_find_vco(pll, freq_hz); + if (!vco) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + + /* + * Dynamic pll update will not support switching frequencies across + * vco ranges. In those cases fall back to normal alpha set rate. + */ + if (curr_vco->val != vco->val) + return clk_alpha_pll_set_rate(hw, rate, parent_rate); + + a = a << (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); + + regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l); + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL(pll), a); + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL_U(pll), a >> 32); + + /* Ensure that the write above goes through before proceeding. */ + mb(); + + if (clk_hw_is_enabled(hw)) { + if (pll->flags & SUPPORTS_DYNAMIC_UPDATE) + clk_alpha_pll_dynamic_update(pll); + else + clk_alpha_pll_slew_update(pll); + } + + return 0; +} + +/* + * Slewing plls should be bought up at frequency which is in the middle of the + * desired VCO range. So after bringing up the pll at calibration freq, set it + * back to desired frequency(that was set by previous clk_set_rate). + */ +static int clk_alpha_pll_calibrate(struct clk_hw *hw) +{ + unsigned long calibration_freq, freq_hz; + struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + struct clk_hw *parent; + const struct pll_vco *vco; + u64 a; + u32 l, alpha_width = pll_alpha_width(pll); + int rc; + + parent = clk_hw_get_parent(hw); + if (!parent) { + pr_err("alpha pll: no valid parent found\n"); + return -EINVAL; + } + + vco = alpha_pll_find_vco(pll, clk_hw_get_rate(hw)); + if (!vco) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + + /* + * As during slewing plls vco_sel won't be allowed to change, vco table + * should have only one entry table, i.e. index = 0, find the + * calibration frequency. + */ + calibration_freq = (pll->vco_table[0].min_freq + + pll->vco_table[0].max_freq)/2; + + freq_hz = alpha_pll_round_rate(calibration_freq, + clk_hw_get_rate(parent), &l, &a, alpha_width); + if (freq_hz != calibration_freq) { + pr_err("alpha_pll: call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + /* Setup PLL for calibration frequency */ + a <<= (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); + + regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l); + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL(pll), a); + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL_U(pll), a >> 32); + + 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); + + /* Bringup the pll at calibration frequency */ + rc = clk_alpha_pll_enable(hw); + if (rc) { + pr_err("alpha pll calibration failed\n"); + return rc; + } + + /* + * PLL is already running at calibration frequency. + * So slew pll to the previously set frequency. + */ + freq_hz = alpha_pll_round_rate(clk_hw_get_rate(hw), + clk_hw_get_rate(parent), &l, &a, alpha_width); + + + pr_debug("pll %s: setting back to required rate %lu, freq_hz %ld\n", + hw->init->name, clk_hw_get_rate(hw), freq_hz); + + /* Setup the PLL for the new frequency */ + a <<= (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); + + regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l); + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL(pll), a); + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL_U(pll), a >> 32); + + regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), + PLL_ALPHA_EN, PLL_ALPHA_EN); + + if (pll->flags & SUPPORTS_DYNAMIC_UPDATE) + return clk_alpha_pll_dynamic_update(pll); + else + return clk_alpha_pll_slew_update(pll); +} + +static int clk_alpha_pll_slew_enable(struct clk_hw *hw) +{ + int rc; + + rc = clk_alpha_pll_calibrate(hw); + if (rc) + return rc; + + rc = clk_alpha_pll_enable(hw); + + return rc; +} + +const struct clk_ops clk_alpha_pll_slew_ops = { + .prepare = clk_prepare_regmap, + .unprepare = clk_unprepare_regmap, + .pre_rate_change = clk_pre_change_regmap, + .post_rate_change = clk_post_change_regmap, + .enable = clk_alpha_pll_slew_enable, + .disable = clk_alpha_pll_disable, + .recalc_rate = clk_alpha_pll_recalc_rate, + .round_rate = clk_alpha_pll_round_rate, + .set_rate = clk_alpha_pll_slew_set_rate, + .init = clk_alpha_pll_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif +}; diff --git a/drivers/clk/qcom/clk-alpha-pll.h b/drivers/clk/qcom/clk-alpha-pll.h index 6592cb0a0f34..25f3b8c0c08e 100644 --- a/drivers/clk/qcom/clk-alpha-pll.h +++ b/drivers/clk/qcom/clk-alpha-pll.h @@ -54,6 +54,11 @@ enum { extern const u8 clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_MAX][PLL_OFF_MAX_REGS]; +struct pll_vco_data { + unsigned long freq; + u8 post_div_val; +}; + struct pll_vco { unsigned long min_freq; unsigned long max_freq; @@ -65,6 +70,7 @@ struct pll_vco { * @offset: base address of registers * @vco_table: array of VCO settings * @regs: alpha pll register map (see @clk_alpha_pll_regs) + * @vco_data: array of VCO data settings like post div * @clkr: regmap clock handle */ struct clk_alpha_pll { @@ -73,13 +79,24 @@ struct clk_alpha_pll { struct alpha_pll_config *config; const struct pll_vco *vco_table; size_t num_vco; + + const struct pll_vco_data *vco_data; + size_t num_vco_data; + #define SUPPORTS_OFFLINE_REQ BIT(0) #define SUPPORTS_FSM_MODE BIT(2) + /* + * Some PLLs support dynamically updating their rate without disabling + * the PLL first. Set this flag to enable this support. + */ + #define SUPPORTS_DYNAMIC_UPDATE BIT(3) #define SUPPORTS_FSM_LEGACY_MODE BIT(4) +#define SUPPORTS_SLEW BIT(4) u8 flags; struct clk_regmap clkr; + unsigned long min_supported_freq; }; /** @@ -127,6 +144,8 @@ struct alpha_pll_config { u32 pre_div_mask; u32 post_div_val; u32 post_div_mask; + u32 test_ctl_mask; + u32 test_ctl_hi_mask; u32 vco_val; u32 vco_mask; const u32 *custom_reg_offset; @@ -155,6 +174,7 @@ extern const struct clk_ops clk_alpha_pll_zonda_5lpe_ops; extern const struct clk_ops clk_alpha_pll_lucid_5lpe_ops; extern const struct clk_ops clk_alpha_pll_fixed_lucid_5lpe_ops; extern const struct clk_ops clk_alpha_pll_postdiv_lucid_5lpe_ops; +extern const struct clk_ops clk_alpha_pll_slew_ops; extern const struct clk_ops clk_trion_fixed_pll_ops; extern const struct clk_ops clk_trion_pll_postdiv_ops;