本文转自公众号欢迎关注
以太网驱动的编写与调试往往从MDIO接口开始,MDIO是MAC访问PHY的接口。实现通过MDIO对PHY进行操作才能配置PHY,所以实现MDIO读写是第一步。DWC_ether_qos提供了SMA模块,操作两个寄存器即可实现PHY寄存器的读写,比较简单方便,且支持C45和C22两种模式,另外有比较灵活的配置参数后面会详讲。
DWC_ether_qos中SMA(Station Management Agent)是一种双线站管理接口(MIM:Station Management interface),即MDIO管理接口,通过SMA模块即MDIO接口可以访问PHY的寄存器。
其具备以下特征:
l时钟
IEEE 802.3中规定,MDC的最大操作频率(gmi_MDC_o)是2.5MHz。在DWC_ether_qos中,gmii_mdc_o时钟是从csr_i分频得到的,通过MAC_MDIO_Address寄存器中的CR字段配置,
要注意该分频值一般要使得MDC满足不大于2.5MHz的要求。如果系统支持更高的频率该分频也可以分频到大于2.5MHz的频率。
l模式选择:MAC_MDIO_Address 的C45E 配置使用C45还是C22格式。
l前导抑制:MAC_MDIO_Address的PSE控制是否发送只有一个bit的前导的帧。
l时钟保持与连续传输:MAC_MDIO_Address 的NTC可以配置MDIO帧发送完后时钟保持07个CLK。在此基础上还可以配置MAC_MDIO_Address 的BTB使能连续传输,此时不等07个时钟保持结束,Busy信号就会发送完帧后立即清除,软件可以开始下一次传输,否则需要等到配置的0~7个保持时钟之后Busy才会清除。BTB必须在NTC大于0时才能使能。
MDIO帧的一些特征:
MDC一般是1MHz~2.5MHz。
MDIO引脚需要一个1.5k欧姆的上拉电阻,以在空闲和周转期间保持MDIO高电平。
MDC下降沿修改数据,上升沿锁存数据,这样使得数据的建立和保持时间都是半个周期。
高位先发送,高字节先发送。
参考
IEEE 802.3,Section 22.2.4.5
SMA支持在该模式下操作时的读后递增地址。
MAC_MDIO_Address 的C45E 配置为1使用C45.
字段 | 描述 |
---|---|
IDLE | gmii_mdc_o无时钟,MDIO处于高阻态。通过外部MDIO上拉到高电平 |
PREAMBLE | 32 个连续的1,前导抑制模式则只有一个bit。 |
START | 包开始2’b00 |
OPCODE | 操作码■ 2’b00 读写地址寄存器■ 2’b01 读写数据, 读写完地址不递增■ 2’b10 读写数据,读写完地址自动递增■ 2’b11 读写数据,仅写完地址自动递增 |
PHY ADDR | 5-bit 的PHY地址,可以编码最多32 个PHY。 |
DEV ADDR | 5-bit 设备地址可以编码32个设备。 |
TA | Turnaround寄存器地址和帧的数据字段之间的2位时间间隔,以避免在读取事务期间发生争用。■ 2’bZ0: 读时 第一个bit MAC和PHY都处于高组态,第二个bit是PHY拉低。■ 2’b10: 写时 10都是MAC驱动。Z 表示三态 |
DATA/ADDRESS | 16-bit 值: 对于地址周期(OPCODE = 2'b00), 表示下一个周期需要访问的寄存器地址. 对于数据写周期,表示需要写入寄存器的数据内容. 对于read,post-read递增帧,表示从PHY读出的寄存器内容. |
完成一次C45寄存器读写需要两步,第一步发送地址包,第二步再读写
MAC_MDIO_Address 的C45E 配置为0使用C22.
帧结构如下
字段 | 描述 |
---|---|
IDLE | gmii_mdc_o无时钟,MDIO处于高阻态。通过外部MDIO上拉到高电平。 |
PREAMBLE | 32 个连续的1,前导抑制模式则只有一个bit。 |
START | 包开始2’b01 |
OPCODE | ■ 2’b01 写■ 2’b10 读 |
PHY ADDR | 5-bit 的PHY地址,可以编码最多32 个PHY。 |
REG ADDR | 5-bit 寄存器地址可以编码32个寄存器。寻址对应指定MMD的某一个寄存器。 |
TA | Turnaround寄存器地址和帧的数据字段之间的2位时间间隔,以避免在读取事务期间发生争用。■ 2’bZ0: 读时 第一个bit MAC和PHY都处于高组态,第二个bit是PHY拉低。■ 2’b10: 写时 10都是MAC驱动。Z 表示三态 |
DATA | 16-bit 值:■ 写时DWC_ether_qos 驱动MDIO■ 读时PHY 驱动MDIO |
完成C22读写只需要一步
读写波形如下
使用C22访问C45寄存器
参考https://www.ieee802.org/3/efm/public/nov02/oam/pannell_oam_1_1102.pdf
用13号寄存器作为命令寄存器,14号寄存器作为寄存器/数据寄存器
步骤如下,分为4步完成:
1.C22模式写寄存器13,高两位FN为00,低5位DEV设备地址。
2.C22模式将16位寄存器地址写入14号寄存器。
3.C22模式写寄存器13,高两位FN为01或者10或者11,低5位DEV设备地址。
4.读:转至步骤5。写:转至步骤6
5.C22模式读14号寄存器的16位内容。
6.C22模式将16位寄存器内容写入14号寄存器。
MDIO操作仅需要两个寄存器
MAC_MDIO_Address和MAC_MDIO_Data
Offset: 0x200
PSE[27]: bit27,置位则只发送1bit的前导,否则发送32位的前导
BTB **[26]: **bit26,NTC 不为0时生效,NTC为0时不能设置为1.
设置为1时,NTC不为0时,MDIO发送完帧不等NTC个时钟就立即清除GB位,可以立即重新下一次读写。
设置为0,则等待NTC保持时钟之后GB才清除。
**PA[25:21] **5位的PHY地址
RDA[22:16] 对于C22模式的寄存器地址,C45的设备地址MMD。
NTC[14:12] MDIO帧的发送完后,MDC再保持该配置个时钟。
CR[11:8]:MDC时钟选择 一般配置为1~2.5MHz
0000: CSR clock = 60-100 MHz; MDC clock = CSR clock/42
0001: CSR clock = 100-150 MHz; MDC clock = CSR clock/62
0010: CSR clock = 20-35 MHz; MDC clock = CSR clock/16
0011: CSR clock = 35-60 MHz; MDC clock = CSR clock/26
0100: CSR clock = 150-250 MHz; MDC clock = CSR clock/102
0101: CSR clock = 250-300 MHz; MDC clock = CSR clock/124
0110, 0111: Reserved
1000: CSR clock/4
1001: CSR clock/6
1010: CSR clock/8
1011: CSR clock/10
1100: CSR clock/12
1101: CSR clock/14
1110: CSR clock/16
1111: CSR clock/18
SKAP[4]:只有C45E使能时才有,置位时不发送地址包
GOC_1_0[3:2]:
00: Reserved
01: Write
10: Post Read Increment Address for Clause 45 PHY
11: Read
C45E[1]: 使能C45模式
GB[0]: 软件写1触发读写操作,读写完硬件清零。
Offset: 0x204
RA[31:16]:C45E模式时指定16位寄存器地址。
GD[15:0]:读出或者写入寄存器的16位值。
实现基本的寄存器级别的操作接口,
后面再对关键参数MDC时钟,NTC,PSE等测试,抓取波形进行验证。
/*
* GB_MASK bit0 置位,触发MDIO操作, 操作完GB_MASK位硬件清零
*/
GMAC_INLINE int gmac_cfg_mdio_address(uint8_t id, uint8_t pse, uint8_t btb, uint8_t phyadd,
uint8_t reg_dev, uint8_t ntc, uint32_t csrclk,
uint8_t skap, uint8_t cmd, uint8_t c45)
{
(void)id;
uint32_t tmp = 0;
uint8_t csr = GMAC_CLK_60_100MHZ;
if(csrclk >= 250000000ul)
{
csr = GMAC_CLK_250_300MHZ;
}
else if(csrclk >= 150000000ul)
{
csr = GMAC_CLK_150_250MHZ;
}
else if(csrclk >= 100000000ul)
{
csr = GMAC_CLK_100_150MHZ;
}
else if(csrclk >= 60000000ul)
{
csr = GMAC_CLK_60_100MHZ;
}
else if(csrclk >= 35000000ul)
{
csr = GMAC_CLK_35_60MHZ;
}
else
{
csr = GMAC_CLK_20_35MHZ;
}
tmp = ((uint32_t)csr < < CR_OFFSET)
| ((uint32_t)cmd < < GOC_0_OFFSET)
| (uint32_t)GB_MASK
| (((uint32_t)reg_dev < < RDA_OFFSET) & RDA_MASK)
| (((uint32_t)phyadd < < PA_OFFSET) & PA_MASK)
| (((uint32_t)pse < < PSE_OFFSET) & PSE_MASK)
| (((uint32_t)btb < < BTB_OFFSET) & BTB_MASK)
| (((uint32_t)ntc < < NTC_OFFSET) & NTC_MASK)
| (((uint32_t)skap < < SKAP_OFFSET) & SKAP_MASK)
| (((uint32_t)c45 < < C45E_OFFSET) & C45E_MASK);
GMAC_WRITE_REG(CFG_MAC_MDIO_ADDRESS_ADDR, tmp);
return 0;
}
GMAC_INLINE int gmac_get_mdio_data(uint8_t id, uint16_t* data, uint16_t* regaddr)
{
(void)id;
uint32_t tmp = 0;
tmp = GMAC_READ_REG(CFG_MAC_MDIO_DATA_ADDR);
if(data != (void*)0)
{
*data = (tmp & GD_MASK) > > GD_OFFSET;
}
if(regaddr != (void*)0)
{
*regaddr = (tmp & REG_ADDR_MASK) > > REG_ADDR_OFFSET;
}
return 0;
}
GMAC_INLINE int gmac_set_mdio_data(uint8_t id, uint16_t data, uint16_t regaddr)
{
(void)id;
uint32_t tmp = 0;
tmp = (((uint32_t)regaddr<
我这里时钟是60MHz,设置CR为0,即分频42,理论上60/42=1.429MHz
实测MDC频率为1.449MHz
设置CR为3,即分频26,理论上60/26=2.31MHz
实测MDC频率为2.326MHz
NTC=0,帧发送完后MDC立即结束
NTC=7,帧发送完后MDC保持7个CLK
PSE=0,发送32个前导1
PSE=1,发送1个前导1,可以看到这个PHY是不支持前导抑制的,即发送1个前导时不能读写。
驱动代码如下
int iot_gmac_mdio_read(uint8_t id, uint8_t phyadd, uint8_t reg, uint16_t *data, uint32_t clk)
{
int timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
gmac_cfg_mdio_address(id, 0, 0, phyadd, reg, 0, clk, 0, GMAC_CMD_PHY_RD, 0);
timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
gmac_get_mdio_data(id, data, (void*)0);
return 0;
}
int iot_gmac_mdio_write(uint8_t id, uint8_t phyadd, uint8_t reg, uint16_t data, uint32_t clk)
{
int timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
gmac_set_mdio_data(id, data, 0);
gmac_cfg_mdio_address(id, 0, 0, phyadd, reg, 0, clk, 0, GMAC_CMD_PHY_WR, 0);
timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
return 0;
}
写
如下是往0号寄存器写0x9040即Reset软复位PHY
读
如下是读0号寄存器为0x1040即Reset软复位PHY完成,硬件自清零了Reset位。
驱动代码如下
int iot_gmac_mdio_readc45(uint8_t id, uint8_t phyadd, uint8_t dev, uint16_t reg, uint16_t *data, uint32_t clk)
{
int timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
gmac_set_mdio_data(id, 0, reg);
gmac_cfg_mdio_address(id, 0, 0, phyadd, dev, 0, clk, 0, GMAC_CMD_PHY_RD, 1);
timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
gmac_get_mdio_data(id, data, (void*)0);
return 0;
}
int iot_gmac_mdio_writec45(uint8_t id, uint8_t phyadd, uint8_t dev, uint16_t reg, uint16_t data, uint32_t clk)
{
int timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
gmac_set_mdio_data(id, data, reg);
gmac_cfg_mdio_address(id, 0, 0, phyadd, dev, 0, clk, 0, GMAC_CMD_PHY_WR, 1);
timeout = IOT_GMAC_MDIO_TIMEOUT;
while(gmac_is_mdio_busy(id) && (timeout-- > 0));
if(timeout <= 0)
{
return -1;
}
return 0;
}
波形如下:两个包,一个是地址包,一个是数据包
读
写
以下是RTL8211F的MMD寄存器,不支持直接C45模式,但是支持C22模式读C45寄存器,
通过13和14号寄存器实现。
驱动代码如下
int dwc_mdio_c22readc45(uint8_t id, uint8_t phyadd, uint8_t dev, uint16_t reg, uint16_t *data, uint32_t clk)
{
iot_gmac_mdio_write(id, phyadd, 13, (0< < 14) | dev, clk);
iot_gmac_mdio_write(id, phyadd, 14, reg, clk);
iot_gmac_mdio_write(id, phyadd, 13, (1u< < 14) | dev, clk);
iot_gmac_mdio_read(id, phyadd, 14, data, clk);
return 0;
}
int dwc_mdio_c22writec45(uint8_t id, uint8_t phyadd, uint8_t dev, uint16_t reg, uint16_t data, uint32_t clk)
{
iot_gmac_mdio_write(id, phyadd, 13, (0< < 14) | dev, clk);
iot_gmac_mdio_write(id, phyadd, 14, reg, clk);
iot_gmac_mdio_write(id, phyadd, 13, (1u< < 14) | dev, clk);
iot_gmac_mdio_write(id, phyadd, 14, data, clk);
return 0;
}
测试代码如下
uint16_t c45reg = 0;
dwc_mdio_c22writec45(p_ctrl.unit,p_ctrl.phy_addr,3,0,1u< < 10,p_ctrl.clk);
dwc_mdio_c22readc45(p_ctrl.unit,p_ctrl.phy_addr,3,0,&c45reg,p_ctrl.clk);
printf("PC1R=%xrn",c45reg);
c45reg = 0;
dwc_mdio_c22readc45(p_ctrl.unit,p_ctrl.phy_addr,3,1,&c45reg,p_ctrl.clk);
printf("PS1R=%xrn",c45reg);
c45reg = 0;
dwc_mdio_c22readc45(p_ctrl.unit,p_ctrl.phy_addr,3,20,&c45reg,p_ctrl.clk);
printf("EEECR=%xrn",c45reg);
c45reg = 0;
dwc_mdio_c22readc45(p_ctrl.unit,p_ctrl.phy_addr,3,22,&c45reg,p_ctrl.clk);
printf("EEEWER=%xrn",c45reg);
c45reg = 0;
dwc_mdio_c22readc45(p_ctrl.unit,p_ctrl.phy_addr,7,60,&c45reg,p_ctrl.clk);
printf("EEEAR=%xrn",c45reg);
c45reg = 0;
dwc_mdio_c22readc45(p_ctrl.unit,p_ctrl.phy_addr,7,61,&c45reg,p_ctrl.clk);
printf("EEELPAR=%xrn",c45reg);
打印结果如下
可以看到PC1R的bit10设置为了1,其他寄存器对照手册可以看到都是默认值
波形如下
写4步完成
读4步完成
以太网驱动编写调试的第一步都是调通MDIO接口,重点是要去抓取信号分析实际的波形,比如寄存器的每一个参数对应波形的什么变化,这在驱动编写调试中很重要,一定要对寄存器每一个bit的功能都非常清晰。
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !