/* * Copyright (c) 2018 Nuvoton Technology Corp. * Copyright (c) 2018 ARM Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Description: NUC472 MAC driver source file */ #include <stdbool.h> #include "nuc472_eth.h" #include "mbed_toolchain.h" //#define NU_TRACE #include "numaker_eth_hal.h" #define ETH_TRIGGER_RX() do{EMAC->RXST = 0;}while(0) #define ETH_TRIGGER_TX() do{EMAC->TXST = 0;}while(0) #define ETH_ENABLE_TX() do{EMAC->CTL |= EMAC_CTL_TXON;}while(0) #define ETH_ENABLE_RX() do{EMAC->CTL |= EMAC_CTL_RXON_Msk;}while(0) #define ETH_DISABLE_TX() do{EMAC->CTL &= ~EMAC_CTL_TXON;}while(0) #define ETH_DISABLE_RX() do{EMAC->CTL &= ~EMAC_CTL_RXON_Msk;}while(0) #define EMAC_ENABLE_INT(emac, u32eIntSel) ((emac)->INTEN |= (u32eIntSel)) #define EMAC_DISABLE_INT(emac, u32eIntSel) ((emac)->INTEN &= ~ (u32eIntSel)) MBED_ALIGN(4) struct eth_descriptor rx_desc[RX_DESCRIPTOR_NUM]; MBED_ALIGN(4) struct eth_descriptor tx_desc[TX_DESCRIPTOR_NUM]; struct eth_descriptor volatile *cur_tx_desc_ptr, *cur_rx_desc_ptr, *fin_tx_desc_ptr; __attribute__((section("EMAC_RAM"))) MBED_ALIGN(4) uint8_t rx_buf[RX_DESCRIPTOR_NUM][PACKET_BUFFER_SIZE]; __attribute__((section("EMAC_RAM"))) MBED_ALIGN(4) uint8_t tx_buf[TX_DESCRIPTOR_NUM][PACKET_BUFFER_SIZE]; eth_callback_t nu_eth_txrx_cb = NULL; void *nu_userData = NULL; extern void ack_emac_rx_isr(void); static bool isPhyReset = false; static uint16_t phyLPAval = 0; // PTP source clock is 84MHz (Real chip using PLL). Each tick is 11.90ns // Assume we want to set each tick to 100ns. // Increase register = (100 * 2^31) / (10^9) = 214.71 =~ 215 = 0xD7 // Addend register = 2^32 * tick_freq / (84MHz), where tick_freq = (2^31 / 215) MHz // From above equation, addend register = 2^63 / (84M * 215) ~= 510707200 = 0x1E70C600 static void mdio_write(uint8_t addr, uint8_t reg, uint16_t val) { EMAC->MIIMDAT = val; EMAC->MIIMCTL = (addr << EMAC_MIIMCTL_PHYADDR_Pos) | reg | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_WRITE_Msk | EMAC_MIIMCTL_MDCON_Msk; while (EMAC->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk); } static uint16_t mdio_read(uint8_t addr, uint8_t reg) { EMAC->MIIMCTL = (addr << EMAC_MIIMCTL_PHYADDR_Pos) | reg | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_MDCON_Msk; while (EMAC->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk); return (EMAC->MIIMDAT); } static int reset_phy(void) { uint16_t reg; uint32_t delayCnt; mdio_write(CONFIG_PHY_ADDR, MII_BMCR, BMCR_RESET); delayCnt = 2000; while (delayCnt > 0) { delayCnt--; if ((mdio_read(CONFIG_PHY_ADDR, MII_BMCR) & BMCR_RESET) == 0) { break; } } if (delayCnt == 0) { NU_DEBUGF(("Reset phy failed\n")); return (-1); } mdio_write(CONFIG_PHY_ADDR, MII_ADVERTISE, ADVERTISE_CSMA | ADVERTISE_10HALF | ADVERTISE_10FULL | ADVERTISE_100HALF | ADVERTISE_100FULL); reg = mdio_read(CONFIG_PHY_ADDR, MII_BMCR); mdio_write(CONFIG_PHY_ADDR, MII_BMCR, reg | BMCR_ANRESTART); delayCnt = 200000; while (delayCnt > 0) { delayCnt--; if ((mdio_read(CONFIG_PHY_ADDR, MII_BMSR) & (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)) == (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)) { break; } } if (delayCnt == 0) { NU_DEBUGF(("AN failed. Set to 100 FULL\n")); EMAC->CTL |= (EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); return (-1); } else { reg = mdio_read(CONFIG_PHY_ADDR, MII_LPA); phyLPAval = reg; if (reg & ADVERTISE_100FULL) { NU_DEBUGF(("100 full\n")); EMAC->CTL |= (EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); } else if (reg & ADVERTISE_100HALF) { NU_DEBUGF(("100 half\n")); EMAC->CTL = (EMAC->CTL & ~EMAC_CTL_FUDUP_Msk) | EMAC_CTL_OPMODE_Msk; } else if (reg & ADVERTISE_10FULL) { NU_DEBUGF(("10 full\n")); EMAC->CTL = (EMAC->CTL & ~EMAC_CTL_OPMODE_Msk) | EMAC_CTL_FUDUP_Msk; } else { NU_DEBUGF(("10 half\n")); EMAC->CTL &= ~(EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); } } printf("PHY ID 1:0x%x\r\n", mdio_read(CONFIG_PHY_ADDR, MII_PHYSID1)); printf("PHY ID 2:0x%x\r\n", mdio_read(CONFIG_PHY_ADDR, MII_PHYSID2)); return (0); } static void init_tx_desc(void) { uint32_t i; cur_tx_desc_ptr = fin_tx_desc_ptr = &tx_desc[0]; for (i = 0; i < TX_DESCRIPTOR_NUM; i++) { tx_desc[i].status1 = TXFD_PADEN | TXFD_CRCAPP | TXFD_INTEN; tx_desc[i].buf = &tx_buf[i][0]; tx_desc[i].status2 = 0; tx_desc[i].next = &tx_desc[(i + 1) % TX_DESCRIPTOR_NUM]; } EMAC->TXDSA = (unsigned int)&tx_desc[0]; return; } static void init_rx_desc(void) { uint32_t i; cur_rx_desc_ptr = &rx_desc[0]; for (i = 0; i < RX_DESCRIPTOR_NUM; i++) { rx_desc[i].status1 = OWNERSHIP_EMAC; rx_desc[i].buf = &rx_buf[i][0]; rx_desc[i].status2 = 0; rx_desc[i].next = &rx_desc[(i + 1) % (RX_DESCRIPTOR_NUM)]; } EMAC->RXDSA = (unsigned int)&rx_desc[0]; return; } void numaker_set_mac_addr(uint8_t *addr) { EMAC->CAM0M = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3]; EMAC->CAM0L = (addr[4] << 24) | (addr[5] << 16); EMAC->CAMCTL = EMAC_CAMCTL_CMPEN_Msk | EMAC_CAMCTL_AMP_Msk | EMAC_CAMCTL_ABP_Msk; EMAC->CAMEN = 1; // Enable CAM entry 0 } static void __eth_clk_pin_init() { /* Unlock protected registers */ SYS_UnlockReg(); /* Enable IP clock */ 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)); /* Update System Core Clock */ SystemCoreClockUpdate(); /*---------------------------------------------------------------------------------------------------------*/ /* Init I/O Multi-function */ /*---------------------------------------------------------------------------------------------------------*/ // Configure RMII pins SYS->GPC_MFPL &= ~(SYS_GPC_MFPL_PC0MFP_Msk | SYS_GPC_MFPL_PC1MFP_Msk | SYS_GPC_MFPL_PC2MFP_Msk | SYS_GPC_MFPL_PC3MFP_Msk | SYS_GPC_MFPL_PC4MFP_Msk | SYS_GPC_MFPL_PC6MFP_Msk | SYS_GPC_MFPL_PC7MFP_Msk); 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_Msk; 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_Msk | SYS_GPB_MFPH_PB15MFP_Msk); SYS->GPB_MFPH |= SYS_GPB_MFPH_PB14MFP_EMAC_MII_MDC | SYS_GPB_MFPH_PB15MFP_EMAC_MII_MDIO; /* Lock protected registers */ SYS_LockReg(); } void numaker_eth_init(uint8_t *mac_addr) { // init CLK & pins __eth_clk_pin_init(); // Reset MAC EMAC->CTL = EMAC_CTL_RST_Msk; while (EMAC->CTL & EMAC_CTL_RST_Msk) {} init_tx_desc(); init_rx_desc(); numaker_set_mac_addr(mac_addr); // need to reconfigure hardware address 'cos we just RESET emc... EMAC->CTL |= EMAC_CTL_STRIPCRC_Msk | EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk | EMAC_CTL_RMIIEN_Msk | EMAC_CTL_RMIIRXCTL_Msk; EMAC->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; /* Limit the max receive frame length to 1514 + 4 */ EMAC->MRFL = NU_ETH_MAX_FLEN; /* Set RX FIFO threshold as 8 words */ if (isPhyReset != true) { if (!reset_phy()) { isPhyReset = true; } } else { if (phyLPAval & ADVERTISE_100FULL) { NU_DEBUGF(("100 full\n")); EMAC->CTL |= (EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); } else if (phyLPAval & ADVERTISE_100HALF) { NU_DEBUGF(("100 half\n")); EMAC->CTL = (EMAC->CTL & ~EMAC_CTL_FUDUP_Msk) | EMAC_CTL_OPMODE_Msk; } else if (phyLPAval & ADVERTISE_10FULL) { NU_DEBUGF(("10 full\n")); EMAC->CTL = (EMAC->CTL & ~EMAC_CTL_OPMODE_Msk) | EMAC_CTL_FUDUP_Msk; } else { NU_DEBUGF(("10 half\n")); EMAC->CTL &= ~(EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk); } } EMAC_ENABLE_RX(); EMAC_ENABLE_TX(); } void ETH_halt(void) { EMAC->CTL &= ~(EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk); } unsigned int m_status; void EMAC_RX_IRQHandler(void) { m_status = EMAC->INTSTS & 0xFFFF; EMAC->INTSTS = m_status; if (m_status & EMAC_INTSTS_RXBEIF_Msk) { // Shouldn't goes here, unless descriptor corrupted mbed_error_printf("### RX Bus error [0x%x]\r\n", m_status); if (nu_eth_txrx_cb != NULL) { nu_eth_txrx_cb('B', nu_userData); } return; } EMAC_DISABLE_INT(EMAC, (EMAC_INTEN_RDUIEN_Msk | EMAC_INTEN_RXGDIEN_Msk)); if (nu_eth_txrx_cb != NULL) { nu_eth_txrx_cb('R', nu_userData); } } void numaker_eth_trigger_rx(void) { EMAC_ENABLE_INT(EMAC, (EMAC_INTEN_RDUIEN_Msk | EMAC_INTEN_RXGDIEN_Msk)); ETH_TRIGGER_RX(); } int numaker_eth_get_rx_buf(uint16_t *len, uint8_t **buf) { unsigned int cur_entry, status; cur_entry = EMAC->CRXDSA; if ((cur_entry == (uint32_t)cur_rx_desc_ptr) && (!(m_status & EMAC_INTSTS_RDUIF_Msk))) { // cur_entry may equal to cur_rx_desc_ptr if RDU occures return -1; } status = cur_rx_desc_ptr->status1; if (status & OWNERSHIP_EMAC) { return -1; } if (status & RXFD_RXGD) { *buf = cur_rx_desc_ptr->buf; *len = status & 0xFFFF; // length of payload should be <= 1514 if (*len > (NU_ETH_MAX_FLEN - 4)) { NU_DEBUGF(("%s... unexpected long packet length=%d, buf=0x%x\r\n", __FUNCTION__, *len, *buf)); *len = 0; // Skip this unexpected long packet } if (*len == (NU_ETH_MAX_FLEN - 4)) { NU_DEBUGF(("%s... length=%d, buf=0x%x\r\n", __FUNCTION__, *len, *buf)); } } return 0; } void numaker_eth_rx_next(void) { cur_rx_desc_ptr->status1 = OWNERSHIP_EMAC; cur_rx_desc_ptr = cur_rx_desc_ptr->next; } void EMAC_TX_IRQHandler(void) { unsigned int cur_entry, status; status = EMAC->INTSTS & 0xFFFF0000; EMAC->INTSTS = status; if (status & EMAC_INTSTS_TXBEIF_Msk) { // Shouldn't goes here, unless descriptor corrupted mbed_error_printf("### TX Bus error [0x%x]\r\n", status); if (nu_eth_txrx_cb != NULL) { nu_eth_txrx_cb('B', nu_userData); } return; } cur_entry = EMAC->CTXDSA; while (cur_entry != (uint32_t)fin_tx_desc_ptr) { fin_tx_desc_ptr = fin_tx_desc_ptr->next; } if (nu_eth_txrx_cb != NULL) { nu_eth_txrx_cb('T', nu_userData); } } uint8_t *numaker_eth_get_tx_buf(void) { if (cur_tx_desc_ptr->status1 & OWNERSHIP_EMAC) { return (NULL); } else { return (cur_tx_desc_ptr->buf); } } void numaker_eth_trigger_tx(uint16_t length, void *p) { struct eth_descriptor volatile *desc; cur_tx_desc_ptr->status2 = (unsigned int)length; desc = cur_tx_desc_ptr->next; // in case TX is transmitting and overwrite next pointer before we can update cur_tx_desc_ptr cur_tx_desc_ptr->status1 |= OWNERSHIP_EMAC; cur_tx_desc_ptr = desc; ETH_TRIGGER_TX(); } int numaker_eth_link_ok(void) { /* first, a dummy read to latch */ mdio_read(CONFIG_PHY_ADDR, MII_BMSR); if (mdio_read(CONFIG_PHY_ADDR, MII_BMSR) & BMSR_LSTATUS) { return 1; } return 0; } void numaker_eth_set_cb(eth_callback_t eth_cb, void *userData) { nu_eth_txrx_cb = eth_cb; nu_userData = userData; } // Override mbed_mac_address of mbed_interface.c to provide ethernet devices with a semi-unique MAC address void mbed_mac_address(char *mac) { uint32_t uID1; // Fetch word 0 uint32_t word0 = *(uint32_t *)0x7F804; // 2KB Data Flash at 0x7F800 // Fetch word 1 // we only want bottom 16 bits of word1 (MAC bits 32-47) // and bit 9 forced to 1, bit 8 forced to 0 // Locally administered MAC, reduced conflicts // http://en.wikipedia.org/wiki/MAC_address uint32_t word1 = *(uint32_t *)0x7F800; // 2KB Data Flash at 0x7F800 if (word0 == 0xFFFFFFFF) { // Not burn any mac address at 1st 2 words of Data Flash // with a semi-unique MAC address from the UUID /* Enable FMC ISP function */ SYS_UnlockReg(); FMC_Open(); // = FMC_ReadUID(0); uID1 = FMC_ReadUID(1); word1 = (uID1 & 0x003FFFFF) | ((uID1 & 0x030000) << 6) >> 8; word0 = ((FMC_ReadUID(0) >> 4) << 20) | ((uID1 & 0xFF) << 12) | (FMC_ReadUID(2) & 0xFFF); /* Disable FMC ISP function */ FMC_Close(); /* Lock protected registers */ SYS_LockReg(); } word1 |= 0x00000200; word1 &= 0x0000FEFF; mac[0] = (word1 & 0x0000ff00) >> 8; mac[1] = (word1 & 0x000000ff); mac[2] = (word0 & 0xff000000) >> 24; mac[3] = (word0 & 0x00ff0000) >> 16; mac[4] = (word0 & 0x0000ff00) >> 8; mac[5] = (word0 & 0x000000ff); NU_DEBUGF(("mac address %02x-%02x-%02x-%02x-%02x-%02x \r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])); } void numaker_eth_enable_interrupts(void) { EMAC->INTEN |= EMAC_INTEN_RXIEN_Msk | EMAC_INTEN_TXIEN_Msk ; NVIC_EnableIRQ(EMAC_RX_IRQn); NVIC_EnableIRQ(EMAC_TX_IRQn); } void numaker_eth_disable_interrupts(void) { NVIC_DisableIRQ(EMAC_RX_IRQn); NVIC_DisableIRQ(EMAC_TX_IRQn); }