而我們的產品,基於Camera的正常工作,必須解決此類問題。選擇了其中一款使用CSI接口Camera的平臺:全志A20來做研究和解決。
0. 基礎知識 :
0.0. 目錄結構:
除了常見的,在linux-3.3/drivers/media/video/ 目錄下有v4l2一些文件外,全志還在另一個目錄下放置了csi以及做支持模組芯片的代碼:linux-3.3/drivers/media/video/sunxi_csi/
其中,csi部分在csi0或者csi1中。 模組對應代碼在device中。
這些代碼,基本組成了全志CSI 接口 Camera的全部驅動代碼。
0.1. CSI:
COMS Sensor Interface:
CSI接口通常從COMS Sensor,Video Encoder和其它視頻輸出設備收集數據。
0.2. 模組芯片:
手頭這塊板子上自帶GT2005模組。所以需要看linux-3.3/drivers/media/video/sunxi_csi/device/gt2005.c
1. 驅動結構:
CSI 模組Driver的基本思路其實挺簡單,就是需要創建主設備號爲81的device. /dev/videoX。並將其open,close,ioctl 與Driver聯繫起來,最終反應到Sensor (gt2005)層面。
我們首先從linux-3.3/drivers/media/video/sunxi_csi/csi0部分開始着手,這個文件夾內的三個文件:sunxi_csi_reg.c ,sunxi_drv_csi.c, sunxi_csi_reg.h 最終會生成sunxi_csi0.ko。 會被以modules形式insmod 進系統。
1.1:CSI0 modules學習:
當sunxi_csi0.ko被insmod時,sunxi_drv_csi.c中的csi_init()被調用。
csi_init() 中註冊了一個driver--csi_driver.
platform_driver_register(&csi_driver);
csi_driver中有偵測函數:.probe = csi_probe,
Sam的理解是:因爲CSI接口一直連接着,所以當此Driver剛被註冊時,csi_probe就被調用。
請注意:csi_probe中作了相當多事情,是sunxi_csi0.ko的重點。
它做了很多準備工作.並通過video_register_device()創建了Device. /dev/videoX. 並將此device的操作與v4l2_fops聯繫起來。
所以,對此Device的open,close, ioctl等, 都使用v4l2_fops中提供的函數指證。
而我們看v4l2_fops中的ioctl. 它最終是 video_device ->fops->ioctl()。
其實就是:csi_template 中的:.fops = &csi_fops, 的ioctl. 即:video_ioctl2
video_ioctl2,在v4l2-ioctl.c中定義:其實就是:
static long __video_do_ioctl(struct file *file,unsigned int cmd, void *arg)
所以,經過CSI程序的鏈接。最終,對Device /dev/videoX的操作,最終都被歸結到__video_do_ioctl
同時,csi_ioctl_ops也被鏈接到ioctl_ops。請注意,未來要用到這裏。
看到這裏,大家都會疑惑。 這怎麼和Sensor操作練習起來了。這部分只將CSI和V4L2 連起來了。
這就是下一部要說的gt2005.ko了。
1.2: GT2005模塊學習:
同樣道理,insmod gt2005.ko時,gt2005.c中的init_sensor()會被啓動。
在init_sensor()中,只執行了:i2c_add_driver(&sensor_driver); 其中,sensor_driver 同樣會在驅動安裝後立刻啓動。
static int sensor_probe(struct i2c_client *client,const struct i2c_device_id *id)
v4l2_i2c_subdev_init(sd, client, &sensor_ops);
這一句代碼是關鍵。
它將sensor_ops加入v4l2_subdev->ops中去了。而v4l2_subdev類型的sd被加入list.未來會用。(關鍵3)
分析下:sensor_ops.
static const struct v4l2_subdev_ops sensor_ops = {
.core = &sensor_core_ops,
.video = &sensor_video_ops,
};
也就是有:core和video 兩個。一定要注意了,這裏和未來很有關係。(關鍵4)
__video_do_ioctl()最終會通過vfd->ioctl_ops 來做實際動作。而ioctl_ops是誰呢?就是在在sunxi_drv_csi.c中的csi_ioctl_ops
2. ioctl的完整流程:
當用戶open /dev/videoX後,使用ioctl()時,
例1:得到能力集:
rel = ioctl(Handle, VIDIOC_QUERYCAP, &cap);
通過上面的分析, 它通過 __video_do_ioctl 最終調用到:vfd->ioctl_ops->vidioc_querycap.
也就是 csi_ioctl_ops 的 .vidioc_querycap = vidioc_querycap,
也就是說: 調用ioctl(VIDIOC_QUERYCAP). 會通過__vidoe_do_ioctl() 最終調用到sunxi_drv_sic.c中的vidioc_querycap()
爲什麼沒有進Sensor呢? 是因爲能力集在代碼層面維護。不需要去問Sensor。
例2:得到像素格式
ioctl(Handle, VIDIOC_G_FMT, &Format)
它最終被調用sunxi_drv_csi.c中的vidioc_g_fmt_vid_cap()
因爲格式也是由程序維護,所以沒有訪問Sensor。
例3:設置像素格式
ioctl(Handle, VIDIOC_S_FMT, &Format);
最終被調用sunxi_drv_csi.c中的vidioc_s_fmt_vid_cap()
注意: 設置像素格式,需要通知Sensor。
所以會被調用:
v4l2_subdev_call(dev->sd,video,s_mbus_fmt,&ccm_fmt);
這裏的video. 就是表明調用的是關鍵4 那個的sensor_video_ops。
這裏的sd,其實就是關鍵3 那裏的v4l2_subdev類型sd.
所以,又被調用到gt2005.c中的sensor_s_fmt()
此時會向Sensor中寫入數據。sensor_write
例4:
ioctl(Handle, VIDIOC_G_PARM, &Stream_Parm)
會通過__vidoe_do_ioctl()
調用到sunxi_drv_csi.c中vidioc_g_parm()
這裏會:v4l2_subdev_call(dev->sd,video,g_parm,parms);
在gt2005.c中寫死的。 如果格式大於 800x600. 則15幀。 小於800x600. 則30幀。
例5:
ioctl(Handle, VIDIOC_G_PARM, &Stream_Parm)
會通過__vidoe_do_ioctl()
調用到sunxi_drv_csi.c中vidioc_s_parm()
這裏會:v4l2_subdev_call(dev->sd,video,s_parm,parms);
在gt2005.c 中。這裏打空。所以其實不能調。
例6:
ioctl(Handle, VIDIOC_G_CTRL, &ctrl)
會通過__vidoe_do_ioctl()
調用到sunxi_drv_csi.c中vidioc_g_ctrl()
這裏會:v4l2_subdev_call(dev->sd,core,g_ctrl,ctrl);
注意:這裏是core. 所以調用gt2005.c中的sensor_g_ctrl()
例7:
ioctl(Handle, VIDIOC_S_CTRL, &ctrl)
會通過__vidoe_do_ioctl()
調用到sunxi_drv_csi.c中vidioc_s_ctrl()
這裏會:v4l2_subdev_call(dev->sd,core,s_ctrl,ctrl);
注意:這裏是core. 所以調用gt2005.c中的sensor_s_ctrl()
但有一點需要注意:在A20平臺編程中,大家會發現所有VIDIOC_G_CTRL, VIDIOC_S_CTRL全不可用。Sam查了一下代碼。發現是全志修改Kernel時弄錯了。
錯誤如下:
struct v4l2_control {
__u32 id;
__s32 value;
__u32 user_pt;
};
看起來,是全志一位叫Raymon的工程師非常隨意的修改了v4l2_control結構體。添加了4個字節的user_pt;
但這會造成嚴重後果,首先,大量使用v4l2_control的接口會出現未知問題。
更嚴重的是:ioctl的cmd這一項是計算出來的。v4l2_control的大小會影響到cmd的值。
所以,在A20平臺上,應用程序使用VIDIOC_G_CTRL, VIDIOC_S_CTRL時,
__video_do_ioctl(struct file *file,unsigned int cmd, void *arg)
參數2:cmd這一項和Kernel中算出的cmd值對不上。所以無法進入:
case VIDIOC_G_CTRL:
case VIDIOC_S_CTRL:
如果有全志工程師偶爾看到這篇Blog。 請通知Driver部門。修改這個明顯錯誤。