camera

曝光 exposure

为了讲清曝光这个词,我们还是回到小孔成像。假设一个黑乎乎的密闭房间,一面墙壁上开了个小圆窗户,窗对面的内壁上安上感光材料(白沥青,大型胶卷或CCD/CMOS)。这就是一台大型房式照相机。在没有打开小窗之前,房间里是黑乎乎的。我们打开小窗,光线从小孔而入,射到对面墙壁的胶卷上,产生光化反应(或光电反应,如果是CCD/CMOS),照片就诞生了。此过程就叫做曝光。要得正确曝光的图片,必须精确决定曝光量。所谓曝光量就是让多少光进入这个密闭房间里。如果进光量太大,照片就会白花花一片,晚上变成了白天。如果进光量太小,照片就会黑乎乎的,白人变成黑人。 幸好我们有了光圈和快门两样工具可以一起来控制曝光量。
曝光就是光圈和快门的组合。
可以这样认为:光圈(值)大小其实就是那个小圆窗户开多大,快门(速度)就是窗户打开多久。假设窗户只打开1/4,时间为4秒钟可以正确曝光的话,很显然,窗户打开一半,时间2秒钟也能让底片正确曝光,因为1/4*4=1/2*2=1,进光量都是一样多。同样的,如果窗户全开,曝光时间就只需要1秒了。假若一个镜头光圈全开为F4,用摄影行话来说,光圈F4快门速度1秒为正确曝光值,那F5.6和2秒以及F8和4秒也同样能得到准确曝光的图片。重要结论:一张正确曝光的图片可以有N种不同的光圈和快门速度组合。
总结以上几个名词解释,有三个因素能影响一张图片是否正确曝光:光圈,快门速度,ISO。其中光圈和速度联合决定进光量,ISO决定CCD/CMOS的感光速度。如果进光量不够,我们可以开大光圈或者降低快门速度,还是不够的话就提高ISO。大光圈的缺点是解像度不如中等光圈,快门速度降低则图片可能会糊,提高ISO后图片质量也会下降 。没有完美的方案,如何取舍要灵活决定


待研究内容:
http://blog.csdn.net/dahailinan/article/category/1194245

引用如下文章:
http://blog.csdn.net/hp0773/article/details/8052144

作者:刘洪涛,华清远见嵌入式培训中心讲师。

说明:
        理解摄像头驱动需要四个前提:
        1)摄像头基本的工作原理和S5PC100集成的Camera控制器的工作原理
        2)platform_device和platform_driver工作原理
        3)Linux内核V4L2驱动架构
        4)Linux内核I2C驱动架构

1. 摄像头工作原理

OV9650/9655是CMOS接口的图像传感器芯片,可以感知外部的视觉信号并将其转换为数字信号并输出。通过下面的框图可以清晰的看到它的工作原理:

我们需要通过XVCLK1给摄像头提供时钟,RESET是复位线,PWDN在摄像头工作时应该始终为低。HREF是行参考信号,PCLK是像素时钟,VSYNC是场同步信号。一旦给摄像头提供了时钟,并且复位摄像头,摄像头就开始工作了,通过HREF,PCLK和VSYNC同步传输数字图像信号。数据是通过D0~D7这八根数据线并行送出的。

OV9650向外传输的图像格式是YUV的格式,YUV是一种压缩后的图像数据格式,它里面还包含很多具体的格式类型,我们的摄像头对应的是YCbCr(8bits, 4:2:2,Interpolated color).一定要搞清楚格式,后面的驱动里面设置的格式一定要和这个格式一致。

OV9650里面有很多寄存器需要配置,配置这些寄存器就需要通过芯片里面的SCCB总线去配置。SCCB其实是一种弱化的I2C总线。我们可以直接把摄像头接在S5PC100的I2C控制器上,利用I2C总线去读写寄存器,当然直接使用GPIO模拟I2C也可以实现读写。我们的驱动代码里两种操作模式都实现了。

从OV9650采集过来的数据没法直接交给CPU处理。S5PC100芯片里面集成了Camera控制器,叫FIMC(FullyInteractiveMobileCamera)。摄像头需要先把图像数据传给控制器,经过控制器处理(裁剪拉升后直接预览或者编码)之后交给CPU处理。

实际上摄像头工作需要的时钟也是FIMC给它提供的。

2. 驱动开发思路

因为驱动程序是承接硬件和软件的桥梁,因此开发摄像头驱动我们要搞清楚两方面的内容:第一是摄像头的硬件接口,也就是它是怎么和芯片连接的,如何控制它,如何给摄像头复位以及传送数据的格式等等;第二是摄像头的软件接口,Linux内核里面摄像头属于标准的V4L2设备,但是这个摄像头只是一个传感器,具体的操作都需要通过FIMC来控制,这看起来关系比较复杂。

