4220 lines
120 KiB
C
4220 lines
120 KiB
C
/****************************************************************************
|
|
* arch/arm/src/stm32/stm32_eth.c
|
|
*
|
|
* Copyright (C) 2011-2012, 2014 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_STM32_ETHMAC)
|
|
|
|
#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 "stm32_gpio.h"
|
|
#include "stm32_rcc.h"
|
|
#include "stm32_syscfg.h"
|
|
#include "stm32_eth.h"
|
|
|
|
#include <arch/board/board.h>
|
|
|
|
/* STM32_NETHERNET determines the number of physical interfaces
|
|
* that will be supported.
|
|
*/
|
|
|
|
#if STM32_NETHERNET > 0
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
/* Configuration ************************************************************/
|
|
/* See configs/stm3240g-eval/README.txt for an explanation of the configuration
|
|
* settings.
|
|
*/
|
|
|
|
#if STM32_NETHERNET > 1
|
|
# error "Logic to support multiple Ethernet interfaces is incomplete"
|
|
#endif
|
|
|
|
/* If processing is not done at the interrupt level, then work queue support
|
|
* is required.
|
|
*/
|
|
|
|
#if defined(CONFIG_NET_NOINTS) && !defined(CONFIG_SCHED_WORKQUEUE)
|
|
# error Work queue support is required
|
|
#endif
|
|
|
|
/* Select work queue */
|
|
|
|
#if defined(CONFIG_SCHED_WORKQUEUE)
|
|
# if defined(CONFIG_STM32_ETHMAC_HPWORK)
|
|
# define ETHWORK HPWORK
|
|
# elif defined(CONFIG_STM32_ETHMAC_LPWORK)
|
|
# define ETHWORK LPWORK
|
|
# else
|
|
# error Neither CONFIG_STM32_ETHMAC_HPWORK nor CONFIG_STM32_ETHMAC_LPWORK defined
|
|
# endif
|
|
#endif
|
|
|
|
#if !defined(CONFIG_STM32_SYSCFG) && !defined(CONFIG_STM32_CONNECTIVITYLINE)
|
|
# error "CONFIG_STM32_SYSCFG must be defined in the NuttX configuration"
|
|
#endif
|
|
|
|
#ifndef CONFIG_STM32_PHYADDR
|
|
# error "CONFIG_STM32_PHYADDR must be defined in the NuttX configuration"
|
|
#endif
|
|
|
|
#if !defined(CONFIG_STM32_MII) && !defined(CONFIG_STM32_RMII)
|
|
# warning "Neither CONFIG_STM32_MII nor CONFIG_STM32_RMII defined"
|
|
#endif
|
|
|
|
#if defined(CONFIG_STM32_MII) && defined(CONFIG_STM32_RMII)
|
|
# error "Both CONFIG_STM32_MII and CONFIG_STM32_RMII defined"
|
|
#endif
|
|
|
|
#ifdef CONFIG_STM32_MII
|
|
# if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F40XX)
|
|
# if !defined(CONFIG_STM32_MII_MCO1) && !defined(CONFIG_STM32_MII_MCO2) && !defined(CONFIG_STM32_MII_EXTCLK)
|
|
# warning "Neither CONFIG_STM32_MII_MCO1, CONFIG_STM32_MII_MCO2, nor CONFIG_STM32_MII_EXTCLK defined"
|
|
# endif
|
|
# if defined(CONFIG_STM32_MII_MCO1) && defined(CONFIG_STM32_MII_MCO2)
|
|
# error "Both CONFIG_STM32_MII_MCO1 and CONFIG_STM32_MII_MCO2 defined"
|
|
# endif
|
|
# elif defined(CONFIG_STM32_CONNECTIVITYLINE)
|
|
# if !defined(CONFIG_STM32_MII_MCO) && !defined(CONFIG_STM32_MII_EXTCLK)
|
|
# warning "Neither CONFIG_STM32_MII_MCO nor CONFIG_STM32_MII_EXTCLK defined"
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_STM32_RMII
|
|
# if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F40XX)
|
|
# if !defined(CONFIG_STM32_RMII_MCO1) && !defined(CONFIG_STM32_RMII_MCO2) && !defined(CONFIG_STM32_RMII_EXTCLK)
|
|
# warning "Neither CONFIG_STM32_RMII_MCO1, CONFIG_STM32_RMII_MCO2, nor CONFIG_STM32_RMII_EXTCLK defined"
|
|
# endif
|
|
# if defined(CONFIG_STM32_RMII_MCO1) && defined(CONFIG_STM32_RMII_MCO2)
|
|
# error "Both CONFIG_STM32_RMII_MCO1 and CONFIG_STM32_RMII_MCO2 defined"
|
|
# endif
|
|
# elif defined(CONFIG_STM32_CONNECTIVITYLINE)
|
|
# if !defined(CONFIG_STM32_RMII_MCO) && !defined(CONFIG_STM32_RMII_EXTCLK)
|
|
# warning "Neither CONFIG_STM32_RMII_MCO nor CONFIG_STM32_RMII_EXTCLK defined"
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_STM32_AUTONEG
|
|
# ifndef CONFIG_STM32_PHYSR
|
|
# error "CONFIG_STM32_PHYSR must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifdef CONFIG_STM32_PHYSR_ALTCONFIG
|
|
# ifndef CONFIG_STM32_PHYSR_ALTMODE
|
|
# error "CONFIG_STM32_PHYSR_ALTMODE must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_STM32_PHYSR_10HD
|
|
# error "CONFIG_STM32_PHYSR_10HD must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_STM32_PHYSR_100HD
|
|
# error "CONFIG_STM32_PHYSR_100HD must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_STM32_PHYSR_10FD
|
|
# error "CONFIG_STM32_PHYSR_10FD must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_STM32_PHYSR_100FD
|
|
# error "CONFIG_STM32_PHYSR_100FD must be defined in the NuttX configuration"
|
|
# endif
|
|
# else
|
|
# ifndef CONFIG_STM32_PHYSR_SPEED
|
|
# error "CONFIG_STM32_PHYSR_SPEED must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_STM32_PHYSR_100MBPS
|
|
# error "CONFIG_STM32_PHYSR_100MBPS must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_STM32_PHYSR_MODE
|
|
# error "CONFIG_STM32_PHYSR_MODE must be defined in the NuttX configuration"
|
|
# endif
|
|
# ifndef CONFIG_STM32_PHYSR_FULLDUPLEX
|
|
# error "CONFIG_STM32_PHYSR_FULLDUPLEX must be defined in the NuttX configuration"
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_STM32_ETH_PTP
|
|
# warning "CONFIG_STM32_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_STM32_ETH_ENHANCEDDESC
|
|
#undef CONFIG_STM32_ETH_HWCHECKSUM
|
|
|
|
/* 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_STM32_ETH_BUFSIZE
|
|
# define CONFIG_STM32_ETH_BUFSIZE OPTIMAL_ETH_BUFSIZE
|
|
#endif
|
|
|
|
#if CONFIG_STM32_ETH_BUFSIZE > ETH_TDES1_TBS1_MASK
|
|
# error "CONFIG_STM32_ETH_BUFSIZE is too large"
|
|
#endif
|
|
|
|
#if (CONFIG_STM32_ETH_BUFSIZE & 15) != 0
|
|
# error "CONFIG_STM32_ETH_BUFSIZE must be aligned"
|
|
#endif
|
|
|
|
#if CONFIG_STM32_ETH_BUFSIZE != OPTIMAL_ETH_BUFSIZE
|
|
# warning "You using an incomplete/untested configuration"
|
|
#endif
|
|
|
|
#ifndef CONFIG_STM32_ETH_NRXDESC
|
|
# define CONFIG_STM32_ETH_NRXDESC 8
|
|
#endif
|
|
#ifndef CONFIG_STM32_ETH_NTXDESC
|
|
# define CONFIG_STM32_ETH_NTXDESC 4
|
|
#endif
|
|
|
|
/* We need at least one more free buffer than transmit buffers */
|
|
|
|
#define STM32_ETH_NFREEBUFFERS (CONFIG_STM32_ETH_NTXDESC+1)
|
|
|
|
/* Extremely detailed register debug that you would normally never want
|
|
* enabled.
|
|
*/
|
|
|
|
#ifndef CONFIG_DEBUG_NET_INFO
|
|
# undef CONFIG_STM32_ETHMAC_REGDEBUG
|
|
#endif
|
|
|
|
/* Clocking *****************************************************************/
|
|
/* Set MACMIIAR CR bits depending on HCLK setting */
|
|
|
|
#if STM32_HCLK_FREQUENCY >= 20000000 && STM32_HCLK_FREQUENCY < 35000000
|
|
# define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_20_35
|
|
#elif STM32_HCLK_FREQUENCY >= 35000000 && STM32_HCLK_FREQUENCY < 60000000
|
|
# define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_35_60
|
|
#elif STM32_HCLK_FREQUENCY >= 60000000 && STM32_HCLK_FREQUENCY < 100000000
|
|
# define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_60_100
|
|
#elif STM32_HCLK_FREQUENCY >= 100000000 && STM32_HCLK_FREQUENCY < 150000000
|
|
# define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_100_150
|
|
#elif STM32_HCLK_FREQUENCY >= 150000000 && STM32_HCLK_FREQUENCY <= 180000000
|
|
# define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_150_180
|
|
#else
|
|
# error "STM32_HCLK_FREQUENCY not supportable"
|
|
#endif
|
|
|
|
/* Timing *******************************************************************/
|
|
/* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per
|
|
* second
|
|
*/
|
|
|
|
#define STM32_WDDELAY (1*CLK_TCK)
|
|
|
|
/* TX timeout = 1 minute */
|
|
|
|
#define STM32_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_MACCR_RE Bit 2: Receiver enable
|
|
* ETH_MACCR_TE Bit 3: Transmitter enable
|
|
* ETH_MACCR_DC Bit 4: Deferral check
|
|
* ETH_MACCR_BL Bits 5-6: Back-off limit
|
|
* ETH_MACCR_APCS Bit 7: Automatic pad/CRC stripping
|
|
* ETH_MACCR_RD Bit 9: Retry disable
|
|
* ETH_MACCR_IPCO Bit 10: IPv4 checksum offload
|
|
* ETH_MACCR_DM Bit 11: Duplex mode
|
|
* ETH_MACCR_LM Bit 12: Loopback mode
|
|
* ETH_MACCR_ROD Bit 13: Receive own disable
|
|
* ETH_MACCR_FES Bit 14: Fast Ethernet speed
|
|
* ETH_MACCR_CSD Bit 16: Carrier sense disable
|
|
* ETH_MACCR_IFG Bits 17-19: Interframe gap
|
|
* ETH_MACCR_JD Bit 22: Jabber disable
|
|
* ETH_MACCR_WD Bit 23: Watchdog disable
|
|
* ETH_MACCR_CSTF Bits 25: CRC stripping for Type frames (F2/F4 only)
|
|
*/
|
|
|
|
#if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F40XX)
|
|
#define MACCR_CLEAR_BITS \
|
|
(ETH_MACCR_RE | ETH_MACCR_TE | ETH_MACCR_DC | ETH_MACCR_BL_MASK | \
|
|
ETH_MACCR_APCS | ETH_MACCR_RD | ETH_MACCR_IPCO | ETH_MACCR_DM | \
|
|
ETH_MACCR_LM | ETH_MACCR_ROD | ETH_MACCR_FES | ETH_MACCR_CSD | \
|
|
ETH_MACCR_IFG_MASK | ETH_MACCR_JD | ETH_MACCR_WD | ETH_MACCR_CSTF)
|
|
#else
|
|
#define MACCR_CLEAR_BITS \
|
|
(ETH_MACCR_RE | ETH_MACCR_TE | ETH_MACCR_DC | ETH_MACCR_BL_MASK | \
|
|
ETH_MACCR_APCS | ETH_MACCR_RD | ETH_MACCR_IPCO | ETH_MACCR_DM | \
|
|
ETH_MACCR_LM | ETH_MACCR_ROD | ETH_MACCR_FES | ETH_MACCR_CSD | \
|
|
ETH_MACCR_IFG_MASK | ETH_MACCR_JD | ETH_MACCR_WD)
|
|
#endif
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
* ETH_MACCR_RE Receiver enable 0 (disabled)
|
|
* ETH_MACCR_TE Transmitter enable 0 (disabled)
|
|
* ETH_MACCR_DC Deferral check 0 (disabled)
|
|
* ETH_MACCR_BL Back-off limit 0 (10)
|
|
* ETH_MACCR_APCS Automatic pad/CRC stripping 0 (disabled)
|
|
* ETH_MACCR_RD Retry disable 1 (disabled)
|
|
* ETH_MACCR_IPCO IPv4 checksum offload Depends on CONFIG_STM32_ETH_HWCHECKSUM
|
|
* ETH_MACCR_LM Loopback mode 0 (disabled)
|
|
* ETH_MACCR_ROD Receive own disable 0 (enabled)
|
|
* ETH_MACCR_CSD Carrier sense disable 0 (enabled)
|
|
* ETH_MACCR_IFG Interframe gap 0 (96 bits)
|
|
* ETH_MACCR_JD Jabber disable 0 (enabled)
|
|
* ETH_MACCR_WD Watchdog disable 0 (enabled)
|
|
* ETH_MACCR_CSTF CRC stripping for Type frames 0 (disabled, F2/F4 only)
|
|
*
|
|
* The following are set conditioinally based on mode and speed.
|
|
*
|
|
* ETH_MACCR_DM Duplex mode Depends on priv->fduplex
|
|
* ETH_MACCR_FES Fast Ethernet speed Depends on priv->mbps100
|
|
*/
|
|
|
|
#ifdef CONFIG_STM32_ETH_HWCHECKSUM
|
|
# define MACCR_SET_BITS \
|
|
(ETH_MACCR_BL_10 | ETH_MACCR_RD | ETH_MACCR_IPCO | ETH_MACCR_IFG(96))
|
|
#else
|
|
# define MACCR_SET_BITS \
|
|
(ETH_MACCR_BL_10 | ETH_MACCR_RD | ETH_MACCR_IFG(96))
|
|
#endif
|
|
|
|
/* 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_MACFFR_PM | ETH_MACFFR_HU | ETH_MACFFR_HM | ETH_MACFFR_DAIF | \
|
|
ETH_MACFFR_PAM | ETH_MACFFR_BFD | ETH_MACFFR_PCF_MASK | ETH_MACFFR_SAIF | \
|
|
ETH_MACFFR_SAF | ETH_MACFFR_HPF | ETH_MACFFR_RA)
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
* ETH_MACFFR_PM Promiscuous mode 0 (disabled)
|
|
* ETH_MACFFR_HU Hash unicast 0 (perfect dest filtering)
|
|
* ETH_MACFFR_HM Hash multicast 0 (perfect dest filtering)
|
|
* ETH_MACFFR_DAIF Destination address inverse filtering 0 (normal)
|
|
* ETH_MACFFR_PAM Pass all multicast 0 (Depends on HM bit)
|
|
* ETH_MACFFR_BFD Broadcast frames disable 0 (enabled)
|
|
* ETH_MACFFR_PCF Pass control frames 1 (block all but PAUSE)
|
|
* ETH_MACFFR_SAIF Source address inverse filtering 0 (not used)
|
|
* ETH_MACFFR_SAF Source address filter 0 (disabled)
|
|
* ETH_MACFFR_HPF Hash or perfect filter 0 (Only matching frames passed)
|
|
* ETH_MACFFR_RA Receive all 0 (disabled)
|
|
*/
|
|
|
|
#define MACFFR_SET_BITS (ETH_MACFFR_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_MACFCR_FCB_BPA | ETH_MACFCR_TFCE | ETH_MACFCR_RFCE | ETH_MACFCR_UPFD | \
|
|
ETH_MACFCR_PLT_MASK | ETH_MACFCR_ZQPD | ETH_MACFCR_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_MACFCR_PLT_M4 | ETH_MACFCR_ZQPD)
|
|
|
|
/* 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_DMAOMR_SR Bit 1: Start/stop receive
|
|
* TH_DMAOMR_OSF Bit 2: Operate on second frame
|
|
* ETH_DMAOMR_RTC Bits 3-4: Receive threshold control
|
|
* ETH_DMAOMR_FUGF Bit 6: Forward undersized good frames
|
|
* ETH_DMAOMR_FEF Bit 7: Forward error frames
|
|
* ETH_DMAOMR_ST Bit 13: Start/stop transmission
|
|
* ETH_DMAOMR_TTC Bits 14-16: Transmit threshold control
|
|
* ETH_DMAOMR_FTF Bit 20: Flush transmit FIFO
|
|
* ETH_DMAOMR_TSF Bit 21: Transmit store and forward
|
|
* ETH_DMAOMR_DFRF Bit 24: Disable flushing of received frames
|
|
* ETH_DMAOMR_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_DMAOMR_SR | ETH_DMAOMR_OSF | ETH_DMAOMR_RTC_MASK | ETH_DMAOMR_FUGF | \
|
|
ETH_DMAOMR_FEF | ETH_DMAOMR_ST | ETH_DMAOMR_TTC_MASK | ETH_DMAOMR_FTF | \
|
|
ETH_DMAOMR_TSF | ETH_DMAOMR_DFRF | ETH_DMAOMR_RSF | ETH_DMAOMR_DTCEFD)
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
* ETH_DMAOMR_SR Start/stop receive 0 (not running)
|
|
* TH_DMAOMR_OSF Operate on second frame 1 (enabled)
|
|
* ETH_DMAOMR_RTC Receive threshold control 0 (64 bytes)
|
|
* ETH_DMAOMR_FUGF Forward undersized good frames 0 (disabled)
|
|
* ETH_DMAOMR_FEF Forward error frames 0 (disabled)
|
|
* ETH_DMAOMR_ST Start/stop transmission 0 (not running)
|
|
* ETH_DMAOMR_TTC Transmit threshold control 0 (64 bytes)
|
|
* ETH_DMAOMR_FTF Flush transmit FIFO 0 (no flush)
|
|
* ETH_DMAOMR_TSF Transmit store and forward Depends on CONFIG_STM32_ETH_HWCHECKSUM
|
|
* ETH_DMAOMR_DFRF Disable flushing of received frames 0 (enabled)
|
|
* ETH_DMAOMR_RSF Receive store and forward Depends on CONFIG_STM32_ETH_HWCHECKSUM
|
|
* TH_DMAOMR_DTCEFD Dropping of TCP/IP checksum error Depends on CONFIG_STM32_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
|
|
*/
|
|
|
|
#ifdef CONFIG_STM32_ETH_HWCHECKSUM
|
|
# define DMAOMR_SET_MASK \
|
|
(ETH_DMAOMR_OSF | ETH_DMAOMR_RTC_64 | ETH_DMAOMR_TTC_64 | \
|
|
ETH_DMAOMR_TSF | ETH_DMAOMR_RSF)
|
|
#else
|
|
# define DMAOMR_SET_MASK \
|
|
(ETH_DMAOMR_OSF | ETH_DMAOMR_RTC_64 | ETH_DMAOMR_TTC_64 | \
|
|
ETH_DMAOMR_DTCEFD)
|
|
#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_DMABMR_SR Bit 0: Software reset
|
|
* ETH_DMABMR_DA Bit 1: DMA Arbitration
|
|
* ETH_DMABMR_DSL Bits 2-6: Descriptor skip length
|
|
* ETH_DMABMR_EDFE Bit 7: Enhanced descriptor format enable
|
|
* ETH_DMABMR_PBL Bits 8-13: Programmable burst length
|
|
* ETH_DMABMR_RTPR Bits 14-15: RX TX priority ratio
|
|
* ETH_DMABMR_FB Bit 16: Fixed burst
|
|
* ETH_DMABMR_RDP Bits 17-22: RX DMA PBL
|
|
* ETH_DMABMR_USP Bit 23: Use separate PBL
|
|
* ETH_DMABMR_FPM Bit 24: 4xPBL mode
|
|
* ETH_DMABMR_AAB Bit 25: Address-aligned beats
|
|
* ETH_DMABMR_MB Bit 26: Mixed burst (F2/F4 only)
|
|
*/
|
|
|
|
#if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F40XX)
|
|
#define DMABMR_CLEAR_MASK \
|
|
(ETH_DMABMR_SR | ETH_DMABMR_DA | ETH_DMABMR_DSL_MASK | ETH_DMABMR_EDFE | \
|
|
ETH_DMABMR_PBL_MASK | ETH_DMABMR_RTPR_MASK | ETH_DMABMR_FB | ETH_DMABMR_RDP_MASK | \
|
|
ETH_DMABMR_USP | ETH_DMABMR_FPM | ETH_DMABMR_AAB | ETH_DMABMR_MB)
|
|
#else
|
|
#define DMABMR_CLEAR_MASK \
|
|
(ETH_DMABMR_SR | ETH_DMABMR_DA | ETH_DMABMR_DSL_MASK | ETH_DMABMR_EDFE | \
|
|
ETH_DMABMR_PBL_MASK | ETH_DMABMR_RTPR_MASK | ETH_DMABMR_FB | ETH_DMABMR_RDP_MASK | \
|
|
ETH_DMABMR_USP | ETH_DMABMR_FPM | ETH_DMABMR_AAB)
|
|
#endif
|
|
|
|
/* The following bits are set or left zero unconditionally in all modes.
|
|
*
|
|
*
|
|
* ETH_DMABMR_SR Software reset 0 (no reset)
|
|
* ETH_DMABMR_DA DMA Arbitration 0 (round robin)
|
|
* ETH_DMABMR_DSL Descriptor skip length 0
|
|
* ETH_DMABMR_EDFE Enhanced descriptor format enable Depends on CONFIG_STM32_ETH_ENHANCEDDESC
|
|
* ETH_DMABMR_PBL Programmable burst length 32 beats
|
|
* ETH_DMABMR_RTPR RX TX priority ratio 2:1
|
|
* ETH_DMABMR_FB Fixed burst 1 (enabled)
|
|
* ETH_DMABMR_RDP RX DMA PBL 32 beats
|
|
* ETH_DMABMR_USP Use separate PBL 1 (enabled)
|
|
* ETH_DMABMR_FPM 4xPBL mode 0 (disabled)
|
|
* ETH_DMABMR_AAB Address-aligned beats 1 (enabled)
|
|
* ETH_DMABMR_MB Mixed burst 0 (disabled, F2/F4 only)
|
|
*/
|
|
|
|
#ifdef CONFIG_STM32_ETH_ENHANCEDDESC
|
|
# define DMABMR_SET_MASK \
|
|
(ETH_DMABMR_DSL(0) | ETH_DMABMR_PBL(32) | ETH_DMABMR_EDFE | ETH_DMABMR_RTPR_2TO1 | \
|
|
ETH_DMABMR_FB | ETH_DMABMR_RDP(32) | ETH_DMABMR_USP | ETH_DMABMR_AAB)
|
|
#else
|
|
# define DMABMR_SET_MASK \
|
|
(ETH_DMABMR_DSL(0) | ETH_DMABMR_PBL(32) | ETH_DMABMR_RTPR_2TO1 | ETH_DMABMR_FB | \
|
|
ETH_DMABMR_RDP(32) | ETH_DMABMR_USP | ETH_DMABMR_AAB)
|
|
#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_TBUI | ETH_DMAINT_RI | ETH_DMAINT_ERI)
|
|
|
|
#define ETH_DMAINT_ABNORMAL \
|
|
(ETH_DMAINT_TPSI | ETH_DMAINT_TJTI | ETH_DMAINT_ROI | ETH_DMAINT_TUI | \
|
|
ETH_DMAINT_RBUI | ETH_DMAINT_RPSI | ETH_DMAINT_RWTI | /* ETH_DMAINT_ETI | */ \
|
|
ETH_DMAINT_FBEI)
|
|
|
|
/* 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 stm32_ethmac_s encapsulates all state information for a single hardware
|
|
* interface
|
|
*/
|
|
|
|
struct stm32_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 the NuttX network */
|
|
|
|
struct net_driver_s dev; /* Interface understood by the network */
|
|
|
|
/* 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_STM32_ETH_NRXDESC];
|
|
struct eth_txdesc_s txtable[CONFIG_STM32_ETH_NTXDESC];
|
|
|
|
/* Buffer allocations */
|
|
|
|
uint8_t rxbuffer[CONFIG_STM32_ETH_NRXDESC*CONFIG_STM32_ETH_BUFSIZE];
|
|
uint8_t alloc[STM32_ETH_NFREEBUFFERS*CONFIG_STM32_ETH_BUFSIZE];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct stm32_ethmac_s g_stm32ethmac[STM32_NETHERNET];
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
/* Register operations ******************************************************/
|
|
|
|
#if defined(CONFIG_STM32_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG_FEATURES)
|
|
static uint32_t stm32_getreg(uint32_t addr);
|
|
static void stm32_putreg(uint32_t val, uint32_t addr);
|
|
static void stm32_checksetup(void);
|
|
#else
|
|
# define stm32_getreg(addr) getreg32(addr)
|
|
# define stm32_putreg(val,addr) putreg32(val,addr)
|
|
# define stm32_checksetup()
|
|
#endif
|
|
|
|
/* Free buffer management */
|
|
|
|
static void stm32_initbuffer(FAR struct stm32_ethmac_s *priv);
|
|
static inline uint8_t *stm32_allocbuffer(FAR struct stm32_ethmac_s *priv);
|
|
static inline void stm32_freebuffer(FAR struct stm32_ethmac_s *priv, uint8_t *buffer);
|
|
static inline bool stm32_isfreebuffer(FAR struct stm32_ethmac_s *priv);
|
|
|
|
/* Common TX logic */
|
|
|
|
static int stm32_transmit(FAR struct stm32_ethmac_s *priv);
|
|
static int stm32_txpoll(struct net_driver_s *dev);
|
|
static void stm32_dopoll(FAR struct stm32_ethmac_s *priv);
|
|
|
|
/* Interrupt handling */
|
|
|
|
static void stm32_enableint(FAR struct stm32_ethmac_s *priv, uint32_t ierbit);
|
|
static void stm32_disableint(FAR struct stm32_ethmac_s *priv, uint32_t ierbit);
|
|
|
|
static void stm32_freesegment(FAR struct stm32_ethmac_s *priv,
|
|
FAR struct eth_rxdesc_s *rxfirst, int segments);
|
|
static int stm32_recvframe(FAR struct stm32_ethmac_s *priv);
|
|
static void stm32_receive(FAR struct stm32_ethmac_s *priv);
|
|
static void stm32_freeframe(FAR struct stm32_ethmac_s *priv);
|
|
static void stm32_txdone(FAR struct stm32_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void stm32_interrupt_work(FAR void *arg);
|
|
#endif
|
|
static int stm32_interrupt(int irq, FAR void *context);
|
|
|
|
/* Watchdog timer expirations */
|
|
|
|
static inline void stm32_txtimeout_process(FAR struct stm32_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void stm32_txtimeout_work(FAR void *arg);
|
|
#endif
|
|
static void stm32_txtimeout_expiry(int argc, uint32_t arg, ...);
|
|
|
|
static inline void stm32_poll_process(FAR struct stm32_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void stm32_poll_work(FAR void *arg);
|
|
#endif
|
|
static void stm32_poll_expiry(int argc, uint32_t arg, ...);
|
|
|
|
/* NuttX callback functions */
|
|
|
|
static int stm32_ifup(struct net_driver_s *dev);
|
|
static int stm32_ifdown(struct net_driver_s *dev);
|
|
static inline void stm32_txavail_process(FAR struct stm32_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_NOINTS
|
|
static void stm32_txavail_work(FAR void *arg);
|
|
#endif
|
|
static int stm32_txavail(struct net_driver_s *dev);
|
|
#if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6)
|
|
static int stm32_addmac(struct net_driver_s *dev, FAR const uint8_t *mac);
|
|
#endif
|
|
#ifdef CONFIG_NET_IGMP
|
|
static int stm32_rmmac(struct net_driver_s *dev, FAR const uint8_t *mac);
|
|
#endif
|
|
#ifdef CONFIG_NETDEV_PHY_IOCTL
|
|
static int stm32_ioctl(struct net_driver_s *dev, int cmd, long arg);
|
|
#endif
|
|
|
|
/* Descriptor Initialization */
|
|
|
|
static void stm32_txdescinit(FAR struct stm32_ethmac_s *priv);
|
|
static void stm32_rxdescinit(FAR struct stm32_ethmac_s *priv);
|
|
|
|
/* PHY Initialization */
|
|
|
|
#if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT)
|
|
static int stm32_phyintenable(FAR struct stm32_ethmac_s *priv);
|
|
#endif
|
|
#if defined(CONFIG_STM32_AUTONEG) || defined(CONFIG_NETDEV_PHY_IOCTL) || \
|
|
defined(CONFIG_ETH0_PHY_DM9161)
|
|
static int stm32_phyread(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t *value);
|
|
#endif
|
|
static int stm32_phywrite(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t value);
|
|
#ifdef CONFIG_ETH0_PHY_DM9161
|
|
static inline int stm32_dm9161(FAR struct stm32_ethmac_s *priv);
|
|
#endif
|
|
static int stm32_phyinit(FAR struct stm32_ethmac_s *priv);
|
|
|
|
/* MAC/DMA Initialization */
|
|
|
|
#ifdef CONFIG_STM32_MII
|
|
static inline void stm32_selectmii(void);
|
|
#endif
|
|
#ifdef CONFIG_STM32_RMII
|
|
static inline void stm32_selectrmii(void);
|
|
#endif
|
|
static inline void stm32_ethgpioconfig(FAR struct stm32_ethmac_s *priv);
|
|
static void stm32_ethreset(FAR struct stm32_ethmac_s *priv);
|
|
static int stm32_macconfig(FAR struct stm32_ethmac_s *priv);
|
|
static void stm32_macaddress(FAR struct stm32_ethmac_s *priv);
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
static void stm32_ipv6multicast(FAR struct stm32_ethmac_s *priv);
|
|
#endif
|
|
static int stm32_macenable(FAR struct stm32_ethmac_s *priv);
|
|
static int stm32_ethconfig(FAR struct stm32_ethmac_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: stm32_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
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32_ETHMAC_REGDEBUG
|
|
static uint32_t stm32_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)
|
|
{
|
|
ninfo("...\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 */
|
|
|
|
ninfo("[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 */
|
|
|
|
ninfo("%08x->%08x\n", addr, val);
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_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_STM32_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG_FEATURES)
|
|
static void stm32_putreg(uint32_t val, uint32_t addr)
|
|
{
|
|
/* Show the register value being written */
|
|
|
|
ninfo("%08x<-%08x\n", addr, val);
|
|
|
|
/* Write the value */
|
|
|
|
putreg32(val, addr);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_checksetup
|
|
*
|
|
* Description:
|
|
* Show the state of critical configuration registers.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32_ETHMAC_REGDEBUG
|
|
static void stm32_checksetup(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_initbuffer(FAR struct stm32_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 < STM32_ETH_NFREEBUFFERS;
|
|
i++, buffer += CONFIG_STM32_ETH_BUFSIZE)
|
|
{
|
|
sq_addlast((FAR sq_entry_t *)buffer, &priv->freeb);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 *stm32_allocbuffer(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
/* Allocate a buffer by returning the head of the free buffer list */
|
|
|
|
return (uint8_t *)sq_remfirst(&priv->freeb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_freebuffer(FAR struct stm32_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: stm32_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 stm32_isfreebuffer(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
/* Return TRUE if the free buffer list is not empty */
|
|
|
|
return !sq_empty(&priv->freeb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_transmit(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
struct eth_txdesc_s *txdesc;
|
|
struct eth_txdesc_s *txfirst;
|
|
|
|
/* The internal (optimal) network buffer size may be configured to be larger
|
|
* than the Ethernet buffer size.
|
|
*/
|
|
|
|
#if OPTIMAL_ETH_BUFSIZE > CONFIG_STM32_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;
|
|
|
|
ninfo("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_STM32_ETH_BUFSIZE
|
|
if (priv->dev.d_len > CONFIG_STM32_ETH_BUFSIZE)
|
|
{
|
|
/* Yes... how many buffers will be need to send the packet? */
|
|
|
|
bufcount = (priv->dev.d_len + (CONFIG_STM32_ETH_BUFSIZE-1)) / CONFIG_STM32_ETH_BUFSIZE;
|
|
lastsize = priv->dev.d_len - (bufcount - 1) * CONFIG_STM32_ETH_BUFSIZE;
|
|
|
|
ninfo("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_STM32_ETH_BUFSIZE;
|
|
buffer += CONFIG_STM32_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++;
|
|
|
|
ninfo("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_STM32_ETH_NTXDESC)
|
|
{
|
|
stm32_disableint(priv, ETH_DMAINT_RI);
|
|
}
|
|
|
|
/* Check if the TX Buffer unavailable flag is set */
|
|
|
|
if ((stm32_getreg(STM32_ETH_DMASR) & ETH_DMAINT_TBUI) != 0)
|
|
{
|
|
/* Clear TX Buffer unavailable flag */
|
|
|
|
stm32_putreg(ETH_DMAINT_TBUI, STM32_ETH_DMASR);
|
|
|
|
/* Resume DMA transmission */
|
|
|
|
stm32_putreg(0, STM32_ETH_DMATPDR);
|
|
}
|
|
|
|
/* Enable TX interrupts */
|
|
|
|
stm32_enableint(priv, ETH_DMAINT_TI);
|
|
|
|
/* Setup the TX timeout watchdog (perhaps restarting the timer) */
|
|
|
|
(void)wd_start(priv->txtimeout, STM32_TXTIMEOUT, stm32_txtimeout_expiry, 1, (uint32_t)priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_txpoll
|
|
*
|
|
* Description:
|
|
* The transmitter is available, check if the network 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 stm32_txpoll(struct net_driver_s *dev)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_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 */
|
|
|
|
stm32_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 stm32_freeframe() has not yet run. If stm32_freeframe() has run,
|
|
* the buffer1 pointer (tdes2) will be nullified (and inflight should be <
|
|
* CONFIG_STM32_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 = stm32_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: stm32_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 (stm32_txdone),
|
|
* 2. When new TX data is available (stm32_txavail_process), and
|
|
* 3. After a TX timeout to restart the sending process
|
|
* (stm32_txtimeout_process).
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_dopoll(FAR struct stm32_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 stm32_freeframe() has not yet run. If stm32_freeframe() has run,
|
|
* the buffer1 pointer (tdes2) will be nullified (and inflight should be <
|
|
* CONFIG_STM32_ETH_NTXDESC).
|
|
*/
|
|
|
|
if ((priv->txhead->tdes0 & ETH_TDES0_OWN) == 0 &&
|
|
priv->txhead->tdes2 == 0)
|
|
{
|
|
/* If we have the descriptor, then poll the network for new XMIT data.
|
|
* Allocate a buffer for the poll.
|
|
*/
|
|
|
|
DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL);
|
|
dev->d_buf = stm32_allocbuffer(priv);
|
|
|
|
/* We can't poll if we have no buffers */
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
(void)devif_poll(dev, stm32_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);
|
|
stm32_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_enableint(FAR struct stm32_ethmac_s *priv, uint32_t ierbit)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Enable the specified "normal" interrupt */
|
|
|
|
regval = stm32_getreg(STM32_ETH_DMAIER);
|
|
regval |= (ETH_DMAINT_NIS | ierbit);
|
|
stm32_putreg(regval, STM32_ETH_DMAIER);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_disableint(FAR struct stm32_ethmac_s *priv, uint32_t ierbit)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Disable the "normal" interrupt */
|
|
|
|
regval = stm32_getreg(STM32_ETH_DMAIER);
|
|
regval &= ~ierbit;
|
|
|
|
/* Are all "normal" interrupts now disabled? */
|
|
|
|
if ((regval & ETH_DMAINT_NORMAL) == 0)
|
|
{
|
|
/* Yes.. disable normal interrupts */
|
|
|
|
regval &= ~ETH_DMAINT_NIS;
|
|
}
|
|
|
|
stm32_putreg(regval, STM32_ETH_DMAIER);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_freesegment(FAR struct stm32_ethmac_s *priv,
|
|
FAR struct eth_rxdesc_s *rxfirst, int segments)
|
|
{
|
|
struct eth_rxdesc_s *rxdesc;
|
|
int i;
|
|
|
|
ninfo("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 managment logic */
|
|
|
|
priv->rxcurr = NULL;
|
|
priv->segments = 0;
|
|
|
|
/* Check if the RX Buffer unavailable flag is set */
|
|
|
|
if ((stm32_getreg(STM32_ETH_DMASR) & ETH_DMAINT_RBUI) != 0)
|
|
{
|
|
/* Clear RBUS Ethernet DMA flag */
|
|
|
|
stm32_putreg(ETH_DMAINT_RBUI, STM32_ETH_DMASR);
|
|
|
|
/* Resume DMA reception */
|
|
|
|
stm32_putreg(0, STM32_ETH_DMARPDR);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_recvframe(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
struct eth_rxdesc_s *rxdesc;
|
|
struct eth_rxdesc_s *rxcurr;
|
|
uint8_t *buffer;
|
|
int i;
|
|
|
|
ninfo("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 (!stm32_isfreebuffer(priv))
|
|
{
|
|
nerr("ERROR: 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 transmisson. 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_STM32_ETH_NRXDESC &&
|
|
priv->inflight < CONFIG_STM32_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;
|
|
}
|
|
|
|
ninfo("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: substruct 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 = stm32_allocbuffer(priv);
|
|
|
|
/* Take the buffer from the RX descriptor of the first free
|
|
* segment, put it into the network 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, remebering where we should re-start scanning
|
|
* and resetting the segment scanning logic
|
|
*/
|
|
|
|
priv->rxhead = (struct eth_rxdesc_s *)rxdesc->rdes3;
|
|
stm32_freesegment(priv, rxcurr, priv->segments);
|
|
|
|
ninfo("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.
|
|
*/
|
|
|
|
nerr("ERROR: Dropped, RX descriptor errors: %08x\n", rxdesc->rdes0);
|
|
stm32_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;
|
|
|
|
ninfo("rxhead: %p rxcurr: %p segments: %d\n",
|
|
priv->rxhead, priv->rxcurr, priv->segments);
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_receive(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Loop while while stm32_recvframe() successfully retrieves valid
|
|
* Ethernet frames.
|
|
*/
|
|
|
|
while (stm32_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 network buffer configuration
|
|
* (this should not happen)
|
|
*/
|
|
|
|
if (dev->d_len > CONFIG_NET_ETH_MTU)
|
|
{
|
|
nerr("ERROR: Dropped, Too big: %d\n", dev->d_len);
|
|
|
|
/* Free dropped packet buffer */
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
stm32_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
dev->d_len = 0;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* We only accept IP packets of the configured type and ARP packets */
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
if (BUF->type == HTONS(ETHTYPE_IP))
|
|
{
|
|
ninfo("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 */
|
|
|
|
stm32_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (BUF->type == HTONS(ETHTYPE_IP6))
|
|
{
|
|
ninfo("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 */
|
|
|
|
stm32_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_ARP
|
|
if (BUF->type == htons(ETHTYPE_ARP))
|
|
{
|
|
ninfo("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)
|
|
{
|
|
stm32_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
nerr("ERROR: 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 */
|
|
|
|
stm32_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
dev->d_len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_freeframe(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
struct eth_txdesc_s *txdesc;
|
|
int i;
|
|
|
|
ninfo("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.
|
|
*/
|
|
|
|
ninfo("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 */
|
|
|
|
stm32_freebuffer(priv, (uint8_t *)txdesc->tdes2);
|
|
}
|
|
|
|
/* In any event, make sure that TDES2 is nullified. */
|
|
|
|
txdesc->tdes2 = 0;
|
|
|
|
/* Check if this is the last segement 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.
|
|
*/
|
|
|
|
stm32_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;
|
|
|
|
ninfo("txhead: %p txtail: %p inflight: %d\n",
|
|
priv->txhead, priv->txtail, priv->inflight);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_txdone
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating that the last TX packet
|
|
* transfer(s) are complete.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by the watchdog logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_txdone(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
DEBUGASSERT(priv->txtail != NULL);
|
|
|
|
/* Scan the TX descriptor change, returning buffers to free list */
|
|
|
|
stm32_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, STM32_WDDELAY, stm32_poll_expiry, 1, priv);
|
|
|
|
/* And disable further TX interrupts. */
|
|
|
|
stm32_disableint(priv, ETH_DMAINT_TI);
|
|
}
|
|
|
|
/* Then poll the network for new XMIT data */
|
|
|
|
stm32_dopoll(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_interrupt_process(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
uint32_t dmasr;
|
|
|
|
/* Get the DMA interrupt status bits (no MAC interrupts are expected) */
|
|
|
|
dmasr = stm32_getreg(STM32_ETH_DMASR);
|
|
|
|
/* Mask only enabled interrupts. This depends on the fact that the interrupt
|
|
* related bits (0-16) correspond in these two registers.
|
|
*/
|
|
|
|
dmasr &= stm32_getreg(STM32_ETH_DMAIER);
|
|
|
|
/* Check if there are pending "normal" interrupts */
|
|
|
|
if ((dmasr & ETH_DMAINT_NIS) != 0)
|
|
{
|
|
/* Yes.. Check if we received an incoming packet, if so, call
|
|
* stm32_receive()
|
|
*/
|
|
|
|
if ((dmasr & ETH_DMAINT_RI) != 0)
|
|
{
|
|
/* Clear the pending receive interrupt */
|
|
|
|
stm32_putreg(ETH_DMAINT_RI, STM32_ETH_DMASR);
|
|
|
|
/* Handle the received package */
|
|
|
|
stm32_receive(priv);
|
|
}
|
|
|
|
/* Check if a packet transmission just completed. If so, call
|
|
* stm32_txdone(). This may disable further TX interrupts if there
|
|
* are no pending transmissions.
|
|
*/
|
|
|
|
if ((dmasr & ETH_DMAINT_TI) != 0)
|
|
{
|
|
/* Clear the pending receive interrupt */
|
|
|
|
stm32_putreg(ETH_DMAINT_TI, STM32_ETH_DMASR);
|
|
|
|
/* Check if there are pending transmissions */
|
|
|
|
stm32_txdone(priv);
|
|
}
|
|
|
|
/* Clear the pending normal summary interrupt */
|
|
|
|
stm32_putreg(ETH_DMAINT_NIS, STM32_ETH_DMASR);
|
|
}
|
|
|
|
/* Handle error interrupt only if CONFIG_DEBUG_NET is eanbled */
|
|
|
|
#ifdef CONFIG_DEBUG_NET
|
|
|
|
/* Check if there are pending "anormal" interrupts */
|
|
|
|
if ((dmasr & ETH_DMAINT_AIS) != 0)
|
|
{
|
|
/* Just let the user know what happened */
|
|
|
|
nerr("ERROR: Abormal event(s): %08x\n", dmasr);
|
|
|
|
/* Clear all pending abnormal events */
|
|
|
|
stm32_putreg(ETH_DMAINT_ABNORMAL, STM32_ETH_DMASR);
|
|
|
|
/* Clear the pending abnormal summary interrupt */
|
|
|
|
stm32_putreg(ETH_DMAINT_AIS, STM32_ETH_DMASR);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_interrupt_work(FAR void *arg)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* Process pending Ethernet interrupts */
|
|
|
|
state = net_lock();
|
|
stm32_interrupt_process(priv);
|
|
net_unlock(state);
|
|
|
|
/* Re-enable Ethernet interrupts at the NVIC */
|
|
|
|
up_enable_irq(STM32_IRQ_ETH);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_interrupt(int irq, FAR void *context)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = &g_stm32ethmac[0];
|
|
|
|
#ifdef CONFIG_NET_NOINTS
|
|
uint32_t dmasr;
|
|
|
|
/* Get the DMA interrupt status bits (no MAC interrupts are expected) */
|
|
|
|
dmasr = stm32_getreg(STM32_ETH_DMASR);
|
|
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(STM32_IRQ_ETH);
|
|
|
|
/* 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(ETHWORK, &priv->work);
|
|
|
|
/* Schedule to perform the interrupt processing on the worker thread. */
|
|
|
|
work_queue(ETHWORK, &priv->work, stm32_interrupt_work, priv, 0);
|
|
}
|
|
|
|
#else
|
|
/* Process the interrupt now */
|
|
|
|
stm32_interrupt_process(priv);
|
|
#endif
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_txtimeout_process(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
/* Then reset the hardware. Just take the interface down, then back
|
|
* up again.
|
|
*/
|
|
|
|
stm32_ifdown(&priv->dev);
|
|
stm32_ifup(&priv->dev);
|
|
|
|
/* Then poll for new XMIT data */
|
|
|
|
stm32_dopoll(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_txtimeout_work(FAR void *arg)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
/* Process pending Ethernet interrupts */
|
|
|
|
state = net_lock();
|
|
stm32_txtimeout_process(priv);
|
|
net_unlock(state);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_txtimeout_expiry(int argc, uint32_t arg, ...)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_ethmac_s *)arg;
|
|
|
|
nerr("ERROR: 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 stm32_ifup() is called.
|
|
*/
|
|
|
|
up_disable_irq(STM32_IRQ_ETH);
|
|
|
|
/* Cancel any pending poll or interrupt work. This will have no effect
|
|
* on work that has already been started.
|
|
*/
|
|
|
|
work_cancel(ETHWORK, &priv->work);
|
|
|
|
/* Schedule to perform the TX timeout processing on the worker thread. */
|
|
|
|
work_queue(ETHWORK, &priv->work, stm32_txtimeout_work, priv, 0);
|
|
|
|
#else
|
|
/* Process the timeout now */
|
|
|
|
stm32_txtimeout_process(priv);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_poll_process(FAR struct stm32_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 stm32_freeframe() has not yet run. If stm32_freeframe() has run,
|
|
* the buffer1 pointer (tdes2) will be nullified (and inflight should be <
|
|
* CONFIG_STM32_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 = stm32_allocbuffer(priv);
|
|
|
|
/* We can't poll if we have no buffers */
|
|
|
|
if (dev->d_buf)
|
|
{
|
|
/* Update TCP timing states and poll the network for new XMIT data.
|
|
*/
|
|
|
|
(void)devif_timer(dev, stm32_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);
|
|
stm32_freebuffer(priv, dev->d_buf);
|
|
dev->d_buf = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Setup the watchdog poll timer again */
|
|
|
|
(void)wd_start(priv->txpoll, STM32_WDDELAY, stm32_poll_expiry, 1, priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_poll_work(FAR void *arg)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
/* Perform the poll */
|
|
|
|
state = net_lock();
|
|
stm32_poll_process(priv);
|
|
net_unlock(state);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_poll_expiry(int argc, uint32_t arg, ...)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_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(ETHWORK, &priv->work, stm32_poll_work, priv, 0);
|
|
}
|
|
else
|
|
{
|
|
/* No.. Just re-start the watchdog poll timer, missing one polling
|
|
* cycle.
|
|
*/
|
|
|
|
(void)wd_start(priv->txpoll, STM32_WDDELAY, stm32_poll_expiry, 1, (uint32_t)priv);
|
|
}
|
|
|
|
#else
|
|
/* Process the interrupt now */
|
|
|
|
stm32_poll_process(priv);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_ifup(struct net_driver_s *dev)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_ethmac_s *)dev->d_private;
|
|
int ret;
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
ninfo("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
|
|
ninfo("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 = stm32_ethconfig(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Set and activate a timer process */
|
|
|
|
(void)wd_start(priv->txpoll, STM32_WDDELAY, stm32_poll_expiry, 1, (uint32_t)priv);
|
|
|
|
/* Enable the Ethernet interrupt */
|
|
|
|
priv->ifup = true;
|
|
up_enable_irq(STM32_IRQ_ETH);
|
|
|
|
stm32_checksetup();
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_ifdown
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Stop the interface.
|
|
*
|
|
* Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_ifdown(struct net_driver_s *dev)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_ethmac_s *)dev->d_private;
|
|
irqstate_t flags;
|
|
|
|
ninfo("Taking the network down\n");
|
|
|
|
/* Disable the Ethernet interrupt */
|
|
|
|
flags = enter_critical_section();
|
|
up_disable_irq(STM32_IRQ_ETH);
|
|
|
|
/* 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 stm32_ifup() always
|
|
* successfully brings the interface back up.
|
|
*/
|
|
|
|
stm32_ethreset(priv);
|
|
|
|
/* Mark the device "down" */
|
|
|
|
priv->ifup = false;
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_txavail_process(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
ninfo("ifup: %d\n", priv->ifup);
|
|
|
|
/* Ignore the notification if the interface is not yet up */
|
|
|
|
if (priv->ifup)
|
|
{
|
|
/* Poll the network for new XMIT data */
|
|
|
|
stm32_dopoll(priv);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_txavail_work(FAR void *arg)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_ethmac_s *)arg;
|
|
net_lock_t state;
|
|
|
|
/* Perform the poll */
|
|
|
|
state = net_lock();
|
|
stm32_txavail_process(priv);
|
|
net_unlock(state);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_txavail(struct net_driver_s *dev)
|
|
{
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_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(ETHWORK, &priv->work, stm32_txavail_work, priv, 0);
|
|
}
|
|
|
|
#else
|
|
irqstate_t flags;
|
|
|
|
/* Disable interrupts because this function may be called from interrupt
|
|
* level processing.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Perform the out-of-cycle poll now */
|
|
|
|
stm32_txavail_process(priv);
|
|
leave_critical_section(flags);
|
|
#endif
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_calcethcrc
|
|
*
|
|
* Description:
|
|
* Function to calculate the CRC used by STM32 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 stm32_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: stm32_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 stm32_addmac(struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
uint32_t crc;
|
|
uint32_t hashindex;
|
|
uint32_t temp;
|
|
uint32_t registeraddress;
|
|
|
|
ninfo("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 = stm32_calcethcrc(mac, 6);
|
|
|
|
hashindex = (crc >> 26) & 0x3F;
|
|
|
|
if (hashindex > 31)
|
|
{
|
|
registeraddress = STM32_ETH_MACHTHR;
|
|
hashindex -= 32;
|
|
}
|
|
else
|
|
{
|
|
registeraddress = STM32_ETH_MACHTLR;
|
|
}
|
|
|
|
temp = stm32_getreg(registeraddress);
|
|
temp |= 1 << hashindex;
|
|
stm32_putreg(temp, registeraddress);
|
|
|
|
temp = stm32_getreg(STM32_ETH_MACFFR);
|
|
temp |= (ETH_MACFFR_HM | ETH_MACFFR_HPF);
|
|
stm32_putreg(temp, STM32_ETH_MACFFR);
|
|
|
|
return OK;
|
|
}
|
|
#endif /* CONFIG_NET_IGMP || CONFIG_NET_ICMPv6 */
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_rmmac(struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
uint32_t crc;
|
|
uint32_t hashindex;
|
|
uint32_t temp;
|
|
uint32_t registeraddress;
|
|
|
|
ninfo("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 = stm32_calcethcrc(mac, 6);
|
|
|
|
hashindex = (crc >> 26) & 0x3F;
|
|
|
|
if (hashindex > 31)
|
|
{
|
|
registeraddress = STM32_ETH_MACHTHR;
|
|
hashindex -= 32;
|
|
}
|
|
else
|
|
{
|
|
registeraddress = STM32_ETH_MACHTLR;
|
|
}
|
|
|
|
temp = stm32_getreg(registeraddress);
|
|
temp &= ~(1 << hashindex);
|
|
stm32_putreg(temp, registeraddress);
|
|
|
|
/* If there is no address registered any more, delete multicast filtering */
|
|
|
|
if (stm32_getreg(STM32_ETH_MACHTHR) == 0 &&
|
|
stm32_getreg(STM32_ETH_MACHTLR) == 0)
|
|
{
|
|
temp = stm32_getreg(STM32_ETH_MACFFR);
|
|
temp &= ~(ETH_MACFFR_HM | ETH_MACFFR_HPF);
|
|
stm32_putreg(temp, STM32_ETH_MACFFR);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_txdescinit
|
|
*
|
|
* Description:
|
|
* Initializes the DMA TX descriptors in chain mode.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_txdescinit(FAR struct stm32_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_STM32_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_STM32_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 */
|
|
|
|
stm32_putreg((uint32_t)priv->txtable, STM32_ETH_DMATDLAR);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_rxdescinit
|
|
*
|
|
* Description:
|
|
* Initializes the DMA RX descriptors in chain mode.
|
|
*
|
|
* Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_rxdescinit(FAR struct stm32_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_STM32_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_STM32_ETH_BUFSIZE;
|
|
|
|
/* Set Buffer1 address pointer */
|
|
|
|
rxdesc->rdes2 = (uint32_t)&priv->rxbuffer[i*CONFIG_STM32_ETH_BUFSIZE];
|
|
|
|
/* Initialize the next descriptor with the Next Descriptor Polling Enable */
|
|
|
|
if (i < (CONFIG_STM32_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 */
|
|
|
|
stm32_putreg((uint32_t)priv->rxtable, STM32_ETH_DMARDLAR);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_ioctl(struct net_driver_s *dev, int cmd, long arg)
|
|
{
|
|
#ifdef CONFIG_ARCH_PHY_INTERRUPT
|
|
FAR struct stm32_ethmac_s *priv = (FAR struct stm32_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 = stm32_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_STM32_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 = stm32_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 = stm32_phywrite(req->phy_id, req->reg_num, req->val_in);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_NETDEV_PHY_IOCTL */
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_phyintenable(struct stm32_ethmac_s *priv)
|
|
{
|
|
#warning Missing logic
|
|
return -ENOSYS;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_STM32_AUTONEG) || defined(CONFIG_NETDEV_PHY_IOCTL) || \
|
|
defined(CONFIG_ETH0_PHY_DM9161)
|
|
static int stm32_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 = stm32_getreg(STM32_ETH_MACMIIAR);
|
|
regval &= ETH_MACMIIAR_CR_MASK;
|
|
|
|
/* Set the PHY device address, PHY register address, and set the buy bit.
|
|
* the ETH_MACMIIAR_MW is clear, indicating a read operation.
|
|
*/
|
|
|
|
regval |= (((uint32_t)phydevaddr << ETH_MACMIIAR_PA_SHIFT) & ETH_MACMIIAR_PA_MASK);
|
|
regval |= (((uint32_t)phyregaddr << ETH_MACMIIAR_MR_SHIFT) & ETH_MACMIIAR_MR_MASK);
|
|
regval |= ETH_MACMIIAR_MB;
|
|
|
|
stm32_putreg(regval, STM32_ETH_MACMIIAR);
|
|
|
|
/* Wait for the transfer to complete */
|
|
|
|
for (timeout = 0; timeout < PHY_READ_TIMEOUT; timeout++)
|
|
{
|
|
if ((stm32_getreg(STM32_ETH_MACMIIAR) & ETH_MACMIIAR_MB) == 0)
|
|
{
|
|
*value = (uint16_t)stm32_getreg(STM32_ETH_MACMIIDR);
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
nerr("ERROR: MII transfer timed out: phydevaddr: %04x phyregaddr: %04x\n",
|
|
phydevaddr, phyregaddr);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_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 = stm32_getreg(STM32_ETH_MACMIIAR);
|
|
regval &= ETH_MACMIIAR_CR_MASK;
|
|
|
|
/* Set the PHY device address, PHY register address, and set the busy bit.
|
|
* the ETH_MACMIIAR_MW is set, indicating a write operation.
|
|
*/
|
|
|
|
regval |= (((uint32_t)phydevaddr << ETH_MACMIIAR_PA_SHIFT) & ETH_MACMIIAR_PA_MASK);
|
|
regval |= (((uint32_t)phyregaddr << ETH_MACMIIAR_MR_SHIFT) & ETH_MACMIIAR_MR_MASK);
|
|
regval |= (ETH_MACMIIAR_MB | ETH_MACMIIAR_MW);
|
|
|
|
/* Write the value into the MACIIDR register before setting the new MACMIIAR
|
|
* register value.
|
|
*/
|
|
|
|
stm32_putreg(value, STM32_ETH_MACMIIDR);
|
|
stm32_putreg(regval, STM32_ETH_MACMIIAR);
|
|
|
|
/* Wait for the transfer to complete */
|
|
|
|
for (timeout = 0; timeout < PHY_WRITE_TIMEOUT; timeout++)
|
|
{
|
|
if ((stm32_getreg(STM32_ETH_MACMIIAR) & ETH_MACMIIAR_MB) == 0)
|
|
{
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
nerr("ERROR: MII transfer timed out: phydevaddr: %04x phyregaddr: %04x value: %04x\n",
|
|
phydevaddr, phyregaddr, value);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_dm9161(FAR struct stm32_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 = stm32_phyread(CONFIG_STM32_PHYADDR, MII_PHYID1, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: 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();
|
|
}
|
|
|
|
ninfo("PHY ID1: 0x%04X\n", phyval);
|
|
|
|
/* Now check the "DAVICOM Specified Configuration Register (DSCR)", Register 16 */
|
|
|
|
ret = stm32_phyread(CONFIG_STM32_PHYADDR, 16, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: 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: stm32_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 stm32_phyinit(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
#ifdef CONFIG_STM32_AUTONEG
|
|
volatile uint32_t timeout;
|
|
#endif
|
|
|
|
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 = stm32_getreg(STM32_ETH_MACMIIAR);
|
|
regval &= ~ETH_MACMIIAR_CR_MASK;
|
|
regval |= ETH_MACMIIAR_CR;
|
|
stm32_putreg(regval, STM32_ETH_MACMIIAR);
|
|
|
|
/* Put the PHY in reset mode */
|
|
|
|
ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_MCR, MII_MCR_RESET);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: Failed to reset the PHY: %d\n", ret);
|
|
return ret;
|
|
}
|
|
up_mdelay(PHY_RESET_DELAY);
|
|
|
|
/* Perform any necessary, board-specific PHY initialization */
|
|
|
|
#ifdef CONFIG_STM32_PHYINIT
|
|
ret = stm32_phy_boardinitialize(0);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: Failed to initialize the PHY: %d\n", ret);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* Special workaround for the Davicom DM9161 PHY is required. */
|
|
|
|
#ifdef CONFIG_ETH0_PHY_DM9161
|
|
ret = stm32_dm9161(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* Perform auto-negotion if so configured */
|
|
|
|
#ifdef CONFIG_STM32_AUTONEG
|
|
/* Wait for link status */
|
|
|
|
for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++)
|
|
{
|
|
ret = stm32_phyread(CONFIG_STM32_PHYADDR, MII_MSR, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: Failed to read the PHY MSR: %d\n", ret);
|
|
return ret;
|
|
}
|
|
else if ((phyval & MII_MSR_LINKSTATUS) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (timeout >= PHY_RETRY_TIMEOUT)
|
|
{
|
|
nerr("ERROR: Timed out waiting for link status: %04x\n", phyval);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* Enable auto-gegotiation */
|
|
|
|
ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_MCR, MII_MCR_ANENABLE);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: Failed to enable auto-negotiation: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Wait until auto-negotiation completes */
|
|
|
|
for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++)
|
|
{
|
|
ret = stm32_phyread(CONFIG_STM32_PHYADDR, MII_MSR, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: Failed to read the PHY MSR: %d\n", ret);
|
|
return ret;
|
|
}
|
|
else if ((phyval & MII_MSR_ANEGCOMPLETE) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (timeout >= PHY_RETRY_TIMEOUT)
|
|
{
|
|
nerr("ERROR: Timed out waiting for auto-negotiation\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* Read the result of the auto-negotiation from the PHY-specific register */
|
|
|
|
ret = stm32_phyread(CONFIG_STM32_PHYADDR, CONFIG_STM32_PHYSR, &phyval);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: Failed to read PHY status register\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Remember the selected speed and duplex modes */
|
|
|
|
ninfo("PHYSR[%d]: %04x\n", CONFIG_STM32_PHYSR, phyval);
|
|
|
|
/* Different PHYs present speed and mode information in different ways. IF
|
|
* This CONFIG_STM32_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_STM32_PHYSR_ALTCONFIG
|
|
switch (phyval & CONFIG_STM32_PHYSR_ALTMODE)
|
|
{
|
|
default:
|
|
case CONFIG_STM32_PHYSR_10HD:
|
|
priv->fduplex = 0;
|
|
priv->mbps100 = 0;
|
|
break;
|
|
|
|
case CONFIG_STM32_PHYSR_100HD:
|
|
priv->fduplex = 0;
|
|
priv->mbps100 = 1;
|
|
break;
|
|
|
|
case CONFIG_STM32_PHYSR_10FD:
|
|
priv->fduplex = 1;
|
|
priv->mbps100 = 0;
|
|
break;
|
|
|
|
case CONFIG_STM32_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_STM32_PHYSR_MODE) == CONFIG_STM32_PHYSR_FULLDUPLEX)
|
|
{
|
|
priv->fduplex = 1;
|
|
}
|
|
|
|
if ((phyval & CONFIG_STM32_PHYSR_SPEED) == CONFIG_STM32_PHYSR_100MBPS)
|
|
{
|
|
priv->mbps100 = 1;
|
|
}
|
|
#endif
|
|
|
|
#else /* Auto-negotion not selected */
|
|
|
|
phyval = 0;
|
|
#ifdef CONFIG_STM32_ETHFD
|
|
phyval |= MII_MCR_FULLDPLX;
|
|
#endif
|
|
#ifdef CONFIG_STM32_ETH100MBPS
|
|
phyval |= MII_MCR_SPEED100;
|
|
#endif
|
|
|
|
ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_MCR, phyval);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: 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_STM32_ETHFD
|
|
priv->fduplex = 1;
|
|
#endif
|
|
#ifdef CONFIG_STM32_ETH100MBPS
|
|
priv->mbps100 = 1;
|
|
#endif
|
|
#endif
|
|
|
|
ninfo("Duplex: %s Speed: %d MBps\n",
|
|
priv->fduplex ? "FULL" : "HALF",
|
|
priv->mbps100 ? 100 : 10);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_selectmii
|
|
*
|
|
* Description:
|
|
* Selects the MII inteface.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
************************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32_MII
|
|
static inline void stm32_selectmii(void)
|
|
{
|
|
uint32_t regval;
|
|
|
|
#ifdef CONFIG_STM32_CONNECTIVITYLINE
|
|
regval = getreg32(STM32_AFIO_MAPR);
|
|
regval &= ~AFIO_MAPR_MII_RMII_SEL;
|
|
putreg32(regval, STM32_AFIO_MAPR);
|
|
#else
|
|
regval = getreg32(STM32_SYSCFG_PMC);
|
|
regval &= ~SYSCFG_PMC_MII_RMII_SEL;
|
|
putreg32(regval, STM32_SYSCFG_PMC);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_selectrmii
|
|
*
|
|
* Description:
|
|
* Selects the RMII inteface.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
************************************************************************************/
|
|
|
|
static inline void stm32_selectrmii(void)
|
|
{
|
|
uint32_t regval;
|
|
|
|
#ifdef CONFIG_STM32_CONNECTIVITYLINE
|
|
regval = getreg32(STM32_AFIO_MAPR);
|
|
regval |= AFIO_MAPR_MII_RMII_SEL;
|
|
putreg32(regval, STM32_AFIO_MAPR);
|
|
#else
|
|
regval = getreg32(STM32_SYSCFG_PMC);
|
|
regval |= SYSCFG_PMC_MII_RMII_SEL;
|
|
putreg32(regval, STM32_SYSCFG_PMC);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_ethgpioconfig(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
/* Configure GPIO pins to support Ethernet */
|
|
|
|
#if defined(CONFIG_STM32_MII) || defined(CONFIG_STM32_RMII)
|
|
|
|
/* MDC and MDIO are common to both modes */
|
|
|
|
stm32_configgpio(GPIO_ETH_MDC);
|
|
stm32_configgpio(GPIO_ETH_MDIO);
|
|
|
|
/* Set up the MII interface */
|
|
|
|
#if defined(CONFIG_STM32_MII)
|
|
|
|
/* Select the MII interface */
|
|
|
|
stm32_selectmii();
|
|
|
|
/* Provide clocking via MCO, MCO1 or MCO2:
|
|
*
|
|
* "MCO1 (microcontroller clock output), used to output HSI, LSE, HSE or PLL
|
|
* clock (through a configurable prescaler) on PA8 pin."
|
|
*
|
|
* "MCO2 (microcontroller clock output), used to output HSE, PLL, SYSCLK or
|
|
* PLLI2S clock (through a configurable prescaler) on PC9 pin."
|
|
*/
|
|
|
|
# if defined(CONFIG_STM32_MII_MCO1)
|
|
/* Configure MC01 to drive the PHY. Board logic must provide MC01 clocking
|
|
* info.
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_MCO1);
|
|
stm32_mco1config(BOARD_CFGR_MC01_SOURCE, BOARD_CFGR_MC01_DIVIDER);
|
|
|
|
# elif defined(CONFIG_STM32_MII_MCO2)
|
|
/* Configure MC02 to drive the PHY. Board logic must provide MC02 clocking
|
|
* info.
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_MCO2);
|
|
stm32_mco2config(BOARD_CFGR_MC02_SOURCE, BOARD_CFGR_MC02_DIVIDER);
|
|
|
|
# elif defined(CONFIG_STM32_MII_MCO)
|
|
/* Setup MCO pin for alternative usage */
|
|
|
|
stm32_configgpio(GPIO_MCO);
|
|
stm32_mcoconfig(BOARD_CFGR_MCO_SOURCE);
|
|
# endif
|
|
|
|
/* 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
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_ETH_MII_COL);
|
|
stm32_configgpio(GPIO_ETH_MII_CRS);
|
|
stm32_configgpio(GPIO_ETH_MII_RXD0);
|
|
stm32_configgpio(GPIO_ETH_MII_RXD1);
|
|
stm32_configgpio(GPIO_ETH_MII_RXD2);
|
|
stm32_configgpio(GPIO_ETH_MII_RXD3);
|
|
stm32_configgpio(GPIO_ETH_MII_RX_CLK);
|
|
stm32_configgpio(GPIO_ETH_MII_RX_DV);
|
|
stm32_configgpio(GPIO_ETH_MII_RX_ER);
|
|
stm32_configgpio(GPIO_ETH_MII_TXD0);
|
|
stm32_configgpio(GPIO_ETH_MII_TXD1);
|
|
stm32_configgpio(GPIO_ETH_MII_TXD2);
|
|
stm32_configgpio(GPIO_ETH_MII_TXD3);
|
|
stm32_configgpio(GPIO_ETH_MII_TX_CLK);
|
|
stm32_configgpio(GPIO_ETH_MII_TX_EN);
|
|
|
|
/* Set up the RMII interface. */
|
|
|
|
#elif defined(CONFIG_STM32_RMII)
|
|
|
|
/* Select the RMII interface */
|
|
|
|
stm32_selectrmii();
|
|
|
|
/* Provide clocking via MCO, MCO1 or MCO2:
|
|
*
|
|
* "MCO1 (microcontroller clock output), used to output HSI, LSE, HSE or PLL
|
|
* clock (through a configurable prescaler) on PA8 pin."
|
|
*
|
|
* "MCO2 (microcontroller clock output), used to output HSE, PLL, SYSCLK or
|
|
* PLLI2S clock (through a configurable prescaler) on PC9 pin."
|
|
*/
|
|
|
|
# if defined(CONFIG_STM32_RMII_MCO1)
|
|
/* Configure MC01 to drive the PHY. Board logic must provide MC01 clocking
|
|
* info.
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_MCO1);
|
|
stm32_mco1config(BOARD_CFGR_MC01_SOURCE, BOARD_CFGR_MC01_DIVIDER);
|
|
|
|
# elif defined(CONFIG_STM32_RMII_MCO2)
|
|
/* Configure MC02 to drive the PHY. Board logic must provide MC02 clocking
|
|
* info.
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_MCO2);
|
|
stm32_mco2config(BOARD_CFGR_MC02_SOURCE, BOARD_CFGR_MC02_DIVIDER);
|
|
|
|
# elif defined(CONFIG_STM32_RMII_MCO)
|
|
/* Setup MCO pin for alternative usage */
|
|
|
|
stm32_configgpio(GPIO_MCO);
|
|
stm32_mcoconfig(BOARD_CFGR_MCO_SOURCE);
|
|
# endif
|
|
|
|
/* RMII interface pins (7):
|
|
*
|
|
* RMII_TXD[1:0], RMII_TX_EN, RMII_RXD[1:0], RMII_CRS_DV, MDC, MDIO,
|
|
* RMII_REF_CLK
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_ETH_RMII_CRS_DV);
|
|
stm32_configgpio(GPIO_ETH_RMII_REF_CLK);
|
|
stm32_configgpio(GPIO_ETH_RMII_RXD0);
|
|
stm32_configgpio(GPIO_ETH_RMII_RXD1);
|
|
stm32_configgpio(GPIO_ETH_RMII_TXD0);
|
|
stm32_configgpio(GPIO_ETH_RMII_TXD1);
|
|
stm32_configgpio(GPIO_ETH_RMII_TX_EN);
|
|
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_STM32_ETH_PTP
|
|
/* Enable pulse-per-second (PPS) output signal */
|
|
|
|
stm32_configgpio(GPIO_ETH_PPS_OUT);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_ethreset
|
|
*
|
|
* Description:
|
|
* Reset the Ethernet block.
|
|
*
|
|
* Parameters:
|
|
* priv - A reference to the private driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_ethreset(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Reset the Ethernet on the AHB bus (F1 Connectivity Line) or AHB1 bus (F2
|
|
* and F4)
|
|
*/
|
|
|
|
#if defined(CONFIG_STM32_CONNECTIVITYLINE)
|
|
regval = stm32_getreg(STM32_RCC_AHBRSTR);
|
|
regval |= RCC_AHBRSTR_ETHMACRST;
|
|
stm32_putreg(regval, STM32_RCC_AHBRSTR);
|
|
|
|
regval &= ~RCC_AHBRSTR_ETHMACRST;
|
|
stm32_putreg(regval, STM32_RCC_AHBRSTR);
|
|
#else
|
|
regval = stm32_getreg(STM32_RCC_AHB1RSTR);
|
|
regval |= RCC_AHB1RSTR_ETHMACRST;
|
|
stm32_putreg(regval, STM32_RCC_AHB1RSTR);
|
|
|
|
regval &= ~RCC_AHB1RSTR_ETHMACRST;
|
|
stm32_putreg(regval, STM32_RCC_AHB1RSTR);
|
|
#endif
|
|
|
|
/* 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 = stm32_getreg(STM32_ETH_DMABMR);
|
|
regval |= ETH_DMABMR_SR;
|
|
stm32_putreg(regval, STM32_ETH_DMABMR);
|
|
|
|
/* 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 ((stm32_getreg(STM32_ETH_DMABMR) & ETH_DMABMR_SR) != 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_macconfig(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Set up the MACCR register */
|
|
|
|
regval = stm32_getreg(STM32_ETH_MACCR);
|
|
regval &= ~MACCR_CLEAR_BITS;
|
|
regval |= MACCR_SET_BITS;
|
|
|
|
if (priv->fduplex)
|
|
{
|
|
/* Set the DM bit for full duplex support */
|
|
|
|
regval |= ETH_MACCR_DM;
|
|
}
|
|
|
|
if (priv->mbps100)
|
|
{
|
|
/* Set the FES bit for 100Mbps fast ethernet support */
|
|
|
|
regval |= ETH_MACCR_FES;
|
|
}
|
|
|
|
stm32_putreg(regval, STM32_ETH_MACCR);
|
|
|
|
/* Set up the MACFFR register */
|
|
|
|
regval = stm32_getreg(STM32_ETH_MACFFR);
|
|
regval &= ~MACFFR_CLEAR_BITS;
|
|
regval |= MACFFR_SET_BITS;
|
|
stm32_putreg(regval, STM32_ETH_MACFFR);
|
|
|
|
/* Set up the MACHTHR and MACHTLR registers */
|
|
|
|
stm32_putreg(0, STM32_ETH_MACHTHR);
|
|
stm32_putreg(0, STM32_ETH_MACHTLR);
|
|
|
|
/* Setup up the MACFCR register */
|
|
|
|
regval = stm32_getreg(STM32_ETH_MACFCR);
|
|
regval &= ~MACFCR_CLEAR_MASK;
|
|
regval |= MACFCR_SET_MASK;
|
|
stm32_putreg(regval, STM32_ETH_MACFCR);
|
|
|
|
/* Setup up the MACVLANTR register */
|
|
|
|
stm32_putreg(0, STM32_ETH_MACVLANTR);
|
|
|
|
/* DMA Configuration */
|
|
/* Set up the DMAOMR register */
|
|
|
|
regval = stm32_getreg(STM32_ETH_DMAOMR);
|
|
regval &= ~DMAOMR_CLEAR_MASK;
|
|
regval |= DMAOMR_SET_MASK;
|
|
stm32_putreg(regval, STM32_ETH_DMAOMR);
|
|
|
|
/* Set up the DMABMR register */
|
|
|
|
regval = stm32_getreg(STM32_ETH_DMABMR);
|
|
regval &= ~DMABMR_CLEAR_MASK;
|
|
regval |= DMABMR_SET_MASK;
|
|
stm32_putreg(regval, STM32_ETH_DMABMR);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_macaddress(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
uint32_t regval;
|
|
|
|
ninfo("%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];
|
|
stm32_putreg(regval, STM32_ETH_MACA0HR);
|
|
|
|
/* 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];
|
|
stm32_putreg(regval, STM32_ETH_MACA0LR);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_ipv6multicast(FAR struct stm32_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;
|
|
|
|
ninfo("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
(void)stm32_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)stm32_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)stm32_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet);
|
|
|
|
#endif /* CONFIG_NET_ICMPv6_ROUTER */
|
|
}
|
|
#endif /* CONFIG_NET_ICMPv6 */
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_macenable(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Set the MAC address */
|
|
|
|
stm32_macaddress(priv);
|
|
|
|
#ifdef CONFIG_NET_ICMPv6
|
|
/* Set up the IPv6 multicast address */
|
|
|
|
stm32_ipv6multicast(priv);
|
|
#endif
|
|
|
|
/* Enable transmit state machine of the MAC for transmission on the MII */
|
|
|
|
regval = stm32_getreg(STM32_ETH_MACCR);
|
|
regval |= ETH_MACCR_TE;
|
|
stm32_putreg(regval, STM32_ETH_MACCR);
|
|
|
|
/* Flush Transmit FIFO */
|
|
|
|
regval = stm32_getreg(STM32_ETH_DMAOMR);
|
|
regval |= ETH_DMAOMR_FTF;
|
|
stm32_putreg(regval, STM32_ETH_DMAOMR);
|
|
|
|
/* Enable receive state machine of the MAC for reception from the MII */
|
|
|
|
/* Enables or disables the MAC reception. */
|
|
|
|
regval = stm32_getreg(STM32_ETH_MACCR);
|
|
regval |= ETH_MACCR_RE;
|
|
stm32_putreg(regval, STM32_ETH_MACCR);
|
|
|
|
/* Start DMA transmission */
|
|
|
|
regval = stm32_getreg(STM32_ETH_DMAOMR);
|
|
regval |= ETH_DMAOMR_ST;
|
|
stm32_putreg(regval, STM32_ETH_DMAOMR);
|
|
|
|
/* Start DMA reception */
|
|
|
|
regval = stm32_getreg(STM32_ETH_DMAOMR);
|
|
regval |= ETH_DMAOMR_SR;
|
|
stm32_putreg(regval, STM32_ETH_DMAOMR);
|
|
|
|
/* Enable Ethernet DMA interrupts.
|
|
*
|
|
* The STM32 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.
|
|
*/
|
|
|
|
stm32_putreg(ETH_MACIMR_ALLINTS, STM32_ETH_MACIMR);
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
stm32_putreg((ETH_DMAINT_RECV_ENABLE | ETH_DMAINT_ERROR_ENABLE), STM32_ETH_DMAIER);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_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 stm32_ethconfig(FAR struct stm32_ethmac_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
/* NOTE: The Ethernet clocks were initialized early in the boot-up
|
|
* sequence in stm32_rcc.c.
|
|
*/
|
|
|
|
/* Reset the Ethernet block */
|
|
|
|
ninfo("Reset the Ethernet block\n");
|
|
stm32_ethreset(priv);
|
|
|
|
/* Initialize the PHY */
|
|
|
|
ninfo("Initialize the PHY\n");
|
|
ret = stm32_phyinit(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Initialize the MAC and DMA */
|
|
|
|
ninfo("Initialize the MAC and DMA\n");
|
|
ret = stm32_macconfig(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Initialize the free buffer list */
|
|
|
|
stm32_initbuffer(priv);
|
|
|
|
/* Initialize TX Descriptors list: Chain Mode */
|
|
|
|
stm32_txdescinit(priv);
|
|
|
|
/* Initialize RX Descriptors list: Chain Mode */
|
|
|
|
stm32_rxdescinit(priv);
|
|
|
|
/* Enable normal MAC operation */
|
|
|
|
ninfo("Enable normal operation\n");
|
|
return stm32_macenable(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Function: stm32_ethinitialize
|
|
*
|
|
* Description:
|
|
* Initialize the Ethernet driver for one interface. If the STM32 chip
|
|
* supports multiple Ethernet controllers, then board specific logic
|
|
* must implement up_netinitialize() and call this function to initialize
|
|
* the desired interfaces.
|
|
*
|
|
* Parameters:
|
|
* intf - In the case where there are multiple EMACs, this value
|
|
* identifies which EMAC is to be initialized.
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if STM32_NETHERNET == 1
|
|
static inline
|
|
#endif
|
|
|
|
int stm32_ethinitialize(int intf)
|
|
{
|
|
struct stm32_ethmac_s *priv;
|
|
|
|
ninfo("intf: %d\n", intf);
|
|
|
|
/* Get the interface structure associated with this interface number. */
|
|
|
|
DEBUGASSERT(intf < STM32_NETHERNET);
|
|
priv = &g_stm32ethmac[intf];
|
|
|
|
/* Initialize the driver structure */
|
|
|
|
memset(priv, 0, sizeof(struct stm32_ethmac_s));
|
|
priv->dev.d_ifup = stm32_ifup; /* I/F up (new IP address) callback */
|
|
priv->dev.d_ifdown = stm32_ifdown; /* I/F down callback */
|
|
priv->dev.d_txavail = stm32_txavail; /* New TX data callback */
|
|
#ifdef CONFIG_NET_IGMP
|
|
priv->dev.d_addmac = stm32_addmac; /* Add multicast MAC address */
|
|
priv->dev.d_rmmac = stm32_rmmac; /* Remove multicast MAC address */
|
|
#endif
|
|
#ifdef CONFIG_NETDEV_PHY_IOCTL
|
|
priv->dev.d_ioctl = stm32_ioctl; /* Support PHY ioctl() calls */
|
|
#endif
|
|
priv->dev.d_private = (void *)g_stm32ethmac; /* 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 */
|
|
|
|
stm32_ethgpioconfig(priv);
|
|
|
|
/* Attach the IRQ to the driver */
|
|
|
|
if (irq_attach(STM32_IRQ_ETH, stm32_interrupt))
|
|
{
|
|
/* We could not attach the ISR to the interrupt */
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Put the interface in the down state. */
|
|
|
|
stm32_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. If STM32_NETHERNET
|
|
* greater than one, then board specific logic will have to supply a
|
|
* version of up_netinitialize() that calls stm32_ethinitialize() with
|
|
* the appropriate interface number.
|
|
*
|
|
* Parameters:
|
|
* None.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if STM32_NETHERNET == 1
|
|
void up_netinitialize(void)
|
|
{
|
|
(void)stm32_ethinitialize(0);
|
|
}
|
|
#endif
|
|
|
|
#endif /* STM32_NETHERNET > 0 */
|
|
#endif /* CONFIG_NET && CONFIG_STM32_ETHMAC */
|