/**************************************************************************//** * @copyright (C) 2019 Nuvoton Technology Corp. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of Nuvoton Technology Corp. nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #include "FreeRTOS.h" #include "list.h" #include "FreeRTOS_IP.h" #include "m480_eth.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; } while( 0 ) #define ETH_DISABLE_TX() do { EMAC->CTL &= ~EMAC_CTL_TXON; } while( 0 ) #define ETH_DISABLE_RX() do { EMAC->CTL &= ~EMAC_CTL_RXON; } while( 0 ) struct eth_descriptor rx_desc[ RX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) ); struct eth_descriptor tx_desc[ TX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) ); #ifdef __ICCARM__ #pragma data_alignment=4 struct eth_descriptor rx_desc[ RX_DESCRIPTOR_NUM ]; struct eth_descriptor tx_desc[ TX_DESCRIPTOR_NUM ]; uint8_t rx_buf[ RX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ]; uint8_t tx_buf[ TX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ]; #else struct eth_descriptor rx_desc[ RX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) ); struct eth_descriptor tx_desc[ TX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) ); uint8_t rx_buf[ RX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ] __attribute__( ( aligned( 4 ) ) ); uint8_t tx_buf[ TX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ] __attribute__( ( aligned( 4 ) ) ); #endif struct eth_descriptor volatile * cur_tx_desc_ptr, * cur_rx_desc_ptr, * fin_tx_desc_ptr; /* 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 ) { 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 ) { 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 ); 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 ); } } FreeRTOS_printf( ( "PHY ID 1:0x%x\r\n", mdio_read( CONFIG_PHY_ADDR, MII_PHYSID1 ) ) ); FreeRTOS_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 ]; } 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 ) % TX_DESCRIPTOR_NUM ]; } EMAC->RXDSA = ( unsigned int ) &rx_desc[ 0 ]; } 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 ); } 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) = 1.25 MHz if system is running at 160 MH */ CLK_SetModuleClock( EMAC_MODULE, 0, CLK_CLKDIV3_EMAC( 127 ) ); /* Update System Core Clock */ SystemCoreClockUpdate(); /*---------------------------------------------------------------------------------------------------------*/ /* Init I/O Multi-function */ /*---------------------------------------------------------------------------------------------------------*/ /* Configure RMII pins */ SYS->GPA_MFPL &= ~( SYS_GPA_MFPL_PA6MFP_Msk | SYS_GPA_MFPL_PA7MFP_Msk ); SYS->GPA_MFPL |= SYS_GPA_MFPL_PA6MFP_EMAC_RMII_RXERR | SYS_GPA_MFPL_PA7MFP_EMAC_RMII_CRSDV; SYS->GPC_MFPL &= ~( SYS_GPC_MFPL_PC6MFP_Msk | SYS_GPC_MFPL_PC7MFP_Msk ); SYS->GPC_MFPL |= SYS_GPC_MFPL_PC6MFP_EMAC_RMII_RXD1 | SYS_GPC_MFPL_PC7MFP_EMAC_RMII_RXD0; SYS->GPC_MFPH &= ~SYS_GPC_MFPH_PC8MFP_Msk; SYS->GPC_MFPH |= SYS_GPC_MFPH_PC8MFP_EMAC_RMII_REFCLK; SYS->GPE_MFPH &= ~( SYS_GPE_MFPH_PE8MFP_Msk | SYS_GPE_MFPH_PE9MFP_Msk | SYS_GPE_MFPH_PE10MFP_Msk | SYS_GPE_MFPH_PE11MFP_Msk | SYS_GPE_MFPH_PE12MFP_Msk ); SYS->GPE_MFPH |= SYS_GPE_MFPH_PE8MFP_EMAC_RMII_MDC | SYS_GPE_MFPH_PE9MFP_EMAC_RMII_MDIO | SYS_GPE_MFPH_PE10MFP_EMAC_RMII_TXD0 | SYS_GPE_MFPH_PE11MFP_EMAC_RMII_TXD1 | SYS_GPE_MFPH_PE12MFP_EMAC_RMII_TXEN; /* Enable high slew rate on all RMII TX output pins */ PE->SLEWCTL = ( GPIO_SLEWCTL_HIGH << GPIO_SLEWCTL_HSREN10_Pos ) | ( GPIO_SLEWCTL_HIGH << GPIO_SLEWCTL_HSREN11_Pos ) | ( GPIO_SLEWCTL_HIGH << GPIO_SLEWCTL_HSREN12_Pos ); /* Lock protected registers */ SYS_LockReg(); } int numaker_eth_init( uint8_t * mac_addr ) { int ret = 0; /* 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 because we just RESET EMAC... */ /* Configure the MAC interrupt enable register. */ EMAC->INTEN = EMAC_INTEN_RXIEN_Msk | EMAC_INTEN_TXIEN_Msk | EMAC_INTEN_RXGDIEN_Msk | EMAC_INTEN_TXCPIEN_Msk | EMAC_INTEN_RXBEIEN_Msk | EMAC_INTEN_TXBEIEN_Msk | EMAC_INTEN_RDUIEN_Msk | EMAC_INTEN_TSALMIEN_Msk | EMAC_INTEN_WOLIEN_Msk; /* Configure the MAC control register. */ EMAC->CTL = EMAC_CTL_STRIPCRC_Msk | EMAC_CTL_RMIIEN_Msk; /* Accept packets for us and all broadcast and multicast packets */ EMAC->CAMCTL = EMAC_CAMCTL_CMPEN_Msk | EMAC_CAMCTL_AMP_Msk | EMAC_CAMCTL_ABP_Msk; EMAC->CAMEN = 1; /* Enable CAM entry 0 */ ret = reset_phy(); EMAC_ENABLE_RX(); EMAC_ENABLE_TX(); return ret; } void ETH_halt( void ) { EMAC->CTL &= ~( EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk ); } unsigned int m_status; void EMAC_RX_IRQHandler( void ) { /* NU_DEBUGF(("%s ... \r\n", __FUNCTION__)); */ m_status = EMAC->INTSTS & 0xFFFF; EMAC->INTSTS = m_status; if( m_status & EMAC_INTSTS_RXBEIF_Msk ) { /* Shouldn't goes here, unless descriptor corrupted */ NU_DEBUGF( ( "RX descriptor corrupted \r\n" ) ); /*return; */ } /* FIX ME: for rx-event, to ack rx_isr into event queue */ xNetworkCallback( 'R' ); } void numaker_eth_trigger_rx( void ) { 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 occurred */ { 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; } 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 */ return; } cur_entry = EMAC->CTXDSA; while( cur_entry != ( uint32_t ) fin_tx_desc_ptr ) { fin_tx_desc_ptr = fin_tx_desc_ptr->next; } /* FIX ME: for tx-event, no-op at this stage */ xNetworkCallback( 'T' ); } 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; */ /*} */ /* Provide ethernet devices with a semi-unique MAC address */ void numaker_mac_address( uint8_t * 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 ); }