第3章 PHY芯片驱动:从寄存器到链路状态

PHY芯片,说白了就是以太网的物理层收发器。它负责把MAC层送来的数字信号转成能在网线上跑的模拟信号,反过来也一样。很多刚入行的朋友觉得PHY驱动就是配几个寄存器,其实没那么简单。

我个人习惯把PHY驱动分成四个模块:寄存器映射、自动协商、链路检测、复位与配置。咱们一个一个来聊。

3.1 PHY寄存器映射——标准与扩展

IEEE 802.3定义了一套标准寄存器,地址从0到31,共32个。前16个(0-15)是基本寄存器,所有PHY芯片都得支持。后16个(16-31)是扩展的,各厂家自己玩。

我刚开始做PHY驱动时,以为所有芯片都按标准来。结果换了个型号,读出来的ID全不对。后来才明白——寄存器映射虽然标准,但不同芯片的扩展寄存器差异很大。

寄存器地址 名称 功能
0 BMCR 基本控制寄存器(复位、自协商、速度、双工)
1 BMSR 基本状态寄存器(链路状态、自协商完成)
2-3 PHYIDR1/2 PHY标识符(厂家+型号)
4-5 ANAR/ANLPAR 自协商通告/对端能力
6-7 ANER/ANNPTR 自协商扩展/下一页

核心要点:读PHYIDR1和PHYIDR2是驱动初始化的第一步。如果读出来全是0或0xFFFF,说明MDIO通信有问题,或者PHY地址配错了。

3.2 自动协商机制——双方握手

自动协商,说白了就是两个PHY芯片互相喊话:「我能跑100M全双工,你呢?」「我也能,那就100M全双工吧。」

这个过程靠FLP(快速链路脉冲)完成。每个PHY把自己的能力写在ANAR寄存器里,对端收到后填到ANLPAR里。两边取交集,选最优模式。

优先级从高到低:

  1. 1000BASE-T全双工
  2. 1000BASE-T半双工
  3. 100BASE-TX全双工
  4. 100BASE-TX半双工
  5. 10BASE-T全双工
  6. 10BASE-T半双工

避坑指南:我曾经遇到一个项目,PHY死活协商不到千兆。查了两天,发现是PCB走线太长,FLP信号衰减太厉害。换了个短网线就好了。所以硬件设计时,MDI走线长度一定要控制好。

3.3 链路状态检测——轮询还是中断?

检测链路状态,通常有两种方式:

  • 轮询:定时读BMSR寄存器的bit 2(链路状态位)。简单,但浪费CPU。
  • 中断:PHY芯片支持中断引脚,链路变化时主动通知CPU。高效,但硬件要支持。

我个人习惯,在资源紧张的单片机上用轮询,周期100ms左右。在Linux等系统里用中断,配合phy_state_machine状态机。

为什么轮询周期不能太短?因为PHY寄存器读取本身就有延迟,而且链路状态变化没那么快。我曾经见过有人1ms轮询一次,结果CPU占用率飙到30%,完全没必要。

/* 轮询链路状态示例 */
int phy_check_link(struct phy_device *phydev)
{
    uint16_t bmsr;
    
    /* 读BMSR寄存器 */
    bmsr = phy_read(phydev, PHY_BMSR);
    
    /* 检查bit 2 */
    if (bmsr & PHY_BMSR_LINK_STATUS)
        return 1;  /* 链路正常 */
    else
        return 0;  /* 链路断开 */
}

3.4 PHY复位与配置——顺序很重要

PHY复位有两种:硬件复位(拉低RST引脚)和软件复位(写BMCR的bit 15)。

我建议的做法是:

  1. 先硬件复位,等待至少1ms
  2. 再软件复位,等待PHY自检完成(约50ms)
  3. 读PHYID确认通信正常
  4. 配置自协商或强制模式
  5. 等待自协商完成(约2-3秒)

注意:软件复位后,所有寄存器恢复默认值。如果你之前配过什么,复位后得重新配。我曾经犯过这个错——复位后忘了重新配置LED模式,结果指示灯全灭,还以为板子坏了。

