0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内核之ISP驱动流程分析

冬至配饺子 来源:嵌入式软件开发交流 作者:young 2022-08-07 16:13 次阅读

ISP驱动分析

Linux版本: 4.19

芯片平台: RK3399/RK3288

源码路径:

drivers/media/platform/rk-isp10/cif_isp10_v4l2.c

drivers/media/platform/rk-isp10/cif_isp10.c

(1)装载和卸载函数

//DTS匹配

static const struct of_device_id cif_isp10_v4l2_of_match[] = {

{.compatible = "rockchip,rk3288-cif-isp",

.data = (void *)&rk3288_cfg},

{.compatible = "rockchip,rk3399-cif-isp",

.data = (void *)&rk3399_cfg},

{},

};

static struct platform_driver cif_isp10_v4l2_plat_drv = {

.driver = {

.name = DRIVER_NAME,

.of_match_table = of_match_ptr(cif_isp10_v4l2_of_match),

.pm = &cif_isp10_dev_pm_ops,

},

.probe = cif_isp10_v4l2_drv_probe,

.remove = cif_isp10_v4l2_drv_remove,

.suspend = cif_isp10_v4l2_drv_suspend,

.resume = cif_isp10_v4l2_drv_resume,

};

static int cif_isp10_v4l2_init(void)

{

int ret;

g_cif_isp10_v4l2_dev_cnt = 0;

ret = platform_driver_register(&cif_isp10_v4l2_plat_drv); //注册platform_driver

if (ret) {

cif_isp10_pltfrm_pr_err(NULL,

"cannot register platform driver, failed with %d\n",

ret);

return -ENODEV;

}

return ret;

}

static void __exit cif_isp10_v4l2_exit(void)

{

platform_driver_unregister(&cif_isp10_v4l2_plat_drv);

}

上面就是简单地注册了一个platform设备。

(2)probe()

static int cif_isp10_v4l2_drv_probe(struct platform_device *pdev)

{

const struct of_device_id *match;

struct device_node *node = pdev->dev.of_node;

struct cif_isp10_device *dev = NULL;

struct cif_isp10_v4l2_device *cif_isp10_v4l2_dev;

int ret;

//........

//分配cif_isp10_v4l2_device

cif_isp10_v4l2_dev = devm_kzalloc(

&pdev->dev,

sizeof(struct cif_isp10_v4l2_device),

GFP_KERNEL);

//.....

match = of_match_node(cif_isp10_v4l2_of_match, node); //获取匹配的是RK3288还是RK3399

dev = cif_isp10_create(&pdev->dev, //创建cif_isp10_device

cif_isp10_v4l2_event,

cif_isp10_v4l2_requeue_bufs,

(struct pltfrm_soc_cfg *)match->data);

//......

dev->dev_id = g_cif_isp10_v4l2_dev_cnt;

dev->isp_dev.dev_id = &dev->dev_id;

dev->nodes = (void *)cif_isp10_v4l2_dev;

dev->isp_state = CIF_ISP10_STATE_IDLE;

spin_lock_init(&dev->vbq_lock);

spin_lock_init(&dev->vbreq_lock);

spin_lock_init(&dev->iowrite32_verify_lock);

spin_lock_init(&dev->isp_state_lock);

init_waitqueue_head(&dev->isp_stop_wait);

mutex_init(&dev->api_mutex);

ret = v4l2_device_register(dev->dev, &dev->v4l2_dev); //注册v4l2_device

//......

ret = cif_isp10_v4l2_register_video_device( //注册video_device, 即生成/dev/videox节点,该节点具有VIDEO_OVERLAY功能

dev,

&cif_isp10_v4l2_dev->node[SP_DEV].vdev, //SP:selfpath

SP_VDEV_NAME,

V4L2_CAP_VIDEO_OVERLAY,

CIF_ISP10_V4L2_SP_DEV_MAJOR,

&cif_isp10_v4l2_fops,

&cif_isp10_v4l2_sp_ioctlops);

if (ret)

goto err;

ret = register_cifisp_device(&dev->isp_dev, //注册ISP video_device

&cif_isp10_v4l2_dev->node[ISP_DEV].vdev,

&dev->v4l2_dev,

dev->config.base_addr);

if (ret)

goto err;

ret = cif_isp10_v4l2_register_video_device( //注册video_device, 即生成/dev/videox节点,该节点具有VIDEO_CAPTURE功能

dev,

&cif_isp10_v4l2_dev->node[MP_DEV].vdev, //MP:mainpath

MP_VDEV_NAME,

V4L2_CAP_VIDEO_CAPTURE,

CIF_ISP10_V4L2_MP_DEV_MAJOR,

&cif_isp10_v4l2_fops,

&cif_isp10_v4l2_mp_ioctlops);

if (ret)

goto err;

ret = cif_isp10_v4l2_register_video_device( //注册video_device, 即生成/dev/videox节点,该节点具有VIDEO_OUTPUT功能

dev,

&cif_isp10_v4l2_dev->node[DMA_DEV].vdev,

DMA_VDEV_NAME,

V4L2_CAP_VIDEO_OUTPUT,

CIF_ISP10_V4L2_DMA_DEV_MAJOR,

&cif_isp10_v4l2_fops,

&cif_isp10_v4l2_dma_ioctlops);

if (ret)

goto err;

cif_isp10_v4l2_register_imgsrc_subdev( //注册v4l2_subdev,关联v4l2_device和v4l2_subdev

dev);

pm_runtime_enable(&pdev->dev);

g_cif_isp10_v4l2_dev[g_cif_isp10_v4l2_dev_cnt] =

cif_isp10_v4l2_dev;

g_cif_isp10_v4l2_dev_cnt++;

return 0;

err:

cif_isp10_destroy(dev);

return ret;

}