相比较而言,硬件接口容易搞懂,通过读芯片手册和原理图基本上就没有问题了,软件接口比较复杂,主要中间有一个Camera控制器。下面主要集中分析软件接口。

3. 硬件接口

摄像头的硬件原理图如下:

拿到原理图,我们需要关注的是1、2两个管脚分别连接到I2C_SDA1和I2C_SCL1,这说明可以通过I2C控制器1来配置摄像头。另外调试摄像头的时候,可以根据这个原理图使用示波器来测量波形以验证代码是否正确。

这里还需要注意的是开发驱动之前最好用万用表测量摄像头的各个管脚是否和芯片连接正确,否则即使代码没有问题也看不到图像。

另外,还需要仔细阅读芯片手册里Camera控制器一章的描述。主要是明确以下信息:

FIMC支持以上三种视频工业标准,OV9650支持ITU-R 601 YcbCr 8-bit mode,这对后面的驱动编写非常重要。

MPLL和APLL都可以作为摄像头的时钟源,不过推荐使用MPLL。这对后面的驱动开发也有帮助。

4. 软件接口(如何和FIMC驱动对接)

硬件的问题搞清楚之后就可以集中精力关注软件的接口了。驱动可以有两种实现方法:第一种是把摄像头驱动做成普通的V4L2设备,直接调用FIMC里的寄存器实现视频数据的捕捉和处理;第二种利用内核已经实现好的FIMC的驱动,通过某种接口形式,把我们的摄像头驱动挂接在FIMC驱动之下。

这两种方法第一种实现起来代码量比较大,因为需要直接操作FIMC的寄存器,难度也大一些;第二种方法是利用内核已经做好的FIMC驱动,难点在于如何把摄像头驱动和FIMC驱动整合起来。

在Android下面,第一种方法并不可行,因为FIMC这个模块不仅仅是一个摄像头的控制接口,它还承担着V4L2的output功能和overlay(显示叠层)的功能,这两个功能对Android的显示系统非常重要。因此最好的方案还是第二种,找到摄像头驱动和FIMC驱动对接的接口,只要明确了这个接口,后面的事情就好办了,工作量也不大。

4-1: FIMC驱动的总体结构分析

FIMC的驱动在内核中的位置:

drivers/media/video/samsung/fimc
        fimc40_regs.c
        fimc43_regs.c
        fimc_capture.c
        fimc_dev.c
        fimc_output.c
        fimc_overlay.c
        fimc_v4l2.c

这些源码里面最基础的是fimc_dev.c,这里面注册了一个platform_driver,在相应的平台代码里面有对应的platform_device的描述。这种SOC上的控制器一般都会挂接在platform_bus上以实现在系统初始化时的device和driver的匹配。

在driver的probe函数里面,主要完成了资源获取以及v4l2设备的注册。因为FIMC一共有三套一样的控制器(fimc0,fimc1, fimc2),所以驱动里使用了一个数组来描述:

struct video_device fimc_video_device[FIMC_DEVICES] = {
                [0]= {
                        .fops=&fimc_fops,
                        .ioctl_ops=&fimc_v4l2_ops,
                        .release=fimc_vdev_release,
                },
                [1]= {
                        .fops=&fimc_fops,
                        .ioctl_ops=&fimc_v4l2_ops,
                        .release=fimc_vdev_release,
                },
                [2]= {
                        .fops=&fimc_fops,
                        .ioctl_ops=&fimc_v4l2_ops,
                        .release=fimc_vdev_release,
                },
        };

在probe函数里,调用video_register_device()来注册这三个video_device,在用户空间里就会在/dev下看到三个video设备节点,video0,video1,video2.每个video_device的成员fops对应的是针对v4l2设备的基本操作,定义如下:

static const struct v4l2_file_operations fimc_fops = {
                .owner= THIS_MODULE,
                .open= fimc_open,
                .release= fimc_release,
                .ioctl= video_ioctl2,
                .read = fimc_read,
                .write= fimc_write,
                .mmap= fimc_mmap,
                .poll= fimc_poll,
        };

另一个成员ioctl_ops非常重要,因为它是对v4l2的所有ioctl操作集合的描述。fimc_v4l2_ops定义在fimc_v4l2.c里面:

