標籤:
【晶片簡介】
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;
}
PHY晶片 AR8033 學習筆記