V4L2 Menu Controls And Inheritance-Free Linux Device Drivers Course

V4L2 Menu Controls And Inheritance

Free Linux kernel development course — menu controls, linked auto/manual controls, and how sub-device controls flow up into the bridge driver.

We closed the last lecture of this free linux kernel development course with brightness, contrast, saturation, and a volatile autogain control on ep_camsensor. This lecture finishes the controls picture with two remaining pieces: v4l2 menu controls — the enum-style controls used for things like auto-exposure mode — and control inheritance, the mechanism that lets a sub-device’s controls automatically surface through the bridge driver’s own /dev/videoX node. We’ll also correct a placement detail about v4l2_ctrl_handler_setup() that matters on real hardware. This closes out the sub-device operations arc of this free linux device drivers course before we move on to the async and media controller frameworks.

Topics Covered
v4l2_ctrl_new_std_menu linked auto/manual controls v4l2_ctrl_add_handler control inheritance is_private

What You Will Learn

  • How v4l2 menu controls work and the exact v4l2_ctrl_new_std_menu() signature
  • The linked auto/manual control pattern used for gain and exposure
  • Why real sensor drivers often call v4l2_ctrl_handler_setup() from s_stream instead of probe()
  • How sub-device controls automatically get added to the bridge’s own control handler via v4l2_ctrl_add_handler()
  • How is_private keeps a control hidden from the bridge while staying reachable on the sub-device node

Prerequisites

This lecture continues directly from v4l2 control ids and callbacks, and assumes ep_camsensor already has a shared v4l2_ctrl_ops table with brightness/contrast/saturation/autogain controls, as built in the previous lecture.

Menu Controls: v4l2_ctrl_new_std_menu()

Some controls don’t take an arbitrary numeric value — they choose between a small, fixed set of named options, backed by a C enum. Auto-exposure mode is the classic example: a camera is either fully automatic, fully manual, or one of a couple of priority modes in between. These are created with a dedicated helper instead of v4l2_ctrl_new_std():

struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl,
                                          const struct v4l2_ctrl_ops *ops,
                                          u32 id, u8 max, u64 mask, u8 def);

max is the highest enum value the menu will expose (letting you cut a standard enum down to only the entries your hardware actually supports), mask is a bitmask that can disable individual entries within that range, and def is the default selection. Here’s how ep_camsensor adds a two-choice auto-exposure control, restricting the standard enum v4l2_exposure_auto_type down to just V4L2_EXPOSURE_AUTO and V4L2_EXPOSURE_MANUAL:

sensor->auto_exposure =
    v4l2_ctrl_new_std_menu(&sensor->ctrl_handler, &ep_camsensor_ctrl_ops,
                            V4L2_CID_EXPOSURE_AUTO,
                            V4L2_EXPOSURE_MANUAL, /* max: restrict menu to AUTO+MANUAL */
                            0,                     /* mask: no entries disabled */
                            V4L2_EXPOSURE_AUTO);   /* def: start in auto mode */

Add struct v4l2_ctrl *auto_exposure; and struct v4l2_ctrl *exposure; fields to struct ep_camsensor for this to compile — we’ll use both in the next section.

The Linked Auto/Manual Control Pattern

Auto-exposure and auto-gain controls are rarely standalone — they’re paired with a companion control that holds the manually-set value to fall back to. The common pattern: when the auto control is switched to manual, immediately apply the companion control’s currently stored value; when it’s switched to auto, hand control over to the hardware’s own auto-exposure or auto-gain algorithm instead. Extending ep_camsensor_s_ctrl() from the previous lecture:

static int ep_camsensor_s_ctrl(struct v4l2_ctrl *ctrl)
{
    struct ep_camsensor *sensor =
        container_of(ctrl->handler, struct ep_camsensor, ctrl_handler);

    switch (ctrl->id) {
    case V4L2_CID_BRIGHTNESS:
        return ep_camsensor_write_reg(sensor, EP_CAMSENSOR_REG_BRIGHTNESS, ctrl->val);
    case V4L2_CID_CONTRAST:
        return ep_camsensor_write_reg(sensor, EP_CAMSENSOR_REG_CONTRAST, ctrl->val);
    case V4L2_CID_SATURATION:
        return ep_camsensor_write_reg(sensor, EP_CAMSENSOR_REG_SATURATION, ctrl->val);
    case V4L2_CID_AUTOGAIN:
        if (!ctrl->val)
            return ep_camsensor_write_reg(sensor, EP_CAMSENSOR_REG_GAIN,
                                           sensor->gain->val);
        return ep_camsensor_set_autogain(sensor, ctrl->val);
    case V4L2_CID_EXPOSURE_AUTO:
        if (ctrl->val == V4L2_EXPOSURE_MANUAL)
            return ep_camsensor_write_reg(sensor, EP_CAMSENSOR_REG_EXPOSURE,
                                           sensor->exposure->val);
        return ep_camsensor_set_autoexposure(sensor, ctrl->val);
    default:
        return -EINVAL;
    }
}

