简介:xs9922视频解码器Linux驱动,适配Linux 5.9内核,面向嵌入式视频采集、安防监控与工业视觉方向的驱动开发工程师。该芯片可将HDCCTV高清协议及CVBS标清协议输入的模拟复合视频信号,经过模数转换、视频解码和2D图像处理,最终输出YCbCr数据,并通过MIPI CSI接口传输给主控编码芯片;视频制式覆盖720P/1080P高清与960H/D1标清,适用于车载记录、工业相机等实时视频输入场景。资源包仅有3个文件,体积约17KB,包含驱动主程序C文件、寄存器配置头文件与说明文档txt,整体结构精简,便于快速掌握驱动骨架并迁移到不同内核版本。当前已有67人学习,适合打算在其硬件平台中集成该解码器,或希望学习MIPI CSI视频驱动开发方式的开发者。通过阅读代码可了解芯片寄存器初始化、制式切换、2D图像处理流程以及主控接口设计,对调试同类模拟视频解码方案具有直接参考价值。
1. xs9922 视频解码器 Linux 驱动:为什么这类芯片驱动值得自己动手写
接手 xs9922 视频解码器驱动时,第一反应是找厂商要现成的,结果 SDK 里给的要么是 Android 版本、要么是裸机初始化代码,放到 Linux 下基本跑不起来。这其实是视频解码芯片驱动开发的常态:芯片本身是个带 I2C 接口的寄存器黑匣子,驱动真正要解决的从来不是怎么读寄存器,而是怎么把芯片的输入输出对齐到 V4L2 框架、怎么在分辨率切换时不出错、怎么让系统休眠唤醒后还能正常工作。
这份资源的价值在于它不是零散的寄存器手册摘抄,而是一条完整的驱动开发路径,从 I2C 设备注册到 V4L2 Subdev 接入,再到时序配置和调试手段,全部串在一条线上。适合三类人:刚入门 Linux 驱动开发、想看看字符设备驱动框架之外的真实驱动长什么样的新手;手里正好有 xs9922 或同类解码芯片、需要快速出驱动的嵌入式工程师;以及想理解 V4L2 Subdev 机制在实际项目中怎么落地的开发者。下面按驱动开发的真实顺序展开,每一步都给出可复现的做法。
2. 把芯片挂进 Linux 设备模型:I2C 客户端与 platform 驱动的两次握手
2.1 先搞清楚芯片在系统里的身份:它不是字符设备,是 I2C 从设备
很多第一次写视频解码器驱动的同事会问:xs9922 是不是该注册成一个miscdevice,然后用户态直接 open 它去 read 帧数据?这个理解需要纠正。xs9922 这类解码芯片的工作方式是:外部视频源(比如 CVBS 模拟摄像头)先进入 xs9922 的模拟前端,芯片完成 ADC 转换、解码、缩放后,通过并行接口或 BT.656 接口把数字视频流送给主控芯片的 CSI/并口接收端。主控端负责采集和后续处理,xs9922 本身只是完成格式转换。
所以在 Linux 驱动体系里,xs9922 的身份是 I2C 从设备。它的作用域是控制面,不是数据面。数据面由主控端的 video capture 驱动承担,而 xs9922 驱动的职责只有三件事:通过 I2C 读写寄存器完成芯片初始化、响应 V4L2 的格式查询和设置请求、在电源状态变化时保存和恢复寄存器配置。一个标准的做法是把它实现成一个i2c_driver+v4l2_subdev,而不是一个独立的字符设备。
这样设计带来的好处很直接:主控端的采集驱动可以通过v4l2_subdev的操作集去调用 xs9922 的s_stream回调,从而在开始采集时自动把解码芯片拉起来,不需要用户态去同时操作两个设备节点。这也是 Linux 视频驱动体系里最标准的层级关系。如果直接把 xs9922 注册成字符设备,用户态就得自己协调采集驱动和解码驱动的启动顺序,这在多路视频输入的场景下会迅速变成灾难。
2.2 从 i2c_driver 到 v4l2_subdev 的完整注册步骤
在 Linux 下挂载 xs9922,第一步是构造一个i2c_driver结构体并注册到 I2C 子系统。这里给出最常用的注册骨架,后面所有功能都在这个骨架上扩展。
#include <linux/i2c.h> #include <linux/module.h> #include <linux/videodev2.h> #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> static const struct i2c_device_id xs9922_id[] = { { "xs9922", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, xs9922_id); static const struct of_device_id xs9922_of_match[] = { { .compatible = "mixinno,xs9922" }, { } }; MODULE_DEVICE_TABLE(of, xs9922_of_match); static int xs9922_probe(struct i2c_client *client) { struct v4l2_subdev *sd; struct xs9922_dev *dev; dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; sd = &dev->sd; v4l2_i2c_subdev_init(sd, client, &xs9922_subdev_ops); dev->client = client; /* 注册 subdev 到 v4l2 设备树 */ v4l2_async_register_subdev(sd); dev_info(&client->dev, "xs9922 probed\n"); return 0; } static void xs9922_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); v4l2_async_unregister_subdev(sd); v4l2_device_unregister_subdev(sd); } static struct i2c_driver xs9922_i2c_driver = { .driver = { .name = "xs9922", .of_match_table = xs9922_of_match, }, .probe = xs9922_probe, .remove = xs9922_remove, .id_table = xs9922_id, }; module_i2c_driver(xs9922_i2c_driver); MODULE_LICENSE("GPL");这段代码的逻辑可以拆成四层看。第一层是i2c_driver结构体的定义,它告诉内核这个驱动负责哪类 I2C 从设备,匹配方式有两种:i2c_device_id表用于 legacy 方式注册的设备,of_match_table用于设备树方式。在嵌入式平台上,推荐用设备树方式,因为可以直接在设备树里配置 I2C 总线号和芯片地址。第二层是probe函数,它在设备树里匹配到对应节点后触发,核心动作是调用v4l2_i2c_subdev_init把 I2C 客户端封装成v4l2_subdev。第三层是v4l2_async_register_subdev,这个调用让 xs9922 能被同级的采集驱动异步发现。第四层是 remove 函数做反向注销。
参数上需要注意两点。第一,v4l2_i2c_subdev_init的第三个参数xs9922_subdev_ops是一个v4l2_subdev_ops结构体,里面至少要实现core、pad、video三组回调中的部分函数,否则后续在用户态通过media-ctl配置管道时会发现没有可调用的操作。第二,v4l2_async_register_subdev和v4l2_async_unregister_subdev必须成对出现,否则驱动模块卸载时内核会因为 subdev 仍然挂在异步通知链上而报错。
2.3 设备树节点的写法与地址确认
设备树节点是驱动能不能被 probe 到的第一道关口。xs9922 走 I2C 接口,硬件上通常挂在某个 I2C 控制器下,地址由芯片的 ADDR 引脚电平决定,常见的地址是 0x40、0x42 等偶数地址。设备树里用reg属性标注地址,用compatible属性对齐驱动的of_match_table。
&i2c2 { status = "okay"; clock-frequency = <400000>; xs9922: xs9922@40 { compatible = "mixinno,xs9922"; reg = <0x40>; first-field = <0>; channel-mode = <4>; reset-gpio = <&gpio3 15 GPIO_ACTIVE_LOW>; }; };这个节点的写法有几个细节直接决定驱动能否正常工作。clock-frequency设成400000即 400kHz,这是快速模式的标准频率,xs9922 的数据手册支持 400kHz,所以这里可以大胆用快速模式,传输效率比默认的 100kHz 快四倍。first-field表示奇偶场顺序,0表示偶场在前,这个参数如果不匹配,隔行扫描模式下图像会呈现明显的抖动和横纹。channel-mode表示通道数量,芯片有 4 通道版本和 8 通道版本,这里的数字要和硬件实际接的摄像头路数一致。reset-gpio是芯片硬件复位引脚,驱动里在初始化前应该先拉低再拉高完成一次复位,否则芯片可能停留在上次遗留的配置状态。
还有一个经常被忽略的细节:在probe里一定要读取芯片的 ID 寄存器回读验证,而不是直接初始化。因为 I2C 地址被复用的情况很常见,地址对上但芯片不对的场景并不罕见。验证代码如下。
static int xs9922_check_id(struct xs9922_dev *dev) { u8 id = 0; int ret; ret = regmap_read(dev->regmap, CHIP_ID_REG, &id); if (ret < 0) return ret; if (id != EXPECTED_CHIP_ID) { dev_err(&dev->client->dev, "chip id mismatch: read 0x%02x, expect 0x%02x\n", id, EXPECTED_CHIP_ID); return -ENODEV; } return 0; }这段代码说明一件事:Linux i2c 驱动对设备是否真实存在是盲目的,它只负责在总线地址上发数据。如果地址对应的是空气,regmap_read会返回 -ETIMEDOUT 或者读到总线上的随机电平。所以坚持读 ID,是对驱动生命周期负责的第一步。从那以后我每次写 I2C 设备驱动,都会把 ID 校验放在probe流程的最前面,宁可多读一次寄存器,也不让错误设备被当成 xs9922 初始化。
3. 寄存器配置与时序协商:让 xs9922 输出正确的视频流
3.1 初始化序列的本质:把芯片硬线逻辑的默认状态接到实际场景
xs9922 上电后有一个默认输出状态,但这个状态几乎不可能直接适配到你的主控接收端。它的工作流程大致是:模拟输入经过 ADC 采样,再由解码器恢复出 BT.656 或并行 RGB 信号,最后输出给主控。中间每个环节都有对应的寄存器组控制,包括但不限于输入选择、钳位电平、增益、输出格式、同步信号极性和时钟极性。
驱动的初始化序列本质上就是把这些寄存器的默认值改写成符合当前硬件设计的值。所以初始化序列不是抄一遍厂商 SDK 就完事的,实际要针对你的板子改的参数至少包括:输入通道选择寄存器(决定哪个物理通道接入解码器)、输出格式寄存器(决定是 BT.656 还是并行 YCbCr)、时序寄存器(决定行场同步极性和有效像素窗口)。这就意味着拿到一份参考驱动后,第一步不是编译,而是对照原理图找出 xs9922 的输出引脚接在主控的哪个接收接口上,再回去查该接口对应的时序要求。
通用的初始化流程可以用下面这个表格概括:
| 步骤 | 目标寄存器域 | 典型设置 | 作用 |
|---|---|---|---|
| 1 | 软复位控制 | 写 0x80 后延时 | 把芯片恢复到已知状态 |
| 2 | 输入通道选择 | 按硬件接线选通道 | 决定模拟前端接到哪一路 |
| 3 | 输出格式 | BT.656 或并行 YCbCr | 匹配主控接收接口 |
| 4 | 同步极性 | 根据主控需要配置 | 否则图像偏移或撕裂 |
| 5 | 钳位与增益 | 按信号幅度调整 | 影响亮度与对比度 |
| 6 | 中断/状态使能 | 按需打开 | 用于检测信号丢失 |
每个芯片的具体寄存器位定义都要以数据手册为准,但流程本身是通用的。这里要特别强调第 4 步同步极性,很多驱动的翻车现场都出在这里:主控侧配置的行场同步极性如果和 xs9922 输出侧不一致,表现出来就是图像整体偏移或者顶部有一条彩色噪带,而且这种问题在逻辑分析仪上看寄存器值是看不出来的。
3.2 初始化序列的代码实现:从一维数组到分步延时
实际工程里初始化序列最常见的实现方式是一张寄存器-值对照表,驱动按顺序写入。对于 xs9922 这类寄存器数量多的解码芯片,可以按功能块拆成多张表,比如输入配置表、输出配置表、时序配置表。这里给出一个简化但结构完整的写法。
struct xs9922_reg_value { u8 addr; u8 value; }; static const struct xs9922_reg_value xs9922_bt656_init[] = { /* 软复位:让所有寄存器回到默认状态 */ { 0x00, 0x80 }, { 0x00, 0x00 }, /* 输入通道:选择 AIN0 作为模拟输入 */ { 0x05, 0x00 }, /* 输出格式:BT.656,8bit */ { 0x10, 0x02 }, { 0x11, 0x01 }, /* 同步极性:行同步低有效,场同步高有效 */ { 0x14, 0x00 }, { 0x15, 0x00 }, }; static int xs9922_load_init_seq(struct xs9922_dev *dev, const struct xs9922_reg_value *seq, int len) { /* 要注意,软复位后需要等待芯片内部 PLL 稳定 */ usleep_range(10000, 20000); for (int i = 0; i < len; i++) { int ret = regmap_write(dev->regmap, seq[i].addr, seq[i].value); if (ret < 0) return ret; } return 0; }这段代码的书写顺序就是实际硬件动作的顺序。先写软复位寄存器让芯片重启内部逻辑,这时芯片内部 PLL 还在建立过程,所以紧接着的usleep_range(10000, 20000)是必须的——延时 10 到 20 毫秒,短了芯片可能还没稳定,后面的寄存器写入会丢。之后按输入、输出、时序的顺序逐项配置,每项之间没有严格的延时需求,因为此时 PLL 已经稳定。
参数层面的要点有三个。第一,usleep_range的参数取的是范围而不是精确值,内核调度器可以根据这个范围做一定的合并,比msleep(15)这种固定延时更友好。第二,寄存器地址 0x00 通常会复用为软复位和芯片 ID 寄存器,写0x80触发复位,之后要立刻写回0x00释放复位状态,否则芯片一直处于复位中,后续写入全部无效。第三,输入通道选择寄存器的值要和设备树里的channel-mode属性联动,比如选了通道,那么 8 通道模式下和 4 通道模式下的寄存器位含义是不同的。
3.3 格式协商:处理好 get_fmt 与 set_fmt 的边界条件
V4L2 Subdev 的核心操作之一是格式协商,主控采集驱动会调用 xs9922 驱动的get_fmt和set_fmt来确认视频格式。这里推荐的做法是:set_fmt只验证参数但不要真的去修改芯片寄存器,真正的寄存器改写放到s_stream(1)时一次性完成。
static int xs9922_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *format) { struct xs9922_dev *dev = v4l2_get_subdevdata(sd); if (format->pad != 0) return -EINVAL; format->format.code = MEDIA_BUS_FMT_UYVY8_2X8; format->format.width = dev->width; format->format.height = dev->height; format->format.field = V4L2_FIELD_INTERLACED; return 0; } static int xs9922_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *format) { struct xs9922_dev *dev = v4l2_get_subdevdata(sd); if (format->pad != 0) return -EINVAL; switch (format->format.code) { case MEDIA_BUS_FMT_UYVY8_2X8: case MEDIA_BUS_FMT_YUYV8_2X8: break; default: dev_err(&dev->client->dev, "unsupported mbus code: 0x%04x\n", format->format.code); return -EINVAL; } format->format.width = 720; format->format.height = 576; format->format.field = V4L2_FIELD_INTERLACED; return 0; }get_fmt和set_fmt的区别在代码里看得很清楚。get_fmt是把驱动内部维护的当前格式返回给调用者,这里的dev->width和dev->height是驱动内存里保存的软件状态,不是寄存器的实时值。set_fmt则是被动接收调用者请求的格式,但这里只对 media bus code 做了白名单校验,成功匹配就直接回填标准 PAL 制式的 720x576 分辨率。这背后的考虑是 xs9922 这类模拟解码芯片的输出分辨率实际上是由输入信号制式决定的,驱动不能随意切换分辨率,只能在 NTSC 的 720x480 和 PAL 的 720x576 之间二选一。
所以这里是故意省略了实际寄存器写入动作的。如果你在set_fmt里直接操作寄存器,会发现一个隐蔽的问题:用户态 v4l2-ctl 调set_fmt时,V4L2 框架会先做一轮格式探测,如果驱动的set_fmt真的改了寄存器,这一轮探测就会把芯片状态弄乱。正确做法是先记录格式值但不动寄存器,等主控采集驱动回调s_stream时再统一按记录的参数初始化芯片。这也是大多数视频解码驱动通用的模式,不是 xs9922 特有的设计。
4. 避坑与排查:xs9922 驱动开发中最常见的五个翻车现场
4.1 现象:I2C 读回全部为 0xFF,probe 阶段直接失败
原因分析:地址不对的优先级最高。xs9922 的 I2C 地址由外部引脚决定,可能是 0x40、0x42 或 0x44,不同批次芯片的默认地址可能不同。其次要考虑 I2C 总线是否真的有时钟,如果总线被其他设备拉死,读回来就是 0xFF。第三个原因是硬件复位没有拉高,芯片处于复位态,I2C 接口不响应。
解决方案:写一个最小的 I2C 探测工具,遍历 0x30 到 0x50 之间的偶数地址,对每个地址执行一次读 ID 操作,看哪个地址有正常应答。同时用示波器测量 I2C 时钟和 SDA 线上的波形,确认总线没有异常占住。复位引脚那里加一个上拉电阻,确保默认状态是高电平。
为什么我只要读到 0xFF 就放弃继续配置?因为 I2C 的读操作在设备无应答时总线上表现出的就是高阻态上拉,读回来全 1。这个现象可以明确指向地址错误、总线错误或设备未上电,没有继续往下写的意义。
4.2 现象:probe 成功,但用户态采集到的是整幅绿色或花屏图像
原因分析:图像数据已经进入主控,但格式对不上。最常见的是 xs9922 输出的是 BT.656 格式,而主控采集端按并行 YCbCr 方式解析,字节顺序错位;或者输出的位宽是 8 bit,主控侧配置成了 16 bit。
解决方案:先确认两者之间的连接是 BT.656 还是并行接口。如果是 BT.656,主控侧必须配置为内嵌同步模式,不能配置独立的行场同步信号输入。如果是并行接口,确认每一根线的位序号是否严格对齐,很多翻车就发生在数据线交叉没对齐的情况。这类问题排查时用示波器数 clk 和第一个有效像素的对应关系,会比盯寄存器更有效率。
4.3 现象:图像能出来但有一条上下滚动的亮带
原因分析:这几乎可以锁定为场同步信号问题。V4L2 的set_fmt里如果 field 设置成了V4L2_FIELD_NONE,而 xs9922 实际输出的是隔行扫描信号,接收端就会把两个场交错拼到一起,出现滚动亮带。另外first-field属性配置反了也有同样表现。
解决方案:把 field 强制设为V4L2_FIELD_INTERLACED,并和设备树里的first-field对齐。如果画面构可以看清楚但是运动边缘有锯齿,说明方向反了,把first-field的 0 和 1 对调一次。
4.4 现象:图像颜色偏绿或红色分量异常
原因分析:YUV 到 RGB 的转换过程中,色度采样格式不匹配。xs9922 输出的可能是 YUV422,而采集驱动按 YUV444 解析,UV 分量的位置错位,直接导致颜色混乱。这种问题不是 xs9922 寄存器错误造成的,而是主控采集端对 media bus format 的理解不一致。
解决方案:两端统一使用MEDIA_BUS_FMT_UYVY8_2X8或MEDIA_BUS_FMT_YUYV8_2X8。建议优先选 UYVY,因为 BT.656 标准默认的字节序就是 U 在前 Y 在后,选这个可以少踩一个字节序的坑。
4.5 现象:系统休眠后唤醒,视频流起不来,但驱动没有报错
原因分析:这是视频解码驱动最常见的问题。休眠时芯片断电或时钟停摆,芯片内部寄存器内容丢失。唤醒后驱动没有做初始化恢复,寄存器仍然是丢失后的状态。
解决方案:注册pm_runtime回调函数,在系统 resume 阶段重新加载初始化序列。注意在芯片供电稳定之后再执行 I2C 操作,必要时在 resume 里加等待供电稳定的延时。
static int xs9922_resume(struct device *dev) { struct xs9922_dev *xs9922 = dev_get_drvdata(dev); /* 供电稳定后重新初始化 */ usleep_range(50000, 100000); return xs9922_load_init_seq(xs9922, xs9922_bt656_init, ARRAY_SIZE(xs9922_bt656_init)); } static int xs9922_suspend(struct device *dev) { /* 通常不需要额外动作,但需要保证流停止 */ return 0; } static SIMPLE_DEV_PM_OPS(xs9922_pm_ops, xs9922_suspend, xs9922_resume);这段代码里SIMPLE_DEV_PM_OPS宏会在没有配置电源管理的情况下自动生成空回调,保证驱动在非 PM 平台也能编译通过。resume里的usleep_range(50000, 100000)是给电源域一个稳定时间,常规模拟供电芯片在 50 毫秒内基本能稳住。从那以后我每次做视频类芯片驱动,都会把 suspend/resume 的寄存器恢复在最初的设计里预留好,如果后面发现没做,就得在系统休眠功能上线时紧急补课,那才是真正的手忙脚乱。
5. 把驱动接到用户态:从 device tree 到视频采集验证的完整链路
5.1 media controller 拓扑与 V4L2 设备的组合关系
驱动层面完成 xs9922 的初始化还不够,要让数据真正流到用户态,必须把 xs9922 这个 subdev 挂到一个主控 video device 上。常见的主控接收端有 Rockchip、Ambarella、NXP 等平台,它们的采集驱动会注册一个主video_device,然后在异步通知回调里把 xs9922 subdev 关联到自己下面。关联之后,用户态就可以通过 media controller API 看到完整的管道拓扑。
media-ctl -p查看输出的拓扑结构。正常情况下,管道里应该能看到 xs9922 的 entity 节点,后面连着采集驱动的 entity 节点。如果 xs9922 没有出现在拓扑里,说明v4l2_async_register_subdev没有和采集驱动完成握手,需要检查采集驱动的 async 匹配条件和 xs9922 的of_match_table是否一致。
5.2 用 v4l2-ctl 模拟用户态操作验证驱动正确性
验证驱动的正确性,不需要写复杂的应用代码,v4l2-ctl就够用。下面这个命令组合可以测试格式协商、管道配置和流开关这三个核心流程。
v4l2-ctl -d /dev/video0 --set-fmt-video=width=720,height=576,pixelformat=UYVY media-ctl -d /dev/media0 -V '"xs9922":0[fmt:UYVY8_2X8/720x576]' v4l2-ctl -d /dev/video0 --stream-mmap --stream-out-mmap=/tmp/capture.raw第一条命令检查主控 video device 是否接收了格式设置。第二条命令里media-ctl -V是直接写 subdev 的 pad 格式,这里要注意引号里的 entity 名必须和media-ctl -p打印出来的完全一致,否则 media-controller 会报找不到节点。第三条命令是正式拉流,把数据存成文件后用ffplay或 Python 脚本解析。
如果第三条命令拉流时报VIDIOC_STREAMON超时,最常见的错误是s_stream回调里配置时序后芯片没有输出,检查方向有两个:一是 xs9922 初始化序列里的软复位和 PLL 延时时序对不对,二是主控采集端的时钟极性配置和 xs9922 不匹配。
5.3 一条完整的验证流程:从 dmesg 到原始帧数据
完整的验证流程我一般按这样走:第一步看dmesg确认 probe 成功,确认没有 I2C 报错;第二步media-ctl -p确认拓扑完整;第三步用 v4l2-ctl 做格式链路验证;第四步拉流保存原始数据;第五步把原始 YUV 文件解析成可视图片。
dmesg 里重点关注两类日志。一类是xs9922 chip id match表示 ID 校验通过,另一类是 regmap 读写异常,regmap_read返回负数说明 I2C 通信出问题。这两类日志一条都不能漏。
dmesg | grep -i xs9922 ffplay -f rawvideo -video_size 720x576 -pixel_format uyvy422 /tmp/capture.rawffplay这条命令如果看到的是正常的画面内容而不是绿屏、花屏,基本可以宣判驱动链路是通的。如果画面有横纹,回头检查first-field属性;如果有偏移,检查同步极性。这些排查方向在前面都已经展开过。
5.4 调试底层波形:没有示波器时的替代手段
示波器不是随时都能拿到,特别是在现场调试时。没有示波器的前提下,可以用主控端的寄存器回读来反向验证 xs9922 的输出状态。很多主控的采集接口模块会提供状态寄存器,能够反映是否检测到了行场同步信号、PLL 是否锁定、数据线是否有电平翻转。在主控侧驱动里读这些状态寄存器,可以间接判断 xs9922 是否真的输出了信号。
devmem 0x30640018 32用devmem直接读主控采集接口模块的状态寄存器,如果看到 bit 位指示 PLL 锁定、同步信号有效,说明 xs9922 的输出侧已经工作,问题大概率在主控的格式配置上。如果同步信号无效,那问题在 xs9922 侧,回到初始化序列排查。这种分工排查在双端联调时特别实用,两边不容易互相甩锅。
从那以后我每次验证视频类驱动都习惯先跑一遍 v4l2-ctl 的完整流程再碰别的功能,配合 devmem 读状态寄存器,能够快速把问题定位到芯片侧还是主控侧,这个分工方式帮我避开了大量低效的联合调试。希望这份 xs9922 驱动开发的实战拆解能帮到你,不管是正在啃一块新板子,还是准备把手里的视频驱动从能用做到好用。
本文还有配套的精品资源,点击获取