【思路描述】
想要在Linux上使用AR8033需要做2部分工作,一是編寫設備驅動並將設備驅動注冊到內核,二是創建設備通信要使用的mdio總線並將設備注冊到總線上。關於 AR8033 芯片的介紹可以看我的這篇博文《PHY芯片 AR8033學習筆記》。
【代碼分析】
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
return driver_register(&drv->driver);
}
可以看到,對於所有驅動而言初始化到這一步時都會指向probe()、remove()、shutdown()這3個函數,這3個函數分別用來返回“平台設備驅動”的probe()、remove()、shutdown()函數。代碼如下:
static int platform_drv_probe(struct device*_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->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)
{
int ret;
struct device_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)
return ret;
ret= driver_add_groups(drv, drv->groups);
if(ret)
bus_remove_driver(drv);
return ret;
}
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)
{
struct mdio_gpio_platform_data *pdata = pdev->dev.platform_data;
struct mii_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);
return ret;
}
進入函數 mdiobus_register() 查看代碼,內容可以分為2部分。一是對 mdio 總線設備進行真正的注冊,二是注冊成功後,在總線上根據 phy_mask搜索 PHY 設備。代碼如下:
int mdiobus_register(struct mii_bus *bus)
{
int i, 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);
goto error;
}
}
}
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)
{
struct phy_device *phydev;
struct mdio_board_entry *be;
int err;
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);
return NULL;
}
return phydev;
}