三星平台fimc驱动详解

24 篇文章 28 订阅

一、硬件接口

摄像头

摄像头传感器由摄像头接口和控制接口(一般为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; //ITU or MIPI(csi)
    
    /* 像素格式 */
    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_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;
    }
}

从中可以看出,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所指定的目的地址处
 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值