[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