/* * File : drv_emac.c * COPYRIGHT (C) 2006 - 2017, RT-Thread Development Team * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Change Logs: * Date Author Notes * 2017-08-08 Yang the first version */ #include #include "lwipopts.h" #include #include #include "drv_emac.h" #include "fsl_iocon.h" #include "fsl_sctimer.h" #include "fsl_phy.h" #define IOCON_PIO_DIGITAL_EN 0x0100u /*!< Enables digital function */ #define IOCON_PIO_FUNC0 0x00u /*!< Selects pin function 0 */ #define IOCON_PIO_FUNC1 0x01u /*!< Selects pin function 1 */ #define IOCON_PIO_FUNC7 0x07u /*!< Selects pin function 7 */ #define IOCON_PIO_INPFILT_OFF 0x0200u /*!< Input filter disabled */ #define IOCON_PIO_INV_DI 0x00u /*!< Input function is not inverted */ #define IOCON_PIO_MODE_INACT 0x00u /*!< No addition pin function */ #define IOCON_PIO_MODE_PULLUP 0x20u /*!< Selects pull-up function */ #define IOCON_PIO_OPENDRAIN_DI 0x00u /*!< Open drain is disabled */ #define IOCON_PIO_SLEW_FAST 0x0400u /*!< Fast mode, slew rate control is disabled */ #define IOCON_PIO_SLEW_STANDARD 0x00u /*!< Standard mode, output slew rate control is enabled */ #define PIN8_IDX 8u /*!< Pin number for pin 8 in a port 4 */ #define PIN10_IDX 10u /*!< Pin number for pin 10 in a port 4 */ #define PIN11_IDX 11u /*!< Pin number for pin 11 in a port 4 */ #define PIN12_IDX 12u /*!< Pin number for pin 12 in a port 4 */ #define PIN13_IDX 13u /*!< Pin number for pin 13 in a port 4 */ #define PIN14_IDX 14u /*!< Pin number for pin 14 in a port 4 */ #define PIN15_IDX 15u /*!< Pin number for pin 15 in a port 4 */ #define PIN16_IDX 16u /*!< Pin number for pin 16 in a port 4 */ #define PIN17_IDX 17u /*!< Pin number for pin 17 in a port 0 */ #define PIN26_IDX 26u /*!< Pin number for pin 26 in a port 2 */ #define PIN29_IDX 29u /*!< Pin number for pin 29 in a port 0 */ #define PIN30_IDX 30u /*!< Pin number for pin 30 in a port 0 */ #define PORT0_IDX 0u /*!< Port index */ #define PORT2_IDX 2u /*!< Port index */ #define PORT4_IDX 4u /*!< Port index */ #define EMAC_PHY_AUTO 0 #define EMAC_PHY_10MBIT 1 #define EMAC_PHY_100MBIT 2 #define MAX_ADDR_LEN 6 /* EMAC_RAM_BASE is defined in board.h and the size is 16KB */ #define RX_DESC_BASE ETH_RAM_BASE #define RX_STAT_BASE (RX_DESC_BASE + NUM_RX_FRAG*8) #define TX_DESC_BASE (RX_STAT_BASE + NUM_RX_FRAG*8) #define TX_STAT_BASE (TX_DESC_BASE + NUM_TX_FRAG*8) #define RX_BUF_BASE (TX_STAT_BASE + NUM_TX_FRAG*4) #define TX_BUF_BASE (RX_BUF_BASE + NUM_RX_FRAG*ETH_FRAG_SIZE) /* RX and TX descriptor and status definitions. */ #define RX_DESC_PACKET(i) (*(unsigned int *)(RX_DESC_BASE + 8*i)) #define RX_DESC_CTRL(i) (*(unsigned int *)(RX_DESC_BASE+4 + 8*i)) #define RX_STAT_INFO(i) (*(unsigned int *)(RX_STAT_BASE + 8*i)) #define RX_STAT_HASHCRC(i) (*(unsigned int *)(RX_STAT_BASE+4 + 8*i)) #define TX_DESC_PACKET(i) (*(unsigned int *)(TX_DESC_BASE + 8*i)) #define TX_DESC_CTRL(i) (*(unsigned int *)(TX_DESC_BASE+4 + 8*i)) #define TX_STAT_INFO(i) (*(unsigned int *)(TX_STAT_BASE + 4*i)) #define RX_BUF(i) (RX_BUF_BASE + ETH_FRAG_SIZE*i) #define TX_BUF(i) (TX_BUF_BASE + ETH_FRAG_SIZE*i) struct lpc_emac { /* inherit from ethernet device */ struct eth_device parent; rt_uint8_t phy_mode; /* interface address info. */ rt_uint8_t dev_addr[MAX_ADDR_LEN]; /* hw address */ }; static struct lpc_emac lpc_emac_device; static struct rt_semaphore sem_lock; static struct rt_event tx_event; #if defined(__GNUC__) #ifndef __ALIGN_END #define __ALIGN_END __attribute__((aligned(ENET_BUFF_ALIGNMENT))) #endif #ifndef __ALIGN_BEGIN #define __ALIGN_BEGIN #endif #else #ifndef __ALIGN_END #define __ALIGN_END #endif #ifndef __ALIGN_BEGIN #if defined(__CC_ARM) #define __ALIGN_BEGIN __align(ENET_BUFF_ALIGNMENT) #elif defined(__ICCARM__) #define __ALIGN_BEGIN #endif #endif #endif #define ENET_RXBD_NUM (4) #define ENET_TXBD_NUM (4) #define PHY_ADDR (0x00U) #define ENET_LOOP_COUNT (20U) #define ENET_ALIGN(x, align) ((unsigned int)((x) + ((align)-1)) & (unsigned int)(~(unsigned int)((align)-1))) #define ENET_BuffSizeAlign(n) ENET_ALIGN(n, ENET_BUFF_ALIGNMENT) __ALIGN_BEGIN enet_rx_bd_struct_t g_rxBuffDescrip[ENET_RXBD_NUM] __ALIGN_END; __ALIGN_BEGIN enet_tx_bd_struct_t g_txBuffDescrip[ENET_TXBD_NUM] __ALIGN_END; uint8_t multicastAddr[6] = {0x01, 0x00, 0x5e, 0x00, 0x01, 0x81}; uint8_t *g_txbuff[ENET_TXBD_NUM]; uint32_t g_txIdx = 0; uint8_t g_txbuffIdx = 0; uint8_t g_txGenIdx = 0; uint8_t g_txCosumIdx = 0; uint8_t g_txUsed = 0; uint8_t g_rxGenIdx = 0; uint32_t g_rxCosumIdx = 0; uint32_t g_rxbuffer[ENET_RXBD_NUM]; static uint8_t *ENET_RXRead(int32_t *length) { uint32_t control; uint8_t *data; *length = 0; /* Get the Frame size */ control = ENET_GetRxDescriptor(&g_rxBuffDescrip[g_rxGenIdx]); if (!(control & ENET_RXDESCRIP_RD_OWN_MASK)) { if (control & ENET_RXDESCRIP_WR_LD_MASK) { /* if no error */ if (control & ENET_RXDESCRIP_WR_ERRSUM_MASK) { *length = -1; } else { *length = control & ENET_RXDESCRIP_WR_PACKETLEN_MASK; data = (uint8_t *)g_rxbuffer[g_rxGenIdx]; } g_rxGenIdx = (g_rxGenIdx + 1) % ENET_RXBD_NUM; } } return data; } static void ENET_RXClaim(uint8_t* buffer) { if (ENET_GetDmaInterruptStatus(ENET, 0) & kENET_DmaRxBuffUnavail) { ENET_UpdateRxDescriptor(&g_rxBuffDescrip[g_rxCosumIdx], buffer, NULL, true, false); /* Command for rx poll when the dma suspend. */ ENET_UpdateRxDescriptorTail(ENET, 0, (uint32_t)&g_rxBuffDescrip[ENET_RXBD_NUM]); } else { ENET_UpdateRxDescriptor(&g_rxBuffDescrip[g_rxCosumIdx], buffer, NULL, true, false); } if (buffer) { g_rxbuffer[g_rxCosumIdx] = (uint32_t)buffer; } g_rxCosumIdx = (g_rxCosumIdx + 1) % ENET_RXBD_NUM; } static status_t ENET_TXQueue(uint8_t *data, uint16_t length) { uint32_t txdescTailAddr; /* Fill the descriptor. */ if (ENET_IsTxDescriptorDmaOwn(&g_txBuffDescrip[g_txGenIdx])) { return kStatus_Fail; } ENET_SetupTxDescriptor(&g_txBuffDescrip[g_txGenIdx], data, length, NULL, 0, length, true, false, kENET_FirstLastFlag, 0); /* Increase the index. */ g_txGenIdx = (g_txGenIdx + 1) % ENET_TXBD_NUM; g_txUsed++; /* Update the transmit tail address. */ txdescTailAddr = (uint32_t)&g_txBuffDescrip[g_txGenIdx]; if (!g_txGenIdx) { txdescTailAddr = (uint32_t)&g_txBuffDescrip[ENET_TXBD_NUM]; } ENET_UpdateTxDescriptorTail(ENET, 0, txdescTailAddr); return kStatus_Success; } static void ENET_TXReclaim() { if (!ENET_IsTxDescriptorDmaOwn(&g_txBuffDescrip[g_txCosumIdx]) && (g_txUsed > 0)) { /* Free tx buffers. */ free(g_txbuff[g_txCosumIdx]); g_txUsed--; g_txCosumIdx = (g_txCosumIdx + 1) % ENET_TXBD_NUM; } } void ETHERNET_IRQHandler(void) { /* Check for the interrupt source type. */ /* DMA CHANNEL 0. */ uint32_t status; /* enter interrupt */ rt_interrupt_enter(); status = ENET_GetDmaInterruptStatus(ENET, 0); if (status) { if (status & kENET_DmaRx) { /* a frame has been received */ eth_device_ready(&(lpc_emac_device.parent)); } if (status & kENET_DmaTx) { /* set event */ rt_event_send(&tx_event, 0x01); } /* Clear the interrupt. */ ENET_ClearDmaInterruptStatus(ENET, 0, status); } /* leave interrupt */ rt_interrupt_leave(); } static rt_err_t lpc_emac_init(rt_device_t dev) { int32_t status, index; void *buff; phy_speed_t speed; phy_duplex_t duplex; enet_config_t config; bool link = false; uint32_t timedelay; uint32_t refClock = 50000000; /* 50MHZ for rmii reference clock. */ for (index = 0; index < ENET_RXBD_NUM; index++) { /* This is for rx buffers, static alloc and dynamic alloc both ok. use as your wish. */ buff = rt_malloc(ENET_FRAME_MAX_FRAMELEN); if (buff) { g_rxbuffer[index] = (uint32_t)buff; } else { rt_kprintf("Mem Alloc fail\r\n"); } } /* prepare the buffer configuration. */ enet_buffer_config_t buffConfig = { ENET_RXBD_NUM, ENET_TXBD_NUM, &g_txBuffDescrip[0], &g_txBuffDescrip[0], &g_rxBuffDescrip[0], &g_rxBuffDescrip[4], &g_rxbuffer[0], ENET_BuffSizeAlign(ENET_FRAME_MAX_FRAMELEN), }; { const uint32_t port0_pin17_config = ( IOCON_PIO_FUNC7 | /* Pin is configured as ENET_TXD1 */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT0_IDX, PIN17_IDX, port0_pin17_config); /* PORT0 PIN17 (coords: E14) is configured as ENET_TXD1 */ const uint32_t port2_pin26_config = ( IOCON_PIO_FUNC0 | /* Pin is configured as PIO2_26 */ IOCON_PIO_MODE_PULLUP | /* Selects pull-up function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT2_IDX, PIN26_IDX, port2_pin26_config); /* PORT2 PIN26 (coords: H11) is configured as PIO2_26 */ const uint32_t port4_pin10_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_RX_DV */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN10_IDX, port4_pin10_config); /* PORT4 PIN10 (coords: B9) is configured as ENET_RX_DV */ const uint32_t port4_pin11_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_RXD0 */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN11_IDX, port4_pin11_config); /* PORT4 PIN11 (coords: A9) is configured as ENET_RXD0 */ const uint32_t port4_pin12_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_RXD1 */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN12_IDX, port4_pin12_config); /* PORT4 PIN12 (coords: A6) is configured as ENET_RXD1 */ const uint32_t port4_pin13_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_TX_EN */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN13_IDX, port4_pin13_config); /* PORT4 PIN13 (coords: B6) is configured as ENET_TX_EN */ const uint32_t port4_pin14_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_RX_CLK */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN14_IDX, port4_pin14_config); /* PORT4 PIN14 (coords: B5) is configured as ENET_RX_CLK */ const uint32_t port4_pin15_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_MDC */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN15_IDX, port4_pin15_config); /* PORT4 PIN15 (coords: A4) is configured as ENET_MDC */ const uint32_t port4_pin16_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_MDIO */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN16_IDX, port4_pin16_config); /* PORT4 PIN16 (coords: C4) is configured as ENET_MDIO */ const uint32_t port4_pin8_config = ( IOCON_PIO_FUNC1 | /* Pin is configured as ENET_TXD0 */ IOCON_PIO_MODE_INACT | /* No addition pin function */ IOCON_PIO_INV_DI | /* Input function is not inverted */ IOCON_PIO_DIGITAL_EN | /* Enables digital function */ IOCON_PIO_INPFILT_OFF | /* Input filter disabled */ IOCON_PIO_SLEW_STANDARD | /* Standard mode, output slew rate control is enabled */ IOCON_PIO_OPENDRAIN_DI /* Open drain is disabled */ ); IOCON_PinMuxSet(IOCON, PORT4_IDX, PIN8_IDX, port4_pin8_config); /* PORT4 PIN8 (coords: B14) is configured as ENET_TXD0 */ } status = PHY_Init(ENET, PHY_ADDR, 0); if (status == kStatus_Success) { PHY_GetLinkSpeedDuplex(ENET, PHY_ADDR, &speed, &duplex); /* Use the actual speed and duplex when phy success to finish the autonegotiation. */ config.miiSpeed = (enet_mii_speed_t)speed; config.miiDuplex = (enet_mii_duplex_t)duplex; } else { rt_kprintf("PHY_Init failed!\n"); return RT_ERROR; } /* Wait for link up and get the actual PHY link speed. */ PHY_GetLinkStatus(ENET, PHY_ADDR, &link); while (!link) { rt_kprintf("PHY Wait for link up!\n"); for (timedelay = 0; timedelay < 0xFFFFFU; timedelay++) { __ASM("nop"); } PHY_GetLinkStatus(ENET, PHY_ADDR, &link); } /* Get default configuration 100M RMII. */ ENET_GetDefaultConfig(&config); /* Initialize ENET. */ ENET_Init(ENET, &config, &lpc_emac_device.dev_addr[0], refClock); /* Enable the tx/rx interrupt. */ ENET_EnableInterrupts(ENET, (kENET_DmaTx | kENET_DmaRx)); EnableIRQ(ETHERNET_IRQn); /* Initialize Descriptor. */ ENET_DescriptorInit(ENET, &config, &buffConfig); /* Active TX/RX. */ ENET_StartRxTx(ENET, 1, 1); return RT_EOK; } static rt_err_t lpc_emac_open(rt_device_t dev, rt_uint16_t oflag) { return RT_EOK; } static rt_err_t lpc_emac_close(rt_device_t dev) { return RT_EOK; } static rt_size_t lpc_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 lpc_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 lpc_emac_control(rt_device_t dev, rt_uint8_t cmd, void *args) { switch (cmd) { case NIOCTL_GADDR: /* get mac address */ if (args) rt_memcpy(args, lpc_emac_device.dev_addr, 6); else return -RT_ERROR; break; default : break; } return RT_EOK; } /* EtherNet Device Interface */ /* transmit packet. */ rt_err_t lpc_emac_tx(rt_device_t dev, struct pbuf *p) { rt_uint8_t *buffer; rt_err_t result; rt_uint32_t recved; /* lock EMAC device */ rt_sem_take(&sem_lock, RT_WAITING_FOREVER); /* there is no block yet, wait a flag */ result = rt_event_recv(&tx_event, 0x01, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_NO, &recved); if (result == RT_EOK) ENET_TXReclaim(); /* Create the buffer for zero-copy transmit. */ buffer = (uint8_t *)malloc(p->tot_len); if (buffer) { /* copy data to tx buffer */ pbuf_copy_partial(p, buffer, p->tot_len, 0); while ((g_txbuffIdx + 1) % ENET_TXBD_NUM == g_txCosumIdx) { rt_thread_delay(RT_TICK_PER_SECOND / 20); // 50 ms } /* Store the buffer for mem free. */ g_txbuff[g_txbuffIdx] = buffer; g_txbuffIdx = (g_txbuffIdx + 1) % ENET_TXBD_NUM; /* Send the frame out (wait unitl the descriptor ready). */ while (ENET_TXQueue(buffer, p->tot_len) != kStatus_Success); } /* unlock EMAC device */ rt_sem_release(&sem_lock); return RT_EOK; } /* reception packet. */ struct pbuf *lpc_emac_rx(rt_device_t dev) { struct pbuf *p; uint8_t *data; int length; /* init p pointer */ p = RT_NULL; /* lock EMAC device */ rt_sem_take(&sem_lock, RT_WAITING_FOREVER); length = 0; data = ENET_RXRead(&length); if (length > 0) { void *buffer; /* Update the buffers and then we can delivery the previous buffer diectly to the application without memcpy. */ buffer = rt_malloc(ENET_FRAME_MAX_FRAMELEN); if (buffer) { ENET_RXClaim(buffer); } else { ENET_RXClaim(NULL); } /* Do what you want to do with the data and then free the used one. */ p = pbuf_alloc(PBUF_LINK, length, PBUF_RAM); if (p != RT_NULL) { rt_memcpy(p->payload, data, length); p->tot_len = length; } rt_free(data); } else if (length < 0) { ENET_RXClaim(NULL); } /* unlock EMAC device */ rt_sem_release(&sem_lock); return p; } int lpc_emac_hw_init(void) { rt_event_init(&tx_event, "tx_event", RT_IPC_FLAG_FIFO); rt_sem_init(&sem_lock, "eth_lock", 1, RT_IPC_FLAG_FIFO); /* set autonegotiation mode */ lpc_emac_device.phy_mode = EMAC_PHY_AUTO; // OUI 00-60-37 NXP Semiconductors lpc_emac_device.dev_addr[0] = 0x00; lpc_emac_device.dev_addr[1] = 0x60; lpc_emac_device.dev_addr[2] = 0x37; /* set mac address: (only for test) */ lpc_emac_device.dev_addr[3] = 0x12; lpc_emac_device.dev_addr[4] = 0x34; lpc_emac_device.dev_addr[5] = 0x56; lpc_emac_device.parent.parent.init = lpc_emac_init; lpc_emac_device.parent.parent.open = lpc_emac_open; lpc_emac_device.parent.parent.close = lpc_emac_close; lpc_emac_device.parent.parent.read = lpc_emac_read; lpc_emac_device.parent.parent.write = lpc_emac_write; lpc_emac_device.parent.parent.control = lpc_emac_control; lpc_emac_device.parent.parent.user_data = RT_NULL; lpc_emac_device.parent.eth_rx = lpc_emac_rx; lpc_emac_device.parent.eth_tx = lpc_emac_tx; eth_device_init(&(lpc_emac_device.parent), "e0"); return 0; } INIT_DEVICE_EXPORT(lpc_emac_hw_init);