4. 常见PHY寄存器:控制寄存器、状态寄存器、厂商ID寄存器

拿到一颗PHY芯片,第一件事是什么?我的习惯是先把它的核心寄存器摸清楚。说白了,这就像开车前要看仪表盘——你得知道油门、刹车在哪,水温、转速怎么看。

MDIO能访问的PHY寄存器很多,但最常用的就那十几个。今天咱们重点讲三个:控制寄存器、状态寄存器、厂商ID寄存器。这三个摸透了,80%的调试场景你都能应付。

常见PHY寄存器知识结构 PHY 常见寄存器 控制寄存器 (Reg 0) 状态寄存器 (Reg 1) 厂商ID寄存器 (Reg 2/3) 复位 (Bit 15) 自协商使能 (Bit 12) 速度选择 (Bit 13/6) 全双工 (Bit 8) 链接状态 (Bit 2) 自协商完成 (Bit 5) 远端错误 (Bit 3) 能力集 (Bit 0-1, 6-7) OUI (22位) 型号编号 (6位) 版本号 (4位) 说明:这三个寄存器是MDIO调试的起点, 能帮你快速定位80%的PHY链路问题

4.1 控制寄存器(Reg 0)——PHY的"油门"

控制寄存器的地址是0x00。它的核心功能就是你让PHY干什么活。你可能要问了:一个16位的寄存器,能干多少事?

其实不多,但每一件都很关键。我遇到过不少工程师,PHY不通了就去怀疑硬件布线,结果一看控制寄存器——自协商根本没打开。这就像你踩了油门才发现忘了松手刹。

控制寄存器关键的几个位:

Bit位 名称 说明
15 Reset 软件复位PHY。写1触发复位,复位后自动清零
14 Loopback 环回模式。调试用的,数据发出去绕回来
13 Speed Selection (MSB) 速度选择高位,与Bit6配合:00=10M, 01=100M, 10=1000M
12 Auto-Negotiation Enable 自协商使能。我建议你平时都开着
11 Power Down 掉电模式。写1进低功耗,写0唤醒
10 Isolate 隔离模式,断开MII/RMII接口
9 Restart Auto-Negotiation 重启自协商。如果你想强制重新握手,就置1
8 Duplex Mode 双工模式:0=半双工,1=全双工
6 Speed Selection (LSB) 速度选择低位,与Bit13配合
我常用的调试技巧: 快速检查链路是否正常时,先读Reg 0确认自协商使能位(Bit 12)是否为1。如果不是,直接写0x1200进去——打开自协商并选择100M全双工。要是还不通,再去查硬件。

控制寄存器典型的操作场景:

  1. 复位PHY:写0x8000到Reg 0,等100ms再读,如果Bit 15变回0表示复位完成。
  2. 强制设定速度:比如强制100M全双工,写0x2100(自协商关闭 + 100M + 全双工)。
  3. 环回测试:写0x4000,数据发出去在内部绕回,不用连网线就能测。

我做项目时踩过的坑:

记得有一次调试交换机的PHY,死活连不通。反复检查硬件、时钟、复位,都没问题。后来我读了Reg 0,发现Bit 10 (Isolate)被置1了——原来是上电时序不对,导致PHY进入了隔离模式。写0清掉,立刻通了。

4.2 状态寄存器(Reg 1)——PHY的"仪表盘"

状态寄存器地址是0x01。它能告诉你PHY当前的状态——类似、仪表盘上的转速表和故障灯。你不需要去猜链路到底好没好,直接读这个寄存器就清楚了。

状态寄存器关键的几个位:

Bit位 名称 说明
5 Auto-Negotiation Complete 自协商是否完成。1表示协商成功,0表示还在协商或失败
3 Remote Fault 远端错误。对面出问题了,这位置1
2 Link Status 链接状态。1=链路已建立,0=断开
1 Jabber Detect 超长帧检测。不太常用,但出现时说明链路异常
0 Extended Capability 扩展能力。1表示支持扩展寄存器,一般都为1

特别注意: 状态寄存器Bit 2 (Link Status)是一个锁存位。一旦链路断开,它就会变成0并且保持——即使链路后来恢复了,你也要重新读一次才会更新。我见过有人调试时看到Link Status为0就以为网线坏了,其实是因为没做二次读取。

