Setting ctrl with V4L2 on ov5640

I would like to control various ov5640 camera parameters by using ioctl and VIDIOC_S_CTRL from V4L2 in the following manner:

#include <string>
#include <iostream>

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <cstring>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>

#define IOCTL_TRIES 3
#define CLEAR(x) memset (&(x), 0, sizeof (x))

static int xioctl(int fd, int request, void *arg)
{
    int r;
    int tries = IOCTL_TRIES;

    do {
        r = ioctl(fd, request, arg);
    } while (--tries > 0 && r == -1 && EINTR == errno);

    return r;
}

bool v4l2_ctrl_set(int fd, uint32_t id, int val)
{
    struct v4l2_control ctrl;
    CLEAR(ctrl);

    ctrl.id = id;
    ctrl.value = val;
    if (xioctl(fd, VIDIOC_S_CTRL, &ctrl) == -1) {
        std::cout << "Failed to set ctrl with id " 
                  << id << " to value " << val
                  << "\nerror (" << errno << "): " << strerror(errno) << std::endl;

       	return false;
    }
    
    return true;
}

int main()
{

    int fd = open("/dev/video0", O_RDWR | O_NONBLOCK);
    if (fd == -1) {
        std::cout << "Failed to open the camera" << std::endl;
        return -1;
    }

    v4l2_ctrl_set(fd, V4L2_CID_SATURATION, 100);

    return 0;
}

Unfortunately ioctl fails and I get error (25): Inappropriate ioctl for device. I’m using Intrinsyc Open-Q 820 µSOM with linaro 4.14. I’ve managed to add some debugs prints to ov5640 driver file in ov5640_s_ctrl function before if (sensor->power_count == 0) { (in case there were problems with power save mode) and recompile the kernel. I ran the code, but looking through dmesg my printk message doesn’t get printed, so that means that ov5640_s_ctrl doesn’t get called even though the callback is set:

static const struct v4l2_ctrl_ops ov5640_ctrl_ops = {
	.g_volatile_ctrl = ov5640_g_volatile_ctrl,
	.s_ctrl = ov5640_s_ctrl,
};

Am I using V4L2 wrong? Should I enable something before setting the controls? It’s even more confusing since I manage to get an image from the camera with v4l2, but I can’t set/get any controls.

You’re using the wrong video device, ‘/dev/video0’ is the qcom video pipeline output (cf https://linuxtv.org/downloads/v4l-dvb-apis/v4l-drivers/qcom_camss.html for the diagram). So you have to send these ioctls to the ov5640 v4l2 subdevice (which is probably v4l-subdev10).

You can use sudo media-ctl -d /dev/media0 to display the pipeline.

1 Like

You are absolutely right, /dev/v4l-subdevice19 was the correct one! I don’t know how this page passed me when reading v4l2 docs… This even clears up a bit the magic line sudo media-ctl -d /dev/media0 -l '"msm_csiphy0":1->"msm_csid0":0[1],"msm_csid0":1->"msm_ispif0":0[1],"msm_ispif0":1->"msm_vfe0_rdi0":0[1]'… Can you please explain what :0/1 (after colon) (e.g "msm_csiphy0":1) and [1] (e.g "msm_ispif0":0[1]) mean?

Do I also use this subdevice to set the mmap, pixel format and retrieve an image (dequeue + queue), as I managed to do that with /dev/video0? Or is it just for setting the controls?

Well I admit it’s not as simple as for as standard e.g. USB camera, since it involves several devices here (sensor, csi interface, vfe interfaces…).

You get image from vfe rdi0 output (dev/video0) so use this device for frame queue/dequeue. The ov5640 subdevice is just for ov5640 related controls, you cannot directly get an image from it.

media-ctl --help
...
link = pad '->' pad '[' flags ']' ;

In your case:

"msm_csiphy0":1->"msm_csid0":0[1]

The number after colon is the pad index of the device (as described by media-ctl --print-topology). so you connect msm_csiphy0 pad0 (Source) to msm_csid pad0 (Sink). [1] is just a flag meaning active link.

1 Like

I thought that ones and zeros had to do something with sources and sinks, but just wasn’t sure which ones. Thanks again for the thorough explanation!

I tried setting the autoexposure (V4L2_CID_EXPOSURE_AUTO) to manual (V4L2_EXPOSURE_MANUAL), but the power of the device is set to off so this check fails:

if (sensor->power_count == 0)
     return 0;

Looking through the kernel driver I noticed that the power is controled by the ov5640_s_power which is a subdevice callback set in v4l2_subdev_core_ops.s_power. Any suggestions to how I turn the camera power on, before sending xioctl(fd, VIDIOC_S_CTRL, &ctrl) to /dev/v4l-subdev20?

Well AFAIR, the pipeline need to be enabled to power/enable subdevices, so I suppose you have to configure the pipeline and open /dev/video0 (pipeline output) before performing your controls on the sensor subdevice.

That was it, you are right again! Thanks for the help!