基于2.6.35内核的ov9650摄像头驱动分析

本文详细解析了OV9650摄像头驱动的工作原理,包括I2C设备驱动注册流程、camera控制器驱动程序及V4L2驱动程序如何协同工作实现对OV9650摄像头的控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


驱动分析:
打开 ov9650 驱动首先找到驱动入口函数
static int __init s5pc100_camera_init(void)

在这个函数中间做只有一句话
platform_driver_register(&s5pc100_camera_driver); 
这个就是平台驱动注册,所以在驱动注册之前我们需要构建 s5pc100_camera_driver 这个结构体,并且在内核中间我们需要添加平台资源信息,在这里平台资源的信息中间的 name 这个成员必须跟 s5pc100_camera_driver 这个结构体中间的成员 name 一致,这个在平台驱动注册的时候内核遍历内核的时候才能找到我们的加进去的平台资源配对成功,在 platform_driver_register 函数注册成功的时候,内核就会调用 5pc100_camera_driver 结构体中间的 probe 成员执行,我们先来看看 5pc100_camera_driver 这个结构体
struct platform_driver s5pc100_camera_driver = {

        .probe =s5pc100_camera_probe,
.remove =__devexit_p(s5pc100_camera_remove),
        .driver = {
               .name = "s5pc100-camif",
        },
};
当驱动加载 platform_driver_register 注册成功的时候内核就会调用 probe 成员,驱动卸载的时候就会调用 remove 成员,我们先来看看驱动注册的时候做了什么事情,来看看这个 probe 函数 s5pc100_camera_probe
camera_gpio_cfg();   
这个是 camera 接口的 io 楼设置看看这个函数的内容
static void camera_gpio_cfg(void)

{
               s3c_gpio_cfgpin(S5PC100_GPE0(0), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(1), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(2), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(3), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(4), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(5), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(6), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(7), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(0), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(1), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(2), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(3), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(4), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(5), S3C_GPIO_SFN(2));
               //s3c_gpio_setpull(cam->base_addr + S5PC100_GPB(2),S3C_GPIO_PULL_UP);
}
我们这里根据数据手册就可以发现这里就是设置 io 的功能为 camera 接口如下图


camera_dev = kzalloc(sizeof(struct s5pc100_camera_device),GFP_KERNEL);
这里是为 camera_dev 这个结构体申请空间,在来看看这个结构体中间有哪些成员
struct s5pc100_camera_device {

        void __iomem        *reg_base;  // 这个是配置寄存器经过映射后基地址,其实就是 s5pc100 camera 接口控制寄存器的首地址经过映射后的地址,这个地方应该存的是这个东东,后面可以验证
        int                        irq;//camera
所属中断的中断号
        struct clk               *fimc_clk;    //fimc
时钟结构体指针
        struct clk               *cam_clk;    //cam_clk
时钟结构体指针
        struct device        *dev;         //dev
设备结构体
        struct resource*addr_res;     //
设备资源中的地址资源指针
        struct resource*irq_res;       //
设备资源中的中断资源结构体指针
        struct resource*addr_req;     //
设备资源中地址内存资源结构体指针
        structs5pc100_camera_buffer *frame;     //
缓冲区结构体指针
        struct list_headinqueue, outqueue;   //
双向链表 首指针跟尾指针
        int state;

        int nbuffers;
        int stream;
        struct video_device*v4ldev;   // V4L2 驱动注册的时候需要的核心数据结构
        struct mutexopen_mutex, fileop_mutex;     //
定义两个互斥锁
        spinlock_tqueue_lock;          //
定义一个自旋锁
        wait_queue_head_t wait_open,wait_frame, wait_stream;   
定义三个等待队列头指针
        int input;          //
输入的图像格式选择
        int format;       //
现在的视频格式
        int srcHsize; / //
原图像尺寸
        int srcVsize;

        int wndVsize;       / 窗口头像大小
        int targetHsize;

        int targetVsize;     // 目标输出视频大小
        int cmdcode;     //
命令代码
};

