歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux下smi/mdio總線驅動

MII(媒體獨立接口), 是IEEE802.3定義的以太網行業標准接口, smi是mii中的標准管理接口, 有兩跟管腳, mdio 和mdc ,用來現實雙向的數據輸入/輸出和時鐘同步。mdio主要作用用來配置/讀取phy的寄存器, 實現監控作用。 Smi總線也就是mdio總線。

以mips 架構的caium octeon 處理器為例介紹mdio總線的驅動。

內核代碼 drivers/net/phy/mdio-octeon.c

static int __init octeon_mdiobus_mod_init(void)

  // 同uart,usb,spi,i2c等總線一樣, mdio作為platform驅動注冊到內核
    return platform_driver_register(&octeon_mdiobus_driver);
}


    static struct platform_driver octeon_mdiobus_driver = {
    .driver = {                         
        .name      = "mdio-octeon",     
        .owner      = THIS_MODULE,       
        .of_match_table = octeon_mdiobus_match,
    },                                   
    .probe      = octeon_mdiobus_probe, 
    .remove    = __exit_p(octeon_mdiobus_remove),
};                                       
       

內核根據of_match_table 找到了octeon-3860-mdio 的驅動文件,
 
static struct of_device_id octeon_mdiobus_match[] = {
    {
        .compatible = "cavium,octeon-3860-mdio",

    },                                 
    {},                                 
};
MODULE_DEVICE_TABLE(of, octeon_mdiobus_match);

該驅動說明支持符合”canium,octeon-3860-mdio”規范接口的操作。

進入probe()

static int __init octeon_mdiobus_probe(struct platform_device *pdev)

    /*probe() 總體思想即填充一個struct octeon_mdiobus的數據結構,
  最後將此數據結構作為pdev的私有成員。octeon_mdiobus 定義為:
struct octeon_mdiobus { 
    //struct mii_bus 是linux定義mii總線的通用數據結構。                                                           
    struct mii_bus *mii_bus;
    u64 register_base;
    resource_size_t mdio_phys;
    resource_size_t regsize;
    enum octeon_mdiobus_mode mode;
    int phy_irq[PHY_MAX_ADDR];
};
octeon_mdiobus_mode 定義:
enum octeon_mdiobus_mode {
    UNINIT = 0,
    C22,    // IEEE802.3-2005
的條款22.2.4, 22.3.4
    C45    //條款45.不用的條款使用不同的數據幀結構。
};
*/
    struct octeon_mdiobus *bus;

    struct resource *res_mem;
    union cvmx_smix_en smi_en;
    int err = -ENOENT;
   
//為platfrom設備pdev 的私有數據分配內存,長度struct octeon_mdiobus的長度
    bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
    if (!bus)
        return -ENOMEM;
   

/*
      獲取io內存地址資源的描述。此資源的描述記錄在uboot的設備樹源文件dts裡。
      關於該描述信息,參考最後附錄。
      */
    res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
                       
    if (res_mem == NULL) {       
        dev_err(&pdev->dev, "found no memory resource\n");
        err = -ENXIO;       
        goto fail_region;   
    }                     

//獲取了io內存地址的首地址及長度的描述後, 按此描述向系統申請相應資源。
    bus->mdio_phys = res_mem->start;       
    bus->regsize = resource_size(res_mem); 
    if (!devm_request_mem_region(&pdev->dev, bus->mdio_phys, bus->regsize,

                    res_mem->name)) {     
        dev_err(&pdev->dev, "request_mem_region failed\n");                                                             
        goto fail_region;                 
    } 

