V4L2 Fwnode Graph API-Free Linux Device Drivers Course

V4L2 Fwnode Graph API

Free Linux Kernel Development Course — Understanding the V4L2 Fwnode Endpoint API for Camera and Media Drivers

In the previous lecture of this free linux kernel development course we learned why probing camera sensor drivers on a device-tree based board cannot rely on plain sequential probing, and how the V4L2 async framework solves that problem using a notifier plus graph binding. In this lecture we go one level deeper and study the actual v4l2 fwnode endpoint api that the kernel uses to read that graph information out of the device tree (or ACPI) and turn it into something a driver can act on.

This is one of the most practical topics in any free linux device drivers course, because almost every modern camera, display bridge, or audio codec driver in the kernel tree uses this exact API to discover how it is wired to its neighbours on the board.

What You Will Learn

fwnode_handle abstraction struct fwnode_endpoint struct v4l2_fwnode_endpoint v4l2_mbus_type bus types v4l2_fwnode_endpoint_alloc_parse() fwnode_graph_* helper functions writing an original bridge driver

Prerequisites

Before this lecture, you should already be comfortable with the graph-binding concept and the V4L2 async notifier covered earlier in this free embedded linux course. You should also know basic device tree syntax (nodes, phandles, port/endpoint sub-nodes) and have a cross-compiled kernel tree available for building an out-of-tree module.

Why a Generic Fwnode Was Needed

Before the fwnode abstraction existed, every subsystem that wanted to walk a device graph had two separate code paths: one for device tree (built around struct device_node) and one for ACPI (built around struct acpi_device). That duplication meant every bus-mapping bug had to be fixed twice, and any board using ACPI simply could not use the of_graph helper functions at all.

The kernel solves this by giving both struct device_node and struct acpi_device a common embedded member of type struct fwnode_handle. Because both firmware description formats “inherit” from the same handle type, a single generic graph walker can operate on either one without caring which firmware format the board actually uses underneath.

Fwnode Abstraction Layer

device_node (Device Tree) –\ >— fwnode_handle — fwnode_graph_* API acpi_device (ACPI) –/ | v struct fwnode_endpoint (port, id, local_fwnode)

struct fwnode_endpoint: The Generic Endpoint

Once a firmware node is exposed as a fwnode_handle, an individual graph endpoint (the endpoint sub-node under a port node) can be represented generically with struct fwnode_endpoint. This structure replaced the older, device-tree-only struct of_endpoint.

struct fwnode_endpoint {
    unsigned int port;
    unsigned int id;
    const struct fwnode_handle *local_fwnode;
};
  • port — the numeric port index, e.g. 0 for port@0
  • id — the endpoint index inside that port, e.g. 1 for endpoint@1
  • local_fwnode — pointer back to the firmware node this endpoint belongs to

struct v4l2_fwnode_endpoint and the V4L2 Fwnode Endpoint API

The media subsystem builds a V4L2-specific structure on top of the generic one. This is the structure every camera or bridge driver actually works with when it calls the v4l2 fwnode endpoint api:

struct v4l2_fwnode_endpoint {
    struct fwnode_endpoint base;

    /* filled in by v4l2_fwnode_endpoint_parse() /
       v4l2_fwnode_endpoint_alloc_parse() */
    enum v4l2_mbus_type bus_type;
    union {
        struct v4l2_fwnode_bus_parallel parallel;
        struct v4l2_fwnode_bus_mipi_csi1 mipi_csi1;
        struct v4l2_fwnode_bus_mipi_csi2 mipi_csi2;
    } bus;

    u64 *link_frequencies;
    unsigned int nr_of_link_frequencies;
};

base holds the generic port/id information described above. bus_type tells the driver which physical media bus this endpoint uses, and bus is a union holding the bus-specific timing/lane properties for whichever type was found. link_frequencies is a dynamically sized array of pixel-link clock rates supported on that link, and nr_of_link_frequencies tells you how many entries it holds.

The v4l2_mbus_type Enum

enum v4l2_mbus_type {
    V4L2_MBUS_UNKNOWN,
    V4L2_MBUS_PARALLEL,
    V4L2_MBUS_BT656,
    V4L2_MBUS_CSI1,
    V4L2_MBUS_CCP2,
    V4L2_MBUS_CSI2_DPHY,
    V4L2_MBUS_CSI2_CPHY,
    V4L2_MBUS_DPI,
};

