| // 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); | |
| } | |
| /* Write L_VAL from conf if it exist */ | |
| if (hd->l_val) | |
| regmap_write(regmap, hd->l_reg, hd->l_val); | |
| 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 int clk_hfpll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) | |
| { | |
| struct clk_hfpll *h = to_clk_hfpll(hw); | |
| struct hfpll_data const *hd = h->d; | |
| unsigned long rrate; | |
| req->rate = clamp(req->rate, hd->min_rate, hd->max_rate); | |
| rrate = DIV_ROUND_UP(req->rate, req->best_parent_rate) * req->best_parent_rate; | |
| if (rrate > hd->max_rate) | |
| rrate -= req->best_parent_rate; | |
| req->rate = rrate; | |
| return 0; | |
| } | |
| /* | |
| * 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_hw_is_enabled(hw); | |
| 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_hw_get_name(hw)); | |
| 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, | |
| .determine_rate = clk_hfpll_determine_rate, | |
| .set_rate = clk_hfpll_set_rate, | |
| .recalc_rate = clk_hfpll_recalc_rate, | |
| .init = clk_hfpll_init, | |
| }; | |
| EXPORT_SYMBOL_GPL(clk_ops_hfpll); |