/*
申請成功後, 使用ioremap將這個片io內存地址映射出來,以便交與應用層使用,
register_basae 即為這個映射後的地址基地址,通過操作這個地址, 就可以實現在用戶層操作smi的寄存器了。
/*
bus->register_base = (u64)ioremap(bus->mdio_phys, bus->regsize);


//為mii_bus數據結構分配內存
bus->mii_bus = mdiobus_alloc();

    if (!bus->mii_bus) 
        goto fail_mdio_alloc;

                                                   
    smi_en.u64 = 0;                               
    smi_en.s.en = 1; 

/*
cvmx_write_csr 是cavium octeon 處理器提供write寄存器的API
      #define SMI_EN      0x20    這是寄存器的其基地址上的偏移值
      第一個參數是目的寄存器地址, 第二個參數是要write的數值
*/             
    cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64);                                                                                 
                                                   




/* mii_bus 數據結構定義如下, read/write 的函數指針
struct mii_bus {
    const char *name;
    char id[MII_BUS_ID_SIZE];
    void *priv;
    int (*read)(struct mii_bus *bus, int phy_id, int regnum);
    int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val);
    int (*reset)(struct mii_bus *bus);

    struct mutex mdio_lock;
    struct device *parent;
    enum {
        MDIOBUS_ALLOCATED = 1,
        MDIOBUS_REGISTERED,
        MDIOBUS_UNREGISTERED,
        MDIOBUS_RELEASED,
    } state;
    struct device dev;
   
    struct phy_device *phy_map[PHY_MAX_ADDR];
    u32 phy_mask;
    int *irq;
}; 

*/

    // 將bus 保存為mii_bus的私有數據
    bus->mii_bus->priv = bus;

    //定義mii_bus的中斷號
    bus->mii_bus->irq = bus->phy_irq;

    //mii_bus的總線名稱
    bus->mii_bus->name = "mdio-octeon";
    snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", bus->register_base);
    bus->mii_bus->parent = &pdev->dev;
                       
    //填充mii_bus read/write的實現函數。
    bus->mii_bus->read = octeon_mdiobus_read;

    bus->mii_bus->write = octeon_mdiobus_write;
                       
    //將bus作為platfrom 的私有數據
    dev_set_drvdata(&pdev->dev, bus);                                                                                                         
   
//向內核注冊屬於octeon 的mii總線
    err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node);

    if (err)   
        goto fail_register;
   
    dev_info(&pdev->dev, "Version " DRV_VERSION "\n");
   
    /*
    將mii_bus 保存在一個全局指針數組裡, 定義在arch/mips/cavium-octeon/setup.c
struct mii_bus *octeon_mdiobuses[4];
EXPORT_SYMBOL(octeon_mdiobuses);                                                               
    */                     
    octeon_mdiobuses[octeon_mdiobus_bus2unit(bus)] = bus->mii_bus;

    return 0;
}

mdio 工作大郅流程:

發送一個2bit的開始標識碼和一個2bit的operate標志,該operate 標志在C22和C45裡有不同定義。發送一個5bit 的phy 設備地址和5bitPHY寄存器地址。 再空閒MDIO需要2個時鐘的訪問時間。 MDIO串行讀出/寫入16bit的寄存器數據。 結束後MDIOMDIO進入高阻狀態。

C22下的數據幀格式:

st op phyaddr regaddr ta data

01    phy_op 5bit地址 5bit地址 2bit訪問時間 16bit讀寫數據

C22 下的op :10 : write, 01 : read

     
C45數據幀格式:

st op phyaddr type ta addr/data

00    phy_op 5bit地址 5bit類型 2bit訪問時間 16bit寄存器地址/數據

     
兩者主要差異在op處, C45的op段:

00=address
01=write
,11=read
,10=post-read-increment-address 

當op 為00 時, 這個數據幀傳入的指定的16bit寄存器地址, 最大地址63336.
當op 為 01/11 時, 這個數據幀才是具體write/read 操作。

因此, 在c45 條款下,  完成一次真正的I/O 操作要使用兩個數據幀。

另外,當 op 為 10 時, 含義是當本次讀操作結束後, 將寄存器地址加1, 適於與遍歷所有的寄存器。

Octeon 對該數幀的定義是:

