在HXX板中,DM8168使用了和EVM板一样的配置,即两个EMAC模块分别连接到两个PHY上,用同一个MDIO模块管理。这两个PHY配置为不同地址。在TFTP方式烧写内核和文件系统过程中,U-Boot默认使用且只支持EMAC0实现网口通信。
然而在新研FXX板中,DM8168改变了网口连接方式,其EMAC0未引出板外,EMAC1通过PHY引出RJ45接口。因此在烧写内核和文件系统过程中只能使用EMAC1,这就需要修改U-Boot源码使其支持EMAC1。
U-Boot的启动过程不赘述,其中与EMAC相关的几个函数的调用关系为
start_armboot()
–>eth_initialize()
–>board_eth_init()
–>davinci_emac_initialize()
–>davinci_eth_mdio_enable()
&davinci_eth_phy_detect()
&davinci_eth_phy_read()
&davinci_eth_phy_write()
,此外还有一些如miiphy_init()
、eth_register()
、miiphy_register()
等与硬件无关的代码。
初始化过程将网络设备注册到U-Boot系统中,但并不真正使用它。在调用U-Boot网络命令时,才会调用网口的关闭、打开、接收、发送等函数。在DM8168上对应davinci_eth_open()
、davinci_eth_close()
、davinci_eth_send_packet()
和davinci_eth_rcv_packet()
函数。
了解了这个脉络,就可以进一步分析代码了。先看第一个真正的硬件操作:
============ drivers/net/davince_emac.c 115 129 ==================
static void davinci_eth_mdio_enable(void)
{
u_int32_t clkdiv;
clkdiv = (EMAC_MDIO_BUS_FREQ / EMAC_MDIO_CLOCK_FREQ) - 1;
writel((clkdiv & 0xff) |
MDIO_CONTROL_ENABLE |
MDIO_CONTROL_FAULT |
MDIO_CONTROL_FAULT_ENABLE,
&adap_mdio->CONTROL);
while (readl(&adap_mdio->CONTROL) & MDIO_CONTROL_IDLE)
;
}
它计算出时钟分频系数,写入到一个寄存器中,然后读该寄存器中某一位直到时钟稳定。
============ drivers/net/davince_emac.c 95 102 ==================
/* EMAC Addresses */
static volatile emac_regs *adap_emac = (emac_regs *)EMAC_BASE_ADDR;
static volatile ewrap_regs *adap_ewrap = (ewrap_regs *)EMAC_WRAPPER_BASE_ADDR;
static volatile mdio_regs *adap_mdio = (mdio_regs *)EMAC_MDIO_BASE_ADDR;
/* EMAC descriptors */
static volatile emac_desc *emac_rx_desc = (emac_desc *)(EMAC_WRAPPER_RAM_ADDR + EMAC_RX_DESC_BASE);
static volatile emac_desc *emac_tx_desc = (emac_desc *)(EMAC_WRAPPER_RAM_ADDR + EMAC_TX_DESC_BASE);
可见寄存器是由EMAC_MDIO_BASE_ADDR等宏来寻址的,这些宏在arch/arm/include/asm/arch-ti81xx/emac_defs.h文件中定义,默认指向了EMA0的基址。需要修改这些宏指向EMAC1。
注意DM8168的两个EMAC共用同一个MDIO模块,因此EMAC_MDIO_BASE_ADDR不需要修改。
再看下一个函数davinci_eth_phy_detect():
============ drivers/net/davince_emac.c 143 145 ==================
phy_act_state = readl(&adap_mdio->ALIVE) & EMAC_MDIO_PHY_MASK;
if (phy_act_state == 0)
return(0); /* No active PHYs */
说明MDIO的ALIVE寄存器中某一位必须为1,这个掩码宏也在arch/arm/include/asm/arch-ti81xx/emac_defs.h文件中定义
======= arch/arm/include/asm/arch-ti81xx/emac_defs.h 54 56 =============
/*TODO: verify Phy address */
#define EMAC_MDIO_PHY_NUM (1)
#define EMAC_MDIO_PHY_MASK (1 << EMAC_MDIO_PHY_NUM)
可见EMAC_MDIO_PHY_NUM表示PHY的地址,因此要根据EMAC1外接PHY的物理地址修改其值。
其他几个函数不再详述,分析其源码可知不再需要进一步修改。
做完上述两处修改后重新编译U-Boot并烧入Nandflash启动,发现网口1仍然不能使用。这时就要想办法开始调试了。
U-Boot的调试有两个办法,一是依靠打印信息,二是通过其提供的md等命令观察内存。
首先是在drivers/net/davince_emac.c中把emac_dbg变量值赋为1,开启debug_emac宏,其次是在include/configs/ti8168_evm.h中定义一个DEBUG宏,开启一些DEBUG打印信息。
对比前后两次的打印信息,发现错误情况下少了一句Ethernet PHY: GENERIC @ 0x01。在代码中搜索定位分析,最后发现是davinci_emac_initialize()
函数走到了异常退出分支。这时通过手动添加的一些打印语句,定位到davinci_eth_phy_detect()
函数中上面代码里读ALIVE寄存器值不正确。我们外部连接两个PHY,地址分别是1和2,因此期望第1和2位置位,即读到6,但实际读到值为2,即只有PHY1正常。
再次分析代码后也未找到问题,怀疑是使用的这块板子PHY2硬件问题,换块板子再试发现现象一样,且两块板子在内核启动后两个网口都能正常使用,因此暂时排除硬件问题。
在这个问题上纠结许久也未解决。后来一次偶然间在启动失败后用U-Boot的md命令看ALIVE寄存器时,发现该寄存器值是6,但为什么U-Boot启动过程中读到的值是2呢?在U-Boot中加上一个读失败重读机制后发现,一段时间后该值会由2变为6然后一直保持6。
原来ALIVE寄存器值是MDIO模块自行通过MDIO接口依次与每个地址通信后的结果,通信需要一定时间,读早了地址2还未访问过,自然2位上是0了。在davinci_eth_phy_detect()
前加上一段延时,再检测就正常了。
初始化成功后用TFTP协议下载内核镜像,但是一直超时失败。U-Boot的打印显示网口先关闭又打开,发送一个ARP请求包后一直收不到ARP应答。然而在上位机用Wireshark抓包,发现收不到DM8168发来的任何包。这说明问题在网口发送上,并未真正将数据发送成功。
在跟踪问题一段时间后,在davinci_emac_initialize()
函数的注释里发现一段话:
This function initializes the emac hardware. It does NOT initialize EMAC modules power or pin multiplexors
从这段话中想起了电源和引脚复用的问题。查询DM8168手册得知,两个EMAC的电源和时钟从上电后就默认正常供给,不需要特殊配置。EMAC0涉及的几个引脚默认就是属于EMAC模块使用,EMAC1引脚默认却并非EMAC模块,需要配置几个PINCTRL寄存器。
分析U-Boot启动流程,在第一阶段的s_init()
函数中调用了一个set_muxconf_regs()
函数,这个函数就是用来配置引脚复用的,在这里配置后再测试,TFTP功能正常,问题解决。
在board_eth_init()
函数中,有一段配置网口MAC地址的代码:
============ board/ti/ti8168/evm.c 195 217 ==================
if(!eth_getenv_enetaddr("ethaddr", mac_addr)) {
printf("<ethaddr> not set. Reading from E-fuse\n");
/* try reading mac address from efuse */
mac_lo = __raw_readl(MAC_ID0_LO);
mac_hi = __raw_readl(MAC_ID0_HI);
mac_addr[0] = mac_hi & 0xFF;
mac_addr[1] = (mac_hi & 0xFF00) >> 8;
mac_addr[2] = (mac_hi & 0xFF0000) >> 16;
mac_addr[3] = (mac_hi & 0xFF000000) >> 24;
mac_addr[4] = mac_lo & 0xFF;
mac_addr[5] = (mac_lo & 0xFF00) >> 8;
/* set the ethaddr variable with MACID detected */
eth_setenv_enetaddr("ethaddr", mac_addr);
}
if(is_valid_ether_addr(mac_addr)) {
printf("Detected MACID:%x:%x:%x:%x:%x:%x\n",mac_addr[0],
mac_addr[1], mac_addr[2], mac_addr[3],
mac_addr[4], mac_addr[5]);
davinci_eth_set_mac_addr(mac_addr);
} else {
printf("Caution:using static MACID!! Set <ethaddr> variable\n");
}
这里需要注意MAC_ID0_LO和MAC_ID0_HI寄存器。DM8168有两组类似的只读寄存器,另一组名称为MAC_ID1_LO和MAC_ID1_HI,它们保存了出厂时TI烧入的两组MAC地址,是全球独一无二的,正好可以用来分别配置到两个EMAC。这里U-Boot代码默认给EMAC0配置第一组地址。在Linux内核中,也是将第一组地址配给EMAC0,第二组地址配为EMAC1。为了与稍后启动的Linux内核保持一致,这里将代码修改为MAC_ID1_LO和MAC_ID1_HI。当然不改也可以,完全没有问题。