[PATCH RFC 27/40] RFC: clk/ccf: add UCLASS_CLK compat shim

Casey Connolly casey.connolly at linaro.org
Thu Mar 19 21:56:49 CET 2026


When CCF_FULL is enabled, UCLASS_CLK drivers no longer work normally
since they're entirely invisible to CCF.

This compat shim attempts to bridge the gap by registering a clock
provider with CCF and register clk_hw's on-demand, mapping them
to individual clk IDs of the underlying UCLASS_CLK device. It then
translates between clk_ops and clk_ops_uboot so that clk functions work
as expected.

It's not clear if there is a need for this shim, or if platforms would
instead adjust all of their clock drivers at once. It hasn't been
extensively tested but is provided as-is in case it's useful.

Signed-off-by: Casey Connolly <casey.connolly at linaro.org>
---
 drivers/clk/ccf/Kconfig  |   7 ++
 drivers/clk/ccf/Makefile |   1 +
 drivers/clk/ccf/clk.h    |   6 ++
 drivers/clk/ccf/compat.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/clk-uclass.c |   3 +
 5 files changed, 244 insertions(+)

diff --git a/drivers/clk/ccf/Kconfig b/drivers/clk/ccf/Kconfig
index ec5d2d91b870..4ae131c843a9 100644
--- a/drivers/clk/ccf/Kconfig
+++ b/drivers/clk/ccf/Kconfig
@@ -5,4 +5,11 @@ config CLK_CCF_FULL
 	  Enable this option if you want to use the full Linux kernel's Common
 	  Clock Framework [CCF] code in U-Boot. This is a full API compatible
 	  port as opposed to U-Boot "uCCF", drivers must register clocks at
 	  runtime and should not use UCLASS_CLK.
+
+config CLK_CCF_FULL_COMPAT
+	bool "U-Boot clk compat shim for Common Clock Framework [CCF] clocks"
+	depends on CLK_CCF_FULL
+	help
+	  Enable this option if you want to use U-Boot UCLASS_CLK clocks together
+	  with Linux CCF clocks
diff --git a/drivers/clk/ccf/Makefile b/drivers/clk/ccf/Makefile
index a337b9f29d7a..39879b34e645 100644
--- a/drivers/clk/ccf/Makefile
+++ b/drivers/clk/ccf/Makefile
@@ -14,4 +14,5 @@ obj-y += clk.o \
 	clk-fixed-rate.o
 
 obj-$(CONFIG_CLK_COMPOSITE_CCF) += clk-composite.o
 
+obj-$(CONFIG_CLK_CCF_FULL_COMPAT) += compat.o
diff --git a/drivers/clk/ccf/clk.h b/drivers/clk/ccf/clk.h
index 72607020ed8e..f4cdc51459b5 100644
--- a/drivers/clk/ccf/clk.h
+++ b/drivers/clk/ccf/clk.h
@@ -38,8 +38,14 @@ int clk_hw_create_clk_uboot(struct clk *clk, struct clk_hw *hw)
 }
 void __clk_put(struct clk *clk) { }
 #endif
 
+#if CONFIG_IS_ENABLED(CLK_CCF_FULL_COMPAT)
+void clk_ccf_full_setup_compat(struct udevice *dev);
+#else
+static inline void clk_ccf_full_setup_compat(struct udevice *dev) { }
+#endif
+
 // Linux compat
 
 #define clk_prepare_lock()
 #define clk_prepare_unlock()