camera_dev->dev =&cam->dev;  // 初始化 camera_dev->dev 这个成员
camera_dev->addr_res =platform_get_resource(cam, IORESOURCE_MEM, 0);//
初始化 camera_dev->addr_res 这个成员,将平台资源中的地址资源首地址取过来
camera_dev->irq_res  =platform_get_resource(cam, IORESOURCE_IRQ, 0);//
初始化 camera_dev->irq_res 这个成员,其实就是将平台资源中的 irq 资源取过来
iosize =resource_size(camera_dev->addr_res); 
其实这个函数是计算这个地址资源的大小,以方便后面的物理地址转换为虚拟地址
camera_dev->reg_base =ioremap(camera_dev->addr_res->start, iosize);//
初始化 camera_dev->reg_base 这个成员,跟上面说那个结构体哪里一样,就是地址资源经过映射后得到虚拟地址的首地址存储在这里
camera_dev->irq =camera_dev->irq_res->start;//
初始化 camera_dev->irq 这个成员,其实就是中断资源的第一个
camera_dev->fimc_clk =clk_get(camera_dev->dev, "fimc");//
初始化 camera_dev->fimc_clk 这个成员,其实就是通过 clk_get 函数获取 fimc 时钟并赋值给过去
clk_enable(camera_dev->fimc_clk);//
使能 fimc 时钟
camera_dev->cam_clk =clk_get(camera_dev->dev, "div_cam");//
初始化 camera_dev->cam_clk 这个成员,通过 clk_get 获取时钟 cam_clk 时钟
clk_set_rate(camera_dev->cam_clk,24000000);//
设置 cam_clk 的时钟为 24Mhz

camera_reset();  //camera
复位函数,来看看 camera 接口初始化做了什么事情
writel(readl(camera_dev->reg_base +S5PC100_CISRCFMT) | 1<< 31, camera_dev->reg_base + S5PC100_ CISRCFMT);

这里将 CISRCFMTn 这个寄存器的 31 位置 1 ,那么就是选择 ITU-R BT.601 YCbCr 8-bit mode enable
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) | 1<< 31, camera_dev->reg_base + S5PC100_CIGCTRL);

这里是将 CIGCTRLn 的第三十一位置 1 ,该位为 camera 的软复位位,这里写 1 ,产生复位
mdelay(10);//
延时等待复位
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) & ~(1<< 31), camera_dev->reg_base +S5PC100_CIGCTRL);

这里是将 CIGCTRLn 的第三十一位置 0 ,取消复位;
到这里这个函数就完了,我们接着往下看
sensor_reset();    //
外设复位
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) | 1<< 30, camera_dev->reg_base + S5PC100_CIGCTRL);

这里是将 CIGCTRLn 的第三十位置 1 ,该位为外设电源,这里写 1 ,外设断电复位
mdelay(10); 
延时等待复位
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) & ~(1<< 30), camera_dev->reg_base +S5PC100_CIGCTRL);
重新给外设上电
到这里这个函数结束
mdelay(10);   
延时 10ms 等待
for(i = 0; i <ARRAY_SIZE(s5pc100_sensor_table); i++)

        {
               s5pc100_sensor_table[i].init();
        }
这里是循环调用 s5pc100_sensor_table[i].init(); 这个函数表里面成员的 init 函数来初始化,用法跟汇编里面查表类似来看看这个循环里面做了什么事情,首先来看看这个函数表里面的函数
struct s5pc100_sensor s5pc100_sensor_table[] =

{
        {
               .init = ov9650_init,
               .cleanup = ov9650_exit,
        },
};
这里一看,变知道 i < ARRAY_SIZE(s5pc100_sensor_table); 这个应该等于 1 ,因为里面只有一个成员,所以,这个循环就执行了一个函数 ov9650_init, 在来看看这个函数做了一些什么事情
int ov9650_init(void)

