简介:本资源是面向嵌入式Linux驱动开发工程师与汽车电子系统工程师的DS90UB947串行器I2C驱动实现,专为i.MX6平台适配设计,解决LVDS布线成本高、抗干扰弱等车载仪表显示场景下的信号远距离可靠传输问题。方案采用DS90UB947(本地端)与配套DS90UB948(远端)组成FPD-Link III链路,将SoC原生LVDS输出转换为高速差分串行信号,显著降低线束成本并提升EMC性能。压缩包仅4KB,含3个核心文件:C语言驱动主体(ds90ub947.c)、头文件定义(ds90ub947.h)及简明使用说明(readme.txt),结构精炼,便于快速集成至Yocto或Buildroot内核模块编译流程。目前已有1491人学习下载,读者可直接获取可编译的I2C设备驱动框架、寄存器配置逻辑、电源管理接口及与deserializer协同工作的初始化时序注释,适用于车载HMI显示系统原型开发与驱动移植验证。
1. DS90UB947 Linux Driver 是什么:不是“即插即用”的黑匣子,而是需要手动绑定、时序对齐、寄存器级调试的 SerDes 驱动栈
你手头有一块基于 TI DS90UB947 的 FPD-Link III 摄像头模组,接在某嵌入式主板(比如 AM62A/AM68 平台)上,内核启动后dmesg | grep -i ub947完全静默,/sys/bus/i2c/devices/下找不到设备节点,v4l2-ctl --list-devices也空空如也——这不是硬件坏了,大概率是驱动没活过来。DS90UB947 Linux Driver 不是一段编译进内核就自动工作的代码,它是一套需深度耦合平台设备树、I²C 初始化顺序、GPIO 复位时序、以及 CSI-2 接收端(如 IMX8MP 的 MIPI CSI)协同配置的驱动栈。它解决的核心问题是:把高速串行链路(FPD-Link III)上的原始视频流,可靠地解串、同步、并转换为标准 V4L2 buffer 流,供上层应用消费。适合正在做车载环视、ADAS 前视模组移植、或工业多摄像头同步采集的嵌入式工程师——尤其当你已确认硬件连接无误,却卡在“内核认不出芯片”这一步时,这份驱动就是你必须亲手拆解、打补丁、调参数的临门一脚。
2. 驱动架构与内核集成:从源码结构到 Kconfig 编译选项的硬核对齐
DS90UB947 的 Linux 驱动并非主线内核原生支持,而是由 TI 提供的 out-of-tree 补丁包(常见于 SDK 发布包中),或社区维护的 fork 分支(如 linux-ti-staging)。它的核心逻辑分三层:I²C 控制面(读写寄存器)、中断响应面(LOCK/UNLOCK 状态通知)、数据面(通过 platform device 绑定到 CSI host controller)。理解这个分层,才能避免“编译过了但设备不注册”的玄学翻车。
2.1 源码组织与关键文件定位
典型驱动包结构如下(以 TI SDK v8.7 为例):
drivers/media/i2c/ds90ub947/ ├── Kconfig # 内核配置项定义 ├── Makefile # 编译规则 ├── ds90ub947.c # 主驱动文件:probe/init/remove ├── ds90ub947-reg.h # 寄存器宏定义(含 BANK 切换逻辑) ├── ds90ub947-core.c # 核心状态机与 I²C 封装 └── ds90ub947-video.c# V4L2 设备注册与 stream 控制提示:
ds90ub947-core.c中的ds90ub947_read_reg()和ds90ub947_write_reg()是所有寄存器操作的统一入口,内部自动处理 BANK 切换(DS90UB947 有 BANK0/BANK1,地址空间重叠,必须先写 BANK_SEL 寄存器再访问目标寄存器)。这是新手最容易漏掉的底层细节——直接裸写 I²C 读写会失败。
2.2 Kconfig 配置与模块编译控制
驱动启用依赖两个条件:
CONFIG_VIDEO_DS90UB947=m或=y(Kconfig 中定义);CONFIG_VIDEO_V4L2_SUBDEV_API=y(子设备 API 必须开启,否则v4l2_subdev结构体未定义)。
在内核源码根目录执行:
make menuconfig # 进入 Device Drivers → Multimedia support → Video capture adapters → # <*> DS90UB947 FPD-Link III serializer driver若你的内核版本较新(≥6.1),可能需手动将drivers/media/i2c/ds90ub947/目录拷贝至内核源码对应路径,并修改上级drivers/media/i2c/Makefile,追加:
obj-$(CONFIG_VIDEO_DS90UB947) += ds90ub947/同时在drivers/media/i2c/Kconfig中插入该驱动的 menuconfig 条目(参考同类驱动如ds90ub953的写法)。
2.3 驱动加载依赖链:为什么 probe 总是失败?
ds90ub947_probe()执行前,内核必须完成三件事:
- I²C bus 已注册且 clock enable(
i2c_bus_clk必须在ds90ub947加载前 ready); - RESET GPIO 已正确 request 并拉低/拉高(驱动中
gpiod_get_optional()获取,若未配置则 fallback 到软件 reset); clock-frequency属性在设备树中声明(DS90UB947 要求 I²C 速率为 100kHz,超频会导致寄存器读写校验失败)。
常见错误是:I²C bus 在驱动 probe 时尚未完成 clock 初始化,导致i2c_transfer()返回-EPROBE_DEFER,内核不断重试直至超时。此时dmesg会打印deferred probe pending,而非报错。解决方案是在设备树中确保 I²C controller node 的clocks属性完整,并检查clk_ignore_unused是否被误设。
3. 设备树绑定详解:从 pinmux 到 video-interface 的七处关键配置
DS90UB947 不是独立视频设备,它是 CSI 链路中的一个“中间件”。设备树(DTS)必须同时描述:I²C 连接关系、GPIO 复位控制、电源域、时钟供给、以及最重要的——与 CSI host 的 video-interface 绑定。少一处,驱动就无法完成初始化。
3.1 I²C 子节点:地址、时序与中断引脚
&i2c2 { status = "okay"; clock-frequency = <100000>; // 强制 100kHz,不可用 400k ds90ub947@54 { compatible = "ti,ds90ub947"; reg = <0x54>; // I²C 地址,由 ADDR0/ADDR1 引脚决定 interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; // LOCK 中断 interrupt-names = "lock"; /* RESET GPIO:必须显式声明,否则驱动 fallback 到软件 reset */ reset-gpios = <&gpio5 12 GPIO_ACTIVE_LOW>; // GPIO5_12,低电平有效 /* 电源:AVDD/DVDD/IOVDD 三路供电 */ avdd-supply = <&ldo1>; dvdd-supply = <&ldo2>; iovdd-supply = <&ldo3>; /* 视频格式:必须与 sensor 输出严格一致 */ ti,video-format = "yuv422-8"; ti,data-lanes = <2>; // FPD-Link III 使用 2 data lanes ti,link-rate-mbps = <1200>; // 实际链路速率,影响 deserializer timing }; };注意:
ti,link-rate-mbps不是理论最大值,而是 sensor 实际输出的像素时钟经编码后的等效速率。例如 OV10640 在 1280x720@30fps 下,MIPI CSI-2 时钟为 240MHz,经 DS90UB947 编码后链路速率为 1200Mbps。此值直接影响ds90ub947_set_link_rate()中的 PLL 配置,填错会导致 LOCK 信号不稳定。
3.2 CSI Host 绑定:video-interface 与 port endpoint 的生死关联
DS90UB947 的输出必须通过video-interface绑定到 CSI host(如mipi_csi2_rx):
&csi2_rx { status = "okay"; ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; ds90ub947_ep: endpoint { remote-endpoint = <&ds90ub947_out>; >v4l2-ctl -d /dev/v4l-subdev0 --set-fmt-video=width=1280,height=720,pixelformat=UYVY v4l2-ctl -d /dev/v4l-subdev0 --stream-mmap --stream-count=100若报ioctl(VIDIOC_STREAMON): Invalid argument,原因通常是:
pixelformat不匹配 sensor 输出(DS90UB947 仅支持 YUV422-8/10、RGB888,不支持 RAW);width/height超出 sensor 实际分辨率(驱动会校验sensor->max_width);- CSI host 的
num-lanes与>static irqreturn_t ds90ub947_irq_handler(int irq, void *dev_id) { struct ds90ub947_device *dev = dev_id; u8 status1, status2, pll_status; unsigned long flags; // 1. 读取关键状态寄存器(BANK1) ds90ub947_select_bank(dev, 1); ds90ub947_read_reg(dev, 0x05, &status1); // STATUS1 ds90ub947_read_reg(dev, 0x06, &status2); // STATUS2 ds90ub947_read_reg(dev, 0x07, &pll_status); // PLL_STATUS // 2. 记录带纳秒精度的时间戳 struct timespec64 ts; ktime_get_real_ts64(&ts); // 3. 写入 ring buffer(预分配 128-entry) spin_lock_irqsave(&dev->snap_lock, flags); struct snap_entry *e = &dev->snap_buf[dev->snap_head]; e->ts_sec = ts.tv_sec; e->ts_nsec = ts.tv_nsec; e->status1 = status1; e->status2 = status2; e->pll_status = pll_status; e->lock_state = !!(status1 & BIT(7)); // LOCK bit dev->snap_head = (dev->snap_head + 1) & (SNAP_BUF_SIZE - 1); spin_unlock_irqrestore(&dev->snap_lock, flags); return IRQ_HANDLED; }6.2 用户态提取:用 debugfs 暴露快照缓冲区
在
ds90ub947_remove()中添加 debugfs 节点:dev->debugfs_dir = debugfs_create_dir("ds90ub947", NULL); debugfs_create_file("snapshot", 0444, dev->debugfs_dir, dev, &ds90ub947_snap_fops);实现
ds90ub947_snap_fops.read,按 ring buffer 当前 head/tail 顺序 copy 数据到 user buffer。6.3 分析快照:锁定失锁前的寄存器组合
运行
cat /sys/kernel/debug/ds90ub947/snapshot > snap.bin,用 Python 解析:import struct with open('snap.bin', 'rb') as f: while True: data = f.read(24) # 4*uint64 + 3*uint8 if len(data) < 24: break sec, nsec, s1, s2, pll, lock = struct.unpack('<QQBBBH', data) if lock == 0: # 失锁时刻 print(f"[{sec}.{nsec:09d}] LOST LOCK: STATUS1=0x{s1:02x} STATUS2=0x{s2:02x} PLL=0x{pll:02x}")典型输出:
[123456.789012345] LOST LOCK: STATUS1=0x00 STATUS2=0x00 PLL=0x00说明 PLL 已完全失锁,且 STATUS2 为 0,意味着 clock input(
CLKIN引脚)中断。此时应查 sensor 是否断电,或CLKIN走线是否虚焊。从那以后我每次调试 DS90UB947 失锁问题,都强制走一遍寄存器快照 + 时间戳分析,哪怕只是临时加两行 printk —— 因为硬件问题藏得深,但寄存器不会说谎。希望帮到你。
本文还有配套的精品资源,点击获取