diff --git a/drivers/clk/ccf/compat.c b/drivers/clk/ccf/compat.c
new file mode 100644
index 000000000000..3059de04f6ef
--- /dev/null
+++ b/drivers/clk/ccf/compat.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 Linaro Ltd.
+ *
+ * CCF compat for U-Boot UCLASS_CLK type clocks
+ */
+
+#define pr_fmt(fmt) "[CCF Compat] "fmt
+
+#include <dm/devres.h>
+#include <dm/ofnode.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/clk-conf.h>
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/printk.h>
+
+#include "clk.h"
+
+struct ccf_compat_hw {
+	struct clk_hw hw;
+	ulong id;
+	ulong data;
+};
+
+struct ccf_compat_data {
+	struct udevice *dev;
+	int num_clks;
+	struct ccf_compat_hw hws[];
+};
+
+static inline struct clk hw_to_clk(struct clk_hw *hw)
+{
+	struct clk clk;
+	struct ccf_compat_hw *ccf_hw = container_of(hw, struct ccf_compat_hw, hw);
+
+	clk.dev = hw->clk->dev;
+	clk.id = ccf_hw->id;
+	clk.data = ccf_hw->data;
+
+	return clk;
+}
+
+static inline const struct clk_ops_uboot *clk_dev_ops(struct udevice *dev)
+{
+	return (const struct clk_ops_uboot *)dev->driver->ops;
+}
+
+
+static int ccf_compat_clk_enable(struct clk_hw *hw)
+{
+	struct clk clk = hw_to_clk(hw);
+	const struct clk_ops_uboot *ops = clk_dev_ops(clk.dev);
+
+	return ops->enable(&clk);
+}
+
+static void ccf_compat_clk_disable(struct clk_hw *hw)
+{
+	struct clk clk = hw_to_clk(hw);
+	const struct clk_ops_uboot *ops = clk_dev_ops(clk.dev);
+	int ret;
+
+	ret = ops->disable(&clk);
+	if (ret < 0)
+		pr_err("Couldn't disable clk %s: %d\n", clk_hw_get_name(hw), ret);
+}
+
+static long ccf_compat_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct clk clk = hw_to_clk(hw);
+	const struct clk_ops_uboot *ops = clk_dev_ops(clk.dev);
+	long _rate;
+
+	if (!rate && *parent_rate)
+		rate = *parent_rate;
+
+	_rate = ops->round_rate(&clk, rate);
+	if (rate > 0)
+		*parent_rate = _rate;
+
+	return _rate;
+}
+
+static int ccf_compat_clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+	struct clk clk = hw_to_clk(hw);
+	const struct clk_ops_uboot *ops = clk_dev_ops(clk.dev);
+
+	req->rate = ops->get_rate(&clk);
+
+	return 0;
+}
+
+static int ccf_compat_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long parent_rate)
+{
+	struct clk clk = hw_to_clk(hw);
+	const struct clk_ops_uboot *ops = clk_dev_ops(clk.dev);
+	long _rate;
+
+	_rate = ops->set_rate(&clk, rate);
+
+	return _rate < 0 ? _rate : 0;
+}
+
+static int ccf_compat_clk_init(struct clk_hw *hw)
+{
+	struct clk clk = hw_to_clk(hw);
+	const struct clk_ops_uboot *ops = clk_dev_ops(clk.dev);
+
+	return ops->request(&clk);
+}
+
+static struct clk_ops ccf_compat_ops = {
+	.enable = ccf_compat_clk_enable,
+	.disable = ccf_compat_clk_disable,
+	.round_rate = ccf_compat_clk_round_rate,
+	.determine_rate = ccf_compat_clk_determine_rate,
+	.set_rate = ccf_compat_clk_set_rate,
+	.init = ccf_compat_clk_init,
+};
+
+/* Find or allocate a new ccf_compat_hw */
+static struct ccf_compat_hw *ccf_compat_find_hw(struct ccf_compat_data *compat, ulong id,
+						ulong data)
+{
+	struct ccf_compat_hw *ccf_hw;
+	struct clk_init_data *init;
+	int ret;
+
+	for (int i = 0; i < compat->num_clks; i++) {
+		ccf_hw = &compat->hws[i];
+		if (ccf_hw->id == id && ccf_hw->data == data)
+			return ccf_hw;
+	}
+
+	/* We haven't been requested this clock before so we need to allocate it */
+
+	compat->num_clks += 1;
+	compat = devm_krealloc(compat->dev, compat->hws,
+			       sizeof(struct ccf_compat_data) +
+			       sizeof(struct ccf_compat_hw) * compat->num_clks, GFP_KERNEL);
+
+	ccf_hw = &compat->hws[compat->num_clks - 1];
+	ccf_hw->id = id;
+	ccf_hw->data = data;
+
+	/* Allocate init data for this clock */
+	init = devm_kzalloc(compat->dev, sizeof(struct clk_init_data) + 64, GFP_KERNEL);
+	init->ops = &ccf_compat_ops;
+	init->name = (char *)init + sizeof(struct clk_init_data);
+	snprintf((char *)init->name, 64, "compat-%s.%lu.%lu", compat->dev->name, id, data);
+
+	ccf_hw->hw.init = init;
+
+	/*
+	 * Register the new clock we just created so CCF will be able to find the clk_core for the
+	 * clk_hw we just created.
+	 */
+	ret = devm_clk_hw_register(compat->dev, &ccf_hw->hw);
+	if (ret) {
+		printf("Failed to register hw clock %s, err=%d\n", init->name, ret);
+		ccf_hw = NULL;
+	}
+
+	devm_kfree(compat->dev, init);
+	return ccf_hw;
+}
+
+static struct clk_hw *ccf_compat_get_hw(struct ofnode_phandle_args *clkspec, void *_data)
+{
+	struct ccf_compat_data *compat = _data;
+	struct ccf_compat_hw *ccf_hw;
+	struct udevice *dev = compat->dev, *consumer;
+	const struct clk_ops_uboot *uboot_ops = dev->driver->ops;
+	struct clk clk = { 0 };
+	ulong id, data = 0;
+	int ret;
+
+	/* Determine the clk ID and extra data */
+	if (uboot_ops->of_xlate) {
+		ret = device_get_global_by_ofnode(clkspec->node, &consumer);
+		if (ret) {
+			pr_err("%s: couldn't find consumer device, err %d\n",
+			       dev->name, ret);
+			return ERR_PTR(ret);
+		}
+		clk.dev = consumer;
+
+		ret = uboot_ops->of_xlate(&clk, clkspec);
+		if (ret) {
+			pr_err("%s: of_xlate() failed err %d\n", dev->name, ret);
+			return ERR_PTR(ret);
+		}
+
+		id = clk.id;
+		data = clk.data;
+	} else {
+		id = clkspec->args[0];
+	}
+
+	/* Find or create a new ccf_compat_hw for this specific clk */
+	ccf_hw = ccf_compat_find_hw(compat, id, data);
+	if (!ccf_hw)
+		return ERR_PTR(-ENOENT);
+
+	return &ccf_hw->hw;
+}
+
+/**
+ * Shim UCLASS_CLK providers into CCF_FULL so that CCF clocks
+ * can work alongside non-CCF clocks.
+ *
+ * This is ONLY compatible with CONFIG_CLK_CCF_FULL!
+ */
+void clk_ccf_full_setup_compat(struct udevice *dev)
+{
+	struct ccf_compat_data *compat;
+
+	compat = devm_kzalloc(dev, sizeof(*compat), GFP_KERNEL);
+	compat->dev = dev;
+
+	/* Register a clock provider with CCF */
+	devm_of_clk_add_hw_provider(dev, ccf_compat_get_hw, compat);
+}
diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c
index d54d76745560..065c80e8c2b4 100644
--- a/drivers/clk/clk-uclass.c
+++ b/drivers/clk/clk-uclass.c
@@ -894,8 +894,11 @@ int clk_uclass_post_probe(struct udevice *dev)
 	 * using assigned-clocks
 	 */
 	clk_set_defaults(dev, CLK_DEFAULTS_POST);
 
+	if (CONFIG_IS_ENABLED(CLK_CCF_FULL))
+		clk_ccf_full_setup_compat(dev);
+
 	return 0;
 }
 
 UCLASS_DRIVER(clk) = {

-- 
2.51.0



More information about the U-Boot mailing list