[ Linux IMX6ULL ] PHY驱动框架解析 —— MDIO总线 |CSDN创作打卡

平台:freescale 内核版本:Linux version 4.1.15

文章目录

    • 概述
      • PHY控制器驱动
    • PHY驱动解析
      • 一、相关结构体
      • 二、网口和mdio总线设备树配置(代码以imx6ull为例)
      • 三、MAC驱动和mdio控制器注册
      • 四、PHY设备驱动

概述

PHY芯片为OSI最底层 —— 物理层,通过MII/GMII/RMII/SGMII/XGMII等多种媒体独立接口(介质无关接口)与数据链路层的MAC芯片相连,并通过MDIO接口实现对PHY 状态的监控、配置和管理

PHY与MAC整体的连接框图:[ Linux IMX6ULL ] PHY驱动框架解析 —— MDIO总线 |CSDN创作打卡_第1张图片
注:MII是走网络数据的

PHY控制器驱动

PHY控制器驱动和SPI/I2C类似,控制器功能是实现具体的读写功能。实现方法有两种(与I2C类似):
1、直接调用CPU的MDIO控制器(直接调用CPU对应的寄存器)
2、通过GPIO/外围soc模拟MDIO时序的方式
PHY的控制器一般被描述为mdio_bus平台
注:这是一个设备,等同于SPI/I2C中的master设备;和总线、驱动、设备中的bus不一样
既然是平台设备,那么设备树中有可以被解析为平台的设备节点,也有对应的平台设备驱动。

PHY驱动解析

一、相关结构体

// 平台结构体
struct platform_driver {
    int (*probe)(struct platform_device *);     
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
    bool prevent_deferred_probe;
};

/* 在dts文件里,每个大括号{}代表一个节点,比如根节点里有个大括号,对应一个device_node结构体;memory也是一个大括号,也是对应一个device_node结构体
 * 节点里面有各种属性,也有可能里面还有子节点,所以它们还有一些父子关系
 * 根节点下的memory、chosen、led等节点是并列关系
 * 对于父子关系、兄弟关系,在device_node结构体里面肯定有成员来描述这些关系
 */ 
// 设备节点结构体
struct device_node {
    const char *name;            /* node的名称,取最后一次"/"和"@"之间字符串 */
    const char *type;            /* device_type的属性名称,没有为 */
    phandle phandle;             /* phandle属性值 */
    const char *full_name;       /* 指向该结构体结束位置,存放node的路径全名  */
    struct fwnode_handle fwnode;

    struct  property *properties; /* 指向该节点下的第一个属性,其他属性与该属性链表相接 */
    struct  property *deadprops;  /* removed properties */
    struct  device_node *parent;  /* 父节点 */
    struct  device_node *child;   /* 子节点 */
    struct  device_node *sibling; /* 与自己同等级的node */
    struct  kobject kobj;         /* sysfs文件系统目录体现 */
    unsigned long _flags;         /* 当前node状态标志位 */ 
    void    *data;
};