3.5 常见PHY芯片驱动实现

3.5.1 LAN8720——低功耗小封装

LAN8720是Microchip的10/100M PHY,RMII接口,QFN封装,很适合空间受限的产品。

它的特殊寄存器:

寄存器 地址 说明
MODE_CTRL 0x1F 模式控制,可配RMII时钟源
INT_CTRL 0x1B 中断控制,使能链路变化中断
LED_CFG 0x1C LED配置,可自定义指示灯行为

驱动初始化代码:

int lan8720_init(struct phy_device *phydev)
{
    uint16_t id1, id2;
    
    /* 读PHY ID */
    id1 = phy_read(phydev, PHY_IDR1);
    id2 = phy_read(phydev, PHY_IDR2);
    
    if (id1 != 0x0007 || (id2 & 0xFFF0) != 0xC0F0) {
        printf("LAN8720 not detected!\n");
        return -1;
    }
    
    /* 软件复位 */
    phy_write(phydev, PHY_BMCR, PHY_BMCR_RESET);
    delay_ms(50);
    
    /* 使能自协商 */
    phy_write(phydev, PHY_BMCR, PHY_BMCR_ANENABLE);
    
    /* 配置LED:Link/Activity */
    phy_write(phydev, 0x1C, 0x0001);
    
    return 0;
}

3.5.2 DP83848——工业级稳定

DP83848是TI的工业级PHY,支持-40°C到85°C,MII/RMII都支持。我在一个户外设备上用过,三年没出过问题。

它的特殊寄存器:

寄存器 地址 说明
PHYSTS 0x10 详细状态,含极性、信号质量
PHYCR 0x11 PHY控制,可配BIST测试
LEDCR 0x18 LED控制,支持三色LED

DP83848有个很实用的功能——信号质量指示。读PHYSTS的bit 10-12,可以知道当前链路的信号质量。这在排查网线问题时特别有用。

/* DP83848信号质量检测 */
int dp83848_signal_quality(struct phy_device *phydev)
{
    uint16_t physts;
    
    physts = phy_read(phydev, 0x10);
    
    /* bit 10-12: SQ[2:0] */
    return (physts >> 10) & 0x07;
    /* 返回值:0=差,7=好 */
}

经验之谈:DP83848的复位时序比LAN8720要长。硬件复位后,我建议等至少10ms再操作。另外,它的LED默认配置是100M绿色、Link黄色、Activity闪烁。如果客户要改,记得在初始化时配LEDCR。

3.6 PHY驱动核心流程

下面这张图,是我自己总结的PHY驱动初始化流程。每次写新驱动,我都照着这个框架来。

PHY驱动初始化流程 硬件复位(拉低RST) 等待 ≥ 1ms 软件复位(写BMCR bit15) 等待 50ms(PHY自检) 读PHYIDR1/2确认芯片 配置自协商 / 强制模式 注意 软件复位后所有寄存器 恢复默认值,需重新配置 LED、中断等扩展功能

3.7 调试技巧与避坑

调试PHY驱动,我常用的三板斧:

  • 读ID:如果读不到正确ID,先查MDIO时钟频率。很多PHY对MDIO时钟有要求,不能超过2.5MHz。
  • 看状态:BMSR寄存器bit 2是链路状态,bit 5是自协商完成。这两个位能帮你快速定位问题。
  • 抓波形:用示波器看MDIO和MDC的波形,确认时序是否正确。我曾经遇到一个案例,MDIO上拉电阻没焊,导致通信时好时坏。

重要提醒:PHY芯片的VDDIO电压必须和MAC侧匹配。比如STM32的GPIO是3.3V,那PHY的VDDIO也得接3.3V。接错了轻则通信异常,重则烧芯片。我见过不止一次这种低级错误。

嗯,PHY驱动这块,说白了就是读读写写那几个寄存器。但每个芯片都有自己的脾气,多看看数据手册,多动手试试,慢慢就熟了。