上面主要做了:

(1)创建和初始化cif_isp10_device,该结构体中保存着从DTS中解析出来的信息

(2)注册v4l2_device

(3)注册了4个video_device:

rkisp1_ispdev:ISP设备

rkisp1_selfpath: 图像捕获设备

rkisp1_mainpath: 图像捕获设备,用于高分辨率

rkisp1_dmapath: DMA设备

(4)注册v4l2_subdev, 将v4l2_device和v4l2_subdev关联到一起。

*注意: 应用层就是通过访问video_device生成的节点来进行操作Camera,所以video_device注册时指定了很多ioctl函数。

(3)创建cif_isp10_device

struct cif_isp10_device *cif_isp10_create(

CIF_ISP10_PLTFRM_DEVICE pdev,

void (*sof_event)(struct cif_isp10_device *dev, __u32 frame_sequence),

void (*requeue_bufs)(struct cif_isp10_device *dev,

enum cif_isp10_stream_id stream_id),

struct pltfrm_soc_cfg *soc_cfg)

{

int ret;

struct cif_isp10_device *dev;

cif_isp10_pltfrm_pr_dbg(NULL, "\n");

//分配结构体

dev = kzalloc(sizeof(*dev), GFP_KERNEL);

//......

dev->sof_event = sof_event;

dev->requeue_bufs = requeue_bufs;

ret = cif_isp10_pltfrm_dev_init(dev,

&pdev, &dev->config.base_addr); //平台初始化(重映射寄存器地址,申请中断等)

cif_isp10_pltfrm_soc_init(dev, soc_cfg); //soc相关初始化(ISP,DPHY等时钟初始化)

ret = cif_isp10_img_srcs_init(dev); //初始化图像源,即ISP连接的Camera

ret = cif_isp10_register_isrs(dev); //注册中断处理函数 (ISR, Interrupt Service Routine),

//......

dev->pm_state = CIF_ISP10_PM_STATE_OFF;

dev->sp_stream.state = CIF_ISP10_STATE_DISABLED;

dev->sp_stream.id = CIF_ISP10_STREAM_SP;

dev->mp_stream.state = CIF_ISP10_STATE_DISABLED;

dev->mp_stream.id = CIF_ISP10_STREAM_MP;

dev->dma_stream.state = CIF_ISP10_STATE_DISABLED;

dev->dma_stream.id = CIF_ISP10_STREAM_DMA;

dev->config.mi_config.async_updt = 0;

(void)cif_isp10_init(dev, CIF_ISP10_ALL_STREAMS); //初始化所有的流(SP,MP,DMA)

cif_isp10_pltfrm_event_init(dev->dev, &dev->dma_stream.done);

cif_isp10_pltfrm_event_init(dev->dev, &dev->sp_stream.done);

cif_isp10_pltfrm_event_init(dev->dev, &dev->mp_stream.done);

dev->img_src_exps.exp_valid_frms[VALID_FR_EXP_T_INDEX] = 4;

dev->img_src_exps.exp_valid_frms[VALID_FR_EXP_G_INDEX] = 4;

dev->img_src_exps.inited = false;

mutex_init(&dev->img_src_exps.mutex);

memset(&dev->img_src_exps.data, 0x00, sizeof(dev->img_src_exps.data));

spin_lock_init(&dev->img_src_exps.lock);

INIT_LIST_HEAD(&dev->img_src_exps.list);

dev->vs_wq = alloc_workqueue("cif isp10 vs workqueue",

WQ_UNBOUND | WQ_MEM_RECLAIM, 1);

/* TBD: clean this up */

init_output_formats();

return dev;

//省略异常处理.....

}