{
        returni2c_add_driver(&ov9650_driver);
}
这个看看其实这里就是一个 i2c 设备的驱动注册程序,等于说,我们在这个 ov9650 这个 cmos 摄像头驱动里面包含了一个 i2c 设备的驱动,这个是为什么,其实我们这里去看看摄像头的硬件连接,其实我们这里就不难知道,其实 ov9650 跟我们 a8 板相连的时候使用到了 i2c1 接口,通过阅读 ov9650 的手册也知道,其实 ov9650 在初始化的时候需要设置内部寄存器,然后根据 ov9650 数据手册里面提供设置内部寄存器的时序图发现,其实这个时序是一个弱化的 i2c 协议,所以我们这里直接利用 i2c 控制器来连接,可以直接用这个接口来对其进行初始化。我们接下来再来看看这个 i2c1 设备驱动的注册过程吧,通过这个驱动程序,我们可以了解到,对 ov9650 初始化需要做些什么事情。
首先 i2c_add_driver(&ov9650_driver); 利用这个函数来注册驱动,在利用这个函数注册驱动注册之前,我们需要先去构建 ov9650_driver 这个结构体
static struct i2c_driver ov9650_driver = {

        .driver = {
               .name        = "ov9650",
               .owner        = THIS_MODULE,
        },
        .probe               = ov9650_probe,
        .remove               = __devexit_p(ov9650_remove),
        .id_table       = ov9650_id,
};
这里需要注意的是, .name 这个必须跟我们在 i2c1 资源哪里的 name 的名字一致,否则驱动注册不成功,当内核配对成功的时候,那么就会调用这个函数里面的 .probe 这个函数,那么我们来看看这个函数 ov9650_probe 里面做了什么事情
reg = 0x0A;

        ret = ov9650_reg_read(ov9650_client,reg, &PLDH);
首先这里是调用 ov9650_reg_read 函数来读取 ov9650 这个内部的 0A 单元中间的内容,那么这个单元的内容是什么,我们去看看 ov9650 手册便可以知道

这里一看很显然这里是产品编号,并且这个寄存器只读。 MSB 代表产品编号高八位
reg = 0x0B;

        ret =ov9650_reg_read(ov9650_client, reg, &PLDL);
这里跟上面差不多,读取 ov9650 内部 0X0B 单元里面的内容

这里一看还是产品编号,只读, LSB 代表是产品编号的第八位
if(((PLDH << 8)| PLDL) ==OV9650_PRODUCT_ID)

               printk("found sensor: product id = 0x%02x%02x!\n", PLDH,PLDL);
        else
               return -ENODEV;
这里就是判断我们读出来的产品 ID 编号是否与厂商给出的 ID 号一致
ov9650_init_regs();   //
这里就是应该是这个 I2C 初始化的重点,初始化 ov9650 内部寄存器
我们去看看这里做了那些事情
static void ov9650_init_regs(void)

{
        int i;
       
        for (i=0;i<ARRAY_SIZE(regs); i++)
        {
               if (regs[i].subaddr == 0xff)
               {
                       mdelay(regs[i].value);
                       continue;
               }
               ov9650_reg_write(ov9650_client, regs[i].subaddr, regs[i].value);
        }
}
跟上面那个初始化是一样的,这里也是用一个循环给 ov9650 内部的寄存器设置,是怎样设置的捏!我们仔细分析一下这个代码,得知当 regs[i].subaddr==0xff 的时候,那么我们就在这里做延时,延时的时长为 regs[i].value 毫秒,然后结束本次循环,然后接着下次循环,如果 regs[i].subaddr =0xff 的时候,我们就将 regs[i].subaddr 这个值作为 ov9650 内部的地址写入 regs[i].value 这个值,那么其实这个函数就是对 ov9650 内部的寄存器进行一个初始化,我们来看看这个表
static struct ov9650_reg