const struct v4l2_ioctl_ops fimc_v4l2_ops = {
                .vidioc_querycap        =fimc_querycap,
                .vidioc_reqbufs        =fimc_reqbufs,
                .vidioc_querybuf        =fimc_querybuf,
                .vidioc_g_ctrl        =fimc_g_ctrl,
                .vidioc_s_ctrl        =fimc_s_ctrl,
                .vidioc_cropcap        =fimc_cropcap,
                .vidioc_g_crop        =fimc_g_crop,
                .vidioc_s_crop        =fimc_s_crop,
                .vidioc_streamon        =fimc_streamon,
                .vidioc_streamoff        =fimc_streamoff,
                .vidioc_qbuf        =fimc_qbuf,
                .vidioc_dqbuf        =fimc_dqbuf,
                .vidioc_enum_fmt_vid_cap=fimc_enum_fmt_vid_capture,
                .vidioc_g_fmt_vid_cap        =fimc_g_fmt_vid_capture,
                .vidioc_s_fmt_vid_cap        =fimc_s_fmt_vid_capture,
                .vidioc_try_fmt_vid_cap        =fimc_try_fmt_vid_capture,
                .vidioc_enum_input        =fimc_enum_input,
                .vidioc_g_input        =fimc_g_input,
                .vidioc_s_input        =fimc_s_input,
                .vidioc_g_parm        =fimc_g_parm,
                .vidioc_s_parm        =fimc_s_parm,
                .vidioc_g_fmt_vid_out        =fimc_g_fmt_vid_out,
                .vidioc_s_fmt_vid_out        =fimc_s_fmt_vid_out,
                .vidioc_try_fmt_vid_out        =fimc_try_fmt_vid_out,
                .vidioc_g_fbuf        =fimc_g_fbuf,
                .vidioc_s_fbuf        =fimc_s_fbuf,
                .vidioc_try_fmt_vid_overlay=fimc_try_fmt_overlay,
                .vidioc_g_fmt_vid_overlay        =fimc_g_fmt_vid_overlay,
                .vidioc_s_fmt_vid_overlay        =fimc_s_fmt_vid_overlay,
        };

可以看到,FIMC的驱动实现了v4l2所有的接口,可以分为v4l2-input设备接口,v4l2-output设备接口以及v4l2-overlay设备接口。这里我们主要关注v4l2-input设备接口,因为摄像头属于视频输入设备。

fimc_v4l2.c里面注册了很多的回调函数,都是用于实现v4l2的标准接口的,但是这些回调函数基本上都不是在fimc_v4l2.c里面实现的,而是有相应的.c分别去实现。比如:

v4l2-input设备的操作实现: fimc_capture.c
        v4l2-output设备的操作实现:fimc_output.c
        v4l2-overlay设备的操作实现:fimc_overlay.c

这些代码其实都是和具体硬件操作无关的,这个驱动把所有操作硬件寄存器的代码都写到一个文件里面了,就是fimc40_regs.c。这样把硬件相关的代码和硬件无关的代码分开来实现是非常好的方式,可以最大限度的实现代码复用。

这些驱动源码的组织关系如下:

4-2: FIMC驱动的Camera接口分析

接口的关键还是在于fimc_dev.c里的probe函数。probe里面会调用一个函数叫fimc_init_global(),这里面会完成摄像头的分配以及时钟的获取。这个函数的原型如下:

static int fimc_init_global( struct platform_device *pdev )

这个platform_device是内核从平台代码那里传递过来的,里面包含的就是和具体平台相关的信息,其中就应该包含摄像头信息。

函数的实现:

static int fimc_init_global(struct platform_device *pdev)
        {
                structfimc_control *ctrl;
                structs3c_platform_fimc *pdata;
                //这个结构体就是用来描述一个摄像头的,先不管它里面的内容
                //等会儿在分析平台代码的时候可以看到它是如何被填充的
                structs3c_platform_camera *cam;
                structclk *srclk;
                intid, i;

        //获得平台信息
                pdata=to_fimc_plat(&pdev->dev);
                id= pdev->id; //id号可能是0,1,2
                ctrl=get_fimc_ctrl(id); //获得id号对应的fimc_control结构体指针

        /* Registeringexternal camera modules. re-arrange order to be sure */
                for(i= 0; i < FIMC_MAXCAMS; i++) {
                        cam=pdata->camera[i]; //从平台数据取得camera的信息
                        if(!cam)
                                continue;//change break to continue by ys

                /*WriteBackdoesn't need clock setting */
                        if(cam->id==CAMERA_WB) {
                                fimc_dev->camera[cam->id]=cam;
                                break;
                        }

                        //获得时钟源信息
                        srclk=clk_get(&pdev->dev, cam->srclk_name);
                        if(IS_ERR(srclk)){
                                fimc_err("%s:failedto get mclk source\n", __func__);
                                return-EINVAL;
                        }

                //获得camera的时钟信息
                        /*mclk*/
                        cam->clk=clk_get(&pdev->dev, cam->clk_name);
                        if(IS_ERR(cam->clk)){
                                fimc_err("%s:failedto get mclk source\n", __func__);
                                return-EINVAL;
                        }

                if(cam->clk->set_parent) {
                                cam->clk->parent=srclk;
                                cam->clk->set_parent(cam->clk,srclk);
                        }

                /*Assigncamera device to fimc */
                        fimc_dev->camera[cam->id]=cam; // 将从平台获得的camera分配给全局数据结构
                                                                                        //fimc_dev
                }

        fimc_dev->initialized= 1;

        return 0;
        }