struct net_device {
    char  name[IFNAMSIZ];         /* 用于存放网络设备的设备名称 */
    char  *ifalias;               /* 网络设备的别名 */
    int   ifindex;                /* 网络设备的接口索引值,独一无二的网络设备标识符 */
    struct hlist_node  name_hlist;  /* 这个字段用于构建网络设备名的哈希散列表,而struct net中的name_hlist就指向每个哈希散列表的链表头 */
    struct hlist_node  index_hlist; /* 用于构建网络设备的接口索引值哈希散列表,在struct net中的index_hlist用于指向接口索引值哈希散列表的链表头 */
    struct list_head   dev_list;    /* 用于将每一个网络设备加入到一个网络命名空间中的网络设备双链表中 */
    unsigned int       flags;       /* 网络设备接口的标识符 */
    unsigned int       priv_flags;  /* 网络设备接口的标识符,但对用户空间不可见;*/
    unsigned short     type;        /* 接口硬件类型 */
    unsigned int       mtu;         /* 网络设备接口的最大传输单元 */
    unsigned short     hard_header_len;   /* 硬件接口头长度 */
    unsigned char      *dev_addr;    /* 网络设备接口的MAC地址 */
    bool           uc_promisc;       /* 网络设备接口的单播模式 */
    unsigned int       promiscuity;  /* 网络设备接口的混杂模式 */
    unsigned int       allmulti;     /* 网络设备接口的全组播模式 */
    struct netdev_hw_addr_list  uc;  /* 辅助单播MAC地址列表 */
    struct netdev_hw_addr_list  mc;  /* 主mac地址列表 */
    struct netdev_hw_addr_list  dev_addrs;  /* hw设备地址列表 */
    unsigned char      broadcast[MAX_ADDR_LEN];   /* hw广播地址 */
    struct netdev_rx_queue  *_rx;       /* 网络设备接口的数据包接收队列 */
    struct netdev_queue *_tx            /* 网络设备接口的数据包发送队列 */
    unsigned int        num_tx_queues;  /* TX队列数 */
    unsigned int        real_num_tx_queues;  /* 当前设备活动的TX队列数 */
    unsigned long      tx_queue_len;    /* 每个队列允许的最大帧 */
    unsigned long      state;           /* 网络设备接口的状态 */
    struct net_device_stats    stats;   /* 网络设备接口的统计情况 */
    possible_net_t         nd_net;      /* 用于执行网络设备所在的命名空间 */
}struct mii_bus {
    const char *name;             // 总线名字
    char id[MII_BUS_ID_SIZE];     // ID MII_BUS_ID_SIZE=61
    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];       // PHY设备数组

    u32 phy_mask;
    int *irq;                   // 中断
};

// PHY设备结构体
struct phy_device {
    struct phy_driver *drv;                // PHY设备驱动  
    struct mii_bus *bus;                   // 对应的MII总线 
    struct device dev;                     // 设备文件  
    u32 phy_id;                            // PHY ID  
    
    struct phy_c45_device_ids c45_ids;       
    bool is_c45;
    bool is_internal;
    bool has_fixups;
    bool suspended;

    enum phy_state state;                   // PHY状态
    u32 dev_flags;
    phy_interface_t interface;              // PHY接口  
    int addr;                               // PHY 总线地址(0~31) 

    int speed;                               // 速度  
    int duplex;                              // 双工模式  
    int pause;                               // 停止  
    int asym_pause;
    int link;

    u32 interrupts;                           // 中断使能标志  
    u32 supported;
    u32 advertising;
    u32 lp_advertising;
    int autoneg;
    int link_timeout;
    int irq;                                   // 中断号                              

    void *priv;                                // 私有数据  
    struct work_struct phy_queue;              // PHY工作队列  
    struct delayed_work state_queue;           // PHY延时工作队列  
    atomic_t irq_disable;

    struct mutex lock;

    struct net_device *attached_dev;             // 网络设备  

    void (*adjust_link)(struct net_device *dev);
};

// PHY驱动结构体
struct phy_driver {
    struct mdio_driver_common mdiodrv;
    u32 phy_id;
    char *name;
    u32 phy_id_mask;
    u32 features;
    u32 flags;
    const void *driver_data;
    
    int (*soft_reset)(struct phy_device *phydev);
    int (*config_init)(struct phy_device *phydev);
    int (*probe)(struct phy_device *phydev);
    int (*suspend)(struct phy_device *phydev);
    int (*resume)(struct phy_device *phydev);
    int (*config_aneg)(struct phy_device *phydev);
    int (*aneg_done)(struct phy_device *phydev);
    int (*read_status)(struct phy_device *phydev);
    int (*ack_interrupt)(struct phy_device *phydev);
    int (*config_intr)(struct phy_device *phydev);
    int (*did_interrupt)(struct phy_device *phydev);
    void (*remove)(struct phy_device *phydev);
    int (*match_phy_device)(struct phy_device *phydev);
    int (*ts_info)(struct phy_device *phydev, struct ethtool_ts_info *ti);
    int  (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
    bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
    void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
    int (*set_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);
    void (*get_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);
    void (*link_change_notify)(struct phy_device *dev);
    int (*read_mmd)(struct phy_device *dev, int devnum, u16 regnum);
    int (*write_mmd)(struct phy_device *dev, int devnum, u16 regnum,
             u16 val);
    int (*read_page)(struct phy_device *dev);
    int (*write_page)(struct phy_device *dev, int page)
    int (*module_info)(struct phy_device *dev,
               struct ethtool_modinfo *modinfo);
    int (*module_eeprom)(struct phy_device *dev,
                 struct ethtool_eeprom *ee, u8 *data);
    int (*get_sset_count)(struct phy_device *dev);
    void (*get_strings)(struct phy_device *dev, u8 *data);
    void (*get_stats)(struct phy_device *dev,
              struct ethtool_stats *stats, u64 *data);
    int (*get_tunable)(struct phy_device *dev,
               struct ethtool_tunable *tuna, void *data);
    int (*set_tunable)(struct phy_device *dev,
                struct ethtool_tunable *tuna,
                const void *data);
    int (*set_loopback)(struct phy_device *dev, bool enable);
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};

二、网口和mdio总线设备树配置(代码以imx6ull为例)

// imx6ull设备树配置
fec1: ethernet@02188000 {
    compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";
    reg = <0x02188000 0x4000>;
    interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>,
             <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_ENET>,
         <&clks IMX6UL_CLK_ENET_AHB>,
         <&clks IMX6UL_CLK_ENET_PTP>,
         <&clks IMX6UL_CLK_ENET_REF>,
         <&clks IMX6UL_CLK_ENET_REF>;
    clock-names = "ipg", "ahb", "ptp",
              "enet_clk_ref", "enet_out";
    stop-mode = <&gpr 0x10 3>;
    fsl,num-tx-queues=<1>;
    fsl,num-rx-queues=<1>;
    fsl,magic-packet;
    fsl,wakeup_irq = <0>;
    status = "disabled";
            };

fec2: ethernet@020b4000 {
    compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";
    reg = <0x020b4000 0x4000>;
    interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>,
             <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_ENET>,
         <&clks IMX6UL_CLK_ENET_AHB>,
         <&clks IMX6UL_CLK_ENET_PTP>,
         <&clks IMX6UL_CLK_ENET2_REF_125M>,
         <&clks IMX6UL_CLK_ENET2_REF_125M>;
    clock-names = "ipg", "ahb", "ptp",
              "enet_clk_ref", "enet_out";
    stop-mode = <&gpr 0x10 4>;
    fsl,num-tx-queues=<1>;
    fsl,num-rx-queues=<1>;
    fsl,magic-packet;
    fsl,wakeup_irq = <0>;
    status = "disabled";
};

&fec1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_enet1>;
    phy-reset-duration =<100>;
    phy1-reset-gpios = <&gpio5 5 GPIO_ACTIVE_LOW>;    // ENET_RST  ENET1_RST
    phy-mode = "rmii";
    phy-handle = <&ethphy0>;
    status = "okay";
};

&fec2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_enet2>;
    phy-mode = "rmii";
    phy-handle = <&ethphy1>;
    status = "okay";

    mdio {                                            // mdio总线设备节点
        #address-cells = <1>;
        #size-cells = <0>;

        ethphy0: ethernet-phy@2 {                    // 设备fec1,地址为2
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <2>;
        };

        ethphy1: ethernet-phy@1 {                     // 设备fec2,地址为1
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <1>;
        };
    };
};

三、MAC驱动和mdio控制器注册

static const struct of_device_id fec_dt_ids[] = {
    { .compatible = "fsl,imx25-fec", .data = &fec_devtype[IMX25_FEC], },
    { .compatible = "fsl,imx27-fec", .data = &fec_devtype[IMX27_FEC], },
    { .compatible = "fsl,imx28-fec", .data = &fec_devtype[IMX28_FEC], },
    { .compatible = "fsl,imx6q-fec", .data = &fec_devtype[IMX6Q_FEC], },
    { .compatible = "fsl,mvf600-fec", .data = &fec_devtype[MVF600_FEC], },
    { .compatible = "fsl,imx6sx-fec", .data = &fec_devtype[IMX6SX_FEC], },
    { .compatible = "fsl,imx6ul-fec", .data = &fec_devtype[IMX6UL_FEC], },
    { /* sentinel */ }
};

static struct platform_driver fec_driver = {
    .driver = {
        .name   = DRIVER_NAME,
        .pm = &fec_pm_ops,
        .of_match_table = fec_dt_ids,
    },
    .id_table = fec_devtype,
    .probe  = fec_probe,
    .remove = fec_drv_remove,
};

