Camera | 5.Linux v4l2架构(基于rk3568)

Posted 一口Linux

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Camera | 5.Linux v4l2架构(基于rk3568)相关的知识,希望对你有一定的参考价值。

上一篇我们讲解了如何编写基于V4L2的应用程序编写,本文主要讲解内核中V4L2架构,以及一些最重要的结构体、注册函数。

厂家在实现自己的摄像头控制器驱动时,总体上都遵循这个架构来实现,但是不同厂家、不同型号的SoC,具体的驱动实现仍然会有一些差别。

读者可以通过本文了解各个结构体与对应的摄像头模块、SoC上控制器模块、以及他们之间接口关系,并能够了解这些硬件模块与V4L2架构之间关系。

下一张我们基于瑞芯微rk3568来详细讲解具体V4L2的实现。

一、V4L2架构

V4L2子系统是Linux内核中关于Video(视频)设备的API接口,是V4L(Video for Linux)子系统的升级版本。

V4L(Video for Linux)是Linux内核中关于视频设备的API接口,出现于Linux内核2.1版本,经过修改bug和添加功能,Linux内核2.5版本推出了V4L2(Video for Linux Two)子系统,功能更多且更稳定。

V4L2子系统向上为虚拟文件系统提供了统一的接口,应用程序可通过虚拟文件系统访问Video设备。

V4L2子系统向下给Video设备提供接口,同时管理所有Video设备。

二、V4L2架构包括哪些设备

  1. Video设备又分为主设备和从设备对于Camera来说,
    主设备:
    Camera Host控制器为主设备,负责图像数据的接收和传输,
    从设备:
    从设备为Camera Sensor,一般为I2C接口,可通过从设备控制Camera采集图像的行为,如图像的大小、图像的FPS等。

  2. V4L2的主设备号是81,次设备号范围0~255
    这些次设备号又分为多类设备:

  • 视频设备(次设备号范围0-63)
  • Radio(收音机)设备(次设备号范围64-127)
  • Teletext设备(次设备号范围192-223)
  • VBI设备(次设备号范围224-255)。
  1. V4L2设备对应的设备节点有**/dev/videoX、/dev/vbiX、/dev/radioX**。
    本文只讨论视频设备,视频设备对应的设备节点是**/dev/videoX**,视频设备以高频摄像头或Camera为输入源,Linux内核驱动该类设备,接收相应的视频信息并处理。

V4L2框架的架构如下图所示:

  • user space:
    应用程序主要通过libv4l库来操作摄像头
    也可以基于字符设备/dev/videoX自己编写应用程序
    guvcview:用于调试usb摄像头(还有个软件cheese也可以)
    v4l2 utilities: v4l2 的工具集(参考前面第3篇文章)

  • kernel space:
    sensor、ISP、VIPP、CSI、CCI都为从设备
    从dphy物理层获取视频数据册通过vb2子模块
    CCI :主要是通过GPIO(供电、片选)、I2C(下发配置命令给sensor)实现配置sensor
    EHCI/OHCI:USB类型摄像头

  • hardware
    CSIC Controller:从dphy获取mipi协议帧
    I2C Controller:与sensor的i2c block通信
    GPIO Controller:sensor通常需要供电或者片选

  • external device
    sensror:摄像头的接口主要有:USB,DVP.MIPI(CSI)

三、Linux内核中V4L2驱动代码

Linux系统中视频输入设备主要包括以下四个部分:

  • 1.字符设备驱动:
    V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;

  • 2.V4L2驱动核心:
    主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;

  • 3.平台V4L2设备驱动:
    在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_device;

  • 4.具体的sensor驱动:
    主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。

V4L2核心源码位于drivers/media/v4l2-core,根据功能可以划分为四类:

由上图可知:

  • 1.字符设备模块:
    由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数。

  • 2.V4L2基础框架:
    由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件构建V4L2基础框架。

  • 3.videobuf管理
    由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。

  • 4.Ioctl框架:
    由v4l2-ioctl.c文件实现,构建V4L2 ioctl的框架。