我曾经踩过的坑: 有一次现场调试,客户说设备经常掉线。我远程读了Reg 1的Bit 2,一直显示1——链路正常。但客户说设备确实会断。后来我做了个脚本连续读Reg 1,才发现Bit 2其实偶尔会跳0然后又恢复。再一看Bit 3 (Remote Fault),竟然也是1。最后查出是对端的PHY电源不稳,导致间歇性复位。所以你看,单读一次状态寄存器是不够的,要做持续监控。

状态寄存器怎么用才到位?

  • 链路不通? 先读Reg 1,看Bit 2是否为1。如果为0,检查网线和对端。
  • 自协商没结果? 看Bit 5。如果长时间为0,可能是对端不支持自协商,需要手动设置速度。
  • 偶尔断链? 写一个循环,每秒读一次Reg 1,连续读100次。看看Bit 2有没有抖动。

4.3 厂商ID寄存器(Reg 2 & Reg 3)——PHY的"身份证"

厂商ID寄存器占两个地址:Reg 2是厂商ID的高16位,Reg 3是厂商ID的低16位。合起来是32位。

这里面包含三个信息:

  • OUI (Organizationally Unique Identifier):24位,但实际只用了22位。IEEE分配给厂商的唯一标识。
  • 型号编号:6位,厂商定义的具体芯片型号。
  • 版本号:4位,芯片的修订版本。

32位ID的结构:

寄存器 Bit位 内容 说明
Reg 2 15:0 OUI 高16位 厂商唯一标识的高16位
Reg 3 15:10 OUI 低6位 OUI的22位中的后6位
Reg 3 9:4 型号编号 6位,比如RTL8211、LAN8720各有编号
Reg 3 3:0 版本号 4位,用来区分同型号的不同批次

这个寄存器有什么实际用途?

假设你要换一款PHY芯片,但不确定驱动支不支持。先读Reg 2和Reg 3,拿到厂商ID。然后去网上查一下,就知道这颗芯片的兼容性了。

举个例子: 我调试过一款工控板,读到的Reg 2=0x001C, Reg 3=0xC915。查出来是博通BCM5461的ID,于是直接套用了已知的驱动配置,半小时就调通了。

常用的厂商ID速查表(我整理过,分享给你):

厂商 OUI高16位 (Reg 2) 常见型号 低16位 (Reg 3) 包含型号
Broadcom (博通) 0x001c BCM5461 0xC915 (编号0x24, 版本1)
Micrel (现Microchip) 0x0022 KSZ9031 0x1620 (编号0x05, 版本0)
Realtek (瑞昱) 0x001c RTL8211 0xC832 (编号0x20, 版本2)
National Semiconductor 0x0800 DP83848 0x1781 (编号0x05, 版本1)
Marvell 0x0141 88E1512 0x0DD0 (编号0x37, 版本0)

我个人的做法: 拿到一块新板子,我会写一个小脚本,循环扫描PHY地址0~31,每个地址读Reg 2和Reg 3。然后打印出所有能找到的PHY及其厂商ID。这样一次就能知道板子上有哪些PHY、各是什么型号——再也不需要去翻原理图了。

读取厂商ID的伪代码:

// 假设我们有读MDIO寄存器的函数
// mdio_read(phy_addr, reg_addr) 返回16位值

uint16_t reg2 = mdio_read(phy_addr, 2);
uint16_t reg3 = mdio_read(phy_addr, 3);

// 拼装32位ID
uint32_t phy_id = ((uint32_t)reg2 << 16) | reg3;

// 提取OUI (22位)
uint32_t oui = (reg2 << 6) | (reg3 >> 10);

// 提取型号编号 (6位)
uint8_t model = (reg3 >> 4) & 0x3F;

// 提取版本号 (4位)
uint8_t revision = reg3 & 0x0F;

printf("PHY ID: 0x%08X\n", phy_id);
printf("OUI: 0x%06X, Model: 0x%02X, Rev: 0x%01X\n", oui, model, revision);

你看,其实三个寄存器加起来也就48位。但它们涵盖了PHY的基本控制、状态反馈、身份识别——就像一个人的姓名、健康状况、身份证号。调试的时候,先读这三个寄存器,就能对PHY的状况有个基本判断。

我记得有句话说得好:调试不是靠猜,是靠读数。控制寄存器告诉你该做什么,状态寄存器告诉你现在怎么样,厂商ID寄存器告诉你面前的是谁。三者配合,调试效率至少提高一倍。