[PATCH v2 3/6] clk: renesas: Add R-Car X5H CPG driver

From: Geert Uytterhoeven

Date: Wed Jul 08 2026 - 06:22:26 EST


Add a minimal Clock Pulse Generator driver for the R-Car X5H (R8A78000)
SoC. For now this supports just the few fixed-rate clocks that are
needed by the current minimal DTS.

Signed-off-by: Geert Uytterhoeven <geert+renesas@xxxxxxxxx>
---
v2:
- Spin off from "clk: renesas: Add R-Car X5H CPG SCMI remapping
driver",
- Add default support not using SCMI,
- Drop all SCMI remapping support,
- Document use of -1 as a sentinel,
- Document use of FIXED_CLK() IDs.
---
drivers/clk/renesas/Kconfig | 4 +
drivers/clk/renesas/Makefile | 1 +
drivers/clk/renesas/r8a78000-cpg.c | 216 +++++++++++++++++++++++++++++
3 files changed, 221 insertions(+)
create mode 100644 drivers/clk/renesas/r8a78000-cpg.c

diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig
index 0203ecbb38825f13..f0482bdfc4616cfa 100644
--- a/drivers/clk/renesas/Kconfig
+++ b/drivers/clk/renesas/Kconfig
@@ -34,6 +34,7 @@ config CLK_RENESAS
select CLK_R8A779F0 if ARCH_R8A779F0
select CLK_R8A779G0 if ARCH_R8A779G0
select CLK_R8A779H0 if ARCH_R8A779H0
+ select CLK_R8A78000 if ARCH_R8A78000
select CLK_R9A06G032 if ARCH_R9A06G032
select CLK_R9A07G043 if ARCH_R9A07G043
select CLK_R9A07G044 if ARCH_R9A07G044
@@ -176,6 +177,9 @@ config CLK_R8A779H0
bool "R-Car V4M clock support" if COMPILE_TEST
select CLK_RCAR_GEN4_CPG

+config CLK_R8A78000
+ bool "R-Car X5H clock support" if COMPILE_TEST
+
config CLK_R9A06G032
bool "RZ/N1D clock support" if COMPILE_TEST

diff --git a/drivers/clk/renesas/Makefile b/drivers/clk/renesas/Makefile
index bd2bed91ab291d72..4f76f8c402ffe9a3 100644
--- a/drivers/clk/renesas/Makefile
+++ b/drivers/clk/renesas/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_CLK_R8A779A0) += r8a779a0-cpg-mssr.o
obj-$(CONFIG_CLK_R8A779F0) += r8a779f0-cpg-mssr.o
obj-$(CONFIG_CLK_R8A779G0) += r8a779g0-cpg-mssr.o
obj-$(CONFIG_CLK_R8A779H0) += r8a779h0-cpg-mssr.o
+obj-$(CONFIG_CLK_R8A78000) += r8a78000-cpg.o
obj-$(CONFIG_CLK_R9A06G032) += r9a06g032-clocks.o
obj-$(CONFIG_CLK_R9A07G043) += r9a07g043-cpg.o
obj-$(CONFIG_CLK_R9A07G044) += r9a07g044-cpg.o
diff --git a/drivers/clk/renesas/r8a78000-cpg.c b/drivers/clk/renesas/r8a78000-cpg.c
new file mode 100644
index 0000000000000000..c53d8f33ee88b339
--- /dev/null
+++ b/drivers/clk/renesas/r8a78000-cpg.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * R-Car X5H Clock Pulse Generator
+ *
+ * Copyright (C) 2026 Glider bv
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/dev_printk.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/renesas,r8a78000-cpg.h>
+
+struct clk_map_in {
+ int dt_id; /* DT binding clock ID or -1 sentinel */
+ u32 fw_id; /* FIXED_CLK() ID */
+};
+
+struct clk_map {
+ int dt_id; /* DT binding clock ID or -1 sentinel */
+ u32 fw_id; /* FIXED_CLK() ID */
+ struct clk_hw *hw;
+};
+
+enum fixed_clk {
+ FIXED_CLK_66M,
+ FIXED_CLK_266M,
+ NUM_FIXED_CLKS
+};
+
+static const unsigned long fixed_clk_rates[NUM_FIXED_CLKS] = {
+ [FIXED_CLK_66M] = 66666000,
+ [FIXED_CLK_266M] = 266660000,
+};
+
+#define FIXED_CLK(rate) FIXED_CLK_ ## rate
+
+/**
+ * struct r8a78000_cpg_priv - Clock Pulse Generator Private Data
+ *
+ * @dev: CPG device
+ * @map: Mapping from DT clock IDs
+ * @fixed_hws: Fixed rate clocks
+ */
+struct r8a78000_cpg_priv {
+ struct device *dev;
+ const struct clk_map *map;
+ struct clk_hw *fixed_hws[NUM_FIXED_CLKS];
+};
+
+static const struct clk_map *clk_map_find(const struct clk_map *map, u32 id)
+{
+ if (!map)
+ return NULL;
+
+ for (; map->dt_id >= 0; map++) {
+ if (map->dt_id == id)
+ return map;
+ }
+
+ return NULL;
+}
+
+static struct clk_hw *r8a78000_clk_get(struct of_phandle_args *spec,
+ void *data)
+{
+ struct r8a78000_cpg_priv *priv = data;
+ struct device *dev = priv->dev;
+ const struct clk_map *map;
+ struct clk_hw *hw;
+ u32 id;
+
+ if (spec->args_count != 1)
+ return ERR_PTR(-EINVAL);
+
+ id = spec->args[0];
+
+ map = clk_map_find(priv->map, id);
+ if (!map) {
+ dev_err(dev, "Unknown clock %u\n", id);
+ return ERR_PTR(-ENOENT);
+ }
+
+ dev_dbg(dev, "Mapping DT clock %u to fixed clock %u\n", id, map->fw_id);
+
+ hw = map->hw;
+
+ dev_dbg(dev, "clock %u is %s at %lu Hz\n", id, clk_hw_get_name(hw),
+ clk_hw_get_rate(hw));
+
+ return hw;
+}
+
+static void unregister_fixed_clks(void *data)
+{
+ struct r8a78000_cpg_priv *priv = data;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(priv->fixed_hws); i++)
+ clk_hw_unregister_fixed_rate(priv->fixed_hws[i]);
+}
+
+static int register_fixed_clks(struct r8a78000_cpg_priv *priv)
+{
+ struct device *dev = priv->dev;
+ unsigned long rate;
+ struct clk_hw *hw;
+ const char *name;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(fixed_clk_rates); i++) {
+ rate = fixed_clk_rates[i];
+ name = devm_kasprintf(dev, GFP_KERNEL, "cpg-%lu", rate);
+ if (!name)
+ return -ENOMEM;
+
+ hw = clk_hw_register_fixed_rate(dev, name, NULL, 0, rate);
+ if (IS_ERR(hw)) {
+ while (i-- > 0)
+ clk_hw_unregister_fixed_rate(priv->fixed_hws[i]);
+ return PTR_ERR(hw);
+ }
+
+ priv->fixed_hws[i] = hw;
+ }
+
+ return devm_add_action_or_reset(dev, unregister_fixed_clks, priv);
+}
+
+static const struct clk_map *fill_clk_map(struct r8a78000_cpg_priv *priv,
+ const struct clk_map_in *map_in)
+{
+ struct device *dev = priv->dev;
+ struct clk_map *map;
+ unsigned int i;
+
+ for (i = 0; map_in[i].dt_id >= 0; i++) { }
+
+ map = devm_kcalloc(dev, i + 1, sizeof(*map), GFP_KERNEL);
+ if (!map)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; ; i++) {
+ map[i].dt_id = map_in[i].dt_id;
+ if (map[i].dt_id < 0)
+ break;
+
+ map[i].fw_id = map_in[i].fw_id;
+ map[i].hw = priv->fixed_hws[map[i].fw_id];
+ continue;
+ }
+
+ return map;
+}
+
+static int r8a78000_cpg_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct r8a78000_cpg_priv *priv;
+ const struct clk_map_in *map;
+ int ret;
+
+ map = of_device_get_match_data(dev);
+ if (!map)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+
+ ret = register_fixed_clks(priv);
+ if (ret)
+ return ret;
+
+ /*
+ * We cannot do lazy look-up in r8a78000_clk_get(), as that function is
+ * called with of_clk_mutex already held.
+ */
+ priv->map = fill_clk_map(priv, map);
+ if (IS_ERR(priv->map))
+ return PTR_ERR(priv->map);
+
+ return devm_of_clk_add_hw_provider(dev, r8a78000_clk_get, priv);
+}
+
+static const struct clk_map_in r8a78000_cpg_default[] = {
+ { R8A78000_CPG_SGASYNCD4_PERW_BUS, FIXED_CLK(266M) },
+ { R8A78000_CPG_SGASYNCD16_PERW_BUS, FIXED_CLK(66M) },
+ { -1 }
+};
+
+static const struct of_device_id r8a78000_cpg_match[] = {
+ {
+ .compatible = "renesas,r8a78000-cpg",
+ .data = &r8a78000_cpg_default,
+ },
+ { /* sentinel */ }
+};
+
+static struct platform_driver r8a78000_cpg_driver = {
+ .probe = r8a78000_cpg_probe,
+ .driver = {
+ .name = "r8a78000-cpg",
+ .of_match_table = r8a78000_cpg_match,
+ .suppress_bind_attrs = true,
+ },
+};
+
+builtin_platform_driver(r8a78000_cpg_driver)
+
+MODULE_DESCRIPTION("R-Car X5H CPG Driver");
--
2.43.0