系统相关
首页 > 系统相关> > 找回了当年一篇V4L2 linux 摄像头驱动的博客

找回了当年一篇V4L2 linux 摄像头驱动的博客

作者:互联网

从csdn找回 , 无缘无故被封了。。当时损失不少啊!!!!!!!!!

linux 摄像头驱动 :

核心数据结构:


    /**
     * struct fimc_dev - abstraction for FIMC entity
     * @slock:    the spinlock protecting this data structure
     * @lock:    the mutex protecting this data structure
     * @pdev:    pointer to the FIMC platform device
     * @pdata:    pointer to the device platform data
     * @variant:    the IP variant information
     * @id:        FIMC device index (0..FIMC_MAX_DEVS)
     * @num_clocks: the number of clocks managed by this device instance
     * @clock:    clocks required for FIMC operation
     * @regs:    the mapped hardware registers
     * @regs_res:    the resource claimed for IO registers
     * @irq:    FIMC interrupt number
     * @irq_queue:    interrupt handler waitqueue
     * @m2m:    memory-to-memory V4L2 device information
     * @vid_cap:    camera capture device information
     * @state:    flags used to synchronize m2m and capture mode operation
     * @alloc_ctx:    videobuf2 memory allocator context
     */
    struct fimc_dev {
        spinlock_t            slock;
        struct mutex            lock;
        struct platform_device        *pdev;
        struct s5p_platform_fimc    *pdata;
        struct samsung_fimc_variant    *variant;
        u16                id;
        u16                num_clocks;
        struct clk            *clock[MAX_FIMC_CLOCKS];
        void __iomem            *regs;
        struct resource            *regs_res;
        int                irq;
        wait_queue_head_t        irq_queue;
        struct fimc_m2m_device        m2m;
        struct fimc_vid_cap        vid_cap;
        unsigned long            state;
        struct vb2_alloc_ctx        *alloc_ctx;
    };
     
    /**
     * fimc_ctx - the device context data
     * @slock:        spinlock protecting this data structure
     * @s_frame:        source frame properties
     * @d_frame:        destination frame properties
     * @out_order_1p:    output 1-plane YCBCR order
     * @out_order_2p:    output 2-plane YCBCR order
     * @in_order_1p        input 1-plane YCBCR order
     * @in_order_2p:    input 2-plane YCBCR order
     * @in_path:        input mode (DMA or camera)
     * @out_path:        output mode (DMA or FIFO)
     * @scaler:        image scaler properties
     * @effect:        image effect
     * @rotation:        image clockwise rotation in degrees
     * @flip:        image flip mode
     * @flags:        additional flags for image conversion
     * @state:        flags to keep track of user configuration
     * @fimc_dev:        the FIMC device this context applies to
     * @m2m_ctx:        memory-to-memory device context
     */
    struct fimc_ctx {
        spinlock_t        slock;
        struct fimc_frame    s_frame;
        struct fimc_frame    d_frame;
        u32            out_order_1p;
        u32            out_order_2p;
        u32            in_order_1p;
        u32            in_order_2p;
        enum fimc_datapath    in_path;
        enum fimc_datapath    out_path;
        struct fimc_scaler    scaler;
        struct fimc_effect    effect;
        int            rotation;
        u32            flip;
        u32            flags;
        u32            state;
        struct fimc_dev        *fimc_dev;
        struct v4l2_m2m_ctx    *m2m_ctx;
    };

    /* fimc controller abstration */
    struct fimc_control {
        int                id;        /* controller id */
        char                name[16];
        atomic_t            in_use;
        void __iomem            *regs;        /* register i/o */
        struct clk            *clk;        /* interface clock */
        struct regulator    *regulator;        /* pd regulator */
        struct fimc_meminfo        mem;        /* for reserved mem */
     
        /* kernel helpers */
        struct mutex            lock;        /* controller lock */
        struct mutex            alloc_lock;
        struct mutex            v4l2_lock;
        wait_queue_head_t        wq;
        struct device            *dev;
        int                irq;
     
        /* v4l2 related */
        struct video_device        *vd;
        struct v4l2_device        v4l2_dev;
     
        /* fimc specific */
        struct fimc_limit        *limit;        /* H/W limitation */
        struct s3c_platform_camera    *cam;        /* activated camera */
        struct fimc_capinfo        *cap;        /* capture dev info */
        struct fimc_outinfo        *out;        /* output dev info */
        struct fimc_fbinfo        fb;        /* fimd info */
        struct fimc_scaler        sc;        /* scaler info */
        struct fimc_effect        fe;        /* fimc effect info */
     
        enum fimc_status        status;
        enum fimc_log            log;
     
        u32                ctx_busy[FIMC_MAX_CTXS];
    };



    /* global */
    struct fimc_global {
        struct fimc_control        ctrl[FIMC_DEVICES];
        struct s3c_platform_camera    camera[FIMC_MAXCAMS];
        int                camera_isvalid[FIMC_MAXCAMS];
        int                active_camera;
        int                initialized;
    };





设备对象结构:在platform 平台 probe时使用,传递给设备驱动

    struct platform_device {
        const char    * name;
        int        id;
        struct device    dev;
        u32        num_resources;
        struct resource    * resource;
     
        const struct platform_device_id    *id_entry;
     
        /* MFD cell pointer */
        struct mfd_cell *mfd_cell;
     
        /* arch specific additions */
        struct pdev_archdata    archdata;
    };

设备信息:

    static struct i2c_board_info  ov9650_i2c_info =
    {
        I2C_BOARD_INFO("OV9650", 0x60>>1),
        .platform_data = &ov9650_plat,
    };
     
    static struct s3c_platform_camera ov9650 = {
        .id            = CAMERA_PAR_A,
        .type        = CAM_TYPE_ITU,
        .fmt        = ITU_601_YCBCR422_8BIT,
        .order422    = CAM_ORDER422_8BIT_CBYCRY,
        .i2c_busnum    = IIC_NUM_CAM_USED,
        .info        = &ov9650_i2c_info,
        .pixelformat    = V4L2_PIX_FMT_VYUY,
        .srclk_name    = "mout_mpll",
        .clk_name    = "sclk_cam0",
        .clk_rate    = 24000000,
    //    .line_length    = 640,
        .line_length    = 1920,
        .width        = 640,
        .height    = 480,
        .window    = {
            .left    = 0,
            .top    = 0,
            .width    = 640,
            .height= 480,
        },
     
        /* Polarity */
        .inv_pclk    = 0,
        .inv_vsync    = 0,
        .inv_href    = 0,
        .inv_hsync    = 0,
     
        .initialized    = 0,
     
    //    .cam_power    = smdkv210_OV9650_power,
        .cam_power    = tqcam_OV9650_power,
     
    };



    static struct s3c_platform_fimc fimc_plat_lsi = {
        .srclk_name    = "mout_mpll",
        .clk_name    = "sclk_fimc",
        .lclk_name    = "fimc",
        .clk_rate    = 166750000,
    #if defined(CONFIG_VIDEO_S5K4EA)
        .default_cam    = CAMERA_CSI_C,
    #else
    #ifdef CAM_ITU_CH_A
        .default_cam    = CAMERA_PAR_A,
    #else
        .default_cam    = CAMERA_PAR_B,
    #endif
    #endif
        .camera        = {
     
                &ov9650,
        },
        .hw_ver        = 0x43,
    };


    static struct s5p_media_device tq210_media_devs[] = {
        [0] = {
            .id            = S5P_MDEV_MFC,
            .name        = "mfc",
            .bank        = 0,
            .memsize    = S5PV210_VIDEO_SAMSUNG_MEMSIZE_MFC0,
            .paddr        = 0,
        },
        [1] = {
            .id            = S5P_MDEV_MFC,
            .name        = "mfc",
            .bank        = 1,
            .memsize    = S5PV210_VIDEO_SAMSUNG_MEMSIZE_MFC1,
            .paddr        = 0,
        },
        [2] = {
            .id            = S5P_MDEV_FIMC0,
            .name        = "fimc0",
            .bank        = 1,
            .memsize    = S5PV210_VIDEO_SAMSUNG_MEMSIZE_FIMC0,
            .paddr        = 0,
        },
    .............................
     

media_devs指向tq210_media_devs数组;


    void __init s3c_fimc0_set_platdata(struct s3c_platform_fimc *pd)
    {
        struct s3c_platform_fimc *npd;
     
        if (!pd)
            pd = &default_fimc0_data;
     
        npd = kmemdup(pd, sizeof(struct s3c_platform_fimc), GFP_KERNEL);
        if (!npd)
            printk(KERN_ERR "%s: no memory for platform data\n", __func__);
        else {
            if (!npd->cfg_gpio)
                npd->cfg_gpio = s3c_fimc0_cfg_gpio;
     
            if (!npd->clk_on)
                npd->clk_on = s3c_fimc_clk_on;
     
            if (!npd->clk_off)
                npd->clk_off = s3c_fimc_clk_off;
     
            npd->hw_ver = 0x45;
     
            /* starting physical address of memory region */
            npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMC0, 1);
            /* size of memory region */
            npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMC0, 1);
     
            s3c_device_fimc0.dev.platform_data = npd;
        }


    struct platform_device s3c_device_fimc0 = {
        .name        = "s3c-fimc",
        .id        = 0,
        .num_resources    = ARRAY_SIZE(s3c_fimc0_resource),
        .resource    = s3c_fimc0_resource,
    };




设备驱动注册:入口处;

    static struct platform_driver fimc_driver = {
        .probe        = fimc_probe,
        .remove        = fimc_remove,
        .suspend    = fimc_suspend,
        .resume        = fimc_resume,
        .driver        = {
            .name    = FIMC_NAME,
            .owner    = THIS_MODULE,
        },
    };


    static int fimc_register(void)
    {
        platform_driver_register(&fimc_driver);
     
        return 0;
    }
     
    static void fimc_unregister(void)
    {
        platform_driver_unregister(&fimc_driver);
    }
     
    late_initcall(fimc_register);

在注册fimc_driver驱动后;根据bus device driver 在platform平台上匹配;会调用

fimc_driver->fimc_probe();函数;注册绑定设备对象;

    static int __devinit fimc_probe(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
        struct fimc_control *ctrl;
        struct clk *srclk;
        int ret;
        if (!fimc_dev) {   struct *fimc_global
            fimc_dev = kzalloc(sizeof(*fimc_dev), GFP_KERNEL);
            if (!fimc_dev) {
                dev_err(&pdev->dev, "%s: not enough memory\n",
                    __func__);
                return -ENOMEM;
            }
        }
     
        ctrl = fimc_register_controller(pdev);
                     
        if (!ctrl) {
            printk(KERN_ERR "%s: cannot register fimc\n", __func__);
            goto err_alloc;
        }
     
        pdata = to_fimc_plat(&pdev->dev);
        if (pdata->cfg_gpio)
            pdata->cfg_gpio(pdev);
     
     
        /* fimc source clock */
        srclk = clk_get(&pdev->dev, pdata->srclk_name);
        /* fimc clock */
        ctrl->clk = clk_get(&pdev->dev, pdata->clk_name);
        /* set parent for mclk */
        clk_set_parent(ctrl->clk, srclk);
        /* set rate for mclk */
        clk_set_rate(ctrl->clk, pdata->clk_rate);
     
        /* V4L2 device-subdev registration */
        ret = v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
        if (ret) {
            fimc_err("%s: v4l2 device register failed\n", __func__);
            goto err_fimc;
        }
     
        /* things to initialize once */
        if (!fimc_dev->initialized) {
            ret = fimc_init_global(pdev);
            if (ret)
                goto err_v4l2;
        }
     
        /* video device register */
        ret = video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);
        if (ret) {
            fimc_err("%s: cannot register video driver\n", __func__);
            goto err_v4l2;
        }
     
        video_set_drvdata(ctrl->vd, ctrl);
     
        ret = device_create_file(&(pdev->dev), &dev_attr_log_level);
        ..............
        return -EINVAL;



