U-Boot中改用DM8168的网口1

背景

在HXX板中,DM8168使用了和EVM板一样的配置,即两个EMAC模块分别连接到两个PHY上,用同一个MDIO模块管理。这两个PHY配置为不同地址。在TFTP方式烧写内核和文件系统过程中,U-Boot默认使用且只支持EMAC0实现网口通信。
然而在新研FXX板中,DM8168改变了网口连接方式,其EMAC0未引出板外,EMAC1通过PHY引出RJ45接口。因此在烧写内核和文件系统过程中只能使用EMAC1,这就需要修改U-Boot源码使其支持EMAC1。

U-Boot网口驱动分析

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功能正常,问题解决。

MAC地址

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");
    }
  1. 它先从环境变量中读取MAC地址,若环境变量中没有设置,则从MAC_ID0_LO和MAC_ID0_HI寄存器中读取并配入环境变量,这样下次启动就直接可以使用该值了。
  2. 然后验证MAC地址的有效性,即它不能是广播地址,也不能是全0地址。
  3. 若验证通过则将其写入全局变量中保存备用,否则打印报警。

这里需要注意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。当然不改也可以,完全没有问题。

你可能感兴趣的:(u-boot,网口)