Linux Composite Clock Driver Guide-Free Linux Device Drivers Course
Combining mux, divider, and gate hardware blocks into one clk_hw with the Common Clock Framework
What You Will Learn
clk_hw_register_composite()
mux + rate + gate composition
mmio-only constraint recap
building an ep_composite_clk_demo driver
Prerequisites
This lecture assumes you already understand the individual clock types covered earlier in this linux composite clock driver series — specifically the gate clock (struct clk_gate), the mux clock (struct clk_mux), and the divider clock (struct clk_divider) — since a composite clock is literally an assembly of instances of these three. If any of those feel unfamiliar, it is worth revisiting them before continuing, because this lecture will not re-derive their internals from scratch.
Why a Separate Composite Abstraction Exists
You might reasonably ask: if clk_hw_register_gate(), clk_hw_register_mux(), and clk_hw_register_divider() already exist, why not just register three clocks and chain them with clock-output-names and parent references? Technically you can — but doing so creates three visible entries in the clock tree (clk_summary, clk_hw lookups, debugfs) for what is, from a consumer’s point of view, one physical output. It also means three separate registrations, three separate error-handling paths, and three separate teardown calls, when the hardware exposes exactly one enable, one divider, and one input selector for one output pin. The composite clock framework solves this by presenting a single clk_hw to the rest of the kernel while internally forwarding operations — get_parent, set_parent, recalc_rate, round_rate, set_rate, is_enabled, enable, disable — to whichever of the three sub-clocks actually implements that operation.
struct clk_composite Field by Field
The Common Clock Framework represents a composite clock with the following structure:
struct clk_composite {
struct clk_hw hw;
struct clk_ops ops;
struct clk_hw *mux_hw;
struct clk_hw *rate_hw;
struct clk_hw *gate_hw;
const struct clk_ops *mux_ops;
const struct clk_ops *rate_ops;
const struct clk_ops *gate_ops;
};
#define to_clk_composite(_hw) container_of(_hw, struct clk_composite, hw)
- hw — the usual clk_hw handle that ties this driver-private structure into the generic CCF core, exactly like every other clock type we have registered.
- mux_hw — a pointer to an already-initialized clk_hw representing the mux portion (typically a struct clk_mux embedding its own clk_hw).
- rate_hw — a pointer to the clk_hw representing whichever rate-changing block is present; almost always a struct clk_divider, though nothing prevents a custom rate-capable clk_hw here.
- gate_hw — a pointer to the clk_hw representing the enable/disable block, typically a struct clk_gate.
- mux_ops / rate_ops / gate_ops — the corresponding clk_ops for each sub-block. These are usually the exported built-in ops (clk_mux_ops, clk_divider_ops, clk_gate_ops) you already used when registering those clock types standalone, reused here rather than duplicated.
Any of mux_hw, rate_hw, or gate_hw may be NULL if that particular stage does not exist for a given output — for example, a branch that only has a divider and a gate, with no mux, simply leaves mux_hw and mux_ops NULL. The composite core detects which stages are present and synthesizes a combined clk_ops accordingly, only forwarding to the ops that are actually populated.
How a Composite clk_ops Call Is Routed
Composite Clock Call Forwarding
|
v
composite_ops.set_rate()
|
+–> rate_hw present? –> yes –> rate_ops->set_rate(rate_hw, …)
| (delegates to the divider’s own logic)
+–> rate_hw NULL? –> yes –> call is a no-op / returns cached rateconsumer calls clk_enable(clk)
|
v
composite_ops.enable()
|
+–> gate_hw present? –> yes –> gate_ops->enable(gate_hw)
+–> gate_hw NULL? –> yes –> treated as always-enabled
Registering a Composite Clock: clk_hw_register_composite()
Composite clocks are registered through the following API, declared in linux/clk-provider.h:
struct clk_hw *clk_hw_register_composite(
struct device *dev, const char *name,
const char * const *parent_names, int num_parents,
struct clk_hw *mux_hw,
const struct clk_ops *mux_ops,
struct clk_hw *rate_hw,
const struct clk_ops *rate_ops,
struct clk_hw *gate_hw,
const struct clk_ops *gate_ops,
unsigned long flags);
A managed devm_clk_hw_register_composite() variant exists as well and is the recommended choice in modern platform/MMIO drivers, since it ties clock lifetime to the struct device and avoids a manual unregister path. Every clk_hw pointer you pass in must already be initialized — meaning you build the mux, divider, and gate clk_hw objects first (by embedding struct clk_mux, struct clk_divider, and struct clk_gate in your own driver data as shown in earlier lectures), and only then hand their addresses to clk_hw_register_composite(), which stitches them together and returns one combined clk_hw for the whole branch. Just like the plain gate and mux registration helpers, this interface is mmio-only: it assumes register-based enable/disable and divider math. Non-mmio composite-style clocks — for example an I2C-controlled clock chip that internally has a mux and a divider — must fall back to the low-level clk_hw_register() plus a hand-written clk_ops, the same escape hatch we covered when discussing SPI/I2C-based gate and mux clocks earlier in this chapter.
Worked Example: ep_composite_clk_demo
The following original demo builds a composite clock out of a two-parent mux, a 3-bit divider, and a single-bit gate — all packed into one imaginary 32-bit control register at offset 0x40 of a platform device’s MMIO region, which is a common layout on real clock controllers.
#define EP_COMP_REG 0x40
#define EP_COMP_MUX_SHIFT 8
#define EP_COMP_MUX_WIDTH 1
#define EP_COMP_DIV_SHIFT 4
#define EP_COMP_DIV_WIDTH 3
#define EP_COMP_GATE_BIT 0
struct ep_composite_priv {
struct clk_mux mux;
struct clk_divider div;
struct clk_gate gate;
struct clk_hw *hw;
void __iomem *base;
};
static int ep_composite_probe(struct platform_device *pdev)
{
struct ep_composite_priv *priv;
const char *parents[] = { "ep_pll_a", "ep_pll_b" };
struct clk_hw *hw;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
priv->mux.reg = priv->base + EP_COMP_REG;
priv->mux.shift = EP_COMP_MUX_SHIFT;
priv->mux.mask = BIT(EP_COMP_MUX_WIDTH) - 1;
priv->div.reg = priv->base + EP_COMP_REG;
priv->div.shift = EP_COMP_DIV_SHIFT;
priv->div.width = EP_COMP_DIV_WIDTH;
priv->gate.reg = priv->base + EP_COMP_REG;
priv->gate.bit_idx = EP_COMP_GATE_BIT;
hw = devm_clk_hw_register_composite(&pdev->dev, "ep_branch_clk",
parents, ARRAY_SIZE(parents),
&priv->mux.hw, &clk_mux_ops,
&priv->div.hw, &clk_divider_ops,
&priv->gate.hw, &clk_gate_ops,
0);
if (IS_ERR(hw))
return PTR_ERR(hw);
priv->hw = hw;
platform_set_drvdata(pdev, priv);
return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, hw);
}
static const struct of_device_id ep_composite_of_match[] = {
{ .compatible = "ep,composite-clk" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_composite_of_match);
static struct platform_driver ep_composite_driver = {
.probe = ep_composite_probe,
.driver = {
.name = "ep_composite_clk",
.of_match_table = ep_composite_of_match,
},
};
module_platform_driver(ep_composite_driver);
Notice that priv->mux, priv->div, and priv->gate all point their reg field at the same offset — this is intentional and reflects how real composite clock branches typically pack mux, divider, and gate bit fields into one control register, exactly the scenario this abstraction exists to handle cleanly.
Build and Test Walkthrough
$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_composite_clk.ko
$ dmesg | tail
[ 102.881233] ep_composite_clk ep_composite_clk.0: registered ep_branch_clk (mux+div+gate)
$ sudo mount -t debugfs none /sys/kernel/debug 2>/dev/null
$ cat /sys/kernel/debug/clk/ep_branch_clk/clk_rate
48000000
$ cat /sys/kernel/debug/clk/ep_branch_clk/clk_enable_count
1
Comparison: Composite vs. Manually Chained Clocks
| Aspect | Composite clk_hw | Three separate clk_hw nodes |
|---|---|---|
| Entries in clk_summary | One | Three |
| Registration calls | One | Three, plus manual parent wiring |
| Teardown paths | One (or one devm_ cleanup) | Three |
| Matches physical hardware | Yes — one output pin, one clk_hw | No — invents intermediate nodes |
| Mmio-only | Yes | Yes (per sub-block) |
Common Mistakes
- Passing a clk_hw that was never separately initialized (e.g. forgetting to set priv->mux.mask before registration) — the composite core does not initialize the sub-structures for you.
- Assuming all three of mux_hw, rate_hw, and gate_hw are mandatory. Any subset is valid; pass NULL/NULL for stages that do not exist in your hardware.
- Trying to use clk_hw_register_composite() for a non-mmio (I2C/SPI) composite chip — it will compile but will not behave correctly, since the underlying mux/divider/gate ops assume direct register access.
- Forgetting that mux_ops/rate_ops/gate_ops are the shared, exported built-in ops structures — you should reuse clk_mux_ops, clk_divider_ops, and clk_gate_ops rather than redefining them per driver, unless you have a genuine hardware-specific override (as covered when we discussed reusing individual built-in callbacks).
Best Practices
- Prefer the devm_ variant (devm_clk_hw_register_composite()) in platform drivers so clock lifetime is automatically tied to the device.
- Keep the sub-structures (clk_mux, clk_divider, clk_gate) inside your own driver-private struct, exactly as you would for a standalone gate or mux clock — the composite framework does not allocate them for you.
- Document, in a comment near the register offsets, which bit fields belong to mux/divider/gate — composite register layouts are the easiest place in a clock driver to introduce an off-by-one bit-shift bug.
Summary and Key Takeaways
The composite clock type is the natural conclusion of everything this chapter has built toward: gate, mux, fixed-factor, and divider clocks are not just standalone building blocks — they are also reusable components that struct clk_composite and clk_hw_register_composite() can bolt together behind a single clk_hw whenever real hardware packs several of these behaviors into one physical output. Understanding struct clk_composite’s mux_hw/rate_hw/gate_hw/*_ops fields, and knowing when to reach for this abstraction instead of three independently registered clocks, is exactly the kind of practical judgment call this free linux kernel development course is designed to build. In the next lecture, we bring every clock type covered so far together into one full, worked clock tree — oscillator, PLL-style derived clocks, mux, and device tree modeling — before turning to the clk_summary debugfs interface for verifying it all at runtime.
FAQ
Do I need all three of mux_hw, rate_hw, and gate_hw for a composite clock?
No. Any of the three may be NULL if that stage does not exist in hardware; the composite ops only forward calls to the stages that are actually populated.
Can a composite clock be used for an I2C or SPI-based clock chip?
Not through clk_hw_register_composite() — it assumes mmio register access. Non-mmio composite-style hardware needs the low-level clk_hw_register() plus hand-written clk_ops.
Do I write new mux_ops/rate_ops/gate_ops for a composite clock?
Usually not — you reuse the same exported built-in ops (clk_mux_ops, clk_divider_ops, clk_gate_ops) you would use to register those clock types standalone.
Does a composite clock create extra entries in clk_summary?
No — that is precisely its advantage. One clk_hw, one clk_summary entry, even though internally three sub-blocks are involved.
Is there a managed (devm_) version of clk_hw_register_composite()?
Yes, devm_clk_hw_register_composite() ties the clock’s lifetime to the struct device and is the recommended choice for platform drivers.
Where can I find a real kernel example of a composite clock driver?
Several SoC clock controller drivers under drivers/clk/ use this pattern for clock branches that combine mux, divider, and gate hardware behind one output.
What happens if I pass an uninitialized clk_mux structure as mux_hw?
The registration will not fail outright, but the mux behavior will be wrong at runtime since fields like mask, shift, and reg were never set — always fully initialize each sub-structure before registration.
Keep Building Real Kernel Skills
This lecture is part of EmbeddedPathashala’s free linux kernel development course covering the Common Clock Framework end to end.
