说明:
        理解摄像头驱动需要四个前提:
        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(8 bits, 4:2:2, Interpolated color).一定要搞清楚格式,后面的驱动里面设置的格式一定要和这个格式一致。

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

从OV9650采集过来的数据没法直接交给CPU处理。S5PC100芯片里面集成了Camera控制器,叫FIMC(Fully Interactive Mobile Camera)。摄像头需要先把图像数据传给控制器,经过控制器处理(裁剪拉升后直接预览或者编码)之后交给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)
        {
                struct fimc_control *ctrl;
                struct s3c_platform_fimc *pdata;
                //这个结构体就是用来描述一个摄像头的,先不管它里面的内容
                //等会儿在分析平台代码的时候可以看到它是如何被填充的
                struct s3c_platform_camera *cam;
                struct clk *srclk;
                int id, i;

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

/* Registering external 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

/* WriteBack doesn'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: failed to 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: failed to get mclk source\n", __func__);
                                return -EINVAL;
                        }

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

/* Assign camera 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 {
                struct fimc_control        ctrl[FIMC_DEVICES];
                struct s3c_platform_camera *camera[FIMC_MAXCAMS];
                int        initialized;
        };

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

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

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

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

#ifdef CONFIG_VIDEO_OV9650
        /* add by ys for ov9650 */
        static struct s3c_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 */
                /* default resol 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 struct s3c_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);

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

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

转自:http://blog.csdn.net/dahailinan/article/details/7599296

(转)FS_S5PC100平台上Linux Camera驱动开发详解(一) .的更多相关文章

  1. (转)FS_S5PC100平台上Linux Camera驱动开发详解(二)

    4-3 摄像头的初始化流程及v4l2子设备驱动 这个问题弄清楚了以后下面就来看获得Camera信息以后如何做后续的处理: 在fimc_init_global调用结束之后我们获得了OV9650的信息,之 ...

  2. Linux设备驱动开发详解

    Linux设备驱动开发详解 http://download.csdn.net/detail/wuyouzi067/9581380

  3. 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道

    http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...

  4. 《linux设备驱动开发详解》笔记——12linux设备驱动的软件架构思想

    本章重点讲解思想.思想.思想. 12.1 linux驱动的软件架构 下述三种思想,在linux的spi.iic.usb等复杂驱动里广泛使用.后面几节分别对这些思想进行详细说明. 思想1:驱动与设备分离 ...

  5. Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)

    Linux 文件系统与设备文件系统(3) 成于坚持,败于止步 sysfs 文件系统与 Linux 设备模型 1.sysfs 文件系统 Linux 2.6 内核引入了 sysfs 文件系统,sysfs ...

  6. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    结合实际代码和书中描述,可能跟书上有一定出入.本文后续芯片相关代码参考ZYNQ. 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供 ...

  7. Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)

    Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...

  8. linux设备驱动开发详解 笔记

      在目录的 Makefile 中关于 RTC_DRV_S3C 的编译脚本为: obj -$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o 上述脚本意味着如果 RTC_DRV_S3 ...

  9. 《linux设备驱动开发详解》笔记——14 linux网络设备驱动

    14.1 网络设备驱动结构 网络协议接口层:硬件无关,标准收发函数dev_queue_xmit()和netif_rx();  注意,netif_rx是将接收到的数据给上层,有时也在驱动收到数据以后调用 ...

随机推荐

  1. @SuppressWarnings 参数列表信息

  2. Python 访问set

    访问set 由于set存储的是无序集合,所以我们没法通过索引来访问. 访问 set中的某个元素实际上就是判断一个元素是否在set中. 例如,存储了班里同学名字的set: >>> s ...

  3. 打造通用的Android下拉刷新组件(适用于ListView、GridView等各类View)

    前言 近期在做项目时,使用了一个开源的下拉刷新ListView组件.极其的不稳定,bug还多.稳定的组件又写得太复杂了,jar包较大.在我的一篇博客中也讲述过下拉刷新的实现,即Android打造(Li ...

  4. 【linux】FTP添加用户,设置权限和目录

    一.目的,新建一个用户 test2,登录ftp,它只有自己的主目录权限,其他同级和上级目录没有权限 二.ftp安装.配置 yum -y install vsftpd //通过yum来安装vsftpd ...

  5. LaTex 常见错误及解决办法

    出现错误: Multirow 要用库的  导入\usepackage{multirow} ,,即可

  6. EMQ ---v2.3.11源码成熟度

    从原作者那边了解到,总体还可以,但是做不到99.99%稳定.主要是连接内存占用没有保护. pubsub均衡时很稳定,但是集群或大量消息向少量订阅发布时会崩溃,小概率情况. EMQ中CPU是公平分配给M ...

  7. Python 爬虫实例(5)—— 爬取爱奇艺视频电视剧的链接(2017-06-30 10:37)

    1. 我们找到  爱奇艺电视剧的链接地址 http://list.iqiyi.com/www/2/-------------11-1-1-iqiyi--.html 我们点击翻页发现爱奇艺的链接是这样的 ...

  8. Python2.X和Python3.X中的urllib区别

    Urllib是Python提供的一个用于操作URL的模块,在Python2.X中,有Urllib库,也有Urllib2库,在Python3.X中Urllib2合并到了Urllib中,我们爬取网页的时候 ...

  9. 数据库中varchar类型数据转换为numeric类型

    numeric有好几种选择,有整形.小数型等等.都是用cast来实现 前提:A表的ID字段是VARCHAR类型 .SELECT CAST(ID AS INTEGER) FROM A .SELECT C ...

  10. blender, merge顶点

    选择Edit Mode:,和vertex select: 同时选中两个要merge的顶点(同时选中多个顶点:http://www.cnblogs.com/wantnon/p/4526573.html) ...