What is Linux Device Tree Phandle Parsing in Linux Kernel

Linux Device Tree Phandle Parsing-Free Linux Device Drivers Course

Free Linux Kernel Development Course — Common Clock Framework, Lecture 6

📘 Chapter 4: Common Clock Framework
⏱ 20 min read
🐧 Kernel 6.x Verified

Free Linux Kernel Development Course Keywords

linux device tree phandle parsing
of_parse_phandle_with_args
free linux device drivers course
free embedded linux course
of_phandle_args struct

In the last lecture we saw how a consumer node uses clocks and clock-names to describe which clock it needs. But how does the kernel actually turn clocks = <&osc 0>; into a real clock pointer? The answer is a single, generic mechanism used all across the kernel: linux device tree phandle parsing via of_parse_phandle_with_args(). This lecture in our free Linux kernel development course walks through that API in detail, then generalizes it beyond clocks to show why the same function also drives GPIO, DMA, and interrupt bindings.

What You Will Learn

The clk_get() call chain down to phandle parsing
of_parse_phandle_with_args() prototype and parameters
struct of_phandle_args fields
How the same API generalizes to GPIO/DMA bindings
Original ep_phandle_parse_demo driver

Prerequisites

This lecture builds directly on Lecture 5 (linux clock consumer device tree binding) and Lecture 4 (clock provider DT exposure). You should be comfortable reading basic device tree phandle syntax before continuing.

From devm_clk_get() to Phandle Parsing

When a driver calls devm_clk_get(dev, "core"), the kernel eventually reaches of_clk_get_by_name(), which calls an internal helper that does two things: parse the phandle-plus-arguments list, then hand the result to the registered provider’s lookup callback.

Clock Lookup Call Chain
devm_clk_get() / clk_get()
of_clk_get_by_name()
__of_clk_get()
of_parse_phandle_with_args()
of_clk_get_from_provider()

The important insight here is that __of_clk_get() itself contains almost no clock-specific logic. Its entire job is to call the generic parser with two clock-specific string constants — "clocks" as the list property name, and "#clock-cells" as the cell-count property name — and then pass the parsed result to a provider-lookup function.

Understanding the of_parse_phandle_with_args() API

Here is the function’s prototype, unchanged in spirit from earlier kernels through 6.x:

int of_parse_phandle_with_args(const struct device_node *np,
                                const char *list_name,
                                const char *cells_name,
                                int index,
                                struct of_phandle_args *out_args);
Parameter Meaning
np Device node containing the list property (the consumer node)
list_name Name of the property holding the phandle list, e.g. "clocks"
cells_name Name of the cell-count property on the provider, e.g. "#clock-cells"
index Which entry in the list to parse (0 for the first)
out_args Output parameter filled with the resolved node and its argument cells

It returns 0 on success and a negative errno on failure, filling out_args only on the success path.

struct of_phandle_args

#define MAX_PHANDLE_ARGS 16

struct of_phandle_args {
    struct device_node *np;
    int args_count;
    uint32_t args[MAX_PHANDLE_ARGS];
};
np — resolved device node the phandle pointed to (the provider)
args_count — how many cells followed the phandle in this entry
args[] — the actual cell values, e.g. the output index for a clock

Why This API Is Not Just for Clocks

Because of_parse_phandle_with_args() only takes property-name strings as parameters, it works for any phandle-list binding, not only clocks. The same generic function backs GPIO descriptors ("gpios" / "#gpio-cells"), DMA channel requests ("dmas" / "#dma-cells"), and interrupt parents. Consider a device tree with two independent phandle-list bindings, one using single-cell GPIO-style arguments and another using a custom one-cell scheme:

ep_gpio_provider: gpio-controller@0 {
    compatible = "ep,gpio-demo";
    #gpio-cells = <2>;
    gpio-controller;
};

ep_list_provider: list-provider@0 {
    compatible = "ep,list-demo";
    #list-cells = <1>;
};

ep_consumer: consumer@0 {
    compatible = "ep,phandle-parse-demo";
    ep-list = <&ep_gpio_provider 1 2 &ep_list_provider 3>;
};

Here, index 0 of ep-list resolves to ep_gpio_provider with two argument cells (1, 2), and index 1 resolves to ep_list_provider with one argument cell (3) — even though both entries sit in the same property, because each provider declares its own cell count via its own #*-cells property.

Original Demo: ep_phandle_parse_demo

Let’s write a small platform driver that calls of_parse_phandle_with_args() directly, to see the generic API in action outside the clock subsystem.

Device Tree Node

ep_target: target-provider@0 {
    compatible = "ep,target-demo";
    #ep-cells = <2>;
};

ep_parse_demo: phandle-parse-demo@0 {
    compatible = "ep,phandle-parse-demo";
    ep-refs = <&ep_target 7 9>;
};

Driver Code

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>

