PHY芯片 AR8033 学习笔记

【芯片简介】

    AR8033是Atheros公司的第4代10/100/1000Mbps速率以太网PHY芯片,可用于家庭网关、企业交换机、移动基站、光模块等设备。该芯片采用RGMII协议或SGMII协议与MAC芯片进行通信,只需要单一3.3V电源供电,可自行整流变换出片内电路所需其它电源。AR8033还内置了一个工作频率为1.25GHz的SerDes接口,可以直接与光纤收发模块连接,将光信号转换为适用于1000BASE-X/100 BASE-FX传输模式的电信号或用于与MAC芯片通信的SGMII协议电信号。

PHY芯片在OSI协议栈中属于最底层的物理层,与其它层的关系图如下:

PHY芯片 AR8033 学习笔记_第1张图片

图1 PHY芯片的工作位置处于OSI底层

    从硬件上来说,一般PHY芯片为模数混合电路,负责接收电、光这类模拟信号,经过解调和A/D转换后通过MII接口将信号交给MAC芯片进行处理。一般MAC芯片为纯数字电路。

 

 

【SerDes接口】

    SerDes是SERializer(串行器)/DESerializer(解串器)的简称。它是一种主流的时分多路复用(TDM)、点对点(P2P)的串行通信技术,即在发送端多路低速并行信号被转换成高速串行信号,经过传输媒体(光缆或铜线),最后在接收端高速串行信号重新转换成低速并行信号。

 

 

【RGMII接口】

    RGMII即Reduced GMII,是GMII的简化版本。它的接口信号线数量为14根(COL/CRS端口状态指示信号,这里没有画出),时钟频率为125MHz,TX/RX数据宽度为4位,为了保持1000Mbps的传输速率不变,RGMII接口在时钟的上升沿和下降沿都采样数据。在参考时钟的上升沿发送GMII接口中的TXD[3:0]/RXD[3:0],在参考时钟的下降沿发送GMII接口中的TXD[7:4]/RXD[7:4]。RGMII兼容100Mbps和10Mbps两种速率,此时参考时钟速率分别为25MHz和2.5MHz。

PHY芯片 AR8033 学习笔记_第2张图片

图2 RGMII接口

    TX_EN信号线上传送TX_EN和TX_ER两种信息,在TX_CLK的上升沿发送TX_EN,下降沿发送TX_ER;同样的,RX_DV信号线上也传送RX_DV和RX_ER两种信息,在RX_CLK的上升沿发送RX_DV,下降沿发送RX_ER。

 

 

【SGMII接口】

    SGMII即Serial GMII,是PHY与MAC之间的接口,时钟频率625MHz,收发各一对差分信号线,所以总的数据速率为1.25Gbps = 625Mbps* 2。GMII和RGMII都是并行的,而且需要随路时钟,PCB布线相对麻烦,不适于背板应用;而SGMII是串行的,不需要提供另外的时钟,MAC和PHY使用CDR来恢复时钟。参考时钟RX_CLK由PHY提供,是可选的,在时钟信号的上升沿和下降沿均发生采样,主要用于MAC侧没有时钟的情况,一般情况下,RX_CLK不使用,收发都可以从数据中恢复出时钟。

PHY芯片 AR8033 学习笔记_第3张图片

图3 SGMII接口图

    在TXD发送的串行数据中,每8bits数据会插入TX_EN/TX_ER 的2bits控制信息,同样,在RXD接收数据中,每8bits数据会插入RX_DV/RX_ER 的2bits控制信息,称作8B/10B编码。

 

 

【MDC/MDIO接口】

    接口有MDC和MDIO两条线。其中MDC上是由MAC提供的参考时钟信号,MDIO则是可双向传输的配置数据线,配合MDC时钟进行异步传输。AR8033的MDIO接口是开漏输出,所以在使用时需要外部上拉1.5k电阻。MDIO数据帧的组成如下:

图4 MDIO数据帧结构

各字段的含义如下:

PRE        对MDC时钟信号的回复,用于校正异步传输时钟,内容为32个数字1。

ST          数据帧开始标识。

OP          操作码,10表示读数据,01表示写数据。

PHYAD   PHY芯片的物理地址,共5位,其中3位可以在AR8033内部进行配置。

REGAD  寄存器地址,共5位,用于选中PHY芯片内的32个寄存器之一。

TA                 用于防止在数据传输期间建立新的连接,共2位。读操作期间第1位为高阻态,第2位为0;写操作期间第1位为1,第2位为0.

