[PATCH v3 3/5] clk: spacemit: ccu_pll: add plla type clock

Yixun Lan
2025-12-26T11:02:48Z

Introduce a new clock PLLA for SpacemiT's K3 SoC which has a different
register layout comparing to previous PPL type. And, It is configured
by swcr1, swcr3 and swcr2 BIT[15:8].

Signed-off-by: Yixun Lan <dlan@gentoo.org>
---
drivers/clk/spacemit/ccu_common.h | 1 +
drivers/clk/spacemit/ccu_pll.c | 118 ++++++++++++++++++++++++++++++++++++++
drivers/clk/spacemit/ccu_pll.h | 57 ++++++++++++++----
3 files changed, 166 insertions(+), 10 deletions(-)

diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h
index 9b4ef24875e5..e23167172044 100644
--- a/drivers/clk/spacemit/ccu_common.h
+++ b/drivers/clk/spacemit/ccu_common.h
@@ -26,6 +26,7 @@ struct ccu_common {
/* For PLL */
struct {
u32 reg_swcr1;
+ u32 reg_swcr2;
u32 reg_swcr3;
};
};
diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c
index 76d0244873d8..d4066a0ed452 100644
--- a/drivers/clk/spacemit/ccu_pll.c
+++ b/drivers/clk/spacemit/ccu_pll.c
@@ -17,6 +17,9 @@
#define PLL_SWCR3_EN ((u32)BIT(31))
#define PLL_SWCR3_MASK GENMASK(30, 0)

+#define PLLA_SWCR2_EN ((u32)BIT(16))
+#define PLLA_SWCR2_MASK GENMASK(15, 8)
+
static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll,
unsigned long rate)
{
@@ -148,6 +151,110 @@ static int ccu_pll_init(struct clk_hw *hw)
return 0;
}

