sensor的驱动

v4l2_i2c_new_subdev_board先用client = i2c_new_device(adapter, info);创建info对应的i2c_client对象(代表着一个i2c client),并进行驱动匹配。匹配就会触发i2c sensor驱动的probe调用。现在进入到目录drivers/media/i2c/soc_camera/,我们还是看OV2640驱动吧,毕竟前面的板级文件里只有在定义了CONFIG_SOC_CAMERA_OV2640宏才会编译进去。

  1. static struct i2c_driver ov2640_i2c_driver = {
  2. .driver = {
  3. .name = "ov2640",
  4. },
  5. .probe = ov2640_probe,
  6. .remove = ov2640_remove,
  7. .id_table = ov2640_id,
  8. };
  9. module_i2c_driver(ov2640_i2c_driver);

直接看ov2640_probe。这是一个i2c sensor驱动该做的事情。同样,直接将说明插入到代码中:

  1. static int ov2640_probe(struct i2c_client *client,
  2. const struct i2c_device_id *did)
  3. {
  4. struct ov2640_priv *priv;
  5. struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
  6. struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
  7. int ret;
  8. if (!ssdd) {
  9. dev_err(&adapter->dev,
  10. "OV2640: Missing platform_data for driver\n");
  11. return -EINVAL;
  12. }
  13. if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
  14. dev_err(&adapter->dev,
  15. "OV2640: I2C-Adapter doesn't support SMBUS\n");
  16. return -EIO;
  17. }
  18. priv = devm_kzalloc(&client->dev, sizeof(struct ov2640_priv), GFP_KERNEL);
  19. if (!priv) {
  20. dev_err(&adapter->dev,
  21. "Failed to allocate memory for private data!\n");
  22. return -ENOMEM;
  23. }
  24. v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops);//这里就是初始化v4l2框架提供的v4l2_subdev,前面说了i2c sensor在v4l2框架里是小弟,host才是老大,小弟用v4l2_subdev来描述,老大用v4l2_device来描述。这里仅仅是初始化,怎么和老大绑定起来的事情在前面还没讲完的soc_camera_probe里,后面会讲解
  25. //这部分就是添加该sensor支持的ioctl了,采用v4l2提供的现有机制。我们会发现小弟sensor会有自己的ctrl handler,camera也有自己的ctrl handler,sensor和camera的关系属于包含关系,一般camera都包括了sensor。camera是整体,sensor是部分
  26. v4l2_ctrl_handler_init(&priv->hdl, 2);
  27. v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops,
  28. V4L2_CID_VFLIP, 0, 1, 1, 0);
  29. v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops,
  30. V4L2_CID_HFLIP, 0, 1, 1, 0);
  31. priv->subdev.ctrl_handler = &priv->hdl;
  32. if (priv->hdl.error)
  33. return priv->hdl.error;
  34. priv->clk = v4l2_clk_get(&client->dev, "mclk");//这里就是获取之前在soc_camera_i2c_init里注册的时钟啦
  35. if (IS_ERR(priv->clk)) {
  36. ret = PTR_ERR(priv->clk);
  37. goto eclkget;
  38. }
  39. ret = ov2640_video_probe(client);//这里就是初始化sensor了,通过i2c总线进行配置,不同的sensor配置方法不一样,这就不继续分析了。不过有一点要说明,就是前面不是注册了很多ioctl么,里面都会有默认值,但是分析到现在(也就是设备启动到现在),硬件还没有初始化到默认值,于是在ov2640_video_probe里调用v4l2_ctrl_handler_setup来将所有的ioctl执行一遍,进行初始化
  40. if (ret) {
  41. v4l2_clk_put(priv->clk);
  42. eclkget:
  43. v4l2_ctrl_handler_free(&priv->hdl);
  44. } else {
  45. dev_info(&adapter->dev, "OV2640 Probed\n");
  46. }
  47. return ret;
  48. }

从这里我们可以看到ov2640_probe主要就是分配了自己的数据结构,当然里面嵌入了嵌入v4l2框架的v4l2_subdev以及自己ioctl的支持v4l2_ctrl_handler等数据结构,并对这些数据结构进行相应的初始化以及将其绑定到i2c_clientov2640_probe执行完后,我们前面的i2c_new_device就可以返回了。v4l2_i2c_new_subdev_board在执行完i2c_new_device后,用sd = i2c_get_clientdata(client);获取i2c sensor驱动里自己的数据结构里的v4l2_subdev。并调用v4l2_device_register_subdevv4l2_subdev与老大v4l2_device进行绑定。绑定的过程中,会将sensor里的所有的ioctl操作添加到icd的ctrl里去。这样icd导出给应用的设备文件的某些ioctl的操作,会引起i2c sensor的ioctl也可以得到调用到。

分析到这里,基本就结束了。总结一下,camera通过通用的camera驱动将其先放到全局的链表中,然后camera host驱动会去探测属于它的camera驱动,然后将camera添加进来。添加的过程中,会触发camera里的sensor驱动进行匹配并初始化sensor,同时会将sensor驱动里的ioctl操作集合通过v4l2提供的ctrl机制添加到camera里来,最终通过video_device字符设备导出给应用层操作。下面通过几个情景来进一步了解内部的整个流程

导出给应用层的设备文件相关信息

前文已经说过,soc_camera_probe里面会调用video_dev_create创建并初始化了video_device并通过soc_camera_probe_finish间接(调用soc_camera_video_start->video_register_device)注册了video_device,通过__video_register_device分析可以知道,设备名及设备后缀的确立是根据类别以及注册的先后顺序来的(应用层通过设备节点/dev/videoX打开video4linux devices。/dev/videoX是一个字符设备,主设备号81,次设备号: (0~63)分配给capture设备,64127分配给radio设备,223255分配给VBI设备,128~191分配给其他类型的)。需要注意里面有一句vdev->dev_parent = vdev->v4l2_dev->dev;也就是说video_device的父是v4l2_device。还需要注意的是里面有一句vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;它是不会执行的,因为在video_dev_create里已经将其初始化了(vdev->ctrl_handler = &icd->ctrl_handler;),前文也已经说明,v4l2_device的ctrl集合同时包含了i2c sensor的ctrl集合。还需要注意注册的时候操作集合设置为v4l2_fops,后面分析会用到它。