DATA     表示 从寄存器读到的数据 或 向寄存器写入的数据,共16位。从高位开始传输。

IDLE      帧间空闲信号,呈高阻态。相邻两个数据帧之间至少要有1个时钟的空闲信号。

 

 

【工作模式配置】

    AR8033可以工作在3种模式类别下:电口模式、光口模式、光电转换器模式。根据4根模式选择引脚上电平的不同可以将AR8033配置到不同的工作模式。官方文档描述如下:

PHY芯片 AR8033 学习笔记_第4张图片

图5 模式选择引脚

PHY芯片 AR8033 学习笔记_第5张图片

PHY芯片 AR8033 学习笔记_第6张图片

图6 光口模式的引脚电平情况

    从文档中可以看到,当我们希望AR8033工作在1000Mbps光口模式时,应该把RX_DV、RXD2、RX_CLK、RXD3这4个引脚的电平下拉和上拉为0010的情况。同理,要工作在100Mbps光口模式则可以将电平设置为0110或1110。

 

 

【应用示例】

    我们可以使用AR8033对光口进行管理。根据官方数据手册描述,可以采用如下结构:

PHY芯片 AR8033 学习笔记_第7张图片

图7 AR8033光纤模块系统框图

    从框图中可以看出,当AR8033芯片用于光纤模块管理时,需要配置成100BASE-FX模式或1000BASE-X模式,与交换机芯片之间的数据传输采用RGMII协议。实际应用时,AR8033的工作模式直接由主控芯片通过MDC/MDIO总线进行配置;数据传输路径是AR8033传递给交换芯片,再由交换芯片传递给主控芯片。示意图如下:

PHY芯片 AR8033 学习笔记_第8张图片

图8 AR8033应用示意图

    在示意图中可以看到,光模块芯片AR8033与交换芯片之间的数据传输使用RGMII协议,交换芯片与主控芯片之间的数据传输使用SGMII协议。

 

 

【代码分析】

    想要在Linux上使用AR8033需要做2部分工作,一是编写设备驱动并将设备驱动注册到内核,二是创建设备通信要使用的mdio总线并将设备注册到总线上。

a)  驱动注册流程:

    文件mdio_gpio.c 是 mdio_gpio 模块的代码所在。在模块加载函数 mdio_gpio_init() 中通过语句 ret = platform_driver_register(&mdio_gpio_driver) 将mdio 驱动注册为“平台设备驱动”,其中mdio_gpio_driver 是一个 platform_driver 结构体,初始化代码如下:

static struct platform_driver mdio_gpio_driver= {

       .probe= mdio_gpio_probe,    // 关联设备的probe函数

       .remove= __devexit_p(mdio_gpio_remove),    // 关联设备的remove函数

       .driver            = {

              .name      = "mdio-gpio",    // 驱动名

              .owner    = THIS_MODULE,

       },

};

    而在 platform_driver_register() 这个函数中,则进一步将驱动总线类型设定为 platform_bus_type,以及关联驱动的probe()函数、remove()函数和shutdown()函数。其代码细节如下:

int platform_driver_register(structplatform_driver *drv)

{

       drv->driver.bus= &platform_bus_type;

       if(drv->probe)

              drv->driver.probe = platform_drv_probe;    // 注意,这是drv->driver.probe

       if(drv->remove)

              drv->driver.remove= platform_drv_remove;    // 关联drv->driver.remove

       if(drv->shutdown)

              drv->driver.shutdown= platform_drv_shutdown;    // 关联drv->driver.shutdown

       returndriver_register(&drv->driver);

}

    可以看到,对于所有驱动而言初始化到这一步时都会指向probe()、remove()、shutdown()这3个函数,这3个函数分别用来返回“平台设备驱动”的probe()、remove()、shutdown()函数。代码如下:

static int platform_drv_probe(struct device*_dev)

{

       structplatform_driver *drv = to_platform_driver(_dev->driver);

       structplatform_device *dev = to_platform_device(_dev);

       returndrv->probe(dev);

}

    而在platform_driver_register()函数结尾的 return 语句中,再调用driver_register(&drv->driver) 进一步对驱动进行注册。通过语句driver_find(drv->name, drv->bus)查找总线上是否已经注册过该驱动,若没有则使用语句bus_add_driver(drv) 将驱动添加到总线中。至此,驱动注册流程结束。代码如下:

int driver_register(struct device_driver*drv)

