• 找回了当年一篇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 ", __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 ",
                        __func__);
                    return -ENOMEM;
                }
            }
         
            ctrl = fimc_register_controller(pdev);
                         
            if (!ctrl) {
                printk(KERN_ERR "%s: cannot register fimc ", __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 ", __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 ", __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 ", __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 ", __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 ", __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 ", __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 ",
                            __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 ",
                            __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 ",
                       __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 ");
                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 ");
                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 ", __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 ", __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 ", __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 ",
                           __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 ");
            ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
            if (ret < 0) {
                printk(KERN_WARNING "videodev: unable to get major %d ",
                        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 ");
                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) ",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 ", __func__, i);
         
            if (i < 0 || i >= FIMC_MAXCAMS) {
                fimc_err("%s: invalid input index ", __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. ", __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. ", __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 ");
         
            i2c_info = ctrl->cam->info;
            if (!i2c_info) {
                fimc_err("%s: subdev i2c board info missing ", __func__);
                return -ENODEV;
            }
         
            name = i2c_info->type;
            if (!name) {
                fimc_err("subdev i2c driver name missing-skip registration ");
                return -ENODEV;
            }
         
            addr = i2c_info->addr;
            if (!addr) {
                fimc_err("subdev i2c address missing-skip registration ");
                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 ",
                        __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 ", __func__);
    return -EINVAL;
    }


    if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) {
    fimc_err("%s: mmap fail ", __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 ",
                 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 ",
                 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 ");
                return NULL;
            }
         
            v4l2_info(&vid_cap->v4l2_dev, "subdevice %s registered successfuly ",
                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;

  • 相关阅读:
    大端序与小端序
    中断分类
    PHP开发框架[国内框架]
    PHP开发框架[流行度排名]
    ecshop 后台分页功能
    Windows下phpStudy中的Apache无法启动的排查方法
    Windows里配置Apache2.2+PHP5.3+mod_fcgid运行高效的FastCGI模式
    Apache多虚拟主机多版本PHP(5.2+5.3+5.4)共存运行配置全过程
    让 Node.js 支持 ES6 的语法
    微信小程序请求wx.request数据,渲染到页面
  • 原文地址:https://www.cnblogs.com/codestack/p/12906064.html
Copyright © 2020-2023  润新知