摄像头驱动
-
2023年4月16日发(作者:怎么查对方qqip)FS_S5PC100平台上LinuxCamera驱动开发详解(⼀).
说明:
理解摄像头驱动需要四个前提:
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(8bits,4:2:2,Interpolatedcolor).⼀定要搞清楚格式,后⾯的驱动⾥⾯设置的格式⼀定要和这个格式⼀致。
OV9650⾥⾯有很多寄存器需要配置,配置这些寄存器就需要通过芯⽚⾥⾯的SCCB总线去配置。SCCB其实是⼀种弱化的I2C总线。我们
可以直接把摄像头接在S5PC100的I2C控制器上,利⽤I2C总线去读写寄存器,当然直接使⽤GPIO模拟I2C也可以实现读写。我们的驱动代码⾥两种操作模式都实现了。
从OV9650采集过来的数据没法直接交给CPU处理。S5PC100芯⽚⾥⾯集成了Camera控制器,叫FIMC(FullyInteractiveMobile
Camera)。摄像头需要先把图像数据传给控制器,经过控制器处理(裁剪拉升后直接预览或者编码)之后交给CPU处理。
实际上摄像头⼯作需要的时钟也是FIMC给它提供的。2.驱动开发思路
因为驱动程序是承接硬件和软件的桥梁,因此开发摄像头驱动我们要搞清楚两⽅⾯的内容:第⼀是摄像头的硬件接⼝,也就是它是怎么和芯
⽚连接的,如何控制它,如何给摄像头复位以及传送数据的格式等等;第⼆是摄像头的软件接⼝,Linux内核⾥⾯摄像头属于标准的V4L2设备,但是这个摄像头只是⼀个传感器,具体的操作都需要通过FIMC来控制,这看起来关系⽐较复杂。
相⽐较⽽⾔,硬件接⼝容易搞懂,通过读芯⽚⼿册和原理图基本上就没有问题了,软件接⼝⽐较复杂,主要中间有⼀个Camera控制器。下
⾯主要集中分析软件接⼝。3.硬件接⼝
摄像头的硬件原理图如下:
拿到原理图,我们需要关注的是1、2两个管脚分别连接到I2C_SDA1和I2C_SCL1,这说明可以通过I2C控制器1来配置摄像头。另外调试摄像头的时候,可以根据这个原理图使⽤⽰波器来测量波形以验证代码是否正确。
这⾥还需要注意的是开发驱动之前最好⽤万⽤表测量摄像头的各个管脚是否和芯⽚连接正确,否则即使代码没有问题也看不到图像。另外,还需要仔细阅读芯⽚⼿册⾥Camera控制器⼀章的描述。主要是明确以下信息:
FIMC⽀持以上三种视频⼯业标准,OV9650⽀持ITU-R601YcbCr8-bitmode,这对后⾯的驱动编写⾮常重要。
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__v4l2.c
这些源码⾥⾯最基础的是fimc_dev.c,这⾥⾯注册了⼀个platform_driver,在相应的平台代码⾥⾯有对应的platform_device的描述。这种SOC上的控制器⼀般都会挂接在platform_bus上以实现在系统初始化时的device和driver的匹配。
在driver的probe函数⾥⾯,主要完成了资源获取以及v4l2设备的注册。因为FIMC⼀共有三套⼀样的控制器(fimc0,fimc1,fimc2),所以驱动⾥使⽤了⼀个数组来描述:
structvideo_devicefimc_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设备的基本操作,定义如下:
staticconststructv4l2_file_operationsfimc_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⾥⾯:
conststructv4l2_ioctl_opsfimc_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_4l2-overlay设备的操作实现:fimc_overlay.c
这些代码其实都是和具体硬件操作⽆关的,这个驱动把所有操作硬件寄存器的代码都写到⼀个⽂件⾥⾯了,就是fimc40_regs.c。这样把硬
件相关的代码和硬件⽆关的代码分开来实现是⾮常好的⽅式,可以最⼤限度的实现代码复⽤。这些驱动源码的组织关系如下:
4-2:FIMC驱动的Camera接⼝分析
接⼝的关键还是在于fimc_dev.c⾥的probe函数。probe⾥⾯会调⽤⼀个函数叫fimc_init_global(),这⾥⾯会完成摄像头的分配以及时钟的获取。这个函数的原型如下:
staticintfimc_init_global(structplatform_device*pdev)
这个platform_device是内核从平台代码那⾥传递过来的,⾥⾯包含的就是和具体平台相关的信息,其中就应该包含摄像头信息。函数的实现:
staticintfimc_init_global(structplatform_device*pdev){
structfimc_control*ctrl;
structs3c_platform_fimc*pdata;
//这个结构体就是⽤来描述⼀个摄像头的,先不管它⾥⾯的内容
//等会⼉在分析平台代码的时候可以看到它是如何被填充的
structs3c_platform_camera*cam;
structclk*srclk;
intid,i;//获得平台信息
pdata=to_fimc_plat(&pdev->dev);id=pdev->id;//id号可能是0,1,2
ctrl=get_fimc_ctrl(id);//获得id号对应的fimc_control结构体指针
/*-arrangeordertobesure*/for(i=0;i cam=pdata->camera[i];//从平台数据取得camera的信息if(!cam) continue;//changebreaktocontinuebyys /*WriteBackdoesn'tneedclocksetting*/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:failedtogetmclksourcen",__func__); return-EINVAL;} //获得camera的时钟信息/*mclk*/ cam->clk=clk_get(&pdev->dev,cam->clk_name);if(IS_ERR(cam->clk)){ fimc_err("%s:failedtogetmclksourcen",__func__); return-EINVAL;} if(cam->clk->set_parent){ cam->clk->parent=srclk; cam->clk->set_parent(cam->clk,srclk);} /*Assigncameradevicetofimc*/ fimc_dev->camera[cam->id]=cam;//将从平台获得的camera分配给全局数据结构 //fimc_dev} fimc_dev->initialized=1; return0;} 可以看到这个函数实际上就是把camera的信息从平台数据那⾥取过来,然后分配给fimc__dev定义在fimc.h⾥⾯。类型为struct fimc_global,原型如下:/*global*/ structfimc_global{ structfimc_controlctrl[FIMC_DEVICES]; structs3c_platform_camera*camera[FIMC_MAXCAMS]; intinitialized;}; 现在我们需要看⼀下平台代码那⾥如何描述⼀个摄像头以及如何把抽象数据结构传递到平台数据⾥⾯。S5PC100SOC对应的平台代码位于: arch/arm/mach-s5pc100/mach-smdkc100.c我们是这样来描述⼀个camera的: #ifdefCONFIG_VIDEO_OV9650/*addbyysforov9650*/ staticstructs3c_platform_cameracamera_c={ .id=CAMERA_PAR_A,/*FIXME*/ .type=CAM_TYPE_ITU,/*2.0MITU*/.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*/ /*defaultresolforpreviewkindofthing*/ .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结构体的⼀个成员:/*Interfacesetting*/ staticstructs3c_platform_fimcfimc_plat={ .default_cam=CAMERA_PAR_A, .camera[2]=&camera_c, .hw_ver=0x40,}; 这⾥会把camera_c赋值给fimc_plat⾥的camera数组的第三个元素,之所以是第三个是因为Android的原因。这在分析Android的摄像头硬件抽象层时会有解释。 structs3c_platform_fimc这个结构体其实就是fimc对应的平台数据结构。在平台代码⾥,会由以下三个函数负责注册: s3c_fimc0_set_platdata(&fimc_plat); s3c_fimc1_set_platdata(&fimc_plat);s3c_fimc2_set_platdata(&fimc_plat); ⾄于这⼏个函数如何实现,这⾥就不分析了,有兴趣可以⾃⼰看代码。 也就是说只要平台代码这边我们填充了⼀个structs3c_platform_camera类型的结构体,然后把它添加到fimc_plat⾥⾯,fimc的驱动就能获得对应的Camera的信息。 - 摄像头驱动