/* * Copyright (c) 2013 Nuvoton Technology Corp. * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * Description: NUC472 MAC driver source file */ #include #ifdef RT_USING_LWIP #include #include "nuc472_eth.h" #include #include #include #include "lwipopts.h" #define ETH_TRIGGER_RX() EMAC->RXST = 0 #define ETH_TRIGGER_TX() EMAC->TXST = 0 #define ETH_ENABLE_TX() EMAC->CTL |= EMAC_CTL_TXON #define ETH_ENABLE_RX() EMAC->CTL |= EMAC_CTL_RXON #define ETH_DISABLE_TX() EMAC->CTL &= ~EMAC_CTL_TXON #define ETH_DISABLE_RX() EMAC->CTL &= ~EMAC_CTL_RXON rt_uint8_t ethaddr[] = {0x00, 0x00, 0x00, 0x59, 0x16, 0x88}; struct rt_nuc472_emac { struct eth_device parent; EMAC_T *emac_base; rt_uint8_t dev_addr[6]; struct eth_descriptor volatile *cur_tx_desc_ptr, *cur_rx_desc_ptr, *fin_tx_desc_ptr; struct eth_descriptor rx_desc[RX_DESCRIPTOR_NUM]; struct eth_descriptor tx_desc[TX_DESCRIPTOR_NUM]; rt_uint8_t rx_buf[RX_DESCRIPTOR_NUM][PACKET_BUFFER_SIZE]; rt_uint8_t tx_buf[TX_DESCRIPTOR_NUM][PACKET_BUFFER_SIZE]; }; typedef struct rt_nuc472_emac* rt_nuc472_emac_t; struct rt_nuc472_emac nuc472_emac_device; static void mdio_write(rt_nuc472_emac_t emac, rt_uint8_t addr, rt_uint8_t reg, rt_uint16_t val) { EMAC_T *emac_base = emac->emac_base; emac_base->MIIMDAT = val; emac_base->MIIMCTL = (addr << EMAC_MIIMCTL_PHYADDR_Pos) | reg | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_WRITE_Msk | EMAC_MIIMCTL_MDCON_Msk; while (emac_base->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk); } static rt_uint16_t mdio_read(rt_nuc472_emac_t emac, rt_uint8_t addr, rt_uint8_t reg) { EMAC_T *emac_base = emac->emac_base; emac_base->MIIMCTL = (addr << EMAC_MIIMCTL_PHYADDR_Pos) | reg | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_MDCON_Msk; while (emac_base->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk); return(emac_base->MIIMDAT); } static int reset_phy(rt_nuc472_emac_t emac) { rt_uint16_t reg; rt_uint32_t delay; EMAC_T *emac_base = emac->emac_base; mdio_write(emac, CONFIG_PHY_ADDR, MII_BMCR, BMCR_RESET); delay = 2000; while(delay-- > 0) { if((mdio_read(emac, CONFIG_PHY_ADDR, MII_BMCR) & BMCR_RESET) == 0) break; } if(delay == 0) { rt_kprintf("Reset phy failed\n"); return(-1); } mdio_write(emac, CONFIG_PHY_ADDR, MII_ADVERTISE, ADVERTISE_CSMA | ADVERTISE_10HALF | ADVERTISE_10FULL | ADVERTISE_100HALF | ADVERTISE_100FULL); reg = mdio_read(emac, CONFIG_PHY_ADDR, MII_BMCR); mdio_write(emac, CONFIG_PHY_ADDR, MII_BMCR, reg | BMCR_ANRESTART); delay = 200000; while(delay-- > 0) { if((mdio_read(emac, CONFIG_PHY_ADDR, MII_BMSR) & (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)) == (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)) break; } if(delay == 0) { rt_kprintf("AN failed. Set to 100 FULL\n"); emac_base->CTL |= (EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); return(-1); } else { reg = mdio_read(emac, CONFIG_PHY_ADDR, MII_LPA); if(reg & ADVERTISE_100FULL) { rt_kprintf("100 full\n"); emac_base->CTL |= (EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); } else if(reg & ADVERTISE_100HALF) { rt_kprintf("100 half\n"); emac_base->CTL = (EMAC->CTL & ~EMAC_CTL_FUDUP_Msk) | EMAC_CTL_OPMODE_Msk; } else if(reg & ADVERTISE_10FULL) { rt_kprintf("10 full\n"); emac_base->CTL = (EMAC->CTL & ~EMAC_CTL_OPMODE_Msk) | EMAC_CTL_FUDUP_Msk; } else { rt_kprintf("10 half\n"); emac_base->CTL &= ~(EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); } } return(0); } static void init_tx_desc(rt_nuc472_emac_t emac) { EMAC_T *emac_base = emac->emac_base; rt_uint32_t i; emac->cur_tx_desc_ptr = emac->fin_tx_desc_ptr = &emac->tx_desc[0]; for(i = 0; i < TX_DESCRIPTOR_NUM; i++) { emac->tx_desc[i].status1 = TXFD_PADEN | TXFD_CRCAPP | TXFD_INTEN; emac->tx_desc[i].buf = &emac->tx_buf[i][0]; emac->tx_desc[i].status2 = 0; emac->tx_desc[i].next = &emac->tx_desc[(i + 1) % TX_DESCRIPTOR_NUM]; } emac_base->TXDSA = (unsigned int)&emac->tx_desc[0]; return; } static void init_rx_desc(rt_nuc472_emac_t emac) { EMAC_T *emac_base = emac->emac_base; rt_uint32_t i; emac->cur_rx_desc_ptr = &emac->rx_desc[0]; for(i = 0; i < RX_DESCRIPTOR_NUM; i++) { emac->rx_desc[i].status1 = OWNERSHIP_EMAC; emac->rx_desc[i].buf = &emac->rx_buf[i][0]; emac->rx_desc[i].status2 = 0; emac->rx_desc[i].next = &emac->rx_desc[(i + 1) % RX_DESCRIPTOR_NUM]; } emac_base->RXDSA = (unsigned int)&emac->rx_desc[0]; return; } static void set_mac_addr(rt_nuc472_emac_t emac, rt_uint8_t *addr) { EMAC_T *emac_base = emac->emac_base; emac_base->CAM0M = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3]; emac_base->CAM0L = (addr[4] << 24) | (addr[5] << 16); emac_base->CAMCTL = EMAC_CAMCTL_CMPEN_Msk | EMAC_CAMCTL_AMP_Msk | EMAC_CAMCTL_ABP_Msk; emac_base->CAMEN = 1; // Enable CAM entry 0 } void EMAC_init(rt_nuc472_emac_t emac, rt_uint8_t *mac_addr) { EMAC_T *emac_base = emac->emac_base; RT_ASSERT(emac->dev_addr != RT_NULL); CLK_EnableModuleClock(EMAC_MODULE); // Configure MDC clock rate to HCLK / (127 + 1) = 656 kHz if system is running at 84 MHz CLK_SetModuleClock(EMAC_MODULE, 0, CLK_CLKDIV3_EMAC(127)); // Configure RMII pins SYS->GPC_MFPL = SYS_GPC_MFPL_PC0MFP_EMAC_REFCLK | SYS_GPC_MFPL_PC1MFP_EMAC_MII_RXERR | SYS_GPC_MFPL_PC2MFP_EMAC_MII_RXDV | SYS_GPC_MFPL_PC3MFP_EMAC_MII_RXD1 | SYS_GPC_MFPL_PC4MFP_EMAC_MII_RXD0 | SYS_GPC_MFPL_PC6MFP_EMAC_MII_TXD0 | SYS_GPC_MFPL_PC7MFP_EMAC_MII_TXD1; SYS->GPC_MFPH = SYS_GPC_MFPH_PC8MFP_EMAC_MII_TXEN; // Enable high slew rate on all RMII pins PC->SLEWCTL |= 0x1DF; // Configure MDC, MDIO at PB14 & PB15 SYS->GPB_MFPH = SYS_GPB_MFPH_PB14MFP_EMAC_MII_MDC | SYS_GPB_MFPH_PB15MFP_EMAC_MII_MDIO; // Reset MAC emac_base->CTL = EMAC_CTL_RST_Msk; init_tx_desc(emac); init_rx_desc(emac); set_mac_addr(emac, mac_addr); // need to reconfigure hardware address 'cos we just RESET emc... reset_phy(emac); emac_base->CTL |= EMAC_CTL_STRIPCRC_Msk | EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk | EMAC_CTL_RMIIEN_Msk | EMAC_CTL_RMIIRXCTL_Msk; emac_base->INTEN |= EMAC_INTEN_RXIEN_Msk | EMAC_INTEN_RXGDIEN_Msk | EMAC_INTEN_RDUIEN_Msk | EMAC_INTEN_RXBEIEN_Msk | EMAC_INTEN_TXIEN_Msk | EMAC_INTEN_TXABTIEN_Msk | EMAC_INTEN_TXCPIEN_Msk | EMAC_INTEN_TXBEIEN_Msk; emac_base->RXST = 0; // trigger Rx //NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); NVIC_SetPriority(EMAC_TX_IRQn, 0); NVIC_EnableIRQ(EMAC_TX_IRQn); NVIC_SetPriority(EMAC_RX_IRQn, 1); NVIC_EnableIRQ(EMAC_RX_IRQn); } void ETH_halt(rt_nuc472_emac_t emac) { EMAC_T *emac_base = emac->emac_base; emac_base->CTL &= ~(EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk); } void emac_emac_done(rt_nuc472_emac_t emac) { EMAC_T *emac_base = emac->emac_base; unsigned int status; status = emac_base->INTSTS & 0xFFFF0000; emac_base->INTSTS = status; if(status & EMAC_INTSTS_TSALMIF_Msk) { // TODO: time stamp alarm. write me!! } if(status & EMAC_INTSTS_TXBEIF_Msk) { // Shouldn't goes here, unless descriptor corrupted return; } } rt_uint8_t *emac_get_tx_buf(rt_nuc472_emac_t emac) { if(emac->cur_tx_desc_ptr->status1 & OWNERSHIP_EMAC) { return(RT_NULL); } else { return(emac->cur_tx_desc_ptr->buf); } } void ETH_trigger_tx(rt_nuc472_emac_t emac, rt_uint16_t length) { struct eth_descriptor volatile *desc; emac->cur_tx_desc_ptr->status2 = (unsigned int)length; desc = emac->cur_tx_desc_ptr->next; // in case TX is transmitting and overwrite next pointer before we can update cur_tx_desc_ptr emac->cur_tx_desc_ptr->status1 |= OWNERSHIP_EMAC; emac->cur_tx_desc_ptr = desc; ETH_TRIGGER_TX(); } /* * NUC472 EMAC Driver for RT-Thread * Change Logs: * Date Author Notes * 2017-12-31 Bluebear233 first implementation */ void rt_nuc472_emac_tx_isr(rt_nuc472_emac_t emac) { emac_emac_done(emac); } void rt_nuc472_emac_rx_isr(rt_nuc472_emac_t emac) { EMAC_T *emac_base = emac->emac_base; unsigned int status = emac_base->INTSTS & 0xFFFF; emac_base->INTSTS = status; eth_device_ready(&emac->parent); } void EMAC_TX_IRQHandler(void) { /* enter interrupt */ rt_interrupt_enter(); rt_nuc472_emac_tx_isr(&nuc472_emac_device); /* leave interrupt */ rt_interrupt_leave(); } void EMAC_RX_IRQHandler(void) { /* enter interrupt */ rt_interrupt_enter(); rt_nuc472_emac_rx_isr(&nuc472_emac_device); /* leave interrupt */ rt_interrupt_leave(); } static rt_err_t rt_nuc472_emac_init(rt_device_t dev) { rt_nuc472_emac_t emac = (rt_nuc472_emac_t)dev; EMAC_init(emac, emac->dev_addr); #if LWIP_IPV4 && LWIP_IGMP netif_set_igmp_mac_filter(stm32_eth->parent.netif, igmp_mac_filter); #endif /* LWIP_IPV4 && LWIP_IGMP */ return RT_EOK; } static rt_err_t rt_nuc472_emac_open(rt_device_t dev, rt_uint16_t oflag) { return RT_EOK; } static rt_err_t rt_nuc472_emac_close(rt_device_t dev) { return RT_EOK; } static rt_size_t rt_nuc472_emac_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size) { rt_set_errno(-RT_ENOSYS); return 0; } static rt_size_t rt_nuc472_emac_write (rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size) { rt_set_errno(-RT_ENOSYS); return 0; } static rt_err_t rt_nuc472_emac_control(rt_device_t dev, int cmd, void *args) { rt_nuc472_emac_t emac = (rt_nuc472_emac_t)dev; switch(cmd) { case NIOCTL_GADDR: /* get mac address */ if(args) rt_memcpy(args, emac->dev_addr, 6); else return -RT_ERROR; break; default : break; } return RT_EOK; } rt_err_t rt_nuc472_emac_tx(rt_device_t dev, struct pbuf* p) { rt_nuc472_emac_t emac = (rt_nuc472_emac_t)dev; struct pbuf* q; rt_uint32_t offset; rt_uint8_t *buf = emac_get_tx_buf(emac); /* get free tx buffer */ if(buf == RT_NULL) { rt_kprintf("none tx buf\n"); return -RT_ERROR; } offset = 0; for (q = p; q != NULL; q = q->next) { rt_uint8_t* ptr; rt_uint32_t len; len = q->len; ptr = q->payload; /* Copy the frame to be sent into memory pointed by the current ETHERNET DMA Tx descriptor */ while (len) { buf[offset] = *ptr; offset ++; ptr ++; len --; } } ETH_trigger_tx(emac, offset); /* Return SUCCESS */ return RT_EOK; } struct pbuf *rt_nuc472_emac_rx(rt_device_t dev) { rt_nuc472_emac_t emac = (rt_nuc472_emac_t)dev; unsigned int status; struct pbuf* p; /* init p pointer */ p = RT_NULL; status = emac->cur_rx_desc_ptr->status1; if(status & OWNERSHIP_EMAC) { goto end; } if (status & RXFD_RXGD) { //ethernetif_input(status & 0xFFFF, cur_rx_desc_ptr->buf, cur_rx_desc_ptr->status2, (u32_t)cur_rx_desc_ptr->next); /* allocate buffer */ p = pbuf_alloc(PBUF_LINK, status & 0xFFFF, PBUF_RAM); if (p != RT_NULL) { const char * from; struct pbuf* q; from = (const char *)(emac->cur_rx_desc_ptr->buf); for (q = p; q != RT_NULL; q = q->next) { /* Copy the received frame into buffer from memory pointed by the current ETHERNET DMA Rx descriptor */ memcpy(q->payload, from, q->len); from += q->len; } } } emac->cur_rx_desc_ptr->status1 = OWNERSHIP_EMAC; emac->cur_rx_desc_ptr = emac->cur_rx_desc_ptr->next; return p; end: ETH_TRIGGER_RX(); return p; } static void rt_hw_nuc472_emac_register(rt_nuc472_emac_t emac, char *dev_name, EMAC_T *emac_base, rt_uint8_t *mac_addr) { emac->emac_base = emac_base; rt_memcpy(emac->dev_addr, mac_addr, sizeof(emac->dev_addr)); emac->parent.parent.init = rt_nuc472_emac_init; emac->parent.parent.open = rt_nuc472_emac_open; emac->parent.parent.close = rt_nuc472_emac_close; emac->parent.parent.read = rt_nuc472_emac_read; emac->parent.parent.write = rt_nuc472_emac_write; emac->parent.parent.control = rt_nuc472_emac_control; emac->parent.parent.user_data = RT_NULL; emac->parent.eth_rx = rt_nuc472_emac_rx; emac->parent.eth_tx = rt_nuc472_emac_tx; /* register eth device */ eth_device_init(&(emac->parent), dev_name); } void rt_hw_nuc472_emac_init(void) { rt_hw_nuc472_emac_register(&nuc472_emac_device, "eh0", EMAC, ethaddr); } #endif