原文出自http://blog.csdn.net/mirkerson/article/details/8170245
基于2.6.35内核的OV9650摄像头驱动分析
驱动分析:
打开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);
分配内存 其实这个函数主要执行的功能是调用了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 //结束视频显示函数
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/38400.html