Linux驱动分析之V4L2驱动框架

一口Linux 2022-09-26 00:00


前言

    前面我们了解了一些Camera的基础知识《Camera基础知识》。接下来来看看Linux为Camera提供的驱动框架。



V4L2简介

    V4L2( Video for Linux Two),是一套Linux内核视频设备的驱动框架。该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)。V4L2在设计时,是要支持更广泛的设备,它们其中只有一部分在本质上是真正的视频设备,所以它不仅仅是为Camera设计。



V4L2框架

网络图片


    在应用层,我们可以在 /dev 目录发现 videoX的设备节点,应用程序打开设备节点进行数据捕获,显示视频画面。video0、video1、video2...这些设备节点是在核心层注册。

    核心层(v4l2-dev.c)起承上启下的作用,它会为每一个驱动注册进来的设备设置一个统一的接口 v4l2_fops ,这些统一的接口最终将调用到驱动中的 video_device 的 fops 。



重要结构体


  • video_device

//表示一个视频设备struct video_device {#if defined(CONFIG_MEDIA_CONTROLLER);  struct media_entity entity;  struct media_intf_devnode *intf_devnode;  struct media_pipeline pipe;#endif;  const struct v4l2_file_operations *fops; //文件操作接口(dev/videoX)  u32 device_caps; //设备功能,用于v4l2_capabilities(应用层定义的结构体)  struct device dev;  struct cdev *cdev; //字符设备  struct v4l2_device *v4l2_dev; //V4L2设备  struct device *dev_parent;  struct v4l2_ctrl_handler *ctrl_handler; //设备节点对应的控制句柄  struct vb2_queue *queue;  struct v4l2_prio_state *prio;  char name[32];  //Video设备名称  enum vfl_devnode_type vfl_type; //V4L设备类型  enum vfl_devnode_direction vfl_dir; //V4L 接收者/发送者/m2m  int minor; //子设备号,主设备为81  u16 num;   unsigned long flags;  int index;  spinlock_t fh_lock;  struct list_head        fh_list;  int dev_debug;  v4l2_std_id tvnorms;  void (*release)(struct video_device *vdev); //video_device release()回调  const struct v4l2_ioctl_ops *ioctl_ops; //IOCTL回调  unsigned long valid_ioctls[BITS_TO_LONGS(BASE_VIDIOC_PRIVATE)];  struct mutex *lock;};



  • v4l2_device

struct v4l2_device {  struct device *dev;  struct media_device *mdev;  struct list_head subdevs; //用于追踪已注册的subdev  spinlock_t lock;  char name[V4L2_DEVICE_NAME_SIZE]; //设备名  void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);  struct v4l2_ctrl_handler *ctrl_handler; //控制句柄  struct v4l2_prio_state prio; //设备的优先状态  struct kref ref; //引用  void (*release)(struct v4l2_device *v4l2_dev);//引用计数为0后调用};

嵌入到video_device中,表示一个v4l2设备的实例。



  • v4l2_subdev

struct v4l2_subdev {#if defined(CONFIG_MEDIA_CONTROLLER);  struct media_entity entity;#endif;  struct list_head list;  //subdev列表  struct module *owner;  bool owner_v4l2_dev;  u32 flags;  struct v4l2_device *v4l2_dev;  //依附的v4l2_device  const struct v4l2_subdev_ops *ops; //subdev操作函数  const struct v4l2_subdev_internal_ops *internal_ops;  struct v4l2_ctrl_handler *ctrl_handler; //控制句柄  char name[V4L2_SUBDEV_NAME_SIZE]; //subdev名称  u32 grp_id;  void *dev_priv;  void *host_priv;  struct video_device *devnode;  struct device *dev;  struct fwnode_handle *fwnode;  struct list_head async_list;  struct v4l2_async_subdev *asd;  struct v4l2_async_notifier *notifier;  struct v4l2_async_notifier *subdev_notifier;  struct v4l2_subdev_platform_data *pdata;//subdev平台数据};

依附在v4l2_device之下,并表示一个v4l2设备的子设备,一个v4l2_device下可以有多个sub_device。



  • v4l2_fh

struct v4l2_fh {  struct list_head        list; //文件句柄列表  struct video_device     *vdev; //依附的video_device  struct v4l2_ctrl_handler *ctrl_handler;  enum v4l2_priority      prio; //文件句柄的优先级  wait_queue_head_t wait;  struct mutex            subscribe_lock;  struct list_head        subscribed; //订阅的事件列表  struct list_head        available; //可用的事件  unsigned int            navailable; //可用的事件数  u32 sequence;  struct v4l2_m2m_ctx     *m2m_ctx;};

用于追踪的文件句柄



v4l2_device和v4l2_subdev的关系:

subdev的设计目的是为了多路复用,就是用一个v4l2_device可以挂接多个v4l2_subdev。所谓的多路复用就是使用一个摄像头控制器来控制多个摄像头。比如手机上有前置和后置摄像头。


在V4L2驱动中,使用v4l2_device来表示摄像头控制器(ISP)。使用v4l2_subdev来表示具体的某一个摄像头(Sensor)。




  • v4l2_file_operations

//V4L2设备操作函数struct v4l2_file_operations {  struct module *owner;  ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  __poll_t (*poll) (struct file *, struct poll_table_struct *);  long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);#ifdef CONFIG_COMPAT;  long (*compat_ioctl32) (struct file *, unsigned int, unsigned long);#endif;  unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long);  int (*mmap) (struct file *, struct vm_area_struct *);  int (*open) (struct file *);  int (*release) (struct file *);};



  • v4l2_ioctl_ops

//IOCTL操作函数struct v4l2_ioctl_ops {    ......    int (*vidioc_overlay)(struct file *file, void *fh, unsigned int i);    int (*vidioc_g_fbuf)(struct file *file, void *fh, struct v4l2_framebuffer *a);    int (*vidioc_s_fbuf)(struct file *file, void *fh, const struct v4l2_framebuffer *a);    int (*vidioc_streamon)(struct file *file, void *fh, enum v4l2_buf_type i);    int (*vidioc_streamoff)(struct file *file, void *fh, enum v4l2_buf_type i);    ......    int (*vidioc_enum_framesizes)(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize);    int (*vidioc_enum_frameintervals)(struct file *file, void *fh, struct v4l2_frmivalenum *fival);    int (*vidioc_s_dv_timings)(struct file *file, void *fh, struct v4l2_dv_timings *timings);    int (*vidioc_g_dv_timings)(struct file *file, void *fh, struct v4l2_dv_timings *timings);    int (*vidioc_query_dv_timings)(struct file *file, void *fh, struct v4l2_dv_timings *timings);    int (*vidioc_enum_dv_timings)(struct file *file, void *fh, struct v4l2_enum_dv_timings *timings);    int (*vidioc_dv_timings_cap)(struct file *file, void *fh, struct v4l2_dv_timings_cap *cap);    int (*vidioc_g_edid)(struct file *file, void *fh, struct v4l2_edid *edid);    int (*vidioc_s_edid)(struct file *file, void *fh, struct v4l2_edid *edid);    int (*vidioc_subscribe_event)(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub);    int (*vidioc_unsubscribe_event)(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub);    long (*vidioc_default)(struct file *file, void *fh, bool valid_prio, unsigned int cmd, void *arg);};



