nuttx/arch/arm/src/stm32h7/stm32_ethernet.c
chao an 8850dee746 net/devif: move preprocess of txpoll into common code
Signed-off-by: chao an <anchao@xiaomi.com>
2022-11-27 12:11:12 +08:00

4354 lines
123 KiB
C

/****************************************************************************
* arch/arm/src/stm32h7/stm32_ethernet.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <arpa/inet.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/queue.h>
#include <nuttx/wdog.h>
#include <nuttx/wqueue.h>
#include <nuttx/signal.h>
#include <nuttx/net/mii.h>
#include <nuttx/net/arp.h>
#include <nuttx/net/netdev.h>
#include <nuttx/crc64.h>
#if defined(CONFIG_NET_PKT)
# include <nuttx/net/pkt.h>
#endif
#include <nuttx/cache.h>
#include "arm_internal.h"
#include "barriers.h"
#include "hardware/stm32_syscfg.h"
#include "hardware/stm32_pinmap.h"
#include "stm32_gpio.h"
#include "stm32_rcc.h"
#include "stm32_ethernet.h"
#include "stm32_uid.h"
#include <arch/board/board.h>
/* STM32H7_NETHERNET determines the number of physical interfaces that can
* be supported by the hardware. CONFIG_STM32H7_ETHMAC will defined if
* any STM32H7 Ethernet support is enabled in the configuration.
*/
#if STM32H7_NETHERNET > 0 && defined(CONFIG_STM32H7_ETHMAC)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Memory synchronization */
#define MEMORY_SYNC() do { ARM_DSB(); ARM_ISB(); } while (0)
/* Configuration ************************************************************/
/* See boards/arm/stm32/stm3240g-eval/README.txt for an explanation of the
* configuration settings.
*/
#if STM32H7_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_SCHED_WORKQUEUE)
# error Work queue support is required
#else
/* Select work queue */
# if defined(CONFIG_STM32H7_ETHMAC_HPWORK)
# define ETHWORK HPWORK
# elif defined(CONFIG_STM32H7_ETHMAC_LPWORK)
# define ETHWORK LPWORK
# else
# define ETHWORK LPWORK
# endif
#endif
#ifndef CONFIG_STM32H7_PHYADDR
# error "CONFIG_STM32H7_PHYADDR must be defined in the NuttX configuration"
#endif
#if !defined(CONFIG_STM32H7_MII) && !defined(CONFIG_STM32H7_RMII)
# warning "Neither CONFIG_STM32H7_MII nor CONFIG_STM32H7_RMII defined"
#endif
#if defined(CONFIG_STM32H7_MII) && defined(CONFIG_STM32H7_RMII)
# error "Both CONFIG_STM32H7_MII and CONFIG_STM32H7_RMII defined"
#endif
#ifdef CONFIG_STM32H7_MII
# if !defined(CONFIG_STM32H7_MII_MCO1) && !defined(CONFIG_STM32H7_MII_MCO2) && \
!defined(CONFIG_STM32H7_MII_EXTCLK)
# warning "Neither CONFIG_STM32H7_MII_MCO1, CONFIG_STM32H7_MII_MCO2, nor CONFIG_STM32H7_MII_EXTCLK defined"
# endif
# if defined(CONFIG_STM32H7_MII_MCO1) && defined(CONFIG_STM32H7_MII_MCO2)
# error "Both CONFIG_STM32H7_MII_MCO1 and CONFIG_STM32H7_MII_MCO2 defined"
# endif
#endif
#ifdef CONFIG_STM32H7_RMII
# if !defined(CONFIG_STM32H7_RMII_MCO1) && !defined(CONFIG_STM32H7_RMII_MCO2) && \
!defined(CONFIG_STM32H7_RMII_EXTCLK)
# warning "Neither CONFIG_STM32H7_RMII_MCO1, CONFIG_STM32H7_RMII_MCO2, nor CONFIG_STM32H7_RMII_EXTCLK defined"
# endif
# if defined(CONFIG_STM32H7_RMII_MCO1) && defined(CONFIG_STM32H7_RMII_MCO2)
# error "Both CONFIG_STM32H7_RMII_MCO1 and CONFIG_STM32H7_RMII_MCO2 defined"
# endif
#endif
#ifdef CONFIG_STM32H7_AUTONEG
# ifndef CONFIG_STM32H7_PHYSR
# error "CONFIG_STM32H7_PHYSR must be defined in the NuttX configuration"
# endif
# ifdef CONFIG_STM32H7_PHYSR_ALTCONFIG
# ifndef CONFIG_STM32H7_PHYSR_ALTMODE
# error "CONFIG_STM32H7_PHYSR_ALTMODE must be defined in the NuttX configuration"
# endif
# ifndef CONFIG_STM32H7_PHYSR_10HD
# error "CONFIG_STM32H7_PHYSR_10HD must be defined in the NuttX configuration"
# endif
# ifndef CONFIG_STM32H7_PHYSR_100HD
# error "CONFIG_STM32H7_PHYSR_100HD must be defined in the NuttX configuration"
# endif
# ifndef CONFIG_STM32H7_PHYSR_10FD
# error "CONFIG_STM32H7_PHYSR_10FD must be defined in the NuttX configuration"
# endif
# ifndef CONFIG_STM32H7_PHYSR_100FD
# error "CONFIG_STM32H7_PHYSR_100FD must be defined in the NuttX configuration"
# endif
# else
# ifndef CONFIG_STM32H7_PHYSR_SPEED
# error "CONFIG_STM32H7_PHYSR_SPEED must be defined in the NuttX configuration"
# endif
# ifndef CONFIG_STM32H7_PHYSR_100MBPS
# error "CONFIG_STM32H7_PHYSR_100MBPS must be defined in the NuttX configuration"
# endif
# ifndef CONFIG_STM32H7_PHYSR_MODE
# error "CONFIG_STM32H7_PHYSR_MODE must be defined in the NuttX configuration"
# endif
# ifndef CONFIG_STM32H7_PHYSR_FULLDUPLEX
# error "CONFIG_STM32H7_PHYSR_FULLDUPLEX must be defined in the NuttX configuration"
# endif
# endif
#endif
#ifdef CONFIG_STM32H7_ETH_PTP
# warning "CONFIG_STM32H7_ETH_PTP is not yet supported"
#endif
#undef CONFIG_STM32H7_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_PKTSIZE + 4 + 15) & ~15)
#ifdef CONFIG_STM32H7_ETH_BUFSIZE
# define ETH_BUFSIZE CONFIG_STM32H7_ETH_BUFSIZE
#else
# define ETH_BUFSIZE OPTIMAL_ETH_BUFSIZE
#endif
#if ETH_BUFSIZE > ETH_CTX_TDES2_MSS_MASK
# error "ETH_BUFSIZE is too large"
#endif
#if (ETH_BUFSIZE & 15) != 0
# error "ETH_BUFSIZE must be aligned"
#endif
#if ETH_BUFSIZE != OPTIMAL_ETH_BUFSIZE
# warning "You are using an incomplete/untested configuration"
#endif
#ifndef CONFIG_STM32H7_ETH_NRXDESC
# define CONFIG_STM32H7_ETH_NRXDESC 8
#endif
#ifndef CONFIG_STM32H7_ETH_NTXDESC
# define CONFIG_STM32H7_ETH_NTXDESC 4
#endif
#ifndef min
# define min(a,b) ((a) < (b) ? (a) : (b))
#endif
/* We need at least one more free buffer than transmit buffers */
#define STM32_ETH_NFREEBUFFERS (CONFIG_STM32H7_ETH_NTXDESC+1)
/* Buffers used for DMA access must begin on an address aligned with the
* D-Cache line and must be an even multiple of the D-Cache line size.
* These size/alignment requirements are necessary so that D-Cache flush
* and invalidate operations will not have any additional effects.
*
* The TX and RX descriptors are 16 bytes in size
*/
#define DMA_BUFFER_MASK (ARMV7M_DCACHE_LINESIZE - 1)
#define DMA_ALIGN_UP(n) (((n) + DMA_BUFFER_MASK) & ~DMA_BUFFER_MASK)
#define DMA_ALIGN_DOWN(n) ((n) & ~DMA_BUFFER_MASK)
#define DESC_SIZE 16
#define DESC_PADSIZE DMA_ALIGN_UP(DESC_SIZE)
#define ALIGNED_BUFSIZE DMA_ALIGN_UP(ETH_BUFSIZE)
#define RXTABLE_SIZE (STM32H7_NETHERNET * CONFIG_STM32H7_ETH_NRXDESC)
#define TXTABLE_SIZE (STM32H7_NETHERNET * CONFIG_STM32H7_ETH_NTXDESC)
#define RXBUFFER_SIZE (CONFIG_STM32H7_ETH_NRXDESC * ALIGNED_BUFSIZE)
#define RXBUFFER_ALLOC (STM32H7_NETHERNET * RXBUFFER_SIZE)
#define TXBUFFER_SIZE (STM32_ETH_NFREEBUFFERS * ALIGNED_BUFSIZE)
#define TXBUFFER_ALLOC (STM32H7_NETHERNET * TXBUFFER_SIZE)
/* Extremely detailed register debug that you would normally never want
* enabled.
*/
#ifndef CONFIG_DEBUG_NET_INFO
# undef CONFIG_STM32H7_ETHMAC_REGDEBUG
#endif
/* Clocking *****************************************************************/
/* Set MACMDIOAR CR bits depending on HCLK setting */
#if STM32_HCLK_FREQUENCY >= 20000000 && STM32_HCLK_FREQUENCY < 35000000
# define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV16
#elif STM32_HCLK_FREQUENCY >= 35000000 && STM32_HCLK_FREQUENCY < 60000000
# define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV26
#elif STM32_HCLK_FREQUENCY >= 60000000 && STM32_HCLK_FREQUENCY < 100000000
# define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV42
#elif STM32_HCLK_FREQUENCY >= 100000000 && STM32_HCLK_FREQUENCY < 150000000
# define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV62
#elif STM32_HCLK_FREQUENCY >= 150000000 && STM32_HCLK_FREQUENCY <= 250000000
# define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV102
#elif STM32_HCLK_FREQUENCY >= 250000000 && STM32_HCLK_FREQUENCY <= 300000000
# define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV124
#else
# error "STM32_HCLK_FREQUENCY not supportable"
#endif
/* Timing *******************************************************************/
/* 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 (0x0001998)
/* MAC reset ready delays in loop counts */
#define MAC_READY_USTIMEOUT (200)
/* 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 0: Receiver enable
* ETH_MACCR_TE Bit 1: Transmitter enable
* ETH_MACCR_PRELEN Bits 2-3: Preamble length for transmit packets
* ETH_MACCR_DC Bit 4: Deferral check
* ETH_MACCR_BL Bits 5-6: Back-off limit
* ETH_MACCR_DR Bit 8: Retry disable
* ETH_MACCR_DCRS Bit 9: Carrier sense disable
* ETH_MACCR_DO Bit 10: Receive own disable
* ETH_MACCR_LM Bit 12: Loopback mode
* ETH_MACCR_DM Bit 13: Duplex mode
* ETH_MACCR_FES Bit 14: Fast Ethernet speed
* ETH_MACCR_JE Bit 16: Jumbo packet enable
* ETH_MACCR_JD Bit 17: Jabber disable
* ETH_MACCR_WD Bit 19: Watchdog disable
* ETH_MACCR_ACS Bit 20: Automatic pad/CRC stripping
* ETH_MACCR_CST Bit 21: CRC stripping for Type frames
* ETH_MACCR_S2KP Bit 22: IEEE 802.3as Support for 2K Packets
* ETH_MACCR_GPSLCE Bit 23: Giant Packet Size Limit Control Enable
* ETH_MACCR_IPG Bits 24-26: Inter-packet gap
* ETH_MACCR_IPC Bit 27: IPv4 checksum offload
* ETH_MACCR_SARC Bits 28-30: Src address insertion or replacement
* ETH_MACCR_ARPEN Bit 31: ARP Offload Enable
*/
#define MACCR_CLEAR_BITS \
(ETH_MACCR_RE | ETH_MACCR_TE | ETH_MACCR_PRELEN_MASK | ETH_MACCR_DC | \
ETH_MACCR_BL_MASK | ETH_MACCR_DR | ETH_MACCR_DCRS | ETH_MACCR_DO | \
ETH_MACCR_LM | ETH_MACCR_DM | ETH_MACCR_FES | ETH_MACCR_JE | \
ETH_MACCR_JD | ETH_MACCR_WD | ETH_MACCR_ACS | ETH_MACCR_CST | \
ETH_MACCR_S2KP | ETH_MACCR_GPSLCE | ETH_MACCR_IPG_MASK | \
ETH_MACCR_IPC | ETH_MACCR_SARC_MASK | ETH_MACCR_ARPEN)
/* 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_ACS Automatic pad/CRC stripping 0 (disabled)
* ETH_MACCR_DR Retry disable 1 (disabled)
* ETH_MACCR_IPC IPv4 checksum offload
* Depends on CONFIG_STM32H7_ETH_HWCHECKSUM
* ETH_MACCR_LM Loopback mode 0 (disabled)
* ETH_MACCR_DO Receive own disable 0 (enabled)
* ETH_MACCR_DCRS Carrier sense disable 0 (enabled)
* ETH_MACCR_IPG Inter-packet gap 0 (96 bits)
* ETH_MACCR_JD Jabber disable 0 (enabled)
* ETH_MACCR_WD Watchdog disable 0 (enabled)
* ETH_MACCR_CST CRC stripping for Type frames 0 (disabled)
*
* The following are set conditionally 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_STM32H7_ETH_HWCHECKSUM
# define MACCR_SET_BITS \
(ETH_MACCR_BL_10 | ETH_MACCR_DR | ETH_MACCR_IPC | ETH_MACCR_IPG(96))
#else
# define MACCR_SET_BITS \
(ETH_MACCR_BL_10 | ETH_MACCR_DR | ETH_MACCR_IPG(96))
#endif
/* Clear the MACPFR 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_MACPFR_PM Bit 0: Promiscuous mode
* ETH_MACPFR_HUC Bit 1: Hash unicast
* ETH_MACPFR_HMC Bit 2: Hash multicast
* ETH_MACPFR_DAIF Bit 3: Destination address inverse filtering
* ETH_MACPFR_PAM Bit 4: Pass all multicast
* ETH_MACPFR_DBF Bit 5: Broadcast frames disable
* ETH_MACPFR_PCF Bits 6-7: Pass control frames
* ETH_MACPFR_SAIF Bit 8: Source address inverse filtering
* ETH_MACPFR_SAF Bit 9: Source address filter
* ETH_MACPFR_HPF Bit 10: Hash or perfect filter
* ETH_MACPFR_VTFE Bit 16: VLAN Tag Filter Enable
* ETH_MACPFR_IPFE Bit 20: Layer 3 and Layer 4 Filter Enable
* ETH_MACPFR_DNTU Bit 21: Drop Non-TCP/UDP over IP Packets
* ETH_MACPFR_RA Bit 31: Receive all
*/
#define MACPFR_CLEAR_BITS \
(ETH_MACPFR_PM | ETH_MACPFR_HUC | ETH_MACPFR_HMC | ETH_MACPFR_DAIF | \
ETH_MACPFR_PAM | ETH_MACPFR_DBF | ETH_MACPFR_PCF_MASK | ETH_MACPFR_SAIF | \
ETH_MACPFR_SAF | ETH_MACPFR_HPF | ETH_MACPFR_VTFE | ETH_MACPFR_IPFE | \
ETH_MACPFR_DNTU | ETH_MACPFR_RA)
/* The following bits are set or left zero unconditionally in all modes.
*
* ETH_MACPFR_HUC Hash unicast 0 (perfect
* dest filtering)
* ETH_MACPFR_HMC Hash multicast 0 (perfect
* dest filtering)
* ETH_MACPFR_DAIF Destination address inverse filtering 0 (normal)
* ETH_MACPFR_PAM Pass all multicast 0 (Depends on HMC
* bit)
* ETH_MACPFR_DBF Broadcast frames disable 0 (enabled)
* ETH_MACPFR_PCF Pass control frames 1 (block all but
* PAUSE)
* ETH_MACPFR_SAIF Source address inverse filtering 0 (not used)
* ETH_MACPFR_SAF Source address filter 0 (disabled)
* ETH_MACPFR_HPF Hash or perfect filter 0 (Only matching
* frames passed)
* ETH_MACPFR_RA Receive all 0 (disabled)
*/
#ifdef CONFIG_NET_PROMISCUOUS
# define MACPFR_SET_BITS (ETH_MACPFR_PCF_PAUSE | ETH_MACPFR_PM)
#else
# define MACPFR_SET_BITS (ETH_MACPFR_PCF_PAUSE)
#endif
/* Clear the MACQTXFCR and MACRXFCR 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_MACQTXFCR_FCB_BPA Bit 0: Flow control busy/back pressure activate
* ETH_MACQTXFCR_TFE Bit 1: Transmit flow control enable
* ETH_MACQTXFCR_PLT Bits 4-6: Pause low threshold
* ETH_MACQTXFCR_DZPQ Bit 7: Zero-quanta pause disable
* ETH_MACQTXFCR_PT Bits 16-31: Pause time
* ETH_MACRXFCR_RFE Bit 0: Receive flow control enable
* ETH_MACRXFCR_UP Bit 1: Unicast pause frame detect
*/
#define MACQTXFCR_CLEAR_MASK \
(ETH_MACQTXFCR_FCB_BPA | ETH_MACQTXFCR_TFE | \
ETH_MACQTXFCR_PLT_MASK | ETH_MACQTXFCR_DZPQ | ETH_MACQTXFCR_PT_MASK)
#define MACRXFCR_CLEAR_MASK \
(ETH_MACRXFCR_RFE | ETH_MACRXFCR_UP)
/* The following bits are set or left zero unconditionally in all modes.
*
* ETH_MACQTXFCR_FCB_BPA Flow control busy/back pressure activate 0
* (no pause control frame)
* ETH_MACQTXFCR_TFE Transmit flow control enable 0
* (disabled)
* ETH_MACQTXFCR_PLT Pause low threshold 0
* (pause time - 4)
* ETH_MACQTXFCR_DZPQ Zero-quanta pause disable 1
* (disabled)
* ETH_MACQTXFCR_PT Pause time 0
* ETH_MACRXFCR_RFE Receive flow control enable 0
* (disabled)
* ETH_MACRXFCR_UP Unicast pause frame detect 0
* (disabled)
*/
#define MACQTXFCR_SET_MASK (ETH_MACQTXFCR_PLT_M4 | ETH_MACQTXFCR_DZPQ)
#define MACRXFCR_SET_MASK (0)
/* Clear the MTLTXQOMR 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_MTLTXQOMR_FTQ Bit 0: Flush Transmit Queue
* ETH_MTLTXQOMR_TSF Bit 1: Transmit Store and Forward
* ETH_MTLTXQOMR_TXQEN Bits 2-3: Transmit Queue Enable
* ETH_MTLTXQOMR_TTC Bits 4-6: Transmit Threshold Control
* ETH_MTLTXQOMR_TQS Bits 16-24: Transmit Queue Size
*/
#define MTLTXQOMR_CLEAR_MASK \
(ETH_MTLTXQOMR_FTQ | ETH_MTLTXQOMR_TSF | \
ETH_MTLTXQOMR_TXQEN_MASK | ETH_MTLTXQOMR_TTC_MASK | \
ETH_MTLTXQOMR_TQS_MASK)
/* Clear the MTLRXQOMR 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_MTLRXQOMR_RTC_MASK Bits 0-1: Receive Queue Threshold Control
* ETH_MTLRXQOMR_FUP Bit 3: Forward Undersized Good Packets
* ETH_MTLRXQOMR_FEP Bit 4: Forward Error Packets
* ETH_MTLRXQOMR_RSF Bit 5: Receive Queue Store and Forward
* ETH_MTLRXQOMR_DIS_TCP_EF Bit 6: Disable Dropping of TCP/IP Checksum Error
* Packets
* ETH_MTLRXQOMR_EHFC Bit 7: Enable Hardware Flow Control
* ETH_MTLRXQOMR_RFA_MASK Bits 8-10: Threshold for Activating Flow Control
* ETH_MTLRXQOMR_RFD_MASK Bits 14-16: Threshold for Deactivating Flow
* Control
* ETH_MTLRXQOMR_RQS_MASK Bits 20-22: Receive Queue Size
*/
#define MTLRXQOMR_CLEAR_MASK \
(ETH_MTLRXQOMR_RTC_MASK | ETH_MTLRXQOMR_FUP | \
ETH_MTLRXQOMR_FEP | ETH_MTLRXQOMR_RSF | \
ETH_MTLRXQOMR_DIS_TCP_EF | ETH_MTLRXQOMR_EHFC | \
ETH_MTLRXQOMR_RFA_MASK | ETH_MTLRXQOMR_RFD_MASK | \
ETH_MTLRXQOMR_RQS_MASK)
#define MTLTXQOMR_SET_MASK \
((0xd << ETH_MTLTXQOMR_TQS_SHIFT) | ETH_MTLTXQOMR_TTC_64 | \
ETH_MTLTXQOMR_TXQEN_ENABLED | ETH_MTLTXQOMR_FTQ)
/* TODO: Check whether use flow control (bits RFD, RFA EHFC) */
#define MTLRXQOMR_SET_MASK \
((0x7 << ETH_MTLRXQOMR_RQS_SHIFT) | ETH_MTLRXQOMR_RTC_64)
#ifdef CONFIG_STM32H7_ETH_HWCHECKSUM
/* TODO */
# error CONFIG_STM32H7_ETH_HWCHECKSUM not supported
#endif
/* Clear the DMAMR 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_DMAMR_SWR Bit 0: Software Reset
* ETH_DMAMR_DA Bit 1: DMA Tx or Rx Arbitration Scheme
* ETH_DMAMR_TXPR Bit 11: Transmit priority
* ETH_DMAMR_PR Bits 12-14: Priority ratio
* ETH_DMAMR_INTM Bits 16-17: Interrupt Mode
*/
#define DMAMR_CLEAR_MASK \
(ETH_DMAMR_SWR | ETH_DMAMR_DA | ETH_DMAMR_TXPR | ETH_DMAMR_PR_MASK | \
ETH_DMAMR_INTM_MASK)
#define DMAMR_SET_MASK (ETH_DMAMR_PR_2TO1)
/* TODO: Comment these bits */
#define DMACCR_CLEAR_MASK \
( ETH_DMACCR_MSS_MASK | ETH_DMACCR_PBLX8 | ETH_DMACCR_DSL_MASK)
/* Set the descriptor skip length in 32-bit words */
#define DMACCR_SET_MASK \
(ETH_DMACCR_DSL((DESC_PADSIZE - DESC_SIZE)/4))
#define DMASBMR_CLEAR_MASK \
(STM32_ETH_DMASBMR_FB | STM32_ETH_DMASBMR_AAL | \
STM32_ETH_DMASBMR_MB | STM32_ETH_DMASBMR_RB)
#define DMASBMR_SET_MASK \
(STM32_ETH_DMASBMR_FB | STM32_ETH_DMASBMR_AAL)
#define DMACTXCR_CLEAR_MASK \
(ETH_DMACTXCR_ST | ETH_DMACTXCR_ST | ETH_DMACTXCR_OSF | \
ETH_DMACTXCR_TSE | ETH_DMACTXCR_TXPBL_MASK)
#define DMACTXCR_SET_MASK \
(ETH_DMACTXCR_OSF | ETH_DMACTXCR_TXPBL(32))
#define DMACRXCR_CLEAR_MASK \
(ETH_DMACRXCR_SR | ETH_DMACRXCR_RBSZ_MASK | \
ETH_DMACRXCR_RXPBL_MASK | ETH_DMACRXCR_RPF)
#define DMACRXCR_SET_MASK \
(ETH_DMACRXCR_RBSZ(ALIGNED_BUFSIZE) | ETH_DMACRXCR_RXPBL(32))
/* 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_DMACIER_TIE | ETH_DMACIER_TBUE | ETH_DMACIER_RIE | ETH_DMACIER_ERIE)
#define ETH_DMAINT_ABNORMAL \
(ETH_DMACIER_TXSE | ETH_DMACIER_RBUE | ETH_DMACIER_RSE | \
ETH_DMACIER_RWTE | /* ETH_DMACIER_ETIE | */ ETH_DMACIER_FBEE)
/* Normal receive, transmit, error interrupt enable bit sets */
#define ETH_DMAINT_RECV_ENABLE (ETH_DMACIER_NIE | ETH_DMACIER_RIE)
#define ETH_DMAINT_XMIT_ENABLE (ETH_DMACIER_NIE | ETH_DMACIER_TIE)
#define ETH_DMAINT_XMIT_DISABLE (ETH_DMACIER_TIE)
#ifdef CONFIG_DEBUG_NET
# define ETH_DMAINT_ERROR_ENABLE (ETH_DMACIER_AIE | 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
****************************************************************************/
/* This union type forces the allocated size of TX&RX descriptors to be the
* padded to a exact multiple of the Cortex-M7 D-Cache line size.
*/
union stm32_desc_u
{
uint8_t pad[DESC_PADSIZE];
struct eth_desc_s desc;
};
/* 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 */
uint8_t intf; /* Ethernet interface number */
struct wdog_s txtimeout; /* TX timeout timer */
struct work_s irqwork; /* For deferring interrupt work to the work queue */
struct work_s pollwork; /* For deferring poll work to the work queue */
/* 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_desc_s *txhead; /* Next available TX descriptor */
struct eth_desc_s *rxhead; /* Next available RX descriptor */
struct eth_desc_s *txchbase; /* TX descriptor ring base address */
struct eth_desc_s *rxchbase; /* RX descriptor ring base address */
struct eth_desc_s *txtail; /* First "in_flight" TX descriptor */
struct eth_desc_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 */
};
/****************************************************************************
* Private Data
****************************************************************************/
/* DMA buffers. DMA buffers must:
*
* 1. Be a multiple of the D-Cache line size. This requirement is assured
* by the definition of RXDMA buffer size above.
* 2. Be aligned a D-Cache line boundaries, and
* 3. Be positioned in DMA-able memory. This must be managed by logic
* in the linker script file.
*
* These DMA buffers are defined sequentially here to best assure optimal
* packing of the buffers.
*/
/* Descriptor allocations */
static union stm32_desc_u g_rxtable[RXTABLE_SIZE]
aligned_data(ARMV7M_DCACHE_LINESIZE);
static union stm32_desc_u g_txtable[TXTABLE_SIZE]
aligned_data(ARMV7M_DCACHE_LINESIZE);
/* Buffer allocations */
static uint8_t g_rxbuffer[RXBUFFER_ALLOC]
aligned_data(ARMV7M_DCACHE_LINESIZE);
static uint8_t g_txbuffer[TXBUFFER_ALLOC]
aligned_data(ARMV7M_DCACHE_LINESIZE);
/* These are the pre-allocated Ethernet device structures */
static struct stm32_ethmac_s g_stm32ethmac[STM32H7_NETHERNET];
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Register operations ******************************************************/
#ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG
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(struct stm32_ethmac_s *priv,
uint8_t *txbuffer);
static inline uint8_t *stm32_allocbuffer(struct stm32_ethmac_s *priv);
static inline void stm32_freebuffer(struct stm32_ethmac_s *priv,
uint8_t *buffer);
static inline bool stm32_isfreebuffer(struct stm32_ethmac_s *priv);
/* Common TX logic */
static int stm32_transmit(struct stm32_ethmac_s *priv);
static int stm32_txpoll(struct net_driver_s *dev);
static void stm32_dopoll(struct stm32_ethmac_s *priv);
/* Interrupt handling */
static void stm32_enableint(struct stm32_ethmac_s *priv, uint32_t ierbit);
static void stm32_disableint(struct stm32_ethmac_s *priv, uint32_t ierbit);
static void stm32_freesegment(struct stm32_ethmac_s *priv,
struct eth_desc_s *rxfirst, int segments);
static int stm32_recvframe(struct stm32_ethmac_s *priv);
static void stm32_receive(struct stm32_ethmac_s *priv);
static void stm32_freeframe(struct stm32_ethmac_s *priv);
static void stm32_txdone(struct stm32_ethmac_s *priv);
static void stm32_interrupt_work(void *arg);
static int stm32_interrupt(int irq, void *context, void *arg);
/* Watchdog timer expirations */
static void stm32_txtimeout_work(void *arg);
static void stm32_txtimeout_expiry(wdparm_t arg);
/* NuttX callback functions */
static int stm32_ifup(struct net_driver_s *dev);
static int stm32_ifdown(struct net_driver_s *dev);
static void stm32_txavail_work(void *arg);
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, const uint8_t *mac);
#endif
#ifdef CONFIG_NET_IGMP
static int stm32_rmmac(struct net_driver_s *dev, const uint8_t *mac);
#endif
#ifdef CONFIG_NETDEV_PHY_IOCTL
static int stm32_ioctl(struct net_driver_s *dev, int cmd,
unsigned long arg);
#endif
/* Descriptor Initialization */
static void stm32_txdescinit(struct stm32_ethmac_s *priv,
union stm32_desc_u *txtable);
static void stm32_rxdescinit(struct stm32_ethmac_s *priv,
union stm32_desc_u *rxtable, uint8_t *rxbuffer);
/* PHY Initialization */
#if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT)
static int stm32_phyintenable(struct stm32_ethmac_s *priv);
#endif
static int stm32_phyread(uint16_t phydevaddr, uint16_t phyregaddr,
uint16_t *value);
static int stm32_phywrite(uint16_t phydevaddr, uint16_t phyregaddr,
uint16_t value, uint16_t mask);
#ifdef CONFIG_ETH0_PHY_DM9161
static inline int stm32_dm9161(struct stm32_ethmac_s *priv);
#endif
static int stm32_phyinit(struct stm32_ethmac_s *priv);
#ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG
static void stm32_phyregdump(void);
#endif
/* MAC/DMA Initialization */
#ifdef CONFIG_STM32H7_MII
static inline void stm32_selectmii(void);
#endif
#ifdef CONFIG_STM32H7_RMII
static inline void stm32_selectrmii(void);
#endif
static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv);
static void stm32_ethreset(struct stm32_ethmac_s *priv);
static int stm32_macconfig(struct stm32_ethmac_s *priv);
static void stm32_macaddress(struct stm32_ethmac_s *priv);
#ifdef CONFIG_NET_ICMPv6
static void stm32_ipv6multicast(struct stm32_ethmac_s *priv);
#endif
static int stm32_macenable(struct stm32_ethmac_s *priv);
static int stm32_ethconfig(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_STM32H7_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
*
****************************************************************************/
#ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG
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_STM32H7_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
* txbuffer - DMA memory allocated for TX buffers.
*
* Returned Value:
* None
*
* Assumptions:
* Called during early driver initialization before Ethernet interrupts
* are enabled.
*
****************************************************************************/
static void stm32_initbuffer(struct stm32_ethmac_s *priv, uint8_t *txbuffer)
{
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 = txbuffer;
i < STM32_ETH_NFREEBUFFERS;
i++, buffer += ALIGNED_BUFSIZE)
{
sq_addlast((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(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(struct stm32_ethmac_s *priv,
uint8_t *buffer)
{
/* Free the buffer by adding it to the end of the free buffer list */
sq_addlast((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(struct stm32_ethmac_s *priv)
{
/* Return TRUE if the free buffer list is not empty */
return !sq_empty(&priv->freeb);
}
/****************************************************************************
* Function: stm32_get_next_txdesc
*
* Description:
* Returns the next tx descriptor in the list
*
* Parameters:
* priv - Reference to the driver state structure
* curr - Pointer to a tx descriptor
*
* Returned Value:
* pointer to the next tx descriptor for the current interface
*
****************************************************************************/
static struct eth_desc_s *stm32_get_next_txdesc(struct stm32_ethmac_s *priv,
struct eth_desc_s * curr)
{
union stm32_desc_u *first =
&g_txtable[priv->intf * CONFIG_STM32H7_ETH_NTXDESC];
union stm32_desc_u *last =
&g_txtable[priv->intf * CONFIG_STM32H7_ETH_NTXDESC +
CONFIG_STM32H7_ETH_NTXDESC - 1];
union stm32_desc_u *next = ((union stm32_desc_u *)curr) + 1;
if (next > last)
{
next = first;
}
return &next->desc;
}
/****************************************************************************
* 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(struct stm32_ethmac_s *priv)
{
struct eth_desc_s *txdesc;
struct eth_desc_s *txfirst;
/* The internal (optimal) network buffer size may be configured to be
* larger than the Ethernet buffer size.
*/
#if OPTIMAL_ETH_BUFSIZE > ALIGNED_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 tdes3: %08" PRIx32 "\n",
priv->dev.d_len, priv->dev.d_buf, txdesc, txdesc->des3);
DEBUGASSERT(txdesc);
/* Flush the contents of the TX buffer into physical memory */
up_clean_dcache((uintptr_t)priv->dev.d_buf,
(uintptr_t)priv->dev.d_buf + priv->dev.d_len);
/* 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 > ALIGNED_BUFSIZE
if (priv->dev.d_len > ALIGNED_BUFSIZE)
{
/* Yes... how many buffers will be need to send the packet? */
bufcount = (priv->dev.d_len + (ALIGNED_BUFSIZE - 1)) / ALIGNED_BUFSIZE;
lastsize = priv->dev.d_len - (bufcount - 1) * ALIGNED_BUFSIZE;
ninfo("bufcount: %d lastsize: %d\n", bufcount, lastsize);
/* Set the first segment bit in the first TX descriptor */
txdesc->des3 = ETH_TDES3_RD_FD;
/* Set up all but the last TX descriptor */
buffer = priv->dev.d_buf;
for (i = 0; i < bufcount; i++)
{
DEBUGASSERT((txdesc->des3 & ETH_TDES3_RD_OWN) == 0);
/* Set the Buffer1 address pointer */
txdesc->des0 = (uint32_t)buffer;
/* Set the Buffer2 address pointer */
txdesc->des1 = 0;
/* 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
*/
txdesc->des3 |= ETH_TDES3_RD_LD;
/* This segment is, most likely, of fractional buffersize */
/* ask for an interrupt when this segment transfer completes. */
txdesc->des2 = lastsize | ETH_TDES2_RD_IOC;
buffer += lastsize;
}
else
{
/* This is not the last segment. We don't want an interrupt
* when this segment transfer completes.
*/
/* The size of the transfer is the whole buffer */
txdesc->des2 = ALIGNED_BUFSIZE;
buffer += ALIGNED_BUFSIZE;
}
/* Give the descriptor to DMA */
txdesc->des3 |= ETH_TDES3_RD_OWN;
/* Flush the contents of the modified TX descriptor into physical
* memory.
*/
up_clean_dcache((uintptr_t)txdesc,
(uintptr_t)txdesc + sizeof(struct eth_desc_s));
/* Point to the next available TX descriptor */
txdesc = stm32_get_next_txdesc(priv, txdesc);
}
}
else
#endif
{
DEBUGASSERT((txdesc->des3 & ETH_TDES3_RD_OWN) == 0);
/* Set the Buffer1 address pointer */
txdesc->des0 = (uint32_t)priv->dev.d_buf;
/* Set the Buffer2 address pointer */
txdesc->des1 = 0;
/* Set frame size, and we do
* want an interrupt when the transfer completes.
*/
DEBUGASSERT(priv->dev.d_len <= CONFIG_NET_ETH_PKTSIZE);
txdesc->des2 = priv->dev.d_len | ETH_TDES2_RD_IOC;
/* The single descriptor is both the first and last segment. */
/* Set OWN bit of the TX descriptor des3. This gives the buffer to
* Ethernet DMA
*/
txdesc->des3 = (ETH_TDES3_RD_OWN | ETH_TDES3_RD_LD | ETH_TDES3_RD_FD);
/* Flush the contents of the modified TX descriptor into physical
* memory.
*/
up_clean_dcache((uintptr_t)txdesc,
(uintptr_t)txdesc + sizeof(struct eth_desc_s));
/* Point to the next available TX descriptor */
txdesc = stm32_get_next_txdesc(priv, txdesc);
}
/* 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_STM32H7_ETH_NTXDESC)
{
stm32_disableint(priv, ETH_DMACIER_RIE);
}
MEMORY_SYNC();
/* Enable TX interrupts */
stm32_enableint(priv, ETH_DMACIER_TIE);
/* Setup the TX timeout watchdog (perhaps restarting the timer) */
wd_start(&priv->txtimeout, STM32_TXTIMEOUT,
stm32_txtimeout_expiry, (wdparm_t)priv);
/* Update the tx descriptor tail pointer register to start the DMA */
putreg32((uintptr_t)txdesc, STM32_ETH_DMACTXDTPR);
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)
{
struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private;
DEBUGASSERT(priv->dev.d_buf != NULL);
/* 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_TDES3_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_STM32H7_ETH_NTXDESC).
*/
if ((priv->txhead->des3 & ETH_TDES3_RD_OWN) != 0 ||
priv->txhead->des0 != 0)
{
/* We have to terminate the poll if we have no more descriptors
* available for another transfer.
*/
nerr("No tx descriptors available");
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. */
nerr("No tx buffer available");
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(struct stm32_ethmac_s *priv)
{
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_TDES3_RD_OWN may be cleared BUT still
* not available because stm32_freeframe() has not yet run. If
* stm32_freeframe() has run, the buffer1 pointer (des0) will be
* nullified (and inflight should be < CONFIG_STM32H7_ETH_NTXDESC).
*/
if ((priv->txhead->des3 & ETH_TDES3_RD_OWN) == 0 &&
priv->txhead->des0 == 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)
{
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;
}
}
else
{
nerr("No tx buffers");
}
}
else
{
nerr("No tx descriptors\n");
}
}
/****************************************************************************
* 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(struct stm32_ethmac_s *priv, uint32_t ierbit)
{
uint32_t regval;
/* Enable the specified "normal" interrupt */
regval = stm32_getreg(STM32_ETH_DMACIER);
regval |= (ETH_DMACIER_NIE | ierbit);
stm32_putreg(regval, STM32_ETH_DMACIER);
}
/****************************************************************************
* 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(struct stm32_ethmac_s *priv, uint32_t ierbit)
{
uint32_t regval;
/* Disable the "normal" interrupt */
regval = stm32_getreg(STM32_ETH_DMACIER);
regval &= ~ierbit;
/* Are all "normal" interrupts now disabled? */
if ((regval & ETH_DMAINT_NORMAL) == 0)
{
/* Yes.. disable normal interrupts */
regval &= ~ETH_DMACIER_NIE;
}
stm32_putreg(regval, STM32_ETH_DMACIER);
}
/****************************************************************************
* Function: stm32_get_next_rxdesc
*
* Description:
* Returns the next rx descriptor in the list
*
* Parameters:
* priv - Reference to the driver state structure
* curr - Pointer to a rx descriptor
*
* Returned Value:
* pointer to the next rx descriptor for the current interface
*
****************************************************************************/
static struct eth_desc_s *stm32_get_next_rxdesc(struct stm32_ethmac_s *priv,
struct eth_desc_s * curr)
{
union stm32_desc_u *first =
&g_rxtable[priv->intf * CONFIG_STM32H7_ETH_NRXDESC];
union stm32_desc_u *last =
&g_rxtable[priv->intf * CONFIG_STM32H7_ETH_NRXDESC +
CONFIG_STM32H7_ETH_NRXDESC - 1];
union stm32_desc_u *next = ((union stm32_desc_u *)curr) + 1;
if (next > last)
{
next = first;
}
return &next->desc;
}
/****************************************************************************
* 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(struct stm32_ethmac_s *priv,
struct eth_desc_s *rxfirst, int segments)
{
struct eth_desc_s *rxdesc;
int i;
ninfo("rxfirst: %p segments: %d\n", rxfirst, segments);
/* Give the freed RX buffers back to the Ethernet MAC to be refilled */
rxdesc = rxfirst;
for (i = 0; i < segments; i++)
{
/* Set OWN bit in RX descriptors. This gives the buffers back to DMA */
rxdesc->des3 = ETH_RDES3_RD_OWN | ETH_RDES3_RD_IOC |
ETH_RDES3_RD_BUF1V;
/* Make sure that the modified RX descriptor is written to physical
* memory.
*/
up_clean_dcache((uintptr_t)rxdesc,
(uintptr_t)rxdesc + sizeof(struct eth_desc_s));
/* Get the next RX descriptor in the chain */
rxdesc = stm32_get_next_rxdesc(priv, rxdesc);
/* Update the tail pointer */
stm32_putreg((uintptr_t)rxdesc, STM32_ETH_DMACRXDTPR);
}
/* Reset the segment management logic */
priv->rxcurr = NULL;
priv->segments = 0;
/* Check if the RX Buffer unavailable flag is set */
if ((stm32_getreg(STM32_ETH_DMACSR) & ETH_DMACSR_RBU) != 0)
{
/* Clear the RBU flag */
stm32_putreg(ETH_DMACSR_RBU, STM32_ETH_DMACSR);
nerr("ETH_DMACSR_RBU\n");
/* To resume processing Rx descriptors, the application should change
* the ownership of the descriptor and issue a Receive Poll Demand
* command. If this command is not issued, the Rx process resumes when
* the next recognized incoming packet is received. In ring mode, the
* application should advance the Receive Descriptor Tail Pointer
* register of a channel. This bit is set only when the DMA owns the
* previous Rx descriptor.
*/
}
}
/****************************************************************************
* 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(struct stm32_ethmac_s *priv)
{
struct eth_desc_s *rxdesc;
struct eth_desc_s *rxcurr = NULL;
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;
/* Forces the first RX descriptor to be re-read from physical memory */
up_invalidate_dcache((uintptr_t)rxdesc,
(uintptr_t)rxdesc + sizeof(struct eth_desc_s));
for (i = 0;
(rxdesc->des3 & ETH_RDES3_WB_OWN) == 0 &&
i < CONFIG_STM32H7_ETH_NRXDESC &&
priv->inflight < CONFIG_STM32H7_ETH_NTXDESC;
i++)
{
/* Check if this is a normal descriptor */
if (!(rxdesc->des3 & ETH_RDES3_WB_CTXT))
{
/* Check if this is the first segment in the frame */
if ((rxdesc->des3 & ETH_RDES3_WB_FD) != 0 &&
(rxdesc->des3 & ETH_RDES3_WB_LD) == 0)
{
priv->rxcurr = rxdesc;
priv->segments = 1;
}
/* Check if this is an intermediate segment in the frame */
else if (((rxdesc->des3 & ETH_RDES3_WB_LD) == 0) &&
((rxdesc->des3 & ETH_RDES3_WB_FD) == 0))
{
priv->segments++;
}
/* Otherwise, it is the last segment in the frame */
else
{
priv->segments++;
/* Check if 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->des3 & ETH_RDES3_WB_ES) == 0)
{
struct net_driver_s *dev = &priv->dev;
/* Get the Frame Length of the received packet: subtract 4
* bytes of the CRC
*/
dev->d_len = ((rxdesc->des3 & ETH_RDES3_WB_PL_MASK) >>
ETH_RDES3_WB_PL_SHIFT) - 4;
if (priv->segments > 1 ||
dev->d_len > ALIGNED_BUFSIZE)
{
/* The Frame is to big, it spans segments */
nerr("ERROR: Dropped, RX descriptor Too big: %d in %d "
"segments\n", dev->d_len, priv->segments);
stm32_freesegment(priv, rxcurr, priv->segments);
}
else
{
/* 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->des0;
rxcurr->des0 = (uint32_t)buffer;
/* Make sure that the modified RX descriptor is written
* to physical memory.
*/
up_clean_dcache((uintptr_t)rxcurr,
(uintptr_t)rxdesc +
sizeof(struct eth_desc_s));
/* Remember where we should re-start scanning and reset
* the segment scanning logic
*/
priv->rxhead = stm32_get_next_rxdesc(priv, rxdesc);
stm32_freesegment(priv, rxcurr, priv->segments);
/* Force the completed RX DMA buffer to be re-read from
* physical memory.
*/
up_invalidate_dcache((uintptr_t)dev->d_buf,
(uintptr_t)dev->d_buf +
min(dev->d_len, ALIGNED_BUFSIZE));
ninfo("rxhead: %p d_buf: %p d_len: %d\n",
priv->rxhead, dev->d_buf, dev->d_len);
/* Return success */
return OK;
}
}
else
{
/* Drop the frame that contains the errors, reset the
* segment scanning logic, and continue scanning with the
* next frame.
*/
nwarn("WARNING: DROPPED RX descriptor errors: "
"%08" PRIx32 "\n",
rxdesc->des3);
stm32_freesegment(priv, rxcurr, priv->segments);
}
}
}
else
{
/* Drop the context descriptors, we are not interested */
DEBUGASSERT(rxcurr != NULL);
stm32_freesegment(priv, rxcurr, 1);
}
/* Try the next descriptor */
rxdesc = stm32_get_next_rxdesc(priv, rxdesc);
/* Force the next RX descriptor to be re-read from physical memory */
up_invalidate_dcache((uintptr_t)rxdesc,
(uintptr_t)rxdesc + sizeof(struct eth_desc_s));
}
/* 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(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_PKTSIZE)
{
nwarn("WARNING: 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("IPv6 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
{
nwarn("WARNING: 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(struct stm32_ethmac_s *priv)
{
struct eth_desc_s *txdesc;
uint32_t des3_tmp;
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);
/* Force re-reading of the TX descriptor for physical memory */
up_invalidate_dcache((uintptr_t)txdesc,
(uintptr_t)txdesc + sizeof(struct eth_desc_s));
for (i = 0; (txdesc->des3 & ETH_TDES3_RD_OWN) == 0; i++)
{
/* There should be a buffer assigned to all in-flight
* TX descriptors.
*/
ninfo("txtail: %p des0: %08" PRIx32
" des2: %08" PRIx32 " des3: %08" PRIx32 "\n",
txdesc, txdesc->des0, txdesc->des2, txdesc->des3);
DEBUGASSERT(txdesc->des0 != 0);
/* Yes.. Free the buffer */
stm32_freebuffer(priv, (uint8_t *)txdesc->des0);
/* In any event, make sure that des0-3 are nullified. */
txdesc->des0 = 0;
txdesc->des1 = 0;
txdesc->des2 = 0;
des3_tmp = txdesc->des3;
txdesc->des3 = 0;
/* Flush the contents of the modified TX descriptor into
* physical memory.
*/
up_clean_dcache((uintptr_t)txdesc,
(uintptr_t)txdesc + sizeof(struct eth_desc_s));
/* Check if this was the last segment of a TX frame */
if ((des3_tmp & ETH_TDES3_RD_LD) != 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_DMACIER_RIE);
/* 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 = stm32_get_next_txdesc(priv, txdesc);
/* Force re-reading of the TX descriptor for physical memory */
up_invalidate_dcache((uintptr_t)txdesc,
(uintptr_t)txdesc +
sizeof(struct eth_desc_s));
}
/* 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(s) is done
*
* Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* Global interrupts are disabled by the watchdog logic.
*
****************************************************************************/
static void stm32_txdone(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);
/* And disable further TX interrupts. */
stm32_disableint(priv, ETH_DMACIER_TIE);
}
/* Then poll the network for new XMIT data */
stm32_dopoll(priv);
}
/****************************************************************************
* 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
*
****************************************************************************/
static void stm32_interrupt_work(void *arg)
{
struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg;
uint32_t dmasr;
DEBUGASSERT(priv);
/* Process pending Ethernet interrupts */
net_lock();
/* Get the DMA interrupt status bits (no MAC interrupts are expected) */
dmasr = stm32_getreg(STM32_ETH_DMACSR);
/* 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_DMACIER);
/* Check if there are pending "normal" interrupts */
if ((dmasr & ETH_DMACSR_NIS) != 0)
{
/* Yes.. Check if we received an incoming packet, if so, call
* stm32_receive()
*/
if ((dmasr & ETH_DMACSR_RI) != 0)
{
/* Clear the pending receive interrupt */
stm32_putreg(ETH_DMACSR_RI, STM32_ETH_DMACSR);
/* 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_DMACSR_TI) != 0)
{
/* Clear the pending receive interrupt */
stm32_putreg(ETH_DMACSR_TI, STM32_ETH_DMACSR);
/* Check if there are pending transmissions */
stm32_txdone(priv);
}
/* Clear the pending normal summary interrupt */
stm32_putreg(ETH_DMACSR_NIS, STM32_ETH_DMACSR);
}
/* Handle error interrupt only if CONFIG_DEBUG_NET is eanbled */
#ifdef CONFIG_DEBUG_NET
/* Check if there are pending "abnormal" interrupts */
if ((dmasr & ETH_DMACSR_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_DMACSR);
/* Clear the pending abnormal summary interrupt */
stm32_putreg(ETH_DMACSR_AIS, STM32_ETH_DMACSR);
}
#endif
net_unlock();
/* Re-enable Ethernet interrupts at the NVIC */
up_enable_irq(STM32_IRQ_ETH);
}
/****************************************************************************
* 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, void *context, void *arg)
{
struct stm32_ethmac_s *priv = &g_stm32ethmac[0];
uint32_t dmasr;
/* Get the DMA interrupt status bits (no MAC interrupts are expected) */
dmasr = stm32_getreg(STM32_ETH_DMACSR);
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_DMACSR_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);
}
DEBUGASSERT(work_available(&priv->irqwork));
/* Schedule to perform the interrupt processing on the worker thread. */
work_queue(ETHWORK, &priv->irqwork, stm32_interrupt_work, priv, 0);
}
return OK;
}
/****************************************************************************
* 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
*
****************************************************************************/
static void stm32_txtimeout_work(void *arg)
{
struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg;
/* Reset the hardware. Just take the interface down, then back up again. */
net_lock();
stm32_ifdown(&priv->dev);
stm32_ifup(&priv->dev);
/* Then poll for new XMIT data */
stm32_dopoll(priv);
net_unlock();
}
/****************************************************************************
* 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:
* arg - The argument
*
* Returned Value:
* None
*
* Assumptions:
* Global interrupts are disabled by the watchdog logic.
*
****************************************************************************/
static void stm32_txtimeout_expiry(wdparm_t arg)
{
struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg;
nerr("ERROR: Timeout!\n");
/* 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);
/* Schedule to perform the TX timeout processing on the worker thread. */
DEBUGASSERT(work_available(&priv->irqwork));
work_queue(ETHWORK, &priv->irqwork, stm32_txtimeout_work, priv, 0);
}
/****************************************************************************
* 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)
{
struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private;
int ret;
#ifdef CONFIG_NET_IPv4
ninfo("Bringing up: %d.%d.%d.%d\n",
(int)(dev->d_ipaddr & 0xff), (int)((dev->d_ipaddr >> 8) & 0xff),
(int)((dev->d_ipaddr >> 16) & 0xff), (int)(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;
}
/* 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)
{
struct stm32_ethmac_s *priv = (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 timeout timers */
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_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.
*
****************************************************************************/
static void stm32_txavail_work(void *arg)
{
struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg;
ninfo("ifup: %d\n", priv->ifup);
/* Ignore the notification if the interface is not yet up */
net_lock();
if (priv->ifup)
{
/* Poll the network for new XMIT data */
stm32_dopoll(priv);
}
net_unlock();
}
/****************************************************************************
* 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)
{
struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private;
/* 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->pollwork))
{
/* Schedule to serialize the poll on the worker thread. */
work_queue(ETHWORK, &priv->pollwork, stm32_txavail_work, priv, 0);
}
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, 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_MACHT1R;
hashindex -= 32;
}
else
{
registeraddress = STM32_ETH_MACHT0R;
}
temp = stm32_getreg(registeraddress);
temp |= 1 << hashindex;
stm32_putreg(temp, registeraddress);
temp = stm32_getreg(STM32_ETH_MACPFR);
temp |= (ETH_MACPFR_HMC | ETH_MACPFR_HPF);
stm32_putreg(temp, STM32_ETH_MACPFR);
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, 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_MACHT0R;
hashindex -= 32;
}
else
{
registeraddress = STM32_ETH_MACHT1R;
}
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_MACHT0R) == 0 &&
stm32_getreg(STM32_ETH_MACHT1R) == 0)
{
temp = stm32_getreg(STM32_ETH_MACPFR);
temp &= ~(ETH_MACPFR_HMC | ETH_MACPFR_HPF);
stm32_putreg(temp, STM32_ETH_MACPFR);
}
return OK;
}
#endif
/****************************************************************************
* Function: stm32_txdescinit
*
* Description:
* Initializes the DMA TX descriptors in chain mode.
*
* Parameters:
* priv - Reference to the driver state structure
* txtable - List of pre-allocated TX descriptors for the Ethernet
* interface
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static void stm32_txdescinit(struct stm32_ethmac_s *priv,
union stm32_desc_u *txtable)
{
struct eth_desc_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 = &txtable[0].desc;
priv->txchbase = &txtable[0].desc;
/* 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_STM32H7_ETH_NTXDESC; i++)
{
txdesc = &txtable[i].desc;
#ifdef CHECKSUM_BY_HARDWARE
#if 0
/* Enable the checksum insertion for the TX frames TODO! */
txdesc->des0 |= ETH_TDES0_CIC_ALL;
#endif
#endif
/* Clear Buffer1 address pointer (buffers will be assigned as they
* are used)
*/
txdesc->des0 = 0;
/* Clear the rest of the descriptor as well */
txdesc->des1 = 0;
txdesc->des2 = 0;
txdesc->des3 = 0;
}
/* Flush all of the initialized TX descriptors to physical memory */
up_clean_dcache((uintptr_t)txtable,
(uintptr_t)txtable +
TXTABLE_SIZE * sizeof(union stm32_desc_u));
/* Set Channel Tx descriptor ring length register
* TODO: Why -1 is needed? Without this the ring doesn't wrap around
* properly but the DMACCATXDR advances to outside the descriptor ring
*/
stm32_putreg(CONFIG_STM32H7_ETH_NTXDESC - 1, STM32_ETH_DMACTXRLR);
/* Set Transmit Descriptor List Address Register */
stm32_putreg((uint32_t)&txtable[0].desc, STM32_ETH_DMACTXDLAR);
/* Set Transmit Descriptor Tail pointer */
stm32_putreg((uint32_t)&txtable[0].desc, STM32_ETH_DMACTXDTPR);
}
/****************************************************************************
* Function: stm32_rxdescinit
*
* Description:
* Initializes the DMA RX descriptors in chain mode.
*
* Parameters:
* priv - Reference to the driver state structure
* rxtable - List of pre-allocated RX descriptors for the Ethernet
* interface
* txbuffer - List of pre-allocated DMA buffers for the Ethernet interface
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static void stm32_rxdescinit(struct stm32_ethmac_s *priv,
union stm32_desc_u *rxtable,
uint8_t *rxbuffer)
{
struct eth_desc_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 = &rxtable[0].desc;
priv->rxchbase = &rxtable[0].desc;
/* 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 RX descriptor */
for (i = 0; i < CONFIG_STM32H7_ETH_NRXDESC; i++)
{
rxdesc = &rxtable[i].desc;
/* Set Buffer1 address pointer */
rxdesc->des0 = (uint32_t)&rxbuffer[i * ALIGNED_BUFSIZE];
/* Set Buffer1 address high bytes */
rxdesc->des1 = 0;
/* Set Buffer2 address high bytes */
rxdesc->des2 = 0;
/* Set Own bit of the RX descriptor des3 and buffer address valid */
rxdesc->des3 = ETH_RDES3_RD_OWN | ETH_RDES3_RD_IOC |
ETH_RDES3_RD_BUF1V;
}
/* Flush all of the initialized RX descriptors to physical memory */
up_clean_dcache((uintptr_t)rxtable,
(uintptr_t)rxtable +
RXTABLE_SIZE * sizeof(union stm32_desc_u));
/* Set Receive Descriptor ring length register
* TODO: Why -1 is needed? Without this the ring doesn't wrap around
* properly but the DMACCARXDR advances to outside the descriptor ring
*/
stm32_putreg(CONFIG_STM32H7_ETH_NRXDESC - 1, STM32_ETH_DMACRXRLR);
/* Set Receive Descriptor List Address Register */
stm32_putreg((uint32_t)&rxtable[0].desc, STM32_ETH_DMACRXDLAR);
/* Set Receive Descriptor Tail pointer Address */
stm32_putreg((uint32_t)&rxtable[CONFIG_STM32H7_ETH_NRXDESC - 1].desc,
STM32_ETH_DMACRXDTPR);
}
/****************************************************************************
* 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, unsigned long arg)
{
#ifdef CONFIG_ARCH_PHY_INTERRUPT
struct stm32_ethmac_s *priv = (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_STM32H7_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,
0xffff);
}
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:
*
****************************************************************************/
static int stm32_phyread(uint16_t phydevaddr, uint16_t phyregaddr,
uint16_t *value)
{
volatile uint32_t timeout;
uint32_t regval;
/* Configure the MACMDIOAR register, preserving CSR Clock Range CR[3:0]
* bits
*/
regval = stm32_getreg(STM32_ETH_MACMDIOAR);
regval &= ETH_MACMDIOAR_CR_MASK;
/* Set the PHY device address, PHY register address, and set the buy bit.
* the ETH_MACMDIOAR_GOC == 3, indicating a read operation.
*/
regval |= (((uint32_t)phydevaddr << ETH_MACMDIOAR_PA_SHIFT) &
ETH_MACMDIOAR_PA_MASK);
regval |= (((uint32_t)phyregaddr << ETH_MACMDIOAR_RDA_SHIFT) &
ETH_MACMDIOAR_RDA_MASK);
regval |= ETH_MACMDIOAR_MB | ETH_MACMDIOAR_GOC_READ;
stm32_putreg(regval, STM32_ETH_MACMDIOAR);
/* Wait for the transfer to complete */
for (timeout = 0; timeout < PHY_READ_TIMEOUT; timeout++)
{
if ((stm32_getreg(STM32_ETH_MACMDIOAR) & ETH_MACMDIOAR_MB) == 0)
{
*value = (uint16_t)stm32_getreg(STM32_ETH_MACMDIODR);
return OK;
}
}
ninfo("MII transfer timed out: phydevaddr: %04x phyregaddr: %04x\n",
phydevaddr, phyregaddr);
return -ETIMEDOUT;
}
/****************************************************************************
* 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 set, uint16_t clear)
{
volatile uint32_t timeout;
uint32_t regval;
uint16_t value;
/* Configure the MACMDIOAR register, preserving CSR Clock Range CR[3:0]
* bits
*/
regval = stm32_getreg(STM32_ETH_MACMDIOAR);
regval &= ETH_MACMDIOAR_CR_MASK;
/* Read the existing register value, if clear mask is given */
if (clear != 0xffff)
{
if (stm32_phyread(phydevaddr, phyregaddr, &value) != OK)
{
return -ETIMEDOUT;
}
value &= ~clear;
value |= set;
}
else
{
value = set;
}
/* Set the PHY device address, PHY register address, and set the busy bit.
* the ETH_MACMDIOAR_GOC == 1, indicating a write operation.
*/
regval |= (((uint32_t)phydevaddr << ETH_MACMDIOAR_PA_SHIFT) &
ETH_MACMDIOAR_PA_MASK);
regval |= (((uint32_t)phyregaddr << ETH_MACMDIOAR_RDA_SHIFT) &
ETH_MACMDIOAR_RDA_MASK);
regval |= (ETH_MACMDIOAR_MB | ETH_MACMDIOAR_GOC_WRITE);
/* Write the value into the MACMDIODR register before setting the new
* MACMDIOAR register value.
*/
stm32_putreg(value, STM32_ETH_MACMDIODR);
stm32_putreg(regval, STM32_ETH_MACMDIOAR);
/* Wait for the transfer to complete */
for (timeout = 0; timeout < PHY_WRITE_TIMEOUT; timeout++)
{
if ((stm32_getreg(STM32_ETH_MACMDIOAR) & ETH_MACMDIOAR_MB) == 0)
{
return OK;
}
}
ninfo("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(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_STM32H7_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_STM32H7_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_phyregdump
*
* Description:
* Dump the MII registers 0-31
*
* Parameters:
*
* Returned Value:
*
* Assumptions:
*
****************************************************************************/
#ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG
static void stm32_phyregdump()
{
uint16_t phyval;
int ret;
int i;
for (i = 0; i < 0x20; i++)
{
ret = stm32_phyread(CONFIG_STM32H7_PHYADDR, i, &phyval);
if (ret < 0)
{
nerr("ERROR: Failed to read reg: 0%2x\n", i);
}
else
{
ninfo("Phy reg 0x%02x == 0x%x\n", i, phyval);
}
}
}
#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(struct stm32_ethmac_s *priv)
{
#ifdef CONFIG_STM32H7_AUTONEG
volatile uint32_t timeout;
#endif
uint32_t regval;
uint16_t phyval;
int ret;
int to;
/* Assume 10MBps and half duplex */
priv->mbps100 = 0;
priv->fduplex = 0;
/* Setup up PHY clocking by setting the CR field in the MACMDIOAR reg */
regval = stm32_getreg(STM32_ETH_MACMDIOAR);
regval &= ~ETH_MACMDIOAR_CR_MASK;
regval |= ETH_MACMDIOAR_CR;
stm32_putreg(regval, STM32_ETH_MACMDIOAR);
/* Put the PHY in reset mode */
ret = stm32_phywrite(CONFIG_STM32H7_PHYADDR, MII_MCR, MII_MCR_RESET,
MII_MCR_RESET);
if (ret < 0)
{
nerr("ERROR: Failed to reset the PHY: %d\n", ret);
return ret;
}
to = PHY_RESET_DELAY;
do
{
up_mdelay(10);
to -= 10;
ret = stm32_phyread(CONFIG_STM32H7_PHYADDR, MII_MCR, &phyval);
}
while (phyval & MII_MCR_RESET && to > 0);
if (to <= 0)
{
nerr("ERROR: Phy reset timeout\n");
return ret;
}
else
{
ninfo("Phy reset in %d ms\n", PHY_RESET_DELAY - to);
}
#ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG
stm32_phyregdump();
#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-negotiation if so configured */
#ifdef CONFIG_STM32H7_AUTONEG
/* Wait for link status */
for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++)
{
ret = stm32_phyread(CONFIG_STM32H7_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;
}
nxsig_usleep(100);
}
if (timeout >= PHY_RETRY_TIMEOUT)
{
nerr("ERROR: Timed out waiting for link status: %04x\n", phyval);
return -ETIMEDOUT;
}
/* Enable auto-negotiation */
ret = stm32_phywrite(CONFIG_STM32H7_PHYADDR, MII_MCR, MII_MCR_ANENABLE,
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_STM32H7_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;
}
nxsig_usleep(100);
}
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_STM32H7_PHYADDR, CONFIG_STM32H7_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_STM32H7_PHYSR, phyval);
/* Different PHYs present speed and mode information in different ways. IF
* This CONFIG_STM32H7_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_STM32H7_PHYSR_ALTCONFIG
switch (phyval & CONFIG_STM32H7_PHYSR_ALTMODE)
{
default:
nerr("ERROR: Unrecognized PHY status setting\n");
/* Falls through */
case CONFIG_STM32H7_PHYSR_10HD:
priv->fduplex = 0;
priv->mbps100 = 0;
break;
case CONFIG_STM32H7_PHYSR_100HD:
priv->fduplex = 0;
priv->mbps100 = 1;
break;
case CONFIG_STM32H7_PHYSR_10FD:
priv->fduplex = 1;
priv->mbps100 = 0;
break;
case CONFIG_STM32H7_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_STM32H7_PHYSR_MODE) ==
CONFIG_STM32H7_PHYSR_FULLDUPLEX)
{
priv->fduplex = 1;
}
if ((phyval & CONFIG_STM32H7_PHYSR_SPEED) == CONFIG_STM32H7_PHYSR_100MBPS)
{
priv->mbps100 = 1;
}
#endif
#else /* Auto-negotiation not selected */
phyval = 0;
#ifdef CONFIG_STM32H7_ETHFD
phyval |= MII_MCR_FULLDPLX;
#endif
#ifdef CONFIG_STM32H7_ETH100MBPS
phyval |= MII_MCR_SPEED100;
#endif
ret = stm32_phywrite(CONFIG_STM32H7_PHYADDR, MII_MCR, phyval, 0xffff);
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_STM32H7_ETHFD
priv->fduplex = 1;
#endif
#ifdef CONFIG_STM32H7_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 interface.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_STM32H7_MII
static inline void stm32_selectmii(void)
{
uint32_t regval;
regval = getreg32(STM32_SYSCFG_PMC);
regval &= ~SYSCFG_PMC_EPIS_MASK;
regval |= SYSCFG_PMC_EPIS_MII;
putreg32(regval, STM32_SYSCFG_PMC);
}
#endif
/****************************************************************************
* Name: stm32_selectrmii
*
* Description:
* Selects the RMII interface.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_STM32H7_RMII
static inline void stm32_selectrmii(void)
{
uint32_t regval;
regval = getreg32(STM32_SYSCFG_PMC);
regval &= ~SYSCFG_PMC_EPIS_MASK;
regval |= SYSCFG_PMC_EPIS_RMII;
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(struct stm32_ethmac_s *priv)
{
/* Configure GPIO pins to support Ethernet */
#if defined(CONFIG_STM32H7_MII) || defined(CONFIG_STM32H7_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_STM32H7_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_STM32H7_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_STM32H7_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_STM32H7_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_STM32H7_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_STM32H7_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_STM32H7_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_STM32H7_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_STM32H7_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(struct stm32_ethmac_s *priv)
{
uint32_t regval;
volatile uint32_t timeout;
/* Reset the Ethernet on the AHB1 bus */
regval = stm32_getreg(STM32_RCC_AHB1RSTR);
regval |= RCC_AHB1RSTR_ETH1MACRST;
stm32_putreg(regval, STM32_RCC_AHB1RSTR);
regval &= ~RCC_AHB1RSTR_ETH1MACRST;
stm32_putreg(regval, STM32_RCC_AHB1RSTR);
/* Perform a software reset by setting the SR bit in the DMAMR 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_DMAMR);
regval |= ETH_DMAMR_SWR;
stm32_putreg(regval, STM32_ETH_DMAMR);
/* 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.
*/
timeout = MAC_READY_USTIMEOUT;
while (timeout-- && (stm32_getreg(STM32_ETH_DMAMR) & ETH_DMAMR_SWR) != 0)
{
up_udelay(1);
}
/* According to the spec, these need to be done before creating
* the descriptor lists, so initialize these already here
*/
/* Set up the DMAMR register */
regval = stm32_getreg(STM32_ETH_DMAMR);
regval &= ~DMAMR_CLEAR_MASK;
regval |= DMAMR_SET_MASK;
stm32_putreg(regval, STM32_ETH_DMAMR);
/* Set up the DMASBMR register */
regval = stm32_getreg(STM32_ETH_DMASBMR);
regval &= ~DMASBMR_CLEAR_MASK;
regval |= DMASBMR_SET_MASK;
stm32_putreg(regval, STM32_ETH_DMASBMR);
}
/****************************************************************************
* 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(struct stm32_ethmac_s *priv)
{
uint32_t regval;
/* DMA Configuration */
/* Set up the ETH_DMACCR register */
regval = stm32_getreg(STM32_ETH_DMACCR);
regval &= ~DMACCR_CLEAR_MASK;
regval |= DMACCR_SET_MASK;
stm32_putreg(regval, STM32_ETH_DMACCR);
/* Set up the ETH_DMACTXCR register */
regval = stm32_getreg(STM32_ETH_DMACTXCR);
regval &= ~DMACTXCR_CLEAR_MASK;
regval |= DMACTXCR_SET_MASK;
stm32_putreg(regval, STM32_ETH_DMACTXCR);
/* Set up the ETH_DMACRXCR register */
regval = stm32_getreg(STM32_ETH_DMACRXCR);
regval &= ~DMACRXCR_CLEAR_MASK;
regval |= DMACRXCR_SET_MASK;
stm32_putreg(regval, STM32_ETH_DMACRXCR);
/* MTL configuration */
/* Set up the MTLTXQOMR register */
regval = stm32_getreg(STM32_ETH_MTLTXQOMR);
regval &= ~MTLTXQOMR_CLEAR_MASK;
regval |= MTLTXQOMR_SET_MASK;
stm32_putreg(regval, STM32_ETH_MTLTXQOMR);
/* Set up the MTLRXQOMR register */
regval = stm32_getreg(STM32_ETH_MTLRXQOMR);
regval &= ~MTLRXQOMR_CLEAR_MASK;
regval |= MTLRXQOMR_SET_MASK;
stm32_putreg(regval, STM32_ETH_MTLRXQOMR);
/* MAC Configuration */
/* 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 MACPFR register */
regval = stm32_getreg(STM32_ETH_MACPFR);
regval &= ~MACPFR_CLEAR_BITS;
regval |= MACPFR_SET_BITS;
stm32_putreg(regval, STM32_ETH_MACPFR);
/* Set up the MACHT0R and MACHT1R registers */
stm32_putreg(0, STM32_ETH_MACHT0R);
stm32_putreg(0, STM32_ETH_MACHT1R);
/* Setup up the MACQTXFCR register */
regval = stm32_getreg(STM32_ETH_MACQTXFCR);
regval &= ~MACQTXFCR_CLEAR_MASK;
regval |= MACQTXFCR_SET_MASK;
stm32_putreg(regval, STM32_ETH_MACQTXFCR);
/* Setup up the MACRXFCR register */
regval = stm32_getreg(STM32_ETH_MACRXFCR);
regval &= ~MACRXFCR_CLEAR_MASK;
regval |= MACRXFCR_SET_MASK;
stm32_putreg(regval, STM32_ETH_MACRXFCR);
/* Setup up the MACVTR register */
stm32_putreg(0, STM32_ETH_MACVTR);
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(struct stm32_ethmac_s *priv)
{
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.ether_addr_octet[0],
dev->d_mac.ether.ether_addr_octet[1],
dev->d_mac.ether.ether_addr_octet[2],
dev->d_mac.ether.ether_addr_octet[3],
dev->d_mac.ether.ether_addr_octet[4],
dev->d_mac.ether.ether_addr_octet[5]);
/* Set the MAC address high register */
regval = ((uint32_t)dev->d_mac.ether.ether_addr_octet[5] << 8) |
(uint32_t)dev->d_mac.ether.ether_addr_octet[4];
stm32_putreg(regval, STM32_ETH_MACA0HR);
/* Set the MAC address low register */
regval = ((uint32_t)dev->d_mac.ether.ether_addr_octet[3] << 24) |
((uint32_t)dev->d_mac.ether.ether_addr_octet[2] << 16) |
((uint32_t)dev->d_mac.ether.ether_addr_octet[1] << 8) |
(uint32_t)dev->d_mac.ether.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(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]);
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.
*/
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.
*/
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(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_MTLTXQOMR);
regval |= ETH_MTLTXQOMR_FTQ;
stm32_putreg(regval, STM32_ETH_MTLTXQOMR);
/* 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_DMACTXCR);
regval |= ETH_DMACTXCR_ST;
stm32_putreg(regval, STM32_ETH_DMACTXCR);
/* Start DMA reception */
regval = stm32_getreg(STM32_ETH_DMACRXCR);
regval |= ETH_DMACRXCR_SR;
stm32_putreg(regval, STM32_ETH_DMACRXCR);
/* Enable Ethernet DMA interrupts */
stm32_putreg(ETH_MACIER_ALLINTS, STM32_ETH_MACIER);
/* 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_XMIT_ENABLE |
ETH_DMAINT_ERROR_ENABLE), STM32_ETH_DMACIER);
/* Clear Tx and Rx process stopped flags */
regval = stm32_getreg(STM32_ETH_DMACSR);
regval |= (ETH_DMACSR_TPS | ETH_DMACSR_RPS);
stm32_putreg(regval, STM32_ETH_DMACSR);
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(struct stm32_ethmac_s *priv)
{
int ret;
/* NOTE: The Ethernet clocks were initialized early in the boot-up
* sequence in stm32_rcc.c.
*/
#ifdef CONFIG_STM32H7_PHYINIT
/* Perform any necessary, board-specific PHY initialization */
ret = stm32_phy_boardinitialize(0);
if (ret < 0)
{
nerr("ERROR: Failed to initialize the PHY: %d\n", ret);
return ret;
}
#endif
/* Initialize the free buffer list */
stm32_initbuffer(priv, &g_txbuffer[priv->intf * TXBUFFER_SIZE]);
/* Reset the Ethernet block */
ninfo("Reset the Ethernet block\n");
stm32_ethreset(priv);
/* Initialize TX Descriptors list */
stm32_txdescinit(priv,
&g_txtable[priv->intf * CONFIG_STM32H7_ETH_NTXDESC]);
/* Initialize RX Descriptors list */
stm32_rxdescinit(priv,
&g_rxtable[priv->intf * CONFIG_STM32H7_ETH_NRXDESC],
&g_rxbuffer[priv->intf * RXBUFFER_SIZE]);
/* 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;
}
/* 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 arm_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 STM32H7_NETHERNET > 1 || defined(CONFIG_NETDEV_LATEINIT)
int stm32_ethinitialize(int intf)
#else
static inline int stm32_ethinitialize(int intf)
#endif
{
struct stm32_ethmac_s *priv;
uint8_t uid[12];
uint64_t crc;
int ret = OK;
ninfo("intf: %d\n", intf);
/* Get the interface structure associated with this interface number. */
DEBUGASSERT(intf < STM32H7_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 = g_stm32ethmac; /* Used to recover private state */
priv->intf = intf; /* Remember the interface number */
stm32_get_uniqueid(uid);
crc = crc64(uid, 12);
/* Specify as localy administrated address */
priv->dev.d_mac.ether.ether_addr_octet[0] = (crc >> 0) | 0x02;
priv->dev.d_mac.ether.ether_addr_octet[0] &= ~0x1;
priv->dev.d_mac.ether.ether_addr_octet[1] = crc >> 8;
priv->dev.d_mac.ether.ether_addr_octet[2] = crc >> 16;
priv->dev.d_mac.ether.ether_addr_octet[3] = crc >> 24;
priv->dev.d_mac.ether.ether_addr_octet[4] = crc >> 32;
priv->dev.d_mac.ether.ether_addr_octet[5] = crc >> 40;
/* Configure GPIO pins to support Ethernet */
stm32_ethgpioconfig(priv);
/* Attach the IRQ to the driver */
if (irq_attach(STM32_IRQ_ETH, stm32_interrupt, NULL))
{
/* We could not attach the ISR to the interrupt */
return -EAGAIN;
}
#ifdef CONFIG_STM32H7_PHYINIT
/* Perform any necessary, board-specific PHY initialization */
ret = stm32_phy_boardinitialize(0);
if (ret < 0)
{
nerr("ERROR: Failed to initialize the PHY: %d\n", ret);
return ret;
}
#endif
/* Put the interface in the down state. */
stm32_ifdown(&priv->dev);
/* Register the device with the OS so that socket IOCTLs can be performed */
netdev_register(&priv->dev, NET_LL_ETHERNET);
return ret;
}
/****************************************************************************
* Function: arm_netinitialize
*
* Description:
* This is the "standard" network initialization logic called from the
* low-level initialization logic in arm_initialize.c. If STM32H7_NETHERNET
* greater than one, then board specific logic will have to supply a
* version of arm_netinitialize() that calls stm32_ethinitialize() with
* the appropriate interface number.
*
* Parameters:
* None.
*
* Returned Value:
* None.
*
* Assumptions:
*
****************************************************************************/
#if STM32H7_NETHERNET == 1 && !defined(CONFIG_NETDEV_LATEINIT)
void arm_netinitialize(void)
{
stm32_ethinitialize(0);
}
#endif
#endif /* STM32H7_NETHERNET > 0 && CONFIG_STM32H7_ETHMAC */