{
        unsigned charsubaddr;
        unsigned char value;
}regs[] = {
        /* OV9650intialization parameter table for VGA application */
        {0x12, 0x80},       // Camera Soft reset. Self cleared after reset.
        {CHIP_DELAY, 10},
        {0x11,0x80},{0x6a,0x3e},{0x3b,0x09},{0x13,0xe0},{0x01,0x80},{0x02,0x80},{0x00,0x00},{0x10,0x00},
       {0x13,0xe5},{0x39,0x43},{0x38,0x12},{0x37,0x00},{0x35,0x91},{0x0e,0xa0},{0x1e,0x04},{0xA8,0x80},
       {0x12,0x40},{0x04,0x00},{0x0c,0x04},{0x0d,0x80},{0x18,0xc6},{0x17,0x26},{0x32,0xad},{0x03,0x00},
       {0x1a,0x3d},{0x19,0x01},{0x3f,0xa6},{0x14,0x2e},{0x15,0x10},{0x41,0x02},{0x42,0x08},{0x1b,0x00},
       {0x16,0x06},{0x33,0xe2},{0x34,0xbf},{0x96,0x04},{0x3a,0x00},{0x8e,0x00},{0x3c,0x77},{0x8B,0x06},
       {0x94,0x88},{0x95,0x88},{0x40,0xc1},{0x29,0x3f},{0x0f,0x42},{0x3d,0x92},{0x69,0x40},{0x5C,0xb9},
       {0x5D,0x96},{0x5E,0x10},{0x59,0xc0},{0x5A,0xaf},{0x5B,0x55},{0x43,0xf0},{0x44,0x10},{0x45,0x68},
       {0x46,0x96},{0x47,0x60},{0x48,0x80},{0x5F,0xe0},{0x60,0x8c},{0x61,0x20},{0xa5,0xd9},{0xa4,0x74},
       {0x8d,0x02},{0x13,0xe7},{0x4f,0x3a},{0x50,0x3d},{0x51,0x03},{0x52,0x12},{0x53,0x26},{0x54,0x38},
       {0x55,0x40},{0x56,0x40},{0x57,0x40},{0x58,0x0d},{0x8C,0x23},{0x3E,0x02},{0xa9,0xb8},{0xaa,0x92},
       {0xab,0x0a},{0x8f,0xdf},{0x90,0x00},{0x91,0x00},{0x9f,0x00},{0xa0,0x00},{0x3A,0x01},{0x24,0x70},
       {0x25,0x64},{0x26,0xc3},{0x2a,0x00},{0x2b,0x00},{0x6c,0x40},{0x6d,0x30},{0x6e,0x4b},{0x6f,0x60},
        {0x70,0x70},{0x71,0x70},{0x72,0x70},{0x73,0x70},{0x74,0x60},{0x75,0x60},{0x76,0x50},{0x77,0x48},
       {0x78,0x3a},{0x79,0x2e},{0x7a,0x28},{0x7b,0x22},{0x7c,0x04},{0x7d,0x07},{0x7e,0x10},{0x7f,0x28},
       {0x80,0x36},{0x81,0x44},{0x82,0x52},{0x83,0x60},{0x84,0x6c},{0x85,0x78},{0x86,0x8c},{0x87,0x9e},
       {0x88,0xbb},{0x89,0xd2},{0x8a,0xe6},
};
哇!咋一看!这些数据,由于这些数据较多,所以这里就利用这个循环来进行对这些 ov9650 内部的寄存器进行一个初始化,初始化哪些寄存器,有什么样的功能我们可以通过阅读 ov9650 的数据手册可以知道。通过查阅 ov9650 的第十章 interface 这张里面的寄存器表就知道。这里先提一下,首先对 ov9650 进行软复位,然后延时 10ms ,然后就开始对 ov9650 内部的寄存器进行设置
到这里我们 I2C1 的驱动注册完成,那么也就是对 ov9650 内部寄存器的初始化完成。我们在回去接着看看程序注册的时候还做了什么
camera_dev->v4ldev = video_device_alloc();
向系统申请分配一个表示 Radio 设备的 structvideo_device 结构体变量为后面的 V4L2 驱动的注册做准备, v4l2 这个东西就是给视频驱动提供了统一的接口,使得应用程序可以使用统一的 API 函数操作不同的视频设备,极大地简化了视频系统的开发和维护。
camera_init();       //
这个函数其实是对上面讲到的那个结构体 camera_dev 中的几个成员分别初始化,初始化等待队列,初始化互斥锁,初始化自旋锁,下面贴出代码
void camera_init(void)

