这节分析uboot中的网口驱动代码。
1 网口驱动函数列表
函数名 | 函数用途 |
tsec_initialize() | 网口初始化函数 |
tsec_init() | 网口启动函数 |
tsec_local_mdio_write() | MDIO口写函数 |
tsec_local_mdio_read() | MDIO口读函数 |
tsec_send() | 网口发送函数 |
tsec_recv() | 网口接收函数 |
tsec_configure_serdes() | 配置TBI PHY的函数 |
fsl_serdes_init() | Serdes模块初始化函数 |
init_phy() | PHY初始化函数 |
adjust_link() | 根据PHY状态配置MAC的函数 |
2 tsec_initialize()函数
该函数为ETSEC的初始化函数,在该函数中要初始化eth_device结构和私有的tsec_private结构,并初始化PHY。
int tsec_initialize(bd_t * bis, int index, char *devname)
{
struct eth_device *dev;
int i;
struct tsec_private *priv;
/*为dev分配空间*/
dev = (struct eth_device *)malloc(sizeof *dev);
if (NULL == dev)
return 0;
memset(dev, 0, sizeof *dev);
/*为priv分配空间*/
priv = (struct tsec_private *)malloc(sizeof(*priv));
if (NULL == priv)
return 0;
/*从tsec_info 数组中取合适的值去初始化私有结构tsec_private*/
privlist[num_tsecs++] = priv;
priv->regs = tsec_info[index].regs; //每个tsec寄存器的基址
priv->phyregs = tsec_info[index].miiregs; //PHY MDIO读写状态寄存器基址
/*TBI PHY的MDIO读写状态寄存器基址*/
priv->phyregs_sgmii = tsec_info[index].miiregs_sgmii;
priv->phyaddr = tsec_info[index].phyaddr; //PHY 地址
priv->flags = tsec_info[index].flags;
priv->ID = index;
/*使用将priv结构体挂到dev结构体下,挂载tsec的打开、关闭、发送、接收函数*/
sprintf(dev->name, tsec_info[index].devname);
dev->iobase = 0;
dev->priv = priv;
dev->init = tsec_init;
dev->halt = tsec_halt;
dev->send = tsec_send;
dev->recv = tsec_recv;
/*初始化IP地址*/
for (i = 0; i < 6; i++)
dev->enetaddr[i] = 0;
/*设置当前活跃的网口名相当于 set ethact eTSECn,将多个网口级联*/
eth_register(dev);
/* 通过设置MACCFG1寄存器重启 MAC */
priv->regs->maccfg1 |= MACCFG1_SOFT_RESET;
udelay(2); /* Soft Reset must be asserted for 3 TX clocks */
priv->regs->maccfg1 &= ~(MACCFG1_SOFT_RESET);
/*挂载MII口的读写函数*/
#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) /
&& !defined(BITBANGMII)
miiphy_register(dev->name, tsec_miiphy_read, tsec_miiphy_write);
#endif
/* 初始化PHY,并返回 */
return init_phy(dev);
}
3 init_phy()
static int init_phy(struct eth_device *dev)
{
struct tsec_private *priv = (struct tsec_private *)dev->priv;
struct phy_info *curphy;
volatile tsec_t *regs = priv->regs;
/*在TBIPA的寄存器中写入TBI PHY的地址*/
regs->tbipa = CONFIG_SYS_TBIPA_VALUE + priv->ID;
asm("sync");
/* 重启MII接口 */
priv->phyregs->miimcfg = MIIMCFG_RESET;
asm("sync");
priv->phyregs->miimcfg = MIIMCFG_INIT_VALUE;
asm("sync");
while (priv->phyregs->miimind & MIIMIND_BUSY) ;
/* 通过读PHY的2号3号寄存器获得该ETSEC外连的PHY的ID,搜索phy_info数组,找到符合ID的PHY信息返回。 */
curphy = get_phy_info(dev);
if (curphy == NULL)
{
priv->phyinfo = NULL;
printf("%s: No PHY found/n", dev->name);
return 0;
}
/*如果是SGMII的接口,需要使用TBI PHY,初始化TBI PHY,注意这里名字竟然叫serdes配置,Linux里面也这么叫,真是误人子弟啊。*/
if (regs->ecntrl & ECNTRL_SGMII_MODE)
tsec_configure_serdes(priv);
/*在符合条件的PHY的phy_info数组中调用其初始化配置函数*/
priv->phyinfo = curphy;
phy_run_commands(priv, priv->phyinfo->config);
return 1;
}
4 phy_info结构
Uboot中使用这个结构来完成phy的操作,所有的phy都要使用这个结构表示,下面是88E1111的phy_info结构:
struct phy_info phy_info_M88E1111S = {
0x01410cc, // PHY ID
"Marvell 88E1111S", // PHY名称
4,
(struct phy_cmd[])
{
/* 配置数组,在调用priv->phyinfo->config时将依次调用下面的内容,每个大括号内,第一个为PHY寄存器地址,第二个为value*/
/* Reset and configure the PHY */
{MIIM_CONTROL, MIIM_CONTROL_RESET, NULL},
/* Delay RGMII TX and RX */
{MIIM_GBIT_CONTROL, MIIM_GBIT_CONTROL_INIT, NULL},
{MIIM_ANAR, MIIM_ANAR_INIT, NULL},
{MIIM_CONTROL, MIIM_CONTROL_RESET, NULL},
{MIIM_CONTROL, MIIM_CONTROL_INIT, &mii_cr_init},
{miim_end,}
},
(struct phy_cmd[])
{
/* 启动数组,在ETSEC启动的时候要依次调用。 */
/* Status is read once to clear old link state */
{MIIM_STATUS, miim_read, NULL},
/* Auto-negotiate */
{MIIM_STATUS, miim_read, &mii_parse_sr},
/* Read the status */
{MIIM_88E1011_PHY_STATUS, miim_read, &mii_parse_88E1011_psr},
{miim_end,} },
(struct phy_cmd[])
{
/* shutdown */
{miim_end,}
},
};
需要注意的是,这个数组时uboot的源码中提供的,但是由于PHY与MAC之间接口使用的不同,这个数组中的内容需要根据需要,参考相应PHY的datasheet作出一定的修改。
5 tsec_init()
该函数不会在初始化的时候调用,它在每当你使用网口的时候被调用,使用网口,不管是ping,还是tftp。
int tsec_init(struct eth_device *dev, bd_t * bd)
{
uint tempval;
char tmpbuf[MAC_ADDR_LEN];
int i;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
/* 初始化MACCFG2和ECNTRL两个寄存器,这两个寄存器非常重要,它们主要是用来是配置MAC对PHY的接口。在这里先给个初始化的值,默认为GMII。*/
tsec_halt(dev);
regs->maccfg2 = MACCFG2_INIT_SETTINGS;
regs->ecntrl = ECNTRL_INIT_SETTINGS;
/* 配置MAC地址。 */
for (i = 0; i < 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];
regs->macstnaddr1 = tempval;
tempval = *((uint *) (tmpbuf + 4));
regs->macstnaddr2 = tempval;
/* reset the indices to zero */
rxIdx = 0;
txIdx = 0;
/* 清除其它的寄存器 */
init_registers(regs);
/* 启动tsec */
startup_tsec(dev);
/* If there's no link, fail */
return (priv->link ? 0 : -1);
}
6 startup_tsec()
static void startup_tsec(struct eth_device *dev)
{
int i;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
/* 初始化BD表基址指针 */
regs->tbase = (unsigned int)(&rtx.txbd[txIdx]);
regs->rbase = (unsigned int)(&rtx.rxbd[rxIdx]);
/* 初始化RX BD*/
for (i = 0; i < 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;
/*初始化TX BD*/
for (i = 0; i < TX_BUF_CNT; i++) {
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)
phy_run_commands(priv, priv->phyinfo->startup);
/*根据PHY的Copper侧值配置MAC寄存器*/
adjust_link(dev);
/* 使能MACCFG1中的发送接收使能 */
regs->maccfg1 |= (MACCFG1_RX_EN | MACCFG1_TX_EN);
/* 让DMA知道可以准备搬运了这里的DMA是ETSEC内部的,并不是CPU中的DMA单元。*/
regs->dmactrl |= DMACTRL_INIT_SETTINGS;
regs->tstat = TSTAT_CLEAR_THALT;
regs->dmactrl &= ~(DMACTRL_GRS | DMACTRL_GTS);
}
参照上面的phy_info数组的startup中的内容得知这里phy_run_commands(priv, priv->phyinfo->startup)要调用两个函数mii_parse_sr和mii_parse_88E1011_psr。
这两个函数主要是配置三个重要的priv结构体中的成员
priv->link
priv->speed
priv-> duplexity
分别是link状态,速率和双工。具体的代码就不分析了,主要是读PHY的Copper侧寄存器,然后根据寄存器的值去配置这三个成员,在后面的adjust_link函数中会根据这三个成员的值去配置MAC的MACCFG2和ECNTRL寄存器。