Part of Embedded Pathashala’s free Linux kernel development course — learn how camera sensors and other V4L2 sub-devices are initialized and wired into the media framework on the latest Linux kernel.
If you are following our free linux kernel development course, you already know that a V4L2 sub-device (struct v4l2_subdev) represents an individual IP block — a camera sensor, an image signal processor, an HDMI receiver — inside a larger media pipeline. But a sub-device object doesn’t do anything useful until it has been correctly initialized and linked to the physical bus device it controls. This lecture covers exactly that: the v4l2 subdevice initialization APIs, how the kernel links a sub-device to its I2C or SPI client, and the top-level operations table that every sub-device driver builds on. This is a core topic for anyone taking a free embedded linux course or working through a free linux device drivers course on camera or media subsystems.
What You Will Learn
- Why a sub-device needs explicit initialization before it can be used
- The difference between the generic, I2C, and SPI sub-device init helpers
- How the kernel links a
v4l2_subdevand its underlying bus client together - How sub-device name mangling works and why it matters for debugging
- The structure of
struct v4l2_subdev_opsand its operation categories - A working, original I2C camera sub-device driver skeleton on the current kernel API
Prerequisites
This lecture builds directly on the sub-device concept covered earlier in this free linux device drivers course. You should already be comfortable with struct v4l2_device, struct video_device, and the general idea of a bridge driver talking to one or more sub-devices over I2C or SPI. Basic familiarity with the Linux I2C client API (struct i2c_client, probe/remove) is assumed.
Why Sub-devices Need Explicit Initialization
A struct v4l2_subdev is just a plain C structure sitting in memory until the kernel is told how to treat it. Initialization does three things: it clears and prepares internal fields so the object is in a known state, it attaches the operations table (v4l2_subdev_ops) that defines what the sub-device can do, and — for bus-attached sub-devices — it creates a two-way link between the sub-device object and the underlying bus client structure, so that either one can be reached starting from the other.
Most camera sensors, ISPs, and tuners sit behind an I2C or SPI bus, so the kernel provides ready-made helpers for those two cases on top of the generic initializer. Understanding all three, and when to use which, is the first practical skill in sub-device driver development.
The Three Initialization APIs
Every current kernel provides these three functions, declared in include/media/v4l2-subdev.h:
void v4l2_subdev_init(struct v4l2_subdev *sd,
const struct v4l2_subdev_ops *ops);
void v4l2_i2c_subdev_init(struct v4l2_subdev *sd,
struct i2c_client *client,
const struct v4l2_subdev_ops *ops);
void v4l2_spi_subdev_init(struct v4l2_subdev *sd,
struct spi_device *spi,
const struct v4l2_subdev_ops *ops);
v4l2_subdev_init() is the generic base case: it stores the operations pointer, initializes the internal list head used when the sub-device is attached to a v4l2_device, and sets a few default fields. It is used directly only for sub-devices that are not tied to a discoverable bus — for example, a purely software sub-device, or one whose bus handling the driver manages entirely on its own.
v4l2_i2c_subdev_init() and v4l2_spi_subdev_init() both call v4l2_subdev_init() internally and then add bus-specific bookkeeping on top. This is the pattern you will use for the vast majority of real camera sensor drivers, since nearly all of them are I2C-controlled.
How the I2C Variant Links Sub-device and Client
The important part of v4l2_i2c_subdev_init() is the two-way pointer relationship it sets up between the sub-device and the I2C client:
sd->flags |= V4L2_SUBDEV_FL_IS_I2C;
sd->owner = client->dev.driver->owner;
sd->dev = &client->dev;
v4l2_set_subdevdata(sd, client);
i2c_set_clientdata(client, sd);
After this call, given a pointer to the I2C client, you can always retrieve the sub-device with i2c_get_clientdata(client). Given a pointer to the sub-device, you can retrieve the I2C client with v4l2_get_subdevdata(sd). Since most real drivers wrap the sub-device inside a larger private structure, the container_of() macro is then used to walk back from the embedded v4l2_subdev to the enclosing driver structure:
struct ep_camsensor {
struct v4l2_subdev sd;
struct i2c_client *client;
/* sensor-specific fields */
};
static inline struct ep_camsensor *to_ep_camsensor(struct v4l2_subdev *sd)
{
return container_of(sd, struct ep_camsensor, sd);
}
This “point to one another” pattern is one of the most common idioms across the entire Linux driver model, not just V4L2 — you will see the same trick with platform devices, USB interfaces, and clock providers.
Sub-device Name Mangling
The bus-specific initializers also fill in the sub-device’s human-readable name, derived from the driver name and the bus address, so that two instances of the same sensor on different I2C buses or addresses never collide in logs or in media-ctl output. Conceptually, the name is built as "<driver_name> <adapter_id>-<i2c_address>" (address printed as a 4-digit hex value). This means if you probe two ep_camsensor chips on adapter 2 at addresses 0x36 and 0x3c, you will see two distinctly named entities such as ep_camsensor 2-0036 and ep_camsensor 2-003c in media-ctl -p output — extremely useful once you have more than one sensor on a board.
struct v4l2_subdev_ops: The Top-Level Operations Table
Once the sub-device object exists, it needs to advertise what it can actually do. That is the job of struct v4l2_subdev_ops, defined in include/media/v4l2-subdev.h. On the current kernel it looks like this (trimmed to the categories a typical camera sensor driver cares about):
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core;
const struct v4l2_subdev_tuner_ops *tuner;
const struct v4l2_subdev_audio_ops *audio;
const struct v4l2_subdev_video_ops *video;
const struct v4l2_subdev_vbi_ops *vbi;
const struct v4l2_subdev_ir_ops *ir;
const struct v4l2_subdev_sensor_ops *sensor;
const struct v4l2_subdev_pad_ops *pad;
};
Each pointer is a category of related callbacks. A driver only fills in the categories that are relevant to it and leaves the rest NULL:
| Category | Purpose |
|---|---|
core | Generic, category-independent callbacks: logging, firmware loading, custom ioctls, power management, register debug access |
video | Streaming control and format negotiation for devices opened in classic “video” mode (no media-controller pad routing) |
pad | Format/routing negotiation through media-controller pads — the modern replacement path for most new drivers |
sensor | Camera-sensor-specific quirks such as garbage frames or corrupted lines at stream start |
tuner, audio, vbi, ir | TV tuner, audio, VBI, and infrared sub-device categories — rarely used outside TV/tuner hardware |
A key detail worth internalizing: core ops are shared across every category of sub-device. Whether you are writing a camera sensor, an HDMI receiver, or a tuner, you are always free to implement core callbacks, since they are not tied to any particular data path. We’ll go deep into core_ops, video_ops, and sensor_ops in the next lecture of this free embedded systems course.
How These Callbacks Actually Get Invoked
It’s worth being precise about when a v4l2_subdev_ops callback is reachable at all: these operations only matter for sub-devices that are exposed to user space through their own char device node — the /dev/v4l-subdevX node we build in a later lecture on sub-device registration. A registered sub-device node reuses the exact same file-operations plumbing as a regular bridge video_device, but with a different, subdev-specific file_operations table assigned at init time, roughly subdev->devnode.fops = &v4l2_subdev_fops;. That table’s unlocked_ioctl handler routes each incoming ioctl through an internal dispatcher (subdev_do_ioctl(), in drivers/media/v4l2-core/v4l2-subdev.c on the current kernel) which maps the ioctl number to the matching category/callback pair in your v4l2_subdev_ops table.
Put simply, the real call chain for a sub-device ioctl is:
v4l2_fops -> v4l2_subdev_fops.unlocked_ioctl -> subdev_do_ioctl() -> our_custom_subdev_ops
This is why leaving an ops category as NULL is completely safe: if user space (or another kernel caller) triggers an ioctl that maps to a category your driver never populated, the dispatcher simply reports that the operation isn’t supported instead of dereferencing a missing pointer.
Original Demo: ep_camsensor Initialization
Let’s extend the ep_camsensor I2C sub-device driver from our sub-device concept lecture and wire in proper initialization. This is a self-contained, original skeleton — not copied from any book — that you can build and load on a QEMU or real board with I2C support.
#include <linux/i2c.h>
#include <linux/module.h>
#include <media/v4l2-subdev.h>
#include <media/v4l2-async.h>
struct ep_camsensor {
struct v4l2_subdev sd;
struct i2c_client *client;
};
static inline struct ep_camsensor *to_ep_camsensor(struct v4l2_subdev *sd)
{
return container_of(sd, struct ep_camsensor, sd);
}
/* core ops and video ops are wired up in the next lecture */
static const struct v4l2_subdev_ops ep_camsensor_ops = {
/* .core = &ep_camsensor_core_ops, */
/* .video = &ep_camsensor_video_ops, */
};
static int ep_camsensor_probe(struct i2c_client *client)
{
struct ep_camsensor *sensor;
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
sensor->client = client;
v4l2_i2c_subdev_init(&sensor->sd, client, &ep_camsensor_ops);
dev_info(&client->dev, "ep_camsensor initialized as %s\n",
sensor->sd.name);
return 0;
}
static void ep_camsensor_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ep_camsensor *sensor = to_ep_camsensor(sd);
dev_info(&client->dev, "ep_camsensor %s removed\n", sensor->sd.name);
}
static const struct i2c_device_id ep_camsensor_id[] = {
{ "ep_camsensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ep_camsensor_id);
static struct i2c_driver ep_camsensor_driver = {
.driver = {
.name = "ep_camsensor",
},
.probe = ep_camsensor_probe,
.remove = ep_camsensor_remove,
.id_table = ep_camsensor_id,
};
module_i2c_driver(ep_camsensor_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala demo V4L2 I2C sub-device");
Build and Test
Build the module against your configured kernel tree, then load it and register a dummy I2C device at runtime to trigger a probe (useful on a devboard or QEMU without real sensor hardware attached):
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_camsensor.ko
# Instantiate a fake I2C device on bus 2 at address 0x36 to trigger probe()
$ echo ep_camsensor 0x36 | sudo tee /sys/bus/i2c/devices/i2c-2/new_device
Expected kernel log output:
$ dmesg | tail
[ 102.441823] ep_camsensor 2-0036: ep_camsensor initialized as ep_camsensor 2-0036
Notice the name mangling in action: the sub-device’s sd.name is not just "ep_camsensor", it is "ep_camsensor 2-0036" — driver name plus adapter ID plus 4-digit hex address, exactly as produced internally by v4l2_i2c_subdev_init().
Common Mistakes and Troubleshooting
| Mistake | Symptom | Fix |
|---|---|---|
Calling v4l2_subdev_init() directly for an I2C sensor | Sub-device has a generic name, no bus linkage; i2c_get_clientdata() returns garbage | Use v4l2_i2c_subdev_init() for any I2C-controlled sub-device |
Forgetting to embed struct v4l2_subdev and instead allocating it separately | container_of() computations become invalid, driver crashes on remove | Always embed the sub-device as the first or a named field of your private struct |
Assuming v4l2_subdev_ops categories are mandatory | Driver defines empty callbacks “just in case” | Leave unused categories NULL; the framework treats that as “not supported” and returns -ENOIOCTLCMD safely |
Mixing up video_ops and pad_ops | Format negotiation calls silently do nothing | Use pad_ops for anything integrated with the media controller framework; reserve video_ops for simple non-MC video-mode devices |
Best Practices
- Always initialize the sub-device inside your bus driver’s
probe(), never lazily on first open. - Keep the private driver structure as the single source of truth, with the sub-device and bus client embedded inside it.
- Only populate
v4l2_subdev_opscategories your hardware genuinely supports — an accurate, minimal ops table is easier to maintain and debug. - Use
dev_info()/dev_dbg()tied to the underlying bus device (&client->dev) rather than genericpr_info(), so log lines are automatically prefixed with the correct bus/address context.
Real-World Use Cases
Every mainline camera sensor driver under drivers/media/i2c/ — CMOS sensors, HDMI-to-CSI bridges, image signal processors — begins exactly this way: a private structure embedding struct v4l2_subdev, initialized with v4l2_i2c_subdev_init(), with a carefully scoped v4l2_subdev_ops table. Once you’re comfortable with this pattern, reading real sensor drivers in the kernel tree becomes far easier, because the boilerplate is always structured the same way.
Summary / Key Takeaways
v4l2_subdev_init()is the generic base initializer;v4l2_i2c_subdev_init()andv4l2_spi_subdev_init()add bus-specific linkage on top of it.- I2C/SPI variants create a two-way pointer relationship between the sub-device and the bus client, retrievable via
i2c_get_clientdata()/v4l2_get_subdevdata(), and back to the driver struct viacontainer_of(). - The bus-specific initializers also generate a collision-free, human-readable sub-device name from the driver name, adapter ID, and address.
struct v4l2_subdev_opsis a table of category pointers —core,video,pad,sensor,tuner,audio,vbi,ir— and only the relevant ones need to be filled in.
Conclusion
Sub-device initialization is a small amount of code, but getting it right is what makes every later step in a V4L2 driver — registration, control handling, streaming — actually work. Once your sub-device is correctly initialized and its operations table is in place, you’re ready to implement the individual callbacks, which is exactly what we cover next in this free linux kernel development course.
Frequently Asked Questions
What is the difference between v4l2_subdev_init and v4l2_i2c_subdev_init?
v4l2_subdev_init() only sets up the generic sub-device fields and attaches the ops table. v4l2_i2c_subdev_init() calls that function internally and additionally links the sub-device to its I2C client, sets the V4L2_SUBDEV_FL_IS_I2C flag, and generates a bus-qualified name.
Can a sub-device be both an I2C device and use v4l2_subdev_init directly?
Technically yes, but you would then be responsible for manually replicating the linkage and naming logic that v4l2_i2c_subdev_init() already provides. There is no practical benefit to doing this for a genuine I2C sensor.
Why does my sub-device show a name like “ep_camsensor 2-0036” instead of just the driver name?
This is the automatic name mangling performed by the bus-specific init helpers, combining driver name, I2C adapter number, and 4-digit hex address, so multiple instances of the same sensor never collide in media-ctl output or logs.
Do I have to implement every category in struct v4l2_subdev_ops?
No. Leave any category pointer you don’t need as NULL. The v4l2_subdev_call() macro (covered in the next lecture) safely returns -ENOIOCTLCMD when a category or callback is missing.
What happens if I allocate struct v4l2_subdev separately instead of embedding it?
container_of() depends on the sub-device being a field inside your larger private structure at a fixed offset. If you allocate it separately, that calculation is invalid and will corrupt memory access when you try to reach your driver-specific fields.
Is v4l2_spi_subdev_init still used in modern kernels?
Yes, though it is far less common than the I2C variant since most modern camera sensors use I2C for control. SPI-controlled sub-devices (some older tuners and specialised sensors) still use it the same way.
Where should I learn more about this for free?
This lecture is part of Embedded Pathashala’s free linux kernel development course, which also covers V4L2, videobuf2, Bluetooth, and general kernel/driver programming at no cost.
Continue Your Free Linux Kernel Development Course
Master V4L2 sub-device operations, registration, and controls step by step with Embedded Pathashala.
Next Lecture Browse Full Course Index