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,
.type = CAM_TYPE_ITU,
.fmt = ITU_601_YCBCR422_8BIT,
.order422 = CAM_ORDER422_8BIT_CBYCRY,
.i2c_busnum = 0,
.info = &s5k4ba_i2c_info,
.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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
看一看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"}">
static int smdkv210_cam0_power(int onoff)
{
int err;
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);
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
其中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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
当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_register(&pdev->dev, &ctrl->v4l2_dev);
if (!fimc_dev->initialized)
fimc_init_global(pdev);
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
首先来看一看fimc_register_controller
struct fimc_control *fimc_register_controller(struct platform_device *pdev)
{
struct s3c_platform_fimc *pdata;
struct fimc_control *ctrl;
pdata = to_fimc_plat(&pdev->dev);
ctrl = get_fimc_ctrl(id);
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
可以看到fimc_register_controller
中设置了ctrl的video_device,申请了寄存器的地址空间,申请了中断(中断程序我们后面会分析),复位摄像头控制器的寄存器
接下来再来看一看fimc_probe
的fimc_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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
注册了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_init(sd, ops);
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,
.s_config = s5k4ba_s_config,
.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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
定义了一系列的回调函数,估计会被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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
拷贝fimc全局变量里面的摄像头信息
-
VIDIOC_S_INPUT
int fimc_s_input(struct file *file, void *fh, unsigned int i)
{
fimc_configure_subdev(ctrl);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
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_info = ctrl->cam->info;
name = i2c_info->type;
addr = i2c_info->addr;
sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
name, i2c_info, &addr);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我们继续看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);
sd = i2c_get_clientdata(client);
v4l2_device_register_subdev(v4l2_dev, sd);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
上面程序中使用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));
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
-
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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
设置像素格式就是将像素格式拷贝到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];
int nr_bufs;
int irq;
int lastirq;
u32 flip;
u32 rotate;
};
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
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;
break;
...
}
fimc_alloc_buffers(ctrl, size, align);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
为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);
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
mmap
内存映射
static int fimc_mmap(struct file *filp, struct vm_area_struct *vma)
{
fimc_mmap_cap(filp, vma);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
VIDIOC_QBUF
缓存入队列
static int fimc_qbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
{
fimc_qbuf_capture(fh, b);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
int fimc_qbuf_capture(void *fh, struct v4l2_buffer *b)
{
fimc_add_inqueue(ctrl, b->index);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
将指定的buf加入队列中
static int fimc_add_inqueue(struct fimc_control *ctrl, int i)
{
list_add_tail(&cap->bufs[i].list, &cap->inq);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
poll
等待数据准备好
static u32 fimc_poll(struct file *filp, poll_table *wait)
{
poll_wait(filp, &ctrl->wq, wait);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
唤醒
什么会唤醒等待队列?
fimc_dev.c的probe函数中,一开始就申请了中断,前面在分析的时候并没有仔细看,现在来好好看一看
当有一帧数据准备完成的时候,就会调用fimc的中断函数,中断会唤醒等待队列,poll就会返回
static irqreturn_t fimc_irq(int irq, void *dev_id)
{
fimc_irq_cap(ctrl);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static inline void fimc_irq_cap(struct fimc_control *ctrl)
{
fimc_hwset_clear_irq(ctrl);
wake_up(&ctrl->wq);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
VIDIOC_DQBUF
出队列
在poll返回后,就调用dqbuf获取缓存
static int fimc_dqbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
{
fimc_dqbuf_capture(fh, b);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
int fimc_dqbuf_capture(void *fh, struct v4l2_buffer *b)
{
pp = ((fimc_hwget_frame_count(ctrl) + 2) % 4);
b->index = cap->outq[pp];
fimc_add_outqueue(ctrl, pp);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这里说一下什么是ping-pong
ping-pong是一个硬件上的东西,视频采集需要一个缓存区,可以把ping-pong看作是一个循环队列,以S5PV210为例,有四个ping-pong,每个ping-pong都存放着目的buf的地址,每当有图像数据准备好,就会通过dma存到对应的ping-pong所指定的目的地址处
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">
文章目录
一、硬件接口
-
摄像头
摄像头传感器由摄像头接口和控制接口(一般为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.c
、fimc_output.c
、fimc_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;
enum fimc_cam_format fmt;
int i2c_busnum;
struct i2c_board_info *info;
struct v4l2_subdev *sd;
int width;
int height;
int inv_pclk;
int inv_vsync;
int inv_href;
int inv_hsync;
int (*cam_power)(int onoff);
};
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
-
s3c_platform_fimc
摄像头接口的平台信息
struct s3c_platform_fimc {
const char srclk_name[16];
const char clk_name[16];
const char lclk_name[16];
u32 clk_rate;
struct s3c_platform_camera *camera[5];
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);
};
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
fimc_control
摄像头控制器
struct fimc_control {
void __iomem *regs;
struct video_device *vd;
struct v4l2_device v4l2_dev;
...
};
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
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,
.type = CAM_TYPE_ITU,
.fmt = ITU_601_YCBCR422_8BIT,
.order422 = CAM_ORDER422_8BIT_CBYCRY,
.i2c_busnum = 0,
.info = &s5k4ba_i2c_info,
.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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
看一看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"}">
static int smdkv210_cam0_power(int onoff)
{
int err;
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);
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
其中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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
当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_register(&pdev->dev, &ctrl->v4l2_dev);
if (!fimc_dev->initialized)
fimc_init_global(pdev);
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
首先来看一看fimc_register_controller
struct fimc_control *fimc_register_controller(struct platform_device *pdev)
{
struct s3c_platform_fimc *pdata;
struct fimc_control *ctrl;
pdata = to_fimc_plat(&pdev->dev);
ctrl = get_fimc_ctrl(id);
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
可以看到fimc_register_controller
中设置了ctrl的video_device,申请了寄存器的地址空间,申请了中断(中断程序我们后面会分析),复位摄像头控制器的寄存器
接下来再来看一看fimc_probe
的fimc_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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
注册了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_init(sd, ops);
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,
.s_config = s5k4ba_s_config,
.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">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
定义了一系列的回调函数,估计会被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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
拷贝fimc全局变量里面的摄像头信息
-
VIDIOC_S_INPUT
int fimc_s_input(struct file *file, void *fh, unsigned int i)
{
fimc_configure_subdev(ctrl);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
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_info = ctrl->cam->info;
name = i2c_info->type;
addr = i2c_info->addr;
sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
name, i2c_info, &addr);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我们继续看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);
sd = i2c_get_clientdata(client);
v4l2_device_register_subdev(v4l2_dev, sd);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
上面程序中使用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));
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
-
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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
设置像素格式就是将像素格式拷贝到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];
int nr_bufs;
int irq;
int lastirq;
u32 flip;
u32 rotate;
};
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
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;
break;
...
}
fimc_alloc_buffers(ctrl, size, align);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
为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);
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
mmap
内存映射
static int fimc_mmap(struct file *filp, struct vm_area_struct *vma)
{
fimc_mmap_cap(filp, vma);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
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);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
VIDIOC_QBUF
缓存入队列
static int fimc_qbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
{
fimc_qbuf_capture(fh, b);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
int fimc_qbuf_capture(void *fh, struct v4l2_buffer *b)
{
fimc_add_inqueue(ctrl, b->index);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
将指定的buf加入队列中
static int fimc_add_inqueue(struct fimc_control *ctrl, int i)
{
list_add_tail(&cap->bufs[i].list, &cap->inq);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
poll
等待数据准备好
static u32 fimc_poll(struct file *filp, poll_table *wait)
{
poll_wait(filp, &ctrl->wq, wait);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
唤醒
什么会唤醒等待队列?
fimc_dev.c的probe函数中,一开始就申请了中断,前面在分析的时候并没有仔细看,现在来好好看一看
当有一帧数据准备完成的时候,就会调用fimc的中断函数,中断会唤醒等待队列,poll就会返回
static irqreturn_t fimc_irq(int irq, void *dev_id)
{
fimc_irq_cap(ctrl);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static inline void fimc_irq_cap(struct fimc_control *ctrl)
{
fimc_hwset_clear_irq(ctrl);
wake_up(&ctrl->wq);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
-
VIDIOC_DQBUF
出队列
在poll返回后,就调用dqbuf获取缓存
static int fimc_dqbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
{
fimc_dqbuf_capture(fh, b);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
int fimc_dqbuf_capture(void *fh, struct v4l2_buffer *b)
{
pp = ((fimc_hwget_frame_count(ctrl) + 2) % 4);
b->index = cap->outq[pp];
fimc_add_outqueue(ctrl, pp);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这里说一下什么是ping-pong
ping-pong是一个硬件上的东西,视频采集需要一个缓存区,可以把ping-pong看作是一个循环队列,以S5PV210为例,有四个ping-pong,每个ping-pong都存放着目的buf的地址,每当有图像数据准备好,就会通过dma存到对应的ping-pong所指定的目的地址处
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146288.html","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: