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

平台linuxstructlinux内核videocam

说明:
        理解摄像头驱动需要四个前提:
        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

时间: 2024-10-11 21:15:57

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

(转)FS_S5PC100平台上Linux 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

《Linux设备驱动开发详解(基于最新4.0内核)》前言

Linux从未停歇脚步.Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核.做技术,从来没有终南捷径,拼的就是坐冷板凳的傻劲. 这是一个连阅读都被碎片化的时代,在这样一个时代,人们趋向于激进.浮躁.内心的不安宁使我们极难静下心来研究什么.我见过许许多多的Linux工程师,他们的简历书写着"精通"Linux内核,有多年的工作经验,而他们的"精通"却只是把某个寄存器从0改成1,从1改成0的不

《Linux设备驱动开发详解(第3版)》海量更新总结

本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]添加关于QEMU模拟vexpress板的描述 第2章 <驱动设计的硬件基础> [N]增加关于SoC的介绍:[N]增加关于eFuse的内容:[D]删除ISA总线的内容了:[N]增加关于SP

《Linux设备驱动开发详解(第3版)》进展同步更新

本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2014.6.30 目前初步完成4-9章,相对于第2版,这几章主要的变更. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第4章 <Linux内核模块>[F]改正关于模块使用非GPL license的问题:[F]修正关于__exit修饰函数的内存管理 第5章 <Linux文件系统与设备文件>[F]修正关于文件系统与块设备驱动关系图:[N]增加应用到驱动的file操作调用图:[N]增加通过ne

《Linux设备驱动开发详解:基于最新的Linux 4.0内核》china-pub预售

<Linux设备驱动开发详解:基于最新的Linux 4.0内核>china-pub今日上线进入预售阶段: http://product.china-pub.com/4733972 推荐序一 技术日新月异,产业斗转星移,滚滚红尘,消逝的事物太多,新事物的诞生也更迅猛.众多新生事物如灿烂烟花,转瞬即逝.当我们仰望星空时,在浩如烟海的专业名词中寻找,赫然发现,Linux的生命力之旺盛顽强,斗志之昂扬雄壮,令人称奇.它正以摧枯拉朽之势迅速占领包括服务器.云计算.消费电子.工业控制.仪器仪表.导航娱乐等

《Linux设备驱动开发详解:基于最新的Linux 4.0内核》china-pub 预售

<Linux设备驱动开发详解:基于最新的Linux 4.0内核>china-pub今日上线进入预售阶段: http://product.china-pub.com/4733972 推荐序一 技术日新月异,产业斗转星移,滚滚红尘,消逝的事物太多,新事物的诞生也更迅猛.众多新生事物如灿烂烟花,转瞬即逝.当我们仰望星空时,在浩如烟海的专业名词中寻找,赫然发现,Linux的生命力之旺盛顽强,斗志之昂扬雄壮,令人称奇.它正以摧枯拉朽之势迅速占领包括服务器.云计算.消费电子.工业控制.仪器仪表.导航娱乐等

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

14.1 网络设备驱动结构 网络协议接口层:硬件无关,标准收发函数dev_queue_xmit()和netif_rx();  注意,netif_rx是将接收到的数据给上层,有时也在驱动收到数据以后调用. 网络设备接口层,net_device,统一接口名称,使上层独立于具体硬件. 设备驱动功能层,实现net_device的各成员 物理层 在整个以太网架构里,有两个数据结构非常重要,即sk_buff和net_device,后面两节有说明. 还有一些与内核交互的函数,需要掌握,如netif_start

《linux设备驱动开发详解》笔记——8阻塞与非阻塞IO

8.1 阻塞与非阻塞IO 8.1.0 概述 阻塞:访问设备时,若不能获取资源,则进程挂起,进入睡眠状态:也就是进入等待队列 非阻塞:不能获取资源时,不睡眠,要么退出.要么一直查询:直接退出且无资源时,返回-EAGAIN 阻塞进程的唤醒:必须有地方能够唤醒处于睡眠状态的阻塞进程,否则就真睡不醒了.一般是在中断中. 阻塞与非阻塞可以在open时设置,也可以通过fcntl和ioctl重新设置 8.1.1 等待队列 linux驱动中,可以用等待队列wait queue实现阻塞.等待队列与linux进程调

LINUX设备驱动开发详解----第一篇随笔

1,软件的设计宗旨呢,是高内聚,低耦合.其意思是一个驱动程序里面,尽量是自己把事情都干完,别跟其他模块或驱动牵扯太多.不然出问题的时候,就不好排查,当然这样也利于移植,只要搞清楚了驱动程序里面的代码,那换个平台,也变得轻松. 2,驱动是沟通硬件和应用的桥梁.无操作系统下的驱动一般是由一个h文件和c文件组成.h文件里面是一些结构体的定义以及提供给外部调用的函数声明,c文件就是这些函数的具体实现.无操作系统的软件结构是这样的:一个无限循环里,对设备中断的检测或轮询,并处理. 应用工程师在写代码的时候