linux摄像头驱动的拍照流程分析(针对展讯8810(ARM架构),android平台)
首先,我们根据 展讯 SC8810的datasheet的摄像原理相关章节,找到了摄像相关寄存器的名字(CAP_FRM_SIZE、CAP_IMG_DECI),通过在驱动目录(kernel/drivers/media/),对这些名字的搜索,很幸运的可以找到相关源文件。
$ find kernel/drivers/media/ | xargs grep "CAP_FRM_SIZE"
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: union _CAP_FRM_SIZE_TAG
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: struct _CAP_FRM_SIZE_MAP
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: union _CAP_FRM_SIZE_TAG
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: struct _CAP_FRM_SIZE_MAP
$ find kernel/drivers/media/ | xargs grep "CAP_IMG_DECI" kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: union _CAP_IMG_DECI_TAG
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: struct _CAP_IMG_DECI_MAP
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: union _CAP_IMG_DECI_TAG
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h: struct _CAP_IMG_DECI_MAP
$ vim kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h (这个文件里定义了摄像头相关的寄存器)
$ find kernel/drivers/media/ | xargs grep "sc8810_reg_isp.h"
kernel/drivers/media/video/sprd_dcam/sc8810/sc8810_reg_isp.h:* drivers/media/video/sprd_dcam/sc8810_reg_isp.h (这个是源文件的头注释,实际上路径写错了。)
kernel//drivers/media/video/sprd_dcam/sc8810/dcam_drv_sc8810.h:#include "sc8810_reg_isp.h"
kernel/drivers/media/video/sprd_scale/scale_drv_sc8810.h:#include "../sprd_dcam/sc8810/sc8810_reg_isp.h"
我们看到 ,有 “dcam_drv_sc8810.h”和 “scale_drv_sc8810.h” 两个头文件又引用了 “sc8810_reg_isp.h”。
我们继续向下追踪:
$ find kernel/drivers/media/ | xargs grep "dcam_drv_sc8810.h"
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_drv_sc8810.c:#include "dcam_drv_sc8810.h"
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_service_sc8810.h:#include "dcam_drv_sc8810.h"
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_drv_sc8810.h:* drivers/media/video/sprd_dcam/sc8810/dcam_drv_sc8810.h(这个是源文件的头注释。)
最后, “dcam_drv_sc8810.c”引用了 “dcam_drv_sc8810.h” ,所以,我们猜想 “dcam_drv_sc8810.c” 肯定是使用了这些寄存器进行编程的。
另外, “dcam_service_sc8810.h” 除了包含了 “dcam_drv_sc8810.h” 之外,自己还定义了一些枚举类型、结构体和宏,例如:
typedef struct dcam_init_param { DCAM_MODE_TYPE_E mode; DCAM_DATA_FORMAT_E format; DCAM_YUV_PATTERN_E yuv_pattern; RGB_TYPE_E display_rgb_type; DCAM_SIZE_T0 input_size; DCAM_POLARITY_T polarity; DCAM_RECT_T0 input_rect; DCAM_RECT_T0 display_rect; DCAM_RECT_T0 encoder_rect; DCAM_ROTATION_E rotation; int skip_frame; uint32_t first_buf_addr; uint32_t first_u_buf_addr; uint32_t zoom_level; uint32_t zoom_multiple; uint32_t skip_flag; uint32_t is_Y_UV; }DCAM_INIT_PARAM_T;
顺便追踪下,dcam_service的路径:
$ find kernel/drivers/media/ | xargs grep "dcam_service_sc8810.h"
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_service_sc8810.c:#include "dcam_service_sc8810.h"
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_service.h:#include "dcam_service_sc8810.h"
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_service_sc8810.h:* drivers/media/video/sprd_dcam/dcam_service_sc8810.h
在 “dcam_service_sc8810.c” 中我们可以看到如下内容,主要是对摄像头的某些参数进行了初始化。 这里先列出部分源文件,为了使大家有一个印象。之后再详细分析流程。
typedef struct dcam_parameter { DCAM_MODE_TYPE_E mode; DCAM_DATA_FORMAT_E format; DCAM_YUV_PATTERN_E yuv_pattern; RGB_TYPE_E display_rgb_type; DCAM_SIZE_T0 input_size; DCAM_POLARITY_T polarity; DCAM_RECT_T0 input_rect; DCAM_RECT_T0 display_rect; DCAM_RECT_T0 encoder_rect; DCAM_ROTATION_E rotation; int skip_frame; uint32_t first_buf_addr; uint32_t first_buf_uv_addr; uint32_t zoom_level; uint32_t zoom_multiple; uint32_t no_skip_frame_flag; uint32_t is_Y_UV; }DCAM_PARAMETER_T; DCAM_PARAMETER_T g_dcam_param;
int dcam_parameter_init(DCAM_INIT_PARAM_T *init_param) { DCAM_TRACE("DCAM: dcam_parameter_init start. \n"); g_dcam_param.mode = init_param->mode; g_dcam_param.format = init_param->format; g_dcam_param.yuv_pattern = init_param->yuv_pattern; g_dcam_param.display_rgb_type = init_param->display_rgb_type; g_dcam_param.input_size.w = init_param->input_size.w; g_dcam_param.input_size.h = init_param->input_size.h; g_dcam_param.polarity.hsync = init_param->polarity.hsync; g_dcam_param.polarity.vsync = init_param->polarity.vsync; g_dcam_param.polarity.pclk = init_param->polarity.pclk; g_dcam_param.input_rect.x = init_param->input_rect.x; g_dcam_param.input_rect.y = init_param->input_rect.y; g_dcam_param.input_rect.w = init_param->input_rect.w; g_dcam_param.input_rect.h = init_param->input_rect.h; g_dcam_param.display_rect.x = init_param->display_rect.x; g_dcam_param.display_rect.y = init_param->display_rect.y; g_dcam_param.display_rect.w = init_param->display_rect.w; g_dcam_param.display_rect.h = init_param->display_rect.h; g_dcam_param.encoder_rect.x = init_param->encoder_rect.x; g_dcam_param.encoder_rect.y = init_param->encoder_rect.y; g_dcam_param.encoder_rect.w = init_param->encoder_rect.w; g_dcam_param.encoder_rect.h = init_param->encoder_rect.h; g_dcam_param.rotation = init_param->rotation; g_dcam_param.skip_frame = init_param->skip_frame; g_dcam_param.first_buf_addr = init_param->first_buf_addr; g_dcam_param.first_buf_uv_addr = init_param->first_u_buf_addr; g_dcam_param.zoom_level = init_param->zoom_level; g_dcam_param.zoom_multiple = init_param->zoom_multiple; g_dcam_param.is_Y_UV = init_param->is_Y_UV; if(0 == init_param->skip_flag) { g_dcam_param.no_skip_frame_flag = 1; } else { g_dcam_param.no_skip_frame_flag = 0; } DCAM_TRACE("DCAM: dcam_parameter_init mode: %d, format: %d, yuv_pattern: %d. \n", g_dcam_param.mode,g_dcam_param.format,g_dcam_param.yuv_pattern); DCAM_TRACE("DCAM: dcam_parameter_init disp w: %d, disp h: %d, input_rect:w: %d, h:%d\n", g_dcam_param.display_rect.w,g_dcam_param.display_rect.h,g_dcam_param.input_rect.w,g_dcam_param.input_rect.h); DCAM_TRACE("DCAM: dcam_parameter_init end. \n"); DCAM_TRACE("DCAM: dcam_parameter_init input rect:%d,%d,%d,%d\n", g_dcam_param.input_rect.x,g_dcam_param.input_rect.y,g_dcam_param.input_rect.w, g_dcam_param.input_rect.h); return 0; }
我们回过头来看 “dcam_drv_sc8810.c” 这个文件中做了什么吧。它主要定义了如下变量和 函数,当然,为了让篇幅不至于臃肿,我并没有列出所有的函数和变量的定义。
typedef struct _isp_path_desc_tag { ISP_SIZE_T input_size; ISP_RECT_T input_rect; ISP_SIZE_T sc_input_size; ISP_SIZE_T output_size; ISP_FRAME_T input_frame; uint32_t input_format; ISP_FRAME_T *p_output_frame_head; ISP_FRAME_T *p_output_frame_cur; uint32_t output_frame_count; uint32_t output_format; uint32_t output_frame_flag; ISP_FRAME_T swap_frame; ISP_FRAME_T line_frame; uint32_t scale_en; uint32_t sub_sample_en; uint32_t sub_sample_factor; uint32_t sub_sample_mode; uint32_t slice_en; uint32_t h_scale_coeff; uint32_t v_scale_coeff; }ISP_PATH_DESCRIPTION_T; typedef struct _isp_module_tagss { ISP_MODE_E isp_mode; uint32_t module_addr; ISP_CAP_DESCRIPTION_T isp_cap; ISP_PATH_DESCRIPTION_T isp_path1; ISP_PATH_DESCRIPTION_T isp_path2; ISP_ISR_FUNC_PTR user_func[ISP_IRQ_NUMBER]; }ISP_MODULE_T; static ISP_FRAME_T s_path1_frame[ISP_PATH1_FRAME_COUNT_MAX]; static ISP_FRAME_T s_path2_frame[ISP_PATH2_FRAME_COUNT_MAX]; static ISP_MODULE_T s_isp_mod; uint32_t g_is_stop = 0; static void _ISP_DrvierModuleReset(uint32_t base_addr); static uint32_t _ISP_DriverReadIrqLine(uint32_t base_addr); static void _ISP_DriverIrqClear(uint32_t base_addr,uint32_t mask); static void _ISP_DriverIrqDisable(uint32_t base_addr,uint32_t mask); static void _ISP_DriverIrqEnable(uint32_t base_addr,uint32_t mask); static void _ISP_ISRSensorStartOfFrame(uint32_t base_addr); static void _ISP_ISRSensorEndOfFrame(uint32_t base_addr); static void _ISP_ISRCapStartOfFrame(uint32_t base_addr); static void _ISP_ISRCapEndOfFrame(uint32_t base_addr); static void _ISP_ISRPath1Done(uint32_t base_addr); static void _ISP_ISRCapFifoOverflow(uint32_t base_addr); static void _ISP_ISRSensorLineErr(uint32_t base_addr); static void _ISP_ISRSensorFrameErr(uint32_t base_addr); static void _ISP_ISRJpegBufOverflow(uint32_t base_addr); static ISR_EXE_T _ISP_ISRSystemRoot(uint32_t param); static void _ISP_DriverISRRoot(uint32_t base_addr); static void _ISP_DriverLinkFrames(void); static void _ISP_DriverAutoCopy(uint32_t base_addr); static int32_t _ISP_DriverCalcSC1Size(void); static int32_t _ISP_DriverSetSC1Coeff(uint32_t base_addr); static int32_t _ISP_DriverPath1TrimAndScaling(uint32_t base_addr); static int32_t _ISP_DriverCalcSC2Size(void); static int32_t _ISP_DriverGenScxCoeff(uint32_t base_addr, uint32_t idxScx); static void _ISP_ISRPath2Done(uint32_t base_addr);
这里先介绍一下 _pard 这个宏。因为摄像头驱动里对寄存器的操作都用到了它。一共有如下几个文件中定义了这个宏。
kernel/drivers/media/video/sprd_rotation/rotation_reg_sc8800g2.h:#define _pard(a) __raw_readl(a)
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_common.h:#define _pard(a) __raw_readl(a)
kernel/drivers/media/video/sprd_dcam/dcam_common.h:#define _pard(a) __raw_readl(a)
kernel/drivers/media/video/sprd_scale/scale_drv_sc8810.h:#define _pard(a) __raw_readl(a)
kernel/drivers/media/video/sprd_scale/scale_reg_sc8800g2.h:#define _pard(a) __raw_readl(a)
#define _pard(a) __raw_readl(a) ,而 __raw_readl(a) 的意思就是 把a为地址的存储单元的数据读出来。 具体过程可以参考“嵌入式linux和uboot中关于读写寄存器的函数(__raw_writel, writel等) ”。
#define readl(a) __arch_getl(a)
#define __arch_getl(a) (*(volatile unsigned int *)(a)) //就是把值通过指针读出来
“dcam_drv_sc8810.c” 中的函数流程如下:
int32_t ISP_DriverStart(uint32_t base_addr, uint32_t is_Y_UV) ISP_CHECK_PARAM_ZERO_POINTER(base_addr); _ISP_DriverPath1TrimAndScaling(base_addr); _ISP_DriverIrqClear(base_addr,ISP_IRQ_LINE_MASK); _ISP_DriverIrqEnable(base_addr, ISP_IRQ_LINE_MASK); #ifdef DCAM_DRV_DEBUG _ISP_GetReg(); #endif _ISP_DriverForeCopy(base_addr); int32_t ISP_DriverStop(uint32_t base_addr) ISP_CHECK_PARAM_ZERO_POINTER(base_addr); _ISP_DriverIrqDisable(base_addr,ISP_IRQ_LINE_MASK); switch(s_isp_mod.isp_mode) { case ISP_MODE_CAPTURE: case ISP_MODE_MPEG: case ISP_MODE_VT: case ISP_MODE_PREVIEW_EX: p_isp_reg->dcam_path_cfg_u.mBits.cap_eb = 0; if(ISP_MODE_PREVIEW_EX == s_isp_mod.isp_mode) { isp_put_path2(); } msleep(20);//wait the dcam stop break; case ISP_MODE_PREVIEW: { p_isp_reg->dcam_path_cfg_u.mBits.cap_eb = 0; g_is_stop = 1; } break; default: rtn = ISP_DRV_RTN_MODE_ERR; break; } _ISP_DriverIrqDisable(base_addr, ISP_IRQ_LINE_MASK); _ISP_DriverIrqClear(base_addr,ISP_IRQ_LINE_MASK);
ISP_DriverStart() 在 dcam_service_sc8810.c 和 dcam_sc8800g2.c中被调用了:
$ find kernel/drivers/media/ | xargs grep "= ISP_DriverStart"
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_service_sc8810.c: rtn_drv = ISP_DriverStart(s->module_addr, g_dcam_param.is_Y_UV);
kernel/drivers/media/video/sprd_dcam/sc8810/dcam_service_sc8810.c: rtn_drv = ISP_DriverStart(s->module_addr, 0);
kernel/drivers/media/video/sprd_dcam/dcam_sc8800g2.c: rtn_drv = ISP_DriverStart(s->module_addr);
kernel/drivers/media/video/sprd_dcam/dcam_sc8800g2.c: rtn_drv = ISP_DriverStart(s->module_addr);
int dcam_start(void) // kernel~video/sprd_dcam/sc8810/dcam_v4l2.c
ISP_ServiceStartPreview() // ~/sprd_dcam/sc8810/dcam_service_sc8810.c 前面的路径略掉
_ISP_ServiceStartPreview()
ISP_DriverStart(s->module_addr, g_dcam_param.is_Y_UV);// ~/sprd_dcam/sc8810/dcam_drv_sc8810.c
有两个地方调用了dcam_start()
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) //kernel/drivers/media/video/sprd_dcam/sc8810/dcam_v4l2.c
dcam_start()
static void dcam_start_handle(int param) // kernel/drivers/media/video/sprd_dcam/sc8810/dcam_v4l2.c
dcam_start();
static int vidioc_handle_ctrl(struct v4l2_control *ctrl) // kernel/drivers/media/video/sprd_dcam/sc8810/dcam_v4l2.c
switch(ctrl->id)
case: V4L2_CID_BRIGHTNESS||* 等其他好几个分支都由调用dcam_start_handle
dcam_start_handle(is_previewing)
// kernel/drivers/media/video/sprd_dcam/sc8810/dcam_v4l2.c 文件中dcam_ioctl_ops 结构体被V4L2子系统注册。
static const struct v4l2_ioctl_ops dcam_ioctl_ops = { .vidioc_g_parm = vidioc_g_parm, .vidioc_s_parm = vidioc_s_parm, .vidioc_querycap = vidioc_querycap, .vidioc_cropcap = vidioc_cropcap, .vidioc_s_crop = vidioc_s_crop, .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, .vidioc_reqbufs = vidioc_reqbufs, .vidioc_querybuf = vidioc_querybuf, .vidioc_qbuf = vidioc_qbuf, .vidioc_dqbuf = vidioc_dqbuf, .vidioc_s_std = vidioc_s_std, .vidioc_enum_input = vidioc_enum_input, .vidioc_g_input = vidioc_g_input, .vidioc_s_input = vidioc_s_input, .vidioc_queryctrl = vidioc_queryctrl, .vidioc_g_ctrl = vidioc_g_ctrl, .vidioc_s_ctrl = vidioc_s_ctrl, .vidioc_streamon = vidioc_streamon, .vidioc_streamoff = vidioc_streamoff, .vidioc_g_crop = vidioc_g_crop, .vidioc_g_output = vidioc_g_output, .vidioc_querymenu = vidioc_querymenu, #ifdef CONFIG_VIDEO_V4L1_COMPAT // .vidiocgmbuf = vidiocgmbuf, #endif }; static struct video_device dcam_template = { .name = "dcam", .fops = &dcam_fops, .ioctl_ops = &dcam_ioctl_ops, .minor = -1, .release = video_device_release, .tvnorms = V4L2_STD_525_60, .current_norm = V4L2_STD_NTSC_M, }; static int __init create_instance(int inst) { struct dcam_dev *dev; struct video_device *vfd; int ret, i; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "%s-%03d", DCAM_MODULE_NAME, inst); ret = v4l2_device_register(NULL, &dev->v4l2_dev); if (ret) goto free_dev; /* init video dma queues */ INIT_LIST_HEAD(&dev->vidq.active); init_waitqueue_head(&dev->vidq.wq); /* initialize locks */ spin_lock_init(&dev->slock); mutex_init(&dev->mutex); ret = -ENOMEM; vfd = video_device_alloc(); if (!vfd) goto unreg_dev; *vfd = dcam_template; vfd->debug = debug; ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr); if (ret < 0) goto rel_vdev; video_set_drvdata(vfd, dev); /* Set all controls to their default value. */ for (i = 0; i < ARRAY_SIZE(dcam_qctrl); i++) dev->qctl_regs[i] = dcam_qctrl[i].default_value; /* Now that everything is fine, let's add it to device list */ list_add_tail(&dev->dcam_devlist, &dcam_devlist); snprintf(vfd->name, sizeof(vfd->name), "%s (%i)", dcam_template.name, vfd->num); if (video_nr >= 0) video_nr++; dev->vfd = vfd; v4l2_info(&dev->v4l2_dev, "V4L2 device registered as /dev/video%d\n", vfd->num); return 0; rel_vdev: video_device_release(vfd); unreg_dev: v4l2_device_unregister(&dev->v4l2_dev); free_dev: kfree(dev); return ret; } static struct platform_driver dcam_driver = { .probe = dcam_probe, .remove = dcam_remove, .driver = { .owner = THIS_MODULE, .name = "sc8800g_dcam", }, }; int __init dcam_v4l2_init(void) { int ret = 0, i; if(platform_driver_register(&dcam_driver) != 0) { printk("platform device register Failed \n"); return -1; } if (n_devs <= 0) n_devs = 1; for (i = 0; i < n_devs; i++) { ret = create_instance(i) ; if (ret) { /* If some instantiations succeeded, keep driver */ if (i) ret = 0; break; } } if (ret < 0) { printk(KERN_INFO "Error %d while loading dcam driver\n", ret); return ret; } printk(KERN_INFO "Video Technology Magazine Virtual Video " "Capture Board ver %u.%u.%u successfully loaded.\n", (DCAM_VERSION >> 16) & 0xFF, (DCAM_VERSION >> 8) & 0xFF, DCAM_VERSION & 0xFF); /* n_devs will reflect the actual number of allocated devices */ n_devs = i; return ret; } module_init(dcam_v4l2_init); module_exit(dcam_v4l2_exit);