On current kernels, before calling the parser you set vep.bus_type yourself. If your driver supports only one bus type, set it explicitly and the parser will return -ENXIO if the device tree describes a different bus. If your driver can adapt to more than one bus, set V4L2_MBUS_UNKNOWN and let the parser read the bus-type property from firmware and fill it in for you.

Comparing the Two Parsing Functions

FunctionVariable-size data (link-frequencies)AllocationTypical use
v4l2_fwnode_endpoint_parse()Not parsedCaller-owned struct on stackSimple endpoints, fixed bus config
v4l2_fwnode_endpoint_alloc_parse()Parsed and allocatedFreed with v4l2_fwnode_endpoint_free()Sensors that advertise link frequency lists
/* Current mainline kernel prototypes, include/media/v4l2-fwnode.h */
int v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode,
                                struct v4l2_fwnode_endpoint *vep);

int v4l2_fwnode_endpoint_alloc_parse(struct fwnode_handle *fwnode,
                                      struct v4l2_fwnode_endpoint *vep);

void v4l2_fwnode_endpoint_free(struct v4l2_fwnode_endpoint *vep);

Walking the Graph: fwnode_graph_* Helpers

Parsing a single endpoint is only half the job — a bridge driver first has to discover the endpoint fwnode itself by walking the graph. The generic fwnode_graph_* API (successor to the device-tree-only of_graph_* calls) gives you that:

struct fwnode_handle *fwnode_graph_get_next_endpoint(
        const struct fwnode_handle *fwnode,
        struct fwnode_handle *prev);

struct fwnode_handle *fwnode_graph_get_remote_endpoint(
        const struct fwnode_handle *fwnode);

struct fwnode_handle *fwnode_graph_get_remote_port_parent(
        const struct fwnode_handle *fwnode);

fwnode_graph_get_next_endpoint() iterates every endpoint under a device’s ports, fwnode_graph_get_remote_endpoint() follows a remote-endpoint phandle to the endpoint on the other side of the link, and fwnode_graph_get_remote_port_parent() jumps straight to the device node that owns the remote endpoint — exactly what you need to match an async sub-device against a discovered fwnode.

An Original Device Tree Binding Example

The following is a fresh, original binding between an imaginary bridge device and an imaginary sensor, written to illustrate the concept without reusing any book example. Do not copy vendor names from other tutorials — always match the compatible string to your real hardware:

&i2c2 {
    #address-cells = <1>;
    #size-cells = <0>;

    ep_visionsensor@36 {
        compatible = "epboard,visionsensor";
        reg = <0x36>;

        port {
            visionsensor_out: endpoint {
                remote-endpoint = <&mediabridge_in>;
                bus-type = <4>; /* CSI2_DPHY */
                data-lanes = <1 2>;
                link-frequencies = /bits/ 64 <300000000 400000000>;
            };
        };
    };
};

&csi_bridge {
    port {
        mediabridge_in: endpoint {
            remote-endpoint = <&visionsensor_out>;
            bus-type = <4>;
            data-lanes = <1 2>;
        };
    };
};

Original Demo: Parsing an Endpoint in a Bridge Driver

The following minimal, original module (prefixed ep_) shows the full workflow: get the port node, walk to its endpoint, parse it with the V4L2 fwnode endpoint API, and print what was found. It targets a current mainline kernel.

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <media/v4l2-fwnode.h>

static int ep_graphdemo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct fwnode_handle *ep;
    struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_UNKNOWN };
    int ret;

    ep = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
    if (!ep) {
        dev_err(dev, "ep_graphdemo: no endpoint found under this node\n");
        return -ENODEV;
    }

    ret = v4l2_fwnode_endpoint_alloc_parse(ep, &vep);
    fwnode_handle_put(ep);
    if (ret) {
        dev_err(dev, "ep_graphdemo: endpoint parse failed (%d)\n", ret);
        return ret;
    }

    dev_info(dev, "ep_graphdemo: port=%u id=%u bus_type=%d nr_link_freq=%u\n",
             vep.base.port, vep.base.id, vep.bus_type,
             vep.nr_of_link_frequencies);

    v4l2_fwnode_endpoint_free(&vep);
    return 0;
}