瑞芯微平台还包括ISP的驱动框架,下面是rk3568对应的ISP相关代码:
Linux Kernel-4.19
	|-- arch/arm/boot/dts DTS配置文件
	|-- drivers/phy/rockchip
	|-- phy-rockchip-mipi-rx.c mipi dphy驱动
	|-- phy-rockchip-csi2-dphy-common.h
	|-- phy-rockchip-csi2-dphy-hw.c
	|-- phy-rockchip-csi2-dphy.c
	|-- drivers/media
		|-- v4l2-core
		|-- platform/rockchip/cif RKCIF驱动
		|-- platform/rockchip/isp RKISP驱动
			|-- dev.c 包含 probe、异步注册、clock、pipeline、 iommu及media/v4l2 framework
			|-- capture_v21.c 包含 mp/sp/rawwr的配置及 vb2,帧中断处理
			|-- dmarx.c 包含 rawrd的配置及 vb2,帧中断处理
			|-- isp_params.c 3A相关参数设置
			|-- isp_stats.c 3A相关统计
			|-- isp_mipi_luma.c mipi数据亮度统计
			|-- regs.c 寄存器相关的读写操作
			|-- rkisp.c isp subdev和entity注册,包含从 mipi 接收数据,并有 crop 功能
			|-- csi.c csi subdev和mipi配置
			|-- bridge.c bridge subdev,isp和ispp交互桥梁
		|-- platform/rockchip/ispp rkispp驱动
			|-- dev.c 包含 probe、异步注册、clock、pipeline、 iommu及media/v4l2 framework
			|-- stream.c 包含 4路video输出的配置及 vb2,帧中断处理
			|-- rkispp.c ispp subdev和entity注册
			|-- params.c TNR/NR/SHP/FEC/ORB参数设置
			|-- stats.c ORB统计信息
		|-- i2c
			|-- ov13850.c CIS(cmos image sensor)驱动

四、结构体详解

V4L2中有几个最重要的几个结构体,v4l2_device、video_device、v4l2_subdev等。
他们大致关系如下:

1.v4l2_device主设备

V4L2主设备实例使用struct v4l2_device结构体表示,v4l2_device是V4L2子系统的入口,管理着V4L2子系统的主设备和从设备;

v4l2_device用来描述一个v4l2设备实例,可以包含多个子设备,对应的是例如 I2C、CSI、MIPI 等设备,它们是从属于一个 V4L2 device 之下的;

简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体嵌入到一个更大的结构体中以提供v4l2框架的功能,比如struct isp_device

需要与媒体框架整合的驱动必须手动设置dev->driver_data,指向包含v4l2_device结构体实例的驱动特定设备结构体。这可以在注册V4L2设备实例前通过dev_set_drvdata()函数完成。

同时必须设置v4l2_device结构体的mdev域,指向适当的初始化并注册过的media_device实例。

 [include/media/v4l2-device.h]
    struct v4l2_device 
        struct device *dev;  // 父设备指针
    #if defined(CONFIG_MEDIA_CONTROLLER)  // 多媒体设备配置选项
        // 用于运行时数据流的管理,
        struct media_device *mdev;
    #endif
        // 注册的子设备的v4l2_subdev结构体都挂载此链表中
        struct list_head subdevs;
        // 同步用的自旋锁
        spinlock_t lock;
        // 独一无二的设备名称,默认使用driver name + bus ID
        char name[V4L2_DEVICE_NAME_SIZE];
        // 被一些子设备回调的通知函数,但这个设置与子设备相关。子设备支持的任何通知必须在
        // include/media/<subdevice>.h 中定义一个消息头。
        void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);
        // 提供子设备(主要是video和ISP设备)在用户空间的特效操作接口,
        // 比如改变输出图像的亮度、对比度、饱和度等等
        struct v4l2_ctrl_handler *ctrl_handler;
        // 设备优先级状态
        struct v4l2_prio_state prio;
        /* BKL replacement mutex. Temporary solution only. */
        struct mutex ioctl_lock;
        // struct v4l2_device结构体的引用计数,等于0时才释放
        struct kref ref;
        // 引用计数ref为0时,调用release函数进行释放资源和清理工作
        void (*release)(struct v4l2_device *v4l2_dev);
    ;

