简介:这份资源面向嵌入式软硬件工程师与驱动开发者,围绕Silicon Labs的Sil9136-3 USB转串行桥接芯片,提供参考手册与驱动源码,帮助解决芯片初始化、接口配置与数据传输等开发问题。压缩包共24个文件,约13.73MB,包含PDF参考手册与编程指南、C与H源码、Makefile构建脚本、KO内核模块及固件等,覆盖硬件电气特性、寄存器操作、驱动编译与加载等环节。已有571人学习下载,适合需要快速上手或移植驱动的开发者。通过手册可掌握引脚定义、操作模式与编程接口,借助源码可理解底层读写、中断处理与抽象层设计,并参考readme与示例文件完成编译验证,为工业控制、智能家居、物联网模块等场景的串行接口扩展提供完整支撑。
1. 拿到一份 sil9136 参考手册和驱动源码,我第一件事是翻目录而不是编译
很多做嵌入式显示驱动的朋友,拿到一份芯片资料包,第一反应是找 Makefile 直接 make。我早年也这样,结果在 sil9136 这类 HDMI 发送芯片上翻过车——编译过了,板子点不亮,回头翻手册才发现初始化序列里少配了一个 PLL 分频寄存器。sil9136 是 Silicon Image 家的 HDMI 发送器,常见于工控主板、视频采集卡和国产化显示终端,配套的参考手册加驱动源码,基本就是把这颗芯片从「上电」到「出图」的全过程摊开给你看。这份资源适合两类人:一是要在 Linux 或裸机环境里点亮 HDMI 输出的驱动工程师,二是想搞懂 HDMI 发送端寄存器模型和 EDID 交互逻辑的底层开发者。它不教你 HDMI 协议入门,但能让你少走我当年那段弯路。
2. 先看清家底:sil9136 驱动源码的目录结构与模块划分
2.1 一份典型驱动包拆开长什么样
拿到压缩包解压后,目录通常不会太深,但每一层都有明确分工。我拆过的 sil9136 驱动包,结构大致如下(不同来源会有出入,但核心文件跑不掉):
sil9136_driver/ ├── Makefile ├── Kconfig ├── sil9136_core.c # 芯片初始化、寄存器读写、电源管理 ├── sil9136_hdmi.c # HDMI 模式设置、时序配置 ├── sil9136_edid.c # EDID 读取与解析 ├── sil9136_i2c.c # I2C 通信层 ├── sil9136_regs.h # 寄存器地址与位域定义 ├── sil9136_api.h # 对外接口声明 └── doc/ └── sil9136_reference_manual.pdfsil9136_regs.h是整份代码的字典,所有寄存器偏移和位定义都在这里。我一般先花二十分钟把这份头文件过一遍,比直接看 C 文件快得多。sil9136_core.c负责芯片上电后的初始化流程,包括复位、时钟配置、TMDS 使能。sil9136_hdmi.c处理视频模式切换,比如从 720p 切到 1080p 时重新计算像素时钟和前后肩参数。sil9136_edid.c则通过 DDC 通道读取显示器能力,决定输出分辨率上限。
2.2 寄存器模型:理解 sil9136 的钥匙
sil9136 的寄存器分页管理,每页 256 字节,通过0x1E寄存器切换页号。这个设计在 HDMI 发送芯片里很常见,但新手容易在这里栽跟头——写寄存器时忘了切页,数据就写到别的功能块去了。源码里通常会有这样的封装:
/* sil9136_i2c.c 中的寄存器写入封装 */ int sil9136_write_reg(struct sil9136_dev *dev, u8 page, u8 offset, u8 value) { int ret; u8 buf[2]; /* 先切换页号,再写目标寄存器 */ ret = sil9136_i2c_write(dev, SIL9136_REG_PAGE_SEL, page); if (ret < 0) return ret; buf[0] = offset; buf[1] = value; return sil9136_i2c_write_raw(dev, buf, 2); }这段代码的关键在于SIL9136_REG_PAGE_SEL这个宏,它对应物理地址0x1E。每次写非当前页的寄存器,都必须先调一次页切换。参数page是页号,offset是页内偏移,value是要写入的字节。我见过有人为了省事,在初始化时一次性把所有页的配置写进一个数组,结果因为没切页,后半段配置全丢了。常见做法是在sil9136_core.c的sil9136_init_sequence函数里,按页分组写入,每组开头显式切页。
2.3 驱动与手册的对应关系
参考手册里每个寄存器都有详细描述,但手册不会告诉你代码里怎么用。我的习惯是拿一支荧光笔,把手册里标为「必须配置」的寄存器在sil9136_regs.h里找到对应宏,然后在sil9136_core.c里搜这个宏,看它在哪个函数里被写。这样能把手册的静态描述和代码的动态流程串起来。比如手册里0x08页的0x1A寄存器控制 TMDS 输出使能,代码里就会在sil9136_hdmi_enable函数里找到对应的写操作。这种对照阅读法,比单独看任何一边都有效。
3. 跑起来才算数:从编译到点屏的完整操作链路
3.1 编译环境与依赖确认
这份驱动源码通常面向 Linux 内核,编译前先确认内核头文件路径。如果是外部模块编译,Makefile 里一般会写:
obj-m += sil9136_driver.o sil9136_driver-objs := sil9136_core.o sil9136_hdmi.o sil9136_edid.o sil9136_i2c.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean编译命令就是make,但前提是KERNELDIR指向的内核源码树已经配置过。如果报错说找不到modules目标,八成是内核头文件没装全。我一般会先跑make -C /lib/modules/$(uname -r)/build M=$PWD modules确认环境,再执行make。编译产物是sil9136_driver.ko,用insmod加载。
3.2 设备树或板级配置的适配
sil9136 通过 I2C 控制,通过并行视频口或 MIPI 接收像素数据。加载驱动前,得在设备树里声明 I2C 地址和中断引脚。一个典型的设备树节点长这样:
&i2c2 { status = "okay"; clock-frequency = <100000>; sil9136: hdmi-transmitter@39 { compatible = "sil,sil9136"; reg = <0x39>; interrupt-parent = <&gpio1>; interrupts = <18 IRQ_TYPE_EDGE_FALLING>; reset-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; status = "okay"; }; };reg = <0x39>是 sil9136 的 I2C 从地址,具体值看手册的「I2C Address」章节。reset-gpios指定复位引脚,驱动加载时会拉低再拉高完成硬件复位。如果设备树里漏了reset-gpios,驱动可能加载成功但芯片没复位,表现就是 I2C 读写全部超时。我踩过这个坑,后来养成习惯:加载驱动后先dmesg | grep sil9136,看有没有「reset gpio not found」之类的警告。
3.3 点屏验证与 EDID 读取
驱动加载成功后,用modetest或fbset查看输出状态。如果是 DRM 框架,modetest -M sil9136能列出支持的显示模式。EDID 读取是否正常,直接决定能不能协商出正确分辨率。源码里sil9136_edid.c的读取函数大致逻辑是:
/* 通过 I2C 读取 EDID 数据块 */ int sil9136_read_edid(struct sil9136_dev *dev, u8 *edid_buf) { int ret; u8 seg = 0; /* 先写段地址,再读 128 字节 EDID */ ret = sil9136_i2c_write(dev, 0x50, &seg, 1); if (ret < 0) return ret; ret = sil9136_i2c_read(dev, 0x50, edid_buf, 128); if (ret < 0) return ret; /* 校验 EDID 头,固定为 00 FF FF FF FF FF FF 00 */ if (edid_buf[0] != 0x00 || edid_buf[1] != 0xFF) { dev_err(dev->dev, "invalid EDID header\n"); return -EINVAL; } return 0; }0x50是 DDC 通道的标准 I2C 地址。seg用于扩展 EDID 的段选择,标准 128 字节 EDID 写 0 即可。校验头是判断读取是否成功的第一道关卡,如果头不对,后面解析分辨率、时序全是白搭。我一般会在读取失败时用示波器看 DDC 的 SCL/SDA 有没有波形,没波形就查上拉电阻和线序,有波形但数据错就查 I2C 速率是否匹配。
4. 避坑与排查:sil9136 调试中最容易翻车的五个点
4.1 现象:驱动加载成功,但 HDMI 无输出
原因通常是 TMDS 输出使能位没写,或者像素时钟没配。sil9136 的 TMDS 使能在0x08页的0x1A寄存器 bit0,很多初始化序列会漏掉这一步。解决方法是检查sil9136_hdmi_enable函数里有没有对SIL9136_TMDS_ENABLE的写操作,没有就补上。另外确认像素时钟源是否锁定,手册里0x09页的0x1E寄存器有 PLL 锁定状态位,读一下就知道。
4.2 现象:I2C 通信随机失败
原因多半是 I2C 速率过高或上拉电阻偏大。sil9136 的 DDC 通道标准速率是 100kHz,但有些板子为了兼容其他设备把总线拉到 400kHz,导致时序余量不足。解决方法是把设备树里的clock-frequency降到 100000,同时确认 SCL/SDA 上拉电阻在 2.2k 到 4.7k 之间。如果还不行,用逻辑分析仪抓一次完整读写,看 ACK 位是否正常。
4.3 现象:EDID 读取全为 0xFF
原因通常是 DDC 通道没供电或显示器没上电。HDMI 接口的+5V引脚要给显示器的 EDID 芯片供电,如果板子没输出这个 5V,读回来就是全 1。解决方法是量一下 HDMI 座子的 18 脚有没有 5V,没有就查供电电路。另一个可能是 DDC 的 I2C 地址被其他设备占用了,用i2cdetect扫一下总线,看 0x50 是否被标记为UU。
4.4 现象:分辨率协商正确但画面偏移或闪烁
原因通常是时序参数和显示器实际能力不匹配。EDID 里给出的时序是显示器的「推荐值」,但 sil9136 的像素重复、前后肩配置如果和 EDID 不一致,就会出现偏移。解决方法是把sil9136_hdmi.c里的时序结构体和 EDID 解析出的detailed_timing逐项对比,重点看h_active、h_front_porch、h_sync_width这三个值。我一般会在驱动里加一句dev_dbg把实际写入的时序打出来,和modetest显示的期望值对一遍。
4.5 现象:热插拔后无法重新出图
原因通常是中断处理里没重新初始化 TMDS 或没重新读 EDID。HDMI 热插拔会触发 HPD 中断,驱动应该在中断下半部里重新走一遍sil9136_hdmi_enable和sil9136_read_edid。如果中断注册了但没处理,或者处理了但没重新配置,就会黑屏。解决方法是检查sil9136_core.c里的中断服务函数,确认 HPD 事件有没有调用完整的重配置流程。我见过有人只在中断里打了个日志,那当然不会出图。
5. 二次开发怎么改:从改分辨率到接新屏的实操思路
5.1 改分辨率:从 1080p 到 4K 的寄存器调整
sil9136 支持的最高分辨率取决于具体型号和像素时钟上限。如果要改到 4K,第一步是确认芯片手册里的最大 TMDS 时钟频率。假设支持 300MHz,那 4K30 的像素时钟约 297MHz,刚好卡在边缘。代码上要改三处:sil9136_hdmi.c里的模式表增加 4K 条目,sil9136_core.c里的 PLL 分频参数重新计算,sil9136_regs.h里如果有硬编码的时序宏也要同步更新。我一般会先用手册里的 PLL 计算公式算出分频比,再写个临时脚本验证像素时钟是否落在允许范围内。
5.2 接新屏:EDID 解析的扩展与容错
不同显示器的 EDID 格式有差异,有些国产屏的 EDID 里detailed_timing描述块不标准,驱动解析时可能越界。二次开发时建议在sil9136_edid.c里加一层校验:先检查 EDID 的extension flag,如果有扩展块就多读 128 字节;再检查每个detailed_timing的像素时钟是否非零,为零就跳过。我还会把解析出的时序存一份到 debugfs,方便出问题时直接cat出来看,不用重新编译驱动。
5.3 验证方法:用 modetest 和示波器交叉确认
改完代码后,先modetest -M sil9136 -p看输出状态,再用modetest -M sil9136 -s <connector_id>:<mode>设置模式。如果画面出来了但颜色不对,用示波器量 TMDS 差分对的眼图,看幅度和共模电压是否正常。我习惯在改完 PLL 参数后,先量像素时钟输出引脚,确认频率和计算值一致,再看 TMDS 输出。这样能把问题定位在「时钟域」还是「数据域」,省得来回猜。
5.4 一个具体技巧:用寄存器回读做自检
sil9136 的大部分寄存器支持回读。我一般会在初始化序列写完后,把关键寄存器再读一遍,和写入值比对。比如 TMDS 使能位、PLL 锁定状态、像素时钟分频比,这三个读回来一致,基本就能确定芯片配置生效了。这个自检逻辑可以写成sil9136_self_check函数,在probe末尾调用,失败就返回-EIO。从那以后我每次改完初始化序列,都强制走一遍寄存器回读,再也没出现过「编译通过但点不亮」的情况。希望帮到你。
本文还有配套的精品资源,点击获取