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(也就是控制寄存器)。流程是这样的:

  1. MAC先发送32个1的前导码。
  2. 然后发送起始码01。
  3. 操作码发10(读)。
  4. PHY地址发00001(即0x01)。
  5. 寄存器地址发00000(即0x00)。
  6. 转向位:MAC输出ZZ(高阻),PHY会在第二个时钟周期开始驱动MDIO。
  7. PHY输出16位寄存器值。

这个过程中,MAC要精确控制MDIO的输入输出切换。我见过有的FPGA实现,直接硬编码时序,结果切换早了或晚了,读回来的数据错位。

注意:如果你用软件模拟(比如GPIO来敲MDIO),记得要把GPIO方向切换加足够的延迟。我曾经在ARM上跑Linux内核驱动,默认的udelay(1)不够,导致读回来的寄存器值偶尔跳变。

3.2.2 写入PHY寄存器

还是举例子。我要往PHY 0x01的寄存器0x09(RGMII控制/状态寄存器)写入0x1000(打开RGMII延时)。

  1. 前导码32个1。
  2. 起始码01。
  3. 操作码发01(写)。
  4. PHY地址00001。
  5. 寄存器地址01001(即0x09)。
  6. 转向位:MAC输出10。注意!写操作时,MAC直接输出这个,不需要切换方向。
  7. 数据位: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协议与PHY寄存器读写知识结构 MDIO/MDC协议 物理层:MDC(时钟) + MDIO(数据) MAC主,PHY从 帧格式: 32位 前导+起始+操作+地址+数据 操作类型 读(10) / 写(01) 读取流程 发命令 → 切换方向 → 收数据 写入流程 发命令 → 发数据 → 验证(读回比对) 实践要点 ① 确认PHY地址 ② 检查写保护 ③ 写后读回验证 ④ 留意复位与影子寄存器

这张图把整个知识体系串起来了:底层协议、帧结构、读写流程、再到实践要点。你看,从MDIO/MDC协议出发,物理层决定怎么连,帧格式决定数据长什么样,操作类型决定下一步怎么走,最后落地到实践中要避开的坑。