在Linux下添加一块新的网卡驱动(以下简称phy芯片)时,需要实现mac驱动和phy驱动。通常,mac驱动已由CPU厂家实现。作为驱动工程师,我们仅需匹配phy驱动即可。以下的phy驱动调试均在Linux5.10的源码下进行。
一、CPU与网络芯片的连接情况
1.CPU与phy芯片连接分为两种:
网络数据连接:CPU <---->rmii/rgmii <------> phy <------> RJ45网口
连接方式包括:rmii/rgmii/mii等。
控制数据连接:CPU <---->MDIO <------> phy寄存器 (主要配置一些寄存器内容,明确phy地址)
连接方式包括:SMI/SPI/IIC等
2.注意事项
某些芯片可通过外部io(拉高或拉低电平)来配置寄存器内容(硬件strap)
phy芯片的前16个寄存器为标准寄存器,遵循IEEE802.3标准
CPU的mac层驱动,由CPU芯片厂家提供,在使用是,注意在dts中配置引脚复用方式。
在使用网口的其他未使用,需要在uboot中将这些资源关闭。
对于单口phy,配置一些基本的寄存器即可,对于多口phy(例如switch),可以使用通用驱动,配置成单口phy,以验证连接的准确性,检查上电是否有交换功能,以验证硬件的正确性。
如要使用switch功能,还需要根据芯片手册配置vlan。
phyID在寄存器的第一页第2字节和第3字节。
查看时钟提供的方式以及时钟频率。
3.SMI接口
SMI是MAC内核访问PHY寄存器接口,它由两根线组成,双工,MDC为时钟,MDIO为双向数据通信,原理上跟I2C总线很类似,也可以通过总线访问多个不同的phy。
MDC/MDIO基本特性:
两线制:MDC(时钟线)和MDIO(数据线)。
时钟频率:2.5MHz
通信方式:总线制,可同时接入的PHY数量为32个
通过SMI接口,MAC芯片主动的轮询PHY层芯片,获得状态信息,并发出命令信息。时序可分为两类:cluase22和cluase45.,详见下图
class22格式
class45格式
二、设备树示例
gmac0: ethernet@40120000 {
compatible = "xxxxxxxxx-gmac"; //mac层驱动,有MCU厂家提供
reg = <0x0 0x40120000 0x0 0x10000>;
interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "macirq";
phy-mode = "rgmii-id"; //phy模式,包含rmii,rgmii等
nuvoton,ma35d1-sys = <&sys>;
clocks = <&clk EMAC0_GATE>, <&clk EPLL_DIV8>;
clock-names = "stmmaceth", "ptp_ref";
resets = <&reset XXXXXX_GMAC0>;
reset-names = "stmmaceth";
mac-id = <0>; //mac的序号
clk_csr = <4>; //时钟
status = "disable";
phy-handle = <ð_phy0>; //phy节点引用
mdio0 {
compatible = "snps,dwmac-mdio"; //mdio驱动
#address-cells = <1>;
#size-cells = <0>;
eth_phy0: ethernet-phy@1 { //phy节点实例,可在该节点下实现自己的phy驱动,实现自己的一些配置,1表示phy地址,与reg一致
reg = <1>;//
fixed-link{ //此为固定连接
speed=<100>;//速度100M
full-duplex;//
}
};
};
};
三、驱动注册流程
1.如何找到驱动
驱动使用结构体struct phy_driver进行注册,驱动加载时,会根据phy_id进行匹配驱动,找到驱动后,即可对phy寄存器进行相应的配置,并设置驱动名字。
因此结构体中以下两个参数phyid和name所有的phy驱动不要设置一致。
2.注册驱动demo
#define JLSEMI_PHY_ID_MASK 0xfffffff0
static struct phy_driver jlsemi_drivers[] = {
{
.phy_id = 0x937c4101, //phyID,可从芯片中读取到
.phy_id_mask = JLSEMI_PHY_ID_MASK, //掩码
.name = DRIVER_NAME_100M, //驱动名字
/* PHY_BASIC_FEATURES */
.features = PHY_BASIC_FEATURES,
.probe = jl51xx_probe, //驱动探测
.config_intr = jl51xx_config_intr, //配置
.read_status = jl51xx_read_status, //读状态
.config_init = jl51xx_config_init, //初始化
.remove = jl51xx_remove,//删除
#if (JLSEMI_PHY_WOL)
.get_wol = jl51xx_get_wol,
.set_wol = jl51xx_set_wol,
#endif
}
};
module_phy_driver(jlsemi_drivers);//注册phy驱动
注意:对于某些phy,可能存在link state需要cpu进行设置,在初始化时设置或者在.suspend函数中进行设置即可
四、遇到的问题
1) no phy at addr -1;stmmac_open: cannot attach to phy (error -19)
未识别到phy,可能是phy addr不正确,需要修改设备树中phyaddr (eth_phy0: ethernet-phy@1)
phy地址需要查看硬件手册及原理图
2) linkup,但是网络不通
可能是晶振导致时钟不对,另外检查芯片上的strap引脚是否配置正确
3) 使用 "virtual,mdio-gpio" 驱动,出现问题1中的记录,应检查gpio配置和phyaddr配置,且在设备树中将phy节点放置在根目录下
简单配置如下
{
mdio1 {
compatible = "virtual,mdio-gpio";
#address-cells = <1>;
#size-cells = <0>;
gpios =<&gpiof 0 0 #用于模拟sdio数据的两个gpio
&gpiof 1 0 >; #
eth_phy1: ethernet-phy@1 {#phy芯片的地址为1
reg = <1>;
};
};
gmac1: ethernet@40130000 {
compatible = "xxxxxxxx-gmac";
status = "disable";
phy-handle = <ð_phy1>;#引用phy节点
};
}
4) mii_bus无法读写内部寄存器:
可能是phy的时序要求不一样,对标准phy寄存器,使用Clause22时序即可
对于某些内部寄存器,可使用扩展的Clause45时序进行读写
对于一些特殊的phy芯片,读写内部寄存器的C45时序要求不同,例如:其startFrame为01,OpCode为00,
此时可根据芯片手册的时序要求,使用virtual,mdio-gpio驱动来实现特殊的时序
备注:因芯片的smi接口为寄存器操作,是标准的smi接口,不方便修改。改为gpio模拟时序后更方便修改时序
在Linux源码中有virtual,mdio-gpio驱动。在Linux源码中有很多类似的驱动支持,遇见新的硬件,可先在Linux源码中寻找驱动支持,
找到类似的解决方案。若Linux源码中没有相关内容,可找芯片厂家进行支持。