Keywords covered in this lecture
clk_ops per clock chip
CLK_SET_RATE_PARENT
parent clock chaining
free linux kernel development course
free linux device drivers course
The embedding pattern from the previous lecture recovers your private struct inside
any clk_ops callback, but it left one question open: how do you actually get from
there to the shared driver data, and how do you avoid repeating the same clock names
and ops in every probe call? This lecture introduces the linux static clock
metadata pattern — keeping clock names, parent relationships, and ops
in one constant table separate from the per-device private data, and chaining several
related clocks together with parent_index and CLK_SET_RATE_PARENT.
What You Will Learn
- Reaching shared driver data from inside any clk_ops callback
- Why clock metadata belongs in a static const table, not per-device memory
- Chaining clocks together with parent_index and CLK_SET_RATE_PARENT
- Allowing device tree names to override built-in default clock names
- Registering a multi-clock chip driver in a single probe loop
- Reusing the onecell decode callback from an earlier lecture
Prerequisites
- Embedding clk_hw in driver structs and container_of (previous lecture)
- Dynamic onecell clock allocation pattern
- CCF clock decoding callbacks (of_clk_hw_onecell_get)
- Basic struct clk_init_data fields: name, ops, parent_names, flags
Reaching Shared Driver Data From Any Callback
The embedding pattern gives you a wrapper struct per clock line, and that wrapper can
itself hold a back-pointer to a bigger, driver-wide context struct. That means any
clk_ops callback can recover both the individual clock’s own state and the shared state
(register base, lock, parent clock) in exactly two steps:
static unsigned long ep_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ep_clk_hw *eclk = to_ep_clk(hw);
struct ep_driver_data *drvdata = eclk->drvdata;
/* eclk holds this clock's own metadata; drvdata holds shared state */
return parent_rate;
}
Why Clock Metadata Belongs in a Static Table
A clock chip’s identity — how many outputs it has, what each output is named by
default, which output feeds which other output, and which ops apply to each — never
changes between two boards using the same chip. Only the private, per-device state
(register base, I2C client, GPIO lines) changes from one board to the next. Mixing the
two together means duplicating the same metadata inside every dynamically allocated
instance, so it is better kept in one static const table shared by every
instance of the driver.
/* ep_multiclk_meta.c - static, read-only clock metadata table */
#include <linux/clk-provider.h>
#define EP_CLK_REF 0
#define EP_CLK_OUT 1
#define EP_CLK_OUT_A 2
#define EP_CLK_OUT_B 3
#define EP_NUM_CLKS 4
struct ep_clk_meta {
const char *name;
int parent_index; /* -1 means "external reference clock" */
const struct clk_ops *ops;
};
static const struct clk_ops ep_ref_ops;
static const struct clk_ops ep_out_ops;
static const struct clk_ops ep_leaf_ops;
static const struct ep_clk_meta ep_clks[EP_NUM_CLKS] = {
[EP_CLK_REF] = {
.name = "ep_ref",
.parent_index = -1,
.ops = &ep_ref_ops,
},
[EP_CLK_OUT] = {
.name = "ep_out",
.parent_index = EP_CLK_REF,
.ops = &ep_out_ops,
},
[EP_CLK_OUT_A] = {
.name = "ep_out_a",
.parent_index = EP_CLK_OUT,
.ops = &ep_leaf_ops,
},
[EP_CLK_OUT_B] = {
.name = "ep_out_b",
.parent_index = EP_CLK_OUT,
.ops = &ep_leaf_ops,
},
};
Here ep_out_a and ep_out_b both derive from ep_out,
which itself derives from the external reference clock. This is exactly the kind of
internal clock tree that small multi-output clock generator chips expose in real
hardware.
Chaining Parents with CLK_SET_RATE_PARENT
When one internal clock line is really just a tap off another internal line (rather
than the external reference), two things need to happen during registration: the parent
name must point at the sibling clock’s own name instead of the external clock, and the
CLK_SET_RATE_PARENT flag must be set so that a rate change request on the
child is allowed to propagate up and change the shared parent’s rate too.
/* ep_multiclk_probe.c - dynamic per-device registration using the static table above */
struct ep_clk_hw {
struct clk_hw hw;
struct clk_init_data init;
struct ep_driver_data *drvdata;
};
struct ep_driver_data {
void __iomem *base;
struct clk *xclk;
struct clk_hw_onecell_data clk_hw_data;
struct ep_clk_hw hw[EP_NUM_CLKS];
};
static int ep_multiclk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ep_driver_data *drvdata;
const char *xclk_name;
int i, ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
drvdata->xclk = devm_clk_get(dev, NULL);
if (IS_ERR(drvdata->xclk))
return PTR_ERR(drvdata->xclk);
xclk_name = __clk_get_name(drvdata->xclk);
drvdata->clk_hw_data.hws = devm_kcalloc(dev, EP_NUM_CLKS,
sizeof(struct clk_hw *), GFP_KERNEL);
if (!drvdata->clk_hw_data.hws)
return -ENOMEM;
drvdata->clk_hw_data.num = EP_NUM_CLKS;
for (i = 0; i < EP_NUM_CLKS; i++) {
int parent_index = ep_clks[i].parent_index;
const char *dt_name;
if (of_property_read_string_index(dev->of_node, "clock-output-names",
i, &dt_name) == 0)
drvdata->hw[i].init.name = dt_name;
else
drvdata->hw[i].init.name = ep_clks[i].name;
drvdata->hw[i].init.ops = ep_clks[i].ops;
drvdata->hw[i].init.num_parents = 1;
drvdata->hw[i].init.flags = 0;
if (parent_index >= 0) {
drvdata->hw[i].init.parent_names = &drvdata->hw[parent_index].init.name;
drvdata->hw[i].init.flags |= CLK_SET_RATE_PARENT;
} else {
drvdata->hw[i].init.parent_names = &xclk_name;
}
drvdata->hw[i].drvdata = drvdata;
drvdata->hw[i].hw.init = &drvdata->hw[i].init;
ret = devm_clk_hw_register(dev, &drvdata->hw[i].hw);
if (ret)
return ret;
drvdata->clk_hw_data.hws[i] = &drvdata->hw[i].hw;
}
/* reusing the generic onecell decode callback from an earlier lecture */
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
&drvdata->clk_hw_data);
}
Notice the decode callback did not change
Even though this driver now has parent chaining and per-clock ops, the actual
lookup from a device tree index to a clk_hw pointer is still just index-into-array
work, so of_clk_hw_onecell_get() from an earlier lecture is still the
correct decode callback here. Parent chaining is a registration-time concern; decoding
is a lookup-time concern, and the two stay independent of each other.
Matching device tree node:
ep_clk_chip: clock-controller@4000 {
compatible = "ep,clk-multichip-demo";
reg = <0x4000 0x100>;
#clock-cells = <1>;
clocks = <&ep_osc_clk>;
clock-output-names = "ep_ref", "ep_out", "ep_out_a", "ep_out_b";
};
Build and Run
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo insmod ep_multiclk_provider.ko
cat /sys/kernel/debug/clk/clk_summary | grep ep_
Expected clk_summary excerpt, showing the parent-child tree taking shape:
ep_ref 1 1 0 24000000 0
ep_out 1 1 0 24000000 0
ep_out_a 1 1 0 24000000 0
ep_out_b 0 0 0 24000000 0
Static Metadata vs Fully Dynamic Fields
| Field | Belongs In | Reason |
|---|---|---|
| Default clock name | Static const table | Same for every board using this chip |
| clk_ops set | Static const table | Behavior is fixed by the hardware design |
| parent_index | Static const table | Internal wiring never changes per board |
| Register base address | Per-device drvdata | Differs across boards and instances |
| Actual DT-supplied clock name | Per-device drvdata | Board integrators may rename outputs |
Common Mistakes
- Duplicating the metadata table inside every allocated drvdata instance
- Forgetting CLK_SET_RATE_PARENT when chaining an internal parent
- Pointing parent_names at the metadata table’s name instead of the registered init.name
- Registering clocks out of dependency order so a child references an unset parent name
Best Practices
- Keep one static const metadata table per chip variant, indexed by clock role
- Let clock-output-names override the table’s default name, never the other way around
- Register parents before children in the same probe loop whenever possible
- Reuse an existing generic decode callback unless the lookup logic truly needs custom code
Summary and Key Takeaways
- A back-pointer inside your embedded clk_hw wrapper reaches shared driver data in two steps
- Clock names, ops, and parent wiring belong in a static const table, not per-device memory
- CLK_SET_RATE_PARENT lets a rate-change request propagate to an internal parent clock
- Decode callbacks stay independent of registration-time metadata and parent chaining
Frequently Asked Questions
Why separate metadata from drvdata at all?
Metadata describes the chip design and never changes across devices, while drvdata
holds runtime state unique to each instance. Keeping them apart avoids duplicating
read-only information every time a device probes.
What happens if CLK_SET_RATE_PARENT is left unset for a chained clock?
A rate change request on the child clock will be rejected or silently ignored if the
child itself cannot change rate, since the CCF will not know it is allowed to ask the
parent to change instead.
Can clock-output-names rename only some of the clocks?
Yes, since the code falls back to the metadata table’s default name for any index
where of_property_read_string_index() does not succeed.
Does parent_index = -1 have special meaning?
In this pattern it marks a clock whose parent is the external reference clock
obtained through devm_clk_get(), rather than another clock inside the same chip.
Do I need a new decode callback for a chained clock tree?
No. Decoding only maps a device tree index to a clk_hw pointer, so the same generic
onecell callback used for a flat clock list still works for a chained tree.
Suggested Images For This Lecture
- Diagram: static metadata table feeding a dynamic per-device registration loop
- Tree diagram: ep_ref to ep_out to ep_out_a/ep_out_b parent chain
- Screenshot: clk_summary showing the nested clock tree
Continue This Free Linux Kernel Development Course
Next, we start the “Providing Clock Ops” section and tour the six base clock types
the CCF gives you out of the box.
