UBOOT PHY 驱动分析及调试方法
UBOOT版本:2017.11
一、PHY 简介
Media Independent Interface ( MII ),介质独立接口,起初是定义 100M 以太网(Fast Ethernet)的 MAC 层与 PHY 芯片之间的传输标准。
MAC 与 PHY 之间的 MII 连接可以是可插拔的连接器,或者是同一块 PCB 上 MAC 与 PHY 之间的走线。
MDIO 与MDCLK是 MII 接口的一部分,二者可称为 SMI (Serial Management Interface) 串行管理接口,用于在 MAC 和 PHY 之间传递配置信息。
在 MDIO 规范中定义 PHY 地址为 5 bit,即同一组MDIO最多可配置 2^5 = 32 个 PHY。
MII 接口图如下所示:
能够和MII相提并论的还有RMII(精简MII)、SMII(串行MII)、GMII(千兆MII)、RGMII(精简GMII)等接口,它们与普通MII相比较,仅是传输速率与数据传输方式不同等,这里不做过多的介绍。
二、PHY 设备创建及驱动匹配
以 rockchip 为例,当它的 mac 驱动 gmac_rockchip 成功的被匹配时,会调用到 gmac_rockchip_probe ,mac 相关我们不做分析,直接进入主题 designware_eth_probe 函数中。
首先会获取到对应的 regulator ,并会做一些电源配置,
1 device_get_supply_regulator(dev, "phy-supply",
2 &phy_supply);
3 regulator_set_enable(phy_supply, true);
UBOOT 中引入了 DM 驱动模型,相关的设备与驱动事先已经绑定过,如这里通过 phandle 进而找出其对应的,驱动在 rk8xx.c 中定义:
1 U_BOOT_DRIVER(rk8xx_switch) = {
2 .name = "rk8xx_switch",
3 .id = UCLASS_REGULATOR,
4 .ops = &rk8xx_switch_ops,
5 .probe = rk8xx_switch_probe,
6 };
接下来要初始化 mdio 总线,并将其注册到系统的 mii_devs 中,在获取某个控制器时,即可通过 miiphy_get_dev_by_name 来获取:
1 dw_mdio_init(dev->name, dev);
2 priv->bus = miiphy_get_dev_by_name(dev->name);
电源以及 MDIO 总线已初始化完毕,万事俱备,接下来就正式的对 phy 进行操作了,一起来看 dw_phy_init 函数:
1 static int dw_phy_init(struct dw_eth_dev *priv, void *dev)
2 {
3 struct phy_device *phydev;
4 int mask = 0xffffffff, ret;
5
6 #ifdef CONFIG_PHY_ADDR
7 mask = 1 << CONFIG_PHY_ADDR;
8 #endif
9
10 phydev = phy_find_by_mask(priv->bus, mask, priv->interface);
11 if (!phydev)
12 return -ENODEV;
13
14 phy_connect_dev(phydev, dev);
15
16 phydev->supported &= PHY_GBIT_FEATURES;
17 if (priv->max_speed) {
18 ret = phy_set_supported(phydev, priv->max_speed);
19 if (ret)
20 return ret;
21 }
22 phydev->advertising = phydev->supported;
23
24 priv->phydev = phydev;
25 phy_config(phydev);
26
27 return 0;
28 }
我们主要来看 phy_find_by_mask ,这里是核心,其它细节配置不去分析以免喧宾夺主。
在 phy_find_by_mask 中先是调用 search_for_existing_phy(bus, phy_mask, interface) ,它通过查询 phymap 看看我们系统中是不是已经有了能用的 phy dev 了,如果有,那么证明它已经初始化过并已经绑定过合适的驱动,用它就没有问题,
显然我们是没有的,那么就需要自己创造了,接下来来到 create_phy_by_mask :
1 static struct phy_device *create_phy_by_mask(struct mii_dev *bus,
2 unsigned phy_mask, int devad, phy_interface_t interface)
3 {
4 u32 phy_id = 0xffffffff;
5 bool is_c45;
6
7 while (phy_mask) {
8 int addr = ffs(phy_mask) - 1;
9 int r = get_phy_id(bus, addr, devad, &phy_id);
10 /* If the PHY ID is mostly f's, we didn't find anything */
11 if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) {
12 is_c45 = (devad == MDIO_DEVAD_NONE) ? false : true;
13 return phy_device_create(bus, addr, phy_id, is_c45,
14 interface);
15 }
16 phy_mask &= ~(1 << addr);
17 }
18 return NULL;
19 }
这段代码比较有意思,如果你的 phy addr 没有指定,那么会通过 ffs 与 phy_mask &= ~(1 << addr) 来实现地址范围为 0~31 的遍历,通过读取 phy id,依次去尝试指定地址上是否有 phy 会响应。
假设存在一个地址为 0x0F 的设备,那么就会继而调用到 phy_device_create ,它会创建一个 phy dev,初始化配置、并为其搜索合适的 phy driver 并绑定,实现代码如下:
1 static struct phy_device *phy_device_create(struct mii_dev *bus, int addr,
2 u32 phy_id, bool is_c45,
3 phy_interface_t interface)
4 {
5 struct phy_device *dev;
6
7 /* We allocate the device, and initialize the
8 * default values */
9 dev = malloc(sizeof(*dev));
10 if (!dev) {
11 printf("Failed to allocate PHY device for %s:%d\n",
12 bus->name, addr);
13 return NULL;
14 }
15
16 memset(dev, 0, sizeof(*dev));
17
18 dev->duplex = -1;
19 dev->link = 0;
20 dev->interface = interface;
21
22 #ifdef CONFIG_DM_ETH
23 dev->node = ofnode_null();
24 #endif
25
26 dev->autoneg = AUTONEG_ENABLE;
27
28 dev->addr = addr;
29 dev->phy_id = phy_id;
30 dev->is_c45 = is_c45;
31 dev->bus = bus;
32
33 dev->drv = get_phy_driver(dev, interface);
34
35 phy_probe(dev);
36
37 bus->phymap[addr] = dev;
38
39 return dev;
40 }
get_phy_driver 就是执行 driver 的搜索动作,那么匹配的依据是什么?还记得上一步探索是否存在 phy 设备的条件是什么么,没有错,是 phy id,它是从实际设备中读取出来的值。
然后与系统中 phy driver 链表每个元素进行比对,如果一致那么就配对绑定。假如读出 id 为 0x1cc915 ,那么就会直接匹配 RealTek RTL8211E 。若是没有找到合适的 driver,系统会将用 Generic PHY 通用驱动与其配对。
接下来就是 phy_probe 会调用到 phy driver 中的 probe 函数,对 phy 设备进行硬件配置,
最后就是将已完备的 phy dev 加入到 phymap 数组,以 addr 为索引,证明它已经是一个经过认可的 phy dev 了,整体流程比较简单,不做过多的分析。
三、PHY 调试
UBOOT 带有 MII 命令集,可以通过相关配置打开,在控制台中通过命令来调试比较方便,
MII 命令集:
mii - MII utility commands
Usage:
mii device - list available devices
mii device <devname> - set current device
mii info <addr> - display MII PHY info
mii read <addr> <reg> - read MII PHY <addr> register <reg>
mii write <addr> <reg> <data> - write MII PHY <addr> register <reg>
mii modify <addr> <reg> <data> <mask> - modify MII PHY <addr> register <reg>
updating bits identified in <mask>
mii dump <addr> <reg> - pretty-print <addr> <reg> (0-5 only)
如可用 mii dump 命令查看寄存器 0 相关信息:
=> mii dump
0. (1040) -- PHY control register --
(8000:0000) 0.15 = 0 reset
(4000:0000) 0.14 = 0 loopback
(2040:0040) 0. 6,13 = b10 speed selection = 1000 Mbps
(1000:1000) 0.12 = 1 A/N enable
(0800:0000) 0.11 = 0 power-down
(0400:0000) 0.10 = 0 isolate
(0200:0000) 0. 9 = 0 restart A/N
(0100:0000) 0. 8 = 0 duplex = half
(0080:0000) 0. 7 = 0 collision test enable
(003f:0000) 0. 5- 0 = 0 (reserved)