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

深入学习Linux摄像头系列

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

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

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

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

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

一、硬件接口

  • 摄像头

    摄像头传感器由摄像头接口和控制接口(一般为i2c)组成

    摄像头接口用于传输传感器采集到的数据

    控制接口用于控制摄像头传感器(例如设置图像格式…)

    在这里插入图片描述

  • 芯片

    芯片上由多个摄像头控制器,多个摄像头接口,多个i2c控制器(i2c总线)

    摄像头控制器负责控制摄像头接口和处理接收到的数据,摄像头接口负责传输图像数据,i2c控制器负责传输控制信息

    在这里插入图片描述

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

在这里插入图片描述

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

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

二、fimc驱动总览

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

有以下文件

  • fimc_dev.c

    fimc的平台驱动

  • fimc_v4l2.c

    实现了一系列的ioctl操作

  • fimc_capture.c

    实现了capture功能

  • fimc_output.c

    实现了output功能

  • fimc_overlay.c

    实现了overlay功能

  • fimc_regs.c

    fimc控制器的寄存器操作

  • csis.c

    csis接口的摄像头

各文件的组织形式如下

在这里插入图片描述

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

三、源码分析

3.1 几个主要对象

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

  • s3c_platform_camera

    摄像头传感器

    struct s3c_platform_camera {
        /* 标记摄像头在哪个摄像头接口 */
    	enum fimc_cam_index		id;
    
        /* 摄像头接口类型 */
    	enum fimc_cam_type		type; //ITU or MIPI
        
        /* 像素格式 */
    	enum fimc_cam_format	fmt;
    
        /* i2c相关信息 */
    	int						i2c_busnum; //摄像头所接的i2c总线
    	struct i2c_board_info	  *info; //摄像头的i2c信息
    	struct v4l2_subdev		 *sd; //表明这是一个v4l2_subdev
    
    	int				width; //图像的宽
    	int				height; //图像的高
    
    
    	/* 摄像头接口的时序极性 */
    	int				inv_pclk;
    	int				inv_vsync;
    	int				inv_href;
    	int				inv_hsync;
    
        /* 使能摄像头 */
    	int				(*cam_power)(int onoff);
    };
    
  • s3c_platform_fimc

    摄像头接口的平台信息

    struct s3c_platform_fimc {
        /* 时钟相关 */
    	const char			srclk_name[16];		/* source of interface clock name */
    	const char			clk_name[16];		/* interface clock name */
    	const char			lclk_name[16];		/* interface clock name */
    	u32				clk_rate;		/* clockrate for interface clock */
    	
        /* 保存摄像头的信息 */
    	struct s3c_platform_camera	*camera[5];		/* FIXME */
    	
    	void				(*cfg_gpio)(struct platform_device *pdev);
    	int				(*clk_on)(struct platform_device *pdev, struct clk *clk);
    	int				(*clk_off)(struct platform_device *pdev, struct clk *clk);
    };
    
  • fimc_control

    摄像头控制器

    struct fimc_control {
        /* 寄存器地址 */
    	void __iomem			*regs;
    
    	/* video_device和v4l2_device */
    	struct video_device		*vd;
    	struct v4l2_device		v4l2_dev;
    
        ...
    };
    
  • 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; // 当前使用的摄像头
    };
    

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

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,
};

看一看s5k4ba_i2c_info

static struct i2c_board_info  s5k4ba_i2c_info = {
	I2C_BOARD_INFO("S5K4BA", 0x2d),
	.platform_data = &s5k4ba_plat,
};

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

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

static int s5k5ba_power_en(int onoff)
{
    smdkv210_cam1_power(onoff);
}
/* 设置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;
}

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,
};

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);
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;
}

其中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,
};
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,
	},
};

对于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;
}

当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);
}

首先来看一看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);
}

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,
	},
};
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,
};
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,
};

可以看到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;
    }
}

从中可以看出,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);

注册了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,
};

当该驱动得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);
}

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);
}

我们看以下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,
};

定义了一系列的回调函数,估计会被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的应用编程流程

  • 查询设备功能(VIDIOC_QUERYCAP)
  • 枚举输入设备(VIDIOC_ENUMINPUT)
  • 设置输入设备(VIDIOC_S_INPUT)
  • 枚举像素格式(VIDIOC_ENUM_FMT)
  • 设置像素格式(VIDIOC_S_FMT)
  • 申请缓存(VIDIOC_REQBUFS)
  • 映射缓存(mmap)
  • 缓存入队列(VIDIOC_QBUF)
  • 打开流(VIDIOC_STREAMON)
  • 等待数据可读(poll)
  • 缓存出队列(VIDIOC_DQBUF)

下面继续分析fimc驱动

  • VIDIOC_QUERYCAP

    获取设备支持的功能

    static int fimc_querycap(struct file *filp, void *fh,
    			 struct v4l2_capability *cap)
    {
    	cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
    				V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING);
    }
    
  • VIDIOC_ENUMINPUT

    枚举输入设备

    这个函数是实现v4l2_subdev的重点

    int fimc_enum_input(struct file *file, void *fh, struct v4l2_input *inp)
    {
        struct fimc_global *fimc = get_fimc_dev();
        
        strcpy(inp->name, fimc->camera[inp->index].info->type);
    }
    

    拷贝fimc全局变量里面的摄像头信息

  • VIDIOC_S_INPUT

    int fimc_s_input(struct file *file, void *fh, unsigned int i)
    {
        /* 注册子设备 */
        fimc_configure_subdev(ctrl);
    }
    
    static 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;
        
    	i2c_adap = i2c_get_adapter(ctrl->cam->i2c_busnum);
        
        /* 获取摄像头的i2c信息 */
        i2c_info = ctrl->cam->info;
        name = i2c_info->type;
        addr = i2c_info->addr;
        
        /* 注册v4l2_subdev */
    	sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
    			name, i2c_info, &addr);
    }
    

    我们继续看v4l2_i2c_new_subdev_board

    struct v4l2_subdev *v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev,
    		struct i2c_adapter *adapter, const char *module_name,
    		struct i2c_board_info *info, const unsigned short *probe_addrs)
    {
        struct v4l2_subdev *sd = NULL;
        struct i2c_client *client;
        
        client = i2c_new_device(adapter, info); //注册i2c设备
        
        sd = i2c_get_clientdata(client); //从注册的i2c设备获取v4l2_subdev
        
        /* 注册v4l2_subdev */
        v4l2_device_register_subdev(v4l2_dev, sd)}
    

    上面程序中使用i2c_new_device注册一个i2c设备,那么此时如果有匹配的i2c_driver,就会调用其probe函数

    sd = i2c_get_clientdata(client);这行代码直接从注册的i2c设备获取client_data,并指明client_data就是v4l2_subdev,那么一定是在i2c驱动中,生成了一个v4l2_subdev,并将其设置到i2c_client的client_data里,没错,这更我们之前分析的摄像头驱动时吻合的

  • VIDIOC_ENUM_FMT

    枚举像素格式

    static const struct v4l2_fmtdesc capture_fmts[] = {
    	{
    		.index		= 0,
    		.type		= V4L2_BUF_TYPE_VIDEO_CAPTURE,
    		.flags		= FORMAT_FLAGS_PACKED,
    		.description	= "RGB-5-6-5",
    		.pixelformat	= V4L2_PIX_FMT_RGB565,
    	}, {
    		.index		= 2,
    		.type		= V4L2_BUF_TYPE_VIDEO_CAPTURE,
    		.flags		= FORMAT_FLAGS_PACKED,
    		.description	= "YUV 4:2:2 packed, YCbYCr",
    		.pixelformat	= V4L2_PIX_FMT_YUYV,
    	}, {
    		.index		= 3,
    		.type		= V4L2_BUF_TYPE_VIDEO_CAPTURE,
    		.flags		= FORMAT_FLAGS_PACKED,
    		.description	= "YUV 4:2:2 packed, CbYCrY",
    		.pixelformat	= V4L2_PIX_FMT_UYVY,
    	},
        ...
    };
    ...
    int fimc_enum_fmt_vid_capture(struct file *file, void *fh,
    					struct v4l2_fmtdesc *f)
    {
        memcpy(f, &capture_fmts[i], sizeof(*f));
    }
    
  • VIDIOC_S_FMT

    设置像素格式

    int fimc_s_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f)
    {
        struct fimc_capinfo *cap; //捕获设备的信息
        
        ctrl->cap = kmalloc(sizeof(*cap), GFP_KERNEL);
    	cap = ctrl->cap;
    	memcpy(&cap->fmt, &f->fmt.pix, sizeof(cap->fmt)); 
    
    	depth = fimc_fmt_depth(ctrl, f);
    }
    

    设置像素格式就是将像素格式拷贝到fimc_capinfo中

    fimc_capinfo时描述的是捕获设备的信息,如下

    struct fimc_capinfo {
    	struct v4l2_cropcap	cropcap;
    	struct v4l2_rect	crop;
    	struct v4l2_pix_format	fmt;
    	struct fimc_buf_set	bufs[FIMC_CAPBUFS]; //视频缓存
    	struct list_head	inq;
    	int			outq[FIMC_PHYBUFS]; //dma地址
    	int			nr_bufs;
    	int			irq;
    	int			lastirq;
    
    	/* flip: V4L2_CID_xFLIP, rotate: 90, 180, 270 */
    	u32			flip;
    	u32			rotate;
    };
    
  • VIDIOC_REQBUFS

    申请缓存

    int fimc_reqbufs_capture(void *fh, struct v4l2_requestbuffers *b)
    {
        int size[4] = { 0, 0, 0, 0}; // 颜色分量的大小
        
        /* 计算像素各颜色分量的大小 */
        switch()
        {
        ...
    	case V4L2_PIX_FMT_YUV420:
    		size[0] = cap->fmt.width * cap->fmt.height;
    		size[1] = cap->fmt.width * cap->fmt.height >> 2;
    		size[2] = cap->fmt.width * cap->fmt.height >> 2;
    		size[3] = 16; /* Padding buffer */
        	break;
        ...
        }
        
        /* 分配dma缓存 */
        fimc_alloc_buffers(ctrl, size, align);
    }
    

    为cap中的buf分配缓存

    static int fimc_alloc_buffers(struct fimc_control *ctrl, int size[], int align)
    {
        for()
        {
            fimc_dma_alloc(ctrl, &cap->bufs[i], plane, align);
        }
    }
    
  • mmap

    内存映射

    static int fimc_mmap(struct file *filp, struct vm_area_struct *vma)
    {
        fimc_mmap_cap(filp, vma);
    }
    
    static inline int fimc_mmap_cap(struct file *filp, struct vm_area_struct *vma)
    {
        pfn = __phys_to_pfn(ctrl->cap->bufs[idx].base[0]);
        remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
    }
    
  • VIDIOC_QBUF

    缓存入队列

    static int fimc_qbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
    {
        fimc_qbuf_capture(fh, b);
    }
    
    int fimc_qbuf_capture(void *fh, struct v4l2_buffer *b)
    {
        fimc_add_inqueue(ctrl, b->index);
    }
    

    将指定的buf加入队列中

    static int fimc_add_inqueue(struct fimc_control *ctrl, int i)
    {
        list_add_tail(&cap->bufs[i].list, &cap->inq);
    }
    
  • poll

    等待数据准备好

    static u32 fimc_poll(struct file *filp, poll_table *wait)
    {
        /* 加入等待队列 */
        poll_wait(filp, &ctrl->wq, wait);
    }
    
  • 唤醒

    什么会唤醒等待队列?

    fimc_dev.c的probe函数中,一开始就申请了中断,前面在分析的时候并没有仔细看,现在来好好看一看

    当有一帧数据准备完成的时候,就会调用fimc的中断函数,中断会唤醒等待队列,poll就会返回

    static irqreturn_t fimc_irq(int irq, void *dev_id)
    {
        fimc_irq_cap(ctrl);
    }
    
    static inline void fimc_irq_cap(struct fimc_control *ctrl)
    {
    	fimc_hwset_clear_irq(ctrl); //清中断
        
        /* 唤醒等待队列 */
        wake_up(&ctrl->wq);
    }
    
  • VIDIOC_DQBUF

    出队列

    在poll返回后,就调用dqbuf获取缓存

    static int fimc_dqbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
    {
    	fimc_dqbuf_capture(fh, b);
    }
    
    int fimc_dqbuf_capture(void *fh, struct v4l2_buffer *b)
    {
        /* 找到准备好的ping-pong */
        pp = ((fimc_hwget_frame_count(ctrl) + 2) % 4); 
        
        b->index = cap->outq[pp];
        
        /* 重新设置ping-pong的buf */
        fimc_add_outqueue(ctrl, pp);
    }
    

    这里说一下什么是ping-pong

    ping-pong是一个硬件上的东西,视频采集需要一个缓存区,可以把ping-pong看作是一个循环队列,以S5PV210为例,有四个ping-pong,每个ping-pong都存放着目的buf的地址,每当有图像数据准备好,就会通过dma存到对应的ping-pong所指定的目的地址处

  • 5
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Samsung Exynos 是三星公司开发的一系列处理器,主要用于智能手机和平板电脑等移动设备。以下是一些与 Samsung Exynos 相关的内核配置项介绍: 1. CONFIG_ARCH_EXYNOS:该配置项表示启用 Exynos 处理器的支持。如果要在 Linux 内核中使用 Exynos 处理器,必须启用该选项。 2. CONFIG_ARCH_EXYNOS5:该配置项表示启用 Exynos 5 系列处理器的支持。Exynos 5 系列处理器是一种基于 ARM Cortex-A15 架构的处理器。 3. CONFIG_ARCH_EXYNOS7:该配置项表示启用 Exynos 7 系列处理器的支持。Exynos 7 系列处理器是一种基于 ARM Cortex-A57 和 Cortex-A53 架构的处理器。 4. CONFIG_SOC_EXYNOS:该配置项表示启用 Exynos 处理器的 SoC 支持。如果要在 Linux 内核中使用 Exynos 处理器的 SoC 功能,必须启用该选项。 5. CONFIG_EXYNOS_ACE:该配置项表示启用 Exynos 处理器的 ACE 引擎支持。ACE 引擎是 Exynos 处理器的硬件加速引擎,用于加速音频和视频编解码等操作。 6. CONFIG_EXYNOS_MFC:该配置项表示启用 Exynos 处理器的 MFC 引擎支持。MFC 引擎是 Exynos 处理器的硬件加速引擎,用于加速视频编解码等操作。 7. CONFIG_EXYNOS_FIMC:该配置项表示启用 Exynos 处理器的 FIMC 引擎支持。FIMC 引擎是 Exynos 处理器的硬件加速引擎,用于加速图像处理等操作。 8. CONFIG_EXYNOS_IOMMU:该配置项表示启用 Exynos 处理器的 IOMMU 支持。IOMMU 是一种硬件技术,用于将虚拟地址转换为物理地址,以提高系统的安全性和性能。 9. CONFIG_EXYNOS_BUS_DEV_AHB:该配置项表示启用 Exynos 处理器的 AHB 总线设备支持。AHB 总线是一种高性能、低功耗的总线,用于连接处理器和外设。 10. CONFIG_EXYNOS_BUS_DEV_AXI:该配置项表示启用 Exynos 处理器的 AXI 总线设备支持。AXI 总线是一种高性能、高带宽的总线,用于连接处理器和存储器等高速设备。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值