linux摄像头内核驱动开发,FS_S5PC100平台上Linux Camera驱动开发详解(一)
說(shuō)明:
理解攝像頭驅(qū)動(dòng)需要四個(gè)前提:
1)攝像頭基本的工作原理和S5PC100集成的Camera控制器的工作原理
2)platform_device和platform_driver工作原理
3)Linux內(nèi)核V4L2驅(qū)動(dòng)架構(gòu)
4)Linux內(nèi)核I2C驅(qū)動(dòng)架構(gòu)
1. 攝像頭工作原理
OV9650/9655是CMOS接口的圖像傳感器芯片,可以感知外部的視覺(jué)信號(hào)并將其轉(zhuǎn)換為數(shù)字信號(hào)并輸出。通過(guò)下面的框圖可以清晰的看到它的工作原理:
我們需要通過(guò)XVCLK1給攝像頭提供時(shí)鐘,RESET是復(fù)位線,PWDN在攝像頭工作時(shí)應(yīng)該始終為低。HREF是行參考信號(hào),PCLK是像素時(shí)鐘,VSYNC是場(chǎng)同步信號(hào)。一旦給攝像頭提供了時(shí)鐘,并且復(fù)位攝像頭,攝像頭就開始工作了,通過(guò)HREF,PCLK和VSYNC同步傳輸數(shù)字圖像信號(hào)。數(shù)據(jù)是通過(guò)D0~D7這八根數(shù)據(jù)線并行送出的。
OV9650向外傳輸?shù)膱D像格式是YUV的格式,YUV是一種壓縮后的圖像數(shù)據(jù)格式,它里面還包含很多具體的格式類型,我們的攝像頭對(duì)應(yīng)的是YCbCr(8 bits, 4:2:2, Interpolated color).一定要搞清楚格式,后面的驅(qū)動(dòng)里面設(shè)置的格式一定要和這個(gè)格式一致。
OV9650里面有很多寄存器需要配置,配置這些寄存器就需要通過(guò)芯片里面的SCCB總線去配置。SCCB其實(shí)是一種弱化的I2C總線。我們可以直接把攝像頭接在S5PC100的I2C控制器上,利用I2C總線去讀寫寄存器,當(dāng)然直接使用GPIO模擬I2C也可以實(shí)現(xiàn)讀寫。我們的驅(qū)動(dòng)代碼里兩種操作模式都實(shí)現(xiàn)了。
從OV9650采集過(guò)來(lái)的數(shù)據(jù)沒(méi)法直接交給CPU處理。S5PC100芯片里面集成了Camera控制器,叫FIMC(Fully Interactive Mobile Camera)。攝像頭需要先把圖像數(shù)據(jù)傳給控制器,經(jīng)過(guò)控制器處理(裁剪拉升后直接預(yù)覽或者編碼)之后交給CPU處理。
實(shí)際上攝像頭工作需要的時(shí)鐘也是FIMC給它提供的。
2. 驅(qū)動(dòng)開發(fā)思路
因?yàn)轵?qū)動(dòng)程序是承接硬件和軟件的橋梁,因此開發(fā)攝像頭驅(qū)動(dòng)我們要搞清楚兩方面的內(nèi)容:第一是攝像頭的硬件接口,也就是它是怎么和芯片連接的,如何控制它,如何給攝像頭復(fù)位以及傳送數(shù)據(jù)的格式等等;第二是攝像頭的軟件接口,Linux內(nèi)核里面攝像頭屬于標(biāo)準(zhǔn)的V4L2設(shè)備,但是這個(gè)攝像頭只是一個(gè)傳感器,具體的操作都需要通過(guò)FIMC來(lái)控制,這看起來(lái)關(guān)系比較復(fù)雜。
相比較而言,硬件接口容易搞懂,通過(guò)讀芯片手冊(cè)和原理圖基本上就沒(méi)有問(wèn)題了,軟件接口比較復(fù)雜,主要中間有一個(gè)Camera控制器。下面主要集中分析軟件接口。
3. 硬件接口
攝像頭的硬件原理圖如下:
拿到原理圖,我們需要關(guān)注的是1、2兩個(gè)管腳分別連接到I2C_SDA1和I2C_SCL1,這說(shuō)明可以通過(guò)I2C控制器1來(lái)配置攝像頭。另外調(diào)試攝像頭的時(shí)候,可以根據(jù)這個(gè)原理圖使用示波器來(lái)測(cè)量波形以驗(yàn)證代碼是否正確。
這里還需要注意的是開發(fā)驅(qū)動(dòng)之前好用萬(wàn)用表測(cè)量攝像頭的各個(gè)管腳是否和芯片連接正確,否則即使代碼沒(méi)有問(wèn)題也看不到圖像。
另外,還需要仔細(xì)閱讀芯片手冊(cè)里Camera控制器一章的描述。主要是明確以下信息:
FIMC支持以上三種視頻工業(yè)標(biāo)準(zhǔn),OV9650支持ITU-R 601 YcbCr 8-bit mode,這對(duì)后面的驅(qū)動(dòng)編寫非常重要。
MPLL和APLL都可以作為攝像頭的時(shí)鐘源,不過(guò)推薦使用MPLL。這對(duì)后面的驅(qū)動(dòng)開發(fā)也有幫助。
4. 軟件接口(如何和FIMC驅(qū)動(dòng)對(duì)接)
硬件的問(wèn)題搞清楚之后就可以集中精力關(guān)注軟件的接口了。驅(qū)動(dòng)可以有兩種實(shí)現(xiàn)方法:第一種是把攝像頭驅(qū)動(dòng)做成普通的V4L2設(shè)備,直接調(diào)用FIMC里的寄存器實(shí)現(xiàn)視頻數(shù)據(jù)的捕捉和處理;第二種利用內(nèi)核已經(jīng)實(shí)現(xiàn)好的FIMC的驅(qū)動(dòng),通過(guò)某種接口形式,把我們的攝像頭驅(qū)動(dòng)掛接在FIMC驅(qū)動(dòng)之下。
這兩種方法第一種實(shí)現(xiàn)起來(lái)代碼量比較大,因?yàn)樾枰苯硬僮鱂IMC的寄存器,難度也大一些;第二種方法是利用內(nèi)核已經(jīng)做好的FIMC驅(qū)動(dòng),難點(diǎn)在于如何把攝像頭驅(qū)動(dòng)和FIMC驅(qū)動(dòng)整合起來(lái)。
在Android下面,第一種方法并不可行,因?yàn)镕IMC這個(gè)模塊不僅僅是一個(gè)攝像頭的控制接口,它還承擔(dān)著V4L2的output功能和overlay(顯示疊層)的功能,這兩個(gè)功能對(duì)Android的顯示系統(tǒng)非常重要。因此好的方案還是第二種,找到攝像頭驅(qū)動(dòng)和FIMC驅(qū)動(dòng)對(duì)接的接口,只要明確了這個(gè)接口,后面的事情就好辦了,工作量也不大。
4-1: FIMC驅(qū)動(dòng)的總體結(jié)構(gòu)分析
FIMC的驅(qū)動(dòng)在內(nèi)核中的位置:
drivers/media/video/samsung/fimc
fimc40_regs.c
fimc43_regs.c
fimc_capture.c
fimc_dev.c
fimc_output.c
fimc_overlay.c
fimc_v4l2.c
這些源碼里面基礎(chǔ)的是fimc_dev.c,這里面注冊(cè)了一個(gè)platform_driver,在相應(yīng)的平臺(tái)代碼里面有對(duì)應(yīng)的platform_device的描述。這種SOC上的控制器一般都會(huì)掛接在platform_bus上以實(shí)現(xiàn)在系統(tǒng)初始化時(shí)的device和driver的匹配。
在driver的probe函數(shù)里面,主要完成了資源獲取以及v4l2設(shè)備的注冊(cè)。因?yàn)镕IMC一共有三套一樣的控制器(fimc0, fimc1, fimc2),所以驅(qū)動(dòng)里使用了一個(gè)數(shù)組來(lái)描述:
struct video_device fimc_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函數(shù)里,調(diào)用video_register_device()來(lái)注冊(cè)這三個(gè)video_device,在用戶空間里就會(huì)在/dev下看到三個(gè)video設(shè)備節(jié)點(diǎn),video0,video1,video2. 每個(gè)video_device的成員fops對(duì)應(yīng)的是針對(duì)v4l2設(shè)備的基本操作,定義如下:
static const struct v4l2_file_operations fimc_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,
};
另一個(gè)成員ioctl_ops非常重要,因?yàn)樗菍?duì)v4l2的所有ioctl操作集合的描述。fimc_v4l2_ops定義在fimc_v4l2.c里面:
const struct v4l2_ioctl_ops fimc_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的驅(qū)動(dòng)實(shí)現(xiàn)了v4l2所有的接口,可以分為v4l2-input設(shè)備接口,v4l2-output設(shè)備接口以及v4l2-overlay設(shè)備接口。這里我們主要關(guān)注v4l2-input設(shè)備接口,因?yàn)閿z像頭屬于視頻輸入設(shè)備。
fimc_v4l2.c里面注冊(cè)了很多的回調(diào)函數(shù),都是用于實(shí)現(xiàn)v4l2的標(biāo)準(zhǔn)接口的,但是這些回調(diào)函數(shù)基本上都不是在fimc_v4l2.c里面實(shí)現(xiàn)的,而是有相應(yīng)的.c分別去實(shí)現(xiàn)。比如:
v4l2-input設(shè)備的操作實(shí)現(xiàn): fimc_capture.c
v4l2-output設(shè)備的操作實(shí)現(xiàn): fimc_output.c
v4l2-overlay設(shè)備的操作實(shí)現(xiàn): fimc_overlay.c
這些代碼其實(shí)都是和具體硬件操作無(wú)關(guān)的,這個(gè)驅(qū)動(dòng)把所有操作硬件寄存器的代碼都寫到一個(gè)文件里面了,就是fimc40_regs.c。這樣把硬件相關(guān)的代碼和硬件無(wú)關(guān)的代碼分開來(lái)實(shí)現(xiàn)是非常好的方式,可以大限度的實(shí)現(xiàn)代碼復(fù)用。
這些驅(qū)動(dòng)源碼的組織關(guān)系如下:
4-2: FIMC驅(qū)動(dòng)的Camera接口分析
接口的關(guān)鍵還是在于fimc_dev.c里的probe函數(shù)。probe里面會(huì)調(diào)用一個(gè)函數(shù)叫fimc_init_global(),這里面會(huì)完成攝像頭的分配以及時(shí)鐘的獲取。這個(gè)函數(shù)的原型如下:
static int fimc_init_global( struct platform_device *pdev )
這個(gè)platform_device是內(nèi)核從平臺(tái)代碼那里傳遞過(guò)來(lái)的,里面包含的就是和具體平臺(tái)相關(guān)的信息,其中就應(yīng)該包含攝像頭信息。
函數(shù)的實(shí)現(xiàn):
static int fimc_init_global(struct platform_device *pdev)
{
struct fimc_control *ctrl;
struct s3c_platform_fimc *pdata;
//這個(gè)結(jié)構(gòu)體就是用來(lái)描述一個(gè)攝像頭的,先不管它里面的內(nèi)容
//等會(huì)兒在分析平臺(tái)代碼的時(shí)候可以看到它是如何被填充的
struct s3c_platform_camera *cam;
struct clk *srclk;
int id, i;
//獲得平臺(tái)信息
pdata = to_fimc_plat(&pdev->dev);
id = pdev->id; //id號(hào)可能是0,1,2
ctrl = get_fimc_ctrl(id); //獲得id號(hào)對(duì)應(yīng)的fimc_control結(jié)構(gòu)體指針
/* Registering external camera modules. re-arrange order to be sure */
for (i = 0; i < FIMC_MAXCAMS; i++) {
cam = pdata->camera[i]; //從平臺(tái)數(shù)據(jù)取得camera的信息
if (!cam)
continue; // change break to continue by ys
/* WriteBack doesn't need clock setting */
if(cam->id == CAMERA_WB) {
fimc_dev->camera[cam->id] = cam;
break;
}
// 獲得時(shí)鐘源信息
srclk = clk_get(&pdev->dev, cam->srclk_name);
if (IS_ERR(srclk)) {
fimc_err("%s: failed to get mclk source\n", __func__);
return -EINVAL;
}
// 獲得camera的時(shí)鐘信息
/* mclk */
cam->clk = clk_get(&pdev->dev, cam->clk_name);
if (IS_ERR(cam->clk)) {
fimc_err("%s: failed to get mclk source\n", __func__);
return -EINVAL;
}
if (cam->clk->set_parent) {
cam->clk->parent = srclk;
cam->clk->set_parent(cam->clk, srclk);
}
/* Assign camera device to fimc */
fimc_dev->camera[cam->id] = cam; // 將從平臺(tái)獲得的camera分配給全局?jǐn)?shù)據(jù)結(jié)構(gòu)
// fimc_dev
}
fimc_dev->initialized = 1;
return 0;
}
可以看到這個(gè)函數(shù)實(shí)際上就是把camera的信息從平臺(tái)數(shù)據(jù)那里取過(guò)來(lái),然后分配給fimc_dev. fimc_dev定義在fimc.h里面。類型為struct fimc_global,原型如下:
/* global */
struct fimc_global {
struct fimc_control????????ctrl[FIMC_DEVICES];
struct s3c_platform_camera *camera[FIMC_MAXCAMS];
int????????initialized;
};
現(xiàn)在我們需要看一下平臺(tái)代碼那里如何描述一個(gè)攝像頭以及如何把抽象數(shù)據(jù)結(jié)構(gòu)傳遞到平臺(tái)數(shù)據(jù)里面。
S5PC100 SOC對(duì)應(yīng)的平臺(tái)代碼位于:
arch/arm/mach-s5pc100/mach-smdkc100.c
我們是這樣來(lái)描述一個(gè)camera的:
#ifdef CONFIG_VIDEO_OV9650
/* add by ys for ov9650 */
static struct s3c_platform_camera camera_c = {
.id = CAMERA_PAR_A, /* FIXME */
.type = CAM_TYPE_ITU, /* 2.0M ITU */
.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 */
/* default resol for preview kind of thing */
.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相關(guān)的所有信息。type代表攝像頭是ITU的接口,fmt代表攝像頭輸出的格式是ITU_601_YCBCR422_8BIT,order422代表YUV三個(gè)分量的順序是YcbCr。這些都和前面的描述相符。另外里面還有時(shí)鐘源的信息,時(shí)鐘的大小以及捕捉圖像的解析度,這里設(shè)置的是640x480(VGA模式),因?yàn)榻?jīng)過(guò)調(diào)試發(fā)現(xiàn)OV9650工作在VGA的模式下比較流暢清晰。Polarity代表信號(hào)的極性,具體的設(shè)置要和攝像頭本身的設(shè)置一致。
i2c_busnum是I2C總線的總線編號(hào),因?yàn)镾5PC100一共有兩條I2C總線(0和1),我們連在SDA1上,所以i2c_busnum是1。
camera_c是fimc_plat結(jié)構(gòu)體的一個(gè)成員:
/* Interface setting */
static struct s3c_platform_fimc fimc_plat = {
.default_cam = CAMERA_PAR_A,
.camera[ 2 ] = &camera_c,
.hw_ver = 0x40,
};
這里會(huì)把camera_c賦值給fimc_plat里的camera數(shù)組的第三個(gè)元素,之所以是第三個(gè)是因?yàn)锳ndroid的原因。這在分析Android的攝像頭硬件抽象層時(shí)會(huì)有解釋。
struct s3c_platform_fimc這個(gè)結(jié)構(gòu)體其實(shí)就是fimc對(duì)應(yīng)的平臺(tái)數(shù)據(jù)結(jié)構(gòu)。在平臺(tái)代碼里,會(huì)由以下三個(gè)函數(shù)負(fù)責(zé)注冊(cè):
s3c_fimc0_set_platdata(&fimc_plat);
s3c_fimc1_set_platdata(&fimc_plat);
s3c_fimc2_set_platdata(&fimc_plat);
至于這幾個(gè)函數(shù)如何實(shí)現(xiàn),這里就不分析了,有興趣可以自己看代碼。
也就是說(shuō)只要平臺(tái)代碼這邊我們填充了一個(gè)struct s3c_platform_camera類型的結(jié)構(gòu)體,然后把它添加到fimc_plat里面,fimc的驅(qū)動(dòng)就能獲得對(duì)應(yīng)的Camera的信息。
總結(jié)
以上是生活随笔為你收集整理的linux摄像头内核驱动开发,FS_S5PC100平台上Linux Camera驱动开发详解(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux解决软件依赖的命令,通过yum
- 下一篇: linux如何分析系统的堆栈,Linux