[PATCH 1/4] clk: renesas: rzv2h-cpg: Allow MIPI DSI to reuse PLL divider parameters
From: Prabhakar
Date: Tue Nov 03 2026 - 12:34:38 EST
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
The CPG PLLDSI output feeds the PLLDSI inside the MIPI DSI block, so
the divider parameters selected by the CPG must also satisfy the DSI
PLL constraints. The DSI driver checks both sets of constraints during
mode validation, but the CPG's .determine_rate() callback performs its
own search and can select a different combination.
Allow the DSI driver to cache the selected divider parameters per DSI
PLL instance and reuse them in .determine_rate() when the requested
rate matches the cached frequency, including its error. Protect the
shared cache with a spinlock to avoid races between mode validation
and rate determination.
Move the maximum number of DSI PLL instances to the public header so
the CPG driver and library share the same limit.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@xxxxxxxxxxxxxx>
---
drivers/clk/renesas/rzv2h-cpg-lib.c | 48 +++++++++++++++++++++++++++++
drivers/clk/renesas/rzv2h-cpg.c | 15 ++++++---
include/linux/clk/renesas.h | 16 ++++++++++
3 files changed, 75 insertions(+), 4 deletions(-)
diff --git a/drivers/clk/renesas/rzv2h-cpg-lib.c b/drivers/clk/renesas/rzv2h-cpg-lib.c
index 124239c7327e..4b59bf60edb0 100644
--- a/drivers/clk/renesas/rzv2h-cpg-lib.c
+++ b/drivers/clk/renesas/rzv2h-cpg-lib.c
@@ -7,12 +7,60 @@
*
*/
+#include <linux/bug.h>
+#include <linux/cleanup.h>
#include <linux/clk/renesas.h>
#include <linux/export.h>
#include <linux/math.h>
+#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/units.h>
+/*
+ * Divider parameters handed over by the MIPI DSI driver, which evaluates both
+ * the CPG and the DSI PLL constraints before settling on a combination. They
+ * are indexed by DSI PLL instance, as the producer only knows the instance it
+ * targets, so they are shared by all CPG instances. The producer runs in the
+ * DSI mode validation path and the consumer in .determine_rate(), so the
+ * accesses are protected by a spinlock.
+ */
+static DEFINE_SPINLOCK(cache_pll_dsi_lock);
+static struct rzv2h_pll_div_pars cache_pll_dsi_pars[RZV2H_CPG_MAX_DSI_PLL];
+
+/**
+ * rzv2h_cpg_cache_pll_pars - Cache divider parameters for a PLLDSI instance
+ *
+ * @pars: PLL and divider parameters computed by the MIPI DSI driver
+ * @dsi_instance: PLLDSI instance the parameters are meant for
+ *
+ * Stores @pars so that the PLLDSI divider's .determine_rate() can use them
+ * instead of running its own search, when asked for the matching rate. The
+ * DSI driver needs this because the display clock is set before the DSI
+ * bridge is enabled, and the CPG alone does not know the DSI PLL constraints.
+ *
+ * A later call for the same @dsi_instance overwrites the previous entry.
+ * An out-of-range @dsi_instance is ignored with a warning.
+ */
+void rzv2h_cpg_cache_pll_pars(struct rzv2h_pll_div_pars pars,
+ unsigned int dsi_instance)
+{
+ if (WARN_ON(dsi_instance >= RZV2H_CPG_MAX_DSI_PLL))
+ return;
+
+ guard(spinlock)(&cache_pll_dsi_lock);
+ cache_pll_dsi_pars[dsi_instance] = pars;
+}
+EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_cache_pll_pars, "RZV2H_CPG");
+
+struct rzv2h_pll_div_pars rzv2h_cpg_get_cached_pll_pars(unsigned int dsi_instance)
+{
+ if (WARN_ON(dsi_instance >= RZV2H_CPG_MAX_DSI_PLL))
+ return (struct rzv2h_pll_div_pars){0};
+
+ guard(spinlock)(&cache_pll_dsi_lock);
+ return cache_pll_dsi_pars[dsi_instance];
+}
+
/**
* rzv2h_cpg_get_pll_pars - Finds the best combination of PLL parameters
* for a given frequency.
diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cpg.c
index 7ca2447fd811..e6f6a3fec449 100644
--- a/drivers/clk/renesas/rzv2h-cpg.c
+++ b/drivers/clk/renesas/rzv2h-cpg.c
@@ -12,6 +12,7 @@
*/
#include <linux/bitfield.h>
+#include <linux/bug.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clk/renesas.h>
@@ -72,9 +73,6 @@
#define CPG_CLKSTATUS0 (0x700)
-/* On RZ/G3E SoC we have two DSI PLLs */
-#define MAX_CPG_DSI_PLL 2
-
/**
* struct rzv2h_pll_dsi_info - PLL DSI information, holds the limits and parameters
*
@@ -123,7 +121,7 @@ struct rzv2h_cpg_priv {
struct reset_controller_dev rcdev;
- struct rzv2h_pll_dsi_info pll_dsi_info[MAX_CPG_DSI_PLL];
+ struct rzv2h_pll_dsi_info pll_dsi_info[RZV2H_CPG_MAX_DSI_PLL];
};
#define rcdev_to_priv(x) container_of(x, struct rzv2h_cpg_priv, rcdev)
@@ -235,6 +233,7 @@ static int rzv2h_cpg_plldsi_div_determine_rate(struct clk_hw *hw,
{
struct rzv2h_plldsi_div_clk *dsi_div = to_plldsi_div_clk(hw);
struct pll_clk *pll_clk = to_pll(clk_hw_get_parent(hw));
+ struct rzv2h_pll_div_pars cached_dsi_parameters;
struct rzv2h_cpg_priv *priv = dsi_div->priv;
u8 table[RZV2H_MAX_DIV_TABLES] = { 0 };
struct rzv2h_pll_div_pars *dsi_params;
@@ -247,6 +246,14 @@ static int rzv2h_cpg_plldsi_div_determine_rate(struct clk_hw *hw,
dsi_params = &dsi_info->pll_dsi_parameters;
rate_millihz = mul_u32_u32(req->rate, MILLI);
+
+ cached_dsi_parameters = rzv2h_cpg_get_cached_pll_pars(pll_clk->pll.instance);
+ if (rate_millihz == cached_dsi_parameters.div.error_millihz +
+ cached_dsi_parameters.div.freq_millihz) {
+ *dsi_params = cached_dsi_parameters;
+ goto exit_determine_rate;
+ }
+
if (rate_millihz == dsi_params->div.error_millihz + dsi_params->div.freq_millihz)
goto exit_determine_rate;
diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h
index c9495558cd5c..c1582ed9bf69 100644
--- a/include/linux/clk/renesas.h
+++ b/include/linux/clk/renesas.h
@@ -159,6 +159,9 @@ struct rzv2h_pll_div_pars {
} div;
};
+/* On RZ/G3E SoC we have two DSI PLLs */
+#define RZV2H_CPG_MAX_DSI_PLL 2
+
#define RZV2H_CPG_PLL_DSI_LIMITS(name) \
static const struct rzv2h_pll_limits (name) = { \
.fout = { .min = 25 * MEGA, .max = 375 * MEGA }, \
@@ -196,6 +199,9 @@ bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits,
struct rzv2h_pll_div_pars *pars,
const u8 *table, u8 table_size, u64 freq_millihz);
+void rzv2h_cpg_cache_pll_pars(struct rzv2h_pll_div_pars pars,
+ unsigned int dsi_instance);
+struct rzv2h_pll_div_pars rzv2h_cpg_get_cached_pll_pars(unsigned int dsi_instance);
#else
static inline bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
struct rzv2h_pll_pars *pars,
@@ -211,6 +217,16 @@ static inline bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *li
{
return false;
}
+
+static inline void rzv2h_cpg_cache_pll_pars(struct rzv2h_pll_div_pars pars,
+ unsigned int dsi_instance)
+{
+}
+
+static inline struct rzv2h_pll_div_pars rzv2h_cpg_get_cached_pll_pars(unsigned int dsi_instance)
+{
+ return (struct rzv2h_pll_div_pars){0};
+}
#endif
#define rzv2h_get_pll_pars rzv2h_cpg_get_pll_pars
--
2.56.0