Notice the shape: V4L2_CID_AUTOGAIN set to 0 (off) falls back to writing sensor->gain->val — the last manually-set gain value — directly to the register. Any non-zero value instead calls a dedicated auto-gain helper. The same shape repeats for V4L2_CID_EXPOSURE_AUTO: selecting V4L2_EXPOSURE_MANUAL falls back to the stored sensor->exposure->val, while any other selection hands off to an auto-exposure helper. This is exactly the kind of control relationship that becomes important once you look at formal control clusters — a topic worth its own dedicated lecture, since it also affects locking between related controls.

Linked auto/manual control flow
V4L2_CID_AUTOGAIN set to 0 (manual) -> write sensor->gain->val directly to gain register V4L2_CID_AUTOGAIN set to non-zero (auto) -> hand off to ep_camsensor_set_autogain() (hardware’s own auto-gain algorithm takes over)

Where To Call v4l2_ctrl_handler_setup(): A Correction

In the previous lecture we called v4l2_ctrl_handler_setup() straight from probe(), right after creating all controls. That’s fine for a simulated device, but on real hardware it can be the wrong place: many sensors are still powered down at probe time, and writing register values to unpowered hardware simply fails or is silently ignored. This is why real-world drivers frequently call it later — from the sub-device’s video_ops.s_stream callback, at the moment streaming actually starts and the sensor is guaranteed to be powered and configured:

static int ep_camsensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    struct ep_camsensor *sensor = to_ep_camsensor(sd);
    int ret;

    if (enable) {
        ret = v4l2_ctrl_handler_setup(&sensor->ctrl_handler);
        if (ret) {
            dev_err(&sensor->client->dev,
                    "%s: control setup failed (%d)\n", __func__, ret);
            return ret;
        }
    }

    sensor->streaming = enable;
    dev_info(&sensor->client->dev, "streaming %s\n",
             enable ? "started" : "stopped");
    return 0;
}

The rule of thumb: if your driver powers hardware on immediately at probe and keeps it powered, calling v4l2_ctrl_handler_setup() in probe() (as our earlier ep_camsensor examples did) is fine. If power is only applied when streaming begins, move the call into s_stream as shown here, and always check its return value.

Control Inheritance: From Sub-device Up To The Bridge

It’s common for a sub-device driver to implement controls that overlap with ones the bridge’s own V4L2 driver also cares about. The framework handles this automatically at registration time: when v4l2_device_register_subdev() is called and both the sub-device’s and the bridge’s v4l2_device have a ctrl_handler set, the sub-device’s controls are merged into the bridge’s handler using the v4l2_ctrl_add_handler() helper, which copies a given handler’s controls into another handler. Any sub-device control whose ID the bridge already implements is simply skipped during this merge — in other words, the bridge driver can always override a sub-device control by implementing that same control ID itself.

Control inheritance at registration
v4l2_device_register_subdev(v4l2_dev, sd) both sd->ctrl_handler and v4l2_dev->ctrl_handler set? -> v4l2_ctrl_add_handler() merges sd’s controls into v4l2_dev’s handler -> any control ID the bridge already owns is skipped (bridge wins)

Sometimes, though, a control performs low-level, hardware-specific work that the sub-device driver deliberately does not want exposed through the bridge at all. For that case, set the control’s is_private field to 1 (or true) right after creating it:

struct v4l2_ctrl *internal_ctrl;

internal_ctrl = v4l2_ctrl_new_std(&sensor->ctrl_handler, &ep_camsensor_ctrl_ops,
                                   V4L2_CID_GAIN, 0, 1023, 1, 500);
if (internal_ctrl)
    internal_ctrl->is_private = 1;

Important note: even though a private control is deliberately kept out of the bridge’s merged control handler, it remains fully accessible through the sub-device’s own control device node — is_private only affects whether the control is inherited upward, not whether it’s reachable at all.

Build and Test

$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_camsensor.ko
$ echo ep_camsensor 0x36 | sudo tee /sys/bus/i2c/devices/i2c-2/new_device

List the new menu control and confirm its allowed values:

$ v4l2-ctl -d /dev/v4l-subdev0 --list-ctrls-menus
                 exposure_auto 0x009a0901 (menu)   : min=0 max=1 default=0 value=0
                                1: Manual Mode
                                0: Auto Mode

Switch it to manual and confirm the linked fallback behaviour in dmesg:

$ v4l2-ctl -d /dev/v4l-subdev0 --set-ctrl exposure_auto=1