{
       mutex_init(&camera_dev->open_mutex);
       mutex_init(&camera_dev->fileop_mutex);   初始化互斥锁
       spin_lock_init(&camera_dev->queue_lock); 
初始化自旋锁
       init_waitqueue_head(&camera_dev->wait_frame);

       init_waitqueue_head(&camera_dev->wait_stream);  初始化等待队列
       init_waitqueue_head(&camera_dev->wait_open);

}

strcpy(camera_dev->v4ldev->name,"cam->base_addr + S5PC100 Soc Camera"); 这个是对 camera_dev 这个结构体中间的 v4ldev 这个结构体的成员 name 初始化,为后面 v4l2 注册做准备
camera_dev->v4ldev->fops =&s5pc100_camera_fops;//
camera_dev->v4ldev->fops 这个成员初始化,其实是对 v4l2 构建操作方法,我们来看看 s5pc100_camera_fops 这个操作方法实现了那些功能
static struct v4l2_file_operationss5pc100_camera_fops = {

        .owner        = THIS_MODULE,
        .open       = s5pc100_camera_open,       
        .release =s5pc100_camera_release,
        .ioctl        = s5pc100_camera_ioctl,
        .mmap       = s5pc100_camera_mmap,
};
分别实现 open ioctl release ,还有 mmap 这四个函数在分析的最后分析这几个操作方法的实现
camera_dev->v4ldev->release =video_device_release;//
初始化 camera_dev->v4ldev->release 这个成员
video_set_drvdata(camera_dev->v4ldev,camera_dev);//
这个函数其实是在初始化 camera_dev->v4ldev –>dev->p->driver_data 这个成员,也就是将 camera_dev 结构体指针传到 camera_dev->v4ldev –>dev->p->driver_data 这地方去
ret =video_register_device(camera_dev->v4ldev, VFL_TYPE_GRABBER,video_nr);//
这里调用 video_register_device 这个函数来注册一个 v4l2 驱动
if((ret = request_irq(camera_dev->irq,cam_isr, IRQF_DISABLED | IRQF_TRIGGER_RISING , "camera", NULL)) <0)

        {
               printk("request irq %d failed\n", camera_dev->irq);
        }
这个地方是为 camera 这个接口注册中断函数;
其实在这个中断函数中间什么事情也没有做就是一个打印信息,在这里可以不用关心,贴出源码如下:
irqreturn_t cam_isr(int irq, void *devid)

{
        printk("incam_isr\n");
        //wake_up_interruptible(&camera_dev->wait_frame);
        return IRQ_HANDLED;
}
init_image_buffer(camera_dev);// 初始化视频缓冲区,在这里我们去看看这个函数实现怎样的功能
cam->frame = img_buff;   //
初始化 cam->frame 这个成员
size = MAX_WIDTH * MAX_HEIGHT * formats[3].depth/ 8;//
计算缓冲区的大小,在这里 formats[3]. 其实是用的 RGB-32 (B-G-R) 的模式,这样现实一个 640*480 大小的图片,缓冲区的大小为 640*480*32/8 个字节,这里除八是因为 32 是位数所以要除 8 转换为字节数
order = get_order(size);//get_order
函数可以用来从一个整数参数 size( 必须是 2 的幂 ) 中提取 order
img_buff[0].order = order;

       img_buff[0].virt_base = __get_free_pages(GFP_KERNEL|GFP_DMA, img_buff[0].order);
        img_buff[0].img_size= size;
初始化 img_buff[0]. 中的成员变量, __get_free_pages 这个函数是通过之前获取到的 order 来分配内存,并将分配到的内存首地址给 img_buff[0].virt_base
img_buff[0].phy_base = img_buff[0].virt_base -PAGE_OFFSET + PHYS_OFFSET;

这里是计算 DMA 地址,这里是怎样计算的捏!!其实这里就是一个 dma 的虚拟地址,在驱动中间我们需要用其真是的物理地址,所以我们这个时候的算法就是:虚拟地址 - 虚拟地址偏移 = 地址偏移量;地址偏移量 + 物理地址偏移 = 我们要求的物理地址了