可以看到这个函数实际上就是把camera的信息从平台数据那里取过来,然后分配给fimc_dev.fimc_dev定义在fimc.h里面。类型为struct fimc_global,原型如下:

/* global */
        struct fimc_global{
                structfimc_control        ctrl[FIMC_DEVICES];
                structs3c_platform_camera*camera[FIMC_MAXCAMS];
                int        initialized;
        };

现在我们需要看一下平台代码那里如何描述一个摄像头以及如何把抽象数据结构传递到平台数据里面。

S5PC100 SOC对应的平台代码位于:

arch/arm/mach-s5pc100/mach-smdkc100.c

我们是这样来描述一个camera的:

#ifdef CONFIG_VIDEO_OV9650
        /* add by ys forov9650 */
        static structs3c_platform_camera camera_c = {
                .id= CAMERA_PAR_A, /* FIXME */
                .type=CAM_TYPE_ITU, /* 2.0M ITU */
                .fmt= ITU_601_YCBCR422_8BIT,
                .order422=CAM_ORDER422_8BIT_YCBYCR,
                .i2c_busnum= 1,
                .info= &camera_info[2],
                .pixelformat= V4L2_PIX_FMT_YUYV,
                .srclk_name= "dout_mpll",
                .clk_name= "sclk_cam",
                .clk_rate= 16000000, /* 16MHz */
                .line_length= 640, /* 640*480 */
                /*defaultresol for preview kind of thing */
                .width= 640,
                .height= 480,
                .window= {
                        .left=0,
                        .top=0,
                        .width=640,
                        .height=480,
                },

        /* Polarity */
                .inv_pclk= 1,
                .inv_vsync= 0,
                .inv_href= 0,
                .inv_hsync= 0,

        .initialized = 0,
        };
        #endif

这里面的信息描述了OV9650相关的所有信息。type代表摄像头是ITU的接口,fmt代表摄像头输出的格式是ITU_601_YCBCR422_8BIT,order422代表YUV三个分量的顺序是YcbCr。这些都和前面的描述相符。另外里面还有时钟源的信息,时钟的大小以及捕捉图像的解析度,这里设置的是640x480(VGA模式),因为经过调试发现OV9650工作在VGA的模式下比较流畅清晰。Polarity代表信号的极性,具体的设置要和摄像头本身的设置一致。

i2c_busnum是I2C总线的总线编号,因为S5PC100一共有两条I2C总线(0和1),我们连在SDA1上,所以i2c_busnum是1。

camera_c是fimc_plat结构体的一个成员:

/* Interface setting */
        static structs3c_platform_fimc fimc_plat = {
                .default_cam= CAMERA_PAR_A,
                .camera[2 ] = &camera_c,
                .hw_ver= 0x40,
        };

这里会把camera_c赋值给fimc_plat里的camera数组的第三个元素,之所以是第三个是因为Android的原因。这在分析Android的摄像头硬件抽象层时会有解释。

struct s3c_platform_fimc这个结构体其实就是fimc对应的平台数据结构。在平台代码里,会由以下三个函数负责注册:

s3c_fimc0_set_platdata(&fimc_plat);
        s3c_fimc1_set_platdata(&fimc_plat);
        s3c_fimc2_set_platdata(&fimc_plat);

至于这几个函数如何实现,这里就不分析了,有兴趣可以自己看代码。

也就是说只要平台代码这边我们填充了一个structs3c_platform_camera类型的结构体,然后把它添加到fimc_plat里面,fimc的驱动就能获得对应的Camera的信息。


4-3 摄像头的初始化流程及v4l2子设备驱动

这个问题弄清楚了以后下面就来看获得Camera信息以后如何做后续的处理:

