简介:本资源面向嵌入式软硬件工程师与驱动开发者,围绕Silicon Labs的Sil9136-3 USB转串行桥接芯片,提供参考手册与驱动源码,帮助解决接口扩展、寄存器配置及底层驱动移植等实际问题。压缩包共24个文件,约13.73MB,包含PDF数据手册与编程参考、C与H源码、Makefile构建脚本、KO内核模块及固件等,覆盖硬件电气特性、编程接口说明到可编译驱动代码的完整链路。资源中附有sil9024驱动实现与1080P视频样本,便于对照理解芯片初始化、寄存器读写、中断处理与数据传输流程,快速搭建验证环境。目前已有571人学习下载,适合需要快速掌握Sil9136驱动开发与接口扩展的中高级开发者参考。
1. 拿到 sil9136 参考手册和驱动源码,先别急着编译
如果你手里正捏着一块带 sil9136 芯片的板子,屏幕点不亮、分辨率不对、或者内核日志里反复刷 HDMI 相关的报错,那这份「sil9136 参考手册 + 驱动源码」的压缩包,大概率就是你接下来几天要反复翻的东西。sil9136 是一颗常见的 HDMI 发送芯片,广泛用在工控主板、广告机、部分 ARM 开发板上,负责把 SoC 输出的并行 RGB 或 BT1120 信号转成 HDMI 输出。它的驱动通常以 Linux 内核模块形式存在,源码里包含寄存器定义、时序配置、EDID 解析、热插拔检测这些核心逻辑。
这份资源的价值在于:参考手册给了你寄存器级别的黑匣子说明,驱动源码给了你可直接改的工程实现。适合两类人——一是做 BSP 移植、需要把 sil9136 在自家板子上跑起来的嵌入式工程师;二是遇到显示异常、想从寄存器层面定位问题的驱动调试者。但我要先泼一盆冷水:这类芯片手册和源码包,直接拿来编译大概率翻车,因为手册版本、源码分支、内核版本三者对不上是常态。下面按我实际拆包的顺序,把这份资源怎么用、参数怎么设、坑在哪,一条条讲清楚。
2. 拆包先看目录:手册、源码、配置三者的对应关系
2.1 压缩包里通常有什么
拿到一个 sil9136 的参考手册加驱动源码包,解压后一般能看到这几类文件:PDF 格式的寄存器手册(Register Map / Programming Guide)、一个driver/或src/目录放 C 源码、若干.dts或.dtsi设备树片段、可能还有一个Makefile或Kconfig。手册负责告诉你每个寄存器地址和位域含义,源码负责把这些寄存器操作封装成函数,设备树负责告诉内核这颗芯片挂在哪条 I2C 总线上、复位脚和中断脚接在哪。
我一般会先做一件事:把手册里的寄存器基地址和源码里#define的宏对一遍。常见做法是打开手册的 Register Map 章节,找到0x00到0xFF这段,再在源码里搜SIL9136_REG_之类的宏前缀。如果地址对不上,说明手册和源码不是同一版本,后面所有调试都会建立在错误前提上。
2.2 版本对齐比什么都重要
sil9136 的驱动在不同内核版本下差异很大。老代码可能是i2c_driver的旧式probe签名,新内核要求probe返回int并配合devm_系列接口。如果你把一份 3.x 时代的驱动直接塞进 5.x 内核,编译报错只是第一关,更麻烦的是运行时 I2C 通信时序对不上。
提示:先确认你的内核版本,再决定是改驱动适配内核,还是换一份匹配的驱动。改驱动的成本通常低于改内核。
对齐版本的具体做法:在源码目录执行grep -rn "probe" sil9136*.c看函数签名,再对比你内核头文件里的struct i2c_driver定义。如果签名不一致,优先改驱动,不要动内核公共头文件。
2.3 设备树片段怎么读
设备树里通常会有这样一段:
&i2c2 { status = "okay"; clock-frequency = <100000>; sil9136: hdmi@39 { compatible = "sil,sil9136"; reg = <0x39>; reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; interrupt-parent = <&gpio1>; interrupts = <16 IRQ_TYPE_EDGE_FALLING>; status = "okay"; }; };这段的关键参数有三个:reg是 I2C 从机地址,sil9136 常见是0x39或0x3B,取决于CS引脚电平;reset-gpios是复位脚,低电平有效;interrupts是热插拔中断,用来检测 HDMI 线插拔。这三个参数任何一个写错,驱动要么 probe 失败,要么 probe 成功但永远收不到热插拔事件。
逻辑说明:clock-frequency设成100000是标准 I2C 速率,sil9136 寄存器读写对时序不苛刻,但如果你板子上拉电阻偏大,可以降到50000试试。compatible字符串必须和驱动里of_device_id表完全一致,差一个字符都匹配不上。
3. 驱动编译与加载:从 Makefile 到 insmod 的完整链路
3.1 先改 Makefile 里的内核路径
驱动源码包里的Makefile通常长这样:
obj-m += sil9136.o sil9136-objs := sil9136_main.o sil9136_hdmi.o sil9136_edid.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如果你是在交叉编译环境里,KERNELDIR要指向你的内核源码树,而不是开发板的/lib/modules。常见做法是:
make KERNELDIR=/path/to/your/kernel/source ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j4参数说明:ARCH和CROSS_COMPILE必须和你的目标板一致,否则编出来的.ko加载时会报invalid module format。-j4是并行编译核数,按你机器来。
3.2 编译报错先看这三类
第一类:implicit declaration of function 'devm_gpio_request'。这是内核版本差异,老接口是gpio_request,新接口是devm_gpio_request。解决方法是看内核头文件里哪个存在,改驱动。
第二类:unknown field 'name' in struct i2c_device_id。这是i2c_device_id结构体在新内核里字段变了,通常把.name改成.compatible或直接删掉这一项。
第三类:section mismatch。这是__init和__exit标注用错了,把不该标__init的函数去掉标注即可。
注意:编译通过不代表能加载。
insmod之后一定要dmesg | tail -30看内核日志,probe 失败的原因九成写在里面。
3.3 加载顺序和依赖
sil9136 驱动通常依赖 I2C 子系统和 GPIO 子系统。加载前先确认:
lsmod | grep i2c lsmod | grep gpio如果 I2C 控制器驱动没加载,sil9136 的 probe 会直接返回-EPROBE_DEFER,内核日志里会写deferred probe pending。这时候不是驱动的问题,是依赖没就绪。常见做法是把 sil9136 驱动编成模块,等系统起来后手动insmod,这样能清楚看到加载顺序。
加载命令:
insmod sil9136.ko dmesg | grep -i sil9136如果看到sil9136: probed successfully之类的字样,说明 I2C 通信和 GPIO 申请都过了。接下来才是真正的显示调试。
4. 寄存器配置与时序调试:手册怎么翻、参数怎么算
4.1 手册里最该先看的三个表
sil9136 手册通常有三大块:Input Timing、Output Timing、Register Map。Input Timing 告诉你芯片能接受什么样的并行信号,Output Timing 告诉你 HDMI 输出支持哪些分辨率。Register Map 是操作手册,每个寄存器地址、默认值、可写位都列在里面。
我一般先翻 Input Timing 表,确认我的 SoC 输出格式是 RGB888 还是 BT1120,时钟极性是高有效还是低有效。这两个参数在设备树或驱动platform_data里对应bus_format和pclk_polarity。如果设错,现象是屏幕有背光但无图像,或者图像整体偏色。
4.2 分辨率配置的寄存器计算
以 1080p60 为例,手册里会给出一组推荐寄存器值。但你的板子晶振可能不是标准 27MHz,这时候要自己算分频。常见做法是找到PLL_CTRL寄存器,根据公式:
Fout = Fin * (N + 1) / (M + 1)其中Fin是输入晶振频率,N和M是寄存器里的分频系数。手册里一般会有一个 PLL 配置表,列出常见输入频率对应的N、M值。如果表里没有你的晶振频率,就按公式反推,取最接近的整数组合。
/* 示例:设置 PLL 分频,具体寄存器名以手册为准 */ #define SIL9136_PLL_CTRL 0x1A #define SIL9136_PLL_N_MASK 0x3F #define SIL9136_PLL_M_SHIFT 6 static int sil9136_set_pll(struct sil9136 *ctx, u8 n, u8 m) { u8 val = (n & SIL9136_PLL_N_MASK) | ((m << SIL9136_PLL_M_SHIFT) & 0xC0); return sil9136_write_reg(ctx, SIL9136_PLL_CTRL, val); }逻辑说明:这段代码把N和M拼成一个字节写进 PLL 控制寄存器。参数说明:n和m来自手册推荐表或你自己计算的结果,写之前先读一次原值,确认没有其他位被误改。
4.3 EDID 读取失败怎么排查
EDID 是显示器告诉主机自己支持哪些分辨率的“身份证”。sil9136 驱动里通常有一段读 EDID 的代码,通过 I2C 从地址0x50读 128 字节。如果读失败,内核日志会写EDID read failed或checksum error。
排查步骤:先用i2cdetect -y 2确认0x50地址是否可见。如果不可见,检查 HDMI 线是否插好、显示器是否上电。如果可见但读出来校验和错,常见原因是 I2C 速率太高,把clock-frequency降到50000再试。还有一种情况是 sil9136 的 EDID 通道需要先使能,手册里有个EDID_CTRL寄存器,bit0 是使能位,忘了写就会一直读不到。
5. 避坑与常见问题:那些让我熬夜的 sil9136 故障
5.1 现象:probe 成功但屏幕无输出
原因:Input Timing 配置和 SoC 实际输出不匹配。驱动 probe 只检查 I2C 通信,不检查信号格式。
解决:用示波器量 SoC 的 PCLK、HSYNC、VSYNC 引脚,确认频率和极性。然后对照手册 Input Timing 表,改驱动里的bus_format和pclk_polarity。没有示波器的话,可以逐个试手册里列出的几种常见组合,每次改完重新加载驱动。
5.2 现象:热插拔中断触发一次后再也不触发
原因:中断标志位没清除。sil9136 的中断是电平触发还是边沿触发,手册里写得很清楚,但驱动里如果只读状态寄存器没写清除位,中断线会一直拉低。
解决:在中断处理函数里,读完状态后必须写一次INT_CLEAR寄存器。常见做法是:
static irqreturn_t sil9136_irq_handler(int irq, void *dev_id) { struct sil9136 *ctx = dev_id; u8 status; sil9136_read_reg(ctx, SIL9136_INT_STATUS, &status); if (status & SIL9136_INT_HPD) { /* 处理热插拔 */ sil9136_hpd_event(ctx); } /* 关键:清除中断标志 */ sil9136_write_reg(ctx, SIL9136_INT_CLEAR, status); return IRQ_HANDLED; }参数说明:SIL9136_INT_CLEAR的地址以手册为准,写进去的值通常是读出来的status,表示清除已处理的中断位。
5.3 现象:加载驱动后系统随机死机
原因:I2C 读写没有加锁,或者中断上下文里调用了可能睡眠的函数。
解决:确认所有 I2C 读写都在进程上下文,中断处理里只做标记,实际处理丢到工作队列。另外检查sil9136_write_reg里有没有msleep之类的调用,有的话移到工作队列里执行。
5.4 现象:EDID 读出来全是 0xFF
原因:HDMI 线质量差或者显示器 EDID 芯片供电不足。
解决:换一根短一点的 HDMI 线试试。如果换了线还是 0xFF,在 sil9136 的EDID_CTRL寄存器里把 EDID 供电位打开,有些板子默认是关的。手册里搜EDID_POWER或类似关键词能找到对应位。
5.5 现象:编译出的 .ko 加载时报 unknown symbol
原因:驱动里用了内核没导出的符号,或者依赖的模块没先加载。
解决:modinfo sil9136.ko看depends字段,按顺序加载依赖。如果是符号本身不存在,说明你的内核配置里没开对应功能,比如CONFIG_GPIO_SYSFS没开但驱动里用了gpio_export。
6. 进阶:用寄存器回读验证配置是否真正生效
调试到后期,最怕的是“以为写进去了,其实没生效”。sil9136 的大部分寄存器是可读可写的,写完再读回来对比,是最便宜的验证手段。我一般会在驱动里加一个debugfs节点,或者直接在 probe 结束后做一轮回读。
static int sil9136_verify_regs(struct sil9136 *ctx) { struct { u8 addr; u8 expected; } checks[] = { { SIL9136_PLL_CTRL, 0x45 }, { SIL9136_INPUT_CTRL, 0x03 }, { SIL9136_OUTPUT_CTRL, 0x01 }, }; int i, ret; u8 val; for (i = 0; i < ARRAY_SIZE(checks); i++) { ret = sil9136_read_reg(ctx, checks[i].addr, &val); if (ret) return ret; if (val != checks[i].expected) { dev_err(ctx->dev, "reg 0x%02x: expect 0x%02x, got 0x%02x\n", checks[i].addr, checks[i].expected, val); return -EIO; } } return 0; }逻辑说明:这段代码在初始化完成后,把几个关键寄存器的期望值和实际回读值做对比。参数说明:checks数组里的期望值来自手册推荐配置或你自己的计算,实际项目中可以把这个表做成可配置的,方便不同分辨率切换。
除了寄存器回读,还有一个技巧是抓 I2C 波形。如果你有逻辑分析仪,把 SCL 和 SDA 接上,看每次写寄存器的地址和数据是否符合预期。我遇到过好几次是 I2C 地址写错了一位,驱动日志一切正常,但芯片根本没收到命令。从那以后我每次调新板子,都强制走一遍“回读 + 波形”双确认,省得后面返工。
另外,sil9136 手册里有些寄存器是“写 1 清除”类型,回读时读到的是清除后的值,不要拿它和写入值直接比。这类寄存器在手册里会标注W1C,遇到这种就跳过回读,或者回读后做掩码处理。希望这些能帮你少走点弯路。
本文还有配套的精品资源,点击获取