简介:本资源面向嵌入式软硬件工程师与驱动开发者,围绕Silicon Labs的Sil9136-3 USB转串行桥接芯片,提供参考手册与驱动源码,用于解决接口扩展、寄存器配置及底层驱动移植等实际问题。压缩包共24个文件,约13.73MB,包含PDF数据手册与编程参考、C与H源码、Makefile构建脚本、KO内核模块及固件等,覆盖硬件电气特性、编程接口说明到可编译驱动实现。其中sil9024系列源码展示了寄存器读写、I2C通信与模块加载流程,配合手册可快速理解芯片初始化与数据传输机制。目前已有571人学习下载,适合工业控制、智能家居及物联网模块开发中需要扩展SPI、I2C或UART接口的读者参考,帮助缩短驱动调试周期。
1. 从一份 sil9136 参考手册与驱动源码说起:显示桥接芯片的底层开发入口
如果你手头有一块带 sil9136 芯片的板子,屏幕点不亮、分辨率不对、或者 HDMI 输出时有时无,翻遍社区也找不到一份能直接对着寄存器改的文档,那这份 sil9136 参考手册加驱动源码的组合就是为你准备的。sil9136 是一颗常见的 HDMI 发送/显示桥接芯片,广泛出现在工控主板、嵌入式显示模块和部分国产化终端设备上。厂商通常只给一个二进制固件或一个黑盒驱动,出了问题只能等原厂支持。这份资源把寄存器手册和驱动源码放在一起,意味着你可以直接看到每个时钟分频、每个色彩空间转换、每个中断状态位是怎么被配置的。适合谁?适合做嵌入式 Linux 显示驱动、FPGA 视频输出、或者需要把 sil9136 从“能用”调到“稳定”的一线开发者。新手能照着把驱动编进内核跑通,熟手能直接对着手册改时序参数。
2. 项目定位与技术栈:这不是一个应用,而是一套底层显示链路
2.1 sil9136 在系统中的位置
sil9136 通常挂在 SoC 的 I2C 总线上,接收来自显示控制器的并行 RGB 或 BT656 信号,转换成 HDMI/DVI 输出。它内部有完整的 TMDS 编码器、音频嵌入模块和 EDID 解析逻辑。驱动源码要做的就是通过 I2C 配置这些模块,让 SoC 输出的视频流能被显示器正确识别。常见做法是把它注册成一个 DRM encoder 或者一个独立的 framebuffer 设备,具体取决于你的内核版本和显示子系统架构。
2.2 技术栈与依赖
这份资源的核心是 C 语言驱动加寄存器手册。驱动部分通常依赖 Linux 内核的 I2C 子系统、DRM 框架或者旧的 fbdev 框架。如果你用的是 4.x 以后的内核,大概率要走 DRM bridge 那套;如果是老平台,可能就是一个字符设备加 ioctl。手册部分一般是 PDF 或 Excel 寄存器表,里面按页列出每个寄存器的 bit 定义。编译驱动需要内核头文件、交叉编译工具链,以及一个能跑起来的最小根文件系统。调试阶段还会用到 i2c-tools 和示波器或 HDMI 协议分析仪。
2.3 目录结构长什么样
拿到压缩包后,解压出来一般是这样几块:
sil9136_ref_manual/ ├── doc/ │ ├── sil9136_registers.pdf # 寄存器手册,按页分模块 │ └── sil9136_hw_checklist.txt # 硬件上电时序和引脚说明 ├── driver/ │ ├── sil9136_drv.c # 主驱动文件,probe/remove 和 I2C 读写 │ ├── sil9136_drv.h # 寄存器偏移和位掩码定义 │ ├── sil9136_edid.c # EDID 解析和模式匹配 │ └── Makefile # 内核模块编译入口 └── example/ └── dts_sil9136.dtsi # 设备树节点示例这个结构很典型:文档和代码分离,驱动按功能拆成几个文件,设备树单独给一个片段。你拿到后第一件事不是急着编译,而是先翻手册确认你板子上的 sil9136 是哪个封装、I2C 地址是 0x3B 还是 0x3C,这决定了后面所有配置的起点。
3. 驱动核心模块拆解:从 I2C 读写到 HDMI 输出
3.1 I2C 寄存器读写封装
所有对 sil9136 的控制都通过 I2C 完成。驱动里最底层的就是两个函数:读一个字节和写一个字节。手册里每个寄存器都有页号和偏移,所以实际访问是“先写页选择寄存器,再读写目标寄存器”。
/* sil9136_drv.c: 基础 I2C 读写,带页切换 */ static int sil9136_write_reg(struct i2c_client *client, u8 page, u8 reg, u8 val) { u8 buf[2]; int ret; /* 先切页,sil9136 的寄存器按页组织,每页 256 字节 */ buf[0] = 0x00; /* 页选择寄存器地址 */ buf[1] = page; ret = i2c_master_send(client, buf, 2); if (ret < 0) return ret; /* 再写目标寄存器 */ buf[0] = reg; buf[1] = val; return i2c_master_send(client, buf, 2); } static int sil9136_read_reg(struct i2c_client *client, u8 page, u8 reg, u8 *val) { u8 page_buf[2] = {0x00, page}; int ret; ret = i2c_master_send(client, page_buf, 2); if (ret < 0) return ret; /* 读操作需要先写寄存器地址,再读一个字节 */ ret = i2c_master_send(client, ®, 1); if (ret < 0) return ret; return i2c_master_recv(client, val, 1); }逻辑说明:sil9136 的寄存器空间是分页的,直接写偏移会跑到错误的页上。参数 page 来自手册的页号,reg 是页内偏移,val 是要写入的值。注意 i2c_master_send 和 i2c_master_recv 的返回值是实际传输的字节数,小于预期就说明 I2C 通信有问题,常见原因是上拉电阻没焊或者地址冲突。
3.2 上电初始化序列
手册里会有一个“Power-Up Sequence”章节,规定了先给哪个电源、再释放哪个复位、最后配置哪个时钟。驱动里对应一个初始化函数,顺序不能乱。
/* 上电初始化,严格按手册时序 */ static int sil9136_power_on(struct sil9136_dev *dev) { struct i2c_client *client = dev->client; int ret; /* 1. 复位芯片,拉低再拉高由硬件完成,这里只写复位寄存器 */ ret = sil9136_write_reg(client, 0x00, 0x01, 0x01); if (ret < 0) return ret; msleep(10); /* 手册要求至少 5ms,留余量 */ /* 2. 配置 PLL,输入时钟频率来自 SoC 的 pixel clock */ ret = sil9136_write_reg(client, 0x00, 0x02, dev->pll_config); if (ret < 0) return ret; /* 3. 使能 TMDS 输出 */ ret = sil9136_write_reg(client, 0x00, 0x03, 0x80); if (ret < 0) return ret; /* 4. 等待 TMDS 稳定,手册建议 20ms */ msleep(20); return 0; }逻辑说明:复位后的延时不能省,很多“屏幕闪一下就没信号”的问题就是延时不够。pll_config 这个值要根据你的输入像素时钟算出来,手册里会给公式,一般是pll_config = (pixel_clk / ref_clk) * 某个系数。TMDS 使能后不要立刻读状态,芯片内部需要时间锁定。
3.3 EDID 解析与模式匹配
显示器通过 EDID 告诉主机自己支持哪些分辨率。sil9136 内部有 EDID 缓冲,驱动要把它读出来,解析出首选时序,然后配置 SoC 的显示控制器输出对应分辨率。
/* 读取 EDID 并解析首选模式 */ static int sil9136_read_edid(struct sil9136_dev *dev, struct edid *edid) { u8 edid_data[128]; int i, ret; /* EDID 在 sil9136 的特定页里,通常是页 0x04 开始 */ for (i = 0; i < 128; i++) { ret = sil9136_read_reg(dev->client, 0x04, i, &edid_data[i]); if (ret < 0) return ret; } /* 检查 EDID 头,必须是 00 FF FF FF FF FF FF 00 */ if (edid_data[0] != 0x00 || edid_data[1] != 0xFF) { dev_err(&dev->client->dev, "EDID header invalid\n"); return -EINVAL; } /* 解析首选时序,通常在 detailed timing descriptor 里 */ /* 这里简化处理,实际要按 EDID 标准解析 pixel clock 和 blanking */ dev->preferred_width = (edid_data[0x38] << 8) | edid_data[0x39]; dev->preferred_height = (edid_data[0x3A] << 8) | edid_data[0x3B]; return 0; }逻辑说明:EDID 头不对说明 I2C 读回来的数据是乱的,先查地址和上拉。首选时序的解析比上面复杂,实际驱动里会用内核的drm_edid或fb_edid解析器,这里只展示手动读的流程。参数 preferred_width 和 preferred_height 拿到后要传给显示控制器,不能只配 sil9136。
3.4 中断处理与热插拔检测
HDMI 支持热插拔,显示器拔掉再插上,驱动要能重新读 EDID 并恢复输出。sil9136 有一个 HPD 中断引脚,驱动里注册中断处理函数。
/* HPD 中断处理,重新检测连接状态 */ static irqreturn_t sil9136_hpd_isr(int irq, void *data) { struct sil9136_dev *dev = data; u8 status; /* 读中断状态寄存器,清除中断标志 */ sil9136_read_reg(dev->client, 0x00, 0x10, &status); if (!(status & 0x01)) return IRQ_NONE; /* 重新读 EDID 并通知显示子系统 */ schedule_work(&dev->hpd_work); return IRQ_HANDLED; }逻辑说明:中断里不能做 I2C 读写,因为 I2C 可能睡眠,所以用 workqueue 延后处理。status 的 bit0 是 HPD 状态变化标志,读一次就自动清除。hpd_work 里会重新走一遍 EDID 读取和模式设置。
4. 编译、加载与调试:让驱动在板子上跑起来
4.1 交叉编译内核模块
驱动源码里的 Makefile 通常长这样:
# driver/Makefile obj-m += sil9136_drv.o sil9136_drv-objs := sil9136_drv.o sil9136_edid.o KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build CROSS_COMPILE ?= arm-linux-gnueabihf- ARCH ?= arm all: make -C $(KERNEL_DIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules clean: make -C $(KERNEL_DIR) M=$(PWD) clean编译命令:
make KERNEL_DIR=/path/to/kernel/source ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-参数说明:KERNEL_DIR 必须指向你板子实际运行的内核源码目录,不能指向主机内核。ARCH 和 CROSS_COMPILE 要和你的工具链一致。编译产物是 sil9136_drv.ko,用 scp 或 U 盘拷到板子上。
4.2 设备树节点配置
驱动需要知道 I2C 地址、中断引脚和复位引脚。设备树里加一个节点:
/* example/dts_sil9136.dtsi */ &i2c1 { sil9136: hdmi-bridge@3b { compatible = "sil,sil9136"; reg = <0x3b>; interrupt-parent = <&gpio1>; interrupts = <12 IRQ_TYPE_EDGE_FALLING>; reset-gpios = <&gpio1 13 GPIO_ACTIVE_LOW>; status = "okay"; }; };参数说明:reg 是 I2C 从地址,0x3b 是 7 位地址。interrupts 里的 12 是 GPIO 编号,要和你原理图对上。reset-gpios 是复位引脚,驱动 probe 时会拉低再拉高。如果设备树里地址写错,probe 会直接失败,dmesg 里能看到 “no such device”。
4.3 加载模块与查看日志
# 加载驱动 insmod sil9136_drv.ko # 查看内核日志 dmesg | tail -30 # 确认 I2C 设备被识别 i2cdetect -y 1如果 i2cdetect 在 0x3b 位置显示 “UU”,说明驱动已经占用了这个地址。如果显示 “--”,说明 I2C 通信没通,先查硬件。dmesg 里会打印 probe 成功或失败的信息,失败时重点看 “sil9136: probe failed” 后面的错误码。
4.4 用 i2c-tools 手动验证寄存器
在写驱动之前,我一般会先用 i2c-tools 手动读写几个关键寄存器,确认硬件是活的:
# 读芯片 ID,手册里通常有一个固定的 ID 寄存器 i2cget -y 1 0x3b 0x00 # 写页选择,再读状态 i2cset -y 1 0x3b 0x00 0x01 i2cget -y 1 0x3b 0x01这一步能省掉很多瞎猜。如果 i2cget 返回 “Error: Read failed”,先查上拉电阻和电源,别急着改驱动。
5. 避坑与常见问题:那些让我熬夜的 sil9136 调试记录
5.1 屏幕闪一下就黑,但 i2cdetect 正常
现象:驱动 probe 成功,i2cdetect 能看到设备,但 HDMI 输出几秒后消失。原因:PLL 配置值算错了,TMDS 时钟不稳定,显示器握手失败后进入保护。解决:对着手册重新算 PLL 分频,用示波器量 TMDS 时钟输出,确保在显示器支持的范围内。常见做法是先用一个已知能点亮的标准分辨率(比如 1280x720)验证,再改到目标分辨率。
5.2 EDID 读回来全是 0xFF
现象:驱动读 EDID 头不对,打印 “EDID header invalid”。原因:sil9136 的 EDID 缓冲没有使能,或者 I2C 读的页号错了。解决:先写 EDID 使能寄存器,再读。手册里 EDID 通常在页 0x04,但有些封装在页 0x0A,以你手上的手册为准。另外确认 HPD 引脚已经拉高,显示器没接的话 EDID 缓冲是空的。
5.3 加载模块后内核直接 panic
现象:insmod 后系统挂掉,串口打印 oops。原因:probe 函数里访问了空指针,常见于设备树节点没匹配上但驱动还是跑了。解决:在 probe 开头加if (!client->dev.of_node) return -ENODEV;,确保设备树匹配成功再往下走。另外检查 i2c_get_clientdata 的返回值,不要直接解引用。
5.4 热插拔后分辨率不恢复
现象:拔掉 HDMI 再插上,显示器识别但分辨率变成默认的 640x480。原因:HPD 中断处理里只重新读了 EDID,但没有重新配置显示控制器。解决:在 hpd_work 里调用 drm_helper_hpd_irq_event 或者手动触发 mode set,把新的首选时序应用到 SoC 侧。注意 workqueue 里不能睡眠太久,I2C 读 EDID 要加超时。
5.5 音频没有输出
现象:视频正常,但 HDMI 音频无声。原因:sil9136 的音频嵌入模块没使能,或者 I2S 数据格式配错。解决:手册里音频相关寄存器在页 0x02,先使能音频采样率和通道数,再配置 I2S 输入格式。常见做法是先用 48kHz 立体声验证,再改到其他格式。如果 SoC 侧 I2S 没输出,先查 I2S 时钟和 DMA。
6. 二次开发与改造:从能跑到好用的几个关键点
拿到这份源码后,最直接的改造是适配你自己的板子。第一步改设备树,把 I2C 地址、中断引脚、复位引脚换成你原理图上的实际值。第二步改 PLL 配置,根据你的输入像素时钟重新算分频。第三步改 EDID 解析,如果你不需要热插拔,可以把 HPD 中断去掉,简化成只在上电时读一次 EDID。
如果你想把它接入 DRM 框架,需要把驱动改成一个 drm_bridge 或者 drm_encoder。核心工作是实现drm_bridge_funcs里的enable、disable、mode_set回调,把 sil9136 的寄存器配置放到这些回调里。这样就能和 SoC 的显示控制器联动,支持更复杂的显示管线。改造时注意 DRM 的 atomic 模式要求所有配置在一次 commit 里完成,不能像 fbdev 那样分步写寄存器。
另一个常见需求是支持更多分辨率。手册里会给出不同分辨率下的时序参数表,你需要把这些参数做成一个数组,根据 EDID 解析出的模式去匹配。匹配不上时回退到 1280x720 或者 1024x768。我一般会加一个模块参数force_mode,调试时强制指定分辨率,省得每次改代码。
验证改造是否成功,不能只看屏幕亮了。要确认三件事:一是dmesg里没有 I2C 超时或 EDID 校验错误;二是cat /sys/class/drm/card0-HDMI-A-1/modes能看到正确的模式列表;三是拔插 HDMI 后分辨率能自动恢复。这三条都过了,才算真正稳定。
从那以后我每次改 sil9136 的配置,都强制走一遍“i2cget 读 ID → 手动写 PLL → 读 EDID 头 → 加载驱动看 dmesg”的流程,不再直接改代码编译。这个习惯帮我省掉了至少三次通宵排查。希望帮到你。
本文还有配套的精品资源,点击获取