上面是主要的初始化,包括dts解析,时钟设置,关联Camera等配置。

主要的几个初始化函数就是上面注释的位置,我们分析一下 cif_isp10_img_srcs_init,它会获取ISP上关联的Camera。

static int cif_isp10_img_srcs_init(

struct cif_isp10_device *dev)

{

int ret = 0;

memset(dev->img_src_array, 0x00, sizeof(dev->img_src_array));

dev->img_src_cnt = cif_isp10_pltfrm_get_img_src_device(dev->dev, //获取ISP上的Camera,最多10个

dev->img_src_array, CIF_ISP10_NUM_INPUTS);

if (dev->img_src_cnt > 0)

return 0;

dev->img_src_cnt = 0;

ret = -EFAULT;

cif_isp10_pltfrm_pr_err(dev->dev,

"failed with error %d\n", ret);

return ret;

}

int cif_isp10_pltfrm_get_img_src_device(

struct device *dev,

struct cif_isp10_img_src **img_src_array,

unsigned int array_len)

{

struct device_node *node = NULL;

struct device_node *camera_list_node = NULL;

struct i2c_client *client = NULL;

int ret = 0;

int index, size = 0;

const __be32 *phandle;

int num_cameras = 0;

struct cif_isp10_device *cif_isp10_dev = dev_get_drvdata(dev);

node = of_node_get(dev->of_node);

//.......

//获取ISP上关联的Camera

phandle = of_get_property(node,

"rockchip,camera-modules-attached", &size);

//.......

for (index = 0; index < size / sizeof(*phandle); index++) {

camera_list_node = of_parse_phandle(node,

"rockchip,camera-modules-attached", index);

of_node_put(node);

//......

//判断是不是I2C subdev,是的话就加入到数组中

if (!strcmp(camera_list_node->type,

"v4l2-i2c-subdev")) {

client = of_find_i2c_device_by_node(

camera_list_node);

//......

} else {

//......

continue;

}

//加到数组中

img_src_array[num_cameras] =

cif_isp10_img_src_to_img_src(

&client->dev,

&(cif_isp10_dev->soc_cfg));

if (!IS_ERR_OR_NULL(img_src_array[num_cameras])) {

cif_isp10_pltfrm_pr_info(dev,

"%s attach to cif isp10 img_src_array[%d]\n",

cif_isp10_img_src_g_name(

img_src_array[num_cameras]),

num_cameras);

num_cameras++;

if (num_cameras >= array_len) {

cif_isp10_pltfrm_pr_err(dev,

"cif isp10 isn't support > %d 'camera modules attached'\n",

array_len);

break;

}

}

}

return num_cameras;

//省略异常处理.....

}

上面就是获取DTS中的如下定义:

&cif_isp0 {

rockchip,camera-modules-attached = <&camera0>;

status = "okay";

};

获取完后保存在数组中,cif_isp10_v4l2_register_imgsrc_subdev函数中会将这些v4l2_subdev和v4l2_device关联。

(4)ioctl

应用层调用ioctl会先调用到v4l2_file_operations中的unlocked_ioctl或compat_ioctl32,然后最终会调用到v4l2_ioctl_ops中的各个ioctl。

所以应用层对Camera的控制主要就是通过ioctl,我们随便找两个看看:

查询V4L2功能

static int v4l2_querycap(struct file *file,

void *priv, struct v4l2_capability *cap)

{

struct vb2_queue *queue = to_vb2_queue(file);

struct video_device *vdev = video_devdata(file);

struct cif_isp10_device *dev = to_cif_isp10_device(queue);

u32 stream_ids = to_stream_id(file); //获取id

strcpy(cap->driver, DRIVER_NAME);

strlcpy(cap->card, vdev->name, sizeof(cap->card));

snprintf(cap->bus_info, sizeof(cap->bus_info),

"platform:" DRIVER_NAME "-%03i",

dev->dev_id);

//根据ID(SP,MP,DMA)返回对应的功能

if (stream_ids == CIF_ISP10_STREAM_SP) {

cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |

V4L2_CAP_STREAMING;

cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE |

V4L2_CAP_STREAMING;

} else if (stream_ids == CIF_ISP10_STREAM_MP) {

cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |

V4L2_CAP_STREAMING;

cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE |

V4L2_CAP_STREAMING;

}

else if (stream_ids == CIF_ISP10_STREAM_DMA)

cap->capabilities = V4L2_CAP_VIDEO_M2M_MPLANE |

V4L2_CAP_VIDEO_M2M;

cap->capabilities |= V4L2_CAP_DEVICE_CAPS;

cap->device_caps |= V4L2_CAP_DEVICE_CAPS;

return 0;

}

