PHY芯片根据不同的需求,会有不同的工作模式,例如需要设置其以太网通信速率为千兆、百兆、十兆或自动协商,而处理器也需要知道其连接状态,例如需要知道网络是否已经连接通,以及当前的链接速率是多少。为了实现这些功能,IEEE 在以太网标准 IEEE 802.3 中使用若干条款定义了用处理器用来管理PHY 芯片的标准控制接口——MDIO。
本章,我们将学习 MDIO 接口相关知识,并设计相应的驱动模块,通过 MDIO接口带领大家学习如何配置 PHY 芯片。
提示:以下是本篇文章正文内容,下面案例可供参考
MDIO 是一种简单的双线串行接口,将管理器件(如 MAC 控制器、微处理器)与具备管理功能的 PHY芯片相连接,从而控制收发器并从收发器收集状态信息。可收集的信息包括链接状态、传输速度与选择、断电、低功率休眠状态、TX/RX 模式选择、自动协商控制、环回模式控制等。除了拥有 IEEE 要求的功能之外,收发器厂商还可添加更多的信息收集功能。MDIO 总线包含两个信号:MDC 和 MDIO。MDC 是管理数据的时钟信号,由管理器(如处理器或 MAC)输出给 PHY 芯片,最高速率可超过 5MHz。MDIO 则是管理数据的输入输出双向接口,其中的数据与 MDC 时钟同步。和大家熟悉的 I2C 接口相似,MDIO 总线也支持多设备模式,即一个 MDIO总线上最多可连接 32 个 PHY 芯片的 MDIO 接口,通过在传输过程中的 PHY 器件地址字段传输不同的器件地址值来指定具体操作哪个 PHY 芯片。
每个 PHY 芯片都有一个硬件地址,该地址为 5 位,由固定部分和可变部分组成,可变部分可以通过物理上拉或者接地设置。若多个 PHY 芯片同时连接在同一个 MDIO 总线上,需要设置不同的 PHY 器件地址以示区分。每个 PHY 芯片中都有最多 32 个寄存器可供读写(32 个寄存器,需要 5 位寄存器地址),包括由 IEEE 802.3 规范定义的前 16 个标准功能寄存器和后续最多 16 个由 PHY 芯片厂家自定义的功能寄存器。所有 PHY 芯片的前 16 个寄存器功能定义都必须相同,后续 0~16 个寄存器的功能由 PHY 芯片厂家根据实际需求自定义。
我使用的 开发板上使用的 PHY 芯片型号为Realtek 的 RTL8211 ,通过查询手册可以知道其器件地址由固定的 00+由引脚控制的 PHYAD[2:0]组成,为00001。
介绍了 MDIO 接口之后,要想正确的使用 MDIO 接口来管理 PHY 芯片,还需要了解 MDIO 接口的时序流程。MDIO 的工作流程为:
根据协议帧的格式和上面的读写时序图,我们发现这个协议帧里面的变化内容有 OP、PYHAD、REGAD、以及传输的数据(DATA)等字段,在这里我们可以把这些作为端口,同时加入传输开始 start 和传输完成 done 等握手信号来指示当前的模块工作状态,因此设计出图所示模块单元:
模块使用状态机来描述 MDIO 接口的读写时序,对于时序图中的每个字段都采样独热码定义了一个状态,如下:
localparam IDLE = 8'h01,
PRE = 8'h02,
ST = 8'h04,
OP = 8'h08,
PHYAD = 8'h10,
REGAD = 8'h20,
TA = 8'h40,
DATA = 8'h80;
在不同状态下,让状态机按照时序图对差异部分进行描述,从而实现对phy寄存器的配置和读取,该部分代码如下:
always @ (negedge mdc or negedge rst_n)
if (!rst_n) begin
rddata <= 'd0;
mdio_oe <= 1'b0;
mdio_o <= 1'b1;
cnt <= 'd0;
done <= 1'b0;
state <= IDLE;
end else begin
case (state)
IDLE:
begin
mdio_o <= 1'b1;
mdio_oe <= 1'b0;
done <= 1'b0;
rddata <= 'd0;
if (start)
begin
cnt <= 'd0;
state <= PRE;
mdio_oe <= 1'b1;
end
end
PRE://PRE 32'hffff_ffff 32bit
begin
mdio_oe <= 1'b1;
mdio_o <= 1'b1;
cnt <= cnt + 1'b1;
if (cnt > 'd30)
begin
cnt <= 'd0;
state <= ST;
mdio_o <= 1'b0;
end
end
ST: //ST 01 2bit
begin
mdio_o <= 1'b1;
cnt <= cnt + 1'b1;
if (cnt >= 'd1)
begin
cnt <= 'd0;
state <= OP;
mdio_o <= if_read;
end
end
OP