open操作流程

open的设备文件当然就是上面小节所说的设备文件。open操作最终会导向到字符设备注册时提供的ops,在这里也就是v4l2_fops。因此我们直接分析v4l2_fops里面的open吧!至于怎么导向到这里的过程,属于字符设备驱动范畴,这里不进行说明。先贴一下v4l2_open代码:

  1. static int v4l2_open(struct inode *inode, struct file *filp)
  2. {
  3. struct video_device *vdev;
  4. int ret = 0;
  5. /* Check if the video device is available */
  6. mutex_lock(&videodev_lock);
  7. vdev = video_devdata(filp);
  8. /* return ENODEV if the video device has already been removed. */
  9. if (vdev == NULL || !video_is_registered(vdev)) {
  10. mutex_unlock(&videodev_lock);
  11. return -ENODEV;
  12. }
  13. /* and increase the device refcount */
  14. video_get(vdev);
  15. mutex_unlock(&videodev_lock);
  16. if (vdev->fops->open) {
  17. if (video_is_registered(vdev))
  18. ret = vdev->fops->open(filp);
  19. else
  20. ret = -ENODEV;
  21. }
  22. if (vdev->debug)
  23. printk(KERN_DEBUG "%s: open (%d)\n",
  24. video_device_node_name(vdev), ret);
  25. /* decrease the refcount in case of an error */
  26. if (ret)
  27. video_put(vdev);
  28. return ret;
  29. }
  30. 先通过video_devdata拿到video_device对象指针。这个内部实现是通过video_device注册的时候,会根据子设备号为索引将其放入到一个全局的video_device数组中,因此v4l2_open的时候,只需要根据当前的设备文件的子设备号为索引再到video_device里取出来即可。v4l2_open的核心操作还是通过:
  31. if (vdev->fops->open) {
  32. if (video_is_registered(vdev))
  33. ret = vdev->fops->open(filp);
  34. else
  35. ret = -ENODEV;
  36. }
  37. open的操作最终导向到video_device的回调函数集合里的open中去,其实就是回调到通用设备驱动实现的open。该回调操作集合在video_dev_create里面初始化为soc_camera_fops。因此最终调用到了soc_camera_fops里面的open,即soc_camera_open。它主要做了两件事情,第一,file->private_data = icd;将icd存放file->private_data中,这样在之后的readwriteioctl时可以直接从private_dataicd了,免去了video_get_drvdata获取的麻烦。第二,这件事只在第一次open该设备文件的时候会调用,主要代码如下:
  38. /* Now we really have to activate the camera */
  39. if (icd->use_count == 1) {
  40. struct soc_camera_desc *sdesc = to_soc_camera_desc(icd);
  41. /* Restore parameters before the last close() per V4L2 API */
  42. struct v4l2_format f = {
  43. .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
  44. .fmt.pix = {
  45. .width = icd->user_width,
  46. .height = icd->user_height,
  47. .field = icd->field,
  48. .colorspace = icd->colorspace,
  49. .pixelformat =
  50. icd->current_fmt->host_fmt->fourcc,
  51. },
  52. };
  53. /* The camera could have been already on, try to reset */
  54. if (sdesc->subdev_desc.reset)
  55. sdesc->subdev_desc.reset(icd->pdev);
  56. ret = soc_camera_add_device(icd);
  57. if (ret < 0) {
  58. dev_err(icd->pdev, "Couldn't activate the camera: %d\n", ret);
  59. goto eiciadd;
  60. }
  61. ret = __soc_camera_power_on(icd);
  62. if (ret < 0)
  63. goto epower;
  64. pm_runtime_enable(&icd->vdev->dev);
  65. ret = pm_runtime_resume(&icd->vdev->dev);
  66. if (ret < 0 && ret != -ENOSYS)
  67. goto eresume;
  68. /*
  69. * Try to configure with default parameters. Notice: this is the
  70. * very first open, so, we cannot race against other calls,
  71. * apart from someone else calling open() simultaneously, but
  72. * .host_lock is protecting us against it.
  73. */
  74. ret = soc_camera_set_fmt(icd, &f);
  75. if (ret < 0)
  76. goto esfmt;
  77. if (ici->ops->init_videobuf) {
  78. ici->ops->init_videobuf(&icd->vb_vidq, icd);
  79. } else {
  80. ret = ici->ops->init_videobuf2(&icd->vb2_vidq, icd);
  81. if (ret < 0)
  82. goto einitvb;
  83. }
  84. v4l2_ctrl_handler_setup(&icd->ctrl_handler);
  85. }

看第一行注释其实就知道它是要做什么了,主要是激活设备,所谓的设备就是camera啦。首先复位设备,这个在板级相关文件里通过camera_link来实现reset的回调,可能会为NULL。其次,调用soc_camera_add_device来开启clk及host的add回调。然后调用__soc_camera_power_on来让i2c sensor控制power on,然后就是最重要的,调用ici->ops->init_videobuf或者ici->ops->init_videobuf2,即host实现的init_videobuf来初始化video buffer queue,最后调用v4l2_ctrl_handler_setup来将所有的ioctl执行一遍,进行初始化,这里用icd的ctrl集合。总的来说,open操作所做的主要是各种初始化准备工作,过程中会回调host及i2c sensor驱动实现的一些api。

close操作

close操作和open操作类似,最终会回调到soc_camera_close,它主要是做一些去初始化操作,不过因此可能多次打开了设备文件,所以它只在使用技术为0的时候彻底去初始化open所做的工作。

ioctl操作

