| /**************************************************************************** |
| * arch/arm/src/stm32/stm32_eth.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> |
| #if defined(CONFIG_NET) && defined(CONFIG_STM32_ETHMAC) |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <time.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <assert.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/net/phy.h> |
| #include <nuttx/net/mii.h> |
| #include <nuttx/net/ip.h> |
| #include <nuttx/net/netdev.h> |
| |
| #if defined(CONFIG_NET_PKT) |
| # include <nuttx/net/pkt.h> |
| #endif |
| |
| #include "arm_internal.h" |
| #include "chip.h" |
| #include "stm32_gpio.h" |
| #include "stm32_rcc.h" |
| #include "stm32_syscfg.h" |
| #include "stm32_eth.h" |
| |
| #include <arch/board/board.h> |
| |
| /* STM32_NETHERNET determines the number of physical interfaces |
| * that will be supported. |
| */ |
| |
| #if STM32_NETHERNET > 0 |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| /* See boards/arm/stm32/stm3240g-eval/README.txt for an explanation of the |
| * configuration settings. |
| */ |
| |
| #if STM32_NETHERNET > 1 |
| # error "Logic to support multiple Ethernet interfaces is incomplete" |
| #endif |
| |
| /* Work queue support is required. */ |
| |
| #if !defined(CONFIG_SCHED_WORKQUEUE) |
| # error Work queue support is required |
| #endif |
| |
| /* The low priority work queue is preferred. If it is not enabled, LPWORK |
| * will be the same as HPWORK. |
| * |
| * NOTE: However, the network should NEVER run on the high priority work |
| * queue! That queue is intended only to service short back end interrupt |
| * processing that never suspends. Suspending the high priority work queue |
| * may bring the system to its knees! |
| */ |
| |
| #define ETHWORK LPWORK |
| |
| #if !defined(CONFIG_STM32_SYSCFG) && !defined(CONFIG_STM32_CONNECTIVITYLINE) |
| # error "CONFIG_STM32_SYSCFG must be defined in the NuttX configuration" |
| #endif |
| |
| #ifndef CONFIG_STM32_PHYADDR |
| # error "CONFIG_STM32_PHYADDR must be defined in the NuttX configuration" |
| #endif |
| |
| #if !defined(CONFIG_STM32_MII) && !defined(CONFIG_STM32_RMII) |
| # warning "Neither CONFIG_STM32_MII nor CONFIG_STM32_RMII defined" |
| #endif |
| |
| #if defined(CONFIG_STM32_MII) && defined(CONFIG_STM32_RMII) |
| # error "Both CONFIG_STM32_MII and CONFIG_STM32_RMII defined" |
| #endif |
| |
| #ifdef CONFIG_STM32_MII |
| # if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F4XXX) |
| # if !defined(CONFIG_STM32_MII_MCO1) && !defined(CONFIG_STM32_MII_MCO2) && !defined(CONFIG_STM32_MII_EXTCLK) |
| # warning "Neither CONFIG_STM32_MII_MCO1, CONFIG_STM32_MII_MCO2, nor CONFIG_STM32_MII_EXTCLK defined" |
| # endif |
| # if defined(CONFIG_STM32_MII_MCO1) && defined(CONFIG_STM32_MII_MCO2) |
| # error "Both CONFIG_STM32_MII_MCO1 and CONFIG_STM32_MII_MCO2 defined" |
| # endif |
| # elif defined(CONFIG_STM32_CONNECTIVITYLINE) |
| # if !defined(CONFIG_STM32_MII_MCO) && !defined(CONFIG_STM32_MII_EXTCLK) |
| # warning "Neither CONFIG_STM32_MII_MCO nor CONFIG_STM32_MII_EXTCLK defined" |
| # endif |
| # endif |
| #endif |
| |
| #ifdef CONFIG_STM32_RMII |
| # if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F4XXX) |
| # if !defined(CONFIG_STM32_RMII_MCO1) && !defined(CONFIG_STM32_RMII_MCO2) && !defined(CONFIG_STM32_RMII_EXTCLK) |
| # warning "Neither CONFIG_STM32_RMII_MCO1, CONFIG_STM32_RMII_MCO2, nor CONFIG_STM32_RMII_EXTCLK defined" |
| # endif |
| # if defined(CONFIG_STM32_RMII_MCO1) && defined(CONFIG_STM32_RMII_MCO2) |
| # error "Both CONFIG_STM32_RMII_MCO1 and CONFIG_STM32_RMII_MCO2 defined" |
| # endif |
| # elif defined(CONFIG_STM32_CONNECTIVITYLINE) |
| # if !defined(CONFIG_STM32_RMII_MCO) && !defined(CONFIG_STM32_RMII_EXTCLK) |
| # warning "Neither CONFIG_STM32_RMII_MCO nor CONFIG_STM32_RMII_EXTCLK defined" |
| # endif |
| # endif |
| #endif |
| |
| #ifdef CONFIG_STM32_AUTONEG |
| # ifndef CONFIG_STM32_PHYSR |
| # error "CONFIG_STM32_PHYSR must be defined in the NuttX configuration" |
| # endif |
| # ifdef CONFIG_STM32_PHYSR_ALTCONFIG |
| # ifndef CONFIG_STM32_PHYSR_ALTMODE |
| # error "CONFIG_STM32_PHYSR_ALTMODE must be defined in the NuttX configuration" |
| # endif |
| # ifndef CONFIG_STM32_PHYSR_10HD |
| # error "CONFIG_STM32_PHYSR_10HD must be defined in the NuttX configuration" |
| # endif |
| # ifndef CONFIG_STM32_PHYSR_100HD |
| # error "CONFIG_STM32_PHYSR_100HD must be defined in the NuttX configuration" |
| # endif |
| # ifndef CONFIG_STM32_PHYSR_10FD |
| # error "CONFIG_STM32_PHYSR_10FD must be defined in the NuttX configuration" |
| # endif |
| # ifndef CONFIG_STM32_PHYSR_100FD |
| # error "CONFIG_STM32_PHYSR_100FD must be defined in the NuttX configuration" |
| # endif |
| # else |
| # ifndef CONFIG_STM32_PHYSR_SPEED |
| # error "CONFIG_STM32_PHYSR_SPEED must be defined in the NuttX configuration" |
| # endif |
| # ifndef CONFIG_STM32_PHYSR_100MBPS |
| # error "CONFIG_STM32_PHYSR_100MBPS must be defined in the NuttX configuration" |
| # endif |
| # ifndef CONFIG_STM32_PHYSR_MODE |
| # error "CONFIG_STM32_PHYSR_MODE must be defined in the NuttX configuration" |
| # endif |
| # ifndef CONFIG_STM32_PHYSR_FULLDUPLEX |
| # error "CONFIG_STM32_PHYSR_FULLDUPLEX must be defined in the NuttX configuration" |
| # endif |
| # endif |
| #endif |
| |
| /* These definitions are used to enable the PHY interrupts */ |
| |
| #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) |
| # if defined( CONFIG_ETH0_PHY_AM79C874) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_KS8721) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_KSZ8041) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_KSZ8051) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_KSZ8061) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_KSZ8081) |
| # define MII_INT_REG MII_KSZ8081_INT |
| # define MII_INT_SETEN MII_KSZ80X1_INT_LDEN | MII_KSZ80X1_INT_LUEN |
| # define MII_INT_CLREN 0 |
| # elif defined( CONFIG_ETH0_PHY_KSZ90x1) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_DP83848C) |
| # define MII_INT_REG MII_DP83848C_MISR |
| # define MII_INT_SETEN MII_DP83848C_LINK_INT_EN |
| # define MII_INT_CLREN 0 |
| # elif defined( CONFIG_ETH0_PHY_LAN8720) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_LAN8740) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_LAN8740A) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_LAN8742A) |
| # error missing logic |
| # elif defined( CONFIG_ETH0_PHY_DM9161) |
| # error missing logic |
| # else |
| # error unknown PHY |
| # endif |
| #endif |
| |
| /* This driver does not use IPv4 checksum offloading. */ |
| |
| #undef CONFIG_STM32_ETH_HWCHECKSUM |
| |
| /* Add 4 to the configured buffer size to account for the 2 byte checksum |
| * memory needed at the end of the maximum size packet. Buffer sizes must |
| * be an even multiple of 4, 8, or 16 bytes (depending on buswidth). We |
| * will use the 16-byte alignment in all cases. |
| */ |
| |
| #define OPTIMAL_ETH_BUFSIZE ((CONFIG_NET_ETH_PKTSIZE + 4 + 15) & ~15) |
| |
| #ifndef CONFIG_STM32_ETH_BUFSIZE |
| # define CONFIG_STM32_ETH_BUFSIZE OPTIMAL_ETH_BUFSIZE |
| #endif |
| |
| #if CONFIG_STM32_ETH_BUFSIZE > ETH_TDES1_TBS1_MASK |
| # error "CONFIG_STM32_ETH_BUFSIZE is too large" |
| #endif |
| |
| #if (CONFIG_STM32_ETH_BUFSIZE & 15) != 0 |
| # error "CONFIG_STM32_ETH_BUFSIZE must be aligned" |
| #endif |
| |
| #if CONFIG_STM32_ETH_BUFSIZE != OPTIMAL_ETH_BUFSIZE |
| # warning "You using an incomplete/untested configuration" |
| #endif |
| |
| #ifndef CONFIG_STM32_ETH_NRXDESC |
| # define CONFIG_STM32_ETH_NRXDESC 8 |
| #endif |
| #ifndef CONFIG_STM32_ETH_NTXDESC |
| # define CONFIG_STM32_ETH_NTXDESC 4 |
| #endif |
| |
| /* We need at least one more free buffer than transmit buffers */ |
| |
| #define STM32_ETH_NFREEBUFFERS (CONFIG_STM32_ETH_NTXDESC+1) |
| |
| /* Extremely detailed register debug that you would normally never want |
| * enabled. |
| */ |
| |
| #ifndef CONFIG_DEBUG_NET_INFO |
| # undef CONFIG_STM32_ETHMAC_REGDEBUG |
| #endif |
| |
| /* Clocking *****************************************************************/ |
| |
| /* Set MACMIIAR CR bits depending on HCLK setting */ |
| |
| #if STM32_HCLK_FREQUENCY >= 20000000 && STM32_HCLK_FREQUENCY < 35000000 |
| # define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_20_35 |
| #elif STM32_HCLK_FREQUENCY >= 35000000 && STM32_HCLK_FREQUENCY < 60000000 |
| # define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_35_60 |
| #elif STM32_HCLK_FREQUENCY >= 60000000 && STM32_HCLK_FREQUENCY < 100000000 |
| # define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_60_100 |
| #elif STM32_HCLK_FREQUENCY >= 100000000 && STM32_HCLK_FREQUENCY < 150000000 |
| # define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_100_150 |
| #elif STM32_HCLK_FREQUENCY >= 150000000 && STM32_HCLK_FREQUENCY <= 180000000 |
| # define ETH_MACMIIAR_CR ETH_MACMIIAR_CR_150_180 |
| #else |
| # error "STM32_HCLK_FREQUENCY not supportable" |
| #endif |
| |
| /* Timing *******************************************************************/ |
| |
| /* TX timeout = 1 minute */ |
| |
| #define STM32_TXTIMEOUT (60*CLK_TCK) |
| |
| /* PHY reset/configuration delays in milliseconds */ |
| |
| #define PHY_RESET_DELAY (65) |
| #define PHY_CONFIG_DELAY (1000) |
| |
| /* PHY read/write delays in loop counts */ |
| |
| #define PHY_READ_TIMEOUT (0x0004ffff) |
| #define PHY_WRITE_TIMEOUT (0x0004ffff) |
| #define PHY_RETRY_TIMEOUT (0x0004ffff) |
| |
| /* Register values **********************************************************/ |
| |
| /* Clear the MACCR bits that will be setup during MAC initialization (or that |
| * are cleared unconditionally). Per the reference manual, all reserved bits |
| * must be retained at their reset value. |
| * |
| * ETH_MACCR_RE Bit 2: Receiver enable |
| * ETH_MACCR_TE Bit 3: Transmitter enable |
| * ETH_MACCR_DC Bit 4: Deferral check |
| * ETH_MACCR_BL Bits 5-6: Back-off limit |
| * ETH_MACCR_APCS Bit 7: Automatic pad/CRC stripping |
| * ETH_MACCR_RD Bit 9: Retry disable |
| * ETH_MACCR_IPCO Bit 10: IPv4 checksum offload |
| * ETH_MACCR_DM Bit 11: Duplex mode |
| * ETH_MACCR_LM Bit 12: Loopback mode |
| * ETH_MACCR_ROD Bit 13: Receive own disable |
| * ETH_MACCR_FES Bit 14: Fast Ethernet speed |
| * ETH_MACCR_CSD Bit 16: Carrier sense disable |
| * ETH_MACCR_IFG Bits 17-19: Interframe gap |
| * ETH_MACCR_JD Bit 22: Jabber disable |
| * ETH_MACCR_WD Bit 23: Watchdog disable |
| * ETH_MACCR_CSTF Bits 25: CRC stripping for Type frames (F2/F4 only) |
| */ |
| |
| #if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F4XXX) |
| #define MACCR_CLEAR_BITS \ |
| (ETH_MACCR_RE | ETH_MACCR_TE | ETH_MACCR_DC | ETH_MACCR_BL_MASK | \ |
| ETH_MACCR_APCS | ETH_MACCR_RD | ETH_MACCR_IPCO | ETH_MACCR_DM | \ |
| ETH_MACCR_LM | ETH_MACCR_ROD | ETH_MACCR_FES | ETH_MACCR_CSD | \ |
| ETH_MACCR_IFG_MASK | ETH_MACCR_JD | ETH_MACCR_WD | ETH_MACCR_CSTF) |
| #else |
| #define MACCR_CLEAR_BITS \ |
| (ETH_MACCR_RE | ETH_MACCR_TE | ETH_MACCR_DC | ETH_MACCR_BL_MASK | \ |
| ETH_MACCR_APCS | ETH_MACCR_RD | ETH_MACCR_IPCO | ETH_MACCR_DM | \ |
| ETH_MACCR_LM | ETH_MACCR_ROD | ETH_MACCR_FES | ETH_MACCR_CSD | \ |
| ETH_MACCR_IFG_MASK | ETH_MACCR_JD | ETH_MACCR_WD) |
| #endif |
| |
| /* The following bits are set or left zero unconditionally in all modes. |
| * |
| * ETH_MACCR_RE Receiver enable 0 (disabled) |
| * ETH_MACCR_TE Transmitter enable 0 (disabled) |
| * ETH_MACCR_DC Deferral check 0 (disabled) |
| * ETH_MACCR_BL Back-off limit 0 (10) |
| * ETH_MACCR_APCS Automatic pad/CRC stripping 0 (disabled) |
| * ETH_MACCR_RD Retry disable 1 (disabled) |
| * ETH_MACCR_IPCO IPv4 checksum offload Depends on |
| * CONFIG_STM32_ETH_HWCHECKSUM |
| * ETH_MACCR_LM Loopback mode 0 (disabled) |
| * ETH_MACCR_ROD Receive own disable 0 (enabled) |
| * ETH_MACCR_CSD Carrier sense disable 0 (enabled) |
| * ETH_MACCR_IFG Interframe gap 0 (96 bits) |
| * ETH_MACCR_JD Jabber disable 0 (enabled) |
| * ETH_MACCR_WD Watchdog disable 0 (enabled) |
| * ETH_MACCR_CSTF CRC stripping for Type frames 0 (disabled, F2/F4 only) |
| * |
| * The following are set conditioinally based on mode and speed. |
| * |
| * ETH_MACCR_DM Duplex mode Depends on priv->fduplex |
| * ETH_MACCR_FES Fast Ethernet speed Depends on priv->mbps100 |
| */ |
| |
| #ifdef CONFIG_STM32_ETH_HWCHECKSUM |
| # define MACCR_SET_BITS \ |
| (ETH_MACCR_BL_10 | ETH_MACCR_RD | ETH_MACCR_IPCO | ETH_MACCR_IFG(96)) |
| #else |
| # define MACCR_SET_BITS \ |
| (ETH_MACCR_BL_10 | ETH_MACCR_RD | ETH_MACCR_IFG(96)) |
| #endif |
| |
| /* Clear the MACCR bits that will be setup during MAC initialization (or that |
| * are cleared unconditionally). Per the reference manual, all reserved bits |
| * must be retained at their reset value. |
| * |
| * ETH_MACFFR_PM Bit 0: Promiscuous mode |
| * ETH_MACFFR_HU Bit 1: Hash unicast |
| * ETH_MACFFR_HM Bit 2: Hash multicast |
| * ETH_MACFFR_DAIF Bit 3: Destination address inverse filtering |
| * ETH_MACFFR_PAM Bit 4: Pass all multicast |
| * ETH_MACFFR_BFD Bit 5: Broadcast frames disable |
| * ETH_MACFFR_PCF Bits 6-7: Pass control frames |
| * ETH_MACFFR_SAIF Bit 8: Source address inverse filtering |
| * ETH_MACFFR_SAF Bit 9: Source address filter |
| * ETH_MACFFR_HPF Bit 10: Hash or perfect filter |
| * ETH_MACFFR_RA Bit 31: Receive all |
| */ |
| |
| #define MACFFR_CLEAR_BITS \ |
| (ETH_MACFFR_PM | ETH_MACFFR_HU | ETH_MACFFR_HM | ETH_MACFFR_DAIF | \ |
| ETH_MACFFR_PAM | ETH_MACFFR_BFD | ETH_MACFFR_PCF_MASK | ETH_MACFFR_SAIF | \ |
| ETH_MACFFR_SAF | ETH_MACFFR_HPF | ETH_MACFFR_RA) |
| |
| /* The following bits are set or left zero unconditionally in all modes. |
| * |
| * ETH_MACFFR_HU Hash unicast 0 (perfect dest filtering) |
| * ETH_MACFFR_HM Hash multicast 0 (perfect dest filtering) |
| * ETH_MACFFR_DAIF Destination address 0 (normal) |
| * inverse filtering |
| * ETH_MACFFR_PAM Pass all multicast 0 (Depends on HM bit) |
| * ETH_MACFFR_BFD Broadcast frames disable 0 (enabled) |
| * ETH_MACFFR_PCF Pass control frames 1 (block all but PAUSE) |
| * ETH_MACFFR_SAIF Source address inverse 0 (not used) |
| * filtering |
| * ETH_MACFFR_SAF Source address filter 0 (disabled) |
| * ETH_MACFFR_HPF Hash or perfect filter 0 (Only matching frames passed) |
| * ETH_MACFFR_RA Receive all 0 (disabled) |
| */ |
| |
| #ifdef CONFIG_NET_PROMISCUOUS |
| # define MACFFR_SET_BITS (ETH_MACFFR_PCF_PAUSE | ETH_MACFFR_PM) |
| #else |
| # define MACFFR_SET_BITS (ETH_MACFFR_PCF_PAUSE) |
| #endif |
| |
| /* Clear the MACFCR bits that will be setup during MAC initialization (or |
| * that are cleared unconditionally). Per the reference manual, all reserved |
| * bits must be retained at their reset value. |
| * |
| * ETH_MACFCR_FCB_BPA Bit 0: Flow control busy/back pressure activate |
| * ETH_MACFCR_TFCE Bit 1: Transmit flow control enable |
| * ETH_MACFCR_RFCE Bit 2: Receive flow control enable |
| * ETH_MACFCR_UPFD Bit 3: Unicast pause frame detect |
| * ETH_MACFCR_PLT Bits 4-5: Pause low threshold |
| * ETH_MACFCR_ZQPD Bit 7: Zero-quanta pause disable |
| * ETH_MACFCR_PT Bits 16-31: Pause time |
| */ |
| |
| #define MACFCR_CLEAR_MASK \ |
| (ETH_MACFCR_FCB_BPA | ETH_MACFCR_TFCE | ETH_MACFCR_RFCE | ETH_MACFCR_UPFD | \ |
| ETH_MACFCR_PLT_MASK | ETH_MACFCR_ZQPD | ETH_MACFCR_PT_MASK) |
| |
| /* The following bits are set or left zero unconditionally in all modes. |
| * |
| * ETH_MACFCR_FCB_BPA Flow control busy/back 0 (no pause control frame) |
| * activate pressure |
| * ETH_MACFCR_TFCE Transmit flow control enable 0 (disabled) |
| * ETH_MACFCR_RFCE Receive flow control enable 0 (disabled) |
| * ETH_MACFCR_UPFD Unicast pause frame detect 0 (disabled) |
| * ETH_MACFCR_PLT Pause low threshold 0 (pause time - 4) |
| * ETH_MACFCR_ZQPD Zero-quanta pause disable 1 (disabled) |
| * ETH_MACFCR_PT Pause time 0 |
| */ |
| |
| #define MACFCR_SET_MASK (ETH_MACFCR_PLT_M4 | ETH_MACFCR_ZQPD) |
| |
| /* Clear the DMAOMR bits that will be setup during MAC initialization (or |
| * that are cleared unconditionally). Per the reference manual, all reserved |
| * bits must be retained at their reset value. |
| * |
| * ETH_DMAOMR_SR Bit 1: Start/stop receive |
| * TH_DMAOMR_OSF Bit 2: Operate on second frame |
| * ETH_DMAOMR_RTC Bits 3-4: Receive threshold control |
| * ETH_DMAOMR_FUGF Bit 6: Forward undersized good frames |
| * ETH_DMAOMR_FEF Bit 7: Forward error frames |
| * ETH_DMAOMR_ST Bit 13: Start/stop transmission |
| * ETH_DMAOMR_TTC Bits 14-16: Transmit threshold control |
| * ETH_DMAOMR_FTF Bit 20: Flush transmit FIFO |
| * ETH_DMAOMR_TSF Bit 21: Transmit store and forward |
| * ETH_DMAOMR_DFRF Bit 24: Disable flushing of received frames |
| * ETH_DMAOMR_RSF Bit 25: Receive store and forward |
| * TH_DMAOMR_DTCEFD Bit 26: Dropping of TCP/IP checksum error frames disable |
| */ |
| |
| #define DMAOMR_CLEAR_MASK \ |
| (ETH_DMAOMR_SR | ETH_DMAOMR_OSF | ETH_DMAOMR_RTC_MASK | ETH_DMAOMR_FUGF | \ |
| ETH_DMAOMR_FEF | ETH_DMAOMR_ST | ETH_DMAOMR_TTC_MASK | ETH_DMAOMR_FTF | \ |
| ETH_DMAOMR_TSF | ETH_DMAOMR_DFRF | ETH_DMAOMR_RSF | ETH_DMAOMR_DTCEFD) |
| |
| /* The following bits are set or left zero unconditionally in all modes. |
| * |
| * ETH_DMAOMR_SR Start/stop receive 0 (not running) |
| * TH_DMAOMR_OSF Operate on second frame 1 (enabled) |
| * ETH_DMAOMR_RTC Receive threshold control 0 (64 bytes) |
| * ETH_DMAOMR_FUGF Forward undersized good 0 (disabled) |
| * frames |
| * ETH_DMAOMR_FEF Forward error frames 0 (disabled) |
| * ETH_DMAOMR_ST Start/stop transmission 0 (not running) |
| * ETH_DMAOMR_TTC Transmit threshold control 0 (64 bytes) |
| * ETH_DMAOMR_FTF Flush transmit FIFO 0 (no flush) |
| * ETH_DMAOMR_TSF Transmit store and forward 1 (enabled) |
| * ETH_DMAOMR_DFRF Disable flushing of received 0 (enabled) |
| * frames |
| * ETH_DMAOMR_RSF Receive store and forward 1 (enabled) |
| * TH_DMAOMR_DTCEFD Dropping of TCP/IP checksum Depends on |
| * error frames disable CONFIG_STM32_ETH_HWCHECKSUM |
| * |
| * When the checksum offload feature is enabled, we need to enable the Store |
| * and Forward mode: the store and forward guarantee that a whole frame is |
| * stored in the FIFO, so the MAC can insert/verify the checksum, if the |
| * checksum is OK the DMA can handle the frame otherwise the frame is dropped |
| */ |
| |
| #ifdef CONFIG_STM32_ETH_HWCHECKSUM |
| # define DMAOMR_SET_MASK \ |
| (ETH_DMAOMR_OSF | ETH_DMAOMR_RTC_64 | ETH_DMAOMR_TTC_64 | \ |
| ETH_DMAOMR_TSF | ETH_DMAOMR_RSF) |
| #else |
| # define DMAOMR_SET_MASK \ |
| (ETH_DMAOMR_OSF | ETH_DMAOMR_RTC_64 | ETH_DMAOMR_TTC_64 | \ |
| ETH_DMAOMR_TSF | ETH_DMAOMR_RSF | ETH_DMAOMR_DTCEFD) |
| #endif |
| |
| /* Clear the DMABMR bits that will be setup during MAC initialization (or |
| * that are cleared unconditionally). Per the reference manual, all reserved |
| * bits must be retained at their reset value. |
| * |
| * ETH_DMABMR_SR Bit 0: Software reset |
| * ETH_DMABMR_DA Bit 1: DMA Arbitration |
| * ETH_DMABMR_DSL Bits 2-6: Descriptor skip length |
| * ETH_DMABMR_EDFE Bit 7: Enhanced descriptor format enable |
| * ETH_DMABMR_PBL Bits 8-13: Programmable burst length |
| * ETH_DMABMR_RTPR Bits 14-15: RX TX priority ratio |
| * ETH_DMABMR_FB Bit 16: Fixed burst |
| * ETH_DMABMR_RDP Bits 17-22: RX DMA PBL |
| * ETH_DMABMR_USP Bit 23: Use separate PBL |
| * ETH_DMABMR_FPM Bit 24: 4xPBL mode |
| * ETH_DMABMR_AAB Bit 25: Address-aligned beats |
| * ETH_DMABMR_MB Bit 26: Mixed burst (F2/F4 only) |
| */ |
| |
| #if defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F4XXX) |
| #define DMABMR_CLEAR_MASK \ |
| (ETH_DMABMR_SR | ETH_DMABMR_DA | ETH_DMABMR_DSL_MASK | ETH_DMABMR_EDFE | \ |
| ETH_DMABMR_PBL_MASK | ETH_DMABMR_RTPR_MASK | ETH_DMABMR_FB | ETH_DMABMR_RDP_MASK | \ |
| ETH_DMABMR_USP | ETH_DMABMR_FPM | ETH_DMABMR_AAB | ETH_DMABMR_MB) |
| #else |
| #define DMABMR_CLEAR_MASK \ |
| (ETH_DMABMR_SR | ETH_DMABMR_DA | ETH_DMABMR_DSL_MASK | ETH_DMABMR_EDFE | \ |
| ETH_DMABMR_PBL_MASK | ETH_DMABMR_RTPR_MASK | ETH_DMABMR_FB | ETH_DMABMR_RDP_MASK | \ |
| ETH_DMABMR_USP | ETH_DMABMR_FPM | ETH_DMABMR_AAB) |
| #endif |
| |
| /* The following bits are set or left zero unconditionally in all modes. |
| * |
| * |
| * ETH_DMABMR_SR Software reset 0 (no reset) |
| * ETH_DMABMR_DA DMA Arbitration 0 (round robin) |
| * ETH_DMABMR_DSL Descriptor skip length 0 |
| * ETH_DMABMR_EDFE Enhanced descriptor format Depends on |
| * enable CONFIG_STM32_ETH_ENHANCEDDESC |
| * ETH_DMABMR_PBL Programmable burst length 32 beats |
| * ETH_DMABMR_RTPR RX TX priority ratio 2:1 |
| * ETH_DMABMR_FB Fixed burst 1 (enabled) |
| * ETH_DMABMR_RDP RX DMA PBL 32 beats |
| * ETH_DMABMR_USP Use separate PBL 1 (enabled) |
| * ETH_DMABMR_FPM 4xPBL mode 0 (disabled) |
| * ETH_DMABMR_AAB Address-aligned beats 1 (enabled) |
| * ETH_DMABMR_MB Mixed burst 0 (disabled, F2/F4 only) |
| */ |
| |
| #ifdef CONFIG_STM32_ETH_ENHANCEDDESC |
| # define DMABMR_SET_MASK \ |
| (ETH_DMABMR_DSL(0) | ETH_DMABMR_PBL(32) | ETH_DMABMR_EDFE | ETH_DMABMR_RTPR_2TO1 | \ |
| ETH_DMABMR_FB | ETH_DMABMR_RDP(32) | ETH_DMABMR_USP | ETH_DMABMR_AAB) |
| #else |
| # define DMABMR_SET_MASK \ |
| (ETH_DMABMR_DSL(0) | ETH_DMABMR_PBL(32) | ETH_DMABMR_RTPR_2TO1 | ETH_DMABMR_FB | \ |
| ETH_DMABMR_RDP(32) | ETH_DMABMR_USP | ETH_DMABMR_AAB) |
| #endif |
| |
| /* Interrupt bit sets *******************************************************/ |
| |
| /* All interrupts in the normal and abnormal interrupt summary. Early |
| * transmit interrupt (ETI) is excluded from the abnormal set because it |
| * causes too many interrupts and is not interesting. |
| */ |
| |
| #define ETH_DMAINT_NORMAL \ |
| (ETH_DMAINT_TI | ETH_DMAINT_TBUI | ETH_DMAINT_RI | ETH_DMAINT_ERI) |
| |
| #define ETH_DMAINT_ABNORMAL \ |
| (ETH_DMAINT_TPSI | ETH_DMAINT_TJTI | ETH_DMAINT_ROI | ETH_DMAINT_TUI | \ |
| ETH_DMAINT_RBUI | ETH_DMAINT_RPSI | ETH_DMAINT_RWTI | /* ETH_DMAINT_ETI | */ \ |
| ETH_DMAINT_FBEI) |
| |
| /* Normal receive, transmit, error interrupt enable bit sets */ |
| |
| #define ETH_DMAINT_RECV_ENABLE (ETH_DMAINT_NIS | ETH_DMAINT_RI) |
| #define ETH_DMAINT_XMIT_ENABLE (ETH_DMAINT_NIS | ETH_DMAINT_TI) |
| #define ETH_DMAINT_XMIT_DISABLE (ETH_DMAINT_TI) |
| |
| #define ETH_DMAINT_ERROR_ENABLE (ETH_DMAINT_AIS | ETH_DMAINT_ABNORMAL) |
| |
| /* Helpers ******************************************************************/ |
| |
| /* This is a helper pointer for accessing the contents of the Ethernet |
| * header |
| */ |
| |
| #define BUF ((struct eth_hdr_s *)priv->dev.d_buf) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* The stm32_ethmac_s encapsulates all state information for a single |
| * hardware interface |
| */ |
| |
| struct stm32_ethmac_s |
| { |
| uint8_t ifup : 1; /* true:ifup false:ifdown */ |
| uint8_t mbps100 : 1; /* 100MBps operation (vs 10 MBps) */ |
| uint8_t fduplex : 1; /* Full (vs. half) duplex */ |
| 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_txdesc_s *txhead; /* Next available TX descriptor */ |
| struct eth_rxdesc_s *rxhead; /* Next available RX descriptor */ |
| |
| struct eth_txdesc_s *txtail; /* First "in_flight" TX descriptor */ |
| struct eth_rxdesc_s *rxcurr; /* First RX descriptor of the segment */ |
| uint16_t segments; /* RX segment count */ |
| uint16_t inflight; /* Number of TX transfers "in_flight" */ |
| sq_queue_t freeb; /* The free buffer list */ |
| |
| #ifdef CONFIG_STM32_ETH_TIMESTAMP_RX |
| uint32_t rxtimelow; /* Received packet timestamp subsecond */ |
| uint32_t rxtimehigh; /* Received packet timestamp seconds */ |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Descriptor allocations */ |
| |
| static struct eth_rxdesc_s g_rxtable[CONFIG_STM32_ETH_NRXDESC] |
| aligned_data(4); |
| static struct eth_txdesc_s g_txtable[CONFIG_STM32_ETH_NTXDESC] |
| aligned_data(4); |
| |
| /* Buffer allocations */ |
| |
| static uint8_t g_rxbuffer[CONFIG_STM32_ETH_NRXDESC * |
| CONFIG_STM32_ETH_BUFSIZE] aligned_data(4); |
| static uint8_t g_alloc[STM32_ETH_NFREEBUFFERS * |
| CONFIG_STM32_ETH_BUFSIZE] aligned_data(4); |
| |
| static struct stm32_ethmac_s g_stm32ethmac[STM32_NETHERNET]; |
| |
| #ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES |
| volatile bool g_rtc_enabled; |
| static struct timespec g_stm32_eth_ptp_basetime; |
| #endif |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Register operations ******************************************************/ |
| |
| #if defined(CONFIG_STM32_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG_FEATURES) |
| static uint32_t stm32_getreg(uint32_t addr); |
| static void stm32_putreg(uint32_t val, uint32_t addr); |
| static void stm32_checksetup(void); |
| #else |
| # define stm32_getreg(addr) getreg32(addr) |
| # define stm32_putreg(val,addr) putreg32(val,addr) |
| # define stm32_checksetup() |
| #endif |
| |
| /* Free buffer management */ |
| |
| static void stm32_initbuffer(struct stm32_ethmac_s *priv, uint8_t *alloc); |
| 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_rxdesc_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_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) |
| static int stm32_addmac(struct net_driver_s *dev, const uint8_t *mac); |
| #endif |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int stm32_rmmac(struct net_driver_s *dev, const uint8_t *mac); |
| #endif |
| #ifdef CONFIG_NETDEV_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, |
| struct eth_txdesc_s *txtable); |
| static void stm32_rxdescinit(struct stm32_ethmac_s *priv, |
| struct eth_rxdesc_s *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 |
| #if defined(CONFIG_STM32_AUTONEG) || defined(CONFIG_NETDEV_PHY_IOCTL) || \ |
| defined(CONFIG_ETH0_PHY_DM9161) |
| static int stm32_phyread(uint16_t phydevaddr, uint16_t phyregaddr, |
| uint16_t *value); |
| #endif |
| static int stm32_phywrite(uint16_t phydevaddr, uint16_t phyregaddr, |
| uint16_t value); |
| #ifdef CONFIG_ETH0_PHY_DM9161 |
| static inline int stm32_dm9161(struct stm32_ethmac_s *priv); |
| #endif |
| static int stm32_phyinit(struct stm32_ethmac_s *priv); |
| |
| /* MAC/DMA Initialization */ |
| |
| #ifdef CONFIG_STM32_MII |
| static inline void stm32_selectmii(void); |
| #endif |
| #ifdef CONFIG_STM32_RMII |
| static inline void stm32_selectrmii(void); |
| #endif |
| static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv); |
| static int 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); |
| |
| /* PTP initialization and access */ |
| |
| #ifdef CONFIG_STM32_ETH_PTP |
| static int stm32_eth_ptp_adjust(long ppb); |
| static void stm32_eth_ptp_init(uint64_t timestamp); |
| static uint64_t stm32_eth_ptp_gettime(void); |
| #endif |
| |
| #ifdef CONFIG_STM32_ETH_TIMESTAMP_RX |
| static void stm32_eth_ptp_convert_rxtime(struct stm32_ethmac_s *priv); |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: stm32_getreg |
| * |
| * Description: |
| * This function may to used to intercept an monitor all register accesses. |
| * Clearly this is nothing you would want to do unless you are debugging |
| * this driver. |
| * |
| * Input Parameters: |
| * addr - The register address to read |
| * |
| * Returned Value: |
| * The value read from the register |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_STM32_ETHMAC_REGDEBUG |
| static uint32_t stm32_getreg(uint32_t addr) |
| { |
| static uint32_t prevaddr = 0; |
| static uint32_t preval = 0; |
| static uint32_t count = 0; |
| |
| /* Read the value from the register */ |
| |
| uint32_t val = getreg32(addr); |
| |
| /* Is this the same value that we read from the same register last time? |
| * Are we polling the register? If so, suppress some of the output. |
| */ |
| |
| if (addr == prevaddr && val == preval) |
| { |
| if (count == 0xffffffff || ++count > 3) |
| { |
| if (count == 4) |
| { |
| ninfo("...\n"); |
| } |
| |
| return val; |
| } |
| } |
| |
| /* No this is a new address or value */ |
| |
| else |
| { |
| /* Did we print "..." for the previous value? */ |
| |
| if (count > 3) |
| { |
| /* Yes.. then show how many times the value repeated */ |
| |
| ninfo("[repeats %d more times]\n", count - 3); |
| } |
| |
| /* Save the new address, value, and count */ |
| |
| prevaddr = addr; |
| preval = val; |
| count = 1; |
| } |
| |
| /* Show the register value read */ |
| |
| ninfo("%08x->%08x\n", addr, val); |
| return val; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: stm32_putreg |
| * |
| * Description: |
| * This function may to used to intercept an monitor all register accesses. |
| * Clearly this is nothing you would want to do unless you are debugging |
| * this driver. |
| * |
| * Input Parameters: |
| * val - The value to write to the register |
| * addr - The register address to read |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_STM32_ETHMAC_REGDEBUG) && defined(CONFIG_DEBUG_FEATURES) |
| static void stm32_putreg(uint32_t val, uint32_t addr) |
| { |
| /* Show the register value being written */ |
| |
| ninfo("%08x<-%08x\n", addr, val); |
| |
| /* Write the value */ |
| |
| putreg32(val, addr); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: stm32_checksetup |
| * |
| * Description: |
| * Show the state of critical configuration registers. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_STM32_ETHMAC_REGDEBUG |
| static void stm32_checksetup(void) |
| { |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: stm32_initbuffer |
| * |
| * Description: |
| * Initialize the free buffer list. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called during early driver initialization before Ethernet interrupts |
| * are enabled. |
| * |
| ****************************************************************************/ |
| |
| static void stm32_initbuffer(struct stm32_ethmac_s *priv, uint8_t *alloc) |
| { |
| 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 = alloc; |
| i < STM32_ETH_NFREEBUFFERS; |
| i++, buffer += CONFIG_STM32_ETH_BUFSIZE) |
| { |
| sq_addlast((sq_entry_t *)buffer, &priv->freeb); |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_allocbuffer |
| * |
| * Description: |
| * Allocate one buffer from the free buffer list. |
| * |
| * Input 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. |
| * |
| * Input 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. |
| * |
| * Input 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_transmit |
| * |
| * Description: |
| * Start hardware transmission. Called either from the txdone interrupt |
| * handling or from watchdog based polling. |
| * |
| * Input 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_txdesc_s *txdesc; |
| struct eth_txdesc_s *txfirst; |
| |
| /* The internal (optimal) network buffer size may be configured to be |
| * larger than the Ethernet buffer size. |
| */ |
| |
| #if OPTIMAL_ETH_BUFSIZE > CONFIG_STM32_ETH_BUFSIZE |
| uint8_t *buffer; |
| int bufcount; |
| int lastsize; |
| int i; |
| #endif |
| |
| /* Verify that the hardware is ready to send another packet. If we get |
| * here, then we are committed to sending a packet; Higher level logic |
| * must have assured that there is no transmission in progress. |
| */ |
| |
| txdesc = priv->txhead; |
| txfirst = txdesc; |
| |
| ninfo("d_len: %d d_buf: %p txhead: %p tdes0: %08" PRIx32 "\n", |
| priv->dev.d_len, priv->dev.d_buf, txdesc, txdesc->tdes0); |
| |
| DEBUGASSERT(txdesc && (txdesc->tdes0 & ETH_TDES0_OWN) == 0); |
| |
| /* Is the size to be sent greater than the size of the Ethernet buffer? */ |
| |
| DEBUGASSERT(priv->dev.d_len > 0 && priv->dev.d_buf != NULL); |
| |
| #if OPTIMAL_ETH_BUFSIZE > CONFIG_STM32_ETH_BUFSIZE |
| if (priv->dev.d_len > CONFIG_STM32_ETH_BUFSIZE) |
| { |
| /* Yes... how many buffers will be need to send the packet? */ |
| |
| bufcount = (priv->dev.d_len + (CONFIG_STM32_ETH_BUFSIZE - 1)) / |
| CONFIG_STM32_ETH_BUFSIZE; |
| lastsize = priv->dev.d_len - (bufcount - 1) * CONFIG_STM32_ETH_BUFSIZE; |
| |
| ninfo("bufcount: %d lastsize: %d\n", bufcount, lastsize); |
| |
| /* Set the first segment bit in the first TX descriptor */ |
| |
| txdesc->tdes0 |= ETH_TDES0_FS; |
| |
| /* Set up all but the last TX descriptor */ |
| |
| buffer = priv->dev.d_buf; |
| |
| for (i = 0; i < bufcount; i++) |
| { |
| /* This could be a normal event but the design does not handle it */ |
| |
| DEBUGASSERT((txdesc->tdes0 & ETH_TDES0_OWN) == 0); |
| |
| /* Set the Buffer1 address pointer */ |
| |
| txdesc->tdes2 = (uint32_t)buffer; |
| |
| /* Set the buffer size in all TX descriptors */ |
| |
| if (i == (bufcount - 1)) |
| { |
| /* This is the last segment. Set the last segment bit in the |
| * last TX descriptor and ask for an interrupt when this |
| * segment transfer completes. |
| */ |
| |
| txdesc->tdes0 |= (ETH_TDES0_LS | ETH_TDES0_IC); |
| |
| /* This segment is, most likely, of fractional buffersize */ |
| |
| txdesc->tdes1 = lastsize; |
| buffer += lastsize; |
| } |
| else |
| { |
| /* This is not the last segment. We don't want an interrupt |
| * when this segment transfer completes. |
| */ |
| |
| txdesc->tdes0 &= ~ETH_TDES0_IC; |
| |
| /* The size of the transfer is the whole buffer */ |
| |
| txdesc->tdes1 = CONFIG_STM32_ETH_BUFSIZE; |
| buffer += CONFIG_STM32_ETH_BUFSIZE; |
| } |
| |
| /* Give the descriptor to DMA */ |
| |
| txdesc->tdes0 |= ETH_TDES0_OWN; |
| txdesc = (struct eth_txdesc_s *)txdesc->tdes3; |
| } |
| } |
| else |
| #endif |
| { |
| /* The single descriptor is both the first and last segment. And we do |
| * want an interrupt when the transfer completes. |
| */ |
| |
| txdesc->tdes0 |= (ETH_TDES0_FS | ETH_TDES0_LS | ETH_TDES0_IC); |
| |
| /* Set frame size */ |
| |
| DEBUGASSERT(priv->dev.d_len <= CONFIG_STM32_ETH_BUFSIZE); |
| txdesc->tdes1 = priv->dev.d_len; |
| |
| /* Set the Buffer1 address pointer */ |
| |
| txdesc->tdes2 = (uint32_t)priv->dev.d_buf; |
| |
| /* Set OWN bit of the TX descriptor tdes0. This gives the buffer to |
| * Ethernet DMA |
| */ |
| |
| txdesc->tdes0 |= ETH_TDES0_OWN; |
| |
| /* Point to the next available TX descriptor */ |
| |
| txdesc = (struct eth_txdesc_s *)txdesc->tdes3; |
| } |
| |
| /* Remember where we left off in the TX descriptor chain */ |
| |
| priv->txhead = txdesc; |
| |
| /* Detach the buffer from priv->dev structure. That buffer is now |
| * "in-flight". |
| */ |
| |
| priv->dev.d_buf = NULL; |
| priv->dev.d_len = 0; |
| |
| /* If there is no other TX buffer, in flight, then remember the location |
| * of the TX descriptor. This is the location to check for TX done events. |
| */ |
| |
| if (!priv->txtail) |
| { |
| DEBUGASSERT(priv->inflight == 0); |
| priv->txtail = txfirst; |
| } |
| |
| /* Increment the number of TX transfer in-flight */ |
| |
| priv->inflight++; |
| |
| ninfo("txhead: %p txtail: %p inflight: %d\n", |
| priv->txhead, priv->txtail, priv->inflight); |
| |
| /* If all TX descriptors are in-flight, then we have to disable receive |
| * interrupts too. This is because receive events can trigger more |
| * un-stoppable transmit events. |
| */ |
| |
| if (priv->inflight >= CONFIG_STM32_ETH_NTXDESC) |
| { |
| stm32_disableint(priv, ETH_DMAINT_RI); |
| } |
| |
| /* Check if the TX Buffer unavailable flag is set */ |
| |
| if ((stm32_getreg(STM32_ETH_DMASR) & ETH_DMAINT_TBUI) != 0) |
| { |
| /* Clear TX Buffer unavailable flag */ |
| |
| stm32_putreg(ETH_DMAINT_TBUI, STM32_ETH_DMASR); |
| |
| /* Resume DMA transmission */ |
| |
| stm32_putreg(0, STM32_ETH_DMATPDR); |
| } |
| |
| /* Enable TX interrupts */ |
| |
| stm32_enableint(priv, ETH_DMAINT_TI); |
| |
| /* Setup the TX timeout watchdog (perhaps restarting the timer) */ |
| |
| wd_start(&priv->txtimeout, STM32_TXTIMEOUT, |
| stm32_txtimeout_expiry, (wdparm_t)priv); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_txpoll |
| * |
| * Description: |
| * The transmitter is available, check if the network has any outgoing |
| * packets ready to send. This is a callback from devif_poll(). |
| * devif_poll() may be called: |
| * |
| * 1. When the preceding TX packet send is complete, |
| * 2. When the preceding TX packet send timesout and the interface is reset |
| * 3. During normal TX polling |
| * |
| * Input 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_TDES0_OWN may be cleared BUT still |
| * not available because stm32_freeframe() has not yet run. If |
| * stm32_freeframe() has run, the buffer1 pointer (tdes2) will be |
| * nullified (and inflight should be < CONFIG_STM32_ETH_NTXDESC). |
| */ |
| |
| if ((priv->txhead->tdes0 & ETH_TDES0_OWN) != 0 || |
| priv->txhead->tdes2 != 0) |
| { |
| /* We have to terminate the poll if we have no more descriptors |
| * available for another transfer. |
| */ |
| |
| return -EBUSY; |
| } |
| |
| /* We have the descriptor, we can continue the poll. Allocate a new |
| * buffer for the poll. |
| */ |
| |
| dev->d_buf = stm32_allocbuffer(priv); |
| |
| /* We can't continue the poll if we have no buffers */ |
| |
| if (dev->d_buf == NULL) |
| { |
| /* Terminate the poll. */ |
| |
| return -ENOMEM; |
| } |
| |
| /* If zero is returned, the polling will continue until all connections |
| * have been examined. |
| */ |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_dopoll |
| * |
| * Description: |
| * The function is called in order to perform an out-of-sequence TX poll. |
| * This is done: |
| * |
| * 1. After completion of a transmission (stm32_txdone), |
| * 2. When new TX data is available (stm32_txavail_process), and |
| * 3. After a TX timeout to restart the sending process |
| * (stm32_txtimeout_process). |
| * |
| * Input 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_TDES0_OWN may be cleared BUT still |
| * not available because stm32_freeframe() has not yet run. If |
| * stm32_freeframe() has run, the buffer1 pointer (tdes2) will be |
| * nullified (and inflight should be < CONFIG_STM32_ETH_NTXDESC). |
| */ |
| |
| if ((priv->txhead->tdes0 & ETH_TDES0_OWN) == 0 && |
| priv->txhead->tdes2 == 0) |
| { |
| /* If we have the descriptor, then poll the network for new XMIT data. |
| * Allocate a buffer for the poll. |
| */ |
| |
| DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL); |
| dev->d_buf = stm32_allocbuffer(priv); |
| |
| /* We can't poll if we have no buffers */ |
| |
| if (dev->d_buf) |
| { |
| devif_poll(dev, stm32_txpoll); |
| |
| /* We will, most likely end up with a buffer to be freed. But it |
| * might not be the same one that we allocated above. |
| */ |
| |
| if (dev->d_buf) |
| { |
| DEBUGASSERT(dev->d_len == 0); |
| stm32_freebuffer(priv, dev->d_buf); |
| dev->d_buf = NULL; |
| } |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_enableint |
| * |
| * Description: |
| * Enable a "normal" interrupt |
| * |
| * Input 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_DMAIER); |
| regval |= (ETH_DMAINT_NIS | ierbit); |
| stm32_putreg(regval, STM32_ETH_DMAIER); |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_disableint |
| * |
| * Description: |
| * Disable a normal interrupt. |
| * |
| * Input 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_DMAIER); |
| regval &= ~ierbit; |
| |
| /* Are all "normal" interrupts now disabled? */ |
| |
| if ((regval & ETH_DMAINT_NORMAL) == 0) |
| { |
| /* Yes.. disable normal interrupts */ |
| |
| regval &= ~ETH_DMAINT_NIS; |
| } |
| |
| stm32_putreg(regval, STM32_ETH_DMAIER); |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_freesegment |
| * |
| * Description: |
| * The function is called when a frame is received using the DMA receive |
| * interrupt. It scans the RX descriptors to the received frame. |
| * |
| * Input 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_rxdesc_s *rxfirst, int segments) |
| { |
| struct eth_rxdesc_s *rxdesc; |
| int i; |
| |
| ninfo("rxfirst: %p segments: %d\n", rxfirst, segments); |
| |
| /* Set OWN bit in RX descriptors. This gives the buffers back to DMA */ |
| |
| rxdesc = rxfirst; |
| for (i = 0; i < segments; i++) |
| { |
| rxdesc->rdes0 = ETH_RDES0_OWN; |
| rxdesc = (struct eth_rxdesc_s *)rxdesc->rdes3; |
| } |
| |
| /* Reset the segment management logic */ |
| |
| priv->rxcurr = NULL; |
| priv->segments = 0; |
| |
| /* Check if the RX Buffer unavailable flag is set */ |
| |
| if ((stm32_getreg(STM32_ETH_DMASR) & ETH_DMAINT_RBUI) != 0) |
| { |
| /* Clear RBUS Ethernet DMA flag */ |
| |
| stm32_putreg(ETH_DMAINT_RBUI, STM32_ETH_DMASR); |
| |
| /* Resume DMA reception */ |
| |
| stm32_putreg(0, STM32_ETH_DMARPDR); |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_recvframe |
| * |
| * Description: |
| * The function is called when a frame is received using the DMA receive |
| * interrupt. It scans the RX descriptors of the received frame. |
| * |
| * NOTE: This function will silently discard any packets containing errors. |
| * |
| * Input 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_rxdesc_s *rxdesc; |
| struct eth_rxdesc_s *rxcurr; |
| uint8_t *buffer; |
| int i; |
| |
| ninfo("rxhead: %p rxcurr: %p segments: %d\n", |
| priv->rxhead, priv->rxcurr, priv->segments); |
| |
| /* Check if there are free buffers. We cannot receive new frames in this |
| * design unless there is at least one free buffer. |
| */ |
| |
| if (!stm32_isfreebuffer(priv)) |
| { |
| nerr("ERROR: No free buffers\n"); |
| return -ENOMEM; |
| } |
| |
| /* Scan descriptors owned by the CPU. Scan until: |
| * |
| * 1) We find a descriptor still owned by the DMA, |
| * 2) We have examined all of the RX descriptors, or |
| * 3) All of the TX descriptors are in flight. |
| * |
| * This last case is obscure. It is due to that fact that each packet |
| * that we receive can generate an unstoppable transmisson. So we have |
| * to stop receiving when we can not longer transmit. In this case, the |
| * transmit logic should also have disabled further RX interrupts. |
| */ |
| |
| rxdesc = priv->rxhead; |
| for (i = 0; |
| (rxdesc->rdes0 & ETH_RDES0_OWN) == 0 && |
| i < CONFIG_STM32_ETH_NRXDESC && |
| priv->inflight < CONFIG_STM32_ETH_NTXDESC; |
| i++) |
| { |
| /* Check if this is the first segment in the frame */ |
| |
| if ((rxdesc->rdes0 & ETH_RDES0_FS) != 0 && |
| (rxdesc->rdes0 & ETH_RDES0_LS) == 0) |
| { |
| priv->rxcurr = rxdesc; |
| priv->segments = 1; |
| } |
| |
| /* Check if this is an intermediate segment in the frame */ |
| |
| else if (((rxdesc->rdes0 & ETH_RDES0_LS) == 0) && |
| ((rxdesc->rdes0 & ETH_RDES0_FS) == 0)) |
| { |
| priv->segments++; |
| } |
| |
| /* Otherwise, it is the last segment in the frame */ |
| |
| else |
| { |
| priv->segments++; |
| |
| /* Check if there is only one segment in the frame */ |
| |
| if (priv->segments == 1) |
| { |
| rxcurr = rxdesc; |
| } |
| else |
| { |
| rxcurr = priv->rxcurr; |
| } |
| |
| ninfo("rxhead: %p rxcurr: %p segments: %d\n", |
| priv->rxhead, priv->rxcurr, priv->segments); |
| |
| /* Check if any errors are reported in the frame */ |
| |
| if ((rxdesc->rdes0 & ETH_RDES0_ES) == 0) |
| { |
| struct net_driver_s *dev = &priv->dev; |
| |
| /* Get the Frame Length of the received packet: substruct 4 |
| * bytes of the CRC |
| */ |
| |
| dev->d_len = ((rxdesc->rdes0 & ETH_RDES0_FL_MASK) >> |
| ETH_RDES0_FL_SHIFT) - 4; |
| |
| if (priv->segments > 1 || |
| dev->d_len > CONFIG_STM32_ETH_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->rdes2; |
| rxcurr->rdes2 = (uint32_t)buffer; |
| |
| #ifdef CONFIG_STM32_ETH_TIMESTAMP_RX |
| priv->rxtimelow = rxcurr->rdes6; |
| priv->rxtimehigh = rxcurr->rdes7; |
| #endif |
| |
| /* Return success, remembering where we should re-start |
| * scanning and resetting the segment scanning logic |
| */ |
| |
| priv->rxhead = (struct eth_rxdesc_s *)rxdesc->rdes3; |
| stm32_freesegment(priv, rxcurr, priv->segments); |
| |
| ninfo("rxhead: %p d_buf: %p d_len: %d\n", |
| priv->rxhead, dev->d_buf, dev->d_len); |
| |
| return OK; |
| } |
| } |
| else |
| { |
| /* Drop the frame that contains the errors, reset the segment |
| * scanning logic, and continue scanning with the next frame. |
| */ |
| |
| nerr("ERROR: Dropped, RX descriptor errors: %08" PRIx32 "\n", |
| rxdesc->rdes0); |
| stm32_freesegment(priv, rxcurr, priv->segments); |
| } |
| } |
| |
| /* Try the next descriptor */ |
| |
| rxdesc = (struct eth_rxdesc_s *)rxdesc->rdes3; |
| } |
| |
| /* We get here after all of the descriptors have been scanned or when |
| * rxdesc points to the first descriptor owned by the DMA. Remember |
| * where we left off. |
| */ |
| |
| priv->rxhead = rxdesc; |
| |
| ninfo("rxhead: %p rxcurr: %p segments: %d\n", |
| priv->rxhead, priv->rxcurr, priv->segments); |
| |
| return -EAGAIN; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_receive |
| * |
| * Description: |
| * An interrupt was received indicating the availability of a new RX packet |
| * |
| * Input 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 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; |
| } |
| |
| #ifdef CONFIG_STM32_ETH_TIMESTAMP_RX |
| stm32_eth_ptp_convert_rxtime(priv); |
| #endif |
| |
| /* 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"); |
| |
| /* Receive an IPv4 packet from the network device */ |
| |
| ipv4_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data that should be |
| * sent out on the network, d_len field will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| /* 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, d_len field will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| /* 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_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data that should be |
| * sent out on the network, d_len field will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| stm32_transmit(priv); |
| } |
| } |
| else |
| #endif |
| { |
| nerr("ERROR: Dropped, Unknown type: %04x\n", BUF->type); |
| } |
| |
| /* We are finished with the RX buffer. NOTE: If the buffer is |
| * re-used for transmission, the dev->d_buf field will have been |
| * nullified. |
| */ |
| |
| if (dev->d_buf) |
| { |
| /* Free the receive packet buffer */ |
| |
| stm32_freebuffer(priv, dev->d_buf); |
| dev->d_buf = NULL; |
| dev->d_len = 0; |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_freeframe |
| * |
| * Description: |
| * Scans the TX descriptors and frees the buffers of completed transfers. |
| * |
| * Input 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_txdesc_s *txdesc; |
| |
| 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); |
| |
| while ((txdesc->tdes0 & ETH_TDES0_OWN) == 0) |
| { |
| /* There should be a buffer assigned to all in-flight |
| * TX descriptors. |
| */ |
| |
| ninfo("txtail: %p tdes0: %08" PRIx32 |
| " tdes2: %08" PRIx32 " tdes3: %08" PRIx32 "\n", |
| txdesc, txdesc->tdes0, txdesc->tdes2, txdesc->tdes3); |
| |
| DEBUGASSERT(txdesc->tdes2 != 0); |
| |
| /* Check if this is the first segment of a TX frame. */ |
| |
| if ((txdesc->tdes0 & ETH_TDES0_FS) != 0) |
| { |
| /* Yes.. Free the buffer */ |
| |
| stm32_freebuffer(priv, (uint8_t *)txdesc->tdes2); |
| } |
| |
| /* In any event, make sure that TDES2 is nullified. */ |
| |
| txdesc->tdes2 = 0; |
| |
| /* Check if this is the last segment of a TX frame */ |
| |
| if ((txdesc->tdes0 & ETH_TDES0_LS) != 0) |
| { |
| /* Yes.. Decrement the number of frames "in-flight". */ |
| |
| priv->inflight--; |
| |
| /* If all of the TX descriptors were in-flight, |
| * then RX interrupts may have been disabled... |
| * we can re-enable them now. |
| */ |
| |
| stm32_enableint(priv, ETH_DMAINT_RI); |
| |
| /* If there are no more frames in-flight, then bail. */ |
| |
| if (priv->inflight <= 0) |
| { |
| priv->txtail = NULL; |
| priv->inflight = 0; |
| return; |
| } |
| } |
| |
| /* Try the next descriptor in the TX chain */ |
| |
| txdesc = (struct eth_txdesc_s *)txdesc->tdes3; |
| } |
| |
| /* We get here if (1) there are still frames "in-flight". Remember |
| * where we left off. |
| */ |
| |
| priv->txtail = txdesc; |
| |
| ninfo("txhead: %p txtail: %p inflight: %d\n", |
| priv->txhead, priv->txtail, priv->inflight); |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_txdone |
| * |
| * Description: |
| * An interrupt was received indicating that the last TX packet |
| * transfer(s) are complete. |
| * |
| * Input 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_DMAINT_TI); |
| } |
| |
| /* Then poll the network for new XMIT data */ |
| |
| stm32_dopoll(priv); |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_interrupt_work |
| * |
| * Description: |
| * Perform interrupt related work from the worker thread |
| * |
| * Input 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_DMASR); |
| |
| /* Mask only enabled interrupts. This depends on the fact that the |
| * interrupt related bits (0-16) correspond in these two registers. |
| */ |
| |
| dmasr &= stm32_getreg(STM32_ETH_DMAIER); |
| |
| /* Check if there are pending "normal" interrupts */ |
| |
| if ((dmasr & ETH_DMAINT_NIS) != 0) |
| { |
| /* Yes.. Check if we received an incoming packet, if so, call |
| * stm32_receive() |
| */ |
| |
| if ((dmasr & ETH_DMAINT_RI) != 0) |
| { |
| /* Clear the pending receive interrupt */ |
| |
| stm32_putreg(ETH_DMAINT_RI, STM32_ETH_DMASR); |
| |
| /* Handle the received package */ |
| |
| stm32_receive(priv); |
| } |
| |
| /* Check if a packet transmission just completed. If so, call |
| * stm32_txdone(). This may disable further TX interrupts if there |
| * are no pending transmissions. |
| */ |
| |
| if ((dmasr & ETH_DMAINT_TI) != 0) |
| { |
| /* Clear the pending receive interrupt */ |
| |
| stm32_putreg(ETH_DMAINT_TI, STM32_ETH_DMASR); |
| |
| /* Check if there are pending transmissions */ |
| |
| stm32_txdone(priv); |
| } |
| |
| /* Clear the pending normal summary interrupt */ |
| |
| stm32_putreg(ETH_DMAINT_NIS, STM32_ETH_DMASR); |
| } |
| |
| /* Check if there are pending "abnormal" interrupts */ |
| |
| if ((dmasr & ETH_DMAINT_AIS) != 0) |
| { |
| /* Just let the user know what happened */ |
| |
| nerr("ERROR: Abnormal event(s): %08" PRIx32 "\n", dmasr); |
| |
| /* Clear all pending abnormal events */ |
| |
| stm32_putreg(ETH_DMAINT_ABNORMAL, STM32_ETH_DMASR); |
| |
| /* Clear the pending abnormal summary interrupt */ |
| |
| stm32_putreg(ETH_DMAINT_AIS, STM32_ETH_DMASR); |
| |
| /* In case of any error that stops the DMA, reset the MAC. */ |
| |
| if (dmasr & (ETH_DMAINT_FBEI | ETH_DMAINT_RPSI | |
| ETH_DMAINT_TJTI | ETH_DMAINT_TPSI)) |
| { |
| /* As per the datasheet's recommendation, the MAC |
| * needs to be reset for all fatal errors. The |
| * scheduled job will take the interface down and |
| * up again. |
| */ |
| |
| work_queue(ETHWORK, &priv->irqwork, stm32_txtimeout_work, priv, 0); |
| |
| /* Interrupts need to remain disabled, no other |
| * processing will take place. After reset |
| * everything will be restored. |
| */ |
| |
| net_unlock(); |
| return; |
| } |
| } |
| |
| net_unlock(); |
| |
| /* Re-enable Ethernet interrupts at the NVIC */ |
| |
| up_enable_irq(STM32_IRQ_ETH); |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_interrupt |
| * |
| * Description: |
| * Hardware interrupt handler |
| * |
| * Input 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_DMASR); |
| if (dmasr != 0) |
| { |
| /* Disable further Ethernet interrupts. Because Ethernet interrupts |
| * are also disabled if the TX timeout event occurs, there can be no |
| * race condition here. |
| */ |
| |
| up_disable_irq(STM32_IRQ_ETH); |
| |
| /* Check if a packet transmission just completed. */ |
| |
| if ((dmasr & ETH_DMAINT_TI) != 0) |
| { |
| /* If a TX transfer just completed, then cancel the TX timeout so |
| * there will be no race condition between any subsequent timeout |
| * expiration and the deferred interrupt processing. |
| */ |
| |
| wd_cancel(&priv->txtimeout); |
| } |
| |
| /* 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 |
| * |
| * Input 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. |
| * |
| * Input 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, |
| * perhaps canceling any pending IRQ processing. |
| */ |
| |
| 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 |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * Zero is returned on success; a negated errno value is returned on any |
| * failure. |
| * |
| * 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: %u.%u.%u.%u\n", |
| ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr), |
| ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr)); |
| #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; |
| } |
| |
| #ifdef CONFIG_STM32_ETH_PTP |
| /* Enable PTP timer */ |
| |
| stm32_eth_ptp_init(0); |
| |
| #ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES |
| if (!g_rtc_enabled) |
| { |
| /* Transfer time from system low-resolution timer to PTP basetime */ |
| |
| struct timespec ts; |
| clock_gettime(CLOCK_REALTIME, &ts); |
| up_rtc_settime(&ts); |
| g_rtc_enabled = true; |
| } |
| #endif /* CONFIG_STM32_ETH_PTP_RTC_HIRES */ |
| |
| #endif /* CONFIG_STM32_ETH_PTP */ |
| |
| /* 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. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * Returns zero on success; a negated errno value is returned on any |
| * failure. |
| * |
| * 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; |
| int ret = OK; |
| |
| 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); |
| |
| #ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES |
| if (g_rtc_enabled) |
| { |
| /* Transfer back to system low-resolution timer */ |
| |
| struct timespec ts; |
| up_rtc_gettime(&ts); |
| g_rtc_enabled = false; |
| clock_settime(CLOCK_REALTIME, &ts); |
| } |
| #endif |
| |
| /* 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. |
| */ |
| |
| ret = stm32_ethreset(priv); |
| if (ret < 0) |
| { |
| nerr("ERROR: stm32_ethreset failed (timeout), " |
| "still assuming it's going down.\n"); |
| } |
| |
| /* Mark the device "down" */ |
| |
| priv->ifup = false; |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_txavail_work |
| * |
| * Description: |
| * Perform an out-of-cycle poll on the worker thread. |
| * |
| * Input 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. |
| * |
| * Input 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 |
| * |
| * Input Parameters: |
| * data - the data to be checked |
| * length - length of the data |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_NET_MCASTGROUP) || 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 |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be added |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_NET_MCASTGROUP) || 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_MACHTHR; |
| hashindex -= 32; |
| } |
| else |
| { |
| registeraddress = STM32_ETH_MACHTLR; |
| } |
| |
| temp = stm32_getreg(registeraddress); |
| temp |= 1 << hashindex; |
| stm32_putreg(temp, registeraddress); |
| |
| temp = stm32_getreg(STM32_ETH_MACFFR); |
| temp |= (ETH_MACFFR_HM | ETH_MACFFR_HPF); |
| stm32_putreg(temp, STM32_ETH_MACFFR); |
| |
| return OK; |
| } |
| #endif /* CONFIG_NET_MCASTGROUP || CONFIG_NET_ICMPv6 */ |
| |
| /**************************************************************************** |
| * Function: stm32_rmmac |
| * |
| * Description: |
| * NuttX Callback: Remove the specified MAC address from the hardware |
| * multicast address filtering |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be removed |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| 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_MACHTHR; |
| hashindex -= 32; |
| } |
| else |
| { |
| registeraddress = STM32_ETH_MACHTLR; |
| } |
| |
| temp = stm32_getreg(registeraddress); |
| temp &= ~(1 << hashindex); |
| stm32_putreg(temp, registeraddress); |
| |
| /* If there is no address registered any more, delete multicast filtering */ |
| |
| if (stm32_getreg(STM32_ETH_MACHTHR) == 0 && |
| stm32_getreg(STM32_ETH_MACHTLR) == 0) |
| { |
| temp = stm32_getreg(STM32_ETH_MACFFR); |
| temp &= ~(ETH_MACFFR_HM | ETH_MACFFR_HPF); |
| stm32_putreg(temp, STM32_ETH_MACFFR); |
| } |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: stm32_txdescinit |
| * |
| * Description: |
| * Initializes the DMA TX descriptors in chain mode. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void stm32_txdescinit(struct stm32_ethmac_s *priv, |
| struct eth_txdesc_s *txtable) |
| { |
| struct eth_txdesc_s *txdesc; |
| int i; |
| |
| /* priv->txhead 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; |
| |
| /* priv->txtail will point to the first segment of the oldest pending |
| * "in-flight" TX transfer. NULL means that there are no active TX |
| * transfers. |
| */ |
| |
| priv->txtail = NULL; |
| priv->inflight = 0; |
| |
| /* Initialize each TX descriptor */ |
| |
| for (i = 0; i < CONFIG_STM32_ETH_NTXDESC; i++) |
| { |
| txdesc = &txtable[i]; |
| |
| /* Set Second Address Chained bit */ |
| |
| txdesc->tdes0 = ETH_TDES0_TCH; |
| |
| #ifdef CHECKSUM_BY_HARDWARE |
| /* Enable the checksum insertion for the TX frames */ |
| |
| txdesc->tdes0 |= ETH_TDES0_CIC_ALL; |
| #endif |
| |
| /* Clear Buffer1 address pointer (buffers will be assigned as they |
| * are used) |
| */ |
| |
| txdesc->tdes2 = 0; |
| |
| /* Initialize the next descriptor with |
| * the Next Descriptor Polling Enable |
| */ |
| |
| if (i < (CONFIG_STM32_ETH_NTXDESC - 1)) |
| { |
| /* Set next descriptor address register with next descriptor base |
| * address |
| */ |
| |
| txdesc->tdes3 = (uint32_t)&txtable[i + 1]; |
| } |
| else |
| { |
| /* For last descriptor, set next descriptor address register equal |
| * to the first descriptor base address |
| */ |
| |
| txdesc->tdes3 = (uint32_t)txtable; |
| } |
| } |
| |
| /* Set Transmit Descriptor List Address Register */ |
| |
| stm32_putreg((uint32_t)txtable, STM32_ETH_DMATDLAR); |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_rxdescinit |
| * |
| * Description: |
| * Initializes the DMA RX descriptors in chain mode. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void stm32_rxdescinit(struct stm32_ethmac_s *priv, |
| struct eth_rxdesc_s *rxtable, |
| uint8_t *rxbuffer) |
| { |
| struct eth_rxdesc_s *rxdesc; |
| int i; |
| |
| /* priv->rxhead will point to the first, RX descriptor in the chain. |
| * This will be where we receive the first incomplete frame. |
| */ |
| |
| priv->rxhead = rxtable; |
| |
| /* If we accumulate the frame in segments, priv->rxcurr points to the |
| * RX descriptor of the first segment in the current TX frame. |
| */ |
| |
| priv->rxcurr = NULL; |
| priv->segments = 0; |
| |
| /* Initialize each TX descriptor */ |
| |
| for (i = 0; i < CONFIG_STM32_ETH_NRXDESC; i++) |
| { |
| rxdesc = &rxtable[i]; |
| |
| /* Set Own bit of the RX descriptor rdes0 */ |
| |
| rxdesc->rdes0 = ETH_RDES0_OWN; |
| |
| /* Set Buffer1 size and Second Address Chained bit and enabled DMA |
| * RX desc receive interrupt |
| */ |
| |
| rxdesc->rdes1 = ETH_RDES1_RCH | (uint32_t)CONFIG_STM32_ETH_BUFSIZE; |
| |
| /* Set Buffer1 address pointer */ |
| |
| rxdesc->rdes2 = (uint32_t)&rxbuffer[i * CONFIG_STM32_ETH_BUFSIZE]; |
| |
| /* Initialize the next descriptor with |
| * the Next Descriptor Polling Enable |
| */ |
| |
| if (i < (CONFIG_STM32_ETH_NRXDESC - 1)) |
| { |
| /* Set next descriptor address register with next descriptor base |
| * address |
| */ |
| |
| rxdesc->rdes3 = (uint32_t)&rxtable[i + 1]; |
| } |
| else |
| { |
| /* For last descriptor, set next descriptor address register equal |
| * to the first descriptor base address |
| */ |
| |
| rxdesc->rdes3 = (uint32_t)rxtable; |
| } |
| } |
| |
| /* Set Receive Descriptor List Address Register */ |
| |
| stm32_putreg((uint32_t)rxtable, STM32_ETH_DMARDLAR); |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_ioctl |
| * |
| * Description: |
| * Executes the SIOCxMIIxxx command and responds using the request struct |
| * that must be provided as its 2nd parameter. |
| * |
| * When called with SIOCGMIIPHY it will get the PHY address for the device |
| * and write it to the req->phy_id field of the request struct. |
| * |
| * When called with SIOCGMIIREG it will read a register of the PHY that is |
| * specified using the req->reg_no struct field and then write its output |
| * to the req->val_out field. |
| * |
| * When called with SIOCSMIIREG it will write to a register of the PHY that |
| * is specified using the req->reg_no struct field and use req->val_in as |
| * its input. |
| * |
| * 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_IOCTL |
| static int stm32_ioctl(struct net_driver_s *dev, int cmd, unsigned long arg) |
| { |
| #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) |
| struct stm32_ethmac_s *priv = |
| (struct stm32_ethmac_s *)dev->d_private; |
| #endif |
| int ret; |
| |
| switch (cmd) |
| { |
| #ifdef CONFIG_NETDEV_PHY_IOCTL |
| #ifdef CONFIG_ARCH_PHY_INTERRUPT |
| case SIOCMIINOTIFY: /* Set up for PHY event notifications */ |
| { |
| struct mii_ioctl_notify_s *req = |
| (struct mii_ioctl_notify_s *)((uintptr_t)arg); |
| |
| ret = phy_notify_subscribe(dev->d_ifname, req->pid, &req->event); |
| if (ret == OK) |
| { |
| /* Enable PHY link up/down interrupts */ |
| |
| ret = stm32_phyintenable(priv); |
| } |
| } |
| break; |
| #endif |
| |
| case SIOCGMIIPHY: /* Get MII PHY address */ |
| { |
| struct mii_ioctl_data_s *req = |
| (struct mii_ioctl_data_s *)((uintptr_t)arg); |
| req->phy_id = CONFIG_STM32_PHYADDR; |
| ret = OK; |
| } |
| break; |
| |
| case SIOCGMIIREG: /* Get register from MII PHY */ |
| { |
| struct mii_ioctl_data_s *req = |
| (struct mii_ioctl_data_s *)((uintptr_t)arg); |
| ret = stm32_phyread(req->phy_id, req->reg_num, &req->val_out); |
| } |
| break; |
| |
| case SIOCSMIIREG: /* Set register in MII PHY */ |
| { |
| struct mii_ioctl_data_s *req = |
| (struct mii_ioctl_data_s *)((uintptr_t)arg); |
| ret = stm32_phywrite(req->phy_id, req->reg_num, req->val_in); |
| } |
| break; |
| #endif /* CONFIG_NETDEV_PHY_IOCTL */ |
| |
| default: |
| ret = -ENOTTY; |
| break; |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_NETDEV_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. |
| * |
| * Input 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) |
| { |
| uint16_t phyval; |
| int ret; |
| |
| ret = stm32_phyread(CONFIG_STM32_PHYADDR, MII_INT_REG, &phyval); |
| if (ret == OK) |
| { |
| /* Enable link up/down interrupts */ |
| |
| #ifdef CONFIG_ETH0_PHY_DP83848C |
| ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_DP83848C_MICR, |
| MII_DP83848C_INT_EN | MII_DP83848C_INT_OEN); |
| #endif |
| ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_INT_REG, |
| (phyval & ~MII_INT_CLREN) | MII_INT_SETEN); |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: stm32_phyread |
| * |
| * Description: |
| * Read a PHY register. |
| * |
| * Input Parameters: |
| * phydevaddr - The PHY device address |
| * phyregaddr - The PHY register address |
| * value - The location to return the 16-bit PHY register value. |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_STM32_AUTONEG) || defined(CONFIG_NETDEV_PHY_IOCTL) || \ |
| defined(CONFIG_ETH0_PHY_DM9161) |
| static int stm32_phyread(uint16_t phydevaddr, |
| uint16_t phyregaddr, uint16_t *value) |
| { |
| volatile uint32_t timeout; |
| uint32_t regval; |
| |
| regval = stm32_getreg(STM32_ETH_MACMIIAR); |
| |
| /* Clear the busy bit before accessing the MACMIIAR register. */ |
| |
| regval &= ~ETH_MACMIIAR_MB; |
| stm32_putreg(regval, STM32_ETH_MACMIIAR); |
| |
| /* Configure the MACMIIAR register, |
| * preserving CSR Clock Range CR[2:0] bits |
| */ |
| |
| regval &= ETH_MACMIIAR_CR_MASK; |
| |
| /* Set the PHY device address, PHY register address, and set the busy bit. |
| * the ETH_MACMIIAR_MW is clear, indicating a read operation. |
| */ |
| |
| regval |= (phydevaddr << ETH_MACMIIAR_PA_SHIFT) & ETH_MACMIIAR_PA_MASK; |
| regval |= (phyregaddr << ETH_MACMIIAR_MR_SHIFT) & ETH_MACMIIAR_MR_MASK; |
| regval |= ETH_MACMIIAR_MB; |
| |
| stm32_putreg(regval, STM32_ETH_MACMIIAR); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < PHY_READ_TIMEOUT; timeout++) |
| { |
| if ((stm32_getreg(STM32_ETH_MACMIIAR) & ETH_MACMIIAR_MB) == 0) |
| { |
| *value = (uint16_t)stm32_getreg(STM32_ETH_MACMIIDR); |
| return OK; |
| } |
| } |
| |
| nerr("ERROR: MII transfer timed out: phydevaddr: %04x phyregaddr: %04x\n", |
| phydevaddr, phyregaddr); |
| |
| return -ETIMEDOUT; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: stm32_phywrite |
| * |
| * Description: |
| * Write to a PHY register. |
| * |
| * Input Parameters: |
| * phydevaddr - The PHY device address |
| * phyregaddr - The PHY register address |
| * value - The 16-bit value to write to the PHY register value. |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int stm32_phywrite(uint16_t phydevaddr, |
| uint16_t phyregaddr, uint16_t value) |
| { |
| volatile uint32_t timeout; |
| uint32_t regval; |
| |
| regval = stm32_getreg(STM32_ETH_MACMIIAR); |
| |
| /* Clear the busy bit before accessing the MACMIIAR register. */ |
| |
| regval &= ~ETH_MACMIIAR_MB; |
| stm32_putreg(regval, STM32_ETH_MACMIIAR); |
| |
| /* Configure the MACMIIAR register, |
| * preserving CSR Clock Range CR[2:0] bits |
| */ |
| |
| regval &= ETH_MACMIIAR_CR_MASK; |
| |
| /* Set the PHY device address, PHY register address, and set the busy bit. |
| * the ETH_MACMIIAR_MW is set, indicating a write operation. |
| */ |
| |
| regval |= (phydevaddr << ETH_MACMIIAR_PA_SHIFT) & ETH_MACMIIAR_PA_MASK; |
| regval |= (phyregaddr << ETH_MACMIIAR_MR_SHIFT) & ETH_MACMIIAR_MR_MASK; |
| regval |= (ETH_MACMIIAR_MB | ETH_MACMIIAR_MW); |
| |
| /* Write the value into the MACIIDR register before setting the new |
| * MACMIIAR register value. |
| */ |
| |
| stm32_putreg(value, STM32_ETH_MACMIIDR); |
| stm32_putreg(regval, STM32_ETH_MACMIIAR); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < PHY_WRITE_TIMEOUT; timeout++) |
| { |
| if ((stm32_getreg(STM32_ETH_MACMIIAR) & ETH_MACMIIAR_MB) == 0) |
| { |
| return OK; |
| } |
| } |
| |
| nerr("ERROR: MII transfer timed out: " |
| "phydevaddr: %04x phyregaddr: %04x value: %04x\n", |
| phydevaddr, phyregaddr, value); |
| |
| return -ETIMEDOUT; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_dm9161 |
| * |
| * Description: |
| * Special workaround for the Davicom DM9161 PHY is required. On power, |
| * up, the PHY is not usually configured correctly but will work after |
| * a powered-up reset. This is really a workaround for some more |
| * fundamental issue with the PHY clocking initialization, but the |
| * root cause has not been studied (nor will it be with this workaround). |
| * |
| * Input 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_STM32_PHYADDR, MII_PHYID1, &phyval); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read the PHY ID1: %d\n", ret); |
| return ret; |
| } |
| |
| /* If we failed to read the PHY ID1 register, |
| * then 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)"(16) */ |
| |
| ret = stm32_phyread(CONFIG_STM32_PHYADDR, 16, &phyval); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read the PHY Register 0x10: %d\n", ret); |
| return ret; |
| } |
| |
| /* Bit 8 of the DSCR register is zero, then the DM9161 has not selected |
| * RMII. If RMII is not selected, then reset the MCU to recover. |
| */ |
| |
| else if ((phyval & (1 << 8)) == 0) |
| { |
| up_systemreset(); |
| } |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: stm32_phyinit |
| * |
| * Description: |
| * Configure the PHY and determine the link speed/duplex. |
| * |
| * Input 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_STM32_AUTONEG |
| volatile uint32_t timeout; |
| #endif |
| |
| uint32_t regval; |
| uint16_t phyval; |
| int ret; |
| |
| /* Assume 10MBps and half duplex */ |
| |
| priv->mbps100 = 0; |
| priv->fduplex = 0; |
| |
| /* Setup up PHY clocking by setting the SR field in the MACMIIAR register */ |
| |
| regval = stm32_getreg(STM32_ETH_MACMIIAR); |
| regval &= ~ETH_MACMIIAR_CR_MASK; |
| regval |= ETH_MACMIIAR_CR; |
| stm32_putreg(regval, STM32_ETH_MACMIIAR); |
| |
| /* Put the PHY in reset mode */ |
| |
| ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_MCR, MII_MCR_RESET); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to reset the PHY: %d\n", ret); |
| return ret; |
| } |
| |
| up_mdelay(PHY_RESET_DELAY); |
| |
| /* Perform any necessary, board-specific PHY initialization */ |
| |
| #ifdef CONFIG_STM32_PHYINIT |
| ret = stm32_phy_boardinitialize(0); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to initialize the PHY: %d\n", ret); |
| return ret; |
| } |
| #endif |
| |
| /* Special workaround for the Davicom DM9161 PHY is required. */ |
| |
| #ifdef CONFIG_ETH0_PHY_DM9161 |
| ret = stm32_dm9161(priv); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| #endif |
| |
| /* Perform auto-negotiation if so configured */ |
| |
| #ifdef CONFIG_STM32_AUTONEG |
| /* Wait for link status */ |
| |
| for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++) |
| { |
| ret = stm32_phyread(CONFIG_STM32_PHYADDR, MII_MSR, &phyval); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read the PHY MSR: %d\n", ret); |
| return ret; |
| } |
| else if ((phyval & MII_MSR_LINKSTATUS) != 0) |
| { |
| break; |
| } |
| } |
| |
| if (timeout >= PHY_RETRY_TIMEOUT) |
| { |
| nerr("ERROR: Timed out waiting for link status: %04x\n", phyval); |
| return -ETIMEDOUT; |
| } |
| |
| /* Enable auto-gegotiation */ |
| |
| ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_MCR, MII_MCR_ANENABLE); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to enable auto-negotiation: %d\n", ret); |
| return ret; |
| } |
| |
| /* Wait until auto-negotiation completes */ |
| |
| for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++) |
| { |
| ret = stm32_phyread(CONFIG_STM32_PHYADDR, MII_MSR, &phyval); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read the PHY MSR: %d\n", ret); |
| return ret; |
| } |
| else if ((phyval & MII_MSR_ANEGCOMPLETE) != 0) |
| { |
| break; |
| } |
| } |
| |
| if (timeout >= PHY_RETRY_TIMEOUT) |
| { |
| nerr("ERROR: Timed out waiting for auto-negotiation\n"); |
| return -ETIMEDOUT; |
| } |
| |
| /* Read the result of the auto-negotiation from the PHY-specific register */ |
| |
| ret = stm32_phyread(CONFIG_STM32_PHYADDR, CONFIG_STM32_PHYSR, &phyval); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read PHY status register\n"); |
| return ret; |
| } |
| |
| /* Remember the selected speed and duplex modes */ |
| |
| ninfo("PHYSR[%d]: %04x\n", CONFIG_STM32_PHYSR, phyval); |
| |
| /* Different PHYs present speed and mode information in different ways. |
| * IF This CONFIG_STM32_PHYSR_ALTCONFIG is selected, this indicates that |
| * the PHY represents speed and mode information are combined, for example, |
| * with separate bits for 10HD, 100HD, 10FD and 100FD. |
| */ |
| |
| #ifdef CONFIG_STM32_PHYSR_ALTCONFIG |
| switch (phyval & CONFIG_STM32_PHYSR_ALTMODE) |
| { |
| default: |
| case CONFIG_STM32_PHYSR_10HD: |
| priv->fduplex = 0; |
| priv->mbps100 = 0; |
| break; |
| |
| case CONFIG_STM32_PHYSR_100HD: |
| priv->fduplex = 0; |
| priv->mbps100 = 1; |
| break; |
| |
| case CONFIG_STM32_PHYSR_10FD: |
| priv->fduplex = 1; |
| priv->mbps100 = 0; |
| break; |
| |
| case CONFIG_STM32_PHYSR_100FD: |
| priv->fduplex = 1; |
| priv->mbps100 = 1; |
| break; |
| } |
| |
| /* Different PHYs present speed and mode information in different ways. |
| * Some will present separate information for speed and mode (this is the |
| * default). Those PHYs, for example, may provide a 10/100 Mbps indication |
| * and a separate full/half duplex indication. |
| */ |
| |
| #else |
| if ((phyval & CONFIG_STM32_PHYSR_MODE) == CONFIG_STM32_PHYSR_FULLDUPLEX) |
| { |
| priv->fduplex = 1; |
| } |
| |
| if ((phyval & CONFIG_STM32_PHYSR_SPEED) == CONFIG_STM32_PHYSR_100MBPS) |
| { |
| priv->mbps100 = 1; |
| } |
| #endif |
| |
| #else /* Auto-negotiation not selected */ |
| |
| phyval = 0; |
| #ifdef CONFIG_STM32_ETHFD |
| phyval |= MII_MCR_FULLDPLX; |
| #endif |
| #ifdef CONFIG_STM32_ETH100MBPS |
| phyval |= MII_MCR_SPEED100; |
| #endif |
| |
| ret = stm32_phywrite(CONFIG_STM32_PHYADDR, MII_MCR, phyval); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to write the PHY MCR: %d\n", ret); |
| return ret; |
| } |
| |
| up_mdelay(PHY_CONFIG_DELAY); |
| |
| /* Remember the selected speed and duplex modes */ |
| |
| #ifdef CONFIG_STM32_ETHFD |
| priv->fduplex = 1; |
| #endif |
| #ifdef CONFIG_STM32_ETH100MBPS |
| priv->mbps100 = 1; |
| #endif |
| #endif |
| |
| ninfo("Duplex: %s Speed: %d MBps\n", |
| priv->fduplex ? "FULL" : "HALF", |
| priv->mbps100 ? 100 : 10); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_selectmii |
| * |
| * Description: |
| * Selects the MII interface. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_STM32_MII |
| static inline void stm32_selectmii(void) |
| { |
| uint32_t regval; |
| |
| #ifdef CONFIG_STM32_CONNECTIVITYLINE |
| regval = getreg32(STM32_AFIO_MAPR); |
| regval &= ~AFIO_MAPR_MII_RMII_SEL; |
| putreg32(regval, STM32_AFIO_MAPR); |
| #else |
| regval = getreg32(STM32_SYSCFG_PMC); |
| regval &= ~SYSCFG_PMC_MII_RMII_SEL; |
| putreg32(regval, STM32_SYSCFG_PMC); |
| #endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: stm32_selectrmii |
| * |
| * Description: |
| * Selects the RMII interface. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_STM32_RMII |
| static inline void stm32_selectrmii(void) |
| { |
| uint32_t regval; |
| |
| #ifdef CONFIG_STM32_CONNECTIVITYLINE |
| regval = getreg32(STM32_AFIO_MAPR); |
| regval |= AFIO_MAPR_MII_RMII_SEL; |
| putreg32(regval, STM32_AFIO_MAPR); |
| #else |
| regval = getreg32(STM32_SYSCFG_PMC); |
| regval |= SYSCFG_PMC_MII_RMII_SEL; |
| putreg32(regval, STM32_SYSCFG_PMC); |
| #endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: stm32_ethgpioconfig |
| * |
| * Description: |
| * Configure GPIOs for the Ethernet interface. |
| * |
| * Input 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_STM32_MII) || defined(CONFIG_STM32_RMII) |
| |
| /* MDC and MDIO are common to both modes */ |
| |
| stm32_configgpio(GPIO_ETH_MDC); |
| stm32_configgpio(GPIO_ETH_MDIO); |
| |
| /* Set up the MII interface */ |
| |
| # if defined(CONFIG_STM32_MII) |
| |
| /* Select the MII interface */ |
| |
| stm32_selectmii(); |
| |
| /* Provide clocking via MCO, MCO1 or MCO2: |
| * |
| * "MCO1 (microcontroller clock output), used to output HSI, LSE, HSE or |
| * PLL clock (through a configurable prescaler) on PA8 pin." |
| * |
| * "MCO2 (microcontroller clock output), used to output HSE, PLL, SYSCLK or |
| * PLLI2S clock (through a configurable prescaler) on PC9 pin." |
| */ |
| |
| # if defined(CONFIG_STM32_MII_MCO1) |
| /* Configure MC01 to drive the PHY. Board logic must provide MC01 clocking |
| * info. |
| */ |
| |
| stm32_configgpio(GPIO_MCO1); |
| stm32_mco1config(BOARD_CFGR_MC01_SOURCE, BOARD_CFGR_MC01_DIVIDER); |
| |
| # elif defined(CONFIG_STM32_MII_MCO2) |
| /* Configure MC02 to drive the PHY. Board logic must provide MC02 clocking |
| * info. |
| */ |
| |
| stm32_configgpio(GPIO_MCO2); |
| stm32_mco2config(BOARD_CFGR_MC02_SOURCE, BOARD_CFGR_MC02_DIVIDER); |
| |
| # elif defined(CONFIG_STM32_MII_MCO) |
| /* Setup MCO pin for alternative usage */ |
| |
| stm32_configgpio(GPIO_MCO); |
| stm32_mcoconfig(BOARD_CFGR_MCO_SOURCE); |
| # endif |
| |
| /* MII interface pins (17): |
| * |
| * MII_TX_CLK, MII_TXD[3:0], MII_TX_EN, MII_RX_CLK, MII_RXD[3:0], |
| * MII_RX_ER, MII_RX_DV, MII_CRS, MII_COL, MDC, MDIO |
| */ |
| |
| stm32_configgpio(GPIO_ETH_MII_COL); |
| stm32_configgpio(GPIO_ETH_MII_CRS); |
| stm32_configgpio(GPIO_ETH_MII_RXD0); |
| stm32_configgpio(GPIO_ETH_MII_RXD1); |
| stm32_configgpio(GPIO_ETH_MII_RXD2); |
| stm32_configgpio(GPIO_ETH_MII_RXD3); |
| stm32_configgpio(GPIO_ETH_MII_RX_CLK); |
| stm32_configgpio(GPIO_ETH_MII_RX_DV); |
| stm32_configgpio(GPIO_ETH_MII_RX_ER); |
| stm32_configgpio(GPIO_ETH_MII_TXD0); |
| stm32_configgpio(GPIO_ETH_MII_TXD1); |
| stm32_configgpio(GPIO_ETH_MII_TXD2); |
| stm32_configgpio(GPIO_ETH_MII_TXD3); |
| stm32_configgpio(GPIO_ETH_MII_TX_CLK); |
| stm32_configgpio(GPIO_ETH_MII_TX_EN); |
| |
| /* Set up the RMII interface. */ |
| |
| # elif defined(CONFIG_STM32_RMII) |
| |
| /* Select the RMII interface */ |
| |
| stm32_selectrmii(); |
| |
| /* Provide clocking via MCO, MCO1 or MCO2: |
| * |
| * "MCO1 (microcontroller clock output), used to output HSI, LSE, HSE or |
| * PLL clock (through a configurable prescaler) on PA8 pin." |
| * |
| * "MCO2 (microcontroller clock output), used to output HSE, PLL, SYSCLK or |
| * PLLI2S clock (through a configurable prescaler) on PC9 pin." |
| */ |
| |
| # if defined(CONFIG_STM32_RMII_MCO1) |
| /* Configure MC01 to drive the PHY. Board logic must provide MC01 clocking |
| * info. |
| */ |
| |
| stm32_configgpio(GPIO_MCO1); |
| stm32_mco1config(BOARD_CFGR_MC01_SOURCE, BOARD_CFGR_MC01_DIVIDER); |
| |
| # elif defined(CONFIG_STM32_RMII_MCO2) |
| /* Configure MC02 to drive the PHY. Board logic must provide MC02 clocking |
| * info. |
| */ |
| |
| stm32_configgpio(GPIO_MCO2); |
| stm32_mco2config(BOARD_CFGR_MC02_SOURCE, BOARD_CFGR_MC02_DIVIDER); |
| |
| # elif defined(CONFIG_STM32_RMII_MCO) |
| /* Setup MCO pin for alternative usage */ |
| |
| stm32_configgpio(GPIO_MCO); |
| stm32_mcoconfig(BOARD_CFGR_MCO_SOURCE); |
| # endif |
| |
| /* RMII interface pins (7): |
| * |
| * RMII_TXD[1:0], RMII_TX_EN, RMII_RXD[1:0], RMII_CRS_DV, MDC, MDIO, |
| * RMII_REF_CLK |
| */ |
| |
| stm32_configgpio(GPIO_ETH_RMII_CRS_DV); |
| stm32_configgpio(GPIO_ETH_RMII_REF_CLK); |
| stm32_configgpio(GPIO_ETH_RMII_RXD0); |
| stm32_configgpio(GPIO_ETH_RMII_RXD1); |
| stm32_configgpio(GPIO_ETH_RMII_TXD0); |
| stm32_configgpio(GPIO_ETH_RMII_TXD1); |
| stm32_configgpio(GPIO_ETH_RMII_TX_EN); |
| |
| # endif |
| #endif |
| |
| #ifdef CONFIG_STM32_ETH_PTP_GPIO |
| /* Enable pulse-per-second (PPS) output signal */ |
| |
| stm32_configgpio(GPIO_ETH_PPS_OUT); |
| #endif |
| } |
| |
| #ifdef CONFIG_STM32_ETH_PTP |
| |
| /**************************************************************************** |
| * Function: stm32_eth_ptp_adjust |
| * |
| * Description: |
| * Adjust PTP timer run rate. |
| * |
| * Input Parameters: |
| * ppb - Adjustment in parts per billion (nanoseconds per second). |
| * Zero is default rate, positive value makes clock run faster |
| * and negative value slower. |
| * |
| * Returned Value: |
| * OK on success, negated errno on failure. |
| * |
| * Assumptions: |
| * Adjustment is between -0.5e9 and +0.5e9 (+- 50%) |
| * |
| ****************************************************************************/ |
| |
| static int stm32_eth_ptp_adjust(long ppb) |
| { |
| uint32_t regval; |
| uint64_t addend; |
| uint32_t increment; |
| |
| /* Compute addend value to achieve nominal timer rate. |
| * Increment is set by stm32_eth_ptp_init() and remains constants after |
| * that. |
| */ |
| |
| increment = stm32_getreg(STM32_ETH_PTPSSIR) & ETH_PTPSSIR_MASK; |
| addend = ((uint64_t)1 << (32 + 31)) / (STM32_SYSCLK_FREQUENCY * increment); |
| |
| /* Apply rate adjustment, if any */ |
| |
| if (ppb != 0) |
| { |
| addend += (int64_t)addend * ppb / NSEC_PER_SEC; |
| } |
| |
| /* Check for overflows */ |
| |
| if (addend == 0 || (uint32_t)addend != addend) |
| { |
| nerr("PTP adjustment out of range: ppb=%ld, addend=%lld\n", |
| ppb, addend); |
| return -EINVAL; |
| } |
| |
| /* Perform addend register update */ |
| |
| stm32_putreg((uint32_t)addend, STM32_ETH_PTPTSAR); |
| regval = stm32_getreg(STM32_ETH_PTPTSCR); |
| stm32_putreg(regval | ETH_PTPTSCR_TSARU, STM32_ETH_PTPTSCR); |
| up_udelay(1); |
| if (stm32_getreg(STM32_ETH_PTPTSCR) & ETH_PTPTSCR_TSARU) |
| { |
| /* This can happen if Ethernet PHY clock is stopped */ |
| |
| nerr("PTP addend update failed\n"); |
| return -EBUSY; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_eth_ptp_init |
| * |
| * Description: |
| * Configure the PTP timestamp counter of the Ethernet peripheral. |
| * |
| * Input Parameters: |
| * timestamp: Initial timestamp |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void stm32_eth_ptp_init(uint64_t timestamp) |
| { |
| uint32_t regval; |
| uint32_t increment; |
| |
| /* The PPS timestamp counter consists of a 32-bit seconds counter and |
| * 31-bit subsecond counter. The PTP input clock (SYSCLK) is divided by |
| * 2^32 / ADDEND and multiplied by INCREMENT. This calculation aims for |
| * ADDEND of 2^31 to provide +- 50% rate adjustment range. |
| * |
| * ADDEND value is then adjusted to compensate for rounding errors in |
| * the 8-bit INCREMENT value. The final rounding error will be less than |
| * 1 ppb. The timer frequency is approximately half of SYSCLK frequency, |
| * with phase jitter of one SYSCLK period. |
| */ |
| |
| increment = ((uint32_t)1 << 31) / (STM32_SYSCLK_FREQUENCY / 2); |
| DEBUGASSERT(increment > 0 && (increment & ETH_PTPSSIR_MASK) == increment); |
| |
| /* Timestamp counter initialization process |
| * (STM32F407 reference manual section 33.5.9 |
| * "Programming steps for system time generation initialization") |
| */ |
| |
| regval = ETH_PTPTSCR_TSE; |
| stm32_putreg(regval, STM32_ETH_PTPTSCR); |
| stm32_putreg(increment, STM32_ETH_PTPSSIR); |
| |
| /* Update addend value to default rate */ |
| |
| stm32_eth_ptp_adjust(0); |
| |
| /* Enable fine update mode */ |
| |
| regval |= ETH_PTPTSCR_TSFCU; |
| stm32_putreg(regval, STM32_ETH_PTPTSCR); |
| |
| /* Initialize counter value */ |
| |
| stm32_putreg((uint32_t)(timestamp >> 32), STM32_ETH_PTPTSHUR); |
| stm32_putreg((uint32_t)(timestamp >> 1), STM32_ETH_PTPTSLUR); |
| stm32_putreg(regval | ETH_PTPTSCR_TSSTI, STM32_ETH_PTPTSCR); |
| up_udelay(1); |
| |
| /* Initialization should complete within a few clock cycles. |
| * If not, there is probably something wrong with the PHY clock domain. |
| */ |
| |
| if (stm32_getreg(STM32_ETH_PTPTSCR) & ETH_PTPTSCR_TSSTI) |
| { |
| nerr("PTP timestamp initialization failed\n"); |
| } |
| |
| /* Enable packet timestamping */ |
| |
| #ifdef CONFIG_STM32_ETH_TIMESTAMP_RX |
| regval |= ETH_PTPTSCR_TSSARFE; |
| stm32_putreg(regval, STM32_ETH_PTPTSCR); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_eth_ptp_gettime |
| * |
| * Description: |
| * Read PTP timestamp registers. The 64-bit timestamp consists of two |
| * registers that are updated continuously. This function employs |
| * double-read pattern to correctly handle overflow of the lower register. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * 64-bit timestamp, where upper 32 bits are the second count and lower |
| * 32-bits are the subsecond count. |
| * If timer is not yet initialized, returns 0. |
| * |
| * Assumptions: |
| * Can be called from interrupt or task context. |
| * |
| ****************************************************************************/ |
| |
| static uint64_t stm32_eth_ptp_gettime(void) |
| { |
| uint32_t high1; |
| uint32_t low; |
| uint32_t high2; |
| |
| high1 = getreg32(STM32_ETH_PTPTSHR); |
| low = getreg32(STM32_ETH_PTPTSLR); |
| high2 = getreg32(STM32_ETH_PTPTSHR); |
| |
| if (high1 == high2) |
| { |
| return ((uint64_t)high2 << 32) | ((low & ETH_PTPTSLR_MASK) << 1); |
| } |
| else |
| { |
| /* Lower counter overflowed between the two register reads. |
| * Take its value as 0. |
| */ |
| |
| return ((uint64_t)high2 << 32); |
| } |
| } |
| |
| static inline void ptp_to_timespec(uint64_t timestamp, struct timespec *ts) |
| { |
| ts->tv_sec = (timestamp >> 32); |
| ts->tv_nsec = ((uint32_t)timestamp * (uint64_t)NSEC_PER_SEC) >> 32; |
| } |
| |
| /* Convert RX timestamp to CLOCK_REALTIME */ |
| #ifdef CONFIG_STM32_ETH_TIMESTAMP_RX |
| static void stm32_eth_ptp_convert_rxtime(struct stm32_ethmac_s *priv) |
| { |
| uint64_t timestamp; |
| struct timespec rxtime; |
| |
| timestamp = ((uint64_t)priv->rxtimehigh << 32) |
| | ((priv->rxtimelow & ETH_PTPTSLR_MASK) << 1); |
| |
| /* Timestamp of 0 indicates that Ethernet peripheral didn't store the |
| * timestamp. Timestamp of all ones indicates "corrupt timestamp" |
| * according to reference manual. In either case, we pass along |
| * a timestamp of all zeros to application. |
| */ |
| |
| if (timestamp == 0 || timestamp >= UINT64_MAX - 1) |
| { |
| nerr("Packet RX timestamp is invalid\n"); |
| priv->dev.d_rxtime.tv_sec = 0; |
| priv->dev.d_rxtime.tv_nsec = 0; |
| return; |
| } |
| |
| #ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES |
| /* PTP is the system time reference, just add the base time */ |
| |
| ptp_to_timespec(timestamp, &rxtime); |
| clock_timespec_add(&rxtime, &g_stm32_eth_ptp_basetime, |
| &priv->dev.d_rxtime); |
| |
| #else |
| { |
| struct timespec realtime; |
| uint64_t ptptime; |
| irqstate_t flags; |
| |
| /* Sample PTP and CLOCK_REALTIME close to each other */ |
| |
| flags = enter_critical_section(); |
| clock_gettime(CLOCK_REALTIME, &realtime); |
| ptptime = stm32_eth_ptp_gettime(); |
| leave_critical_section(flags); |
| |
| /* Compute how much time has elapsed since packet reception |
| * and add that to current time. |
| */ |
| |
| timestamp = ptptime - timestamp; |
| ptp_to_timespec(timestamp, &rxtime); |
| clock_timespec_add(&rxtime, &realtime, &priv->dev.d_rxtime); |
| } |
| #endif /* CONFIG_STM32_ETH_PTP_RTC_HIRES */ |
| } |
| #endif /* CONFIG_STM32_ETH_TIMESTAMP_RX */ |
| |
| #endif /* CONFIG_STM32_ETH_PTP */ |
| |
| /**************************************************************************** |
| * Function: stm32_ethreset |
| * |
| * Description: |
| * Reset the Ethernet block. |
| * |
| * Input Parameters: |
| * priv - A reference to the private driver state structure |
| * |
| * Returned Value: |
| * Zero on success, or a negated errno value on any failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int stm32_ethreset(struct stm32_ethmac_s *priv) |
| { |
| uint32_t regval; |
| uint32_t retries; |
| |
| /* Reset the Ethernet on the AHB bus (F1 Connectivity Line) or AHB1 bus (F2 |
| * and F4) |
| */ |
| |
| #if defined(CONFIG_STM32_CONNECTIVITYLINE) |
| regval = stm32_getreg(STM32_RCC_AHBRSTR); |
| regval |= RCC_AHBRSTR_ETHMACRST; |
| stm32_putreg(regval, STM32_RCC_AHBRSTR); |
| |
| regval &= ~RCC_AHBRSTR_ETHMACRST; |
| stm32_putreg(regval, STM32_RCC_AHBRSTR); |
| #else |
| regval = stm32_getreg(STM32_RCC_AHB1RSTR); |
| regval |= RCC_AHB1RSTR_ETHMACRST; |
| stm32_putreg(regval, STM32_RCC_AHB1RSTR); |
| |
| regval &= ~RCC_AHB1RSTR_ETHMACRST; |
| stm32_putreg(regval, STM32_RCC_AHB1RSTR); |
| #endif |
| |
| /* Perform a software reset by setting the SR bit in the DMABMR register. |
| * This Resets all MAC subsystem internal registers and logic. After this |
| * reset all the registers holds their reset values. |
| */ |
| |
| regval = stm32_getreg(STM32_ETH_DMABMR); |
| regval |= ETH_DMABMR_SR; |
| stm32_putreg(regval, STM32_ETH_DMABMR); |
| |
| /* Wait for software reset to complete. The SR bit is cleared automatically |
| * after the reset operation has completed in all core clock domains. |
| * Should take at most a few clock ticks of the 50 MHz domain. |
| */ |
| |
| retries = 10; |
| while (((stm32_getreg(STM32_ETH_DMABMR) & ETH_DMABMR_SR) != 0) && |
| retries > 0) |
| { |
| retries--; |
| up_udelay(1); |
| } |
| |
| if (retries == 0) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_macconfig |
| * |
| * Description: |
| * Configure the Ethernet MAC for DMA operation. |
| * |
| * Input 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; |
| |
| /* Set up the MACCR register */ |
| |
| regval = stm32_getreg(STM32_ETH_MACCR); |
| regval &= ~MACCR_CLEAR_BITS; |
| regval |= MACCR_SET_BITS; |
| |
| if (priv->fduplex) |
| { |
| /* Set the DM bit for full duplex support */ |
| |
| regval |= ETH_MACCR_DM; |
| } |
| |
| if (priv->mbps100) |
| { |
| /* Set the FES bit for 100Mbps fast ethernet support */ |
| |
| regval |= ETH_MACCR_FES; |
| } |
| |
| stm32_putreg(regval, STM32_ETH_MACCR); |
| |
| /* Set up the MACFFR register */ |
| |
| regval = stm32_getreg(STM32_ETH_MACFFR); |
| regval &= ~MACFFR_CLEAR_BITS; |
| regval |= MACFFR_SET_BITS; |
| stm32_putreg(regval, STM32_ETH_MACFFR); |
| |
| /* Set up the MACHTHR and MACHTLR registers */ |
| |
| stm32_putreg(0, STM32_ETH_MACHTHR); |
| stm32_putreg(0, STM32_ETH_MACHTLR); |
| |
| /* Setup up the MACFCR register */ |
| |
| regval = stm32_getreg(STM32_ETH_MACFCR); |
| regval &= ~MACFCR_CLEAR_MASK; |
| regval |= MACFCR_SET_MASK; |
| stm32_putreg(regval, STM32_ETH_MACFCR); |
| |
| /* Setup up the MACVLANTR register */ |
| |
| stm32_putreg(0, STM32_ETH_MACVLANTR); |
| |
| /* DMA Configuration */ |
| |
| /* Set up the DMAOMR register */ |
| |
| regval = stm32_getreg(STM32_ETH_DMAOMR); |
| regval &= ~DMAOMR_CLEAR_MASK; |
| regval |= DMAOMR_SET_MASK; |
| stm32_putreg(regval, STM32_ETH_DMAOMR); |
| |
| /* Set up the DMABMR register */ |
| |
| regval = stm32_getreg(STM32_ETH_DMABMR); |
| regval &= ~DMABMR_CLEAR_MASK; |
| regval |= DMABMR_SET_MASK; |
| stm32_putreg(regval, STM32_ETH_DMABMR); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_macaddress |
| * |
| * Description: |
| * Configure the selected MAC address. |
| * |
| * Input 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. |
| * |
| * Input 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. |
| * |
| * Input 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_DMAOMR); |
| regval |= ETH_DMAOMR_FTF; |
| stm32_putreg(regval, STM32_ETH_DMAOMR); |
| |
| /* Enable receive state machine of the MAC for reception from the MII */ |
| |
| /* Enables or disables the MAC reception. */ |
| |
| regval = stm32_getreg(STM32_ETH_MACCR); |
| regval |= ETH_MACCR_RE; |
| stm32_putreg(regval, STM32_ETH_MACCR); |
| |
| /* Start DMA transmission */ |
| |
| regval = stm32_getreg(STM32_ETH_DMAOMR); |
| regval |= ETH_DMAOMR_ST; |
| stm32_putreg(regval, STM32_ETH_DMAOMR); |
| |
| /* Start DMA reception */ |
| |
| regval = stm32_getreg(STM32_ETH_DMAOMR); |
| regval |= ETH_DMAOMR_SR; |
| stm32_putreg(regval, STM32_ETH_DMAOMR); |
| |
| /* Enable Ethernet DMA interrupts. |
| * |
| * The STM32 hardware supports two interrupts: (1) one dedicated to normal |
| * Ethernet operations and the other, used only for the Ethernet wakeup |
| * event. The wake-up interrupt is not used by this driver. |
| * |
| * The first Ethernet vector is reserved for interrupts generated by the |
| * MAC and the DMA. The MAC provides PMT and time stamp trigger interrupts, |
| * neither of which are used by this driver. |
| */ |
| |
| stm32_putreg(ETH_MACIMR_ALLINTS, STM32_ETH_MACIMR); |
| |
| /* Ethernet DMA supports two classes of interrupts: Normal interrupt |
| * summary (NIS) and Abnormal interrupt summary (AIS) with a variety |
| * individual normal and abnormal interrupting events. Here only |
| * the normal receive event is enabled (unless DEBUG is enabled). Transmit |
| * events will only be enabled when a transmit interrupt is expected. |
| */ |
| |
| stm32_putreg(ETH_DMAINT_RECV_ENABLE | ETH_DMAINT_ERROR_ENABLE, |
| STM32_ETH_DMAIER); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: stm32_ethconfig |
| * |
| * Description: |
| * Configure the Ethernet interface for DMA operation. |
| * |
| * Input 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. |
| */ |
| |
| /* Reset the Ethernet block */ |
| |
| ninfo("Reset the Ethernet block\n"); |
| ret = stm32_ethreset(priv); |
| if (ret < 0) |
| { |
| nerr("ERROR: Reset of Ethernet block failed\n"); |
| return ret; |
| } |
| |
| /* Initialize the PHY */ |
| |
| ninfo("Initialize the PHY\n"); |
| ret = stm32_phyinit(priv); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Initialize the MAC and DMA */ |
| |
| ninfo("Initialize the MAC and DMA\n"); |
| ret = stm32_macconfig(priv); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Initialize the free buffer list */ |
| |
| stm32_initbuffer(priv, g_alloc); |
| |
| /* Initialize TX Descriptors list: Chain Mode */ |
| |
| stm32_txdescinit(priv, g_txtable); |
| |
| /* Initialize RX Descriptors list: Chain Mode */ |
| |
| stm32_rxdescinit(priv, g_rxtable, g_rxbuffer); |
| |
| /* 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. |
| * |
| * Input Parameters: |
| * intf - In the case where there are multiple EMACs, this value |
| * identifies which EMAC is to be initialized. |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #if STM32_NETHERNET == 1 || defined(CONFIG_NETDEV_LATEINIT) |
| static inline |
| #endif |
| int stm32_ethinitialize(int intf) |
| { |
| struct stm32_ethmac_s *priv; |
| int ret; |
| |
| ninfo("intf: %d\n", intf); |
| |
| /* Get the interface structure associated with this interface number. */ |
| |
| DEBUGASSERT(intf < STM32_NETHERNET); |
| priv = &g_stm32ethmac[intf]; |
| |
| /* Initialize the driver structure */ |
| |
| memset(priv, 0, sizeof(struct stm32_ethmac_s)); |
| priv->dev.d_ifup = stm32_ifup; /* I/F up (new IP address) callback */ |
| priv->dev.d_ifdown = stm32_ifdown; /* I/F down callback */ |
| priv->dev.d_txavail = stm32_txavail; /* New TX data callback */ |
| #ifdef CONFIG_NET_MCASTGROUP |
| priv->dev.d_addmac = stm32_addmac; /* Add multicast MAC address */ |
| priv->dev.d_rmmac = stm32_rmmac; /* Remove multicast MAC address */ |
| #endif |
| #ifdef CONFIG_NETDEV_IOCTL |
| priv->dev.d_ioctl = stm32_ioctl; /* Support PHY ioctl() calls */ |
| #endif |
| priv->dev.d_private = g_stm32ethmac; /* Used to recover private state from dev */ |
| |
| /* 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; |
| } |
| |
| /* Put the interface in the down state. */ |
| |
| ret = stm32_ifdown(&priv->dev); |
| if (ret < 0) |
| { |
| nerr("ERROR: Initialization of Ethernet block failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Register the device with the OS so that socket IOCTLs can be performed */ |
| |
| netdev_register(&priv->dev, NET_LL_ETHERNET); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: arm_netinitialize |
| * |
| * Description: |
| * This is the "standard" network initialization logic called from the |
| * low-level initialization logic in arm_initialize.c. If STM32_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. |
| * |
| * Input Parameters: |
| * None. |
| * |
| * Returned Value: |
| * None. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #if STM32_NETHERNET == 1 && !defined(CONFIG_NETDEV_LATEINIT) |
| void arm_netinitialize(void) |
| { |
| stm32_ethinitialize(0); |
| } |
| #endif |
| |
| #ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES |
| |
| /**************************************************************************** |
| * Name: up_rtc_initialize |
| * |
| * Description: |
| * Initialize the builtin, MCU hardware RTC per the selected |
| * configuration. This function is called once very early in the OS |
| * initialization sequence. |
| * |
| * NOTE that initialization of external RTC hardware that depends on the |
| * availability of OS resources (such as SPI or I2C) must be deferred |
| * until the system has fully booted. Other, RTC-specific initialization |
| * functions are used in that case. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| int up_rtc_initialize(void) |
| { |
| /* Nothing to do, the PTP RTC is not available until Ethernet peripheral |
| * is enabled. |
| */ |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: up_rtc_gettime |
| * |
| * Description: |
| * Get the current time from the high resolution RTC clock/counter. This |
| * interface is only supported by the high-resolution RTC/counter hardware |
| * implementation. |
| * It is used to replace the system timer. |
| * |
| * Input Parameters: |
| * tp - The location to return the high resolution time value. |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| int up_rtc_gettime(FAR struct timespec *tp) |
| { |
| uint64_t timestamp; |
| timestamp = stm32_eth_ptp_gettime(); |
| |
| if (timestamp == 0) |
| { |
| /* PTP timer is not initialized yet. |
| * Normally we shouldn't end up here because g_rtc_enabled is false. |
| */ |
| |
| DEBUGASSERT(!g_rtc_enabled); |
| return -EBUSY; |
| } |
| |
| ptp_to_timespec(timestamp, tp); |
| clock_timespec_add(tp, &g_stm32_eth_ptp_basetime, tp); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: up_rtc_settime |
| * |
| * Description: |
| * Set the RTC to the provided time. All RTC implementations must be able |
| * to set their time based on a standard timespec. |
| * |
| * Input Parameters: |
| * tp - the time to use |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| int up_rtc_settime(FAR const struct timespec *tp) |
| { |
| struct timespec ptptime; |
| uint64_t timestamp; |
| timestamp = stm32_eth_ptp_gettime(); |
| |
| if (timestamp == 0) |
| { |
| /* PTP timer is not initialized yet. |
| * Normally we shouldn't end up here because g_rtc_enabled is false. |
| */ |
| |
| DEBUGASSERT(!g_rtc_enabled); |
| return -EBUSY; |
| } |
| |
| /* Compute new basetime to get from PTP timestamp to wall clock time. |
| * We keep the PTP timer 0-based to avoid 32-bit seconds count |
| * overflow issues. |
| */ |
| |
| ptp_to_timespec(timestamp, &ptptime); |
| clock_timespec_subtract(tp, &ptptime, &g_stm32_eth_ptp_basetime); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: up_rtc_adjtime |
| * |
| * Description: |
| * Adjust RTC frequency (running rate). Used by adjtime() when RTC is used |
| * as system time source. |
| * |
| * Input Parameters: |
| * ppb - Adjustment in parts per billion (nanoseconds per second). |
| * Zero is default rate, positive value makes clock run faster |
| * and negative value slower. |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno value on failure. |
| * |
| * Assumptions: |
| * Called from within a critical section. |
| ****************************************************************************/ |
| |
| int up_rtc_adjtime(long ppb) |
| { |
| return stm32_eth_ptp_adjust(ppb); |
| } |
| |
| #endif /* CONFIG_STM32_ETH_PTP_RTC_HIRES */ |
| |
| #endif /* STM32_NETHERNET > 0 */ |
| #endif /* CONFIG_NET && CONFIG_STM32_ETHMAC */ |