【芯片简介】
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协议栈中属于最底层的物理层,与其它层的关系图如下:
图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。
图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不使用,收发都可以从数据中恢复出时钟。
图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配置到不同的工作模式。官方文档描述如下:
图5 模式选择引脚
图6 光口模式的引脚电平情况
从文档中可以看到,当我们希望AR8033工作在1000Mbps光口模式时,应该把RX_DV、RXD2、RX_CLK、RXD3这4个引脚的电平下拉和上拉为0010的情况。同理,要工作在100Mbps光口模式则可以将电平设置为0110或1110。
【应用示例】
我们可以使用AR8033对光口进行管理。根据官方数据手册描述,可以采用如下结构:
图7 AR8033光纤模块系统框图
从框图中可以看出,当AR8033芯片用于光纤模块管理时,需要配置成100BASE-FX模式或1000BASE-X模式,与交换机芯片之间的数据传输采用RGMII协议。实际应用时,AR8033的工作模式直接由主控芯片通过MDC/MDIO总线进行配置;数据传输路径是AR8033传递给交换芯片,再由交换芯片传递给主控芯片。示意图如下:
图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;
}