static const struct of_device_id ep_graphdemo_of_match[] = {
    { .compatible = "epboard,graphdemo" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_graphdemo_of_match);

static struct platform_driver ep_graphdemo_driver = {
    .probe = ep_graphdemo_probe,
    .driver = {
        .name = "ep_graphdemo",
        .of_match_table = ep_graphdemo_of_match,
    },
};
module_platform_driver(ep_graphdemo_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala V4L2 fwnode graph parsing demo");

Building and Loading the Demo

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

Expected Output

[  102.481223] ep_graphdemo: port=0 id=0 bus_type=4 nr_link_freq=2

bus_type=4 corresponds to V4L2_MBUS_CSI2_DPHY in the enum shown earlier, and nr_link_freq=2 confirms the two link-frequencies entries from our device tree snippet were parsed correctly.

Common Mistakes

  • Forgetting v4l2_fwnode_endpoint_free() after alloc_parse(), leaking the link-frequencies array
  • Leaving bus_type uninitialized instead of explicitly setting V4L2_MBUS_UNKNOWN
  • Not calling fwnode_handle_put() on the endpoint returned by fwnode_graph_get_next_endpoint()
  • Assuming of_graph_* functions still work on ACPI-described boards
  • Mismatching the bus-type property value between the two ends of a remote-endpoint link

Best Practices

Always let the parser validate against an explicit expected bus type when your driver only supports one bus — this turns a silent misconfiguration into a clear -ENXIO at probe time. Prefer v4l2_fwnode_endpoint_alloc_parse() over the plain _parse() variant whenever your binding includes link-frequencies, and always pair it with v4l2_fwnode_endpoint_free(). From a security and robustness standpoint, treat every value read from firmware as untrusted input — bounds-check nr_of_link_frequencies before indexing into link_frequencies in your own code.

Real-World Use Cases

This exact pattern is what lets a single generic bridge driver (an ISP, a CSI-2 receiver, a display controller) support many different sensor or panel modules without code changes — the board integrator only edits the device tree. It is the same mechanism used by mainline drivers for MIPI CSI-2 camera sensors, HDMI bridges, and DSI display panels across many SoC families.

Summary and Key Takeaways

The v4l2 fwnode endpoint api gives every V4L2 driver a single, firmware-format-independent way to discover how it is wired to neighbouring devices on the board. struct fwnode_handle unifies device tree and ACPI, struct fwnode_endpoint abstracts one graph endpoint, and struct v4l2_fwnode_endpoint layers V4L2-specific bus and link-frequency data on top. The fwnode_graph_* functions walk the graph, while v4l2_fwnode_endpoint_parse() and v4l2_fwnode_endpoint_alloc_parse() turn a discovered endpoint into data your driver can act on. Mastering this API is essential groundwork for the media controller topics coming up next in this free linux development course.

Frequently Asked Questions

What is the difference between fwnode_handle and device_node?

device_node is the device-tree-specific representation of a firmware node. fwnode_handle is a generic wrapper embedded inside both device_node and acpi_device, letting the same graph-walking code work on either firmware format.

When should I use v4l2_fwnode_endpoint_alloc_parse() instead of v4l2_fwnode_endpoint_parse()?

Use the alloc_parse() variant whenever the endpoint binding may contain the variable-length link-frequencies property. It allocates memory for that array and must be paired with v4l2_fwnode_endpoint_free().

Does the v4l2 fwnode endpoint api work with ACPI-based boards?

Yes. Because both device_node and acpi_device expose the common fwnode_handle member, the same fwnode_graph_* and v4l2_fwnode_endpoint_* calls work regardless of which firmware description format the board uses.

What does bus_type = V4L2_MBUS_UNKNOWN actually do?

It tells the parser to determine the bus type itself by reading the bus-type property from the endpoint’s firmware node, rather than requiring the driver to already know which bus is in use.

Why does my probe fail with -ENXIO?

-ENXIO means the bus type found in firmware does not match the bus_type you set on the vep structure before calling the parser. Check that your device tree bus-type property matches what your driver expects.

Is this API only used for camera sensors?

No. Any media-related component that needs to describe a physical link to a neighbouring device — camera sensors, CSI-2 bridges, HDMI/DSI display bridges — uses the same graph and fwnode endpoint API.

Where can I practice this for free?

This lecture is part of EmbeddedPathashala’s free linux kernel development course, which also covers the free embedded systems course and free linux device drivers course tracks end to end.

Continue the Free Linux Kernel Development Course

Next up: the Linux Media Controller framework and how it ties these graph-bound sub-devices into a single capture pipeline.

Next Lecture Browse Full Course Index

Leave a Reply

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