注册函数:

v4l2_device_register

使用v4l2_device_register注册v4l2_device结构体.如果v4l2_dev->name为空,则它将被设置为从dev中衍生出的值(为了更加精确,形式为驱动名后跟bus_id)。

如果在调用v4l2_device_register前已经设置好了,则不会被修改。如果dev为NULL,则必须在调用v4l2_device_register前设置v4l2_dev->name。可以基于驱动名和驱动的全局atomic_t类型的实例编号,通过v4l2_device_set_name()设置name。

这样会生成类似ivtv0、ivtv1等名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:cx18-0、cx18-1等。

dev参数通常是一个指向pci_dev、usb_interface或platform_device的指针,很少使其为NULL,除非是一个ISA设备或者当一个设备创建了多个PCI设备,使得v4l2_dev无法与一个特定的父设备关联。

使用v4l2_device_unregister卸载v4l2_device结构体。如果dev->driver_data域指向 v4l2_dev,将会被重置为NULL。主设备注销的同时也会自动注销所有子设备。如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。

由于v4l2_device有一个指向父设备的指针必须被清除,同时标志父设备已消失,所以必须调用v4l2_device_disconnect函数清理v4l2_device中指向父设备的dev指针。v4l2_device_disconnect并不注销主设备,因此依然要调用v4l2_device_unregister函数注销主设备。

[include/media/v4l2-device.h]
    // 注册v4l2_device结构体,并初始化v4l2_device结构体
    // dev-父设备结构体指针,若为NULL,在注册之前设备名称name必须被设置,
    // v4l2_dev-v4l2_device结构体指针
    // 返回值-0成功,小于0-失败
    int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)

    // 卸载注册的v4l2_device结构体
    // v4l2_dev-v4l2_device结构体指针
    void v4l2_device_unregister(struct v4l2_device *v4l2_dev)

    // 设置设备名称,填充v4l2_device结构体中的name成员
    // v4l2_dev-v4l2_device结构体指针
    // basename-设备名称基本字符串
    // instance-设备计数,调用v4l2_device_set_name后会自加1
    // 返回值-返回设备计数自加1的值
    int v4l2_device_set_name(struct v4l2_device *v4l2_dev, 
            const char *basename, atomic_t *instance)

    // 热插拔设备断开时调用此函数
    // v4l2_dev-v4l2_device结构体指针
    void v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
同一个硬件的情况下。如ivtvfb驱动是一个使用ivtv硬件的帧缓冲驱动,同时alsa驱动也使用此硬件。可以使用如下例程遍历所有注册的设备:
    static int callback(struct device *dev, void *p)
    
        struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);

        /* 测试这个设备是否已经初始化 */
        if (v4l2_dev == NULL)
            return 0;
        ...
        return 0;
    

    int iterate(void *p)
    
        struct device_driver *drv;
        int err;

        /* 在PCI 总线上查找ivtv驱动。
        pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */
        drv = driver_find("ivtv", &pci_bus_type);
        /* 遍历所有的ivtv设备实例 */
        err = driver_for_each_device(drv, NULL, p, callback);
        put_driver(drv);
        return err;
    

2. video_device

V4L2子系统使用v4l2_device结构体管理设备,设备的具体操作方法根据设备类型决定,

前面说过管理的设备分为很多种,

若是视频设备,则需要注册video_device结构体,并提供相应的操作方法。

对于视频设备Camera而言,Camera控制器可以视为主设备,接在Camera控制器上的摄像头可以视为从设备

struct video_device

	const struct v4l2_file_operations *fops;
	struct cdev *cdev;     //vdev->cdev->ops = &v4l2_fops;  字符设备描述符
	struct v4l2_device *v4l2_dev;
	struct v4l2_ctrl_handler *ctrl_handler;

	struct vb2_queue *queue;  //q->ops = &dmarx_vb2_ops; buf操作真正驱动回调函数
  …………
	const struct v4l2_ioctl_ops *ioctl_ops;//vdev->ioctl_ops = &rkisp_dmarx_ioctl; 
  …………
