diff --git a/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/TARGET_NUC472/nuc472_eth.c b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/TARGET_NUC472/nuc472_eth.c new file mode 100644 index 0000000000..1fff61de08 --- /dev/null +++ b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/TARGET_NUC472/nuc472_eth.c @@ -0,0 +1,434 @@ +/* + * 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 "nuc472_eth.h" +#include "mbed_toolchain.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) + +#define NU_DEBUGF printf + +/* +#ifdef __ICCARM__ +#pragma data_alignment=4 +struct eth_descriptor rx_desc[RX_DESCRIPTOR_NUM]; +struct eth_descriptor tx_desc[TX_DESCRIPTOR_NUM]; +#else +struct eth_descriptor rx_desc[RX_DESCRIPTOR_NUM] __attribute__ ((aligned(4))); +struct eth_descriptor tx_desc[TX_DESCRIPTOR_NUM] __attribute__ ((aligned(4))); +#endif +*/ +struct eth_descriptor rx_desc[RX_DESCRIPTOR_NUM] MBED_ALIGN(4); +struct eth_descriptor tx_desc[TX_DESCRIPTOR_NUM] MBED_ALIGN(4); + +struct eth_descriptor volatile *cur_tx_desc_ptr, *cur_rx_desc_ptr, *fin_tx_desc_ptr; + +uint8_t rx_buf[RX_DESCRIPTOR_NUM][PACKET_BUFFER_SIZE] MBED_ALIGN(4); +uint8_t tx_buf[TX_DESCRIPTOR_NUM][PACKET_BUFFER_SIZE] MBED_ALIGN(4); + +typedef void (* eth_callback_t) (char, void*); +eth_callback_t nu_eth_txrx_cb = NULL; +void *nu_userData = NULL; + +//extern void ethernetif_input(uint16_t len, uint8_t *buf, uint32_t s, uint32_t ns); +extern void ack_emac_rx_isr(void); + +// 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 delay; + + + mdio_write(CONFIG_PHY_ADDR, MII_BMCR, BMCR_RESET); + + delay = 2000; + while(delay-- > 0) { + if((mdio_read(CONFIG_PHY_ADDR, MII_BMCR) & BMCR_RESET) == 0) + break; + + } + + if(delay == 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); + + delay = 200000; + while(delay-- > 0) { + if((mdio_read(CONFIG_PHY_ADDR, MII_BMSR) & (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)) + == (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)) + break; + } + + if(delay == 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); + } + } + + 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) % TX_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() +{ + /* 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)); + /*---------------------------------------------------------------------------------------------------------*/ + /* 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; + +} + +void numaker_eth_init(uint8_t *mac_addr) +{ + // init CLK & pins + __eth_clk_pin_init(); + + // Reset MAC + 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... + reset_phy(); + + 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; + EMAC->RXST = 0; // trigger Rx +} + + + +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 + NU_DEBUGF("RX descriptor corrupted \r\n"); + //return; + } + if (nu_eth_txrx_cb != NULL) nu_eth_txrx_cb('R', nu_userData); //ack_emac_rx_isr(); +} + +void EMAC_RX_Action(void) +{ + unsigned int cur_entry, status; + do { + + 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 + break; + status = cur_rx_desc_ptr->status1; + + if(status & OWNERSHIP_EMAC) + break; + + if (status & RXFD_RXGD) { + // Lwip will invoke osMutexWait for resource protection, so ethernetif_input can't be called in EMAC_RX_IRQHandler. + //ethernetif_input(status & 0xFFFF, cur_rx_desc_ptr->buf, cur_rx_desc_ptr->status2, (uint32_t)cur_rx_desc_ptr->next); + + } + + cur_rx_desc_ptr->status1 = OWNERSHIP_EMAC; + cur_rx_desc_ptr = cur_rx_desc_ptr->next; + + } while (1); + + ETH_TRIGGER_RX(); +// eth_arch_tcpip_thread(); +} + +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 occures + return -1; + status = cur_rx_desc_ptr->status1; + + if(status & OWNERSHIP_EMAC) + return -1; + + if (status & RXFD_RXGD) { + cur_rx_desc_ptr->status1 = OWNERSHIP_EMAC; + cur_rx_desc_ptr = cur_rx_desc_ptr->next; + } + cur_rx_desc_ptr->status1 = OWNERSHIP_EMAC; + cur_rx_desc_ptr = cur_rx_desc_ptr->next; + return 0; +} + +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; + } + + 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); + + printf("mac address %02x-%02x-%02x-%02x-%02x-%02x \r\n", mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]); +} \ No newline at end of file diff --git a/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/TARGET_NUC472/nuc472_eth.h b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/TARGET_NUC472/nuc472_eth.h new file mode 100644 index 0000000000..dc440335a7 --- /dev/null +++ b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/TARGET_NUC472/nuc472_eth.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018 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 EMAC driver header file + */ +#include "NUC472_442.h" +#include "numaker_emac_config.h" +#ifndef _NUC472_ETH_ +#define _NUC472_ETH_ + +/* Generic MII registers. */ + +#define MII_BMCR 0x00 /* Basic mode control register */ +#define MII_BMSR 0x01 /* Basic mode status register */ +#define MII_PHYSID1 0x02 /* PHYS ID 1 */ +#define MII_PHYSID2 0x03 /* PHYS ID 2 */ +#define MII_ADVERTISE 0x04 /* Advertisement control reg */ +#define MII_LPA 0x05 /* Link partner ability reg */ +#define MII_EXPANSION 0x06 /* Expansion register */ +#define MII_DCOUNTER 0x12 /* Disconnect counter */ +#define MII_FCSCOUNTER 0x13 /* False carrier counter */ +#define MII_NWAYTEST 0x14 /* N-way auto-neg test reg */ +#define MII_RERRCOUNTER 0x15 /* Receive error counter */ +#define MII_SREVISION 0x16 /* Silicon revision */ +#define MII_RESV1 0x17 /* Reserved... */ +#define MII_LBRERROR 0x18 /* Lpback, rx, bypass error */ +#define MII_PHYADDR 0x19 /* PHY address */ +#define MII_RESV2 0x1a /* Reserved... */ +#define MII_TPISTATUS 0x1b /* TPI status for 10mbps */ +#define MII_NCONFIG 0x1c /* Network interface config */ + +/* Basic mode control register. */ +#define BMCR_RESV 0x007f /* Unused... */ +#define BMCR_CTST 0x0080 /* Collision test */ +#define BMCR_FULLDPLX 0x0100 /* Full duplex */ +#define BMCR_ANRESTART 0x0200 /* Auto negotiation restart */ +#define BMCR_ISOLATE 0x0400 /* Disconnect DP83840 from MII */ +#define BMCR_PDOWN 0x0800 /* Powerdown the DP83840 */ +#define BMCR_ANENABLE 0x1000 /* Enable auto negotiation */ +#define BMCR_SPEED100 0x2000 /* Select 100Mbps */ +#define BMCR_LOOPBACK 0x4000 /* TXD loopback bits */ +#define BMCR_RESET 0x8000 /* Reset the DP83840 */ + +/* Basic mode status register. */ +#define BMSR_ERCAP 0x0001 /* Ext-reg capability */ +#define BMSR_JCD 0x0002 /* Jabber detected */ +#define BMSR_LSTATUS 0x0004 /* Link status */ +#define BMSR_ANEGCAPABLE 0x0008 /* Able to do auto-negotiation */ +#define BMSR_RFAULT 0x0010 /* Remote fault detected */ +#define BMSR_ANEGCOMPLETE 0x0020 /* Auto-negotiation complete */ +#define BMSR_RESV 0x07c0 /* Unused... */ +#define BMSR_10HALF 0x0800 /* Can do 10mbps, half-duplex */ +#define BMSR_10FULL 0x1000 /* Can do 10mbps, full-duplex */ +#define BMSR_100HALF 0x2000 /* Can do 100mbps, half-duplex */ +#define BMSR_100FULL 0x4000 /* Can do 100mbps, full-duplex */ +#define BMSR_100BASE4 0x8000 /* Can do 100mbps, 4k packets */ + +/* Advertisement control register. */ +#define ADVERTISE_SLCT 0x001f /* Selector bits */ +#define ADVERTISE_CSMA 0x0001 /* Only selector supported */ +#define ADVERTISE_10HALF 0x0020 /* Try for 10mbps half-duplex */ +#define ADVERTISE_10FULL 0x0040 /* Try for 10mbps full-duplex */ +#define ADVERTISE_100HALF 0x0080 /* Try for 100mbps half-duplex */ +#define ADVERTISE_100FULL 0x0100 /* Try for 100mbps full-duplex */ +#define ADVERTISE_100BASE4 0x0200 /* Try for 100mbps 4k packets */ +#define ADVERTISE_RESV 0x1c00 /* Unused... */ +#define ADVERTISE_RFAULT 0x2000 /* Say we can detect faults */ +#define ADVERTISE_LPACK 0x4000 /* Ack link partners response */ +#define ADVERTISE_NPAGE 0x8000 /* Next page bit */ + +#define RX_DESCRIPTOR_NUM NU_RX_RING_LEN//4 //2 // 4: Max Number of Rx Frame Descriptors +#define TX_DESCRIPTOR_NUM NU_TX_RING_LEN//4 //2 // 4: Max number of Tx Frame Descriptors + +#define PACKET_BUFFER_SIZE NU_ETH_MAX_FLEN//1520 + +#define CONFIG_PHY_ADDR 1 + + +// Frame Descriptor's Owner bit +#define OWNERSHIP_EMAC 0x80000000 // 1 = EMAC +//#define OWNERSHIP_CPU 0x7fffffff // 0 = CPU + + + +// Rx Frame Descriptor Status +#define RXFD_RXGD 0x00100000 // Receiving Good Packet Received +#define RXFD_RTSAS 0x00800000 // RX Time Stamp Available + + +// Tx Frame Descriptor's Control bits +#define TXFD_TTSEN 0x08 // Tx Time Stamp Enable +#define TXFD_INTEN 0x04 // Interrupt Enable +#define TXFD_CRCAPP 0x02 // Append CRC +#define TXFD_PADEN 0x01 // Padding Enable + +// Tx Frame Descriptor Status +#define TXFD_TXCP 0x00080000 // Transmission Completion +#define TXFD_TTSAS 0x08000000 // TX Time Stamp Available + +// Tx/Rx buffer descriptor structure +struct eth_descriptor; +struct eth_descriptor { + uint32_t status1; + uint8_t *buf; + uint32_t status2; + struct eth_descriptor *next; +#ifdef TIME_STAMPING + uint32_t backup1; + uint32_t backup2; + uint32_t reserved1; + uint32_t reserved2; +#endif +}; + +#ifdef TIME_STAMPING + +#define ETH_TS_ENABLE() do{EMAC->TSCTL = EMAC_TSCTL_TSEN_Msk;}while(0) +#define ETH_TS_START() do{EMAC->TSCTL |= (EMAC_TSCTL_TSMODE_Msk | EMAC_TSCTL_TSIEN_Msk);}while(0) +s32_t ETH_settime(u32_t sec, u32_t nsec); +s32_t ETH_gettime(u32_t *sec, u32_t *nsec); +s32_t ETH_updatetime(u32_t neg, u32_t sec, u32_t nsec); +s32_t ETH_adjtimex(int ppm); +void ETH_setinc(void); + +#endif + +extern void numaker_eth_init(uint8_t *mac_addr); +extern uint8_t *numaker_eth_get_tx_buf(void); +extern void numaker_eth_trigger_tx(uint16_t length, void *p); + +#endif /* _NUC472_ETH_ */ diff --git a/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac.cpp b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac.cpp new file mode 100644 index 0000000000..88960b9a38 --- /dev/null +++ b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2018 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: NuMaker EMAC + */ + +#include +#include +#include +#include + +#include "cmsis_os.h" + +#include "mbed_interface.h" +#include "mbed_assert.h" +#include "netsocket/nsapi_types.h" +#include "mbed_shared_queues.h" + + +#include "numaker_emac_config.h" +#include "numaker_emac.h" + + +/******************************************************************************** + * + ********************************************************************************/ +#define NU_BUFF_ALIGNMENT 4 +#define PHY_LINKED_STATE 1 +#define PHY_UNLINKED_STATE 0 +#define PACKET_BUFFER_SIZE NU_ETH_MAX_FLEN + +typedef void (* eth_callback_t) (char, void*); +extern "C" void numaker_init_eth_hardware(void); +extern "C" void mbed_mac_address(char *mac); +extern "C" void numaker_eth_init(uint8_t *mac_addr); +extern "C" void numaker_eth_trigger_rx(void); +extern "C" int numaker_eth_get_rx_buf(uint16_t *len, uint8_t **buf); +extern "C" uint8_t *numaker_eth_get_tx_buf(void); +extern "C" void numaker_eth_trigger_tx(uint16_t length, void *p); +extern "C" int numaker_eth_link_ok(void); +extern "C" void numaker_eth_set_cb(eth_callback_t eth_cb, void *userData); +extern "C" void numaker_set_mac_addr(uint8_t *addr); + +/* \brief Flags for worker thread */ +#define FLAG_TX 1 +#define FLAG_RX 2 + +/** \brief Driver thread priority */ +#define THREAD_PRIORITY (osPriorityNormal) + +#define PHY_TASK_PERIOD_MS 200 + +NUMAKER_EMAC::NUMAKER_EMAC() : thread(0), hwaddr() +{ +} + +static osThreadId_t create_new_thread(const char *threadName, void (*thread)(void *arg), void *arg, int stacksize, osPriority_t priority, os_thread_t *thread_cb) +{ + osThreadAttr_t attr = {0}; + attr.name = threadName; + attr.stack_mem = malloc(stacksize); + attr.cb_mem = thread_cb; + attr.stack_size = stacksize; + attr.cb_size = sizeof(os_thread_t); + attr.priority = priority; + return osThreadNew(thread, arg, &attr); +} + + +/** \brief Ethernet receive interrupt handler + * + * This function handles the receive interrupt. + */ +void NUMAKER_EMAC::rx_isr() +{ + if (thread) { + osThreadFlagsSet(thread, FLAG_RX); + } +} + +void NUMAKER_EMAC::tx_isr() +{ + /* No-op at this stage */ +} + +void NUMAKER_EMAC::ethernet_callback(char event, void *param) +{ + NUMAKER_EMAC *enet = static_cast(param); + switch (event) + { + case 'R': //For RX event + enet->rx_isr(); + break; + case 'T': //For TX event + enet->tx_isr(); + break; + default: + break; + } +} + + +/** + * In this function, the hardware should be initialized. + */ +bool NUMAKER_EMAC::low_level_init_successful() +{ + /* Init ETH */ + + mbed_mac_address((char *)hwaddr); + + /* Enable clock & set EMAC configuration */ + /* Enable MAC and DMA transmission and reception */ + numaker_eth_init(hwaddr); + + numaker_eth_set_cb(&NUMAKER_EMAC::ethernet_callback, this); + + return true; +} + + +int NUMAKER_EMAC::low_level_input(emac_mem_buf_t **buf) +{ + uint16_t len = 0; + uint8_t *buffer; + uint32_t bufferoffset = 0; + uint32_t byteslefttocopy = 0; + emac_mem_buf_t *q; + uint32_t payloadoffset = 0; + + /* get received frame */ + if ( !numaker_eth_get_rx_buf(&len, &buffer)) { + return -1; + } + + byteslefttocopy = len; + + if (len > 0) { + /* Allocate a memory buffer chain from buffer pool */ + *buf = memory_manager->alloc_pool(len, 0); + } + + if (*buf != NULL) { + bufferoffset = 0; + for (q = *buf; q != NULL; q = memory_manager->get_next(q)) { + byteslefttocopy = memory_manager->get_len(q); + payloadoffset = 0; + + /* Copy data in pbuf */ + memcpy(static_cast(memory_manager->get_ptr(q)) + payloadoffset, static_cast(buffer) + bufferoffset, byteslefttocopy); + bufferoffset = bufferoffset + byteslefttocopy; + } + } + + return 0; +} + + +/** \brief Worker thread. + * + * Woken by thread flags to receive packets or clean up transmit + * + * \param[in] pvParameters pointer to the interface data + */ + +void NUMAKER_EMAC::thread_function(void* pvParameters) +{ + static struct NUMAKER_EMAC *nu_enet = static_cast(pvParameters); + + for (;;) { + uint32_t flags = osThreadFlagsWait(FLAG_RX, osFlagsWaitAny, osWaitForever); + + if (flags & FLAG_RX) { + nu_enet->packet_rx(); + } + } +} + +/** \brief Packet reception task + * + * This task is called when a packet is received. It will + * pass the packet to the LWIP core. + */ +void NUMAKER_EMAC::packet_rx() +{ + /* move received packet into a new buf */ + while (1) { + emac_mem_buf_t *p = NULL; + if (low_level_input(&p) < 0) { + break; + } + if (p) { + emac_link_input_cb(p); + } + } + numaker_eth_trigger_rx(); + +} + + +/** + * This function should do the actual transmission of the packet. The packet is + * contained in the buf that is passed to the function. This buf + * might be chained. + * + * @param buf the MAC packet to send (e.g. IP packet including MAC addresses and type) + * @return true if the packet could be sent + * false value if the packet couldn't be sent + * + * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to + * strange results. You might consider waiting for space in the DMA queue + * to become available since the stack doesn't retry to send a packet + * dropped because of memory failure (except for the TCP timers). + */ +bool NUMAKER_EMAC::link_out(emac_mem_buf_t *buf) +{ + bool result; + emac_mem_buf_t *q; + uint8_t *buffer = numaker_eth_get_tx_buf(); + uint32_t framelength = 0; + uint32_t bufferoffset = 0; + uint32_t byteslefttocopy = 0; + uint32_t payloadoffset = 0; + + /* Get exclusive access */ + TXLockMutex.lock(); + + /* copy frame from buf to driver buffers */ + for (q = buf; q != NULL; q = memory_manager->get_next(q)) { + + /* Get bytes in current lwIP buffer */ + byteslefttocopy = memory_manager->get_len(q); + payloadoffset = 0; + + /* Check if the length of data to copy is bigger than Tx buffer size*/ + while ((byteslefttocopy + bufferoffset) > PACKET_BUFFER_SIZE) { + /* Copy data to Tx buffer*/ + memcpy(static_cast(buffer) + bufferoffset, static_cast(memory_manager->get_ptr(q)) + payloadoffset, (PACKET_BUFFER_SIZE - bufferoffset)); + + /* Point to next descriptor */ + numaker_eth_trigger_tx(PACKET_BUFFER_SIZE, NULL); + buffer = numaker_eth_get_tx_buf(); + if( buffer == NULL ) goto error; + + byteslefttocopy = byteslefttocopy - (PACKET_BUFFER_SIZE - bufferoffset); + payloadoffset = payloadoffset + (PACKET_BUFFER_SIZE - bufferoffset); + framelength = framelength + (PACKET_BUFFER_SIZE - bufferoffset); + bufferoffset = 0; + } + + /* Copy the remaining bytes */ + memcpy(static_cast(buffer) + bufferoffset, static_cast(memory_manager->get_ptr(q)) + payloadoffset, byteslefttocopy); + bufferoffset = bufferoffset + byteslefttocopy; + framelength = framelength + byteslefttocopy; + } + + /* Prepare transmit descriptors to give to DMA */ + numaker_eth_trigger_tx(byteslefttocopy, NULL); + + result = true; + +error: + + memory_manager->free(buf); + + /* Restore access */ + TXLockMutex.unlock(); + + return result; +} + + +void NUMAKER_EMAC::phy_task() +{ + + // Get current status + int state; + state = numaker_eth_link_ok(); + + + if ((state & PHY_LINKED_STATE) && !(phy_state & PHY_LINKED_STATE)) { + printf("Link Up\r\n"); + if (emac_link_state_cb) emac_link_state_cb(true); + } else if (!(state & PHY_LINKED_STATE) && (phy_state & PHY_LINKED_STATE)) { + printf("Link Down\r\n"); + if (emac_link_state_cb) emac_link_state_cb(false); + } + phy_state = state; + +} + +bool NUMAKER_EMAC::power_up() +{ + /* Initialize the hardware */ + if (!low_level_init_successful()) + return false; + + /* Worker thread */ + thread = create_new_thread("numaker_emac_thread", &NUMAKER_EMAC::thread_function, this, THREAD_STACKSIZE, THREAD_PRIORITY, &thread_cb); + + /* PHY monitoring task */ + phy_state = PHY_UNLINKED_STATE; + + phy_task_handle = mbed::mbed_event_queue()->call_every(PHY_TASK_PERIOD_MS, mbed::callback(this, &NUMAKER_EMAC::phy_task)); + + /* Allow the PHY task to detect the initial link state and set up the proper flags */ + osDelay(10); + + return true; +} + + +uint32_t NUMAKER_EMAC::get_mtu_size() const +{ + return NU_ETH_MTU_SIZE; +} + +uint32_t NUMAKER_EMAC::get_align_preference() const +{ + return NU_BUFF_ALIGNMENT; +} + +void NUMAKER_EMAC::get_ifname(char *name, uint8_t size) const +{ + memcpy(name, NU_ETH_IF_NAME, (size < sizeof(NU_ETH_IF_NAME)) ? size : sizeof(NU_ETH_IF_NAME)); +} + +uint8_t NUMAKER_EMAC::get_hwaddr_size() const +{ + return NU_HWADDR_SIZE; +} + +bool NUMAKER_EMAC::get_hwaddr(uint8_t *addr) const +{ + mbed_mac_address((char *)addr); + return true; +} + +void NUMAKER_EMAC::set_hwaddr(const uint8_t *addr) +{ + memcpy(hwaddr, addr, sizeof hwaddr); + numaker_set_mac_addr(const_cast(addr)); +} + +void NUMAKER_EMAC::set_link_input_cb(emac_link_input_cb_t input_cb) +{ + emac_link_input_cb = input_cb; +} + +void NUMAKER_EMAC::set_link_state_cb(emac_link_state_change_cb_t state_cb) +{ + emac_link_state_cb = state_cb; +} + +void NUMAKER_EMAC::add_multicast_group(const uint8_t *addr) +{ + /* No-op at this stage */ +} + +void NUMAKER_EMAC::remove_multicast_group(const uint8_t *addr) +{ + /* No-op at this stage */ +} + +void NUMAKER_EMAC::set_all_multicast(bool all) +{ + /* No-op at this stage */ +} + + +void NUMAKER_EMAC::power_down() +{ + /* No-op at this stage */ +} + +void NUMAKER_EMAC::set_memory_manager(EMACMemoryManager &mem_mngr) +{ + memory_manager = &mem_mngr; +} + + +NUMAKER_EMAC &NUMAKER_EMAC::get_instance() { + static NUMAKER_EMAC emac; + return emac; +} + +// Weak so a module can override +MBED_WEAK EMAC &EMAC::get_default_instance() { + return NUMAKER_EMAC::get_instance(); +} + + + + diff --git a/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac.h b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac.h new file mode 100644 index 0000000000..057c67681f --- /dev/null +++ b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017 ARM Limited. All rights reserved. + */ + +#ifndef NUMAKER_EMAC_H_ +#define NUMAKER_EMAC_H_ + +#include "EMAC.h" +#include "rtos/Semaphore.h" +#include "rtos/Mutex.h" + +class NUMAKER_EMAC : public EMAC { +public: + NUMAKER_EMAC(); + + static NUMAKER_EMAC &get_instance(); + + /** + * Return maximum transmission unit + * + * @return MTU in bytes + */ + virtual uint32_t get_mtu_size() const; + + /** + * Gets memory buffer alignment preference + * + * Gets preferred memory buffer alignment of the Emac device. IP stack may or may not + * align link out memory buffer chains using the alignment. + * + * @return Memory alignment requirement in bytes + */ + virtual uint32_t get_align_preference() const; + + /** + * Return interface name + * + * @param name Pointer to where the name should be written + * @param size Maximum number of character to copy + */ + virtual void get_ifname(char *name, uint8_t size) const; + + /** + * Returns size of the underlying interface HW address size. + * + * @return HW address size in bytes + */ + virtual uint8_t get_hwaddr_size() const; + + /** + * Return interface-supplied HW address + * + * Copies HW address to provided memory, @param addr has to be of correct size see @a get_hwaddr_size + * + * HW address need not be provided if this interface does not have its own HW + * address configuration; stack will choose address from central system + * configuration if the function returns false and does not write to addr. + * + * @param addr HW address for underlying interface + * @return true if HW address is available + */ + virtual bool get_hwaddr(uint8_t *addr) const; + + /** + * Set HW address for interface + * + * Provided address has to be of correct size, see @a get_hwaddr_size + * + * Called to set the MAC address to actually use - if @a get_hwaddr is provided + * the stack would normally use that, but it could be overridden, eg for test + * purposes. + * + * @param addr Address to be set + */ + virtual void set_hwaddr(const uint8_t *addr); + + /** + * Sends the packet over the link + * + * That can not be called from an interrupt context. + * + * @param buf Packet to be send + * @return True if the packet was send successfully, False otherwise + */ + virtual bool link_out(emac_mem_buf_t *buf); + + /** + * Initializes the HW + * + * @return True on success, False in case of an error. + */ + virtual bool power_up(); + + /** + * Deinitializes the HW + * + */ + virtual void power_down(); + + /** + * Sets a callback that needs to be called for packets received for that interface + * + * @param input_cb Function to be register as a callback + */ + virtual void set_link_input_cb(emac_link_input_cb_t input_cb); + + /** + * Sets a callback that needs to be called on link status changes for given interface + * + * @param state_cb Function to be register as a callback + */ + virtual void set_link_state_cb(emac_link_state_change_cb_t state_cb); + + /** Add device to a multicast group + * + * @param address A multicast group hardware address + */ + virtual void add_multicast_group(const uint8_t *address); + + /** Remove device from a multicast group + * + * @param address A multicast group hardware address + */ + virtual void remove_multicast_group(const uint8_t *address); + + /** Request reception of all multicast packets + * + * @param all True to receive all multicasts + * False to receive only multicasts addressed to specified groups + */ + virtual void set_all_multicast(bool all); + + /** Sets memory manager that is used to handle memory buffers + * + * @param mem_mngr Pointer to memory manager + */ + virtual void set_memory_manager(EMACMemoryManager &mem_mngr); + +private: + bool low_level_init_successful(); + void tx_isr(); + void rx_isr(); + void packet_rx(); + int low_level_input(emac_mem_buf_t **buf); + static void thread_function(void* pvParameters); + void phy_task(); + static void ethernet_callback(char event, void *param); + + os_thread_t thread_cb; + osThreadId_t thread; /**< Processing thread */ + rtos::Mutex TXLockMutex;/**< TX critical section mutex */ + rtos::Semaphore xTXDCountSem; /**< TX free buffer counting semaphore */ + emac_link_input_cb_t emac_link_input_cb; /**< Callback for incoming data */ + emac_link_state_change_cb_t emac_link_state_cb; /**< Link state change callback */ + EMACMemoryManager *memory_manager; /**< Memory manager */ + int phy_task_handle; /**< Handle for phy task event */ + int phy_state; + uint8_t hwaddr[6]; +}; + +#endif /* NUMAKER_EMAC_H_ */ diff --git a/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac_config.h b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac_config.h new file mode 100644 index 0000000000..30b62f680f --- /dev/null +++ b/features/netsocket/emac-drivers/TARGET_NUVOTON_EMAC/numaker_emac_config.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 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: NuMaker EMAC Config header file + */ + +#ifndef NUMAKER_EMAC_CONFIG_H__ +#define NUMAKER_EMAC_CONFIG_H__ + +#define NU_RX_RING_LEN (8) +#define NU_TX_RING_LEN (4) + +#define NU_ETH_MAX_FLEN (1520) + +#define NU_HWADDR_SIZE (6) + +#define NU_ETH_MTU_SIZE 1500 +#define NU_ETH_IF_NAME "nu" + +#define THREAD_STACKSIZE 512 + +#endif // #define NUMAKER_EMAC_CONFIG_H__ + diff --git a/targets/targets.json b/targets/targets.json index d1311705bb..a25f4de6f1 100755 --- a/targets/targets.json +++ b/targets/targets.json @@ -3697,7 +3697,7 @@ "NUMAKER_PFM_NUC472": { "core": "Cortex-M4F", "default_toolchain": "ARM", - "extra_labels": ["NUVOTON", "NUC472", "NU_XRAM_SUPPORTED", "FLASH_CMSIS_ALGO"], + "extra_labels": ["NUVOTON", "NUC472", "NU_XRAM_SUPPORTED", "FLASH_CMSIS_ALGO", "NUVOTON_EMAC"], "is_disk_virtual": true, "supported_toolchains": ["ARM", "uARM", "GCC_ARM", "IAR"], "config": { @@ -3720,7 +3720,7 @@ }, "inherits": ["Target"], "macros_add": ["MBEDTLS_CONFIG_HW_SUPPORT"], - "device_has": ["ANALOGIN", "I2C", "I2CSLAVE", "I2C_ASYNCH", "INTERRUPTIN", "LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "SERIAL", "SERIAL_ASYNCH", "SERIAL_FC", "STDIO_MESSAGES", "SLEEP", "SPI", "SPISLAVE", "SPI_ASYNCH", "TRNG", "CAN", "FLASH"], + "device_has": ["ANALOGIN", "I2C", "I2CSLAVE", "I2C_ASYNCH", "INTERRUPTIN", "LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "SERIAL", "SERIAL_ASYNCH", "SERIAL_FC", "STDIO_MESSAGES", "SLEEP", "SPI", "SPISLAVE", "SPI_ASYNCH", "TRNG", "CAN", "FLASH", "EMAC"], "features": ["LWIP"], "release_versions": ["5"], "device_name": "NUC472HI8AE", @@ -3954,7 +3954,7 @@ "NUMAKER_PFM_M487": { "core": "Cortex-M4F", "default_toolchain": "ARM", - "extra_labels": ["NUVOTON", "M480", "FLASH_CMSIS_ALGO"], + "extra_labels": ["NUVOTON", "M480", "FLASH_CMSIS_ALGO","NUVOTON_EMAC"], "is_disk_virtual": true, "supported_toolchains": ["ARM", "uARM", "GCC_ARM", "IAR"], "config": { @@ -3985,7 +3985,7 @@ }, "inherits": ["Target"], "macros_add": ["MBEDTLS_CONFIG_HW_SUPPORT"], - "device_has": ["ANALOGIN", "I2C", "I2CSLAVE", "I2C_ASYNCH", "INTERRUPTIN", "LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "SERIAL", "SERIAL_ASYNCH", "SERIAL_FC", "STDIO_MESSAGES", "SLEEP", "SPI", "SPISLAVE", "SPI_ASYNCH", "TRNG", "FLASH", "CAN"], + "device_has": ["ANALOGIN", "I2C", "I2CSLAVE", "I2C_ASYNCH", "INTERRUPTIN", "LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "SERIAL", "SERIAL_ASYNCH", "SERIAL_FC", "STDIO_MESSAGES", "SLEEP", "SPI", "SPISLAVE", "SPI_ASYNCH", "TRNG", "FLASH", "CAN", "EMAC"], "features": ["LWIP"], "release_versions": ["5"], "device_name": "M487JIDAE",