v4l2 camera 驱动架构 之 isp controller 驱动
link 这个链接指向本文的源代码
link 这个链接指向 v4l2 标准文档,文档中包含sample code。这份文档的标题是"Video for Linux Two API Specification",如果链接失效可以用这个标题重新google。
isp camera 驱动分为 sensor 驱动和 isp controller 驱动两部分,其中sensor 驱动就是摄像头芯片的驱动。isp controller 驱动实现的是 v4l2 驱动架构中 soc_camera_host 接口,主要负责 isp dma 的管理和一些 v4l2 的标准操作。
1. soc_camera_host结构: isp controller 驱动是一个普通的 platform 驱动。在 probe 函数中最重要的操作是通过调用soc_camera_host_register 注册一个 struct soc_camera_host。soc_camera_host 有5个字段需要填充: 1) .drv_name :名称。 2) .ops :操作函数族。 3) .priv :指向驱动私有数据,将被作为参数传给上面 ops 接口中的操作函数。 4) .v4l2_dev.dev :指向这个 platform 驱动的 dev。 5) .nr :对于这个参数,有这么一句注释:"the soc_host.nr should be equal to bus_id in soc_camera_link, otherwise the driver will fail to load".
2. soc_camera_host_ops结构: 这个结构是这个驱动的核心,实现了一些v4l2的标准操作。请先熟悉一下v4l2 sample code再看下面的内容,否则很难理解。
static struct soc_camera_host_ops map100_soc_camera_host_ops = { .owner = THIS_MODULE,
// add 会在 open 时调用。在我公司的代码里主要作用是 powerup isp controller 和给 sensor 分频。另外,probe //的时候也会调这个函数,因为 probe 的主要操作是初始化 sensor 驱动, //而这需要先分频给 sensor。 .add = map100_camera_add_device,
//remove 会在 close 时调用。主要作用是 power down isp controller。 //另外,probe 初始化 sensor 结束之后也会调用。 .remove = map100_camera_remove_device,
.set_bus_param = map100_camera_set_bus_param,
//剪裁相关的一个接口 .set_crop = map100_camera_set_crop,
//设置格式 .set_fmt = map100_camera_set_fmt,
//VIDIOC_TRY_FMT的实现 .try_fmt = map100_camera_try_fmt,
//初始化isp dma,会在open的时候调用 .init_videobuf = map100_camera_init_videobuf,
//这个接口本身不会分配dma内存,只是对dma buffer做初始化 //VIDIOC_REQBUFS的实现 .reqbufs = map100_camera_reqbufs,
//select系统调用的实现 .poll = map100_camera_poll,
//VIDIOC_QUERYCAP的实现
//在这个函数里面填写设备支持的特性。 .querycap = map100_camera_querycap, };
这个结构体中的接口具体实现参见代码。
3. videobuf_queue_ops 结构: 这个结构用来管理一个dma buffer的队列。
static struct videobuf_queue_ops map100_videobuf_ops = { //设置 dma buffer 的 size 和最大个数,但是不分配 dma buffer //dma buffer 是在用户调用 VIDIOC_REQBUFS 操作码时分配的 .buf_setup = map100_videobuf_setup,
//检查 map100_buffer 包含的物理内存和映射到用户空间的内 //存是否有效 .buf_prepare = map100_videobuf_prepare,
//just push the buffer into queue .buf_queue = map100_videobuf_queue,
.buf_release = map100_videobuf_release, };
各个接口的实现参见代码中的实现。
注意: 1) 所有的 videobuf_queue_ops 操作都是有锁保护的,这个锁是 videobuf_queue_dma_contig_init 初始化时传入的。因为 videobuf_queue_ops 函数族与应用层v4l2 buffer的入队出队操作相对应,所以这个锁实际上是在同步用户层与中断处理函数对v4l2 buffer(实际上是 dma buffer)队列的访问。其实,这个dma buffer的队列很像一个"ring buffer",中断处理函数负责填充这个"ring buffer",应用层则从这个"ring buffer"取数据。这里使用"ring buffer"是为了更好的性能。 2) " dma buffer "队列的实现: a. 在 buf_queue函数中把应用层的dma buffer入队; b. 在中断处理函数中把填充完成的dma buffer出队; c. 中断处理函数的实现: 首先,如果一个请求已经填充完毕,则将这个请求从从" dma 请求队列"出队,然后唤醒等待在该请求上的进程; 其次,处理" dma buffer队列"中的下一个dma buffer。也就是修改 isp 的 dma 地址使其指向这个dma buffer的物理地址,然后重新打开isp中断(就是清中断寄存器,启动下一次填充)。
关于修改dma地址,有一点要注意,收到中断后,清中断寄存器之前可以修改dma地址,但是清中断之后不能修改dma地址,否则会阻塞。 (不知道为什么会这样)
3) 对于自定义的 dma buffer 结构体,videobuf_buffer 必须是第一个结构体成员。
4. dma 的初始化和分配: 1)open 的时候通过 init_videobuf 初始化 dma; 2)用户执行 VIDIOC_REQBUFS 操作码时会真正分配 dma 内存,之后会调用 reqbufs。