对于设备信息的传递:

根据platform_device的id

ctrl->vd = &fimc_video_device[id];

指向:

    struct video_device fimc_video_device[FIMC_DEVICES] = {
        [0] = {
            .fops = &fimc_fops,
            .ioctl_ops = &fimc_v4l2_ops,
            .release = fimc_vdev_release,
        },


    static
    struct fimc_control *fimc_register_controller(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
        struct fimc_control *ctrl;
        struct resource *res;
        int id, mdev_id;
     
        id = pdev->id;
        mdev_id = S5P_MDEV_FIMC0 + id;
        pdata = to_fimc_plat(&pdev->dev);
     
        ctrl = get_fimc_ctrl(id);
        ctrl->id = id;
        ctrl->dev = &pdev->dev;
        ctrl->vd = &fimc_video_device[id];
        ctrl->vd->minor = id;
     
        /* alloc from bank1 as default */
        ctrl->mem.base = pdata->pmem_start;
        ctrl->mem.size = pdata->pmem_size;
        ctrl->mem.curr = ctrl->mem.base;
     
        ctrl->status = FIMC_STREAMOFF;
        switch (pdata->hw_ver) {
        case 0x40:
            ctrl->limit = &fimc40_limits[id];
            break;
        ..........................
        }
     
        ctrl->log = FIMC_LOG_DEFAULT;
     
        sprintf(ctrl->name, "%s%d", FIMC_NAME, id);
        strcpy(ctrl->vd->name, ctrl->name);
     
        atomic_set(&ctrl->in_use, 0);
        mutex_init(&ctrl->lock);
        mutex_init(&ctrl->alloc_lock);
        mutex_init(&ctrl->v4l2_lock);
        init_waitqueue_head(&ctrl->wq);
     
        /* get resource for io memory */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res) {
            fimc_err("%s: failed to get io memory region\n", __func__);
            return NULL;
        }
     
        /* request mem region request_mem_region函数并没有做实际性的映射工作,
    只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源。*/
        res = request_mem_region(res->start, res->end - res->start + 1,
                pdev->name);
        if (!res) {
            fimc_err("%s: failed to request io memory region\n", __func__);
            return NULL;
        }
     
        /* ioremap for register block 在将I/O内存资源的物理地址映射成核心虚地址后 */
        ctrl->regs = ioremap(res->start, res->end - res->start + 1);
        if (!ctrl->regs) {
            fimc_err("%s: failed to remap io region\n", __func__);
            return NULL;
        }
     
        /* irq */
        ctrl->irq = platform_get_irq(pdev, 0);
        if (request_irq(ctrl->irq, fimc_irq, IRQF_DISABLED, ctrl->name, ctrl))
            fimc_err("%s: request_irq failed\n", __func__);
     
        fimc_hwset_reset(ctrl);
     
        return ctrl;

v4l2_device和device关联起来

    int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
    {
        if (v4l2_dev == NULL)
            return -EINVAL;
     
        INIT_LIST_HEAD(&v4l2_dev->subdevs);
        spin_lock_init(&v4l2_dev->lock);
        mutex_init(&v4l2_dev->ioctl_lock);
        v4l2_prio_init(&v4l2_dev->prio);
        kref_init(&v4l2_dev->ref);
        v4l2_dev->dev = dev;
        if (dev == NULL) {
            /* If dev == NULL, then name must be filled in by the caller */
            WARN_ON(!v4l2_dev->name[0]);
            return 0;
        }
     
        /* Set name to driver name + device name if it is empty. */
        if (!v4l2_dev->name[0])
            snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
                dev->driver->name, dev_name(dev));
        if (!dev_get_drvdata(dev))
            dev_set_drvdata(dev, v4l2_dev);
        return 0;


获取platform_device的相关信息

    static int fimc_init_global(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
        struct s3c_platform_camera *cam;
        int i;
     
        pdata = to_fimc_plat(&pdev->dev);
     
        /* Registering external camera modules. re-arrange order to be sure */
        for (i = 0; i < FIMC_MAXCAMS; i++) {
            cam = pdata->camera[i];
            if (!cam)
                break;
     
            cam->srclk = clk_get(&pdev->dev, cam->srclk_name);
            if (IS_ERR(cam->srclk)) {
                dev_err(&pdev->dev, "%s: failed to get mclk source\n",
                        __func__);
                return -EINVAL;
            }
     
            /* mclk */
            cam->clk = clk_get(&pdev->dev, cam->clk_name);
            if (IS_ERR(cam->clk)) {
                dev_err(&pdev->dev, "%s: failed to get mclk source\n",
                        __func__);
                clk_put(cam->srclk);
                return -EINVAL;
            }
     
            clk_put(cam->clk);
            clk_put(cam->srclk);
     
            /* Assign camera device to fimc */
            memcpy(&fimc_dev->camera[i], cam, sizeof(*cam));
            fimc_dev->camera_isvalid[i] = 1;
            fimc_dev->camera[i].initialized = 0;
        }
     
        fimc_dev->active_camera = -1;
        fimc_dev->initialized = 1;
     
        return 0;


对于:video_register_device

主要是设置 其cdev的fops

vdev->cdev->ops = &v4l2_fops;

并注册

cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);;

设置设备号

vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);

注册device到总线中去;

ret = device_register(&vdev->dev);

vdev->dev.release = v4l2_device_release;

注册entry:

