/**************************************************************************** * arch/arm/src/stm32h7/stm32_ethernet.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_NET_PKT) # include #endif #include #include "arm_internal.h" #include "barriers.h" #include "hardware/stm32_syscfg.h" #include "hardware/stm32_pinmap.h" #include "stm32_gpio.h" #include "stm32_rcc.h" #include "stm32_ethernet.h" #include "stm32_uid.h" #include #include "stm32_multi_board.h" /* STM32H7_NETHERNET determines the number of physical interfaces that can * be supported by the hardware. CONFIG_STM32H7_ETHMAC will defined if * any STM32H7 Ethernet support is enabled in the configuration. */ #if STM32H7_NETHERNET > 0 && defined(CONFIG_STM32H7_ETHMAC) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Memory synchronization */ #define MEMORY_SYNC() do { ARM_DSB(); ARM_ISB(); } while (0) /* Configuration ************************************************************/ /* See boards/arm/stm32/stm3240g-eval/README.txt for an explanation of the * configuration settings. */ #if STM32H7_NETHERNET > 1 # error "Logic to support multiple Ethernet interfaces is incomplete" #endif /* If processing is not done at the interrupt level, then work queue support * is required. */ #if !defined(CONFIG_SCHED_WORKQUEUE) # error Work queue support is required #else /* Select work queue */ # if defined(CONFIG_STM32H7_ETHMAC_HPWORK) # define ETHWORK HPWORK # elif defined(CONFIG_STM32H7_ETHMAC_LPWORK) # define ETHWORK LPWORK # else # define ETHWORK LPWORK # endif #endif #ifndef CONFIG_STM32H7_PHYADDR # error "CONFIG_STM32H7_PHYADDR must be defined in the NuttX configuration" #endif #if !defined(CONFIG_STM32H7_MII) && !defined(CONFIG_STM32H7_RMII) # warning "Neither CONFIG_STM32H7_MII nor CONFIG_STM32H7_RMII defined" #endif #if defined(CONFIG_STM32H7_MII) && defined(CONFIG_STM32H7_RMII) # error "Both CONFIG_STM32H7_MII and CONFIG_STM32H7_RMII defined" #endif #ifdef CONFIG_STM32H7_MII # if !defined(CONFIG_STM32H7_MII_MCO1) && !defined(CONFIG_STM32H7_MII_MCO2) && \ !defined(CONFIG_STM32H7_MII_EXTCLK) # warning "Neither CONFIG_STM32H7_MII_MCO1, CONFIG_STM32H7_MII_MCO2, nor CONFIG_STM32H7_MII_EXTCLK defined" # endif # if defined(CONFIG_STM32H7_MII_MCO1) && defined(CONFIG_STM32H7_MII_MCO2) # error "Both CONFIG_STM32H7_MII_MCO1 and CONFIG_STM32H7_MII_MCO2 defined" # endif #endif #ifdef CONFIG_STM32H7_RMII # if !defined(CONFIG_STM32H7_RMII_MCO1) && !defined(CONFIG_STM32H7_RMII_MCO2) && \ !defined(CONFIG_STM32H7_RMII_EXTCLK) # warning "Neither CONFIG_STM32H7_RMII_MCO1, CONFIG_STM32H7_RMII_MCO2, nor CONFIG_STM32H7_RMII_EXTCLK defined" # endif # if defined(CONFIG_STM32H7_RMII_MCO1) && defined(CONFIG_STM32H7_RMII_MCO2) # error "Both CONFIG_STM32H7_RMII_MCO1 and CONFIG_STM32H7_RMII_MCO2 defined" # endif #endif #ifdef CONFIG_STM32H7_AUTONEG # ifndef CONFIG_STM32H7_PHYSR # error "CONFIG_STM32H7_PHYSR must be defined in the NuttX configuration" # endif # ifdef CONFIG_STM32H7_PHYSR_ALTCONFIG # ifndef CONFIG_STM32H7_PHYSR_ALTMODE # error "CONFIG_STM32H7_PHYSR_ALTMODE must be defined in the NuttX configuration" # endif # ifndef CONFIG_STM32H7_PHYSR_10HD # error "CONFIG_STM32H7_PHYSR_10HD must be defined in the NuttX configuration" # endif # ifndef CONFIG_STM32H7_PHYSR_100HD # error "CONFIG_STM32H7_PHYSR_100HD must be defined in the NuttX configuration" # endif # ifndef CONFIG_STM32H7_PHYSR_10FD # error "CONFIG_STM32H7_PHYSR_10FD must be defined in the NuttX configuration" # endif # ifndef CONFIG_STM32H7_PHYSR_100FD # error "CONFIG_STM32H7_PHYSR_100FD must be defined in the NuttX configuration" # endif # else # ifndef CONFIG_STM32H7_PHYSR_SPEED # error "CONFIG_STM32H7_PHYSR_SPEED must be defined in the NuttX configuration" # endif # ifndef CONFIG_STM32H7_PHYSR_100MBPS # error "CONFIG_STM32H7_PHYSR_100MBPS must be defined in the NuttX configuration" # endif # ifndef CONFIG_STM32H7_PHYSR_MODE # error "CONFIG_STM32H7_PHYSR_MODE must be defined in the NuttX configuration" # endif # ifndef CONFIG_STM32H7_PHYSR_FULLDUPLEX # error "CONFIG_STM32H7_PHYSR_FULLDUPLEX must be defined in the NuttX configuration" # endif # endif #endif #ifdef CONFIG_STM32H7_ETH_PTP # warning "CONFIG_STM32H7_ETH_PTP is not yet supported" #endif #undef CONFIG_STM32H7_ETH_HWCHECKSUM /* Add 4 to the configured buffer size to account for the 2 byte checksum * memory needed at the end of the maximum size packet. Buffer sizes must * be an even multiple of 4, 8, or 16 bytes (depending on buswidth). We * will use the 16-byte alignment in all cases. */ #define OPTIMAL_ETH_BUFSIZE ((CONFIG_NET_ETH_PKTSIZE + 4 + 15) & ~15) #ifdef CONFIG_STM32H7_ETH_BUFSIZE # define ETH_BUFSIZE CONFIG_STM32H7_ETH_BUFSIZE #else # define ETH_BUFSIZE OPTIMAL_ETH_BUFSIZE #endif #if ETH_BUFSIZE > ETH_CTX_TDES2_MSS_MASK # error "ETH_BUFSIZE is too large" #endif #if (ETH_BUFSIZE & 15) != 0 # error "ETH_BUFSIZE must be aligned" #endif #if ETH_BUFSIZE != OPTIMAL_ETH_BUFSIZE # warning "You are using an incomplete/untested configuration" #endif #ifndef CONFIG_STM32H7_ETH_NRXDESC # define CONFIG_STM32H7_ETH_NRXDESC 8 #endif #ifndef CONFIG_STM32H7_ETH_NTXDESC # define CONFIG_STM32H7_ETH_NTXDESC 4 #endif /* We need at least one more free buffer than transmit buffers */ #define STM32_ETH_NFREEBUFFERS (CONFIG_STM32H7_ETH_NTXDESC+1) /* Buffers used for DMA access must begin on an address aligned with the * D-Cache line and must be an even multiple of the D-Cache line size. * These size/alignment requirements are necessary so that D-Cache flush * and invalidate operations will not have any additional effects. * * The TX and RX descriptors are 16 bytes in size */ #define DMA_BUFFER_MASK (ARMV7M_DCACHE_LINESIZE - 1) #define DMA_ALIGN_UP(n) (((n) + DMA_BUFFER_MASK) & ~DMA_BUFFER_MASK) #define DMA_ALIGN_DOWN(n) ((n) & ~DMA_BUFFER_MASK) #define DESC_SIZE 16 #define DESC_PADSIZE DMA_ALIGN_UP(DESC_SIZE) #define ALIGNED_BUFSIZE DMA_ALIGN_UP(ETH_BUFSIZE) #define RXTABLE_SIZE (STM32H7_NETHERNET * CONFIG_STM32H7_ETH_NRXDESC) #define TXTABLE_SIZE (STM32H7_NETHERNET * CONFIG_STM32H7_ETH_NTXDESC) #define RXBUFFER_SIZE (CONFIG_STM32H7_ETH_NRXDESC * ALIGNED_BUFSIZE) #define RXBUFFER_ALLOC (STM32H7_NETHERNET * RXBUFFER_SIZE) #define TXBUFFER_SIZE (STM32_ETH_NFREEBUFFERS * ALIGNED_BUFSIZE) #define TXBUFFER_ALLOC (STM32H7_NETHERNET * TXBUFFER_SIZE) /* Extremely detailed register debug that you would normally never want * enabled. */ #ifndef CONFIG_DEBUG_NET_INFO # undef CONFIG_STM32H7_ETHMAC_REGDEBUG #endif /* Clocking *****************************************************************/ /* Set MACMDIOAR CR bits depending on HCLK setting */ #if STM32_HCLK_FREQUENCY >= 20000000 && STM32_HCLK_FREQUENCY < 35000000 # define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV16 #elif STM32_HCLK_FREQUENCY >= 35000000 && STM32_HCLK_FREQUENCY < 60000000 # define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV26 #elif STM32_HCLK_FREQUENCY >= 60000000 && STM32_HCLK_FREQUENCY < 100000000 # define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV42 #elif STM32_HCLK_FREQUENCY >= 100000000 && STM32_HCLK_FREQUENCY < 150000000 # define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV62 #elif STM32_HCLK_FREQUENCY >= 150000000 && STM32_HCLK_FREQUENCY <= 250000000 # define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV102 #elif STM32_HCLK_FREQUENCY >= 250000000 && STM32_HCLK_FREQUENCY <= 300000000 # define ETH_MACMDIOAR_CR ETH_MACMDIOAR_CR_DIV124 #else # error "STM32_HCLK_FREQUENCY not supportable" #endif /* Timing *******************************************************************/ /* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per * second */ #define STM32_WDDELAY (0.25*CLK_TCK) /* TX timeout = 1 minute */ #define STM32_TXTIMEOUT (60*CLK_TCK) /* PHY reset/configuration delays in milliseconds */ #define PHY_RESET_DELAY (65) #define PHY_CONFIG_DELAY (1000) /* PHY read/write delays in loop counts */ #define PHY_READ_TIMEOUT (0x0004ffff) #define PHY_WRITE_TIMEOUT (0x0004ffff) #define PHY_RETRY_TIMEOUT (0x0004ffff) #define MAC_READY_USTIMEOUT (200) /* Register values **********************************************************/ /* Clear the MACCR bits that will be setup during MAC initialization (or that * are cleared unconditionally). Per the reference manual, all reserved bits * must be retained at their reset value. * * ETH_MACCR_RE Bit 0: Receiver enable * ETH_MACCR_TE Bit 1: Transmitter enable * ETH_MACCR_PRELEN Bits 2-3: Preamble length for transmit packets * ETH_MACCR_DC Bit 4: Deferral check * ETH_MACCR_BL Bits 5-6: Back-off limit * ETH_MACCR_DR Bit 8: Retry disable * ETH_MACCR_DCRS Bit 9: Carrier sense disable * ETH_MACCR_DO Bit 10: Receive own disable * ETH_MACCR_LM Bit 12: Loopback mode * ETH_MACCR_DM Bit 13: Duplex mode * ETH_MACCR_FES Bit 14: Fast Ethernet speed * ETH_MACCR_JE Bit 16: Jumbo packet enable * ETH_MACCR_JD Bit 17: Jabber disable * ETH_MACCR_WD Bit 19: Watchdog disable * ETH_MACCR_ACS Bit 20: Automatic pad/CRC stripping * ETH_MACCR_CST Bit 21: CRC stripping for Type frames * ETH_MACCR_S2KP Bit 22: IEEE 802.3as Support for 2K Packets * ETH_MACCR_GPSLCE Bit 23: Giant Packet Size Limit Control Enable * ETH_MACCR_IPG Bits 24-26: Inter-packet gap * ETH_MACCR_IPC Bit 27: IPv4 checksum offload * ETH_MACCR_SARC Bits 28-30: Src address insertion or replacement * ETH_MACCR_ARPEN Bit 31: ARP Offload Enable */ #define MACCR_CLEAR_BITS \ (ETH_MACCR_RE | ETH_MACCR_TE | ETH_MACCR_PRELEN_MASK | ETH_MACCR_DC | \ ETH_MACCR_BL_MASK | ETH_MACCR_DR | ETH_MACCR_DCRS | ETH_MACCR_DO | \ ETH_MACCR_LM | ETH_MACCR_DM | ETH_MACCR_FES | ETH_MACCR_JE | \ ETH_MACCR_JD | ETH_MACCR_WD | ETH_MACCR_ACS | ETH_MACCR_CST | \ ETH_MACCR_S2KP | ETH_MACCR_GPSLCE | ETH_MACCR_IPG_MASK | \ ETH_MACCR_IPC | ETH_MACCR_SARC_MASK | ETH_MACCR_ARPEN) /* The following bits are set or left zero unconditionally in all modes. * * ETH_MACCR_RE Receiver enable 0 (disabled) * ETH_MACCR_TE Transmitter enable 0 (disabled) * ETH_MACCR_DC Deferral check 0 (disabled) * ETH_MACCR_BL Back-off limit 0 (10) * ETH_MACCR_ACS Automatic pad/CRC stripping 0 (disabled) * ETH_MACCR_DR Retry disable 1 (disabled) * ETH_MACCR_IPC IPv4 checksum offload * Depends on CONFIG_STM32H7_ETH_HWCHECKSUM * ETH_MACCR_LM Loopback mode 0 (disabled) * ETH_MACCR_DO Receive own disable 0 (enabled) * ETH_MACCR_DCRS Carrier sense disable 0 (enabled) * ETH_MACCR_IPG Inter-packet gap 0 (96 bits) * ETH_MACCR_JD Jabber disable 0 (enabled) * ETH_MACCR_WD Watchdog disable 0 (enabled) * ETH_MACCR_CST CRC stripping for Type frames 0 (disabled) * * The following are set conditionally based on mode and speed. * * ETH_MACCR_DM Duplex mode Depends on priv->fduplex * ETH_MACCR_FES Fast Ethernet speed Depends on priv->mbps100 */ #ifdef CONFIG_STM32H7_ETH_HWCHECKSUM # define MACCR_SET_BITS \ (ETH_MACCR_BL_10 | ETH_MACCR_DR | ETH_MACCR_IPC | ETH_MACCR_IPG(96)) #else # define MACCR_SET_BITS \ (ETH_MACCR_BL_10 | ETH_MACCR_DR | ETH_MACCR_IPG(96)) #endif /* Clear the MACPFR bits that will be setup during MAC initialization (or * that are cleared unconditionally). Per the reference manual, all reserved * bits must be retained at their reset value. * * ETH_MACPFR_PR Bit 0: Promiscuous mode * ETH_MACPFR_HUC Bit 1: Hash unicast * ETH_MACPFR_HMC Bit 2: Hash multicast * ETH_MACPFR_DAIF Bit 3: Destination address inverse filtering * ETH_MACPFR_PM Bit 4: Pass all multicast * ETH_MACPFR_DBF Bit 5: Broadcast frames disable * ETH_MACPFR_PCF Bits 6-7: Pass control frames * ETH_MACPFR_SAIF Bit 8: Source address inverse filtering * ETH_MACPFR_SAF Bit 9: Source address filter * ETH_MACPFR_HPF Bit 10: Hash or perfect filter * ETH_MACPFR_VTFE Bit 16: VLAN Tag Filter Enable * ETH_MACPFR_IPFE Bit 20: Layer 3 and Layer 4 Filter Enable * ETH_MACPFR_DNTU Bit 21: Drop Non-TCP/UDP over IP Packets * ETH_MACPFR_RA Bit 31: Receive all */ #define MACPFR_CLEAR_BITS \ (ETH_MACPFR_PR | ETH_MACPFR_HUC | ETH_MACPFR_HMC | ETH_MACPFR_DAIF | \ ETH_MACPFR_PM | ETH_MACPFR_DBF | ETH_MACPFR_PCF_MASK | ETH_MACPFR_SAIF | \ ETH_MACPFR_SAF | ETH_MACPFR_HPF | ETH_MACPFR_VTFE | ETH_MACPFR_IPFE | \ ETH_MACPFR_DNTU | ETH_MACPFR_RA) /* The following bits are set or left zero unconditionally in all modes. * * ETH_MACPFR_PR Promiscuous mode 0 (disabled) * ETH_MACPFR_HUC Hash unicast 0 (perfect * dest filtering) * ETH_MACPFR_HMC Hash multicast 0 (perfect * dest filtering) * ETH_MACPFR_DAIF Destination address inverse filtering 0 (normal) * ETH_MACPFR_PM Pass all multicast 0 (Depends on HMC * bit) * ETH_MACPFR_DBF Broadcast frames disable 0 (enabled) * ETH_MACPFR_PCF Pass control frames 1 (block all but * PAUSE) * ETH_MACPFR_SAIF Source address inverse filtering 0 (not used) * ETH_MACPFR_SAF Source address filter 0 (disabled) * ETH_MACPFR_HPF Hash or perfect filter 0 (Only matching * frames passed) * ETH_MACPFR_RA Receive all 0 (disabled) */ #define MACPFR_SET_BITS (ETH_MACPFR_PCF_PAUSE) /* Clear the MACQTXFCR and MACRXFCR bits that will be setup during MAC * initialization (or that are cleared unconditionally). Per the reference * manual, all reserved bits must be retained at their reset value. * * ETH_MACQTXFCR_FCB_BPA Bit 0: Flow control busy/back pressure activate * ETH_MACQTXFCR_TFE Bit 1: Transmit flow control enable * ETH_MACQTXFCR_PLT Bits 4-6: Pause low threshold * ETH_MACQTXFCR_DZPQ Bit 7: Zero-quanta pause disable * ETH_MACQTXFCR_PT Bits 16-31: Pause time * ETH_MACRXFCR_RFE Bit 0: Receive flow control enable * ETH_MACRXFCR_UP Bit 1: Unicast pause frame detect */ #define MACQTXFCR_CLEAR_MASK \ (ETH_MACQTXFCR_FCB_BPA | ETH_MACQTXFCR_TFE | \ ETH_MACQTXFCR_PLT_MASK | ETH_MACQTXFCR_DZPQ | ETH_MACQTXFCR_PT_MASK) #define MACRXFCR_CLEAR_MASK \ (ETH_MACRXFCR_RFE | ETH_MACRXFCR_UP) /* The following bits are set or left zero unconditionally in all modes. * * ETH_MACQTXFCR_FCB_BPA Flow control busy/back pressure activate 0 * (no pause control frame) * ETH_MACQTXFCR_TFE Transmit flow control enable 0 * (disabled) * ETH_MACQTXFCR_PLT Pause low threshold 0 * (pause time - 4) * ETH_MACQTXFCR_DZPQ Zero-quanta pause disable 1 * (disabled) * ETH_MACQTXFCR_PT Pause time 0 * ETH_MACRXFCR_RFE Receive flow control enable 0 * (disabled) * ETH_MACRXFCR_UP Unicast pause frame detect 0 * (disabled) */ #define MACQTXFCR_SET_MASK (ETH_MACQTXFCR_PLT_M4 | ETH_MACQTXFCR_DZPQ) #define MACRXFCR_SET_MASK (0) /* Clear the MTLTXQOMR bits that will be setup during MAC initialization * (or that are cleared unconditionally). Per the reference manual, all * reserved bits must be retained at their reset value. * ETH_MTLTXQOMR_FTQ Bit 0: Flush Transmit Queue * ETH_MTLTXQOMR_TSF Bit 1: Transmit Store and Forward * ETH_MTLTXQOMR_TXQEN Bits 2-3: Transmit Queue Enable * ETH_MTLTXQOMR_TTC Bits 4-6: Transmit Threshold Control * ETH_MTLTXQOMR_TQS Bits 16-24: Transmit Queue Size */ #define MTLTXQOMR_CLEAR_MASK \ (ETH_MTLTXQOMR_FTQ | ETH_MTLTXQOMR_TSF | \ ETH_MTLTXQOMR_TXQEN_MASK | ETH_MTLTXQOMR_TTC_MASK | \ ETH_MTLTXQOMR_TQS_MASK) /* Clear the MTLRXQOMR bits that will be setup during MAC initialization * (or that are cleared unconditionally). Per the reference manual, all * reserved bits must be retained at their reset value. * * ETH_MTLRXQOMR_RTC_MASK Bits 0-1: Receive Queue Threshold Control * ETH_MTLRXQOMR_FUP Bit 3: Forward Undersized Good Packets * ETH_MTLRXQOMR_FEP Bit 4: Forward Error Packets * ETH_MTLRXQOMR_RSF Bit 5: Receive Queue Store and Forward * ETH_MTLRXQOMR_DIS_TCP_EF Bit 6: Disable Dropping of TCP/IP Checksum Error * Packets * ETH_MTLRXQOMR_EHFC Bit 7: Enable Hardware Flow Control * ETH_MTLRXQOMR_RFA_MASK Bits 8-10: Threshold for Activating Flow Control * ETH_MTLRXQOMR_RFD_MASK Bits 14-16: Threshold for Deactivating Flow * Control * ETH_MTLRXQOMR_RQS_MASK Bits 20-22: Receive Queue Size */ #define MTLRXQOMR_CLEAR_MASK \ (ETH_MTLRXQOMR_RTC_MASK | ETH_MTLRXQOMR_FUP | \ ETH_MTLRXQOMR_FEP | ETH_MTLRXQOMR_RSF | \ ETH_MTLRXQOMR_DIS_TCP_EF | ETH_MTLRXQOMR_EHFC | \ ETH_MTLRXQOMR_RFA_MASK | ETH_MTLRXQOMR_RFD_MASK | \ ETH_MTLRXQOMR_RQS_MASK) #define MTLTXQOMR_SET_MASK \ ((0xd << ETH_MTLTXQOMR_TQS_SHIFT) | ETH_MTLTXQOMR_TTC_64 | \ ETH_MTLTXQOMR_TXQEN_ENABLED | ETH_MTLTXQOMR_FTQ) /* TODO: Check whether use flow control (bits RFD, RFA EHFC) */ #define MTLRXQOMR_SET_MASK \ ((0x7 << ETH_MTLRXQOMR_RQS_SHIFT) | ETH_MTLRXQOMR_RTC_64) #ifdef CONFIG_STM32H7_ETH_HWCHECKSUM /* TODO */ # error CONFIG_STM32H7_ETH_HWCHECKSUM not supported #endif /* Clear the DMAMR bits that will be setup during MAC initialization (or that * are cleared unconditionally). Per the reference manual, all reserved bits * must be retained at their reset value. * * ETH_DMAMR_SWR Bit 0: Software Reset * ETH_DMAMR_DA Bit 1: DMA Tx or Rx Arbitration Scheme * ETH_DMAMR_TXPR Bit 11: Transmit priority * ETH_DMAMR_PR Bits 12-14: Priority ratio * ETH_DMAMR_INTM Bits 16-17: Interrupt Mode */ #define DMAMR_CLEAR_MASK \ (ETH_DMAMR_SWR | ETH_DMAMR_DA | ETH_DMAMR_TXPR | ETH_DMAMR_PR_MASK | \ ETH_DMAMR_INTM_MASK) #define DMAMR_SET_MASK (ETH_DMAMR_PR_2TO1) /* TODO: Comment these bits */ #define DMACCR_CLEAR_MASK \ ( ETH_DMACCR_MSS_MASK | ETH_DMACCR_PBLX8 | ETH_DMACCR_DSL_MASK) /* Set the descriptor skip length in 32-bit words */ #define DMACCR_SET_MASK \ (ETH_DMACCR_DSL((DESC_PADSIZE - DESC_SIZE)/4)) #define DMASBMR_CLEAR_MASK \ (STM32_ETH_DMASBMR_FB | STM32_ETH_DMASBMR_AAL | \ STM32_ETH_DMASBMR_MB | STM32_ETH_DMASBMR_RB) #define DMASBMR_SET_MASK \ (STM32_ETH_DMASBMR_FB | STM32_ETH_DMASBMR_AAL) #define DMACTXCR_CLEAR_MASK \ (ETH_DMACTXCR_ST | ETH_DMACTXCR_ST | ETH_DMACTXCR_OSF | \ ETH_DMACTXCR_TSE | ETH_DMACTXCR_TXPBL_MASK) #define DMACTXCR_SET_MASK \ (ETH_DMACTXCR_OSF | ETH_DMACTXCR_TXPBL(32)) #define DMACRXCR_CLEAR_MASK \ (ETH_DMACRXCR_SR | ETH_DMACRXCR_RBSZ_MASK | \ ETH_DMACRXCR_RXPBL_MASK | ETH_DMACRXCR_RPF) #define DMACRXCR_SET_MASK \ (ETH_DMACRXCR_RBSZ(ALIGNED_BUFSIZE) | ETH_DMACRXCR_RXPBL(32)) /* Interrupt bit sets *******************************************************/ /* All interrupts in the normal and abnormal interrupt summary. Early * transmit interrupt (ETI) is excluded from the abnormal set because it * causes too many interrupts and is not interesting. */ #define ETH_DMAINT_NORMAL \ (ETH_DMACIER_TIE | ETH_DMACIER_TBUE | ETH_DMACIER_RIE | ETH_DMACIER_ERIE) #define ETH_DMAINT_ABNORMAL \ (ETH_DMACIER_TXSE | ETH_DMACIER_RBUE | ETH_DMACIER_RSE | \ ETH_DMACIER_RWTE | /* ETH_DMACIER_ETIE | */ ETH_DMACIER_FBEE) /* Normal receive, transmit, error interrupt enable bit sets */ #define ETH_DMAINT_RECV_ENABLE (ETH_DMACIER_NIE | ETH_DMACIER_RIE) #define ETH_DMAINT_XMIT_ENABLE (ETH_DMACIER_NIE | ETH_DMACIER_TIE) #define ETH_DMAINT_XMIT_DISABLE (ETH_DMACIER_TIE) #ifdef CONFIG_DEBUG_NET # define ETH_DMAINT_ERROR_ENABLE (ETH_DMACIER_AIE | ETH_DMAINT_ABNORMAL) #else # define ETH_DMAINT_ERROR_ENABLE (0) #endif /* Helpers ******************************************************************/ /* This is a helper pointer for accessing the contents of the Ethernet * header */ #define BUF ((struct eth_hdr_s *)priv->dev.d_buf) /**************************************************************************** * Private Types ****************************************************************************/ /* This union type forces the allocated size of TX&RX descriptors to be the * padded to a exact multiple of the Cortex-M7 D-Cache line size. */ union stm32_desc_u { uint8_t pad[DESC_PADSIZE]; struct eth_desc_s desc; }; /* The stm32_ethmac_s encapsulates all state information for a single * hardware interface */ struct stm32_ethmac_s { uint8_t ifup : 1; /* true:ifup false:ifdown */ uint8_t mbps100 : 1; /* 100MBps operation (vs 10 MBps) */ uint8_t fduplex : 1; /* Full (vs. half) duplex */ uint8_t intf; /* Ethernet interface number */ WDOG_ID txpoll; /* TX poll timer */ WDOG_ID txtimeout; /* TX timeout timer */ struct work_s irqwork; /* For deferring interrupt work to the work queue */ struct work_s pollwork; /* For deferring poll work to the work queue */ /* This holds the information visible to the NuttX network */ struct net_driver_s dev; /* Interface understood by the network */ /* Used to track transmit and receive descriptors */ struct eth_desc_s *txhead; /* Next available TX descriptor */ struct eth_desc_s *rxhead; /* Next available RX descriptor */ struct eth_desc_s *txchbase; /* TX descriptor ring base address */ struct eth_desc_s *rxchbase; /* RX descriptor ring base address */ struct eth_desc_s *txtail; /* First "in_flight" TX descriptor */ struct eth_desc_s *rxcurr; /* First RX descriptor of the segment */ uint16_t segments; /* RX segment count */ uint16_t inflight; /* Number of TX transfers "in_flight" */ sq_queue_t freeb; /* The free buffer list */ }; /**************************************************************************** * Private Data ****************************************************************************/ /* DMA buffers. DMA buffers must: * * 1. Be a multiple of the D-Cache line size. This requirement is assured * by the definition of RXDMA buffer size above. * 2. Be aligned a D-Cache line boundaries, and * 3. Be positioned in DMA-able memory. This must be managed by logic * in the linker script file. * * These DMA buffers are defined sequentially here to best assure optimal * packing of the buffers. */ /* Descriptor allocations */ static union stm32_desc_u g_rxtable[RXTABLE_SIZE] __attribute__((aligned(ARMV7M_DCACHE_LINESIZE))); static union stm32_desc_u g_txtable[TXTABLE_SIZE] __attribute__((aligned(ARMV7M_DCACHE_LINESIZE))); /* Buffer allocations */ static uint8_t g_rxbuffer[RXBUFFER_ALLOC] __attribute__((aligned(ARMV7M_DCACHE_LINESIZE))); static uint8_t g_txbuffer[TXBUFFER_ALLOC] __attribute__((aligned(ARMV7M_DCACHE_LINESIZE))); /* These are the pre-allocated Ethernet device structures */ static struct stm32_ethmac_s g_stm32ethmac[STM32H7_NETHERNET]; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Register operations ******************************************************/ #ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG static uint32_t stm32_getreg(uint32_t addr); static void stm32_putreg(uint32_t val, uint32_t addr); static void stm32_checksetup(void); #else # define stm32_getreg(addr) getreg32(addr) # define stm32_putreg(val,addr) putreg32(val,addr) # define stm32_checksetup() #endif /* Free buffer management */ static void stm32_initbuffer(struct stm32_ethmac_s *priv, uint8_t *txbuffer); static inline uint8_t *stm32_allocbuffer(struct stm32_ethmac_s *priv); static inline void stm32_freebuffer(struct stm32_ethmac_s *priv, uint8_t *buffer); static inline bool stm32_isfreebuffer(struct stm32_ethmac_s *priv); /* Common TX logic */ static int stm32_transmit(struct stm32_ethmac_s *priv); static int stm32_txpoll(struct net_driver_s *dev); static void stm32_dopoll(struct stm32_ethmac_s *priv); /* Interrupt handling */ static void stm32_enableint(struct stm32_ethmac_s *priv, uint32_t ierbit); static void stm32_disableint(struct stm32_ethmac_s *priv, uint32_t ierbit); static void stm32_freesegment(struct stm32_ethmac_s *priv, struct eth_desc_s *rxfirst, int segments); static int stm32_recvframe(struct stm32_ethmac_s *priv); static void stm32_receive(struct stm32_ethmac_s *priv); static void stm32_freeframe(struct stm32_ethmac_s *priv); static void stm32_txdone(struct stm32_ethmac_s *priv); static void stm32_interrupt_work(void *arg); static int stm32_interrupt(int irq, void *context, FAR void *arg); /* Watchdog timer expirations */ static void stm32_txtimeout_work(void *arg); static void stm32_txtimeout_expiry(int argc, uint32_t arg, ...); static void stm32_poll_work(void *arg); static void stm32_poll_expiry(int argc, uint32_t arg, ...); /* NuttX callback functions */ static int stm32_ifup(struct net_driver_s *dev); static int stm32_ifdown(struct net_driver_s *dev); static void stm32_txavail_work(void *arg); static int stm32_txavail(struct net_driver_s *dev); #if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6) static int stm32_addmac(struct net_driver_s *dev, const uint8_t *mac); #endif #ifdef CONFIG_NET_IGMP static int stm32_rmmac(struct net_driver_s *dev, const uint8_t *mac); #endif #ifdef CONFIG_NETDEV_PHY_IOCTL static int stm32_ioctl(struct net_driver_s *dev, int cmd, unsigned long arg); #endif /* Descriptor Initialization */ static void stm32_txdescinit(struct stm32_ethmac_s *priv, union stm32_desc_u *txtable); static void stm32_rxdescinit(struct stm32_ethmac_s *priv, union stm32_desc_u *rxtable, uint8_t *rxbuffer); /* PHY Initialization */ #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) static int stm32_phyintenable(struct stm32_ethmac_s *priv); #endif static int stm32_phyread(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t *value); static int stm32_phywrite(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t value, uint16_t mask); #ifdef CONFIG_ETH0_PHY_DM9161 static inline int stm32_dm9161(struct stm32_ethmac_s *priv); #endif static int stm32_phyinit(struct stm32_ethmac_s *priv); #ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG static void stm32_phyregdump(void); #endif /* MAC/DMA Initialization */ #ifdef CONFIG_STM32H7_MII static inline void stm32_selectmii(void); #endif #ifdef CONFIG_STM32H7_RMII static inline void stm32_selectrmii(void); #endif static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv); static void stm32_ethreset(long swrBitTimeout); static int stm32_macconfig(struct stm32_ethmac_s *priv); static void stm32_macaddress(struct stm32_ethmac_s *priv); #ifdef CONFIG_NET_ICMPv6 static void stm32_ipv6multicast(struct stm32_ethmac_s *priv); #endif static int stm32_macenable(struct stm32_ethmac_s *priv); static int stm32_ethconfig(struct stm32_ethmac_s *priv); /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: stm32_getreg * * Description: * This function may to used to intercept an monitor all register accesses. * Clearly this is nothing you would want to do unless you are debugging * this driver. * * Input Parameters: * addr - The register address to read * * Returned Value: * The value read from the register * ****************************************************************************/ #ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG static uint32_t stm32_getreg(uint32_t addr) { static uint32_t prevaddr = 0; static uint32_t preval = 0; static uint32_t count = 0; /* Read the value from the register */ uint32_t val = getreg32(addr); /* Is this the same value that we read from the same register last time? * Are we polling the register? If so, suppress some of the output. */ if (addr == prevaddr && val == preval) { if (count == 0xffffffff || ++count > 3) { if (count == 4) { ninfo("...\n"); } return val; } } /* No this is a new address or value */ else { /* Did we print "..." for the previous value? */ if (count > 3) { /* Yes.. then show how many times the value repeated */ ninfo("[repeats %d more times]\n", count - 3); } /* Save the new address, value, and count */ prevaddr = addr; preval = val; count = 1; } /* Show the register value read */ ninfo("%08x->%08x\n", addr, val); return val; } #endif /**************************************************************************** * Name: stm32_putreg * * Description: * This function may to used to intercept an monitor all register accesses. * Clearly this is nothing you would want to do unless you are debugging * this driver. * * Input Parameters: * val - The value to write to the register * addr - The register address to read * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG static void stm32_putreg(uint32_t val, uint32_t addr) { /* Show the register value being written */ ninfo("%08x<-%08x\n", addr, val); /* Write the value */ putreg32(val, addr); } #endif /**************************************************************************** * Name: stm32_checksetup * * Description: * Show the state of critical configuration registers. * * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG static void stm32_checksetup(void) { } #endif /**************************************************************************** * Function: stm32_initbuffer * * Description: * Initialize the free buffer list. * * Parameters: * priv - Reference to the driver state structure * txbuffer - DMA memory allocated for TX buffers. * * Returned Value: * None * * Assumptions: * Called during early driver initialization before Ethernet interrupts * are enabled. * ****************************************************************************/ static void stm32_initbuffer(struct stm32_ethmac_s *priv, uint8_t *txbuffer) { uint8_t *buffer; int i; /* Initialize the head of the free buffer list */ sq_init(&priv->freeb); /* Add all of the pre-allocated buffers to the free buffer list */ for (i = 0, buffer = txbuffer; i < STM32_ETH_NFREEBUFFERS; i++, buffer += ALIGNED_BUFSIZE) { sq_addlast((sq_entry_t *)buffer, &priv->freeb); } } /**************************************************************************** * Function: stm32_allocbuffer * * Description: * Allocate one buffer from the free buffer list. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * Pointer to the allocated buffer on success; NULL on failure * * Assumptions: * May or may not be called from an interrupt handler. In either case, * global interrupts are disabled, either explicitly or indirectly through * interrupt handling logic. * ****************************************************************************/ static inline uint8_t *stm32_allocbuffer(struct stm32_ethmac_s *priv) { /* Allocate a buffer by returning the head of the free buffer list */ return (uint8_t *)sq_remfirst(&priv->freeb); } /**************************************************************************** * Function: stm32_freebuffer * * Description: * Return a buffer to the free buffer list. * * Parameters: * priv - Reference to the driver state structure * buffer - A pointer to the buffer to be freed * * Returned Value: * None * * Assumptions: * May or may not be called from an interrupt handler. In either case, * global interrupts are disabled, either explicitly or indirectly through * interrupt handling logic. * ****************************************************************************/ static inline void stm32_freebuffer(struct stm32_ethmac_s *priv, uint8_t *buffer) { /* Free the buffer by adding it to the end of the free buffer list */ sq_addlast((sq_entry_t *)buffer, &priv->freeb); } /**************************************************************************** * Function: stm32_isfreebuffer * * Description: * Return TRUE if the free buffer list is not empty. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * True if there are one or more buffers in the free buffer list; * false if the free buffer list is empty * * Assumptions: * None. * ****************************************************************************/ static inline bool stm32_isfreebuffer(struct stm32_ethmac_s *priv) { /* Return TRUE if the free buffer list is not empty */ return !sq_empty(&priv->freeb); } /**************************************************************************** * Function: stm32_get_next_txdesc * * Description: * Returns the next tx descriptor in the list * * Parameters: * priv - Reference to the driver state structure * curr - Pointer to a tx descriptor * * Returned Value: * pointer to the next tx descriptor for the current interface * ****************************************************************************/ static struct eth_desc_s *stm32_get_next_txdesc(struct stm32_ethmac_s *priv, struct eth_desc_s * curr) { union stm32_desc_u *first = &g_txtable[priv->intf * CONFIG_STM32H7_ETH_NTXDESC]; union stm32_desc_u *last = &g_txtable[priv->intf * CONFIG_STM32H7_ETH_NTXDESC + CONFIG_STM32H7_ETH_NTXDESC - 1]; union stm32_desc_u *next = ((union stm32_desc_u *)curr) + 1; if (next > last) { next = first; } return &next->desc; } /**************************************************************************** * Function: stm32_transmit * * Description: * Start hardware transmission. Called either from the txdone interrupt * handling or from watchdog based polling. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * May or may not be called from an interrupt handler. In either case, * global interrupts are disabled, either explicitly or indirectly through * interrupt handling logic. * ****************************************************************************/ static int stm32_transmit(struct stm32_ethmac_s *priv) { struct eth_desc_s *txdesc; struct eth_desc_s *txfirst; /* The internal (optimal) network buffer size may be configured to be * larger than the Ethernet buffer size. */ #if OPTIMAL_ETH_BUFSIZE > ALIGNED_BUFSIZE uint8_t *buffer; int bufcount; int lastsize; int i; #endif /* Verify that the hardware is ready to send another packet. If we get * here, then we are committed to sending a packet; Higher level logic * must have assured that there is no transmission in progress. */ txdesc = priv->txhead; txfirst = txdesc; ninfo("d_len: %d d_buf: %p txhead: %p tdes3: %08x\n", priv->dev.d_len, priv->dev.d_buf, txdesc, txdesc->des3); DEBUGASSERT(txdesc); /* Flush the contents of the TX buffer into physical memory */ up_clean_dcache((uintptr_t)priv->dev.d_buf, (uintptr_t)priv->dev.d_buf + priv->dev.d_len); /* Is the size to be sent greater than the size of the Ethernet buffer? */ DEBUGASSERT(priv->dev.d_len > 0 && priv->dev.d_buf != NULL); #if OPTIMAL_ETH_BUFSIZE > ALIGNED_BUFSIZE if (priv->dev.d_len > ALIGNED_BUFSIZE) { /* Yes... how many buffers will be need to send the packet? */ bufcount = (priv->dev.d_len + (ALIGNED_BUFSIZE - 1)) / ALIGNED_BUFSIZE; lastsize = priv->dev.d_len - (bufcount - 1) * ALIGNED_BUFSIZE; ninfo("bufcount: %d lastsize: %d\n", bufcount, lastsize); /* Set the first segment bit in the first TX descriptor */ txdesc->des3 = ETH_TDES3_RD_FD; /* Set up all but the last TX descriptor */ buffer = priv->dev.d_buf; for (i = 0; i < bufcount; i++) { DEBUGASSERT((txdesc->des3 & ETH_TDES3_RD_OWN) == 0); /* Set the Buffer1 address pointer */ txdesc->des0 = (uint32_t)buffer; /* Set the Buffer2 address pointer */ txdesc->des1 = 0; /* Set the buffer size in all TX descriptors */ if (i == (bufcount - 1)) { /* This is the last segment. Set the last segment bit in the * last TX descriptor */ txdesc->des3 |= ETH_TDES3_RD_LD; /* This segment is, most likely, of fractional buffersize */ /* ask for an interrupt when this segment transfer completes. */ txdesc->des2 = lastsize | ETH_TDES2_RD_IOC; buffer += lastsize; } else { /* This is not the last segment. We don't want an interrupt * when this segment transfer completes. */ /* The size of the transfer is the whole buffer */ txdesc->des2 = ALIGNED_BUFSIZE; buffer += ALIGNED_BUFSIZE; } /* Give the descriptor to DMA */ txdesc->des3 |= ETH_TDES3_RD_OWN; /* Flush the contents of the modified TX descriptor into physical * memory. */ up_clean_dcache((uintptr_t)txdesc, (uintptr_t)txdesc + sizeof(struct eth_desc_s)); /* Point to the next available TX descriptor */ txdesc = stm32_get_next_txdesc(priv, txdesc); } } else #endif { DEBUGASSERT((txdesc->des3 & ETH_TDES3_RD_OWN) == 0); /* Set the Buffer1 address pointer */ txdesc->des0 = (uint32_t)priv->dev.d_buf; /* Set the Buffer2 address pointer */ txdesc->des1 = 0; /* Set frame size, and we do * want an interrupt when the transfer completes. */ DEBUGASSERT(priv->dev.d_len <= CONFIG_NET_ETH_PKTSIZE); txdesc->des2 = priv->dev.d_len | ETH_TDES2_RD_IOC; /* The single descriptor is both the first and last segment. */ /* Set OWN bit of the TX descriptor des3. This gives the buffer to * Ethernet DMA */ txdesc->des3 = (ETH_TDES3_RD_OWN | ETH_TDES3_RD_LD | ETH_TDES3_RD_FD); /* Flush the contents of the modified TX descriptor into physical * memory. */ up_clean_dcache((uintptr_t)txdesc, (uintptr_t)txdesc + sizeof(struct eth_desc_s)); /* Point to the next available TX descriptor */ txdesc = stm32_get_next_txdesc(priv, txdesc); } /* Remember where we left off in the TX descriptor chain */ priv->txhead = txdesc; /* Detach the buffer from priv->dev structure. That buffer is now * "in-flight". */ priv->dev.d_buf = NULL; priv->dev.d_len = 0; /* If there is no other TX buffer, in flight, then remember the location * of the TX descriptor. This is the location to check for TX done events. */ if (!priv->txtail) { DEBUGASSERT(priv->inflight == 0); priv->txtail = txfirst; } /* Increment the number of TX transfer in-flight */ priv->inflight++; ninfo("txhead: %p txtail: %p inflight: %d\n", priv->txhead, priv->txtail, priv->inflight); /* If all TX descriptors are in-flight, then we have to disable receive * interrupts too. This is because receive events can trigger more un- * stoppable transmit events. */ if (priv->inflight >= CONFIG_STM32H7_ETH_NTXDESC) { stm32_disableint(priv, ETH_DMACIER_RIE); } MEMORY_SYNC(); /* Enable TX interrupts */ stm32_enableint(priv, ETH_DMACIER_TIE); /* Setup the TX timeout watchdog (perhaps restarting the timer) */ wd_start(priv->txtimeout, STM32_TXTIMEOUT, stm32_txtimeout_expiry, 1, (uint32_t)priv); /* Update the tx descriptor tail pointer register to start the DMA */ putreg32((uintptr_t)txdesc, STM32_ETH_DMACTXDTPR); return OK; } /**************************************************************************** * Function: stm32_txpoll * * Description: * The transmitter is available, check if the network has any outgoing * packets ready to send. This is a callback from devif_poll(). * devif_poll() may be called: * * 1. When the preceding TX packet send is complete, * 2. When the preceding TX packet send timesout and the interface is reset * 3. During normal TX polling * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * May or may not be called from an interrupt handler. In either case, * global interrupts are disabled, either explicitly or indirectly through * interrupt handling logic. * ****************************************************************************/ static int stm32_txpoll(struct net_driver_s *dev) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private; DEBUGASSERT(priv->dev.d_buf != NULL); /* If the polling resulted in data that should be sent out on the network, * the field d_len is set to a value > 0. */ if (priv->dev.d_len > 0) { /* Look up the destination MAC address and add it to the Ethernet * header. */ #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 if (IFF_IS_IPv4(priv->dev.d_flags)) #endif { arp_out(&priv->dev); } #endif /* CONFIG_NET_IPv4 */ #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 else #endif { neighbor_out(&priv->dev); } #endif /* CONFIG_NET_IPv6 */ /* Send the packet */ stm32_transmit(priv); DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL); /* Check if the next TX descriptor is owned by the Ethernet DMA or * CPU. We cannot perform the TX poll if we are unable to accept * another packet for transmission. * * In a race condition, ETH_TDES3_OWN may be cleared BUT still not * available because stm32_freeframe() has not yet run. If * stm32_freeframe() has run, the buffer1 pointer (tdes2) will be * nullified (and inflight should be < CONFIG_STM32H7_ETH_NTXDESC). */ if ((priv->txhead->des3 & ETH_TDES3_RD_OWN) != 0 || priv->txhead->des0 != 0) { /* We have to terminate the poll if we have no more descriptors * available for another transfer. */ nerr("No tx descriptors available"); return -EBUSY; } /* We have the descriptor, we can continue the poll. Allocate a new * buffer for the poll. */ dev->d_buf = stm32_allocbuffer(priv); /* We can't continue the poll if we have no buffers */ if (dev->d_buf == NULL) { /* Terminate the poll. */ nerr("No tx buffer available"); return -ENOMEM; } } /* If zero is returned, the polling will continue until all connections * have been examined. */ return 0; } /**************************************************************************** * Function: stm32_dopoll * * Description: * The function is called in order to perform an out-of-sequence TX poll. * This is done: * * 1. After completion of a transmission (stm32_txdone), * 2. When new TX data is available (stm32_txavail_process), and * 3. After a TX timeout to restart the sending process * (stm32_txtimeout_process). * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void stm32_dopoll(struct stm32_ethmac_s *priv) { struct net_driver_s *dev = &priv->dev; /* Check if the next TX descriptor is owned by the Ethernet DMA or * CPU. We cannot perform the TX poll if we are unable to accept * another packet for transmission. * * In a race condition, ETH_TDES3_RD_OWN may be cleared BUT still not * available because stm32_freeframe() has not yet run. If * stm32_freeframe() has run, the buffer1 pointer (des0) will be * nullified (and inflight should be < CONFIG_STM32H7_ETH_NTXDESC). */ if ((priv->txhead->des3 & ETH_TDES3_RD_OWN) == 0 && priv->txhead->des0 == 0) { /* If we have the descriptor, then poll the network for new XMIT data. * Allocate a buffer for the poll. */ DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL); dev->d_buf = stm32_allocbuffer(priv); /* We can't poll if we have no buffers */ if (dev->d_buf) { devif_timer(dev, 0, stm32_txpoll); /* We will, most likely end up with a buffer to be freed. But it * might not be the same one that we allocated above. */ if (dev->d_buf) { DEBUGASSERT(dev->d_len == 0); stm32_freebuffer(priv, dev->d_buf); dev->d_buf = NULL; } } else { nerr("No tx buffers"); } } else { nerr("No tx descriptors\n"); } } /**************************************************************************** * Function: stm32_enableint * * Description: * Enable a "normal" interrupt * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void stm32_enableint(struct stm32_ethmac_s *priv, uint32_t ierbit) { uint32_t regval; /* Enable the specified "normal" interrupt */ regval = stm32_getreg(STM32_ETH_DMACIER); regval |= (ETH_DMACIER_NIE | ierbit); stm32_putreg(regval, STM32_ETH_DMACIER); } /**************************************************************************** * Function: stm32_disableint * * Description: * Disable a normal interrupt. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void stm32_disableint(struct stm32_ethmac_s *priv, uint32_t ierbit) { uint32_t regval; /* Disable the "normal" interrupt */ regval = stm32_getreg(STM32_ETH_DMACIER); regval &= ~ierbit; /* Are all "normal" interrupts now disabled? */ if ((regval & ETH_DMAINT_NORMAL) == 0) { /* Yes.. disable normal interrupts */ regval &= ~ETH_DMACIER_NIE; } stm32_putreg(regval, STM32_ETH_DMACIER); } /**************************************************************************** * Function: stm32_get_next_rxdesc * * Description: * Returns the next rx descriptor in the list * * Parameters: * priv - Reference to the driver state structure * curr - Pointer to a rx descriptor * * Returned Value: * pointer to the next rx descriptor for the current interface * ****************************************************************************/ static struct eth_desc_s *stm32_get_next_rxdesc(struct stm32_ethmac_s *priv, struct eth_desc_s * curr) { union stm32_desc_u *first = &g_rxtable[priv->intf * CONFIG_STM32H7_ETH_NRXDESC]; union stm32_desc_u *last = &g_rxtable[priv->intf * CONFIG_STM32H7_ETH_NRXDESC + CONFIG_STM32H7_ETH_NRXDESC - 1]; union stm32_desc_u *next = ((union stm32_desc_u *)curr) + 1; if (next > last) { next = first; } return &next->desc; } /**************************************************************************** * Function: stm32_freesegment * * Description: * The function is called when a frame is received using the DMA receive * interrupt. It scans the RX descriptors to the received frame. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void stm32_freesegment(struct stm32_ethmac_s *priv, struct eth_desc_s *rxfirst, int segments) { struct eth_desc_s *rxdesc; int i; ninfo("rxfirst: %p segments: %d\n", rxfirst, segments); /* Give the freed RX buffers back to the Ethernet MAC to be refilled */ rxdesc = rxfirst; for (i = 0; i < segments; i++) { /* Set OWN bit in RX descriptors. This gives the buffers back to DMA */ rxdesc->des3 = ETH_RDES3_RD_OWN | ETH_RDES3_RD_IOC | ETH_RDES3_RD_BUF1V; /* Make sure that the modified RX descriptor is written to physical * memory. */ up_clean_dcache((uintptr_t)rxdesc, (uintptr_t)rxdesc + sizeof(struct eth_desc_s)); /* Get the next RX descriptor in the chain */ rxdesc = stm32_get_next_rxdesc(priv, rxdesc); /* Update the tail pointer */ stm32_putreg((uintptr_t)rxdesc, STM32_ETH_DMACRXDTPR); } /* Reset the segment management logic */ priv->rxcurr = NULL; priv->segments = 0; /* Check if the RX Buffer unavailable flag is set */ if ((stm32_getreg(STM32_ETH_DMACSR) & ETH_DMACSR_RBU) != 0) { /* Clear the RBU flag */ stm32_putreg(ETH_DMACSR_RBU, STM32_ETH_DMACSR); nerr("ETH_DMACSR_RBU\n"); /* To resume processing Rx descriptors, the application should change * the ownership of the descriptor and issue a Receive Poll Demand * command. If this command is not issued, the Rx process resumes when * the next recognized incoming packet is received. In ring mode, the * application should advance the Receive Descriptor Tail Pointer * register of a channel. This bit is set only when the DMA owns the * previous Rx descriptor. */ } } /**************************************************************************** * Function: stm32_recvframe * * Description: * The function is called when a frame is received using the DMA receive * interrupt. It scans the RX descriptors of the received frame. * * NOTE: This function will silently discard any packets containing errors. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * OK if a packet was successfully returned; -EAGAIN if there are no * further packets available * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static int stm32_recvframe(struct stm32_ethmac_s *priv) { struct eth_desc_s *rxdesc; struct eth_desc_s *rxcurr = NULL; uint8_t *buffer; int i; ninfo("rxhead: %p rxcurr: %p segments: %d\n", priv->rxhead, priv->rxcurr, priv->segments); /* Check if there are free buffers. We cannot receive new frames in this * design unless there is at least one free buffer. */ if (!stm32_isfreebuffer(priv)) { nerr("ERROR: No free buffers\n"); return -ENOMEM; } /* Scan descriptors owned by the CPU. Scan until: * * 1) We find a descriptor still owned by the DMA, * 2) We have examined all of the RX descriptors, or * 3) All of the TX descriptors are in flight. * * This last case is obscure. It is due to that fact that each packet * that we receive can generate an unstoppable transmisson. So we have * to stop receiving when we can not longer transmit. In this case, the * transmit logic should also have disabled further RX interrupts. */ rxdesc = priv->rxhead; /* Forces the first RX descriptor to be re-read from physical memory */ up_invalidate_dcache((uintptr_t)rxdesc, (uintptr_t)rxdesc + sizeof(struct eth_desc_s)); for (i = 0; (rxdesc->des3 & ETH_RDES3_WB_OWN) == 0 && i < CONFIG_STM32H7_ETH_NRXDESC && priv->inflight < CONFIG_STM32H7_ETH_NTXDESC; i++) { /* Check if this is a normal descriptor */ if (!(rxdesc->des3 & ETH_RDES3_WB_CTXT)) { /* Check if this is the first segment in the frame */ if ((rxdesc->des3 & ETH_RDES3_WB_FD) != 0 && (rxdesc->des3 & ETH_RDES3_WB_LD) == 0) { priv->rxcurr = rxdesc; priv->segments = 1; } /* Check if this is an intermediate segment in the frame */ else if (((rxdesc->des3 & ETH_RDES3_WB_LD) == 0) && ((rxdesc->des3 & ETH_RDES3_WB_FD) == 0)) { priv->segments++; } /* Otherwise, it is the last segment in the frame */ else { priv->segments++; /* Check if there is only one segment in the frame */ if (priv->segments == 1) { rxcurr = rxdesc; } else { rxcurr = priv->rxcurr; } ninfo("rxhead: %p rxcurr: %p segments: %d\n", priv->rxhead, priv->rxcurr, priv->segments); /* Check if any errors are reported in the frame */ if ((rxdesc->des3 & ETH_RDES3_WB_ES) == 0) { struct net_driver_s *dev = &priv->dev; /* Get the Frame Length of the received packet: subtract 4 * bytes of the CRC */ dev->d_len = ((rxdesc->des3 & ETH_RDES3_WB_PL_MASK) >> ETH_RDES3_WB_PL_SHIFT) - 4; /* Get a buffer from the free list. We don't even check if * this is successful because we already assure the free * list is not empty above. */ buffer = stm32_allocbuffer(priv); /* Take the buffer from the RX descriptor of the first free * segment, put it into the network device structure, then * replace the buffer in the RX descriptor with the newly * allocated buffer. */ DEBUGASSERT(dev->d_buf == NULL); dev->d_buf = (uint8_t *)rxcurr->des0; rxcurr->des0 = (uint32_t)buffer; /* Make sure that the modified RX descriptor is written to * physical memory. */ up_clean_dcache((uintptr_t)rxcurr, (uintptr_t)rxdesc + sizeof(struct eth_desc_s)); /* Remember where we should re-start scanning and reset the * segment scanning logic */ priv->rxhead = stm32_get_next_rxdesc(priv, rxdesc); stm32_freesegment(priv, rxcurr, priv->segments); /* Force the completed RX DMA buffer to be re-read from * physical memory. */ up_invalidate_dcache((uintptr_t)dev->d_buf, (uintptr_t)dev->d_buf + dev->d_len); ninfo("rxhead: %p d_buf: %p d_len: %d\n", priv->rxhead, dev->d_buf, dev->d_len); /* Return success */ return OK; } else { /* Drop the frame that contains the errors, reset the * segment scanning logic, and continue scanning with the * next frame. */ nwarn("WARNING: DROPPED RX descriptor errors: %08x\n", rxdesc->des3); stm32_freesegment(priv, rxcurr, priv->segments); } } } else { /* Drop the context descriptors, we are not interested */ DEBUGASSERT(rxcurr != NULL); stm32_freesegment(priv, rxcurr, 1); } /* Try the next descriptor */ rxdesc = stm32_get_next_rxdesc(priv, rxdesc); /* Force the next RX descriptor to be re-read from physical memory */ up_invalidate_dcache((uintptr_t)rxdesc, (uintptr_t)rxdesc + sizeof(struct eth_desc_s)); } /* We get here after all of the descriptors have been scanned or when * rxdesc points to the first descriptor owned by the DMA. Remember where * we left off. */ priv->rxhead = rxdesc; ninfo("rxhead: %p rxcurr: %p segments: %d\n", priv->rxhead, priv->rxcurr, priv->segments); return -EAGAIN; } /**************************************************************************** * Function: stm32_receive * * Description: * An interrupt was received indicating the availability of a new RX packet * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void stm32_receive(struct stm32_ethmac_s *priv) { struct net_driver_s *dev = &priv->dev; /* Loop while while stm32_recvframe() successfully retrieves valid * Ethernet frames. */ while (stm32_recvframe(priv) == OK) { #ifdef CONFIG_NET_PKT /* When packet sockets are enabled, feed the frame into the packet * tap */ pkt_input(&priv->dev); #endif /* Check if the packet is a valid size for the network buffer * configuration (this should not happen) */ if (dev->d_len > CONFIG_NET_ETH_PKTSIZE) { nwarn("WARNING: DROPPED Too big: %d\n", dev->d_len); /* Free dropped packet buffer */ if (dev->d_buf) { stm32_freebuffer(priv, dev->d_buf); dev->d_buf = NULL; dev->d_len = 0; } continue; } /* We only accept IP packets of the configured type and ARP packets */ #ifdef CONFIG_NET_IPv4 if (BUF->type == HTONS(ETHTYPE_IP)) { ninfo("IPv4 frame\n"); /* Handle ARP on input then give the IPv4 packet to the network * layer */ arp_ipin(&priv->dev); ipv4_input(&priv->dev); /* If the above function invocation resulted in data that should * be sent out on the network, the field d_len will set to a * value > 0. */ if (priv->dev.d_len > 0) { /* Update the Ethernet header with the correct MAC address */ #ifdef CONFIG_NET_IPv6 if (IFF_IS_IPv4(priv->dev.d_flags)) #endif { arp_out(&priv->dev); } #ifdef CONFIG_NET_IPv6 else { neighbor_out(&priv->dev); } #endif /* And send the packet */ stm32_transmit(priv); } } else #endif #ifdef CONFIG_NET_IPv6 if (BUF->type == HTONS(ETHTYPE_IP6)) { ninfo("IPv6 frame\n"); /* Give the IPv6 packet to the network layer */ ipv6_input(&priv->dev); /* If the above function invocation resulted in data that should * be sent out on the network, the field d_len will set to a * value > 0. */ if (priv->dev.d_len > 0) { /* Update the Ethernet header with the correct MAC address */ #ifdef CONFIG_NET_IPv4 if (IFF_IS_IPv4(priv->dev.d_flags)) { arp_out(&priv->dev); } else #endif #ifdef CONFIG_NET_IPv6 { neighbor_out(&priv->dev); } #endif /* And send the packet */ stm32_transmit(priv); } } else #endif #ifdef CONFIG_NET_ARP if (BUF->type == htons(ETHTYPE_ARP)) { ninfo("ARP frame\n"); /* Handle ARP packet */ arp_arpin(&priv->dev); /* If the above function invocation resulted in data that should * be sent out on the network, the field d_len will set to a * value > 0. */ if (priv->dev.d_len > 0) { stm32_transmit(priv); } } else #endif { nwarn("WARNING: DROPPED Unknown type: %04x\n", BUF->type); } /* We are finished with the RX buffer. NOTE: If the buffer is * re-used for transmission, the dev->d_buf field will have been * nullified. */ if (dev->d_buf) { /* Free the receive packet buffer */ stm32_freebuffer(priv, dev->d_buf); dev->d_buf = NULL; dev->d_len = 0; } } } /**************************************************************************** * Function: stm32_freeframe * * Description: * Scans the TX descriptors and frees the buffers of completed TX * transfers. * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None. * * Assumptions: * Global interrupts are disabled by interrupt handling logic. * ****************************************************************************/ static void stm32_freeframe(struct stm32_ethmac_s *priv) { struct eth_desc_s *txdesc; uint32_t des3_tmp; int i; ninfo("txhead: %p txtail: %p inflight: %d\n", priv->txhead, priv->txtail, priv->inflight); /* Scan for "in-flight" descriptors owned by the CPU */ txdesc = priv->txtail; if (txdesc) { DEBUGASSERT(priv->inflight > 0); /* Force re-reading of the TX descriptor for physical memory */ up_invalidate_dcache((uintptr_t)txdesc, (uintptr_t)txdesc + sizeof(struct eth_desc_s)); for (i = 0; (txdesc->des3 & ETH_TDES3_RD_OWN) == 0; i++) { /* There should be a buffer assigned to all in-flight * TX descriptors. */ ninfo("txtail: %p des0: %08x des2: %08x des3: %08x\n", txdesc, txdesc->des0, txdesc->des2, txdesc->des3); DEBUGASSERT(txdesc->des0 != 0); /* Yes.. Free the buffer */ stm32_freebuffer(priv, (uint8_t *)txdesc->des0); /* In any event, make sure that des0-3 are nullified. */ txdesc->des0 = 0; txdesc->des1 = 0; txdesc->des2 = 0; des3_tmp = txdesc->des3; txdesc->des3 = 0; /* Flush the contents of the modified TX descriptor into * physical memory. */ up_clean_dcache((uintptr_t)txdesc, (uintptr_t)txdesc + sizeof(struct eth_desc_s)); /* Check if this was the last segment of a TX frame */ if ((des3_tmp & ETH_TDES3_RD_LD) != 0) { /* Yes.. Decrement the number of frames "in-flight". */ priv->inflight--; /* If all of the TX descriptors were in-flight, then RX * interruptsmay have been disabled... we can re-enable them * now. */ stm32_enableint(priv, ETH_DMACIER_RIE); /* If there are no more frames in-flight, then bail. */ if (priv->inflight <= 0) { priv->txtail = NULL; priv->inflight = 0; return; } } /* Try the next descriptor in the TX chain */ txdesc = stm32_get_next_txdesc(priv, txdesc); /* Force re-reading of the TX descriptor for physical memory */ up_invalidate_dcache((uintptr_t)txdesc, (uintptr_t)txdesc + sizeof(struct eth_desc_s)); } /* We get here if (1) there are still frames "in-flight". Remember * where we left off. */ priv->txtail = txdesc; ninfo("txhead: %p txtail: %p inflight: %d\n", priv->txhead, priv->txtail, priv->inflight); } } /**************************************************************************** * Function: stm32_txdone * * Description: * An interrupt was received indicating that the last TX packet(s) is done * * Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by the watchdog logic. * ****************************************************************************/ static void stm32_txdone(struct stm32_ethmac_s *priv) { DEBUGASSERT(priv->txtail != NULL); /* Scan the TX descriptor change, returning buffers to free list */ stm32_freeframe(priv); /* If no further xmits are pending, then cancel the TX timeout */ if (priv->inflight <= 0) { /* Cancel the TX timeout */ wd_cancel(priv->txtimeout); /* And disable further TX interrupts. */ stm32_disableint(priv, ETH_DMACIER_TIE); } /* Then poll the network for new XMIT data */ stm32_dopoll(priv); } /**************************************************************************** * Function: stm32_interrupt_work * * Description: * Perform interrupt related work from the worker thread * * Parameters: * arg - The argument passed when work_queue() was called. * * Returned Value: * OK on success * * Assumptions: * Ethernet interrupts are disabled * ****************************************************************************/ static void stm32_interrupt_work(void *arg) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg; uint32_t dmasr; DEBUGASSERT(priv); /* Process pending Ethernet interrupts */ net_lock(); /* Get the DMA interrupt status bits (no MAC interrupts are expected) */ dmasr = stm32_getreg(STM32_ETH_DMACSR); /* Mask only enabled interrupts. This depends on the fact that the * interrupt related bits (0-16) correspond in these two registers. */ dmasr &= stm32_getreg(STM32_ETH_DMACIER); /* Check if there are pending "normal" interrupts */ if ((dmasr & ETH_DMACSR_NIS) != 0) { /* Yes.. Check if we received an incoming packet, if so, call * stm32_receive() */ if ((dmasr & ETH_DMACSR_RI) != 0) { /* Clear the pending receive interrupt */ stm32_putreg(ETH_DMACSR_RI, STM32_ETH_DMACSR); /* Handle the received package */ stm32_receive(priv); } /* Check if a packet transmission just completed. If so, call * stm32_txdone(). This may disable further TX interrupts if there * are no pending transmissions. */ if ((dmasr & ETH_DMACSR_TI) != 0) { /* Clear the pending receive interrupt */ stm32_putreg(ETH_DMACSR_TI, STM32_ETH_DMACSR); /* Check if there are pending transmissions */ stm32_txdone(priv); } /* Clear the pending normal summary interrupt */ stm32_putreg(ETH_DMACSR_NIS, STM32_ETH_DMACSR); } /* Handle error interrupt only if CONFIG_DEBUG_NET is eanbled */ #ifdef CONFIG_DEBUG_NET /* Check if there are pending "abnormal" interrupts */ if ((dmasr & ETH_DMACSR_AIS) != 0) { /* Just let the user know what happened */ nerr("ERROR: Abormal event(s): %08x\n", dmasr); /* Clear all pending abnormal events */ stm32_putreg(ETH_DMAINT_ABNORMAL, STM32_ETH_DMACSR); /* Clear the pending abnormal summary interrupt */ stm32_putreg(ETH_DMACSR_AIS, STM32_ETH_DMACSR); } #endif net_unlock(); /* Re-enable Ethernet interrupts at the NVIC */ up_enable_irq(STM32_IRQ_ETH); } /**************************************************************************** * Function: stm32_interrupt * * Description: * Hardware interrupt handler * * Parameters: * irq - Number of the IRQ that generated the interrupt * context - Interrupt register state save info (architecture-specific) * * Returned Value: * OK on success * * Assumptions: * ****************************************************************************/ static int stm32_interrupt(int irq, void *context, FAR void *arg) { struct stm32_ethmac_s *priv = &g_stm32ethmac[0]; uint32_t dmasr; /* Get the DMA interrupt status bits (no MAC interrupts are expected) */ dmasr = stm32_getreg(STM32_ETH_DMACSR); if (dmasr != 0) { /* Disable further Ethernet interrupts. Because Ethernet interrupts * are also disabled if the TX timeout event occurs, there can be no * race condition here. */ up_disable_irq(STM32_IRQ_ETH); /* Check if a packet transmission just completed. */ if ((dmasr & ETH_DMACSR_TI) != 0) { /* If a TX transfer just completed, then cancel the TX timeout so * there will be no race condition between any subsequent timeout * expiration and the deferred interrupt processing. */ wd_cancel(priv->txtimeout); } DEBUGASSERT(work_available(&priv->irqwork)); /* Schedule to perform the interrupt processing on the worker thread. */ work_queue(ETHWORK, &priv->irqwork, stm32_interrupt_work, priv, 0); } return OK; } /**************************************************************************** * Function: stm32_txtimeout_work * * Description: * Perform TX timeout related work from the worker thread * * Parameters: * arg - The argument passed when work_queue() as called. * * Returned Value: * OK on success * * Assumptions: * Ethernet interrupts are disabled * ****************************************************************************/ static void stm32_txtimeout_work(void *arg) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg; /* Reset the hardware. Just take the interface down, then back up again. */ net_lock(); stm32_ifdown(&priv->dev); stm32_ifup(&priv->dev); /* Then poll for new XMIT data */ stm32_dopoll(priv); net_unlock(); } /**************************************************************************** * Function: stm32_txtimeout_expiry * * Description: * Our TX watchdog timed out. Called from the timer interrupt handler. * The last TX never completed. Reset the hardware and start again. * * Parameters: * argc - The number of available arguments * arg - The first argument * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by the watchdog logic. * ****************************************************************************/ static void stm32_txtimeout_expiry(int argc, uint32_t arg, ...) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg; nerr("ERROR: Timeout!\n"); /* Disable further Ethernet interrupts. This will prevent some race * conditions with interrupt work. There is still a potential race * condition with interrupt work that is already queued and in progress. * * Interrupts will be re-enabled when stm32_ifup() is called. */ up_disable_irq(STM32_IRQ_ETH); /* Schedule to perform the TX timeout processing on the worker thread. */ DEBUGASSERT(work_available(&priv->irqwork)); work_queue(ETHWORK, &priv->irqwork, stm32_txtimeout_work, priv, 0); } /**************************************************************************** * Function: stm32_poll_work * * Description: * Perform periodic polling from the worker thread * * Parameters: * arg - The argument passed when work_queue() as called. * * Returned Value: * OK on success * * Assumptions: * Ethernet interrupts are disabled * ****************************************************************************/ static void stm32_poll_work(void *arg) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg; struct net_driver_s *dev = &priv->dev; /* Check if the next TX descriptor is owned by the Ethernet DMA or CPU. * We cannot perform the timer poll if we are unable to accept another * packet for transmission. Hmmm.. might be bug here. Does this mean if * there is a transmit in progress, we will miss TCP time state updates? * * In a race condition, ETH_TDES3_OWN may be cleared BUT still not * available because stm32_freeframe() has not yet run. If * stm32_freeframe() has run, the buffer1 pointer (des2) will be nullified * (and inflight should be < CONFIG_STM32H7_ETH_NTXDESC). */ net_lock(); if ((priv->txhead->des3 & ETH_TDES3_RD_OWN) == 0 && priv->txhead->des0 == 0) { /* If we have the descriptor, then perform the timer poll. Allocate a * buffer for the poll. */ DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL); dev->d_buf = stm32_allocbuffer(priv); /* We can't poll if we have no buffers */ if (dev->d_buf) { /* Update TCP timing states and poll the network for new XMIT * data. */ devif_timer(dev, STM32_WDDELAY, stm32_txpoll); /* We will, most likely end up with a buffer to be freed. But it * might not be the same one that we allocated above. */ if (dev->d_buf) { DEBUGASSERT(dev->d_len == 0); stm32_freebuffer(priv, dev->d_buf); dev->d_buf = NULL; } } } /* Setup the watchdog poll timer again */ wd_start(priv->txpoll, STM32_WDDELAY, stm32_poll_expiry, 1, priv); net_unlock(); } /**************************************************************************** * Function: stm32_poll_expiry * * Description: * Periodic timer handler. Called from the timer interrupt handler. * * Parameters: * argc - The number of available arguments * arg - The first argument * * Returned Value: * None * * Assumptions: * Global interrupts are disabled by the watchdog logic. * ****************************************************************************/ static void stm32_poll_expiry(int argc, uint32_t arg, ...) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg; /* Schedule to perform the interrupt processing on the worker thread. */ if (work_available(&priv->pollwork)) { work_queue(ETHWORK, &priv->pollwork, stm32_poll_work, priv, 0); } else { wd_start(priv->txpoll, STM32_WDDELAY, stm32_poll_expiry, 1, priv); } } /**************************************************************************** * Function: stm32_ifup * * Description: * NuttX Callback: Bring up the Ethernet interface when an IP address is * provided * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static int stm32_ifup(struct net_driver_s *dev) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private; int ret; #ifdef CONFIG_NET_IPv4 ninfo("Bringing up: %d.%d.%d.%d\n", dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24); #endif #ifdef CONFIG_NET_IPv6 ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2], dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5], dev->d_ipv6addr[6], dev->d_ipv6addr[7]); #endif /* Configure the Ethernet interface for DMA operation. */ ret = stm32_ethconfig(priv); if (ret < 0) { return ret; } /* Set and activate a timer process */ wd_start(priv->txpoll, STM32_WDDELAY, stm32_poll_expiry, 1, (uint32_t)priv); /* Enable the Ethernet interrupt */ priv->ifup = true; up_enable_irq(STM32_IRQ_ETH); stm32_checksetup(); return OK; } /**************************************************************************** * Function: stm32_ifdown * * Description: * NuttX Callback: Stop the interface. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static int stm32_ifdown(struct net_driver_s *dev) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private; irqstate_t flags; ninfo("Taking the network down\n"); /* Disable the Ethernet interrupt */ flags = enter_critical_section(); up_disable_irq(STM32_IRQ_ETH); /* Cancel the TX poll timer and TX timeout timers */ wd_cancel(priv->txpoll); wd_cancel(priv->txtimeout); /* Put the EMAC in its reset, non-operational state. This should be * a known configuration that will guarantee the stm32_ifup() always * successfully brings the interface back up. */ stm32_ethreset(MAC_READY_USTIMEOUT); /* Mark the device "down" */ priv->ifup = false; leave_critical_section(flags); return OK; } /**************************************************************************** * Function: stm32_txavail_work * * Description: * Perform an out-of-cycle poll on the worker thread. * * Parameters: * arg - Reference to the NuttX driver state structure (cast to void*) * * Returned Value: * None * * Assumptions: * Called on the higher priority worker thread. * ****************************************************************************/ static void stm32_txavail_work(void *arg) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)arg; ninfo("ifup: %d\n", priv->ifup); /* Ignore the notification if the interface is not yet up */ net_lock(); if (priv->ifup) { /* Poll the network for new XMIT data */ stm32_dopoll(priv); } net_unlock(); } /**************************************************************************** * Function: stm32_txavail * * Description: * Driver callback invoked when new TX data is available. This is a * stimulus perform an out-of-cycle poll and, thereby, reduce the TX * latency. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * Called in normal user mode * ****************************************************************************/ static int stm32_txavail(struct net_driver_s *dev) { struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private; /* Is our single work structure available? It may not be if there are * pending interrupt actions and we will have to ignore the Tx * availability action. */ if (work_available(&priv->pollwork)) { /* Schedule to serialize the poll on the worker thread. */ work_queue(ETHWORK, &priv->pollwork, stm32_txavail_work, priv, 0); } return OK; } /**************************************************************************** * Function: stm32_calcethcrc * * Description: * Function to calculate the CRC used by STM32 to check an ethernet frame * * Parameters: * data - the data to be checked * length - length of the data * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6) static uint32_t stm32_calcethcrc(const uint8_t *data, size_t length) { uint32_t crc = 0xffffffff; size_t i; int j; for (i = 0; i < length; i++) { for (j = 0; j < 8; j++) { if (((crc >> 31) ^ (data[i] >> j)) & 0x01) { /* x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1 */ crc = (crc << 1) ^ 0x04c11db7; } else { crc = crc << 1; } } } return ~crc; } #endif /**************************************************************************** * Function: stm32_addmac * * Description: * NuttX Callback: Add the specified MAC address to the hardware multicast * address filtering * * Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be added * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6) static int stm32_addmac(struct net_driver_s *dev, const uint8_t *mac) { uint32_t crc; uint32_t hashindex; uint32_t temp; uint32_t registeraddress; ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); /* Add the MAC address to the hardware multicast hash table */ crc = stm32_calcethcrc(mac, 6); hashindex = (crc >> 26) & 0x3f; if (hashindex > 31) { registeraddress = STM32_ETH_MACHT0R; hashindex -= 32; } else { registeraddress = STM32_ETH_MACHT1R; } temp = stm32_getreg(registeraddress); temp |= 1 << hashindex; stm32_putreg(temp, registeraddress); temp = stm32_getreg(STM32_ETH_MACPFR); temp |= (ETH_MACPFR_HMC | ETH_MACPFR_HPF); stm32_putreg(temp, STM32_ETH_MACPFR); return OK; } #endif /* CONFIG_NET_IGMP || CONFIG_NET_ICMPv6 */ /**************************************************************************** * Function: stm32_rmmac * * Description: * NuttX Callback: Remove the specified MAC address from the hardware * multicast address filtering * * Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be removed * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #ifdef CONFIG_NET_IGMP static int stm32_rmmac(struct net_driver_s *dev, const uint8_t *mac) { uint32_t crc; uint32_t hashindex; uint32_t temp; uint32_t registeraddress; ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); /* Remove the MAC address to the hardware multicast hash table */ crc = stm32_calcethcrc(mac, 6); hashindex = (crc >> 26) & 0x3f; if (hashindex > 31) { registeraddress = STM32_ETH_MACHT0R; hashindex -= 32; } else { registeraddress = STM32_ETH_MACHT1R; } temp = stm32_getreg(registeraddress); temp &= ~(1 << hashindex); stm32_putreg(temp, registeraddress); /* If there is no address registered any more, delete multicast filtering */ if (stm32_getreg(STM32_ETH_MACHT0R) == 0 && stm32_getreg(STM32_ETH_MACHT1R) == 0) { temp = stm32_getreg(STM32_ETH_MACPFR); temp &= ~(ETH_MACPFR_HMC | ETH_MACPFR_HPF); stm32_putreg(temp, STM32_ETH_MACPFR); } return OK; } #endif /**************************************************************************** * Function: stm32_txdescinit * * Description: * Initializes the DMA TX descriptors in chain mode. * * Parameters: * priv - Reference to the driver state structure * txtable - List of pre-allocated TX descriptors for the Ethernet * interface * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static void stm32_txdescinit(struct stm32_ethmac_s *priv, union stm32_desc_u *txtable) { struct eth_desc_s *txdesc; int i; /* priv->txhead will point to the first, available TX descriptor in the * chain. Set the priv->txhead pointer to the first descriptor in the * table. */ priv->txhead = &txtable[0].desc; priv->txchbase = &txtable[0].desc; /* priv->txtail will point to the first segment of the oldest pending * "in-flight" TX transfer. NULL means that there are no active TX * transfers. */ priv->txtail = NULL; priv->inflight = 0; /* Initialize each TX descriptor */ for (i = 0; i < CONFIG_STM32H7_ETH_NTXDESC; i++) { txdesc = &txtable[i].desc; #ifdef CHECKSUM_BY_HARDWARE #if 0 /* Enable the checksum insertion for the TX frames TODO! */ txdesc->des0 |= ETH_TDES0_CIC_ALL; #endif #endif /* Clear Buffer1 address pointer (buffers will be assigned as they * are used) */ txdesc->des0 = 0; /* Clear the rest of the descriptor as well */ txdesc->des1 = 0; txdesc->des2 = 0; txdesc->des3 = 0; } /* Flush all of the initialized TX descriptors to physical memory */ up_clean_dcache((uintptr_t)txtable, (uintptr_t)txtable + TXTABLE_SIZE * sizeof(union stm32_desc_u)); /* Set Channel Tx descriptor ring length register * TODO: Why -1 is needed? Without this the ring doesn't wrap around * properly but the DMACCATXDR advances to outside the descriptor ring */ stm32_putreg(CONFIG_STM32H7_ETH_NTXDESC - 1, STM32_ETH_DMACTXRLR); /* Set Transmit Descriptor List Address Register */ stm32_putreg((uint32_t)&txtable[0].desc, STM32_ETH_DMACTXDLAR); /* Set Transmit Descriptor Tail pointer */ stm32_putreg((uint32_t)&txtable[0].desc, STM32_ETH_DMACTXDTPR); } /**************************************************************************** * Function: stm32_rxdescinit * * Description: * Initializes the DMA RX descriptors in chain mode. * * Parameters: * priv - Reference to the driver state structure * rxtable - List of pre-allocated RX descriptors for the Ethernet * interface * txbuffer - List of pre-allocated DMA buffers for the Ethernet interface * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static void stm32_rxdescinit(struct stm32_ethmac_s *priv, union stm32_desc_u *rxtable, uint8_t *rxbuffer) { struct eth_desc_s *rxdesc; int i; /* priv->rxhead will point to the first, RX descriptor in the chain. * This will be where we receive the first incomplete frame. */ priv->rxhead = &rxtable[0].desc; priv->rxchbase = &rxtable[0].desc; /* If we accumulate the frame in segments, priv->rxcurr points to the * RX descriptor of the first segment in the current TX frame. */ priv->rxcurr = NULL; priv->segments = 0; /* Initialize each RX descriptor */ for (i = 0; i < CONFIG_STM32H7_ETH_NRXDESC; i++) { rxdesc = &rxtable[i].desc; /* Set Buffer1 address pointer */ rxdesc->des0 = (uint32_t)&rxbuffer[i * ALIGNED_BUFSIZE]; /* Set Buffer1 address high bytes */ rxdesc->des1 = 0; /* Set Buffer2 address high bytes */ rxdesc->des2 = 0; /* Set Own bit of the RX descriptor des3 and buffer address valid */ rxdesc->des3 = ETH_RDES3_RD_OWN | ETH_RDES3_RD_IOC | ETH_RDES3_RD_BUF1V; } /* Flush all of the initialized RX descriptors to physical memory */ up_clean_dcache((uintptr_t)rxtable, (uintptr_t)rxtable + RXTABLE_SIZE * sizeof(union stm32_desc_u)); /* Set Receive Descriptor ring length register * TODO: Why -1 is needed? Without this the ring doesn't wrap around * properly but the DMACCARXDR advances to outside the descriptor ring */ stm32_putreg(CONFIG_STM32H7_ETH_NRXDESC - 1, STM32_ETH_DMACRXRLR); /* Set Receive Descriptor List Address Register */ stm32_putreg((uint32_t)&rxtable[0].desc, STM32_ETH_DMACRXDLAR); /* Set Receive Descriptor Tail pointer Address */ stm32_putreg((uint32_t)&rxtable[CONFIG_STM32H7_ETH_NRXDESC - 1].desc, STM32_ETH_DMACRXDTPR); } /**************************************************************************** * Function: stm32_ioctl * * Description: * Executes the SIOCxMIIxxx command and responds using the request struct * that must be provided as its 2nd parameter. * * When called with SIOCGMIIPHY it will get the PHY address for the device * and write it to the req->phy_id field of the request struct. * * When called with SIOCGMIIREG it will read a register of the PHY that is * specified using the req->reg_no struct field and then write its output * to the req->val_out field. * * When called with SIOCSMIIREG it will write to a register of the PHY that * is specified using the req->reg_no struct field and use req->val_in as * its input. * * Parameters: * dev - Ethernet device structure * cmd - SIOCxMIIxxx command code * arg - Request structure also used to return values * * Returned Value: Negated errno on failure. * * Assumptions: * ****************************************************************************/ #ifdef CONFIG_NETDEV_PHY_IOCTL static int stm32_ioctl(struct net_driver_s *dev, int cmd, unsigned long arg) { #ifdef CONFIG_ARCH_PHY_INTERRUPT struct stm32_ethmac_s *priv = (struct stm32_ethmac_s *)dev->d_private; #endif int ret; switch (cmd) { #ifdef CONFIG_ARCH_PHY_INTERRUPT case SIOCMIINOTIFY: /* Set up for PHY event notifications */ { struct mii_iotcl_notify_s *req = (struct mii_iotcl_notify_s *)((uintptr_t)arg); ret = phy_notify_subscribe(dev->d_ifname, req->pid, req->signo, req->arg); if (ret == OK) { /* Enable PHY link up/down interrupts */ ret = stm32_phyintenable(priv); } } break; #endif case SIOCGMIIPHY: /* Get MII PHY address */ { struct mii_ioctl_data_s *req = (struct mii_ioctl_data_s *)((uintptr_t)arg); req->phy_id = CONFIG_STM32H7_PHYADDR; ret = OK; } break; case SIOCGMIIREG: /* Get register from MII PHY */ { struct mii_ioctl_data_s *req = (struct mii_ioctl_data_s *)((uintptr_t)arg); ret = stm32_phyread(req->phy_id, req->reg_num, &req->val_out); } break; case SIOCSMIIREG: /* Set register in MII PHY */ { struct mii_ioctl_data_s *req = (struct mii_ioctl_data_s *)((uintptr_t)arg); ret = stm32_phywrite(req->phy_id, req->reg_num, req->val_in, 0xffff); } break; default: ret = -ENOTTY; break; } return ret; } #endif /* CONFIG_NETDEV_PHY_IOCTL */ /**************************************************************************** * Function: stm32_phyintenable * * Description: * Enable link up/down PHY interrupts. The interrupt protocol is like this: * * - Interrupt status is cleared when the interrupt is enabled. * - Interrupt occurs. Interrupt is disabled (at the processor level) when * is received. * - Interrupt status is cleared when the interrupt is re-enabled. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * OK on success; Negated errno (-ETIMEDOUT) on failure. * ****************************************************************************/ #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) static int stm32_phyintenable(struct stm32_ethmac_s *priv) { #warning Missing logic return -ENOSYS; } #endif /**************************************************************************** * Function: stm32_phyread * * Description: * Read a PHY register. * * Parameters: * phydevaddr - The PHY device address * phyregaddr - The PHY register address * value - The location to return the 16-bit PHY register value. * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ static int stm32_phyread(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t *value) { volatile uint32_t timeout; uint32_t regval; /* Configure the MACMDIOAR register, preserving CSR Clock Range CR[3:0] * bits */ regval = stm32_getreg(STM32_ETH_MACMDIOAR); regval &= ETH_MACMDIOAR_CR_MASK; /* Set the PHY device address, PHY register address, and set the buy bit. * the ETH_MACMDIOAR_GOC == 3, indicating a read operation. */ regval |= (((uint32_t)phydevaddr << ETH_MACMDIOAR_PA_SHIFT) & ETH_MACMDIOAR_PA_MASK); regval |= (((uint32_t)phyregaddr << ETH_MACMDIOAR_RDA_SHIFT) & ETH_MACMDIOAR_RDA_MASK); regval |= ETH_MACMDIOAR_MB | ETH_MACMDIOAR_GOC_READ; stm32_putreg(regval, STM32_ETH_MACMDIOAR); /* Wait for the transfer to complete */ for (timeout = 0; timeout < PHY_READ_TIMEOUT; timeout++) { if ((stm32_getreg(STM32_ETH_MACMDIOAR) & ETH_MACMDIOAR_MB) == 0) { *value = (uint16_t)stm32_getreg(STM32_ETH_MACMDIODR); return OK; } } ninfo("MII transfer timed out: phydevaddr: %04x phyregaddr: %04x\n", phydevaddr, phyregaddr); return -ETIMEDOUT; } /**************************************************************************** * Function: stm32_phywrite * * Description: * Write to a PHY register. * * Parameters: * phydevaddr - The PHY device address * phyregaddr - The PHY register address * value - The 16-bit value to write to the PHY register value. * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ static int stm32_phywrite(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t set, uint16_t clear) { volatile uint32_t timeout; uint32_t regval; uint16_t value; /* Configure the MACMDIOAR register, preserving CSR Clock Range CR[3:0] * bits */ regval = stm32_getreg(STM32_ETH_MACMDIOAR); regval &= ETH_MACMDIOAR_CR_MASK; /* Read the existing register value, if clear mask is given */ if (clear != 0xffff) { if (stm32_phyread(phydevaddr, phyregaddr, &value) != OK) { return -ETIMEDOUT; } value &= ~clear; value |= set; } else { value = set; } /* Set the PHY device address, PHY register address, and set the busy bit. * the ETH_MACMDIOAR_GOC == 1, indicating a write operation. */ regval |= (((uint32_t)phydevaddr << ETH_MACMDIOAR_PA_SHIFT) & ETH_MACMDIOAR_PA_MASK); regval |= (((uint32_t)phyregaddr << ETH_MACMDIOAR_RDA_SHIFT) & ETH_MACMDIOAR_RDA_MASK); regval |= (ETH_MACMDIOAR_MB | ETH_MACMDIOAR_GOC_WRITE); /* Write the value into the MACMDIODR register before setting the new * MACMDIOAR register value. */ stm32_putreg(value, STM32_ETH_MACMDIODR); stm32_putreg(regval, STM32_ETH_MACMDIOAR); /* Wait for the transfer to complete */ for (timeout = 0; timeout < PHY_WRITE_TIMEOUT; timeout++) { if ((stm32_getreg(STM32_ETH_MACMDIOAR) & ETH_MACMDIOAR_MB) == 0) { return OK; } } ninfo("MII transfer timed out: phydevaddr: %04x phyregaddr: %04x value: " "%04x\n", phydevaddr, phyregaddr, value); return -ETIMEDOUT; } /**************************************************************************** * Function: stm32_dm9161 * * Description: * Special workaround for the Davicom DM9161 PHY is required. On power, * up, the PHY is not usually configured correctly but will work after * a powered-up reset. This is really a workaround for some more * fundamental issue with the PHY clocking initialization, but the * root cause has not been studied (nor will it be with this workaround). * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_ETH0_PHY_DM9161 static inline int stm32_dm9161(struct stm32_ethmac_s *priv) { uint16_t phyval; int ret; /* Read the PHYID1 register; A failure to read the PHY ID is one * indication that check if the DM9161 PHY CHIP is not ready. */ ret = stm32_phyread(CONFIG_STM32H7_PHYADDR, MII_PHYID1, &phyval); if (ret < 0) { nerr("ERROR: Failed to read the PHY ID1: %d\n", ret); return ret; } /* If we failed to read the PHY ID1 register, the reset the MCU to * recover */ else if (phyval == 0xffff) { up_systemreset(); } ninfo("PHY ID1: 0x%04X\n", phyval); /* Now check the "DAVICOM Specified Configuration Register (DSCR)", * Register 16 */ ret = stm32_phyread(CONFIG_STM32H7_PHYADDR, 16, &phyval); if (ret < 0) { nerr("ERROR: Failed to read the PHY Register 0x10: %d\n", ret); return ret; } /* Bit 8 of the DSCR register is zero, then the DM9161 has not selected * RMII. If RMII is not selected, then reset the MCU to recover. */ else if ((phyval & (1 << 8)) == 0) { up_systemreset(); } return OK; } #endif /**************************************************************************** * Function: stm32_phyregdump * * Description: * Dump the MII registers 0-31 * * Parameters: * * Returned Value: * * Assumptions: * ****************************************************************************/ #ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG static void stm32_phyregdump() { uint16_t phyval; int ret; int i; for (i = 0; i < 0x20; i++) { ret = stm32_phyread(CONFIG_STM32H7_PHYADDR, i, &phyval); if (ret < 0) { nerr("ERROR: Failed to read reg: 0%2x\n", i); } else { ninfo("Phy reg 0x%02x == 0x%x\n", i, phyval); } } } #endif /**************************************************************************** * Function: stm32_phyinit * * Description: * Configure the PHY and determine the link speed/duplex. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ static int stm32_phyinit(struct stm32_ethmac_s *priv) { #ifdef CONFIG_STM32H7_AUTONEG volatile uint32_t timeout; #endif uint32_t regval; uint16_t phyval; int ret; /* Assume 10MBps and half duplex */ priv->mbps100 = 0; priv->fduplex = 0; /* Setup up PHY clocking by setting the CR field in the MACMDIOAR reg */ regval = stm32_getreg(STM32_ETH_MACMDIOAR); regval &= ~ETH_MACMDIOAR_CR_MASK; regval |= ETH_MACMDIOAR_CR; stm32_putreg(regval, STM32_ETH_MACMDIOAR); /* Put the PHY in reset mode */ ret = stm32_phywrite(CONFIG_STM32H7_PHYADDR, MII_MCR, MII_MCR_RESET, MII_MCR_RESET); if (ret < 0) { nerr("ERROR: Failed to reset the PHY: %d\n", ret); return ret; } up_mdelay(PHY_RESET_DELAY); #ifdef CONFIG_STM32H7_ETHMAC_REGDEBUG stm32_phyregdump(); #endif /* Special workaround for the Davicom DM9161 PHY is required. */ #ifdef CONFIG_ETH0_PHY_DM9161 ret = stm32_dm9161(priv); if (ret < 0) { return ret; } #endif /* Perform auto-negotiation if so configured */ ninfo("stm32_phyinit beforeautoneg\n"); #ifdef CONFIG_STM32H7_AUTONEG /* Enable auto-negotiation */ ninfo("stm32_phyinit autoneg\n"); ret = stm32_phywrite(CONFIG_STM32H7_PHYADDR, MII_MCR, MII_MCR_ANENABLE, MII_MCR_ANENABLE); if (ret < 0) { nerr("ERROR: Failed to enable auto-negotiation: %d\n", ret); return ret; } /* Wait until auto-negotiation completes */ for (timeout = 0; timeout < PHY_RETRY_TIMEOUT; timeout++) { ret = stm32_phyread(CONFIG_STM32H7_PHYADDR, MII_MSR, &phyval); if (ret < 0) { nerr("ERROR: Failed to read the PHY MSR: %d\n", ret); return ret; } else if ((phyval & MII_MSR_ANEGCOMPLETE) != 0) { break; } } if (timeout >= PHY_RETRY_TIMEOUT) { nerr("ERROR: Timed out waiting for auto-negotiation\n"); /* Use default settings, if available */ phyval = 0; #ifdef CONFIG_STM32H7_ETHFD phyval |= MII_MCR_FULLDPLX; #endif #ifdef CONFIG_STM32H7_ETH100MBPS phyval |= MII_MCR_SPEED100; #endif ninfo("autoneg timeout PHYSR[%d]: %04x\n", CONFIG_STM32H7_PHYSR, phyval); ret = stm32_phywrite(CONFIG_STM32H7_PHYADDR, MII_MCR, phyval, 0xffff); if (ret < 0) { nerr("ERROR: Failed to write the PHY MCR: %d\n", ret); return ret; } up_mdelay(PHY_CONFIG_DELAY); /* Remember the selected speed and duplex modes */ #ifdef CONFIG_STM32H7_ETHFD priv->fduplex = 1; #endif #ifdef CONFIG_STM32H7_ETH100MBPS priv->mbps100 = 1; #endif } else { /* Read the result of the auto-negotiation from the PHY-specific register */ ret = stm32_phyread(CONFIG_STM32H7_PHYADDR, CONFIG_STM32H7_PHYSR, &phyval); if (ret < 0) { nerr("ERROR: Failed to read PHY status register\n"); return ret; } /* Remember the selected speed and duplex modes */ ninfo("PHYSR[%d]: %04x\n", CONFIG_STM32H7_PHYSR, phyval); /* Different PHYs present speed and mode information in different ways. IF * This CONFIG_STM32H7_PHYSR_ALTCONFIG is selected, this indicates that * the PHY represents speed and mode information are combined, for * example, with separate bits for 10HD, 100HD, 10FD and 100FD. */ #ifdef CONFIG_STM32H7_PHYSR_ALTCONFIG switch (phyval & CONFIG_STM32H7_PHYSR_ALTMODE) { default: nerr("ERROR: Unrecognized PHY status setting\n"); /* Falls through */ case CONFIG_STM32H7_PHYSR_10HD: priv->fduplex = 0; priv->mbps100 = 0; break; case CONFIG_STM32H7_PHYSR_100HD: priv->fduplex = 0; priv->mbps100 = 1; break; case CONFIG_STM32H7_PHYSR_10FD: priv->fduplex = 1; priv->mbps100 = 0; break; case CONFIG_STM32H7_PHYSR_100FD: priv->fduplex = 1; priv->mbps100 = 1; break; } /* Different PHYs present speed and mode information in different ways. * Some will present separate information for speed and mode (this is the * default). Those PHYs, for example, may provide a 10/100 Mbps * indication and a separate full/half duplex indication. */ #else ninfo("stm32_phyinit no autoneg\n"); if ((phyval & CONFIG_STM32H7_PHYSR_MODE) == CONFIG_STM32H7_PHYSR_FULLDUPLEX) { priv->fduplex = 1; } if ((phyval & CONFIG_STM32H7_PHYSR_SPEED) == CONFIG_STM32H7_PHYSR_100MBPS) { priv->mbps100 = 1; } #endif } #else /* Auto-negotiation not selected */ phyval = 0; #ifdef CONFIG_STM32H7_ETHFD phyval |= MII_MCR_FULLDPLX; #endif #ifdef CONFIG_STM32H7_ETH100MBPS phyval |= MII_MCR_SPEED100; #endif ninfo("no autoneg PHYSR[%d]: %04x\n", CONFIG_STM32H7_PHYSR, phyval); ret = stm32_phywrite(CONFIG_STM32H7_PHYADDR, MII_MCR, phyval, 0xffff); if (ret < 0) { nerr("ERROR: Failed to write the PHY MCR: %d\n", ret); return ret; } up_mdelay(PHY_CONFIG_DELAY); /*Deian:*/ //step1: set swr bit and wait with timeout (5 seconds) ninfo("Deian: reseting swr and waiting 5 seconds (before setting loopback mode on)\n"); stm32_ethreset(5000000); //step2: set loopback mode ninfo("Deian: set loopback mode on\n"); stm32_phywrite(CONFIG_STM32H7_PHYADDR,MII_MCR, MII_MCR_LOOPBACK, MII_MCR_LOOPBACK); //step3: set swr bit (no timeout) ninfo("Deian: reseting swr and waiting 100 seconds (after setting loopback mode on)\n"); stm32_ethreset(100000000); //step4: unset loopback ninfo("Deian: unset loopback mode\n"); uint16_t val; stm32_phyread(CONFIG_STM32H7_PHYADDR,MII_MCR, &val); ninfo("Deian: phyval:%x", val); val &= 0xbfff; //(resets bit 14, LP) ninfo("Deian: modified phyval:%x", val); stm32_phywrite(CONFIG_STM32H7_PHYADDR,MII_MCR, val, 0xffff); ninfo("Deian: set loopback back to normal"); /* Remember the selected speed and duplex modes */ #ifdef CONFIG_STM32H7_ETHFD priv->fduplex = 1; #endif #ifdef CONFIG_STM32H7_ETH100MBPS priv->mbps100 = 1; #endif #endif ninfo("Duplex: %s Speed: %d MBps\n", priv->fduplex ? "FULL" : "HALF", priv->mbps100 ? 100 : 10); return OK; } /**************************************************************************** * Name: stm32_selectmii * * Description: * Selects the MII interface. * * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_STM32H7_MII static inline void stm32_selectmii(void) { uint32_t regval; regval = getreg32(STM32_SYSCFG_PMC); regval &= ~SYSCFG_PMC_EPIS_MASK; regval |= SYSCFG_PMC_EPIS_MII; putreg32(regval, STM32_SYSCFG_PMC); } #endif /**************************************************************************** * Name: stm32_selectrmii * * Description: * Selects the RMII interface. * * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ static inline void stm32_selectrmii(void) { uint32_t regval; regval = getreg32(STM32_SYSCFG_PMC); regval &= ~SYSCFG_PMC_EPIS_MASK; regval |= SYSCFG_PMC_EPIS_RMII; putreg32(regval, STM32_SYSCFG_PMC); } /**************************************************************************** * Function: stm32_ethgpioconfig * * Description: * Configure GPIOs for the Ethernet interface. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * None. * * Assumptions: * ****************************************************************************/ static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv) { /* Configure GPIO pins to support Ethernet */ #if defined(CONFIG_STM32H7_MII) || defined(CONFIG_STM32H7_RMII) /* MDC and MDIO are common to both modes */ stm32_configgpio(GPIO_ETH_MDC); stm32_configgpio(GPIO_ETH_MDIO); /* Set up the MII interface */ #if defined(CONFIG_STM32H7_MII) /* Select the MII interface */ stm32_selectmii(); /* Provide clocking via MCO, MCO1 or MCO2: * * "MCO1 (microcontroller clock output), used to output HSI, LSE, HSE or * PLL clock (through a configurable prescaler) on PA8 pin." * * "MCO2 (microcontroller clock output), used to output HSE, PLL, SYSCLK or * PLLI2S clock (through a configurable prescaler) on PC9 pin." */ # if defined(CONFIG_STM32H7_MII_MCO1) /* Configure MC01 to drive the PHY. Board logic must provide MC01 clocking * info. */ stm32_configgpio(GPIO_MCO1); stm32_mco1config(BOARD_CFGR_MC01_SOURCE, BOARD_CFGR_MC01_DIVIDER); # elif defined(CONFIG_STM32H7_MII_MCO2) /* Configure MC02 to drive the PHY. Board logic must provide MC02 clocking * info. */ stm32_configgpio(GPIO_MCO2); stm32_mco2config(BOARD_CFGR_MC02_SOURCE, BOARD_CFGR_MC02_DIVIDER); # elif defined(CONFIG_STM32H7_MII_MCO) /* Setup MCO pin for alternative usage */ stm32_configgpio(GPIO_MCO); stm32_mcoconfig(BOARD_CFGR_MCO_SOURCE); # endif /* MII interface pins (17): * * MII_TX_CLK, MII_TXD[3:0], MII_TX_EN, MII_RX_CLK, MII_RXD[3:0], * MII_RX_ER, MII_RX_DV, MII_CRS, MII_COL, MDC, MDIO */ stm32_configgpio(GPIO_ETH_MII_COL); stm32_configgpio(GPIO_ETH_MII_CRS); stm32_configgpio(GPIO_ETH_MII_RXD0); stm32_configgpio(GPIO_ETH_MII_RXD1); #ifdef CONFIG_BROOKS_MULTI_BOARD_SUPPORT stm32_configgpio(mMultiBoardSupportPins.Eth_Mii_RxD2); #else stm32_configgpio(GPIO_ETH_MII_RXD2); #endif /* CONFIG_BROOKS_MULTI_BOARD_SUPPORT */ stm32_configgpio(GPIO_ETH_MII_RXD3); stm32_configgpio(GPIO_ETH_MII_RX_CLK); stm32_configgpio(GPIO_ETH_MII_RX_DV); stm32_configgpio(GPIO_ETH_MII_RX_ER); stm32_configgpio(GPIO_ETH_MII_TXD0); stm32_configgpio(GPIO_ETH_MII_TXD1); stm32_configgpio(GPIO_ETH_MII_TXD2); stm32_configgpio(GPIO_ETH_MII_TXD3); stm32_configgpio(GPIO_ETH_MII_TX_CLK); stm32_configgpio(GPIO_ETH_MII_TX_EN); /* Set up the RMII interface. */ #elif defined(CONFIG_STM32H7_RMII) /* Select the RMII interface */ stm32_selectrmii(); /* Provide clocking via MCO, MCO1 or MCO2: * * "MCO1 (microcontroller clock output), used to output HSI, LSE, HSE or * PLL clock (through a configurable prescaler) on PA8 pin." * * "MCO2 (microcontroller clock output), used to output HSE, PLL, SYSCLK or * PLLI2S clock (through a configurable prescaler) on PC9 pin." */ # if defined(CONFIG_STM32H7_RMII_MCO1) /* Configure MC01 to drive the PHY. Board logic must provide MC01 clocking * info. */ stm32_configgpio(GPIO_MCO1); stm32_mco1config(BOARD_CFGR_MC01_SOURCE, BOARD_CFGR_MC01_DIVIDER); # elif defined(CONFIG_STM32H7_RMII_MCO2) /* Configure MC02 to drive the PHY. Board logic must provide MC02 clocking * info. */ stm32_configgpio(GPIO_MCO2); stm32_mco2config(BOARD_CFGR_MC02_SOURCE, BOARD_CFGR_MC02_DIVIDER); # elif defined(CONFIG_STM32H7_RMII_MCO) /* Setup MCO pin for alternative usage */ stm32_configgpio(GPIO_MCO); stm32_mcoconfig(BOARD_CFGR_MCO_SOURCE); # endif /* RMII interface pins (7): * * RMII_TXD[1:0], RMII_TX_EN, RMII_RXD[1:0], RMII_CRS_DV, MDC, MDIO, * RMII_REF_CLK */ stm32_configgpio(GPIO_ETH_RMII_CRS_DV); stm32_configgpio(GPIO_ETH_RMII_REF_CLK); stm32_configgpio(GPIO_ETH_RMII_RXD0); stm32_configgpio(GPIO_ETH_RMII_RXD1); stm32_configgpio(GPIO_ETH_RMII_TXD0); stm32_configgpio(GPIO_ETH_RMII_TXD1); stm32_configgpio(GPIO_ETH_RMII_TX_EN); #endif #endif #ifdef CONFIG_STM32H7_ETH_PTP /* Enable pulse-per-second (PPS) output signal */ stm32_configgpio(GPIO_ETH_PPS_OUT); #endif } /**************************************************************************** * Function: stm32_ethreset * * Description: * Reset the Ethernet block. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * None. * * Assumptions: * ****************************************************************************/ static void stm32_ethreset(long swrBitTimeout) { uint32_t regval; volatile uint32_t timeout; ninfo("stm32_ethreset start\n"); /* Reset the Ethernet on the AHB1 bus */ regval = stm32_getreg(STM32_RCC_AHB1RSTR); regval |= RCC_AHB1RSTR_ETH1MACRST; stm32_putreg(regval, STM32_RCC_AHB1RSTR); regval &= ~RCC_AHB1RSTR_ETH1MACRST; stm32_putreg(regval, STM32_RCC_AHB1RSTR); /* Perform a software reset by setting the SR bit in the DMAMR register. * This Resets all MAC subsystem internal registers and logic. After this * reset all the registers holds their reset values. */ regval = stm32_getreg(STM32_ETH_DMAMR); regval |= ETH_DMAMR_SWR; stm32_putreg(regval, STM32_ETH_DMAMR); /* Wait for software reset to complete. The SR bit is cleared * automatically after the reset operation has completed in all of the * core clock domains. */ timeout = swrBitTimeout; ninfo("stm32_ethreset waiting for swr[us]:%d\n",timeout); while (timeout-- && (stm32_getreg(STM32_ETH_DMAMR) & ETH_DMAMR_SWR) != 0) { up_udelay(1); } ninfo("\nstm32_ethreset done waiting\n"); /* According to the spec, these need to be done before creating * the descriptor lists, so initialize these already here */ /* Set up the DMAMR register */ regval = stm32_getreg(STM32_ETH_DMAMR); regval &= ~DMAMR_CLEAR_MASK; regval |= DMAMR_SET_MASK; stm32_putreg(regval, STM32_ETH_DMAMR); /* Set up the DMASBMR register */ regval = stm32_getreg(STM32_ETH_DMASBMR); regval &= ~DMASBMR_CLEAR_MASK; regval |= DMASBMR_SET_MASK; stm32_putreg(regval, STM32_ETH_DMASBMR); ninfo("stm32_ethreset done\n"); } /**************************************************************************** * Function: stm32_macconfig * * Description: * Configure the Ethernet MAC for DMA operation. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ static int stm32_macconfig(struct stm32_ethmac_s *priv) { uint32_t regval; /* DMA Configuration */ /* Set up the ETH_DMACCR register */ regval = stm32_getreg(STM32_ETH_DMACCR); regval &= ~DMACCR_CLEAR_MASK; regval |= DMACCR_SET_MASK; stm32_putreg(regval, STM32_ETH_DMACCR); /* Set up the ETH_DMACTXCR register */ regval = stm32_getreg(STM32_ETH_DMACTXCR); regval &= ~DMACTXCR_CLEAR_MASK; regval |= DMACTXCR_SET_MASK; stm32_putreg(regval, STM32_ETH_DMACTXCR); /* Set up the ETH_DMACRXCR register */ regval = stm32_getreg(STM32_ETH_DMACRXCR); regval &= ~DMACRXCR_CLEAR_MASK; regval |= DMACRXCR_SET_MASK; stm32_putreg(regval, STM32_ETH_DMACRXCR); /* MTL configuration */ /* Set up the MTLTXQOMR register */ regval = stm32_getreg(STM32_ETH_MTLTXQOMR); regval &= ~MTLTXQOMR_CLEAR_MASK; regval |= MTLTXQOMR_SET_MASK; stm32_putreg(regval, STM32_ETH_MTLTXQOMR); /* Set up the MTLRXQOMR register */ regval = stm32_getreg(STM32_ETH_MTLRXQOMR); regval &= ~MTLRXQOMR_CLEAR_MASK; regval |= MTLRXQOMR_SET_MASK; stm32_putreg(regval, STM32_ETH_MTLRXQOMR); /* MAC Configuration */ /* Set up the MACCR register */ regval = stm32_getreg(STM32_ETH_MACCR); regval &= ~MACCR_CLEAR_BITS; regval |= MACCR_SET_BITS; if (priv->fduplex) { /* Set the DM bit for full duplex support */ regval |= ETH_MACCR_DM; } if (priv->mbps100) { /* Set the FES bit for 100Mbps fast ethernet support */ regval |= ETH_MACCR_FES; } stm32_putreg(regval, STM32_ETH_MACCR); /* Set up the MACPFR register */ regval = stm32_getreg(STM32_ETH_MACPFR); regval &= ~MACPFR_CLEAR_BITS; regval |= MACPFR_SET_BITS; stm32_putreg(regval, STM32_ETH_MACPFR); /* Set up the MACHT0R and MACHT1R registers */ stm32_putreg(0, STM32_ETH_MACHT0R); stm32_putreg(0, STM32_ETH_MACHT1R); /* Setup up the MACQTXFCR register */ regval = stm32_getreg(STM32_ETH_MACQTXFCR); regval &= ~MACQTXFCR_CLEAR_MASK; regval |= MACQTXFCR_SET_MASK; stm32_putreg(regval, STM32_ETH_MACQTXFCR); /* Setup up the MACRXFCR register */ regval = stm32_getreg(STM32_ETH_MACRXFCR); regval &= ~MACRXFCR_CLEAR_MASK; regval |= MACRXFCR_SET_MASK; stm32_putreg(regval, STM32_ETH_MACRXFCR); /* Setup up the MACVTR register */ stm32_putreg(0, STM32_ETH_MACVTR); return OK; } /**************************************************************************** * Function: stm32_macaddress * * Description: * Configure the selected MAC address. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ static void stm32_macaddress(struct stm32_ethmac_s *priv) { struct net_driver_s *dev = &priv->dev; uint32_t regval; ninfo("%s MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", dev->d_ifname, dev->d_mac.ether.ether_addr_octet[0], dev->d_mac.ether.ether_addr_octet[1], dev->d_mac.ether.ether_addr_octet[2], dev->d_mac.ether.ether_addr_octet[3], dev->d_mac.ether.ether_addr_octet[4], dev->d_mac.ether.ether_addr_octet[5]); /* Set the MAC address high register */ regval = ((uint32_t)dev->d_mac.ether.ether_addr_octet[5] << 8) | (uint32_t)dev->d_mac.ether.ether_addr_octet[4]; stm32_putreg(regval, STM32_ETH_MACA0HR); /* Set the MAC address low register */ regval = ((uint32_t)dev->d_mac.ether.ether_addr_octet[3] << 24) | ((uint32_t)dev->d_mac.ether.ether_addr_octet[2] << 16) | ((uint32_t)dev->d_mac.ether.ether_addr_octet[1] << 8) | (uint32_t)dev->d_mac.ether.ether_addr_octet[0]; stm32_putreg(regval, STM32_ETH_MACA0LR); } /**************************************************************************** * Function: stm32_ipv6multicast * * Description: * Configure the IPv6 multicast MAC address. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ #ifdef CONFIG_NET_ICMPv6 static void stm32_ipv6multicast(struct stm32_ethmac_s *priv) { struct net_driver_s *dev; uint16_t tmp16; uint8_t mac[6]; /* For ICMPv6, we need to add the IPv6 multicast address * * For IPv6 multicast addresses, the Ethernet MAC is derived by * the four low-order octets OR'ed with the MAC 33:33:00:00:00:00, * so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map * to the Ethernet MAC address 33:33:00:01:00:03. * * NOTES: This appears correct for the ICMPv6 Router Solicitation * Message, but the ICMPv6 Neighbor Solicitation message seems to * use 33:33:ff:01:00:03. */ mac[0] = 0x33; mac[1] = 0x33; dev = &priv->dev; tmp16 = dev->d_ipv6addr[6]; mac[2] = 0xff; mac[3] = tmp16 >> 8; tmp16 = dev->d_ipv6addr[7]; mac[4] = tmp16 & 0xff; mac[5] = tmp16 >> 8; ninfo("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); stm32_addmac(dev, mac); #ifdef CONFIG_NET_ICMPv6_AUTOCONF /* Add the IPv6 all link-local nodes Ethernet address. This is the * address that we expect to receive ICMPv6 Router Advertisement * packets. */ stm32_addmac(dev, g_ipv6_ethallnodes.ether_addr_octet); #endif /* CONFIG_NET_ICMPv6_AUTOCONF */ #ifdef CONFIG_NET_ICMPv6_ROUTER /* Add the IPv6 all link-local routers Ethernet address. This is the * address that we expect to receive ICMPv6 Router Solicitation * packets. */ stm32_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet); #endif /* CONFIG_NET_ICMPv6_ROUTER */ } #endif /* CONFIG_NET_ICMPv6 */ /**************************************************************************** * Function: stm32_macenable * * Description: * Enable normal MAC operation. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ static int stm32_macenable(struct stm32_ethmac_s *priv) { uint32_t regval; /* Set the MAC address */ stm32_macaddress(priv); #ifdef CONFIG_NET_ICMPv6 /* Set up the IPv6 multicast address */ stm32_ipv6multicast(priv); #endif /* Enable transmit state machine of the MAC for transmission on the MII */ regval = stm32_getreg(STM32_ETH_MACCR); regval |= ETH_MACCR_TE; stm32_putreg(regval, STM32_ETH_MACCR); /* Flush Transmit FIFO */ regval = stm32_getreg(STM32_ETH_MTLTXQOMR); regval |= ETH_MTLTXQOMR_FTQ; stm32_putreg(regval, STM32_ETH_MTLTXQOMR); /* Enable receive state machine of the MAC for reception from the MII */ /* Enables or disables the MAC reception. */ regval = stm32_getreg(STM32_ETH_MACCR); regval |= ETH_MACCR_RE; stm32_putreg(regval, STM32_ETH_MACCR); /* Start DMA transmission */ regval = stm32_getreg(STM32_ETH_DMACTXCR); regval |= ETH_DMACTXCR_ST; stm32_putreg(regval, STM32_ETH_DMACTXCR); /* Start DMA reception */ regval = stm32_getreg(STM32_ETH_DMACRXCR); regval |= ETH_DMACRXCR_SR; stm32_putreg(regval, STM32_ETH_DMACRXCR); /* Enable Ethernet DMA interrupts */ stm32_putreg(ETH_MACIER_ALLINTS, STM32_ETH_MACIER); /* Ethernet DMA supports two classes of interrupts: Normal interrupt * summary (NIS) and Abnormal interrupt summary (AIS) with a variety * individual normal and abnormal interrupting events. Here only * the normal receive event is enabled (unless DEBUG is enabled). Transmit * events will only be enabled when a transmit interrupt is expected. */ stm32_putreg((ETH_DMAINT_RECV_ENABLE | ETH_DMAINT_XMIT_ENABLE | ETH_DMAINT_ERROR_ENABLE), STM32_ETH_DMACIER); /* Clear Tx and Rx process stopped flags */ regval = stm32_getreg(STM32_ETH_DMACSR); regval |= (ETH_DMACSR_TPS | ETH_DMACSR_RPS); stm32_putreg(regval, STM32_ETH_DMACSR); return OK; } /**************************************************************************** * Function: stm32_ethconfig * * Description: * Configure the Ethernet interface for DMA operation. * * Parameters: * priv - A reference to the private driver state structure * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ static int stm32_ethconfig(struct stm32_ethmac_s *priv) { int ret; /* NOTE: The Ethernet clocks were initialized early in the boot-up * sequence in stm32_rcc.c. */ #ifdef CONFIG_STM32H7_PHYINIT /* Perform any necessary, board-specific PHY initialization */ ret = stm32_phy_boardinitialize(0); if (ret < 0) { nerr("ERROR: Failed to initialize the PHY: %d\n", ret); return ret; } #endif /* Initialize the free buffer list */ stm32_initbuffer(priv, &g_txbuffer[priv->intf * TXBUFFER_SIZE]); /* Reset the Ethernet block */ ninfo("Reset the Ethernet block\n"); stm32_ethreset(MAC_READY_USTIMEOUT); /* Initialize TX Descriptors list */ stm32_txdescinit(priv, &g_txtable[priv->intf * CONFIG_STM32H7_ETH_NTXDESC]); /* Initialize RX Descriptors list */ stm32_rxdescinit(priv, &g_rxtable[priv->intf * CONFIG_STM32H7_ETH_NRXDESC], &g_rxbuffer[priv->intf * RXBUFFER_SIZE]); /* Initialize the PHY */ ninfo("Initialize the PHY\n"); ret = stm32_phyinit(priv); if (ret < 0) { return ret; } /* Initialize the MAC and DMA */ ninfo("Initialize the MAC and DMA\n"); ret = stm32_macconfig(priv); if (ret < 0) { return ret; } /* Enable normal MAC operation */ ninfo("Enable normal operation\n"); return stm32_macenable(priv); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Function: stm32_ethinitialize * * Description: * Initialize the Ethernet driver for one interface. If the STM32 chip * supports multiple Ethernet controllers, then board specific logic * must implement arm_netinitialize() and call this function to initialize * the desired interfaces. * * Parameters: * intf - In the case where there are multiple EMACs, this value * identifies which EMAC is to be initialized. * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ #if STM32H7_NETHERNET > 1 || defined(CONFIG_NETDEV_LATEINIT) int stm32_ethinitialize(int intf) #else static inline int stm32_ethinitialize(int intf) #endif { struct stm32_ethmac_s *priv; uint8_t uid[12]; uint64_t crc; ninfo("intf: %d\n", intf); /* Get the interface structure associated with this interface number. */ DEBUGASSERT(intf < STM32H7_NETHERNET); priv = &g_stm32ethmac[intf]; /* Initialize the driver structure */ memset(priv, 0, sizeof(struct stm32_ethmac_s)); priv->dev.d_ifup = stm32_ifup; /* I/F up (new IP address) callback */ priv->dev.d_ifdown = stm32_ifdown; /* I/F down callback */ priv->dev.d_txavail = stm32_txavail; /* New TX data callback */ #ifdef CONFIG_NET_IGMP priv->dev.d_addmac = stm32_addmac; /* Add multicast MAC address */ priv->dev.d_rmmac = stm32_rmmac; /* Remove multicast MAC address */ #endif #ifdef CONFIG_NETDEV_PHY_IOCTL priv->dev.d_ioctl = stm32_ioctl; /* Support PHY ioctl() calls */ #endif priv->dev.d_private = (void *)g_stm32ethmac; /* Used to recover private state */ priv->intf = intf; /* Remember the interface number */ /* Create a watchdog for timing polling for and timing of transmissions */ priv->txpoll = wd_create(); /* Create periodic poll timer */ priv->txtimeout = wd_create(); /* Create TX timeout timer */ stm32_get_uniqueid(uid); crc = crc64(uid, 12); /* Specify as localy administrated address */ priv->dev.d_mac.ether.ether_addr_octet[0] = (crc >> 0) | 0x02; priv->dev.d_mac.ether.ether_addr_octet[0] &= ~0x1; priv->dev.d_mac.ether.ether_addr_octet[1] = crc >> 8; priv->dev.d_mac.ether.ether_addr_octet[2] = crc >> 16; priv->dev.d_mac.ether.ether_addr_octet[3] = crc >> 24; priv->dev.d_mac.ether.ether_addr_octet[4] = crc >> 32; priv->dev.d_mac.ether.ether_addr_octet[5] = crc >> 40; /* Configure GPIO pins to support Ethernet */ stm32_ethgpioconfig(priv); /* Attach the IRQ to the driver */ if (irq_attach(STM32_IRQ_ETH, stm32_interrupt, NULL)) { /* We could not attach the ISR to the interrupt */ return -EAGAIN; } #ifdef CONFIG_STM32H7_PHYINIT /* Perform any necessary, board-specific PHY initialization */ ret = stm32_phy_boardinitialize(0); if (ret < 0) { nerr("ERROR: Failed to initialize the PHY: %d\n", ret); return ret; } #endif /* Put the interface in the down state. */ stm32_ifdown(&priv->dev); /* Register the device with the OS so that socket IOCTLs can be performed */ netdev_register(&priv->dev, NET_LL_ETHERNET); return OK; } /**************************************************************************** * Function: arm_netinitialize * * Description: * This is the "standard" network initialization logic called from the * low-level initialization logic in arm_initialize.c. If STM32H7_NETHERNET * greater than one, then board specific logic will have to supply a * version of arm_netinitialize() that calls stm32_ethinitialize() with * the appropriate interface number. * * Parameters: * None. * * Returned Value: * None. * * Assumptions: * ****************************************************************************/ #if STM32H7_NETHERNET == 1 && !defined(CONFIG_NETDEV_LATEINIT) void arm_netinitialize(void) { stm32_ethinitialize(0); } #endif #endif /* STM32H7_NETHERNET > 0 && CONFIG_STM32H7_ETHMAC */