+static const struct ccu_pll_rate_tbl *ccu_plla_lookup_matched_entry(struct ccu_pll *pll)
+{
+ struct ccu_pll_config *config = &pll->config;
+ const struct ccu_pll_rate_tbl *entry;
+ u32 i, swcr1, swcr2, swcr3;
+
+ swcr1 = ccu_read(&pll->common, swcr1);
+ swcr2 = ccu_read(&pll->common, swcr2);
+ swcr2 &= PLLA_SWCR2_MASK;
+ swcr3 = ccu_read(&pll->common, swcr3);
+
+ for (i = 0; i < config->tbl_num; i++) {
+ entry = &config->rate_tbl[i];
+
+ if (swcr1 == entry->swcr1 &&
+ swcr2 == entry->swcr2 &&
+ swcr3 == entry->swcr3)
+ return entry;
+ }
+
+ return NULL;
+}
+
+static void ccu_plla_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
+{
+ struct ccu_common *common = &pll->common;
+
+ regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
+ regmap_write(common->regmap, common->reg_swcr3, entry->swcr3);
+ ccu_update(common, swcr2, PLLA_SWCR2_MASK, entry->swcr2);
+}
+
+static int ccu_plla_is_enabled(struct clk_hw *hw)
+{
+ struct ccu_common *common = hw_to_ccu_common(hw);
+
+ return ccu_read(common, swcr2) & PLLA_SWCR2_EN;
+}
+
+static int ccu_plla_enable(struct clk_hw *hw)
+{
+ struct ccu_pll *pll = hw_to_ccu_pll(hw);
+ struct ccu_common *common = &pll->common;
+ unsigned int tmp;
+
+ ccu_update(common, swcr2, PLLA_SWCR2_EN, PLLA_SWCR2_EN);
+
+ /* check lock status */
+ return regmap_read_poll_timeout_atomic(common->lock_regmap,
+ pll->config.reg_lock,
+ tmp,
+ tmp & pll->config.mask_lock,
+ PLL_DELAY_US, PLL_TIMEOUT_US);
+}
+
+static void ccu_plla_disable(struct clk_hw *hw)
+{
+ struct ccu_common *common = hw_to_ccu_common(hw);
+
+ ccu_update(common, swcr2, PLLA_SWCR2_EN, 0);
+}
+
+/*
+ * PLLAs must be gated before changing rate, which is ensured by
+ * flag CLK_SET_RATE_GATE.
+ */
+static int ccu_plla_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct ccu_pll *pll = hw_to_ccu_pll(hw);
+ const struct ccu_pll_rate_tbl *entry;
+
+ entry = ccu_pll_lookup_best_rate(pll, rate);
+ ccu_plla_update_param(pll, entry);
+
+ return 0;
+}
+
+static unsigned long ccu_plla_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct ccu_pll *pll = hw_to_ccu_pll(hw);
+ const struct ccu_pll_rate_tbl *entry;
+
+ entry = ccu_plla_lookup_matched_entry(pll);
+
+ WARN_ON_ONCE(!entry);
+
+ return entry ? entry->rate : 0;
+}
+
+static int ccu_plla_init(struct clk_hw *hw)
+{
+ struct ccu_pll *pll = hw_to_ccu_pll(hw);
+
+ if (ccu_plla_lookup_matched_entry(pll))
+ return 0;
+
+ ccu_plla_disable(hw);
+ ccu_plla_update_param(pll, &pll->config.rate_tbl[0]);
+
+ return 0;
+}
+
const struct clk_ops spacemit_ccu_pll_ops = {
.init = ccu_pll_init,
.enable = ccu_pll_enable,
@@ -158,3 +265,14 @@ const struct clk_ops spacemit_ccu_pll_ops = {
.is_enabled = ccu_pll_is_enabled,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_pll_ops, "CLK_SPACEMIT");
+
+const struct clk_ops spacemit_ccu_plla_ops = {
+ .init = ccu_plla_init,
+ .enable = ccu_plla_enable,
+ .disable = ccu_plla_disable,
+ .set_rate = ccu_plla_set_rate,
+ .recalc_rate = ccu_plla_recalc_rate,
+ .determine_rate = ccu_pll_determine_rate,
+ .is_enabled = ccu_plla_is_enabled,
+};
+EXPORT_SYMBOL_NS_GPL(spacemit_ccu_plla_ops, "CLK_SPACEMIT");
diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h
index 0592f4c3068c..e41db5c97c1a 100644
--- a/drivers/clk/spacemit/ccu_pll.h
+++ b/drivers/clk/spacemit/ccu_pll.h
@@ -16,14 +16,31 @@
* configuration.
*
* @rate: PLL rate
- * @swcr1: Register value of PLLX_SW1_CTRL (PLLx_SWCR1).
- * @swcr3: Register value of the PLLx_SW3_CTRL's lowest 31 bits of
- * PLLx_SW3_CTRL (PLLx_SWCR3). This highest bit is for enabling
- * the PLL and not contained in this field.
+ * @swcr1: Value of register PLLx_SW1_CTRL.
+ * @swcr2: Value of register PLLAx_SW2_CTRL.
+ * @swcr3: value of register PLLx_SW3_CTRL.
+ *
+ * See below tables for the register used in PPL/PPLA clocks
+ *
+ * Regular PLL type
+ * | Enable | swcr3 | PLLx_SW3_CTRL - BIT[31] |
+ * -----------------------------------------------
+ * | Config | swcr1 | PLLx_SW1_CTRL - BIT[31:0] |
+ * | | swcr2 | Not used |
+ * | | swcr3 | PLLx_SW3_CTRL - BIT[30:0] |
+ *
+ * Special PLL type A
+ * | Enable | swcr2 | PLLAx_SW2_CTRL - BIT[16] |
+ * -----------------------------------------------
+ * | Config | swcr1 | PLLAx_SW1_CTRL - BIT[31:0] |
+ * | | swcr2 | PLLAx_SW2_CTRL - BIT[15:8] |
+ * | | swcr3 | PLLAx_SW3_CTRL - BIT[31:0] |
+ *
*/
struct ccu_pll_rate_tbl {
unsigned long rate;
u32 swcr1;
+ u32 swcr2;
u32 swcr3;
};

@@ -36,11 +53,19 @@ struct ccu_pll_config {

#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \
{ \
- .rate = _rate, \
+ .rate = _rate, \
.swcr1 = _swcr1, \
.swcr3 = _swcr3, \
}

+#define CCU_PLLA_RATE(_rate, _swcr1, _swcr2, _swcr3) \
+ { \
+ .rate = _rate, \
+ .swcr1 = _swcr1, \
+ .swcr2 = _swcr2, \
+ .swcr3 = _swcr3, \
+ }
+
struct ccu_pll {
struct ccu_common common;
struct ccu_pll_config config;
@@ -54,26 +79,37 @@ struct ccu_pll {
.mask_lock = (_mask_lock), \
}

-#define CCU_PLL_HWINIT(_name, _flags) \
+#define CCU_PLL_COMMON_HWINIT(_name, _ops, _flags) \
(&(struct clk_init_data) { \
.name = #_name, \
- .ops = &spacemit_ccu_pll_ops, \
+ .ops = _ops, \
.parent_data = &(struct clk_parent_data) { .index = 0 }, \
.num_parents = 1, \
.flags = _flags, \
})

-#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3, _reg_lock, \
- _mask_lock, _flags) \
+#define CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \
+ _reg_lock, _mask_lock, _ops, _flags) \
static struct ccu_pll _name = { \
.config = CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock), \
.common = { \
.reg_swcr1 = _reg_swcr1, \
+ .reg_swcr2 = _reg_swcr2, \
.reg_swcr3 = _reg_swcr3, \
- .hw.init = CCU_PLL_HWINIT(_name, _flags) \
+ .hw.init = CCU_PLL_COMMON_HWINIT(_name, _ops, _flags) \
} \
}

+#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3, _reg_lock, \
+ _mask_lock, _flags) \
+ CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, 0, _reg_swcr3, \
+ _reg_lock, _mask_lock, &spacemit_ccu_pll_ops, _flags)
+
+#define CCU_PLLA_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \
+ _reg_lock, _mask_lock, _flags) \
+ CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \
+ _reg_lock, _mask_lock, &spacemit_ccu_plla_ops, _flags)
+
static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
@@ -82,5 +118,6 @@ static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw)
}

extern const struct clk_ops spacemit_ccu_pll_ops;
+extern const struct clk_ops spacemit_ccu_plla_ops;

#endif

--
2.52.0