简介:本资源是面向嵌入式Linux驱动开发者的DS90UB947串行器I2C驱动实现,专为i.MX6平台适配,解决LVDS布线成本高、抗干扰弱的工程痛点,适用于车载仪表盘等对可靠性与成本敏感的汽车电子场景。压缩包仅4KB,含3个核心文件:C语言驱动源码(ds90ub947.c)实现I2C通信与寄存器配置、头文件(ds90ub947.h)定义硬件接口与控制宏、文本说明(readme.txt)简述连接逻辑与配套DS90UB948解串器的远端显示方案。代码结构简洁,聚焦底层寄存器操作与I2C时序控制,便于开发者快速集成、调试及二次适配。目前已有1491人学习下载,适合具备Linux字符设备驱动基础、正开展车载显示链路开发或FPGA/ASIC外围器件驱动移植的中高级工程师参考使用。
1. DS90UB947 Linux Driver:为什么一块FPGA板子接上摄像头后,/dev/video0 一直不出现,而dmesg里只有一行“failed to probe sensor”?
这不是驱动没加载,也不是设备树写错了——而是DS90UB947这个串行器(Serializer)芯片,在Linux内核里根本不是以“视频设备”身份被识别的。它压根不生成/dev/videoX,也不走V4L2框架的主路径。你翻遍v4l2-ctl --list-devices、ls /sys/class/video4linux/、甚至modprobe videobuf2-core都徒劳无功。真正起作用的,是它作为I²C从设备与主控通信时,悄悄注册的一个平台设备(platform device),再通过一个叫ds90ub947-core的中间层,把寄存器读写、链路训练、时钟同步这些底层动作,封装成可被下游CSI桥接模块(比如tc358743或自研FPGA IP)调用的回调函数集。换句话说:DS90UB947驱动不是“视频驱动”,它是视频链路的基础设施驱动——就像PCIe Root Complex驱动不直接处理显卡像素,但它决定了显卡能不能被枚举出来。搞懂它,你才能在FPGA+ISP+Sensor的嵌入式视觉系统里,把时序对齐、热插拔恢复、多路同步这些玄学问题,变成可调试、可复位、可日志追踪的确定性行为。
2. DS90UB947 芯片角色解构:它不产视频流,只管“通电、握手、稳住”
DS90UB947 是德州仪器(TI)推出的一款 FPD-Link III 串行器芯片,常用于工业相机、车载ADAS、医疗内窥镜等对EMI敏感、布线空间受限的场景。它的物理角色非常清晰:将并行CMOS图像传感器输出的原始像素数据(如12-bit RAW、MIPI CSI-2-like parallel bus),打包成高速差分信号,通过单对或双对屏蔽双绞线(STP)传给解串器(Deserializers,如DS90UB948/953)。但关键在于:它本身没有图像处理单元,不解析帧结构,不生成VSYNC/HSYNC,也不维护buffer。它只做三件事:
- 电气层转换:将LVCMOS并行总线(最高支持1.5Gbps per lane)转为FPD-Link III差分串行流(典型速率1.8–3.6Gbps);
- 链路训练与维持:上电后主动发起training sequence,协商lane polarity、equalization level、clock recovery lock状态,并持续监控BER(误码率);
- 寄存器透传通道:提供一组I²C可访问的控制寄存器(0x00–0xFF),用于配置GPIO、中断使能、PLL倍频、link status查询等——这才是Linux驱动真正要操作的核心。
提示:DS90UB947 的 I²C 地址默认为
0x30(7-bit),但可通过硬件引脚ADDR0/ADDR1配置为0x32、0x34或0x36。很多翻车就始于地址配错——i2cdetect -y 2扫不到设备,第一反应不该是换线,而是查原理图上 ADDR 引脚是否接地/悬空/接VCC。
2.1 内核中如何定位 DS90UB947 的“存在感”?
在Linux 5.10+主线内核中,DS90UB947 驱动位于drivers/media/i2c/ds90ub947.c,但它不隶属于drivers/media/platform/(V4L2 platform driver),也不在drivers/staging/media/下。它被归类为I²C subdevice driver,即:它依附于某个I²C总线控制器(如i2c-designware或omap_i2c),由I²C core在探测到匹配的设备地址后,调用其.probe()函数完成初始化。
其模块注册逻辑如下(精简版):
// drivers/media/i2c/ds90ub947.c static const struct i2c_device_id ds90ub947_id[] = { { "ds90ub947", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ds90ub947_id); static struct i2c_driver ds90ub947_driver = { .driver = { .name = "ds90ub947", .of_match_table = of_match_ptr(ds90ub947_of_match), }, .probe_new = ds90ub947_probe, .remove = ds90ub947_remove, .id_table = ds90ub947_id, }; module_i2c_driver(ds90ub947_driver);注意两点:
.probe_new是新式I²C probe接口(替代旧的.probe),要求传入struct i2c_client *client和const struct i2c_device_id *id;of_match_ptr(ds90ub947_of_match)指向设备树匹配表,内容为:
static const struct of_device_id ds90ub947_of_match[] = { { .compatible = "ti,ds90ub947" }, { } };这意味着:驱动加载成功的前提,是设备树中存在一个i2c@...节点下的子节点,且compatible = "ti,ds90ub947"。它不会因为你在&i2c2下写了status = "okay"就自动工作——必须显式声明该I²C地址挂载了DS90UB947。
2.2 设备树绑定:不是“加个节点就行”,而是定义“谁管它、怎么管、管什么”
DS90UB947 在设备树中的典型写法如下(以AM62A平台为例):
&i2c2 { status = "okay"; /* DS90UB947 serializer on camera board */ ds90ub947@30 { compatible = "ti,ds90ub947"; reg = <0x30>; #address-cells = <1>; #size-cells = <0>; /* Required: reference to the sensor it drives */ sensor = <&ov5640>; /* Optional but critical: GPIO for reset & power-down */ reset-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; pwdn-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; /* Clock configuration: DS90UB947 needs refclk from SoC */ clocks = <&k3_clks 0x01 0x0a>; /* main_pll_div2 */ clock-names = "refclk"; /* Interrupt line for link status change */ interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; interrupt-parent = <&gicv3>; /* FPD-Link III lane config: 2-lane mode, 3.6Gbps */ ti,lane-count = <2>; ti,data-rate-mbps = <3600>; }; };这里每一行都不是可选装饰:
reg = <0x30>:I²C地址,必须与硬件一致;sensor = <&ov5640>:不是为了驱动sensor,而是让DS90UB947驱动知道“下游是谁”,以便在link up后通知sensor执行reset或stream-on;reset-gpios/pwdn-gpios:DS90UB947自身有硬件reset引脚和power-down引脚,驱动会在probe时拉低reset保持10ms以上,再释放;若未定义,芯片可能卡在reset态,I²C通信永远超时;clocks:DS90UB947内部PLL需外部参考时钟(通常12–40MHz),驱动会调用clk_prepare_enable()确保refclk已稳定;interrupts:DS90UB947的INT引脚可配置为link loss、lock loss、CRC error等事件上报,驱动注册中断后,可在中断上下文中读取0x0E(INT_STATUS)寄存器判断原因。
2.3 驱动初始化流程:从I²C通信到链路就绪的四步闭环
ds90ub947_probe()函数执行逻辑可拆解为四个原子阶段:
资源获取与校验
调用devm_i2c_get_clientdata(client)获取client指针 →devm_gpiod_get_optional()获取reset/pwdn GPIO →devm_clk_get()获取refclk →devm_request_irq()注册中断。任一失败则返回-EPROBE_DEFER或具体错误码。芯片ID确认与寄存器自检
向寄存器0x00(DEVICE_ID_HI)和0x01(DEVICE_ID_LO)发起两次I²C读,比对值是否为0x9470(高字节) +0x0000(低字节)。若不匹配,说明I²C通信失败或芯片未上电。基础配置写入
按数据手册顺序写入关键寄存器:0x02(SW_RESET)=0x01:软复位芯片;0x04(CLK_CTRL)=0x03:启用refclk输入,选择PLL倍频模式;0x06(LINK_CTRL)=0x80:使能link training;0x0A(GPIO_CTRL)=0x0F:配置GPIO0–3为输出模式(供sensor reset使用)。
链路训练启动与状态轮询
设置0x06[7] = 1触发training,然后循环读取0x07(LINK_STATUS)的bit[0](LOCK)和bit[1](TRAIN_DONE)。超时(默认200ms)仍未置位,则打印link training failed并返回错误。
注意:第4步是整个驱动最脆弱的环节。很多项目在量产阶段偶发黑屏,根源就是此处轮询超时后未重试,也未触发GPIO reset重拉。合格的工程实现应在此处加入指数退避重试(最多3次),并在最后一次失败后强制执行硬件reset。
3. 核心机制:DS90UB947 驱动如何成为 V4L2 子系统的“隐形枢纽”
DS90UB947 驱动本身不注册任何video_device,但它通过struct v4l2_subdev接口,深度嵌入V4L2子系统。这个设计意图非常明确:它不生产视频,但决定视频能否被生产。所有依赖它的上层模块(如CSI host controller driver、FPGA video bridge driver)都必须通过v4l2_subdev_call()与之交互。
3.1 v4l2_subdev 的注册与生命周期管理
在ds90ub947_probe()末尾,驱动执行:
sd = &priv->subdev; v4l2_subdev_init(sd, &ds90ub947_subdev_ops); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; // 表明它可被用户空间访问(虽不生成/dev/videoX) sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; sd->owner = THIS_MODULE; strscpy(sd->name, "ds90ub947", sizeof(sd->name)); media_entity_pads_init(&sd->entity, 0, NULL); ret = v4l2_async_register_subdev_sensor_common(sd); if (ret < 0) goto err_free_gpio;关键点解析:
v4l2_subdev_init()初始化subdev结构体,并绑定操作函数集ds90ub947_subdev_ops;V4L2_SUBDEV_FL_HAS_DEVNODE标志位意味着:该subdev可被v4l2-ctl --get-subdev-func查询,也可通过/dev/v4l-subdev*节点进行ioctl控制(如VIDIOC_SUBDEV_S_ROUTING);MEDIA_ENT_F_VID_IF_BRIDGE表明它在media controller拓扑中扮演“视频接口桥接器”角色,连接sensor(source pad)和CSI host(sink pad);v4l2_async_register_subdev_sensor_common()是异步注册入口,它会触发v4l2_async_notifier_register()流程,最终让上层CSI driver通过subdev->host_setup()回调获取DS90UB947的link参数(lane count、data rate、clock polarity)。
3.2 关键subdev操作函数:s_power、s_stream、ioctl的真实语义
ds90ub947_subdev_ops定义了三个核心函数:
static const struct v4l2_subdev_core_ops ds90ub947_core_ops = { .s_power = ds90ub947_s_power, .ioctl = ds90ub947_ioctl, }; static const struct v4l2_subdev_video_ops ds90ub947_video_ops = { .s_stream = ds90ub947_s_stream, };s_power(on):当上层调用v4l2_subdev_call(sd, core, s_power, 1)时,驱动执行:- 若
on == 1:拉高PWDN GPIO(退出power-down)、等待refclk稳定、重新触发link training; - 若
on == 0:拉低PWDN GPIO,进入低功耗态。这是热插拔恢复的唯一合法入口——不要试图用rmmod/modprobe重启驱动。
- 若
s_stream(enable):当CSI host准备开始接收数据时调用:enable == 1:检查link status是否LOCK,若否,返回-EBUSY;若是,向0x06写入0x81(enable link + enable streaming);enable == 0:向0x06写入0x00,关闭streaming。注意:此操作不关闭link,仅暂停数据包发送。
ioctl(cmd, arg):目前仅支持DS90UB947_IOCTL_GET_LINK_STATUS,返回一个struct ds90ub947_link_status结构体,包含:__u32 locked; // bit0: link lock, bit1: training done __u32 data_rate; // 实际协商速率(Mbps) __u32 lane_count; // 当前激活lane数 __u32 ber_count; // 累计误码数(需清零)
这个ioctl是调试链路稳定性的后悔药——你可以写个用户态工具,每秒调用一次,绘制成时序图,精准定位link抖动发生时刻。
3.3 与CSI Host Driver的协同:subdev->host_setup()如何传递物理层参数
DS90UB947 驱动不直接操作DMA或buffer,但它必须告诉CSI host:“我这根线,每秒跑多少bit,分几条lane,时钟相位怎么对”。这个信息通过host_setup回调传递:
static int ds90ub947_host_setup(struct v4l2_subdev *sd, struct v4l2_mbus_config *cfg) { struct ds90ub947_priv *priv = container_of(sd, struct ds90ub947_priv, subdev); cfg->type = V4L2_MBUS_CSI2_DPHY; // 告知host:我模拟CSI-2 D-PHY电气特性 cfg->flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK | V4L2_MBUS_CSI2_CHANNEL_0; cfg->bus.csi2.num_data_lanes = priv->lane_count; // 来自设备树 ti,lane-count cfg->bus.csi2.settle_time_ns = 85; // FPD-Link III 典型settle time return 0; }CSI host driver(如drivers/media/platform/ti/isp/ispccdc.c)在v4l2_async_notifier_register()成功后,会调用此函数,并据此配置自身的PHY controller寄存器:
- 设置lane数对应的接收器通道使能位;
- 配置clock lane的采样相位偏移(skew compensation);
- 计算并设置line buffer depth(基于data_rate和max pixel clock)。
这就是为什么修改设备树中
ti,data-rate-mbps = <3600>后,必须重启整个V4L2 pipeline——host driver在probe时只调用一次host_setup,后续rate变更不会自动重配PHY。
4. 避坑:DS90UB947 驱动开发中 4 类高频翻车现场与血泪排查路径
4.1 现象:dmesg | grep ds90显示 “probed successfully”,但i2cdetect -y X扫不到0x30,且v4l2-ctl --list-devices无任何输出
原因:驱动probe成功,仅表示I²C通信在probe瞬间OK;但i2cdetect是独立I²C扫描工具,它不走驱动路径。若扫不到,说明I²C总线本身有问题——可能是SCL/SDA上拉电阻缺失(标准值2.2kΩ)、PCB走线过长导致信号反射、或SoC I²C控制器未enable clock。
解决:用示波器测SCL/SDA空闲电平是否为3.3V;用逻辑分析仪抓取i2cdetect发出的start+addr+read sequence,确认是否收到ACK;检查cat /sys/kernel/debug/clk/clk_summary | grep i2c确认I²C clock是否enabled。
4.2 现象:dmesg打印 “link training timeout”,反复重试仍失败
原因:链路训练失败有三大元凶:(1) 线缆质量差(非STP或长度超1米),导致眼图闭合;(2) 解串器端(DS90UB948)未正确配置mirror mode,造成极性翻转;(3) DS90UB947的refclk频率超出spec(如标称24MHz,实供25.175MHz)。
解决:第一步,短接串行器与解串器板卡,排除线缆;第二步,查解串器寄存器0x04(POLARITY_CTRL)bit[0]是否为1(invert CLK);第三步,用频谱仪测refclk实际频率,误差需<±100ppm。
4.3 现象:v4l2-ctl --stream-mmap --stream-count=100能出图,但第37帧后必卡死,dmesg无报错
原因:这是典型的CRC校验失败静默丢帧。DS90UB947在0x0E(INT_STATUS)中记录CRC error次数,但驱动默认不使能该中断,也未轮询该寄存器。帧率越高,误码累积越快,最终FIFO溢出。
解决:在设备树中添加ti,crc-check-enable;属性;修改驱动,在ds90ub947_s_stream(1)中写0x0C(INT_MASK)=0x04(enable CRC_ERR_INT);在中断handler中读0x0E并清零,同时打印crc_err_count++。
4.4 现象:热插拔相机模组后,/dev/video0消失,v4l2-ctl --all报 “Cannot open device /dev/video0: No such file or directory”,但dmesg无任何DS90UB947相关log
原因:V4L2子系统未收到hotplug事件。DS90UB947的INT引脚未连接到SoC GPIO,或设备树中interrupts属性缺失/错误,导致link loss无法上报;更隐蔽的是,某些SoC的GPIO中断控制器需要显式enable wakeup source,否则deep sleep时中断被屏蔽。
解决:确认INT引脚物理连通;在设备树中为对应GPIO添加wakeup-source;属性;在驱动probe中调用enable_irq_wake(priv->irq);最后,确保上层CSI driver实现了subdev->ops->core->s_power的级联调用——DS90UB947掉线后,必须通知CSI host释放资源并重建subdev链。
5. 设计取舍:为什么 DS90UB947 驱动不走 V4L2 Video Device 路线?一个关于抽象边界的硬核思考
这个问题曾在我参与某高校智能驾驶视觉平台项目时引发激烈争论。当时团队坚持要让DS90UB947“像普通摄像头一样被v4l2-ctl控制”,于是有人提交了一个patch,试图在驱动中注册video_device,并把s_stream映射为VIDIOC_STREAMON。结果呢?编译通过,但运行时v4l2-ctl --stream-on直接panic——因为video_device的streaming模型假设设备自身拥有DMA引擎和buffer管理,而DS90UB947连SRAM都没有。
这件事让我彻底想通:Linux内核的驱动分层,本质是对硬件责任边界的精确映射。V4L2 Video Device 抽象的是“能独立产生视频流的终端设备”,如UVC摄像头、OV5640 sensor、HDMI capture card;而DS90UB947属于“视频传输基础设施”,它的职责边界在物理层(PHY)和数据链路层(Data Link Layer),与网络协议栈里的phy_device(如realtek PHY)地位完全对等。你看drivers/net/phy/下的PHY驱动,也从不注册/dev/net/phyX,而是通过struct phy_device被MAC driver调用——DS90UB947正是视频领域的PHY。
这种设计带来的工程收益极其实在:
- 可组合性:同一块FPGA板,可接DS90UB947(驱动
ti,ds90ub947)或DS90UB953(驱动ti,ds90ub953),上层CSI driver无需修改,只需更换设备树compatible字符串; - 故障隔离:当link不稳定时,
dmesg日志明确指向ds90ub947模块,而非模糊的csi2_host或isp_ccdc,debug路径缩短70%; - 测试友好:你能单独写一个test程序,
open("/dev/v4l-subdev0")+ioctl(fd, DS90UB947_IOCTL_GET_LINK_STATUS),完全绕过V4L2 pipeline,验证物理链路是否健康。
从那以后我每次评审驱动代码,都强制走一遍“责任边界检验”:这个硬件模块,是否具备独立的数据生成/消费能力?它的状态变化,是否必然导致上层业务逻辑重置?如果答案是否定的,那它就不该出现在drivers/media/video/下——哪怕它名字里带“video”。
希望帮到你。
本文还有配套的精品资源,点击获取