V4L2源代码之旅五:V4L2的起点和终点

转自:http://www.cnblogs.com/ronnydm/p/5768899.html

一. 思路  

  不管V4L2如何封装,始终是一个Driver,那么Driver必然会提供对User空间的file_operations,所以file_operations是线索起点。
  终点就是V4L2可以正确调用到我们注册的关于Sensor和ISP的操作接口.

二. 起点

复制代码
/* linux-3.08/drivers/media/video/v4l2-dev.c */
static int __init videodev_init(void)
{
    dev_t dev = MKDEV(VIDEO_MAJOR, 0);  
  /*
   * #define VIDEO_MAJOR    81 【linux-3.08/include/media/v4l2-dev.h】
   * 所以V4L2的主设备号是81,次设备号从0开始.
   */
int ret; printk(KERN_INFO "Linux video capture interface: v2.00\n"); ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
  /*
   * #define VIDEO_NUM_DEVICES    256
   * #define VIDEO_NAME              "video4linux"
   * register_chrdev_region:分配主设备号.第二个参数是:请求的连续设备编号的个数(count)。那么是否意味着从81开始的256个编号都是V4L2的主设备呢?ofcause not!!
* 在《Linux设备驱动程序》一书中,关于设备编号有这么一句话:如果count非常大,则所请求的范围可能和下一个主设备重叠,但只要我们所请求的编号范围是可用的,则不会带来任何问题。
* 所以,V4L2支持的最多的主设备是256,如果没有256,那么在81到256这个范围的主设备号是可能(其实是大多数都分配给其他设备了)分配给其他设备,并不会有任何问题.
   * 第三个参数:和该设备号范围关联的设备名称,将出现在/proc/devices和sysfs中。

  */
if (ret < 0) { printk(KERN_WARNING "videodev: unable to get major %d\n", VIDEO_MAJOR); return ret; } ret = class_register(&video_class); // sys filesystem,暂时不care if (ret < 0) { unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); printk(KERN_WARNING "video_dev: class_register failed\n"); return -EIO; } return 0; } static void __exit videodev_exit(void) { dev_t dev = MKDEV(VIDEO_MAJOR, 0); class_unregister(&video_class); unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); } module_init(videodev_init) module_exit(videodev_exit) MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab <mchehab@infradead.org>"); MODULE_DESCRIPTION("Device registrar for Video4Linux drivers v2"); MODULE_LICENSE("GPL"); MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR);
复制代码

  此时此刻,我们发现V4L2的module_init就分配了个设备号,并没有注册字符设备,也并没有向User空间提供file_operation。但是我们又会发现在v4l2-dev.c中的__video_register_device函数中,注册了file_operation,而且函数名字也起的特别有意思:register_device。可以猜测:驱动会调用register_device来注册设备,然后由v4l2-dev.c统一管理。

  所以此时,再从入口追下去意义不大。我们开始看看一个device如何告诉V4L2的,并且交给V4L2来管理的。

三.终点

  说明:下文中我贴的代码,有部分不是Linux官方源码,是我们的系统驱动代码。我们的系统Sensor是OV8858,ISP是OVISP2.2.2。

        其实,看看Linux提供的一个虚拟设备VIVI也是不错的选择。

  我们的OVISP基于Platform总线的,所以必然Driver和Device分离,那么Device相关的设置在linux-3.08/arch/目录下,而Driver在linux-3.08/driver/目录下。

  所以我们先看看Device相关的设置,Device相关的设置是几个比较重要的结构体,注册到Platform总线中,Driver中操作Device。

复制代码
/* linux-3.08/arch/mips/xburst/soc-m200/common/platform.c */
static u64 ovisp_camera_dma_mask = ~(u64)0;
static struct resource ovisp_resource_camera[] = {
    [0] = {
        .start = ISP_IOBASE,
        .end = ISP_IOBASE + 0x50000,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_ISP,
        .end = IRQ_ISP,
        .flags = IORESOURCE_IRQ,
    },
};

struct platform_device ovisp_device_camera = {
    .name = "ovisp-camera",
    .id = -1,
    .dev = {
        .dma_mask = &ovisp_camera_dma_mask,
        .coherent_dma_mask = 0xffffffff,
    },
    .num_resources = ARRAY_SIZE(ovisp_resource_camera),
    .resource = ovisp_resource_camera,
};
复制代码
复制代码
/* linux-3.08/arch/mips/xburst/soc-m200/chip-m200/glass/common/camera.c */
static struct ovisp_camera_client ovisp_camera_clients[] = {

#ifdef CONFIG_M200_BACK_CAMERA
    {
        .board_info = &back_camera_board_info,
        .flags = CAMERA_CLIENT_IF_MIPI
        | CAMERA_CLIENT_POWER_SUPPLY_ALWAYS,
        .mclk_rate = 26000000,
        .power = back_camera_power,
        .reset = back_camera_reset,
    },
#endif /* CONFIG_M200_BACK_CAMERA */

#ifdef CONFIG_M200_FRONT_CAMERA
    {
        .board_info = &front_camera_board_info,

        .flags = CAMERA_CLIENT_IF_MIPI
        | CAMERA_CLIENT_CLK_EXT
        | CAMERA_CLIENT_ISP_BYPASS,
        .mclk_rate = 26000000,
        .power = front_camera_power,
        .reset = front_camera_reset,
    },
#endif /* CONFIG_M200_FRONT_CAMERA */

};

struct ovisp_camera_platform_data ovisp_camera_info = {
#ifdef CONFIG_OVISP_I2C
    .i2c_adapter_id = 8, /* larger than host i2c nums */
    .flags = CAMERA_USE_ISP_I2C | CAMERA_USE_HIGH_BYTE
    | CAMERA_I2C_PIO_MODE | CAMERA_I2C_STANDARD_SPEED,
#else
    .i2c_adapter_id = 3, /* use cpu's i2c adapter */
    .flags = CAMERA_USE_HIGH_BYTE
    | CAMERA_I2C_PIO_MODE | CAMERA_I2C_STANDARD_SPEED,
#endif
    .client = ovisp_camera_clients,
    .client_num = ARRAY_SIZE(ovisp_camera_clients),
};
复制代码

  ovisp_device_camera是ISP相关;ovisp_camera_info是Sensor相关。所以很好的设计这两个结构体是非常重要的。因为一个平台对应一款ISP,但是一个平台必须要适配不同Sensor。这样,将ISP和Camera分离是至关重要的,分离的好处就是换一颗Sensor,那么ISP相关的配置则基本不需要改动。这里的设计思维是:ISP本质作为platform_device(ovisp_device_camera),将Sensor相关的参数(ovisp_camera_info)配置到ovisp_device_camera->dev->platform_data中,这样在加载ISP的驱动时,就可以通过平台总线获取到ovisp_device_camera,那么就可以获取到ovisp_camera->dev->platform_data,即获取到ovisp_camera_info。具体代码就不赘述了。这两个结构体会注册到Platform上,在Driver端会拿到并使用。

  我们接着看Driver端的操作:

  首先说下3个关键的文件:

  1. ov8858.c:封装Sensor相关的操作,例如初始化,stream on,stream off等功能。

  2.ovisp-isp.c:封装ISP相关操作,例如ISP的初始化,ISP功能配置等。

  3. ovisp-video.c:调用ov8858.c和ovisp-isp.c中的函数,注册到V4L2架构,实现预览,拍照,录像等功能。

复制代码
/* linux-3.08/drivers/media/video/ovisp-video.c */
static int ovisp_camera_probe(struct platform_device *pdev)  // pdev就是上边我们说的ovisp_device_camera
{
  
struct ovisp_camera_dev *camdev; struct ovisp_camera_platform_data *pdata; struct video_device *vfd; struct isp_device *isp; struct vb2_queue *q; struct resource *res; int irq; int ret; int i = 0; pdata = pdev->dev.platform_data; // pdata就是上边说的ovisp_camera_info if (!pdata) { ISP_PRINT(ISP_ERROR,"Platform data not set\n"); return -EINVAL; } /*for csi debug*/ ret = sysfs_create_group(&pdev->dev.kobj, &mipi_csi_attr_group); if (ret < 0) goto err_sysfs_create;   /* 平台总线相关操作,你懂得! */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!res || !irq) { ISP_PRINT(ISP_ERROR,"Not enough platform resources"); return -ENODEV; } res = request_mem_region(res->start, res->end - res->start + 1, dev_name(&pdev->dev)); // 申请IO空间.mips架构对IO的读取和普通内存读取一致,并没有像X86那样有单独的IO空间和IO指令 if (!res) { ISP_PRINT(ISP_ERROR,"Not enough memory for resources\n"); return -EBUSY; } camdev = kzalloc(sizeof(*camdev), GFP_KERNEL); // 驱动开发者自己定义的结构体,是个大杂烩,在这个结构体中可以找到所有的资源 if (!camdev) { ISP_PRINT(ISP_ERROR,"Failed to allocate camera device\n"); ret = -ENOMEM; goto exit; } isp = kzalloc(sizeof(*isp), GFP_KERNEL);
  // ISP数据及操作结构体,我们将ISP的操作会单独写在另一个ovisp-isp.c中,这样ovisp-isp.c设置改结构体,ovisp-video.c使用改结构体
if (!isp) { ISP_PRINT(ISP_ERROR,"Failed to allocate isp device\n"); ret = -ENOMEM; goto free_camera_dev;; }
  /* 刚才申请的中断赋值给isp结构体,也就说中断都是和ISP相关的,也就是ISP给主CPU发送中断 */ isp
->irq = irq; isp->res = res; isp->dev = &pdev->dev; isp->pdata = pdata; isp->irq_notify = ovisp_camera_irq_notify; // 回调,本来ovisp-video.c调用ovisp-isp.c,但是isp收到中断后通知ovisp-video.c是通过改回调函数。 isp->data = camdev; ret = isp_device_init(isp); // ISP初始化 if (ret) { ISP_PRINT(ISP_ERROR,"Unable to init isp device.n"); goto free_isp_dev; } snprintf(camdev->v4l2_dev.name, sizeof(camdev->v4l2_dev.name), "%s", dev_name(&pdev->dev));
  // 关键:向V4L2注册 struct v4l2_device. ret
= v4l2_device_register(NULL, &camdev->v4l2_dev); if (ret) { ISP_PRINT(ISP_ERROR,"Failed to register v4l2 device\n"); ret = -ENOMEM; goto release_isp_dev; } spin_lock_init(&camdev->slock); camdev->isp = isp; camdev->dev = &pdev->dev; camdev->pdata = pdata; camdev->input = -1; camdev->refcnt = 0; camdev->frame.field = V4L2_FIELD_INTERLACED; camdev->first_init = 1;
  // Sensor上电
for (i = 0 ; i < pdata->client_num; i++) { ISP_PRINT(ISP_INFO,"sensor: %s\n", pdata->client[i].board_info->type); if ( !(pdata->client[i].flags & CAMERA_CLIENT_POWER_SUPPLY_ALWAYS) ) { camdev->camera_power = regulator_get(camdev->dev, "cpu_avdd"); if(IS_ERR(camdev->camera_power)) { dev_warn(camdev->dev, "camera regulator missing\n"); ret = -ENXIO; goto regulator_error; } } } #if 1
   /* 非常重要:V4L2内存相关,暂时忽略,后期会专题研究这个东西 */ /* Initialize contiguous memory allocator */ camdev->alloc_ctx = ovisp_vb2_init_ctx(camdev->dev); if (IS_ERR(camdev->alloc_ctx)) { ret = PTR_ERR(camdev->alloc_ctx); goto unreg_v4l2_dev; } #endif #ifdef OVISP_DEBUGTOOL_ENABLE camdev->offline.size = 10*1024*1024; camdev->offline.vaddr = dma_alloc_coherent(camdev->dev, camdev->offline.size, &(camdev->offline.paddr), GFP_KERNEL); ISP_PRINT(ISP_WARNING,"camdev->offline.vaddr:0x%08lx,camdev->offline.paddr:0x%08lx", (unsigned long)camdev->offline.vaddr, (unsigned long)camdev->offline.paddr); camdev->bracket.size = 10*1024*1024; camdev->bracket.vaddr = dma_alloc_coherent(camdev->dev, camdev->bracket.size, &(camdev->bracket.paddr), GFP_KERNEL); ISP_PRINT(ISP_WARNING, "camdev->bracket.vaddr:0x%08lx,camdev->bracket.paddr:0x%08lx", (unsigned long)camdev->bracket.vaddr, (unsigned long)camdev->bracket.paddr); #endif ret = ovisp_camera_init_subdev(camdev); // 这个函数也很重要,见下文 if (ret) { ISP_PRINT(ISP_ERROR,"Failed to register v4l2 device of subdev\n"); ret = -ENOMEM; goto cleanup_ctx; } /* Initialize queue. */ q = &camdev->vbq; memset(q, 0, sizeof(camdev->vbq)); q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; q->io_modes = VB2_MMAP | VB2_USERPTR; q->drv_priv = camdev; q->buf_struct_size = sizeof(struct ovisp_camera_buffer); q->ops = &ovisp_vb2_qops; q->mem_ops = &ovisp_vb2_memops; vb2_queue_init(q); // videobuf相关,暂时忽略 mutex_init(&camdev->mlock);
  // struct video_device 分配 vfd
= video_device_alloc(); if (!vfd) { ISP_PRINT(ISP_ERROR,"Failed to allocate video device\n"); ret = -ENOMEM; goto free_i2c; }
  // 设置 struct video_device
  /*
static struct video_device ovisp_camera = {
    .name = "ovisp-video-camera",
    .minor = -1,
    .release = video_device_release,
    .fops = &ovisp_v4l2_fops,
    .ioctl_ops = &ovisp_v4l2_ioctl_ops,
};
  非常关键的结构体,User空间调用的真正的终点,我们将之注册给V4L2,由它管理
  */
memcpy(vfd,
&ovisp_camera, sizeof(ovisp_camera)); vfd->lock = &camdev->mlock; vfd->v4l2_dev = &camdev->v4l2_dev; // 上边我们猜测struct属于struct video_device,看来没错 vfd->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG; //vfd->debug = V4L2_DEBUG_IOCTL; camdev->vfd = vfd; ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); // 注册struct video_device if (ret < 0) { ISP_PRINT(ISP_ERROR,"Failed to register video device\n"); goto free_video_device; } video_set_drvdata(vfd, camdev); // 将struct device和camdev关联 platform_set_drvdata(pdev, camdev); // 将pdev和camdev关联 /* init v4l2_priority */ v4l2_prio_init(&camdev->prio); // 优先级 ret = isp_debug_init(); if(ret) goto fail_debug; return 0; fail_debug: free_video_device: video_device_release(vfd); free_i2c: ovisp_camera_free_subdev(camdev); cleanup_ctx: ovisp_vb2_cleanup_ctx(camdev->alloc_ctx); for (i = 0 ; i < pdata->client_num; i++) { if ( !(pdata->client[i].flags & CAMERA_CLIENT_POWER_SUPPLY_ALWAYS) ) { regulator_error: regulator_put(camdev->camera_power); } } #if defined(CONFIG_VIDEO_OV5640) && defined(CONFIG_BOARD_MONDAY) regulator_put(pmu_dvdd); #endif unreg_v4l2_dev: v4l2_device_unregister(&camdev->v4l2_dev); release_isp_dev: isp_device_release(camdev->isp); free_isp_dev: kfree(camdev->isp); free_camera_dev: kfree(camdev); err_sysfs_create: exit: return ret; } static int __exit ovisp_camera_remove(struct platform_device *pdev) { struct ovisp_camera_dev *camdev = platform_get_drvdata(pdev); struct ovisp_camera_platform_data *pdata = camdev->pdata; int i = 0; for (i = 0; i < pdata->client_num; i++) { if ( !(pdata->client[i].flags & CAMERA_CLIENT_POWER_SUPPLY_ALWAYS) ) { regulator_put(camdev->camera_power); } } #if defined(CONFIG_VIDEO_OV5640) && defined(CONFIG_BOARD_MONDAY) regulator_put(pmu_dvdd); #endif video_device_release(camdev->vfd); v4l2_device_unregister(&camdev->v4l2_dev); platform_set_drvdata(pdev, NULL); #ifdef OVISP_DEBUGTOOL_ENABLE dma_free_coherent(camdev->dev, camdev->offline.size, camdev->offline.vaddr, (dma_addr_t)camdev->offline.paddr); #endif ovisp_vb2_cleanup_ctx(camdev->alloc_ctx); ovisp_camera_free_subdev(camdev); isp_device_release(camdev->isp); isp_debug_deinit(); kfree(camdev->isp); kfree(camdev); return 0; } #ifdef CONFIG_PM static int ovisp_camera_suspend(struct device *dev) { struct ovisp_camera_dev *camdev = dev_get_drvdata(dev); isp_dev_call(camdev->isp, suspend, NULL); return 0; } static int ovisp_camera_resume(struct device *dev) { struct ovisp_camera_dev *camdev = dev_get_drvdata(dev); isp_dev_call(camdev->isp, resume, NULL); return 0; } static struct dev_pm_ops ovisp_camera_pm_ops = { .suspend = ovisp_camera_suspend, .resume = ovisp_camera_resume, }; #endif static struct platform_driver ovisp_camera_driver = { .probe = ovisp_camera_probe, // 无疑,probe是我们看platform驱动的入口 .remove = __exit_p(ovisp_camera_remove), .driver = { .name = "ovisp-camera", .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &ovisp_camera_pm_ops, // 电源管理,在实际开发中非常重要,因为嵌入式系统对功耗非常敏感。我们的焦点在V4L2,所以不会Care. #endif }, }; static int __init ovisp_camera_init(void) { return platform_driver_register(&ovisp_camera_driver); // 基于platform总线,platform总线不是我们的重点,所以不会细说。 } static void __exit ovisp_camera_exit(void) { platform_driver_unregister(&ovisp_camera_driver); } module_init(ovisp_camera_init); module_exit(ovisp_camera_exit); MODULE_DESCRIPTION("OVISP camera driver"); MODULE_LICENSE("GPL");
复制代码
复制代码
static int ovisp_camera_init_client(struct ovisp_camera_dev *camdev,
                    struct ovisp_camera_client *client,
                    int index)
{
    struct ovisp_camera_subdev *csd = &camdev->csd[index];
    int i2c_adapter_id;
    int err = 0;
    int ret;
  i2c_adapter_id = camdev->pdata->i2c_adapter_id; // 拿到i2c_adapter_id

if (client->flags & CAMERA_CLIENT_ISP_BYPASS) // 确认Sensor是否是bypass csd->bypass = 1; else csd->bypass = 0; ret = ovisp_camera_get_mclk(camdev, client, index); // Sensor clck的配置 ret = isp_dev_call(camdev->isp, init, NULL); // 调用ISP的init函数 ret = ovisp_subdev_power_on(camdev, client, index); // Sensor 上电 csd->i2c_adap = i2c_get_adapter(i2c_adapter_id); // 根据i2c_adapter_id获取i2c_adap csd->sd = v4l2_i2c_new_subdev_board(&camdev->v4l2_dev, csd->i2c_adap, client->board_info, NULL);
  /* 这是我想说的关键:这个函数是V4L2提供的。V4L2将Sensor作为一个subdev,而且Sensor是走i2c总线,对于这种设备,
    V4L2提供了统一的接口(v4l2-common.c)来实现其初始化注册等功能。
   在下文中可以看到,在OV8858.c中,OV8858这个Sensor是一个I2C设备,何时注册这个设备?就是由该方法控制。
  */
} static int ovisp_camera_init_subdev(struct ovisp_camera_dev *camdev) { struct ovisp_camera_platform_data *pdata = camdev->pdata; unsigned int i; int ret; camdev->clients = 0; for (i = 0; i < pdata->client_num; i++) { // 一个Sensor就是一个client_num ret = ovisp_camera_init_client(camdev, &pdata->client[i], camdev->clients); camdev->csd[camdev->clients++].client = &pdata->client[i]; // 将ovisp_camera_clients放在camdev中 } return 0; }
复制代码

总结在ISP初始化过程中,V4L2相关的设置(忽略videobuf):

    ① v4l2_device_register:注册v4l2_dev

    ② v4l2_i2c_new_subdev_board:添加Sensor这个subdev

    ③ video_device_alloc:video_device的分配

    ④ video_register_device:video_device的注册

    ⑤ video_set_drvdata:将vdieo_device和camdev关联

    ⑥ v4l2_prio_init:优先级的初始化



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值