[PATCH v3 07/12] clk: qcom: ipq-cmn-pll: Add PON reference clock support
From: Luo Jie
Date: Fri Aug 14 2026 - 08:50:04 EST
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 <jie.luo@xxxxxxxxxxxxxxxx>
---
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