union cvmx_smix_cmd {
    uint64_t u64;
    struct cvmx_smix_cmd_s {
    uint64_t reserved_18_63              : 46;
//保留
    uint64_t phy_op                      : 2;  //phy_op
    uint64_t reserved_13_15              : 3;
    uint64_t phy_adr                      : 5;     //phy芯片地址                                                       
    uint64_t reserved_5_7                : 3;
    uint64_t reg_adr                      : 5; //寄存器地址
  }



struct cvmx_smix_cmd_s 為uint64_t 大小, 即8個字節。


static int octeon_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum)

    struct octeon_mdiobus *p = bus->priv;
    union cvmx_smix_cmd smi_cmd;
    union cvmx_smix_rd_dat smi_rd;

    //如果C22條款, read操作時op為 01.
    unsigned int op = 1; /* MDIO_CLAUSE_22_READ */
    int timeout = 1000;
   
    //寄存器是否滿足是否有C45 標志
    if (regnum & MII_ADDR_C45) {

    /*
如果是C45條款, 要先發送一個數據幀將寄存器地址寫入, 第二個數據幀才是read/write操作
      octeon_mdiobus_c45_addr() 函數完成第一個數據幀的作用。
      */
        int r = octeon_mdiobus_c45_addr(p, phy_id, regnum);
        if (r < 0)
            return r;

   
    //將regnum處理後封裝到smi_cmd裡。
        regnum = (regnum >> 16) & 0x1f;
 
        //C45條款下read操作時op為11
        op = 3; /* MDIO_CLAUSE_45_READ */
    } else {
        //C22條款下, 只需要將mdio配置為C22模式即可。
        octeon_mdiobus_set_mode(p, C22);
    }
   
   
    smi_cmd.u64 = 0;
    smi_cmd.s.phy_op = op; /* MDIO_CLAUSE_22_READ */
    smi_cmd.s.phy_adr = phy_id;
    smi_cmd.s.reg_adr = regnum;

    /*
由於smi_cmd 是聯合體, 將smi_cmd.u64的數值傳給函數, 寄存器即可解析出op, phy_id, regnum等參數。
    */
    cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64);
   
    do {
       
  //read之前等待1000個時鐘周期
        __delay(1000);
       
      // 讀取到寄存器的數值,保存到u64中。
        smi_rd.u64 = cvmx_read_csr(p->register_base + SMI_RD_DAT);
    } while (smi_rd.s.pending && --timeout);
   
    //如果數據有效, 發送寄存器數值中去掉頭部信息的部分, 即u64中的有效載荷。
    if (smi_rd.s.val)
        return smi_rd.s.dat;

    else
        return -EIO;

 
smi 的write 操作原理同上。


附錄:  dts 裡smi總線io地址資源描述

Dts裡的描述是根據cavium octeon datasheet來寫的, cavium octeon 關於smi 寄存器地址的定義是:
smi0從 0x0001180000001800到0x0001180000001828

smi 從0x0001180000001900
到0x0001180000001920

所以在描述reg地址范圍時, 要適當大於這個范圍, 但不能跟其他寄存器地址沖突。
        smi0: mdio@1180000001800 {
            compatible = "cavium,octeon-3860-mdio";
            #address-cells = <1>;
            #size-cells = <0>; 
            reg = <0x11800 0x00001800 0x0 0x40>;
            ..
}                   

關於compatible 的描述, "cavium,octeon-3860-mdio";

cavium 表示了該smi0總線可以兼容“cavium, octeon-3860-mdio”設備, 內核啟動後, 會根據這個描述尋找對應的驅動。

        smi1: mdio@1180000001900 {
          compatible = "cavium,octeon-3860-mdio";
            #address-cells = <1>;
            #size-cells = <0>; 
            reg = <0x11800 0x00001900 0x0 0x40>;
        }; 
Copyright © Linux教程網 All Rights Reserved