在fimc_init_global调用结束之后我们获得了OV9650的信息,之后在probe函数里面就会继续调用一个函数:fimc_configure_subdev().

这个函数的实现如下:

/*
        * Assign v4l2 device and subdev to fimc
        * it is called per every fimc ctrl registering
        */
        static int fimc_configure_subdev(struct platform_device *pdev, int id)
        {
        struct s3c_platform_fimc *pdata;
        struct s3c_platform_camera *cam;
        struct i2c_adapter *i2c_adap;
        struct i2c_board_info *i2c_info;
        struct v4l2_subdev *sd;
        struct fimc_control *ctrl;
        unsigned short addr;
        char *name;
        int cfg;

ctrl = get_fimc_ctrl(id);
        pdata = to_fimc_plat(&pdev->dev);
        cam = pdata->camera[id];

/* Subdev registration */
        if (cam) {
                i2c_adap= i2c_get_adapter(cam->i2c_busnum);
                // 省略结果判断语句
                i2c_info = cam->info;
                // 省略结果判断语句
                name = i2c_info->type;
                // 省略结果判断语句
                addr = i2c_info->addr;
                // 省略结果判断语句

                /*
                        *NOTE: first time subdev being registered,
                        *s_config is called and try to initialize subdev device
                        *but in this point, we are not giving MCLK and power to subdev
                        *so nothing happens but pass platform data through
                        */
                        sd= v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,name,i2c_info, &addr);
                        if(!sd) {
                                fimc_err("%s:v4l2 subdev board registering failed\n",
                                __func__);
                        }

                /*Assign camera device to fimc */
                        fimc_dev->camera[cam->id]= cam;

                 /*Assign subdev to proper camera device pointer */
                        fimc_dev->camera[cam->id]->sd= sd;

                return 0;
        }

这里面涉及到一些内核中I2C总线的操作,因为OV9650本身从总线的角度来讲可以看做是一个I2C的设备。实际上刚才分析平台数据的时候忽略了一个重要的信息,就是camera_c里面有一个成员是info,类型为structi2c_board_info,熟悉Linux内核I2C驱动架构就应该知道i2c_board_info是用来描述一个I2C设备。平台代码里是这样定义的:

/* Add by ys for ov9650 sensor */
        static struct ov9650_platform_data ov9650 = {
                .default_width = 640,
                .default_height = 480,
                /* ITU-R BT 601: 16 */
                .pixelformat= V4L2_PIX_FMT_YUYV, // 16 YUV 4:2:2
                .freq = 22000000, // 24MHz
                .is_mipi= 0, // Don't use MIPI-CSI-2
         };

static struct i2c_board_info camera_info[] = {
                {
                        I2C_BOARD_INFO("S5K4BA",0x2d),
                        .platform_data= &s5k4ba,
                },
                {
                        I2C_BOARD_INFO("S5K6AA",0x3c),
                        .platform_data= &s5k6aa,
                },
                /*add by ys for ov9650 sensor */
                {
                        I2C_BOARD_INFO("OV9650",0x60 >> 1),
                        .platform_data= &ov9650,
                },
        };

数组的第三个元素就是描述OV9650的,0x60就是OV9650作为从设备的设备地址,之所以这里要右移一位是因为控制器驱动还会把这个地址左移一位。

我们再回到fimc_configure_subdev这个函数里面,它首先从平台数据那里取得camera,如果不为空,就从上面的i2c_board_info里面取得i2c相应的信息,比如适配器(adapter),i2c_info, name,addr等信息,这里就不详细讲解i2c驱动的框架层了,这部分单独去学习难度并不大。总之获得所有需要的信息之后,就会调用v4l2_i2c_new_subdev_board()这个函数,这函数是整个Camera驱动的关键。简单来说,这个函数会做两件事情,第一件事情是根据传递过来的i2c设备的相关信息向I2C总线添加一个i2c设备,然后向v4l2子系统注册一个v4l2子设备(sub-dev)。也就是说OV9650既是一个I2C设备,也是一个V4L的子设备,这样就把i2c和v4l2联系起来了。

在具体分析v4l2_i2c_new_subdev_board()这个函数里具体都作了哪些工作之前,我们注意在这个函数调用之前有一段注释很有用,这段注释如下:

912        /*
        913        *NOTE: first time subdev being registered,
        914        *s_config is called and try to initialize subdev device
        915        *but in this point, we are not giving MCLK and power to subdev
        916        *so nothing happens but pass platform data through
        917        */
        918        sd= v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
        919                name,i2c_info, &addr);

