3 PHY寄存器基础:如何读写PHY寄存器,MDIO/MDC协议简介
好,咱们直接进入主题。做RGMII延时补偿,说白了就是跟PHY寄存器打交道。你得知道往哪个地址写什么值,还得确保写进去了、读出来是对的。这一节我就带你搞明白两件事:一是MDIO/MDC协议到底怎么工作,二是我们怎么用它来读写PHY寄存器。
3.1 MDIO/MDC协议到底是什么?
MDIO,全称是Management Data Input/Output。你可以把它理解成一条管理通道,专门用来配置和监控PHY芯片的状态。MDC则是配套的时钟。
我在项目里见过不少新同事,上来就问:“我能不能直接用I2C或者SPI去配PHY?”
嗯,其实大多数PHY都支持MDIO/MDC,这是IEEE 802.3标准定义的,简单又可靠。
3.1.1 物理层连接
MDIO/MDC只需要两根线:
- MDC:时钟线,由MAC侧提供,最高频率通常为2.5MHz(也有跑12.5MHz的,看芯片手册)。
- MDIO:数据线,双向的。MAC发送命令或数据,PHY回复数据。
我个人习惯把MDIO理解成一个半双工总线,谁控制时钟谁就是主设备。MAC是主,PHY是从。
3.1.2 帧格式
MDIO的帧结构是固定的,一共32位。我整理了一张表,你看一眼就明白了:
| 字段 | 位宽 | 说明 |
|---|---|---|
| 前导码 (Preamble) | 32位 | 全1,用于同步。有些PHY可以省略,但我建议不要省,兼容性最好。 |
| 起始码 (Start of Frame) | 2位 | 固定为01,表示一帧开始。 |
| 操作码 (Op Code) | 2位 | 读操作是10,写操作是01。这个别搞反。 |
| PHY地址 (PHY Address) | 5位 | 你想访问哪个PHY?地址范围0~31。 |
| 寄存器地址 (Register Address) | 5位 | PHY寄存器编号,范围也是0~31。标准寄存器就这么多。 |
| 转向位 (Turnaround) | 2位 | 读操作时,PHY先输出高阻,然后驱动数据。写操作时,MAC直接输出数据。 |
| 数据 (Data) | 16位 | 要写入或读出的寄存器值。 |
小提示:读操作时,转向位的2个时钟周期里,MAC要把MDIO从输出转为输入。很多驱动卡在这一步——忘了切换方向,读到的一直是0xFF。
3.2 如何操作PHY寄存器?
搞清楚了帧格式,读写其实就很简单了。我们一条一条说。
3.2.1 读取PHY寄存器
举个例子,我想读PHY地址0x01的寄存器0x00(也就是控制寄存器)。流程是这样的:
- MAC先发送32个1的前导码。
- 然后发送起始码01。
- 操作码发10(读)。
- PHY地址发00001(即0x01)。
- 寄存器地址发00000(即0x00)。
- 转向位:MAC输出ZZ(高阻),PHY会在第二个时钟周期开始驱动MDIO。
- PHY输出16位寄存器值。
这个过程中,MAC要精确控制MDIO的输入输出切换。我见过有的FPGA实现,直接硬编码时序,结果切换早了或晚了,读回来的数据错位。
注意:如果你用软件模拟(比如GPIO来敲MDIO),记得要把GPIO方向切换加足够的延迟。我曾经在ARM上跑Linux内核驱动,默认的udelay(1)不够,导致读回来的寄存器值偶尔跳变。
3.2.2 写入PHY寄存器
还是举例子。我要往PHY 0x01的寄存器0x09(RGMII控制/状态寄存器)写入0x1000(打开RGMII延时)。
- 前导码32个1。
- 起始码01。
- 操作码发01(写)。
- PHY地址00001。
- 寄存器地址01001(即0x09)。
- 转向位:MAC输出10。注意!写操作时,MAC直接输出这个,不需要切换方向。
- 数据位:MAC输出0x1000。
写操作相对简单,因为一直是MAC在驱动MDIO。但有个隐藏的坑——你写完值,PHY真的接受了吗?
3.2.3 验证写入
我的习惯是:写完一个寄存器后,马上读回来比对。如果读回的值不一致,大概率是PHY地址错了、写保护没关闭,或者PHY还没准备好。
写操作通常不会撞上PHY内部处理周期,但有些PHY在初始化或复位期间不响应操作。你想想看,如果刚拉完硬件复位,不到1ms就去写寄存器,PHY可能直接忽略你。
3.3 处理特殊情况
实践中会遇到一些标准之外的东西。
3.3.1 寄存器访问延迟
大多数PHY的寄存器访问是即时的,写进去立刻生效。但有些功能,比如软复位、回环模式切换,需要几个微秒甚至毫秒的稳定时间。我记得有一次配Marvell的PHY,写了一个自协商重启位,结果等了几十毫秒才完成——驱动里没有加等待,业务数据全丢了。
3.3.2 影子寄存器与分页
5位寄存器地址只能寻址32个标准寄存器。但现在的PHY功能越来越多,寄存器早就不够用了。于是各家想到的办法:
- 影子寄存器:往某个标准寄存器写一个特殊值,把其他寄存器组“映射”出来。
- 分页机制:通过一个扩展地址寄存器(比如0x1D或0x1E)指定页码,然后读写其他地址。
不同厂家方案不同,你得去翻手册。我踩过最深的坑是——把Broadcom的PHY页码寄存器搞混了,结果读到的全是0。
3.3.3 复位与默认值
每一位PHY芯片都有一堆寄存器。上电后,所有寄存器都恢复到出厂默认值。控制寄存器(地址0x00)的第15位是软复位位,写1触发复位,复位完成后硬件自动清0。这个我们在后面的RGMII延时配置中会用到。
重点:大部分PHY的0号寄存器里都有一个复位位,复位后所有寄存器回到默认状态。所以配置RGMII延时前,先确认PHY是否处于默认状态,免得你配了半天,发现还在上一次的配置里。
3.4 本章知识结构
我把这一节的核心关系整理成一张图,方便你对照理解。
这张图把整个知识体系串起来了:底层协议、帧结构、读写流程、再到实践要点。你看,从MDIO/MDC协议出发,物理层决定怎么连,帧格式决定数据长什么样,操作类型决定下一步怎么走,最后落地到实践中要避开的坑。