;

注册函数:

[rk_android11.0_sdk_220718\\kernel\\drivers\\media\\v4l2-core\\v4l2-dev.c]


static inline int __must_check video_register_device(struct video_device *vdev,
        int type, int nr)

    return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);

int __video_register_device(struct video_device *vdev, int type, int nr,
        int warn_if_nr_in_use, struct module *owner)

    ····
    int minor_cnt = VIDEO_NUM_DEVICES;//次设备个数默认为256
    const char *name_base;

    /* A minor value of -1 marks this video device as never
       having been registered */
    vdev->minor = -1;

    /* the release callback MUST be present 如果之前没有声明销毁函数,则报错*/
    if (WARN_ON(!vdev->release))
        return -EINVAL;
    /* the v4l2_dev pointer MUST be present 如果之前未注册v4l2_device则报错*/
    if (WARN_ON(!vdev->v4l2_dev))
        return -EINVAL;

    /* Part 1: check device type */
    switch (type) 
    //根据设备类型类注册设备,摄像头设备为VFL_TYPE_GRABBER类型
    case VFL_TYPE_GRABBER:
        name_base = "video";
    ·····
    ·····
    vdev->vfl_type = type;
    vdev->cdev = NULL;
    if (vdev->dev_parent == NULL)
        vdev->dev_parent = vdev->v4l2_dev->dev;
    if (vdev->ctrl_handler == NULL)
        //设置video_device的ctrl_handler,存在v4l2_device结构体中
        vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
    /* Part 2: find a free minor, device node number and device index. */
    /*2.寻找空闲次设备号,设备个数和设备下标*/

    /* Pick a device node number 寻找一个空项位置*/
    mutex_lock(&videodev_lock);
    nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
    //
    if (nr == minor_cnt)
        nr = devnode_find(vdev, 0, minor_cnt);
    if (nr == minor_cnt) 
        printk(KERN_ERR "could not get a free device node number\\n");
        mutex_unlock(&videodev_lock);
        return -ENFILE;
    
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* 1-on-1 mapping of device node number to minor number */
    i = nr;
#else
    /* The device node number and minor numbers are independent, so
       we just find the first free minor number. */
    for (i = 0; i < VIDEO_NUM_DEVICES; i++)
        if (video_device[i] == NULL)
            break;
    if (i == VIDEO_NUM_DEVICES) 
        mutex_unlock(&videodev_lock);
        printk(KERN_ERR "could not get a free minor\\n");
        return -ENFILE;
    
#endif
    vdev->minor = i + minor_offset;
    vdev->num = nr;
    devnode_set(vdev);

    /* Should not happen since we thought this minor was free */

    vdev->index = get_index(vdev);
    video_device[vdev->minor] = vdev;

    if (vdev->ioctl_ops)
        determine_valid_ioctls(vdev);

    /* Part 3: Initialize the character device */
    vdev->cdev = cdev_alloc();
    if (vdev->cdev == NULL) 
        ret = -ENOMEM;
        goto cleanup;
    
    vdev->cdev->ops = &v4l2_fops;//设置字符设备的系统调用函数
    vdev->cdev->owner = owner;

    //注册字符设备
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

    /* Part 4: register the device with sysfs */
    vdev->dev.class = &video_class;
    vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
    vdev->dev.parent = vdev->dev_parent;
    //设置video结点名称,如果设备类型为VFL_TYPE_GRABBER,名称为videoX
    dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);

    //注册device文件,生成设备文件/dev/videoX
    ret = device_register(&vdev->dev);
    /* Register the release callback that will be called when the last
       reference to the device goes away. */
    //设置销毁video设备的回调函数
    vdev->dev.release = v4l2_device_release;

    /* Increase v4l2_device refcount */
    v4l2_device_get(vdev->v4l2_dev);

这个函数主要做四件事:

  1. 检查设备类型,赋予设备名称
  2. 寻找一个空闲的设备位置,寻找合适的主设备号和次设号
  3. 初始化字符设备,使用v4l2_device的v4l2_fops初始化video_device的fops,release函数等
  4. 注册字符设备,并生成/dev/videoX结点,注册subdev时也会调用这个接口

