网络MAC控制器的初始化流程和SD控制器其实类似,大致分为以下几步:
- 引脚复用:老生常谈的东西,在使用MAC功能之前需要将引脚复用设置正确,由于本次我们采用的uboot自带网络功能,已经将引脚复用初始化好了,所以这个不需要再次设置。
- 初始化时钟:设置ccu寄存器使能MAC控制器的时钟源。
- 复位和初始化MAC控制器:由于网络是使用的DMA描述符来传输数据,所以我们还需要设置DMA描述符内存和缓冲区。
时钟源使能和SD类似,我们在ccu中添加一个 ccu_emac_bus_gate_enable 接口:
void ccu_emac_bus_gate_enable (int enable) { u32 val; val = readl(CCU_REG_EMAC_BUS); if (enable) { // de assert emac writel(val | (1 << 16), CCU_REG_EMAC_BUS); usleep(100); // emac gate pass writel(val | (1 << 0), CCU_REG_EMAC_BUS); } else { // emac gate mask writel(val & (~(1 << 0)), CCU_REG_EMAC_BUS); } }
MAC控制器初始化中有一个比较重要的操作就是发送和接收DMA描述符及缓冲区的设置。网络收发的数据需要存放在DMA内存中,而用来描述这些数据的描述符本身也要占用DMA内存,所以我们使用 API_VmmDmaAlloc 接口来申请DMA内存区内存,通过这个接口申请出来的内存是不带cache的,所以避免了cache一致性问题。
我们将收发描述符各自初始化使用512个,一般情况下这足够了,每个描述符对应的buffer设置为和uboot下驱动中一样的大小2044。接收描述符初始化时需要将描述符的所有者设置为DMA硬件所有,因为接收是异步的,所以还需要在中断中进行接收数据的处理, 本网卡驱动中,发送不使用中断。DMA描述符的设置如下所示:
static void rx_descs_init(struct emac_eth_dev *emac) { struct emac_dma_desc *desc_table_p; char *rxbuffs; struct emac_dma_desc *desc_p; int i; desc_table_p = emac->rx_chain = API_VmmDmaAlloc(CONFIG_RX_DESCR_NUM * sizeof(struct emac_dma_desc)); rxbuffs = emac->rxbuffer = API_VmmDmaAlloc(RX_TOTAL_BUFSIZE); bzero(desc_table_p, 0); bzero(rxbuffs, 0); for (i = 0; i < CONFIG_RX_DESCR_NUM; i++) { desc_p = &desc_table_p[i]; desc_p->buf_addr = (uintptr_t)&rxbuffs[i * CONFIG_ETH_BUFSIZE]; desc_p->next = (uintptr_t)&desc_table_p[i + 1]; desc_p->ctl_size = CONFIG_ETH_RXSIZE; desc_p->status = EMAC_DESC_OWN_DMA; } /* Correcting the last pointer of the chain */ desc_p->next = (uintptr_t)&desc_table_p[0]; writel((uintptr_t)&desc_table_p[0], (emac->mac_reg + EMAC_RX_DMA_DESC)); emac->rx_currdesc = 0; } static void tx_descs_init(struct emac_eth_dev *emac) { struct emac_dma_desc *desc_table_p; char *txbuffs; struct emac_dma_desc *desc_p; int i; desc_table_p = emac->tx_chain = API_VmmDmaAlloc(CONFIG_TX_DESCR_NUM * sizeof(struct emac_dma_desc)); txbuffs = emac->txbuffer = API_VmmDmaAlloc(TX_TOTAL_BUFSIZE); for (i = 0; i < CONFIG_TX_DESCR_NUM; i++) { desc_p = &desc_table_p[i]; desc_p->buf_addr = (uintptr_t)&txbuffs[i * CONFIG_ETH_BUFSIZE]; desc_p->next = (uintptr_t)&desc_table_p[i + 1]; desc_p->ctl_size = 0; desc_p->status = 0; } /* Correcting the last pointer of the chain */ desc_p->next = (uintptr_t)&desc_table_p[0]; writel((uintptr_t)&desc_table_p[0], emac->mac_reg + EMAC_TX_DMA_DESC); emac->tx_currdesc = 0; }
发送描述符的大小和所有者在实际发送函数中会去设置,DMA描述符初始化好之后就将描述符基址设置到相应的寄存器中, 同时设置其他寄存器使能DMA功能,最后对外封装出 emacInit 接口使用:
int emacInit(struct netdev *pnetdev) { struct emac_eth_dev *emac = &emac_dev; emac->mac_reg = (void *)EMAC_REG_BASE; emac->sysctl_reg = (void *)SYS_CFG_REG_BASE ; // TODO:初始化引脚复用 // TODO:初始化ccu时钟和复位 ccu_emac_bus_gate_enable(1); sun8i_emac_set_syscon(emac); // 初始化mdio和phy sun8i_mdio_init(pnetdev); sun8i_emac_eth_init(pnetdev, emac); return 0; }
这其中还有其他一些系统寄存器需要去设置,大家自行对照手册和源码进行深入分析,这里就不展开了。
评论