  • v4l2_subdev_ops

//subdev操作函数struct v4l2_subdev_ops {    const struct v4l2_subdev_core_ops       *core;     //subdev核心操作回调    const struct v4l2_subdev_tuner_ops      *tuner;    //radio模式打开v4l设备时的操作回调    const struct v4l2_subdev_audio_ops      *audio;    //音频相关设置回调    const struct v4l2_subdev_video_ops      *video;    //video模式打开v4l设备时的操作回调    const struct v4l2_subdev_vbi_ops        *vbi;    //通过vbi设备节点以video模式打开v4l设备时的操作回调    const struct v4l2_subdev_ir_ops         *ir;     //IR(红外)设备操作函数    const struct v4l2_subdev_sensor_ops     *sensor; //sensor操作函数    const struct v4l2_subdev_pad_ops        *pad;    //pad操作函数};

v4l2可以用于很多类型的设备,所以上面的回调只需根据实际设备的需要实现部分即可。

上面的v4l2_ioctl_ops中实现的部分ioctl最终会调用到v4l2_subdev_ops中的回调函数。



API函数

//注册/注销video_deviceint video_register_device(struct video_device *vdev, enum vfl_devnode_type type, int nr)void video_unregister_device(struct video_device *vdev)//分配/释放video_devicestruct video_device * __must_check video_device_alloc(void);void video_device_release(struct video_device *vdev)
//注册/注销v4l2_deviceint v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
//注册/注销v4l2_subdev(关联v4l2_device和v4l2_subdev)int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd)void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)
//初始化v4l2_subdevvoid v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops);
/********************************I2C subdev*************************************///初始化v4l2_subdev, 该subdev是I2C设备void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, const struct v4l2_subdev_ops *ops)

/*******************************SPI subdev************************************************///初始化v4l2_subdev, 该subdev是SPI设备void v4l2_spi_subdev_init(struct v4l2_subdev *sd, struct spi_device *spi, const struct v4l2_subdev_ops *ops)



Camera驱动分析

Linux版本:4.19

Sensor: OV13850


(1)装载和卸载函数

//DTS匹配表static const struct of_device_id ov13850_of_match[] = {  {.compatible = "omnivision,ov13850-v4l2-i2c-subdev"},  {},};
MODULE_DEVICE_TABLE(i2c, ov13850_id);
static struct i2c_driver ov13850_i2c_driver = { .driver = { .name = ov13850_DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = ov13850_of_match }, .probe = ov13850_probe, .remove = ov13850_remove, .id_table = ov13850_id,};
module_i2c_driver(ov13850_i2c_driver);

OV13850是使用I2C接口进行控制,所以使用i2c_driver进行注册。



(2)probe()

static int ov13850_probe(struct i2c_client *client,  const struct i2c_device_id *id){  dev_info(&client->dev, "probing...\n");
ov13850_filltimings(&ov13850_custom_config); //填充时序信息 v4l2_i2c_subdev_init(&ov13850.sd, client, &ov13850_camera_module_ops); //初始化v4l2_subdev ov13850.sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ov13850.custom = ov13850_custom_config;
mutex_init(&ov13850.lock); dev_info(&client->dev, "probing successful\n"); return 0;}

上面主要是根据全局变量ov13850_custom_config中的信息填充时序信息。然后初始化v4l2_subdev, ov13850是I2C接口,所以使用v4l2_i2c_subdev_init 进行初始化。v4l2_i2c_subdev_init就是对v4l2_subdev_init的封装。


//v4l2_subdev_opsstatic struct v4l2_subdev_ops ov13850_camera_module_ops = {  .core = &ov13850_camera_module_core_ops,    //核心操作  .video = &ov13850_camera_module_video_ops,  //video操作  .pad = &ov13850_camera_module_pad_ops};
static struct ov_camera_module_custom_config ov13850_custom_config = { .start_streaming = ov13850_start_streaming, //sensor开始输出数据流 .stop_streaming = ov13850_stop_streaming, //sensor停止输出数据流 .s_ctrl = ov13850_s_ctrl, .s_ext_ctrls = ov13850_s_ext_ctrls, //sensor控制(设置自动曝光控制) .g_ctrl = ov13850_g_ctrl, .g_timings = ov13850_g_timings, //获取sensor时序 .check_camera_id = ov13850_check_camera_id, //读取Sensor ID .s_vts = ov13850_auto_adjust_fps, //自动调节刷新率 .set_flip = ov13850_set_flip, //设置sensor镜像#ifdef OV13850_ONE_LANE .configs = ov13850_onelane_configs, //单lane的配置信息(分辨率,刷新率等) .num_configs = ARRAY_SIZE(ov13850_onelane_configs),
#else .configs = ov13850_configs, //多lane的配置信息 .num_configs = ARRAY_SIZE(ov13850_configs),#endif .power_up_delays_ms = {5, 20, 0}, /* *0: Exposure time valid fileds; 曝光时间 *1: Exposure gain valid fileds; 曝光增益 *(2 fileds == 1 frames) */ .exposure_valid_frame = {4, 4}};

上面设置的回调基本都是去设置寄存器。



(3)打开数据流

static int ov13850_start_streaming(struct ov_camera_module *cam_mod){  int ret = 0;
ov_camera_module_pr_debug(cam_mod, "active config=%s\n", cam_mod->active_config->name);
ret = ov13850_g_VTS(cam_mod, &cam_mod->vts_min); if (IS_ERR_VALUE(ret)) goto err;
mutex_lock(&cam_mod->lock); ret = ov_camera_module_write_reg(cam_mod, 0x0100, 1); //写0x0100寄存器, 选择streaming模式 0:standby 1:streaming mutex_unlock(&cam_mod->lock); if (IS_ERR_VALUE(ret)) goto err;
msleep(25);
return 0;err: ov_camera_module_pr_err(cam_mod, "failed with error (%d)\n", ret); return ret;}

主要就是操作寄存器,开启数据流传输。其他的一些操作函数也基本类似。



总结

