In the previous lecture of this free linux kernel development course, we covered name-based clock lookup and clock unregistration. But on almost every modern embedded board, clocks are described through the device tree, not through name strings. This lecture explains linux clock provider device tree integration in detail: how struct of_clk_provider binds a clock to a DTS node, and how of_clk_add_hw_provider() is used to expose your clocks so that any consumer node with a clocks property can find them automatically.
This is lecture 4 of the Common Clock Framework series inside our free linux device drivers course and free embedded systems course at EmbeddedPathashala.
What You Will Learn
- Why device tree changed how clocks are exposed to consumers
- The internal fields of
struct of_clk_provider - How to use
of_clk_add_hw_provider()step by step - How to remove a provider with
of_clk_del_provider() - How the managed variant
devm_of_clk_add_hw_provider()simplifies cleanup - How to write and test an original clock provider driver bound to a DT node
Prerequisites
- Completion of the earlier CCF lectures in this series (data structures, provider registration, clock unregistration)
- Basic device tree syntax (nodes, properties, phandles)
- A kernel 6.x build environment with device tree support enabled
From Name Lookup to Device Tree Lookup
In the early days of the Linux kernel, before device tree was widely adopted, a clock provider exposed each of its clock lines by calling clk_hw_register_clkdev() once per clock, registering a name-based lookup entry for every single line. This worked, but it did not scale well and tied driver code to board-specific naming conventions.
Today, each clock provider is represented by a node in the device tree, and instead of bundling a clock with a name string, the kernel bundles a clock with its device tree node using a dedicated data structure: struct of_clk_provider.
struct of_clk_provider: Binding a Clock to a DT Node
struct of_clk_provider {
struct list_head link;
struct device_node *node;
struct clk *(*get)(struct of_phandle_args *clkspec, void *data);
struct clk_hw *(*get_hw)(struct of_phandle_args *clkspec, void *data);
void *data;
};
| Field | Meaning |
|---|---|
link |
Links this provider into the kernel’s global of_clk_providers list |
node |
The device tree node that represents this clock provider hardware |
get_hw |
Callback the kernel calls to resolve a consumer’s phandle reference into a clk_hw |
get |
Legacy equivalent of get_hw, kept only for old, non-clk_hw based drivers |
data |
A private context pointer passed back to your get_hw callback, usually your driver’s clock array |
Every registered provider is tracked in one global, mutex-protected list so the kernel can walk it whenever a consumer driver calls clk_get() with a phandle-based clock reference:
static LIST_HEAD(of_clk_providers);
static DEFINE_MUTEX(of_clk_mutex);
clocks = <&provider 0>
of_clk_provider entry
get_hw() callback
struct clk_hw
Registering a Provider: of_clk_add_hw_provider()
Once your clocks are registered with clk_hw_register() (or its devm_* variant), the next step is exposing them to the device tree by calling of_clk_add_hw_provider():
int of_clk_add_hw_provider(
struct device_node *np,
struct clk_hw *(*get)(struct of_phandle_args *clkspec, void *data),
void *data);
| Argument | Description |
|---|---|
np |
Device node pointer for the clock provider, typically pdev->dev.of_node |
get |
Your callback that decodes a phandle specifier into the right clk_hw |
data |
Context pointer, usually the array of registered clocks for this device |
This function returns 0 on success. Its counterpart, used during driver removal, is:
void of_clk_del_provider(struct device_node *np);
And its resource-managed version, which removes the need to call the delete function manually, is devm_of_clk_add_hw_provider().
Important note: Some SoC clock drivers still call both
clk_hw_register_clkdev()andof_clk_add_hw_provider()to support boards with and without device tree. Unless you have that exact compatibility requirement, stick to only the device tree provider path.
Hands-On Example: ep_of_clk_provider_demo
Let’s write an original driver that registers two fixed clock lines and exposes both through a single of_clk_add_hw_provider() call, using a simple index-based get_hw callback.
// ep_of_clk_provider_demo.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_device.h>
#define EP_CLK_COUNT 2
struct ep_clk_bank {
struct clk_hw *hws[EP_CLK_COUNT];
};
static struct clk_hw *ep_clk_get_hw(struct of_phandle_args *clkspec, void *data)
{
struct ep_clk_bank *bank = data;
unsigned int idx = clkspec->args[0];
if (idx >= EP_CLK_COUNT)
return ERR_PTR(-EINVAL);
return bank->hws[idx];
}
static int ep_of_clk_provider_demo_probe(struct platform_device *pdev)
{
struct ep_clk_bank *bank;
int ret;
bank = devm_kzalloc(&pdev->dev, sizeof(*bank), GFP_KERNEL);
if (!bank)
return -ENOMEM;
bank->hws[0] = devm_clk_hw_register_fixed_rate(&pdev->dev,
"ep_periph_clk", NULL, 0, 48000000);
if (IS_ERR(bank->hws[0]))
return PTR_ERR(bank->hws[0]);
bank->hws[1] = devm_clk_hw_register_fixed_rate(&pdev->dev,
"ep_bus_clk", NULL, 0, 12000000);
if (IS_ERR(bank->hws[1]))
return PTR_ERR(bank->hws[1]);
ret = devm_of_clk_add_hw_provider(&pdev->dev, ep_clk_get_hw, bank);
if (ret) {
dev_err(&pdev->dev, "failed to register clock provider\n");
return ret;
}
dev_info(&pdev->dev, "ep_of_clk_provider_demo: 2 clocks exposed via DT\n");
return 0;
}
static const struct of_device_id ep_of_clk_provider_demo_of_match[] = {
{ .compatible = "ep,of-clk-provider-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_of_clk_provider_demo_of_match);
static struct platform_driver ep_of_clk_provider_demo_driver = {
.probe = ep_of_clk_provider_demo_probe,
.driver = {
.name = "ep_of_clk_provider_demo",
.of_match_table = ep_of_clk_provider_demo_of_match,
},
};
module_platform_driver(ep_of_clk_provider_demo_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Demo driver for of_clk_add_hw_provider with two clock outputs");
Matching Device Tree Nodes
ep_clk_provider: ep-clk-provider {
compatible = "ep,of-clk-provider-demo";
#clock-cells = <1>;
};
ep_consumer: ep-consumer {
compatible = "ep,consumer-demo";
clocks = <&ep_clk_provider 0>, <&ep_clk_provider 1>;
clock-names = "periph", "bus";
};
Notice the #clock-cells = <1> property. It tells the device tree that each clock reference to this provider carries exactly one argument cell — the index used inside our ep_clk_get_hw() callback.
Build and Load Steps
make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo insmod ep_of_clk_provider_demo.ko
dmesg | tail -n 5
Expected Output
[ 2001.331122] ep_of_clk_provider_demo: 2 clocks exposed via DT
Confirm both clocks are visible to the CCF core via debugfs:
cat /sys/kernel/debug/clk/clk_summary | grep ep_periph_clk
cat /sys/kernel/debug/clk/clk_summary | grep ep_bus_clk
Comparison: Legacy vs Modern Provider Exposure
| Approach | Function Combination | Status |
|---|---|---|
| Legacy name-based | clk_register() + clk_register_clkdev() |
Deprecated, avoid in new code |
| Legacy device tree | clk_register() + of_clk_add_provider() |
Found in old drivers, not recommended |
| Modern name-based | clk_hw_register() + clk_hw_register_clkdev() |
Use only for genuine non-DT needs |
| Modern device tree | clk_hw_register() + of_clk_add_hw_provider() |
Recommended for all new provider drivers |
Common Mistakes
- Mismatched #clock-cells — if your provider node declares
#clock-cells = <1>but the consumer only supplies zero arguments, resolution will fail. - Forgetting devm_of_clk_add_hw_provider() cleanup — if you use the plain
of_clk_add_hw_provider(), you must callof_clk_del_provider()yourself inremove(). - Index out of range in get_hw() — always validate
clkspec->args[0]against your clock array bounds and returnERR_PTR(-EINVAL)rather than crashing. - Registering the provider before the clocks exist — always call
of_clk_add_hw_provider()only after every clock in your bank has been successfully registered.
Best Practices
- Prefer
devm_of_clk_add_hw_provider()so provider removal is handled automatically. - Keep your
get_hwcallback small, defensive, and free of side effects. - Use a single provider registration call per device even when exposing multiple clock lines, exactly as shown in the example.
- Document your
#clock-cellsnumbering scheme in your DT binding so consumer authors know which index maps to which clock.
Security and Performance Considerations
The of_clk_providers list is walked under a mutex every time a consumer resolves a clock phandle, so an unusually large number of providers on a system could add a small amount of lookup latency during boot; in practice this is negligible compared to overall boot time. From a robustness standpoint, always validate the index or arguments passed into your get_hw callback — a malformed or malicious device tree overlay should never be able to cause an out-of-bounds array access in your driver.
Real-World Use Case
A typical SoC clock controller driver registers dozens of PLLs, dividers, and gate clocks during probe, then exposes all of them through a single of_clk_add_hw_provider() call with a get_hw callback that indexes into a large internal clock array. Every peripheral node in the device tree — UART, I2C, SPI, USB — then simply references this one provider with a numeric index, without any driver needing to know clock names at all.
Summary and Key Takeaways
struct of_clk_providerbinds a set of registered clocks to a device tree node.of_clk_add_hw_provider()exposes clocks to any consumer using aclocksproperty and a phandle.of_clk_del_provider()removes the provider;devm_of_clk_add_hw_provider()automates this cleanup.- The
get_hwcallback is where you decode the phandle argument cells into the correctclk_hw. - Modern drivers should combine
clk_hw_register()withof_clk_add_hw_provider()and avoid the legacy name-based path unless required.
Conclusion
With this lecture, our free linux kernel development course has now covered the complete lifecycle of a clock provider driver: registering clocks, exposing them through linux clock provider device tree bindings, and safely unregistering everything on removal. This device-tree-based exposure model is what almost every modern SoC clock driver uses today, making it one of the most important building blocks in embedded Linux clock management.
Frequently Asked Questions
What is struct of_clk_provider used for?
It binds a registered clock (or set of clocks) to a device tree node so that consumer nodes can reference it using a phandle in their clocks property.
What does the get_hw callback do in of_clk_add_hw_provider?
It receives the phandle argument cells from a consumer’s clock reference and returns the matching clk_hw pointer, or an error if the reference is invalid.
Why do I need #clock-cells in my provider’s DT node?
It tells the device tree how many argument cells each clock reference to this provider carries, which your get_hw callback uses to identify the exact clock line requested.
Do I still need of_clk_del_provider() if I use devm_of_clk_add_hw_provider()?
No, the managed variant automatically removes the provider when the device is detached, so you do not need to call of_clk_del_provider() yourself.
Can a single provider expose multiple clocks?
Yes, exactly as shown in the ep_of_clk_provider_demo example, one of_clk_add_hw_provider() call combined with an index-based get_hw callback can expose any number of registered clocks.
What is the difference between get and get_hw in struct of_clk_provider?
get_hw is the modern callback returning a clk_hw pointer for provider-side code. get is the legacy equivalent kept only for old, non-clk_hw based drivers.
Is of_clk_add_provider() still valid in modern kernels?
It still exists for backward compatibility with old, non-clk_hw drivers, but new provider drivers should use of_clk_add_hw_provider() instead.
Related Free Courses on EmbeddedPathashala
free linux development course
free linux device drivers course
free linux kernel development course
linux clock provider device tree
Continue Your Free Linux Kernel Journey
Explore more lectures in our free embedded Linux and device driver course series.
