资讯动态

sil9136视频桥接芯片驱动源码拆解:从I2C探测到EDID与HPD链路

发布时间:2026/10/17 3:30:37 来源:尧图企业网站定制
简介围绕Sil9136 USB到串行桥接芯片这份资源包整合了官方参考手册与可编译的驱动源码面向嵌入式硬件工程师、驱动开发人员以及物联网设备设计者适用于USB转SPI/I2C/UART等多串口扩展场景。压缩包共24个文件、约13.73MB包含3份PDF文档数据手册、编程参考手册及补充文档、C源码与对应头文件、Makefile、编译生成的.o/ko模块以及配置辅助文件从芯片电气特性、引脚定义、操作模式到软件初始化、寄存器读写与中断处理均有覆盖。目前已有571人学习下载内容经过整理与编译验证便于在Linux环境下直接参考或交叉编译。利用这套资料开发者可快速搭建Sil9136的驱动框架解决硬件调试和驱动移植中的常见问题对芯片选型评估和产品开发都颇有参考价值。1. 拆包 sil9136 参考手册与驱动源码一次把视频桥接芯片的驱动链路看清如果你接过嵌入式显示相关的活大概率碰到过这种局面芯片手册几百页寄存器表比字典还厚驱动源码散落在各个 BSP 里拷过来编译一堆报错最后靠改 GPIO 延时把画面“试”出来。这份 sil9136 参考手册加驱动源码的打包资源解决的就是这个痛——把一颗视频桥接/转换芯片从硬件管脚定义、寄存器映射到 Linux 驱动框架下的完整实现一次性给全。它适合两类人一是正在做显示相关板卡定制、需要快速移植驱动的嵌入式工程师二是想搞懂 I2C 设备驱动怎么写、中断和 EDID 上报机制怎么配合的学生开发者。拿到手不是看手册而是对着源码把芯片的初始化时序、状态机切换、HPD 事件上报整条链路走一遍你会明显感觉到比对着 PDF 盲读效率高得多。2. 参考手册与源码包结构先搞清这一包里到底放了什么2.1 文件清单与目录组织PDF 之外的硬货在哪解压后第一件事不是翻手册而是先把目录树列出来。常见做法是用 tree 命令或直接看压缩包注释通常这类资源会按doc/、driver/、include/、utility/四块组织。doc 目录下面是数据手册和编程指南PDF 或 odt 格式都有driver 目录才是主角——Linux 内核驱动源码包含sil9136.c、sil9136.h以及 Makefileinclude 目录是芯片寄存器头文件按功能块拆成reg_hdmi.h、reg_i2c.h、reg_cbus.h这类utility 目录一般放厂商提供的调试工具源码或脚本比如通过 I2C 总线直接读写寄存器的小工具。我一般会先看sil9136.c的头部注释和MODULE_DESCRIPTION这能最快确认驱动对应的内核版本和 API 风格。如果源码里用了i2c_driver结构体加probe/remove回调说明这是标准 I2C 客户端驱动如果出现spi_driver那就是走 SPI 接口的变体。sil9136 这类视频转换芯片绝大多数板卡设计中 I2C 是控制通道视频数据走并行接口或 LVDS这点从i2c_device_id表和of_match_table就能确认。2.2 硬件特性在手册里的分布规律别从头翻到尾数据手册几百页从头读到尾的人是少数效率也低。我拆过几份这类芯片手册后发现重点是三个区域管脚描述表Pin Description、寄存器映射表Register Map和电气特性表DC/AC Characteristics。管脚表决定硬件接线的可行性比如你的主控 I2C 引脚是否支持 1.8V 电平HPD 脚有没有内部上拉寄存器映射表决定驱动代码里那些魔法数字从哪来电气特性表则是排查信号完整性问题的依据。以 sil9136 为例驱动源码里反复出现的0x7F这类寄存器偏移手册里会有专门一段描述控制通道CBUS的访问机制。要看懂源码里sil9136_read_reg这类函数的参数不需要把整本手册背下来只需要找到寄存器映射表按地址区间划分功能块视频参数块、音频参数块、中断状态块、EDID RAM 块。驱动里读中断状态时用的那一串REG_INT_STATUS_*宏定义必然对应手册里中断相关寄存器的位定义。对着这个思路去读源码和手册的对应关系很快就建立起来了。2.3 技术栈判断这驱动跑在内核哪个版本上判断技术栈最直接的方式是看驱动的 API 风格。老内核2.6.x里i2c_driver结构体有attach_adapter回调新内核4.x 以上全部走probe函数。sil9136 驱动源码如果用的是devm_kzalloc、device_property_read_u32这类设备管理 API说明依赖较新的内核机制至少 3.14 以上。如果出现of_property_read_u32则是 3.x 时代的老风格。我遇到过一份移植到 5.10 内核报错的驱动原因就是源码里还在用i2c_new_device而新版内核改成了i2c_new_client_device。这种 API 变动是移植时最常见的坑。读代码时顺便把 Makefile 里的ccflags-y看一下有没有开启额外的调试宏有些版本会带SIL9136_DEBUG这种条件编译开关开启后会在内核日志里打印大量寄存器读写信息调试阶段很有用量产阶段记得关掉。3. 核心驱动链路拆解I2C 探测、EDID 读取与 HPD 中断上报3.1 I2C 客户端注册与设备树匹配probe 是怎么被调起来的sil9136 驱动的主入口是标准 I2C 驱动框架。设备树里声明了这个芯片的节点后内核 I2C 子系统会按compatible字符串匹配of_match_table匹配上了就调用probe函数。下面是一段典型的设备树节点写法i2c2 { status okay; sil9136: sil913639 { compatible sil9136,i2c; reg 0x39; reset-gpios gpio4 17 GPIO_ACTIVE_LOW; hpd-gpios gpio4 18 GPIO_ACTIVE_HIGH; vdd-supply reg_1v8; }; };这段描述里reg 0x39是芯片的 I2C 从机地址reset-gpios和hpd-gpios分别指向复位引脚与热插拔检测引脚所连接的 GPIO 控制器节点。vdd-supply指向一个稳压器节点驱动在 probe 时需要通过regulator_get获取供电控制句柄。驱动源码中对应的是static const struct i2c_device_id sil9136_id[] { { sil9136, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, sil9136_id); static const struct of_device_id sil9136_of_match[] { { .compatible sil9136,i2c }, { } }; MODULE_DEVICE_TABLE(of, sil9136_of_match);i2c_device_id表用于非设备树平台比如通过板级文件i2c_register_board_info注册设备of_device_id表则是设备树平台的匹配入口。两个表都挂着驱动就能同时兼容新旧两种内核平台这是很多老驱动移植时容易丢的部分——只改设备树忘了同步i2c_device_id结果老平台直接 probe 失败。3.2 寄存器读写函数封装所有控制操作的地基芯片驱动到最后就是读寄存器、写寄存器、等状态。sil9136 的寄存器分页机制比较复杂部分功能块需要先往某个 page 寄存器写入页号才能访问目标偏移。源码里sil9136_write_reg会把页切换和偏移访问封装到一起调用的地方不需要关心页细节。static int sil9136_write_reg(struct i2c_client *client, u8 page, u8 offset, u8 value) { struct i2c_msg msg; u8 buf[3]; int ret; buf[0] page; buf[1] offset; buf[2] value; msg.addr client-addr; msg.flags 0; msg.len 3; msg.buf buf; ret i2c_transfer(client-adapter, msg, 1); if (ret ! 1) { dev_err(client-dev, write reg failed: page0x%02x offset0x%02x\n, page, offset); return -EIO; } return 0; }这个封装的精妙之处在于把页号写入和目标寄存器访问合并成一次 I2C 事务。大部分 I2C 控制器对单次传输长度没有严格限制合并传输可以避免分开写页号后再写寄存器之间被其他设备插入操作导致页状态错乱的问题。buf[0]是页号buf[1]是寄存器偏移buf[2]是要写入的值。i2c_transfer返回值是成功传输的消息数量不是 1 就说明总线上出了问题打印关键字dev_err能帮你在内核日志里快速定位是地址错误还是设备掉线。读操作封装类似区别在于要分两次传输先写页号和偏移然后读取数据。有些 I2C 控制器不支持 repeat start 操作这种情况下驱动会退化为两个独立的i2c_transfer调用中间可能会被抢占但实际使用中影响不大因为寄存器访问本身不要求原子性。3.3 HPD 中断处理与 EDID 缓存状态上报的完整链路视频桥接芯片有一个核心职责检测显示设备是否插入并把显示器的 EDID 数据读回来供主控解析。sil9136 的 HPD 引脚接到主控 GPIO 后驱动要处理两种事件HPD 上升沿表示显示器插入下降沿表示拔出。static irqreturn_t sil9136_hpd_irq_handler(int irq, void *dev_id) { struct sil9136_priv *priv dev_id; int state; state gpiod_get_value(priv-hpd_gpio); if (state) { sil9136_read_edid(priv-client); sysfs_notify(priv-client-dev.kobj, NULL, hpd_state); dev_info(priv-client-dev, HPD high, display connected\n); } else { priv-edid_ready false; dev_info(priv-client-dev, HPD low, display disconnected\n); } return IRQ_HANDLED; }这段中断处理代码里gpiod_get_value读取 HPD GPIO 当前电平sil9136_read_edid在插入事件发生时立即去读 EDID 数据并缓存在priv结构体里sysfs_notify通知用户态监听者。这里的关键设计决策是EDID 读取放在中断上下文里执行还是放到工作队列。EDID 读操作涉及多次 I2C 读取每读一次要几十微秒完整读一个 256 字节的 EDID 块大约需要几毫秒放在中断上下文会阻塞其他中断处理所以我一般会改成schedule_work或threaded_irq。dev_info打出的日志在调试阶段能看到插入/拔出事件的实时时间戳对排查 HPD 抖动问题很有价值。EDID 缓存机制方面源码里常见的做法是维护一个struct edid *指针和edid_ready标志位。主控通过驱动提供的get_edid回调读取时先检查标志位如果为 false 就返回 -EAGAIN 让上层重试而不是返回空指针。这种设计避免了对 EDID 数据并发读写导致的数据竞争问题。3.4 视频参数配置序列初始化时序里的关键寄存器probe 函数通常会调用一组初始化序列把芯片从默认状态切换到目标工作模式。初始化序列一般包含软复位、时钟源选择、视频输出格式配置、I2C 总线速度设置、中断使能。以下是常见的软复位加基本配置代码static int sil9136_init(struct sil9136_priv *priv) { int ret; /* 软复位置位 RESET 寄存器等待芯片重新初始化 */ ret sil9136_write_reg(priv-client, 0x00, REG_SOFT_RESET, 0x01); if (ret) return ret; usleep_range(5000, 10000); /* 配置主时钟分频根据输入时钟频率选择合适的 PLL 参数 */ ret sil9136_write_reg(priv-client, 0x01, REG_CLK_DIV, 0x08); if (ret) return ret; /* 设置视频极性默认上升沿采样可通过设备属性覆盖 */ ret sil9136_write_reg(priv-client, 0x01, REG_POLARITY, 0x00); if (ret) return ret; /* 使能 HPD 中断屏蔽其余中断避免频繁唤醒 */ ret sil9136_write_reg(priv-client, 0x02, REG_INT_MASK, 0x01); if (ret) return ret; return 0; }这段初始化代码中usleep_range而不是msleep是因为软复位等待时间在毫秒级以下短延时用usleep_range更适合因为它允许内核在睡眠期间调度其他任务同时保持较高的唤醒精度。REG_CLK_DIV的设置直接影响输出时钟的频率如果分频系数设置错误画面会出现行场不同步、画面偏移或者完全无信号。设备树里可以增加clock-frequency之类的自定义属性驱动里用device_property_read_u32读取后覆盖默认值这就是后面改分辨率兼容性的入口点。4. 编译、烧录与运行调试从源码到设备起来4.1 交叉编译环境搭建与 Makefile 修改要点拿到驱动源码后第一步是确认编译环境。如果是 ARM 平台需要先找到对应的交叉编译工具链常见的有arm-linux-gnueabihf-或aarch64-linux-gnu-前缀。然后把工具链路径加进 PATH并在内核源码顶层目录执行make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- menuconfig确认 I2C 支持已开启。驱动自身的 Makefile 通常长这样obj-m sil9136.o KDIR : /path/to/kernel/source PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) cleanKDIR要改成你实际内核源码的路径注意不是内核头文件目录而是完整源码树编译模块需要用到内核的 Kbuild 系统。执行make后如果生成sil9136.ko说明编译通过。有些环境需要指定 ARCH 和 CROSS_COMPILE可以在 make 命令行追加make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- KDIR/home/xxx/kernel-5.10如果遇到include/uapi/linux/...找不到头文件的报错通常是内核源码没配置过或make modules_prepare没执行。在 KDIR 目录下先跑一次make modules_prepare能解决大部分这类问题。4.2 设备树配置与模块加载流程编译只是第一步真正让驱动跑起来还需要设备树支持。把前面写过的那段设备树节点加到你的板级 dtsi 文件里注意 I2C 总线编号要和硬件实际连接的控制器对应。如果 I2C2 上还挂着其他设备确认地址没有冲突——sil9136 的地址跳线引脚可能会改从机地址我遇到过复位后从 0x39 变成 0x3D 的情况原因是某个 GPIO 状态影响了地址引脚的电平。加载模块的命令insmod sil9136.ko或者用 modprobe 配合 depmodcp sil9136.ko /lib/modules/$(uname -r)/extra/ depmod -a modprobe sil9136设备加载成功后dmesg | tail会看到 probe 成功的信息。确认 I2C 通信正常可以先用 i2cdetect 扫描总线i2cdetect -y 2如果看到39出现在列表里说明芯片的 I2C 地址正确且响应正常。如果扫描不到先查硬件供电有没有到位、复位引脚电平对不对、I2C 上拉电阻是否焊接。这个排查顺序比反复改驱动代码效率高得多。4.3 用户态验证读 EDID 和状态切换测试模块加载后需要验证驱动功能是否完整。最直观的验证方式是读 EDIDcat /sys/devices/platform/.../sil9136/edid edid.bin如果能在设备树中配置好 sysfs 属性或者驱动里实现了show回调这个命令就会把 EDID 原始数据导出出来。再配合edid-decode工具解析edid-decode edid.bin解析结果里能看到显示器名称、分辨率支持列表、色彩深度等关键信息。如果解析失败大概率是 EDID 读取时序有问题——比如读取时芯片还没稳定或者 I2C 读操作被 NACK。模拟拔出和插入可以看中断日志echo 0 /sys/class/gpio/gpioXX/value # 拉低 HPD dmesg | tail -5 # 应该看到 HPD low, display disconnected这一步验证的是中断边沿触发配置是否正确。很多板子的问题出在设备树里interrupts属性没有配置或者 GPIO 管脚复用被其他外设占用导致中断信号根本到不了处理器。5. 避坑指南移植 sil9136 驱动时常见的翻车现场5.1 现象probe 失败返回 -ENODEV但 i2cdetect 能扫到设备原因设备树 compatible 字符串与驱动里of_match_table不一致。很多厂商源码里 compatible 写的是sil9136而你在设备树里写了sil9136,i2c匹配不上直接返回失败。解决把设备树的 compatible 改成和驱动源码of_device_id表里完全一致。或者反过来改源码里的匹配表两者必须精确匹配差一个字符都不行。排查方法是在probe函数入口加dev_info打印看函数有没有被调用——没进 probe 说明匹配层就断了。5.2 现象模块加载正常但 EDID 读取超时原因I2C 时钟配置过快。sil9136 数据手册里对 I2C 时序有明确限制很多主控 I2C 控制器默认跑 400kHz但芯片工作电压偏低或者 PCB 走线过长时400kHz 的实际建立时间不达标读操作会频繁 NACK。解决把 I2C 时钟降到 100kHz 或 200kHz 重试。如果确认是硬件走线问题检查 I2C 上拉电阻阻值过大的上拉电阻10k 以上会拉慢边沿时间。我一般会在设备树 i2c 节点里设置clock-frequency 100000这种玄学问题改驱动代码没用先查硬件。5.3 现象HPD 中断频繁触发系统日志被刷屏原因HPD 引脚悬空或存在电容耦合噪声导致 GPIO 电平在临界点反复跳动每次跳变都触发一次中断。解决硬件上在 HPD 引脚加 10k 下拉电阻或上拉取决于逻辑极性和 100nF 电容滤除高频噪声。软件上在中断处理里加防抖逻辑记录上次触发时间如果间隔小于某阈值比如 20ms就忽略本次事件。我一般会在驱动里用一个jiffies时间戳比较来过滤掉小于 50ms 的连续跳变。5.4 现象显示画面偏移、花屏但寄存器配置看起来没问题原因输出分辨率与主控端配置的分辨率不一致或者视频时序参数HTotal/VTotal对不上。sil9136 内部虽然做了格式转换但输入端的时序参数需要与主控输出严格匹配。解决核对主控端 LCD 控制器输出的pixel clock、h_back_porch、v_front_porch等参数。驱动源码里通常有一张预设分辨率表检查当前配置是否选对了表项。我见过有人在设备树里改了输出分辨率但驱动初始化序列里依然是默认分辨率两边对不上导致画面异常。5.5 现象写入寄存器总是返回错误但读取正常原因有机会是芯片进入保护模式或写保护使能。某些版本芯片在检测到输入信号异常时会锁定寄存器写入需要通过特定序列解锁。解决检查手册里是否有关于写保护寄存器的描述执行解锁序列——一般需要连续向某个 magic 地址写入特定值。再看驱动里有没有设置REG_WRITE_ENABLE之类的寄存器。如果源码注释里提到unlock sequence确认初始化时有没有正确执行。这类问题往往卡住大半天手册第一遍可能不会注意。6. 二次开发从改分辨率支持到扩展其他功能拿到这份资源后真正值钱的地方在于改造。我建议的改法分三个档次改参数适配新屏幕、改驱动结构适配新内核、加功能实现自定义逻辑。改参数适配新屏幕是基础操作。假设你换了一块 1080p 的 LCD 面板需要确认两处一是设备树里display-timing节点的参数包括 active 区域的宽高、前后肩、像素时钟二是驱动源码里初始化序列中视频输出相关寄存器的配置。我一般习惯先改设备树跑一遍因为内核的 display timing 解析有日志输出能看出参数是否被正确读取再回去改寄存器更稳。适配新内核是不可避免的功课。把旧版驱动往 5.15 内核移植时重点看这几个地方i2c_new_device改成i2s_new_client_deviceof_property_read_u32换成device_property_read_u32兼容 ACPI 和 device treegpio_request_one换成devm_gpiod_get。中断申请函数从request_irq改成devm_request_threaded_irq并利用线程化中断做耗时操作。每次内核大版本升级这些 API 变动都会有文档说明但在实际项目里最稳妥的方式是找一个同内核版本的最新驱动做参考对着改。加入自定义功能这块如果项目需要支持音量调节或者音频通道切换可以在sil9136.c里增加对应的 IOCTL 接口或者 sysfs 节点。参考现有属性文件的做法加一个audio_mute的 sysfs 节点写 1 时调用寄存器设置静音读时返回当前状态。这样用户态程序只需要读写文件就能控制音频通断省去写应用层通讯协议的麻烦。做这些改动时我有个习惯每改一个寄存器或者 API 调用先在旁边注释里写明为什么这么改关联手册哪个章节。最初是给自己留后路后来发现这习惯救了周围不少人——有些寄存器配置隔三个月回来看完全想不起当初为什么这么设。从那以后我每次拿到新芯片驱动都会强制自己先花半小时把目录结构和关键宏定义扫一遍再动手改。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