What is Linux Clock Provider Device Tree in Linux Kernel

Linux Clock Provider Device Tree-Free Linux Device Drivers Course
Free Linux Kernel Development Course — Common Clock Framework, Part 4
🎓 Free Linux Kernel Course
🧩 Free Linux Device Drivers Course
⚙️ Free Embedded Systems Course

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);
Device Tree Clock Resolution Flow
Consumer node has
clocks = <&provider 0>
Kernel finds matching
of_clk_provider entry
Calls provider’s
get_hw() callback
Returns resolved
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() and of_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 call of_clk_del_provider() yourself in remove().
  • Index out of range in get_hw() — always validate clkspec->args[0] against your clock array bounds and return ERR_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_hw callback 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-cells numbering 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_provider binds a set of registered clocks to a device tree node.
  • of_clk_add_hw_provider() exposes clocks to any consumer using a clocks property and a phandle.
  • of_clk_del_provider() removes the provider; devm_of_clk_add_hw_provider() automates this cleanup.
  • The get_hw callback is where you decode the phandle argument cells into the correct clk_hw.
  • Modern drivers should combine clk_hw_register() with of_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 embedded systems course
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.

Leave a Reply

Your email address will not be published. Required fields are marked *