ret = media_device_register_entity(vdev->v4l2_dev->mdev,。。。

io口操作时要用:

video_device[vdev->minor] = vdev;

    /**
     *    __video_register_device - register video4linux devices
     *    @vdev: video device structure we want to register
     *    @type: type of device to register
     *    @nr:   which device node number (0 == /dev/video0, 1 == /dev/video1, ...
     *             -1 == first free)
     *    @warn_if_nr_in_use: warn if the desired device node number
     *           was already in use and another number was chosen instead.
     *    @owner: module that owns the video device node
     *
     *    The registration code assigns minor numbers and device node numbers
     *    based on the requested type and registers the new device node with
     *    the kernel.
     *
     *    This function assumes that struct video_device was zeroed when it
     *    was allocated and does not contain any stale date.
     *
     *    An error is returned if no free minor or device node number could be
     *    found, or if the registration of the device node failed.
     *
     *    Zero is returned on success.
     *
     *    Valid types are
     *
     *    %VFL_TYPE_GRABBER - A frame grabber
     *
     *    %VFL_TYPE_VBI - Vertical blank data (undecoded)
     *
     *    %VFL_TYPE_RADIO - A radio card
     *
     *    %VFL_TYPE_SUBDEV - A subdevice
     */
    int __video_register_device(struct video_device *vdev, int type, int nr,
            int warn_if_nr_in_use, struct module *owner)
    {
        int i = 0;
        int ret;
        int minor_offset = 0;
        int minor_cnt = VIDEO_NUM_DEVICES;
        const char *name_base;
     
        /* A minor value of -1 marks this video device as never
           having been registered */
        vdev->minor = -1;
     
        /* the release callback MUST be present */
        WARN_ON(!vdev->release);
        if (!vdev->release)
            return -EINVAL;
     
        /* v4l2_fh support */
        spin_lock_init(&vdev->fh_lock);
        INIT_LIST_HEAD(&vdev->fh_list);
     
        /* Part 1: check device type */
        switch (type) {
        case VFL_TYPE_GRABBER:
            name_base = "video";
            break;
        case VFL_TYPE_VBI:
            name_base = "vbi";
            break;
        case VFL_TYPE_RADIO:
            name_base = "radio";
            break;
        case VFL_TYPE_SUBDEV:
            name_base = "v4l-subdev";
            break;
        default:
            printk(KERN_ERR "%s called with unknown type: %d\n",
                   __func__, type);
            return -EINVAL;
        }
     
        vdev->vfl_type = type;
        vdev->cdev = NULL;
        if (vdev->v4l2_dev) {
            if (vdev->v4l2_dev->dev)
                vdev->parent = vdev->v4l2_dev->dev;
            if (vdev->ctrl_handler == NULL)
                vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
            /* If the prio state pointer is NULL, then use the v4l2_device
               prio state. */
            if (vdev->prio == NULL)
                vdev->prio = &vdev->v4l2_dev->prio;
        }
     
        /* Part 2: find a free minor, device node number and device index. */
    #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
        /* Keep the ranges for the first four types for historical
         * reasons.
         * Newer devices (not yet in place) should use the range
         * of 128-191 and just pick the first free minor there
         * (new style). */
        switch (type) {
        case VFL_TYPE_GRABBER:
            minor_offset = 0;
            minor_cnt = 64;
            break;
        case VFL_TYPE_RADIO:
            minor_offset = 64;
            minor_cnt = 64;
            break;
        case VFL_TYPE_VBI:
            minor_offset = 224;
            minor_cnt = 32;
            break;
        default:
            minor_offset = 128;
            minor_cnt = 64;
            break;
        }
    #endif
     
        /* Pick a device node number */
        mutex_lock(&videodev_lock);
        nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
        if (nr == minor_cnt)
            nr = devnode_find(vdev, 0, minor_cnt);
        if (nr == minor_cnt) {
            printk(KERN_ERR "could not get a free device node number\n");
            mutex_unlock(&videodev_lock);
            return -ENFILE;
        }
    #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
        /* 1-on-1 mapping of device node number to minor number */
        i = nr;
    #else
        /* The device node number and minor numbers are independent, so
           we just find the first free minor number. */
        for (i = 0; i < VIDEO_NUM_DEVICES; i++)
            if (video_device[i] == NULL)
                break;
        if (i == VIDEO_NUM_DEVICES) {
            mutex_unlock(&videodev_lock);
            printk(KERN_ERR "could not get a free minor\n");
            return -ENFILE;
        }
    #endif
        vdev->minor = i + minor_offset;
        vdev->num = nr;
        devnode_set(vdev);
     
        /* Should not happen since we thought this minor was free */
        WARN_ON(video_device[vdev->minor] != NULL);
        vdev->index = get_index(vdev);
        mutex_unlock(&videodev_lock);
     
        /* Part 3: Initialize the character device */
        vdev->cdev = cdev_alloc();
        if (vdev->cdev == NULL) {
            ret = -ENOMEM;
            goto cleanup;
        }
        vdev->cdev->ops = &v4l2_fops;
        vdev->cdev->owner = owner;
        ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
        if (ret < 0) {
            printk(KERN_ERR "%s: cdev_add failed\n", __func__);
            kfree(vdev->cdev);
            vdev->cdev = NULL;
            goto cleanup;
        }
     
        /* Part 4: register the device with sysfs */
        vdev->dev.class = &video_class;
        vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
        if (vdev->parent)
            vdev->dev.parent = vdev->parent;
        dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
        ret = device_register(&vdev->dev);
        if (ret < 0) {
            printk(KERN_ERR "%s: device_register failed\n", __func__);
            goto cleanup;
        }
        /* Register the release callback that will be called when the last
           reference to the device goes away. */
        vdev->dev.release = v4l2_device_release;
     
        if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
            printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,
                name_base, nr, video_device_node_name(vdev));
     
        /* Increase v4l2_device refcount */
        if (vdev->v4l2_dev)
            v4l2_device_get(vdev->v4l2_dev);
     
    #if defined(CONFIG_MEDIA_CONTROLLER)
        /* Part 5: Register the entity. */
        if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
            vdev->vfl_type != VFL_TYPE_SUBDEV) {
            vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
            vdev->entity.name = vdev->name;
            vdev->entity.v4l.major = VIDEO_MAJOR;
            vdev->entity.v4l.minor = vdev->minor;
            ret = media_device_register_entity(vdev->v4l2_dev->mdev,
                &vdev->entity);
            if (ret < 0)
                printk(KERN_WARNING
                       "%s: media_device_register_entity failed\n",
                       __func__);
        }
    #endif
        /* Part 6: Activate this minor. The char device can now be used. */
        set_bit(V4L2_FL_REGISTERED, &vdev->flags);
        mutex_lock(&videodev_lock);
        video_device[vdev->minor] = vdev;
        mutex_unlock(&videodev_lock);
     
        return 0;
     
    cleanup:
        mutex_lock(&videodev_lock);
        if (vdev->cdev)
            cdev_del(vdev->cdev);
        devnode_clear(vdev);
        mutex_unlock(&videodev_lock);
        /* Mark this video device as never having been registered. */
        vdev->minor = -1;
        return ret;
    }
    EXPORT_SYMBOL(__video_register_device);
     
    /**
     *    video_unregister_device - unregister a video4linux device
     *    @vdev: the device to unregister
     *
     *    This unregisters the passed device. Future open calls will
     *    be met with errors.
     */
    void video_unregister_device(struct video_device *vdev)
    {
        /* Check if vdev was ever registered at all */
        if (!vdev || !video_is_registered(vdev))
            return;
     
        mutex_lock(&videodev_lock);
        /* This must be in a critical section to prevent a race with v4l2_open.
         * Once this bit has been cleared video_get may never be called again.
         */
        clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
        mutex_unlock(&videodev_lock);
        device_unregister(&vdev->dev);
    }
    EXPORT_SYMBOL(video_unregister_device);
     
    /*
     *    Initialise video for linux
     */
    static int __init videodev_init(void)
    {
        dev_t dev = MKDEV(VIDEO_MAJOR, 0);
        int ret;
     
        printk(KERN_INFO "Linux video capture interface: v2.00\n");
        ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
        if (ret < 0) {
            printk(KERN_WARNING "videodev: unable to get major %d\n",
                    VIDEO_MAJOR);
            return ret;
        }
     
        ret = class_register(&video_class);
        if (ret < 0) {
            unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
            printk(KERN_WARNING "video_dev: class_register failed\n");
            return -EIO;
        }
     
        return 0;
    }
     
    static void __exit videodev_exit(void)
    {
        dev_t dev = MKDEV(VIDEO_MAJOR, 0);
     
        class_unregister(&video_class);
        unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
    }
     
    module_init(videodev_init)
    module_exit(videodev_exit)
     
    MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab <mchehab@infradead.org>");
    MODULE_DESCRIPTION("Device registrar for Video4Linux drivers v2");
    MODULE_LICENSE("GPL");
    MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR);
     
     
    /*
     * Local variables:
     * c-basic-offset: 8
     * End:
     */

