/**************************************************************************** * arch/arm/src/lpc17xx/lpc17_ethernet.c * * Copyright (C) 2010-2012 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 NuttX 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 OWNER 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #if defined(CONFIG_NET) && defined(CONFIG_LPC17_ETHERNET) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chip.h" #include "up_arch.h" #include "lpc17_syscon.h" #include "lpc17_ethernet.h" #include "lpc17_emacram.h" #include "lpc17_internal.h" #include /* Does this chip have and ethernet controller? */ #if LPC17_NETHCONTROLLERS > 0 /**************************************************************************** * Definitions ****************************************************************************/ /* Configuration ************************************************************/ /* CONFIG_LPC17_NINTERFACES determines the number of physical interfaces * that will be supported -- unless it is more than actually supported by the * hardware! */ #if !defined(CONFIG_LPC17_NINTERFACES) || CONFIG_LPC17_NINTERFACES > LPC17_NETHCONTROLLERS # undef CONFIG_LPC17_NINTERFACES # define CONFIG_LPC17_NINTERFACES LPC17_NETHCONTROLLERS #endif /* The logic here has a few hooks for support for multiple interfaces, but * that capability is not yet in place (and I won't worry about it until I get * the first multi-interface LPC17xx). */ #if CONFIG_LPC17_NINTERFACES > 1 # warning "Only a single ethernet controller is supported" # undef CONFIG_LPC17_NINTERFACES # define CONFIG_LPC17_NINTERFACES 1 #endif /* If IGMP is enabled, then accept multi-cast frames. */ #if defined(CONFIG_NET_IGMP) && !defined(CONFIG_NET_MULTICAST) # define CONFIG_NET_MULTICAST 1 #endif /* If the user did not specify a priority for Ethernet interrupts, set the * interrupt priority to the maximum. */ #ifndef CONFIG_NET_PRIORITY # define CONFIG_NET_PRIORITY NVIC_SYSH_PRIORITY_MAX #endif /* Debug Configuration *****************************************************/ /* Register debug -- can only happen of CONFIG_DEBUG is selected */ #ifndef CONFIG_DEBUG # undef CONFIG_NET_REGDEBUG #endif /* CONFIG_NET_DUMPPACKET will dump the contents of each packet to the * console. */ #ifndef CONFIG_DEBUG # undef CONFIG_NET_DUMPPACKET #endif #ifdef CONFIG_NET_DUMPPACKET # define lpc17_dumppacket(m,a,n) lib_dumpbuffer(m,a,n) #else # define lpc17_dumppacket(m,a,n) #endif /* Timing *******************************************************************/ /* TX poll deley = 1 seconds. CLK_TCK is the number of clock ticks per second */ #define LPC17_WDDELAY (1*CLK_TCK) #define LPC17_POLLHSEC (1*2) /* TX timeout = 1 minute */ #define LPC17_TXTIMEOUT (60*CLK_TCK) /* Interrupts ***************************************************************/ #define ETH_RXINTS (ETH_INT_RXOVR | ETH_INT_RXERR | ETH_INT_RXFIN | ETH_INT_RXDONE) #define ETH_TXINTS (ETH_INT_TXUNR | ETH_INT_TXERR | ETH_INT_TXFIN | ETH_INT_TXDONE) /* Misc. Helpers ***********************************************************/ /* This is a helper pointer for accessing the contents of the Ethernet header */ #define BUF ((struct uip_eth_hdr *)priv->lp_dev.d_buf) /* This is the number of ethernet GPIO pins that must be configured */ #define GPIO_NENET_PINS 10 /* PHYs *********************************************************************/ /* Select PHY-specific values. Add more PHYs as needed. */ #if defined(CONFIG_PHY_KS8721) # define LPC17_PHYNAME "KS8721" # define LPC17_PHYID1 MII_PHYID1_KS8721 # define LPC17_PHYID2 MII_PHYID2_KS8721 # define LPC17_HAVE_PHY 1 #elif defined(CONFIG_PHY_DP83848C) # define LPC17_PHYNAME "DP83848C" # define LPC17_PHYID1 MII_PHYID1_DP83848C # define LPC17_PHYID2 MII_PHYID2_DP83848C # define LPC17_HAVE_PHY 1 #elif defined(CONFIG_PHY_LAN8720) # define LPC17_PHYNAME "LAN8720" # define LPC17_PHYID1 MII_PHYID1_LAN8720 # define LPC17_PHYID2 MII_PHYID2_LAN8720 # define LPC17_HAVE_PHY 1 #else # warning "No PHY specified!" # undef LPC17_HAVE_PHY #endif #define MII_BIG_TIMEOUT 666666 /* These definitions are used to remember the speed/duplex settings */ #define LPC17_SPEED_MASK 0x01 #define LPC17_SPEED_100 0x01 #define LPC17_SPEED_10 0x00 #define LPC17_DUPLEX_MASK 0x02 #define LPC17_DUPLEX_FULL 0x02 #define LPC17_DUPLEX_HALF 0x00 #define LPC17_10BASET_HD (LPC17_SPEED_10 | LPC17_DUPLEX_HALF) #define LPC17_10BASET_FD (LPC17_SPEED_10 | LPC17_DUPLEX_FULL) #define LPC17_100BASET_HD (LPC17_SPEED_100 | LPC17_DUPLEX_HALF) #define LPC17_100BASET_FD (LPC17_SPEED_100 | LPC17_DUPLEX_FULL) #ifdef CONFIG_PHY_SPEED100 # ifdef CONFIG_PHY_FDUPLEX # define LPC17_MODE_DEFLT LPC17_100BASET_FD # else # define LPC17_MODE_DEFLT LPC17_100BASET_HD # endif #else # ifdef CONFIG_PHY_FDUPLEX # define LPC17_MODE_DEFLT LPC17_10BASET_FD # else # define LPC17_MODE_DEFLT LPC17_10BASET_HD # endif #endif /**************************************************************************** * Private Types ****************************************************************************/ /* EMAC statistics (debug only) */ #if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_NET) struct lpc17_statistics_s { #ifdef ENABLE_WOL uint32_t wol; /* Wake-up interrupts */ #endif uint32_t rx_finished; /* Rx finished interrupts */ uint32_t rx_done; /* Rx done interrupts */ uint32_t rx_ovrerrors; /* Number of Rx overrun error interrupts */ uint32_t rx_errors; /* Number of Rx error interrupts (OR of other errors) */ uint32_t rx_packets; /* Number of packets received (sum of the following): */ uint32_t rx_ip; /* Number of Rx IP packets received */ uint32_t rx_arp; /* Number of Rx ARP packets received */ uint32_t rx_dropped; /* Number of dropped, unsupported Rx packets */ uint32_t rx_pkterr; /* Number of dropped, error in Rx descriptor */ uint32_t rx_pktsize; /* Number of dropped, too small or too big */ uint32_t rx_fragment; /* Number of dropped, packet fragments */ uint32_t tx_packets; /* Number of Tx packets queued */ uint32_t tx_pending; /* Number of Tx packets that had to wait for a TxDesc */ uint32_t tx_unpend; /* Number of pending Tx packets that were sent */ uint32_t tx_finished; /* Tx finished interrupts */ uint32_t tx_done; /* Tx done interrupts */ uint32_t tx_underrun; /* Number of Tx underrun error interrupts */ uint32_t tx_errors; /* Number of Tx error inerrupts (OR of other errors) */ uint32_t tx_timeouts; /* Number of Tx timeout errors */ }; # define EMAC_STAT(priv,name) priv->lp_stat.name++ #else # define EMAC_STAT(priv,name) #endif /* The lpc17_driver_s encapsulates all state information for a single hardware * interface */ struct lpc17_driver_s { /* The following fields would only be necessary on chips that support * multiple Ethernet controllers. */ #if CONFIG_LPC17_NINTERFACES > 1 uint32_t lp_base; /* Ethernet controller base address */ int lp_irq; /* Ethernet controller IRQ */ #endif bool lp_ifup; /* true:ifup false:ifdown */ bool lp_mode; /* speed/duplex */ bool lp_txpending; /* There is a pending Tx in lp_dev */ #ifdef LPC17_HAVE_PHY uint8_t lp_phyaddr; /* PHY device address */ #endif uint32_t lp_inten; /* Shadow copy of INTEN register */ WDOG_ID lp_txpoll; /* TX poll timer */ WDOG_ID lp_txtimeout; /* TX timeout timer */ #if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_NET) struct lpc17_statistics_s lp_stat; #endif /* This holds the information visible to uIP/NuttX */ struct uip_driver_s lp_dev; /* Interface understood by uIP */ }; /**************************************************************************** * Private Data ****************************************************************************/ /* Array of ethernet driver status structures */ static struct lpc17_driver_s g_ethdrvr[CONFIG_LPC17_NINTERFACES]; /* ENET pins are on P1[0,1,4,6,8,9,10,14,15] + MDC on P1[16] or P2[8] and * MDIO on P1[17] or P2[9]. The board.h file will define GPIO_ENET_MDC and * PGIO_ENET_MDIO to selec which pin setting to use. * * On older Rev '-' devices, P1[6] ENET-TX_CLK would also have be to configured. */ static const uint16_t g_enetpins[GPIO_NENET_PINS] = { GPIO_ENET_TXD0, GPIO_ENET_TXD1, GPIO_ENET_TXEN, GPIO_ENET_CRS, GPIO_ENET_RXD0, GPIO_ENET_RXD1, GPIO_ENET_RXER, GPIO_ENET_REFCLK, GPIO_ENET_MDC, GPIO_ENET_MDIO }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Register operations */ #ifdef CONFIG_NET_REGDEBUG static void lpc17_printreg(uint32_t addr, uint32_t val, bool iswrite); static void lpc17_checkreg(uint32_t addr, uint32_t val, bool iswrite); static uint32_t lpc17_getreg(uint32_t addr); static void lpc17_putreg(uint32_t val, uint32_t addr); #else # define lpc17_getreg(addr) getreg32(addr) # define lpc17_putreg(val,addr) putreg32(val,addr) #endif /* Common TX logic */ static int lpc17_txdesc(struct lpc17_driver_s *priv); static int lpc17_transmit(struct lpc17_driver_s *priv); static int lpc17_uiptxpoll(struct uip_driver_s *dev); /* Interrupt handling */ static void lpc17_response(struct lpc17_driver_s *priv); static void lpc17_rxdone(struct lpc17_driver_s *priv); static void lpc17_txdone(struct lpc17_driver_s *priv); static int lpc17_interrupt(int irq, void *context); /* Watchdog timer expirations */ static void lpc17_polltimer(int argc, uint32_t arg, ...); static void lpc17_txtimeout(int argc, uint32_t arg, ...); /* NuttX callback functions */ static int lpc17_ifup(struct uip_driver_s *dev); static int lpc17_ifdown(struct uip_driver_s *dev); static int lpc17_txavail(struct uip_driver_s *dev); #ifdef CONFIG_NET_IGMP static int lpc17_addmac(struct uip_driver_s *dev, const uint8_t *mac); static int lpc17_rmmac(struct uip_driver_s *dev, const uint8_t *mac); #endif /* Initialization functions */ #if defined(CONFIG_NET_REGDEBUG) && defined(CONFIG_DEBUG_GPIO) static void lpc17_showpins(void); #else # define lpc17_showpins() #endif /* PHY initialization functions */ #ifdef LPC17_HAVE_PHY # ifdef CONFIG_NET_REGDEBUG static void lpc17_showmii(uint8_t phyaddr, const char *msg); # else # define lpc17_showmii(phyaddr,msg) # endif static void lpc17_phywrite(uint8_t phyaddr, uint8_t regaddr, uint16_t phydata); static uint16_t lpc17_phyread(uint8_t phyaddr, uint8_t regaddr); static inline int lpc17_phyreset(uint8_t phyaddr); # ifdef CONFIG_PHY_AUTONEG static inline int lpc17_phyautoneg(uint8_t phyaddr); # endif static int lpc17_phymode(uint8_t phyaddr, uint8_t mode); static inline int lpc17_phyinit(struct lpc17_driver_s *priv); #else # define lpc17_phyinit(priv) #endif /* EMAC Initialization functions */ static inline void lpc17_txdescinit(struct lpc17_driver_s *priv); static inline void lpc17_rxdescinit(struct lpc17_driver_s *priv); static void lpc17_macmode(uint8_t mode); static void lpc17_ethreset(struct lpc17_driver_s *priv); /**************************************************************************** * Private Functions ****************************************************************************/ /******************************************************************************* * Name: lpc17_printreg * * Description: * Print the contents of an LPC17xx register operation * *******************************************************************************/ #ifdef CONFIG_NET_REGDEBUG static void lpc17_printreg(uint32_t addr, uint32_t val, bool iswrite) { dbg("%08x%s%08x\n", addr, iswrite ? "<-" : "->", val); } #endif /******************************************************************************* * Name: lpc17_checkreg * * Description: * Get the contents of an LPC17xx register * *******************************************************************************/ #ifdef CONFIG_NET_REGDEBUG static void lpc17_checkreg(uint32_t addr, uint32_t val, bool iswrite) { static uint32_t prevaddr = 0; static uint32_t preval = 0; static uint32_t count = 0; static bool prevwrite = false; /* Is this the same value that we read from/wrote to the same register last time? * Are we polling the register? If so, suppress the output. */ if (addr == prevaddr && val == preval && prevwrite == iswrite) { /* Yes.. Just increment the count */ count++; } else { /* No this is a new address or value or operation. Were there any * duplicate accesses before this one? */ if (count > 0) { /* Yes.. Just one? */ if (count == 1) { /* Yes.. Just one */ lpc17_printreg(prevaddr, preval, prevwrite); } else { /* No.. More than one. */ dbg("[repeats %d more times]\n", count); } } /* Save the new address, value, count, and operation for next time */ prevaddr = addr; preval = val; count = 0; prevwrite = iswrite; /* Show the new regisgter access */ lpc17_printreg(addr, val, iswrite); } } #endif /******************************************************************************* * Name: lpc17_getreg * * Description: * Get the contents of an LPC17xx register * *******************************************************************************/ #ifdef CONFIG_NET_REGDEBUG static uint32_t lpc17_getreg(uint32_t addr) { /* Read the value from the register */ uint32_t val = getreg32(addr); /* Check if we need to print this value */ lpc17_checkreg(addr, val, false); return val; } #endif /******************************************************************************* * Name: lpc17_putreg * * Description: * Set the contents of an LPC17xx register to a value * *******************************************************************************/ #ifdef CONFIG_NET_REGDEBUG static void lpc17_putreg(uint32_t val, uint32_t addr) { /* Check if we need to print this value */ lpc17_checkreg(addr, val, true); /* Write the value */ putreg32(val, addr); } #endif /**************************************************************************** * Function: lpc17_txdesc * * Description: * Check if a free TX descriptor is available. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * May or may not be called from an interrupt handler. In either case, * global interrupts are disabled, either explicitly or indirectly through * interrupt handling logic. * ****************************************************************************/ static int lpc17_txdesc(struct lpc17_driver_s *priv) { unsigned int prodidx; unsigned int considx; /* Get the next producer index */ prodidx = lpc17_getreg(LPC17_ETH_TXPRODIDX) & ETH_TXPRODIDX_MASK; if (++prodidx >= CONFIG_NET_NTXDESC) { /* Wrap back to index zero */ prodidx = 0; } /* If the next producer index would overrun the consumer index, then there * are no available Tx descriptors. */ considx = lpc17_getreg(LPC17_ETH_TXCONSIDX) & ETH_TXCONSIDX_MASK; return prodidx != considx ? OK : -EAGAIN; } /**************************************************************************** * Function: lpc17_transmit * * Description: * Start hardware transmission. Called either from the txdone interrupt * handling or from watchdog based polling. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * May or may not be called from an interrupt handler. In either case, * global interrupts are disabled, either explicitly or indirectly through * interrupt handling logic. * ****************************************************************************/ static int lpc17_transmit(struct lpc17_driver_s *priv) { uint32_t *txdesc; void *txbuffer; unsigned int prodidx; /* Verify that the hardware is ready to send another packet. If we get * here, then we are committed to sending a packet; Higher level logic * must have assured that there is no transmission in progress. */ DEBUGASSERT(lpc17_txdesc(priv) == OK); /* Increment statistics and dump the packet *if so configured) */ EMAC_STAT(priv, tx_packets); lpc17_dumppacket("Transmit packet", priv->lp_dev.d_buf, priv->lp_dev.d_len); /* Get the current producer index */ prodidx = lpc17_getreg(LPC17_ETH_TXPRODIDX) & ETH_TXPRODIDX_MASK; /* Get the packet address from the descriptor and set the descriptor control * fields. */ txdesc = (uint32_t*)(LPC17_TXDESC_BASE + (prodidx << 3)); txbuffer = (void*)*txdesc++; *txdesc = TXDESC_CONTROL_INT | TXDESC_CONTROL_LAST | TXDESC_CONTROL_CRC | (priv->lp_dev.d_len - 1); /* Copy the packet data into the Tx buffer assignd to this descriptor. It * should fit because each packet buffer is the MTU size and breaking up * largerTCP messasges is handled by higher level logic. The hardware * does, however, support breaking up larger messages into many fragments, * however, that capability is not exploited here. * * This would be a great performance improvement: Remove the buffer from * the lp_dev structure and replace it a pointer directly into the EMAC * DMA memory. This could eliminate the following, costly memcpy. */ DEBUGASSERT(priv->lp_dev.d_len <= LPC17_MAXPACKET_SIZE); memcpy(txbuffer, priv->lp_dev.d_buf, priv->lp_dev.d_len); /* Bump the producer index, making the packet available for transmission. */ if (++prodidx >= CONFIG_NET_NTXDESC) { /* Wrap back to index zero */ prodidx = 0; } lpc17_putreg(prodidx, LPC17_ETH_TXPRODIDX); /* Enable Tx interrupts */ priv->lp_inten |= ETH_TXINTS; lpc17_putreg(priv->lp_inten, LPC17_ETH_INTEN); /* Setup the TX timeout watchdog (perhaps restarting the timer) */ (void)wd_start(priv->lp_txtimeout, LPC17_TXTIMEOUT, lpc17_txtimeout, 1, (uint32_t)priv); return OK; } /**************************************************************************** * Function: lpc17_uiptxpoll * * Description: * The transmitter is available, check if uIP has any outgoing packets ready * to send. This is a callback from uip_poll(). uip_poll() may be called: * * 1. When the preceding TX packet send is complete, * 2. When the preceding TX packet send timesout and the interface is reset * 3. During normal TX polling * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * May or may not be called from an interrupt handler. In either case, * global interrupts are disabled, either explicitly or indirectly through * interrupt handling logic. * ****************************************************************************/ static int lpc17_uiptxpoll(struct uip_driver_s *dev) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)dev->d_private; int ret = OK; /* If the polling resulted in data that should be sent out on the network, * the field d_len is set to a value > 0. */ if (priv->lp_dev.d_len > 0) { /* Send this packet. In this context, we know that there is space for * at least one more packet in the descriptor list. */ uip_arp_out(&priv->lp_dev); lpc17_transmit(priv); /* Check if there is room in the device to hold another packet. If not, * return any non-zero value to terminate the poll. */ ret = lpc17_txdesc(priv); } /* If zero is returned, the polling will continue until all connections have * been examined. */ return ret; } /**************************************************************************** * Function: lpc17_response * * Description: * While processing an RxDone event, higher logic decides to send a packet, * possibly a response to the incoming packet (but probably not, in reality). * However, since the Rx and Tx operations are decoupled, there is no * guarantee that there will be a Tx descriptor available at that time. * This function will perform that check and, if no Tx descriptor is * available, this function will (1) stop incoming Rx processing (bad), and * (2) hold the outgoing packet in a pending state until the next Tx * interrupt occurs. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void lpc17_response(struct lpc17_driver_s *priv) { int ret; /* Check if there is room in the device to hold another packet. */ ret = lpc17_txdesc(priv); if (ret == OK) { /* Yes.. queue the packet now. */ lpc17_transmit(priv); } else { /* No.. mark the Tx as pending and halt further Tx interrupts */ DEBUGASSERT((priv->lp_inten & ETH_INT_TXDONE) != 0); priv->lp_txpending = true; priv->lp_inten &= ~ETH_RXINTS; lpc17_putreg(priv->lp_inten, LPC17_ETH_INTEN); EMAC_STAT(priv, tx_pending); } } /**************************************************************************** * Function: lpc17_rxdone * * Description: * An interrupt was received indicating the availability of a new RX packet * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void lpc17_rxdone(struct lpc17_driver_s *priv) { uint32_t *rxstat; bool fragment; unsigned int prodidx; unsigned int considx; unsigned int pktlen; /* Get the current producer and consumer indices */ considx = lpc17_getreg(LPC17_ETH_RXCONSIDX) & ETH_RXCONSIDX_MASK; prodidx = lpc17_getreg(LPC17_ETH_RXPRODIDX) & ETH_RXPRODIDX_MASK; /* Loop while there are incoming packets to be processed, that is, while * the producer index is not equal to the consumer index. */ fragment = false; while (considx != prodidx) { /* Update statistics */ EMAC_STAT(priv, rx_packets); /* Get the Rx status and packet length (-4+1) */ rxstat = (uint32_t*)(LPC17_RXSTAT_BASE + (considx << 3)); pktlen = (*rxstat & RXSTAT_INFO_RXSIZE_MASK) - 3; /* Check for errors. NOTE: The DMA engine reports bogus length errors, * making this a pretty useless check. */ if ((*rxstat & RXSTAT_INFO_ERROR) != 0) { nlldbg("Error. considx: %08x prodidx: %08x rxstat: %08x\n", considx, prodidx, *rxstat); EMAC_STAT(priv, rx_pkterr); } /* If the pktlen is greater then the buffer, then we cannot accept * the packet. Also, since the DMA packet buffers are set up to * be the same size as our max packet size, any fragments also * imply that the packet is too big. */ /* else */ if (pktlen > CONFIG_NET_BUFSIZE + CONFIG_NET_GUARDSIZE) { nlldbg("Too big. considx: %08x prodidx: %08x pktlen: %d rxstat: %08x\n", considx, prodidx, pktlen, *rxstat); EMAC_STAT(priv, rx_pktsize); } else if ((*rxstat & RXSTAT_INFO_LASTFLAG) == 0) { nlldbg("Fragment. considx: %08x prodidx: %08x pktlen: %d rxstat: %08x\n", considx, prodidx, pktlen, *rxstat); EMAC_STAT(priv, rx_fragment); fragment = true; } else if (fragment) { nlldbg("Last fragment. considx: %08x prodidx: %08x pktlen: %d rxstat: %08x\n", considx, prodidx, pktlen, *rxstat); EMAC_STAT(priv, rx_fragment); fragment = false; } else { uint32_t *rxdesc; void *rxbuffer; /* Get the Rx buffer address from the Rx descriptor */ rxdesc = (uint32_t*)(LPC17_RXDESC_BASE + (considx << 3)); rxbuffer = (void*)*rxdesc; /* Copy the data data from the EMAC DMA RAM to priv->lp_dev.d_buf. * Set amount of data in priv->lp_dev.d_len * * Here would be a great performance improvement: Remove the * buffer from the lp_dev structure and replace it with a pointer * directly into the EMAC DMA memory. This could eliminate the * following, costly memcpy. */ memcpy(priv->lp_dev.d_buf, rxbuffer, pktlen); priv->lp_dev.d_len = pktlen; lpc17_dumppacket("Received packet", priv->lp_dev.d_buf, priv->lp_dev.d_len); /* We only accept IP packets of the configured type and ARP packets */ #ifdef CONFIG_NET_IPv6 if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) #else if (BUF->type == HTONS(UIP_ETHTYPE_IP)) #endif { /* Handle the incoming Rx packet */ EMAC_STAT(priv, rx_ip); uip_arp_ipin(&priv->lp_dev); uip_input(&priv->lp_dev); /* If the above function invocation resulted in data that * should be sent out on the network, the field d_len will * set to a value > 0. */ if (priv->lp_dev.d_len > 0) { uip_arp_out(&priv->lp_dev); lpc17_response(priv); } } else if (BUF->type == htons(UIP_ETHTYPE_ARP)) { EMAC_STAT(priv, rx_arp); uip_arp_arpin(&priv->lp_dev); /* If the above function invocation resulted in data that * should be sent out on the network, the field d_len will * set to a value > 0. */ if (priv->lp_dev.d_len > 0) { lpc17_response(priv); } } else { /* Unrecognized... drop it. */ EMAC_STAT(priv, rx_dropped); } } /* Bump up the consumer index and resample the producer index (which * might also have gotten bumped up by the hardware). */ if (++considx >= CONFIG_NET_NRXDESC) { /* Wrap back to index zero */ considx = 0; } lpc17_putreg(considx, LPC17_ETH_RXCONSIDX); prodidx = lpc17_getreg(LPC17_ETH_RXPRODIDX) & ETH_RXPRODIDX_MASK; } } /**************************************************************************** * Function: lpc17_txdone * * Description: * An interrupt was received indicating that the last TX packet(s) is done * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void lpc17_txdone(struct lpc17_driver_s *priv) { /* Cancel the pending Tx timeout */ wd_cancel(priv->lp_txtimeout); /* Disable further Tx interrupts. Tx interrupts may be re-enabled again * depending upon the result of the poll. */ priv->lp_inten &= ~ETH_TXINTS; lpc17_putreg(priv->lp_inten, LPC17_ETH_INTEN); /* Verify that the hardware is ready to send another packet. Since a Tx * just completed, this must be the case. */ DEBUGASSERT(lpc17_txdesc(priv) == OK); /* Check if there is a pending Tx transfer that was scheduled by Rx handling * while the Tx logic was busy. If so, processing that pending Tx now. */ if (priv->lp_txpending) { /* Clear the pending condition, send the packet, and restore Rx interrupts */ priv->lp_txpending = false; EMAC_STAT(priv, tx_unpend); lpc17_transmit(priv); priv->lp_inten |= ETH_RXINTS; lpc17_putreg(priv->lp_inten, LPC17_ETH_INTEN); } /* Otherwise poll uIP for new XMIT data */ else { (void)uip_poll(&priv->lp_dev, lpc17_uiptxpoll); } } /**************************************************************************** * Function: lpc17_interrupt * * Description: * Hardware interrupt handler * * Parameters: * irq - Number of the IRQ that generated the interrupt * context - Interrupt register state save info (architecture-specific) * * Returned Value: * OK on success * * Assumptions: * ****************************************************************************/ static int lpc17_interrupt(int irq, void *context) { register struct lpc17_driver_s *priv; uint32_t status; #if CONFIG_LPC17_NINTERFACES > 1 # error "A mechanism to associate and interface with an IRQ is needed" #else priv = &g_ethdrvr[0]; #endif /* Get the interrupt status (zero means no interrupts pending). */ status = lpc17_getreg(LPC17_ETH_INTST); if (status != 0) { /* Clear all pending interrupts */ lpc17_putreg(status, LPC17_ETH_INTCLR); /* Handle each pending interrupt **************************************/ /* Check for Wake-Up on Lan *******************************************/ #ifdef CONFIG_NET_WOL if ((status & ETH_INT_WKUP) != 0) { EMAC_STAT(priv, wol); # warning "Missing logic" } else #endif /* Fatal Errors *******************************************************/ /* RX OVERRUN -- Fatal overrun error in the receive queue. The fatal * interrupt should be resolved by a Rx soft-reset. The bit is not * set when there is a nonfatal overrun error. * * TX UNDERRUN -- Interrupt set on a fatal underrun error in the * transmit queue. The fatal interrupt should be resolved by a Tx * soft-reset. The bit is not set when there is a nonfatal underrun * error. */ if ((status & (ETH_INT_RXOVR|ETH_INT_TXUNR)) != 0) { if ((status & ETH_INT_RXOVR) != 0) { nlldbg("RX Overrun. status: %08x\n", status); EMAC_STAT(priv, rx_ovrerrors); } if ((status & ETH_INT_TXUNR) != 0) { nlldbg("TX Underrun. status: %08x\n", status); EMAC_STAT(priv, tx_underrun); } /* ifup() will reset the EMAC and bring it back up */ (void)lpc17_ifup(&priv->lp_dev); } else { /* Check for receive events ***************************************/ /* RX ERROR -- Triggered on receive errors: AlignmentError, * RangeError, LengthError, SymbolError, CRCError or NoDescriptor * or Overrun. NOTE: (1) We will still need to call lpc17_rxdone * on RX errors to bump the considx over the bad packet. (2) The * DMA engine reports bogus length errors, making this a pretty * useless check anyway. */ if ((status & ETH_INT_RXERR) != 0) { nlldbg("RX Error. status: %08x\n", status); EMAC_STAT(priv, rx_errors); } /* RX FINISHED -- Triggered when all receive descriptors have * been processed i.e. on the transition to the situation * where ProduceIndex == ConsumeIndex. */ if ((status & ETH_INT_RXFIN) != 0) { EMAC_STAT(priv, rx_finished); DEBUGASSERT(lpc17_getreg(LPC17_ETH_RXPRODIDX) == lpc17_getreg(LPC17_ETH_RXCONSIDX)); } /* RX DONE -- Triggered when a receive descriptor has been * processed while the Interrupt bit in the Control field of * the descriptor was set. */ if ((status & ETH_INT_RXDONE) != 0) { EMAC_STAT(priv, rx_done); /* We have received at least one new incoming packet. */ lpc17_rxdone(priv); } /* Check for Tx events ********************************************/ /* TX ERROR -- Triggered on transmit errors: LateCollision, * ExcessiveCollision and ExcessiveDefer, NoDescriptor or Underrun. * NOTE: We will still need to call lpc17_txdone() in order to * clean up after the failed transmit. */ if ((status & ETH_INT_TXERR) != 0) { nlldbg("TX Error. status: %08x\n", status); EMAC_STAT(priv, tx_errors); } /* TX FINISHED -- Triggered when all transmit descriptors have * been processed i.e. on the transition to the situation * where ProduceIndex == ConsumeIndex. */ if ((status & ETH_INT_TXFIN) != 0) { EMAC_STAT(priv, tx_finished); } /* TX DONE -- Triggered when a descriptor has been transmitted * while the Interrupt bit in the Control field of the * descriptor was set. */ if ((status & ETH_INT_TXDONE) != 0) { EMAC_STAT(priv, tx_done); /* A packet transmission just completed */ lpc17_txdone(priv); } } } /* Clear the pending interrupt */ #if 0 /* Apparently not necessary */ # if CONFIG_LPC17_NINTERFACES > 1 lpc17_clrpend(priv->irq); # else lpc17_clrpend(LPC17_IRQ_ETH); # endif #endif return OK; } /**************************************************************************** * Function: lpc17_txtimeout * * Description: * Our TX watchdog timed out. Called from the timer interrupt handler. * The last TX never completed. Reset the hardware and start again. * * Parameters: * argc - The number of available arguments * arg - The first argument * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by the watchdog logic. * ****************************************************************************/ static void lpc17_txtimeout(int argc, uint32_t arg, ...) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)arg; /* Increment statistics and dump debug info */ EMAC_STAT(priv, tx_timeouts); if (priv->lp_ifup) { /* Then reset the hardware. ifup() will reset the interface, then bring * it back up. */ (void)lpc17_ifup(&priv->lp_dev); /* Then poll uIP for new XMIT data */ (void)uip_poll(&priv->lp_dev, lpc17_uiptxpoll); } } /**************************************************************************** * Function: lpc17_polltimer * * Description: * Periodic timer handler. Called from the timer interrupt handler. * * Parameters: * argc - The number of available arguments * arg - The first argument * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by the watchdog logic. * ****************************************************************************/ static void lpc17_polltimer(int argc, uint32_t arg, ...) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)arg; /* Check if there is room in the send another TX packet. We cannot perform * the TX poll if he are unable to accept another packet for transmission. */ if (lpc17_txdesc(priv) == OK) { /* If so, update TCP timing states and poll uIP for new XMIT data. Hmmm.. * might be bug here. Does this mean if there is a transmit in progress, * we will missing TCP time state updates? */ (void)uip_timer(&priv->lp_dev, lpc17_uiptxpoll, LPC17_POLLHSEC); } /* Setup the watchdog poll timer again */ (void)wd_start(priv->lp_txpoll, LPC17_WDDELAY, lpc17_polltimer, 1, arg); } /**************************************************************************** * Function: lpc17_ifup * * Description: * NuttX Callback: Bring up the Ethernet interface when an IP address is * provided * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static int lpc17_ifup(struct uip_driver_s *dev) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)dev->d_private; uint32_t regval; int ret; ndbg("Bringing up: %d.%d.%d.%d\n", dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24); /* Reset the Ethernet controller (again) */ lpc17_ethreset(priv); /* Initialize the PHY and wait for the link to be established */ ret = lpc17_phyinit(priv); if (ret != 0) { ndbg("lpc17_phyinit failed: %d\n", ret); return ret; } /* Configure the MAC station address */ regval = (uint32_t)priv->lp_dev.d_mac.ether_addr_octet[5] << 8 | (uint32_t)priv->lp_dev.d_mac.ether_addr_octet[4]; lpc17_putreg(regval, LPC17_ETH_SA0); regval = (uint32_t)priv->lp_dev.d_mac.ether_addr_octet[3] << 8 | (uint32_t)priv->lp_dev.d_mac.ether_addr_octet[2]; lpc17_putreg(regval, LPC17_ETH_SA1); regval = (uint32_t)priv->lp_dev.d_mac.ether_addr_octet[1] << 8 | (uint32_t)priv->lp_dev.d_mac.ether_addr_octet[0]; lpc17_putreg(regval, LPC17_ETH_SA2); /* Initialize Ethernet interface for the PHY setup */ lpc17_macmode(priv->lp_mode); /* Initialize EMAC DMA memory -- descriptors, status, packet buffers, etc. */ lpc17_txdescinit(priv); lpc17_rxdescinit(priv); /* Configure to pass all received frames */ regval = lpc17_getreg(LPC17_ETH_MAC1); regval |= ETH_MAC1_PARF; lpc17_putreg(regval, LPC17_ETH_MAC1); /* Set up RX filter and configure to accept broadcast addresses, multicast * addresses, and perfect station address matches. We should also accept * perfect matches and, most likely, broadcast (for example, for ARP requests). * Other RX filter options will only be enabled if so selected. NOTE: There * is a selection CONFIG_NET_BROADCAST, but this enables receipt of UDP * broadcast packets inside of the stack. */ regval = ETH_RXFLCTRL_PERFEN | ETH_RXFLCTRL_BCASTEN; #ifdef CONFIG_NET_MULTICAST regval |= (ETH_RXFLCTRL_MCASTEN | ETH_RXFLCTRL_UCASTEN); #endif #ifdef CONFIG_NET_HASH regval |= (ETH_RXFLCTRL_MCASTHASHEN | ETH_RXFLCTRL_UCASTHASHEN); #endif lpc17_putreg(regval, LPC17_ETH_RXFLCTRL); /* Clear any pending interrupts (shouldn't be any) */ lpc17_putreg(0xffffffff, LPC17_ETH_INTCLR); /* Configure interrupts. The Ethernet interrupt was attached during one-time * initialization, so we only need to set the interrupt priority, configure * interrupts, and enable them. */ /* Set the interrupt to the highest priority */ #ifdef CONFIG_ARCH_IRQPRIO #if CONFIG_LPC17_NINTERFACES > 1 (void)up_prioritize_irq(priv->irq, CONFIG_NET_PRIORITY); #else (void)up_prioritize_irq(LPC17_IRQ_ETH, CONFIG_NET_PRIORITY); #endif #endif /* Enable Ethernet interrupts. The way we do this depends on whether or * not Wakeup on Lan (WoL) has been configured. */ #ifdef CONFIG_NET_WOL /* Configure WoL: Clear all receive filter WoLs and enable the perfect * match WoL interrupt. We will wait until the Wake-up to finish * bringing things up. */ lpc17_putreg(0xffffffff, LPC17_ETH_RXFLWOLCLR); lpc17_putreg(ETH_RXFLCTRL_RXFILEN, LPC17_ETH_RXFLCTRL); priv->lp_inten = ETH_INT_WKUP; lpc17_putreg(ETH_INT_WKUP, LPC17_ETH_INTEN); #else /* Otherwise, enable all Rx interrupts. Tx interrupts, SOFTINT and WoL are * excluded. Tx interrupts will not be enabled until there is data to be * sent. */ priv->lp_inten = ETH_RXINTS; lpc17_putreg(ETH_RXINTS, LPC17_ETH_INTEN); #endif /* Enable Rx. "Enabling of the receive function is located in two places. * The receive DMA manager needs to be enabled and the receive data path * of the MAC needs to be enabled. To prevent overflow in the receive * DMA engine the receive DMA engine should be enabled by setting the * RxEnable bit in the Command register before enabling the receive data * path in the MAC by setting the RECEIVE ENABLE bit in the MAC1 register." */ regval = lpc17_getreg(LPC17_ETH_CMD); regval |= ETH_CMD_RXEN; lpc17_putreg(regval, LPC17_ETH_CMD); regval = lpc17_getreg(LPC17_ETH_MAC1); regval |= ETH_MAC1_RE; lpc17_putreg(regval, LPC17_ETH_MAC1); /* Enable Tx */ regval = lpc17_getreg(LPC17_ETH_CMD); regval |= ETH_CMD_TXEN; lpc17_putreg(regval, LPC17_ETH_CMD); /* Set and activate a timer process */ (void)wd_start(priv->lp_txpoll, LPC17_WDDELAY, lpc17_polltimer, 1, (uint32_t)priv); /* Finally, make the interface up and enable the Ethernet interrupt at * the interrupt controller */ priv->lp_ifup = true; #if CONFIG_LPC17_NINTERFACES > 1 up_enable_irq(priv->irq); #else up_enable_irq(LPC17_IRQ_ETH); #endif return OK; } /**************************************************************************** * Function: lpc17_ifdown * * Description: * NuttX Callback: Stop the interface. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static int lpc17_ifdown(struct uip_driver_s *dev) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)dev->d_private; irqstate_t flags; /* Disable the Ethernet interrupt */ flags = irqsave(); up_disable_irq(LPC17_IRQ_ETH); /* Cancel the TX poll timer and TX timeout timers */ wd_cancel(priv->lp_txpoll); wd_cancel(priv->lp_txtimeout); /* Reset the device and mark it as down. */ lpc17_ethreset(priv); priv->lp_ifup = false; irqrestore(flags); return OK; } /**************************************************************************** * Function: lpc17_txavail * * Description: * Driver callback invoked when new TX data is available. This is a * stimulus perform an out-of-cycle poll and, thereby, reduce the TX * latency. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * Called in normal user mode * ****************************************************************************/ static int lpc17_txavail(struct uip_driver_s *dev) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)dev->d_private; irqstate_t flags; /* Disable interrupts because this function may be called from interrupt * level processing. */ flags = irqsave(); /* Ignore the notification if the interface is not yet up */ if (priv->lp_ifup) { /* Check if there is room in the hardware to hold another outgoing packet. */ if (lpc17_txdesc(priv) == OK) { /* If so, then poll uIP for new XMIT data */ (void)uip_poll(&priv->lp_dev, lpc17_uiptxpoll); } } irqrestore(flags); return OK; } /**************************************************************************** * Function: lpc17_addmac * * Description: * NuttX Callback: Add the specified MAC address to the hardware multicast * address filtering * * Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be added * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #ifdef CONFIG_NET_IGMP static int lpc17_addmac(struct uip_driver_s *dev, const uint8_t *mac) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)dev->d_private; /* Add the MAC address to the hardware multicast routing table */ #warning "Not implemented" return OK; } #endif /**************************************************************************** * Function: lpc17_rmmac * * Description: * NuttX Callback: Remove the specified MAC address from the hardware multicast * address filtering * * Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be removed * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #ifdef CONFIG_NET_IGMP static int lpc17_rmmac(struct uip_driver_s *dev, const uint8_t *mac) { struct lpc17_driver_s *priv = (struct lpc17_driver_s *)dev->d_private; /* Add the MAC address to the hardware multicast routing table */ #warning "Not implemented" return OK; } #endif /******************************************************************************* * Name: lpc17_showpins * * Description: * Dump GPIO registers * * Parameters: * None * * Returned Value: * None * * Assumptions: * *******************************************************************************/ #if defined(CONFIG_NET_REGDEBUG) && defined(CONFIG_DEBUG_GPIO) static void lpc17_showpins(void) { lpc17_dumpgpio(GPIO_PORT1|GPIO_PIN0, "P1[1-15]"); lpc17_dumpgpio(GPIO_PORT1|GPIO_PIN16, "P1[16-31]"); } #endif /******************************************************************************* * Name: lpc17_showmii * * Description: * Dump PHY MII registers * * Parameters: * phyaddr - The device address where the PHY was discovered * * Returned Value: * None * * Assumptions: * *******************************************************************************/ #if defined(CONFIG_NET_REGDEBUG) && defined(LPC17_HAVE_PHY) static void lpc17_showmii(uint8_t phyaddr, const char *msg) { dbg("PHY " LPC17_PHYNAME ": %s\n", msg); dbg(" MCR: %04x\n", lpc17_phyread(phyaddr, MII_MCR)); dbg(" MSR: %04x\n", lpc17_phyread(phyaddr, MII_MSR)); dbg(" ADVERTISE: %04x\n", lpc17_phyread(phyaddr, MII_ADVERTISE)); dbg(" LPA: %04x\n", lpc17_phyread(phyaddr, MII_LPA)); dbg(" EXPANSION: %04x\n", lpc17_phyread(phyaddr, MII_EXPANSION)); #ifdef CONFIG_PHY_KS8721 dbg(" 10BTCR: %04x\n", lpc17_phyread(phyaddr, MII_KS8721_10BTCR)); #endif } #endif /**************************************************************************** * Function: lpc17_phywrite * * Description: * Write a value to an MII PHY register * * Parameters: * phyaddr - The device address where the PHY was discovered * regaddr - The address of the PHY register to be written * phydata - The data to write to the PHY register * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #ifdef LPC17_HAVE_PHY static void lpc17_phywrite(uint8_t phyaddr, uint8_t regaddr, uint16_t phydata) { uint32_t regval; /* Set PHY address and PHY register address */ regval = ((uint32_t)phyaddr << ETH_MADR_PHYADDR_SHIFT) | ((uint32_t)regaddr << ETH_MADR_REGADDR_SHIFT); lpc17_putreg(regval, LPC17_ETH_MADR); /* Set up to write */ lpc17_putreg(ETH_MCMD_WRITE, LPC17_ETH_MCMD); /* Write the register data to the PHY */ lpc17_putreg((uint32_t)phydata, LPC17_ETH_MWTD); /* Wait for the PHY command to complete */ while ((lpc17_getreg(LPC17_ETH_MIND) & ETH_MIND_BUSY) != 0); } #endif /**************************************************************************** * Function: lpc17_phyread * * Description: * Read a value from an MII PHY register * * Parameters: * phyaddr - The device address where the PHY was discovered * regaddr - The address of the PHY register to be written * * Returned Value: * Data read from the PHY register * * Assumptions: * ****************************************************************************/ #ifdef LPC17_HAVE_PHY static uint16_t lpc17_phyread(uint8_t phyaddr, uint8_t regaddr) { uint32_t regval; lpc17_putreg(0, LPC17_ETH_MCMD); /* Set PHY address and PHY register address */ regval = ((uint32_t)phyaddr << ETH_MADR_PHYADDR_SHIFT) | ((uint32_t)regaddr << ETH_MADR_REGADDR_SHIFT); lpc17_putreg(regval, LPC17_ETH_MADR); /* Set up to read */ lpc17_putreg(ETH_MCMD_READ, LPC17_ETH_MCMD); /* Wait for the PHY command to complete */ while ((lpc17_getreg(LPC17_ETH_MIND) & (ETH_MIND_BUSY|ETH_MIND_NVALID)) != 0); lpc17_putreg(0, LPC17_ETH_MCMD); /* Return the PHY register data */ return (uint16_t)(lpc17_getreg(LPC17_ETH_MRDD) & ETH_MRDD_MASK); } #endif /**************************************************************************** * Function: lpc17_phyreset * * Description: * Reset the PHY * * Parameters: * phyaddr - The device address where the PHY was discovered * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #ifdef LPC17_HAVE_PHY static inline int lpc17_phyreset(uint8_t phyaddr) { int32_t timeout; uint16_t phyreg; /* Reset the PHY. Needs a minimal 50uS delay after reset. */ lpc17_phywrite(phyaddr, MII_MCR, MII_MCR_RESET); /* Wait for a minimum of 50uS no matter what */ up_udelay(50); /* The MCR reset bit is self-clearing. Wait for it to be clear indicating * that the reset is complete. */ for (timeout = MII_BIG_TIMEOUT; timeout > 0; timeout--) { phyreg = lpc17_phyread(phyaddr, MII_MCR); if ((phyreg & MII_MCR_RESET) == 0) { return OK; } } ndbg("Reset failed. MCR: %04x\n", phyreg); return -ETIMEDOUT; } #endif /**************************************************************************** * Function: lpc17_phyautoneg * * Description: * Enable auto-negotiation. * * Parameters: * phyaddr - The device address where the PHY was discovered * * Returned Value: * None * * Assumptions: * The adverisement regiser has already been configured. * ****************************************************************************/ #if defined(LPC17_HAVE_PHY) && defined(CONFIG_PHY_AUTONEG) static inline int lpc17_phyautoneg(uint8_t phyaddr) { int32_t timeout; uint16_t phyreg; /* Start auto-negotiation */ lpc17_phywrite(phyaddr, MII_MCR, MII_MCR_ANENABLE | MII_MCR_ANRESTART); /* Wait for autonegotiation to complete */ for (timeout = MII_BIG_TIMEOUT; timeout > 0; timeout--) { /* Check if auto-negotiation has completed */ phyreg = lpc17_phyread(phyaddr, MII_MSR); if ((phyreg & MII_MSR_ANEGCOMPLETE) != 0) { /* Yes.. return success */ return OK; } } ndbg("Auto-negotiation failed. MSR: %04x\n", phyreg); return -ETIMEDOUT; } #endif /**************************************************************************** * Function: lpc17_phymode * * Description: * Set the PHY to operate at a selected speed/duplex mode. * * Parameters: * phyaddr - The device address where the PHY was discovered * mode - speed/duplex mode * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #ifdef LPC17_HAVE_PHY static int lpc17_phymode(uint8_t phyaddr, uint8_t mode) { int32_t timeout; uint16_t phyreg; /* Disable auto-negotiation and set fixed Speed and Duplex settings: * * MII_MCR_UNIDIR 0=Disable unidirectional enable * MII_MCR_SPEED1000 0=Reserved on 10/100 * MII_MCR_CTST 0=Disable collision test * MII_MCR_FULLDPLX ?=Full duplex * MII_MCR_ANRESTART 0=Don't restart auto negotiation * MII_MCR_ISOLATE 0=Don't electronically isolate PHY from MII * MII_MCR_PDOWN 0=Don't powerdown the PHY * MII_MCR_ANENABLE 0=Disable auto negotiation * MII_MCR_SPEED100 ?=Select 100Mbps * MII_MCR_LOOPBACK 0=Disable loopback mode * MII_MCR_RESET 0=No PHY reset */ phyreg = 0; if ((mode & LPC17_SPEED_MASK) == LPC17_SPEED_100) { phyreg = MII_MCR_SPEED100; } if ((mode & LPC17_DUPLEX_MASK) == LPC17_DUPLEX_FULL) { phyreg |= MII_MCR_FULLDPLX; } lpc17_phywrite(phyaddr, MII_MCR, phyreg); /* Then wait for the link to be established */ for (timeout = MII_BIG_TIMEOUT; timeout > 0; timeout--) { #ifdef CONFIG_PHY_DP83848C phyreg = lpc17_phyread(phyaddr, MII_DP83848C_STS); if ((phyreg & 0x0001) != 0) { /* Yes.. return success */ return OK; } #else phyreg = lpc17_phyread(phyaddr, MII_MSR); if ((phyreg & MII_MSR_LINKSTATUS) != 0) { /* Yes.. return success */ return OK; } #endif } ndbg("Link failed. MSR: %04x\n", phyreg); return -ETIMEDOUT; } #endif /**************************************************************************** * Function: lpc17_phyinit * * Description: * Initialize the PHY * * Parameters: * priv - Pointer to EMAC device driver structure * * Returned Value: * None directly. As a side-effect, it will initialize priv->lp_phyaddr * and priv->lp_phymode. * * Assumptions: * ****************************************************************************/ #ifdef LPC17_HAVE_PHY static inline int lpc17_phyinit(struct lpc17_driver_s *priv) { unsigned int phyaddr; uint16_t phyreg; uint32_t regval; int ret; /* MII configuration: host clocked divided per board.h, no suppress * preamble, no scan increment. */ lpc17_putreg(ETH_MCFG_CLKSEL_DIV, LPC17_ETH_MCFG); lpc17_putreg(0, LPC17_ETH_MCMD); /* Enter RMII mode and select 100 MBPS support */ lpc17_putreg(ETH_CMD_RMII, LPC17_ETH_CMD); lpc17_putreg(ETH_SUPP_SPEED, LPC17_ETH_SUPP); /* Find PHY Address. Because the controller has a pull-up and the * PHY has pull-down resistors on RXD lines some times the PHY * latches different at different addresses. */ for (phyaddr = 1; phyaddr < 32; phyaddr++) { /* Check if we can see the selected device ID at this * PHY address. */ phyreg = (unsigned int)lpc17_phyread(phyaddr, MII_PHYID1); nvdbg("Addr: %d PHY ID1: %04x\n", phyaddr, phyreg); if (phyreg == LPC17_PHYID1) { phyreg = lpc17_phyread(phyaddr, MII_PHYID2); nvdbg("Addr: %d PHY ID2: %04x\n", phyaddr, phyreg); if (phyreg == LPC17_PHYID2) { break; } } } /* Check if the PHY device address was found */ if (phyaddr > 31) { /* Failed to find PHY at any location */ ndbg("No PHY detected\n"); return -ENODEV; } nvdbg("phyaddr: %d\n", phyaddr); /* Save the discovered PHY device address */ priv->lp_phyaddr = phyaddr; /* Reset the PHY */ ret = lpc17_phyreset(phyaddr); if (ret < 0) { return ret; } lpc17_showmii(phyaddr, "After reset"); /* Check for preamble suppression support */ phyreg = lpc17_phyread(phyaddr, MII_MSR); if ((phyreg & MII_MSR_MFRAMESUPPRESS) != 0) { /* The PHY supports preamble suppression */ regval = lpc17_getreg(LPC17_ETH_MCFG); regval |= ETH_MCFG_SUPPRE; lpc17_putreg(regval, LPC17_ETH_MCFG); } /* Are we configured to do auto-negotiation? */ #ifdef CONFIG_PHY_AUTONEG /* Setup the Auto-negotiation advertisement: 100 or 10, and HD or FD */ lpc17_phywrite(phyaddr, MII_ADVERTISE, (MII_ADVERTISE_100BASETXFULL | MII_ADVERTISE_100BASETXHALF | MII_ADVERTISE_10BASETXFULL | MII_ADVERTISE_10BASETXHALF | MII_ADVERTISE_CSMA)); /* Then perform the auto-negotiation */ ret = lpc17_phyautoneg(phyaddr); if (ret < 0) { return ret; } #else /* Set up the fixed PHY configuration */ ret = lpc17_phymode(phyaddr, LPC17_MODE_DEFLT); if (ret < 0) { return ret; } #endif /* The link is established */ lpc17_showmii(phyaddr, "After link established"); /* Check configuration */ #if defined(CONFIG_PHY_KS8721) phyreg = lpc17_phyread(phyaddr, MII_KS8721_10BTCR); switch (phyreg & KS8721_10BTCR_MODE_MASK) { case KS8721_10BTCR_MODE_10BTHD: /* 10BASE-T half duplex */ priv->lp_mode = LPC17_10BASET_HD; lpc17_putreg(0, LPC17_ETH_SUPP); break; case KS8721_10BTCR_MODE_100BTHD: /* 100BASE-T half duplex */ priv->lp_mode = LPC17_100BASET_HD; break; case KS8721_10BTCR_MODE_10BTFD: /* 10BASE-T full duplex */ priv->lp_mode = LPC17_10BASET_FD; lpc17_putreg(0, LPC17_ETH_SUPP); break; case KS8721_10BTCR_MODE_100BTFD: /* 100BASE-T full duplex */ priv->lp_mode = LPC17_100BASET_FD; break; default: ndbg("Unrecognized mode: %04x\n", phyreg); return -ENODEV; } #elif defined(CONFIG_PHY_DP83848C) phyreg = lpc17_phyread(phyaddr, MII_DP83848C_STS); /* Configure for full/half duplex mode and speed */ switch (phyreg & 0x0006) { case 0x0000: priv->lp_mode = LPC17_100BASET_HD; break; case 0x0002: priv->lp_mode = LPC17_10BASET_HD; break; case 0x0004: priv->lp_mode = LPC17_100BASET_FD; break; case 0x0006: priv->lp_mode = LPC17_10BASET_FD; break; default: ndbg("Unrecognized mode: %04x\n", phyreg); return -ENODEV; } #elif defined(CONFIG_PHY_LAN8720) { uint16_t advertise; uint16_t lpa; up_udelay(500); advertise = lpc17_phyread(phyaddr, MII_ADVERTISE); lpa = lpc17_phyread(phyaddr, MII_LPA); /* Check for 100BASETX full duplex */ if ((advertise & MII_ADVERTISE_100BASETXFULL) != 0 && (lpa & MII_LPA_100BASETXFULL) != 0) { priv->lp_mode = LPC17_100BASET_FD; } /* Check for 100BASETX half duplex */ else if ((advertise & MII_ADVERTISE_100BASETXHALF) != 0 && (lpa & MII_LPA_100BASETXHALF) != 0) { priv->lp_mode = LPC17_100BASET_HD; } /* Check for 10BASETX full duplex */ else if ((advertise & MII_ADVERTISE_10BASETXFULL) != 0 && (lpa & MII_LPA_10BASETXFULL) != 0) { priv->lp_mode = LPC17_10BASET_FD; } /* Check for 10BASETX half duplex */ else if ((advertise & MII_ADVERTISE_10BASETXHALF) != 0 && (lpa & MII_LPA_10BASETXHALF) != 0) { priv->lp_mode = LPC17_10BASET_HD; } else { ndbg("Unrecognized mode: %04x\n", phyreg); return -ENODEV; } } #else # warning "PHY Unknown: speed and duplex are bogus" #endif ndbg("%dBase-T %s duplex\n", (priv->lp_mode & LPC17_SPEED_MASK) == LPC17_SPEED_100 ? 100 : 10, (priv->lp_mode & LPC17_DUPLEX_MASK) == LPC17_DUPLEX_FULL ?"full" : "half"); /* Disable auto-configuration. Set the fixed speed/duplex mode. * (probably more than little redundant). * * REVISIT: Revisit the following CONFIG_PHY_CEMENT_DISABLE work-around. * It is should not needed if CONFIG_PHY_AUTONEG is defined and is known * cause a problem for at least one PHY (DP83848I PHY). It might be * safe just to remove this elided coded for all PHYs. */ #ifndef CONFIG_PHY_CEMENT_DISABLE ret = lpc17_phymode(phyaddr, priv->lp_mode); #endif lpc17_showmii(phyaddr, "After final configuration"); return ret; } #else static inline int lpc17_phyinit(struct lpc17_driver_s *priv) { priv->lp_mode = LPC17_MODE_DEFLT; return OK; } #endif /**************************************************************************** * Function: lpc17_txdescinit * * Description: * Initialize the EMAC Tx descriptor table * * Parameters: * priv - Pointer to EMAC device driver structure * * Returned Value: * None directory. * As a side-effect, it will initialize priv->lp_phyaddr and * priv->lp_phymode. * * Assumptions: * ****************************************************************************/ static inline void lpc17_txdescinit(struct lpc17_driver_s *priv) { uint32_t *txdesc; uint32_t *txstat; uint32_t pktaddr; int i; /* Configure Tx descriptor and status tables */ lpc17_putreg(LPC17_TXDESC_BASE, LPC17_ETH_TXDESC); lpc17_putreg(LPC17_TXSTAT_BASE, LPC17_ETH_TXSTAT); lpc17_putreg(CONFIG_NET_NTXDESC-1, LPC17_ETH_TXDESCRNO); /* Initialize Tx descriptors and link to packet buffers */ txdesc = (uint32_t*)LPC17_TXDESC_BASE; pktaddr = LPC17_TXBUFFER_BASE; for (i = 0; i < CONFIG_NET_NTXDESC; i++) { *txdesc++ = pktaddr; *txdesc++ = (TXDESC_CONTROL_INT | (LPC17_MAXPACKET_SIZE - 1)); pktaddr += LPC17_MAXPACKET_SIZE; } /* Initialize Tx status */ txstat = (uint32_t*)LPC17_TXSTAT_BASE; for (i = 0; i < CONFIG_NET_NTXDESC; i++) { *txstat++ = 0; } /* Point to first Tx descriptor */ lpc17_putreg(0, LPC17_ETH_TXPRODIDX); } /**************************************************************************** * Function: lpc17_rxdescinit * * Description: * Initialize the EMAC Rx descriptor table * * Parameters: * priv - Pointer to EMAC device driver structure * * Returned Value: * None directory. * As a side-effect, it will initialize priv->lp_phyaddr and * priv->lp_phymode. * * Assumptions: * ****************************************************************************/ static inline void lpc17_rxdescinit(struct lpc17_driver_s *priv) { uint32_t *rxdesc; uint32_t *rxstat; uint32_t pktaddr; int i; /* Configure Rx descriptor and status tables */ lpc17_putreg(LPC17_RXDESC_BASE, LPC17_ETH_RXDESC); lpc17_putreg(LPC17_RXSTAT_BASE, LPC17_ETH_RXSTAT); lpc17_putreg(CONFIG_NET_NRXDESC-1, LPC17_ETH_RXDESCNO); /* Initialize Rx descriptors and link to packet buffers */ rxdesc = (uint32_t*)LPC17_RXDESC_BASE; pktaddr = LPC17_RXBUFFER_BASE; for (i = 0; i < CONFIG_NET_NRXDESC; i++) { *rxdesc++ = pktaddr; *rxdesc++ = (RXDESC_CONTROL_INT | (LPC17_MAXPACKET_SIZE - 1)); pktaddr += LPC17_MAXPACKET_SIZE; } /* Initialize Rx status */ rxstat = (uint32_t*)LPC17_RXSTAT_BASE; for (i = 0; i < CONFIG_NET_NRXDESC; i++) { *rxstat++ = 0; *rxstat++ = 0; } /* Point to first Rx descriptor */ lpc17_putreg(0, LPC17_ETH_RXCONSIDX); } /**************************************************************************** * Function: lpc17_macmode * * Description: * Set the MAC to operate at a selected speed/duplex mode. * * Parameters: * mode - speed/duplex mode * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #ifdef LPC17_HAVE_PHY static void lpc17_macmode(uint8_t mode) { uint32_t regval; /* Set up for full or half duplex operation */ if ((mode & LPC17_DUPLEX_MASK) == LPC17_DUPLEX_FULL) { /* Set the back-to-back inter-packet gap */ lpc17_putreg(21, LPC17_ETH_IPGT); /* Set MAC to operate in full duplex mode with CRC and Pad enabled */ regval = lpc17_getreg(LPC17_ETH_MAC2); regval |= (ETH_MAC2_FD | ETH_MAC2_CRCEN | ETH_MAC2_PADCRCEN); lpc17_putreg(regval, LPC17_ETH_MAC2); /* Select full duplex operation for ethernet controller */ regval = lpc17_getreg(LPC17_ETH_CMD); regval |= (ETH_CMD_FD | ETH_CMD_RMII | ETH_CMD_PRFRAME); lpc17_putreg(regval, LPC17_ETH_CMD); } else { /* Set the back-to-back inter-packet gap */ lpc17_putreg(18, LPC17_ETH_IPGT); /* Set MAC to operate in half duplex mode with CRC and Pad enabled */ regval = lpc17_getreg(LPC17_ETH_MAC2); regval &= ~ETH_MAC2_FD; regval |= (ETH_MAC2_CRCEN | ETH_MAC2_PADCRCEN); lpc17_putreg(regval, LPC17_ETH_MAC2); /* Select half duplex operation for ethernet controller */ regval = lpc17_getreg(LPC17_ETH_CMD); regval &= ~ETH_CMD_FD; regval |= (ETH_CMD_RMII | ETH_CMD_PRFRAME); lpc17_putreg(regval, LPC17_ETH_CMD); } /* This is currently done in lpc17_phyinit(). That doesn't * seem like the right place. It should be done here. */ #if 0 regval = lpc17_getreg(LPC17_ETH_SUPP); if ((mode & LPC17_SPEED_MASK) == LPC17_SPEED_100) { regval |= ETH_SUPP_SPEED; } else { regval &= ~ETH_SUPP_SPEED; } lpc17_putreg(regval, LPC17_ETH_SUPP); #endif } #endif /**************************************************************************** * Function: lpc17_ethreset * * Description: * Configure and reset the Ethernet module, leaving it in a disabled state. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * ****************************************************************************/ static void lpc17_ethreset(struct lpc17_driver_s *priv) { irqstate_t flags; /* Reset the MAC */ flags = irqsave(); /* Put the MAC into the reset state */ lpc17_putreg((ETH_MAC1_TXRST | ETH_MAC1_MCSTXRST | ETH_MAC1_RXRST | ETH_MAC1_MCSRXRST | ETH_MAC1_SIMRST | ETH_MAC1_SOFTRST), LPC17_ETH_MAC1); /* Disable RX/RX, clear modes, reset all control registers */ lpc17_putreg((ETH_CMD_REGRST | ETH_CMD_TXRST | ETH_CMD_RXRST), LPC17_ETH_CMD); /* Take the MAC out of the reset state */ up_udelay(50); lpc17_putreg(0, LPC17_ETH_MAC1); /* The RMII bit must be set on initialization (I'm not sure this needs * to be done here but... oh well). */ lpc17_putreg(ETH_CMD_RMII, LPC17_ETH_CMD); /* Set other misc configuration-related registers to default values */ lpc17_putreg(0, LPC17_ETH_MAC2); lpc17_putreg(0, LPC17_ETH_SUPP); lpc17_putreg(0, LPC17_ETH_TEST); lpc17_putreg(18, LPC17_ETH_IPGR); lpc17_putreg(((15 << ETH_CLRT_RMAX_SHIFT) | (55 << ETH_CLRT_COLWIN_SHIFT)), LPC17_ETH_CLRT); /* Set the Maximum Frame size register. "This field resets to the value * 0x0600, which represents a maximum receive frame of 1536 octets. An * untagged maximum size Ethernet frame is 1518 octets. A tagged frame adds * four octets for a total of 1522 octets. If a shorter maximum length * restriction is desired, program this 16-bit field." */ lpc17_putreg(LPC17_MAXPACKET_SIZE, LPC17_ETH_MAXF); /* Disable all Ethernet controller interrupts */ lpc17_putreg(0, LPC17_ETH_INTEN); /* Clear any pending interrupts (shouldn't be any) */ lpc17_putreg(0xffffffff, LPC17_ETH_INTCLR); irqrestore(flags); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Function: lpc17_ethinitialize * * Description: * Initialize one Ethernet controller and driver structure. * * Parameters: * intf - Selects the interface to be initialized. * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ #if CONFIG_LPC17_NINTERFACES > 1 int lpc17_ethinitialize(int intf) #else static inline int lpc17_ethinitialize(int intf) #endif { struct lpc17_driver_s *priv; uint32_t regval; int ret; int i; DEBUGASSERT(intf < CONFIG_LPC17_NINTERFACES); priv = &g_ethdrvr[intf]; /* Turn on the ethernet MAC clock */ regval = lpc17_getreg(LPC17_SYSCON_PCONP); regval |= SYSCON_PCONP_PCENET; lpc17_putreg(regval, LPC17_SYSCON_PCONP); /* Configure all GPIO pins needed by ENET */ for (i = 0; i < GPIO_NENET_PINS; i++) { (void)lpc17_configgpio(g_enetpins[i]); } lpc17_showpins(); /* Initialize the driver structure */ memset(priv, 0, sizeof(struct lpc17_driver_s)); priv->lp_dev.d_ifup = lpc17_ifup; /* I/F down callback */ priv->lp_dev.d_ifdown = lpc17_ifdown; /* I/F up (new IP address) callback */ priv->lp_dev.d_txavail = lpc17_txavail; /* New TX data callback */ #ifdef CONFIG_NET_IGMP priv->lp_dev.d_addmac = lpc17_addmac; /* Add multicast MAC address */ priv->lp_dev.d_rmmac = lpc17_rmmac; /* Remove multicast MAC address */ #endif priv->lp_dev.d_private = (void*)priv; /* Used to recover private state from dev */ #if CONFIG_LPC17_NINTERFACES > 1 # error "A mechanism to associate base address an IRQ with an interface is needed" priv->lp_base = ??; /* Ethernet controller base address */ priv->lp_irq = ??; /* Ethernet controller IRQ number */ #endif /* Create a watchdog for timing polling for and timing of transmisstions */ priv->lp_txpoll = wd_create(); /* Create periodic poll timer */ priv->lp_txtimeout = wd_create(); /* Create TX timeout timer */ /* Reset the Ethernet controller and leave in the ifdown statue. The * Ethernet controller will be properly re-initialized each time * lpc17_ifup() is called. */ lpc17_ifdown(&priv->lp_dev); /* Attach the IRQ to the driver */ #if CONFIG_LPC17_NINTERFACES > 1 ret = irq_attach(priv->irq, lpc17_interrupt); #else ret = irq_attach(LPC17_IRQ_ETH, lpc17_interrupt); #endif if (ret != 0) { /* We could not attach the ISR to the the interrupt */ return -EAGAIN; } /* Register the device with the OS so that socket IOCTLs can be performed */ (void)netdev_register(&priv->lp_dev); return OK; } /**************************************************************************** * Name: up_netinitialize * * Description: * Initialize the first network interface. If there are more than one * interface in the chip, then board-specific logic will have to provide * this function to determine which, if any, Ethernet controllers should * be initialized. * ****************************************************************************/ #if CONFIG_LPC17_NINTERFACES == 1 void up_netinitialize(void) { (void)lpc17_ethinitialize(0); } #endif #endif /* LPC17_NETHCONTROLLERS > 0 */ #endif /* CONFIG_NET && CONFIG_LPC17_ETHERNET */