加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Linux下smi/mdio总线驱动

(2015-01-21 10:55:54)
标签:

it

分类: linux
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; 
    

 
    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 保存为 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"); 
    
                          
    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;  
    int timeout = 1000; 
    
    // 寄存器是否满足是否有 C45 标志 
    if (regnum & MII_ADDR_C45) { 

     
        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;  
    } else { 
        //C22 条款下, 只需要将 mdio 配置为 C22 模式即可。 
        octeon_mdiobus_set_mode(p, C22); 
    } 
    
    
    smi_cmd.u64 = 0; 
    smi_cmd.s.phy_op = op;  
    smi_cmd.s.phy_adr = phy_id; 
    smi_cmd.s.reg_adr = 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>; 
        }; 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有