* [PATCH 0/4] Add PLL3 and LCDC_CLKD support for RZ/T2H and RZ/N2H
@ 2026-05-11 19:19 Prabhakar
2026-05-11 19:19 ` [PATCH 1/4] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations Prabhakar
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: Prabhakar @ 2026-05-11 19:19 UTC (permalink / raw)
To: Geert Uytterhoeven, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Magnus Damm
Cc: linux-renesas-soc, linux-clk, linux-kernel, devicetree, Prabhakar,
Biju Das, Fabrizio Castro, Lad Prabhakar
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Hi all,
This series adds support for the PLL3 and LCDC_CLKD clocks on Renesas
RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. These clocks are essential
for the display pipeline, specifically feeding the LCD controller.
Key Changes:
- PLL Reference Flexibility in the RZ/V2H(P) CPG driver
- MSTP Dummy-Read Mechanism
- LCDC implementation in the RZ/T2H CPG driver.
Cheers,
Prabhakar
Lad Prabhakar (4):
clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for
calculations
clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for
LCDC on RZ/T2H
dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID
clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H
display pipeline
drivers/clk/renesas/Kconfig | 2 +
drivers/clk/renesas/r9a09g077-cpg.c | 369 +++++++++++++++++-
drivers/clk/renesas/renesas-cpg-mssr.c | 65 ++-
drivers/clk/renesas/rzv2h-cpg.c | 7 +-
.../clock/renesas,r9a09g077-cpg-mssr.h | 1 +
.../clock/renesas,r9a09g087-cpg-mssr.h | 1 +
include/linux/clk/renesas.h | 5 +
7 files changed, 444 insertions(+), 6 deletions(-)
base-commit: 5fcbbc1fcc4fa78bb5a184caa2c32db423676577
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 1/4] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations
2026-05-11 19:19 [PATCH 0/4] Add PLL3 and LCDC_CLKD support for RZ/T2H and RZ/N2H Prabhakar
@ 2026-05-11 19:19 ` Prabhakar
2026-05-13 0:02 ` sashiko-bot
2026-05-11 19:19 ` [PATCH 2/4] clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for LCDC on RZ/T2H Prabhakar
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Prabhakar @ 2026-05-11 19:19 UTC (permalink / raw)
To: Geert Uytterhoeven, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Magnus Damm
Cc: linux-renesas-soc, linux-clk, linux-kernel, devicetree, Prabhakar,
Biju Das, Fabrizio Castro, Lad Prabhakar
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Use a per-SoC PLL reference input frequency for PLL parameter
calculations instead of relying on the hardcoded 24MHz constant.
Add an input_fref field to struct rzv2h_pll_limits and derive the PLL
reference frequency from it in rzv2h_get_pll_pars(). Fall back to the
existing 24MHz value when no SoC-specific input is provided.
This allows the existing PLL divider calculation logic to be reused
unchanged on SoCs such as RZ/T2H, which use a 48MHz PLL reference
input instead of the 24MHz reference used on RZ/V2H(P), while keeping
current RZ/V2H(P) behaviour intact.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
drivers/clk/renesas/rzv2h-cpg.c | 7 ++++---
include/linux/clk/renesas.h | 5 +++++
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cpg.c
index e271c04cee34..b1d640e5c0f9 100644
--- a/drivers/clk/renesas/rzv2h-cpg.c
+++ b/drivers/clk/renesas/rzv2h-cpg.c
@@ -242,6 +242,7 @@ struct rzv2h_plldsi_div_clk {
bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits,
struct rzv2h_pll_pars *pars, u64 freq_millihz)
{
+ unsigned long input_fref = limits->input_fref ?: RZ_V2H_OSC_CLK_IN_MEGA;
u64 fout_min_millihz = mul_u32_u32(limits->fout.min, MILLI);
u64 fout_max_millihz = mul_u32_u32(limits->fout.max, MILLI);
struct rzv2h_pll_pars p, best;
@@ -254,7 +255,7 @@ bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits,
best.error_millihz = S64_MAX;
for (p.p = limits->p.min; p.p <= limits->p.max; p.p++) {
- u32 fref = RZ_V2H_OSC_CLK_IN_MEGA / p.p;
+ u32 fref = input_fref / p.p;
u16 divider;
for (divider = 1 << limits->s.min, p.s = limits->s.min;
@@ -335,9 +336,9 @@ bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits,
continue;
/* PLL_M component of (output * 65536 * PLL_P) */
- output = mul_u32_u32(p.m * 65536, RZ_V2H_OSC_CLK_IN_MEGA);
+ output = mul_u32_u32(p.m * 65536, input_fref);
/* PLL_K component of (output * 65536 * PLL_P) */
- output += p.k * RZ_V2H_OSC_CLK_IN_MEGA;
+ output += p.k * input_fref;
/* Make it in mHz */
output *= MILLI;
output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider);
diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h
index 0949400f44de..bd2d49e7290f 100644
--- a/include/linux/clk/renesas.h
+++ b/include/linux/clk/renesas.h
@@ -53,6 +53,8 @@ static inline void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target) { }
* various parameters used to configure a PLL. These limits ensure
* the PLL operates within valid and stable ranges.
*
+ * @input_fref: Reference input frequency to the PLL (in MHz)
+ *
* @fout: Output frequency range (in MHz)
* @fout.min: Minimum allowed output frequency
* @fout.max: Maximum allowed output frequency
@@ -78,6 +80,8 @@ static inline void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target) { }
* @k.max: Maximum delta-sigma value
*/
struct rzv2h_pll_limits {
+ u32 input_fref;
+
struct {
u32 min;
u32 max;
@@ -156,6 +160,7 @@ struct rzv2h_pll_div_pars {
#define RZV2H_CPG_PLL_DSI_LIMITS(name) \
static const struct rzv2h_pll_limits (name) = { \
+ .input_fref = 24 * MEGA, \
.fout = { .min = 25 * MEGA, .max = 375 * MEGA }, \
.fvco = { .min = 1600 * MEGA, .max = 3200 * MEGA }, \
.m = { .min = 64, .max = 533 }, \
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 2/4] clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for LCDC on RZ/T2H
2026-05-11 19:19 [PATCH 0/4] Add PLL3 and LCDC_CLKD support for RZ/T2H and RZ/N2H Prabhakar
2026-05-11 19:19 ` [PATCH 1/4] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations Prabhakar
@ 2026-05-11 19:19 ` Prabhakar
2026-05-13 0:18 ` sashiko-bot
2026-05-11 19:19 ` [PATCH 3/4] dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID Prabhakar
2026-05-11 19:19 ` [PATCH 4/4] clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline Prabhakar
3 siblings, 1 reply; 9+ messages in thread
From: Prabhakar @ 2026-05-11 19:19 UTC (permalink / raw)
To: Geert Uytterhoeven, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Magnus Damm
Cc: linux-renesas-soc, linux-clk, linux-kernel, devicetree, Prabhakar,
Biju Das, Fabrizio Castro, Lad Prabhakar
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Per the RZ/T2H hardware manual, to secure processing after release from
the module-stop state, dummy read the same MSTPCRm register at least
seven times for most IP blocks, at least 100 times for LCDC and at
least 300 times for RTC before proceeding with subsequent processing.
The existing udelay(10) satisfies the seven dummy-read requirement for
most IP blocks. Extend this to support per-IP dummy-read requirements
by introducing a table-driven lookup, rzt2h_mstp_delay_table, where
each entry records the MSTPCRm register index, bit position and the
minimum dummy-read count from the hardware manual, converted to
microseconds via RZT2H_MSTP_READS_TO_US().
Introduce cpg_rzt2h_mstp_get_delay_us() to replace the open-coded
udelay(10) calls. In cpg_mstp_clock_endisable() the exact register and
bit are known so the lookup matches on both fields. In
cpg_mssr_resume_noirq() the register is known but not the individual
bit, so pass RZT2H_MSTP_ANY_BIT causing the lookup to match on the
register alone and return the delay for the first matching entry.
Add an entry for LCDC which requires at least 100 dummy reads. Adding
support for further IP blocks with non-default requirements only needs
a new entry in rzt2h_mstp_delay_table with no logic changes needed.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
drivers/clk/renesas/renesas-cpg-mssr.c | 65 +++++++++++++++++++++++++-
1 file changed, 63 insertions(+), 2 deletions(-)
diff --git a/drivers/clk/renesas/renesas-cpg-mssr.c b/drivers/clk/renesas/renesas-cpg-mssr.c
index 5b84cbee030b..e6bf5062e40e 100644
--- a/drivers/clk/renesas/renesas-cpg-mssr.c
+++ b/drivers/clk/renesas/renesas-cpg-mssr.c
@@ -96,6 +96,24 @@ static const u16 mstpcr_for_gen4[] = {
#define RZT2H_MSTPCR_BLOCK(x) ((x) >> RZT2H_MSTPCR_BLOCK_SHIFT)
#define RZT2H_MSTPCR_OFFSET(x) ((x) & RZT2H_MSTPCR_OFFSET_MASK)
+/* Dummy read counts as specified by the RZ/T2H hardware manual */
+#define RZT2H_MSTP_DEFAULT_DUMMY_READS 7
+#define RZT2H_MSTP_LCDC_DUMMY_READS 100
+
+/*
+ * Time per dummy read in nanoseconds, derived from the original udelay(10)
+ * which was used to satisfy the 7 dummy-read requirement:
+ * 10000 ns / 7 reads = 1429 ns per read.
+ */
+#define RZT2H_MSTP_DUMMY_READ_NS 1429
+#define RZT2H_MSTP_READS_TO_US(n) (((n) * RZT2H_MSTP_DUMMY_READ_NS) / 1000)
+#define RZT2H_MSTP_DEFAULT_DELAY_US RZT2H_MSTP_READS_TO_US(RZT2H_MSTP_DEFAULT_DUMMY_READS)
+
+#define RZT2H_MSTPCRM_INDEX 12
+#define RZT2H_MSTPCRM04_LCDC 4
+
+#define RZT2H_MSTP_ANY_BIT U32_MAX
+
static const u16 mstpcr_for_rzt2h[] = {
RZT2H_MSTPCR(0, 0x300), /* MSTPCRA */
RZT2H_MSTPCR(0, 0x304), /* MSTPCRB */
@@ -113,6 +131,35 @@ static const u16 mstpcr_for_rzt2h[] = {
RZT2H_MSTPCR(1, 0x334), /* MSTPCRN */
};
+/**
+ * struct rzt2h_mstp_delay_entry - MSTP dummy-read requirement for RZ/T2H
+ *
+ * @reg: Index into control_regs[]. Exact match.
+ * @bit: MSTP bit position, or RZT2H_MSTP_ANY_BIT for register-level match.
+ * @delay_us: Computed delay in microseconds to satisfy the dummy read requirement.
+ */
+struct rzt2h_mstp_delay_entry {
+ u32 reg;
+ u32 bit;
+ u32 delay_us;
+};
+
+/*
+ * Per RZ/T2H HW manual: to secure processing after release from the
+ * module-stop state, dummy read the same register at least seven times
+ * (except RTC and LCDC) after writing to initiate release from the
+ * module-stop state. For RTC, dummy read at least 300 times and for
+ * LCDC, at least 100 times.
+ *
+ * Instead of performing the actual dummy reads, an equivalent delay is
+ * added using udelay(), computed from the required read count via
+ * RZT2H_MSTP_READS_TO_US().
+ */
+static const struct rzt2h_mstp_delay_entry rzt2h_mstp_delay_table[] = {
+ { RZT2H_MSTPCRM_INDEX, RZT2H_MSTPCRM04_LCDC,
+ RZT2H_MSTP_READS_TO_US(RZT2H_MSTP_LCDC_DUMMY_READS) },
+};
+
/*
* Standby Control Register offsets (RZ/A)
* Base address is FRQCR register
@@ -253,6 +300,20 @@ static void cpg_rzt2h_mstp_write(struct cpg_mssr_priv *priv, u16 offset, u32 val
writel(value, base + RZT2H_MSTPCR_OFFSET(offset));
}
+static unsigned int cpg_rzt2h_mstp_get_delay_us(u32 reg, u32 bit)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rzt2h_mstp_delay_table); i++) {
+ const struct rzt2h_mstp_delay_entry *e = &rzt2h_mstp_delay_table[i];
+
+ if (e->reg == reg && (e->bit == bit || bit == RZT2H_MSTP_ANY_BIT))
+ return e->delay_us;
+ }
+
+ return RZT2H_MSTP_DEFAULT_DELAY_US;
+}
+
static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable)
{
struct mstp_clock *clock = to_mstp_clock(hw);
@@ -312,7 +373,7 @@ static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable)
* register, we simply add a delay after the read operation.
*/
cpg_rzt2h_mstp_read(priv, priv->control_regs[reg]);
- udelay(10);
+ udelay(cpg_rzt2h_mstp_get_delay_us(reg, bit));
return 0;
}
@@ -1142,7 +1203,7 @@ static int cpg_mssr_resume_noirq(struct device *dev)
cpg_rzt2h_mstp_write(priv, priv->control_regs[reg], newval);
/* See cpg_mstp_clock_endisable() on why this is necessary. */
cpg_rzt2h_mstp_read(priv, priv->control_regs[reg]);
- udelay(10);
+ udelay(cpg_rzt2h_mstp_get_delay_us(reg, RZT2H_MSTP_ANY_BIT));
continue;
} else
writel(newval, priv->pub.base0 + priv->control_regs[reg]);
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 3/4] dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID
2026-05-11 19:19 [PATCH 0/4] Add PLL3 and LCDC_CLKD support for RZ/T2H and RZ/N2H Prabhakar
2026-05-11 19:19 ` [PATCH 1/4] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations Prabhakar
2026-05-11 19:19 ` [PATCH 2/4] clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for LCDC on RZ/T2H Prabhakar
@ 2026-05-11 19:19 ` Prabhakar
2026-05-12 17:16 ` Conor Dooley
2026-05-11 19:19 ` [PATCH 4/4] clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline Prabhakar
3 siblings, 1 reply; 9+ messages in thread
From: Prabhakar @ 2026-05-11 19:19 UTC (permalink / raw)
To: Geert Uytterhoeven, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Magnus Damm
Cc: linux-renesas-soc, linux-clk, linux-kernel, devicetree, Prabhakar,
Biju Das, Fabrizio Castro, Lad Prabhakar
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Add the LCDC clockd (LCDC_CLKD) definition for the Renesas RZ/T2H
(R9A09G077) and RZ/N2H (R9A09G087) SoCs. LCDC_CLKD is used as the
operating clock for LCDC.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h | 1 +
include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h b/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h
index c4863e444458..f6cb8d649a46 100644
--- a/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h
+++ b/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h
@@ -34,5 +34,6 @@
#define R9A09G077_XSPI_CLK0 22
#define R9A09G077_XSPI_CLK1 23
#define R9A09G077_PCLKCAN 24
+#define R9A09G077_LCDC_CLKD 25
#endif /* __DT_BINDINGS_CLOCK_RENESAS_R9A09G077_CPG_H__ */
diff --git a/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h b/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h
index 0d53f1e65077..312e563b322e 100644
--- a/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h
+++ b/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h
@@ -34,5 +34,6 @@
#define R9A09G087_XSPI_CLK0 22
#define R9A09G087_XSPI_CLK1 23
#define R9A09G087_PCLKCAN 24
+#define R9A09G087_LCDC_CLKD 25
#endif /* __DT_BINDINGS_CLOCK_RENESAS_R9A09G087_CPG_H__ */
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 4/4] clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline
2026-05-11 19:19 [PATCH 0/4] Add PLL3 and LCDC_CLKD support for RZ/T2H and RZ/N2H Prabhakar
` (2 preceding siblings ...)
2026-05-11 19:19 ` [PATCH 3/4] dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID Prabhakar
@ 2026-05-11 19:19 ` Prabhakar
2026-05-13 0:56 ` sashiko-bot
3 siblings, 1 reply; 9+ messages in thread
From: Prabhakar @ 2026-05-11 19:19 UTC (permalink / raw)
To: Geert Uytterhoeven, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Magnus Damm
Cc: linux-renesas-soc, linux-clk, linux-kernel, devicetree, Prabhakar,
Biju Das, Fabrizio Castro, Lad Prabhakar
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Add the clock definitions and PLL logic required to supply the LCDC
(VSPD/FCPVD/DU) blocks on the RZ/T2H (R9A09G077) SoC. The RZ/T2H display
subsystem depends on a dedicated PLL (PLL3) and a set of new derived
clocks.
Introduce a new PLL clock type and implement rate recalculation,
programming and locking sequences for PLL3 using the RZ/T2H specific
divider and VCO limits. Add the corresponding muxes and divider entries,
expose the LCDC core clock, and register the LCDC module clock using the
correct PCLK parent.
This enables the RZ/T2H clock driver to generate the display pipeline
clocking tree needed by the DU and VSP-based composition engines, allowing
upcoming display support to be integrated without duplicating CPG logic.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
drivers/clk/renesas/Kconfig | 2 +
drivers/clk/renesas/r9a09g077-cpg.c | 369 +++++++++++++++++++++++++++-
2 files changed, 370 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig
index 0203ecbb3882..5be45d9f9672 100644
--- a/drivers/clk/renesas/Kconfig
+++ b/drivers/clk/renesas/Kconfig
@@ -218,10 +218,12 @@ config CLK_R9A09G057
config CLK_R9A09G077
bool "RZ/T2H clock support" if COMPILE_TEST
select CLK_RENESAS_CPG_MSSR
+ select CLK_RZV2H
config CLK_R9A09G087
bool "RZ/N2H clock support" if COMPILE_TEST
select CLK_RENESAS_CPG_MSSR
+ select CLK_RZV2H
config CLK_SH73A0
bool "SH-Mobile AG5 clock support" if COMPILE_TEST
diff --git a/drivers/clk/renesas/r9a09g077-cpg.c b/drivers/clk/renesas/r9a09g077-cpg.c
index f777601a23b9..48052e7b93fd 100644
--- a/drivers/clk/renesas/r9a09g077-cpg.c
+++ b/drivers/clk/renesas/r9a09g077-cpg.c
@@ -8,16 +8,23 @@
#include <linux/bitfield.h>
#include <linux/clk-provider.h>
+#include <linux/clk/renesas.h>
#include <linux/device.h>
#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/math.h>
+#include <linux/module.h>
#include <linux/types.h>
+#include <linux/units.h>
#include <dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h>
#include <dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h>
#include "renesas-cpg-mssr.h"
+MODULE_IMPORT_NS("RZV2H_CPG");
+
#define RZT2H_REG_BLOCK_SHIFT 11
#define RZT2H_REG_OFFSET_MASK GENMASK(10, 0)
#define RZT2H_REG_CONF(block, offset) (((block) << RZT2H_REG_BLOCK_SHIFT) | \
@@ -66,11 +73,26 @@
#define DIVSCI2ASYNC CONF_PACK(SCKCR3, 10, 2)
#define DIVSCI3ASYNC CONF_PACK(SCKCR3, 12, 2)
#define DIVSCI4ASYNC CONF_PACK(SCKCR3, 14, 2)
+#define LCDCDIVSEL CONF_PACK(SCKCR3, 20, 4)
+
+#define PLL3EN FIELD_PREP_CONST(OFFSET_MASK, (0xc0))
+
+#define CPG_PLLEN BIT(0)
+#define CPG_PLL3_VCO_CTR0(x) ((x) + 0x4)
+#define CPG_PLL3_VCO_CTR0_PDIV GENMASK(21, 16)
+#define CPG_PLL3_VCO_CTR0_MDIV GENMASK(9, 0)
+#define CPG_PLL3_VCO_CTR1(x) ((x) + 0x8)
+#define CPG_PLL3_VCO_CTR1_KDIV GENMASK(31, 16)
+#define CPG_PLL3_VCO_CTR1_SDIV GENMASK(2, 0)
+#define CPG_PLL_MON(x) ((x) - 0x10)
+#define CPG_PLL_MON_LOCK BIT(0)
enum rzt2h_clk_types {
CLK_TYPE_RZT2H_DIV = CLK_TYPE_CUSTOM, /* Clock with divider */
CLK_TYPE_RZT2H_MUX, /* Clock with clock source selector */
CLK_TYPE_RZT2H_FSELXSPI, /* Clock with FSELXSPIn source selector */
+ CLK_TYPE_RZT2H_PLL3, /* PLL3 Clock */
+ CLK_TYPE_RZT2H_LCDCDIV, /* LCDC divider clock */
};
#define DEF_DIV(_name, _id, _parent, _conf, _dtable) \
@@ -83,10 +105,51 @@ enum rzt2h_clk_types {
#define DEF_DIV_FSELXSPI(_name, _id, _parent, _conf, _dtable) \
DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_FSELXSPI, .conf = _conf, \
.parent = _parent, .dtable = _dtable, .flag = 0)
+#define DEF_PLL3(_name, _id, _parent, _conf) \
+ DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_PLL3, .conf = _conf, \
+ .parent = _parent)
+#define DEF_DIV_LCDC(_name, _id, _parent, _conf, _dtable) \
+ DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_LCDCDIV, .conf = _conf, \
+ .parent = _parent, .dtable = _dtable, .flag = CLK_SET_RATE_PARENT)
+
+struct pll_clk {
+ void __iomem *reg;
+ const struct rzv2h_pll_limits *limits;
+ struct device *dev;
+ struct rzv2h_pll_pars pll_parameters;
+ struct clk_hw hw;
+ unsigned long cur_rate;
+};
+
+#define to_pll(_hw) container_of(_hw, struct pll_clk, hw)
+
+struct r9a09g077_lcdc_div_clk {
+ const struct clk_div_table *dtable;
+ void __iomem *reg;
+ struct device *dev;
+ struct clk_hw hw;
+ u32 conf;
+ u8 divider;
+};
+
+#define to_lcdc_div_clk(_hw) \
+ container_of(_hw, struct r9a09g077_lcdc_div_clk, hw)
+
+#define RZT2H_MAX_LCDC_DIV_TABLES 16
+
+static const struct rzv2h_pll_limits r9a09g077_cpg_pll3_limits = {
+ .input_fref = 48 * MEGA,
+ .fout = { .min = 25 * MEGA, .max = 430 * MEGA },
+ .fvco = { .min = 1600 * MEGA, .max = 3200 * MEGA },
+ .m = { .min = 0x40, .max = 0x3ff },
+ .p = { .min = 0x2, .max = 0x8 },
+ .s = { .min = 0x0, .max = 0x6 },
+ .k = { .min = -32768, .max = 32767 },
+};
enum clk_ids {
/* Core Clock Outputs exported to DT */
- LAST_DT_CORE_CLK = R9A09G077_PCLKCAN,
+ LAST_DT_CORE_CLK = R9A09G077_LCDC_CLKD,
/* External Input Clocks */
CLK_EXTAL,
@@ -96,10 +159,12 @@ enum clk_ids {
CLK_PLL0,
CLK_PLL1,
CLK_PLL2,
+ CLK_PLL3,
CLK_PLL4,
CLK_SEL_CLK_PLL0,
CLK_SEL_CLK_PLL1,
CLK_SEL_CLK_PLL2,
+ CLK_SEL_CLK_PLL3,
CLK_SEL_CLK_PLL4,
CLK_PLL4D1,
CLK_PLL4D1_DIV3,
@@ -107,6 +172,7 @@ enum clk_ids {
CLK_PLL4D3,
CLK_PLL4D3_DIV10,
CLK_PLL4D3_DIV20,
+ CLK_PLL4D50,
CLK_SCI0ASYNC,
CLK_SCI1ASYNC,
CLK_SCI2ASYNC,
@@ -119,6 +185,7 @@ enum clk_ids {
CLK_SPI3ASYNC,
CLK_DIVSELXSPI0_SCKCR,
CLK_DIVSELXSPI1_SCKCR,
+ CLK_LCDDIVSEL,
/* Module Clocks */
MOD_CLK_BASE,
@@ -130,6 +197,26 @@ static const struct clk_div_table dtable_1_2[] = {
{0, 0},
};
+static const struct clk_div_table dtable_2_32[] = {
+ {0, 2},
+ {1, 4},
+ {2, 6},
+ {3, 8},
+ {4, 10},
+ {5, 12},
+ {6, 14},
+ {7, 16},
+ {8, 18},
+ {9, 20},
+ {10, 22},
+ {11, 24},
+ {12, 26},
+ {13, 28},
+ {14, 30},
+ {15, 32},
+ {0, 0},
+};
+
static const struct clk_div_table dtable_6_8_16_32_64[] = {
{6, 64},
{5, 32},
@@ -152,6 +239,7 @@ static const struct clk_div_table dtable_24_25_30_32[] = {
static const char * const sel_clk_pll0[] = { ".loco", ".pll0" };
static const char * const sel_clk_pll1[] = { ".loco", ".pll1" };
static const char * const sel_clk_pll2[] = { ".loco", ".pll2" };
+static const char * const sel_clk_pll3[] = { ".loco", ".pll3" };
static const char * const sel_clk_pll4[] = { ".loco", ".pll4" };
static const char * const sel_clk_pll4d1_div3_div4[] = { ".pll4d1_div3", ".pll4d1_div4" };
static const char * const sel_clk_pll4d3_div10_div20[] = { ".pll4d3_div10", ".pll4d3_div20" };
@@ -173,10 +261,14 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = {
sel_clk_pll1, ARRAY_SIZE(sel_clk_pll1), CLK_MUX_READ_ONLY),
DEF_MUX(".sel_clk_pll2", CLK_SEL_CLK_PLL2, SEL_PLL,
sel_clk_pll2, ARRAY_SIZE(sel_clk_pll2), CLK_MUX_READ_ONLY),
+ DEF_MUX(".sel_clk_pll3", CLK_SEL_CLK_PLL3, SEL_PLL,
+ sel_clk_pll3, ARRAY_SIZE(sel_clk_pll3), CLK_MUX_READ_ONLY),
DEF_MUX(".sel_clk_pll4", CLK_SEL_CLK_PLL4, SEL_PLL,
sel_clk_pll4, ARRAY_SIZE(sel_clk_pll4), CLK_MUX_READ_ONLY),
DEF_FIXED(".pll4d1", CLK_PLL4D1, CLK_SEL_CLK_PLL4, 1, 1),
+ DEF_FIXED(".pll4d50", CLK_PLL4D50, CLK_SEL_CLK_PLL4, 50, 1),
+ DEF_PLL3(".pll3", CLK_PLL3, CLK_PLL4D50, PLL3EN),
DEF_FIXED(".pll4d1_div3", CLK_PLL4D1_DIV3, CLK_PLL4D1, 3, 1),
DEF_FIXED(".pll4d1_div4", CLK_PLL4D1_DIV4, CLK_PLL4D1, 4, 1),
DEF_FIXED(".pll4d3", CLK_PLL4D3, CLK_SEL_CLK_PLL4, 3, 1),
@@ -229,6 +321,7 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = {
DEF_FIXED("PCLKL", R9A09G077_CLK_PCLKL, CLK_SEL_CLK_PLL1, 16, 1),
DEF_FIXED("PCLKAH", R9A09G077_CLK_PCLKAH, CLK_PLL4D1, 6, 1),
DEF_FIXED("PCLKAM", R9A09G077_CLK_PCLKAM, CLK_PLL4D1, 12, 1),
+ DEF_FIXED("PCLKAL", R9A09G077_CLK_PCLKAL, CLK_PLL4D1, 24, 1),
DEF_FIXED("SDHI_CLKHS", R9A09G077_SDHI_CLKHS, CLK_SEL_CLK_PLL2, 1, 1),
DEF_FIXED("USB_CLK", R9A09G077_USB_CLK, CLK_PLL4D1, 48, 1),
DEF_FIXED("ETCLKA", R9A09G077_ETCLKA, CLK_SEL_CLK_PLL1, 5, 1),
@@ -242,6 +335,8 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = {
FSELXSPI1, dtable_6_8_16_32_64),
DEF_MUX("PCLKCAN", R9A09G077_PCLKCAN, FSELCANFD,
sel_clk_pll4d3_div10_div20, ARRAY_SIZE(sel_clk_pll4d3_div10_div20), 0),
+ DEF_DIV_LCDC("LCDCDIV", R9A09G077_LCDC_CLKD, CLK_SEL_CLK_PLL3, LCDCDIVSEL,
+ dtable_2_32),
};
static const struct mssr_mod_clk r9a09g077_mod_clks[] __initconst = {
@@ -272,6 +367,7 @@ static const struct mssr_mod_clk r9a09g077_mod_clks[] __initconst = {
DEF_MOD("sci5fck", 600, CLK_SCI5ASYNC),
DEF_MOD("iic2", 601, R9A09G077_CLK_PCLKL),
DEF_MOD("spi3", 602, CLK_SPI3ASYNC),
+ DEF_MOD("lcdc", 1204, R9A09G077_CLK_PCLKAL),
DEF_MOD("sdhi0", 1212, R9A09G077_CLK_PCLKAM),
DEF_MOD("sdhi1", 1213, R9A09G077_CLK_PCLKAM),
};
@@ -481,6 +577,272 @@ r9a09g077_cpg_fselxspi_div_clk_register(struct device *dev,
return hw->clk;
}
+static unsigned long r9a09g077_cpg_pll3_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct pll_clk *pll_clk = to_pll(hw);
+ unsigned int ctr0, ctr1;
+ u8 pdiv, sdiv;
+ u64 rate;
+ u16 mdiv;
+ s16 kdiv;
+
+ ctr0 = readl(CPG_PLL3_VCO_CTR0(pll_clk->reg));
+ ctr1 = readl(CPG_PLL3_VCO_CTR1(pll_clk->reg));
+
+ pdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_PDIV, ctr0);
+ mdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_MDIV, ctr0);
+ kdiv = (s16)FIELD_GET(CPG_PLL3_VCO_CTR1_KDIV, ctr1);
+ sdiv = FIELD_GET(CPG_PLL3_VCO_CTR1_SDIV, ctr1);
+
+ rate = mul_u64_u32_shr(parent_rate, (mdiv << 16) + kdiv, 16 + sdiv);
+
+ return DIV_ROUND_CLOSEST_ULL(rate, pdiv);
+}
+
+static int r9a09g077_cpg_pll3_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct pll_clk *pll_clk = to_pll(hw);
+ u64 rate_millihz;
+
+ if (req->rate == pll_clk->cur_rate)
+ return 0;
+
+ rate_millihz = mul_u32_u32(req->rate, MILLI);
+ if (!rzv2h_get_pll_pars(pll_clk->limits, &pll_clk->pll_parameters,
+ rate_millihz)) {
+ dev_dbg(pll_clk->dev,
+ "failed to determine rate for req->rate: %lu\n",
+ req->rate);
+ return -EINVAL;
+ }
+ req->rate = DIV_ROUND_CLOSEST_ULL(pll_clk->pll_parameters.freq_millihz, MILLI);
+ pll_clk->cur_rate = req->rate;
+
+ return 0;
+}
+
+static int r9a09g077_cpg_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct pll_clk *pll_clk = to_pll(hw);
+ struct rzv2h_pll_pars *params = &pll_clk->pll_parameters;
+ void __iomem *offset = pll_clk->reg;
+ u32 val;
+ int ret;
+
+ /* Put PLL into standby mode */
+ writel(0, offset);
+ ret = readl_poll_timeout_atomic(CPG_PLL_MON(offset),
+ val, !(val & CPG_PLL_MON_LOCK),
+ 100, 2000);
+ if (ret) {
+ dev_err(pll_clk->dev, "Failed to put PLL into standby mode");
+ return ret;
+ }
+
+ /* Output clock setting 1 */
+ val = readl(CPG_PLL3_VCO_CTR0(offset));
+ FIELD_MODIFY(CPG_PLL3_VCO_CTR0_MDIV, &val, params->m);
+ FIELD_MODIFY(CPG_PLL3_VCO_CTR0_PDIV, &val, params->p);
+ writel(val, CPG_PLL3_VCO_CTR0(offset));
+
+ /* Output clock setting 2 */
+ val = readl(CPG_PLL3_VCO_CTR1(offset));
+ FIELD_MODIFY(CPG_PLL3_VCO_CTR1_KDIV, &val, params->k);
+ FIELD_MODIFY(CPG_PLL3_VCO_CTR1_SDIV, &val, params->s);
+ writel(val, CPG_PLL3_VCO_CTR1(offset));
+
+ writel(CPG_PLLEN, offset);
+
+ /* PLL normal mode transition, output clock stability check */
+ ret = readl_poll_timeout_atomic(CPG_PLL_MON(offset),
+ val, (val & CPG_PLL_MON_LOCK),
+ 100, 2000);
+ if (ret) {
+ writel(0, offset);
+ dev_err(pll_clk->dev, "Failed to put PLL into normal mode");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct clk_ops r9a09g077_cpg_pll3_ops = {
+ .recalc_rate = r9a09g077_cpg_pll3_clk_recalc_rate,
+ .determine_rate = r9a09g077_cpg_pll3_determine_rate,
+ .set_rate = r9a09g077_cpg_pll3_set_rate,
+};
+
+static struct clk * __init
+r9a09g077_cpg_pll3_clk_register(struct device *dev,
+ const struct cpg_core_clk *core,
+ void __iomem *addr,
+ struct cpg_mssr_pub *pub,
+ const struct rzv2h_pll_limits *limits)
+{
+ struct clk_init_data init = {};
+ const struct clk *parent;
+ const char *parent_name;
+ struct pll_clk *pll_clk;
+ int ret;
+
+ parent = pub->clks[core->parent];
+ if (IS_ERR(parent))
+ return ERR_CAST(parent);
+
+ pll_clk = devm_kzalloc(dev, sizeof(*pll_clk), GFP_KERNEL);
+ if (!pll_clk)
+ return ERR_PTR(-ENOMEM);
+
+ parent_name = __clk_get_name(parent);
+ init.name = core->name;
+ init.ops = &r9a09g077_cpg_pll3_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ pll_clk->dev = dev;
+ pll_clk->hw.init = &init;
+ pll_clk->reg = addr;
+ pll_clk->limits = limits;
+
+ ret = devm_clk_hw_register(dev, &pll_clk->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return pll_clk->hw.clk;
+}
+
+static int r9a09g077_cpg_lcdc_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw);
+ struct pll_clk *pll_clk = to_pll(clk_hw_get_parent(clk_hw_get_parent(hw)));
+ u8 table[RZT2H_MAX_LCDC_DIV_TABLES] = { 0 };
+ struct rzv2h_pll_div_pars dsi_params;
+ const struct clk_div_table *div;
+ unsigned int i = 0;
+ u64 freq_millihz;
+
+ for (div = dsi_div->dtable; div->div; div++) {
+ if (i >= RZT2H_MAX_LCDC_DIV_TABLES)
+ return -EINVAL;
+ table[i++] = div->div;
+ }
+
+ freq_millihz = mul_u32_u32(req->rate, MILLI);
+
+ if (!rzv2h_get_pll_divs_pars(pll_clk->limits, &dsi_params, table,
+ i, freq_millihz)) {
+ dev_err(dsi_div->dev,
+ "LCDC divider failed to determine rate for req->rate: %lu\n",
+ req->rate);
+ return -EINVAL;
+ }
+
+ req->rate = DIV_ROUND_CLOSEST_ULL(dsi_params.div.freq_millihz, MILLI);
+ req->best_parent_rate = req->rate * dsi_params.div.divider_value;
+ dsi_div->divider = dsi_params.div.divider_value;
+ pll_clk->cur_rate = req->best_parent_rate;
+ pll_clk->pll_parameters = dsi_params.pll;
+
+ return 0;
+}
+
+static int r9a09g077_cpg_lcdc_div_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw);
+ const struct clk_div_table *clkt;
+ bool divider_found = false;
+ u32 val, shift;
+
+ for (clkt = dsi_div->dtable; clkt->div; clkt++) {
+ if (clkt->div == dsi_div->divider) {
+ divider_found = true;
+ break;
+ }
+ }
+
+ if (!divider_found)
+ return -EINVAL;
+
+ shift = GET_SHIFT(dsi_div->conf);
+ val = readl(dsi_div->reg);
+ val &= ~(clk_div_mask(GET_WIDTH(dsi_div->conf)) << shift);
+ val |= clkt->val << shift;
+ writel(val, dsi_div->reg);
+
+ return 0;
+}
+
+static unsigned long
+r9a09g077_cpg_lcdc_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw);
+ u32 div;
+
+ div = readl(dsi_div->reg);
+ div >>= GET_SHIFT(dsi_div->conf);
+ div &= clk_div_mask(GET_WIDTH(dsi_div->conf));
+ div = dsi_div->dtable[div].div;
+
+ return DIV_ROUND_CLOSEST_ULL(parent_rate, div);
+}
+
+static const struct clk_ops r9a09g077_cpg_lcdc_div_ops = {
+ .recalc_rate = r9a09g077_cpg_lcdc_div_recalc_rate,
+ .determine_rate = r9a09g077_cpg_lcdc_div_determine_rate,
+ .set_rate = r9a09g077_cpg_lcdc_div_set_rate,
+};
+
+static struct clk * __init
+r9a09g077_cpg_lcdc_div_clk_register(struct device *dev,
+ const struct cpg_core_clk *core,
+ void __iomem *addr,
+ struct cpg_mssr_pub *pub)
+{
+ struct r9a09g077_lcdc_div_clk *clk_hw_data;
+ struct clk **clks = pub->clks;
+ struct clk_init_data init;
+ const struct clk *parent;
+ const char *parent_name;
+ struct clk_hw *hw;
+ int ret;
+
+ parent = clks[core->parent];
+ if (IS_ERR(parent))
+ return ERR_CAST(parent);
+
+ clk_hw_data = devm_kzalloc(dev, sizeof(*clk_hw_data), GFP_KERNEL);
+ if (!clk_hw_data)
+ return ERR_PTR(-ENOMEM);
+
+ clk_hw_data->dtable = core->dtable;
+ clk_hw_data->reg = addr;
+ clk_hw_data->conf = core->conf;
+ clk_hw_data->dev = dev;
+ clk_hw_data->divider = 32; /* Initialize divider for LCDC */
+
+ parent_name = __clk_get_name(parent);
+ init.name = core->name;
+ init.ops = &r9a09g077_cpg_lcdc_div_ops;
+ init.flags = core->flag;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ hw = &clk_hw_data->hw;
+ hw->init = &init;
+ ret = devm_clk_hw_register(dev, hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return hw->clk;
+}
+
static struct clk * __init
r9a09g077_cpg_clk_register(struct device *dev, const struct cpg_core_clk *core,
const struct cpg_mssr_info *info,
@@ -497,6 +859,11 @@ r9a09g077_cpg_clk_register(struct device *dev, const struct cpg_core_clk *core,
return r9a09g077_cpg_mux_clk_register(dev, core, addr, pub);
case CLK_TYPE_RZT2H_FSELXSPI:
return r9a09g077_cpg_fselxspi_div_clk_register(dev, core, addr, pub);
+ case CLK_TYPE_RZT2H_PLL3:
+ return r9a09g077_cpg_pll3_clk_register(dev, core, pub->base1 + offset,
+ pub, &r9a09g077_cpg_pll3_limits);
+ case CLK_TYPE_RZT2H_LCDCDIV:
+ return r9a09g077_cpg_lcdc_div_clk_register(dev, core, addr, pub);
default:
return ERR_PTR(-EINVAL);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH 3/4] dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID
2026-05-11 19:19 ` [PATCH 3/4] dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID Prabhakar
@ 2026-05-12 17:16 ` Conor Dooley
0 siblings, 0 replies; 9+ messages in thread
From: Conor Dooley @ 2026-05-12 17:16 UTC (permalink / raw)
To: Prabhakar
Cc: Geert Uytterhoeven, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Magnus Damm, linux-renesas-soc,
linux-clk, linux-kernel, devicetree, Biju Das, Fabrizio Castro,
Lad Prabhakar
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/4] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations
2026-05-11 19:19 ` [PATCH 1/4] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations Prabhakar
@ 2026-05-13 0:02 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-05-13 0:02 UTC (permalink / raw)
To: Prabhakar; +Cc: wsa+renesas, devicetree, krzk+dt, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] On 32-bit architectures, the calculation `output += p.k * input_fref;` performs an incorrect zero-extended addition when `p.k` is negative, corrupting the PLL calculation.
- [Low] The newly added `input_fref` field is documented as being in MHz, but the code initializes and expects it in Hz.
--
commit a6beaf951f9f846b0e14a16acf1b5de978587208
Author: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations
This commit modifies the rzv2h-cpg driver to use a per-SoC PLL reference
input frequency instead of a hardcoded 24MHz constant. It adds an
input_fref field to struct rzv2h_pll_limits and updates the PLL parameter
calculations to use it.
> diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cpg.c
> index f6c47fb89bca5..cd990dc10fa96 100644
> --- a/drivers/clk/renesas/rzv2h-cpg.c
> +++ b/drivers/clk/renesas/rzv2h-cpg.c
[ ... ]
> @@ -330,9 +331,9 @@ bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits,
> continue;
>
> /* PLL_M component of (output * 65536 * PLL_P) */
> - output = mul_u32_u32(p.m * 65536, RZ_V2H_OSC_CLK_IN_MEGA);
> + output = mul_u32_u32(p.m * 65536, input_fref);
> /* PLL_K component of (output * 65536 * PLL_P) */
> - output += p.k * RZ_V2H_OSC_CLK_IN_MEGA;
> + output += p.k * input_fref;
On 32-bit architectures, does this calculation perform an incorrect
zero-extended addition when p.k is negative?
Since p.k is a signed 16-bit integer (s16) and input_fref is unsigned long
(32-bit on ARM32 architectures), negative values of p.k might get promoted to
a 32-bit unsigned integer before multiplication.
This would yield a 32-bit unsigned result. When added to the u64 output
variable, this result would be zero-extended to 64 bits instead of being
sign-extended, adding a large positive value rather than subtracting the
frequency offset.
While this existed prior to this patch, replacing the RZ_V2H_OSC_CLK_IN_MEGA
constant with input_fref maintains the same integer promotion behavior.
Could p.k be explicitly cast to a signed 64-bit integer here?
> /* Make it in mHz */
> output *= MILLI;
> output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider);
> diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h
> index c360df9fa735c..402bfeaffdfc4 100644
> --- a/include/linux/clk/renesas.h
> +++ b/include/linux/clk/renesas.h
> @@ -53,6 +53,8 @@ static inline void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target) { }
> * various parameters used to configure a PLL. These limits ensure
> * the PLL operates within valid and stable ranges.
> *
> + * @input_fref: Reference input frequency to the PLL (in MHz)
This isn't a bug, but is the documentation correct about the unit being in MHz?
Looking later in the diff, the field appears to be initialized in Hz:
[ ... ]
> @@ -156,6 +160,7 @@ struct rzv2h_pll_div_pars {
>
> #define RZV2H_CPG_PLL_DSI_LIMITS(name) \
> static const struct rzv2h_pll_limits (name) = { \
> + .input_fref = 24 * MEGA, \
> .fout = { .min = 25 * MEGA, .max = 375 * MEGA }, \
It seems the field is initialized and expected in Hz (24,000,000 Hz) rather
than MHz. Should the documentation be updated to state (in Hz) to match the
implementation?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260511191910.1945705-1-prabhakar.mahadev-lad.rj@bp.renesas.com?part=1
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/4] clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for LCDC on RZ/T2H
2026-05-11 19:19 ` [PATCH 2/4] clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for LCDC on RZ/T2H Prabhakar
@ 2026-05-13 0:18 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-05-13 0:18 UTC (permalink / raw)
To: Prabhakar; +Cc: conor+dt, wsa+renesas, krzk+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Integer division truncation in RZT2H_MSTP_READS_TO_US causes insufficient dummy-read delays, violating hardware requirements.
- [Medium] The table-driven MSTP delay lookup logic contradicts its documentation and is structurally brittle, failing to support register-level wildcards or multiple IP blocks per register safely.
--
commit 45819a6f54fdfd758c7050cb72ae4d08ec929f51
Author: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for LCDC on RZ/T2H
This commit introduces a table-driven mechanism to apply MSTP dummy-read
delays per-IP block on RZ/T2H. It replaces the hardcoded udelay(10) with
a computed delay based on the hardware manual's minimum dummy-read
requirements.
> diff --git a/drivers/clk/renesas/renesas-cpg-mssr.c b/drivers/clk/renesas/renesas-cpg-mssr.c
> index 26ea85cfaa027..19a96ca17d73a 100644
> --- a/drivers/clk/renesas/renesas-cpg-mssr.c
> +++ b/drivers/clk/renesas/renesas-cpg-mssr.c
> @@ -96,6 +96,24 @@ static const u16 mstpcr_for_gen4[] = {
> #define RZT2H_MSTPCR_BLOCK(x) ((x) >> RZT2H_MSTPCR_BLOCK_SHIFT)
> #define RZT2H_MSTPCR_OFFSET(x) ((x) & RZT2H_MSTPCR_OFFSET_MASK)
>
> +/* Dummy read counts as specified by the RZ/T2H hardware manual */
> +#define RZT2H_MSTP_DEFAULT_DUMMY_READS 7
> +#define RZT2H_MSTP_LCDC_DUMMY_READS 100
> +
> +/*
> + * Time per dummy read in nanoseconds, derived from the original udelay(10)
> + * which was used to satisfy the 7 dummy-read requirement:
> + * 10000 ns / 7 reads = 1429 ns per read.
> + */
> +#define RZT2H_MSTP_DUMMY_READ_NS 1429
> +#define RZT2H_MSTP_READS_TO_US(n) (((n) * RZT2H_MSTP_DUMMY_READ_NS) / 1000)
Does this macro truncate the division, resulting in an insufficient delay?
For the LCDC requirement of 100 reads, n = 100 evaluates to
142,900 / 1000 = 142 microseconds.
However, 142 us only covers 99.37 reads at 1429 ns per read.
This falls short of the hardware manual requirement of "at least 100 times"
mentioned in the commit message.
Should this use DIV_ROUND_UP to ensure the minimum delay is always met?
> +#define RZT2H_MSTP_DEFAULT_DELAY_US RZT2H_MSTP_READS_TO_US(RZT2H_MSTP_DEFAULT_DUMMY_READS)
> +
> +#define RZT2H_MSTPCRM_INDEX 12
> +#define RZT2H_MSTPCRM04_LCDC 4
> +
> +#define RZT2H_MSTP_ANY_BIT U32_MAX
[ ... ]
> +/**
> + * struct rzt2h_mstp_delay_entry - MSTP dummy-read requirement for RZ/T2H
> + *
> + * @reg: Index into control_regs[]. Exact match.
> + * @bit: MSTP bit position, or RZT2H_MSTP_ANY_BIT for register-level match.
> + * @delay_us: Computed delay in microseconds to satisfy the dummy read requirement.
> + */
> +struct rzt2h_mstp_delay_entry {
> + u32 reg;
> + u32 bit;
> + u32 delay_us;
> +};
[ ... ]
> @@ -253,6 +300,20 @@ static void cpg_rzt2h_mstp_write(struct cpg_mssr_priv *priv, u16 offset, u32 val
> writel(value, base + RZT2H_MSTPCR_OFFSET(offset));
> }
>
> +static unsigned int cpg_rzt2h_mstp_get_delay_us(u32 reg, u32 bit)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(rzt2h_mstp_delay_table); i++) {
> + const struct rzt2h_mstp_delay_entry *e = &rzt2h_mstp_delay_table[i];
> +
> + if (e->reg == reg && (e->bit == bit || bit == RZT2H_MSTP_ANY_BIT))
> + return e->delay_us;
> + }
> +
> + return RZT2H_MSTP_DEFAULT_DELAY_US;
> +}
Is the register-level match fully supported by this logic?
The struct documentation above states e->bit can be RZT2H_MSTP_ANY_BIT for a
register-level match.
However, the condition here checks if the passed bit argument is
RZT2H_MSTP_ANY_BIT, but does not check if e->bit is RZT2H_MSTP_ANY_BIT.
If the table contained an entry with e->bit set to RZT2H_MSTP_ANY_BIT,
it seems it would be ignored when querying for a specific bit.
Additionally, in cpg_mssr_resume_noirq(), the driver queries the delay by
passing RZT2H_MSTP_ANY_BIT.
Because this loop returns on the very first match, if multiple IPs in the
same register have custom requirements in the future, will it return the
delay of the first entry instead of the maximum required delay for the
entire register?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260511191910.1945705-1-prabhakar.mahadev-lad.rj@bp.renesas.com?part=2
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 4/4] clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline
2026-05-11 19:19 ` [PATCH 4/4] clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline Prabhakar
@ 2026-05-13 0:56 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-05-13 0:56 UTC (permalink / raw)
To: Prabhakar; +Cc: wsa+renesas, krzk+dt, conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Uninitialized `struct clk_init_data` allocated on the stack causes kernel panics.
- [High] Invalid downcast of grandparent clock causes type confusion and memory corruption.
- [High] State mutation in `determine_rate` callbacks violates Common Clock Framework constraints.
- [High] Missing locking during read-modify-write on the highly shared `SCKCR3` register.
- [High] Potential divide-by-zero kernel panic in `r9a09g077_cpg_pll3_clk_recalc_rate`.
- [Medium] Integer truncation of requested 64-bit clock rates leads to unexpected hardware frequencies.
- [Medium] Amplified rounding error during `best_parent_rate` calculation.
--
commit 92d34dcfa10f7893860a62c602064adfd5fe8364
Author: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline
This patch adds the clock definitions and PLL logic required to supply the LCDC
blocks on the RZ/T2H SoC. It introduces a new PLL clock type, implements rate
recalculation and locking sequences, and adds the corresponding muxes and
divider entries.
> diff --git a/drivers/clk/renesas/r9a09g077-cpg.c b/drivers/clk/renesas/r9a09g077-cpg.c
> index 93b15e06a19bc..64d45dbea5f1e 100644
> --- a/drivers/clk/renesas/r9a09g077-cpg.c
> +++ b/drivers/clk/renesas/r9a09g077-cpg.c
[ ... ]
> +static unsigned long r9a09g077_cpg_pll3_clk_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct pll_clk *pll_clk = to_pll(hw);
> + unsigned int ctr0, ctr1;
> + u8 pdiv, sdiv;
> + u64 rate;
> + u16 mdiv;
> + s16 kdiv;
> +
> + ctr0 = readl(CPG_PLL3_VCO_CTR0(pll_clk->reg));
> + ctr1 = readl(CPG_PLL3_VCO_CTR1(pll_clk->reg));
> +
> + pdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_PDIV, ctr0);
> + mdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_MDIV, ctr0);
> + kdiv = (s16)FIELD_GET(CPG_PLL3_VCO_CTR1_KDIV, ctr1);
> + sdiv = FIELD_GET(CPG_PLL3_VCO_CTR1_SDIV, ctr1);
> +
> + rate = mul_u64_u32_shr(parent_rate, (mdiv << 16) + kdiv, 16 + sdiv);
> +
> + return DIV_ROUND_CLOSEST_ULL(rate, pdiv);
> +}
Is it possible for pdiv to be 0 if the hardware boots in an uninitialized
state?
The DIV_ROUND_CLOSEST_ULL macro will cause a kernel panic if the divisor
is zero.
> +static int r9a09g077_cpg_pll3_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct pll_clk *pll_clk = to_pll(hw);
> + u64 rate_millihz;
> +
> + if (req->rate == pll_clk->cur_rate)
> + return 0;
> +
> + rate_millihz = mul_u32_u32(req->rate, MILLI);
Does this implicitly cast the 64-bit req->rate (which is an unsigned long on
ARM64) to a 32-bit integer, dropping the upper bits before multiplication?
> + if (!rzv2h_get_pll_pars(pll_clk->limits, &pll_clk->pll_parameters,
> + rate_millihz)) {
> + dev_dbg(pll_clk->dev,
> + "failed to determine rate for req->rate: %lu\n",
> + req->rate);
> + return -EINVAL;
> + }
> + req->rate = DIV_ROUND_CLOSEST_ULL(pll_clk->pll_parameters.freq_millihz, MILLI);
> + pll_clk->cur_rate = req->rate;
> +
> + return 0;
> +}
Is it safe to mutate the driver state (pll_clk->pll_parameters and
pll_clk->cur_rate) inside a determine_rate callback?
The common clock framework can call this via clk_round_rate for queries.
This could overwrite the active configuration without actually changing the
hardware.
[ ... ]
> +static int r9a09g077_cpg_lcdc_div_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw);
> + struct pll_clk *pll_clk = to_pll(clk_hw_get_parent(clk_hw_get_parent(hw)));
Since the grandparent clock CLK_SEL_CLK_PLL3 is a multiplexer, could the
parent hardware actually be the fixed-rate loco clock instead of a
pll_clk structure?
If so, the to_pll macro would cast a fixed-rate clock to a PLL clock,
potentially leading to out-of-bounds memory access later in this function.
> + u8 table[RZT2H_MAX_LCDC_DIV_TABLES] = { 0 };
> + struct rzv2h_pll_div_pars dsi_params;
> + const struct clk_div_table *div;
> + unsigned int i = 0;
> + u64 freq_millihz;
> +
> + for (div = dsi_div->dtable; div->div; div++) {
> + if (i >= RZT2H_MAX_LCDC_DIV_TABLES)
> + return -EINVAL;
> + table[i++] = div->div;
> + }
> +
> + freq_millihz = mul_u32_u32(req->rate, MILLI);
Similar to the PLL3 callback, does this also truncate the 64-bit rate down
to 32 bits?
> +
> + if (!rzv2h_get_pll_divs_pars(pll_clk->limits, &dsi_params, table,
> + i, freq_millihz)) {
> + dev_err(dsi_div->dev,
> + "LCDC divider failed to determine rate for req->rate: %lu\n",
> + req->rate);
> + return -EINVAL;
> + }
> +
> + req->rate = DIV_ROUND_CLOSEST_ULL(dsi_params.div.freq_millihz, MILLI);
> + req->best_parent_rate = req->rate * dsi_params.div.divider_value;
Could calculating req->best_parent_rate using the already rounded req->rate
amplify the Hz-level rounding error?
Using the exact internal rate from dsi_params.pll.freq_millihz / MILLI
might be more accurate.
> + dsi_div->divider = dsi_params.div.divider_value;
> + pll_clk->cur_rate = req->best_parent_rate;
> + pll_clk->pll_parameters = dsi_params.pll;
> +
> + return 0;
> +}
Does mutating dsi_div->divider and pll_clk properties here break queries if
the framework only requested a rate check via clk_round_rate?
> +static int r9a09g077_cpg_lcdc_div_set_rate(struct clk_hw *hw,
> + unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw);
> + const struct clk_div_table *clkt;
> + bool divider_found = false;
> + u32 val, shift;
> +
> + for (clkt = dsi_div->dtable; clkt->div; clkt++) {
> + if (clkt->div == dsi_div->divider) {
> + divider_found = true;
> + break;
> + }
> + }
> +
> + if (!divider_found)
> + return -EINVAL;
> +
> + shift = GET_SHIFT(dsi_div->conf);
> + val = readl(dsi_div->reg);
> + val &= ~(clk_div_mask(GET_WIDTH(dsi_div->conf)) << shift);
> + val |= clkt->val << shift;
> + writel(val, dsi_div->reg);
> +
> + return 0;
> +}
Does this read-modify-write sequence need to be protected by pub->rmw_lock?
It looks like SCKCR3 is highly shared with other dividers like DIVSCI0ASYNC,
and concurrent modifications could race.
[ ... ]
> +static struct clk * __init
> +r9a09g077_cpg_lcdc_div_clk_register(struct device *dev,
> + const struct cpg_core_clk *core,
> + void __iomem *addr,
> + struct cpg_mssr_pub *pub)
> +{
> + struct r9a09g077_lcdc_div_clk *clk_hw_data;
> + struct clk **clks = pub->clks;
> + struct clk_init_data init;
Since this is allocated on the stack without zero initialization, could
fields like parent_hws and parent_data contain uninitialized garbage?
The common clock framework might dereference these pointers during
registration before falling back to checking parent_names.
> + const struct clk *parent;
> + const char *parent_name;
> + struct clk_hw *hw;
> + int ret;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260511191910.1945705-1-prabhakar.mahadev-lad.rj@bp.renesas.com?part=4
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-05-13 0:56 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 19:19 [PATCH 0/4] Add PLL3 and LCDC_CLKD support for RZ/T2H and RZ/N2H Prabhakar
2026-05-11 19:19 ` [PATCH 1/4] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations Prabhakar
2026-05-13 0:02 ` sashiko-bot
2026-05-11 19:19 ` [PATCH 2/4] clk: renesas: cpg-mssr: Add table-driven MSTP dummy-read delay for LCDC on RZ/T2H Prabhakar
2026-05-13 0:18 ` sashiko-bot
2026-05-11 19:19 ` [PATCH 3/4] dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID Prabhakar
2026-05-12 17:16 ` Conor Dooley
2026-05-11 19:19 ` [PATCH 4/4] clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline Prabhakar
2026-05-13 0:56 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox