CSI接口Camera驱动学习

CSI

CSI:COMS Sensor Interface

在Android4.0升级后,突然发现大量平台对Camera的支持均非常不好。要么Camera使用不稳定,要么各类ioctl设置不能使用,更有甚者,连Camera Device都不能创建。

而我们的产品,基于Camera的正常工作,必须解决此类问题。选择了其中一款使用CSI接口Camera的平台:全志A20来做研究和解决。

  1. 基础知识 :
    0.0. 目录结构:
    除了常见的,在linux-3.3/drivers/media/video/ 目录下有v4l2一些文件外,全志还在另一个目录下放置了csi以及做支持模组芯片的代码:linux-3.3/drivers/media/video/sunxi_csi/
    其中,csi部分在csi0或者csi1中。 模组对应代码在device中。
    这些代码,基本组成了全志CSI 接口 Camera的全部驱动代码。

0.1. CSI:
COMS Sensor Interface:
CSI接口通常从COMS Sensor,Video Encoder和其它视频输出设备收集数据。

0.2. 模组芯片:
手头这块板子上自带GT2005模组。所以需要看 linux-3.3/drivers/media/video/sunxi_csi/device/gt2005.c

  1. 驱动结构:
    CSI 模组Driver的基本思路其实挺简单,就是需要创建主设备号为81的device. /dev/videoX。并将其open,close,ioctl 与Driver联系起来,最终反应到Sensor (gt2005)层面。

我们首先从 linux-3.3/drivers/media/video/sunxi_csi/csi0部分开始着手,这个文件夹内的三个文件:sunxi_csi_reg.c ,sunxi_drv_csi.c, sunxi_csi_reg.h 最终会生成sunxi_csi0.ko。 会被以modules形式insmod 进系统。

1.1:CSI0 modules学习:
当sunxi_csi0.ko被insmod时,sunxi_drv_csi.c中的csi_init()被调用。
csi_init() 中注册了一个driver–csi_driver.
platform_driver_register(&csi_driver);

csi_driver中有侦测函数:.probe = csi_probe,
Sam的理解是:因为CSI接口一直连接着,所以当此Driver刚被注册时,csi_probe就被调用。
请注意:csi_probe中作了相当多事情,是sunxi_csi0.ko的重点。

它做了很多准备工作.并通过video_register_device()创建了Device. /dev/videoX. 并将此device的操作与v4l2_fops联系起来。
所以,对此Device的open,close, ioctl等, 都使用v4l2_fops中提供的函数指证。

而我们看v4l2_fops中的ioctl. 它最终是 video_device ->fops->ioctl()。
其实就是:csi_template 中的:.fops = &csi_fops, 的ioctl. 即: video_ioctl2

video_ioctl2,在v4l2-ioctl.c中定义:其实就是:
static long __video_do_ioctl(struct file *file,unsigned int cmd, void *arg)

所以,经过CSI程序的链接。最终,对Device /dev/videoX的操作,最终都被归结到 __video_do_ioctl

同时,csi_ioctl_ops也被链接到ioctl_ops。请注意,未来要用到这里。

看到这里,大家都会疑惑。 这怎么和Sensor操作练习起来了。这部分只将CSI和V4L2 连起来了。
这就是下一部要说的gt2005.ko了。

1.2: GT2005模块学习:
同样道理,insmod gt2005.ko时,gt2005.c中的init_sensor()会被启动。
在init_sensor()中,只执行了:i2c_add_driver(&sensor_driver); 其中,sensor_driver 同样会在驱动安装后立刻启动。

static int sensor_probe(struct i2c_client *client,const struct i2c_device_id *id)
v4l2_i2c_subdev_init(sd, client, &sensor_ops);
这一句代码是关键。
它将sensor_ops加入v4l2_subdev->ops中去了。而v4l2_subdev类型的sd被加入list.未来会用。(关键3)

分析下:sensor_ops.
static const struct v4l2_subdev_ops sensor_ops = {
.core = &sensor_core_ops,
.video = &sensor_video_ops,
};
也就是有:core和video 两个。一定要注意了,这里和未来很有关系。(关键4)

__video_do_ioctl()最终会通过vfd->ioctl_ops 来做实际动作。而ioctl_ops是谁呢?就是在在sunxi_drv_csi.c中的 csi_ioctl_ops

  1. ioctl的完整流程:
    当用户open /dev/videoX后,使用ioctl()时,

例1:得到能力集:
rel = ioctl(Handle, VIDIOC_QUERYCAP, &cap);
通过上面的分析, 它通过 __video_do_ioctl 最终调用到:vfd->ioctl_ops->vidioc_querycap.
也就是 csi_ioctl_ops 的 .vidioc_querycap = vidioc_querycap,

也就是说: 调用ioctl( VIDIOC_QUERYCAP). 会通过__vidoe_do_ioctl() 最终调用到sunxi_drv_sic.c中的vidioc_querycap()
为什么没有进Sensor呢? 是因为能力集在代码层面维护。不需要去问Sensor。

例2:得到像素格式
ioctl(Handle, VIDIOC_G_FMT, &Format)
它最终被调用sunxi_drv_csi.c中的vidioc_g_fmt_vid_cap()
因为格式也是由程序维护,所以没有访问Sensor。

例3:设置像素格式
ioctl(Handle, VIDIOC_S_FMT, &Format);
最终 被调用sunxi_drv_csi.c中的vidioc_s_fmt_vid_cap()
注意: 设置像素格式,需要通知Sensor。
所以会被调用:
v4l2_subdev_call(dev->sd, video,s_mbus_fmt,&ccm_fmt);
这里的video. 就是表明调用的是关键4 那个的 sensor_video_ops。
这里的sd,其实就是关键3 那里的 v4l2_subdev类型sd.
所以,又被调用到gt2005.c中的sensor_s_fmt()
此时会向Sensor中写入数据。sensor_write

例4:
ioctl(Handle, VIDIOC_G_PARM, &Stream_Parm)
会通过 __vidoe_do_ioctl()
调用到 sunxi_drv_csi.c中vidioc_g_parm()
这里会:v4l2_subdev_call(dev->sd, video,g_parm,parms);
在gt2005.c中写死的。 如果格式大于 800x600. 则15帧。 小于800x600. 则30帧。

例5:
ioctl(Handle, VIDIOC_G_PARM, &Stream_Parm)
会通过 __vidoe_do_ioctl()
调用到 sunxi_drv_csi.c中vidioc_s_parm()
这里会:v4l2_subdev_call(dev->sd, video,s_parm,parms);
在gt2005.c 中。这里打空。所以其实不能调。

例6:
ioctl(Handle, VIDIOC_G_CTRL, &ctrl)
会通过 __vidoe_do_ioctl()
调用到 sunxi_drv_csi.c中vidioc_g_ctrl()
这里会:v4l2_subdev_call(dev->sd, core,g_ctrl,ctrl);
注意:这里是core. 所以调用gt2005.c中的sensor_g_ctrl()

例7:
ioctl(Handle, VIDIOC_S_CTRL, &ctrl)
会通过 __vidoe_do_ioctl()
调用到 sunxi_drv_csi.c中vidioc_s_ctrl()
这里会:v4l2_subdev_call(dev->sd, core,s_ctrl,ctrl);
注意:这里是core. 所以调用gt2005.c中的sensor_s_ctrl()

但有一点需要注意:在A20平台编程中,大家会发现所有 VIDIOC_G_CTRL, VIDIOC_S_CTRL全不可用。Sam查了一下代码。发现是全志修改Kernel时弄错了。
错误如下:
struct v4l2_control {
__u32 id;
__s32 value;
__u32 user_pt;
};
看起来,是全志一位叫Raymon的工程师非常随意的修改了v4l2_control结构体。添加了4个字节的user_pt;
但这会造成严重后果,首先,大量使用v4l2_control的接口会出现未知问题。
更严重的是:ioctl的cmd这一项是计算出来的。v4l2_control的大小会影响到cmd的值。

所以,在A20平台上,应用程序使用 VIDIOC_G_CTRL, VIDIOC_S_CTRL时,
__video_do_ioctl(struct file *file,unsigned int cmd, void *arg)
参数2:cmd这一项和Kernel中算出的cmd值对不上。所以无法进入:
case VIDIOC_G_CTRL:
case VIDIOC_S_CTRL:

如果有全志工程师偶尔看到这篇Blog。 请通知Driver部门。修改这个明显错误。

上一篇:学习笔记(十四):基于位置无关感知策略的WIFI手势识别


下一篇:再探共享存储