当前位置: 首页 > news >正文

MDIO总线驱动开发实战:Linux内核4.19下PHY寄存器读写3种方法

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 寄存器映射关键步骤

  1. 获取寄存器物理地址
  2. 使用ioremap映射到虚拟地址空间
  3. 通过读写操作寄存器

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管理功能。只有在遇到以下情况时才考虑其他方法:

  1. PHY设备不符合标准MDIO协议
  2. 需要极高的时序控制精度
  3. 内核现有驱动无法满足性能要求
  4. 调试和分析底层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_registers

6.3 常见问题排查

  1. 读取返回0xFFFF

    • 检查PHY地址是否正确
    • 确认MDIO总线时钟配置
    • 验证硬件连接
  2. 操作超时

    • 检查MDIO控制器驱动是否正常
    • 确认PHY设备上电
    • 排查总线冲突
  3. 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功能,如:

  • 自定义状态检测
  • 特殊功耗管理
  • 硬件特定优化
  • 诊断接口
http://www.jsqmd.com/news/1133370/

相关文章:

  • 基于STM32和A89307的无刷电机FOC控制方案
  • IIM-42652与PIC18LF26K22构建6DoF运动追踪系统
  • 基于STM32与Si4731的可编程收音机系统开发
  • PIC18F85J50与M24C04-R EEPROM嵌入式存储方案详解
  • 2026年洛阳新房装修:告别东奔西跑!这家装修公司连家电都帮你配齐了
  • TPA3128D2与PIC18LF45K80音频放大器设计实战
  • XTR116与STM32的4-20mA电流环工业应用设计
  • STM32L152ZD与Si4732构建高性能收音机系统
  • 终极指南:如何使用NBTExplorer轻松编辑Minecraft游戏数据
  • 信安毕业设计容易的开题大全
  • 如何快速获取百度网盘真实下载链接:终极免费解决方案
  • STM32与AD74413R高精度混合信号系统设计
  • PCF8591与MK60DN512VLQ10信号转换系统设计与实现
  • 2026最新8款基础版免费AI编程工具权威实测
  • 【JAVA毕设源码分享】基于springboot护工管理便捷服务系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 用LangGraph+Gemini 3 Pro构建可调试的数据分析工作流
  • WS2812智能LED与TM4C1294微控制器的嵌入式视觉开发
  • 5分钟学会用NBTExplorer编辑Minecraft游戏数据,新手也能轻松上手
  • 终极指南:3分钟学会用NBTExplorer编辑Minecraft游戏数据
  • 深蓝词库转换:3分钟实现跨平台输入法词库自由迁移
  • 终极指南:如何安全解锁原神60FPS限制,享受高帧率游戏体验
  • GTA5线上小助手:从新手到高手的完整使用指南
  • Day14 | vLLM——把训完的模型搬上线的“印钞机“
  • OASIS Ring智能戒指:Vibe Coding新宠,能否成为AI交互最佳硬件?
  • 5分钟看懂MetaBMC:为什么它是服务器管理的未来?
  • STM32驱动WS2812 LED灯带的实战指南
  • STM32与Si4732打造高性能数字收音机方案
  • 【JAVA毕设源码分享】基于Web的商品预购平台的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 明日方舟智能助手实战指南:5个核心技巧告别手动肝日常
  • Si4732与MKV44F128VLH16在数字音频处理中的协同应用