当前位置:网站首页>PHY驱动调试之 --- PHY控制器驱动(二)
PHY驱动调试之 --- PHY控制器驱动(二)
2022-07-05 06:28:00 【恩来贺】
1. 前言
内核版本:linux 4.9.225,以freescale为例。
2. 概述
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的驱动一样,分为控制器驱动和设备器驱动。本节先讲控制器驱动。
3. PHY的控制器驱动总述
PHY的控制器驱动和SPI/I2C非常类似,控制器的核心功能是实现具体的读写功能。区别在于PHY的控制器读写功能的实现大致可以分为两种方式():
- 直接调用CPU的MDIO控制器(直接调用cpu对应的寄存器)的方式
- 通过GPIO/外围soc模拟MDIO时序的方式。
PHY的控制器一般被描述为mdio_bus平台设备(注意:这是一个设备,等同于SPI/I2C中的master设备;和总线、驱动、设备中的bus不是一个概念)。
既然是平台设备,那么设备树中必定要有可以被解析为平台设备的节点,也要有对应的平台设备驱动。与SPI驱动类似,PHY设备模型也是在控制器驱动的probe函数中注册的。
本文两者都会涉及,主要讲后者。
4. 通过GPIO/外围soc模拟MDIO时序的方式
4.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: [email protected] {
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: [email protected] { //描述控制器下挂PHY设备的节点
reg = <0x0>; //PHY的地址
};
};
4.2 控制器平台驱动代码走读
4.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); //注册控制器平台设备驱动
4.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, 0, NULL)
| | |---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设备
|
******************************************************************************************************************/
5. 直接调用CPU的MDIO控制器的方式
5.1 控制器平台设备在设备树中的大致描述方式(不完全准确,主要描述匹配的规则)
# linux4.9.225\Documentation\devicetree\bindings\powerpc\fsl\fman.txt
Example for FMan v3 internal MDIO:
[email protected] { //描述MDIO控制器驱动节点
compatible = "fsl,fman-mdio";
reg = <0xe3120 0xee0>;
fsl,fman-internal-mdio;
tbi1: [email protected] { //描述控制器下挂PHY设备的节点
reg = <0x8>;
device_type = "tbi-phy";
};
};
5.2 控制器平台驱动代码走读
5.2.1 控制器平台驱动的注册
# 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); //注册控制器平台设备驱动
5.2.2 控制器平台驱动的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的流程与第四小节一致,这里就不再列出。
6. 控制器的读写会在哪里得到调用?
在PHY设备的注册中(读PHY ID)、PHY的初始化、自协商、中断、状态、能力获取等流程中经常可以看到phy_read和phy_write两个函数(下一节要讲的PHY驱动),这两个函数的实现就依赖于控制器设备mii_bus的读写。
phy_read和phy_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_read和mdiobus_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->read和bus->write读写接口在这里得到调用。
边栏推荐
- 2022-5-the fourth week daily
- Package webapp or H5 pages into apps
- How to correctly ask questions in CSDN Q & A
- 1.手动创建Oracle数据库
- The route of wechat applet jumps again without triggering onload
- [Chongqing Guangdong education] 1185t administrative leadership reference test of National Open University in autumn 2018
- Knapsack problem acwing 9 Group knapsack problem
- 5. Oracle TABLESPACE
- Record of problems in ollvm compilation
- Find the combination number acwing 888 Find the combination number IV
猜你喜欢
Instruction execution time
TCP's understanding of three handshakes and four waves
Mutual transformation between two-dimensional array and sparse array (sparse matrix)
Qt项目中的日志库log4qt使用
7.Oracle-表结构
vsCode创建自己的代码模板
Alibaba's new member "Lingyang" officially appeared, led by Peng Xinyu, Alibaba's vice president, and assembled a number of core department technical teams
Marvell 88e1515 PHY loopback mode test
安装OpenCV--conda建立虚拟环境并在jupyter中添加此环境的kernel
Game theory acwing 893 Set Nim game
随机推荐
Integer to 8-bit binary explanation (including positive and negative numbers) scope of application -127~+127
Install opencv -- CONDA to establish a virtual environment and add the kernel of this environment in jupyter
Vscode configures the typera editor for MD
CGroup CPU group source code analysis
MQClientException: No route info of this topic: type_ topic
Instruction execution time
H5内嵌App适配暗黑模式
Markdown syntax
Sum of two numbers, the numbers in the array are converted to decimal, added, and output inversely
VLAN experiment
Vant Weapp SwipeCell設置多個按鈕
微信小程序路由再次跳转不触发onload
Vscode creates its own code template
代码中的英语全部
The route of wechat applet jumps again without triggering onload
What is linting
Architecture
Design specification for mobile folding screen
Find the combination number acwing 888 Find the combination number IV
SRE核心体系了解