$ dmesg | tail -2
[  530.204411] ep_camsensor 2-0036: write reg 0x14 = 100

That single write is ep_camsensor_s_ctrl() falling back to sensor->exposure->val the moment manual mode was selected — exactly the linked-control behaviour described above.

Common Mistakes and Troubleshooting

MistakeSymptomFix
Calling v4l2_ctrl_handler_setup() in probe() before the sensor is poweredRegister writes silently fail or are ignored; hardware never actually reaches its default stateMove the call into s_stream (or wherever your driver guarantees the sensor is powered) if power isn’t applied at probe time
Setting max on v4l2_ctrl_new_std_menu() to the full enum range when hardware only supports a subsetUserspace apps offer menu choices the hardware silently ignores or rejectsRestrict max (and use mask if needed) to exactly the entries your hardware supports
Forgetting to skip the manual-value write when the auto control is actually in auto modeAuto-gain/auto-exposure keeps getting overwritten by a stale manual valueAlways branch on the auto control’s own value first, exactly as shown in the linked-control pattern
Expecting a private sub-device control to disappear entirelyConfusion when the control still shows up via /dev/v4l-subdevXRemember is_private only blocks inheritance into the bridge’s handler, not access via the sub-device node itself

Best Practices

  • Use v4l2_ctrl_new_std_menu() for any enum-backed control, and trim max/mask to match what your hardware genuinely supports.
  • Keep the manual companion control’s current value cached in your private struct so the auto/manual fallback pattern has something correct to write.
  • Decide v4l2_ctrl_handler_setup()‘s call site based on your power-management model, not by default habit — probe-time only if hardware is already powered there.
  • Use is_private deliberately for controls that are genuinely internal, not as a general-purpose way to hide controls you simply haven’t finished testing.

Real-World Use Cases

Auto-exposure and auto-gain menu/linked-control pairs appear in almost every mainline camera sensor driver — it’s the standard way hardware auto-algorithms and manual user control coexist under one control ID. Control inheritance is what lets a single ISP bridge driver present one unified control list on /dev/video0 even though several of those controls are actually implemented deep inside an attached sensor sub-device — application authors never need to know or care which layer actually owns a given control.

Summary / Key Takeaways

  • v4l2_ctrl_new_std_menu() creates enum-backed menu controls, with max/mask letting you restrict which enum entries are actually offered.
  • The linked auto/manual pattern branches on the auto control’s own value: manual falls back to a cached companion value, auto hands off to a hardware-specific routine.
  • v4l2_ctrl_handler_setup() should be called wherever your driver can guarantee the hardware is powered — often s_stream rather than probe() — and its return value should always be checked.
  • At sub-device registration, matching control IDs are inherited into the bridge’s handler via v4l2_ctrl_add_handler() unless the bridge already implements them or the sub-device marks the control is_private.

Conclusion

This closes out the sub-device operations and controls arc of this free linux kernel development course: initialization, the operations table, safe calling conventions, registration, and now a complete controls implementation including menu controls and inheritance. ep_camsensor is now a fully functional, self-contained V4L2 sub-device. The next stretch of lectures moves into the async core and media controller framework — the pieces that tie multiple sub-devices together into one coherent camera pipeline, and the modern, devicetree-friendly replacement for the synchronous registration model we covered earlier.

Frequently Asked Questions

What is the difference between v4l2_ctrl_new_std and v4l2_ctrl_new_std_menu?

v4l2_ctrl_new_std creates controls with a numeric min/max/step/default range. v4l2_ctrl_new_std_menu creates enum-backed controls that select between a fixed, named set of choices, using max/mask to restrict which enum entries are offered.

Why do some drivers call v4l2_ctrl_handler_setup from s_stream instead of probe?

Because on hardware that is only powered up when streaming starts, calling it earlier in probe would attempt register writes against unpowered hardware, which fails or is silently ignored.

What does v4l2_ctrl_add_handler actually do?

It copies the controls owned by one control handler into another. It’s what the framework uses automatically at sub-device registration time to merge a sub-device’s controls into the bridge’s own control handler.

If a bridge and a sub-device both implement the same control ID, which one wins?

The bridge’s own implementation wins. During the automatic merge at registration, any sub-device control whose ID the bridge already implements is skipped.

Does is_private hide a control completely?

No. It only prevents that control from being inherited into the bridge’s control handler. The control remains fully accessible through the sub-device’s own control device node.

Is this part of a free course?

Yes, this is part of Embedded Pathashala’s free Linux kernel development course, which also covers Bluetooth and general embedded Linux driver topics at no cost.

Continue Your Free Linux Kernel Development Course

Next up: the V4L2 async core and media controller framework, tying multiple sub-devices into one pipeline.

Next Lecture Browse Full Course Index

Leave a Reply

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