设置drv_privedata;

video_set_drvdata(ctrl->vd, ctrl);

    if(ctrl->vd->dev->p->driver_data ==null)
     即ctrl->vd->dev->p->driver_data = ctrl;

对于v4l2_subdev的注册即iic设备的注册; 以及和v4l2设备的绑定如何??

    static const struct i2c_device_id ov3640_id[] = {
        { OV3640_DRIVER_NAME, 0 },
        { },
    };
    MODULE_DEVICE_TABLE(i2c, ov3640_id);
     
    static struct v4l2_i2c_driver_data v4l2_i2c_data = {
        .name = OV3640_DRIVER_NAME,
        .probe = ov3640_probe,
        .remove = __devexit_p(ov3640_remove),
        .id_table = ov3640_id,
    };


    /* Bus-based I2C implementation for kernels >= 2.6.26 */
     
    static int __init v4l2_i2c_drv_init(void)
    {
        v4l2_i2c_driver.driver.name = v4l2_i2c_data.name;
        v4l2_i2c_driver.command = v4l2_i2c_data.command;
        v4l2_i2c_driver.probe = v4l2_i2c_data.probe;
        v4l2_i2c_driver.remove = v4l2_i2c_data.remove;
        v4l2_i2c_driver.suspend = v4l2_i2c_data.suspend;
        v4l2_i2c_driver.resume = v4l2_i2c_data.resume;
        v4l2_i2c_driver.id_table = v4l2_i2c_data.id_table;
        return i2c_add_driver(&v4l2_i2c_driver);
    }
     
     
    static void __exit v4l2_i2c_drv_cleanup(void)
    {
        i2c_del_driver(&v4l2_i2c_driver);
    }
     
    module_init(v4l2_i2c_drv_init);
    module_exit(v4l2_i2c_drv_cleanup);



关于iic的注册分析iic设备去分析iic设备驱动;
如果匹配到

    static const struct v4l2_subdev_core_ops ov3640_core_ops = {
        .init = ov3640_init,    /* initializing API */
        .s_config = ov3640_s_config,    /* Fetch platform data */
        .queryctrl = ov3640_queryctrl,
        .querymenu = ov3640_querymenu,
        .g_ctrl = ov3640_g_ctrl,
        .s_ctrl = ov3640_s_ctrl,
    };
     
    static const struct v4l2_subdev_video_ops ov3640_video_ops = {
        .g_fmt = ov3640_g_fmt,
        .s_fmt = ov3640_s_fmt,
        .enum_framesizes = ov3640_enum_framesizes,
        .enum_frameintervals = ov3640_enum_frameintervals,
        .enum_fmt = ov3640_enum_fmt,
        .try_fmt = ov3640_try_fmt,
        .g_parm = ov3640_g_parm,
        .s_parm = ov3640_s_parm,
    };
     
    static const struct v4l2_subdev_ops ov3640_ops = {
        .core = &ov3640_core_ops,
        .video = &ov3640_video_ops,
    };
     
    static int ov3640_remove(struct i2c_client *client);
    /*
     * ov3640_probe
     * Fetching platform data is being done with s_config subdev call.
     * In probe routine, we just register subdev device
     */
    static int ov3640_probe(struct i2c_client *client,
                 const struct i2c_device_id *id)
    {
        struct ov3640_state *state;
        struct v4l2_subdev *sd;
        int err = 0;
     
        state = kzalloc(sizeof(struct ov3640_state), GFP_KERNEL);
        if (state == NULL)
            return -ENOMEM;
     
        sd = &state->sd;
        strcpy(sd->name, OV3640_DRIVER_NAME);
     
        /* Registering subdev */
        v4l2_i2c_subdev_init(sd, client, &ov3640_ops);
     
        err =  checkIfOV3640(sd);
        dev_info(&client->dev, "ov3640 has been probed,err(%d)\n",err);
        if(err < 0)
            ov3640_remove(client);
        return err;
    }

