MDIO总线驱动开发实战:Linux内核4.19下PHY寄存器读写3种方法
📅 2026/7/6 7:35:24
👁️ 阅读次数
📝 编程学习
MDIO总线驱动开发实战:Linux内核4.19下PHY寄存器读写3种方法
在嵌入式Linux驱动开发中,MDIO总线作为MAC与PHY之间的管理接口,其驱动开发是网络设备开发的关键环节。本文将深入探讨Linux内核4.19环境下三种不同的PHY寄存器操作方法,每种方法都配有可直接编译的内核驱动代码示例。
1. MDIO总线基础与内核框架
MDIO(Management Data Input/Output)总线是以太网标准中定义的二线制串行接口,由MDC(时钟线)和MDIO(数据线)组成。在Linux内核中,MDIO子系统提供了完整的框架支持,主要包括以下几个核心组件:
- mii_bus结构体:表示一个MDIO总线实例
- phy_device结构体:表示连接的PHY设备
- MDIO总线驱动:实现具体SoC的MDIO控制器操作
内核中MDIO操作遵循两种协议规范:
- Clause 22:传统5位地址空间,最大支持32个PHY
- Clause 45:扩展16位地址空间,支持更多PHY设备
以下是一个典型的MDIO总线注册代码框架:
static struct mii_bus *my_mii_bus; my_mii_bus = mdiobus_alloc(); if (!my_mii_bus) { return -ENOMEM; } my_mii_bus->name = "my_mdio"; my_mii_bus->read = my_mdio_read; my_mii_bus->write = my_mdio_write; my_mii_bus->priv = private_data; if (mdiobus_register(my_mii_bus)) { mdiobus_free(my_mii_bus); return -ENXIO; }2. 方法一:通过mii_bus结构体直接操作
这是最基础的MDIO操作方法,直接使用mii_bus提供的读写函数。这种方法适合需要精细控制MDIO时序或特殊PHY设备的情况。
2.1 核心API函数
int mdiobus_read(struct mii_bus *bus, int addr, int regnum); int mdiobus_write(struct mii_bus *bus, int addr, int regnum, u16 val);2.2 完整示例代码
#include <linux/module.h> #include <linux/mii.h> static struct mii_bus *mdio_bus; static int __init mdio_demo_init(void) { int phy_id = 1; // PHY地址 int reg = MII_BMCR; // 基本模式控制寄存器 u16 value; // 获取系统中第一个MDIO总线 mdio_bus = mdiobus_get_by_name("fixed-0"); if (!mdio_bus) { pr_err("Failed to get MDIO bus\n"); return -ENODEV; } // 读取PHY寄存器 value = mdiobus_read(mdio_bus, phy_id, reg); if (value < 0) { pr_err("Read failed\n"); return value; } pr_info("PHY 0x%x reg 0x%x = 0x%04x\n", phy_id, reg, value); // 写入PHY寄存器 value |= BMCR_ANENABLE; // 使能自动协商 if (mdiobus_write(mdio_bus, phy_id, reg, value) < 0) { pr_err("Write failed\n"); return -EIO; } return 0; } static void __exit mdio_demo_exit(void) { if (mdio_bus) mdiobus_put(mdio_bus); } module_init(mdio_demo_init); module_exit(mdio_demo_exit); MODULE_LICENSE("GPL");2.3 操作要点
- 通过
mdiobus_get_by_name()获取已注册的MDIO总线实例 - 读写操作需要指定PHY地址和寄存器号
- Clause 45设备需要使用特殊寄存器访问方式
- 操作完成后需要调用
mdiobus_put()释放引用
3. 方法二:通过phy_device抽象层操作
Linux内核提供了更上层的PHY抽象层,通过phy_device结构体管理PHY设备,这种方法更符合内核设计理念。
3.1 PHY设备核心API
struct phy_device *phy_find_first(struct mii_bus *bus); int phy_read(struct phy_device *phydev, u32 regnum); int phy_write(struct phy_device *phydev, u32 regnum, u16 val);3.2 完整示例代码
#include <linux/module.h> #include <linux/phy.h> static struct phy_device *phydev; static int __init phy_demo_init(void) { struct mii_bus *bus; int reg = MII_BMCR; u16 value; // 获取MDIO总线 bus = mdiobus_get_by_name("fixed-0"); if (!bus) { pr_err("MDIO bus not found\n"); return -ENODEV; } // 查找第一个PHY设备 phydev = phy_find_first(bus); if (!phydev) { pr_err("No PHY device found\n"); mdiobus_put(bus); return -ENODEV; } // 读取PHY寄存器 value = phy_read(phydev, reg); pr_info("PHY reg 0x%x = 0x%04x\n", reg, value); // 修改并写回寄存器 value |= BMCR_FULLDPLX; phy_write(phydev, reg, value); mdiobus_put(bus); return 0; } static void __exit phy_demo_exit(void) { // PHY设备引用由内核管理,无需显式释放 } module_init(phy_demo_init); module_exit(phy_demo_exit); MODULE_LICENSE("GPL");3.3 操作优势
- 自动处理PHY设备的发现和初始化
- 支持PHY状态变化通知机制
- 提供更高级的功能如PHY状态机管理
- 内置PHY驱动框架,支持多种PHY芯片
4. 方法三:内存映射直接操作寄存器
对于某些嵌入式SoC,MDIO控制器寄存器可能直接映射到内存空间,此时可以直接操作硬件寄存器。
4.1 寄存器映射关键步骤
- 获取寄存器物理地址
- 使用ioremap映射到虚拟地址空间
- 通过读写操作寄存器
4.2 完整示例代码
#include <linux/module.h> #include <linux/io.h> #define MDIO_BASE 0x1E720000 #define MDIO_CTRL 0x00 #define MDIO_DATA 0x04 #define MDIO_ADDR 0x08 static void __iomem *mdio_regs; static u16 mdio_direct_read(int phy_id, int reg) { // 设置PHY地址和寄存器号 writel((phy_id << 8) | reg, mdio_regs + MDIO_ADDR); // 触发读操作 writel(0x1, mdio_regs + MDIO_CTRL); // 等待操作完成 while (readl(mdio_regs + MDIO_CTRL) & 0x1); return readw(mdio_regs + MDIO_DATA); } static void mdio_direct_write(int phy_id, int reg, u16 val) { // 写入数据 writew(val, mdio_regs + MDIO_DATA); // 设置PHY地址和寄存器号 writel((phy_id << 8) | reg, mdio_regs + MDIO_ADDR); // 触发写操作 writel(0x3, mdio_regs + MDIO_CTRL); // 等待操作完成 while (readl(mdio_regs + MDIO_CTRL) & 0x1); } static int __init direct_mdio_init(void) { int phy_id = 1; int reg = MII_BMCR; // 映射寄存器 mdio_regs = ioremap(MDIO_BASE, 0x100); if (!mdio_regs) { pr_err("Failed to map MDIO registers\n"); return -ENOMEM; } // 读取演示 u16 val = mdio_direct_read(phy_id, reg); pr_info("Direct read: 0x%04x\n", val); // 写入演示 mdio_direct_write(phy_id, reg, val | BMCR_SPEED100); return 0; } static void __exit direct_mdio_exit(void) { if (mdio_regs) iounmap(mdio_regs); } module_init(direct_mdio_init); module_exit(direct_mdio_exit); MODULE_LICENSE("GPL");4.3 注意事项
- 需要准确了解SoC的MDIO控制器寄存器布局
- 必须处理内存屏障和字节序问题
- 通常需要实现完整的MDIO总线操作函数
- 不如内核标准接口稳定,可能随硬件变化而失效
5. 三种方法对比与选择建议
| 方法 | 复杂度 | 可移植性 | 功能完整性 | 适用场景 |
|---|---|---|---|---|
| mii_bus直接操作 | 中 | 高 | 基础功能 | 需要精细控制的场景 |
| phy_device抽象 | 低 | 最高 | 完整功能 | 大多数标准PHY设备 |
| 寄存器直接操作 | 高 | 低 | 自定义实现 | 特殊硬件或性能敏感场景 |
在实际项目中,建议优先考虑phy_device抽象层方法,它提供了最完整的PHY管理功能。只有在遇到以下情况时才考虑其他方法:
- PHY设备不符合标准MDIO协议
- 需要极高的时序控制精度
- 内核现有驱动无法满足性能要求
- 调试和分析底层MDIO通信问题
6. 高级技巧与调试方法
6.1 Clause 45设备访问
对于支持Clause 45的PHY设备,需要使用MMD(MDIO Manageable Device)访问方式:
static int phy_read_c45(struct phy_device *phydev, int devad, int reg) { return mdiobus_c45_read(phydev->mdio.bus, phydev->mdio.addr, devad, reg); }6.2 MDIO总线调试
内核提供了MDIO总线调试文件系统接口:
# 查看系统中所有MDIO总线 ls /sys/bus/mdio_bus/devices/ # 查看特定PHY寄存器 cat /sys/bus/mdio_bus/devices/fixed-0/phy_registers6.3 常见问题排查
读取返回0xFFFF:
- 检查PHY地址是否正确
- 确认MDIO总线时钟配置
- 验证硬件连接
操作超时:
- 检查MDIO控制器驱动是否正常
- 确认PHY设备上电
- 排查总线冲突
Clause 45设备无法识别:
- 确保使用正确的访问方法
- 检查PHY的C22兼容模式寄存器
7. 实战案例:自定义PHY驱动开发
以下是一个简单PHY驱动的框架示例,展示如何将PHY操作封装为内核模块:
#include <linux/phy.h> #include <linux/module.h> static int myphy_config_init(struct phy_device *phydev) { int err; // 硬件初始化 err = phy_write(phydev, MII_BMCR, BMCR_RESET); if (err < 0) return err; // 配置自动协商 err = phy_write(phydev, MII_ADVERTISE, ADVERTISE_ALL | ADVERTISE_PAUSE_CAP); if (err < 0) return err; return 0; } static struct phy_driver myphy_driver = { .phy_id = 0x01410c00, .name = "My PHY", .phy_id_mask = 0xfffffff0, .features = PHY_BASIC_FEATURES, .config_init = myphy_config_init, .suspend = genphy_suspend, .resume = genphy_resume, }; module_phy_driver(myphy_driver); MODULE_DESCRIPTION("Custom PHY driver"); MODULE_AUTHOR("Your Name"); MODULE_LICENSE("GPL");这个驱动框架可以扩展实现更复杂的PHY功能,如:
- 自定义状态检测
- 特殊功耗管理
- 硬件特定优化
- 诊断接口
编程学习
技术分享
实战经验