这部分是应用操作的关键。应用层透过ioctl并基于v4l2提供的支持,来配置设备。对应到这里,就是配置sensor和camera host以及v4l2相关的东西。先从网上引入一张v4l2 ioctl相关的图片,好有个大体的了解:

下面从正常使用v4l2获取视频的流程来分析ioctl

一般在open设备文件后,我们会先获取该设备的能力集合,通过如下代码(以下所有代码仅仅是为了展示,正式代码应该要做出错检查什么的哈)

  1. Struct v4l2_capability cap;
  2. ioctl(fd, VIDIOC_QUERYCAP, &cap);

这里使用v4l2头文件里定义的命令宏VIDIOC_QUERYCAP来获取设备的能力集合。我们看看获取的过程吧!和open操作类似,ioctl操作最终会导向到字符设备注册时提供的v4l2_fops。因此我们直接分析v4l2_fops里面的v4l2_ioctl吧!同样,采用代码行内注释方式:

  1. static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  2. {
  3. struct video_device *vdev = video_devdata(filp);
  4. int ret = -ENODEV;
  5. if (vdev->fops->unlocked_ioctl) {//如果video_device的回调函数集合里有实现unlocked_ioctl,那么进入这个里面,通用驱动
  6. //soc_camera实现了unlocked_ioctl,即video_ioctl2。因此,我们会进入到这个里面
  7. struct mutex *lock = v4l2_ioctl_get_lock(vdev, cmd);
  8. if (lock && mutex_lock_interruptible(lock))
  9. return -ERESTARTSYS;
  10. if (video_is_registered(vdev))
  11. ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);//这里就是调用通用驱动soc_camera实现的video_ioctl2了,后面会详细分析这个函数
  12. if (lock)
  13. mutex_unlock(lock);
  14. } else if (vdev->fops->ioctl) {
  15. /* This code path is a replacement for the BKL. It is a major
  16. * hack but it will have to do for those drivers that are not
  17. * yet converted to use unlocked_ioctl.
  18. *
  19. * There are two options: if the driver implements struct
  20. * v4l2_device, then the lock defined there is used to
  21. * serialize the ioctls. Otherwise the v4l2 core lock defined
  22. * below is used. This lock is really bad since it serializes
  23. * completely independent devices.
  24. *
  25. * Both variants suffer from the same problem: if the driver
  26. * sleeps, then it blocks all ioctls since the lock is still
  27. * held. This is very common for VIDIOC_DQBUF since that
  28. * normally waits for a frame to arrive. As a result any other
  29. * ioctl calls will proceed very, very slowly since each call
  30. * will have to wait for the VIDIOC_QBUF to finish. Things that
  31. * should take 0.01s may now take 10-20 seconds.
  32. *
  33. * The workaround is to *not* take the lock for VIDIOC_DQBUF.
  34. * This actually works OK for videobuf-based drivers, since
  35. * videobuf will take its own internal lock.
  36. */
  37. static DEFINE_MUTEX(v4l2_ioctl_mutex);
  38. struct mutex *m = vdev->v4l2_dev ?
  39. &vdev->v4l2_dev->ioctl_lock : &v4l2_ioctl_mutex;
  40. if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m))
  41. return -ERESTARTSYS;
  42. if (video_is_registered(vdev))
  43. ret = vdev->fops->ioctl(filp, cmd, arg);
  44. if (cmd != VIDIOC_DQBUF)
  45. mutex_unlock(m);
  46. } else
  47. ret = -ENOTTY;
  48. return ret;
  49. }

现在继续看video_ioctl2

  1. long video_ioctl2(struct file *file,
  2. unsigned int cmd, unsigned long arg)
  3. {
  4. return video_usercopy(file, cmd, arg, __video_do_ioctl);
  5. }

这个函数简单的不能再简单了,不过之所以简单,是因为video_usercopy帮忙做了很多事情。它实现了用户空间和内核空间传输传递的实现,因此在__video_do_ioctl不用再考虑这些了。下面继续看__video_do_ioctl,同样,采用代码行内注释方式:

  1. static long __video_do_ioctl(struct file *file,
  2. unsigned int cmd, void *arg)
  3. {
  4. struct video_device *vfd = video_devdata(file);
  5. const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
  6. bool write_only = false;
  7. struct v4l2_ioctl_info default_info;
  8. const struct v4l2_ioctl_info *info;
  9. void *fh = file->private_data;
  10. struct v4l2_fh *vfh = NULL;
  11. int use_fh_prio = 0;
  12. int debug = vfd->debug;
  13. long ret = -ENOTTY;
  14. if (ops == NULL) {
  15. pr_warn("%s: has no ioctl_ops.\n",
  16. video_device_node_name(vfd));
  17. return ret;
  18. }
  19. if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {//这个不会进入,因为soc_camera根本就没设置它,所以直接忽略吧
  20. vfh = file->private_data;
  21. use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
  22. }
  23. if (v4l2_is_known_ioctl(cmd)) {//这里是判断是不是已知的ioctl命令调用,我们后面会看看,都有哪些已知调用
  24. info = &v4l2_ioctls[_IOC_NR(cmd)];
  25. if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&//检测ioctl是否有效
  26. !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
  27. goto done;
  28. if (use_fh_prio && (info->flags & INFO_FL_PRIO)) {//优先级检测,暂时也忽略吧
  29. ret = v4l2_prio_check(vfd->prio, vfh->prio);
  30. if (ret)
  31. goto done;
  32. }
  33. } else {//如果不是已知ioctl调用
  34. default_info.ioctl = cmd;
  35. default_info.flags = 0;
  36. default_info.debug = v4l_print_default;
  37. info = &default_info;
  38. }
  39. write_only = _IOC_DIR(cmd) == _IOC_WRITE;
  40. if (info->flags & INFO_FL_STD) {//如果设置了INFO_FL_STD(后面分析v4l2_is_known_ioctl的时候,会知道哪些ioctl会设置它,哪些不会设置)
  41. typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
  42. const void *p = vfd->ioctl_ops;
  43. const vidioc_op *vidioc = p + info->u.offset;
  44. ret = (*vidioc)(file, fh, arg);
  45. } else if (info->flags & INFO_FL_FUNC) {//如果设置了INFO_FL_FUNC(后面分析v4l2_is_known_ioctl的时候,会知道哪些ioctl会设置它,哪些不会设置)
  46. ret = info->u.func(ops, file, fh, arg);
  47. } else if (!ops->vidioc_default) {
  48. ret = -ENOTTY;
  49. } else {//如果不是已知ioctl且ops->vidioc_default不为NULL
  50. ret = ops->vidioc_default(file, fh,
  51. use_fh_prio ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
  52. cmd, arg);
  53. }
  54. done:
  55. if (debug) {
  56. v4l_printk_ioctl(video_device_node_name(vfd), cmd);
  57. if (ret < 0)
  58. pr_cont(": error %ld", ret);
  59. if (debug == V4L2_DEBUG_IOCTL)
  60. pr_cont("\n");
  61. else if (_IOC_DIR(cmd) == _IOC_NONE)
  62. info->debug(arg, write_only);
  63. else {
  64. pr_cont(": ");
  65. info->debug(arg, write_only);
  66. }
  67. }
  68. return ret;
  69. }

