[PATCH v3 07/12] clk: qcom: ipq-cmn-pll: Add PON reference clock support
Luo Jie <[email protected]>
| Newsgroups | org.kernel.vger.linux-clk,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The PON (Passive Optical Network) reference clock's parent is cmn_pll_div2 (CMN PLL rate / 2); it is then further divided by a configurable 8-bit divider (1-255). The clock supports two modes: - UNIPHY mode (CMN_PLL_PON_MODE_SEL = 0): fixed 31.25 MHz output - PON mode (CMN_PLL_PON_MODE_SEL = 1): parent_rate / divider The clock output can be independently enabled and disabled via the CMN_PLL_PON_EN bit. Signed-off-by: Luo Jie <[email protected]> --- drivers/clk/qcom/ipq-cmn-pll.c | 182 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c index a5c2224ff22e..8987e0ff7dea 100644 --- a/drivers/clk/qcom/ipq-cmn-pll.c +++ b/drivers/clk/qcom/ipq-cmn-pll.c @@ -73,6 +73,11 @@ #define CMN_PLL_NSS_CLK_SEL GENMASK(13, 8) #define CMN_PLL_PPE_CLK_SEL GENMASK(5, 0) +#define CMN_PLL_PON_CONFIG 0x42c +#define CMN_PLL_PON_MODE_SEL BIT(9) +#define CMN_PLL_PON_EN BIT(8) +#define CMN_PLL_PON_DIV_CTRL GENMASK(7, 0) + #define CMN_PLL_POWER_ON_AND_RESET 0x780 #define CMN_ANA_EN_SW_RSTN BIT(6) @@ -92,11 +97,13 @@ * @CMN_PLL_CLK_FIXED_RATE: plain fixed rate clock * @CMN_PLL_CLK_NSS: NSS clock with configurable divider * @CMN_PLL_CLK_PPE: PPE clock with configurable divider + * @CMN_PLL_CLK_PON: PON reference clock */ enum cmn_pll_clk_type { CMN_PLL_CLK_FIXED_RATE, CMN_PLL_CLK_NSS, CMN_PLL_CLK_PPE, + CMN_PLL_CLK_PON, }; /** @@ -427,6 +434,175 @@ static struct clk_hw *ipq_cmn_pll_regmap_div_register(struct platform_device *pd return &div_clk->clkr.hw; } +/* + * PON (Passive Optical Network) reference clock operations. + * The PON refclk's parent is cmn_pll_div2 (CMN PLL rate / 2); it is + * then divided by a configurable 8-bit divider (1-255). + */ +static int clk_pon_clk_enable(struct clk_hw *hw) +{ + struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw); + + return regmap_set_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG, + CMN_PLL_PON_EN); +} + +static void clk_pon_clk_disable(struct clk_hw *hw) +{ + struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw); + + regmap_clear_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG, + CMN_PLL_PON_EN); +} + +static int clk_pon_clk_is_enabled(struct clk_hw *hw) +{ + struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw); + + return regmap_test_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG, + CMN_PLL_PON_EN); +} + +static unsigned long clk_pon_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw); + u32 val, div; + int ret; + + ret = regmap_read(pon_clk->regmap, CMN_PLL_PON_CONFIG, &val); + if (ret) + return 0; + + /* Check if in UNIPHY mode (bit 9 = 0) - fixed 31.25 MHz */ + if (!(val & CMN_PLL_PON_MODE_SEL)) + return 31250000UL; + + /* PON mode: calculate from divider */ + div = FIELD_GET(CMN_PLL_PON_DIV_CTRL, val); + if (!div) + return 0; + + return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, div); +} + +static int clk_pon_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned long div, pon_rate, uniphy_rate = 31250000UL; + bool uniphy_rate_valid, pon_rate_valid; + + if (!req->rate) + return -EINVAL; + + div = DIV64_U64_ROUND_CLOSEST((u64)req->best_parent_rate, req->rate); + + /* Clamp to valid range (1-255) */ + div = clamp_t(unsigned long, div, 1, 255); + + pon_rate = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, div); + + uniphy_rate_valid = uniphy_rate >= req->min_rate && + uniphy_rate <= req->max_rate; + pon_rate_valid = pon_rate >= req->min_rate && + pon_rate <= req->max_rate; + + if (!uniphy_rate_valid && !pon_rate_valid) + return -EINVAL; + + /* Pick whichever mode gets closer to the requested rate */ + if (uniphy_rate_valid && pon_rate_valid) { + unsigned long diff_uniphy, diff_pon; + + diff_uniphy = abs_diff(req->rate, uniphy_rate); + diff_pon = abs_diff(req->rate, pon_rate); + req->rate = diff_uniphy < diff_pon ? uniphy_rate : pon_rate; + } else { + req->rate = uniphy_rate_valid ? uniphy_rate : pon_rate; + } + + return 0; +} + +static int clk_pon_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw); + unsigned long div; + + if (rate == 0) + return -EINVAL; + + /* + * An exact request for 31.25 MHz is always satisfiable by UNIPHY + * mode, even though PON mode with a suitable divider can produce + * the same frequency for some parent rates. Preferring UNIPHY here + * is safe: this function is only ever called with a rate produced + * by clk_pon_clk_determine_rate() through the standard + * clk_set_rate() path, and either mode yields the identical output + * rate for this value. + */ + if (rate == 31250000UL) + return regmap_clear_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG, + CMN_PLL_PON_MODE_SEL); + + div = DIV64_U64_ROUND_CLOSEST((u64)parent_rate, rate); + if (div == 0 || div > 255) + return -EINVAL; + + /* Switch to PON mode and program the divider in a single write */ + return regmap_update_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG, + CMN_PLL_PON_MODE_SEL | CMN_PLL_PON_DIV_CTRL, + CMN_PLL_PON_MODE_SEL | + FIELD_PREP(CMN_PLL_PON_DIV_CTRL, div)); +} + +static const struct clk_ops clk_pon_clk_ops = { + .enable = clk_pon_clk_enable, + .disable = clk_pon_clk_disable, + .is_enabled = clk_pon_clk_is_enabled, + .recalc_rate = clk_pon_clk_recalc_rate, + .determine_rate = clk_pon_clk_determine_rate, + .set_rate = clk_pon_clk_set_rate, +}; + +static struct clk_hw *ipq_cmn_pll_pon_clk_register(struct platform_device *pdev, + struct regmap *regmap, + struct clk_hw *parent_hw, + const char *name) +{ + struct clk_parent_data pdata = { .hw = parent_hw }; + struct device *dev = &pdev->dev; + struct clk_init_data init = {}; + struct clk_cmn_pll *pon_clk; + int ret; + + pon_clk = devm_kzalloc(dev, sizeof(*pon_clk), GFP_KERNEL); + if (!pon_clk) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.parent_data = &pdata; + init.num_parents = 1; + init.ops = &clk_pon_clk_ops; + /* + * The PON reference clock may already be enabled by bootloader + * or consumed by hardware without an in-kernel client driver. + * Add CLK_IGNORE_UNUSED so the clock framework does not disable + * it when no consumer has claimed it. + */ + init.flags = CLK_IGNORE_UNUSED; + + pon_clk->hw.init = &init; + pon_clk->regmap = regmap; + + ret = devm_clk_hw_register(dev, &pon_clk->hw); + if (ret) + return ERR_PTR(ret); + + return &pon_clk->hw; +} + static int ipq_cmn_pll_register_clks(struct platform_device *pdev) { const struct cmn_pll_fixed_output_clk *p, *fixed_clk; @@ -499,6 +675,12 @@ static int ipq_cmn_pll_register_clks(struct platform_device *pdev) fixed_clk[i].name, CMN_PLL_PPE_CLK_SEL); break; + case CMN_PLL_CLK_PON: + hw = ipq_cmn_pll_pon_clk_register(pdev, + cmn_pll->regmap, + cmn_pll->div2_hw, + fixed_clk[i].name); + break; } if (IS_ERR(hw)) -- 2.43.0