module_platform_driver(fec_driver);            //向platform注册mac驱动

设备树的compatible和驱动的compatible匹配成功之后,会调用fec_probe函数,此为mac驱动的真正入口
1、注册网络设备net_device
2、申请队列和DMA
3、申请MDIO总线
4、创建并注册PHY设备

fec_probe(struct platform_device *pdev)
	-> struct device_node *np = pdev->dev.of_node, *phy_node;    // 获取设备树节点句柄,并创建一个phy的设备树节点句柄
	-> fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs);   // 从设备树获取fsl,num-tx-queues和fsl,num-rx-queues的属性值
	-> ndev = alloc_etherdev_mqs          // 申请net_device
	-> netdev_priv(ndev)                                   //  获取私有数据空间首地址
--------------------------------------------------------------------------------------------------------------------------
	-> of_parse_phandle(np, "phy-handle", 0)      // 从mac的设备树节点中获取phy子节点
	-> of_get_phy_mode(pdev->dev.of_node)      // 从设备树节点中获取phy模式,phy-mode = "rmii";
	-> fec_reset_phy(pdev);               // 复位phy
	-> fec_enet_init(ndev)               // 申请队列和DMA,设置MAC地址
	-> of_property_read_u32(np, "fsl,wakeup_irq", &irq)      // 唤醒中断
	-> fec_enet_mii_init(pdev);         // 注册MDIO总线、注册phy_device
		-> fep->mii_bus = mdiobus_alloc()                 //申请MDIO总线
		-> fep->mii_bus->name = "fec_enet_mii_bus";     // 总线名字
		-> fep->mii_bus->read = fec_enet_mdio_read;     // 总线的读函数
		-> fep->mii_bus->write = fec_enet_mdio_write;     // 总线的写函数
		-> snprintf(fep->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x",
        pdev->name, fep->dev_id + 1);                         // 总线id       
		-> of_get_child_by_name(pdev->dev.of_node, "mdio");            // 获取phy节点句柄
		-> of_mdiobus_register          // 注册mii_bus设备,并通过设备树子节点创建PHY设备 drivers/of/of_mdio.c      	    of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)      
			-> mdio->phy_mask = ~0;    // 屏蔽所有PHY,防止自动探测。相反,设备树中列出的phy将在总线注册后填充
			-> mdio->dev.of_node = np;
			-> mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
			-> mdiobus_register(mdio)                 // 注册MDIO总线设备
    			-> bus->dev.parent = bus->parent;
    			-> bus->dev.class = &mdio_bus_class;            // 总线设备类“/sys/bus/mdio_bus”
				/*-----------------------------------------
				static struct class mdio_bus_class = {
    			.name       = "mdio_bus",
    			.dev_release    = mdiobus_release,
				};
				-------------------------------------------*/
    			-> bus->dev.groups = NULL;
    			-> dev_set_name(&bus->dev, "%s", bus->id);      //设置总线设备的名称
   				-> device_register(&bus->dev);                            // 注册总线设备
				->  if (bus->reset)      bus->reset(bus);                  // 总线复位
---------------------------------------另一条分支解析(可忽略)--------------------------------------------------------
				->  phydev = mdiobus_scan(bus, i);	              // 扫描phy设备
					-> phydev = get_phy_device(bus, addr);	//获取创建phy设备
					->err = phy_device_register(phydev);	//注册phy设备
--------------------------------------------------------------------------------------------------------------------
				-> for_each_available_child_of_node(np, child) {       // 遍历这个平台设备的子节点并为每个phy注册一个phy_device
				-> addr = of_mdio_parse_addr(&mdio->dev, child)          // 从子节点的"reg"属性中获得PHY设备的地址 
					-> of_property_read_u32(np, "reg", &addr)
				-> if (addr < 0)     scanphys = true;      continue;             // 如果未获得子节点的"reg"属性,则在后面再启用扫描可能存在的PHY的,然后注册
				-> of_mdiobus_register_phy(mdio, child, addr) 	}             // 创建并注册PHY设备
					-> is_c45 = of_device_is_compatible(child,"ethernet-phy-ieee802.3-c45") //判断设备树中的PHY的属性是否指定45号条款
					-> if (!is_c45 && !of_get_phy_id(child, &phy_id))      //如果设备树中的PHY的属性未指定45号条款 且未通过"ethernet-phy-id%4x.%4x"属性指定PHY的ID
					-> phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
					-> else    phy = get_phy_device(mdio, addr, is_c45);    //用这个分支
						-> get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);     //通过mdio得到PHY的ID
							-> mdiobus_read(bus, addr, MII_PHYSID1)
								-> __mdiobus_read(bus, addr, regnum);
									-> bus->read(bus, addr, regnum)
							-> mdiobus_read(bus, addr, MII_PHYSID2)
						-> phy_device_create(bus, addr, phy_id, is_c45, &c45_ids)    // 创建PHY设备
							-> struct phy_device *dev;
							-> dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    							dev->dev.release = phy_device_release;
    							dev->speed = 0;
    							dev->duplex = -1;
    							dev->pause = 0;
    							dev->asym_pause = 0;
    							dev->link = 1;
   								dev->interface = PHY_INTERFACE_MODE_GMII;
   	   							dev->autoneg = AUTONEG_ENABLE;      // 默认支持自协商(自动使能)
    							dev->is_c45 = is_c45;
    							dev->addr = addr;
    							dev->phy_id = phy_id;
   		 						if (c45_ids)
        							dev->c45_ids = *c45_ids;
    							dev->bus = bus;
    							dev->dev.parent = bus->parent;
    	   						dev->dev.bus = &mdio_bus_type;    //PHY设备和驱动都会挂在mdio_bus下,匹配时会调用对应的match函数  --   
					/*----------------------------------------------------------------------------------------------------
								struct bus_type mdio_bus_type = {
    											.name       = "mdio_bus",                //总线名称
   											   .match      = mdio_bus_match,            //用来匹配总线上设备和驱动的函数
    											.pm     = MDIO_BUS_PM_OPS,
   							 					.dev_groups = mdio_dev_groups,
								};
						----------------------------------------------------------------------------------------------------*/
    							dev->irq = bus->irq != NULL ? bus->irq[addr] : PHY_POLL;
    							dev_set_name(&dev->dev, PHY_ID_FMT, bus->id, addr);
    	   						dev->state = PHY_DOWN;     //指示PHY设备和驱动程序尚未准备就绪,在PHY驱动的probe函数中会更改为READY
 								-> INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);  //PHY的状态机(核心WORK)后续解析
 								-> INIT_WORK(&dev->phy_queue, phy_change);   // 由phy_interrupt / timer调度以处理PHY状态的更改
								->  request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));            // 加载内核模块
 								-> device_initialize(&dev->dev);     //设备模型中的一些设备,主要是kset、kobject、ktype的设置
							-> irq_of_parse_and_map(child, 0);      //将中断解析并映射到linux virq空间(
							-> of_node_get(child);    //将OF节点与设备结构相关联
							-> phy->dev.of_node = child;
							-> phy_device_register(phy)    // 注册phy设备
								-> if (phydev->bus->phy_map[phydev->addr]) //判断PHY是否已经注册了  
								-> phydev->bus->phy_map[phydev->addr] = phydev;  //添加PHY到总线的phy_map里
								-> phy_scan_fixups(phydev);     //执行匹配的fixups  
								-> device_add(&phydev->dev);   // 注册到linux设备模型框架中
						->  if (!scanphys)   return 0;       // 如果从子节点的"reg"属性中获得PHY设备的地址,scanphys=false,这里就直接返回了,因为不需要再扫描了
------------一般来说只要设备树种指定了PHY设备的"reg"属性,后面的流程可以自动忽略 ------------
	-> register_netdev(ndev)                  // 向内核注册net_device

注:PHY的状态机(核心WORK)后续解析 PHY状态机以及网络相关操作命令解析

四、PHY设备驱动

PHY设备驱动是基于device、driver、bus的连接方式
总线 ———— struct mii_bus
设备 ———— struct phy_device
驱动 ———— struct phy_driver
注:phy设备不像i2c和spi有一个board_info函数进行设备的添加,而是直接读取phy中的寄存器
<根据IEEE的规定,PHY芯片的前16个寄存器的内容必须是固定的>

路径:kernel/drivers/net/phy/phy_device.c

static int __init phy_init(void)
{
    int rc;

    rc = mdio_bus_init();                  // mdio_bus总线注册
    if (rc)
        return rc;

    rc = phy_drivers_register(genphy_driver,     // 注册通用的PHY设备驱动  
                  ARRAY_SIZE(genphy_driver));
    if (rc)
        mdio_bus_exit();

    return rc;
}
subsys_initcall(phy_init);
// 注:subsys_initcall(phy_init) 这行的作用非常重要,这一行就决定了内核在启动的时候会调用该函数,注册完了之后紧接着又注册一个通用的PHY驱动。
static struct class mdio_bus_class = {
    .name       = "mdio_bus",
    .dev_release    = mdiobus_release,
};

// 设备和驱动的匹配函数
static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
    struct phy_device *phydev = to_phy_device(dev);    // 获取PHY设备  
    struct phy_driver *phydrv = to_phy_driver(drv);    // 获取PHY驱动  

    if (of_driver_match_device(dev, drv))
        return 1;

    if (phydrv->match_phy_device)
        return phydrv->match_phy_device(phydev);

    return (phydrv->phy_id & phydrv->phy_id_mask) ==
        (phydev->phy_id & phydrv->phy_id_mask);          // 比较phy_id 
}

struct bus_type mdio_bus_type = {
    .name       = "mdio_bus",                //总线名称
    .match      = mdio_bus_match,            //用来匹配总线上设备和驱动的函数
    .pm     = MDIO_BUS_PM_OPS,
    .dev_groups = mdio_dev_groups,
};
EXPORT_SYMBOL(mdio_bus_type);

int __init mdio_bus_init(void)
{
    int ret;

    ret = class_register(&mdio_bus_class);            //注册设备类   /sys/class/mdio_bus
    if (!ret) {
        ret = bus_register(&mdio_bus_type);           //总线注册    /sys/bus/mdio_bus
        if (ret)
            class_unregister(&mdio_bus_class);
    }

    return ret;
}

注:在phy_init函数中不仅注册了mdio_bus总线,还注册了一个通用的PHY驱动作为缺省的内核PHY驱动,但是如果PHY芯片的内部寄存器和802.3定义的并不一样或者需要特殊的功能配置以实现更强的功能,这就需要专有的驱动。
一般在内核源码 drivers\net\phy目录下都有对应的驱动,以AT8035为例

static struct phy_driver at803x_driver[] = {
{
    /* ATHEROS 8035 */
    .phy_id         = ATH8035_PHY_ID,
    .name           = "Atheros 8035 ethernet",
    .phy_id_mask        = 0xffffffef,
    .probe          = at803x_probe,
    .config_init        = at803x_config_init,
    .link_change_notify = at803x_link_change_notify,
    .set_wol        = at803x_set_wol,
    .get_wol        = at803x_get_wol,
    .suspend        = at803x_suspend,
    .resume         = at803x_resume,
    .features       = PHY_GBIT_FEATURES,
    .flags          = PHY_HAS_INTERRUPT,
    .config_aneg        = genphy_config_aneg,
    .read_status        = genphy_read_status,
    .driver         = {
        .owner = THIS_MODULE,
    },
}, {
    /* ATHEROS 8030 */
    .phy_id         = ATH8030_PHY_ID,
    .name           = "Atheros 8030 ethernet",
    .phy_id_mask        = 0xffffffef,
    .probe          = at803x_probe,
    .config_init        = at803x_config_init,
    .link_change_notify = at803x_link_change_notify,
    .set_wol        = at803x_set_wol,
    .get_wol        = at803x_get_wol,
    .suspend        = at803x_suspend,
    .resume         = at803x_resume,
    .features       = PHY_GBIT_FEATURES,
    .flags          = PHY_HAS_INTERRUPT,
    .config_aneg        = genphy_config_aneg,
    .read_status        = genphy_read_status,
    .driver         = {
        .owner = THIS_MODULE,
    },
}, {
    /* ATHEROS 8031 */
    .phy_id         = ATH8031_PHY_ID,
    .name           = "Atheros 8031 ethernet",
    .phy_id_mask        = 0xffffffef,
    .probe          = at803x_probe,
    .config_init        = at803x_config_init,
    .link_change_notify = at803x_link_change_notify,
    .set_wol        = at803x_set_wol,
    .get_wol        = at803x_get_wol,
    .suspend        = at803x_suspend,
    .resume         = at803x_resume,
    .features       = PHY_GBIT_FEATURES,
    .flags          = PHY_HAS_INTERRUPT,
    .config_aneg        = genphy_config_aneg,
    .read_status        = genphy_read_status,
    .ack_interrupt      = &at803x_ack_interrupt,
    .config_intr        = &at803x_config_intr,
    .driver         = {
        .owner = THIS_MODULE,
    },
} };

