前言
RTL8305NB是一款5口10/100M的以太网交换机芯片。在实际应用的过程中,我们可能需要获取到交换机芯片每个端口的状态,比较坑的是原厂提供的数据手册中根本没有提到任何SMI管理接口配置说明。原理图如下图所示:
RTL8305NB寄存器详解
好在PHY是 IEEE802.3 中定义的一个标准模块,而IEEE802.3 又定义了地址为0-15 这16个寄存器的为基本寄存器,也就是说不同厂商这前16个寄存器定义是一致的。
查询相关资料得知,在Status register(状态寄存器)中bit2就是我们想要的link状态:
RTL8305NB五口交换机芯片的五个端口,我们可以将其理解为拥有5个PHY,每个端口对应的这五个PHY的地址不同,读取这五个PHY的状态寄存器,即可获取到每个端口的link状态。经过测试程序遍历读取PHY地址0-8的状态寄存器,得出P0-P4的PHY地址并没有按照0-4的顺序对应。
其端口与PHY地址对应关系如下:
端口 | PHY地址 |
---|---|
P0 | 0x00 |
P1 | 0x02 |
P2 | 0x05 |
P3 | 0x06 |
P4 | 0x07 |
这也与另一篇讲RTL8305NB寄存器文章描述的对应关系一致:
https://blog.csdn.net/laoguanhua/article/details/81241289
更多寄存器定义可参考: https://blog.csdn.net/wuheshi/article/details/79085546
光口配置
RTL8305NB芯片默认是电口模式,但其实它也是支持光口模式的。前面说了寄存器地址0-15是基本寄存器,而地址16-31的寄存器则是留给芯片制造商自由定义。但现在芯片功能很强大,32个寄存器远远不够用,于是厂家就想出办法,使用page的方式来扩展。该芯片将端口切换成光口模式,就涉及到page的切换。
从上述文档可以得知,PHY地址8的寄存器31,就是Page切换的地址,往该地址写入0x8000即可切换到PHY page。
切到PHY page后,将寄存器28的bit5置高即可将端口配置为光口模式。
如下为将交换机芯片的PORT1和PORT2配置为光口的示例代码:
#define PORT1_PHY_ADDR 0x02 // SFP Port1 PHY地址
#define PORT2_PHY_ADDR 0x05 // SFP Port2 PHY地址
void switch_fiber_port_init(void)
{
unsigned short data = 0;
smi_reg_write(0x08, 0x1F, 0x8000); // 切换Page至PHY page
smi_reg_write(PORT1_PHY_ADDR, 0x1F, 0x00); // 设置Port1为光口
data = smi_reg_read(PORT1_PHY_ADDR, 0x1C); // 读出寄存器28原始状态
data |= (0x01 << 5);// 将bit5置高
smi_reg_write(PORT1_PHY_ADDR, 0x1C, data); // 将修改后的值写入寄存器
smi_reg_write(PORT2_PHY_ADDR, 0x1F, 0x00); // 设置Port2为光口
data = smi_reg_read(PORT2_PHY_ADDR, 0x1C); // 读出寄存器28原始状态
data |= (0x01 << 5);// 将bit5置高
smi_reg_write(PORT2_PHY_ADDR, 0x1C, data); // 将修改后的值写入寄存器
smi_reg_write(0x08, 0x1F, 0x0); // 切回MAC Page
}
注意: RTL8305NB虽然支持光口,但是只能支持100M光口。而市面上大多数光口交换机都是千兆起步,且存在不兼容100M光口的情况,所以在调试或实际项目使用时,需要选用支持100M光口的交换机。目前测试了三款光口交换机,支持情况如下:
品牌 | 型号 | 是否支持百兆光口 |
---|---|---|
华为数通智选 | S5735-L8T4X-A1 | 不支持 |
Flyin | FLY-IMC-2F8TG | 支持 |
Flyin | FLY-IMC-2F4TG-WG | 不支持 |
GPIO模拟SMI总线通讯示例代码
如下为GD32F4xx通过GPIO模拟MDIO/MDC与读写RTL8305NB交换机芯片寄存器示例代码,延时函数、GPIO初始化、输入输出控制需要自行修改后使用。
smi.c
示例代码:
#include <stdio.h>
#include "los_tick.h"
#include "gd32f4xx_gpio.h"
#include "rtl8305nb_smi.h"
static void MDC_OUT(void);
static void MDIO_OUT(void);
static void MDIO_IN(void);
static void MDC_H(void);
static void MDC_L(void);
static int GET_MDIO(void);
static void SET_MDIO(int val);
/* 设置MDC为输出引脚,在MDC输出时钟之前设置 */
static void MDC_OUT(void)
{
gpio_mode_set(SMI_GROUP, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, MDC_PIN);
gpio_output_options_set(SMI_GROUP, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, MDC_PIN);
}
/* 设置MDIO的gpio引脚为输出引脚 */
static void MDIO_OUT(void)
{
gpio_mode_set(SMI_GROUP, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, MDIO_PIN);
gpio_output_options_set(SMI_GROUP, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, MDIO_PIN);
}
/* 设置MDIO的gpio引脚为输入引脚 */
static void MDIO_IN(void)
{
gpio_mode_set(SMI_GROUP, GPIO_MODE_INPUT, GPIO_PUPD_NONE, MDIO_PIN);
}
/* MDC输出高电平,在MDC设置为输出后调用 */
static void MDC_H(void)
{
gpio_bit_set(SMI_GROUP, MDC_PIN);
}
/* MDC输出低电平,在MDC设置为输出后调用 */
static void MDC_L(void)
{
gpio_bit_reset(SMI_GROUP, MDC_PIN);
}
/* 获得MDIO的数据,只获得一个bit */
static int GET_MDIO(void)
{
return gpio_input_bit_get(SMI_GROUP, MDIO_PIN);
}
/* 设置MDIO的数据,一个bit */
static void SET_MDIO(int val)
{
if (val != 0) {
gpio_bit_set(SMI_GROUP, MDIO_PIN);
} else {
gpio_bit_reset(SMI_GROUP, MDIO_PIN);
}
}
/* MDIO发送一个bit的数据,MDIO必须已经被配置为输出 */
static void mdio_bb_send_bit(int val)
{
MDC_OUT();
SET_MDIO(val);
LOS_UDelay(MDIO_DELAY);
MDC_H();
LOS_UDelay(MDIO_DELAY);
MDC_L();
// LOS_UDelay(MDIO_DELAY);
}
/* MDIO 获取一个bit的数据,MDIO必须已经被配置为输入. */
static int mdio_bb_get_bit(void)
{
int value;
MDC_OUT();
LOS_UDelay(MDIO_DELAY);
MDC_H();
LOS_UDelay(MDIO_READ_DELAY);
value = GET_MDIO();
// LOS_UDelay(MDIO_DELAY);
MDC_L();
return value;
}
/*
* MDIO发送一个数据,MDIO 必须被配置为输出模式.
* value:要发送的数据
* bits:数据的位数
*
* */
static void mdio_bb_send_num(unsigned int value, int bits)
{
int i;
MDIO_OUT();
for (i = bits - 1; i >= 0; i--)
mdio_bb_send_bit((value >> i) & 1);
}
/*
* MDIO获取一个数据,MDIO 必须被配置为输入模式.
* bits:获取数据的位数
*
* */
static int mdio_bb_get_num(int bits)
{
int i;
int ret = 0;
for (i = bits - 1; i >= 0; i--) {
ret <<= 1;
ret |= mdio_bb_get_bit();
}
return ret;
}
/* Utility to send the preamble, address, and
* register (common to read and write).
*/
static void mdio_bb_cmd(int op, int phy, int reg)
{
int i = 0;
MDIO_OUT(); //设置MDIO引脚为输出引脚
/*发送32bit的1,这个帧前缀域不是必须的,某些物理层芯片的MDIO操作就没有这个域*/
for (i = 0; i < 32; i++)
mdio_bb_send_bit(1);
/* 发送开始位(01),和读操作码(10),写操作码(01)
* Clause 45 操作,开始位是(00),(11)为读,(10)为写
*/
#if MDIO_C45_TEST
mdio_bb_send_bit(0);
if (op & MDIO_C45)
mdio_bb_send_bit(0);
else
mdio_bb_send_bit(1);
#else
mdio_bb_send_bit(0);
mdio_bb_send_bit(1);
#endif
mdio_bb_send_bit((op >> 1) & 1);
mdio_bb_send_bit((op >> 0) & 1);
mdio_bb_send_num(phy, 5);
mdio_bb_send_num(reg, 5);
}
static int mdio_bb_cmd_addr(int phy, int addr)
{
unsigned int dev_addr = (addr >> 16) & 0x1F;
unsigned int reg = addr & 0xFFFF;
mdio_bb_cmd(MDIO_C45_ADDR, phy, dev_addr);
/* send the turnaround (10) */
mdio_bb_send_bit(1);
mdio_bb_send_bit(0);
mdio_bb_send_num(reg, 16);
MDIO_IN();
mdio_bb_get_bit();
return dev_addr;
}
void mdio_set_turnaround(void)
{
int i = 0;
MDIO_IN();
MDC_OUT();
for (i = 0; i < 1; i++) {
LOS_UDelay(MDIO_DELAY);
MDC_H();
LOS_UDelay(MDIO_DELAY);
MDC_L();
}
}
unsigned int smi_reg_read(int phy, int reg)
{
unsigned int ret, i;
#if MDIO_C45_TEST
/* 寄存器是否满足有C45标志 */
if (reg & MII_ADDR_C45) {
reg = mdio_bb_cmd_addr(phy, reg);
mdio_bb_cmd(MDIO_C45_READ, phy, reg);
} else
mdio_bb_cmd(MDIO_READ, phy, reg);
#else
mdio_bb_cmd(MDIO_READ, phy, reg);
#endif
MDIO_IN();
// mdio_set_turnaround();
#if 1
/* check the turnaround bit: the PHY should be driving it to zero */
if (mdio_bb_get_bit() != 0) {
/* PHY didn\'t driver TA low -- flush any bits it may be trying to send*/
for (i = 0; i < 32; i++)
mdio_bb_get_bit();
// bios_log("PHY didn\'t driver TA low! \r\n");
return 0xFFFF;
}
#endif
ret = mdio_bb_get_num(16);
mdio_bb_get_bit();
return ret;
}
int smi_reg_write(unsigned int phy, unsigned int reg, unsigned int val)
{
#if MDIO_C45_TEST
if (reg & MII_ADDR_C45) {
reg = mdio_bb_cmd_addr(phy, reg);
mdio_bb_cmd(MDIO_C45_WRITE, phy, reg);
} else
mdio_bb_cmd(MDIO_WRITE, phy, reg);
#else
mdio_bb_cmd(MDIO_WRITE, phy, reg);
#endif
#if 1
/* send the turnaround (10) */
mdio_bb_send_bit(1);
mdio_bb_send_bit(0);
#else
mdio_set_turnaround();
#endif
mdio_bb_send_num(val, 16);
MDIO_IN();
// mdio_bb_get_bit();
return 0;
}
int smi_init(void)
{
gpio_mode_set(SMI_GROUP, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, MDC_PIN);
gpio_output_options_set(SMI_GROUP, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, MDC_PIN);
gpio_mode_set(SMI_GROUP, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, MDIO_PIN);
gpio_output_options_set(SMI_GROUP, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, MDIO_PIN);
gpio_mode_set(PORT_LED_GROUP, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, PORT1_LED);
gpio_output_options_set(PORT_LED_GROUP, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, PORT1_LED);
gpio_mode_set(PORT_LED_GROUP, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, PORT2_LED);
gpio_output_options_set(PORT_LED_GROUP, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, PORT2_LED);
gpio_mode_set(GPIOG, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_15);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_15);
}
smi.h
示例代码:
#ifndef __SMI_H__
#define __SMI_H__
#define PORT1_PHY_ADDR 0x02 // SFP Port1 PHY地址
#define PORT2_PHY_ADDR 0x05 // SFP Port2 PHY地址
#define STATUS_REG_ADDR 0x01 // 状态寄存器地址
#define SMI_GROUP GPIOG
#define MDC_PIN GPIO_PIN_9
#define MDIO_PIN GPIO_PIN_10
#define PORT_LED_GROUP GPIOD
#define PORT1_LED GPIO_PIN_0
#define PORT2_LED GPIO_PIN_11
#define MDIO_DELAY 10 // us
#define MDIO_READ_DELAY 10 // us
/* Or MII_ADDR_C45 into regnum for read/write on mii_bus to enable the 21 bit
* IEEE 802.3ae clause 45 addressing mode used by 10GIGE phy chips.
*/
#define MII_ADDR_C45 (1 << 30)
#define MDIO_READ 2
#define MDIO_WRITE 1
#define MDIO_C45 (1 << 15)
#define MDIO_C45_ADDR (MDIO_C45 | 0)
#define MDIO_C45_READ (MDIO_C45 | 3)
#define MDIO_C45_WRITE (MDIO_C45 | 1)
#define MDIO_C45_TEST 1
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
int smi_init(void);
unsigned int smi_reg_read(int phy, int reg);
int smi_reg_write(unsigned int phy, unsigned int reg, unsigned int val);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __SMI_H__ */