意思就是说当v4l2的subdev(子设备)第一次被注册时,s_config这个函数会被调用并且尝试去初始化这个subdev。但是在这个时候,我们不会给这个subdev供给时钟(MCLK)和电源。所以这时除了把平台数据传递过去,其它什么事情都没有做。这里的关键是s_config函数,这个函数其实是v4l2-subdev对应的一个回调函数,从面向对象的角度来看,也就是内核中每一个抽象成v4l2-subdev的对象所对应的一个方法。但是在fimc的驱动看不到有什么地方去实现这个方法,这是因为这里摄像头控制器的驱动和具体某一种摄像头的驱动是分离的,毕竟摄像头可以有很多种,但是控制器就一个,这样可以分层实现,结构非常清晰而且易于扩展。

也就说必须有个地方专门去实现s_config函数,这就是具体摄像头的驱动需要作的工作。这个驱动需要我们自己去实现。我把它放在了driver/media/video/ov9650/这个目录下。里面有两个.c源码,ov9650.c和sccb.c。sccb.c是对sccb总线协议的软件模拟,其实就是用gpio来模拟i2c总线,因为sccb相当于一种弱化的i2c总线。Ov9650.c则是真正的摄像头驱动。这个驱动的主要任务就是初始化摄像头里的寄存器(通过i2c总线),当然它还可以实现其它附加功能,比如改变摄像头的分辨率,调节亮度、对比度等等,但是这里都没有实现。里面我们自己定义了一个结构体:structov9650_state

65 struct ov9650_state {
        66        structov9650_platform_data *pdata;
        67        struct v4l2_subdev sd;
        68        struct v4l2_pix_format pix;
        69        structv4l2_fract timeperframe;
        70        struct ov9650_userset userset;
        71        int freq; /* MCLK in KHz */
        72        int is_mipi;
        73        int isize;
        74        int ver;
        75        int fps;
        76 };

这里面唯一需要关心的成员就是第二项structv4l2_subdev sd,内核中用structv4l2_subdev来抽象一个v4l2子设备,这个成员一定是用来注册v4l2-subdev的。那么什么时候注册成了我们需要关心的问题。注意到这里还有一个重要的结构体:

621 static struct v4l2_i2c_driver_data v4l2_i2c_data = {
        622        .name = OV9650_DRIVER_NAME,
        623        .probe = ov9650_probe,
        624        .remove = ov9650_remove,
        625        .id_table = ov9650_id,
        626 };

这里面有probe函数,照理说应该注册一个driver才对,但是整个驱动都没有类似register_xxx_driver之类的函数,但是如果我们看看这个结构体是如何定义的,就知道问题的答案了。struct v4l2_i2c_driver_data定义在include/media/ v4l2-i2c-drv.h:

38 #ifndef __V4L2_I2C_DRV_H__
        39 #define __V4L2_I2C_DRV_H__
        40
        41 #include <media/v4l2-common.h>
        42
        43 struct v4l2_i2c_driver_data {
        44        const char * const name;
        45        int(*command)(struct i2c_client *client, unsigned int cmd, void *arg);
        46        int(*probe)(struct i2c_client *client, const struct i2c_device_id *id);
        47        int(*remove)(struct i2c_client *client);
        48        int(*suspend)(struct i2c_client *client, pm_message_t state);
        49        int(*resume)(struct i2c_client *client);
        50        conststruct i2c_device_id *id_table;
        51 };
        52
        53 static struct v4l2_i2c_driver_data v4l2_i2c_data;
        54 static struct i2c_driver v4l2_i2c_driver;
        55
        56
        57 /* Bus-based I2C implementation for kernels >= 2.6.26 */
        58
        59 static int __init v4l2_i2c_drv_init(void)
        60 {
        61        v4l2_i2c_driver.driver.name= v4l2_i2c_data.name;
        62        v4l2_i2c_driver.command= v4l2_i2c_data.command;
        63        v4l2_i2c_driver.probe= v4l2_i2c_data.probe;
        64        v4l2_i2c_driver.remove= v4l2_i2c_data.remove;
        65        v4l2_i2c_driver.suspend= v4l2_i2c_data.suspend;
        66        v4l2_i2c_driver.resume= v4l2_i2c_data.resume;
        67        v4l2_i2c_driver.id_table= v4l2_i2c_data.id_table;
        68        returni2c_add_driver(&v4l2_i2c_driver);
        69 }

72 static void __exit v4l2_i2c_drv_cleanup(void)
        73 {
        74        i2c_del_driver(&v4l2_i2c_driver);
        75 }
        76
        77 module_init(v4l2_i2c_drv_init);
        78 module_exit(v4l2_i2c_drv_cleanup);
        79
        80 #endif /* __V4L2_I2C_DRV_H__ */