    我们从上面的内容中可以看出,sensor端的驱动没有特别复杂,主要是一些参数和控制相关的内容。sensor主要是生产数据,而数据的处理主要交给ISP。


end


一口Linux 


关注,回复【1024】海量Linux资料赠送

精彩文章合集


文章推荐

【专辑】ARM
【专辑】粉丝问答
【专辑】所有原创
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图
一口Linux 写点代码,写点人生!
评论
  • 在当前人工智能(AI)与物联网(IoT)的快速发展趋势下,各行各业的数字转型与自动化进程正以惊人的速度持续进行。如今企业在设计与营运技术系统时所面临的挑战不仅是技术本身,更包含硬件设施、第三方软件及配件等复杂的外部因素。然而这些系统往往讲究更精密的设计与高稳定性,哪怕是任何一个小小的问题,都可能对整体业务运作造成严重影响。 POS应用环境与客户需求以本次分享的客户个案为例,该客户是一家全球领先的信息技术服务与数字解决方案提供商,遭遇到一个由他们所开发的POS机(Point of Sal
    百佳泰测试实验室 2025-01-09 17:35 88浏览
  • 职场是人生的重要战场,既是谋生之地,也是实现个人价值的平台。然而,有些思维方式却会悄无声息地拖住你的后腿,让你原地踏步甚至退步。今天,我们就来聊聊职场中最忌讳的五种思维方式,看看自己有没有中招。1. 固步自封的思维在职场中,最可怕的事情莫过于自满于现状,拒绝学习和改变。世界在不断变化,行业的趋势、技术的革新都在要求我们与时俱进。如果你总觉得自己的方法最优,或者害怕尝试新事物,那就很容易被淘汰。与其等待机会找上门,不如主动出击,保持学习和探索的心态。加入优思学院,可以帮助你快速提升自己,与行业前沿
    优思学院 2025-01-09 15:48 77浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 82浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 112浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 68浏览
  • HDMI 2.2 规格将至,开启视听新境界2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新HDMI规范为规模庞大的 HDMI 生态系统带来更多选择,为创建、分发和体验理想的终端用户效果提供更先进的解决方案。新技术为电视、电影和游戏工作室等内容制作商在当前和未来提供更高质量的选择,同时实现多种分发平台。96Gbps的更高带宽和新一代 HDMI 固定比率速率传输(Fixed Rate Link)技术为各种设备应用提供更优质的音频和视频。终端用户显示器能以最
    百佳泰测试实验室 2025-01-09 17:33 96浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 102浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 74浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 87浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 96浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 88浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 82浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