linux PHY 驱动
firefly-3399
linux内核版本:4.4
MAC(Media Access Control)
1.网络硬件部分
2.驱动部分
参考博客
http://www.jianshu.com/p/77bb0ba1768c
http://www.360doc.com/content/13/0717/16/2768962_300623597.shtml
http://www.latelee.org/programming-under-linux/linux-phy-driver.html
https://www.cnblogs.com/jason-lu/articles/3195473.html
http://blog.csdn.net/heli200482128/article/details/54091677
1.MAC和PHY硬件及接口协议
1.1网络硬件组成
网络有PHY,MAC和CPU组成。针对不同的组合形式可以有下面几种类型
方案一: CPU集成了MAC和PHY
方案二: CPU集成MAC,PHY采用独立芯片(3399是这种)
方案三: CPU不集成MAC与PHY,MAC与PHY采用集成芯片
由于在 RK 系列的 SoC 中内置了以太网 MAC 控制器,所以只需要搭配一颗以太网 PHY 芯片,
即可实现以太网卡功能。按照规范,即使是不同厂家的 PHY,仍然有一部分寄存器的定义是通用的,
只要配置了这些通用的寄存器,基本上 PHY 就可以正常工作。
1.2如何进行数据传输
在软件上网络的操作分为下面几步:
- 为数据收发分配内存
- 初始化MAC寄存器
- 初始化PHY寄存器
- 启动收发
1.3 MAC与PHY
收发数据这种耗时的事情,使用DMA是最合适的。CPU只需要告诉DMA起始地址和长度剩下的事情就可以自动完成,一般在MAC中有一组寄存器专门记录数据地址,cpu按照MAC的要求把数据放好后,启动MAC的数据发送就可以了
左边的连接在处理器上,右边的连接PHY芯片,MII DATA 与MIIM是与PHY进行数据传输和控制的
PHY芯片上的寄存器CPU不可以直接访问,只能通过MAC上的MIIM寄存器实现间接访问;右边就是我们的网口
1.4PHY与MAC之间的接口
RK3399使用的是RGMII,RGMII即Reduced GMII,是RGMII的简化版本,将接口信号线数量从24根减少到14根(COL/CRS端口状态指示信号,这里没有画出),时钟频率仍旧为125MHz,TX/RX数据宽度从8为变为4位,为了保持1000Mbps的传输速率不变,RGMII接口在时钟的上升沿和下降沿都采样数据。在参考时钟的上升沿发送GMII接口中的TXD[3:0]/RXD[3:0],在参考时钟的下降沿发送GMII接口中的TXD[7:4]/RXD[7:4]。RGMI同时也兼容100Mbps和10Mbps两种速率,此时参考时钟速率分别为25MHz和2.5MHz。另外,MDC和MDIO是MIIM(MII Management)用来配置PHY寄存器的
RGMII接口
2.驱动
- 2.1: PHY驱动
phy_init //.\kernel\drivers\net\phy\phy_device.c
mdio_bus_init
class_register 在/sys/class/下创建mdio_bus
bus_register 创建总线mdio_bus
phy_drivers_register
phy_driver_register 注册phy_driver
new_driver->driver.name = new_driver->name; //可以搜索设备树中的这个名字
new_driver->driver.bus = &mdio_bus_type;
new_driver->driver.probe = phy_probe; //当匹配成功回调用的函数
new_driver->driver.remove = phy_remove;
driver_register(&new_driver->driver); //device_driver注册
phy_driver注册成功了,等待phy_device的注册。根据测试发现phy_device的注册不依靠设备树(根据上面驱动的名字在设备树中搜不到),该设备的注册在后面的MDIO驱动中调用mdiobus_register中会注册phy_device
- 2.2: MAC驱动DTS
rk3399.dtsi
gmac: ethernet@fe300000 {
compatible = "rockchip,rk3399-gmac";
reg = <0x0 0xfe300000 0x0 0x10000>;
rockchip,grf = <&grf>;
interrupts = <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH 0>;
interrupt-names = "macirq";
clocks = <&cru SCLK_MAC>, <&cru SCLK_MAC_RX>,
<&cru SCLK_MAC_TX>, <&cru SCLK_MACREF>,
<&cru SCLK_MACREF_OUT>, <&cru ACLK_GMAC>,
<&cru PCLK_GMAC>;
clock-names = "stmmaceth", "mac_clk_rx",
"mac_clk_tx", "clk_mac_ref",
"clk_mac_refout", "aclk_mac",
"pclk_mac";
resets = <&cru SRST_A_GMAC>;
reset-names = "stmmaceth";
power-domains = <&power RK3399_PD_GMAC>;
status = "disabled";
};
rk3399-firefly-linux.dts
&gmac {
phy-supply = <&vcc_phy>; //phy供电有&vcc_phy提供
phy-mode = "rgmii"; //rgmii 或 rmii
clock_in_out = "input"; //时钟由PHY输入给MAC
snps,reset-gpio = <&gpio3 15 GPIO_ACTIVE_LOW>;
snps,reset-active-low;
snps,reset-delays-us = <0 10000 50000>;
assigned-clocks = <&cru SCLK_RMII_SRC>; //MAC的时钟源
assigned-clock-parents = <&clkin_gmac>; //MAC父时钟,下面再继续看
pinctrl-names = "default";
pinctrl-0 = <&rgmii_pins>; //必须和phy-mode一致
tx_delay = <0x28>;
rx_delay = <0x11>;
status = "okay";
};
clkin_gmac: external-gmac-clock {
compatible = "fixed-clock";
clock-frequency = <125000000>; //125Mhz
clock-output-names = "clkin_gmac";
#clock-cells = <0>;
};
- 2.3: MAC驱动Driver
E:\linux_kernel\hfs-workstation\rk3399-linux-origin-2017.4.5\kernel\drivers\net\ethernet\stmicro\stmmac\dwmac-rk.c
通过上面的设备树成功匹配了平台设备
module_platform_driver(rk_gmac_dwmac_driver)
rk_gmac_probe
of_device_get_match_data //返回的是匹配的of_device_id的data
stmmac_get_platform_resources(pdev, &stmmac_res) //利用设备树中资源给stmmac_res赋值
stmmac_res->irq = platform_get_irq_byname(pdev, "macirq") //名字和设备树对应上
platform_get_resource //得到IORESOURCE_MEM资源,然后映射后填充到stmmac_res
plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac) //填充plat_dat
of_get_mac_address //得到mac地址
plat->interface = of_get_phy_mode(np) // 得到接口类型,设备树中是RGMII
of_property_read_u32 //得到max speed
...
//一些赋值
plat_dat->has_gmac = true;
plat_dat->init = rk_gmac_init; //选择MAC与PHY通信接口(RGMII/GMII)
plat_dat->exit = rk_gmac_exit;
plat_dat->fix_mac_speed = rk_fix_speed;
plat_dat->get_eth_addr = rk_get_eth_addr_vendor;
rk_gmac_setup //分配填充bsp_priv然后放到plat_dat->bsp_priv
bsp_priv = devm_kzalloc(dev, sizeof(*bsp_priv), GFP_KERNEL) //分配结构体
bsp_priv->phy_iface = of_get_phy_mode(dev->of_node) //接口类型RGMII
of_property_read_string //和设备树对应上,给bsp_priv->clock_input = true;
of_property_read_u32 //读取设备树“tx_delay”,bsp_priv->tx_delay = 0x28;
of_property_read_u32 //读取设备树“rx_delay”,bsp_priv->rx_delay = 0x11;
gmac_clk_init //配置了clk到bsp_priv
rk_gmac_init //gmac初始化
bsp_priv->ops->set_to_rgmii(bsp_priv, bsp_priv->tx_delay,bsp_priv->rx_delay) //rk_gmac_ops的set_to_rgmii方法
regmap_write // 写了几次寄存器
phy_power_on
gmac_clk_enable
pm_runtime_enable
pm_runtime_get_sync
stmmac_dvr_probe //E:\linux_kernel\hfs-workstation\rk3399-linux-origin-2017.4.5\kernel\drivers\net\ethernet\stmicro\stmmac\stmmac_main.c
alloc_etherdev //分配net_device
stmmac_set_ethtool_ops //设置了好多钩子函数
dev_set_drvdata //把priv->dev == device->driver_data
stmmac_verify_args //合适一些驱动参数
stmmac_hw_init //初始化mac并且得到能力
...
stmmac_mdio_register //mdio bus 注册
mdiobus_alloc //分配一个mii_bus
new_bus->read = &stmmac_mdio_read; //读取phy内部寄存器的数据
new_bus->write = &stmmac_mdio_write; //写phy内部寄存器的数据
new_bus->reset = &stmmac_mdio_reset; //重制MII总线
mdiobus_register //注册mii_bus
device_register //注册设备文件
bus->reset(bus); //总线复位
mdiobus_scan //循环扫描phy设备
phydev = get_phy_device(bus, addr, false); //获取创建phy设备
err = phy_device_register(phydev); //注册phy设备
bus->state = MDIOBUS_REGISTERED; //状态设置为已注册
register_netdev //注册net_device,里面有个operations函数集合
当执行ifconfig eth0 up 后,执行了E:\linux_kernel\hfs-workstation\rk3399-linux-origin-2017.4.5\kernel\drivers\net\ethernet\stmicro\stmmac\stmmac_main.c的stmmac_open函数,在执行后phy_drivers的probe函数才会执行
stmmac_open
stmmac_check_ether_addr //检查mac地址是否无效的
stmmac_init_phy //初始化phy
phy_connect //连接ethernet设备和PHY设备
bus_find_device_by_name //从mdio总线上根据名字查找device
to_phy_device //通过device得到phy_device
phy_connect_direct //连接ethernet设备和特定的PHY设备
phy_attach_direct
err = d->driver->probe(d); //phy_drivers的probe函数
device_bind_driver //绑定驱动到设备
phy_init_hw //(这里加了修改时钟的函数,硬件改了phy)
phy_start_machine //启动PHY状态机
alloc_dma_desc_resources //分配TX/RX资源
init_dma_desc_rings //初始化RX/TX的回环描述
stmmac_hw_setup //设置mac为可用状态,配置mac核心寄存器,然后DMA数据准备接收和发送
phy_start //从新启动PHY设备