{

       intret;

       structdevice_driver *other;

       BUG_ON(!drv->bus->p);    // 打开调试

   // 做一些检测

       if((drv->bus->probe && drv->probe) ||

           (drv->bus->remove &&drv->remove) ||

           (drv->bus->shutdown &&drv->shutdown))

              printk(KERN_WARNING"Driver '%s' needs updating - please use "

                     "bus_typemethods\n", drv->name);

 

       other = driver_find(drv->name, drv->bus);    // 在总线中查找是否已经注册该驱动

       if(other) {

              printk(KERN_ERR"Error: Driver '%s' is already registered, "

                     "aborting...\n",drv->name);

              return-EBUSY;

       }

       ret = bus_add_driver(drv);   // 将驱动添加到总线

       if(ret)

              returnret;

       ret= driver_add_groups(drv, drv->groups);

       if(ret)

              bus_remove_driver(drv);

       returnret;

}

 

b)  设备注册流程:

    驱动注册流程的末尾会调用驱动的 probe() 函数,即 mdio_gpio_probe() 函数。在该函数中,通过语句new_bus = mdio_gpio_bus_init(&pdev->dev, pdata,pdev->id) 初始化一个 mdio 总线设备,在使用语句 mdiobus_register(new_bus)对该设备总线进行注册。代码如下:

static int __devinit mdio_gpio_probe(structplatform_device *pdev)

{

       structmdio_gpio_platform_data *pdata = pdev->dev.platform_data;

       structmii_bus *new_bus;

       intret;

       if(!pdata)

              return-ENODEV;

       new_bus = mdio_gpio_bus_init(&pdev->dev, pdata,pdev->id);

       if(!new_bus)

              return-ENODEV;

       ret = mdiobus_register(new_bus);

       if(ret)

              mdio_gpio_bus_deinit(&pdev->dev);

       returnret;

}

    进入函数 mdiobus_register() 查看代码,内容可以分为2部分。一是对 mdio 总线设备进行真正的注册,二是注册成功后,在总线上根据 phy_mask搜索 PHY 设备。代码如下:

 

int mdiobus_register(struct mii_bus *bus)

{

       inti, err;

       if(NULL == bus || NULL == bus->name ||

                     NULL== bus->read ||

                     NULL== bus->write)

              return-EINVAL;

       BUG_ON(bus->state!= MDIOBUS_ALLOCATED &&

              bus->state != MDIOBUS_UNREGISTERED);

       bus->dev.parent= bus->parent;

       bus->dev.class= &mdio_bus_class;

       bus->dev.groups= NULL;

       dev_set_name(&bus->dev,"%s", bus->id);

       err = device_register(&bus->dev);    // 注册 mdio总线设备

       if(err) {

              printk(KERN_ERR"mii_bus %s failed to register\n", bus->id);

              return-EINVAL;

       }

       mutex_init(&bus->mdio_lock);

       if(bus->reset)

              bus->reset(bus);

       for(i = 0; i < PHY_MAX_ADDR; i++) {

              if((bus->phy_mask & (1 << i)) == 0) {

                     structphy_device *phydev;

                     phydev = mdiobus_scan(bus, i);    // 在总线上搜索 phy 设备

                     if(IS_ERR(phydev)) {

                            err= PTR_ERR(phydev);

                            gotoerror;

                     }

              }

       }

       bus->state= MDIOBUS_REGISTERED;

…   // 以下省略

}

    进入函数 mdiobus_scan() 查看代码,可以看到在该函数中使用 phydev = get_phy_device(bus, addr) 语句从总线设备上获取到 phy 设备,然后通过语句phy_device_register(phydev) 对phy 设备进行注册。至此,设备注册流程结束。代码如下:

struct phy_device *mdiobus_scan(structmii_bus *bus, int addr)

{

       structphy_device *phydev;

       structmdio_board_entry *be;

       interr;

 

       phydev = get_phy_device(bus, addr);    // 从总线设备上获取 phy 设备

       if(IS_ERR(phydev) || phydev == NULL)

              returnphydev;

       mutex_lock(&__mdio_board_lock);

       list_for_each_entry(be,&__mdio_board_list, list)

              mdiobus_setup_phydev_from_boardinfo(bus,phydev,

                                              &be->board_info);

       mutex_unlock(&__mdio_board_lock);

       err = phy_device_register(phydev);    // phy 设备注册到内核

       if(err) {

              phy_device_free(phydev);

              returnNULL;

       }

       returnphydev;

}

你可能感兴趣的:(嵌入式,驱动,光纤,PHY芯片,AR8033)