下面看看v4l2_is_known_ioctl吧!继续代码内注释,你懂得!

  1. #define IOCTL_INFO_STD(_ioctl, _vidioc, _debug, _flags) \
  2. [_IOC_NR(_ioctl)] = { \
  3. .ioctl = _ioctl, \
  4. .flags = _flags | INFO_FL_STD, \
  5. .name = #_ioctl, \
  6. .u.offset = offsetof(struct v4l2_ioctl_ops, _vidioc), \
  7. .debug = _debug, \
  8. }
  9. #define IOCTL_INFO_FNC(_ioctl, _func, _debug, _flags) \
  10. [_IOC_NR(_ioctl)] = { \
  11. .ioctl = _ioctl, \
  12. .flags = _flags | INFO_FL_FUNC, \
  13. .name = #_ioctl, \
  14. .u.func = _func, \
  15. .debug = _debug, \
  16. }
  17. static struct v4l2_ioctl_info v4l2_ioctls[] = {
  18. IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
  19. IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)),
  20. IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, INFO_FL_CLEAR(v4l2_format, type)),
  21. IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
  22. IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
  23. IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
  24. IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf, v4l_print_framebuffer, 0),
  25. IOCTL_INFO_STD(VIDIOC_S_FBUF, vidioc_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
  26. IOCTL_INFO_FNC(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO),
  27. IOCTL_INFO_FNC(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),
  28. IOCTL_INFO_STD(VIDIOC_EXPBUF, vidioc_expbuf, v4l_print_exportbuffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_exportbuffer, flags)),
  29. IOCTL_INFO_FNC(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE),
  30. IOCTL_INFO_FNC(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
  31. IOCTL_INFO_FNC(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
  32. IOCTL_INFO_FNC(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)),
  33. IOCTL_INFO_FNC(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO),
  34. IOCTL_INFO_STD(VIDIOC_G_STD, vidioc_g_std, v4l_print_std, 0),
  35. IOCTL_INFO_FNC(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO),
  36. IOCTL_INFO_FNC(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)),
  37. IOCTL_INFO_FNC(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)),
  38. IOCTL_INFO_FNC(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),
  39. IOCTL_INFO_FNC(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),
  40. IOCTL_INFO_FNC(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)),
  41. IOCTL_INFO_FNC(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO),
  42. IOCTL_INFO_STD(VIDIOC_G_AUDIO, vidioc_g_audio, v4l_print_audio, 0),
  43. IOCTL_INFO_STD(VIDIOC_S_AUDIO, vidioc_s_audio, v4l_print_audio, INFO_FL_PRIO),
  44. IOCTL_INFO_FNC(VIDIOC_QUERYCTRL, v4l_queryctrl, v4l_print_queryctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_queryctrl, id)),
  45. IOCTL_INFO_FNC(VIDIOC_QUERYMENU, v4l_querymenu, v4l_print_querymenu, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_querymenu, index)),
  46. IOCTL_INFO_STD(VIDIOC_G_INPUT, vidioc_g_input, v4l_print_u32, 0),
  47. IOCTL_INFO_FNC(VIDIOC_S_INPUT, v4l_s_input, v4l_print_u32, INFO_FL_PRIO),
  48. IOCTL_INFO_STD(VIDIOC_G_EDID, vidioc_g_edid, v4l_print_edid, INFO_FL_CLEAR(v4l2_edid, edid)),
  49. IOCTL_INFO_STD(VIDIOC_S_EDID, vidioc_s_edid, v4l_print_edid, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_edid, edid)),
  50. IOCTL_INFO_STD(VIDIOC_G_OUTPUT, vidioc_g_output, v4l_print_u32, 0),
  51. IOCTL_INFO_FNC(VIDIOC_S_OUTPUT, v4l_s_output, v4l_print_u32, INFO_FL_PRIO),
  52. IOCTL_INFO_FNC(VIDIOC_ENUMOUTPUT, v4l_enumoutput, v4l_print_enumoutput, INFO_FL_CLEAR(v4l2_output, index)),
  53. IOCTL_INFO_STD(VIDIOC_G_AUDOUT, vidioc_g_audout, v4l_print_audioout, 0),
  54. IOCTL_INFO_STD(VIDIOC_S_AUDOUT, vidioc_s_audout, v4l_print_audioout, INFO_FL_PRIO),
  55. IOCTL_INFO_FNC(VIDIOC_G_MODULATOR, v4l_g_modulator, v4l_print_modulator, INFO_FL_CLEAR(v4l2_modulator, index)),
  56. IOCTL_INFO_STD(VIDIOC_S_MODULATOR, vidioc_s_modulator, v4l_print_modulator, INFO_FL_PRIO),
  57. IOCTL_INFO_FNC(VIDIOC_G_FREQUENCY, v4l_g_frequency, v4l_print_frequency, INFO_FL_CLEAR(v4l2_frequency, tuner)),
  58. IOCTL_INFO_FNC(VIDIOC_S_FREQUENCY, v4l_s_frequency, v4l_print_frequency, INFO_FL_PRIO),
  59. IOCTL_INFO_FNC(VIDIOC_CROPCAP, v4l_cropcap, v4l_print_cropcap, INFO_FL_CLEAR(v4l2_cropcap, type)),
  60. IOCTL_INFO_FNC(VIDIOC_G_CROP, v4l_g_crop, v4l_print_crop, INFO_FL_CLEAR(v4l2_crop, type)),
  61. IOCTL_INFO_FNC(VIDIOC_S_CROP, v4l_s_crop, v4l_print_crop, INFO_FL_PRIO),
  62. IOCTL_INFO_STD(VIDIOC_G_SELECTION, vidioc_g_selection, v4l_print_selection, 0),
  63. IOCTL_INFO_STD(VIDIOC_S_SELECTION, vidioc_s_selection, v4l_print_selection, INFO_FL_PRIO),
  64. IOCTL_INFO_STD(VIDIOC_G_JPEGCOMP, vidioc_g_jpegcomp, v4l_print_jpegcompression, 0),
  65. IOCTL_INFO_STD(VIDIOC_S_JPEGCOMP, vidioc_s_jpegcomp, v4l_print_jpegcompression, INFO_FL_PRIO),
  66. IOCTL_INFO_FNC(VIDIOC_QUERYSTD, v4l_querystd, v4l_print_std, 0),
  67. IOCTL_INFO_FNC(VIDIOC_TRY_FMT, v4l_try_fmt, v4l_print_format, 0),
  68. IOCTL_INFO_STD(VIDIOC_ENUMAUDIO, vidioc_enumaudio, v4l_print_audio, INFO_FL_CLEAR(v4l2_audio, index)),
  69. IOCTL_INFO_STD(VIDIOC_ENUMAUDOUT, vidioc_enumaudout, v4l_print_audioout, INFO_FL_CLEAR(v4l2_audioout, index)),
  70. IOCTL_INFO_FNC(VIDIOC_G_PRIORITY, v4l_g_priority, v4l_print_u32, 0),
  71. IOCTL_INFO_FNC(VIDIOC_S_PRIORITY, v4l_s_priority, v4l_print_u32, INFO_FL_PRIO),
  72. IOCTL_INFO_FNC(VIDIOC_G_SLICED_VBI_CAP, v4l_g_sliced_vbi_cap, v4l_print_sliced_vbi_cap, INFO_FL_CLEAR(v4l2_sliced_vbi_cap, type)),
  73. IOCTL_INFO_FNC(VIDIOC_LOG_STATUS, v4l_log_status, v4l_print_newline, 0),
  74. IOCTL_INFO_FNC(VIDIOC_G_EXT_CTRLS, v4l_g_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
  75. IOCTL_INFO_FNC(VIDIOC_S_EXT_CTRLS, v4l_s_ext_ctrls, v4l_print_ext_controls, INFO_FL_PRIO | INFO_FL_CTRL),
  76. IOCTL_INFO_FNC(VIDIOC_TRY_EXT_CTRLS, v4l_try_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
  77. IOCTL_INFO_STD(VIDIOC_ENUM_FRAMESIZES, vidioc_enum_framesizes, v4l_print_frmsizeenum, INFO_FL_CLEAR(v4l2_frmsizeenum, pixel_format)),
  78. IOCTL_INFO_STD(VIDIOC_ENUM_FRAMEINTERVALS, vidioc_enum_frameintervals, v4l_print_frmivalenum, INFO_FL_CLEAR(v4l2_frmivalenum, height)),
  79. IOCTL_INFO_STD(VIDIOC_G_ENC_INDEX, vidioc_g_enc_index, v4l_print_enc_idx, 0),
  80. IOCTL_INFO_STD(VIDIOC_ENCODER_CMD, vidioc_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
  81. IOCTL_INFO_STD(VIDIOC_TRY_ENCODER_CMD, vidioc_try_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
  82. IOCTL_INFO_STD(VIDIOC_DECODER_CMD, vidioc_decoder_cmd, v4l_print_decoder_cmd, INFO_FL_PRIO),
  83. IOCTL_INFO_STD(VIDIOC_TRY_DECODER_CMD, vidioc_try_decoder_cmd, v4l_print_decoder_cmd, 0),
  84. IOCTL_INFO_FNC(VIDIOC_DBG_S_REGISTER, v4l_dbg_s_register, v4l_print_dbg_register, 0),
  85. IOCTL_INFO_FNC(VIDIOC_DBG_G_REGISTER, v4l_dbg_g_register, v4l_print_dbg_register, 0),
  86. IOCTL_INFO_FNC(VIDIOC_S_HW_FREQ_SEEK, v4l_s_hw_freq_seek, v4l_print_hw_freq_seek, INFO_FL_PRIO),
  87. IOCTL_INFO_STD(VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings, v4l_print_dv_timings, INFO_FL_PRIO),
  88. IOCTL_INFO_STD(VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings, v4l_print_dv_timings, 0),
  89. IOCTL_INFO_FNC(VIDIOC_DQEVENT, v4l_dqevent, v4l_print_event, 0),
  90. IOCTL_INFO_FNC(VIDIOC_SUBSCRIBE_EVENT, v4l_subscribe_event, v4l_print_event_subscription, 0),
  91. IOCTL_INFO_FNC(VIDIOC_UNSUBSCRIBE_EVENT, v4l_unsubscribe_event, v4l_print_event_subscription, 0),
  92. IOCTL_INFO_FNC(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE),
  93. IOCTL_INFO_FNC(VIDIOC_PREPARE_BUF, v4l_prepare_buf, v4l_print_buffer, INFO_FL_QUEUE),
  94. IOCTL_INFO_STD(VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings, v4l_print_enum_dv_timings, 0),
  95. IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, 0),
  96. IOCTL_INFO_STD(VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, type)),
  97. IOCTL_INFO_FNC(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0),
  98. IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_INFO, v4l_dbg_g_chip_info, v4l_print_dbg_chip_info, INFO_FL_CLEAR(v4l2_dbg_chip_info, match)),
  99. };
  100. #define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls)
  101. bool v4l2_is_known_ioctl(unsigned int cmd)
  102. {
  103. if (_IOC_NR(cmd) >= V4L2_IOCTLS)//如果该命令对应的数量超过了v4l2_ioctls的大小(linux ioctl
  104. //的cmd有一套机制的,它由方向、类型、序号、负载数据大小组成,所以可以通过cmd提取出该cmd对应的序号),
  105. //那么就不是已知命令咯。v4l2根据视频这类设备的实际情况,默认定义了一些命令
  106. return false;
  107. return v4l2_ioctls[_IOC_NR(cmd)].ioctl == cmd;//进一步判断从cmd里提取出的序号上的成员的cmd与它是否完全相等,相等,才表示确实是已知命令啦
  108. }

需要补充下,IOCTL_INFO_FNC定义了INFO_FL_FUNC flag,而IOCTL_INFO_STD定义了INFO_FL_STD flag,不同的命令又会再附加一些flag,比如INFO_FL_PRIOINFO_FL_CTRLINFO_FL_QUEUE等等。用IOCTL_INFO_FNC定义的命令条目,存放的是函数指针,而INFO_FL_STD定义的命令条目,存放的是相对于结构体v4l2_ioctl_ops的偏移。v4l2_ioctl_ops相信都布魔人啦,就是video_device设备的ioctl_ops啦,对应到我们这里,就是soc_camera_ioctl_ops。总结一下,有些ioctl,会直接回调v4l2_ioctls数组里面对应条目初始化时填写的函数,有些则会回调video_device设备的ioctl_ops里面的函数,具体哪个函数,则是通过v4l2_ioctls数组里面对应条目初始化时填写的函数偏移决定。

经过上面的层层分析,我们知道:

  1. IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),

最终会被使用,看到它是用IOCTL_INFO_FNC定义的,于是我们就知道了v4l_querycap函数会被回调。下面看v4l_querycap

  1. static int v4l_querycap(const struct v4l2_ioctl_ops *ops,
  2. struct file *file, void *fh, void *arg)
  3. {
  4. struct v4l2_capability *cap = (struct v4l2_capability *)arg;
  5. cap->version = LINUX_VERSION_CODE;
  6. return ops->vidioc_querycap(file, fh, cap);
  7. }