其实,只要包含了这个头文件,就会向内核注册一个i2c-driver。而我们在摄像头驱动里实现的probe函数就会被赋给这个v4l2_i2c_drvier的probe成员。这个i2c-driver是和v4l2-subdev紧密关联的。针对v4l2-subdev,驱动里面注册了两套操作集合,分别是v4l2_subdev_video_ops和v4l2_subdev_core_ops。这两个结构体里面包含了很多回调函数,但是大多数回调函数我们在驱动都是以空函数的形式呈现的。只是实现了最关键的两个函数,分别是:

555        .init        =ov9650_init, /* initializing API */
        556        .s_config= ov9650_s_config, /* Fetch platform data */

从前面的分析可以知道s_config函数是在v4l2_i2c_new_subdev_board()被调用的时候注册的。v4l2_i2c_new_subdev_board是在drivers/media/video/v4l2-common.c里实现的,关键代码如下:

897 /* Load an i2c sub-device. */
        898 struct v4l2_subdev *v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev,
        899        structi2c_adapter *adapter, const char *module_name,
        900        structi2c_board_info *info, const unsigned short *probe_addrs)
        901 {
        902        structv4l2_subdev *sd = NULL;
        903        struct i2c_client *client;
        904
        905        BUG_ON(!v4l2_dev);
        906
        907        if (module_name)
        908                request_module(module_name);
        909
        910        /* Create the i2c client */
        911        if(info->addr == 0 && probe_addrs){
        912                client= i2c_new_probed_device(adapter, info, probe_addrs);
        913                printk("r1.====================client_addr: %d=======================================\n",client->addr);
        914        }else{
        915                client = i2c_new_device(adapter, info);
        916//                printk("r2.====================client_addr: 0x%x, clent->driver->id :
        0x%x========================================\n",client->addr,client->driver->id);
        917        }
        918
        919        /*Note: by loading the module first we are certain that c->driver
        920                willbe set if the driver was found. If the module was not loaded
        921                first,then the i2c core tries to delay-load the module for us,
        922                andthen c->driver is still NULL until the module is finally
        923                loaded.This delay-load mechanism doesn't work if other drivers
        924                wantto use the i2c device, so explicitly loading the module
        925                isthe best alternative. */
        926        if(client == NULL || client->driver == NULL)
        927                gotoerror;
        928
        929
        printk("1.============================================================\n");
        930        /*Lock the module so we can safely get the v4l2_subdev pointer */
        931        if(!try_module_get(client->driver->driver.owner))
        932                gotoerror;
        933        sd= i2c_get_clientdata(client);
        934
        printk("2.============================================================\n");
        935
        936        /*Register with the v4l2_device which increases the module's
        937                usecount as well. */
        938        if(v4l2_device_register_subdev(v4l2_dev, sd))
        939                sd= NULL;
        940        printk("3.============================================================\n");
        941        /*Decrease the module use count to match the first try_module_get. */
        942        module_put(client->driver->driver.owner);
        943
        944        if (sd) {
        945                /*We return errors from v4l2_subdev_call only if we have the
        946                        callbackas the .s_config is not mandatory */
        947                interr = v4l2_subdev_call(sd, core, s_config,
        948                        info->irq,info->platform_data);
        949
        950                if(err && err != -ENOIOCTLCMD) {
        951                        v4l2_device_unregister_subdev(sd);
        952                        d= NULL;
        953                }
        954        }
        955
        956 error:
        957                /*If we have a client but no subdev, then something went wrong and
        958                        wemust unregister the client. */
        959                if(client && sd == NULL)
        960                        i2c_unregister_device(client);
        961                returnsd;
        962         }

我们通过函数参数已经把i2c_adapter和i2c_board_info等信息传递过来,驱动里面正是利用这些信息向内核添加了一个i2c_device,即915行的client =i2c_new_device(adapter, info);当i2c总线上新注册了一个i2c_device,总线就会为我们匹配相应的i2c_driver,如果匹配成功就会调用驱动的probe函数。这时刚才分析ov9650.c里面的ov9650_probe函数就会被调用,在这个probe里面其实就是初始化了v4l2-subdev:

v4l2_i2c_subdev_init(sd, client, &ov9650_ops);

