千兆网口Freescale ETSEC + Marvell 88E1111 uboot Linux 驱动分析Word格式.docx
《千兆网口Freescale ETSEC + Marvell 88E1111 uboot Linux 驱动分析Word格式.docx》由会员分享,可在线阅读,更多相关《千兆网口Freescale ETSEC + Marvell 88E1111 uboot Linux 驱动分析Word格式.docx(29页珍藏版)》请在冰豆网上搜索。
![千兆网口Freescale ETSEC + Marvell 88E1111 uboot Linux 驱动分析Word格式.docx](https://file1.bdocx.com/fileroot1/2023-2/2/36e336be-c7f2-4530-9a30-701706331fd9/36e336be-c7f2-4530-9a30-701706331fd91.gif)
如果CPU与PHY之间是RTBI接口,那么PCS层的工作在ETSEC中已经做完了,ETSEC中的TBI模块可以做PCS层的工作,PHY只需要做PMA和PMD的工作即可;
如果CPU与PHY之间是SGMII接口,那么PHY只需要完成PMD的工作,ETSEC中的PCS由TBI完成,而PMA由CPU自带的Serdes模块完成。
3BD表结构
在千兆以太网的驱动中,现在一般都使用一个叫BD表的东西来管理MAC层发送和接收的内存区域,如下图所示:
在IMMR映射的寄存器空间中有两组寄存器TBASEn和RBASEn,分别为TxBDRingn
和
RxBDRingn的指针。
MPC8314的ETSEC允许有8个TxBDRing和8个RxBDRing,他们都存放在内存的某个区域中。
每个BufferDescriptor
都是有8个字节构成,两个字节的状态,两个字节的数据长度和四个字节的数据指针,这个指针指向内存的另一块地方,这才是真正存储发送接收数据的地方。
BufferDescriptor必须在网口初始化的时候初始化,并将自己的地址赋给TBASEn和RBASEn。
在网口驱动程序中可以看到,每个BDRing中的BD数量是可变的(我们设为64),而他们之间并没有指针连接,只是一段连续的空间,顺序下来的,所谓的环只是一个虚拟的概念,在最后一个BD时,需要将BD状态位中的W位(Wrap)置一,表示这是最后一个BD,他的下一个BD就是第一个BD。
如下图所示:
下面一节将结合uboot源码分析一下网口初始化以及PHY配置的过程,再下一节会分析内核中的驱动。
为什么先说uboot,因为在我看来,驱动程序就是分为两个部分,1
按照Datasheet的说明去配置寄存器,2
添加符合操作系统规范去融入操作系统。
在uboot下系统很简单,代码一目了然,所以我们应该在boot下先把寄存器配置好,再去分析复杂的多的内核代码。
这节分析uboot中的网口驱动代码。
网口驱动函数列表
函数名
函数用途
tsec_initialize()
网口初始化函数
tsec_init()
网口启动函数
tsec_local_mdio_write()
MDIO口写函数
tsec_local_mdio_read()
MDIO口读函数
tsec_send()
网口发送函数
tsec_recv()
网口接收函数
tsec_configure_serdes()
配置TBIPHY的函数
fsl_serdes_init()
Serdes模块初始化函数
init_phy()
PHY初始化函数
adjust_link()
根据PHY状态配置MAC的函数
2tsec_initialize()函数
该函数为ETSEC的初始化函数,在该函数中要初始化eth_device结构和私有的tsec_private结构,并初始化PHY。
inttsec_initialize(bd_t*bis,intindex,char*devname)
{
structeth_device*dev;
inti;
structtsec_private*priv;
/*为dev分配空间*/
dev=(structeth_device*)malloc(sizeof*dev);
if(NULL==dev)
return0;
memset(dev,0,sizeof*dev);
/*为priv分配空间*/
priv=(structtsec_private*)malloc(sizeof(*priv));
if(NULL==priv)
/*从tsec_info数组中取合适的值去初始化私有结构tsec_private*/
privlist[num_tsecs++]=priv;
priv->
regs=tsec_info[index].regs;
//每个tsec寄存器的基址
phyregs=tsec_info[index].miiregs;
//PHYMDIO读写状态寄存器基址
/*TBIPHY的MDIO读写状态寄存器基址*/
phyregs_sgmii=tsec_info[index].miiregs_sgmii;
phyaddr=tsec_info[index].phyaddr;
//PHY地址
flags=tsec_info[index].flags;
ID=index;
/*使用将priv结构体挂到dev结构体下,挂载tsec的打开、关闭、发送、接收函数*/
sprintf(dev->
name,tsec_info[index].devname);
dev->
iobase=0;
priv=priv;
init=tsec_init;
halt=tsec_halt;
send=tsec_send;
recv=tsec_recv;
/*初始化IP地址*/
for(i=0;
i<
6;
i++)
enetaddr[i]=0;
/*设置当前活跃的网口名相当于setethacteTSECn,将多个网口级联*/
eth_register(dev);
/*通过设置MACCFG1寄存器重启MAC*/
regs->
maccfg1|=MACCFG1_SOFT_RESET;
udelay
(2);
/*SoftResetmustbeassertedfor3TXclocks*/
maccfg1&
=~(MACCFG1_SOFT_RESET);
/*挂载MII口的读写函数*/
#ifdefined(CONFIG_MII)||defined(CONFIG_CMD_MII)/
&
!
defined(BITBANGMII)
miiphy_register(dev->
name,tsec_miiphy_read,tsec_miiphy_write);
#endif
/*初始化PHY,并返回*/
returninit_phy(dev);
}
3init_phy()
staticintinit_phy(structeth_device*dev)
structtsec_private*priv=(structtsec_private*)dev->
priv;
structphy_info*curphy;
volatiletsec_t*regs=priv->
regs;
/*在TBIPA的寄存器中写入TBIPHY的地址*/
tbipa=CONFIG_SYS_TBIPA_VALUE+priv->
ID;
asm("
sync"
);
/*重启MII接口*/
phyregs->
miimcfg=MIIMCFG_RESET;
miimcfg=MIIMCFG_INIT_VALUE;
while(priv->
miimind&
MIIMIND_BUSY);
/*通过读PHY的2号3号寄存器获得该ETSEC外连的PHY的ID,搜索phy_info数组,找到符合ID的PHY信息返回。
*/
curphy=get_phy_info(dev);
if(curphy==NULL)
phyinfo=NULL;
printf("
%s:
NoPHYfound/n"
dev->
name);
/*如果是SGMII的接口,需要使用TBIPHY,初始化TBIPHY,注意这里名字竟然叫serdes配置,Linux里面也这么叫,真是误人子弟啊。
*/
if(regs->
ecntrl&
ECNTRL_SGMII_MODE)
tsec_configure_serdes(priv);
/*在符合条件的PHY的phy_info数组中调用其初始化配置函数*/
phyinfo=curphy;
phy_run_commands(priv,priv->
phyinfo->
config);
return1;
4phy_info结构
Uboot中使用这个结构来完成phy的操作,所有的phy都要使用这个结构表示,下面是88E1111的phy_info结构:
structphy_infophy_info_M88E1111S={
0x01410cc,
//PHYID
"
Marvell88E1111S"
//PHY名称
4,
(structphy_cmd[])
{
/*配置数组,在调用priv->
config时将依次调用下面的内容,每个大括号内,第一个为PHY寄存器地址,第二个为value*/
/*ResetandconfigurethePHY*/
{MIIM_CONTROL,MIIM_CONTROL_RESET,NULL},
/*DelayRGMIITXandRX*/
{MIIM_GBIT_CONTROL,MIIM_GBIT_CONTROL_INIT,NULL},
{MIIM_ANAR,MIIM_ANAR_INIT,NULL},
{MIIM_CONTROL,MIIM_CONTROL_INIT,&
mii_cr_init},
{miim_end,}
},
/*启动数组,在ETSEC启动的时候要依次调用。
/*Statusisreadoncetoclearoldlinkstate*/
{MIIM_STATUS,miim_read,NULL},
/*Auto-negotiate*/
{MIIM_STATUS,miim_read,&
mii_parse_sr},
/*Readthestatus*/
{MIIM_88E1011_PHY_STATUS,miim_read,&
mii_parse_88E1011_psr},
{miim_end,}
/*shutdown*/
},
};
需要注意的是,这个数组时uboot的源码中提供的,但是由于PHY与MAC之间接口使用的不同,这个数组中的内容需要根据需要,参考相应PHY的datasheet作出一定的修改。
5tsec_init()
该函数不会在初始化的时候调用,它在每当你使用网口的时候被调用,使用网口,不管是ping,还是tftp。
inttsec_init(structeth_device*dev,bd_t*bd)
uinttempval;
chartmpbuf[MAC_ADDR_LEN];
/*初始化MACCFG2和ECNTRL两个寄存器,这两个寄存器非常重要,它们主要是用来是配置MAC对PHY的接口。
在这里先给个初始化的值,默认为GMII。
*/
tsec_halt(dev);
maccfg2=MACCFG2_INIT_SETTINGS;
ecntrl=ECNTRL_INIT_SETTINGS;
/*配置MAC地址。
MAC_ADDR_LEN;
i++)
tmpbuf[MAC_ADDR_LEN-1-i]=dev->
enetaddr[i];
}
tempval=(tmpbuf[0]<
<
24)|(tmpbuf[1]<
16)|(tmpbuf[2]<
8)|
tmpbuf[3];
macstnaddr1=tempval;
tempval=*((uint*)(tmpbuf+4));
macstnaddr2=tempval;
/*resettheindicestozero*/
rxIdx=0;
txIdx=0;
/*清除其它的寄存器*/
init_registers(regs);
/*启动tsec*/
startup_tsec(dev);
/*Ifthere'
snolink,fail*/
return(priv->
link?
0:
-1);
6startup_tsec()
staticvoidstartup_tsec(structeth_device*dev)
/*初始化BD表基址指针*/
tbase=(unsignedint)(&
rtx.txbd[txIdx]);
rbase=(unsignedint)(&
rtx.rxbd[rxIdx]);
/*初始化RXBD*/
PKTBUFSRX;
i++){
rtx.rxbd[i].status=(RXBD_EMPTY|RXBD_INTERRUPT);
rtx.rxbd[i].length=0;
rtx.rxbd[i].bufPtr=(uint)NetRxPackets[i];
rtx.rxbd[PKTBUFSRX-1].status|=RXBD_WRAP;
/*初始化TXBD*/
TX_BUF_CNT;
rtx.txbd[i].status=0;
rtx.txbd[i].length=0;
rtx.txbd[i].bufPtr=0;
rtx.txbd[TX_BUF_CNT-1].status|=TXBD_WRAP;
/*又要去找phy_info数组了,这次调用的是startup中的命令和函数*/
if(priv->
phyinfo)
startup);
/*根据PHY的Copper侧值配置MAC寄存器*/
adjust_link(dev);
/*使能MACCFG1中的发送接收使能*/
maccfg1|=(MACCFG1_RX_EN|MACCFG1_TX_EN);
/*让DMA知道可以准备搬运了这里的DMA是ETSEC内部的,并不是CPU中的DMA单元。
dmactrl|=DMACTRL_INIT_SETTINGS;
tstat=TSTAT_CLEAR_THALT;
dmactrl&
=~(DMACTRL_GRS|DMACTRL_GTS);
参照上面的phy_info数组的startup中的内容得知这里phy_run_commands(priv,priv->
st