设置client和sd;

    void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
            const struct v4l2_subdev_ops *ops)
    {
        v4l2_subdev_init(sd, ops);
        sd->flags |= V4L2_SUBDEV_FL_IS_I2C;
        /* the owner is the same as the i2c_client's driver owner */
        sd->owner = client->driver->driver.owner;
        /* i2c_client and v4l2_subdev point to one another */
        v4l2_set_subdevdata(sd, client);
        i2c_set_clientdata(client, sd);
        /* initialize name */
        snprintf(sd->name, sizeof(sd->name), "%s %d-%04x",
            client->driver->driver.name, i2c_adapter_id(client->adapter),
            client->addr);
    }




其中v4l2_subdev和i2c_client相互关联并设置其ov3640_ops;

而在  v4l2_device中设置input  camera id

 此处分析问题

    int fimc_s_input(struct file *file, void *fh, unsigned int i)
    {
        struct fimc_global *fimc = get_fimc_dev();
        struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl;
        int ret = 0;
     
        fimc_dbg("%s: index %d\n", __func__, i);
     
        if (i < 0 || i >= FIMC_MAXCAMS) {
            fimc_err("%s: invalid input index\n", __func__);
            return -EINVAL;
        }
     
        if (!fimc->camera_isvalid[i])
            return -EINVAL;
     
        if (fimc->camera[i].sd && ctrl->id != 2) {
            fimc_err("%s: Camera already in use.\n", __func__);
            return -EBUSY;
        }
     
        mutex_lock(&ctrl->v4l2_lock);
        /* If ctrl->cam is not NULL, there is one subdev already registered.
         * We need to unregister that subdev first.
         */
        if (i != fimc->active_camera) {
            fimc_release_subdev(ctrl);
            ctrl->cam = &fimc->camera[i];
            ret = fimc_configure_subdev(ctrl);
            if (ret < 0) {
                mutex_unlock(&ctrl->v4l2_lock);
                fimc_err("%s: Could not register camera sensor "
                        "with V4L2.\n", __func__);
                return -ENODEV;
            }
            fimc->active_camera = i;
        }
     
        if (ctrl->id == 2) {
            if (i == fimc->active_camera) {
                ctrl->cam = &fimc->camera[i];
            } else {
                mutex_unlock(&ctrl->v4l2_lock);
                return -EINVAL;
            }
        }
     
        mutex_unlock(&ctrl->v4l2_lock);
     
        return 0;


其中配置fimc_configure_subdev();为核心

    int fimc_configure_subdev(struct fimc_control *ctrl)
    {
        struct i2c_adapter *i2c_adap;
        struct i2c_board_info *i2c_info;
        struct v4l2_subdev *sd;
        unsigned short addr;
        int err;
        char *name;
     
        err = 0;
        /* set parent for mclk */
        if (clk_get_parent(ctrl->cam->clk->parent))
            clk_set_parent(ctrl->cam->clk->parent, ctrl->cam->srclk);
     
        /* set rate for mclk */
        if (clk_get_rate(ctrl->cam->clk))
            clk_set_rate(ctrl->cam->clk, ctrl->cam->clk_rate);
     
        i2c_adap = i2c_get_adapter(ctrl->cam->i2c_busnum);
        if (!i2c_adap)
            fimc_err("subdev i2c_adapter missing-skip registration\n");
     
        i2c_info = ctrl->cam->info;
        if (!i2c_info) {
            fimc_err("%s: subdev i2c board info missing\n", __func__);
            return -ENODEV;
        }
     
        name = i2c_info->type;
        if (!name) {
            fimc_err("subdev i2c driver name missing-skip registration\n");
            return -ENODEV;
        }
     
        addr = i2c_info->addr;
        if (!addr) {
            fimc_err("subdev i2c address missing-skip registration\n");
            return -ENODEV;
        }
        /*
         * NOTE: first time subdev being registered,
         * s_config is called and try to initialize subdev device
         * but in this point, we are not giving MCLK and power to subdev
         * so nothing happens but pass platform data through
         */
        sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
                i2c_info, &addr);
        if (!sd) {
            fimc_err("%s: v4l2 subdev board registering failed\n",
                    __func__);
            err = -1;
        }
     
        /* Assign subdev to proper camera device pointer */
        ctrl->cam->sd = sd;
        return err;
    //    return 0;
    }

找到adapter;

    struct i2c_adapter *i2c_get_adapter(int nr)
    {
        struct i2c_adapter *adapter;
     
        mutex_lock(&core_lock);
        adapter = idr_find(&i2c_adapter_idr, nr);
        if (adapter && !try_module_get(adapter->owner))
            adapter = NULL;
     
        mutex_unlock(&core_lock);
        return adapter;
    }

