(转)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 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,类型为struct i2c_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总线),当然它还可以实现其它附加功能,比如改变摄像头的分辨率,调节亮度、对比度等等,但是这里都没有实现。里面我们自己定义了一个结构体:struct ov9650_state

65 struct ov9650_state { 
        66        struct ov9650_platform_data *pdata;
        67        struct v4l2_subdev sd;
        68        struct v4l2_pix_format pix;
        69        struct v4l2_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 };

这里面唯一需要关心的成员就是第二项struct v4l2_subdev sd,内核中用struct v4l2_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        const struct 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        return i2c_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        struct i2c_adapter *adapter, const char *module_name,
        900        struct i2c_board_info *info, const unsigned short *probe_addrs)
        901 {
        902        struct v4l2_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                will be 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                and then c->driver is still NULL until the module is finally
        923                loaded. This delay-load mechanism doesn‘t work if other drivers
        924                want to use the i2c device, so explicitly loading the module
        925                is the best alternative. */
        926        if (client == NULL || client->driver == NULL)
        927                goto error;
        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                goto error;
        933        sd = i2c_get_clientdata(client);
        934 
        printk("2.============================================================\n");
        935 
        936        /* Register with the v4l2_device which increases the module‘s
        937                use count 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                        callback as the .s_config is not mandatory */
        947                int err = 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                        we must unregister the client. */
        959                if (client && sd == NULL)
        960                        i2c_unregister_device(client); 
        961                return sd;
        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                return ret;
        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/

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

时间: 2024-11-17 01:35:44

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

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

平台linuxstructlinux内核videocam 说明:        理解摄像头驱动需要四个前提:        1)摄像头基本的工作原理和S5PC100集成的Camera控制器的工作原理        2)platform_device和platform_driver工作原理        3)Linux内核V4L2驱动架构        4)Linux内核I2C驱动架构 1. 摄像头工作原理 OV9650/9655是CMOS接口的图像传感器芯片,可以感知外部的视觉信号并将其转换为数

《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文件就是这些函数的具体实现.无操作系统的软件结构是这样的:一个无限循环里,对设备中断的检测或轮询,并处理. 应用工程师在写代码的时候