zoukankan      html  css  js  c++  java
  • Linux下smi/mdio总线驱动

    Linuxsmimdio总线驱动
    
    韩大卫@吉林师范大学
    
    
    MII(媒体独立接口), 是IEEE802.3定义的以太网行业标准接口, smimii中的标准管理接口, 有两跟管脚, 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
    ");
            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
    ");                                                               
            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 "
    ");
         
        /*
        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的开始标识码和一个2bitoperate标志,该operate 标志在C22C45里有不同定义。发送一个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 : write01 read
    
           
    C45数据帧格式:
    
    st	op		phyaddr	type		ta			addrdata
    
    00    phy_op	5bit地址	5bit类型	2bit访问时间		16bit寄存器地址/数据
    
          
    两者主要差异在op处, C45op段:
    
    00=address
     01=write
    11=read
    10=post-read-increment-address   
    
    op 00 时, 这个数据帧传入的指定的16bit寄存器地址, 最大地址63336.
    op 01/11 时, 这个数据帧才是具体write/read 操作。 
    
    因此, 在c45 条款下,  完成一次真正的IO 操作要使用两个数据帧。
    
    另外,当 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操作时op01.
        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操作时op11
            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 寄存器地址的定义是:
    smi00x00011800000018000x0001180000001828 
     
    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>;
            };                      
    
  • 相关阅读:
    软工实践寒假作业(2/2)
    软工实践寒假作业(1/2)
    个人技术总结——关于springboot的定时任务框架quartz
    个人作业——软件工程实践总结&个人技术博客
    个人作业——软件评测
    结对第二次作业——某次疫情统计可视化的实现
    结对第一次 — 疫情统计可视化(原型设计)
    软工实践寒假作业(2/2)
    软工实践寒假作业(1/2)
    个人作业——软件工程实践总结&个人技术博客
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3206453.html
Copyright © 2011-2022 走看看