static int ep_phandle_parse_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct of_phandle_args spec;
    int ret, i;

    ret = of_parse_phandle_with_args(np, "ep-refs", "#ep-cells",
                                      0, &spec);
    if (ret) {
        dev_err(&pdev->dev, "failed to parse ep-refs: %d\n", ret);
        return ret;
    }

    dev_info(&pdev->dev, "ep_phandle_parse_demo: resolved node = %pOF\n", spec.np);
    dev_info(&pdev->dev, "ep_phandle_parse_demo: args_count = %d\n", spec.args_count);

    for (i = 0; i < spec.args_count; i++)
        dev_info(&pdev->dev, "ep_phandle_parse_demo: args[%d] = %u\n", i, spec.args[i]);

    of_node_put(spec.np);
    return 0;
}

static const struct of_device_id ep_phandle_parse_of_match[] = {
    { .compatible = "ep,phandle-parse-demo" },
    {}
};
MODULE_DEVICE_TABLE(of, ep_phandle_parse_of_match);

static struct platform_driver ep_phandle_parse_driver = {
    .probe = ep_phandle_parse_probe,
    .driver = {
        .name = "ep_phandle_parse_demo",
        .of_match_table = ep_phandle_parse_of_match,
    },
};
module_platform_driver(ep_phandle_parse_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala generic phandle parsing demo");

Build and Run

make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo insmod ep_phandle_parse_demo.ko
dmesg | tail -5

Expected dmesg Output

[  55.221007] ep_phandle_parse_demo: resolved node = /soc/target-provider@0
[  55.221012] ep_phandle_parse_demo: args_count = 2
[  55.221015] ep_phandle_parse_demo: args[0] = 7
[  55.221017] ep_phandle_parse_demo: args[1] = 9

This confirms that of_parse_phandle_with_args() correctly resolved the phandle to the ep_target node and extracted both argument cells (7 and 9) exactly as written in the DT — the same mechanism that resolves a clock specifier like &osc 0 under the hood.

of_parse_phandle_with_args() vs Simpler Alternatives

API Use case
of_parse_phandle() Simple single-phandle properties with no argument cells
of_parse_phandle_with_args() Phandle lists where each entry carries a variable number of argument cells (clocks, GPIOs, DMA, interrupts)
of_parse_phandle_with_fixed_args() Same idea, but when every entry is known to have a fixed cell count regardless of a #*-cells property

Common Mistakes

  • Forgetting of_node_put(spec.np) after using the resolved node, leaking a device node reference.
  • Assuming args_count is always fixed instead of reading the provider’s own #*-cells property.
  • Passing the wrong index when a property holds multiple phandle entries, silently parsing the wrong one.

Best Practices

  • Always check the return value before touching out_args — it is only valid on success.
  • Always release the resolved node with of_node_put() once you’re done with it.
  • Reuse this generic API instead of writing custom phandle-list parsing when defining a new binding.

Summary and Key Takeaways

  • of_parse_phandle_with_args() is the single generic engine behind clock, GPIO, DMA, and interrupt phandle-list bindings.
  • It resolves a phandle entry to a struct of_phandle_args containing the target node and its argument cells.
  • Clock-specific code like __of_clk_get() is just a thin wrapper supplying "clocks" and "#clock-cells" as parameters.
  • Understanding this API means you understand the DT-parsing backbone of nearly every subsystem, not just clocks.

Conclusion

With linux device tree phandle parsing now demystified, you can see exactly how a simple line like clocks = <&osc 0>; becomes a live struct clk pointer inside your driver. This same knowledge transfers directly to GPIO, DMA, and interrupt bindings elsewhere in the kernel — making it one of the most reusable pieces of device tree knowledge in this free Linux kernel development course. In the next lecture, we return to the Common Clock Framework itself and look at consumer-side rate and parent APIs.

Frequently Asked Questions

Does of_parse_phandle_with_args() work for non-clock bindings?

Yes, it is fully generic — it is used internally by GPIO, DMA, interrupt, and many other phandle-list bindings, not just clocks.

What does args_count represent?

It is the number of argument cells that followed the phandle for that specific list entry, as declared by the provider’s own #*-cells property.

Why do I need to call of_node_put() on spec.np?

of_parse_phandle_with_args() takes a reference on the resolved device node; you must release it with of_node_put() to avoid a reference leak.

What happens if the index parameter is out of range?

The function returns a negative error code and does not populate out_args.

Is of_parse_phandle_with_fixed_args() a replacement for this API?

No, it solves a narrower case where every entry has the same fixed cell count regardless of any #*-cells property, and is used far less often.

Can MAX_PHANDLE_ARGS ever be exceeded?

The kernel enforces this constant as an upper bound on argument cells per entry; a binding needing more than this is considered a design smell.

Continue This Free Linux Kernel Development Course

You now understand the generic engine behind every phandle-list binding in the kernel.

 

Leave a Reply

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