V4L2 Media Bus Types Explained
A lecture from EmbeddedPathashala’s free Linux kernel development course — understanding how camera sensors talk to SoCs
If you are following our free linux kernel development course, you already know that V4L2 sub-devices are connected through a graph of ports and endpoints described in the fwnode. But a graph link on its own does not tell the kernel how pixel data physically travels from a camera sensor to an SoC’s capture interface. That job belongs to the media bus type — the electrical and protocol-level contract between the two ends of an endpoint. In this lecture, part of our free embedded systems course, we explain every media bus type the V4L2 fwnode API understands, walk through the current kernel data structures, and build an original demo driver that parses and prints a sensor’s bus configuration.
What You Will Learn
- Why V4L2 needs a separate “media bus” abstraction on top of the fwnode graph
- Every media bus type supported by the current kernel: MIPI CSI-1, CCP2, Parallel, BT.656, MIPI CSI-2 (D-PHY and C-PHY), and DPI
- The current
struct v4l2_fwnode_endpointlayout and its per-bus configuration structures - How to read bus properties from a device tree endpoint node
- How to write and test an original driver that parses and reports bus configuration
Prerequisites
- Comfortable with platform drivers and probe()/remove() — covered earlier in this free linux development course
- Familiarity with the fwnode graph API (
fwnode_graph_get_next_endpoint(),fwnode_graph_parse_endpoint()) from the previous lecture in this series - A working cross-build or native build environment for a recent (6.x) Linux kernel tree
Why Media Buses Exist Separately From the Graph
The fwnode graph tells the kernel who is connected to whom — port 0 of the sensor connects to port 1 of the bridge. It says nothing about how the bits move between them. A camera sensor might push out parallel HSYNC/VSYNC-timed data over eight wires, or it might serialize the same pixels over one or two differential MIPI CSI-2 lanes. Both are valid “connections” in the graph, but the capture driver on the receiving end must configure completely different hardware blocks depending on which one it is.
This is exactly what the V4L2 fwnode API solves. On top of the generic fwnode_graph_parse_endpoint() call (which only fills in generic port/endpoint IDs), V4L2 layers v4l2_fwnode_endpoint_parse(), which additionally reads bus-specific device tree or ACPI properties and fills in a bus-type-specific structure describing lane counts, clock polarity, bus width, and similar electrical details.
Where Bus Parsing Fits in the Graph
The Media Bus Types Supported by V4L2
The kernel enumerates every supported bus in enum v4l2_mbus_type (declared in include/media/v4l2-mediabus.h). Each value corresponds to a real-world electrical interface used by camera and video sensors:
| Bus Type | Enum Value | Typical Use Case |
|---|---|---|
| Parallel | V4L2_MBUS_PARALLEL | Classic HSYNC/VSYNC/PCLK camera interfaces on low-cost or legacy sensors |
| BT.656 | V4L2_MBUS_BT656 | Embedded-sync parallel video (fewer pins, timing embedded in the data stream) |
| MIPI CSI-1 | V4L2_MBUS_CSI1 | Older MIPI Alliance serial camera interface, now rare in new designs |
| CCP2 (SMIA) | V4L2_MBUS_CCP2 | SMIA-defined Compact Camera Port 2, common in older mobile camera modules |
| MIPI CSI-2 (D-PHY) | V4L2_MBUS_CSI2_DPHY | The dominant interface on modern smartphone and embedded camera sensors |
| MIPI CSI-2 (C-PHY) | V4L2_MBUS_CSI2_CPHY | Higher-bandwidth alternative physical layer for MIPI CSI-2, used on newer high-resolution sensors |
| DPI | V4L2_MBUS_DPI | Display Parallel Interface, used for parallel RGB display/video panels |
Parallel and BT.656 — the Simplest Buses
A parallel bus carries one pixel per clock on a set of data lines, framed by separate HSYNC and VSYNC signals. It is easy to debug on a logic analyzer and cheap to implement, which is why it still appears on low-end and legacy sensors. BT.656 is a variant that embeds the sync information directly into the data stream instead of using dedicated sync pins, trading a couple of extra wires for simpler routing.
MIPI CSI-1 and CCP2 — the Predecessors
Before MIPI CSI-2 became the industry default, MIPI CSI-1 and the SMIA-defined CCP2 handled serial camera data over one or two lanes. The kernel still models both with the same data structure because they share an almost identical electrical description — a single data lane, a clock lane, and their polarities.
MIPI CSI-2 — D-PHY and C-PHY
MIPI CSI-2 is the bus you will encounter on nearly every modern embedded camera sensor. It transmits pixel data over one or more high-speed differential data lanes plus a clock lane, using a lightweight packet-based protocol. Two physical layers exist: D-PHY, the long-established differential signalling layer, and the newer C-PHY, which uses three-wire signalling to squeeze more bandwidth per pin. Since kernel 5.x, these are represented as two distinct v4l2_mbus_type values rather than one combined CSI-2 type, because their electrical properties genuinely differ.
Current Kernel Data Structures
The layout of struct v4l2_fwnode_endpoint has evolved across kernel releases — older kernels embedded bus-specific structs directly under v4l2_fwnode_bus_* names, while current mainline kernels use the shared media-bus-config structures so the same layout can be reused outside fwnode parsing as well. Here is the structure as it stands on a recent 6.x kernel, declared in include/media/v4l2-fwnode.h:
struct v4l2_fwnode_endpoint {
struct fwnode_endpoint base;
enum v4l2_mbus_type bus_type;
struct {
struct v4l2_mbus_config_parallel parallel;
struct v4l2_mbus_config_mipi_csi1 mipi_csi1;
struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
} bus;
u64 *link_frequencies;
unsigned int nr_of_link_frequencies;
};
The bus member is a plain struct containing every bus variant side by side (earlier kernels used a union here) — the kernel only fills in the member matching bus_type, and driver code must only read that member. The three per-bus structures look like this:
/* Parallel and BT.656 buses */
struct v4l2_mbus_config_parallel {
unsigned int flags;
unsigned char bus_width;
unsigned char data_shift;
};
/* MIPI CSI-1 and CCP2 buses */
struct v4l2_mbus_config_mipi_csi1 {
unsigned char clock_inv:1;
unsigned char strobe:1;
bool lane_polarity[2];
unsigned char data_lane;
unsigned char clock_lane;
};
/* MIPI CSI-2 bus (D-PHY or C-PHY) */
struct v4l2_mbus_config_mipi_csi2 {
unsigned int flags;
unsigned char data_lanes[4];
unsigned char clock_lane;
unsigned char num_data_lanes;
bool lane_polarities[5];
};
Note the flags field, common to parallel and CSI-2 configurations. It carries generic V4L2_MBUS_* flags such as clock polarity, HSYNC/VSYNC active level for parallel buses, or continuous-clock mode for CSI-2. You will use these flags constantly when writing bridge or capture drivers that must program hardware timing registers to match the sensor.
Reading Bus Properties From Device Tree
Bus configuration is described in the endpoint sub-node of the device tree, using standard media-bindings properties. A CSI-2 sensor endpoint typically looks like this:
&i2c1 {
ep_camsensor: camera-sensor@10 {
compatible = "ep,camsensor";
reg = <0x10>;
port {
ep_camsensor_out: endpoint {
remote-endpoint = <&ep_bridge_in>;
data-lanes = <1 2>;
clock-lanes = <0>;
clock-noncontinuous;
link-frequencies = /bits/ 64 <450000000 600000000>;
};
};
};
};
For a parallel-bus sensor, the same endpoint would instead carry properties such as bus-width, hsync-active, vsync-active, and pclk-sample. v4l2_fwnode_endpoint_parse() looks at the properties present and, when bus_type is left as V4L2_MBUS_UNKNOWN, infers the correct bus type automatically from which properties it finds.
Building an Original Bus-Parsing Demo Driver
Let’s write ep_busparser, a small platform driver that looks up its own endpoint, parses the bus configuration, and logs a human-readable summary — a practical exercise for this free linux kernel development course.
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <media/v4l2-fwnode.h>
static void ep_busparser_report(struct device *dev,
struct v4l2_fwnode_endpoint *vep)
{
switch (vep->bus_type) {
case V4L2_MBUS_PARALLEL:
case V4L2_MBUS_BT656:
dev_info(dev, "parallel/bt656 bus: width=%u shift=%u flags=0x%x\n",
vep->bus.parallel.bus_width,
vep->bus.parallel.data_shift,
vep->bus.parallel.flags);
break;
case V4L2_MBUS_CSI1:
case V4L2_MBUS_CCP2:
dev_info(dev, "csi1/ccp2 bus: data_lane=%u clock_lane=%u\n",
vep->bus.mipi_csi1.data_lane,
vep->bus.mipi_csi1.clock_lane);
break;
case V4L2_MBUS_CSI2_DPHY:
case V4L2_MBUS_CSI2_CPHY:
dev_info(dev, "csi2 bus: num_data_lanes=%u clock_lane=%u flags=0x%x\n",
vep->bus.mipi_csi2.num_data_lanes,
vep->bus.mipi_csi2.clock_lane,
vep->bus.mipi_csi2.flags);
break;
default:
dev_info(dev, "unknown or unsupported bus type: %d\n", vep->bus_type);
}
}
static int ep_busparser_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, "no endpoint found in fwnode\n");
return -ENODEV;
}
ret = v4l2_fwnode_endpoint_parse(ep, &vep);
fwnode_handle_put(ep);
if (ret) {
dev_err(dev, "failed to parse endpoint: %d\n", ret);
return ret;
}
ep_busparser_report(dev, &vep);
return 0;
}
static const struct of_device_id ep_busparser_of_match[] = {
{ .compatible = "ep,busparser" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_busparser_of_match);
static struct platform_driver ep_busparser_driver = {
.probe = ep_busparser_probe,
.driver = {
.name = "ep_busparser",
.of_match_table = ep_busparser_of_match,
},
};
module_platform_driver(ep_busparser_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("EmbeddedPathashala demo: V4L2 media bus type parser");
Building and Loading the Demo
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo insmod ep_busparser.ko
dmesg | tail -n 5
With a matching device tree node using compatible = "ep,busparser" and a CSI-2 endpoint like the one shown earlier, the expected kernel log looks like this:
[ 12.441022] ep_busparser ep_busparser@0: csi2 bus: num_data_lanes=2 clock_lane=0 flags=0x0
[ 12.441130] ep_busparser: probe of ep_busparser@0 succeeded
Try changing the device tree endpoint to use bus-width/hsync-active properties instead of data-lanes, rebuild the DTB, and re-insert the module — you should see the driver automatically detect and report the parallel bus branch instead, with no code changes required.
Common Mistakes and Troubleshooting
- Forgetting to initialize
bus_type: if you want the parser to auto-detect the bus, you must explicitly setvep.bus_type = V4L2_MBUS_UNKNOWNbefore callingv4l2_fwnode_endpoint_parse()— leaving it uninitialized reads garbage stack memory. - Reading the wrong union/struct member: always check
bus_typebefore touchingvep.bus.parallel,vep.bus.mipi_csi1, orvep.bus.mipi_csi2— reading the wrong member reads unrelated fields. - Missing
fwnode_handle_put(): every endpoint returned byfwnode_graph_get_next_endpoint()holds a reference that must be dropped, or you leak fwnode references on every probe. - Confusing CSI-2 D-PHY and C-PHY nodes: mixing up the two in a device tree that was written for the other physical layer produces a driver that probes successfully but never captures a usable image.
Best Practices
- Prefer
V4L2_MBUS_UNKNOWNplus auto-detection over hardcoding a bus type, unless your driver genuinely supports only one bus. - Validate
nr_of_link_frequenciesbefore indexing intolink_frequencies— it is legitimately zero on many boards. - Keep bus-parsing logic in probe(), not in interrupt or streaming paths — it only needs to run once at bind time.
- Log the parsed bus configuration at probe time (as our demo does) — it saves hours of debugging device tree mismatches later.
Summary and Key Takeaways
- The fwnode graph describes connections; the V4L2 fwnode API describes how data physically moves across those connections.
v4l2_fwnode_endpoint_parse()fills a bus-type-specific structure insidestruct v4l2_fwnode_endpointbased on the properties present in the endpoint’s device tree node.- Seven bus types are currently modelled: Parallel, BT.656, CSI-1, CCP2, CSI-2 D-PHY, CSI-2 C-PHY, and DPI.
- Always check
bus_typebefore reading the corresponding member of thebusstruct, and always release the endpoint reference you obtained.
Understanding media bus types is the missing link between “the graph says these two devices are connected” and “the driver actually knows how to configure hardware to receive pixels correctly.” With this piece in place, you now have the full picture of how V4L2 async binding, the fwnode graph, and bus configuration work together — everything you need to read and write real camera sub-device drivers, right here in our free linux device drivers course.
Frequently Asked Questions
What is the difference between the V4L2 fwnode graph and a media bus type?
The fwnode graph describes which endpoints are wired together (topology). The media bus type describes the electrical/protocol details of that specific wire — parallel, MIPI CSI-2, etc.
Which media bus type should I use for a new camera sensor design?
For nearly all new embedded and mobile camera designs, MIPI CSI-2 (D-PHY, or C-PHY for higher bandwidth) is the standard choice. Parallel and BT.656 remain relevant mainly for legacy or ultra-low-cost sensors.
Does v4l2_fwnode_endpoint_parse() work with ACPI as well as device tree?
Yes. Because it operates on the generic fwnode_handle abstraction, the same function call works whether the firmware description comes from device tree or ACPI.
What happened to struct v4l2_fwnode_bus_parallel and v4l2_fwnode_bus_mipi_csi2?
Current mainline kernels renamed these to v4l2_mbus_config_parallel, v4l2_mbus_config_mipi_csi1, and v4l2_mbus_config_mipi_csi2, and moved the bus configuration into a plain struct instead of a union, so the same layout can be shared with non-fwnode media bus configuration code.
Why is CSI-2 split into D-PHY and C-PHY as separate bus types?
D-PHY and C-PHY are genuinely different physical signalling layers with different lane counts, timing, and electrical parameters, even though both carry the CSI-2 packet protocol. The kernel models them as distinct v4l2_mbus_type values so drivers can configure the correct PHY hardware.
What does the flags field in v4l2_mbus_config_parallel actually control?
It encodes generic V4L2_MBUS_* bit flags such as HSYNC/VSYNC active-high or active-low, pixel clock sampling edge, and master/slave mode — properties a capture driver needs to correctly configure its timing block.
Can one sensor driver support more than one bus type?
Yes — some sensors expose both a parallel and a CSI-2 output selectable via device tree. Such drivers typically parse bus_type at probe time and branch their register configuration accordingly, similar to the ep_busparser example in this lecture.
Is this lecture part of a free course?
Yes — this lecture is part of EmbeddedPathashala’s free linux kernel development course, which also covers device drivers, DMA, ALSA SoC, the Common Clock Framework, and the full V4L2 stack from scratch.
Continue the Free Linux Kernel Development Course
Next, we move deeper into the Linux Media Controller framework — how entities, pads, and links tie your sub-devices into a complete capture pipeline.
Browse All Lectures Join the Free Course