代码很清晰,其中,ops就是__video_do_ioctl通过ops = vfd->ioctl_ops;video_device拿到的,对应到我们这里,就是soc_camera_ioctl_ops啦!函数将应用层的ioctl转移到soc_camera_ioctl_ops里面的vidioc_querycap了。对应代码:

  1. static int soc_camera_querycap(struct file *file, void *priv,
  2. struct v4l2_capability *cap)
  3. {
  4. struct soc_camera_device *icd = file->private_data;
  5. struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
  6. WARN_ON(priv != file->private_data);
  7. strlcpy(cap->driver, ici->drv_name, sizeof(cap->driver));
  8. return ici->ops->querycap(ici, cap);
  9. }

看到这里,估计要骂人了。或许都在想,为什么不直接调用啊,这样一层一层回调多麻烦,对吧!这里将ioctl又转移到ici->ops->querycap了。我认为这就是高手与菜鸟之间的差别了。菜鸟写的代码没有层次感,扩展性差,模块之间紧耦合等等。我们看到v4l_querycap属于v4l2架构层,它负责填cap->version,因为这个是它可以完成而别的模块不应该填写的,不然就重复了,对吧。而soc_camera_querycap负责填充了cap->driversoc_camera_querycap是属于通用soc_camera驱动层,它当然可以填写cap->driver咯,不然每个host驱动都要自己填写cap->driver,重复,对吧!v4l2框架是只与soc_camera打交道的(因此是它使用v4l2机制注册了video_device,v4l2就认video_device),它不用知道camera host的任何信息,camera host是由soc_camera来负责的。层次是很清晰的。我们继续看ici->ops->querycap,即isi_soc_camera_host_ops里的isi_camera_querycap

  1. static int isi_camera_querycap(struct soc_camera_host *ici,
  2. struct v4l2_capability *cap)
  3. {
  4. strcpy(cap->driver, "atmel-isi");
  5. strcpy(cap->card, "Atmel Image Sensor Interface");
  6. cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE |
  7. V4L2_CAP_STREAMING);
  8. return 0;
  9. }

设备的能力集,当然只有host最清楚了,所以才由host填capabilities。不过我们看到第一行代码貌似是多余的,也许写该驱动的人没像我这样分析过吧!^_^

分析到这里,总算把VIDIOC_QUERYCAP分析完了。后面分析其他命令的时候,就不会再向上面一样一步一步分析啦,因为都是相同的路线,最多就是这个进if,那个进else,这个直接callback,那个通过偏移callback啦!

在获取VIDIOC_QUERYCAP后,一般会设置视频捕获格式,通过如下代码(以下所有代码仅仅是为了展示,正式代码应该要做出错检查什么的哈)

  1. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  2. fmt.fmt.pix.width = 640;
  3. fmt.fmt.pix.height = 480;
  4. fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //像素格式
  5. fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
  6. ioctl(fd, VIDIOC_S_FMT, &fmt);

