4025 lines
113 KiB
C
4025 lines
113 KiB
C
/****************************************************************************
|
|
* arch/arm/src/lpc43/lpc43_eth.c
|
|
*
|
|
* Copyright (C) 2011-2015 Gregory Nutt. All rights reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* 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 <nuttx/config.h>
|
|
#if defined(CONFIG_NET) && defined(CONFIG_LPC43_ETHERNET)
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <debug.h>
|
|
#include <queue.h>
|
|
#include <errno.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/wdog.h>
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
# include <nuttx/wqueue.h>
|
|
#endif
|
|
|
|
#include <nuttx/net/mii.h>
|
|
#include <nuttx/net/arp.h>
|
|
#include <nuttx/net/netdev.h>
|
|
#if defined(CONFIG_NET_PKT)
|
|
# include <nuttx/net/pkt.h>
|
|
#endif
|
|
|
|
#include "up_internal.h"
|
|
|
|
#include "chip.h"
|
|
#include "lpc43_pinconfig.h"
|
|
#include "lpc43_ethernet.h"
|
|
#include "chip/lpc43_creg.h"
|
|
#include "chip/lpc43_cgu.h"
|
|
#include "lpc43_rgu.h"
|
|
#include "up_arch.h"
|
|
#include <arch/board/board.h>
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
/* Configuration ************************************************************/
|
|
|
|
/* If processing is not done at the interrupt level, then high priority
|
|
* work queue support is required.
|
|
*/
|
|
|
|
#if defined(CONFIG_NET_NOINTS) && !defined(CONFIG_SCHED_HPWORK)
|
|
# error High priority work queue support is required
|
|
#endif
|
|
|
|
#ifndef CONFIG_LPC43_PHYADDR
|
|
# error "CONFIG_LPC43_PHYADDR must be defined in the NuttX configuration"
|
|
#endif
|
|
|
|
#if !defined(CONFIG_LPC43_MII) && !defined(CONFIG_LPC43_RMII)
|
|
# warning "Neither CONFIG_LPC43_MII nor CONFIG_LPC43_RMII defined"
|
|
#endif
|
|
|
|
#if defined(CONFIG_LPC43_MII) && defined(CONFIG_LPC43_RMII)
|
|
# error "Both CONFIG_LPC43_MII and CONFIG_LPC43_RMII defined"
|
|
#endif
|
|
|
|
#ifdef CONFIG_LPC43_AUTONEG
|
|
# ifndef CONFIG_LPC43_PHYSR
|
|
# error "CONFIG_LPC43_PHYSR must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifdef CONFIG_LPC43_PHYSR_ALTCONFIG
|
|
# ifndef CONFIG_LPC43_PHYSR_ALTMODE
|
|
# error "CONFIG_LPC43_PHYSR_ALTMODE must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_LPC43_PHYSR_10HD
|
|
# error "CONFIG_LPC43_PHYSR_10HD must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_LPC43_PHYSR_100HD
|
|
# error "CONFIG_LPC43_PHYSR_100HD must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_LPC43_PHYSR_10FD
|
|
# error "CONFIG_LPC43_PHYSR_10FD must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_LPC43_PHYSR_100FD
|
|
# error "CONFIG_LPC43_PHYSR_100FD must be defined in the NuttX configuration"
|
|
# endif
|
|
# else
|
|
# ifndef CONFIG_LPC43_PHYSR_SPEED
|
|
# error "CONFIG_LPC43_PHYSR_SPEED must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_LPC43_PHYSR_100MBPS
|
|
# error "CONFIG_LPC43_PHYSR_100MBPS must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_LPC43_PHYSR_MODE
|
|
# error "CONFIG_LPC43_PHYSR_MODE must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_LPC43_PHYSR_FULLDUPLEX
|
|
# error "CONFIG_LPC43_PHYSR_FULLDUPLEX must be defined in the NuttX configuration"
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_LPC43_ETH_PTP
|
|
# warning "CONFIG_LPC43_ETH_PTP is not yet supported"
|
|
#endif
|
|
|
|
/* This driver does not use enhanced descriptors. Enhanced descriptors must
|
|
* be used, however, if time stamping or and/or IPv4 checksum offload is
|
|
* supported.
|
|
*/
|
|
|
|
#undef CONFIG_LPC43_ETH_ENHANCEDDESC
|
|
#undef CONFIG_LPC43_ETH_HWCHECKSUM
|
|
|
|
/* Ethernet buffer sizes, number of buffers, and number of descriptors */
|
|
|
|
#ifndef CONFIG_NET_MULTIBUFFER
|
|
# error "CONFIG_NET_MULTIBUFFER is required"
|
|
#endif
|
|
|
|
/* Add 4 to the configured buffer size to account for the 2 byte checksum
|
|
* memory needed at the end of the maximum size packet. Buffer sizes must
|
|
* be an even multiple of 4, 8, or 16 bytes (depending on buswidth). We
|
|
* will use the 16-byte alignment in all cases.
|
|
*/
|
|
|
|
#define OPTIMAL_ETH_BUFSIZE ((CONFIG_NET_ETH_MTU + 4 + 15) & ~15)
|
|
|
|
#ifndef CONFIG_LPC43_ETH_BUFSIZE
|
|
# define CONFIG_LPC43_ETH_BUFSIZE OPTIMAL_ETH_BUFSIZE
|
|
#endif
|
|
|
|
#if CONFIG_LPC43_ETH_BUFSIZE > ETH_TDES1_TBS1_MASK
|
|
# error "CONFIG_LPC43_ETH_BUFSIZE is too large"
|
|
#endif
|
|
|
|
#if (CONFIG_LPC43_ETH_BUFSIZE & 15) != 0
|
|
# error "CONFIG_LPC43_ETH_BUFSIZE must be aligned"
|
|
#endif
|
|
|
|
#if CONFIG_LPC43_ETH_BUFSIZE != OPTIMAL_ETH_BUFSIZE
|
|
# warning "You using an incomplete/untested configuration"
|
|
#endif
|
|
|
|
#ifndef CONFIG_LPC43_ETH_NRXDESC
|
|
# define CONFIG_LPC43_ETH_NRXDESC 8
|
|
#endif
|
|
#ifndef CONFIG_LPC43_ETH_NTXDESC
|
|
# define CONFIG_LPC43_ETH_NTXDESC 4
|
|
#endif
|
|
|
|
/* We need at least one more free buffer than transmit buffers */
|
|
|
|
#define LPC43_ETH_NFREEBUFFERS (CONFIG_LPC43_ETH_NTXDESC+1)
|
|
|
|
/* Extremely detailed register debug that you would normally never want
|
|
* enabled.
|
|
*/
|
|
|
|
#ifndef CONFIG_DEBUG
|
|
# undef CONFIG_LPC43_ETHMAC_REGDEBUG
|
|
#endif
|
|
|
|
/* Clocking *****************************************************************/
|
|
/* Set MACMIIAR CR bits depending on HCLK setting */
|
|
|
|
#if BOARD_FCLKOUT_FREQUENCY >= 20000000 && BOARD_FCLKOUT_FREQUENCY < 35000000
|
|
# define ETH_MACMIIA_CR ETH_MACMIIA_CR_20_35
|
|
#elif BOARD_FCLKOUT_FREQUENCY >= 35000000 && BOARD_FCLKOUT_FREQUENCY < 60000000
|
|
# define ETH_MACMIIA_CR ETH_MACMIIA_CR_35_60
|
|
#elif BOARD_FCLKOUT_FREQUENCY >= 60000000 && BOARD_FCLKOUT_FREQUENCY < 100000000
|
|
# define ETH_MACMIIA_CR ETH_MACMIIA_CR_60_100
|
|
#elif BOARD_FCLKOUT_FREQUENCY >= 100000000 && BOARD_FCLKOUT_FREQUENCY < 150000000
|
|
# define ETH_MACMIIA_CR ETH_MACMIIA_CR_100_150
|
|
#elif BOARD_FCLKOUT_FREQUENCY >= 150000000 && BOARD_FCLKOUT_FREQUENCY <= 250000000
|
|
# define ETH_MACMIIA_CR ETH_MACMIIA_CR_150_250
|
|
#elif BOARD_FCLKOUT_FREQUENCY >= 250000000 && BOARD_FCLKOUT_FREQUENCY <= 350000000
|
|
# define ETH_MACMIIA_CR ETH_MACMIIA_CR_250_300
|
|
#else
|
|
# error "BOARD_FCLKOUT_FREQUENCY not supportable"
|
|
#endif
|
|
|
|
/* Timing *******************************************************************/
|
|
/* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per
|
|
* second
|
|
*/
|
|
|
|
#define LPC43_WDDELAY (1*CLK_TCK)
|
|
|
|
/* TX timeout = 1 minute */
|
|
|
|
#define LPC43_TXTIMEOUT (60*CLK_TCK)
|
|
|
|
/* PHY reset/configuration delays in milliseconds */
|
|
|
|
#define PHY_RESET_DELAY (65)
|
|
#define PHY_CONFIG_DELAY (1000)
|
|
|
|
/* PHY read/write delays in loop counts */
|
|
|
|
#define PHY_READ_TIMEOUT (0x0004ffff)
|
|
#define PHY_WRITE_TIMEOUT (0x0004ffff)
|
|
#define PHY_RETRY_TIMEOUT (0x0004ffff)
|
|
|
|
/* Register values **********************************************************/
|
|
|
|
/* Clear the MACCR bits that will be setup during MAC initialization (or that
|
|
* are cleared unconditionally). Per the reference manual, all reserved bits
|
|
* must be retained at their reset value.
|
|
*
|
|
* ETH_MACCFG_RE Bit 2: Receiver enable
|
|
* ETH_MACCFG_TE Bit 3: Transmitter enable
|
|
* ETH_MACCFG_DC Bit 4: Deferral check
|
|
* ETH_MACCFG_BL Bits 5-6: Back-off limit
|
|
* ETH_MACCFG_APCS Bit 7: Automatic pad/CRC stripping
|
|
* ETH_MACCFG_RD Bit 9: Retry disable
|
|
* ETH_MACCFG_DM Bit 11: Duplex mode
|
|
* ETH_MACCFG_LM Bit 12: Loopback mode
|
|
* ETH_MACCFG_DO Bit 13: Receive own disable
|
|
* ETH_MACCFG_FES Bit 14: Fast Ethernet speed
|
|
* ETH_MACCFG_CSD Bit 16: Carrier sense disable
|
|
* ETH_MACCFG_IFG Bits 17-19: Interframe gap
|
|
* ETH_MACCFG_JD Bit 22: Jabber disable
|
|
* ETH_MACCFG_WD Bit 23: Watchdog disable
|
|
* ETH_MACCFG_CSTF Bits 25: CRC stripping for Type frames (F2/F4 only)
|
|
*/
|
|
|
|
#define MACCR_CLEAR_BITS \
|
|
(ETH_MACCFG_RE | ETH_MACCFG_TE | ETH_MACCFG_DF | ETH_MACCFG_BL_MASK | \
|
|
ETH_MACCFG_ACS | ETH_MACCFG_RD | ETH_MACCFG_DM | \
|
|
ETH_MACCFG_LM | ETH_MACCFG_DO | ETH_MACCFG_FES | ETH_MACCFG_DCRS | \
|
|
ETH_MACCFG_JE | ETH_MACCFG_IFG_MASK | ETH_MACCFG_JD | ETH_MACCFG_WD)
|
|
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
* ETH_MACCFG_RE Receiver enable 0 (disabled)
|
|
* ETH_MACCFG_TE Transmitter enable 0 (disabled)
|
|
* ETH_MACCFG_DC Deferral check 0 (disabled)
|
|
* ETH_MACCFG_BL Back-off limit 0 (10)
|
|
* ETH_MACCFG_APCS Automatic pad/CRC stripping 0 (disabled)
|
|
* ETH_MACCFG_RD Retry disable 1 (disabled)
|
|
* ETH_MACCFG_LM Loopback mode 0 (disabled)
|
|
* ETH_MACCFG_ROD Receive own disable 0 (enabled)
|
|
* ETH_MACCFG_CSD Carrier sense disable 0 (enabled)
|
|
* ETH_MACCFG_IFG Interframe gap 0 (96 bits)
|
|
* ETH_MACCFG_JD Jabber disable 0 (enabled)
|
|
* ETH_MACCFG_WD Watchdog disable 0 (enabled)
|
|
* ETH_MACCFG_CSTF CRC stripping for Type frames 0 (disabled, F2/F4 only)
|
|
*
|
|
* The following are set conditioinally based on mode and speed.
|
|
*
|
|
* ETH_MACCFG_DM Duplex mode Depends on priv->fduplex
|
|
* ETH_MACCFG_FES Fast Ethernet speed Depends on priv->mbps100
|
|
*/
|
|
|
|
# define MACCR_SET_BITS \
|
|
(ETH_MACCFG_BL_10 | ETH_MACCFG_RD | ETH_MACCFG_IFG(96))
|
|
|
|
|
|
/* Clear the MACCR bits that will be setup during MAC initialization (or that
|
|
* are cleared unconditionally). Per the reference manual, all reserved bits
|
|
* must be retained at their reset value.
|
|
*
|
|
* ETH_MACFFR_PM Bit 0: Promiscuous mode
|
|
* ETH_MACFFR_HU Bit 1: Hash unicast
|
|
* ETH_MACFFR_HM Bit 2: Hash multicast
|
|
* ETH_MACFFR_DAIF Bit 3: Destination address inverse filtering
|
|
* ETH_MACFFR_PAM Bit 4: Pass all multicast
|
|
* ETH_MACFFR_BFD Bit 5: Broadcast frames disable
|
|
* ETH_MACFFR_PCF Bits 6-7: Pass control frames
|
|
* ETH_MACFFR_SAIF Bit 8: Source address inverse filtering
|
|
* ETH_MACFFR_SAF Bit 9: Source address filter
|
|
* ETH_MACFFR_HPF Bit 10: Hash or perfect filter
|
|
* ETH_MACFFR_RA Bit 31: Receive all
|
|
*/
|
|
|
|
#define MACFFR_CLEAR_BITS \
|
|
(ETH_MACFFLT_PR | ETH_MACFFLT_HUC | ETH_MACFFLT_HMC | ETH_MACFFLT_DAIF | \
|
|
ETH_MACFFLT_PM | ETH_MACFFLT_DBF | ETH_MACFFLT_PCF_MASK | ETH_MACFFLT_HPF | \
|
|
ETH_MACFFLT_RA)
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
* ETH_MACFFLT_PM Promiscuous mode 0 (disabled)
|
|
* ETH_MACFFLT_HU Hash unicast 0 (perfect dest filtering)
|
|
* ETH_MACFFLT_HM Hash multicast 0 (perfect dest filtering)
|
|
* ETH_MACFFLT_DAIF Destination address inverse filtering 0 (normal)
|
|
* ETH_MACFFLT_PAM Pass all multicast 0 (Depends on HM bit)
|
|
* ETH_MACFFLT_BFD Broadcast frames disable 0 (enabled)
|
|
* ETH_MACFFLT_PCF Pass control frames 1 (block all but PAUSE)
|
|
* ETH_MACFFLT_HPF Hash or perfect filter 0 (Only matching frames passed)
|
|
* ETH_MACFFLT_RA Receive all 0 (disabled)
|
|
*/
|
|
|
|
#define MACFFR_SET_BITS (ETH_MACFFLT_PCF_PAUSE)
|
|
|
|
/* Clear the MACFCR bits that will be setup during MAC initialization (or that
|
|
* are cleared unconditionally). Per the reference manual, all reserved bits
|
|
* must be retained at their reset value.
|
|
*
|
|
* ETH_MACFCR_FCB_BPA Bit 0: Flow control busy/back pressure activate
|
|
* ETH_MACFCR_TFCE Bit 1: Transmit flow control enable
|
|
* ETH_MACFCR_RFCE Bit 2: Receive flow control enable
|
|
* ETH_MACFCR_UPFD Bit 3: Unicast pause frame detect
|
|
* ETH_MACFCR_PLT Bits 4-5: Pause low threshold
|
|
* ETH_MACFCR_ZQPD Bit 7: Zero-quanta pause disable
|
|
* ETH_MACFCR_PT Bits 16-31: Pause time
|
|
*/
|
|
|
|
#define MACFCR_CLEAR_MASK \
|
|
(ETH_MACFC_FCB | ETH_MACFC_TFE | ETH_MACFC_RFE | ETH_MACFC_UP | \
|
|
ETH_MACFC_PLT_MASK | ETH_MACFC_DZPQ | ETH_MACFC_PT_MASK)
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
* ETH_MACFCR_FCB_BPA Flow control busy/back pressure activate 0 (no pause control frame)
|
|
* ETH_MACFCR_TFCE Transmit flow control enable 0 (disabled)
|
|
* ETH_MACFCR_RFCE Receive flow control enable 0 (disabled)
|
|
* ETH_MACFCR_UPFD Unicast pause frame detect 0 (disabled)
|
|
* ETH_MACFCR_PLT Pause low threshold 0 (pause time - 4)
|
|
* ETH_MACFCR_ZQPD Zero-quanta pause disable 1 (disabled)
|
|
* ETH_MACFCR_PT Pause time 0
|
|
*/
|
|
|
|
#define MACFCR_SET_MASK (ETH_MACFC_PLT(4) | ETH_MACFC_DZPQ)
|
|
|
|
/* Clear the DMAOMR bits that will be setup during MAC initialization (or that
|
|
* are cleared unconditionally). Per the reference manual, all reserved bits
|
|
* must be retained at their reset value.
|
|
*
|
|
* ETH_DMAOPMODE_SR Bit 1: Start/stop receive
|
|
* TH_DMAOMR_OSF Bit 2: Operate on second frame
|
|
* ETH_DMAOPMODE_RTC Bits 3-4: Receive threshold control
|
|
* ETH_DMAOPMODE_FUGF Bit 6: Forward undersized good frames
|
|
* ETH_DMAOPMODE_FEF Bit 7: Forward error frames
|
|
* ETH_DMAOPMODE_ST Bit 13: Start/stop transmission
|
|
* ETH_DMAOPMODE_TTC Bits 14-16: Transmit threshold control
|
|
* ETH_DMAOPMODE_FTF Bit 20: Flush transmit FIFO
|
|
* ETH_DMAOPMODE_TSF Bit 21: Transmit store and forward
|
|
* ETH_DMAOPMODE_DFRF Bit 24: Disable flushing of received frames
|
|
* ETH_DMAOPMODE_RSF Bit 25: Receive store and forward
|
|
* TH_DMAOMR_DTCEFD Bit 26: Dropping of TCP/IP checksum error frames disable
|
|
*/
|
|
|
|
#define DMAOMR_CLEAR_MASK \
|
|
(ETH_DMAOPMODE_SR | ETH_DMAOPMODE_OSF | ETH_DMAOPMODE_RTC_MASK | ETH_DMAOPMODE_FUF | \
|
|
ETH_DMAOPMODE_FEF | ETH_DMAOPMODE_ST | ETH_DMAOPMODE_TTC_MASK | ETH_DMAOPMODE_FTF | \
|
|
ETH_DMAOPMODE_DFF)
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
* ETH_DMAOPMODE_SR Start/stop receive 0 (not running)
|
|
* TH_DMAOMR_OSF Operate on second frame 1 (enabled)
|
|
* ETH_DMAOPMODE_RTC Receive threshold control 0 (64 bytes)
|
|
* ETH_DMAOPMODE_FUGF Forward undersized good frames 0 (disabled)
|
|
* ETH_DMAOPMODE_FEF Forward error frames 0 (disabled)
|
|
* ETH_DMAOPMODE_ST Start/stop transmission 0 (not running)
|
|
* ETH_DMAOPMODE_TTC Transmit threshold control 0 (64 bytes)
|
|
* ETH_DMAOPMODE_FTF Flush transmit FIFO 0 (no flush)
|
|
* ETH_DMAOPMODE_TSF Transmit store and forward Depends on CONFIG_LPC43_ETH_HWCHECKSUM
|
|
* ETH_DMAOPMODE_DFRF Disable flushing of received frames 0 (enabled)
|
|
* ETH_DMAOPMODE_RSF Receive store and forward Depends on CONFIG_LPC43_ETH_HWCHECKSUM
|
|
* TH_DMAOMR_DTCEFD Dropping of TCP/IP checksum error Depends on CONFIG_LPC43_ETH_HWCHECKSUM
|
|
* frames disable
|
|
*
|
|
* When the checksum offload feature is enabled, we need to enable the Store
|
|
* and Forward mode: the store and forward guarantee that a whole frame is
|
|
* stored in the FIFO, so the MAC can insert/verify the checksum, if the
|
|
* checksum is OK the DMA can handle the frame otherwise the frame is dropped
|
|
*/
|
|
|
|
#if CONFIG_LPC43_ETH_HWCHECKSUM
|
|
# define DMAOMR_SET_MASK \
|
|
(ETH_DMAOPMODE_OSF | ETH_DMAOPMODE_RTC_64 | ETH_DMAOPMODE_TTC_64 | \
|
|
ETH_DMAOPMODE_TSF | ETH_DMAOPMODE_RSF)
|
|
#else
|
|
# define DMAOMR_SET_MASK \
|
|
(ETH_DMAOPMODE_OSF | ETH_DMAOPMODE_RTC_64 | ETH_DMAOPMODE_TTC_64)
|
|
#endif
|
|
|
|
/* Clear the DMABMR bits that will be setup during MAC initialization (or that
|
|
* are cleared unconditionally). Per the reference manual, all reserved bits
|
|
* must be retained at their reset value.
|
|
*
|
|
* ETH_DMABMODE_SR Bit 0: Software reset
|
|
* ETH_DMABMODE_DA Bit 1: DMA Arbitration
|
|
* ETH_DMABMODE_DSL Bits 2-6: Descriptor skip length
|
|
* ETH_DMABMODE_ATDS Bit 7: Enhanced descriptor format enable
|
|
* ETH_DMABMODE_PBL Bits 8-13: Programmable burst length
|
|
* ETH_DMABMODE_RTPR Bits 14-15: RX TX priority ratio
|
|
* ETH_DMABMODE_FB Bit 16: Fixed burst
|
|
* ETH_DMABMODE_RDP Bits 17-22: RX DMA PBL
|
|
* ETH_DMABMODE_USP Bit 23: Use separate PBL
|
|
* ETH_DMABMODE_FPM Bit 24: 4xPBL mode
|
|
* ETH_DMABMODE_AAB Bit 25: Address-aligned beats
|
|
* ETH_DMABMODE_MB Bit 26: Mixed burst (F2/F4 only)
|
|
*/
|
|
|
|
#define DMABMR_CLEAR_MASK \
|
|
(ETH_DMABMODE_SWR | ETH_DMABMODE_DA | ETH_DMABMODE_DSL_MASK | ETH_DMABMODE_ATDS | \
|
|
ETH_DMABMODE_PBL_MASK | ETH_DMABMODE_PR_MASK | ETH_DMABMODE_FB | ETH_DMABMODE_RPBL_MASK | \
|
|
ETH_DMABMODE_USP | ETH_DMABMODE_PBL8X | ETH_DMABMODE_AAL | ETH_DMABMODE_MB | ETH_DMABMODE_TXPR)
|
|
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
*
|
|
* ETH_DMABMODE_SR Software reset 0 (no reset)
|
|
* ETH_DMABMODE_DA DMA Arbitration 0 (round robin)
|
|
* ETH_DMABMODE_DSL Descriptor skip length 0
|
|
* ETH_DMABMODE_ATDS Enhanced descriptor format enable Depends on CONFIG_LPC43_ETH_ENHANCEDDESC
|
|
* ETH_DMABMODE_PBL Programmable burst length 32 beats
|
|
* ETH_DMABMODE_RTPR RX TX priority ratio 2:1
|
|
* ETH_DMABMODE_FB Fixed burst 1 (enabled)
|
|
* ETH_DMABMODE_RDP RX DMA PBL 32 beats
|
|
* ETH_DMABMODE_USP Use separate PBL 1 (enabled)
|
|
* ETH_DMABMODE_FPM 4xPBL mode 0 (disabled)
|
|
* ETH_DMABMODE_AAB Address-aligned beats 1 (enabled)
|
|
* ETH_DMABMODE_MB Mixed burst 0 (disabled, F2/F4 only)
|
|
*/
|
|
|
|
#ifdef CONFIG_LPC43_ETH_ENHANCEDDESC
|
|
# define DMABMR_SET_MASK \
|
|
(ETH_DMABMODE_DSL(0) | ETH_DMABMODE_PBL(32) | ETH_DMABMODE_ATDS | ETH_DMABMODE_RTPR_2TO1 | \
|
|
ETH_DMABMODE_FB | ETH_DMABMODE_RPBL(32) | ETH_DMABMODE_USP | ETH_DMABMODE_AAB)
|
|
#else
|
|
# define DMABMR_SET_MASK \
|
|
(ETH_DMABMODE_DSL(0) | ETH_DMABMODE_PBL(32) | ETH_DMABMODE_PR_2TO1 | ETH_DMABMODE_FB | \
|
|
ETH_DMABMODE_RPBL(32) | ETH_DMABMODE_USP | ETH_DMABMODE_AAL)
|
|
#endif
|
|
|
|
/* Interrupt bit sets *******************************************************/
|
|
/* All interrupts in the normal and abnormal interrupt summary. Early transmit
|
|
* interrupt (ETI) is excluded from the abnormal set because it causes too
|
|
* many interrupts and is not interesting.
|
|
*/
|
|
|
|
#define ETH_DMAINT_NORMAL \
|
|
(ETH_DMAINT_TI | ETH_DMAINT_TU | ETH_DMAINT_RI | ETH_DMAINT_ERI)
|
|
|
|
#define ETH_DMAINT_ABNORMAL \
|
|
(ETH_DMAINT_TPS | ETH_DMAINT_TJT | ETH_DMAINT_OVF | ETH_DMAINT_UNF | \
|
|
ETH_DMAINT_RU | ETH_DMAINT_RPS | ETH_DMAINT_RWT | /* ETH_DMAINT_ETI | */ \
|
|
ETH_DMAINT_FBI)
|
|
|
|
/* Normal receive, transmit, error interrupt enable bit sets */
|
|
|
|
#define ETH_DMAINT_RECV_ENABLE (ETH_DMAINT_NIS | ETH_DMAINT_RI)
|
|
#define ETH_DMAINT_XMIT_ENABLE (ETH_DMAINT_NIS | ETH_DMAINT_TI)
|
|
#define ETH_DMAINT_XMIT_DISABLE (ETH_DMAINT_TI)
|
|
|
|
#ifdef CONFIG_DEBUG_NET
|
|
# define ETH_DMAINT_ERROR_ENABLE (ETH_DMAINT_AIS | ETH_DMAINT_ABNORMAL)
|
|
#else
|
|
# define ETH_DMAINT_ERROR_ENABLE (0)
|
|
#endif
|
|
|
|
/* Helpers ******************************************************************/
|
|
/* This is a helper pointer for accessing the contents of the Ethernet
|
|
* header
|
|
*/
|
|
|
|
#define BUF ((struct eth_hdr_s *)priv->dev.d_buf)
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The lpc43_ethmac_s encapsulates all state information for a single hardware
|
|
* interface
|
|
*/
|
|
|
|
struct lpc43_ethmac_s
|
|
{
|
|
uint8_t ifup : 1; /* true:ifup false:ifdown */
|
|
uint8_t mbps100 : 1; /* 100MBps operation (vs 10 MBps) */
|
|
uint8_t fduplex : 1; /* Full (vs. half) duplex */
|
|
WDOG_ID txpoll; /* TX poll timer */
|
|
WDOG_ID txtimeout; /* TX timeout timer */
|
|
#ifdef CONFIG_NET_NOINTS
|
|
struct work_s work; /* For deferring work to the work queue */
|
|
#endif
|
|
|
|
/* This holds the information visible to uIP/NuttX */
|
|
|
|
struct net_driver_s dev; /* Interface understood by uIP */
|
|
|
|
/* Used to track transmit and receive descriptors */
|
|
|
|
struct eth_txdesc_s *txhead; /* Next available TX descriptor */
|
|
struct eth_rxdesc_s *rxhead; /* Next available RX descriptor */
|
|
|
|
struct eth_txdesc_s *txtail; /* First "in_flight" TX descriptor */
|
|
struct eth_rxdesc_s *rxcurr; /* First RX descriptor of the segment */
|
|
uint16_t segments; /* RX segment count */
|
|
uint16_t inflight; /* Number of TX transfers "in_flight" */
|
|
sq_queue_t freeb; /* The free buffer list */
|
|
|
|
/* Descriptor allocations */
|
|
|
|
struct eth_rxdesc_s rxtable[CONFIG_LPC43_ETH_NRXDESC];
|
|
struct eth_txdesc_s txtable[CONFIG_LPC43_ETH_NTXDESC];
|
|
|
|
/* Buffer allocations */
|
|
|
|
uint8_t rxbuffer[CONFIG_LPC43_ETH_NRXDESC*CONFIG_LPC43_ETH_BUFSIZE];
|
|
uint8_t alloc[LPC43_ETH_NFREEBUFFERS*CONFIG_LPC43_ETH_BUFSIZE];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct lpc43_ethmac_s g_lpc43ethmac;
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
/* Register operations ******************************************************/
|
|
|
|
#if defined(CONFIG_LPC43_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG)
|
|
static uint32_t lpc43_getreg(uint32_t addr);
|
|
static void lpc43_putreg(uint32_t val, uint32_t addr);
|
|
static void lpc43_checksetup(void);
|
|
#else
|
|
# define lpc43_getreg(addr) getreg32(addr)
|
|
# define lpc43_putreg(val,addr) putreg32(val,addr)
|
|
# define lpc43_checksetup()
|
|
#endif
|
|
|
|
/* Free buffer management */
|
|
|
|
static void lpc43_initbuffer(FAR struct lpc43_ethmac_s *priv);
|
|
static inline uint8_t *lpc43_allocbuffer(FAR struct lpc43_ethmac_s *priv);
|
|
static inline void lpc43_freebuffer(FAR struct lpc43_ethmac_s *priv, uint8_t *buffer);
|
|
static inline bool lpc43_isfreebuffer(FAR struct lpc43_ethmac_s *priv);
|
|
|
|
/* Common TX logic */
|
|
|
|
static int lpc43_transmit(FAR struct lpc43_ethmac_s *priv);
|
|
static int lpc43_txpoll(struct net_driver_s *dev);
|
|
static void lpc43_dopoll(FAR struct lpc43_ethmac_s *priv);
|
|
|
|
/* Interrupt handling */
|
|
|
|
static void lpc43_enableint(FAR struct lpc43_ethmac_s *priv, uint32_t ierbit);
|
|
static void lpc43_disableint(FAR struct lpc43_ethmac_s *priv, uint32_t ierbit);
|
|
|
|
static void lpc43_freesegment(FAR struct lpc43_ethmac_s *priv,
|
|
FAR struct eth_rxdesc_s *rxfirst, int segments);
|
|
static int lpc43_recvframe(FAR struct lpc43_ethmac_s *priv);
|
|
static void lpc43_receive(FAR struct lpc43_ethmac_s *priv);
|
|
static void lpc43_freeframe(FAR struct lpc43_ethmac_s *priv);
|
|
static void lpc43_txdone(FAR struct lpc43_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_interrupt_work(FAR void *arg);
|
|
#endif
|
|
static int lpc43_interrupt(int irq, FAR void *context);
|
|
|
|
/* Watchdog timer expirations */
|
|
|
|
static inline void lpc43_txtimeout_process(FAR struct lpc43_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_txtimeout_work(FAR void *arg);
|
|
#endif
|
|
static void lpc43_txtimeout_expiry(int argc, uint32_t arg, ...);
|
|
|
|
static inline void lpc43_poll_process(FAR struct lpc43_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_poll_work(FAR void *arg);
|
|
#endif
|
|
static void lpc43_poll_expiry(int argc, uint32_t arg, ...);
|
|
|
|
/* NuttX callback functions */
|
|
|
|
static int lpc43_ifup(struct net_driver_s *dev);
|
|
static int lpc43_ifdown(struct net_driver_s *dev);
|
|
static inline void lpc43_txavail_process(FAR struct lpc43_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_txavail_work(FAR void *arg);
|
|
#endif
|
|
static int lpc43_txavail(struct net_driver_s *dev);
|
|
#if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6)
|
|
static int lpc43_addmac(struct net_driver_s *dev, FAR const uint8_t *mac);
|
|
#endif
|
|
#ifdef CONFIG_NET_IGMP
|
|
static int lpc43_rmmac(struct net_driver_s *dev, FAR const uint8_t *mac);
|
|
#endif
|
|
#ifdef CONFIG_NETDEV_PHY_IOCTL
|
|
static int lpc43_ioctl(struct net_driver_s *dev, int cmd, long arg);
|
|
#endif
|
|
/* Descriptor Initialization */
|
|
|
|
static void lpc43_txdescinit(FAR struct lpc43_ethmac_s *priv);
|
|
static void lpc43_rxdescinit(FAR struct lpc43_ethmac_s *priv);
|
|
|
|
/* PHY Initialization */
|
|
#if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT)
|
|
static int lpc43_phyintenable(FAR struct lpc43_ethmac_s *priv);
|
|
#endif
|
|
static int lpc43_phyread(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t *value);
|
|
static int lpc43_phywrite(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t value);
|
|
#ifdef CONFIG_ETH0_PHY_DM9161
|
|
static inline int lpc43_dm9161(FAR struct lpc43_ethmac_s *priv);
|
|
#endif
|
|
static int lpc43_phyinit(FAR struct lpc43_ethmac_s *priv);
|
|
|
|
/* MAC/DMA Initialization */
|
|
|
|
#ifdef CONFIG_LPC43_MII
|
|
static inline void lpc43_selectmii(void);
|
|
#endif
|
|
#ifdef CONFIG_LPC43_RMII
|
|
static inline void lpc43_selectrmii(void);
|
|
#endif
|
|
static inline void lpc43_ethgpioconfig(FAR struct lpc43_ethmac_s *priv);
|
|
static void lpc43_ethreset(FAR struct lpc43_ethmac_s *priv);
|
|
static int lpc43_macconfig(FAR struct lpc43_ethmac_s *priv);
|
|
static void lpc43_macaddress(FAR struct lpc43_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
static void lpc43_ipv6multicast(FAR struct lpc43_ethmac_s *priv);
|
|
#endif
|
|
static int lpc43_macenable(FAR struct lpc43_ethmac_s *priv);
|
|
static int lpc43_ethconfig(FAR struct lpc43_ethmac_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: lpc43_getreg
|
|
*
|
|
* Description:
|
|
* This function may to used to intercept an monitor all register accesses.
|
|
* Clearly this is nothing you would want to do unless you are debugging
|
|
* this driver.
|
|
*
|
|
* Input Parameters:
|
|
* addr - The register address to read
|
|
*
|
|
* Returned Value:
|
|
* The value read from the register
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_LPC43_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG)
|
|
static uint32_t lpc43_getreg(uint32_t addr)
|
|
{
|
|
static uint32_t prevaddr = 0;
|
|
static uint32_t preval = 0;
|
|
static uint32_t count = 0;
|
|
|
|
/* Read the value from the register */
|
|
|
|
uint32_t val = getreg32(addr);
|
|
|
|
/* Is this the same value that we read from the same register last time?
|
|
* Are we polling the register? If so, suppress some of the output.
|
|
*/
|
|
|
|
if (addr == prevaddr && val == preval)
|
|
{
|
|
if (count == 0xffffffff || ++count > 3)
|
|
{
|
|
if (count == 4)
|
|
{
|
|
lldbg("...\n");
|
|
}
|
|
|
|
return val;
|
|
}
|
|
}
|
|
|
|
/* No this is a new address or value */
|
|
|
|
else
|
|
{
|
|
/* Did we print "..." for the previous value? */
|
|
|
|
if (count > 3)
|
|
{
|
|
/* Yes.. then show how many times the value repeated */
|
|
|
|
lldbg("[repeats %d more times]\n", count-3);
|
|
}
|
|
|
|
/* Save the new address, value, and count */
|
|
|
|
prevaddr = addr;
|
|
preval = val;
|
|
count = 1;
|
|
}
|
|
|
|
/* Show the register value read */
|
|
|
|
lldbg("%08x->%08x\n", addr, val);
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: lpc43_putreg
|
|
*
|
|
* Description:
|
|
* This function may to used to intercept an monitor all register accesses.
|
|
* Clearly this is nothing you would want to do unless you are debugging
|
|
* this driver.
|
|
*
|
|
* Input Parameters:
|
|
* val - The value to write to the register
|
|
* addr - The register address to read
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_LPC43_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG)
|
|
static void lpc43_putreg(uint32_t val, uint32_t addr)
|
|
{
|
|
/* Show the register value being written */
|
|
|
|
lldbg("%08x<-%08x\n", addr, val);
|
|
|
|
/* Write the value */
|
|
|
|
putreg32(val, addr);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: lpc43_checksetup
|
|
*
|
|
* Description:
|
|
* Show the state of critical configuration registers.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_LPC43_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG)
|
|
static void lpc43_checksetup(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_initbuffer
|
|
*
|
|
* Description:
|
|
* Initialize the free buffer list.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Called during early driver initialization before Ethernet interrupts
|
|
* are enabled.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_initbuffer(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
uint8_t *buffer;
|
|
int i;
|
|
|
|
/* Initialize the head of the free buffer list */
|
|
|
|
sq_init(&priv->freeb);
|
|
|
|
/* Add all of the pre-allocated buffers to the free buffer list */
|
|
|
|
for (i = 0, buffer = priv->alloc;
|
|
i < LPC43_ETH_NFREEBUFFERS;
|
|
i++, buffer += CONFIG_LPC43_ETH_BUFSIZE)
|
|
{
|
|
sq_addlast((FAR sq_entry_t *)buffer, &priv->freeb);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_allocbuffer
|
|
*
|
|
* Description:
|
|
* Allocate one buffer from the free buffer list.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* Pointer to the allocated buffer on success; NULL 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 inline uint8_t *lpc43_allocbuffer(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
/* Allocate a buffer by returning the head of the free buffer list */
|
|
|
|
return (uint8_t *)sq_remfirst(&priv->freeb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_freebuffer
|
|
*
|
|
* Description:
|
|
* Return a buffer to the free buffer list.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* buffer - A pointer to the buffer to be freed
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* 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 inline void lpc43_freebuffer(FAR struct lpc43_ethmac_s *priv, uint8_t *buffer)
|
|
{
|
|
/* Free the buffer by adding it to to the end of the free buffer list */
|
|
|
|
sq_addlast((FAR sq_entry_t *)buffer, &priv->freeb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_isfreebuffer
|
|
*
|
|
* Description:
|
|
* Return TRUE if the free buffer list is not empty.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* True if there are one or more buffers in the free buffer list;
|
|
* false if the free buffer list is empty
|
|
*
|
|
* Assumptions:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline bool lpc43_isfreebuffer(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
/* Return TRUE if the free buffer list is not empty */
|
|
|
|
return !sq_empty(&priv->freeb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_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 lpc43_transmit(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
struct eth_txdesc_s *txdesc;
|
|
struct eth_txdesc_s *txfirst;
|
|
|
|
/* The internal (optimal) uIP buffer size may be configured to be larger
|
|
* than the Ethernet buffer size.
|
|
*/
|
|
|
|
#if OPTIMAL_ETH_BUFSIZE > CONFIG_LPC43_ETH_BUFSIZE
|
|
uint8_t *buffer;
|
|
int bufcount;
|
|
int lastsize;
|
|
int i;
|
|
#endif
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
txdesc = priv->txhead;
|
|
txfirst = txdesc;
|
|
|
|
nllvdbg("d_len: %d d_buf: %p txhead: %p tdes0: %08x\n",
|
|
priv->dev.d_len, priv->dev.d_buf, txdesc, txdesc->tdes0);
|
|
|
|
DEBUGASSERT(txdesc && (txdesc->tdes0 & ETH_TDES0_OWN) == 0);
|
|
|
|
/* Is the size to be sent greater than the size of the Ethernet buffer? */
|
|
|
|
DEBUGASSERT(priv->dev.d_len > 0 && priv->dev.d_buf != NULL);
|
|
|
|
#if OPTIMAL_ETH_BUFSIZE > CONFIG_LPC43_ETH_BUFSIZE
|
|
if (priv->dev.d_len > CONFIG_LPC43_ETH_BUFSIZE)
|
|
{
|
|
/* Yes... how many buffers will be need to send the packet? */
|
|
|
|
bufcount = (priv->dev.d_len + (CONFIG_LPC43_ETH_BUFSIZE-1)) / CONFIG_LPC43_ETH_BUFSIZE;
|
|
lastsize = priv->dev.d_len - (bufcount - 1) * CONFIG_LPC43_ETH_BUFSIZE;
|
|
|
|
nllvdbg("bufcount: %d lastsize: %d\n", bufcount, lastsize);
|
|
|
|
/* Set the first segment bit in the first TX descriptor */
|
|
|
|
txdesc->tdes0 |= ETH_TDES0_FS;
|
|
|
|
/* Set up all but the last TX descriptor */
|
|
|
|
buffer = priv->dev.d_buf;
|
|
|
|
for (i = 0; i < bufcount; i++)
|
|
{
|
|
/* This could be a normal event but the design does not handle it */
|
|
|
|
DEBUGASSERT((txdesc->tdes0 & ETH_TDES0_OWN) == 0);
|
|
|
|
/* Set the Buffer1 address pointer */
|
|
|
|
txdesc->tdes2 = (uint32_t)buffer;
|
|
|
|
/* Set the buffer size in all TX descriptors */
|
|
|
|
if (i == (bufcount-1))
|
|
{
|
|
/* This is the last segment. Set the last segment bit in the
|
|
* last TX descriptor and ask for an interrupt when this
|
|
* segment transfer completes.
|
|
*/
|
|
|
|
txdesc->tdes0 |= (ETH_TDES0_LS | ETH_TDES0_IC);
|
|
|
|
/* This segement is, most likely, of fractional buffersize */
|
|
|
|
txdesc->tdes1 = lastsize;
|
|
buffer += lastsize;
|
|
}
|
|
else
|
|
{
|
|
/* This is not the last segment. We don't want an interrupt
|
|
* when this segment transfer completes.
|
|
*/
|
|
|
|
txdesc->tdes0 &= ~ETH_TDES0_IC;
|
|
|
|
/* The size of the transfer is the whole buffer */
|
|
|
|
txdesc->tdes1 = CONFIG_LPC43_ETH_BUFSIZE;
|
|
buffer += CONFIG_LPC43_ETH_BUFSIZE;
|
|
}
|
|
|
|
/* Give the descriptor to DMA */
|
|
|
|
txdesc->tdes0 |= ETH_TDES0_OWN;
|
|
txdesc = (struct eth_txdesc_s *)txdesc->tdes3;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* The single descriptor is both the first and last segment. And we do
|
|
* want an interrupt when the transfer completes.
|
|
*/
|
|
|
|
txdesc->tdes0 |= (ETH_TDES0_FS | ETH_TDES0_LS | ETH_TDES0_IC);
|
|
|
|
/* Set frame size */
|
|
|
|
DEBUGASSERT(priv->dev.d_len <= CONFIG_NET_ETH_MTU);
|
|
txdesc->tdes1 = priv->dev.d_len;
|
|
|
|
/* Set the Buffer1 address pointer */
|
|
|
|
txdesc->tdes2 = (uint32_t)priv->dev.d_buf;
|
|
|
|
/* Set OWN bit of the TX descriptor tdes0. This gives the buffer to
|
|
* Ethernet DMA
|
|
*/
|
|
|
|
txdesc->tdes0 |= ETH_TDES0_OWN;
|
|
|
|
/* Point to the next available TX descriptor */
|
|
|
|
txdesc = (struct eth_txdesc_s *)txdesc->tdes3;
|
|
}
|
|
|
|
/* Remember where we left off in the TX descriptor chain */
|
|
|
|
priv->txhead = txdesc;
|
|
|
|
/* Detach the buffer from priv->dev structure. That buffer is now
|
|
* "in-flight".
|
|
*/
|
|
|
|
priv->dev.d_buf = NULL;
|
|
priv->dev.d_len = 0;
|
|
|
|
/* If there is no other TX buffer, in flight, then remember the location
|
|
* of the TX descriptor. This is the location to check for TX done events.
|
|
*/
|
|
|
|
if (!priv->txtail)
|
|
{
|
|
DEBUGASSERT(priv->inflight == 0);
|
|
priv->txtail = txfirst;
|
|
}
|
|
|
|
/* Increment the number of TX transfer in-flight */
|
|
|
|
priv->inflight++;
|
|
|
|
nllvdbg("txhead: %p txtail: %p inflight: %d\n",
|
|
priv->txhead, priv->txtail, priv->inflight);
|
|
|
|
/* If all TX descriptors are in-flight, then we have to disable receive interrupts
|
|
* too. This is because receive events can trigger more un-stoppable transmit
|
|
* events.
|
|
*/
|
|
|
|
if (priv->inflight >= CONFIG_LPC43_ETH_NTXDESC)
|
|
{
|
|
lpc43_disableint(priv, ETH_DMAINT_RI);
|
|
}
|
|
|
|
/* Check if the TX Buffer unavailable flag is set */
|
|
|
|
if ((lpc43_getreg(LPC43_ETH_DMASTAT) & ETH_DMAINT_TU) != 0)
|
|
{
|
|
/* Clear TX Buffer unavailable flag */
|
|
|
|
lpc43_putreg(ETH_DMAINT_TU, LPC43_ETH_DMASTAT);
|
|
|
|
/* Resume DMA transmission */
|
|
|
|
lpc43_putreg(0, LPC43_ETH_DMATXPD);
|
|
}
|
|
|
|
/* Enable TX interrupts */
|
|
|
|
lpc43_enableint(priv, ETH_DMAINT_TI);
|
|
|
|
/* Setup the TX timeout watchdog (perhaps restarting the timer) */
|
|
|
|
(void)wd_start(priv->txtimeout, LPC43_TXTIMEOUT, lpc43_txtimeout_expiry, 1, (uint32_t)priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_txpoll
|
|
*
|
|
* Description:
|
|
* The transmitter is available, check if uIP has any outgoing packets ready
|
|
* to send. This is a callback from devif_poll(). devif_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 lpc43_txpoll(struct net_driver_s *dev)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)dev->d_private;
|
|
|
|
DEBUGASSERT(priv->dev.d_buf != NULL);
|
|
|
|
/* 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->dev.d_len > 0)
|
|
{
|
|
/* Look up the destination MAC address and add it to the Ethernet
|
|
* header.
|
|
*/
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (IFF_IS_IPv4(priv->dev.d_flags))
|
|
#endif
|
|
{
|
|
arp_out(&priv->dev);
|
|
}
|
|
#endif /* CONFIG_NET_IPv4 */
|
|
|
|
#ifdef CONFIG_NET_IPv6
|
|
#ifdef CONFIG_NET_IPv4
|
|
else
|
|
#endif
|
|
{
|
|
neighbor_out(&priv->dev);
|
|
}
|
|
#endif /* CONFIG_NET_IPv6 */
|
|
|
|
/* Send the packet */
|
|
|
|
lpc43_transmit(priv);
|
|
DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL);
|
|
|
|
/* Check if the next TX descriptor is owned by the Ethernet DMA or CPU. We
|
|
* cannot perform the TX poll if we are unable to accept another packet for
|
|
* transmission.
|
|
*
|
|
* In a race condition, ETH_TDES0_OWN may be cleared BUT still not available
|
|
* because lpc43_freeframe() has not yet run. If lpc43_freeframe() has run,
|
|
* the buffer1 pointer (tdes2) will be nullified (and inflight should be <
|
|
* CONFIG_LPC43_ETH_NTXDESC).
|
|
*/
|
|
|
|
if ((priv->txhead->tdes0 & ETH_TDES0_OWN) != 0 ||
|
|
priv->txhead->tdes2 != 0)
|
|
{
|
|
/* We have to terminate the poll if we have no more descriptors
|
|
* available for another transfer.
|
|
*/
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* We have the descriptor, we can continue the poll. Allocate a new
|
|
* buffer for the poll.
|
|
*/
|
|
|
|
dev->d_buf = lpc43_allocbuffer(priv);
|
|
|
|
/* We can't continue the poll if we have no buffers */
|
|
|
|
if (dev->d_buf == NULL)
|
|
{
|
|
/* Terminate the poll. */
|
|
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
/* If zero is returned, the polling will continue until all connections have
|
|
* been examined.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_dopoll
|
|
*
|
|
* Description:
|
|
* The function is called in order to perform an out-of-sequence TX poll.
|
|
* This is done:
|
|
*
|
|
* 1. After completion of a transmission (lpc43_txdone),
|
|
* 2. When new TX data is available (lpc43_txavail_process), and
|
|
* 3. After a TX timeout to restart the sending process
|
|
* (lpc43_txtimeout_process).
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_dopoll(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Check if the next TX descriptor is owned by the Ethernet DMA or
|
|
* CPU. We cannot perform the TX poll if we are unable to accept
|
|
* another packet for transmission.
|
|
*
|
|
* In a race condition, ETH_TDES0_OWN may be cleared BUT still not available
|
|
* because lpc43_freeframe() has not yet run. If lpc43_freeframe() has run,
|
|
* the buffer1 pointer (tdes2) will be nullified (and inflight should be <
|
|
* CONFIG_LPC43_ETH_NTXDESC).
|
|
*/
|
|
|
|
if ((priv->txhead->tdes0 & ETH_TDES0_OWN) == 0 &&
|
|
priv->txhead->tdes2 == 0)
|
|
{
|
|
/* If we have the descriptor, then poll for new XMIT data.
|
|
* Allocate a buffer for the poll.
|
|
*/
|
|
|
|
DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL);
|
|
dev->d_buf = lpc43_allocbuffer(priv);
|
|
|
|
/* We can't poll if we have no buffers */
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
(void)devif_poll(dev, lpc43_txpoll);
|
|
|
|
/* We will, most likely end up with a buffer to be freed. But it
|
|
* might not be the same one that we allocated above.
|
|
*/
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
DEBUGASSERT(dev->d_len == 0);
|
|
lpc43_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_enableint
|
|
*
|
|
* Description:
|
|
* Enable a "normal" interrupt
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_enableint(FAR struct lpc43_ethmac_s *priv, uint32_t ierbit)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Enable the specified "normal" interrupt */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMAINTEN);
|
|
regval |= (ETH_DMAINT_NIS | ierbit);
|
|
lpc43_putreg(regval, LPC43_ETH_DMAINTEN);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_disableint
|
|
*
|
|
* Description:
|
|
* Disable a normal interrupt.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_disableint(FAR struct lpc43_ethmac_s *priv, uint32_t ierbit)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Disable the "normal" interrupt */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMAINTEN);
|
|
regval &= ~ierbit;
|
|
|
|
/* Are all "normal" interrupts now disabled? */
|
|
|
|
if ((regval & ETH_DMAINT_NORMAL) == 0)
|
|
{
|
|
/* Yes.. disable normal interrupts */
|
|
|
|
regval &= ~ETH_DMAINT_NIS;
|
|
}
|
|
|
|
lpc43_putreg(regval, LPC43_ETH_DMAINTEN);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_freesegment
|
|
*
|
|
* Description:
|
|
* The function is called when a frame is received using the DMA receive
|
|
* interrupt. It scans the RX descriptors to the received frame.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_freesegment(FAR struct lpc43_ethmac_s *priv,
|
|
FAR struct eth_rxdesc_s *rxfirst, int segments)
|
|
{
|
|
struct eth_rxdesc_s *rxdesc;
|
|
int i;
|
|
|
|
nllvdbg("rxfirst: %p segments: %d\n", rxfirst, segments);
|
|
|
|
/* Set OWN bit in RX descriptors. This gives the buffers back to DMA */
|
|
|
|
rxdesc = rxfirst;
|
|
for (i = 0; i < segments; i++)
|
|
{
|
|
rxdesc->rdes0 = ETH_RDES0_OWN;
|
|
rxdesc = (struct eth_rxdesc_s *)rxdesc->rdes3;
|
|
}
|
|
|
|
/* Reset the segment management logic */
|
|
|
|
priv->rxcurr = NULL;
|
|
priv->segments = 0;
|
|
|
|
/* Check if the RX Buffer unavailable flag is set */
|
|
|
|
if ((lpc43_getreg(LPC43_ETH_DMASTAT) & ETH_DMAINT_RU) != 0)
|
|
{
|
|
/* Clear RBUS Ethernet DMA flag */
|
|
|
|
lpc43_putreg(ETH_DMAINT_RU, LPC43_ETH_DMASTAT);
|
|
|
|
/* Resume DMA reception */
|
|
|
|
lpc43_putreg(0, LPC43_ETH_DMARXPD);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_recvframe
|
|
*
|
|
* Description:
|
|
* The function is called when a frame is received using the DMA receive
|
|
* interrupt. It scans the RX descriptors of the received frame.
|
|
*
|
|
* NOTE: This function will silently discard any packets containing errors.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK if a packet was successfully returned; -EAGAIN if there are no
|
|
* further packets available
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_recvframe(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
struct eth_rxdesc_s *rxdesc;
|
|
struct eth_rxdesc_s *rxcurr;
|
|
uint8_t *buffer;
|
|
int i;
|
|
|
|
nllvdbg("rxhead: %p rxcurr: %p segments: %d\n",
|
|
priv->rxhead, priv->rxcurr, priv->segments);
|
|
|
|
/* Check if there are free buffers. We cannot receive new frames in this
|
|
* design unless there is at least one free buffer.
|
|
*/
|
|
|
|
if (!lpc43_isfreebuffer(priv))
|
|
{
|
|
nlldbg("No free buffers\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Scan descriptors owned by the CPU. Scan until:
|
|
*
|
|
* 1) We find a descriptor still owned by the DMA,
|
|
* 2) We have examined all of the RX descriptors, or
|
|
* 3) All of the TX descriptors are in flight.
|
|
*
|
|
* This last case is obscure. It is due to that fact that each packet
|
|
* that we receive can generate an unstoppable transmission. So we have
|
|
* to stop receiving when we can not longer transmit. In this case, the
|
|
* transmit logic should also have disabled further RX interrupts.
|
|
*/
|
|
|
|
rxdesc = priv->rxhead;
|
|
for (i = 0;
|
|
(rxdesc->rdes0 & ETH_RDES0_OWN) == 0 &&
|
|
i < CONFIG_LPC43_ETH_NRXDESC &&
|
|
priv->inflight < CONFIG_LPC43_ETH_NTXDESC;
|
|
i++)
|
|
{
|
|
/* Check if this is the first segment in the frame */
|
|
|
|
if ((rxdesc->rdes0 & ETH_RDES0_FS) != 0 &&
|
|
(rxdesc->rdes0 & ETH_RDES0_LS) == 0)
|
|
{
|
|
priv->rxcurr = rxdesc;
|
|
priv->segments = 1;
|
|
}
|
|
|
|
/* Check if this is an intermediate segment in the frame */
|
|
|
|
else if (((rxdesc->rdes0 & ETH_RDES0_LS) == 0) &&
|
|
((rxdesc->rdes0 & ETH_RDES0_FS) == 0))
|
|
{
|
|
priv->segments++;
|
|
}
|
|
|
|
/* Otherwise, it is the last segment in the frame */
|
|
|
|
else
|
|
{
|
|
priv->segments++;
|
|
|
|
/* Check if the there is only one segment in the frame */
|
|
|
|
if (priv->segments == 1)
|
|
{
|
|
rxcurr = rxdesc;
|
|
}
|
|
else
|
|
{
|
|
rxcurr = priv->rxcurr;
|
|
}
|
|
|
|
nllvdbg("rxhead: %p rxcurr: %p segments: %d\n",
|
|
priv->rxhead, priv->rxcurr, priv->segments);
|
|
|
|
/* Check if any errors are reported in the frame */
|
|
|
|
if ((rxdesc->rdes0 & ETH_RDES0_ES) == 0)
|
|
{
|
|
struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Get the Frame Length of the received packet: subtract 4
|
|
* bytes of the CRC
|
|
*/
|
|
|
|
dev->d_len = ((rxdesc->rdes0 & ETH_RDES0_FL_MASK) >> ETH_RDES0_FL_SHIFT) - 4;
|
|
|
|
/* Get a buffer from the free list. We don't even check if
|
|
* this is successful because we already assure the free
|
|
* list is not empty above.
|
|
*/
|
|
|
|
buffer = lpc43_allocbuffer(priv);
|
|
|
|
/* Take the buffer from the RX descriptor of the first free
|
|
* segment, put it into the uIP device structure, then replace
|
|
* the buffer in the RX descriptor with the newly allocated
|
|
* buffer.
|
|
*/
|
|
|
|
DEBUGASSERT(dev->d_buf == NULL);
|
|
dev->d_buf = (uint8_t *)rxcurr->rdes2;
|
|
rxcurr->rdes2 = (uint32_t)buffer;
|
|
|
|
/* Return success, remembering where we should re-start scanning
|
|
* and resetting the segment scanning logic
|
|
*/
|
|
|
|
priv->rxhead = (struct eth_rxdesc_s *)rxdesc->rdes3;
|
|
lpc43_freesegment(priv, rxcurr, priv->segments);
|
|
|
|
nllvdbg("rxhead: %p d_buf: %p d_len: %d\n",
|
|
priv->rxhead, dev->d_buf, dev->d_len);
|
|
|
|
return OK;
|
|
}
|
|
else
|
|
{
|
|
/* Drop the frame that contains the errors, reset the segment
|
|
* scanning logic, and continue scanning with the next frame.
|
|
*/
|
|
|
|
nlldbg("DROPPED: RX descriptor errors: %08x\n", rxdesc->rdes0);
|
|
lpc43_freesegment(priv, rxcurr, priv->segments);
|
|
}
|
|
}
|
|
|
|
/* Try the next descriptor */
|
|
|
|
rxdesc = (struct eth_rxdesc_s *)rxdesc->rdes3;
|
|
}
|
|
|
|
/* We get here after all of the descriptors have been scanned or when rxdesc points
|
|
* to the first descriptor owned by the DMA. Remember where we left off.
|
|
*/
|
|
|
|
priv->rxhead = rxdesc;
|
|
|
|
nllvdbg("rxhead: %p rxcurr: %p segments: %d\n",
|
|
priv->rxhead, priv->rxcurr, priv->segments);
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_receive
|
|
*
|
|
* 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 lpc43_receive(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Loop while while lpc43_recvframe() successfully retrieves valid
|
|
* Ethernet frames.
|
|
*/
|
|
|
|
while (lpc43_recvframe(priv) == OK)
|
|
{
|
|
#ifdef CONFIG_NET_PKT
|
|
/* When packet sockets are enabled, feed the frame into the packet tap */
|
|
|
|
pkt_input(&priv->dev);
|
|
#endif
|
|
|
|
/* Check if the packet is a valid size for the uIP buffer configuration
|
|
* (this should not happen)
|
|
*/
|
|
|
|
if (dev->d_len > CONFIG_NET_ETH_MTU)
|
|
{
|
|
nlldbg("DROPPED: Too big: %d\n", dev->d_len);
|
|
/* Free dropped packet buffer */
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
lpc43_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
dev->d_len = 0;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
/* When packet sockets are enabled, feed the frame into the packet tap */
|
|
|
|
pkt_input(&priv->dev);
|
|
#endif
|
|
|
|
/* We only accept IP packets of the configured type and ARP packets */
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (BUF->type == HTONS(ETHTYPE_IP))
|
|
{
|
|
nllvdbg("IPv4 frame\n");
|
|
|
|
/* Handle ARP on input then give the IPv4 packet to the network
|
|
* layer
|
|
*/
|
|
|
|
arp_ipin(&priv->dev);
|
|
ipv4_input(&priv->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->dev.d_len > 0)
|
|
{
|
|
/* Update the Ethernet header with the correct MAC address */
|
|
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (IFF_IS_IPv4(priv->dev.d_flags))
|
|
#endif
|
|
{
|
|
arp_out(&priv->dev);
|
|
}
|
|
#ifdef CONFIG_NET_IPv6
|
|
else
|
|
{
|
|
neighbor_out(&priv->dev);
|
|
}
|
|
#endif
|
|
|
|
/* And send the packet */
|
|
|
|
lpc43_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (BUF->type == HTONS(ETHTYPE_IP6))
|
|
{
|
|
nllvdbg("Iv6 frame\n");
|
|
|
|
/* Give the IPv6 packet to the network layer */
|
|
|
|
ipv6_input(&priv->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->dev.d_len > 0)
|
|
{
|
|
/* Update the Ethernet header with the correct MAC address */
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (IFF_IS_IPv4(priv->dev.d_flags))
|
|
{
|
|
arp_out(&priv->dev);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
{
|
|
neighbor_out(&priv->dev);
|
|
}
|
|
#endif
|
|
|
|
/* And send the packet */
|
|
|
|
lpc43_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_ARP
|
|
if (BUF->type == htons(ETHTYPE_ARP))
|
|
{
|
|
nllvdbg("ARP frame\n");
|
|
|
|
/* Handle ARP packet */
|
|
|
|
arp_arpin(&priv->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->dev.d_len > 0)
|
|
{
|
|
lpc43_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
nlldbg("DROPPED: Unknown type: %04x\n", BUF->type);
|
|
}
|
|
|
|
/* We are finished with the RX buffer. NOTE: If the buffer is
|
|
* re-used for transmission, the dev->d_buf field will have been
|
|
* nullified.
|
|
*/
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
/* Free the receive packet buffer */
|
|
|
|
lpc43_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
dev->d_len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_freeframe
|
|
*
|
|
* Description:
|
|
* Scans the TX descriptors and frees the buffers of completed TX transfers.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_freeframe(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
struct eth_txdesc_s *txdesc;
|
|
int i;
|
|
|
|
nllvdbg("txhead: %p txtail: %p inflight: %d\n",
|
|
priv->txhead, priv->txtail, priv->inflight);
|
|
|
|
/* Scan for "in-flight" descriptors owned by the CPU */
|
|
|
|
txdesc = priv->txtail;
|
|
if (txdesc)
|
|
{
|
|
DEBUGASSERT(priv->inflight > 0);
|
|
|
|
for (i = 0; (txdesc->tdes0 & ETH_TDES0_OWN) == 0; i++)
|
|
{
|
|
/* There should be a buffer assigned to all in-flight
|
|
* TX descriptors.
|
|
*/
|
|
|
|
nllvdbg("txtail: %p tdes0: %08x tdes2: %08x tdes3: %08x\n",
|
|
txdesc, txdesc->tdes0, txdesc->tdes2, txdesc->tdes3);
|
|
|
|
DEBUGASSERT(txdesc->tdes2 != 0);
|
|
|
|
/* Check if this is the first segment of a TX frame. */
|
|
|
|
if ((txdesc->tdes0 & ETH_TDES0_FS) != 0)
|
|
{
|
|
/* Yes.. Free the buffer */
|
|
|
|
lpc43_freebuffer(priv, (uint8_t *)txdesc->tdes2);
|
|
}
|
|
|
|
/* In any event, make sure that TDES2 is nullified. */
|
|
|
|
txdesc->tdes2 = 0;
|
|
|
|
/* Check if this is the last segment of a TX frame */
|
|
|
|
if ((txdesc->tdes0 & ETH_TDES0_LS) != 0)
|
|
{
|
|
/* Yes.. Decrement the number of frames "in-flight". */
|
|
|
|
priv->inflight--;
|
|
|
|
/* If all of the TX descriptors were in-flight, then RX interrupts
|
|
* may have been disabled... we can re-enable them now.
|
|
*/
|
|
|
|
lpc43_enableint(priv, ETH_DMAINT_RI);
|
|
|
|
/* If there are no more frames in-flight, then bail. */
|
|
|
|
if (priv->inflight <= 0)
|
|
{
|
|
priv->txtail = NULL;
|
|
priv->inflight = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Try the next descriptor in the TX chain */
|
|
|
|
txdesc = (struct eth_txdesc_s *)txdesc->tdes3;
|
|
}
|
|
|
|
/* We get here if (1) there are still frames "in-flight". Remember
|
|
* where we left off.
|
|
*/
|
|
|
|
priv->txtail = txdesc;
|
|
|
|
nllvdbg("txhead: %p txtail: %p inflight: %d\n",
|
|
priv->txhead, priv->txtail, priv->inflight);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_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 the watchdog logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_txdone(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
DEBUGASSERT(priv->txtail != NULL);
|
|
|
|
/* Scan the TX descriptor change, returning buffers to free list */
|
|
|
|
lpc43_freeframe(priv);
|
|
|
|
/* If no further xmits are pending, then cancel the TX timeout */
|
|
|
|
if (priv->inflight <= 0)
|
|
{
|
|
/* Cancel the TX timeout */
|
|
|
|
wd_cancel(priv->txtimeout);
|
|
|
|
/* Then make sure that the TX poll timer is running (if it is already
|
|
* running, the following would restart it). This is necessary to
|
|
* avoid certain race conditions where the polling sequence can be
|
|
* interrupted.
|
|
*/
|
|
|
|
(void)wd_start(priv->txpoll, LPC43_WDDELAY, lpc43_poll_expiry, 1, priv);
|
|
|
|
/* And disable further TX interrupts. */
|
|
|
|
lpc43_disableint(priv, ETH_DMAINT_TI);
|
|
}
|
|
|
|
/* Then poll uIP for new XMIT data */
|
|
|
|
lpc43_dopoll(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_interrupt_process
|
|
*
|
|
* Description:
|
|
* Interrupt processing. This may be performed either within the interrupt
|
|
* handler or on the worker thread, depending upon the configuration
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Ethernet interrupts are disabled
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lpc43_interrupt_process(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
uint32_t dmasr;
|
|
|
|
/* Get the DMA interrupt status bits (no MAC interrupts are expected) */
|
|
|
|
dmasr = lpc43_getreg(LPC43_ETH_DMASTAT);
|
|
|
|
/* Mask only enabled interrupts. This depends on the fact that the interrupt
|
|
* related bits (0-16) correspond in these two registers.
|
|
*/
|
|
|
|
dmasr &= lpc43_getreg(LPC43_ETH_DMAINTEN);
|
|
|
|
/* Check if there are pending "normal" interrupts */
|
|
|
|
if ((dmasr & ETH_DMAINT_NIS) != 0)
|
|
{
|
|
/* Yes.. Check if we received an incoming packet, if so, call
|
|
* lpc43_receive()
|
|
*/
|
|
|
|
if ((dmasr & ETH_DMAINT_RI) != 0)
|
|
{
|
|
/* Clear the pending receive interrupt */
|
|
|
|
lpc43_putreg(ETH_DMAINT_RI, LPC43_ETH_DMASTAT);
|
|
|
|
/* Handle the received package */
|
|
|
|
lpc43_receive(priv);
|
|
}
|
|
|
|
/* Check if a packet transmission just completed. If so, call
|
|
* lpc43_txdone(). This may disable further TX interrupts if there
|
|
* are no pending tansmissions.
|
|
*/
|
|
|
|
if ((dmasr & ETH_DMAINT_TI) != 0)
|
|
{
|
|
/* Clear the pending receive interrupt */
|
|
|
|
lpc43_putreg(ETH_DMAINT_TI, LPC43_ETH_DMASTAT);
|
|
|
|
/* Check if there are pending transmissions */
|
|
|
|
lpc43_txdone(priv);
|
|
}
|
|
|
|
/* Clear the pending normal summary interrupt */
|
|
|
|
lpc43_putreg(ETH_DMAINT_NIS, LPC43_ETH_DMASTAT);
|
|
}
|
|
|
|
/* Handle error interrupt only if CONFIG_DEBUG_NET is eanbled */
|
|
|
|
#ifdef CONFIG_DEBUG_NET
|
|
|
|
/* Check if there are pending "abnormal" interrupts */
|
|
|
|
if ((dmasr & ETH_DMAINT_AIS) != 0)
|
|
{
|
|
/* Just let the user know what happened */
|
|
|
|
nlldbg("Abnormal event(s): %08x\n", dmasr);
|
|
|
|
/* Clear all pending abnormal events */
|
|
|
|
lpc43_putreg(ETH_DMAINT_ABNORMAL, LPC43_ETH_DMASTAT);
|
|
|
|
/* Clear the pending abnormal summary interrupt */
|
|
|
|
lpc43_putreg(ETH_DMAINT_AIS, LPC43_ETH_DMASTAT);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_interrupt_work
|
|
*
|
|
* Description:
|
|
* Perform interrupt related work from the worker thread
|
|
*
|
|
* Parameters:
|
|
* arg - The argument passed when work_queue() was called.
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
* Assumptions:
|
|
* Ethernet interrupts are disabled
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_interrupt_work(FAR void *arg)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* Process pending Ethernet interrupts */
|
|
|
|
state = net_lock();
|
|
lpc43_interrupt_process(priv);
|
|
net_unlock(state);
|
|
|
|
/* Re-enable Ethernet interrupts at the NVIC */
|
|
|
|
up_enable_irq(LPC43M4_IRQ_ETHERNET);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_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 lpc43_interrupt(int irq, FAR void *context)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = &g_lpc43ethmac;
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
uint32_t dmasr;
|
|
|
|
/* Get the DMA interrupt status bits (no MAC interrupts are expected) */
|
|
|
|
dmasr = lpc43_getreg(LPC43_ETH_DMASTAT);
|
|
if (dmasr != 0)
|
|
{
|
|
/* Disable further Ethernet interrupts. Because Ethernet interrupts
|
|
* are also disabled if the TX timeout event occurs, there can be no
|
|
* race condition here.
|
|
*/
|
|
|
|
up_disable_irq(LPC43M4_IRQ_ETHERNET);
|
|
|
|
/* Check if a packet transmission just completed. */
|
|
|
|
if ((dmasr & ETH_DMAINT_TI) != 0)
|
|
{
|
|
/* If a TX transfer just completed, then cancel the TX timeout so
|
|
* there will be no race condition between any subsequent timeout
|
|
* expiration and the deferred interrupt processing.
|
|
*/
|
|
|
|
wd_cancel(priv->txtimeout);
|
|
}
|
|
|
|
/* Cancel any pending poll work */
|
|
|
|
work_cancel(HPWORK, &priv->work);
|
|
|
|
/* Schedule to perform the interrupt processing on the worker thread. */
|
|
|
|
work_queue(HPWORK, &priv->work, lpc43_interrupt_work, priv, 0);
|
|
}
|
|
|
|
#else
|
|
/* Process the interrupt now */
|
|
|
|
lpc43_interrupt_process(priv);
|
|
#endif
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_txtimeout_process
|
|
*
|
|
* Description:
|
|
* Process a TX timeout. Called from the either the watchdog timer
|
|
* expiration logic or from the worker thread, depending upon the
|
|
* configuration. The timeout means that the last TX never completed.
|
|
* Reset the hardware and start again.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by the watchdog logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lpc43_txtimeout_process(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
/* Then reset the hardware. Just take the interface down, then back
|
|
* up again.
|
|
*/
|
|
|
|
lpc43_ifdown(&priv->dev);
|
|
lpc43_ifup(&priv->dev);
|
|
|
|
/* Then poll uIP for new XMIT data */
|
|
|
|
lpc43_dopoll(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_txtimeout_work
|
|
*
|
|
* Description:
|
|
* Perform TX timeout related work from the worker thread
|
|
*
|
|
* Parameters:
|
|
* arg - The argument passed when work_queue() as called.
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
* Assumptions:
|
|
* Ethernet interrupts are disabled
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_txtimeout_work(FAR void *arg)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
/* Process pending Ethernet interrupts */
|
|
|
|
state = net_lock();
|
|
lpc43_txtimeout_process(priv);
|
|
net_unlock(state);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_txtimeout_expiry
|
|
*
|
|
* 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 lpc43_txtimeout_expiry(int argc, uint32_t arg, ...)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)arg;
|
|
|
|
nlldbg("Timeout!\n");
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
/* Disable further Ethernet interrupts. This will prevent some race
|
|
* conditions with interrupt work. There is still a potential race
|
|
* condition with interrupt work that is already queued and in progress.
|
|
*
|
|
* Interrupts will be re-enabled when lpc43_ifup() is called.
|
|
*/
|
|
|
|
up_disable_irq(LPC43M4_IRQ_ETHERNET);
|
|
|
|
/* Cancel any pending poll or interrupt work. This will have no effect
|
|
* on work that has already been started.
|
|
*/
|
|
|
|
work_cancel(HPWORK, &priv->work);
|
|
|
|
/* Schedule to perform the TX timeout processing on the worker thread. */
|
|
|
|
work_queue(HPWORK, &priv->work, lpc43_txtimeout_work, priv, 0);
|
|
|
|
#else
|
|
/* Process the timeout now */
|
|
|
|
lpc43_txtimeout_process(priv);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_poll_process
|
|
*
|
|
* Description:
|
|
* Perform the periodic poll. This may be called either from watchdog
|
|
* timer logic or from the worker thread, depending upon the configuration.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lpc43_poll_process(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Check if the next TX descriptor is owned by the Ethernet DMA or CPU. We
|
|
* cannot perform the timer poll if we are unable to accept another packet
|
|
* for transmission. Hmmm.. might be bug here. Does this mean if there is
|
|
* a transmit in progress, we will miss TCP time state updates?
|
|
*
|
|
* In a race condition, ETH_TDES0_OWN may be cleared BUT still not available
|
|
* because lpc43_freeframe() has not yet run. If lpc43_freeframe() has run,
|
|
* the buffer1 pointer (tdes2) will be nullified (and inflight should be <
|
|
* CONFIG_LPC43_ETH_NTXDESC).
|
|
*/
|
|
|
|
if ((priv->txhead->tdes0 & ETH_TDES0_OWN) == 0 &&
|
|
priv->txhead->tdes2 == 0)
|
|
{
|
|
/* If we have the descriptor, then perform the timer poll. Allocate a
|
|
* buffer for the poll.
|
|
*/
|
|
|
|
DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL);
|
|
dev->d_buf = lpc43_allocbuffer(priv);
|
|
|
|
/* We can't poll if we have no buffers */
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
/* Update TCP timing states and poll for new XMIT data.
|
|
*/
|
|
|
|
(void)devif_timer(dev, lpc43_txpoll);
|
|
|
|
/* We will, most likely end up with a buffer to be freed. But it
|
|
* might not be the same one that we allocated above.
|
|
*/
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
DEBUGASSERT(dev->d_len == 0);
|
|
lpc43_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Setup the watchdog poll timer again */
|
|
|
|
(void)wd_start(priv->txpoll, LPC43_WDDELAY, lpc43_poll_expiry, 1, priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_poll_work
|
|
*
|
|
* Description:
|
|
* Perform periodic polling from the worker thread
|
|
*
|
|
* Parameters:
|
|
* arg - The argument passed when work_queue() as called.
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
* Assumptions:
|
|
* Ethernet interrupts are disabled
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_poll_work(FAR void *arg)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
/* Perform the poll */
|
|
|
|
state = net_lock();
|
|
lpc43_poll_process(priv);
|
|
net_unlock(state);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_poll_expiry
|
|
*
|
|
* 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 lpc43_poll_expiry(int argc, uint32_t arg, ...)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)arg;
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
/* Is our single work structure available? It may not be if there are
|
|
* pending interrupt actions.
|
|
*/
|
|
|
|
if (work_available(&priv->work))
|
|
{
|
|
/* Schedule to perform the interrupt processing on the worker thread. */
|
|
|
|
work_queue(HPWORK, &priv->work, lpc43_poll_work, priv, 0);
|
|
}
|
|
else
|
|
{
|
|
/* No.. Just re-start the watchdog poll timer, missing one polling
|
|
* cycle.
|
|
*/
|
|
|
|
(void)wd_start(priv->txpoll, LPC43_WDDELAY, lpc43_poll_expiry, 1,
|
|
(uint32_t)priv);
|
|
}
|
|
|
|
#else
|
|
/* Process the interrupt now */
|
|
|
|
lpc43_poll_process(priv);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_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 lpc43_ifup(struct net_driver_s *dev)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)dev->d_private;
|
|
int ret;
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
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);
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
ndbg("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
|
|
dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
|
|
dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
|
|
dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
|
|
#endif
|
|
|
|
/* Configure the Ethernet interface for DMA operation. */
|
|
|
|
ret = lpc43_ethconfig(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Set and activate a timer process */
|
|
|
|
(void)wd_start(priv->txpoll, LPC43_WDDELAY, lpc43_poll_expiry, 1,
|
|
(uint32_t)priv);
|
|
|
|
/* Enable the Ethernet interrupt */
|
|
|
|
priv->ifup = true;
|
|
up_enable_irq(LPC43M4_IRQ_ETHERNET);
|
|
|
|
lpc43_checksetup();
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_ifdown
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Stop the interface.
|
|
*
|
|
* Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_ifdown(struct net_driver_s *dev)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)dev->d_private;
|
|
irqstate_t flags;
|
|
|
|
ndbg("Taking the network down\n");
|
|
|
|
/* Disable the Ethernet interrupt */
|
|
|
|
flags = irqsave();
|
|
up_disable_irq(LPC43M4_IRQ_ETHERNET);
|
|
|
|
/* Cancel the TX poll timer and TX timeout timers */
|
|
|
|
wd_cancel(priv->txpoll);
|
|
wd_cancel(priv->txtimeout);
|
|
|
|
/* Put the EMAC in its reset, non-operational state. This should be
|
|
* a known configuration that will guarantee the lpc43_ifup() always
|
|
* successfully brings the interface back up.
|
|
*/
|
|
|
|
lpc43_ethreset(priv);
|
|
|
|
/* Mark the device "down" */
|
|
|
|
priv->ifup = false;
|
|
irqrestore(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_txavail_process
|
|
*
|
|
* Description:
|
|
* Perform an out-of-cycle poll.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Called in normal user mode
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lpc43_txavail_process(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
nvdbg("ifup: %d\n", priv->ifup);
|
|
|
|
/* Ignore the notification if the interface is not yet up */
|
|
|
|
if (priv->ifup)
|
|
{
|
|
/* Poll for new XMIT data */
|
|
|
|
lpc43_dopoll(priv);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_txavail_work
|
|
*
|
|
* Description:
|
|
* Perform an out-of-cycle poll on the worker thread.
|
|
*
|
|
* Parameters:
|
|
* arg - Reference to the NuttX driver state structure (cast to void*)
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Called on the higher priority worker thread.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void lpc43_txavail_work(FAR void *arg)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
/* Perform the poll */
|
|
|
|
state = net_lock();
|
|
lpc43_txavail_process(priv);
|
|
net_unlock(state);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_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 lpc43_txavail(struct net_driver_s *dev)
|
|
{
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)dev->d_private;
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
/* Is our single work structure available? It may not be if there are
|
|
* pending interrupt actions and we will have to ignore the Tx
|
|
* availability action.
|
|
*/
|
|
|
|
if (work_available(&priv->work))
|
|
{
|
|
/* Schedule to serialize the poll on the worker thread. */
|
|
|
|
work_queue(HPWORK, &priv->work, lpc43_txavail_work, priv, 0);
|
|
}
|
|
|
|
#else
|
|
irqstate_t flags;
|
|
|
|
/* Disable interrupts because this function may be called from interrupt
|
|
* level processing.
|
|
*/
|
|
|
|
flags = irqsave();
|
|
|
|
/* Perform the out-of-cycle poll now */
|
|
|
|
lpc43_txavail_process(priv);
|
|
irqrestore(flags);
|
|
#endif
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_calcethcrc
|
|
*
|
|
* Description:
|
|
* Function to calculate the CRC used by LPC43 to check an Ethernet frame
|
|
*
|
|
* Parameters:
|
|
* data - the data to be checked
|
|
* length - length of the data
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6)
|
|
static uint32_t lpc43_calcethcrc(const uint8_t *data, size_t length)
|
|
{
|
|
uint32_t crc = 0xffffffff;
|
|
size_t i;
|
|
int j;
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
for (j = 0; j < 8; j++)
|
|
{
|
|
if (((crc >> 31) ^ (data[i] >> j)) & 0x01)
|
|
{
|
|
/* x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1 */
|
|
crc = (crc << 1) ^ 0x04c11db7;
|
|
}
|
|
else
|
|
{
|
|
crc = crc << 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ~crc;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_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:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6)
|
|
static int lpc43_addmac(struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
uint32_t crc;
|
|
uint32_t hashindex;
|
|
uint32_t temp;
|
|
uint32_t registeraddress;
|
|
|
|
nllvdbg("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
/* Add the MAC address to the hardware multicast hash table */
|
|
|
|
crc = lpc43_calcethcrc(mac, 6);
|
|
|
|
hashindex = (crc >> 26) & 0x3F;
|
|
|
|
if (hashindex > 31)
|
|
{
|
|
registeraddress = LPC43_ETH_MACHTHI;
|
|
hashindex -= 32;
|
|
}
|
|
else
|
|
{
|
|
registeraddress = LPC43_ETH_MACHTLO;
|
|
}
|
|
|
|
temp = lpc43_getreg(registeraddress);
|
|
temp |= 1 << hashindex;
|
|
lpc43_putreg(temp, registeraddress);
|
|
|
|
temp = lpc43_getreg(LPC43_ETH_MACFFLT);
|
|
temp |= (ETH_MACFFLT_HM | ETH_MACFFLT_HPF);
|
|
lpc43_putreg(temp, LPC43_ETH_MACFFLT);
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_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 lpc43_rmmac(struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
uint32_t crc;
|
|
uint32_t hashindex;
|
|
uint32_t temp;
|
|
uint32_t registeraddress;
|
|
|
|
nllvdbg("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
/* Remove the MAC address to the hardware multicast hash table */
|
|
|
|
crc = lpc43_calcethcrc(mac, 6);
|
|
|
|
hashindex = (crc >> 26) & 0x3F;
|
|
|
|
if (hashindex > 31)
|
|
{
|
|
registeraddress = LPC43_ETH_MACHTHI;
|
|
hashindex -= 32;
|
|
}
|
|
else
|
|
{
|
|
registeraddress = LPC43_ETH_MACHTLO;
|
|
}
|
|
|
|
temp = lpc43_getreg(registeraddress);
|
|
temp &= ~(1 << hashindex);
|
|
lpc43_putreg(temp, registeraddress);
|
|
|
|
/* If there is no address registered any more, delete multicast filtering */
|
|
|
|
if (lpc43_getreg(LPC43_ETH_MACHTHI) == 0 &&
|
|
lpc43_getreg(LPC43_ETH_MACHTLO) == 0)
|
|
{
|
|
temp = lpc43_getreg(LPC43_ETH_MACFFLT);
|
|
temp &= ~(ETH_MACFFLT_HM | ETH_MACFFLT_HPF);
|
|
lpc43_putreg(temp, LPC43_ETH_MACFFLT);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_txdescinit
|
|
*
|
|
* Description:
|
|
* Initializes the DMA TX descriptors in chain mode.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_txdescinit(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
struct eth_txdesc_s *txdesc;
|
|
int i;
|
|
|
|
/* priv->txhead will point to the first, available TX descriptor in the chain.
|
|
* Set the priv->txhead pointer to the first descriptor in the table.
|
|
*/
|
|
|
|
priv->txhead = priv->txtable;
|
|
|
|
/* priv->txtail will point to the first segment of the oldest pending
|
|
* "in-flight" TX transfer. NULL means that there are no active TX
|
|
* transfers.
|
|
*/
|
|
|
|
priv->txtail = NULL;
|
|
priv->inflight = 0;
|
|
|
|
/* Initialize each TX descriptor */
|
|
|
|
for (i = 0; i < CONFIG_LPC43_ETH_NTXDESC; i++)
|
|
{
|
|
txdesc = &priv->txtable[i];
|
|
|
|
/* Set Second Address Chained bit */
|
|
|
|
txdesc->tdes0 = ETH_TDES0_TCH;
|
|
|
|
#ifdef CHECKSUM_BY_HARDWARE
|
|
/* Enable the checksum insertion for the TX frames */
|
|
|
|
txdesc->tdes0 |= ETH_TDES0_CIC_ALL;
|
|
#endif
|
|
|
|
/* Clear Buffer1 address pointer (buffers will be assigned as they
|
|
* are used)
|
|
*/
|
|
|
|
txdesc->tdes2 = 0;
|
|
|
|
/* Initialize the next descriptor with the Next Descriptor Polling Enable */
|
|
|
|
if (i < (CONFIG_LPC43_ETH_NTXDESC-1))
|
|
{
|
|
/* Set next descriptor address register with next descriptor base
|
|
* address
|
|
*/
|
|
|
|
txdesc->tdes3 = (uint32_t)&priv->txtable[i+1];
|
|
}
|
|
else
|
|
{
|
|
/* For last descriptor, set next descriptor address register equal
|
|
* to the first descriptor base address
|
|
*/
|
|
|
|
txdesc->tdes3 = (uint32_t)priv->txtable;
|
|
}
|
|
}
|
|
|
|
/* Set Transmit Desciptor List Address Register */
|
|
|
|
lpc43_putreg((uint32_t)priv->txtable, LPC43_ETH_DMATXDLA);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_rxdescinit
|
|
*
|
|
* Description:
|
|
* Initializes the DMA RX descriptors in chain mode.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_rxdescinit(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
struct eth_rxdesc_s *rxdesc;
|
|
int i;
|
|
|
|
/* priv->rxhead will point to the first, RX descriptor in the chain.
|
|
* This will be where we receive the first incomplete frame.
|
|
*/
|
|
|
|
priv->rxhead = priv->rxtable;
|
|
|
|
/* If we accumulate the frame in segments, priv->rxcurr points to the
|
|
* RX descriptor of the first segment in the current TX frame.
|
|
*/
|
|
|
|
priv->rxcurr = NULL;
|
|
priv->segments = 0;
|
|
|
|
/* Initialize each TX descriptor */
|
|
|
|
for (i = 0; i < CONFIG_LPC43_ETH_NRXDESC; i++)
|
|
{
|
|
rxdesc = &priv->rxtable[i];
|
|
|
|
/* Set Own bit of the RX descriptor rdes0 */
|
|
|
|
rxdesc->rdes0 = ETH_RDES0_OWN;
|
|
|
|
/* Set Buffer1 size and Second Address Chained bit and enabled DMA
|
|
* RX desc receive interrupt
|
|
*/
|
|
|
|
rxdesc->rdes1 = ETH_RDES1_RCH | (uint32_t)CONFIG_LPC43_ETH_BUFSIZE;
|
|
|
|
/* Set Buffer1 address pointer */
|
|
|
|
rxdesc->rdes2 = (uint32_t)&priv->rxbuffer[i*CONFIG_LPC43_ETH_BUFSIZE];
|
|
|
|
/* Initialize the next descriptor with the Next Descriptor Polling Enable */
|
|
|
|
if (i < (CONFIG_LPC43_ETH_NRXDESC-1))
|
|
{
|
|
/* Set next descriptor address register with next descriptor base
|
|
* address
|
|
*/
|
|
|
|
rxdesc->rdes3 = (uint32_t)&priv->rxtable[i+1];
|
|
}
|
|
else
|
|
{
|
|
/* For last descriptor, set next descriptor address register equal
|
|
* to the first descriptor base address
|
|
*/
|
|
|
|
rxdesc->rdes3 = (uint32_t)priv->rxtable;
|
|
}
|
|
}
|
|
|
|
/* Set Receive Descriptor List Address Register */
|
|
|
|
lpc43_putreg((uint32_t)priv->rxtable, LPC43_ETH_DMARXDLA);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_ioctl
|
|
*
|
|
* Description:
|
|
* Executes the SIOCxMIIxxx command and responds using the request struct
|
|
* that must be provided as its 2nd parameter.
|
|
*
|
|
* When called with SIOCGMIIPHY it will get the PHY address for the device
|
|
* and write it to the req->phy_id field of the request struct.
|
|
*
|
|
* When called with SIOCGMIIREG it will read a register of the PHY that is
|
|
* specified using the req->reg_no struct field and then write its output
|
|
* to the req->val_out field.
|
|
*
|
|
* When called with SIOCSMIIREG it will write to a register of the PHY that
|
|
* is specified using the req->reg_no struct field and use req->val_in as
|
|
* its input.
|
|
*
|
|
* Parameters:
|
|
* dev - Ethernet device structure
|
|
* cmd - SIOCxMIIxxx command code
|
|
* arg - Request structure also used to return values
|
|
*
|
|
* Returned Value: Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NETDEV_PHY_IOCTL
|
|
static int lpc43_ioctl(struct net_driver_s *dev, int cmd, long arg)
|
|
{
|
|
#ifdef CONFIG_ARCH_PHY_INTERRUPT
|
|
FAR struct lpc43_ethmac_s *priv = (FAR struct lpc43_ethmac_s *)dev->d_private;
|
|
#endif
|
|
int ret;
|
|
|
|
switch (cmd)
|
|
{
|
|
#ifdef CONFIG_ARCH_PHY_INTERRUPT
|
|
case SIOCMIINOTIFY: /* Set up for PHY event notifications */
|
|
{
|
|
struct mii_iotcl_notify_s *req = (struct mii_iotcl_notify_s *)((uintptr_t)arg);
|
|
|
|
ret = phy_notify_subscribe(dev->d_ifname, req->pid, req->signo, req->arg);
|
|
if (ret == OK)
|
|
{
|
|
/* Enable PHY link up/down interrupts */
|
|
|
|
ret = lpc43_phyintenable(priv);
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case SIOCGMIIPHY: /* Get MII PHY address */
|
|
{
|
|
struct mii_ioctl_data_s *req = (struct mii_ioctl_data_s *)((uintptr_t)arg);
|
|
req->phy_id = CONFIG_LPC43_PHYADDR;
|
|
ret = OK;
|
|
}
|
|
break;
|
|
|
|
case SIOCGMIIREG: /* Get register from MII PHY */
|
|
{
|
|
struct mii_ioctl_data_s *req = (struct mii_ioctl_data_s *)((uintptr_t)arg);
|
|
ret = lpc43_phyread(req->phy_id, req->reg_num, &req->val_out);
|
|
}
|
|
break;
|
|
|
|
case SIOCSMIIREG: /* Set register in MII PHY */
|
|
{
|
|
struct mii_ioctl_data_s *req = (struct mii_ioctl_data_s *)((uintptr_t)arg);
|
|
ret = lpc43_phywrite(req->phy_id, req->reg_num, req->val_in);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_NETDEV_PHY_IOCTL */
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_phyintenable
|
|
*
|
|
* Description:
|
|
* Enable link up/down PHY interrupts. The interrupt protocol is like
|
|
* this:
|
|
*
|
|
* - Interrupt status is cleared when the interrupt is enabled.
|
|
* - Interrupt occurs. Interrupt is disabled (at the processor level) when
|
|
* is received.
|
|
* - Interrupt status is cleared when the interrupt is re-enabled.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno (-ETIMEDOUT) on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT)
|
|
static int lpc43_phyintenable(struct lpc43_ethmac_s *priv)
|
|
{
|
|
#warning Missing logic
|
|
return -ENOSYS;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_phyread
|
|
*
|
|
* Description:
|
|
* Read a PHY register.
|
|
*
|
|
* Parameters:
|
|
* phydevaddr - The PHY device address
|
|
* phyregaddr - The PHY register address
|
|
* value - The location to return the 16-bit PHY register value.
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_phyread(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t *value)
|
|
{
|
|
volatile uint32_t timeout;
|
|
uint32_t regval;
|
|
|
|
/* Configure the MACMIIAR register, preserving CSR Clock Range CR[2:0] bits */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACMIIA);
|
|
regval &= ETH_MACMIIA_CR_MASK;
|
|
|
|
/* Set the PHY device address, PHY register address, and set the buy bit.
|
|
* the ETH_MACMIIA_WR is clear, indicating a read operation.
|
|
*/
|
|
|
|
regval |= (((uint32_t)phydevaddr << ETH_MACMIIA_PA_SHIFT) & ETH_MACMIIA_PA_MASK);
|
|
regval |= (((uint32_t)phyregaddr << ETH_MACMIIA_MR_SHIFT) & ETH_MACMIIA_MR_MASK);
|
|
regval |= ETH_MACMIIA_GB;
|
|
|
|
lpc43_putreg(regval, LPC43_ETH_MACMIIA);
|
|
|
|
/* Wait for the transfer to complete */
|
|
|
|
for (timeout = 0; timeout < PHY_READ_TIMEOUT; timeout++)
|
|
{
|
|
if ((lpc43_getreg(LPC43_ETH_MACMIIA) & ETH_MACMIIA_GB) == 0)
|
|
{
|
|
*value = (uint16_t)lpc43_getreg(LPC43_ETH_MACMIID);
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
ndbg("MII transfer timed out: phydevaddr: %04x phyregaddr: %04x\n",
|
|
phydevaddr, phyregaddr);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_phywrite
|
|
*
|
|
* Description:
|
|
* Write to a PHY register.
|
|
*
|
|
* Parameters:
|
|
* phydevaddr - The PHY device address
|
|
* phyregaddr - The PHY register address
|
|
* value - The 16-bit value to write to the PHY register value.
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_phywrite(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t value)
|
|
{
|
|
volatile uint32_t timeout;
|
|
uint32_t regval;
|
|
|
|
/* Configure the MACMIIAR register, preserving CSR Clock Range CR[2:0] bits */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACMIIA);
|
|
regval &= ETH_MACMIIA_CR_MASK;
|
|
|
|
/* Set the PHY device address, PHY register address, and set the busy bit.
|
|
* the ETH_MACMIIA_WR is set, indicating a write operation.
|
|
*/
|
|
|
|
regval |= (((uint32_t)phydevaddr << ETH_MACMIIA_PA_SHIFT) & ETH_MACMIIA_PA_MASK);
|
|
regval |= (((uint32_t)phyregaddr << ETH_MACMIIA_MR_SHIFT) & ETH_MACMIIA_MR_MASK);
|
|
regval |= (ETH_MACMIIA_GB | ETH_MACMIIA_WR);
|
|
|
|
/* Write the value into the MACIIDR register before setting the new MACMIIAR
|
|
* register value.
|
|
*/
|
|
|
|
lpc43_putreg(value, LPC43_ETH_MACMIID);
|
|
lpc43_putreg(regval, LPC43_ETH_MACMIIA);
|
|
|
|
/* Wait for the transfer to complete */
|
|
|
|
for (timeout = 0; timeout < PHY_WRITE_TIMEOUT; timeout++)
|
|
{
|
|
if ((lpc43_getreg(LPC43_ETH_MACMIIA) & ETH_MACMIIA_GB) == 0)
|
|
{
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
ndbg("MII transfer timed out: phydevaddr: %04x phyregaddr: %04x value: %04x\n",
|
|
phydevaddr, phyregaddr, value);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_dm9161
|
|
*
|
|
* Description:
|
|
* Special workaround for the Davicom DM9161 PHY is required. On power,
|
|
* up, the PHY is not usually configured correctly but will work after
|
|
* a powered-up reset. This is really a workaround for some more
|
|
* fundamental issue with the PHY clocking initialization, but the
|
|
* root cause has not been studied (nor will it be with this workaround).
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_ETH0_PHY_DM9161
|
|
static inline int lpc43_dm9161(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
uint16_t phyval;
|
|
int ret;
|
|
|
|
/* Read the PHYID1 register; A failure to read the PHY ID is one
|
|
* indication that check if the DM9161 PHY CHIP is not ready.
|
|
*/
|
|
|
|
ret = lpc43_phyread(CONFIG_LPC43_PHYADDR, MII_PHYID1, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to read the PHY ID1: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* If we failed to read the PHY ID1 register, the reset the MCU to recover */
|
|
|
|
else if (phyval == 0xffff)
|
|
{
|
|
up_systemreset();
|
|
}
|
|
|
|
nvdbg("PHY ID1: 0x%04X\n", phyval);
|
|
|
|
/* Now check the "DAVICOM Specified Configuration Register (DSCR)", Register 16 */
|
|
|
|
ret = lpc43_phyread(CONFIG_LPC43_PHYADDR, 16, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to read the PHY Register 0x10: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Bit 8 of the DSCR register is zero, then the DM9161 has not selected RMII.
|
|
* If RMII is not selected, then reset the MCU to recover.
|
|
*/
|
|
|
|
else if ((phyval & (1 << 8)) == 0)
|
|
{
|
|
up_systemreset();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_phyinit
|
|
*
|
|
* Description:
|
|
* Configure the PHY and determine the link speed/duplex.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_phyinit(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
volatile uint32_t timeout;
|
|
uint32_t regval;
|
|
uint16_t phyval;
|
|
int ret;
|
|
|
|
/* Assume 10MBps and half duplex */
|
|
|
|
priv->mbps100 = 0;
|
|
priv->fduplex = 0;
|
|
|
|
/* Setup up PHY clocking by setting the SR field in the MACMIIAR register */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACMIIA);
|
|
regval &= ~ETH_MACMIIA_CR_MASK;
|
|
regval |= ETH_MACMIIA_CR;
|
|
lpc43_putreg(regval, LPC43_ETH_MACMIIA);
|
|
|
|
/* Put the PHY in reset mode */
|
|
|
|
ret = lpc43_phywrite(CONFIG_LPC43_PHYADDR, MII_MCR, MII_MCR_RESET);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to reset the PHY: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
up_mdelay(PHY_RESET_DELAY);
|
|
|
|
#ifdef CONFIG_LPC43_PHYINIT
|
|
/* Perform any necessary, board-specific PHY initialization */
|
|
|
|
ret = lpc43_phy_boardinitialize(0);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to initialize the PHY: %d\n", ret);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_ETH0_PHY_DM9161
|
|
/* Special workaround for the Davicom DM9161 PHY is required. */
|
|
|
|
ret = lpc43_dm9161(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* Perform auto-negotion if so configured */
|
|
|
|
#ifdef CONFIG_LPC43_AUTONEG
|
|
/* Wait for link status */
|
|
|
|
for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++)
|
|
{
|
|
ret = lpc43_phyread(CONFIG_LPC43_PHYADDR, MII_MSR, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to read the PHY MSR: %d\n", ret);
|
|
return ret;
|
|
}
|
|
else if ((phyval & MII_MSR_LINKSTATUS) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (timeout >= PHY_RETRY_TIMEOUT)
|
|
{
|
|
ndbg("Timed out waiting for link status: %04x\n", phyval);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* Enable auto-negotiation */
|
|
|
|
ret = lpc43_phywrite(CONFIG_LPC43_PHYADDR, MII_MCR, MII_MCR_ANENABLE);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to enable auto-negotiation: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Wait until auto-negotiation completes */
|
|
|
|
for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++)
|
|
{
|
|
ret = lpc43_phyread(CONFIG_LPC43_PHYADDR, MII_MSR, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to read the PHY MSR: %d\n", ret);
|
|
return ret;
|
|
}
|
|
else if ((phyval & MII_MSR_ANEGCOMPLETE) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (timeout >= PHY_RETRY_TIMEOUT)
|
|
{
|
|
ndbg("Timed out waiting for auto-negotiation\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* Read the result of the auto-negotiation from the PHY-specific register */
|
|
|
|
ret = lpc43_phyread(CONFIG_LPC43_PHYADDR, CONFIG_LPC43_PHYSR, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to read PHY status register\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Remember the selected speed and duplex modes */
|
|
|
|
nvdbg("PHYSR[%d]: %04x\n", CONFIG_LPC43_PHYSR, phyval);
|
|
|
|
/* Different PHYs present speed and mode information in different ways. IF
|
|
* This CONFIG_LPC43_PHYSR_ALTCONFIG is selected, this indicates that the PHY
|
|
* represents speed and mode information are combined, for example, with
|
|
* separate bits for 10HD, 100HD, 10FD and 100FD.
|
|
*/
|
|
|
|
#ifdef CONFIG_LPC43_PHYSR_ALTCONFIG
|
|
switch (phyval & CONFIG_LPC43_PHYSR_ALTMODE)
|
|
{
|
|
default:
|
|
case CONFIG_LPC43_PHYSR_10HD:
|
|
priv->fduplex = 0;
|
|
priv->mbps100 = 0;
|
|
break;
|
|
|
|
case CONFIG_LPC43_PHYSR_100HD:
|
|
priv->fduplex = 0;
|
|
priv->mbps100 = 1;
|
|
break;
|
|
|
|
case CONFIG_LPC43_PHYSR_10FD:
|
|
priv->fduplex = 1;
|
|
priv->mbps100 = 0;
|
|
break;
|
|
|
|
case CONFIG_LPC43_PHYSR_100FD:
|
|
priv->fduplex = 1;
|
|
priv->mbps100 = 1;
|
|
break;
|
|
}
|
|
|
|
/* Different PHYs present speed and mode information in different ways. Some
|
|
* will present separate information for speed and mode (this is the default).
|
|
* Those PHYs, for example, may provide a 10/100 Mbps indication and a separate
|
|
* full/half duplex indication.
|
|
*/
|
|
|
|
#else
|
|
if ((phyval & CONFIG_LPC43_PHYSR_MODE) == CONFIG_LPC43_PHYSR_FULLDUPLEX)
|
|
{
|
|
priv->fduplex = 1;
|
|
}
|
|
|
|
if ((phyval & CONFIG_LPC43_PHYSR_SPEED) == CONFIG_LPC43_PHYSR_100MBPS)
|
|
{
|
|
priv->mbps100 = 1;
|
|
}
|
|
#endif
|
|
|
|
#else /* Auto-negotion not selected */
|
|
|
|
phyval = 0;
|
|
#ifdef CONFIG_LPC43_ETHFD
|
|
phyval |= MII_MCR_FULLDPLX;
|
|
#endif
|
|
#ifdef CONFIG_LPC43_ETH100MBPS
|
|
phyval |= MII_MCR_SPEED100;
|
|
#endif
|
|
|
|
ret = lpc43_phywrite(CONFIG_LPC43_PHYADDR, MII_MCR, phyval);
|
|
if (ret < 0)
|
|
{
|
|
ndbg("Failed to write the PHY MCR: %d\n", ret);
|
|
return ret;
|
|
}
|
|
up_mdelay(PHY_CONFIG_DELAY);
|
|
|
|
/* Remember the selected speed and duplex modes */
|
|
|
|
#ifdef CONFIG_LPC43_ETHFD
|
|
priv->fduplex = 1;
|
|
#endif
|
|
#ifdef CONFIG_LPC43_ETH100MBPS
|
|
priv->mbps100 = 1;
|
|
#endif
|
|
#endif
|
|
|
|
ndbg("Duplex: %s Speed: %d MBps\n",
|
|
priv->fduplex ? "FULL" : "HALF",
|
|
priv->mbps100 ? 100 : 10);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: lpc43_selectmii
|
|
*
|
|
* Description:
|
|
* Selects the MII inteface.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
************************************************************************************/
|
|
|
|
#ifdef CONFIG_LPC43_MII
|
|
static inline void lpc43_selectmii(void)
|
|
{
|
|
uint32_t regval;
|
|
|
|
regval = getreg32(LPC43_CREG6);
|
|
regval &= ~SYSCFG_PMC_MII_RMII_SEL;
|
|
putreg32(regval, LPC43_CREG6);
|
|
}
|
|
#endif
|
|
|
|
/************************************************************************************
|
|
* Name: lpc43_selectrmii
|
|
*
|
|
* Description:
|
|
* Selects the RMII inteface.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
************************************************************************************/
|
|
|
|
static inline void lpc43_selectrmii(void)
|
|
{
|
|
uint32_t regval;
|
|
|
|
regval = getreg32(LPC43_CREG6);
|
|
regval |= CREG6_ETHMODE_RMII;
|
|
putreg32(regval, LPC43_CREG6);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_ethgpioconfig
|
|
*
|
|
* Description:
|
|
* Configure GPIOs for the Ethernet interface.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lpc43_ethgpioconfig(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
/* Configure GPIO pins to support Ethernet */
|
|
|
|
#if defined(CONFIG_LPC43_MII) || defined(CONFIG_LPC43_RMII)
|
|
/* MDC and MDIO are common to both modes */
|
|
|
|
lpc43_pin_config(PINCONF_ENET_MDC);
|
|
lpc43_pin_config(PINCONF_ENET_MDIO);
|
|
|
|
/* Set up the MII interface */
|
|
|
|
#if defined(CONFIG_LPC43_MII)
|
|
/* Select the MII interface */
|
|
|
|
lpc43_selectmii();
|
|
|
|
/* MII interface pins (17):
|
|
*
|
|
* MII_TX_CLK, MII_TXD[3:0], MII_TX_EN, MII_RX_CLK, MII_RXD[3:0], MII_RX_ER,
|
|
* MII_RX_DV, MII_CRS, MII_COL, MDC, MDIO
|
|
*/
|
|
|
|
lpc43_pin_config(GPIO_ETH_MII_COL);
|
|
lpc43_pin_config(GPIO_ETH_MII_CRS);
|
|
lpc43_pin_config(GPIO_ETH_MII_RXD0);
|
|
lpc43_pin_config(GPIO_ETH_MII_RXD1);
|
|
lpc43_pin_config(GPIO_ETH_MII_RXD2);
|
|
lpc43_pin_config(GPIO_ETH_MII_RXD3);
|
|
lpc43_pin_config(GPIO_ETH_MII_RX_CLK);
|
|
lpc43_pin_config(GPIO_ETH_MII_RX_DV);
|
|
lpc43_pin_config(GPIO_ETH_MII_RX_ER);
|
|
lpc43_pin_config(GPIO_ETH_MII_TXD0);
|
|
lpc43_pin_config(GPIO_ETH_MII_TXD1);
|
|
lpc43_pin_config(GPIO_ETH_MII_TXD2);
|
|
lpc43_pin_config(GPIO_ETH_MII_TXD3);
|
|
lpc43_pin_config(GPIO_ETH_MII_TX_CLK);
|
|
lpc43_pin_config(GPIO_ETH_MII_TX_EN);
|
|
|
|
/* Set up the RMII interface. */
|
|
|
|
#elif defined(CONFIG_LPC43_RMII)
|
|
/* Select the RMII interface */
|
|
|
|
lpc43_selectrmii();
|
|
|
|
/* RMII interface pins (7):
|
|
*
|
|
* RMII_TXD[1:0], RMII_TX_EN, RMII_RXD[1:0], RMII_CRS_DV, MDC, MDIO,
|
|
* RMII_REF_CLK
|
|
*/
|
|
|
|
lpc43_pin_config(PINCONF_ENET_RX_DV);
|
|
lpc43_pin_config(PINCONF_ENET_REF_CLK);
|
|
lpc43_pin_config(PINCONF_ENET_RXD0);
|
|
lpc43_pin_config(PINCONF_ENET_RXD1);
|
|
lpc43_pin_config(PINCONF_ENET_TXD0);
|
|
lpc43_pin_config(PINCONF_ENET_TXD1);
|
|
lpc43_pin_config(PINCONF_ENET_TXEN);
|
|
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_ethreset
|
|
*
|
|
* Description:
|
|
* Reset the Ethernet block.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_ethreset(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Reset the Ethernet */
|
|
|
|
lpc43_putreg(RGU_CTRL0_ETHERNET_RST, LPC43_RGU_CTRL0);
|
|
|
|
/* Perform a software reset by setting the SR bit in the DMABMR register.
|
|
* This Resets all MAC subsystem internal registers and logic. After this
|
|
* reset all the registers holds their reset values.
|
|
*/
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMABMODE);
|
|
regval |= ETH_DMABMODE_SWR;
|
|
lpc43_putreg(regval, LPC43_ETH_DMABMODE);
|
|
|
|
/* Wait for software reset to complete. The SR bit is cleared automatically
|
|
* after the reset operation has completed in all of the core clock domains.
|
|
*/
|
|
|
|
while ((lpc43_getreg(LPC43_ETH_DMABMODE) & ETH_DMABMODE_SWR) != 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_macconfig
|
|
*
|
|
* Description:
|
|
* Configure the Ethernet MAC for DMA operation.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_macconfig(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Set up the MACCR register */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACCR);
|
|
regval &= ~MACCR_CLEAR_BITS;
|
|
regval |= MACCR_SET_BITS;
|
|
|
|
if (priv->fduplex)
|
|
{
|
|
/* Set the DM bit for full duplex support */
|
|
|
|
regval |= ETH_MACCFG_DM;
|
|
}
|
|
|
|
if (priv->mbps100)
|
|
{
|
|
/* Set the FES bit for 100Mbps fast ethernet support */
|
|
|
|
regval |= ETH_MACCFG_FES;
|
|
}
|
|
|
|
lpc43_putreg(regval, LPC43_ETH_MACCR);
|
|
|
|
/* Set up the MACFFR register */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACFFLT);
|
|
regval &= ~MACFFR_CLEAR_BITS;
|
|
regval |= MACFFR_SET_BITS;
|
|
lpc43_putreg(regval, LPC43_ETH_MACFFLT);
|
|
|
|
/* Set up the MACHTHR and MACHTLR registers */
|
|
|
|
lpc43_putreg(0, LPC43_ETH_MACHTHI);
|
|
lpc43_putreg(0, LPC43_ETH_MACHTLO);
|
|
|
|
/* Setup up the MACFCR register */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACFC);
|
|
regval &= ~MACFCR_CLEAR_MASK;
|
|
regval |= MACFCR_SET_MASK;
|
|
lpc43_putreg(regval, LPC43_ETH_MACFC);
|
|
|
|
/* Setup up the MACVLANTR register */
|
|
|
|
lpc43_putreg(0, LPC43_ETH_MACVLANT);
|
|
|
|
/* DMA Configuration */
|
|
/* Set up the DMAOMR register */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMAOPMODE);
|
|
regval &= ~DMAOMR_CLEAR_MASK;
|
|
regval |= DMAOMR_SET_MASK;
|
|
lpc43_putreg(regval, LPC43_ETH_DMAOPMODE);
|
|
|
|
/* Set up the DMABMR register */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMABMODE);
|
|
regval &= ~DMABMR_CLEAR_MASK;
|
|
regval |= DMABMR_SET_MASK;
|
|
lpc43_putreg(regval, LPC43_ETH_DMABMODE);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_macaddress
|
|
*
|
|
* Description:
|
|
* Configure the selected MAC address.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lpc43_macaddress(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
uint32_t regval;
|
|
|
|
nllvdbg("%s MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
dev->d_ifname,
|
|
dev->d_mac.ether_addr_octet[0], dev->d_mac.ether_addr_octet[1],
|
|
dev->d_mac.ether_addr_octet[2], dev->d_mac.ether_addr_octet[3],
|
|
dev->d_mac.ether_addr_octet[4], dev->d_mac.ether_addr_octet[5]);
|
|
|
|
/* Set the MAC address high register */
|
|
|
|
regval = ((uint32_t)dev->d_mac.ether_addr_octet[5] << 8) |
|
|
(uint32_t)dev->d_mac.ether_addr_octet[4];
|
|
lpc43_putreg(regval, LPC43_ETH_MACA0HI);
|
|
|
|
/* Set the MAC address low register */
|
|
|
|
regval = ((uint32_t)dev->d_mac.ether_addr_octet[3] << 24) |
|
|
((uint32_t)dev->d_mac.ether_addr_octet[2] << 16) |
|
|
((uint32_t)dev->d_mac.ether_addr_octet[1] << 8) |
|
|
(uint32_t)dev->d_mac.ether_addr_octet[0];
|
|
lpc43_putreg(regval, LPC43_ETH_MACA0LO);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_ipv6multicast
|
|
*
|
|
* Description:
|
|
* Configure the IPv6 multicast MAC address.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
static void lpc43_ipv6multicast(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
struct net_driver_s *dev;
|
|
uint16_t tmp16;
|
|
uint8_t mac[6];
|
|
|
|
/* For ICMPv6, we need to add the IPv6 multicast address
|
|
*
|
|
* For IPv6 multicast addresses, the Ethernet MAC is derived by
|
|
* the four low-order octets OR'ed with the MAC 33:33:00:00:00:00,
|
|
* so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map
|
|
* to the Ethernet MAC address 33:33:00:01:00:03.
|
|
*
|
|
* NOTES: This appears correct for the ICMPv6 Router Solicitation
|
|
* Message, but the ICMPv6 Neighbor Solicitation message seems to
|
|
* use 33:33:ff:01:00:03.
|
|
*/
|
|
|
|
mac[0] = 0x33;
|
|
mac[1] = 0x33;
|
|
|
|
dev = &priv->dev;
|
|
tmp16 = dev->d_ipv6addr[6];
|
|
mac[2] = 0xff;
|
|
mac[3] = tmp16 >> 8;
|
|
|
|
tmp16 = dev->d_ipv6addr[7];
|
|
mac[4] = tmp16 & 0xff;
|
|
mac[5] = tmp16 >> 8;
|
|
|
|
nvdbg("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
(void)lpc43_addmac(dev, mac);
|
|
|
|
#ifdef CONFIG_NET_ICMPv6_AUTOCONF
|
|
/* Add the IPv6 all link-local nodes Ethernet address. This is the
|
|
* address that we expect to receive ICMPv6 Router Advertisement
|
|
* packets.
|
|
*/
|
|
|
|
(void)lpc43_addmac(dev, g_ipv6_ethallnodes.ether_addr_octet);
|
|
|
|
#endif /* CONFIG_NET_ICMPv6_AUTOCONF */
|
|
#ifdef CONFIG_NET_ICMPv6_ROUTER
|
|
/* Add the IPv6 all link-local routers Ethernet address. This is the
|
|
* address that we expect to receive ICMPv6 Router Solicitation
|
|
* packets.
|
|
*/
|
|
|
|
(void)lpc43_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet);
|
|
|
|
#endif /* CONFIG_NET_ICMPv6_ROUTER */
|
|
}
|
|
#endif /* CONFIG_NET_ICMPv6 */
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_macenable
|
|
*
|
|
* Description:
|
|
* Enable normal MAC operation.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_macenable(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Set the MAC address */
|
|
|
|
lpc43_macaddress(priv);
|
|
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
/* Set up the IPv6 multicast address */
|
|
|
|
lpc43_ipv6multicast(priv);
|
|
#endif
|
|
|
|
/* Enable transmit state machine of the MAC for transmission on the MII */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACCR);
|
|
regval |= ETH_MACCFG_TE;
|
|
lpc43_putreg(regval, LPC43_ETH_MACCR);
|
|
|
|
/* Flush Transmit FIFO */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMAOPMODE);
|
|
regval |= ETH_DMAOPMODE_FTF;
|
|
lpc43_putreg(regval, LPC43_ETH_DMAOPMODE);
|
|
|
|
/* Enable receive state machine of the MAC for reception from the MII */
|
|
|
|
/* Enables or disables the MAC reception. */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_MACCR);
|
|
regval |= ETH_MACCFG_RE;
|
|
lpc43_putreg(regval, LPC43_ETH_MACCR);
|
|
|
|
/* Start DMA transmission */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMAOPMODE);
|
|
regval |= ETH_DMAOPMODE_ST;
|
|
lpc43_putreg(regval, LPC43_ETH_DMAOPMODE);
|
|
|
|
/* Start DMA reception */
|
|
|
|
regval = lpc43_getreg(LPC43_ETH_DMAOPMODE);
|
|
regval |= ETH_DMAOPMODE_SR;
|
|
lpc43_putreg(regval, LPC43_ETH_DMAOPMODE);
|
|
|
|
/* Enable Ethernet DMA interrupts.
|
|
*
|
|
* The LPC43 hardware supports two interrupts: (1) one dedicated to normal
|
|
* Ethernet operations and the other, used only for the Ethernet wakeup
|
|
* event. The wake-up interrupt is not used by this driver.
|
|
*
|
|
* The first Ethernet vector is reserved for interrupts generated by the
|
|
* MAC and the DMA. The MAC provides PMT and time stamp trigger interrupts,
|
|
* neither of which are used by this driver.
|
|
*/
|
|
|
|
lpc43_putreg(ETH_MACIM_ALLINTS, LPC43_ETH_MACIM);
|
|
|
|
/* Ethernet DMA supports two classes of interrupts: Normal interrupt
|
|
* summary (NIS) and Abnormal interrupt summary (AIS) with a variety
|
|
* individual normal and abnormal interrupting events. Here only
|
|
* the normal receive event is enabled (unless DEBUG is enabled). Transmit
|
|
* events will only be enabled when a transmit interrupt is expected.
|
|
*/
|
|
|
|
lpc43_putreg((ETH_DMAINT_RECV_ENABLE | ETH_DMAINT_ERROR_ENABLE),
|
|
LPC43_ETH_DMAINTEN);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_ethconfig
|
|
*
|
|
* Description:
|
|
* Configure the Ethernet interface for DMA operation.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lpc43_ethconfig(FAR struct lpc43_ethmac_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
/* NOTE: The Ethernet clocks were initialized early in the boot-up
|
|
* sequence in lpc43_rcc.c.
|
|
*/
|
|
|
|
/* Reset the Ethernet block */
|
|
|
|
nllvdbg("Reset the Ethernet block\n");
|
|
lpc43_ethreset(priv);
|
|
|
|
/* Initialize the PHY */
|
|
|
|
nllvdbg("Initialize the PHY\n");
|
|
ret = lpc43_phyinit(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Initialize the MAC and DMA */
|
|
|
|
nllvdbg("Initialize the MAC and DMA\n");
|
|
ret = lpc43_macconfig(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Initialize the free buffer list */
|
|
|
|
lpc43_initbuffer(priv);
|
|
|
|
/* Initialize TX Descriptors list: Chain Mode */
|
|
|
|
lpc43_txdescinit(priv);
|
|
|
|
/* Initialize RX Descriptors list: Chain Mode */
|
|
|
|
lpc43_rxdescinit(priv);
|
|
|
|
/* Enable normal MAC operation */
|
|
|
|
nllvdbg("Enable normal operation\n");
|
|
return lpc43_macenable(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Function: lpc43_ethinitialize
|
|
*
|
|
* Description:
|
|
* Initialize the Ethernet driver for one interface.
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int lpc43_ethinitialize(void)
|
|
{
|
|
struct lpc43_ethmac_s *priv;
|
|
|
|
/* Get the interface structure associated with this interface number. */
|
|
|
|
priv = &g_lpc43ethmac;
|
|
|
|
/* Initialize the driver structure */
|
|
|
|
memset(priv, 0, sizeof(struct lpc43_ethmac_s));
|
|
priv->dev.d_ifup = lpc43_ifup; /* I/F up (new IP address) callback */
|
|
priv->dev.d_ifdown = lpc43_ifdown; /* I/F down callback */
|
|
priv->dev.d_txavail = lpc43_txavail; /* New TX data callback */
|
|
#ifdef CONFIG_NET_IGMP
|
|
priv->dev.d_addmac = lpc43_addmac; /* Add multicast MAC address */
|
|
priv->dev.d_rmmac = lpc43_rmmac; /* Remove multicast MAC address */
|
|
#endif
|
|
#ifdef CONFIG_NETDEV_PHY_IOCTL
|
|
priv->dev.d_ioctl = lpc43_ioctl; /* Support PHY ioctl() calls */
|
|
#endif
|
|
priv->dev.d_private = (void *)&g_lpc43ethmac; /* Used to recover private state from dev */
|
|
|
|
/* Create a watchdog for timing polling for and timing of transmisstions */
|
|
|
|
priv->txpoll = wd_create(); /* Create periodic poll timer */
|
|
priv->txtimeout = wd_create(); /* Create TX timeout timer */
|
|
|
|
/* Configure GPIO pins to support Ethernet */
|
|
|
|
lpc43_ethgpioconfig(priv);
|
|
|
|
/* Attach the IRQ to the driver */
|
|
|
|
if (irq_attach(LPC43M4_IRQ_ETHERNET, lpc43_interrupt))
|
|
{
|
|
/* We could not attach the ISR to the interrupt */
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Put the interface in the down state. */
|
|
|
|
lpc43_ifdown(&priv->dev);
|
|
|
|
/* Register the device with the OS so that socket IOCTLs can be performed */
|
|
|
|
(void)netdev_register(&priv->dev, NET_LL_ETHERNET);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: up_netinitialize
|
|
*
|
|
* Description:
|
|
* This is the "standard" network initialization logic called from the
|
|
* low-level initialization logic in up_initialize.c.
|
|
*
|
|
* Parameters:
|
|
* None.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
void up_netinitialize(void)
|
|
{
|
|
(void)lpc43_ethinitialize();
|
|
}
|
|
|
|
#endif /* CONFIG_NET && CONFIG_LPC43_ETHERNET */
|