• fimc_global

    fimc驱动的全局变量

    struct fimc_global {
    	struct fimc_control		ctrl[FIMC_DEVICES]; //摄像头控制器
    	struct s3c_platform_camera	camera[FIMC_MAXCAMS]; //摄像头
    	int				camera_isvalid[FIMC_MAXCAMS]; //标记是否存在摄像头
    	int				active_camera; // 当前使用的摄像头
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  • 介绍完上面几个结构体后,开始分析源码

    3.2 fimc的平台设备

    fimc的驱动采用的platform总线,分成设备和驱动,我们先介绍设备

    首先看mach-smdkc110.c文件,这里描述了一系列的硬件信息

    这里有多个摄像头描述,其中一个如下

    static struct s3c_platform_camera s5k4ba = {
    	.id		= CAMERA_PAR_A, //摄像头在接口A
    	.type		= CAM_TYPE_ITU, //ITU模式
    	.fmt		= ITU_601_YCBCR422_8BIT, //传感器输入格式YCbCr422
    	.order422	= CAM_ORDER422_8BIT_CBYCRY, //输入Y U V的顺序
    	.i2c_busnum	= 0, //i2c总线0
    	.info		= &s5k4ba_i2c_info, //i2c的设备描述
    	.pixelformat	= V4L2_PIX_FMT_UYVY, //经过摄像头控制器后的输出格式
    	.srclk_name	= "mout_mpll",
    	.clk_name	= "sclk_cam1",
    	.clk_rate	= 44000000, //时钟频率
    	.line_length	= 1920,
    	.width		= 800, //图像宽
    	.height		= 600, //图像高
    	/* 裁剪 */
        .window		= {
    		.left	= 0,
    		.top	= 0,
    		.width	= 800,
    		.height	= 600,
    	},
    
    	/* 时序极性 */
    	.inv_pclk	= 0,
    	.inv_vsync	= 1,
    	.inv_href	= 0,
    	.inv_hsync	= 0,
    
    	.initialized	= 0,
    	.cam_power	= s5k5ba_power_en,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    看一看s5k4ba_i2c_info

    static struct i2c_board_info  s5k4ba_i2c_info = {
    	I2C_BOARD_INFO("S5K4BA", 0x2d),
    	.platform_data = &s5k4ba_plat,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    其中表明i2c设备名称为S5K4BA,i2c从地址0x2d

    再看一看s5k5ba_power_en如何使能摄像头

    static int s5k5ba_power_en(int onoff)
    {
        smdkv210_cam1_power(onoff);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    /* 设置GPIO */
    static int smdkv210_cam0_power(int onoff)
    {
    	int err;
    	/* Camera A */
    	err = gpio_request(GPIO_PS_VOUT, "GPH0");
    	if (err)
    		printk(KERN_ERR "failed to request GPH0 for CAM_2V8\n");
    
    	s3c_gpio_setpull(GPIO_PS_VOUT, S3C_GPIO_PULL_NONE);
    	gpio_direction_output(GPIO_PS_VOUT, 0);
    	gpio_direction_output(GPIO_PS_VOUT, 1);
    	gpio_free(GPIO_PS_VOUT);
    
    	return 0;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    s3c_platform_camera描述一个摄像头的信息(哪个摄像头接口,在哪个i2c总线,像素格式…)

    s5k4ba被嵌入到fimc的平台数据中

    static struct s3c_platform_fimc fimc_plat_lsi = {
    	.srclk_name	= "mout_mpll",
    	.clk_name	= "sclk_fimc",
    	.lclk_name	= "sclk_fimc_lclk",
    	.clk_rate	= 166750000,
    	.default_cam	= CAMERA_PAR_A, //默认接口
        
        /* 保存摄像头信息,可以多个 */
    	.camera		= {
    			&s5k4ba,
    	},
    	.hw_ver		= 0x43,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    s3c_platform_fimc存放着所有摄像头的信息,供驱动程序读取

    mach-smdkc110.c文件中,fimc_plat_lsi最终被设置到fimc的平台设备的平台数据中

    s3c_fimc0_set_platdata(&fimc_plat_lsi);
    s3c_fimc1_set_platdata(&fimc_plat_lsi);
    s3c_fimc2_set_platdata(&fimc_plat_lsi);
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    void __init s3c_fimc0_set_platdata(struct s3c_platform_fimc *pd)
    {
        struct s3c_platform_fimc *npd;
        
        /* 复制一份 */
        npd = kmemdup(pd, sizeof(struct s3c_platform_fimc), GFP_KERNEL);
        
        npd->cfg_gpio = s3c_fimc0_cfg_gpio;
        npd->clk_on = s3c_fimc_clk_on;
        npd->clk_off = s3c_fimc_clk_off;
        
        /* 分配视频缓存 */
        npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMC0, 1);
    
        /* 将器设置到fimc平台设备的平台数据中 */
    	s3c_device_fimc0.dev.platform_data = npd;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    其中s3c_device_fimc0是一个platform总线的平台设备,在内核启动时,会被注册进内核

    struct platform_device s3c_device_fimc0 = {
    	.name		= "s3c-fimc",
    	.id		= 0,
    	.num_resources	= ARRAY_SIZE(s3c_fimc0_resource),
    	.resource	= s3c_fimc0_resource,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    static struct resource s3c_fimc0_resource[] = {
    	[0] = {
    		.start	= S5P_PA_FIMC0, //寄存器地址
    		.end	= S5P_PA_FIMC0 + S5P_SZ_FIMC0 - 1,
    		.flags	= IORESOURCE_MEM,
    	},
    	[1] = {
    		.start	= IRQ_FIMC0, //中断
    		.end	= IRQ_FIMC0,
    		.flags	= IORESOURCE_IRQ,
    	},
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    对于S5PV210来说,芯片上有三个摄像头控制器,那么就有三个fimc的平台设备,三个控制器在经过驱动程序后会生成/dev/video0、/dev/video1、/dev/video2三个设备节点

    上面各对象的包裹关系如下

    在这里插入图片描述

    然后再将platform_device注册进内核

    3.2 fimc的平台驱动

    接下来分析platform_driver

    platform_driver在fimc_dev.c中注册

    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;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    当platform_dev和platform_driver匹配的时候,会调用probe函数

    struct fimc_global *fimc_dev;
    ...
    static int __devinit fimc_probe(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
        struct fimc_control *ctrl;
        
        /* 为全局变量分配内存 */
        if (!fimc_dev)
            fimc_dev = kzalloc(sizeof(*fimc_dev), GFP_KERNEL);
    
        /* 通过平台设备的信息注册摄像头控制器 */
        ctrl = fimc_register_controller(pdev);
    
        /* 注册v4l2_device,主要是为了管理v4l2_subdev */
    	v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
    
        /* 初始化fimc的全局变量 */
    	if (!fimc_dev->initialized)
            fimc_init_global(pdev);
    
        /* 注册video_device字符设备,生成设备节点 */
    	video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    首先来看一看fimc_register_controller

    struct fimc_control *fimc_register_controller(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
    	struct fimc_control *ctrl;
        
        /* 从platform_dev中中获取fimc的平台设备信息 */
        pdata = to_fimc_plat(&pdev->dev);
        
        /* 从全局变量中得到fimc ctrl */
    	ctrl = get_fimc_ctrl(id);
        
        /* 设置ctrl的video_device */
    	ctrl->vd = &fimc_video_device[id];
    
        /* 申请寄存器地址空间 */
    	platform_get_resource(pdev, IORESOURCE_MEM, 0);
        request_mem_region(res->start, res->end - res->start + 1, pdev->name);
        ctrl->regs = ioremap(res->start, res->end - res->start + 1);
        
        /* 申请中断 */
        ctrl->irq = platform_get_irq(pdev, 0);
        request_irq(ctrl->irq, fimc_irq, IRQF_DISABLED, ctrl->name, ctrl)
            
        /* 初始化寄存器,复位摄像头控制器 */
        fimc_hwset_reset(ctrl);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    fimc_video_device是一个video_device的全局数组,每一个摄像头控制器对应其中一项

    struct video_device fimc_video_device[FIMC_DEVICES] = {
    	[0] = {
    		.fops = &fimc_fops,
    		.ioctl_ops = &fimc_v4l2_ops,
    		.release = fimc_vdev_release,
    	},
    	[1] = {
    		.fops = &fimc_fops,
    		.ioctl_ops = &fimc_v4l2_ops,
    		.release = fimc_vdev_release,
    	},
    	[2] = {
    		.fops = &fimc_fops,
    		.ioctl_ops = &fimc_v4l2_ops,
    		.release = fimc_vdev_release,
    	},
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
    static const struct v4l2_file_operations fimc_fops = {
    	.owner		= THIS_MODULE,
    	.open		= fimc_open,
    	.release	= fimc_release,
    	.ioctl		= video_ioctl2,
    	.read		= fimc_read,
    	.write		= fimc_write,
    	.mmap		= fimc_mmap,
    	.poll		= fimc_poll,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    const struct v4l2_ioctl_ops fimc_v4l2_ops = {
    	.vidioc_querycap		= fimc_querycap,
    	.vidioc_reqbufs			= fimc_reqbufs,
    	.vidioc_querybuf		= fimc_querybuf,
    	.vidioc_g_ctrl			= fimc_g_ctrl,
    	.vidioc_s_ctrl			= fimc_s_ctrl,
    	.vidioc_s_ext_ctrls		= fimc_s_ext_ctrls,
    	.vidioc_cropcap			= fimc_cropcap,
    	.vidioc_g_crop			= fimc_g_crop,
    	.vidioc_s_crop			= fimc_s_crop,
    	.vidioc_streamon		= fimc_streamon,
    	.vidioc_streamoff		= fimc_streamoff,
    	.vidioc_qbuf			= fimc_qbuf,
    	.vidioc_dqbuf			= fimc_dqbuf,
    	.vidioc_enum_fmt_vid_cap	= fimc_enum_fmt_vid_capture,
    	.vidioc_g_fmt_vid_cap		= fimc_g_fmt_vid_capture,
    	.vidioc_s_fmt_vid_cap		= fimc_s_fmt_vid_capture,
    	.vidioc_try_fmt_vid_cap		= fimc_try_fmt_vid_capture,
    	.vidioc_enum_input		= fimc_enum_input,
    	.vidioc_g_input			= fimc_g_input,
    	.vidioc_s_input			= fimc_s_input,
    	.vidioc_g_parm			= fimc_g_parm,
    	.vidioc_s_parm			= fimc_s_parm,
    	.vidioc_queryctrl		= fimc_queryctrl,
    	.vidioc_querymenu		= fimc_querymenu,
    	.vidioc_g_fmt_vid_out		= fimc_g_fmt_vid_out,
    	.vidioc_s_fmt_vid_out		= fimc_s_fmt_vid_out,
    	.vidioc_try_fmt_vid_out		= fimc_try_fmt_vid_out,
    	.vidioc_g_fbuf			= fimc_g_fbuf,
    	.vidioc_s_fbuf			= fimc_s_fbuf,
    	.vidioc_try_fmt_vid_overlay	= fimc_try_fmt_overlay,
    	.vidioc_g_fmt_vid_overlay	= fimc_g_fmt_vid_overlay,
    	.vidioc_s_fmt_vid_overlay	= fimc_s_fmt_vid_overlay,
    };
    
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    可以看到fimc_register_controller中设置了ctrl的video_device,申请了寄存器的地址空间,申请了中断(中断程序我们后面会分析),复位摄像头控制器的寄存器

    接下来再来看一看fimc_probefimc_init_global函数

    static int fimc_init_global(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
        pdata = to_fimc_plat(&pdev->dev);
        
        for()
        {
            cam = pdata->camera[i];
            memcpy(&fimc_dev->camera[i], cam, sizeof(*cam));
            fimc_dev->camera_isvalid[i] = 1;
        }
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    从中可以看出,fimc_init_global函数会将fimc平台信息的所有摄像头信息拷贝到fimc的全局变量中

    如如果有三个fimc的platform_dev,那么就会调用三次fimc_probe函数,最后会创建三个设备节点/dev/videox,并且将所有摄像头信息保存到fimc的全局变量中

    接下俩就是一系列的ioctl操作了,此部分将在稍后介绍

    需要注意的是,在此之前,我们介绍的都是摄像头控制器的驱动程序,并没有涉及到真正的摄像头的驱动程序,摄像头在这里被抽象成一个v4l2_subdev,而摄像头控制器是v4l2_device

    再继续fimc的驱动前,我们先来看一个摄像头的驱动程序

    3.3 s5k4ba的驱动程序

    内核源码s5k4ba.c

    首先看驱动的入口函数,当你拖动到最下面的时候,你会惊奇地发现,怎么没有定义module_init入口呢?

    这是不可能地,真相只有一个,那就是在头文件中

    v4l2-i2c-drv.h

    struct v4l2_i2c_driver_data {
    	const char * const name;
    	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    	int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
    	int (*remove)(struct i2c_client *client);
    	int (*suspend)(struct i2c_client *client, pm_message_t state);
    	int (*resume)(struct i2c_client *client);
    	const struct i2c_device_id *id_table;
    };
    
    static struct v4l2_i2c_driver_data v4l2_i2c_data;
    static struct i2c_driver v4l2_i2c_driver;
    
    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);
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    注册了v4l2_i2c_driver,而v4l2_i2c_driver得数据来源自v4l2_i2c_data,所以对于编写驱动程序得人来说,就需要去填充v4l2_i2c_data

    下面回到s5k4ba.c

    static const struct i2c_device_id s5k4ba_id[] = {
    	{ S5K4BA_DRIVER_NAME, 0 },
    	{ },
    };
    
    static struct v4l2_i2c_driver_data v4l2_i2c_data = {
    	.name = S5K4BA_DRIVER_NAME,
    	.probe = s5k4ba_probe,
    	.remove = __devexit_p(s5k4ba_remove),
    	.id_table = s5k4ba_id,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    当该驱动得id_table中得名字与i2c设备的名字匹配时,就调用probe函数

    static int s5k4ba_probe(struct i2c_client *client,
    			 const struct i2c_device_id *id)
    {
        struct v4l2_subdev *sd;
    
        sd = &state->sd;
        
        v4l2_i2c_subdev_init(sd, client, &s5k4ba_ops);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    probe函数会分配一个v4l2_subdev,并调用v4l2_i2c_subdev_init初始化

    接下来看一看v4l2_i2c_subdev_init

    void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
    		const struct v4l2_subdev_ops *ops)
    {
        /* 设置v4l2_subdev的ops */
    	v4l2_subdev_init(sd, ops);
        
        /* 将v4l2_subdev设置为i2c_client的client_data */
        i2c_set_clientdata(client, sd);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    我们看以下s5k4ba_ops的定义

    static const struct v4l2_subdev_core_ops s5k4ba_core_ops = {
    	.init = s5k4ba_init,	/* initializing API */
    	.s_config = s5k4ba_s_config,	/* Fetch platform data */
    	.queryctrl = s5k4ba_queryctrl,
    	.querymenu = s5k4ba_querymenu,
    	.g_ctrl = s5k4ba_g_ctrl,
    	.s_ctrl = s5k4ba_s_ctrl,
    };
    
    static const struct v4l2_subdev_video_ops s5k4ba_video_ops = {
    	.g_fmt = s5k4ba_g_fmt,
    	.s_fmt = s5k4ba_s_fmt,
    	.enum_framesizes = s5k4ba_enum_framesizes,
    	.enum_frameintervals = s5k4ba_enum_frameintervals,
    	.enum_fmt = s5k4ba_enum_fmt,
    	.try_fmt = s5k4ba_try_fmt,
    	.g_parm = s5k4ba_g_parm,
    	.s_parm = s5k4ba_s_parm,
    };
    
    static const struct v4l2_subdev_ops s5k4ba_ops = {
    	.core = &s5k4ba_core_ops,
    	.video = &s5k4ba_video_ops,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    定义了一系列的回调函数,估计会被v4l2_dev调用

    总结一下:s5k4ba.的驱动程序注册了一个i2c_driver,当i2c_driver遇上匹配的i2c_client时,就会调用probe函数,probe函数会生成一个v4l2_subdev,并设置好它的ops(一系列的回调函数),然后将v4l2_subdev设置为i2c_client的clientdata

    那么什么时候会有匹配的i2c_client呢?我们接下来分析fimc的驱动

    3.4 fimc驱动详细分析

    为了更加详细地分析fimc的驱动,接下来我们按照v4l2的应用编程流程来分析fimc的驱动

    首先回顾以下v4l2的应用编程流程

    下面继续分析fimc驱动

    data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146288.html","extend1":"pc","ab":"new"}">>
    id="article_content" class="article_content clearfix"> id="content_views" class="markdown_views prism-atom-one-light">

    深入学习Linux摄像头系列

    深入学习Linux摄像头(一)v4l2应用编程

    深入学习Linux摄像头(二)v4l2驱动框架

    深入学习Linux摄像头(三)虚拟摄像头驱动分析

    深入学习Linux摄像头(四)三星平台fimc驱动详解

    深入学习Linux摄像头(四)三星平台fimc驱动详解

    class="toc">

    文章目录

    一、硬件接口

    摄像头传感器和芯片的接法如下

    在这里插入图片描述

    其中摄像头控制器和摄像头接口是分离的,摄像头控制器可以选择控制哪一个摄像头接口

    fimc是三星平台摄像头控制器的一套驱动程序

    二、fimc驱动总览

    fimc的文件集中在drivers/media/video/samsung/fimc目录下

    有以下文件

    各文件的组织形式如下

    在这里插入图片描述

    fimc_dev.c是平台的驱动,负责一些初始化的工作,fimc_v4l2.c设置了一系列的ioctl操作,fimc_capture.cfimc_output.cfimc_overlay.c实现了一些具体功能的ioctl,通过fimc_regs.c实现对摄像头控制器的硬件操作

    三、源码分析

    3.1 几个主要对象

    在分析源码前,先介绍几个对象

    介绍完上面几个结构体后,开始分析源码

    3.2 fimc的平台设备

    fimc的驱动采用的platform总线,分成设备和驱动,我们先介绍设备

    首先看mach-smdkc110.c文件,这里描述了一系列的硬件信息

    这里有多个摄像头描述,其中一个如下

    static struct s3c_platform_camera s5k4ba = {
    	.id		= CAMERA_PAR_A, //摄像头在接口A
    	.type		= CAM_TYPE_ITU, //ITU模式
    	.fmt		= ITU_601_YCBCR422_8BIT, //传感器输入格式YCbCr422
    	.order422	= CAM_ORDER422_8BIT_CBYCRY, //输入Y U V的顺序
    	.i2c_busnum	= 0, //i2c总线0
    	.info		= &s5k4ba_i2c_info, //i2c的设备描述
    	.pixelformat	= V4L2_PIX_FMT_UYVY, //经过摄像头控制器后的输出格式
    	.srclk_name	= "mout_mpll",
    	.clk_name	= "sclk_cam1",
    	.clk_rate	= 44000000, //时钟频率
    	.line_length	= 1920,
    	.width		= 800, //图像宽
    	.height		= 600, //图像高
    	/* 裁剪 */
        .window		= {
    		.left	= 0,
    		.top	= 0,
    		.width	= 800,
    		.height	= 600,
    	},
    
    	/* 时序极性 */
    	.inv_pclk	= 0,
    	.inv_vsync	= 1,
    	.inv_href	= 0,
    	.inv_hsync	= 0,
    
    	.initialized	= 0,
    	.cam_power	= s5k5ba_power_en,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    看一看s5k4ba_i2c_info

    static struct i2c_board_info  s5k4ba_i2c_info = {
    	I2C_BOARD_INFO("S5K4BA", 0x2d),
    	.platform_data = &s5k4ba_plat,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    其中表明i2c设备名称为S5K4BA,i2c从地址0x2d

    再看一看s5k5ba_power_en如何使能摄像头

    static int s5k5ba_power_en(int onoff)
    {
        smdkv210_cam1_power(onoff);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    /* 设置GPIO */
    static int smdkv210_cam0_power(int onoff)
    {
    	int err;
    	/* Camera A */
    	err = gpio_request(GPIO_PS_VOUT, "GPH0");
    	if (err)
    		printk(KERN_ERR "failed to request GPH0 for CAM_2V8\n");
    
    	s3c_gpio_setpull(GPIO_PS_VOUT, S3C_GPIO_PULL_NONE);
    	gpio_direction_output(GPIO_PS_VOUT, 0);
    	gpio_direction_output(GPIO_PS_VOUT, 1);
    	gpio_free(GPIO_PS_VOUT);
    
    	return 0;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    s3c_platform_camera描述一个摄像头的信息(哪个摄像头接口,在哪个i2c总线,像素格式…)

    s5k4ba被嵌入到fimc的平台数据中

    static struct s3c_platform_fimc fimc_plat_lsi = {
    	.srclk_name	= "mout_mpll",
    	.clk_name	= "sclk_fimc",
    	.lclk_name	= "sclk_fimc_lclk",
    	.clk_rate	= 166750000,
    	.default_cam	= CAMERA_PAR_A, //默认接口
        
        /* 保存摄像头信息,可以多个 */
    	.camera		= {
    			&s5k4ba,
    	},
    	.hw_ver		= 0x43,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    s3c_platform_fimc存放着所有摄像头的信息,供驱动程序读取

    mach-smdkc110.c文件中,fimc_plat_lsi最终被设置到fimc的平台设备的平台数据中

    s3c_fimc0_set_platdata(&fimc_plat_lsi);
    s3c_fimc1_set_platdata(&fimc_plat_lsi);
    s3c_fimc2_set_platdata(&fimc_plat_lsi);
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    void __init s3c_fimc0_set_platdata(struct s3c_platform_fimc *pd)
    {
        struct s3c_platform_fimc *npd;
        
        /* 复制一份 */
        npd = kmemdup(pd, sizeof(struct s3c_platform_fimc), GFP_KERNEL);
        
        npd->cfg_gpio = s3c_fimc0_cfg_gpio;
        npd->clk_on = s3c_fimc_clk_on;
        npd->clk_off = s3c_fimc_clk_off;
        
        /* 分配视频缓存 */
        npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMC0, 1);
    
        /* 将器设置到fimc平台设备的平台数据中 */
    	s3c_device_fimc0.dev.platform_data = npd;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    其中s3c_device_fimc0是一个platform总线的平台设备,在内核启动时,会被注册进内核

    struct platform_device s3c_device_fimc0 = {
    	.name		= "s3c-fimc",
    	.id		= 0,
    	.num_resources	= ARRAY_SIZE(s3c_fimc0_resource),
    	.resource	= s3c_fimc0_resource,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    static struct resource s3c_fimc0_resource[] = {
    	[0] = {
    		.start	= S5P_PA_FIMC0, //寄存器地址
    		.end	= S5P_PA_FIMC0 + S5P_SZ_FIMC0 - 1,
    		.flags	= IORESOURCE_MEM,
    	},
    	[1] = {
    		.start	= IRQ_FIMC0, //中断
    		.end	= IRQ_FIMC0,
    		.flags	= IORESOURCE_IRQ,
    	},
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    对于S5PV210来说,芯片上有三个摄像头控制器,那么就有三个fimc的平台设备,三个控制器在经过驱动程序后会生成/dev/video0、/dev/video1、/dev/video2三个设备节点

    上面各对象的包裹关系如下

    在这里插入图片描述

    然后再将platform_device注册进内核

    3.2 fimc的平台驱动

    接下来分析platform_driver

    platform_driver在fimc_dev.c中注册

    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;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    当platform_dev和platform_driver匹配的时候,会调用probe函数

    struct fimc_global *fimc_dev;
    ...
    static int __devinit fimc_probe(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
        struct fimc_control *ctrl;
        
        /* 为全局变量分配内存 */
        if (!fimc_dev)
            fimc_dev = kzalloc(sizeof(*fimc_dev), GFP_KERNEL);
    
        /* 通过平台设备的信息注册摄像头控制器 */
        ctrl = fimc_register_controller(pdev);
    
        /* 注册v4l2_device,主要是为了管理v4l2_subdev */
    	v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
    
        /* 初始化fimc的全局变量 */
    	if (!fimc_dev->initialized)
            fimc_init_global(pdev);
    
        /* 注册video_device字符设备,生成设备节点 */
    	video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    首先来看一看fimc_register_controller

    struct fimc_control *fimc_register_controller(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
    	struct fimc_control *ctrl;
        
        /* 从platform_dev中中获取fimc的平台设备信息 */
        pdata = to_fimc_plat(&pdev->dev);
        
        /* 从全局变量中得到fimc ctrl */
    	ctrl = get_fimc_ctrl(id);
        
        /* 设置ctrl的video_device */
    	ctrl->vd = &fimc_video_device[id];
    
        /* 申请寄存器地址空间 */
    	platform_get_resource(pdev, IORESOURCE_MEM, 0);
        request_mem_region(res->start, res->end - res->start + 1, pdev->name);
        ctrl->regs = ioremap(res->start, res->end - res->start + 1);
        
        /* 申请中断 */
        ctrl->irq = platform_get_irq(pdev, 0);
        request_irq(ctrl->irq, fimc_irq, IRQF_DISABLED, ctrl->name, ctrl)
            
        /* 初始化寄存器,复位摄像头控制器 */
        fimc_hwset_reset(ctrl);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    fimc_video_device是一个video_device的全局数组,每一个摄像头控制器对应其中一项

    struct video_device fimc_video_device[FIMC_DEVICES] = {
    	[0] = {
    		.fops = &fimc_fops,
    		.ioctl_ops = &fimc_v4l2_ops,
    		.release = fimc_vdev_release,
    	},
    	[1] = {
    		.fops = &fimc_fops,
    		.ioctl_ops = &fimc_v4l2_ops,
    		.release = fimc_vdev_release,
    	},
    	[2] = {
    		.fops = &fimc_fops,
    		.ioctl_ops = &fimc_v4l2_ops,
    		.release = fimc_vdev_release,
    	},
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
    static const struct v4l2_file_operations fimc_fops = {
    	.owner		= THIS_MODULE,
    	.open		= fimc_open,
    	.release	= fimc_release,
    	.ioctl		= video_ioctl2,
    	.read		= fimc_read,
    	.write		= fimc_write,
    	.mmap		= fimc_mmap,
    	.poll		= fimc_poll,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    const struct v4l2_ioctl_ops fimc_v4l2_ops = {
    	.vidioc_querycap		= fimc_querycap,
    	.vidioc_reqbufs			= fimc_reqbufs,
    	.vidioc_querybuf		= fimc_querybuf,
    	.vidioc_g_ctrl			= fimc_g_ctrl,
    	.vidioc_s_ctrl			= fimc_s_ctrl,
    	.vidioc_s_ext_ctrls		= fimc_s_ext_ctrls,
    	.vidioc_cropcap			= fimc_cropcap,
    	.vidioc_g_crop			= fimc_g_crop,
    	.vidioc_s_crop			= fimc_s_crop,
    	.vidioc_streamon		= fimc_streamon,
    	.vidioc_streamoff		= fimc_streamoff,
    	.vidioc_qbuf			= fimc_qbuf,
    	.vidioc_dqbuf			= fimc_dqbuf,
    	.vidioc_enum_fmt_vid_cap	= fimc_enum_fmt_vid_capture,
    	.vidioc_g_fmt_vid_cap		= fimc_g_fmt_vid_capture,
    	.vidioc_s_fmt_vid_cap		= fimc_s_fmt_vid_capture,
    	.vidioc_try_fmt_vid_cap		= fimc_try_fmt_vid_capture,
    	.vidioc_enum_input		= fimc_enum_input,
    	.vidioc_g_input			= fimc_g_input,
    	.vidioc_s_input			= fimc_s_input,
    	.vidioc_g_parm			= fimc_g_parm,
    	.vidioc_s_parm			= fimc_s_parm,
    	.vidioc_queryctrl		= fimc_queryctrl,
    	.vidioc_querymenu		= fimc_querymenu,
    	.vidioc_g_fmt_vid_out		= fimc_g_fmt_vid_out,
    	.vidioc_s_fmt_vid_out		= fimc_s_fmt_vid_out,
    	.vidioc_try_fmt_vid_out		= fimc_try_fmt_vid_out,
    	.vidioc_g_fbuf			= fimc_g_fbuf,
    	.vidioc_s_fbuf			= fimc_s_fbuf,
    	.vidioc_try_fmt_vid_overlay	= fimc_try_fmt_overlay,
    	.vidioc_g_fmt_vid_overlay	= fimc_g_fmt_vid_overlay,
    	.vidioc_s_fmt_vid_overlay	= fimc_s_fmt_vid_overlay,
    };
    
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    可以看到fimc_register_controller中设置了ctrl的video_device,申请了寄存器的地址空间,申请了中断(中断程序我们后面会分析),复位摄像头控制器的寄存器

    接下来再来看一看fimc_probefimc_init_global函数

    static int fimc_init_global(struct platform_device *pdev)
    {
        struct s3c_platform_fimc *pdata;
        pdata = to_fimc_plat(&pdev->dev);
        
        for()
        {
            cam = pdata->camera[i];
            memcpy(&fimc_dev->camera[i], cam, sizeof(*cam));
            fimc_dev->camera_isvalid[i] = 1;
        }
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    从中可以看出,fimc_init_global函数会将fimc平台信息的所有摄像头信息拷贝到fimc的全局变量中

    如如果有三个fimc的platform_dev,那么就会调用三次fimc_probe函数,最后会创建三个设备节点/dev/videox,并且将所有摄像头信息保存到fimc的全局变量中

    接下俩就是一系列的ioctl操作了,此部分将在稍后介绍

    需要注意的是,在此之前,我们介绍的都是摄像头控制器的驱动程序,并没有涉及到真正的摄像头的驱动程序,摄像头在这里被抽象成一个v4l2_subdev,而摄像头控制器是v4l2_device

    再继续fimc的驱动前,我们先来看一个摄像头的驱动程序

    3.3 s5k4ba的驱动程序

    内核源码s5k4ba.c

    首先看驱动的入口函数,当你拖动到最下面的时候,你会惊奇地发现,怎么没有定义module_init入口呢?

    这是不可能地,真相只有一个,那就是在头文件中

    v4l2-i2c-drv.h

    struct v4l2_i2c_driver_data {
    	const char * const name;
    	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    	int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
    	int (*remove)(struct i2c_client *client);
    	int (*suspend)(struct i2c_client *client, pm_message_t state);
    	int (*resume)(struct i2c_client *client);
    	const struct i2c_device_id *id_table;
    };
    
    static struct v4l2_i2c_driver_data v4l2_i2c_data;
    static struct i2c_driver v4l2_i2c_driver;
    
    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);
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    注册了v4l2_i2c_driver,而v4l2_i2c_driver得数据来源自v4l2_i2c_data,所以对于编写驱动程序得人来说,就需要去填充v4l2_i2c_data

    下面回到s5k4ba.c

    static const struct i2c_device_id s5k4ba_id[] = {
    	{ S5K4BA_DRIVER_NAME, 0 },
    	{ },
    };
    
    static struct v4l2_i2c_driver_data v4l2_i2c_data = {
    	.name = S5K4BA_DRIVER_NAME,
    	.probe = s5k4ba_probe,
    	.remove = __devexit_p(s5k4ba_remove),
    	.id_table = s5k4ba_id,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    当该驱动得id_table中得名字与i2c设备的名字匹配时,就调用probe函数

    static int s5k4ba_probe(struct i2c_client *client,
    			 const struct i2c_device_id *id)
    {
        struct v4l2_subdev *sd;
    
        sd = &state->sd;
        
        v4l2_i2c_subdev_init(sd, client, &s5k4ba_ops);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    probe函数会分配一个v4l2_subdev,并调用v4l2_i2c_subdev_init初始化

    接下来看一看v4l2_i2c_subdev_init

    void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
    		const struct v4l2_subdev_ops *ops)
    {
        /* 设置v4l2_subdev的ops */
    	v4l2_subdev_init(sd, ops);
        
        /* 将v4l2_subdev设置为i2c_client的client_data */
        i2c_set_clientdata(client, sd);
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    我们看以下s5k4ba_ops的定义

    static const struct v4l2_subdev_core_ops s5k4ba_core_ops = {
    	.init = s5k4ba_init,	/* initializing API */
    	.s_config = s5k4ba_s_config,	/* Fetch platform data */
    	.queryctrl = s5k4ba_queryctrl,
    	.querymenu = s5k4ba_querymenu,
    	.g_ctrl = s5k4ba_g_ctrl,
    	.s_ctrl = s5k4ba_s_ctrl,
    };
    
    static const struct v4l2_subdev_video_ops s5k4ba_video_ops = {
    	.g_fmt = s5k4ba_g_fmt,
    	.s_fmt = s5k4ba_s_fmt,
    	.enum_framesizes = s5k4ba_enum_framesizes,
    	.enum_frameintervals = s5k4ba_enum_frameintervals,
    	.enum_fmt = s5k4ba_enum_fmt,
    	.try_fmt = s5k4ba_try_fmt,
    	.g_parm = s5k4ba_g_parm,
    	.s_parm = s5k4ba_s_parm,
    };
    
    static const struct v4l2_subdev_ops s5k4ba_ops = {
    	.core = &s5k4ba_core_ops,
    	.video = &s5k4ba_video_ops,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    定义了一系列的回调函数,估计会被v4l2_dev调用

    总结一下:s5k4ba.的驱动程序注册了一个i2c_driver,当i2c_driver遇上匹配的i2c_client时,就会调用probe函数,probe函数会生成一个v4l2_subdev,并设置好它的ops(一系列的回调函数),然后将v4l2_subdev设置为i2c_client的clientdata

    那么什么时候会有匹配的i2c_client呢?我们接下来分析fimc的驱动

    3.4 fimc驱动详细分析

    为了更加详细地分析fimc的驱动,接下来我们按照v4l2的应用编程流程来分析fimc的驱动

    首先回顾以下v4l2的应用编程流程

    下面继续分析fimc驱动

    data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146288.html","extend1":"pc","ab":"new"}">>
    注:本文转载自blog.csdn.net的JT同学的文章"https://blog.csdn.net/weixin_42462202/article/details/99765607"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
    复制链接

    评论记录:

    未查询到任何数据!