XilinxLinuxV4L2视频管道驱动程序分析
对一个封装为V4L2的Sensor,在AP层增加一个直接操作I2C的API.
这里使用的是全志T507的SDK,linux版本是191
因为在全志平台,sensor设备并不是默认做为一个标准的I2C设备,可以通过/dev/i2c-x可以直接对设备进行读写.而是注册为V4L2的设备.这样AP层就无法通过常规的I2C操作来操作Sensor的Register.
全志平台的I2C有自己的标准:TWI:NormalTwoWireInterface:“全志平台兼容I2C标准协议的总线控制协议”.
但是软件层面,用的还是I2C的描述,这样就太恶心了,我认为完全没有必要新发明一套私有的体系,这样违背了设立I2C的初衷.
这样做的目的是什么?
之所以注册为/dev/videox,就是为了避免AP层对sensor直接进行I2C操作而可能引发问题.而I2C的操作,全志自己封装了1层cci来进行操作.
那么具体是使用cci还是使用twi,从kernel里面的配置可以得知是可以选择的,默认是保留现在的cci方式,可选是将V4L2做为一个常规的I2C设备.
.config-Linux/arm64191KernelConfigurationDeviceDrivers>Multimediasupport>V4Lplatformdevices>selectcciorccitotwi—>useinternalccichengeccitotwi
怎么做?
一开始我也不知道V4L2的命令是如何运作的,那么只能通过解剖麻雀的方法进行研究.我对subdevice的理解是设备可以作为I2C,sensor,v4l2的子设备,可以互相指向和转化,所以叫做subdevice,这个有新的理解再补充.
// 从v4l2_subdev获取i2c_client:
struct i2c_client *client = v4l2_get_subdevdata(sd);
从sd成员来获取父成员的地址
获取info的成员成员sd的地址
这2个是一个互为相反的操作
1研究一下V4L2的subdevice的初始化
在sunxi平台,/dev/video0的设备,大致是经过下面的流程进行了初始化.
module_init(vin_init);
└──>vin_init(void);
│ // drivers/media/platform/sunxi-vin/vin-video/vin_core.c
├── sunxi_csi_platform_register();
└──>sunxi_vin_core_register_driver();
└──>platform_driver_register(&vin_core_driver);
└──>static struct platform_driver vin_core_driver = {
.probe = vin_core_probe,
...
}
vin_core_probe();
│ //drivers/media/platform/sunxi-vin/vin-video/vin_video.c
└──>vin_initialize_capture_subdev();
│ // drivers/media/v4l2-core/v4l2-subdev.c
│ // 此时将device作为链表加入init序列,并开始初始化具体的device,下面是间接的调用
├──>v4l2_subdev_init();
│ sd->internal_ops = &vin_capture_sd_internal_ops;
│ struct v4l2_subdev_internal_ops vin_capture_sd_internal_ops = {
│ .registered = vin_capture_subdev_registered,
│ .unregistered = vin_capture_subdev_unregistered,
│};
└──v4l2_set_subdevdata(sd)
└──>vin_capture_subdev_registered()
| // drivers/media/platform/sunxi-vin/vin-video/vin_video.c
├──>vin_init_controls()
│ │ //将3A之类的命令注册一下,并给初始值
│ ├──>v4l2_ctrl_new_std(V4L2_CID_XXX)
│ ├──>v4l2_ctrl_new_std_menu(V4L2_CID_XXX)
│ └──>v4l2_ctrl_new_custom(V4L2_CID_XXX)
└──>vin_init_video()
├──>cap->vdev.name // vin_video0
├──>cap->vdev.fops = &vin_fops; // 文件接口的ioctl
├──>cap->vdev.ioctl_ops = &vin_ioctl_ops; // ioctl的命令列表
├──>video_register_device(cap->vdev)
├──>video_set_drvdata()
└──>vb2_queue_init()
2具体到我使用的sensormlx75027,具体的初始化流程
// drivers/media/platform/sunxi-vin/modules/sensor/mlx75027_mipi.c
module_init(init_sensor);
│ static struct i2c_driver sensor_driver = {
│ .probe = sensor_probe,
│ ...
│ };
└──>init_sensor(void);
└──>cci_dev_init_helper(&sensor_driver);
└──>sensor_driver->probe()
└──>sensor_probe()
├──>sensor_init_controls(sensor_ctrl_ops);
│ │ // 定义sensor对3A命令处理的逻辑,可覆盖父级别
│ └──>v4l2_ctrl_new_std(V4L2_CID_XXX);
└──>cci_dev_probe_helper(sensor_ops); // 设置sensor_ops
│ // 定义sensor对3A命令处理的逻辑,可覆盖父级别
└──>struct v4l2_subdev_ops sensor_ops = {
.core = &sensor_core_ops,
...
};
└──>struct v4l2_subdev_core_ops sensor_core_ops = {
.ioctl = sensor_ioctl, // 上层通过ioctl,最终会调用到这里,我实现的自定义命令,也会到这个处理
...
};
V4L2cmd的类型
第一种是定义在/include/uapi/linux/videodevh中VIDIOC_XXX这种命令的个数不超过#defineBASE_VIDIOC_PRIVATE192做为v4l2保留的命令,自定义的命令,不建议加在这个区间内.这些命令是v4l2操作的基本函数,包括设备的打开和关闭,流数据的请求等
/*
* I O C T L C O D E S F O R V I D E O D E V I C E S
*
*/
#define VIDIOC_QUERYCAP _IOR("V", 0, struct v4l2_capability)
#define VIDIOC_RESERVED _IO("V", 1)
#define VIDIOC_ENUM_FMT _IOWR("V", 2, struct v4l2_fmtdesc)
#define VIDIOC_G_FMT _IOWR("V", 4, struct v4l2_format)
#define VIDIOC_S_FMT _IOWR("V", 5, struct v4l2_format)
#define VIDIOC_REQBUFS _IOWR("V", 8, struct v4l2_requestbuffers)
#define VIDIOC_QUERYBUF _IOWR("V", 9, struct v4l2_buffer)
#define VIDIOC_G_FBUF _IOR("V", 10, struct v4l2_framebuffer)
#define VIDIOC_S_FBUF _IOW("V", 11, struct v4l2_framebuffer)
#define VIDIOC_OVERLAY _IOW("V", 14, int)
#define VIDIOC_QBUF _IOWR("V", 15, struct v4l2_buffer)
#define VIDIOC_EXPBUF _IOWR("V", 16, struct v4l2_exportbuffer)
#define VIDIOC_DQBUF _IOWR("V", 17, struct v4l2_buffer)
#define VIDIOC_STREAMON _IOW("V", 18, int)
#define VIDIOC_STREAMOFF _IOW("V", 19, int)
第二种是定义在/include/uapi/linux/v4l2-controls.h中V4L2_CID_XXX因为是应用层使用,所以这里定义了地址,并按照一定的大小来区分不同的应用可以使用的范围.
#define V4L2_CTRL_CLASS_USER 0x00980000 /* Old-style "user" controls */
#define V4L2_CID_BASE (V4L2_CTRL_CLASS_USER | 0x900)
#define V4L2_CID_USER_BASE V4L2_CID_BASE
#define V4L2_CID_USER_CLASS (V4L2_CTRL_CLASS_USER | 1)
#define V4L2_CID_BRIGHTNESS (V4L2_CID_BASE+0)
#define V4L2_CID_CONTRAST (V4L2_CID_BASE+1)
第三种是定义在/include/media/sunxi_camera_vh里面给各个Camera使用的cmd.可以看到在私有的192之后,可以添加sensor的私有数据VIDIOC_ISP_XXX和VIDIOC_VIN_SENSOR_XXX.
#define BASE_VIDIOC_PRIVATE 192
#define VIDIOC_ISP_AE_STAT_REQ _IOWR("V", BASE_VIDIOC_PRIVATE + 1, struct isp_stat_buf)
#define VIDIOC_ISP_HIST_STAT_REQ _IOWR("V", BASE_VIDIOC_PRIVATE + 2, struct isp_stat_buf)
...
#define VIDIOC_VIN_SENSOR_CFG_REQ _IOWR("V", BASE_VIDIOC_PRIVATE + 60, struct sensor_config)
#define VIDIOC_VIN_SENSOR_EXP_GAIN _IOWR("V", BASE_VIDIOC_PRIVATE + 61, struct sensor_exp_gain)
#define VIDIOC_VIN_SENSOR_SET_FPS _IOWR("V", BASE_VIDIOC_PRIVATE + 62, struct sensor_fps)
...
// 我新增的命令放在了最后
struct msg_i2c {
unsigned short addr;
unsigned short value;
};
#define VIDIOC_VIN_SET_I2C_DATA _IOWR("V", BASE_VIDIOC_PRIVATE + 76, struct msg_i2c)
不同类型命令的处理过程
VIDIOC_XXX的处理
int sel = 0;
int mode = 5;
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (-1 == camera_init(sel, mode))
return -1;
if (-1 == camera_fmt_set(mode))
return -1;
if (-1 == req_frame_buffers())
return -1;
pixformat <span class="token operator">=</span> TVD_PL_YUV420<span class="token punctuation">;</span>
<span class="token function">disp_init</span><span class="token punctuation">(</span>input_size<span class="token punctuation">.</span>width<span class="token punctuation">,</span> input_size<span class="token punctuation">.</span>height<span class="token punctuation">,</span> pixformat<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span> <span class="token operator">==</span> <span class="token function">ioctl</span><span class="token punctuation">(</span>fd<span class="token punctuation">,</span> VIDIOC_STREAMON<span class="token punctuation">,</span> <span class="token operator">&</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"VIDIOC_STREAMON failed
"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
在ioctl之后,从kernel到driver的流程如下//在第1节已经知道了vin_init_video会注册cap->vdev.fops=&vin_fops;//所以对vin设备的ioctl都会到这个函数staticstructv4l2_file_operationsvin_fops={.unlocked_ioctl=video_ioctl2,}staticstructv4l2_ioctl_infov4l2_ioctls[]={IOCTL_INFO_FNC,IOCTL_INFO_FNC,}
EXPORT_SYMBOL;video_ioctl2└──>__video_do_ioctl├──>v4l2_is_known_ioctl//如果是在已知cmd列表里面,就执行注册的func├──>structv4l2_ioctl_infoinfo=&v4l2_ioctls[_IOC_NR];└──>info->func;└──>v4l_streamon└──>ops->vidioc_streamonarg);└──>staticconststructv4l2_ioctl_opsvin_ioctl_ops={.vidioc_streamon=vidioc_streamon,.vidioc_streamoff=vidioc_streamoff,}///drivers/media/platform/sunxi-vin/vin-video/vin_video.cvidioc_streamon└──>vb2_streamon│///drivers/media/v4l2-core/videobuf2-core.c└──>vb2_core_streamon├──>v4l_vb2q_enable_media_source└──>vb2_start_streaming│///drivers/media/platform/sunxi-vin/modules/sensor/mlx75027_mipi.c└──>structv4l2_subdev_video_opssensor_video_ops={.s_stream=sensor_s_stream,}sensor_s_stream└──>sensor_s_streamon;└──>sensor_write;//实际操作寄存器
// drivers/media/platform/sunxi-vin/modules/sensor/mlx75027_mipi.c
module_init(init_sensor);
│ static struct i2c_driver sensor_driver = {
│ .probe = sensor_probe,
│ ...
│ };
└──>init_sensor(void);
└──>cci_dev_init_helper(&sensor_driver);
└──>sensor_driver->probe()
└──>sensor_probe()
├──>sensor_init_controls(sensor_video_ops);
│ │ // 定义sensor对3A命令处理的逻辑,可覆盖父级别
│ └──>v4l2_ctrl_new_std(V4L2_CID_XXX);
└──>cci_dev_probe_helper(sensor_ops); // 设置sensor_ops
│ // 定义sensor对3A命令处理的逻辑,可覆盖父级别
└──>v4l2_subdev_video_ops sensor_video_ops = {
.s_parm = sensor_s_parm,
.g_parm = sensor_g_parm,
...
};
└──>struct v4l2_ioctl_ops soc_camera_ioctl_ops = {
.vidioc_g_parm = soc_camera_g_parm,
}
└──>soc_camera_g_parm()
└──>default_g_parm()
└──>vidioc_g_parm()
└──>v4l2_subdev_call(vinc->vid_cap.pipe.sd[VIN_IND_SENSOR], video, g_parm, parms);
└──>in_ctrl_ops = {
.g_volatile_ctrl = vin_g_volatile_ctrl,
};
└──>vin_g_volatile_ctrl()
// /drivers/media/platform/sunxi-vin/modules/sensor/mlx75027_mipi.c
v4l2_ctrl_ops sensor_ctrl_ops = {
.g_volatile_ctrl = sensor_g_ctrl,
};
└──>int sensor_g_ctrl(struct v4l2_ctrl *ctrl)
switch (ctrl->id)
case V4L2_CID_EXPOSURE:
sensor_g_exp(); // 实际的处理函数
自定义cmd的处理
// /include/media/sunxi_camera_v2.h 在头文件增加新的cmd和传入的参数定义
struct msg_i2c {
unsigned short addr;
unsigned short value;
};
#define VIDIOC_VIN_SET_I2C_DATA _IOWR("V", BASE_VIDIOC_PRIVATE + 76, struct msg_i2c)
///drivers/media/platform/sunxi-vin/vin-video/vin_video.cstructv4l2_ioctl_opsvin_ioctl_ops={.vidioc_default=vin_param_handler,//走的是这个函数└──>vin_param_handler└──>switch{caseVIDIOC_VIN_SET_I2C_DATA:vidioc_set_i2c_zhang;└──>structvin_corevinc=video_drvdata;v4l2_subdev_call;│///drivers/media/platform/sunxi-vin/modules/sensor/mlx75027_mipi.c└──>structv4l2_subdev_opssensor_ops={.core=&sensor_core_ops,};structv4l2_subdev_core_opssensor_core_ops={.ioctl=sensor_ioctl,}└──>sensor_ioctl└──>switch{caseVIDIOC_VIN_SET_I2C_DATA:sensor_s_i2c_dataarg);└──>sensor_write;
v4l2_subdev_call这个是实现自定义cmd的重点
/*
* Call an ops of a v4l2_subdev, doing the right checks against
* NULL pointers.
*
* Example: err = v4l2_subdev_call(sd, video, s_std, norm);
*/
#define v4l2_subdev_call(sd, o, f, args...)
(!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ?
(sd)->ops->o->f((sd), ##args) : -ENOIOCTLCMD))
vidioc_set_i2c_zhang(struct file *file, struct v4l2_fh *fh, struct msg_i2c *i2c);
└──>v4l2_subdev_call(vinc->vid_cap.pipe.sd[VIN_IND_SENSOR], core, ioctl, VIDIOC_VIN_SET_I2C_DATA, i2c);
structv4l2_subdev_opssensor_ops={.core=&sensor_core_ops,};structv4l2_subdev_core_opssensor_core_ops={.ioctl=sensor_ioctl,}
v4l2_subdev_call;实际上等价于v4l2_subdev_call;翻译过来就是通过subdevice来调用sensor_ops的ioctl方法,传入的参数是VIDIOC_VIN_SET_I2C_DATA,方法的结构体是structmsg_i2c.
遇到的问题
is_isp_used和is_bayer_raw的值会影响V4L2_CID_XXX的处理逻辑
static int vin_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
struct vin_vid_cap *cap = container_of(ctrl->handler, struct vin_vid_cap, ctrl_handler);
struct sensor_instance *inst = get_valid_sensor(cap->vinc);
if (inst->is_isp_used && inst->is_bayer_raw) {
// 一般而言,V4L2_CID_XXX 会走这个分支
} else {
}
// /drivers/media/platform/sunxi-vin/vin.c
static int __vin_handle_sensor_info(struct sensor_instance *inst)
{
if (inst->cam_type == SENSOR_RAW) {
inst->is_bayer_raw = 1;
inst->is_isp_used = 1;
} else if (inst->cam_type == SENSOR_YUV) {
inst->is_bayer_raw = 0;
inst->is_isp_used = 0;
} else {
inst->is_bayer_raw = 0;
inst->is_isp_used = 0;
}
return 0;
}
// 从这个函数可以看到,只有sensor的type是SENSOR_RAW的时候,这2个属性才都是1.
// 所以就需要在初始化列表里面将sensor的属性配置好,如果是新增的sensor,这2个值都是0.
// /drivers/media/platform/sunxi-vfe/utility/sensor_info.c
struct sensor_item sensor_list_t[] = {
/* name i2c_addr sensor type sensor size sensor max pclk */
{ "ov2640", 0x60, SENSOR_YUV, PIXEL_NUM_2M, CORE_CLK_RATE_FOR_2M},
{ "ov5647_mipi", 0x6c, SENSOR_RAW, PIXEL_NUM_5M, CORE_CLK_RATE_FOR_5M},
}
// 可以把新增的sensor添加在这个结构体的后面.
linux内核宏container_of的意思
linux内核宏container_of剖析关于linuxcontainer_of用法container_of用法及实现linux驱动程序中container_of宏解析详解Linux内核之位操作
struct struct *struct_p= container_of(struct_member_p, struct struct, struct_member);
//container_of 的作用是:已知 struct结构中某个成员struct_member的指针struct_member_p,就可以知道整个struct的指针struct_p
全志提供了V4L2设备直接读写I2C的做法
【注意事项】IIC在系统和硬件上正常工作;上述命令需要在Camera正常工作状态下执行才会有效,如/sys/devices/sensor节点是否存在,Camera是否上电等等;上述命令只在tina-linux系统有效,rtos系统暂不支持;
文章为作者独立观点,不代表 股票程序化软件自动交易接口观点