From c23dde6c193d26fae9b2a8e18140b90faeba3661 Mon Sep 17 00:00:00 2001
From: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Date: Wed, 15 Jan 2025 14:59:22 +0200
Subject: [PATCH] feat(nxp-clk): restore pll output dividers rate

Reconfiguration of the PLL may be requested while some output dividers
are already enabled. To prevent setting a different frequency for these
enabled dividers, the driver will attempt to adjust the division factor
to achieve the initially requested rate.

Change-Id: I7800c05b2f21bbdeda243db865942b647983687d
Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
---
 drivers/nxp/clk/s32cc/s32cc_clk_drv.c | 144 ++++++++++++++++++++++++--
 1 file changed, 136 insertions(+), 8 deletions(-)

diff --git a/drivers/nxp/clk/s32cc/s32cc_clk_drv.c b/drivers/nxp/clk/s32cc/s32cc_clk_drv.c
index a7320486d..c235e046f 100644
--- a/drivers/nxp/clk/s32cc/s32cc_clk_drv.c
+++ b/drivers/nxp/clk/s32cc/s32cc_clk_drv.c
@@ -281,6 +281,70 @@ static void enable_odiv(uintptr_t pll_addr, uint32_t div_index)
 	mmio_setbits_32(PLLDIG_PLLODIV(pll_addr, div_index), PLLDIG_PLLODIV_DE);
 }
 
+static void enable_odivs(uintptr_t pll_addr, uint32_t ndivs, uint32_t mask)
+{
+	uint32_t i;
+
+	for (i = 0; i < ndivs; i++) {
+		if ((mask & BIT_32(i)) != 0U) {
+			enable_odiv(pll_addr, i);
+		}
+	}
+}
+
+static int adjust_odiv_settings(const struct s32cc_pll *pll, uintptr_t pll_addr,
+				uint32_t odivs_mask, unsigned long old_vco)
+{
+	uint64_t old_odiv_freq, odiv_freq;
+	uint32_t i, pllodiv, pdiv;
+	int ret = 0;
+
+	if (old_vco == 0UL) {
+		return 0;
+	}
+
+	for (i = 0; i < pll->ndividers; i++) {
+		if ((odivs_mask & BIT_32(i)) == 0U) {
+			continue;
+		}
+
+		pllodiv = mmio_read_32(PLLDIG_PLLODIV(pll_addr, i));
+
+		pdiv = PLLDIG_PLLODIV_DIV(pllodiv);
+
+		old_odiv_freq = ((old_vco * FP_PRECISION) / (pdiv + 1U)) / FP_PRECISION;
+		pdiv = (uint32_t)(pll->vco_freq * FP_PRECISION / old_odiv_freq / FP_PRECISION);
+
+		odiv_freq = pll->vco_freq * FP_PRECISION / pdiv / FP_PRECISION;
+
+		if (old_odiv_freq != odiv_freq) {
+			ERROR("Failed to adjust ODIV %" PRIu32 " to match previous frequency\n",
+			      i);
+		}
+
+		pllodiv = PLLDIG_PLLODIV_DIV_SET(pdiv - 1U);
+		mmio_write_32(PLLDIG_PLLODIV(pll_addr, i), pllodiv);
+	}
+
+	return ret;
+}
+
+static uint32_t get_enabled_odivs(uintptr_t pll_addr, uint32_t ndivs)
+{
+	uint32_t mask = 0;
+	uint32_t pllodiv;
+	uint32_t i;
+
+	for (i = 0; i < ndivs; i++) {
+		pllodiv = mmio_read_32(PLLDIG_PLLODIV(pll_addr, i));
+		if ((pllodiv & PLLDIG_PLLODIV_DE) != 0U) {
+			mask |= BIT_32(i);
+		}
+	}
+
+	return mask;
+}
+
 static void disable_odivs(uintptr_t pll_addr, uint32_t ndivs)
 {
 	uint32_t i;
@@ -305,18 +369,54 @@ static void disable_pll_hw(uintptr_t pll_addr)
 	mmio_write_32(PLLDIG_PLLCR(pll_addr), PLLDIG_PLLCR_PLLPD);
 }
 
