总览
PHY 的作用
文章试图解决的问题
含有 PHY 层的协议,在 Linux 中 PHY 层的内存布局,在 SOC 上,外部,Controller 内外都有哪些内存空间
目前只涵盖了涉及的数据结构和初始化,文章重点在初始化过程上
PHY 框架
PHY 和 Controller 及 SOC 的模式有三种
PHY WITHIN THE CONTROLLER
PHY WITHIN THE SoC
PHY EXTERNAL TO THE SOC
EXTERNAL PHY 框架
结构示意图
以太网
PHY 的整个硬件系统组成比较复杂,PHY 与 MAC 相连 (也可以通过一个中间设备相连),MAC 与 CPU 相连(有集成在内部的,也有外接的方式),PHY 与 MAC 通过 MII 和 MDIO/MDC 相连,MII 是走网络数据的,MDIO/MDC 是用来与 PHY 的寄存器通讯的,对 PHY 进行配置。
PHY 的驱动与 I2C/SPI 的驱动一样,分为控制器驱动 和设备器驱动
sunxi-gmac.c 中把以太网的 phy 驱动的常规 probe 放在了 geth_phy_init 中,在网卡 geth_open 中调用,创建了 mdio_bus 设备,mdio_bus 在网络上博客描述是类似 SPI_Master 结构,spi_master 创建 spi 总线让 spi device 和 spi driver 挂接,mdio_bus 创建 mdio 总线让 phy device, phy driver 挂接,需要注意的是这里创建的是,mdio_bus 类下的一个设备,但是他的设备名字也叫 mdio_bus
c
// drivers/net/phy/mdio_bus.c
struct bus_type mdio_bus_type = {
.name = "mdio_bus",
.match = mdio_bus_match,
.uevent = mdio_uevent,
};
static struct class mdio_bus_class = {
.name = "mdio_bus",
.dev_release = mdiobus_release,
};
c
// drivers/net/phy/phy_device.c
static int phy_bus_match(struct device *dev, struct device_driver *drv)
{
struct phy_device *phydev = to_phy_device(dev);
struct phy_driver *phydrv = to_phy_driver(drv);
const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
int i;
if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
return 0;
if (phydrv->match_phy_device)
return phydrv->match_phy_device(phydev);
if (phydev->is_c45) {
for (i = 1; i < num_ids; i++) {
if (phydev->c45_ids.device_ids[i] == 0xffffffff)
continue;
if ((phydrv->phy_id & phydrv->phy_id_mask) ==
(phydev->c45_ids.device_ids[i] &
phydrv->phy_id_mask))
return 1;
}
return 0;
} else {
return (phydrv->phy_id & phydrv->phy_id_mask) ==
(phydev->phy_id & phydrv->phy_id_mask);
}
}
mdio_bus 类和 mdio_bus 总线设备在 phy.c
d1-h PHY 初始化
- 全志的 sunxi-gmac.c 用作了 ti 那边以太网的两个文件的用处
- mdio_bus 相关,对应 drivers/net/ethernet/ti/davinci_mdio.c ,在 sunxi-gmac.c geth_open 中
- net_device 相关,对应 drivers/net/ethernet/ti/cpsw.c 在 sunxi-gmac geth_probe 中
这个文章讲了 Ti 的 TI phy 初始化及和 mac 连接过程
这里记录一下 d1-h 板子 phy 初始化过程
- 读硬件芯片的方法是怎么实现的
前面提到 mdio_bus 实例刚注册就可以进行在总线上的获取 phy_id 的操作,说明 mdio_bus 总线的读写不依赖 mdio_bus 实例,在 d1-h 手册里可以看到对 phy 进行读写的寄存器
一个 commad register 和一个 data register,读操作把想读的 PHY_REG_ADDR 写入 command,随后从 data register 读即可,写操作把想写的 PHY_REG_ADDR 和 写入 command 数据写入 data register 即可。
关键点
这个问题有两个点花了很多时间
- phy_id 的获取:一直在软件层面根据 0x001cc916 找软件层面的代码,最后发现是硬件芯片负责初始化,phy_address 也是同理,硬件通过某机制协调引脚的电平,芯片手册描述是会通过机制算出未被占用的最小。
- phy_driver:查到 0x001cc916 后一直在找这个文件中的信息是怎么被加载到内核中的。realtek
module_phy_driver 是只要文件编成 ko 在 init 阶段执行,MODULE_DEVICE_TABLE 宏根据网上搜索的结果也是内核编译成 ko 处理。 phy_device_create 中间代码有 request_module 的过程。原本意味是这个 realtek.c 提供 phy_driver phy_device,在 request_module 中加载 phy_driver。
结果看 Kconfig 和 Makefile 发现 realtek.c 文件根本没有编译进去,phy_device_create 的 request_module 的返回值不是根据有没有找到 driver 返回,而是有没有执行下去,即不管找没找到 driver 都会返回正常值并继续往下进行。正确的流程是另有一条线进行即 device_bind_driver 函数
小结:
这个方式下对 PHY 的控制就像 SPI I2C 从设备似的,只能通过 MDIO 格式配置寄存器,无法直接访问 PHY 内部的寄存器地址
SOC 内部 PHY 框架
结构示意图
SOC 内部 PHY 框架遵循下图
GENERIC PHY FRAMEWORK 使用方法
-
绑定设备与驱动
- 设备树
- 不通过设备树
-
PHY 驱动
- 要提供一系列 phy_ops (init, exit, power_on, power_off)
- 注册到 GENERIC PHY FRAMEWORK 上
-
控制器驱动
- 获得 PHY 的引用
- 调用 PHY Framework APIs(phy_init, phy_exit, phy_power_on, phy_power_off)
PCIE
pci-j721e.c 中的 PHY 初始化
根据PCIe控制器的工作模式(RC模式或EP模式)采取不同的初始化。
如果是 RC 模式,进行主机模式的初始化,包括配置 PCIe 主机桥、PHY 初始化等;
如果是 EP 模式,进行端点模式的初始化,包括初始化 PHY 等;
所以不管是 RC 还是 EP,都执行 PHY 初始化 cdns_pcie_init_phy()
注:of_parse_phandle_with_args 的使用:
ret = of_parse_phandle_with_args(np, "phys", "#phy-cells",index, &args);
猜想:pcie 中没有实体的 phy 芯片,虚拟的 phy 也是调的 serdies 的 phy,TI 上关于 PCIE 部分的用户手册如下。
CONTROLLER 内部 PHY 框架
USB
USB 芯片分为 Controller 和 PHY
USB 中 PHY 结构
CTL 主要实现 USB 的协议和控制。内部逻辑主要有 MAC 层、 CSR 层和 FIFO 控制层,还有其他低功耗管理之类层次。
MAC 实现按 USB 协议进行数据包打包和解包,并把数据按照 PIPE 总线格式发送给 PHY(USB2 . 0 为 UTMI)。
CSR 层进行寄存器控制,软件对 USB 芯片的控制就是通过 CSR 寄存器,这部分和 CPU 进行交互访问,主要作为 Slave 通过 AXI 或者 AHB 进行交互。
FIFO 控制层主要是和 DDR 进行数据交互,控制 USB 从 DDR 搬运数据的通道,主要作为 Master 通过 AXI / AHB 进行交互。
PHY 部分功能主要实现并转串的功能,把 UTMI 或者 PIPE 口的并行数据转换成串行数据,再通过差分数据线输出到芯片外部。
USB 芯片内部实现的功能就是接受软件的控制,进而从内存搬运数据(FIFO)并按照 USB 协议进行数据打包(MAC),并串转换(PHY)后输出到芯片外部。或者从芯片外部接收差分数据信号,串并转换后进行数据解包并写到内存里
初始化
代码分析
phy->ops 结构体(linux\include\linux\phy\phy.h)
phy 结构体(linux\include\linux\phy\phy.h)
DI-H 开发板 USB PHY 总结
上图为 D1-H 开发板 USB2.0HOST 控制器框图,由此可以认为 phy 初始化过程在 usb host 中定义,在实际代码当中也未找到关于 phy 单独的初始化过程,设备树中也未发现 phy 相关的定义。
c
usbc0:usbc0@0 {
device_type = "usbc0";
compatible = "allwinner,sunxi-otg-manager";
usb_port_type = <2>;
usb_detect_type = <1>;
usb_id_gpio;
usb_det_vbus_gpio;
usb_regulator_io = "nocare";
usb_wakeup_suspend = <0>;
usb_luns = <3>;
usb_serial_unique = <0>;
usb_serial_number = "20080411";
rndis_wceis = <1>;
status = "okay";
};
udc:udc-controller@0x04100000 {
compatible = "allwinner,sunxi-udc";
reg = <0x0 0x04100000 0x0 0x1000>, /*udc base*/
<0x0 0x00000000 0x0 0x100>; /*sram base*/
interrupts-extended = <&plic0 45 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_OTG>;
clock-names = "bus_otg";
resets = <&ccu RST_BUS_OTG>, <&ccu RST_USB_PHY0>;
reset-names = "otg", "phy";
status = "okay";
};
ehci0:ehci0-controller@0x04101000 {
compatible = "allwinner,sunxi-ehci0";
reg = <0x0 0x04101000 0x0 0xFFF>, /*hci0 base*/
<0x0 0x00000000 0x0 0x100>, /*sram base*/
<0x0 0x04100000 0x0 0x1000>; /*otg base*/
interrupts-extended = <&plic0 46 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_EHCI0>;
clock-names = "bus_hci";
resets = <&ccu RST_BUS_EHCI0>, <&ccu RST_USB_PHY0>;
reset-names = "hci", "phy";
hci_ctrl_no = <0>;
status = "okay";
};
ohci0:ohci0-controller@0x04101400 {
compatible = "allwinner,sunxi-ohci0";
reg = <0x0 0x04101400 0x0 0xFFF>, /*hci0 base*/
<0x0 0x00000000 0x0 0x100>, /*sram base*/
<0x0 0x04100000 0x0 0x1000>; /*otg base*/
interrupts-extended = <&plic0 47 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_OHCI0>, <&ccu CLK_USB_OHCI0>;
clock-names = "bus_hci", "ohci";
resets = <&ccu RST_BUS_OHCI0>, <&ccu RST_USB_PHY0>;
reset-names = "hci", "phy";
hci_ctrl_no = <0>;
status = "okay";
};
usbc1:usbc1@0 {
device_type = "usbc1";
usb_regulator_io = "nocare";
usb_wakeup_suspend = <0>;
status = "disable";
};
ehci1:ehci1-controller@0x04200000 {
compatible = "allwinner,sunxi-ehci1";
reg = <0x0 0x04200000 0x0 0xFFF>, /*ehci1 base*/
<0x0 0x00000000 0x0 0x100>, /*sram base*/
<0x0 0x04100000 0x0 0x1000>; /*otg base*/
interrupts-extended = <&plic0 49 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_EHCI1>;
clock-names = "bus_hci";
resets = <&ccu RST_BUS_EHCI1>, <&ccu RST_USB_PHY1>;
reset-names = "hci", "phy";
hci_ctrl_no = <1>;
status = "disable";
};
ohci1:ohci1-controller@0x04200400 {
compatible = "allwinner,sunxi-ohci1";
reg = <0x0 0x04200400 0x0 0xFFF>, /*ohci1 base*/
<0x0 0x00000000 0x0 0x100>, /*sram base*/
<0x0 0x04100000 0x0 0x1000>; /*otg base*/
interrupts-extended = <&plic0 50 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_OHCI1>, <&ccu CLK_USB_OHCI1>;
clock-names = "bus_hci", "ohci";
resets = <&ccu RST_BUS_OHCI1>, <&ccu RST_USB_PHY1>;
reset-names = "hci", "phy";
hci_ctrl_no = <1>;
status = "disable";
};
c
static int sunxi_ehci_hcd_probe(struct platform_device *pdev)
{
int ret = 0;
#if defined(CONFIG_ARCH_SUN50IW10)
int val = 0;
#endif
struct sunxi_hci_hcd *sunxi_ehci = NULL;
if (pdev == NULL) {
DMSG_PANIC("ERR: %s, Argment is invalid\n", __func__);
return -1;
}
/* if usb is disabled, can not probe */
if (usb_disabled()) {
DMSG_PANIC("ERR: usb hcd is disabled\n");
return -ENODEV;
}
ret = init_sunxi_hci(pdev, SUNXI_USB_EHCI);
if (ret != 0) {
dev_err(&pdev->dev, "init_sunxi_hci is fail\n");
return -1;
}
sunxi_insmod_ehci(pdev);
sunxi_ehci = pdev->dev.platform_data;
if (sunxi_ehci == NULL) {
DMSG_PANIC("ERR: %s, sunxi_ehci is null\n", __func__);
return -1;
}
if (sunxi_ehci->usbc_no == HCI0_USBC_NO) {
ret = register_pm_notifier(&sunxi_ehci_pm_nb);
if (ret) {
DMSG_PANIC("ERR: %s, can not register suspend notifier\n", __func__);
return -1;
}
}
init_completion(&sunxi_ehci->standby_complete);
if (ehci_enable[sunxi_ehci->usbc_no]) {
device_create_file(&pdev->dev, &dev_attr_ehci_enable);
ehci_enable[sunxi_ehci->usbc_no] = 0;
}
return 0;
}
总结
EXTERNAL(以太网) | WITHIN SOC (PCIE) | WITHIN CONTROLLER (USB) | |
---|---|---|---|
与 Controller 的连接 | 通过各种接口 | 通过 UTMI PIPE3 | 和 Controller 共享地址空间 |
是否有单独的驱动 | 可以有 | 有 | 无 |
有无设备树项 | 可以没有 | 可以没有 | 无 |
需要关注程度 | 低 | 中 | 高 |