i2c_info = ctrl->cam->info;addr = i2c_info->addr; camera的iic信息;


    /* Load an i2c sub-device. */
    struct v4l2_subdev *v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev,
            struct i2c_adapter *adapter, struct i2c_board_info *info,
            const unsigned short *probe_addrs)
    {
        struct v4l2_subdev *sd = NULL;
        struct i2c_client *client;
     
        BUG_ON(!v4l2_dev);
     
        request_module(I2C_MODULE_PREFIX "%s", info->type);
     
        /* Create the i2c client */
        if (info->addr == 0 && probe_addrs)
            client = i2c_new_probed_device(adapter, info, probe_addrs,
                               NULL);
        else
            client = i2c_new_device(adapter, info);
     
        /* Note: by loading the module first we are certain that c->driver
           will be set if the driver was found. If the module was not loaded
           first, then the i2c core tries to delay-load the module for us,
           and then c->driver is still NULL until the module is finally
           loaded. This delay-load mechanism doesn't work if other drivers
           want to use the i2c device, so explicitly loading the module
           is the best alternative. */
        if (client == NULL || client->driver == NULL)
            goto error;
     
        /* Lock the module so we can safely get the v4l2_subdev pointer */
        if (!try_module_get(client->driver->driver.owner))
            goto error;
        sd = i2c_get_clientdata(client);
     
        /* Register with the v4l2_device which increases the module's
           use count as well. */
        if (v4l2_device_register_subdev(v4l2_dev, sd))
            sd = NULL;
        /* Decrease the module use count to match the first try_module_get. */
        module_put(client->driver->driver.owner);
     
    error:
        /* If we have a client but no subdev, then something went wrong and
           we must unregister the client. */
        if (client && sd == NULL)
            i2c_unregister_device(client);
        return sd;
    }
    E


struct i2c_client * client = i2c_new_device(adapter, info);

分析:就是分析问题

    int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
                    struct v4l2_subdev *sd)
    {
    #if defined(CONFIG_MEDIA_CONTROLLER)
        struct media_entity *entity = &sd->entity;很重要
    #endif
        int err;
     
        /* Check for valid input */
        if (v4l2_dev == NULL || sd == NULL || !sd->name[0])
            return -EINVAL;
     
        /* Warn if we apparently re-register a subdev */
        WARN_ON(sd->v4l2_dev != NULL);
     
        if (!try_module_get(sd->owner))
            return -ENODEV;
     
        sd->v4l2_dev = v4l2_dev;
        if (sd->internal_ops && sd->internal_ops->registered) {
            err = sd->internal_ops->registered(sd);
            if (err) {
                module_put(sd->owner);
                return err;
            }
        }
     
        /* This just returns 0 if either of the two args is NULL */
        err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler);
        if (err) {
            if (sd->internal_ops && sd->internal_ops->unregistered)
                sd->internal_ops->unregistered(sd);
            module_put(sd->owner);
            return err;
        }
     
    #if defined(CONFIG_MEDIA_CONTROLLER)
        /* Register the entity. */
        if (v4l2_dev->mdev) {
            err = media_device_register_entity(v4l2_dev->mdev, entity);
            if (err < 0) {
                if (sd->internal_ops && sd->internal_ops->unregistered)
                    sd->internal_ops->unregistered(sd);
                module_put(sd->owner);
                return err;
            }
        }
    #endif
     
        spin_lock(&v4l2_dev->lock);
        list_add_tail(&sd->list, &v4l2_dev->subdevs);
        spin_unlock(&v4l2_dev->lock);
     
        return 0;


struct media_entity *entity = &sd->entity;  入口点

sd->v4l2_dev = v4l2_dev;

media_device_register_entity(v4l2_dev->mdev, entity);

         -----.>加入链表

list_add_tail(&sd->list, &v4l2_dev->subdevs);

ctrl->cam->sd = sd;


static inline int fimc_mmap_cap(struct file *filp, struct vm_area_struct *vma)
{
struct fimc_prv_data *prv_data =
(struct fimc_prv_data *)filp->private_data;
struct fimc_control *ctrl = prv_data->ctrl;
u32 size = vma->vm_end - vma->vm_start;
u32 pfn, idx = vma->vm_pgoff;


vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_flags |= VM_RESERVED;


/*
* page frame number of the address for a source frame
* to be stored at.
*/
pfn = __phys_to_pfn(ctrl->cap->bufs[idx].base[0]);


if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) {
fimc_err("%s: writable mapping must be shared\n", __func__);
return -EINVAL;
}


if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) {
fimc_err("%s: mmap fail\n", __func__);
return -EINVAL;
}


return 0;
}


对于:v4l2_device和v4l2_sud关联;

这是另一种 v4l2_file_operations  fimc_capture_fops;

于一开始的v4l2_file_operations   fimc_ops实现不同但是 原理差不多;

