3. 硬件接口层适配:DWC3 / EHCI / OHCI 控制器差异、寄存器映射与 DMA 配置、PHY 初始化与时钟树调试
硬件接口层适配,说白了就是让 USB 协议栈跟具体的芯片“对上话”。
我早年刚接触 USB 驱动时,总觉得这部分就是填几个寄存器。后来踩的坑多了才明白——控制器差异、DMA 配置、PHY 初始化,这三件事但凡有一个没对齐,整个 USB 栈就跑不起来。
3.1 三大控制器:DWC3 / EHCI / OHCI 到底差在哪?
先看一张总览图,把三者的定位理清楚。
3.2 寄存器映射:从 Datasheet 到代码
拿到一份新的 SoC 手册,我习惯先做一件事——找到 USB 控制器的基地址。
DWC3 的寄存器空间通常分为两部分:
- Global Registers:全局控制,包括版本号、中断使能、DMA 控制
- Device / Host Registers:根据当前角色切换,映射到同一段地址偏移
EHCI 和 OHCI 就简单多了。它们各自有独立的 CAPLENGTH 和 HCCPARAMS 寄存器,用来描述能力指针。
3.3 DMA 配置:环形队列 vs 链表
DMA 是 USB 高性能的关键。但不同控制器的 DMA 模型差别很大。
| 控制器 | DMA 模型 | 描述符格式 | 对齐要求 |
|---|---|---|---|
| DWC3 | TRB 环形队列 | 16 字节 / 条目 | 16 字节对齐 |
| EHCI | 周期 / 异步列表 | qTD (32 字节) | 32 字节对齐 |
| OHCI | ED / TD 链表 | ED (16 字节) + TD (16 字节) | 16 字节对齐 |
我在项目中遇到过一个问题:DWC3 的 TRB 必须放在 非 Cacheable 的内存区域。否则 CPU 写好了描述符,DMA 读到的却是脏数据。
3.4 PHY 初始化:让物理层“活”过来
PHY 是 USB 的物理层,负责把数字信号变成差分线上的模拟信号。
初始化步骤通常包括:
- 上电复位:释放 PHY 的复位信号
- 时钟选择:配置参考时钟频率(24 MHz / 26 MHz / 19.2 MHz 等)
- 接口模式:选择 UTMI / ULPI / PIPE3
- 校准:阻抗校准、摆幅调整
- 状态轮询:等待 PHY 进入 Ready 状态
DWC3 内部集成了 PIPE3 接口,可以直接连接 USB 3.0 PHY。而 EHCI 通常通过 ULPI 接口连接外部 PHY。
3.5 时钟树调试:从源头排查
时钟树是 USB 调试的“第一道关”。我总结了一个排查顺序:
- 检查参考时钟:用示波器测 PHY 的 XI 引脚,确认频率和幅度
- 检查 PLL 锁定:读 PHY 的状态寄存器,看 PLL Lock 位是否置 1
- 检查接口时钟:UTMI 需要 60 MHz,ULPI 需要 60 MHz,PIPE3 需要 125 MHz
- 检查总线时钟:AHB / AXI 时钟必须满足控制器的最低频率要求
举个例子。有一次客户反馈 USB 3.0 设备无法识别。我远程看了 log,发现 PHY 的 PLL Lock 位一直为 0。最后定位到是参考时钟的晶振负载电容配错了,导致频率偏了 0.5%。换了个电容就好了。
3.6 实战:DWC3 寄存器映射示例
下面是一段 DWC3 初始化代码的骨架,展示了基地址映射和核心寄存器操作:
/* DWC3 全局寄存器基地址 */
#define DWC3_GLOBALS_BASE 0x4A000000
#define DWC3_GSNPSID 0x00 /* 版本 ID 寄存器 */
#define DWC3_GHWPARAMS0 0x08 /* 硬件参数寄存器 */
#define DWC3_GCTL 0x10 /* 全局控制寄存器 */
static void __iomem *dwc3_base;
/* 1. 映射寄存器空间 */
dwc3_base = ioremap(DWC3_GLOBALS_BASE, 0x10000);
/* 2. 读取版本号,验证映射是否正确 */
u32 id = readl(dwc3_base + DWC3_GSNPSID);
pr_info("DWC3 Version: 0x%08x\n", id);
/* 3. 读取硬件参数 */
u32 hwparams0 = readl(dwc3_base + DWC3_GHWPARAMS0);
u32 num_eps = (hwparams0 >> 10) & 0x1F;
pr_info("Number of endpoints: %d\n", num_eps);
/* 4. 配置全局控制寄存器 */
u32 gctl = readl(dwc3_base + DWC3_GCTL);
gctl |= DWC3_GCTL_SCALEDOWN(0) | DWC3_GCTL_DISSCRAMBLE(0);
writel(gctl, dwc3_base + DWC3_GCTL);
这段代码看起来简单,但我在实际项目中踩过一个坑:ioremap 的 size 不够大。DWC3 的寄存器空间可能跨越 64KB,如果只映射 4KB,访问后面的 endpoint 寄存器就会触发异常。
3.7 小结
硬件接口层适配,核心就三件事:
- 控制器差异:DWC3 是全能选手,EHCI 是 2.0 主力,OHCI 是 1.1 老将
- DMA 配置:描述符格式和对齐要求必须严格匹配
- PHY 与时钟:初始化顺序和频率匹配是成败关键
嗯,这部分内容比较“硬”,但它是整个 USB 协议栈的基石。基础打牢了,后面移植协议栈和调试驱动才会顺手。