这只是某个ioctl的处理函数,在内部还有非常多,我们就不一一举例了。因为ISP和Camera已经关联在一起了,所以ISP中的ioctl会去调用Camera驱动中的ioctl。这也就串联起来了!

建议大家可以去网上找个Camera拍照的应用demo,就会清楚为什么驱动会分析到ioctl了。

2. 打开数据流

static int cif_isp10_v4l2_streamon(

struct file *file,

void *priv,

enum v4l2_buf_type buf_type)

{

//......

//打开buffer, 准备接收数据流

ret = vb2_streamon(queue, buf_type);

//开启Camera数据流。让数据流从Camera流到ISP

ret = cif_isp10_streamon(dev, stream_ids);

if (IS_ERR_VALUE(ret)) {

goto err;

}

return 0;

//......

}

开启队列中的buffer,然后调用Camera中的接口开启数据流,让数据流从Camera流到ISP。

int cif_isp10_streamon(

struct cif_isp10_device *dev,

u32 stream_ids)

{

int ret = 0;

bool streamon_sp = stream_ids & CIF_ISP10_STREAM_SP;

bool streamon_mp = stream_ids & CIF_ISP10_STREAM_MP;

bool streamon_dma = stream_ids & CIF_ISP10_STREAM_DMA;

//......

stream_ids = 0;

if (streamon_mp && dev->mp_stream.updt_cfg)

stream_ids |= CIF_ISP10_STREAM_MP;

if (streamon_sp && dev->sp_stream.updt_cfg)

stream_ids |= CIF_ISP10_STREAM_SP;

ret = cif_isp10_config_cif(dev, stream_ids);

if (IS_ERR_VALUE(ret))

goto err;

//开启数据传输

ret = cif_isp10_start(dev, streamon_sp, streamon_mp);

if (IS_ERR_VALUE(ret))

goto err;

//......

}

static int cif_isp10_start(

struct cif_isp10_device *dev,

bool start_sp,

bool start_mp)

{

if (!CIF_ISP10_INP_IS_DMA(dev->config.input_sel)) {

//调用Camera中的ioctl开启数据流

mutex_lock(&dev->img_src_exps.mutex);

cif_isp10_img_src_ioctl(dev->img_src,

RK_VIDIOC_SENSOR_MODE_DATA,

&dev->img_src_exps.data[0].data);

cif_isp10_img_src_ioctl(dev->img_src,

RK_VIDIOC_SENSOR_MODE_DATA,

&dev->img_src_exps.data[1].data);

dev->img_src_exps.data[0].v_frame_id = 0;

dev->img_src_exps.data[1].v_frame_id = 0;

mutex_unlock(&dev->img_src_exps.mutex);

//.......

}

//.......

return ret;

}

long cif_isp10_img_src_ioctl(

struct cif_isp10_img_src *img_src,

unsigned int cmd,

void *arg)

{

if (!img_src) {

cif_isp10_pltfrm_pr_err(NULL, "img_src is NULL\n");

return -EINVAL;

}

//调用Camera的ioctl

return img_src->ops->ioctl(img_src->img_src, cmd, arg);

}

通过上面的一些列调用关系可以看出,最终调用了Camera的ioctl。这里img_src指的就是sensor。数据就开始从Camera一直流向ISP。

总结

我们分析了ISP驱动的一个大致流程,Camera的很多核心算法不是放在驱动上的,大部分都是放在应用层上面的。所以我们在驱动上看到的更多是一些控制,参数配置等接口。



审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11216

    浏览量

    208773
  • ISP
    ISP
    +关注

    关注

    6

    文章

    475

    浏览量

    51688
  • Linux驱动
    +关注

    关注

    0

    文章

    43

    浏览量

    9945
  • RK3399
    +关注

    关注

    2

    文章

    211

    浏览量

    24744