这个函数把v4l2-subdev和i2c_client互相关联起来,就是把指针赋给彼此的void *private。因此在注册完i2c_device之后,sd =i2c_get_clientdata(client);这条语句就可以把保存在i2c_cilent里的指向v4l2-subdev的指针取出来。得到sd之后,就调用v4l2_device_register_subdev(v4l2_dev,sd)注册一个v4l2-subdev。如果注册成功,就会调用v4l2_subdev_call(sd, core, s_config,info->irq,info->platform_data);这个函数会调用sd对应的v4l2_subdev_core_ops操作集合里的s_config函数,因此这时ov9650.c里的ov9650_s_config函数就会被调用。这个s_config函数的第三个参数是platform_data,因此驱动里面可以把平台数据保存起来以便使用,当然我们这里只是保存起来并没有使用,可以留作以后的功能扩展。

现在ov9650驱动里的probe和s_config函数的调用时间和功能都已经清楚了,还剩下一个ov9650_init函数还没有被调用。其实我们可以在任何希望调用它的时候利用v4l2_subdev_call来调用这init方法。不过在摄像头控制器的驱动里面已经有相应的地方调用了,就是在fimc_capture.c里面,有一个静态函数fimc_init_camera,这里会调用:

178        /* subdev call for init */
        179        ret= v4l2_subdev_call(cam->sd, core, init, 0);
        180        if (ret == -ENOIOCTLCMD) {
        181                fimc_err("%s:init subdev api not supported\n", __func__);
        182                returnret;
        183        }

这时ov9650.c里的ov9650_init就会被调到了。Ov9650_init做的工作其实就是初始化摄像头的寄存器,初始化可以有两种方法,一个是通过sccb.c里面提供的方法(gpio模拟i2c),另一个就是直接利用内核提供的i2c总线数据读写方法来操作,我们的代码里两种方法都实现了,不过用sccb更稳妥一些,因为不知道什么原因,直接操作i2c总线有时候会失败,这个问题仍需解决。fimc_init_camera这个函数可能会在fimc_s_input或者fimc_streamon_capture两个函数中被调用。也就是当应用程序中调用ioctl( v4l2_fd,VIDIOC_S_INPUT, &index )或者ioctl(v4l2_fd, VIDIOC_STREAMON,&type)时都有可能调用到fimc_init_camera。,当然它只会被调用一次。

还有最后一个问题就是何时给摄像头发送复位信号(原理图上第4管脚,CAM_RST),因为摄像头只有接到复位信号(高电平或者低电平)才能正常工作。我们这里是在打开v4l2设备的时候,也就是open(“/dev/video0”,RD_WR)时复位的,具体就是在fimc_dev.c里对应得v4l2的open方法:fimc_open:

653        if (pdata->hw_ver == 0x40)
        654                fimc_hw_reset_camera(ctrl);

这个fimc_hw_reset_camera就是复位函数,定义在fimc40_reg.c中:

1113 int fimc_hw_reset_camera(struct fimc_control *ctrl)
        1114 {
        1115        u32 cfg;
        1116
        1117 #ifdef CONFIG_VIDEO_OV9650
        1118        /* high reset */
        1119        cfg= readl(ctrl->regs + S3C_CIGCTRL);
        1120        cfg |= S3C_CIGCTRL_CAMRST_A;
        1121        writel(cfg,ctrl->regs + S3C_CIGCTRL);
        1122        mdelay(20);
        1123
        1124        cfg= readl(ctrl->regs + S3C_CIGCTRL);
        1125        cfg&= ~S3C_CIGCTRL_CAMRST_A;
        1126        writel(cfg,ctrl->regs + S3C_CIGCTRL);
        1127        udelay(2000);
        1128 #endif
        1129 #ifdef CONFIG_VIDEO_OV9655
        1130        /* low reset */
        1131        cfg= readl(ctrl->regs + S3C_CIGCTRL);
        1132        cfg&= ~S3C_CIGCTRL_CAMRST_A;
        1133        writel(cfg,ctrl->regs + S3C_CIGCTRL);
        1134        mdelay(20);
        1135
        1136        cfg= readl(ctrl->regs + S3C_CIGCTRL);
        1137        cfg |= S3C_CIGCTRL_CAMRST_A;
        1138        writel(cfg,ctrl->regs + S3C_CIGCTRL);
        1139        udelay(2000);
        }

因为ov9650是高电平复位,ov9655是低电平复位,所以需要分开来写。

5. 驱动总体流程图

http://www.farsight.com.cn/FarsightBBS/dispbbs.asp?boardid=84&Id=27535

6. 应用程序如何操作摄像头

细节请参考代码:ov9650_v4l2.c

操作摄像头需要符合v4l2的标准接口,接口描述及示例请参考v4l2官网:
        http://v4l2spec.bytesex.org/
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