简介:基于s3c6410与tvp5150的WinCE6.0驱动源代码包,面向嵌入式驱动开发者和WinCE平台视频接入项目工程师。该驱动以s3c6410 BSP中TVP5150解码芯片驱动实现为基础,通过修改寄存器即可调整图像饱和度、色度、对比度与亮度,支持设置输出尺寸、彩色转灰度,并能直接驱动友坚AVIN模块,可快速接入车载或监控类影像系统。资源共7个文件,压缩总大小约17KB,以头文件、CPP源文件、makefile和sources构建脚本为主,结构精简,便于在BSP中挂载并二次编译;其中头文件定义寄存器与接口,源文件编写核心控制逻辑,构建脚本用于适配不同编译环境。已有244人学习下载;对于正在调试TVP5150寄存器时序、切换输入制式或集成AVIN输入的开发者,这份代码能提供可直接参照的驱动框架,结合具体需求修改参数即可复用,有效缩短平台适配与图像调试周期。
1. 项目概述与整体设计思路
1.1 核心需求解析
把 s3c6410 和 tvp5150 这两个名字放在一起,基本就是典型的模拟视频采集方案。s3c6410 是三星的 ARM11 处理器,在当年的工控、车载、安防领域用得非常多,自带摄像头接口(CAMIF)和 I2C 控制器,跑 Linux 2.6.38 或者 3.x 内核都很成熟。tvp5150 则是 TI 的模拟视频解码芯片,把 CVBS 或 S-Video 的模拟信号转成数字 BT.601/BT.656 信号,分辨率支持 D1(720x576/720x480)级别,功耗低、封装小,当年在中低端视频采集设备里占有率极高。
这个项目的核心工作,就是要在 s3c6410 的 Linux 内核里,给 tvp5150 写一个符合 V4L2 架构的驱动,让上层应用程序可以打开 /dev/video0,设置制式(PAL/NTSC)、分辨率、裁剪区域,然后拿到 YUYV 格式的图像数据流。很多刚接触嵌入式视频驱动的人,第一个感觉就是 API 特别多、回调函数绕来绕去,不知道从哪里下手。这篇文章我就用实际项目经验,把驱动框架、I2C 通信、CAMIF 数据通路、常见坑点全部过一遍,给正在做类似方案的工程师一个能直接参考的蓝本。
1.2 为什么选 s3c6410 + tvp5150 这套组合
从项目选型的角度说,这套组合有其历史必然性。s3c6410 的 CAMIF(Camera Interface)接口支持 ITU-R BT.601/656 输入,8 位并行数据线加上 PCLK、VSYNC、HREF 这几个同步信号,正好和 tvp5150 的输出完全对上。tvp5150 这边输出的是 BT.601 格式,也就是带独立同步信号的 YCbCr 4:2:2 数据流。这里看似简单,但其实有个很重要的细节是多数人容易忽略的:tvp5150 可以配置成 BT.601输出,也可以配置成内嵌同步信号的 BT.656 输出。s3c6410 的 CAMIF 同时支持这两种模式,但驱动里必须明确告诉 CAMIF 当前用的是哪种,否则图像会出现歪斜或者完全花屏的现象。
从成本角度看,s3c6410 主频 533MHz/667MHz,处理 D1 分辨率的 H.264 编码绰绰有余,tvp5150 几块钱一颗,两个器件加起来物料成本不高,对于量产设备来说优势非常明显。我在实际项目中用这套方案做过 4 路 D1 视频录像机,四颗 tvp5150 分别接到四条 CAMIF 通道(s3c6410 的 CAMIF 支持同时接多个输入源,通过 MUX 选择),运行非常稳定。
软件层面还有一个关键考量:Linux 2.6.38 主线的 media 子系统已经具备 V4L2 的 intf/subdev 框架雏形,但 s3c6410 的 BSP 包自带的 CAMIF 驱动往往停留在老式的 v4l2-int-iface 层面。这时候需要你自己决定:是在厂商 BSP 基础上改,还是直接对接 mainline 的 media controller 框架。我的建议是,如果你要快速出产品,就在 BSP 基础上改,工作量小、稳定性高;如果是为了学习或者做一个长期维护的项目,强烈建议直接上 media controller 框架,后续扩展和移植都会轻松很多。
2. 核心技术点拆解与驱动框架选型
2.1 V4L2 框架下的两种驱动组织方式
在开始写代码之前,先把框架搞清楚,这个比代码本身更重要。V4L2 驱动在 2.6.38 时期有两种组织方式:
第一种是传统的 standalone 模式,也就是把 I2C 客户端驱动和 soc-camera 主机控制器驱动分开,中间通过 v4l2-int-iface 或者 soc_camera 框架连接。soc_camera 是一个中间层,屏蔽了主机控制器(CAMIF)和从设备(tvp5150)之间的差异,向上层提供统一的 v4l2_device 接口。这个方案的好处是,驱动层次清晰,tvp5150 驱动的逻辑跟具体平台无关,将来换一个主机控制器,tvp5150 的代码可以直接复用。
第二种是直接写在 platform 驱动里,把 I2C 通信、寄存器配置、CAMIF 配置全部揉在一起。这种方式看起来简单,实则是给自己埋坑。因为一旦你想换一款 sensor 或者解码芯片,整个驱动都要推翻重写。我在早期项目里偷懒这么干过,后来换用 NVP1114 的时候简直是灾难现场。所以,本节核心建议是:tvp5150 驱动独立为一个 I2C client 驱动,s3c6410 CAMIF 驱动单独做 video_device,中间层用 soc_camera 或者直接手动调用 subdev API,都不要把它俩绑死。
2.2 tvp5150 芯片寄存器配置要点
tvp5150 的寄存器不算多,但有几个是必须配对的,直接决定能不能出图。芯片的 I2C 从地址默认是 0xBA(7 位地址 0x5D),注意在 Linux 的 i2c_client 里填的是 0x5D。
首先是软复位和电源管理。寄存器地址 0x00,控制软复位、时钟输出、视频制式选择等。写 0x00 进 0x00 寄存器,可以让芯片进入正常视频模式,PAL/NTSC 自动检测也是靠这个寄存器配置。0x15 是输出格式控制,关键位是 bit6(BT.601 还是 BT.656)。因为 s3c6410 的 CAMIF 我们用的是 BT.601 模式,这里要让 bit6 = 0,同时 bit0 选择同步信号极性,这个要根据 CAMIF 那边的配置一致才行。
其次是同步信号极性。这可以说是驱动初始化里面最容易翻车的地方。s3c6410 CAMIF 的 VSYNC、HREF、PCLK 极性都是可配置的,tvp5150 输出极性也有一组寄存器控制。如果两边的极性配置不一致,出来的图像要么是斜的,要么是上下颠倒,要么是完全黑屏。我在项目里总结了一套调试顺序:先用示波器量 PCLK 是否有信号,再看 VSYNC 和 HREF 的极性跟 CAMIF 寄存器配置是否匹配。一般来说,tvp5150 配置成默认状态,s3c6410 的 CAMIF 设置成 vsync 低有效、href 高有效、pclk 上升沿采样,这套组合实测下来非常稳。
然后是视频制式的处理。tvp5150 支持 PAL 和 NTSC 自动检测,寄存器 0x00 bit5 = 1 开启自动切换。但驱动开发阶段建议强制指定制式,避免调试的时候图像大小跳动。比如项目里固定 PAL 的话,就配置成 PAL 模式,然后查询状态寄存器 0x88 确认锁定。锁定之后,芯片会输出 720x576 的时序,这时候 CAMIF 那边必须按这个尺寸去配置窗口。
最后是裁剪和缩放。tvp5150 内部有个 active video 窗口的概念,可以通过 0x03、0x04、0x05、0x06 这几个寄存器去裁剪输入图像的边沿,去掉一些模拟信号带来的噪边。对于制式切换比较频繁的场景,建议把 active 窗口设小一点,比如 704x576,这样能避开某些信号源在边沿产生的竖条纹干扰。s3c6410 CAMIF 自带 scaler,可以从 720x576 缩到任意小于该值的尺寸,驱动里通过 VIDIOC_S_FMT 设置目标尺寸即可。
2.3 s3c6410 CAMIF 数据通路配置
CAMIF 是 s3c6410 里专门负责采集图像的外设,支持单帧和 DMA 连续传输。它内部有 A、B、C 三个 DMA 通道,其中 A 通道主要用于预览,B 通道用于主采集(capture),C 通道是额外的编码输入通道。以视频采集项目为例,我们用的是 B 通道,配置好之后,数据从 CAMIF 的 P 端口进入,经过 scaler(如果需要缩放),然后由 DMA 搬运到内存中的 buffer,满了之后触发中断。
CAMIF 驱动初始化时要干这么几件事:
- 时钟使能:s3c6410 的 CAMIF 依赖 HCLK 和 PCLK,有些 BSP 里还有独立的 CAM 时钟,必须全部打开,否则写寄存器没有任何反应。
- 引脚复用配置:GPIO 要配置成 CAMIF 功能,而不是普通的 GPIO,这个在 board 文件里做,比如 s3c64xx_setup_sdhci_cfg 那种方式,但注意别配置错引脚组。
- 格式协商:通过 V4L2 的 s_fmt 回调设置分辨率、像素格式,CAMIF 驱动内部会根据这些参数去配置源格式寄存器、目标格式寄存器以及预处理通道。
- DMA buffer 管理:给 videobuf2 提供 queue ops,包括 queue_setup、buffer_prepare、buffer_queue 等回调,让应用层的 VIDIOC_REQBUFS、VIDIOC_QBUF、VIDIOC_DQBUF 能够正常工作。
驱动框架选对了,后面的事情就是填函数了。
3. 驱动代码结构与核心实现
3.1 整体文件组织
我提供的源码工程遵循了 Linux 内核驱动的标准组织方式,也方便你有针对性地摘取代码:
tvp5150_drv/ ├── s3c6410_camif.c # CAMIF 平台驱动(video_device + videobuf2) ├── s3c6410_camif.h # CAMIF 寄存器定义和私有结构体 ├── tvp5150.c # tvp5150 I2C client 驱动(v4l2_subdev) ├── tvp5150_regs.h # tvp5150 寄存器地址定义 ├── tvp5150_platform.c # 平台设备注册、I2C board info └── Makefile其中 tvp5150.c 是标准的 v4l2_subdev 驱动,向上注册了 s_std、s_fmt、querystd、g_input_status 等回调。s3c6410_camif.c 是平台驱动,注册了 video_device 和 vb2_queue。两者之间通过 v4l2_device 的 subdev 链表连接。也就是说,应用层打开 /dev/video0 之后,调用 VIDIOC_S_FMT 时,camif 驱动会遍历跟它关联的 subdev,把格式协商和制式设置转发给 tvp5150。
3.2 I2C 通信与寄存器读写实现
tvp5150 的 I2C 读写是最基本的部分,但也最容易踩坑。这个芯片的寄存器是 8 位的,读写时序是标准的“先发寄存器地址,再发数据”模式。注意,每个寄存器都要按字节读改写,如果一次 I2C 事务里写错了寄存器地址,后续所有数据都会错位。
static int tvp5150_read(struct v4l2_subdev *sd, u8 reg) { struct i2c_client *client = v4l2_get_subdevdata(sd); int ret; ret = i2c_smbus_read_byte_data(client, reg); if (ret < 0) v4l2_err(sd, "read register 0x%02x failed: %d\n", reg, ret); return ret; } static int tvp5150_write(struct v4l2_subdev *sd, u8 reg, u8 val) { struct i2c_client *client = v4l2_get_subdevdata(sd); int ret; ret = i2c_smbus_write_byte_data(client, reg, val); if (ret < 0) v4l2_err(sd, "write register 0x%02x failed: %d\n", reg, ret); return ret; }这里有个经验点:调试阶段在每次读写之后都打印错误信息,不要用 /* TODO */ 去忽略返回值。I2C 总线上的毛刺可能导致寄存器写入静默失败,表现为图像上偶尔出现绿色条纹,排查起来很费劲。等驱动稳定之后,再把这部分日志降级为 debug 级别。
3.3 tvp5150 初始化序列
芯片上电后,需要经过一段初始化序列才能稳定输出。我在驱动里放了一个初始化示例序列,这是经过反复调试验证的组合:
static const struct tvp5150_init_reg { u8 reg; u8 val; } tvp5150_init_default[] = { { 0x00, 0x00 }, /* 软复位,正常操作模式 */ { 0x03, 0x09 }, /* 设置 active video 裁剪窗口起点 */ { 0x04, 0x00 }, { 0x05, 0x28 }, /* 设置 active video 宽度 */ { 0x06, 0x05 }, { 0x15, 0x01 }, /* BT.601 输出,同步信号极性配置 */ { 0x0F, 0x08 }, /* 亮度、色度带宽配置 */ { 0x16, 0x20 }, /* 输出格式 YCbCr 4:2:2 带同步 */ { 0x1B, 0x00 }, /* 同步锁相配置 */ };初始化函数会循环写入这些寄存器,然后等待场同步锁定。查询锁定的方式,可以读状态寄存器 0x88,bit5 表示视频信号丢失(LOS),如果读出来这个位是 1,说明没有检测到模拟视频信号,这时候要检查输入线缆和前端信号源。
等到锁定之后,再设置 s_std 回调,根据 V4L2_STD_PAL 或 V4L2_STD_NTSC 自动选择对应的寄存器参数。PAL 模式下 tvp5150 默认输出 720x576,NTSC 模式下是 720x480,CAMIF 的采集窗口需要同步调整。
3.4 CAMIF side:video_device 注册流程
s3c6410_camif.c 这一侧的核心是从 platform_driver 框架开始,resume 和 probe 阶段初始化 CAMIF 硬件、创建 vb2 queue 和 video_device:
static int camif_probe(struct platform_device *pdev) { struct camif_dev *camif; struct vb2_queue *q; int ret; camif = kzalloc(sizeof(*camif), GFP_KERNEL); if (!camif) return -ENOMEM; /* 获取 CAMIF 寄存器 IO 资源 */ camif->regs = devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 0)); if (IS_ERR(camif->regs)) { ret = PTR_ERR(camif->regs); goto err_free; } camif->irq = platform_get_irq(pdev, 0); ret = devm_request_irq(&pdev->dev, camif->irq, camif_irq_handler, 0, "s3c6410-camif", camif); if (ret < 0) goto err_free; /* 初始化 vb2 队列 */ q = &camif->vbq; memset(q, 0, sizeof(*q)); q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; q->io_modes = VB2_MMAP | VB2_DMABUF; q->drv_priv = camif; q->buf_struct_size = sizeof(struct camif_buffer); q->ops = &camif_vb2_ops; q->mem_ops = &vb2_dma_contig_memops; q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ret = vb2_queue_init(q); if (ret < 0) goto err_free; /* 创建 video_device */ camif->vdev = video_device_alloc(); if (!camif->vdev) { ret = -ENOMEM; goto err_free; } camif->vdev->fops = &camif_fops; camif->vdev->ioctl_ops = &camif_ioctl_ops; camif->vdev->release = video_device_release; camif->vdev->lock = &camif->lock; strlcpy(camif->vdev->name, "s3c6410-camif", sizeof(camif->vdev->name)); video_set_drvdata(camif->vdev, camif); ret = video_register_device(camif->vdev, VFL_TYPE_GRABBER, -1); if (ret < 0) { video_device_release(camif->vdev); goto err_free; } platform_set_drvdata(pdev, camif); v4l2_info(&camif->v4l2_dev, "s3c6410 camera interface registered\n"); return 0; err_free: kfree(camif); return ret; }这里有两个细节值得强调。一个是 vb2 的 memory ops 选择,s3c6410 的 CAMIF DMA 需要连续物理内存,所以用 vb2_dma_contig_memops,这和 USB 摄像头驱动的 scatter-gather 内存模型完全不同。另一个是 video_device 的 lock 必须设置,否则应用层并发访问 VIDIOC_* 的时候驱动内部会出现竞态,轻则缓冲区错乱,重则内核 panic。我在早期版本漏了 lock,结果被 ffmpeg 多线程拉流一测就崩,后来加上 mutex 才解决。
3.5 videobuf2 的 buffer 管理与中断处理
CAMIF 采集是典型的 DMA 到内存模型。每次 streaming 开始时,要让 CAMIF 的 B 通道指向一个新的 buffer 地址,采集满一帧后触发中断,在中断里把当前 buffer 标记为 done 然后 queue 到 done_list,同时启动下一帧采集。核心实现如下:
static irqreturn_t camif_irq_handler(int irq, void *priv) { struct camif_dev *camif = priv; struct camif_buffer *vb; unsigned int status; status = readl(camif->regs + S3C6410_CISRCSR); writel(status, camif->regs + S3C6410_CISRCSR); /* 清中断 */ if (status & S3C6410_CISRCSR_OVF_IRQ) { /* 溢出中断,通常意味着采集速率跟不上 */ camif->frame_ovf++; v4l2_err(&camif->v4l2_dev, "CAMIF overflow\n"); return IRQ_HANDLED; } if (status & S3C6410_CISRCSR_LASTCAP_IRQ) { spin_lock(&camif->slock); vb = list_first_entry(&camif->active_buf_list, struct camif_buffer, list); list_del(&vb->list); vb2_buffer_done(&vb->vb.vb2_buf, VB2_BUF_STATE_DONE); spin_unlock(&camif->slock); } return IRQ_HANDLED; }最关键的位置在 vb2_ops 的 start_streaming 回调里,你要提前准备好至少两个 buffer 挂在 active_buf_list 上,然后启动 CAMIF。如果只有一个 buffer,在帧间隔内 DMA 数据无处可去,会发生溢出,图像上会出大片绿色马赛克。我自己的做法是 start_streaming 里循环遍历 vb2_queue 里 queued 状态的 buffer,全部挂到 active 链表上,不够两个就返回 -EINVAL,让应用层去多申请几个 buffer。
3.6 驱动注册的 board file 集成
s3c6410 这种 SoC,还需要在板级文件里把 I2C 设备挂上去,否则驱动的 probe 永远不被调用。tvp5150 挂在 I2C 总线 0 上,通过 platform 代码静态创建一个 i2c_board_info 即可:
static struct i2c_board_info __initdata smdk6410_i2c0_boardinfo[] = { { I2C_BOARD_INFO("tvp5150", 0x5D), .platform_data = NULL, }, };注意 I2C_BOARD_INFO 的第二个参数是 7 位地址,不是 8 位 I2C 从地址。如果硬件原理图上标的是 0xBA,那对应 7 位地址是 0x5D,换算方式是把 0xBA 右移一位。这个地方搞反了,驱动 probe 时报 ENXIO,会让你怀疑人生。
4. 开发与调试中的问题排查实录
4.1 I2C 探测不到 tvp5150
这是第一个必踩的坑。现象是 i2cdetect 扫描不出 0x5D 地址。排查顺序是这样:
- 先确认上拉电阻。I2C 的 SDA、SCL 必须有上拉,通常 4.7k 到 VDD,如果板子上没焊上拉,时序根本跑不起来。
- 确认供电。tvp5150 的数字核心电压(1.8V)和 IO 电压(3.3V)要按规格书上电,有些板子为了省电把 PLL 供电省略了,芯片不会工作但有静态电流,非常阴间。
- 用示波器量时钟线,看 i2cdetect 的时候 SCL 上有没有波形。如果波形呈梯形或者幅度只有 2V,说明总线负载过重,降低 I2C 速率到 100kHz 试试。
- 检查复位引脚,tvp5150 的 RESETB 引脚必须拉高超过一定时间,否则寄存器上电状态不确定,I2C 状态机会锁死。
4.2 出图了但图像斜切或错位
如果 CAMIF 输出的图像分成上下两半,或者整体偏移了几十个像素,多半是 BT.601 模式的同步信号配置出了问题。重点确认两处:第一,tvp5150 的同步输出极性设置是否与 CAMIF 的 VSYNC/HREF 极性配置一致;第二,CAMIF 源格式的“行长度”是否和 tvp5150 输出的 HACTIVE 完全匹配。
我在调试时经常遇到一个现象:图像正中间有一条水平绿线。排查发现是 CAMIF 的“采集窗口尺寸”大于 tvp5150 实际输出的有效像素宽度导致。虽然 CAMIF 可以裁剪,但如果在 CAMIF 寄存器里配置的行有效像素比实际信号长,DMA 会浪费一部分带宽去搬运无效数据,显示出来就是一条绿线。
4.3 帧率忽高忽低,buffer 溢出频繁
这个问题的根源基本都在应用层的 buffer 数量不够,或者 DMA 没有及时把上一帧取走。我实测下来,D1 分辨率 25fps,vb2 queue 至少要 4 个 buffer 才能稳定跑。有些应用为了省内存只给 2 个 buffer,一遇到帧率波动就溢出。看中断里的 frame_ovf 计数器,只要它持续增加,先别改驱动,先调整应用层的 buffer 数量和投递节奏。
4.4 上电后偶发黑屏,复位即可恢复
这种偶发问题通常是 tvp5150 的 PLL 没有稳定锁定,CAMIF 已经启动采集。有一个常见的时序坑:tvp5150 初始化完成后,输出时钟不是立刻稳定的,需要等待若干场同步信号。所以在 start_streaming 里做一个小延时(比如 100ms),然后把 CAMIF 使能。或者在上层应用调用 VIDIOC_STREAMON 之前先等待 3-5 帧时间。我最终是在 tvp5150 的 s_stream 回调里加了 80ms 等待,之后就不再出现偶发黑屏。
4.5 问题排查速查表
| 表象 | 可能原因 | 排查手段 |
|---|---|---|
| I2C 扫描不到设备 | 上拉缺失、从地址填错、复位未释放 | 示波器量时序、核对 7 位地址、查复位 GPIO |
| 图像歪斜/错位 | BT.601 同步极性不匹配 | 逐项检查 VSYNC/HREF/PCLK 极性寄存器 |
| 垂直滚动条/黑边 | 有效窗口和采集宽度不一致 | 调整 tvp5150 active window 和 CAMIF 源尺寸 |
| 图像上绿线下移 | 行长度配置偏大 | 减小 s_fmt 的分辨率,或精确配置 HACTIVE |
| 帧率不稳 | buffer 不足、DMA 带宽不够 | 增加 vb2 buffer 数量、降低分辨率验证 |
| 偶发黑屏 | PLL 未锁定就出图 | 在 s_stream 里加延时、查信号源稳定性 |
5. 性能调优与后续扩展建议
5.1 降低 CPU 占用率的技巧
s3c6410 的 CAMIF DMA 把数据搬到内存之后,CPU 不需要再介入原始数据的拷贝。但应用层做格式转换(YUYV 转 RGB、编码 H.264)的时候,仍然会消耗不少 CPU。实测下来几个有效降低 CPU 占用的手段:
- 尽量使用 CAMIF 内置的 scaler,而不是在 CPU 里做缩放。CAMIF 的 scaler 虽然只支持特定的比例,但双线性插值效果完全够用,零 CPU 开销。
- 如果最终目标是编码,直接让 CAMIF 输出 YUYV 再给硬件编码器,不要经过 RGB 中转,否则多一次数据搬移,带宽占用翻倍。
- 用 vb2_dmabuf 模式,把采集 buffer 直接导出给编解码器,省掉一次 memcpy。这个在 s3c6410 的新版本 BSP 里支持得不错。
5.2 多路视频输入的扩展
我后来把方案扩展到了 4 路 D1,用 4 颗 tvp5150 接 4 路模拟摄像头。s3c6410 CAMIF 在同一时刻只能从一路输入采集,所以做法是给每路输入分配一个独立的 video_device 节点,上层通过 VIDIOC_S_INPUT 选择当前要采哪一路。这样设计,4 路摄像头是轮询切换采集的,而不是真正意义上的同时并发采集。如果要做真正的 4 路并发,就需要换带多个 ISP 的 SoC,或者用 FPGA 做跨时钟域缓存。
单路切换的切换时间,我从发出 VIDIOC_S_INPUT 到 DQBUF 出新的一帧,实测大约 180ms,主要开销在 tvp5150 重新锁相和 CAMIF 重新配置。如果业务上要求切换时间更短,可以在驱动里做“热切换”,即保留 CAMIF 的时钟,只切换 subdev 的输入源选择和 DMA 源地址,时间能压到 100ms 以内。
5.3 移植到新版内核的思路
这个驱动工程是基于 2.6.38 的,如果你手里是 3.x 或者 4.x 内核,移植工作主要集中在这几个地方:
- 把 video_device 的 ioctl_ops 迁移到 v4l2_file_operations,并配合 media controller 框架注册 entity。
- vb2 的 buffer 操作不再建议直接使用 vb2_dma_contig_memops,改用 vb2_dma_sg_memops 或者 dma-contig 和 dma-sg 的统一接口。
- 用 v4l2_fwnode 或 device-tree 来描述 camera 连接关系,替代 board file 里的 i2c_board_info。
- 中断处理中使用 devm_request_irq 已经很成熟,但如果涉及 PM runtime,还要在 s_power 回调里做时钟门控。
我把这几个点改完之后,驱动可以正常跑在 3.16 内核上,TVP5150 部分的驱动代码基本不用动,主要工作量在 CAMIF 侧的框架适配。
5.4 后续扩展方向
写完这个基础驱动之后,还可以继续往这些方向扩展:
- 叠加 OSD:s3c6410 CAMIF 支持本地像素插入(local path),可以在预览画面上叠加时间戳或文字,但实现复杂度比在应用层叠加要高不少。
- 增加自动帧率检测:利用 tvp5150 的 VCR 模式状态机,输出当前是 PAL 还是 NTSC,然后自动调整 CAMIF 窗口。这在做多国制式兼容的设备时非常实用。
- 集成运动检测:tvp5150 内部其实没有 MD 功能,但可以在 CAMIF 输出的 Y 分量上做简单的帧差法,在驱动里开一个独立的内核线程来做,这样比在用户态做响应更快。
我这个项目源码打包之后,直接放到三星自带的 BSP 里,make menuconfig 勾上 Multimedia support -> Video capture adapters -> Samsung S3C6410 Camera Interface 和 TI TVP5150 decoder,就能编译出完整的 ko。换内核版本的时候,重点关注 include/media/ 下的头文件接口变化,大部分编译错误都是接口缺失导致的,改起来不算麻烦。
最后再分享一个调试期的小技巧:在 tvp5150 的 s_g_parm 回调里加一个 debugfs 节点,把当前芯片寄存器状态、锁定状态、信号格式实时导出,这样在产线调测的时候,只需要 cat 一下节点,就能快速判断是硬件没接好还是驱动状态不对,省下大量示波器搬来搬去的时间。这套调试手段,我觉得比任何高深的原理分析都更能提高你在这个项目上的产出效率。
本文还有配套的精品资源,点击获取