收藏 人收藏

    评论

    相关推荐

    Linux编译驱动内核及应用程序分析

    作为一名嵌入式Linux新手,在学习的过程中会遇到很多问题。写了一个驱动程序怎么编译?怎么加载进内核
    的头像 发表于 01-17 13:46 6589次阅读
    <b class='flag-5'>Linux</b>编译<b class='flag-5'>驱动</b>、<b class='flag-5'>内核</b>及应用程序<b class='flag-5'>分析</b>

    Linux驱动分析input子系统

    Linux内核为了能够处理各种不同类型的输入设备,比如: 触摸屏 ,鼠标 , 键盘 , 操纵杆等设备 ,设计并实现了Linux 输入子系统 ,它为驱动和应用提供了统一的接口函数,方便实
    发表于 02-01 10:38 512次阅读

    Linux内核分析笔记总结

    孟宁老师这门课并没有完整的分析Linux内核中代码,而是针对关键部分进行了讲解分析,个人认为内核代码也是存在二八定律的情况,少部分关键代码经
    发表于 07-18 06:00

    基于Linux内核输入子系统的驱动研究

    Linux因其完全开放的特性和稳定优良的性能深受欢迎,当推出了内核输入子系统后,更方便了嵌入式领域的驱动开放。介绍了Linux的设备驱动基础
    发表于 09-12 16:38 23次下载

    linux内核启动内核解压过程分析

    linux启动时内核解压过程分析,一份不错的文档,深入了解内核必备
    发表于 03-09 13:39 1次下载

    linux2.6内核设备驱动模型精华

    linux 内核驱动部分详解
    发表于 04-27 10:43 20次下载

    基于Linux 2.6内核Makefile分析

    基于2.4内核的,可以说关于2.6内核Makefile相关的文章凤毛麟角,笔者抽时间完成了这篇分析文章,让读者迅速熟悉Linux最新Makefile体系,从而加深对
    发表于 09-18 19:09 0次下载
    基于<b class='flag-5'>Linux</b> 2.6<b class='flag-5'>内核</b>Makefile<b class='flag-5'>分析</b>

    Linux内核输入子系统的驱动研究

    Linux内核输入子系统的驱动研究
    发表于 10-31 14:41 14次下载
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>输入子系统的<b class='flag-5'>驱动</b>研究

    关于Linux 2.6内核Makefile的分析

    的介绍文章都是基于2.4内核的,可以说关于2.6内核Makefile相关的文章凤毛麟角,笔者抽时间完成了这篇分析文章,让读者迅速熟悉Linux最新Makefile体系,从而加深对
    发表于 11-02 10:12 1次下载

    linux内核启动流程

    Linux的启动代码真的挺大,从汇编到C,从Makefile到LDS文件,需要理解的东西很多。毕竟Linux内核是由很多人,花费了巨大的时间和精力写出来的。而且直到现在,这个世界上仍然有成千上万的程序员在不断完善
    发表于 11-14 16:19 4331次阅读
    <b class='flag-5'>linux</b><b class='flag-5'>内核</b>启动<b class='flag-5'>流程</b>

    基于Linux与Busybox的Reboot命令流程分析

    busybox是如何运行这个命令,同时又是如何调用到Linux内核中的mach_reset中的arch_reset,当针对不同的ARM芯片时,作为Linux内核开发和
    发表于 05-05 14:31 2503次阅读
    基于<b class='flag-5'>Linux</b>与Busybox的Reboot命令<b class='flag-5'>流程</b><b class='flag-5'>分析</b>

    如何使用Linux内核实现USB驱动程序框架

    Linux内核提供了完整的USB驱动程序框架。USB总线采用树形结构,在一条总线上只能有唯一的主机设备。 Linux内核从主机和设备两个角度
    发表于 11-06 17:59 20次下载
    如何使用<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>实现USB<b class='flag-5'>驱动</b>程序框架

    Linux内核SoftIrq源代码分析

    我们在分析linux内核中断剖析时,简单的聊了一下SOFTIRQ, 而没有进行深入分析. Linux内核
    发表于 06-23 15:22 543次阅读

    Linux内核代码60%都是驱动

    为什么Linux内核代码60%都是驱动? 如果每支持新的设备就加入驱动内核会不会变得越来越臃肿?
    的头像 发表于 07-11 11:48 852次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>代码60%都是<b class='flag-5'>驱动</b>?

    linux驱动程序如何加载进内核

    Linux系统中,驱动程序是内核与硬件设备之间的桥梁。它们允许内核与硬件设备进行通信,从而实现对硬件设备的控制和管理。 驱动程序的编写
    的头像 发表于 08-30 15:02 356次阅读