zoukankan      html  css  js  c++  java
  • (转)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 ",
                                    __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======================================= ",client->addr);
            914        }else{
            915                 client = i2c_new_device(adapter, info);
            916 //                printk("r2.====================client_addr : 0x%x, clent->driver->id :
            0x%x======================================== ",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.============================================================ ");
            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.============================================================ ");
            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.============================================================ ");
            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 ", __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
  • 相关阅读:
    每日一水 POJ8道水题
    编译和使用 MySQL C++ Connector
    j2ee model1模型完成分页逻辑的实现 详解!
    DB查询分析器访问EXCEL时,要在表名前后加上中括弧或双引号
    指向结构体变量的指针
    EOSS V3.0 企业运营支撑系统(基于RBAC原理的权限管理)
    MybatisGen1.0 Mybatis JavaBean Mapper生成工具
    The table name must be enclosed in double quotation marks or sqare bracket while accessing EXCEL by
    资源-Android:Android
    软件-开发软件:Android Studio
  • 原文地址:https://www.cnblogs.com/lihaiping/p/cameradrv2.html
Copyright © 2011-2022 走看看