直接看IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),再看v4l_s_fmt

  1. static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops,
  2. struct file *file, void *fh, void *arg)
  3. {
  4. struct v4l2_format *p = arg;
  5. struct video_device *vfd = video_devdata(file);
  6. bool is_vid = vfd->vfl_type == VFL_TYPE_GRABBER;
  7. bool is_sdr = vfd->vfl_type == VFL_TYPE_SDR;
  8. bool is_rx = vfd->vfl_dir != VFL_DIR_TX;
  9. bool is_tx = vfd->vfl_dir != VFL_DIR_RX;
  10. switch (p->type) {
  11. case V4L2_BUF_TYPE_VIDEO_CAPTURE:
  12. if (unlikely(!is_rx || !is_vid || !ops->vidioc_s_fmt_vid_cap))
  13. break;
  14. CLEAR_AFTER_FIELD(p, fmt.pix);
  15. return ops->vidioc_s_fmt_vid_cap(file, fh, arg);
  16. ......
  17. ......
  18. }
  19. return -EINVAL;
  20. }

最终又调用到soc_camera_ioctl_ops里的vidioc_s_fmt_vid_cap了,即soc_camera_s_fmt_vid_cap。我想应该能想到最终会调用到host的callback吧。soc_camera_set_fmt->(ici->ops->set_fmt(icd, f));

在设置了视频捕获格式后,就是向驱动申请缓冲区了,通过如下代码(以下所有代码仅仅是为了展示,正式代码应该要做出错检查什么的哈)

  1. Struct v4l2_requestbuffers req;
  2. req.count = 4;
  3. req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4. req.memory = V4L2_MEMORY_MMAP;
  5. ioctl(fd, VIDIOC_REQBUFS, &req);

直接看IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),再看v4l_reqbufs:

  1. static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
  2. struct file *file, void *fh, void *arg)
  3. {
  4. struct v4l2_requestbuffers *p = arg;
  5. int ret = check_fmt(file, p->type);
  6. if (ret)
  7. return ret;
  8. CLEAR_AFTER_FIELD(p, memory);
  9. return ops->vidioc_reqbufs(file, fh, p);
  10. }

还是一样的,先回调到soc_camera层的vidioc_reqbufs,即soc_camera_reqbufs,最终调用到host初始化buffer相关callback了:

  1. static int soc_camera_reqbufs(struct file *file, void *priv,
  2. struct v4l2_requestbuffers *p)
  3. {
  4. int ret;
  5. struct soc_camera_device *icd = file->private_data;
  6. struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
  7. WARN_ON(priv != file->private_data);
  8. if (icd->streamer && icd->streamer != file)
  9. return -EBUSY;
  10. if (ici->ops->init_videobuf) {
  11. ret = videobuf_reqbufs(&icd->vb_vidq, p);
  12. if (ret < 0)
  13. return ret;
  14. ret = ici->ops->reqbufs(icd, p);
  15. } else {
  16. ret = vb2_reqbufs(&icd->vb2_vidq, p);
  17. }
  18. if (!ret && !icd->streamer)
  19. icd->streamer = file;
  20. return ret;
  21. }

注意,v4l2使用的buffer我们可以用v4l2提供的现有的videobuf2机制就可以了。

请求完buffer,应用层一般会获取它请求的buffer,然后对其做mmap:

  1. 获取每个缓冲区的信息,映射到用户空间
  2. structbuffer {
  3. void *start;
  4. size_t length;
  5. } *buffers;
  6. buffers = calloc(req.count, sizeof(*buffers));
  7. for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
  8. struct v4l2_buffer buf;
  9. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  10. buf.memory = V4L2_MEMORY_MMAP;
  11. buf.index = n_buffers;
  12. if (-1 == ioctl(fd, VIDIOC_QUERYBUF, & buf))
  13. errno_exit("VIDIOC_QUERYBUF");
  14. buffers[n_buffers].length = buf.length;
  15. buffers[n_buffers].start=
  16. mmap(NULL /* start anywhere */,
  17. buf.length,
  18. PROT_READ | PROT_WRITE /* required */,
  19. MAP_SHARED /* recommended */,
  20. fd, buf.m.offset);
  21. }

VIDIOC_QUERYBUF对应的回调函数是v4l_querybuf,还是回调soc_cameravidioc_querybuf,即soc_camera_querybuf,它内部的实现就部分析了,和请求buffer的思想是一样的。

下面看mmap的实现,对应的video_device的mmap是soc_camera_mmap,内部的实现还是和上面的一样,如果使用自己的buffer实现,就走自己的路,如果是用v4l2提供的现有的机制实现,那么就继续调用相应的api即可。

缓冲区mmap好后,我们剩下的就是将发命令将buffer放入到视频采集的buffer队列中去,并开启流,好让底层去填充它:

  1. for (i =0; i < n_buffers; ++i) {
  2. struct v4l2_buffer buf;
  3. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4. buf.memory = V4L2_MEMORY_MMAP;
  5. buf.index = i;
  6. if (-1 == ioctl(fd, VIDIOC_QBUF, &buf))
  7. errno_exit("VIDIOC_QBUF");
  8. }
  9. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  10. ioctl(fd, VIDIOC_STREAMON, & type);

VIDIOC_QBUF对应的回调函数是v4l_qbuf,还是回调soc_cameravidioc_qbuf,即soc_camera_qbuf,它内部的实现就部分析了,和上面类似。

VIDIOC_STREAMON对应的回调函数是v4l_streamon,还是回调soc_cameravidioc_streamon,即soc_camera_streamon,它内部的实现就部分析了,和上面类似,不过需要额外做一些事情,比如让sensor开始工作,对吧!

  1. static int soc_camera_streamon(struct file *file, void *priv,
  2. enum v4l2_buf_type i)
  3. {
  4. struct soc_camera_device *icd = file->private_data;
  5. struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
  6. struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
  7. int ret;
  8. WARN_ON(priv != file->private_data);
  9. if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
  10. return -EINVAL;
  11. if (icd->streamer != file)
  12. return -EBUSY;
  13. /* This calls buf_queue from host driver's videobuf_queue_ops */
  14. if (ici->ops->init_videobuf)
  15. ret = videobuf_streamon(&icd->vb_vidq);
  16. else
  17. ret = vb2_streamon(&icd->vb2_vidq, i);
  18. if (!ret)
  19. v4l2_subdev_call(sd, video, s_stream, 1);
  20. return ret;
  21. }