到这里 init_image_buffer 函数已经看完了
init_camif_config(camera_dev);  
初始化 camera 接口寄存器的一些配置,进去看看这个函数是怎样设置的
cam->format = 3;       
设置数据格式可以从前面 size = MAX_WIDTH * MAX_HEIGHT * formats[3].depth 这个地方看到

        cam->srcHsize =640;        //
设置源数据的水平像素    CISRCFMTn 寄存器的 SrcHsize_CAM
        cam->srcVsize =480;        //
设置源数据的垂直像素    CISRCFMTn 寄存器的 SrcVsize_CAM
        cam->wndHsize =640;

        cam->wndVsize =480; //lht

        cam->targetHsize= cam->wndHsize;// 目标图像的水平像素
        cam->targetVsize= cam->wndVsize; //
目标图像的垂直像素

update_camera_config(cam,(u32)-1);  //
根据 cam 中的一些信息配置 camera 的配置寄存器这个函数的内容为 update_camera_regs(cam);   再去看看这个函数
update_source_fmt_regs(cam);

update_target_fmt_regs(cam);
先来看看 update_source_fmt_regs(cam); 这个函数
fg = (1<<31)                                      // ITU-R BT.601 YCbCr 8-bit mode

|(0<<30) // CB,Cr value offset cntrolfor YCbCr
                              |(cam->srcHsize<<16)        // target image width
                              |(0<<14)                               // input order is YCbYCr
                              |(cam->srcVsize<<0);        // source image height
        writel(cfg,cam->reg_base + S5PC100_CISRCFMT);

        cfg = (1<<15)
                       |(1<<14)
                       |(1<<30)
                       |(1<<29);
        writel(cfg,cam->reg_base + S5PC100_CIWDOFST);

        cfg = (1<<26)
                       |(1<<29)
                       |(1<<16)
                       |(1<<7)
                       |(0<<0);
        writel(cfg,cam->reg_base + S5PC100_CIGCTRL);
        writel(0,cam->reg_base + S5PC100_CIWDOFST2);
现在我将这个程序直接整理出来看看
CISRCFMTn=
1<<31 |(0<<30)|(640<<16)|(0<<14)|(480<<0)=
CIWDOFSTn=(1<<15)|(1<<14)|(1<<30)|(1<<29)

CIGCTRLn=(1<<29)|(1<<26)|(1<<7)|(0<<0)
这个就是这个函数所实现的功能   可以查阅 s5pc100 芯片手册查看
再来看看 update_target_fmt_regs(cam); 这个函数
writel(img_buff[0].phy_base, cam->reg_base +S5PC100_CIOYSA1);

               cfg = (2 << 29) | (cam->targetHsize << 16) |(cam->targetVsize << 0)|(3<<14);
               writel(cfg, cam->reg_base + S5PC100_CITRGFMT);
               calculate_prescaler_ratio_shift(cam->srcHsize, cam->targetHsize,&prescaler_h_ratio, &h_shift);
               calculate_prescaler_ratio_shift(cam->srcVsize, cam->targetVsize,&prescaler_v_ratio, &v_shift);
               main_h_ratio = (cam->srcHsize << 8) / (cam->targetHsize<< h_shift);
               main_v_ratio = (cam->srcVsize << 8) / (cam->targetVsize<< v_shift);
               cfg = ((10 - (h_shift + v_shift)) << 28) | (prescaler_h_ratio<< 16) | (prescaler_v_ratio << 0);  
               writel(cfg, cam->reg_base + S5PC100_CISCPRERATIO);
               cfg = (cam->targetHsize << 16) | (cam->targetVsize <<0);
               writel(cfg, cam->reg_base + S5PC100_CISCPREDST);
               cfg = (main_h_ratio << 16) | (main_v_ratio << 0);
               writel(cfg, cam->reg_base + S5PC100_CISCCTRL);
               cfg = cam->targetVsize * cam->targetHsize;
               writel(cfg, cam->reg_base + S5PC100_CITAREA);

               cfg = (cam->targetVsize << 16) | (cam->targetHsize <<0);
               writel(cfg, cam->reg_base + S5PC100_ORGOSIZE);

CIOYSA1=_get_free_pages(GFP_KERNEL|GFP_DMA,img_buff[0].order); 申请的首地址
CITRGFMTn=
2<<29 |(640<<16)|(3<<14)|(480<<0)
CISCPRERATIOn=(10<<28)|(1<<16)|(1<<0)

CISCPREDSTn=(640<<16)|(480<<0)
CISCCTRLn=(((640<<8)/(640<<1))<<16)|(((480<<8)/(480<<1))<<0)
CITAREAn=640*480
ORGOSIZEn=(640<<16)|(480<<0)
这个函数就做了这些事情,对照数据手册可以去查看设置那些功能
到这里我们 ov9650 的驱动已经分析玩了
总结:在这个 ov9650 驱动中间我们使用到了 I2C1 的驱动,然后使用到 camera 控制器驱动程序,以及 v4l2 驱动程序,将这三个驱动融合共同实现对 ov9650 的控制以及现实,以及给上层提供良好的接口


static struct v4l2_file_operationss5pc100_camera_fops = {

        .owner        = THIS_MODULE,
        .open       = s5pc100_camera_open,       
        .release =s5pc100_camera_release,
        .ioctl        = s5pc100_camera_ioctl,
        .mmap       = s5pc100_camera_mmap,
};

首先来看 open 函数
static int s5pc100_camera_open(struct file*filp)

{
        printk("cameraopen\n");

        return 0;
} 这个函数其实什么也没有做就是打印一条信息
再看看 s5pc100_camera_release 这个函数
static int s5pc100_camera_release(struct file*filp)

{
        printk("camerarelease\n");
        return 0;
} 这个函数跟 open 都是没有做什么事情
接下来我们先来看看。 Mmap 函数这个函数比较短
static int s5pc100_camera_mmap(struct file*filp, struct vm_area_struct *vma)

{
        unsigned long size =vma->vm_end - vma->vm_start,
                                start = vma->vm_start,
                                 off= vma->vm_pgoff;


        if(!(vma->vm_flags & (VM_WRITE | VM_READ))) {
               return -EACCES;
        }

       if(io_remap_pfn_range(vma, start, off, size, vma->vm_page_prot))
               return -EAGAIN;

        return 0;
} 通过 io_remap_pfn_range 这个函数实现内核空间映射到用户空间
这样就实现了 mmap 函数,再来看看 loctl 的实现
static long s5pc100_camera_ioctl(struct file*filp,

               unsigned int cmd, unsigned long arg)
{
        structs5pc100_camera_device *cam = video_drvdata(filp);
        int err = 0;

        if (mutex_lock_interruptible(&cam->fileop_mutex))
               return -ERESTARTSYS;


        err =s5pc100_ioctl_v4l2(filp, cmd, (void __user *)arg);

       mutex_unlock(&cam->fileop_mutex);

        return err;
}
从这里看出这里转调 s5pc100_ioctl_v4l2 这个函数来实现 ioctl 的那我们再去看看这个函数做了什么事情
static long s5pc100_ioctl_v4l2(struct file*filp,

               unsigned int cmd, void __user *arg)
{
        structs5pc100_camera_device *cam = video_drvdata(filp);

        switch (cmd) {
        caseVIDIOC_QUERYCAP:
               //printk("VIDIOC_QUERYBUF\n");
               return s5pc100_vidioc_querycap(cam, arg);
        case VIDIOC_REQBUFS:
               //printk("VIDIOC_REQBUFS\n");
               return s5pc100_vidioc_reqbufs(cam, arg);
        caseVIDIOC_QUERYBUF:
               //printk("VIDIOC_QUERYBUF\n");
               return s5pc100_vidioc_querybuf(cam, arg);
        case VIDIOC_QBUF:
               //printk("VIDIOC_QBUF\n");
               return s5pc100_vidioc_qbuf(cam, arg);
        case VIDIOC_DQBUF:
               //printk("VIDIOC_DQBUF\n");
               return s5pc100_vidioc_dqbuf(cam, filp, arg);
        caseVIDIOC_STREAMON:
               //printk("VIDIOC_STREAMON\n");
               return s5pc100_vidioc_streamon(cam, arg);
        case VIDIOC_STREAMOFF:
               //printk("VIDIOC_STREAMOFF\n");
               return s5pc100_vidioc_streamoff(cam, arg);
        caseVIDIOC_QUERYCTRL:
        case VIDIOC_CROPCAP:
        case VIDIOC_G_PARM:
        case VIDIOC_S_PARM:
        case VIDIOC_S_CTRL:
        case VIDIOC_G_CTRL:
        caseVIDIOC_ENUMINPUT:
        case VIDIOC_G_CROP:
        case VIDIOC_S_CROP:
        caseVIDIOC_ENUM_FMT:
        case VIDIOC_G_FMT:
        case VIDIOC_TRY_FMT:
        case VIDIOC_S_FMT:
        case VIDIOC_ENUM_FRAMESIZES:
        caseVIDIOC_G_JPEGCOMP:
        caseVIDIOC_S_JPEGCOMP:
        case VIDIOC_G_STD:
        case VIDIOC_S_STD:
        caseVIDIOC_QUERYSTD:
        case VIDIOC_ENUMSTD:
        caseVIDIOC_QUERYMENU:
        caseVIDIOC_ENUM_FRAMEINTERVALS:
               return 0;
               return -EINVAL;

        default:
               return -EINVAL;

        }
}
我们将上面实现的函数功能都来看看
case VIDIOC_QUERYCAP:               return s5pc100_vidioc_querycap(cam, arg);

