123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- // SPDX-License-Identifier: GPL-2.0
- // Copyright (c) 2018, The Linux Foundation. All rights reserved.
- #include <linux/kernel.h>
- #include <linux/export.h>
- #include <linux/regmap.h>
- #include <linux/delay.h>
- #include <linux/err.h>
- #include <linux/clk-provider.h>
- #include <linux/spinlock.h>
- #include "clk-regmap.h"
- #include "clk-hfpll.h"
- #define PLL_OUTCTRL BIT(0)
- #define PLL_BYPASSNL BIT(1)
- #define PLL_RESET_N BIT(2)
- /* Initialize a HFPLL at a given rate and enable it. */
- static void __clk_hfpll_init_once(struct clk_hw *hw)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- if (likely(h->init_done))
- return;
- /* Configure PLL parameters for integer mode. */
- if (hd->config_val)
- regmap_write(regmap, hd->config_reg, hd->config_val);
- regmap_write(regmap, hd->m_reg, 0);
- regmap_write(regmap, hd->n_reg, 1);
- if (hd->user_reg) {
- u32 regval = hd->user_val;
- unsigned long rate;
- rate = clk_hw_get_rate(hw);
- /* Pick the right VCO. */
- if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
- regval |= hd->user_vco_mask;
- regmap_write(regmap, hd->user_reg, regval);
- }
- if (hd->droop_reg)
- regmap_write(regmap, hd->droop_reg, hd->droop_val);
- h->init_done = true;
- }
- static void __clk_hfpll_enable(struct clk_hw *hw)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- u32 val;
- __clk_hfpll_init_once(hw);
- /* Disable PLL bypass mode. */
- regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);
- /*
- * H/W requires a 5us delay between disabling the bypass and
- * de-asserting the reset. Delay 10us just to be safe.
- */
- udelay(10);
- /* De-assert active-low PLL reset. */
- regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);
- /* Wait for PLL to lock. */
- if (hd->status_reg)
- /*
- * Busy wait. Should never timeout, we add a timeout to
- * prevent any sort of stall.
- */
- regmap_read_poll_timeout(regmap, hd->status_reg, val,
- !(val & BIT(hd->lock_bit)), 0,
- 100 * USEC_PER_MSEC);
- else
- udelay(60);
- /* Enable PLL output. */
- regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
- }
- /* Enable an already-configured HFPLL. */
- static int clk_hfpll_enable(struct clk_hw *hw)
- {
- unsigned long flags;
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- u32 mode;
- spin_lock_irqsave(&h->lock, flags);
- regmap_read(regmap, hd->mode_reg, &mode);
- if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
- __clk_hfpll_enable(hw);
- spin_unlock_irqrestore(&h->lock, flags);
- return 0;
- }
- static void __clk_hfpll_disable(struct clk_hfpll *h)
- {
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- /*
- * Disable the PLL output, disable test mode, enable the bypass mode,
- * and assert the reset.
- */
- regmap_update_bits(regmap, hd->mode_reg,
- PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
- }
- static void clk_hfpll_disable(struct clk_hw *hw)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- unsigned long flags;
- spin_lock_irqsave(&h->lock, flags);
- __clk_hfpll_disable(h);
- spin_unlock_irqrestore(&h->lock, flags);
- }
- static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long *parent_rate)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- unsigned long rrate;
- rate = clamp(rate, hd->min_rate, hd->max_rate);
- rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate;
- if (rrate > hd->max_rate)
- rrate -= *parent_rate;
- return rrate;
- }
- /*
- * For optimization reasons, assumes no downstream clocks are actively using
- * it.
- */
- static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long parent_rate)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- unsigned long flags;
- u32 l_val, val;
- bool enabled;
- l_val = rate / parent_rate;
- spin_lock_irqsave(&h->lock, flags);
- enabled = __clk_is_enabled(hw->clk);
- if (enabled)
- __clk_hfpll_disable(h);
- /* Pick the right VCO. */
- if (hd->user_reg && hd->user_vco_mask) {
- regmap_read(regmap, hd->user_reg, &val);
- if (rate <= hd->low_vco_max_rate)
- val &= ~hd->user_vco_mask;
- else
- val |= hd->user_vco_mask;
- regmap_write(regmap, hd->user_reg, val);
- }
- regmap_write(regmap, hd->l_reg, l_val);
- if (enabled)
- __clk_hfpll_enable(hw);
- spin_unlock_irqrestore(&h->lock, flags);
- return 0;
- }
- static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
- unsigned long parent_rate)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- u32 l_val;
- regmap_read(regmap, hd->l_reg, &l_val);
- return l_val * parent_rate;
- }
- static int clk_hfpll_init(struct clk_hw *hw)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- u32 mode, status;
- regmap_read(regmap, hd->mode_reg, &mode);
- if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
- __clk_hfpll_init_once(hw);
- return 0;
- }
- if (hd->status_reg) {
- regmap_read(regmap, hd->status_reg, &status);
- if (!(status & BIT(hd->lock_bit))) {
- WARN(1, "HFPLL %s is ON, but not locked!\n",
- __clk_get_name(hw->clk));
- clk_hfpll_disable(hw);
- __clk_hfpll_init_once(hw);
- }
- }
- return 0;
- }
- static int hfpll_is_enabled(struct clk_hw *hw)
- {
- struct clk_hfpll *h = to_clk_hfpll(hw);
- struct hfpll_data const *hd = h->d;
- struct regmap *regmap = h->clkr.regmap;
- u32 mode;
- regmap_read(regmap, hd->mode_reg, &mode);
- mode &= 0x7;
- return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
- }
- const struct clk_ops clk_ops_hfpll = {
- .enable = clk_hfpll_enable,
- .disable = clk_hfpll_disable,
- .is_enabled = hfpll_is_enabled,
- .round_rate = clk_hfpll_round_rate,
- .set_rate = clk_hfpll_set_rate,
- .recalc_rate = clk_hfpll_recalc_rate,
- .init = clk_hfpll_init,
- };
- EXPORT_SYMBOL_GPL(clk_ops_hfpll);
|