这里会调用v4l2_subdev的video类ops的s_stream。如果看懂了前文的相关内容,应该知道v4l2_subdev对应的ops就是i2c sensor驱动实现的函数集!看文件ov2640.c

  1. static struct v4l2_subdev_video_ops ov2640_subdev_video_ops = {
  2. .s_stream = ov2640_s_stream,
  3. .g_mbus_fmt = ov2640_g_fmt,
  4. .s_mbus_fmt = ov2640_s_fmt,
  5. .try_mbus_fmt = ov2640_try_fmt,
  6. .cropcap = ov2640_cropcap,
  7. .g_crop = ov2640_g_crop,
  8. .enum_mbus_fmt = ov2640_enum_fmt,
  9. .g_mbus_config = ov2640_g_mbus_config,
  10. };
  11. static struct v4l2_subdev_ops ov2640_subdev_ops = {
  12. .core = &ov2640_subdev_core_ops,
  13. .video = &ov2640_subdev_video_ops,
  14. };

于是,这里就是调用ov2640_s_stream了。

总结

  基本上都分析完了。我突然想起另外一个问题,那就是v4l2提供的ctrl机制怎么调用的呢!i2c sensor里都添加了这些ctrl啊,对吧!这个其实是通过命令VIDIOC_G_CTRLVIDIOC_S_CTRL来调用的。我们可以通过它们来配置sensor的一些参数,比如曝光、白平衡等。我认为这篇博文应该可以帮助要写sensor驱动或者host驱动的人吧_

基本分析完了吧!!!!!!   有什么漏掉的,欢迎大家指出!

2015年6月

camera驱动框架分析(下)的更多相关文章

  1. camera驱动框架分析(上)【转】

    转自:https://www.cnblogs.com/rongpmcu/p/7662738.html 前言 camera驱动框架涉及到的知识点比较多,特别是camera本身的接口就有很多,有些是直接连 ...

  2. camera驱动框架分析(上)

    前言 camera驱动框架涉及到的知识点比较多,特别是camera本身的接口就有很多,有些是直接连接到soc的camif口上的,有些是通过usb接口导出的,如usb camera.我这里主要讨论前者, ...

  3. camera驱动框架分析(中)

    camera host的驱动 下面开始分析camera host吧,如果仅仅是想知道camera sensor驱动怎么写,而不想知道内部具体怎么个调用流程,怎么个架构设计,那可以跳过该部分,直接去看i ...

  4. Linux USB驱动框架分析(2)【转】

    转自:http://blog.chinaunix.net/uid-23046336-id-3243543.html   看了http://blog.chinaunix.net/uid-11848011 ...

  5. Linux USB驱动框架分析 【转】

    转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...

  6. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  7. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  8. Linux USB驱动框架分析【转】

    转自:http://blog.csdn.net/jeffade/article/details/7701431 Linux USB驱动框架分析(一) 初次接触和OS相关的设备驱动编写,感觉还挺有意思的 ...

  9. uart驱动框架分析(二)uart_add_one_port

    作者:lizuobin (百问网论坛答疑助手) 原文: https://blog.csdn.net/lizuobin2/article/details/51801183 (所用开发板:mini2440 ...

随机推荐

  1. 常用模块(数据序列化 json、pickle、shelve)

    本节内容 前言 json模块 pickle模块 shelve模块 总结 一.前言 1. 现实需求 每种编程语言都有各自的数据类型,其中面向对象的编程语言还允许开发者自定义数据类型(如:自定义类),Py ...

  2. Python 3 学习笔记之——键盘输入和读写文件

    1. 键盘输入 Python提供了 input() 内置函数从标准输入读入一行文本,默认的标准输入是键盘.input 可以接收一个 Python 表达式作为输入,并将运算结果返回. str = inp ...

  3. Tensorflow编程基础之Mnist手写识别实验+关于cross_entropy的理解

    好久没有静下心来写点东西了,最近好像又回到了高中时候的状态,休息不好,无法全心学习,恶性循环,现在终于调整的好一点了,听着纯音乐突然非常伤感,那些曾经快乐的大学时光啊,突然又慢慢的一下子出现在了眼前, ...

  4. windows下连接hadoop运行eclipse报错Permission denied:

    这是权限问题,试了一下同时也不能在hdfs创建文件夹. 解决: 修改如下hadoop的配置文件:etc/hadoop/hdfs-site.xml,如没有的话可以添加上. <property> ...

  5. C++STL中的vector的简单实用

    [原创] 使用C++STL中的vector, #include <stdio.h> #include<stdlib.h> #include<vector> usin ...

  6. C语言数组作业总结

    数组作业总结 评分注意事项. 注意用Markdown语法排版,尤其注意伪代码用代码符号渲染.用符号 ``` 生成代码块. 变量名不规范,没注释,没缩进,括号不对齐,倒扣5分. PTA上写的所有代码务必 ...

  7. LTE QOS

    http://wenku.baidu.com/link?url=ziFIkdKaC7MU2RY-bTOp2bt87WFPw5_02bqmYs5W6w4ktOfPHEcWesK1U2T7YiyXjVSM ...

  8. [Java] Java常见错误

    1.处理java错误"编码 GBK 的不可映射字符" (1)首先记事本打开java源文件 (2)然后另存为,选择ANSI编码 (3)覆盖 (4)再试一下,ok,编译通过.

  9. java8 增强的Iterator遍历集合元素

    Iterator接口也是Java集合框架的成员,与Collection和Map两个系列的集合不一样的是Collection和Map系列主要用于充当容器的作用,而Iterator正如其名字一样是主要用于 ...

  10. SRM710 div1 ReverseMancala(trick)

    题目大意, 给定一个有n个点的环,n不超过10,每个点上有一个权重 起始时权重将会给出,然后有2种操作 第一种操作是,选择一个位置i,获得权重w = a[i],把a[i]变成0,然后接下来在环上顺着走 ...