3. v4l2_subdev从设备

V4L2从设备使用struct v4l2_subdev结构体表示,该结构体用于对子设备进行抽象。

几乎所有的设备都有多个 IC 模块

  • 它们可能是实体的(例如 USB 摄像头里面包含 ISP、sensor 等)
  • 也可能是抽象的(如 USB 设备里面的抽象拓扑结构)
  • 它们在 /dev 目录下面生成了多个设备节点,并且这些 IC 模块还创建了一些非 v4l2 设备:DVB、ALSA、FB、I2C 和输入设备。

通常情况下,这些IC模块通过一个或者多个 I2C 总线连接到主桥驱动上面,同时其它的总线仍然可用,这些 IC 就称为 ‘sub-devices’

一个V4L2主设备可能对应多个V4L2从设备,所有主设备对应的从设备都挂到v4l2_device结构体的subdevs链表中。

对于视频设备,从设备就是摄像头,通常情况下是I2C设备,主设备可通过I2C总线控制从设备

例如控制摄像头的焦距、闪光灯等,同时使用 MIPI 或者 LVDS 等接口进行图像数据传输。

struct v4l2_subdev中包含的struct v4l2_subdev_ops是一个完备的操作函数集,用于对接各种不同的子设备,比如video、audio、sensor等;

同时还有一个核心的函数集struct v4l2_subdev_core_ops,提供更通用的功能。
子设备驱动根据设备特点实现该函数集中的某些函数即可。

 [include/media/v4l2-subdev.h]
    #define V4L2_SUBDEV_FL_IS_I2C        (1U << 0)  // 从设备是I2C设备
    #define V4L2_SUBDEV_FL_IS_SPI        (1U << 1)  // 从设备是SPI设备
    #define V4L2_SUBDEV_FL_HAS_DEVNODE    (1U << 2)  // 从设备需要设备节点
    #define V4L2_SUBDEV_FL_HAS_EVENTS    (1U << 3)  // 从设备会产生事件

    struct v4l2_subdev 
    #if defined(CONFIG_MEDIA_CONTROLLER)  // 多媒体配置选项
        struct media_entity entity;
    #endif
        struct list_head list;  // 子设备串联链表
        struct module *owner;  // 属于那个模块,一般指向i2c_lient驱动模块
        bool owner_v4l2_dev;
        // 标志位,确定该设备属于那种设备,由V4L2_SUBDEV_FL_IS_XX宏确定
        u32 flags;
        // 指向主设备的v4l2_device结构体
        struct v4l2_device *v4l2_dev;
        // v4l2子设备的操作函数集合
        const struct v4l2_subdev_ops *ops;
        // 提供给v4l2框架的操作函数,只有v4l2框架会调用,驱动不使用
        const struct v4l2_subdev_internal_ops *internal_ops;
        // 从设备的控制接口
        struct v4l2_ctrl_handler *ctrl_handler;
        // 从设备的名称,必须独一无二
        char name[V4L2_SUBDEV_NAME_SIZE];
        // 从设备组的ID,由驱动定义,相似的从设备可以编为一组,
        u32 grp_id;
        // 从设备私有数据指针,一般指向i2c_client的设备结构体dev
        void *dev_priv;
        // 主设备私有数据指针,一般指向v4l2_device嵌入的结构体
        void *host_priv;
        // 指向video设备结构体
        struct video_device *devnode;
        // 指向物理设备
        struct device *dev;
        // 将所有从设备连接到全局subdev_list链表或notifier->done链表
        struct list_head async_list;
        // 指向struct v4l2_async_subdev,用于异步事件
        struct v4l2_async_subdev *asd;
        // 指向管理的notifier,用于主设备和从设备的异步关联
        struct v4l2_async_notifier *notifier;
        /* common part of subdevice platform data */
        struct v4l2_subdev_platform_data *pdata;
    ;
    // 提供给v4l2框架的操作函数,只有v4l2框架会调用,驱动不使用
    struct v4l2_subdev_internal_ops 
        // v4l2_subdev注册时回调此函数,使v4l2_dev指向主设备的v4l2_device结构体
        int (*registered)(struct v4l2_subdev *sd);
        // v4l2_subdev卸载时回调此函数
        void (*unregistered)(struct v4l2_subdev *sd);
        // 应用调用open打开从设备节点时调用此函数
        int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
        // 应用调用close时调用此函数
        int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
    ;