module_phy_driver(at803x_driver);

static struct mdio_device_id __maybe_unused atheros_tbl[] = {
    { ATH8030_PHY_ID, 0xffffffef },
    { ATH8031_PHY_ID, 0xffffffef },
    { ATH8035_PHY_ID, 0xffffffef },
    { }
};

// 生成一个名为__mod_mdio__realtek_tbl_device_table,
// 内核构建时,depmod程序会在所有模块中搜索符号__mod_mdio__realtek_tbl_device_table
// 把数据(设备列表)从模块中抽出,添加到映射文件 /lib/modules/KERNEL_VERSION/modules.mdiomap 中
// 当depmod结束之后,所有的MDIO设备连同他们的模块名字都被该文件列出。
// 在需要驱动的时候,由modules.mdiomap 文件来找寻恰当的驱动程序。
// 模块加载时候可以研究下。。。。。。。
MODULE_DEVICE_TABLE(mdio, atheros_tbl);

1、同一品牌的PHY设备有多种不同的型号,内核为了支持一次可以注册多个型号的PHY的驱动,在include\linux\phy.h中提供了用于注册PHY驱动的宏module_phy_driver。

#define phy_module_driver(__phy_drivers, __count)           \
static int __init phy_module_init(void)                 \
{                                   \
    return phy_drivers_register(__phy_drivers, __count);        \
}                                   \
                     \
#define module_phy_driver(__phy_drivers)                \
    phy_module_driver(__phy_drivers, ARRAY_SIZE(__phy_drivers))

2、phy_driver_register定义

int phy_drivers_register(struct phy_driver *new_driver, int n)
{
    int i, ret = 0;

    for (i = 0; i < n; i++) {
        ret = phy_driver_register(new_driver + i);                // 注册数组中所有的phy驱动
                -> new_driver->driver.name = new_driver->name;    // 驱动名称
                -> new_driver->driver.bus = &mdio_bus_type;       // 驱动挂载的总线
                -> new_driver->driver.probe = phy_probe;          // PHY设备和驱动匹配后调用的probe函数 
                -> new_driver->driver.remove = phy_remove;
                -> retval = driver_register(&new_driver->driver); // 向linux设备模型框架中注册device_driver驱动
        if (ret) {
            while (i-- > 0)
                phy_driver_unregister(new_driver + i);
            break;
        }
    }
    return ret;
}

3、设备和驱动通过phy_id匹配后,调用phy_probe

static int phy_probe(struct device *dev)
{
    struct phy_device *phydev = to_phy_device(dev);      // 获取PHY设备  
    struct device_driver *drv = phydev->dev.driver;
    struct phy_driver *phydrv = to_phy_driver(drv);      // 获取PHY驱动 
    hydev->drv = phydrv;                                 // 捆绑一下  
    
    if (!(phydrv->flags & PHY_HAS_INTERRUPT) &&          // 设置中断方式
        phy_interrupt_is_valid(phydev))
        phydev->irq = PHY_POLL;

    if (phydrv->flags & PHY_IS_INTERNAL)
        phydev->is_internal = true;

    mutex_lock(&phydev->lock);
    phydev->supported = phydrv->features;              // 设置设备特性
    phydev->advertising = phydev->supported;           // 设置设备特性
    phydev->state = PHY_READY;                         // 状态设置为ready
    
    if (phydev->drv->probe)                            // 判断驱动有probe方式
        err = phydev->drv->probe(phydev);              // 调用 

    mutex_unlock(&phydev->lock);

    return err;
}

设备驱动与控制驱动之间的框图
[ Linux IMX6ULL ] PHY驱动框架解析 —— MDIO总线 |CSDN创作打卡_第2张图片

你可能感兴趣的:(NXP(IMX系列),linux,nxp,c语言,phy,arm)