简介:本资源是面向嵌入式Linux驱动开发工程师与汽车电子系统工程师的DS90UB947串行器I²C驱动实现,专为解决i.MX6平台LVDS直驱成本高、布线难的问题而设计,适用于车载仪表盘等对EMI敏感、空间受限的远程显示场景。压缩包仅4KB,含3个核心文件:C语言驱动源码(ds90ub947.c)实现I²C通信与寄存器配置、头文件(ds90ub947.h)定义硬件抽象接口与寄存器映射、文本说明(readme.txt)简述连接拓扑及配套DS90UB948解串器协同使用方式。资源结构精炼,无冗余依赖,可直接集成至Yocto或Buildroot内核模块编译流程,适合中高级开发者快速适配FPD-Link III串行传输方案。目前已有1491人学习下载,提供从硬件连接逻辑到驱动层初始化、寄存器写入、状态轮询的完整实现路径,是理解车载SerDes驱动开发范式的实用参考。
1. DS90UB947 Linux Driver:不是“即插即用”的摄像头桥接驱动,而是需要手动绑定、时序校准、寄存器级调试的嵌入式视频链路黑匣子
你手头有一块基于 TI DS90UB947 芯片的 FPD-Link III 摄像头模组,接在某国产 ARM64 SoC 开发板上,内核是 5.10 LTS —— 但dmesg里压根看不到 probe 成功日志,/dev/v4l-subdev*一个没出来,v4l2-ctl --list-devices空空如也。这不是驱动没加载,而是驱动加载了却“认不出”你的硬件:DS90UB947 不是标准 USB 摄像头,它不走 UVC 协议,也不靠 ACPI 自动枚举;它是一颗需要精确配置 I²C 地址、MIPI CSI-2 lane 数、像素时钟极性、帧同步信号相位的串行器(Serializer),必须通过 Device Tree 显式声明其拓扑关系,并在驱动中完成寄存器初始化序列、链路训练(Link Training)和状态机轮询。这份 DS90UB947 Linux Driver 资源包,就是某实验室在实车环视系统中反复打磨出的可复现内核模块 + 完整 DTS 示例 + 寄存器配置表 + 链路诊断脚本集合。它不承诺“烧写即跑”,但能让你从i2cdetect -y 2扫到 0x30 开始,一步步把那条“看不见”的 FPD-Link III 视频通道点亮。适合正在啃车载摄像头 Bring-up 的嵌入式驱动工程师、需要对接第三方模组的 BSP 团队,以及被 MIPI 时序玄学折磨到凌晨三点的图像算法同事。
2. 驱动架构与 Device Tree 绑定:为什么不能只 insmod ko?必须先画清“谁连谁”的物理拓扑
DS90UB947 是一颗 Serializer(串行器),常配对 DS90UB948(Deserializer,解串器)使用,构成 FPD-Link III 点对点视频链路。Linux 内核中,它的驱动模型严格遵循 V4L2 Subdevice 架构:DS90UB947 本身不产生 video device(如/dev/video0),而是作为 sensor 的“前置通道控制器”,为上游图像传感器(如 OV10640、AR0233)提供时钟、同步信号和数据通路。因此,驱动加载 ≠ 设备就绪;真正的起点,是 Device Tree 中三者的层级绑定:sensor → ds90ub947 → csi_host(SoC 的 MIPI CSI 控制器)。漏掉任一环,probe 就会静默失败。
2.1 驱动核心逻辑:subdev 注册 + 异步 probe + 链路训练状态机
该驱动包基于 Linux 5.10+ 主线drivers/media/i2c/ds90ub947.c补丁增强版,关键改动点如下:
// drivers/media/i2c/ds90ub947.c 片段(已适配主流 SoC) static int ds90ub947_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ds90ub947_device *ds90 = devm_kzalloc(&client->dev, sizeof(*ds90), GFP_KERNEL); // ... 分配资源、初始化 regmap(I²C 寄存器访问层) // 关键:注册为 v4l2_subdev,但不立即注册 video_device v4l2_i2c_subdev_init(&ds90->sd, client, &ds90ub947_subdev_ops); // 启动异步 probe:等待上游 sensor 和下游 csi_host 均 ready 后再执行 link setup async_schedule(ds90ub947_async_probe, ds90); return 0; }逻辑说明:
async_schedule()是避坑核心。若在probe()中直接调用ds90ub947_link_setup(),此时 sensor 子设备可能尚未注册,v4l2_fwnode_endpoint_parse()会失败。异步机制确保所有依赖节点(sensor、csi_host)的of_node已被内核解析完毕。
参数说明:ds90ub947_subdev_ops定义了.init_state(寄存器复位)、.s_power(电源控制)、.s_stream(流控)等回调,其中.s_stream内部触发完整的 Link Training 流程(见第 4 章)。
2.2 Device Tree 必须声明的四个物理连接节点
以下 DTS 片段(以 Rockchip RK3399 平台为例)是驱动工作的最小必要拓扑,缺一不可:
// arch/arm64/boot/dts/rockchip/rk3399-evb.dtsi &i2c2 { status = "okay"; clock-frequency = <400000>; /* Step 1: DS90UB947 串行器节点 */ ds90ub947@30 { compatible = "ti,ds90ub947"; reg = <0x30>; // I²C 地址,需与硬件跳线一致(常见 0x30 / 0x31) #address-cells = <1>; #size-cells = <0>; ti,link-training = <1>; // 启用链路训练(必须!) ti,phy-lanes = <4>; // FPD-Link III 使用的物理 lane 数(4 或 2) ti,csi-lanes = <2>; // 输出到 SoC CSI 的 MIPI lane 数(通常为 2) /* Step 2: 声明上游 sensor(如 OV10640)通过此串行器接入 */ port@0 { reg = <0>; ds90ub947_in: endpoint { remote-endpoint = <&ov10640_out>; // 指向 sensor 的 output endpoint }; }; /* Step 3: 声明下游 SoC CSI 控制器接收此串行器输出 */ port@1 { reg = <1>; ds90ub947_out: endpoint { remote-endpoint = <&csi_in0>; // 指向 SoC CSI 的 input endpoint }; }; }; }; /* Step 4: 上游 sensor 节点(必须存在且正确引用 ds90ub947_in) */ &ov10640 { status = "okay"; port { ov10640_out: endpoint { remote-endpoint = <&ds90ub947_in>; }; }; }; /* Step 5: 下游 SoC CSI 控制器节点(必须存在且正确引用 ds90ub947_out) */ &csi0 { status = "okay"; ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; csi_in0: endpoint { remote-endpoint = <&ds90ub947_out>; ># 步骤 1:确认内核配置已启用(必须!) zcat /proc/config.gz | grep -E "(V4L2_ASYNC|MEDIA_CONTROLLER)" # 应输出: # CONFIG_V4L2_ASYNC=y # CONFIG_MEDIA_CONTROLLER=y # 步骤 2:加载驱动(注意顺序:先 sensor,再 ds90ub947) modprobe ov10640 # 加载 sensor 驱动(确保其已编译进内核或为 ko) modprobe ds90ub947 # 此时 ds90ub947 的 async_probe 才会触发 # 步骤 3:检查 probe 日志(关键!) dmesg | tail -30 | grep -i "ds90\|link\|train" # 正常应看到: # [ 42.123456] ds90ub947 2-0030: Linked to sensor ov10640 # [ 42.123789] ds90ub947 2-0030: Starting link training... # [ 42.124123] ds90ub947 2-0030: Link training successful (0x00000001)逻辑说明:
modprobe加载顺序至关重要。ds90ub947的异步 probe 会主动查找remote-endpoint指向的ov10640节点。若ov10640.ko未加载,v4l2_async_notifier_register()会超时失败,dmesg中仅显示async notifier not ready。
参数说明:dmesg中Link training successful后的十六进制值(如0x00000001)是 Link Status Register(地址 0x0A)的读回值,bit0=1表示 Training Pass,bit1=1表示 Lock,bit2=1表示 Sync。全 0 表示训练彻底失败。
3. 寄存器级调试与链路训练:用 i2cget/i2cset 手动敲开 DS90UB947 的“保险箱”
当dmesg显示Link training failed,别急着重刷固件——DS90UB947 的链路训练失败,90% 源于寄存器配置与硬件时序不匹配。驱动包附带的ds90ub947_regs.h头文件和reg_dump.sh脚本,就是你的万用表。我们不依赖“自动训练”,而是用最原始的 I²C 工具,逐字节验证关键寄存器状态。
3.1 必查的五个黄金寄存器及其物理意义
DS90UB947 的寄存器空间分为 Configuration(0x00–0x1F)、Status(0x20–0x2F)、Training(0x30–0x3F)三大区。以下寄存器是链路能否建立的“判决点”,必须用i2cget实时读取:
| 寄存器地址 | 名称 | 读取值(正常) | 物理意义 | 调试价值 |
|---|---|---|---|---|
| 0x00 | DEVICE_ID | 0x9470 | 芯片 ID,确认 I²C 通信物理连通 | 排除 I²C 硬件断路 |
| 0x0A | LINK_STATUS | 0x00000001 | 训练状态:bit0=TrainPass, bit1=Lock, bit2=Sync | 最核心诊断寄存器 |
| 0x30 | TRAIN_CTRL | 0x00000001 | 训练控制:bit0=Start Training(写 1 触发,自动清零) | 确认训练指令是否发出 |
| 0x31 | TRAIN_RESULT | 0x00000000 | 训练结果:bit0=Fail, bit1=Timeout, bit2=NoSignal | 若非 0,看哪一位被置 1 |
| 0x10 | CLK_CTRL | 0x00000003 | 时钟控制:bit0=CLK_EN, bit1=CLK_INV(像素时钟极性) | 解决“有图无同步”或“图像撕裂” |
操作命令(以 I²C bus 2,地址 0x30 为例):
# 读 DEVICE_ID(2 字节) i2cget -y 2 0x30 0x00 w # 读 LINK_STATUS(4 字节) i2cget -y 2 0x30 0x0A i # 读 TRAIN_RESULT(4 字节) i2cget -y 2 0x30 0x31 i参数说明:
w表示 word(2 字节),i表示 i2c block(4 字节)。DS90UB947 寄存器多为 32-bit,必须用i读取,否则只读低 16-bit 导致误判。
3.2 手动触发链路训练:绕过驱动,直击硬件
当驱动训练失败,用i2cset强制写TRAIN_CTRL是最快验证链路物理层的方法:
# 步骤 1:复位芯片(写 0x01 到 0x01,软复位) i2cset -y 2 0x30 0x01 0x01 # 步骤 2:等待 100ms(让复位完成) sleep 0.1 # 步骤 3:使能时钟(写 0x03 到 0x10,CLK_EN + CLK_INV) i2cset -y 2 0x30 0x10 0x03 # 步骤 4:启动训练(写 0x01 到 0x30) i2cset -y 2 0x30 0x30 0x01 # 步骤 5:轮询 LINK_STATUS,直到 bit0=1(最多 100 次,每次 10ms) for i in $(seq 1 100); do val=$(i2cget -y 2 0x30 0x0A i | awk '{print $2}') if [ "$val" = "0x00000001" ] || [ "$val" = "0x00000003" ] || [ "$val" = "0x00000007" ]; then echo "Training PASS at try $i, LINK_STATUS=0x$val" break fi sleep 0.01 done逻辑说明:此脚本模拟驱动
ds90ub947_link_training()函数的核心流程。若手动训练成功(LINK_STATUS返回0x00000001),证明硬件链路(线缆、连接器、供电)完好,问题一定在驱动的时序参数或 DTS 配置;若手动也失败,则需用示波器抓CLK,SYNC,DATA信号,检查时序裕量(Timing Margin)。
参数说明:CLK_INV(0x10 寄存器 bit1)是玄学高发区。某些 sensor 输出的像素时钟是反相的,若此处配置错误,LINK_STATUS.bit1(Lock)永远为 0。驱动包中的ds90ub947_dt_match[]表已预置常见 sensor 的clk_inv值,但首次调试务必手动验证。
3.3 驱动包自带的 reg_dump.sh:一键导出全寄存器快照用于比对
驱动包根目录下的tools/reg_dump.sh是血泪经验凝结的诊断利器。它按功能区分类读取寄存器,并高亮异常值:
#!/bin/bash # tools/reg_dump.sh BUS=2; ADDR=0x30 echo "== DS90UB947 Register Dump (Bus $BUS, Addr $ADDR) ==" echo "DEVICE_ID: $(i2cget -y $BUS $ADDR 0x00 w)" echo "LINK_STATUS: $(i2cget -y $BUS $ADDR 0x0A i)" echo "TRAIN_CTRL: $(i2cget -y $BUS $ADDR 0x30 i)" echo "TRAIN_RESULT: $(i2cget -y $BUS $ADDR 0x31 i)" echo "CLK_CTRL: $(i2cget -y $BUS $ADDR 0x10 i)" echo "PHY_CFG: $(i2cget -y $BUS $ADDR 0x12 i)" # PHY 配置:lane 数、均衡等 # 高亮警告:若 TRAIN_RESULT 非零,标红输出 TR_RES=$(i2cget -y $BUS $ADDR 0x31 i | awk '{print $2}') if [ "$TR_RES" != "0x00000000" ]; then echo -e "\033[1;31mWARNING: TRAIN_RESULT = $TR_RES (Check bits: Fail=bit0, Timeout=bit1, NoSignal=bit2)\033[0m" fi使用场景:当你拿到一块新模组,或更换线缆后图像异常,运行
./tools/reg_dump.sh > dump_before.txt,再对比工作正常的dump_after.txt,用diff直接定位差异寄存器。曾有案例因PHY_CFG(0x12)中均衡系数(Equalization Level)从0x00000002错配为0x00000000,导致长线缆下眼图闭合,LINK_STATUS.bit1=0持续失败。
4. 常见问题排查:五条真实踩坑记录,每一条都来自凌晨三点的崩溃现场
DS90UB947 的坑,不在代码逻辑,而在物理世界与数字世界的缝隙里。以下是该驱动包用户反馈最集中的五个问题,按现象、原因、解决三步法还原:
4.1 现象:dmesg显示ds90ub947 2-0030: Failed to get remote endpoint
原因:Device Tree 中ds90ub947@30节点下的port@0或port@1的remote-endpoint属性,指向了一个不存在的节点名。例如,&ov10640_out在 DTS 中实际定义为&ov10640_ep,拼写错误导致of_find_node_by_name()返回 NULL。
解决:用dtc -I fs /sys/firmware/devicetree/base | grep -A5 -B5 "ds90"查看内核实际解析的 DTS,确认remote-endpoint的 label 是否与&xxx定义完全一致。注意:label 名区分大小写,且不能有空格或特殊字符。
4.2 现象:dmesg显示Link training successful,但v4l2-ctl --list-devices无输出,/dev/video*缺失
原因:DS90UB947 作为 subdev 已就绪,但上游 sensor(如 OV10640)的v4l2_async_subdev未正确注册,或其s_stream(1)回调未被调用,导致整个 video pipeline 未激活。
解决:
- 运行
v4l2-ctl --all -d /dev/v4l-subdev0(subdev0 通常是 sensor)确认 sensor 参数可读; - 若报错
Cannot open device /dev/v4l-subdev0, 则modprobe ov10640后检查dmesg | grep ov10640是否有registered as subdev; - 强制触发 stream on:
v4l2-ctl -d /dev/v4l-subdev0 --stream-on,再查/dev/video*。
4.3 现象:图像出现规律性水平条纹,或整帧偏移 1-2 像素
原因:CLK_CTRL(0x10)寄存器的CLK_INV(bit1)配置错误,或SYNC_CTRL(0x11)中VSYNC_POL/HSYNC_POL极性与 sensor 输出不匹配。
解决:
- 先用
i2cget -y 2 0x30 0x10 i读当前CLK_CTRL; - 若值为
0x00000001(仅 CLK_EN),尝试i2cset -y 2 0x30 0x10 0x03(加 CLK_INV); - 若无效,查 sensor datasheet 的 timing diagram,确认其 VSYNC/HSYNC 是 active-high 还是 active-low,对应修改
SYNC_CTRL的 bit0/bit1。
4.4 现象:短距离线缆工作正常,换用 1 米以上同轴线缆后LINK_STATUS.bit1=0(Lock 失败)
原因:FPD-Link III 信号在长线缆上衰减,接收端眼图(Eye Diagram)闭合,DS90UB947 无法锁定数据。驱动默认的 PHY 均衡(Equalization)系数不足。
解决:调整PHY_CFG(0x12)寄存器的均衡等级:
- 默认值
0x00000002(Level 2); - 长线缆尝试
i2cset -y 2 0x30 0x12 0x00000004(Level 4); - 注意:过高的均衡会引入噪声,需配合示波器验证眼图张开度。
4.5 现象:modprobe ds90ub947后系统卡死,dmesg无任何输出
原因:I²C 总线被 DS90UB947 拉死(SCL/SDA 持续低电平),常见于硬件设计缺陷:
- DS90UB947 的 I²C 上拉电阻缺失或阻值过大(>10kΩ);
- 与同一 I²C 总线上其他器件(如 eeprom)的地址冲突(0x30 被占用);
- 芯片供电不稳(AVDD=1.8V,DVDD=3.3V),导致 I²C 接口逻辑异常。
解决: - 用万用表测 I²C 总线 SCL/SDA 对地电压,正常应为 3.3V(上拉后);若为 0V,拔掉 DS90UB947 后重测,确认是否芯片短路;
i2cdetect -y 2查看 0x30 是否被唯一识别;- 示波器抓 AVDD/DVDD 波形,确认无纹波或跌落。
5. 进阶技巧:用寄存器快照比对法定位偶发性链路中断,以及我的“后悔药”工作流
在某跨平台系统实测中,我们遇到一个极其隐蔽的问题:车辆颠簸时,DS90UB947 链路会偶发中断(LINK_STATUS从0x00000007突变为0x00000000),持续数秒后自动恢复,但期间图像冻结。dmesg无报错,常规日志无法捕获瞬态。最终靠寄存器快照比对法定位到根源——并非硬件松动,而是PHY_CFG(0x12)中的Pre-emphasis(预加重)参数在高温下漂移,导致长线缆眼图裕量不足。
5.1 寄存器快照比对法:给每一次“闪退”拍一张数字 X 光片
核心思想:在链路正常时,保存一份全寄存器基准快照(Baseline);当异常发生时,立即抓取当前快照(Snapshot),用 diff 工具逐字节比对,差异项即故障线索。
# 步骤 1:生成 Baseline(链路稳定运行 10 分钟后执行) ./tools/reg_dump_full.sh > baseline_20240520_1430.txt # 步骤 2:部署守护进程,实时监控 LINK_STATUS # 创建 monitor_link.sh #!/bin/bash while true; do STATUS=$(i2cget -y 2 0x30 0x0A i 2>/dev/null | awk '{print $2}') if [ "$STATUS" != "0x00000007" ] && [ "$STATUS" != "0x00000001" ]; then echo "$(date): LINK_STATUS abnormal! $STATUS" >> /var/log/ds90_link.log ./tools/reg_dump_full.sh > snapshot_$(date +%Y%m%d_%H%M%S).txt # 发送告警(可选) logger "DS90UB947 link down: $STATUS" fi sleep 0.5 done # 步骤 3:后台运行监控 nohup ./monitor_link.sh ®_dump_full.sh 功能:遍历 DS90UB947 全部 0x00–0xFF 寄存器(共 256 个),用
i2cget -y 2 0x30 0xXX i读取并格式化输出,生成人类可读的完整快照。
比对技巧:diff baseline_*.txt snapshot_*.txt | grep "^>"只显示 Snapshot 中新增的行(即变化的寄存器)。曾发现0x12: 0x00000004(Baseline)→0x12: 0x00000000(Snapshot),直接锁定 PHY 配置丢失,进而查到是驱动中ds90ub947_s_stream()未在 resume 时重写PHY_CFG。
5.2 我的“后悔药”工作流:每次修改 DTS 或寄存器,必做的三件事
从那以后,我每次调试 DS90UB947,都强制走完这三步,哪怕只是改了一个 bit:
- 改前备份:
cp arch/arm64/boot/dts/rockchip/rk3399-evb.dts .backup/rk3399-evb_dts_$(date +%s).dts - 改后验证:
dtc -I dts -O dtb -o /tmp/test.dtb rk3399-evb.dts && sudo cp /tmp/test.dtb /boot/dtbs/ && sudo reboot,重启后第一件事dmesg | grep ds90确认 probe 成功; - 寄存器固化:若手动
i2cset调通了某个寄存器(如0x12),立即将其写入驱动源码的ds90ub947_init_settings[]数组,而非留在脚本里——因为i2cset是易失的,断电即丢,而驱动 init 是永久的。
这个习惯让我少熬了至少 20 个通宵。它不保证一次成功,但保证每次失败都有迹可循、每次成功都可复现。DS90UB947 不是一个“驱动”,它是一套需要你亲手校准的精密仪器。希望帮到你。
本文还有配套的精品资源,点击获取