If you are following our free embedded Linux course, you already know that a clock provider driver must register its clocks with the Common Clock Framework (CCF) before any consumer can use them. But registration alone is not enough. Somewhere the kernel needs a way to look up a clock by name, and just as importantly, a way to safely tear it down when a device is removed or probing fails. This lecture on linux clock unregistration api and clock lookup completes that picture by walking through the internal struct clk_lookup, the modern clk_hw_register_clkdev() helper, and the full set of clock unregistration functions available in the current kernel.
This is lecture 3 of our Common Clock Framework series, part of the growing free linux device drivers course and free linux kernel development course available on EmbeddedPathashala. If you have not read the earlier lectures on CCF data structures and clock provider registration, we recommend going through those first.
What You Will Learn
- How the kernel resolves a clock lookup using
struct clk_lookup - Why
clk_hw_register_clkdev()replaced the older name-based registration approach - The difference between
clk_hw_register()andclk_hw_register_clkdev() - All four clock unregistration APIs and when each one is required
- How to write, build, and test a driver that safely unregisters its clock
- Common mistakes engineers make while tearing down clock providers
Prerequisites
- A working Linux kernel build environment (kernel 6.x recommended)
- Basic familiarity with platform drivers (
probe()/remove()) - Completion of our earlier lectures on CCF data structures and clock provider registration
- Basic C programming knowledge
Why Clock Lookup Still Matters
Before the device tree became the default way to describe hardware, a clock provider had no automatic way to tell a consumer driver which clock belonged to it. The kernel solved this with a small lookup table. Every time a clock was registered for name-based lookup, an entry was added to a global list that mapped a device identifier and a connection identifier to an actual clock. Even today, on platforms that still rely on board files or on legacy non-device-tree code paths, this lookup mechanism is alive and well inside the CCF core.
struct clk_lookup: The Core Data Structure
At the heart of this mechanism sits struct clk_lookup. Conceptually it is a very small structure, but each field plays an important role in how the kernel resolves a clock lookup request.
struct clk_lookup {
struct list_head node;
const char *dev_id;
const char *con_id;
struct clk *clk;
struct clk_hw *clk_hw;
};
clk_get(dev, con_id)
global clocks list
dev_id + con_id
struct clk
Here is what each field means in plain language:
- node — the list entry used to link this lookup into the kernel’s global clocks list
- dev_id — usually the device name, used to identify which device this clock belongs to
- con_id — the “connection id”, a string that names the specific clock line (for example “uart_clk” or NULL for a default clock)
- clk — the legacy consumer-facing clock pointer
- clk_hw — the modern provider-facing clock handle
Internally, whenever a lookup entry is added, the kernel simply appends it to a protected linked list, guarded by a mutex so that concurrent registration from multiple drivers stays safe:
static void __clkdev_add(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);
list_add_tail(&cl->node, &clocks);
mutex_unlock(&clocks_mutex);
}
You will rarely call this function directly. It is invoked internally by the registration helper we look at next.
Registering a Name-Based Clock Lookup
The modern, recommended function for registering a name-based clock lookup entry is clk_hw_register_clkdev(). It wraps a lower-level legacy function internally, but as a driver author you should only ever call the clk_hw-based version.
int clk_hw_register_clkdev(struct clk_hw *hw,
const char *con_id,
const char *dev_id);
| Parameter | Meaning |
|---|---|
hw |
The already-registered clk_hw handle for this clock |
con_id |
Connection id string consumers will request, or NULL |
dev_id |
Device name string this clock is bound to |
clk_hw_register() vs clk_hw_register_clkdev(): Don’t Confuse Them
These two function names look almost identical, which trips up a lot of beginners in our free embedded systems course. They solve two completely different problems:
| Function | Purpose |
|---|---|
clk_hw_register() |
Registers the clock itself in the Common Clock Framework core |
clk_hw_register_clkdev() |
Registers a lookup entry so a consumer can later find that already-registered clock by name |
Important note: On a device-tree-only platform, you almost never need
clk_hw_register_clkdev(). Device tree consumers resolve clocks throughof_clk_add_hw_provider()instead (covered in the next lecture). Only reach for name-based lookup when you have a genuine legacy or non-DT use case.
Linux Clock Unregistration API: Tearing Down a Clock Safely
Every clock a driver registers must eventually be unregistered — either because the underlying hardware is removed, or because something failed midway during probe() and the driver needs to unwind cleanly. Getting this wrong is one of the most common sources of use-after-free bugs in provider drivers, so understanding the full linux clock unregistration api surface is essential.
The Four Unregistration Functions
| Function | Targets | When to Use |
|---|---|---|
clk_hw_unregister(struct clk_hw *hw) |
clk_hw-based clocks |
Manual cleanup of a modern clock, e.g. in a plain remove() callback |
clk_unregister(struct clk *clk) |
Legacy clk-based clocks |
Only in old drivers still using the legacy consumer-style registration |
devm_clk_hw_unregister(struct device *dev, struct clk_hw *hw) |
clk_hw-based clocks |
Devres-managed cleanup when the clock was registered with a plain (non-devm) call but you still want automatic teardown |
devm_clk_unregister(struct device *dev, struct clk *clk) |
Legacy clk-based clocks |
Devres-managed cleanup for legacy clocks |
Tip: If you originally registered your clock with
devm_clk_hw_register(), the Devres core already unregisters it automatically on driver detach — you do not need to callclk_hw_unregister()yourself. Manual unregistration functions exist mainly for clocks registered with the plain, non-managed variants, or for early error-path cleanup insideprobe().
Hands-On Example: ep_clk_unregister_demo
Let’s build a small, original platform driver that registers a fixed-rate clock along with a name-based clock lookup entry, and then correctly unregisters both on removal. This driver is written from scratch for this lecture and targets modern kernel 6.x APIs.
// ep_clk_unregister_demo.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/of.h>
struct ep_clk_priv {
struct clk_hw hw;
};
static const struct clk_ops ep_clk_ops = {
.recalc_rate = NULL, /* fixed rate demo, using clk_hw_register_fixed_rate below instead */
};
static int ep_clk_unregister_demo_probe(struct platform_device *pdev)
{
struct clk_hw *hw;
int ret;
/* Step 1: register a simple fixed-rate clock at 24 MHz */
hw = clk_hw_register_fixed_rate(&pdev->dev, "ep_demo_clk", NULL, 0, 24000000);
if (IS_ERR(hw)) {
dev_err(&pdev->dev, "failed to register ep_demo_clk\n");
return PTR_ERR(hw);
}
/* Step 2: register a name-based lookup so a consumer can find it */
ret = clk_hw_register_clkdev(hw, "ep_uart_clk", "ep-uart-consumer");
if (ret) {
dev_err(&pdev->dev, "failed to register clkdev lookup\n");
clk_hw_unregister_fixed_rate(hw);
return ret;
}
platform_set_drvdata(pdev, hw);
dev_info(&pdev->dev, "ep_clk_unregister_demo: clock registered at 24MHz\n");
return 0;
}
static int ep_clk_unregister_demo_remove(struct platform_device *pdev)
{
struct clk_hw *hw = platform_get_drvdata(pdev);
clk_hw_unregister_fixed_rate(hw);
dev_info(&pdev->dev, "ep_clk_unregister_demo: clock unregistered cleanly\n");
return 0;
}
static const struct of_device_id ep_clk_unregister_demo_of_match[] = {
{ .compatible = "ep,clk-unregister-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_clk_unregister_demo_of_match);
static struct platform_driver ep_clk_unregister_demo_driver = {
.probe = ep_clk_unregister_demo_probe,
.remove = ep_clk_unregister_demo_remove,
.driver = {
.name = "ep_clk_unregister_demo",
.of_match_table = ep_clk_unregister_demo_of_match,
},
};
module_platform_driver(ep_clk_unregister_demo_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Demo driver for clk_hw_register_clkdev and unregistration");
Matching Device Tree Node
ep_clk_demo: ep-clk-demo {
compatible = "ep,clk-unregister-demo";
};
Build and Load Steps
make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo insmod ep_clk_unregister_demo.ko
dmesg | tail -n 5
sudo rmmod ep_clk_unregister_demo
dmesg | tail -n 5
Expected Output
[ 1234.001122] ep_clk_unregister_demo: clock registered at 24MHz
...
[ 1240.556677] ep_clk_unregister_demo: clock unregistered cleanly
You can also verify the clock briefly appeared in the clock tree while the module was loaded:
cat /sys/kernel/debug/clk/clk_summary | grep ep_demo_clk
Common Mistakes with Clock Unregistration
- Forgetting to unregister in an error path — if
clk_hw_register_clkdev()fails after a successfulclk_hw_register(), you must still unregister the clock before returning an error, exactly as shown above. - Mixing legacy and modern APIs — never call
clk_unregister()on a handle obtained from aclk_hw_register()family function, and vice versa. - Double unregistration — calling both a manual unregister function and relying on devm cleanup for the same clock will cause a use-after-free.
- Ignoring the return value of registration functions — always check with
IS_ERR()before proceeding.
Best Practices
- Prefer the
devm_*variants whenever the clock’s lifetime matches the device’s lifetime. - Only add a clkdev lookup entry if you have a genuine non-DT consumer; otherwise rely on the DT provider mechanism.
- Always unwind clock registration in the reverse order of registration during error handling.
- Use
clk_summaryin debugfs during development to confirm clocks appear and disappear as expected.
Security and Performance Considerations
Clock unregistration itself is not commonly a security-sensitive path, but leaving stale clkdev lookup entries behind after a module unload can cause a later consumer driver to dereference a freed clk_hw pointer, leading to a kernel panic. From a performance standpoint, the clocks list and lookup list are protected by mutexes, so registering or unregistering a very large number of clocks in a tight loop (uncommon in real drivers) can introduce lock contention; this is rarely an issue for normal driver probe/remove flows.
Real-World Use Case
A typical SoC PMIC (power management IC) driver registers several regulator-derived clocks during probe. If the PMIC is removed (for example, on a hot-pluggable expansion board), the driver’s remove() callback must unregister every clock it registered, in the correct order, so that dependent peripheral drivers do not reference a dangling clock handle. This exact pattern — register, expose to consumers, and cleanly unregister — is why understanding the full linux clock unregistration api matters for production-quality drivers.
Summary and Key Takeaways
struct clk_lookupmaps adev_id/con_idpair to a clock for name-based consumer lookup.clk_hw_register_clkdev()is the modern way to add such a lookup entry; it is rarely needed on device-tree-only platforms.clk_hw_register()registers the clock itself;clk_hw_register_clkdev()only registers a way to find it — they are not interchangeable.- Four unregistration functions exist:
clk_hw_unregister(),clk_unregister(), and theirdevm_*managed counterparts. - Devres-managed clocks are unregistered automatically; manually registered clocks must be unregistered explicitly in
remove()or in error paths.
Conclusion
Clock lookup and clock unregistration are two sides of the same coin: one lets a consumer find a clock, and the other guarantees that once a clock provider goes away, nothing in the kernel is left holding a dangling reference to it. Mastering the linux clock unregistration api alongside the older name-based lookup mechanism gives you a complete, production-ready understanding of clock provider lifecycle management. In the next lecture of this free linux kernel development course, we move to the device-tree-based side of exposing clocks with struct of_clk_provider and of_clk_add_hw_provider().
Frequently Asked Questions
What is the difference between clk_hw_register() and clk_hw_register_clkdev()?
clk_hw_register() creates the clock in the Common Clock Framework core. clk_hw_register_clkdev() only adds a name-based lookup entry so a consumer without device tree support can find an already-registered clock.
Do I need clk_hw_register_clkdev() on a device tree platform?
Usually not. Device tree consumers resolve clocks through of_clk_add_hw_provider(), which we cover in the next lecture. Name-based lookup is mainly kept for legacy or non-DT boards.
What happens if I forget to unregister a clock in remove()?
The clock and its lookup entry remain in the kernel’s internal lists even though the underlying hardware or module is gone, which can lead to a later consumer dereferencing a stale pointer and crashing the kernel.
Should I use devm_clk_hw_unregister() or clk_hw_unregister()?
Use the devm variant if you want the Devres core to automatically unregister the clock when the device detaches. Use the plain function when you need explicit control, such as in an early error path inside probe().
Can I mix clk_unregister() with a clk_hw_register()-based clock?
No. Legacy and modern clock APIs use different underlying handles. Always match clk_hw_register() with clk_hw_unregister(), and legacy clk_register() with clk_unregister().
What does the con_id field in struct clk_lookup actually do?
It is a string identifying a specific clock line for a device, allowing one device to have multiple named clocks looked up independently. It can be NULL if the device only has a single default clock.
Is struct clk_lookup something I need to fill in manually?
No. You should always go through clk_hw_register_clkdev(), which fills in and manages the clk_lookup structure internally.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{“@type”: “Question”, “name”: “What is the difference between clk_hw_register() and clk_hw_register_clkdev()?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “clk_hw_register() creates the clock in the Common Clock Framework core. clk_hw_register_clkdev() only adds a name-based lookup entry so a consumer without device tree support can find an already-registered clock.”}},
{“@type”: “Question”, “name”: “Do I need clk_hw_register_clkdev() on a device tree platform?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Usually not. Device tree consumers resolve clocks through of_clk_add_hw_provider(). Name-based lookup is mainly kept for legacy or non-DT boards.”}},
{“@type”: “Question”, “name”: “What happens if I forget to unregister a clock in remove()?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “The clock and its lookup entry remain in the kernel’s internal lists even though the hardware is gone, which can lead to a crash when a later consumer dereferences a stale pointer.”}},
{“@type”: “Question”, “name”: “Should I use devm_clk_hw_unregister() or clk_hw_unregister()?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Use the devm variant for automatic cleanup on device detach. Use the plain function for explicit control, such as in an early probe() error path.”}},
{“@type”: “Question”, “name”: “Can I mix clk_unregister() with a clk_hw_register()-based clock?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No, always match the legacy and modern API families consistently.”}},
{“@type”: “Question”, “name”: “What does the con_id field in struct clk_lookup actually do?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “It identifies a specific named clock line for a device, allowing multiple clocks per device to be looked up independently.”}},
{“@type”: “Question”, “name”: “Is struct clk_lookup something I need to fill in manually?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No, clk_hw_register_clkdev() manages it internally.”}}
]
}
Related Free Courses on EmbeddedPathashala
free linux development course
free linux device drivers course
free linux kernel development course
linux clock unregistration api
Continue Your Free Linux Kernel Journey
Explore more lectures in our free embedded Linux and device driver course series.