static const struct v4l2_file_operations fimc_capture_fops = {
.owner = THIS_MODULE,
.open = fimc_capture_open,
.release = fimc_capture_close,
.poll = fimc_capture_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = fimc_capture_mmap,
};

    static int fimc_capture_open(struct file *file)
    {
        struct fimc_dev *fimc = video_drvdata(file);
        int ret = 0;
     
        dbg("pid: %d, state: 0x%lx", task_pid_nr(current), fimc->state);
     
        /* Return if the corresponding video mem2mem node is already opened. */
        if (fimc_m2m_active(fimc))
            return -EBUSY;
     
        if (++fimc->vid_cap.refcnt == 1) {
            ret = fimc_isp_subdev_init(fimc, 0);
            if (ret) {
                fimc->vid_cap.refcnt--;
                return -EIO;
            }
        }
     
        file->private_data = fimc->vid_cap.ctx;
     
        return 0;


    static int fimc_isp_subdev_init(struct fimc_dev *fimc, unsigned int index)
    {
        struct s5p_fimc_isp_info *isp_info;
        struct s5p_platform_fimc *pdata = fimc->pdata;
        int ret;
     
        if (index >= pdata->num_clients)
            return -EINVAL;
     
        isp_info = &pdata->isp_info[index];
     
        if (isp_info->clk_frequency)
            clk_set_rate(fimc->clock[CLK_CAM], isp_info->clk_frequency);
     
        ret = clk_enable(fimc->clock[CLK_CAM]);
        if (ret)
            return ret;
     
        ret = fimc_subdev_attach(fimc, index);
        if (ret)
            return ret;
     
        ret = fimc_hw_set_camera_polarity(fimc, isp_info);
        if (ret)
            return ret;
     
        ret = v4l2_subdev_call(fimc->vid_cap.sd, core, s_power, 1);
        if (!ret)
            return ret;
     
        /* enabling power failed so unregister subdev */
        fimc_subdev_unregister(fimc);
     
        v4l2_err(&fimc->vid_cap.v4l2_dev, "ISP initialization failed: %d\n",
             ret);
     
        return ret;
    }


    /**
     * fimc_subdev_attach - attach v4l2_subdev to camera host interface
     *
     * @fimc: FIMC device information
     * @index: index to the array of available subdevices,
     *       -1 for full array search or non negative value
     *       to select specific subdevice
     */
    static int fimc_subdev_attach(struct fimc_dev *fimc, int index)
    {
        struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
        struct s5p_platform_fimc *pdata = fimc->pdata;
        struct s5p_fimc_isp_info *isp_info;
        struct v4l2_subdev *sd;
        int i;
     
        for (i = 0; i < pdata->num_clients; ++i) {
            isp_info = &pdata->isp_info[i];
     
            if (index >= 0 && i != index)
                continue;
     
            sd = fimc_subdev_register(fimc, isp_info);
            if (!IS_ERR_OR_NULL(sd)) {
                vid_cap->sd = sd;
                vid_cap->input_index = i;
     
                return 0;
            }
        }
     
        vid_cap->input_index = -1;
        vid_cap->sd = NULL;
        v4l2_err(&vid_cap->v4l2_dev, "fimc%d: sensor attach failed\n",
             fimc->id);
        return -ENODEV;


    static struct v4l2_subdev *fimc_subdev_register(struct fimc_dev *fimc,
                            struct s5p_fimc_isp_info *isp_info)
    {
        struct i2c_adapter *i2c_adap;
        struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
        struct v4l2_subdev *sd = NULL;
     
        i2c_adap = i2c_get_adapter(isp_info->i2c_bus_num);
        if (!i2c_adap)
            return ERR_PTR(-ENOMEM);
     
        sd = v4l2_i2c_new_subdev_board(&vid_cap->v4l2_dev, i2c_adap,
                           isp_info->board_info, NULL);
        if (!sd) {
            v4l2_err(&vid_cap->v4l2_dev, "failed to acquire subdev\n");
            return NULL;
        }
     
        v4l2_info(&vid_cap->v4l2_dev, "subdevice %s registered successfuly\n",
            isp_info->board_info->type);
     
        return sd;
    }


    /* Load an i2c sub-device. */
    struct v4l2_subdev *v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev,
            struct i2c_adapter *adapter, struct i2c_board_info *info,
            const unsigned short *probe_addrs)
    {
        struct v4l2_subdev *sd = NULL;
        struct i2c_client *client;
     
        BUG_ON(!v4l2_dev);
     
        request_module(I2C_MODULE_PREFIX "%s", info->type);
     
        /* Create the i2c client */
        if (info->addr == 0 && probe_addrs)
            client = i2c_new_probed_device(adapter, info, probe_addrs,
                               NULL);
        else
            client = i2c_new_device(adapter, info);
     
        /* Note: by loading the module first we are certain that c->driver
           will be set if the driver was found. If the module was not loaded
           first, then the i2c core tries to delay-load the module for us,
           and then c->driver is still NULL until the module is finally
           loaded. This delay-load mechanism doesn't work if other drivers
           want to use the i2c device, so explicitly loading the module
           is the best alternative. */
        if (client == NULL || client->driver == NULL)
            goto error;
     
        /* Lock the module so we can safely get the v4l2_subdev pointer */
        if (!try_module_get(client->driver->driver.owner))
            goto error;
        sd = i2c_get_clientdata(client);
     
        /* Register with the v4l2_device which increases the module's
           use count as well. */
        if (v4l2_device_register_subdev(v4l2_dev, sd))
            sd = NULL;
        /* Decrease the module use count to match the first try_module_get. */
        module_put(client->driver->driver.owner);
     
    error:
        /* If we have a client but no subdev, then something went wrong and
           we must unregister the client. */
        if (client && sd == NULL)
            i2c_unregister_device(client);
        return sd;


    EXPORT_SYMBOL_GPL(v4l2_device_unregister);
     
    int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
                    struct v4l2_subdev *sd)
    {
    #if defined(CONFIG_MEDIA_CONTROLLER)
        struct media_entity *entity = &sd->entity;
    #endif
        int err;
     
        /* Check for valid input */
        if (v4l2_dev == NULL || sd == NULL || !sd->name[0])
            return -EINVAL;
     
        /* Warn if we apparently re-register a subdev */
        WARN_ON(sd->v4l2_dev != NULL);
     
        if (!try_module_get(sd->owner))
            return -ENODEV;
     
        sd->v4l2_dev = v4l2_dev;
        if (sd->internal_ops && sd->internal_ops->registered) {
            err = sd->internal_ops->registered(sd);
            if (err) {
                module_put(sd->owner);
                return err;
            }
        }
     
        /* This just returns 0 if either of the two args is NULL */
        err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler);
        if (err) {
            if (sd->internal_ops && sd->internal_ops->unregistered)
                sd->internal_ops->unregistered(sd);
            module_put(sd->owner);
            return err;
        }
     
    #if defined(CONFIG_MEDIA_CONTROLLER)
        /* Register the entity. */
        if (v4l2_dev->mdev) {
            err = media_device_register_entity(v4l2_dev->mdev, entity);
            if (err < 0) {
                if (sd->internal_ops && sd->internal_ops->unregistered)
                    sd->internal_ops->unregistered(sd);
                module_put(sd->owner);
                return err;
            }
        }
    #endif
     
        spin_lock(&v4l2_dev->lock);
        list_add_tail(&sd->list, &v4l2_dev->subdevs);
        spin_unlock(&v4l2_dev->lock);
     
        return 0;

标签:struct,V4L2,dev,fimc,linux,device,i2c,v4l2,摄像头
来源: https://www.cnblogs.com/codestack/p/12906064.html