【以太网驱动】以太网扫盲篇二:PHY的控制器驱动框架分析

嵌入式悦翔园 2023-07-26 11:40

关注星标公众号,第一时间获取信息

还没有学习第一篇内容的建议从第一篇开始学习会比较容易理解【以太网驱动】以太网扫盲篇一:各种网络总线 mii总线,mdio总线介绍

1. 概述

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

PHY与MAC整体的大致连接框架如下(图片来源于网络):

PHY的整个硬件系统组成比较复杂,PHY与MAC相连(也可以通过一个中间设备相连),MAC与CPU相连(有集成在内部的,也有外接的方式)。

PHY与MAC通过MII和MDIO/MDC相连,MII是走网络数据的,MDIO/MDC是用来与PHY的寄存器通讯的,对PHY进行配置。

PHY的驱动与I2C/SPI的驱动一样,分为控制器驱动设备器驱动。本节先讲控制器驱动。

2. PHY的控制器驱动总述

PHY的控制器驱动和SPI/I2C非常类似,控制器的核心功能是实现具体的读写功能。区别在于PHY的控制器读写功能的实现大致可以分为两种方式():

直接调用CPU的MDIO控制器(直接调用cpu对应的寄存器)的方式; 通过GPIO/外围soc模拟MDIO时序的方式; PHY的控制器一般被描述为mdio_bus平台设备(注意:这是一个设备,等同于SPI/I2C中的master设备;和总线、驱动、设备中的bus不是一个概念)。

既然是平台设备,那么设备树中必定要有可以被解析为平台设备的节点,也要有对应的平台设备驱动。与SPI驱动类似,PHY设备模型也是在控制器驱动的probe函数中注册的。

3. 通过GPIO/外围soc模拟MDIO时序的方式

3.1 控制器平台设备在设备树中的大致描述方式(不完全准确,主要描述匹配的规则)

# linux-4.9.225\Documentation\devicetree\bindings\soc\fsl\cpm_qe\network.txt
* MDIO
Currently defined compatibles: fsl,pq1-fec-mdio (reg is same as first resource of FEC device) fsl,cpm2-mdio-bitbang (reg is port C registers)
Properties for fsl,cpm2-mdio-bitbang: 
fsl,mdio-pin : pin of port C controlling mdio data 
fsl,mdc-pin : pin of port C controlling mdio clock
Example: mdio@10d40 { 
 compatible = "fsl,mpc8272ads-mdio-bitbang",
     "fsl,mpc8272-mdio-bitbang",
        "fsl,cpm2-mdio-bitbang";
  reg = <10d40 14>;
  #address-cells = <1>;
  #size-cells = <0>;
  fsl,mdio-pin = <12>;
  fsl,mdc-pin = <13>; 
  
 # linux-4.9.225\Documentation\devicetree\bindings\phy 
 xxx_phy: xxx-phy@xxx {                       //描述控制器下挂PHY设备的节点
  reg = <0x0>;                             //PHY的地址
 };   
};

3.2 控制器平台驱动代码走读

3.2.1 控制器平台驱动的注册

static const struct of_device_id fs_enet_mdio_bb_match[] = {
 {
  .compatible = "fsl,cpm2-mdio-bitbang",           //匹配平台设备的名称
 },
 {},
};
MODULE_DEVICE_TABLE(of, fs_enet_mdio_bb_match);
 
static struct platform_driver fs_enet_bb_mdio_driver = {
 .driver = {
  .name = "fsl-bb-mdio",
  .of_match_table = fs_enet_mdio_bb_match,
 },
 .probe = fs_enet_mdio_probe,
 .remove = fs_enet_mdio_remove,
};
 
module_platform_driver(fs_enet_bb_mdio_driver);     //注册控制器平台设备驱动

3.2.2 控制器平台驱动的probe函数走读

/**********************************************************************************************
            通过GPIO/外围soc模拟MDIO时序方式的MDIO驱动(probe函数中完成PHY设备的创建和注册)
***********************************************************************************************/

# linux-4.9.225\drivers\net\ethernet\freescale\fs_enet\mii-bitbang.c
 
fs_enet_mdio_probe(struct platform_device *ofdev)
|--- bitbang = kzalloc(sizeof(struct bb_info), GFP_KERNEL)
|
|--- bitbang->ctrl.ops = &bb_ops  ----------------------------------------------->| static struct mdiobb_ops bb_ops = { 
|                   |  .owner = THIS_MODULE, 
|                                                                                 |  .set_mdc = mdc,
|                                                                                 |  .set_mdio_dir = mdio_dir,
|            |  .set_mdio_data = mdio,               |-->实现为GPIO的读写
|            |  .get_mdio_data = mdio_read,
|            | };  
|                                                                                  \<---------------------------------------------------------|
|--- new_bus = alloc_mdio_bitbang(&bitbang->ctrl)                                                                                             |
|    |--- bus = mdiobus_alloc()        -----------|                  | struct mdiobb_ctrl *ctrl = bus->priv|  |
|    |--- bus->read = mdiobb_read      -----------|                                                  | ctrl->ops->set_mdc                  |  |
|    |--- bus->write = mdiobb_write    -----------|--mdiobb_read/mdiobb_write/mdiobb_reset函数的实现 -| ctrl->ops->set_mdio_dir             |--|
|    |--- bus->reset = mdiobb_reset    -----------|       /                                          | ctrl->ops->set_mdio_data            |
|    |--- bus->priv = ctrl  <----------------------------                                            | ctrl->ops->get_mdio_data            |
|                                                                                                  
|--- fs_mii_bitbang_init                                      //设置用来模拟mdc和mdio的管脚资源
|    |--- of_address_to_resource(np, 0, &res)                 //转换设备树地址并作为资源返回,设备树中指定
|    |    
|    |--- snprintf(bus->id, MII_BUS_ID_SIZE, "%x", res.start) //把资源的起始地址设置为bus->id
|    |
|    |--- data = of_get_property(np, "fsl,mdio-pin", &len)
|    |--- mdio_pin = *data                                    //决定控制mdio数据的端口的引脚
|    |
|    |--- data = of_get_property(np, "fsl,mdc-pin", &len)
|    |--- mdc_pin = *data                                     //控制mdio时钟的端口引脚
|    |
|    |--- bitbang->dir = ioremap(res.start, resource_size(&res)) 
|    |
|    |--- bitbang->dat = bitbang->dir + 4                        
|    |--- bitbang->mdio_msk = 1 << (31 - mdio_pin)               
|    |--- bitbang->mdc_msk = 1 << (31 - mdc_pin)
|    
|--- of_mdiobus_register(new_bus, ofdev->dev.of_node)       //注册mii_bus设备,并通过设备树子节点创建PHY设备 <===of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
|    |--- mdio->phy_mask = ~0                               //屏蔽所有PHY,防止自动探测。相反,设备树中列出的phy将在总线注册后填充
|    |--- mdio->dev.of_node = np
|    |--- mdiobus_register(mdio)                            //@注意@ 注册MDIO总线设备(注意是总线设备不是总线,因为总线也是一种设备。mdio_bus是在其他地方注册的,后面会讲到)
|    |    |--- __mdiobus_register(bus, THIS_MODULE)
|    |    |    |--- bus->owner = owner
|    |    |    |--- bus->dev.parent = bus->parent 
|    |    |    |--- bus->dev.class = &mdio_bus_class
|    |    |    |--- bus->dev.groups = NULL
|    |    |    |--- dev_set_name(&bus->dev, "%s", bus->id)  //设置总线设备的名称
|    |    |    |--- device_register(&bus->dev)              //注册总线设备
|    | 
|    |--- 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)                                //如果未获得子节点的"reg"属性,则在后面再启用扫描可能存在的PHY的,然后注册
|         |    |--- scanphys = true 
|         |    |--- continue
|         | 
|         |--- 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_device_create(mdio, addr, phy_id, 0NULL)   
|         |    |---else //我这里采用的是else分支
|         |    |    |---phy = get_phy_device(mdio, addr, is_c45)        //在@bus上的@addr处读取PHY的ID寄存器,然后分配并返回表示它的phy_device
|         |    |        |--- get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids)        //通过mdio得到PHY的ID
|         |    |        |--- phy_device_create(bus, addr, phy_id, is_c45, &c45_ids)  //创建PHY设备
|         |    |             |--- struct phy_device *dev
|         |    |             |--- struct mdio_device *mdiodev
|         |    |             |--- dev = kzalloc(sizeof(*dev), GFP_KERNEL)
|         |    |             |--- mdiodev = &dev->mdio                     //mdiodev是最新的内核引入,较老的版本没有这个结构
|         |    |             |--- mdiodev->dev.release = phy_device_release
|         |    |             |--- mdiodev->dev.parent = &bus->dev
|         |    |             |--- mdiodev->dev.bus = &mdio_bus_type        //PHY设备和驱动都会挂在mdio_bus下,匹配时会调用对应的match函数  ---|        
|         |    |             |--- mdiodev->bus = bus                                                                                         |
|         |    |             |--- mdiodev->pm_ops = MDIO_BUS_PHY_PM_OPS                                                                      |
|         |    |             |--- mdiodev->bus_match = phy_bus_match       //真正实现PHY设备和驱动匹配的函数<--------------------------------|
|         |    |             |--- mdiodev->addr = addr
|         |    |             |--- mdiodev->flags = MDIO_DEVICE_FLAG_PHY
|         |    |             |--- mdiodev->device_free = phy_mdio_device_free
|         |    |             |--- diodev->device_remove = phy_mdio_device_remove
|         |    |             |--- dev->speed = SPEED_UNKNOWN
|         |    |             |--- dev->duplex = DUPLEX_UNKNOWN
|         |    |             |--- 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->phy_id = phy_id
|         |    |             |--- if (c45_ids)
|         |    |             |    |--- dev->c45_ids = *c45_ids
|         |    |             |--- dev->irq = bus->irq[addr]
|         |    |             |--- dev_set_name(&mdiodev->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(&mdiodev->dev) //设备模型中的一些设备,主要是kset、kobject、ktype的设置
|         |    |  
|         |    |--- irq_of_parse_and_map(child, 0//将中断解析并映射到linux virq空间(未深入研究)
|         |    |--- if (of_property_read_bool(child, "broken-turn-around"))//MDIO总线中的TA(Turnaround time)
|         |    |    |--- mdio->phy_ignore_ta_mask |= 1 << addr
|         |    | 
|         |    |--- of_node_get(child)//将OF节点与设备结构相关联,以便以后查找
|         |    |--- phy->mdio.dev.of_node = child
|         |    |
|         |    |--- phy_device_register(phy)//注册PHY设备
|         |    |    |--- mdiobus_register_device(&phydev->mdio) //注册到mdiodev->bus,其实笔者认为这是一个虚拟的注册,仅仅是根据PHY的地址在mdiodev->bus->mdio_map数组对应位置填充这个mdiodev 
|         |    |    |    |--- mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev  // 方便通过mdiodev->bus统一管理和查找,以及关联bus的读写函数,方便PHY的功能配置
|         |    |    |
|         |    | |--- device_add(&phydev->mdio.dev)//注册到linux设备模型框架中
|         |
|         |--- if (!scanphys)  //如果从子节点的"reg"属性中获得PHY设备的地址,scanphys=false,这里就直接返回了,因为不需要再扫描了
|         |    |--- return 0 
|         |
/******************************************************************************************************************
 一般来说只要设备树种指定了PHY设备的"reg"属性,后面的流程可以自动忽略
******************************************************************************************************************
|         |--- for_each_available_child_of_node(np, child)       //自动扫描具有空"reg"属性的PHY
|              |--- if (of_find_property(child, "reg", NULL))    //跳过具有reg属性集的PHY
|              |    |--- continue
|              |  
|              |--- for (addr = 0; addr < PHY_MAX_ADDR; addr++)         //循环遍历扫描
|                   |--- if (mdiobus_is_registered_device(mdio, addr))  //跳过已注册的PHY
|                   |    |--- continue
|                   |
|                   |--- dev_info(&mdio->dev, "scan phy %s at address %i\n", child->name, addr) //打印扫描的PHY,建议开发人员设置"reg"属性
|                   |
|                   |--- if (of_mdiobus_child_is_phy(child))
|                        |--- of_mdiobus_register_phy(mdio, child, addr) //注册PHY设备
|                                                                       
******************************************************************************************************************/

4. 直接调用CPU的MDIO控制器的方式

控制器平台设备在设备树中的大致描述方式(不完全准确,主要描述匹配的规则)

# linux4.9.225\Documentation\devicetree\bindings\powerpc\fsl\fman.txt
Example for FMan v3 internal MDIO:
mdio@e3120 {                                 //描述MDIO控制器驱动节点
 compatible = "fsl,fman-mdio";
 reg = <0xe3120 0xee0>;
 fsl,fman-internal-mdio;
 tbi1: tbi-phy@8 {                       //描述控制器下挂PHY设备的节点
  reg = <0x8>;
  device_type = "tbi-phy"
 }; 
};

控制器平台驱动的注册

# linux-4.9.225\drivers\net\ethernet\freescale\fsl_pq_mdio.c
static const struct of_device_id fsl_pq_mdio_match[] = {
 ......
 
 /* No Kconfig option for Fman support yet */
 {
  .compatible = "fsl,fman-mdio",                  //匹配平台设备的名称
  .data = &(struct fsl_pq_mdio_data) {
   .mii_offset = 0,
   /* Fman TBI operations are handled elsewhere */
  },
 },
 ......
 {},
};
 
static struct platform_driver fsl_pq_mdio_driver = {
 .driver = {
  .name = "fsl-pq_mdio",
  .of_match_table = fsl_pq_mdio_match,
 },
 .probe = fsl_pq_mdio_probe,
 .remove = fsl_pq_mdio_remove,
};
 
module_platform_driver(fsl_pq_mdio_driver);          //注册控制器平台设备驱动

控制器平台驱动的probe函数走读

/****************************************************************************************
          直接调用CPU的MDIO控制器的方式的MDIO控制器驱动(probe函数中涉及PHY设备的创建和注册)
****************************************************************************************/

# linux-4.9.225\drivers\net\ethernet\freescale\fsl_pq_mdio.c
 
fsl_pq_mdio_probe(struct platform_device *pdev
|--- struct fsl_pq_mdio_priv *priv
|--- struct mii_bus *new_bus
|
|--- new_bus = mdiobus_alloc_size(sizeof(*priv))  //分配结构体
|--- priv = new_bus->priv
|--- new_bus->name = "Freescale PowerQUICC MII Bus"
|--- new_bus->read = &fsl_pq_mdio_read           //总线的读接口
|--- new_bus->write = &fsl_pq_mdio_write         //总线的写接口
|--- new_bus->reset = &fsl_pq_mdio_reset         //总线的复位接口

|--- of_address_to_resource(np, 0, &res)     //获取控制器地址资源
|--- snprintf(bus->id, MII_BUS_ID_SIZE, "%x", res.start)    //把资源的起始地址设置为bus->id                  
|    
|--- of_mdiobus_register(new_bus, np)//注册mii_bus设备,并通过设备树中控制器的子节点创建PHY设备,这一点与模拟方式流程相同  

of_mdiobus_register的流程与第四小节一致,这里就不再列出。

5. 控制器的读写会在哪里得到调用?

在PHY设备的注册中(读PHY ID)、PHY的初始化、自协商、中断、状态、能力获取等流程中经常可以看到phy_readphy_write两个函数(下一节要讲的PHY驱动),这两个函数的实现就依赖于控制器设备mii_bus的读写。

phy_readphy_write定义在linux-4.9.225\include\linux\phy.h中,如下:

static inline int phy_read(struct phy_device *phydev, u32 regnum)
{
 return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, regnum);
}
 
static inline int phy_write(struct phy_device *phydev, u32 regnum, u16 val)
{
 return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val);
}

其中mdiobus_readmdiobus_write定义在linux-4.9.225\drivers\net\phy\mdio_bus.c中,如下:

/**
 * mdiobus_read - Convenience function for reading a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to read
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */

int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{
 int retval;
 
 BUG_ON(in_interrupt());
 
 mutex_lock(&bus->mdio_lock);
 retval = bus->read(bus, addr, regnum);
 mutex_unlock(&bus->mdio_lock);
 
 return retval;
}
 
/**
 * mdiobus_write - Convenience function for writing a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */

int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
 int err;
 
 BUG_ON(in_interrupt());
 
 mutex_lock(&bus->mdio_lock);
 err = bus->write(bus, addr, regnum, val);
 mutex_unlock(&bus->mdio_lock);
 
 return err;
}

可以清楚的看到bus->readbus->write读写接口在这里得到调用。

6. mdio_bus总线

接下来要讲的PHY设备驱动是基于device、driver、bus的连接方式。其驱动涉及如下几个重要部分:

  • 总线 - sturct mii_bus (mii stand for media independent interface)
  • 设备 - struct phy_device
  • 驱动 - struct phy_driver

关于PHY设备的创建和注册已经在第5节的probe函数中有过详细的描述(需要注意的是:phy设备不像i2c/spi有一个board_info函数进行设备的添加,而是直接读取phy中的寄存器<根据IEEE的规定,PHY芯片的前16个寄存器的内容必须是固定的>),本节就不再描述;

总线注册的入口函数

# linux-4.9.225\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,ARRAY_SIZE(genphy_driver), THIS_MODULE); //通用PHY驱动
 if (rc)
  mdio_bus_exit();
 
 return rc;
}
 
subsys_initcall(phy_init); 

subsys_initcall(phy_init) 这行的作用非常重要,这一行就决定了内核在启动的时候会调用该函数,注册完了之后紧接着又注册一个通用的PHY驱动。

总线注册函数--- mdio_bus_init解析

# linux-4.9.225\drivers\net\phy\mdio_bus.c
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 mdio_device *mdio = to_mdio_device(dev);
 
 if (of_driver_match_device(dev, drv))
  return 1;
 
 if (mdio->bus_match)
  return mdio->bus_match(dev, drv);
 
 return 0;
}
 
struct bus_type mdio_bus_type = {
 .name  = "mdio_bus",     //总线名称
 .match  = mdio_bus_match, //用来匹配总线上设备和驱动的函数
 .pm  = MDIO_BUS_PM_OPS,
};
EXPORT_SYMBOL(mdio_bus_type);
 
int __init mdio_bus_init(void)
{
 int ret;
 
 ret = class_register(&mdio_bus_class); //注册设备类 (在linux设备模型中,我再仔细讲这个类的概念)
 if (!ret) {
  ret = bus_register(&mdio_bus_type);//总线注册
  if (ret)
   class_unregister(&mdio_bus_class);
 }
 
 return ret;
}

总线中的match函数解析

/**
 * mdio_bus_match - determine if given MDIO driver supports the given
 *      MDIO device
 * @dev: target MDIO device
 * @drv: given MDIO driver
 *
 * Description: Given a MDIO device, and a MDIO driver, return 1 if
 *   the driver supports the device.  Otherwise, return 0. This may
 *   require calling the devices own match function, since different classes
 *   of MDIO devices have different match criteria.
 */

static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
 struct mdio_device *mdio = to_mdio_device(dev);
 
 if (of_driver_match_device(dev, drv))
  return 1;
 
 if (mdio->bus_match)               //实现匹配的函数
  return mdio->bus_match(dev, drv);
 
 return 0;
}

7. 设备驱动的注册

phy_init函数中不仅注册了mdio_bus总线,还注册了一个通用的PHY驱动作为缺省的内核PHY驱动,但是如果PHY芯片的内部寄存器和802.3定义的并不一样或者需要特殊的功能配置以实现更强的功能,这就需要专有的驱动。

关于通用PHY驱动的知识,网上有一大堆讲解,本节就不再重复的去描述。

对于市场上存在的主流PHY品牌,一般在内核源码 drivers\net\phy目录下都有对应的驱动。本节主要以realtek RTL8211F为例,讲述PHY的驱动,代码如下:

# linux-4.9.225\drivers\net\phy\realtek.c
static struct phy_driver realtek_drvs[] = {
 ......
 , {
  .phy_id  = 0x001cc916,
  .name  = "RTL8211F Gigabit Ethernet",
  .phy_id_mask = 0x001fffff,
  .features = PHY_GBIT_FEATURES,
  .flags  = PHY_HAS_INTERRUPT,
  .config_aneg = &genphy_config_aneg,
  .config_init = &rtl8211f_config_init,
  .read_status = &genphy_read_status,
  .ack_interrupt = &rtl8211f_ack_interrupt,
  .config_intr = &rtl8211f_config_intr,
  .suspend = genphy_suspend,
  .resume  = genphy_resume,
 },
};
 
module_phy_driver(realtek_drvs);                           //注册PHY驱动
 
static struct mdio_device_id __maybe_unused realtek_tbl[] = {
 { 0x001cc9120x001fffff },
 { 0x001cc9140x001fffff },
 { 0x001cc9150x001fffff },
 { 0x001cc9160x001fffff },
 { }
};
 
MODULE_DEVICE_TABLE(mdio, realtek_tbl);

phy驱动的注册

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

# linux-4.9.225\include\linux\phy.h
 
#define phy_module_driver(__phy_drivers, __count)   \
static int __init phy_module_init(void)     \
{         \
 return phy_drivers_register(__phy_drivers, __count, THIS_MODULE); \

 
#define module_phy_driver(__phy_drivers)    \
 phy_module_driver(__phy_drivers, ARRAY_SIZE(__phy_drivers))

2、其中phy_driver_register定义如下(注意这里与老版本内核有一定的改动)

/**
 * phy_driver_register - register a phy_driver with the PHY layer
 * @new_driver: new phy_driver to register
 * @owner: module owning this PHY
 */

int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
 int retval;
 
 new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
 new_driver->mdiodrv.driver.name = new_driver->name;//驱动名称
 new_driver->mdiodrv.driver.bus = &mdio_bus_type;   //驱动挂载的总线
 new_driver->mdiodrv.driver.probe = phy_probe;      //PHY设备和驱动匹配后调用的probe函数 
 new_driver->mdiodrv.driver.remove = phy_remove;
 new_driver->mdiodrv.driver.owner = owner;
 
 retval = driver_register(&new_driver->mdiodrv.driver); //向linux设备模型框架中注册device_driver驱动
 if (retval) {
  pr_err("%s: Error %d in registering driver\n",
         new_driver->name, retval);
 
  return retval;
 }
 
 pr_debug("%s: Registered new driver\n", new_driver->name);
 
 return 0;
}
 
int phy_drivers_register(struct phy_driver *new_driver, int n,
    struct module *owner)

{
 int i, ret = 0;
 
 for (i = 0; i < n; i++) {
  ret = phy_driver_register(new_driver + i, owner);//注册数组中所有的phy驱动
  if (ret) {
   while (i-- > 0)
    phy_driver_unregister(new_driver + i);
   break;
  }
 }
 return ret;
}

8. 设备驱动与控制器驱动之间的关系图

原文链接:https://www.cnblogs.com/jianhua1992/category/2242540.html ;本文仅作交流分享,版权归原作者所有,如有侵权,请联系作者删除。

推荐阅读



01

加入嵌入式交流群


02

嵌入式资源获取


03

STM32中断优先级详解


04

STM32下载程序新思路--使用串口下载程序


嵌入式悦翔园 专注于嵌入式技术,包括但不限于STM32、Arduino、51单片机、物联网、Linux等编程学习笔记,同时包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论 (0)
  • 案例概况在丹麦哥本哈根,西门子工程师们成功完成了一项高安全设施的数据集成项目。他们利用宏集Cogent DataHub软件,将高安全设施内的设备和仪器与远程监控位置连接起来,让技术人员能够在不违反安全规定、不引入未经授权人员的情况下,远程操作所需设备。突破OPC 服务器的远程连接难题该项目最初看似是一个常规的 OPC 应用:目标是将高安全性设施中的冷水机(chiller)设备及其 OPC DA 服务器,与远程监控站的两套 SCADA 系统(作为 OPC DA 客户端)连接起来。然而,在实际实施过
    宏集科技 2025-03-27 13:20 120浏览
  • 文/陈昊编辑/cc孙聪颖‍2025 年,作为中国实施制造强国战略第一个十年计划的关键里程碑,被赋予了极为重大的意义。两会政府工作报告清晰且坚定地指出,要全力加速新质生产力的发展进程,推动传统产业全方位向高端化、智能化与绿色化转型。基于此,有代表敏锐提议,中国制造应从前沿技术的应用切入,逐步拓展至产业生态的构建,最终延伸到提升用户体验的维度,打出独树一帜、具有鲜明特色的发展牌。正是在这样至关重要的时代背景之下,于 AWE 2025(中国家电及消费电子博览会)这一备受瞩目的舞台上,高端厨房的中国方案
    华尔街科技眼 2025-03-25 16:10 90浏览
  • 长期以来,智能家居对于大众家庭而言就像空中楼阁一般,华而不实,更有甚者,还将智能家居认定为资本家的营销游戏。商家们举着“智慧家居、智慧办公”的口号,将原本价格亲民、能用几十年的家电器具包装成为了高档商品,而消费者们最终得到的却是家居设备之间缺乏互操作性、不同品牌生态之间互不兼容的碎片化体验。这种早期的生态割裂现象致使消费者们对智能家居兴趣缺失,也造就了“智能家居无用论”的刻板印象。然而,自Matter协议发布之后,“命运的齿轮”开始转动,智能家居中的生态割裂现象与品牌生态之间的隔阂正被基于IP架
    华普微HOPERF 2025-03-27 09:46 133浏览
  • 汽车导航系统市场及应用环境参照调研机构GII的研究报告中的市场预测,全球汽车导航系统市场预计将于 2030年达到472亿美元的市场规模,而2024年至2030年的年复合成长率则为可观的6.7%。汽车导航系统无疑已成为智能汽车不可或缺的重要功能之一。随着人们在日常生活中对汽车导航功能的日渐依赖,一旦出现定位不准确或地图错误等问题,就可能导致车主开错路线,平白浪费更多行车时间,不仅造成行车不便,甚或可能引发交通事故的发生。有鉴于此,如果想要提供消费者完善的使用者体验,在车辆开发阶段便针对汽车导航功能
    百佳泰测试实验室 2025-03-27 14:51 218浏览
  • ​2025年3月27日​,贞光科技授权代理品牌紫光同芯正式发布新一代汽车安全芯片T97-415E。作为T97-315E的迭代升级产品,该芯片以大容量存储、全球化合规认证、双SPI接口协同为核心突破,直击智能网联汽车"多场景安全并行"与"出口合规"两大行业痛点,助力车企抢占智能驾驶与全球化市场双赛道。行业趋势锚定:三大升级回应智能化浪潮1. 大容量存储:破解车联网多任务瓶颈随着​车机功能泛在化​(数字钥匙、OTA、T-BOX等安全服务集成),传统安全芯片面临存储资源挤占难题。T97-415E创新性
    贞光科技 2025-03-27 13:50 168浏览
  • WT588F02B是广州唯创电子推出的一款高性能语音芯片,广泛应用于智能家电、安防设备、玩具等领域。然而,在实际开发中,用户可能会遇到烧录失败的问题,导致项目进度受阻。本文将从下载连线、文件容量、线路长度三大核心因素出发,深入分析烧录失败的原因并提供系统化的解决方案。一、检查下载器与芯片的物理连接问题表现烧录时提示"连接超时"或"设备未响应",或烧录进度条卡顿后报错。原因解析接口错位:WT588F02B采用SPI/UART双模通信,若下载器引脚定义与芯片引脚未严格对应(如TXD/RXD交叉错误)
    广州唯创电子 2025-03-26 09:05 150浏览
  • 在智能语音产品的开发过程中,麦克风阵列的选型直接决定了用户体验的优劣。广州唯创电子提供的单麦克风与双麦克风解决方案,为不同场景下的语音交互需求提供了灵活选择。本文将深入解析两种方案的性能差异、适用场景及工程实现要点,为开发者提供系统化的设计决策依据。一、基础参数对比分析维度单麦克风方案双麦克风方案BOM成本¥1.2-2.5元¥4.8-6.5元信噪比(1m)58-62dB65-68dB拾音角度全向360°波束成形±30°功耗8mW@3.3V15mW@3.3V典型响应延迟120ms80ms二、技术原
    广州唯创电子 2025-03-27 09:23 180浏览
  • 在电子设计中,电磁兼容性(EMC)是确保设备既能抵御外部电磁干扰(EMI),又不会对自身或周围环境产生过量电磁辐射的关键。电容器、电感和磁珠作为三大核心元件,通过不同的机制协同作用,有效抑制电磁干扰。以下是其原理和应用场景的详细解析:1. 电容器:高频噪声的“吸尘器”作用原理:电容器通过“通高频、阻低频”的特性,为高频噪声提供低阻抗路径到地,形成滤波效果。例如,在电源和地之间并联电容,可吸收电源中的高频纹波和瞬态干扰。关键应用场景:电源去耦:在IC电源引脚附近放置0.1μF陶瓷电容,滤除数字电路
    时源芯微 2025-03-27 11:19 186浏览
  • 六西格玛首先是作为一个量度质量水平的指标,它代表了近乎完美的质量的水平。如果你每天都吃一个苹果,有一间水果店的老板跟你说,他们所卖的苹果,质量达到六西格玛水平,换言之,他们每卖一百万个苹果,只会有3.4个是坏的。你算了一下,发现你如果要从这个店里买到一个坏苹果,需要805年。你会还会选择其他店吗?首先发明六西格玛这个词的人——比尔·史密斯(Bill Smith)他是摩托罗拉(Motorloa)的工程师,在追求这个近乎完美的质量水平的时候,发明了一套方法模型,开始时是MAIC,后来慢慢演变成DMA
    优思学院 2025-03-27 11:47 169浏览
  • 在智能终端设备开发中,语音芯片与功放电路的配合直接影响音质表现。广州唯创电子的WTN6、WT588F等系列芯片虽功能强大,但若硬件设计不当,可能导致输出声音模糊、杂音明显。本文将以WTN6与WT588F系列为例,解析音质劣化的常见原因及解决方法,帮助开发者实现清晰纯净的语音输出。一、声音不清晰的典型表现与核心原因当语音芯片输出的音频信号存在以下问题时,需针对性排查:背景杂音:持续的“沙沙”声或高频啸叫,通常由信号干扰或滤波不足导致。语音失真:声音断断续续或含混不清,可能与信号幅度不匹配或功放参数
    广州唯创电子 2025-03-25 09:32 113浏览
  • 在当今竞争激烈的工业环境中,效率和响应速度已成为企业制胜的关键。为了满足这一需求,我们隆重推出宏集Panorama COOX,这是Panorama Suite中首款集成的制造执行系统(MES)产品。这一创新产品将Panorama平台升级为全面的工业4.0解决方案,融合了工业SCADA和MES技术的双重优势,帮助企业实现生产效率和运营能力的全面提升。深度融合SCADA与MES,开启工业新纪元宏集Panorama COOX的诞生,源于我们对创新和卓越运营的不懈追求。通过战略性收购法国知名MES领域专
    宏集科技 2025-03-27 13:22 215浏览
  •       知识产权保护对工程师的双向影响      正向的激励,保护了工程师的创新成果与权益,给企业带来了知识产权方面的收益,企业的创新和发明大都是工程师的劳动成果,他们的职务发明应当受到奖励和保护,是企业发展的重要源泉。专利同时也成了工程师职称评定的指标之一,专利体现了工程师的创新能力,在求职、竞聘技术岗位或参与重大项目时,专利证书能显著增强个人竞争力。专利将工程师的创意转化为受法律保护的“无形资产”,避免技术成果被他人抄袭或无偿使
    广州铁金刚 2025-03-25 11:48 184浏览
  • 在嵌入式语音系统的开发过程中,广州唯创电子推出的WT588系列语音芯片凭借其优异的音质表现和灵活的编程特性,广泛应用于智能终端、工业控制、消费电子等领域。作为该系列芯片的关键状态指示信号,BUSY引脚的设计处理直接影响着系统交互的可靠性和功能拓展性。本文将从电路原理、应用场景、设计策略三个维度,深入解析BUSY引脚的技术特性及其工程实践要点。一、BUSY引脚工作原理与信号特性1.1 电气参数电平标准:输出3.3V TTL电平(与VDD同源)驱动能力:典型值±8mA(可直接驱动LED)响应延迟:语
    广州唯创电子 2025-03-26 09:26 216浏览
  • 家电,在人们的日常生活中扮演着不可或缺的角色,也是提升人们幸福感的重要组成部分,那你了解家电的发展史吗?#70年代结婚流行“四大件”:手表、自行车、缝纫机,收音机,合成“三转一响”。#80年代随着改革开放的深化,中国经济开始飞速发展,黑白电视机、冰箱、洗衣机这“新三件”,成为了人们对生活的新诉求。#90年代彩电、冰箱、全自动洗衣机开始大量进入普通家庭,快速全面普及,90年代末,家电产品实现了从奢侈品到必需品的转变。#00年代至今00年代,随着人们追求高品质生活的愿望,常用的电视机、洗衣机等已经远
    启英AI平台 2025-03-25 14:12 92浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