+static bool is_pll_enabled(uintptr_t pll_base)
+{
+	uint32_t pllcr, pllsr;
+
+	pllcr = mmio_read_32(PLLDIG_PLLCR(pll_base));
+	pllsr = mmio_read_32(PLLDIG_PLLSR(pll_base));
+
+	/* Enabled and locked PLL */
+	if ((pllcr & PLLDIG_PLLCR_PLLPD) != 0U) {
+		return false;
+	}
+
+	if ((pllsr & PLLDIG_PLLSR_LOCK) == 0U) {
+		return false;
+	}
+
+	return true;
+}
+
 static int program_pll(const struct s32cc_pll *pll, uintptr_t pll_addr,
 		       const struct s32cc_clk_drv *drv, uint32_t sclk_id,
-		       unsigned long sclk_freq)
+		       unsigned long sclk_freq, unsigned int depth)
 {
 	uint32_t rdiv = 1, mfi, mfn;
+	unsigned long old_vco = 0UL;
+	unsigned int ldepth = depth;
+	uint32_t odivs_mask;
 	int ret;
 
+	ret = update_stack_depth(&ldepth);
+	if (ret != 0) {
+		return ret;
+	}
+
 	ret = get_pll_mfi_mfn(pll->vco_freq, sclk_freq, &mfi, &mfn);
 	if (ret != 0) {
 		return -EINVAL;
 	}
 
+	odivs_mask = get_enabled_odivs(pll_addr, pll->ndividers);
+
+	if (is_pll_enabled(pll_addr)) {
+		ret = get_module_rate(&pll->desc, drv, &old_vco, ldepth);
+		if (ret != 0) {
+			return ret;
+		}
+	}
+
 	/* Disable ODIVs*/
 	disable_odivs(pll_addr, pll->ndividers);
 
@@ -334,8 +434,16 @@ static int program_pll(const struct s32cc_pll *pll, uintptr_t pll_addr,
 	mmio_write_32(PLLDIG_PLLFD(pll_addr),
 		      PLLDIG_PLLFD_MFN_SET(mfn) | PLLDIG_PLLFD_SMDEN);
 
+	ret = adjust_odiv_settings(pll, pll_addr, odivs_mask, old_vco);
+	if (ret != 0) {
+		return ret;
+	}
+
 	enable_pll_hw(pll_addr);
 
+	/* Enable out dividers */
+	enable_odivs(pll_addr, pll->ndividers, odivs_mask);
+
 	return ret;
 }
 
@@ -344,10 +452,11 @@ static int enable_pll(struct s32cc_clk_obj *module,
 		      unsigned int depth)
 {
 	const struct s32cc_pll *pll = s32cc_obj2pll(module);
+	unsigned int clk_src, ldepth = depth;
+	unsigned long sclk_freq, pll_vco;
 	const struct s32cc_clkmux *mux;
 	uintptr_t pll_addr = UL(0x0);
-	unsigned int ldepth = depth;
-	unsigned long sclk_freq;
+	bool pll_enabled;
 	uint32_t sclk_id;
 	int ret;
 
@@ -387,7 +496,20 @@ static int enable_pll(struct s32cc_clk_obj *module,
 		return -EINVAL;
 	};
 
-	return program_pll(pll, pll_addr, drv, sclk_id, sclk_freq);
+	ret = get_module_rate(&pll->desc, drv, &pll_vco, depth);
+	if (ret != 0) {
+		return ret;
+	}
+
+	pll_enabled = is_pll_enabled(pll_addr);
+	clk_src = mmio_read_32(PLLDIG_PLLCLKMUX(pll_addr));
+
+	if ((clk_src == sclk_id) && pll_enabled &&
+	    (pll_vco == pll->vco_freq)) {
+		return 0;
+	}
+
+	return program_pll(pll, pll_addr, drv, sclk_id, sclk_freq, ldepth);
 }
 
 static inline struct s32cc_pll *get_div_pll(const struct s32cc_pll_out_div *pdiv)
@@ -449,6 +571,7 @@ static int enable_pll_div(struct s32cc_clk_obj *module,
 	uintptr_t pll_addr = 0x0ULL;
 	unsigned int ldepth = depth;
 	const struct s32cc_pll *pll;
+	unsigned long pll_vco;
 	uint32_t dc;
 	int ret;
 
@@ -469,7 +592,14 @@ static int enable_pll_div(struct s32cc_clk_obj *module,
 		return -EINVAL;
 	}
 
-	dc = (uint32_t)(pll->vco_freq / pdiv->freq);
+	ret = get_module_rate(&pll->desc, drv, &pll_vco, ldepth);
+	if (ret != 0) {
+		ERROR("Failed to enable the PLL due to unknown rate for 0x%" PRIxPTR "\n",
+		      pll_addr);
+		return ret;
+	}
+
+	dc = (uint32_t)(pll_vco / pdiv->freq);
 
 	config_pll_out_div(pll_addr, pdiv->index, dc);
 
@@ -1225,7 +1355,6 @@ static int get_pll_freq(const struct s32cc_clk_obj *module,
 	unsigned int ldepth = depth;
 	uintptr_t pll_addr = 0UL;
 	uint64_t t1, t2;
-	uint32_t pllpd;
 	int ret;
 
 	ret = update_stack_depth(&ldepth);
@@ -1240,8 +1369,7 @@ static int get_pll_freq(const struct s32cc_clk_obj *module,
 	}
 
 	/* Disabled PLL */
-	pllpd = mmio_read_32(PLLDIG_PLLCR(pll_addr)) & PLLDIG_PLLCR_PLLPD;
-	if (pllpd != 0U) {
+	if (!is_pll_enabled(pll_addr)) {
 		*rate = pll->vco_freq;
 		return 0;
 	}