使用v4l2_subdev_init初始化v4l2_subdev结构体。然后必须用一个唯一的名字初始化subdev->name,同时初始化模块的owner域。

若从设备是I2C设备,则可使用v4l2_i2c_subdev_init函数进行初始化,该函数内部会调用v4l2_subdev_init,同时设置flags、owner、dev、name等成员。

 [include/media/v4l2-subdev.h]
    // 初始化v4l2_subdev结构体
    // ops-v4l2子设备的操作函数集合指针,保存到v4l2_subdev结构体的ops成员中
    void v4l2_subdev_init(struct v4l2_subdev *sd,
            const struct v4l2_subdev_ops *ops);

    [include/media/v4l2-common.h]
    // 初始化V4L2从设备为I2C设备的v4l2_subdev结构体
    // sd-v4l2_subdev结构体指针
    // client-i2c_client结构体指针
    // ops-v4l2子设备的操作函数集合指针,保存到v4l2_subdev结构体的ops成员中
    void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, 
        struct i2c_client *client,
        const struct v4l2_subdev_ops *ops);

从设备必须向V4L2子系统注册v4l2_subdev结构体,使用v4l2_device_register_subdev注册,使用v4l2_device_unregister_subdev注销。

[include/media/v4l2-device.h]
    // 向V4L2子系统注册v4l2_subdev结构体
    // v4l2_dev-主设备v4l2_device结构体指针
    // sd-从设备v4l2_subdev结构体指针
    // 返回值 0-成功,小于0-失败
    int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
                    struct v4l2_subdev *sd)

    // 从V4L2子系统注销v4l2_subdev结构体
    // sd-从设备v4l2_subdev结构体指针    
    void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);

V4L2从设备驱动都必须有一个v4l2_subdev结构体。
这个结构体可以单独代表一个简单的从设备,也可以嵌入到一个更大的结构体中,与更多设备状态信息保存在一起。通常有一个下级设备结构体(比如:i2c_client)包含了内核创建的设备数据。

建议使用v4l2_set_subdevdata()将这个结构体的指针保存在v4l2_subdev的私有数据域(dev_priv)中。可以更方便的通过v4l2_subdev找到实际的低层总线特定设备数据。

对于常用的i2c_client结构体,i2c_set_clientdata函数可用于保存一个v4l2_subdev指针,i2c_get_clientdata可以获取一个v4l2_subdev指针;对于其他总线可能需要使用其他相关函数。

[include/media/v4l2-subdev.h]
    // 将i2c_client的指针保存到v4l2_subdev结构体的dev_priv成员中
    static inline void v4l2_set_subdevdata(struct v4l2_subdev *sd, void *p)
    
        sd->dev_priv = p;
    

    [include/linux/i2c.h]
    // 可以将v4l2_subdev结构体指针保存到i2c_client中dev成员的driver_data中
    static inline void i2c_set_clientdata(struct i2c_client *dev, void *data)
    
        dev_set_drvdata(&dev->dev, data);
    
    // 获取i2c_client结构体中dev成员的driver_data,一般指向v4l2_subdev
    static inline void *i2c_get_clientdata(const struct i2c_client *dev)
    
        return dev_get_drvdata(&dev->dev);
    

主设备驱动中也应保存每个子设备的私有数据,比如一个指向特定主设备的各设备私有数据的指针。为此v4l2_subdev结构体提供主设备

以上是关于Camera | 5.Linux v4l2架构(基于rk3568)的主要内容,如果未能解决你的问题,请参考以下文章

v4l2 Camera详细设置

Camera | 4.瑞芯微平台MIPI摄像头应用程序编写

Camera[1] 驱动V4L2分析

android camera V4L2 FIMC

android camera V4L2 FIMC

camera驱动