查询一个驱动的功能         源码为下
{

        structv4l2_capability cap = {
               .driver = "s5pc100_camera",
               .capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE,
        };

        strlcpy(cap.card,cam->v4ldev->name, sizeof(cap.card));

        if (copy_to_user(arg,&cap, sizeof(cap)))
               return -EFAULT;

        return 0;
}
其实就睡返回 cap 这个结构体给用户空间
case VIDIOC_REQBUFS:             return s5pc100_vidioc_reqbufs(cam,arg);

分配内存 &nbsp;     其实这个函数主要执行的功能是调用了 s5pc100_request_buffers 这个函数,我们来看看这个函数的功能
{

        int i;

        cam->nbuffers =1;
        count = count >cam->nbuffers ? cam->nbuffers : count;

        for (i = 0; i <count; i++) {
               cam->frame[i].buf.index = i;
               cam->frame[i].buf.m.offset = cam->frame[i].phy_base;
               cam->frame[i].buf.length = cam->frame[i].img_size;
               cam->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
               cam->frame[i].buf.sequence = 0;
               cam->frame[i].buf.field = V4L2_FIELD_NONE;
               cam->frame[i].buf.memory = V4L2_MEMORY_MMAP;
               cam->frame[i].buf.flags = 0;
        }

        returncam->nbuffers;
}
注意这个函数中间 cam->frame[i].buf.m.offset = cam->frame[i].phy_base;
               cam->frame[i].buf.length = cam->frame[i].img_size;

这两句话很重要的, cam->frame[i].phy_base 这个就是之前算出来的物理地址的首地址, cam->frame[i].img_size; 这个就是那个地址的大小,这里看到返回值是 cam->nbuffers 是分配的内存数量,这里程序不管怎样都值分配一个


s5pc100_vidioc_querybuf(cam, arg);
再来看看这个函数做了什么事情
最主要的功能就是 memcpy(&b, &cam->frame[0].buf, sizeof(b)); 这句话了,将上面那个函数里面准备的物理地址取出来这样我们就可以将这个物理地址映射为虚拟地址来使用

s5pc100_vidioc_streamon   //
开始视频显示函数
s5pc100_vidioc_streamoff&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//
结束视频显示函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值